Skip to content

Commit a2fd6cc

Browse files
zoeprietoshimwellpshriwise
authored
Support rotation in MeshFilter (#3176)
Co-authored-by: Jonathan Shimwell <[email protected]> Co-authored-by: Patrick Shriwise <[email protected]>
1 parent a230b86 commit a2fd6cc

File tree

12 files changed

+594
-2
lines changed

12 files changed

+594
-2
lines changed

include/openmc/tallies/filter_mesh.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,21 @@ class MeshFilter : public Filter {
5151

5252
virtual bool translated() const { return translated_; }
5353

54+
virtual void set_rotation(const vector<double>& rotation);
55+
56+
virtual const vector<double>& rotation() const { return rotation_; }
57+
58+
virtual bool rotated() const { return rotated_; }
59+
5460
protected:
5561
//----------------------------------------------------------------------------
5662
// Data members
5763

5864
int32_t mesh_; //!< Index of the mesh
5965
bool translated_ {false}; //!< Whether or not the filter is translated
6066
Position translation_ {0.0, 0.0, 0.0}; //!< Filter translation
67+
bool rotated_ {false}; //!< Whether or not the filter is rotated
68+
vector<double> rotation_; //!< Filter rotation
6169
};
6270

6371
} // namespace openmc

openmc/filter.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,25 @@ class MeshFilter(Filter):
833833
translation : Iterable of float
834834
This array specifies a vector that is used to translate (shift) the mesh
835835
for this filter
836+
rotation : Iterable of float
837+
This array specifies the angles in degrees about the x, y, and z axes
838+
that the mesh should be rotated. The rotation applied is an intrinsic
839+
rotation with specified Tait-Bryan angles. That is to say, if the angles
840+
are :math:`(\phi, \theta, \psi)`, then the rotation matrix applied is
841+
:math:`R_z(\psi) R_y(\theta) R_x(\phi)` or
842+
843+
.. math::
844+
845+
\left [ \begin{array}{ccc} \cos\theta \cos\psi & -\cos\phi \sin\psi
846+
+ \sin\phi \sin\theta \cos\psi & \sin\phi \sin\psi + \cos\phi
847+
\sin\theta \cos\psi \\ \cos\theta \sin\psi & \cos\phi \cos\psi +
848+
\sin\phi \sin\theta \sin\psi & -\sin\phi \cos\psi + \cos\phi
849+
\sin\theta \sin\psi \\ -\sin\theta & \sin\phi \cos\theta & \cos\phi
850+
\cos\theta \end{array} \right ]
851+
852+
A rotation matrix can also be specified directly by setting this
853+
attribute to a nested list (or 2D numpy array) that specifies each
854+
element of the matrix.
836855
bins : list of tuple
837856
A list of mesh indices for each filter bin, e.g. [(1, 1, 1), (2, 1, 1),
838857
...]
@@ -845,6 +864,7 @@ def __init__(self, mesh, filter_id=None):
845864
self.mesh = mesh
846865
self.id = filter_id
847866
self._translation = None
867+
self._rotation = None
848868

849869
def __hash__(self):
850870
string = type(self).__name__ + '\n'
@@ -856,6 +876,7 @@ def __repr__(self):
856876
string += '{: <16}=\t{}\n'.format('\tMesh ID', self.mesh.id)
857877
string += '{: <16}=\t{}\n'.format('\tID', self.id)
858878
string += '{: <16}=\t{}\n'.format('\tTranslation', self.translation)
879+
string += '{: <16}=\t{}\n'.format('\tRotation', self.rotation)
859880
return string
860881

861882
@classmethod
@@ -879,6 +900,10 @@ def from_hdf5(cls, group, **kwargs):
879900
if translation:
880901
out.translation = translation[()]
881902

903+
rotation = group.get('rotation')
904+
if rotation:
905+
out.rotation = rotation[()]
906+
882907
return out
883908

884909
@property
@@ -911,6 +936,15 @@ def translation(self, t):
911936
cv.check_length('mesh filter translation', t, 3)
912937
self._translation = np.asarray(t)
913938

939+
@property
940+
def rotation(self):
941+
return self._rotation
942+
943+
@rotation.setter
944+
def rotation(self, rotation):
945+
cv.check_length('mesh filter rotation', rotation, 3)
946+
self._rotation = np.asarray(rotation)
947+
914948
def can_merge(self, other):
915949
# Mesh filters cannot have more than one bin
916950
return False
@@ -996,6 +1030,8 @@ def to_xml_element(self):
9961030
subelement.text = str(self.mesh.id)
9971031
if self.translation is not None:
9981032
element.set('translation', ' '.join(map(str, self.translation)))
1033+
if self.rotation is not None:
1034+
element.set('rotation', ' '.join(map(str, self.rotation.ravel())))
9991035
return element
10001036

