const TIME_EPSILON: f64 = 1.0 / 86400.0;
pub fn find_crossing_time<F>(
mut calculate_value: F,
start_jd: f64,
end_jd: f64,
target_value: f64,
period: f64,
) -> Option<f64>
where
F: FnMut(f64) -> f64,
{
let mut low_time = start_jd;
let mut high_time = end_jd;
let mut mid_time;
if (high_time - low_time) < TIME_EPSILON {
return Some(low_time);
}
for _ in 0..64 { mid_time = (low_time + high_time) / 2.0;
if (high_time - low_time) < TIME_EPSILON {
return Some(mid_time);
}
let mut mid_value = calculate_value(mid_time);
if period > 0.0 {
mid_value = normalize_relative(mid_value, target_value, period);
}
if mid_value < target_value {
low_time = mid_time;
} else {
high_time = mid_time;
}
}
Some((low_time + high_time) / 2.0)
}
pub fn find_angle_crossing<F>(
calculate_angle: F,
start_jd: f64,
end_jd: f64,
target_angle: f64,
) -> Option<f64>
where
F: FnMut(f64) -> f64,
{
find_crossing_time(calculate_angle, start_jd, end_jd, target_angle, 360.0)
}
fn normalize_relative(val: f64, ref_val: f64, period: f64) -> f64 {
let mut diff = val - ref_val;
if diff > period / 2.0 {
diff -= period;
} else if diff < -period / 2.0 {
diff += period;
}
ref_val + diff
}
#[cfg(test)]
mod tests {
use super::*;
use core::f64::consts::PI;
#[test]
fn test_find_crossing_linear() {
let result = find_crossing_time(
|x| 2.0 * x,
0.0,
10.0,
10.0,
0.0 );
assert!(result.is_some());
let val = result.unwrap();
assert!((val - 5.0).abs() < 1e-4);
}
#[test]
fn test_find_crossing_sine() {
let result = find_crossing_time(
|x| x.sin(),
0.0,
PI/2.0,
0.5,
0.0
);
assert!(result.is_some());
let val = result.unwrap();
assert!((val - PI/6.0).abs() < 1e-4);
}
}