Skip to content

Commit

Permalink
fix #3 add complexity rank for relative comparison (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
maksym-arutyunyan authored Jul 4, 2022
1 parent 075267c commit 7101503
Show file tree
Hide file tree
Showing 9 changed files with 393 additions and 89 deletions.
8 changes: 5 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
[package]
name = "big_o"
description = "Infers asymptotic computational complexity"
version = "0.1.1"
version = "0.1.2"
edition = "2021"
authors = ["Maksym Arutyunyan"]
repository = "https://github.com/maksym-arutyunyan/big_o"

homepage = "https://github.com/maksym-arutyunyan/big_o"
documentation = "https://docs.rs/big-o/"
readme = "README.md"
license = "Apache-2.0"
keywords = ["big_o", "asymptotic", "complexity"]
keywords = ["big_o", "big-o", "asymptotic", "complexity", "analysis", "performance", "algorithm"]
categories = ["development-tools", "development-tools::profiling", "development-tools::testing"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ assert_eq!(complexity.name, big_o::Name::Quadratic);
assert_eq!(complexity.notation, "O(n^2)");
assert_approx_eq!(complexity.params.gain.unwrap(), 1.0, 1e-6);
assert_approx_eq!(complexity.params.offset.unwrap(), 0.0, 1e-6);
assert!(complexity.rank < big_o::complexity("O(n^3)").unwrap().rank);
```
6 changes: 3 additions & 3 deletions examples/stress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ fn main() {
.map(|i| i as f64)
.map(|x| (x, f(x)))
.collect();
let (best, _all) = big_o::infer_complexity(data).unwrap();
let (complexity, _all) = big_o::infer_complexity(data).unwrap();

let (a, b) = match name {
big_o::Name::Constant => (0.0, offset),
Expand All @@ -69,9 +69,9 @@ fn main() {
_ => (gain, offset),
};

let is_ok = name == best.name;
let is_ok = name == complexity.name;
if !is_ok {
let line = format!("{},{:?},{},{},{:?}", is_ok, name, a, b, best.name);
let line = format!("{},{:?},{},{},{:?}", is_ok, name, a, b, complexity.name);
if let Err(e) = writeln!(file, "{}", line) {
eprintln!("Couldn't write to file: {}", e);
}
Expand Down
147 changes: 146 additions & 1 deletion src/complexity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ pub struct Complexity {

/// Approximation function parameters
pub params: Params,

/// Relative rank to compare complexities
pub rank: u32,
}

impl Complexity {
Expand Down Expand Up @@ -49,6 +52,37 @@ impl Complexity {
}
}

pub struct ComplexityBuilder {
name: Name,
params: Option<Params>,
}

impl ComplexityBuilder {
pub fn new(name: Name) -> Self {
Self { name, params: None }
}

#[allow(dead_code)] // Used in tests.
pub fn power(&mut self, x: f64) -> &mut Self {
self.params = Some(Params::new().power(x).build());
self
}

pub fn build(&self) -> Complexity {
let mut params = Params::new().build();
if let Some(p) = &self.params {
params = p.clone();
}
let rank = rank(self.name, params.clone());
Complexity {
name: self.name,
notation: name::notation(self.name),
params,
rank,
}
}
}

/// Transforms input data into linear complexity.
fn linearize(name: Name, x: f64, y: f64) -> (f64, f64) {
match name {
Expand Down Expand Up @@ -79,6 +113,35 @@ fn delinearize(name: Name, gain: f64, offset: f64, residuals: f64) -> Params {
}
}

fn rank(name: Name, params: Params) -> u32 {
// Rank is similar to a degree of a corresponding polynomial:
// - constant: 0, f(x) = x ^ 0.000
// - logarithmic: 130, empirical value k for a big x in f(x) = x ^ k
// base 1_000_000 log of 6 is 0.130
// approx. f(x) = x ^ 0.130
// - linear: 1_000, f(x) = x ^ 1.000
// - linearithmic: 1_130, approx. f(x) = x ^ 1.130
// - quadratic: 2_000, f(x) = x ^ 2.000
// - cubic: 3_000, f(x) = x ^ 3.000
// - polynomial: depends on polynomial degree
// - exponential: 1_000_000, practically there is no sense in polynomial degree > 1_000.000
match name {
Name::Constant => 0,
Name::Logarithmic => 130,
Name::Linear => 1_000,
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::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
Expand All @@ -87,10 +150,92 @@ pub fn fit(name: Name, data: Vec<(f64, f64)>) -> Result<Complexity, &'static str
.collect();

let (gain, offset, residuals) = linalg::fit_line(linearized)?;
let params = delinearize(name, gain, offset, residuals);
let rank = rank(name, params.clone());

Ok(Complexity {
name,
notation: name::notation(name),
params: delinearize(name, gain, offset, residuals),
params,
rank,
})
}

/// Creates `Complexity` from string.
///
/// # Example
/// ```
/// use big_o::{complexity, Name::*};
///
/// let linear = complexity("O(n)").unwrap();
/// assert_eq!(linear.name, Linear);
///
/// let cubic = complexity("O(n^3)").unwrap();
/// assert_eq!(cubic.name, Cubic);
///
/// assert!(linear.rank < cubic.rank);
/// ```
pub fn complexity(string: &str) -> Result<Complexity, &'static str> {
let name: Name = string.try_into()?;
Ok(crate::complexity::ComplexityBuilder::new(name).build())
}

