1use crate::{
8 error::{QuantRS2Error, QuantRS2Result},
9 gate::GateOp,
10 optimization::OptimizationChain,
11 qubit::QubitId,
12};
13use serde::{Deserialize, Serialize};
14use std::{
15 collections::{HashMap, HashSet, VecDeque},
16 sync::{Arc, RwLock},
17 time::{Duration, Instant},
18};
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct LazyEvaluationConfig {
23 pub max_buffer_size: usize,
25 pub max_defer_time: Duration,
27 pub enable_dependency_optimization: bool,
29 pub enable_speculative_optimization: bool,
31 pub num_optimization_threads: usize,
33 pub optimization_cache_size: usize,
35}
36
37impl Default for LazyEvaluationConfig {
38 fn default() -> Self {
39 Self {
40 max_buffer_size: 1000,
41 max_defer_time: Duration::from_millis(100),
42 enable_dependency_optimization: true,
43 enable_speculative_optimization: true,
44 num_optimization_threads: 4,
45 optimization_cache_size: 10000,
46 }
47 }
48}
49
50#[derive(Debug, Clone)]
52pub struct LazyGateContext {
53 pub gate_id: usize,
55 pub gate: Box<dyn GateOp>,
57 pub dependencies: HashSet<usize>,
59 pub dependents: HashSet<usize>,
61 pub priority: f64,
63 pub created_at: Instant,
65 pub is_evaluated: bool,
67 pub cached_result: Option<OptimizationResult>,
69}
70
71#[derive(Debug, Clone)]
73pub struct OptimizationResult {
74 pub optimized_gates: Vec<Box<dyn GateOp>>,
76 pub stats: OptimizationStats,
78 pub optimization_time: Duration,
80}
81
82#[derive(Debug, Clone, Default)]
84pub struct OptimizationStats {
85 pub gates_before: usize,
87 pub gates_after: usize,
89 pub passes_applied: usize,
91 pub performance_improvement: f64,
93 pub memory_savings: usize,
95}
96
97pub struct LazyOptimizationPipeline {
99 config: LazyEvaluationConfig,
101 gate_buffer: Arc<RwLock<HashMap<usize, LazyGateContext>>>,
103 dependency_graph: Arc<RwLock<DependencyGraph>>,
105 optimization_chain: OptimizationChain,
107 optimization_cache: Arc<RwLock<OptimizationCache>>,
109 next_gate_id: Arc<RwLock<usize>>,
111 worker_handles: Vec<std::thread::JoinHandle<()>>,
113 shutdown_signal: Arc<RwLock<bool>>,
115}
116
117#[derive(Debug, Default)]
119struct DependencyGraph {
120 edges: HashMap<usize, HashSet<usize>>,
122 reverse_edges: HashMap<usize, HashSet<usize>>,
124 topo_order_cache: Option<Vec<usize>>,
126}
127
128struct OptimizationCache {
130 entries: HashMap<u64, CachedOptimization>,
132 lru_queue: VecDeque<u64>,
134 max_size: usize,
136}
137
138#[derive(Debug, Clone)]
140struct CachedOptimization {
141 result: OptimizationResult,
143 access_count: usize,
145 last_accessed: Instant,
147}
148
149impl LazyOptimizationPipeline {
150 pub fn new(
152 config: LazyEvaluationConfig,
153 optimization_chain: OptimizationChain,
154 ) -> QuantRS2Result<Self> {
155 let gate_buffer = Arc::new(RwLock::new(HashMap::new()));
156 let dependency_graph = Arc::new(RwLock::new(DependencyGraph::default()));
157 let optimization_cache = Arc::new(RwLock::new(OptimizationCache::new(
158 config.optimization_cache_size,
159 )));
160 let next_gate_id = Arc::new(RwLock::new(0));
161 let shutdown_signal = Arc::new(RwLock::new(false));
162
163 let mut worker_handles = Vec::new();
165 for worker_id in 0..config.num_optimization_threads {
166 let handle = Self::start_worker_thread(
167 worker_id,
168 Arc::clone(&gate_buffer),
169 Arc::clone(&dependency_graph),
170 Arc::clone(&optimization_cache),
171 Arc::clone(&shutdown_signal),
172 config.clone(),
173 );
174 worker_handles.push(handle);
175 }
176
177 Ok(Self {
178 config,
179 gate_buffer,
180 dependency_graph,
181 optimization_chain,
182 optimization_cache,
183 next_gate_id,
184 worker_handles,
185 shutdown_signal,
186 })
187 }
188
189 pub fn add_gate(&self, gate: Box<dyn GateOp>) -> QuantRS2Result<usize> {
191 let gate_id = {
192 let mut next_id = self
193 .next_gate_id
194 .write()
195 .map_err(|_| QuantRS2Error::RuntimeError("Gate ID lock poisoned".to_string()))?;
196 let id = *next_id;
197 *next_id += 1;
198 id
199 };
200
201 let dependencies = self.analyze_dependencies(gate.as_ref())?;
203
204 let priority = self.calculate_priority(gate.as_ref(), &dependencies);
206
207 let context = LazyGateContext {
208 gate_id,
209 gate,
210 dependencies: dependencies.clone(),
211 dependents: HashSet::new(),
212 priority,
213 created_at: Instant::now(),
214 is_evaluated: false,
215 cached_result: None,
216 };
217
218 {
220 let mut graph = self.dependency_graph.write().map_err(|_| {
221 QuantRS2Error::RuntimeError("Dependency graph lock poisoned".to_string())
222 })?;
223 graph.add_gate(gate_id, dependencies);
224 }
225
226 {
228 let mut buffer = self.gate_buffer.write().map_err(|_| {
229 QuantRS2Error::RuntimeError("Gate buffer lock poisoned".to_string())
230 })?;
231 buffer.insert(gate_id, context);
232 }
233
234 self.check_forced_evaluation()?;
236
237 Ok(gate_id)
238 }
239
240 pub fn evaluate_gate(&self, gate_id: usize) -> QuantRS2Result<OptimizationResult> {
242 if let Some(cached) = self.get_cached_result(gate_id)? {
244 return Ok(cached);
245 }
246
247 let context = {
249 let buffer = self.gate_buffer.read().map_err(|_| {
250 QuantRS2Error::RuntimeError("Gate buffer lock poisoned".to_string())
251 })?;
252 buffer.get(&gate_id).cloned().ok_or_else(|| {
253 QuantRS2Error::InvalidInput(format!("Gate {gate_id} not found in buffer"))
254 })?
255 };
256
257 self.evaluate_dependencies(&context.dependencies)?;
259
260 let result = self.optimize_gate_context(&context)?;
262
263 self.cache_optimization_result(gate_id, &result)?;
265
266 {
268 let mut buffer = self.gate_buffer.write().map_err(|_| {
269 QuantRS2Error::RuntimeError("Gate buffer lock poisoned".to_string())
270 })?;
271 if let Some(ctx) = buffer.get_mut(&gate_id) {
272 ctx.is_evaluated = true;
273 ctx.cached_result = Some(result.clone());
274 }
275 }
276
277 Ok(result)
278 }
279
280 pub fn evaluate_all(&self) -> QuantRS2Result<Vec<OptimizationResult>> {
282 let ordered_gates = {
284 let graph = self.dependency_graph.read().map_err(|_| {
285 QuantRS2Error::RuntimeError("Dependency graph lock poisoned".to_string())
286 })?;
287 graph.topological_sort()
288 };
289
290 let mut results = Vec::new();
291 for gate_id in ordered_gates {
292 if let Ok(result) = self.evaluate_gate(gate_id) {
293 results.push(result);
294 }
295 }
296
297 {
299 if let Ok(mut buffer) = self.gate_buffer.write() {
300 buffer.clear();
301 }
302 }
303
304 Ok(results)
305 }
306
307 pub fn get_statistics(&self) -> LazyEvaluationStats {
309 let buffer = self.gate_buffer.read().ok();
310 let cache = self.optimization_cache.read().ok();
311
312 let (total_gates, evaluated_gates) = buffer
313 .as_ref()
314 .map(|b| {
315 let total = b.len();
316 let evaluated = b.values().filter(|ctx| ctx.is_evaluated).count();
317 (total, evaluated)
318 })
319 .unwrap_or((0, 0));
320 let pending_gates = total_gates - evaluated_gates;
321
322 let (cache_hits, cache_size, avg_time) = cache
323 .as_ref()
324 .map(|c| {
325 (
326 c.get_hit_count(),
327 c.entries.len(),
328 c.get_average_optimization_time(),
329 )
330 })
331 .unwrap_or((0, 0, Duration::ZERO));
332
333 LazyEvaluationStats {
334 total_gates,
335 evaluated_gates,
336 pending_gates,
337 cache_hits,
338 cache_size,
339 average_optimization_time: avg_time,
340 }
341 }
342
343 fn analyze_dependencies(&self, gate: &dyn GateOp) -> QuantRS2Result<HashSet<usize>> {
345 let gate_qubits: HashSet<QubitId> = gate.qubits().into_iter().collect();
346 let mut dependencies = HashSet::new();
347
348 let buffer = self
349 .gate_buffer
350 .read()
351 .map_err(|_| QuantRS2Error::RuntimeError("Gate buffer lock poisoned".to_string()))?;
352 for (gate_id, context) in buffer.iter() {
353 let context_qubits: HashSet<QubitId> = context.gate.qubits().into_iter().collect();
354
355 if !gate_qubits.is_disjoint(&context_qubits) {
357 dependencies.insert(*gate_id);
358 }
359 }
360
361 Ok(dependencies)
362 }
363
364 fn calculate_priority(&self, gate: &dyn GateOp, dependencies: &HashSet<usize>) -> f64 {
366 let mut priority = 0.0;
367
368 priority += 10.0 / (gate.num_qubits() as f64 + 1.0);
370
371 priority -= dependencies.len() as f64 * 0.5;
373
374 match gate.name() {
376 "H" | "X" | "Y" | "Z" => priority += 5.0,
377 "CNOT" | "CZ" => priority += 3.0,
378 "RX" | "RY" | "RZ" => priority += 2.0,
379 _ => priority += 1.0,
380 }
381
382 priority.max(0.1)
383 }
384
385 fn check_forced_evaluation(&self) -> QuantRS2Result<()> {
387 let buffer = self
388 .gate_buffer
389 .read()
390 .map_err(|_| QuantRS2Error::RuntimeError("Gate buffer lock poisoned".to_string()))?;
391
392 if buffer.len() >= self.config.max_buffer_size {
394 drop(buffer);
395 return self.force_oldest_evaluation();
396 }
397
398 let now = Instant::now();
400 for context in buffer.values() {
401 if now.duration_since(context.created_at) > self.config.max_defer_time {
402 drop(buffer);
403 return self.force_oldest_evaluation();
404 }
405 }
406
407 Ok(())
408 }
409
410 fn force_oldest_evaluation(&self) -> QuantRS2Result<()> {
412 let oldest_gate_id = {
413 let buffer = self.gate_buffer.read().map_err(|_| {
414 QuantRS2Error::RuntimeError("Gate buffer lock poisoned".to_string())
415 })?;
416 buffer
417 .values()
418 .filter(|ctx| !ctx.is_evaluated)
419 .min_by_key(|ctx| ctx.created_at)
420 .map(|ctx| ctx.gate_id)
421 };
422
423 if let Some(gate_id) = oldest_gate_id {
424 self.evaluate_gate(gate_id)?;
425 }
426
427 Ok(())
428 }
429
430 fn evaluate_dependencies(&self, dependencies: &HashSet<usize>) -> QuantRS2Result<()> {
432 for &dep_id in dependencies {
433 if !self.is_gate_evaluated(dep_id) {
434 self.evaluate_gate(dep_id)?;
435 }
436 }
437 Ok(())
438 }
439
440 fn is_gate_evaluated(&self, gate_id: usize) -> bool {
442 self.gate_buffer
443 .read()
444 .ok()
445 .and_then(|buffer| buffer.get(&gate_id).map(|ctx| ctx.is_evaluated))
446 .unwrap_or(false)
447 }
448
449 fn optimize_gate_context(
451 &self,
452 context: &LazyGateContext,
453 ) -> QuantRS2Result<OptimizationResult> {
454 let start_time = Instant::now();
455
456 let input_gates = vec![context.gate.clone_gate()];
458 let optimized_gates = self.optimization_chain.optimize(input_gates)?;
459
460 let optimization_time = start_time.elapsed();
461
462 let stats = OptimizationStats {
464 gates_before: 1,
465 gates_after: optimized_gates.len(),
466 passes_applied: 1, performance_improvement: self.estimate_performance_improvement(&optimized_gates),
468 memory_savings: self.estimate_memory_savings(&optimized_gates),
469 };
470
471 Ok(OptimizationResult {
472 optimized_gates,
473 stats,
474 optimization_time,
475 })
476 }
477
478 fn estimate_performance_improvement(&self, gates: &[Box<dyn GateOp>]) -> f64 {
480 let base_improvement = 1.0 / (gates.len() as f64 + 1.0);
482
483 let single_qubit_bonus = gates.iter().filter(|g| g.num_qubits() == 1).count() as f64 * 0.1;
485
486 base_improvement + single_qubit_bonus
487 }
488
489 fn estimate_memory_savings(&self, gates: &[Box<dyn GateOp>]) -> usize {
491 gates
493 .iter()
494 .map(|g| match g.num_qubits() {
495 1 => 16, 2 => 64, n => (1 << (2 * n)) * 16, })
499 .sum()
500 }
501
502 fn get_cached_result(&self, gate_id: usize) -> QuantRS2Result<Option<OptimizationResult>> {
504 let buffer = self
505 .gate_buffer
506 .read()
507 .map_err(|_| QuantRS2Error::RuntimeError("Gate buffer lock poisoned".to_string()))?;
508 if let Some(context) = buffer.get(&gate_id) {
509 if let Some(ref result) = context.cached_result {
510 return Ok(Some(result.clone()));
511 }
512 }
513 drop(buffer);
514
515 let gate_hash = self.compute_gate_hash(gate_id)?;
517 let mut cache = self.optimization_cache.write().map_err(|_| {
518 QuantRS2Error::RuntimeError("Optimization cache lock poisoned".to_string())
519 })?;
520 if let Some(cached) = cache.get_mut(gate_hash) {
521 return Ok(Some(cached.result.clone()));
522 }
523
524 Ok(None)
525 }
526
527 fn cache_optimization_result(
529 &self,
530 gate_id: usize,
531 result: &OptimizationResult,
532 ) -> QuantRS2Result<()> {
533 let gate_hash = self.compute_gate_hash(gate_id)?;
534 let mut cache = self.optimization_cache.write().map_err(|_| {
535 QuantRS2Error::RuntimeError("Optimization cache lock poisoned".to_string())
536 })?;
537
538 let cached = CachedOptimization {
539 result: result.clone(),
540 access_count: 1,
541 last_accessed: Instant::now(),
542 };
543
544 cache.insert(gate_hash, cached);
545 Ok(())
546 }
547
548 fn compute_gate_hash(&self, gate_id: usize) -> QuantRS2Result<u64> {
550 use std::collections::hash_map::DefaultHasher;
551 use std::hash::{Hash, Hasher};
552
553 let buffer = self
554 .gate_buffer
555 .read()
556 .map_err(|_| QuantRS2Error::RuntimeError("Gate buffer lock poisoned".to_string()))?;
557 let context = buffer
558 .get(&gate_id)
559 .ok_or_else(|| QuantRS2Error::InvalidInput(format!("Gate {gate_id} not found")))?;
560
561 let mut hasher = DefaultHasher::new();
562 context.gate.name().hash(&mut hasher);
563
564 if let Ok(matrix) = context.gate.matrix() {
566 for elem in &matrix {
567 elem.re.to_bits().hash(&mut hasher);
568 elem.im.to_bits().hash(&mut hasher);
569 }
570 }
571
572 Ok(hasher.finish())
573 }
574
575 fn start_worker_thread(
577 _worker_id: usize,
578 gate_buffer: Arc<RwLock<HashMap<usize, LazyGateContext>>>,
579 _dependency_graph: Arc<RwLock<DependencyGraph>>,
580 _optimization_cache: Arc<RwLock<OptimizationCache>>,
581 shutdown_signal: Arc<RwLock<bool>>,
582 config: LazyEvaluationConfig,
583 ) -> std::thread::JoinHandle<()> {
584 std::thread::spawn(move || {
585 let sleep_duration = Duration::from_millis(10);
586
587 loop {
588 {
590 match shutdown_signal.read() {
591 Ok(shutdown) if *shutdown => break,
592 Err(_) => break, _ => {}
594 }
595 }
596
597 if config.enable_speculative_optimization {
599 let high_priority_gates = {
600 match gate_buffer.read() {
601 Ok(buffer) => buffer
602 .values()
603 .filter(|ctx| !ctx.is_evaluated && ctx.priority > 5.0)
604 .map(|ctx| ctx.gate_id)
605 .collect::<Vec<_>>(),
606 Err(_) => continue, }
608 };
609
610 for gate_id in high_priority_gates {
611 if let Ok(mut buffer) = gate_buffer.write() {
614 if let Some(ctx) = buffer.get_mut(&gate_id) {
615 ctx.priority += 0.1; }
618 }
619 }
620 }
621
622 std::thread::sleep(sleep_duration);
623 }
624 })
625 }
626}
627
628impl Drop for LazyOptimizationPipeline {
629 fn drop(&mut self) {
630 {
632 if let Ok(mut shutdown) = self.shutdown_signal.write() {
633 *shutdown = true;
634 }
635 }
636
637 while let Some(handle) = self.worker_handles.pop() {
639 let _ = handle.join();
640 }
641 }
642}
643
644impl DependencyGraph {
645 fn add_gate(&mut self, gate_id: usize, dependencies: HashSet<usize>) {
647 self.edges.insert(gate_id, dependencies.clone());
648
649 for dep in dependencies {
651 self.reverse_edges
652 .entry(dep)
653 .or_insert_with(HashSet::new)
654 .insert(gate_id);
655 }
656
657 self.topo_order_cache = None;
659 }
660
661 fn topological_sort(&self) -> Vec<usize> {
663 if let Some(ref cached) = self.topo_order_cache {
664 return cached.clone();
665 }
666
667 let mut result = Vec::new();
668 let mut in_degree: HashMap<usize, usize> = HashMap::new();
669 let mut queue = VecDeque::new();
670
671 for (&node, edges) in &self.edges {
673 in_degree.entry(node).or_insert(0);
674 for &dep in edges {
675 *in_degree.entry(dep).or_insert(0) += 1;
676 }
677 }
678
679 for (&node, °ree) in &in_degree {
681 if degree == 0 {
682 queue.push_back(node);
683 }
684 }
685
686 while let Some(node) = queue.pop_front() {
688 result.push(node);
689
690 if let Some(dependents) = self.reverse_edges.get(&node) {
691 for &dependent in dependents {
692 if let Some(degree) = in_degree.get_mut(&dependent) {
693 *degree -= 1;
694 if *degree == 0 {
695 queue.push_back(dependent);
696 }
697 }
698 }
699 }
700 }
701
702 result
703 }
704}
705
706impl OptimizationCache {
707 fn new(max_size: usize) -> Self {
708 Self {
709 entries: HashMap::new(),
710 lru_queue: VecDeque::new(),
711 max_size,
712 }
713 }
714
715 fn get_mut(&mut self, hash: u64) -> Option<&mut CachedOptimization> {
716 if let Some(cached) = self.entries.get_mut(&hash) {
717 cached.access_count += 1;
718 cached.last_accessed = Instant::now();
719
720 self.lru_queue.retain(|&h| h != hash);
722 self.lru_queue.push_front(hash);
723
724 Some(cached)
725 } else {
726 None
727 }
728 }
729
730 fn insert(&mut self, hash: u64, cached: CachedOptimization) {
731 while self.entries.len() >= self.max_size {
733 if let Some(oldest_hash) = self.lru_queue.pop_back() {
734 self.entries.remove(&oldest_hash);
735 } else {
736 break;
737 }
738 }
739
740 self.entries.insert(hash, cached);
741 self.lru_queue.push_front(hash);
742 }
743
744 fn get_hit_count(&self) -> usize {
745 self.entries.values().map(|c| c.access_count).sum()
746 }
747
748 fn get_average_optimization_time(&self) -> Duration {
749 if self.entries.is_empty() {
750 return Duration::ZERO;
751 }
752
753 let total_time: Duration = self
754 .entries
755 .values()
756 .map(|c| c.result.optimization_time)
757 .sum();
758
759 total_time / self.entries.len() as u32
760 }
761}
762
763#[derive(Debug, Clone)]
765pub struct LazyEvaluationStats {
766 pub total_gates: usize,
767 pub evaluated_gates: usize,
768 pub pending_gates: usize,
769 pub cache_hits: usize,
770 pub cache_size: usize,
771 pub average_optimization_time: Duration,
772}
773
774#[cfg(test)]
775mod tests {
776 use super::*;
777 use crate::gate::single::{Hadamard, PauliX, PauliZ};
778 use crate::optimization::OptimizationChain;
779
780 #[test]
781 fn test_lazy_pipeline_creation() {
782 let config = LazyEvaluationConfig::default();
783 let chain = OptimizationChain::new();
784
785 let pipeline =
786 LazyOptimizationPipeline::new(config, chain).expect("Failed to create pipeline");
787 let stats = pipeline.get_statistics();
788
789 assert_eq!(stats.total_gates, 0);
790 assert_eq!(stats.evaluated_gates, 0);
791 }
792
793 #[test]
794 fn test_gate_addition() {
795 let config = LazyEvaluationConfig::default();
796 let chain = OptimizationChain::new();
797
798 let pipeline =
799 LazyOptimizationPipeline::new(config, chain).expect("Failed to create pipeline");
800
801 let h_gate = Box::new(Hadamard {
802 target: crate::qubit::QubitId(0),
803 });
804 let gate_id = pipeline.add_gate(h_gate).expect("Failed to add gate");
805
806 assert_eq!(gate_id, 0);
807
808 let stats = pipeline.get_statistics();
809 assert_eq!(stats.total_gates, 1);
810 assert_eq!(stats.pending_gates, 1);
811 }
812
813 #[test]
814 #[ignore] fn test_gate_evaluation() {
816 let config = LazyEvaluationConfig::default();
817 let chain = OptimizationChain::new();
818
819 let pipeline =
820 LazyOptimizationPipeline::new(config, chain).expect("Failed to create pipeline");
821
822 let h_gate = Box::new(Hadamard {
823 target: crate::qubit::QubitId(0),
824 });
825 let gate_id = pipeline.add_gate(h_gate).expect("Failed to add gate");
826
827 let result = pipeline
828 .evaluate_gate(gate_id)
829 .expect("Failed to evaluate gate");
830 assert!(result.optimization_time > Duration::ZERO);
831
832 let stats = pipeline.get_statistics();
833 assert_eq!(stats.evaluated_gates, 1);
834 assert_eq!(stats.pending_gates, 0);
835 }
836
837 #[test]
838 fn test_dependency_analysis() {
839 let config = LazyEvaluationConfig::default();
840 let chain = OptimizationChain::new();
841
842 let pipeline =
843 LazyOptimizationPipeline::new(config, chain).expect("Failed to create pipeline");
844
845 let h_gate = Box::new(Hadamard {
847 target: crate::qubit::QubitId(0),
848 });
849 let x_gate = Box::new(PauliX {
850 target: crate::qubit::QubitId(0),
851 });
852 let z_gate = Box::new(PauliZ {
853 target: crate::qubit::QubitId(1),
854 });
855
856 let _h_id = pipeline
857 .add_gate(h_gate)
858 .expect("Failed to add Hadamard gate");
859 let _x_id = pipeline
860 .add_gate(x_gate)
861 .expect("Failed to add PauliX gate");
862 let _z_id = pipeline
863 .add_gate(z_gate)
864 .expect("Failed to add PauliZ gate");
865
866 let results = pipeline
870 .evaluate_all()
871 .expect("Failed to evaluate all gates");
872 assert!(results.len() <= 3);
874 }
875
876 #[test]
877 #[ignore] fn test_optimization_caching() {
879 let config = LazyEvaluationConfig::default();
880 let chain = OptimizationChain::new();
881
882 let pipeline =
883 LazyOptimizationPipeline::new(config, chain).expect("Failed to create pipeline");
884
885 let h_gate = Box::new(Hadamard {
886 target: crate::qubit::QubitId(0),
887 });
888 let gate_id = pipeline.add_gate(h_gate).expect("Failed to add gate");
889
890 let result1 = pipeline
892 .evaluate_gate(gate_id)
893 .expect("Failed to evaluate gate first time");
894
895 let result2 = pipeline
897 .evaluate_gate(gate_id)
898 .expect("Failed to evaluate gate second time");
899
900 assert_eq!(result1.stats.gates_before, result2.stats.gates_before);
902 assert_eq!(result1.stats.gates_after, result2.stats.gates_after);
903 }
904}