예제 #1
0
class Photons():
    """
    Integrate over the beam and create the photons
    """
    def __init__(self):
        pass


    def initialize(self, lattice):
        # read settings
        settings = Settings()['generator']['photons']
        self._enabled = settings['enabled']
        self._full_events = settings['full_events']
        self._nth_step = settings['nth_step']
        self._time = settings['time']
        self._energy_cutoff = settings['energy_cutoff']
        self._sigma_h = settings['sigma']['horizontal']
        self._sigma_v = settings['sigma']['vertical']
        self._stepsize_h = 2.0 * self._sigma_h / settings['steps']['horizontal']
        self._stepsize_v = 2.0 * self._sigma_v / settings['steps']['vertical']
        self._crossing_angle = Settings()['machine']['crossing_angle']
        
        self._region_enabled = settings['region']['enabled']
        if settings['region']['range'][0] < settings['region']['range'][1]:
            self._region_left = settings['region']['range'][0]
            self._region_right = settings['region']['range'][1]
        else:
            self._region_left = settings['region']['range'][1]
            self._region_right = settings['region']['range'][0]

        self._target_zone_enabled = settings['target_zone']['enabled']
        self._target_zone_radius = settings['target_zone']['radius']
        self._target_zone_boundary = settings['target_zone']['boundary']

        # create synchrotron radiation power spectrum PDF
        self._spectrum = Spectrum()
        self._spectrum.initialize(settings['spectrum']['resolution'],
                                  settings['spectrum']['cutoff'],
                                  settings['spectrum']['seed'],
                                  settings['spectrum']['interpolation'])

        # internal parameters
        self._lattice = lattice
        self._call_count = 0
        self._dl = 0.0

        # internal constants and pre-calculated factors
        self._alpha = 1.0 / 137.035999074
        self._speed_of_light = 2.99792458e8 # [m/s]
        self._hbar = 6.58211928e-25 # [GeV s]
        self._gamma = Settings()['machine']['beam_energy'] / 510.998928e-6
        self._current = Settings()['machine']['beam_current'] * 6.241508e18
        self._num_photon_factor = (5.0 / (2.0*math.sqrt(3))) * \
                                  self._gamma * self._alpha * \
                                  self._current * self._time
        self._crit_e_factor = 3.0 / 2.0 * self._speed_of_light * \
                              self._hbar * (self._gamma**3)


    def create(self, step, beam, output, hepevt):
        if not self._enabled:
            return

        # accumulate steps only inside magnets and, if enabled,
        # inside the region
        if (not step.in_vacuum) and \
           (not self._region_enabled or \
            (self._region_enabled and \
             step.s0ip >= self._region_left and step.s0ip <= self._region_right)):
            self._dl += step.dl
            self._call_count += 1

        # radiate photons if the n-th step is reached,
        # the current step is on a magnet to vacuum boundary,
        # or the region is enabled and the step leaves the region
        if (self._call_count >= self._nth_step) or \
           (self._call_count > 0 and step.on_boundary) or \
           (self._region_enabled and self._call_count > 0 and step.ds < 0.0 and \
            step.s0ip <= self._region_left) or \
           (self._region_enabled and self._call_count > 0 and step.ds > 0.0 and \
            step.s0ip >= self._region_right):
            self._integrate_beam(math.fabs(self._dl),
                                 step, beam,
                                 output, hepevt)
            self._dl = 0.0
            self._call_count = 0


    def write_spectrum(self, output):
        self._spectrum.write(output)


    def _intersect_target_zone(self, vertex, direction):
        """
        Target zone is an z-axis aligned cylinder with an inner and an outer
        radius and a lower and upper boundary. This code intersects a line with
        the cylinder, not a ray! That's ok for the SyncRad Generator, as this
        shouldn't introduce too many false intersections as long as the generation
        is done in a way such that the photons travel towards the IP.
        """

        # calculate slopes
        if math.fabs(direction[2]) > 0.0000000001:
            slope_xz = direction[0] / direction[2]
            slope_yz = direction[1] / direction[2]
        else:
            slope_xz = 0.0
            slope_yz = 0.0

        # calculate the distance from the z-axis (radius) that the ray has at the
        # lower and upper z-boundary of the cylinder.
        def calc_radius2(boundary):
            x_b = slope_xz*(boundary-vertex[2]) + vertex[0]
            y_b = slope_yz*(boundary-vertex[2]) + vertex[1]
            return x_b**2 + y_b**2

        r_low = calc_radius2(self._target_zone_boundary[0])
        r_up  = calc_radius2(self._target_zone_boundary[1])

        # since it is a z-axis aligned cylinder, the radius can simply be compared
        # to the cylinder radii, in order to check for the intersection of the ray.
        radius2 = [self._target_zone_radius[0]**2, self._target_zone_radius[1]**2]
        return ((r_low < radius2[1]) and (r_up  > radius2[0])) or \
               ((r_up  < radius2[1]) and (r_low > radius2[0]))


    def _integrate_beam(self, dl, step, beam, output, hepevt):
        """
        integrate over the beam profile
        """
        total_number_photons = 0
        total_number_photons_cut = 0

        k1s = []
        for region in self._lattice.get(step.s0ip):
            idx = region.index(step.s0ip)
            k1s.append([region.k1(idx), region.sk1(idx)])

        hsize, vsize, ch, cv = beam.size()
        prob_norm_1 = 1.0 / (2.506628 * hsize * vsize)
        prob_norm_2 = 1.0 / (2.0*math.pi * hsize * vsize)
        weight_factor = self._stepsize_h * self._stepsize_v * hsize * vsize
        xstep = self._stepsize_h * hsize
        ystep = self._stepsize_v * vsize
        xs_max = self._sigma_h * hsize
        ys_max = self._sigma_v * vsize

        cx_s = math.sin(self._crossing_angle)
        cx_c = math.cos(self._crossing_angle)

        xs = -1.0 * self._sigma_h * hsize + 0.5*xstep
        while xs <= xs_max:
            ys = -1.0 * self._sigma_v * vsize + 0.5*ystep
            while ys <= ys_max:
                # calculate local radius
                local_gh = step.gh
                local_gv = step.gv
                for k1 in k1s:
                    local_gh += (k1[0] * xs) - (k1[1] * ys)
                    local_gv += (k1[0] * ys) + (k1[1] * xs)
                rho_inv = math.sqrt(local_gh**2 + local_gv**2)

                # calculate weight
                prob = 0.0
                nsigh = xs / hsize
                nsigv = ys / vsize

                # beam profile. Either Talman tails or Gaussian
                if (math.fabs(nsigv) > 5.0) and (beam.emitv / beam.emith < 0.2):
                    prob = prob_norm_1 * math.exp(-0.5*nsigh**2) * \
                                         math.exp(-7.4 -1.2*math.fabs(nsigv))
                else:
                    prob = prob_norm_2 * math.exp(-0.5*(nsigh**2 + nsigv**2))
                weight = prob * weight_factor

                # calculate number of radiated photons
                num_photons = int(self._num_photon_factor * rho_inv * weight * dl)
                total_number_photons += num_photons

                if num_photons > 0:

                    # calculate critical energy
                    crit_e = self._crit_e_factor * rho_inv

                    # calculate vertex
                    vx = (cx_c*(step.xip+xs)) + (cx_s*step.zip)
                    vy = step.yip+ys
                    vz = (cx_c*step.zip) - (cx_s*(step.xip+xs))

                    # calculate momentum
                    px_temp = -step.zip * ((math.pi - step.xip_prime) + (ch * xs))
                    py_temp = -step.zip * (step.yip_prime + (cv * ys))
                    pz_temp = -step.zip

                    # rotate momentum into Geant4 space
                    px = (cx_c*px_temp) + (cx_s*pz_temp)
                    py = py_temp
                    pz = (cx_c*pz_temp) - (cx_s*px_temp)
                    norm = 1.0/math.sqrt(px**2 + py**2 + pz**2)

                    # if the target zone feature is on, only write events if
                    # the photons will hit the zone.
                    if (not self._target_zone_enabled) or \
                       (self._target_zone_enabled and \
                        self._intersect_target_zone([vx, vy, vz], [px, py, pz])):

                        # if full event writing is turned on, get the energies
                        # for all radiated photons and write them into the
                        # event file
                        if self._full_events:
                            energies = self._spectrum.random(crit_e, num_photons,
                                                             self._energy_cutoff)
                            if len(energies) > 0:
                                evt = hepevt.event(vx, vy, vz)
                                for e in energies:
                                    evt.add(px*e*norm, py*e*norm, pz*e*norm)
                                evt.commit()
                            total_number_photons_cut += len(energies)
                        else:
                            evt = hepevt.event(vx, vy, vz,
                                               num_photons=num_photons,
                                               critical_e=crit_e)
                            evt.add(px*norm, py*norm, pz*norm)
                            evt.commit()

                ys += ystep
            xs += xstep
        output.write(["%f:%i:%i:%e:%e:%e:%e\n"%(step.s0ip,
                                       total_number_photons,
                                       total_number_photons_cut,
                                       step.x, step.y,
                                       step.xp, step.yp)])