rspice 0.1.0

Pure-Rust circuit simulation backend
Documentation
//! **rspice** is a pure-Rust circuit simulation backend.
//!
//! # Creating circuits
//! ```rust
//! use rspice::Circuit;
//! use rspice::components::{Resistor, VoltageSource, Ground};
//!
//! let mut circuit = Circuit::new();
//! // nets are wires connecting possibly multiple components
//! let top_wire = circuit.createNet();
//! let bottom_wire = circuit.createNet();
//! // components are created and connected like this
//! let R1 = Resistor::create(&mut circuit, top_wire, bottom_wire, 100.0);
//! VoltageSource::create(&mut circuit, top_wire, bottom_wire, 5.0);
//! // we use a ground to set a voltage reference
//! // since the simulation will fail otherwise
//! Ground::create(&mut circuit, bottom_wire);
//! ```
//!
//! # Simulating circuits
//! The following simulation types are supported:
//! * Transient: the circuit is simulated over time, step by step.
//!
//! ## Transient analysis
//! ```rust
//! # use rspice::Circuit;
//! # use rspice::components::{Resistor, VoltageSource, Ground};
//!
//! # let mut circuit = Circuit::new();
//! # let top_wire = circuit.createNet();
//! # let bottom_wire = circuit.createNet();
//! # let R1 = Resistor::create(&mut circuit, top_wire, bottom_wire, 100.0);
//! # VoltageSource::create(&mut circuit, top_wire, bottom_wire, 5.0);
//! # Ground::create(&mut circuit, bottom_wire);
//! // ...
//!
//! // takes total duration and step size as arguments;
//! // make sure steps are short enough to capture
//! // any relevant frequencies in the circuit
//! let result = circuit.performTransientAnalysis(1.0, 1.0e-3).unwrap();
//! let time = 0.4;
//! assert_eq!(5.0, result.voltage_at(top_wire, time));
//! assert_eq!(0.0, result.voltage_at(bottom_wire, time));
//! // currents are measured at each connection, from component to net
//! assert_eq!(-5.0/100.0, result.current_at(&R1, 0, time)); // top_wire -> R1
//! assert_eq!(5.0/100.0, result.current_at(&R1, 1, time)); // R1 -> bottom_wire
//! ```

extern crate sparsela;
use sparsela::SparseMatrix;

/// Generic error type.
pub enum RspiceError {
    /// An error that could indicate a bug in the simulator.
    InternalError,
    /// The circuit's corresponding equation could not be solved.
    ///
    /// This may indicate an invalid circuit.
    CouldNotSolve,
}

impl std::fmt::Debug for RspiceError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        f.write_str(match self {
            RspiceError::InternalError => "rspice: internal error",
            RspiceError::CouldNotSolve => "rspice: no solution or underspecified",
        })?;
        Ok(())
    }
}

/// Reference to a net in a circuit.
#[derive(Clone, Copy)]
pub struct NetRef {
    index: usize,
}

/// Reference to a connection between a component and a net.
#[derive(Clone, Copy)]
pub struct ConnectionRef {
    index: usize,
}

/// Reference to a component in a circuit.
#[derive(Clone)]
pub struct ComponentRef {
    connections: Vec<usize>,
}

impl ComponentRef {
    /// Get the `index`-th connection to the component.
    pub fn connection(&self, index: usize) -> ConnectionRef {
        ConnectionRef {
            index: self.connections[index],
        }
    }

    /// Get all connections to the component.
    pub fn connection_count(&self) -> usize {
        self.connections.len()
    }

    /// Assemble a compound component from some existing connections.
    ///
    /// These will become the terminals of the new component.
    pub fn assemble(connections: &Vec<ConnectionRef>) -> Self {
        let mut raw_connections = Vec::new();
        for connection in connections {
            raw_connections.push(connection.index);
        }
        Self {
            connections: raw_connections,
        }
    }
}

/// Reference to an equation in a linear system.
#[derive(Clone, Copy)]
pub struct EquationRef {
    index: usize,
}

/// Matrix representing a transient circuit simulation's linear equation.
pub struct TransientMatrix {
    // coefficient * x = constant
    //          net voltage terms   connection current terms
    //                         |_   _____|
    //                         | \ /     |
    // net equations        -  # # # # # #      V     - net voltages
    //                      \  # # # # # #      dV/dt /
    // connection equations -  # # # # # #  \/  I     - connection currents
    //                      |  # # # # # #  /\  dI/dt |
    //                      |  # # # # # #      I     |
    //                      \  # # # # # #      dI/dt /
    // a derivative immediately follows each value in row and column
    coefficient: SparseMatrix<f64>,
    constant: Vec<f64>,
    net_count: usize,
    connection_count: usize,
}

