oxiphysics_python/world_api/
lbm.rs1#![allow(missing_docs)]
7
8#[derive(Debug, Clone)]
14#[allow(dead_code)]
15pub struct PyLbmConfig {
16 pub width: usize,
18 pub height: usize,
20 pub viscosity: f64,
22 pub body_force: [f64; 2],
24}
25
26impl PyLbmConfig {
27 pub fn new(width: usize, height: usize, viscosity: f64) -> Self {
29 Self {
30 width,
31 height,
32 viscosity: viscosity.max(1e-6),
33 body_force: [0.0; 2],
34 }
35 }
36
37 pub fn lid_driven_cavity() -> Self {
39 Self::new(64, 64, 0.01)
40 }
41
42 pub fn omega(&self) -> f64 {
46 1.0 / (3.0 * self.viscosity + 0.5)
47 }
48}
49
50#[derive(Debug, Clone)]
55#[allow(dead_code)]
56pub struct PyLbmGrid {
57 width: usize,
59 height: usize,
61 omega: f64,
63 body_force: [f64; 2],
65 f: Vec<f64>,
67 f_tmp: Vec<f64>,
69 step_count: u64,
71}
72
73const D2Q9_W: [f64; 9] = [
75 4.0 / 9.0,
76 1.0 / 9.0,
77 1.0 / 9.0,
78 1.0 / 9.0,
79 1.0 / 9.0,
80 1.0 / 36.0,
81 1.0 / 36.0,
82 1.0 / 36.0,
83 1.0 / 36.0,
84];
85const D2Q9_EX: [f64; 9] = [0.0, 1.0, 0.0, -1.0, 0.0, 1.0, -1.0, -1.0, 1.0];
87const D2Q9_EY: [f64; 9] = [0.0, 0.0, 1.0, 0.0, -1.0, 1.0, 1.0, -1.0, -1.0];
88
89impl PyLbmGrid {
90 pub fn new(config: &PyLbmConfig) -> Self {
93 let n = config.width * config.height;
94 let mut f = vec![0.0f64; n * 9];
95 for i in 0..n {
97 for q in 0..9 {
98 f[i * 9 + q] = D2Q9_W[q];
99 }
100 }
101 Self {
102 width: config.width,
103 height: config.height,
104 omega: config.omega(),
105 body_force: config.body_force,
106 f: f.clone(),
107 f_tmp: f,
108 step_count: 0,
109 }
110 }
111
112 pub fn width(&self) -> usize {
114 self.width
115 }
116
117 pub fn height(&self) -> usize {
119 self.height
120 }
121
122 pub fn step_count(&self) -> u64 {
124 self.step_count
125 }
126
127 pub fn step(&mut self) {
131 let w = self.width;
132 let h = self.height;
133 let omega = self.omega;
134 let bfx = self.body_force[0];
135 let bfy = self.body_force[1];
136
137 for y in 0..h {
139 for x in 0..w {
140 let idx = (y * w + x) * 9;
141 let rho: f64 = self.f[idx..idx + 9].iter().sum();
143 let mut ux = 0.0f64;
145 let mut uy = 0.0f64;
146 for q in 0..9 {
147 ux += D2Q9_EX[q] * self.f[idx + q];
148 uy += D2Q9_EY[q] * self.f[idx + q];
149 }
150 let rho_inv = if rho > 1e-15 { 1.0 / rho } else { 0.0 };
151 ux = ux * rho_inv + bfx * 0.5 / omega;
152 uy = uy * rho_inv + bfy * 0.5 / omega;
153 let u2 = ux * ux + uy * uy;
154 for q in 0..9 {
156 let eu = D2Q9_EX[q] * ux + D2Q9_EY[q] * uy;
157 let feq = D2Q9_W[q] * rho * (1.0 + 3.0 * eu + 4.5 * eu * eu - 1.5 * u2);
158 self.f_tmp[idx + q] = self.f[idx + q] * (1.0 - omega) + feq * omega;
159 }
160 }
161 }
162
163 let f_src = self.f_tmp.clone();
165 for y in 0..h {
166 for x in 0..w {
167 let dst_idx = (y * w + x) * 9;
168 #[allow(clippy::manual_memcpy)]
169 for q in 0..9 {
170 let src_x =
171 ((x as isize - D2Q9_EX[q] as isize).rem_euclid(w as isize)) as usize;
172 let src_y =
173 ((y as isize - D2Q9_EY[q] as isize).rem_euclid(h as isize)) as usize;
174 let src_idx = (src_y * w + src_x) * 9;
175 self.f[dst_idx + q] = f_src[src_idx + q];
176 }
177 }
178 }
179
180 self.step_count += 1;
181 }
182
183 pub fn velocity_at(&self, x: usize, y: usize) -> [f64; 2] {
187 if x >= self.width || y >= self.height {
188 return [0.0; 2];
189 }
190 let idx = (y * self.width + x) * 9;
191 let rho: f64 = self.f[idx..idx + 9].iter().sum();
192 if rho < 1e-15 {
193 return [0.0; 2];
194 }
195 let mut ux = 0.0f64;
196 let mut uy = 0.0f64;
197 for q in 0..9 {
198 ux += D2Q9_EX[q] * self.f[idx + q];
199 uy += D2Q9_EY[q] * self.f[idx + q];
200 }
201 [ux / rho, uy / rho]
202 }
203
204 pub fn density_at(&self, x: usize, y: usize) -> f64 {
206 if x >= self.width || y >= self.height {
207 return 0.0;
208 }
209 let idx = (y * self.width + x) * 9;
210 self.f[idx..idx + 9].iter().sum()
211 }
212
213 pub fn velocity_field(&self) -> Vec<f64> {
216 let n = self.width * self.height;
217 let mut out = vec![0.0f64; n * 2];
218 for y in 0..self.height {
219 for x in 0..self.width {
220 let cell = y * self.width + x;
221 let v = self.velocity_at(x, y);
222 out[cell * 2] = v[0];
223 out[cell * 2 + 1] = v[1];
224 }
225 }
226 out
227 }
228
229 pub fn density_field(&self) -> Vec<f64> {
231 let n = self.width * self.height;
232 let mut out = vec![0.0f64; n];
233 for y in 0..self.height {
234 for x in 0..self.width {
235 out[y * self.width + x] = self.density_at(x, y);
236 }
237 }
238 out
239 }
240}