bland 0.2.0

Pure-Rust library for paper-ready, monochrome, hatch-patterned technical plots in the visual tradition of 1960s-80s engineering reports.
Documentation
//! Smith chart helpers.
//!
//! Plots reflection coefficient `Γ = (Z − 1) / (Z + 1)` on the unit
//! disk for normalized impedance `Z = r + jx`. Constant-resistance
//! circles and constant-reactance arcs form the classical grid.

pub const DEFAULT_R_VALUES: [f64; 5] = [0.2, 0.5, 1.0, 2.0, 5.0];
pub const DEFAULT_X_VALUES: [f64; 5] = [0.2, 0.5, 1.0, 2.0, 5.0];

/// Convert normalized impedance `(r, x)` to reflection coefficient
/// `(Re Γ, Im Γ)`.
pub fn gamma_from_z(r: f64, x: f64) -> (f64, f64) {
    let denom = (r + 1.0).powi(2) + x * x;
    ((r * r + x * x - 1.0) / denom, 2.0 * x / denom)
}

/// Inverse: `(Γ_re, Γ_im)` to `(r, x)`. Returns `(NAN, NAN)` at Γ = 1.
pub fn z_from_gamma(gr: f64, gi: f64) -> (f64, f64) {
    let denom = (1.0 - gr).powi(2) + gi * gi;
    if denom == 0.0 {
        return (f64::NAN, f64::NAN);
    }
    ((1.0 - gr * gr - gi * gi) / denom, 2.0 * gi / denom)
}

/// Full circle of constant normalized resistance `r` in Γ space.
/// Center `(r/(r+1), 0)`, radius `1/(r+1)`. Tangent to the unit disk
/// at `(1, 0)`.
pub fn r_circle(r: f64, n: usize) -> (Vec<f64>, Vec<f64>) {
    let cx = r / (r + 1.0);
    let rad = 1.0 / (r + 1.0);
    trace_circle(cx, 0.0, rad, n)
}

/// Full circle of constant reactance `x`. Center `(1, 1/x)`, radius
/// `1/|x|`. Only the portion inside the unit disk is meaningful — the
/// figure's circle clip masks the rest.
pub fn x_arc(x: f64, n: usize) -> (Vec<f64>, Vec<f64>) {
    let cx = 1.0;
    let cy = 1.0 / x;
    let rad = 1.0 / x.abs();
    trace_circle(cx, cy, rad, n)
}

fn trace_circle(cx: f64, cy: f64, rad: f64, n: usize) -> (Vec<f64>, Vec<f64>) {
    let n = n.max(3);
    let step = std::f64::consts::TAU / n as f64;
    let mut xs = Vec::with_capacity(n + 1);
    let mut ys = Vec::with_capacity(n + 1);
    for i in 0..=n {
        let phi = i as f64 * step;
        xs.push(cx + rad * phi.cos());
        ys.push(cy + rad * phi.sin());
    }
    (xs, ys)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn z_eq_1_maps_to_origin() {
        let (gr, gi) = gamma_from_z(1.0, 0.0);
        assert!(gr.abs() < 1e-9);
        assert!(gi.abs() < 1e-9);
    }

    #[test]
    fn short_maps_to_minus_one() {
        let (gr, gi) = gamma_from_z(0.0, 0.0);
        assert!((gr + 1.0).abs() < 1e-9);
        assert!(gi.abs() < 1e-9);
    }
}