Skip to content

Commit 9f6d536

Browse files
NybergWISCpshriwise
authored andcommitted
Commit including most of the sampling work in distribution_spatial.cpp and testing
1 parent 80077ed commit 9f6d536

18 files changed

Lines changed: 4669 additions & 39 deletions

‎include/openmc/distribution_spatial.h‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,13 @@ class MeshIndependent : public SpatialDistribution {
110110

111111
private:
112112
UnstructuredMesh* umesh_ptr_;
113-
Position sampled_;
113+
int32_t mesh_map_idx_;
114+
std::string sample_scheme_;
115+
double total_weight_;
116+
std::vector<double> mesh_CDF_;
117+
std::vector<double> mesh_weights_;
118+
int64_t tot_bins_;
119+
114120
};
115121

116122
//==============================================================================

‎include/openmc/mesh.h‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ class Mesh {
7979
//
8080
//! \param[in] seed Seed to use for random sampling
8181
//! \param[out] r Position within tet
82-
virtual Position sample(uint64_t* seed) const=0;
82+
virtual Position sample(uint64_t* seed, int32_t tet_bin) const=0;
8383

8484
//! Determine which bins were crossed by a particle
8585
//
@@ -166,7 +166,7 @@ class StructuredMesh : public Mesh {
166166
}
167167
};
168168

169-
Position sample(uint64_t* seed) const override;
169+
Position sample(uint64_t* seed, int32_t tet_bin) const override;
170170

171171
int get_bin(Position r) const override;
172172