impl std::fmt::Debug for TransientMatrix {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        for y in 0..(self.net_count + self.connection_count) * 2 {
            for x in 0..(self.net_count + self.connection_count) * 2 {
                f.write_str(&format!("{:4} ", self.coefficient[(y, x)]))?;
            }
            f.write_str(&format!("| {:4}\n", self.constant[y]))?;
        }
        Ok(())
    }
}

impl TransientMatrix {
    fn new(net_count: usize, connection_count: usize) -> Self {
        // every net adds two variables (its voltage and derivative)
        // and an equation (net zero current, net zero current derivative);
        // every connection adds two variables (its current and derivative)
        // and two equations (of the component's choice).

        // the first
        let total_size = (net_count + connection_count) * 2;
        Self {
            coefficient: SparseMatrix::new((total_size, total_size)),
            constant: vec![0.0; total_size],
            net_count: net_count,
            connection_count: connection_count,
        }
    }

    /// Zero out all coefficients and constant in an equation.
    pub fn clear_equation(&mut self, equation: EquationRef) {
        /*for x in 0..(self.net_count + self.connection_count) * 2 {
            self.coefficient[(self.net_count * 2 + equation.index, x)] = 0.0;
        }*/
        self.coefficient
            .clear_row(self.net_count * 2 + equation.index);
        self.constant[self.net_count * 2 + equation.index] = 0.0;
    }

    /// Access the coefficient of `net`'s voltage in `equation`.
    pub fn voltage_term(&mut self, equation: EquationRef, net: NetRef) -> &mut f64 {
        &mut self.coefficient[(self.net_count * 2 + equation.index, net.index * 2)]
    }

    /// Access the coefficient of `net`'s voltage derivative over time in `equation`.
    pub fn voltage_derivative_term(&mut self, equation: EquationRef, net: NetRef) -> &mut f64 {
        &mut self.coefficient[(self.net_count * 2 + equation.index, net.index * 2 + 1)]
    }

    /// Access the coefficient of `connection`'s current in `equation`.
    pub fn current_term(&mut self, equation: EquationRef, connection: ConnectionRef) -> &mut f64 {
        &mut self.coefficient[(
            self.net_count * 2 + equation.index,
            (self.net_count + connection.index) * 2,
        )]
    }

    /// Access the coefficient of `connection`'s current derivative over time in `equation`.
    pub fn current_derivative_term(
        &mut self,
        equation: EquationRef,
        connection: ConnectionRef,
    ) -> &mut f64 {
        &mut self.coefficient[(
            self.net_count * 2 + equation.index,
            (self.net_count + connection.index) * 2 + 1,
        )]
    }

    /// Access the constant term in `equation`.
    pub fn constant_term(&mut self, equation: EquationRef) -> &mut f64 {
        &mut self.constant[self.net_count * 2 + equation.index]
    }

    fn raw_current_term(&mut self, raw_equation: usize, connection: usize) -> &mut f64 {
        &mut self.coefficient[(raw_equation, (self.net_count + connection) * 2)]
    }

    fn raw_current_derivative_term(&mut self, raw_equation: usize, connection: usize) -> &mut f64 {
        &mut self.coefficient[(raw_equation, (self.net_count + connection) * 2 + 1)]
    }

    fn solve(&self) -> Result<Vec<f64>, RspiceError> {
        match self
            .coefficient
            .clone()
            .solveGauss(&self.constant, Some(1e-6f64))
        {
            Ok(v) => Ok(v),
            Err(_) => Err(RspiceError::CouldNotSolve),
        }
    }
}

/// Vector representing a transient circuit simulation's voltages and currents.
pub struct TransientVector {
    vector: Vec<f64>,
    net_count: usize,
}

impl TransientVector {
    fn new(vector: Vec<f64>, net_count: usize) -> Self {
        Self {
            vector: vector,
            net_count: net_count,
        }
    }

    /// Get the value of `net`'s voltage.
    pub fn voltage(&self, net: NetRef) -> f64 {
        self.vector[net.index * 2]
    }

    /// Get the value of `net`'s voltage derivative.
    pub fn voltage_derivative(&self, net: NetRef) -> f64 {
        self.vector[net.index * 2 + 1]
    }

    /// Get the value of `connection`'s current.
    pub fn current(&self, connection: ConnectionRef) -> f64 {
        self.vector[(self.net_count + connection.index) * 2]
    }

    /// Get the value of `connection`'s current derivative.
    pub fn current_derivative(&self, connection: ConnectionRef) -> f64 {
        self.vector[(self.net_count + connection.index) * 2 + 1]
    }
}

mod basic_component;
pub use basic_component::BasicComponent;
mod circuit;
pub use circuit::Circuit;
pub mod components;
mod transient_result;
pub use transient_result::TransientResult;