1#[derive(Debug, Clone)]
4pub struct RadiationCalculator {
5 rho_air: f64,
6 c_air: f64,
7}
8
9impl RadiationCalculator {
10 pub fn new() -> Self {
11 Self {
12 rho_air: 1.225,
13 c_air: 343.0,
14 }
15 }
16
17 pub fn with_params(rho_air: f64, c_air: f64) -> Self {
18 Self { rho_air, c_air }
19 }
20
21 pub fn radiated_power(
26 &self,
27 points: &[[f64; 2]],
28 values: &[f64],
29 cell_area: f64,
30 freq_dominant: f64,
31 ) -> f64 {
32 assert_eq!(points.len(), values.len());
33
34 let omega = 2.0 * std::f64::consts::PI * freq_dominant;
35 let k = omega / self.c_air;
36 let prefactor = self.rho_air * omega * omega / (4.0 * std::f64::consts::PI * self.c_air);
37
38 let mut power = 0.0;
39 for a in 0..values.len() {
40 let va = values[a];
41 power += va * va * cell_area * cell_area;
42 for b in a + 1..values.len() {
43 let dx = points[a][0] - points[b][0];
44 let dy = points[a][1] - points[b][1];
45 let dist = (dx * dx + dy * dy).sqrt();
46 let kr = k * dist;
47 let sinc = if kr < 1e-10 { 1.0 } else { kr.sin() / kr };
48 power += 2.0 * va * values[b] * sinc * cell_area * cell_area;
49 }
50 }
51
52 prefactor * power
53 }
54
55 pub fn modal_efficiency(&self, radius: f64, m: i32, freq: f64) -> f64 {
56 let k = 2.0 * std::f64::consts::PI * freq / self.c_air;
57 let ka = k * radius;
58 let sigma_base = match m.unsigned_abs() {
59 0 => 1.0,
60 1 => 0.5,
61 _ => 0.25,
62 };
63 sigma_base * (ka * ka).min(1.0)
64 }
65}
66
67impl Default for RadiationCalculator {
68 fn default() -> Self {
69 Self::new()
70 }
71}
72
73#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
74#[derive(Debug, Clone)]
75pub struct RadiationParams {
76 pub rho_air: f64,
77 pub c_air: f64,
78 pub max_modes: usize,
79}
80
81#[derive(Debug, Clone)]
82struct ModeData {
83 phi: Vec<f64>,
84 r_factor: f64,
85}
86
87#[derive(Debug, Clone)]
89pub struct ModalRadiationCalculator {
90 modes: Vec<ModeData>,
91 da: f64,
92 norm: f64,
93 n_active: usize,
94}
95
96impl ModalRadiationCalculator {
97 pub fn new(
98 params: &RadiationParams,
99 nx: usize,
100 ny: usize,
101 dx: f64,
102 active_cells: &[(usize, usize)],
103 d_val: f64,
104 rho_h: f64,
105 ) -> Self {
106 use std::f64::consts::PI;
107
108 let da = dx * dx;
109 let lx_eff = (nx as f64 - 3.0) * dx;
110 let ly_eff = (ny as f64 - 3.0) * dx;
111 let norm = 4.0 / (lx_eff * ly_eff);
112
113 let active_points: Vec<[f64; 2]> = active_cells
114 .iter()
115 .map(|&(i, j)| centered_point(i, j, nx, ny, dx))
116 .collect();
117
118 let n_cells = active_cells.len();
119 let mut dist_table = vec![0.0_f64; n_cells * (n_cells + 1) / 2];
120 let mut dist_idx = 0;
121 for a in 0..n_cells {
122 for b in a..n_cells {
123 if a == b {
124 dist_table[dist_idx] = 0.0;
125 } else {
126 let dx = active_points[a][0] - active_points[b][0];
127 let dy = active_points[a][1] - active_points[b][1];
128 dist_table[dist_idx] = (dx * dx + dy * dy).sqrt();
129 }
130 dist_idx += 1;
131 }
132 }
133
134 let m_max = (nx - 3) / 2;
135 let n_max = (ny - 3) / 2;
136 let coeff = (d_val / rho_h).sqrt();
137
138 let mut mode_list: Vec<(usize, usize, f64)> = Vec::new();
139 for m in 1..=m_max {
140 for n in 1..=n_max {
141 let kx = m as f64 * PI / lx_eff;
142 let ky = n as f64 * PI / ly_eff;
143 let omega_mn = coeff * (kx * kx + ky * ky);
144 mode_list.push((m, n, omega_mn / (2.0 * PI)));
145 }
146 }
147 mode_list.sort_by(|a, b| a.2.total_cmp(&b.2));
148 mode_list.truncate(params.max_modes);
149
150 let mut modes = Vec::with_capacity(mode_list.len());
151 for &(m, n, f_mn) in &mode_list {
152 let phi: Vec<f64> = active_cells
153 .iter()
154 .map(|&(i, j)| {
155 (m as f64 * PI * (i as f64 - 1.0) / (nx as f64 - 3.0)).sin()
156 * (n as f64 * PI * (j as f64 - 1.0) / (ny as f64 - 3.0)).sin()
157 })
158 .collect();
159
160 let omega = 2.0 * PI * f_mn;
161 let k = omega / params.c_air;
162 let prefactor = params.rho_air * omega * omega / (4.0 * PI * params.c_air);
163
164 let mut sum = 0.0;
165 let mut dist_idx = 0;
166 for a in 0..n_cells {
167 let phi_a = phi[a];
168 for (b, &phi_b) in phi.iter().enumerate().take(n_cells).skip(a) {
169 let r = dist_table[dist_idx];
170 dist_idx += 1;
171 let kr = k * r;
172 let sinc = if kr < 1e-10 { 1.0 } else { kr.sin() / kr };
173 let contrib = phi_a * phi_b * sinc;
174 if a == b {
175 sum += contrib;
176 } else {
177 sum += 2.0 * contrib;
178 }
179 }
180 }
181
182 modes.push(ModeData {
183 phi,
184 r_factor: prefactor * sum * da * da,
185 });
186 }
187
188 Self {
189 modes,
190 da,
191 norm,
192 n_active: n_cells,
193 }
194 }
195
196 pub fn radiated_power(&self, active_values: &[f64]) -> f64 {
197 assert_eq!(active_values.len(), self.n_active);
198 self.modes
199 .iter()
200 .map(|mode| {
201 let mut a_mn = 0.0;
202 for (value, phi) in active_values.iter().zip(mode.phi.iter()) {
203 a_mn += value * phi;
204 }
205 a_mn *= self.da * self.norm;
206 mode.r_factor * a_mn * a_mn
207 })
208 .sum()
209 }
210
211 pub fn num_modes(&self) -> usize {
212 self.modes.len()
213 }
214}
215
216fn centered_point(i: usize, j: usize, nx: usize, ny: usize, dx: f64) -> [f64; 2] {
217 let cx = (nx / 2) as f64 * dx;
218 let cy = (ny / 2) as f64 * dx;
219 [i as f64 * dx - cx, j as f64 * dx - cy]
220}