use crate::math::Vec3;
pub fn displacement(initial_velocity: f64, acceleration: f64, time: f64) -> f64 {
initial_velocity * time + 0.5 * acceleration * time * time
}
pub fn velocity(initial_velocity: f64, acceleration: f64, time: f64) -> f64 {
initial_velocity + acceleration * time
}
pub fn velocity_squared(initial_velocity: f64, acceleration: f64, displacement: f64) -> f64 {
initial_velocity * initial_velocity + 2.0 * acceleration * displacement
}
pub fn position_3d(pos: Vec3, vel: Vec3, acc: Vec3, t: f64) -> Vec3 {
pos + vel * t + acc * (0.5 * t * t)
}
pub fn velocity_3d(vel: Vec3, acc: Vec3, t: f64) -> Vec3 {
vel + acc * t
}
pub fn projectile_range(speed: f64, angle_rad: f64, g: f64) -> f64 {
assert!(g > 0.0, "gravitational acceleration must be positive");
speed * speed * (2.0 * angle_rad).sin() / g
}
pub fn projectile_max_height(speed: f64, angle_rad: f64, g: f64) -> f64 {
assert!(g > 0.0, "gravitational acceleration must be positive");
let sin_a = angle_rad.sin();
speed * speed * sin_a * sin_a / (2.0 * g)
}
pub fn projectile_time_of_flight(speed: f64, angle_rad: f64, g: f64) -> f64 {
assert!(g > 0.0, "gravitational acceleration must be positive");
2.0 * speed * angle_rad.sin() / g
}
pub fn force(mass: f64, acceleration: f64) -> f64 {
mass * acceleration
}
pub fn force_3d(mass: f64, acceleration: Vec3) -> Vec3 {
acceleration * mass
}
pub fn acceleration(force: f64, mass: f64) -> f64 {
assert!(mass > 0.0, "mass must be positive");
force / mass
}
pub fn weight(mass: f64, g: f64) -> f64 {
mass * g
}
pub fn momentum(mass: f64, velocity: f64) -> f64 {
mass * velocity
}
pub fn momentum_3d(mass: f64, velocity: Vec3) -> Vec3 {
velocity * mass
}
pub fn impulse(force: f64, delta_t: f64) -> f64 {
force * delta_t
}
pub fn elastic_collision_1d(m1: f64, v1: f64, m2: f64, v2: f64) -> (f64, f64) {
assert!(m1 > 0.0, "m1 must be positive");
assert!(m2 > 0.0, "m2 must be positive");
let total = m1 + m2;
let v1f = ((m1 - m2) * v1 + 2.0 * m2 * v2) / total;
let v2f = ((m2 - m1) * v2 + 2.0 * m1 * v1) / total;
(v1f, v2f)
}
pub fn inelastic_collision_1d(m1: f64, v1: f64, m2: f64, v2: f64) -> f64 {
assert!(m1 > 0.0, "m1 must be positive");
assert!(m2 > 0.0, "m2 must be positive");
(m1 * v1 + m2 * v2) / (m1 + m2)
}
pub fn coefficient_of_restitution(v1i: f64, v2i: f64, v1f: f64, v2f: f64) -> f64 {
assert!(v1i != v2i, "initial velocities must differ");
-(v1f - v2f) / (v1i - v2i)
}
pub fn kinetic_energy(mass: f64, speed: f64) -> f64 {
0.5 * mass * speed * speed
}
pub fn potential_energy_gravity(mass: f64, g: f64, height: f64) -> f64 {
mass * g * height
}
pub fn potential_energy_spring(spring_constant: f64, displacement: f64) -> f64 {
0.5 * spring_constant * displacement * displacement
}
pub fn work(force: f64, displacement: f64, angle_rad: f64) -> f64 {
force * displacement * angle_rad.cos()
}
pub fn power(work: f64, time: f64) -> f64 {
assert!(time > 0.0, "time must be positive");
work / time
}
pub fn power_instantaneous(force: f64, velocity: f64) -> f64 {
force * velocity
}
pub fn angular_velocity(delta_theta: f64, delta_t: f64) -> f64 {
assert!(delta_t != 0.0, "time interval must not be zero");
delta_theta / delta_t
}
pub fn angular_acceleration(delta_omega: f64, delta_t: f64) -> f64 {
assert!(delta_t != 0.0, "time interval must not be zero");
delta_omega / delta_t
}
pub fn torque(radius: f64, force: f64, angle_rad: f64) -> f64 {
radius * force * angle_rad.sin()
}
pub fn torque_3d(r: Vec3, f: Vec3) -> Vec3 {
r.cross(&f)
}
pub fn moment_of_inertia_point(mass: f64, radius: f64) -> f64 {
mass * radius * radius
}
pub fn moment_of_inertia_solid_sphere(mass: f64, radius: f64) -> f64 {
0.4 * mass * radius * radius
}
pub fn moment_of_inertia_solid_cylinder(mass: f64, radius: f64) -> f64 {
0.5 * mass * radius * radius
}
pub fn rotational_kinetic_energy(moment_of_inertia: f64, angular_velocity: f64) -> f64 {
0.5 * moment_of_inertia * angular_velocity * angular_velocity
}
pub fn angular_momentum(moment_of_inertia: f64, angular_velocity: f64) -> f64 {
moment_of_inertia * angular_velocity
}
pub fn centripetal_acceleration(speed: f64, radius: f64) -> f64 {
assert!(radius > 0.0, "radius must be positive");
speed * speed / radius
}
pub fn centripetal_force(mass: f64, speed: f64, radius: f64) -> f64 {
assert!(radius > 0.0, "radius must be positive");
mass * speed * speed / radius
}
pub fn friction_force(coefficient: f64, normal_force: f64) -> f64 {
coefficient * normal_force
}
pub fn shm_period_spring(mass: f64, spring_constant: f64) -> f64 {
assert!(spring_constant > 0.0, "spring constant must be positive");
2.0 * std::f64::consts::PI * (mass / spring_constant).sqrt()
}
pub fn shm_period_pendulum(length: f64, g: f64) -> f64 {
assert!(g > 0.0, "gravitational acceleration must be positive");
2.0 * std::f64::consts::PI * (length / g).sqrt()
}
pub fn shm_position(amplitude: f64, angular_freq: f64, time: f64, phase: f64) -> f64 {
amplitude * (angular_freq * time + phase).cos()
}
pub fn shm_velocity(amplitude: f64, angular_freq: f64, time: f64, phase: f64) -> f64 {
-amplitude * angular_freq * (angular_freq * time + phase).sin()
}
pub fn damped_frequency(natural_freq: f64, damping_ratio: f64) -> f64 {
if damping_ratio >= 1.0 {
return 0.0;
}
natural_freq * (1.0 - damping_ratio * damping_ratio).sqrt()
}
pub fn damped_amplitude(initial_amplitude: f64, damping_coeff: f64, time: f64) -> f64 {
initial_amplitude * (-damping_coeff * time).exp()
}
pub fn damped_position(
amplitude: f64,
damping_coeff: f64,
angular_freq: f64,
time: f64,
phase: f64,
) -> f64 {
amplitude * (-damping_coeff * time).exp() * (angular_freq * time + phase).cos()
}
pub fn damping_ratio(damping_coeff: f64, mass: f64, spring_constant: f64) -> f64 {
assert!(mass > 0.0, "mass must be positive");
assert!(spring_constant > 0.0, "spring constant must be positive");
damping_coeff / (2.0 * (mass * spring_constant).sqrt())
}
pub fn critical_damping(mass: f64, spring_constant: f64) -> f64 {
2.0 * (mass * spring_constant).sqrt()
}
pub fn logarithmic_decrement(damping_ratio: f64) -> f64 {
assert!(damping_ratio >= 0.0 && damping_ratio < 1.0, "damping ratio must be in [0, 1)");
2.0 * std::f64::consts::PI * damping_ratio / (1.0 - damping_ratio * damping_ratio).sqrt()
}
pub fn quality_factor(damping_ratio: f64) -> f64 {
assert!(damping_ratio > 0.0, "damping ratio must be positive");
1.0 / (2.0 * damping_ratio)
}
pub fn decay_time(damping_coeff: f64) -> f64 {
assert!(damping_coeff > 0.0, "damping coefficient must be positive");
1.0 / damping_coeff
}
pub fn driven_amplitude(f0: f64, omega: f64, omega0: f64, gamma: f64) -> f64 {
let delta = omega0 * omega0 - omega * omega;
let denom = (delta * delta + (2.0 * gamma * omega).powi(2)).sqrt();
f0 / denom
}
pub fn driven_phase(omega: f64, omega0: f64, gamma: f64) -> f64 {
(2.0 * gamma * omega).atan2(omega0 * omega0 - omega * omega)
}
pub fn resonance_frequency(natural_freq: f64, damping_coeff: f64) -> f64 {
let sq = natural_freq * natural_freq - 2.0 * damping_coeff * damping_coeff;
if sq <= 0.0 {
return 0.0;
}
sq.sqrt()
}
pub fn resonance_amplitude(f0: f64, omega0: f64, gamma: f64) -> f64 {
assert!(gamma > 0.0, "damping coefficient must be positive");
let denom_sq = omega0 * omega0 - gamma * gamma;
if denom_sq <= 0.0 {
return f64::INFINITY;
}
f0 / (2.0 * gamma * denom_sq.sqrt())
}
pub fn coupled_normal_frequencies(k: f64, k_coupling: f64, m: f64) -> (f64, f64) {
assert!(m > 0.0, "mass must be positive");
let omega1 = (k / m).sqrt();
let omega2 = ((k + 2.0 * k_coupling) / m).sqrt();
(omega1, omega2)
}
pub fn beat_frequency_coupled(freq1: f64, freq2: f64) -> f64 {
(freq1 - freq2).abs()
}
#[cfg(test)]
mod tests {
use super::*;
fn approx(a: f64, b: f64) -> bool {
(a - b).abs() < 1e-6
}
#[test]
fn test_displacement() {
assert!(approx(displacement(0.0, 2.0, 3.0), 9.0));
}
#[test]
fn test_velocity() {
assert!(approx(velocity(10.0, -9.8, 1.0), 0.2));
}
#[test]
fn test_force() {
assert!(approx(force(5.0, 3.0), 15.0));
}
#[test]
fn test_elastic_collision() {
let (v1f, v2f) = elastic_collision_1d(1.0, 2.0, 1.0, 0.0);
assert!(approx(v1f, 0.0));
assert!(approx(v2f, 2.0));
}
#[test]
fn test_inelastic_collision() {
let vf = inelastic_collision_1d(2.0, 3.0, 1.0, 0.0);
assert!(approx(vf, 2.0));
}
#[test]
fn test_kinetic_energy() {
assert!(approx(kinetic_energy(2.0, 3.0), 9.0));
}
#[test]
fn test_work() {
assert!(approx(work(10.0, 5.0, 0.0), 50.0));
}
#[test]
fn test_centripetal_force() {
assert!(approx(centripetal_force(2.0, 3.0, 1.5), 12.0));
}
#[test]
fn test_projectile_range() {
let r = projectile_range(20.0, std::f64::consts::PI / 4.0, 9.8);
assert!(approx(r, 40.816326530612244));
}
#[test]
fn test_shm_period_pendulum() {
let t = shm_period_pendulum(1.0, 9.8);
assert!((t - 2.006).abs() < 0.01);
}
#[test]
fn test_torque() {
assert!(approx(torque(2.0, 5.0, std::f64::consts::PI / 2.0), 10.0));
}
#[test]
fn test_momentum_conservation() {
let vf = inelastic_collision_1d(2.0, 3.0, 1.0, -1.0);
assert!(approx(momentum(2.0, 3.0) + momentum(1.0, -1.0), 5.0));
assert!(approx(momentum(3.0, vf), 5.0));
}
#[test]
fn test_damped_frequency_underdamped() {
let wd = damped_frequency(10.0, 0.1);
assert!(approx(wd, 9.949874));
}
#[test]
fn test_damped_frequency_overdamped() {
assert!(approx(damped_frequency(10.0, 1.0), 0.0));
assert!(approx(damped_frequency(10.0, 2.0), 0.0));
}
#[test]
fn test_damped_amplitude() {
let a = damped_amplitude(5.0, 0.5, 2.0);
assert!(approx(a, 1.839397));
}
#[test]
fn test_damped_position() {
assert!(approx(damped_position(3.0, 0.5, 10.0, 0.0, 0.0), 3.0));
}
#[test]
fn test_damping_ratio() {
assert!(approx(damping_ratio(4.0, 1.0, 16.0), 0.5));
}
#[test]
fn test_critical_damping() {
assert!(approx(critical_damping(1.0, 100.0), 20.0));
}
#[test]
fn test_logarithmic_decrement() {
let d = logarithmic_decrement(0.1);
assert!(approx(d, 0.631484));
}
#[test]
fn test_quality_factor() {
assert!(approx(quality_factor(0.05), 10.0));
}
#[test]
fn test_decay_time() {
assert!(approx(decay_time(0.25), 4.0));
}
#[test]
fn test_driven_amplitude_at_resonance() {
let a = driven_amplitude(10.0, 5.0, 5.0, 0.5);
assert!(approx(a, 2.0));
}
#[test]
fn test_driven_amplitude_off_resonance() {
let a = driven_amplitude(1.0, 1.0, 3.0, 0.5);
assert!(approx(a, 0.124035));
}
#[test]
fn test_driven_phase_at_resonance() {
let p = driven_phase(5.0, 5.0, 0.5);
assert!(approx(p, std::f64::consts::PI / 2.0));
}
#[test]
fn test_driven_phase_low_freq() {
let p = driven_phase(0.0, 5.0, 0.5);
assert!(approx(p, 0.0));
}
#[test]
fn test_resonance_frequency() {
let wr = resonance_frequency(10.0, 1.0);
assert!(approx(wr, 9.899495));
}
#[test]
fn test_resonance_frequency_overdamped() {
assert!(approx(resonance_frequency(1.0, 1.0), 0.0));
}
#[test]
fn test_resonance_amplitude() {
assert!(approx(resonance_amplitude(10.0, 5.0, 0.5), 2.010076));
}
#[test]
fn test_coupled_normal_frequencies() {
let (w1, w2) = coupled_normal_frequencies(4.0, 3.0, 1.0);
assert!(approx(w1, 2.0));
assert!(approx(w2, 3.162278));
}
#[test]
fn test_beat_frequency_coupled() {
assert!(approx(beat_frequency_coupled(5.0, 3.0), 2.0));
assert!(approx(beat_frequency_coupled(3.0, 5.0), 2.0));
}
#[test]
fn test_velocity_squared() {
assert!(approx(velocity_squared(3.0, 2.0, 4.0), 25.0));
}
#[test]
fn test_position_3d() {
let pos = Vec3 { x: 1.0, y: 0.0, z: 0.0 };
let vel = Vec3 { x: 2.0, y: 0.0, z: 0.0 };
let acc = Vec3 { x: 0.0, y: -10.0, z: 0.0 };
let result = position_3d(pos, vel, acc, 1.0);
assert!(approx(result.x, 3.0));
assert!(approx(result.y, -5.0));
assert!(approx(result.z, 0.0));
}
#[test]
fn test_velocity_3d() {
let vel = Vec3 { x: 1.0, y: 2.0, z: 3.0 };
let acc = Vec3 { x: 0.0, y: -9.8, z: 0.0 };
let result = velocity_3d(vel, acc, 2.0);
assert!(approx(result.x, 1.0));
assert!(approx(result.y, -17.6));
assert!(approx(result.z, 3.0));
}
#[test]
fn test_projectile_max_height() {
let h = projectile_max_height(20.0, std::f64::consts::PI / 4.0, 9.8);
assert!(approx(h, 10.204081632653061));
}
#[test]
fn test_projectile_time_of_flight() {
let t = projectile_time_of_flight(20.0, std::f64::consts::PI / 6.0, 9.8);
assert!(approx(t, 2.040816));
}
#[test]
fn test_force_3d() {
let acc = Vec3 { x: 1.0, y: -2.0, z: 3.0 };
let result = force_3d(5.0, acc);
assert!(approx(result.x, 5.0));
assert!(approx(result.y, -10.0));
assert!(approx(result.z, 15.0));
}
#[test]
fn test_acceleration() {
assert!(approx(acceleration(20.0, 4.0), 5.0));
}
#[test]
fn test_weight() {
assert!(approx(weight(10.0, 9.8), 98.0));
}
#[test]
fn test_momentum_3d() {
let vel = Vec3 { x: 3.0, y: -1.0, z: 2.0 };
let result = momentum_3d(4.0, vel);
assert!(approx(result.x, 12.0));
assert!(approx(result.y, -4.0));
assert!(approx(result.z, 8.0));
}
#[test]
fn test_impulse() {
assert!(approx(impulse(100.0, 0.05), 5.0));
}
#[test]
fn test_coefficient_of_restitution() {
assert!(approx(coefficient_of_restitution(2.0, 0.0, 0.0, 2.0), 1.0));
assert!(approx(coefficient_of_restitution(3.0, 0.0, 1.0, 1.0), 0.0));
}
#[test]
fn test_potential_energy_gravity() {
assert!(approx(potential_energy_gravity(5.0, 9.8, 10.0), 490.0));
}
#[test]
fn test_potential_energy_spring() {
assert!(approx(potential_energy_spring(200.0, 0.1), 1.0));
}
#[test]
fn test_power() {
assert!(approx(power(500.0, 10.0), 50.0));
}
#[test]
fn test_power_instantaneous() {
assert!(approx(power_instantaneous(20.0, 3.0), 60.0));
}
#[test]
fn test_angular_velocity() {
assert!(approx(angular_velocity(2.0 * std::f64::consts::PI, 1.0), 6.283185));
}
#[test]
fn test_angular_acceleration() {
assert!(approx(angular_acceleration(10.0, 2.0), 5.0));
}
#[test]
fn test_torque_3d() {
let r = Vec3 { x: 1.0, y: 0.0, z: 0.0 };
let f = Vec3 { x: 0.0, y: 1.0, z: 0.0 };
let result = torque_3d(r, f);
assert!(approx(result.x, 0.0));
assert!(approx(result.y, 0.0));
assert!(approx(result.z, 1.0));
}
#[test]
fn test_moment_of_inertia_point() {
assert!(approx(moment_of_inertia_point(2.0, 3.0), 18.0));
}
#[test]
fn test_moment_of_inertia_solid_sphere() {
assert!(approx(moment_of_inertia_solid_sphere(10.0, 2.0), 16.0));
}
#[test]
fn test_moment_of_inertia_solid_cylinder() {
assert!(approx(moment_of_inertia_solid_cylinder(10.0, 2.0), 20.0));
}
#[test]
fn test_rotational_kinetic_energy() {
assert!(approx(rotational_kinetic_energy(4.0, 3.0), 18.0));
}
#[test]
fn test_angular_momentum() {
assert!(approx(angular_momentum(5.0, 3.0), 15.0));
}
#[test]
fn test_centripetal_acceleration() {
assert!(approx(centripetal_acceleration(3.0, 3.0), 3.0));
}
#[test]
fn test_friction_force() {
assert!(approx(friction_force(0.3, 100.0), 30.0));
}
#[test]
fn test_shm_period_spring() {
assert!(approx(shm_period_spring(1.0, 4.0), 3.141593));
}
#[test]
fn test_shm_position() {
assert!(approx(shm_position(5.0, 2.0, 0.0, 0.0), 5.0));
assert!(approx(shm_position(5.0, 2.0, std::f64::consts::PI / 4.0, 0.0), 0.0));
}
#[test]
fn test_shm_velocity() {
assert!(approx(shm_velocity(5.0, 2.0, 0.0, 0.0), 0.0));
assert!(approx(
shm_velocity(5.0, 2.0, std::f64::consts::PI / 4.0, 0.0),
-10.0
));
}
#[test]
fn test_resonance_amplitude_overdamped() {
let a = resonance_amplitude(1.0, 1.0, 2.0);
assert!(a.is_infinite());
}
}