1use scirs2_core::ndarray::{Array1, Array2};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, PartialEq)]
12pub enum EnzymeMechanism {
13 MichaelisMenten {
15 km: f64, vmax: f64, },
18 OrderedSequential {
20 ka: f64, kb: f64, kp: f64, kq: f64, kcat: f64, },
26 RandomSequential {
28 ka: f64, kb: f64, kp: f64, kq: f64, kcat: f64, alpha: f64, },
35 PingPong {
37 ka: f64, kb: f64, kp: f64, kq: f64, kcat1: f64, kcat2: f64, },
44 Hill {
46 kd: f64, vmax: f64, n: f64, },
50 Allosteric {
52 km: f64, vmax: f64, ka_act: f64, ka_inh: f64, n_act: f64, n_inh: f64, },
59}
60
61#[derive(Debug, Clone)]
63pub struct EnzymeParameters {
64 pub mechanism: EnzymeMechanism,
66 pub temperature: f64,
68 pub ph: f64,
70 pub ionic_strength: f64,
72 pub temperature_params: Option<TemperatureParams>,
74 pub ph_params: Option<PhParams>,
76}
77
78#[derive(Debug, Clone)]
80pub struct TemperatureParams {
81 pub delta_h: f64,
83 pub delta_s: f64,
85 pub delta_cp: f64,
87 pub temp_ref: f64,
89}
90
91#[derive(Debug, Clone)]
93pub struct PhParams {
94 pub pka_values: Vec<f64>,
96 pub activity_coefficients: Vec<f64>,
98 pub ph_optimum: f64,
100}
101
102#[derive(Debug, Clone)]
104pub struct MetabolicPathway {
105 pub name: String,
107 pub enzymes: Vec<EnzymeDefinition>,
109 pub metabolites: Vec<String>,
111 pub stoichiometry_matrix: Array2<f64>,
113 pub regulations: Vec<Regulation>,
115 pub external_metabolites: HashMap<usize, f64>,
117}
118
119#[derive(Debug, Clone)]
121pub struct EnzymeDefinition {
122 pub name: String,
124 pub parameters: EnzymeParameters,
126 pub substrates: Vec<usize>,
128 pub products: Vec<usize>,
130 pub effectors: Vec<usize>,
132 pub enzyme_concentration: f64,
134}
135
136#[derive(Debug, Clone)]
138pub struct Regulation {
139 pub target_enzyme: usize,
141 pub effector_metabolite: usize,
143 pub regulation_type: RegulationType,
145 pub strength: f64,
147}
148
149#[derive(Debug, Clone, PartialEq)]
151pub enum RegulationType {
152 CompetitiveInhibition,
154 NonCompetitiveInhibition,
156 UncompetitiveInhibition,
158 AllostericActivation,
160 AllostericInhibition,
162 FeedbackInhibition,
164}
165
166#[derive(Debug, Clone)]
168pub struct PathwayAnalysis {
169 pub flux_control_coefficients: Array1<f64>,
171 pub concentration_control_coefficients: Array2<f64>,
173 pub elasticity_coefficients: Array2<f64>,
175 pub steady_state_fluxes: Array1<f64>,
177 pub steady_state_concentrations: Array1<f64>,
179}
180
181impl EnzymeParameters {
182 pub fn michaelis_menten(km: f64, vmax: f64) -> Self {
184 Self {
185 mechanism: EnzymeMechanism::MichaelisMenten { km, vmax },
186 temperature: 310.15, ph: 7.4,
188 ionic_strength: 0.15,
189 temperature_params: None,
190 ph_params: None,
191 }
192 }
193
194 pub fn hill(kd: f64, vmax: f64, n: f64) -> Self {
196 Self {
197 mechanism: EnzymeMechanism::Hill { kd, vmax, n },
198 temperature: 310.15,
199 ph: 7.4,
200 ionic_strength: 0.15,
201 temperature_params: None,
202 ph_params: None,
203 }
204 }
205
206 pub fn allosteric(
208 km: f64,
209 vmax: f64,
210 ka_act: f64,
211 ka_inh: f64,
212 n_act: f64,
213 n_inh: f64,
214 ) -> Self {
215 Self {
216 mechanism: EnzymeMechanism::Allosteric {
217 km,
218 vmax,
219 ka_act,
220 ka_inh,
221 n_act,
222 n_inh,
223 },
224 temperature: 310.15,
225 ph: 7.4,
226 ionic_strength: 0.15,
227 temperature_params: None,
228 ph_params: None,
229 }
230 }
231
232 pub fn calculate_rate(&self, concentrations: &[f64]) -> f64 {
234 let base_rate = match &self.mechanism {
235 EnzymeMechanism::MichaelisMenten { km, vmax } => {
236 if concentrations.is_empty() {
237 return 0.0;
238 }
239 let s = concentrations[0];
240 vmax * s / (km + s)
241 }
242 EnzymeMechanism::OrderedSequential {
243 ka,
244 kb,
245 kp,
246 kq,
247 kcat,
248 } => {
249 if concentrations.len() < 2 {
250 return 0.0;
251 }
252 let a = concentrations[0];
253 let b = concentrations[1];
254 let p = if concentrations.len() > 2 {
255 concentrations[2]
256 } else {
257 0.0
258 };
259 let q = if concentrations.len() > 3 {
260 concentrations[3]
261 } else {
262 0.0
263 };
264
265 let numerator = kcat * a * b;
267 let denominator =
268 ka * kb + kb * a + ka * b + a * b + (kp * a * q) / kq + (kq * b * p) / kp;
269 if denominator > 1e-12 {
270 numerator / denominator
271 } else {
272 0.0
273 }
274 }
275 EnzymeMechanism::RandomSequential {
276 ka,
277 kb,
278 kp,
279 kq,
280 kcat,
281 alpha,
282 } => {
283 if concentrations.len() < 2 {
284 return 0.0;
285 }
286 let a = concentrations[0];
287 let b = concentrations[1];
288 let p = if concentrations.len() > 2 {
289 concentrations[2]
290 } else {
291 0.0
292 };
293 let q = if concentrations.len() > 3 {
294 concentrations[3]
295 } else {
296 0.0
297 };
298
299 let numerator = kcat * a * b;
301 let denominator = ka * kb * (1.0 + alpha)
302 + kb * a
303 + ka * b
304 + a * b
305 + (kp * a * q) / (kq * alpha)
306 + (kq * b * p) / (kp * alpha);
307 if denominator > 1e-12 {
308 numerator / denominator
309 } else {
310 0.0
311 }
312 }
313 EnzymeMechanism::PingPong {
314 ka,
315 kb,
316 kp,
317 kq,
318 kcat1,
319 kcat2,
320 } => {
321 if concentrations.len() < 2 {
322 return 0.0;
323 }
324 let a = concentrations[0];
325 let b = concentrations[1];
326 let p = if concentrations.len() > 2 {
327 concentrations[2]
328 } else {
329 0.0
330 };
331 let q = if concentrations.len() > 3 {
332 concentrations[3]
333 } else {
334 0.0
335 };
336
337 let v1 = kcat1;
339 let v2 = kcat2;
340 let numerator = v1 * v2 * a * b;
341 let denominator = v2 * ka * b + v1 * kb * a + v1 * kp * q + v2 * kq * p;
342 if denominator > 1e-12 {
343 numerator / denominator
344 } else {
345 0.0
346 }
347 }
348 EnzymeMechanism::Hill { kd, vmax, n } => {
349 if concentrations.is_empty() {
350 return 0.0;
351 }
352 let s = concentrations[0];
353 let s_n = s.powf(*n);
354 let kd_n = kd.powf(*n);
355 vmax * s_n / (kd_n + s_n)
356 }
357 EnzymeMechanism::Allosteric {
358 km,
359 vmax,
360 ka_act,
361 ka_inh,
362 n_act,
363 n_inh,
364 } => {
365 if concentrations.is_empty() {
366 return 0.0;
367 }
368 let s = concentrations[0];
369 let activator = if concentrations.len() > 1 {
370 concentrations[1]
371 } else {
372 0.0
373 };
374 let inhibitor = if concentrations.len() > 2 {
375 concentrations[2]
376 } else {
377 0.0
378 };
379
380 let base_rate = vmax * s / (km + s);
382
383 let activation_factor = if activator > 0.0 {
385 (1.0 + (activator / ka_act).powf(*n_act))
386 / (1.0 + (activator / ka_act).powf(*n_act))
387 } else {
388 1.0
389 };
390
391 let inhibition_factor = if inhibitor > 0.0 {
392 1.0 / (1.0 + (inhibitor / ka_inh).powf(*n_inh))
393 } else {
394 1.0
395 };
396
397 base_rate * activation_factor * inhibition_factor
398 }
399 };
400
401 let temp_correction = self.calculate_temperature_correction();
403 let ph_correction = self.calculate_ph_correction();
404
405 base_rate * temp_correction * ph_correction
406 }
407
408 fn calculate_temperature_correction(&self) -> f64 {
410 if let Some(ref temp_params) = self.temperature_params {
411 let t = self.temperature;
412 let t_ref = temp_params.temp_ref;
413 let r = 8.314; let delta_h_corr = temp_params.delta_h + temp_params.delta_cp * (t - t_ref);
417 let delta_s_corr = temp_params.delta_s + temp_params.delta_cp * (t / t_ref).ln();
418
419 let delta_g = delta_h_corr - t * delta_s_corr;
420 (-delta_g / (r * t)).exp()
421 } else {
422 let ea = 50000.0; let r = 8.314;
425 let t_ref = 298.15;
426 (-ea / r * (1.0 / self.temperature - 1.0 / t_ref)).exp()
427 }
428 }
429
430 fn calculate_ph_correction(&self) -> f64 {
432 if let Some(ref ph_params) = self.ph_params {
433 let mut total_activity = 0.0;
435 let ph = self.ph;
436
437 for (i, &pka) in ph_params.pka_values.iter().enumerate() {
438 let alpha = 1.0 / (1.0 + 10.0_f64.powf(pka - ph));
439 total_activity += alpha * ph_params.activity_coefficients.get(i).unwrap_or(&1.0);
440 }
441
442 total_activity / ph_params.pka_values.len() as f64
443 } else {
444 let ph_opt = 7.4;
446 let ph_width = 2.0;
447 let delta_ph = (self.ph - ph_opt) / ph_width;
448 (-0.5 * delta_ph * delta_ph).exp()
449 }
450 }
451}
452
453impl MetabolicPathway {
454 pub fn new(_name: String, num_metabolites: usize, numenzymes: usize) -> Self {
456 Self {
457 name: _name,
458 enzymes: Vec::new(),
459 metabolites: (0..num_metabolites).map(|i| format!("M{i}")).collect(),
460 stoichiometry_matrix: Array2::zeros((numenzymes, num_metabolites)),
461 regulations: Vec::new(),
462 external_metabolites: HashMap::new(),
463 }
464 }
465
466 pub fn add_enzyme(&mut self, enzyme: EnzymeDefinition) {
468 self.enzymes.push(enzyme);
469 }
470
471 pub fn add_regulation(&mut self, regulation: Regulation) {
473 self.regulations.push(regulation);
474 }
475
476 pub fn set_external_metabolite(&mut self, _metaboliteidx: usize, concentration: f64) {
478 self.external_metabolites
479 .insert(_metaboliteidx, concentration);
480 }
481
482 pub fn calculate_reaction_rates(&self, concentrations: &Array1<f64>) -> Array1<f64> {
484 let mut rates = Array1::zeros(self.enzymes.len());
485
486 for (i, enzyme) in self.enzymes.iter().enumerate() {
487 let substrate_concentrations: Vec<f64> = enzyme
489 .substrates
490 .iter()
491 .map(|&idx| concentrations.get(idx).copied().unwrap_or(0.0))
492 .collect();
493
494 let effector_concentrations: Vec<f64> = enzyme
496 .effectors
497 .iter()
498 .map(|&idx| concentrations.get(idx).copied().unwrap_or(0.0))
499 .collect();
500
501 let mut all_concentrations = substrate_concentrations;
503 all_concentrations.extend(effector_concentrations);
504
505 let base_rate = enzyme.parameters.calculate_rate(&all_concentrations);
507
508 let regulated_rate = self.apply_regulations(i, base_rate, concentrations);
510
511 rates[i] = regulated_rate * enzyme.enzyme_concentration * 1e-9; }
513
514 rates
515 }
516
517 fn apply_regulations(
519 &self,
520 enzyme_idx: usize,
521 base_rate: f64,
522 concentrations: &Array1<f64>,
523 ) -> f64 {
524 let mut modified_rate = base_rate;
525
526 for regulation in &self.regulations {
527 if regulation.target_enzyme == enzyme_idx {
528 let effector_conc = concentrations
529 .get(regulation.effector_metabolite)
530 .copied()
531 .unwrap_or(0.0);
532
533 let regulation_factor = match regulation.regulation_type {
534 RegulationType::CompetitiveInhibition => {
535 1.0 / (1.0 + effector_conc / regulation.strength)
536 }
537 RegulationType::NonCompetitiveInhibition => {
538 1.0 / (1.0 + effector_conc / regulation.strength)
539 }
540 RegulationType::UncompetitiveInhibition => {
541 1.0 / (1.0 + effector_conc / regulation.strength)
542 }
543 RegulationType::AllostericActivation => {
544 1.0 + effector_conc / regulation.strength
545 }
546 RegulationType::AllostericInhibition => {
547 1.0 / (1.0 + (effector_conc / regulation.strength).powf(2.0))
548 }
549 RegulationType::FeedbackInhibition => {
550 1.0 / (1.0 + (effector_conc / regulation.strength).powf(4.0))
551 }
552 };
553
554 modified_rate *= regulation_factor;
555 }
556 }
557
558 modified_rate
559 }
560
561 pub fn calculate_derivatives(&self, concentrations: &Array1<f64>) -> Array1<f64> {
563 let reaction_rates = self.calculate_reaction_rates(concentrations);
564 let mut derivatives = Array1::zeros(concentrations.len());
565
566 for (reaction_idx, &rate) in reaction_rates.iter().enumerate() {
568 for metabolite_idx in 0..derivatives.len() {
569 if let Some(&stoich) = self
570 .stoichiometry_matrix
571 .get((reaction_idx, metabolite_idx))
572 {
573 derivatives[metabolite_idx] += stoich * rate;
574 }
575 }
576 }
577
578 for &metabolite_idx in self.external_metabolites.keys() {
580 if metabolite_idx < derivatives.len() {
581 derivatives[metabolite_idx] = 0.0;
582 }
583 }
584
585 derivatives
586 }
587
588 pub fn control_analysis(&self, _steady_stateconcentrations: &Array1<f64>) -> PathwayAnalysis {
590 let num_enzymes = self.enzymes.len();
591 let num_metabolites = _steady_stateconcentrations.len();
592
593 let flux_control_coefficients =
595 self.calculate_flux_control_coefficients(_steady_stateconcentrations);
596
597 let concentration_control_coefficients = Array2::zeros((num_enzymes, num_metabolites));
599
600 let elasticity_coefficients =
602 self.calculate_elasticity_coefficients(_steady_stateconcentrations);
603
604 let steady_state_fluxes = self.calculate_reaction_rates(_steady_stateconcentrations);
606
607 PathwayAnalysis {
608 flux_control_coefficients,
609 concentration_control_coefficients,
610 elasticity_coefficients,
611 steady_state_fluxes,
612 steady_state_concentrations: _steady_stateconcentrations.clone(),
613 }
614 }
615
616 fn calculate_flux_control_coefficients(&self, concentrations: &Array1<f64>) -> Array1<f64> {
618 let num_enzymes = self.enzymes.len();
619 let mut flux_control_coefficients = Array1::zeros(num_enzymes);
620
621 let base_flux = self.calculate_reaction_rates(concentrations).sum();
622 let perturbation = 0.01; for i in 0..num_enzymes {
625 let mut perturbed_pathway = self.clone();
627 perturbed_pathway.enzymes[i].enzyme_concentration *= 1.0 + perturbation;
628
629 let perturbed_flux = perturbed_pathway
630 .calculate_reaction_rates(concentrations)
631 .sum();
632
633 if base_flux > 1e-12 {
635 flux_control_coefficients[i] =
636 ((perturbed_flux - base_flux) / base_flux) / perturbation;
637 }
638 }
639
640 flux_control_coefficients
641 }
642
643 fn calculate_elasticity_coefficients(&self, concentrations: &Array1<f64>) -> Array2<f64> {
645 let num_enzymes = self.enzymes.len();
646 let num_metabolites = concentrations.len();
647 let mut elasticity_coefficients = Array2::zeros((num_enzymes, num_metabolites));
648
649 let base_rates = self.calculate_reaction_rates(concentrations);
650 let perturbation = 0.01; for enzyme_idx in 0..num_enzymes {
653 for metabolite_idx in 0..num_metabolites {
654 if concentrations[metabolite_idx] > 1e-12 {
655 let mut perturbed_concentrations = concentrations.clone();
656 perturbed_concentrations[metabolite_idx] *= 1.0 + perturbation;
657
658 let perturbed_rates = self.calculate_reaction_rates(&perturbed_concentrations);
659
660 if base_rates[enzyme_idx] > 1e-12 {
662 elasticity_coefficients[(enzyme_idx, metabolite_idx)] =
663 ((perturbed_rates[enzyme_idx] - base_rates[enzyme_idx])
664 / base_rates[enzyme_idx])
665 / perturbation;
666 }
667 }
668 }
669 }
670
671 elasticity_coefficients
672 }
673}
674
675pub mod pathways {
677 use super::*;
678 use scirs2_core::ndarray::arr2;
679
680 pub fn simple_glycolysis() -> MetabolicPathway {
682 let mut pathway = MetabolicPathway::new("Simple Glycolysis".to_string(), 6, 3);
683
684 pathway.metabolites = vec![
686 "Glucose".to_string(),
687 "G6P".to_string(),
688 "F6P".to_string(),
689 "FBP".to_string(),
690 "PEP".to_string(),
691 "Pyruvate".to_string(),
692 ];
693
694 pathway.add_enzyme(EnzymeDefinition {
696 name: "Hexokinase".to_string(),
697 parameters: EnzymeParameters::michaelis_menten(0.1, 100.0), substrates: vec![0], products: vec![1], effectors: vec![],
701 enzyme_concentration: 50.0, });
703 pathway.add_enzyme(EnzymeDefinition {
705 name: "Phosphofructokinase".to_string(),
706 parameters: EnzymeParameters::allosteric(
707 0.3, 200.0, 0.1, 2.0, 2.0, 4.0, ),
714 substrates: vec![2], products: vec![3], effectors: vec![], enzyme_concentration: 30.0,
718 });
719 pathway.add_enzyme(EnzymeDefinition {
721 name: "Pyruvate Kinase".to_string(),
722 parameters: EnzymeParameters::hill(0.5, 300.0, 2.0), substrates: vec![4], products: vec![5], effectors: vec![],
726 enzyme_concentration: 100.0,
727 });
728 pathway.stoichiometry_matrix = arr2(&[
730 [-1.0, 1.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, -1.0, 1.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, -1.0, 1.0], ]);
734 pathway.add_regulation(Regulation {
736 target_enzyme: 0,
737 effector_metabolite: 1,
738 regulation_type: RegulationType::FeedbackInhibition,
739 strength: 1.0, });
741
742 pathway.set_external_metabolite(0, 5.0); pathway.set_external_metabolite(5, 0.1); pathway
747 }
748
749 pub fn tca_cycle() -> MetabolicPathway {
751 let mut pathway = MetabolicPathway::new("TCA Cycle".to_string(), 8, 8);
752
753 pathway.metabolites = vec![
756 "Acetyl-CoA".to_string(),
757 "Citrate".to_string(),
758 "Isocitrate".to_string(),
759 "α-Ketoglutarate".to_string(),
760 "Succinyl-CoA".to_string(),
761 "Succinate".to_string(),
762 "Fumarate".to_string(),
763 "Malate".to_string(),
764 ];
765
766 let enzyme_params = [
768 ("Citrate Synthase", 0.1, 50.0),
769 ("Aconitase", 0.3, 80.0),
770 ("Isocitrate Dehydrogenase", 0.2, 60.0),
771 ("α-Ketoglutarate Dehydrogenase", 0.4, 40.0),
772 ("Succinyl-CoA Synthetase", 0.1, 70.0),
773 ("Succinate Dehydrogenase", 0.5, 30.0),
774 ("Fumarase", 0.2, 100.0),
775 ("Malate Dehydrogenase", 0.3, 90.0),
776 ];
777
778 for (i, (name, km, vmax)) in enzyme_params.iter().enumerate() {
779 pathway.add_enzyme(EnzymeDefinition {
780 name: name.to_string(),
781 parameters: EnzymeParameters::michaelis_menten(*km, *vmax),
782 substrates: vec![i],
783 products: vec![(i + 1) % 8],
784 effectors: vec![],
785 enzyme_concentration: 50.0,
786 });
787 }
788
789 let mut stoich = Array2::zeros((8, 8));
791 for i in 0..8 {
792 stoich[[i, i]] = -1.0; stoich[[i, (i + 1) % 8]] = 1.0; }
795 pathway.stoichiometry_matrix = stoich;
796
797 pathway
798 }
799
800 pub fn purine_biosynthesis() -> MetabolicPathway {
802 let mut pathway = MetabolicPathway::new("Purine Biosynthesis".to_string(), 10, 10);
803
804 pathway.metabolites = vec![
806 "PRPP".to_string(), "5-Phosphoribosylamine".to_string(), "GAR".to_string(), "FGAR".to_string(), "FGAM".to_string(), "AIR".to_string(), "CAIR".to_string(), "SAICAR".to_string(), "AICAR".to_string(), "IMP".to_string(), ];
817
818 let enzymes = [
820 (
821 "PRPP Amidotransferase",
822 EnzymeParameters::michaelis_menten(0.1, 50.0),
823 ),
824 (
825 "GAR Synthetase",
826 EnzymeParameters::michaelis_menten(0.2, 60.0),
827 ),
828 (
829 "GAR Transformylase",
830 EnzymeParameters::michaelis_menten(0.15, 40.0),
831 ),
832 (
833 "FGAM Synthetase",
834 EnzymeParameters::michaelis_menten(0.3, 30.0),
835 ),
836 (
837 "AIR Synthetase",
838 EnzymeParameters::michaelis_menten(0.25, 45.0),
839 ),
840 (
841 "AIR Carboxylase",
842 EnzymeParameters::michaelis_menten(0.1, 35.0),
843 ),
844 (
845 "SAICAR Synthetase",
846 EnzymeParameters::michaelis_menten(0.2, 55.0),
847 ),
848 (
849 "SAICAR Lyase",
850 EnzymeParameters::michaelis_menten(0.4, 70.0),
851 ),
852 (
853 "AICAR Transformylase",
854 EnzymeParameters::michaelis_menten(0.3, 50.0),
855 ),
856 ("IMP Synthase", EnzymeParameters::hill(0.2, 40.0, 2.0)),
857 ];
858
859 for (i, (name, params)) in enzymes.iter().enumerate() {
860 pathway.add_enzyme(EnzymeDefinition {
861 name: name.to_string(),
862 parameters: params.clone(),
863 substrates: vec![i],
864 products: vec![i + 1],
865 effectors: vec![],
866 enzyme_concentration: 25.0,
867 });
868 }
869
870 let mut stoich = Array2::zeros((10, 10));
872 for i in 0..9 {
873 stoich[[i, i]] = -1.0; stoich[[i, i + 1]] = 1.0; }
876 pathway.stoichiometry_matrix = stoich;
877
878 pathway.add_regulation(Regulation {
880 target_enzyme: 0,
881 effector_metabolite: 9,
882 regulation_type: RegulationType::FeedbackInhibition,
883 strength: 0.5,
884 });
885
886 pathway
887 }
888}
889
890#[cfg(test)]
891mod tests {
892 use crate::ode::{enzyme_kinetics::pathways, EnzymeParameters};
893 use approx::assert_abs_diff_eq;
894 use scirs2_core::ndarray::Array1;
895
896 #[test]
897 fn test_michaelis_menten_kinetics() {
898 let mut params = EnzymeParameters::michaelis_menten(1.0, 100.0);
899 params.temperature = 298.15; let rate_at_km = params.calculate_rate(&[1.0]);
903 assert_abs_diff_eq!(rate_at_km, 50.0, epsilon = 1e-10);
904
905 let rate_high_s = params.calculate_rate(&[100.0]);
907 assert!(rate_high_s > 99.0);
908 }
909
910 #[test]
911 fn test_hill_kinetics() {
912 let mut params = EnzymeParameters::hill(1.0, 100.0, 2.0);
913 params.temperature = 298.15; let rate_at_kd = params.calculate_rate(&[1.0]);
917 assert_abs_diff_eq!(rate_at_kd, 50.0, epsilon = 1e-10);
918
919 let rate_low = params.calculate_rate(&[0.1]);
921 let rate_high = params.calculate_rate(&[10.0]);
922 assert!(rate_high > rate_low);
923 }
924
925 #[test]
926 fn test_simple_glycolysis_pathway() {
927 let pathway = pathways::simple_glycolysis();
928
929 assert_eq!(pathway.enzymes.len(), 3);
930 assert_eq!(pathway.metabolites.len(), 6);
931 assert_eq!(pathway.regulations.len(), 1);
932
933 let concentrations = Array1::from_vec(vec![5.0, 0.1, 0.1, 0.1, 0.1, 0.1]);
935 let rates = pathway.calculate_reaction_rates(&concentrations);
936
937 for &rate in rates.iter() {
939 assert!(rate >= 0.0);
940 }
941 }
942
943 #[test]
944 fn test_tca_cycle_pathway() {
945 let pathway = pathways::tca_cycle();
946
947 assert_eq!(pathway.enzymes.len(), 8);
948 assert_eq!(pathway.metabolites.len(), 8);
949
950 let concentrations = Array1::from_vec(vec![1.0; 8]);
952 let rates = pathway.calculate_reaction_rates(&concentrations);
953
954 for &rate in rates.iter() {
956 assert!(rate >= 0.0);
957 }
958 }
959
960 #[test]
961 fn test_allosteric_regulation() {
962 let params = EnzymeParameters::allosteric(
963 1.0, 100.0, 0.5, 2.0, 2.0, 2.0, );
970
971 let rate_base = params.calculate_rate(&[1.0]);
973
974 let rate_activated = params.calculate_rate(&[1.0, 0.5]);
976
977 let rate_inhibited = params.calculate_rate(&[1.0, 0.0, 2.0]);
979
980 assert!(rate_activated >= rate_base);
981 assert!(rate_inhibited <= rate_base);
982 }
983
984 #[test]
985 fn test_temperature_effects() {
986 let mut params = EnzymeParameters::michaelis_menten(1.0, 100.0);
987
988 params.temperature = 298.15; let rate_25c = params.calculate_rate(&[1.0]);
991
992 params.temperature = 310.15; let rate_37c = params.calculate_rate(&[1.0]);
994
995 assert!(rate_37c > rate_25c);
997 }
998
999 #[test]
1000 fn test_pathway_derivatives() {
1001 let pathway = pathways::simple_glycolysis();
1002 let concentrations = Array1::from_vec(vec![5.0, 0.1, 0.1, 0.1, 0.1, 0.1]);
1003
1004 let derivatives = pathway.calculate_derivatives(&concentrations);
1005
1006 assert_abs_diff_eq!(derivatives[0], 0.0, epsilon = 1e-10); assert_abs_diff_eq!(derivatives[5], 0.0, epsilon = 1e-10); }
1010
1011 #[test]
1012 fn test_control_analysis() {
1013 let pathway = pathways::simple_glycolysis();
1014 let concentrations = Array1::from_vec(vec![5.0, 1.0, 0.5, 0.3, 0.2, 0.1]);
1015
1016 let analysis = pathway.control_analysis(&concentrations);
1017
1018 let sum_fcc = analysis.flux_control_coefficients.sum();
1020 assert_abs_diff_eq!(sum_fcc, 1.0, epsilon = 0.1);
1021 }
1022}