- 统一 UT 事件时刻与 TT 查询时刻的边界判断 - 将外行星留点搜索锚定到对应冲日周期 - 修正水星、金星合日、留、大距事件选择 - 统一七大行星视位置计算辅助逻辑 - 增加公开 Last/Next 边界和 JPL/NAOJ 基线回归测试
184 lines
4.1 KiB
Go
184 lines
4.1 KiB
Go
package basic
|
|
|
|
import "math"
|
|
|
|
const innerEventEpsilon = 4.0 / 86400.0
|
|
|
|
func eventQueryTTAsUT(queryTT float64) float64 {
|
|
return TD2UT(queryTT, false)
|
|
}
|
|
|
|
func eventUTQueryTTDelta(eventUT, queryTT float64) float64 {
|
|
return eventUT - eventQueryTTAsUT(queryTT)
|
|
}
|
|
|
|
func eventUTQueryBeforeOrEqual(eventUT, queryTT float64) bool {
|
|
return eventUTQueryTTDelta(eventUT, queryTT) <= innerEventEpsilon
|
|
}
|
|
|
|
func eventUTQueryAfterOrEqual(eventUT, queryTT float64) bool {
|
|
return eventUTQueryTTDelta(eventUT, queryTT) >= -innerEventEpsilon
|
|
}
|
|
|
|
func eventUTNextQueryTT(eventUT float64) float64 {
|
|
return TD2UT(eventUT, true) + 1.0
|
|
}
|
|
|
|
func eventUTLastQueryTT(eventUT float64) float64 {
|
|
return TD2UT(eventUT, true) - 1.0
|
|
}
|
|
|
|
func innerNextCycleOffset(delta, period float64) float64 {
|
|
if delta <= 0 {
|
|
return -delta * period / 360.0
|
|
}
|
|
return (360.0 - delta) * period / 360.0
|
|
}
|
|
|
|
func innerLastCycleOffset(delta, period float64) float64 {
|
|
if delta >= 0 {
|
|
return delta * period / 360.0
|
|
}
|
|
return (360.0 + delta) * period / 360.0
|
|
}
|
|
|
|
func clampFloat64(v, min, max float64) float64 {
|
|
if v < min {
|
|
return min
|
|
}
|
|
if v > max {
|
|
return max
|
|
}
|
|
return v
|
|
}
|
|
|
|
func scanWindowForMinAbs(start, end, step float64, fn func(float64) float64) float64 {
|
|
if end < start {
|
|
start, end = end, start
|
|
}
|
|
if step <= 0 || end == start {
|
|
return start
|
|
}
|
|
bestJD := start
|
|
bestAbs := math.Abs(fn(start))
|
|
for jd := start + step; jd < end; jd += step {
|
|
candidateAbs := math.Abs(fn(jd))
|
|
if candidateAbs < bestAbs {
|
|
bestAbs = candidateAbs
|
|
bestJD = jd
|
|
}
|
|
}
|
|
endAbs := math.Abs(fn(end))
|
|
if endAbs < bestAbs {
|
|
return end
|
|
}
|
|
return bestJD
|
|
}
|
|
|
|
func scanWindowForMax(start, end, step float64, fn func(float64) float64) float64 {
|
|
if end < start {
|
|
start, end = end, start
|
|
}
|
|
if step <= 0 || end == start {
|
|
return start
|
|
}
|
|
bestJD := start
|
|
bestVal := fn(start)
|
|
for jd := start + step; jd < end; jd += step {
|
|
candidateVal := fn(jd)
|
|
if candidateVal > bestVal {
|
|
bestVal = candidateVal
|
|
bestJD = jd
|
|
}
|
|
}
|
|
endVal := fn(end)
|
|
if endVal > bestVal {
|
|
return end
|
|
}
|
|
return bestJD
|
|
}
|
|
|
|
func boundedEventZeroRefine(seed, start, end, halfWindow, step float64, fn func(float64) float64) float64 {
|
|
if end < start {
|
|
start, end = end, start
|
|
}
|
|
if end <= start {
|
|
return start
|
|
}
|
|
maxHalfWindow := (end - start) / 2
|
|
if halfWindow > maxHalfWindow {
|
|
halfWindow = maxHalfWindow
|
|
}
|
|
if halfWindow <= 0 {
|
|
return clampFloat64(seed, start, end)
|
|
}
|
|
seed = clampFloat64(seed, start+halfWindow, end-halfWindow)
|
|
return eventZeroRefine(seed, halfWindow, step, fn)
|
|
}
|
|
|
|
func zeroEventInWindow(start, end, coarseStep, halfWindow, refineStep float64, coarseFn, exactFn func(float64) float64) float64 {
|
|
if end < start {
|
|
start, end = end, start
|
|
}
|
|
if end <= start {
|
|
return start
|
|
}
|
|
rangeDays := end - start
|
|
if coarseStep <= 0 || coarseStep > rangeDays {
|
|
coarseStep = rangeDays / 6.0
|
|
}
|
|
if coarseStep < 0.5 {
|
|
coarseStep = 0.5
|
|
}
|
|
if refineStep <= 0 {
|
|
refineStep = 0.5 / 86400.0
|
|
}
|
|
if halfWindow <= 0 {
|
|
halfWindow = coarseStep
|
|
}
|
|
guess := scanWindowForMinAbs(start, end, coarseStep, coarseFn)
|
|
return boundedEventZeroRefine(guess, start, end, halfWindow, refineStep, exactFn)
|
|
}
|
|
|
|
func maximizeInWindow(start, end, coarseStep float64, coarseFn, exactFn func(float64) float64) float64 {
|
|
if end < start {
|
|
start, end = end, start
|
|
}
|
|
if end <= start {
|
|
return start
|
|
}
|
|
rangeDays := end - start
|
|
if coarseStep <= 0 || coarseStep > rangeDays {
|
|
coarseStep = rangeDays / 6.0
|
|
}
|
|
if coarseStep < 0.5 {
|
|
coarseStep = 0.5
|
|
}
|
|
guess := scanWindowForMax(start, end, coarseStep, coarseFn)
|
|
left := clampFloat64(guess-coarseStep, start, end)
|
|
right := clampFloat64(guess+coarseStep, start, end)
|
|
if right-left <= innerEventEpsilon {
|
|
return guess
|
|
}
|
|
for i := 0; i < 20; i++ {
|
|
third := (right - left) / 3.0
|
|
leftThird := left + third
|
|
rightThird := right - third
|
|
if exactFn(leftThird) <= exactFn(rightThird) {
|
|
left = leftThird
|
|
continue
|
|
}
|
|
right = rightThird
|
|
}
|
|
bestJD := guess
|
|
bestVal := exactFn(bestJD)
|
|
for _, jd := range []float64{left, (left + right) / 2.0, right} {
|
|
candidateVal := exactFn(jd)
|
|
if candidateVal > bestVal {
|
|
bestVal = candidateVal
|
|
bestJD = jd
|
|
}
|
|
}
|
|
return bestJD
|
|
}
|