use serde::{Deserialize, Serialize};
use crate::error::RasayanError;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReceptorState {
pub surface: f64,
pub active: f64,
pub desensitized: f64,
pub internalized: f64,
}
impl Default for ReceptorState {
fn default() -> Self {
Self {
surface: 0.9,
active: 0.05,
desensitized: 0.03,
internalized: 0.02,
}
}
}
impl ReceptorState {
#[must_use = "validation errors should be handled"]
pub fn validate(&self) -> Result<(), RasayanError> {
for (name, value) in [
("surface", self.surface),
("active", self.active),
("desensitized", self.desensitized),
("internalized", self.internalized),
] {
if value < 0.0 {
return Err(RasayanError::NegativeConcentration {
name: name.into(),
value,
});
}
}
Ok(())
}
#[must_use]
pub fn total(&self) -> f64 {
self.surface + self.active + self.desensitized + self.internalized
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReceptorConfig {
pub binding_rate: f64,
pub dissociation_rate: f64,
pub grk_rate: f64,
pub internalization_rate: f64,
pub recycling_rate: f64,
pub degradation_rate: f64,
pub synthesis_rate: f64,
}
impl Default for ReceptorConfig {
fn default() -> Self {
Self {
binding_rate: 0.5,
dissociation_rate: 0.2,
grk_rate: 0.3,
internalization_rate: 0.1,
recycling_rate: 0.05,
degradation_rate: 0.02,
synthesis_rate: 0.01,
}
}
}
impl ReceptorConfig {
#[must_use = "validation errors should be handled"]
pub fn validate(&self) -> Result<(), RasayanError> {
for (name, value) in [
("binding_rate", self.binding_rate),
("dissociation_rate", self.dissociation_rate),
("grk_rate", self.grk_rate),
("internalization_rate", self.internalization_rate),
("recycling_rate", self.recycling_rate),
("degradation_rate", self.degradation_rate),
("synthesis_rate", self.synthesis_rate),
] {
if value < 0.0 {
return Err(RasayanError::InvalidParameter {
name: name.into(),
value,
reason: "must be non-negative".into(),
});
}
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[must_use]
pub struct ReceptorFlux {
pub signaling_output: f64,
pub internalization: f64,
pub recycling: f64,
}
impl ReceptorState {
#[must_use = "flux contains receptor signaling output"]
pub fn tick(&mut self, config: &ReceptorConfig, ligand: f64, dt: f64) -> ReceptorFlux {
tracing::trace!(dt, ligand, active = self.active, "receptor_tick");
let v_bind = config.binding_rate * self.surface * ligand.max(0.0);
let v_dissoc = config.dissociation_rate * self.active;
let v_grk = config.grk_rate * self.active;
let v_intern = config.internalization_rate * self.desensitized;
let v_recycle = config.recycling_rate * self.internalized;
let v_degrade = config.degradation_rate * self.internalized;
let v_synth = config.synthesis_rate;
self.surface += (v_dissoc - v_bind + v_recycle + v_synth) * dt;
self.active += (v_bind - v_dissoc - v_grk) * dt;
self.desensitized += (v_grk - v_intern) * dt;
self.internalized += (v_intern - v_recycle - v_degrade) * dt;
self.surface = self.surface.max(0.0);
self.active = self.active.max(0.0);
self.desensitized = self.desensitized.max(0.0);
self.internalized = self.internalized.max(0.0);
ReceptorFlux {
signaling_output: self.active,
internalization: v_intern,
recycling: v_recycle,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_valid() {
assert!(ReceptorState::default().validate().is_ok());
assert!(ReceptorConfig::default().validate().is_ok());
}
#[test]
fn test_ligand_activates() {
let mut state = ReceptorState::default();
let config = ReceptorConfig::default();
let flux = state.tick(&config, 1.0, 0.1);
assert!(flux.signaling_output > 0.0);
}
#[test]
fn test_prolonged_exposure_desensitizes() {
let mut state = ReceptorState::default();
let config = ReceptorConfig::default();
let initial_surface = state.surface;
for _ in 0..200 {
let _ = state.tick(&config, 1.0, 0.1);
}
assert!(
state.surface < initial_surface,
"Prolonged agonist should reduce surface receptors"
);
assert!(
state.internalized > ReceptorState::default().internalized,
"Internalized pool should grow"
);
}
#[test]
fn test_recovery_after_removal() {
let mut state = ReceptorState::default();
let config = ReceptorConfig::default();
for _ in 0..100 {
let _ = state.tick(&config, 1.0, 0.1);
}
let post_exposure_surface = state.surface;
for _ in 0..500 {
let _ = state.tick(&config, 0.0, 0.1);
}
assert!(
state.surface > post_exposure_surface,
"Surface receptors should recover"
);
}
#[test]
fn test_bounded() {
let mut state = ReceptorState::default();
let config = ReceptorConfig::default();
for _ in 0..1000 {
let _ = state.tick(&config, 0.5, 0.01);
}
assert!(state.validate().is_ok());
}
#[test]
fn test_serde_roundtrip() {
let state = ReceptorState::default();
let json = serde_json::to_string(&state).unwrap();
let state2: ReceptorState = serde_json::from_str(&json).unwrap();
assert_eq!(state, state2);
}
}