use suns::events::coronal_mass_ejections::*;
use suns::events::solar_flares::*;
use suns::events::sunspots::*;
use suns::{SOLAR_LUMINOSITY, SOLAR_RADIUS};
#[test]
fn flare_nanoflare_energy() {
assert!((FlareCategory::Nanoflare.typical_energy_j() - 1e17).abs() < 1e15);
}
#[test]
fn flare_microflare_energy() {
assert!((FlareCategory::Microflare.typical_energy_j() - 1e20).abs() < 1e18);
}
#[test]
fn flare_xclass_energy() {
assert!((FlareCategory::XClass.typical_energy_j() - 1e26).abs() < 1e24);
}
#[test]
fn flare_superflare_energy() {
assert!((FlareCategory::Superflare.typical_energy_j() - 1e27).abs() < 1e25);
}
#[test]
fn flare_energy_ordering() {
assert!(
FlareCategory::Nanoflare.typical_energy_j() < FlareCategory::Microflare.typical_energy_j()
);
assert!(
FlareCategory::Microflare.typical_energy_j() < FlareCategory::CClass.typical_energy_j()
);
assert!(FlareCategory::CClass.typical_energy_j() < FlareCategory::MClass.typical_energy_j());
assert!(FlareCategory::MClass.typical_energy_j() < FlareCategory::XClass.typical_energy_j());
assert!(
FlareCategory::XClass.typical_energy_j() < FlareCategory::Superflare.typical_energy_j()
);
}
#[test]
fn flare_frequency_ordering() {
assert!(
FlareCategory::Nanoflare.frequency_per_day()
> FlareCategory::Microflare.frequency_per_day()
);
assert!(
FlareCategory::Microflare.frequency_per_day() > FlareCategory::CClass.frequency_per_day()
);
assert!(FlareCategory::CClass.frequency_per_day() > FlareCategory::MClass.frequency_per_day());
assert!(FlareCategory::MClass.frequency_per_day() > FlareCategory::XClass.frequency_per_day());
assert!(
FlareCategory::XClass.frequency_per_day() > FlareCategory::Superflare.frequency_per_day()
);
}
#[test]
fn flare_labels() {
assert_eq!(FlareCategory::Nanoflare.label(), "Nanoflare");
assert_eq!(FlareCategory::XClass.label(), "X-class");
assert_eq!(FlareCategory::Superflare.label(), "Superflare");
}
#[test]
fn flare_duration_ordering() {
assert!(
FlareCategory::Nanoflare.typical_duration_s() < FlareCategory::CClass.typical_duration_s()
);
assert!(
FlareCategory::CClass.typical_duration_s() < FlareCategory::MClass.typical_duration_s()
);
assert!(
FlareCategory::MClass.typical_duration_s() < FlareCategory::XClass.typical_duration_s()
);
assert!(
FlareCategory::XClass.typical_duration_s() < FlareCategory::Superflare.typical_duration_s()
);
}
#[test]
fn carrington_event_properties() {
let c = carrington_class_event();
assert!((c.energy_j - 5e25).abs() < 1e23);
assert!((c.duration_s - 300.0).abs() < 1.0);
}
#[test]
fn carrington_peak_luminosity() {
let c = carrington_class_event();
let lum = c.peak_luminosity();
assert!(lum > 1e23);
}
#[test]
fn bastille_day_properties() {
let b = bastille_day_flare();
assert!((b.energy_j - 1e25).abs() < 1e23);
assert!((b.duration_s - 1200.0).abs() < 1.0);
assert!((b.latitude_deg - 18.0).abs() < 1e-6);
}
#[test]
fn bastille_day_peak_flux_positive() {
let b = bastille_day_flare();
assert!(b.peak_flux_at_earth() > 0.0);
}
#[test]
fn flare_goes_classification_xclass() {
let b = bastille_day_flare();
let class = b.goes_classification();
assert!(class == "X" || class == "M" || class == "C");
}
#[test]
fn flare_sep_flux_x_class() {
let b = bastille_day_flare();
assert!(b.sep_flux_protons_cm2() > 0.0);
}
#[test]
fn flare_sep_flux_c_class_zero() {
let c = SolarFlareEvent {
category: FlareCategory::CClass,
energy_j: 1e23,
duration_s: 300.0,
latitude_deg: 15.0,
longitude_deg: 0.0,
cycle_phase: 0.5,
};
assert!((c.sep_flux_protons_cm2() - 0.0).abs() < 1e-10);
}
#[test]
fn flare_radio_burst_m_class() {
let m = SolarFlareEvent {
category: FlareCategory::MClass,
energy_j: 1e25,
duration_s: 1800.0,
latitude_deg: 15.0,
longitude_deg: 0.0,
cycle_phase: 0.5,
};
assert!(m.radio_burst_expected());
}
#[test]
fn flare_no_radio_burst_c_class() {
let c = SolarFlareEvent {
category: FlareCategory::CClass,
energy_j: 1e23,
duration_s: 300.0,
latitude_deg: 15.0,
longitude_deg: 0.0,
cycle_phase: 0.5,
};
assert!(!c.radio_burst_expected());
}
#[test]
fn flare_total_radiated_fraction_tiny() {
let b = bastille_day_flare();
let frac = b.total_radiated_fraction();
assert!(frac < 1e-2);
assert!(frac > 0.0);
}
#[test]
fn flare_frequency_distribution_decreasing() {
let f1 = flare_frequency_distribution(1e20);
let f2 = flare_frequency_distribution(1e25);
assert!(f1 > f2);
}
#[test]
fn nanoflare_coronal_heating_positive() {
assert!(nanoflare_coronal_heating_rate() > 0.0);
}
#[test]
fn total_nanoflare_luminosity_positive() {
let l = total_nanoflare_luminosity();
assert!(l > 0.0);
assert!(l < SOLAR_LUMINOSITY);
}
#[test]
fn flare_waiting_time_inverse() {
let t = flare_waiting_time(1.0);
assert!((t - 86400.0).abs() < 1.0);
}
#[test]
fn cme_slow_speed() {
let c = CoronalMassEjection::slow();
assert!((c.speed_ms - 2.5e5).abs() < 1e3);
}
#[test]
fn cme_fast_speed() {
let c = CoronalMassEjection::fast();
assert!((c.speed_ms - 3.0e6).abs() < 1e4);
}
#[test]
fn cme_halo_angular_width() {
let c = CoronalMassEjection::halo();
assert!((c.angular_width_deg - 360.0).abs() < 1e-6);
}
#[test]
fn cme_kinetic_energy_positive() {
let c = CoronalMassEjection::slow();
assert!(c.kinetic_energy() > 0.0);
}
#[test]
fn cme_fast_more_energetic() {
let slow = CoronalMassEjection::slow();
let fast = CoronalMassEjection::fast();
assert!(fast.kinetic_energy() > slow.kinetic_energy());
}
#[test]
fn cme_travel_time_to_earth_positive() {
let c = CoronalMassEjection::slow();
let t = c.travel_time_to_earth();
assert!(t > 0.0);
assert!(t < 1e7); }
#[test]
fn cme_slow_longer_travel_than_fast() {
let slow = CoronalMassEjection::slow();
let fast = CoronalMassEjection::fast();
assert!(slow.travel_time_to_earth() > fast.travel_time_to_earth());
}
#[test]
fn cme_momentum_positive() {
let c = CoronalMassEjection::fast();
assert!(c.momentum() > 0.0);
}
#[test]
fn cme_solid_angle_halo_large() {
let h = CoronalMassEjection::halo();
assert!(h.solid_angle_sr() > 2.0 * std::f64::consts::PI);
}
#[test]
fn cme_is_supercritical() {
let fast = CoronalMassEjection::fast();
assert!(fast.is_supercritical());
}
#[test]
fn cme_dst_index_negative_for_fast() {
let fast = CoronalMassEjection::fast();
assert!(fast.dst_index_estimate() < 0.0);
}
#[test]
fn cme_fast_geoeffective() {
let fast = CoronalMassEjection::fast();
assert!(fast.is_geoeffective());
}
#[test]
fn cme_forbush_decrease_bounded() {
let c = CoronalMassEjection::fast();
let fd = c.forbush_decrease_percent();
assert!(fd >= 0.0);
assert!(fd <= 20.0);
}
#[test]
fn cme_rate_varies_with_cycle() {
let min = cme_rate_at_cycle_phase(0.0);
let max = cme_rate_at_cycle_phase(0.5);
assert!(max > min);
}
#[test]
fn cme_arrival_probability_bounded() {
assert!(cme_arrival_probability(60.0) > 0.0);
assert!(cme_arrival_probability(60.0) <= 1.0);
assert!((cme_arrival_probability(360.0) - 1.0).abs() < 1e-10);
}
#[test]
fn cme_drag_decelerates() {
let v = drag_based_cme_speed(2e6, 4e5, 1e-5, 1e5);
assert!(v < 2e6);
assert!(v > 4e5);
}
#[test]
fn interplanetary_shock_compression_reasonable() {
let c = interplanetary_shock_compression(3.0);
assert!(c > 1.0);
assert!(c < 4.0);
}
#[test]
fn sunspot_typical_temperature() {
let s = Sunspot::typical();
assert!((s.umbra_temperature_k - 3700.0).abs() < 1.0);
assert!((s.penumbra_temperature_k - 4500.0).abs() < 1.0);
}
#[test]
fn sunspot_large_bigger_field() {
let typical = Sunspot::typical();
let large = Sunspot::large();
assert!(large.magnetic_field_t > typical.magnetic_field_t);
}
#[test]
fn sunspot_umbra_smaller_than_total() {
let s = Sunspot::typical();
assert!(s.umbra_area_m2() < s.total_area_m2());
}
#[test]
fn sunspot_penumbra_surrounds_umbra() {
let s = Sunspot::typical();
assert!(s.penumbra_area_m2() > s.umbra_area_m2());
}
#[test]
fn sunspot_area_millionths_positive() {
let s = Sunspot::typical();
assert!(s.area_millionths_hemisphere() > 0.0);
}
#[test]
fn sunspot_luminosity_deficit_positive() {
let s = Sunspot::typical();
assert!(s.luminosity_deficit() > 0.0);
}
#[test]
fn sunspot_brightness_contrast_less_than_one() {
let s = Sunspot::typical();
assert!(s.brightness_contrast() < 1.0);
assert!(s.brightness_contrast() > 0.0);
}
#[test]
fn sunspot_magnetic_energy_positive() {
let s = Sunspot::typical();
assert!(s.magnetic_energy() > 0.0);
}
#[test]
fn sunspot_wilson_depression_positive() {
let s = Sunspot::typical();
assert!(s.wilson_depression_m() > 0.0);
}
#[test]
fn sunspot_evershed_flow_positive() {
let s = Sunspot::typical();
assert!(s.evershed_flow_speed() > 0.0);
}
#[test]
fn sunspot_lifetime_positive() {
let s = Sunspot::typical();
assert!(s.estimated_lifetime_days() > 0.0);
}
#[test]
fn sunspot_joy_law_tilt() {
let s = Sunspot::typical();
assert!(s.joy_law_tilt_deg() > 0.0);
}
#[test]
fn sunspot_default_equals_typical() {
let d = Sunspot::default();
let t = Sunspot::typical();
assert!((d.latitude_deg - t.latitude_deg).abs() < 1e-10);
assert!((d.diameter_m - t.diameter_m).abs() < 1e-10);
}
#[test]
fn cycle_25_properties() {
let c = cycle_25();
assert_eq!(c.cycle_number, 25);
assert!((c.ssn_max - 150.0).abs() < 1e-6);
}
#[test]
fn cycle_25_current_ssn_positive() {
let c = cycle_25();
assert!(c.current_ssn() > 0.0);
}
#[test]
fn cycle_phase_0_gives_zero_ssn() {
let c = SunspotCycle {
cycle_number: 25,
phase: 0.0,
ssn_max: 150.0,
};
assert!(c.current_ssn().abs() < 1e-10);
}
#[test]
fn cycle_phase_half_gives_max_ssn() {
let c = SunspotCycle {
cycle_number: 25,
phase: 0.5,
ssn_max: 150.0,
};
assert!((c.current_ssn() - 150.0).abs() < 1e-6);
}
#[test]
fn cycle_activity_level_at_minimum() {
let c = SunspotCycle {
cycle_number: 25,
phase: 0.01,
ssn_max: 150.0,
};
assert_eq!(c.activity_level(), "Minimum");
}
#[test]
fn cycle_activity_level_at_maximum() {
let c = SunspotCycle {
cycle_number: 25,
phase: 0.5,
ssn_max: 200.0,
};
assert_eq!(c.activity_level(), "Maximum");
}
#[test]
fn butterfly_latitude_decreases_with_phase() {
let c1 = SunspotCycle {
cycle_number: 25,
phase: 0.1,
ssn_max: 150.0,
};
let c2 = SunspotCycle {
cycle_number: 25,
phase: 0.9,
ssn_max: 150.0,
};
assert!(c1.butterfly_latitude_deg() > c2.butterfly_latitude_deg());
}
#[test]
fn f107_increases_with_ssn() {
let low = SunspotCycle {
cycle_number: 25,
phase: 0.1,
ssn_max: 50.0,
};
let high = SunspotCycle {
cycle_number: 25,
phase: 0.5,
ssn_max: 200.0,
};
assert!(high.f107_index() > low.f107_index());
}
#[test]
fn tsi_variation_positive_at_max() {
let c = SunspotCycle {
cycle_number: 25,
phase: 0.5,
ssn_max: 150.0,
};
assert!(c.tsi_variation_wm2() > 0.0);
}
#[test]
fn total_solar_irradiance_near_1361() {
let c = cycle_25();
let tsi = c.total_solar_irradiance();
assert!((tsi - 1361.0).abs() < 1.0);
}
#[test]
fn wolf_number_computation() {
let w = wolf_number(5, 30);
let expected = 0.6 * (10.0 * 5.0 + 30.0);
assert!((w - expected).abs() < 1e-6);
}
#[test]
fn sunspot_number_to_area_positive() {
assert!(sunspot_number_to_area_msh(100.0) > 0.0);
}
#[test]
fn maunder_minimum_zero_ssn() {
let c = maunder_minimum_cycle();
assert!(c.current_ssn().abs() < 1e-10);
}
#[test]
fn sporer_law_decreasing() {
let l1 = sporer_law_latitude(0.1);
let l2 = sporer_law_latitude(0.9);
assert!(l1 > l2);
}
#[test]
fn active_region_probability_bounded() {
let p = active_region_probability(100.0);
assert!(p > 0.0);
assert!(p <= 1.0);
}
#[test]
fn facular_brightening_positive() {
assert!(facular_brightening(100.0) > 0.0);
}
#[test]
fn total_luminosity_variation_near_solar() {
let l = total_luminosity_variation(100.0);
let ratio = l / SOLAR_LUMINOSITY;
assert!((ratio - 1.0).abs() < 0.1);
}
#[test]
fn total_luminosity_variation_zero_ssn_equals_solar() {
let l = total_luminosity_variation(0.0);
assert!((l - SOLAR_LUMINOSITY).abs() < 1e20);
}
#[test]
fn solar_radius_positive_and_reasonable() {
let r = SOLAR_RADIUS;
assert!(r > 6e8, "SOLAR_RADIUS={r} too small");
assert!(r < 7e8, "SOLAR_RADIUS={r} too large");
}