10011037
@classmethod
@@ -1008,6 +1044,13 @@ def from_xml_element(cls, elem: ET.Element, **kwargs) -> MeshFilter:
10081044
translation = get_elem_list(elem, "translation", float) or []
10091045
if translation:
10101046
out.translation = translation
1047+
1048+
rotation = get_elem_list(elem, 'rotation', float) or []
1049+
if rotation:
1050+
if len(rotation) == 3:
1051+
out.rotation = rotation
1052+
elif len(rotation) == 9:
1053+
out.rotation = np.array(rotation).reshape(3, 3)
10111054
return out
10121055

10131056

openmc/lib/filter.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@
9797
_dll.openmc_mesh_filter_set_translation.argtypes = [c_int32, POINTER(c_double*3)]
9898
_dll.openmc_mesh_filter_set_translation.restype = c_int
9999
_dll.openmc_mesh_filter_set_translation.errcheck = _error_handler
100+
_dll.openmc_mesh_filter_get_rotation.argtypes = [c_int32, POINTER(c_double),
101+
POINTER(c_size_t)]
102+
_dll.openmc_mesh_filter_get_rotation.restype = c_int
103+
_dll.openmc_mesh_filter_get_rotation.errcheck = _error_handler
104+
_dll.openmc_mesh_filter_set_rotation.argtypes = [
105+
c_int32, POINTER(c_double), c_size_t]
106+
_dll.openmc_mesh_filter_set_rotation.restype = c_int
107+
_dll.openmc_mesh_filter_set_rotation.errcheck = _error_handler
100108
_dll.openmc_meshborn_filter_get_mesh.argtypes = [c_int32, POINTER(c_int32)]
101109
_dll.openmc_meshborn_filter_get_mesh.restype = c_int
102110
_dll.openmc_meshborn_filter_get_mesh.errcheck = _error_handler
@@ -393,6 +401,10 @@ class MeshFilter(Filter):
393401
Mesh used for the filter
394402
translation : Iterable of float
395403
3-D coordinates of the translation vector
404+
rotation : Iterable of float
405+
The rotation matrix or angles of the filter mesh. This can either be
406+
a fully specified 3 x 3 rotation matrix or an Iterable of length 3
407+
with the angles in degrees about the x, y, and z axes, respectively.
396408
397409
"""
398410
filter_type = 'mesh'
@@ -422,6 +434,34 @@ def translation(self):
422434
def translation(self, translation):
423435
_dll.openmc_mesh_filter_set_translation(self._index, (c_double*3)(*translation))
424436

437+
@property
438+
def rotation(self):
439+
rotation_data = np.zeros(12)
440+
rot_size = c_size_t()
441+
442+
_dll.openmc_mesh_filter_get_rotation(
443+
self._index, rotation_data.ctypes.data_as(POINTER(c_double)),
444+
rot_size)
445+
rot_size = rot_size.value
446+
447+
if rot_size == 9:
448+
return rotation_data[:rot_size].shape(3, 3)
449+
elif rot_size in (0, 12):
450+
# If size is 0, rotation_data[9:] will be zeros. This indicates no
451+
# rotation and is the most straightforward way to always return
452+
# an iterable of floats
453+
return rotation_data[9:]
454+
else:
455+
raise ValueError(
456+
f'Invalid size of rotation matrix: {rot_size}')
457+
458+
@rotation.setter
459+
def rotation(self, rotation_data):
460+
flat_rotation = np.asarray(rotation_data, dtype=float).flatten()
461+
462+
_dll.openmc_mesh_filter_set_rotation(
463+
self._index, flat_rotation.ctypes.data_as(POINTER(c_double)),
464+
c_size_t(len(flat_rotation)))
425465

426466
class MeshBornFilter(Filter):
427467
"""MeshBorn filter stored internally.

src/cell.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ void Cell::set_rotation(const vector<double>& rot)
5757
fatal_error(fmt::format("Non-3D rotation vector applied to cell {}", id_));
5858
}
5959

