caelus 0.11.0 → 0.13.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 +1 -1
- package/accuracy.json +1 -1
- package/dist/src/chart.d.ts +193 -13
- package/dist/src/chart.js +163 -13
- package/dist/src/compiler.d.ts +44 -3
- package/dist/src/compiler.js +44 -3
- package/dist/src/core.d.ts +33 -2
- package/dist/src/core.js +33 -2
- package/dist/src/derived.d.ts +91 -2
- package/dist/src/derived.js +91 -2
- package/dist/src/directions.d.ts +27 -0
- package/dist/src/directions.js +69 -0
- package/dist/src/eclipses.d.ts +17 -1
- package/dist/src/eclipses.js +17 -1
- package/dist/src/events.d.ts +53 -7
- package/dist/src/events.js +53 -7
- package/dist/src/features.d.ts +69 -7
- package/dist/src/features.js +69 -7
- package/dist/src/firdaria.d.ts +49 -0
- package/dist/src/firdaria.js +62 -0
- package/dist/src/houses.d.ts +13 -4
- package/dist/src/houses.js +13 -4
- package/dist/src/index.d.ts +9 -0
- package/dist/src/index.js +9 -0
- package/dist/src/lots.d.ts +18 -0
- package/dist/src/lots.js +52 -0
- package/dist/src/pheno.d.ts +16 -2
- package/dist/src/pheno.js +16 -2
- package/dist/src/profections.d.ts +27 -0
- package/dist/src/profections.js +49 -0
- package/dist/src/query.d.ts +47 -6
- package/dist/src/query.js +47 -6
- package/dist/src/releasing.d.ts +32 -0
- package/dist/src/releasing.js +109 -0
- package/dist/src/turbo.d.ts +35 -1
- package/dist/src/turbo.js +35 -1
- package/dist/src/vargas.d.ts +32 -0
- package/dist/src/vargas.js +52 -0
- package/dist/src/vedic.d.ts +66 -0
- package/dist/src/vedic.js +101 -0
- package/dist/src/yogas.d.ts +26 -0
- package/dist/src/yogas.js +39 -0
- package/dist/src/yogini.d.ts +54 -0
- package/dist/src/yogini.js +63 -0
- package/package.json +1 -1
package/dist/src/query.d.ts
CHANGED
|
@@ -6,8 +6,26 @@ export interface Predicate {
|
|
|
6
6
|
(engine: Engine, t: number): number;
|
|
7
7
|
bodies: Set<string>;
|
|
8
8
|
}
|
|
9
|
-
/**
|
|
10
|
-
*
|
|
9
|
+
/**
|
|
10
|
+
* A {@link Predicate} that holds while `body` is within `orb` degrees of an
|
|
11
|
+
* exact aspect to `target`. Feed it to {@link when} to find the time windows,
|
|
12
|
+
* or compose it with {@link allOf}/{@link anyOf}.
|
|
13
|
+
*
|
|
14
|
+
* @param body The transiting body.
|
|
15
|
+
* @param kind Aspect name, e.g. `"conjunction"`, `"square"`, `"trine"`,
|
|
16
|
+
* `"opposition"`, `"sextile"`.
|
|
17
|
+
* @param target The aspect target: a fixed ecliptic longitude in degrees (e.g.
|
|
18
|
+
* a natal point) or another body id (a mutual aspect).
|
|
19
|
+
* @param orb Half-width of the window in degrees. Defaults to `1.0`.
|
|
20
|
+
* @param zodiac Zodiac for the longitudes. Defaults to tropical.
|
|
21
|
+
* @returns A predicate, true while within orb of the exact aspect.
|
|
22
|
+
* @throws Error if `kind` is not a known aspect.
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* const natalSun = 79.3;
|
|
26
|
+
* when(engine, aspect("saturn", "square", natalSun, 1), jd0, jd1); // Saturn squares
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
11
29
|
export declare function aspect(body: BodyId, kind: string, target: number | BodyId, orb?: number, zodiac?: Zodiac): Predicate;
|
|
12
30
|
/** True while `body` is in `sign` (index 0=Aries..11=Pisces, or name). */
|
|
13
31
|
export declare function inSign(body: BodyId, sign: number | string, zodiac?: Zodiac): Predicate;
|
|
@@ -25,8 +43,31 @@ export interface WhenOptions {
|
|
|
25
43
|
step?: number;
|
|
26
44
|
maxIntervals?: number;
|
|
27
45
|
}
|
|
28
|
-
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
46
|
+
/**
|
|
47
|
+
* Solve for the time intervals within `[jdStart, jdEnd]` (UT Julian Days) where
|
|
48
|
+
* a {@link Predicate} holds. Predicates compose from {@link aspect},
|
|
49
|
+
* {@link inSign}, {@link retrograde}, {@link notRetrograde}, and the
|
|
50
|
+
* {@link allOf}/{@link anyOf} combinators, so one call answers questions like
|
|
51
|
+
* "when is Venus in Taurus while Mercury is direct?".
|
|
52
|
+
*
|
|
53
|
+
* Returned intervals are sorted and disjoint; endpoints touching the range
|
|
54
|
+
* bounds are clamped. The scan step defaults to 0.125 d when a fast body (Moon,
|
|
55
|
+
* nodes, Lilith) is involved and 1 d otherwise — override it with `opts.step`.
|
|
56
|
+
*
|
|
57
|
+
* @param engine The engine used to evaluate positions.
|
|
58
|
+
* @param predicate A celestial predicate (see {@link aspect}, {@link inSign}).
|
|
59
|
+
* @param jdStart Start of the search window, Julian Day (UT).
|
|
60
|
+
* @param jdEnd End of the search window, Julian Day (UT).
|
|
61
|
+
* @param opts `step` (scan resolution in days) and `maxIntervals`.
|
|
62
|
+
* @returns Sorted, disjoint `[startUt, endUt]` intervals where the predicate is
|
|
63
|
+
* true.
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* const windows = when(
|
|
67
|
+
* engine,
|
|
68
|
+
* allOf(inSign("venus", "Taurus"), notRetrograde("mercury")),
|
|
69
|
+
* julianDay(2025, 1, 1), julianDay(2026, 1, 1),
|
|
70
|
+
* );
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
32
73
|
export declare function when(engine: Engine, predicate: Predicate, jdStart: number, jdEnd: number, opts?: WhenOptions): Interval[];
|
package/dist/src/query.js
CHANGED
|
@@ -35,8 +35,26 @@ function mk(fn, bodies) {
|
|
|
35
35
|
return p;
|
|
36
36
|
}
|
|
37
37
|
// ---------------------------------------------------------------- predicates
|
|
38
|
-
/**
|
|
39
|
-
*
|
|
38
|
+
/**
|
|
39
|
+
* A {@link Predicate} that holds while `body` is within `orb` degrees of an
|
|
40
|
+
* exact aspect to `target`. Feed it to {@link when} to find the time windows,
|
|
41
|
+
* or compose it with {@link allOf}/{@link anyOf}.
|
|
42
|
+
*
|
|
43
|
+
* @param body The transiting body.
|
|
44
|
+
* @param kind Aspect name, e.g. `"conjunction"`, `"square"`, `"trine"`,
|
|
45
|
+
* `"opposition"`, `"sextile"`.
|
|
46
|
+
* @param target The aspect target: a fixed ecliptic longitude in degrees (e.g.
|
|
47
|
+
* a natal point) or another body id (a mutual aspect).
|
|
48
|
+
* @param orb Half-width of the window in degrees. Defaults to `1.0`.
|
|
49
|
+
* @param zodiac Zodiac for the longitudes. Defaults to tropical.
|
|
50
|
+
* @returns A predicate, true while within orb of the exact aspect.
|
|
51
|
+
* @throws Error if `kind` is not a known aspect.
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* const natalSun = 79.3;
|
|
55
|
+
* when(engine, aspect("saturn", "square", natalSun, 1), jd0, jd1); // Saturn squares
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
40
58
|
export function aspect(body, kind, target, orb = 1.0, zodiac = "tropical") {
|
|
41
59
|
const ang = QUERY_ASPECTS[kind];
|
|
42
60
|
if (ang === undefined)
|
|
@@ -117,10 +135,33 @@ function bisect(f, a, b, tol = 1e-6) {
|
|
|
117
135
|
}
|
|
118
136
|
return 0.5 * (a + b);
|
|
119
137
|
}
|
|
120
|
-
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
138
|
+
/**
|
|
139
|
+
* Solve for the time intervals within `[jdStart, jdEnd]` (UT Julian Days) where
|
|
140
|
+
* a {@link Predicate} holds. Predicates compose from {@link aspect},
|
|
141
|
+
* {@link inSign}, {@link retrograde}, {@link notRetrograde}, and the
|
|
142
|
+
* {@link allOf}/{@link anyOf} combinators, so one call answers questions like
|
|
143
|
+
* "when is Venus in Taurus while Mercury is direct?".
|
|
144
|
+
*
|
|
145
|
+
* Returned intervals are sorted and disjoint; endpoints touching the range
|
|
146
|
+
* bounds are clamped. The scan step defaults to 0.125 d when a fast body (Moon,
|
|
147
|
+
* nodes, Lilith) is involved and 1 d otherwise — override it with `opts.step`.
|
|
148
|
+
*
|
|
149
|
+
* @param engine The engine used to evaluate positions.
|
|
150
|
+
* @param predicate A celestial predicate (see {@link aspect}, {@link inSign}).
|
|
151
|
+
* @param jdStart Start of the search window, Julian Day (UT).
|
|
152
|
+
* @param jdEnd End of the search window, Julian Day (UT).
|
|
153
|
+
* @param opts `step` (scan resolution in days) and `maxIntervals`.
|
|
154
|
+
* @returns Sorted, disjoint `[startUt, endUt]` intervals where the predicate is
|
|
155
|
+
* true.
|
|
156
|
+
* @example
|
|
157
|
+
* ```ts
|
|
158
|
+
* const windows = when(
|
|
159
|
+
* engine,
|
|
160
|
+
* allOf(inSign("venus", "Taurus"), notRetrograde("mercury")),
|
|
161
|
+
* julianDay(2025, 1, 1), julianDay(2026, 1, 1),
|
|
162
|
+
* );
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
124
165
|
export function when(engine, predicate, jdStart, jdEnd, opts = {}) {
|
|
125
166
|
let step = opts.step;
|
|
126
167
|
if (step === undefined) {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Engine, Zodiac } from "./chart.js";
|
|
2
|
+
/** Period length in 360-day years for each sign, Aries..Pisces. */
|
|
3
|
+
export declare const ZR_PERIODS: readonly [15, 8, 20, 25, 19, 20, 8, 15, 12, 27, 30, 12];
|
|
4
|
+
/** Days per period unit at each level (each a twelfth of the one above). */
|
|
5
|
+
export declare const LEVEL_UNIT: Record<number, number>;
|
|
6
|
+
export interface ZrPeriod {
|
|
7
|
+
level: number;
|
|
8
|
+
sign: string;
|
|
9
|
+
lord: string;
|
|
10
|
+
start: number;
|
|
11
|
+
end: number;
|
|
12
|
+
/** True when this period is the loosing of the bond (jumped to the opposite sign). */
|
|
13
|
+
lb: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** Flat timeline of releasing periods down to `maxLevel` over `horizonYears`
|
|
16
|
+
* (360-day years) from birth. */
|
|
17
|
+
export declare function zrRelease(lotSign: number, natalJd: number, maxLevel?: number, horizonYears?: number): ZrPeriod[];
|
|
18
|
+
export interface ZrActive {
|
|
19
|
+
l1: string;
|
|
20
|
+
l2: string;
|
|
21
|
+
l3: string;
|
|
22
|
+
l4: string;
|
|
23
|
+
}
|
|
24
|
+
/** The L1..L4 releasing signs active at `targetJd`; null outside the span. */
|
|
25
|
+
export declare function zrActive(lotSign: number, natalJd: number, targetJd: number): ZrActive | null;
|
|
26
|
+
/** Zodiacal releasing active at `targetJd`, releasing from the Lot of Spirit
|
|
27
|
+
* (default) or Fortune of the natal chart. */
|
|
28
|
+
export declare function zrAt(engine: Engine, natalJd: number, targetJd: number, lat: number, lonEast: number, lot?: "spirit" | "fortune", zodiac?: Zodiac): {
|
|
29
|
+
lot: string;
|
|
30
|
+
lot_sign: string;
|
|
31
|
+
day: boolean;
|
|
32
|
+
} & Partial<ZrActive>;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* astroengine releasing -- zodiacal releasing (aphesis), the Hellenistic
|
|
3
|
+
* time-lord technique from Vettius Valens, released from a Lot (usually Spirit
|
|
4
|
+
* or Fortune).
|
|
5
|
+
*
|
|
6
|
+
* From the Lot's sign, periods release sign by sign. A sign's period length is
|
|
7
|
+
* its planetary minor years (Aries 15, Taurus 8, Gemini 20, Cancer 25, Leo 19,
|
|
8
|
+
* Virgo 20, Libra 8, Scorpio 15, Sagittarius 12, Capricorn 27, Aquarius 30,
|
|
9
|
+
* Pisces 12). Convention: 360-day years, each level a twelfth of the one above
|
|
10
|
+
* (L1 = period x 360 days, L2 = x 30, L3 = x 2.5, L4 = x 2.5/12). Within a
|
|
11
|
+
* period the next level releases from the same sign and fills it (last
|
|
12
|
+
* sub-period truncates at the boundary); when a sub-level returns to the sign it
|
|
13
|
+
* began on it looses the bond, jumping to the opposite sign (+6) once. Mirrors
|
|
14
|
+
* the Python reference (astroengine/releasing.py); the golden fixtures pin the
|
|
15
|
+
* two together.
|
|
16
|
+
*/
|
|
17
|
+
import { SIGNS } from "./chart.js";
|
|
18
|
+
import { isDayChart } from "./derived.js";
|
|
19
|
+
import { lotSpirit, lotFortune } from "./lots.js";
|
|
20
|
+
import { SIGN_RULERS } from "./profections.js";
|
|
21
|
+
/** Period length in 360-day years for each sign, Aries..Pisces. */
|
|
22
|
+
export const ZR_PERIODS = [15, 8, 20, 25, 19, 20, 8, 15, 12, 27, 30, 12];
|
|
23
|
+
/** Days per period unit at each level (each a twelfth of the one above). */
|
|
24
|
+
export const LEVEL_UNIT = { 1: 360, 2: 30, 3: 2.5, 4: 2.5 / 12 };
|
|
25
|
+
const FULL_CYCLE = ZR_PERIODS.reduce((a, b) => a + b, 0) * 360; // one full L1 cycle, days
|
|
26
|
+
const EPS = 1e-9;
|
|
27
|
+
function release(out, level, maxLevel, startSign, spanStart, spanEnd, horizon) {
|
|
28
|
+
const unit = LEVEL_UNIT[level];
|
|
29
|
+
let sign = startSign;
|
|
30
|
+
let lb = false;
|
|
31
|
+
let pendingLb = false;
|
|
32
|
+
let cur = spanStart;
|
|
33
|
+
while (cur < spanEnd - EPS && cur < horizon - EPS) {
|
|
34
|
+
const plen = ZR_PERIODS[sign] * unit;
|
|
35
|
+
const subEnd = Math.min(cur + plen, spanEnd, horizon);
|
|
36
|
+
out.push({ level, sign: SIGNS[sign], lord: SIGN_RULERS[sign], start: cur, end: subEnd, lb: pendingLb });
|
|
37
|
+
if (level < maxLevel) {
|
|
38
|
+
release(out, level + 1, maxLevel, sign, cur, Math.min(cur + plen, spanEnd), horizon);
|
|
39
|
+
}
|
|
40
|
+
cur += plen;
|
|
41
|
+
pendingLb = false;
|
|
42
|
+
const nxt = (sign + 1) % 12;
|
|
43
|
+
if (nxt === startSign && !lb) {
|
|
44
|
+
sign = (startSign + 6) % 12;
|
|
45
|
+
lb = true;
|
|
46
|
+
pendingLb = true;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
sign = nxt;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Flat timeline of releasing periods down to `maxLevel` over `horizonYears`
|
|
54
|
+
* (360-day years) from birth. */
|
|
55
|
+
export function zrRelease(lotSign, natalJd, maxLevel = 2, horizonYears = 100) {
|
|
56
|
+
const out = [];
|
|
57
|
+
const horizon = natalJd + horizonYears * 360;
|
|
58
|
+
release(out, 1, maxLevel, lotSign, natalJd, natalJd + FULL_CYCLE, horizon);
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
/** The (sign, start, end) of the sub-period containing `target`, or null. */
|
|
62
|
+
function subAt(unit, startSign, spanStart, spanEnd, target) {
|
|
63
|
+
let sign = startSign;
|
|
64
|
+
let lb = false;
|
|
65
|
+
let cur = spanStart;
|
|
66
|
+
while (cur < spanEnd - EPS) {
|
|
67
|
+
const plen = ZR_PERIODS[sign] * unit;
|
|
68
|
+
const subEnd = Math.min(cur + plen, spanEnd);
|
|
69
|
+
if (cur <= target && target < subEnd)
|
|
70
|
+
return [sign, cur, subEnd];
|
|
71
|
+
cur += plen;
|
|
72
|
+
const nxt = (sign + 1) % 12;
|
|
73
|
+
if (nxt === startSign && !lb) {
|
|
74
|
+
sign = (startSign + 6) % 12;
|
|
75
|
+
lb = true;
|
|
76
|
+
}
|
|
77
|
+
else
|
|
78
|
+
sign = nxt;
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
/** The L1..L4 releasing signs active at `targetJd`; null outside the span. */
|
|
83
|
+
export function zrActive(lotSign, natalJd, targetJd) {
|
|
84
|
+
const l1 = subAt(360, lotSign, natalJd, natalJd + FULL_CYCLE, targetJd);
|
|
85
|
+
if (l1 === null)
|
|
86
|
+
return null;
|
|
87
|
+
const l2 = subAt(30, l1[0], l1[1], l1[2], targetJd);
|
|
88
|
+
if (l2 === null)
|
|
89
|
+
return null;
|
|
90
|
+
const l3 = subAt(2.5, l2[0], l2[1], l2[2], targetJd);
|
|
91
|
+
if (l3 === null)
|
|
92
|
+
return null;
|
|
93
|
+
const l4 = subAt(2.5 / 12, l3[0], l3[1], l3[2], targetJd);
|
|
94
|
+
if (l4 === null)
|
|
95
|
+
return null;
|
|
96
|
+
return { l1: SIGNS[l1[0]], l2: SIGNS[l2[0]], l3: SIGNS[l3[0]], l4: SIGNS[l4[0]] };
|
|
97
|
+
}
|
|
98
|
+
/** Zodiacal releasing active at `targetJd`, releasing from the Lot of Spirit
|
|
99
|
+
* (default) or Fortune of the natal chart. */
|
|
100
|
+
export function zrAt(engine, natalJd, targetJd, lat, lonEast, lot = "spirit", zodiac = "tropical") {
|
|
101
|
+
const asc = engine.chartAt(natalJd, lat, lonEast, { zodiac }).angles.asc;
|
|
102
|
+
const day = isDayChart(engine, natalJd, lat, lonEast);
|
|
103
|
+
const sun = engine.longitude("sun", natalJd, { zodiac });
|
|
104
|
+
const moon = engine.longitude("moon", natalJd, { zodiac });
|
|
105
|
+
const lotLon = (lot === "spirit" ? lotSpirit : lotFortune)(asc, sun, moon, day);
|
|
106
|
+
const lotSign = ((Math.floor(lotLon / 30) % 12) + 12) % 12;
|
|
107
|
+
const active = zrActive(lotSign, natalJd, targetJd) ?? {};
|
|
108
|
+
return { lot, lot_sign: SIGNS[lotSign], day, ...active };
|
|
109
|
+
}
|
package/dist/src/turbo.d.ts
CHANGED
|
@@ -9,12 +9,46 @@ export interface TurboPack {
|
|
|
9
9
|
zodiac: string;
|
|
10
10
|
bodies: Record<string, TurboBody>;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Runtime evaluator for a **turbo pack** — a segmented Chebyshev fit of the
|
|
14
|
+
* engine's apparent longitude over a fixed range and body set. Evaluating a
|
|
15
|
+
* longitude costs a couple dozen multiply-adds, so a century-scale transit scan
|
|
16
|
+
* that calls it tens of thousands of times runs in milliseconds. Construct one
|
|
17
|
+
* from a pack you minted offline; it does no fitting, no I/O, and needs no
|
|
18
|
+
* {@link Engine}.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* const turbo = new Turbo(pack); // a TurboPack generated for your range/bodies
|
|
23
|
+
* if (turbo.has("mars")) turbo.longitude("mars", jd);
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
12
26
|
export declare class Turbo {
|
|
27
|
+
/** Start of the pack's valid Julian Day (UT) range. */
|
|
13
28
|
readonly jd0: number;
|
|
29
|
+
/** End of the pack's valid Julian Day (UT) range. */
|
|
14
30
|
readonly jd1: number;
|
|
15
31
|
private readonly bodies;
|
|
32
|
+
/**
|
|
33
|
+
* @param pack A {@link TurboPack}: the fitted segments plus its `jd0`/`jd1`
|
|
34
|
+
* range, minted offline for your bodies and span.
|
|
35
|
+
*/
|
|
16
36
|
constructor(pack: TurboPack);
|
|
37
|
+
/**
|
|
38
|
+
* Whether this pack can evaluate a given body.
|
|
39
|
+
*
|
|
40
|
+
* @param body Body id to test.
|
|
41
|
+
* @returns `true` if {@link Turbo.longitude} accepts `body`.
|
|
42
|
+
*/
|
|
17
43
|
has(body: string): boolean;
|
|
18
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Apparent ecliptic longitude (degrees) of a body from the turbo pack, in the
|
|
46
|
+
* pack's own zodiac. The hot path for bulk scans.
|
|
47
|
+
*
|
|
48
|
+
* @param body A body id the pack contains (see {@link Turbo.has}).
|
|
49
|
+
* @param jd Julian Day (UT), within `[jd0, jd1]`.
|
|
50
|
+
* @returns Ecliptic longitude in degrees, `[0, 360)`.
|
|
51
|
+
* @throws Error if the pack lacks `body`, or `jd` is outside `[jd0, jd1]`.
|
|
52
|
+
*/
|
|
19
53
|
longitude(body: string, jd: number): number;
|
|
20
54
|
}
|
package/dist/src/turbo.js
CHANGED
|
@@ -22,19 +22,53 @@ function clenshaw(coeffs, x) {
|
|
|
22
22
|
}
|
|
23
23
|
return x * b0 - b1 + coeffs[0];
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Runtime evaluator for a **turbo pack** — a segmented Chebyshev fit of the
|
|
27
|
+
* engine's apparent longitude over a fixed range and body set. Evaluating a
|
|
28
|
+
* longitude costs a couple dozen multiply-adds, so a century-scale transit scan
|
|
29
|
+
* that calls it tens of thousands of times runs in milliseconds. Construct one
|
|
30
|
+
* from a pack you minted offline; it does no fitting, no I/O, and needs no
|
|
31
|
+
* {@link Engine}.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const turbo = new Turbo(pack); // a TurboPack generated for your range/bodies
|
|
36
|
+
* if (turbo.has("mars")) turbo.longitude("mars", jd);
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
25
39
|
export class Turbo {
|
|
40
|
+
/** Start of the pack's valid Julian Day (UT) range. */
|
|
26
41
|
jd0;
|
|
42
|
+
/** End of the pack's valid Julian Day (UT) range. */
|
|
27
43
|
jd1;
|
|
28
44
|
bodies;
|
|
45
|
+
/**
|
|
46
|
+
* @param pack A {@link TurboPack}: the fitted segments plus its `jd0`/`jd1`
|
|
47
|
+
* range, minted offline for your bodies and span.
|
|
48
|
+
*/
|
|
29
49
|
constructor(pack) {
|
|
30
50
|
this.jd0 = pack.jd0;
|
|
31
51
|
this.jd1 = pack.jd1;
|
|
32
52
|
this.bodies = pack.bodies;
|
|
33
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Whether this pack can evaluate a given body.
|
|
56
|
+
*
|
|
57
|
+
* @param body Body id to test.
|
|
58
|
+
* @returns `true` if {@link Turbo.longitude} accepts `body`.
|
|
59
|
+
*/
|
|
34
60
|
has(body) {
|
|
35
61
|
return body in this.bodies;
|
|
36
62
|
}
|
|
37
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* Apparent ecliptic longitude (degrees) of a body from the turbo pack, in the
|
|
65
|
+
* pack's own zodiac. The hot path for bulk scans.
|
|
66
|
+
*
|
|
67
|
+
* @param body A body id the pack contains (see {@link Turbo.has}).
|
|
68
|
+
* @param jd Julian Day (UT), within `[jd0, jd1]`.
|
|
69
|
+
* @returns Ecliptic longitude in degrees, `[0, 360)`.
|
|
70
|
+
* @throws Error if the pack lacks `body`, or `jd` is outside `[jd0, jd1]`.
|
|
71
|
+
*/
|
|
38
72
|
longitude(body, jd) {
|
|
39
73
|
const b = this.bodies[body];
|
|
40
74
|
if (!b)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* astroengine vargas -- Vedic divisional charts (vargas).
|
|
3
|
+
*
|
|
4
|
+
* A varga D-n divides each 30-degree sign into n equal parts and maps each part
|
|
5
|
+
* to a sign by a classical (Parashari) rule. This covers the unambiguous,
|
|
6
|
+
* textbook set: D1 (rasi), D3 (drekkana), D9 (navamsa), D10 (dasamsa), and D12
|
|
7
|
+
* (dwadasamsa); the contested hora (D2) and unequal trimsamsa (D30) are left to
|
|
8
|
+
* a later step. Rules (rasi/div 0-based; an "odd sign" is the 1st, 3rd, ... =
|
|
9
|
+
* even rasi index): D1 the sign; D3 (rasi + 4*div); D9 element start
|
|
10
|
+
* ([Aries, Capricorn, Libra, Cancer] by element) + div; D10 odd (rasi + div),
|
|
11
|
+
* even (rasi + 8 + div); D12 (rasi + div). Computed from rasi = floor(lon/30)
|
|
12
|
+
* and div = floor(within/(30/n)) so sign boundaries stay robust and
|
|
13
|
+
* `Math.floor` matches Python's `math.floor`. Built on the validated sidereal
|
|
14
|
+
* longitudes. Mirrors the Python reference (astroengine/vargas.py).
|
|
15
|
+
*/
|
|
16
|
+
import { Engine, BodyId, Zodiac } from "./chart.js";
|
|
17
|
+
/** Supported divisions. */
|
|
18
|
+
export declare const VARGA_DIVISIONS: readonly [1, 3, 9, 10, 12];
|
|
19
|
+
export interface Varga {
|
|
20
|
+
varga: number;
|
|
21
|
+
rasi: string;
|
|
22
|
+
rasi_index: number;
|
|
23
|
+
sign: string;
|
|
24
|
+
sign_index: number;
|
|
25
|
+
division: number;
|
|
26
|
+
}
|
|
27
|
+
/** The varga D-n placement of a sidereal longitude. */
|
|
28
|
+
export declare function varga(siderealLon: number, n: number): Varga;
|
|
29
|
+
/** The varga D-n of a body (default the Moon) at jd, in a sidereal zodiac. */
|
|
30
|
+
export declare function vargaAt(engine: Engine, jdUt: number, n: number, body?: BodyId, zodiac?: Zodiac): Varga;
|
|
31
|
+
/** The full divisional chart D-n at jd: the varga sign of each body. */
|
|
32
|
+
export declare function vargaChart(engine: Engine, jdUt: number, n: number, bodies?: BodyId[], zodiac?: Zodiac): Record<string, Varga>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* astroengine vargas -- Vedic divisional charts (vargas).
|
|
3
|
+
*
|
|
4
|
+
* A varga D-n divides each 30-degree sign into n equal parts and maps each part
|
|
5
|
+
* to a sign by a classical (Parashari) rule. This covers the unambiguous,
|
|
6
|
+
* textbook set: D1 (rasi), D3 (drekkana), D9 (navamsa), D10 (dasamsa), and D12
|
|
7
|
+
* (dwadasamsa); the contested hora (D2) and unequal trimsamsa (D30) are left to
|
|
8
|
+
* a later step. Rules (rasi/div 0-based; an "odd sign" is the 1st, 3rd, ... =
|
|
9
|
+
* even rasi index): D1 the sign; D3 (rasi + 4*div); D9 element start
|
|
10
|
+
* ([Aries, Capricorn, Libra, Cancer] by element) + div; D10 odd (rasi + div),
|
|
11
|
+
* even (rasi + 8 + div); D12 (rasi + div). Computed from rasi = floor(lon/30)
|
|
12
|
+
* and div = floor(within/(30/n)) so sign boundaries stay robust and
|
|
13
|
+
* `Math.floor` matches Python's `math.floor`. Built on the validated sidereal
|
|
14
|
+
* longitudes. Mirrors the Python reference (astroengine/vargas.py).
|
|
15
|
+
*/
|
|
16
|
+
import { BODIES, SIGNS } from "./chart.js";
|
|
17
|
+
/** Element start sign for the navamsa (fire, earth, air, water by rasi % 4). */
|
|
18
|
+
const NAVAMSA_START = [0, 9, 6, 3];
|
|
19
|
+
/** Supported divisions. */
|
|
20
|
+
export const VARGA_DIVISIONS = [1, 3, 9, 10, 12];
|
|
21
|
+
function vargaSign(rasi, div, n) {
|
|
22
|
+
switch (n) {
|
|
23
|
+
case 1: return rasi;
|
|
24
|
+
case 3: return (rasi + 4 * div) % 12;
|
|
25
|
+
case 9: return (NAVAMSA_START[rasi % 4] + div) % 12;
|
|
26
|
+
case 10: return rasi % 2 === 0 ? (rasi + div) % 12 : (rasi + 8 + div) % 12;
|
|
27
|
+
case 12: return (rasi + div) % 12;
|
|
28
|
+
default: throw new Error(`unsupported varga D${n}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** The varga D-n placement of a sidereal longitude. */
|
|
32
|
+
export function varga(siderealLon, n) {
|
|
33
|
+
const lon = ((siderealLon % 360) + 360) % 360;
|
|
34
|
+
const rasi = Math.floor(lon / 30) % 12;
|
|
35
|
+
const within = lon - rasi * 30;
|
|
36
|
+
let div = Math.floor(within / (30 / n));
|
|
37
|
+
if (div >= n)
|
|
38
|
+
div = n - 1; // guard a boundary rounding to n
|
|
39
|
+
const s = vargaSign(rasi, div, n);
|
|
40
|
+
return { varga: n, rasi: SIGNS[rasi], rasi_index: rasi, sign: SIGNS[s], sign_index: s, division: div + 1 };
|
|
41
|
+
}
|
|
42
|
+
/** The varga D-n of a body (default the Moon) at jd, in a sidereal zodiac. */
|
|
43
|
+
export function vargaAt(engine, jdUt, n, body = "moon", zodiac = "sidereal:lahiri") {
|
|
44
|
+
return varga(engine.longitude(body, jdUt, { zodiac }), n);
|
|
45
|
+
}
|
|
46
|
+
/** The full divisional chart D-n at jd: the varga sign of each body. */
|
|
47
|
+
export function vargaChart(engine, jdUt, n, bodies = BODIES, zodiac = "sidereal:lahiri") {
|
|
48
|
+
const out = {};
|
|
49
|
+
for (const b of bodies)
|
|
50
|
+
out[b] = varga(engine.longitude(b, jdUt, { zodiac }), n);
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* astroengine vedic -- the Vedic/Jyotish layer: nakshatras and the Vimshottari
|
|
3
|
+
* dasha, built on the already-validated sidereal longitudes.
|
|
4
|
+
*
|
|
5
|
+
* A nakshatra is one of 27 equal lunar mansions of 13 deg 20' in the sidereal
|
|
6
|
+
* zodiac, each with four padas and a ruling planet cycling Ketu, Venus, Sun,
|
|
7
|
+
* Moon, Mars, Rahu, Jupiter, Saturn, Mercury. The Vimshottari dasha is a
|
|
8
|
+
* 120-year sequence of planetary periods in that order; the starting dasha is
|
|
9
|
+
* the lord of the Moon's birth nakshatra, with the elapsed portion set by how
|
|
10
|
+
* far the Moon has moved through it. Mahadashas subdivide into antardashas and
|
|
11
|
+
* pratyantardashas of the nine lords, proportional to their years. Nakshatra
|
|
12
|
+
* placement is exact division of the sidereal longitude; the dasha year is a
|
|
13
|
+
* fixed 365.25 days by default (the common Jyotish convention). Mirrors the
|
|
14
|
+
* Python reference (astroengine/vedic.py); the golden fixtures pin the two.
|
|
15
|
+
*/
|
|
16
|
+
import { Engine, BodyId, Zodiac } from "./chart.js";
|
|
17
|
+
export declare const NAKSHATRAS: readonly ["Ashwini", "Bharani", "Krittika", "Rohini", "Mrigashira", "Ardra", "Punarvasu", "Pushya", "Ashlesha", "Magha", "Purva Phalguni", "Uttara Phalguni", "Hasta", "Chitra", "Swati", "Vishakha", "Anuradha", "Jyeshtha", "Mula", "Purva Ashadha", "Uttara Ashadha", "Shravana", "Dhanishta", "Shatabhisha", "Purva Bhadrapada", "Uttara Bhadrapada", "Revati"];
|
|
18
|
+
/** The Vimshottari order and each lord's period in years (totalling 120). */
|
|
19
|
+
export declare const VIMSHOTTARI_ORDER: readonly ["ketu", "venus", "sun", "moon", "mars", "rahu", "jupiter", "saturn", "mercury"];
|
|
20
|
+
export declare const VIMSHOTTARI_YEARS: Record<(typeof VIMSHOTTARI_ORDER)[number], number>;
|
|
21
|
+
export declare const NAK_SPAN: number;
|
|
22
|
+
export declare const DASHA_YEAR = 365.25;
|
|
23
|
+
export interface Nakshatra {
|
|
24
|
+
index: number;
|
|
25
|
+
name: string;
|
|
26
|
+
pada: number;
|
|
27
|
+
lord: string;
|
|
28
|
+
/** Degrees into the nakshatra, 0..13.333. */
|
|
29
|
+
pos: number;
|
|
30
|
+
}
|
|
31
|
+
/** The nakshatra of a sidereal longitude. */
|
|
32
|
+
export declare function nakshatra(siderealLon: number): Nakshatra;
|
|
33
|
+
/** The nakshatra of a body (default the Moon) at jd, in a sidereal zodiac. */
|
|
34
|
+
export declare function nakshatraAt(engine: Engine, jdUt: number, body?: BodyId, zodiac?: Zodiac): Nakshatra;
|
|
35
|
+
export interface DashaSub {
|
|
36
|
+
lord: string;
|
|
37
|
+
start: number;
|
|
38
|
+
end: number;
|
|
39
|
+
}
|
|
40
|
+
export interface Dasha {
|
|
41
|
+
level: number;
|
|
42
|
+
lord: string;
|
|
43
|
+
start: number;
|
|
44
|
+
end: number;
|
|
45
|
+
sub: DashaSub[];
|
|
46
|
+
}
|
|
47
|
+
export interface DashaTimeline {
|
|
48
|
+
start_lord: string;
|
|
49
|
+
balance_years: number;
|
|
50
|
+
dashas: Dasha[];
|
|
51
|
+
}
|
|
52
|
+
/** The Vimshottari dasha timeline from the Moon's sidereal longitude. */
|
|
53
|
+
export declare function vimshottariDashas(moonLon: number, natalJd: number, levels?: number, yearLength?: number, count?: number): DashaTimeline;
|
|
54
|
+
export interface DashaActive {
|
|
55
|
+
maha: string;
|
|
56
|
+
antar: string | null;
|
|
57
|
+
pratyantar: string | null;
|
|
58
|
+
}
|
|
59
|
+
/** The mahadasha, antardasha, and pratyantardasha lords active at targetJd. */
|
|
60
|
+
export declare function vimshottariActive(moonLon: number, natalJd: number, targetJd: number, yearLength?: number): DashaActive | null;
|
|
61
|
+
/** Vimshottari dasha active at targetJd, from the natal Moon's nakshatra. */
|
|
62
|
+
export declare function vimshottariAt(engine: Engine, natalJd: number, targetJd: number, zodiac?: Zodiac, yearLength?: number): {
|
|
63
|
+
moon_nakshatra: string;
|
|
64
|
+
moon_pada: number;
|
|
65
|
+
start_lord: string;
|
|
66
|
+
} & Partial<DashaActive>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
export const NAKSHATRAS = [
|
|
2
|
+
"Ashwini", "Bharani", "Krittika", "Rohini", "Mrigashira", "Ardra",
|
|
3
|
+
"Punarvasu", "Pushya", "Ashlesha", "Magha", "Purva Phalguni",
|
|
4
|
+
"Uttara Phalguni", "Hasta", "Chitra", "Swati", "Vishakha", "Anuradha",
|
|
5
|
+
"Jyeshtha", "Mula", "Purva Ashadha", "Uttara Ashadha", "Shravana",
|
|
6
|
+
"Dhanishta", "Shatabhisha", "Purva Bhadrapada", "Uttara Bhadrapada", "Revati",
|
|
7
|
+
];
|
|
8
|
+
/** The Vimshottari order and each lord's period in years (totalling 120). */
|
|
9
|
+
export const VIMSHOTTARI_ORDER = [
|
|
10
|
+
"ketu", "venus", "sun", "moon", "mars", "rahu", "jupiter", "saturn", "mercury",
|
|
11
|
+
];
|
|
12
|
+
export const VIMSHOTTARI_YEARS = {
|
|
13
|
+
ketu: 7, venus: 20, sun: 6, moon: 10, mars: 7, rahu: 18, jupiter: 16, saturn: 19, mercury: 17,
|
|
14
|
+
};
|
|
15
|
+
export const NAK_SPAN = 360 / 27; // 13 deg 20'
|
|
16
|
+
const VIMSHOTTARI_TOTAL = 120;
|
|
17
|
+
export const DASHA_YEAR = 365.25; // days per dasha-year (common Jyotish convention)
|
|
18
|
+
function mod360(x) {
|
|
19
|
+
return ((x % 360) + 360) % 360;
|
|
20
|
+
}
|
|
21
|
+
/** The nakshatra of a sidereal longitude. */
|
|
22
|
+
export function nakshatra(siderealLon) {
|
|
23
|
+
const lon = mod360(siderealLon);
|
|
24
|
+
const i = Math.floor(lon / NAK_SPAN) % 27;
|
|
25
|
+
const pos = lon - i * NAK_SPAN;
|
|
26
|
+
const pada = Math.floor(pos / (NAK_SPAN / 4)) + 1;
|
|
27
|
+
return { index: i, name: NAKSHATRAS[i], pada, lord: VIMSHOTTARI_ORDER[i % 9], pos };
|
|
28
|
+
}
|
|
29
|
+
/** The nakshatra of a body (default the Moon) at jd, in a sidereal zodiac. */
|
|
30
|
+
export function nakshatraAt(engine, jdUt, body = "moon", zodiac = "sidereal:lahiri") {
|
|
31
|
+
return nakshatra(engine.longitude(body, jdUt, { zodiac }));
|
|
32
|
+
}
|
|
33
|
+
/** The Vimshottari dasha timeline from the Moon's sidereal longitude. */
|
|
34
|
+
export function vimshottariDashas(moonLon, natalJd, levels = 2, yearLength = DASHA_YEAR, count = 9) {
|
|
35
|
+
const lon = mod360(moonLon);
|
|
36
|
+
const nakI = Math.floor(lon / NAK_SPAN) % 27;
|
|
37
|
+
const pos = lon - nakI * NAK_SPAN;
|
|
38
|
+
const startLord = VIMSHOTTARI_ORDER[nakI % 9];
|
|
39
|
+
const elapsed = pos / NAK_SPAN;
|
|
40
|
+
const y0 = VIMSHOTTARI_YEARS[startLord];
|
|
41
|
+
const li = VIMSHOTTARI_ORDER.indexOf(startLord);
|
|
42
|
+
let t = natalJd - elapsed * y0 * yearLength;
|
|
43
|
+
const dashas = [];
|
|
44
|
+
for (let k = 0; k < count; k++) {
|
|
45
|
+
const lord = VIMSHOTTARI_ORDER[(li + k) % 9];
|
|
46
|
+
const years = VIMSHOTTARI_YEARS[lord];
|
|
47
|
+
const span = years * yearLength;
|
|
48
|
+
const maha = { level: 1, lord, start: t, end: t + span, sub: [] };
|
|
49
|
+
if (levels >= 2) {
|
|
50
|
+
const sli = VIMSHOTTARI_ORDER.indexOf(lord);
|
|
51
|
+
let st = t;
|
|
52
|
+
for (let j = 0; j < 9; j++) {
|
|
53
|
+
const sl = VIMSHOTTARI_ORDER[(sli + j) % 9];
|
|
54
|
+
const subSpan = (years * VIMSHOTTARI_YEARS[sl] / VIMSHOTTARI_TOTAL) * yearLength;
|
|
55
|
+
maha.sub.push({ lord: sl, start: st, end: st + subSpan });
|
|
56
|
+
st += subSpan;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
dashas.push(maha);
|
|
60
|
+
t += span;
|
|
61
|
+
}
|
|
62
|
+
return { start_lord: startLord, balance_years: (1 - elapsed) * y0, dashas };
|
|
63
|
+
}
|
|
64
|
+
function activeIn(periods, target) {
|
|
65
|
+
return periods.find((p) => p.start <= target && target < p.end) ?? null;
|
|
66
|
+
}
|
|
67
|
+
/** The mahadasha, antardasha, and pratyantardasha lords active at targetJd. */
|
|
68
|
+
export function vimshottariActive(moonLon, natalJd, targetJd, yearLength = DASHA_YEAR) {
|
|
69
|
+
const timeline = vimshottariDashas(moonLon, natalJd, 2, yearLength, 10).dashas;
|
|
70
|
+
const maha = activeIn(timeline, targetJd);
|
|
71
|
+
if (maha === null)
|
|
72
|
+
return null;
|
|
73
|
+
const antar = activeIn(maha.sub, targetJd);
|
|
74
|
+
if (antar === null)
|
|
75
|
+
return { maha: maha.lord, antar: null, pratyantar: null };
|
|
76
|
+
const ay = VIMSHOTTARI_YEARS[maha.lord]
|
|
77
|
+
* VIMSHOTTARI_YEARS[antar.lord] / VIMSHOTTARI_TOTAL;
|
|
78
|
+
const sli = VIMSHOTTARI_ORDER.indexOf(antar.lord);
|
|
79
|
+
let st = antar.start;
|
|
80
|
+
let pratyantar = null;
|
|
81
|
+
for (let j = 0; j < 9; j++) {
|
|
82
|
+
const sl = VIMSHOTTARI_ORDER[(sli + j) % 9];
|
|
83
|
+
const span = (ay * VIMSHOTTARI_YEARS[sl] / VIMSHOTTARI_TOTAL) * yearLength;
|
|
84
|
+
if (st <= targetJd && targetJd < st + span) {
|
|
85
|
+
pratyantar = sl;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
st += span;
|
|
89
|
+
}
|
|
90
|
+
return { maha: maha.lord, antar: antar.lord, pratyantar };
|
|
91
|
+
}
|
|
92
|
+
/** Vimshottari dasha active at targetJd, from the natal Moon's nakshatra. */
|
|
93
|
+
export function vimshottariAt(engine, natalJd, targetJd, zodiac = "sidereal:lahiri", yearLength = DASHA_YEAR) {
|
|
94
|
+
const moonLon = engine.longitude("moon", natalJd, { zodiac });
|
|
95
|
+
const nak = nakshatra(moonLon);
|
|
96
|
+
const active = vimshottariActive(moonLon, natalJd, targetJd, yearLength) ?? {};
|
|
97
|
+
return {
|
|
98
|
+
moon_nakshatra: nak.name, moon_pada: nak.pada,
|
|
99
|
+
start_lord: VIMSHOTTARI_ORDER[nak.index % 9], ...active,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* astroengine yogas -- classical Vedic yogas (planetary combinations) judged on
|
|
3
|
+
* the sidereal rasi (D1) chart.
|
|
4
|
+
*
|
|
5
|
+
* Covers the well-defined, placement-based yogas with no textual variation: the
|
|
6
|
+
* five Pancha Mahapurusha yogas (a non-luminary in its own sign or exaltation
|
|
7
|
+
* AND in a kendra from the Ascendant -- Ruchaka/Mars, Bhadra/Mercury,
|
|
8
|
+
* Hamsa/Jupiter, Malavya/Venus, Shasha/Saturn); Gajakesari (Jupiter in a kendra
|
|
9
|
+
* from the Moon); Budha-Aditya (Sun and Mercury in one sign); and
|
|
10
|
+
* Chandra-Mangala (Moon and Mars in one sign). Own-sign/exaltation use the
|
|
11
|
+
* engine's `dignities`; houses are whole-sign from the Ascendant. The
|
|
12
|
+
* variant-laden yogas (Kemadruma, lordship-based raja/dhana) are left to a later
|
|
13
|
+
* step. Mirrors the Python reference (astroengine/yogas.py).
|
|
14
|
+
*/
|
|
15
|
+
import { Engine, BodyId, Zodiac } from "./chart.js";
|
|
16
|
+
export declare const YOGA_PLANETS: BodyId[];
|
|
17
|
+
export interface Yoga {
|
|
18
|
+
yoga: string;
|
|
19
|
+
planets: string[];
|
|
20
|
+
}
|
|
21
|
+
/** The placement yogas present in a chart. `signs` maps each of the seven
|
|
22
|
+
* classical planets to its 0-based sign index; `ascSign` is the Ascendant's
|
|
23
|
+
* sign index. */
|
|
24
|
+
export declare function detectYogas(signs: Record<string, number>, ascSign: number): Yoga[];
|
|
25
|
+
/** The placement yogas of a natal chart, from the sidereal rasi positions. */
|
|
26
|
+
export declare function yogasAt(engine: Engine, natalJd: number, lat: number, lonEast: number, zodiac?: Zodiac): Yoga[];
|