mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-27 13:45:36 -04:00
implemented python interface and tests of math.F90
This commit is contained in:
parent
600969ce2b
commit
7fb5449ca1
5 changed files with 200 additions and 10 deletions
|
|
@ -1,4 +1,4 @@
|
|||
from ctypes import (c_int, c_double, POINTER)
|
||||
from ctypes import (c_int, c_double, POINTER, c_void_p)
|
||||
|
||||
import numpy as np
|
||||
from numpy.ctypeslib import ndpointer
|
||||
|
|
@ -18,7 +18,8 @@ _dll.calc_zn.restype = None
|
|||
_dll.calc_zn.argtypes = [POINTER(c_int), POINTER(c_double), POINTER(c_double),
|
||||
ndpointer(c_double)]
|
||||
_dll.evaluate_legendre.restype = c_double
|
||||
_dll.evaluate_legendre.argtypes = [ndpointer(c_double), POINTER(c_double)]
|
||||
_dll.evaluate_legendre.argtypes = [POINTER(c_int), POINTER(c_double),
|
||||
POINTER(c_double)]
|
||||
_dll.rotate_angle.restype = None
|
||||
_dll.rotate_angle.argtypes = [ndpointer(c_double), POINTER(c_double),
|
||||
ndpointer(c_double), POINTER(c_double)]
|
||||
|
|
@ -169,7 +170,9 @@ def evaluate_legendre(data, x):
|
|||
"""
|
||||
|
||||
data_arr = np.array(data, dtype=np.float64)
|
||||
return _dll.evaluate_legendre(data_arr, c_double(x))
|
||||
return _dll.evaluate_legendre(c_int(len(data)),
|
||||
data_arr.ctypes.data_as(POINTER(c_double)),
|
||||
c_double(x))
|
||||
|
||||
|
||||
def rotate_angle(uvw0, mu, phi=None):
|
||||
|
|
@ -194,7 +197,9 @@ def rotate_angle(uvw0, mu, phi=None):
|
|||
|
||||
uvw = np.zeros(3, dtype=np.float64)
|
||||
uvw0_arr = np.array(uvw0, dtype=np.float64)
|
||||
_dll.rotate_angle(uvw0_arr, c_double(mu), uvw, c_double(phi))
|
||||
|
||||
_dll.rotate_angle(uvw0_arr, c_double(mu),
|
||||
uvw, c_double(phi))
|
||||
return uvw
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -703,16 +703,17 @@ contains
|
|||
! and the value of x
|
||||
!===============================================================================
|
||||
|
||||
pure function evaluate_legendre(data, x) result(val) bind(C)
|
||||
real(C_DOUBLE), intent(in) :: data(:)
|
||||
pure function evaluate_legendre(n, data, x) result(val) bind(C)
|
||||
integer(C_INT), intent(in) :: n
|
||||
real(C_DOUBLE), intent(in) :: data(n)
|
||||
real(C_DOUBLE), intent(in) :: x
|
||||
real(C_DOUBLE) :: val
|
||||
|
||||
integer(C_INT) :: l
|
||||
|
||||
val = HALF * data(1)
|
||||
do l = 1, size(data) - 1
|
||||
val = val + (real(l,8) + HALF) * data(l + 1) * calc_pn(l,x)
|
||||
do l = 1, n - 1
|
||||
val = val + (real(l, 8) + HALF) * data(l + 1) * calc_pn(l,x)
|
||||
end do
|
||||
|
||||
end function evaluate_legendre
|
||||
|
|
|
|||
|
|
@ -1097,7 +1097,9 @@ contains
|
|||
end if
|
||||
|
||||
scatt_coeffs(gin) % data(imu, gout) = &
|
||||
evaluate_legendre(input_scatt(gin) % data(:, gout), mu)
|
||||
evaluate_legendre( &
|
||||
size(input_scatt(gin) % data, dim=1), &
|
||||
input_scatt(gin) % data(:, gout), mu)
|
||||
|
||||
! Ensure positivity of distribution
|
||||
if (scatt_coeffs(gin) % data(imu, gout) < ZERO) &
|
||||
|
|
@ -2079,6 +2081,7 @@ contains
|
|||
|
||||
scatt_coeffs(gin, iazi, ipol) % data(imu, gout) = &
|
||||
evaluate_legendre(&
|
||||
size(input_scatt(gin, iazi, ipol) % data, dim=1), &
|
||||
input_scatt(gin, iazi, ipol) % data(:, gout), mu)
|
||||
|
||||
! Ensure positivity of distribution
|
||||
|
|
|
|||
|
|
@ -445,7 +445,8 @@ contains
|
|||
if (gout < this % gmin(gin) .or. gout > this % gmax(gin)) then
|
||||
f = ZERO
|
||||
else
|
||||
f = evaluate_legendre(this % dist(gin) % data(:, gout), mu)
|
||||
f = evaluate_legendre(size(this % dist(gin) % data, dim=1), &
|
||||
this % dist(gin) % data(:, gout), mu)
|
||||
end if
|
||||
|
||||
end function scattdatalegendre_calc_f
|
||||
|
|
|
|||
180
tests/unit_tests/test_math.py
Normal file
180
tests/unit_tests/test_math.py
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
import numpy as np
|
||||
import scipy as sp
|
||||
|
||||
import openmc
|
||||
import openmc.capi
|
||||
|
||||
|
||||
def test_normal_percentile():
|
||||
# normal_percentile has three branches to consider:
|
||||
# p < 0.02425; 0.02425 <= p <= 0.97575; and p > 0.97575
|
||||
test_ps = [0.02, 0.4, 0.5, 0.6, 0.98]
|
||||
|
||||
# The reference solutions come from Scipy
|
||||
ref_zs = [sp.stats.norm.ppf(p) for p in test_ps]
|
||||
|
||||
test_zs = [openmc.capi.math.normal_percentile(p) for p in test_ps]
|
||||
|
||||
assert np.allclose(ref_zs, test_zs)
|
||||
|
||||
|
||||
def test_t_percentile():
|
||||
# Permutations include 1 DoF, 2 DoF, and > 2 DoF
|
||||
# We will test 5 p-values at 3-DoF values
|
||||
test_ps = [0.02, 0.4, 0.5, 0.6, 0.98]
|
||||
test_dfs = [1, 2, 5]
|
||||
|
||||
# The reference solutions come from Scipy
|
||||
ref_ts = [[sp.stats.t.ppf(p, df) for p in test_ps] for df in test_dfs]
|
||||
|
||||
test_ts = [[openmc.capi.math.t_percentile(p, df) for p in test_ps]
|
||||
for df in test_dfs]
|
||||
|
||||
# The 5 DoF approximation in openmc.capi.math.t_percentile is off by up to
|
||||
# 8e-3 from the scipy solution, so test that one separately with looser
|
||||
# tolerance
|
||||
assert np.allclose(ref_ts[:-1], test_ts[:-1])
|
||||
assert np.allclose(ref_ts[-1], test_ts[-1], atol=1e-2)
|
||||
|
||||
|
||||
def test_calc_pn():
|
||||
max_order = 10
|
||||
test_ns = np.array([i for i in range(0, max_order + 1)])
|
||||
test_xs = np.linspace(-1., 1., num=5, endpoint=True)
|
||||
|
||||
# Reference solutions from scipy
|
||||
ref_vals = [sp.special.eval_legendre(n, test_xs) for n in test_ns]
|
||||
|
||||
test_vals = [[openmc.capi.math.calc_pn(n, x) for x in test_xs]
|
||||
for n in test_ns]
|
||||
|
||||
assert np.allclose(ref_vals, test_vals)
|
||||
|
||||
|
||||
def test_calc_rn():
|
||||
max_order = 10
|
||||
test_ns = np.array([i for i in range(0, max_order + 1)])
|
||||
azi = 0.1 # Longitude
|
||||
pol = 0.2 # Latitude
|
||||
test_uvw = np.array([np.sin(pol) * np.cos(azi),
|
||||
np.sin(pol) * np.sin(azi),
|
||||
np.cos(pol)])
|
||||
|
||||
# Reference solutions from the equations
|
||||
ref_vals = []
|
||||
|
||||
def coeff(n, m):
|
||||
return np.sqrt((2. * n + 1) * sp.special.factorial(n - m) /
|
||||
(sp.special.factorial(n + m)))
|
||||
|
||||
def pnm_bar(n, m, mu):
|
||||
val = coeff(n, m)
|
||||
if m != 0:
|
||||
val *= np.sqrt(2.)
|
||||
val *= sp.special.lpmv([m], [n], [mu])
|
||||
return val[0]
|
||||
|
||||
ref_vals = []
|
||||
for n in test_ns:
|
||||
for m in range(-n, n + 1):
|
||||
if m < 0:
|
||||
ylm = pnm_bar(n, np.abs(m), np.cos(pol)) * \
|
||||
np.sin(np.abs(m) * azi)
|
||||
else:
|
||||
ylm = pnm_bar(n, m, np.cos(pol)) * np.cos(m * azi)
|
||||
|
||||
# Un-normalize for comparison
|
||||
ylm /= np.sqrt(2. * n + 1.)
|
||||
ref_vals.append(ylm)
|
||||
|
||||
test_vals = []
|
||||
for n in test_ns:
|
||||
ylms = openmc.capi.math.calc_rn(n, test_uvw)
|
||||
test_vals.extend(ylms.tolist())
|
||||
|
||||
assert np.allclose(ref_vals, test_vals)
|
||||
|
||||
|
||||
def test_calc_zn():
|
||||
pass
|
||||
|
||||
|
||||
def test_evaluate_legendre():
|
||||
max_order = 10
|
||||
# Coefficients are set to 1, but will incorporate the (2l+1)/2 norm factor
|
||||
# for the reference solution
|
||||
test_coeffs = [0.5 * (2. * l + 1.) for l in range(max_order + 1)]
|
||||
test_xs = np.linspace(-1., 1., num=5, endpoint=True)
|
||||
|
||||
ref_vals = np.polynomial.legendre.legval(test_xs, test_coeffs)
|
||||
|
||||
# Set the coefficients back to 1s for the test values
|
||||
test_coeffs = [1. for l in range(max_order + 1)]
|
||||
test_vals = np.array([openmc.capi.math.evaluate_legendre(test_coeffs, x)
|
||||
for x in test_xs])
|
||||
|
||||
assert np.allclose(ref_vals, test_vals)
|
||||
|
||||
|
||||
def test_rotate_angle():
|
||||
uvw0 = np.array([1., 0., 0.])
|
||||
phi = 0.
|
||||
mu = 0.
|
||||
|
||||
# reference: mu of 0 pulls the vector the bottom, so:
|
||||
ref_uvw = np.array([0., 0., -1.])
|
||||
|
||||
test_uvw = openmc.capi.math.rotate_angle(uvw0, mu, phi)
|
||||
|
||||
assert np.allclose(ref_uvw, test_uvw)
|
||||
|
||||
# Repeat for mu = 1 (no change)
|
||||
mu = 1.
|
||||
ref_uvw = np.array([1., 0., 0.])
|
||||
|
||||
test_uvw = openmc.capi.math.rotate_angle(uvw0, mu, phi)
|
||||
|
||||
assert np.allclose(ref_uvw, test_uvw)
|
||||
|
||||
# Need to test phi=None somehow...
|
||||
|
||||
|
||||
def test_maxwell_spectrum():
|
||||
settings = openmc.capi.settings
|
||||
settings.seed = 1
|
||||
T = 0.5
|
||||
ref_val = 0.6129982175261098
|
||||
test_val = openmc.capi.math.maxwell_spectrum(T)
|
||||
print(test_val)
|
||||
assert np.isclose(ref_val, test_val)
|
||||
|
||||
|
||||
def test_watt_spectrum():
|
||||
settings = openmc.capi.settings
|
||||
settings.seed = 1
|
||||
a = 0.5
|
||||
b = 0.75
|
||||
ref_val = 0.6247242713640233
|
||||
test_val = openmc.capi.math.watt_spectrum(a, b)
|
||||
print(test_val)
|
||||
assert np.isclose(ref_val, test_val)
|
||||
|
||||
|
||||
def test_broaden_wmp_polynomials():
|
||||
# Two branches of the code to worry about, beta > 6 and otherwise
|
||||
# beta = sqrtE * dopp
|
||||
# First lets do beta > 6
|
||||
test_E = 0.5
|
||||
test_dopp = 100. # approximately U235 at room temperature
|
||||
n = 4
|
||||
ref_val = [2., 1.41421356, 1.0001, 0.70731891]
|
||||
test_val = openmc.capi.math.broaden_wmp_polynomials(test_E, test_dopp, n)
|
||||
|
||||
assert np.allclose(ref_val, test_val)
|
||||
|
||||
# now beta < 6
|
||||
test_dopp = 5.
|
||||
ref_val = [1.99999885, 1.41421356, 1.04, 0.79195959]
|
||||
test_val = openmc.capi.math.broaden_wmp_polynomials(test_E, test_dopp, n)
|
||||
|
||||
assert np.allclose(ref_val, test_val)
|
||||
Loading…
Add table
Add a link
Reference in a new issue