@@ -565,7 +565,7 @@ class MOABMesh : public UnstructuredMesh {
565565

566566
// Overridden Methods
567567

568-
Position sample(uint64_t* seed) const override;
568+
Position sample(uint64_t* seed, int32_t tet_bin) const override;
569569

570570
void bins_crossed(Position r0, Position r1, const Direction& u,
571571
vector<int>& bins, vector<double>& lengths) const override;
@@ -729,7 +729,7 @@ class LibMesh : public UnstructuredMesh {
729729
void bins_crossed(Position r0, Position r1, const Direction& u,
730730
vector<int>& bins, vector<double>& lengths) const override;
731731

732-
Position sample(uint64_t* seed) const override;
732+
Position sample(uint64_t* seed, int32_t tet_bin) const override;
733733

734734
int get_bin(Position r) const override;
735735

‎openmc/settings.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111
from xml.etree import ElementTree as ET
1212

1313
import openmc.checkvalue as cv
14+
from openmc.stats.multivariate import MeshIndependent
1415

1516
from . import RegularMesh, Source, VolumeCalculation, WeightWindows
1617
from ._xml import clean_indentation, get_text, reorder_attributes
1718
from openmc.checkvalue import PathLike
19+
from .mesh import MeshBase
1820

1921

2022
class RunMode(Enum):
@@ -975,6 +977,8 @@ def _create_max_order_subelement(self, root):
975977
def _create_source_subelement(self, root):
976978
for source in self.source:
977979
root.append(source.to_xml_element())
980+
if isinstance(source.space, MeshIndependent):
981+
root.append(source.space.mesh.to_xml_element())
978982

979983
def _create_volume_calcs_subelement(self, root):
980984
for calc in self.volume_calculations:
@@ -1323,6 +1327,12 @@ def _keff_trigger_from_xml_element(self, root):
13231327
def _source_from_xml_element(self, root):
13241328
for elem in root.findall('source'):
13251329
self.source.append(Source.from_xml_element(elem))
1330+
if isinstance(Source.from_xml_element(elem), MeshIndependent):
1331+
mesh_id = int(get_text(elem, 'mesh'))
1332+
path = f"./mesh[@id='{mesh_id}']"
1333+
mesh_elem = root.find(path)
1334+
if mesh_elem is not None:
1335+
self.source.space.mesh = MeshBase.from_xml_element(mesh_elem)
13261336

13271337
def _volume_calcs_from_xml_element(self, root):
13281338
volume_elems = root.findall("volume_calc")

‎openmc/source.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from openmc.stats.multivariate import UnitSphere, Spatial
1717
from openmc.stats.univariate import Univariate
1818
from ._xml import get_text
19+
from .mesh import MeshBase
1920

2021

2122
class Source:

‎openmc/stats/multivariate.py‎

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
from math import pi, cos
44
from numbers import Real
55
from xml.etree import ElementTree as ET
6+
from xmlrpc.client import boolean
67

78
import numpy as np
89

910
import openmc.checkvalue as cv
1011
from .._xml import get_text
1112
from .univariate import Univariate, Uniform, PowerLaw
13+
from ..mesh import MeshBase
1214

1315

1416
class UnitSphere(ABC):
@@ -269,6 +271,8 @@ def from_xml_element(cls, elem):
269271
return CylindricalIndependent.from_xml_element(elem)
270272
elif distribution == 'spherical':
271273
return SphericalIndependent.from_xml_element(elem)
274+
elif distribution == 'mesh':
275+
return MeshIndependent.from_xml_element(elem)
272276
elif distribution == 'box' or distribution == 'fission':
273277
return Box.from_xml_element(elem)
274278
elif distribution == 'point':
@@ -616,6 +620,120 @@ def from_xml_element(cls, elem):
616620
origin = [float(x) for x in elem.get('origin').split()]
617621
return cls(r, phi, z, origin=origin)
618622

623+
class MeshIndependent(Spatial):
624+
"""Spatial distribution for a mesh.
625+
626+
This distribution allows one to specify a mesh to sample over and the scheme to sample over the entire mesh.
627+
628+
.. versionadded:: 0.13
629+
630+
Parameters
631+
----------
632+
elem_weight_scheme : str, optional
633+
The scheme for weighting and sampling elements from the mesh. Options are 'file' and 'volume' based weights.
634+
mesh : openmc.MeshBase
635+
The mesh instance used for sampling, mesh is written into settings.xml, mesh.id is written into the source distribution
636+
weights_from_file : Iterable of Real, optional
637+
A list of values which represent the weights of each element
638+
639+
640+
Attributes
641+
----------
642+
elem_weight_scheme : str, optional
643+
Weighting scheme for sampling element from mesh, defaults to volume
644+
mesh : openmc.MeshBase
645+
The mesh instance used for sampling, mesh is written into settings.xml, mesh.id is written into the source distribution
646+
weights_from_file : Iterable of Real, optional
647+
A list of values which represent the weights of each element
648+
649+
"""
650+
651+
def __init__(self, mesh, weights_from_file=None, volume_weighting=False):
652+
self.mesh = mesh
653+
self.weights_from_file = weights_from_file
654+
self.volume_weighting = volume_weighting
655+
656+
@property
657+
def mesh(self):
658+
return self._mesh
659+
660+
@mesh.setter
661+
def mesh(self, mesh):
662+
if mesh != None:
663+
cv.check_type('Unstructured Mesh', mesh, MeshBase)
664+
self._mesh = mesh
665+
666+
@property
667+
def volume_weighting(self):
668+
return self._volume_weighting
669+
670+
@volume_weighting.setter
671+
def volume_weighting(self, volume_weighting):
672+
cv.check_type('Scheme for sampling an element from the mesh', volume_weighting, bool)
673+
self._volume_weighting = volume_weighting
674+
675+
@property
676+
def weights_from_file(self):
677+
return self._weights_from_file
678+
679+
@weights_from_file.setter
680+
def weights_from_file(self, given_weights):
681+
if given_weights is not None:
682+
self._weights_from_file = (np.array(given_weights, dtype=float)).flatten()
683+
else:
684+
self._weights_from_file = None
685+
686+
@property
687+
def num_weight_bins(self):
688+
if self.weights_from_file == None:
689+
raise ValueError('Weight bins are not set')
690+
return self.weights_from_file.size
691+
692+
def to_xml_element(self):
693+
"""Return XML representation of the spatial distribution
694+
695+
Returns
696+
-------
697+
element : xml.etree.ElementTree.Element
698+
XML element containing spatial distribution data
699+
700+
"""
701+
element = ET.Element('space')
702+
element.set('type', 'mesh')
703+
element.set("mesh_id", str(self.mesh.id))
704+
element.set("volume_weighting", str(self.volume_weighting))
705+
706+
if self.weights_from_file is not None:
707+
subelement = ET.SubElement(element, 'weights_from_file')
708+
subelement.text = ' '.join(str(e) for e in self.weights_from_file)
709+
710+
return element
711+
712+
@classmethod
713+
def from_xml_element(cls, elem):
714+
"""Generate spatial distribution from an XML element
715+
716+
Parameters
717+
----------
718+
elem : xml.etree.ElementTree.Element
719+
XML element
720+
721+
Returns
722+
-------
723+
openmc.stats.MeshIndependent
724+
Spatial distribution generated from XML element
725+
726+
"""
727+
728+
mesh_id = int(elem.get('mesh_id'))
729+
volume_weighting = elem.get("volume_weighting")
730+
volume_weighting = get_text(elem, 'volume_weighting').lower() == 'true'
731+
if elem.get('weights_from_file') is not None:
732+
weights_from_file = [float(b) for b in get_text(elem, 'weights_from_file').split()]
733+
else:
734+
weights_from_file = None
735+
return cls(volume_weighting, mesh_id, weights_from_file)
736+
619737

620738
class Box(Spatial):
621739
"""Uniform distribution of coordinates in a rectangular cuboid.

‎src/distribution_spatial.cpp‎

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
#include "openmc/error.h"
44
#include "openmc/random_lcg.h"
55
#include "openmc/xml_interface.h"
6+
#include "openmc/mesh.h"
7+
#include "openmc/search.h"
8+
9+
#include <iostream>
10+
#include <fstream>
611

712
namespace openmc {
813

@@ -184,24 +189,71 @@ Position SphericalIndependent::sample(uint64_t* seed) const
184189
// MeshIndependent implementation
185190
//==============================================================================
186191

187-
// Loosely adapted Patrick's code shown here
188192
MeshIndependent::MeshIndependent(pugi::xml_node node)
189193
{
190194
// No in-tet distributions implemented, could include distributions for the barycentric coords
195+
// Read in unstructured mesh from mesh_id value
196+
int32_t mesh_id = std::stoi(get_node_value(node, "mesh_id"));
197+
// Get pointer to spatial distribution
198+
mesh_map_idx_ = model::mesh_map.at(mesh_id);
199+
const auto& mesh_ptr = model::meshes[mesh_map_idx_];
191200

192-
int32_t mesh_id = std::stoi(get_node_value(node, "mesh"));
193-
const auto& mesh_ptr = model::meshes[mesh_id];
194-
195-
// line from Patrick code, unsure of use
196-
// int bin = mesh_ptr->get_bin();
197-
const UnstructuredMesh* umesh_ptr_ = dynamic_cast<UnstructuredMesh*>(mesh_ptr.get());
201+
// Check whether mesh pointer points to an unstructured mesh
202+
umesh_ptr_ = dynamic_cast<UnstructuredMesh*>(mesh_ptr.get());
198203
if (!umesh_ptr_) {fatal_error("Mesh passed to spatial distribution is not an unstructured mesh object"); }
204+
205+
// Create CDF based on weighting scheme
206+
tot_bins_ = umesh_ptr_->n_bins();
207+
std::vector<double> weights = {};
208+
weights.resize(tot_bins_);
209+
double temp_total_weight = 0.0;
210+
mesh_CDF_.resize(tot_bins_+1);
211+
mesh_CDF_[0] = {0.0};
212+
total_weight_ = 0.0;
213+
mesh_weights_.resize(tot_bins_);
214+
215+
// Create cdfs for sampling for an element over a mesh
216+
// Volume scheme is weighted by the volume of each tet
217+
// File scheme is weighted by an array given in the xml file
218+
219+
if (check_for_node(node, "weights_from_file")) {
220+
mesh_weights_ = get_node_array<double>(node, "weights_from_file");
221+
if (mesh_weights_.size() != tot_bins_){
222+
write_message("WARNING: The size of the weights array from the xml file does not equal the number of elements in the mesh.");
223+
} if (get_node_value_bool(node, "volume_weighting")){
224+
for (int i = 0; i < tot_bins_; i++){
225+
weights[i] = mesh_weights_[i]*umesh_ptr_->volume(i);
226+
}
227+
} else if (!get_node_value_bool(node, "volume_weighting")){
228+
for (int i = 0; i < tot_bins_; i++){
229+
weights[i] = mesh_weights_[i];
230+
}
231+
}
232+
} else if (get_node_value_bool(node, "volume_weighting")){
233+
for (int i = 0; i<tot_bins_; i++){
234+
weights[i] = umesh_ptr_->volume(i);
235+
}
236+
} else {
237+
for (int i = 0; i<tot_bins_; i++){
238+
weights[i] = 1;
239+
}
240+
}
241+
for (int i = 0; i<tot_bins_; i++){
242+
temp_total_weight = temp_total_weight + weights[i];
243+
}
244+
total_weight_ = temp_total_weight;
245+
for (int i = 0; i<tot_bins_; i++){
246+
mesh_CDF_[i+1] = mesh_CDF_[i] + weights[i]/total_weight_;
247+
}
199248
}
200249

201250
Position MeshIndependent::sample(uint64_t* seed) const
202251
{
203-
Position sampled_ = umesh_ptr_->sample(seed);
204-
return sampled_;
252+
// Create random variable for sampling element from mesh
253+
float eta = prn(seed);
254+
// Sample over the CDF defined in initialization above
255+
int32_t tet_bin = lower_bound_index(mesh_CDF_.begin(), mesh_CDF_.end(), eta);
256+
return umesh_ptr_->sample(seed, tet_bin);
205257
}
206258

207259

‎src/initialize.cpp‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,6 @@ void read_separate_xml_files()
405405

406406
// Finalize cross sections having assigned temperatures
407407
finalize_cross_sections();
408-
409408
read_tallies_xml();
410409

411410
// Initialize distribcell_filters

‎src/mesh.cpp‎

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ StructuredMesh::MeshIndex StructuredMesh::get_indices_from_bin(int bin) const
358358
return ijk;
359359
}
360360

361-
Position StructuredMesh::sample(uint64_t* seed) const {
361+
Position StructuredMesh::sample(uint64_t* seed, int32_t tet_bin) const {
362362
fatal_error("Position sampling on structured meshes is not yet implemented");
363363
}
364364

@@ -2085,10 +2085,7 @@ std::string MOABMesh::library() const
20852085
}
20862086

20872087
// Sample position within a tet for MOAB type tets
2088-
Position MOABMesh::sample(uint64_t* seed) const {
2089-
// Get bin # assuming equal weights, IMP weigh by activity
2090-
// This may underweigh first and last bins
2091-
int64_t tet_bin = trunc(n_bins()*prn(seed));
2088+
Position MOABMesh::sample(uint64_t* seed, int32_t tet_bin) const {
20922089

20932090
moab::EntityHandle tet_ent = get_ent_handle_from_bin(tet_bin);
20942091

@@ -2568,18 +2565,13 @@ void LibMesh::initialize()
25682565
}
25692566

25702567
// Sample position within a tet for LibMesh type tets
2571-
Position LibMesh::sample(uint64_t* seed) const {
2572-
// Get bin # assuming equal weights, IMP weigh by activity
2573-
// This may underweight first and last bin by 1/2 TODO
2574-
int64_t tet_xi = trunc(n_bins()*prn(seed));
2575-
2576-
const auto& elem = get_element_from_bin(tet_xi);
2577-
2568+
Position LibMesh::sample(uint64_t* seed, int32_t tet_bin) const {
2569+
const auto& elem = get_element_from_bin(tet_bin);
25782570
// Get tet vertex coordinates from LibMesh
25792571
std::array<Position, 4> tet_verts;
25802572
for (int i = 0; i < elem.n_nodes(); i++) {
25812573
auto node_ref = elem.node_ref(i);
2582-
tet_verts[i] = {node_ref(0), node_ref(1), node_ref(2)};
2574+
tet_verts[i] = {node_ref(0), node_ref(1), node_ref(2)};
25832575
}
25842576
// Samples position within tet using Barycentric coordinates
25852577
Position r = this->sample_tet(tet_verts, seed);

0 commit comments

Comments
 (0)