package basic import ( "math" "sort" "time" . "b612.me/astro/tools" ) const ( moonMaxDeclinationMeanMonthDays = 27.321582247 moonMaxDeclinationBaseCycle = 1336.86 moonMaxDeclinationSearchSpan = 3 ) // DeclinationEvent 赤纬极值事件 / declination extremum event. type DeclinationEvent struct { // JD 是事件发生时刻对应的世界时儒略日 / event time as UTC-based Julian day. JD float64 // Declination 是该时刻月心地心赤纬,单位度 / geocentric lunar declination at the event, in degrees. Declination float64 } type moonMaxDeclinationCoefficients struct { D0 float64 M0 float64 MP0 float64 F0 float64 JDE0 float64 sign float64 tc [44]float64 dc [37]float64 } var moonMaxDeclinationNorthCoefficients = moonMaxDeclinationCoefficients{ D0: 152.2029, M0: 14.8591, MP0: 4.6881, F0: 325.8867, JDE0: 2451562.5897, sign: 1, tc: [44]float64{ 0.8975, -0.4726, -0.1030, -0.0976, -0.0462, -0.0461, -0.0438, 0.0162, -0.0157, 0.0145, 0.0136, -0.0095, -0.0091, -0.0089, 0.0075, -0.0068, 0.0061, -0.0047, -0.0043, -0.0040, -0.0037, 0.0031, 0.0030, -0.0029, -0.0029, -0.0027, 0.0024, -0.0021, 0.0019, 0.0018, 0.0018, 0.0017, 0.0017, -0.0014, 0.0013, 0.0013, 0.0012, 0.0011, -0.0011, 0.0010, 0.0010, -0.0009, 0.0007, -0.0007, }, dc: [37]float64{ 5.1093, 0.2658, 0.1448, -0.0322, 0.0133, 0.0125, -0.0124, -0.0101, 0.0097, -0.0087, 0.0074, 0.0067, 0.0063, 0.0060, -0.0057, -0.0056, 0.0052, 0.0041, -0.0040, 0.0038, -0.0034, -0.0029, 0.0029, -0.0028, -0.0028, -0.0023, -0.0021, 0.0019, 0.0018, 0.0017, 0.0015, 0.0014, -0.0012, -0.0012, -0.0010, -0.0010, 0.0006, }, } var moonMaxDeclinationSouthCoefficients = moonMaxDeclinationCoefficients{ D0: 345.6676, M0: 1.3951, MP0: 186.21, F0: 145.1633, JDE0: 2451548.9289, sign: -1, tc: [44]float64{ -0.8975, -0.4726, -0.1030, -0.0976, 0.0541, 0.0516, -0.0438, 0.0112, 0.0157, 0.0023, -0.0136, 0.0110, 0.0091, 0.0089, 0.0075, -0.0030, -0.0061, -0.0047, -0.0043, 0.0040, -0.0037, -0.0031, 0.0030, 0.0029, -0.0029, -0.0027, 0.0024, -0.0021, -0.0019, -0.0006, -0.0018, -0.0017, 0.0017, 0.0014, -0.0013, -0.0013, 0.0012, 0.0011, 0.0011, 0.0010, 0.0010, -0.0009, -0.0007, -0.0007, }, dc: [37]float64{ -5.1093, 0.2658, -0.1448, 0.0322, 0.0133, 0.0125, -0.0015, 0.0101, -0.0097, 0.0087, 0.0074, 0.0067, -0.0063, -0.0060, 0.0057, -0.0056, -0.0052, -0.0041, -0.0040, -0.0038, 0.0034, -0.0029, 0.0029, 0.0028, -0.0028, 0.0023, 0.0021, 0.0019, 0.0018, -0.0017, 0.0015, 0.0014, 0.0012, -0.0012, 0.0010, -0.0010, 0.0037, }, } // MoonMaximumNorthDeclinations 指定年月内的所有月球最大北赤纬事件 / all maximum northern lunar declination events in the given Gregorian month. func MoonMaximumNorthDeclinations(year int, month time.Month) []DeclinationEvent { return moonMaximumDeclinationsInMonth(year, month, moonMaxDeclinationNorthCoefficients) } // MoonMaximumSouthDeclinations 指定年月内的所有月球最大南赤纬事件 / all maximum southern lunar declination events in the given Gregorian month. func MoonMaximumSouthDeclinations(year int, month time.Month) []DeclinationEvent { return moonMaximumDeclinationsInMonth(year, month, moonMaxDeclinationSouthCoefficients) } // LastMoonMaximumNorthDeclination 指定时刻之前最近一次月球最大北赤纬 / last maximum northern lunar declination at or before jd. func LastMoonMaximumNorthDeclination(jd float64) DeclinationEvent { return moonMaximumDeclinationSearch(jd, moonMaxDeclinationNorthCoefficients, -1, true) } // NextMoonMaximumNorthDeclination 指定时刻之后最近一次月球最大北赤纬 / next maximum northern lunar declination after jd. func NextMoonMaximumNorthDeclination(jd float64) DeclinationEvent { return moonMaximumDeclinationSearch(jd, moonMaxDeclinationNorthCoefficients, 1, false) } // ClosestMoonMaximumNorthDeclination 离指定时刻最近一次月球最大北赤纬 / closest maximum northern lunar declination to jd. func ClosestMoonMaximumNorthDeclination(jd float64) DeclinationEvent { return moonClosestMaximumDeclination(jd, moonMaxDeclinationNorthCoefficients) } // LastMoonMaximumSouthDeclination 指定时刻之前最近一次月球最大南赤纬 / last maximum southern lunar declination at or before jd. func LastMoonMaximumSouthDeclination(jd float64) DeclinationEvent { return moonMaximumDeclinationSearch(jd, moonMaxDeclinationSouthCoefficients, -1, true) } // NextMoonMaximumSouthDeclination 指定时刻之后最近一次月球最大南赤纬 / next maximum southern lunar declination after jd. func NextMoonMaximumSouthDeclination(jd float64) DeclinationEvent { return moonMaximumDeclinationSearch(jd, moonMaxDeclinationSouthCoefficients, 1, false) } // ClosestMoonMaximumSouthDeclination 离指定时刻最近一次月球最大南赤纬 / closest maximum southern lunar declination to jd. func ClosestMoonMaximumSouthDeclination(jd float64) DeclinationEvent { return moonClosestMaximumDeclination(jd, moonMaxDeclinationSouthCoefficients) } func moonMaximumDeclinationsInMonth(year int, month time.Month, coeffs moonMaxDeclinationCoefficients) []DeclinationEvent { startUTC := time.Date(year, month, 1, 0, 0, 0, 0, time.UTC) endUTC := startUTC.AddDate(0, 1, 0) startTT := UTC2TT(Date2JD(startUTC)) endTT := UTC2TT(Date2JD(endUTC)) kStart := int(math.Floor((startTT-coeffs.JDE0)/moonMaxDeclinationMeanMonthDays)) - 1 kEnd := int(math.Ceil((endTT-coeffs.JDE0)/moonMaxDeclinationMeanMonthDays)) + 1 cfg := apsisSearchConfig{ bracketHalfWidth: moonApsisBracketHalfWidth, sampleStep: moonApsisSampleStep, derivativeStep: moonApsisDerivativeStep, toleranceDays: moonApsisToleranceDays, maxIterations: moonApsisMaxIterations, maximize: coeffs.sign > 0, } events := make([]DeclinationEvent, 0, 2) for k := kStart; k <= kEnd; k++ { event := moonMaximumDeclinationEvent(k, coeffs, cfg) eventTimeUTC := JD2DateByZone(event.JD, time.UTC, false) if eventTimeUTC.Before(startUTC) || !eventTimeUTC.Before(endUTC) { continue } events = append(events, event) } sort.Slice(events, func(i, j int) bool { return events[i].JD < events[j].JD }) return events } func moonMaximumDeclinationEvent(k int, coeffs moonMaxDeclinationCoefficients, cfg apsisSearchConfig) DeclinationEvent { seedTT := moonMaximumDeclinationSeedTT(k, coeffs) eventTT, declination := refineDistanceExtremum(seedTT, cfg, func(sampleTT float64) float64 { return HMoonTrueDecN(sampleTT, -1) }) return DeclinationEvent{ JD: TT2UTC(eventTT), Declination: declination, } } func moonMaximumDeclinationSearch(jd float64, coeffs moonMaxDeclinationCoefficients, direction int, includeCurrent bool) DeclinationEvent { if !finite(jd) { // 非有限查询算不出周期序号(centerK 会把 NaN/±Inf 压成 MinInt64,种子外推到 1e40 后步长小于一个 ULP)。 return DeclinationEvent{} } cfg := moonMaximumDeclinationSearchConfig(coeffs) centerK := moonMaximumDeclinationOffset(jd, coeffs) step := 1 if direction < 0 { step = -1 } event := moonMaximumDeclinationEvent(centerK, coeffs, cfg) // 事件时刻随周期序号单调递增:该方向上最近的候选就是 centerK 沿该方向第一个满足方向不变量 // 的周期;命中后只需再回退检查更早的周期是否同样满足,可达范围仍旧是 ±3 个周期。 if !moonMaximumDeclinationMatchesDirection(event.JD-jd, direction, includeCurrent) { for offset := 1; offset <= moonMaxDeclinationSearchSpan; offset++ { event = moonMaximumDeclinationEvent(centerK+step*offset, coeffs, cfg) if moonMaximumDeclinationMatchesDirection(event.JD-jd, direction, includeCurrent) { return event } } return DeclinationEvent{} } for offset := 1; offset <= moonMaxDeclinationSearchSpan; offset++ { previous := moonMaximumDeclinationEvent(centerK-step*offset, coeffs, cfg) if !moonMaximumDeclinationMatchesDirection(previous.JD-jd, direction, includeCurrent) { break } event = previous } return event } func moonClosestMaximumDeclination(jd float64, coeffs moonMaxDeclinationCoefficients) DeclinationEvent { if !finite(jd) { return DeclinationEvent{} } cfg := moonMaximumDeclinationSearchConfig(coeffs) centerK := moonMaximumDeclinationOffset(jd, coeffs) center := moonMaximumDeclinationEvent(centerK, coeffs, cfg) var last, next DeclinationEvent if center.JD <= jd { last = center next = moonMaximumDeclinationAround(jd, coeffs, cfg, centerK, 1, false) } else { next = center last = moonMaximumDeclinationAround(jd, coeffs, cfg, centerK, -1, true) } lastDistance := math.Abs(jd - last.JD) nextDistance := math.Abs(next.JD - jd) if lastDistance <= nextDistance { return last } return next } // moonMaximumDeclinationAround 从 centerK 沿 step 方向找第一个满足方向不变量的周期;±3 个周期内没有就返回零事件。 func moonMaximumDeclinationAround(jd float64, coeffs moonMaxDeclinationCoefficients, cfg apsisSearchConfig, centerK, step int, includeCurrent bool) DeclinationEvent { direction := 1 if step < 0 { direction = -1 } for offset := 1; offset <= moonMaxDeclinationSearchSpan; offset++ { event := moonMaximumDeclinationEvent(centerK+step*offset, coeffs, cfg) if moonMaximumDeclinationMatchesDirection(event.JD-jd, direction, includeCurrent) { return event } } return DeclinationEvent{} } func moonMaximumDeclinationSearchConfig(coeffs moonMaxDeclinationCoefficients) apsisSearchConfig { return apsisSearchConfig{ bracketHalfWidth: moonApsisBracketHalfWidth, sampleStep: moonApsisSampleStep, derivativeStep: moonApsisDerivativeStep, toleranceDays: moonApsisToleranceDays, maxIterations: moonApsisMaxIterations, maximize: coeffs.sign > 0, } } // moonMaximumDeclinationOffset 查询时刻落在哪个平均周期(种子多项式的中心序号)。 func moonMaximumDeclinationOffset(jd float64, coeffs moonMaxDeclinationCoefficients) int { return int(math.Round((UTC2TT(jd) - coeffs.JDE0) / moonMaxDeclinationMeanMonthDays)) } func moonMaximumDeclinationMatchesDirection(delta float64, direction int, includeCurrent bool) bool { switch direction { case -1: if includeCurrent { return delta <= 0 } return delta < 0 case 1: if includeCurrent { return delta >= 0 } return delta > 0 default: return true } } func moonMaximumDeclinationEarlier(a, b DeclinationEvent) bool { return a.JD < b.JD } func moonMaximumDeclinationSeedTT(k int, coeffs moonMaxDeclinationCoefficients) float64 { cycle := float64(k) T := cycle / moonMaxDeclinationBaseCycle D := Limit360(coeffs.D0 + 333.0705546*cycle - 0.0004214*T*T + 0.00000011*T*T*T) M := Limit360(coeffs.M0 + 26.9281592*cycle - 0.0000355*T*T - 0.0000001*T*T*T) MP := Limit360(coeffs.MP0 + 356.9562794*cycle + 0.0103066*T*T + 0.00001251*T*T*T) F := Limit360(coeffs.F0 + 1.4467807*cycle - 0.0020690*T*T - 0.00000215*T*T*T) E := 1 - 0.002516*T - 0.0000074*T*T return coeffs.JDE0 + moonMaxDeclinationMeanMonthDays*cycle + 0.000119804*T*T - 0.000000141*T*T*T + coeffs.tc[0]*Cos(F) + coeffs.tc[1]*Sin(MP) + coeffs.tc[2]*Sin(2*F) + coeffs.tc[3]*Sin(2*D-MP) + coeffs.tc[4]*Cos(MP-F) + coeffs.tc[5]*Cos(MP+F) + coeffs.tc[6]*Sin(2*D) + coeffs.tc[7]*Sin(M)*E + coeffs.tc[8]*Cos(3*F) + coeffs.tc[9]*Sin(MP+2*F) + coeffs.tc[10]*Cos(2*D-F) + coeffs.tc[11]*Cos(2*D-MP-F) + coeffs.tc[12]*Cos(2*D-MP+F) + coeffs.tc[13]*Cos(2*D+F) + coeffs.tc[14]*Sin(2*MP) + coeffs.tc[15]*Sin(MP-2*F) + coeffs.tc[16]*Cos(2*MP-F) + coeffs.tc[17]*Sin(MP+3*F) + coeffs.tc[18]*Sin(2*D-M-MP)*E + coeffs.tc[19]*Cos(MP-2*F) + coeffs.tc[20]*Sin(2*(D-MP)) + coeffs.tc[21]*Sin(F) + coeffs.tc[22]*Sin(2*D+MP) + coeffs.tc[23]*Cos(MP+2*F) + coeffs.tc[24]*Sin(2*D-M)*E + coeffs.tc[25]*Sin(MP+F) + coeffs.tc[26]*Sin(M-MP)*E + coeffs.tc[27]*Sin(MP-3*F) + coeffs.tc[28]*Sin(2*MP+F) + coeffs.tc[29]*Cos(2*(D-MP)-F) + coeffs.tc[30]*Sin(3*F) + coeffs.tc[31]*Cos(MP+3*F) + coeffs.tc[32]*Cos(2*MP) + coeffs.tc[33]*Cos(2*D-MP) + coeffs.tc[34]*Cos(2*D+MP+F) + coeffs.tc[35]*Cos(MP) + coeffs.tc[36]*Sin(3*MP+F) + coeffs.tc[37]*Sin(2*D-MP+F) + coeffs.tc[38]*Cos(2*(D-MP)) + coeffs.tc[39]*Cos(D+F) + coeffs.tc[40]*Sin(M+MP)*E + coeffs.tc[41]*Sin(2*(D-F)) + coeffs.tc[42]*Cos(2*MP+F) + coeffs.tc[43]*Cos(3*MP+F) }