1use dualis_units::{Length, LengthVec, Time};
30use glam::DVec3;
31
32pub trait ScalarField {
34 fn at(&self, p: LengthVec, t: Time) -> f64;
36
37 fn gradient(&self, p: LengthVec, t: Time, h: Length) -> DVec3 {
39 let step = h.to_si();
40 let mut out = DVec3::ZERO;
41 for axis in 0..3 {
42 let mut d = DVec3::ZERO;
43 d[axis] = step;
44 let plus = self.at(p + LengthVec::from_si(d), t);
45 let minus = self.at(p - LengthVec::from_si(d), t);
46 out[axis] = (plus - minus) / (2.0 * step);
47 }
48 out
49 }
50
51 fn laplacian(&self, p: LengthVec, t: Time, h: Length) -> f64 {
55 let step = h.to_si();
56 let centre = self.at(p, t);
57 let mut sum = 0.0;
58 for axis in 0..3 {
59 let mut d = DVec3::ZERO;
60 d[axis] = step;
61 sum += self.at(p + LengthVec::from_si(d), t) + self.at(p - LengthVec::from_si(d), t)
62 - 2.0 * centre;
63 }
64 sum / (step * step)
65 }
66
67 fn rate(&self, p: LengthVec, t: Time, dt: Time) -> f64 {
69 (self.at(p, t + dt) - self.at(p, t - dt)) / (2.0 * dt.to_si())
70 }
71}
72
73pub trait VectorField {
75 fn at(&self, p: LengthVec, t: Time) -> DVec3;
77
78 fn divergence(&self, p: LengthVec, t: Time, h: Length) -> f64 {
81 let step = h.to_si();
82 let mut sum = 0.0;
83 for axis in 0..3 {
84 let mut d = DVec3::ZERO;
85 d[axis] = step;
86 sum += (self.at(p + LengthVec::from_si(d), t)[axis]
87 - self.at(p - LengthVec::from_si(d), t)[axis])
88 / (2.0 * step);
89 }
90 sum
91 }
92
93 fn curl(&self, p: LengthVec, t: Time, h: Length) -> DVec3 {
97 let step = h.to_si();
98 let d = |axis: usize| {
99 let mut e = DVec3::ZERO;
100 e[axis] = step;
101 let plus = self.at(p + LengthVec::from_si(e), t);
102 let minus = self.at(p - LengthVec::from_si(e), t);
103 (plus - minus) / (2.0 * step)
104 };
105 let (dx, dy, dz) = (d(0), d(1), d(2));
106 DVec3::new(dy.z - dz.y, dz.x - dx.z, dx.y - dy.x)
107 }
108}
109
110#[derive(Clone, Copy, Debug, PartialEq)]
113pub struct Uniform(pub f64);
114
115impl ScalarField for Uniform {
116 fn at(&self, _p: LengthVec, _t: Time) -> f64 {
117 self.0
118 }
119}
120
121pub struct Analytic<F>(pub F);
123
124impl<F: Fn(LengthVec, Time) -> f64> ScalarField for Analytic<F> {
125 fn at(&self, p: LengthVec, t: Time) -> f64 {
126 (self.0)(p, t)
127 }
128}
129
130impl<F: Fn(LengthVec, Time) -> DVec3> VectorField for Analytic<F> {
131 fn at(&self, p: LengthVec, t: Time) -> DVec3 {
132 (self.0)(p, t)
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 const H: Length = Length::from_si(1e-4);
141
142 #[test]
146 fn derivatives_match_the_closed_form() {
147 let f = Analytic(|p: LengthVec, _t: Time| {
148 let v = p.to_si();
149 v.x * v.x + 2.0 * v.y * v.y + 3.0 * v.z * v.z
150 });
151 let p = LengthVec::m(0.3, -0.2, 0.5);
152 let t = Time::ZERO;
153
154 let grad = f.gradient(p, t, H);
155 let expected = DVec3::new(0.6, -0.8, 3.0);
156 assert!((grad - expected).length() < 1e-9, "got {grad}");
157
158 let lap = f.laplacian(p, t, H);
160 assert!((lap - 12.0).abs() < 1e-6, "got {lap}");
161 }
162
163 #[test]
166 fn divergence_and_curl_match_the_closed_form() {
167 let omega = 3.0;
168 let rotation = Analytic(|p: LengthVec, _t: Time| {
169 let v = p.to_si();
170 DVec3::new(-3.0 * v.y, 3.0 * v.x, 0.0)
171 });
172 let p = LengthVec::m(0.4, 0.1, -0.2);
173 let t = Time::ZERO;
174
175 assert!(
176 VectorField::divergence(&rotation, p, t, H).abs() < 1e-9,
177 "a rotation moves fluid around, not outwards"
178 );
179 let curl = rotation.curl(p, t, H);
180 assert!(
181 (curl - DVec3::new(0.0, 0.0, 2.0 * omega)).length() < 1e-9,
182 "got {curl}"
183 );
184 }
185
186 #[test]
190 fn a_radial_field_diverges_without_rotating() {
191 let outflow = Analytic(|p: LengthVec, _t: Time| p.to_si());
192 let p = LengthVec::m(0.2, -0.4, 0.7);
193 let t = Time::ZERO;
194 assert!((VectorField::divergence(&outflow, p, t, H) - 3.0).abs() < 1e-9);
195 assert!(outflow.curl(p, t, H).length() < 1e-9);
196 }
197
198 #[test]
200 fn a_uniform_field_has_no_derivatives_of_any_kind() {
201 let u = Uniform(4.2);
202 let p = LengthVec::m(1.0, 2.0, 3.0);
203 assert_eq!(u.at(p, Time::s(9.0)), 4.2);
204 assert_eq!(u.gradient(p, Time::ZERO, H), DVec3::ZERO);
205 assert_eq!(u.laplacian(p, Time::ZERO, H), 0.0);
206 assert_eq!(u.rate(p, Time::ZERO, Time::s(0.1)), 0.0);
207 }
208
209 #[test]
210 fn time_derivatives_match_the_closed_form() {
211 let f = Analytic(|_p: LengthVec, t: Time| t.to_si() * t.to_si());
213 let rate = f.rate(LengthVec::ZERO, Time::s(3.0), Time::s(1e-4));
214 assert!((rate - 6.0).abs() < 1e-9, "got {rate}");
215 }
216}