use std::ops::Add;
use std::ops::AddAssign;
use std::ops::Div;
use std::ops::Mul;
use std::ops::Sub;
use crate::bins::Scheme;
use crate::bins::SolidAngleBin;
use crate::convergence::Convergeable;
use crate::params::Params;
use crate::powers::Powers;
use crate::zones::{Zone, ZoneType, Zones};
use itertools::Itertools;
use pyo3::prelude::*;
#[cfg(feature = "stub-gen")]
use pyo3_stub_gen::derive::*;
use rand_distr::num_traits::Pow;
use super::component::GOComponent;
use super::scatt_result::{ScattResult1D, ScattResult2D};
#[cfg_attr(feature = "stub-gen", gen_stub_pyclass)]
#[pyclass(module = "goad._goad")]
#[derive(Debug, Clone)]
pub struct Results {
pub zones: Zones,
pub powers: Powers,
pub params: Params,
}
impl AddAssign for Results {
fn add_assign(&mut self, other: Self) {
*self = self.clone() + other;
}
}
impl Pow<f32> for Results {
type Output = Self;
fn pow(self, rhs: f32) -> Self {
Self {
zones: self.zones.pow(rhs),
powers: self.powers.pow(rhs),
params: self.params.pow(rhs),
}
}
}
impl Mul<f32> for Results {
type Output = Self;
fn mul(self, rhs: f32) -> Self {
Self {
zones: self.zones * rhs,
powers: self.powers * rhs,
params: self.params * rhs,
}
}
}
impl Mul for Results {
type Output = Self;
fn mul(self, other: Self) -> Self {
Self {
zones: self.zones * other.zones,
powers: self.powers * other.powers,
params: self.params * other.params,
}
}
}
impl Add for Results {
type Output = Self;
fn add(self, other: Self) -> Self {
Self {
zones: self.zones + other.zones,
powers: self.powers + other.powers,
params: self.params + other.params,
}
}
}
impl Sub for Results {
type Output = Self;
fn sub(self, other: Self) -> Self {
Self {
zones: self.zones - other.zones,
powers: self.powers - other.powers,
params: self.params - other.params,
}
}
}
impl Div<f32> for Results {
type Output = Self;
fn div(self, rhs: f32) -> Self {
Self {
zones: self.zones / rhs,
powers: self.powers / rhs,
params: self.params / rhs,
}
}
}
impl Div for Results {
type Output = Self;
fn div(self, other: Self) -> Self {
Self {
zones: self.zones / other.zones,
powers: self.powers.div_elem(&other.powers),
params: self.params.div_elem(&other.params),
}
}
}
impl Convergeable for Results {
fn zero_like(&self) -> Self {
Results::new_with_zones(self.zones.zero_like())
}
fn weighted_add(&self, other: &Self, w1: f32, w2: f32) -> Self {
Self {
zones: self.zones.weighted_add(&other.zones, w1, w2),
powers: self.powers.weighted_add(&other.powers, w1, w2),
params: self.params.weighted_add(&other.params, w1, w2),
}
}
fn mul_elem(&self, other: &Self) -> Self {
self.clone() * other.clone()
}
fn div_elem(&self, other: &Self) -> Self {
self.clone() / other.clone()
}
fn add_elem(&self, other: &Self) -> Self {
self.clone() + other.clone()
}
fn sub_elem(&self, other: &Self) -> Self {
self.clone() - other.clone()
}
fn scale(&self, scalar: f32) -> Self {
self.clone() * scalar
}
fn sqrt_elem(&self) -> Self {
self.clone().pow(0.5)
}
fn to_weighted(&self) -> Self {
Results::to_weighted(self)
}
fn weights(&self) -> Self {
Results::weights(self)
}
}
impl Results {
pub fn bins(&self) -> Vec<SolidAngleBin> {
self.zones
.full_zone()
.map(|z| z.bins.clone())
.unwrap_or_default()
}
pub fn new_empty(bins: &[SolidAngleBin]) -> Self {
let field_2d: Vec<ScattResult2D> =
bins.iter().map(|&bin| ScattResult2D::new(bin)).collect();
let full_zone = Zone::new(
ZoneType::Full,
bins.to_vec(),
field_2d,
None, );
Self {
zones: Zones::new(vec![full_zone]),
powers: Powers::new(),
params: Params::new(),
}
}
pub fn new_with_zones(zones: Zones) -> Self {
Self {
zones,
powers: Powers::new(),
params: Params::new(),
}
}
pub fn mueller_to_1d(&mut self) {
for zone in self.zones.iter_mut() {
match &zone.scheme {
Scheme::Custom { .. } => continue,
Scheme::Simple { .. } | Scheme::Interval { .. } => {}
}
let theta_groups: Vec<Vec<&ScattResult2D>> = zone
.field_2d
.iter()
.chunk_by(|result| result.bin.theta)
.into_iter()
.map(|(_, group)| group.collect())
.collect();
let field_1d: Vec<ScattResult1D> = theta_groups
.into_iter()
.map(|group| Self::integrate_over_phi(group))
.collect();
zone.field_1d = Some(field_1d);
}
}
fn integrate_over_phi(phi_group: Vec<&ScattResult2D>) -> ScattResult1D {
let theta_bin = phi_group[0].bin.theta;
let mut result = ScattResult1D::new(theta_bin);
for phi_result in phi_group {
let phi_width_rad = phi_result.bin.phi.width().to_radians();
result.mueller_total += phi_result.mueller_total * phi_width_rad;
result.mueller_beam += phi_result.mueller_beam * phi_width_rad;
result.mueller_ext += phi_result.mueller_ext * phi_width_rad;
}
result
}
pub fn compute_params(&mut self, wavelength: f32) {
let absorbed = self.powers.absorbed;
for zone in self.zones.iter_mut() {
zone.compute_params(wavelength, absorbed, &self.params);
self.params.merge(&zone.params);
}
}
pub fn to_weighted(&self) -> Self {
let mut result = self.clone();
result.params = self.params.to_weighted();
result
}
pub fn weights(&self) -> Self {
Self {
zones: self.zones.ones_like(),
powers: Powers::ones(),
params: self.params.weights(),
}
}
pub fn print(&self) {
println!("Powers: {:?}", self.powers);
for component in [GOComponent::Total, GOComponent::Beam, GOComponent::ExtDiff] {
let comp_str = match component {
GOComponent::Total => "Total",
GOComponent::Beam => "Beam",
GOComponent::ExtDiff => "ExtDiff",
};
if let Some(val) = self.params.asymmetry(&component) {
println!("{} Asymmetry: {}", comp_str, val);
}
if let Some(val) = self.params.scatt_cross(&component) {
println!("{} Scat Cross: {}", comp_str, val);
}
if let Some(val) = self.params.ext_cross(&component) {
println!("{} Ext Cross: {}", comp_str, val);
}
if let Some(val) = self.params.albedo(&component) {
println!("{} Albedo: {}", comp_str, val);
}
}
if let Some(val) = self.params.scatt_cross(&GOComponent::Beam) {
println!(
"Beam Scat Cross / Output power: {}",
val / self.powers.output
);
}
}
}