Skip to main content

qualia_core_db/specialized_libs/physics_simulation/
distributed.rs

1use super::*;
2
3/// Mesh coordinator for distributed simulations
4pub struct MeshCoordinator {
5    mesh_network: Arc<Mutex<MeshNetworkManager>>,
6    node_manager: NodeManager,
7    load_balancer: MeshLoadBalancer,
8    synchronization: MeshSynchronization,
9}
10
11/// Status snapshot of the underlying mesh network.
12#[derive(Debug, Clone)]
13pub struct MeshStatus {
14    pub total_nodes: u32,
15    pub acoustic_nodes: u32,
16    pub ble_nodes: u32,
17    pub active_routes: u32,
18    pub pending_messages: u32,
19}
20
21/// Node manager
22pub struct NodeManager {
23    nodes: HashMap<String, MeshNode>,
24    node_capabilities: HashMap<String, NodeCapabilities>,
25    node_status: HashMap<String, NodeStatus>,
26}
27
28/// Mesh node
29#[derive(Debug, Clone)]
30pub struct MeshNode {
31    pub node_id: String,
32    pub node_type: NodeType,
33    pub capabilities: NodeCapabilities,
34    pub current_load: f64,
35    pub network_address: String,
36    pub last_heartbeat: u64,
37}
38
39/// Node types
40#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
41pub enum NodeType {
42    /// Master node
43    Master,
44    /// Worker node
45    Worker,
46    /// Storage node
47    Storage,
48    /// Visualization node
49    Visualization,
50    /// I/O node
51    IO,
52}
53
54/// Node capabilities
55#[derive(Debug, Clone)]
56pub struct NodeCapabilities {
57    pub cpu_cores: usize,
58    pub memory_size: u64,
59    pub gpu_count: usize,
60    pub storage_capacity: u64,
61    pub network_bandwidth: f64,
62    pub supported_algorithms: Vec<String>,
63}
64
65/// Node status
66#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
67pub enum NodeStatus {
68    Active,
69    Idle,
70    Busy,
71    Offline,
72    Error,
73}
74
75/// Mesh load balancer
76pub struct MeshLoadBalancer {
77    balancing_strategy: LoadBalancingStrategy,
78    load_metrics: LoadMetrics,
79    redistribution_policy: RedistributionPolicy,
80}
81
82/// Load balancing strategies
83#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
84pub enum LoadBalancingStrategy {
85    /// Round-robin
86    RoundRobin,
87    /// Load-based
88    LoadBased,
89    /// Capability-based
90    CapabilityBased,
91    /// Geographic
92    Geographic,
93    /// Adaptive
94    Adaptive,
95}
96
97/// Load metrics
98#[derive(Debug, Clone)]
99pub struct LoadMetrics {
100    pub cpu_utilization: f64,
101    pub memory_utilization: f64,
102    pub network_utilization: f64,
103    pub task_completion_rate: f64,
104}
105
106/// Redistribution policy
107#[derive(Debug, Clone)]
108pub struct RedistributionPolicy {
109    pub redistribution_threshold: f64,
110    pub redistribution_interval: u64,
111    pub max_redistribution_time: u64,
112}
113
114/// Mesh synchronization
115pub struct MeshSynchronization {
116    synchronization_method: SynchronizationMethod,
117    consistency_model: ConsistencyModel,
118    conflict_resolution: ConflictResolution,
119}
120
121/// Synchronization methods
122#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
123pub enum SynchronizationMethod {
124    /// Barrier synchronization
125    Barrier,
126    /// Point-to-point synchronization
127    PointToPoint,
128    /// Collective synchronization
129    Collective,
130    /// Asynchronous synchronization
131    Asynchronous,
132    /// Hybrid synchronization
133    Hybrid,
134}
135
136/// Consistency models
137#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
138pub enum ConsistencyModel {
139    /// Strong consistency
140    Strong,
141    /// Eventual consistency
142    Eventual,
143    /// Causal consistency
144    Causal,
145    /// Weak consistency
146    Weak,
147    /// Eventually consistent
148    Eventually,
149}
150
151/// Conflict resolution
152#[derive(Debug, Clone)]
153pub struct ConflictResolution {
154    resolution_strategy: ConflictResolutionStrategy,
155    conflict_detection: ConflictDetection,
156    resolution_policy: ResolutionPolicy,
157}
158
159/// Conflict resolution strategies
160#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
161pub enum ConflictResolutionStrategy {
162    /// Last writer wins
163    LastWriterWins,
164    /// First writer wins
165    FirstWriterWins,
166    /// Vector clock
167    VectorClock,
168    /// Lamport timestamp
169    LamportTimestamp,
170    /// Paxos algorithm
171    Paxos,
172    /// Raft algorithm
173    Raft,
174}
175
176/// Conflict detection
177#[derive(Debug, Clone)]
178pub struct ConflictDetection {
179    detection_method: ConflictDetectionMethod,
180    conflict_types: Vec<ConflictType>,
181}
182
183/// Conflict detection methods
184#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
185pub enum ConflictDetectionMethod {
186    /// Version number
187    VersionNumber,
188    /// Timestamp
189    Timestamp,
190    /// Hash-based
191    HashBased,
192    /// Content-based
193    ContentBased,
194}
195
196/// Conflict types
197#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
198pub enum ConflictType {
199    /// Write-write conflict
200    WriteWrite,
201    /// Read-write conflict
202    ReadWrite,
203    /// Update-update conflict
204    UpdateUpdate,
205    /// Delete-update conflict
206    DeleteUpdate,
207}
208
209/// Resolution policy
210#[derive(Debug, Clone)]
211pub struct ResolutionPolicy {
212    policy_id: String,
213    policy_rules: Vec<ResolutionRule>,
214    default_action: ResolutionAction,
215}
216
217/// Resolution rules
218#[derive(Debug, Clone)]
219pub struct ResolutionRule {
220    pub rule_id: String,
221    pub condition: String,
222    pub action: ResolutionAction,
223    pub priority: u32,
224}
225
226/// Resolution actions
227#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
228pub enum ResolutionAction {
229    Accept,
230    Reject,
231    Merge,
232    Transform,
233    Escalate,
234}
235
236impl MeshCoordinator {
237    pub fn new() -> Self {
238        Self {
239            mesh_network: Arc::new(Mutex::new(MeshNetworkManager::new())),
240            node_manager: NodeManager::new(),
241            load_balancer: MeshLoadBalancer::new(),
242            synchronization: MeshSynchronization::new(),
243        }
244    }
245
246    pub fn initialize(&mut self) -> Result<(), PhysicsError> {
247        self.node_manager.initialize()?;
248        self.load_balancer.initialize()?;
249        self.synchronization.initialize()?;
250        Ok(())
251    }
252
253    pub fn initialize_mesh_network(&mut self) -> Result<(), PhysicsError> {
254        // Lock the mesh network and call its initialization method.
255        let mut network = self.mesh_network.lock().map_err(|e| {
256            PhysicsError::NetworkError(format!("Mesh network lock poisoned: {}", e))
257        })?;
258        network
259            .initialize()
260            .map_err(|e| PhysicsError::NetworkError(format!("Mesh init failed: {}", e)))
261    }
262
263    /// Query the current mesh network status.
264    pub fn get_mesh_status(&self) -> Result<MeshStatus, PhysicsError> {
265        let network = self.mesh_network.lock().map_err(|e| {
266            PhysicsError::NetworkError(format!("Mesh network lock poisoned: {}", e))
267        })?;
268        let status: NetworkStatus = network.get_network_status();
269        Ok(MeshStatus {
270            total_nodes: status.total_nodes,
271            acoustic_nodes: status.acoustic_nodes,
272            ble_nodes: status.ble_nodes,
273            active_routes: status.active_routes,
274            pending_messages: status.pending_messages,
275        })
276    }
277
278    /// Distribute a simulation task (raw bytes) through the mesh network.
279    pub fn distribute_simulation_task(&self, task_data: &[u8]) -> Result<(), PhysicsError> {
280        let mut network = self.mesh_network.lock().map_err(|e| {
281            PhysicsError::NetworkError(format!("Mesh network lock poisoned: {}", e))
282        })?;
283        network
284            .send_message_ephemeral("broadcast", task_data, MessagePriority::High)
285            .map_err(|e| PhysicsError::NetworkError(format!("Mesh send failed: {}", e)))?;
286        Ok(())
287    }
288
289    pub fn distribute_simulation(
290        &self,
291        _simulation: &Simulation,
292    ) -> Result<NodeDistribution, PhysicsError> {
293        // Distribute simulation across available nodes
294        let distribution = NodeDistribution {
295            node_ids: vec![
296                "node1".to_string(),
297                "node2".to_string(),
298                "node3".to_string(),
299            ],
300            node_loads: vec![0.33, 0.33, 0.34],
301            communication_pattern: CommunicationPattern::Hybrid,
302        };
303
304        Ok(distribution)
305    }
306
307    pub fn collect_results(
308        &self,
309        results: &[SimulationResult],
310    ) -> Result<Vec<PhysicsField>, PhysicsError> {
311        if results.is_empty() {
312            return Ok(Vec::new());
313        }
314        // Group fields by name prefix (strip node suffix), then average across nodes
315        let mut field_groups: HashMap<String, Vec<&PhysicsField>> = HashMap::new();
316        for result in results {
317            for field in &result.fields {
318                // Strip node-specific suffix (e.g. "velocity_node1" -> "velocity")
319                let base_name = field
320                    .field_id
321                    .split('_')
322                    .next()
323                    .unwrap_or(&field.field_id)
324                    .to_string();
325                field_groups.entry(base_name).or_default().push(field);
326            }
327        }
328        let mut combined_fields = Vec::new();
329        for (base_name, fields) in field_groups {
330            if fields.is_empty() {
331                continue;
332            }
333            let dim = fields[0].dimensions.clone();
334            let data_len = fields[0].data.len();
335            let mut combined_data = vec![0.0f64; data_len];
336            for field in &fields {
337                if field.data.len() == data_len {
338                    for (i, &v) in field.data.iter().enumerate() {
339                        combined_data[i] += v;
340                    }
341                }
342            }
343            let count = fields.len() as f64;
344            for v in &mut combined_data {
345                *v /= count;
346            }
347            combined_fields.push(PhysicsField {
348                field_id: base_name.clone(),
349                field_type: fields[0].field_type.clone(),
350                dimensions: dim,
351                data: combined_data,
352                metadata: FieldMetadata {
353                    field_name: fields[0].metadata.field_name.clone(),
354                    physical_quantity: fields[0].metadata.physical_quantity.clone(),
355                    units: fields[0].metadata.units.clone(),
356                    time_step: fields[0].metadata.time_step,
357                    iteration: fields[0].metadata.iteration,
358                },
359            });
360        }
361        Ok(combined_fields)
362    }
363}
364
365impl NodeManager {
366    pub fn new() -> Self {
367        Self {
368            nodes: HashMap::new(),
369            node_capabilities: HashMap::new(),
370            node_status: HashMap::new(),
371        }
372    }
373
374    pub fn initialize(&mut self) -> Result<(), PhysicsError> {
375        // Initialize with default nodes
376        let node1 = MeshNode {
377            node_id: "node1".to_string(),
378            node_type: NodeType::Worker,
379            capabilities: NodeCapabilities::new(),
380            current_load: 0.0,
381            network_address: "192.168.1.1".to_string(),
382            last_heartbeat: std::time::SystemTime::now()
383                .duration_since(std::time::UNIX_EPOCH)
384                .unwrap()
385                .as_secs(),
386        };
387
388        self.nodes.insert("node1".to_string(), node1);
389        Ok(())
390    }
391
392    /// Register capabilities for a node.
393    pub fn add_node_capability(&mut self, node_id: &str, caps: NodeCapabilities) {
394        self.node_capabilities.insert(node_id.to_string(), caps);
395    }
396
397    /// Get the capabilities registered for a node, if any.
398    pub fn get_node_capability(&self, node_id: &str) -> Option<&NodeCapabilities> {
399        self.node_capabilities.get(node_id)
400    }
401
402    /// Set the status of a node.
403    pub fn set_node_status(&mut self, node_id: &str, status: NodeStatus) {
404        self.node_status.insert(node_id.to_string(), status);
405    }
406
407    /// Get the status of a node, if any.
408    pub fn get_node_status(&self, node_id: &str) -> Option<&NodeStatus> {
409        self.node_status.get(node_id)
410    }
411
412    /// List all node IDs that have a registered status.
413    pub fn list_node_status_ids(&self) -> Vec<String> {
414        self.node_status.keys().cloned().collect()
415    }
416}
417
418impl NodeCapabilities {
419    pub fn new() -> Self {
420        Self {
421            cpu_cores: 8,
422            memory_size: 16 * 1024 * 1024 * 1024, // 16GB
423            gpu_count: 1,
424            storage_capacity: 1 * 1024 * 1024 * 1024 * 1024, // 1TB
425            network_bandwidth: 1000.0,                       // 1 Gbps
426            supported_algorithms: vec!["CFD".to_string(), "FEM".to_string()],
427        }
428    }
429}
430
431impl MeshLoadBalancer {
432    pub fn new() -> Self {
433        Self {
434            balancing_strategy: LoadBalancingStrategy::LoadBased,
435            load_metrics: LoadMetrics::new(),
436            redistribution_policy: RedistributionPolicy::new(),
437        }
438    }
439
440    pub fn initialize(&mut self) -> Result<(), PhysicsError> {
441        Ok(())
442    }
443
444    /// Get the balancing strategy.
445    pub fn get_balancing_strategy(&self) -> &LoadBalancingStrategy {
446        &self.balancing_strategy
447    }
448
449    /// Set the balancing strategy.
450    pub fn set_balancing_strategy(&mut self, strategy: LoadBalancingStrategy) {
451        self.balancing_strategy = strategy;
452    }
453
454    /// Get a reference to the load metrics.
455    pub fn get_load_metrics(&self) -> &LoadMetrics {
456        &self.load_metrics
457    }
458
459    /// Get a mutable reference to the load metrics.
460    pub fn get_load_metrics_mut(&mut self) -> &mut LoadMetrics {
461        &mut self.load_metrics
462    }
463
464    /// Get a reference to the redistribution policy.
465    pub fn get_redistribution_policy(&self) -> &RedistributionPolicy {
466        &self.redistribution_policy
467    }
468
469    /// Get a mutable reference to the redistribution policy.
470    pub fn get_redistribution_policy_mut(&mut self) -> &mut RedistributionPolicy {
471        &mut self.redistribution_policy
472    }
473}
474
475impl LoadMetrics {
476    pub fn new() -> Self {
477        Self {
478            cpu_utilization: 0.0,
479            memory_utilization: 0.0,
480            network_utilization: 0.0,
481            task_completion_rate: 0.0,
482        }
483    }
484}
485
486impl RedistributionPolicy {
487    pub fn new() -> Self {
488        Self {
489            redistribution_threshold: 0.8,
490            redistribution_interval: 60,  // 1 minute
491            max_redistribution_time: 300, // 5 minutes
492        }
493    }
494}
495
496impl MeshSynchronization {
497    pub fn new() -> Self {
498        Self {
499            synchronization_method: SynchronizationMethod::Hybrid,
500            consistency_model: ConsistencyModel::Eventual,
501            conflict_resolution: ConflictResolution::new(),
502        }
503    }
504
505    pub fn initialize(&mut self) -> Result<(), PhysicsError> {
506        self.conflict_resolution.initialize()?;
507        Ok(())
508    }
509
510    /// Get the synchronization method.
511    pub fn get_synchronization_method(&self) -> &SynchronizationMethod {
512        &self.synchronization_method
513    }
514
515    /// Set the synchronization method.
516    pub fn set_synchronization_method(&mut self, method: SynchronizationMethod) {
517        self.synchronization_method = method;
518    }
519
520    /// Get the consistency model.
521    pub fn get_consistency_model(&self) -> &ConsistencyModel {
522        &self.consistency_model
523    }
524
525    /// Set the consistency model.
526    pub fn set_consistency_model(&mut self, model: ConsistencyModel) {
527        self.consistency_model = model;
528    }
529}
530
531impl ConflictResolution {
532    pub fn new() -> Self {
533        Self {
534            resolution_strategy: ConflictResolutionStrategy::LastWriterWins,
535            conflict_detection: ConflictDetection::new(),
536            resolution_policy: ResolutionPolicy::new(),
537        }
538    }
539
540    pub fn initialize(&mut self) -> Result<(), PhysicsError> {
541        Ok(())
542    }
543
544    /// Get the resolution strategy.
545    pub fn get_resolution_strategy(&self) -> &ConflictResolutionStrategy {
546        &self.resolution_strategy
547    }
548
549    /// Set the resolution strategy.
550    pub fn set_resolution_strategy(&mut self, strategy: ConflictResolutionStrategy) {
551        self.resolution_strategy = strategy;
552    }
553
554    /// Get a reference to the conflict detection.
555    pub fn get_conflict_detection(&self) -> &ConflictDetection {
556        &self.conflict_detection
557    }
558
559    /// Get a mutable reference to the conflict detection.
560    pub fn get_conflict_detection_mut(&mut self) -> &mut ConflictDetection {
561        &mut self.conflict_detection
562    }
563
564    /// Get a reference to the resolution policy.
565    pub fn get_resolution_policy(&self) -> &ResolutionPolicy {
566        &self.resolution_policy
567    }
568
569    /// Get a mutable reference to the resolution policy.
570    pub fn get_resolution_policy_mut(&mut self) -> &mut ResolutionPolicy {
571        &mut self.resolution_policy
572    }
573}
574
575impl ConflictDetection {
576    pub fn new() -> Self {
577        Self {
578            detection_method: ConflictDetectionMethod::Timestamp,
579            conflict_types: vec![ConflictType::WriteWrite],
580        }
581    }
582
583    /// Get the detection method.
584    pub fn get_detection_method(&self) -> &ConflictDetectionMethod {
585        &self.detection_method
586    }
587
588    /// Set the detection method.
589    pub fn set_detection_method(&mut self, method: ConflictDetectionMethod) {
590        self.detection_method = method;
591    }
592
593    /// Get all registered conflict types.
594    pub fn get_conflict_types(&self) -> &[ConflictType] {
595        &self.conflict_types
596    }
597
598    /// Add a conflict type to monitor.
599    pub fn add_conflict_type(&mut self, ctype: ConflictType) {
600        self.conflict_types.push(ctype);
601    }
602}
603
604impl ResolutionPolicy {
605    pub fn new() -> Self {
606        Self {
607            policy_id: "default".to_string(),
608            policy_rules: Vec::new(),
609            default_action: ResolutionAction::Accept,
610        }
611    }
612
613    /// Get the policy ID.
614    pub fn get_policy_id(&self) -> &str {
615        &self.policy_id
616    }
617
618    /// Get all policy rules.
619    pub fn get_policy_rules(&self) -> &[ResolutionRule] {
620        &self.policy_rules
621    }
622
623    /// Add a resolution rule to the policy.
624    pub fn add_policy_rule(&mut self, rule: ResolutionRule) {
625        self.policy_rules.push(rule);
626    }
627
628    /// Get the default action.
629    pub fn get_default_action(&self) -> &ResolutionAction {
630        &self.default_action
631    }
632
633    /// Set the default action.
634    pub fn set_default_action(&mut self, action: ResolutionAction) {
635        self.default_action = action;
636    }
637}
638
639impl PhysicsSimulationLibrary {
640    /// Run distributed simulation
641    pub fn run_distributed_simulation(
642        &mut self,
643        simulation: &mut Simulation,
644    ) -> Result<PhysicsSimulationResult<Vec<PhysicsField>>, PhysicsError> {
645        let start_time = std::time::Instant::now();
646
647        // Initialize mesh coordinator
648        self.mesh_coordinator.initialize_mesh_network()?;
649
650        // Distribute simulation across nodes
651        let node_distribution = self.mesh_coordinator.distribute_simulation(simulation)?;
652
653        // Run simulation on each node
654        let mut results = Vec::new();
655        for node_id in node_distribution.node_ids {
656            let node_result = self.run_simulation_on_node(simulation, &node_id)?;
657            results.push(node_result);
658        }
659
660        // Collect results
661        let final_result = self.mesh_coordinator.collect_results(&results)?;
662
663        let simulation_time = start_time.elapsed().as_millis() as u64;
664
665        // Aggregate REAL convergence across the nodes: converged only if every node did;
666        // residual is the worst (max) node residual; iterations the max node iteration count.
667        let all_converged =
668            !results.is_empty() && results.iter().all(|r| r.convergence_info.converged);
669        let agg_residual = results
670            .iter()
671            .map(|r| r.convergence_info.residual_norm)
672            .fold(0.0f64, f64::max);
673        let agg_iterations = results
674            .iter()
675            .map(|r| r.convergence_info.iterations)
676            .max()
677            .unwrap_or(0);
678        let agg_conv_rate = results
679            .iter()
680            .map(|r| r.convergence_info.convergence_rate)
681            .fold(0.0f64, f64::max);
682
683        Ok(PhysicsSimulationResult {
684            result: final_result,
685            simulation_time,
686            solver_time: simulation_time,
687            data_time: 0,
688            convergence_info: ConvergenceInfo {
689                converged: all_converged,
690                iterations: agg_iterations,
691                residual_norm: agg_residual,
692                convergence_rate: agg_conv_rate,
693                final_error: agg_residual,
694            },
695            // Runtime utilization is not sampled per call; left at 0.0 (not measured).
696            performance_info: PerformanceInfo {
697                cpu_utilization: 0.0,
698                memory_utilization: 0.0,
699                network_utilization: 0.0,
700                io_utilization: 0.0,
701                parallel_efficiency: 0.0,
702            },
703        })
704    }
705    fn run_simulation_on_node(
706        &self,
707        simulation: &Simulation,
708        node_id: &str,
709    ) -> Result<SimulationResult, PhysicsError> {
710        let nx = simulation.config.spatial_resolution.nx;
711        let dx = simulation.config.spatial_resolution.dx;
712        let dt = simulation.config.time_step;
713        let nu = 1.5e-5_f64; // kinematic viscosity of air (m²/s)
714
715        // 1D Burgers equation for velocity: u_t + u*u_x = nu * u_xx
716        let mut u = vec![0.0f64; nx];
717        for i in 0..nx {
718            let x = i as f64 * dx;
719            u[i] = (std::f64::consts::PI * x).sin();
720        }
721        let steps = ((simulation.config.total_time / dt) as usize)
722            .max(1)
723            .min(500);
724        let mut residual = f64::INFINITY;
725        let mut prev_residual = f64::INFINITY;
726        for _ in 0..steps {
727            let mut u_new = u.clone();
728            let mut sumsq = 0.0f64;
729            for i in 1..nx - 1 {
730                let advection = -u[i] * (u[i + 1] - u[i - 1]) / (2.0 * dx);
731                let diffusion = nu * (u[i + 1] - 2.0 * u[i] + u[i - 1]) / (dx * dx);
732                u_new[i] = u[i] + dt * (advection + diffusion);
733                let d = u_new[i] - u[i];
734                sumsq += d * d;
735            }
736            prev_residual = residual;
737            residual = sumsq.sqrt();
738            u = u_new;
739        }
740        // Real measured convergence of the explicit integration.
741        let node_converged = residual.is_finite() && residual < 1e-6;
742        let node_conv_rate = if prev_residual.is_finite() && prev_residual > 0.0 {
743            residual / prev_residual
744        } else {
745            0.0
746        };
747        let node_residual = if residual.is_finite() {
748            residual
749        } else {
750            f64::MAX
751        };
752
753        // Pressure: approximate via Bernoulli P + 0.5*rho*u^2 = const
754        let rho = 1.225_f64;
755        let p_ref = 101325.0_f64;
756        let pressure: Vec<f64> = u.iter().map(|&ui| p_ref - 0.5 * rho * ui * ui).collect();
757
758        // Temperature: adiabatic relation T = T0*(P/P0)^((gamma-1)/gamma)
759        let gamma = 1.4_f64;
760        let t0 = 293.15_f64;
761        let temperature: Vec<f64> = pressure
762            .iter()
763            .map(|&pi| t0 * (pi / p_ref).powf((gamma - 1.0) / gamma))
764            .collect();
765
766        let velocity_field = PhysicsField {
767            field_id: format!("velocity_{}", node_id),
768            field_type: FieldType::Vector,
769            dimensions: vec![nx],
770            data: u,
771            metadata: FieldMetadata {
772                field_name: "Velocity".to_string(),
773                physical_quantity: "Velocity".to_string(),
774                units: "m/s".to_string(),
775                time_step: steps as u64,
776                iteration: steps as u64,
777            },
778        };
779        let pressure_field = PhysicsField {
780            field_id: format!("pressure_{}", node_id),
781            field_type: FieldType::Scalar,
782            dimensions: vec![nx],
783            data: pressure,
784            metadata: FieldMetadata {
785                field_name: "Pressure".to_string(),
786                physical_quantity: "Pressure".to_string(),
787                units: "Pa".to_string(),
788                time_step: steps as u64,
789                iteration: steps as u64,
790            },
791        };
792        let temperature_field = PhysicsField {
793            field_id: format!("temperature_{}", node_id),
794            field_type: FieldType::Scalar,
795            dimensions: vec![nx],
796            data: temperature,
797            metadata: FieldMetadata {
798                field_name: "Temperature".to_string(),
799                physical_quantity: "Temperature".to_string(),
800                units: "K".to_string(),
801                time_step: steps as u64,
802                iteration: steps as u64,
803            },
804        };
805
806        Ok(SimulationResult {
807            node_id: node_id.to_string(),
808            fields: vec![velocity_field, pressure_field, temperature_field],
809            convergence_info: ConvergenceInfo {
810                converged: node_converged,
811                iterations: steps as u32,
812                residual_norm: node_residual,
813                convergence_rate: node_conv_rate,
814                final_error: node_residual,
815            },
816            // Runtime utilization is not sampled per call; left at 0.0 (not measured)
817            // rather than fabricated.
818            performance_info: PerformanceInfo {
819                cpu_utilization: 0.0,
820                memory_utilization: 0.0,
821                network_utilization: 0.0,
822                io_utilization: 0.0,
823                parallel_efficiency: 0.0,
824            },
825        })
826    }
827}