-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
380 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,7 @@ | |
# though prefer full imports in new code | ||
from .cccc import ( | ||
compxs, | ||
dif3d, | ||
dlayxs, | ||
fixsrc, | ||
gamiso, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
# Copyright 2023 TerraPower, LLC | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
""" | ||
Module for reading from and writing to DIF3D files, which are module dependent | ||
binary inputs for the DIF3D code. | ||
""" | ||
|
||
from armi import runLog | ||
from armi.nuclearDataIO import cccc | ||
|
||
|
||
FILE_SPEC_2D_PARAMS = ( | ||
[ | ||
"IPROBT", | ||
"ISOLNT", | ||
"IXTRAP", | ||
"MINBSZ", | ||
"NOUTMX", | ||
"IRSTRT", | ||
"LIMTIM", | ||
"NUPMAX", | ||
"IOSAVE", | ||
"IOMEG1", | ||
"INRMAX", | ||
"NUMORP", | ||
"IRETRN", | ||
] | ||
+ [f"IEDF{e}" for e in range(1, 11)] | ||
+ [ | ||
"NOUTBQ", | ||
"I0FLUX", | ||
"NOEDIT", | ||
"NOD3ED", | ||
"ISRHED", | ||
"NSN", | ||
"NSWMAX", | ||
"NAPRX", | ||
"NAPRXZ", | ||
"NFMCMX", | ||
"NXYSWP", | ||
"NZSWP", | ||
"ISYMF", | ||
"NCMRZS", | ||
"ISEXTR", | ||
"NPNO", | ||
"NXTR", | ||
"IOMEG2", | ||
"IFULL", | ||
"NVFLAG", | ||
"ISIMPL", | ||
"IWNHFL", | ||
"IPERT", | ||
"IHARM", | ||
] | ||
) | ||
|
||
FILE_SPEC_3D_PARAMS = [ | ||
"EPS1", | ||
"EPS2", | ||
"EPS3", | ||
"EFFK", | ||
"FISMIN", | ||
"PSINRM", | ||
"POWIN", | ||
"SIGBAR", | ||
"EFFKQ", | ||
"EPSWP", | ||
] + [f"DUM{e}" for e in range(1, 21)] | ||
|
||
TITLE_RANGE = 11 | ||
|
||
|
||
class Dif3dData(cccc.DataContainer): | ||
def __init__(self): | ||
cccc.DataContainer.__init__(self) | ||
|
||
self.twoD = {e: None for e in FILE_SPEC_2D_PARAMS} | ||
self.threeD = {e: None for e in FILE_SPEC_3D_PARAMS} | ||
self.fourD = None | ||
self.fiveD = None | ||
|
||
|
||
class Dif3dStream(cccc.StreamWithDataContainer): | ||
@staticmethod | ||
def _getDataContainer() -> Dif3dData: | ||
return Dif3dData() | ||
|
||
def _rwFileID(self) -> None: | ||
""" | ||
Record for file identification information. | ||
The parameters are stored as a dictionary under the attribute `metadata`. | ||
""" | ||
with self.createRecord() as record: | ||
for param in ["HNAME", "HUSE1", "HUSE2"]: | ||
self._metadata[param] = record.rwString(self._metadata[param], 8) | ||
self._metadata["VERSION"] = record.rwInt(self._metadata["VERSION"]) | ||
|
||
def _rw1DRecord(self) -> None: | ||
""" | ||
Record for problem title, storage, and dump specifications. | ||
The parameters are stored as a dictionary under the attribute `metadata`. | ||
""" | ||
with self.createRecord() as record: | ||
for i in range(TITLE_RANGE): | ||
param = f"TITLE{i}" | ||
self._metadata[param] = record.rwString(self._metadata[param], 8) | ||
self._metadata["MAXSIZ"] = record.rwInt(self._metadata["MAXSIZ"]) | ||
self._metadata["MAXBLK"] = record.rwInt(self._metadata["MAXBLK"]) | ||
self._metadata["IPRINT"] = record.rwInt(self._metadata["IPRINT"]) | ||
|
||
def _rw2DRecord(self) -> None: | ||
""" | ||
Record for DIF3D integer control parameters. | ||
The parameters are stored as a dictionary under the attribute `twoD`. | ||
""" | ||
with self.createRecord() as record: | ||
for param in FILE_SPEC_2D_PARAMS: | ||
self._data.twoD[param] = record.rwInt(self._data.twoD[param]) | ||
|
||
def _rw3DRecord(self) -> None: | ||
""" | ||
Record for convergence criteria and other sundry floating point data (such as | ||
k-effective). | ||
The parameters are stored as a dictionary under the attribute `threeD`. | ||
""" | ||
with self.createRecord() as record: | ||
for param in FILE_SPEC_3D_PARAMS: | ||
self._data.threeD[param] = record.rwDouble(self._data.threeD[param]) | ||
|
||
def _rw4DRecord(self) -> None: | ||
""" | ||
Record for the optimum overrelaxation factors. This record is only present when | ||
using DIF3D-FD and if `NUMORP` is greater than 0. | ||
The parameters are stored as a dictionary under the attribute `fourD`. This | ||
could be changed into a list in the future since this record represents groupwise | ||
data. | ||
""" | ||
if self._data.twoD["NUMORP"] != 0: | ||
omegaParams = [f"OMEGA{e}" for e in range(1, self._data.twoD["NUMORP"] + 1)] | ||
|
||
with self.createRecord() as record: | ||
# Initialize the record if we're reading | ||
if self._data.fourD is None: | ||
self._data.fourD = {omegaParam: None for omegaParam in omegaParams} | ||
|
||
for omegaParam in omegaParams: | ||
self._data.fourD[omegaParam] = record.rwDouble( | ||
self._data.fourD[omegaParam] | ||
) | ||
|
||
def _rw5DRecord(self) -> None: | ||
""" | ||
Record for the axial coarse mesh rebalancing boundaries. Coarse mesh balancing is | ||
disabled in DIF3D-VARIANT, so this record is only relevant for DIF3D-Nodal. This | ||
record is only present if `NCMRZS` is greater than 0. | ||
The parameters are stored as a dictionary under the attribute `fiveD`. | ||
""" | ||
if self._data.twoD["NCMRZS"] != 0: | ||
zcmrcParams = [f"ZCMRC{e}" for e in range(1, self._data.twoD["NCMRZS"] + 1)] | ||
nzintsParams = [ | ||
f"NZINTS{e}" for e in range(1, self._data.twoD["NCMRZS"] + 1) | ||
] | ||
|
||
with self.createRecord() as record: | ||
# Initialize the record if we're reading | ||
if self._data.fiveD is None: | ||
self._data.fiveD = {zcmrcParam: None for zcmrcParam in zcmrcParams} | ||
self._data.fiveD.update( | ||
{nzintsParam: None for nzintsParam in nzintsParams} | ||
) | ||
|
||
for zcmrcParam in zcmrcParams: | ||
self._data.fiveD[zcmrcParam] = record.rwDouble( | ||
self._data.fiveD[zcmrcParam] | ||
) | ||
for nzintsParam in nzintsParams: | ||
self._data.fiveD[nzintsParam] = record.rwInt( | ||
self._data.fiveD[nzintsParam] | ||
) | ||
|
||
def readWrite(self): | ||
"""Reads or writes metadata and data from 5 records""" | ||
msg = f"{'Reading' if 'r' in self._fileMode else 'Writing'} DIF3D binary data {self}" | ||
runLog.info(msg) | ||
|
||
self._rwFileID() | ||
self._rw1DRecord() | ||
self._rw2DRecord() | ||
self._rw3DRecord() | ||
self._rw4DRecord() | ||
self._rw5DRecord() | ||
|
||
|
||
readBinary = Dif3dStream.readBinary | ||
readAscii = Dif3dStream.readAscii | ||
writeBinary = Dif3dStream.writeBinary | ||
writeAscii = Dif3dStream.writeAscii |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
# Copyright 2023 TerraPower, LLC | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
"""Test reading/writing of DIF3D binary input.""" | ||
|
||
import os | ||
import unittest | ||
|
||
from armi.nuclearDataIO.cccc import dif3d | ||
from armi.utils.directoryChangers import TemporaryDirectoryChanger | ||
|
||
THIS_DIR = os.path.dirname(__file__) | ||
|
||
SIMPLE_HEXZ_INP = os.path.join(THIS_DIR, "../../tests", "simple_hexz.inp") | ||
SIMPLE_HEXZ_DIF3D = os.path.join(THIS_DIR, "fixtures", "simple_hexz.dif3d") | ||
|
||
|
||
class TestDif3dSimpleHexz(unittest.TestCase): | ||
@classmethod | ||
def setUpClass(cls): | ||
""" | ||
Load DIF3D data from binary file. This binary file was generated by running | ||
dif3d.exe v11.0r3284 on the SIMPLE_HEXZ_INP file above (and renaming the DIF3D | ||
binary file to simple_hexz.dif3d). | ||
""" | ||
cls.df = dif3d.Dif3dStream.readBinary(SIMPLE_HEXZ_DIF3D) | ||
|
||
def test__rwFileID(self): | ||
"""Verify the file identification info.""" | ||
self.assertEqual(self.df.metadata["HNAME"], "DIF3D") | ||
self.assertEqual(self.df.metadata["HUSE1"], "") | ||
self.assertEqual(self.df.metadata["HUSE2"], "") | ||
self.assertEqual(self.df.metadata["VERSION"], 1) | ||
|
||
def test__rwFile1DRecord(self): | ||
"""Verify the rest of the metadata""" | ||
TITLE_A6 = ["3D Hex", "-Z to", "genera", "te NHF", "LUX fi", "le"] | ||
EXPECTED_TITLE = TITLE_A6 + [""] * 5 | ||
for i in range(dif3d.TITLE_RANGE): | ||
self.assertEqual(self.df.metadata[f"TITLE{i}"], EXPECTED_TITLE[i]) | ||
self.assertEqual(self.df.metadata["MAXSIZ"], 10000) | ||
self.assertEqual(self.df.metadata["MAXBLK"], 1800000) | ||
self.assertEqual(self.df.metadata["IPRINT"], 0) | ||
|
||
def test__rw2DRecord(self): | ||
"""Verify the control parameters""" | ||
EXPECTED_2D = [ | ||
0, | ||
0, | ||
0, | ||
10000, | ||
30, | ||
0, | ||
1000000000, | ||
5, | ||
0, | ||
0, | ||
50, | ||
0, | ||
1, | ||
1, | ||
0, | ||
0, | ||
0, | ||
110, | ||
10, | ||
100, | ||
1, | ||
0, | ||
0, | ||
0, | ||
0, | ||
0, | ||
0, | ||
0, | ||
0, | ||
10, | ||
40, | ||
32, | ||
0, | ||
0, | ||
2, | ||
0, | ||
0, | ||
0, | ||
0, | ||
0, | ||
0, | ||
0, | ||
0, | ||
0, | ||
0, | ||
0, | ||
0, | ||
] | ||
for i, param in enumerate(dif3d.FILE_SPEC_2D_PARAMS): | ||
self.assertEqual(self.df.twoD[param], EXPECTED_2D[i]) | ||
|
||
def test__rw3DRecord(self): | ||
"""Verify the convergence criteria and other floating point data""" | ||
EXPECTED_3D = [ | ||
1e-7, | ||
1e-5, | ||
1e-5, | ||
3.823807613470224e-01, | ||
1e-3, | ||
4e-2, | ||
1e0, | ||
0e0, | ||
0e0, | ||
9.999999747378752e-05, | ||
] + [0.0 for i in range(1, 21)] | ||
for i, param in enumerate(dif3d.FILE_SPEC_3D_PARAMS): | ||
self.assertEqual(self.df.threeD[param], EXPECTED_3D[i]) | ||
|
||
def test__rw4DRecord(self): | ||
"""Verify the optimum overrelaxation factors""" | ||
self.assertEqual(self.df.fourD, None) | ||
|
||
def test__rw5DRecord(self): | ||
"""Verify the axial coarse-mesh rebalance boundaries""" | ||
self.assertEqual(self.df.fiveD, None) | ||
|
||
def test_writeBinary(self): | ||
"""Verify binary equivalence of written DIF3D file.""" | ||
with TemporaryDirectoryChanger(): | ||
dif3d.Dif3dStream.writeBinary(self.df, "DIF3D2") | ||
with open(SIMPLE_HEXZ_DIF3D, "rb") as f1, open("DIF3D2", "rb") as f2: | ||
expectedData = f1.read() | ||
actualData = f2.read() | ||
for expected, actual in zip(expectedData, actualData): | ||
self.assertEqual(expected, actual) | ||
|
||
|
||
class TestDif3dEmptyRecords(unittest.TestCase): | ||
def test_empty4and5Records(self): | ||
"""Since the inputs results in these being None, get test coverage another way""" | ||
df = dif3d.Dif3dStream.readBinary(SIMPLE_HEXZ_DIF3D) | ||
# Hack some values that allow 4 and 5 records to be populated \ | ||
# and then populate them | ||
df.twoD["NUMORP"] = 1 | ||
df.twoD["NCMRZS"] = 1 | ||
df.fourD = {"OMEGA1": 1.0} | ||
df.fiveD = {"ZCMRC1": 1.0, "NZINTS1": 10} | ||
with TemporaryDirectoryChanger(): | ||
# Write then read a new one | ||
dif3d.Dif3dStream.writeBinary(df, "DIF3D2") | ||
df2 = dif3d.Dif3dStream.readBinary("DIF3D2") | ||
# Kinda a null test, but this coverage caught some code mistakes! | ||
self.assertEqual(df2.fourD, df.fourD) | ||
self.assertEqual(df2.fiveD, df.fiveD) |
Oops, something went wrong.