blackholesfactory 0.0.4

Black hole factory — create and simulate stellar, intermediate-mass, and supermassive black holes with full Kerr physics
Documentation
use blackholesfactory::config::parameters::*;
use blackholesfactory::types::binary::*;
use blackholesfactory::types::intermediate_mass::*;
use blackholesfactory::types::primordial::*;
use blackholesfactory::types::stellar_mass::*;
use blackholesfactory::types::supermassive::*;

#[test]
fn stellar_from_mass_in_range() {
    let bh = StellarMassBlackHole::from_mass(21.2, 0.998);
    let m = bh.mass_solar();
    assert!((STELLAR_MASS_MIN_SOLAR..=STELLAR_MASS_MAX_SOLAR).contains(&m));
}

#[test]
fn stellar_from_collapse_mass_varies() {
    let bh1 = StellarMassBlackHole::from_collapse(15.0, 0.5);
    let bh2 = StellarMassBlackHole::from_collapse(50.0, 0.5);
    assert!(bh2.mass_solar() > bh1.mass_solar());
}

#[test]
fn stellar_kick_velocity_positive() {
    let bh = StellarMassBlackHole::from_collapse(25.0, 0.7);
    assert!(bh.kick_velocity() > 0.0);
}

#[test]
fn stellar_x_ray_luminosity_positive() {
    let bh = StellarMassBlackHole::from_mass(21.2, 0.998);
    assert!(bh.x_ray_luminosity(0.01) > 0.0);
}

#[test]
fn stellar_tidal_disruption_radius_positive() {
    let bh = StellarMassBlackHole::from_mass(21.2, 0.998);
    let r = bh.tidal_disruption_radius(SOLAR_MASS, 6.957e8);
    assert!(r > 0.0);
}

#[test]
fn stellar_gw_strain_positive() {
    let bh = StellarMassBlackHole::from_mass(21.2, 0.998);
    let dist = 6.85e3 * LIGHT_YEAR;
    let h = bh.gravitational_wave_strain(10.0 * SOLAR_MASS, dist, 1e10);
    assert!(h > 0.0);
}

#[test]
fn intermediate_new_mass_in_range() {
    let bh = IntermediateMassBlackHole::new(2e4, 0.5, FormationChannel::RunawayMerger);
    let m = bh.mass_solar();
    assert!((INTERMEDIATE_MASS_MIN_SOLAR..=INTERMEDIATE_MASS_MAX_SOLAR).contains(&m));
}

#[test]
fn intermediate_from_runaway_merger() {
    let bh = IntermediateMassBlackHole::from_runaway_merger(1e6);
    let m = bh.mass_solar();
    assert!((INTERMEDIATE_MASS_MIN_SOLAR..=INTERMEDIATE_MASS_MAX_SOLAR).contains(&m));
}

#[test]
fn intermediate_from_hierarchical_mergers() {
    let bh = IntermediateMassBlackHole::from_hierarchical_mergers(50.0, 10);
    let m = bh.mass_solar();
    assert!(m >= INTERMEDIATE_MASS_MIN_SOLAR);
}

#[test]
fn intermediate_from_pop_iii() {
    let bh = IntermediateMassBlackHole::from_pop_iii(500.0);
    let m = bh.mass_solar();
    assert!(m >= INTERMEDIATE_MASS_MIN_SOLAR);
}

#[test]
fn intermediate_qpo_frequency_positive() {
    let bh = IntermediateMassBlackHole::new(2e4, 0.5, FormationChannel::RunawayMerger);
    assert!(bh.ulf_qpo_frequency() > 0.0);
}

#[test]
fn intermediate_influence_radius_positive() {
    let bh = IntermediateMassBlackHole::new(2e4, 0.5, FormationChannel::RunawayMerger);
    assert!(bh.influence_radius(100e3) > 0.0);
}

#[test]
fn intermediate_merger_recoil_positive() {
    let bh = IntermediateMassBlackHole::new(2e4, 0.5, FormationChannel::RunawayMerger);
    assert!(bh.merger_recoil_velocity(0.5) > 0.0);
}

#[test]
fn supermassive_new_mass_in_range() {
    let bh = SupermassiveBlackHole::new(6.5e9, 0.9, 375e3);
    let m = bh.mass_solar();
    assert!((SUPERMASSIVE_MASS_MIN_SOLAR..=SUPERMASSIVE_MASS_MAX_SOLAR).contains(&m));
}

