primer3 0.1.0

Safe Rust bindings to the primer3 primer design library
Documentation
//! Shared salt and buffer concentration parameters.
//!
//! [`SolutionConditions`] is used by [`TmParams`](crate::TmParams),
//! [`ThermoArgs`](crate::ThermoArgs), and [`OligoSettings`](crate::OligoSettings)
//! to avoid duplicating the same 7 concentration fields across multiple structs.

/// Salt and buffer concentrations for thermodynamic calculations.
///
/// Defaults match primer3-py's standard PCR conditions.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SolutionConditions {
    /// Monovalent cation concentration in mM (default: 50.0).
    pub mv_conc: f64,
    /// Divalent cation concentration in mM (default: 1.5).
    pub dv_conc: f64,
    /// dNTP concentration in mM (default: 0.6).
    pub dntp_conc: f64,
    /// DNA strand concentration in nM (default: 50.0).
    pub dna_conc: f64,
    /// DMSO concentration in % (default: 0.0).
    pub dmso_conc: f64,
    /// DMSO correction factor (default: 0.6).
    pub dmso_fact: f64,
    /// Formamide concentration in mol/L (default: 0.0).
    pub formamide_conc: f64,
}

impl Default for SolutionConditions {
    fn default() -> Self {
        Self {
            mv_conc: 50.0,
            dv_conc: 1.5,
            dntp_conc: 0.6,
            dna_conc: 50.0,
            dmso_conc: 0.0,
            dmso_fact: 0.6,
            formamide_conc: 0.0,
        }
    }
}

impl SolutionConditions {
    /// Sets the monovalent cation concentration in mM and returns `self`.
    pub fn with_mv_conc(mut self, mv_conc: f64) -> Self {
        self.mv_conc = mv_conc;
        self
    }

    /// Sets the divalent cation concentration in mM and returns `self`.
    pub fn with_dv_conc(mut self, dv_conc: f64) -> Self {
        self.dv_conc = dv_conc;
        self
    }

    /// Sets the dNTP concentration in mM and returns `self`.
    pub fn with_dntp_conc(mut self, dntp_conc: f64) -> Self {
        self.dntp_conc = dntp_conc;
        self
    }

    /// Sets the DNA strand concentration in nM and returns `self`.
    pub fn with_dna_conc(mut self, dna_conc: f64) -> Self {
        self.dna_conc = dna_conc;
        self
    }
}

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

    #[test]
    fn test_default_conditions() {
        let c = SolutionConditions::default();
        assert!((c.mv_conc - 50.0).abs() < f64::EPSILON);
        assert!((c.dv_conc - 1.5).abs() < f64::EPSILON);
        assert!((c.dntp_conc - 0.6).abs() < f64::EPSILON);
        assert!((c.dna_conc - 50.0).abs() < f64::EPSILON);
        assert!((c.dmso_conc).abs() < f64::EPSILON);
        assert!((c.dmso_fact - 0.6).abs() < f64::EPSILON);
        assert!((c.formamide_conc).abs() < f64::EPSILON);
    }

    #[test]
    fn test_builder_methods() {
        let c = SolutionConditions::default()
            .with_mv_conc(100.0)
            .with_dv_conc(3.0)
            .with_dntp_conc(0.8)
            .with_dna_conc(250.0);
        assert!((c.mv_conc - 100.0).abs() < f64::EPSILON);
        assert!((c.dv_conc - 3.0).abs() < f64::EPSILON);
        assert!((c.dntp_conc - 0.8).abs() < f64::EPSILON);
        assert!((c.dna_conc - 250.0).abs() < f64::EPSILON);
    }
}