use serde::{Deserialize, Serialize};
use crate::constants::{FARADAY, R_GAS};
use crate::error::RasayanError;
#[must_use]
#[inline]
pub fn nernst(temp_kelvin: f64, z: i32, conc_out: f64, conc_in: f64) -> f64 {
if conc_in <= 0.0 || conc_out <= 0.0 || z == 0 {
return 0.0;
}
(R_GAS * temp_kelvin / (z as f64 * FARADAY)) * (conc_out / conc_in).ln() * 1000.0
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MembranePermeability {
pub p_na: f64,
pub p_k: f64,
pub p_cl: f64,
}
pub const DEFAULT_PERMEABILITY: MembranePermeability = MembranePermeability {
p_na: 0.04,
p_k: 1.0,
p_cl: 0.45,
};
impl Default for MembranePermeability {
fn default() -> Self {
DEFAULT_PERMEABILITY
}
}
impl MembranePermeability {
#[must_use = "validation errors should be handled"]
pub fn validate(&self) -> Result<(), RasayanError> {
for (name, value) in [("p_na", self.p_na), ("p_k", self.p_k), ("p_cl", self.p_cl)] {
if value < 0.0 {
return Err(RasayanError::InvalidParameter {
name: name.into(),
value,
reason: "permeability must be non-negative".into(),
});
}
}
Ok(())
}
}
#[must_use]
#[inline]
pub fn goldman(temp_kelvin: f64, ions: &IonicState, perm: &MembranePermeability) -> f64 {
let num = perm.p_na * ions.na_out + perm.p_k * ions.k_out + perm.p_cl * ions.cl_in;
let den = perm.p_na * ions.na_in + perm.p_k * ions.k_in + perm.p_cl * ions.cl_out;
if den <= 0.0 || num <= 0.0 {
return 0.0;
}
(R_GAS * temp_kelvin / FARADAY) * (num / den).ln() * 1000.0
}
#[must_use]
#[inline]
pub fn fick_flux(diffusion_coeff: f64, conc_diff: f64, thickness: f64) -> f64 {
if thickness <= 0.0 {
return 0.0;
}
-diffusion_coeff * conc_diff / thickness
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct IonicState {
pub na_in: f64,
pub na_out: f64,
pub k_in: f64,
pub k_out: f64,
pub cl_in: f64,
pub cl_out: f64,
}
impl Default for IonicState {
fn default() -> Self {
Self {
na_in: 12.0,
na_out: 145.0,
k_in: 155.0,
k_out: 4.0,
cl_in: 4.0,
cl_out: 120.0,
}
}
}
impl IonicState {
#[must_use = "validation errors should be handled"]
pub fn validate(&self) -> Result<(), RasayanError> {
for (name, value) in [
("na_in", self.na_in),
("na_out", self.na_out),
("k_in", self.k_in),
("k_out", self.k_out),
("cl_in", self.cl_in),
("cl_out", self.cl_out),
] {
if value < 0.0 {
return Err(RasayanError::NegativeConcentration {
name: name.into(),
value,
});
}
}
Ok(())
}
#[must_use]
pub fn resting_potential(&self) -> f64 {
goldman(310.0, self, &DEFAULT_PERMEABILITY)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_nernst_potassium() {
let e = nernst(310.0, 1, 4.0, 155.0);
assert!(e < -80.0 && e > -110.0);
}
#[test]
fn test_resting_potential() {
let ions = IonicState::default();
let vm = ions.resting_potential();
assert!(vm < -50.0 && vm > -100.0);
}
#[test]
fn test_fick() {
let flux = fick_flux(1e-9, 10.0, 1e-6);
assert!(flux < 0.0);
}
#[test]
fn test_serde_roundtrip() {
let ions = IonicState::default();
let json = serde_json::to_string(&ions).unwrap();
let ions2: IonicState = serde_json::from_str(&json).unwrap();
assert_eq!(ions, ions2);
}
#[test]
fn test_permeability_serde_roundtrip() {
let perm = MembranePermeability::default();
let json = serde_json::to_string(&perm).unwrap();
let perm2: MembranePermeability = serde_json::from_str(&json).unwrap();
assert_eq!(perm, perm2);
}
#[test]
fn test_nernst_zero_concentrations() {
assert!(nernst(310.0, 1, 0.0, 155.0).abs() < f64::EPSILON);
assert!(nernst(310.0, 1, 4.0, 0.0).abs() < f64::EPSILON);
assert!(nernst(310.0, 0, 4.0, 155.0).abs() < f64::EPSILON);
}
#[test]
fn test_fick_zero_thickness() {
assert!(fick_flux(1e-9, 10.0, 0.0).abs() < f64::EPSILON);
}
#[test]
fn test_ionic_validate_valid() {
assert!(IonicState::default().validate().is_ok());
}
#[test]
fn test_ionic_validate_negative() {
let ions = IonicState {
na_in: -1.0,
..IonicState::default()
};
assert!(ions.validate().is_err());
}
#[test]
fn test_permeability_validate_valid() {
assert!(MembranePermeability::default().validate().is_ok());
}
#[test]
fn test_permeability_validate_negative() {
let perm = MembranePermeability {
p_na: -0.1,
..MembranePermeability::default()
};
assert!(perm.validate().is_err());
}
}