#[test]
fn supermassive_from_m_sigma() {
    let bh = SupermassiveBlackHole::from_m_sigma(200e3);
    let m = bh.mass_solar();
    assert!(m >= SUPERMASSIVE_MASS_MIN_SOLAR);
}

#[test]
fn supermassive_m_sigma_deviation_bounded() {
    let bh = SupermassiveBlackHole::from_m_sigma(200e3);
    assert!(bh.m_sigma_deviation() < 0.01);
}

#[test]
fn supermassive_eddington_positive() {
    let bh = SupermassiveBlackHole::new(6.5e9, 0.9, 375e3);
    assert!(bh.eddington_luminosity() > 0.0);
}

#[test]
fn supermassive_bondi_accretion_positive() {
    let bh = SupermassiveBlackHole::new(6.5e9, 0.9, 375e3);
    let mdot = bh.bondi_accretion_rate(1e-21, 1e6);
    assert!(mdot > 0.0);
}

#[test]
fn supermassive_jet_power_positive() {
    let bh = SupermassiveBlackHole::new(6.5e9, 0.9, 375e3);
    assert!(bh.jet_power_estimate() > 0.0);
}

#[test]
fn supermassive_shadow_angular_diameter_reasonable() {
    let bh = SupermassiveBlackHole::new(6.5e9, 0.9, 375e3);
    let dist = 16.8e6 * PARSEC;
    let d = bh.shadow_angular_diameter_uas(dist);
    assert!(d > 10.0 && d < 100.0);
}

#[test]
fn supermassive_swallows_star_whole() {
    let bh = SupermassiveBlackHole::new(6.5e9, 0.9, 375e3);
    assert!(!bh.can_disrupt_star(SOLAR_MASS, 6.957e8));
}

#[test]
fn supermassive_inspiral_timescale_positive() {
    let bh = SupermassiveBlackHole::new(6.5e9, 0.9, 375e3);
    let t = bh.inspiral_timescale(1e4 * SOLAR_MASS, 0.1 * PARSEC);
    assert!(t > 0.0);
}

#[test]
fn supermassive_dynamical_friction_positive() {
    let bh = SupermassiveBlackHole::new(6.5e9, 0.9, 375e3);
    let t = bh.dynamical_friction_timescale(1e4 * SOLAR_MASS, PARSEC, 375e3);
    assert!(t > 0.0);
}

#[test]
fn primordial_new_mass_clamped() {
    let bh = PrimordialBlackHole::new(1e12, PrimordialFormation::DensityFluctuation);
    assert!((PRIMORDIAL_MASS_MIN_KG..=PRIMORDIAL_MASS_MAX_KG).contains(&bh.mass_kg()));
}

#[test]
fn primordial_hawking_temperature_positive() {
    let bh = PrimordialBlackHole::new(1e12, PrimordialFormation::CosmicString);
    assert!(bh.hawking_temperature() > 0.0);
}

#[test]
fn primordial_evaporation_time_finite() {
    let bh = PrimordialBlackHole::new(1e12, PrimordialFormation::BubbleCollision);
    let t = bh.evaporation_time();
    assert!(t > 0.0 && t < f64::INFINITY);
}

#[test]
fn primordial_light_evaporates_fast() {
    let bh = PrimordialBlackHole::new(1e8, PrimordialFormation::PhaseTransition);
    assert!(bh.is_evaporated(UNIVERSE_AGE));
}

#[test]
fn primordial_heavy_survives() {
    let bh = PrimordialBlackHole::new(1e12, PrimordialFormation::DomainWall);
    assert!(!bh.is_evaporated(UNIVERSE_AGE));
}

#[test]
fn primordial_luminosity_positive() {
    let bh = PrimordialBlackHole::new(1e12, PrimordialFormation::DensityFluctuation);
    assert!(bh.luminosity() > 0.0);
}

#[test]
fn primordial_peak_emission_energy_positive() {
    let bh = PrimordialBlackHole::new(1e12, PrimordialFormation::DensityFluctuation);
    assert!(bh.peak_emission_energy() > 0.0);
}

#[test]
fn primordial_with_spin_builder() {
    let bh = PrimordialBlackHole::new(1e15, PrimordialFormation::CosmicString).with_spin(0.5);
    assert!((bh.singularity.spin - 0.5).abs() < 1e-10);
}

#[test]
fn primordial_with_formation_epoch() {
    let bh = PrimordialBlackHole::new(1e12, PrimordialFormation::DensityFluctuation)
        .with_formation_epoch(1e-23);
    assert!((bh.formation_epoch - 1e-23).abs() < 1e-30);
}

