mod alcabitius;
mod campanus;
mod equal;
mod koch;
mod morinus;
mod placidus;
mod porphyry;
mod regiomontanus;
mod sripathi;
mod whole_sign;
use vedaksha_math::angle::{deg_to_rad, normalize_degrees, rad_to_deg};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct HouseCusps {
pub cusps: [f64; 12],
pub asc: f64,
pub mc: f64,
pub system: HouseSystem,
pub polar_fallback: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum HouseSystem {
Placidus,
Koch,
Equal,
WholeSign,
Campanus,
Regiomontanus,
Porphyry,
Morinus,
Alcabitius,
Sripathi,
}
#[must_use]
pub fn compute_houses(ramc: f64, latitude: f64, obliquity: f64, system: HouseSystem) -> HouseCusps {
match system {
HouseSystem::Placidus => placidus::compute(ramc, latitude, obliquity),
HouseSystem::Koch => koch::compute(ramc, latitude, obliquity),
HouseSystem::Equal => equal::compute(ramc, latitude, obliquity),
HouseSystem::WholeSign => whole_sign::compute(ramc, latitude, obliquity),
HouseSystem::Campanus => campanus::compute(ramc, latitude, obliquity),
HouseSystem::Regiomontanus => regiomontanus::compute(ramc, latitude, obliquity),
HouseSystem::Porphyry => porphyry::compute(ramc, latitude, obliquity),
HouseSystem::Morinus => morinus::compute(ramc, latitude, obliquity),
HouseSystem::Alcabitius => alcabitius::compute(ramc, latitude, obliquity),
HouseSystem::Sripathi => sripathi::compute(ramc, latitude, obliquity),
}
}
fn compute_asc_mc(ramc_deg: f64, lat_deg: f64, eps_deg: f64) -> (f64, f64) {
let ramc = deg_to_rad(ramc_deg);
let lat = deg_to_rad(lat_deg);
let eps = deg_to_rad(eps_deg);
let sin_ramc = libm::sin(ramc);
let cos_ramc = libm::cos(ramc);
let cos_eps = libm::cos(eps);
let sin_eps = libm::sin(eps);
let tan_lat = libm::tan(lat);
let y = cos_ramc;
let x = -(sin_ramc * cos_eps + tan_lat * sin_eps);
let asc = normalize_degrees(rad_to_deg(libm::atan2(y, x)));
let mc = normalize_degrees(rad_to_deg(libm::atan2(sin_ramc, cos_ramc * cos_eps)));
(asc, mc)
}
const POLAR_LAT_THRESHOLD: f64 = 66.56;
const MAX_ITERATIONS: usize = 50;
const CONVERGENCE_THRESHOLD: f64 = 1e-8;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn asc_mc_equator_vernal() {
let (asc, mc) = compute_asc_mc(0.0, 0.0, 23.44);
assert!((asc - 90.0).abs() < 1.0, "ASC={asc}");
assert!(mc.abs() < 1.0 || (360.0 - mc).abs() < 1.0, "MC={mc}");
}
#[test]
fn asc_mc_equator_90() {
let (asc, mc) = compute_asc_mc(90.0, 0.0, 23.44);
assert!(mc > 80.0 && mc < 100.0, "MC={mc}");
assert!(asc > 170.0 && asc < 200.0, "ASC={asc}");
}
#[test]
fn all_systems_produce_12_cusps() {
let systems = [
HouseSystem::Placidus,
HouseSystem::Koch,
HouseSystem::Equal,
HouseSystem::WholeSign,
HouseSystem::Campanus,
HouseSystem::Regiomontanus,
HouseSystem::Porphyry,
HouseSystem::Morinus,
HouseSystem::Alcabitius,
HouseSystem::Sripathi,
];
for sys in &systems {
let result = compute_houses(30.0, 45.0, 23.44, *sys);
assert_eq!(result.cusps.len(), 12, "system {:?}", sys);
for (i, &cusp) in result.cusps.iter().enumerate() {
assert!(
(0.0..360.0).contains(&cusp),
"system {:?}, cusp {i}: {cusp}",
sys
);
}
}
}
#[test]
fn mc_via_atan2_quadrant() {
let (_, mc) = compute_asc_mc(200.0, 45.0, 23.44);
assert!(
mc >= 180.0 && mc < 360.0,
"MC for RAMC=200 should be in [180, 360), got {mc}"
);
}
}