rantlib 0.1.0

Analysis Library for Non-linear Dynamical Systems written in Rust
Documentation
use std::{cmp::Ordering, fmt::Debug, fs::read_to_string};

use thiserror::Error;

use crate::simulate::period::Cycle;

pub fn read_tna_file(path: &str) -> anyhow::Result<Vec<Vec<f64>>> {
    let content = read_to_string(path)?;

    let lines = content.lines().filter(|line| {
        let line_content: String = line.chars().filter(|c| !c.is_whitespace()).collect();
        !(line_content.is_empty() || line_content.starts_with('#'))
    });

    Ok(lines
        .map(|line| {
            line.split_ascii_whitespace()
                .map(|part| part.parse::<f64>())
                .collect::<Result<Vec<f64>, _>>()
        })
        .collect::<Result<Vec<Vec<f64>>, _>>()?)
}

#[derive(Debug, Error)]
enum TnaError {
    #[error("too few values in line")]
    TooFewValues,
}

pub fn read_tna_periods_file<S, P>(
    path: &str,
    unproject_initial_state_and_parameters: impl Fn(Vec<f64>) -> anyhow::Result<(S, P)>,
) -> anyhow::Result<Vec<(S, P, usize)>> {
    read_tna_file(path)?
        .into_iter()
        .map(|mut line| -> anyhow::Result<(S, P, usize)> {
            let period = line.pop().ok_or(TnaError::TooFewValues)?;
            let (state, parameters) = unproject_initial_state_and_parameters(line)?;
            Ok((state, parameters, period as usize))
        })
        .collect::<anyhow::Result<Vec<_>>>()
}

pub fn assert_equals_tna_periods<S, P>(
    mut rant_result: Vec<(S, P, Cycle<S>)>,
    mut ant_result: Vec<(S, P, usize)>,
    compare_states: impl Fn(&S, &S) -> Ordering,
    compare_parameters: impl Fn(&P, &P) -> Ordering,
) where
    S: Debug,
    P: Debug,
{
    let sort_by_state_and_parameters = |a: (&S, &P), b: (&S, &P)| {
        let state_ordering = compare_states(a.0, b.0);
        match state_ordering {
            Ordering::Equal => compare_parameters(a.1, b.1),
            order => order,
        }
    };

    rant_result.sort_by(|a, b| sort_by_state_and_parameters((&a.0, &a.1), (&b.0, &b.1)));
    ant_result.sort_by(|a, b| sort_by_state_and_parameters((&a.0, &a.1), (&b.0, &b.1)));

    let mut differences = vec![];

    for ((_, rant_parameters, rant_result), (_, ant_parameters, ant_result)) in
        rant_result.into_iter().zip(ant_result)
    {
        match rant_result {
            Cycle::FixedPoint(p) => {
                if ant_result != 1 {
                    differences.push(format!(
                        "RAnT found fixed point {:?} for parameters {:?}, AnT got cycle of length {} (parameters: {:?})",
                        p, rant_parameters, ant_result, ant_parameters,
                    ))
                }
            }
            Cycle::Cycle(c) => {
                if ant_result != c.len() {
                    differences.push(format!(
                        "RAnT has cycle of length {} for parameters {:?}, AnT got {} (parameters: {:?}) - rant cycle: {:?}",
                        c.len(), rant_parameters, ant_result, ant_parameters, c,
                    ))
                }
            }
            Cycle::Divergence => {
                if ant_result != 0 {
                    differences.push(format!(
                        "RAnT found divergence for parameters {:?}, AnT got cycle of length {} (parameters: {:?})",
                        rant_parameters, ant_result, ant_parameters,
                    ))
                }
            }
        }
    }

    if !differences.is_empty() {
        for difference in &differences {
            println!("{}", difference)
        }
        panic!("too many differences: {}", differences.len());
    }
}