use std::collections::HashMap;
use tracing::trace;
use crate::datatype::Point;
use crate::error::{Error, Result};
use crate::io::Dataset;
#[allow(dead_code)]
pub(crate) fn bilinear(points: &Vec<(f32, f32, f32)>, target: &(f32, f32)) -> Result<f32> {
if points.len() != 4 {
return Err(Error::InvalidArgument);
}
for point in points {
if target.0 == point.0 && target.1 == point.1 {
return Ok(point.2);
}
}
let a = points[0];
let b = points[1];
let c = points[2];
let d = points[3];
let bt = (b.0 - a.0, b.1 - a.1, b.2);
let dt = (d.0 - a.0, d.1 - a.1, d.2);
let tt = (target.0 - a.0, target.1 - a.1);
let det_bd = (bt.0 * dt.1) - (dt.0 * bt.1);
if det_bd == 0.0 {
return Err(Error::InvalidArgument);
}
let cbm = vec![
vec![dt.1 / det_bd, -(dt.0 / det_bd)],
vec![-(bt.1 / det_bd), bt.0 / det_bd],
];
let x = cbm[0][0] * tt.0 + cbm[0][1] * tt.1;
let y = cbm[1][0] * tt.0 + cbm[1][1] * tt.1;
let a00 = a.2;
let a10 = b.2 - a.2; let a01 = d.2 - a.2; let a11 = c.2 - a.2 - a10 - a01;
Ok(a00 + a10 * x + a01 * y + a11 * x * y)
}
#[test]
fn test_interp() {
let check_interp = [(
20.0,
23.0,
-77.0,
-19.0,
123.0,
145.0,
10.0,
20.0,
30.0,
40.0,
1230.0,
19.971951219512192,
)];
for (x, y, x1, y1, x2, y2, q11, q21, q12, q22, t, val) in check_interp {
let points = vec![
(x1 + t, y1 + t, q11),
(x1 + t, y2 + t, q12),
(x2 + t, y2 + t, q22),
(x2 + t, y1 + t, q21),
];
let target = (x + t, y + t);
let ans = bilinear(&points, &target).unwrap();
assert!(
(ans - val).abs() < f32::EPSILON,
"expected: {}. actual value: {}",
val,
ans
);
}
}
#[test]
fn test_edges() {
let check_interp = [
(
0.0, 0.0, 0.0, 0.0, 10.0, 10.0, 0.0, 5.0, 10.0, 15.0, 0.0, 0.0,
),
(
10.0, 0.0, 0.0, 0.0, 10.0, 10.0, 0.0, 5.0, 10.0, 15.0, 0.0, 5.0,
),
(
0.0, 10.0, 0.0, 0.0, 10.0, 10.0, 0.0, 5.0, 10.0, 15.0, 0.0, 10.0,
),
(
10.0, 10.0, 0.0, 0.0, 10.0, 10.0, 0.0, 5.0, 10.0, 15.0, 0.0, 15.0,
),
];
for (x, y, x1, y1, x2, y2, q11, q21, q12, q22, t, val) in check_interp {
let points = vec![
(x1 + t, y1 + t, q11),
(x1 + t, y2 + t, q12),
(x2 + t, y2 + t, q22),
(x2 + t, y1 + t, q21),
];
let target = (x + t, y + t);
let ans = bilinear(&points, &target).unwrap();
assert!(
(ans - val).abs() < f32::EPSILON,
"expected: {}. actual value: {}",
val,
ans
);
}
}
#[derive(Debug)]
struct LinearFit<T> {
slope: T,
intercept: T,
}
impl<T> LinearFit<T>
where
T: Copy,
T: std::ops::Sub<Output = T>,
T: std::ops::Mul<Output = T>,
{
#[allow(dead_code)]
fn predict(&self, x: T) -> T {
(x - self.intercept) * self.slope
}
}
const LINEAR_RELATION_TOLERANCE: f64 = 0.005;
impl LinearFit<f64> {
fn from_fit(x: ndarray::ArrayD<f64>) -> Result<LinearFit<f64>> {
let dx = &x.slice(ndarray::s![1..]) - &x.slice(ndarray::s![..-1]);
let slope = dx.mean().expect("Failed to calculate mean");
let criteria = ((dx - slope) / slope)
.abs()
.into_iter()
.map(|v| v > LINEAR_RELATION_TOLERANCE)
.any(|v| v);
if criteria {
return Err(Error::Undefined(
"Linear is a bad approximation".to_string(),
));
}
Ok(LinearFit {
slope,
intercept: x[0],
})
}
}
struct RegularGrid<'a> {
dataset: Box<dyn Dataset + 'a>,
x_size: usize,
x_map: LinearFit<f64>,
y_size: usize,
y_map: LinearFit<f64>,
dimension_order: HashMap<String, String>,
}
impl<'a> RegularGrid<'a> {
#[allow(dead_code)]
fn open(dataset: impl Dataset + 'a, varname_x: &str, varname_y: &str) -> Result<Self> {
let dimension_order = dataset.dimensions_order(varname_x, varname_y);
let x_size = dataset.dimension_len(varname_x).unwrap();
let x_map = LinearFit::from_fit(dataset.values(varname_x).unwrap())?;
let y_size = dataset.dimension_len(varname_y).unwrap();
let y_map = LinearFit::from_fit(dataset.values(varname_x).unwrap())?;
Ok(Self {
dataset: Box::new(dataset),
x_size,
x_map,
y_size,
y_map,
dimension_order,
})
}
#[allow(dead_code)]
fn nearest(&self, varname: &str, point: Point<f64>) -> Result<f32> {
let i = self.x_map.predict(*point.x()).round() as usize;
if i >= self.x_size {
return Err(Error::IndexOutOfBounds);
}
let j = self.y_map.predict(*point.y()).round() as usize;
if j >= self.y_size {
return Err(Error::IndexOutOfBounds);
}
match self.dimension_order.get(varname) {
Some(v) => match v.as_str() {
"xy" => {
trace!("Assuming dimension order is 'xy'");
self.dataset.get_variable(varname, i, j)
}
"yx" => {
trace!("Assuming dimension order is 'yx'");
self.dataset.get_variable(varname, j, i)
}
_ => return Err(Error::Undefined("Dimension order not found".to_string())),
},
_ => return Err(Error::Undefined("Variable not found".to_string())),
}
}
}