Ejemplo n.º 1
0
    def _fill_multiplicity(self, *args, **kwargs):
        """Populates all tabs (_nonel_tab, _incl_tab, _incl_diff_tab) so they can work
        with the _optimize_indices() method of the base class (CrossSectionBase).
        """
        from ._phenom_relations import multiplicity_table

        self._nonel_tab = {100: (), 101: ()}

        for mom in self._nonel_tab:
            for dau in [2, 3, 4, 100, 101]:
                self._incl_diff_tab[mom, dau] = ()

        new_multiplicity = {}
        nuclides = sorted([k for k in spec_data.keys() if isinstance(k, int)])
        for mom in nuclides:
            A, _, _ = get_AZN(mom)
            if (mom < 101) or (A > config.max_mass) or \
                isinstance(mom, str) or (spec_data[mom]['lifetime'] < config.tau_dec_threshold):
                continue
            mults = multiplicity_table(mom)
            # dau_list, csincl_list = zip(*((k, v) for k, v in mults.iteritems()))

            self._nonel_tab[mom] = ()
            for dau in [2, 3, 4, 100, 101]:
                self._incl_diff_tab[mom, dau] = SophiaSuperposition.incl_diff(
                    self, mom, dau)[1]

            for dau, mult in mults.items():
                new_multiplicity[mom, dau] = mult
                self._incl_tab[mom, dau] = np.array([])

        self.multiplicity = new_multiplicity
Ejemplo n.º 2
0
def gxn_multiplicities(mother):
    """Multiplicities for multineutron emission A(g,xn)X
    
    Arguments:
        A {int} -- Nucleon number of the target nucleus
    """
    cs_sum = 0
    cs_gxn_incl = {100: 0}
    Am, _, _ = get_AZN(mother)

    for xi in range(2, xm(Am)):
        cs = cs_gxn(Am, xi)
        cs_gxn_incl[100] += xi * cs
        cs_gxn_incl[mother - xi * 100] = cs

        cs_sum += cs

    if cs_sum == 0:
        cs_sum = inf

    for dau in cs_gxn_incl:
        if cs_gxn_incl[dau] != 0:
            cs_gxn_incl[dau] /= cs_sum

    return cs_gxn_incl
Ejemplo n.º 3
0
    def _load(self, model_prefix):
        from prince_cr.data import db_handler
        info(2, "Load tabulated cross sections")
        # The energy grid is given in MeV, so we convert to GeV
        photo_nuclear_tables = db_handler.photo_nuclear_db(model_prefix)

        egrid = photo_nuclear_tables["energy_grid"]
        info(2, "Egrid loading finished")

        # Integer idices of mothers and inclusive channels are stored
        # in first column(s)
        pid_nonel = photo_nuclear_tables["inel_mothers"]
        pids_incl = photo_nuclear_tables["mothers_daughters"]

        # the rest of the line denotes the crosssection on the egrid in mbarn,
        # which is converted here to cm^2
        nonel_raw = photo_nuclear_tables["inelastic_cross_sctions"]
        incl_raw = photo_nuclear_tables["fragment_yields"]

        info(2, "Data file loading finished")

        # Now write the raw data into a dict structure
        _nonel_tab = {}
        for pid, csgrid in zip(pid_nonel, nonel_raw):
            if get_AZN(pid)[0] > config.max_mass:
                continue
            _nonel_tab[pid] = csgrid

        # If proton and neutron cross sections are not in contained
        # in the files, set them to 0. Needed for TALYS and CRPropa2
        for pid in [101, 100]:
            if pid not in _nonel_tab:
                _nonel_tab[pid] = np.zeros_like(egrid)

        # mo = mother, da = daughter
        _incl_tab = {}
        for (mo, da), csgrid in zip(pids_incl, incl_raw):
            if get_AZN(mo)[0] > config.max_mass:
                continue
            _incl_tab[mo, da] = csgrid

        self._egrid_tab = egrid
        self._nonel_tab = _nonel_tab
        self._incl_tab = _incl_tab
        # Set initial range to whole egrid
        self.set_range()
        info(2, "Finished initialization")
