caelus 0.6.0 → 0.7.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.
@@ -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
+ }
@@ -6,3 +6,4 @@ export * from "./events.js";
6
6
  export * from "./stars.js";
7
7
  export * from "./eclipses.js";
8
8
  export * from "./query.js";
9
+ export * from "./derived.js";
package/dist/src/index.js CHANGED
@@ -6,3 +6,4 @@ export * from "./events.js";
6
6
  export * from "./stars.js";
7
7
  export * from "./eclipses.js";
8
8
  export * from "./query.js";
9
+ export * from "./derived.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "caelus",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Astrological ephemeris engine. MIT, no AGPL, no ephemeris files. Checked against Swiss Ephemeris.",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",