1use std::collections::VecDeque;
4
5use crate::error::{M1ndError, M1ndResult};
6use crate::graph::Graph;
7use crate::types::*;
8
9pub const DEFAULT_NUM_HARMONICS: u8 = 5;
15pub const DEFAULT_SWEEP_STEPS: u32 = 20;
17pub const DEFAULT_PULSE_BUDGET: u64 = 50_000;
19pub const REFLECTION_PHASE_SHIFT: f32 = std::f32::consts::PI;
21pub const HUB_REFLECTION_THRESHOLD: f32 = 4.0;
23pub const HUB_REFLECTION_COEFF: f32 = 0.3;
25
26#[derive(Clone, Copy, Debug)]
35pub struct WavePulse {
36 pub node: NodeId,
37 pub amplitude: FiniteF32,
39 pub phase: FiniteF32,
41 pub frequency: PosF32,
43 pub wavelength: PosF32,
45 pub hops: u8,
47 pub prev_node: NodeId,
49}
50
51#[derive(Clone, Copy, Debug, Default)]
58pub struct WaveAccumulator {
59 pub real: FiniteF32,
61 pub imag: FiniteF32,
63}
64
65impl WaveAccumulator {
66 pub fn accumulate(&mut self, pulse: &WavePulse) {
68 let (sin_p, cos_p) = pulse.phase.get().sin_cos();
69 let amp = pulse.amplitude.get();
70 self.real = FiniteF32::new(self.real.get() + amp * cos_p);
71 self.imag = FiniteF32::new(self.imag.get() + amp * sin_p);
72 }
73
74 pub fn amplitude(&self) -> FiniteF32 {
76 let r = self.real.get();
77 let i = self.imag.get();
78 FiniteF32::new((r * r + i * i).sqrt())
79 }
80
81 pub fn phase(&self) -> FiniteF32 {
83 FiniteF32::new(self.imag.get().atan2(self.real.get()))
84 }
85}
86
87#[derive(Clone, Debug)]
94pub struct StandingWaveResult {
95 pub accumulators: Vec<WaveAccumulator>,
97 pub antinodes: Vec<(NodeId, FiniteF32)>,
99 pub wave_nodes: Vec<NodeId>,
101 pub total_energy: FiniteF32,
103 pub pulses_processed: u64,
105}
106
107pub struct StandingWavePropagator {
110 max_hops: u8,
111 min_amplitude: FiniteF32,
112 pulse_budget: u64,
113}
114
115impl StandingWavePropagator {
116 pub fn new(max_hops: u8, min_amplitude: FiniteF32, pulse_budget: u64) -> Self {
117 Self {
118 max_hops,
119 min_amplitude,
120 pulse_budget,
121 }
122 }
123
124 pub fn propagate(
130 &self,
131 graph: &Graph,
132 seeds: &[(NodeId, FiniteF32)],
133 frequency: PosF32,
134 wavelength: PosF32,
135 ) -> M1ndResult<StandingWaveResult> {
136 let n = graph.num_nodes() as usize;
137 let mut accumulators = vec![WaveAccumulator::default(); n];
138 let mut pulse_count = 0u64;
139
140 let avg_degree = graph.avg_degree();
141 let mut queue = VecDeque::new();
142
143 for &(node, amp) in seeds {
145 if node.as_usize() >= n {
146 continue;
147 }
148 let pulse = WavePulse {
149 node,
150 amplitude: amp,
151 phase: FiniteF32::ZERO,
152 frequency,
153 wavelength,
154 hops: 0,
155 prev_node: node,
156 };
157 accumulators[node.as_usize()].accumulate(&pulse);
158 queue.push_back(pulse);
159 pulse_count += 1;
160 }
161
162 while let Some(pulse) = queue.pop_front() {
163 if pulse_count >= self.pulse_budget {
164 break; }
166 if pulse.hops >= self.max_hops {
167 continue;
168 }
169 if pulse.amplitude.get().abs() < self.min_amplitude.get() {
170 continue;
171 }
172
173 let range = graph.csr.out_range(pulse.node);
174 let out_degree = (range.end - range.start) as f32;
175
176 if out_degree == 0.0 || (out_degree == 1.0 && pulse.hops > 0) {
178 let reflected = WavePulse {
180 node: pulse.prev_node,
181 amplitude: FiniteF32::new(pulse.amplitude.get() * 0.9), phase: FiniteF32::new(
183 (pulse.phase.get() + REFLECTION_PHASE_SHIFT) % (2.0 * std::f32::consts::PI),
184 ),
185 frequency,
186 wavelength,
187 hops: pulse.hops + 1,
188 prev_node: pulse.node,
189 };
190 if reflected.amplitude.get().abs() >= self.min_amplitude.get() {
191 accumulators[reflected.node.as_usize()].accumulate(&reflected);
192 queue.push_back(reflected);
193 pulse_count += 1;
194 }
195 continue;
196 }
197
198 let phase_advance = 2.0 * std::f32::consts::PI * frequency.get() / wavelength.get();
200
201 let is_hub = avg_degree > 0.0 && out_degree / avg_degree > HUB_REFLECTION_THRESHOLD;
203
204 if is_hub {
205 let reflected_amp = pulse.amplitude.get() * HUB_REFLECTION_COEFF;
207 let reflected = WavePulse {
208 node: pulse.prev_node,
209 amplitude: FiniteF32::new(reflected_amp),
210 phase: FiniteF32::new(
211 (pulse.phase.get() + REFLECTION_PHASE_SHIFT) % (2.0 * std::f32::consts::PI),
212 ),
213 frequency,
214 wavelength,
215 hops: pulse.hops + 1,
216 prev_node: pulse.node,
217 };
218 if reflected.amplitude.get().abs() >= self.min_amplitude.get()
219 && reflected.node.as_usize() < n
220 {
221 accumulators[reflected.node.as_usize()].accumulate(&reflected);
222 queue.push_back(reflected);
223 pulse_count += 1;
224 }
225 }
226
227 let transmission = if is_hub {
229 1.0 - HUB_REFLECTION_COEFF
230 } else {
231 1.0
232 };
233
234 for j in range {
235 if pulse_count >= self.pulse_budget {
236 break;
237 }
238 let tgt = graph.csr.targets[j];
239 if tgt == pulse.prev_node {
240 continue; }
242 let tgt_idx = tgt.as_usize();
243 if tgt_idx >= n {
244 continue;
245 }
246
247 let w = graph.csr.read_weight(EdgeIdx::new(j as u32)).get();
248 let new_amp = pulse.amplitude.get() * w * transmission / out_degree.max(1.0);
249 let new_phase = (pulse.phase.get() + phase_advance) % (2.0 * std::f32::consts::PI);
250
251 if new_amp.abs() < self.min_amplitude.get() {
252 continue;
253 }
254
255 let new_pulse = WavePulse {
256 node: tgt,
257 amplitude: FiniteF32::new(new_amp),
258 phase: FiniteF32::new(new_phase),
259 frequency,
260 wavelength,
261 hops: pulse.hops + 1,
262 prev_node: pulse.node,
263 };
264
265 accumulators[tgt_idx].accumulate(&new_pulse);
266 queue.push_back(new_pulse);
267 pulse_count += 1;
268 }
269 }
270
271 let mut antinodes: Vec<(NodeId, FiniteF32)> = accumulators
273 .iter()
274 .enumerate()
275 .map(|(i, acc)| (NodeId::new(i as u32), acc.amplitude()))
276 .filter(|(_, a)| a.get() > self.min_amplitude.get())
277 .collect();
278 antinodes.sort_by(|a, b| b.1.cmp(&a.1));
279
280 let wave_nodes: Vec<NodeId> = accumulators
281 .iter()
282 .enumerate()
283 .filter(|(_, acc)| {
284 acc.amplitude().get() < self.min_amplitude.get() * 2.0
285 && acc.amplitude().get() > 0.0
286 })
287 .map(|(i, _)| NodeId::new(i as u32))
288 .collect();
289
290 let total_energy: f32 = accumulators
291 .iter()
292 .map(|a| {
293 let amp = a.amplitude().get();
294 amp * amp
295 })
296 .sum();
297
298 Ok(StandingWaveResult {
299 accumulators,
300 antinodes,
301 wave_nodes,
302 total_energy: FiniteF32::new(total_energy.sqrt()),
303 pulses_processed: pulse_count,
304 })
305 }
306}
307
308#[derive(Clone, Debug)]
314pub struct HarmonicResult {
315 pub harmonic: u8,
316 pub frequency: PosF32,
317 pub total_energy: FiniteF32,
318 pub antinodes: Vec<(NodeId, FiniteF32)>,
319}
320
321#[derive(Clone, Debug)]
324pub struct HarmonicAnalysis {
325 pub harmonics: Vec<HarmonicResult>,
326 pub harmonic_groups: Vec<Vec<NodeId>>,
328}
329
330pub struct HarmonicAnalyzer {
333 propagator: StandingWavePropagator,
334 num_harmonics: u8,
335}
336
337impl HarmonicAnalyzer {
338 pub fn new(propagator: StandingWavePropagator, num_harmonics: u8) -> Self {
339 Self {
340 propagator,
341 num_harmonics,
342 }
343 }
344
345 pub fn analyze(
348 &self,
349 graph: &Graph,
350 seeds: &[(NodeId, FiniteF32)],
351 base_frequency: PosF32,
352 base_wavelength: PosF32,
353 ) -> M1ndResult<HarmonicAnalysis> {
354 let mut harmonics = Vec::new();
355
356 for h in 1..=self.num_harmonics {
357 let freq = PosF32::new(base_frequency.get() * h as f32).unwrap();
358 let wl = PosF32::new(base_wavelength.get() / h as f32).unwrap();
359
360 let result = self.propagator.propagate(graph, seeds, freq, wl)?;
361
362 harmonics.push(HarmonicResult {
363 harmonic: h,
364 frequency: freq,
365 total_energy: result.total_energy,
366 antinodes: result.antinodes,
367 });
368 }
369
370 let n = graph.num_nodes() as usize;
372 let mut node_harmonics: Vec<Vec<u8>> = vec![Vec::new(); n];
373 for hr in &harmonics {
374 for &(node, _) in &hr.antinodes {
375 if node.as_usize() < n {
376 node_harmonics[node.as_usize()].push(hr.harmonic);
377 }
378 }
379 }
380
381 let mut groups: std::collections::HashMap<Vec<u8>, Vec<NodeId>> =
383 std::collections::HashMap::new();
384 for i in 0..n {
385 if !node_harmonics[i].is_empty() {
386 groups
387 .entry(node_harmonics[i].clone())
388 .or_default()
389 .push(NodeId::new(i as u32));
390 }
391 }
392
393 let harmonic_groups: Vec<Vec<NodeId>> = groups.into_values().collect();
394
395 Ok(HarmonicAnalysis {
396 harmonics,
397 harmonic_groups,
398 })
399 }
400}
401
402#[derive(Clone, Debug)]
408pub struct ResonantFrequency {
409 pub frequency: PosF32,
410 pub total_energy: FiniteF32,
411}
412
413pub struct ResonantFrequencyDetector {
416 propagator: StandingWavePropagator,
417 sweep_steps: u32,
418}
419
420impl ResonantFrequencyDetector {
421 pub fn new(propagator: StandingWavePropagator, sweep_steps: u32) -> Self {
422 Self {
423 propagator,
424 sweep_steps,
425 }
426 }
427
428 pub fn detect(
431 &self,
432 graph: &Graph,
433 seeds: &[(NodeId, FiniteF32)],
434 freq_min: PosF32,
435 freq_max: PosF32,
436 ) -> M1ndResult<Vec<ResonantFrequency>> {
437 let step = (freq_max.get() - freq_min.get()) / self.sweep_steps.max(1) as f32;
438 let mut energies = Vec::new();
439
440 for i in 0..self.sweep_steps {
441 let f = freq_min.get() + step * i as f32;
442 let freq = PosF32::new(f.max(0.01)).unwrap();
443 let wl = PosF32::new((10.0 / f).max(0.1)).unwrap(); let result = self.propagator.propagate(graph, seeds, freq, wl)?;
445 energies.push(ResonantFrequency {
446 frequency: freq,
447 total_energy: result.total_energy,
448 });
449 }
450
451 let mut peaks = Vec::new();
453 for i in 1..energies.len().saturating_sub(1) {
454 let prev = energies[i - 1].total_energy.get();
455 let curr = energies[i].total_energy.get();
456 let next = energies[i + 1].total_energy.get();
457 if curr > prev && curr > next {
458 peaks.push(energies[i].clone());
459 }
460 }
461
462 peaks.sort_by(|a, b| b.total_energy.cmp(&a.total_energy));
463 Ok(peaks)
464 }
465}
466
467#[derive(Clone, Debug)]
474pub struct SympatheticResult {
475 pub source_seeds: Vec<NodeId>,
477 pub sympathetic_nodes: Vec<(NodeId, FiniteF32)>,
479 pub checked_disconnected: bool,
481}
482
483pub struct SympatheticResonanceDetector {
486 propagator: StandingWavePropagator,
487 min_resonance: FiniteF32,
488}
489
490impl SympatheticResonanceDetector {
491 pub fn new(propagator: StandingWavePropagator, min_resonance: FiniteF32) -> Self {
492 Self {
493 propagator,
494 min_resonance,
495 }
496 }
497
498 pub fn detect(
502 &self,
503 graph: &Graph,
504 source_seeds: &[(NodeId, FiniteF32)],
505 frequency: PosF32,
506 wavelength: PosF32,
507 ) -> M1ndResult<SympatheticResult> {
508 let result = self
509 .propagator
510 .propagate(graph, source_seeds, frequency, wavelength)?;
511
512 let n = graph.num_nodes() as usize;
514 let mut seed_neighborhood = vec![false; n];
515 for &(s, _) in source_seeds {
516 let idx = s.as_usize();
517 if idx < n {
518 seed_neighborhood[idx] = true;
519 let range = graph.csr.out_range(s);
520 for j in range {
521 let tgt = graph.csr.targets[j].as_usize();
522 if tgt < n {
523 seed_neighborhood[tgt] = true;
524 let range2 = graph.csr.out_range(graph.csr.targets[j]);
526 for k in range2 {
527 let tgt2 = graph.csr.targets[k].as_usize();
528 if tgt2 < n {
529 seed_neighborhood[tgt2] = true;
530 }
531 }
532 }
533 }
534 }
535 }
536
537 let sympathetic_nodes: Vec<(NodeId, FiniteF32)> = result
539 .antinodes
540 .iter()
541 .filter(|&&(node, amp)| {
542 !seed_neighborhood[node.as_usize()] && amp.get() >= self.min_resonance.get()
543 })
544 .cloned()
545 .collect();
546
547 Ok(SympatheticResult {
548 source_seeds: source_seeds.iter().map(|s| s.0).collect(),
549 sympathetic_nodes,
550 checked_disconnected: true,
551 })
552 }
553}
554
555pub struct ResonanceEngine {
562 pub propagator: StandingWavePropagator,
563 pub harmonic_analyzer: HarmonicAnalyzer,
564 pub frequency_detector: ResonantFrequencyDetector,
565 pub sympathetic_detector: SympatheticResonanceDetector,
566}
567
568impl ResonanceEngine {
569 pub fn with_defaults() -> Self {
570 let propagator =
571 StandingWavePropagator::new(10, FiniteF32::new(0.01), DEFAULT_PULSE_BUDGET);
572 Self {
573 harmonic_analyzer: HarmonicAnalyzer::new(
574 StandingWavePropagator::new(10, FiniteF32::new(0.01), DEFAULT_PULSE_BUDGET),
575 DEFAULT_NUM_HARMONICS,
576 ),
577 frequency_detector: ResonantFrequencyDetector::new(
578 StandingWavePropagator::new(10, FiniteF32::new(0.01), DEFAULT_PULSE_BUDGET),
579 DEFAULT_SWEEP_STEPS,
580 ),
581 sympathetic_detector: SympatheticResonanceDetector::new(
582 StandingWavePropagator::new(10, FiniteF32::new(0.01), DEFAULT_PULSE_BUDGET),
583 FiniteF32::new(0.05),
584 ),
585 propagator,
586 }
587 }
588
589 pub fn analyze(
592 &self,
593 graph: &Graph,
594 seeds: &[(NodeId, FiniteF32)],
595 ) -> M1ndResult<ResonanceReport> {
596 let base_freq = PosF32::new(1.0).unwrap();
597 let base_wl = PosF32::new(4.0).unwrap();
598
599 let standing_wave = self
600 .propagator
601 .propagate(graph, seeds, base_freq, base_wl)?;
602 let harmonics = self
603 .harmonic_analyzer
604 .analyze(graph, seeds, base_freq, base_wl)?;
605 let resonant_frequencies = self.frequency_detector.detect(
606 graph,
607 seeds,
608 PosF32::new(0.1).unwrap(),
609 PosF32::new(10.0).unwrap(),
610 )?;
611 let sympathetic = self
612 .sympathetic_detector
613 .detect(graph, seeds, base_freq, base_wl)?;
614
615 Ok(ResonanceReport {
616 standing_wave,
617 harmonics,
618 resonant_frequencies,
619 sympathetic,
620 })
621 }
622
623 pub fn export_wave_pattern(
626 &self,
627 result: &StandingWaveResult,
628 graph: &Graph,
629 ) -> M1ndResult<WavePatternExport> {
630 let n = graph.num_nodes() as usize;
631 let nodes: Vec<WavePatternNode> = (0..n)
632 .map(|i| {
633 let acc = &result.accumulators[i];
634 let amp = acc.amplitude().get();
635 let is_antinode = amp > 0.1;
636
637 let label = graph.strings.resolve(graph.nodes.label[i]);
639
640 WavePatternNode {
641 node_id: label.to_string(),
642 amplitude: amp,
643 phase: acc.phase().get(),
644 is_antinode,
645 }
646 })
647 .collect();
648
649 Ok(WavePatternExport { nodes })
650 }
651}
652
653#[derive(Clone, Debug)]
655pub struct ResonanceReport {
656 pub standing_wave: StandingWaveResult,
657 pub harmonics: HarmonicAnalysis,
658 pub resonant_frequencies: Vec<ResonantFrequency>,
659 pub sympathetic: SympatheticResult,
660}
661
662#[derive(Clone, Debug, serde::Serialize)]
664pub struct WavePatternExport {
665 pub nodes: Vec<WavePatternNode>,
666}
667
668#[derive(Clone, Debug, serde::Serialize)]
669pub struct WavePatternNode {
670 pub node_id: String,
671 pub amplitude: f32,
672 pub phase: f32,
673 pub is_antinode: bool,
674}
675
676static_assertions::assert_impl_all!(ResonanceEngine: Send, Sync);