Ejemplo n.º 4
0
def cs_Rincl(Z, A, yields):
    """Returns incl of residual production
    
    [description]
    
    Arguments:
        Z {[type]} -- [description]
        A {[type]} -- [description]
    """
    Amax = max(yields.keys())
    nuclist = [100, 101]
    csilist = [cs_nincl(Z, A), cs_pincl(Z, A)]
    sub_frags = {}
    for nz in range(0, int(Z / 2.) + 1):
        for nn in range(0, int((A - Z) / 2.) + 1):
            Ared = min(Amax, nn + nz)

            # print nn, nz, A-Z, Z
            if nn + nz == 0:
                # add pion contribution
                nuclist += [A * 100 + Z, A * 100 + Z - 1, A * 100 + Z + 1]
                csilist += [
                    cs_gpi(A) / 3,
                ] * 3
                continue

            cs_incl = 0
            new_frag = 0
            if (nn == 1) and (nz == 0):
                cs_incl = cs_gn(A)
            elif (nn > 1) and (nz == 0):
                cs_incl = cs_gxn(A, nn)
            elif (nn == 0) and (nz == 1):
                cs_incl = cs_gp(Z)
            elif (nn >= 1) and (nz >= 1):
                cs_incl = cs_gSp(Z, A, nz, nn)
                new_frag = 100 * Ared + Ared / 2 - nz
                if new_frag > 0:
                    csilist.append(cs_incl)
                    nuclist.append(new_frag)
                sub_frags = yields[Ared]

            if cs_incl > 0:
                csilist.append(cs_incl)
                nuclist.append(100 * (A - nn - nz) + Z - nz)
                if sub_frags:
                    suma = 0
                    for nuc, val in sub_frags.items():
                        Af, _, _ = get_AZN(nuc)
                        suma += Af * val
                    norm = Ared / suma

                    for nuc, val in sub_frags.items():
                        if nuc in nuclist:
                            csilist[nuclist.index(nuc)] += val * norm * cs_incl
                        else:
                            csilist.append(val * norm * cs_incl)

    return nuclist, csilist
Ejemplo n.º 5
0
 def superposition_incl(mother, daughter):
     from scipy.integrate import trapz
     _, Z, N = get_AZN(mother)
     cs_diff = self.redist_proton[daughter].T * Z * self.cs_proton_grid + \
         self.redist_neutron[daughter].T * N * self.cs_neutron_grid
     cs_incl = trapz(cs_diff,
                     x=self.xcenters,
                     dx=bin_widths(self.xbins),
                     axis=0)
     return cs_incl[self._range]
Ejemplo n.º 6
0
    def incl_scale(self, mother, daughter, scale='A'):
        """Same as :func:`~cross_sections.CrossSectionBase.nonel_scale`,
        just for inclusive cross sections.
        """

        egr, csection = self.incl(mother, daughter)

        if scale == 'A':
            scale = 1. / get_AZN(mother)[0]

        return egr, scale * csection
Ejemplo n.º 7
0
    def nonel(self, mother):
        """Computes nonelasatic as A * universal_funtion below 1.9GeV
        and as SophiaSuperposition with given scaling betond 1.9GeV
        """
        e_max = 1.2  # prefixed based on data
        e_scale = .3  # prefixed based on data
        A, _, _ = get_AZN(mother)
        egrid, csnonel = SophiaSuperposition.nonel(self, mother)

        csnonel[egrid <= e_max] = A * self.univ_spl(egrid[egrid <= e_max])
        csnonel[egrid > e_scale] = (csnonel * self.A_eff(A, egrid) /
                                    A)[egrid > e_scale]

        return egrid, csnonel
