diff --git a/Cargo.toml b/Cargo.toml index 9e14f14..c624dcf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iterative_methods" -version = "0.2.0" +version = "0.2.1" authors = ["Daniel Vainsencher ", "Daniel Fox "] edition = "2018" description = "Iterative methods and associated utilities as StreamingIterators." diff --git a/examples/derivative_descent.rs b/examples/derivative_descent.rs new file mode 100644 index 0000000..e213e99 --- /dev/null +++ b/examples/derivative_descent.rs @@ -0,0 +1,39 @@ +//! Example from top level crate documentation +use iterative_methods::derivative_descent::*; +use iterative_methods::*; +use streaming_iterator::*; + +fn main() { + // Problem: minimize the convex parabola f(x) = x^2 + x + let function = |x| x * x + x; + + // An iterative solution by gradient descent + let derivative = |x| 2.0 * x + 1.0; + let step_size = 0.2; + let x_0 = 2.0; + + // Au naturale: + let mut x = x_0; + for i in 0..10 { + x -= step_size * derivative(x); + println!("x_{} = {:.2}; f(x_{}) = {:.4}", i, x, i, x * x + x); + } + + // Using replaceable components: + let dd = DerivativeDescent::new(function, derivative, step_size, x_0); + let dd = enumerate(dd); + let mut dd = dd.take(10); + while let Some(&Numbered { + item: Some(ref curr), + count, + }) = dd.next() + { + println!( + "x_{} = {:.2}; f(x_{}) = {:.4}", + count, + curr.x, + count, + curr.value() + ); + } +} diff --git a/examples/fibonacci.rs b/examples/fibonacci.rs deleted file mode 100644 index a6e9dce..0000000 --- a/examples/fibonacci.rs +++ /dev/null @@ -1,39 +0,0 @@ -/// State of Fibonacci iterator. -struct FibonacciIterable { - s0: T, - s1: T, -} - -impl FibonacciIterable { - fn start(first: f64, second: f64) -> FibonacciIterable { - FibonacciIterable:: { - s0: first, - s1: second, - } - } -} - -impl Iterator for FibonacciIterable { - type Item = f64; - fn next(&mut self) -> Option { - let out = self.s0; - self.s0 = self.s1; - self.s1 = self.s0 + out; - Some(out) - } -} - -/// Demonstrate usage of fibonacci sequence as an Iterator. -fn fib_demo() { - let fib = FibonacciIterable::start(0.0, 1.0); - - // enumerate is a simple iterator adaptor annotating the results - // with their place in the sequence. - for (i, n) in fib.enumerate().take(10) { - println!("fib {} is {}", i, n) - } -} - -fn main() { - fib_demo(); -} diff --git a/examples/weighting_samples.rs b/examples/weighting_samples.rs index ee53b5f..a85323b 100644 --- a/examples/weighting_samples.rs +++ b/examples/weighting_samples.rs @@ -4,11 +4,11 @@ use streaming_iterator::*; use utils::*; fn wd_iterable_counter_demo() { - println!("\n\n -----WdIterable Counter Demo----- \n\n"); + println!("\n\n -----Weight Counter Demo----- \n\n"); let counter_stream = Counter::new(); let mut counter_stream_copy = counter_stream.clone(); - let mut wd_iter = WdIterable { + let mut wd_iter = Weight { it: counter_stream, f: expose_w, wd: Some(new_datum(0., 0.)), diff --git a/src/algorithms.rs b/src/algorithms.rs index 9b4b07d..6dc8768 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -26,7 +26,8 @@ mod tests { pub fn solve_approximately(p: LinearSystem) -> V { let solution = ConjugateGradient::for_problem(&p).take(20); - last(solution.map(|s| s.x_k.clone())).expect("CGIterable should always return a solution.") + last(solution.map(|s| s.x_k.clone())) + .expect("ConjugateGradient should always return a solution.") } pub fn show_progress(p: LinearSystem) { diff --git a/src/conjugate_gradient.rs b/src/conjugate_gradient.rs index 24702dd..e73eeea 100644 --- a/src/conjugate_gradient.rs +++ b/src/conjugate_gradient.rs @@ -1,24 +1,22 @@ +//! Implementation of conjugate gradient + +//! following [lecture notes](http://www.math.psu.edu/shen_w/524/CG_lecture.pdf) +//! by Shen. Thanks Shen! +//! +//! Pseudo code: +//! Set r_0 = A*x_0 - b and p_0 =-r_0, k=0 +//! while r_k != 0: +//! alpha_k = ||r_k||^2 / ||p_k||^2_A +//! x_{k+1} = x_k + alpha_k*p_k +//! r_{k+1} = r_K + alpha_k*A*p_k +//! beta_k = ||r_{k+1}||^2 / ||r_k||^2 +//! p_{k+1} = -r_k + beta_k * p_k +//! k += 1 + use crate::utils::{LinearSystem, M, S, V}; use ndarray::ArrayBase; use std::f64::{MIN_POSITIVE, NAN}; use streaming_iterator::*; -// Scalar, Vector and Matrix types: - -/// Implementation of conjugate gradient - -/// following -/// http://www.math.psu.edu/shen_w/524/CG_lecture.pdf. -/// Thanks Shen! - -// Pseudo code: -// Set r_0 = A*x_0 - b and p_0 =-r_0, k=0 -// while r_k != 0: -// alpha_k = ||r_k||^2 / ||p_k||^2_A -// x_{k+1} = x_k + alpha_k*p_k -// r_{k+1} = r_K + alpha_k*A*p_k -// beta_k = ||r_{k+1}||^2 / ||r_k||^2 -// p_{k+1} = -r_k + beta_k * p_k -// k += 1 // A few notes: // @@ -38,7 +36,7 @@ use streaming_iterator::*; /// Store the state of a conjugate gradient computation. /// -/// This implementation deviates from the psuedo code given in this +/// This implementation deviates from the pseudocode given in this /// module slightly: the prelude to the loop is run in initialization, /// and advance implements the loop. Since advance is always called /// before get, the currently calculated quantities (suffixed by `_k`) diff --git a/src/derivative_descent.rs b/src/derivative_descent.rs new file mode 100644 index 0000000..4a034d5 --- /dev/null +++ b/src/derivative_descent.rs @@ -0,0 +1,49 @@ +//! Library code for example from crate top-level documentation + +use super::*; + +#[derive(Debug, Clone)] +pub struct DerivativeDescent +where + V: Fn(f64) -> f64, + D: Fn(f64) -> f64, +{ + pub value: V, + pub derivative: D, + pub step_size: f64, + pub x: f64, +} + +impl DerivativeDescent +where + V: Fn(f64) -> f64, + D: Fn(f64) -> f64, +{ + pub fn new(value: V, derivative: D, step_size: f64, x_0: f64) -> DerivativeDescent { + DerivativeDescent { + value, + derivative, + step_size, + x: x_0, + } + } + + pub fn value(&self) -> f64 { + (&self.value)(self.x) + } +} + +impl StreamingIterator for DerivativeDescent +where + V: Fn(f64) -> f64, + D: Fn(f64) -> f64, +{ + type Item = DerivativeDescent; + fn advance(&mut self) { + self.x -= self.step_size * (self.derivative)(self.x); + } + + fn get(&self) -> Option<&Self::Item> { + Some(&self) + } +} diff --git a/src/lib.rs b/src/lib.rs index 3f4b9aa..5489263 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,64 @@ -//! # iterative-methods -//! A demonstration of the use of StreamingIterators and their adapters to implement iterative algorithms. +//! # Iterative methods +//! +//! Implements [iterative +//! methods](https://en.wikipedia.org/wiki/Iterative_method) and +//! utilities for using and developing them as +//! [StreamingIterators](https://crates.io/crates/streaming-iterator). A +//! series of [blog +//! posts](https://daniel-vainsencher.github.io/book/iterative_methods_part_1.html) +//! provide a gentle introduction. +//! +//! +//! ... but ok fine, here is a really quick example: +//!``` +//!# extern crate iterative_methods; +//!# use iterative_methods::derivative_descent::*; +//!# use iterative_methods::*; +//!# use streaming_iterator::*; +//!# +//!// Problem: minimize the convex parabola f(x) = x^2 + x +//!let function = |x| x * x + x; +//! +//!// An iterative solution by gradient descent +//!let derivative = |x| 2.0 * x + 1.0; +//!let step_size = 0.2; +//!let x_0 = 2.0; +//! +//!// Au naturale: +//!let mut x = x_0; +//!for i in 0..10 { +//! x -= step_size * derivative(x); +//! println!("x_{} = {:.2}; f(x_{}) = {:.4}", i, x, i, x * x + x); +//!} +//! +//!// Using replaceable components: +//!let dd = DerivativeDescent::new(function, derivative, step_size, x_0); +//!let dd = enumerate(dd); +//!let mut dd = dd.take(10); +//!while let Some(&Numbered{item: Some(ref curr), count}) = dd.next() { +//! println!("x_{} = {:.2}; f(x_{}) = {:.4}", count, curr.x, count, curr.value()); +//!} +//!``` +//! +//! Both produce the exact same output (below), and the first common +//! approach is much easier to look at, the descent step is right +//! there. The second separates the algorithm and every other concern +//! into an easily reusable and composable components. If that sounds +//! useful, have fun exploring. +//! +//!```ignore +//! x_0 = 1.00; f(x_0) = 2.0000 +//! x_1 = 0.40; f(x_1) = 0.5600 +//! x_2 = 0.04; f(x_2) = 0.0416 +//! x_3 = -0.18; f(x_3) = -0.1450 +//! x_4 = -0.31; f(x_4) = -0.2122 +//! x_5 = -0.38; f(x_5) = -0.2364 +//! x_6 = -0.43; f(x_6) = -0.2451 +//! x_7 = -0.46; f(x_7) = -0.2482 +//! x_8 = -0.47; f(x_8) = -0.2494 +//! x_9 = -0.48; f(x_9) = -0.2498 +//!``` + #[cfg(test)] extern crate quickcheck; extern crate yaml_rust; @@ -16,6 +75,7 @@ use yaml_rust::{Yaml, YamlEmitter}; pub mod algorithms; pub mod conjugate_gradient; +pub mod derivative_descent; pub mod utils; /// Creates an iterator which returns initial elements until and @@ -92,7 +152,7 @@ pub struct AnnotatedResult { /// An adaptor that annotates every underlying item `x` with `f(x)`. #[derive(Clone, Debug)] -pub struct AnnotatedIterable +pub struct Annotate where I: Sized + StreamingIterator, T: Clone, @@ -103,15 +163,15 @@ where pub current: Option>, } -impl AnnotatedIterable +impl Annotate where I: StreamingIterator, T: Sized + Clone, F: FnMut(&T) -> A, { /// Annotate every underlying item with the result of applying `f` to it. - pub fn new(it: I, f: F) -> AnnotatedIterable { - AnnotatedIterable { + pub fn new(it: I, f: F) -> Annotate { + Annotate { it, f, current: None, @@ -119,7 +179,7 @@ where } } -impl StreamingIterator for AnnotatedIterable +impl StreamingIterator for Annotate where I: StreamingIterator, T: Sized + Clone, @@ -150,23 +210,23 @@ where } /// Annotate every underlying item with its score, as defined by `f`. -pub fn assess(it: I, f: F) -> AnnotatedIterable +pub fn assess(it: I, f: F) -> Annotate where T: Clone, F: FnMut(&T) -> A, I: StreamingIterator, { - AnnotatedIterable::new(it, f) + Annotate::new(it, f) } -/// Apply `f` to every underlying item. -pub fn inspect(it: I, f: F) -> AnnotatedIterable +/// Apply `f(_)->()` to every underlying item (for side-effects). +pub fn inspect(it: I, f: F) -> Annotate where I: Sized + StreamingIterator, F: FnMut(&T), T: Clone, { - AnnotatedIterable::new(it, f) + Annotate::new(it, f) } /// Get the item before the first None, assuming any exist. @@ -178,11 +238,10 @@ where it.fold(None, |_acc, i| Some((*i).clone())) } -/// Times every call to `advance` on the underlying -/// StreamingIterator. Stores both the time at which it starts, and -/// the duration it took to run. +/// Adaptor that times every call to `advance` on adaptee. Stores +/// start time and duration. #[derive(Clone, Debug)] -pub struct TimedIterable +pub struct Time where I: StreamingIterator, T: Clone, @@ -192,6 +251,8 @@ where timer: Instant, } +/// Wrapper for Time. +/// /// TimedResult decorates with two duration fields: start_time is /// relative to the creation of the process generating results, and /// duration is relative to the start of the creation of the current @@ -206,19 +267,19 @@ pub struct TimedResult { /// Wrap each value of a streaming iterator with the durations: /// - between the call to this function and start of the value's computation /// - it took to calculate that value -pub fn time(it: I) -> TimedIterable +pub fn time(it: I) -> Time where I: Sized + StreamingIterator, T: Sized + Clone, { - TimedIterable { + Time { it, current: None, timer: Instant::now(), } } -impl StreamingIterator for TimedIterable +impl StreamingIterator for Time where I: Sized + StreamingIterator, T: Sized + Clone, @@ -303,18 +364,19 @@ where /// Write items of StreamingIterator to a file. #[derive(Debug)] -pub struct ToFileIterable { +struct WriteToFile { pub it: I, pub write_function: F, pub file_writer: File, } -/// An adaptor that writes each item to a new line of a file. -pub fn item_to_file( +/// An adaptor that calls a function to write each item to a file. +#[allow(dead_code)] +fn write_to_file( it: I, write_function: F, file_path: String, -) -> Result, std::io::Error> +) -> Result, std::io::Error> where I: Sized + StreamingIterator, T: std::fmt::Debug, @@ -329,7 +391,7 @@ where .append(true) .create(true) .open(file_path)?; - Ok(ToFileIterable { + Ok(WriteToFile { it, write_function, file_writer, @@ -339,7 +401,7 @@ where result } -impl StreamingIterator for ToFileIterable +impl StreamingIterator for WriteToFile where I: Sized + StreamingIterator, T: std::fmt::Debug, @@ -351,7 +413,7 @@ where fn advance(&mut self) { if let Some(item) = self.it.next() { (self.write_function)(&item, &mut self.file_writer) - .expect("Write item to file in ToFileIterable advance failed."); + .expect("Write item to file in WriteToFile advance failed."); } else { self.file_writer.flush().expect("Flush of file failed."); } @@ -444,7 +506,7 @@ where /// Write items of StreamingIterator to a Yaml file. #[derive(Debug)] -pub struct ToYamlIterable { +pub struct WriteYamlDocuments { pub it: I, pub file_writer: File, } @@ -453,7 +515,7 @@ pub struct ToYamlIterable { pub fn write_yaml_documents( it: I, file_path: String, -) -> Result, std::io::Error> +) -> Result, std::io::Error> where I: Sized + StreamingIterator, T: std::fmt::Debug, @@ -467,13 +529,13 @@ where .append(true) .create(true) .open(file_path)?; - Ok(ToYamlIterable { it, file_writer }) + Ok(WriteYamlDocuments { it, file_writer }) } }; result } -/// Function used by ToYamlIterable to specify how to write each item to file. +/// Function used by WriteYamlDocuments to specify how to write each item to file. /// pub fn write_yaml_object(item: &T, file_writer: &mut std::fs::File) -> std::io::Result<()> where @@ -492,7 +554,7 @@ where Ok(()) } -impl StreamingIterator for ToYamlIterable +impl StreamingIterator for WriteYamlDocuments where I: Sized + StreamingIterator, T: std::fmt::Debug + YamlDataType, @@ -502,8 +564,8 @@ where #[inline] fn advance(&mut self) { if let Some(item) = self.it.next() { - (write_yaml_object)(&item, &mut self.file_writer) - .expect("Write item to file in ToYamlIterable advance failed."); + write_yaml_object(&item, &mut self.file_writer) + .expect("Write item to file in WriteYamlDocuments advance failed."); } else { self.file_writer.flush().expect("Flush of file failed."); } @@ -602,7 +664,8 @@ where } } -/// An optimal reservoir sampling algorithm is implemented. +/// Adaptor to reservoir sample. +/// /// `ReservoirSample` wraps a `StreamingIterator`, `I` and /// produces a `StreamingIterator` whose items are samples of size `capacity` /// from the stream of `I`. (This is not the capacity of the `Vec` which holds the `reservoir`; @@ -692,13 +755,11 @@ where } } -/// Weighted Sampling +/// Wrapper for Weight. +/// /// The WeightedDatum struct wraps the values of a data set to include /// a weight for each datum. Currently, the main motivation for this /// is to use it for Weighted Reservoir Sampling (WRS). -/// -/// WRS is currently deprecated, but WeightedDatum and WdIterable are not. -/// #[derive(Debug, Clone, PartialEq)] pub struct WeightedDatum { pub value: U, @@ -716,13 +777,15 @@ where WeightedDatum { value, weight } } -/// WdIterable provides an easy conversion of any iterable to one whose items are WeightedDatum. -/// WdIterable holds an iterator and a function. The function is defined by the user to extract +/// Adaptor wrapping items with a computed weight. +/// +/// Weight provides an easy conversion of any iterable to one whose items are WeightedDatum. +/// Weight holds an iterator and a function. The function is defined by the user to extract /// weights from the iterable and package the old items and extracted weights into items as /// WeightedDatum #[derive(Debug, Clone)] -pub struct WdIterable +pub struct Weight where I: StreamingIterator, { @@ -732,15 +795,15 @@ where } /// Annotates items of an iterable with a weight using a function `f`. -pub fn wd_iterable(it: I, f: F) -> WdIterable +pub fn wd_iterable(it: I, f: F) -> Weight where I: StreamingIterator, F: FnMut(&T) -> f64, { - WdIterable { it, wd: None, f } + Weight { it, wd: None, f } } -impl StreamingIterator for WdIterable +impl StreamingIterator for Weight where I: StreamingIterator, F: FnMut(&T) -> f64, @@ -804,7 +867,9 @@ where } } -/// The weighted reservoir sampling algorithm of M. T. Chao is implemented. +/// Adaptor that reservoir samples with weights +/// +/// Uses the algorithm of M. T. Chao. /// `WeightedReservoirSample` wraps a `StreamingIterator`, `I`, whose items must be of type `WeightedDatum` and /// produces a `StreamingIterator` whose items are samples of size `capacity` /// from the stream of `I`. (This is not the capacity of the `Vec` which holds the `reservoir`; @@ -944,16 +1009,16 @@ mod tests { } let target_annotations = vec![0., 2., 4.]; let mut annotations: Vec = Vec::with_capacity(3); - let mut ann_iter = AnnotatedIterable::new(iter, f); + let mut ann_iter = Annotate::new(iter, f); while let Some(n) = ann_iter.next() { annotations.push(n.annotation); } assert_eq!(annotations, target_annotations); } - /// ToYamlIterable Test: Write stream of scalars to yaml + /// WriteYamlDocuments Test: Write stream of scalars to yaml /// - /// This writes a stream of scalars to a yaml file using ToYamlIterable iterable. + /// This writes a stream of scalars to a yaml file using WriteYamlDocuments iterable. /// It would fail if the file path used to write the data already existed /// due to the functionality of write_yaml_documents(). #[test] @@ -979,11 +1044,11 @@ mod tests { assert_eq!("---\n0\n---\n1\n---\n2\n---\n3\n", &contents); } - /// ToYamlIterable Test: Write stream of vecs to yaml + /// WriteYamlDocuments Test: Write stream of vecs to yaml /// - /// This writes a stream of vecs to a yaml file using ToYamlIterable iterable. + /// This writes a stream of vecs to a yaml file using WriteYamlDocuments iterable. /// It would fail if the file path used to write the data already existed - /// due to the functionality of item_to_file(). + /// due to the functionality of write_to_file(). #[test] fn write_vec_to_yaml_test() { let test_file_path = "./vec_to_file_test.yaml"; diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index ef5e1a5..5aa1507 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -44,9 +44,9 @@ fn test_timed_iterable() { assert!(st_diff.iter().all(|diff| *diff >= 0.)); } -/// Test that WdIterable followed by ExtractValue is a roundtrip. +/// Test that Weight followed by ExtractValue is a roundtrip. /// -/// WdIterable wraps the items of a simple Counter iterable as WeightedDatum +/// Weight wraps the items of a simple Counter iterable as WeightedDatum /// with the square of the count as the weight. Then ExtractValue unwraps, leaving /// items with only the original value. The items of a clone of the original iterator /// and the wrapped/unwrapped iterator are checked to be equal. @@ -55,7 +55,7 @@ fn test_timed_iterable() { fn wd_iterable_extract_value_test() { let mut counter_stream = Counter::new(); let counter_stream_copy = counter_stream.clone(); - let wd_iter = WdIterable { + let wd_iter = Weight { it: counter_stream_copy, f: expose_w, wd: Some(new_datum(0., 0.)), @@ -70,7 +70,7 @@ fn wd_iterable_extract_value_test() { } } -/// Test the integration of ReservoirSample, Enumerate, and ToFileIterable. +/// Test the integration of ReservoirSample, Enumerate, and WriteToFile. /// /// A stream of 2 zeros and 8 ones subjected to reservoir sampling using a seeded rng. /// The stream of reservoirs is adapted with enumerate() and then write_yaml_documents(). After