Skip to content

Commit

Permalink
recalculate delinearized residuals (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
maksym-arutyunyan authored Sep 20, 2022
1 parent 619318b commit 5fb288d
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 43 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "big_o"
description = "Infers asymptotic computational complexity"
version = "0.1.2"
version = "0.1.3"
edition = "2021"
authors = ["Maksym Arutyunyan"]
repository = "https://github.com/maksym-arutyunyan/big_o"
Expand Down
98 changes: 56 additions & 42 deletions src/complexity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,37 +19,35 @@ pub struct Complexity {
pub rank: u32,
}

impl Complexity {
/// Returns a calculated approximation function `f(x)`
pub fn get_function(&self) -> Result<Box<dyn Fn(f64) -> f64>, &'static str> {
let p = &self.params;
if let (Some(a), Some(b)) = match self.name {
Name::Polynomial => (p.gain, p.power),
Name::Exponential => (p.gain, p.base),
_other => (p.gain, p.offset),
} {
let f: Box<dyn Fn(f64) -> f64> = match self.name {
Name::Constant => Box::new(move |_x| b),
Name::Logarithmic => Box::new(move |x| a * x.ln() + b),
Name::Linear => Box::new(move |x| a * x + b),
Name::Linearithmic => Box::new(move |x| a * x * x.ln() + b),
Name::Quadratic => Box::new(move |x| a * x.powi(2) + b),
Name::Cubic => Box::new(move |x| a * x.powi(3) + b),
Name::Polynomial => Box::new(move |x| a * x.powf(b)),
Name::Exponential => Box::new(move |x| a * b.powf(x)),
};
Ok(f)
} else {
Err("No cofficients to compute f(x)")
}
/// Returns a calculated approximation function `f(x)`
fn get_function(name: Name, params: Params) -> Result<Box<dyn Fn(f64) -> f64>, &'static str> {
if let (Some(a), Some(b)) = match name {
Name::Polynomial => (params.gain, params.power),
Name::Exponential => (params.gain, params.base),
_other => (params.gain, params.offset),
} {
let f: Box<dyn Fn(f64) -> f64> = match name {
Name::Constant => Box::new(move |_x| b),
Name::Logarithmic => Box::new(move |x| a * x.ln() + b),
Name::Linear => Box::new(move |x| a * x + b),
Name::Linearithmic => Box::new(move |x| a * x * x.ln() + b),
Name::Quadratic => Box::new(move |x| a * x.powi(2) + b),
Name::Cubic => Box::new(move |x| a * x.powi(3) + b),
Name::Polynomial => Box::new(move |x| a * x.powf(b)),
Name::Exponential => Box::new(move |x| a * b.powf(x)),
};
Ok(f)
} else {
Err("No cofficients to compute f(x)")
}
}

/// Computes values of `f(x)` given `x`
pub fn compute_f(&self, x: Vec<f64>) -> Result<Vec<f64>, &'static str> {
let f = self.get_function()?;
let y = x.into_iter().map(f).collect();
Ok(y)
}
/// Computes values of `f(x)` given `x`
#[allow(dead_code)]
fn compute_f(name: Name, params: Params, x: Vec<f64>) -> Result<Vec<f64>, &'static str> {
let f = get_function(name, params)?;
let y = x.into_iter().map(f).collect();
Ok(y)
}