Ejemplo n.º 8
0
    def incl(self, mother, daughter):
        r"""Returns inclusive cross section.

        Inclusive cross section for daughter in photo-nuclear
        interactions of `mother`.

        Args:
            mother (int): Mother nucleus(on)
            daughter (int): Daughter nucleus(on)

        Returns:
            (numpy.array, numpy.array): self._egrid_tab (:math:`\epsilon_r`),
            inclusive cross section in :math:`cm^{-2}`
        """

        _, Z, N = get_AZN(mother)

        if daughter <= 101:
            # raise Exception('Boost conserving cross section called ' +
            #                 'for redistributed particle')
            from scipy.integrate import trapz

            _, cs_diff = self.incl_diff(mother, daughter)
            cs_incl = trapz(cs_diff,
                            x=self.xcenters,
                            dx=bin_widths(self.xbins),
                            axis=0)
            return self.egrid, cs_incl[self._range]

        elif daughter >= 200 and daughter not in [mother - 101, mother - 100]:
            info(10, 'mother, daughter', mother, daughter, 'out of range')
            return self.egrid[[0, -1]], np.array([0., 0.])

        if daughter in [mother - 101]:
            cgrid = Z * self.cs_proton_grid
            # created incl. diff. index for all particle created in p-gamma
            for da in self.redist_proton:
                self.incl_diff_idcs.append((mother, da))
            return self.egrid, cgrid[self._range]
        elif daughter in [mother - 100]:
            cgrid = N * self.cs_neutron_grid
            # created incl. diff. channel index for all particle created in n-gamma
            for da in self.redist_neutron:
                self.incl_diff_idcs.append((mother, da))
            return self.egrid, cgrid[self._range]
        else:
            raise Exception(
                'Channel {:} to {:} not allowed in this superposition model'.
                format(mother, daughter))
Ejemplo n.º 9
0
def cs_gSp_all_inA(A):
    """Cross section summed for all possible spallation events
    """
    n = 0
    cs_summed = 0
    cs_vals = []
    if A in species_by_mass:
        for nuc in species_by_mass[A]:
            A, Z, N = get_AZN(nuc)
            cs_summed = cs_gSp_all(Z, A)
            cs_vals.append(cs_gSp_all(Z, A))
            n += 1

    # return cs_summed / n
    return max(cs_vals)
Ejemplo n.º 10
0
    def get_full(self, mother, daughter, ygrid, xgrid=None):
        """Return the full response function :math:`f(y) + g(y) + h(x,y)`
        on the grid that is provided. xgrid is ignored if `h(x,y)` not in the channel.
        """
        if xgrid is not None and ygrid.shape != xgrid.shape:
            raise Exception('ygrid and xgrid do not have the same shape!!')
        if get_AZN(mother)[0] < get_AZN(daughter)[0]:
            info(
                3,
                'WARNING: channel {:} -> {:} with daughter heavier than mother!'
                .format(mother, daughter))

        res = np.zeros(ygrid.shape)

        if (mother, daughter) in self.incl_intp:
            res += self.incl_intp[(mother, daughter)](ygrid)
        elif (mother, daughter) in self.incl_diff_intp:
            #incl_diff_res = self.incl_diff_intp[(mother, daughter)](
            #    xgrid, ygrid, grid=False)
            #if mother == 101:
            #    incl_diff_res = np.where(xgrid < 0.9, incl_diff_res, 0.)
            #res += incl_diff_res
            #if not(mother == daughter):
            res += self.incl_diff_intp[(mother, daughter)].inteval(xgrid,
                                                                   ygrid,
                                                                   grid=False)

        if mother == daughter and mother in self.nonel_intp:
            # nonel cross section leads to absorption, therefore the minus
            if xgrid is None:
                res -= self.nonel_intp[mother](ygrid)
            else:
                diagonal = xgrid == 1.
                res[diagonal] -= self.nonel_intp[mother](ygrid[diagonal])

        return res
