diff --git a/doc/fileformatspec/depcode_input.rst b/doc/fileformatspec/depcode_input.rst new file mode 100644 index 000000000..043f89ba2 --- /dev/null +++ b/doc/fileformatspec/depcode_input.rst @@ -0,0 +1,546 @@ +.. _depcode_input: + +``depcode`` Properties +====================== + +The ``depcode`` property has a large number of potential inputs depending on which depletion code we are using. The generic properties are given directly +below, and code-specific properties are given in their appropriate subsections. + +.. note:: The code-specific properties add to or modify the existing generic + properties. They do not replace them, but there can be new ones. + +Generic properties +------------------ + +Required properties: ``codename``, ``template_input_file_path``, ``geo_file_paths``. + +.. _codename_property: + +``codename`` +~~~~~~~~~~~~ + + :description: + Name of depletion code + + :type: + ``string`` + + :enum: + ``serpent``, ``openmc`` + + +.. _exec_path_property: + +``exec_path`` +~~~~~~~~~~~~~ + + :description: + Path to depletion code executable + + :type: + ``string`` + + +.. _template_input_file_path_property: + +``template_input_file_path`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :description: + Path(s) to user's template depletion code input file(s) with reactor model + + +.. _geo_file_paths_property: + +``geo_file_paths`` +~~~~~~~~~~~~~~~~~~ + + :description: + Path(s) to geometry file(s) to switch to in depletion code runs + + :type: + ``array`` + + :items: + + :type: + ``string`` + + :minItems: + 1 + + :uniqueItems: + ``true`` + + +.. _serpent_specific_properties: + +Serpent-specific properties +--------------------------- + +.. _serpent_exec_path_property: + +``exec_path`` +~~~~~~~~~~~~~ + + :default: + ``sss2`` + + +.. _serpent_template_input_file_path_property: + +``template_input_file_path`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :pattern: + ``^(.\\/)*(.*)$`` + + +.. _openmc_specific_properties: + +OpenMC-specific properties +-------------------------- + +.. _openmc_exec_path_property: + +``exec_path`` +~~~~~~~~~~~~~ + + :description: + Path to OpenMC depletion script + + :const: + ``openmc_deplete.py`` + + :default: + ``openmc_deplete.py`` + + +.. _openmc_template_input_file_path_property: + +``template_input_file_path`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :description: + Paths to OpenMC template input files. + + :type: + ``object``. + + :required: + ``settings``, ``materials`` + + :properties: + + :settings: + + :description: + OpenMC settings file + + :type: + ``string`` + + :pattern: + ``^(.\\/)+(.+)\\.xml$`` + + :default: + ``settings.xml`` + + :materials: + + :description: + OpenMC materials file + + :type: + ``string`` + + :pattern: + ``^(.\\/)*(.*)\\.xml$`` + + :default: + ``materials.xml`` + + +.. _openmc_geo_file_paths_property: + +``geo_file_paths`` +~~~~~~~~~~~~~~~~~~ + + :items: + + :type: + ``string`` + + :pattern: + ``^(.\\/)*(.*)\\.xml$`` + + :default: + ``geometry.xml`` + + +.. _openmc_chain_file_path_property: + +``chain_file_path`` +~~~~~~~~~~~~~~~~~~~ + + :description: + Path to depletion chain file + + :pattern: + ``^(.\\/)*(.*)\\.xml$`` + + :type: + ``string`` + + +.. _opemc_depletion_settings_property: + +``depletion_settings`` +~~~~~~~~~~~~~~~~~~~~~~ + :description: + OpenMC depletion settings + + :type: + ``object``. + + :default: + ``{}`` + + :properties: + + :method: + + :description: + Integration method used for depletion + + :type: + ``string`` + + :enum: + ``cecm``, ``predictor``, ``cf4``, ``epc_rk4``, ``si_celi``, ``si_leqi``, + ``celi``, ``leqi`` + + :default: + ``predictor`` + + + :final_step: + + :description: + Indicate whether or not a transport solve should be run at the end of the + last timestep + + :type: + ``boolean`` + + :default: + ``true`` + + + :operator_kwargs: + + :description: + Keyword arguments passed to the depletion operator initalizer + + :type: + ``object`` + + :default: + ``{}`` + + :properties: + :ref:`openmc_operator_kwargs_properties` + + :output: + + :description: + Capture OpenMC output from standard out + + :type: + ``boolean`` + + :default: + ``true`` + + + :integrator_kwargs: + + :description: + Remaining keyword arguments for the depletion Integrator initalizer + + :type: + ``object`` + + :default: + ``{}`` + + :properties: + + :solver: + + :description: + Bateman equations solver type + + :type: + ``string`` + + :enum: + ``cram16``, ``cram48`` + + + :n_steps: + + :description: + Number of stochastic iterations for stochastic integrators + + :type: + ``number`` + + :minimum: + 1 + + +.. _openmc_operator_kwargs_properties: + +``operator_kwargs`` Properties +------------------------------ + +``diff_burnable_mats`` +~~~~~~~~~~~~~~~~~~~~~~ + + :description: + Whether to differentiate burnable materials with multiple instances. + + :type: + ``boolean`` + + :default: + ``false`` + + +``normalization_mode`` +~~~~~~~~~~~~~~~~~~~~~~ + + :description: + Indicate how tally resutls should be normalized + + :type: + ``string`` + + :enum: + ``energy-deposition``, ``fission-q``, ``source-rate`` + + :default: + ``fission-q`` + + +``fission_q`` +~~~~~~~~~~~~~ + + :description: + Path to fission Q values + + :default: + ``null`` + + +``dilute_initial`` +~~~~~~~~~~~~~~~~~~ + + :description: + Initial atom density to add for nuclides that are zero in initial + condition. + + :type: + ``number`` + + :minimum: + 0 + + :default: + 1000 + + +``fission_yield_mode`` +~~~~~~~~~~~~~~~~~~~~~~ + + :description: + Determine what fission energy helper is used + + :type: + ``string`` + + :enum: + ``constant``, ``cutoff``, ``average`` + + :default: + ``constant`` + + +``fission_yield_opts`` +~~~~~~~~~~~~~~~~~~~~~~ + + :description: + Arguments for the fission yield helper + + :default: + ``null``. See :ref:`openmc_constant_fission_yield_opts_properties` + and :ref:`openmc_cutoff_fission_yield_opts_properties` for object + properties when ``fission_yield_mode`` is ``constant`` and + ``cutoff``, respectively. + + +``reaction_rate_mode`` +~~~~~~~~~~~~~~~~~~~~~~ + + :description: + Indicate how one-group reaction rates should be calculated + + :type: + ``string`` + + :enum: + ``direct``, ``flux`` + + :default: + ``direct`` + + +``reaction_rate_opts`` +~~~~~~~~~~~~~~~~~~~~~~ + + :default: + ``null``. See :ref:`openmc_flux_reaction_rate_opts_properties` for + object properties when ``reaction_rate_mode`` is ``flux``. + + +``reduce_chain`` +~~~~~~~~~~~~~~~~ + + :description: + Whether or not to reduce the depletion chain. + + :type: + ``boolean`` + + :default: + ``false`` + + +``reduce_chain_level`` +~~~~~~~~~~~~~~~~~~~~~~ + + :description: + Depth of serach while reducing depletion chain + + :default: + ``null`` + + +.. _openmc_constant_fission_yield_opts_properties: + +``fission_yield_opts`` Properties -- ``constant`` fission yield mode +-------------------------------------------------------------------- + +``energy`` +~~~~~~~~~~ + + :description: + Energy of fission yield libraries [MeV] + + :type: + ``number`` + + +.. _openmc_cutoff_fission_yield_opts_properties: + +``fission_yield_opts`` Properties -- ``cutoff`` fission yield mode +------------------------------------------------------------------ + +``cutoff`` +~~~~~~~~~~ + + :description: + Cutoff energy in eV + + :type: + ``number`` + + +``thermal_energy`` +~~~~~~~~~~~~~~~~~~ + + :description: + Energy of yield data corresponding to thermal yields + + :type: + ``number`` + + +``fast_energy`` +~~~~~~~~~~~~~~~ + + :description: + Energy of yield data corresponding to fast yields + + :type: + ``number`` + + +.. _openmc_flux_reaction_rate_opts_properties: + +``reaction_rate_opts`` Properties -- ``flux`` reaction rate mode +---------------------------------------------------------------- + +``energies`` +~~~~~~~~~~~~ + + :description: + Energy group boundaries + + :type: + ``array`` + + :items: + + :type: + ``number`` + + :minItems: + 2 + + +``reactions`` +~~~~~~~~~~~~~ + + :description: + Reactions to tally + + :type: + ``array`` + + :items: + + :type: + ``string`` + + :minItems: + 1 + + +``nuclides`` +~~~~~~~~~~~~ + + :description: + Nuclides on which to tally reactions + + :type: + ``array`` + + :items: + + :type: + ``string`` + + :minItems: + 1 diff --git a/doc/fileformatspec/index.rst b/doc/fileformatspec/index.rst index 4c08dbc5e..bcee28b22 100644 --- a/doc/fileformatspec/index.rst +++ b/doc/fileformatspec/index.rst @@ -1,11 +1,28 @@ .. _fileformatspec: -========================== File Format Specifications ========================== +.. _fileformatspect_input_files: + +Input Files +----------- + +.. toctree:: + :maxdepth: 1 + + saltproc_input + depcode_input + simulation_input + reactor_input + + +.. _fileformatspec_output_files: + +Output Files +------------ + .. toctree:: :maxdepth: 1 - inputfile databasefile diff --git a/doc/fileformatspec/inputfile.rst b/doc/fileformatspec/inputfile.rst deleted file mode 100644 index 4afe867a4..000000000 --- a/doc/fileformatspec/inputfile.rst +++ /dev/null @@ -1,7 +0,0 @@ -=================== -SaltProc Input File -=================== - -.. note:: The ``allOf`` block in under ``depcode`` picks out the correct schema for ``template_inputfiles_path`` based on the depletion code. - -.. jsonschema:: ../../saltproc/input_schema.json diff --git a/doc/fileformatspec/reactor_input.rst b/doc/fileformatspec/reactor_input.rst new file mode 100644 index 000000000..c61378945 --- /dev/null +++ b/doc/fileformatspec/reactor_input.rst @@ -0,0 +1,118 @@ +.. _reactor_input: + +``reactor`` Properties +========================= + +Required properties: ``volume``, ``mass_flowrate``, ``power_levels``, +``depletion_timesteps``, ``timestep_units`` + +.. _volume_property: + +``volume`` +---------- + :description: + reactor core volume [cm^3] + + :type: + ``number`` + + :minimum: + 0 + + +.. _mass_flowrate_property: + +``mass_flowrate`` +----------------- + + :description: + Salt mass flowrate through reactor core [g/s] + + :type: + ``number`` + + :minimum: + 0 + + +.. _power_levels_property: + +``power_levels`` +---------------- + + :description: + Reactor power or power step list durng depletion step [W] + + :type: + ``array`` + + :items: + + :type: + ``number`` + + :minimum: + 0 + + :minItems: + 1 + + :uniqueItems: + ``false`` + + +.. _depletion_timesteps_property: + +``depletion_timesteps`` +----------------------- + + :description: + Depletion timestep size or list of timestep sizes + + :type: + ``array`` + + :items: + + :type: + ``number`` + + :minimum: + 0 + + :minItems: + 1 + + :uniqueItems: + ``false`` + +.. _timestep_type_property: + +``timestep_type`` +----------------- + + :description: + Depletion step type + + :type: + ``string`` + + :enum: + ``cumulative``, ``stepwise`` + + :default: + ``stepwise`` + +.. _timestep_unites_property: + +``timestep_units`` +------------------ + + :description: + Timestep unit + + :type: + ``string`` + + :enum: + ``s``, ``sec``, ``min``, ``minute``, ``h``, ``hr``, ``hour``, ``d``, ``day``, ``a``, ``year``, ``yr``, ``MWd/kg``, ``mwd/kg``, ``MWD/KG``, ``MWD/kg``, ``MWd/KG`` diff --git a/doc/fileformatspec/saltproc_input.rst b/doc/fileformatspec/saltproc_input.rst new file mode 100644 index 000000000..5c0065d59 --- /dev/null +++ b/doc/fileformatspec/saltproc_input.rst @@ -0,0 +1,121 @@ +.. _saltproc_input: + +SaltProc Input File +=================== + +The main SaltProc input file is a JSON file validated against a JSON schema. +In this section, we will describe the structure of this schema. The top level +datatype of the schema is a JSON ``object``. + +Required properties are as follows: ``proc_input_file``, ``dot_input_file``, ``output_path``, ``depcode``, ``simulation``, ``reactor``. + +.. _proc_input_file_property: + +``proc_input_file`` +------------------- + + :description: + File containing processing system objects + + :type: + ``string`` + + :pattern: + ``^(.*)\\.json$`` + + +.. _dot_input_file_property: + +``dot_input_file`` +------------------ + + :description: + Graph file containing processing system structure + + :type: + ``string`` + + :pattern: + ``^(.*)\\.dot$`` + + +.. _output_path_property: + +``output_path`` +--------------- + + :description: + Path output data storing folder + + :type: + ``string`` + + :pattern: + ``^(.\\/)*(.*)$`` + + :default: + ``saltproc_runtime`` + + +.. _n_depletion_steps_property: + +``n_depletion_steps`` +--------------------- + + :description: + Number of steps for constant power and depletion interval case + + :type: + ``number`` + + +.. _depcode_property: + +``depcode`` +----------- + + :description: + Depcode class input parameters + + :type: + ``object`` + + :default: + ``{}`` + + :properties: + :ref:`depcode_input` + +.. _simulation_property: + +``simulation`` +-------------- + + :description: + Simulation class input parameters + + :type: + ``object`` + + :default: + ``{}`` + + :properties: + :ref:`simulation_input` + +.. _reactor_property: + +``reactor`` +----------- + + :description: + Reactor class input parameters + + :type: + ``object``. See :ref:`reactor_input` for object properties. + + :default: + ``{}`` + + :properties: + :ref:`reactor_input` diff --git a/doc/fileformatspec/simulation_input.rst b/doc/fileformatspec/simulation_input.rst new file mode 100644 index 000000000..638f72ef9 --- /dev/null +++ b/doc/fileformatspec/simulation_input.rst @@ -0,0 +1,65 @@ +.. _simulation_input: + +``simulation`` Properties +========================= + +Required properties: ``sim_name``, ``db_name`` + +.. _sim_name_property: + +``sim_name`` +------------ + + :description: + Name of simulation + + :type: + ``string`` + + +.. _db_name_property: + +``db_name`` +----------- + + :description: + Output HDF5 database file name + + :type: + ``string`` + + :default: + ``saltproc_results.h5`` + + :pattern: + ``^(.*)\\.h5$`` + + +.. _restart_flag_property: + +``restart_flag`` +---------------- + + :description: + Restart simulation from the step when it stopped? + + :type: + ``boolean`` + + :default: + ``false`` + + +.. _adjust_geo_property: + +``adjust_geo`` +-------------- + + :description: + switch to another geometry when keff drops below 1? + + :type: + ``boolean`` + + :default: + ``false`` diff --git a/doc/releasenotes/v0.5.0.rst b/doc/releasenotes/v0.5.0.rst index 22a8e2a73..aa044cf76 100644 --- a/doc/releasenotes/v0.5.0.rst +++ b/doc/releasenotes/v0.5.0.rst @@ -97,18 +97,26 @@ Python API Changes - Input file format changes: + - Added default values for certain input parameters + - Added depletion settings for OpenMC + - ``num_depsteps`` → ``n_depletion_steps`` - ``depcode['template_inputfile_path']`` → ``depcode['template_input_file_path']`` - The ``depcode['template_input_file_path']`` input variable now has the following depletion-code-depentent types: - ``str`` for ``Serpent2`` - ``dict`` of ``str`` to ``str`` for ``openmc`` - - ``depcode['iter_inputfile']`` → (removed) - ``depcode['iter_matfile']`` → (removed) + - ``depcode['npop']`` → (removed) + - ``depcode['active_cycles']`` → (removed) + - ``depcode['inactive_cycles']`` → (removed) + - ``reactor['dep_step_length_cumulative']`` → ``depletion_timesteps`` + - (new) → ``reactor['timestep_type']`` + - (new) → ``reactor['timestep_units']`` -.. note:: While the ``iter_inputfile`` and ``iter_matfile`` variables have been removed from the saltproc input file, - they remain part of the ``DepcodeSerpent`` class as attributes. Their use is unchanged, save for the fact they can no - longer be initialized by the user during object creation. +.. note:: Variables that have been removed from the saltproc input file + remain part of the ``Depcode`` classes as attributes. Their use is unchanged, save for the fact they can no + longer be initialized by the user from the SaltProc input file. - New/changed classes, methods, and attributes: @@ -130,6 +138,7 @@ Python API Changes - ``write_depcode_input()`` → ``write_runtime_input()`` - ``iter_inputfile`` → ``runtime_inputfile`` - ``iter_matfile`` → ``runtime_matfile`` + - ``geo_files`` → ``geo_file_paths`` - ``DepcodeSerpent`` → ``SerpentDepcode`` @@ -153,6 +162,7 @@ Python API Changes - ``write_depcode_input()`` → ``write_runtime_input()`` - ``iter_inputfile`` → ``runtime_inputfile`` - ``iter_matfile`` → ``runtime_matfile`` + - ``geo_files`` → ``geo_file_paths`` - ``change_sim_par()`` → (deleted) - (new function) → ``get_neutron_settings()`` - (new function) → ``_get_burnable_materials_file()`` @@ -179,6 +189,12 @@ Python API Changes - ``calc_rem_efficiency()`` → ``calculate_removal_efficiency`` + - ``Reactor`` + + - ``dep_step_length_cumulative`` → ``depletion_timesteps`` + - (new) → ``timestep_units`` + - (new) → ``timestep_type`` + - ``Sparger`` - ``calc_rem_efficiency()`` → ``calculate_removal_efficiency`` diff --git a/examples/msbr/msbr_endfb71.serpent b/examples/msbr/msbr_endfb71.serpent index ee9c9bf7a..5f0cd9943 100644 --- a/examples/msbr/msbr_endfb71.serpent +++ b/examples/msbr/msbr_endfb71.serpent @@ -58,7 +58,7 @@ set sfylib "endfb71.sfy" % --- Neutron population and criticality cycles: -set pop 10000 125 25 1.0 1 %500 400 60 1.0 1 %30000 400 100 1.0 1 +set pop 300 400 10 set gcu -1 %set usym 0 3 2 0.0 0.0 0 90 diff --git a/examples/msbr/msbr_endfb71_main.json b/examples/msbr/msbr_endfb71_main.json index 485692235..2d0cd0d39 100644 --- a/examples/msbr/msbr_endfb71_main.json +++ b/examples/msbr/msbr_endfb71_main.json @@ -1,15 +1,11 @@ { "proc_input_file": "msbr_objects.json", "dot_input_file": "msbr.dot", - "output_path": "./data", - "num_depsteps": 12, + "n_depletion_steps": 12, "depcode": { "codename": "serpent", "exec_path": "sss2", "template_input_file_path": "./msbr_endfb71.serpent", - "npop": 50, - "active_cycles": 20, - "inactive_cycles": 20, "geo_file_paths": ["./geometry/msbr_full.ini"] }, "simulation": { @@ -22,6 +18,7 @@ "volume": 1.0, "mass_flowrate": 9920000, "power_levels": [ 2250000000 ], - "dep_step_length_cumulative": [ 3 ] + "depletion_timesteps": [3], + "timestep_units": "d" } } diff --git a/examples/msbr/msbr_main.json b/examples/msbr/msbr_main.json index dccab2b94..4862d4b53 100644 --- a/examples/msbr/msbr_main.json +++ b/examples/msbr/msbr_main.json @@ -1,24 +1,20 @@ { "proc_input_file": "msbr_objects.json", "dot_input_file": "msbr.dot", - "output_path": "data", - "num_depsteps": 12, + "n_depletion_steps": 12, "depcode": { "codename": "serpent", - "exec_path": "sss2", "template_input_file_path": "msbr.serpent", "geo_file_paths": ["geometry/msbr_full.ini"] }, "simulation": { - "sim_name": "msbr_example_simulation", - "db_name": "msbr_kl_100_saltproc.h5", - "restart_flag": false, - "adjust_geo": false + "sim_name": "msbr_kl_100_simulation" }, "reactor": { "volume": 1.0, "mass_flowrate": 9920000, "power_levels": [ 2250000000 ], - "dep_step_length_cumulative": [ 3 ] + "depletion_timesteps": [ 3 ], + "timestep_units": "d" } } diff --git a/examples/tap/tap_main.json b/examples/tap/tap_main.json index 7c2c5b0f4..d90380961 100644 --- a/examples/tap/tap_main.json +++ b/examples/tap/tap_main.json @@ -1,11 +1,9 @@ { "proc_input_file": "tap_objects.json", "dot_input_file": "tap.dot", - "output_path": "data", - "num_depsteps": 3, + "n_depletion_steps": 3, "depcode": { "codename": "serpent", - "exec_path": "sss2", "template_input_file_path": "tap.serpent", "geo_file_paths": [ "geometry/347_base.ini", @@ -26,15 +24,14 @@ ] }, "simulation": { - "sim_name": "test_simulation", - "db_name": "db_saltproc.h5", - "restart_flag": false, + "sim_name": "tap_example_simulation", "adjust_geo": true }, "reactor": { "volume": 1.0, "mass_flowrate": 9920000, "power_levels": [ 1250000000 ], - "dep_step_length_cumulative": [ 2 ] + "depletion_timesteps": [ 2 ], + "timestep_units": "d" } } diff --git a/saltproc/_schema_default.py b/saltproc/_schema_default.py new file mode 100644 index 000000000..f7a7a0bac --- /dev/null +++ b/saltproc/_schema_default.py @@ -0,0 +1,21 @@ +from jsonschema import Draft202012Validator, validators + +def extend_with_default(validator_class): + validate_properties = validator_class.VALIDATORS["properties"] + + def set_defaults(validator, properties, instance, schema): + for property, subschema in properties.items(): + if "default" in subschema: + instance.setdefault(property, subschema["default"]) + + for error in validate_properties( + validator, properties, instance, schema, + ): + yield error + + return validators.extend( + validator_class, {"properties" : set_defaults}, + ) + + +DefaultFillingValidator = extend_with_default(Draft202012Validator) diff --git a/saltproc/abc.py b/saltproc/abc.py index 4d04df979..65084ca17 100644 --- a/saltproc/abc.py +++ b/saltproc/abc.py @@ -31,7 +31,7 @@ def __init__(self, output_path, exec_path, template_input_file_path, - geo_files): + geo_file_paths): """Initialize a Depcode object. Parameters @@ -47,7 +47,7 @@ def __init__(self, the input type (e.g. material, geometry, settings, etc.) as a string are keys, and the path to the input file are values. Type depends on depletion code in use. - geo_files : str or list, optional + geo_file_paths : str or list, optional Path to file that contains the reactor geometry. List of `str` if reactivity control by switching geometry is `On` or just `str` otherwise. @@ -57,7 +57,7 @@ def __init__(self, self.output_path = output_path self.exec_path = exec_path self.template_input_file_path = template_input_file_path - self.geo_files = geo_files + self.geo_file_paths = geo_file_paths self.neutronics_parameters = {} self.step_metadata = {} self.runtime_inputfile = None @@ -115,7 +115,7 @@ def run_depletion_step(self, cores, nodes): @abstractmethod def switch_to_next_geometry(self): """Changes the geometry used in the depletion code simulation to the - next geometry file in ``geo_files`` + next geometry file in ``geo_file_paths`` """ @abstractmethod diff --git a/saltproc/app.py b/saltproc/app.py index 1ebcdf75a..91c5f4a63 100644 --- a/saltproc/app.py +++ b/saltproc/app.py @@ -13,6 +13,18 @@ from saltproc import SerpentDepcode, OpenMCDepcode, Simulation, Reactor from saltproc import Process, Sparger, Separator, Materialflow +# Validator that fills defualt values of JSON schema before validating +from saltproc._schema_default import DefaultFillingValidator + + +CODENAME_MAP = {'serpent': SerpentDepcode, + 'openmc': OpenMCDepcode} + +SECOND_UNITS = ('s', 'sec') +MINUTE_UNITS = ('min', 'minute') +HOUR_UNITS = ('h', 'hr', 'hour') +DAY_UNITS = ('d', 'day') +YEAR_UNITS = ('a', 'year', 'yr') def run(): """ Inititializes main run""" @@ -30,22 +42,22 @@ def run(): simulation.check_restart() # Run sequence # Start sequence - for dep_step in range(len(msr.dep_step_length_cumulative)): - print("\n\n\nStep #%i has been started" % (dep_step + 1)) + for step_idx in range(len(msr.depletion_timesteps)): + print("\n\n\nStep #%i has been started" % (step_idx + 1)) simulation.sim_depcode.write_runtime_input(msr, - dep_step, + step_idx, simulation.restart_flag) depcode.run_depletion_step(cores, nodes) - if dep_step == 0 and simulation.restart_flag is False: # First step + if step_idx == 0 and simulation.restart_flag is False: # First step # Read general simulation data which never changes simulation.store_run_init_info() - # Parse and store data for initial state (beginning of dep_step) + # Parse and store data for initial state (beginning of step_idx) mats = depcode.read_depleted_materials(False) - simulation.store_mat_data(mats, dep_step - 1, False) + simulation.store_mat_data(mats, step_idx - 1, False) # Finish of First step # Main sequence mats = depcode.read_depleted_materials(True) - simulation.store_mat_data(mats, dep_step, False) + simulation.store_mat_data(mats, step_idx, False) simulation.store_run_step_info() # Reprocessing here print("\nMass and volume of fuel before reproc: %f g, %f cm3" % @@ -75,12 +87,12 @@ def run(): # mats['ctrlPois'].vol)) print("Removed mass [g]:", extracted_mass) # Store in DB after reprocessing and refill (right before next depl) - simulation.store_after_repr(mats, waste_and_feed_streams, dep_step) + simulation.store_after_repr(mats, waste_and_feed_streams, step_idx) depcode.update_depletable_materials(mats, simulation.burn_time) del mats, waste_streams, waste_and_feed_streams, extracted_mass gc.collect() # Switch to another geometry? - if simulation.adjust_geo and simulation.read_k_eds_delta(dep_step): + if simulation.adjust_geo and simulation.read_k_eds_delta(step_idx): depcode.switch_to_next_geometry() print("\nTime at the end of current depletion step: %fd" % simulation.burn_time) @@ -88,7 +100,7 @@ def run(): '''print("Reactor object data.\n", msr.mass_flowrate, msr.power_levels, - msr.dep_step_length_cumulative)''' + msr.depletion_timesteps)''' def parse_arguments(): @@ -151,11 +163,11 @@ def read_main_input(main_inp_file): input_schema = (Path(__file__).parents[0] / 'input_schema.json') with open(main_inp_file) as f: - j = json.load(f) + input_parameters = json.load(f) with open(input_schema) as s: - v = json.load(s) + schema = json.load(s) try: - jsonschema.validate(instance=j, schema=v) + DefaultFillingValidator(schema).validate(input_parameters) except jsonschema.exceptions.ValidationError: print("Your input file is improperly structured.\ Please see saltproc/tests/test.json for an example.") @@ -165,36 +177,44 @@ def read_main_input(main_inp_file): # Saltproc settings process_file = str((input_path / - j['proc_input_file']).resolve()) + input_parameters['proc_input_file']).resolve()) dot_file = str(( input_path / - j['dot_input_file']).resolve()) - output_path = j['output_path'] - num_depsteps = j['num_depsteps'] + input_parameters['dot_input_file']).resolve()) + output_path = input_parameters['output_path'] + n_depletion_steps = input_parameters['n_depletion_steps'] # Global output path output_path = (input_path / output_path) - j['output_path'] = output_path.resolve() + input_parameters['output_path'] = output_path.resolve() + + # Create output directoy if it doesn't exist + if not Path(input_parameters['output_path']).exists(): + Path(input_parameters['output_path']).mkdir(parents=True) # Class settings - depcode_input = j['depcode'] - simulation_input = j['simulation'] - reactor_input = j['reactor'] + depcode_input = input_parameters['depcode'] + simulation_input = input_parameters['simulation'] + reactor_input = input_parameters['reactor'] - if depcode_input['codename'] == 'serpent': + codename = depcode_input['codename'].lower() + if codename == 'serpent': depcode_input['template_input_file_path'] = str(( input_path / depcode_input['template_input_file_path']).resolve()) - elif depcode_input['codename'] == 'openmc': + elif codename == 'openmc': for key in depcode_input['template_input_file_path']: value = depcode_input['template_input_file_path'][key] depcode_input['template_input_file_path'][key] = str(( input_path / value).resolve()) + depcode_input['chain_file_path'] = \ + str((input_path / + depcode_input['chain_file_path']).resolve()) else: - raise ValueError( - f'{depcode_input["codename"]} ' - 'is not a supported depletion code') + raise ValueError(f'{codename} is not a supported depletion code.' + ' Accepts: "serpent" or "openmc".') + depcode_input['codename'] = codename depcode_input['output_path'] = output_path geo_list = depcode_input['geo_file_paths'] @@ -209,12 +229,11 @@ def read_main_input(main_inp_file): simulation_input['db_name'] = str(db_name.resolve()) reactor_input = _process_main_input_reactor_params( - reactor_input, num_depsteps) + reactor_input, n_depletion_steps, depcode_input['codename']) return input_path, process_file, dot_file, ( depcode_input, simulation_input, reactor_input) - def _print_simulation_input_info(simulation_input, depcode_input): """Helper function for `run()` """ print('Initiating Saltproc:\n' @@ -231,20 +250,10 @@ def _print_simulation_input_info(simulation_input, depcode_input): def _create_depcode_object(depcode_input): """Helper function for `run()` """ - codename = depcode_input['codename'].lower() - if codename == 'serpent': - depcode = SerpentDepcode - elif codename == 'openmc': - depcode = OpenMCDepcode - else: - raise ValueError( - f'{codename} is not a supported depletion code.' - 'Accepts: "serpent" or "openmc".') - - depcode = depcode(depcode_input['output_path'], - depcode_input['exec_path'], - depcode_input['template_input_file_path'], - geo_files=depcode_input['geo_file_paths']) + codename = depcode_input.pop('codename') + depcode = CODENAME_MAP[codename] + depcode = depcode(**depcode_input) + depcode_input['codename'] = codename return depcode @@ -264,48 +273,89 @@ def _create_simulation_object(simulation_input, depcode, cores, nodes): def _create_reactor_object(reactor_input): """Helper function for `run()` """ - msr = Reactor( - volume=reactor_input['volume'], - mass_flowrate=reactor_input['mass_flowrate'], - power_levels=reactor_input['power_levels'], - dep_step_length_cumulative=reactor_input['dep_step_length_cumulative']) + msr = Reactor(**reactor_input) return msr -def _process_main_input_reactor_params(reactor_input, num_depsteps): +def _process_main_input_reactor_params(reactor_input, + n_depletion_steps, + codename): """ Process SaltProc reactor class input parameters based on the value and - data type of the `num_depsteps` parameter, and throw errors if the input - parameters are incorrect. + data type of the `n_depletion_steps` parameter as well as the depletion code + being used, and throw errors if the input parameters are incorrect. """ - dep_step_length_cumulative = reactor_input['dep_step_length_cumulative'] - power_levels = reactor_input['power_levels'] - if num_depsteps is not None and len(dep_step_length_cumulative) == 1: - if num_depsteps < 0.0 or not int: - raise ValueError('Depletion step interval cannot be negative') - # Make `power_levels` and `dep_step_length_cumulative` - # lists of length `num_depsteps` - else: - step = int(num_depsteps) - deptot = float(dep_step_length_cumulative[0]) * step - dep_step_length_cumulative = \ - np.linspace(float(dep_step_length_cumulative[0]), - deptot, - num=step) - power_levels = float(power_levels[0]) * \ - np.ones_like(dep_step_length_cumulative) - reactor_input['dep_step_length_cumulative'] = \ - dep_step_length_cumulative - reactor_input['power_levels'] = power_levels - elif num_depsteps is None and isinstance(dep_step_length_cumulative, - (np.ndarray, list)): - if len(dep_step_length_cumulative) != len(power_levels): - raise ValueError( - 'Depletion step list and power list shape mismatch') + + depletion_timesteps = np.array(reactor_input['depletion_timesteps']) + power_levels = np.array(reactor_input['power_levels']) + depletion_timesteps, power_levels = \ + _validate_depletion_timesteps_power_levels(n_depletion_steps, + depletion_timesteps, + power_levels) + + + if reactor_input['timestep_type'] == 'cumulative': + depletion_timesteps = _convert_cumulative_to_stepwise(depletion_timesteps) + + timestep_units = reactor_input['timestep_units'] + depletion_timesteps = _scale_depletion_timesteps(timestep_units, + depletion_timesteps, + codename) + + reactor_input['depletion_timesteps'] = list(depletion_timesteps) + reactor_input['power_levels'] = list(power_levels) return reactor_input +def _validate_depletion_timesteps_power_levels(n_depletion_steps, + depletion_timesteps, + power_levels): + """Ensure that the number of depletion timesteps and power levels match + `n_depletion_steps` if it is given. Otherwise, compare the lengths of + `depletion_timesteps` and `power_levels`""" + if n_depletion_steps is not None: + if n_depletion_steps < 0.0 or not int: + raise ValueError('There must be a positive integer number' + ' of depletion steps. Provided' + f' n_depletion_steps: {n_depletion_steps}') + if len(depletion_timesteps) == 1: + depletion_timesteps = np.repeat(depletion_timesteps, n_depletion_steps) + if len(power_levels) == 1: + power_levels = np.repeat(power_levels, n_depletion_steps) + + if len(depletion_timesteps) != len(power_levels): + raise ValueError('depletion_timesteps and power_levels length mismatch:' + f' depletion_timesteps has {len(depletion_timesteps)}' + f' entries and power_levels has {len(power_levels)}' + ' entries.') + else: + return depletion_timesteps, power_levels + + +def _convert_cumulative_to_stepwise(depletion_timesteps): + ts = np.diff(depletion_timesteps) + return np.concatenate(([depletion_timesteps[0]], ts)) + + +def _scale_depletion_timesteps(timestep_units, depletion_timesteps, codename): + """Scale `depletion_timesteps` to the correct value based on `timestep_units`""" + # serpent base timestep units are days or mwd/kg + if not(timestep_units in DAY_UNITS) and timestep_units.lower() != 'mwd/kg' and codename == 'serpent': + if timestep_units in SECOND_UNITS: + depletion_timesteps /= 60 * 60 * 24 + elif timestep_units in MINUTE_UNITS: + depletion_timesteps /= 60 * 24 + elif timestep_units in HOUR_UNITS: + depletion_timesteps /= 24 + elif timestep_units in YEAR_UNITS: + depletion_timesteps *= 365.25 + else: + raise IOError(f'Unrecognized time unit: {timestep_units}') + + return depletion_timesteps + + def reprocess_materials(mats, process_file, dot_file): """Applies extraction reprocessing scheme to burnable materials. diff --git a/saltproc/input_schema.json b/saltproc/input_schema.json index 995557c15..73ca0936e 100644 --- a/saltproc/input_schema.json +++ b/saltproc/input_schema.json @@ -17,15 +17,17 @@ "output_path": { "description": "Path output data storing folder", "type": "string", - "pattern": "^(.\\/)*(.*)$" + "pattern": "^(.\\/)*(.*)$", + "default": "saltproc_runtime" }, - "num_depsteps": { + "n_depletion_steps": { "description": "Number of steps for constant power and depletion interval case", "type": "number" }, "depcode": { "description": "Depcode class input parameters", "type": "object", + "default": {}, "properties": { "codename": { "description": "Name of depletion code", @@ -33,7 +35,7 @@ "enum": ["serpent", "openmc"]}, "exec_path": { "description": "Path to depletion code executable", - "type": "string" }, + "type": "string"}, "template_input_file_path": { "description": "Path(s) to user's template depletion code input file(s) with reactor model"}, "geo_file_paths": { @@ -41,40 +43,34 @@ "type": "array", "items": { "type": "string"}, "minItems": 1, - "uniqueItems": false - } + "uniqueItems": true} }, + "required": ["codename", "template_input_file_path", "geo_file_paths"], "allOf": [ { - "if": { - "properties": { "codename": { "const": "serpent" } } - }, + "if": {"properties": { "codename": { "const": "serpent" }}}, "then": { "properties": { "exec_path": { - "description": "Path to Serpent executable", - "type": "string", "default": "sss2"}, "template_input_file_path": { - "description": "Path to Serpent template inputfile", - "type": "string", "pattern": "^(.\\/)*(.*)$"} } - } + } }, { "if": { - "properties": { "codename": { "const": "openmc" } } - }, + "properties": { "codename": { "const": "openmc" }}}, "then": { "properties": { "exec_path": { "description": "Path to OpenMC depletion script", - "type": "string", + "const": "openmc_deplete.py", "default": "openmc_deplete.py"}, "template_input_file_path": { "description": "Paths to OpenMC template input files", "type": "object", + "required": ["settings", "materials"], "properties": { "settings": { "description": "OpenMC settings file", @@ -85,81 +81,236 @@ "description": "OpenMC materials file", "type": "string", "pattern": "^(.\\/)*(.*)\\.xml$", - "default": "materials.xml"}, - "chain_file": { - "description": "OpenMC depletion chain file", - "type": "string", - "pattern": "^(.\\/)*(.*)\\.xml$"} - }, - - "required": ["settings", "materials", "chain_file"] + "default": "materials.xml"} + } }, "geo_file_paths": { - "description": "Path(s) to geometry file(s) to switch to in OpenMC code runs", - "type": "array", "items": { "type": "string", "pattern": "^(.\\/)*(.*)\\.xml$"}, - "minItems": 1, - "default": ["geometry.xml"] + "default": ["geometry.xml"]}, + "chain_file_path": { + "description": "Path to depletion chain file", + "pattern": "^(.\\/)*(.*)\\.xml$", + "type": "string"}, + "depletion_settings" : { + "description": "OpenMC depletion settings", + "type": "object", + "default": {}, + "properties": { + "method": { + "description": "Integration method used for depletion", + "type": "string", + "enum": ["cecm", "predictor", "cf4", "epc_rk4", "si_celi", "si_leqi", "celi", "leqi"], + "default": "predictor"}, + "final_step": { + "description": "Indicate whether or not a transport solve should be run at the end of the last timestep", + "type": "boolean", + "default": true}, + "operator_kwargs": { + "description": "Keyword arguments passed to the depletion operator initalizer", + "type": "object", + "allOf": [ + { + "if": {"properties": { "fission_yield_mode": { "const": "constant" }}}, + "then": { + "properties": { + "fission_yield_opts": { + "type": "object", + "properties": { + "energy": { + "description": "Energy of fission yield libraries [MeV]", + "type": "number"} + } + } + } + } + }, + { + "if": {"properties": { "fission_yield_mode": { "const": "cutoff" }}}, + "then": { + "properties": { + "fission_yield_opts": { + "type": "object", + "properties": { + "cutoff": { + "description": "Cutoff energy in eV", + "type": "number"}, + "thermal_energy": { + "description": "Energy of yield data corresponding to thermal yields", + "type": "number"}, + "fast_energy": { + "description": "Energy of yield data corresponding to fast yields", + "type": "number"} + } + } + } + } + }, + { + "if": {"properties": { "reaction_rate_mode": { "const": "flux" }}}, + "then": { + "properties": { + "reaction_rate_opts": { + "type": "object", + "properties": { + "energies": { + "description": "Energy group boundaries", + "type": "array", + "items": { + "type": "number", + "minItems": 2} + }, + "reactions": { + "description": "Reactions to tally", + "type": "array", + "items": { + "type": "string", + "minItems": 1} + }, + "nuclides": { + "description": "Nuclides on which to tally reactions", + "type": "array", + "items": { + "type": "string", + "minItems": 1} + } + } + } + } + } + } + ], + "properties": { + "diff_burnable_mats": { + "description": "Whether to differentiate burnable materials with multiple instances.", + "type": "boolean", + "default": false}, + "normalization_mode": { + "description": "Indicate how tally resutls should be normalized", + "type": "string", + "enum": ["energy-deposition", "fission-q", "source-rate"], + "default": "fission-q"}, + "fission_q": { + "description": "Path to fission Q values", + "default": null}, + "dilute_initial": { + "description": "Initial atom density to add for nuclides that are zero in initial condition.", + "type": "number", + "minimum": 0, + "default": 1000}, + "fission_yield_mode": { + "description": "Determine what fission energy helper is used", + "type": "string", + "enum": ["constant", "cutoff", "average"], + "default": "constant"}, + "fission_yield_opts": { + "description": "Arguments for the fission yield helper", + "default": null}, + "reaction_rate_mode": { + "description": "Indicate how one-group reaction rates should be calculated", + "type": "string", + "enum": ["direct", "flux"], + "default": "direct"}, + "reaction_rate_opts": { + "default": null}, + "reduce_chain": { + "description": "Whether or not to reduce the depletion chain.", + "type": "boolean", + "default": false}, + "reduce_chain_level": { + "description": "Depth of serach while reducing depletion chain", + "default": null} + }, + "default": {} + }, + "output": { + "description": "Capture OpenMC output from standard out", + "type": "boolean", + "default": true}, + "integrator_kwargs": { + "description": "Remaining keyword arguments for the depletion Integrator initalizer", + "type": "object", + "properties": { + "solver": { + "description": "Bateman equations solver type", + "type": "string", + "enum": ["cram16", "cram48"]}, + "n_steps": { + "description": "Number of stochastic iterations for stochastic integrators", + "type": "number", + "minimum": 1} + }, + "default": {} + } + } } } } } - - ], - "required": ["codename", "exec_path", "template_input_file_path", "npop", "active_cycles","inactive_cycles", "geo_file_paths"] + ] }, - "simulation": { - "description": "Simulation class input parameters", - "type": "object", - "properties": { - "sim_name": { - "description": "Name of simulation", - "type": "string"}, - "db_name": { - "description": "Output HDF5 database file name", - "type": "string", + "simulation": { + "description": "Simulation class input parameters", + "type": "object", + "properties": { + "sim_name": { + "description": "Name of simulation", + "type": "string"}, + "db_name": { + "description": "Output HDF5 database file name", + "type": "string", + "default": "saltproc_results.h5", "pattern": "^(.*)\\.h5$"}, - "restart_flag": { - "description": "Restart simulation from the step when it stopped?", - "type": "boolean"}, - "adjust_geo": { - "description": "switch to another geometry when keff drops below 1?", - "type": "boolean"} - }, - "requires": ["sim_name", "db_name", "restart_flag", "adjust_geo"] - }, - "reactor": { - "description": "Reactor class input parameters", - "type": "object", - "properties": { - "volume": { - "description": "reactor core volume [cm^3]", - "type": "number", + "restart_flag": { + "description": "Restart simulation from the step when it stopped?", + "type": "boolean", + "default": false}, + "adjust_geo": { + "description": "switch to another geometry when keff drops below 1?", + "type": "boolean", + "default": false} + }, + "default": {}, + "required": ["sim_name"] + }, + "reactor": { + "description": "Reactor class input parameters", + "type": "object", + "properties": { + "volume": { + "description": "reactor core volume [cm^3]", + "type": "number", "minimum": 0}, - "mass_flowrate": { - "description": "Salt mass flowrate through reactor core [g/s]", - "type": "number", + "mass_flowrate": { + "description": "Salt mass flowrate through reactor core [g/s]", + "type": "number", "minimum": 0 }, - "power_levels": { - "description": "Reactor power or power step list durng depletion step [W]", - "type": "array", - "items": { "type": "number", - "minimum": 0}, - "minItems": 1, - "uniqueItems": false - }, - "dep_step_length_cumulative": { - "description": "Depletion step length(s) (cumulative) [d]", - "type": "array", - "items": { "type": "number", - "minimum": 0}, - "minItems": 1, - "uniqueItems": false - } - }, - "required": ["volume", "mass_flowrate", "power_levels", "dep_step_length_cumulative"] - } + "power_levels": { + "description": "Reactor power or power step list durng depletion step [W]", + "type": "array", + "items": { "type": "number", "minimum": 0}, + "minItems": 1, + "uniqueItems": false}, + "depletion_timesteps": { + "description": "Depletion timestep size or list of timestep sizes", + "type": "array", + "items": { "type": "number", "minimum": 0}, + "minItems": 1, + "uniqueItems": false}, + "timestep_type": { + "description": "Depletion step type", + "type": "string", + "enum": ["cumulative", "stepwise"], + "default": "stepwise"}, + "timestep_units": { + "description": "Timestep unit", + "type": "string", + "enum": ["s", "sec", "min", "minute", "h", "hr", "hour", "d", "day", "a", "yr", "year", "MWd/kg", "mwd/kg", "MWD/KG", "MWD/kg", "MWd/KG"] + } + }, + "default": {}, + "required": ["volume", "mass_flowrate", "power_levels", "depletion_timesteps", "timestep_units"] + } }, "required": ["proc_input_file", "dot_input_file", "output_path", "depcode", "simulation", "reactor"] } diff --git a/saltproc/openmc_depcode.py b/saltproc/openmc_depcode.py index e48004ba7..1dea7aa44 100644 --- a/saltproc/openmc_depcode.py +++ b/saltproc/openmc_depcode.py @@ -3,6 +3,7 @@ import shutil import re import json +from pathlib import Path from pyne import nucname as pyname from pyne import serpent @@ -42,7 +43,10 @@ def __init__(self, output_path, exec_path, template_input_file_path, - geo_files): + geo_file_paths, + depletion_settings, + chain_file_path + ): """Initialize a OpenMCDepcode object. Parameters @@ -56,22 +60,31 @@ def __init__(self, material, and settings) for OpenMC. File type as strings are keys (e.g. 'geometry', 'settings', 'material'), and file path as strings are values. - geo_files : str or list, optional + geo_file_paths : str or list, optional Path to file that contains the reactor geometry. List of `str` if reactivity control by switching geometry is `On` or just `str` otherwise. + depletion_settings : dict + Keyword arguments to pass to :func:`openmc.model.deplete()`. + chain_file_path : str + Path to depletion chain file """ # if using the default depletion file, make sure we have the right path if exec_path == "openmc_deplete.py": - exec_path = (Path(__file__).parents[0] / exec_path) + exec_path = (Path(__file__).parents[0] / exec_path).resolve() + else: + exec_path == (Path(template_input_file_path['settings'].parents[0]) / exec_path).resolve() + + self.depletion_settings = depletion_settings + self.chain_file_path = chain_file_path super().__init__("openmc", output_path, exec_path, template_input_file_path, - geo_files) + geo_file_paths) self.runtime_inputfile = \ {'geometry': str((output_path / 'geometry.xml').resolve()), 'settings': str((output_path / 'settings.xml').resolve())} @@ -138,8 +151,8 @@ def run_depletion_step(self, cores, nodes): self.runtime_inputfile['settings'], '--tallies', self.runtime_inputfile['tallies'], - '--depletion_settings', - self.runtime_inputfile['depletion_settings']) + '--directory', + str(self.output_path)) print('Running %s' % (self.codename)) # TODO: Need to figure out how to adapt this to openmc @@ -156,11 +169,11 @@ def run_depletion_step(self, cores, nodes): def switch_to_next_geometry(self): """Switches the geometry file for the OpenMC depletion simulation to - the next geometry file in `geo_files`. + the next geometry file in `geo_file_paths`. """ mats = openmc.Materials.from_xml(self.runtime_matfile) next_geometry = openmc.Geometry.from_xml( - path=self.geo_files.pop(0), + path=self.geo_file_paths.pop(0), materials=mats) next_geometry.export_to_xml(path=self.runtime_inputfile['geometry']) del mats, next_geometry @@ -183,7 +196,7 @@ def write_runtime_input(self, reactor, depletion_step, restart): materials = openmc.Materials.from_xml( self.template_input_file_path['materials']) geometry = openmc.Geometry.from_xml( - self.geo_files[0], materials=materials) + self.geo_file_paths[0], materials=materials) settings = openmc.Settings.from_xml( self.template_input_file_path['settings']) self.npop = settings.particles @@ -205,7 +218,8 @@ def write_runtime_input(self, reactor, depletion_step, restart): del materials, geometry, settings def write_depletion_settings(self, reactor, step_idx): - """Write the depeletion settings for the OpenMC depletion step. + """Write the depeletion settings for the ``openmc.deplete`` + module. Parameters ---------- @@ -216,42 +230,34 @@ def write_depletion_settings(self, reactor, step_idx): Current depletion step. """ - depletion_settings = {} current_power = reactor.power_levels[step_idx] # Get current depletion step length - if step_idx == 0: - step_length = reactor.dep_step_length_cumulative[0] - else: - step_length = \ - reactor.dep_step_length_cumulative[step_idx] - \ - reactor.dep_step_length_cumulative[step_idx - 1] + step_length = reactor.depletion_timesteps[step_idx] - out_path = os.path.dirname(self.runtime_inputfile['settings']) - depletion_settings['directory'] = out_path - depletion_settings['timesteps'] = [step_length] + self.depletion_settings['directory'] = str(self.output_path) + self.depletion_settings['timesteps'] = [step_length] operator_kwargs = {} - + input_path = Path(self.template_input_file_path['materials']).parents[0] try: - operator_kwargs['chain_file'] = \ - self.template_input_file_path['chain_file'] + if not(operator_kwargs['fission_q'] is None): + operator_kwargs['fission_q'] = \ + (input_path / operator_kwargs['fission_q']).resolve().as_posix() except KeyError: - raise SyntaxError("No chain file defined. Please provide \ - a chain file in your saltproc input file") + pass + operator_kwargs['chain_file'] = self.chain_file_path + + self.depletion_settings['operator_kwargs'].update(operator_kwargs) integrator_kwargs = {} integrator_kwargs['power'] = current_power - integrator_kwargs['timestep_units'] = 'd' # days - - depletion_settings['operator_kwargs'] = operator_kwargs - depletion_settings['integrator_kwargs'] = integrator_kwargs + integrator_kwargs['timestep_units'] = reactor.timestep_units + self.depletion_settings['integrator_kwargs'].update(integrator_kwargs) - self.runtime_inputfile['depletion_settings'] = \ - os.path.join(out_path, 'depletion_settings.json') - json_dep_settings = json.JSONEncoder().encode(depletion_settings) - with open(self.runtime_inputfile['depletion_settings'], 'w') as f: - f.writelines(json_dep_settings) + with open(self.output_path / 'depletion_settings.json', 'w') as f: + depletion_settings_json = json.dumps(self.depletion_settings, indent=4) + f.write(depletion_settings_json) def update_depletable_materials(self, mats, dep_end_time): """Updates material file with reprocessed material compositions. diff --git a/saltproc/openmc_deplete.py b/saltproc/openmc_deplete.py index 1d377bfa8..2ea23ce49 100644 --- a/saltproc/openmc_deplete.py +++ b/saltproc/openmc_deplete.py @@ -1,6 +1,8 @@ +import argparse +import json + import openmc import openmc.deplete as od -import argparse def parse_arguments(): @@ -16,6 +18,8 @@ def parse_arguments(): Path to openmc settings `.xml` file tallies : str Path to openmc tallies `.xml` file + dirctory : str + Directory to write the XML files to. depletion_settings : str Path to the OpenMCDepcode depletion_settings file @@ -24,31 +28,26 @@ def parse_arguments(): parser.add_argument('--materials', type=str, default=1, - help='path to openmc material \ - material xml file') + help='path to openmc material material xml file') parser.add_argument('--geometry', type=str, default=1, - help='path to openmc geometry \ - xml file') + help='path to openmc geometry xml file') parser.add_argument('--settings', type=str, default=None, - help='path to openmc settings \ - xml file') + help='path to openmc settings xml file') parser.add_argument('--tallies', type=str, default=None, - help='path to openmc tallies \ - xml file') - parser.add_argument('--depletion_settings', - type=str, + help='path to openmc tallies xml file') + parser.add_argument('--directory', + type='str', default=None, - help='path to saltproc depletion \ - settings json file') + help='path to output directory') args = parser.parse_args() return str(args.materials), str(args.geometry), str(args.settings), \ - str(args.tallies), str(args.depletion_settings) + str(args.tallies), str(args.directory) args = parse_arguments() @@ -63,12 +62,17 @@ def parse_arguments(): settings=settings, tallies=tallies) -with open(args.depletion_settings) as f: +with open(f'{args.directory}/depletion_settings.json') as f: depletion_settings = json.load(f) -model.deplete(depletion_settings['timesteps'], - directory=depletion_settings['directory'], - operator_kwargs=depletion_settings['operator_kwargs'], - **depletion_settings['integrator_kwargs']) +timesteps = depletion_settings.pop('timesteps') +fission_q = depletion_settings['operator_kwargs']['fission_q'] +if not(fission_q is None): + with open(fission_q, 'r') as f: + fission_q = json.load(f) + + depletion_settings['operator_kwargs']['fission_q'] = fission_q + +model.deplete(timesteps, **depletion_settings) del materials, geometry, settings, tallies, model diff --git a/saltproc/reactor.py b/saltproc/reactor.py index 2d8dbcce2..c655ec037 100644 --- a/saltproc/reactor.py +++ b/saltproc/reactor.py @@ -7,7 +7,9 @@ def __init__(self, volume=1.0, mass_flowrate=0.0, power_levels=[0.0], - dep_step_length_cumulative=[1]): + depletion_timesteps=[1], + timestep_type='stepwise', + timestep_units='d'): """Initializes the class. Parameters @@ -18,12 +20,18 @@ def __init__(self, Total mass flowrate through reactor core (g/s). power_levels : array [:math:`N_{steps}` x1] Normalized power level for each depletion step (W). - dep_step_length_cumulative : array [:math:`N_{steps}` x1] - Cumulative depletion time (d). + depletion_timesteps : array [:math:`N_{steps}` x1] + Array of timesteps. + timestep_type: str + 'cumulative', 'stepwise'. + timestep_units : str + Timestep units """ # initialize all object attributes self.volume = volume self.mass_flowrate = mass_flowrate self.power_levels = power_levels - self.dep_step_length_cumulative = dep_step_length_cumulative + self.depletion_timesteps = depletion_timesteps + self.timestep_units = timestep_units + self.timestep_type = timestep_type diff --git a/saltproc/serpent_depcode.py b/saltproc/serpent_depcode.py index e5ccdc6c1..6f6dea171 100644 --- a/saltproc/serpent_depcode.py +++ b/saltproc/serpent_depcode.py @@ -41,7 +41,7 @@ def __init__(self, output_path, exec_path, template_input_file_path, - geo_files): + geo_file_paths): """Initialize a SerpentDepcode object. Parameters @@ -52,7 +52,7 @@ def __init__(self, Path to Serpent2 executable. template_input_file_path : str Path to user input file for Serpent2 - geo_files : str or list, optional + geo_file_paths : str or list, optional Path to file that contains the reactor geometry. List of `str` if reactivity control by switching geometry is `On` or just `str` otherwise. @@ -62,7 +62,7 @@ def __init__(self, output_path, exec_path, template_input_file_path, - geo_files) + geo_file_paths) self.runtime_inputfile = \ str((output_path / 'runtime_input.serpent').resolve()) self.runtime_matfile = str((output_path / 'runtime_mat.ini').resolve()) @@ -257,7 +257,7 @@ def insert_path_to_geometry(self, lines): """ lines.insert(5, # Inserts on 6th line - 'include \"' + str(self.geo_files[0]) + '\"\n') + 'include \"' + str(self.geo_file_paths[0]) + '\"\n') return lines def read_depleted_materials(self, read_at_end=False): @@ -379,9 +379,7 @@ def set_power_load(self, file_lines, reactor, step_idx): - """Add power load attributes in a :class:`Reactor` object to the - ``set power P dep daystep DEPSTEP`` line in the Serpent2 runtime input - file. + """Set the power for the current depletion step Parameters ---------- @@ -402,20 +400,24 @@ def set_power_load(self, line_idx = 8 # burnup setting line index by default current_power = reactor.power_levels[step_idx] - if step_idx == 0: - step_length = reactor.dep_step_length_cumulative[0] - else: - step_length = \ - reactor.dep_step_length_cumulative[step_idx] - \ - reactor.dep_step_length_cumulative[step_idx - 1] + + step_length = reactor.depletion_timesteps[step_idx] + for line in file_lines: if line.startswith('set power '): line_idx = file_lines.index(line) del file_lines[line_idx] - file_lines.insert(line_idx, # Insert on 9th line - 'set power %5.9E dep daystep %7.5E\n' % - (current_power, step_length)) + if reactor.timestep_units == 'MWd/kg': + step_type = 'bu' + else: + step_type = 'day' + + step_type += 'step' + + file_lines.insert(line_idx, + f'set power %5.9E dep %s %7.5E\n' % + (current_power, step_type, step_length)) return file_lines def run_depletion_step(self, cores, nodes): @@ -483,9 +485,9 @@ def switch_to_next_geometry(self): lines = f.readlines() current_geo_file = lines[geo_line_n].split('\"')[1] - current_geo_idx = self.geo_files.index(current_geo_file) + current_geo_idx = self.geo_file_paths.index(current_geo_file) try: - new_geo_file = self.geo_files[current_geo_idx + 1] + new_geo_file = self.geo_file_paths[current_geo_idx + 1] except IndexError: print('No more geometry files available \ and the system went subcritical \n\n') diff --git a/tests/conftest.py b/tests/conftest.py index 9deb3a466..87829b3fc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,7 @@ from pathlib import Path import pytest -from saltproc.app import read_main_input, _create_depcode_object +from saltproc.app import read_main_input, _create_depcode_object, _create_simulation_object, _create_reactor_object from saltproc import Simulation @@ -23,51 +23,65 @@ def path_test_file(cwd): @pytest.fixture(scope='session') -def serpent_depcode(cwd): - """SerpentDepcode object for unit tests""" +def serpent_runtime(cwd, tmpdir_factory): + """SaltProc objects for Serpent unit tests""" saltproc_input = str(cwd / 'serpent_data' / 'tap_input.json') - _, _, _, object_input = read_main_input(saltproc_input) - depcode = _create_depcode_object(object_input[0]) + depcode_input, simulation_input, reactor_input = \ + read_main_input(saltproc_input)[3] + depcode = _create_depcode_object(depcode_input) depcode.runtime_inputfile = str(cwd / 'serpent_data' / 'tap_reference') + output_dir = str(depcode.output_path).split('/')[-1] + depcode.output_path = tmpdir_factory.mktemp(f'serpent_{output_dir}') + + simulation = _create_simulation_object(simulation_input, depcode, 1, 1) + + reactor = _create_reactor_object(reactor_input) + + return depcode, simulation, reactor + +@pytest.fixture(scope='session') +def serpent_depcode(serpent_runtime): + """SerpentDepcode object for unit tests""" + depcode = serpent_runtime[0] return depcode @pytest.fixture(scope='session') -def openmc_depcode(cwd): - """OpenMCDepcode object for unit tests""" - saltproc_input = str(cwd / 'openmc_data' / 'tap_input.json') - _, _, _, object_input = read_main_input(saltproc_input) - depcode = _create_depcode_object(object_input[0]) +def serpent_reactor(serpent_runtime): + reactor = serpent_runtime[2] + return reactor + - # Openmc initlialization - openmc_input_path = (cwd / 'openmc_data') +@pytest.fixture(scope='session') +def simulation(serpent_runtime): + """Simulation object for unit tests""" + simulation = serpent_runtime[1] + return simulation - openmc_runtime_inputfiles = { - "geometry": "geometry.xml", - "settings": "settings.xml", - } - for key in openmc_runtime_inputfiles: - openmc_runtime_inputfiles[key] = \ - str(openmc_input_path / openmc_runtime_inputfiles[key]) +@pytest.fixture(scope='session') +def openmc_runtime(cwd, tmpdir_factory): + """SaltProc objects for OpenMC unit tests""" + saltproc_input = str(cwd / 'openmc_data' / 'tap_input.json') + depcode_input, simulation_input, reactor_input = \ + read_main_input(saltproc_input)[3] + depcode = _create_depcode_object(depcode_input) + output_dir = str(depcode.output_path).split('/')[-1] + depcode.output_path = tmpdir_factory.mktemp(f'openmc_{output_dir}') + reactor = _create_reactor_object(reactor_input) - depcode.runtime_inputfile = openmc_runtime_inputfiles - depcode.runtime_matfile = str(openmc_input_path / 'materials.xml') + return depcode, reactor + +@pytest.fixture(scope='session') +def openmc_depcode(openmc_runtime): + """OpenMCDepcode objects for unit tests""" + depcode = openmc_runtime[0] return depcode @pytest.fixture(scope='session') -def simulation(cwd, serpent_depcode): - """Simulation object for unit tests""" - simulation = Simulation( - sim_name='test_simulation', - sim_depcode=serpent_depcode, - core_number=1, - node_number=1, - db_path=str( - cwd / - 'serpent_data' / - 'tap_reference_db.h5')) - return simulation +def openmc_reactor(openmc_runtime): + reactor = openmc_runtime[1] + return reactor diff --git a/tests/integration_tests/file_interface_openmc/test.py b/tests/integration_tests/file_interface_openmc/test.py index c5e056967..1893317b5 100644 --- a/tests/integration_tests/file_interface_openmc/test.py +++ b/tests/integration_tests/file_interface_openmc/test.py @@ -18,7 +18,9 @@ def geometry_switch(scope='module'): def msr(scope='module'): reactor = Reactor(volume=1.0, power_levels=[1.250E+09, 1.250E+09, 5.550E+09], - dep_step_length_cumulative=[111.111, 2101.9, 3987.5]) + depletion_timesteps=[111.111, 2101.9, 3987.5], + timestep_type='cumulative', + timestep_units='d') return reactor @@ -27,7 +29,7 @@ def test_write_runtime_input(openmc_depcode, msr): input_materials = openmc.Materials.from_xml( openmc_depcode.template_input_file_path['materials']) input_geometry = openmc.Geometry.from_xml( - openmc_depcode.geo_files[0], + openmc_depcode.geo_file_paths[0], materials=input_materials) input_cells = input_geometry.get_all_cells() @@ -74,13 +76,13 @@ def test_write_depletion_settings(openmc_depcode, msr): Unit test for `Depcodeopenmc_depcode.write_depletion_settings` """ openmc_depcode.write_depletion_settings(msr, 0) - with open(openmc_depcode.runtime_inputfile['depletion_settings']) as f: + with open(openmc_depcode.output_path / 'depletion_settings.json') as f: j = json.load(f) - assert j['directory'] == str(Path( - openmc_depcode.runtime_inputfile['settings']).parents[0]) - assert j['timesteps'][0] == msr.dep_step_length_cumulative[0] + assert Path(j['directory']).resolve() == Path( + openmc_depcode.output_path) + assert j['timesteps'][0] == msr.depletion_timesteps[0] assert j['operator_kwargs']['chain_file'] == \ - openmc_depcode.template_input_file_path['chain_file'] + openmc_depcode.chain_file_path assert j['integrator_kwargs']['power'] == msr.power_levels[0] assert j['integrator_kwargs']['timestep_units'] == 'd' @@ -93,7 +95,7 @@ def test_write_saltproc_openmc_tallies(openmc_depcode): mat = openmc.Materials.from_xml( openmc_depcode.template_input_file_path['materials']) geo = openmc.Geometry.from_xml( - openmc_depcode.geo_files[0], mat) + openmc_depcode.geo_file_paths[0], mat) openmc_depcode.write_saltproc_openmc_tallies(mat, geo) del mat, geo tallies = openmc.Tallies.from_xml(openmc_depcode.runtime_inputfile['tallies']) @@ -131,7 +133,7 @@ def test_switch_to_next_geometry(openmc_depcode): mat = openmc.Materials.from_xml( openmc_depcode.template_input_file_path['materials']) expected_geometry = openmc.Geometry.from_xml( - openmc_depcode.geo_files[0], mat) + openmc_depcode.geo_file_paths[0], mat) expected_cells = expected_geometry.get_all_cells() expected_lattices = expected_geometry.get_all_lattices() expected_surfaces = expected_geometry.get_all_surfaces() diff --git a/tests/integration_tests/file_interface_serpent/test.py b/tests/integration_tests/file_interface_serpent/test.py index 6096d77a4..b9618e0ab 100644 --- a/tests/integration_tests/file_interface_serpent/test.py +++ b/tests/integration_tests/file_interface_serpent/test.py @@ -14,15 +14,7 @@ def geometry_switch(scope='module'): return (path / 'serpent_data' / 'tap_geometry_switch.ini') -@pytest.fixture -def msr(scope='module'): - reactor = Reactor(volume=1.0, - power_levels=[1.250E+09, 1.250E+09, 5.550E+09], - dep_step_length_cumulative=[111.111, 2101.9, 3987.5]) - return reactor - - -def test_runtime_input_from_template(serpent_depcode, msr): +def test_runtime_input_from_template(serpent_depcode, serpent_reactor): file = serpent_depcode.template_input_file_path file_data = serpent_depcode.read_plaintext_file(file) @@ -86,18 +78,15 @@ def test_runtime_input_from_template(serpent_depcode, msr): remove(serpent_depcode.runtime_matfile) # set_power_load - time = msr.dep_step_length_cumulative.copy() - time.insert(0, 0.0) - depsteps = np.diff(time) - for idx in range(len(msr.power_levels)): - file_data = serpent_depcode.set_power_load(file_data, msr, idx) + for idx in range(len(serpent_reactor.power_levels)): + file_data = serpent_depcode.set_power_load(file_data, serpent_reactor, idx) assert file_data[8].split()[4] == 'daystep' - assert file_data[8].split()[2] == str("%5.9E" % msr.power_levels[idx]) - assert file_data[8].split()[5] == str("%7.5E" % depsteps[idx]) + assert file_data[8].split()[2] == str("%5.9E" % serpent_reactor.power_levels[idx]) + assert file_data[8].split()[5] == str("%7.5E" % serpent_reactor.depletion_timesteps[idx]) -def test_write_runtime_files(serpent_depcode, msr): +def test_write_runtime_files(serpent_depcode, serpent_reactor): mats = serpent_depcode.read_depleted_materials(True) # update_depletable_materials @@ -117,7 +106,7 @@ def test_write_runtime_files(serpent_depcode, msr): remove(serpent_depcode.runtime_matfile) # write_runtime_input - serpent_depcode.write_runtime_input(msr, + serpent_depcode.write_runtime_input(serpent_reactor, 0, False) @@ -126,11 +115,11 @@ def test_write_runtime_files(serpent_depcode, msr): assert file_data[0] == f'include "{serpent_depcode.runtime_matfile}"\n' assert file_data[8].split()[2] == '1.250000000E+09' assert file_data[8].split()[4] == 'daystep' - assert file_data[8].split()[-1] == '1.11111E+02' + assert file_data[8].split()[-1] == '5.00000E+00' assert file_data[20] == 'set pop 50 20 20\n' # switch_to_next_geometry - serpent_depcode.geo_files += ['../../examples/406.inp', + serpent_depcode.geo_file_paths += ['../../examples/406.inp', '../../examples/988.inp'] serpent_depcode.switch_to_next_geometry() file_data = serpent_depcode.read_plaintext_file(file) diff --git a/tests/integration_tests/run_constant_reprocessing/tap_input.json b/tests/integration_tests/run_constant_reprocessing/tap_input.json index 5c0a45838..730ad0d84 100644 --- a/tests/integration_tests/run_constant_reprocessing/tap_input.json +++ b/tests/integration_tests/run_constant_reprocessing/tap_input.json @@ -1,24 +1,20 @@ { "proc_input_file": "tap_processes.json", "dot_input_file": "../../tap_paths.dot", - "output_path": ".", - "num_depsteps": 2, + "n_depletion_steps": 2, "depcode": { "codename": "serpent", - "exec_path": "sss2", "template_input_file_path": "tap_template.ini", "geo_file_paths": ["tap_geometry_base.ini"] }, "simulation": { - "sim_name": "tap_integration_test", - "db_name": "test_db.h5", - "restart_flag": false, - "adjust_geo": false + "sim_name": "tap_constant_reprocessing" }, "reactor": { "volume": 1.0, "mass_flowrate": 9.92E+6, "power_levels": [ 1.250E+9 ], - "dep_step_length_cumulative": [ 5 ] + "depletion_timesteps": [ 5 ], + "timestep_units": "d" } } diff --git a/tests/integration_tests/run_constant_reprocessing/test.py b/tests/integration_tests/run_constant_reprocessing/test.py index 1c12b7a34..0d41bf6b9 100644 --- a/tests/integration_tests/run_constant_reprocessing/test.py +++ b/tests/integration_tests/run_constant_reprocessing/test.py @@ -1,5 +1,6 @@ """Run SaltProc with reprocessing""" from pathlib import Path +import shutil import numpy as np import pytest @@ -11,7 +12,7 @@ @pytest.fixture def setup(scope='module'): cwd = str(Path(__file__).parents[0].resolve()) - test_db = cwd + '/test_db.h5' + test_db = cwd + '/saltproc_runtime/saltproc_results.h5' ref_db = cwd + '/tap_reference_db.h5' tol = 1e-9 @@ -28,6 +29,8 @@ def test_integration_2step_constant_ideal_removal_heavy(setup): np.testing.assert_equal(read_keff(test_db), read_keff(ref_db)) assert_db_almost_equal(test_db, ref_db, tol) + shutil.rmtree(cwd + '/saltproc_runtime') + def read_keff(file): db = tb.open_file(file, mode='r') sim_param = db.root.simulation_parameters diff --git a/tests/integration_tests/run_no_reprocessing/test.py b/tests/integration_tests/run_no_reprocessing/test.py index fb78e7c5c..4c923a2e4 100644 --- a/tests/integration_tests/run_no_reprocessing/test.py +++ b/tests/integration_tests/run_no_reprocessing/test.py @@ -1,7 +1,6 @@ """Run SaltProc without reprocessing""" -import os -import glob from pathlib import Path +import shutil import numpy as np import pytest @@ -14,31 +13,28 @@ @pytest.fixture def setup(): cwd = str(Path(__file__).parents[0].resolve()) - main_input = cwd + '/test_input.json' + saltproc_input = cwd + '/test_input.json' input_path, process_input_file, path_input_file, object_input = \ - app.read_main_input(main_input) + app.read_main_input(saltproc_input) depcode = app._create_depcode_object(object_input[0]) - sss_file = cwd + '/_test' - depcode.runtime_inputfile = sss_file - depcode.runtime_matfile = cwd + '/_test_mat' simulation = app._create_simulation_object(object_input[1], depcode, 1, 1) reactor = app._create_reactor_object(object_input[2]) - return cwd, simulation, reactor, sss_file + return cwd, simulation, reactor @pytest.mark.slow def test_integration_2step_saltproc_no_reproc_heavy(setup): - cwd, simulation, reactor, sss_file = setup + cwd, simulation, reactor = setup runsim_no_reproc(simulation, reactor, 2) - saltproc_out = sss_file + '_dep.m' + output_path = str(simulation.sim_depcode.output_path) ref_result = serpent.parse_dep(cwd + '/reference_dep.m', make_mats=False) - test_result = serpent.parse_dep(saltproc_out, make_mats=False) + test_result = serpent.parse_dep(f'{output_path}/runtime_input.serpent_dep.m', make_mats=False) ref_mdens_error = np.loadtxt(cwd + '/reference_error') @@ -47,13 +43,8 @@ def test_integration_2step_saltproc_no_reproc_heavy(setup): test_mdens_error = np.array(ref_fuel_mdens - test_fuel_mdens) np.testing.assert_array_almost_equal(test_mdens_error, ref_mdens_error) - # Cleaning after testing - out_file_list = glob.glob(cwd + '/_test*') - for file in out_file_list: - try: - os.remove(file) - except OSError: - print("Error while deleting file : ", file) + + shutil.rmtree(cwd + '/saltproc_runtime') def runsim_no_reproc(simulation, reactor, nsteps): diff --git a/tests/integration_tests/run_no_reprocessing/test_input.json b/tests/integration_tests/run_no_reprocessing/test_input.json index ff6eb6094..31e0ac209 100644 --- a/tests/integration_tests/run_no_reprocessing/test_input.json +++ b/tests/integration_tests/run_no_reprocessing/test_input.json @@ -1,24 +1,21 @@ { "proc_input_file": "processes.json", "dot_input_file": "paths.dot", - "output_path": ".", - "num_depsteps": 2, + "n_depletion_steps": 2, "depcode": { "codename": "serpent", - "exec_path": "sss2", "template_input_file_path": "test_input.ini", "geo_file_paths": ["../../serpent_data/tap_geometry_base.ini"] }, "simulation": { - "sim_name": "Integration test", - "db_name": "_test_db.h5", - "restart_flag": true, - "adjust_geo": false + "sim_name": "test_no_reprocessing", + "restart_flag": true }, "reactor": { "volume": 1.0, "mass_flowrate": 0.0, "power_levels": [ 1.250E+9 ], - "dep_step_length_cumulative": [ 3 ] + "depletion_timesteps": [ 3 ], + "timestep_units": "d" } } diff --git a/tests/openmc_data/constant_fission_yield_input.json b/tests/openmc_data/constant_fission_yield_input.json new file mode 100644 index 000000000..f6497b641 --- /dev/null +++ b/tests/openmc_data/constant_fission_yield_input.json @@ -0,0 +1,30 @@ +{ + "proc_input_file": "../tap_processes.json", + "dot_input_file": "../tap_paths.dot", + "n_depletion_steps": 2, + "depcode": { + "codename": "openmc", + "template_input_file_path": { + "materials": "tap_materials.xml", + "settings": "tap_settings.xml" + }, + "geo_file_paths": ["tap_geometry_base.xml"], + "chain_file_path": "test_chain.xml", + "depletion_settings": { + "operator_kwargs": { + "fission_yield_mode": "constant", + "fission_yield_opts": {"energy": 0.0253} + } + } + }, + "simulation": { + "sim_name": "tap_test_simulation_openmc" + }, + "reactor": { + "volume": 1.0, + "mass_flowrate": 9.92E+6, + "power_levels": [ 1.250E+9 ], + "depletion_timesteps": [ 5 ], + "timestep_units": "d" + } +} diff --git a/tests/openmc_data/cutoff_fission_yield_input.json b/tests/openmc_data/cutoff_fission_yield_input.json new file mode 100644 index 000000000..656f111c2 --- /dev/null +++ b/tests/openmc_data/cutoff_fission_yield_input.json @@ -0,0 +1,33 @@ +{ + "proc_input_file": "../tap_processes.json", + "dot_input_file": "../tap_paths.dot", + "n_depletion_steps": 2, + "depcode": { + "codename": "openmc", + "template_input_file_path": { + "materials": "tap_materials.xml", + "settings": "tap_settings.xml" + }, + "geo_file_paths": ["tap_geometry_base.xml"], + "chain_file_path": "test_chain.xml", + "depletion_settings": { + "operator_kwargs": { + "fission_yield_mode": "cutoff", + "fission_yield_opts": { + "cutoff": 112.0, + "thermal_energy": 0.0253, + "fast_energy": 500000.0} + } + } + }, + "simulation": { + "sim_name": "tap_test_simulation_openmc" + }, + "reactor": { + "volume": 1.0, + "mass_flowrate": 9.92E+6, + "power_levels": [ 1.250E+9 ], + "depletion_timesteps": [ 5 ], + "timestep_units": "d" + } +} diff --git a/tests/openmc_data/depletion_settings.json b/tests/openmc_data/depletion_settings.json deleted file mode 100644 index 939ecfb26..000000000 --- a/tests/openmc_data/depletion_settings.json +++ /dev/null @@ -1 +0,0 @@ -{"directory": "/home/ooblack/projects/saltproc/tests/openmc_data", "timesteps": [111.111], "operator_kwargs": {"chain_file": "/home/ooblack/projects/saltproc/tests/openmc_data/tap_chain.xml"}, "integrator_kwargs": {"power": 1250000000.0, "timestep_units": "d"}} \ No newline at end of file diff --git a/tests/openmc_data/flux_reaction_rate_input.json b/tests/openmc_data/flux_reaction_rate_input.json new file mode 100644 index 000000000..5d3c79774 --- /dev/null +++ b/tests/openmc_data/flux_reaction_rate_input.json @@ -0,0 +1,33 @@ +{ + "proc_input_file": "../tap_processes.json", + "dot_input_file": "../tap_paths.dot", + "n_depletion_steps": 2, + "depcode": { + "codename": "openmc", + "template_input_file_path": { + "materials": "tap_materials.xml", + "settings": "tap_settings.xml" + }, + "geo_file_paths": ["tap_geometry_base.xml"], + "chain_file_path": "test_chain.xml", + "depletion_settings": { + "operator_kwargs": { + "reaction_rate_mode": "flux", + "reaction_rate_opts": { + "energies": [0.0253, 500000.0], + "reactions": ["(n,gamma)"], + "nuclides": ["U235", "Pu239"]} + } + } + }, + "simulation": { + "sim_name": "tap_test_simulation_openmc" + }, + "reactor": { + "volume": 1.0, + "mass_flowrate": 9.92E+6, + "power_levels": [ 1.250E+9 ], + "depletion_timesteps": [ 5 ], + "timestep_units": "d" + } +} diff --git a/tests/openmc_data/tap_input.json b/tests/openmc_data/tap_input.json index 689ec4347..5fc05554d 100644 --- a/tests/openmc_data/tap_input.json +++ b/tests/openmc_data/tap_input.json @@ -1,28 +1,24 @@ { "proc_input_file": "../tap_processes.json", "dot_input_file": "../tap_paths.dot", - "output_path": "../temp_data", - "num_depsteps": 2, + "n_depletion_steps": 2, "depcode": { "codename": "openmc", - "exec_path": "openmc", "template_input_file_path": { "materials": "tap_materials.xml", - "settings": "tap_settings.xml", - "chain_file": "tap_chain.xml" + "settings": "tap_settings.xml" }, - "geo_file_paths": ["tap_geometry_base.xml"] + "geo_file_paths": ["tap_geometry_base.xml"], + "chain_file_path": "test_chain.xml" }, "simulation": { - "sim_name": "tap_test_simulation_openmc", - "db_name": "db_saltproc.h5", - "restart_flag": false, - "adjust_geo": false + "sim_name": "tap_test_simulation_openmc" }, "reactor": { "volume": 1.0, "mass_flowrate": 9.92E+6, "power_levels": [ 1.250E+9 ], - "dep_step_length_cumulative": [ 5 ] + "depletion_timesteps": [ 5 ], + "timestep_units": "d" } } diff --git a/tests/serpent_data/tap_input.json b/tests/serpent_data/tap_input.json index 98ca10fd6..41525d03e 100644 --- a/tests/serpent_data/tap_input.json +++ b/tests/serpent_data/tap_input.json @@ -1,24 +1,20 @@ { "proc_input_file": "../tap_processes.json", "dot_input_file": "../tap_paths.dot", - "output_path": "../temp_data", - "num_depsteps": 2, + "n_depletion_steps": 2, "depcode": { "codename": "serpent", - "exec_path": "sss2", "template_input_file_path": "tap_template.ini", "geo_file_paths": ["tap_geometry_base.ini"] }, "simulation": { - "sim_name": "tap_test_simulation_serpent", - "db_name": "db_saltproc.h5", - "restart_flag": false, - "adjust_geo": false + "sim_name": "tap_test_simulation_serpent" }, "reactor": { "volume": 1.0, "mass_flowrate": 9.92E+6, "power_levels": [ 1.250E+9 ], - "dep_step_length_cumulative": [ 5 ] + "depletion_timesteps": [ 5 ], + "timestep_units": "d" } } diff --git a/tests/unit_tests/test_app.py b/tests/unit_tests/test_app.py index e7cac6104..03ab4d27c 100644 --- a/tests/unit_tests/test_app.py +++ b/tests/unit_tests/test_app.py @@ -4,8 +4,28 @@ import numpy as np import pytest from saltproc.app import read_main_input, get_extraction_processes +from saltproc.app import (_validate_depletion_timesteps_power_levels, + _convert_cumulative_to_stepwise, + _scale_depletion_timesteps) +from saltproc.app import (SECOND_UNITS, MINUTE_UNITS, HOUR_UNITS, DAY_UNITS, + YEAR_UNITS) from saltproc.app import get_feeds, get_extraction_process_paths +expected_depletion_settings = {'method': 'predictor', + 'final_step': True, + 'operator_kwargs': { + 'diff_burnable_mats': False, + 'normalization_mode': 'fission-q', + 'fission_q': None, + 'dilute_initial': 1000, + 'fission_yield_mode': 'constant', + 'fission_yield_opts': None, + 'reaction_rate_mode': 'direct', + 'reaction_rate_opts': None, + 'reduce_chain': False, + 'reduce_chain_level': None}, + 'output': True, + 'integrator_kwargs': {}} @pytest.mark.parametrize("codename, ext", [ ("serpent", ".ini"), @@ -23,16 +43,138 @@ def test_read_main_input(cwd, codename, ext): assert depcode_input['codename'] == codename assert depcode_input['geo_file_paths'][0] == \ str(data_path / ('tap_geometry_base' + ext)) + if codename == 'openmc': + assert depcode_input['template_input_file_path'] == \ + {'materials': str((input_path / 'tap_materials.xml').resolve()), + 'settings': str((input_path / 'tap_settings.xml').resolve())} + assert depcode_input['chain_file_path'] == \ + str((input_path / 'test_chain.xml').resolve()) + assert depcode_input['depletion_settings'] == \ + expected_depletion_settings + elif codename == 'serpent': + assert depcode_input['template_input_file_path'] == \ + str((input_path / 'tap_template.ini').resolve()) assert simulation_input['db_name'] == \ - str((data_path / '../temp_data/db_saltproc.h5').resolve()) + str((data_path / f'../{codename}_data/saltproc_runtime/saltproc_results.h5').resolve()) assert simulation_input['restart_flag'] is False np.testing.assert_equal( reactor_input['power_levels'], [ 1.250E+9, 1.250E+9]) - np.testing.assert_equal(reactor_input['dep_step_length_cumulative'], - [5, 10]) + np.testing.assert_equal(reactor_input['depletion_timesteps'], + [5, 5]) + + assert reactor_input['timestep_units'] == 'd' + assert reactor_input['timestep_type'] == 'stepwise' + + +def test_convert_cumulative_to_stepwise(): + timesteps = _convert_cumulative_to_stepwise([2, 4, 6]) + np.testing.assert_equal(timesteps, [2, 2, 2]) + + +@pytest.mark.parametrize("n_depletion_steps, depletion_timesteps, power_levels, throws_error", [ + (3, [1], [1], False), + (3, [1], [1, 1, 1], False), + (3, [1, 1, 1], [1], False), + (3, [1], [1, 1], True), + (3, [1, 1], [1, 1], False), + (None, [1, 1, 1], [1, 1, 1], False), + (None, [1], [1, 1, 1], True), + (None, [1, 1, 1], [1], True)]) +def test_validate_depletion_timesteps_power_levels(n_depletion_steps, + depletion_timesteps, + power_levels, + throws_error): + if throws_error: + with pytest.raises(ValueError): + _validate_depletion_timesteps_power_levels(n_depletion_steps, + depletion_timesteps, + power_levels) + else: + depletion_steps, power_levels = \ + _validate_depletion_timesteps_power_levels(n_depletion_steps, + depletion_timesteps, + power_levels) + assert (len(depletion_steps) == 2 or len(depletion_steps) == 3) + + +@pytest.mark.parametrize("expected_depletion_timesteps, timestep_units", [ + ([1/86400], SECOND_UNITS), + ([1/1440], MINUTE_UNITS), + ([1/24], HOUR_UNITS), + ([1.], DAY_UNITS), + ([365.25], YEAR_UNITS) +]) +def test_scale_depletion_timesteps(expected_depletion_timesteps, + timestep_units): + expected_depletion_timesteps = np.array(expected_depletion_timesteps) + base_timestep = np.array([1.]) + for unit in timestep_units: + input_timestep = base_timestep.copy() + scaled_timesteps = \ + _scale_depletion_timesteps(unit, input_timestep, 'serpent') + np.testing.assert_equal(scaled_timesteps, expected_depletion_timesteps) + input_timestep = base_timestep.copy() + scaled_timesteps = \ + _scale_depletion_timesteps(unit, input_timestep, 'openmc') + np.testing.assert_equal(scaled_timesteps, base_timestep) + input_timestep = base_timestep.copy() + scaled_timesteps = \ + _scale_depletion_timesteps('MWD/KG', input_timestep, 'serpent') + np.testing.assert_equal(scaled_timesteps, base_timestep) + input_timestep = base_timestep.copy() + scaled_timesteps = \ + _scale_depletion_timesteps('MWD/KG', input_timestep, 'openmc') + np.testing.assert_equal(scaled_timesteps, base_timestep) + + bad_unit = 'months' + with pytest.raises(IOError, + match=f'Unrecognized time unit: {bad_unit}'): + _scale_depletion_timesteps(bad_unit, [1], 'serpent') + + +@pytest.mark.parametrize("filename", [ + "constant_fission_yield", + "cutoff_fission_yield", + "flux_reaction_rate"]) +def test_openmc_depletion_settings(cwd, filename): + data_path = 'openmc_data' + data_path = cwd / data_path + main_input = str(data_path / f'{filename}_input.json') + out = read_main_input(main_input) + input_path, process_input_file, path_input_file, object_input = out + depcode_input, simulation_input, reactor_input = object_input + + assert depcode_input['template_input_file_path'] == \ + {'materials': str((input_path / 'tap_materials.xml').resolve()), + 'settings': str((input_path / 'tap_settings.xml').resolve())} + assert depcode_input['chain_file_path'] == \ + str((input_path / 'test_chain.xml').resolve()) + + modified_operator_kwargs = expected_depletion_settings['operator_kwargs'].copy() + if filename == 'constant_fission_yield': + + operator_kwargs = {'fission_yield_opts': {'energy': 0.0253}} + modified_operator_kwargs.update(operator_kwargs) + elif filename == 'cutoff_fission_yield': + operator_kwargs = {'fission_yield_mode': 'cutoff', + 'fission_yield_opts': {'cutoff': 112.0, + 'thermal_energy': 0.0253, + 'fast_energy': 5.0e5}} + modified_operator_kwargs.update(operator_kwargs) + + elif filename == 'flux_reaction_rate': + operator_kwargs = {'reaction_rate_mode': 'flux', + 'reaction_rate_opts': {'energies': [0.0253, 500000.0], + 'reactions': ['(n,gamma)'], + 'nuclides': ['U235', 'Pu239']} + } + modified_operator_kwargs.update(operator_kwargs) + + assert depcode_input['depletion_settings']['operator_kwargs'] == \ + modified_operator_kwargs def test_get_extraction_processes(proc_test_file): diff --git a/tests/unit_tests/test_simulation.py b/tests/unit_tests/test_simulation.py index f82a99b12..6d19016f8 100644 --- a/tests/unit_tests/test_simulation.py +++ b/tests/unit_tests/test_simulation.py @@ -1,4 +1,5 @@ """Test Simulation functions""" +from pathlib import Path def test_check_switch_geo_trigger(simulation): @@ -21,4 +22,7 @@ def test_check_switch_geo_trigger(simulation): def test_read_k_eds_delta(simulation): + old_db_path = simulation.db_path + simulation.db_path = str(Path(simulation.db_path).parents[1] / 'tap_reference_db.h5') assert simulation.read_k_eds_delta(7) is False + simulation.db_path = old_db_path