astro/basic/moon_planet_conjunction.go

389 lines
11 KiB
Go
Raw Normal View History

package basic
import "math"
const (
moonPlanetConjunctionEstimateN = 8
moonPlanetConjunctionNearQueryDeltaDeg = 3.0
moonPlanetConjunctionBracketStepDays = 0.5
moonPlanetConjunctionNearQueryStepDays = 0.25
moonPlanetConjunctionNearQueryHalfSpan = 1.5
moonPlanetConjunctionBracketHalfSpan = 2.0
moonPlanetConjunctionBracketGrowth = 2.0
moonPlanetConjunctionBracketAttempts = 3
moonPlanetConjunctionRefineStepDays = 0.5 / 86400.0
moonPlanetConjunctionEventTolerance = 0.01
moonPlanetConjunctionFallbackSpanScale = 1.5
)
type moonPlanetConjunctionLocalResult struct {
lastUT float64
nextUT float64
}
func emptyMoonPlanetConjunctionLocalResult() moonPlanetConjunctionLocalResult {
return moonPlanetConjunctionLocalResult{
lastUT: math.NaN(),
nextUT: math.NaN(),
}
}
// MoonPlanetConjunctionPlanet 月球合月目标行星 / target planet for Moon-planet conjunction events.
type MoonPlanetConjunctionPlanet int
const (
MoonPlanetConjunctionMercury MoonPlanetConjunctionPlanet = iota + 1
MoonPlanetConjunctionVenus
MoonPlanetConjunctionMars
MoonPlanetConjunctionJupiter
MoonPlanetConjunctionSaturn
MoonPlanetConjunctionUranus
MoonPlanetConjunctionNeptune
)
func moonPlanetConjunctionWrappedDelta(diff float64) float64 {
diff = math.Mod(diff+180, 360)
if diff < 0 {
diff += 360
}
return diff - 180
}
func moonPlanetConjunctionDeltaAt(jdTT float64, planet MoonPlanetConjunctionPlanet, n int) float64 {
moonRA := HMoonGeocentricApparentRaN(jdTT, n)
var planetRA float64
switch planet {
case MoonPlanetConjunctionMercury:
planetRA = MercuryApparentRaN(jdTT, n)
case MoonPlanetConjunctionVenus:
planetRA = VenusApparentRaN(jdTT, n)
case MoonPlanetConjunctionMars:
planetRA = MarsApparentRaN(jdTT, n)
case MoonPlanetConjunctionJupiter:
planetRA = JupiterApparentRaN(jdTT, n)
case MoonPlanetConjunctionSaturn:
planetRA = SaturnApparentRaN(jdTT, n)
case MoonPlanetConjunctionUranus:
planetRA = UranusApparentRaN(jdTT, n)
case MoonPlanetConjunctionNeptune:
planetRA = NeptuneApparentRaN(jdTT, n)
default:
return math.NaN()
}
return moonPlanetConjunctionWrappedDelta(moonRA - planetRA)
}
func moonPlanetConjunctionPeriodDays(planet MoonPlanetConjunctionPlanet) float64 {
switch planet {
case MoonPlanetConjunctionMercury:
return 28.1
case MoonPlanetConjunctionVenus:
return 28.4
case MoonPlanetConjunctionMars:
return 29.2
case MoonPlanetConjunctionJupiter:
return 28.0
case MoonPlanetConjunctionSaturn:
return 27.4
case MoonPlanetConjunctionUranus:
return 27.3
case MoonPlanetConjunctionNeptune:
return 27.3
default:
return math.NaN()
}
}
func moonPlanetConjunctionInDirection(eventUT, queryTT float64, direction int) bool {
switch direction {
case -1:
return eventUTQueryBeforeOrEqual(eventUT, queryTT)
case 1:
return eventUTQueryAfterOrEqual(eventUT, queryTT)
default:
return true
}
}
func moonPlanetConjunctionFindBracket(centerTT, halfSpan, step float64, planet MoonPlanetConjunctionPlanet) (float64, float64, bool) {
if math.IsNaN(centerTT) || math.IsNaN(halfSpan) || math.IsNaN(step) || halfSpan <= 0 || step <= 0 {
return 0, 0, false
}
start := centerTT - halfSpan
end := centerTT + halfSpan
samples := int(math.Ceil((end-start)/step)) + 1
prevTT := start
prevVal := moonPlanetConjunctionDeltaAt(prevTT, planet, -1)
if math.IsNaN(prevVal) {
return 0, 0, false
}
if prevVal == 0 {
return prevTT, prevTT, true
}
bestLeft := 0.0
bestRight := 0.0
bestDistance := math.Inf(1)
for i := 1; i <= samples; i++ {
tt := start + float64(i)*step
if tt > end {
tt = end
}
val := moonPlanetConjunctionDeltaAt(tt, planet, -1)
if math.IsNaN(val) {
return 0, 0, false
}
if val == 0 {
return tt, tt, true
}
if prevVal*val < 0 {
mid := (prevTT + tt) / 2.0
distance := math.Abs(mid - centerTT)
if distance < bestDistance {
bestLeft = prevTT
bestRight = tt
bestDistance = distance
}
}
if tt == end {
break
}
prevTT = tt
prevVal = val
}
if math.IsInf(bestDistance, 1) {
return 0, 0, false
}
return bestLeft, bestRight, true
}
func moonPlanetConjunctionRefineBracket(leftTT, rightTT float64, planet MoonPlanetConjunctionPlanet) float64 {
if leftTT > rightTT {
leftTT, rightTT = rightTT, leftTT
}
if leftTT == rightTT {
return leftTT
}
center := (leftTT + rightTT) / 2.0
halfWindow := (rightTT - leftTT) / 2.0
return eventZeroRefine(center, halfWindow, moonPlanetConjunctionRefineStepDays, func(sampleTT float64) float64 {
return moonPlanetConjunctionDeltaAt(sampleTT, planet, -1)
})
}
func moonPlanetConjunctionEventUT(leftTT, rightTT float64, planet MoonPlanetConjunctionPlanet) float64 {
eventTT := moonPlanetConjunctionRefineBracket(leftTT, rightTT, planet)
if math.Abs(moonPlanetConjunctionDeltaAt(eventTT, planet, -1)) > moonPlanetConjunctionEventTolerance {
return math.NaN()
}
return TD2UT(eventTT, false)
}
func moonPlanetConjunctionCollectLocalEvent(result *moonPlanetConjunctionLocalResult, queryTT, eventUT float64) {
if math.IsNaN(eventUT) {
return
}
if eventUTQueryBeforeOrEqual(eventUT, queryTT) {
if math.IsNaN(result.lastUT) || math.Abs(eventUTQueryTTDelta(eventUT, queryTT)) < math.Abs(eventUTQueryTTDelta(result.lastUT, queryTT)) {
result.lastUT = eventUT
}
}
if eventUTQueryAfterOrEqual(eventUT, queryTT) {
if math.IsNaN(result.nextUT) || math.Abs(eventUTQueryTTDelta(eventUT, queryTT)) < math.Abs(eventUTQueryTTDelta(result.nextUT, queryTT)) {
result.nextUT = eventUT
}
}
}
func moonPlanetConjunctionShouldCheckLocal(queryTT float64, planet MoonPlanetConjunctionPlanet) bool {
delta := moonPlanetConjunctionDeltaAt(queryTT, planet, moonPlanetConjunctionEstimateN)
if math.IsNaN(delta) {
return false
}
return math.Abs(delta) <= moonPlanetConjunctionNearQueryDeltaDeg
}
func moonPlanetConjunctionLocalEvents(queryTT float64, planet MoonPlanetConjunctionPlanet) moonPlanetConjunctionLocalResult {
result := emptyMoonPlanetConjunctionLocalResult()
start := queryTT - moonPlanetConjunctionNearQueryHalfSpan
end := queryTT + moonPlanetConjunctionNearQueryHalfSpan
step := moonPlanetConjunctionNearQueryStepDays
prevTT := start
prevVal := moonPlanetConjunctionDeltaAt(prevTT, planet, -1)
if math.IsNaN(prevVal) {
return result
}
samples := int(math.Ceil((end-start)/step)) + 1
for i := 1; i <= samples; i++ {
tt := start + float64(i)*step
if tt > end {
tt = end
}
val := moonPlanetConjunctionDeltaAt(tt, planet, -1)
if math.IsNaN(val) {
return emptyMoonPlanetConjunctionLocalResult()
}
if prevVal == 0 || val == 0 || prevVal*val < 0 {
moonPlanetConjunctionCollectLocalEvent(&result, queryTT, moonPlanetConjunctionEventUT(prevTT, tt, planet))
}
if tt == end {
break
}
prevTT = tt
prevVal = val
}
return result
}
func moonPlanetConjunctionMaybeLocalEvents(queryTT float64, planet MoonPlanetConjunctionPlanet) moonPlanetConjunctionLocalResult {
if !moonPlanetConjunctionShouldCheckLocal(queryTT, planet) {
return emptyMoonPlanetConjunctionLocalResult()
}
return moonPlanetConjunctionLocalEvents(queryTT, planet)
}
func moonPlanetConjunctionGuessTT(queryTT float64, planet MoonPlanetConjunctionPlanet, direction int) float64 {
delta := moonPlanetConjunctionDeltaAt(queryTT, planet, moonPlanetConjunctionEstimateN)
if math.IsNaN(delta) {
return math.NaN()
}
period := moonPlanetConjunctionPeriodDays(planet)
if math.IsNaN(period) {
return math.NaN()
}
switch direction {
case -1:
return queryTT - innerLastCycleOffset(delta, period)
case 1:
return queryTT + innerNextCycleOffset(delta, period)
default:
return math.NaN()
}
}
func moonPlanetConjunctionDirectionalFallback(queryTT float64, planet MoonPlanetConjunctionPlanet, direction int) float64 {
period := moonPlanetConjunctionPeriodDays(planet)
if math.IsNaN(period) {
return math.NaN()
}
span := period * moonPlanetConjunctionFallbackSpanScale
if span <= 0 {
return math.NaN()
}
step := moonPlanetConjunctionNearQueryStepDays
start := queryTT
end := queryTT
switch direction {
case -1:
start -= span
case 1:
end += span
default:
return math.NaN()
}
prevTT := start
prevVal := moonPlanetConjunctionDeltaAt(prevTT, planet, -1)
if math.IsNaN(prevVal) {
return math.NaN()
}
bestEventUT := math.NaN()
for tt := start + step; ; tt += step {
if tt > end {
tt = end
}
val := moonPlanetConjunctionDeltaAt(tt, planet, -1)
if math.IsNaN(val) {
return math.NaN()
}
if prevVal == 0 || val == 0 || prevVal*val < 0 {
eventUT := moonPlanetConjunctionEventUT(prevTT, tt, planet)
if !math.IsNaN(eventUT) && moonPlanetConjunctionInDirection(eventUT, queryTT, direction) {
if direction == 1 {
return eventUT
}
bestEventUT = eventUT
}
}
if tt == end {
break
}
prevTT = tt
prevVal = val
}
return bestEventUT
}
func moonPlanetConjunctionDirectionalEventWithLocal(queryTT float64, planet MoonPlanetConjunctionPlanet, direction int, local moonPlanetConjunctionLocalResult) float64 {
switch direction {
case -1:
if !math.IsNaN(local.lastUT) {
return local.lastUT
}
case 1:
if !math.IsNaN(local.nextUT) {
return local.nextUT
}
}
guessTT := moonPlanetConjunctionGuessTT(queryTT, planet, direction)
if math.IsNaN(guessTT) {
return math.NaN()
}
halfSpan := moonPlanetConjunctionBracketHalfSpan
for attempt := 0; attempt < moonPlanetConjunctionBracketAttempts; attempt++ {
left, right, ok := moonPlanetConjunctionFindBracket(guessTT, halfSpan, moonPlanetConjunctionBracketStepDays, planet)
if ok {
eventUT := moonPlanetConjunctionEventUT(left, right, planet)
if math.IsNaN(eventUT) {
halfSpan *= moonPlanetConjunctionBracketGrowth
continue
}
if moonPlanetConjunctionInDirection(eventUT, queryTT, direction) {
return eventUT
}
}
halfSpan *= moonPlanetConjunctionBracketGrowth
}
return moonPlanetConjunctionDirectionalFallback(queryTT, planet, direction)
}
func moonPlanetConjunctionDirectionalEvent(queryTT float64, planet MoonPlanetConjunctionPlanet, direction int) float64 {
return moonPlanetConjunctionDirectionalEventWithLocal(queryTT, planet, direction, moonPlanetConjunctionMaybeLocalEvents(queryTT, planet))
}
// LastMoonPlanetConjunction 指定时刻之前最近一次行星合月(赤经合) / previous Moon-planet conjunction at or before jde.
func LastMoonPlanetConjunction(jde float64, planet MoonPlanetConjunctionPlanet) float64 {
return moonPlanetConjunctionDirectionalEvent(jde, planet, -1)
}
// NextMoonPlanetConjunction 指定时刻之后最近一次行星合月(赤经合) / next Moon-planet conjunction at or after jde.
func NextMoonPlanetConjunction(jde float64, planet MoonPlanetConjunctionPlanet) float64 {
return moonPlanetConjunctionDirectionalEvent(jde, planet, 1)
}
// ClosestMoonPlanetConjunction 离指定时刻最近一次行星合月(赤经合) / closest Moon-planet conjunction to jde.
func ClosestMoonPlanetConjunction(jde float64, planet MoonPlanetConjunctionPlanet) float64 {
local := moonPlanetConjunctionMaybeLocalEvents(jde, planet)
if !math.IsNaN(local.lastUT) && !math.IsNaN(local.nextUT) {
if sameEventJD(local.lastUT, local.nextUT) {
return local.lastUT
}
return closestEventUTToQueryTT(jde, local.lastUT, local.nextUT)
}
if !math.IsNaN(local.lastUT) {
return local.lastUT
}
if !math.IsNaN(local.nextUT) {
return local.nextUT
}
last := moonPlanetConjunctionDirectionalEventWithLocal(jde, planet, -1, local)
next := moonPlanetConjunctionDirectionalEventWithLocal(jde, planet, 1, local)
if math.IsNaN(last) {
return next
}
if math.IsNaN(next) {
return last
}
return closestEventUTToQueryTT(jde, last, next)
}