Skip to content

Commit fd964bc

Browse files
GuyStenshimwell
andauthored
Enable specifying reference direction for azimuthal angle in PolarAzimuthal distribution (#3582)
Co-authored-by: shimwell <[email protected]>
1 parent 5fc289b commit fd964bc

File tree

6 files changed

+72
-4
lines changed

6 files changed

+72
-4
lines changed

include/openmc/distribution_multi.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ class PolarAzimuthal : public UnitSphereDistribution {
5151
Distribution* phi() const { return phi_.get(); }
5252

5353
private:
54+
Direction v_ref_ {1.0, 0.0, 0.0}; //!< reference direction
55+
Direction w_ref_;
5456
UPtrDist mu_; //!< Distribution of polar angle
5557
UPtrDist phi_; //!< Distribution of azimuthal angle
5658
};

openmc/stats/multivariate.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ class PolarAzimuthal(UnitSphere):
7979
reference_uvw : Iterable of float
8080
Direction from which polar angle is measured. Defaults to the positive
8181
z-direction.
82+
reference_vwu : Iterable of float
83+
Direction from which azimuthal angle is measured. Defaults to the positive
84+
x-direction.
8285
8386
Attributes
8487
----------
@@ -89,8 +92,9 @@ class PolarAzimuthal(UnitSphere):
8992
9093
"""
9194

92-
def __init__(self, mu=None, phi=None, reference_uvw=(0., 0., 1.)):
95+
def __init__(self, mu=None, phi=None, reference_uvw=(0., 0., 1.), reference_vwu=(1., 0., 0.)):
9396
super().__init__(reference_uvw)
97+
self.reference_vwu = reference_vwu
9498
if mu is not None:
9599
self.mu = mu
96100
else:
@@ -100,6 +104,20 @@ def __init__(self, mu=None, phi=None, reference_uvw=(0., 0., 1.)):
100104
self.phi = phi
101105
else:
102106
self.phi = Uniform(0., 2*pi)
107+
108+
@property
109+
def reference_vwu(self):
110+
return self._reference_vwu
111+
112+
@reference_vwu.setter
113+
def reference_vwu(self, vwu):
114+
cv.check_type('reference v direction', vwu, Iterable, Real)
115+
vwu = np.asarray(vwu)
116+
uvw = self.reference_uvw
117+
cv.check_greater_than('reference v direction must not be parallel to reference u direction', np.linalg.norm(np.cross(vwu,uvw)), 1e-6*np.linalg.norm(vwu))
118+
vwu -= vwu.dot(uvw)*uvw
119+
cv.check_less_than('reference v direction must be orthogonal to reference u direction', np.abs(vwu.dot(uvw)), 1e-6)
120+
self._reference_vwu = vwu/np.linalg.norm(vwu)
103121

104122
@property
105123
def mu(self):
@@ -132,6 +150,8 @@ def to_xml_element(self):
132150
element.set("type", "mu-phi")
133151
if self.reference_uvw is not None:
134152
element.set("reference_uvw", ' '.join(map(str, self.reference_uvw)))
153+
if self.reference_vwu is not None:
154+
element.set("reference_vwu", ' '.join(map(str, self.reference_vwu)))
135155
element.append(self.mu.to_xml_element('mu'))
136156
element.append(self.phi.to_xml_element('phi'))
137157
return element
@@ -155,6 +175,9 @@ def from_xml_element(cls, elem):
155175
uvw = get_elem_list(elem, "reference_uvw", float)
156176
if uvw is not None:
157177
mu_phi.reference_uvw = uvw
178+
vwu = get_elem_list(elem, "reference_vwu", float)
179+
if vwu is not None:
180+
mu_phi.reference_vwu = vwu
158181
mu_phi.mu = Univariate.from_xml_element(elem.find('mu'))
159182
mu_phi.phi = Univariate.from_xml_element(elem.find('phi'))
160183
return mu_phi

src/distribution_multi.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@ PolarAzimuthal::PolarAzimuthal(Direction u, UPtrDist mu, UPtrDist phi)
5858
PolarAzimuthal::PolarAzimuthal(pugi::xml_node node)
5959
: UnitSphereDistribution {node}
6060
{
61+
// Read reference directional unit vector
62+
if (check_for_node(node, "reference_vwu")) {
63+
auto v_ref = get_node_array<double>(node, "reference_vwu");
64+
if (v_ref.size() != 3)
65+
fatal_error("Angular distribution reference v direction must have "
66+
"three parameters specified.");
67+
v_ref_ = Direction(v_ref.data());
68+
}
69+
w_ref_ = u_ref_.cross(v_ref_);
6170
if (check_for_node(node, "mu")) {
6271
pugi::xml_node node_dist = node.child("mu");
6372
mu_ = distribution_from_xml(node_dist);
@@ -79,11 +88,15 @@ Direction PolarAzimuthal::sample(uint64_t* seed) const
7988
double mu = mu_->sample(seed);
8089
if (mu == 1.0)
8190
return u_ref_;
91+
if (mu == -1.0)
92+
return -u_ref_;
8293

8394
// Sample azimuthal angle
8495
double phi = phi_->sample(seed);
8596

86-
return rotate_angle(u_ref_, mu, &phi, seed);
97+
double f = std::sqrt(1 - mu * mu);
98+
99+
return mu * u_ref_ + f * std::cos(phi) * v_ref_ + f * std::sin(phi) * w_ref_;
87100
}
88101

89102
//==============================================================================

tests/regression_tests/source/inputs_true.dat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
<parameters>-2.0 0.0 2.0 0.2 0.3 0.2</parameters>
2626
</z>
2727
</space>
28-
<angle type="mu-phi" reference_uvw="0.0 0.0 1.0">
28+
<angle type="mu-phi" reference_uvw="0.0 0.0 1.0" reference_vwu="1.0 0.0 0.0">
2929
<mu type="discrete">
3030
<parameters>-1.0 0.0 1.0 0.5 0.25 0.25</parameters>
3131
</mu>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
k-combined:
2-
3.034717E-01 2.799386E-03
2+
3.080655E-01 4.837707E-03

tests/unit_tests/test_stats.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,3 +516,33 @@ def test_combine_distributions():
516516
# uncertainty of the expected value
517517
samples = combined.sample(10_000)
518518
assert_sample_mean(samples, 0.25)
519+
520+
def test_reference_vwu_projection():
521+
"""When a non-orthogonal vector is provided, the setter should project out
522+
any component along reference_uvw so the stored vector is orthogonal.
523+
"""
524+
pa = openmc.stats.PolarAzimuthal() # default reference_uvw == (0, 0, 1)
525+
526+
# Provide a vector that is not orthogonal to (0,0,1)
527+
pa.reference_vwu = (2.0, 0.5, 0.3)
528+
529+
reference_v = np.asarray(pa.reference_vwu)
530+
reference_u = np.asarray(pa.reference_uvw)
531+
532+
# reference_v should be orthogonal to reference_u
533+
assert abs(np.dot(reference_v, reference_u)) < 1e-6
534+
535+
536+
def test_reference_vwu_normalization():
537+
"""When a non-normalized vector is provided, the setter should normalize
538+
the projected vector to unit length.
539+
"""
540+
pa = openmc.stats.PolarAzimuthal() # default reference_uvw == (0, 0, 1)
541+
542+
# Provide a vector that is neither orthogonal to (0,0,1) nor unit-length
543+
pa.reference_vwu = (2.0, 0.5, 0.3)
544+
545+
reference_v = np.asarray(pa.reference_vwu)
546+
547+
# reference_v should be unit length
548+
assert np.isclose(np.linalg.norm(reference_v), 1.0, atol=1e-12)

0 commit comments

Comments
 (0)