Files
astro/planet/internal/semianalytic/apparent.go
T

248 lines
6.9 KiB
Go
Raw Normal View History

package semianalytic
import (
"errors"
"math"
"sync"
"b612.me/astro/internal/ephem"
"b612.me/astro/planet/internal/semianalytic/lunar"
"b612.me/astro/planet/internal/semianalytic/planets"
)
const (
cKMPerDay = 299792.458 * 86400
// 太阳史瓦西半径 2GM☉/c²,AU。
srsAU = 1.97412574336e-8
)
var errInvalidObserver = errors.New("semianalytic: non-finite observer state")
type observer struct {
jd float64
p, v [3]float64 // 地球日心位置 km、相对质心速度 km/日
sunVel [3]float64 // 太阳相对质心速度 km/日
}
// 同一时刻常连续求多个天体,观测者状态缓存最近一次。
var (
observerMu sync.RWMutex
observerCache observer
observerSet bool
)
func observerAt(jd float64) (observer, error) {
observerMu.RLock()
c, set := observerCache, observerSet
observerMu.RUnlock()
if set && c.jd == jd {
return c, nil
}
e, err := helio(ephem.Earth, jd)
if err != nil {
return observer{}, err
}
// 太阳质心速度只进光行差与光行时修正:快速档、只计四颗巨行星,内行星贡献 < 0.1 mas。
var sv [3]float64
_, total := planets.GM(1)
for id := 1; id <= 4; id++ {
gm, _ := planets.GM(id)
total += gm
}
for id := 5; id <= 8; id++ {
gm, _ := planets.GM(id)
_, v, err := planets.Fast().HeliocentricState(id, jd)
if err != nil {
return observer{}, err
}
for i := range sv {
sv[i] -= gm * v[i]
}
total += gm
}
o := observer{jd: jd, p: e.p}
for i := range sv {
sv[i] /= total
o.sunVel[i] = sv[i]
o.v[i] = e.v[i] + sv[i]
// 坏表读出的非有限速度不得进缓存,否则同一时刻会一直返回 NaN 方向而 err 为 nil。
if !finite(o.v[i]) || !finite(o.p[i]) {
return observer{}, errInvalidObserver
}
}
observerMu.Lock()
observerCache, observerSet = o, true
observerMu.Unlock()
return o, nil
}
// targetHelio 目标日心位置;fast 用快速档,只作光行时初值与几何距离。
func targetHelio(b ephem.Body, jd float64, fast bool) ([3]float64, error) {
switch {
case b == ephem.Sun:
return [3]float64{}, nil
case b == ephem.Moon:
s, err := helio(ephem.Moon, jd)
return s.p, err
case b >= ephem.Mercury && b <= ephem.Neptune && b != ephem.EMB:
if fast {
return planets.Fast().Heliocentric(int(b), jd)
}
return planets.Default().Heliocentric(int(b), jd)
}
return [3]float64{}, ephem.ErrUnsupportedBody
}
type apparentEntry struct {
jd float64
dir [3]float64
dist, tau float64
set bool
}
var (
apparentMu sync.RWMutex
apparentCache [10]apparentEntry
)
func apparentStore(slot int, e apparentEntry) {
e.set = true
apparentMu.Lock()
apparentCache[slot] = e
apparentMu.Unlock()
}
// cacheSlot 天体对应的缓存槽;-1 表示不支持该天体。
func cacheSlot(b ephem.Body) int {
switch {
case b == ephem.Sun:
return 0
case b == ephem.Moon:
return 9
case b >= ephem.Mercury && b <= ephem.Neptune && b != ephem.EMB:
return int(b)
}
return -1
}
// Apparent 地心视方向(ICRF 单位矢量)、几何地心距 km、光行时 日 / geocentric apparent direction.
func Apparent(target ephem.Body, jd float64) (dir [3]float64, distKM, tauDays float64, err error) {
slot := cacheSlot(target)
if slot < 0 {
return dir, 0, 0, ephem.ErrUnsupportedBody
}
if !finite(jd) {
return dir, 0, 0, ephem.ErrOutOfRange
}
apparentMu.RLock()
c := apparentCache[slot]
apparentMu.RUnlock()
if c.set && c.jd == jd {
return c.dir, c.dist, c.tau, nil
}
o, err := observerAt(jd)
if err != nil {
return dir, 0, 0, err
}
if target == ephem.Moon {
e, err := apparentMoon(jd, o)
if err != nil {
return dir, 0, 0, err
}
apparentStore(slot, e)
return e.dir, e.dist, e.tau, nil
}
now, err := targetHelio(target, jd, true)
if err != nil {
return dir, 0, 0, err
}
g := vsub(now, o.p)
dist := vlen(g)
tau := dist / cKMPerDay
// 迭代光行时,每次误差缩小约 v/c 倍:先用快速档收敛 τ,末次用全表;太阳在 τ 内相对质心的位移按线性计入。
var r [3]float64
for k := 0; k < 2; k++ {
if r, err = targetHelio(target, jd-tau, k == 0); err != nil {
return dir, 0, 0, err
}
for i := range g {
g[i] = r[i] - o.p[i] - o.sunVel[i]*tau
}
tau = vlen(g) / cKMPerDay
}
q := vscale(g, 1/vlen(g))
if target != ephem.Sun && target != ephem.Moon {
q = deflect(q, r, o.p)
}
q = aberrate(q, o.v)
e := apparentEntry{jd: jd, dir: toICRF(q), dist: dist, tau: tau}
apparentStore(slot, e)
return e.dir, e.dist, e.tau, nil
}
// apparentMoon 月球地心视方向。光行时仅约 1.3 s,若像行星那样在 jd 与 jd−τ 各求一次地月质心
// 日心位置(~1.5e8 km)再与地球相减,远历元表求值的舍入噪声(2981 年约 2e-4 km)会原样进入
// 地心矢量,超过下游牛顿迭代的残差容差。这里全程用地心量:
// 月球日心(jd−τ) − 地球日心(jd) = EMB(jd−τ) − EMB(jd) + f·m(jd−τ) + m(jd)/(1+μ),
// 其中 EMB 在 τ 内的位移按 jd 时刻速度线性外推,二阶项 ½aτ² < 1e-5 km。
// 月球地心位置同样按 jd 时刻解析速度外推(½aτ² ≈ 2e-6 km):直接求 m(jd−τ) 时,jd−τ 的舍入
// (~4.7e-10 日)乘月球地心速度(~8.8e4 km/日)给出 ~1e-5 km 的逐点抖动,有限差分导数会把它放大。
func apparentMoon(jd float64, o observer) (apparentEntry, error) {
emb, err := planetHelio(int(ephem.EMB), jd)
if err != nil {
return apparentEntry{}, err
}
now, vel := lunar.Default().PositionVelocityJ2000(jd)
dist := vlen(now)
tau := dist / cKMPerDay
f := emrat / (1 + emrat)
g := now
for k := 0; k < 2; k++ {
for i := range g {
g[i] = f*(now[i]-vel[i]*tau) + now[i]/(1+emrat) - (emb.v[i]+o.sunVel[i])*tau
}
tau = vlen(g) / cKMPerDay
}
q := aberrate(vscale(g, 1/vlen(g)), o.v)
return apparentEntry{jd: jd, dir: toICRF(q), dist: dist, tau: tau}, nil
}
// deflect 太阳引力偏折;p 目标方向,t 目标日心位置,e 地球日心位置。
func deflect(p, t, e [3]float64) [3]float64 {
em := vlen(e) / auKM
eu := vscale(e, 1/vlen(e))
q := vscale(t, 1/vlen(t))
qpe := vdot(q, eu) + 1
if qpe < 1e-9 {
return p
}
w := srsAU / em / qpe
pe, pq := vdot(p, eu), vdot(p, q)
// p × (e × q) = e(p·q) − q(p·e)
for i := range p {
p[i] += w * (eu[i]*pq - q[i]*pe)
}
return p
}
// aberrate 相对论光行差;v 观测者相对质心速度 km/日。
func aberrate(p, v [3]float64) [3]float64 {
b := vscale(v, 1/cKMPerDay)
bm := 1 / math.Sqrt(1-vdot(b, b))
pdv := vdot(p, b)
w1 := 1 + pdv/(1+1/bm)
w2 := 1 / (1 + pdv)
var r [3]float64
for i := range p {
r[i] = (p[i]/bm + w1*b[i]) * w2
}
return vscale(r, 1/vlen(r))
}
func vdot(a, b [3]float64) float64 { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2] }
func vlen(a [3]float64) float64 { return math.Sqrt(vdot(a, a)) }
func vsub(a, b [3]float64) [3]float64 {
return [3]float64{a[0] - b[0], a[1] - b[1], a[2] - b[2]}
}
func vscale(a [3]float64, k float64) [3]float64 { return [3]float64{a[0] * k, a[1] * k, a[2] * k} }