|
| 1 | +from __future__ import division |
| 2 | +''' |
| 3 | +@date: Created on 04.12.2014 |
| 4 | +@author: Hannes Bartosik, Michael Schenk |
| 5 | +
|
| 6 | +TODO: |
| 7 | + - Speed up RFBucketAperture. It is currently EXTREMELY slow. Use |
| 8 | + RectangularApertureZ as an alternative. |
| 9 | +''' |
| 10 | +cimport cython |
| 11 | +import numpy as np |
| 12 | +cimport numpy as np |
| 13 | + |
| 14 | +from . import Element |
| 15 | + |
| 16 | +from abc import ABCMeta, abstractmethod |
| 17 | + |
| 18 | +class Aperture(Element): |
| 19 | + ''' Abstract base class for Aperture elements. An aperture is |
| 20 | + generally defined as a condition on the phase space coordinates. |
| 21 | + Particles not fulfilling this condition are tagged as lost. They |
| 22 | + will be removed from the tracked particles as soon as the |
| 23 | + Particles.update_losses() method is called. ''' |
| 24 | + __metaclass__ = ABCMeta |
| 25 | + |
| 26 | + def __init__(self, apply_losses_here, *args, **kwargs): |
| 27 | + ''' The boolean apply_losses_here controls whether the |
| 28 | + Particles.update_losses(beam) method is called to relocate lost |
| 29 | + particles to the end of the bunch.u_all arrays (u = x, y, z, |
| 30 | + ...) and to actually remove them from the numpy array views |
| 31 | + beam.u and leave them in an untracked state. In case there are |
| 32 | + several Aperture elements placed at a segment boundary of the |
| 33 | + accelerator ring, apply_losses_here should only be set to True |
| 34 | + for the last one to increase performance. ''' |
| 35 | + self.apply_losses_here = apply_losses_here |
| 36 | + |
| 37 | + def track(self, beam): |
| 38 | + ''' Tag particles not passing through the aperture as lost. If |
| 39 | + there are any losses and the self.apply_losses_here is set to |
| 40 | + True, the method Particles.update_losses() is called to relocate |
| 41 | + lost particles to the end of the beam.u_all numpy arrays (u = x, |
| 42 | + y, z, ...) and to actually remove them from the numpy array |
| 43 | + views beam.u and leave them in an untracked state. Also, clean |
| 44 | + currently cached slice_sets of the bunch as losses change its |
| 45 | + state. ''' |
| 46 | + losses = self.tag_lost_particles(beam) |
| 47 | + |
| 48 | + if losses and self.apply_losses_here: |
| 49 | + beam.update_losses() |
| 50 | + beam.clean_slices() |
| 51 | + |
| 52 | + @abstractmethod |
| 53 | + def tag_lost_particles(self, beam): |
| 54 | + ''' This method is called by Aperture.track(beam) to identify |
| 55 | + particles not passing through the aperture and set their |
| 56 | + bunch.alive state to 0 (false) to mark them as lost. Return |
| 57 | + whether or not any lost particles were found. ''' |
| 58 | + pass |
| 59 | + |
| 60 | + |
| 61 | +class RectangularApertureX(Aperture): |
| 62 | + ''' Mark particles with transverse spatial coord (x) outside the |
| 63 | + interval (x_high, x_low) as lost. ''' |
| 64 | + |
| 65 | + def __init__(self, x_low, x_high, apply_losses_here=True, |
| 66 | + *args, **kwargs): |
| 67 | + ''' The arguments x_low and x_high define the interval of |
| 68 | + horizontal spatial coordinates for which particles pass through |
| 69 | + the rectangular horizontal aperture. The boolean |
| 70 | + apply_losses_here specifies whether the |
| 71 | + Particles.update_losses(beam) method should be called after |
| 72 | + tagging lost particles to relocate them to the end of the |
| 73 | + bunch.u_all arrays (u = x, y, z, ...), remove them from the |
| 74 | + views bunch.u and leave them in an untracked state. In case |
| 75 | + there are several Aperture elements placed at a segment boundary |
| 76 | + of the accelerator ring, apply_losses_here should only be set to |
| 77 | + True for the last one to increase performance. ''' |
| 78 | + self.x_low = x_low |
| 79 | + self.x_high = x_high |
| 80 | + super(RectangularApertureX, self).__init__(apply_losses_here) |
| 81 | + |
| 82 | + def tag_lost_particles(self, beam): |
| 83 | + ''' This method is called by Aperture.track(beam) to identify |
| 84 | + particles not passing through the aperture and set their |
| 85 | + bunch.alive state to 0 (false) to mark them as lost. The search |
| 86 | + for lost particles is done using a cython function. Return |
| 87 | + whether or not any lost particles were found. ''' |
| 88 | + return cytag_lost_rectangular( |
| 89 | + beam.x, beam.alive, self.x_low, self.x_high) |
| 90 | + |
| 91 | + |
| 92 | +class RectangularApertureY(Aperture): |
| 93 | + ''' Mark particles with transverse spatial coord (y) outside the |
| 94 | + interval (y_high, y_low) as lost. ''' |
| 95 | + |
| 96 | + def __init__(self, y_low, y_high, apply_losses_here=True, |
| 97 | + *args, **kwargs): |
| 98 | + ''' The arguments y_low and y_high define the interval of |
| 99 | + vertical spatial coordinates for which particles pass through |
| 100 | + the rectangular vertical aperture. The boolean apply_losses_here |
| 101 | + specifies whether the Particles.update_losses(beam) method |
| 102 | + should be called after tagging lost particles to relocate them |
| 103 | + to the end of the bunch.u_all arrays (u = x, y, z, ...), remove |
| 104 | + them from the views bunch.u and leave them in an untracked |
| 105 | + state. In case there are several Aperture elements placed at a |
| 106 | + segment boundary of the accelerator ring, apply_losses_here |
| 107 | + should only be set to True for the last one to increase |
| 108 | + performance. ''' |
| 109 | + self.y_low = y_low |
| 110 | + self.y_high = y_high |
| 111 | + super(RectangularApertureY, self).__init__(apply_losses_here) |
| 112 | + |
| 113 | + def tag_lost_particles(self, beam): |
| 114 | + ''' This method is called by Aperture.track(beam) to identify |
| 115 | + particles not passing through the aperture and set their |
| 116 | + bunch.alive state to 0 (false) to mark them as lost. The search |
| 117 | + for lost particles is done using a cython function. Return |
| 118 | + whether or not any lost particles were found. ''' |
| 119 | + return cytag_lost_rectangular( |
| 120 | + beam.y, beam.alive, self.y_low, self.y_high) |
| 121 | + |
| 122 | + |
| 123 | +class RectangularApertureZ(Aperture): |
| 124 | + ''' Mark particles with longitudinal spatial coord (z) outside the |
| 125 | + interval (z_high, z_low) as lost. ''' |
| 126 | + |
| 127 | + def __init__(self, z_low, z_high, apply_losses_here=True, |
| 128 | + *args, **kwargs): |
| 129 | + ''' The arguments z_low and z_high define the interval of |
| 130 | + longitudinal spatial coordinates for which particles pass |
| 131 | + through the rectangular longitudinal aperture. The boolean |
| 132 | + apply_losses_here specifies whether the |
| 133 | + Particles.update_losses(beam) method should be called after |
| 134 | + tagging lost particles to relocate them to the end of the |
| 135 | + bunch.u_all arrays (u = x, y, z, ...), remove them from the |
| 136 | + views bunch.u and leave them in an untracked state. In case |
| 137 | + there are several Aperture elements placed at a segment boundary |
| 138 | + of the accelerator ring, apply_losses_here should only be set to |
| 139 | + True for the last one to increase performance. ''' |
| 140 | + self.z_low = z_low |
| 141 | + self.z_high = z_high |
| 142 | + super(RectangularApertureZ, self).__init__(apply_losses_here) |
| 143 | + |
| 144 | + def tag_lost_particles(self, beam): |
| 145 | + ''' This method is called by Aperture.track(beam) to identify |
| 146 | + particles not passing through the aperture and set their |
| 147 | + bunch.alive state to 0 (false) to mark them as lost. The |
| 148 | + search for lost particles is done using a cython function. |
| 149 | + Return whether or not any lost particles were found. ''' |
| 150 | + return cytag_lost_rectangular( |
| 151 | + beam.z, beam.alive, self.z_low, self.z_high) |
| 152 | + |
| 153 | + |
| 154 | +class CircularApertureXY(Aperture): |
| 155 | + ''' Mark particles with transverse spatial coords (x, y) outside a |
| 156 | + circle of specified radius, i.e. x**2 + y**2 > radius**2, as lost. |
| 157 | + ''' |
| 158 | + |
| 159 | + def __init__(self, radius, apply_losses_here=True, *args, **kwargs): |
| 160 | + ''' The argument radius defines the radius of the circular |
| 161 | + (transverse) aperture. The argument apply_losses_here specifies |
| 162 | + whether the Particles.update_losses(beam) method should be |
| 163 | + called after tagging lost particles to relocate them to the end |
| 164 | + of the bunch.u_all arrays (u = x, y, z, ...), remove them from |
| 165 | + the views bunch.u and leave them in an untracked state. In case |
| 166 | + there are several Aperture elements placed at a segment boundary |
| 167 | + of the accelerator ring, apply_losses_here should only be set to |
| 168 | + True for the last one to increase performance. ''' |
| 169 | + self.radius_square = radius * radius |
| 170 | + super(CircularApertureXY, self).__init__(apply_losses_here) |
| 171 | + |
| 172 | + def tag_lost_particles(self, beam): |
| 173 | + ''' This method is called by Aperture.track(beam) to identify |
| 174 | + particles not passing through the aperture and set their |
| 175 | + bunch.alive state to 0 (false) to mark them as lost. The search |
| 176 | + for lost particles is done using a cython function. Return |
| 177 | + whether or not any lost particles were found. ''' |
| 178 | + return cytag_lost_circular( |
| 179 | + beam.x, beam.y, beam.alive, self.radius_square) |
| 180 | + |
| 181 | + |
| 182 | +class RFBucketAperture(Aperture): |
| 183 | + ''' Mark particles with longitudinal phase space coords (z, dp) |
| 184 | + outside the accepted region as lost. |
| 185 | +
|
| 186 | + NOTE: THE CURRENT IMPLEMENTATION IS EXTREMELY SLOW! For 1e6 |
| 187 | + macroparticles, executing it once takes 120ms. Hence, as an |
| 188 | + alternative, one may use the RectangularApertureZ using the z-limits |
| 189 | + of the RFBucket. Like this, particles leaking from the RFBucket will |
| 190 | + at some point be marked lost and finally removed as well. ''' |
| 191 | + |
| 192 | + def __init__(self, is_accepted, apply_losses_here=True, |
| 193 | + *args, **kwargs): |
| 194 | + ''' The argument is_accepted takes a reference to a function of |
| 195 | + the form is_accepted(z, dp) returning a boolean array saying |
| 196 | + whether the pair (z, dp) is in- or outside the specified region |
| 197 | + of the longitudinal phase space. Usually, is_accepted can be |
| 198 | + generated either using the RFSystems.RFBucket.make_is_accepted |
| 199 | + or using directly RFSystems.RFBucket.is_in_separatrix. The |
| 200 | + argument apply_losses_here specifies whether the |
| 201 | + Particles.update_losses(beam) method should be called after |
| 202 | + tagging lost particles to relocate them to the end of the |
| 203 | + bunch.u_all arrays (u = x, y, z, ...), remove them from the |
| 204 | + views bunch.u and leave them in an untracked state. In case |
| 205 | + there are several Aperture elements placed at a segment boundary |
| 206 | + of the accelerator ring, apply_losses_here should only be set to |
| 207 | + True for the last one to increase performance. ''' |
| 208 | + self.is_accepted = is_accepted |
| 209 | + super(RFBucketAperture, self).__init__(apply_losses_here) |
| 210 | + |
| 211 | + def tag_lost_particles(self, beam): |
| 212 | + ''' This method is called by Aperture.track(beam) to identify |
| 213 | + particles not passing through the aperture and set their |
| 214 | + bunch.alive state to 0 (false) to mark them as lost. The |
| 215 | + search for lost particles is done using a cython function. |
| 216 | + Return whether or not any lost particles were found. ''' |
| 217 | + mask_lost = ~self.is_accepted(beam.z, beam.dp) |
| 218 | + beam.alive[mask_lost] = 0 |
| 219 | + return np.sum(beam.alive) |
| 220 | + |
| 221 | + |
| 222 | +''' Cython functions for fast id and tagging of lost particles. ''' |
| 223 | + |
| 224 | +@cython.boundscheck(False) |
| 225 | +@cython.wraparound(False) |
| 226 | +cpdef cytag_lost_rectangular(double[::1] u, unsigned int[::1] alive, |
| 227 | + double low_lim, double high_lim): |
| 228 | + ''' Cython function for fast identification and tagging of particles |
| 229 | + lost at a rectangular aperture element, i.e. it tags particles with |
| 230 | + a spatial coord u (beam.x, beam.y or beam.z) lying outside the |
| 231 | + interval (low_lim, high_lim) as lost. Returns whether or not any |
| 232 | + lost particles were found. ''' |
| 233 | + cdef unsigned int n = alive.shape[0] |
| 234 | + cdef unsigned int losses = 0 |
| 235 | + cdef unsigned int i |
| 236 | + for i in xrange(n): |
| 237 | + if u[i] < low_lim or u[i] > high_lim: |
| 238 | + alive[i] = 0 |
| 239 | + losses = 1 |
| 240 | + return losses |
| 241 | + |
| 242 | +@cython.boundscheck(False) |
| 243 | +@cython.wraparound(False) |
| 244 | +cpdef cytag_lost_circular( |
| 245 | + double[::1] u, double[::1] v, unsigned int[::1] alive, |
| 246 | + double radius_square): |
| 247 | + ''' Cython function for fast identification and tagging of particles |
| 248 | + lost at a circular transverse aperture element of a given radius, |
| 249 | + i.e. it tags particles with spatial coords u, v (usually (beam.x, |
| 250 | + beam.y)) fulfilling u**2 + v**2 > radius_square as lost. Returns |
| 251 | + whether or not any lost particles were found. ''' |
| 252 | + cdef unsigned int n = alive.shape[0] |
| 253 | + cdef unsigned int losses = 0 |
| 254 | + cdef unsigned int i |
| 255 | + for i in xrange(n): |
| 256 | + if (u[i]*u[i] + v[i]*v[i]) > radius_square: |
| 257 | + alive[i] = 0 |
| 258 | + losses = 1 |
| 259 | + return losses |
0 commit comments