1use crate::boundary::BoundingBox;
4use phyz_math::Vec3;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum SolverType {
9 RigidBody,
11 Particle,
13 Electromagnetic,
15 MolecularDynamics,
17 QuantumField,
19 LatticeBoltzmann,
21}
22
23#[derive(Clone, Debug)]
25pub enum ForceTransfer {
26 Direct {
28 damping: f64,
30 },
31 Flux {
33 rate: f64,
35 },
36 Barrier {
38 stiffness: f64,
42 equilibrium: f64,
43 },
44}
45
46impl ForceTransfer {
47 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 -damping * (vel_a - vel_b)
62 }
63 ForceTransfer::Flux { rate } => {
64 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 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#[derive(Clone, Debug)]
93pub struct Coupling {
94 pub solver_a: SolverType,
96 pub solver_b: SolverType,
98 pub overlap_region: BoundingBox,
100 pub force_transfer: ForceTransfer,
102}
103
104impl Coupling {
105 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 pub fn is_in_region(&self, pos: &Vec3) -> bool {
122 self.overlap_region.contains(pos)
123 }
124
125 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 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 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 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 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 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}