reda-spice 0.1.0

Spice simulate and parse library
Documentation
use std::{collections::HashMap, path::Path};
use reda_unit::{Current, CurrentUnit, Number, Unit, UnitNumber, Voltage, VoltageUnit};

use crate::probe::Drawer;

use super::AnalysisError;

#[derive(Debug, Clone)]
pub struct DcAnalysis<U> {
    pub sweep: Vec<UnitNumber<U>>, 
    pub nodes: HashMap<String, Vec<Voltage>>,
    pub branches: HashMap<String, Vec<Current>>,
    pub internal_parameters: HashMap<String, Vec<Number>>,
}

pub type DcVoltageAnalysis = DcAnalysis<VoltageUnit>;
pub type DcCurrentAnalysis = DcAnalysis<CurrentUnit>;

impl<U: Unit> DcAnalysis<U> {
    pub fn get_node(&self, name: &str) -> Option<&Vec<Voltage>> {
        self.nodes.get(name)
    }

    pub fn get_branch(&self, name: &str) -> Option<&Vec<Current>> {
        self.branches.get(name)
    }

    pub fn get_internal(&self, name: &str) -> Option<&Vec<Number>> {
        self.internal_parameters.get(name)
    }

    pub fn get_voltage_at(&self, node: &str, when: UnitNumber<U>) -> Option<Voltage> {
        let values = self.nodes.get(node)?;
        if values.len() != self.sweep.len() || values.len() < 2 {
            return None;
        }

        for i in 0..self.sweep.len() - 1 {
            let t0 = self.sweep[i];
            let t1 = self.sweep[i + 1];

            if when >= t0 && when <= t1 {
                let v0 = values[i];
                let v1 = values[i + 1];

                let ratio = (when - t0) / (t1 - t0);
                return Some(v0 + (v1 - v0) * ratio);
            }
        }

        None 
    }
}

impl<U: Unit> DcAnalysis<U> {
    pub fn draw_all_nodes<P: AsRef<Path>>(&self, drawer: &Drawer, path: P) -> Result<(), AnalysisError> {
        self.draw_nodes_filter(drawer, path, |_| true)
    }

    pub fn draw_nodes<P: AsRef<Path>>(
        &self, 
        drawer: &Drawer, 
        nodes: &[&str],
        path: P,
    ) -> Result<(), AnalysisError> {
        self.draw_nodes_filter(drawer, path, |name| nodes.contains(&name))
    }

    pub fn draw_nodes_filter<P: AsRef<Path>, Pre: Fn(&str) -> bool>(
        &self, 
        drawer: &Drawer, 
        path: P,
        predicate: Pre
    ) -> Result<(), AnalysisError> {
        let mut all_signals: Vec<(String, Vec<f64>)> = Vec::new();
        for (k, v) in &self.nodes {
            if predicate(k.as_str()) {
                let values = v.iter().map(|v| v.to_f64()).collect();
                all_signals.push((k.into(), values));
            }
        }
        let sweep: Vec<_> = self.sweep.iter().map(|t| t.to_f64()).collect();

        drawer.draw(U::name(), "V",  &sweep, &all_signals, path).map_err(|e| AnalysisError::PlotError(e))
    }

    pub fn draw_all_branchs<P: AsRef<Path>>(&self, drawer: &Drawer, path: P) -> Result<(), AnalysisError> {
        self.draw_branchs_filter(drawer, path, |_| true)
    }

    pub fn draw_branchs<P: AsRef<Path>>(
        &self, 
        drawer: &Drawer, 
        branchs: &[&str],
        path: P,
    ) -> Result<(), AnalysisError> {
        self.draw_branchs_filter(drawer, path, |name| branchs.contains(&name))
    }

    pub fn draw_branchs_filter<P: AsRef<Path>, Pre: Fn(&str) -> bool>(
        &self, 
        drawer: &Drawer, 
        path: P,
        predicate: Pre
    ) -> Result<(), AnalysisError> {
        let mut all_signals: Vec<(String, Vec<f64>)> = Vec::new();
        for (k, c) in &self.branches {
            if predicate(k.as_str()) {
                let values = c.iter().map(|v| v.to_f64()).collect();
                all_signals.push((k.into(), values));
            }
        }
        let sweep: Vec<_> = self.sweep.iter().map(|t| t.to_f64()).collect();

        drawer.draw(U::name(), "I",  &sweep, &all_signals, path).map_err(|e| AnalysisError::PlotError(e))
    }
}