#[test]
fn primordial_remaining_lifetime_decreases() {
    let bh = PrimordialBlackHole::new(1e12, PrimordialFormation::PhaseTransition);
    let early = bh.remaining_lifetime(0.0);
    let later = bh.remaining_lifetime(1e50);
    assert!(early > later);
}

#[test]
fn primordial_horizon_mass_at_epoch() {
    let m = PrimordialBlackHole::horizon_mass_at_epoch(1e-5);
    assert!(m > 0.0);
}

#[test]
fn binary_total_mass() {
    let bb = BinaryBlackHole::new(30.0 * SOLAR_MASS, 35.0 * SOLAR_MASS, 1e9);
    let expected = 65.0 * SOLAR_MASS;
    assert!((bb.total_mass() - expected).abs() / expected < 1e-10);
}

#[test]
fn binary_chirp_mass_positive() {
    let bb = BinaryBlackHole::new(30.0 * SOLAR_MASS, 35.0 * SOLAR_MASS, 1e9);
    assert!(bb.chirp_mass() > 0.0);
}

#[test]
fn binary_orbital_period_positive() {
    let bb = BinaryBlackHole::new(30.0 * SOLAR_MASS, 30.0 * SOLAR_MASS, 1e9);
    assert!(bb.orbital_period() > 0.0);
}

#[test]
fn binary_gw_frequency_positive() {
    let bb = BinaryBlackHole::new(30.0 * SOLAR_MASS, 30.0 * SOLAR_MASS, 1e9);
    assert!(bb.gravitational_wave_frequency() > 0.0);
}

#[test]
fn binary_merger_timescale_positive() {
    let bb = BinaryBlackHole::new(30.0 * SOLAR_MASS, 30.0 * SOLAR_MASS, 1e9);
    assert!(bb.merger_timescale() > 0.0);
}

#[test]
fn binary_eccentricity_slows_merger() {
    let circ = BinaryBlackHole::new(30.0 * SOLAR_MASS, 30.0 * SOLAR_MASS, 1e9);
    let ecc =
        BinaryBlackHole::new(30.0 * SOLAR_MASS, 30.0 * SOLAR_MASS, 1e9).with_eccentricity(0.8);
    assert!(ecc.merger_timescale() < circ.merger_timescale());
}

#[test]
fn binary_gw_strain_positive() {
    let bb = BinaryBlackHole::new(30.0 * SOLAR_MASS, 30.0 * SOLAR_MASS, 1e9);
    assert!(bb.gravitational_wave_strain(100e6 * PARSEC) > 0.0);
}

#[test]
fn binary_gw_luminosity_positive() {
    let bb = BinaryBlackHole::new(30.0 * SOLAR_MASS, 30.0 * SOLAR_MASS, 1e9);
    assert!(bb.gravitational_wave_luminosity() > 0.0);
}

#[test]
fn binary_final_spin_bounded() {
    let bb = BinaryBlackHole::new(30.0 * SOLAR_MASS, 30.0 * SOLAR_MASS, 1e9);
    let spin = bb.final_spin_estimate();
    assert!(spin > 0.0 && spin <= 0.998);
}

#[test]
fn binary_final_mass_less_than_total() {
    let bb = BinaryBlackHole::new(30.0 * SOLAR_MASS, 30.0 * SOLAR_MASS, 1e9);
    assert!(bb.final_mass_estimate() < bb.total_mass());
}

#[test]
fn binary_energy_radiated_positive() {
    let bb = BinaryBlackHole::new(30.0 * SOLAR_MASS, 30.0 * SOLAR_MASS, 1e9);
    assert!(bb.energy_radiated() > 0.0);
}

#[test]
fn binary_with_spins_builder() {
    let bb = BinaryBlackHole::new(30.0 * SOLAR_MASS, 30.0 * SOLAR_MASS, 1e9).with_spins(0.7, 0.3);
    assert!((bb.primary.spin - 0.7).abs() < 1e-10);
    assert!((bb.secondary.spin - 0.3).abs() < 1e-10);
}

#[test]
fn binary_symmetric_mass_ratio_bounded() {
    let bb = BinaryBlackHole::new(30.0 * SOLAR_MASS, 30.0 * SOLAR_MASS, 1e9);
    let eta = bb.symmetric_mass_ratio();
    assert!(eta > 0.0 && eta <= 0.25);
}

#[test]
fn binary_orbital_velocity_positive() {
    let bb = BinaryBlackHole::new(30.0 * SOLAR_MASS, 30.0 * SOLAR_MASS, 1e9);
    assert!(bb.orbital_velocity() > 0.0);
}