caelus 0.6.0 → 0.8.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/dist/src/derived.d.ts +51 -0
- package/dist/src/derived.js +184 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +2 -0
- package/dist/src/turbo.d.ts +20 -0
- package/dist/src/turbo.js +50 -0
- package/package.json +1 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Engine, BodyId, Zodiac } from "./chart.js";
|
|
2
|
+
export declare const TROPICAL_YEAR = 365.24219;
|
|
3
|
+
/** Shorter-arc midpoint of two longitudes (degrees). */
|
|
4
|
+
export declare function midpointLon(a: number, b: number): number;
|
|
5
|
+
/** UT JDs in [jdStart, jdEnd] when `body` returns to its natal longitude.
|
|
6
|
+
* Outer-planet returns can show three crossings around a retrograde loop. */
|
|
7
|
+
export declare function returns(engine: Engine, body: BodyId, natalJd: number, jdStart: number, jdEnd: number, zodiac?: Zodiac, maxHits?: number): number[];
|
|
8
|
+
export declare function solarReturn(engine: Engine, natalJd: number, jdStart: number, jdEnd: number, zodiac?: Zodiac): number[];
|
|
9
|
+
export declare function lunarReturn(engine: Engine, natalJd: number, jdStart: number, jdEnd: number, zodiac?: Zodiac): number[];
|
|
10
|
+
/** The JD whose real positions are the secondary-progressed positions for the
|
|
11
|
+
* age (targetJd - natalJd): one day of motion per year of life. */
|
|
12
|
+
export declare function progressedJd(natalJd: number, targetJd: number, yearLength?: number): number;
|
|
13
|
+
export declare function progressedLongitude(engine: Engine, body: BodyId, natalJd: number, targetJd: number, yearLength?: number, zodiac?: Zodiac): number;
|
|
14
|
+
/** Solar-arc direction angle (degrees, forward): how far the secondary-
|
|
15
|
+
* progressed Sun has moved from the natal Sun. Add it to any natal longitude. */
|
|
16
|
+
export declare function solarArc(engine: Engine, natalJd: number, targetJd: number, yearLength?: number, zodiac?: Zodiac): number;
|
|
17
|
+
export declare function directedLongitude(engine: Engine, body: BodyId, natalJd: number, targetJd: number, yearLength?: number, zodiac?: Zodiac): number;
|
|
18
|
+
/** Midpoint-method composite: the shorter-arc midpoint of each body's two
|
|
19
|
+
* longitudes. Angles compose the same way via midpointLon on the two ASC/MC. */
|
|
20
|
+
export declare function compositeLongitudes(engine: Engine, jdA: number, jdB: number, bodies: BodyId[], zodiac?: Zodiac): Record<string, number>;
|
|
21
|
+
/** Time and place for a Davison relationship chart: the temporal midpoint and
|
|
22
|
+
* the geographic midpoint (mean latitude, shorter-arc mean longitude). Compute
|
|
23
|
+
* a normal chart at these to get the Davison chart. Returns [jd, lat, lonEast]. */
|
|
24
|
+
export declare function davisonParams(jdA: number, jdB: number, latA: number, lonEastA: number, latB: number, lonEastB: number): [number, number, number];
|
|
25
|
+
/** The nth-harmonic longitude of a point: lon * n, wrapped to 360. */
|
|
26
|
+
export declare function harmonicLongitude(lon: number, n: number): number;
|
|
27
|
+
export declare function harmonicChart(engine: Engine, jd: number, bodies: BodyId[], n: number, zodiac?: Zodiac): Record<string, number>;
|
|
28
|
+
/** Reflection across the solstice (Cancer-Capricorn) axis. */
|
|
29
|
+
export declare function antiscion(lon: number): number;
|
|
30
|
+
/** Reflection across the equinox (Aries-Libra) axis. */
|
|
31
|
+
export declare function contraAntiscion(lon: number): number;
|
|
32
|
+
export type DeclinationKind = "parallel" | "contraparallel" | null;
|
|
33
|
+
/** Classify two declinations: parallel (same), contraparallel (opposite), null. */
|
|
34
|
+
export declare function declinationAspect(decA: number, decB: number, orb?: number): DeclinationKind;
|
|
35
|
+
export interface DeclinationPair {
|
|
36
|
+
a: string;
|
|
37
|
+
b: string;
|
|
38
|
+
kind: DeclinationKind;
|
|
39
|
+
}
|
|
40
|
+
export declare function declinationAspects(engine: Engine, bodies: BodyId[], jd: number, orb?: number): DeclinationPair[];
|
|
41
|
+
/** |declination| minus the mean obliquity, degrees. Positive = out of bounds. */
|
|
42
|
+
export declare function outOfBoundsMargin(engine: Engine, body: BodyId, jd: number): number;
|
|
43
|
+
export declare function outOfBounds(engine: Engine, body: BodyId, jd: number): boolean;
|
|
44
|
+
/** Essential dignities of `body` in `sign`: domicile, exaltation, detriment,
|
|
45
|
+
* fall (the last two are the signs opposite domicile and exaltation). */
|
|
46
|
+
export declare function dignities(body: string, sign: number | string): string[];
|
|
47
|
+
export declare function dignityOf(engine: Engine, body: BodyId, jd: number, zodiac?: Zodiac): string[];
|
|
48
|
+
/** Diurnal when the Sun is above the horizon at the given place. */
|
|
49
|
+
export declare function isDayChart(engine: Engine, jd: number, lat: number, lonEast: number): boolean;
|
|
50
|
+
export declare function planetarySect(body: string): "diurnal" | "nocturnal" | null;
|
|
51
|
+
export declare function inSect(body: string, dayChart: boolean): boolean | null;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* astroengine derived -- standard chart derivations built on the validated
|
|
3
|
+
* primitives: returns, secondary progressions, solar arc directions, composite
|
|
4
|
+
* charts, Davison charts.
|
|
5
|
+
*
|
|
6
|
+
* These are constructions on top of apparent positions (already checked against
|
|
7
|
+
* Swiss Ephemeris), so this layer is time-mapping and arithmetic, not new
|
|
8
|
+
* ephemeris. Mirrors the Python reference (astroengine/derived.py); the
|
|
9
|
+
* golden fixtures pin the two together.
|
|
10
|
+
*/
|
|
11
|
+
import { mod, meanObliquity, jdTT, DEG } from "./core.js";
|
|
12
|
+
import { SIGNS } from "./chart.js";
|
|
13
|
+
import { crossings } from "./events.js";
|
|
14
|
+
import { azAlt } from "./pheno.js";
|
|
15
|
+
export const TROPICAL_YEAR = 365.24219; // mean tropical year, days
|
|
16
|
+
/** Shorter-arc midpoint of two longitudes (degrees). */
|
|
17
|
+
export function midpointLon(a, b) {
|
|
18
|
+
const d = mod(b - a + 180, 360) - 180; // signed shortest a -> b
|
|
19
|
+
return mod(a + d / 2, 360);
|
|
20
|
+
}
|
|
21
|
+
// ---------------------------------------------------------------- returns
|
|
22
|
+
/** UT JDs in [jdStart, jdEnd] when `body` returns to its natal longitude.
|
|
23
|
+
* Outer-planet returns can show three crossings around a retrograde loop. */
|
|
24
|
+
export function returns(engine, body, natalJd, jdStart, jdEnd, zodiac = "tropical", maxHits = 60) {
|
|
25
|
+
const natalLon = engine.longitude(body, natalJd, { zodiac });
|
|
26
|
+
return crossings(engine, body, natalLon, jdStart, jdEnd, zodiac, maxHits);
|
|
27
|
+
}
|
|
28
|
+
export function solarReturn(engine, natalJd, jdStart, jdEnd, zodiac = "tropical") {
|
|
29
|
+
return returns(engine, "sun", natalJd, jdStart, jdEnd, zodiac);
|
|
30
|
+
}
|
|
31
|
+
export function lunarReturn(engine, natalJd, jdStart, jdEnd, zodiac = "tropical") {
|
|
32
|
+
return returns(engine, "moon", natalJd, jdStart, jdEnd, zodiac);
|
|
33
|
+
}
|
|
34
|
+
// ----------------------------------------------- secondary progressions
|
|
35
|
+
/** The JD whose real positions are the secondary-progressed positions for the
|
|
36
|
+
* age (targetJd - natalJd): one day of motion per year of life. */
|
|
37
|
+
export function progressedJd(natalJd, targetJd, yearLength = TROPICAL_YEAR) {
|
|
38
|
+
return natalJd + (targetJd - natalJd) / yearLength;
|
|
39
|
+
}
|
|
40
|
+
export function progressedLongitude(engine, body, natalJd, targetJd, yearLength = TROPICAL_YEAR, zodiac = "tropical") {
|
|
41
|
+
return engine.longitude(body, progressedJd(natalJd, targetJd, yearLength), { zodiac });
|
|
42
|
+
}
|
|
43
|
+
// ----------------------------------------------------------- solar arc
|
|
44
|
+
/** Solar-arc direction angle (degrees, forward): how far the secondary-
|
|
45
|
+
* progressed Sun has moved from the natal Sun. Add it to any natal longitude. */
|
|
46
|
+
export function solarArc(engine, natalJd, targetJd, yearLength = TROPICAL_YEAR, zodiac = "tropical") {
|
|
47
|
+
const pjd = progressedJd(natalJd, targetJd, yearLength);
|
|
48
|
+
const natalSun = engine.longitude("sun", natalJd, { zodiac });
|
|
49
|
+
const progSun = engine.longitude("sun", pjd, { zodiac });
|
|
50
|
+
return mod(progSun - natalSun, 360); // Sun only moves forward
|
|
51
|
+
}
|
|
52
|
+
export function directedLongitude(engine, body, natalJd, targetJd, yearLength = TROPICAL_YEAR, zodiac = "tropical") {
|
|
53
|
+
const arc = solarArc(engine, natalJd, targetJd, yearLength, zodiac);
|
|
54
|
+
return mod(engine.longitude(body, natalJd, { zodiac }) + arc, 360);
|
|
55
|
+
}
|
|
56
|
+
// ----------------------------------------------------------- composite
|
|
57
|
+
/** Midpoint-method composite: the shorter-arc midpoint of each body's two
|
|
58
|
+
* longitudes. Angles compose the same way via midpointLon on the two ASC/MC. */
|
|
59
|
+
export function compositeLongitudes(engine, jdA, jdB, bodies, zodiac = "tropical") {
|
|
60
|
+
const out = {};
|
|
61
|
+
for (const body of bodies) {
|
|
62
|
+
const la = engine.longitude(body, jdA, { zodiac });
|
|
63
|
+
const lb = engine.longitude(body, jdB, { zodiac });
|
|
64
|
+
out[body] = midpointLon(la, lb);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
// ----------------------------------------------------------- davison
|
|
69
|
+
/** Time and place for a Davison relationship chart: the temporal midpoint and
|
|
70
|
+
* the geographic midpoint (mean latitude, shorter-arc mean longitude). Compute
|
|
71
|
+
* a normal chart at these to get the Davison chart. Returns [jd, lat, lonEast]. */
|
|
72
|
+
export function davisonParams(jdA, jdB, latA, lonEastA, latB, lonEastB) {
|
|
73
|
+
const midJd = 0.5 * (jdA + jdB);
|
|
74
|
+
const midLat = 0.5 * (latA + latB);
|
|
75
|
+
let midLon = midpointLon(mod(lonEastA, 360), mod(lonEastB, 360));
|
|
76
|
+
if (midLon > 180)
|
|
77
|
+
midLon -= 360; // back to (-180, 180] east-longitude
|
|
78
|
+
return [midJd, midLat, midLon];
|
|
79
|
+
}
|
|
80
|
+
// ----------------------------------------------------------- harmonics
|
|
81
|
+
/** The nth-harmonic longitude of a point: lon * n, wrapped to 360. */
|
|
82
|
+
export function harmonicLongitude(lon, n) {
|
|
83
|
+
return mod(lon * n, 360);
|
|
84
|
+
}
|
|
85
|
+
export function harmonicChart(engine, jd, bodies, n, zodiac = "tropical") {
|
|
86
|
+
const out = {};
|
|
87
|
+
for (const b of bodies)
|
|
88
|
+
out[b] = harmonicLongitude(engine.longitude(b, jd, { zodiac }), n);
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
// ----------------------------------------------------------- antiscia
|
|
92
|
+
/** Reflection across the solstice (Cancer-Capricorn) axis. */
|
|
93
|
+
export function antiscion(lon) {
|
|
94
|
+
return mod(180 - lon, 360);
|
|
95
|
+
}
|
|
96
|
+
/** Reflection across the equinox (Aries-Libra) axis. */
|
|
97
|
+
export function contraAntiscion(lon) {
|
|
98
|
+
return mod(-lon, 360);
|
|
99
|
+
}
|
|
100
|
+
/** Classify two declinations: parallel (same), contraparallel (opposite), null. */
|
|
101
|
+
export function declinationAspect(decA, decB, orb = 1.0) {
|
|
102
|
+
if (Math.abs(decA - decB) <= orb)
|
|
103
|
+
return "parallel";
|
|
104
|
+
if (Math.abs(decA + decB) <= orb)
|
|
105
|
+
return "contraparallel";
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
export function declinationAspects(engine, bodies, jd, orb = 1.0) {
|
|
109
|
+
const decs = {};
|
|
110
|
+
for (const b of bodies)
|
|
111
|
+
decs[b] = engine.position(b, jd).dec;
|
|
112
|
+
const out = [];
|
|
113
|
+
for (let i = 0; i < bodies.length; i++) {
|
|
114
|
+
for (let j = i + 1; j < bodies.length; j++) {
|
|
115
|
+
const kind = declinationAspect(decs[bodies[i]], decs[bodies[j]], orb);
|
|
116
|
+
if (kind)
|
|
117
|
+
out.push({ a: bodies[i], b: bodies[j], kind });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
// ----------------------------------------------------------- out of bounds
|
|
123
|
+
/** |declination| minus the mean obliquity, degrees. Positive = out of bounds. */
|
|
124
|
+
export function outOfBoundsMargin(engine, body, jd) {
|
|
125
|
+
const dec = engine.position(body, jd).dec;
|
|
126
|
+
const eps = meanObliquity(jdTT(jd)) / DEG;
|
|
127
|
+
return Math.abs(dec) - eps;
|
|
128
|
+
}
|
|
129
|
+
export function outOfBounds(engine, body, jd) {
|
|
130
|
+
return outOfBoundsMargin(engine, body, jd) > 0;
|
|
131
|
+
}
|
|
132
|
+
// ----------------------------------------------------------- dignities
|
|
133
|
+
const DOMICILE = {
|
|
134
|
+
sun: [4], moon: [3], mercury: [2, 5], venus: [1, 6],
|
|
135
|
+
mars: [0, 7], jupiter: [8, 11], saturn: [9, 10],
|
|
136
|
+
};
|
|
137
|
+
const EXALTATION = {
|
|
138
|
+
sun: 0, moon: 1, mercury: 5, venus: 11, mars: 9, jupiter: 3, saturn: 6,
|
|
139
|
+
};
|
|
140
|
+
function signIndex(sign) {
|
|
141
|
+
return typeof sign === "number" ? sign : SIGNS.indexOf(sign);
|
|
142
|
+
}
|
|
143
|
+
/** Essential dignities of `body` in `sign`: domicile, exaltation, detriment,
|
|
144
|
+
* fall (the last two are the signs opposite domicile and exaltation). */
|
|
145
|
+
export function dignities(body, sign) {
|
|
146
|
+
const idx = signIndex(sign);
|
|
147
|
+
const dom = DOMICILE[body] ?? [];
|
|
148
|
+
const out = [];
|
|
149
|
+
if (dom.includes(idx))
|
|
150
|
+
out.push("domicile");
|
|
151
|
+
if (EXALTATION[body] === idx)
|
|
152
|
+
out.push("exaltation");
|
|
153
|
+
if (dom.map((d) => mod(d + 6, 12)).includes(idx))
|
|
154
|
+
out.push("detriment");
|
|
155
|
+
if (body in EXALTATION && mod(EXALTATION[body] + 6, 12) === idx)
|
|
156
|
+
out.push("fall");
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
export function dignityOf(engine, body, jd, zodiac = "tropical") {
|
|
160
|
+
const lon = engine.longitude(body, jd, { zodiac });
|
|
161
|
+
return dignities(body, mod(Math.floor(lon / 30), 12));
|
|
162
|
+
}
|
|
163
|
+
// ----------------------------------------------------------- sect
|
|
164
|
+
const DIURNAL = new Set(["sun", "jupiter", "saturn"]);
|
|
165
|
+
const NOCTURNAL = new Set(["moon", "venus", "mars"]);
|
|
166
|
+
/** Diurnal when the Sun is above the horizon at the given place. */
|
|
167
|
+
export function isDayChart(engine, jd, lat, lonEast) {
|
|
168
|
+
const sun = engine.position("sun", jd);
|
|
169
|
+
const [, alt] = azAlt(engine.data, sun.lon, sun.lat, jd, lat, lonEast);
|
|
170
|
+
return alt > 0;
|
|
171
|
+
}
|
|
172
|
+
export function planetarySect(body) {
|
|
173
|
+
if (DIURNAL.has(body))
|
|
174
|
+
return "diurnal";
|
|
175
|
+
if (NOCTURNAL.has(body))
|
|
176
|
+
return "nocturnal";
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
export function inSect(body, dayChart) {
|
|
180
|
+
const s = planetarySect(body);
|
|
181
|
+
if (s === null)
|
|
182
|
+
return null;
|
|
183
|
+
return (s === "diurnal") === Boolean(dayChart);
|
|
184
|
+
}
|
package/dist/src/index.d.ts
CHANGED
package/dist/src/index.js
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface TurboBody {
|
|
2
|
+
seg_days: number;
|
|
3
|
+
segments: number[][];
|
|
4
|
+
}
|
|
5
|
+
export interface TurboPack {
|
|
6
|
+
jd0: number;
|
|
7
|
+
jd1: number;
|
|
8
|
+
degree: number;
|
|
9
|
+
zodiac: string;
|
|
10
|
+
bodies: Record<string, TurboBody>;
|
|
11
|
+
}
|
|
12
|
+
export declare class Turbo {
|
|
13
|
+
readonly jd0: number;
|
|
14
|
+
readonly jd1: number;
|
|
15
|
+
private readonly bodies;
|
|
16
|
+
constructor(pack: TurboPack);
|
|
17
|
+
has(body: string): boolean;
|
|
18
|
+
/** Apparent ecliptic longitude (degrees) from the turbo pack. */
|
|
19
|
+
longitude(body: string, jd: number): number;
|
|
20
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* astroengine turbo -- fast longitude evaluator for a turbo pack.
|
|
3
|
+
*
|
|
4
|
+
* A turbo pack is a segmented Chebyshev representation of the engine's apparent
|
|
5
|
+
* longitude, fit to the engine itself (see python/astroengine/turbo.py and
|
|
6
|
+
* fit_turbo.py). Evaluating a longitude is a couple of dozen multiply-adds, so
|
|
7
|
+
* a century-scale transit scan that calls it tens of thousands of times runs in
|
|
8
|
+
* milliseconds. The pack is data you mint for your range and bodies; this is
|
|
9
|
+
* the runtime-only evaluator (no fitting, no engine, no I/O).
|
|
10
|
+
*
|
|
11
|
+
* The evaluator mirrors the Python reference exactly, so both reproduce the
|
|
12
|
+
* pack bit-identically.
|
|
13
|
+
*/
|
|
14
|
+
import { mod } from "./core.js";
|
|
15
|
+
function clenshaw(coeffs, x) {
|
|
16
|
+
let b0 = 0;
|
|
17
|
+
let b1 = 0;
|
|
18
|
+
for (let i = coeffs.length - 1; i >= 1; i--) {
|
|
19
|
+
const t = 2 * x * b0 - b1 + coeffs[i];
|
|
20
|
+
b1 = b0;
|
|
21
|
+
b0 = t;
|
|
22
|
+
}
|
|
23
|
+
return x * b0 - b1 + coeffs[0];
|
|
24
|
+
}
|
|
25
|
+
export class Turbo {
|
|
26
|
+
jd0;
|
|
27
|
+
jd1;
|
|
28
|
+
bodies;
|
|
29
|
+
constructor(pack) {
|
|
30
|
+
this.jd0 = pack.jd0;
|
|
31
|
+
this.jd1 = pack.jd1;
|
|
32
|
+
this.bodies = pack.bodies;
|
|
33
|
+
}
|
|
34
|
+
has(body) {
|
|
35
|
+
return body in this.bodies;
|
|
36
|
+
}
|
|
37
|
+
/** Apparent ecliptic longitude (degrees) from the turbo pack. */
|
|
38
|
+
longitude(body, jd) {
|
|
39
|
+
const b = this.bodies[body];
|
|
40
|
+
if (!b)
|
|
41
|
+
throw new Error(`turbo: no pack for ${body}`);
|
|
42
|
+
if (jd < this.jd0 || jd > this.jd1) {
|
|
43
|
+
throw new Error(`jd ${jd} outside turbo range ${this.jd0}-${this.jd1}`);
|
|
44
|
+
}
|
|
45
|
+
const seg = b.seg_days;
|
|
46
|
+
const i = Math.min(Math.floor((jd - this.jd0) / seg), b.segments.length - 1);
|
|
47
|
+
const x = 2 * (jd - (this.jd0 + i * seg)) / seg - 1;
|
|
48
|
+
return mod(clenshaw(b.segments[i], x), 360);
|
|
49
|
+
}
|
|
50
|
+
}
|