#[cfg(feature = "polars")]
use polars::prelude::*;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{
stats::{Evals, Steps, Timer},
status::Status,
traits::{Real, State},
};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct Solution<T, Y>
where
T: Real,
Y: State<T>,
{
pub t: Vec<T>,
pub y: Vec<Y>,
pub status: Status<T, Y>,
pub evals: Evals,
pub steps: Steps,
pub timer: Timer<T>,
}
impl<T, Y> Default for Solution<T, Y>
where
T: Real,
Y: State<T>,
{
fn default() -> Self {
Self::new()
}
}
impl<T, Y> Solution<T, Y>
where
T: Real,
Y: State<T>,
{
pub fn new() -> Self {
Solution {
t: Vec::with_capacity(100),
y: Vec::with_capacity(100),
status: Status::Uninitialized,
evals: Evals::new(),
steps: Steps::new(),
timer: Timer::Off,
}
}
}
impl<T, Y> Solution<T, Y>
where
T: Real,
Y: State<T>,
{
pub fn push(&mut self, t: T, y: Y) {
self.t.push(t);
self.y.push(y);
}
pub fn pop(&mut self) -> Option<(T, Y)> {
if self.t.is_empty() || self.y.is_empty() {
return None;
}
let t = self.t.pop().unwrap();
let y = self.y.pop().unwrap();
Some((t, y))
}
pub fn truncate(&mut self, index: usize) {
self.t.truncate(index);
self.y.truncate(index);
}
}
impl<T, Y> Solution<T, Y>
where
T: Real,
Y: State<T>,
{
pub fn into_tuple(self) -> (Vec<T>, Vec<Y>) {
(self.t, self.y)
}
pub fn last(&self) -> Result<(&T, &Y), Box<dyn std::error::Error>> {
let t = self.t.last().ok_or("No t steps available")?;
let y = self.y.last().ok_or("No y vectors available")?;
Ok((t, y))
}
pub fn iter(&self) -> std::iter::Zip<std::slice::Iter<'_, T>, std::slice::Iter<'_, Y>> {
self.t.iter().zip(self.y.iter())
}
#[cfg(not(feature = "polars"))]
pub fn to_csv(&self, filename: &str) -> Result<(), Box<dyn std::error::Error>> {
use std::io::{BufWriter, Write};
let path = std::path::Path::new(filename);
if let Some(parent) = path.parent() {
if !parent.exists() {
std::fs::create_dir_all(parent)?;
}
}
let file = std::fs::File::create(filename)?;
let mut writer = BufWriter::new(file);
let n = self.y[0].len();
let mut header = String::from("t");
for i in 0..n {
header.push_str(&format!(",y{}", i));
}
writeln!(writer, "{}", header)?;
for (t, y) in self.iter() {
let mut row = format!("{:?}", t);
for i in 0..n {
row.push_str(&format!(",{:?}", y.get(i)));
}
writeln!(writer, "{}", row)?;
}
writer.flush()?;
Ok(())
}
#[cfg(feature = "polars")]
pub fn to_csv(&self, filename: &str) -> Result<(), Box<dyn std::error::Error>> {
let path = std::path::Path::new(filename);
if !path.exists() {
std::fs::create_dir_all(path.parent().unwrap())?;
}
let mut file = std::fs::File::create(filename)?;
let t = self.t.iter().map(|x| x.to_f64()).collect::<Vec<f64>>();
let mut columns = vec![Column::new("t".into(), t)];
let n = self.y[0].len();
for i in 0..n {
let header = format!("y{}", i);
columns.push(Column::new(
header.into(),
self.y
.iter()
.map(|x| x.get(i).to_f64())
.collect::<Vec<f64>>(),
));
}
let mut df = DataFrame::new(columns)?;
CsvWriter::new(&mut file).finish(&mut df)?;
Ok(())
}
#[cfg(feature = "polars")]
pub fn to_polars(&self) -> Result<DataFrame, PolarsError> {
let t = self.t.iter().map(|x| x.to_f64()).collect::<Vec<f64>>();
let mut columns = vec![Column::new("t".into(), t)];
let n = self.y[0].len();
for i in 0..n {
let header = format!("y{}", i);
columns.push(Column::new(
header.into(),
self.y
.iter()
.map(|x| x.get(i).to_f64())
.collect::<Vec<f64>>(),
));
}
DataFrame::new(columns)
}
#[cfg(feature = "polars")]
pub fn to_named_polars(
&self,
t_name: &str,
y_names: Vec<&str>,
) -> Result<DataFrame, PolarsError> {
let t = self.t.iter().map(|x| x.to_f64()).collect::<Vec<f64>>();
let mut columns = vec![Column::new(t_name.into(), t)];
let n = self.y[0].len();
if y_names.len() != n {
return Err(PolarsError::ComputeError(
format!(
"Expected {} column names for state variables, but got {}",
n,
y_names.len()
)
.into(),
));
}
for (i, name) in y_names.iter().enumerate() {
columns.push(Column::new(
(*name).into(),
self.y
.iter()
.map(|x| x.get(i).to_f64())
.collect::<Vec<f64>>(),
));
}
DataFrame::new(columns)
}
}