diff --git a/doc/releasenotes/v0.5.0.rst b/doc/releasenotes/v0.5.0.rst index b4e995d75..22a8e2a73 100644 --- a/doc/releasenotes/v0.5.0.rst +++ b/doc/releasenotes/v0.5.0.rst @@ -148,13 +148,15 @@ Python API Changes - ``write_mat_file()`` → ``update_depletable_materials()`` - ``get_nuc_name()`` → ``convert_nuclide_code_to_name()`` - ``convert_nuclide_name_serpent_to_zam()`` → ``convert_nuclide_code_to_zam()`` - - ``change_sim_par()`` → (deleted) - - (new function) → ``get_neutron_settings()`` - ``create_iter_matfile()`` → ``create_runtime_matfile()`` - ``replace_burnup_parameters()`` → ``set_power_load()`` - ``write_depcode_input()`` → ``write_runtime_input()`` - ``iter_inputfile`` → ``runtime_inputfile`` - ``iter_matfile`` → ``runtime_matfile`` + - ``change_sim_par()`` → (deleted) + - (new function) → ``get_neutron_settings()`` + - (new function) → ``_get_burnable_materials_file()`` + - (new function) → ``_get_burnable_material_card_data()`` - ``OpenMCDepcode`` is a ``Depcode`` subclass that interfaces with ``openmc``. This class implements the following functions diff --git a/saltproc/serpent_depcode.py b/saltproc/serpent_depcode.py index 679bff333..e5ccdc6c1 100644 --- a/saltproc/serpent_depcode.py +++ b/saltproc/serpent_depcode.py @@ -35,7 +35,6 @@ class SerpentDepcode(Depcode): inactive_cycles : int Number of inactive cycles. - """ def __init__(self, @@ -95,7 +94,7 @@ def get_neutron_settings(self, file_lines): def create_runtime_matfile(self, file_lines): """Creates the runtime material file tracking burnable materials - ans inserts the path to this file in the Serpent2 runtime input file + and inserts the path to this file in the Serpent2 runtime input file Parameters ---------- @@ -108,6 +107,20 @@ def create_runtime_matfile(self, file_lines): Serpent2 runtime input file with updated material file path. """ + burnable_materials_path, absolute_path = self._get_burnable_materials_file(file_lines) + + # Create data directory + Path.mkdir(Path(self.runtime_matfile).parents[0], exist_ok=True) + + # Get material cards + flines = self.read_plaintext_file(absolute_path) + self._get_burnable_material_card_data(flines) + + # Create file with path for SaltProc rewritable iterative material file + shutil.copy2(absolute_path, self.runtime_matfile) + return [line.replace(burnable_materials_path, self.runtime_matfile) for line in file_lines] + + def _get_burnable_materials_file(self, file_lines): runtime_dir = Path(self.template_input_file_path).parents[0] include_card = [line for line in file_lines if line.startswith("include ")] if not include_card: @@ -119,17 +132,30 @@ def create_runtime_matfile(self, file_lines): absolute_path = (runtime_dir / burnable_materials_path) else: absolute_path = Path(burnable_materials_path) - with open(absolute_path) as f: - if 'mat ' not in f.read(): - raise IOError('Template file ' - f'{self.template_input_file_path} includes ' - 'no file with materials description') - # Create data directory - Path.mkdir(Path(self.runtime_matfile).parents[0], exist_ok=True) - - # Create file with path for SaltProc rewritable iterative material file - shutil.copy2(absolute_path, self.runtime_matfile) - return [line.replace(burnable_materials_path, self.runtime_matfile) for line in file_lines] + with open(absolute_path) as f: + if 'mat ' not in f.read(): + raise IOError('Template file ' + f'{self.template_input_file_path} includes ' + 'no file with materials description') + return burnable_materials_path, absolute_path.resolve() + + def _get_burnable_material_card_data(self, file_lines): + # Get data for matfile + mat_cards = \ + [line.split() for line in file_lines if line.startswith("mat ")] + + for card in mat_cards: + if 'fix' not in card: + raise IOError(f'"mat" card for burnable material "{card[1]}"' + ' does not have a "fix" option. Burnable materials' + ' in SaltProc must include the "fix" option. See' + ' the serpent wiki for more information:' + ' https://serpent.vtt.fi/mediawiki/index.php/Input_syntax_manual#mat') + # Get volume indices + card_volume_idx = [(card.index('vol') + 1) for card in mat_cards] + mat_names = [card[1] for card in mat_cards] + mat_data = zip(mat_cards, card_volume_idx)#, mat_extensions) + self._burnable_material_card_data = dict(zip(mat_names, mat_data)) def convert_nuclide_code_to_name(self, nuc_code): """Converts Serpent2 nuclide code to symbolic nuclide name. @@ -520,13 +546,17 @@ def update_depletable_materials(self, mats, dep_end_time): f.write('%% Material compositions (after %f days)\n\n' % dep_end_time) nuc_code_map = self.map_nuclide_code_zam_to_serpent() + if not(hasattr(self, '_burnable_material_card_data')): + lines = self.read_plaintext_file(self.template_input_file_path) + _, abs_src_matfile = self.get_burnable_materials_file(lines) + file_lines = self.read_plaintext_file(abs_src_matfile) + self._get_burnable_material_card_data(file_lines) for name, mat in mats.items(): - f.write('mat %s %5.9E burn 1 fix %3s %4i vol %7.5E\n' % - (name, - -mat.density, - '09c', - mat.temp, - mat.vol)) + mat_card, card_volume_idx = self._burnable_material_card_data[name] + mat_card[2] = str(-mat.density) + mat_card[card_volume_idx] = "%7.5E" % mat.vol + f.write(" ".join(mat_card)) + f.write("\n") for nuc_code, mass_fraction in mat.comp.items(): zam_code = pyname.zzaaam(nuc_code) f.write(' %9s %7.14E\n' % diff --git a/tests/integration_tests/file_interface_serpent/test.py b/tests/integration_tests/file_interface_serpent/test.py index e1e0bffd0..6096d77a4 100644 --- a/tests/integration_tests/file_interface_serpent/test.py +++ b/tests/integration_tests/file_interface_serpent/test.py @@ -36,6 +36,53 @@ def test_runtime_input_from_template(serpent_depcode, msr): file_data = serpent_depcode.create_runtime_matfile(file_data) assert file_data[0].split()[-1] == '\"' + \ serpent_depcode.runtime_matfile + '\"' + + # get_burnable_material_card_data + burnable_material_card_data = {'fuel': + (['mat', + 'fuel', + '-4.960200000E+00', + 'rgb', + '253', + '231', + '37', + 'burn', + '1', + 'fix', + '09c', + '900', + 'vol', + '4.435305E+7', + '%', + 'just', + 'core', + 'volume', + '2.27175E+07'], 13), + 'ctrlPois': + (['mat', + 'ctrlPois', + '-2.52', + 'burn', + '1', + 'fix', + '09c', + '900', + 'rgb', + '255', + '128', + '0', + 'vol', + '1.11635E+04'], 13)} + + for ref_key, test_key in \ + zip(serpent_depcode._burnable_material_card_data.keys(), + burnable_material_card_data.keys()): + assert ref_key == test_key + ref_data = serpent_depcode._burnable_material_card_data[ref_key] + test_data = burnable_material_card_data[test_key] + np.testing.assert_array_equal(np.array(ref_data, dtype=object), + np.array(test_data, dtype=object)) + remove(serpent_depcode.runtime_matfile) # set_power_load diff --git a/tests/unit_tests/test_serpent_depcode.py b/tests/unit_tests/test_serpent_depcode.py index 4a5f2d244..691d16f09 100644 --- a/tests/unit_tests/test_serpent_depcode.py +++ b/tests/unit_tests/test_serpent_depcode.py @@ -1,6 +1,8 @@ """Test SerpentDepcode functions""" import pytest import numpy as np +import tempfile +from pathlib import Path from saltproc import SerpentDepcode @@ -35,6 +37,37 @@ def test_get_neutron_settings(serpent_depcode): assert serpent_depcode.active_cycles == 20 assert serpent_depcode.inactive_cycles == 20 +def test_get_burnable_materials_file(serpent_depcode): + err1 = (f'Template file {serpent_depcode.template_input_file_path}' + ' has no statements') + + with pytest.raises(IOError, match=err1): + lines_no_include = ['this line does not start with include'] + serpent_depcode._get_burnable_materials_file(lines_no_include) + + with tempfile.NamedTemporaryFile(mode='w+') as tf: + tf.write('some junk') + old_template = serpent_depcode.template_input_file_path + serpent_depcode.template_input_file_path = tf.name + + err2 = (f'Template file {serpent_depcode.template_input_file_path}' + ' includes no file with materials description') + with pytest.raises(IOError, match=err2): + lines_bad_matfile = [f'include "{tf.name}"'] + serpent_depcode._get_burnable_materials_file(lines_bad_matfile) + serpent_depcode.template_input_file_path = old_template + +def test_get_burnable_material_card_data(serpent_depcode): + bad_mat_cards = ['mat fuel -9.2 burn 1 fix 09c', + 'mat blanket -9.1 burn 1'] + + err = ('"mat" card for burnable material "blanket" does not have a "fix"' + ' option. Burnable materials in SaltProc must include the "fix"' + ' option. See the serpent wiki for more information:' + ' https://serpent.vtt.fi/mediawiki/index.php/Input_syntax_manual#mat') + with pytest.raises(IOError, match=err): + serpent_depcode._get_burnable_material_card_data(bad_mat_cards) + def test_read_plaintext_file(serpent_depcode): template_str = serpent_depcode.read_plaintext_file(