pub struct ComplexityBuilder {
Expand All @@ -62,7 +60,7 @@ impl ComplexityBuilder {
Self { name, params: None }
}

#[allow(dead_code)] // Used in tests.
#[allow(dead_code)] // Used in tests.
pub fn power(&mut self, x: f64) -> &mut Self {
self.params = Some(Params::new().power(x).build());
self
Expand Down Expand Up @@ -98,7 +96,7 @@ fn linearize(name: Name, x: f64, y: f64) -> (f64, f64) {
}

/// Converts linear coeffs `gain` and `offset` to corresponding complexity params.
fn delinearize(name: Name, gain: f64, offset: f64, residuals: f64) -> Params {
fn delinearize(name: Name, gain: f64, offset: f64) -> Params {
// Delinearize coeffs.
let (a, b) = match name {
Name::Polynomial => (offset.exp(), gain),
Expand All @@ -107,12 +105,23 @@ fn delinearize(name: Name, gain: f64, offset: f64, residuals: f64) -> Params {
};
// Convert coeffs to params.
match name {
Name::Polynomial => Params::new().gain(a).power(b).residuals(residuals).build(),
Name::Exponential => Params::new().gain(a).base(b).residuals(residuals).build(),
_other => Params::new().gain(a).offset(b).residuals(residuals).build(),
Name::Polynomial => Params::new().gain(a).power(b).build(),
Name::Exponential => Params::new().gain(a).base(b).build(),
_other => Params::new().gain(a).offset(b).build(),
}
}

fn calculate_residuals(
name: Name,
params: Params,
data: Vec<(f64, f64)>,
) -> Result<f64, &'static str> {
let f = get_function(name, params)?;
let residuals = data.into_iter().map(|(x, y)| (y - f(x)).abs()).sum();

Ok(residuals)
}

fn rank(name: Name, params: Params) -> u32 {
// Rank is similar to a degree of a corresponding polynomial:
// - constant: 0, f(x) = x ^ 0.000
Expand All @@ -132,25 +141,30 @@ fn rank(name: Name, params: Params) -> u32 {
Name::Linearithmic => 1_130,
Name::Quadratic => 2_000,
Name::Cubic => 3_000,
Name::Polynomial => {
match params.power {
Some(power) => std::cmp::min((1_000.0 * power) as u32, 1_000_000),
None => panic!("Polynomial is missing its power parameter"),
}
}
Name::Polynomial => match params.power {
Some(power) => std::cmp::min((1_000.0 * power) as u32, 1_000_000),
None => panic!("Polynomial is missing its power parameter"),
},
Name::Exponential => 1_000_000,
}
}

/// Fits a function of given complexity into input data.
pub fn fit(name: Name, data: Vec<(f64, f64)>) -> Result<Complexity, &'static str> {
let linearized = data
.clone()
.into_iter()
.map(|(x, y)| linearize(name, x, y))
.collect();

let (gain, offset, residuals) = linalg::fit_line(linearized)?;
let params = delinearize(name, gain, offset, residuals);
let (gain, offset, _residuals) = linalg::fit_line(linearized)?;
let params = delinearize(name, gain, offset);
// Calculate delinearized residuals.
let residuals = calculate_residuals(name, params.clone(), data)?;
let params = Params {
residuals: Some(residuals),
..params
};
let rank = rank(name, params.clone());

Ok(Complexity {
Expand Down
60 changes: 60 additions & 0 deletions tests/regressions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#[test]
fn regression_0001() {
let data: Vec<(f64, f64)> = vec![
(1.0, 45385.0),
(2.0, 90769.0),
(3.0, 136155.0),
(4.0, 181539.0),
(5.0, 226925.0),
(6.0, 272309.0),
(7.0, 317695.0),
(8.0, 363079.0),
(9.0, 408465.0),
(10.0, 453849.0),
(11.0, 499235.0),
(12.0, 544619.0),
(13.0, 590004.0),
(14.0, 635389.0),
(15.0, 680773.0),
(16.0, 726159.0),
(17.0, 771543.0),
(18.0, 816929.0),
(19.0, 862313.0),
(20.0, 907699.0),
(21.0, 953083.0),
(22.0, 998469.0),
(23.0, 1043853.0),
(24.0, 1089239.0),
(25.0, 1134623.0),
(26.0, 1180008.0),
(27.0, 1225393.0),
(28.0, 1270777.0),
(29.0, 1316163.0),
(30.0, 1361547.0),
(31.0, 1406933.0),
(32.0, 1452317.0),
(33.0, 1497703.0),
(34.0, 1543087.0),
(35.0, 1588473.0),
(36.0, 1633857.0),
(37.0, 1679243.0),
(38.0, 1724627.0),
(39.0, 1770012.0),
(40.0, 1815397.0),
(41.0, 1860781.0),
(42.0, 1906167.0),
(43.0, 1951551.0),
(44.0, 1996937.0),
(45.0, 2042321.0),
(46.0, 2087707.0),
(47.0, 2133091.0),
(48.0, 2178477.0),
(49.0, 2223861.0),
];
let (complexity, all) = big_o::infer_complexity(data).unwrap();
assert_eq!(
complexity.name,
big_o::Name::Linear,
"\n\ncomplexity={complexity:?}, \n\nall={all:?}"
);
}

0 comments on commit 5fb288d

Please sign in to comment.