You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
168 lines
4.5 KiB
168 lines
4.5 KiB
use crate::payout_curve::Error;
|
|
use ndarray::prelude::*;
|
|
use ndarray::s;
|
|
use std::cmp::Ordering;
|
|
use std::f64::consts::PI;
|
|
|
|
pub fn bisect_left(arr: &Array1<f64>, val: &f64, mut hi: usize) -> usize {
|
|
let mut lo: usize = 0;
|
|
while lo < hi {
|
|
let mid = (lo + hi) / 2;
|
|
if arr[mid] < *val {
|
|
lo = mid + 1;
|
|
} else {
|
|
hi = mid;
|
|
}
|
|
}
|
|
|
|
lo
|
|
}
|
|
|
|
pub fn bisect_right(arr: &Array1<f64>, val: &f64, mut hi: usize) -> usize {
|
|
let mut lo: usize = 0;
|
|
while lo < hi {
|
|
let mid = (lo + hi) / 2;
|
|
if *val < arr[mid] {
|
|
hi = mid;
|
|
} else {
|
|
lo = mid + 1;
|
|
}
|
|
}
|
|
|
|
lo
|
|
}
|
|
|
|
pub fn cmp_f64(a: &f64, b: &f64) -> Ordering {
|
|
if a < b {
|
|
return Ordering::Less;
|
|
} else if a > b {
|
|
return Ordering::Greater;
|
|
}
|
|
Ordering::Equal
|
|
}
|
|
|
|
/// Gauss-Legendre_Quadrature
|
|
///
|
|
/// Could not find a rust implementation of this, so have created one from
|
|
/// a C implementation found
|
|
/// [here](https://rosettacode.org/wiki/Numerical_integration/Gauss-Legendre_Quadrature#C).
|
|
///
|
|
/// The code is well short of optimal, but it gets things moving. Better
|
|
/// versions are provided by, for example,
|
|
/// [numpy.polynomial.legendre.leggauss](https://github.com/numpy/numpy/blob/v1.21.0/numpy/polynomial/legendre.py#L1519-L1584)
|
|
/// but the implementaitons are more involved so we have opted for quick and
|
|
/// dirty for the time being.
|
|
#[derive(Debug, Clone)]
|
|
pub struct GaussLegendreQuadrature {
|
|
pub sample_points: Array1<f64>,
|
|
pub weights: Array1<f64>,
|
|
}
|
|
|
|
impl GaussLegendreQuadrature {
|
|
pub fn new(order: usize) -> Result<Self, Error> {
|
|
if order < 1 {
|
|
return Result::Err(Error::DegreeMustBePositive);
|
|
}
|
|
|
|
let data = legendre_wrapper(&order);
|
|
|
|
Ok(GaussLegendreQuadrature {
|
|
sample_points: data.0,
|
|
weights: data.1,
|
|
})
|
|
}
|
|
}
|
|
|
|
fn legendre_wrapper(order: &usize) -> (Array1<f64>, Array1<f64>) {
|
|
let arr = legendre_coefficients(order);
|
|
legendre_roots(&arr)
|
|
}
|
|
|
|
fn legendre_coefficients(order: &usize) -> Array2<f64> {
|
|
let mut lcoef_arr = Array2::<f64>::zeros((*order + 1, *order + 1));
|
|
lcoef_arr[[0, 0]] = 1.;
|
|
lcoef_arr[[1, 1]] = 1.;
|
|
|
|
for n in 2..*order + 1 {
|
|
let n_64 = n as f64;
|
|
lcoef_arr[[n, 0]] = -(n_64 - 1.) * lcoef_arr[[n - 2, 0]] / n_64;
|
|
|
|
for i in 1..n + 1 {
|
|
lcoef_arr[[n, i]] = ((2. * n_64 - 1.) * lcoef_arr[[n - 1, i - 1]]
|
|
- (n_64 - 1.) * lcoef_arr[[n - 2, i]])
|
|
/ n_64;
|
|
}
|
|
}
|
|
|
|
lcoef_arr
|
|
}
|
|
|
|
fn legendre_eval(coeff_arr: &Array2<f64>, n: &usize, x: &f64) -> f64 {
|
|
let mut s = coeff_arr[[*n, *n]];
|
|
for i in (1..*n + 1).rev() {
|
|
s = s * (*x) + coeff_arr[[*n, i - 1]];
|
|
}
|
|
|
|
s
|
|
}
|
|
|
|
fn legendre_diff(coeff_arr: &Array2<f64>, n: &usize, x: &f64) -> f64 {
|
|
let n_64 = *n as f64;
|
|
n_64 * (x * legendre_eval(coeff_arr, n, x) - legendre_eval(coeff_arr, &(n - 1), x))
|
|
/ (x * x - 1.)
|
|
}
|
|
|
|
fn legendre_roots(coeff_arr: &Array2<f64>) -> (Array1<f64>, Array1<f64>) {
|
|
let n = coeff_arr.shape()[0] - 1;
|
|
let n_64 = n as f64;
|
|
|
|
let mut sample_points_arr = Array1::<f64>::zeros(n + 1);
|
|
let mut weights_arr = Array1::<f64>::zeros(n + 1);
|
|
|
|
for i in 1..n + 1 {
|
|
let i_64 = i as f64;
|
|
let mut x = (PI * (i_64 - 0.25) / (n_64 + 0.5)).cos();
|
|
let mut x1 = x;
|
|
x -= legendre_eval(coeff_arr, &n, &x) / legendre_diff(coeff_arr, &n, &x);
|
|
|
|
while fdim(&x, &x1) > 2e-16 {
|
|
x1 = x;
|
|
x -= legendre_eval(coeff_arr, &n, &x) / legendre_diff(coeff_arr, &n, &x);
|
|
}
|
|
|
|
sample_points_arr[i - 1] = x;
|
|
x1 = legendre_diff(coeff_arr, &n, &x);
|
|
weights_arr[i - 1] = 2. / ((1. - x * x) * x1 * x1);
|
|
}
|
|
|
|
// truncate the dummy value off the end + reverse sample points +
|
|
// use symmetry to stable things up a bit.
|
|
let mut samples = sample_points_arr.slice(s![..n; -1]).to_owned();
|
|
samples = symmetric_samples(&samples);
|
|
|
|
let mut weights = weights_arr.slice(s![..n]).to_owned();
|
|
weights = symmetric_weights(&weights);
|
|
|
|
(samples, weights)
|
|
}
|
|
|
|
fn symmetric_samples(arr: &Array1<f64>) -> Array1<f64> {
|
|
let arr_rev = arr.slice(s![..; -1]).to_owned();
|
|
(arr - &arr_rev) / 2.
|
|
}
|
|
|
|
fn symmetric_weights(arr: &Array1<f64>) -> Array1<f64> {
|
|
let s = &arr.sum_axis(Axis(0));
|
|
let arr_rev = arr.slice(s![..; -1]).to_owned();
|
|
(arr + &arr_rev) / s
|
|
}
|
|
|
|
fn fdim(a: &f64, b: &f64) -> f64 {
|
|
let res;
|
|
if a - b > 0f64 {
|
|
res = a - b;
|
|
} else {
|
|
res = 0f64;
|
|
}
|
|
res
|
|
}
|
|
|