blackholesfactory 0.0.2

Black hole factory — create and simulate stellar, intermediate-mass, and supermassive black holes with full Kerr physics
Documentation
use blackholesfactory::config::parameters::*;
use blackholesfactory::engine::accretion_disk::{AccretionDisk, DiskModel};
use blackholesfactory::engine::evaporation::HawkingEvaporation;
use blackholesfactory::engine::jet_dynamics::RelativisticJet;
use blackholesfactory::engine::spacetime::{KerrSpacetime, SpacetimePoint};
use std::f64::consts::FRAC_PI_2;

#[test]
fn accretion_disk_inner_at_isco() {
    let mass = 10.0 * SOLAR_MASS;
    let mdot = 1e15;
    let d = AccretionDisk::new(mass, mdot);
    let r_isco = isco_radius(mass, 0.0);
    let diff = (d.inner_radius - r_isco).abs() / r_isco;
    assert!(diff < 0.01);
}

#[test]
fn temperature_positive_outside_isco() {
    let d = AccretionDisk::new(10.0 * SOLAR_MASS, 1e15);
    let t = d.temperature_profile(d.inner_radius * 2.0);
    assert!(t > 0.0);
}

#[test]
fn temperature_zero_inside_isco() {
    let d = AccretionDisk::new(10.0 * SOLAR_MASS, 1e15);
    let t = d.temperature_profile(d.inner_radius * 0.5);
    assert!(t == 0.0);
}

#[test]
fn adaf_cooler_than_ss() {
    let mass = 10.0 * SOLAR_MASS;
    let mdot = 1e15;
    let ss = AccretionDisk::new(mass, mdot);
    let adaf = AccretionDisk::new(mass, mdot).with_model(DiskModel::Adaf);
    let r = ss.inner_radius * 3.0;
    assert!(adaf.temperature_profile(r) < ss.temperature_profile(r));
}

#[test]
fn luminosity_positive() {
    let d = AccretionDisk::new(10.0 * SOLAR_MASS, 1e15);
    assert!(d.luminosity() > 0.0);
}

#[test]
fn radiative_efficiency_bounded() {
    let d = AccretionDisk::new(10.0 * SOLAR_MASS, 1e15);
    let eff = d.radiative_efficiency();
    assert!(eff > 0.0 && eff < 1.0);
}

#[test]
fn radial_profile_correct_length() {
    let d = AccretionDisk::new(10.0 * SOLAR_MASS, 1e15);
    let profile = d.radial_profile(50);
    assert_eq!(profile.len(), 50);
}

#[test]
fn total_mass_positive() {
    let d = AccretionDisk::new(10.0 * SOLAR_MASS, 1e15);
    assert!(d.total_mass() > 0.0);
}

#[test]
fn bz_power_positive() {
    let j = RelativisticJet::new(10.0 * SOLAR_MASS, 0.9);
    assert!(j.blandford_znajek_power() > 0.0);
}

#[test]
fn kinetic_luminosity_less_than_bz() {
    let j = RelativisticJet::new(10.0 * SOLAR_MASS, 0.9);
    assert!(j.kinetic_luminosity() < j.blandford_znajek_power());
}

#[test]
fn apparent_superluminal_velocity() {
    let j = RelativisticJet::new(10.0 * SOLAR_MASS, 0.9).with_lorentz_factor(10.0);
    assert!(j.max_apparent_velocity() > C);
}

#[test]
fn doppler_boosting_at_optimal_angle() {
    let j = RelativisticJet::new(10.0 * SOLAR_MASS, 0.9).with_lorentz_factor(5.0);
    let optimal = (1.0 / j.bulk_lorentz_factor).asin();
    let d = j.doppler_factor(optimal);
    assert!(d > 1.0);
}

#[test]
fn synchrotron_cooling_time_positive() {
    let j = RelativisticJet::new(10.0 * SOLAR_MASS, 0.9).with_magnetic_field(1.0);
    let t = j.synchrotron_cooling_time(1e4);
    assert!(t > 0.0);
}

#[test]
fn kerr_event_horizon_positive() {
    let ks = KerrSpacetime::new(10.0 * SOLAR_MASS, 0.9);
    assert!(ks.event_horizon() > 0.0);
}

#[test]
fn kerr_cauchy_less_than_event_horizon() {
    let ks = KerrSpacetime::new(10.0 * SOLAR_MASS, 0.9);
    assert!(ks.cauchy_horizon() < ks.event_horizon());
}

#[test]
fn kerr_ergosphere_ge_horizon_at_equator() {
    let ks = KerrSpacetime::new(10.0 * SOLAR_MASS, 0.9);
    assert!(ks.ergosphere(FRAC_PI_2) >= ks.event_horizon());
}

#[test]
fn photon_trace_terminates() {
    let ks = KerrSpacetime::new(10.0 * SOLAR_MASS, 0.9);
    let rh = ks.event_horizon();
    let traj = ks.trace_photon(
        SpacetimePoint::new(0.0, 50.0 * rh, FRAC_PI_2, 0.0),
        [C, 0.0, C / (50.0 * rh)],
        500,
        0.01,
    );
    assert!(traj.len() > 1 && traj.len() <= 501);
}

#[test]
fn orbital_frequency_decreases_with_radius() {
    let ks = KerrSpacetime::new(10.0 * SOLAR_MASS, 0.9);
    let rh = ks.event_horizon();
    let f1 = ks.orbital_frequency(10.0 * rh);
    let f2 = ks.orbital_frequency(100.0 * rh);
    assert!(f1 > f2);
}

#[test]
fn hawking_temperature_positive() {
    let h = HawkingEvaporation::new(1e12);
    assert!(h.temperature() > 0.0);
}

#[test]
fn evaporation_time_finite() {
    let h = HawkingEvaporation::new(1e12);
    let t = h.evaporation_time();
    assert!(t > 0.0 && t < f64::INFINITY);
}

#[test]
fn page_time_less_than_evaporation() {
    let h = HawkingEvaporation::new(1e12);
    assert!(h.page_time() < h.evaporation_time());
}

#[test]
fn evolve_decreases_mass() {
    let mut h = HawkingEvaporation::new(1e12);
    h.evolve(1e10);
    assert!(h.mass_fraction_remaining() < 1.0);
}

#[test]
fn entropy_decreases_after_evaporation() {
    let h0 = HawkingEvaporation::new(1e12);
    let s0 = h0.entropy();
    let mut h1 = HawkingEvaporation::new(1e12);
    h1.evolve(1e10);
    assert!(h1.entropy() < s0);
}

#[test]
fn stellar_bh_evaporates_slower_than_micro() {
    let micro = HawkingEvaporation::new(1e12);
    let stellar = HawkingEvaporation::new(10.0 * SOLAR_MASS);
    assert!(stellar.evaporation_time() > micro.evaporation_time());
}