611 lines
16 KiB
Go
611 lines
16 KiB
Go
|
|
// Package planets 行星半解析级数运行时(PLN1/PLN2/PLN3 表,日心,J2000 平黄道)。
|
|||
|
|
package planets
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"encoding/binary"
|
|||
|
|
"errors"
|
|||
|
|
"math"
|
|||
|
|
"sync"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
const (
|
|||
|
|
j2000 = 2451545.0
|
|||
|
|
millennium = 365250.0
|
|||
|
|
blendDays = 36525.0
|
|||
|
|
auKM = 149597870.691
|
|||
|
|
tMid = -2.0
|
|||
|
|
tHalf = 10.0
|
|||
|
|
emrat = 81.3005682214972
|
|||
|
|
gmSol = 2.9591220836841438269e-04
|
|||
|
|
maxDeg = 24
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// Body 编号 / body id;3 为地月质心。
|
|||
|
|
const (
|
|||
|
|
Mercury = 1
|
|||
|
|
Venus = 2
|
|||
|
|
EMB = 3
|
|||
|
|
Mars = 4
|
|||
|
|
Jupiter = 5
|
|||
|
|
Saturn = 6
|
|||
|
|
Uranus = 7
|
|||
|
|
Neptune = 8
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
var ci0 = [17]float64{4.402608631669, 3.176134461576, 1.753470369433, 6.203500014141, 4.091360003050,
|
|||
|
|
1.713740719173, 5.598641292287, 2.805136360408, 2.326989734620, 0.5995461070350,
|
|||
|
|
0.8740185101070, 5.481225395663, 5.311897933164, 0.0, 5.19846640063,
|
|||
|
|
1.62790513602, 2.35555563875}
|
|||
|
|
|
|||
|
|
var ci1 = [17]float64{26087.90314068555, 10213.28554743445, 6283.075850353215, 3340.612434145457,
|
|||
|
|
1731.170452721855, 1704.450855027201, 1428.948917844273, 1364.756513629990,
|
|||
|
|
1361.923207632842, 529.6909615623250, 213.2990861084880, 74.78165903077800,
|
|||
|
|
38.13297222612500, 0.3595362285049309, 77713.7714481804, 84334.6615717837,
|
|||
|
|
83286.9142477147}
|
|||
|
|
|
|||
|
|
var gmp = [8]float64{4.9125474514508118699e-11, 7.2434524861627027000e-10, 8.9970116036316091182e-10,
|
|||
|
|
9.5495351057792580598e-11, 2.8253458420837780000e-07, 8.4597151856806587398e-08,
|
|||
|
|
1.2920249167819693900e-08, 1.5243589007842762800e-08}
|
|||
|
|
|
|||
|
|
// topDMu 必须逐步按 float64 舍入(与 Python 参考求值一致),不能写成常量表达式。
|
|||
|
|
var topDMu = func() float64 {
|
|||
|
|
f0, f1, d := 529.6909622785881, 213.2990811942489, 880.0
|
|||
|
|
return (f0 - f1) / d
|
|||
|
|
}()
|
|||
|
|
|
|||
|
|
// skyToVSOP skyfield ecliptic_J2000 → VSOP2013 动力学黄道;求值时用其转置。
|
|||
|
|
var skyToVSOP = func() [3][3]float64 {
|
|||
|
|
as := 180 / math.Pi * 3600
|
|||
|
|
return mul(transpose(mul(rz(-0.05188/as), rx(84381.41136/as))), rx(84381.448/as))
|
|||
|
|
}()
|
|||
|
|
|
|||
|
|
// element 的项按包络阶降序排列,第 d 阶只覆盖前 cnt[d] 项。
|
|||
|
|
type element struct {
|
|||
|
|
secular []float64
|
|||
|
|
arg []int32
|
|||
|
|
cnt []int
|
|||
|
|
c, s [][]float64
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const (
|
|||
|
|
maxKeys = 8192
|
|||
|
|
maxTab = 512
|
|||
|
|
topBits = 6
|
|||
|
|
// 超出此值的幅角乘数必然撑破幂表,先在 int64 下拒绝,保证转 int 不溢出。
|
|||
|
|
maxKeyAbs = maxTab << (2 * topBits)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
var errBadKey = errors.New("planets: argument multiplier out of range")
|
|||
|
|
|
|||
|
|
// factor 幂表槽位;neg 表示取共轭。
|
|||
|
|
type factor struct {
|
|||
|
|
slot int32
|
|||
|
|
neg bool
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type body struct {
|
|||
|
|
top bool
|
|||
|
|
f, l0 float64
|
|||
|
|
rgm float64
|
|||
|
|
// 幂表:VSOP 每个基本幅角一段,TOP 每个 6 位数字层一段
|
|||
|
|
tabMax [17]int
|
|||
|
|
tabOff [17]int
|
|||
|
|
keyOff []int32
|
|||
|
|
factors []factor
|
|||
|
|
keyNeg []bool
|
|||
|
|
el [6]element
|
|||
|
|
// legDeg 长期项与包络用到的最高 Legendre 阶。
|
|||
|
|
legDeg int
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Model 已解析的 PLN1 表,只读、可并发求值 / parsed PLN1 table, safe for concurrent use.
|
|||
|
|
type Model struct {
|
|||
|
|
bodies [9]*body
|
|||
|
|
modernBodies [9]*body
|
|||
|
|
modernStart float64
|
|||
|
|
modernEnd float64
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type reader struct {
|
|||
|
|
b []byte
|
|||
|
|
p int
|
|||
|
|
err error
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (r *reader) take(n int) []byte {
|
|||
|
|
if r.err != nil || r.p+n > len(r.b) {
|
|||
|
|
r.err = errors.New("planets: truncated table")
|
|||
|
|
return make([]byte, n)
|
|||
|
|
}
|
|||
|
|
s := r.b[r.p : r.p+n]
|
|||
|
|
r.p += n
|
|||
|
|
return s
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (r *reader) u8() uint8 { return r.take(1)[0] }
|
|||
|
|
func (r *reader) f64() float64 { return math.Float64frombits(binary.LittleEndian.Uint64(r.take(8))) }
|
|||
|
|
func (r *reader) f32() float32 { return math.Float32frombits(binary.LittleEndian.Uint32(r.take(4))) }
|
|||
|
|
|
|||
|
|
func (r *reader) uvar() uint64 {
|
|||
|
|
n, shift := uint64(0), uint(0)
|
|||
|
|
for {
|
|||
|
|
c := r.u8()
|
|||
|
|
n |= uint64(c&0x7F) << shift
|
|||
|
|
shift += 7
|
|||
|
|
if c < 0x80 || r.err != nil || shift > 56 {
|
|||
|
|
return n
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (r *reader) zvar() int64 {
|
|||
|
|
z := r.uvar()
|
|||
|
|
if z%2 == 0 {
|
|||
|
|
return int64(z >> 1)
|
|||
|
|
}
|
|||
|
|
return -int64(z>>1) - 1
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Parse 解析 PLN1 表 / parse a PLN1 table.
|
|||
|
|
func Parse(b []byte) (*Model, error) {
|
|||
|
|
bodies, err := parseBodies(b)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
return &Model{bodies: bodies}, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// WithModernSegment 为长时段表叠加现代段表,[jdStart, jdEnd] 内优先使用 / add a modern-era segment used inside [jdStart, jdEnd].
|
|||
|
|
func (m *Model) WithModernSegment(b []byte, jdStart, jdEnd float64) error {
|
|||
|
|
bodies, err := parseBodies(b)
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
m.modernBodies, m.modernStart, m.modernEnd = bodies, jdStart, jdEnd
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func parseBodies(b []byte) (bodies [9]*body, err error) {
|
|||
|
|
r := &reader{b: b}
|
|||
|
|
magic := string(r.take(4))
|
|||
|
|
if magic != "PLN1" && magic != "PLN2" && magic != "PLN3" {
|
|||
|
|
return bodies, errors.New("planets: bad magic")
|
|||
|
|
}
|
|||
|
|
quant, packed := magic == "PLN2", magic == "PLN3"
|
|||
|
|
for n := int(r.u8()); n > 0 && r.err == nil; n-- {
|
|||
|
|
id := int(r.u8())
|
|||
|
|
if id < 1 || id > 8 {
|
|||
|
|
return bodies, errors.New("planets: bad body id")
|
|||
|
|
}
|
|||
|
|
bd := &body{top: r.u8() != 0}
|
|||
|
|
secDeg := int(r.u8())
|
|||
|
|
bd.f, bd.l0 = r.f64(), r.f64()
|
|||
|
|
bd.rgm = math.Sqrt(gmp[id-1] + gmSol)
|
|||
|
|
nk := r.uvar()
|
|||
|
|
if nk > maxKeys {
|
|||
|
|
return bodies, errors.New("planets: too many arguments")
|
|||
|
|
}
|
|||
|
|
if err := readKeys(r, bd, int(nk)); err != nil {
|
|||
|
|
return bodies, err
|
|||
|
|
}
|
|||
|
|
bd.legDeg = secDeg
|
|||
|
|
sortedArgs := packed && r.u8() != 0
|
|||
|
|
for v := range bd.el {
|
|||
|
|
var err error
|
|||
|
|
if packed {
|
|||
|
|
err = readElement3(r, &bd.el[v], secDeg, int(nk), sortedArgs)
|
|||
|
|
} else {
|
|||
|
|
err = readElement(r, &bd.el[v], secDeg, int(nk), quant)
|
|||
|
|
}
|
|||
|
|
if err != nil {
|
|||
|
|
return bodies, err
|
|||
|
|
}
|
|||
|
|
if d := len(bd.el[v].cnt) - 1; d > bd.legDeg {
|
|||
|
|
bd.legDeg = d
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
bodies[id] = bd
|
|||
|
|
}
|
|||
|
|
if r.err != nil {
|
|||
|
|
return bodies, r.err
|
|||
|
|
}
|
|||
|
|
if r.p != len(b) {
|
|||
|
|
return bodies, errors.New("planets: trailing bytes")
|
|||
|
|
}
|
|||
|
|
return bodies, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// readKeys 把整数幅角拆成幂表槽位:VSOP 按基本幅角升序,TOP 按 6 位数字层由低到高。
|
|||
|
|
func readKeys(r *reader, bd *body, nk int) error {
|
|||
|
|
type raw struct{ fund, k int }
|
|||
|
|
keys := make([][]raw, nk)
|
|||
|
|
bd.keyNeg = make([]bool, nk)
|
|||
|
|
for j := 0; j < nk && r.err == nil; j++ {
|
|||
|
|
if bd.top {
|
|||
|
|
k := r.zvar()
|
|||
|
|
if k > maxKeyAbs || k < -maxKeyAbs {
|
|||
|
|
return errBadKey
|
|||
|
|
}
|
|||
|
|
a := int(k)
|
|||
|
|
if a < 0 {
|
|||
|
|
a, bd.keyNeg[j] = -a, true
|
|||
|
|
}
|
|||
|
|
for lvl := 0; lvl < 3; lvl++ {
|
|||
|
|
d := a >> uint(topBits*lvl)
|
|||
|
|
if lvl < 2 {
|
|||
|
|
d &= 1<<topBits - 1
|
|||
|
|
}
|
|||
|
|
keys[j] = append(keys[j], raw{lvl, d})
|
|||
|
|
}
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
mb := r.take(3)
|
|||
|
|
mask := int(mb[0]) | int(mb[1])<<8 | int(mb[2])<<16
|
|||
|
|
for i := 0; i < 17; i++ {
|
|||
|
|
if mask>>uint(i)&1 != 0 {
|
|||
|
|
k := r.zvar()
|
|||
|
|
if k > maxKeyAbs || k < -maxKeyAbs {
|
|||
|
|
return errBadKey
|
|||
|
|
}
|
|||
|
|
keys[j] = append(keys[j], raw{i, int(k)})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if r.err != nil {
|
|||
|
|
return r.err
|
|||
|
|
}
|
|||
|
|
for _, ks := range keys {
|
|||
|
|
for _, f := range ks {
|
|||
|
|
a := f.k
|
|||
|
|
if a < 0 {
|
|||
|
|
a = -a
|
|||
|
|
}
|
|||
|
|
if a > bd.tabMax[f.fund] {
|
|||
|
|
bd.tabMax[f.fund] = a
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
n := 0
|
|||
|
|
for i := range bd.tabMax {
|
|||
|
|
bd.tabOff[i] = n
|
|||
|
|
if bd.tabMax[i] > 0 || (bd.top && i < 3) {
|
|||
|
|
n += bd.tabMax[i] + 1
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if n > maxTab {
|
|||
|
|
return errors.New("planets: power table too large")
|
|||
|
|
}
|
|||
|
|
bd.keyOff = make([]int32, nk+1)
|
|||
|
|
for j, ks := range keys {
|
|||
|
|
for _, f := range ks {
|
|||
|
|
a, neg := f.k, false
|
|||
|
|
if a < 0 {
|
|||
|
|
a, neg = -a, true
|
|||
|
|
}
|
|||
|
|
if !bd.top && a == 0 {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
bd.factors = append(bd.factors, factor{int32(bd.tabOff[f.fund] + a), neg})
|
|||
|
|
}
|
|||
|
|
bd.keyOff[j+1] = int32(len(bd.factors))
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// readElement 把文件里按包络阶分块的列序重排为逐项连续;quant 为 PLN2 整数量化系数。
|
|||
|
|
func readElement(r *reader, e *element, secDeg, nk int, quant bool) error {
|
|||
|
|
if secDeg > maxDeg {
|
|||
|
|
return errors.New("planets: secular degree too high")
|
|||
|
|
}
|
|||
|
|
n64 := r.uvar()
|
|||
|
|
if n64 > uint64(nk) {
|
|||
|
|
return errors.New("planets: too many terms")
|
|||
|
|
}
|
|||
|
|
nint := int(n64)
|
|||
|
|
e.secular = make([]float64, secDeg+1)
|
|||
|
|
for i := range e.secular {
|
|||
|
|
e.secular[i] = r.f64()
|
|||
|
|
}
|
|||
|
|
args, env := make([]int32, nint), make([]int, nint)
|
|||
|
|
top := 0
|
|||
|
|
for j := 0; j < nint; j++ {
|
|||
|
|
a := r.uvar()
|
|||
|
|
if a >= uint64(nk) {
|
|||
|
|
return errors.New("planets: argument index out of range")
|
|||
|
|
}
|
|||
|
|
args[j], env[j] = int32(a), int(r.u8())
|
|||
|
|
if env[j] > maxDeg {
|
|||
|
|
return errors.New("planets: envelope degree too high")
|
|||
|
|
}
|
|||
|
|
if env[j] > top {
|
|||
|
|
top = env[j]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 按包络阶降序的稳定排序:pos[j] 为文件第 j 项的新位置
|
|||
|
|
pos := make([]int, nint)
|
|||
|
|
e.arg = make([]int32, nint)
|
|||
|
|
k := 0
|
|||
|
|
for d := top; d >= 0; d-- {
|
|||
|
|
for j := 0; j < nint; j++ {
|
|||
|
|
if env[j] == d {
|
|||
|
|
pos[j], e.arg[k] = k, args[j]
|
|||
|
|
k++
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
coef := func() float64 { return float64(r.f32()) }
|
|||
|
|
if quant {
|
|||
|
|
step := r.f64()
|
|||
|
|
coef = func() float64 { return float64(r.zvar()) * step }
|
|||
|
|
}
|
|||
|
|
if nint == 0 {
|
|||
|
|
return r.err
|
|||
|
|
}
|
|||
|
|
e.cnt = make([]int, top+1)
|
|||
|
|
e.c, e.s = make([][]float64, top+1), make([][]float64, top+1)
|
|||
|
|
for d := 0; d <= top; d++ {
|
|||
|
|
for j := 0; j < nint; j++ {
|
|||
|
|
if env[j] >= d {
|
|||
|
|
e.cnt[d]++
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
e.c[d], e.s[d] = make([]float64, e.cnt[d]), make([]float64, e.cnt[d])
|
|||
|
|
for _, dst := range [][]float64{e.c[d], e.s[d]} {
|
|||
|
|
for j := 0; j < nint; j++ {
|
|||
|
|
if env[j] >= d {
|
|||
|
|
dst[pos[j]] = coef()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return r.err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Heliocentric 日心位置,km,J2000 平黄道 / heliocentric position, km, mean ecliptic J2000.
|
|||
|
|
func (m *Model) Heliocentric(id int, jdTDB float64) ([3]float64, error) {
|
|||
|
|
pos, _, err := m.state(id, jdTDB, false)
|
|||
|
|
return pos, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// HeliocentricState 日心位置(km)与密切速度(km/日),J2000 平黄道 / heliocentric position and osculating velocity.
|
|||
|
|
func (m *Model) HeliocentricState(id int, jdTDB float64) (pos, vel [3]float64, err error) {
|
|||
|
|
return m.state(id, jdTDB, true)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// state wantVel 为 false 时 vel 不计算、恒为零。
|
|||
|
|
func (m *Model) state(id int, jdTDB float64, wantVel bool) (pos, vel [3]float64, err error) {
|
|||
|
|
if id < 1 || id > 8 {
|
|||
|
|
return pos, vel, errors.New("planets: body not in table")
|
|||
|
|
}
|
|||
|
|
bd := m.bodies[id]
|
|||
|
|
if bd == nil {
|
|||
|
|
return pos, vel, errors.New("planets: body not in table")
|
|||
|
|
}
|
|||
|
|
t := (jdTDB - j2000) / millennium
|
|||
|
|
md := m.modernBodies[id]
|
|||
|
|
sky := func(p, v [3]float64) ([3]float64, [3]float64, error) {
|
|||
|
|
if !wantVel {
|
|||
|
|
return toSky(p), v, nil
|
|||
|
|
}
|
|||
|
|
return toSky(p), toSky(v), nil
|
|||
|
|
}
|
|||
|
|
if md == nil || jdTDB <= m.modernStart || jdTDB >= m.modernEnd {
|
|||
|
|
p, v := bd.ellxyz(bd.elements(t), wantVel)
|
|||
|
|
return sky(p, v)
|
|||
|
|
}
|
|||
|
|
w, dw := m.modernWeight(jdTDB)
|
|||
|
|
pm, vm := md.ellxyz(md.elements(t), wantVel)
|
|||
|
|
if w == 1 {
|
|||
|
|
return sky(pm, vm)
|
|||
|
|
}
|
|||
|
|
pl, vl := bd.ellxyz(bd.elements(t), wantVel)
|
|||
|
|
for i := range pos {
|
|||
|
|
pos[i] = pl[i] + w*(pm[i]-pl[i])
|
|||
|
|
if wantVel {
|
|||
|
|
vel[i] = vl[i] + w*(vm[i]-vl[i]) + dw*(pm[i]-pl[i])
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return sky(pos, vel)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// modernWeight 现代段权重及其对 JD 的导数;两端各 blendDays 内用 smoothstep 过渡,位置与速度均连续。
|
|||
|
|
func (m *Model) modernWeight(jd float64) (w, dw float64) {
|
|||
|
|
u, sign := (jd-m.modernStart)/blendDays, 1.0
|
|||
|
|
if e := (m.modernEnd - jd) / blendDays; e < u {
|
|||
|
|
u, sign = e, -1
|
|||
|
|
}
|
|||
|
|
if u >= 1 {
|
|||
|
|
return 1, 0
|
|||
|
|
}
|
|||
|
|
return u * u * (3 - 2*u), sign * 6 * u * (1 - u) / blendDays
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// toSky 动力学黄道 au → skyfield ecliptic_J2000 km。
|
|||
|
|
func toSky(p [3]float64) (out [3]float64) {
|
|||
|
|
for i := range out {
|
|||
|
|
out[i] = (skyToVSOP[0][i]*p[0] + skyToVSOP[1][i]*p[1] + skyToVSOP[2][i]*p[2]) * auKM
|
|||
|
|
}
|
|||
|
|
return out
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// cmul 显式转换阻止编译器融合乘加,保证与 Python 参考求值逐位一致。
|
|||
|
|
func cmul(ar, ai, br, bi float64) (float64, float64) {
|
|||
|
|
return float64(ar*br) - float64(ai*bi), float64(ar*bi) + float64(ai*br)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// phasors 填充每个幅角的 (cos, sin)。
|
|||
|
|
// tc、ts 为倍角表工作区,只读取本次写入的槽位,复用时不必清零。
|
|||
|
|
func (bd *body) phasors(t float64, pc, ps, tc, ts []float64) {
|
|||
|
|
for i := range bd.tabMax {
|
|||
|
|
if bd.tabMax[i] == 0 && !(bd.top && i < 3) {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
var s, c float64
|
|||
|
|
if bd.top {
|
|||
|
|
s, c = math.Sincos(topDMu * t * float64(int(1)<<uint(topBits*i)))
|
|||
|
|
} else {
|
|||
|
|
s, c = math.Sincos(ci0[i] + ci1[i]*t)
|
|||
|
|
}
|
|||
|
|
o := bd.tabOff[i]
|
|||
|
|
tc[o], ts[o] = 1, 0
|
|||
|
|
for j := 1; j <= bd.tabMax[i]; j++ {
|
|||
|
|
tc[o+j], ts[o+j] = cmul(tc[o+j-1], ts[o+j-1], c, s)
|
|||
|
|
}
|
|||
|
|
if bd.top && i == 2 {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
for j := 0; j+1 < len(bd.keyOff); j++ {
|
|||
|
|
fs := bd.factors[bd.keyOff[j]:bd.keyOff[j+1]]
|
|||
|
|
r, im := 1.0, 0.0
|
|||
|
|
if len(fs) > 0 {
|
|||
|
|
r, im = tc[fs[0].slot], ts[fs[0].slot]
|
|||
|
|
if fs[0].neg {
|
|||
|
|
im = -im
|
|||
|
|
}
|
|||
|
|
fs = fs[1:]
|
|||
|
|
}
|
|||
|
|
for _, f := range fs {
|
|||
|
|
br, bi := tc[f.slot], ts[f.slot]
|
|||
|
|
if f.neg {
|
|||
|
|
bi = -bi
|
|||
|
|
}
|
|||
|
|
r, im = cmul(r, im, br, bi)
|
|||
|
|
}
|
|||
|
|
if bd.keyNeg[j] {
|
|||
|
|
im = -im
|
|||
|
|
}
|
|||
|
|
pc[j], ps[j] = r, im
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type scratch struct{ buf []float64 }
|
|||
|
|
|
|||
|
|
var scratchPool = sync.Pool{New: func() interface{} { return new(scratch) }}
|
|||
|
|
|
|||
|
|
// elements t 为 TDB 千年(J2000 起算)。
|
|||
|
|
func (bd *body) elements(t float64) [6]float64 {
|
|||
|
|
x := (t - tMid) / tHalf
|
|||
|
|
var leg [maxDeg + 1]float64
|
|||
|
|
leg[0], leg[1] = 1, x
|
|||
|
|
for k := 2; k <= bd.legDeg; k++ {
|
|||
|
|
leg[k] = (leg[k-1]*x*float64(2*k-1) - leg[k-2]*float64(k-1)) / float64(k)
|
|||
|
|
}
|
|||
|
|
nk := len(bd.keyNeg)
|
|||
|
|
sc := scratchPool.Get().(*scratch)
|
|||
|
|
defer scratchPool.Put(sc)
|
|||
|
|
if cap(sc.buf) < 2*nk+2*maxTab {
|
|||
|
|
sc.buf = make([]float64, 2*nk+2*maxTab)
|
|||
|
|
}
|
|||
|
|
// 各段在读取前都会被完整写入,复用时不必清零。
|
|||
|
|
buf := sc.buf[:2*nk+2*maxTab]
|
|||
|
|
pc, ps := buf[:nk], buf[nk:2*nk]
|
|||
|
|
bd.phasors(t, pc, ps, buf[2*nk:2*nk+maxTab], buf[2*nk+maxTab:])
|
|||
|
|
var el [6]float64
|
|||
|
|
for v := range bd.el {
|
|||
|
|
e := &bd.el[v]
|
|||
|
|
sum := 0.0
|
|||
|
|
for i, c := range e.secular {
|
|||
|
|
sum += c * leg[i]
|
|||
|
|
}
|
|||
|
|
// 逐阶做点积再乘 Legendre 值,避免逐项累加包络系数的读改写。
|
|||
|
|
for d := range e.cnt {
|
|||
|
|
cd, sd := e.c[d], e.s[d]
|
|||
|
|
arg := e.arg[:len(cd)]
|
|||
|
|
s0, s1 := 0.0, 0.0
|
|||
|
|
for i, a := range arg {
|
|||
|
|
s0 += cd[i] * pc[a]
|
|||
|
|
s1 += sd[i] * ps[a]
|
|||
|
|
}
|
|||
|
|
sum += leg[d] * (s0 + s1)
|
|||
|
|
}
|
|||
|
|
el[v] = sum
|
|||
|
|
}
|
|||
|
|
// 先归约平经度再加周期和,与 Python 参考求值一致。
|
|||
|
|
lm := math.Mod(bd.l0+bd.f*t, 2*math.Pi)
|
|||
|
|
if lm < 0 {
|
|||
|
|
lm += 2 * math.Pi
|
|||
|
|
}
|
|||
|
|
el[1] += lm
|
|||
|
|
return el
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// EarthFromEMB 由地月质心与地心月球合成物理地心 / Earth centre from EMB and geocentric Moon.
|
|||
|
|
func EarthFromEMB(emb, moon [3]float64) [3]float64 {
|
|||
|
|
k := 1 / (1 + emrat)
|
|||
|
|
return [3]float64{emb[0] - moon[0]*k, emb[1] - moon[1]*k, emb[2] - moon[2]*k}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ellxyz VSOP2013 椭圆元件 (a,l,k,h,q,p) → 动力学黄道位置(au)与速度(au/日)。
|
|||
|
|
func (bd *body) ellxyz(el [6]float64, wantVel bool) (pos, vel [3]float64) {
|
|||
|
|
a, l, k, h, q, p := el[0], el[1], el[2], el[3], el[4], el[5]
|
|||
|
|
fi := math.Sqrt(1 - k*k - h*h)
|
|||
|
|
ki := math.Sqrt(1 - q*q - p*p)
|
|||
|
|
u := 1 / (1 + fi)
|
|||
|
|
ex := math.Hypot(k, h)
|
|||
|
|
gl := math.Mod(l, 2*math.Pi)
|
|||
|
|
if gl < 0 {
|
|||
|
|
gl += 2 * math.Pi
|
|||
|
|
}
|
|||
|
|
gm := gl - math.Atan2(h, k)
|
|||
|
|
e := gl + (ex-0.125*ex*ex*ex)*math.Sin(gm) + 0.5*ex*ex*math.Sin(2*gm) + 0.375*ex*ex*ex*math.Sin(3*gm)
|
|||
|
|
var zs, zc float64
|
|||
|
|
for it := 0; it < 20; it++ {
|
|||
|
|
zs, zc = math.Sincos(e)
|
|||
|
|
z3r, z3i := k*zc+h*zs, k*zs-h*zc
|
|||
|
|
dl := gl - e + z3i
|
|||
|
|
e += dl / (1 - z3r)
|
|||
|
|
if math.Abs(dl) < 1e-15 {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
zs, zc = math.Sincos(e)
|
|||
|
|
z3r, z3i := k*zc+h*zs, k*zs-h*zc
|
|||
|
|
rsa := 1 - z3r
|
|||
|
|
z1r, z1i := u*k*z3i, u*h*z3i
|
|||
|
|
z2r, z2i := z1i, -z1r
|
|||
|
|
xcw := (-k + zc + z2r) / rsa
|
|||
|
|
xsw := (-h + zs + z2i) / rsa
|
|||
|
|
xm := p*xcw - q*xsw
|
|||
|
|
xr := a * rsa
|
|||
|
|
pos = [3]float64{xr * (xcw - 2*p*xm), xr * (xsw + 2*q*xm), -2 * xr * ki * xm}
|
|||
|
|
if !wantVel {
|
|||
|
|
return pos, vel
|
|||
|
|
}
|
|||
|
|
xms := a * (h + xsw) / fi
|
|||
|
|
xmc := a * (k + xcw) / fi
|
|||
|
|
xn := bd.rgm / math.Pow(a, 1.5)
|
|||
|
|
vel = [3]float64{xn * ((2*p*p-1)*xms + 2*p*q*xmc), xn * ((1-2*q*q)*xmc - 2*p*q*xms), 2 * xn * ki * (p*xms + q*xmc)}
|
|||
|
|
return pos, vel
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func rx(a float64) [3][3]float64 {
|
|||
|
|
s, c := math.Sincos(a)
|
|||
|
|
return [3][3]float64{{1, 0, 0}, {0, c, -s}, {0, s, c}}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func rz(a float64) [3][3]float64 {
|
|||
|
|
s, c := math.Sincos(a)
|
|||
|
|
return [3][3]float64{{c, -s, 0}, {s, c, 0}, {0, 0, 1}}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func mul(a, b [3][3]float64) (out [3][3]float64) {
|
|||
|
|
for i := 0; i < 3; i++ {
|
|||
|
|
for j := 0; j < 3; j++ {
|
|||
|
|
for k := 0; k < 3; k++ {
|
|||
|
|
out[i][j] += a[i][k] * b[k][j]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return out
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func transpose(a [3][3]float64) (out [3][3]float64) {
|
|||
|
|
for i := 0; i < 3; i++ {
|
|||
|
|
for j := 0; j < 3; j++ {
|
|||
|
|
out[i][j] = a[j][i]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return out
|
|||
|
|
}
|