@essrt/physics 6.0.1 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +2 -2
- package/src/constants/coefficients.d.ts +5 -38
- package/src/constants/coefficients.js +3 -5
- package/src/constants/generated.d.ts +2 -2
- package/src/constants/generated.js +9 -9
- package/src/deltat/cycles.cjs +17 -16
- package/src/earth/deep-orbital-history.cjs +10 -0
- package/src/earth/frame-of-date.cjs +105 -0
- package/src/eclipse/besselian.cjs +41 -17
- package/src/eclipse/sun-planetary-completion.cjs +55 -14
- package/src/index.js +5 -1
- package/src/model.js +270 -161
- package/src/moon/apparent.cjs +45 -126
- package/src/moon/series-extension.cjs +2 -1
- package/src/moon/series.cjs +21 -4
- package/src/planets/chain-artifact.js +2 -2
- package/types/constants/coefficients.d.ts +5 -38
- package/types/constants/generated.d.ts +2 -2
- package/types/deltat/cycles.d.cts +16 -14
- package/types/earth/deep-orbital-history.d.cts +2 -0
- package/types/earth/frame-of-date.d.cts +37 -0
- package/types/eclipse/besselian.d.cts +5 -7
- package/types/eclipse/sun-planetary-completion.d.cts +14 -10
- package/types/index.d.ts +1 -1
- package/types/model.d.ts +4 -1
- package/types/moon/apparent.d.cts +7 -68
- package/src/planets/predict.cjs +0 -324
- package/types/planets/predict.d.cts +0 -88
|
@@ -27,13 +27,17 @@ export function createSunPlanetaryCompletion({ embWobbleArcsec, carrierRatesDegP
|
|
|
27
27
|
* landings. */
|
|
28
28
|
export const PAIRED_SUN_HARMONICS_SHA256: "cbc189cea1c20292";
|
|
29
29
|
/** sha256/16 of JSON.stringify([...planets, moonElongation]) — the seven
|
|
30
|
-
* full-precision carrier rates (deg/cy TT) the TERMS table
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
|
|
30
|
+
* full-precision carrier rates (deg/cy TT) the TERMS table pairs with:
|
|
31
|
+
* the SIDEREAL carriers of plan 06 I2 (record rate − the model's J2000
|
|
32
|
+
* precession; Earth the framework sidereal year; the N3 literals kept,
|
|
33
|
+
* the two long-period rows derived on these rates). The model wiring
|
|
34
|
+
* recomputes the rates live from the planet records, so a planet-period /
|
|
35
|
+
* year / month input change moves the carriers automatically while the
|
|
36
|
+
* table stays frozen — a silent few-arcsec stale below the api gate's
|
|
37
|
+
* ≤12″ backstop. test:model recomputes this fingerprint from live
|
|
38
|
+
* constants (identical arithmetic to model.js) and fails on mismatch:
|
|
39
|
+
* re-run the extraction chain (tools/explore/n2-sun-framework-carriers.mjs
|
|
40
|
+
* → i2-sidereal-carrier-table.mjs for the short-period rows,
|
|
41
|
+
* i2-long-inequality.mjs for the long-period rows), re-embed TERMS, and
|
|
42
|
+
* update this value. */
|
|
43
|
+
export const PAIRED_CARRIER_RATES_SHA256: "2d066e92bae955e4";
|
package/types/index.d.ts
CHANGED
|
@@ -66,6 +66,7 @@ import { REFERENCE_DATA } from './constants/index.js';
|
|
|
66
66
|
import { assembleModel } from './model.js';
|
|
67
67
|
export { GENERATED as DEFAULT_CONSTANTS, CONSTANTS_HASH, MODEL_VERSION, PREPRINT_DOI, REFERENCE_DATA };
|
|
68
68
|
export { FITTED_COEFFICIENTS, COEFFICIENTS_HASH } from "./constants/index.js";
|
|
69
|
+
export { computeEarthFrameOfDate, solveWheelAngleForLongitude } from "./earth/frame-of-date.cjs";
|
|
69
70
|
export { eccentricityFromCycles, computeEccentricityIntegrated } from "./planets/ecc-channel.cjs";
|
|
70
71
|
export { buildPlanetChainsFromArtifactData, computePlanetElementsAtYear, computeHeliocentricEclipticFromElements, computePoissonArgRad, computeOsculatingElements, solveKeplerRad, computeApsidalSecularDegPerYr, ANCHOR_EPOCH_YEAR, ANCHOR_EPOCH_JD } from "./planets/keplerian-chain.cjs";
|
|
71
72
|
export { computeEquatorNodeOriginSFrameDeg, convertNodeSFrameToEquatorOriginDeg } from "./planets/inv-plane-frame.cjs";
|
|
@@ -73,6 +74,5 @@ export { CHAIN_ARTIFACT, CHAIN_ARTIFACT_HASH } from "./planets/chain-artifact.js
|
|
|
73
74
|
export { DEEP_MODES_ARTIFACT, DEEP_MODES_ARTIFACT_HASH } from "./moon/deep-modes-artifact.cjs";
|
|
74
75
|
export { createYearLengths, ONE_FAMILY_WINDOW_YEARS } from "./earth/year-lengths.cjs";
|
|
75
76
|
export { computeSolarTorqueShare, createComposedPrecession } from "./earth/precession-composed.cjs";
|
|
76
|
-
export { createPredictivePrecession, calcPlanetPerihelionLongDeg } from "./planets/predict.cjs";
|
|
77
77
|
export { createMoonRecessionHistory, createSolarChannelBudget } from "./deltat/recession-history.cjs";
|
|
78
78
|
export { evalClimateL1OrbitalPermil, laggedL1Terms, createAlphaGiaChannel } from "./climate/l1-orbital.cjs";
|
package/types/model.d.ts
CHANGED
|
@@ -89,6 +89,7 @@ export function assembleModel(C: Readonly<Record<string, any>>, F: Readonly<Reco
|
|
|
89
89
|
}>;
|
|
90
90
|
cardinal: Readonly<{
|
|
91
91
|
jd: (year: number, type: string) => number;
|
|
92
|
+
jdNearUT: (jdUT: number, type: string) => number;
|
|
92
93
|
raDeg: (year: number, type: string) => number;
|
|
93
94
|
yearLengthDays: (year: number, type: string) => number;
|
|
94
95
|
}>;
|
|
@@ -135,6 +136,8 @@ export function assembleModel(C: Readonly<Record<string, any>>, F: Readonly<Reco
|
|
|
135
136
|
eclipse: Readonly<{
|
|
136
137
|
sunLonDegAtJD: (jd: number) => number;
|
|
137
138
|
sunLonCompletedDegAtJD: (jd: number) => number;
|
|
139
|
+
/** The APPARENT Sun (completed geometric − the derived aberration constant + the leading nutation terms on the model's own arguments) — the quantity Horizons' observer ecliptic longitude is; the cardinal instants are its crossings (plan 06 R1/I1). @param {number} jdUT @returns {number} */
|
|
140
|
+
sunApparentLonDegAtJD: (jdUT: number) => number;
|
|
138
141
|
findLunarInRange: (jdStart: number, jdEnd: number) => {
|
|
139
142
|
jd: number;
|
|
140
143
|
beta: number;
|
|
@@ -156,7 +159,7 @@ export function assembleModel(C: Readonly<Record<string, any>>, F: Readonly<Reco
|
|
|
156
159
|
frameworkSunDeps: Readonly<{
|
|
157
160
|
sunMeanLongitudeJ2000Deg: any;
|
|
158
161
|
tropicalRateDegPerCy: number;
|
|
159
|
-
eccentricityAt: (year: number) =>
|
|
162
|
+
eccentricityAt: (year: number) => any;
|
|
160
163
|
perihelionLongitudeDegAt: (year: number) => number;
|
|
161
164
|
meanLongitudeDegAt: (year: number) => number;
|
|
162
165
|
}>;
|
|
@@ -1,75 +1,14 @@
|
|
|
1
|
-
export type MoonCorrectionTable = {
|
|
2
|
-
raSinD: number;
|
|
3
|
-
raCosD: number;
|
|
4
|
-
raSinMp: number;
|
|
5
|
-
raCosMp: number;
|
|
6
|
-
raSinMs: number;
|
|
7
|
-
raCosMs: number;
|
|
8
|
-
decSinD: number;
|
|
9
|
-
decCosD: number;
|
|
10
|
-
decSinMp: number;
|
|
11
|
-
decCosMp: number;
|
|
12
|
-
decSinMs: number;
|
|
13
|
-
decCosMs: number;
|
|
14
|
-
};
|
|
15
|
-
/**
|
|
16
|
-
* @typedef {{raSinD: number, raCosD: number, raSinMp: number, raCosMp: number,
|
|
17
|
-
* raSinMs: number, raCosMs: number, decSinD: number, decCosD: number,
|
|
18
|
-
* decSinMp: number, decCosMp: number, decSinMs: number, decCosMs: number}} MoonCorrectionTable
|
|
19
|
-
*/
|
|
20
1
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* sunMeanLongitudeJ2000Deg: number,
|
|
26
|
-
* perihelionLongitudeJ2000Deg: number,
|
|
27
|
-
* eccentricityJ2000: number,
|
|
28
|
-
* eccentricityDotJ2000: number,
|
|
29
|
-
* d5RateLDegPerDay: number,
|
|
30
|
-
* d5RatePeriDegPerDay: number,
|
|
31
|
-
* speedOfLight: number,
|
|
32
|
-
* },
|
|
33
|
-
* fns: {
|
|
34
|
-
* computeObliquityEarth: (year: number) => number,
|
|
35
|
-
* getAuDistanceKm: () => number,
|
|
36
|
-
* isFrameworkNative: () => boolean,
|
|
37
|
-
* getCorrectionResidual: () => (MoonCorrectionTable | null),
|
|
38
|
-
* getCorrectionLegacy: () => (MoonCorrectionTable | null),
|
|
39
|
-
* },
|
|
40
|
-
* }} deps — the D5 rates are the engines' J2000-frozen values (the year
|
|
41
|
-
* globals are deep-time-mutable, same pattern as FW_A2_RATE); the AU
|
|
42
|
-
* distance is a GETTER (browser-mutable under deep time).
|
|
2
|
+
* No dependencies since R3 item 1 (the former D5 optics constants and the
|
|
3
|
+
* fitted-patch getters are gone); the factory shape is kept for the two
|
|
4
|
+
* scene twins' call sites.
|
|
5
|
+
* @param {object} [_deps] ignored
|
|
43
6
|
*/
|
|
44
|
-
export function createMoonApparent(
|
|
45
|
-
|
|
46
|
-
j2000JD: number;
|
|
47
|
-
julianCenturyDays: number;
|
|
48
|
-
sunMeanLongitudeJ2000Deg: number;
|
|
49
|
-
perihelionLongitudeJ2000Deg: number;
|
|
50
|
-
eccentricityJ2000: number;
|
|
51
|
-
eccentricityDotJ2000: number;
|
|
52
|
-
d5RateLDegPerDay: number;
|
|
53
|
-
d5RatePeriDegPerDay: number;
|
|
54
|
-
speedOfLight: number;
|
|
55
|
-
};
|
|
56
|
-
fns: {
|
|
57
|
-
computeObliquityEarth: (year: number) => number;
|
|
58
|
-
getAuDistanceKm: () => number;
|
|
59
|
-
isFrameworkNative: () => boolean;
|
|
60
|
-
getCorrectionResidual: () => (MoonCorrectionTable | null);
|
|
61
|
-
getCorrectionLegacy: () => (MoonCorrectionTable | null);
|
|
62
|
-
};
|
|
63
|
-
}): {
|
|
64
|
-
sunGeoVecEqD5: (jd: number) => [number, number, number];
|
|
65
|
-
moonAberrationRaDec: (jd: number, ra: number, dec: number) => {
|
|
66
|
-
dRA: number;
|
|
67
|
-
dDec: number;
|
|
68
|
-
};
|
|
69
|
-
overrideRaDec: ({ lonDeg, betRad, meeusT, obliquityDeg }: {
|
|
7
|
+
export function createMoonApparent(_deps?: object): {
|
|
8
|
+
overrideRaDec: ({ lonDeg, betRad, obliquityDeg }: {
|
|
70
9
|
lonDeg: number;
|
|
71
10
|
betRad: number;
|
|
72
|
-
meeusT
|
|
11
|
+
meeusT?: (number | undefined);
|
|
73
12
|
obliquityDeg: number;
|
|
74
13
|
}) => {
|
|
75
14
|
raRad: number;
|
package/src/planets/predict.cjs
DELETED
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Predictive geocentric precession — the physical-beat feature basis
|
|
3
|
-
* (Phase 8.3, L9). Ported originally from
|
|
4
|
-
* tools/lib/python/predictive_formula_physical.py; this module is now THE
|
|
5
|
-
* shared implementation behind what were two hand-mirrors (browser +
|
|
6
|
-
* Node engine, ~220 duplicated lines each).
|
|
7
|
-
*
|
|
8
|
-
* GROUPS: A (Earth ref), B (planet angle), C+D+E (periods+beats × 7
|
|
9
|
-
* harmonics), F (period×angle), I (period×2δ), J (fund×Earth sidebands),
|
|
10
|
-
* L (high-harmonic fund×Earth ecl), K (ecl±icrf carriers × Earth ecl).
|
|
11
|
-
* Groups G+H (ERD×period) were removed after a 0.00%-impact diagnostic.
|
|
12
|
-
*
|
|
13
|
-
* MATCHED PAIR: PREDICT_COEFFS was trained against exactly this basis in
|
|
14
|
-
* exactly this push order — reordering or "simplifying" any group scrambles
|
|
15
|
-
* the ridge-regression dot product silently. Do not touch.
|
|
16
|
-
*
|
|
17
|
-
* The engines OWN the Earth scalar derivations (θ_E, ERD, obliquity,
|
|
18
|
-
* eccentricity) — injected, because the two engines deliberately differ
|
|
19
|
-
* there: the browser evaluates the deep-time epoch-aware forms
|
|
20
|
-
* (H_at_year, phaseAdvanceRadians), the Node engine the J2000-anchored
|
|
21
|
-
* forms. The predictor dot-products (PREDICT_PLANETS / PREDICT_COEFFS
|
|
22
|
-
* lookup and their guard semantics) also stay engine-side.
|
|
23
|
-
*
|
|
24
|
-
* Planet fields and H / balancedYear arrive as GETTERS: the browser
|
|
25
|
-
* mutates all of them on setEpoch and invalidates the feature-template
|
|
26
|
-
* cache via resetTemplateCache() at that site.
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
'use strict';
|
|
30
|
-
|
|
31
|
-
const PHYSICAL_PERIOD_KEYS = ['ecl', 'icrf', 'obliq', 'asc', 'axial', 'wobble'];
|
|
32
|
-
const PHYSICAL_HARMONIC_ORDERS = [1, 2, 4, 6, 8, 12, 16];
|
|
33
|
-
const SIDEBAND_J_HARMONICS = [1, 2, 3, 4];
|
|
34
|
-
const CARRIER_HARMONICS_K = [2, 4, 6, 8, 10, 12];
|
|
35
|
-
const SIDEBAND_K_HARMONICS = [1, 2, 3];
|
|
36
|
-
const CARRIER_HARMONICS_L = [6, 10, 12, 16];
|
|
37
|
-
const SIDEBAND_L_HARMONICS = [1, 2, 3];
|
|
38
|
-
const MAX_BEAT_YEARS = 1e12;
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Any planet's perihelion longitude — simple linear precession from J2000.
|
|
42
|
-
*
|
|
43
|
-
* @param {number} theta0Deg - perihelion longitude at J2000 (degrees)
|
|
44
|
-
* @param {number} periodYears - precession period in years
|
|
45
|
-
* @param {number} year - decimal year
|
|
46
|
-
* @returns {number} longitude in degrees [0, 360)
|
|
47
|
-
*/
|
|
48
|
-
function calcPlanetPerihelionLongDeg(theta0Deg, periodYears, year) {
|
|
49
|
-
return ((theta0Deg + 360.0 * (year - 2000) / periodYears) % 360 + 360) % 360;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Sum/difference beat periods of two cycles, null where degenerate or
|
|
54
|
-
* beyond MAX_BEAT_YEARS.
|
|
55
|
-
*
|
|
56
|
-
* @param {number|null} t1 @param {number|null} t2
|
|
57
|
-
* @returns {{ sum: number|null, diff: number|null }}
|
|
58
|
-
*/
|
|
59
|
-
function beatPair(t1, t2) {
|
|
60
|
-
if (t1 == null || t2 == null) return { sum: null, diff: null };
|
|
61
|
-
const sr = 1 / t1 + 1 / t2;
|
|
62
|
-
const dr = 1 / t1 - 1 / t2;
|
|
63
|
-
let sp = Math.abs(sr) > 1e-20 ? 1 / sr : null;
|
|
64
|
-
let dp = Math.abs(dr) > 1e-20 ? 1 / dr : null;
|
|
65
|
-
if (sp != null && Math.abs(sp) > MAX_BEAT_YEARS) sp = null;
|
|
66
|
-
if (dp != null && Math.abs(dp) > MAX_BEAT_YEARS) dp = null;
|
|
67
|
-
return { sum: sp, diff: dp };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* @typedef {Object} PredictPlanetFields
|
|
72
|
-
* @property {number} perihelionEclipticYears
|
|
73
|
-
* @property {number} longitudePerihelion
|
|
74
|
-
* @property {number|null|undefined} ascendingNodeCyclesIn8H
|
|
75
|
-
* @property {number} axialPrecessionYears
|
|
76
|
-
* @property {number|null|undefined} obliquityCycle
|
|
77
|
-
* @property {number|null|undefined} wobblePeriod
|
|
78
|
-
*/
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* @typedef {Object} PredictDeps
|
|
82
|
-
* @property {() => number} getHYears - holistic year length (live: setEpoch mutates it)
|
|
83
|
-
* @property {() => number} getBalancedYear
|
|
84
|
-
* @property {(planetKey: string) => PredictPlanetFields} getPlanetFields - live per-call reads
|
|
85
|
-
* @property {(year: number) => number} calcEarthPerihelionDeg - θ_E, engine-owned form
|
|
86
|
-
* @property {(year: number) => number} calcErdRate - ERD deg/yr, engine-owned form
|
|
87
|
-
* @property {(year: number) => number} computeObliquityEarthDeg
|
|
88
|
-
* @property {(year: number) => number} computeEccentricityEarth
|
|
89
|
-
* @property {number} obliquityMeanDeg - the fitted solstice-obliquity mean
|
|
90
|
-
* @property {number} eccentricityMean - Earth's mean eccentricity (the one law's base′)
|
|
91
|
-
*/
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Build the predictive-precession machinery over one engine's state.
|
|
95
|
-
* @param {PredictDeps} deps
|
|
96
|
-
*/
|
|
97
|
-
function createPredictivePrecession(deps) {
|
|
98
|
-
/** @type {Record<string, number[]>} */
|
|
99
|
-
const templates = {};
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* The six fundamental periods (years) feeding the feature basis.
|
|
103
|
-
* Earth is pure H-lattice; planets derive icrf/asc from their fields.
|
|
104
|
-
* @param {string} planetName
|
|
105
|
-
* @returns {Record<string, number|null>}
|
|
106
|
-
*/
|
|
107
|
-
function getPlanetFundamentalPeriodsYears(planetName) {
|
|
108
|
-
const H = deps.getHYears();
|
|
109
|
-
const H13 = H / 13;
|
|
110
|
-
if (planetName === 'earth') {
|
|
111
|
-
return { ecl: H / 16, icrf: H / 3, obliq: H / 8, asc: -8 * H / 40, axial: -H / 13, wobble: H / 16 };
|
|
112
|
-
}
|
|
113
|
-
const p = deps.getPlanetFields(planetName);
|
|
114
|
-
const tEcl = p.perihelionEclipticYears;
|
|
115
|
-
const tIcrf = (tEcl * H13) / (H13 - tEcl);
|
|
116
|
-
const ascN = p.ascendingNodeCyclesIn8H;
|
|
117
|
-
const tAsc = ascN ? -8 * H / ascN : null;
|
|
118
|
-
return {
|
|
119
|
-
ecl: tEcl,
|
|
120
|
-
icrf: tIcrf,
|
|
121
|
-
obliq: p.obliquityCycle || null,
|
|
122
|
-
asc: tAsc,
|
|
123
|
-
axial: p.axialPrecessionYears,
|
|
124
|
-
wobble: p.wobblePeriod || null,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Fundamentals + pairwise internal beats + planet×Earth beats, cached
|
|
130
|
-
* per planet until resetTemplateCache().
|
|
131
|
-
* @param {string} planetName
|
|
132
|
-
* @returns {number[]}
|
|
133
|
-
*/
|
|
134
|
-
function getFeatureTemplate(planetName) {
|
|
135
|
-
if (templates[planetName]) return templates[planetName];
|
|
136
|
-
const pp = getPlanetFundamentalPeriodsYears(planetName);
|
|
137
|
-
const ep = getPlanetFundamentalPeriodsYears('earth');
|
|
138
|
-
const template = [];
|
|
139
|
-
for (const k of PHYSICAL_PERIOD_KEYS) {
|
|
140
|
-
if (pp[k] != null) template.push(pp[k]);
|
|
141
|
-
}
|
|
142
|
-
for (let i = 0; i < PHYSICAL_PERIOD_KEYS.length; i++) {
|
|
143
|
-
for (let j = i + 1; j < PHYSICAL_PERIOD_KEYS.length; j++) {
|
|
144
|
-
const b = beatPair(pp[PHYSICAL_PERIOD_KEYS[i]], pp[PHYSICAL_PERIOD_KEYS[j]]);
|
|
145
|
-
if (b.sum != null) template.push(b.sum);
|
|
146
|
-
if (b.diff != null) template.push(b.diff);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
for (const pk of PHYSICAL_PERIOD_KEYS) {
|
|
150
|
-
for (const ek of PHYSICAL_PERIOD_KEYS) {
|
|
151
|
-
const b = beatPair(pp[pk], ep[ek]);
|
|
152
|
-
if (b.sum != null) template.push(b.sum);
|
|
153
|
-
if (b.diff != null) template.push(b.diff);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
templates[planetName] = template;
|
|
157
|
-
return template;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* The full feature vector (~2421 elements for Venus). Push order is the
|
|
162
|
-
* training order — see the module header.
|
|
163
|
-
* @param {number} year - calendar year
|
|
164
|
-
* @param {string} planetKey - e.g. 'venus'
|
|
165
|
-
* @returns {number[]}
|
|
166
|
-
*/
|
|
167
|
-
function buildPredictiveFeatures(year, planetKey) {
|
|
168
|
-
const t = year - deps.getBalancedYear();
|
|
169
|
-
const p = deps.getPlanetFields(planetKey);
|
|
170
|
-
const planetPeriod = Math.abs(p.perihelionEclipticYears);
|
|
171
|
-
const planetTheta0 = p.longitudePerihelion;
|
|
172
|
-
|
|
173
|
-
const thetaE = deps.calcEarthPerihelionDeg(year);
|
|
174
|
-
const thetaP = calcPlanetPerihelionLongDeg(planetTheta0, planetPeriod, year);
|
|
175
|
-
const erd = deps.calcErdRate(year);
|
|
176
|
-
const obliq = deps.computeObliquityEarthDeg(year);
|
|
177
|
-
const ecc = deps.computeEccentricityEarth(year);
|
|
178
|
-
|
|
179
|
-
const thetaERad = thetaE * Math.PI / 180;
|
|
180
|
-
const thetaPRad = thetaP * Math.PI / 180;
|
|
181
|
-
const diff = thetaERad - thetaPRad;
|
|
182
|
-
const sumAngle = thetaERad + thetaPRad;
|
|
183
|
-
|
|
184
|
-
const obliqNorm = obliq - deps.obliquityMeanDeg;
|
|
185
|
-
const eccNorm = ecc - deps.eccentricityMean;
|
|
186
|
-
const erd2 = erd * erd;
|
|
187
|
-
const erd3 = erd2 * erd;
|
|
188
|
-
|
|
189
|
-
const f = [];
|
|
190
|
-
|
|
191
|
-
// GROUP A: Earth reference (49)
|
|
192
|
-
for (const n of [1, 2, 3, 4]) { f.push(Math.cos(n * diff), Math.sin(n * diff)); }
|
|
193
|
-
f.push(Math.cos(sumAngle), Math.sin(sumAngle));
|
|
194
|
-
for (const n of [1, 2]) { f.push(Math.cos(n * thetaERad), Math.sin(n * thetaERad)); }
|
|
195
|
-
for (const n of [1, 2]) { f.push(Math.cos(n * thetaPRad), Math.sin(n * thetaPRad)); }
|
|
196
|
-
f.push(obliqNorm, eccNorm);
|
|
197
|
-
f.push(obliqNorm * Math.cos(diff), obliqNorm * Math.sin(diff));
|
|
198
|
-
f.push(eccNorm * Math.cos(diff), eccNorm * Math.sin(diff));
|
|
199
|
-
f.push(erd, erd2, erd3);
|
|
200
|
-
f.push(erd * Math.cos(diff), erd * Math.sin(diff));
|
|
201
|
-
f.push(erd * Math.cos(2 * diff), erd * Math.sin(2 * diff));
|
|
202
|
-
f.push(erd * Math.cos(sumAngle), erd * Math.sin(sumAngle));
|
|
203
|
-
f.push(erd * obliqNorm, erd * eccNorm);
|
|
204
|
-
f.push(erd2 * Math.cos(diff), erd2 * Math.sin(diff));
|
|
205
|
-
f.push(erd2 * Math.cos(2 * diff));
|
|
206
|
-
f.push(erd * Math.cos(3 * diff), erd * Math.sin(3 * diff));
|
|
207
|
-
f.push(Math.cos(3 * diff), Math.sin(3 * diff));
|
|
208
|
-
f.push(Math.cos(4 * diff), Math.sin(4 * diff));
|
|
209
|
-
f.push(erd * Math.cos(2 * sumAngle), erd * Math.sin(2 * sumAngle));
|
|
210
|
-
f.push(erd2 * Math.cos(sumAngle), erd2 * Math.sin(sumAngle));
|
|
211
|
-
f.push(1.0);
|
|
212
|
-
|
|
213
|
-
// GROUP B: planet angle cross-terms (10)
|
|
214
|
-
for (const n of [3, 4]) { f.push(Math.cos(n * thetaPRad), Math.sin(n * thetaPRad)); }
|
|
215
|
-
f.push(obliqNorm * Math.cos(2 * diff), obliqNorm * Math.sin(2 * diff));
|
|
216
|
-
f.push(eccNorm * Math.cos(2 * diff), eccNorm * Math.sin(2 * diff));
|
|
217
|
-
f.push(Math.cos(thetaERad) * Math.cos(thetaPRad));
|
|
218
|
-
f.push(Math.cos(thetaERad) * Math.sin(thetaPRad));
|
|
219
|
-
|
|
220
|
-
// C+D+E: physical periods + beats × 7 harmonics
|
|
221
|
-
const template = getFeatureTemplate(planetKey);
|
|
222
|
-
for (let i = 0; i < template.length; i++) {
|
|
223
|
-
const basePhase = 2 * Math.PI * t / template[i];
|
|
224
|
-
for (const n of PHYSICAL_HARMONIC_ORDERS) {
|
|
225
|
-
f.push(Math.cos(n * basePhase), Math.sin(n * basePhase));
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const pp = getPlanetFundamentalPeriodsYears(planetKey);
|
|
230
|
-
|
|
231
|
-
// GROUP F: period × angle
|
|
232
|
-
for (const k of PHYSICAL_PERIOD_KEYS) {
|
|
233
|
-
const period = pp[k];
|
|
234
|
-
if (period == null) continue;
|
|
235
|
-
const phase = 2 * Math.PI * t / period;
|
|
236
|
-
f.push(Math.cos(phase) * Math.cos(diff));
|
|
237
|
-
f.push(Math.sin(phase) * Math.sin(diff));
|
|
238
|
-
f.push(Math.cos(phase) * Math.cos(2 * diff));
|
|
239
|
-
f.push(Math.sin(phase) * Math.sin(2 * diff));
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// GROUP I: period × 2δ
|
|
243
|
-
for (const k of PHYSICAL_PERIOD_KEYS) {
|
|
244
|
-
const period = pp[k];
|
|
245
|
-
if (period == null) continue;
|
|
246
|
-
const phase = 2 * Math.PI * t / period;
|
|
247
|
-
const sinP = Math.sin(phase), cosP = Math.cos(phase);
|
|
248
|
-
f.push(sinP * Math.cos(2 * diff), sinP * Math.sin(2 * diff));
|
|
249
|
-
f.push(cosP * Math.cos(2 * diff), cosP * Math.sin(2 * diff));
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// GROUP J: fund × Earth cycles (4 harmonics)
|
|
253
|
-
// Earth's fundamentals are pure H-lattice — never null; the Record type
|
|
254
|
-
// can't express that, hence the casts here and below.
|
|
255
|
-
const ep = getPlanetFundamentalPeriodsYears('earth');
|
|
256
|
-
const earthCyclesJ = /** @type {number[]} */ ([ep.ecl, ep.obliq, ep.icrf, ep.axial]);
|
|
257
|
-
for (const k of PHYSICAL_PERIOD_KEYS) {
|
|
258
|
-
const period = pp[k];
|
|
259
|
-
if (period == null) continue;
|
|
260
|
-
const phaseP = 2 * Math.PI * t / period;
|
|
261
|
-
const sinP = Math.sin(phaseP), cosP = Math.cos(phaseP);
|
|
262
|
-
for (const TE of earthCyclesJ) {
|
|
263
|
-
for (const harm of SIDEBAND_J_HARMONICS) {
|
|
264
|
-
const phaseE = harm * 2 * Math.PI * t / TE;
|
|
265
|
-
const sinE = Math.sin(phaseE), cosE = Math.cos(phaseE);
|
|
266
|
-
f.push(sinP * sinE, sinP * cosE, cosP * sinE, cosP * cosE);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// GROUP L: high-harmonic fundamental × Earth ecl
|
|
272
|
-
const phiEEcl = 2 * Math.PI * t / /** @type {number} */ (ep.ecl);
|
|
273
|
-
for (const k of PHYSICAL_PERIOD_KEYS) {
|
|
274
|
-
const period = pp[k];
|
|
275
|
-
if (period == null) continue;
|
|
276
|
-
const phiFund = 2 * Math.PI * t / period;
|
|
277
|
-
for (const nL of CARRIER_HARMONICS_L) {
|
|
278
|
-
const ccL = Math.cos(nL * phiFund), scL = Math.sin(nL * phiFund);
|
|
279
|
-
for (const kh of SIDEBAND_L_HARMONICS) {
|
|
280
|
-
const modPhase = kh * phiEEcl;
|
|
281
|
-
const smL = Math.sin(modPhase), cmL = Math.cos(modPhase);
|
|
282
|
-
f.push(scL * smL, scL * cmL, ccL * smL, ccL * cmL);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// GROUP K: ICRF internal-beat carriers × Earth ecl sidebands
|
|
288
|
-
if (pp.ecl != null && pp.icrf != null) {
|
|
289
|
-
const phiEcl = 2 * Math.PI * t / pp.ecl;
|
|
290
|
-
const phiIcrf = 2 * Math.PI * t / pp.icrf;
|
|
291
|
-
for (const sign of [+1, -1]) {
|
|
292
|
-
for (const n of CARRIER_HARMONICS_K) {
|
|
293
|
-
const carrierPhase = n * (phiEcl + sign * phiIcrf);
|
|
294
|
-
const sc = Math.sin(carrierPhase), cc = Math.cos(carrierPhase);
|
|
295
|
-
for (const kh of SIDEBAND_K_HARMONICS) {
|
|
296
|
-
const modPhase = kh * phiEEcl;
|
|
297
|
-
const sm = Math.sin(modPhase), cm = Math.cos(modPhase);
|
|
298
|
-
f.push(sc * sm, sc * cm, cc * sm, cc * cm);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
return f;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Invalidate cached feature templates after the underlying planet fields
|
|
309
|
-
* mutate (the browser's setEpoch rebuilds obliquity cycles, wobble
|
|
310
|
-
* periods, and the perihelion periods the templates froze at first touch).
|
|
311
|
-
*/
|
|
312
|
-
function resetTemplateCache() {
|
|
313
|
-
for (const k in templates) delete templates[k];
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return {
|
|
317
|
-
buildPredictiveFeatures,
|
|
318
|
-
getPlanetFundamentalPeriodsYears,
|
|
319
|
-
getFeatureTemplate,
|
|
320
|
-
resetTemplateCache,
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
module.exports = { createPredictivePrecession, calcPlanetPerihelionLongDeg, beatPair };
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
export type PredictPlanetFields = {
|
|
2
|
-
perihelionEclipticYears: number;
|
|
3
|
-
longitudePerihelion: number;
|
|
4
|
-
ascendingNodeCyclesIn8H: number | null | undefined;
|
|
5
|
-
axialPrecessionYears: number;
|
|
6
|
-
obliquityCycle: number | null | undefined;
|
|
7
|
-
wobblePeriod: number | null | undefined;
|
|
8
|
-
};
|
|
9
|
-
export type PredictDeps = {
|
|
10
|
-
/**
|
|
11
|
-
* - holistic year length (live: setEpoch mutates it)
|
|
12
|
-
*/
|
|
13
|
-
getHYears: () => number;
|
|
14
|
-
getBalancedYear: () => number;
|
|
15
|
-
/**
|
|
16
|
-
* - live per-call reads
|
|
17
|
-
*/
|
|
18
|
-
getPlanetFields: (planetKey: string) => PredictPlanetFields;
|
|
19
|
-
/**
|
|
20
|
-
* - θ_E, engine-owned form
|
|
21
|
-
*/
|
|
22
|
-
calcEarthPerihelionDeg: (year: number) => number;
|
|
23
|
-
/**
|
|
24
|
-
* - ERD deg/yr, engine-owned form
|
|
25
|
-
*/
|
|
26
|
-
calcErdRate: (year: number) => number;
|
|
27
|
-
computeObliquityEarthDeg: (year: number) => number;
|
|
28
|
-
computeEccentricityEarth: (year: number) => number;
|
|
29
|
-
/**
|
|
30
|
-
* - the fitted solstice-obliquity mean
|
|
31
|
-
*/
|
|
32
|
-
obliquityMeanDeg: number;
|
|
33
|
-
/**
|
|
34
|
-
* - Earth's mean eccentricity (the one law's base′)
|
|
35
|
-
*/
|
|
36
|
-
eccentricityMean: number;
|
|
37
|
-
};
|
|
38
|
-
/**
|
|
39
|
-
* @typedef {Object} PredictPlanetFields
|
|
40
|
-
* @property {number} perihelionEclipticYears
|
|
41
|
-
* @property {number} longitudePerihelion
|
|
42
|
-
* @property {number|null|undefined} ascendingNodeCyclesIn8H
|
|
43
|
-
* @property {number} axialPrecessionYears
|
|
44
|
-
* @property {number|null|undefined} obliquityCycle
|
|
45
|
-
* @property {number|null|undefined} wobblePeriod
|
|
46
|
-
*/
|
|
47
|
-
/**
|
|
48
|
-
* @typedef {Object} PredictDeps
|
|
49
|
-
* @property {() => number} getHYears - holistic year length (live: setEpoch mutates it)
|
|
50
|
-
* @property {() => number} getBalancedYear
|
|
51
|
-
* @property {(planetKey: string) => PredictPlanetFields} getPlanetFields - live per-call reads
|
|
52
|
-
* @property {(year: number) => number} calcEarthPerihelionDeg - θ_E, engine-owned form
|
|
53
|
-
* @property {(year: number) => number} calcErdRate - ERD deg/yr, engine-owned form
|
|
54
|
-
* @property {(year: number) => number} computeObliquityEarthDeg
|
|
55
|
-
* @property {(year: number) => number} computeEccentricityEarth
|
|
56
|
-
* @property {number} obliquityMeanDeg - the fitted solstice-obliquity mean
|
|
57
|
-
* @property {number} eccentricityMean - Earth's mean eccentricity (the one law's base′)
|
|
58
|
-
*/
|
|
59
|
-
/**
|
|
60
|
-
* Build the predictive-precession machinery over one engine's state.
|
|
61
|
-
* @param {PredictDeps} deps
|
|
62
|
-
*/
|
|
63
|
-
export function createPredictivePrecession(deps: PredictDeps): {
|
|
64
|
-
buildPredictiveFeatures: (year: number, planetKey: string) => number[];
|
|
65
|
-
getPlanetFundamentalPeriodsYears: (planetName: string) => Record<string, number | null>;
|
|
66
|
-
getFeatureTemplate: (planetName: string) => number[];
|
|
67
|
-
resetTemplateCache: () => void;
|
|
68
|
-
};
|
|
69
|
-
/**
|
|
70
|
-
* Any planet's perihelion longitude — simple linear precession from J2000.
|
|
71
|
-
*
|
|
72
|
-
* @param {number} theta0Deg - perihelion longitude at J2000 (degrees)
|
|
73
|
-
* @param {number} periodYears - precession period in years
|
|
74
|
-
* @param {number} year - decimal year
|
|
75
|
-
* @returns {number} longitude in degrees [0, 360)
|
|
76
|
-
*/
|
|
77
|
-
export function calcPlanetPerihelionLongDeg(theta0Deg: number, periodYears: number, year: number): number;
|
|
78
|
-
/**
|
|
79
|
-
* Sum/difference beat periods of two cycles, null where degenerate or
|
|
80
|
-
* beyond MAX_BEAT_YEARS.
|
|
81
|
-
*
|
|
82
|
-
* @param {number|null} t1 @param {number|null} t2
|
|
83
|
-
* @returns {{ sum: number|null, diff: number|null }}
|
|
84
|
-
*/
|
|
85
|
-
export function beatPair(t1: number | null, t2: number | null): {
|
|
86
|
-
sum: number | null;
|
|
87
|
-
diff: number | null;
|
|
88
|
-
};
|