Skip to content

Commit f43e046

Browse files
author
skywalker_cn
committed
Merge branch 'develop' into virtual_lattice_0.15.2
2 parents 84bc446 + e29c46b commit f43e046

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+475
-115
lines changed

cmake/OpenMCConfig.cmake.in

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
get_filename_component(OpenMC_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY)
22

3-
find_package(fmt REQUIRED HINTS ${OpenMC_CMAKE_DIR}/../fmt)
4-
find_package(pugixml REQUIRED HINTS ${OpenMC_CMAKE_DIR}/../pugixml)
5-
find_package(xtl REQUIRED HINTS ${OpenMC_CMAKE_DIR}/../xtl)
6-
find_package(xtensor REQUIRED HINTS ${OpenMC_CMAKE_DIR}/../xtensor)
3+
# Compute the install prefix from this file's location
4+
get_filename_component(_OPENMC_PREFIX "${OpenMC_CMAKE_DIR}/../../.." ABSOLUTE)
5+
6+
find_package(fmt CONFIG REQUIRED HINTS ${_OPENMC_PREFIX})
7+
find_package(pugixml CONFIG REQUIRED HINTS ${_OPENMC_PREFIX})
8+
find_package(xtl CONFIG REQUIRED HINTS ${_OPENMC_PREFIX})
9+
find_package(xtensor CONFIG REQUIRED HINTS ${_OPENMC_PREFIX})
710
if(@OPENMC_USE_DAGMC@)
811
find_package(DAGMC REQUIRED HINTS @DAGMC_DIR@)
912
endif()

docs/source/usersguide/kinetics.rst

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,23 @@ are needed to compute kinetics parameters in OpenMC:
6767
Obtaining kinetics parameters
6868
-----------------------------
6969

70+
The ``Model`` class can be used to automatically generate all IFP tallies using
71+
the Python API with :attr:`openmc.Settings.ifp_n_generation` greater than 0 and
72+
the :meth:`openmc.Model.add_ifp_kinetics_tallies` method::
73+
74+
model = openmc.Model(geometry, settings=settings)
75+
model.add_kinetics_parameters_tallies(num_groups=6) # Add 6 precursor groups
76+
77+
Alternatively, each of the tallies can be manually defined using group-wise or
78+
total :math:`\beta_{\text{eff}}` specified by providing a 6-group
79+
:class:`openmc.DelayedGroupFilter`::
80+
81+
beta_tally = openmc.Tally(name="group-beta-score")
82+
beta_tally.scores = ["ifp-beta-numerator"]
83+
84+
# Add DelayedGroupFilter to enable group-wise tallies
85+
beta_tally.filters = [openmc.DelayedGroupFilter(list(range(1, 7)))]
86+
7087
Here is an example showing how to declare the three available IFP scores in a
7188
single tally::
7289

@@ -95,6 +112,12 @@ for ``ifp-denominator``:
95112
96113
\beta_{\text{eff}} = \frac{S_{\text{ifp-beta-numerator}}}{S_{\text{ifp-denominator}}}
97114
115+
The kinetics parameters can be retrieved directly from a statepoint file using
116+
the :meth:`openmc.StatePoint.ifp_results` method::
117+
118+
with openmc.StatePoint(output_path) as sp:
119+
generation_time, beta_eff = sp.get_kinetics_parameters()
120+
98121
.. only:: html
99122

100123
.. rubric:: References
@@ -107,4 +130,4 @@ for ``ifp-denominator``:
107130
of the Iterated Fission Probability Method in OpenMC to Compute Adjoint-Weighted
108131
Kinetics Parameters", International Conference on Mathematics and Computational
109132
Methods Applied to Nuclear Science and Engineering (M&C 2025), Denver, April 27-30,
110-
2025 (to be presented).
133+
2025.

include/openmc/ifp.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,14 @@ vector<T> _ifp(const T& value, const vector<T>& data)
6868
//!
6969
//! Add the IFP information in the IFP banks using the same index
7070
//! as the one used to append the fission site to the fission bank.
71+
//! The information stored are the delayed group number and lifetime
72+
//! of the neutron that created the fission event.
7173
//! Multithreading protection is guaranteed by the index returned by the
7274
//! thread_safe_append call in physics.cpp.
7375
//!
74-
//! Needs to be done after the delayed group is found.
75-
//!
7676
//! \param[in] p Particle
77-
//! \param[in] site Fission site
7877
//! \param[in] idx Bank index from the thread_safe_append call in physics.cpp
79-
void ifp(const Particle& p, const SourceSite& site, int64_t idx);
78+
void ifp(const Particle& p, int64_t idx);
8079

