From 600969ce2bb726b812b21d8f5bba263f7dc67817 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sun, 29 Apr 2018 06:35:56 -0400 Subject: [PATCH 01/19] Exposed math.F90 to C and then created python wrappers around these functions. Seem to work --- openmc/capi/__init__.py | 1 + openmc/capi/math.py | 263 ++++++++++++++++++++++++++ src/api.F90 | 13 ++ src/distribution_multivariate.F90 | 2 +- src/math.F90 | 211 ++++++++++----------- src/physics.F90 | 13 +- src/physics_mg.F90 | 2 +- src/tallies/tally.F90 | 9 +- src/tallies/tally_filter_sph_harm.F90 | 2 +- 9 files changed, 397 insertions(+), 119 deletions(-) create mode 100644 openmc/capi/math.py diff --git a/openmc/capi/__init__.py b/openmc/capi/__init__.py index 217e782a84..672a6e811f 100644 --- a/openmc/capi/__init__.py +++ b/openmc/capi/__init__.py @@ -47,3 +47,4 @@ from .mesh import * from .filter import * from .tally import * from .settings import settings +from .math import * diff --git a/openmc/capi/math.py b/openmc/capi/math.py new file mode 100644 index 0000000000..d51858fd3b --- /dev/null +++ b/openmc/capi/math.py @@ -0,0 +1,263 @@ +from ctypes import (c_int, c_double, POINTER) + +import numpy as np +from numpy.ctypeslib import ndpointer + +from . import _dll + +_dll.normal_percentile.restype = c_double +_dll.normal_percentile.argtypes = [POINTER(c_double)] +_dll.t_percentile.restype = c_double +_dll.t_percentile.argtypes = [POINTER(c_double), POINTER(c_int)] +_dll.calc_pn.restype = c_double +_dll.calc_pn.argtypes = [POINTER(c_int), POINTER(c_double)] +_dll.calc_rn.restype = None +_dll.calc_rn.argtypes = [POINTER(c_int), ndpointer(c_double), + ndpointer(c_double)] +_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.rotate_angle.restype = None +_dll.rotate_angle.argtypes = [ndpointer(c_double), POINTER(c_double), + ndpointer(c_double), POINTER(c_double)] +_dll.maxwell_spectrum.restype = c_double +_dll.maxwell_spectrum.argtypes = [POINTER(c_double)] +_dll.watt_spectrum.restype = c_double +_dll.watt_spectrum.argtypes = [POINTER(c_double), POINTER(c_double)] +# COMPLEX DATA TYPES? +# _dll.faddeeva.restype = c_double +# _dll.faddeeva.argtypes = [POINTER(c_double)] + +# _dll.w_derivative.restype = c_double +# _dll.w_derivative.argtypes = [POINTER(c_double)] +_dll.broaden_wmp_polynomials.restype = None +_dll.broaden_wmp_polynomials.argtypes = [POINTER(c_double), POINTER(c_double), + POINTER(c_int), ndpointer(c_double)] + + +def normal_percentile(p): + """ Calculate the percentile of the standard normal distribution with a + specified probability level + + Parameters + ---------- + p : float + Probability level + + Returns + ------- + float + Corresponding z-value + + """ + + return _dll.normal_percentile(c_double(p)) + + +def t_percentile(p, df): + """ Calculate the percentile of the Student's t distribution with a + specified probability level and number of degrees of freedom + + Parameters + ---------- + p : float + Probability level + df : int + Degrees of freedom + + Returns + ------- + float + Corresponding t-value + + """ + + return _dll.t_percentile(c_double(p), c_int(df)) + + +def calc_pn(n, x): + """ Calculate the n-th order Legendre polynomial at the value of x. + + Parameters + ---------- + n : int + Legendre order + x : float + Independent variable to evaluate the Legendre at + + Returns + ------- + float + Corresponding Legendre polynomial result + + """ + + return _dll.calc_pn(c_int(n), c_double(x)) + + +def calc_rn(n, uvw): + """ Calculate the n-th order real Spherical Harmonics for a given angle; + all Rn,m values are provided (where -n <= m <= n). + + Parameters + ---------- + n : int + Harmonics order + uvw : iterable of float + Independent variable to evaluate the Legendre at + + Returns + ------- + numpy.ndarray + Corresponding real harmonics value + + """ + + num_nm = 2 * n + 1 + rn = np.empty(num_nm, dtype=np.float64) + uvw_arr = np.array(uvw, dtype=np.float64) + _dll.calc_rn(c_int(n), uvw_arr, rn) + return rn + + +def calc_zn(n, rho, phi): + """ Calculate the n-th order modified Zernike polynomial moment for a + given angle (rho, theta) location in the unit disk. The normalization of + the polynomials is such that the integral of Z_pq*Z_pq over the unit disk + is exactly pi + + Parameters + ---------- + n : int + Maximum order + rho : float + Radial location in the unit disk + phi : float + Theta (radians) location in the unit disk + + Returns + ------- + numpy.ndarray + Corresponding resulting list of coefficients + + """ + + num_bins = ((n + 1) * (n + 2)) / 2 + zn = np.zeros(num_bins, dtype=np.float64) + _dll.calc_zn(c_int(n), c_double(rho), c_double(phi), zn) + return zn + + +def evaluate_legendre(data, x): + """ Finds the value of f(x) given a set of Legendre coefficients + and the value of x. + + Parameters + ---------- + data : iterable of float + Legendre coefficients + x : float + Independent variable to evaluate the Legendre at + + Returns + ------- + float + Corresponding Legendre expansion result + + """ + + data_arr = np.array(data, dtype=np.float64) + return _dll.evaluate_legendre(data_arr, c_double(x)) + + +def rotate_angle(uvw0, mu, phi=None): + """ Rotates direction cosines through a polar angle whose cosine is + mu and through an azimuthal angle sampled uniformly. + + Parameters + ---------- + uvw0 : iterable of float + Original direction cosine + mu : float + Polar angle cosine to rotate + phi : float, optional + Azimuthal angle; if None, one will be sampled uniformly + + Returns + ------- + numpy.ndarray + Rotated direction cosine + + """ + + 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)) + return uvw + + +def maxwell_spectrum(T): + """ Samples an energy from the Maxwell fission distribution based + on a direct sampling scheme. + + Parameters + ---------- + T : float + Spectrum parameter + + Returns + ------- + float + Sampled outgoing energy + + """ + + return _dll.maxwell_spectrum(c_double(T)) + + +def watt_spectrum(a, b): + """ Samples an energy from the Watt energy-dependent fission spectrum. + + Parameters + ---------- + a : float + Spectrum parameter a + b : float + Spectrum parameter b + + Returns + ------- + float + Sampled outgoing energy + + """ + + return _dll.watt_spectrum(c_double(a), c_double(b)) + + +def broaden_wmp_polynomials(E, dopp, n): + """ Doppler broadens the windowed multipole curvefit. The curvefit is a + polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E) ... + + Parameters + ---------- + E : float + Energy to evaluate at + dopp : float + sqrt(atomic weight ratio / kT), with kT given in eV + n : int + Number of components to the polynomial + + Returns + ------- + numpy.ndarray + Resultant leading coefficients + + """ + + factors = np.zeros(n, dtype=np.float64) + _dll.broaden_wmp_polynomials(c_double(E), c_double(dopp), c_int(n), + factors) + return factors diff --git a/src/api.F90 b/src/api.F90 index df183c292f..30aca68343 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -12,6 +12,7 @@ module openmc_api use geometry_header use hdf5_interface use material_header + use math use mesh_header use message_passing use nuclide_header @@ -97,6 +98,18 @@ module openmc_api public :: openmc_tally_set_nuclides public :: openmc_tally_set_scores public :: openmc_tally_set_type + public :: normal_percentile + public :: t_percentile + public :: calc_pn + public :: calc_rn + public :: calc_zn + public :: evaluate_legendre + public :: rotate_angle + public :: maxwell_spectrum + public :: watt_spectrum + public :: faddeeva + public :: w_derivative + public :: broaden_wmp_polynomials contains diff --git a/src/distribution_multivariate.F90 b/src/distribution_multivariate.F90 index 650ab26fa2..4d7d42cc3d 100644 --- a/src/distribution_multivariate.F90 +++ b/src/distribution_multivariate.F90 @@ -119,7 +119,7 @@ contains else ! Sample azimuthal angle phi = this % phi % sample() - uvw(:) = rotate_angle(this % reference_uvw, mu, phi) + call rotate_angle(this % reference_uvw, mu, uvw, phi) end if end function polar_azimuthal_sample diff --git a/src/math.F90 b/src/math.F90 index ee8cd0530b..47e6acefb0 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -6,6 +6,19 @@ module math use random_lcg, only: prn implicit none + private + public :: normal_percentile + public :: t_percentile + public :: calc_pn + public :: calc_rn + public :: calc_zn + public :: evaluate_legendre + public :: rotate_angle + public :: maxwell_spectrum + public :: watt_spectrum + public :: faddeeva + public :: w_derivative + public :: broaden_wmp_polynomials !=============================================================================== ! FADDEEVA_W evaluates the scaled complementary error function. This @@ -29,24 +42,24 @@ contains ! distribution with a specified probability level !=============================================================================== - elemental function normal_percentile(p) result(z) + pure function normal_percentile(p) result(z) bind(C) - real(8), intent(in) :: p ! probability level - real(8) :: z ! corresponding z-value + real(C_DOUBLE), intent(in) :: p ! probability level + real(C_DOUBLE) :: z ! corresponding z-value - real(8) :: q - real(8) :: r - real(8), parameter :: p_low = 0.02425_8 - real(8), parameter :: a(6) = (/ & + real(C_DOUBLE) :: q + real(C_DOUBLE) :: r + real(C_DOUBLE), parameter :: p_low = 0.02425_8 + real(C_DOUBLE), parameter :: a(6) = (/ & -3.969683028665376e1_8, 2.209460984245205e2_8, -2.759285104469687e2_8, & 1.383577518672690e2_8, -3.066479806614716e1_8, 2.506628277459239e0_8 /) - real(8), parameter :: b(5) = (/ & + real(C_DOUBLE), parameter :: b(5) = (/ & -5.447609879822406e1_8, 1.615858368580409e2_8, -1.556989798598866e2_8, & 6.680131188771972e1_8, -1.328068155288572e1_8 /) - real(8), parameter :: c(6) = (/ & + real(C_DOUBLE), parameter :: c(6) = (/ & -7.784894002430293e-3_8, -3.223964580411365e-1_8, -2.400758277161838_8, & -2.549732539343734_8, 4.374664141464968_8, 2.938163982698783_8 /) - real(8), parameter :: d(4) = (/ & + real(C_DOUBLE), parameter :: d(4) = (/ & 7.784695709041462e-3_8, 3.224671290700398e-1_8, & 2.445134137142996_8, 3.754408661907416_8 /) @@ -88,16 +101,16 @@ contains ! specified probability level and number of degrees of freedom !=============================================================================== - elemental function t_percentile(p, df) result(t) + pure function t_percentile(p, df) result(t) bind(C) - real(8), intent(in) :: p ! probability level - integer, intent(in) :: df ! degrees of freedom - real(8) :: t ! corresponding t-value + real(C_DOUBLE), intent(in) :: p ! probability level + integer(C_INT), intent(in) :: df ! degrees of freedom + real(C_DOUBLE) :: t ! corresponding t-value - real(8) :: n ! degrees of freedom as a real(8) - real(8) :: k ! n - 2 - real(8) :: z ! percentile of normal distribution - real(8) :: z2 ! z * z + real(C_DOUBLE) :: n ! degrees of freedom as a real(8) + real(C_DOUBLE) :: k ! n - 2 + real(C_DOUBLE) :: z ! percentile of normal distribution + real(C_DOUBLE) :: z2 ! z * z if (df == 1) then ! For one degree of freedom, the t-distribution becomes a Cauchy @@ -140,12 +153,14 @@ contains ! the return value will be 1.0. !=============================================================================== - elemental function calc_pn(n,x) result(pnx) + pure function calc_pn(n,x) result(pnx) bind(C) - integer, intent(in) :: n ! Legendre order requested - real(8), intent(in) :: x ! Independent variable the Legendre is to be - ! evaluated at; x must be in the domain [-1,1] - real(8) :: pnx ! The Legendre poly of order n evaluated at x + integer(C_INT), intent(in) :: n ! Legendre order requested + real(C_DOUBLE), intent(in) :: x ! Independent variable the Legendre is to + ! be evaluated at; x must be in the + ! domain [-1,1] + real(C_DOUBLE) :: pnx ! The Legendre poly of order n evaluated + ! at x select case(n) case(1) @@ -185,14 +200,16 @@ contains ! (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) !=============================================================================== - pure function calc_rn(n,uvw) result(rn) + subroutine calc_rn(n, uvw, rn) bind(C) - integer, intent(in) :: n ! Order requested - real(8), intent(in) :: uvw(3) ! Direction of travel, assumed to be on unit sphere - real(8) :: rn(2*n + 1) ! The resultant R_n(uvw) + integer(C_INT), intent(in) :: n ! Order requested + real(C_DOUBLE), intent(in) :: uvw(3) ! Direction of travel; + ! assumed to be on unit sphere + real(C_DOUBLE) :: rn(2*n + 1) ! The resultant R_n(uvw) - real(8) :: phi, w ! Azimuthal and Cosine of Polar angles (from uvw) - real(8) :: w2m1 ! (w^2 - 1), frequently used in these + + real(C_DOUBLE) :: phi, w ! Azimuthal and Cosine of Polar angles (from uvw) + real(C_DOUBLE) :: w2m1 ! (w^2 - 1), frequently used in these w = uvw(3) ! z = cos(polar) if (uvw(1) == ZERO) then @@ -572,7 +589,7 @@ contains rn = ONE end select - end function calc_rn + end subroutine calc_rn !=============================================================================== ! CALC_ZN calculates the n-th order modified Zernike polynomial moment for a @@ -581,31 +598,31 @@ contains ! exactly pi !=============================================================================== - subroutine calc_zn(n, rho, phi, zn) + subroutine calc_zn(n, rho, phi, zn) bind(C) ! This procedure uses the modified Kintner's method for calculating Zernike ! polynomials as outlined in Chong, C. W., Raveendran, P., & Mukundan, ! R. (2003). A comparative analysis of algorithms for fast computation of ! Zernike moments. Pattern Recognition, 36(3), 731-742. - integer, intent(in) :: n ! Maximum order - real(8), intent(in) :: rho ! Radial location in the unit disk - real(8), intent(in) :: phi ! Theta (radians) location in the unit disk - real(8), intent(out) :: zn(:) ! The resulting list of coefficients + integer(C_INT), intent(in) :: n ! Maximum order + real(C_DOUBLE), intent(in) :: rho ! Radial location in the unit disk + real(C_DOUBLE), intent(in) :: phi ! Theta (radians) location in the unit disk + real(C_DOUBLE), intent(out) :: zn(:) ! The resulting list of coefficients - real(8) :: sin_phi, cos_phi ! Sine and Cosine of phi - real(8) :: sin_phi_vec(n+1) ! Contains sin(n*phi) - real(8) :: cos_phi_vec(n+1) ! Contains cos(n*phi) - real(8) :: zn_mat(n+1, n+1) ! Matrix form of the coefficients which is - ! easier to work with - real(8) :: k1, k2, k3, k4 ! Variables for R_m_n calculation - real(8) :: sqrt_norm ! normalization for radial moments - integer :: i,p,q ! Loop counters + real(C_DOUBLE) :: sin_phi, cos_phi ! Sine and Cosine of phi + real(C_DOUBLE) :: sin_phi_vec(n+1) ! Contains sin(n*phi) + real(C_DOUBLE) :: cos_phi_vec(n+1) ! Contains cos(n*phi) + real(C_DOUBLE) :: zn_mat(n+1, n+1) ! Matrix form of the coefficients which is + ! easier to work with + real(C_DOUBLE) :: k1, k2, k3, k4 ! Variables for R_m_n calculation + real(C_DOUBLE) :: sqrt_norm ! normalization for radial moments + integer(C_INT) :: i,p,q ! Loop counters - real(8), parameter :: SQRT_N_1(0:10) = [& + real(C_DOUBLE), parameter :: SQRT_N_1(0:10) = [& sqrt(1.0_8), sqrt(2.0_8), sqrt(3.0_8), sqrt(4.0_8), & sqrt(5.0_8), sqrt(6.0_8), sqrt(7.0_8), sqrt(8.0_8), & sqrt(9.0_8), sqrt(10.0_8), sqrt(11.0_8)] - real(8), parameter :: SQRT_2N_2(0:10) = SQRT_N_1*sqrt(2.0_8) + real(C_DOUBLE), parameter :: SQRT_2N_2(0:10) = SQRT_N_1*sqrt(2.0_8) ! n == radial degree ! m == azimuthal frequency @@ -681,41 +698,17 @@ contains end do end subroutine calc_zn -!=============================================================================== -! EXPAND_HARMONIC expands a given series of real spherical harmonics -!=============================================================================== - - pure function expand_harmonic(data, order, uvw) result(val) - real(8), intent(in) :: data(:) - integer, intent(in) :: order - real(8), intent(in) :: uvw(3) - real(8) :: val - - integer :: l, lm_lo, lm_hi - - val = data(1) - lm_lo = 2 - lm_hi = 4 - do l = 1, order - 1 - val = val + sqrt(TWO * real(l,8) + ONE) * & - dot_product(calc_rn(l,uvw), data(lm_lo:lm_hi)) - lm_lo = lm_hi + 1 - lm_hi = lm_lo + 2 * (l + 1) - end do - - end function expand_harmonic - !=============================================================================== ! EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients ! and the value of x !=============================================================================== - pure function evaluate_legendre(data, x) result(val) - real(8), intent(in) :: data(:) - real(8), intent(in) :: x - real(8) :: val + pure function evaluate_legendre(data, x) result(val) bind(C) + real(C_DOUBLE), intent(in) :: data(:) + real(C_DOUBLE), intent(in) :: x + real(C_DOUBLE) :: val - integer :: l + integer(C_INT) :: l val = HALF * data(1) do l = 1, size(data) - 1 @@ -730,20 +723,20 @@ contains ! with direct sampling rather than rejection as is done in MCNP and SERPENT. !=============================================================================== - function rotate_angle(uvw0, mu, phi) result(uvw) - real(8), intent(in) :: uvw0(3) ! directional cosine - real(8), intent(in) :: mu ! cosine of angle in lab or CM - real(8), optional :: phi ! azimuthal angle - real(8) :: uvw(3) ! rotated directional cosine + subroutine rotate_angle(uvw0, mu, uvw, phi) bind(C) + real(C_DOUBLE), intent(in) :: uvw0(3) ! directional cosine + real(C_DOUBLE), intent(in) :: mu ! cosine of angle in lab or CM + real(C_DOUBLE), intent(out) :: uvw(3) ! rotated directional cosine + real(C_DOUBLE), optional :: phi ! azimuthal angle - real(8) :: phi_ ! azimuthal angle - real(8) :: sinphi ! sine of azimuthal angle - real(8) :: cosphi ! cosine of azimuthal angle - real(8) :: a ! sqrt(1 - mu^2) - real(8) :: b ! sqrt(1 - w^2) - real(8) :: u0 ! original cosine in x direction - real(8) :: v0 ! original cosine in y direction - real(8) :: w0 ! original cosine in z direction + real(C_DOUBLE) :: phi_ ! azimuthal angle + real(C_DOUBLE) :: sinphi ! sine of azimuthal angle + real(C_DOUBLE) :: cosphi ! cosine of azimuthal angle + real(C_DOUBLE) :: a ! sqrt(1 - mu^2) + real(C_DOUBLE) :: b ! sqrt(1 - w^2) + real(C_DOUBLE) :: u0 ! original cosine in x direction + real(C_DOUBLE) :: v0 ! original cosine in y direction + real(C_DOUBLE) :: w0 ! original cosine in z direction ! Copy original directional cosines u0 = uvw0(1) @@ -776,7 +769,7 @@ contains uvw(3) = mu*w0 + a*(v0*w0*cosphi - u0*sinphi)/b end if - end function rotate_angle + end subroutine rotate_angle !=============================================================================== ! MAXWELL_SPECTRUM samples an energy from the Maxwell fission distribution based @@ -785,13 +778,13 @@ contains ! be sampled using rule C64 in the Monte Carlo Sampler LA-9721-MS. !=============================================================================== - function maxwell_spectrum(T) result(E_out) + function maxwell_spectrum(T) result(E_out) bind(C) - real(8), intent(in) :: T ! tabulated function of incoming E - real(8) :: E_out ! sampled energy + real(C_DOUBLE), intent(in) :: T ! tabulated function of incoming E + real(C_DOUBLE) :: E_out ! sampled energy - real(8) :: r1, r2, r3 ! random numbers - real(8) :: c ! cosine of pi/2*r3 + real(C_DOUBLE) :: r1, r2, r3 ! random numbers + real(C_DOUBLE) :: c ! cosine of pi/2*r3 r1 = prn() r2 = prn() @@ -813,13 +806,13 @@ contains ! original Watt spectrum derivation (See F. Brown's MC lectures). !=============================================================================== - function watt_spectrum(a, b) result(E_out) + function watt_spectrum(a, b) result(E_out) bind(C) - real(8), intent(in) :: a ! Watt parameter a - real(8), intent(in) :: b ! Watt parameter b - real(8) :: E_out ! energy of emitted neutron + real(C_DOUBLE), intent(in) :: a ! Watt parameter a + real(C_DOUBLE), intent(in) :: b ! Watt parameter b + real(C_DOUBLE) :: E_out ! energy of emitted neutron - real(8) :: w ! sampled from Maxwellian + real(C_DOUBLE) :: w ! sampled from Maxwellian w = maxwell_spectrum(a) E_out = w + a*a*b/4. + (TWO*prn() - ONE)*sqrt(a*a*b*w) @@ -830,9 +823,9 @@ contains ! FADDEEVA the Faddeeva function, using Stephen Johnson's implementation !=============================================================================== - function faddeeva(z) result(wv) - complex(C_DOUBLE_COMPLEX), intent(in) :: z ! The point to evaluate Z at - complex(8) :: wv ! The resulting w(z) value + function faddeeva(z) result(wv) bind(C) + complex(C_DOUBLE_COMPLEX), intent(in) :: z ! The point to evaluate Z at + complex(C_DOUBLE_COMPLEX) :: wv ! The resulting w(z) value real(C_DOUBLE) :: relerr ! Target relative error in inner loop of MIT ! Faddeeva @@ -860,10 +853,10 @@ contains end function faddeeva - recursive function w_derivative(z, order) result(wv) + recursive function w_derivative(z, order) result(wv) bind(C) complex(C_DOUBLE_COMPLEX), intent(in) :: z ! The point to evaluate Z at - integer, intent(in) :: order - complex(8) :: wv ! The resulting w(z) value + integer(C_INT), intent(in) :: order + complex(C_DOUBLE_COMPLEX) :: wv ! The resulting w(z) value select case(order) case (0) @@ -882,12 +875,12 @@ contains ! a/E + b/sqrt(E) + c + d sqrt(E) ... !=============================================================================== - subroutine broaden_wmp_polynomials(E, dopp, n, factors) - real(8), intent(in) :: E ! Energy to evaluate at - real(8), intent(in) :: dopp ! sqrt(atomic weight ratio / kT), + subroutine broaden_wmp_polynomials(E, dopp, n, factors) bind(C) + real(C_DOUBLE), intent(in) :: E ! Energy to evaluate at + real(C_DOUBLE), intent(in) :: dopp ! sqrt(atomic weight ratio / kT), ! kT given in eV. - integer, intent(in) :: n ! number of components to polynomial - real(8), intent(out):: factors(n) ! output leading coefficient + integer(C_INT), intent(in) :: n ! number of components to polynomial + real(C_DOUBLE), intent(out):: factors(n) ! output leading coefficient integer :: i diff --git a/src/physics.F90 b/src/physics.F90 index b614b81851..8183435cb7 100644 --- a/src/physics.F90 +++ b/src/physics.F90 @@ -495,7 +495,8 @@ contains ! Rotate neutron velocity vector to new angle -- note that the speed of the ! neutron in CM does not change in elastic scattering. However, the speed ! will change when we convert back to LAB - v_n = vel * rotate_angle(uvw_cm, mu_cm) + call rotate_angle(uvw_cm, mu_cm, v_n) + v_n = vel * v_n ! Transform back to LAB frame v_n = v_n + v_cm @@ -785,7 +786,7 @@ contains if (abs(mu) > ONE) mu = sign(ONE,mu) ! change direction of particle - uvw = rotate_angle(uvw, mu) + call rotate_angle(uvw, mu, uvw) end subroutine sab_scatter @@ -976,7 +977,8 @@ contains if (abs(mu) < ONE) then ! set and accept target velocity E_t = E_t / awr - v_target = sqrt(E_t) * rotate_angle(uvw, mu) + call rotate_angle(uvw, mu, v_target) + v_target = sqrt(E_t) * v_target exit ARES_REJECT_LOOP end if end do ARES_REJECT_LOOP @@ -1056,7 +1058,8 @@ contains ! Determine velocity vector of target nucleus based on neutron's velocity ! and the sampled angle between them - v_target = vt * rotate_angle(uvw, mu) + call rotate_angle(uvw, mu, v_target) + v_target = vt * v_target end subroutine sample_cxs_target_velocity @@ -1327,7 +1330,7 @@ contains p % mu = mu ! change direction of particle - p % coord(1) % uvw = rotate_angle(p % coord(1) % uvw, mu) + call rotate_angle(p % coord(1) % uvw, mu, p % coord(1) % uvw) ! evaluate yield yield = rxn % products(1) % yield % evaluate(E_in) diff --git a/src/physics_mg.F90 b/src/physics_mg.F90 index eb82085941..05493af057 100644 --- a/src/physics_mg.F90 +++ b/src/physics_mg.F90 @@ -151,7 +151,7 @@ contains p % E = energy_bin_avg(p % g) ! Convert change in angle (mu) to new direction - p % coord(1) % uvw = rotate_angle(p % coord(1) % uvw, p % mu) + call rotate_angle(p % coord(1) % uvw, p % mu, p % coord(1) % uvw) ! Set event component p % event = EVENT_SCATTER diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index f4d12be7e1..f7d3c8a08e 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -46,6 +46,9 @@ module tally end subroutine score_analog_tally_ end interface + real(C_DOUBLE) :: rn(2 * MAX_ANG_ORDER + 1) +!$omp threadprivate(rn) + contains !=============================================================================== @@ -2131,11 +2134,12 @@ contains num_nm = 2 * n + 1 ! multiply score by the angular flux moments and store + call calc_rn(n, p % last_uvw, rn(1:num_nm)) !$omp critical (score_general_scatt_yn) t % results(RESULT_VALUE, score_index: score_index + num_nm - 1, & filter_index) = t % results(RESULT_VALUE, & score_index: score_index + num_nm - 1, filter_index) & - + score * calc_pn(n, p % mu) * calc_rn(n, p % last_uvw) + + score * calc_pn(n, p % mu) * rn(1:num_nm) !$omp end critical (score_general_scatt_yn) end do i = i + (t % moment_order(i) + 1)**2 - 1 @@ -2159,11 +2163,12 @@ contains num_nm = 2 * n + 1 ! multiply score by the angular flux moments and store + call calc_rn(n, uvw, rn(1:num_nm)) !$omp critical (score_general_flux_tot_yn) t % results(RESULT_VALUE, score_index: score_index + num_nm - 1, & filter_index) = t % results(RESULT_VALUE, & score_index: score_index + num_nm - 1, filter_index) & - + score * calc_rn(n, uvw) + + score * rn(1:num_nm) !$omp end critical (score_general_flux_tot_yn) end do i = i + (t % moment_order(i) + 1)**2 - 1 diff --git a/src/tallies/tally_filter_sph_harm.F90 b/src/tallies/tally_filter_sph_harm.F90 index 5c47c2c71f..5faf2b6fdb 100644 --- a/src/tallies/tally_filter_sph_harm.F90 +++ b/src/tallies/tally_filter_sph_harm.F90 @@ -91,7 +91,7 @@ contains ! Calculate n-th order spherical harmonics for (u,v,w) num_nm = 2*n + 1 - rn(1:num_nm) = calc_rn(n, p % last_uvw) + call calc_rn(n, p % last_uvw, rn(1:num_nm)) ! Append matching (bin,weight) for each moment do i = 1, num_nm From 7fb5449ca11207d9547ab4a13955b0636212866d Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Mon, 30 Apr 2018 08:54:29 -0400 Subject: [PATCH 02/19] implemented python interface and tests of math.F90 --- openmc/capi/math.py | 13 ++- src/math.F90 | 9 +- src/mgxs_header.F90 | 5 +- src/scattdata_header.F90 | 3 +- tests/unit_tests/test_math.py | 180 ++++++++++++++++++++++++++++++++++ 5 files changed, 200 insertions(+), 10 deletions(-) create mode 100644 tests/unit_tests/test_math.py diff --git a/openmc/capi/math.py b/openmc/capi/math.py index d51858fd3b..701fc697f0 100644 --- a/openmc/capi/math.py +++ b/openmc/capi/math.py @@ -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 diff --git a/src/math.F90 b/src/math.F90 index 47e6acefb0..313fe94f1d 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -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 diff --git a/src/mgxs_header.F90 b/src/mgxs_header.F90 index 6eabefae3c..9ffb83815c 100644 --- a/src/mgxs_header.F90 +++ b/src/mgxs_header.F90 @@ -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 diff --git a/src/scattdata_header.F90 b/src/scattdata_header.F90 index 511e2a2376..a3108a2df6 100644 --- a/src/scattdata_header.F90 +++ b/src/scattdata_header.F90 @@ -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 diff --git a/tests/unit_tests/test_math.py b/tests/unit_tests/test_math.py new file mode 100644 index 0000000000..17c20fdb52 --- /dev/null +++ b/tests/unit_tests/test_math.py @@ -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) From 8e61254e6b5bc5281cfc8f151a35e81c842fb6a7 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Mon, 30 Apr 2018 15:33:36 -0400 Subject: [PATCH 03/19] Added C++ math functions, now to incorporate them one by one.... --- CMakeLists.txt | 2 + openmc/capi/math.py | 2 +- src/math.F90 | 87 +++++ src/math_functions.cpp | 781 +++++++++++++++++++++++++++++++++++++++++ src/math_functions.h | 94 +++++ 5 files changed, 965 insertions(+), 1 deletion(-) create mode 100644 src/math_functions.cpp create mode 100644 src/math_functions.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1797ae5d31..9f72d5b053 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -438,6 +438,8 @@ set(LIBOPENMC_FORTRAN_SRC set(LIBOPENMC_CXX_SRC src/error.h src/hdf5_interface.h + src/math_functions.h + src/math_functions.cpp src/random_lcg.cpp src/random_lcg.h src/surface.cpp diff --git a/openmc/capi/math.py b/openmc/capi/math.py index 701fc697f0..713ef98132 100644 --- a/openmc/capi/math.py +++ b/openmc/capi/math.py @@ -1,4 +1,4 @@ -from ctypes import (c_int, c_double, POINTER, c_void_p) +from ctypes import (c_int, c_double, POINTER) import numpy as np from numpy.ctypeslib import ndpointer diff --git a/src/math.F90 b/src/math.F90 index 313fe94f1d..76e9fc11a4 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -26,6 +26,93 @@ module math !=============================================================================== interface + + pure function t_percentile_cc(p, df) bind(C, name='t_percentile_c') & + result(t) + use ISO_C_BINDING + implicit none + real(C_DOUBLE), value, intent(in) :: p + integer(C_INT), value, intent(in) :: df + real(C_DOUBLE) :: t + end function t_percentile_cc + + pure function calc_pn_cc(n, x) bind(C, name='calc_pn_c') result(pnx) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: n + real(C_DOUBLE), value, intent(in) :: x + real(C_DOUBLE) :: pnx + end function calc_pn_cc + + subroutine calc_rn_cc(n, uvw, rn) bind(C, name='calc_rn_c') + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: n + real(C_DOUBLE), intent(in) :: uvw(3) + real(C_DOUBLE), intent(in) :: rn(2 * n + 1) + end subroutine calc_rn_cc + + pure function evaluate_legendre_cc(n, data, x) & + bind(C, name='evaluate_legendre_c') result(val) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: n + real(C_DOUBLE), intent(in) :: data(n) + real(C_DOUBLE), value, intent(in) :: x + real(C_DOUBLE) :: val + end function evaluate_legendre_cc + + subroutine rotate_angle_cc(uvw, mu, phi) bind(C, name='rotate_angle_c') + use ISO_C_BINDING + implicit none + real(C_DOUBLE), intent(inout) :: uvw(3) + real(C_DOUBLE), value, intent(in) :: mu + real(C_DOUBLE), value, intent(in) :: phi + end subroutine rotate_angle_cc + + function maxwell_spectrum_cc(T) bind(C, name='maxwell_spectrum_c') & + result(E_out) + use ISO_C_BINDING + implicit none + real(C_DOUBLE), value, intent(in) :: T + real(C_DOUBLE) :: E_out + end function maxwell_spectrum_cc + + function watt_spectrum_cc(a, b) bind(C, name='watt_spectrum_c') & + result(E_out) + use ISO_C_BINDING + implicit none + real(C_DOUBLE), value, intent(in) :: a + real(C_DOUBLE), value, intent(in) :: b + real(C_DOUBLE) :: E_out + end function watt_spectrum_cc + + function faddeeva_cc(z) bind(C, name='faddeeva_c') result(wv) + use ISO_C_BINDING + implicit none + complex(C_DOUBLE_COMPLEX), value, intent(in) :: z + complex(C_DOUBLE_COMPLEX) :: wv + end function faddeeva_cc + + function w_derivative_cc(z, order) bind(C, name='w_derivative_c') & + result(wv) + use ISO_C_BINDING + implicit none + complex(C_DOUBLE_COMPLEX), value, intent(in) :: z + integer(C_INT), value, intent(in) :: order + complex(C_DOUBLE_COMPLEX) :: wv + end function w_derivative_cc + + subroutine broaden_wmp_polynomials_cc(E, dopp, n, factors) & + bind(C, name='broaden_wmp_polynomials_c') + use ISO_C_BINDING + implicit none + real(C_DOUBLE), value, intent(in) :: E + real(C_DOUBLE), value, intent(in) :: dopp + integer(C_INT), value, intent(in) :: n + real(C_DOUBLE), intent(inout) :: factors(n) + end subroutine broaden_wmp_polynomials_cc + function faddeeva_w(z, relerr) bind(C, name='Faddeeva_w') result(w) use ISO_C_BINDING implicit none diff --git a/src/math_functions.cpp b/src/math_functions.cpp new file mode 100644 index 0000000000..3b645c7724 --- /dev/null +++ b/src/math_functions.cpp @@ -0,0 +1,781 @@ +#include "math_functions.h" + +namespace openmc { + + +//============================================================================== +// NORMAL_PERCENTILE calculates the percentile of the standard normal +// distribution with a specified probability level +//============================================================================== + +double __attribute__ ((const)) normal_percentile_c(double p) { + + // return gsl_cdf_ugaussian_Pinv(p); + + double z; + double q; + double r; + const double p_low = 0.02425; + const double a[6] = {-3.969683028665376e1, 2.209460984245205e2, + -2.759285104469687e2, 1.383577518672690e2, + -3.066479806614716e1, 2.506628277459239e0}; + const double b[5] = {-5.447609879822406e1, 1.615858368580409e2, + -1.556989798598866e2, 6.680131188771972e1, + -1.328068155288572e1}; + const double c[6] = {-7.784894002430293e-3, -3.223964580411365e-1, + -2.400758277161838, -2.549732539343734, + 4.374664141464968, 2.938163982698783}; + const double d[4] = {7.784695709041462e-3, 3.224671290700398e-1, + 2.445134137142996, 3.754408661907416}; + + // The rational approximation used here is from an unpublished work at + // http://home.online.no/~pjacklam/notes/invnorm/ + + if (p < p_low) { + // Rational approximation for lower region. + + q = std::sqrt(-2.0 * std::log(p)); + z = (((((c[0]*q + c[1])*q + c[2])*q + c[3])*q + c[4])*q + c[5]) / + ((((d[0]*q + d[1])*q + d[2])*q + d[3])*q + 1.0); + + } else if (p <= 1.0 - p_low) { + // Rational approximation for central region + + q = p - 0.5; + r = q * q; + z = (((((a[0]*r + a[1])*r + a[2])*r + a[3])*r + a[4])*r + a[5])*q / + (((((b[0]*r + b[1])*r + b[2])*r + b[3])*r + b[4])*r + 1.0); + + } else { + // Rational approximation for upper region + + q = std::sqrt(-2.0*std::log(1.0 - p)); + z = -(((((c[0]*q + c[1])*q + c[2])*q + c[3])*q + c[4])*q + c[5]) / + ((((d[0]*q + d[1])*q + d[2])*q + d[3])*q + 1.0); + } + + // Refinement based on Newton's method + + z = z - (0.5 * std::erfc(-z / std::sqrt(2.0)) - p) * std::sqrt(2.0 * M_PI) * + std::exp(0.5 * z * z); + + return z; + +} + +//============================================================================== +// T_PERCENTILE calculates the percentile of the Student's t distribution with +// a specified probability level and number of degrees of freedom +//============================================================================== + +double __attribute__ ((const)) t_percentile_c(double p, int df){ + + // return gsl_cdf_tdist_Pinv(p, static_cast df); + + double t; + double n; + double k; + double z; + double z2; + + if (df == 1) { + // For one degree of freedom, the t-distribution becomes a Cauchy + // distribution whose cdf we can invert directly + + t = std::tan(M_PI*(p - 0.5)); + } else if (df == 2) { + // For two degrees of freedom, the cdf is given by 1/2 + x/(2*sqrt(x^2 + + // 2)). This can be directly inverted to yield the solution below + + t = 2.0 * std::sqrt(2.0)*(p - 0.5) / + std::sqrt(1. - 4. * std::pow(p - 0.5, 2.)); + } else { + // This approximation is from E. Olusegun George and Meenakshi Sivaram, "A + // modification of the Fisher-Cornish approximation for the student t + // percentiles," Communication in Statistics - Simulation and Computation, + // 16 (4), pp. 1123-1132 (1987). + + n = static_cast(df); + k = 1. / (n - 2.); + z = normal_percentile_c(p); + z2 = z * z; + t = std::sqrt(n * k) * (z + (z2 - 3.) * z * k / 4. + ((5. * z2 - 56.) * z2 + + 75.) * z * k * k / 96. + (((z2 - 27.) * 3. * z2 + 417.) * z2 - 315.) * + z * k * k * k / 384.); + } + + return t; +} + +//============================================================================== +// CALC_PN calculates the n-th order Legendre polynomial at the value of x. +//============================================================================== + +double __attribute__ ((const)) calc_pn_c(int n, double x) { + + // return gsl_sf_legendre_Pl(l, x); + + double pnx; + + switch(n) { + case 0: + pnx = 1.; + break; + case 1: + pnx = x; + break; + case 2: + pnx = 1.5 * x * x - 0.5; + break; + case 3: + pnx = 2.5 * x * x * x - 1.5 * x; + break; + case 4: + pnx = 4.375 * std::pow(x, 4.) - 3.75 * x * x + 0.375; + break; + case 5: + pnx = 7.875 * std::pow(x, 5.) - 8.75 * x * x * x + 1.875 * x; + break; + case 6: + pnx = 14.4375 * std::pow(x, 6.) - 19.6875 * std::pow(x, 4.) + + 6.5625 * x * x - 0.3125; + break; + case 7: + pnx = 26.8125 * std::pow(x, 7.) - 43.3125 * std::pow(x, 5.) + + 19.6875 * x * x * x - 2.1875 * x; + break; + case 8: + pnx = 50.2734375 * std::pow(x, 8.) - 93.84375 * std::pow(x, 6.) + + 54.140625 * std::pow(x, 4.) - 9.84375 * x * x + 0.2734375; + break; + case 9: + pnx = 94.9609375 * std::pow(x, 9.) - 201.09375 * std::pow(x, 7.) + + 140.765625 * std::pow(x, 5.) - 36.09375 * x * x * x + 2.4609375 * x; + break; + case 10: + pnx = 180.42578125 * std::pow(x, 10.) - 427.32421875 * std::pow(x, 8.) + + 351.9140625 * std::pow(x, 6.) - 117.3046875 * std::pow(x, 4.) + + 13.53515625 * x * x - 0.24609375; + } + + return pnx; +} + +//============================================================================== +// CALC_RN calculates the n-th order spherical harmonics for a given angle +// (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) +//============================================================================== + +void calc_rn_c(int n, double uvw[3], double rn[]){ + double phi; + double w; + double w2m1; + + // rn[] is assumed to have already been allocated to the correct size + + // Store the cosine of the polar angle and the azimuthal angle + w = uvw[2]; + if (uvw[0] == 0.) { + phi = 0.; + } else { + phi = std::atan2(uvw[1], uvw[0]); + } + + // Store the shorthand of 1-w * w + w2m1 = 1. - w * w; + + // Now evaluate the spherical harmonics function depending on the order + // requested + switch (n) { + case 0: + // l = 0, m = 0 + rn[0] = 1.; + break; + case 1: + // l = 1, m = -1 + rn[0] = -(1.*std::sqrt(w2m1) * std::sin(phi)); + // l = 1, m = 0 + rn[1] = w; + // l = 1, m = 1 + rn[2] = -(1.*std::sqrt(w2m1) * std::cos(phi)); + break; + case 2: + // l = 2, m = -2 + rn[0] = 0.288675134594813 * (-3. * w * w + 3.) * std::sin(2. * phi); + // l = 2, m = -1 + rn[1] = -(1.73205080756888 * w*std::sqrt(w2m1) * std::sin(phi)); + // l = 2, m = 0 + rn[2] = 1.5 * w * w - 0.5; + // l = 2, m = 1 + rn[3] = -(1.73205080756888 * w*std::sqrt(w2m1) * std::cos(phi)); + // l = 2, m = 2 + rn[4] = 0.288675134594813 * (-3. * w * w + 3.) * std::cos(2. * phi); + break; + case 3: + // l = 3, m = -3 + rn[0] = -(0.790569415042095 * std::pow(w2m1, 1.5) * std::sin(3. * phi)); + // l = 3, m = -2 + rn[1] = 1.93649167310371 * w*(w2m1) * std::sin(2.*phi); + // l = 3, m = -1 + rn[2] = -(0.408248290463863*std::sqrt(w2m1)*((7.5)*w * w - 3./2.) * + std::sin(phi)); + // l = 3, m = 0 + rn[3] = 2.5 * std::pow(w, 3) - 1.5 * w; + // l = 3, m = 1 + rn[4] = -(0.408248290463863*std::sqrt(w2m1)*((7.5)*w * w - 3./2.) * + std::cos(phi)); + // l = 3, m = 2 + rn[5] = 1.93649167310371 * w*(w2m1) * std::cos(2.*phi); + // l = 3, m = 3 + rn[6] = -(0.790569415042095 * std::pow(w2m1, 1.5) * std::cos(3.* phi)); + break; + case 4: + // l = 4, m = -4 + rn[0] = 0.739509972887452 * (w2m1 * w2m1) * std::sin(4.0*phi); + // l = 4, m = -3 + rn[1] = -(2.09165006633519 * w * std::pow(w2m1, 1.5) * std::sin(3.* phi)); + // l = 4, m = -2 + rn[2] = 0.074535599249993 * (w2m1)*(52.5 * w * w - 7.5) * std::sin(2. *phi); + // l = 4, m = -1 + rn[3] = -(0.316227766016838*std::sqrt(w2m1)*(17.5 * std::pow(w, 3) - 7.5 * w) * + std::sin(phi)); + // l = 4, m = 0 + rn[4] = 4.375 * std::pow(w, 4) - 3.75 * w * w + 0.375; + // l = 4, m = 1 + rn[5] = -(0.316227766016838*std::sqrt(w2m1)*(17.5 * std::pow(w, 3) - 7.5*w) * + std::cos(phi)); + // l = 4, m = 2 + rn[6] = 0.074535599249993 * (w2m1)*(52.5*w * w - 7.5) * std::cos(2.*phi); + // l = 4, m = 3 + rn[7] = -(2.09165006633519 * w * std::pow(w2m1, 1.5) * std::cos(3.* phi)); + // l = 4, m = 4 + rn[8] = 0.739509972887452 * w2m1 * w2m1 * std::cos(4.0*phi); + break; + case 5: + // l = 5, m = -5 + rn[0] = -(0.701560760020114 * std::pow(w2m1, 2.5) * std::sin(5.0 * phi)); + // l = 5, m = -4 + rn[1] = 2.21852991866236 * w * w2m1 * w2m1 * std::sin(4.0 * phi); + // l = 5, m = -3 + rn[2] = -(0.00996023841111995 * std::pow(w2m1, 1.5) * + ((945.0 /2.)* w * w - 52.5) * std::sin(3.*phi)); + // l = 5, m = -2 + rn[3] = 0.0487950036474267 * (w2m1) + * ((315.0/2.)* std::pow(w, 3) - 52.5 * w) * std::sin(2.*phi); + // l = 5, m = -1 + rn[4] = -(0.258198889747161*std::sqrt(w2m1) * + (39.375 * std::pow(w, 4) - 105.0/4.0 * w * w + 15.0/8.0) * std::sin(phi)); + // l = 5, m = 0 + rn[5] = 7.875 * std::pow(w, 5) - 8.75 * std::pow(w, 3) + 1.875 * w; + // l = 5, m = 1 + rn[6] = -(0.258198889747161 * std::sqrt(w2m1)* + (39.375 * std::pow(w, 4) - 105.0/4.0 * w * w + 15.0/8.0) * std::cos(phi)); + // l = 5, m = 2 + rn[7] = 0.0487950036474267 * (w2m1) * + ((315.0 / 2.) * std::pow(w, 3) - 52.5*w) * std::cos(2.*phi); + // l = 5, m = 3 + rn[8] = -(0.00996023841111995 * std::pow(w2m1, 1.5) * + ((945.0 / 2.) * w * w - 52.5) * std::cos(3.*phi)); + // l = 5, m = 4 + rn[9] = 2.21852991866236 * w * w2m1 * w2m1 * std::cos(4.0*phi); + // l = 5, m = 5 + rn[10] = -(0.701560760020114 * std::pow(w2m1, 2.5) * std::cos(5.0* phi)); + break; + case 6: + // l = 6, m = -6 + rn[0] = 0.671693289381396 * std::pow(w2m1, 3) * std::sin(6.0*phi); + // l = 6, m = -5 + rn[1] = -(2.32681380862329 * w*std::pow(w2m1, 2.5) * std::sin(5.0*phi)); + // l = 6, m = -4 + rn[2] = 0.00104990131391452 * w2m1 * w2m1 * + ((10395.0/2.) * w * w - 945.0/2.) * std::sin(4.0 * phi); + // l = 6, m = -3 + rn[3] = -(0.00575054632785295 * std::pow(w2m1, 1.5) * + ((3465.0/2.) * std::pow(w, 3) - 945.0/2.*w) * std::sin(3.*phi)); + // l = 6, m = -2 + rn[4] = 0.0345032779671177 * (w2m1) * + ((3465.0/8.0)* std::pow(w, 4) - 945.0/4.0 * w * w + 105.0/8.0) * + std::sin(2. * phi); + // l = 6, m = -1 + rn[5] = -(0.218217890235992*std::sqrt(w2m1) * + ((693.0/8.0)* std::pow(w, 5)- 315.0/4.0 * std::pow(w, 3) + (105.0/8.0)*w) * + std::sin(phi)); + // l = 6, m = 0 + rn[6] = 14.4375 * std::pow(w, 6) - 19.6875 * std::pow(w, 4) + 6.5625 * w * w - + 0.3125; + // l = 6, m = 1 + rn[7] = -(0.218217890235992*std::sqrt(w2m1) * + ((693.0/8.0)* std::pow(w, 5)- 315.0/4.0 * std::pow(w, 3) + (105.0/8.0)*w) * + std::cos(phi)); + // l = 6, m = 2 + rn[8] = 0.0345032779671177 * w2m1 * + ((3465.0/8.0)* std::pow(w, 4) -945.0/4.0 * w * w + 105.0/8.0) * + std::cos(2.*phi); + // l = 6, m = 3 + rn[9] = -(0.00575054632785295 * std::pow(w2m1, 1.5) * + ((3465.0/2.) * std::pow(w, 3) - 945.0/2.*w) * std::cos(3.*phi)); + // l = 6, m = 4 + rn[10] = 0.00104990131391452 * w2m1 * w2m1 * + ((10395.0/2.)*w * w - 945.0/2.) * std::cos(4.0*phi); + // l = 6, m = 5 + rn[11] = -(2.32681380862329 * w * std::pow(w2m1, 2.5) * std::cos(5.0*phi)); + // l = 6, m = 6 + rn[12] = 0.671693289381396 * std::pow(w2m1, 3) * std::cos(6.0*phi); + break; + case 7: + // l = 7, m = -7 + rn[0] = -(0.647259849287749 * std::pow(w2m1, 3.5) * std::sin(7.0*phi)); + // l = 7, m = -6 + rn[1] = 2.42182459624969 * w*std::pow(w2m1, 3) * std::sin(6.0*phi); + // l = 7, m = -5 + rn[2] = -(9.13821798555235e-5*std::pow(w2m1, 2.5) * + ((135135.0/2.)*w * w - 10395.0/2.) * std::sin(5.0*phi)); + // l = 7, m = -4 + rn[3] = 0.000548293079133141 * w2m1 * w2m1 * + ((45045.0/2.)*std::pow(w, 3) - 10395.0/2.*w) * std::sin(4.0*phi); + // l = 7, m = -3 + rn[4] = -(0.00363696483726654 * std::pow(w2m1, 1.5) * + ((45045.0/8.0)* std::pow(w, 4) - 10395.0/4.0 * w * w + 945.0/8.0) * + std::sin(3.*phi)); + // l = 7, m = -2 + rn[5] = 0.025717224993682 * (w2m1) * + ((9009.0/8.0)* std::pow(w, 5) -3465.0/4.0 * std::pow(w, 3) + (945.0/8.0)*w) * + std::sin(2.*phi); + // l = 7, m = -1 + rn[6] = -(0.188982236504614*std::sqrt(w2m1) * + ((3003.0/16.0)* std::pow(w, 6) - 3465.0/16.0 * std::pow(w, 4) + + (945.0/16.0)*w * w - 35.0/16.0) * std::sin(phi)); + // l = 7, m = 0 + rn[7] = 26.8125 * std::pow(w, 7) - 43.3125 * std::pow(w, 5) + 19.6875 * std::pow(w, 3) - + 2.1875 * w; + // l = 7, m = 1 + rn[8] = -(0.188982236504614*std::sqrt(w2m1) * ((3003.0/16.0) * std::pow(w, 6) - + 3465.0/16.0 * std::pow(w, 4) + (945.0/16.0)*w * w - 35.0/16.0) * std::cos(phi)); + // l = 7, m = 2 + rn[9] = 0.025717224993682 * (w2m1) * ((9009.0/8.0)* std::pow(w, 5) - + 3465.0/4.0 * std::pow(w, 3) + (945.0/8.0)*w) * std::cos(2.*phi); + // l = 7, m = 3 + rn[10] = -(0.00363696483726654 * std::pow(w2m1, 1.5) * + ((45045.0/8.0)* std::pow(w, 4) - 10395.0/4.0 * w * w + 945.0/8.0) * + std::cos(3.*phi)); + // l = 7, m = 4 + rn[11] = 0.000548293079133141 * w2m1 * w2m1 * + ((45045.0/2.)*std::pow(w, 3) - 10395.0/2.*w) * std::cos(4.0*phi); + // l = 7, m = 5 + rn[12] = -(9.13821798555235e-5*std::pow(w2m1, 2.5) * + ((135135.0/2.)*w * w - 10395.0/2.) * std::cos(5.0*phi)); + // l = 7, m = 6 + rn[13] = 2.42182459624969 * w*std::pow(w2m1, 3) * std::cos(6.0*phi); + // l = 7, m = 7 + rn[14] = -(0.647259849287749 * std::pow(w2m1, 3.5) * std::cos(7.0*phi)); + break; + case 8: + // l = 8, m = -8 + rn[0] = 0.626706654240044 * std::pow(w2m1, 4) * std::sin(8.0*phi); + // l = 8, m = -7 + rn[1] = -(2.50682661696018 * w*std::pow(w2m1, 3.5) * std::sin(7.0*phi)); + // l = 8, m = -6 + rn[2] = 6.77369783729086e-6*std::pow(w2m1, 3)* + ((2027025.0/2.)*w * w - 135135.0/2.) * std::sin(6.0*phi); + // l = 8, m = -5 + rn[3] = -(4.38985792528482e-5*std::pow(w2m1, 2.5) * + ((675675.0/2.)*std::pow(w, 3) - 135135.0/2.*w) * std::sin(5.0*phi)); + // l = 8, m = -4 + rn[4] = 0.000316557156832328 * w2m1 * w2m1 * + ((675675.0/8.0)* std::pow(w, 4) - 135135.0/4.0 * w * w + 10395.0/8.0) * + std::sin(4.0*phi); + // l = 8, m = -3 + rn[5] = -(0.00245204119306875 * std::pow(w2m1, 1.5) * ((135135.0/8.0) * + std::pow(w, 5) - 45045.0/4.0 * std::pow(w, 3) + (10395.0/8.0)*w) * std::sin(3.*phi)); + // l = 8, m = -2 + rn[6] = 0.0199204768222399 * (w2m1) * + ((45045.0/16.0)* std::pow(w, 6)- 45045.0/16.0 * std::pow(w, 4) + + (10395.0/16.0)*w * w - 315.0/16.0) * std::sin(2.*phi); + // l = 8, m = -1 + rn[7] = -(0.166666666666667*std::sqrt(w2m1) * + ((6435.0/16.0)* std::pow(w, 7) - 9009.0/16.0 * std::pow(w, 5) + + (3465.0/16.0)*std::pow(w, 3) - 315.0/16.0 * w) * std::sin(phi)); + // l = 8, m = 0 + rn[8] = 50.2734375 * std::pow(w, 8) - 93.84375 * std::pow(w, 6) + 54.140625 * + std::pow(w, 4) - 9.84375 * w * w + 0.2734375; + // l = 8, m = 1 + rn[9] = -(0.166666666666667*std::sqrt(w2m1) * + ((6435.0/16.0)* std::pow(w, 7) - 9009.0/16.0 * std::pow(w, 5) + + (3465.0/16.0)*std::pow(w, 3) - 315.0/16.0 * w) * std::cos(phi)); + // l = 8, m = 2 + rn[10] = 0.0199204768222399 * (w2m1)*((45045.0/16.0)* std::pow(w, 6)- + 45045.0/16.0 * std::pow(w, 4) + (10395.0/16.0)*w * w - + 315.0/16.0) * std::cos(2.*phi); + // l = 8, m = 3 + rn[11] = -(0.00245204119306875 * std::pow(w2m1, 1.5)* + ((135135.0/8.0) * std::pow(w, 5) - 45045.0/4.0 * std::pow(w, 3) + + (10395.0/8.0)*w) * std::cos(3.*phi)); + // l = 8, m = 4 + rn[12] = 0.000316557156832328 * w2m1 * w2m1*((675675.0/8.0)* std::pow(w, 4) - + 135135.0/4.0 * w * w + 10395.0/8.0) * std::cos(4.0*phi); + // l = 8, m = 5 + rn[13] = -(4.38985792528482e-5*std::pow(w2m1, 2.5)*((675675.0/2.)*std::pow(w, 3) - + 135135.0/2.*w) * std::cos(5.0*phi)); + // l = 8, m = 6 + rn[14] = 6.77369783729086e-6*std::pow(w2m1, 3)*((2027025.0/2.)*w * w - + 135135.0/2.) * std::cos(6.0*phi); + // l = 8, m = 7 + rn[15] = -(2.50682661696018 * w*std::pow(w2m1, 3.5) * std::cos(7.0*phi)); + // l = 8, m = 8 + rn[16] = 0.626706654240044 * std::pow(w2m1, 4) * std::cos(8.0*phi); + break; + case 9: + // l = 9, m = -9 + rn[0] = -(0.609049392175524 * std::pow(w2m1, 4.5) * std::sin(9.0 * phi)); + // l = 9, m = -8 + rn[1] = 2.58397773170915 * w*std::pow(w2m1, 4) * std::sin(8.0 * phi); + // l = 9, m = -7 + rn[2] = -(4.37240315267812e-7*std::pow(w2m1, 3.5) * + ((34459425.0/2.)*w * w - 2027025.0/2.) * std::sin(7.0 * phi)); + // l = 9, m = -6 + rn[3] = 3.02928976464514e-6*std::pow(w2m1, 3)* + ((11486475.0/2.)*std::pow(w, 3) - 2027025.0/2.*w) * std::sin(6.0 * phi); + // l = 9, m = -5 + rn[4] = -(2.34647776186144e-5*std::pow(w2m1, 2.5) * + ((11486475.0/8.0)* std::pow(w, 4) - 2027025.0 / 4.0 * w * w + + 135135.0/8.0) * std::sin(5.0 * phi)); + // l = 9, m = -4 + rn[5] = 0.000196320414650061 * w2m1 * w2m1*((2297295.0/8.0)* std::pow(w, 5) - + 675675.0/4.0 * std::pow(w, 3) + (135135.0/8.0)*w) * std::sin(4.0*phi); + // l = 9, m = -3 + rn[6] = -(0.00173385495536766 * std::pow(w2m1, 1.5) * + ((765765.0/16.0)* std::pow(w, 6) - 675675.0/16.0 * std::pow(w, 4) + + (135135.0/16.0)*w * w - 3465.0/16.0) * std::sin(3. * phi)); + // l = 9, m = -2 + rn[7] = 0.0158910431540932 * (w2m1)*((109395.0/16.0)* std::pow(w, 7)- + 135135.0/16.0 * std::pow(w, 5) + (45045.0/16.0)*std::pow(w, 3) - + 3465.0/16.0 * w) * std::sin(2. * phi); + // l = 9, m = -1 + rn[8] = -(0.149071198499986*std::sqrt(w2m1)*((109395.0/128.0)* std::pow(w, 8) - + 45045.0/32.0 * std::pow(w, 6) + (45045.0/64.0)* std::pow(w, 4) - + 3465.0/32.0 * w * w + 315.0/128.0) * std::sin(phi)); + // l = 9, m = 0 + rn[9] = 94.9609375 * std::pow(w, 9) - 201.09375 * std::pow(w, 7) + + 140.765625 * std::pow(w, 5)- 36.09375 * std::pow(w, 3) + 2.4609375 * w; + // l = 9, m = 1 + rn[10] = -(0.149071198499986*std::sqrt(w2m1)*((109395.0/128.0)* std::pow(w, 8) - + 45045.0/32.0 * std::pow(w, 6) + (45045.0/64.0)* std::pow(w, 4) - + 3465.0/32.0 * w * w + 315.0/128.0) * std::cos(phi)); + // l = 9, m = 2 + rn[11] = 0.0158910431540932 * (w2m1)*((109395.0/16.0)* std::pow(w, 7) - + 135135.0/16.0 * std::pow(w, 5) + (45045.0/16.0)*std::pow(w, 3) - + 3465.0/ 16.0 * w) * std::cos(2. * phi); + // l = 9, m = 3 + rn[12] = -(0.00173385495536766 * std::pow(w2m1, 1.5)*((765765.0/16.0) * + std::pow(w, 6) - 675675.0/16.0 * std::pow(w, 4) + + (135135.0/16.0)* w * w - 3465.0/16.0)* std::cos(3. * phi)); + // l = 9, m = 4 + rn[13] = 0.000196320414650061 * w2m1 * w2m1*((2297295.0/8.0) * std::pow(w, 5) - + 675675.0/4.0 * std::pow(w, 3) + (135135.0/8.0)*w) * std::cos(4.0 * phi); + // l = 9, m = 5 + rn[14] = -(2.34647776186144e-5*std::pow(w2m1, 2.5)*((11486475.0/8.0) * + std::pow(w, 4) - 2027025.0/4.0 * w * w + 135135.0/8.0) * + std::cos(5.0 * phi)); + // l = 9, m = 6 + rn[15] = 3.02928976464514e-6*std::pow(w2m1, 3)*((11486475.0/2.)*std::pow(w, 3) - + 2027025.0/2. * w) * std::cos(6.0 * phi); + // l = 9, m = 7 + rn[16] = -(4.37240315267812e-7*std::pow(w2m1, 3.5)* + ((34459425.0/2.) * w * w - 2027025.0/2.) * std::cos(7.0 * phi)); + // l = 9, m = 8 + rn[17] = 2.58397773170915 * w*std::pow(w2m1, 4) * std::cos(8.0 * phi); + // l = 9, m = 9 + rn[18] = -(0.609049392175524 * std::pow(w2m1, 4.5) * std::cos(9.0 * phi)); + break; + case 10: + // l = 10, m = -10 + rn[0] = 0.593627917136573 * std::pow(w2m1, 5) * std::sin(10.0 * phi); + // l = 10, m = -9 + rn[1] = -(2.65478475211798 * w * std::pow(w2m1, 4.5) * std::sin(9.0 * phi)); + // l = 10, m = -8 + rn[2] = 2.49953651452314e-8 * std::pow(w2m1, 4) * + ((654729075.0/2.) * w * w - 34459425.0/2.) * std::sin(8.0 * phi); + // l = 10, m = -7 + rn[3] = -(1.83677671621093e-7*std::pow(w2m1, 3.5)* + ((218243025.0/2.)*std::pow(w, 3) - 34459425.0/2.*w) * + std::sin(7.0 * phi)); + // l = 10, m = -6 + rn[4] = 1.51464488232257e-6*std::pow(w2m1, 3)*((218243025.0/8.0)* std::pow(w, 4) - + 34459425.0/4.0 * w * w + 2027025.0/8.0) * std::sin(6.0 * phi); + // l = 10, m = -5 + rn[5] = -(1.35473956745817e-5*std::pow(w2m1, 2.5)* + ((43648605.0/8.0)* std::pow(w, 5) - 11486475.0/4.0 * std::pow(w, 3) + + (2027025.0/8.0)*w) * std::sin(5.0 * phi)); + // l = 10, m = -4 + rn[6] = 0.000128521880085575 * w2m1 * w2m1*((14549535.0/16.0)* std::pow(w, 6) - + 11486475.0/16.0 * std::pow(w, 4) + (2027025.0/16.0)*w * w - + 45045.0/16.0) * std::sin(4.0 * phi); + // l = 10, m = -3 + rn[7] = -(0.00127230170115096 * std::pow(w2m1, 1.5)* + ((2078505.0/16.0)* std::pow(w, 7) - 2297295.0/16.0 * std::pow(w, 5) + + (675675.0/16.0)*std::pow(w, 3) - 45045.0/16.0 * w) * std::sin(3. * phi)); + // l = 10, m = -2 + rn[8] = 0.012974982402692 * (w2m1)*((2078505.0/128.0)* std::pow(w, 8) - + 765765.0/32.0 * std::pow(w, 6) + (675675.0/64.0)* std::pow(w, 4) - + 45045.0/32.0 * w * w + 3465.0/128.0) * std::sin(2. * phi); + // l = 10, m = -1 + rn[9] = -(0.134839972492648*std::sqrt(w2m1)*((230945.0/128.0)* std::pow(w, 9) - + 109395.0/32.0 * std::pow(w, 7) + (135135.0/64.0)* std::pow(w, 5) - + 15015.0/32.0 * std::pow(w, 3) + (3465.0/128.0)*w) * std::sin(phi)); + // l = 10, m = 0 + rn[10] = 180.42578125 * std::pow(w, 10) - 427.32421875 * std::pow(w, 8) +351.9140625 + * std::pow(w, 6) - 117.3046875 * std::pow(w, 4) + 13.53515625 * w * w -0.24609375; + // l = 10, m = 1 + rn[11] = -(0.134839972492648*std::sqrt(w2m1)*((230945.0/128.0)* std::pow(w, 9) - + 109395.0/32.0 * std::pow(w, 7) + (135135.0/64.0)* std::pow(w, 5) -15015.0/ + 32.0 * std::pow(w, 3) + (3465.0/128.0)*w) * std::cos(phi)); + // l = 10, m = 2 + rn[12] = 0.012974982402692 * (w2m1)*((2078505.0/128.0)* std::pow(w, 8) - + 765765.0/32.0 * std::pow(w, 6) + (675675.0/64.0)* std::pow(w, 4) - + 45045.0/32.0 * w * w + 3465.0/128.0) * std::cos(2. * phi); + // l = 10, m = 3 + rn[13] = -(0.00127230170115096 * std::pow(w2m1, 1.5)* + ((2078505.0/16.0)* std::pow(w, 7) - 2297295.0/16.0 * std::pow(w, 5) + + (675675.0/16.0)*std::pow(w, 3) - 45045.0/16.0 * w) * std::cos(3. * phi)); + // l = 10, m = 4 + rn[14] = 0.000128521880085575 * w2m1 * w2m1*((14549535.0/16.0)* std::pow(w, 6) - + 11486475.0/16.0 * std::pow(w, 4) + (2027025.0/16.0) * w * w - + 45045.0/16.0) * std::cos(4.0 * phi); + // l = 10, m = 5 + rn[15] = -(1.35473956745817e-5*std::pow(w2m1, 2.5)* + ((43648605.0/8.0)* std::pow(w, 5) - 11486475.0/4.0 * std::pow(w, 3) + + (2027025.0/8.0)*w) * std::cos(5.0 * phi)); + // l = 10, m = 6 + rn[16] = 1.51464488232257e-6*std::pow(w2m1, 3)*((218243025.0/8.0)* std::pow(w, 4) - + 34459425.0/4.0 * w * w + 2027025.0/8.0) * std::cos(6.0 * phi); + // l = 10, m = 7 + rn[17] = -(1.83677671621093e-7*std::pow(w2m1, 3.5) * + ((218243025.0/2.)*std::pow(w, 3) - 34459425.0/2.*w) * std::cos(7.0 * phi)); + // l = 10, m = 8 + rn[18] = 2.49953651452314e-8*std::pow(w2m1, 4)* + ((654729075.0/2.)*w * w - 34459425.0/2.) * std::cos(8.0 * phi); + // l = 10, m = 9 + rn[19] = -(2.65478475211798 * w*std::pow(w2m1, 4.5) * std::cos(9.0 * phi)); + // l = 10, m = 10 + rn[20] = 0.593627917136573 * std::pow(w2m1, 5) * std::cos(10.0 * phi); + } +} + +//============================================================================== +// EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients +// and the value of x +//============================================================================== + +double __attribute__ ((const)) evaluate_legendre_c(int n, double data[], + double x) { + double val; + + val = 0.5 * data[0]; + for (int l = 1; l < n; l++) { + val += (static_cast(l) + 0.5) * data[l] * calc_pn_c(l, x); + } + +} + +//============================================================================== +// ROTATE_ANGLE rotates direction std::cosines through a polar angle whose +// cosine is mu and through an azimuthal angle sampled uniformly. Note that +// this is done with direct sampling rather than rejection as is done in MCNP +// and SERPENT. +//============================================================================== + +void rotate_angle_c(double uvw[3], double mu, double phi) { + double phi_; // azimuthal angle + double sinphi; // std::sine of azimuthal angle + double cosphi; // cosine of azimuthal angle + double a; // sqrt(1 - mu^2) + double b; // sqrt(1 - w^2) + double u0; // original std::cosine in x direction + double v0; // original std::cosine in y direction + double w0; // original std::cosine in z direction + + // Copy original directional std::cosines + u0 = uvw[0]; + v0 = uvw[1]; + w0 = uvw[2]; + + // Sample azimuthal angle in [0,2pi) if none provided + if (phi != -10.) { + phi_ = phi; + } else { + phi_ = 2. * M_PI * prn(); + } + + // Precompute factors to save flops + sinphi = std::sin(phi_); + cosphi = std::cos(phi_); + a = std::sqrt(std::max(0., 1. - mu * mu)); + b = std::sqrt(std::max(0., 1. - w0 * w0)); + + // Need to treat special case where sqrt(1 - w**2) is close to zero by + // expanding about the v component rather than the w component + if (b > 1e-10) { + uvw[0] = mu * u0 + a * (u0 * w0 * cosphi - v0 * sinphi) / b; + uvw[1] = mu * v0 + a * (v0 * w0 * cosphi + u0 * sinphi) / b; + uvw[2] = mu * w0 - a * b * cosphi; + } else { + b = std::sqrt(1. - v0 * v0); + uvw[0] = mu * u0 + a * (u0 * v0 * cosphi + w0 * sinphi) / b; + uvw[1] = mu * v0 - a * b * cosphi; + uvw[2] = mu * w0 + a * (v0 * w0 * cosphi - u0 * sinphi) / b; + } +} + +//============================================================================== +// MAXWELL_SPECTRUM samples an energy from the Maxwell fission distribution +// based on a direct sampling scheme. The probability distribution function for +// a Maxwellian is given as p(x) = 2/(T*sqrt(pi))*sqrt(x/T)*exp(-x/T). +// This PDF can be sampled using rule C64 in the Monte Carlo Sampler LA-9721-MS. +//============================================================================== + +double maxwell_spectrum_c(double T) { + double E_out; // Sampled Energy + + double r1; + double r2; + double r3; // random numbers + double c; // cosine of pi/2*r3 + + r1 = prn(); + r2 = prn(); + r3 = prn(); + + // determine cosine of pi/2*r + c = std::cos(M_PI / 2. * r3); + + // determine outgoing energy + E_out = -T * (std::log(r1) + std::log(r2) * c * c); + + return E_out; +} + +//============================================================================== +// WATT_SPECTRUM samples the outgoing energy from a Watt energy-dependent +// fission spectrum. Although fitted parameters exist for many nuclides, +// generally the continuous tabular distributions (LAW 4) should be used in +// lieu of the Watt spectrum. This direct sampling scheme is an unpublished +// scheme based on the original Watt spectrum derivation (See F. Brown's +// MC lectures). +//============================================================================== + +double watt_spectrum_c(double a, double b) { + double E_out; // Sampled Energy + double w; // sampled from Maxwellian + + w = maxwell_spectrum_c(a); + E_out = w + 0.25 * a * a * b + (2. * prn() - 1.) * std::sqrt(a * a * b * w); + + return E_out; +} + +//============================================================================== +// FADDEEVA the Faddeeva function, using Stephen Johnson's implementation +//============================================================================== + +// std::complex faddeeva_c(std::complex z) { +// std::complex wv; // The resultant w(z) value +// double relerr; // Target relative error in the inner loop of MIT Faddeeva + +// // Technically, the value we want is given by the equation: +// // w(z) = I/Pi * Integrate[Exp[-t^2]/(z-t), {t, -Infinity, Infinity}] +// // as shown in Equation 63 from Hwang, R. N. "A rigorous pole +// // representation of multilevel cross sections and its practical +// // applications." Nuclear Science and Engineering 96.3 (1987): 192-209. +// // +// // The MIT Faddeeva function evaluates w(z) = exp(-z^2)erfc(-iz). These +// // two forms of the Faddeeva function are related by a transformation. +// // +// // If we call the integral form w_int, and the function form w_fun: +// // For imag(z) > 0, w_int(z) = w_fun(z) +// // For imag(z) < 0, w_int(z) = -conjg(w_fun(conjg(z))) + +// // Note that faddeeva_w will interpret zero as machine epsilon + +// relerr = 0.; +// if (z.imag() > 0.) { +// wv = Faddeeva::w(z, relerr); +// } else { +// wv = -std::conj(Faddeeva::w(std::conj(z), relerr)); +// } + +// return wv; +// } + +// std::complex w_derivative_c(std::complex z, int order){ +// std::complex wv; // The resultant w(z) value + +// const std::complex twoi_sqrtpi(0.0, 2.0 / std::sqrt(M_PI)); + +// switch(order) { +// case 0: +// wv = faddeeva_c(z); +// break; +// case 1: +// wv = -2. * z * faddeeva_c(z) + twoi_sqrtpi; +// break; +// default: +// wv = -2. * z * w_derivative_c(z, order - 1) - 2. * (order - 1) * +// w_derivative_c(z, order - 2); +// } + +// return wv; +// } + +//============================================================================== +// BROADEN_WMP_POLYNOMIALS Doppler broadens the windowed multipole curvefit. +// The curvefit is a polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E) ... +//============================================================================== + +void broaden_wmp_polynomials_c(double E, double dopp, int n, double factors[]) { + // Factors is already pre-allocated + + double sqrtE; // sqrt(energy) + double beta; // sqrt(atomic weight ratio * E / kT) + double half_inv_dopp2; // 0.5 / dopp**2 + double quarter_inv_dopp4; // 0.25 / dopp**4 + double erf_beta; // error function of beta + double exp_m_beta2; // exp(-beta**2) + int i; + + sqrtE = std::sqrt(E); + beta = sqrtE * dopp; + half_inv_dopp2 = 0.5 / (dopp * dopp); + quarter_inv_dopp4 = half_inv_dopp2 * half_inv_dopp2; + + if (beta > 6.0) { + // Save time, ERF(6) is 1 to machine precision. + // beta/sqrtpi*exp(-beta**2) is also approximately 1 machine epsilon. + erf_beta = 1.; + exp_m_beta2 = 0.; + } else { + erf_beta = std::erf(beta); + exp_m_beta2 = std::exp(-beta * beta); + } + + // Assume that, for sure, we'll use a second order (1/E, 1/V, const) + // fit, and no less. + + factors[0] = erf_beta / E; + factors[1] = 1. / sqrtE; + factors[2] = factors[0] * (half_inv_dopp2 + E) + exp_m_beta2 / + (beta * std::sqrt(M_PI)); + + // Perform recursive broadening of high order components + for (i = 0; i < n - 3; i++) { + if (i != 0) { + factors[i + 3] = -factors[i - 1] * (i - 1.) * i * quarter_inv_dopp4 + + factors[i + 1] * (E + (1. + 2. * i) * half_inv_dopp2); + } else { + // Although it's mathematically identical, factors[0] will contain + // nothing, and we don't want to have to worry about memory. + factors[i + 3] = factors[i + 1]*(E + (1. + 2. * i) * half_inv_dopp2); + } + } +} + +} // namespace openmc diff --git a/src/math_functions.h b/src/math_functions.h new file mode 100644 index 0000000000..aa048e5f67 --- /dev/null +++ b/src/math_functions.h @@ -0,0 +1,94 @@ +#ifndef MATH_FUNCTIONS_H +#define MATH_FUNCTIONS_H + +#include +#include +#include + +#include "random_lcg.h" +// #include "faddeeva/Faddeeva.hh" + + +namespace openmc { + + +//============================================================================== +// NORMAL_PERCENTILE calculates the percentile of the standard normal +// distribution with a specified probability level +//============================================================================== + +extern "C" double normal_percentile_c(double p) __attribute__ ((const)); + +//============================================================================== +// T_PERCENTILE calculates the percentile of the Student's t distribution with +// a specified probability level and number of degrees of freedom +//============================================================================== + +extern "C" double t_percentile_c(double p, int df) __attribute__ ((const)); + +//============================================================================== +// CALC_PN calculates the n-th order Legendre polynomial at the value of x. +//============================================================================== + +extern "C" double calc_pn_c(int n, double x) __attribute__ ((const)); + +//============================================================================== +// CALC_RN calculates the n-th order spherical harmonics for a given angle +// (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) +//============================================================================== + +extern "C" void calc_rn_c(int n, double uvw[3], double rn[]); + +//============================================================================== +// EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients +// and the value of x +//============================================================================== + +extern "C" double evaluate_legendre_c(int n, double data[], double x) + __attribute__ ((const)); + +//============================================================================== +// ROTATE_ANGLE rotates direction cosines through a polar angle whose cosine is +// mu and through an azimuthal angle sampled uniformly. Note that this is done +// with direct sampling rather than rejection as is done in MCNP and SERPENT. +//============================================================================== + +extern "C" void rotate_angle_c(double uvw[3], double mu, double phi = -10.); + +//============================================================================== +// MAXWELL_SPECTRUM samples an energy from the Maxwell fission distribution +// based on a direct sampling scheme. The probability distribution function for +// a Maxwellian is given as p(x) = 2/(T*sqrt(pi))*sqrt(x/T)*exp(-x/T). +// This PDF can be sampled using rule C64 in the Monte Carlo Sampler LA-9721-MS. +//============================================================================== + +extern "C" double maxwell_spectrum_c(double T); + +//============================================================================== +// WATT_SPECTRUM samples the outgoing energy from a Watt energy-dependent +// fission spectrum. Although fitted parameters exist for many nuclides, +// generally the continuous tabular distributions (LAW 4) should be used in +// lieu of the Watt spectrum. This direct sampling scheme is an unpublished +// scheme based on the original Watt spectrum derivation (See F. Brown's +// MC lectures). +//============================================================================== + +extern "C" double watt_spectrum_c(double a, double b); + +//============================================================================== +// FADDEEVA the Faddeeva function, using Stephen Johnson's implementation +//============================================================================== + +// extern "C" std::complex faddeeva_c(std::complex z); + +// extern "C" std::complex w_derivative_c(std::complex z, int order); + +//============================================================================== +// BROADEN_WMP_POLYNOMIALS Doppler broadens the windowed multipole curvefit. +// The curvefit is a polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E) ... +//============================================================================== + +extern "C" void broaden_wmp_polynomials_c(double E, double dopp, int n, double factors[]); + +} // namespace openmc +#endif // MATH_FUNCTIONS_H \ No newline at end of file From 0e3194315a6323a3df88bc59861b9fe3c6f0007a Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Mon, 30 Apr 2018 15:41:20 -0400 Subject: [PATCH 04/19] C++ized t_percentile --- openmc/capi/math.py | 4 +- src/api.F90 | 1 - src/math.F90 | 94 +---------------------------------- tests/unit_tests/test_math.py | 16 +++--- 4 files changed, 11 insertions(+), 104 deletions(-) diff --git a/openmc/capi/math.py b/openmc/capi/math.py index 713ef98132..3d22ccdac6 100644 --- a/openmc/capi/math.py +++ b/openmc/capi/math.py @@ -5,8 +5,8 @@ from numpy.ctypeslib import ndpointer from . import _dll -_dll.normal_percentile.restype = c_double -_dll.normal_percentile.argtypes = [POINTER(c_double)] +# _dll.normal_percentile.restype = c_double +# _dll.normal_percentile.argtypes = [POINTER(c_double)] _dll.t_percentile.restype = c_double _dll.t_percentile.argtypes = [POINTER(c_double), POINTER(c_int)] _dll.calc_pn.restype = c_double diff --git a/src/api.F90 b/src/api.F90 index 30aca68343..388659f4ab 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -98,7 +98,6 @@ module openmc_api public :: openmc_tally_set_nuclides public :: openmc_tally_set_scores public :: openmc_tally_set_type - public :: normal_percentile public :: t_percentile public :: calc_pn public :: calc_rn diff --git a/src/math.F90 b/src/math.F90 index 76e9fc11a4..40c2d09dc7 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -7,7 +7,6 @@ module math implicit none private - public :: normal_percentile public :: t_percentile public :: calc_pn public :: calc_rn @@ -124,65 +123,6 @@ module math contains -!=============================================================================== -! NORMAL_PERCENTILE calculates the percentile of the standard normal -! distribution with a specified probability level -!=============================================================================== - - pure function normal_percentile(p) result(z) bind(C) - - real(C_DOUBLE), intent(in) :: p ! probability level - real(C_DOUBLE) :: z ! corresponding z-value - - real(C_DOUBLE) :: q - real(C_DOUBLE) :: r - real(C_DOUBLE), parameter :: p_low = 0.02425_8 - real(C_DOUBLE), parameter :: a(6) = (/ & - -3.969683028665376e1_8, 2.209460984245205e2_8, -2.759285104469687e2_8, & - 1.383577518672690e2_8, -3.066479806614716e1_8, 2.506628277459239e0_8 /) - real(C_DOUBLE), parameter :: b(5) = (/ & - -5.447609879822406e1_8, 1.615858368580409e2_8, -1.556989798598866e2_8, & - 6.680131188771972e1_8, -1.328068155288572e1_8 /) - real(C_DOUBLE), parameter :: c(6) = (/ & - -7.784894002430293e-3_8, -3.223964580411365e-1_8, -2.400758277161838_8, & - -2.549732539343734_8, 4.374664141464968_8, 2.938163982698783_8 /) - real(C_DOUBLE), parameter :: d(4) = (/ & - 7.784695709041462e-3_8, 3.224671290700398e-1_8, & - 2.445134137142996_8, 3.754408661907416_8 /) - - ! The rational approximation used here is from an unpublished work at - ! http://home.online.no/~pjacklam/notes/invnorm/ - - if (p < p_low) then - ! Rational approximation for lower region. - - q = sqrt(-TWO*log(p)) - z = (((((c(1)*q + c(2))*q + c(3))*q + c(4))*q + c(5))*q + c(6)) / & - ((((d(1)*q + d(2))*q + d(3))*q + d(4))*q + ONE) - - elseif (p <= ONE - p_low) then - ! Rational approximation for central region - - q = p - HALF - r = q*q - z = (((((a(1)*r + a(2))*r + a(3))*r + a(4))*r + a(5))*r + a(6))*q / & - (((((b(1)*r + b(2))*r + b(3))*r + b(4))*r + b(5))*r + ONE) - - else - ! Rational approximation for upper region - - q = sqrt(-TWO*log(ONE - p)) - z = -(((((c(1)*q + c(2))*q + c(3))*q + c(4))*q + c(5))*q + c(6)) / & - ((((d(1)*q + d(2))*q + d(3))*q + d(4))*q + ONE) - endif - - ! Refinement based on Newton's method -#ifndef NO_F2008 - z = z - (HALF * erfc(-z/sqrt(TWO)) - p) * sqrt(TWO*PI) * exp(HALF*z*z) -#endif - - end function normal_percentile - !=============================================================================== ! T_PERCENTILE calculates the percentile of the Student's t distribution with a ! specified probability level and number of degrees of freedom @@ -194,39 +134,7 @@ contains integer(C_INT), intent(in) :: df ! degrees of freedom real(C_DOUBLE) :: t ! corresponding t-value - real(C_DOUBLE) :: n ! degrees of freedom as a real(8) - real(C_DOUBLE) :: k ! n - 2 - real(C_DOUBLE) :: z ! percentile of normal distribution - real(C_DOUBLE) :: z2 ! z * z - - if (df == 1) then - ! For one degree of freedom, the t-distribution becomes a Cauchy - ! distribution whose cdf we can invert directly - - t = tan(PI*(p - HALF)) - - elseif (df == 2) then - ! For two degrees of freedom, the cdf is given by 1/2 + x/(2*sqrt(x^2 + - ! 2)). This can be directly inverted to yield the solution below - - t = TWO*sqrt(TWO)*(p - HALF)/sqrt(ONE - FOUR*(p - HALF)**2) - - else - - ! This approximation is from E. Olusegun George and Meenakshi Sivaram, "A - ! modification of the Fisher-Cornish approximation for the student t - ! percentiles," Communication in Statistics - Simulation and Computation, - ! 16 (4), pp. 1123-1132 (1987). - - n = real(df,8) - k = ONE/(n - TWO) - z = normal_percentile(p) - z2 = z * z - t = sqrt(n*k) * (z + (z2 - THREE)*z*k/FOUR + ((5._8*z2 - 56._8)*z2 + & - 75._8)*z*k*k/96._8 + (((z2 - 27._8)*THREE*z2 + 417._8)*z2 - 315._8) & - *z*k*k*k/384._8) - - end if + t = t_percentile_cc(p, df) end function t_percentile diff --git a/tests/unit_tests/test_math.py b/tests/unit_tests/test_math.py index 17c20fdb52..98295aefbf 100644 --- a/tests/unit_tests/test_math.py +++ b/tests/unit_tests/test_math.py @@ -5,17 +5,17 @@ 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] +# 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] +# # 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] +# test_zs = [openmc.capi.math.normal_percentile(p) for p in test_ps] - assert np.allclose(ref_zs, test_zs) +# assert np.allclose(ref_zs, test_zs) def test_t_percentile(): From 15b6b1d4eedad8e12e8f323720ccc03635dbd003 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Mon, 30 Apr 2018 16:13:11 -0400 Subject: [PATCH 05/19] CPPized the Pn and Rn functions --- openmc/capi/math.py | 48 ++-- src/math.F90 | 450 ++-------------------------------- src/math_functions.cpp | 31 +-- src/math_functions.h | 14 +- tests/unit_tests/test_math.py | 36 +-- 5 files changed, 84 insertions(+), 495 deletions(-) diff --git a/openmc/capi/math.py b/openmc/capi/math.py index 3d22ccdac6..cd7ab40520 100644 --- a/openmc/capi/math.py +++ b/openmc/capi/math.py @@ -98,6 +98,30 @@ def calc_pn(n, x): return _dll.calc_pn(c_int(n), c_double(x)) +def evaluate_legendre(data, x): + """ Finds the value of f(x) given a set of Legendre coefficients + and the value of x. + + Parameters + ---------- + data : iterable of float + Legendre coefficients + x : float + Independent variable to evaluate the Legendre at + + Returns + ------- + float + Corresponding Legendre expansion result + + """ + + data_arr = np.array(data, dtype=np.float64) + return _dll.evaluate_legendre(c_int(len(data)), + data_arr.ctypes.data_as(POINTER(c_double)), + c_double(x)) + + def calc_rn(n, uvw): """ Calculate the n-th order real Spherical Harmonics for a given angle; all Rn,m values are provided (where -n <= m <= n). @@ -151,30 +175,6 @@ def calc_zn(n, rho, phi): return zn -def evaluate_legendre(data, x): - """ Finds the value of f(x) given a set of Legendre coefficients - and the value of x. - - Parameters - ---------- - data : iterable of float - Legendre coefficients - x : float - Independent variable to evaluate the Legendre at - - Returns - ------- - float - Corresponding Legendre expansion result - - """ - - data_arr = np.array(data, dtype=np.float64) - 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): """ Rotates direction cosines through a polar angle whose cosine is mu and through an azimuthal angle sampled uniformly. diff --git a/src/math.F90 b/src/math.F90 index 40c2d09dc7..9e1f01de5a 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -148,7 +148,7 @@ contains ! the return value will be 1.0. !=============================================================================== - pure function calc_pn(n,x) result(pnx) bind(C) + pure function calc_pn(n, x) result(pnx) bind(C) integer(C_INT), intent(in) :: n ! Legendre order requested real(C_DOUBLE), intent(in) :: x ! Independent variable the Legendre is to @@ -157,39 +157,25 @@ contains real(C_DOUBLE) :: pnx ! The Legendre poly of order n evaluated ! at x - select case(n) - case(1) - pnx = x - case(2) - pnx = 1.5_8 * x * x - HALF - case(3) - pnx = 2.5_8 * x * x * x - 1.5_8 * x - case(4) - pnx = 4.375_8 * (x ** 4) - 3.75_8 * x * x + 0.375_8 - case(5) - pnx = 7.875_8 * (x ** 5) - 8.75_8 * x * x * x + 1.875 * x - case(6) - pnx = 14.4375_8 * (x ** 6) - 19.6875_8 * (x ** 4) + & - 6.5625_8 * x * x - 0.3125_8 - case(7) - pnx = 26.8125_8 * (x ** 7) - 43.3125_8 * (x ** 5) + & - 19.6875_8 * x * x * x - 2.1875_8 * x - case(8) - pnx = 50.2734375_8 * (x ** 8) - 93.84375_8 * (x ** 6) + & - 54.140625 * (x ** 4) - 9.84375_8 * x * x + 0.2734375_8 - case(9) - pnx = 94.9609375_8 * (x ** 9) - 201.09375_8 * (x ** 7) + & - 140.765625_8 * (x ** 5) - 36.09375_8 * x * x * x + 2.4609375_8 * x - case(10) - pnx = 180.42578125_8 * (x ** 10) - 427.32421875_8 * (x ** 8) + & - 351.9140625_8 * (x ** 6) - 117.3046875_8 * (x ** 4) + & - 13.53515625_8 * x * x - 0.24609375_8 - case default - pnx = ONE ! correct for case(0), incorrect for the rest - end select + pnx = calc_pn_cc(n, x) end function calc_pn +!=============================================================================== +! EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients +! and the value of x +!=============================================================================== + + 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 + + val = evaluate_legendre_cc(size(data), data, x) + + end function evaluate_legendre + !=============================================================================== ! CALC_RN calculates the n-th order real spherical harmonics for a given angle ! (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) @@ -202,387 +188,7 @@ contains ! assumed to be on unit sphere real(C_DOUBLE) :: rn(2*n + 1) ! The resultant R_n(uvw) - - real(C_DOUBLE) :: phi, w ! Azimuthal and Cosine of Polar angles (from uvw) - real(C_DOUBLE) :: w2m1 ! (w^2 - 1), frequently used in these - - w = uvw(3) ! z = cos(polar) - if (uvw(1) == ZERO) then - phi = ZERO - else - phi = atan2(uvw(2), uvw(1)) - end if - - w2m1 = (ONE - w**2) - select case(n) - case (0) - ! l = 0, m = 0 - rn(1) = ONE - case (1) - ! l = 1, m = -1 - rn(1) = -(ONE*sqrt(w2m1) * sin(phi)) - ! l = 1, m = 0 - rn(2) = ONE * w - ! l = 1, m = 1 - rn(3) = -(ONE*sqrt(w2m1) * cos(phi)) - case (2) - ! l = 2, m = -2 - rn(1) = 0.288675134594813_8 * (-THREE * w**2 + THREE) * sin(TWO*phi) - ! l = 2, m = -1 - rn(2) = -(1.73205080756888_8 * w*sqrt(w2m1) * sin(phi)) - ! l = 2, m = 0 - rn(3) = 1.5_8 * w**2 - HALF - ! l = 2, m = 1 - rn(4) = -(1.73205080756888_8 * w*sqrt(w2m1) * cos(phi)) - ! l = 2, m = 2 - rn(5) = 0.288675134594813_8 * (-THREE * w**2 + THREE) * cos(TWO*phi) - case (3) - ! l = 3, m = -3 - rn(1) = -(0.790569415042095_8 * (w2m1)**(THREE/TWO) * sin(THREE * phi)) - ! l = 3, m = -2 - rn(2) = 1.93649167310371_8 * w*(w2m1) * sin(TWO*phi) - ! l = 3, m = -1 - rn(3) = -(0.408248290463863_8*sqrt(w2m1)*((15.0_8/TWO)*w**2 - THREE/TWO) * & - sin(phi)) - ! l = 3, m = 0 - rn(4) = 2.5_8 * w**3 - 1.5_8 * w - ! l = 3, m = 1 - rn(5) = -(0.408248290463863_8*sqrt(w2m1)*((15.0_8/TWO)*w**2 - THREE/TWO) * & - cos(phi)) - ! l = 3, m = 2 - rn(6) = 1.93649167310371_8 * w*(w2m1) * cos(TWO*phi) - ! l = 3, m = 3 - rn(7) = -(0.790569415042095_8 * (w2m1)**(THREE/TWO) * cos(THREE* phi)) - case (4) - ! l = 4, m = -4 - rn(1) = 0.739509972887452_8 * (w2m1)**2 * sin(4.0_8*phi) - ! l = 4, m = -3 - rn(2) = -(2.09165006633519_8 * w*(w2m1)**(THREE/TWO) * sin(THREE* phi)) - ! l = 4, m = -2 - rn(3) = 0.074535599249993_8 * (w2m1)*((105.0_8/TWO)*w**2 - 15.0_8/TWO) * & - sin(TWO*phi) - ! l = 4, m = -1 - rn(4) = -(0.316227766016838_8*sqrt(w2m1)*((35.0_8/TWO)*w**3 - 15.0_8/TWO*w)& - * sin(phi)) - ! l = 4, m = 0 - rn(5) = 4.375_8 * w**4 - 3.75_8 * w**2 + 0.375_8 - ! l = 4, m = 1 - rn(6) = -(0.316227766016838_8*sqrt(w2m1)*((35.0_8/TWO)*w**3 - 15.0_8/TWO*w)& - * cos(phi)) - ! l = 4, m = 2 - rn(7) = 0.074535599249993_8 * (w2m1)*((105.0_8/TWO)*w**2 - 15.0_8/TWO) * & - cos(TWO*phi) - ! l = 4, m = 3 - rn(8) = -(2.09165006633519_8 * w*(w2m1)**(THREE/TWO) * cos(THREE* phi)) - ! l = 4, m = 4 - rn(9) = 0.739509972887452_8 * (w2m1)**2 * cos(4.0_8*phi) - case (5) - ! l = 5, m = -5 - rn(1) = -(0.701560760020114_8 * (w2m1)**(5.0_8/TWO) * sin(5.0_8*phi)) - ! l = 5, m = -4 - rn(2) = 2.21852991866236_8 * w*(w2m1)**2 * sin(4.0_8*phi) - ! l = 5, m = -3 - rn(3) = -(0.00996023841111995_8 * (w2m1)**(THREE/TWO)* & - ((945.0_8 /TWO)*w**2 - 105.0_8/TWO) * sin(THREE*phi)) - ! l = 5, m = -2 - rn(4) = 0.0487950036474267_8 * (w2m1) & - * ((315.0_8/TWO)*w**3 - 105.0_8/TWO*w) * sin(TWO*phi) - ! l = 5, m = -1 - rn(5) = -(0.258198889747161_8*sqrt(w2m1)* & - ((315.0_8/8.0_8)*w**4 - 105.0_8/4.0_8 * w**2 + 15.0_8/8.0_8) & - * sin(phi)) - ! l = 5, m = 0 - rn(6) = 7.875_8 * w**5 - 8.75_8 * w**3 + 1.875_8 * w - ! l = 5, m = 1 - rn(7) = -(0.258198889747161_8*sqrt(w2m1)* & - ((315.0_8/8.0_8)*w**4 - 105.0_8/4.0_8 * w**2 + 15.0_8/8.0_8) & - * cos(phi)) - ! l = 5, m = 2 - rn(8) = 0.0487950036474267_8 * (w2m1)* & - ((315.0_8/TWO)*w**3 - 105.0_8/TWO*w) * cos(TWO*phi) - ! l = 5, m = 3 - rn(9) = -(0.00996023841111995_8 * (w2m1)**(THREE/TWO)* & - ((945.0_8 /TWO)*w**2 - 105.0_8/TWO) * cos(THREE*phi)) - ! l = 5, m = 4 - rn(10) = 2.21852991866236_8 * w*(w2m1)**2 * cos(4.0_8*phi) - ! l = 5, m = 5 - rn(11) = -(0.701560760020114_8 * (w2m1)**(5.0_8/TWO) * cos(5.0_8* phi)) - case (6) - ! l = 6, m = -6 - rn(1) = 0.671693289381396_8 * (w2m1)**3 * sin(6.0_8*phi) - ! l = 6, m = -5 - rn(2) = -(2.32681380862329_8 * w*(w2m1)**(5.0_8/TWO) * sin(5.0_8*phi)) - ! l = 6, m = -4 - rn(3) = 0.00104990131391452_8 * (w2m1)**2 * & - ((10395.0_8/TWO)*w**2 - 945.0_8/TWO) * sin(4.0_8*phi) - ! l = 6, m = -3 - rn(4) = -(0.00575054632785295_8 * (w2m1)**(THREE/TWO) * & - ((3465.0_8/TWO)*w**3 - 945.0_8/TWO*w) * sin(THREE*phi)) - ! l = 6, m = -2 - rn(5) = 0.0345032779671177_8 * (w2m1) * & - ((3465.0_8/8.0_8)*w**4 - 945.0_8/4.0_8 * w**2 + 105.0_8/8.0_8) & - * sin(TWO*phi) - ! l = 6, m = -1 - rn(6) = -(0.218217890235992_8*sqrt(w2m1) * & - ((693.0_8/8.0_8)*w**5- 315.0_8/4.0_8 * w**3 + (105.0_8/8.0_8)*w) & - * sin(phi)) - ! l = 6, m = 0 - rn(7) = 14.4375_8 * w**6 - 19.6875_8 * w**4 + 6.5625_8 * w**2 - 0.3125_8 - ! l = 6, m = 1 - rn(8) = -(0.218217890235992_8*sqrt(w2m1) * & - ((693.0_8/8.0_8)*w**5- 315.0_8/4.0_8 * w**3 + (105.0_8/8.0_8)*w) & - * cos(phi)) - ! l = 6, m = 2 - rn(9) = 0.0345032779671177_8 * (w2m1) * & - ((3465.0_8/8.0_8)*w**4 -945.0_8/4.0_8 * w**2 + 105.0_8/8.0_8) & - * cos(TWO*phi) - ! l = 6, m = 3 - rn(10) = -(0.00575054632785295_8 * (w2m1)**(THREE/TWO) * & - ((3465.0_8/TWO)*w**3 - 945.0_8/TWO*w) * cos(THREE*phi)) - ! l = 6, m = 4 - rn(11) = 0.00104990131391452_8 * (w2m1)**2 * & - ((10395.0_8/TWO)*w**2 - 945.0_8/TWO) * cos(4.0_8*phi) - ! l = 6, m = 5 - rn(12) = -(2.32681380862329_8 * w*(w2m1)**(5.0_8/TWO) * cos(5.0_8*phi)) - ! l = 6, m = 6 - rn(13) = 0.671693289381396_8 * (w2m1)**3 * cos(6.0_8*phi) - case (7) - ! l = 7, m = -7 - rn(1) = -(0.647259849287749_8 * (w2m1)**(7.0_8/TWO) * sin(7.0_8*phi)) - ! l = 7, m = -6 - rn(2) = 2.42182459624969_8 * w*(w2m1)**3 * sin(6.0_8*phi) - ! l = 7, m = -5 - rn(3) = -(9.13821798555235d-5*(w2m1)**(5.0_8/TWO)* & - ((135135.0_8/TWO)*w**2 - 10395.0_8/TWO) * sin(5.0_8*phi)) - ! l = 7, m = -4 - rn(4) = 0.000548293079133141_8 * (w2m1)**2* & - ((45045.0_8/TWO)*w**3 - 10395.0_8/TWO*w) * sin(4.0_8*phi) - ! l = 7, m = -3 - rn(5) = -(0.00363696483726654_8 * (w2m1)**(THREE/TWO)* & - ((45045.0_8/8.0_8)*w**4 - 10395.0_8/4.0_8 * w**2 + 945.0_8/8.0_8)* & - sin(THREE*phi)) - ! l = 7, m = -2 - rn(6) = 0.025717224993682_8 * (w2m1)* & - ((9009.0_8/8.0_8)*w**5 -3465.0_8/4.0_8 * w**3 + (945.0_8/8.0_8)*w)* & - sin(TWO*phi) - ! l = 7, m = -1 - rn(7) = -(0.188982236504614_8*sqrt(w2m1)* & - ((3003.0_8/16.0_8)*w**6 - 3465.0_8/16.0_8 * w**4 + & - (945.0_8/16.0_8)*w**2 - 35.0_8/16.0_8) * sin(phi)) - ! l = 7, m = 0 - rn(8) = 26.8125_8 * w**7 - 43.3125_8 * w**5 + 19.6875_8 * w**3 -2.1875_8 & - * w - ! l = 7, m = 1 - rn(9) = -(0.188982236504614_8*sqrt(w2m1)* & - ((3003.0_8/16.0_8)*w**6 - 3465.0_8/16.0_8 * w**4 + & - (945.0_8/16.0_8)*w**2 - 35.0_8/16.0_8) * cos(phi)) - ! l = 7, m = 2 - rn(10) = 0.025717224993682_8 * (w2m1)* & - ((9009.0_8/8.0_8)*w**5 -3465.0_8/4.0_8 * w**3 + (945.0_8/8.0_8)*w)* & - cos(TWO*phi) - ! l = 7, m = 3 - rn(11) = -(0.00363696483726654_8 * (w2m1)**(THREE/TWO)* & - ((45045.0_8/8.0_8)*w**4 - 10395.0_8/4.0_8 * w**2 + 945.0_8/8.0_8)* & - cos(THREE*phi)) - ! l = 7, m = 4 - rn(12) = 0.000548293079133141_8 * (w2m1)**2 * & - ((45045.0_8/TWO)*w**3 - 10395.0_8/TWO*w) * cos(4.0_8*phi) - ! l = 7, m = 5 - rn(13) = -(9.13821798555235d-5*(w2m1)**(5.0_8/TWO)* & - ((135135.0_8/TWO)*w**2 - 10395.0_8/TWO) * cos(5.0_8*phi)) - ! l = 7, m = 6 - rn(14) = 2.42182459624969_8 * w*(w2m1)**3 * cos(6.0_8*phi) - ! l = 7, m = 7 - rn(15) = -(0.647259849287749_8 * (w2m1)**(7.0_8/TWO) * cos(7.0_8*phi)) - case (8) - ! l = 8, m = -8 - rn(1) = 0.626706654240044_8 * (w2m1)**4 * sin(8.0_8*phi) - ! l = 8, m = -7 - rn(2) = -(2.50682661696018_8 * w*(w2m1)**(7.0_8/TWO) * sin(7.0_8*phi)) - ! l = 8, m = -6 - rn(3) = 6.77369783729086d-6*(w2m1)**3* & - ((2027025.0_8/TWO)*w**2 - 135135.0_8/TWO) * sin(6.0_8*phi) - ! l = 8, m = -5 - rn(4) = -(4.38985792528482d-5*(w2m1)**(5.0_8/TWO)* & - ((675675.0_8/TWO)*w**3 - 135135.0_8/TWO*w) * sin(5.0_8*phi)) - ! l = 8, m = -4 - rn(5) = 0.000316557156832328_8 * (w2m1)**2* & - ((675675.0_8/8.0_8)*w**4 - 135135.0_8/4.0_8 * w**2 & - + 10395.0_8/8.0_8) * sin(4.0_8*phi) - ! l = 8, m = -3 - rn(6) = -(0.00245204119306875_8 * (w2m1)**(THREE/TWO)* & - ((135135.0_8/8.0_8)*w**5 - 45045.0_8/4.0_8 * w**3 & - + (10395.0_8/8.0_8)*w) * sin(THREE*phi)) - ! l = 8, m = -2 - rn(7) = 0.0199204768222399_8 * (w2m1)* & - ((45045.0_8/16.0_8)*w**6- 45045.0_8/16.0_8 * w**4 + & - (10395.0_8/16.0_8)*w**2 - 315.0_8/16.0_8) * sin(TWO*phi) - ! l = 8, m = -1 - rn(8) = -(0.166666666666667_8*sqrt(w2m1)* & - ((6435.0_8/16.0_8)*w**7 - 9009.0_8/16.0_8 * w**5 + & - (3465.0_8/16.0_8)*w**3 - 315.0_8/16.0_8 * w) * sin(phi)) - ! l = 8, m = 0 - rn(9) = 50.2734375_8 * w**8 - 93.84375_8 * w**6 + 54.140625_8 * w**4 -& - 9.84375_8 * w**2 + 0.2734375_8 - ! l = 8, m = 1 - rn(10) = -(0.166666666666667_8*sqrt(w2m1)* & - ((6435.0_8/16.0_8)*w**7 - 9009.0_8/16.0_8 * w**5 + & - (3465.0_8/16.0_8)*w**3 - 315.0_8/16.0_8 * w) * cos(phi)) - ! l = 8, m = 2 - rn(11) = 0.0199204768222399_8 * (w2m1)*((45045.0_8/16.0_8)*w**6- & - 45045.0_8/16.0_8 * w**4 + (10395.0_8/16.0_8)*w**2 - & - 315.0_8/16.0_8) * cos(TWO*phi) - ! l = 8, m = 3 - rn(12) = -(0.00245204119306875_8 * (w2m1)**(THREE/TWO)* & - ((135135.0_8/8.0_8)*w**5 - 45045.0_8/4.0_8 * w**3 + & - (10395.0_8/8.0_8)*w) * cos(THREE*phi)) - ! l = 8, m = 4 - rn(13) = 0.000316557156832328_8 * (w2m1)**2*((675675.0_8/8.0_8)*w**4 - & - 135135.0_8/4.0_8 * w**2 + 10395.0_8/8.0_8) * cos(4.0_8*phi) - ! l = 8, m = 5 - rn(14) = -(4.38985792528482d-5*(w2m1)**(5.0_8/TWO)*((675675.0_8/TWO)*w**3 -& - 135135.0_8/TWO*w) * cos(5.0_8*phi)) - ! l = 8, m = 6 - rn(15) = 6.77369783729086d-6*(w2m1)**3*((2027025.0_8/TWO)*w**2 - & - 135135.0_8/TWO) * cos(6.0_8*phi) - ! l = 8, m = 7 - rn(16) = -(2.50682661696018_8 * w*(w2m1)**(7.0_8/TWO) * cos(7.0_8*phi)) - ! l = 8, m = 8 - rn(17) = 0.626706654240044_8 * (w2m1)**4 * cos(8.0_8*phi) - case (9) - ! l = 9, m = -9 - rn(1) = -(0.609049392175524_8 * (w2m1)**(9.0_8/TWO) * sin(9.0_8*phi)) - ! l = 9, m = -8 - rn(2) = 2.58397773170915_8 * w*(w2m1)**4 * sin(8.0_8*phi) - ! l = 9, m = -7 - rn(3) = -(4.37240315267812d-7*(w2m1)**(7.0_8/TWO)* & - ((34459425.0_8/TWO)*w**2 - 2027025.0_8/TWO) * sin(7.0_8*phi)) - ! l = 9, m = -6 - rn(4) = 3.02928976464514d-6*(w2m1)**3* & - ((11486475.0_8/TWO)*w**3 - 2027025.0_8/TWO*w) * sin(6.0_8*phi) - ! l = 9, m = -5 - rn(5) = -(2.34647776186144d-5*(w2m1)**(5.0_8/TWO)* & - ((11486475.0_8/8.0_8)*w**4 - 2027025.0_8/4.0_8 * w**2 + & - 135135.0_8/8.0_8) * sin(5.0_8*phi)) - ! l = 9, m = -4 - rn(6) = 0.000196320414650061_8 * (w2m1)**2*((2297295.0_8/8.0_8)*w**5 - & - 675675.0_8/4.0_8 * w**3 + (135135.0_8/8.0_8)*w) * sin(4.0_8*phi) - ! l = 9, m = -3 - rn(7) = -(0.00173385495536766_8 * (w2m1)**(THREE/TWO)* & - ((765765.0_8/16.0_8)*w**6 - 675675.0_8/16.0_8 * w**4 + & - (135135.0_8/16.0_8)*w**2 - 3465.0_8/16.0_8) * sin(THREE*phi)) - ! l = 9, m = -2 - rn(8) = 0.0158910431540932_8 * (w2m1)*((109395.0_8/16.0_8)*w**7- & - 135135.0_8/16.0_8 * w**5 + (45045.0_8/16.0_8)*w**3 & - - 3465.0_8/16.0_8 * w) * sin(TWO*phi) - ! l = 9, m = -1 - rn(9) = -(0.149071198499986_8*sqrt(w2m1)*((109395.0_8/128.0_8)*w**8 - & - 45045.0_8/32.0_8 * w**6 + (45045.0_8/64.0_8)*w**4 - 3465.0_8/32.0_8 & - * w**2 + 315.0_8/128.0_8) * sin(phi)) - ! l = 9, m = 0 - rn(10) = 94.9609375_8 * w**9 - 201.09375_8 * w**7 + 140.765625_8 * w**5- & - 36.09375_8 * w**3 + 2.4609375_8 * w - ! l = 9, m = 1 - rn(11) = -(0.149071198499986_8*sqrt(w2m1)*((109395.0_8/128.0_8)*w**8 - & - 45045.0_8/32.0_8 * w**6 + (45045.0_8/64.0_8)*w**4 -3465.0_8/32.0_8 & - * w**2 + 315.0_8/128.0_8) * cos(phi)) - ! l = 9, m = 2 - rn(12) = 0.0158910431540932_8 * (w2m1)*((109395.0_8/16.0_8)*w**7 - & - 135135.0_8/16.0_8 * w**5 + (45045.0_8/16.0_8)*w**3 & - - 3465.0_8/ 16.0_8 * w) * cos(TWO*phi) - ! l = 9, m = 3 - rn(13) = -(0.00173385495536766_8 * (w2m1)**(THREE/TWO)*((765765.0_8/16.0_8)& - *w**6 - 675675.0_8/16.0_8 * w**4 + (135135.0_8/16.0_8)*w**2 & - - 3465.0_8/16.0_8)* cos(THREE*phi)) - ! l = 9, m = 4 - rn(14) = 0.000196320414650061_8 * (w2m1)**2*((2297295.0_8/8.0_8)*w**5 - & - 675675.0_8/4.0_8 * w**3 + (135135.0_8/8.0_8)*w) * cos(4.0_8*phi) - ! l = 9, m = 5 - rn(15) = -(2.34647776186144d-5*(w2m1)**(5.0_8/TWO)*((11486475.0_8/8.0_8)* & - w**4 - 2027025.0_8/4.0_8 * w**2 + 135135.0_8/8.0_8) * cos(5.0_8*phi)) - ! l = 9, m = 6 - rn(16) = 3.02928976464514d-6*(w2m1)**3*((11486475.0_8/TWO)*w**3 - & - 2027025.0_8/TWO*w) * cos(6.0_8*phi) - ! l = 9, m = 7 - rn(17) = -(4.37240315267812d-7*(w2m1)**(7.0_8/TWO)* & - ((34459425.0_8/TWO)*w**2 - 2027025.0_8/TWO) * cos(7.0_8*phi)) - ! l = 9, m = 8 - rn(18) = 2.58397773170915_8 * w*(w2m1)**4 * cos(8.0_8*phi) - ! l = 9, m = 9 - rn(19) = -(0.609049392175524_8 * (w2m1)**(9.0_8/TWO) * cos(9.0_8*phi)) - case (10) - ! l = 10, m = -10 - rn(1) = 0.593627917136573_8 * (w2m1)**5 * sin(10.0_8*phi) - ! l = 10, m = -9 - rn(2) = -(2.65478475211798_8 * w*(w2m1)**(9.0_8/TWO) * sin(9.0_8*phi)) - ! l = 10, m = -8 - rn(3) = 2.49953651452314d-8*(w2m1)**4*((654729075.0_8/TWO)*w**2 - & - 34459425.0_8/TWO) * sin(8.0_8*phi) - ! l = 10, m = -7 - rn(4) = -(1.83677671621093d-7*(w2m1)**(7.0_8/TWO)* & - ((218243025.0_8/TWO)*w**3 - 34459425.0_8/TWO*w) * sin(7.0_8*phi)) - ! l = 10, m = -6 - rn(5) = 1.51464488232257d-6*(w2m1)**3*((218243025.0_8/8.0_8)*w**4 - & - 34459425.0_8/4.0_8 * w**2 + 2027025.0_8/8.0_8) * sin(6.0_8*phi) - ! l = 10, m = -5 - rn(6) = -(1.35473956745817d-5*(w2m1)**(5.0_8/TWO)* & - ((43648605.0_8/8.0_8)*w**5 - 11486475.0_8/4.0_8 * w**3 + & - (2027025.0_8/8.0_8)*w) * sin(5.0_8*phi)) - ! l = 10, m = -4 - rn(7) = 0.000128521880085575_8 * (w2m1)**2*((14549535.0_8/16.0_8)*w**6 - & - 11486475.0_8/16.0_8 * w**4 + (2027025.0_8/16.0_8)*w**2 - & - 45045.0_8/16.0_8) * sin(4.0_8*phi) - ! l = 10, m = -3 - rn(8) = -(0.00127230170115096_8 * (w2m1)**(THREE/TWO)* & - ((2078505.0_8/16.0_8)*w**7 - 2297295.0_8/16.0_8 * w**5 + & - (675675.0_8/16.0_8)*w**3 - 45045.0_8/16.0_8 * w) * sin(THREE*phi)) - ! l = 10, m = -2 - rn(9) = 0.012974982402692_8 * (w2m1)*((2078505.0_8/128.0_8)*w**8 - & - 765765.0_8/32.0_8 * w**6 + (675675.0_8/64.0_8)*w**4 - & - 45045.0_8/32.0_8 * w**2 + 3465.0_8/128.0_8) * sin(TWO*phi) - ! l = 10, m = -1 - rn(10) = -(0.134839972492648_8*sqrt(w2m1)*((230945.0_8/128.0_8)*w**9 - & - 109395.0_8/32.0_8 * w**7 + (135135.0_8/64.0_8)*w**5 - & - 15015.0_8/32.0_8 * w**3 + (3465.0_8/128.0_8)*w) * sin(phi)) - ! l = 10, m = 0 - rn(11) = 180.42578125_8 * w**10 - 427.32421875_8 * w**8 +351.9140625_8 & - * w**6 - 117.3046875_8 * w**4 + 13.53515625_8 * w**2 -0.24609375_8 - ! l = 10, m = 1 - rn(12) = -(0.134839972492648_8*sqrt(w2m1)*((230945.0_8/128.0_8)*w**9 - & - 109395.0_8/32.0_8 * w**7 + (135135.0_8/64.0_8)*w**5 -15015.0_8/ & - 32.0_8 * w**3 + (3465.0_8/128.0_8)*w) * cos(phi)) - ! l = 10, m = 2 - rn(13) = 0.012974982402692_8 * (w2m1)*((2078505.0_8/128.0_8)*w**8 - & - 765765.0_8/32.0_8 * w**6 + (675675.0_8/64.0_8)*w**4 -& - 45045.0_8/32.0_8 * w**2 + 3465.0_8/128.0_8) * cos(TWO*phi) - ! l = 10, m = 3 - rn(14) = -(0.00127230170115096_8 * (w2m1)**(THREE/TWO)* & - ((2078505.0_8/16.0_8)*w**7 - 2297295.0_8/16.0_8 * w**5 + & - (675675.0_8/16.0_8)*w**3 - 45045.0_8/16.0_8 * w) * cos(THREE*phi)) - ! l = 10, m = 4 - rn(15) = 0.000128521880085575_8 * (w2m1)**2*((14549535.0_8/16.0_8)*w**6 -& - 11486475.0_8/16.0_8 * w**4 + (2027025.0_8/16.0_8)*w**2 - & - 45045.0_8/16.0_8) * cos(4.0_8*phi) - ! l = 10, m = 5 - rn(16) = -(1.35473956745817d-5*(w2m1)**(5.0_8/TWO)* & - ((43648605.0_8/8.0_8)*w**5 - 11486475.0_8/4.0_8 * w**3 + & - (2027025.0_8/8.0_8)*w) * cos(5.0_8*phi)) - ! l = 10, m = 6 - rn(17) = 1.51464488232257d-6*(w2m1)**3*((218243025.0_8/8.0_8)*w**4 - & - 34459425.0_8/4.0_8 * w**2 + 2027025.0_8/8.0_8) * cos(6.0_8*phi) - ! l = 10, m = 7 - rn(18) = -(1.83677671621093d-7*(w2m1)**(7.0_8/TWO)* & - ((218243025.0_8/TWO)*w**3 - 34459425.0_8/TWO*w) * cos(7.0_8*phi)) - ! l = 10, m = 8 - rn(19) = 2.49953651452314d-8*(w2m1)**4* & - ((654729075.0_8/TWO)*w**2 - 34459425.0_8/TWO) * cos(8.0_8*phi) - ! l = 10, m = 9 - rn(20) = -(2.65478475211798_8 * w*(w2m1)**(9.0_8/TWO) * cos(9.0_8*phi)) - ! l = 10, m = 10 - rn(21) = 0.593627917136573_8 * (w2m1)**5 * cos(10.0_8*phi) - case default - rn = ONE - end select + call calc_rn_cc(n, uvw, rn) end subroutine calc_rn @@ -693,26 +299,6 @@ contains end do end subroutine calc_zn -!=============================================================================== -! EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients -! and the value of x -!=============================================================================== - - 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, n - 1 - val = val + (real(l, 8) + HALF) * data(l + 1) * calc_pn(l,x) - end do - - end function evaluate_legendre - !=============================================================================== ! ROTATE_ANGLE rotates direction cosines through a polar angle whose cosine is ! mu and through an azimuthal angle sampled uniformly. Note that this is done diff --git a/src/math_functions.cpp b/src/math_functions.cpp index 3b645c7724..6ddf944a76 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -161,6 +161,22 @@ double __attribute__ ((const)) calc_pn_c(int n, double x) { return pnx; } +//============================================================================== +// EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients +// and the value of x +//============================================================================== + +double __attribute__ ((const)) evaluate_legendre_c(int n, double data[], + double x) { + double val; + + val = 0.5 * data[0]; + for (int l = 1; l < n; l++) { + val += (static_cast(l) + 0.5) * data[l] * calc_pn_c(l, x); + } + return val; +} + //============================================================================== // CALC_RN calculates the n-th order spherical harmonics for a given angle // (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) @@ -561,21 +577,6 @@ void calc_rn_c(int n, double uvw[3], double rn[]){ } } -//============================================================================== -// EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients -// and the value of x -//============================================================================== - -double __attribute__ ((const)) evaluate_legendre_c(int n, double data[], - double x) { - double val; - - val = 0.5 * data[0]; - for (int l = 1; l < n; l++) { - val += (static_cast(l) + 0.5) * data[l] * calc_pn_c(l, x); - } - -} //============================================================================== // ROTATE_ANGLE rotates direction std::cosines through a polar angle whose diff --git a/src/math_functions.h b/src/math_functions.h index aa048e5f67..bea30cf916 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -32,13 +32,6 @@ extern "C" double t_percentile_c(double p, int df) __attribute__ ((const)); extern "C" double calc_pn_c(int n, double x) __attribute__ ((const)); -//============================================================================== -// CALC_RN calculates the n-th order spherical harmonics for a given angle -// (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) -//============================================================================== - -extern "C" void calc_rn_c(int n, double uvw[3], double rn[]); - //============================================================================== // EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients // and the value of x @@ -47,6 +40,13 @@ extern "C" void calc_rn_c(int n, double uvw[3], double rn[]); extern "C" double evaluate_legendre_c(int n, double data[], double x) __attribute__ ((const)); +//============================================================================== +// CALC_RN calculates the n-th order spherical harmonics for a given angle +// (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) +//============================================================================== + +extern "C" void calc_rn_c(int n, double uvw[3], double rn[]); + //============================================================================== // ROTATE_ANGLE rotates direction cosines through a polar angle whose cosine is // mu and through an azimuthal angle sampled uniformly. Note that this is done diff --git a/tests/unit_tests/test_math.py b/tests/unit_tests/test_math.py index 98295aefbf..c8d33e6012 100644 --- a/tests/unit_tests/test_math.py +++ b/tests/unit_tests/test_math.py @@ -51,6 +51,25 @@ def test_calc_pn(): assert np.allclose(ref_vals, test_vals) +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 since + # evaluate legendre includes the (2l+1)/2 term + 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_calc_rn(): max_order = 10 test_ns = np.array([i for i in range(0, max_order + 1)]) @@ -99,23 +118,6 @@ 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. From feecf0134eda55b9cac11107c55ef46c7450ac4f Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sun, 6 May 2018 11:28:05 -0400 Subject: [PATCH 06/19] Incorporated maxwell and watt_spectrum C++ code, tests pass --- src/math.F90 | 18 ++---------------- src/math_functions.cpp | 12 ++++++------ src/math_functions.h | 4 ++++ tests/unit_tests/test_math.py | 8 ++++---- 4 files changed, 16 insertions(+), 26 deletions(-) diff --git a/src/math.F90 b/src/math.F90 index 6534b66b03..d7e335284c 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -364,18 +364,7 @@ contains real(C_DOUBLE), intent(in) :: T ! tabulated function of incoming E real(C_DOUBLE) :: E_out ! sampled energy - real(C_DOUBLE) :: r1, r2, r3 ! random numbers - real(C_DOUBLE) :: c ! cosine of pi/2*r3 - - r1 = prn() - r2 = prn() - r3 = prn() - - ! determine cosine of pi/2*r - c = cos(PI/TWO*r3) - - ! determine outgoing energy - E_out = -T*(log(r1) + log(r2)*c*c) + E_out = maxwell_spectrum_cc(T) end function maxwell_spectrum @@ -393,10 +382,7 @@ contains real(C_DOUBLE), intent(in) :: b ! Watt parameter b real(C_DOUBLE) :: E_out ! energy of emitted neutron - real(C_DOUBLE) :: w ! sampled from Maxwellian - - w = maxwell_spectrum(a) - E_out = w + a*a*b/4. + (TWO*prn() - ONE)*sqrt(a*a*b*w) + E_out = watt_spectrum_cc(a, b) end function watt_spectrum diff --git a/src/math_functions.cpp b/src/math_functions.cpp index 6ddf944a76..1466bde547 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -56,7 +56,7 @@ double __attribute__ ((const)) normal_percentile_c(double p) { // Refinement based on Newton's method - z = z - (0.5 * std::erfc(-z / std::sqrt(2.0)) - p) * std::sqrt(2.0 * M_PI) * + z = z - (0.5 * std::erfc(-z / std::sqrt(2.0)) - p) * std::sqrt(2.0 * PI) * std::exp(0.5 * z * z); return z; @@ -82,7 +82,7 @@ double __attribute__ ((const)) t_percentile_c(double p, int df){ // For one degree of freedom, the t-distribution becomes a Cauchy // distribution whose cdf we can invert directly - t = std::tan(M_PI*(p - 0.5)); + t = std::tan(PI*(p - 0.5)); } else if (df == 2) { // For two degrees of freedom, the cdf is given by 1/2 + x/(2*sqrt(x^2 + // 2)). This can be directly inverted to yield the solution below @@ -604,7 +604,7 @@ void rotate_angle_c(double uvw[3], double mu, double phi) { if (phi != -10.) { phi_ = phi; } else { - phi_ = 2. * M_PI * prn(); + phi_ = 2. * PI * prn(); } // Precompute factors to save flops @@ -647,7 +647,7 @@ double maxwell_spectrum_c(double T) { r3 = prn(); // determine cosine of pi/2*r - c = std::cos(M_PI / 2. * r3); + c = std::cos(PI / 2. * r3); // determine outgoing energy E_out = -T * (std::log(r1) + std::log(r2) * c * c); @@ -710,7 +710,7 @@ double watt_spectrum_c(double a, double b) { // std::complex w_derivative_c(std::complex z, int order){ // std::complex wv; // The resultant w(z) value -// const std::complex twoi_sqrtpi(0.0, 2.0 / std::sqrt(M_PI)); +// const std::complex twoi_sqrtpi(0.0, 2.0 / std::sqrt(PI)); // switch(order) { // case 0: @@ -764,7 +764,7 @@ void broaden_wmp_polynomials_c(double E, double dopp, int n, double factors[]) { factors[0] = erf_beta / E; factors[1] = 1. / sqrtE; factors[2] = factors[0] * (half_inv_dopp2 + E) + exp_m_beta2 / - (beta * std::sqrt(M_PI)); + (beta * std::sqrt(PI)); // Perform recursive broadening of high order components for (i = 0; i < n - 3; i++) { diff --git a/src/math_functions.h b/src/math_functions.h index bea30cf916..aaba082615 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -11,6 +11,10 @@ namespace openmc { +// TODO: cmath::M_PI has 3 more digits precision than the Fortran constant we +// use so for now we will reuse the Fortran constant until we are OK with +// modifying test results +const double PI = 3.1415926535898; //============================================================================== // NORMAL_PERCENTILE calculates the percentile of the standard normal diff --git a/tests/unit_tests/test_math.py b/tests/unit_tests/test_math.py index c8d33e6012..bfa021cebd 100644 --- a/tests/unit_tests/test_math.py +++ b/tests/unit_tests/test_math.py @@ -147,8 +147,8 @@ def test_maxwell_spectrum(): 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) + + assert ref_val == test_val def test_watt_spectrum(): @@ -158,8 +158,8 @@ def test_watt_spectrum(): 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) + + assert ref_val == test_val def test_broaden_wmp_polynomials(): From 468126f8c4f3a74e915aba1ddba6f14cc37dfecb Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sun, 6 May 2018 20:14:54 -0400 Subject: [PATCH 07/19] Added in Zernike polynomials to C++ code --- openmc/capi/math.py | 8 +- src/faddeeva/Faddeeva.h | 8 +- src/math.F90 | 162 ++++++---------------------------- src/math_functions.cpp | 135 ++++++++++++++++++++++++++-- src/math_functions.h | 20 +++-- tests/unit_tests/test_math.py | 39 +++++++- 6 files changed, 211 insertions(+), 161 deletions(-) diff --git a/openmc/capi/math.py b/openmc/capi/math.py index cd7ab40520..4de46691a6 100644 --- a/openmc/capi/math.py +++ b/openmc/capi/math.py @@ -11,15 +11,15 @@ _dll.t_percentile.restype = c_double _dll.t_percentile.argtypes = [POINTER(c_double), POINTER(c_int)] _dll.calc_pn.restype = c_double _dll.calc_pn.argtypes = [POINTER(c_int), POINTER(c_double)] +_dll.evaluate_legendre.restype = c_double +_dll.evaluate_legendre.argtypes = [POINTER(c_int), POINTER(c_double), + POINTER(c_double)] _dll.calc_rn.restype = None _dll.calc_rn.argtypes = [POINTER(c_int), ndpointer(c_double), ndpointer(c_double)] _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 = [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 +169,7 @@ def calc_zn(n, rho, phi): """ - num_bins = ((n + 1) * (n + 2)) / 2 + num_bins = ((n + 1) * (n + 2)) // 2 zn = np.zeros(num_bins, dtype=np.float64) _dll.calc_zn(c_int(n), c_double(rho), c_double(phi), zn) return zn diff --git a/src/faddeeva/Faddeeva.h b/src/faddeeva/Faddeeva.h index 4293861907..9e26bc1ed9 100644 --- a/src/faddeeva/Faddeeva.h +++ b/src/faddeeva/Faddeeva.h @@ -1,5 +1,5 @@ /* Copyright (c) 2012 Massachusetts Institute of Technology - * + * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including @@ -7,17 +7,17 @@ * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: - * + * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* Available at: http://ab-initio.mit.edu/Faddeeva diff --git a/src/math.F90 b/src/math.F90 index d7e335284c..a386747a46 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -43,14 +43,6 @@ module math real(C_DOUBLE) :: pnx end function calc_pn_cc - subroutine calc_rn_cc(n, uvw, rn) bind(C, name='calc_rn_c') - use ISO_C_BINDING - implicit none - integer(C_INT), value, intent(in) :: n - real(C_DOUBLE), intent(in) :: uvw(3) - real(C_DOUBLE), intent(in) :: rn(2 * n + 1) - end subroutine calc_rn_cc - pure function evaluate_legendre_cc(n, data, x) & bind(C, name='evaluate_legendre_c') result(val) use ISO_C_BINDING @@ -61,6 +53,23 @@ module math real(C_DOUBLE) :: val end function evaluate_legendre_cc + subroutine calc_rn_cc(n, uvw, rn) bind(C, name='calc_rn_c') + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: n + real(C_DOUBLE), intent(in) :: uvw(3) + real(C_DOUBLE), intent(out) :: rn(2 * n + 1) + end subroutine calc_rn_cc + + subroutine calc_zn_cc(n, rho, phi, zn) bind(C, name='calc_zn_c') + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: n + real(C_DOUBLE), value, intent(in) :: rho + real(C_DOUBLE), value, intent(in) :: phi + real(C_DOUBLE), intent(out) :: zn(((n + 1) * (n + 2)) / 2) + end subroutine calc_zn_cc + subroutine rotate_angle_cc(uvw, mu, phi) bind(C, name='rotate_angle_c') use ISO_C_BINDING implicit none @@ -200,102 +209,13 @@ contains !=============================================================================== subroutine calc_zn(n, rho, phi, zn) bind(C) - ! This procedure uses the modified Kintner's method for calculating Zernike - ! polynomials as outlined in Chong, C. W., Raveendran, P., & Mukundan, - ! R. (2003). A comparative analysis of algorithms for fast computation of - ! Zernike moments. Pattern Recognition, 36(3), 731-742. - integer(C_INT), intent(in) :: n ! Maximum order real(C_DOUBLE), intent(in) :: rho ! Radial location in the unit disk real(C_DOUBLE), intent(in) :: phi ! Theta (radians) location in the unit disk - real(C_DOUBLE), intent(out) :: zn(:) ! The resulting list of coefficients + ! The resulting list of coefficients + real(C_DOUBLE), intent(out) :: zn(((n + 1) * (n + 2)) / 2) - real(C_DOUBLE) :: sin_phi, cos_phi ! Sine and Cosine of phi - real(C_DOUBLE) :: sin_phi_vec(n+1) ! Contains sin(n*phi) - real(C_DOUBLE) :: cos_phi_vec(n+1) ! Contains cos(n*phi) - real(C_DOUBLE) :: zn_mat(n+1, n+1) ! Matrix form of the coefficients which is - ! easier to work with - real(C_DOUBLE) :: k1, k2, k3, k4 ! Variables for R_m_n calculation - integer(C_INT) :: i,p,q ! Loop counters - - real(C_DOUBLE), parameter :: SQRT_N_1(0:10) = [& - sqrt(1.0_8), sqrt(2.0_8), sqrt(3.0_8), sqrt(4.0_8), & - sqrt(5.0_8), sqrt(6.0_8), sqrt(7.0_8), sqrt(8.0_8), & - sqrt(9.0_8), sqrt(10.0_8), sqrt(11.0_8)] - real(C_DOUBLE), parameter :: SQRT_2N_2(0:10) = SQRT_N_1*sqrt(2.0_8) - - ! n == radial degree - ! m == azimuthal frequency - - ! ========================================================================== - ! Determine vector of sin(n*phi) and cos(n*phi). This takes advantage of the - ! following recurrence relations so that only a single sin/cos have to be - ! evaluated (http://mathworld.wolfram.com/Multiple-AngleFormulas.html) - ! - ! sin(nx) = 2 cos(x) sin((n-1)x) - sin((n-2)x) - ! cos(nx) = 2 cos(x) cos((n-1)x) - cos((n-2)x) - - sin_phi = sin(phi) - cos_phi = cos(phi) - - sin_phi_vec(1) = 1.0_8 - cos_phi_vec(1) = 1.0_8 - - sin_phi_vec(2) = 2.0_8 * cos_phi - cos_phi_vec(2) = cos_phi - - do i = 3, n+1 - sin_phi_vec(i) = 2.0_8 * cos_phi * sin_phi_vec(i-1) - sin_phi_vec(i-2) - cos_phi_vec(i) = 2.0_8 * cos_phi * cos_phi_vec(i-1) - cos_phi_vec(i-2) - end do - - do i = 1, n+1 - sin_phi_vec(i) = sin_phi_vec(i) * sin_phi - end do - - ! ========================================================================== - ! Calculate R_pq(rho) - - ! Fill the main diagonal first (Eq. 3.9 in Chong) - do p = 0, n - zn_mat(p+1, p+1) = rho**p - end do - - ! Fill in the second diagonal (Eq. 3.10 in Chong) - do q = 0, n-2 - zn_mat(q+2+1, q+1) = (q+2) * zn_mat(q+2+1, q+2+1) - (q+1) * zn_mat(q+1, q+1) - end do - - ! Fill in the rest of the values using the original results (Eq. 3.8 in Chong) - do p = 4, n - k2 = 2 * p * (p - 1) * (p - 2) - do q = p-4, 0, -2 - k1 = (p + q) * (p - q) * (p - 2) / 2 - k3 = -q**2*(p - 1) - p * (p - 1) * (p - 2) - k4 = -p * (p + q - 2) * (p - q - 2) / 2 - zn_mat(p+1, q+1) = ((k2 * rho**2 + k3) * zn_mat(p-2+1, q+1) + k4 * zn_mat(p-4+1, q+1)) / k1 - end do - end do - - ! Roll into a single vector for easier computation later - ! The vector is ordered (0,0), (1,-1), (1,1), (2,-2), (2,0), - ! (2, 2), .... in (n,m) indices - ! Note that the cos and sin vectors are offset by one - ! sin_phi_vec = [sin(x), sin(2x), sin(3x) ...] - ! cos_phi_vec = [1.0, cos(x), cos(2x)... ] - i = 1 - do p = 0, n - do q = -p, p, 2 - if (q < 0) then - zn(i) = zn_mat(p+1, abs(q)+1) * sin_phi_vec(abs(q)) * SQRT_2N_2(p) - else if (q == 0) then - zn(i) = zn_mat(p+1, q+1) * SQRT_N_1(p) - else - zn(i) = zn_mat(p+1, q+1) * cos_phi_vec(abs(q)+1) * SQRT_2N_2(p) - end if - i = i + 1 - end do - end do + call calc_zn_cc(n, rho, phi, zn) end subroutine calc_zn !=============================================================================== @@ -310,44 +230,11 @@ contains real(C_DOUBLE), intent(out) :: uvw(3) ! rotated directional cosine real(C_DOUBLE), optional :: phi ! azimuthal angle - real(C_DOUBLE) :: phi_ ! azimuthal angle - real(C_DOUBLE) :: sinphi ! sine of azimuthal angle - real(C_DOUBLE) :: cosphi ! cosine of azimuthal angle - real(C_DOUBLE) :: a ! sqrt(1 - mu^2) - real(C_DOUBLE) :: b ! sqrt(1 - w^2) - real(C_DOUBLE) :: u0 ! original cosine in x direction - real(C_DOUBLE) :: v0 ! original cosine in y direction - real(C_DOUBLE) :: w0 ! original cosine in z direction - - ! Copy original directional cosines - u0 = uvw0(1) - v0 = uvw0(2) - w0 = uvw0(3) - - ! Sample azimuthal angle in [0,2pi) if none provided + uvw = uvw0 if (present(phi)) then - phi_ = phi + call rotate_angle_cc(uvw, mu, phi) else - phi_ = TWO * PI * prn() - end if - - ! Precompute factors to save flops - sinphi = sin(phi_) - cosphi = cos(phi_) - a = sqrt(max(ZERO, ONE - mu*mu)) - b = sqrt(max(ZERO, ONE - w0*w0)) - - ! Need to treat special case where sqrt(1 - w**2) is close to zero by - ! expanding about the v component rather than the w component - if (b > 1e-10) then - uvw(1) = mu*u0 + a*(u0*w0*cosphi - v0*sinphi)/b - uvw(2) = mu*v0 + a*(v0*w0*cosphi + u0*sinphi)/b - uvw(3) = mu*w0 - a*b*cosphi - else - b = sqrt(ONE - v0*v0) - uvw(1) = mu*u0 + a*(u0*v0*cosphi + w0*sinphi)/b - uvw(2) = mu*v0 - a*b*cosphi - uvw(3) = mu*w0 + a*(v0*w0*cosphi - u0*sinphi)/b + call rotate_angle_cc(uvw, mu, -10._8) end if end subroutine rotate_angle @@ -492,6 +379,9 @@ contains factors(i+3) = factors(i+1)*(E + (ONE + TWO * i) * half_inv_dopp2) end if end do + + ! call broaden_wmp_polynomials_cc(E, dopp, n, factors) + end subroutine broaden_wmp_polynomials end module math diff --git a/src/math_functions.cpp b/src/math_functions.cpp index 1466bde547..9f698f42a9 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -169,9 +169,10 @@ double __attribute__ ((const)) calc_pn_c(int n, double x) { double __attribute__ ((const)) evaluate_legendre_c(int n, double data[], double x) { double val; + int l; val = 0.5 * data[0]; - for (int l = 1; l < n; l++) { + for (l = 1; l < n; l++) { val += (static_cast(l) + 0.5) * data[l] * calc_pn_c(l, x); } return val; @@ -577,6 +578,122 @@ void calc_rn_c(int n, double uvw[3], double rn[]){ } } +//============================================================================== +// CALC_ZN calculates the n-th order modified Zernike polynomial moment for a +// given angle (rho, theta) location in the unit disk. The normalization of the +// polynomials is such tha the integral of Z_pq*Z_pq over the unit disk is +// exactly pi +//============================================================================== + +void calc_zn_c(int n, double rho, double phi, double zn[]) { + // This procedure uses the modified Kintner's method for calculating Zernike + // polynomials as outlined in Chong, C. W., Raveendran, P., & Mukundan, + // R. (2003). A comparative analysis of algorithms for fast computation of + // Zernike moments. Pattern Recognition, 36(3), 731-742. + double sin_phi; // Cosine of phi + double cos_phi; // Sine of phi + double sin_phi_vec[n + 1]; // Sin[n * phi] + double cos_phi_vec[n + 1]; // Cos[n * phi] + double zn_mat[n + 1][n + 1]; // Matrix forms of the coefficients which are + // easier to work with + // Variables for R_m_n calculation + double k1; + double k2; + double k3; + double k4; + // Loop counters + int i; + int p; + int q; + + const double SQRT_N_1[11] = + {std::sqrt(1.), std::sqrt(2.), std::sqrt(3.), std::sqrt(4.), + std::sqrt(5.), std::sqrt(6.), std::sqrt(7.), std::sqrt(8.), + std::sqrt(9.), std::sqrt(10.), std::sqrt(11.)}; + + const double SQRT_2N_2[11] = + {std::sqrt(2.) * std::sqrt(1.), std::sqrt(2.) * std::sqrt(2.), + std::sqrt(2.) * std::sqrt(3.), std::sqrt(2.) * std::sqrt(4.), + std::sqrt(2.) * std::sqrt(5.), std::sqrt(2.) * std::sqrt(6.), + std::sqrt(2.) * std::sqrt(7.), std::sqrt(2.) * std::sqrt(8.), + std::sqrt(2.) * std::sqrt(9.), std::sqrt(2.) * std::sqrt(10.), + std::sqrt(2.) * std::sqrt(11.)}; + + // n == radial degree + // m == azimuthal frequency + + // =========================================================================== + // Determine vector of sin(n*phi) and cos(n*phi). This takes advantage of the + // following recurrence relations so that only a single sin/cos have to be + // evaluated (http://mathworld.wolfram.com/Multiple-AngleFormulas.html) + // + // sin(nx) = 2 cos(x) sin((n-1)x) - sin((n-2)x) + // cos(nx) = 2 cos(x) cos((n-1)x) - cos((n-2)x) + + sin_phi = std::sin(phi); + cos_phi = std::cos(phi); + + sin_phi_vec[0] = 1.0; + cos_phi_vec[0] = 1.0; + + sin_phi_vec[1] = 2.0 * cos_phi; + cos_phi_vec[1] = cos_phi; + + for (i = 2; i <= n; i++) { + sin_phi_vec[i] = 2. * cos_phi * sin_phi_vec[i - 1] - sin_phi_vec[i - 2]; + cos_phi_vec[i] = 2. * cos_phi * cos_phi_vec[i - 1] - cos_phi_vec[i - 2]; + } + + for (i = 0; i <= n; i++) { + sin_phi_vec[i] *= sin_phi; + } + + // =========================================================================== + // Calculate R_pq(rho) + + // Fill the main diagonal first (Eq 3.9 in Chong) + for (p = 0; p <= n; p++) { + zn_mat[p][p] = std::pow(rho, p); + } + + // Fill the 2nd diagonal (Eq 3.10 in Chong) + for (q = 0; q <= n - 2; q++) { + zn_mat[q][q+2] = (q + 2) * zn_mat[q+2][q+2] - (q + 1) * zn_mat[q][q]; + } + + // Fill in the rest of the values using the original results (Eq. 3.8 in Chong) + for (p = 4; p <= n; p++) { + k2 = static_cast (2 * p * (p - 1) * (p - 2)); + for (q = p - 4; q >= 0; q -= 2) { + k1 = static_cast((p + q) * (p - q) * (p - 2)) / 2.; + k3 = static_cast(-q * q * (p - 1) - p * (p - 1) * (p - 2)); + k4 = static_cast(-p * (p + q - 2) * (p - q - 2)) / 2.; + zn_mat[q][p] = + ((k2 * rho * rho + k3) * zn_mat[q][p-2] + k4 * zn_mat[q][p-4]) / k1; + } + } + + // Roll into a single vector for easier computation later + // The vector is ordered (0,0), (1,-1), (1,1), (2,-2), (2,0), + // (2, 2), .... in (n,m) indices + // Note that the cos and sin vectors are offset by one + // sin_phi_vec = [sin(x), sin(2x), sin(3x) ...] + // cos_phi_vec = [1.0, cos(x), cos(2x)... ] + i = 0; + for (p = 0; p <= n; p++) { + for (q = -p; q <= p; q += 2) { + if (q < 0) { + zn[i] = zn_mat[std::abs(q)][p] * sin_phi_vec[std::abs(q) - 1] * SQRT_2N_2[p]; + } else if (q == 0) { + zn[i] = zn_mat[q][p] * SQRT_N_1[p]; + } else { + zn[i] = zn_mat[q][p] * cos_phi_vec[q] * SQRT_2N_2[p]; + } + i++; + } + } + +} //============================================================================== // ROTATE_ANGLE rotates direction std::cosines through a polar angle whose @@ -678,8 +795,8 @@ double watt_spectrum_c(double a, double b) { // FADDEEVA the Faddeeva function, using Stephen Johnson's implementation //============================================================================== -// std::complex faddeeva_c(std::complex z) { -// std::complex wv; // The resultant w(z) value +// double complex __attribute__ ((const)) faddeeva_c(double complex z) { +// double complex wv; // The resultant w(z) value // double relerr; // Target relative error in the inner loop of MIT Faddeeva // // Technically, the value we want is given by the equation: @@ -698,19 +815,19 @@ double watt_spectrum_c(double a, double b) { // // Note that faddeeva_w will interpret zero as machine epsilon // relerr = 0.; -// if (z.imag() > 0.) { -// wv = Faddeeva::w(z, relerr); +// if (cimag(z) > 0.) { +// wv = Faddeeva_w(z, relerr); // } else { -// wv = -std::conj(Faddeeva::w(std::conj(z), relerr)); +// wv = -conj(Faddeeva_w(conj(z), relerr)); // } // return wv; // } -// std::complex w_derivative_c(std::complex z, int order){ -// std::complex wv; // The resultant w(z) value +// double complex w_derivative_c(double complex z, int order){ +// double complex wv; // The resultant w(z) value -// const std::complex twoi_sqrtpi(0.0, 2.0 / std::sqrt(PI)); +// const double complex twoi_sqrtpi = 2.0 / std::sqrt(PI) * I; // switch(order) { // case 0: diff --git a/src/math_functions.h b/src/math_functions.h index aaba082615..4e7b2e8c64 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -2,11 +2,11 @@ #define MATH_FUNCTIONS_H #include -#include +#include #include #include "random_lcg.h" -// #include "faddeeva/Faddeeva.hh" +// #include "faddeeva/Faddeeva.h" namespace openmc { @@ -51,6 +51,15 @@ extern "C" double evaluate_legendre_c(int n, double data[], double x) extern "C" void calc_rn_c(int n, double uvw[3], double rn[]); +//============================================================================== +// CALC_ZN calculates the n-th order modified Zernike polynomial moment for a +// given angle (rho, theta) location in the unit disk. The normalization of the +// polynomials is such tha the integral of Z_pq*Z_pq over the unit disk is +// exactly pi +//============================================================================== + +extern "C" void calc_zn_c(int n, double rho, double phi, double zn[]); + //============================================================================== // ROTATE_ANGLE rotates direction cosines through a polar angle whose cosine is // mu and through an azimuthal angle sampled uniformly. Note that this is done @@ -83,16 +92,17 @@ extern "C" double watt_spectrum_c(double a, double b); // FADDEEVA the Faddeeva function, using Stephen Johnson's implementation //============================================================================== -// extern "C" std::complex faddeeva_c(std::complex z); +// extern "C" double complex faddeeva_c(double complex z) __attribute__ ((const)); -// extern "C" std::complex w_derivative_c(std::complex z, int order); +// extern "C" double complex w_derivative_c(double complex z, int order); //============================================================================== // BROADEN_WMP_POLYNOMIALS Doppler broadens the windowed multipole curvefit. // The curvefit is a polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E) ... //============================================================================== -extern "C" void broaden_wmp_polynomials_c(double E, double dopp, int n, double factors[]); +extern "C" void broaden_wmp_polynomials_c(double E, double dopp, int n, + double factors[]); } // namespace openmc #endif // MATH_FUNCTIONS_H \ No newline at end of file diff --git a/tests/unit_tests/test_math.py b/tests/unit_tests/test_math.py index bfa021cebd..03309bafe8 100644 --- a/tests/unit_tests/test_math.py +++ b/tests/unit_tests/test_math.py @@ -115,7 +115,38 @@ def test_calc_rn(): def test_calc_zn(): - pass + n = 10 + rho = 0.5 + phi = 0.5 + + # Reference solution from running the Fortran implementation + ref_vals = np.array( + [1.00000000e+00, 4.79425539e-01, 8.77582562e-01, + 5.15293637e-01, -8.66025404e-01, 3.30866239e-01, + 3.52667735e-01, -8.47512624e-01, -1.55136145e+00, + 2.50093775e-02, 1.79715684e-01, -1.33048245e+00, + -2.79508497e-01, -8.54292956e-01, -8.22482403e-02, + 6.47865100e-02, -1.18780200e+00, 5.18993370e-01, + 9.50010991e-01, -8.42327938e-02, -8.67263404e-02, + 8.25035501e-03, -7.44248626e-01, 1.52505281e+00, + 1.15751620e+00, 9.79225149e-01, 3.40611006e-01, + -5.78783240e-02, -1.09619759e-02, -3.17938327e-01, + 1.90147482e+00, 2.84658914e-01, 5.21064646e-01, + 1.34842791e-01, 4.25607546e-01, -2.92642715e-02, + -1.25423479e-02, -4.67751162e-02, 1.50696182e+00, + -6.13603897e-01, -8.67187500e-01, -3.93990531e-01, + -6.89672461e-01, 3.28139254e-01, -1.08327149e-02, + -8.53837419e-03, 7.04712042e-02, 7.73660979e-01, + -1.63799891e+00, -8.12396290e-01, -1.48708143e+00, + -1.16158437e-01, -1.03565982e+00, 1.88131088e-01, + -1.84122553e-03, -4.39233743e-03, 9.01295675e-02, + 1.32511582e-01, -1.83260987e+00, -7.93994967e-01, + -2.97978009e-01, -5.09818305e-01, 8.38707753e-01, + -9.29602211e-01, 7.78441102e-02, 1.29931014e-03]) + + test_vals = openmc.capi.math.calc_zn(n, rho, phi) + + assert np.allclose(ref_vals, test_vals) def test_rotate_angle(): @@ -128,7 +159,7 @@ def test_rotate_angle(): test_uvw = openmc.capi.math.rotate_angle(uvw0, mu, phi) - assert np.allclose(ref_uvw, test_uvw) + assert np.array_equal(ref_uvw, test_uvw) # Repeat for mu = 1 (no change) mu = 1. @@ -136,7 +167,7 @@ def test_rotate_angle(): test_uvw = openmc.capi.math.rotate_angle(uvw0, mu, phi) - assert np.allclose(ref_uvw, test_uvw) + assert np.array_equal(ref_uvw, test_uvw) # Need to test phi=None somehow... @@ -180,3 +211,5 @@ def test_broaden_wmp_polynomials(): test_val = openmc.capi.math.broaden_wmp_polynomials(test_E, test_dopp, n) assert np.allclose(ref_val, test_val) + +test_calc_zn() \ No newline at end of file From 044934f93d065888d67dec704f0013aa99eca7d3 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Mon, 7 May 2018 20:07:05 -0400 Subject: [PATCH 08/19] Implemented broaden_wmp_polynomials in C++ --- src/math.F90 | 46 +---------------------------------- src/math_functions.cpp | 14 +++++++---- src/math_functions.h | 2 ++ tests/unit_tests/test_math.py | 9 +++---- 4 files changed, 16 insertions(+), 55 deletions(-) diff --git a/src/math.F90 b/src/math.F90 index a386747a46..6edf2f1c3e 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -336,51 +336,7 @@ contains integer(C_INT), intent(in) :: n ! number of components to polynomial real(C_DOUBLE), intent(out):: factors(n) ! output leading coefficient - integer :: i - - real(8) :: sqrtE ! sqrt(energy) - real(8) :: beta ! sqrt(atomic weight ratio * E / kT) - real(8) :: half_inv_dopp2 ! 0.5 / dopp**2 - real(8) :: quarter_inv_dopp4 ! 0.25 / dopp**4 - real(8) :: erf_beta ! error function of beta - real(8) :: exp_m_beta2 ! exp(-beta**2) - - sqrtE = sqrt(E) - beta = sqrtE * dopp - half_inv_dopp2 = HALF / dopp**2 - quarter_inv_dopp4 = half_inv_dopp2**2 - - if (beta > 6.0_8) then - ! Save time, ERF(6) is 1 to machine precision. - ! beta/sqrtpi*exp(-beta**2) is also approximately 1 machine epsilon. - erf_beta = ONE - exp_m_beta2 = ZERO - else - erf_beta = erf(beta) - exp_m_beta2 = exp(-beta**2) - end if - - ! Assume that, for sure, we'll use a second order (1/E, 1/V, const) - ! fit, and no less. - - factors(1) = erf_beta / E - factors(2) = ONE / sqrtE - factors(3) = factors(1) * (half_inv_dopp2 + E) & - + exp_m_beta2 / (beta * SQRT_PI) - - ! Perform recursive broadening of high order components - do i = 1, n-3 - if (i /= 1) then - factors(i+3) = -factors(i-1) * (i - ONE) * i * quarter_inv_dopp4 & - + factors(i+1) * (E + (ONE + TWO * i) * half_inv_dopp2) - else - ! Although it's mathematically identical, factors(0) will contain - ! nothing, and we don't want to have to worry about memory. - factors(i+3) = factors(i+1)*(E + (ONE + TWO * i) * half_inv_dopp2) - end if - end do - - ! call broaden_wmp_polynomials_cc(E, dopp, n, factors) + call broaden_wmp_polynomials_cc(E, dopp, n, factors) end subroutine broaden_wmp_polynomials diff --git a/src/math_functions.cpp b/src/math_functions.cpp index 9f698f42a9..7ab2a8450b 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -827,7 +827,7 @@ double watt_spectrum_c(double a, double b) { // double complex w_derivative_c(double complex z, int order){ // double complex wv; // The resultant w(z) value -// const double complex twoi_sqrtpi = 2.0 / std::sqrt(PI) * I; +// const double complex twoi_sqrtpi = 2.0 / SQRT_PI * I; // switch(order) { // case 0: @@ -859,6 +859,7 @@ void broaden_wmp_polynomials_c(double E, double dopp, int n, double factors[]) { double erf_beta; // error function of beta double exp_m_beta2; // exp(-beta**2) int i; + double ip1_dbl; sqrtE = std::sqrt(E); beta = sqrtE * dopp; @@ -881,17 +882,20 @@ void broaden_wmp_polynomials_c(double E, double dopp, int n, double factors[]) { factors[0] = erf_beta / E; factors[1] = 1. / sqrtE; factors[2] = factors[0] * (half_inv_dopp2 + E) + exp_m_beta2 / - (beta * std::sqrt(PI)); + (beta * SQRT_PI); // Perform recursive broadening of high order components for (i = 0; i < n - 3; i++) { + ip1_dbl = static_cast(i + 1); if (i != 0) { - factors[i + 3] = -factors[i - 1] * (i - 1.) * i * quarter_inv_dopp4 + - factors[i + 1] * (E + (1. + 2. * i) * half_inv_dopp2); + factors[i + 3] = -factors[i - 1] * (ip1_dbl - 1.) * ip1_dbl * + quarter_inv_dopp4 + factors[i + 1] * + (E + (1. + 2. * ip1_dbl) * half_inv_dopp2); } else { // Although it's mathematically identical, factors[0] will contain // nothing, and we don't want to have to worry about memory. - factors[i + 3] = factors[i + 1]*(E + (1. + 2. * i) * half_inv_dopp2); + factors[i + 3] = factors[i + 1] * + (E + (1. + 2. * ip1_dbl) * half_inv_dopp2); } } } diff --git a/src/math_functions.h b/src/math_functions.h index 4e7b2e8c64..ab35d140a2 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -16,6 +16,8 @@ namespace openmc { // modifying test results const double PI = 3.1415926535898; +const double SQRT_PI = std::sqrt(PI); + //============================================================================== // NORMAL_PERCENTILE calculates the percentile of the standard normal // distribution with a specified probability level diff --git a/tests/unit_tests/test_math.py b/tests/unit_tests/test_math.py index 03309bafe8..99e16712c9 100644 --- a/tests/unit_tests/test_math.py +++ b/tests/unit_tests/test_math.py @@ -199,17 +199,16 @@ def test_broaden_wmp_polynomials(): # 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] + n = 6 + + ref_val = [2., 1.41421356, 1.0001, 0.70731891, 0.50030001, 0.353907] 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] + ref_val = [1.99999885, 1.41421356, 1.04, 0.79195959, 0.6224, 0.50346003] test_val = openmc.capi.math.broaden_wmp_polynomials(test_E, test_dopp, n) assert np.allclose(ref_val, test_val) - -test_calc_zn() \ No newline at end of file From 72d84bc286b946723e278fdcc6474d2e93c26a77 Mon Sep 17 00:00:00 2001 From: Alex Lindsay Date: Mon, 7 May 2018 14:42:09 -0600 Subject: [PATCH 09/19] Use consistent normalization for Zernikes. --- openmc/filter_expansion.py | 13 +++++++------ src/math.F90 | 12 +++--------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/openmc/filter_expansion.py b/openmc/filter_expansion.py index 872eaac9f5..b092410828 100644 --- a/openmc/filter_expansion.py +++ b/openmc/filter_expansion.py @@ -318,15 +318,15 @@ class ZernikeFilter(ExpansionFilter): This filter allows scores to be multiplied by Zernike polynomials of the particle's position normalized to a given unit circle, up to a - user-specified order. The Zernike polynomials follow the definition by `Noll - `_ and are defined as + user-specified order. The standard Zernike polynomials follow the definition by + Born and Wolf, *Principles of Optics* and are defined as .. math:: - Z_n^m(\rho, \theta) = \sqrt{2n + 2} R_n^m(\rho) \cos (m\theta), \quad m > 0 + Z_n^m(\rho, \theta) = R_n^m(\rho) \cos (m\theta), \quad m > 0 - Z_n^{m}(\rho, \theta) = \sqrt{2n + 2} R_n^{m}(\rho) \sin (m\theta), \quad m < 0 + Z_n^{m}(\rho, \theta) = R_n^{m}(\rho) \sin (m\theta), \quad m < 0 - Z_n^{m}(\rho, \theta) = \sqrt{n + 1} R_n^{m}(\rho), \quad m = 0 + Z_n^{m}(\rho, \theta) = R_n^{m}(\rho), \quad m = 0 where the radial polynomials are @@ -335,7 +335,8 @@ class ZernikeFilter(ExpansionFilter): \frac{n+m}{2} - k)! (\frac{n-m}{2} - k)!} \rho^{n-2k}. With this definition, the integral of :math:`(Z_n^m)^2` over the unit disk - is exactly :math:`\pi` for each polynomial. + is :math:`\frac{\epsilon_m\pi}{2n+2}` for each polynomial where :math:`\epsilon_m` is + 2 if :math:`m` equals 0 and 1 otherwise. Specifying a filter with order N tallies moments for all :math:`n` from 0 to N and each value of :math:`m`. The ordering of the Zernike polynomial diff --git a/src/math.F90 b/src/math.F90 index 20bacf88cf..c633131645 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -600,12 +600,6 @@ contains real(8) :: k1, k2, k3, k4 ! Variables for R_m_n calculation integer :: i,p,q ! Loop counters - real(8), parameter :: SQRT_N_1(0:10) = [& - sqrt(1.0_8), sqrt(2.0_8), sqrt(3.0_8), sqrt(4.0_8), & - sqrt(5.0_8), sqrt(6.0_8), sqrt(7.0_8), sqrt(8.0_8), & - sqrt(9.0_8), sqrt(10.0_8), sqrt(11.0_8)] - real(8), parameter :: SQRT_2N_2(0:10) = SQRT_N_1*sqrt(2.0_8) - ! n == radial degree ! m == azimuthal frequency @@ -669,11 +663,11 @@ contains do p = 0, n do q = -p, p, 2 if (q < 0) then - zn(i) = zn_mat(p+1, abs(q)+1) * sin_phi_vec(abs(q)) * SQRT_2N_2(p) + zn(i) = zn_mat(p+1, abs(q)+1) * sin_phi_vec(abs(q)) else if (q == 0) then - zn(i) = zn_mat(p+1, q+1) * SQRT_N_1(p) + zn(i) = zn_mat(p+1, q+1) else - zn(i) = zn_mat(p+1, q+1) * cos_phi_vec(abs(q)+1) * SQRT_2N_2(p) + zn(i) = zn_mat(p+1, q+1) * cos_phi_vec(abs(q)+1) end if i = i + 1 end do From a73f633d2480d8c64677610f7c4479a3c96d921b Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sat, 12 May 2018 15:43:05 -0400 Subject: [PATCH 10/19] Converted calc_rn and calc_pn to provide all values at once instead of only for the requested value of n. Also converted calc_pn to use the recursive definition of the legendre polys --- openmc/capi/math.py | 13 +- src/math.F90 | 80 +- src/math_functions.cpp | 851 ++++++++++----------- src/math_functions.h | 32 +- src/tallies/tally.F90 | 2 +- src/tallies/tally_filter_legendre.F90 | 11 +- src/tallies/tally_filter_sph_harm.F90 | 26 +- src/tallies/tally_filter_sptl_legendre.F90 | 15 +- src/tallies/tally_filter_zernike.F90 | 2 +- tests/unit_tests/test_math.py | 17 +- 10 files changed, 514 insertions(+), 535 deletions(-) diff --git a/openmc/capi/math.py b/openmc/capi/math.py index 4de46691a6..c46390bced 100644 --- a/openmc/capi/math.py +++ b/openmc/capi/math.py @@ -9,8 +9,9 @@ from . import _dll # _dll.normal_percentile.argtypes = [POINTER(c_double)] _dll.t_percentile.restype = c_double _dll.t_percentile.argtypes = [POINTER(c_double), POINTER(c_int)] -_dll.calc_pn.restype = c_double -_dll.calc_pn.argtypes = [POINTER(c_int), POINTER(c_double)] +_dll.calc_pn.restype = None +_dll.calc_pn.argtypes = [POINTER(c_int), POINTER(c_double), + ndpointer(c_double)] _dll.evaluate_legendre.restype = c_double _dll.evaluate_legendre.argtypes = [POINTER(c_int), POINTER(c_double), POINTER(c_double)] @@ -95,7 +96,9 @@ def calc_pn(n, x): """ - return _dll.calc_pn(c_int(n), c_double(x)) + pnx = np.empty(n + 1, dtype=np.float64) + _dll.calc_pn(c_int(n), c_double(x), pnx) + return pnx def evaluate_legendre(data, x): @@ -124,7 +127,7 @@ def evaluate_legendre(data, x): def calc_rn(n, uvw): """ Calculate the n-th order real Spherical Harmonics for a given angle; - all Rn,m values are provided (where -n <= m <= n). + all Rn,m values are provided for all n (where -n <= m <= n). Parameters ---------- @@ -140,7 +143,7 @@ def calc_rn(n, uvw): """ - num_nm = 2 * n + 1 + num_nm = (n + 1) * (n + 1) rn = np.empty(num_nm, dtype=np.float64) uvw_arr = np.array(uvw, dtype=np.float64) _dll.calc_rn(c_int(n), uvw_arr, rn) diff --git a/src/math.F90 b/src/math.F90 index 6edf2f1c3e..7be3e5a6dc 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -26,24 +26,24 @@ module math interface - pure function t_percentile_cc(p, df) bind(C, name='t_percentile_c') & + pure function t_percentile_c_intfc(p, df) bind(C, name='t_percentile_c') & result(t) use ISO_C_BINDING implicit none real(C_DOUBLE), value, intent(in) :: p integer(C_INT), value, intent(in) :: df real(C_DOUBLE) :: t - end function t_percentile_cc + end function t_percentile_c_intfc - pure function calc_pn_cc(n, x) bind(C, name='calc_pn_c') result(pnx) + pure subroutine calc_pn_c_intfc(n, x, pnx) bind(C, name='calc_pn_c') use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: n real(C_DOUBLE), value, intent(in) :: x - real(C_DOUBLE) :: pnx - end function calc_pn_cc + real(C_DOUBLE), intent(out) :: pnx(n + 1) + end subroutine calc_pn_c_intfc - pure function evaluate_legendre_cc(n, data, x) & + pure function evaluate_legendre_c_intfc(n, data, x) & bind(C, name='evaluate_legendre_c') result(val) use ISO_C_BINDING implicit none @@ -51,67 +51,67 @@ module math real(C_DOUBLE), intent(in) :: data(n) real(C_DOUBLE), value, intent(in) :: x real(C_DOUBLE) :: val - end function evaluate_legendre_cc + end function evaluate_legendre_c_intfc - subroutine calc_rn_cc(n, uvw, rn) bind(C, name='calc_rn_c') + pure subroutine calc_rn_c_intfc(n, uvw, rn) bind(C, name='calc_rn_c') use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: n real(C_DOUBLE), intent(in) :: uvw(3) real(C_DOUBLE), intent(out) :: rn(2 * n + 1) - end subroutine calc_rn_cc + end subroutine calc_rn_c_intfc - subroutine calc_zn_cc(n, rho, phi, zn) bind(C, name='calc_zn_c') + pure subroutine calc_zn_c_intfc(n, rho, phi, zn) bind(C, name='calc_zn_c') use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: n real(C_DOUBLE), value, intent(in) :: rho real(C_DOUBLE), value, intent(in) :: phi real(C_DOUBLE), intent(out) :: zn(((n + 1) * (n + 2)) / 2) - end subroutine calc_zn_cc + end subroutine calc_zn_c_intfc - subroutine rotate_angle_cc(uvw, mu, phi) bind(C, name='rotate_angle_c') + subroutine rotate_angle_c_intfc(uvw, mu, phi) bind(C, name='rotate_angle_c') use ISO_C_BINDING implicit none real(C_DOUBLE), intent(inout) :: uvw(3) real(C_DOUBLE), value, intent(in) :: mu real(C_DOUBLE), value, intent(in) :: phi - end subroutine rotate_angle_cc + end subroutine rotate_angle_c_intfc - function maxwell_spectrum_cc(T) bind(C, name='maxwell_spectrum_c') & + function maxwell_spectrum_c_intfc(T) bind(C, name='maxwell_spectrum_c') & result(E_out) use ISO_C_BINDING implicit none real(C_DOUBLE), value, intent(in) :: T real(C_DOUBLE) :: E_out - end function maxwell_spectrum_cc + end function maxwell_spectrum_c_intfc - function watt_spectrum_cc(a, b) bind(C, name='watt_spectrum_c') & + function watt_spectrum_c_intfc(a, b) bind(C, name='watt_spectrum_c') & result(E_out) use ISO_C_BINDING implicit none real(C_DOUBLE), value, intent(in) :: a real(C_DOUBLE), value, intent(in) :: b real(C_DOUBLE) :: E_out - end function watt_spectrum_cc + end function watt_spectrum_c_intfc - function faddeeva_cc(z) bind(C, name='faddeeva_c') result(wv) + function faddeeva_c_intfc(z) bind(C, name='faddeeva_c') result(wv) use ISO_C_BINDING implicit none complex(C_DOUBLE_COMPLEX), value, intent(in) :: z complex(C_DOUBLE_COMPLEX) :: wv - end function faddeeva_cc + end function faddeeva_c_intfc - function w_derivative_cc(z, order) bind(C, name='w_derivative_c') & + function w_derivative_c_intfc(z, order) bind(C, name='w_derivative_c') & result(wv) use ISO_C_BINDING implicit none complex(C_DOUBLE_COMPLEX), value, intent(in) :: z integer(C_INT), value, intent(in) :: order complex(C_DOUBLE_COMPLEX) :: wv - end function w_derivative_cc + end function w_derivative_c_intfc - subroutine broaden_wmp_polynomials_cc(E, dopp, n, factors) & + subroutine broaden_wmp_polynomials_c_intfc(E, dopp, n, factors) & bind(C, name='broaden_wmp_polynomials_c') use ISO_C_BINDING implicit none @@ -119,7 +119,7 @@ module math real(C_DOUBLE), value, intent(in) :: dopp integer(C_INT), value, intent(in) :: n real(C_DOUBLE), intent(inout) :: factors(n) - end subroutine broaden_wmp_polynomials_cc + end subroutine broaden_wmp_polynomials_c_intfc function faddeeva_w(z, relerr) bind(C, name='Faddeeva_w') result(w) use ISO_C_BINDING @@ -143,7 +143,7 @@ contains integer(C_INT), intent(in) :: df ! degrees of freedom real(C_DOUBLE) :: t ! corresponding t-value - t = t_percentile_cc(p, df) + t = t_percentile_c_intfc(p, df) end function t_percentile @@ -157,18 +157,18 @@ contains ! the return value will be 1.0. !=============================================================================== - pure function calc_pn(n, x) result(pnx) bind(C) + pure subroutine calc_pn(n, x, pnx) bind(C) integer(C_INT), intent(in) :: n ! Legendre order requested real(C_DOUBLE), intent(in) :: x ! Independent variable the Legendre is to ! be evaluated at; x must be in the ! domain [-1,1] - real(C_DOUBLE) :: pnx ! The Legendre poly of order n evaluated - ! at x + real(C_DOUBLE), intent(out) :: pnx(n + 1) ! The Legendre polys of order n + ! evaluated at x - pnx = calc_pn_cc(n, x) + call calc_pn_c_intfc(n, x, pnx) - end function calc_pn + end subroutine calc_pn !=============================================================================== ! EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients @@ -181,7 +181,7 @@ contains real(C_DOUBLE), intent(in) :: x real(C_DOUBLE) :: val - val = evaluate_legendre_cc(size(data), data, x) + val = evaluate_legendre_c_intfc(size(data) - 1, data, x) end function evaluate_legendre @@ -190,14 +190,14 @@ contains ! (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) !=============================================================================== - subroutine calc_rn(n, uvw, rn) bind(C) + pure subroutine calc_rn(n, uvw, rn) bind(C) integer(C_INT), intent(in) :: n ! Order requested real(C_DOUBLE), intent(in) :: uvw(3) ! Direction of travel; ! assumed to be on unit sphere - real(C_DOUBLE) :: rn(2*n + 1) ! The resultant R_n(uvw) + real(C_DOUBLE), intent(out) :: rn(2*n + 1) ! The resultant R_n(uvw) - call calc_rn_cc(n, uvw, rn) + call calc_rn_c_intfc(n, uvw, rn) end subroutine calc_rn @@ -208,14 +208,14 @@ contains ! exactly pi !=============================================================================== - subroutine calc_zn(n, rho, phi, zn) bind(C) + pure subroutine calc_zn(n, rho, phi, zn) bind(C) integer(C_INT), intent(in) :: n ! Maximum order real(C_DOUBLE), intent(in) :: rho ! Radial location in the unit disk real(C_DOUBLE), intent(in) :: phi ! Theta (radians) location in the unit disk ! The resulting list of coefficients real(C_DOUBLE), intent(out) :: zn(((n + 1) * (n + 2)) / 2) - call calc_zn_cc(n, rho, phi, zn) + call calc_zn_c_intfc(n, rho, phi, zn) end subroutine calc_zn !=============================================================================== @@ -232,9 +232,9 @@ contains uvw = uvw0 if (present(phi)) then - call rotate_angle_cc(uvw, mu, phi) + call rotate_angle_c_intfc(uvw, mu, phi) else - call rotate_angle_cc(uvw, mu, -10._8) + call rotate_angle_c_intfc(uvw, mu, -10._8) end if end subroutine rotate_angle @@ -251,7 +251,7 @@ contains real(C_DOUBLE), intent(in) :: T ! tabulated function of incoming E real(C_DOUBLE) :: E_out ! sampled energy - E_out = maxwell_spectrum_cc(T) + E_out = maxwell_spectrum_c_intfc(T) end function maxwell_spectrum @@ -269,7 +269,7 @@ contains real(C_DOUBLE), intent(in) :: b ! Watt parameter b real(C_DOUBLE) :: E_out ! energy of emitted neutron - E_out = watt_spectrum_cc(a, b) + E_out = watt_spectrum_c_intfc(a, b) end function watt_spectrum @@ -336,7 +336,7 @@ contains integer(C_INT), intent(in) :: n ! number of components to polynomial real(C_DOUBLE), intent(out):: factors(n) ! output leading coefficient - call broaden_wmp_polynomials_cc(E, dopp, n, factors) + call broaden_wmp_polynomials_c_intfc(E, dopp, n, factors) end subroutine broaden_wmp_polynomials diff --git a/src/math_functions.cpp b/src/math_functions.cpp index b3478f065a..c9d6fc3590 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -8,10 +8,7 @@ namespace openmc { // distribution with a specified probability level //============================================================================== -double __attribute__ ((const)) normal_percentile_c(double p) { - - // return gsl_cdf_ugaussian_Pinv(p); - +double __attribute__ ((const)) normal_percentile_c(const double p) { double z; double q; double r; @@ -68,10 +65,7 @@ double __attribute__ ((const)) normal_percentile_c(double p) { // a specified probability level and number of degrees of freedom //============================================================================== -double __attribute__ ((const)) t_percentile_c(double p, int df){ - - // return gsl_cdf_tdist_Pinv(p, static_cast df); - +double __attribute__ ((const)) t_percentile_c(const double p, const int df){ double t; double n; double k; @@ -108,57 +102,23 @@ double __attribute__ ((const)) t_percentile_c(double p, int df){ } //============================================================================== -// CALC_PN calculates the n-th order Legendre polynomial at the value of x. +// CALC_PN calculates the n-th order Legendre polynomials at the value of x. //============================================================================== -double __attribute__ ((const)) calc_pn_c(int n, double x) { +void calc_pn_c(const int n, const double x, double pnx[]) { + int l; - // return gsl_sf_legendre_Pl(l, x); - - double pnx; - - switch(n) { - case 0: - pnx = 1.; - break; - case 1: - pnx = x; - break; - case 2: - pnx = 1.5 * x * x - 0.5; - break; - case 3: - pnx = 2.5 * x * x * x - 1.5 * x; - break; - case 4: - pnx = 4.375 * std::pow(x, 4.) - 3.75 * x * x + 0.375; - break; - case 5: - pnx = 7.875 * std::pow(x, 5.) - 8.75 * x * x * x + 1.875 * x; - break; - case 6: - pnx = 14.4375 * std::pow(x, 6.) - 19.6875 * std::pow(x, 4.) + - 6.5625 * x * x - 0.3125; - break; - case 7: - pnx = 26.8125 * std::pow(x, 7.) - 43.3125 * std::pow(x, 5.) + - 19.6875 * x * x * x - 2.1875 * x; - break; - case 8: - pnx = 50.2734375 * std::pow(x, 8.) - 93.84375 * std::pow(x, 6.) + - 54.140625 * std::pow(x, 4.) - 9.84375 * x * x + 0.2734375; - break; - case 9: - pnx = 94.9609375 * std::pow(x, 9.) - 201.09375 * std::pow(x, 7.) + - 140.765625 * std::pow(x, 5.) - 36.09375 * x * x * x + 2.4609375 * x; - break; - case 10: - pnx = 180.42578125 * std::pow(x, 10.) - 427.32421875 * std::pow(x, 8.) + - 351.9140625 * std::pow(x, 6.) - 117.3046875 * std::pow(x, 4.) + - 13.53515625 * x * x - 0.24609375; + pnx[0] = 1.; + if (n >= 1) { + pnx[1] = x; } - return pnx; + // Use recursion relation to build the higher orders + for (l = 1; l < n; l ++) { + pnx[l + 1] = (static_cast(2 * l + 1) * x * pnx[l] - + static_cast(l) * pnx[l - 1]) / + (static_cast(l + 1)); + } } //============================================================================== @@ -166,27 +126,34 @@ double __attribute__ ((const)) calc_pn_c(int n, double x) { // and the value of x //============================================================================== -double __attribute__ ((const)) evaluate_legendre_c(int n, double data[], - double x) { +double __attribute__ ((const)) evaluate_legendre_c(const int n, + const double data[], + const double x) { double val; int l; + double* pnx = new double[n + 1]; - val = 0.5 * data[0]; - for (l = 1; l < n; l++) { - val += (static_cast(l) + 0.5) * data[l] * calc_pn_c(l, x); + val = 0.0; + calc_pn_c(n, x, pnx); + for (l = 0; l <= n; l++) { + val += (static_cast(l) + 0.5) * data[l] * pnx[l]; } + delete[] pnx; return val; } //============================================================================== // CALC_RN calculates the n-th order spherical harmonics for a given angle -// (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) +// (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) for n +// all 0 <= n //============================================================================== -void calc_rn_c(int n, double uvw[3], double rn[]){ +void calc_rn_c(const int n, const double uvw[3], double rn[]){ double phi; double w; double w2m1; + int i; + int l; // rn[] is assumed to have already been allocated to the correct size @@ -201,380 +168,383 @@ void calc_rn_c(int n, double uvw[3], double rn[]){ // Store the shorthand of 1-w * w w2m1 = 1. - w * w; - // Now evaluate the spherical harmonics function depending on the order - // requested - switch (n) { - case 0: - // l = 0, m = 0 - rn[0] = 1.; - break; - case 1: - // l = 1, m = -1 - rn[0] = -(1.*std::sqrt(w2m1) * std::sin(phi)); - // l = 1, m = 0 - rn[1] = w; - // l = 1, m = 1 - rn[2] = -(1.*std::sqrt(w2m1) * std::cos(phi)); - break; - case 2: - // l = 2, m = -2 - rn[0] = 0.288675134594813 * (-3. * w * w + 3.) * std::sin(2. * phi); - // l = 2, m = -1 - rn[1] = -(1.73205080756888 * w*std::sqrt(w2m1) * std::sin(phi)); - // l = 2, m = 0 - rn[2] = 1.5 * w * w - 0.5; - // l = 2, m = 1 - rn[3] = -(1.73205080756888 * w*std::sqrt(w2m1) * std::cos(phi)); - // l = 2, m = 2 - rn[4] = 0.288675134594813 * (-3. * w * w + 3.) * std::cos(2. * phi); - break; - case 3: - // l = 3, m = -3 - rn[0] = -(0.790569415042095 * std::pow(w2m1, 1.5) * std::sin(3. * phi)); - // l = 3, m = -2 - rn[1] = 1.93649167310371 * w*(w2m1) * std::sin(2.*phi); - // l = 3, m = -1 - rn[2] = -(0.408248290463863*std::sqrt(w2m1)*((7.5)*w * w - 3./2.) * - std::sin(phi)); - // l = 3, m = 0 - rn[3] = 2.5 * std::pow(w, 3) - 1.5 * w; - // l = 3, m = 1 - rn[4] = -(0.408248290463863*std::sqrt(w2m1)*((7.5)*w * w - 3./2.) * - std::cos(phi)); - // l = 3, m = 2 - rn[5] = 1.93649167310371 * w*(w2m1) * std::cos(2.*phi); - // l = 3, m = 3 - rn[6] = -(0.790569415042095 * std::pow(w2m1, 1.5) * std::cos(3.* phi)); - break; - case 4: - // l = 4, m = -4 - rn[0] = 0.739509972887452 * (w2m1 * w2m1) * std::sin(4.0*phi); - // l = 4, m = -3 - rn[1] = -(2.09165006633519 * w * std::pow(w2m1, 1.5) * std::sin(3.* phi)); - // l = 4, m = -2 - rn[2] = 0.074535599249993 * (w2m1)*(52.5 * w * w - 7.5) * std::sin(2. *phi); - // l = 4, m = -1 - rn[3] = -(0.316227766016838*std::sqrt(w2m1)*(17.5 * std::pow(w, 3) - 7.5 * w) * - std::sin(phi)); - // l = 4, m = 0 - rn[4] = 4.375 * std::pow(w, 4) - 3.75 * w * w + 0.375; - // l = 4, m = 1 - rn[5] = -(0.316227766016838*std::sqrt(w2m1)*(17.5 * std::pow(w, 3) - 7.5*w) * - std::cos(phi)); - // l = 4, m = 2 - rn[6] = 0.074535599249993 * (w2m1)*(52.5*w * w - 7.5) * std::cos(2.*phi); - // l = 4, m = 3 - rn[7] = -(2.09165006633519 * w * std::pow(w2m1, 1.5) * std::cos(3.* phi)); - // l = 4, m = 4 - rn[8] = 0.739509972887452 * w2m1 * w2m1 * std::cos(4.0*phi); - break; - case 5: - // l = 5, m = -5 - rn[0] = -(0.701560760020114 * std::pow(w2m1, 2.5) * std::sin(5.0 * phi)); - // l = 5, m = -4 - rn[1] = 2.21852991866236 * w * w2m1 * w2m1 * std::sin(4.0 * phi); - // l = 5, m = -3 - rn[2] = -(0.00996023841111995 * std::pow(w2m1, 1.5) * - ((945.0 /2.)* w * w - 52.5) * std::sin(3.*phi)); - // l = 5, m = -2 - rn[3] = 0.0487950036474267 * (w2m1) - * ((315.0/2.)* std::pow(w, 3) - 52.5 * w) * std::sin(2.*phi); - // l = 5, m = -1 - rn[4] = -(0.258198889747161*std::sqrt(w2m1) * - (39.375 * std::pow(w, 4) - 105.0/4.0 * w * w + 15.0/8.0) * std::sin(phi)); - // l = 5, m = 0 - rn[5] = 7.875 * std::pow(w, 5) - 8.75 * std::pow(w, 3) + 1.875 * w; - // l = 5, m = 1 - rn[6] = -(0.258198889747161 * std::sqrt(w2m1)* - (39.375 * std::pow(w, 4) - 105.0/4.0 * w * w + 15.0/8.0) * std::cos(phi)); - // l = 5, m = 2 - rn[7] = 0.0487950036474267 * (w2m1) * - ((315.0 / 2.) * std::pow(w, 3) - 52.5*w) * std::cos(2.*phi); - // l = 5, m = 3 - rn[8] = -(0.00996023841111995 * std::pow(w2m1, 1.5) * - ((945.0 / 2.) * w * w - 52.5) * std::cos(3.*phi)); - // l = 5, m = 4 - rn[9] = 2.21852991866236 * w * w2m1 * w2m1 * std::cos(4.0*phi); - // l = 5, m = 5 - rn[10] = -(0.701560760020114 * std::pow(w2m1, 2.5) * std::cos(5.0* phi)); - break; - case 6: - // l = 6, m = -6 - rn[0] = 0.671693289381396 * std::pow(w2m1, 3) * std::sin(6.0*phi); - // l = 6, m = -5 - rn[1] = -(2.32681380862329 * w*std::pow(w2m1, 2.5) * std::sin(5.0*phi)); - // l = 6, m = -4 - rn[2] = 0.00104990131391452 * w2m1 * w2m1 * - ((10395.0/2.) * w * w - 945.0/2.) * std::sin(4.0 * phi); - // l = 6, m = -3 - rn[3] = -(0.00575054632785295 * std::pow(w2m1, 1.5) * - ((3465.0/2.) * std::pow(w, 3) - 945.0/2.*w) * std::sin(3.*phi)); - // l = 6, m = -2 - rn[4] = 0.0345032779671177 * (w2m1) * - ((3465.0/8.0)* std::pow(w, 4) - 945.0/4.0 * w * w + 105.0/8.0) * - std::sin(2. * phi); - // l = 6, m = -1 - rn[5] = -(0.218217890235992*std::sqrt(w2m1) * - ((693.0/8.0)* std::pow(w, 5)- 315.0/4.0 * std::pow(w, 3) + (105.0/8.0)*w) * - std::sin(phi)); - // l = 6, m = 0 - rn[6] = 14.4375 * std::pow(w, 6) - 19.6875 * std::pow(w, 4) + 6.5625 * w * w - - 0.3125; - // l = 6, m = 1 - rn[7] = -(0.218217890235992*std::sqrt(w2m1) * - ((693.0/8.0)* std::pow(w, 5)- 315.0/4.0 * std::pow(w, 3) + (105.0/8.0)*w) * - std::cos(phi)); - // l = 6, m = 2 - rn[8] = 0.0345032779671177 * w2m1 * - ((3465.0/8.0)* std::pow(w, 4) -945.0/4.0 * w * w + 105.0/8.0) * - std::cos(2.*phi); - // l = 6, m = 3 - rn[9] = -(0.00575054632785295 * std::pow(w2m1, 1.5) * - ((3465.0/2.) * std::pow(w, 3) - 945.0/2.*w) * std::cos(3.*phi)); - // l = 6, m = 4 - rn[10] = 0.00104990131391452 * w2m1 * w2m1 * - ((10395.0/2.)*w * w - 945.0/2.) * std::cos(4.0*phi); - // l = 6, m = 5 - rn[11] = -(2.32681380862329 * w * std::pow(w2m1, 2.5) * std::cos(5.0*phi)); - // l = 6, m = 6 - rn[12] = 0.671693289381396 * std::pow(w2m1, 3) * std::cos(6.0*phi); - break; - case 7: - // l = 7, m = -7 - rn[0] = -(0.647259849287749 * std::pow(w2m1, 3.5) * std::sin(7.0*phi)); - // l = 7, m = -6 - rn[1] = 2.42182459624969 * w*std::pow(w2m1, 3) * std::sin(6.0*phi); - // l = 7, m = -5 - rn[2] = -(9.13821798555235e-5*std::pow(w2m1, 2.5) * - ((135135.0/2.)*w * w - 10395.0/2.) * std::sin(5.0*phi)); - // l = 7, m = -4 - rn[3] = 0.000548293079133141 * w2m1 * w2m1 * - ((45045.0/2.)*std::pow(w, 3) - 10395.0/2.*w) * std::sin(4.0*phi); - // l = 7, m = -3 - rn[4] = -(0.00363696483726654 * std::pow(w2m1, 1.5) * - ((45045.0/8.0)* std::pow(w, 4) - 10395.0/4.0 * w * w + 945.0/8.0) * - std::sin(3.*phi)); - // l = 7, m = -2 - rn[5] = 0.025717224993682 * (w2m1) * - ((9009.0/8.0)* std::pow(w, 5) -3465.0/4.0 * std::pow(w, 3) + (945.0/8.0)*w) * - std::sin(2.*phi); - // l = 7, m = -1 - rn[6] = -(0.188982236504614*std::sqrt(w2m1) * - ((3003.0/16.0)* std::pow(w, 6) - 3465.0/16.0 * std::pow(w, 4) + - (945.0/16.0)*w * w - 35.0/16.0) * std::sin(phi)); - // l = 7, m = 0 - rn[7] = 26.8125 * std::pow(w, 7) - 43.3125 * std::pow(w, 5) + 19.6875 * std::pow(w, 3) - - 2.1875 * w; - // l = 7, m = 1 - rn[8] = -(0.188982236504614*std::sqrt(w2m1) * ((3003.0/16.0) * std::pow(w, 6) - - 3465.0/16.0 * std::pow(w, 4) + (945.0/16.0)*w * w - 35.0/16.0) * std::cos(phi)); - // l = 7, m = 2 - rn[9] = 0.025717224993682 * (w2m1) * ((9009.0/8.0)* std::pow(w, 5) - - 3465.0/4.0 * std::pow(w, 3) + (945.0/8.0)*w) * std::cos(2.*phi); - // l = 7, m = 3 - rn[10] = -(0.00363696483726654 * std::pow(w2m1, 1.5) * - ((45045.0/8.0)* std::pow(w, 4) - 10395.0/4.0 * w * w + 945.0/8.0) * - std::cos(3.*phi)); - // l = 7, m = 4 - rn[11] = 0.000548293079133141 * w2m1 * w2m1 * - ((45045.0/2.)*std::pow(w, 3) - 10395.0/2.*w) * std::cos(4.0*phi); - // l = 7, m = 5 - rn[12] = -(9.13821798555235e-5*std::pow(w2m1, 2.5) * - ((135135.0/2.)*w * w - 10395.0/2.) * std::cos(5.0*phi)); - // l = 7, m = 6 - rn[13] = 2.42182459624969 * w*std::pow(w2m1, 3) * std::cos(6.0*phi); - // l = 7, m = 7 - rn[14] = -(0.647259849287749 * std::pow(w2m1, 3.5) * std::cos(7.0*phi)); - break; - case 8: - // l = 8, m = -8 - rn[0] = 0.626706654240044 * std::pow(w2m1, 4) * std::sin(8.0*phi); - // l = 8, m = -7 - rn[1] = -(2.50682661696018 * w*std::pow(w2m1, 3.5) * std::sin(7.0*phi)); - // l = 8, m = -6 - rn[2] = 6.77369783729086e-6*std::pow(w2m1, 3)* - ((2027025.0/2.)*w * w - 135135.0/2.) * std::sin(6.0*phi); - // l = 8, m = -5 - rn[3] = -(4.38985792528482e-5*std::pow(w2m1, 2.5) * - ((675675.0/2.)*std::pow(w, 3) - 135135.0/2.*w) * std::sin(5.0*phi)); - // l = 8, m = -4 - rn[4] = 0.000316557156832328 * w2m1 * w2m1 * - ((675675.0/8.0)* std::pow(w, 4) - 135135.0/4.0 * w * w + 10395.0/8.0) * - std::sin(4.0*phi); - // l = 8, m = -3 - rn[5] = -(0.00245204119306875 * std::pow(w2m1, 1.5) * ((135135.0/8.0) * - std::pow(w, 5) - 45045.0/4.0 * std::pow(w, 3) + (10395.0/8.0)*w) * std::sin(3.*phi)); - // l = 8, m = -2 - rn[6] = 0.0199204768222399 * (w2m1) * - ((45045.0/16.0)* std::pow(w, 6)- 45045.0/16.0 * std::pow(w, 4) + - (10395.0/16.0)*w * w - 315.0/16.0) * std::sin(2.*phi); - // l = 8, m = -1 - rn[7] = -(0.166666666666667*std::sqrt(w2m1) * - ((6435.0/16.0)* std::pow(w, 7) - 9009.0/16.0 * std::pow(w, 5) + - (3465.0/16.0)*std::pow(w, 3) - 315.0/16.0 * w) * std::sin(phi)); - // l = 8, m = 0 - rn[8] = 50.2734375 * std::pow(w, 8) - 93.84375 * std::pow(w, 6) + 54.140625 * - std::pow(w, 4) - 9.84375 * w * w + 0.2734375; - // l = 8, m = 1 - rn[9] = -(0.166666666666667*std::sqrt(w2m1) * - ((6435.0/16.0)* std::pow(w, 7) - 9009.0/16.0 * std::pow(w, 5) + - (3465.0/16.0)*std::pow(w, 3) - 315.0/16.0 * w) * std::cos(phi)); - // l = 8, m = 2 - rn[10] = 0.0199204768222399 * (w2m1)*((45045.0/16.0)* std::pow(w, 6)- - 45045.0/16.0 * std::pow(w, 4) + (10395.0/16.0)*w * w - - 315.0/16.0) * std::cos(2.*phi); - // l = 8, m = 3 - rn[11] = -(0.00245204119306875 * std::pow(w2m1, 1.5)* - ((135135.0/8.0) * std::pow(w, 5) - 45045.0/4.0 * std::pow(w, 3) + - (10395.0/8.0)*w) * std::cos(3.*phi)); - // l = 8, m = 4 - rn[12] = 0.000316557156832328 * w2m1 * w2m1*((675675.0/8.0)* std::pow(w, 4) - - 135135.0/4.0 * w * w + 10395.0/8.0) * std::cos(4.0*phi); - // l = 8, m = 5 - rn[13] = -(4.38985792528482e-5*std::pow(w2m1, 2.5)*((675675.0/2.)*std::pow(w, 3) - - 135135.0/2.*w) * std::cos(5.0*phi)); - // l = 8, m = 6 - rn[14] = 6.77369783729086e-6*std::pow(w2m1, 3)*((2027025.0/2.)*w * w - - 135135.0/2.) * std::cos(6.0*phi); - // l = 8, m = 7 - rn[15] = -(2.50682661696018 * w*std::pow(w2m1, 3.5) * std::cos(7.0*phi)); - // l = 8, m = 8 - rn[16] = 0.626706654240044 * std::pow(w2m1, 4) * std::cos(8.0*phi); - break; - case 9: - // l = 9, m = -9 - rn[0] = -(0.609049392175524 * std::pow(w2m1, 4.5) * std::sin(9.0 * phi)); - // l = 9, m = -8 - rn[1] = 2.58397773170915 * w*std::pow(w2m1, 4) * std::sin(8.0 * phi); - // l = 9, m = -7 - rn[2] = -(4.37240315267812e-7*std::pow(w2m1, 3.5) * - ((34459425.0/2.)*w * w - 2027025.0/2.) * std::sin(7.0 * phi)); - // l = 9, m = -6 - rn[3] = 3.02928976464514e-6*std::pow(w2m1, 3)* - ((11486475.0/2.)*std::pow(w, 3) - 2027025.0/2.*w) * std::sin(6.0 * phi); - // l = 9, m = -5 - rn[4] = -(2.34647776186144e-5*std::pow(w2m1, 2.5) * - ((11486475.0/8.0)* std::pow(w, 4) - 2027025.0 / 4.0 * w * w + - 135135.0/8.0) * std::sin(5.0 * phi)); - // l = 9, m = -4 - rn[5] = 0.000196320414650061 * w2m1 * w2m1*((2297295.0/8.0)* std::pow(w, 5) - - 675675.0/4.0 * std::pow(w, 3) + (135135.0/8.0)*w) * std::sin(4.0*phi); - // l = 9, m = -3 - rn[6] = -(0.00173385495536766 * std::pow(w2m1, 1.5) * - ((765765.0/16.0)* std::pow(w, 6) - 675675.0/16.0 * std::pow(w, 4) + - (135135.0/16.0)*w * w - 3465.0/16.0) * std::sin(3. * phi)); - // l = 9, m = -2 - rn[7] = 0.0158910431540932 * (w2m1)*((109395.0/16.0)* std::pow(w, 7)- - 135135.0/16.0 * std::pow(w, 5) + (45045.0/16.0)*std::pow(w, 3) - - 3465.0/16.0 * w) * std::sin(2. * phi); - // l = 9, m = -1 - rn[8] = -(0.149071198499986*std::sqrt(w2m1)*((109395.0/128.0)* std::pow(w, 8) - - 45045.0/32.0 * std::pow(w, 6) + (45045.0/64.0)* std::pow(w, 4) - - 3465.0/32.0 * w * w + 315.0/128.0) * std::sin(phi)); - // l = 9, m = 0 - rn[9] = 94.9609375 * std::pow(w, 9) - 201.09375 * std::pow(w, 7) + - 140.765625 * std::pow(w, 5)- 36.09375 * std::pow(w, 3) + 2.4609375 * w; - // l = 9, m = 1 - rn[10] = -(0.149071198499986*std::sqrt(w2m1)*((109395.0/128.0)* std::pow(w, 8) - - 45045.0/32.0 * std::pow(w, 6) + (45045.0/64.0)* std::pow(w, 4) - - 3465.0/32.0 * w * w + 315.0/128.0) * std::cos(phi)); - // l = 9, m = 2 - rn[11] = 0.0158910431540932 * (w2m1)*((109395.0/16.0)* std::pow(w, 7) - - 135135.0/16.0 * std::pow(w, 5) + (45045.0/16.0)*std::pow(w, 3) - - 3465.0/ 16.0 * w) * std::cos(2. * phi); - // l = 9, m = 3 - rn[12] = -(0.00173385495536766 * std::pow(w2m1, 1.5)*((765765.0/16.0) * - std::pow(w, 6) - 675675.0/16.0 * std::pow(w, 4) + - (135135.0/16.0)* w * w - 3465.0/16.0)* std::cos(3. * phi)); - // l = 9, m = 4 - rn[13] = 0.000196320414650061 * w2m1 * w2m1*((2297295.0/8.0) * std::pow(w, 5) - - 675675.0/4.0 * std::pow(w, 3) + (135135.0/8.0)*w) * std::cos(4.0 * phi); - // l = 9, m = 5 - rn[14] = -(2.34647776186144e-5*std::pow(w2m1, 2.5)*((11486475.0/8.0) * - std::pow(w, 4) - 2027025.0/4.0 * w * w + 135135.0/8.0) * - std::cos(5.0 * phi)); - // l = 9, m = 6 - rn[15] = 3.02928976464514e-6*std::pow(w2m1, 3)*((11486475.0/2.)*std::pow(w, 3) - - 2027025.0/2. * w) * std::cos(6.0 * phi); - // l = 9, m = 7 - rn[16] = -(4.37240315267812e-7*std::pow(w2m1, 3.5)* - ((34459425.0/2.) * w * w - 2027025.0/2.) * std::cos(7.0 * phi)); - // l = 9, m = 8 - rn[17] = 2.58397773170915 * w*std::pow(w2m1, 4) * std::cos(8.0 * phi); - // l = 9, m = 9 - rn[18] = -(0.609049392175524 * std::pow(w2m1, 4.5) * std::cos(9.0 * phi)); - break; - case 10: - // l = 10, m = -10 - rn[0] = 0.593627917136573 * std::pow(w2m1, 5) * std::sin(10.0 * phi); - // l = 10, m = -9 - rn[1] = -(2.65478475211798 * w * std::pow(w2m1, 4.5) * std::sin(9.0 * phi)); - // l = 10, m = -8 - rn[2] = 2.49953651452314e-8 * std::pow(w2m1, 4) * - ((654729075.0/2.) * w * w - 34459425.0/2.) * std::sin(8.0 * phi); - // l = 10, m = -7 - rn[3] = -(1.83677671621093e-7*std::pow(w2m1, 3.5)* - ((218243025.0/2.)*std::pow(w, 3) - 34459425.0/2.*w) * - std::sin(7.0 * phi)); - // l = 10, m = -6 - rn[4] = 1.51464488232257e-6*std::pow(w2m1, 3)*((218243025.0/8.0)* std::pow(w, 4) - - 34459425.0/4.0 * w * w + 2027025.0/8.0) * std::sin(6.0 * phi); - // l = 10, m = -5 - rn[5] = -(1.35473956745817e-5*std::pow(w2m1, 2.5)* - ((43648605.0/8.0)* std::pow(w, 5) - 11486475.0/4.0 * std::pow(w, 3) + - (2027025.0/8.0)*w) * std::sin(5.0 * phi)); - // l = 10, m = -4 - rn[6] = 0.000128521880085575 * w2m1 * w2m1*((14549535.0/16.0)* std::pow(w, 6) - - 11486475.0/16.0 * std::pow(w, 4) + (2027025.0/16.0)*w * w - - 45045.0/16.0) * std::sin(4.0 * phi); - // l = 10, m = -3 - rn[7] = -(0.00127230170115096 * std::pow(w2m1, 1.5)* - ((2078505.0/16.0)* std::pow(w, 7) - 2297295.0/16.0 * std::pow(w, 5) + - (675675.0/16.0)*std::pow(w, 3) - 45045.0/16.0 * w) * std::sin(3. * phi)); - // l = 10, m = -2 - rn[8] = 0.012974982402692 * (w2m1)*((2078505.0/128.0)* std::pow(w, 8) - - 765765.0/32.0 * std::pow(w, 6) + (675675.0/64.0)* std::pow(w, 4) - - 45045.0/32.0 * w * w + 3465.0/128.0) * std::sin(2. * phi); - // l = 10, m = -1 - rn[9] = -(0.134839972492648*std::sqrt(w2m1)*((230945.0/128.0)* std::pow(w, 9) - - 109395.0/32.0 * std::pow(w, 7) + (135135.0/64.0)* std::pow(w, 5) - - 15015.0/32.0 * std::pow(w, 3) + (3465.0/128.0)*w) * std::sin(phi)); - // l = 10, m = 0 - rn[10] = 180.42578125 * std::pow(w, 10) - 427.32421875 * std::pow(w, 8) +351.9140625 - * std::pow(w, 6) - 117.3046875 * std::pow(w, 4) + 13.53515625 * w * w -0.24609375; - // l = 10, m = 1 - rn[11] = -(0.134839972492648*std::sqrt(w2m1)*((230945.0/128.0)* std::pow(w, 9) - - 109395.0/32.0 * std::pow(w, 7) + (135135.0/64.0)* std::pow(w, 5) -15015.0/ - 32.0 * std::pow(w, 3) + (3465.0/128.0)*w) * std::cos(phi)); - // l = 10, m = 2 - rn[12] = 0.012974982402692 * (w2m1)*((2078505.0/128.0)* std::pow(w, 8) - - 765765.0/32.0 * std::pow(w, 6) + (675675.0/64.0)* std::pow(w, 4) - - 45045.0/32.0 * w * w + 3465.0/128.0) * std::cos(2. * phi); - // l = 10, m = 3 - rn[13] = -(0.00127230170115096 * std::pow(w2m1, 1.5)* - ((2078505.0/16.0)* std::pow(w, 7) - 2297295.0/16.0 * std::pow(w, 5) + - (675675.0/16.0)*std::pow(w, 3) - 45045.0/16.0 * w) * std::cos(3. * phi)); - // l = 10, m = 4 - rn[14] = 0.000128521880085575 * w2m1 * w2m1*((14549535.0/16.0)* std::pow(w, 6) - - 11486475.0/16.0 * std::pow(w, 4) + (2027025.0/16.0) * w * w - - 45045.0/16.0) * std::cos(4.0 * phi); - // l = 10, m = 5 - rn[15] = -(1.35473956745817e-5*std::pow(w2m1, 2.5)* - ((43648605.0/8.0)* std::pow(w, 5) - 11486475.0/4.0 * std::pow(w, 3) + - (2027025.0/8.0)*w) * std::cos(5.0 * phi)); - // l = 10, m = 6 - rn[16] = 1.51464488232257e-6*std::pow(w2m1, 3)*((218243025.0/8.0)* std::pow(w, 4) - - 34459425.0/4.0 * w * w + 2027025.0/8.0) * std::cos(6.0 * phi); - // l = 10, m = 7 - rn[17] = -(1.83677671621093e-7*std::pow(w2m1, 3.5) * - ((218243025.0/2.)*std::pow(w, 3) - 34459425.0/2.*w) * std::cos(7.0 * phi)); - // l = 10, m = 8 - rn[18] = 2.49953651452314e-8*std::pow(w2m1, 4)* - ((654729075.0/2.)*w * w - 34459425.0/2.) * std::cos(8.0 * phi); - // l = 10, m = 9 - rn[19] = -(2.65478475211798 * w*std::pow(w2m1, 4.5) * std::cos(9.0 * phi)); - // l = 10, m = 10 - rn[20] = 0.593627917136573 * std::pow(w2m1, 5) * std::cos(10.0 * phi); + // Now evaluate the spherical harmonics function + rn[0] = 1.; + i = 0; + for (l = 1; l <= n; l++) { + // Set the index to the start of this order + i += 2 * (l - 1) + 1; + + // Now evaluate each + switch (l) { + case 1: + // l = 1, m = -1 + rn[i] = -(std::sqrt(w2m1) * std::sin(phi)); + // l = 1, m = 0 + rn[i + 1] = w; + // l = 1, m = 1 + rn[i + 2] = -(std::sqrt(w2m1) * std::cos(phi)); + break; + case 2: + // l = 2, m = -2 + rn[i] = 0.288675134594813 * (-3. * w * w + 3.) * std::sin(2. * phi); + // l = 2, m = -1 + rn[i + 1] = -(1.73205080756888 * w*std::sqrt(w2m1) * std::sin(phi)); + // l = 2, m = 0 + rn[i + 2] = 1.5 * w * w - 0.5; + // l = 2, m = 1 + rn[i + 3] = -(1.73205080756888 * w*std::sqrt(w2m1) * std::cos(phi)); + // l = 2, m = 2 + rn[i + 4] = 0.288675134594813 * (-3. * w * w + 3.) * std::cos(2. * phi); + break; + case 3: + // l = 3, m = -3 + rn[i] = -(0.790569415042095 * std::pow(w2m1, 1.5) * std::sin(3. * phi)); + // l = 3, m = -2 + rn[i + 1] = 1.93649167310371 * w*(w2m1) * std::sin(2.*phi); + // l = 3, m = -1 + rn[i + 2] = -(0.408248290463863*std::sqrt(w2m1)*((7.5)*w * w - 3./2.) * + std::sin(phi)); + // l = 3, m = 0 + rn[i + 3] = 2.5 * std::pow(w, 3) - 1.5 * w; + // l = 3, m = 1 + rn[i + 4] = -(0.408248290463863*std::sqrt(w2m1)*((7.5)*w * w - 3./2.) * + std::cos(phi)); + // l = 3, m = 2 + rn[i + 5] = 1.93649167310371 * w*(w2m1) * std::cos(2.*phi); + // l = 3, m = 3 + rn[i + 6] = -(0.790569415042095 * std::pow(w2m1, 1.5) * std::cos(3.* phi)); + break; + case 4: + // l = 4, m = -4 + rn[i] = 0.739509972887452 * (w2m1 * w2m1) * std::sin(4.0*phi); + // l = 4, m = -3 + rn[i + 1] = -(2.09165006633519 * w * std::pow(w2m1, 1.5) * std::sin(3.* phi)); + // l = 4, m = -2 + rn[i + 2] = 0.074535599249993 * (w2m1)*(52.5 * w * w - 7.5) * std::sin(2. *phi); + // l = 4, m = -1 + rn[i + 3] = -(0.316227766016838*std::sqrt(w2m1)*(17.5 * std::pow(w, 3) - 7.5 * w) * + std::sin(phi)); + // l = 4, m = 0 + rn[i + 4] = 4.375 * std::pow(w, 4) - 3.75 * w * w + 0.375; + // l = 4, m = 1 + rn[i + 5] = -(0.316227766016838*std::sqrt(w2m1)*(17.5 * std::pow(w, 3) - 7.5*w) * + std::cos(phi)); + // l = 4, m = 2 + rn[i + 6] = 0.074535599249993 * (w2m1)*(52.5*w * w - 7.5) * std::cos(2.*phi); + // l = 4, m = 3 + rn[i + 7] = -(2.09165006633519 * w * std::pow(w2m1, 1.5) * std::cos(3.* phi)); + // l = 4, m = 4 + rn[i + 8] = 0.739509972887452 * w2m1 * w2m1 * std::cos(4.0*phi); + break; + case 5: + // l = 5, m = -5 + rn[i] = -(0.701560760020114 * std::pow(w2m1, 2.5) * std::sin(5.0 * phi)); + // l = 5, m = -4 + rn[i + 1] = 2.21852991866236 * w * w2m1 * w2m1 * std::sin(4.0 * phi); + // l = 5, m = -3 + rn[i + 2] = -(0.00996023841111995 * std::pow(w2m1, 1.5) * + ((945.0 /2.)* w * w - 52.5) * std::sin(3.*phi)); + // l = 5, m = -2 + rn[i + 3] = 0.0487950036474267 * (w2m1) + * ((315.0/2.)* std::pow(w, 3) - 52.5 * w) * std::sin(2.*phi); + // l = 5, m = -1 + rn[i + 4] = -(0.258198889747161*std::sqrt(w2m1) * + (39.375 * std::pow(w, 4) - 105.0/4.0 * w * w + 15.0/8.0) * std::sin(phi)); + // l = 5, m = 0 + rn[i + 5] = 7.875 * std::pow(w, 5) - 8.75 * std::pow(w, 3) + 1.875 * w; + // l = 5, m = 1 + rn[i + 6] = -(0.258198889747161 * std::sqrt(w2m1)* + (39.375 * std::pow(w, 4) - 105.0/4.0 * w * w + 15.0/8.0) * std::cos(phi)); + // l = 5, m = 2 + rn[i + 7] = 0.0487950036474267 * (w2m1) * + ((315.0 / 2.) * std::pow(w, 3) - 52.5*w) * std::cos(2.*phi); + // l = 5, m = 3 + rn[i + 8] = -(0.00996023841111995 * std::pow(w2m1, 1.5) * + ((945.0 / 2.) * w * w - 52.5) * std::cos(3.*phi)); + // l = 5, m = 4 + rn[i + 9] = 2.21852991866236 * w * w2m1 * w2m1 * std::cos(4.0*phi); + // l = 5, m = 5 + rn[i + 10] = -(0.701560760020114 * std::pow(w2m1, 2.5) * std::cos(5.0* phi)); + break; + case 6: + // l = 6, m = -6 + rn[i] = 0.671693289381396 * std::pow(w2m1, 3) * std::sin(6.0*phi); + // l = 6, m = -5 + rn[i + 1] = -(2.32681380862329 * w*std::pow(w2m1, 2.5) * std::sin(5.0*phi)); + // l = 6, m = -4 + rn[i + 2] = 0.00104990131391452 * w2m1 * w2m1 * + ((10395.0/2.) * w * w - 945.0/2.) * std::sin(4.0 * phi); + // l = 6, m = -3 + rn[i + 3] = -(0.00575054632785295 * std::pow(w2m1, 1.5) * + ((3465.0/2.) * std::pow(w, 3) - 945.0/2.*w) * std::sin(3.*phi)); + // l = 6, m = -2 + rn[i + 4] = 0.0345032779671177 * (w2m1) * + ((3465.0/8.0)* std::pow(w, 4) - 945.0/4.0 * w * w + 105.0/8.0) * + std::sin(2. * phi); + // l = 6, m = -1 + rn[i + 5] = -(0.218217890235992*std::sqrt(w2m1) * + ((693.0/8.0)* std::pow(w, 5)- 315.0/4.0 * std::pow(w, 3) + (105.0/8.0)*w) * + std::sin(phi)); + // l = 6, m = 0 + rn[i + 6] = 14.4375 * std::pow(w, 6) - 19.6875 * std::pow(w, 4) + 6.5625 * w * w - + 0.3125; + // l = 6, m = 1 + rn[i + 7] = -(0.218217890235992*std::sqrt(w2m1) * + ((693.0/8.0)* std::pow(w, 5)- 315.0/4.0 * std::pow(w, 3) + (105.0/8.0)*w) * + std::cos(phi)); + // l = 6, m = 2 + rn[i + 8] = 0.0345032779671177 * w2m1 * + ((3465.0/8.0)* std::pow(w, 4) -945.0/4.0 * w * w + 105.0/8.0) * + std::cos(2.*phi); + // l = 6, m = 3 + rn[i + 9] = -(0.00575054632785295 * std::pow(w2m1, 1.5) * + ((3465.0/2.) * std::pow(w, 3) - 945.0/2.*w) * std::cos(3.*phi)); + // l = 6, m = 4 + rn[i + 10] = 0.00104990131391452 * w2m1 * w2m1 * + ((10395.0/2.)*w * w - 945.0/2.) * std::cos(4.0*phi); + // l = 6, m = 5 + rn[i + 11] = -(2.32681380862329 * w * std::pow(w2m1, 2.5) * std::cos(5.0*phi)); + // l = 6, m = 6 + rn[i + 12] = 0.671693289381396 * std::pow(w2m1, 3) * std::cos(6.0*phi); + break; + case 7: + // l = 7, m = -7 + rn[i] = -(0.647259849287749 * std::pow(w2m1, 3.5) * std::sin(7.0*phi)); + // l = 7, m = -6 + rn[i + 1] = 2.42182459624969 * w*std::pow(w2m1, 3) * std::sin(6.0*phi); + // l = 7, m = -5 + rn[i + 2] = -(9.13821798555235e-5*std::pow(w2m1, 2.5) * + ((135135.0/2.)*w * w - 10395.0/2.) * std::sin(5.0*phi)); + // l = 7, m = -4 + rn[i + 3] = 0.000548293079133141 * w2m1 * w2m1 * + ((45045.0/2.)*std::pow(w, 3) - 10395.0/2.*w) * std::sin(4.0*phi); + // l = 7, m = -3 + rn[i + 4] = -(0.00363696483726654 * std::pow(w2m1, 1.5) * + ((45045.0/8.0)* std::pow(w, 4) - 10395.0/4.0 * w * w + 945.0/8.0) * + std::sin(3.*phi)); + // l = 7, m = -2 + rn[i + 5] = 0.025717224993682 * (w2m1) * + ((9009.0/8.0)* std::pow(w, 5) -3465.0/4.0 * std::pow(w, 3) + (945.0/8.0)*w) * + std::sin(2.*phi); + // l = 7, m = -1 + rn[i + 6] = -(0.188982236504614*std::sqrt(w2m1) * + ((3003.0/16.0)* std::pow(w, 6) - 3465.0/16.0 * std::pow(w, 4) + + (945.0/16.0)*w * w - 35.0/16.0) * std::sin(phi)); + // l = 7, m = 0 + rn[i + 7] = 26.8125 * std::pow(w, 7) - 43.3125 * std::pow(w, 5) + 19.6875 * std::pow(w, 3) - + 2.1875 * w; + // l = 7, m = 1 + rn[i + 8] = -(0.188982236504614*std::sqrt(w2m1) * ((3003.0/16.0) * std::pow(w, 6) - + 3465.0/16.0 * std::pow(w, 4) + (945.0/16.0)*w * w - 35.0/16.0) * std::cos(phi)); + // l = 7, m = 2 + rn[i + 9] = 0.025717224993682 * (w2m1) * ((9009.0/8.0)* std::pow(w, 5) - + 3465.0/4.0 * std::pow(w, 3) + (945.0/8.0)*w) * std::cos(2.*phi); + // l = 7, m = 3 + rn[i + 10] = -(0.00363696483726654 * std::pow(w2m1, 1.5) * + ((45045.0/8.0)* std::pow(w, 4) - 10395.0/4.0 * w * w + 945.0/8.0) * + std::cos(3.*phi)); + // l = 7, m = 4 + rn[i + 11] = 0.000548293079133141 * w2m1 * w2m1 * + ((45045.0/2.)*std::pow(w, 3) - 10395.0/2.*w) * std::cos(4.0*phi); + // l = 7, m = 5 + rn[i + 12] = -(9.13821798555235e-5*std::pow(w2m1, 2.5) * + ((135135.0/2.)*w * w - 10395.0/2.) * std::cos(5.0*phi)); + // l = 7, m = 6 + rn[i + 13] = 2.42182459624969 * w*std::pow(w2m1, 3) * std::cos(6.0*phi); + // l = 7, m = 7 + rn[i + 14] = -(0.647259849287749 * std::pow(w2m1, 3.5) * std::cos(7.0*phi)); + break; + case 8: + // l = 8, m = -8 + rn[i] = 0.626706654240044 * std::pow(w2m1, 4) * std::sin(8.0*phi); + // l = 8, m = -7 + rn[i + 1] = -(2.50682661696018 * w*std::pow(w2m1, 3.5) * std::sin(7.0*phi)); + // l = 8, m = -6 + rn[i + 2] = 6.77369783729086e-6*std::pow(w2m1, 3)* + ((2027025.0/2.)*w * w - 135135.0/2.) * std::sin(6.0*phi); + // l = 8, m = -5 + rn[i + 3] = -(4.38985792528482e-5*std::pow(w2m1, 2.5) * + ((675675.0/2.)*std::pow(w, 3) - 135135.0/2.*w) * std::sin(5.0*phi)); + // l = 8, m = -4 + rn[i + 4] = 0.000316557156832328 * w2m1 * w2m1 * + ((675675.0/8.0)* std::pow(w, 4) - 135135.0/4.0 * w * w + 10395.0/8.0) * + std::sin(4.0*phi); + // l = 8, m = -3 + rn[i + 5] = -(0.00245204119306875 * std::pow(w2m1, 1.5) * ((135135.0/8.0) * + std::pow(w, 5) - 45045.0/4.0 * std::pow(w, 3) + (10395.0/8.0)*w) * std::sin(3.*phi)); + // l = 8, m = -2 + rn[i + 6] = 0.0199204768222399 * (w2m1) * + ((45045.0/16.0)* std::pow(w, 6)- 45045.0/16.0 * std::pow(w, 4) + + (10395.0/16.0)*w * w - 315.0/16.0) * std::sin(2.*phi); + // l = 8, m = -1 + rn[i + 7] = -(0.166666666666667*std::sqrt(w2m1) * + ((6435.0/16.0)* std::pow(w, 7) - 9009.0/16.0 * std::pow(w, 5) + + (3465.0/16.0)*std::pow(w, 3) - 315.0/16.0 * w) * std::sin(phi)); + // l = 8, m = 0 + rn[i + 8] = 50.2734375 * std::pow(w, 8) - 93.84375 * std::pow(w, 6) + 54.140625 * + std::pow(w, 4) - 9.84375 * w * w + 0.2734375; + // l = 8, m = 1 + rn[i + 9] = -(0.166666666666667*std::sqrt(w2m1) * + ((6435.0/16.0)* std::pow(w, 7) - 9009.0/16.0 * std::pow(w, 5) + + (3465.0/16.0)*std::pow(w, 3) - 315.0/16.0 * w) * std::cos(phi)); + // l = 8, m = 2 + rn[i + 10] = 0.0199204768222399 * (w2m1)*((45045.0/16.0)* std::pow(w, 6)- + 45045.0/16.0 * std::pow(w, 4) + (10395.0/16.0)*w * w - + 315.0/16.0) * std::cos(2.*phi); + // l = 8, m = 3 + rn[i + 11] = -(0.00245204119306875 * std::pow(w2m1, 1.5)* + ((135135.0/8.0) * std::pow(w, 5) - 45045.0/4.0 * std::pow(w, 3) + + (10395.0/8.0)*w) * std::cos(3.*phi)); + // l = 8, m = 4 + rn[i + 12] = 0.000316557156832328 * w2m1 * w2m1*((675675.0/8.0)* std::pow(w, 4) - + 135135.0/4.0 * w * w + 10395.0/8.0) * std::cos(4.0*phi); + // l = 8, m = 5 + rn[i + 13] = -(4.38985792528482e-5*std::pow(w2m1, 2.5)*((675675.0/2.)*std::pow(w, 3) - + 135135.0/2.*w) * std::cos(5.0*phi)); + // l = 8, m = 6 + rn[i + 14] = 6.77369783729086e-6*std::pow(w2m1, 3)*((2027025.0/2.)*w * w - + 135135.0/2.) * std::cos(6.0*phi); + // l = 8, m = 7 + rn[i + 15] = -(2.50682661696018 * w*std::pow(w2m1, 3.5) * std::cos(7.0*phi)); + // l = 8, m = 8 + rn[i + 16] = 0.626706654240044 * std::pow(w2m1, 4) * std::cos(8.0*phi); + break; + case 9: + // l = 9, m = -9 + rn[i] = -(0.609049392175524 * std::pow(w2m1, 4.5) * std::sin(9.0 * phi)); + // l = 9, m = -8 + rn[i + 1] = 2.58397773170915 * w*std::pow(w2m1, 4) * std::sin(8.0 * phi); + // l = 9, m = -7 + rn[i + 2] = -(4.37240315267812e-7*std::pow(w2m1, 3.5) * + ((34459425.0/2.)*w * w - 2027025.0/2.) * std::sin(7.0 * phi)); + // l = 9, m = -6 + rn[i + 3] = 3.02928976464514e-6*std::pow(w2m1, 3)* + ((11486475.0/2.)*std::pow(w, 3) - 2027025.0/2.*w) * std::sin(6.0 * phi); + // l = 9, m = -5 + rn[i + 4] = -(2.34647776186144e-5*std::pow(w2m1, 2.5) * + ((11486475.0/8.0)* std::pow(w, 4) - 2027025.0 / 4.0 * w * w + + 135135.0/8.0) * std::sin(5.0 * phi)); + // l = 9, m = -4 + rn[i + 5] = 0.000196320414650061 * w2m1 * w2m1*((2297295.0/8.0)* std::pow(w, 5) - + 675675.0/4.0 * std::pow(w, 3) + (135135.0/8.0)*w) * std::sin(4.0*phi); + // l = 9, m = -3 + rn[i + 6] = -(0.00173385495536766 * std::pow(w2m1, 1.5) * + ((765765.0/16.0)* std::pow(w, 6) - 675675.0/16.0 * std::pow(w, 4) + + (135135.0/16.0)*w * w - 3465.0/16.0) * std::sin(3. * phi)); + // l = 9, m = -2 + rn[i + 7] = 0.0158910431540932 * (w2m1)*((109395.0/16.0)* std::pow(w, 7)- + 135135.0/16.0 * std::pow(w, 5) + (45045.0/16.0)*std::pow(w, 3) - + 3465.0/16.0 * w) * std::sin(2. * phi); + // l = 9, m = -1 + rn[i + 8] = -(0.149071198499986*std::sqrt(w2m1)*((109395.0/128.0)* std::pow(w, 8) - + 45045.0/32.0 * std::pow(w, 6) + (45045.0/64.0)* std::pow(w, 4) - + 3465.0/32.0 * w * w + 315.0/128.0) * std::sin(phi)); + // l = 9, m = 0 + rn[i + 9] = 94.9609375 * std::pow(w, 9) - 201.09375 * std::pow(w, 7) + + 140.765625 * std::pow(w, 5)- 36.09375 * std::pow(w, 3) + 2.4609375 * w; + // l = 9, m = 1 + rn[i + 10] = -(0.149071198499986*std::sqrt(w2m1)*((109395.0/128.0)* std::pow(w, 8) - + 45045.0/32.0 * std::pow(w, 6) + (45045.0/64.0)* std::pow(w, 4) - + 3465.0/32.0 * w * w + 315.0/128.0) * std::cos(phi)); + // l = 9, m = 2 + rn[i + 11] = 0.0158910431540932 * (w2m1)*((109395.0/16.0)* std::pow(w, 7) - + 135135.0/16.0 * std::pow(w, 5) + (45045.0/16.0)*std::pow(w, 3) - + 3465.0/ 16.0 * w) * std::cos(2. * phi); + // l = 9, m = 3 + rn[i + 12] = -(0.00173385495536766 * std::pow(w2m1, 1.5)*((765765.0/16.0) * + std::pow(w, 6) - 675675.0/16.0 * std::pow(w, 4) + + (135135.0/16.0)* w * w - 3465.0/16.0)* std::cos(3. * phi)); + // l = 9, m = 4 + rn[i + 13] = 0.000196320414650061 * w2m1 * w2m1*((2297295.0/8.0) * std::pow(w, 5) - + 675675.0/4.0 * std::pow(w, 3) + (135135.0/8.0)*w) * std::cos(4.0 * phi); + // l = 9, m = 5 + rn[i + 14] = -(2.34647776186144e-5*std::pow(w2m1, 2.5)*((11486475.0/8.0) * + std::pow(w, 4) - 2027025.0/4.0 * w * w + 135135.0/8.0) * + std::cos(5.0 * phi)); + // l = 9, m = 6 + rn[i + 15] = 3.02928976464514e-6*std::pow(w2m1, 3)*((11486475.0/2.)*std::pow(w, 3) - + 2027025.0/2. * w) * std::cos(6.0 * phi); + // l = 9, m = 7 + rn[i + 16] = -(4.37240315267812e-7*std::pow(w2m1, 3.5)* + ((34459425.0/2.) * w * w - 2027025.0/2.) * std::cos(7.0 * phi)); + // l = 9, m = 8 + rn[i + 17] = 2.58397773170915 * w*std::pow(w2m1, 4) * std::cos(8.0 * phi); + // l = 9, m = 9 + rn[i + 18] = -(0.609049392175524 * std::pow(w2m1, 4.5) * std::cos(9.0 * phi)); + break; + case 10: + // l = 10, m = -10 + rn[i] = 0.593627917136573 * std::pow(w2m1, 5) * std::sin(10.0 * phi); + // l = 10, m = -9 + rn[i + 1] = -(2.65478475211798 * w * std::pow(w2m1, 4.5) * std::sin(9.0 * phi)); + // l = 10, m = -8 + rn[i + 2] = 2.49953651452314e-8 * std::pow(w2m1, 4) * + ((654729075.0/2.) * w * w - 34459425.0/2.) * std::sin(8.0 * phi); + // l = 10, m = -7 + rn[i + 3] = -(1.83677671621093e-7*std::pow(w2m1, 3.5)* + ((218243025.0/2.)*std::pow(w, 3) - 34459425.0/2.*w) * + std::sin(7.0 * phi)); + // l = 10, m = -6 + rn[i + 4] = 1.51464488232257e-6*std::pow(w2m1, 3)*((218243025.0/8.0)* std::pow(w, 4) - + 34459425.0/4.0 * w * w + 2027025.0/8.0) * std::sin(6.0 * phi); + // l = 10, m = -5 + rn[i + 5] = -(1.35473956745817e-5*std::pow(w2m1, 2.5)* + ((43648605.0/8.0)* std::pow(w, 5) - 11486475.0/4.0 * std::pow(w, 3) + + (2027025.0/8.0)*w) * std::sin(5.0 * phi)); + // l = 10, m = -4 + rn[i + 6] = 0.000128521880085575 * w2m1 * w2m1*((14549535.0/16.0)* std::pow(w, 6) - + 11486475.0/16.0 * std::pow(w, 4) + (2027025.0/16.0)*w * w - + 45045.0/16.0) * std::sin(4.0 * phi); + // l = 10, m = -3 + rn[i + 7] = -(0.00127230170115096 * std::pow(w2m1, 1.5)* + ((2078505.0/16.0)* std::pow(w, 7) - 2297295.0/16.0 * std::pow(w, 5) + + (675675.0/16.0)*std::pow(w, 3) - 45045.0/16.0 * w) * std::sin(3. * phi)); + // l = 10, m = -2 + rn[i + 8] = 0.012974982402692 * (w2m1)*((2078505.0/128.0)* std::pow(w, 8) - + 765765.0/32.0 * std::pow(w, 6) + (675675.0/64.0)* std::pow(w, 4) - + 45045.0/32.0 * w * w + 3465.0/128.0) * std::sin(2. * phi); + // l = 10, m = -1 + rn[i + 9] = -(0.134839972492648*std::sqrt(w2m1)*((230945.0/128.0)* std::pow(w, 9) - + 109395.0/32.0 * std::pow(w, 7) + (135135.0/64.0)* std::pow(w, 5) - + 15015.0/32.0 * std::pow(w, 3) + (3465.0/128.0)*w) * std::sin(phi)); + // l = 10, m = 0 + rn[i + 10] = 180.42578125 * std::pow(w, 10) - 427.32421875 * std::pow(w, 8) +351.9140625 + * std::pow(w, 6) - 117.3046875 * std::pow(w, 4) + 13.53515625 * w * w -0.24609375; + // l = 10, m = 1 + rn[i + 11] = -(0.134839972492648*std::sqrt(w2m1)*((230945.0/128.0)* std::pow(w, 9) - + 109395.0/32.0 * std::pow(w, 7) + (135135.0/64.0)* std::pow(w, 5) -15015.0/ + 32.0 * std::pow(w, 3) + (3465.0/128.0)*w) * std::cos(phi)); + // l = 10, m = 2 + rn[i + 12] = 0.012974982402692 * (w2m1)*((2078505.0/128.0)* std::pow(w, 8) - + 765765.0/32.0 * std::pow(w, 6) + (675675.0/64.0)* std::pow(w, 4) - + 45045.0/32.0 * w * w + 3465.0/128.0) * std::cos(2. * phi); + // l = 10, m = 3 + rn[i + 13] = -(0.00127230170115096 * std::pow(w2m1, 1.5)* + ((2078505.0/16.0)* std::pow(w, 7) - 2297295.0/16.0 * std::pow(w, 5) + + (675675.0/16.0)*std::pow(w, 3) - 45045.0/16.0 * w) * std::cos(3. * phi)); + // l = 10, m = 4 + rn[i + 14] = 0.000128521880085575 * w2m1 * w2m1*((14549535.0/16.0)* std::pow(w, 6) - + 11486475.0/16.0 * std::pow(w, 4) + (2027025.0/16.0) * w * w - + 45045.0/16.0) * std::cos(4.0 * phi); + // l = 10, m = 5 + rn[i + 15] = -(1.35473956745817e-5*std::pow(w2m1, 2.5)* + ((43648605.0/8.0)* std::pow(w, 5) - 11486475.0/4.0 * std::pow(w, 3) + + (2027025.0/8.0)*w) * std::cos(5.0 * phi)); + // l = 10, m = 6 + rn[i + 16] = 1.51464488232257e-6*std::pow(w2m1, 3)*((218243025.0/8.0)* std::pow(w, 4) - + 34459425.0/4.0 * w * w + 2027025.0/8.0) * std::cos(6.0 * phi); + // l = 10, m = 7 + rn[i + 17] = -(1.83677671621093e-7*std::pow(w2m1, 3.5) * + ((218243025.0/2.)*std::pow(w, 3) - 34459425.0/2.*w) * std::cos(7.0 * phi)); + // l = 10, m = 8 + rn[i + 18] = 2.49953651452314e-8*std::pow(w2m1, 4)* + ((654729075.0/2.)*w * w - 34459425.0/2.) * std::cos(8.0 * phi); + // l = 10, m = 9 + rn[i + 19] = -(2.65478475211798 * w*std::pow(w2m1, 4.5) * std::cos(9.0 * phi)); + // l = 10, m = 10 + rn[i + 20] = 0.593627917136573 * std::pow(w2m1, 5) * std::cos(10.0 * phi); + } } } @@ -585,7 +555,7 @@ void calc_rn_c(int n, double uvw[3], double rn[]){ // exactly pi //============================================================================== -void calc_zn_c(int n, double rho, double phi, double zn[]) { +void calc_zn_c(const int n, const double rho, const double phi, double zn[]) { // This procedure uses the modified Kintner's method for calculating Zernike // polynomials as outlined in Chong, C. W., Raveendran, P., & Mukundan, // R. (2003). A comparative analysis of algorithms for fast computation of @@ -689,7 +659,7 @@ void calc_zn_c(int n, double rho, double phi, double zn[]) { // and SERPENT. //============================================================================== -void rotate_angle_c(double uvw[3], double mu, double phi) { +void rotate_angle_c(double uvw[3], const double mu, double phi) { double phi_; // azimuthal angle double sinphi; // std::sine of azimuthal angle double cosphi; // cosine of azimuthal angle @@ -738,7 +708,7 @@ void rotate_angle_c(double uvw[3], double mu, double phi) { // This PDF can be sampled using rule C64 in the Monte Carlo Sampler LA-9721-MS. //============================================================================== -double maxwell_spectrum_c(double T) { +double maxwell_spectrum_c(const double T) { double E_out; // Sampled Energy double r1; @@ -768,7 +738,7 @@ double maxwell_spectrum_c(double T) { // MC lectures). //============================================================================== -double watt_spectrum_c(double a, double b) { +double watt_spectrum_c(const double a, const double b) { double E_out; // Sampled Energy double w; // sampled from Maxwellian @@ -836,7 +806,8 @@ double watt_spectrum_c(double a, double b) { // The curvefit is a polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E) ... //============================================================================== -void broaden_wmp_polynomials_c(double E, double dopp, int n, double factors[]) { +void broaden_wmp_polynomials_c(const double E, const double dopp, const int n, + double factors[]) { // Factors is already pre-allocated double sqrtE; // sqrt(energy) diff --git a/src/math_functions.h b/src/math_functions.h index ab35d140a2..8df72f56cb 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -23,35 +23,37 @@ const double SQRT_PI = std::sqrt(PI); // distribution with a specified probability level //============================================================================== -extern "C" double normal_percentile_c(double p) __attribute__ ((const)); +extern "C" double normal_percentile_c(const double p) __attribute__ ((const)); //============================================================================== // T_PERCENTILE calculates the percentile of the Student's t distribution with // a specified probability level and number of degrees of freedom //============================================================================== -extern "C" double t_percentile_c(double p, int df) __attribute__ ((const)); +extern "C" double t_percentile_c(const double p, const int df) + __attribute__ ((const)); //============================================================================== -// CALC_PN calculates the n-th order Legendre polynomial at the value of x. +// CALC_PN calculates the n-th order Legendre polynomials at the value of x. //============================================================================== -extern "C" double calc_pn_c(int n, double x) __attribute__ ((const)); +extern "C" void calc_pn_c(const int n, const double x, double pnx[]); //============================================================================== // EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients // and the value of x //============================================================================== -extern "C" double evaluate_legendre_c(int n, double data[], double x) - __attribute__ ((const)); +extern "C" double evaluate_legendre_c(const int n, const double data[], + const double x) __attribute__ ((const)); //============================================================================== // CALC_RN calculates the n-th order spherical harmonics for a given angle -// (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) +// (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) for n +// all 0 <= n //============================================================================== -extern "C" void calc_rn_c(int n, double uvw[3], double rn[]); +extern "C" void calc_rn_c(const int n, const double uvw[3], double rn[]); //============================================================================== // CALC_ZN calculates the n-th order modified Zernike polynomial moment for a @@ -60,7 +62,8 @@ extern "C" void calc_rn_c(int n, double uvw[3], double rn[]); // exactly pi //============================================================================== -extern "C" void calc_zn_c(int n, double rho, double phi, double zn[]); +extern "C" void calc_zn_c(const int n, const double rho, const double phi, + double zn[]); //============================================================================== // ROTATE_ANGLE rotates direction cosines through a polar angle whose cosine is @@ -68,7 +71,8 @@ extern "C" void calc_zn_c(int n, double rho, double phi, double zn[]); // with direct sampling rather than rejection as is done in MCNP and SERPENT. //============================================================================== -extern "C" void rotate_angle_c(double uvw[3], double mu, double phi = -10.); +extern "C" void rotate_angle_c(double uvw[3], const double mu, + double phi = -10.); //============================================================================== // MAXWELL_SPECTRUM samples an energy from the Maxwell fission distribution @@ -77,7 +81,7 @@ extern "C" void rotate_angle_c(double uvw[3], double mu, double phi = -10.); // This PDF can be sampled using rule C64 in the Monte Carlo Sampler LA-9721-MS. //============================================================================== -extern "C" double maxwell_spectrum_c(double T); +extern "C" double maxwell_spectrum_c(const double T); //============================================================================== // WATT_SPECTRUM samples the outgoing energy from a Watt energy-dependent @@ -88,7 +92,7 @@ extern "C" double maxwell_spectrum_c(double T); // MC lectures). //============================================================================== -extern "C" double watt_spectrum_c(double a, double b); +extern "C" double watt_spectrum_c(const double a, const double b); //============================================================================== // FADDEEVA the Faddeeva function, using Stephen Johnson's implementation @@ -103,8 +107,8 @@ extern "C" double watt_spectrum_c(double a, double b); // The curvefit is a polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E) ... //============================================================================== -extern "C" void broaden_wmp_polynomials_c(double E, double dopp, int n, - double factors[]); +extern "C" void broaden_wmp_polynomials_c(const double E, const double dopp, + const int n, double factors[]); } // namespace openmc #endif // MATH_FUNCTIONS_H \ No newline at end of file diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 2e1d6184c3..ef8b5915c1 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -7,7 +7,7 @@ module tally use dict_header, only: EMPTY use error, only: fatal_error use geometry_header - use math, only: t_percentile, calc_pn, calc_rn + use math, only: t_percentile use mesh_header, only: RegularMesh, meshes use message_passing use mgxs_header diff --git a/src/tallies/tally_filter_legendre.F90 b/src/tallies/tally_filter_legendre.F90 index b4ee7b06b1..4663df6519 100644 --- a/src/tallies/tally_filter_legendre.F90 +++ b/src/tallies/tally_filter_legendre.F90 @@ -51,13 +51,12 @@ contains type(TallyFilterMatch), intent(inout) :: match integer :: i - real(8) :: wgt + real(C_DOUBLE) :: wgt(this % n_bins) - ! TODO: Use recursive formula to calculate higher orders - do i = 0, this % order - wgt = calc_pn(i, p % mu) - call match % bins % push_back(i + 1) - call match % weights % push_back(wgt) + call calc_pn(this % order, p % mu, wgt) + do i = 1, this % n_bins + call match % bins % push_back(i) + call match % weights % push_back(wgt(i)) end do end subroutine get_all_bins diff --git a/src/tallies/tally_filter_sph_harm.F90 b/src/tallies/tally_filter_sph_harm.F90 index 90f04c4880..4a1f432732 100644 --- a/src/tallies/tally_filter_sph_harm.F90 +++ b/src/tallies/tally_filter_sph_harm.F90 @@ -74,30 +74,32 @@ contains integer :: i, j, n integer :: num_nm - real(8) :: wgt - real(8) :: rn(2*this % order + 1) + real(C_DOUBLE) :: wgt(this % order + 1) + real(C_DOUBLE) :: rn(this % n_bins) + + ! Determine cosine term for scatter expansion if necessary + if (this % cosine == COSINE_SCATTER) then + call calc_pn(this % order, p % mu, wgt) + else + wgt = ONE + end if + + ! Find the Rn,m values + call calc_rn(this % order, p % last_uvw, rn) - ! TODO: Use recursive formula to calculate higher orders j = 0 do n = 0, this % order - ! Determine cosine term for scatter expansion if necessary - if (this % cosine == COSINE_SCATTER) then - wgt = calc_pn(n, p % mu) - else - wgt = ONE - end if - ! Calculate n-th order spherical harmonics for (u,v,w) num_nm = 2*n + 1 - call calc_rn(n, p % last_uvw, rn(1:num_nm)) ! Append matching (bin,weight) for each moment do i = 1, num_nm j = j + 1 call match % bins % push_back(j) - call match % weights % push_back(wgt * rn(i)) + call match % weights % push_back(wgt(n + 1) * rn(j)) end do end do + end subroutine get_all_bins subroutine to_statepoint(this, filter_group) diff --git a/src/tallies/tally_filter_sptl_legendre.F90 b/src/tallies/tally_filter_sptl_legendre.F90 index 83db9a864e..1bfbd0e3b5 100644 --- a/src/tallies/tally_filter_sptl_legendre.F90 +++ b/src/tallies/tally_filter_sptl_legendre.F90 @@ -75,20 +75,19 @@ contains type(TallyFilterMatch), intent(inout) :: match integer :: i - real(8) :: wgt - real(8) :: x ! Position on specified axis - real(8) :: x_norm ! Normalized position + real(C_DOUBLE) :: wgt(this % n_bins) + real(C_DOUBLE) :: x ! Position on specified axis + real(C_DOUBLE) :: x_norm ! Normalized position x = p % coord(1) % xyz(this % axis) if (this % min <= x .and. x <= this % max) then ! Calculate normalized position between min and max x_norm = TWO*(x - this % min)/(this % max - this % min) - ONE - ! TODO: Use recursive formula to calculate higher orders - do i = 0, this % order - wgt = calc_pn(i, x_norm) - call match % bins % push_back(i + 1) - call match % weights % push_back(wgt) + call calc_pn(this % order, x_norm, wgt) + do i = 1, this % n_bins + call match % bins % push_back(i) + call match % weights % push_back(wgt(i)) end do end if end subroutine get_all_bins diff --git a/src/tallies/tally_filter_zernike.F90 b/src/tallies/tally_filter_zernike.F90 index fa205b1808..d005416138 100644 --- a/src/tallies/tally_filter_zernike.F90 +++ b/src/tallies/tally_filter_zernike.F90 @@ -61,7 +61,7 @@ contains integer :: i real(8) :: x, y, r, theta - real(8) :: zn(this % n_bins) + real(C_DOUBLE) :: zn(this % n_bins) ! Determine normalized (r,theta) positions x = p % coord(1) % xyz(1) - this % x diff --git a/tests/unit_tests/test_math.py b/tests/unit_tests/test_math.py index ab27437d61..d48119a372 100644 --- a/tests/unit_tests/test_math.py +++ b/tests/unit_tests/test_math.py @@ -39,14 +39,17 @@ def test_t_percentile(): 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] + ref_vals = np.array([sp.special.eval_legendre(n, test_xs) + for n in range(0, max_order + 1)]) - test_vals = [[openmc.capi.math.calc_pn(n, x) for x in test_xs] - for n in test_ns] + test_vals = [] + for x in test_xs: + test_vals.append(openmc.capi.math.calc_pn(max_order, x).tolist()) + + test_vals = np.swapaxes(np.array(test_vals), 0, 1) assert np.allclose(ref_vals, test_vals) @@ -61,7 +64,7 @@ def test_evaluate_legendre(): ref_vals = np.polynomial.legendre.legval(test_xs, test_coeffs) # Set the coefficients back to 1s for the test values since - # evaluate legendre includes the (2l+1)/2 term + # evaluate legendre incorporates the (2l+1)/2 term on its own test_coeffs = [1. for l in range(max_order + 1)] test_vals = np.array([openmc.capi.math.evaluate_legendre(test_coeffs, x) @@ -107,9 +110,7 @@ def test_calc_rn(): 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()) + test_vals = openmc.capi.math.calc_rn(max_order, test_uvw) assert np.allclose(ref_vals, test_vals) From a6e75c35098f843cbf6973847749085d7842b6f2 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sun, 13 May 2018 06:44:48 -0400 Subject: [PATCH 11/19] Cleaned up the optional parameters interface of rotate_angle --- openmc/capi/math.py | 9 ++++++--- src/math.F90 | 17 ++++++++++------- src/math_functions.cpp | 6 +++--- src/math_functions.h | 2 +- tests/unit_tests/test_math.py | 12 +++++++++++- 5 files changed, 31 insertions(+), 15 deletions(-) diff --git a/openmc/capi/math.py b/openmc/capi/math.py index c46390bced..95e30eff3f 100644 --- a/openmc/capi/math.py +++ b/openmc/capi/math.py @@ -1,4 +1,4 @@ -from ctypes import (c_int, c_double, POINTER) +from ctypes import (c_int, c_double, POINTER, CFUNCTYPE) import numpy as np from numpy.ctypeslib import ndpointer @@ -201,8 +201,11 @@ 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)) + if phi is None: + _dll.rotate_angle(uvw0_arr, c_double(mu), uvw, None) + else: + _dll.rotate_angle(uvw0_arr, c_double(mu), uvw, c_double(phi)) + return uvw diff --git a/src/math.F90 b/src/math.F90 index 7be3e5a6dc..057ebb213f 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -75,7 +75,7 @@ module math implicit none real(C_DOUBLE), intent(inout) :: uvw(3) real(C_DOUBLE), value, intent(in) :: mu - real(C_DOUBLE), value, intent(in) :: phi + real(C_DOUBLE), optional, intent(in) :: phi end subroutine rotate_angle_c_intfc function maxwell_spectrum_c_intfc(T) bind(C, name='maxwell_spectrum_c') & @@ -225,16 +225,19 @@ contains !=============================================================================== subroutine rotate_angle(uvw0, mu, uvw, phi) bind(C) - real(C_DOUBLE), intent(in) :: uvw0(3) ! directional cosine - real(C_DOUBLE), intent(in) :: mu ! cosine of angle in lab or CM - real(C_DOUBLE), intent(out) :: uvw(3) ! rotated directional cosine - real(C_DOUBLE), optional :: phi ! azimuthal angle + real(C_DOUBLE), intent(in) :: uvw0(3) ! directional cosine + real(C_DOUBLE), intent(in) :: mu ! cosine of angle in lab or CM + real(C_DOUBLE), intent(out) :: uvw(3) ! rotated directional cosine + real(C_DOUBLE), target, optional :: phi ! azimuthal angle + + real(C_DOUBLE), pointer :: phi_ptr uvw = uvw0 if (present(phi)) then - call rotate_angle_c_intfc(uvw, mu, phi) + phi_ptr => phi + call rotate_angle_c_intfc(uvw, mu, phi_ptr) else - call rotate_angle_c_intfc(uvw, mu, -10._8) + call rotate_angle_c_intfc(uvw, mu) end if end subroutine rotate_angle diff --git a/src/math_functions.cpp b/src/math_functions.cpp index c9d6fc3590..78ff08b055 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -659,7 +659,7 @@ void calc_zn_c(const int n, const double rho, const double phi, double zn[]) { // and SERPENT. //============================================================================== -void rotate_angle_c(double uvw[3], const double mu, double phi) { +void rotate_angle_c(double uvw[3], const double mu, double* phi) { double phi_; // azimuthal angle double sinphi; // std::sine of azimuthal angle double cosphi; // cosine of azimuthal angle @@ -675,8 +675,8 @@ void rotate_angle_c(double uvw[3], const double mu, double phi) { w0 = uvw[2]; // Sample azimuthal angle in [0,2pi) if none provided - if (phi != -10.) { - phi_ = phi; + if (phi != NULL) { + phi_ = (*phi); } else { phi_ = 2. * PI * prn(); } diff --git a/src/math_functions.h b/src/math_functions.h index 8df72f56cb..5417281ea4 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -72,7 +72,7 @@ extern "C" void calc_zn_c(const int n, const double rho, const double phi, //============================================================================== extern "C" void rotate_angle_c(double uvw[3], const double mu, - double phi = -10.); + double* phi=NULL); //============================================================================== // MAXWELL_SPECTRUM samples an energy from the Maxwell fission distribution diff --git a/tests/unit_tests/test_math.py b/tests/unit_tests/test_math.py index d48119a372..487d02b107 100644 --- a/tests/unit_tests/test_math.py +++ b/tests/unit_tests/test_math.py @@ -170,7 +170,17 @@ def test_rotate_angle(): assert np.array_equal(ref_uvw, test_uvw) - # Need to test phi=None somehow... + # Now to test phi is None + mu = 0.9 + settings = openmc.capi.settings + settings.seed = 1 + + # When seed = 1, phi will be sampled as 1.9116495709698769 + # The resultant reference is from hand-calculations given the above + ref_uvw = [0.9, 0.410813051297112, 0.1457142302040] + test_uvw = openmc.capi.math.rotate_angle(uvw0, mu) + + assert np.allclose(ref_uvw, test_uvw) def test_maxwell_spectrum(): From d7b20fb51ea27fefacfd4b4de2210a1a339e4e66 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sun, 13 May 2018 19:21:36 -0400 Subject: [PATCH 12/19] final preps for a PR --- openmc/capi/math.py | 15 ++++----- src/math_functions.cpp | 63 ++++++----------------------------- src/math_functions.h | 19 ++++------- tests/unit_tests/test_math.py | 13 -------- 4 files changed, 23 insertions(+), 87 deletions(-) diff --git a/openmc/capi/math.py b/openmc/capi/math.py index 95e30eff3f..e5ba5fdd65 100644 --- a/openmc/capi/math.py +++ b/openmc/capi/math.py @@ -1,39 +1,38 @@ -from ctypes import (c_int, c_double, POINTER, CFUNCTYPE) +from ctypes import (c_int, c_double, POINTER) import numpy as np from numpy.ctypeslib import ndpointer from . import _dll -# _dll.normal_percentile.restype = c_double -# _dll.normal_percentile.argtypes = [POINTER(c_double)] _dll.t_percentile.restype = c_double _dll.t_percentile.argtypes = [POINTER(c_double), POINTER(c_int)] + _dll.calc_pn.restype = None _dll.calc_pn.argtypes = [POINTER(c_int), POINTER(c_double), ndpointer(c_double)] + _dll.evaluate_legendre.restype = c_double _dll.evaluate_legendre.argtypes = [POINTER(c_int), POINTER(c_double), POINTER(c_double)] + _dll.calc_rn.restype = None _dll.calc_rn.argtypes = [POINTER(c_int), ndpointer(c_double), ndpointer(c_double)] + _dll.calc_zn.restype = None _dll.calc_zn.argtypes = [POINTER(c_int), POINTER(c_double), POINTER(c_double), ndpointer(c_double)] + _dll.rotate_angle.restype = None _dll.rotate_angle.argtypes = [ndpointer(c_double), POINTER(c_double), ndpointer(c_double), POINTER(c_double)] _dll.maxwell_spectrum.restype = c_double _dll.maxwell_spectrum.argtypes = [POINTER(c_double)] + _dll.watt_spectrum.restype = c_double _dll.watt_spectrum.argtypes = [POINTER(c_double), POINTER(c_double)] -# COMPLEX DATA TYPES? -# _dll.faddeeva.restype = c_double -# _dll.faddeeva.argtypes = [POINTER(c_double)] -# _dll.w_derivative.restype = c_double -# _dll.w_derivative.argtypes = [POINTER(c_double)] _dll.broaden_wmp_polynomials.restype = None _dll.broaden_wmp_polynomials.argtypes = [POINTER(c_double), POINTER(c_double), POINTER(c_int), ndpointer(c_double)] diff --git a/src/math_functions.cpp b/src/math_functions.cpp index 78ff08b055..9cd52096df 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -2,6 +2,16 @@ namespace openmc { +//============================================================================== +// Module constants. +//============================================================================== + +// TODO: cmath::M_PI has 3 more digits precision than the Fortran constant we +// use so for now we will reuse the Fortran constant until we are OK with +// modifying test results +extern "C" const double PI {3.1415926535898}; + +extern "C" const double SQRT_PI {std::sqrt(PI)}; //============================================================================== // NORMAL_PERCENTILE calculates the percentile of the standard normal @@ -748,59 +758,6 @@ double watt_spectrum_c(const double a, const double b) { return E_out; } -//============================================================================== -// FADDEEVA the Faddeeva function, using Stephen Johnson's implementation -//============================================================================== - -// double complex __attribute__ ((const)) faddeeva_c(double complex z) { -// double complex wv; // The resultant w(z) value -// double relerr; // Target relative error in the inner loop of MIT Faddeeva - -// // Technically, the value we want is given by the equation: -// // w(z) = I/Pi * Integrate[Exp[-t^2]/(z-t), {t, -Infinity, Infinity}] -// // as shown in Equation 63 from Hwang, R. N. "A rigorous pole -// // representation of multilevel cross sections and its practical -// // applications." Nuclear Science and Engineering 96.3 (1987): 192-209. -// // -// // The MIT Faddeeva function evaluates w(z) = exp(-z^2)erfc(-iz). These -// // two forms of the Faddeeva function are related by a transformation. -// // -// // If we call the integral form w_int, and the function form w_fun: -// // For imag(z) > 0, w_int(z) = w_fun(z) -// // For imag(z) < 0, w_int(z) = -conjg(w_fun(conjg(z))) - -// // Note that faddeeva_w will interpret zero as machine epsilon - -// relerr = 0.; -// if (cimag(z) > 0.) { -// wv = Faddeeva_w(z, relerr); -// } else { -// wv = -conj(Faddeeva_w(conj(z), relerr)); -// } - -// return wv; -// } - -// double complex w_derivative_c(double complex z, int order){ -// double complex wv; // The resultant w(z) value - -// const double complex twoi_sqrtpi = 2.0 / SQRT_PI * I; - -// switch(order) { -// case 0: -// wv = faddeeva_c(z); -// break; -// case 1: -// wv = -2. * z * faddeeva_c(z) + twoi_sqrtpi; -// break; -// default: -// wv = -2. * z * w_derivative_c(z, order - 1) - 2. * (order - 1) * -// w_derivative_c(z, order - 2); -// } - -// return wv; -// } - //============================================================================== // BROADEN_WMP_POLYNOMIALS Doppler broadens the windowed multipole curvefit. // The curvefit is a polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E) ... diff --git a/src/math_functions.h b/src/math_functions.h index 5417281ea4..431e9ddeeb 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -2,21 +2,22 @@ #define MATH_FUNCTIONS_H #include -#include -#include #include "random_lcg.h" -// #include "faddeeva/Faddeeva.h" namespace openmc { +//============================================================================== +// Module constants. +//============================================================================== + // TODO: cmath::M_PI has 3 more digits precision than the Fortran constant we // use so for now we will reuse the Fortran constant until we are OK with // modifying test results -const double PI = 3.1415926535898; +extern "C" const double PI; -const double SQRT_PI = std::sqrt(PI); +extern "C" const double SQRT_PI; //============================================================================== // NORMAL_PERCENTILE calculates the percentile of the standard normal @@ -94,14 +95,6 @@ extern "C" double maxwell_spectrum_c(const double T); extern "C" double watt_spectrum_c(const double a, const double b); -//============================================================================== -// FADDEEVA the Faddeeva function, using Stephen Johnson's implementation -//============================================================================== - -// extern "C" double complex faddeeva_c(double complex z) __attribute__ ((const)); - -// extern "C" double complex w_derivative_c(double complex z, int order); - //============================================================================== // BROADEN_WMP_POLYNOMIALS Doppler broadens the windowed multipole curvefit. // The curvefit is a polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E) ... diff --git a/tests/unit_tests/test_math.py b/tests/unit_tests/test_math.py index 487d02b107..6825f6b930 100644 --- a/tests/unit_tests/test_math.py +++ b/tests/unit_tests/test_math.py @@ -5,19 +5,6 @@ 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 From aaef39d710bce39ddafabad40b1c1ad011320bb2 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Mon, 14 May 2018 07:09:07 -0400 Subject: [PATCH 13/19] fixing compiler errors --- src/math_functions.cpp | 6 +++--- src/math_functions.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/math_functions.cpp b/src/math_functions.cpp index 9cd52096df..179c028db2 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -685,7 +685,7 @@ void rotate_angle_c(double uvw[3], const double mu, double* phi) { w0 = uvw[2]; // Sample azimuthal angle in [0,2pi) if none provided - if (phi != NULL) { + if (phi != nullptr) { phi_ = (*phi); } else { phi_ = 2. * PI * prn(); @@ -694,8 +694,8 @@ void rotate_angle_c(double uvw[3], const double mu, double* phi) { // Precompute factors to save flops sinphi = std::sin(phi_); cosphi = std::cos(phi_); - a = std::sqrt(std::max(0., 1. - mu * mu)); - b = std::sqrt(std::max(0., 1. - w0 * w0)); + a = std::sqrt(std::fmax(0., 1. - mu * mu)); + b = std::sqrt(std::fmax(0., 1. - w0 * w0)); // Need to treat special case where sqrt(1 - w**2) is close to zero by // expanding about the v component rather than the w component diff --git a/src/math_functions.h b/src/math_functions.h index 431e9ddeeb..fbebd7dd7b 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -73,7 +73,7 @@ extern "C" void calc_zn_c(const int n, const double rho, const double phi, //============================================================================== extern "C" void rotate_angle_c(double uvw[3], const double mu, - double* phi=NULL); + double* phi=nullptr); //============================================================================== // MAXWELL_SPECTRUM samples an energy from the Maxwell fission distribution From 235974df668d4193897ec52a90a25edd8d2fd183 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Mon, 14 May 2018 20:24:38 -0400 Subject: [PATCH 14/19] Resolving @smharpers comments, and anticipating this will fix the travis build issue --- src/math.F90 | 16 ---- src/math_functions.cpp | 199 +++++++++++++---------------------------- src/math_functions.h | 121 +++++++++++++++++-------- 3 files changed, 150 insertions(+), 186 deletions(-) diff --git a/src/math.F90 b/src/math.F90 index 057ebb213f..cc5732e2d6 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -95,22 +95,6 @@ module math real(C_DOUBLE) :: E_out end function watt_spectrum_c_intfc - function faddeeva_c_intfc(z) bind(C, name='faddeeva_c') result(wv) - use ISO_C_BINDING - implicit none - complex(C_DOUBLE_COMPLEX), value, intent(in) :: z - complex(C_DOUBLE_COMPLEX) :: wv - end function faddeeva_c_intfc - - function w_derivative_c_intfc(z, order) bind(C, name='w_derivative_c') & - result(wv) - use ISO_C_BINDING - implicit none - complex(C_DOUBLE_COMPLEX), value, intent(in) :: z - integer(C_INT), value, intent(in) :: order - complex(C_DOUBLE_COMPLEX) :: wv - end function w_derivative_c_intfc - subroutine broaden_wmp_polynomials_c_intfc(E, dopp, n, factors) & bind(C, name='broaden_wmp_polynomials_c') use ISO_C_BINDING diff --git a/src/math_functions.cpp b/src/math_functions.cpp index 179c028db2..df5485ce12 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -3,41 +3,29 @@ namespace openmc { //============================================================================== -// Module constants. +// Mathematical methods //============================================================================== -// TODO: cmath::M_PI has 3 more digits precision than the Fortran constant we -// use so for now we will reuse the Fortran constant until we are OK with -// modifying test results -extern "C" const double PI {3.1415926535898}; - -extern "C" const double SQRT_PI {std::sqrt(PI)}; - -//============================================================================== -// NORMAL_PERCENTILE calculates the percentile of the standard normal -// distribution with a specified probability level -//============================================================================== - -double __attribute__ ((const)) normal_percentile_c(const double p) { - double z; - double q; - double r; - const double p_low = 0.02425; - const double a[6] = {-3.969683028665376e1, 2.209460984245205e2, - -2.759285104469687e2, 1.383577518672690e2, - -3.066479806614716e1, 2.506628277459239e0}; - const double b[5] = {-5.447609879822406e1, 1.615858368580409e2, - -1.556989798598866e2, 6.680131188771972e1, - -1.328068155288572e1}; - const double c[6] = {-7.784894002430293e-3, -3.223964580411365e-1, - -2.400758277161838, -2.549732539343734, - 4.374664141464968, 2.938163982698783}; - const double d[4] = {7.784695709041462e-3, 3.224671290700398e-1, - 2.445134137142996, 3.754408661907416}; +double normal_percentile_c(const double p) { + constexpr double p_low = 0.02425; + constexpr double a[6] = {-3.969683028665376e1, 2.209460984245205e2, + -2.759285104469687e2, 1.383577518672690e2, + -3.066479806614716e1, 2.506628277459239e0}; + constexpr double b[5] = {-5.447609879822406e1, 1.615858368580409e2, + -1.556989798598866e2, 6.680131188771972e1, + -1.328068155288572e1}; + constexpr double c[6] = {-7.784894002430293e-3, -3.223964580411365e-1, + -2.400758277161838, -2.549732539343734, + 4.374664141464968, 2.938163982698783}; + constexpr double d[4] = {7.784695709041462e-3, 3.224671290700398e-1, + 2.445134137142996, 3.754408661907416}; // The rational approximation used here is from an unpublished work at // http://home.online.no/~pjacklam/notes/invnorm/ + double z; + double q; + if (p < p_low) { // Rational approximation for lower region. @@ -47,7 +35,7 @@ double __attribute__ ((const)) normal_percentile_c(const double p) { } else if (p <= 1.0 - p_low) { // Rational approximation for central region - + double r; q = p - 0.5; r = q * q; z = (((((a[0]*r + a[1])*r + a[2])*r + a[3])*r + a[4])*r + a[5])*q / @@ -70,17 +58,9 @@ double __attribute__ ((const)) normal_percentile_c(const double p) { } -//============================================================================== -// T_PERCENTILE calculates the percentile of the Student's t distribution with -// a specified probability level and number of degrees of freedom -//============================================================================== -double __attribute__ ((const)) t_percentile_c(const double p, const int df){ +double t_percentile_c(const double p, const int df){ double t; - double n; - double k; - double z; - double z2; if (df == 1) { // For one degree of freedom, the t-distribution becomes a Cauchy @@ -98,7 +78,10 @@ double __attribute__ ((const)) t_percentile_c(const double p, const int df){ // modification of the Fisher-Cornish approximation for the student t // percentiles," Communication in Statistics - Simulation and Computation, // 16 (4), pp. 1123-1132 (1987). - + double n; + double k; + double z; + double z2; n = static_cast(df); k = 1. / (n - 2.); z = normal_percentile_c(p); @@ -111,61 +94,42 @@ double __attribute__ ((const)) t_percentile_c(const double p, const int df){ return t; } -//============================================================================== -// CALC_PN calculates the n-th order Legendre polynomials at the value of x. -//============================================================================== void calc_pn_c(const int n, const double x, double pnx[]) { - int l; - pnx[0] = 1.; if (n >= 1) { pnx[1] = x; } // Use recursion relation to build the higher orders - for (l = 1; l < n; l ++) { + for (int l = 1; l < n; l ++) { pnx[l + 1] = (static_cast(2 * l + 1) * x * pnx[l] - static_cast(l) * pnx[l - 1]) / (static_cast(l + 1)); } } -//============================================================================== -// EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients -// and the value of x -//============================================================================== -double __attribute__ ((const)) evaluate_legendre_c(const int n, +double evaluate_legendre_c(const int n, const double data[], const double x) { double val; - int l; - double* pnx = new double[n + 1]; + double pnx[n + 1]; val = 0.0; calc_pn_c(n, x, pnx); - for (l = 0; l <= n; l++) { + for (int l = 0; l <= n; l++) { val += (static_cast(l) + 0.5) * data[l] * pnx[l]; } - delete[] pnx; return val; } -//============================================================================== -// CALC_RN calculates the n-th order spherical harmonics for a given angle -// (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) for n -// all 0 <= n -//============================================================================== void calc_rn_c(const int n, const double uvw[3], double rn[]){ + // rn[] is assumed to have already been allocated to the correct size + double phi; double w; - double w2m1; - int i; - int l; - - // rn[] is assumed to have already been allocated to the correct size // Store the cosine of the polar angle and the azimuthal angle w = uvw[2]; @@ -176,12 +140,14 @@ void calc_rn_c(const int n, const double uvw[3], double rn[]){ } // Store the shorthand of 1-w * w + double w2m1; w2m1 = 1. - w * w; // Now evaluate the spherical harmonics function rn[0] = 1.; + int i; i = 0; - for (l = 1; l <= n; l++) { + for (int l = 1; l <= n; l++) { // Set the index to the start of this order i += 2 * (l - 1) + 1; @@ -558,33 +524,12 @@ void calc_rn_c(const int n, const double uvw[3], double rn[]){ } } -//============================================================================== -// CALC_ZN calculates the n-th order modified Zernike polynomial moment for a -// given angle (rho, theta) location in the unit disk. The normalization of the -// polynomials is such tha the integral of Z_pq*Z_pq over the unit disk is -// exactly pi -//============================================================================== void calc_zn_c(const int n, const double rho, const double phi, double zn[]) { // This procedure uses the modified Kintner's method for calculating Zernike // polynomials as outlined in Chong, C. W., Raveendran, P., & Mukundan, // R. (2003). A comparative analysis of algorithms for fast computation of // Zernike moments. Pattern Recognition, 36(3), 731-742. - double sin_phi; // Cosine of phi - double cos_phi; // Sine of phi - double sin_phi_vec[n + 1]; // Sin[n * phi] - double cos_phi_vec[n + 1]; // Cos[n * phi] - double zn_mat[n + 1][n + 1]; // Matrix forms of the coefficients which are - // easier to work with - // Variables for R_m_n calculation - double k1; - double k2; - double k3; - double k4; - // Loop counters - int i; - int p; - int q; // n == radial degree // m == azimuthal frequency @@ -597,41 +542,50 @@ void calc_zn_c(const int n, const double rho, const double phi, double zn[]) { // sin(nx) = 2 cos(x) sin((n-1)x) - sin((n-2)x) // cos(nx) = 2 cos(x) cos((n-1)x) - cos((n-2)x) + double sin_phi; + double cos_phi; sin_phi = std::sin(phi); cos_phi = std::cos(phi); + double sin_phi_vec[n + 1]; // Sin[n * phi] + double cos_phi_vec[n + 1]; // Cos[n * phi] sin_phi_vec[0] = 1.0; cos_phi_vec[0] = 1.0; - sin_phi_vec[1] = 2.0 * cos_phi; cos_phi_vec[1] = cos_phi; - for (i = 2; i <= n; i++) { + for (int i = 2; i <= n; i++) { sin_phi_vec[i] = 2. * cos_phi * sin_phi_vec[i - 1] - sin_phi_vec[i - 2]; cos_phi_vec[i] = 2. * cos_phi * cos_phi_vec[i - 1] - cos_phi_vec[i - 2]; } - for (i = 0; i <= n; i++) { + for (int i = 0; i <= n; i++) { sin_phi_vec[i] *= sin_phi; } // =========================================================================== // Calculate R_pq(rho) + double zn_mat[n + 1][n + 1]; // Matrix forms of the coefficients which are + // easier to work with // Fill the main diagonal first (Eq 3.9 in Chong) - for (p = 0; p <= n; p++) { + for (int p = 0; p <= n; p++) { zn_mat[p][p] = std::pow(rho, p); } // Fill the 2nd diagonal (Eq 3.10 in Chong) - for (q = 0; q <= n - 2; q++) { + for (int q = 0; q <= n - 2; q++) { zn_mat[q][q+2] = (q + 2) * zn_mat[q+2][q+2] - (q + 1) * zn_mat[q][q]; } // Fill in the rest of the values using the original results (Eq. 3.8 in Chong) - for (p = 4; p <= n; p++) { + double k1; + double k2; + double k3; + double k4; + for (int p = 4; p <= n; p++) { k2 = static_cast (2 * p * (p - 1) * (p - 2)); - for (q = p - 4; q >= 0; q -= 2) { + for (int q = p - 4; q >= 0; q -= 2) { k1 = static_cast((p + q) * (p - q) * (p - 2)) / 2.; k3 = static_cast(-q * q * (p - 1) - p * (p - 1) * (p - 2)); k4 = static_cast(-p * (p + q - 2) * (p - q - 2)) / 2.; @@ -646,9 +600,10 @@ void calc_zn_c(const int n, const double rho, const double phi, double zn[]) { // Note that the cos and sin vectors are offset by one // sin_phi_vec = [sin(x), sin(2x), sin(3x) ...] // cos_phi_vec = [1.0, cos(x), cos(2x)... ] + int i; i = 0; - for (p = 0; p <= n; p++) { - for (q = -p; q <= p; q += 2) { + for (int p = 0; p <= n; p++) { + for (int q = -p; q <= p; q += 2) { if (q < 0) { zn[i] = zn_mat[std::abs(q)][p] * sin_phi_vec[std::abs(q) - 1]; } else if (q == 0) { @@ -662,29 +617,19 @@ void calc_zn_c(const int n, const double rho, const double phi, double zn[]) { } -//============================================================================== -// ROTATE_ANGLE rotates direction std::cosines through a polar angle whose -// cosine is mu and through an azimuthal angle sampled uniformly. Note that -// this is done with direct sampling rather than rejection as is done in MCNP -// and SERPENT. -//============================================================================== void rotate_angle_c(double uvw[3], const double mu, double* phi) { - double phi_; // azimuthal angle - double sinphi; // std::sine of azimuthal angle - double cosphi; // cosine of azimuthal angle - double a; // sqrt(1 - mu^2) - double b; // sqrt(1 - w^2) - double u0; // original std::cosine in x direction - double v0; // original std::cosine in y direction - double w0; // original std::cosine in z direction + // Copy original directional cosines + double u0; // original cosine in x direction + double v0; // original cosine in y direction + double w0; // original cosine in z direction - // Copy original directional std::cosines u0 = uvw[0]; v0 = uvw[1]; w0 = uvw[2]; // Sample azimuthal angle in [0,2pi) if none provided + double phi_; if (phi != nullptr) { phi_ = (*phi); } else { @@ -692,6 +637,10 @@ void rotate_angle_c(double uvw[3], const double mu, double* phi) { } // Precompute factors to save flops + double sinphi; // sine of azimuthal angle + double cosphi; // cosine of azimuthal angle + double a; // sqrt(1 - mu^2) + double b; // sqrt(1 - w^2) sinphi = std::sin(phi_); cosphi = std::cos(phi_); a = std::sqrt(std::fmax(0., 1. - mu * mu)); @@ -711,42 +660,27 @@ void rotate_angle_c(double uvw[3], const double mu, double* phi) { } } -//============================================================================== -// MAXWELL_SPECTRUM samples an energy from the Maxwell fission distribution -// based on a direct sampling scheme. The probability distribution function for -// a Maxwellian is given as p(x) = 2/(T*sqrt(pi))*sqrt(x/T)*exp(-x/T). -// This PDF can be sampled using rule C64 in the Monte Carlo Sampler LA-9721-MS. -//============================================================================== double maxwell_spectrum_c(const double T) { - double E_out; // Sampled Energy - double r1; double r2; double r3; // random numbers - double c; // cosine of pi/2*r3 r1 = prn(); r2 = prn(); r3 = prn(); // determine cosine of pi/2*r + double c; c = std::cos(PI / 2. * r3); // determine outgoing energy + double E_out; // Sampled Energy E_out = -T * (std::log(r1) + std::log(r2) * c * c); return E_out; } -//============================================================================== -// WATT_SPECTRUM samples the outgoing energy from a Watt energy-dependent -// fission spectrum. Although fitted parameters exist for many nuclides, -// generally the continuous tabular distributions (LAW 4) should be used in -// lieu of the Watt spectrum. This direct sampling scheme is an unpublished -// scheme based on the original Watt spectrum derivation (See F. Brown's -// MC lectures). -//============================================================================== double watt_spectrum_c(const double a, const double b) { double E_out; // Sampled Energy @@ -758,10 +692,6 @@ double watt_spectrum_c(const double a, const double b) { return E_out; } -//============================================================================== -// BROADEN_WMP_POLYNOMIALS Doppler broadens the windowed multipole curvefit. -// The curvefit is a polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E) ... -//============================================================================== void broaden_wmp_polynomials_c(const double E, const double dopp, const int n, double factors[]) { @@ -773,8 +703,6 @@ void broaden_wmp_polynomials_c(const double E, const double dopp, const int n, double quarter_inv_dopp4; // 0.25 / dopp**4 double erf_beta; // error function of beta double exp_m_beta2; // exp(-beta**2) - int i; - double ip1_dbl; sqrtE = std::sqrt(E); beta = sqrtE * dopp; @@ -800,7 +728,8 @@ void broaden_wmp_polynomials_c(const double E, const double dopp, const int n, (beta * SQRT_PI); // Perform recursive broadening of high order components - for (i = 0; i < n - 3; i++) { + double ip1_dbl; + for (int i = 0; i < n - 3; i++) { ip1_dbl = static_cast(i + 1); if (i != 0) { factors[i + 3] = -factors[i - 1] * (ip1_dbl - 1.) * ip1_dbl * diff --git a/src/math_functions.h b/src/math_functions.h index fbebd7dd7b..b557157179 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -2,6 +2,7 @@ #define MATH_FUNCTIONS_H #include +#include #include "random_lcg.h" @@ -15,89 +16,139 @@ namespace openmc { // TODO: cmath::M_PI has 3 more digits precision than the Fortran constant we // use so for now we will reuse the Fortran constant until we are OK with // modifying test results -extern "C" const double PI; +extern "C" constexpr double PI {3.1415926535898}; -extern "C" const double SQRT_PI; +extern "C" constexpr double SQRT_PI {std::sqrt(PI)}; //============================================================================== -// NORMAL_PERCENTILE calculates the percentile of the standard normal -// distribution with a specified probability level +//! Calculate the percentile of the standard normal distribution with a +//! specified probability level. +//! +//! @param p The probability level +//! @return The requested percentile //============================================================================== -extern "C" double normal_percentile_c(const double p) __attribute__ ((const)); +extern "C" double normal_percentile_c(const double p); //============================================================================== -// T_PERCENTILE calculates the percentile of the Student's t distribution with -// a specified probability level and number of degrees of freedom +//! Calculate the percentile of the Student's t distribution with a specified +//! probability level and number of degrees of freedom. +//! +//! @param p The probability level +//! @param df The degrees of freedom +//! @return The requested percentile //============================================================================== -extern "C" double t_percentile_c(const double p, const int df) - __attribute__ ((const)); +extern "C" double t_percentile_c(const double p, const int df); //============================================================================== -// CALC_PN calculates the n-th order Legendre polynomials at the value of x. +//! Calculate the n-th order Legendre polynomials at the value of x. +//! +//! @param n The maximum order requested +//! @param x The value to evaluate at; x is expected to be within [-1,1] +//! @param pnx The requested Legendre polynomials of order 0 to n (inclusive) +//! evaluated at x. //============================================================================== extern "C" void calc_pn_c(const int n, const double x, double pnx[]); //============================================================================== -// EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients -// and the value of x +//! Find the value of f(x) given a set of Legendre coefficients and the value +//! of x. +//! +//! @param n The maximum order of the expansion +//! @param data The polynomial expansion coefficient data; without the (2l+1)/2 +//! factor. +//! @param x The value to evaluate at; x is expected to be within [-1,1] +//! @return The requested Legendre polynomials of order 0 to n (inclusive) +//! evaluated at x //============================================================================== extern "C" double evaluate_legendre_c(const int n, const double data[], - const double x) __attribute__ ((const)); + const double x); //============================================================================== -// CALC_RN calculates the n-th order spherical harmonics for a given angle -// (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) for n -// all 0 <= n +//! Calculate the n-th order real spherical harmonics for a given angle (in +//! terms of (u,v,w)) for all 0<=n and -m<=n<=n. +//! +//! @param n The maximum order requested +//! @param uvw[3] The direction the harmonics are requested at +//! @param rn The requested harmonics of order 0 to n (inclusive) +//! evaluated at uvw. //============================================================================== extern "C" void calc_rn_c(const int n, const double uvw[3], double rn[]); //============================================================================== -// CALC_ZN calculates the n-th order modified Zernike polynomial moment for a -// given angle (rho, theta) location in the unit disk. The normalization of the -// polynomials is such tha the integral of Z_pq*Z_pq over the unit disk is -// exactly pi +//! Calculate the n-th order modified Zernike polynomial moment for a given +//! angle (rho, theta) location on the unit disk. +//! +//! The normalization of the polynomials is such that the integral of Z_pq^2 +//! over the unit disk is exactly pi +//! +//! @param n The maximum order requested +//! @param rho The radial parameter to specify location on the unit disk +//! @param phi The angle parameter to specify location on the unit disk +//! @param zn The requested moments of order 0 to n (inclusive) +//! evaluated at rho and phi. //============================================================================== extern "C" void calc_zn_c(const int n, const double rho, const double phi, double zn[]); //============================================================================== -// ROTATE_ANGLE rotates direction cosines through a polar angle whose cosine is -// mu and through an azimuthal angle sampled uniformly. Note that this is done -// with direct sampling rather than rejection as is done in MCNP and SERPENT. +//! Rotate the direction cosines through a polar angle whose cosine is mu and +//! through an azimuthal angle sampled uniformly. +//! +//! This is done with direct sampling rather than rejection sampling as is done +//! in MCNP and Serpent. +//! +//! @param uvw[3] The initial, and final, direction vector +//! @param mu The cosine of angle in lab or CM +//! @param phi The azimuthal angle; defaults to a randomly chosen angle //============================================================================== extern "C" void rotate_angle_c(double uvw[3], const double mu, double* phi=nullptr); //============================================================================== -// MAXWELL_SPECTRUM samples an energy from the Maxwell fission distribution -// based on a direct sampling scheme. The probability distribution function for -// a Maxwellian is given as p(x) = 2/(T*sqrt(pi))*sqrt(x/T)*exp(-x/T). -// This PDF can be sampled using rule C64 in the Monte Carlo Sampler LA-9721-MS. +//! Samples an energy from the Maxwell fission distribution based on a direct +//! sampling scheme. +//! +//! The probability distribution function for a Maxwellian is given as +//! p(x) = 2/(T*sqrt(pi))*sqrt(x/T)*exp(-x/T). This PDF can be sampled using +//! rule C64 in the Monte Carlo Sampler LA-9721-MS. +//! +//! @param T The tabulated function of the incoming energy +//! @result The sampled outgoing energy //============================================================================== extern "C" double maxwell_spectrum_c(const double T); //============================================================================== -// WATT_SPECTRUM samples the outgoing energy from a Watt energy-dependent -// fission spectrum. Although fitted parameters exist for many nuclides, -// generally the continuous tabular distributions (LAW 4) should be used in -// lieu of the Watt spectrum. This direct sampling scheme is an unpublished -// scheme based on the original Watt spectrum derivation (See F. Brown's -// MC lectures). +//! Samples an energy from a Watt energy-dependent fission distribution. +//! +//! Although fitted parameters exist for many nuclides, generally the +//! continuous tabular distributions (LAW 4) should be used in lieu of the Watt +//! spectrum. This direct sampling scheme is an unpublished scheme based on the +//! original Watt spectrum derivation (See F. Brown's MC lectures). +//! +//! @param a Watt parameter a +//! @param b Watt parameter b +//! @result The sampled outgoing energy //============================================================================== extern "C" double watt_spectrum_c(const double a, const double b); //============================================================================== -// BROADEN_WMP_POLYNOMIALS Doppler broadens the windowed multipole curvefit. -// The curvefit is a polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E) ... +//! Doppler broadens the windowed multipole curvefit. +//! +//! The curvefit is a polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E)... +//! +//! @param E The energy to evaluate the broadening at +//! @param dopp sqrt(atomic weight ratio / kT) with kT given in eV +//! @param n The number of components to the polynomial +//! @param factors The output leading coefficient //============================================================================== extern "C" void broaden_wmp_polynomials_c(const double E, const double dopp, From 4c5bdd443871b4a2a5a88abd4a1dddf1242cc10b Mon Sep 17 00:00:00 2001 From: Zhuoran Han Date: Wed, 16 May 2018 14:54:24 -0400 Subject: [PATCH 15/19] Checkvalue bug --- openmc/checkvalue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/checkvalue.py b/openmc/checkvalue.py index 3b334319f6..4a82983d96 100644 --- a/openmc/checkvalue.py +++ b/openmc/checkvalue.py @@ -265,7 +265,7 @@ def check_filetype_version(obj, expected_type, expected_version): if this_version[0] != expected_version: raise IOError('{} file has a version of {} which is not ' 'consistent with the version expected by OpenMC, {}' - .format(this_filetype, '.'.join(this_version), + .format(this_filetype, '.'.join(str(v) for v in this_version), expected_version)) except AttributeError: raise IOError('Could not read {} file. This most likely means the {} ' From 873523072dc6c89b13bc1d159bc9351f6df1c1d1 Mon Sep 17 00:00:00 2001 From: Zhuoran Han Date: Wed, 16 May 2018 15:43:10 -0400 Subject: [PATCH 16/19] Split the lines for code format. --- openmc/checkvalue.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openmc/checkvalue.py b/openmc/checkvalue.py index 4a82983d96..cbeac9c373 100644 --- a/openmc/checkvalue.py +++ b/openmc/checkvalue.py @@ -265,8 +265,9 @@ def check_filetype_version(obj, expected_type, expected_version): if this_version[0] != expected_version: raise IOError('{} file has a version of {} which is not ' 'consistent with the version expected by OpenMC, {}' - .format(this_filetype, '.'.join(str(v) for v in this_version), - expected_version)) + .format(this_filetype, + '.'.join(str(v) for v in this_version), + expected_version)) except AttributeError: raise IOError('Could not read {} file. This most likely means the {} ' 'file was produced by a different version of OpenMC than ' From f223c2738feed8ac333a09acdc02cb9b8b6d0729 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 16 May 2018 18:17:23 -0400 Subject: [PATCH 17/19] Swap RectLattice distribcell ordering --- openmc/deplete/results_list.py | 12 ++++++------ openmc/lattice.py | 9 +++++++-- src/geometry.F90 | 16 ++++++++-------- src/tallies/tally_filter_distribcell.F90 | 4 ++-- .../asymmetric_lattice/results_true.dat | 2 +- .../distribmat/inputs_true.dat | 2 +- .../distribmat/results_true.dat | 2 +- tests/regression_tests/distribmat/test.py | 2 +- .../case-1/results_true.dat | 4 ++-- .../case-3/results_true.dat | 2 +- .../multipole/inputs_true.dat | 2 +- .../multipole/results_true.dat | 2 +- tests/regression_tests/multipole/test.py | 2 +- .../tally_aggregation/results_true.dat | 2 +- tests/regression_tests/test_deplete_full.py | 1 + tests/regression_tests/test_reference.h5 | Bin 162320 -> 160024 bytes tests/unit_tests/test_deplete_resultslist.py | 12 ++++++------ 17 files changed, 41 insertions(+), 35 deletions(-) diff --git a/openmc/deplete/results_list.py b/openmc/deplete/results_list.py index d3d45e955b..58e4b66dc1 100644 --- a/openmc/deplete/results_list.py +++ b/openmc/deplete/results_list.py @@ -43,8 +43,8 @@ class ResultsList(list): Total number of atoms for specified nuclide """ - time = np.empty_like(self) - concentration = np.empty_like(self) + time = np.empty_like(self, dtype=float) + concentration = np.empty_like(self, dtype=float) # Evaluate value in each region for i, result in enumerate(self): @@ -73,8 +73,8 @@ class ResultsList(list): Array of reaction rates """ - time = np.empty_like(self) - rate = np.empty_like(self) + time = np.empty_like(self, dtype=float) + rate = np.empty_like(self, dtype=float) # Evaluate value in each region for i, result in enumerate(self): @@ -94,8 +94,8 @@ class ResultsList(list): k-eigenvalue at each time """ - time = np.empty_like(self) - eigenvalue = np.empty_like(self) + time = np.empty_like(self, dtype=float) + eigenvalue = np.empty_like(self, dtype=float) # Get time/eigenvalue at each point for i, result in enumerate(self): diff --git a/openmc/lattice.py b/openmc/lattice.py index 86cfd5c41b..ef33247fca 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -546,10 +546,15 @@ class RectLattice(Lattice): """ if self.ndim == 2: nx, ny = self.shape - return np.broadcast(*np.ogrid[:nx, :ny]) + for iy in range(ny): + for ix in range(nx): + yield (ix, iy) else: nx, ny, nz = self.shape - return np.broadcast(*np.ogrid[:nx, :ny, :nz]) + for iz in range(nz): + for iy in range(ny): + for ix in range(nx): + yield (ix, iy, iz) @property def lower_left(self): diff --git a/src/geometry.F90 b/src/geometry.F90 index e747b619ac..651005631e 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -882,9 +882,9 @@ contains type is (RectLattice) ! Loop over lattice coordinates - do j = 1, lat % n_cells(1) + do m = 1, lat % n_cells(3) do k = 1, lat % n_cells(2) - do m = 1, lat % n_cells(3) + do j = 1, lat % n_cells(1) lat % offset(map, j, k, m) = offset next_univ => universes(lat % universes(j, k, m)) offset = offset + & @@ -999,9 +999,9 @@ contains type is (RectLattice) ! Loop over lattice coordinates - do j = 1, lat % n_cells(1) + do m = 1, lat % n_cells(3) do k = 1, lat % n_cells(2) - do m = 1, lat % n_cells(3) + do j = 1, lat % n_cells(1) next_univ => universes(lat % universes(j, k, m)) ! Found target - stop since target cannot contain itself @@ -1091,9 +1091,9 @@ contains type is (RectLattice) ! Loop over lattice coordinates - do j = 1, lat % n_cells(1) + do m = 1, lat % n_cells(3) do k = 1, lat % n_cells(2) - do m = 1, lat % n_cells(3) + do j = 1, lat % n_cells(1) call count_instance(universes(lat % universes(j, k, m))) end do end do @@ -1166,9 +1166,9 @@ contains type is (RectLattice) ! Loop over lattice coordinates - do j = 1, lat % n_cells(1) + do m = 1, lat % n_cells(3) do k = 1, lat % n_cells(2) - do m = 1, lat % n_cells(3) + do j = 1, lat % n_cells(1) next_univ => universes(lat % universes(j, k, m)) levels_below = max(levels_below, maximum_levels(next_univ)) end do diff --git a/src/tallies/tally_filter_distribcell.F90 b/src/tallies/tally_filter_distribcell.F90 index 6c42161ca7..ddf7f4d922 100644 --- a/src/tallies/tally_filter_distribcell.F90 +++ b/src/tallies/tally_filter_distribcell.F90 @@ -278,9 +278,9 @@ contains old_k = 1 ! Loop over lattice coordinates - do k = 1, n_x + do m = 1, n_z do l = 1, n_y - do m = 1, n_z + do k = 1, n_x if (target_offset >= lat % offset(map, k, l, m) + offset) then if (k == n_x .and. l == n_y .and. m == n_z) then diff --git a/tests/regression_tests/asymmetric_lattice/results_true.dat b/tests/regression_tests/asymmetric_lattice/results_true.dat index ef7608f2fc..f60cfa64f5 100644 --- a/tests/regression_tests/asymmetric_lattice/results_true.dat +++ b/tests/regression_tests/asymmetric_lattice/results_true.dat @@ -1 +1 @@ -3b76b468b9c0e7df6508189b75748a1b7b4f2b37486396749e1a15e536526336f70b60bb207095c39ecbd46822e8c8705ea81184a3c8546e7da09bb905d74637 \ No newline at end of file +bd3fd10177bc0c7b8542f15228decf8608de013872d201e6ae885cf9b5b6d7516ddb32044ee2f7fd5b207e7d37d5ce3f03e347a2e850953bba4fc26b150c89d2 \ No newline at end of file diff --git a/tests/regression_tests/distribmat/inputs_true.dat b/tests/regression_tests/distribmat/inputs_true.dat index 39e611e16a..a1c1e83116 100644 --- a/tests/regression_tests/distribmat/inputs_true.dat +++ b/tests/regression_tests/distribmat/inputs_true.dat @@ -1,7 +1,7 @@ - + diff --git a/tests/regression_tests/distribmat/results_true.dat b/tests/regression_tests/distribmat/results_true.dat index cd6aa95d4f..e0143f0fff 100644 --- a/tests/regression_tests/distribmat/results_true.dat +++ b/tests/regression_tests/distribmat/results_true.dat @@ -3,7 +3,7 @@ k-combined: Cell ID = 11 Name = - Fill = [2, 3, None, 2] + Fill = [2, None, 3, 2] Region = -9 Rotation = None Translation = None diff --git a/tests/regression_tests/distribmat/test.py b/tests/regression_tests/distribmat/test.py index de1b778772..cb598b9d3f 100644 --- a/tests/regression_tests/distribmat/test.py +++ b/tests/regression_tests/distribmat/test.py @@ -34,7 +34,7 @@ class DistribmatTestHarness(PyAPITestHarness): r0 = openmc.ZCylinder(R=0.3) c11 = openmc.Cell(cell_id=11, region=-r0) - c11.fill = [dense_fuel, light_fuel, None, dense_fuel] + c11.fill = [dense_fuel, None, light_fuel, dense_fuel] c12 = openmc.Cell(cell_id=12, region=+r0, fill=moderator) fuel_univ = openmc.Universe(universe_id=11, cells=[c11, c12]) diff --git a/tests/regression_tests/filter_distribcell/case-1/results_true.dat b/tests/regression_tests/filter_distribcell/case-1/results_true.dat index a824d7fce3..2d9835b1ab 100644 --- a/tests/regression_tests/filter_distribcell/case-1/results_true.dat +++ b/tests/regression_tests/filter_distribcell/case-1/results_true.dat @@ -3,10 +3,10 @@ k-combined: tally 1: 1.548980E-02 2.399339E-04 -1.278781E-02 -1.635280E-04 1.426319E-02 2.034385E-04 +1.278781E-02 +1.635280E-04 1.018927E-02 1.038213E-04 tally 2: diff --git a/tests/regression_tests/filter_distribcell/case-3/results_true.dat b/tests/regression_tests/filter_distribcell/case-3/results_true.dat index 8eb7c2b609..acd74781db 100644 --- a/tests/regression_tests/filter_distribcell/case-3/results_true.dat +++ b/tests/regression_tests/filter_distribcell/case-3/results_true.dat @@ -1 +1 @@ -386147796cf64c908002a81bc47af6763a14811ba6c457ca790653ceb8ef1c6b0376b94783f9746baf7389f18321b788c72758ec56ffd05fa3146139e6e53308 \ No newline at end of file +a1f6ccf0bef1075ffc12e7fa058a44c83360801ba6e2c76f00e1bd7e9543fe1832777b122321dcb1cfb5fc1165ce84f711a65ca83903dd57cd09652e003daf62 \ No newline at end of file diff --git a/tests/regression_tests/multipole/inputs_true.dat b/tests/regression_tests/multipole/inputs_true.dat index d1ef3e00aa..3bbcc20247 100644 --- a/tests/regression_tests/multipole/inputs_true.dat +++ b/tests/regression_tests/multipole/inputs_true.dat @@ -1,7 +1,7 @@ - + diff --git a/tests/regression_tests/multipole/results_true.dat b/tests/regression_tests/multipole/results_true.dat index 0ab4191d8b..d734e67bd0 100644 --- a/tests/regression_tests/multipole/results_true.dat +++ b/tests/regression_tests/multipole/results_true.dat @@ -37,5 +37,5 @@ Cell Fill = Material 2 Region = -1 Rotation = None - Temperature = [ 500. 0. 700. 800.] + Temperature = [ 500. 700. 0. 800.] Translation = None diff --git a/tests/regression_tests/multipole/test.py b/tests/regression_tests/multipole/test.py index 5c79d4d6b6..ef797a1bc5 100644 --- a/tests/regression_tests/multipole/test.py +++ b/tests/regression_tests/multipole/test.py @@ -29,7 +29,7 @@ def make_model(): r0 = openmc.ZCylinder(R=0.3) c11 = openmc.Cell(cell_id=11, fill=dense_fuel, region=-r0) - c11.temperature = [500, 0, 700, 800] + c11.temperature = [500, 700, 0, 800] c12 = openmc.Cell(cell_id=12, fill=moderator, region=+r0) fuel_univ = openmc.Universe(universe_id=11, cells=(c11, c12)) diff --git a/tests/regression_tests/tally_aggregation/results_true.dat b/tests/regression_tests/tally_aggregation/results_true.dat index ee763b3bac..0f0a3d60a9 100644 --- a/tests/regression_tests/tally_aggregation/results_true.dat +++ b/tests/regression_tests/tally_aggregation/results_true.dat @@ -1 +1 @@ -eb2002dd2f3016154e7014501629227eb2e5b2a9655b5c0cb3b9d4d681f918fae4197bf4756fe9865c8d34f392b981b287a15e9b2fab5a46b2a5b8a33a8ae770 \ No newline at end of file +8294c7481b3433a4beb25541ae72c1ea915def0c0d0f393f7585371877187f4a2a25e70bb602232e2bc1e877e78db0e9384591e5c71575659f95abb10fae0af3 \ No newline at end of file diff --git a/tests/regression_tests/test_deplete_full.py b/tests/regression_tests/test_deplete_full.py index 19e2c7664b..58e6ddac40 100644 --- a/tests/regression_tests/test_deplete_full.py +++ b/tests/regression_tests/test_deplete_full.py @@ -22,6 +22,7 @@ def test_full(run_in_tmpdir): This test runs a complete OpenMC simulation and tests the outputs. It will take a while. + """ n_rings = 2 diff --git a/tests/regression_tests/test_reference.h5 b/tests/regression_tests/test_reference.h5 index 5323a9a904e0fce1f806fbc49ad90324b4fe1aa9..bae9a1adfefbce2d9124bfe6c9be1b7b2e5d3fa9 100644 GIT binary patch literal 160024 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 L)HDBoUGx6{a^}}A literal 162320 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 Date: Wed, 16 May 2018 21:25:29 -0400 Subject: [PATCH 18/19] Implementing comments from @paulromano, @smharper, and @giudgiud. A few remain open, will close in a day or two after some more GitHub discussion --- CMakeLists.txt | 1 - openmc/capi/math.py | 20 +----- src/math.F90 | 7 +- src/math_functions.cpp | 158 ++++++++++++++--------------------------- src/math_functions.h | 36 +++++----- 5 files changed, 73 insertions(+), 149 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c34dd3770a..dc5558de18 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -435,7 +435,6 @@ set(LIBOPENMC_CXX_SRC src/initialize.cpp src/finalize.cpp src/hdf5_interface.cpp - src/math_functions.h src/math_functions.cpp src/message_passing.cpp src/plot.cpp diff --git a/openmc/capi/math.py b/openmc/capi/math.py index e5ba5fdd65..3b0b723b32 100644 --- a/openmc/capi/math.py +++ b/openmc/capi/math.py @@ -5,6 +5,7 @@ from numpy.ctypeslib import ndpointer from . import _dll + _dll.t_percentile.restype = c_double _dll.t_percentile.argtypes = [POINTER(c_double), POINTER(c_int)] @@ -38,25 +39,6 @@ _dll.broaden_wmp_polynomials.argtypes = [POINTER(c_double), POINTER(c_double), POINTER(c_int), ndpointer(c_double)] -def normal_percentile(p): - """ Calculate the percentile of the standard normal distribution with a - specified probability level - - Parameters - ---------- - p : float - Probability level - - Returns - ------- - float - Corresponding z-value - - """ - - return _dll.normal_percentile(c_double(p)) - - def t_percentile(p, df): """ Calculate the percentile of the Student's t distribution with a specified probability level and number of degrees of freedom diff --git a/src/math.F90 b/src/math.F90 index cc5732e2d6..05942a86e3 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -217,12 +217,7 @@ contains real(C_DOUBLE), pointer :: phi_ptr uvw = uvw0 - if (present(phi)) then - phi_ptr => phi - call rotate_angle_c_intfc(uvw, mu, phi_ptr) - else - call rotate_angle_c_intfc(uvw, mu) - end if + call rotate_angle_c_intfc(uvw, mu, phi) end subroutine rotate_angle diff --git a/src/math_functions.cpp b/src/math_functions.cpp index df5485ce12..159e0a4e23 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -6,7 +6,7 @@ namespace openmc { // Mathematical methods //============================================================================== -double normal_percentile_c(const double p) { +double normal_percentile_c(double p) { constexpr double p_low = 0.02425; constexpr double a[6] = {-3.969683028665376e1, 2.209460984245205e2, -2.759285104469687e2, 1.383577518672690e2, @@ -35,9 +35,8 @@ double normal_percentile_c(const double p) { } else if (p <= 1.0 - p_low) { // Rational approximation for central region - double r; q = p - 0.5; - r = q * q; + double r = q * q; z = (((((a[0]*r + a[1])*r + a[2])*r + a[3])*r + a[4])*r + a[5])*q / (((((b[0]*r + b[1])*r + b[2])*r + b[3])*r + b[4])*r + 1.0); @@ -59,7 +58,7 @@ double normal_percentile_c(const double p) { } -double t_percentile_c(const double p, const int df){ +double t_percentile_c(double p, int df){ double t; if (df == 1) { @@ -78,14 +77,10 @@ double t_percentile_c(const double p, const int df){ // modification of the Fisher-Cornish approximation for the student t // percentiles," Communication in Statistics - Simulation and Computation, // 16 (4), pp. 1123-1132 (1987). - double n; - double k; - double z; - double z2; - n = static_cast(df); - k = 1. / (n - 2.); - z = normal_percentile_c(p); - z2 = z * z; + double n = df; + double k = 1. / (n - 2.); + double z = normal_percentile_c(p); + double z2 = z * z; t = std::sqrt(n * k) * (z + (z2 - 3.) * z * k / 4. + ((5. * z2 - 56.) * z2 + 75.) * z * k * k / 96. + (((z2 - 27.) * 3. * z2 + 417.) * z2 - 315.) * z * k * k * k / 384.); @@ -95,7 +90,7 @@ double t_percentile_c(const double p, const int df){ } -void calc_pn_c(const int n, const double x, double pnx[]) { +void calc_pn_c(int n, double x, double pnx[]) { pnx[0] = 1.; if (n >= 1) { pnx[1] = x; @@ -103,36 +98,28 @@ void calc_pn_c(const int n, const double x, double pnx[]) { // Use recursion relation to build the higher orders for (int l = 1; l < n; l ++) { - pnx[l + 1] = (static_cast(2 * l + 1) * x * pnx[l] - - static_cast(l) * pnx[l - 1]) / - (static_cast(l + 1)); + pnx[l + 1] = ((2 * l + 1) * x * pnx[l] - l * pnx[l - 1]) / (l + 1); } } -double evaluate_legendre_c(const int n, - const double data[], - const double x) { - double val; +double evaluate_legendre_c(int n, const double data[], double x) { double pnx[n + 1]; - - val = 0.0; + double val = 0.0; calc_pn_c(n, x, pnx); for (int l = 0; l <= n; l++) { - val += (static_cast(l) + 0.5) * data[l] * pnx[l]; + val += (l + 0.5) * data[l] * pnx[l]; } return val; } -void calc_rn_c(const int n, const double uvw[3], double rn[]){ +void calc_rn_c(int n, const double uvw[3], double rn[]){ // rn[] is assumed to have already been allocated to the correct size - double phi; - double w; - // Store the cosine of the polar angle and the azimuthal angle - w = uvw[2]; + double w = uvw[2]; + double phi; if (uvw[0] == 0.) { phi = 0.; } else { @@ -140,13 +127,11 @@ void calc_rn_c(const int n, const double uvw[3], double rn[]){ } // Store the shorthand of 1-w * w - double w2m1; - w2m1 = 1. - w * w; + double w2m1 = 1. - w * w; // Now evaluate the spherical harmonics function rn[0] = 1.; - int i; - i = 0; + int i = 0; for (int l = 1; l <= n; l++) { // Set the index to the start of this order i += 2 * (l - 1) + 1; @@ -525,15 +510,7 @@ void calc_rn_c(const int n, const double uvw[3], double rn[]){ } -void calc_zn_c(const int n, const double rho, const double phi, double zn[]) { - // This procedure uses the modified Kintner's method for calculating Zernike - // polynomials as outlined in Chong, C. W., Raveendran, P., & Mukundan, - // R. (2003). A comparative analysis of algorithms for fast computation of - // Zernike moments. Pattern Recognition, 36(3), 731-742. - - // n == radial degree - // m == azimuthal frequency - +void calc_zn_c(int n, double rho, double phi, double zn[]) { // =========================================================================== // Determine vector of sin(n*phi) and cos(n*phi). This takes advantage of the // following recurrence relations so that only a single sin/cos have to be @@ -542,10 +519,8 @@ void calc_zn_c(const int n, const double rho, const double phi, double zn[]) { // sin(nx) = 2 cos(x) sin((n-1)x) - sin((n-2)x) // cos(nx) = 2 cos(x) cos((n-1)x) - cos((n-2)x) - double sin_phi; - double cos_phi; - sin_phi = std::sin(phi); - cos_phi = std::cos(phi); + double sin_phi = std::sin(phi); + double cos_phi = std::cos(phi); double sin_phi_vec[n + 1]; // Sin[n * phi] double cos_phi_vec[n + 1]; // Cos[n * phi] @@ -579,16 +554,12 @@ void calc_zn_c(const int n, const double rho, const double phi, double zn[]) { } // Fill in the rest of the values using the original results (Eq. 3.8 in Chong) - double k1; - double k2; - double k3; - double k4; for (int p = 4; p <= n; p++) { - k2 = static_cast (2 * p * (p - 1) * (p - 2)); + double k2 = 2 * p * (p - 1) * (p - 2); for (int q = p - 4; q >= 0; q -= 2) { - k1 = static_cast((p + q) * (p - q) * (p - 2)) / 2.; - k3 = static_cast(-q * q * (p - 1) - p * (p - 1) * (p - 2)); - k4 = static_cast(-p * (p + q - 2) * (p - q - 2)) / 2.; + double k1 = ((p + q) * (p - q) * (p - 2)) / 2.; + double k3 = -q * q * (p - 1) - p * (p - 1) * (p - 2); + double k4 = (-p * (p + q - 2) * (p - q - 2)) / 2.; zn_mat[q][p] = ((k2 * rho * rho + k3) * zn_mat[q][p-2] + k4 * zn_mat[q][p-4]) / k1; } @@ -600,8 +571,7 @@ void calc_zn_c(const int n, const double rho, const double phi, double zn[]) { // Note that the cos and sin vectors are offset by one // sin_phi_vec = [sin(x), sin(2x), sin(3x) ...] // cos_phi_vec = [1.0, cos(x), cos(2x)... ] - int i; - i = 0; + int i = 0; for (int p = 0; p <= n; p++) { for (int q = -p; q <= p; q += 2) { if (q < 0) { @@ -618,15 +588,11 @@ void calc_zn_c(const int n, const double rho, const double phi, double zn[]) { } -void rotate_angle_c(double uvw[3], const double mu, double* phi) { +void rotate_angle_c(double uvw[3], double mu, double* phi) { // Copy original directional cosines - double u0; // original cosine in x direction - double v0; // original cosine in y direction - double w0; // original cosine in z direction - - u0 = uvw[0]; - v0 = uvw[1]; - w0 = uvw[2]; + double u0 = uvw[0]; // original cosine in x direction + double v0 = uvw[1]; // original cosine in y direction + double w0 = uvw[2]; // original cosine in z direction // Sample azimuthal angle in [0,2pi) if none provided double phi_; @@ -637,14 +603,10 @@ void rotate_angle_c(double uvw[3], const double mu, double* phi) { } // Precompute factors to save flops - double sinphi; // sine of azimuthal angle - double cosphi; // cosine of azimuthal angle - double a; // sqrt(1 - mu^2) - double b; // sqrt(1 - w^2) - sinphi = std::sin(phi_); - cosphi = std::cos(phi_); - a = std::sqrt(std::fmax(0., 1. - mu * mu)); - b = std::sqrt(std::fmax(0., 1. - w0 * w0)); + double sinphi = std::sin(phi_); + double cosphi = std::cos(phi_); + double a = std::sqrt(std::fmax(0., 1. - mu * mu)); + double b = std::sqrt(std::fmax(0., 1. - w0 * w0)); // Need to treat special case where sqrt(1 - w**2) is close to zero by // expanding about the v component rather than the w component @@ -661,54 +623,39 @@ void rotate_angle_c(double uvw[3], const double mu, double* phi) { } -double maxwell_spectrum_c(const double T) { - double r1; - double r2; - double r3; // random numbers - - r1 = prn(); - r2 = prn(); - r3 = prn(); +double maxwell_spectrum_c(double T) { + // Set the random numbers + double r1 = prn(); + double r2 = prn(); + double r3 = prn(); // determine cosine of pi/2*r - double c; - c = std::cos(PI / 2. * r3); + double c = std::cos(PI / 2. * r3); - // determine outgoing energy - double E_out; // Sampled Energy - E_out = -T * (std::log(r1) + std::log(r2) * c * c); + // Determine outgoing energy + double E_out = -T * (std::log(r1) + std::log(r2) * c * c); return E_out; } -double watt_spectrum_c(const double a, const double b) { - double E_out; // Sampled Energy - double w; // sampled from Maxwellian - - w = maxwell_spectrum_c(a); - E_out = w + 0.25 * a * a * b + (2. * prn() - 1.) * std::sqrt(a * a * b * w); +double watt_spectrum_c(double a, double b) { + double w = maxwell_spectrum_c(a); + double E_out = w + 0.25 * a * a * b + (2. * prn() - 1.) * std::sqrt(a * a * b * w); return E_out; } -void broaden_wmp_polynomials_c(const double E, const double dopp, const int n, - double factors[]) { +void broaden_wmp_polynomials_c(double E, double dopp, int n, double factors[]) { // Factors is already pre-allocated + double sqrtE = std::sqrt(E); + double beta = sqrtE * dopp; + double half_inv_dopp2 = 0.5 / (dopp * dopp); + double quarter_inv_dopp4 = half_inv_dopp2 * half_inv_dopp2; - double sqrtE; // sqrt(energy) - double beta; // sqrt(atomic weight ratio * E / kT) - double half_inv_dopp2; // 0.5 / dopp**2 - double quarter_inv_dopp4; // 0.25 / dopp**4 - double erf_beta; // error function of beta - double exp_m_beta2; // exp(-beta**2) - - sqrtE = std::sqrt(E); - beta = sqrtE * dopp; - half_inv_dopp2 = 0.5 / (dopp * dopp); - quarter_inv_dopp4 = half_inv_dopp2 * half_inv_dopp2; - + double erf_beta; // error function of beta + double exp_m_beta2; // exp(-beta**2) if (beta > 6.0) { // Save time, ERF(6) is 1 to machine precision. // beta/sqrtpi*exp(-beta**2) is also approximately 1 machine epsilon. @@ -728,9 +675,8 @@ void broaden_wmp_polynomials_c(const double E, const double dopp, const int n, (beta * SQRT_PI); // Perform recursive broadening of high order components - double ip1_dbl; for (int i = 0; i < n - 3; i++) { - ip1_dbl = static_cast(i + 1); + double ip1_dbl = i + 1; if (i != 0) { factors[i + 3] = -factors[i - 1] * (ip1_dbl - 1.) * ip1_dbl * quarter_inv_dopp4 + factors[i + 1] * diff --git a/src/math_functions.h b/src/math_functions.h index b557157179..18bd026405 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -18,7 +18,7 @@ namespace openmc { // modifying test results extern "C" constexpr double PI {3.1415926535898}; -extern "C" constexpr double SQRT_PI {std::sqrt(PI)}; +extern "C" const double SQRT_PI {std::sqrt(PI)}; //============================================================================== //! Calculate the percentile of the standard normal distribution with a @@ -28,7 +28,7 @@ extern "C" constexpr double SQRT_PI {std::sqrt(PI)}; //! @return The requested percentile //============================================================================== -extern "C" double normal_percentile_c(const double p); +extern "C" double normal_percentile_c(double p); //============================================================================== //! Calculate the percentile of the Student's t distribution with a specified @@ -39,7 +39,7 @@ extern "C" double normal_percentile_c(const double p); //! @return The requested percentile //============================================================================== -extern "C" double t_percentile_c(const double p, const int df); +extern "C" double t_percentile_c(double p, int df); //============================================================================== //! Calculate the n-th order Legendre polynomials at the value of x. @@ -50,7 +50,7 @@ extern "C" double t_percentile_c(const double p, const int df); //! evaluated at x. //============================================================================== -extern "C" void calc_pn_c(const int n, const double x, double pnx[]); +extern "C" void calc_pn_c(int n, double x, double pnx[]); //============================================================================== //! Find the value of f(x) given a set of Legendre coefficients and the value @@ -64,8 +64,7 @@ extern "C" void calc_pn_c(const int n, const double x, double pnx[]); //! evaluated at x //============================================================================== -extern "C" double evaluate_legendre_c(const int n, const double data[], - const double x); +extern "C" double evaluate_legendre_c(int n, const double data[], double x); //============================================================================== //! Calculate the n-th order real spherical harmonics for a given angle (in @@ -77,14 +76,18 @@ extern "C" double evaluate_legendre_c(const int n, const double data[], //! evaluated at uvw. //============================================================================== -extern "C" void calc_rn_c(const int n, const double uvw[3], double rn[]); +extern "C" void calc_rn_c(int n, const double uvw[3], double rn[]); //============================================================================== //! Calculate the n-th order modified Zernike polynomial moment for a given //! angle (rho, theta) location on the unit disk. //! +//! This procedure uses the modified Kintner's method for calculating Zernike +//! polynomials as outlined in Chong, C. W., Raveendran, P., & Mukundan, +//! R. (2003). A comparative analysis of algorithms for fast computation of +//! Zernike moments. Pattern Recognition, 36(3), 731-742. //! The normalization of the polynomials is such that the integral of Z_pq^2 -//! over the unit disk is exactly pi +//! over the unit disk is exactly pi. //! //! @param n The maximum order requested //! @param rho The radial parameter to specify location on the unit disk @@ -93,8 +96,7 @@ extern "C" void calc_rn_c(const int n, const double uvw[3], double rn[]); //! evaluated at rho and phi. //============================================================================== -extern "C" void calc_zn_c(const int n, const double rho, const double phi, - double zn[]); +extern "C" void calc_zn_c(int n, double rho, double phi, double zn[]); //============================================================================== //! Rotate the direction cosines through a polar angle whose cosine is mu and @@ -105,11 +107,11 @@ extern "C" void calc_zn_c(const int n, const double rho, const double phi, //! //! @param uvw[3] The initial, and final, direction vector //! @param mu The cosine of angle in lab or CM -//! @param phi The azimuthal angle; defaults to a randomly chosen angle +//! @param phi The azimuthal angle; will randomly chosen angle if a nullptr +//! is passed //============================================================================== -extern "C" void rotate_angle_c(double uvw[3], const double mu, - double* phi=nullptr); +extern "C" void rotate_angle_c(double uvw[3], double mu, double* phi); //============================================================================== //! Samples an energy from the Maxwell fission distribution based on a direct @@ -123,7 +125,7 @@ extern "C" void rotate_angle_c(double uvw[3], const double mu, //! @result The sampled outgoing energy //============================================================================== -extern "C" double maxwell_spectrum_c(const double T); +extern "C" double maxwell_spectrum_c(double T); //============================================================================== //! Samples an energy from a Watt energy-dependent fission distribution. @@ -138,7 +140,7 @@ extern "C" double maxwell_spectrum_c(const double T); //! @result The sampled outgoing energy //============================================================================== -extern "C" double watt_spectrum_c(const double a, const double b); +extern "C" double watt_spectrum_c(double a, double b); //============================================================================== //! Doppler broadens the windowed multipole curvefit. @@ -151,8 +153,8 @@ extern "C" double watt_spectrum_c(const double a, const double b); //! @param factors The output leading coefficient //============================================================================== -extern "C" void broaden_wmp_polynomials_c(const double E, const double dopp, - const int n, double factors[]); +extern "C" void broaden_wmp_polynomials_c(double E, double dopp, int n, + double factors[]); } // namespace openmc #endif // MATH_FUNCTIONS_H \ No newline at end of file From 890a1b4f55e2964e42f46cb46f8a37b8ee5314fd Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Fri, 18 May 2018 19:07:34 -0400 Subject: [PATCH 19/19] Moved math python api to point to the C++ and simplified the fortran methods --- openmc/capi/math.py | 71 ++++++------- src/api.F90 | 11 -- src/distribution_multivariate.F90 | 2 +- src/math.F90 | 168 ++++-------------------------- src/math_functions.h | 3 + src/mgxs_header.F90 | 5 +- src/physics.F90 | 13 +-- src/physics_mg.F90 | 2 +- src/scattdata_header.F90 | 3 +- 9 files changed, 67 insertions(+), 211 deletions(-) diff --git a/openmc/capi/math.py b/openmc/capi/math.py index 3b0b723b32..d1d7abdf64 100644 --- a/openmc/capi/math.py +++ b/openmc/capi/math.py @@ -6,37 +6,33 @@ from numpy.ctypeslib import ndpointer from . import _dll -_dll.t_percentile.restype = c_double -_dll.t_percentile.argtypes = [POINTER(c_double), POINTER(c_int)] +_dll.t_percentile_c.restype = c_double +_dll.t_percentile_c.argtypes = [c_double, c_int] -_dll.calc_pn.restype = None -_dll.calc_pn.argtypes = [POINTER(c_int), POINTER(c_double), - ndpointer(c_double)] +_dll.calc_pn_c.restype = None +_dll.calc_pn_c.argtypes = [c_int, c_double, ndpointer(c_double)] -_dll.evaluate_legendre.restype = c_double -_dll.evaluate_legendre.argtypes = [POINTER(c_int), POINTER(c_double), - POINTER(c_double)] +_dll.evaluate_legendre_c.restype = c_double +_dll.evaluate_legendre_c.argtypes = [c_int, POINTER(c_double), c_double] -_dll.calc_rn.restype = None -_dll.calc_rn.argtypes = [POINTER(c_int), ndpointer(c_double), - ndpointer(c_double)] +_dll.calc_rn_c.restype = None +_dll.calc_rn_c.argtypes = [c_int, ndpointer(c_double), ndpointer(c_double)] -_dll.calc_zn.restype = None -_dll.calc_zn.argtypes = [POINTER(c_int), POINTER(c_double), POINTER(c_double), - ndpointer(c_double)] +_dll.calc_zn_c.restype = None +_dll.calc_zn_c.argtypes = [c_int, c_double, c_double, ndpointer(c_double)] -_dll.rotate_angle.restype = None -_dll.rotate_angle.argtypes = [ndpointer(c_double), POINTER(c_double), - ndpointer(c_double), POINTER(c_double)] -_dll.maxwell_spectrum.restype = c_double -_dll.maxwell_spectrum.argtypes = [POINTER(c_double)] +_dll.rotate_angle_c.restype = None +_dll.rotate_angle_c.argtypes = [ndpointer(c_double), c_double, + POINTER(c_double)] +_dll.maxwell_spectrum_c.restype = c_double +_dll.maxwell_spectrum_c.argtypes = [c_double] -_dll.watt_spectrum.restype = c_double -_dll.watt_spectrum.argtypes = [POINTER(c_double), POINTER(c_double)] +_dll.watt_spectrum_c.restype = c_double +_dll.watt_spectrum_c.argtypes = [c_double, c_double] -_dll.broaden_wmp_polynomials.restype = None -_dll.broaden_wmp_polynomials.argtypes = [POINTER(c_double), POINTER(c_double), - POINTER(c_int), ndpointer(c_double)] +_dll.broaden_wmp_polynomials_c.restype = None +_dll.broaden_wmp_polynomials_c.argtypes = [c_double, c_double, c_int, + ndpointer(c_double)] def t_percentile(p, df): @@ -57,7 +53,7 @@ def t_percentile(p, df): """ - return _dll.t_percentile(c_double(p), c_int(df)) + return _dll.t_percentile_c(p, df) def calc_pn(n, x): @@ -78,7 +74,7 @@ def calc_pn(n, x): """ pnx = np.empty(n + 1, dtype=np.float64) - _dll.calc_pn(c_int(n), c_double(x), pnx) + _dll.calc_pn_c(n, x, pnx) return pnx @@ -101,9 +97,9 @@ def evaluate_legendre(data, x): """ data_arr = np.array(data, dtype=np.float64) - return _dll.evaluate_legendre(c_int(len(data)), - data_arr.ctypes.data_as(POINTER(c_double)), - c_double(x)) + return _dll.evaluate_legendre_c(len(data), + data_arr.ctypes.data_as(POINTER(c_double)), + x) def calc_rn(n, uvw): @@ -127,7 +123,7 @@ def calc_rn(n, uvw): num_nm = (n + 1) * (n + 1) rn = np.empty(num_nm, dtype=np.float64) uvw_arr = np.array(uvw, dtype=np.float64) - _dll.calc_rn(c_int(n), uvw_arr, rn) + _dll.calc_rn_c(n, uvw_arr, rn) return rn @@ -155,7 +151,7 @@ def calc_zn(n, rho, phi): num_bins = ((n + 1) * (n + 2)) // 2 zn = np.zeros(num_bins, dtype=np.float64) - _dll.calc_zn(c_int(n), c_double(rho), c_double(phi), zn) + _dll.calc_zn_c(n, rho, phi, zn) return zn @@ -179,13 +175,13 @@ def rotate_angle(uvw0, mu, phi=None): """ - uvw = np.zeros(3, dtype=np.float64) uvw0_arr = np.array(uvw0, dtype=np.float64) if phi is None: - _dll.rotate_angle(uvw0_arr, c_double(mu), uvw, None) + _dll.rotate_angle_c(uvw0_arr, mu, None) else: - _dll.rotate_angle(uvw0_arr, c_double(mu), uvw, c_double(phi)) + _dll.rotate_angle_c(uvw0_arr, mu, c_double(phi)) + uvw = uvw0_arr return uvw @@ -206,7 +202,7 @@ def maxwell_spectrum(T): """ - return _dll.maxwell_spectrum(c_double(T)) + return _dll.maxwell_spectrum_c(T) def watt_spectrum(a, b): @@ -226,7 +222,7 @@ def watt_spectrum(a, b): """ - return _dll.watt_spectrum(c_double(a), c_double(b)) + return _dll.watt_spectrum_c(a, b) def broaden_wmp_polynomials(E, dopp, n): @@ -250,6 +246,5 @@ def broaden_wmp_polynomials(E, dopp, n): """ factors = np.zeros(n, dtype=np.float64) - _dll.broaden_wmp_polynomials(c_double(E), c_double(dopp), c_int(n), - factors) + _dll.broaden_wmp_polynomials_c(E, dopp, n, factors) return factors diff --git a/src/api.F90 b/src/api.F90 index a06e2f4cc4..da50007d68 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -95,17 +95,6 @@ module openmc_api public :: openmc_tally_set_nuclides public :: openmc_tally_set_scores public :: openmc_tally_set_type - public :: t_percentile - public :: calc_pn - public :: calc_rn - public :: calc_zn - public :: evaluate_legendre - public :: rotate_angle - public :: maxwell_spectrum - public :: watt_spectrum - public :: faddeeva - public :: w_derivative - public :: broaden_wmp_polynomials contains diff --git a/src/distribution_multivariate.F90 b/src/distribution_multivariate.F90 index 4d7d42cc3d..19db1c297d 100644 --- a/src/distribution_multivariate.F90 +++ b/src/distribution_multivariate.F90 @@ -119,7 +119,7 @@ contains else ! Sample azimuthal angle phi = this % phi % sample() - call rotate_angle(this % reference_uvw, mu, uvw, phi) + uvw = rotate_angle(this % reference_uvw, mu, phi) end if end function polar_azimuthal_sample diff --git a/src/math.F90 b/src/math.F90 index 05942a86e3..33f1ab4d30 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -26,22 +26,22 @@ module math interface - pure function t_percentile_c_intfc(p, df) bind(C, name='t_percentile_c') & + pure function t_percentile(p, df) bind(C, name='t_percentile_c') & result(t) use ISO_C_BINDING implicit none real(C_DOUBLE), value, intent(in) :: p integer(C_INT), value, intent(in) :: df real(C_DOUBLE) :: t - end function t_percentile_c_intfc + end function t_percentile - pure subroutine calc_pn_c_intfc(n, x, pnx) bind(C, name='calc_pn_c') + pure subroutine calc_pn(n, x, pnx) bind(C, name='calc_pn_c') use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: n real(C_DOUBLE), value, intent(in) :: x real(C_DOUBLE), intent(out) :: pnx(n + 1) - end subroutine calc_pn_c_intfc + end subroutine calc_pn pure function evaluate_legendre_c_intfc(n, data, x) & bind(C, name='evaluate_legendre_c') result(val) @@ -53,22 +53,22 @@ module math real(C_DOUBLE) :: val end function evaluate_legendre_c_intfc - pure subroutine calc_rn_c_intfc(n, uvw, rn) bind(C, name='calc_rn_c') + pure subroutine calc_rn(n, uvw, rn) bind(C, name='calc_rn_c') use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: n real(C_DOUBLE), intent(in) :: uvw(3) real(C_DOUBLE), intent(out) :: rn(2 * n + 1) - end subroutine calc_rn_c_intfc + end subroutine calc_rn - pure subroutine calc_zn_c_intfc(n, rho, phi, zn) bind(C, name='calc_zn_c') + pure subroutine calc_zn(n, rho, phi, zn) bind(C, name='calc_zn_c') use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: n real(C_DOUBLE), value, intent(in) :: rho real(C_DOUBLE), value, intent(in) :: phi real(C_DOUBLE), intent(out) :: zn(((n + 1) * (n + 2)) / 2) - end subroutine calc_zn_c_intfc + end subroutine calc_zn subroutine rotate_angle_c_intfc(uvw, mu, phi) bind(C, name='rotate_angle_c') use ISO_C_BINDING @@ -78,24 +78,24 @@ module math real(C_DOUBLE), optional, intent(in) :: phi end subroutine rotate_angle_c_intfc - function maxwell_spectrum_c_intfc(T) bind(C, name='maxwell_spectrum_c') & + function maxwell_spectrum(T) bind(C, name='maxwell_spectrum_c') & result(E_out) use ISO_C_BINDING implicit none real(C_DOUBLE), value, intent(in) :: T real(C_DOUBLE) :: E_out - end function maxwell_spectrum_c_intfc + end function maxwell_spectrum - function watt_spectrum_c_intfc(a, b) bind(C, name='watt_spectrum_c') & + function watt_spectrum(a, b) bind(C, name='watt_spectrum_c') & result(E_out) use ISO_C_BINDING implicit none real(C_DOUBLE), value, intent(in) :: a real(C_DOUBLE), value, intent(in) :: b real(C_DOUBLE) :: E_out - end function watt_spectrum_c_intfc + end function watt_spectrum - subroutine broaden_wmp_polynomials_c_intfc(E, dopp, n, factors) & + subroutine broaden_wmp_polynomials(E, dopp, n, factors) & bind(C, name='broaden_wmp_polynomials_c') use ISO_C_BINDING implicit none @@ -103,7 +103,7 @@ module math real(C_DOUBLE), value, intent(in) :: dopp integer(C_INT), value, intent(in) :: n real(C_DOUBLE), intent(inout) :: factors(n) - end subroutine broaden_wmp_polynomials_c_intfc + end subroutine broaden_wmp_polynomials function faddeeva_w(z, relerr) bind(C, name='Faddeeva_w') result(w) use ISO_C_BINDING @@ -116,52 +116,13 @@ module math contains -!=============================================================================== -! T_PERCENTILE calculates the percentile of the Student's t distribution with a -! specified probability level and number of degrees of freedom -!=============================================================================== - - pure function t_percentile(p, df) result(t) bind(C) - - real(C_DOUBLE), intent(in) :: p ! probability level - integer(C_INT), intent(in) :: df ! degrees of freedom - real(C_DOUBLE) :: t ! corresponding t-value - - t = t_percentile_c_intfc(p, df) - - end function t_percentile - -!=============================================================================== -! CALC_PN calculates the n-th order Legendre polynomial at the value of x. -! Since this function is called repeatedly during the neutron transport process, -! neither n or x is checked to see if they are in the applicable range. -! This is left to the client developer to use where applicable. x is to be in -! the domain of [-1,1], and 0<=n<=5. If x is outside of the range, the return -! value will be outside the expected range; if n is outside the stated range, -! the return value will be 1.0. -!=============================================================================== - - pure subroutine calc_pn(n, x, pnx) bind(C) - - integer(C_INT), intent(in) :: n ! Legendre order requested - real(C_DOUBLE), intent(in) :: x ! Independent variable the Legendre is to - ! be evaluated at; x must be in the - ! domain [-1,1] - real(C_DOUBLE), intent(out) :: pnx(n + 1) ! The Legendre polys of order n - ! evaluated at x - - call calc_pn_c_intfc(n, x, pnx) - - end subroutine calc_pn - !=============================================================================== ! EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients ! and the value of x !=============================================================================== - pure function evaluate_legendre(n, data, x) result(val) bind(C) - integer(C_INT), intent(in) :: n - real(C_DOUBLE), intent(in) :: data(n) + pure function evaluate_legendre(data, x) result(val) bind(C) + real(C_DOUBLE), intent(in) :: data(:) real(C_DOUBLE), intent(in) :: x real(C_DOUBLE) :: val @@ -169,91 +130,23 @@ contains end function evaluate_legendre -!=============================================================================== -! CALC_RN calculates the n-th order real spherical harmonics for a given angle -! (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) -!=============================================================================== - - pure subroutine calc_rn(n, uvw, rn) bind(C) - - integer(C_INT), intent(in) :: n ! Order requested - real(C_DOUBLE), intent(in) :: uvw(3) ! Direction of travel; - ! assumed to be on unit sphere - real(C_DOUBLE), intent(out) :: rn(2*n + 1) ! The resultant R_n(uvw) - - call calc_rn_c_intfc(n, uvw, rn) - - end subroutine calc_rn - -!=============================================================================== -! CALC_ZN calculates the n-th order modified Zernike polynomial moment for a -! given angle (rho, theta) location in the unit disk. The normlization of the -! polynomials is such that the integral of Z_pq*Z_pq over the unit disk is -! exactly pi -!=============================================================================== - - pure subroutine calc_zn(n, rho, phi, zn) bind(C) - integer(C_INT), intent(in) :: n ! Maximum order - real(C_DOUBLE), intent(in) :: rho ! Radial location in the unit disk - real(C_DOUBLE), intent(in) :: phi ! Theta (radians) location in the unit disk - ! The resulting list of coefficients - real(C_DOUBLE), intent(out) :: zn(((n + 1) * (n + 2)) / 2) - - call calc_zn_c_intfc(n, rho, phi, zn) - end subroutine calc_zn - !=============================================================================== ! ROTATE_ANGLE rotates direction cosines through a polar angle whose cosine is ! mu and through an azimuthal angle sampled uniformly. Note that this is done ! with direct sampling rather than rejection as is done in MCNP and SERPENT. !=============================================================================== - subroutine rotate_angle(uvw0, mu, uvw, phi) bind(C) - real(C_DOUBLE), intent(in) :: uvw0(3) ! directional cosine - real(C_DOUBLE), intent(in) :: mu ! cosine of angle in lab or CM - real(C_DOUBLE), intent(out) :: uvw(3) ! rotated directional cosine - real(C_DOUBLE), target, optional :: phi ! azimuthal angle + function rotate_angle(uvw0, mu, phi) result(uvw) + real(C_DOUBLE), intent(in) :: uvw0(3) ! directional cosine + real(C_DOUBLE), intent(in) :: mu ! cosine of angle in lab or CM + real(C_DOUBLE), intent(in), optional :: phi ! azimuthal angle - real(C_DOUBLE), pointer :: phi_ptr + real(C_DOUBLE) :: uvw(3) ! rotated directional cosine uvw = uvw0 call rotate_angle_c_intfc(uvw, mu, phi) - end subroutine rotate_angle - -!=============================================================================== -! MAXWELL_SPECTRUM samples an energy from the Maxwell fission distribution based -! on a direct sampling scheme. The probability distribution function for a -! Maxwellian is given as p(x) = 2/(T*sqrt(pi))*sqrt(x/T)*exp(-x/T). This PDF can -! be sampled using rule C64 in the Monte Carlo Sampler LA-9721-MS. -!=============================================================================== - - function maxwell_spectrum(T) result(E_out) bind(C) - - real(C_DOUBLE), intent(in) :: T ! tabulated function of incoming E - real(C_DOUBLE) :: E_out ! sampled energy - - E_out = maxwell_spectrum_c_intfc(T) - - end function maxwell_spectrum - -!=============================================================================== -! WATT_SPECTRUM samples the outgoing energy from a Watt energy-dependent fission -! spectrum. Although fitted parameters exist for many nuclides, generally the -! continuous tabular distributions (LAW 4) should be used in lieu of the Watt -! spectrum. This direct sampling scheme is an unpublished scheme based on the -! original Watt spectrum derivation (See F. Brown's MC lectures). -!=============================================================================== - - function watt_spectrum(a, b) result(E_out) bind(C) - - real(C_DOUBLE), intent(in) :: a ! Watt parameter a - real(C_DOUBLE), intent(in) :: b ! Watt parameter b - real(C_DOUBLE) :: E_out ! energy of emitted neutron - - E_out = watt_spectrum_c_intfc(a, b) - - end function watt_spectrum + end function rotate_angle !=============================================================================== ! FADDEEVA the Faddeeva function, using Stephen Johnson's implementation @@ -305,21 +198,4 @@ contains end select end function w_derivative -!=============================================================================== -! BROADEN_WMP_POLYNOMIALS Doppler broadens the windowed multipole curvefit. The -! curvefit is a polynomial of the form -! a/E + b/sqrt(E) + c + d sqrt(E) ... -!=============================================================================== - - subroutine broaden_wmp_polynomials(E, dopp, n, factors) bind(C) - real(C_DOUBLE), intent(in) :: E ! Energy to evaluate at - real(C_DOUBLE), intent(in) :: dopp ! sqrt(atomic weight ratio / kT), - ! kT given in eV. - integer(C_INT), intent(in) :: n ! number of components to polynomial - real(C_DOUBLE), intent(out):: factors(n) ! output leading coefficient - - call broaden_wmp_polynomials_c_intfc(E, dopp, n, factors) - - end subroutine broaden_wmp_polynomials - end module math diff --git a/src/math_functions.h b/src/math_functions.h index 18bd026405..68e89251e3 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -1,3 +1,6 @@ +//! \file math_functions.h +//! A collection of elementary math functions. + #ifndef MATH_FUNCTIONS_H #define MATH_FUNCTIONS_H diff --git a/src/mgxs_header.F90 b/src/mgxs_header.F90 index bcdef6c792..5abeccbcfa 100644 --- a/src/mgxs_header.F90 +++ b/src/mgxs_header.F90 @@ -1103,9 +1103,7 @@ contains end if scatt_coeffs(gin) % data(imu, gout) = & - evaluate_legendre( & - size(input_scatt(gin) % data, dim=1), & - input_scatt(gin) % data(:, gout), mu) + evaluate_legendre(input_scatt(gin) % data(:, gout), mu) ! Ensure positivity of distribution if (scatt_coeffs(gin) % data(imu, gout) < ZERO) & @@ -2097,7 +2095,6 @@ 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 diff --git a/src/physics.F90 b/src/physics.F90 index 8183435cb7..b614b81851 100644 --- a/src/physics.F90 +++ b/src/physics.F90 @@ -495,8 +495,7 @@ contains ! Rotate neutron velocity vector to new angle -- note that the speed of the ! neutron in CM does not change in elastic scattering. However, the speed ! will change when we convert back to LAB - call rotate_angle(uvw_cm, mu_cm, v_n) - v_n = vel * v_n + v_n = vel * rotate_angle(uvw_cm, mu_cm) ! Transform back to LAB frame v_n = v_n + v_cm @@ -786,7 +785,7 @@ contains if (abs(mu) > ONE) mu = sign(ONE,mu) ! change direction of particle - call rotate_angle(uvw, mu, uvw) + uvw = rotate_angle(uvw, mu) end subroutine sab_scatter @@ -977,8 +976,7 @@ contains if (abs(mu) < ONE) then ! set and accept target velocity E_t = E_t / awr - call rotate_angle(uvw, mu, v_target) - v_target = sqrt(E_t) * v_target + v_target = sqrt(E_t) * rotate_angle(uvw, mu) exit ARES_REJECT_LOOP end if end do ARES_REJECT_LOOP @@ -1058,8 +1056,7 @@ contains ! Determine velocity vector of target nucleus based on neutron's velocity ! and the sampled angle between them - call rotate_angle(uvw, mu, v_target) - v_target = vt * v_target + v_target = vt * rotate_angle(uvw, mu) end subroutine sample_cxs_target_velocity @@ -1330,7 +1327,7 @@ contains p % mu = mu ! change direction of particle - call rotate_angle(p % coord(1) % uvw, mu, p % coord(1) % uvw) + p % coord(1) % uvw = rotate_angle(p % coord(1) % uvw, mu) ! evaluate yield yield = rxn % products(1) % yield % evaluate(E_in) diff --git a/src/physics_mg.F90 b/src/physics_mg.F90 index 05493af057..eb82085941 100644 --- a/src/physics_mg.F90 +++ b/src/physics_mg.F90 @@ -151,7 +151,7 @@ contains p % E = energy_bin_avg(p % g) ! Convert change in angle (mu) to new direction - call rotate_angle(p % coord(1) % uvw, p % mu, p % coord(1) % uvw) + p % coord(1) % uvw = rotate_angle(p % coord(1) % uvw, p % mu) ! Set event component p % event = EVENT_SCATTER diff --git a/src/scattdata_header.F90 b/src/scattdata_header.F90 index a3108a2df6..511e2a2376 100644 --- a/src/scattdata_header.F90 +++ b/src/scattdata_header.F90 @@ -445,8 +445,7 @@ contains if (gout < this % gmin(gin) .or. gout > this % gmax(gin)) then f = ZERO else - f = evaluate_legendre(size(this % dist(gin) % data, dim=1), & - this % dist(gin) % data(:, gout), mu) + f = evaluate_legendre(this % dist(gin) % data(:, gout), mu) end if end function scattdatalegendre_calc_f