def __init__(self, t=0.0, phi=0.0, temp=300.0, tol=1e-7, cwd='', base_tape9=origen22.BASE_TAPE9, xscache=None, o2exe='o2_therm_linux.exe', *args, **kwargs): """Parameters ---------- t : float Transmutations time [sec]. phi : float or array of floats Neutron flux vector [n/cm^2/sec]. Currently this must either be a scalar or match the group structure of EAF. temp : float, optional Temperature [K] of material, defaults to 300.0. tol : float Tolerance level for chain truncation. cwd : str, optional Current working directory for origen runs. Defaults to this dir. base_tape9 : str or dict, optional A base TAPE9.INP file. If this is a str it is interpreted as a path to a file, which is then read in and parsed. If this is a dict, it is assumed to be in the format described in the main origen22 module. xscache : XSCache, optional A cross section cache to generate cross sections with. o2exe : str, optional Name or path to ORIGEN 2.2 executable. args : tuple, optional Other arguments ignored for compatibility with other Transmuters. kwargs : dict, optional Other keyword arguments ignored for compatibility with other Transmuters. """ if not isinstance(base_tape9, Mapping): base_tape9 = origen22.parse_tape9(tape9=base_tape9) self.base_tape9 = base_tape9 if xscache is None: eafds = EAFDataSource() eafds.load(temp=temp) gs = np.array([eafds.src_group_struct[0], eafds.src_group_struct[-1]]) eafds.dst_group_struct = gs xscache = XSCache(group_struct=gs, data_source_classes=[SimpleDataSource, NullDataSource]) xscache.load(temp=temp) xscache.data_sources.insert(0, eafds) self.xscache = xscache self.t = t self._phi = None self.phi = phi self.temp = temp self.tol = tol self.cwd = os.path.abspath(cwd) self.o2exe = o2exe
def __init__(self, rc): self.rc = rc self.statelibs = {} self.builddir = 'build-' + rc.reactor if not os.path.isdir(self.builddir): os.makedirs(self.builddir) self.eafds = data_source.EAFDataSource() self.omcds = data_source.OpenMCDataSource( cross_sections=rc.openmc_cross_sections, src_group_struct=rc.openmc_group_struct) data_sources = [self.omcds] if not rc.is_thermal: data_sources.append(self.eafds) data_sources += [data_source.SimpleDataSource(), data_source.NullDataSource()] for ds in data_sources[:]: ds.load(rc.temperature) self.xscache = XSCache(data_sources=data_sources, scalars={922380000: 1.05}) self.xscache.load() self.tape9 = None if self.rc.origen_call is NotSpecified: if self.rc.is_thermal: self.origen_call = "o2_therm_linux.exe" else: self.origen_call = "o2_fast_linux.exe" else: self.origen_call = self.rc.origen_call
def __init__(self, t=0.0, phi=0.0, temp=300.0, tol=1e-7, rxs=None, log=None, *args, **kwargs): """Parameters ---------- t : float Transmutations time [sec]. phi : float or array of floats Neutron flux vector [n/cm^2/sec]. Currently this must either be a scalar or match the group structure of EAF. temp : float, optional Temperature [K] of material, defaults to 300.0. tol : float Tolerance level for chain truncation. rxs : set of ints or strs Reaction ids or names to use in transmutation which produce well-defined children. This set should thus not include fission. If None, then the reactions from EAF are used. log : file-like or None The log file object should be written. A None imples the log is not desired. args : tuple, optional Other arguments ignored for compatibility with other Transmuters. kwargs : dict, optional Other keyword arguments ignored for compatibility with other Transmuters. """ eafds = EAFDataSource() eafds.load(temp=temp) gs = np.array([eafds.src_group_struct[0], eafds.src_group_struct[-1]]) eafds.dst_group_struct = gs self.xscache = XSCache(group_struct=gs, data_source_classes=(NullDataSource, )) self.xscache.data_sources.insert(0, eafds) self.t = t self._phi = None self.phi = phi self.temp = temp self.log = log self.tol = tol if rxs is None: rxs = [ 'gamma', 'gamma_1', 'gamma_2', 'p', 'p_1', 'p_2', 'd', 'd_1', 'd_2', 't', 't_1', 't_2', 'He3', 'He3_1', 'He3_2', 'a', 'a_1', 'a_2', 'z_2a', 'z_2p', 'z_2p_1', 'z_2p_2', 'z_2n', 'z_2n_1', 'z_2n_2', 'z_3n', 'z_3n_1', 'z_3n_2', 'na', 'na_1', 'na_2', 'z_2na', 'np', 'np_1', 'np_2', 'n2a', 'nd', 'nd_1', 'nd_2', 'nt', 'nt_1', 'nt_2', 'nHe3', 'nHe3_1', 'nHe3_2', 'z_4n', 'z_4n_1', 'n', 'n_1', 'n_2', 'z_3np' ] rxs = set([rxname.id(rx) for rx in rxs]) rxs.discard(rxname.id('fission')) self.rxs = rxs
def main_gen(ns): """Generates an open TAPE9.INP file. by default this only uses completely open data. """ files = glob(os.path.join(ns.build_dir, 'ENSDF', 'ensdf.*')) if len(files) == 0: grab_ensdf_decay(ns.build_dir) files = glob(os.path.join(ns.build_dir, 'ENSDF', 'ensdf.*')) print("parsing ENSDF decay data") decays, branches = parse_ensdf(files) print("creating ORIGEN decay libraries") t9decay = gendecay(decays, branches, metastable_cutoff=ns.metastable_cutoff) print("creating ORIGEN cross section libraries") xsc = XSCache(data_source_classes=[EAFDataSource, SimpleDataSource, NullDataSource]) xsc.load() t9xsfpy = origen22.xslibs(xscache=xsc, verbose=True) t9 = origen22.merge_tape9([t9decay, t9xsfpy]) origen22.write_tape9(t9, outfile=ns.filename)
def main_gen(ns): """Generates an open TAPE9.INP file. by default this only uses completely open data. """ files = glob(os.path.join(ns.build_dir, 'ENSDF', 'ensdf.*')) if len(files) == 0: grab_ensdf_decay(ns.build_dir) files = glob(os.path.join(ns.build_dir, 'ENSDF', 'ensdf.*')) print("parsing ENSDF decay data") decays, branches = parse_ensdf(files) print("creating ORIGEN decay libraries") t9decay = gendecay(decays, branches, metastable_cutoff=ns.metastable_cutoff) print("creating ORIGEN cross section libraries") xsc = XSCache( data_source_classes=[EAFDataSource, SimpleDataSource, NullDataSource]) xsc.load() t9xsfpy = origen22.xslibs(xscache=xsc, verbose=True) t9 = origen22.merge_tape9([t9decay, t9xsfpy]) origen22.write_tape9(t9, outfile=ns.filename)
def test_xslibs(): exp = { 42: { "_type": "xsfpy", "_subtype": "activation_products", "title": "PyNE Cross Section Data for Activation Products", }, 43: { "_type": "xsfpy", "_subtype": "actinides", "title": "PyNE Cross Section Data for Actinides & Daughters", }, 44: { "_type": "xsfpy", "_subtype": "fission_products", "title": "PyNE Cross Section Data for Fission Products", }, } xsc = XSCache(data_sources=[NullDataSource]) nucs = [922350000, 10010000, 461080000] obs = origen22.xslibs(nucs=nucs, xscache=xsc, nlb=(42, 43, 44)) obs_meta = {} for n in exp: obs_meta[n] = {} for field in ["_type", "_subtype", "title"]: obs_meta[n][field] = obs[n][field] assert_equal(exp, obs_meta) for n in exp: for field in obs[n]: if not field.startswith("sigma_"): continue assert_true(all([v == 0.0 for v in obs[n][field].values()])) assert_true( set(obs[42].keys()) >= set(origen22.ACTIVATION_PRODUCT_FIELDS + origen22.XSFPY_FIELDS) ) assert_true( set(obs[43].keys()) >= set(origen22.ACTINIDE_FIELDS + origen22.XSFPY_FIELDS) ) assert_true( set(obs[44].keys()) >= set(origen22.FISSION_PRODUCT_FIELDS + origen22.XSFPY_FIELDS) )
class OpenMCOrigen(object): """An that combines OpenMC for k-code calculations and ORIGEN for transmutation. """ reactions = {rxname.id(_) for _ in ('total', 'absorption', 'gamma', 'gamma_1', 'z_2n', 'z_2n_1', 'z_3n', 'proton', 'alpha', 'fission')} def __init__(self, rc): self.rc = rc self.statelibs = {} self.builddir = 'build-' + rc.reactor if not os.path.isdir(self.builddir): os.makedirs(self.builddir) self.eafds = data_source.EAFDataSource() self.omcds = data_source.OpenMCDataSource( cross_sections=rc.openmc_cross_sections, src_group_struct=rc.openmc_group_struct) data_sources = [self.omcds] if not rc.is_thermal: data_sources.append(self.eafds) data_sources += [data_source.SimpleDataSource(), data_source.NullDataSource()] for ds in data_sources[:]: ds.load(rc.temperature) self.xscache = XSCache(data_sources=data_sources, scalars={922380000: 1.05}) self.xscache.load() self.tape9 = None if self.rc.origen_call is NotSpecified: if self.rc.is_thermal: self.origen_call = "o2_therm_linux.exe" else: self.origen_call = "o2_fast_linux.exe" else: self.origen_call = self.rc.origen_call def pwd(self, state, directory): """Path to directory we will be running specific physics codes in. Parameters ---------- state : namedtuple (State) The state we are running physics codes for. directory : string The name of the sub-directory we would like. Returns ------- str The path to the desired directory. """ return os.path.join(self.builddir, str(hash(state)), directory) def context(self, state): """Unite parameters in the run-control file and the current state. Parameters ---------- state : namedtuple (State) A State tuple that contains perturbation parameters specific to that state. Returns ------- ctx : dict A dictionary with all relevant perturbation parameters. """ rc = self.rc ctx = dict(rc._dict) ctx.update(zip(rc.perturbation_params, state)) return ctx def generate_run(self, run, fname): """Generate transmutation tables, neutron production/destruction rates, and burnup statistics for a sequence of states with the same initial conditions. Parameters ---------- run : list of States A list of States that has the same initial conditions at increasing burnup times. Returns ------- libs : list of dicts Libraries to write out - one for the full fuel and one for each tracked nuclide. """ self.libs = {'xs': [], 'phi_g': { 'E_g': {'EAF': self.eafds.src_group_struct, 'OpenMC': self.omcds.src_group_struct}, 'phi_g': []}, "fuel": { "TIME": [0], "NEUT_PROD": [0], "NEUT_DEST": [0], "BUd": [0], "material": [self.rc.fuel_material], "tracked_nucs": {nucname.name(n): [self.rc.fuel_material.comp.get(n, 0) * 1000] for n in self.rc.track_nucs}, "phi_tot": [0] }} for nuc in self.rc.track_nucs: self.libs[nuc] = { "TIME": [0], "NEUT_PROD": [0], "NEUT_DEST": [0], "BUd": [0], "material": [Material({nuc: 1}, 1000)], "tracked_nucs": {nucname.name(n): [0] for n in self.rc.track_nucs}, "phi_tot": [0] } self.libs[nuc]["tracked_nucs"][nucname.name(nuc)] = [1000] print([state.burn_times for state in run]) for i, state in enumerate(run): if i > 0: transmute_time = state.burn_times - run[i-1].burn_times results = self.generate(state, transmute_time) self.libs = self._update_libs_with_results(self.libs, results) self.rc.writers[0].write(self.libs, fname) return self.libs def _update_libs_with_results(self, matlibs, newlibs): """Update a set of libraries with results from a single timestep in-place. Parameters ---------- matlibs : dict A set of libraries with nuclides as keys. newlibs: dict A set of libraries for a single timestep, with nuclides as keys. Returns ------- libs : dict The updated library. """ for mat, newlib in newlibs.items(): if mat == 'xs': matlibs[mat].append(newlib) continue elif mat == 'phi_g': matlibs[mat][mat].append(newlib) continue oldlib = matlibs[mat] for nuc in self.rc.track_nucs: name = nucname.name(nuc) nuc_frac = newlib["material"].comp.get(nuc, 0) mass = newlib["material"].mass oldlib["tracked_nucs"][name].append(nuc_frac * mass) for key, value in newlib.items(): if isinstance(key, int): continue else: oldlib[key].append(value) return matlibs def generate(self, state, transmute_time): """Runs physics codes on a specific state. First runs OpenMC for transport, then uses the results to run ORIGEN for a single timestep. Parameters ---------- state : namedtuple (State) A namedtuple containing the state parameters. transmute_time : float The length of the time step we would like to run ORIGEN for. Returns ------- results : dict Dict of physics code results. Keys are either nuclide ID's or "fuel" for the full fuel results. """ print("generating for a state with transmute_time {}".format(transmute_time)) if state in self.statelibs: return self.statelibs[state] rc = self.rc k, phi_g, xstab = self.openmc(state) results = {"fuel": {}} results.update(dict(zip(rc.track_nucs, [{} for _ in rc.track_nucs]))) if 'flux' in rc: phi_tot = state.flux elif 'fuel_specific_power' in rc: G = len(phi_g) fission_id = rxname.id("fission") if G == 1: fission_xs = {xs[0]: xs[2] * 1e-24 for xs in xstab # xs is in barns not cm2 if xs[1] == fission_id} else: fission_xs = {xs[0]: np.sum(xs[2]) * 1e-24 / len(xs[2]) for xs in xstab # xs is in barns not cm2 if xs[1] == fission_id} fuel_material = self.libs["fuel"]["material"][-1] fuel_material.atoms_per_molecule = sum([self.rc.fuel_chemical_form[m] for m in self.rc.fuel_chemical_form]) fuel_atom_frac = fuel_material.to_atom_frac() fuel_number_density = 6.022e23 * self.rc.fuel_density / \ (fuel_material.molecular_mass() * fuel_material.atoms_per_molecule) number_densities = {nuc: fuel_atom_frac[nuc] * fuel_number_density for nuc in fuel_material.comp} sum_N_i_sig_fi = sum([number_densities[nuc] * fission_xs.get(nuc, 0) for nuc in fuel_material.comp]) sum_N_i_sig_fi = sum_N_i_sig_fi[sum_N_i_sig_fi != 0] fuel_specific_power_mwcc = self.rc.fuel_density * 1e-6 * state.fuel_specific_power # see http://iriaxp.iri.tudelft.nl/~leege/SCALE44/origens.PDF for formula # (search for "the specific power due to fission", on p. 22 of the PDF) phi_tot = sum(3.125e16*fuel_specific_power_mwcc/sum_N_i_sig_fi) results = self.run_all_the_origens(state, transmute_time, phi_tot, results) results['xs'] = xstab results['phi_g'] = {'EAF': self.eafds.src_phi_g, 'OpenMC': self.omcds.src_phi_g} self.statelibs[state] = results statedir = os.path.join(self.builddir, str(hash(state))) for dir in os.listdir(self.builddir): if(os.path.join(self.builddir, dir) != statedir): shutil.rmtree(os.path.join(self.builddir, dir)) return results def run_all_the_origens(self, state, transmute_time, phi_tot, results): """Call ORIGEN as much as necessary and unite the results. Parameters ---------- state : namedtuple (State) A namedtuple containing the state parameters. transmute_time : float The length of the transmutation timestep. Has units of [days]. phi_tot : float The total neutron flux. results : dict A dict with material identifiers as keys, and dictionaries as values. The basic data structure to fill. Returns ------- dict A dict of all the ORIGEN results. """ if self.rc.verbose: print("making tape9 for {0} with phi={1}".format(state, phi_tot)) mat = self.libs['fuel']['material'][-1] mat.density = self.rc.fuel_density mat.atoms_per_molecule = 3.0 atom_dens = mat.to_atom_dens() for ds in self.xscache.data_sources: ds.atom_dens = atom_dens self.tape9 = origen22.make_tape9(self.rc.track_nucs, self.xscache, nlb=(219, 220, 221)) self.tape9 = origen22.merge_tape9((self.tape9, origen22.loads_tape9(brightlitetape9))) origen22.write_tape9(self.tape9) for mat_id in results.keys(): pwd = self.pwd(state, "origen{}".format(mat_id)) mat = self.libs[mat_id]["material"][-1] if not os.path.isdir(pwd): os.makedirs(pwd) with indir(pwd): if not os.path.isfile("TAPE6.OUT"): self._make_origen_input(transmute_time, phi_tot, mat) origen_results = [] if self.rc.threads == 1: for mat_id in results.keys(): pwd = self.pwd(state, "origen{}".format(mat_id)) origen_params = (state.burn_times, transmute_time, phi_tot, mat_id, self.libs[mat_id]["material"][-1], pwd, self.origen_call) origen_results.append(_origen(origen_params)) else: origen_params_ls = [(state.burn_times, transmute_time, phi_tot, mat_id, self.libs[mat_id]["material"][-1], self.pwd(state, "origen{}".format(mat_id)), self.origen_call) for mat_id in results] pool = Pool(self.rc.threads) origen_results = pool.map(_origen, origen_params_ls) pool.close() pool.join() for result in origen_results: result[1]["material"] = Material(dict(result[1]["material"]), 1000, attrs={"units": "g"}) return dict(origen_results) def openmc(self, state): """Runs OpenMC for a given state. Parameters ---------- state : namedtuple (State) A namedtuple containing the state parameters. Returns ------- k : float Neutron multiplication factor. phi_g : list of floats Group flux. xstab : list of tuples A list of tuples of the format (nuc, rx, xs). """ # make inputs pwd = self.pwd(state, "omc") if not os.path.isdir(pwd): os.makedirs(pwd) self._make_omc_input(state) statepoint = _find_statepoint(pwd) if statepoint is None: with indir(pwd): subprocess.check_call(['openmc', '-s', '{}'.format(self.rc.threads)]) statepoint = _find_statepoint(pwd) # parse & prepare results k, phi_g, e_g = self._parse_statepoint(statepoint) if self.rc.plot_group_flux: plot_e_g, plot_phi_g = self._find_plot_data(statepoint) with indir(pwd): self._plot_group_flux(plot_e_g, plot_phi_g) xstab = self._generate_xs(e_g, phi_g) return k, phi_g, xstab def _find_plot_data(self, statepoint_path): sp = statepoint.StatePoint(statepoint_path) tally = sp.tallies[3].get_values(['flux']) phi_g = tally.flatten() phi_g /= phi_g.sum() e_g = sp.tallies[3].find_filter('energy').bins return e_g, phi_g def _plot_group_flux(self, e_g, phi_g): """Plot the group flux output by OpenMC and save plot to file. Parameters ---------- e_g : array Energy bins phi_g : array Flux values """ fig = plt.figure(figsize=(12, 8)) plt.loglog(*stair_step(e_g, phi_g), figure=fig) plt.title("Flux vs Energy in") plt.xlabel('E [MeV]') plt.ylabel('Flux [N/cm$^2\cdot$s]') plt.savefig("flux") plt.close() def _make_omc_input(self, state): """Make OpenMC input files for a given state. Parameters ---------- state : namedtuple (State) A namedtuple containing the state parameters. Returns ------- None """ pwd = self.pwd(state, "omc") ctx = self.context(state) rc = self.rc # settings settings = SETTINGS_TEMPLATE.format(**ctx) with open(os.path.join(pwd, 'settings.xml'), 'w') as f: f.write(settings) # materials valid_nucs = self.nucs_in_cross_sections() # discard Cd-119m1 as a valid nuc valid_nucs.discard(481190001) ctx['_fuel_nucs'] = _mat_to_nucs(rc.fuel_material[valid_nucs]) curr_fuel = self.libs['fuel']['material'][-1][valid_nucs] for nuc in curr_fuel.comp: if curr_fuel.comp[nuc] < self.rc.track_nuc_threshold: del curr_fuel.comp[nuc] ctx['_fuel_nucs'] = _mat_to_nucs(curr_fuel[valid_nucs]) ctx['_clad_nucs'] = _mat_to_nucs(rc.clad_material[valid_nucs]) ctx['_cool_nucs'] = _mat_to_nucs(rc.cool_material[valid_nucs]) materials = MATERIALS_TEMPLATE.format(**ctx) with open(os.path.join(pwd, 'materials.xml'), 'w') as f: f.write(materials) # geometry ctx['lattice'] = ctx['lattice'].strip().replace('\n', '\n ') ctx['_latt_shape0'] = ctx['lattice_shape'][0] ctx['_latt_shape1'] = ctx['lattice_shape'][1] ctx['_latt_x_pitch'] = ctx['unit_cell_pitch'] * ctx['lattice_shape'][0] ctx['_latt_y_pitch'] = ctx['unit_cell_pitch'] * ctx['lattice_shape'][1] ctx['_latt_x_half_pitch'] = ctx['_latt_x_pitch'] / 2.0 ctx['_latt_y_half_pitch'] = ctx['_latt_y_pitch'] / 2.0 geometry = GEOMETRY_TEMPLATE.format(**ctx) with open(os.path.join(pwd, 'geometry.xml'), 'w') as f: f.write(geometry) # tallies ctx['_egrid'] = " ".join(map(str, sorted(ctx['group_structure']))) ctx['_eafds_egrid'] = " ".join(map(str, sorted(self.eafds.src_group_struct))) ctx['_omcds_egrid'] = " ".join(map(str, sorted(self.omcds.src_group_struct))) # nucs = core_nucs & valid_nucs tallies = TALLIES_TEMPLATE.format(**ctx) with open(os.path.join(pwd, 'tallies.xml'), 'w') as f: f.write(tallies) # plots plots = PLOTS_TEMPLATE.format(**ctx) with open(os.path.join(pwd, 'plots.xml'), 'w') as f: f.write(plots) def nucs_in_cross_sections(self): """Returns the set of nuclides present in the cross_sections.xml file. Returns ------- nucs : set A set of all nuclides known to OpenMC, in ID form. """ return {n.nucid for n in self.omcds.cross_sections.ace_tables if n.nucid is not None} def _parse_statepoint(self, statepoint_path, tally_id=1): """Parses a statepoint file and reads in the relevant fluxes, assigns them to the DataSources or the XSCache, and returns k, phi_g, and E_g. Parameters ---------- statepoint : xsgen.statepoint.StatePoint An OpenMC StatePoint. tally_id : int The tally id we wish to read group flux from. Returns ------- k : float Neutron multiplication factor. phi_g : list of floats Group flux. e_g : list of floats Group structure. """ sp = statepoint.StatePoint(statepoint_path) temp_tally = [] temp_tally.append(sp.tallies[2].get_values(['flux']).flatten()) temp_tally.append(sp.tallies[3].get_values(['flux']).flatten()) # compute group fluxes for data sources for tally, ds in zip(temp_tally[:2], (self.eafds, self.omcds)): ds.src_phi_g = np.array(tally[::-1]) ds.src_phi_g /= tally.sum() # compute return values k, kerr = sp.k_combined tally = sp.tallies[tally_id].get_values(['flux']) phi_g = tally.flatten() phi_g /= phi_g.sum() e_g = sp.tallies[tally_id].find_filter('energy').bins e_g = e_g[::-1] return k, phi_g, e_g def _generate_xs(self, e_g, phi_g): """Grab xs data from cache depending on group flux. Parameters ---------- e_g : list of floats Group structure. phi_g : list of floats Group flux. Returns ------- data : list of tuples A list of tuples of the format (nuc, rx, xs). """ rc = self.rc verbose = rc.verbose xscache = self.xscache xscache.clear() xscache['E_g'] = e_g xscache['phi_g'] = phi_g G = len(phi_g) temp = rc.temperature rxs = self.reactions nucs = rc.track_nucs dt = np.dtype([('nuc', 'i4'), ('rx', np.uint32), ('xs', 'f8', G)]) data = np.empty(len(nucs)*len(rxs), dtype=dt) i = 0 for nuc in nucs: for rx in rxs: try: xs = xscache[nuc, rx, temp] except KeyError: continue if(len(xs) < G): continue if verbose: print("OpenMC XS:", nucname.name(nuc), rxname.name(rx), xs, type(xs), temp) data[i] = nuc, rx, xs i += 1 return data def _make_origen_input(self, transmute_time, phi_tot, mat): """Make ORIGEN input files for a given state. Parameters ---------- transmute_time : float The time, in days, to run the transmutation for. phi_tot: float Total neutron flux. mat : pyne.material.Material The fuel material to transmute. Returns ------- None """ # may need to filter tape4 for Bad Nuclides # if sum(mat.comp.values()) > 1: for nuc in mat.comp: if mat.comp[nuc] < self.rc.track_nuc_threshold: del mat.comp[nuc] origen22.write_tape4(mat) origen22.write_tape5_irradiation("IRF", transmute_time, phi_tot, xsfpy_nlb=(219, 220, 221), cut_off=self.rc.track_nuc_threshold) origen22.write_tape9(self.tape9)