|
10 | 10 | import h5py |
11 | 11 |
|
12 | 12 | import openmc |
13 | | -from openmc.mesh import MeshBase, RectilinearMesh, CylindricalMesh, SphericalMesh, UnstructuredMesh |
| 13 | +from openmc.mesh import ( |
| 14 | + MeshBase, RegularMesh, RectilinearMesh, CylindricalMesh, SphericalMesh, |
| 15 | + UnstructuredMesh, |
| 16 | +) |
14 | 17 | from openmc.tallies import Tallies |
15 | 18 | import openmc.checkvalue as cv |
16 | 19 | from openmc.checkvalue import PathLike |
@@ -819,6 +822,51 @@ def hdf5_to_wws(path='weight_windows.h5') -> WeightWindowsList: |
819 | 822 | return WeightWindowsList.from_hdf5(path) |
820 | 823 |
|
821 | 824 |
|
| 825 | +def _write_mesh_group(meshes_grp: h5py.Group, mesh: MeshBase): |
| 826 | + """Write *mesh* into *meshes_grp* as a ``mesh <id>`` subgroup. |
| 827 | +
|
| 828 | + Matches the layout produced by ``Mesh::to_hdf5`` in src/mesh.cpp so the |
| 829 | + resulting file is readable by ``openmc_weight_windows_import``. |
| 830 | + """ |
| 831 | + grp = meshes_grp.create_group(f'mesh {mesh.id}') |
| 832 | + grp.attrs['id'] = np.int32(mesh.id) |
| 833 | + grp.create_dataset('name', data=np.bytes_(mesh.name or '')) |
| 834 | + |
| 835 | + if isinstance(mesh, RegularMesh): |
| 836 | + grp.create_dataset('type', data=np.bytes_('regular')) |
| 837 | + grp.create_dataset('dimension', |
| 838 | + data=np.asarray(mesh.dimension, dtype=np.int32)) |
| 839 | + grp.create_dataset('lower_left', |
| 840 | + data=np.asarray(mesh.lower_left, dtype=float)) |
| 841 | + grp.create_dataset('upper_right', |
| 842 | + data=np.asarray(mesh.upper_right, dtype=float)) |
| 843 | + grp.create_dataset('width', data=np.asarray(mesh.width, dtype=float)) |
| 844 | + elif isinstance(mesh, RectilinearMesh): |
| 845 | + grp.create_dataset('type', data=np.bytes_('rectilinear')) |
| 846 | + grp.create_dataset('x_grid', data=np.asarray(mesh.x_grid, dtype=float)) |
| 847 | + grp.create_dataset('y_grid', data=np.asarray(mesh.y_grid, dtype=float)) |
| 848 | + grp.create_dataset('z_grid', data=np.asarray(mesh.z_grid, dtype=float)) |
| 849 | + elif isinstance(mesh, CylindricalMesh): |
| 850 | + grp.create_dataset('type', data=np.bytes_('cylindrical')) |
| 851 | + grp.create_dataset('r_grid', data=np.asarray(mesh.r_grid, dtype=float)) |
| 852 | + grp.create_dataset('phi_grid', data=np.asarray(mesh.phi_grid, dtype=float)) |
| 853 | + grp.create_dataset('z_grid', data=np.asarray(mesh.z_grid, dtype=float)) |
| 854 | + grp.create_dataset('origin', data=np.asarray(mesh.origin, dtype=float)) |
| 855 | + elif isinstance(mesh, SphericalMesh): |
| 856 | + grp.create_dataset('type', data=np.bytes_('spherical')) |
| 857 | + grp.create_dataset('r_grid', data=np.asarray(mesh.r_grid, dtype=float)) |
| 858 | + grp.create_dataset('theta_grid', data=np.asarray(mesh.theta_grid, dtype=float)) |
| 859 | + grp.create_dataset('phi_grid', data=np.asarray(mesh.phi_grid, dtype=float)) |
| 860 | + grp.create_dataset('origin', data=np.asarray(mesh.origin, dtype=float)) |
| 861 | + elif isinstance(mesh, UnstructuredMesh): |
| 862 | + raise NotImplementedError( |
| 863 | + "Exporting weight windows on an UnstructuredMesh is not yet " |
| 864 | + "implemented in Python; use openmc.lib.export_weight_windows()." |
| 865 | + ) |
| 866 | + else: |
| 867 | + raise TypeError(f"Unknown mesh type: {type(mesh).__name__}") |
| 868 | + |
| 869 | + |
822 | 870 | class WeightWindowsList(list): |
823 | 871 | """A list of WeightWindows objects. |
824 | 872 |
|
@@ -1061,29 +1109,66 @@ def from_wwinp(cls, path: PathLike) -> Self: |
1061 | 1109 | def export_to_hdf5(self, path: PathLike = 'weight_windows.h5', **init_kwargs): |
1062 | 1110 | """Write weight windows to an HDF5 file. |
1063 | 1111 |
|
| 1112 | + Writes the file directly via :mod:`h5py`, mirroring the layout |
| 1113 | + produced by :func:`openmc.lib.export_weight_windows`. The previous |
| 1114 | + XML round-trip raised :class:`MemoryError` on multi-GB bound arrays |
| 1115 | + because of the intermediate ASCII allocation inside lxml. |
| 1116 | +
|
1064 | 1117 | Parameters |
1065 | 1118 | ---------- |
1066 | 1119 | path : PathLike |
1067 | 1120 | Path to the file to write weight windows to |
1068 | 1121 | **init_kwargs |
1069 | | - Keyword arguments passed to :func:`openmc.lib.init` |
1070 | | -
|
| 1122 | + Unused. Retained for backward compatibility (previously forwarded |
| 1123 | + to :func:`openmc.lib.init`). |
1071 | 1124 | """ |
1072 | | - import openmc.lib |
1073 | 1125 | cv.check_type('path', path, PathLike) |
1074 | | - |
1075 | | - # Create a temporary model with the weight windows |
1076 | | - model = openmc.Model() |
1077 | | - sph = openmc.Sphere(boundary_type='vacuum') |
1078 | | - cell = openmc.Cell(region=-sph) |
1079 | | - model.geometry = openmc.Geometry([cell]) |
1080 | | - model.settings.weight_windows = self |
1081 | | - model.settings.particles = 100 |
1082 | | - model.settings.batches = 1 |
1083 | | - |
1084 | | - # Get absolute path before moving to temporary directory |
1085 | 1126 | path = Path(path).resolve() |
1086 | 1127 |
|
1087 | | - # Load the model with openmc.lib and then export it to an HDF5 file |
1088 | | - with openmc.lib.TemporarySession(model, **init_kwargs): |
1089 | | - openmc.lib.export_weight_windows(path) |
| 1128 | + with h5py.File(path, 'w') as f: |
| 1129 | + f.attrs['filetype'] = np.bytes_('weight_windows') |
| 1130 | + f.attrs['version'] = np.asarray([1, 0], dtype=np.int32) |
| 1131 | + |
| 1132 | + meshes_grp = f.create_group('meshes') |
| 1133 | + wws_grp = f.create_group('weight_windows') |
| 1134 | + |
| 1135 | + seen_mesh_ids = set() |
| 1136 | + ww_ids = [] |
| 1137 | + for ww in self: |
| 1138 | + if ww.mesh.id not in seen_mesh_ids: |
| 1139 | + _write_mesh_group(meshes_grp, ww.mesh) |
| 1140 | + seen_mesh_ids.add(ww.mesh.id) |
| 1141 | + |
| 1142 | + g = wws_grp.create_group(f'weight_windows_{ww.id}') |
| 1143 | + ww_ids.append(int(ww.id)) |
| 1144 | + |
| 1145 | + g.create_dataset('mesh', data=np.int32(ww.mesh.id)) |
| 1146 | + g.create_dataset('particle_type', |
| 1147 | + data=np.bytes_(str(ww.particle_type))) |
| 1148 | + g.create_dataset('energy_bounds', |
| 1149 | + data=np.asarray(ww.energy_bounds, dtype=float)) |
| 1150 | + |
| 1151 | + # 2D (ne, n_voxels) C-contiguous: the C++ reader expects this layout. |
| 1152 | + ne = ww.lower_ww_bounds.shape[-1] |
| 1153 | + lo = np.ascontiguousarray(ww.lower_ww_bounds.T).reshape(ne, -1) |
| 1154 | + up = np.ascontiguousarray(ww.upper_ww_bounds.T).reshape(ne, -1) |
| 1155 | + g.create_dataset('lower_ww_bounds', data=lo) |
| 1156 | + g.create_dataset('upper_ww_bounds', data=up) |
| 1157 | + |
| 1158 | + g.create_dataset('survival_ratio', |
| 1159 | + data=float(ww.survival_ratio)) |
| 1160 | + # max_lower_bound_ratio is read unconditionally by C++; default 1.0 when unset. |
| 1161 | + mlbr = ww.max_lower_bound_ratio |
| 1162 | + g.create_dataset( |
| 1163 | + 'max_lower_bound_ratio', |
| 1164 | + data=float(mlbr if mlbr is not None else 1.0), |
| 1165 | + ) |
| 1166 | + g.create_dataset('max_split', data=np.int32(ww.max_split)) |
| 1167 | + g.create_dataset('weight_cutoff', |
| 1168 | + data=float(ww.weight_cutoff)) |
| 1169 | + |
| 1170 | + wws_grp.attrs['ids'] = np.asarray(ww_ids, dtype=np.int32) |
| 1171 | + wws_grp.attrs['n_weight_windows'] = np.int32(len(ww_ids)) |
| 1172 | + meshes_grp.attrs['ids'] = np.asarray(sorted(seen_mesh_ids), |
| 1173 | + dtype=np.int32) |
| 1174 | + meshes_grp.attrs['n_meshes'] = np.int32(len(seen_mesh_ids)) |
0 commit comments