1use crate::{EsmFile, Expr};
19use std::collections::HashMap;
20
21#[cfg(feature = "zero_copy")]
22use simd_json;
23
24#[cfg(feature = "parallel")]
25use rayon::prelude::*;
26
27#[cfg(feature = "simd")]
28use wide::f64x4;
29
30#[cfg(feature = "custom_alloc")]
31use bumpalo::Bump;
32
33#[derive(Debug, thiserror::Error)]
35pub enum PerformanceError {
36 #[error("parse error: {0}")]
38 ParseError(String),
39 #[error("Parallel evaluation error: {0}")]
40 ParallelError(String),
41 #[error("expression evaluation error: {0}")]
45 EvalError(String),
46 #[error("SIMD operation error: {0}")]
47 SimdError(String),
48 #[error("Memory allocation error: {0}")]
49 AllocError(String),
50}
51
52pub fn fast_parse(json_str: &str) -> Result<EsmFile, PerformanceError> {
57 #[cfg(feature = "zero_copy")]
58 {
59 let mut bytes = json_str.as_bytes().to_vec();
60 simd_json::serde::from_slice(&mut bytes)
61 .map_err(|e| PerformanceError::ParseError(e.to_string()))
62 }
63 #[cfg(not(feature = "zero_copy"))]
64 {
65 serde_json::from_str(json_str).map_err(|e| PerformanceError::ParseError(e.to_string()))
66 }
67}
68
69#[cfg(feature = "parallel")]
71pub struct ParallelEvaluator {
72 thread_pool: rayon::ThreadPool,
73}
74
75#[cfg(feature = "parallel")]
76impl ParallelEvaluator {
77 pub fn new(num_threads: Option<usize>) -> Result<Self, PerformanceError> {
79 let pool = if let Some(n) = num_threads {
80 rayon::ThreadPoolBuilder::new()
81 .num_threads(n)
82 .build()
83 .map_err(|e| PerformanceError::ParallelError(e.to_string()))?
84 } else {
85 rayon::ThreadPoolBuilder::new()
86 .build()
87 .map_err(|e| PerformanceError::ParallelError(e.to_string()))?
88 };
89
90 Ok(Self { thread_pool: pool })
91 }
92
93 pub fn evaluate_batch(
95 &self,
96 expressions: &[Expr],
97 variables: &HashMap<String, f64>,
98 ) -> Result<Vec<f64>, PerformanceError> {
99 self.thread_pool.install(|| {
100 expressions
101 .par_iter()
102 .map(|expr| {
103 crate::expression::evaluate(expr, variables)
104 .map_err(|e| PerformanceError::ParallelError(format!("{e:?}")))
105 })
106 .collect()
107 })
108 }
109
110 pub fn compute_stoichiometric_matrix_parallel(
112 &self,
113 system: &crate::ReactionSystem,
114 ) -> Result<Vec<Vec<f64>>, PerformanceError> {
115 let num_species = system.species.len();
116 let num_reactions = system.reactions.len();
117
118 let species_index: HashMap<String, usize> = system
123 .species
124 .keys()
125 .enumerate()
126 .map(|(idx, name)| (name.clone(), idx))
127 .collect();
128
129 self.thread_pool.install(|| {
130 let mut matrix: Vec<Vec<f64>> = (0..num_species)
132 .into_par_iter()
133 .map(|_| vec![0.0; num_reactions])
134 .collect();
135
136 let reaction_contributions: Vec<Vec<(usize, f64)>> = system
138 .reactions
139 .par_iter()
140 .enumerate()
141 .map(|(_reaction_idx, reaction)| {
142 let mut contributions = Vec::new();
143
144 for substrate in reaction.substrates.iter().flatten() {
146 if let Some(&species_idx) = species_index.get(&substrate.species) {
147 contributions.push((species_idx, -substrate.coefficient));
148 }
149 }
150
151 for product in reaction.products.iter().flatten() {
153 if let Some(&species_idx) = species_index.get(&product.species) {
154 contributions.push((species_idx, product.coefficient));
155 }
156 }
157
158 contributions
159 })
160 .collect();
161
162 for (reaction_idx, contributions) in reaction_contributions.into_iter().enumerate() {
164 for (species_idx, coeff) in contributions {
165 matrix[species_idx][reaction_idx] += coeff;
166 }
167 }
168
169 Ok(matrix)
170 })
171 }
172}
173
174#[cfg(feature = "simd")]
176pub mod simd_math {
177 use super::*;
178
179 fn elementwise_simd(
182 a: &[f64],
183 b: &[f64],
184 result: &mut [f64],
185 simd_op: impl Fn(f64x4, f64x4) -> f64x4,
186 scalar_op: impl Fn(f64, f64) -> f64,
187 ) -> Result<(), PerformanceError> {
188 if a.len() != b.len() || a.len() != result.len() {
189 return Err(PerformanceError::SimdError(
190 "Vector length mismatch".to_string(),
191 ));
192 }
193
194 let chunks = a.len() / 4;
195
196 for i in 0..chunks {
198 let idx = i * 4;
199 let va = f64x4::from([a[idx], a[idx + 1], a[idx + 2], a[idx + 3]]);
200 let vb = f64x4::from([b[idx], b[idx + 1], b[idx + 2], b[idx + 3]]);
201 let vr = simd_op(va, vb);
202
203 result[idx..idx + 4].copy_from_slice(&vr.to_array());
204 }
205
206 for i in (chunks * 4)..a.len() {
208 result[i] = scalar_op(a[i], b[i]);
209 }
210
211 Ok(())
212 }
213
214 pub fn add_vectors_simd(
216 a: &[f64],
217 b: &[f64],
218 result: &mut [f64],
219 ) -> Result<(), PerformanceError> {
220 elementwise_simd(a, b, result, |va, vb| va + vb, |x, y| x + y)
221 }
222
223 pub fn multiply_vectors_simd(
225 a: &[f64],
226 b: &[f64],
227 result: &mut [f64],
228 ) -> Result<(), PerformanceError> {
229 elementwise_simd(a, b, result, |va, vb| va * vb, |x, y| x * y)
230 }
231
232 pub fn dot_product_simd(a: &[f64], b: &[f64]) -> Result<f64, PerformanceError> {
234 if a.len() != b.len() {
235 return Err(PerformanceError::SimdError(
236 "Vector length mismatch".to_string(),
237 ));
238 }
239
240 let chunks = a.len() / 4;
241 let mut sum = f64x4::from([0.0, 0.0, 0.0, 0.0]);
242
243 for i in 0..chunks {
245 let idx = i * 4;
246 let va = f64x4::from([a[idx], a[idx + 1], a[idx + 2], a[idx + 3]]);
247 let vb = f64x4::from([b[idx], b[idx + 1], b[idx + 2], b[idx + 3]]);
248 sum += va * vb;
249 }
250
251 let mut result = sum.to_array().iter().sum::<f64>();
252
253 for i in (chunks * 4)..a.len() {
255 result += a[i] * b[i];
256 }
257
258 Ok(result)
259 }
260}
261
262#[cfg(feature = "custom_alloc")]
264pub struct ModelAllocator {
265 bump: Bump,
266}
267
268#[cfg(feature = "custom_alloc")]
269impl Default for ModelAllocator {
270 fn default() -> Self {
271 Self::new()
272 }
273}
274
275#[cfg(feature = "custom_alloc")]
276impl ModelAllocator {
277 pub fn new() -> Self {
279 Self { bump: Bump::new() }
280 }
281
282 pub fn with_capacity(capacity: usize) -> Self {
284 Self {
285 bump: Bump::with_capacity(capacity),
286 }
287 }
288
289 pub fn alloc_slice<T>(&self, len: usize) -> &mut [T]
291 where
292 T: Default + Clone,
293 {
294 self.bump.alloc_slice_fill_default(len)
295 }
296
297 pub fn reset(&mut self) {
299 self.bump.reset();
300 }
301
302 pub fn allocated_bytes(&self) -> usize {
304 self.bump.allocated_bytes()
305 }
306}
307
308#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
310pub enum CompactNode {
311 Number(f64),
313 Variable(String),
315 Operator(String),
317}
318
319#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
321pub struct CompactExpr {
322 pub nodes: smallvec::SmallVec<[CompactNode; 8]>,
324 pub var_cache: HashMap<String, usize>,
329}
330
331impl CompactExpr {
332 pub fn from_expr(expr: &Expr) -> Self {
334 let mut nodes = smallvec::SmallVec::new();
335 let mut var_cache = HashMap::new();
336
337 Self::flatten_expr(expr, &mut nodes, &mut var_cache);
338
339 Self { nodes, var_cache }
340 }
341
342 fn flatten_expr(
344 expr: &Expr,
345 nodes: &mut smallvec::SmallVec<[CompactNode; 8]>,
346 var_cache: &mut HashMap<String, usize>,
347 ) {
348 match expr {
349 Expr::Number(n) => {
350 nodes.push(CompactNode::Number(*n));
351 }
352 Expr::Integer(n) => {
353 nodes.push(CompactNode::Number(*n as f64));
356 }
357 Expr::Variable(name) => {
358 let index = var_cache.len();
359 var_cache.entry(name.clone()).or_insert(index);
360 nodes.push(CompactNode::Variable(name.clone()));
361 }
362 Expr::Operator(expr_node) => {
363 for arg in &expr_node.args {
365 Self::flatten_expr(arg, nodes, var_cache);
366 }
367 nodes.push(CompactNode::Operator(expr_node.op.clone()));
368 }
369 }
370 }
371
372 pub fn evaluate_fast(&self, variables: &HashMap<String, f64>) -> Result<f64, PerformanceError> {
375 type Stack = smallvec::SmallVec<[f64; 16]>;
376
377 fn underflow() -> PerformanceError {
378 PerformanceError::EvalError("Stack underflow".to_string())
379 }
380
381 fn pop2(stack: &mut Stack) -> Result<(f64, f64), PerformanceError> {
384 if stack.len() < 2 {
385 return Err(underflow());
386 }
387 let b = stack.pop().unwrap();
388 let a = stack.pop().unwrap();
389 Ok((a, b))
390 }
391
392 fn pop1(stack: &mut Stack) -> Result<f64, PerformanceError> {
394 stack.pop().ok_or_else(underflow)
395 }
396
397 let mut stack = Stack::new();
398
399 for node in &self.nodes {
400 match node {
401 CompactNode::Number(n) => stack.push(*n),
402 CompactNode::Variable(name) => {
403 let value = variables.get(name).ok_or_else(|| {
404 PerformanceError::EvalError(format!("Undefined variable: {name}"))
405 })?;
406 stack.push(*value);
407 }
408 CompactNode::Operator(op) => match op.as_str() {
409 "+" => {
410 let (a, b) = pop2(&mut stack)?;
411 stack.push(a + b);
412 }
413 "-" => {
414 let (a, b) = pop2(&mut stack)?;
415 stack.push(a - b);
416 }
417 "*" => {
418 let (a, b) = pop2(&mut stack)?;
419 stack.push(a * b);
420 }
421 "/" => {
422 let (a, b) = pop2(&mut stack)?;
423 if b == 0.0 {
424 return Err(PerformanceError::EvalError(
425 "Division by zero".to_string(),
426 ));
427 }
428 stack.push(a / b);
429 }
430 "^" => {
431 let (a, b) = pop2(&mut stack)?;
432 stack.push(a.powf(b));
433 }
434 "sin" => {
435 let a = pop1(&mut stack)?;
436 stack.push(a.sin());
437 }
438 "cos" => {
439 let a = pop1(&mut stack)?;
440 stack.push(a.cos());
441 }
442 "exp" => {
443 let a = pop1(&mut stack)?;
444 stack.push(a.exp());
445 }
446 "log" => {
447 let a = pop1(&mut stack)?;
448 if a <= 0.0 {
449 return Err(PerformanceError::EvalError(
450 "Invalid log argument".to_string(),
451 ));
452 }
453 stack.push(a.ln());
454 }
455 _ => {
456 return Err(PerformanceError::EvalError(format!(
457 "Unsupported operator: {op}"
458 )));
459 }
460 },
461 }
462 }
463
464 if stack.len() != 1 {
465 return Err(PerformanceError::EvalError(
466 "Invalid expression".to_string(),
467 ));
468 }
469
470 Ok(stack[0])
471 }
472}
473
474#[cfg(test)]
475mod tests {
476 use super::*;
477 use crate::types::{Expr, ExpressionNode};
478
479 #[test]
480 fn test_compact_expr_creation() {
481 let expr = Expr::operator(ExpressionNode {
482 op: "+".to_string(),
483 args: vec![Expr::Variable("x".to_string()), Expr::Number(1.0)],
484 wrt: None,
485 dim: None,
486 ..Default::default()
487 });
488
489 let compact = CompactExpr::from_expr(&expr);
490 assert_eq!(compact.nodes.len(), 3);
491 assert_eq!(compact.var_cache.len(), 1);
492 assert!(compact.var_cache.contains_key("x"));
493 }
494
495 #[cfg(feature = "parallel")]
496 #[test]
497 fn test_fast_evaluation() {
498 let expr = Expr::operator(ExpressionNode {
499 op: "+".to_string(),
500 args: vec![Expr::Variable("x".to_string()), Expr::Number(1.0)],
501 wrt: None,
502 dim: None,
503 ..Default::default()
504 });
505
506 let compact = CompactExpr::from_expr(&expr);
507 let mut variables = HashMap::new();
508 variables.insert("x".to_string(), 5.0);
509
510 let result = compact.evaluate_fast(&variables).unwrap();
511 assert_eq!(result, 6.0);
512 }
513
514 #[cfg(feature = "simd")]
515 #[test]
516 fn test_simd_operations() {
517 let a = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
518 let b = vec![2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
519 let mut result = vec![0.0; 8];
520
521 simd_math::add_vectors_simd(&a, &b, &mut result).unwrap();
522
523 assert_eq!(result, vec![3.0, 5.0, 7.0, 9.0, 11.0, 13.0, 15.0, 17.0]);
524
525 let dot = simd_math::dot_product_simd(&a, &b).unwrap();
526 assert_eq!(dot, 240.0); }
528}