248 lines
6.9 KiB
Go
248 lines
6.9 KiB
Go
|
|
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} }
|