Ejemplo n.º 11
0
def spallation_multiplicities(mother):
    '''Calculates the inclusive cross sections of all fragments
    of mothter species (moter is a neucos id) for spallation
    '''
    Am, Zm, _ = get_AZN(mother)

    incl_tab = {}
    cs_sum = 0
    for A_big_frag in range(Am // 2, Am - 1):
        for big_frag in species_by_mass[A_big_frag]:
            _, x, y = get_AZN(mother - big_frag)
            spalled_id = 100 * (x + y) + x

            if (x < 1) or (y < 1):
                # in spallation at least a neutron and proton escape
                continue

            cs_frag = cs_gSp(Zm, Am, x, y)
            cs_sum += cs_frag  # sum of all cross sections to normalize incl_tab

            if big_frag in incl_tab:
                incl_tab[big_frag] += cs_frag
            else:
                incl_tab[big_frag] = cs_frag

            # get low fragment incl_tab from using Counter on a prepared list with x, y outputs
            for dau in resmul[spalled_id]:
                if dau in incl_tab:
                    incl_tab[dau] += cs_frag * resmul[spalled_id][dau]
                else:
                    incl_tab[dau] = cs_frag * resmul[spalled_id][dau]
    for dau in incl_tab:
        incl_tab[
            dau] /= cs_sum  # all spallation cross section should match total spallation cross section

    return incl_tab
Ejemplo n.º 12
0
def list_species_by_mass(Amax, tau=inf):
    '''Returns a dictionary with the species stable enough
    to be produced in spallation.
    '''
    species = {}
    for nuc in sorted([k for k in spec_data.keys() if isinstance(k, int)]):
        if (nuc < 100) or (spec_data[nuc]['lifetime'] < tau):
            continue
        At, _, _ = get_AZN(nuc)

        if At in species:
            species[At].append(nuc)
        else:
            species[At] = [nuc]

    return species
Ejemplo n.º 13
0
def combinations(x, y):
    '''Returns possible combinations of nuclei with mass A<=4
    such that they contain x protons and y neutrons.
    '''
    ncos_id = int((x + y) * 100 + x)
    mass_partitions = partitions(x + y)

    for mass_partition in mass_partitions:
        mass_partition = [f for f in mass_partition if f > 1]
        species = [
            species_by_mass[Af] for Af in mass_partition
            if Af in species_by_mass
        ]
        for c in list(itertools.product(*species)):
            _, z, n = get_AZN(sum(c))
            if (z <= x) and (n <= y):
                yield int(y - n) * (100, ) + int(x - z) * (101, ) + c
Ejemplo n.º 14
0
def cs_gSp_all(Z, A):
    """Cross section summed for all possible spallation events
    """
    mother = 100 * A + Z
    cs_tot = 0
    for A_big_frag in range(A / 2, A - 1):
        for big_frag in species_by_mass[A_big_frag]:
            _, x, y = get_AZN(mother - big_frag)
            spalled_id = 100 * (x + y) + x

            if (x < 1) or (y < 1):
                # in spallation at least a neutron and proton escape
                continue

            cs_frag = cs_gSp(Z, A, x, y)
            cs_tot += cs_frag

    return cs_tot
Ejemplo n.º 15
0
def residual_multiplicities():
    '''Makes a dictionary where the ncos id with x,y number of protons and 
    neutrons emitted are the keys, and the multiplicities of species between A=1-4
    are given, normalized such that they add up to x protons and y neutrons.
    This is a function to precompute the table which is used to find the multiplicities
    of the empirical photomeson model.
    '''
    spalled_nucleons = []
    for Am in species_by_mass:
        for mother in species_by_mass[Am]:
            for A_big_frag in range(Am / 2, Am - 1):
                for big_frag in species_by_mass[A_big_frag]:
                    if big_frag % 100 > mother % 100:
                        continue
                    spalled_frag = mother - big_frag
                    spalled_nucleons.append(spalled_frag)
                    # print spalled_nucleons[-1]
    residual_list = {}
    count = 0
    last = 0
    print('Completed.... ', 0)
    cant = float(len(set(spalled_nucleons)))
    for tot in set(spalled_nucleons):
        count += 1
        # print '--', tot, '--', '{:3.3f}'.format(count/cant)
        if int(100 * count / cant) >= last + 5:
            print('Completed.... ', int(100 * count / cant))
            last += 5
        _, x, y = get_AZN(tot)
        counts = Counter([e for elem in combinations(x, y) for e in elem])
        suma = 0
        for k, v in counts.items():
            suma += k * v
        for k in counts:
            counts[k] *= tot / float(suma)
        residual_list[tot] = counts.copy()

    return residual_list
Ejemplo n.º 16
0
def multiplicity_table(mother):
    '''Returns a dict with the multiplicities
    for all fragments from mother is contained.
    The differentence with spallation_inclusive is that
    here all processes are contained
    '''
    gxn_mult = gxn_multiplicities(mother)
    sp_mult = spallation_multiplicities(mother)

    Am, Zm, _ = get_AZN(mother)

    cspi = cs_gpi(Am)
    csp = cs_gp(A=Am)
    csn = cs_gn(Am)
    csxn = cs_gxn_all(Am)
    cs_tot = .28 * Am
    csSp = cs_tot - (cspi + csp + csn + csxn)

    multiplicities = {
        100: 1. * csn / cs_tot,
        101: 1. * csp / cs_tot,
        mother - 100: 1. * csn / cs_tot,
        mother - 101: 1. * csp / cs_tot,
    }

    for dau, mult in gxn_mult.items():
        if dau in multiplicities:
            multiplicities[dau] += mult * csxn / cs_tot
        else:
            multiplicities[dau] = mult * csxn / cs_tot

    for dau, mult in sp_mult.items():
        if dau in multiplicities:
            multiplicities[dau] += mult * csSp / cs_tot
        else:
            multiplicities[dau] = mult * csSp / cs_tot

    return multiplicities
Ejemplo n.º 17
0
    def incl_diff(self, mother, daughter):
        r"""Returns inclusive differential cross section.

        Inclusive differential cross section for daughter in photo-nuclear
        interactions of `mother`. Only defined, if the daughter is distributed
        in :math:`x_{\rm L} = E_{da} / E_{mo}`

        Args:
            mother (int): Mother nucleus(on)
            daughter (int): Daughter nucleus(on)

        Returns:
            (numpy.array, numpy.array, numpy.array): :math:`\epsilon_r` grid,
            :math:`x` grid, differential cross section in :math:`{\rm cm}^{-2}`
        """

        _, Z, N = get_AZN(mother)

        if daughter > 101:
            raise Exception(
                'Redistribution function requested for boost conserving particle'
            )
        csec_diff = None
        # TODO: File shall contain the functions in .T directly
        #   JH: I left it like this on purpose, since the raw data is ordered more consistently
        #       i.e. redist.shape = cs_nonel.shape + xbins.shape
        #       The ordering should rather be changed in the rest of the code
        if daughter in self.redist_proton:
            csec_diff = self.redist_proton[daughter].T * Z

        if daughter in self.redist_neutron:
            cgrid = N * self.cs_neutron_grid
            if np.any(csec_diff):
                csec_diff += self.redist_neutron[daughter].T * N
            else:
                csec_diff = self.redist_neutron[daughter].T * N

        return self.egrid, csec_diff[:, self._range]
Ejemplo n.º 18
0
    def nonel_scale(self, mother, scale='A'):
        """Returns the nonel cross section scaled by `scale`.

        Convenience funtion for plotting, where it is important to
        compare the cross section per nucleon.

        Args:
            mother (int): Mother nucleus(on)
            scale (float): If `A` then nonel/A is returned, otherwise
                           scale can be any float.

        Returns:
            (numpy.array, numpy.array): Tuple of Energy grid in GeV,
                                        scale * inclusive cross section
                                        in :math:`cm^{-2}`
        """

        egr, csection = self.nonel(mother)

        if scale == 'A':
            scale = 1. / get_AZN(mother)[0]

        return egr, scale * csection
Ejemplo n.º 19
0
    def nonel(self, mother):
        r"""Returns non-elastic cross section.

        Absorption cross section of `mother`, which is
        the total minus elastic, or in other words, the inelastic
        cross section.

        Args:
            mother (int): Mother nucleus(on)

        Returns:
           Returns:
            (numpy.array, numpy.array): self._egrid_tab (:math:`\epsilon_r`),
            nonelastic (total) cross section in :math:`cm^{-2}`
        """

        # now interpolate these as Spline
        _, Z, N = get_AZN(mother)

        # the nonelastic crosssection is just a superposition of
        # the proton/neutron number
        cgrid = Z * self.cs_proton_grid + N * self.cs_neutron_grid
        return self.egrid, cgrid[self._range]
Ejemplo n.º 20
0
    def __init__(self, *args, **kwargs):

        if "max_mass" in kwargs:
            max_mass = kwargs.pop("max_mass", config.max_mass)

        # Initialize energy grid
        if config.grid_scale == 'E':
            info(1, 'initialising Energy grid')
            self.cr_grid = EnergyGrid(*config.cosmic_ray_grid)
            self.ph_grid = EnergyGrid(*config.photon_grid)
        else:
            raise Exception(
                "Unknown energy grid scale {:}, adjust config.grid_scale".
                format(config.grid_scale))

        # Cross section handler
        if 'cross_sections' in kwargs:
            self.cross_sections = kwargs['cross_sections']
        else:
            self.cross_sections = cross_sections.CompositeCrossSection([
                (0., cross_sections.TabulatedCrossSection, ('CRP2_TALYS', )),
                (0.14, cross_sections.SophiaSuperposition, ())
            ])

        # Photon field handler
        if 'photon_field' in kwargs:
            self.photon_field = kwargs['photon_field']
        else:
            import prince_cr.photonfields as pf
            self.photon_field = pf.CombinedPhotonField(
                [pf.CMBPhotonSpectrum, pf.CIBGilmore2D])

        # Limit max nuclear mass of eqn system
        if "species_list" in kwargs:
            system_species = list(
                set(kwargs["species_list"])
                & set(self.cross_sections.known_species))
        else:
            system_species = [
                s for s in self.cross_sections.known_species
                if get_AZN(s)[0] <= max_mass
            ]
        # Disable photo-meson production
        if not config.secondaries:
            system_species = [s for s in system_species if s >= 100]
        # Remove particles that are explicitly excluded
        for pid in config.ignore_particles:
            if pid in system_species:
                system_species.remove(pid)

        # Initialize species manager for all species for which cross sections are known
        self.spec_man = data.SpeciesManager(system_species, self.cr_grid.d)

        # Total dimension of system
        self.dim_states = self.cr_grid.d * self.spec_man.nspec
        self.dim_bins = (self.cr_grid.d + 1) * self.spec_man.nspec

        # Initialize continuous energy losses
        self.adia_loss_rates_grid = interaction_rates.ContinuousAdiabaticLossRate(
            prince_run=self, energy='grid')
        self.pair_loss_rates_grid = interaction_rates.ContinuousPairProductionLossRate(
            prince_run=self, energy='grid')
        self.adia_loss_rates_bins = interaction_rates.ContinuousAdiabaticLossRate(
            prince_run=self, energy='bins')
        self.pair_loss_rates_bins = interaction_rates.ContinuousPairProductionLossRate(
            prince_run=self, energy='bins')

        # Initialize the interaction rates
        self.int_rates = interaction_rates.PhotoNuclearInteractionRate(
            prince_run=self)

        # Let species manager know about the photon grid dimensions (for idx calculations)
        # it is accesible under index "ph" for lidx(), uidx() calls
        self.spec_man.add_grid('ph', self.ph_grid.d)
Ejemplo n.º 21
0
    def _optimize_and_generate_index(self):
        """Construct a list of mothers and (mother, daughter) indices.

        Args:
            just_reactions (bool): If True then fill just the reactions index.
        """

        # Integrate out short lived processes and leave only stable particles
        # in the databases
        self._reduce_channels()

        # Go through all three cross section categories
        # index contents in the ..known..variable
        self.reactions = {}

        self._update_indices()

        for mo, da in self.incl_idcs:
            if da >= 100 and get_AZN(da)[0] > get_AZN(mo)[0]:
                raise Exception(
                    'Daughter {0} heavier than mother {1}. Physics??'.format(
                        da, mo))

            if mo not in self.reactions:
                self.reactions[mo] = []
                self.known_species.append(mo)

            if (mo, da) not in self.reactions[mo]:
                # Make sure it's a unique list
                self.reactions[mo].append((mo, da))
            if self.is_differential(mo, da):
                # Move the distributions which are expected to be differential
                # to _incl_diff_tab
                self._incl_diff_tab[(mo, da)] = self._arange_on_xgrid(
                    self._incl_tab.pop((mo, da)))
                info(10, "Channel {0} -> {1} forced to be differential.")
            else:
                self.known_bc_channels.append((mo, da))
                self.known_species.append(da)

        for mo, da in list(self._incl_diff_tab.keys()):
            if da >= 100 and get_AZN(da)[0] > get_AZN(mo)[0]:
                raise Exception(
                    'Daughter {0} heavier than mother {1}. Physics??'.format(
                        da, mo))

            if mo not in self.reactions:
                self.reactions[mo] = []
                self.known_species.append(mo)

            if (mo, da) not in self.reactions[mo]:
                # Make sure it's a unique list to avoid unnecessary loops
                self.reactions[mo].append((mo, da))
                self.known_diff_channels.append((mo, da))
                self.known_species.append(da)

        # Remove duplicates
        self.known_species = sorted(list(set(self.known_species)))
        self.known_bc_channels = sorted(list(set(self.known_bc_channels)))
        self.known_diff_channels = sorted(list(set(self.known_diff_channels)))

        for sp in self.known_species:
            if sp >= 100 and (sp, sp) not in self.known_diff_channels:
                self.known_bc_channels.append((mo, mo))
            if (mo, mo) not in self.reactions[mo]:
                self.reactions[mo].append((mo, mo))

        # Make sure the indices are up to date
        self._update_indices()
Ejemplo n.º 22
0
def nu_from_beta_decay(x_grid, mother, daughter, Gamma=200, angle=None):
    """
    Energy distribution of a neutrinos from beta-decays of mother to daughter
    The res frame distrution is boosted to the observers frame and then angular averaging is done numerically

    Args:
      x_grid (float): energy fraction transferred to the secondary
      mother (int): id of mother
      daughter (int): id of daughter
      Gamma (float): Lorentz factor of the parent particle, default: 200
                     For large Gamma this should not play a role, as the decay is scale invariant
      angle (float): collision angle, if None this will be averaged over 2 pi
    Returns:
      float: probability density on x_grid
    """
    import warnings

    info(10, 'Calculating neutrino energy from beta decay', mother, daughter)

    mass_el = spec_data[20]['mass']
    mass_mo = spec_data[mother]['mass']
    mass_da = spec_data[daughter]['mass']

    Z_mo = spec_data[mother]['charge']
    Z_da = spec_data[daughter]['charge']

    A_mo, _, _ = get_AZN(mother)

    if mother == 100 and daughter == 101:
        # for this channel the masses are already nucleon masses
        qval = mass_mo - mass_da - mass_el
    elif Z_da == Z_mo - 1:  # beta+ decay
        qval = mass_mo - mass_da - 2 * mass_el
    elif Z_da == Z_mo + 1:  # beta- decay
        qval = mass_mo - mass_da
    else:
        raise Exception('Not an allowed beta decay channel: {:} -> {:}'.format(
            mother, daughter))

    # substitute this to the energy grid
    E0 = qval + mass_el
    # NOTE: we subsitute into energy per nucleon here
    Emo = Gamma * mass_mo / A_mo
    E = x_grid * Emo

    # print '------','beta decay','------'
    # print mother
    # print E0
    # print A_mo
    # print Emo

    if angle is None:
        # ctheta = np.linspace(-1, 1, 1000)
        # we use here logspace, as high resolution is mainly needed at small energies
        # otherwise the solution will oscillate at low energy
        ctheta = np.unique(
            np.concatenate((
                np.logspace(-8, 0, 1000) - 1,
                1 - np.logspace(0, -8, 1000),
            )))
    else:
        ctheta = angle

    boost = Gamma * (1 - ctheta)
    Emax = E0 * boost

    E_mesh, boost_mesh = np.meshgrid(E, boost, indexing='ij')
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        res = E_mesh**2 / boost_mesh**5 * (Emax - E_mesh) * np.sqrt(
            (E_mesh - Emax)**2 - boost_mesh**2 * mass_el**2)
    res[E_mesh > Emax] = 0.
    res = np.nan_to_num(res)

    if np.all(res == 0):
        info(10, 'Differential distribution is all zeros for', mother,
             daughter, 'No angle averaging performed!')
    elif angle is None:
        # now average over angle
        res = trapz(res, x=ctheta, axis=1)
        res = res / trapz(res, x=x_grid)
    else:
        res = res[:, 0]
        res = res / trapz(res, x=x_grid)

    return res
Ejemplo n.º 23
0
    def incl_diff(self, mother, daughter):
        """Uses corresponding method from SophiaSuperposition class,
        adding nonel to increase multiplicity by one
        """
        egrid, cs_diff = SophiaSuperposition.incl_diff(self, mother, daughter)
        if (mother, daughter) in self.multiplicity:
            xw = self.xwidths[-1]  # accounting for bin width
            _, cs_nonel = self.nonel(mother)
            cs_diff[-1, :] += \
                self.multiplicity[mother, daughter] * cs_nonel / xw
        elif (mother > 101) and (daughter in [
                2, 3, 4
        ]):  # if it's a pion rescale to A^2/3

            def superposition_incl(mother, daughter):
                from scipy.integrate import trapz
                _, Z, N = get_AZN(mother)
                cs_diff = self.redist_proton[daughter].T * Z * self.cs_proton_grid + \
                    self.redist_neutron[daughter].T * N * self.cs_neutron_grid
                cs_incl = trapz(cs_diff,
                                x=self.xcenters,
                                dx=bin_widths(self.xbins),
                                axis=0)
                return cs_incl[self._range]

            def superposition_multiplicities(mother, daughter):
                _, Z, N = get_AZN(mother)
                cs_incl = superposition_incl(mother, daughter)
                cs_nonel = Z * self.cs_proton_grid + N * self.cs_neutron_grid
                return cs_incl / cs_nonel[self._range]

            Am, _, _ = get_AZN(mother)
            cs_diff *= float(Am)**(-1 / 3.)  # ... rescaling SpM to A^2/3

            cs_incl_pi0_sophia = superposition_incl(
                mother, daughter) * float(Am)**(-1 / 3.)
            cs_incl_pi0_data = 1e-30 * self.pion_spl(
                egrid * 1e3) * Am**(2. / 3)

            M_pi = superposition_multiplicities(mother, daughter)
            M_pi0 = superposition_multiplicities(mother, 4)

            renorm = M_pi / M_pi0 * cs_incl_pi0_data / cs_incl_pi0_sophia
            cs_diff_renormed = cs_diff * renorm
            cs_diff_renormed = self.fade(
                cs_diff, cs_diff_renormed,
                range(32))  # hardcoded index, found manually
            cs_diff = self.fade(cs_diff_renormed, cs_diff,
                                range(55,
                                      95))  # hardcoded index, found manually

            # # additional correction to pion scaling high energies, after paper was corrected
            # def sigm(x, shift=0., gap=1, speed=1, base=0., rising=False):
            #     """Models a general sigmoid with multiple parameters.

            #     Parameters:
            #     -----------
            #     x: x values, argument
            #     shift: middle point, inflection point
            #     gap: maximal value - minimal value
            #     speed: controls the speed of the change,
            #     base: minimal value
            #     """
            #     sigmoid = 1. /(1 + np.exp(- speed * (x - shift)))

            #     if rising:
            #         return gap * sigmoid + base
            #     else:
            #         return gap*( 1. - sigmoid) + base

            alpha_plus = np.where(egrid <= 1., 2. / 3,
                                  1 - np.exp(-4. / 7 * (egrid - 1)**.5) / 3)
            cs_diff *= Am**(alpha_plus - 2. / 3)

        return egrid, cs_diff
Ejemplo n.º 24
0
 def superposition_multiplicities(mother, daughter):
     _, Z, N = get_AZN(mother)
     cs_incl = superposition_incl(mother, daughter)
     cs_nonel = Z * self.cs_proton_grid + N * self.cs_neutron_grid
     return cs_incl / cs_nonel[self._range]