60-
// Compute and store the rotation matrix.
60+
// Compute and store the inverse rotation matrix for the angles given.
6161
rotation_.clear();
6262
rotation_.reserve(rot.size() == 9 ? 9 : 12);
6363
if (rot.size() == 3) {

src/tallies/filter_mesh.cpp

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "openmc/constants.h"
77
#include "openmc/error.h"
88
#include "openmc/mesh.h"
9+
#include "openmc/position.h"
910
#include "openmc/xml_interface.h"
1011

1112
namespace openmc {
@@ -30,6 +31,10 @@ void MeshFilter::from_xml(pugi::xml_node node)
3031
if (check_for_node(node, "translation")) {
3132
set_translation(get_node_array<double>(node, "translation"));
3233
}
34+
// Read the rotation transform.
35+
if (check_for_node(node, "rotation")) {
36+
set_rotation(get_node_array<double>(node, "rotation"));
37+
}
3338
}
3439

3540
void MeshFilter::get_all_bins(
@@ -45,6 +50,12 @@ void MeshFilter::get_all_bins(
4550
last_r -= translation();
4651
r -= translation();
4752
}
53+
// apply rotation if present
54+
if (!rotation_.empty()) {
55+
last_r = last_r.rotate(rotation_);
56+
r = r.rotate(rotation_);
57+
u = u.rotate(rotation_);
58+
}
4859

4960
if (estimator != TallyEstimator::TRACKLENGTH) {
5061
auto bin = model::meshes[mesh_]->get_bin(r);
@@ -65,6 +76,9 @@ void MeshFilter::to_statepoint(hid_t filter_group) const
6576
if (translated_) {
6677
write_dataset(filter_group, "translation", translation_);
6778
}
79+
if (rotated_) {
80+
write_dataset(filter_group, "rotation", rotation_);
81+
}
6882
}
6983

7084
std::string MeshFilter::text_label(int bin) const
@@ -93,6 +107,40 @@ void MeshFilter::set_translation(const double translation[3])
93107
this->set_translation({translation[0], translation[1], translation[2]});
94108
}
95109

110+
void MeshFilter::set_rotation(const vector<double>& rot)
111+
{
112+
rotated_ = true;
113+
114+
// Compute and store the inverse rotation matrix for the angles given.
115+
rotation_.clear();
116+
rotation_.reserve(rot.size() == 9 ? 9 : 12);
117+
if (rot.size() == 3) {
118+
double phi = -rot[0] * PI / 180.0;
119+
double theta = -rot[1] * PI / 180.0;
120+
double psi = -rot[2] * PI / 180.0;
121+
rotation_.push_back(std::cos(theta) * std::cos(psi));
122+
rotation_.push_back(-std::cos(phi) * std::sin(psi) +
123+
std::sin(phi) * std::sin(theta) * std::cos(psi));
124+
rotation_.push_back(std::sin(phi) * std::sin(psi) +
125+
std::cos(phi) * std::sin(theta) * std::cos(psi));
126+
rotation_.push_back(std::cos(theta) * std::sin(psi));
127+
rotation_.push_back(std::cos(phi) * std::cos(psi) +
128+
std::sin(phi) * std::sin(theta) * std::sin(psi));
129+
rotation_.push_back(-std::sin(phi) * std::cos(psi) +
130+
std::cos(phi) * std::sin(theta) * std::sin(psi));
131+
rotation_.push_back(-std::sin(theta));
132+
rotation_.push_back(std::sin(phi) * std::cos(theta));
133+
rotation_.push_back(std::cos(phi) * std::cos(theta));
134+
135+
// When user specifies angles, write them at end of vector
136+
rotation_.push_back(rot[0]);
137+
rotation_.push_back(rot[1]);
138+
rotation_.push_back(rot[2]);
139+
} else {
140+
std::copy(rot.begin(), rot.end(), std::back_inserter(rotation_));
141+
}
142+
}
143+
96144
//==============================================================================
97145
// C-API functions
98146
//==============================================================================
@@ -201,4 +249,48 @@ extern "C" int openmc_mesh_filter_set_translation(
201249
return 0;
202250
}
203251

252+
//! Return the rotation matrix of a mesh filter
253+
extern "C" int openmc_mesh_filter_get_rotation(
254+
int32_t index, double rot[], size_t* n)
255+
{
256+
// Make sure this is a valid index to an allocated filter
257+
if (int err = verify_filter(index))
258+
return err;
259+
260+
// Check the filter type
261+
const auto& filter = model::tally_filters[index];
262+
if (filter->type() != FilterType::MESH) {
263+
set_errmsg("Tried to get a rotation from a non-mesh filter.");
264+
return OPENMC_E_INVALID_TYPE;
265+
}
266+
// Get rotation from the mesh filter and set value
267+
auto mesh_filter = dynamic_cast<MeshFilter*>(filter.get());
268+
*n = mesh_filter->rotation().size();
269+
std::memcpy(rot, mesh_filter->rotation().data(),
270+
*n * sizeof(mesh_filter->rotation()[0]));
271+
return 0;
272+
}
273+
274+
//! Set the flattened rotation matrix of a mesh filter
275+
extern "C" int openmc_mesh_filter_set_rotation(
276+
int32_t index, const double rot[], size_t rot_len)
277+
{
278+
// Make sure this is a valid index to an allocated filter
279+
if (int err = verify_filter(index))
280+
return err;
281+
282+
const auto& filter = model::tally_filters[index];
283+
// Check the filter type
284+
if (filter->type() != FilterType::MESH) {
285+
set_errmsg("Tried to set a rotation from a non-mesh filter.");
286+
return OPENMC_E_INVALID_TYPE;
287+
}
288+
289+
// Get a pointer to the filter and downcast
290+
auto mesh_filter = dynamic_cast<MeshFilter*>(filter.get());
291+
std::vector<double> vec_rot(rot, rot + rot_len);
292+
mesh_filter->set_rotation(vec_rot);
293+
return 0;
294+
}
295+
204296
} // namespace openmc

