def mass_excess(self, ions): """ TODO: generally supplement by data from http://www.nndc.bnl.gov/masses/mass.mas12 """ if not is_iterable(ions): shape = () ions = ions, else: shape = np.shape(ions) if not hasattr(self, '_mass_excess_data'): self._mass_excess_data = dict() me = [] spec_ions_me = { isotope.ion('nt1') : 8.07131714, isotope.ion('h1' ) : 7.28897059, isotope.ion('he4') : 2.42491561, isotope.ion('be8') : 4.941671, } for i in ions: x = self._mass_excess_data.get(i, None) if x is None: try: x = self.__getitem__(i).mass_excess() except KeyError: x = spec_ions_me[isotope.ion(i)] self._mass_excess_data[i] = x me.append(x) if shape == (): return me[0] return np.array(me)
def add_stable(self, ix): """ Flag an ion as stable, extending decays from edge. Assume all isotopes are either isomers or isotopes. Extra gap fillers will be assumed to be isomers in gs (if isomeric) """ Z = ix.Z A = ix.A E = ix.E a = np.array([jx.A for jx in self.decdata.keys() if jx.Z == Z and jx.E == E]) if len(a) > 0: amin = min(a) amax = max(a) if self.isomers: E = 0 else: E = None for a in range(A+1, amin): newion = ion(Z = Z, A = a, E = E) self.decdata[newion] = self.get_decay(newion) for a in range(amax+1, A): newion = ion(Z = Z, A = a, E = E) self.decdata[newion] = self.get_decay(newion) # finally add/overwrite "stable" self.decdata[ix] = [(np.float64(1),)]
def __init__(self, i_in, i_out, rate): i_in = list(iterable(i_in)) i_out = list(iterable(i_out)) for j,i in enumerate(i_in): if isinstance(i, str): i_in[j] = ion(i) for j,i in enumerate(i_out): if isinstance(i, str): i_out[j] = ion(i) self.i_in = i_in self.i_out = i_out self.rate = rate
def get_decay(self, ix): """ Return decay table entry. If the decay is not in stored data, extrapolate from last available decay at same element (Z value). Use gs decays only for extrapolation and assume unspecified excited states do single 'g' decay. TODO: should we store new entries? """ try: return self.decdata[ix] except: if ix.is_isomer(): if ix.E > 0: return [(1., ix.isomer(E = ix.E - 1))] Z = ix.Z a = np.array([ix.A for ix in self.decdata.keys() if ix.Z == Z and ix.E == 0]) assert len(a) > 0, "Problem finding isotopes for decay." assert not (min(a) < ix.A < max(a)), 'decay chain has gaps, no unique extrapolation possible' A = min(max(a), max(min(a), ix.A)) refion = ion(Z = Z, A = A, isomer = ix.is_isomer()) try: refdec = self.decdata[refion] except KeyError: raise Exception('Decay Extrapolation: Could not determine reference nucleus') dec = copy(refdec) dA = ix.A - refion.A for i, branch in enumerate(refdec): br = branch[0] products = list(branch[1:]) # stable if len(products) == 0: assert len(refdec) == 0, 'can only be stable or not' continue #Q: use last or most heavy nucleus? #A: let's check last one is most heavy ap = ufunc_A(products) maxa = np.where(ap == np.max(ap))[0] assert len(maxa) == 1, 'cannot determine decay nucleus' # or I could just use the most heavy nucleus idec = maxa[0] refdecion = products[idec] newdecion = ion(Z = refdecion.Z, A = refdecion.A + dA, isomer = ix.is_isomer()) products[idec] = newdecion dec[i] = tuple([br] + products) return dec
def __init__(self, f, mode = 'winvne2', nt9 = None): assert mode == 'winvne2' assert nt9 is not None l = f.readline() if len(l.strip()) == 0: raise EmptyRecord() nuc, A, Z, N, S, ME, label = l.split() self.ion = isotope.ion(nuc) self.A = int(round(float(A))) self.Z = int(Z) self.N = int(N) self.S = float(S) self.ME = float(ME) self.E = 0. self.label = label assert self.N + self.Z == self.A assert self.ion.N == self.N assert self.ion.Z == self.Z assert self.ion.E == 0 data = [] for i in range(nt9): j = i % 8 if j == 0: l = f.readline() data.append(float(l[12*j:12*(j+1)])) self.coeff = data self.Q = round( self.Z * 7.28898454697355 + self.N * 8.071317791830353 - self.ME, 3) self.formula = self.winvne2_type
def find_rate(self, ion, reac = None, return_other = False, silent = False): if reac is None: i = ion.find('(') if i < 0: i = len(ion) - 2 reac = ion[i:] ion = ion[:i] if not isinstance(ion, isotope.Ion): ion = isotope.ion(ion) if isinstance(reac, int): return self[ion], rate reac = ReactIon(reac) prod = ion + reac forward = True if ion.Z > prod.Z or (ion.Z == prod.Z and ion.A > prod.A): ion, prod = prod, ion reac = -reac forward = False rec = self[ion] ind = self.rate_map[str(reac)] s = '{}{}{}: {}'.format(ion, reac, prod, rec[ind].creac) if rec[ind].cref != '': s +=' ({})'.format(rec[ind].cref) s += ' {} {} {}'.format(ind, *rec[ind].ic) if forward: s += ' forward' else: s += ' reverse' if not silent: print(' [BDAT] ' + s) ret_val = rec, ind, forward if return_other: ret_val += (self[prod],) return ret_val
def __init__(self, *args, check = False, silent = False): (nz, nzx, nn, na, iso, q, sa, ist, gs, ic, c, cm, cref, creac) = args self.ion = isotope.ion(Z=nz, A=na) self.q = q self.sa = sa self.ist = ist self.gs = gs self.cm = cm self.rates = [BDatRate(ic[i], c[i], creac[i], cref[i]) for i in range(len(ic))] if not check: return assert nz == nzx assert nn == na - nz assert self.ion == iso
def stackplot(self, ions=None): """ Produces a plot of contributions of each isotope towards the total ejected fraction also plots the rate of ejection over time for each isotope. Input desired isotopes as a string or isotope.ion vector """ topes = ions if topes is None: topes = input('Enter desired isotopes: ') if isinstance(topes, str): topes = topes.split() topes = np.array([isotope.ion(x) for x in topes]) fig = plt.figure(1) ax = fig.add_subplot(1, 1, 1) taxis = np.linspace(self.tmin, self.tmax, self.numsteps) y = [] # this will collect the required isotopes to be stacked, will also create the legend as stackplot can't do it. for x in range(len(topes)): topeindex = np.where(topes[x] == self.isoinfo)[0][0] y.append(self.mastersum[topeindex]) ax.plot([], [], label=self.isoinfo[topeindex].LaTeX()) ax.plot(taxis, self.total, '--', label='Grand Total') # this resets the colour cycle to default so the stackplot colours match with the legend, generalises the code and allowsthe user to select any number of isotopes plt.gca().set_prop_cycle(None) ax.stackplot(taxis, y) ax.set_ylabel('Ejecta Mass Fraction') ax.set_xlabel('Time(years)') ax.set_title('Contribution by isotopes to ejecta.') ax.legend(loc='upper left') fig.tight_layout() plt.show() fig.savefig( os.path.expanduser('~/python/project/outputfiles/stackplot.pdf'), bbox_inches='tight') fig = plt.figure(2) plt.gca().set_prop_cycle(None) bx = fig.add_subplot(1, 1, 1) for x in range(len(topes)): topeindex = np.where(topes[x] == self.isoinfo)[0][0] m = lambda t: np.exp(self.interpolationfunction(np.log(t))) y = lambda t: -self.IMF(m(t)) * self.c * self.deriv(np.log( t)) * self.isofracinterp[topeindex](t) / t * m(t) bx.plot(taxis, y(taxis), label=self.isoinfo[topeindex].LaTeX()) bx.set_ylabel('Ejecta Rate (massfraction per year)') bx.set_xlabel('Time(years)') bx.set_title('Rate of mass ejection over time.') bx.legend(loc='upper right') fig.tight_layout() plt.show() fig.savefig( os.path.expanduser('~/python/project/outputfiles/ejectarate.pdf'), bbox_inches='tight')
def __init__(self, data=default_data, silent=False): """ TODO - implement data """ self.setup_logger(silent=silent) path = os.getenv('KEPLER_DATA') if not path: path = os.path.join(os.path.expanduser('~'), 'kepler', 'local_data') self.logger.warning('using default path ' + path) filename = os.path.join(path, 'masses_audi_2003.dat') self.comment = () self.iso = np.array([], dtype=np.object) self.mass = np.array([], dtype=np.float64) xre = re.compile('[-+a-zA-Z0-9.]+') with open(filename, 'r') as f: self.logger_file_info(f) for line in f: if not line.startswith((';', '#')): xdata = xre.findall(line) xnum = len(xdata) if xnum == 0: continue if xnum == 2: xion, xabu = tuple(xdata) else: print(line) raise IOError('bad format') self._append(isotope.ion(xion), np.double(xabu)) else: self.comment += (line[2:], ) message = "{:3d} masses loaded in".format(len(self.iso)) self.close_logger(timing=message)
def __init__(self, f, mode='winvne2', nt9=None): assert mode == 'winvne2' assert nt9 is not None l = f.readline() if len(l.strip()) == 0: raise EmptyRecord() nuc, A, Z, N, S, ME, label = l.split() self.ion = isotope.ion(nuc) self.A = int(round(float(A))) self.Z = int(Z) self.N = int(N) self.S = float(S) self.ME = float(ME) self.E = 0. self.label = label assert self.N + self.Z == self.A assert self.ion.N == self.N assert self.ion.Z == self.Z assert self.ion.E == 0 data = [] for i in range(nt9): j = i % 8 if j == 0: l = f.readline() data.append(float(l[12 * j:12 * (j + 1)])) self.coeff = data self.Q = round( self.Z * 7.28898454697355 + self.N * 8.071317791830353 - self.ME, 3) self.formula = self.winvne2_type
def isoyield(self, tmin, tmax, ions=None): """ isoyield acts like massyield except it calculates the yield for any given isotope and time interval. Input tmin, tmax, isotopes as a string or isotope.ion array """ topes = ions if topes is None: topes = input('Enter desired isotopes: ') if isinstance(topes, str): topes = re.split('[ ,;]+', topes) topes = np.array([isotope.ion(x) for x in topes]) for x in range(len(topes)): topeindex = np.where(topes[x] == self.isoinfo)[0][0] self.intsum = yieldout.yieldcode(tmin, tmax, self.correctedtime, self.isosol[topeindex], self.isofracinterp[topeindex], self.interpolationfunction, self.c, self.IMF) print('The yield for ', self.isoinfo[topeindex], ' is ', self.intsum)
def iyplot(tmin, tmax, numsteps, mastersum, isoinfo, total, ions): """ Isotopeyieldplot plots the yield of the entered isotopes on a logscale alongwith the grand total of all isotopes ejected. """ topes = ions if topes is None: topes = input('Enter desired isotopes: ') if isinstance (topes, str): topes = re.split('[ ,;]+', topes) topes = np.array([isotope.ion(x) for x in topes]) fig = plt.figure(1) ax = fig.add_subplot(1,1,1) taxis = np.linspace(tmin, tmax, numsteps) for x in range(len(topes)): topeindex = np.where(topes[x] == isoinfo)[0][0] ax.plot(taxis, mastersum[topeindex], label = isoinfo[topeindex].LaTeX()) ax.plot(taxis, total, '--', label = 'Grand Total') ax.set_ylabel('Ejecta Mass Fraction') ax.set_xlabel('Time(years)') ax.set_yscale('log') ax.set_title('Mass ejection over time for star dependent energy') ax.legend(loc = 'best') fig.tight_layout() plt.show() fig.savefig(os.path.expanduser('~/python/project/outputfiles/noexplisoyieldplot.pdf'), bbox_inches = 'tight')
def decaydata(self): decays = list() for d in self.data: r = d.get_ad() if r is not None: decays += [r] r = d.get_bd() if r is not None: decays += [r] decays += [DecayRate([isotope.ion('be8')], [isotope.ion('he4'), isotope.ion('he4')], np.log(2)/6.7e-8), DecayRate([isotope.ion('nt1')], [isotope.ion('pn1')], np.log(2)/881.5), ] return decays
def __getitem__(self, ion): if not hasattr(self, '_ion_hash'): self._ion_hash = {d.ion : i for i,d in enumerate(self.data)} if not isinstance(ion, isotope.Ion): ion = isotope.ion(ion) try: return self.data[self._ion_hash[ion]] except: raise KeyError('{} not in data base'.format(ion))
def get_ad(self): ic = self[7].ic if ic[0] == 0: return None assert ic[0] == 19 i_in = (self.ion + 'he4', ) i_out = (self.ion, isotope.ion('he4')) rate = self[7].c[0] return DecayRate(i_in, i_out, rate)
def __getitem__(self, ion): """ Return mass of ion in amu (or approximate) TODO: accept list """ try: i, = np.argwhere(self.iso == ion) return self.mass[i[0]] except: pass return np.double(isotope.ion(ion).A)
def __init__(self, filename = '~/Plots/solar/Asplund2009-isotopes_protosun.dat', comment = None, silent = False): """ Load abundace set from Aspund "dat" file. TODO - add option to show comment """ self.setup_logger(silent = silent) comment = stuple(comment) xre = re.compile('[-+a-zA-Z0-9.]+') iso = np.array([],dtype=np.object) abu = np.array([],dtype=np.float64) filename = os.path.expanduser(filename) with open(filename,'r') as f: self.logger_file_info(f) comment += ('', 'Generated from file "{:s}".'.format(filename), 'Original file comments follow:', '') for line in f: if not line.startswith((';','#')): xdata = xre.findall(line) xnum = len(xdata) if xnum == 0: continue if xnum == 5: xiso = isotope.ion(A = int(xdata[2]), Z = int(xdata[1])) xabu = np.double(xdata[4]) else: print(line) raise IOError('bad format') iso = np.append(iso, xiso) abu = np.append(abu, xabu) else: comment += (line[2:].rstrip(),) m = Mass() # well, this could require tests... abu = np.array([a*m(i) for i,a in zip(iso,abu)]) abu = abu/abu.sum() super().__init__( iso = iso, abu = abu, comment = comment) message = "{:3d} isotopes loaded in".format(iso.size) self.close_logger(timing = message)
def daughters(self, ix): """ return dictionary of daughter nuclei (including self) and their weights """ ix = ion(ix) ii = np.where(ix == self.ions)[0][0] col = self.map[ii,:] ii = np.where(col > 0)[0] p = {} for i in ii: p[self.decions[i]] = col[i] return p
def __init__(self, *args, **kwargs): # initialise with minnet? netnum = kwargs.pop('netnum', None) if netnum is None: if len(args) > 0 and isinstance(args[0], str): netnum = args.pop(0) else: netnum = 1 self.netnum = netnum kwargs['duplicates'] = False super().__init__(*args, **kwargs) self.compositions = [] self.minnet = np.array([isotope.ion(i) for i in _minnet]) self.add(self.minnet) # find gaps gaps = [] j = self.minnet[0] for i in range(1, len(self.minnet)): j, i = self.minnet[i], j if i.Z == j.Z and j.A > i.A + 1: gaps.append(isotope.ion(Z = j.Z, A = i.A + 1)) self.gaps = np.array(gaps)
def __init__(self, *args, **kwargs): # initialise with minnet? netnum = kwargs.pop('netnum', None) if netnum is None: if len(args) > 0 and isinstance(args[0], str): netnum = args.pop(0) else: netnum = 1 self.netnum = netnum kwargs['duplicates'] = False super().__init__(*args, **kwargs) self.compositions = [] self.minnet = np.array([isotope.ion(i) for i in _minnet]) self.add(self.minnet) # find gaps gaps = [] j = self.minnet[0] for i in range(1, len(self.minnet)): j, i = self.minnet[i], j if i.Z == j.Z and j.A > i.A + 1: gaps.append(isotope.ion(Z=j.Z, A=i.A + 1)) self.gaps = np.array(gaps)
def mass_excess(self, ions): """ TODO: generally supplement by data from http://www.nndc.bnl.gov/masses/mass.mas12 """ if not is_iterable(ions): shape = () ions = ions, else: shape = np.shape(ions) if not hasattr(self, '_mass_excess_data'): self._mass_excess_data = dict() me = [] spec_ions_me = { isotope.ion('nt1') : 8.07131714, isotope.ion('h1' ) : 7.28897059, isotope.ion('he4') : 2.42491561, isotope.ion('be8') : 4.941671, } for i in ions: x = self._mass_excess_data.get(i, None) if x is None: try: x = self.__getitem__(i).mass_excess() except KeyError: try: x = spec_ions_me[isotope.ion(i)] except KeyError: x = 0 #print(f' [ERROR] NOT FOUND: {i} (returning {x})') print(' [ERROR] NOT FOUND: {} (returning {})'.format(i,x)) self._mass_excess_data[i] = x me.append(x) if shape == (): return me[0] return np.array(me)
def load_nuclear_data( self, filename='/home/alex/kepler/fission/winvne_v2.0.dat', mode='winvne2', ): """ load nuclear data for ReacLib """ self.setup_logger(silent=False) assert mode == 'winvne2' nucdata = [] with open(filename) as f: self.logger.info(f'loading {filename} ...') # read header: grid and nuclei info l = f.readline() assert len(l.strip()) == 0, '{}'.format(len(l)) + ' >' + l + '<' l = f.readline() assert l.rstrip( ) == '010015020030040050060070080090100150200250300350400450500600700800900100' t9grid = [ int(l[3 * i:3 * (i + 1)]) / 10 for i in range(len(l.rstrip()) // 3) ] nt9 = len(t9grid) nuclei = [] while True: l = f.readline() n = isotope.ion(l) # equal nuclei is used as sentinel for end of list if len(nuclei) > 1 and nuclei[-1] == n: break nuclei.append(n) nnuclei = len(nuclei) self.logger.info('{} temperature points for {} nuclei.'.format( nt9, nnuclei)) while True: try: nuc = ReacLibNuc(f, mode=mode, nt9=nt9) if nuc is not None: nucdata.append(nuc) except Exception as e: if isinstance(e, EmptyRecord): break else: raise assert len(nucdata) == len(nuclei) self.nucdata = nucdata self.close_logger(r'loaded {} nuclei in '.format(len(nucdata)))
def output2ionarr(ions, molfrac_out = None): """ extract ions from input data # TODO add IonMap """ if isinstance(ions, AbuData): molfrac_out = ions.molfrac ions = ions.ions elif isinstance(ions, AbuSet): assert molfrac_out in (None, False) molfrac_out = False ions = ions.iso if isinstance(ions, IonList): ions = np.array(ions) ions = np.atleast_1d(ions) if isinstance(ions[0], str): ions = np.array([ion(ix) for ix in ions]) return ions, molfrac_out
def load_nuclear_data(self, filename = '/home/alex/kepler/fission/winvne_v2.0.dat', mode = 'winvne2', ): """ load nuclear data for ReacLib """ self.setup_logger(silent = False) assert mode == 'winvne2' nucdata = [] with open(filename) as f: self.logger.info(f'loading {filename} ...') # read header: grid and nuclei info l = f.readline() assert len(l.strip()) == 0, '{}'.format(len(l)) + ' >' + l + '<' l = f.readline() assert l.rstrip() == '010015020030040050060070080090100150200250300350400450500600700800900100' t9grid = [int(l[3*i:3*(i+1)])/10 for i in range(len(l.rstrip())//3)] nt9 = len(t9grid) nuclei = [] while True: l = f.readline() n = isotope.ion(l) # equal nuclei is used as sentinel for end of list if len(nuclei) > 1 and nuclei[-1] == n: break nuclei.append(n) nnuclei = len(nuclei) self.logger.info('{} temperature points for {} nuclei.'.format(nt9, nnuclei)) while True: try: nuc = ReacLibNuc(f, mode = mode, nt9 = nt9) if nuc is not None: nucdata.append(nuc) except Exception as e: if isinstance(e, EmptyRecord): break else: raise assert len(nucdata) == len(nuclei) self.nucdata = nucdata self.close_logger(r'loaded {} nuclei in '.format(len(nucdata)))
def _abu_massfrac_raw(self, scale): """ Raw scaled solar abundances """ scaled = self.sun * scale + self.bbn * (1 - scale) # beyond-solar scaling if scale > 1.: jj, = np.argwhere(scaled.iso == isotope.ion('He4')) # make sure we have same set of istopes bbn = (self.sun * 0) + self.bbn for j in np.argwhere(scaled.abu < self.sun.abu).flat: scaled.abu[jj] += scaled.abu[j] scaled.abu[j] = self.sun.abu[j] * np.exp( (scale - 1)*(1 - self.bbn.abu[j]/self.sun.abu[j])) scaled.abu[jj] -= scaled.abu[j] scaled.normalize() return scaled.abu
def bbncoc(filename, write=False): """ convert BBN abunaces from http://www2.iap.fr/users/pitrou/primat.htm to data file """ with open(filename) as f: lines = f.readlines() ions = [] abu = [] for i, l in enumerate(lines): if l.count('1 n') > 0: break while True: if lines[i].count('Z=') > 0: break offset = 0 if lines[i].startswith(';'): offset += 2 xions = [ ''.join(lines[i][k:k + 10].split()) for k in range(offset, 70 + offset, 10) ] i += 1 xabu = list(lines[i][offset:].split()) ions += [x for x in xions if len(x) > 0] abu += xabu i += 2 abu = [float(a) for a in abu] ions = isotope.ion(ions) abu = AbuSet(ions, abu) from ionmap import decay abu = decay(abu, stable=True) if write: with open(filename, 'at') as f: for a in abu: #f.write(f'{a[0].name():5s} {a[1]:12.5e}\n') f.write('{:5s} {:12.5e}\n'.format(a[0].name(), a[1])) return abu
def __init__(self, data = default_data, silent = False): """ TODO - implement data """ self.setup_logger(silent = silent) path = os.getenv('KEPLER_DATA') if not path: path=os.path.join(os.path.expanduser('~'),'kepler','local_data') self.logger.warning('using default path ' + path) filename = os.path.join(path,'masses_audi_2003.dat') self.comment = () self.iso = np.array([],dtype=np.object) self.mass = np.array([],dtype=np.float64) xre = re.compile('[-+a-zA-Z0-9.]+') with open(filename,'r') as f: self.logger_file_info(f) for line in f: if not line.startswith((';','#')): xdata = xre.findall(line) xnum = len(xdata) if xnum == 0: continue if xnum == 2: xion,xabu = tuple(xdata) else: print(line) raise IOError('bad format') self._append(isotope.ion(xion), np.double(xabu)) else: self.comment += (line[2:],) message = "{:3d} masses loaded in".format(len(self.iso)) self.close_logger(timing = message)
def data(dbfilename=os.path.expanduser( '~/python/project/znuc2012.S4.star.el.y.stardb.gz')): """ This is the main data collecting module which gets every single isotope/remnant mass from the database which is later used to interpolate from to obtain desired values """ db = stardb.load(dbfilename) # loads database nmass = db.nvalues[0] # finds the number of values masses = db.values[0][:nmass] #creates a vector of the initial masses isodb = stardb.load( os.path.expanduser( '~/python/project/znuc2012.S4.star.deciso.y.stardb.gz')) massnumber = [] for x in range(len(isodb.ions)): mn = isodb.ions[x].A massnumber.append(mn) massnumber = np.array(massnumber) np.save(os.path.expanduser('~/python/project/filestoload/Massnumber'), massnumber) ####################### # write all energy and mixing values energyvalues = np.unique(db.fielddata['energy']) mixingvalues = np.unique(db.fielddata['mixing']) masterremnant = [] # result will be a multidimensional array elementdata = [] isodata = [] r = len(db.ions) # for loop iteration w = len(isodb.ions) for energy in energyvalues: remmixingarray = [] # reinitialise the next dimension elmixingarray = [] isomixingarray = [] for mixing in mixingvalues: ii = np.logical_and(np.isclose(db.fielddata['energy'], energy), np.isclose(db.fielddata['mixing'], mixing)) mass = db.fielddata[ii]['remnant'] remmixingarray.append( mass ) # this is an array of remnant masses for one energy and every mixing value elfill = [] # reinitialise the next dimension again isofill = [] for m in range(w): a = isodb.ions[m] #for obtaining the element string kk = np.where( isodb.ions == isotope.ion(a) ) # finding the indices in db.ions for a particular element jj = np.where(ii) isotopes = isodb.data[jj, kk][ 0] # array of abundances for that particular element isofill.append( isotopes ) # this is an array of element data for every mass for one energy and one mixing value isomixingarray.append(isofill) masterremnant.append( remmixingarray ) # these master arrays have every bit of data under its own energy. so called like elementdata[energy][mixing][elementnumber] gives the element data for every star for a single element. isodata.append(isomixingarray) np.save(os.path.expanduser('~/python/project/filestoload/IsoData'), isodata) np.save(os.path.expanduser('~/python/project/filestoload/RemnantMasses'), masterremnant) np.save(os.path.expanduser('~/python/project/filestoload/Ioninfo'), isodb.ions) time = [] for mass in masses: # for loop will cycle through the masses and grab the lifetime of each star s = str( mass) # converts the mass number to a string for file acquiring if s.endswith('.0'): # formatting issue, to match the filenames s = s[:-2] filename = os.path.expanduser( '~/python/project/dumps/z{}#presn').format(s) # grabs filename corrosponding to this mass d = kepdump.load(filename) # loads the kepdump data for this star time.append(d.time) yr = 365.2425 * 86400 time = np.array(time) / yr dataarray = [masses, time] return dataarray
def ext_ions(self): addions = ('nt1', 'h1', 'he4', 'be8') ions = set(self.ions) | set(isotope.ion(i) for i in addions) return IonList(sorted(list(ions)))
def __init__(self, ions = None, molfrac_in = None, molfrac_out = None, decay = True, isobars = False, elements = False, isotones = False, isotopes = True, solprod = False, stable = False, solions = False, sort = True, ionlist = None, addions = None, keepions = None, stabions = None, orgions = None, solabu = None, keepall = True, decayfile = '~/kepler/local_data/decay.dat', isomers = None, silent = False, debug = False, ): """ Initialize decay map. ions - list of input ions. Currently np.array of isotope.Ion. Probably should allow (or requre?) composition instead? Allow any string, array of strings, ... Same for all ion lists. decay - whether to do decay if disabled and no other option is used, the matrix will just do sorting, if specified molfrac_in - whether input is mass or abundance. molfrac_out - whether output is mass or abundance. TODO - add molfrac, used for both isobars - return result mapped to isobars [only stable or undecayed isotopes] elements - return result mapped to elements [only stable or undecayed isotopes] isotones - return result mapped to isotones [only stable or undecayed isotopes] isotopes - return result mapped to isotopes - if processing isomers ADD - isotopes - return result mapped to isotopes; isomers otherwise ADD - decay - do decay or not solabu - solar abundance set to use: filename, token, or isotope.SolAbu solprod - return result in terms of solar production factor [only stable isotopes] solions - return exactly ions in solar abundace set [plus addions, stabions, keepions] ionlist - output *exactly* these isotops stable - output only stable isotopes, discard chains addions - output these extra ions, but not add to EL/A sums keepions - ions to keep as stable but not add ion EL/A sums stabions - ions to fully treat as stable - may conflict with solprod orgions - ions for which to return the initial value w/o decays sort - sort output ions (Z > A > E), mostly relevant when customized ions are provided. keepall - if set to false, do not keep all ions even if stable set is used. decayfile - decay file to use. [should add allowing to pass object] isomers - process as isomers if input is isotopes silent - whether to be verbose or not """ self.setup_logger(silent) # ions = np.array([ion('pb220')]) # ions = np.array([ion('n13'), ion('o14'), ion('pb209')]) # ions = np.array([ion('n13'), ion('o14')]) ions, molfrac_in, molfrac_out = self.input2ionarr( ions, molfrac_in, molfrac_out) if isomers is None: isomers = np.any(ufunc_is_isomer(ions)) if not isomers and np.any(ufunc_is_isomer(ions)): new_ions = assert_isotope(ions) assert len(np.unique(ufunc_idx(new_ions))) == len(ions), 'Failed to project ions uniquely' ions = new_ions if isomers and not np.all(ufunc_is_isomer(ions)): ions = assert_isomer(ions) # all need to be the same. TODO - more sophisticated assert np.all(ufunc_is_isomer(ions) == ions[0].is_isomer()) if molfrac_in is None: molfrac_in = False if molfrac_out is None: molfrac_out = molfrac_in assert isinstance(ions, np.ndarray), "ions need to be np.array type" assert ions.ndim == 1, "ions need to be 1D array" assert isinstance(ions[0], Ion), "ions must be of type Ion" # find out whether we need list of stable ions need_stable = stable # TODO - check ionlist should exclude isobars, isotopes, ... need_isotopes = isotopes need_isobars = isobars need_isotones = isotones need_elements = elements # add things from ionlist if ionlist: need_isotopes |= np.any(ufunc_is_isotope(ionlist)) need_isobars |= np.any(ufunc_is_isobar(ionlist)) need_isotones |= np.any(ufunc_is_isotone(ionlist)) need_elements |= np.any(ufunc_is_element(ionlist)) if addions: need_isotopes |= np.any(ufunc_is_isotope(addions)) need_isobars |= np.any(ufunc_is_isobar(addions)) need_isotones |= np.any(ufunc_is_isotone(addions)) need_elements |= np.any(ufunc_is_element(addions)) need_orgions = orgions or not decay ### [WORK HERE] # THIS IS WRONG - ONLY IF DECAY # if need_elements or need_isobars or need_isotones or (need_isotopes and isomers): # need_stable = decay if need_elements or need_isobars or need_isotones: need_stable = decay self.ions = ions self.amax = np.max(ufunc_A(ions)) self.zmax = np.max(ufunc_Z(ions)) # check keepions if keepions is not None: if isinstance(keepions, IonSet): keepions = np.array(keepions) keepions = np.atleast_1d(keepions) assert isinstance(keepions, np.ndarray), "keepions need to be np.array type" assert keepions.ndim == 1, "keepions need to be 1D array" assert issubclass(type(keepions[0]),Ion), "keepions must be of type Ion" self.amax = max(self.amax, np.max(ufunc_A(keepions))) self.zmax = max(self.zmax, np.max(ufunc_Z(keepions))) # check stabions if stabions is not None: if isinstance(stabions, IonSet): stabions = np.array(stabions) stabions = np.atleast_1d(stabions) assert isinstance(stabions, np.ndarray), "stabions need to be np.array type" assert stabions.ndim == 1, "stabions need to be 1D array" assert issubclass(type(stabions[0]),Ion), "stabions must be of type Ion" self.amax = max(self.amax, np.max(ufunc_A(stabions))) self.zmax = max(self.zmax, np.max(ufunc_Z(stabions))) # check ionlist if ionlist is not None: if not isinstance(ionlist, np.ndarray): ionlist = np.array(ionlist) ionlist = np.atleast_1d(sn.squeeze(ionlist)) assert ionlist.ndim == 1, "ionlist need to be 1D array" assert issubclass(type(ionlist[0]),Ion), "ionlist must be of type Ion" self.amax = max(self.amax, np.max(ufunc_A(ionlist))) self.zmax = max(self.zmax, np.max(ufunc_Z(ionlist))) # generate decay data self.decdata = DecayData( filename = decayfile, amax = self.amax, zmax = self.zmax, isomers = isomers, silent = silent, debug = debug, ) # self.decdata = DecayData(decayfile, isomers = isomers, silent = silent) # now let's add keepions - not sure about this one - probably wrong if keepions is not None: # the strategy is to modify the decdata for ix in keepions: self.decdata.add_stable(ix) # ...and stabions if stabions is not None: # the strategy is modify the decdata for ix in stabions: self.decdata.add_stable(ix) # construct table from ions self.dectable = {} if decay: d0 = [self.iter_add_dectable(ix) for ix in self.ions] else: d0 = [self.identity_dectable(ix) for ix in self.ions] # let us just use indices into the array to speed things up self.d = np.ndarray(len(self.dectable), dtype = object) for dec in self.dectable.values(): self.d[dec[0][1]] = dec self.decions = np.array([ix for ix in self.dectable.keys()], dtype = object) self.indices = np.array([dec[0][1] for dec in self.dectable.values()], dtype = np.int64) # construct decay matrix - this should be its separate method!!! nions = len(ions) ndec = len(self.dectable) self.decmatrix = np.zeros([nions,ndec], dtype=np.float64) # compute needed decay matrix # # currently this is fixed when isomers are mapped to isotopes, # but this loses the 'maxradio' info for isomers. # # Instead, in the future, a full square decay matrix needs to # be constractued that can invert the isomer submatrix for # isotopes, and similar for all cases, releasing the # requirement for stable or raw isotopes only. # # the extra isotopes should be added after a first full decay # attempt. # # not sure this will ever be useful other than for isotopes. # if need_isotopes and not stable and decay and isomers: self.revind = np.argsort(self.indices) for i,ix in enumerate(self.ions): self.iter_add_decmatrix_iso(i, np.float64(1), d0[i]) else: for i,ix in enumerate(self.ions): self.iter_add_decmatrix(i, np.float64(1), d0[i]) m = self.decions.argsort() self.decions = self.decions[m] self.decmatrix = self.decmatrix[:,self.indices[m]] self.molfrac = [molfrac_in, molfrac_out] self.molfrac_convert(self.decmatrix) # we need to check whether we need solar abundances. need_solabu = solions or solprod # since we can get the list of stable ions from the decay table, # we do not really need this for definition of stable isotopes. if need_solabu: if isinstance(solabu, str): solabu = SolAbu(solabu) elif solabu is None: solabu = SolAbu() assert isinstance(solabu, SolAbu), "Need solar abundace data." # let us assure solar abundance pattern is sorted assert not np.any(solabu.iso.argsort() - np.arange(len(solabu))), "Solar pattern not sorted." # now we construct the map to solar, smap, and the map # from solabu to decions, rmap k = 0 smap = [] rmap = [] for i,iso in enumerate(solabu.iso): while self.decions[k] < iso: k += 1 if k == len(self.decions): break if k == len(self.decions): break if self.decions[k] == iso: smap.append(k) rmap.append(i) # why this ... not need_solabu ??? # why not have need_solabu inply need_stable? if need_stable and not need_solabu: # here we are overwring smap from above!!! # WHY??? # DELETE ??? smap = [i for i,ix in enumerate(self.decions) if len(self.dectable[ix]) == 1] # before we do any of the following, we still need to make # sure to only include stable isotopes. # proably best to keep old array and construct new one piece # by piece. if need_stable or need_solabu: stable_decions = self.decions[smap] stable_decmatrix = self.decmatrix[:,smap] # add missing solar ions # ??? UPDATE to include stab... if solions and len(solabu) > len(stable_decions): ndec = len(solabu) d = np.zeros([nions,ndec], dtype=np.float64) d[:,rmap] = stable_decmatrix[:,:] stable_decions = solabu.iso stable_decmatrix = d ### add missing ions from ion list # .... # can ion list be elements, isobars, isotones? # yes! # # 1) the stuff below needs to be termined which to compute # 2) store computed values separately - not right in decions # 3) then select the ecessary ones for final decions # maybe able to construct decay/isotop map? if need_isotopes and not stable and decay and isomers: # construct a decay matrix that does not double-count. # The key is here a function that finds entries with # identical projected values decidx = ufunc_isotope_idx(self.decions) idx = np.unique( decidx, ) decmatrix = self.decmatrix.copy() # but how to discard double counts??? # ??? mat coeff in range, -I, subtract entire column (mat mult) # submatrix = np.zeros([len(self.decions)]*2) for i in idx: ii = np.where(i == decidx)[0] if len(ii) > 1: deciso = self.decions[ii] # do we need to assume isomeric states are ordered, or that order matters? ions = [] iions = [] for jj,ix in enumerate(deciso): j = np.where(self.ions == ix)[0] if len(j) == 1: ions += [j[0]] iions += [ii[jj]] elif len(j) > 1: raise Exception('something is wrong here') print(self.decmatrix[np.ix_(ions,ii)].transpose()) # next: sort by chain? # find things that depend on each other, in order # then subtract in order # maybe subtract where things depend on self? # OK, for this to work we need to ensure all ions # on 'out' channels are also on 'in' channel so # the matrix can be 'inverted'. assert len(ii) == len(ions), 'matrix cannot be inverted' # currently, further up, we create a differnt # matrix for this case that does not require this # correction. In practice, we want to have both # options, the 'maxradio' for all sub-sets and # supersets. raise NotImplementedError() # *** temporary fix: if need_stable: raw_ions = stable_decions raw_matrix = stable_decmatrix else: raw_ions = self.decions raw_matrix = self.decmatrix if need_elements: decionz = ufunc_Z(raw_ions) elements_decmatrix, z = project(raw_matrix, decionz, return_values = True, axis = -1) elements_decions = np.array([ion(Z = Z) for Z in z]) if need_isobars: deciona = ufunc_A(raw_ions) isobars_decmatrix, a = project(raw_matrix, deciona, return_values = True, axis = -1) isobars_decions = np.array([ion(A = A) for A in a]) if need_isotones: decionn = ufunc_N(raw_ions) isotones_decmatrix, n = project(raw_matrix, decionn, return_values = True, axis = -1) isotones_decions = np.array([ion(N = N) for N in n]) # *** likely, this should not use raw_ions but self.decions if need_isotopes: deciiso = ufunc_isotope_idx(raw_ions) isotopes_decmatrix, i = project(raw_matrix, deciiso, return_values = True, axis = -1) isotopes_decions = np.array([ion(idx = I) for I in i]) if need_orgions: orgions_decmatrix = np.identity(len(self.ions)) orgions_decions = self.ions if solprod: # TODO raise NotImplementedError('solprod') # compile final decions set; make it an IonList if isobars: decmatrix = isobars_decmatrix decions = isobars_decions elif isotones: decmatrix = isotones_decmatrix decions = isotones_decions elif elements: decmatrix = elements_decmatrix decions = elements_decions elif isotopes: decmatrix = isotopes_decmatrix decions = isotopes_decions elif ionlist is not None: # TODO raise NotImplementedError('ionlist') else: decmatrix = self.decmatrix decions = self.decions # clean up self.decions = decions self.map = decmatrix del self.decmatrix self.close_logger(timing = 'matrix constructed in')
def __init__(self, z = 1, silent = False, check = None, molfrac = False, numfrac = False, massfrac = None, metal = None, href = False, ): """ Generate abundances set using z = Z/Z_sun, the scale relative to solar, if z is positive; absolute metallicity Z = -z if z is negative. molfrac = metallicity by mol fraction numfrac = metallicity by number fraction massfrac = metallicity by mass fraction [default] metal = normalize to given metal, e.g., 'Fe' href = use fraction relative to hydrogen, e.g., to compute [Fe/H] """ super().__init__() self.setup_logger(silent = silent) if check is None: check = self._check_default if check: numiso = len(self.ions) # assert self.ions == IonList(SolAbu('Lo09').ions()) assert self.masses.shape[0] == numiso if massfrac is True: assert molfrac == numfrac == False else: massfrac = not (molfrac or numfrac) assert np.count_nonzero([massfrac, molfrac, numfrac]) == 1 if metal is not None: ion_ref = ion(metal) Z_slice = np.where(ufunc_Z(np.array(self.ions)) == ion_ref.Z)[0] else: Z_slice = None self.Z_slice = Z_slice self.numfrac = numfrac self.molfrac = molfrac self.href = href self.solar_abu = self._abu(1.) self.solar_metal = self._abu_metallicity(self.solar_abu, Z_slice = self.Z_slice) if href: self.h_sun = self._abu_metallicity(self.solar_abu, Z_slice = self.hydrogen_slice) # negative values are interpreted as absolute values of z # rather than scale factor if z < 0: z = -z / self.solar_metal x = self._find_x(z) abu = self._abu(x, molfrac = False, numfrac = False) self.abu = abu self.iso = np.array(self.ions) self.normalize() self.is_sorted = True self.sort() self.close_logger(timing='Abundance set generated in')
def __init__(self, z = 1, silent = False, check = None, molfrac = False, numfrac = False, massfrac = None, metal = None, href = False, ): """ Generate abundances set using z = Z/Z_sun, the scale relative to solar, if z is positive; absolute metallicity Z = -z if z is negative. molfrac = metallicity my mol fraction numfrac = metallicity my number fraction massfrac = metallicity my mass fraction [default] metal = normalize to given metal, e.g., 'Fe' href = use fraction relative to hydrogen, e.g., to compute [Fe/H] """ super().__init__() self.setup_logger(silent = silent) if check is None: check = self._check_default if check: numiso = len(self.ions) # assert self.ions == IonList(SolAbu('Lo09').ions()) assert self.masses.shape[0] == numiso if massfrac is True: assert molfrac == numfrac == False else: massfrac = not (molfrac or numfrac) assert np.count_nonzero([massfrac, molfrac, numfrac]) == 1 if metal is not None: ion_ref = ion(metal) Z_slice = np.where(ufunc_Z(np.array(self.ions)) == ion_ref.Z)[0] else: Z_slice = None self.Z_slice = Z_slice self.numfrac = numfrac self.molfrac = molfrac self.href = href self.solar_abu = self._abu(1.) self.solar_metal = self._abu_metallicity(self.solar_abu, Z_slice = self.Z_slice) if href: self.h_sun = self._abu_metallicity(self.solar_abu, Z_slice = self.hydrogen_slice) # negative values are interpreted as absolute values of z # rather than scale factor if z < 0: z = -z / self.solar_metal x = self._find_x(z) abu = self._abu(x, molfrac = False, numfrac = False) self.abu = abu self.iso = np.array(self.ions) self.normalize() self.is_sorted = True self.sort() self.close_logger(timing='Abundance set generated in')
def __init__(self, filename = None, amax = 999, zmax = 999, isomers = False, silent = False, debug = True, nuclei = True, ): """ Load decay file. format c12 [BR] [decay | output]+ examples c9 1 p b8m3 b8m3 g b8m2 b- c8 c8 EC b8m1 b8m1 g b8 b+ be8 be8 2a he4 h1 """ self.setup_logger(silent) self.isomers = isomers self.decayfile = os.path.expandvars(os.path.expanduser(filename)) decdata = {} with open(self.decayfile,'rt') as f: self.logger.info('Loading {} ({})' .format(f.name, byte2human(os.fstat(f.fileno()).st_size))) for s in f: if debug: print('\nprocessing: ' + s.strip()) if s.startswith(';'): continue if len(s.strip()) == 0: continue reac = s.split() ix = ion(reac[0], isomer = isomers) if debug: print('ion = ', ix) if ix.A > amax and ix.Z > zmax: continue if isomers: ix = assert_isomer([ix])[0] else: ix = assert_isotope([ix])[0] if ix is None: continue if debug: print('ion = ', ix) assert ix != Ion.VOID, "Ion name not valid: " + str(ix) i = 1 if len(reac) > i: try: br = np.float64(reac[i]) i += 1 except ValueError: br = np.float64(1.) else: br = np.float64(1.) # we shall accept "empty" as stable.... decays = [] ions = [] for r in reac[i:]: try: d = DecIon(r) except: d = VOID if d == VOID: jx = ion(r, isomer = isomers) ions += [jx] else: decays += [d] if len(reac) == 1: decays += [DecIon('s')] for dec in decays: for p,n in dec.particles().items(): if n < 0: p = -p n = -n ions = [p] * n + ions for jj,jx in enumerate(ions): if jx.is_nucleus(): if isomers: jx = assert_isomer([jx])[0] else: jx = jx.isotope() ions[jj] = jx Z,A,E = ix.ZAE() for i in ions: Z -= i.Z A -= i.A E -= i.E if not (A == 0 and Z == 0): assert Z <= A, 'something is wrong' if isomers: if not (ix.A == A and ix.Z == Z): E = 0 else: E = None out = ion(Z=Z, A=A, E=E) if not out == ix: ions += [out] # filter for nucleons if nuclei: decions = [i for i in ions if i.is_nucleus()] else: decions = ions decinfo = [br] + decions # store info if ix not in decdata: decdata[ix] = [] decdata[ix].append(tuple(decinfo)) # check BRs and ion lists decions = set() for ix, products in decdata.items(): BR = np.sum([x[0] for x in products]) assert BR == 1, "BR don't add up {!s:}: {}".format(ix, BR) for x in products: for i in x[1:]: decions |= {i} missing = decions - set(decdata) if len(missing) != 0: s = "missing ions from decdata: {}".format(', '.join([str(i) for i in missing])) if debug: self.logger.critical(s) else: self.logger.debug(s) self.decdata = decdata self.close_logger(timing = 'data loaded in')