scirs2_integrate/ode/
enzyme_kinetics.rs

1//! Advanced enzyme kinetics and metabolic pathway modeling
2//!
3//! This module provides sophisticated models for enzyme kinetics including
4//! multi-substrate mechanisms, allosteric regulation, and metabolic pathway
5//! network simulation.
6
7use scirs2_core::ndarray::{Array1, Array2};
8use std::collections::HashMap;
9
10/// Types of enzyme mechanisms
11#[derive(Debug, Clone, PartialEq)]
12pub enum EnzymeMechanism {
13    /// Michaelis-Menten single substrate mechanism
14    MichaelisMenten {
15        km: f64,   // Michaelis constant
16        vmax: f64, // Maximum velocity
17    },
18    /// Bi-substrate ordered sequential mechanism (A binds first, then B)
19    OrderedSequential {
20        ka: f64,   // Binding constant for substrate A
21        kb: f64,   // Binding constant for substrate B
22        kp: f64,   // Product release constant for P
23        kq: f64,   // Product release constant for Q
24        kcat: f64, // Catalytic rate constant
25    },
26    /// Bi-substrate random sequential mechanism (A and B can bind in any order)
27    RandomSequential {
28        ka: f64,    // Binding constant for substrate A
29        kb: f64,    // Binding constant for substrate B
30        kp: f64,    // Product release constant for P
31        kq: f64,    // Product release constant for Q
32        kcat: f64,  // Catalytic rate constant
33        alpha: f64, // Interaction parameter
34    },
35    /// Ping-pong mechanism (substrate A binds, product P released, then B binds)
36    PingPong {
37        ka: f64,    // Binding constant for substrate A
38        kb: f64,    // Binding constant for substrate B
39        kp: f64,    // Product release constant for P
40        kq: f64,    // Product release constant for Q
41        kcat1: f64, // First catalytic step
42        kcat2: f64, // Second catalytic step
43    },
44    /// Hill equation for cooperative binding
45    Hill {
46        kd: f64,   // Dissociation constant
47        vmax: f64, // Maximum velocity
48        n: f64,    // Hill coefficient (cooperativity)
49    },
50    /// Allosteric enzyme with activators and inhibitors
51    Allosteric {
52        km: f64,     // Michaelis constant for substrate
53        vmax: f64,   // Maximum velocity
54        ka_act: f64, // Activation constant for activator
55        ka_inh: f64, // Inhibition constant for inhibitor
56        n_act: f64,  // Cooperativity for activator
57        n_inh: f64,  // Cooperativity for inhibitor
58    },
59}
60
61/// Enzyme kinetic parameters
62#[derive(Debug, Clone)]
63pub struct EnzymeParameters {
64    /// Enzyme mechanism type
65    pub mechanism: EnzymeMechanism,
66    /// Temperature (K)
67    pub temperature: f64,
68    /// pH
69    pub ph: f64,
70    /// Ionic strength (M)
71    pub ionic_strength: f64,
72    /// Temperature dependence parameters
73    pub temperature_params: Option<TemperatureParams>,
74    /// pH dependence parameters
75    pub ph_params: Option<PhParams>,
76}
77
78/// Temperature dependence parameters
79#[derive(Debug, Clone)]
80pub struct TemperatureParams {
81    /// Enthalpy of activation (kJ/mol)
82    pub delta_h: f64,
83    /// Entropy of activation (J/(mol·K))
84    pub delta_s: f64,
85    /// Heat capacity change (J/(mol·K))
86    pub delta_cp: f64,
87    /// Reference temperature (K)
88    pub temp_ref: f64,
89}
90
91/// pH dependence parameters
92#[derive(Debug, Clone)]
93pub struct PhParams {
94    /// pKa values for ionizable groups
95    pub pka_values: Vec<f64>,
96    /// Activity coefficients for different ionization states
97    pub activity_coefficients: Vec<f64>,
98    /// Optimal pH
99    pub ph_optimum: f64,
100}
101
102/// Metabolic pathway definition
103#[derive(Debug, Clone)]
104pub struct MetabolicPathway {
105    /// Pathway name
106    pub name: String,
107    /// Enzyme definitions
108    pub enzymes: Vec<EnzymeDefinition>,
109    /// Metabolite names
110    pub metabolites: Vec<String>,
111    /// Stoichiometric matrix (reactions × metabolites)
112    pub stoichiometry_matrix: Array2<f64>,
113    /// Regulatory relationships
114    pub regulations: Vec<Regulation>,
115    /// External metabolite concentrations (fixed)
116    pub external_metabolites: HashMap<usize, f64>,
117}
118
119/// Enzyme definition within a pathway
120#[derive(Debug, Clone)]
121pub struct EnzymeDefinition {
122    /// Enzyme name
123    pub name: String,
124    /// Kinetic parameters
125    pub parameters: EnzymeParameters,
126    /// Substrate indices
127    pub substrates: Vec<usize>,
128    /// Product indices
129    pub products: Vec<usize>,
130    /// Effector indices (activators/inhibitors)
131    pub effectors: Vec<usize>,
132    /// Enzyme concentration (nM)
133    pub enzyme_concentration: f64,
134}
135
136/// Regulatory relationship
137#[derive(Debug, Clone)]
138pub struct Regulation {
139    /// Target enzyme index
140    pub target_enzyme: usize,
141    /// Effector metabolite index
142    pub effector_metabolite: usize,
143    /// Type of regulation
144    pub regulation_type: RegulationType,
145    /// Regulation strength parameter
146    pub strength: f64,
147}
148
149/// Types of metabolic regulation
150#[derive(Debug, Clone, PartialEq)]
151pub enum RegulationType {
152    /// Competitive inhibition
153    CompetitiveInhibition,
154    /// Non-competitive inhibition
155    NonCompetitiveInhibition,
156    /// Uncompetitive inhibition
157    UncompetitiveInhibition,
158    /// Allosteric activation
159    AllostericActivation,
160    /// Allosteric inhibition
161    AllostericInhibition,
162    /// Feedback inhibition
163    FeedbackInhibition,
164}
165
166/// Pathway analysis results
167#[derive(Debug, Clone)]
168pub struct PathwayAnalysis {
169    /// Flux control coefficients
170    pub flux_control_coefficients: Array1<f64>,
171    /// Concentration control coefficients
172    pub concentration_control_coefficients: Array2<f64>,
173    /// Elasticity coefficients
174    pub elasticity_coefficients: Array2<f64>,
175    /// Steady-state fluxes
176    pub steady_state_fluxes: Array1<f64>,
177    /// Steady-state concentrations
178    pub steady_state_concentrations: Array1<f64>,
179}
180
181impl EnzymeParameters {
182    /// Create Michaelis-Menten enzyme parameters
183    pub fn michaelis_menten(km: f64, vmax: f64) -> Self {
184        Self {
185            mechanism: EnzymeMechanism::MichaelisMenten { km, vmax },
186            temperature: 310.15, // 37°C
187            ph: 7.4,
188            ionic_strength: 0.15,
189            temperature_params: None,
190            ph_params: None,
191        }
192    }
193
194    /// Create Hill equation enzyme parameters
195    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    /// Create allosteric enzyme parameters
207    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    /// Calculate reaction rate for this enzyme
233    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                // Ordered sequential rate equation
266                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                // Random sequential rate equation with interaction parameter
300                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                // Ping-pong rate equation
338                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                // Base Michaelis-Menten rate
381                let base_rate = vmax * s / (km + s);
382
383                // Allosteric modulation
384                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        // Apply temperature and pH corrections
402        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    /// Calculate temperature correction factor
409    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; // Gas constant J/(mol·K)
414
415            // van't Hoff equation with heat capacity correction
416            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            // Simple Arrhenius approximation if no detailed parameters
423            let ea = 50000.0; // Default activation energy 50 kJ/mol
424            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    /// Calculate pH correction factor
431    fn calculate_ph_correction(&self) -> f64 {
432        if let Some(ref ph_params) = self.ph_params {
433            // Henderson-Hasselbalch equation for multiple ionizable groups
434            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            // Simple pH bell curve if no detailed parameters
445            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    /// Create a new empty metabolic pathway
455    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    /// Add an enzyme to the pathway
467    pub fn add_enzyme(&mut self, enzyme: EnzymeDefinition) {
468        self.enzymes.push(enzyme);
469    }
470
471    /// Add a regulatory relationship
472    pub fn add_regulation(&mut self, regulation: Regulation) {
473        self.regulations.push(regulation);
474    }
475
476    /// Set external metabolite concentration
477    pub fn set_external_metabolite(&mut self, _metaboliteidx: usize, concentration: f64) {
478        self.external_metabolites
479            .insert(_metaboliteidx, concentration);
480    }
481
482    /// Calculate reaction rates for all enzymes
483    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            // Get substrate _concentrations
488            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            // Get effector _concentrations for allosteric enzymes
495            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            // Combine substrate and effector _concentrations
502            let mut all_concentrations = substrate_concentrations;
503            all_concentrations.extend(effector_concentrations);
504
505            // Calculate base rate
506            let base_rate = enzyme.parameters.calculate_rate(&all_concentrations);
507
508            // Apply regulatory effects
509            let regulated_rate = self.apply_regulations(i, base_rate, concentrations);
510
511            rates[i] = regulated_rate * enzyme.enzyme_concentration * 1e-9; // Convert nM to M
512        }
513
514        rates
515    }
516
517    /// Apply regulatory effects to an enzyme
518    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    /// Calculate concentration time derivatives
562    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        // Apply stoichiometry matrix
567        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        // External metabolites have zero derivatives
579        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    /// Perform metabolic control analysis
589    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        // Calculate flux control coefficients
594        let flux_control_coefficients =
595            self.calculate_flux_control_coefficients(_steady_stateconcentrations);
596
597        // Calculate concentration control coefficients
598        let concentration_control_coefficients = Array2::zeros((num_enzymes, num_metabolites));
599
600        // Calculate elasticity coefficients
601        let elasticity_coefficients =
602            self.calculate_elasticity_coefficients(_steady_stateconcentrations);
603
604        // Calculate steady-state fluxes
605        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    /// Calculate flux control coefficients
617    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; // 1% perturbation
623
624        for i in 0..num_enzymes {
625            // Perturb enzyme concentration
626            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            // Calculate control coefficient
634            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    /// Calculate elasticity coefficients
644    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; // 1% perturbation
651
652        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                    // Calculate elasticity coefficient
661                    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
675/// Factory functions for common metabolic pathways
676pub mod pathways {
677    use super::*;
678    use scirs2_core::ndarray::arr2;
679
680    /// Create a simple glycolysis pathway (simplified)
681    pub fn simple_glycolysis() -> MetabolicPathway {
682        let mut pathway = MetabolicPathway::new("Simple Glycolysis".to_string(), 6, 3);
683
684        // Metabolites: Glucose, G6P, F6P, FBP, PEP, Pyruvate
685        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        // Enzyme 1: Hexokinase (Glucose -> G6P)
695        pathway.add_enzyme(EnzymeDefinition {
696            name: "Hexokinase".to_string(),
697            parameters: EnzymeParameters::michaelis_menten(0.1, 100.0), // Km = 0.1 mM, Vmax = 100 μM/s
698            substrates: vec![0],                                        // Glucose
699            products: vec![1],                                          // G6P
700            effectors: vec![],
701            enzyme_concentration: 50.0, // 50 nM
702        });
703        // Enzyme 2: Phosphofructokinase (F6P -> FBP) - allosteric
704        pathway.add_enzyme(EnzymeDefinition {
705            name: "Phosphofructokinase".to_string(),
706            parameters: EnzymeParameters::allosteric(
707                0.3,   // Km
708                200.0, // Vmax
709                0.1,   // Ka_act (activation by AMP)
710                2.0,   // Ka_inh (inhibition by ATP)
711                2.0,   // n_act
712                4.0,   // n_inh
713            ),
714            substrates: vec![2], // F6P
715            products: vec![3],   // FBP
716            effectors: vec![],   // AMP, ATP (would be separate metabolites)
717            enzyme_concentration: 30.0,
718        });
719        // Enzyme 3: Pyruvate kinase (PEP -> Pyruvate)
720        pathway.add_enzyme(EnzymeDefinition {
721            name: "Pyruvate Kinase".to_string(),
722            parameters: EnzymeParameters::hill(0.5, 300.0, 2.0), // Kd = 0.5 mM, Vmax = 300 μM/s, n = 2
723            substrates: vec![4],                                 // PEP
724            products: vec![5],                                   // Pyruvate
725            effectors: vec![],
726            enzyme_concentration: 100.0,
727        });
728        // Set stoichiometry matrix (enzymes × metabolites)
729        pathway.stoichiometry_matrix = arr2(&[
730            [-1.0, 1.0, 0.0, 0.0, 0.0, 0.0], // Hexokinase: Glucose -> G6P
731            [0.0, 0.0, -1.0, 1.0, 0.0, 0.0], // PFK: F6P -> FBP
732            [0.0, 0.0, 0.0, 0.0, -1.0, 1.0], // Pyruvate kinase: PEP -> Pyruvate
733        ]);
734        // Add feedback inhibition: G6P inhibits Hexokinase
735        pathway.add_regulation(Regulation {
736            target_enzyme: 0,
737            effector_metabolite: 1,
738            regulation_type: RegulationType::FeedbackInhibition,
739            strength: 1.0, // Ki = 1.0 mM
740        });
741
742        // Set external metabolites (glucose and pyruvate)
743        pathway.set_external_metabolite(0, 5.0); // 5 mM glucose
744        pathway.set_external_metabolite(5, 0.1); // 0.1 mM pyruvate
745
746        pathway
747    }
748
749    /// Create a TCA cycle pathway (simplified)
750    pub fn tca_cycle() -> MetabolicPathway {
751        let mut pathway = MetabolicPathway::new("TCA Cycle".to_string(), 8, 8);
752
753        // Metabolites: Acetyl-CoA, Citrate, Isocitrate, α-Ketoglutarate,
754        // Succinyl-CoA, Succinate, Fumarate, Malate, Oxaloacetate
755        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        // Add enzymes for each step of TCA cycle
767        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        // Set stoichiometry matrix for cyclic pathway
790        let mut stoich = Array2::zeros((8, 8));
791        for i in 0..8 {
792            stoich[[i, i]] = -1.0; // Consume substrate
793            stoich[[i, (i + 1) % 8]] = 1.0; // Produce product
794        }
795        pathway.stoichiometry_matrix = stoich;
796
797        pathway
798    }
799
800    /// Create a purine biosynthesis pathway
801    pub fn purine_biosynthesis() -> MetabolicPathway {
802        let mut pathway = MetabolicPathway::new("Purine Biosynthesis".to_string(), 10, 10);
803
804        // Simplified purine biosynthesis pathway
805        pathway.metabolites = vec![
806            "PRPP".to_string(),                  // 0
807            "5-Phosphoribosylamine".to_string(), // 1
808            "GAR".to_string(),                   // 2
809            "FGAR".to_string(),                  // 3
810            "FGAM".to_string(),                  // 4
811            "AIR".to_string(),                   // 5
812            "CAIR".to_string(),                  // 6
813            "SAICAR".to_string(),                // 7
814            "AICAR".to_string(),                 // 8
815            "IMP".to_string(),                   // 9
816        ];
817
818        // Add enzymes with different kinetic models
819        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        // Linear pathway stoichiometry
871        let mut stoich = Array2::zeros((10, 10));
872        for i in 0..9 {
873            stoich[[i, i]] = -1.0; // Consume substrate
874            stoich[[i, i + 1]] = 1.0; // Produce product
875        }
876        pathway.stoichiometry_matrix = stoich;
877
878        // Add feedback inhibition: IMP inhibits first enzyme
879        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; // Set to reference temperature to avoid correction
900
901        // Test at Km concentration (should give Vmax/2)
902        let rate_at_km = params.calculate_rate(&[1.0]);
903        assert_abs_diff_eq!(rate_at_km, 50.0, epsilon = 1e-10);
904
905        // Test at high substrate concentration (should approach Vmax)
906        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; // Set to reference temperature to avoid correction
914
915        // Test Hill equation behavior
916        let rate_at_kd = params.calculate_rate(&[1.0]);
917        assert_abs_diff_eq!(rate_at_kd, 50.0, epsilon = 1e-10);
918
919        // Test cooperativity
920        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        // Test rate calculation with initial concentrations
934        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        // All rates should be positive
938        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        // Test with uniform concentrations
951        let concentrations = Array1::from_vec(vec![1.0; 8]);
952        let rates = pathway.calculate_reaction_rates(&concentrations);
953
954        // All rates should be positive
955        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,   // Km
964            100.0, // Vmax
965            0.5,   // Ka_act
966            2.0,   // Ka_inh
967            2.0,   // n_act
968            2.0,   // n_inh
969        );
970
971        // Test with substrate only
972        let rate_base = params.calculate_rate(&[1.0]);
973
974        // Test with activator
975        let rate_activated = params.calculate_rate(&[1.0, 0.5]);
976
977        // Test with inhibitor
978        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        // Test at different temperatures
989        params.temperature = 298.15; // 25°C
990        let rate_25c = params.calculate_rate(&[1.0]);
991
992        params.temperature = 310.15; // 37°C
993        let rate_37c = params.calculate_rate(&[1.0]);
994
995        // Rate should increase with temperature
996        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        // Check that external metabolites have zero derivatives
1007        assert_abs_diff_eq!(derivatives[0], 0.0, epsilon = 1e-10); // Glucose (external)
1008        assert_abs_diff_eq!(derivatives[5], 0.0, epsilon = 1e-10); // Pyruvate (external)
1009    }
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        // Flux control coefficients should sum to 1 (summation theorem)
1019        let sum_fcc = analysis.flux_control_coefficients.sum();
1020        assert_abs_diff_eq!(sum_fcc, 1.0, epsilon = 0.1);
1021    }
1022}