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) }