src/weight_windows.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -933,7 +933,8 @@ void WeightWindowsGenerator::create_tally()
933933
for (const auto& f : model::tally_filters) {
934934
if (f->type() == FilterType::MESH) {
935935
const auto* mesh_filter = dynamic_cast<MeshFilter*>(f.get());
936-
if (mesh_filter->mesh() == mesh_idx && !mesh_filter->translated()) {
936+
if (mesh_filter->mesh() == mesh_idx && !mesh_filter->translated() &&
937+
!mesh_filter->rotated()) {
937938
ww_tally->add_filter(f.get());
938939
found_mesh_filter = true;
939940
break;

tests/regression_tests/filter_rotations/__init__.py

Whitespace-only changes.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?xml version='1.0' encoding='utf-8'?>
2+
<model>
3+
<materials>
4+
<material id="1" depletable="true">
5+
<density value="10.0" units="g/cm3"/>
6+
<nuclide name="U235" ao="1.0"/>
7+
</material>
8+
<material id="2">
9+
<density value="1.0" units="g/cm3"/>
10+
<nuclide name="Zr90" ao="1.0"/>
11+
</material>
12+
</materials>
13+
<geometry>
14+
<cell id="1" material="1" region="1 -2 3 -4 10 -9" universe="1"/>
15+
<cell id="2" material="2" region="(-1 | 2 | -3 | 4) (5 -6 7 -8) 10 -9" universe="1"/>
16+
<surface id="1" name="minimum x" type="x-plane" coeffs="-5.0"/>
17+
<surface id="2" name="maximum x" type="x-plane" coeffs="5.0"/>
18+
<surface id="3" name="minimum y" type="y-plane" coeffs="-5.0"/>
19+
<surface id="4" name="maximum y" type="y-plane" coeffs="5.0"/>
20+
<surface id="5" name="minimum x" type="x-plane" boundary="reflective" coeffs="-10.0"/>
21+
<surface id="6" name="maximum x" type="x-plane" boundary="reflective" coeffs="10.0"/>
22+
<surface id="7" name="minimum y" type="y-plane" boundary="reflective" coeffs="-10.0"/>
23+
<surface id="8" name="maximum y" type="y-plane" boundary="reflective" coeffs="10.0"/>
24+
<surface id="9" type="z-plane" boundary="vacuum" coeffs="10.0"/>
25+
<surface id="10" type="z-plane" boundary="vacuum" coeffs="-10.0"/>
26+
</geometry>
27+
<settings>
28+
<run_mode>eigenvalue</run_mode>
29+
<particles>1000</particles>
30+
<batches>5</batches>
31+
<inactive>0</inactive>
32+
</settings>
33+
<tallies>
34+
<mesh id="1">
35+
<dimension>3 4 5</dimension>
36+
<lower_left>-9 -9 -9</lower_left>
37+
<upper_right>9 9 9</upper_right>
38+
</mesh>
39+
<mesh id="2">
40+
<dimension>3 4 5</dimension>
41+
<lower_left>-9 -9 -9</lower_left>
42+
<upper_right>9 9 9</upper_right>
43+
</mesh>
44+
<filter id="1" type="mesh">
45+
<bins>1</bins>
46+
</filter>
47+
<filter id="2" type="mesh" rotation="0 0 10">
48+
<bins>2</bins>
49+
</filter>
50+
<tally id="1">
51+
<filters>1</filters>
52+
<scores>total</scores>
53+
</tally>
54+
<tally id="2">
55+
<filters>2</filters>
56+
<scores>total</scores>
57+
</tally>
58+
</tallies>
59+
</model>

0 commit comments

Comments
 (0)