#[allow(unused_imports)]
use super::functions::*;
use super::functions::{
BOLTZMANN_K, SIGMA, blackbody_emissive_power, blackbody_spectral_intensity, wien_displacement,
};
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub struct SolarCell {
pub i_ph: f64,
pub i_0: f64,
pub n_ideal: f64,
pub temperature: f64,
pub r_series: f64,
}
impl SolarCell {
#[allow(dead_code)]
pub fn new(i_ph: f64, i_0: f64, n_ideal: f64, temperature: f64, r_series: f64) -> Self {
Self {
i_ph,
i_0,
n_ideal,
temperature,
r_series,
}
}
#[allow(dead_code)]
pub fn thermal_voltage(&self) -> f64 {
BOLTZMANN_K * self.temperature / 1.602e-19
}
#[allow(dead_code)]
pub fn short_circuit_current(&self) -> f64 {
self.i_ph - self.i_0 * (1.0 / (self.n_ideal * self.thermal_voltage())).exp()
}
#[allow(dead_code)]
pub fn current_at_voltage(&self, v: f64) -> f64 {
let v_t = self.thermal_voltage();
(self.i_ph - self.i_0 * ((v / (self.n_ideal * v_t)).exp() - 1.0)).max(0.0)
}
#[allow(dead_code)]
pub fn power_at_voltage(&self, v: f64) -> f64 {
v * self.current_at_voltage(v)
}
#[allow(dead_code)]
pub fn open_circuit_voltage(&self) -> f64 {
if self.i_0 < f64::EPSILON {
return 0.0;
}
self.n_ideal * self.thermal_voltage() * (self.i_ph / self.i_0 + 1.0).ln()
}
#[allow(dead_code)]
pub fn fill_factor(&self) -> f64 {
let v_t = self.thermal_voltage();
let v_oc = self.open_circuit_voltage();
let v_oc_norm = v_oc / (self.n_ideal * v_t);
if v_oc_norm < 1.0 {
return 0.0;
}
(v_oc_norm - (v_oc_norm + 0.72).ln()) / (v_oc_norm + 1.0)
}
#[allow(dead_code)]
pub fn efficiency(&self, irradiance: f64, area: f64) -> f64 {
let p_in = irradiance * area;
if p_in < f64::EPSILON {
return 0.0;
}
let i_sc = self.short_circuit_current();
let v_oc = self.open_circuit_voltage();
let ff = self.fill_factor();
ff * i_sc * v_oc / p_in
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct RadiationSurface {
pub area: f64,
pub emissivity: f64,
pub temperature: f64,
pub name: String,
}
impl RadiationSurface {
#[allow(dead_code)]
pub fn new(area: f64, emissivity: f64, temp_k: f64, name: &str) -> Self {
Self {
area,
emissivity,
temperature: temp_k,
name: name.to_string(),
}
}
#[allow(dead_code)]
pub fn radiosity(&self) -> f64 {
self.emissivity * SIGMA * self.temperature.powi(4)
}
#[allow(dead_code)]
pub fn surface_resistance(&self) -> f64 {
if (self.emissivity - 1.0).abs() < 1e-12 {
0.0
} else {
(1.0 - self.emissivity) / (self.emissivity * self.area)
}
}
}
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub struct NeutronModeration {
pub sigma_s: f64,
pub sigma_a: f64,
pub xi: f64,
}
impl NeutronModeration {
#[allow(dead_code)]
pub fn new(sigma_s: f64, sigma_a: f64, xi: f64) -> Self {
Self {
sigma_s,
sigma_a,
xi,
}
}
#[allow(dead_code)]
pub fn slowing_down_power(&self) -> f64 {
self.xi * self.sigma_s
}
#[allow(dead_code)]
pub fn moderation_ratio(&self) -> f64 {
if self.sigma_a < f64::EPSILON {
return f64::INFINITY;
}
self.xi * self.sigma_s / self.sigma_a
}
#[allow(dead_code)]
pub fn migration_length_sq(&self) -> f64 {
let d = 1.0 / (3.0 * self.sigma_s);
d / self.sigma_a
}
}
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub struct BlackbodySpectrum {
pub temp_k: f64,
}
impl BlackbodySpectrum {
#[allow(dead_code)]
pub fn new(temp_k: f64) -> Self {
Self { temp_k }
}
#[allow(dead_code)]
pub fn planck(&self, lambda_m: f64) -> f64 {
blackbody_spectral_intensity(lambda_m, self.temp_k)
}
#[allow(dead_code)]
pub fn total_power(&self) -> f64 {
blackbody_emissive_power(self.temp_k)
}
#[allow(dead_code)]
pub fn peak_wavelength(&self) -> f64 {
wien_displacement(self.temp_k)
}
#[allow(dead_code)]
pub fn band_fraction(&self, lambda_a: f64, lambda_b: f64, n_steps: usize) -> f64 {
let eb = self.total_power();
if eb < f64::EPSILON {
return 0.0;
}
let n = n_steps.max(2);
let dlam = (lambda_b - lambda_a) / (n as f64);
let mut sum = 0.0;
for i in 0..=n {
let lam = lambda_a + (i as f64) * dlam;
let w = if i == 0 || i == n { 0.5 } else { 1.0 };
sum += w * std::f64::consts::PI * self.planck(lam);
}
(sum * dlam / eb).clamp(0.0, 1.0)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct MonteCarloViewFactor {
pub n_rays: usize,
pub(super) seed: u64,
}
impl MonteCarloViewFactor {
pub fn new(n_rays: usize) -> Self {
Self {
n_rays,
seed: 12345678901,
}
}
fn rand_next(&mut self) -> f64 {
self.seed = self
.seed
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
(self.seed >> 33) as f64 / (u32::MAX as f64)
}
pub fn parallel_disks(&mut self, r1: f64, r2: f64, h: f64) -> f64 {
let mut hits = 0usize;
for _ in 0..self.n_rays {
let r_orig = r1 * self.rand_next().sqrt();
let phi_orig = 2.0 * std::f64::consts::PI * self.rand_next();
let ox = r_orig * phi_orig.cos();
let oy = r_orig * phi_orig.sin();
let theta = (self.rand_next()).sqrt().asin();
let phi_dir = 2.0 * std::f64::consts::PI * self.rand_next();
let dz = theta.cos();
let dx = theta.sin() * phi_dir.cos();
let dy = theta.sin() * phi_dir.sin();
if dz < 1e-12 {
continue;
}
let t = h / dz;
let ix = ox + dx * t;
let iy = oy + dy * t;
if ix * ix + iy * iy <= r2 * r2 {
hits += 1;
}
}
hits as f64 / self.n_rays as f64
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct RadiationNetwork {
pub n: usize,
pub temperatures: Vec<f64>,
pub emissivities: Vec<f64>,
pub areas: Vec<f64>,
pub view_factors: Vec<f64>,
}
impl RadiationNetwork {
pub fn new(
temperatures: Vec<f64>,
emissivities: Vec<f64>,
areas: Vec<f64>,
view_factors: Vec<f64>,
) -> Self {
let n = temperatures.len();
assert_eq!(emissivities.len(), n);
assert_eq!(areas.len(), n);
assert_eq!(view_factors.len(), n * n);
Self {
n,
temperatures,
emissivities,
areas,
view_factors,
}
}
pub fn f(&self, i: usize, j: usize) -> f64 {
self.view_factors[i * self.n + j]
}
pub fn net_heat_flow_simple(&self, i: usize) -> f64 {
let emit_i = self.emissivities[i] * SIGMA * self.temperatures[i].powi(4) * self.areas[i];
let absorbed: f64 = (0..self.n)
.map(|j| {
self.f(i, j)
* self.emissivities[j]
* SIGMA
* self.temperatures[j].powi(4)
* self.areas[i]
})
.sum();
emit_i - absorbed
}
pub fn emitted_power(&self, i: usize) -> f64 {
self.emissivities[i] * SIGMA * self.temperatures[i].powi(4) * self.areas[i]
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct SpectralEmissivityModel {
pub wavelengths: Vec<f64>,
pub emissivities: Vec<f64>,
}
impl SpectralEmissivityModel {
#[allow(dead_code)]
pub fn new(wavelengths: Vec<f64>, emissivities: Vec<f64>) -> Self {
assert_eq!(
wavelengths.len(),
emissivities.len(),
"wavelengths and emissivities must have the same length"
);
Self {
wavelengths,
emissivities,
}
}
#[allow(dead_code)]
pub fn emissivity_at(&self, lambda: f64) -> f64 {
let n = self.wavelengths.len();
if n == 0 {
return 0.0;
}
if n == 1 {
return self.emissivities[0];
}
if lambda <= self.wavelengths[0] {
return self.emissivities[0];
}
if lambda >= self.wavelengths[n - 1] {
return self.emissivities[n - 1];
}
for i in 0..n - 1 {
let w0 = self.wavelengths[i];
let w1 = self.wavelengths[i + 1];
if lambda >= w0 && lambda <= w1 {
let t = (lambda - w0) / (w1 - w0);
return self.emissivities[i] * (1.0 - t) + self.emissivities[i + 1] * t;
}
}
self.emissivities[n - 1]
}
#[allow(dead_code)]
pub fn effective_total_emissivity(&self, temp_k: f64, n_steps: usize) -> f64 {
let lambda_a = 100e-9_f64;
let lambda_b = 100e-6_f64;
let n = n_steps.max(2);
let dl = (lambda_b - lambda_a) / n as f64;
let e_b_total = blackbody_emissive_power(temp_k);
if e_b_total < f64::EPSILON {
return 0.0;
}
let mut sum = 0.0;
for i in 0..=n {
let lam = lambda_a + i as f64 * dl;
let w = if i == 0 || i == n { 0.5 } else { 1.0 };
let eps = self.emissivity_at(lam);
let e_b_lam = std::f64::consts::PI * blackbody_spectral_intensity(lam, temp_k);
sum += w * eps * e_b_lam;
}
(sum * dl / e_b_total).clamp(0.0, 1.0)
}
}
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub struct IrradiationDosimetry {
pub flux: f64,
pub time_s: f64,
pub kerma_cross_section: f64,
}
impl IrradiationDosimetry {
#[allow(dead_code)]
pub fn new(flux: f64, time_s: f64, kerma_cross_section: f64) -> Self {
Self {
flux,
time_s,
kerma_cross_section,
}
}
#[allow(dead_code)]
pub fn fluence(&self) -> f64 {
self.flux * self.time_s
}
#[allow(dead_code)]
pub fn absorbed_dose_gray(&self) -> f64 {
let e_transfer = 1.602e-13;
self.fluence() * self.kerma_cross_section * 1.0e4 * e_transfer
}
#[allow(dead_code)]
pub fn kerma_rate(&self, rho: f64) -> f64 {
let e_transfer = 1.602e-13;
self.flux * self.kerma_cross_section * 1.0e4 * e_transfer / rho
}
#[allow(dead_code)]
pub fn dose_equivalent_sievert(&self, quality_factor: f64) -> f64 {
self.absorbed_dose_gray() * quality_factor
}
#[allow(dead_code)]
pub fn rem_dose(&self, quality_factor: f64) -> f64 {
self.dose_equivalent_sievert(quality_factor) / 0.01
}
}
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub struct StefanBoltzmannIntegrator {
pub temp_k: f64,
}
impl StefanBoltzmannIntegrator {
#[allow(dead_code)]
pub fn new(temp_k: f64) -> Self {
Self { temp_k }
}
#[allow(dead_code)]
pub fn integrate_full_spectrum(&self, n_steps: usize) -> f64 {
let lambda_a = 100e-9_f64;
let lambda_b = 1e-3_f64;
self.integrate_range(lambda_a, lambda_b, n_steps)
}
#[allow(dead_code)]
pub fn integrate_range(&self, lambda_a: f64, lambda_b: f64, n_steps: usize) -> f64 {
let n = if n_steps.is_multiple_of(2) {
n_steps
} else {
n_steps + 1
};
let n = n.max(2);
let dl = (lambda_b - lambda_a) / n as f64;
let mut sum = 0.0;
for i in 0..=n {
let lam = lambda_a + i as f64 * dl;
let intensity = std::f64::consts::PI * blackbody_spectral_intensity(lam, self.temp_k);
let w = if i == 0 || i == n {
1.0
} else if i % 2 == 1 {
4.0
} else {
2.0
};
sum += w * intensity;
}
sum * dl / 3.0
}
#[allow(dead_code)]
pub fn band_fraction_in_range(&self, lambda_a: f64, lambda_b: f64, n_steps: usize) -> f64 {
let total = blackbody_emissive_power(self.temp_k);
if total < f64::EPSILON {
return 0.0;
}
let band = self.integrate_range(lambda_a, lambda_b, n_steps);
(band / total).clamp(0.0, 1.0)
}
#[allow(dead_code)]
pub fn weighted_emissivity(&self, emissivity_fn: impl Fn(f64) -> f64, n_steps: usize) -> f64 {
let total = blackbody_emissive_power(self.temp_k);
if total < f64::EPSILON {
return 0.0;
}
let lambda_a = 100e-9_f64;
let lambda_b = 1e-3_f64;
let n = n_steps.max(2);
let dl = (lambda_b - lambda_a) / n as f64;
let mut sum = 0.0;
for i in 0..=n {
let lam = lambda_a + i as f64 * dl;
let w = if i == 0 || i == n { 0.5 } else { 1.0 };
let eps = emissivity_fn(lam);
let e_b_lam = std::f64::consts::PI * blackbody_spectral_intensity(lam, self.temp_k);
sum += w * eps * e_b_lam;
}
(sum * dl / total).clamp(0.0, 1.0)
}
}
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub struct RadiativeProperties {
pub emissivity: f64,
pub absorptivity: f64,
pub transmissivity: f64,
pub reflectivity: f64,
}
impl RadiativeProperties {
#[allow(dead_code)]
pub fn new(emissivity: f64, transmissivity: f64) -> Self {
let absorptivity = emissivity;
let reflectivity = (1.0 - absorptivity - transmissivity).max(0.0);
Self {
emissivity,
absorptivity,
transmissivity,
reflectivity,
}
}
#[allow(dead_code)]
pub fn is_consistent(&self) -> bool {
let sum = self.absorptivity + self.transmissivity + self.reflectivity;
(sum - 1.0).abs() < 1.0e-9
}
#[allow(dead_code)]
pub fn emissive_power(&self, temp_k: f64) -> f64 {
self.emissivity * SIGMA * temp_k.powi(4)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ParticipatingMedia {
pub kappa: f64,
pub sigma_s: f64,
pub temperature: f64,
}
impl ParticipatingMedia {
pub fn new(kappa: f64, sigma_s: f64, temperature: f64) -> Self {
Self {
kappa,
sigma_s,
temperature,
}
}
pub fn extinction(&self) -> f64 {
self.kappa + self.sigma_s
}
pub fn albedo(&self) -> f64 {
let b = self.extinction();
if b < f64::EPSILON {
return 0.0;
}
self.sigma_s / b
}
pub fn optical_depth(&self, path_length: f64) -> f64 {
self.extinction() * path_length
}
pub fn transmittance(&self, path_length: f64) -> f64 {
(-self.optical_depth(path_length)).exp()
}
pub fn emission_optically_thin(&self, path_length: f64) -> f64 {
self.kappa * SIGMA * self.temperature.powi(4) * path_length
}
pub fn effective_emissivity(&self, path_length: f64) -> f64 {
1.0 - (-self.kappa * path_length).exp()
}
pub fn mean_beam_length_sphere(radius: f64) -> f64 {
0.65 * 2.0 * radius
}
pub fn mean_beam_length_cylinder(radius: f64) -> f64 {
0.95 * 2.0 * radius
}
}
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub struct RadiationDamage {
pub fluence: f64,
pub displacement_cross_section: f64,
pub atomic_density: f64,
}
impl RadiationDamage {
#[allow(dead_code)]
pub fn new(fluence: f64, sigma_d: f64, n_atoms: f64) -> Self {
Self {
fluence,
displacement_cross_section: sigma_d,
atomic_density: n_atoms,
}
}
#[allow(dead_code)]
pub fn dpa(&self) -> f64 {
self.fluence * self.displacement_cross_section
}
#[allow(dead_code)]
pub fn displaced_fraction(&self) -> f64 {
self.dpa().min(1.0)
}
#[allow(dead_code)]
pub fn swelling_fraction(&self) -> f64 {
let dpa = self.dpa();
1.0e-3 * dpa * dpa
}
}
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub struct GrayBodyModel {
pub eps_ref: f64,
pub deps_dt: f64,
pub temp_ref: f64,
}
impl GrayBodyModel {
#[allow(dead_code)]
pub fn new(eps_ref: f64, deps_dt: f64, temp_ref: f64) -> Self {
Self {
eps_ref,
deps_dt,
temp_ref,
}
}
#[allow(dead_code)]
pub fn emissivity(&self, temp_k: f64) -> f64 {
(self.eps_ref + self.deps_dt * (temp_k - self.temp_ref)).clamp(0.0, 1.0)
}
#[allow(dead_code)]
pub fn emissive_power(&self, temp_k: f64) -> f64 {
self.emissivity(temp_k) * SIGMA * temp_k.powi(4)
}
#[allow(dead_code)]
pub fn effective_blackbody_temperature(&self, temp_k: f64) -> f64 {
let eps = self.emissivity(temp_k);
temp_k * eps.powf(0.25)
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct MCRadiationTransport {
pub n_photons: usize,
pub(super) seed: u64,
}
impl MCRadiationTransport {
#[allow(dead_code)]
pub fn new(n_photons: usize, seed: u64) -> Self {
Self { n_photons, seed }
}
fn rand_next(&mut self) -> f64 {
self.seed = self
.seed
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
(self.seed >> 33) as f64 / (u32::MAX as f64)
}
#[allow(dead_code)]
pub fn slab_transmittance(&mut self, kappa: f64, thickness: f64) -> f64 {
self.slab_transmittance_full(kappa, thickness, 0.0)
}
#[allow(dead_code)]
pub fn slab_transmittance_full(&mut self, kappa: f64, thickness: f64, albedo: f64) -> f64 {
if self.n_photons == 0 {
return 0.0;
}
let beta = kappa / (1.0 - albedo).max(1e-15);
let mut transmitted = 0usize;
for _ in 0..self.n_photons {
let mut x = 0.0_f64;
let mut weight = 1.0_f64;
let mut alive = true;
while alive && x < thickness {
let xi = self.rand_next().max(1e-15);
let s = -xi.ln() / beta;
x += s;
if x >= thickness {
transmitted += 1;
break;
}
if albedo < f64::EPSILON {
alive = false;
} else {
weight *= albedo;
if weight < 0.001 {
let rr = self.rand_next();
if rr < 0.1 {
weight /= 0.1;
} else {
alive = false;
}
}
let cos_theta = 2.0 * self.rand_next() - 1.0;
if cos_theta < 0.0 {
alive = false;
}
}
}
}
transmitted as f64 / self.n_photons as f64
}
#[allow(dead_code)]
pub fn estimate_mean_free_path(&mut self, beta: f64) -> f64 {
let mut total_path = 0.0;
let n = self.n_photons.max(1);
for _ in 0..n {
let xi = self.rand_next().max(1e-15);
total_path += -xi.ln() / beta;
}
total_path / n as f64
}
}