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
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
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")
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
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 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
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
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))
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)
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
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
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
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
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
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
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
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]
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
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]
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)
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()
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
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
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]