use crate::calc_engine::calculator::calc_vertex::{CalcValue, CalcVertex, Value};
use crate::calc_engine::connector::Connector;
use logisheets_parser::ast;
fn opt_of(v: Value) -> Result<Option<f64>, ast::Error> {
match v {
Value::Number(n) => Ok(Some(n)),
Value::Blank | Value::Text(_) | Value::Boolean(_) => Ok(None),
Value::Error(e) => Err(e),
}
}
fn to_opt_vec(value: CalcValue) -> Result<Vec<Option<f64>>, ast::Error> {
match value {
CalcValue::Scalar(s) => Ok(vec![opt_of(s)?]),
CalcValue::Range(m) => {
let mut out = Vec::new();
for v in m.into_iter() {
out.push(opt_of(v)?);
}
Ok(out)
}
_ => Err(ast::Error::Value),
}
}
fn fetch_pairs<C>(
args: Vec<CalcVertex>,
fetcher: &mut C,
) -> Result<(Vec<f64>, Vec<f64>), ast::Error>
where
C: Connector,
{
if args.len() != 2 {
return Err(ast::Error::Unspecified);
}
let mut iter = args.into_iter();
let a = to_opt_vec(fetcher.get_calc_value(iter.next().unwrap()))?;
let b = to_opt_vec(fetcher.get_calc_value(iter.next().unwrap()))?;
if a.len() != b.len() {
return Err(ast::Error::Na);
}
let mut xs = Vec::new();
let mut ys = Vec::new();
for (x, y) in a.into_iter().zip(b.into_iter()) {
if let (Some(x), Some(y)) = (x, y) {
xs.push(x);
ys.push(y);
}
}
Ok((xs, ys))
}
fn moments(xs: &[f64], ys: &[f64]) -> (f64, f64, f64, f64, f64, f64) {
let n = xs.len() as f64;
let mx = xs.iter().sum::<f64>() / n;
let my = ys.iter().sum::<f64>() / n;
let mut sxx = 0.;
let mut syy = 0.;
let mut sxy = 0.;
for (x, y) in xs.iter().zip(ys.iter()) {
sxx += (x - mx).powi(2);
syy += (y - my).powi(2);
sxy += (x - mx) * (y - my);
}
(n, mx, my, sxx, syy, sxy)
}
macro_rules! pairs_or_return {
($xs:ident, $ys:ident, $args:expr, $fetcher:expr) => {
let ($xs, $ys) = match fetch_pairs($args, $fetcher) {
Ok(p) => p,
Err(e) => return CalcVertex::from_error(e),
};
};
}
pub fn calc_correl<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
C: Connector,
{
pairs_or_return!(xs, ys, args, fetcher);
if xs.is_empty() {
return CalcVertex::from_error(ast::Error::Div0);
}
let (_, _, _, sxx, syy, sxy) = moments(&xs, &ys);
if sxx == 0. || syy == 0. {
return CalcVertex::from_error(ast::Error::Div0);
}
CalcVertex::from_number(sxy / (sxx * syy).sqrt())
}
pub fn calc_rsq<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
C: Connector,
{
pairs_or_return!(xs, ys, args, fetcher);
if xs.is_empty() {
return CalcVertex::from_error(ast::Error::Div0);
}
let (_, _, _, sxx, syy, sxy) = moments(&xs, &ys);
if sxx == 0. || syy == 0. {
return CalcVertex::from_error(ast::Error::Div0);
}
let r = sxy / (sxx * syy).sqrt();
CalcVertex::from_number(r * r)
}
pub fn calc_covar<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
C: Connector,
{
pairs_or_return!(xs, ys, args, fetcher);
if xs.is_empty() {
return CalcVertex::from_error(ast::Error::Div0);
}
let (n, _, _, _, _, sxy) = moments(&xs, &ys);
CalcVertex::from_number(sxy / n)
}
pub fn calc_covar_s<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
C: Connector,
{
pairs_or_return!(xs, ys, args, fetcher);
if xs.len() < 2 {
return CalcVertex::from_error(ast::Error::Div0);
}
let (n, _, _, _, _, sxy) = moments(&xs, &ys);
CalcVertex::from_number(sxy / (n - 1.))
}
pub fn calc_slope<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
C: Connector,
{
pairs_or_return!(ys, xs, args, fetcher); if xs.is_empty() {
return CalcVertex::from_error(ast::Error::Div0);
}
let (_, _, _, sxx, _, sxy) = moments(&xs, &ys);
if sxx == 0. {
return CalcVertex::from_error(ast::Error::Div0);
}
CalcVertex::from_number(sxy / sxx)
}
pub fn calc_intercept<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
C: Connector,
{
pairs_or_return!(ys, xs, args, fetcher);
if xs.is_empty() {
return CalcVertex::from_error(ast::Error::Div0);
}
let (_, mx, my, sxx, _, sxy) = moments(&xs, &ys);
if sxx == 0. {
return CalcVertex::from_error(ast::Error::Div0);
}
let slope = sxy / sxx;
CalcVertex::from_number(my - slope * mx)
}
pub fn calc_forecast<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
C: Connector,
{
assert_or_return!(args.len() == 3, ast::Error::Unspecified);
let mut iter = args.into_iter();
let xv = fetcher.get_calc_value(iter.next().unwrap());
assert_f64_from_calc_value!(x, xv);
let ys_arg = iter.next().unwrap();
let xs_arg = iter.next().unwrap();
let (ys, xs) = match fetch_pairs(vec![ys_arg, xs_arg], fetcher) {
Ok(p) => p,
Err(e) => return CalcVertex::from_error(e),
};
if xs.is_empty() {
return CalcVertex::from_error(ast::Error::Div0);
}
let (_, mx, my, sxx, _, sxy) = moments(&xs, &ys);
if sxx == 0. {
return CalcVertex::from_error(ast::Error::Div0);
}
let slope = sxy / sxx;
CalcVertex::from_number(my - slope * mx + slope * x)
}
fn sum_pairs<C, F>(args: Vec<CalcVertex>, fetcher: &mut C, f: F) -> CalcVertex
where
C: Connector,
F: Fn(f64, f64) -> f64,
{
pairs_or_return!(xs, ys, args, fetcher);
let sum: f64 = xs.iter().zip(ys.iter()).map(|(x, y)| f(*x, *y)).sum();
CalcVertex::from_number(sum)
}
pub fn calc_sumx2my2<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
C: Connector,
{
sum_pairs(args, fetcher, |x, y| x * x - y * y)
}
pub fn calc_sumx2py2<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
C: Connector,
{
sum_pairs(args, fetcher, |x, y| x * x + y * y)
}
pub fn calc_sumxmy2<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
C: Connector,
{
sum_pairs(args, fetcher, |x, y| (x - y).powi(2))
}