astro/basic/event_refine.go
starainrt 34ff6a36ae
fix: 修正行星事件边界与留点计算
- 统一 UT 事件时刻与 TT 查询时刻的边界判断
- 将外行星留点搜索锚定到对应冲日周期
- 修正水星、金星合日、留、大距事件选择
- 统一七大行星视位置计算辅助逻辑
- 增加公开 Last/Next 边界和 JPL/NAOJ 基线回归测试
2026-05-22 12:24:41 +08:00

98 lines
2.7 KiB
Go

package basic
import "math"
func eventFixedScanRefine(seed, halfWindow, step float64, fn func(float64) float64) float64 {
start := seed - halfWindow
bestJD := start
bestAbs := math.Abs(fn(start))
samples := int(math.Round((2 * halfWindow) / step))
for i := 1; i < samples; i++ {
candidateJD := start + float64(i)*step
candidateAbs := math.Abs(fn(candidateJD))
if candidateAbs < bestAbs {
bestAbs = candidateAbs
bestJD = candidateJD
}
}
return bestJD
}
func eventZeroBracket(leftJD, leftVal, centerJD, centerVal, rightJD, rightVal float64) (float64, float64, float64, float64, bool) {
if leftVal == 0 {
return leftJD, leftJD, leftVal, leftVal, true
}
if centerVal == 0 {
return centerJD, centerJD, centerVal, centerVal, true
}
if rightVal == 0 {
return rightJD, rightJD, rightVal, rightVal, true
}
if leftVal*centerVal < 0 {
return leftJD, centerJD, leftVal, centerVal, true
}
if centerVal*rightVal < 0 {
return centerJD, rightJD, centerVal, rightVal, true
}
if leftVal*rightVal < 0 {
return leftJD, rightJD, leftVal, rightVal, true
}
return 0, 0, 0, 0, false
}
// eventZeroRefine 细化 seed 附近的零点;无可用括号区间时退回固定步长扫描。
func eventZeroRefine(seed, halfWindow, step float64, fn func(float64) float64) float64 {
leftJD := seed - halfWindow
centerJD := seed
rightJD := seed + halfWindow
leftVal := fn(leftJD)
centerVal := fn(centerJD)
rightVal := fn(rightJD)
bestJD := centerJD
bestAbs := math.Abs(centerVal)
if candidateAbs := math.Abs(leftVal); candidateAbs < bestAbs {
bestAbs = candidateAbs
bestJD = leftJD
}
if candidateAbs := math.Abs(rightVal); candidateAbs < bestAbs {
bestAbs = candidateAbs
bestJD = rightJD
}
bracketLeftJD, bracketRightJD, bracketLeftVal, bracketRightVal, ok := eventZeroBracket(leftJD, leftVal, centerJD, centerVal, rightJD, rightVal)
if !ok {
return eventFixedScanRefine(seed, halfWindow, step, fn)
}
if bracketLeftJD == bracketRightJD {
return bracketLeftJD
}
for i := 0; i < 8; i++ {
candidateJD := (bracketLeftJD + bracketRightJD) / 2
if bracketRightVal != bracketLeftVal {
secantJD := bracketRightJD - bracketRightVal*(bracketRightJD-bracketLeftJD)/(bracketRightVal-bracketLeftVal)
if secantJD > bracketLeftJD && secantJD < bracketRightJD {
candidateJD = secantJD
}
}
candidateVal := fn(candidateJD)
candidateAbs := math.Abs(candidateVal)
if candidateAbs < bestAbs {
bestAbs = candidateAbs
bestJD = candidateJD
}
if candidateVal == 0 || math.Abs(bracketRightJD-bracketLeftJD) <= step {
break
}
if bracketLeftVal*candidateVal < 0 {
bracketRightJD = candidateJD
bracketRightVal = candidateVal
continue
}
bracketLeftJD = candidateJD
bracketLeftVal = candidateVal
}
return bestJD
}