Skip to main content

phyz_coupling/
coupling.rs

1//! Coupling definitions between different solver types.
2
3use crate::boundary::BoundingBox;
4use phyz_math::Vec3;
5
6/// Types of physics solvers that can be coupled.
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum SolverType {
9    /// Rigid body dynamics (ABA/CRBA/RNEA).
10    RigidBody,
11    /// Material point method (MPM).
12    Particle,
13    /// Electromagnetic fields (FDTD).
14    Electromagnetic,
15    /// Molecular dynamics (MD).
16    MolecularDynamics,
17    /// Quantum field theory / lattice gauge.
18    QuantumField,
19    /// Lattice Boltzmann method.
20    LatticeBoltzmann,
21}
22
23/// Force transfer mechanism between solvers.
24#[derive(Clone, Debug)]
25pub enum ForceTransfer {
26    /// Direct velocity damping: F_a = -k (v_a - v_b).
27    Direct {
28        /// Damping coefficient.
29        damping: f64,
30    },
31    /// Momentum flux transfer at specified rate.
32    Flux {
33        /// Transfer rate (kg·m/s² or equivalent).
34        rate: f64,
35    },
36    /// Effective potential barrier.
37    Barrier {
38        /// Potential energy function (stored as parameters).
39        /// In practice, this would be a function pointer or trait object,
40        /// but for simplicity we use a simple spring potential here.
41        stiffness: f64,
42        equilibrium: f64,
43    },
44}
45
46impl ForceTransfer {
47    /// Compute coupling force between two objects.
48    ///
49    /// # Arguments
50    /// * `pos_a` - Position of object in solver A
51    /// * `vel_a` - Velocity of object in solver A
52    /// * `pos_b` - Position of object in solver B
53    /// * `vel_b` - Velocity of object in solver B
54    ///
55    /// # Returns
56    /// Force on object A (force on B is -force_a by Newton's 3rd law)
57    pub fn compute_force(&self, pos_a: &Vec3, vel_a: &Vec3, pos_b: &Vec3, vel_b: &Vec3) -> Vec3 {
58        match self {
59            ForceTransfer::Direct { damping } => {
60                // F_a = -k (v_a - v_b)
61                -damping * (vel_a - vel_b)
62            }
63            ForceTransfer::Flux { rate } => {
64                // Momentum transfer based on velocity difference
65                let rel_vel = vel_a - vel_b;
66                let dir = if rel_vel.norm() > 1e-10 {
67                    rel_vel.normalize()
68                } else {
69                    Vec3::zeros()
70                };
71                -rate * dir
72            }
73            ForceTransfer::Barrier {
74                stiffness,
75                equilibrium,
76            } => {
77                // F = -k (r - r_eq)
78                let r = pos_a - pos_b;
79                let dist = r.norm();
80                if dist > 1e-10 {
81                    let delta = dist - equilibrium;
82                    -stiffness * delta * (r / dist)
83                } else {
84                    Vec3::zeros()
85                }
86            }
87        }
88    }
89}
90
91/// Coupling definition between two solvers.
92#[derive(Clone, Debug)]
93pub struct Coupling {
94    /// First solver type.
95    pub solver_a: SolverType,
96    /// Second solver type.
97    pub solver_b: SolverType,
98    /// Spatial overlap region where coupling is active.
99    pub overlap_region: BoundingBox,
100    /// Force transfer mechanism.
101    pub force_transfer: ForceTransfer,
102}
103
104impl Coupling {
105    /// Create a new coupling.
106    pub fn new(
107        solver_a: SolverType,
108        solver_b: SolverType,
109        overlap_region: BoundingBox,
110        force_transfer: ForceTransfer,
111    ) -> Self {
112        Self {
113            solver_a,
114            solver_b,
115            overlap_region,
116            force_transfer,
117        }
118    }
119
120    /// Check if a position is in the coupling region.
121    pub fn is_in_region(&self, pos: &Vec3) -> bool {
122        self.overlap_region.contains(pos)
123    }
124
125    /// Compute coupling force for objects at given positions/velocities.
126    pub fn compute_coupling_force(
127        &self,
128        pos_a: &Vec3,
129        vel_a: &Vec3,
130        pos_b: &Vec3,
131        vel_b: &Vec3,
132    ) -> Option<Vec3> {
133        // Only apply force if at least one object is in the overlap region
134        if self.is_in_region(pos_a) || self.is_in_region(pos_b) {
135            Some(
136                self.force_transfer
137                    .compute_force(pos_a, vel_a, pos_b, vel_b),
138            )
139        } else {
140            None
141        }
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148    use approx::assert_relative_eq;
149
150    #[test]
151    fn test_direct_force_transfer() {
152        let transfer = ForceTransfer::Direct { damping: 2.0 };
153
154        let pos_a = Vec3::new(0.0, 0.0, 0.0);
155        let vel_a = Vec3::new(1.0, 0.0, 0.0);
156        let pos_b = Vec3::new(1.0, 0.0, 0.0);
157        let vel_b = Vec3::new(0.0, 0.0, 0.0);
158
159        let force = transfer.compute_force(&pos_a, &vel_a, &pos_b, &vel_b);
160
161        // F = -k (v_a - v_b) = -2.0 * (1.0, 0, 0) = (-2.0, 0, 0)
162        assert_relative_eq!(force.x, -2.0);
163        assert_relative_eq!(force.y, 0.0);
164        assert_relative_eq!(force.z, 0.0);
165    }
166
167    #[test]
168    fn test_barrier_force_transfer() {
169        let transfer = ForceTransfer::Barrier {
170            stiffness: 10.0,
171            equilibrium: 1.0,
172        };
173
174        let pos_a = Vec3::new(0.0, 0.0, 0.0);
175        let vel_a = Vec3::zeros();
176        let pos_b = Vec3::new(2.0, 0.0, 0.0);
177        let vel_b = Vec3::zeros();
178
179        let force = transfer.compute_force(&pos_a, &vel_a, &pos_b, &vel_b);
180
181        // Distance = 2.0, equilibrium = 1.0, delta = 1.0
182        // r = pos_a - pos_b = (-2, 0, 0), direction = (-1, 0, 0)
183        // F = -k * delta * direction = -10.0 * 1.0 * (-1, 0, 0) = (10, 0, 0)
184        // (pulling pos_a toward pos_b)
185        assert_relative_eq!(force.x, 10.0, epsilon = 1e-6);
186        assert_relative_eq!(force.y, 0.0);
187        assert_relative_eq!(force.z, 0.0);
188    }
189
190    #[test]
191    fn test_coupling_region() {
192        let coupling = Coupling::new(
193            SolverType::RigidBody,
194            SolverType::Electromagnetic,
195            BoundingBox::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0)),
196            ForceTransfer::Direct { damping: 1.0 },
197        );
198
199        assert!(coupling.is_in_region(&Vec3::new(0.0, 0.0, 0.0)));
200        assert!(!coupling.is_in_region(&Vec3::new(2.0, 0.0, 0.0)));
201    }
202
203    #[test]
204    fn test_coupling_force_computation() {
205        let coupling = Coupling::new(
206            SolverType::RigidBody,
207            SolverType::Particle,
208            BoundingBox::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0)),
209            ForceTransfer::Direct { damping: 2.0 },
210        );
211
212        // Both in region
213        let pos_a = Vec3::new(0.0, 0.0, 0.0);
214        let vel_a = Vec3::new(1.0, 0.0, 0.0);
215        let pos_b = Vec3::new(0.5, 0.0, 0.0);
216        let vel_b = Vec3::zeros();
217
218        let force = coupling
219            .compute_coupling_force(&pos_a, &vel_a, &pos_b, &vel_b)
220            .unwrap();
221        assert_relative_eq!(force.x, -2.0);
222
223        // Both outside region
224        let pos_a_out = Vec3::new(5.0, 0.0, 0.0);
225        let pos_b_out = Vec3::new(6.0, 0.0, 0.0);
226
227        let force_out = coupling.compute_coupling_force(&pos_a_out, &vel_a, &pos_b_out, &vel_b);
228        assert!(force_out.is_none());
229    }
230}