1pub mod cli;
2pub mod constants;
3pub mod loading;
4pub mod validation;
5
6use nalgebra::Complex;
7use pyo3::prelude::*;
8#[cfg(feature = "stub-gen")]
9use pyo3_stub_gen::derive::*;
10use serde::{Deserialize, Serialize};
11use std::path::PathBuf;
12
13use crate::bins::{self, BinningScheme};
14use crate::diff::Mapping;
15use crate::orientation::Euler;
16use crate::orientation::*;
17use crate::zones::ZoneConfig;
18
19fn default_zones() -> Vec<ZoneConfig> {
21 vec![]
22}
23
24#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
26pub struct OutputConfig {
27 pub settings_json: bool,
29 pub mueller_2d: bool,
31 pub mueller_1d: bool,
33 pub mueller_components: MuellerComponentConfig,
35}
36
37#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
39pub struct MuellerComponentConfig {
40 pub total: bool,
42 pub beam: bool,
44 pub external: bool,
46}
47
48pub use self::constants::*;
50pub use self::loading::{load_config, load_config_with_cli, load_default_config};
51
52#[cfg_attr(feature = "stub-gen", gen_stub_pyclass)]
54#[pyclass(module = "goad._goad")]
55#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
56pub struct Settings {
57 pub wavelength: f32,
58 pub beam_power_threshold: f32,
59 pub beam_area_threshold_fac: f32,
60 pub cutoff: f32,
61 pub medium_refr_index: Complex<f32>,
62 pub particle_refr_index: Vec<Complex<f32>>,
63 pub orientation: Orientation,
64 pub geom_name: String,
65 pub max_rec: i32,
66 pub max_tir: i32,
67 #[serde(default = "default_zones")]
69 pub zones: Vec<ZoneConfig>,
70 #[serde(default, skip_serializing)]
73 pub binning: Option<BinningScheme>,
74 pub seed: Option<u64>,
75 #[serde(default = "constants::default_scale_factor")]
77 pub scale: f32,
78 pub distortion: Option<f32>,
79 #[serde(default = "constants::default_geom_scale")]
81 pub geom_scale: Option<Vec<f32>>,
82 #[serde(default = "constants::default_directory")]
83 pub directory: PathBuf,
84 #[serde(default = "constants::default_fov_factor")]
85 pub fov_factor: Option<f32>,
86 pub mapping: Mapping,
87 #[serde(default = "constants::default_output_config")]
88 pub output: OutputConfig,
89 pub coherence: bool,
90 #[serde(default = "constants::default_quiet")]
92 pub quiet: bool,
93}
94
95#[cfg_attr(feature = "stub-gen", gen_stub_pymethods)]
96#[pymethods]
97impl Settings {
98 #[new]
99 #[pyo3(signature = (
100 geom_path,
101 wavelength = DEFAULT_WAVELENGTH,
102 particle_refr_index_re = DEFAULT_PARTICLE_REFR_INDEX_RE,
103 particle_refr_index_im = DEFAULT_PARTICLE_REFR_INDEX_IM,
104 medium_refr_index_re = DEFAULT_MEDIUM_REFR_INDEX_RE,
105 medium_refr_index_im = DEFAULT_MEDIUM_REFR_INDEX_IM,
106 orientation = None,
107 zones = None,
108 beam_power_threshold = DEFAULT_BEAM_POWER_THRESHOLD,
109 beam_area_threshold_fac = DEFAULT_BEAM_AREA_THRESHOLD_FAC,
110 cutoff = DEFAULT_CUTOFF,
111 max_rec = DEFAULT_MAX_REC,
112 max_tir = DEFAULT_MAX_TIR,
113 scale = 1.0,
114 distortion = None,
115 directory = "goad_run",
116 mapping = DEFAULT_MAPPING,
117 coherence = DEFAULT_COHERENCE,
118 quiet = DEFAULT_QUIET,
119 seed = None,
120 ))]
121 fn py_new(
122 geom_path: String,
123 wavelength: f32,
124 particle_refr_index_re: f32,
125 particle_refr_index_im: f32,
126 medium_refr_index_re: f32,
127 medium_refr_index_im: f32,
128 orientation: Option<Orientation>,
129 zones: Option<Vec<ZoneConfig>>,
130 beam_power_threshold: f32,
131 beam_area_threshold_fac: f32,
132 cutoff: f32,
133 max_rec: i32,
134 max_tir: i32,
135 scale: f32,
136 distortion: Option<f32>,
137 directory: &str,
138 mapping: Mapping,
139 coherence: bool,
140 quiet: bool,
141 seed: Option<u64>,
142 ) -> PyResult<Self> {
143 if wavelength <= 0.0 {
145 return Err(pyo3::exceptions::PyValueError::new_err(format!(
146 "Wavelength must be positive, got: {}",
147 wavelength
148 )));
149 }
150
151 if !std::path::Path::new(&geom_path).exists() {
152 return Err(pyo3::exceptions::PyFileNotFoundError::new_err(format!(
153 "Geometry file not found: {}",
154 geom_path
155 )));
156 }
157
158 if cutoff < 0.0 || cutoff > 1.0 {
159 return Err(pyo3::exceptions::PyValueError::new_err(format!(
160 "Cutoff must be between 0 and 1, got: {}",
161 cutoff
162 )));
163 }
164
165 if max_rec < 0 {
166 return Err(pyo3::exceptions::PyValueError::new_err(format!(
167 "max_rec must be non-negative, got: {}",
168 max_rec
169 )));
170 }
171
172 if max_tir < 0 {
173 return Err(pyo3::exceptions::PyValueError::new_err(format!(
174 "max_tir must be non-negative, got: {}",
175 max_tir
176 )));
177 }
178 let orientation = orientation.unwrap_or_else(|| Orientation {
180 scheme: Scheme::Sobol { num_orients: 1 },
181 euler_convention: DEFAULT_EULER_ORDER,
182 });
183
184 let zones = zones.unwrap_or_else(|| {
186 vec![ZoneConfig::new(bins::Scheme::Interval {
187 thetas: vec![0.0, 5.0, 175.0, 179.0, 180.0],
188 theta_spacings: vec![0.1, 2.0, 0.5, 0.1],
189 phis: vec![0.0, 360.0],
190 phi_spacings: vec![7.5],
191 })]
192 });
193
194 let mut settings = Settings {
195 wavelength,
196 beam_power_threshold,
197 beam_area_threshold_fac,
198 cutoff,
199 medium_refr_index: Complex::new(medium_refr_index_re, medium_refr_index_im),
200 particle_refr_index: vec![Complex::new(particle_refr_index_re, particle_refr_index_im)],
201 orientation,
202 geom_name: geom_path,
203 max_rec,
204 max_tir,
205 zones,
206 binning: None,
207 seed,
208 scale,
209 distortion,
210 geom_scale: None,
211 directory: PathBuf::from(directory),
212 fov_factor: None,
213 mapping,
214 output: constants::default_output_config(),
215 coherence,
216 quiet,
217 };
218
219 validation::validate_config(&mut settings);
220
221 Ok(settings)
222 }
223
224 #[setter]
226 fn set_eulers(&mut self, euler: Vec<f32>) {
227 self.orientation = Orientation {
228 scheme: Scheme::Discrete {
229 eulers: vec![Euler::new(euler[0], euler[1], euler[2])],
230 },
231 euler_convention: EulerConvention::XYZ,
232 };
233 }
234
235 #[getter]
237 fn get_eulers(&self) -> Vec<f32> {
238 match &self.orientation.scheme {
239 Scheme::Discrete { eulers } => vec![eulers[0].alpha, eulers[0].beta, eulers[0].gamma],
240 _ => vec![0.0, 0.0, 0.0],
241 }
242 }
243
244 #[setter]
246 fn set_orientation(&mut self, orientation: Orientation) {
247 self.orientation = orientation;
248 }
249
250 #[getter]
252 fn get_orientation(&self) -> Orientation {
253 self.orientation.clone()
254 }
255
256 #[setter]
258 fn set_geom_path(&mut self, geom_path: String) {
259 self.geom_name = geom_path;
260 }
261
262 #[getter]
264 fn get_geom_path(&self) -> String {
265 self.geom_name.clone()
266 }
267
268 #[setter]
270 fn set_wavelength(&mut self, wavelength: f32) {
271 self.wavelength = wavelength;
272 }
273
274 #[getter]
276 fn get_wavelength(&self) -> f32 {
277 self.wavelength
278 }
279
280 #[setter]
282 fn set_particle_refr_index_re(&mut self, re: f32) {
283 if !self.particle_refr_index.is_empty() {
284 self.particle_refr_index[0].re = re;
285 }
286 }
287
288 #[getter]
290 fn get_particle_refr_index_re(&self) -> f32 {
291 if !self.particle_refr_index.is_empty() {
292 self.particle_refr_index[0].re
293 } else {
294 0.0
295 }
296 }
297
298 #[setter]
300 fn set_particle_refr_index_im(&mut self, im: f32) {
301 if !self.particle_refr_index.is_empty() {
302 self.particle_refr_index[0].im = im;
303 }
304 }
305
306 #[getter]
308 fn get_particle_refr_index_im(&self) -> f32 {
309 if !self.particle_refr_index.is_empty() {
310 self.particle_refr_index[0].im
311 } else {
312 0.0
313 }
314 }
315
316 #[setter]
318 fn set_medium_refr_index_re(&mut self, re: f32) {
319 self.medium_refr_index.re = re;
320 }
321
322 #[getter]
324 fn get_medium_refr_index_re(&self) -> f32 {
325 self.medium_refr_index.re
326 }
327
328 #[setter]
330 fn set_medium_refr_index_im(&mut self, im: f32) {
331 self.medium_refr_index.im = im;
332 }
333
334 #[getter]
336 fn get_medium_refr_index_im(&self) -> f32 {
337 self.medium_refr_index.im
338 }
339
340 #[setter]
342 fn set_beam_power_threshold(&mut self, threshold: f32) {
343 self.beam_power_threshold = threshold;
344 }
345
346 #[getter]
348 fn get_beam_power_threshold(&self) -> f32 {
349 self.beam_power_threshold
350 }
351
352 #[setter]
354 fn set_cutoff(&mut self, cutoff: f32) {
355 self.cutoff = cutoff;
356 }
357
358 #[getter]
360 fn get_cutoff(&self) -> f32 {
361 self.cutoff
362 }
363
364 #[setter]
366 fn set_max_rec(&mut self, max_rec: i32) {
367 self.max_rec = max_rec;
368 }
369
370 #[getter]
372 fn get_max_rec(&self) -> i32 {
373 self.max_rec
374 }
375
376 #[setter]
378 fn set_max_tir(&mut self, max_tir: i32) {
379 self.max_tir = max_tir;
380 }
381
382 #[getter]
384 fn get_max_tir(&self) -> i32 {
385 self.max_tir
386 }
387
388 #[setter]
390 fn set_zones(&mut self, zones: Vec<ZoneConfig>) {
391 self.zones = zones;
392 }
393
394 #[getter]
396 fn get_zones(&self) -> Vec<ZoneConfig> {
397 self.zones.clone()
398 }
399
400 #[setter]
402 fn set_geom_scale(&mut self, geom_scale: Option<Vec<f32>>) {
403 self.geom_scale = geom_scale;
404 }
405
406 #[getter]
408 fn get_geom_scale(&self) -> Option<Vec<f32>> {
409 self.geom_scale.clone()
410 }
411
412 #[setter]
414 fn set_seed(&mut self, seed: Option<u64>) {
415 self.seed = seed;
416 }
417
418 #[getter]
420 fn get_seed(&self) -> Option<u64> {
421 self.seed
422 }
423
424 #[setter]
426 fn set_distortion(&mut self, distortion: Option<f32>) {
427 self.distortion = distortion;
428 }
429
430 #[getter]
432 fn get_distortion(&self) -> Option<f32> {
433 self.distortion
434 }
435
436 #[setter]
438 fn set_fov_factor(&mut self, fov_factor: Option<f32>) {
439 self.fov_factor = fov_factor;
440 }
441
442 #[getter]
444 fn get_fov_factor(&self) -> Option<f32> {
445 self.fov_factor
446 }
447
448 #[setter]
450 fn set_quiet(&mut self, quiet: bool) {
451 self.quiet = quiet;
452 }
453
454 #[getter]
456 fn get_quiet(&self) -> bool {
457 self.quiet
458 }
459}
460
461impl Settings {
462 pub fn beam_area_threshold(&self) -> f32 {
463 self.wavelength * self.wavelength * self.beam_area_threshold_fac * self.scale.powi(2)
464 }
465}