eryon_rt/frag/impls/
impl_fragment.rs

1/*
2    Appellation: fragment <module>
3    Contrib: @FL03
4*/
5use crate::actors::prelude::{Driver, OperatorKind, TriadDriver, VNode};
6use crate::frag::FragTonnetz;
7use crate::types::{NodeCapabilities, NodeMessage};
8use rshyper::{EdgeId, VertexId};
9use rstmt::nrt::{LPR, Triad};
10use rstmt::{Aspn as Note, PitchMod};
11use std::collections::{HashMap, HashSet};
12
13impl<D> FragTonnetz<D>
14where
15    D: Driver<Triad>,
16{
17    /// Add a triad to the graph and create a VNode for it
18    pub fn add_triad(&mut self, triad: Triad) -> crate::Result<EdgeId> {
19        // Create vertices for each note if they don't exist
20        let mut vertex_ids = Vec::with_capacity(3);
21
22        for note_class in triad.notes() {
23            // Check if a vertex with this pitch class already exists
24            if let Some(id) = self.find_vertex_by_pitch(note_class) {
25                vertex_ids.push(id);
26            } else {
27                // Create new vertex
28                let note = Note::new(note_class, triad.octave());
29                let vertex_id = self.add_note(note)?;
30                vertex_ids.push(vertex_id);
31            }
32        }
33
34        // Create the hyperedge representing the triad
35        let edge_id = self.add_edge(vertex_ids)?;
36
37        // Create plant and VNode for this triad
38        let plant = D::from_headspace(triad);
39        let vnode = VNode::from_driver(plant);
40
41        // Add to partitions
42        self.partitions_mut().insert(edge_id, vnode);
43
44        Ok(edge_id)
45    }
46    // Incremental transformation computation
47    pub fn add_transformation(&mut self, edge_id: EdgeId) -> crate::Result<()> {
48        if let Some(source_node) = self.get_vnode(&edge_id) {
49            let source_triad = *source_node.driver().headspace();
50            let mut edge_transformations = HashMap::new();
51
52            // Try all possible transformations
53            for transform in [LPR::Leading, LPR::Parallel, LPR::Relative] {
54                let target_triad = source_triad.transform(transform);
55                // Check if the transformed triad exists
56                if let Some(target_edge) = self.find_edge_by_notes(target_triad.notes()) {
57                    edge_transformations.insert(transform, target_edge);
58                }
59            }
60
61            // Store transformations
62            if !edge_transformations.is_empty() {
63                self.transformations.insert(edge_id, edge_transformations);
64            }
65
66            // Check for incoming transformations from other nodes
67            for (&other_edge, other_node) in &self.partitions {
68                if other_edge == edge_id {
69                    continue;
70                }
71
72                let other_triad = *other_node.driver().headspace();
73
74                for transform in LPR::iter() {
75                    let transformed = other_triad.transform(transform);
76                    if transformed.notes() == source_triad.notes() {
77                        // Add incoming transformation
78                        self.transformations
79                            .entry(other_edge)
80                            .or_default()
81                            .insert(transform, edge_id);
82                    }
83                }
84            }
85
86            Ok(())
87        } else {
88            Err(crate::RuntimeError::NodeNotFound)
89        }
90    }
91    /// Balance resources across nodes
92    pub fn balance_resources(&mut self) -> crate::Result<()> {
93        // TODO: Implement resource balancing
94        // currently just a placeholder
95        // true implementation would consider resource usage to make the necessary adjustments
96
97        // For now, just optimize memory on all nodes
98        for node in self.partitions_mut().values_mut() {
99            node.optimize_memory(1000)?;
100        }
101
102        Ok(())
103    }
104    // Standard version of batch transform without requiring rayon
105    pub fn batch_transform_sequential(
106        &mut self,
107        operations: Vec<(EdgeId, Vec<LPR>)>,
108    ) -> crate::Result<()>
109    where
110        D: TriadDriver,
111    {
112        for (edge_id, transforms) in operations {
113            let node = self.get_vnode_mut(&edge_id).expect("Node not found");
114            node.transform_batch(&transforms)?;
115        }
116        Ok(())
117    }
118    /// Calculate capabilities of all nodes
119    pub fn calculate_node_capabilities(&self) -> HashMap<EdgeId, NodeCapabilities> {
120        self.partitions
121            .iter()
122            .map(|(&id, node)| {
123                (
124                    id,
125                    NodeCapabilities {
126                        memory_usage: node.store().size(),
127                        compute_capability: if node.has_surface_network() { 10 } else { 5 },
128                        learning_capability: node.has_surface_network(),
129                        feature_count: node.total_features(),
130                    },
131                )
132            })
133            .collect()
134    }
135    /// Calculate the resource utilization of this fragment
136    pub fn calculate_resource_usage(&self) -> (usize, usize) {
137        // Memory usage: total number of features across all nodes
138        let memory_usage = self
139            .partitions()
140            .values()
141            .map(|node| node.total_features())
142            .sum();
143
144        // Compute usage: complexity based on number of nodes and edges
145        let compute_usage = self.total_partitions() * 10 + self.total_edges();
146
147        (memory_usage, compute_usage)
148    }
149    /// Collect proposed transformations from agent nodes
150    pub fn collect_proposed_transformations(&self) -> Vec<(EdgeId, LPR)> {
151        let mut proposals = Vec::new();
152
153        for (&edge_id, node) in self.partitions() {
154            if let OperatorKind::Agent = node.kind() {
155                if let Some(transform) = node.propose_transformation() {
156                    proposals.push((edge_id, transform));
157                }
158            }
159        }
160
161        proposals
162    }
163    /// Compute and store all possible transformations between triads in the fragment
164    pub fn compute_transformations(&mut self) {
165        // Clear existing transformations
166        self.transformations_mut().clear();
167
168        // For each pair of nodes, check if there's a transformation between them
169        let edges: Vec<EdgeId> = self.partitions().keys().cloned().collect();
170
171        for &source_edge in &edges {
172            let mut edge_transformations = HashMap::new();
173
174            if let Some(source_node) = self.get_vnode(&source_edge) {
175                let source_triad = *source_node.driver().headspace();
176
177                // Try all possible transformations
178                for transform in [LPR::Leading, LPR::Parallel, LPR::Relative] {
179                    let target_triad = source_triad.transform(transform);
180
181                    // Check if the transformed triad exists in our fragment
182                    if let Some(target_edge) = self.find_edge_by_notes(target_triad.notes()) {
183                        edge_transformations.insert(transform, target_edge);
184                    }
185                }
186            }
187
188            // Store transformations for this edge
189            if !edge_transformations.is_empty() {
190                self.transformations
191                    .insert(source_edge, edge_transformations);
192            }
193        }
194    }
195    /// Coordinate learning between nodes with similar contexts
196    pub fn coordinate_learning(&mut self) -> crate::Result<()> {
197        // Early exit for small fragments
198        if self.partitions().len() <= 1 {
199            return Ok(());
200        }
201
202        // 1. Identify nodes with knowledge (have surface networks)
203        // Use filter_map to avoid the separate collect and iteration
204        let knowledge_nodes: Vec<EdgeId> = self
205            .partitions
206            .iter()
207            .filter_map(|(&id, node)| {
208                if node.has_surface_network() && node.total_features() > 0 {
209                    Some(id)
210                } else {
211                    None
212                }
213            })
214            .collect();
215
216        if knowledge_nodes.len() <= 1 {
217            return Ok(());
218        }
219
220        // 2. Simple node distribution - only process a fixed number of transfers
221        // This avoids quadratic behavior by capping the total operations
222        let total_transfers = core::cmp::min(50, knowledge_nodes.len() * 3);
223        let mut transfers_done = 0;
224
225        // Iterate through source nodes
226        for source_id in &knowledge_nodes {
227            if transfers_done >= total_transfers {
228                break;
229            }
230
231            // Extract patterns once per source (key optimization)
232            let patterns = match self
233                .get_vnode(source_id)
234                .and_then(|node| node.extract_knowledge_patterns().ok())
235            {
236                Some(p) if !p.is_empty() => p,
237                _ => continue,
238            };
239
240            // Share with just a few target nodes (limit fan-out)
241            let max_targets = std::cmp::min(3, knowledge_nodes.len() - 1);
242            let mut targets_for_this_source = 0;
243
244            // Share knowledge with targets
245            for target_id in &knowledge_nodes {
246                if source_id == target_id || targets_for_this_source >= max_targets {
247                    continue;
248                }
249
250                // Apply the transfer
251                if let Some(target) = self.get_vnode_mut(target_id) {
252                    if let Ok(_) = target.integrate_external_knowledge(&patterns, source_id) {
253                        targets_for_this_source += 1;
254                        transfers_done += 1;
255                    }
256                }
257
258                if transfers_done >= total_transfers {
259                    break;
260                }
261            }
262        }
263
264        // 3. Record simple event - don't iterate all nodes again
265        if transfers_done > 0 {
266            // Just mark event on the first few nodes as an optimization
267            for (_, node) in self.partitions.iter_mut().take(5) {
268                node.store_mut()
269                    .record_event("knowledge_coordination", Some(vec![knowledge_nodes.len()]));
270            }
271        }
272
273        Ok(())
274    }
275    /// Create a new node with the specified triad
276    pub fn create_node(&mut self, triad: Triad) -> crate::Result<EdgeId> {
277        // Check if this triad already exists
278        if let Some(edge_id) = self.find_edge_by_notes(triad.notes()) {
279            return Ok(edge_id); // Return existing edge ID
280        }
281
282        // Create new node
283        self.add_triad(triad)
284    }
285    /// Execute a transformation path
286    pub fn execute_transformation_path(&mut self, path: &[(EdgeId, LPR)]) -> crate::Result<()>
287    where
288        D: TriadDriver,
289    {
290        for &(node_id, transform) in path {
291            self.send_command(node_id, transform)?;
292        }
293        Ok(())
294    }
295    /// Find an edge by triad notes
296    pub fn find_edge_by_notes(&self, notes: [usize; 3]) -> Option<EdgeId> {
297        self.partitions.iter().find_map(|(&edge_id, vnode)| {
298            if vnode.get_notes() == notes {
299                Some(edge_id)
300            } else {
301                None
302            }
303        })
304    }
305    /// Find a VNode by its ID
306    pub fn get_vnode(&self, id: &EdgeId) -> Option<&VNode<D>> {
307        self.partitions().get(id)
308    }
309    /// Find a VNode by its ID (mutable)
310    pub fn get_vnode_mut(&mut self, id: &EdgeId) -> Option<&mut VNode<D>> {
311        self.partitions_mut().get_mut(id)
312    }
313    /// Find a node by its name
314    pub fn find_node_by_name(&self, name: &str) -> Option<&VNode<D>> {
315        self.partitions()
316            .values()
317            .find(|node| node.store().get_property("name").is_some_and(|n| n == name))
318    }
319    /// Find optimal node for offloading work
320    pub fn find_optimal_node_for(&self, work_type: &str) -> Option<EdgeId> {
321        let capabilities = self.calculate_node_capabilities();
322
323        match work_type {
324            "learning" => capabilities
325                .iter()
326                .filter(|(_, cap)| cap.learning_capability)
327                .min_by_key(|(_, cap)| cap.memory_usage)
328                .map(|(&id, _)| id),
329
330            "pattern_recognition" => capabilities
331                .iter()
332                .max_by_key(|(_, cap)| cap.feature_count)
333                .map(|(&id, _)| id),
334
335            "transformation" => capabilities
336                .iter()
337                .min_by_key(|(_, cap)| cap.memory_usage)
338                .map(|(&id, _)| id),
339
340            _ => None,
341        }
342    }
343    /// Find related nodes - nodes that share patterns or transformations
344    pub fn find_related_nodes(&self, node_id: EdgeId) -> Vec<EdgeId> {
345        let mut related = HashSet::new();
346
347        // Add nodes connected by transformations
348        if let Some(transforms) = self.transformations().get(&node_id) {
349            for &target_id in transforms.values() {
350                related.insert(target_id);
351            }
352        }
353
354        // Find nodes with inverse transformations pointing to this one
355        for (&source, transforms) in self.transformations() {
356            for &target in transforms.values() {
357                if target == node_id {
358                    related.insert(source);
359                }
360            }
361        }
362
363        // Convert to Vec and return
364        related.into_iter().collect()
365    }
366    /// Get nodes with specific operator mode
367    pub fn find_nodes_with_operator(&self, mode: OperatorKind) -> Vec<EdgeId> {
368        self.partitions()
369            .iter()
370            .filter_map(
371                |(&id, node)| {
372                    if node.kind() == mode { Some(id) } else { None }
373                },
374            )
375            .collect()
376    }
377    /// Find nodes containing a specific pitch class
378    pub fn find_nodes_with_pitch(&self, pitch: usize) -> Vec<EdgeId> {
379        self.partitions()
380            .iter()
381            .filter_map(|(&id, node)| {
382                if node.get_notes().contains(&(pitch % 12)) {
383                    Some(id)
384                } else {
385                    None
386                }
387            })
388            .collect()
389    }
390    /// Find optimal transformation path between two nodes
391    pub fn find_transformation_path(
392        &self,
393        source_id: EdgeId,
394        target_id: EdgeId,
395    ) -> Option<Vec<(EdgeId, LPR)>> {
396        // Skip if either node doesn't exist
397        if !self.partitions.contains_key(&source_id) || !self.partitions.contains_key(&target_id) {
398            return None;
399        }
400
401        // BFS search for shortest path
402        use std::collections::VecDeque;
403
404        let mut queue = VecDeque::new();
405        let mut visited = HashSet::new();
406        let mut came_from: HashMap<EdgeId, (EdgeId, LPR)> = HashMap::new();
407
408        queue.push_back(source_id);
409        visited.insert(source_id);
410
411        while let Some(current) = queue.pop_front() {
412            if current == target_id {
413                // Path found, reconstruct it
414                let mut path = Vec::new();
415                let mut node_id = current;
416
417                while let Some(&(prev_id, transform)) = came_from.get(&node_id) {
418                    path.push((prev_id, transform));
419                    node_id = prev_id;
420                }
421
422                path.reverse();
423                return Some(path);
424            }
425
426            // Check all transformations from current node
427            if let Some(transforms) = self.transformations.get(&current) {
428                for (&transform, &next) in transforms {
429                    if !visited.contains(&next) {
430                        visited.insert(next);
431                        came_from.insert(next, (current, transform));
432                        queue.push_back(next);
433                    }
434                }
435            }
436        }
437
438        None // No path found
439    }
440    /// Find a vertex by its pitch class value
441    pub fn find_vertex_by_pitch(&self, pitch: usize) -> Option<VertexId> {
442        self.graph()
443            .nodes()
444            .iter()
445            .find(|(_, node)| node.weight().class() == pitch.pmod())
446            .map(|(id, _)| *id)
447    }
448    /// returns the task load associated with each "surface"
449    pub fn get_all_edge_loads(&self) -> HashMap<EdgeId, f32> {
450        self.partitions()
451            .iter()
452            .map(|(&id, node)| {
453                // Normalize by feature count and surface complexity
454                let feature_count = node.total_features() as f32;
455                let has_surface = if node.has_surface_network() { 1.5 } else { 1.0 };
456                let load = (feature_count / 1000.0 * has_surface).min(1.0);
457                (id, load)
458            })
459            .collect()
460    }
461    /// returns all transformations for a given edge
462    pub fn get_transformations(&self, index: &EdgeId) -> HashMap<LPR, EdgeId> {
463        self.transformations()
464            .get(index)
465            .cloned()
466            .unwrap_or_default()
467    }
468    /// Initialize with major and minor triads for all 12 pitch classes
469    pub fn initialize_with_all_triads(&mut self) -> crate::Result<()> {
470        // Add all major and minor triads
471        for root in 0..12 {
472            self.add_triad(Triad::major(root))?;
473            self.add_triad(Triad::minor(root))?;
474            self.add_triad(Triad::diminished(root))?;
475            self.add_triad(Triad::augmented(root))?;
476        }
477
478        // Compute transformations between all triads
479        self.compute_transformations();
480
481        Ok(())
482    }
483    /// Get proportion of nodes in each operator mode
484    pub fn mode_distribution(&self) -> HashMap<OperatorKind, f32> {
485        use strum::IntoEnumIterator;
486        let mut distribution = HashMap::new();
487
488        for mode in OperatorKind::iter() {
489            distribution.insert(mode, 0.0);
490        }
491
492        let total = self.partitions().len() as f32;
493        if total == 0.0 {
494            return distribution;
495        }
496
497        for node in self.partitions().values() {
498            let mode = node.kind();
499            *distribution.entry(mode).or_insert(0.0) += 1.0;
500        }
501
502        // Convert to percentages
503        for count in distribution.values_mut() {
504            *count /= total;
505        }
506
507        distribution
508    }
509    /// Optimize the fragment based on real-time performance metrics
510    pub fn optimize_for_real_time(&mut self) -> crate::Result<()>
511    where
512        D: TriadDriver,
513    {
514        // 1. Balance operator distribution
515        self.optimize_operator_distribution()?;
516
517        // 2. Optimize memory usage
518        self.balance_resources()?;
519
520        // 3. Update transformation cache
521        self.compute_transformations();
522
523        // 4. Share patterns between nodes
524        self.share_patterns()?;
525
526        // 5. Coordinate learning
527        self.coordinate_learning()?;
528
529        Ok(())
530    }
531    /// Optimize memory usage across all nodes
532    pub fn optimize_memory(&mut self, max_features: usize) -> crate::Result<()> {
533        for node in self.partitions_mut().values_mut() {
534            let _stats = node.optimize_memory(max_features)?;
535        }
536        Ok(())
537    }
538    /// Optimize operator distribution
539    pub fn optimize_operator_distribution(&mut self) -> crate::Result<()> {
540        // Count nodes by operator type
541        let mut operator_counts = HashMap::new();
542
543        for node in self.partitions().values() {
544            let kind = node.kind();
545            *operator_counts.entry(kind).or_insert(0) += 1;
546        }
547
548        // Calculate optimal distribution
549        // For example, ensure we have at least one observer and one agent
550        if !operator_counts.contains_key(&OperatorKind::Observer) {
551            // Find a node to convert to observer
552            if let Some((&id, _)) = self.partitions().iter().next() {
553                self.set_node_operator(id, OperatorKind::Observer)?;
554            }
555        }
556
557        if !operator_counts.contains_key(&OperatorKind::Agent) {
558            // Find a node to convert to agent
559            if let Some((&id, _)) = self.partitions().iter().nth(1) {
560                self.set_node_operator(id, OperatorKind::Agent)?;
561            }
562        }
563
564        Ok(())
565    }
566    /// Calculate optimal voice leading distance between two nodes
567    pub fn optimal_voice_leading_distance(&self, src: &EdgeId, dest: &EdgeId) -> Option<usize> {
568        let source_node = self.get_vnode(src)?;
569        let target_node = self.get_vnode(dest)?;
570
571        let source_notes = source_node.get_notes();
572        let target_notes = target_node.get_notes();
573
574        // This implementation considers all possible voice assignments to find minimal distance
575        fn permutations<T: Copy>(arr: &[T]) -> Vec<Vec<T>> {
576            if arr.len() <= 1 {
577                return vec![arr.to_vec()];
578            }
579
580            let mut result = Vec::new();
581            for (i, &item) in arr.iter().enumerate() {
582                let mut sub_arr = arr.to_vec();
583                sub_arr.remove(i);
584
585                for mut perm in permutations(&sub_arr) {
586                    perm.insert(0, item);
587                    result.push(perm);
588                }
589            }
590
591            result
592        }
593
594        // Calculate all possible voice assignments
595        let target_perms = permutations(&target_notes);
596
597        // Find minimal distance across all possible voice assignments
598        target_perms
599            .iter()
600            .map(|perm| {
601                source_notes
602                    .iter()
603                    .zip(perm)
604                    .map(|(&s, &t)| {
605                        let dist = ((t as isize - s as isize).abs()).pmod() as usize;
606                        std::cmp::min(dist, 12 - dist)
607                    })
608                    .sum()
609            })
610            .min()
611    }
612    /// Process a node message
613    pub fn process_message(&mut self, message: &NodeMessage) -> crate::Result<()>
614    where
615        D: TriadDriver,
616    {
617        match message {
618            NodeMessage::TransformRequest {
619                source,
620                target,
621                transform,
622            } => {
623                // Verify source node exists
624                if !self.partitions.contains_key(source) {
625                    return Err(crate::RuntimeError::NodeNotFound);
626                }
627
628                // Apply transformation to target node
629                self.send_command(*target, *transform)
630            }
631
632            NodeMessage::StateSync { source, .. } => {
633                // Handle state synchronization
634                // Just verify the source node exists for now
635                if !self.partitions.contains_key(source) {
636                    return Err(crate::RuntimeError::NodeNotFound);
637                }
638                Ok(())
639            }
640
641            NodeMessage::PatternShare {
642                source, pattern, ..
643            } => {
644                // Verify source node exists
645                if !self.partitions.contains_key(source) {
646                    return Err(crate::RuntimeError::NodeNotFound);
647                }
648
649                // Share pattern with all other nodes
650                for (&id, node) in self.partitions_mut() {
651                    if id != *source {
652                        let _ = node.learn_transformation_sequence(pattern);
653                    }
654                }
655
656                Ok(())
657            }
658        }
659    }
660    /// Process incoming stream and distribute to optimal nodes
661    pub fn process_stream<I>(&mut self, stream: I) -> crate::Result<Vec<(EdgeId, Vec<LPR>)>>
662    where
663        I: Iterator<Item = (Triad, Vec<LPR>)>,
664        D: TriadDriver,
665    {
666        let mut results = Vec::new();
667
668        for (target_triad, transforms) in stream {
669            // Find or create node for this triad
670            let edge_id = match self.find_edge_by_notes(target_triad.notes()) {
671                Some(id) => id,
672                None => self.add_triad(target_triad)?,
673            };
674
675            // Apply transformations
676            if let Some(node) = self.get_vnode_mut(&edge_id) {
677                node.transform_batch(&transforms)?;
678
679                // Capture the resulting state
680                results.push((edge_id, transforms));
681            }
682        }
683
684        Ok(results)
685    }
686    /// Process a stream of transformations with dynamic resource allocation
687    pub fn process_transformation_stream<I>(&mut self, stream: I) -> crate::Result<()>
688    where
689        I: Iterator<Item = (EdgeId, LPR)>,
690        D: TriadDriver,
691    {
692        // Batch size depends on system resources
693        let optimal_batch_size = 10; // Could be dynamically calculated
694
695        let mut current_batch = Vec::with_capacity(optimal_batch_size);
696        let mut current_edge = None;
697
698        for (edge_id, transform) in stream {
699            // If we're switching to a different edge, process the current batch
700            if current_edge != Some(edge_id) && !current_batch.is_empty() {
701                if let Some(edge) = current_edge {
702                    if let Some(node) = self.get_vnode_mut(&edge) {
703                        node.transform_batch(&current_batch)?;
704                    }
705                }
706                current_batch.clear();
707            }
708
709            // Add to current batch
710            current_edge = Some(edge_id);
711            current_batch.push(transform);
712
713            // Process batch if it reaches optimal size
714            if current_batch.len() >= optimal_batch_size {
715                if let Some(node) = self.get_vnode_mut(&edge_id) {
716                    node.transform_batch(&current_batch)?;
717                }
718                current_batch.clear();
719            }
720        }
721
722        // Process any remaining transformations
723        if let Some(edge_id) = current_edge {
724            if !current_batch.is_empty() {
725                if let Some(node) = self.get_vnode_mut(&edge_id) {
726                    node.transform_batch(&current_batch)?;
727                }
728            }
729        }
730
731        Ok(())
732    }
733    /// Send a command to a specific surface node
734    pub fn send_command(&mut self, node_id: EdgeId, transform: LPR) -> crate::Result<()>
735    where
736        D: TriadDriver,
737    {
738        if let Some(node) = self.get_vnode_mut(&node_id) {
739            node.transform(transform).map_err(|err| err.into())
740        } else {
741            Err(crate::RuntimeError::NodeNotFound)
742        }
743    }
744    /// Set operator mode for all nodes
745    pub fn set_all_nodes_mode(&mut self, mode: OperatorKind) -> crate::Result<()> {
746        for node in self.partitions_mut().values_mut() {
747            node.set_operator_by_kind(mode);
748        }
749        Ok(())
750    }
751    /// Set operator mode for a specific node
752    pub fn set_node_operator(&mut self, node_id: EdgeId, mode: OperatorKind) -> crate::Result<()> {
753        if let Some(node) = self.get_vnode_mut(&node_id) {
754            node.set_operator_by_kind(mode);
755            Ok(())
756        } else {
757            Err(crate::RuntimeError::NodeNotFound)
758        }
759    }
760    /// Share patterns between nodes to enhance collective memory
761    pub fn share_patterns(&mut self) -> crate::Result<()> {
762        // Collect all patterns from all nodes
763        let mut all_patterns = Vec::new();
764
765        for (&edge_id, node) in &self.partitions {
766            // Get patterns with their importance and source
767            let node_patterns = node
768                .store()
769                .patterns()
770                .iter()
771                .map(|p| (edge_id, p.sequence().clone(), *p.importance()))
772                .collect::<Vec<_>>();
773
774            all_patterns.extend(node_patterns);
775        }
776
777        // Sort by importance descending
778        all_patterns
779            .sort_by(|(_, _, a), (_, _, b)| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
780
781        // Share the top patterns with all nodes
782        let top_patterns = all_patterns.into_iter().take(10);
783
784        for (source_id, pattern, ..) in top_patterns {
785            for (&target_id, target_node) in self.partitions_mut() {
786                if target_id != source_id {
787                    // Convert pattern from usize to LPR for learning
788                    let lpr_pattern = pattern
789                        .clone()
790                        .into_iter()
791                        .filter_map(|p| match p {
792                            0 => Some(LPR::Leading),
793                            1 => Some(LPR::Parallel),
794                            2 => Some(LPR::Relative),
795                            _ => None,
796                        })
797                        .collect::<Vec<_>>();
798
799                    // Learn the pattern if possible
800                    let _ = target_node.learn_transformation_sequence(&lpr_pattern);
801                }
802            }
803        }
804
805        Ok(())
806    }
807    /// Split the fragment's workload based on available resources
808    pub fn split_for_distributed_processing(&self) -> Vec<(EdgeId, Vec<LPR>)> {
809        // Determine which nodes are resource-intensive
810        let _capabilities = self.calculate_node_capabilities();
811
812        // Create balanced workload units
813        let mut workloads = Vec::new();
814
815        for id in self.partitions.keys() {
816            // Generate a sequence of transformations this node should process
817            if let Some(node) = self.get_vnode(id) {
818                if node.kind() == OperatorKind::Agent {
819                    // Agents can propose their own transformations
820                    if let Some(transform) = node.propose_transformation() {
821                        workloads.push((*id, vec![transform]));
822                    }
823                }
824            }
825        }
826
827        workloads
828    }
829    /// Calculate voice-leading distance between two triads
830    pub fn voice_leading_distance(&self, from: &EdgeId, to: &EdgeId) -> Option<usize> {
831        // Get the triads from both nodes
832        let from_node = self.get_vnode(from)?;
833        let to_node = self.get_vnode(to)?;
834
835        let from_triad = from_node.headspace();
836        let to_triad = to_node.headspace();
837
838        // Calculate minimal voice leading distance
839        let from_notes = from_triad.notes();
840        let to_notes = to_triad.notes();
841
842        // Start with basic voice-leading distance calculation
843        let mut distance = 0;
844
845        // For each note in the source triad, find the closest note in the target triad
846        for from_note in from_notes {
847            let mut min_dist = 12; // Maximum distance in semitones within an octave
848            for to_note in to_notes {
849                // Calculate minimal semitone distance (considering octave equivalence)
850                let direct_dist = ((to_note as isize - from_note as isize).abs()).pmod() as usize;
851                min_dist = min_dist.min(direct_dist);
852            }
853            distance += min_dist;
854        }
855
856        Some(distance)
857    }
858}