#[cfg(test)]
mod tests {
use super::*;

fn constant() -> Complexity {
ComplexityBuilder::new(Name::Constant).build()
}

fn logarithmic() -> Complexity {
ComplexityBuilder::new(Name::Logarithmic).build()
}

fn linear() -> Complexity {
ComplexityBuilder::new(Name::Linear).build()
}

fn linearithmic() -> Complexity {
ComplexityBuilder::new(Name::Linearithmic).build()
}

fn quadratic() -> Complexity {
ComplexityBuilder::new(Name::Quadratic).build()
}

fn cubic() -> Complexity {
ComplexityBuilder::new(Name::Cubic).build()
}

fn exponential() -> Complexity {
ComplexityBuilder::new(Name::Exponential).build()
}

fn polynomial(power: f64) -> Complexity {
ComplexityBuilder::new(Name::Polynomial)
.power(power)
.build()
}

#[test]
fn test_complecity_rank() {
// O(1) < ... < O(n)
assert!(constant().rank < logarithmic().rank);
assert!(logarithmic().rank < polynomial(0.5).rank);
assert!(polynomial(0.5).rank < linear().rank);

// O(n) < ... < O(n^2)
assert!(linear().rank < linearithmic().rank);
assert!(linearithmic().rank < polynomial(1.5).rank);
assert!(polynomial(1.5).rank < quadratic().rank);

// O(n^2) < ... < O(n^3)
assert!(quadratic().rank < polynomial(2.5).rank);
assert!(polynomial(2.5).rank < cubic().rank);

// O(n^3) < ... < O(c^n)
assert!(cubic().rank < polynomial(3.5).rank);
assert!(polynomial(3.5).rank < exponential().rank);
}
}
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
//! assert_eq!(complexity.notation, "O(n^2)");
//! assert_approx_eq!(complexity.params.gain.unwrap(), 1.0, 1e-6);
//! assert_approx_eq!(complexity.params.offset.unwrap(), 0.0, 1e-6);
//! assert!(complexity.rank < big_o::complexity("O(n^3)").unwrap().rank);
//! ```
mod complexity;
Expand All @@ -24,6 +25,7 @@ mod name;
mod params;
mod validate;

pub use crate::complexity::complexity;
pub use crate::complexity::Complexity;
pub use crate::name::Name;
pub use crate::params::Params;
Expand All @@ -42,6 +44,7 @@ pub use crate::params::Params;
/// assert_eq!(complexity.notation, "O(n^2)");
/// assert_approx_eq!(complexity.params.gain.unwrap(), 1.0, 1e-6);
/// assert_approx_eq!(complexity.params.offset.unwrap(), 0.0, 1e-6);
/// assert!(complexity.rank < big_o::complexity("O(n^3)").unwrap().rank);
/// ```
pub fn infer_complexity(
data: Vec<(f64, f64)>,
Expand Down
Loading

0 comments on commit 7101503

Please sign in to comment.