1use super::types::*;
7use crate::analysis::BifurcationPoint;
8use crate::error::{IntegrateError, IntegrateResult};
9use scirs2_core::ndarray::Array1;
10use scirs2_core::random::Rng;
11
12#[derive(Debug, Clone)]
14pub struct InteractiveParameterExplorer {
15 pub param_dimensions: usize,
17 pub param_bounds: Vec<(f64, f64)>,
19 pub samples_per_dim: usize,
21 pub currentparams: Array1<f64>,
23 pub param_history: Vec<Array1<f64>>,
25 pub response_cache: std::collections::HashMap<String, Array1<f64>>,
27}
28
29impl InteractiveParameterExplorer {
30 pub fn new(
32 param_dimensions: usize,
33 param_bounds: Vec<(f64, f64)>,
34 samples_per_dim: usize,
35 ) -> Self {
36 let currentparams = Array1::from_vec(
37 param_bounds
38 .iter()
39 .map(|(min, max)| (min + max) / 2.0)
40 .collect(),
41 );
42
43 Self {
44 param_dimensions,
45 param_bounds,
46 samples_per_dim,
47 currentparams,
48 param_history: Vec::new(),
49 response_cache: std::collections::HashMap::new(),
50 }
51 }
52
53 pub fn explore_parameter_space<F>(
55 &mut self,
56 system_function: F,
57 exploration_method: ExplorationMethod,
58 ) -> IntegrateResult<ParameterExplorationResult>
59 where
60 F: Fn(&Array1<f64>) -> IntegrateResult<Array1<f64>>,
61 {
62 match exploration_method {
63 ExplorationMethod::GridScan => self.grid_scan_exploration(&system_function),
64 ExplorationMethod::RandomSampling => self.random_sampling_exploration(&system_function),
65 ExplorationMethod::AdaptiveSampling => {
66 self.adaptive_sampling_exploration(&system_function)
67 }
68 ExplorationMethod::GradientGuided => self.gradient_guided_exploration(&system_function),
69 }
70 }
71
72 fn grid_scan_exploration<F>(
74 &mut self,
75 system_function: &F,
76 ) -> IntegrateResult<ParameterExplorationResult>
77 where
78 F: Fn(&Array1<f64>) -> IntegrateResult<Array1<f64>>,
79 {
80 let mut exploration_points = Vec::new();
81 let mut response_values = Vec::new();
82 let mut parameter_grid = Vec::new();
83
84 let grid_indices = self.generate_grid_indices();
86
87 for indices in grid_indices {
88 let params = self.indices_to_parameters(&indices);
89 parameter_grid.push(params.clone());
90
91 match system_function(¶ms) {
93 Ok(response) => {
94 exploration_points.push(params.clone());
95 response_values.push(response.clone());
96
97 let key = self.params_to_cache_key(¶ms);
99 self.response_cache.insert(key, response);
100 }
101 Err(_) => {
102 continue;
104 }
105 }
106 }
107
108 Ok(ParameterExplorationResult {
109 exploration_points,
110 response_values: response_values.clone(),
111 parameter_grid,
112 convergence_history: self.param_history.clone(),
113 exploration_method: ExplorationMethod::GridScan,
114 optimization_metrics: self.compute_exploration_metrics(&response_values)?,
115 })
116 }
117
118 fn random_sampling_exploration<F>(
120 &mut self,
121 system_function: &F,
122 ) -> IntegrateResult<ParameterExplorationResult>
123 where
124 F: Fn(&Array1<f64>) -> IntegrateResult<Array1<f64>>,
125 {
126 use scirs2_core::random::Rng;
127 let mut rng = scirs2_core::random::rng();
128
129 let mut exploration_points = Vec::new();
130 let mut response_values = Vec::new();
131 let mut parameter_grid = Vec::new();
132
133 let total_samples = self.samples_per_dim.pow(self.param_dimensions as u32);
134
135 for _ in 0..total_samples {
136 let mut params = Array1::zeros(self.param_dimensions);
137
138 for i in 0..self.param_dimensions {
139 let (min, max) = self.param_bounds[i];
140 params[i] = rng.random::<f64>() * (max - min) + min;
141 }
142
143 parameter_grid.push(params.clone());
144
145 match system_function(¶ms) {
146 Ok(response) => {
147 exploration_points.push(params.clone());
148 response_values.push(response.clone());
149
150 let key = self.params_to_cache_key(¶ms);
151 self.response_cache.insert(key, response);
152 }
153 Err(_) => continue,
154 }
155 }
156
157 Ok(ParameterExplorationResult {
158 exploration_points,
159 response_values: response_values.clone(),
160 parameter_grid,
161 convergence_history: self.param_history.clone(),
162 exploration_method: ExplorationMethod::RandomSampling,
163 optimization_metrics: self.compute_exploration_metrics(&response_values)?,
164 })
165 }
166
167 fn adaptive_sampling_exploration<F>(
169 &mut self,
170 system_function: &F,
171 ) -> IntegrateResult<ParameterExplorationResult>
172 where
173 F: Fn(&Array1<f64>) -> IntegrateResult<Array1<f64>>,
174 {
175 let mut exploration_points = Vec::new();
176 let mut response_values = Vec::new();
177 let mut parameter_grid = Vec::new();
178
179 let coarse_samples = (self.samples_per_dim as f64).sqrt() as usize;
181 let mut currentresolution = coarse_samples;
182
183 let initial_grid = self.generate_coarse_grid(coarse_samples);
185
186 for params in &initial_grid {
187 parameter_grid.push(params.clone());
188
189 match system_function(params) {
190 Ok(response) => {
191 exploration_points.push(params.clone());
192 response_values.push(response);
193 }
194 Err(_) => continue,
195 }
196 }
197
198 while currentresolution < self.samples_per_dim {
200 let refinement_candidates =
201 self.identify_refinement_regions(&exploration_points, &response_values)?;
202
203 for region in refinement_candidates {
204 let refined_points = self.refine_region(®ion, currentresolution * 2);
205
206 for params in refined_points {
207 parameter_grid.push(params.clone());
208
209 match system_function(¶ms) {
210 Ok(response) => {
211 exploration_points.push(params.clone());
212 response_values.push(response);
213 }
214 Err(_) => continue,
215 }
216 }
217 }
218
219 currentresolution *= 2;
220 }
221
222 Ok(ParameterExplorationResult {
223 exploration_points,
224 response_values: response_values.clone(),
225 parameter_grid,
226 convergence_history: self.param_history.clone(),
227 exploration_method: ExplorationMethod::AdaptiveSampling,
228 optimization_metrics: self.compute_exploration_metrics(&response_values)?,
229 })
230 }
231
232 fn gradient_guided_exploration<F>(
234 &mut self,
235 system_function: &F,
236 ) -> IntegrateResult<ParameterExplorationResult>
237 where
238 F: Fn(&Array1<f64>) -> IntegrateResult<Array1<f64>>,
239 {
240 let mut exploration_points = Vec::new();
241 let mut response_values = Vec::new();
242 let mut parameter_grid = Vec::new();
243
244 let mut currentparams = self.currentparams.clone();
246 exploration_points.push(currentparams.clone());
247 parameter_grid.push(currentparams.clone());
248
249 let initial_response = system_function(¤tparams)?;
250 response_values.push(initial_response.clone());
251
252 let learning_rate = 0.01;
253 let max_iterations = 100;
254 let tolerance = 1e-6;
255
256 for iteration in 0..max_iterations {
257 let gradient = self.compute_numerical_gradient(system_function, ¤tparams)?;
259
260 let gradient_norm = gradient.iter().map(|&g| g * g).sum::<f64>().sqrt();
262
263 if gradient_norm < tolerance {
264 break;
265 }
266
267 for i in 0..self.param_dimensions {
269 currentparams[i] += learning_rate * gradient[i] / gradient_norm;
270
271 let (min, max) = self.param_bounds[i];
273 currentparams[i] = currentparams[i].max(min).min(max);
274 }
275
276 match system_function(¤tparams) {
278 Ok(response) => {
279 exploration_points.push(currentparams.clone());
280 parameter_grid.push(currentparams.clone());
281 response_values.push(response);
282 self.param_history.push(currentparams.clone());
283 }
284 Err(_) => {
285 currentparams = exploration_points.last().unwrap().clone();
287 break;
288 }
289 }
290
291 if iteration > 0 {
293 let current_response = response_values.last().unwrap();
294 let prev_response = &response_values[response_values.len() - 2];
295
296 let response_change = current_response
297 .iter()
298 .zip(prev_response.iter())
299 .map(|(a, b)| (a - b).abs())
300 .fold(0.0, f64::max);
301
302 if response_change < tolerance {
303 break;
304 }
305 }
306 }
307
308 self.currentparams = currentparams;
309
310 Ok(ParameterExplorationResult {
311 exploration_points,
312 response_values: response_values.clone(),
313 parameter_grid,
314 convergence_history: self.param_history.clone(),
315 exploration_method: ExplorationMethod::GradientGuided,
316 optimization_metrics: self.compute_exploration_metrics(&response_values)?,
317 })
318 }
319
320 fn generate_grid_indices(&self) -> Vec<Vec<usize>> {
322 let mut indices = Vec::new();
323 let mut current_index = vec![0; self.param_dimensions];
324
325 loop {
326 indices.push(current_index.clone());
327
328 let mut carry = 1;
330 for i in (0..self.param_dimensions).rev() {
331 current_index[i] += carry;
332 if current_index[i] < self.samples_per_dim {
333 carry = 0;
334 break;
335 } else {
336 current_index[i] = 0;
337 }
338 }
339
340 if carry == 1 {
341 break;
342 }
343 }
344
345 indices
346 }
347
348 fn indices_to_parameters(&self, indices: &[usize]) -> Array1<f64> {
349 let mut params = Array1::zeros(self.param_dimensions);
350
351 for i in 0..self.param_dimensions {
352 let (min, max) = self.param_bounds[i];
353 let fraction = indices[i] as f64 / (self.samples_per_dim - 1) as f64;
354 params[i] = min + fraction * (max - min);
355 }
356
357 params
358 }
359
360 fn params_to_cache_key(&self, params: &Array1<f64>) -> String {
361 params
362 .iter()
363 .map(|&p| format!("{p:.6}"))
364 .collect::<Vec<_>>()
365 .join(",")
366 }
367
368 fn generate_coarse_grid(&self, resolution: usize) -> Vec<Array1<f64>> {
369 let mut grid = Vec::new();
370 let mut indices = vec![0; self.param_dimensions];
371
372 loop {
373 let mut params = Array1::zeros(self.param_dimensions);
374
375 for i in 0..self.param_dimensions {
376 let (min, max) = self.param_bounds[i];
377 let fraction = indices[i] as f64 / (resolution - 1) as f64;
378 params[i] = min + fraction * (max - min);
379 }
380
381 grid.push(params);
382
383 let mut carry = 1;
385 for i in (0..self.param_dimensions).rev() {
386 indices[i] += carry;
387 if indices[i] < resolution {
388 carry = 0;
389 break;
390 } else {
391 indices[i] = 0;
392 }
393 }
394
395 if carry == 1 {
396 break;
397 }
398 }
399
400 grid
401 }
402
403 fn identify_refinement_regions(
404 &self,
405 points: &[Array1<f64>],
406 responses: &[Array1<f64>],
407 ) -> IntegrateResult<Vec<ParameterRegion>> {
408 let mut regions = Vec::new();
409
410 if responses.len() < 2 {
412 return Ok(regions);
413 }
414
415 let best_idx = responses
417 .iter()
418 .enumerate()
419 .max_by(|(_, a), (_, b)| {
420 let a_norm = a.iter().map(|&x| x * x).sum::<f64>().sqrt();
421 let b_norm = b.iter().map(|&x| x * x).sum::<f64>().sqrt();
422 a_norm
423 .partial_cmp(&b_norm)
424 .unwrap_or(std::cmp::Ordering::Equal)
425 })
426 .map(|(i, _)| i)
427 .unwrap_or(0);
428
429 let center = points[best_idx].clone();
430 let radius = 0.1; regions.push(ParameterRegion { center, radius });
433
434 Ok(regions)
435 }
436
437 fn refine_region(&self, region: &ParameterRegion, resolution: usize) -> Vec<Array1<f64>> {
438 let mut refined_points = Vec::new();
439
440 for _ in 0..resolution {
442 let mut rng = scirs2_core::random::rng();
443 let mut point = region.center.clone();
444
445 for i in 0..self.param_dimensions {
446 let (min, max) = self.param_bounds[i];
447 let range = (max - min) * region.radius;
448 let offset = (rng.random::<f64>() - 0.5) * 2.0 * range;
449 point[i] = (point[i] + offset).max(min).min(max);
450 }
451
452 refined_points.push(point);
453 }
454
455 refined_points
456 }
457
458 fn compute_numerical_gradient<F>(
459 &self,
460 system_function: &F,
461 params: &Array1<f64>,
462 ) -> IntegrateResult<Array1<f64>>
463 where
464 F: Fn(&Array1<f64>) -> IntegrateResult<Array1<f64>>,
465 {
466 let epsilon = 1e-6;
467 let mut gradient = Array1::zeros(self.param_dimensions);
468
469 let base_response = system_function(params)?;
470 let _base_norm = base_response.iter().map(|&x| x * x).sum::<f64>().sqrt();
471
472 for i in 0..self.param_dimensions {
473 let mut params_plus = params.clone();
474 let mut params_minus = params.clone();
475
476 params_plus[i] += epsilon;
477 params_minus[i] -= epsilon;
478
479 let response_plus = system_function(¶ms_plus)?;
480 let response_minus = system_function(¶ms_minus)?;
481
482 let norm_plus = response_plus.iter().map(|&x| x * x).sum::<f64>().sqrt();
483 let norm_minus = response_minus.iter().map(|&x| x * x).sum::<f64>().sqrt();
484
485 gradient[i] = (norm_plus - norm_minus) / (2.0 * epsilon);
486 }
487
488 Ok(gradient)
489 }
490
491 fn compute_exploration_metrics(
492 &self,
493 responses: &[Array1<f64>],
494 ) -> IntegrateResult<ExplorationMetrics> {
495 if responses.is_empty() {
496 return Ok(ExplorationMetrics {
497 max_response_norm: 0.0,
498 min_response_norm: 0.0,
499 mean_response_norm: 0.0,
500 response_variance: 0.0,
501 coverage_efficiency: 0.0,
502 });
503 }
504
505 let norms: Vec<f64> = responses
506 .iter()
507 .map(|r| r.iter().map(|&x| x * x).sum::<f64>().sqrt())
508 .collect();
509
510 let max_norm = norms.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
511 let min_norm = norms.iter().fold(f64::INFINITY, |a, &b| a.min(b));
512 let mean_norm = norms.iter().sum::<f64>() / norms.len() as f64;
513
514 let variance =
515 norms.iter().map(|&x| (x - mean_norm).powi(2)).sum::<f64>() / norms.len() as f64;
516
517 let coverage_efficiency = if max_norm > min_norm {
519 variance / (max_norm - min_norm).powi(2)
520 } else {
521 1.0
522 };
523
524 Ok(ExplorationMetrics {
525 max_response_norm: max_norm,
526 min_response_norm: min_norm,
527 mean_response_norm: mean_norm,
528 response_variance: variance,
529 coverage_efficiency,
530 })
531 }
532}
533
534#[derive(Debug, Clone)]
536pub struct BifurcationDiagramGenerator {
537 pub parameter_range: (f64, f64),
539 pub n_parameter_samples: usize,
541 pub transient_steps: usize,
543 pub sampling_steps: usize,
545 pub fixed_point_tolerance: f64,
547 pub period_tolerance: f64,
549}
550
551impl BifurcationDiagramGenerator {
552 pub fn new(_parameterrange: (f64, f64), n_parameter_samples: usize) -> Self {
554 Self {
555 parameter_range: _parameterrange,
556 n_parameter_samples,
557 transient_steps: 1000,
558 sampling_steps: 500,
559 fixed_point_tolerance: 1e-8,
560 period_tolerance: 1e-6,
561 }
562 }
563
564 pub fn generate_1d_map_diagram<F>(
566 &self,
567 map_function: F,
568 initial_condition: f64,
569 ) -> IntegrateResult<BifurcationDiagram>
570 where
571 F: Fn(f64, f64) -> f64, {
573 let mut parameter_values = Vec::new();
574 let mut state_values = Vec::new();
575 let mut stability_flags = Vec::new();
576 let mut bifurcation_points = Vec::new();
577
578 let param_step = (self.parameter_range.1 - self.parameter_range.0)
579 / (self.n_parameter_samples - 1) as f64;
580
581 for i in 0..self.n_parameter_samples {
582 let param = self.parameter_range.0 + i as f64 * param_step;
583
584 let mut x = initial_condition;
586 for _ in 0..self.transient_steps {
587 x = map_function(x, param);
588 }
589
590 let mut attractorstates = Vec::new();
592 for _ in 0..self.sampling_steps {
593 x = map_function(x, param);
594 attractorstates.push(x);
595 }
596
597 let attractor_info = self.analyze_1d_attractor(&attractorstates)?;
599
600 for &state in &attractor_info.representative_states {
602 parameter_values.push(param);
603 state_values.push(state);
604 stability_flags.push(attractor_info.is_stable);
605 }
606
607 if i > 0 {
609 let prev_param = parameter_values
610 [parameter_values.len() - attractor_info.representative_states.len() - 1];
611 if let Some(bif_point) =
612 self.detect_bifurcation_1d(prev_param, param, &map_function, initial_condition)?
613 {
614 bifurcation_points.push(bif_point);
615 }
616 }
617 }
618
619 Ok(BifurcationDiagram {
620 parameters: parameter_values,
621 states: vec![state_values],
622 stability: stability_flags,
623 bifurcation_points,
624 })
625 }
626
627 fn analyze_1d_attractor(&self, states: &[f64]) -> IntegrateResult<AttractorInfo> {
629 let mut representative_states = Vec::new();
631 let mut uniquestates = states.to_vec();
632 uniquestates.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
633 uniquestates.dedup_by(|a, b| (*a - *b).abs() < self.fixed_point_tolerance);
634
635 if uniquestates.len() == 1 {
636 representative_states.push(uniquestates[0]);
638 } else {
639 let period = self.detect_period(states)?;
641
642 if period > 0 && period <= self.sampling_steps / 10 {
643 for i in 0..period {
645 representative_states.push(states[states.len() - period + i]);
646 }
647 } else {
648 let sample_rate = states.len() / 20;
650 for i in (0..states.len()).step_by(sample_rate) {
651 representative_states.push(states[i]);
652 }
653 }
654 }
655
656 let is_stable = uniquestates.len() <= 2; Ok(AttractorInfo {
660 representative_states,
661 is_stable,
662 period: if uniquestates.len() <= 2 {
663 uniquestates.len()
664 } else {
665 0
666 },
667 })
668 }
669
670 fn detect_period(&self, states: &[f64]) -> IntegrateResult<usize> {
672 let max_period = (self.sampling_steps / 10).min(50);
673
674 for period in 1..=max_period {
675 let mut is_periodic = true;
676
677 for i in 0..(states.len() - period) {
678 if (states[i] - states[i + period]).abs() > self.period_tolerance {
679 is_periodic = false;
680 break;
681 }
682 }
683
684 if is_periodic {
685 return Ok(period);
686 }
687 }
688
689 Ok(0) }
691
692 fn detect_bifurcation_1d<F>(
694 &self,
695 param1: f64,
696 param2: f64,
697 map_function: &F,
698 initial_condition: f64,
699 ) -> IntegrateResult<Option<BifurcationPoint>>
700 where
701 F: Fn(f64, f64) -> f64,
702 {
703 let attractor1 = self.sample_attractor_1d(map_function, param1, initial_condition)?;
705 let attractor2 = self.sample_attractor_1d(map_function, param2, initial_condition)?;
706
707 let info1 = self.analyze_1d_attractor(&attractor1)?;
708 let info2 = self.analyze_1d_attractor(&attractor2)?;
709
710 if info1.representative_states.len() != info2.representative_states.len() {
711 let bif_param = (param1 + param2) / 2.0;
713 let bif_state = Array1::from_vec(vec![info1.representative_states[0]]);
714
715 let bif_type = match (
716 info1.representative_states.len(),
717 info2.representative_states.len(),
718 ) {
719 (1, 2) => crate::analysis::BifurcationType::PeriodDoubling,
720 (1, _) => crate::analysis::BifurcationType::PeriodDoubling,
721 (_, 1) => crate::analysis::BifurcationType::Fold,
722 _ => crate::analysis::BifurcationType::Unknown,
723 };
724
725 return Ok(Some(BifurcationPoint {
726 parameter_value: bif_param,
727 state: bif_state,
728 bifurcation_type: bif_type,
729 eigenvalues: vec![], }));
731 }
732
733 Ok(None)
734 }
735
736 fn sample_attractor_1d<F>(
738 &self,
739 map_function: &F,
740 param: f64,
741 initial_condition: f64,
742 ) -> IntegrateResult<Vec<f64>>
743 where
744 F: Fn(f64, f64) -> f64,
745 {
746 let mut x = initial_condition;
747
748 for _ in 0..self.transient_steps {
750 x = map_function(x, param);
751 }
752
753 let mut states = Vec::new();
755 for _ in 0..self.sampling_steps {
756 x = map_function(x, param);
757 states.push(x);
758 }
759
760 Ok(states)
761 }
762}