Skip to main content

qualia_core_db/specialized_libs/physics_simulation/
fields.rs

1use super::*;
2
3impl PhysicsSimulationLibrary {
4    /// HeatTransfer — 1D heat/diffusion equation `u_t = α·u_xx` on a grid with insulated
5    /// (Neumann) ends, so total heat is conserved and the profile relaxes toward its mean.
6    /// The spatial Laplacian is assembled here; time integration is `integrate_dopri5`.
7    pub fn run_heat_diffusion_1d(
8        &self,
9        initial: Vec<f64>,
10        alpha: f64,
11        dx: f64,
12        total_time: f64,
13        num_samples: usize,
14    ) -> Result<HeatDiffusionResult, PhysicsError> {
15        let n = initial.len();
16        if n < 3 || !(alpha > 0.0 && dx > 0.0 && total_time > 0.0) {
17            return Err(PhysicsError::InvalidConfiguration(
18                "require grid length >= 3, alpha > 0, dx > 0, total_time > 0".to_string(),
19            ));
20        }
21        let initial_mean = initial.iter().sum::<f64>() / n as f64;
22        let inv_dx2 = 1.0 / (dx * dx);
23        let deriv = move |_t: f64, u: &[f64], du: &mut [f64]| -> Result<(), OdeError> {
24            // Conservative flux form: du_i = α·(F_{i+1/2} − F_{i-1/2})/dx² with
25            // F_{i+1/2} = u_{i+1} − u_i, and zero flux at both insulated ends. Summing over
26            // i telescopes to α·(F_{n-1/2} − F_{-1/2})/dx² = 0, so total heat is conserved.
27            for i in 0..n {
28                let flux_left = if i == 0 { 0.0 } else { u[i] - u[i - 1] };
29                let flux_right = if i == n - 1 { 0.0 } else { u[i + 1] - u[i] };
30                du[i] = alpha * (flux_right - flux_left) * inv_dx2;
31            }
32            Ok(())
33        };
34        let (final_temp, snapshots, accepted, rejected) =
35            self.integrate_ode_samples(initial, total_time, num_samples, deriv)?;
36        let n_pts = snapshots.len();
37        let times: Vec<f64> = (0..n_pts)
38            .map(|k| total_time * k as f64 / (n_pts - 1).max(1) as f64)
39            .collect();
40        let final_mean = final_temp.iter().sum::<f64>() / n as f64;
41        let max_deviation_from_mean = final_temp
42            .iter()
43            .map(|&v| (v - final_mean).abs())
44            .fold(0.0, f64::max);
45        Ok(HeatDiffusionResult {
46            times,
47            snapshots,
48            final_temperature: final_temp,
49            initial_mean,
50            final_mean,
51            max_deviation_from_mean,
52            steps_accepted: accepted,
53            steps_rejected: rejected,
54        })
55    }
56    /// CEM — 1D scalar wave equation `u_tt = c²·u_xx` (a plane-wave field component) on a
57    /// grid with fixed (Dirichlet) ends. Posed as the first-order system `u_t = v`,
58    /// `v_t = c²·u_xx` and integrated by `integrate_dopri5`. Total wave energy is reported.
59    pub fn run_wave_equation_1d(
60        &self,
61        initial_displacement: Vec<f64>,
62        initial_velocity: Vec<f64>,
63        c: f64,
64        dx: f64,
65        total_time: f64,
66        num_samples: usize,
67    ) -> Result<WaveResult, PhysicsError> {
68        let n = initial_displacement.len();
69        if n < 3 || initial_velocity.len() != n || !(c > 0.0 && dx > 0.0 && total_time > 0.0) {
70            return Err(PhysicsError::InvalidConfiguration(
71                "require matching grids length >= 3, c > 0, dx > 0, total_time > 0".to_string(),
72            ));
73        }
74        let c2 = c * c;
75        let inv_dx2 = 1.0 / (dx * dx);
76        let mut state = Vec::with_capacity(2 * n);
77        state.extend_from_slice(&initial_displacement);
78        state.extend_from_slice(&initial_velocity);
79        // Ends are pinned to zero.
80        state[0] = 0.0;
81        state[n - 1] = 0.0;
82        state[n] = 0.0;
83        state[2 * n - 1] = 0.0;
84
85        let energy = |st: &[f64]| -> f64 {
86            let mut e = 0.0;
87            for i in 0..n {
88                let v = st[n + i];
89                e += 0.5 * v * v * dx;
90            }
91            for i in 0..n - 1 {
92                let grad = (st[i + 1] - st[i]) / dx;
93                e += 0.5 * c2 * grad * grad * dx;
94            }
95            e
96        };
97        let energy_initial = energy(&state);
98
99        let deriv = move |_t: f64, y: &[f64], dy: &mut [f64]| -> Result<(), OdeError> {
100            for i in 0..n {
101                if i == 0 || i == n - 1 {
102                    dy[i] = 0.0; // pinned displacement
103                    dy[n + i] = 0.0; // pinned velocity
104                } else {
105                    dy[i] = y[n + i]; // u_t = v
106                    dy[n + i] = c2 * (y[i + 1] - 2.0 * y[i] + y[i - 1]) * inv_dx2;
107                    // v_t = c² u_xx
108                }
109            }
110            Ok(())
111        };
112        let (final_state, snapshots, accepted, rejected) =
113            self.integrate_ode_samples(state, total_time, num_samples, deriv)?;
114        let energy_final = energy(&final_state);
115        let disp_snapshots: Vec<Vec<f64>> = snapshots.iter().map(|s| s[..n].to_vec()).collect();
116        let n_pts = snapshots.len();
117        let times: Vec<f64> = (0..n_pts)
118            .map(|k| total_time * k as f64 / (n_pts - 1).max(1) as f64)
119            .collect();
120        Ok(WaveResult {
121            times,
122            snapshots: disp_snapshots,
123            final_displacement: final_state[..n].to_vec(),
124            energy_initial,
125            energy_final,
126            steps_accepted: accepted,
127            steps_rejected: rejected,
128        })
129    }
130    /// MultiPhysics — coupled 1D advection–diffusion `u_t + c·u_x = α·u_xx` on a periodic
131    /// grid: a prescribed flow (fluid transport) coupled to diffusion (thermal spreading).
132    /// First-order upwind advection + central diffusion assembled here; integrated by
133    /// `integrate_dopri5`. The periodic scheme conserves `Σ u_i·dx`; the pure-diffusion
134    /// limit (`c = 0`) relaxes toward the mean.
135    pub fn run_advection_diffusion_1d(
136        &self,
137        initial: Vec<f64>,
138        advection_velocity: f64,
139        diffusion_coeff: f64,
140        dx: f64,
141        total_time: f64,
142        num_samples: usize,
143    ) -> Result<AdvectionDiffusionResult, PhysicsError> {
144        let n = initial.len();
145        if n < 3 || !(dx > 0.0 && total_time > 0.0) || diffusion_coeff < 0.0 {
146            return Err(PhysicsError::InvalidConfiguration(
147                "require grid length >= 3, dx > 0, total_time > 0, diffusion_coeff >= 0"
148                    .to_string(),
149            ));
150        }
151        let c = advection_velocity;
152        let alpha = diffusion_coeff;
153        let inv_dx = 1.0 / dx;
154        let inv_dx2 = 1.0 / (dx * dx);
155        let initial_total = initial.iter().sum::<f64>() * dx;
156        let deriv = move |_t: f64, u: &[f64], du: &mut [f64]| -> Result<(), OdeError> {
157            for i in 0..n {
158                let ip1 = (i + 1) % n;
159                let im1 = (i + n - 1) % n;
160                // First-order upwind advection (stable for either sign of c).
161                let adv = if c >= 0.0 {
162                    -c * (u[i] - u[im1]) * inv_dx
163                } else {
164                    -c * (u[ip1] - u[i]) * inv_dx
165                };
166                let diff = alpha * (u[ip1] - 2.0 * u[i] + u[im1]) * inv_dx2;
167                du[i] = adv + diff;
168            }
169            Ok(())
170        };
171        let (final_field, snapshots, accepted, rejected) =
172            self.integrate_ode_samples(initial, total_time, num_samples, deriv)?;
173        let n_pts = snapshots.len();
174        let times: Vec<f64> = (0..n_pts)
175            .map(|k| total_time * k as f64 / (n_pts - 1).max(1) as f64)
176            .collect();
177        let final_total = final_field.iter().sum::<f64>() * dx;
178        Ok(AdvectionDiffusionResult {
179            times,
180            snapshots,
181            final_field,
182            advection_velocity: c,
183            diffusion_coeff: alpha,
184            initial_total,
185            final_total,
186            steps_accepted: accepted,
187            steps_rejected: rejected,
188        })
189    }
190}