use std::collections::HashMap;
use std::sync::Arc;
use rand_v10::RngExt;
use rand_v10::rng;
use serde::Deserialize;
use serde::Serialize;
use crate::numerical::elementary::eval_expr;
use crate::numerical::matrix::Matrix;
use crate::numerical::ode::solve_ode_system_rk4;
use crate::symbolic::core::Expr;
pub const SPEED_OF_LIGHT: f64 = 299_792_458.0;
pub const PLANCK_CONSTANT: f64 = 6.626_070_15e-34;
pub const HBAR: f64 = 1.054_571_817e-34;
pub const ELEMENTARY_CHARGE: f64 = 1.602_176_634e-19;
pub const ELECTRON_MASS: f64 = 9.109_383_56e-31;
pub const PROTON_MASS: f64 = 1.672_621_898e-27;
pub const NEUTRON_MASS: f64 = 1.674_927_351e-27;
pub const GRAVITATIONAL_CONSTANT: f64 = 6.674_30e-11;
pub const AVOGADRO_NUMBER: f64 = 6.022_140_76e23;
pub const BOLTZMANN_CONSTANT: f64 = 1.380_649e-23;
pub const GAS_CONSTANT: f64 = 8.314_462_618;
pub const STEFAN_BOLTZMANN: f64 = 5.670_374_419e-8;
pub const VACUUM_PERMITTIVITY: f64 = 8.854_187_817e-12;
pub const VACUUM_PERMEABILITY: f64 = 1.256_637_061e-6;
pub const COULOMB_CONSTANT: f64 = 8.987_551_787e9;
pub const STANDARD_GRAVITY: f64 = 9.806_65;
pub const ATOMIC_MASS_UNIT: f64 = 1.660_539_067e-27;
pub const BOHR_RADIUS: f64 = 5.291_772_109e-11;
pub const FINE_STRUCTURE_CONSTANT: f64 = 7.297_352_566e-3;
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Particle3D {
pub mass: f64,
pub x: f64,
pub y: f64,
pub z: f64,
pub vx: f64,
pub vy: f64,
pub vz: f64,
}
impl Particle3D {
#[must_use]
pub const fn new(
mass: f64,
x: f64,
y: f64,
z: f64,
vx: f64,
vy: f64,
vz: f64,
) -> Self {
Self {
mass,
x,
y,
z,
vx,
vy,
vz,
}
}
#[must_use]
pub fn kinetic_energy(&self) -> f64 {
0.5 * self.mass
* self
.vz
.mul_add(self.vz, self.vx.mul_add(self.vx, self.vy * self.vy))
}
#[must_use]
pub fn momentum(&self) -> f64 {
self.mass
* self
.vz
.mul_add(self.vz, self.vx.mul_add(self.vx, self.vy * self.vy))
.sqrt()
}
}
pub fn simulate_particle_motion(
force_exprs: (&Expr, &Expr, &Expr),
mass: f64,
initial_pos: (f64, f64, f64),
initial_vel: (f64, f64, f64),
t_range: (f64, f64),
num_steps: usize,
) -> Result<Vec<Vec<f64>>, String> {
let (fx_expr, fy_expr, fz_expr) = force_exprs;
let m_expr = Expr::Constant(mass);
let ode_funcs: Vec<Expr> = vec![
Expr::Variable("y3".to_string()),
Expr::Variable("y4".to_string()),
Expr::Variable("y5".to_string()),
Expr::Div(Arc::new(fx_expr.clone()), Arc::new(m_expr.clone())),
Expr::Div(Arc::new(fy_expr.clone()), Arc::new(m_expr.clone())),
Expr::Div(Arc::new(fz_expr.clone()), Arc::new(m_expr)),
];
let y0 = vec![
initial_pos.0,
initial_pos.1,
initial_pos.2,
initial_vel.0,
initial_vel.1,
initial_vel.2,
];
solve_ode_system_rk4(&ode_funcs, &y0, t_range, num_steps)
}
#[allow(clippy::needless_range_loop)]
#[must_use]
pub fn simulate_ising_model(
size: usize,
temperature: f64,
steps: usize,
) -> Vec<Vec<i8>> {
let mut rng = rng();
let mut lattice = vec![vec![0i8; size]; size];
for i in 0..steps {
for j in 0..size {
lattice[i][j] = if rng.random::<bool>() {
1
} else {
-1
};
}
}
for _ in 0..steps {
let i = rng.random_range(0..size);
let j = rng.random_range(0..size);
let top = lattice[(i + size - 1) % size][j];
let bottom = lattice[(i + 1) % size][j];
let left = lattice[i][(j + size - 1) % size];
let right = lattice[i][(j + 1) % size];
let neighbor_sum = top + bottom + left + right;
let delta_e = 2.0 * f64::from(lattice[i][j]) * f64::from(neighbor_sum);
if delta_e < 0.0 || rng.random::<f64>() < (-delta_e / temperature).exp() {
lattice[i][j] *= -1;
}
}
lattice
}
pub fn solve_1d_schrodinger(
potential_expr: &Expr,
var: &str,
range: (f64, f64),
num_points: usize,
) -> Result<(Vec<f64>, Matrix<f64>), String> {
let (x_min, x_max) = range;
if num_points < 3 {
return Err("num_points must \
be >= 3"
.to_string());
}
let dx = (x_max - x_min) / (num_points as f64 - 1.0);
let points: Vec<f64> = (0..num_points)
.map(|i| (i as f64).mul_add(dx, x_min))
.collect();
let mut potential_values = Vec::with_capacity(num_points);
for &x in &points {
let mut vars = HashMap::new();
vars.insert(var.to_string(), x);
potential_values.push(eval_expr(potential_expr, &vars)?);
}
let n = num_points;
let mut h_data = vec![0.0; n * n];
let factor = -0.5 / (dx * dx);
for i in 0..n {
h_data[i * n + i] = (-2.0f64).mul_add(factor, potential_values[i]);
if i > 0 {
h_data[i * n + i - 1] = factor;
}
if i + 1 < n {
h_data[i * n + i + 1] = factor;
}
}
let hamiltonian = Matrix::new(n, n, h_data);
hamiltonian.jacobi_eigen_decomposition(2000, 1e-12)
}
pub fn solve_2d_schrodinger(
potential_expr: &Expr,
var_x: &str,
var_y: &str,
ranges: (f64, f64, f64, f64),
grid: (usize, usize),
) -> Result<(Vec<f64>, Matrix<f64>), String> {
let (x_min, x_max, y_min, y_max) = ranges;
let (nx, ny) = grid;
if nx < 3 || ny < 3 {
return Err("grid dimensions \
must be at \
least 3 in each \
direction"
.to_string());
}
let dx = (x_max - x_min) / (nx as f64 - 1.0);
let dy = (y_max - y_min) / (ny as f64 - 1.0);
let n = nx * ny;
let mut potential = vec![0.0_f64; n];
for ix in 0..nx {
for iy in 0..ny {
let x = (ix as f64).mul_add(dx, x_min);
let y = (iy as f64).mul_add(dy, y_min);
let mut vars = HashMap::new();
vars.insert(var_x.to_string(), x);
vars.insert(var_y.to_string(), y);
potential[ix * ny + iy] = eval_expr(potential_expr, &vars)?;
}
}
let mut h_data = vec![0.0_f64; n * n];
let fx = -0.5 / (dx * dx);
let fy = -0.5 / (dy * dy);
let large = 1e12;
for ix in 0..nx {
for iy in 0..ny {
let idx = ix * ny + iy;
if ix == 0 || ix == nx - 1 || iy == 0 || iy == ny - 1 {
h_data[idx * n + idx] = large + potential[idx];
continue;
}
h_data[idx * n + idx] = (-2.0f64).mul_add(fx + fy, potential[idx]);
let idx_left = (ix - 1) * ny + iy;
let idx_right = (ix + 1) * ny + iy;
h_data[idx * n + idx_left] = fx;
h_data[idx * n + idx_right] = fx;
let idx_down = ix * ny + (iy - 1);
let idx_up = ix * ny + (iy + 1);
h_data[idx * n + idx_down] = fy;
h_data[idx * n + idx_up] = fy;
}
}
let hamiltonian = Matrix::new(n, n, h_data);
hamiltonian.jacobi_eigen_decomposition(5000, 1e-10)
}
pub fn solve_3d_schrodinger(
potential_expr: &Expr,
var_x: &str,
var_y: &str,
var_z: &str,
ranges: (f64, f64, f64, f64, f64, f64),
grid: (usize, usize, usize),
) -> Result<(Vec<f64>, Matrix<f64>), String> {
let (x_min, x_max, y_min, y_max, z_min, z_max) = ranges;
let (nx, ny, nz) = grid;
if nx < 3 || ny < 3 || nz < 3 {
return Err("grid dimensions \
must be at \
least 3 in each \
direction"
.to_string());
}
let dx = (x_max - x_min) / (nx as f64 - 1.0);
let dy = (y_max - y_min) / (ny as f64 - 1.0);
let dz = (z_max - z_min) / (nz as f64 - 1.0);
let n = nx * ny * nz;
if n > 25000 {
return Err(format!(
"Grid too large (nx*ny*nz \
= {n}). Dense 3D solver \
will be extremely slow \
and memory-heavy. \
Consider smaller grid or \
sparse/iterative methods."
));
}
let mut potential = vec![0.0_f64; n];
for ix in 0..nx {
for iy in 0..ny {
for iz in 0..nz {
let x = (ix as f64).mul_add(dx, x_min);
let y = (iy as f64).mul_add(dy, y_min);
let z = (iz as f64).mul_add(dz, z_min);
let mut vars = HashMap::new();
vars.insert(var_x.to_string(), x);
vars.insert(var_y.to_string(), y);
vars.insert(var_z.to_string(), z);
potential[(ix * ny + iy) * nz + iz] = eval_expr(potential_expr, &vars)?;
}
}
}
let mut h_data = vec![0.0_f64; n * n];
let fx = -0.5 / (dx * dx);
let fy = -0.5 / (dy * dy);
let fz = -0.5 / (dz * dz);
let large = 1e12;
for ix in 0..nx {
for iy in 0..ny {
for iz in 0..nz {
let idx = (ix * ny + iy) * nz + iz;
if ix == 0 || ix == nx - 1 || iy == 0 || iy == ny - 1 || iz == 0 || iz == nz - 1 {
h_data[idx * n + idx] = large + potential[idx];
continue;
}
h_data[idx * n + idx] = (-2.0f64).mul_add(fx + fy + fz, potential[idx]);
let idx_xm = ((ix - 1) * ny + iy) * nz + iz;
let idx_xp = ((ix + 1) * ny + iy) * nz + iz;
let idx_ym = (ix * ny + (iy - 1)) * nz + iz;
let idx_yp = (ix * ny + (iy + 1)) * nz + iz;
let idx_zm = (ix * ny + iy) * nz + (iz - 1);
let idx_zp = (ix * ny + iy) * nz + (iz + 1);
h_data[idx * n + idx_xm] = fx;
h_data[idx * n + idx_xp] = fx;
h_data[idx * n + idx_ym] = fy;
h_data[idx * n + idx_yp] = fy;
h_data[idx * n + idx_zm] = fz;
h_data[idx * n + idx_zp] = fz;
}
}
}
let hamiltonian = Matrix::new(n, n, h_data);
hamiltonian.jacobi_eigen_decomposition(5000, 1e-10)
}
#[allow(clippy::suspicious_operation_groupings)]
pub fn solve_heat_equation_1d_crank_nicolson(
init_func: &dyn Fn(f64) -> f64,
alpha: f64,
range: (f64, f64),
nx: usize,
t_range: (f64, f64),
nt: usize,
) -> Result<Vec<Vec<f64>>, String> {
let (a, b) = range;
if nx < 3 || nt < 1 {
return Err("nx must be >=3 \
and nt >= 1"
.to_string());
}
let dx = (b - a) / (nx as f64 - 1.0);
let dt = (t_range.1 - t_range.0) / (nt as f64);
let r = alpha * dt / (dx * dx);
let interior = nx - 2;
let a_diag = vec![1.0 + r; interior];
let a_lower = vec![-r / 2.0; interior - 1];
let a_upper = vec![-r / 2.0; interior - 1];
let b_diag = vec![1.0 - r; interior];
let b_lower = vec![r / 2.0; interior - 1];
let b_upper = vec![r / 2.0; interior - 1];
let mut u0 = vec![0.0_f64; nx];
for (i, var) in u0.iter_mut().enumerate().take(nx) {
let x = (i as f64).mul_add(dx, a);
*var = init_func(x);
}
u0[0] = 0.0;
u0[nx - 1] = 0.0;
let mut results: Vec<Vec<f64>> = Vec::with_capacity(nt + 1);
results.push(u0.clone());
let solve_tridiag =
|a_l: Vec<f64>, mut a_d: Vec<f64>, a_u: Vec<f64>, mut d: Vec<f64>| -> Vec<f64> {
let n = a_d.len();
for i in 1..n {
let m = a_l[i - 1] / a_d[i - 1];
a_d[i] -= m * a_u[i - 1];
d[i] -= m * d[i - 1];
}
let mut x = vec![0.0_f64; n];
x[n - 1] = d[n - 1] / a_d[n - 1];
for i in (0..n - 1).rev() {
x[i] = a_u[i].mul_add(-x[i + 1], d[i]) / a_d[i];
}
x
};
for _step in 0..nt {
let mut d = vec![0.0_f64; interior];
for i in 0..interior {
let global_i = i + 1;
let mut val = b_diag[i] * u0[global_i];
if i > 0 {
val += b_lower[i - 1] * u0[global_i - 1];
}
if i + 1 < interior {
val += b_upper[i] * u0[global_i + 1];
}
d[i] = val;
}
let u_interior = solve_tridiag(a_lower.clone(), a_diag.clone(), a_upper.clone(), d);
let mut u_new = vec![0.0_f64; nx];
u_new[0] = 0.0;
u_new[1..=interior].copy_from_slice(&u_interior[..interior]);
u_new[nx - 1] = 0.0;
results.push(u_new.clone());
u0 = u_new;
}
Ok(results)
}
pub fn solve_wave_equation_1d(
initial_u: &[f64],
initial_ut: &[f64],
c: f64,
dx: f64,
dt: f64,
num_steps: usize,
) -> Result<Vec<Vec<f64>>, String> {
let n = initial_u.len();
if initial_ut.len() != n {
return Err("initial_ut \
length mismatch"
.to_string());
}
if n < 3 {
return Err("need at least 3 \
grid points"
.to_string());
}
let cfl = c * dt / dx;
if cfl.abs() > 1.0 {
return Err(format!(
"CFL violation: c*dt/dx = \
{cfl} > 1. Reduce dt or \
increase dx."
));
}
let mut u_prev = initial_u.to_owned();
let mut u_curr = vec![0.0; n];
for i in 1..(n - 1) {
let u_xx = (2.0f64.mul_add(-u_prev[i], u_prev[i - 1]) + u_prev[i + 1]) / (dx * dx);
u_curr[i] = (0.5 * (c * c) * (dt * dt)).mul_add(u_xx, dt.mul_add(initial_ut[i], u_prev[i]));
}
u_curr[0] = 0.0;
u_curr[n - 1] = 0.0;
let mut snapshots: Vec<Vec<f64>> = Vec::with_capacity(num_steps + 1);
snapshots.push(u_prev.clone());
snapshots.push(u_curr.clone());
for _step in 2..=num_steps {
let mut u_next = vec![0.0; n];
for i in 1..(n - 1) {
let u_xx = (2.0f64.mul_add(-u_curr[i], u_curr[i - 1]) + u_curr[i + 1]) / (dx * dx);
u_next[i] = ((c * c) * (dt * dt)).mul_add(u_xx, 2.0f64.mul_add(u_curr[i], -u_prev[i]));
}
u_next[0] = 0.0;
u_next[n - 1] = 0.0;
snapshots.push(u_next.clone());
u_prev = u_curr;
u_curr = u_next;
}
Ok(snapshots)
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct ProjectileParams {
pub v0: f64,
pub angle: f64,
pub mass: f64,
pub drag_coeff: f64,
pub area: f64,
pub air_density: f64,
pub dt: f64,
pub max_time: f64,
}
#[must_use]
pub fn projectile_motion_with_drag(params: ProjectileParams) -> Vec<(f64, f64, f64, f64, f64)> {
let ProjectileParams {
v0,
angle,
mass,
drag_coeff,
area,
air_density,
dt,
max_time,
} = params;
let mut results = Vec::new();
let mut t = 0.0;
let mut x = 0.0;
let mut y = 0.0;
let mut vx = v0 * angle.cos();
let mut vy = v0 * angle.sin();
let k = 0.5 * drag_coeff * air_density * area / mass;
while t <= max_time && y >= 0.0 {
results.push((t, x, y, vx, vy));
let v = vx.hypot(vy);
if v > 1e-10 {
let ax = -k * v * vx;
let ay = (k * v).mul_add(-vy, -STANDARD_GRAVITY);
vx += ax * dt;
vy += ay * dt;
} else {
vy -= STANDARD_GRAVITY * dt;
}
x += vx * dt;
y += vy * dt;
t += dt;
}
results
}
#[must_use]
pub fn simple_harmonic_oscillator(
amplitude: f64,
omega: f64,
phase: f64,
time: f64,
) -> f64 {
amplitude * omega.mul_add(time, phase).cos()
}
#[must_use]
pub fn damped_harmonic_oscillator(
amplitude: f64,
omega0: f64,
gamma: f64,
phase: f64,
time: f64,
) -> f64 {
let omega_sq = omega0.mul_add(omega0, -(gamma * gamma));
if omega_sq > 0.0 {
let omega_prime = omega_sq.sqrt();
amplitude * (-gamma * time).exp() * omega_prime.mul_add(time, phase).cos()
} else if omega_sq < 0.0 {
let beta = (-omega_sq).sqrt();
amplitude * (-gamma * time).exp() * ((-beta * time).exp() + (beta * time).exp()) / 2.0
} else {
amplitude * gamma.mul_add(time, 1.0) * (-gamma * time).exp()
}
}
#[must_use]
pub fn simulate_n_body(
mut particles: Vec<Particle3D>,
dt: f64,
num_steps: usize,
g: f64,
) -> Vec<Vec<Particle3D>> {
let n = particles.len();
let mut snapshots = Vec::with_capacity(num_steps);
snapshots.push(particles.clone());
for _ in 0..num_steps {
let mut ax = vec![0.0; n];
let mut ay = vec![0.0; n];
let mut az = vec![0.0; n];
for i in 0..n {
for j in 0..n {
if i != j {
let dx = particles[j].x - particles[i].x;
let dy = particles[j].y - particles[i].y;
let dz = particles[j].z - particles[i].z;
let r_sq = dz.mul_add(dz, dx.mul_add(dx, dy * dy)) + 1e-10; let r = r_sq.sqrt();
let f = g * particles[j].mass / (r_sq * r);
ax[i] += f * dx;
ay[i] += f * dy;
az[i] += f * dz;
}
}
}
for i in 0..n {
particles[i].vx += ax[i] * dt;
particles[i].vy += ay[i] * dt;
particles[i].vz += az[i] * dt;
particles[i].x += particles[i].vx * dt;
particles[i].y += particles[i].vy * dt;
particles[i].z += particles[i].vz * dt;
}
snapshots.push(particles.clone());
}
snapshots
}
#[must_use]
pub fn gravitational_potential_energy(
particles: &[Particle3D],
g: f64,
) -> f64 {
let n = particles.len();
let mut energy = 0.0;
for i in 0..n {
for j in (i + 1)..n {
let dx = particles[j].x - particles[i].x;
let dy = particles[j].y - particles[i].y;
let dz = particles[j].z - particles[i].z;
let r = dz.mul_add(dz, dx.mul_add(dx, dy * dy)).sqrt();
if r > 1e-10 {
energy -= g * particles[i].mass * particles[j].mass / r;
}
}
}
energy
}
#[must_use]
pub fn total_kinetic_energy(particles: &[Particle3D]) -> f64 {
particles.iter().map(Particle3D::kinetic_energy).sum()
}
#[must_use]
pub fn coulomb_force(
q1: f64,
q2: f64,
r: f64,
) -> f64 {
if r.abs() < 1e-15 {
return f64::INFINITY;
}
COULOMB_CONSTANT * q1 * q2 / (r * r)
}
#[must_use]
pub fn electric_field_point_charge(
q: f64,
r: f64,
) -> f64 {
if r.abs() < 1e-15 {
return f64::INFINITY;
}
COULOMB_CONSTANT * q.abs() / (r * r)
}
#[must_use]
pub fn electric_potential_point_charge(
q: f64,
r: f64,
) -> f64 {
if r.abs() < 1e-15 {
return f64::INFINITY * q.signum();
}
COULOMB_CONSTANT * q / r
}
#[must_use]
pub fn magnetic_field_infinite_wire(
current: f64,
r: f64,
) -> f64 {
if r.abs() < 1e-15 {
return f64::INFINITY;
}
VACUUM_PERMEABILITY * current.abs() / (2.0 * std::f64::consts::PI * r)
}
#[must_use]
pub fn lorentz_force(
charge: f64,
velocity: f64,
e_field: f64,
b_field: f64,
) -> f64 {
charge.abs() * velocity.mul_add(b_field, e_field)
}
#[must_use]
pub fn cyclotron_radius(
mass: f64,
velocity: f64,
charge: f64,
b_field: f64,
) -> f64 {
if charge.abs() < 1e-30 || b_field.abs() < 1e-30 {
return f64::INFINITY;
}
mass * velocity / (charge.abs() * b_field)
}
#[must_use]
pub fn ideal_gas_pressure(
n: f64,
t: f64,
v: f64,
) -> f64 {
if v.abs() < 1e-30 {
return f64::INFINITY;
}
n * GAS_CONSTANT * t / v
}
#[must_use]
pub fn ideal_gas_volume(
n: f64,
t: f64,
p: f64,
) -> f64 {
if p.abs() < 1e-30 {
return f64::INFINITY;
}
n * GAS_CONSTANT * t / p
}
#[must_use]
pub fn ideal_gas_temperature(
p: f64,
v: f64,
n: f64,
) -> f64 {
if n.abs() < 1e-30 {
return f64::INFINITY;
}
p * v / (n * GAS_CONSTANT)
}
#[must_use]
pub fn maxwell_boltzmann_speed_distribution(
v: f64,
mass: f64,
temperature: f64,
) -> f64 {
let kt = BOLTZMANN_CONSTANT * temperature;
if kt < 1e-30 {
return 0.0;
}
let a = mass / (2.0 * std::f64::consts::PI * kt);
4.0 * std::f64::consts::PI * a.powf(1.5) * v * v * (-mass * v * v / (2.0 * kt)).exp()
}
#[must_use]
pub fn maxwell_boltzmann_mean_speed(
mass: f64,
temperature: f64,
) -> f64 {
(8.0 * BOLTZMANN_CONSTANT * temperature / (std::f64::consts::PI * mass)).sqrt()
}
#[must_use]
pub fn maxwell_boltzmann_rms_speed(
mass: f64,
temperature: f64,
) -> f64 {
(3.0 * BOLTZMANN_CONSTANT * temperature / mass).sqrt()
}
#[must_use]
pub fn blackbody_power(
area: f64,
temperature: f64,
) -> f64 {
STEFAN_BOLTZMANN * area * temperature.powi(4)
}
#[must_use]
pub fn wien_displacement_wavelength(temperature: f64) -> f64 {
const WIEN_CONSTANT: f64 = 2.897_771_955e-3;
if temperature < 1e-10 {
return f64::INFINITY;
}
WIEN_CONSTANT / temperature
}
#[must_use]
pub fn lorentz_factor(velocity: f64) -> f64 {
let beta = velocity / SPEED_OF_LIGHT;
if beta.abs() >= 1.0 {
return f64::INFINITY;
}
1.0 / beta.mul_add(-beta, 1.0).sqrt()
}
#[must_use]
pub fn time_dilation(
proper_time: f64,
velocity: f64,
) -> f64 {
proper_time * lorentz_factor(velocity)
}
#[must_use]
pub fn length_contraction(
proper_length: f64,
velocity: f64,
) -> f64 {
proper_length / lorentz_factor(velocity)
}
#[must_use]
pub fn relativistic_momentum(
mass: f64,
velocity: f64,
) -> f64 {
lorentz_factor(velocity) * mass * velocity
}
#[must_use]
pub fn relativistic_kinetic_energy(
mass: f64,
velocity: f64,
) -> f64 {
(lorentz_factor(velocity) - 1.0) * mass * SPEED_OF_LIGHT * SPEED_OF_LIGHT
}
#[must_use]
pub fn relativistic_total_energy(
mass: f64,
velocity: f64,
) -> f64 {
lorentz_factor(velocity) * mass * SPEED_OF_LIGHT * SPEED_OF_LIGHT
}
#[must_use]
pub fn mass_energy(mass: f64) -> f64 {
mass * SPEED_OF_LIGHT * SPEED_OF_LIGHT
}
#[must_use]
#[allow(clippy::suspicious_operation_groupings)]
pub fn relativistic_velocity_addition(
v: f64,
w: f64,
) -> f64 {
(v + w) / (1.0 + v * w / (SPEED_OF_LIGHT * SPEED_OF_LIGHT))
}
#[must_use]
pub fn quantum_harmonic_oscillator_energy(
n: u64,
omega: f64,
) -> f64 {
HBAR * omega * (n as f64 + 0.5)
}
#[must_use]
pub fn hydrogen_energy_level(n: u64) -> f64 {
const RYDBERG_ENERGY_J: f64 = 2.179_872_361e-18;
if n == 0 {
return f64::NEG_INFINITY;
}
-RYDBERG_ENERGY_J / (n as f64 * n as f64)
}
#[must_use]
pub fn de_broglie_wavelength(momentum: f64) -> f64 {
if momentum.abs() < 1e-40 {
return f64::INFINITY;
}
PLANCK_CONSTANT / momentum
}
#[must_use]
pub fn heisenberg_position_uncertainty(momentum_uncertainty: f64) -> f64 {
if momentum_uncertainty.abs() < 1e-40 {
return 0.0;
}
HBAR / (2.0 * momentum_uncertainty)
}
#[must_use]
pub fn photon_energy(wavelength: f64) -> f64 {
if wavelength.abs() < 1e-20 {
return f64::INFINITY;
}
PLANCK_CONSTANT * SPEED_OF_LIGHT / wavelength
}
#[must_use]
pub fn photon_wavelength(energy: f64) -> f64 {
if energy.abs() < 1e-40 {
return f64::INFINITY;
}
PLANCK_CONSTANT * SPEED_OF_LIGHT / energy
}
#[must_use]
pub fn compton_wavelength(mass: f64) -> f64 {
if mass.abs() < 1e-40 {
return f64::INFINITY;
}
PLANCK_CONSTANT / (mass * SPEED_OF_LIGHT)
}