mirror of
https://github.com/cosinekitty/astronomy.git
synced 2026-05-19 14:27:52 -04:00
Kotlin searchMoonPhase: allow searching backward in time.
Enhanced the Kotlin function searchMoonPhase to allow searching forward in time when the `limitDays` argument is positive, or backward in time when `limitDays` is negative. Added unit test "moon_reverse" to verify this new feature.
This commit is contained in:
@@ -635,7 +635,7 @@ namespace csharp_test
|
||||
|
||||
const int numNewMoons = 5000;
|
||||
var utList = new double[numNewMoons];
|
||||
double dtMin = 1000.0;
|
||||
double dtMin = +1000.0;
|
||||
double dtMax = -1000.0;
|
||||
|
||||
// Search forward in time from 1800 to find consecutive new moon events.
|
||||
|
||||
@@ -39,6 +39,7 @@ import kotlin.math.cos
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.hypot
|
||||
import kotlin.math.log10
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.pow
|
||||
@@ -5829,7 +5830,9 @@ fun moonPhase(time: Time): Double =
|
||||
* The beginning of the time window in which to search for the Moon reaching the specified phase.
|
||||
*
|
||||
* @param limitDays
|
||||
* The number of days after `startTime` that limits the time window for the search.
|
||||
* The number of days away from `startTime` that limits the time window for the search.
|
||||
* If the value is negative, the search is performed into the past from `startTime`.
|
||||
* Otherwise, the search is performed into the future from `startTime`.
|
||||
*
|
||||
* @return
|
||||
* If successful, returns the date and time the moon reaches the phase specified by
|
||||
@@ -5846,18 +5849,32 @@ fun searchMoonPhase(targetLon: Double, startTime: Time, limitDays: Double): Time
|
||||
// I have seen more than 0.9 days away from the simple prediction.
|
||||
// To be safe, we take the predicted time of the event and search
|
||||
// +/-1.5 days around it (a 3-day wide window).
|
||||
val moonOffset = SearchContext { time -> longitudeOffset(moonPhase(time) - targetLon) }
|
||||
var ya = moonOffset.eval(startTime)
|
||||
if (ya > 0.0) ya -= 360.0 // force searching forward in time, not backward
|
||||
val uncertainty = 1.5
|
||||
val estDt = -(MEAN_SYNODIC_MONTH * ya) / 360.0
|
||||
val dt1 = estDt - uncertainty
|
||||
if (dt1 > limitDays)
|
||||
return null // not possible for moon phase to occur within specified window (too short)
|
||||
val dt2 = min(limitDays, estDt + uncertainty)
|
||||
val moonOffset = SearchContext { time -> longitudeOffset(moonPhase(time) - targetLon) }
|
||||
var estDt: Double
|
||||
var dt1: Double
|
||||
var dt2: Double
|
||||
var ya = moonOffset.eval(startTime)
|
||||
if (limitDays < 0.0) {
|
||||
// Search backward in time.
|
||||
if (ya < 0.0) ya += 360.0
|
||||
estDt = -(MEAN_SYNODIC_MONTH * ya) / 360.0
|
||||
dt2 = estDt + uncertainty
|
||||
if (dt2 < limitDays)
|
||||
return null // not possible for moon phase to occur within specified window (too short)
|
||||
dt1 = max(limitDays, estDt - uncertainty)
|
||||
} else {
|
||||
// Search forward in time
|
||||
if (ya > 0.0) ya -= 360.0
|
||||
estDt = -(MEAN_SYNODIC_MONTH * ya) / 360.0
|
||||
dt1 = estDt - uncertainty
|
||||
if (dt1 > limitDays)
|
||||
return null // not possible for moon phase to occur within specified window (too short)
|
||||
dt2 = min(limitDays, estDt + uncertainty)
|
||||
}
|
||||
val t1 = startTime.addDays(dt1)
|
||||
val t2 = startTime.addDays(dt2)
|
||||
return search(t1, t2, 1.0, moonOffset)
|
||||
return search(t1, t2, 0.1, moonOffset)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,4 +22,4 @@ If successful, returns the date and time the moon reaches the phase specified by
|
||||
|---|---|
|
||||
| targetLon | The difference in geocentric longitude between the Sun and Moon that specifies the lunar phase being sought. This can be any value in the range [0, 360). Certain values have conventional names: 0 = new moon, 90 = first quarter, 180 = full moon, 270 = third quarter. |
|
||||
| startTime | The beginning of the time window in which to search for the Moon reaching the specified phase. |
|
||||
| limitDays | The number of days after startTime that limits the time window for the search. |
|
||||
| limitDays | The number of days away from startTime that limits the time window for the search. If the value is negative, the search is performed into the past from startTime. Otherwise, the search is performed into the future from startTime. |
|
||||
|
||||
@@ -39,6 +39,7 @@ import kotlin.math.cos
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.hypot
|
||||
import kotlin.math.log10
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.pow
|
||||
@@ -5829,7 +5830,9 @@ fun moonPhase(time: Time): Double =
|
||||
* The beginning of the time window in which to search for the Moon reaching the specified phase.
|
||||
*
|
||||
* @param limitDays
|
||||
* The number of days after `startTime` that limits the time window for the search.
|
||||
* The number of days away from `startTime` that limits the time window for the search.
|
||||
* If the value is negative, the search is performed into the past from `startTime`.
|
||||
* Otherwise, the search is performed into the future from `startTime`.
|
||||
*
|
||||
* @return
|
||||
* If successful, returns the date and time the moon reaches the phase specified by
|
||||
@@ -5846,18 +5849,32 @@ fun searchMoonPhase(targetLon: Double, startTime: Time, limitDays: Double): Time
|
||||
// I have seen more than 0.9 days away from the simple prediction.
|
||||
// To be safe, we take the predicted time of the event and search
|
||||
// +/-1.5 days around it (a 3-day wide window).
|
||||
val moonOffset = SearchContext { time -> longitudeOffset(moonPhase(time) - targetLon) }
|
||||
var ya = moonOffset.eval(startTime)
|
||||
if (ya > 0.0) ya -= 360.0 // force searching forward in time, not backward
|
||||
val uncertainty = 1.5
|
||||
val estDt = -(MEAN_SYNODIC_MONTH * ya) / 360.0
|
||||
val dt1 = estDt - uncertainty
|
||||
if (dt1 > limitDays)
|
||||
return null // not possible for moon phase to occur within specified window (too short)
|
||||
val dt2 = min(limitDays, estDt + uncertainty)
|
||||
val moonOffset = SearchContext { time -> longitudeOffset(moonPhase(time) - targetLon) }
|
||||
var estDt: Double
|
||||
var dt1: Double
|
||||
var dt2: Double
|
||||
var ya = moonOffset.eval(startTime)
|
||||
if (limitDays < 0.0) {
|
||||
// Search backward in time.
|
||||
if (ya < 0.0) ya += 360.0
|
||||
estDt = -(MEAN_SYNODIC_MONTH * ya) / 360.0
|
||||
dt2 = estDt + uncertainty
|
||||
if (dt2 < limitDays)
|
||||
return null // not possible for moon phase to occur within specified window (too short)
|
||||
dt1 = max(limitDays, estDt - uncertainty)
|
||||
} else {
|
||||
// Search forward in time
|
||||
if (ya > 0.0) ya -= 360.0
|
||||
estDt = -(MEAN_SYNODIC_MONTH * ya) / 360.0
|
||||
dt1 = estDt - uncertainty
|
||||
if (dt1 > limitDays)
|
||||
return null // not possible for moon phase to occur within specified window (too short)
|
||||
dt2 = min(limitDays, estDt + uncertainty)
|
||||
}
|
||||
val t1 = startTime.addDays(dt1)
|
||||
val t2 = startTime.addDays(dt2)
|
||||
return search(t1, t2, 1.0, moonOffset)
|
||||
return search(t1, t2, 0.1, moonOffset)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -956,6 +956,52 @@ class Tests {
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
fun `Reverse chrono new moon search`() {
|
||||
val numNewMoons = 5000
|
||||
val utList = arrayListOf<Double>()
|
||||
var dtMin = +1000.0
|
||||
var dtMax = -1000.0
|
||||
|
||||
// Search forward in time from 1800 to find consecutive new moon events.
|
||||
var time = Time(1800, 1, 1, 0, 0, 0.0)
|
||||
var i = 0
|
||||
while (i < numNewMoons) {
|
||||
val result = searchMoonPhase(0.0, time, +40.0) ?:
|
||||
fail("Failed to find new moon after $time")
|
||||
utList.add(result.ut)
|
||||
if (i > 0) {
|
||||
// Verify that consecutive new moons are reasonably close to the synodic period (29.5 days) apart.
|
||||
val dt = utList[i] - utList[i-1]
|
||||
if (dt < dtMin) dtMin = dt
|
||||
if (dt > dtMax) dtMax = dt
|
||||
}
|
||||
time = result.addDays(+0.1)
|
||||
++i
|
||||
}
|
||||
|
||||
if (dtMin < 29.273 || dtMax > 29.832)
|
||||
fail("Time between consecutive new moons is suspicious: dtMin=$dtMin, dtMax=$dtMax.")
|
||||
|
||||
// Do a reverse chronological search and make sure the results are consistent with the forward search.
|
||||
time = time.addDays(+20.0)
|
||||
var maxDiff = 0.0
|
||||
i = numNewMoons - 1
|
||||
while (i >= 0) {
|
||||
val result = searchMoonPhase(0.0, time, -40.0) ?:
|
||||
fail("Failed to find new moon before $time")
|
||||
val diff = 86400.0 * abs(result.ut - utList[i])
|
||||
if (diff > maxDiff) maxDiff = diff
|
||||
time = result.addDays(-0.1)
|
||||
--i
|
||||
}
|
||||
|
||||
if (maxDiff > 0.128)
|
||||
fail("Excessive discrepancy in reverse search: $maxDiff seconds.")
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
fun `Rise set test`() {
|
||||
val filename = dataRootDir + "riseset/riseset.txt"
|
||||
|
||||
Reference in New Issue
Block a user