fixedpoint/
lib.rs

1//! # FixedPoint
2//!
3//! A simple library for computing the fixed point of a given function
4//!
5//! ## Example
6//! ```rust
7//! extern crate fixedpoint;
8//!
9//! use fixedpoint::fixedpoint;
10//!
11//! fn func_with_fixed_point(num: u32, param: &u32) -> u32 {
12//!     150 + (((num as f32 / param.clone() as f32).ceil() as u32)*100)
13//! }
14//!
15//! fn main() {
16//!      let val = fixedpoint(&func_with_fixed_point, 0, &150, None, None).unwrap();
17//!      println!("Fixed Point of function exists at: {}", val);
18//! }
19//! ```
20
21
22// Clippy Lints
23// #![warn(cast_possible_truncation)]
24// #![warn(cast_possible_wrap)]
25// #![warn(cast_precision_loss)]
26// #![warn(cast_sign_loss)]
27// #![warn(empty_enum)]
28// #![warn(enum_glob_use)]
29// #![warn(filter_map)]
30// #![warn(if_not_else)]
31// #![warn(indexing_slicing)]
32// #![warn(invalid_upcast_comparisons)]
33// #![warn(items_after_statements)]
34// #![warn(missing_docs_in_private_items)]
35// #![warn(mut_mut)]
36// #![warn(nonminimal_bool)]
37// #![warn(option_map_unwrap_or)]
38// #![warn(option_map_unwrap_or_else)]
39// #![warn(option_unwrap_used)]
40// #![warn(pub_enum_variant_names)]
41// #![warn(result_unwrap_used)]
42// #![warn(shadow_reuse)]
43// #![warn(shadow_same)]
44// #![warn(shadow_unrelated)]
45// #![warn(similar_names)]
46// #![warn(single_match_else)]
47// #![warn(stutter)]
48// #![warn(wrong_pub_self_convention)]
49
50#![warn(missing_docs,
51        missing_debug_implementations,
52        missing_copy_implementations,
53        trivial_casts, trivial_numeric_casts,
54        unsafe_code,
55        unstable_features,
56        unused_import_braces, unused_qualifications)]
57
58#[macro_use]
59extern crate error_chain;
60#[macro_use]
61extern crate log;
62
63#[allow(missing_docs)]
64pub mod errors {
65    // Create the Error, ErrorKind, ResultExt, and Result types
66    error_chain! {
67        errors {
68            ValueLimit
69            IterLimit(limit: usize) {
70                description("Max iteration limit exceeded")
71                display("Could not converge function after {} iterations", limit)
72            }
73        }
74    }
75}
76
77use errors::*;
78
79
80/// Compute the fixed point of a given function
81///
82/// Given a function `func`, compute the fixed point of the function such that `func(x, args) = x`
83/// `args` is a reference to any object that the function uses as auxillary data
84/// `maxiter` is used to limit the maximum number of iterations that the function will go through
85/// before giving up. The default is 100.
86/// If a `maxval` is provided, then the fixedpoint computation will stop when the value of the
87/// function exceeds `maxval`. This is mostly useful in providing upper bounds on monotonic
88/// functions.
89///
90/// The `args` parameter is generic. So you can pass arbitrary data types to it that only the
91/// provided function needs to know how to interpret. This allows you to pass a `Option`, `struct`,
92/// `Result` or any other type of complex object as well.
93pub fn fixedpoint<T, U>(func: &Fn(U, &T) -> U, x0: U, args: &T, maxiter: Option<usize>, maxval: Option<U>) -> Result<U>
94where U: PartialEq + PartialOrd + Copy + std::fmt::Debug {
95    let maxiter = maxiter.unwrap_or(100);
96    let mut itr = maxiter;
97    let mut x = x0;
98    let mut val = func(x0, args);
99    while val != x {
100        trace!("Iteration: {}", maxiter - itr);
101        x = val;
102        val = func(x, args);
103        trace!("\tx: {:?}; F(x): {:?}", x, val);
104        itr -= 1;
105        if itr == 0 {
106            debug!("Max Iterations reached. Last value: {:?}", val);
107            bail!(ErrorKind::IterLimit(maxiter));
108        } else if val > maxval.unwrap_or(val) {
109            debug!("Maximum value of function reached. Last value: {:?}", val);
110            bail!(ErrorKind::ValueLimit);
111        }
112    };
113    Ok(val)
114}
115
116#[cfg(test)]
117mod tests {
118    use fixedpoint;
119
120    fn func_with_fixed_point(num: u32, param: &u32) -> u32 {
121        150 + (((num as f32 / param.clone() as f32).ceil() as u32)*100)
122    }
123
124    #[test]
125    fn basic_tests() {
126        assert_eq!(fixedpoint(&func_with_fixed_point, 0, &150, None, None).unwrap(), 450);
127        assert_eq!(fixedpoint(&func_with_fixed_point, 0, &150, None, Some(400)).is_err(), true);
128        assert_eq!(fixedpoint(&func_with_fixed_point, 0, &10, Some(5), None).is_err(), true);
129    }
130
131    #[test]
132    #[should_panic(expected = "attempt to multiply with overflow")]
133    fn maxiter() {
134        fixedpoint(&func_with_fixed_point, 0, &10, Some(10), None).unwrap_or(0);
135    }
136}