fix: 修正行星事件边界与留点计算
- 统一 UT 事件时刻与 TT 查询时刻的边界判断 - 将外行星留点搜索锚定到对应冲日周期 - 修正水星、金星合日、留、大距事件选择 - 统一七大行星视位置计算辅助逻辑 - 增加公开 Last/Next 边界和 JPL/NAOJ 基线回归测试
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user