pub mod cli;
pub mod constants;
pub mod loading;
pub mod validation;
use nalgebra::Complex;
use pyo3::prelude::*;
#[cfg(feature = "stub-gen")]
use pyo3_stub_gen::derive::*;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use crate::bins::{self, BinningScheme};
use crate::diff::Mapping;
use crate::orientation::Euler;
use crate::orientation::*;
use crate::zones::ZoneConfig;
fn default_zones() -> Vec<ZoneConfig> {
vec![]
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct OutputConfig {
pub settings_json: bool,
pub mueller_2d: bool,
pub mueller_1d: bool,
pub mueller_components: MuellerComponentConfig,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct MuellerComponentConfig {
pub total: bool,
pub beam: bool,
pub external: bool,
}
pub use self::constants::*;
pub use self::loading::{load_config, load_config_with_cli, load_default_config};
#[cfg_attr(feature = "stub-gen", gen_stub_pyclass)]
#[pyclass(module = "goad._goad")]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct Settings {
pub wavelength: f32,
pub beam_power_threshold: f32,
pub beam_area_threshold_fac: f32,
pub cutoff: f32,
pub medium_refr_index: Complex<f32>,
pub particle_refr_index: Vec<Complex<f32>>,
pub orientation: Orientation,
pub geom_name: String,
pub max_rec: i32,
pub max_tir: i32,
#[serde(default = "default_zones")]
pub zones: Vec<ZoneConfig>,
#[serde(default, skip_serializing)]
pub binning: Option<BinningScheme>,
pub seed: Option<u64>,
#[serde(default = "constants::default_scale_factor")]
pub scale: f32,
pub distortion: Option<f32>,
#[serde(default = "constants::default_geom_scale")]
pub geom_scale: Option<Vec<f32>>,
#[serde(default = "constants::default_directory")]
pub directory: PathBuf,
#[serde(default = "constants::default_fov_factor")]
pub fov_factor: Option<f32>,
pub mapping: Mapping,
#[serde(default = "constants::default_output_config")]
pub output: OutputConfig,
pub coherence: bool,
#[serde(default = "constants::default_quiet")]
pub quiet: bool,
}
#[cfg_attr(feature = "stub-gen", gen_stub_pymethods)]
#[pymethods]
impl Settings {
#[new]
#[pyo3(signature = (
geom_path,
wavelength = DEFAULT_WAVELENGTH,
particle_refr_index_re = DEFAULT_PARTICLE_REFR_INDEX_RE,
particle_refr_index_im = DEFAULT_PARTICLE_REFR_INDEX_IM,
medium_refr_index_re = DEFAULT_MEDIUM_REFR_INDEX_RE,
medium_refr_index_im = DEFAULT_MEDIUM_REFR_INDEX_IM,
orientation = None,
zones = None,
beam_power_threshold = DEFAULT_BEAM_POWER_THRESHOLD,
beam_area_threshold_fac = DEFAULT_BEAM_AREA_THRESHOLD_FAC,
cutoff = DEFAULT_CUTOFF,
max_rec = DEFAULT_MAX_REC,
max_tir = DEFAULT_MAX_TIR,
scale = 1.0,
distortion = None,
directory = "goad_run",
mapping = DEFAULT_MAPPING,
coherence = DEFAULT_COHERENCE,
quiet = DEFAULT_QUIET,
seed = None,
))]
fn py_new(
geom_path: String,
wavelength: f32,
particle_refr_index_re: f32,
particle_refr_index_im: f32,
medium_refr_index_re: f32,
medium_refr_index_im: f32,
orientation: Option<Orientation>,
zones: Option<Vec<ZoneConfig>>,
beam_power_threshold: f32,
beam_area_threshold_fac: f32,
cutoff: f32,
max_rec: i32,
max_tir: i32,
scale: f32,
distortion: Option<f32>,
directory: &str,
mapping: Mapping,
coherence: bool,
quiet: bool,
seed: Option<u64>,
) -> PyResult<Self> {
if wavelength <= 0.0 {
return Err(pyo3::exceptions::PyValueError::new_err(format!(
"Wavelength must be positive, got: {}",
wavelength
)));
}
if !std::path::Path::new(&geom_path).exists() {
return Err(pyo3::exceptions::PyFileNotFoundError::new_err(format!(
"Geometry file not found: {}",
geom_path
)));
}
if cutoff < 0.0 || cutoff > 1.0 {
return Err(pyo3::exceptions::PyValueError::new_err(format!(
"Cutoff must be between 0 and 1, got: {}",
cutoff
)));
}
if max_rec < 0 {
return Err(pyo3::exceptions::PyValueError::new_err(format!(
"max_rec must be non-negative, got: {}",
max_rec
)));
}
if max_tir < 0 {
return Err(pyo3::exceptions::PyValueError::new_err(format!(
"max_tir must be non-negative, got: {}",
max_tir
)));
}
let orientation = orientation.unwrap_or_else(|| Orientation {
scheme: Scheme::Sobol { num_orients: 1 },
euler_convention: DEFAULT_EULER_ORDER,
});
let zones = zones.unwrap_or_else(|| {
vec![ZoneConfig::new(bins::Scheme::Interval {
thetas: vec![0.0, 5.0, 175.0, 179.0, 180.0],
theta_spacings: vec![0.1, 2.0, 0.5, 0.1],
phis: vec![0.0, 360.0],
phi_spacings: vec![7.5],
})]
});
let mut settings = Settings {
wavelength,
beam_power_threshold,
beam_area_threshold_fac,
cutoff,
medium_refr_index: Complex::new(medium_refr_index_re, medium_refr_index_im),
particle_refr_index: vec![Complex::new(particle_refr_index_re, particle_refr_index_im)],
orientation,
geom_name: geom_path,
max_rec,
max_tir,
zones,
binning: None,
seed,
scale,
distortion,
geom_scale: None,
directory: PathBuf::from(directory),
fov_factor: None,
mapping,
output: constants::default_output_config(),
coherence,
quiet,
};
validation::validate_config(&mut settings);
Ok(settings)
}
#[setter]
fn set_eulers(&mut self, euler: Vec<f32>) {
self.orientation = Orientation {
scheme: Scheme::Discrete {
eulers: vec![Euler::new(euler[0], euler[1], euler[2])],
},
euler_convention: EulerConvention::XYZ,
};
}
#[getter]
fn get_eulers(&self) -> Vec<f32> {
match &self.orientation.scheme {
Scheme::Discrete { eulers } => vec![eulers[0].alpha, eulers[0].beta, eulers[0].gamma],
_ => vec![0.0, 0.0, 0.0],
}
}
#[setter]
fn set_orientation(&mut self, orientation: Orientation) {
self.orientation = orientation;
}
#[getter]
fn get_orientation(&self) -> Orientation {
self.orientation.clone()
}
#[setter]
fn set_geom_path(&mut self, geom_path: String) {
self.geom_name = geom_path;
}
#[getter]
fn get_geom_path(&self) -> String {
self.geom_name.clone()
}
#[setter]
fn set_wavelength(&mut self, wavelength: f32) {
self.wavelength = wavelength;
}
#[getter]
fn get_wavelength(&self) -> f32 {
self.wavelength
}
#[setter]
fn set_particle_refr_index_re(&mut self, re: f32) {
if !self.particle_refr_index.is_empty() {
self.particle_refr_index[0].re = re;
}
}
#[getter]
fn get_particle_refr_index_re(&self) -> f32 {
if !self.particle_refr_index.is_empty() {
self.particle_refr_index[0].re
} else {
0.0
}
}
#[setter]
fn set_particle_refr_index_im(&mut self, im: f32) {
if !self.particle_refr_index.is_empty() {
self.particle_refr_index[0].im = im;
}
}
#[getter]
fn get_particle_refr_index_im(&self) -> f32 {
if !self.particle_refr_index.is_empty() {
self.particle_refr_index[0].im
} else {
0.0
}
}
#[setter]
fn set_medium_refr_index_re(&mut self, re: f32) {
self.medium_refr_index.re = re;
}
#[getter]
fn get_medium_refr_index_re(&self) -> f32 {
self.medium_refr_index.re
}
#[setter]
fn set_medium_refr_index_im(&mut self, im: f32) {
self.medium_refr_index.im = im;
}
#[getter]
fn get_medium_refr_index_im(&self) -> f32 {
self.medium_refr_index.im
}
#[setter]
fn set_beam_power_threshold(&mut self, threshold: f32) {
self.beam_power_threshold = threshold;
}
#[getter]
fn get_beam_power_threshold(&self) -> f32 {
self.beam_power_threshold
}
#[setter]
fn set_cutoff(&mut self, cutoff: f32) {
self.cutoff = cutoff;
}
#[getter]
fn get_cutoff(&self) -> f32 {
self.cutoff
}
#[setter]
fn set_max_rec(&mut self, max_rec: i32) {
self.max_rec = max_rec;
}
#[getter]
fn get_max_rec(&self) -> i32 {
self.max_rec
}
#[setter]
fn set_max_tir(&mut self, max_tir: i32) {
self.max_tir = max_tir;
}
#[getter]
fn get_max_tir(&self) -> i32 {
self.max_tir
}
#[setter]
fn set_zones(&mut self, zones: Vec<ZoneConfig>) {
self.zones = zones;
}
#[getter]
fn get_zones(&self) -> Vec<ZoneConfig> {
self.zones.clone()
}
#[setter]
fn set_geom_scale(&mut self, geom_scale: Option<Vec<f32>>) {
self.geom_scale = geom_scale;
}
#[getter]
fn get_geom_scale(&self) -> Option<Vec<f32>> {
self.geom_scale.clone()
}
#[setter]
fn set_seed(&mut self, seed: Option<u64>) {
self.seed = seed;
}
#[getter]
fn get_seed(&self) -> Option<u64> {
self.seed
}
#[setter]
fn set_distortion(&mut self, distortion: Option<f32>) {
self.distortion = distortion;
}
#[getter]
fn get_distortion(&self) -> Option<f32> {
self.distortion
}
#[setter]
fn set_fov_factor(&mut self, fov_factor: Option<f32>) {
self.fov_factor = fov_factor;
}
#[getter]
fn get_fov_factor(&self) -> Option<f32> {
self.fov_factor
}
#[setter]
fn set_quiet(&mut self, quiet: bool) {
self.quiet = quiet;
}
#[getter]
fn get_quiet(&self) -> bool {
self.quiet
}
}
impl Settings {
pub fn beam_area_threshold(&self) -> f32 {
self.wavelength * self.wavelength * self.beam_area_threshold_fac * self.scale.powi(2)
}
}