8180
//! Resize the IFP banks used in the simulation
8281
void resize_simulation_ifp_banks();

include/openmc/mesh.h

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -990,25 +990,26 @@ class LibMesh : public UnstructuredMesh {
990990

991991
libMesh::MeshBase* mesh_ptr() const { return m_; };
992992

993-
private:
994-
void initialize() override;
995-
void set_mesh_pointer_from_filename(const std::string& filename);
996-
void build_eqn_sys();
997-
993+
protected:
998994
// Methods
999995

1000996
//! Translate a bin value to an element reference
1001-
const libMesh::Elem& get_element_from_bin(int bin) const;
997+
virtual const libMesh::Elem& get_element_from_bin(int bin) const;
1002998

1003999
//! Translate an element pointer to a bin index
1004-
int get_bin_from_element(const libMesh::Elem* elem) const;
1000+
virtual int get_bin_from_element(const libMesh::Elem* elem) const;
1001+
1002+
libMesh::MeshBase* m_; //!< pointer to libMesh MeshBase instance, always set
1003+
//!< during intialization
1004+
private:
1005+
void initialize() override;
1006+
void set_mesh_pointer_from_filename(const std::string& filename);
1007+
void build_eqn_sys();
10051008

10061009
// Data members
10071010
unique_ptr<libMesh::MeshBase> unique_m_ =
10081011
nullptr; //!< pointer to the libMesh MeshBase instance, only used if mesh is
10091012
//!< created inside OpenMC
1010-
libMesh::MeshBase* m_; //!< pointer to libMesh MeshBase instance, always set
1011-
//!< during intialization
10121013
vector<unique_ptr<libMesh::PointLocatorBase>>
10131014
pl_; //!< per-thread point locators
10141015
unique_ptr<libMesh::EquationSystems>
@@ -1022,8 +1023,34 @@ class LibMesh : public UnstructuredMesh {
10221023
libMesh::BoundingBox bbox_; //!< bounding box of the mesh
10231024
libMesh::dof_id_type
10241025
first_element_id_; //!< id of the first element in the mesh
1026+
};
1027+
1028+
class AdaptiveLibMesh : public LibMesh {
1029+
public:
1030+
// Constructor
1031+
AdaptiveLibMesh(
1032+
libMesh::MeshBase& input_mesh, double length_multiplier = 1.0);
1033+
1034+
// Overridden methods
1035+
int n_bins() const override;
1036+
1037+
void add_score(const std::string& var_name) override;
1038+
1039+
void set_score_data(const std::string& var_name, const vector<double>& values,
1040+
const vector<double>& std_dev) override;
1041+
1042+
void write(const std::string& filename) const override;
1043+
1044+
protected:
1045+
// Overridden methods
1046+
int get_bin_from_element(const libMesh::Elem* elem) const override;
1047+
1048+
const libMesh::Elem& get_element_from_bin(int bin) const override;
1049+
1050+
private:
1051+
// Data members
1052+
const libMesh::dof_id_type num_active_; //!< cached number of active elements
10251053

1026-
const bool adaptive_; //!< whether this mesh has adaptivity enabled or not
10271054
std::vector<libMesh::dof_id_type>
10281055
bin_to_elem_map_; //!< mapping bin indices to dof indices for active
10291056
//!< elements

include/openmc/particle_data.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,7 @@ class ParticleData : public GeometryState {
662662
int& event_mt() { return event_mt_; } // MT number of collision
663663
const int& event_mt() const { return event_mt_; }
664664
int& delayed_group() { return delayed_group_; } // delayed group
665+
const int& delayed_group() const { return delayed_group_; }
665666
const int& parent_nuclide() const { return parent_nuclide_; }
666667
int& parent_nuclide() { return parent_nuclide_; } // Parent nuclide
667668

openmc/deplete/transfer_rates.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ def set_external_source_rate(
366366
rate : float
367367
External source rate in units of mass per time. A positive or
368368
negative value corresponds to a feed or removal rate, respectively.
369-
units : {'g/s', 'g/min', 'g/h', 'g/d', 'g/a'}
369+
rate_units : {'g/s', 'g/min', 'g/h', 'g/d', 'g/a'}
370370
Units for values specified in the `rate` argument. 's' for seconds,
371371
'min' for minutes, 'h' for hours, 'a' for Julian years.
372372
timesteps : list of int, optional

openmc/mesh.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ def get_homogenized_materials(
287287
model: openmc.Model,
288288
n_samples: int | tuple[int, int, int] = 10_000,
289289
include_void: bool = True,
290+
material_volumes: MeshMaterialVolumes | None = None,
290291
**kwargs
291292
) -> list[openmc.Material]:
292293
"""Generate homogenized materials over each element in a mesh.
@@ -305,16 +306,23 @@ def get_homogenized_materials(
305306
the x, y, and z dimensions.
306307
include_void : bool, optional
307308
Whether homogenization should include voids.
309+
material_volumes : MeshMaterialVolumes, optional
310+
Previously computed mesh material volumes to use for homogenization.
311+
If not provided, they will be computed by calling
312+
:meth:`material_volumes`.
308313
**kwargs
309-
Keyword-arguments passed to :meth:`MeshBase.material_volumes`.
314+
Keyword-arguments passed to :meth:`material_volumes`.
310315
311316
Returns
312317
-------
313318
list of openmc.Material
314319
Homogenized material in each mesh element
315320
316321
"""
317-
vols = self.material_volumes(model, n_samples, **kwargs)
322+
if material_volumes is None:
323+
vols = self.material_volumes(model, n_samples, **kwargs)
324+
else:
325+
vols = material_volumes
318326
mat_volume_by_element = [vols.by_element(i) for i in range(vols.num_elements)]
319327

320328
# Create homogenized material for each element
@@ -424,7 +432,6 @@ def material_volumes(
424432

425433
# Restore original tallies
426434
model.tallies = original_tallies
427-
428435
return volumes
429436

430437

openmc/model/model.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22
from collections.abc import Iterable, Sequence
33
import copy
4-
from functools import lru_cache
4+
from functools import cache
55
from pathlib import Path
66
import math
77
from numbers import Integral, Real
@@ -160,7 +160,7 @@ def is_initialized(self) -> bool:
160160
return False
161161

162162
@property
163-
@lru_cache(maxsize=None)
163+
@cache
164164
def _materials_by_id(self) -> dict:
165165
"""Dictionary mapping material ID --> material"""
166166
if self.materials:
@@ -170,14 +170,14 @@ def _materials_by_id(self) -> dict:
170170
return {mat.id: mat for mat in mats}
171171

172172
@property
173-
@lru_cache(maxsize=None)
173+
@cache
174174
def _cells_by_id(self) -> dict:
175175
"""Dictionary mapping cell ID --> cell"""
176176
cells = self.geometry.get_all_cells()
177177
return {cell.id: cell for cell in cells.values()}
178178

179179
@property
180-
@lru_cache(maxsize=None)
180+
@cache
181181
def _cells_by_name(self) -> dict[int, openmc.Cell]:
182182
# Get the names maps, but since names are not unique, store a set for
183183
# each name key. In this way when the user requests a change by a name,
@@ -190,7 +190,7 @@ def _cells_by_name(self) -> dict[int, openmc.Cell]:
190190
return result
191191

192192
@property
193-
@lru_cache(maxsize=None)
193+
@cache
194194
def _materials_by_name(self) -> dict[int, openmc.Material]:
195195
if self.materials is None:
196196
mats = self.geometry.get_all_materials().values()
@@ -203,6 +203,37 @@ def _materials_by_name(self) -> dict[int, openmc.Material]:
203203
result[mat.name].add(mat)
204204
return result
205205

206+
def add_kinetics_parameters_tallies(self, num_groups: int | None = None):
207+
"""Add tallies for calculating kinetics parameters using the IFP method.
208+
209+
This method adds tallies to the model for calculating two kinetics
210+
parameters, the generation time and the effective delayed neutron
211+
fraction (beta effective). After a model is run, these parameters can be
212+
determined through the :meth:`openmc.StatePoint.ifp_results` method.
213+
214+
Parameters
215+
----------
216+
num_groups : int, optional
217+
Number of precursor groups to filter the delayed neutron fraction.
218+
If None, only the total effective delayed neutron fraction is
219+
tallied.
220+
221+
"""
222+
if not any('ifp-time-numerator' in t.scores for t in self.tallies):
223+
gen_time_tally = openmc.Tally(name='IFP time numerator')
224+
gen_time_tally.scores = ['ifp-time-numerator']
225+
self.tallies.append(gen_time_tally)
226+
if not any('ifp-beta-numerator' in t.scores for t in self.tallies):
227+
beta_tally = openmc.Tally(name='IFP beta numerator')
228+
beta_tally.scores = ['ifp-beta-numerator']
229+
if num_groups is not None:
230+
beta_tally.filters = [openmc.DelayedGroupFilter(list(range(1, num_groups + 1)))]
231+
self.tallies.append(beta_tally)
232+
if not any('ifp-denominator' in t.scores for t in self.tallies):
233+
denom_tally = openmc.Tally(name='IFP denominator')
234+
denom_tally.scores = ['ifp-denominator']
235+
self.tallies.append(denom_tally)
236+
206237
@classmethod
207238
def from_xml(
208239
cls,

openmc/settings.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1714,12 +1714,18 @@ def _create_random_ray_subelement(self, root, mesh_memo=None):
17141714
domain_elem = ET.SubElement(mesh_elem, 'domain')
17151715
domain_elem.set('id', str(domain.id))
17161716
domain_elem.set('type', domain.__class__.__name__.lower())
1717-
if mesh_memo is not None and mesh.id not in mesh_memo:
1717+
# See if a <mesh> element already exists -- if not, add it
1718+
path = f"./mesh[@id='{mesh.id}']"
1719+
if root.find(path) is None:
17181720
root.append(mesh.to_xml_element())
1719-
mesh_memo.add(mesh.id)
1721+
if mesh_memo is not None:
1722+
mesh_memo.add(mesh.id)
17201723
else:
17211724
subelement = ET.SubElement(element, key)
1722-
subelement.text = str(value)
1725+
if isinstance(value, bool):
1726+
subelement.text = str(value).lower()
1727+
else:
1728+
subelement.text = str(value)
17231729

17241730
def _create_source_rejection_fraction_subelement(self, root):
17251731
if self._source_rejection_fraction is not None:
@@ -2103,7 +2109,7 @@ def _max_tracks_from_xml_element(self, root):
21032109
if text is not None:
21042110
self.max_tracks = int(text)
21052111

2106-
def _random_ray_from_xml_element(self, root):
2112+
def _random_ray_from_xml_element(self, root, meshes=None):
21072113
elem = root.find('random_ray')
21082114
if elem is not None:
21092115
self.random_ray = {}
@@ -2130,7 +2136,11 @@ def _random_ray_from_xml_element(self, root):
21302136
elif child.tag == 'source_region_meshes':
21312137
self.random_ray['source_region_meshes'] = []
21322138
for mesh_elem in child.findall('mesh'):
2133-
mesh = MeshBase.from_xml_element(mesh_elem)
2139+
mesh_id = int(get_text(mesh_elem, 'id'))
2140+
if meshes and mesh_id in meshes:
2141+
mesh = meshes[mesh_id]
2142+
else:
2143+
mesh = MeshBase.from_xml_element(mesh_elem)
21342144
domains = []
21352145
for domain_elem in mesh_elem.findall('domain'):
21362146
domain_id = int(get_text(domain_elem, "id"))
@@ -2329,7 +2339,7 @@ def from_xml_element(cls, elem, meshes=None):
23292339
settings._max_history_splits_from_xml_element(elem)
23302340
settings._max_tracks_from_xml_element(elem)
23312341
settings._max_secondaries_from_xml_element(elem)
2332-
settings._random_ray_from_xml_element(elem)
2342+
settings._random_ray_from_xml_element(elem, meshes)
23332343
settings._use_decay_photons_from_xml_element(elem)
23342344
settings._source_rejection_fraction_from_xml_element(elem)
23352345

0 commit comments

Comments
 (0)