caelus 0.13.0 → 0.15.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/ashtottari.d.ts +50 -0
- package/dist/src/ashtottari.js +71 -0
- package/dist/src/chart.d.ts +76 -9
- package/dist/src/chart.js +147 -15
- package/dist/src/derived.d.ts +0 -15
- package/dist/src/derived.js +4 -39
- package/dist/src/directions.d.ts +15 -0
- package/dist/src/directions.js +50 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +2 -0
- package/dist/src/rajayoga.d.ts +50 -0
- package/dist/src/rajayoga.js +106 -0
- package/dist/src/vargas.d.ts +1 -1
- package/dist/src/vargas.js +30 -6
- package/dist/src/yogas.d.ts +13 -2
- package/dist/src/yogas.js +45 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -100,4 +100,4 @@ test/golden.test.ts conformance suite vs Python fixtures
|
|
|
100
100
|
- caelus — this package
|
|
101
101
|
- [caelus-birth](https://www.npmjs.com/package/caelus-birth) — local birth time + place → UT (charts take UT; use this)
|
|
102
102
|
- [caelus-wheel](https://www.npmjs.com/package/caelus-wheel) — React SVG chart wheel
|
|
103
|
-
- [caelus-mcp](https://www.npmjs.com/package/caelus-mcp) — MCP server,
|
|
103
|
+
- [caelus-mcp](https://www.npmjs.com/package/caelus-mcp) — MCP server, twenty-two chart tools over stdio
|
package/accuracy.json
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* astroengine ashtottari -- the Ashtottari dasha, a 108-year conditional dasha.
|
|
3
|
+
*
|
|
4
|
+
* Eight lords rule in order -- Sun 6, Moon 15, Mars 8, Mercury 17, Saturn 10,
|
|
5
|
+
* Jupiter 19, Rahu 12, Venus 21 years (totalling 108). Unlike Vimshottari the
|
|
6
|
+
* nakshatra-to-lord mapping is in irregular groups, and the elapsed portion of
|
|
7
|
+
* the first period is measured across the lord's whole multi-nakshatra span.
|
|
8
|
+
*
|
|
9
|
+
* Convention: the JHora/PVR Narasimha Rao mapping (the PyJHora implementation),
|
|
10
|
+
* which the texts vary around; the lord ranges and across-span balance are
|
|
11
|
+
* reproduced from it, validated against the named source in `validate_jyotish`
|
|
12
|
+
* rather than asserted. Mirrors the Python reference (astroengine/ashtottari.py);
|
|
13
|
+
* the golden fixtures pin the two together.
|
|
14
|
+
*/
|
|
15
|
+
import { Engine, Zodiac } from "./chart.js";
|
|
16
|
+
export declare const ASHTOTTARI_ORDER: readonly ["sun", "moon", "mars", "mercury", "saturn", "jupiter", "rahu", "venus"];
|
|
17
|
+
export declare const ASHTOTTARI_YEARS: Record<(typeof ASHTOTTARI_ORDER)[number], number>;
|
|
18
|
+
/** The Ashtottari dasha lord governing a nakshatra index (0-based). */
|
|
19
|
+
export declare function ashtottariLord(nakIndex: number): string;
|
|
20
|
+
export interface AshtottariSub {
|
|
21
|
+
lord: string;
|
|
22
|
+
start: number;
|
|
23
|
+
end: number;
|
|
24
|
+
}
|
|
25
|
+
export interface AshtottariPeriod {
|
|
26
|
+
level: number;
|
|
27
|
+
lord: string;
|
|
28
|
+
years: number;
|
|
29
|
+
start: number;
|
|
30
|
+
end: number;
|
|
31
|
+
sub: AshtottariSub[];
|
|
32
|
+
}
|
|
33
|
+
export interface AshtottariTimeline {
|
|
34
|
+
start_lord: string;
|
|
35
|
+
balance_years: number;
|
|
36
|
+
dashas: AshtottariPeriod[];
|
|
37
|
+
}
|
|
38
|
+
/** The Ashtottari dasha timeline from the Moon's sidereal longitude. */
|
|
39
|
+
export declare function ashtottariDashas(moonLon: number, natalJd: number, levels?: number, yearLength?: number, count?: number): AshtottariTimeline;
|
|
40
|
+
export interface AshtottariActive {
|
|
41
|
+
maha: string;
|
|
42
|
+
antar: string | null;
|
|
43
|
+
}
|
|
44
|
+
/** The maha and antar lord active at targetJd; null before the first period. */
|
|
45
|
+
export declare function ashtottariActive(moonLon: number, natalJd: number, targetJd: number, yearLength?: number): AshtottariActive | null;
|
|
46
|
+
/** Ashtottari dasha active at targetJd, from the natal Moon's nakshatra. */
|
|
47
|
+
export declare function ashtottariAt(engine: Engine, natalJd: number, targetJd: number, zodiac?: Zodiac, yearLength?: number): {
|
|
48
|
+
moon_nakshatra: string;
|
|
49
|
+
start_lord: string;
|
|
50
|
+
} & Partial<AshtottariActive>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { nakshatra, NAK_SPAN, DASHA_YEAR } from "./vedic.js";
|
|
2
|
+
export const ASHTOTTARI_ORDER = [
|
|
3
|
+
"sun", "moon", "mars", "mercury", "saturn", "jupiter", "rahu", "venus",
|
|
4
|
+
];
|
|
5
|
+
export const ASHTOTTARI_YEARS = {
|
|
6
|
+
sun: 6, moon: 15, mars: 8, mercury: 17, saturn: 10, jupiter: 19, rahu: 12, venus: 21,
|
|
7
|
+
};
|
|
8
|
+
const ASHTOTTARI_TOTAL = 108;
|
|
9
|
+
// Each lord's nakshatra group: [lord, start nakshatra index, span]. Rahu wraps
|
|
10
|
+
// past 27 (nakshatras 26, 0, 1, 2).
|
|
11
|
+
const ASHTOTTARI_RANGES = [
|
|
12
|
+
["sun", 6, 4], ["moon", 10, 3], ["mars", 13, 4], ["mercury", 17, 3],
|
|
13
|
+
["saturn", 20, 3], ["jupiter", 23, 3], ["rahu", 26, 4], ["venus", 3, 3],
|
|
14
|
+
];
|
|
15
|
+
/** The Ashtottari dasha lord governing a nakshatra index (0-based). */
|
|
16
|
+
export function ashtottariLord(nakIndex) {
|
|
17
|
+
for (const [lord, start, span] of ASHTOTTARI_RANGES) {
|
|
18
|
+
if (((nakIndex - start) % 27 + 27) % 27 < span)
|
|
19
|
+
return lord;
|
|
20
|
+
}
|
|
21
|
+
throw new Error(`no Ashtottari lord for nakshatra ${nakIndex}`);
|
|
22
|
+
}
|
|
23
|
+
/** The Ashtottari dasha timeline from the Moon's sidereal longitude. */
|
|
24
|
+
export function ashtottariDashas(moonLon, natalJd, levels = 2, yearLength = DASHA_YEAR, count = 8) {
|
|
25
|
+
const lon = ((moonLon % 360) + 360) % 360;
|
|
26
|
+
const nakI = Math.floor(lon / NAK_SPAN) % 27;
|
|
27
|
+
const startLord = ashtottariLord(nakI);
|
|
28
|
+
const [, startNak, spanNak] = ASHTOTTARI_RANGES.find((r) => r[0] === startLord);
|
|
29
|
+
const lordStartDeg = startNak * NAK_SPAN;
|
|
30
|
+
const spanDeg = spanNak * NAK_SPAN;
|
|
31
|
+
const elapsed = (((lon - lordStartDeg) % 360) + 360) % 360 / spanDeg;
|
|
32
|
+
const y0 = ASHTOTTARI_YEARS[startLord];
|
|
33
|
+
const li = ASHTOTTARI_ORDER.indexOf(startLord);
|
|
34
|
+
let t = natalJd - elapsed * y0 * yearLength;
|
|
35
|
+
const dashas = [];
|
|
36
|
+
for (let k = 0; k < count; k++) {
|
|
37
|
+
const lord = ASHTOTTARI_ORDER[(li + k) % 8];
|
|
38
|
+
const years = ASHTOTTARI_YEARS[lord];
|
|
39
|
+
const span = years * yearLength;
|
|
40
|
+
const maha = { level: 1, lord, years, start: t, end: t + span, sub: [] };
|
|
41
|
+
if (levels >= 2) {
|
|
42
|
+
const sli = ASHTOTTARI_ORDER.indexOf(lord);
|
|
43
|
+
let st = t;
|
|
44
|
+
for (let j = 0; j < 8; j++) {
|
|
45
|
+
const sl = ASHTOTTARI_ORDER[(sli + j) % 8];
|
|
46
|
+
const subSpan = (years * ASHTOTTARI_YEARS[sl] / ASHTOTTARI_TOTAL) * yearLength;
|
|
47
|
+
maha.sub.push({ lord: sl, start: st, end: st + subSpan });
|
|
48
|
+
st += subSpan;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
dashas.push(maha);
|
|
52
|
+
t += span;
|
|
53
|
+
}
|
|
54
|
+
return { start_lord: startLord, balance_years: (1 - elapsed) * y0, dashas };
|
|
55
|
+
}
|
|
56
|
+
/** The maha and antar lord active at targetJd; null before the first period. */
|
|
57
|
+
export function ashtottariActive(moonLon, natalJd, targetJd, yearLength = DASHA_YEAR) {
|
|
58
|
+
const timeline = ashtottariDashas(moonLon, natalJd, 2, yearLength, 16).dashas;
|
|
59
|
+
const maha = timeline.find((p) => p.start <= targetJd && targetJd < p.end);
|
|
60
|
+
if (!maha)
|
|
61
|
+
return null;
|
|
62
|
+
const antar = maha.sub.find((s) => s.start <= targetJd && targetJd < s.end);
|
|
63
|
+
return { maha: maha.lord, antar: antar ? antar.lord : null };
|
|
64
|
+
}
|
|
65
|
+
/** Ashtottari dasha active at targetJd, from the natal Moon's nakshatra. */
|
|
66
|
+
export function ashtottariAt(engine, natalJd, targetJd, zodiac = "sidereal:lahiri", yearLength = DASHA_YEAR) {
|
|
67
|
+
const moonLon = engine.longitude("moon", natalJd, { zodiac });
|
|
68
|
+
const nak = nakshatra(moonLon);
|
|
69
|
+
const active = ashtottariActive(moonLon, natalJd, targetJd, yearLength) ?? {};
|
|
70
|
+
return { moon_nakshatra: nak.name, start_lord: ashtottariLord(nak.index), ...active };
|
|
71
|
+
}
|
package/dist/src/chart.d.ts
CHANGED
|
@@ -10,6 +10,37 @@ export declare const SIGNS: string[];
|
|
|
10
10
|
export declare const ASPECTS: Record<string, number>;
|
|
11
11
|
export declare const DEFAULT_ORBS: Record<string, number>;
|
|
12
12
|
export type HouseSystem = "placidus" | "porphyry" | "equal" | "whole_sign" | "koch" | "regiomontanus" | "campanus" | "alcabitius" | "morinus" | "meridian" | "polich_page" | "vehlow";
|
|
13
|
+
/** The canonical house-system ids, in a stable order (also used for error text). */
|
|
14
|
+
export declare const HOUSE_SYSTEMS: readonly HouseSystem[];
|
|
15
|
+
/** Resolve a forgiving house-system string (any case, spaces or hyphens, or a
|
|
16
|
+
* known alias) to a canonical {@link HouseSystem}, or throw listing the valid
|
|
17
|
+
* ids. Lets MCP, share links, and hand-written calls pass "whole sign",
|
|
18
|
+
* "Whole_Sign", "whole", etc. without tripping the strict union. */
|
|
19
|
+
export declare function normalizeHouseSystem(raw: string): HouseSystem;
|
|
20
|
+
export type Element = "fire" | "earth" | "air" | "water";
|
|
21
|
+
export type Modality = "cardinal" | "fixed" | "mutable";
|
|
22
|
+
/** Triplicity (element) of a sign: `"fire"`, `"earth"`, `"air"`, or `"water"`. */
|
|
23
|
+
export declare function element(sign: number | string): Element;
|
|
24
|
+
/** Quadruplicity (modality) of a sign: `"cardinal"`, `"fixed"`, or `"mutable"`. */
|
|
25
|
+
export declare function modality(sign: number | string): Modality;
|
|
26
|
+
/** 1-based quadrant (I–IV) of a 1-based house number: houses 1–3 -> 1, etc. */
|
|
27
|
+
export declare function quadrant(house: number): number;
|
|
28
|
+
/**
|
|
29
|
+
* Essential dignities a body holds in a sign: any of `"domicile"`,
|
|
30
|
+
* `"exaltation"`, `"detriment"`, `"fall"` (the last two are the signs opposite
|
|
31
|
+
* domicile and exaltation). Empty when the body is peregrine there or has no
|
|
32
|
+
* classical rulership (the outer planets, Chiron, the nodes).
|
|
33
|
+
*
|
|
34
|
+
* @param body Body id, e.g. `"mars"`.
|
|
35
|
+
* @param sign A sign index `0`–`11` (Aries = 0) or its name, e.g. `"Aries"`.
|
|
36
|
+
* @returns The dignities held, in the order above; empty if none.
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* dignities("mars", "Aries"); // ["domicile"]
|
|
40
|
+
* dignities("sun", "Libra"); // ["fall"]
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare function dignities(body: string, sign: number | string): string[];
|
|
13
44
|
export type Ayanamsa = keyof typeof AYANAMSA_J2000 & string;
|
|
14
45
|
export type Zodiac = "tropical" | `sidereal:${string}`;
|
|
15
46
|
export interface Observer {
|
|
@@ -58,6 +89,33 @@ export interface Position {
|
|
|
58
89
|
/** Equatorial declination, true equinox of date, degrees. */
|
|
59
90
|
dec: number;
|
|
60
91
|
}
|
|
92
|
+
/** A {@link Position} enriched with chart-relative placement, as returned per
|
|
93
|
+
* body by {@link Engine.chart} and {@link Engine.chartAt}. */
|
|
94
|
+
export interface ChartBody extends Position {
|
|
95
|
+
/** 1-based house the body falls in, by the chart's cusps (1–12). */
|
|
96
|
+
house: number;
|
|
97
|
+
/** Essential dignities held in the body's sign (see {@link dignities});
|
|
98
|
+
* empty when peregrine or for bodies without classical rulerships. */
|
|
99
|
+
dignities: string[];
|
|
100
|
+
}
|
|
101
|
+
/** Default chart bodies that are Chebyshev-packed, so they can fall outside
|
|
102
|
+
* their fitted range (and be omitted from a chart). Opt-in asteroids are
|
|
103
|
+
* packed too, but arrive as arbitrary ids through the string index. */
|
|
104
|
+
export type PackedBody = "chiron";
|
|
105
|
+
/** Bodies guaranteed to be in every chart: the analytic Sun–Pluto and the lunar
|
|
106
|
+
* nodes, which resolve across all supported epochs. (Chiron is Chebyshev-packed
|
|
107
|
+
* and can fall outside its fitted range, so it is *not* guaranteed.) */
|
|
108
|
+
export type AlwaysBody = Exclude<Body, PackedBody>;
|
|
109
|
+
/**
|
|
110
|
+
* A chart's bodies, keyed by id. The analytic core ({@link AlwaysBody}) is
|
|
111
|
+
* always present and needs no presence check. {@link PackedBody} bodies (Chiron)
|
|
112
|
+
* and any opt-in extras requested via {@link ChartOptions.bodies} may be absent
|
|
113
|
+
* when the instant is outside their fitted range (see {@link Chart.unavailable}),
|
|
114
|
+
* so those accesses are typed `ChartBody | undefined` and must be guarded.
|
|
115
|
+
*/
|
|
116
|
+
export type ChartBodies = Record<AlwaysBody, ChartBody> & Partial<Record<PackedBody, ChartBody>> & {
|
|
117
|
+
[id: string]: ChartBody | undefined;
|
|
118
|
+
};
|
|
61
119
|
/** One aspect between two bodies in a {@link Chart}. */
|
|
62
120
|
export interface Aspect {
|
|
63
121
|
/** First body id. */
|
|
@@ -81,8 +139,14 @@ export interface Chart {
|
|
|
81
139
|
houseSystem: HouseSystem;
|
|
82
140
|
/** The house system originally requested, before any polar fallback. */
|
|
83
141
|
houseSystemRequested: HouseSystem;
|
|
84
|
-
/** Apparent
|
|
85
|
-
|
|
142
|
+
/** Apparent position per body, enriched with house and dignities, keyed by
|
|
143
|
+
* body id. See {@link ChartBody}. */
|
|
144
|
+
bodies: ChartBodies;
|
|
145
|
+
/** Body ids that were requested but omitted because the instant falls outside
|
|
146
|
+
* their fitted range (e.g. Chiron and other Chebyshev-packed bodies before
|
|
147
|
+
* ~1850 or after ~2150). Empty for the usual modern dates. The analytic
|
|
148
|
+
* bodies (Sun through Pluto and the nodes) are always present. */
|
|
149
|
+
unavailable: string[];
|
|
86
150
|
/** Chart angles in degrees: Ascendant, Midheaven, Vertex, East Point. */
|
|
87
151
|
angles: {
|
|
88
152
|
asc: number;
|
|
@@ -235,10 +299,10 @@ export declare class Engine {
|
|
|
235
299
|
* instant and place.
|
|
236
300
|
*
|
|
237
301
|
* The first six arguments are calendar fields in **UT** — not local civil
|
|
238
|
-
* time, and not a Julian Day. Passing a JD in `y` builds an instant
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
302
|
+
* time, and not a Julian Day. Passing a JD in `y` builds an absurd instant and
|
|
303
|
+
* throws `RangeError`; use {@link Engine.chartAt} for a chart from a JD. For a
|
|
304
|
+
* birth time given in a local time zone, resolve it to UT first (see the
|
|
305
|
+
* `caelus-birth` package).
|
|
242
306
|
*
|
|
243
307
|
* @param y Year in UT, e.g. `1990` — a calendar year, not a Julian Day.
|
|
244
308
|
* @param mo Month, `1`–`12`.
|
|
@@ -254,9 +318,12 @@ export declare class Engine {
|
|
|
254
318
|
* custom orbs. Defaults to Placidus houses in the tropical zodiac.
|
|
255
319
|
* @returns A {@link Chart}: `bodies`, `cusps`, `angles`, and `aspects`, plus
|
|
256
320
|
* `jdUt` and the house system actually used (Placidus and Koch fall back to
|
|
257
|
-
* whole-sign above the polar circles).
|
|
258
|
-
*
|
|
259
|
-
*
|
|
321
|
+
* whole-sign above the polar circles). A body outside its fitted range
|
|
322
|
+
* (e.g. Chiron before ~1850) is omitted from `bodies` and listed in
|
|
323
|
+
* `unavailable` rather than failing the whole chart.
|
|
324
|
+
* @throws RangeError only if the instant itself is absurd — far outside any
|
|
325
|
+
* supported epoch — which almost always means a Julian Day was passed where
|
|
326
|
+
* calendar fields belong.
|
|
260
327
|
* @example
|
|
261
328
|
* ```ts
|
|
262
329
|
* // 1990-06-10 14:30 UT at Tampa, FL (27.95° N, 82.46° W), Placidus houses
|
package/dist/src/chart.js
CHANGED
|
@@ -23,7 +23,92 @@ export const ASPECTS = {
|
|
|
23
23
|
export const DEFAULT_ORBS = {
|
|
24
24
|
conjunction: 8, sextile: 4, square: 7, trine: 7, opposition: 8,
|
|
25
25
|
};
|
|
26
|
+
/** The canonical house-system ids, in a stable order (also used for error text). */
|
|
27
|
+
export const HOUSE_SYSTEMS = [
|
|
28
|
+
"placidus", "porphyry", "equal", "whole_sign", "koch", "regiomontanus",
|
|
29
|
+
"campanus", "alcabitius", "morinus", "meridian", "polich_page", "vehlow",
|
|
30
|
+
];
|
|
31
|
+
// Short or alternate names that normalization (lowercase + space/hyphen -> "_")
|
|
32
|
+
// can't reach on its own. "whole sign", "Polich Page", etc. already normalize to
|
|
33
|
+
// their canonical id, so only genuinely different spellings live here.
|
|
34
|
+
const HOUSE_ALIASES = {
|
|
35
|
+
whole: "whole_sign", signs: "whole_sign", wholesign: "whole_sign",
|
|
36
|
+
equal_house: "equal", porphyrius: "porphyry", placidean: "placidus",
|
|
37
|
+
};
|
|
38
|
+
/** Resolve a forgiving house-system string (any case, spaces or hyphens, or a
|
|
39
|
+
* known alias) to a canonical {@link HouseSystem}, or throw listing the valid
|
|
40
|
+
* ids. Lets MCP, share links, and hand-written calls pass "whole sign",
|
|
41
|
+
* "Whole_Sign", "whole", etc. without tripping the strict union. */
|
|
42
|
+
export function normalizeHouseSystem(raw) {
|
|
43
|
+
const key = raw.trim().toLowerCase().replace(/[\s-]+/g, "_");
|
|
44
|
+
if (HOUSE_SYSTEMS.includes(key))
|
|
45
|
+
return key;
|
|
46
|
+
const alias = HOUSE_ALIASES[key];
|
|
47
|
+
if (alias)
|
|
48
|
+
return alias;
|
|
49
|
+
throw new Error(`unknown house system '${raw}' (valid: ${HOUSE_SYSTEMS.join(", ")})`);
|
|
50
|
+
}
|
|
51
|
+
const ELEMENTS = ["fire", "earth", "air", "water"];
|
|
52
|
+
const MODALITIES = ["cardinal", "fixed", "mutable"];
|
|
53
|
+
/** Sign index `0`–`11` (Aries = 0) from an index or a sign name (`"Aries"`). */
|
|
54
|
+
function signIndex(sign) {
|
|
55
|
+
return typeof sign === "number" ? mod(Math.floor(sign), 12) : SIGNS.indexOf(sign);
|
|
56
|
+
}
|
|
57
|
+
/** Triplicity (element) of a sign: `"fire"`, `"earth"`, `"air"`, or `"water"`. */
|
|
58
|
+
export function element(sign) {
|
|
59
|
+
return ELEMENTS[mod(signIndex(sign), 4)];
|
|
60
|
+
}
|
|
61
|
+
/** Quadruplicity (modality) of a sign: `"cardinal"`, `"fixed"`, or `"mutable"`. */
|
|
62
|
+
export function modality(sign) {
|
|
63
|
+
return MODALITIES[mod(signIndex(sign), 3)];
|
|
64
|
+
}
|
|
65
|
+
/** 1-based quadrant (I–IV) of a 1-based house number: houses 1–3 -> 1, etc. */
|
|
66
|
+
export function quadrant(house) {
|
|
67
|
+
return Math.floor(mod(house - 1, 12) / 3) + 1;
|
|
68
|
+
}
|
|
69
|
+
// ----------------------------------------------------------- essential dignities
|
|
70
|
+
const DOMICILE = {
|
|
71
|
+
sun: [4], moon: [3], mercury: [2, 5], venus: [1, 6],
|
|
72
|
+
mars: [0, 7], jupiter: [8, 11], saturn: [9, 10],
|
|
73
|
+
};
|
|
74
|
+
const EXALTATION = {
|
|
75
|
+
sun: 0, moon: 1, mercury: 5, venus: 11, mars: 9, jupiter: 3, saturn: 6,
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Essential dignities a body holds in a sign: any of `"domicile"`,
|
|
79
|
+
* `"exaltation"`, `"detriment"`, `"fall"` (the last two are the signs opposite
|
|
80
|
+
* domicile and exaltation). Empty when the body is peregrine there or has no
|
|
81
|
+
* classical rulership (the outer planets, Chiron, the nodes).
|
|
82
|
+
*
|
|
83
|
+
* @param body Body id, e.g. `"mars"`.
|
|
84
|
+
* @param sign A sign index `0`–`11` (Aries = 0) or its name, e.g. `"Aries"`.
|
|
85
|
+
* @returns The dignities held, in the order above; empty if none.
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* dignities("mars", "Aries"); // ["domicile"]
|
|
89
|
+
* dignities("sun", "Libra"); // ["fall"]
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export function dignities(body, sign) {
|
|
93
|
+
const idx = signIndex(sign);
|
|
94
|
+
const dom = DOMICILE[body] ?? [];
|
|
95
|
+
const out = [];
|
|
96
|
+
if (dom.includes(idx))
|
|
97
|
+
out.push("domicile");
|
|
98
|
+
if (EXALTATION[body] === idx)
|
|
99
|
+
out.push("exaltation");
|
|
100
|
+
if (dom.map((d) => mod(d + 6, 12)).includes(idx))
|
|
101
|
+
out.push("detriment");
|
|
102
|
+
if (body in EXALTATION && mod(EXALTATION[body] + 6, 12) === idx)
|
|
103
|
+
out.push("fall");
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
26
106
|
const KM_PER_AU = 149597870.7;
|
|
107
|
+
// A generous window for the analytic models. Every real chart, however
|
|
108
|
+
// historical, sits well inside it; an instant far outside almost always means a
|
|
109
|
+
// Julian Day was passed to chart() where calendar fields belong.
|
|
110
|
+
const JD_SANE_MIN = -2_000_000; // ~ 10000 BC
|
|
111
|
+
const JD_SANE_MAX = 9_000_000; // ~ 20000 AD
|
|
27
112
|
function parseZodiac(zodiac) {
|
|
28
113
|
if (zodiac === "tropical")
|
|
29
114
|
return null;
|
|
@@ -339,10 +424,10 @@ export class Engine {
|
|
|
339
424
|
* instant and place.
|
|
340
425
|
*
|
|
341
426
|
* The first six arguments are calendar fields in **UT** — not local civil
|
|
342
|
-
* time, and not a Julian Day. Passing a JD in `y` builds an instant
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
427
|
+
* time, and not a Julian Day. Passing a JD in `y` builds an absurd instant and
|
|
428
|
+
* throws `RangeError`; use {@link Engine.chartAt} for a chart from a JD. For a
|
|
429
|
+
* birth time given in a local time zone, resolve it to UT first (see the
|
|
430
|
+
* `caelus-birth` package).
|
|
346
431
|
*
|
|
347
432
|
* @param y Year in UT, e.g. `1990` — a calendar year, not a Julian Day.
|
|
348
433
|
* @param mo Month, `1`–`12`.
|
|
@@ -358,9 +443,12 @@ export class Engine {
|
|
|
358
443
|
* custom orbs. Defaults to Placidus houses in the tropical zodiac.
|
|
359
444
|
* @returns A {@link Chart}: `bodies`, `cusps`, `angles`, and `aspects`, plus
|
|
360
445
|
* `jdUt` and the house system actually used (Placidus and Koch fall back to
|
|
361
|
-
* whole-sign above the polar circles).
|
|
362
|
-
*
|
|
363
|
-
*
|
|
446
|
+
* whole-sign above the polar circles). A body outside its fitted range
|
|
447
|
+
* (e.g. Chiron before ~1850) is omitted from `bodies` and listed in
|
|
448
|
+
* `unavailable` rather than failing the whole chart.
|
|
449
|
+
* @throws RangeError only if the instant itself is absurd — far outside any
|
|
450
|
+
* supported epoch — which almost always means a Julian Day was passed where
|
|
451
|
+
* calendar fields belong.
|
|
364
452
|
* @example
|
|
365
453
|
* ```ts
|
|
366
454
|
* // 1990-06-10 14:30 UT at Tampa, FL (27.95° N, 82.46° W), Placidus houses
|
|
@@ -393,8 +481,12 @@ export class Engine {
|
|
|
393
481
|
* @see {@link Engine.chart} for the calendar-field entry point.
|
|
394
482
|
*/
|
|
395
483
|
chartAt(jdUt, lat, lonEast, opts = "placidus") {
|
|
484
|
+
if (!Number.isFinite(jdUt) || jdUt < JD_SANE_MIN || jdUt > JD_SANE_MAX) {
|
|
485
|
+
throw new RangeError(`chart instant (jd ${jdUt}) is far outside the supported range; if you ` +
|
|
486
|
+
`meant a calendar date, pass year/month/day to chart() rather than a Julian Day.`);
|
|
487
|
+
}
|
|
396
488
|
const o = typeof opts === "string" ? { houseSystem: opts } : opts;
|
|
397
|
-
const houseSystem = o.houseSystem ?? "placidus";
|
|
489
|
+
const houseSystem = normalizeHouseSystem(o.houseSystem ?? "placidus");
|
|
398
490
|
const zodiac = o.zodiac ?? "tropical";
|
|
399
491
|
const mode = parseZodiac(zodiac);
|
|
400
492
|
const calc = {
|
|
@@ -406,8 +498,22 @@ export class Engine {
|
|
|
406
498
|
...BODIES, ...(o.bodies ?? []).filter((b) => !BODIES.includes(b)),
|
|
407
499
|
];
|
|
408
500
|
const bodies = {};
|
|
409
|
-
|
|
410
|
-
|
|
501
|
+
const unavailable = [];
|
|
502
|
+
for (const b of names) {
|
|
503
|
+
try {
|
|
504
|
+
bodies[b] = this.position(b, jdUt, calc);
|
|
505
|
+
}
|
|
506
|
+
catch (e) {
|
|
507
|
+
// A Chebyshev-packed body (Chiron, fitted asteroids) outside its fitted
|
|
508
|
+
// range throws RangeError. Omit it and report it rather than discarding
|
|
509
|
+
// the whole chart; the analytic bodies still resolve. Any other error
|
|
510
|
+
// (e.g. a missing data pack) is a real fault and propagates.
|
|
511
|
+
if (e instanceof RangeError)
|
|
512
|
+
unavailable.push(b);
|
|
513
|
+
else
|
|
514
|
+
throw e;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
411
517
|
const [asc, mc, armc, eps] = H.angles(this.data, jdUt, lat, lonEast);
|
|
412
518
|
const [vtx, east] = H.vertexEastPoint(armc, lat * DEG, eps);
|
|
413
519
|
const phi = lat * DEG;
|
|
@@ -454,7 +560,7 @@ export class Engine {
|
|
|
454
560
|
cusps = H.housesVehlow(armc, phi, eps);
|
|
455
561
|
}
|
|
456
562
|
else {
|
|
457
|
-
throw new Error(`unknown house system '${houseSystem}'`);
|
|
563
|
+
throw new Error(`unknown house system '${houseSystem}' (valid: ${HOUSE_SYSTEMS.join(", ")})`);
|
|
458
564
|
}
|
|
459
565
|
}
|
|
460
566
|
catch (err) {
|
|
@@ -477,21 +583,44 @@ export class Engine {
|
|
|
477
583
|
else {
|
|
478
584
|
cuspsDeg = cusps.map(outDeg);
|
|
479
585
|
}
|
|
586
|
+
// Enrich each position with chart-relative placement (house) and the
|
|
587
|
+
// essential dignities of its sign, so callers don't recompute from cusps.
|
|
588
|
+
const chartBodies = {};
|
|
589
|
+
for (const b of names) {
|
|
590
|
+
const p = bodies[b];
|
|
591
|
+
if (!p)
|
|
592
|
+
continue; // omitted: outside its fitted range (see `unavailable`)
|
|
593
|
+
chartBodies[b] = {
|
|
594
|
+
...p,
|
|
595
|
+
house: houseIndex(p.lon, cuspsDeg),
|
|
596
|
+
dignities: dignities(b, Math.floor(mod(p.lon, 360) / 30)),
|
|
597
|
+
};
|
|
598
|
+
}
|
|
480
599
|
return {
|
|
481
600
|
jdUt,
|
|
482
601
|
zodiac,
|
|
483
602
|
houseSystem: used,
|
|
484
603
|
houseSystemRequested: houseSystem,
|
|
485
|
-
bodies,
|
|
604
|
+
bodies: chartBodies,
|
|
605
|
+
unavailable,
|
|
486
606
|
angles: {
|
|
487
607
|
asc: outDeg(asc), mc: outDeg(mc),
|
|
488
608
|
vertex: outDeg(vtx), eastPoint: outDeg(east),
|
|
489
609
|
},
|
|
490
610
|
cusps: cuspsDeg,
|
|
491
|
-
aspects: findAspects(
|
|
611
|
+
aspects: findAspects(chartBodies, o.orbs ?? DEFAULT_ORBS),
|
|
492
612
|
};
|
|
493
613
|
}
|
|
494
614
|
}
|
|
615
|
+
/** 1-based house for an ecliptic longitude (degrees) given the twelve cusp
|
|
616
|
+
* longitudes (degrees), wrapping across 0. */
|
|
617
|
+
function houseIndex(lon, cusps) {
|
|
618
|
+
for (let i = 0; i < 12; i++) {
|
|
619
|
+
if (mod(lon - cusps[i], 360) < mod(cusps[(i + 1) % 12] - cusps[i], 360))
|
|
620
|
+
return i + 1;
|
|
621
|
+
}
|
|
622
|
+
return 12;
|
|
623
|
+
}
|
|
495
624
|
export function findAspects(bodies, orbs = DEFAULT_ORBS) {
|
|
496
625
|
const out = [];
|
|
497
626
|
const names = Object.keys(bodies).filter((b) => !NOT_ASPECTABLE.has(b));
|
|
@@ -511,8 +640,11 @@ export function findAspects(bodies, orbs = DEFAULT_ORBS) {
|
|
|
511
640
|
return out;
|
|
512
641
|
}
|
|
513
642
|
export function fmtLon(deg) {
|
|
514
|
-
|
|
515
|
-
|
|
643
|
+
// Normalize first: a raw or rounded longitude (e.g. exactly 360, or a small
|
|
644
|
+
// negative) would otherwise index SIGNS out of range and render "undefined".
|
|
645
|
+
const norm = mod(deg, 360);
|
|
646
|
+
const sign = SIGNS[Math.floor(norm / 30)];
|
|
647
|
+
const d = mod(norm, 30);
|
|
516
648
|
const m = mod(d, 1) * 60;
|
|
517
649
|
return `${String(Math.floor(d)).padStart(2)}°${String(Math.floor(m)).padStart(2, "0")}' ${sign}`;
|
|
518
650
|
}
|
package/dist/src/derived.d.ts
CHANGED
|
@@ -109,21 +109,6 @@ export declare function declinationAspects(engine: Engine, bodies: BodyId[], jd:
|
|
|
109
109
|
/** |declination| minus the mean obliquity, degrees. Positive = out of bounds. */
|
|
110
110
|
export declare function outOfBoundsMargin(engine: Engine, body: BodyId, jd: number): number;
|
|
111
111
|
export declare function outOfBounds(engine: Engine, body: BodyId, jd: number): boolean;
|
|
112
|
-
/**
|
|
113
|
-
* Essential dignities a body holds in a sign: any of `"domicile"`,
|
|
114
|
-
* `"exaltation"`, `"detriment"`, `"fall"` (the last two are the signs opposite
|
|
115
|
-
* domicile and exaltation). Empty when the body is peregrine there.
|
|
116
|
-
*
|
|
117
|
-
* @param body Body id, e.g. `"mars"`.
|
|
118
|
-
* @param sign A sign index `0`–`11` (Aries = 0) or its name, e.g. `"Aries"`.
|
|
119
|
-
* @returns The dignities held, in the order above; empty if none.
|
|
120
|
-
* @example
|
|
121
|
-
* ```ts
|
|
122
|
-
* dignities("mars", "Aries"); // ["domicile"]
|
|
123
|
-
* dignities("sun", "Libra"); // ["fall"]
|
|
124
|
-
* ```
|
|
125
|
-
*/
|
|
126
|
-
export declare function dignities(body: string, sign: number | string): string[];
|
|
127
112
|
export declare function dignityOf(engine: Engine, body: BodyId, jd: number, zodiac?: Zodiac): string[];
|
|
128
113
|
/** Diurnal when the Sun is above the horizon at the given place. */
|
|
129
114
|
export declare function isDayChart(engine: Engine, jd: number, lat: number, lonEast: number): boolean;
|
package/dist/src/derived.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* golden fixtures pin the two together.
|
|
10
10
|
*/
|
|
11
11
|
import { mod, meanObliquity, jdTT, DEG } from "./core.js";
|
|
12
|
-
import {
|
|
12
|
+
import { dignities } from "./chart.js";
|
|
13
13
|
import { crossings } from "./events.js";
|
|
14
14
|
import { azAlt } from "./pheno.js";
|
|
15
15
|
export const TROPICAL_YEAR = 365.24219; // mean tropical year, days
|
|
@@ -198,44 +198,9 @@ export function outOfBounds(engine, body, jd) {
|
|
|
198
198
|
return outOfBoundsMargin(engine, body, jd) > 0;
|
|
199
199
|
}
|
|
200
200
|
// ----------------------------------------------------------- dignities
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
};
|
|
205
|
-
const EXALTATION = {
|
|
206
|
-
sun: 0, moon: 1, mercury: 5, venus: 11, mars: 9, jupiter: 3, saturn: 6,
|
|
207
|
-
};
|
|
208
|
-
function signIndex(sign) {
|
|
209
|
-
return typeof sign === "number" ? sign : SIGNS.indexOf(sign);
|
|
210
|
-
}
|
|
211
|
-
/**
|
|
212
|
-
* Essential dignities a body holds in a sign: any of `"domicile"`,
|
|
213
|
-
* `"exaltation"`, `"detriment"`, `"fall"` (the last two are the signs opposite
|
|
214
|
-
* domicile and exaltation). Empty when the body is peregrine there.
|
|
215
|
-
*
|
|
216
|
-
* @param body Body id, e.g. `"mars"`.
|
|
217
|
-
* @param sign A sign index `0`–`11` (Aries = 0) or its name, e.g. `"Aries"`.
|
|
218
|
-
* @returns The dignities held, in the order above; empty if none.
|
|
219
|
-
* @example
|
|
220
|
-
* ```ts
|
|
221
|
-
* dignities("mars", "Aries"); // ["domicile"]
|
|
222
|
-
* dignities("sun", "Libra"); // ["fall"]
|
|
223
|
-
* ```
|
|
224
|
-
*/
|
|
225
|
-
export function dignities(body, sign) {
|
|
226
|
-
const idx = signIndex(sign);
|
|
227
|
-
const dom = DOMICILE[body] ?? [];
|
|
228
|
-
const out = [];
|
|
229
|
-
if (dom.includes(idx))
|
|
230
|
-
out.push("domicile");
|
|
231
|
-
if (EXALTATION[body] === idx)
|
|
232
|
-
out.push("exaltation");
|
|
233
|
-
if (dom.map((d) => mod(d + 6, 12)).includes(idx))
|
|
234
|
-
out.push("detriment");
|
|
235
|
-
if (body in EXALTATION && mod(EXALTATION[body] + 6, 12) === idx)
|
|
236
|
-
out.push("fall");
|
|
237
|
-
return out;
|
|
238
|
-
}
|
|
201
|
+
// The pure `dignities(body, sign)` table now lives in chart.ts (with the other
|
|
202
|
+
// sign primitives); re-exported through the package index from there. This
|
|
203
|
+
// engine-aware wrapper resolves a body's sign at an instant first.
|
|
239
204
|
export function dignityOf(engine, body, jd, zodiac = "tropical") {
|
|
240
205
|
const lon = engine.longitude(body, jd, { zodiac });
|
|
241
206
|
return dignities(body, mod(Math.floor(lon / 30), 12));
|
package/dist/src/directions.d.ts
CHANGED
|
@@ -15,6 +15,21 @@ export interface DirectionArcs {
|
|
|
15
15
|
export declare function directionArcs(alpha: number, delta: number, ramc: number, phi: number): DirectionArcs;
|
|
16
16
|
/** Years of life corresponding to an arc of direction under a time key. */
|
|
17
17
|
export declare function directionYears(arc: number, key?: string): number;
|
|
18
|
+
/** Placidus semi-arc mundane directional arc (degrees) for promissor P to
|
|
19
|
+
* significator S: arc = MD_p - (MD_s / SA_s) * SA_p. null if either body is
|
|
20
|
+
* circumpolar. Reduces to the to-MC arc when S is on the meridian, and to 0
|
|
21
|
+
* when P and S coincide. */
|
|
22
|
+
export declare function mundaneDirectionArc(alphaP: number, deltaP: number, alphaS: number, deltaS: number, ramc: number, phi: number): number | null;
|
|
23
|
+
export interface MundaneDirection {
|
|
24
|
+
promissor: string;
|
|
25
|
+
significator: string;
|
|
26
|
+
arc: number;
|
|
27
|
+
years: number;
|
|
28
|
+
jd: number;
|
|
29
|
+
}
|
|
30
|
+
/** Direct mundane (Placidus semi-arc) directions of each promissor to each other
|
|
31
|
+
* significator within `maxYears`, sorted by years. */
|
|
32
|
+
export declare function mundaneDirections(engine: Engine, natalJd: number, lat: number, lonEast: number, bodies?: BodyId[], key?: string, maxYears?: number, yearLength?: number): MundaneDirection[];
|
|
18
33
|
export interface PrimaryDirection {
|
|
19
34
|
body: string;
|
|
20
35
|
angle: "MC" | "IC" | "ASC" | "DSC";
|
package/dist/src/directions.js
CHANGED
|
@@ -44,6 +44,56 @@ export function directionArcs(alpha, delta, ramc, phi) {
|
|
|
44
44
|
export function directionYears(arc, key = "naibod") {
|
|
45
45
|
return arc / KEYS[key];
|
|
46
46
|
}
|
|
47
|
+
/** A body's signed meridian distance from its nearest meridian and the matching
|
|
48
|
+
* semi-arc, [MD, SA] in degrees; null when circumpolar. */
|
|
49
|
+
function semiArcPosition(alpha, delta, ramc, phi) {
|
|
50
|
+
const t = Math.tan(phi * RAD) * Math.tan(delta * RAD);
|
|
51
|
+
if (Math.abs(t) > 1)
|
|
52
|
+
return null;
|
|
53
|
+
const ad = Math.asin(t) * DEG;
|
|
54
|
+
const mdu = ((alpha - ramc + 180) % 360 + 360) % 360 - 180; // upper meridian distance
|
|
55
|
+
if (Math.abs(mdu) <= 90 + ad)
|
|
56
|
+
return [mdu, 90 + ad];
|
|
57
|
+
const sign = mdu >= 0 ? 1 : -1;
|
|
58
|
+
return [sign * (180 - Math.abs(mdu)), 90 - ad];
|
|
59
|
+
}
|
|
60
|
+
/** Placidus semi-arc mundane directional arc (degrees) for promissor P to
|
|
61
|
+
* significator S: arc = MD_p - (MD_s / SA_s) * SA_p. null if either body is
|
|
62
|
+
* circumpolar. Reduces to the to-MC arc when S is on the meridian, and to 0
|
|
63
|
+
* when P and S coincide. */
|
|
64
|
+
export function mundaneDirectionArc(alphaP, deltaP, alphaS, deltaS, ramc, phi) {
|
|
65
|
+
const pp = semiArcPosition(alphaP, deltaP, ramc, phi);
|
|
66
|
+
const ps = semiArcPosition(alphaS, deltaS, ramc, phi);
|
|
67
|
+
if (pp === null || ps === null)
|
|
68
|
+
return null;
|
|
69
|
+
const [mdP, saP] = pp;
|
|
70
|
+
const [mdS, saS] = ps;
|
|
71
|
+
return mdP - (mdS / saS) * saP;
|
|
72
|
+
}
|
|
73
|
+
/** Direct mundane (Placidus semi-arc) directions of each promissor to each other
|
|
74
|
+
* significator within `maxYears`, sorted by years. */
|
|
75
|
+
export function mundaneDirections(engine, natalJd, lat, lonEast, bodies = TRADITIONAL, key = "naibod", maxYears = 90, yearLength = YEAR_DAYS) {
|
|
76
|
+
const ramc = angles(engine.data, natalJd, lat, lonEast)[2] * DEG;
|
|
77
|
+
const pos = {};
|
|
78
|
+
for (const b of bodies)
|
|
79
|
+
pos[b] = engine.position(b, natalJd);
|
|
80
|
+
const out = [];
|
|
81
|
+
for (const p of bodies) {
|
|
82
|
+
for (const s of bodies) {
|
|
83
|
+
if (p === s)
|
|
84
|
+
continue;
|
|
85
|
+
const arc = mundaneDirectionArc(pos[p].ra, pos[p].dec, pos[s].ra, pos[s].dec, ramc, lat);
|
|
86
|
+
if (arc === null)
|
|
87
|
+
continue;
|
|
88
|
+
const years = directionYears(arc, key);
|
|
89
|
+
if (years >= 0 && years <= maxYears) {
|
|
90
|
+
out.push({ promissor: p, significator: s, arc, years, jd: natalJd + years * yearLength });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
out.sort((a, b) => a.years - b.years);
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
47
97
|
/** Direct primary directions of the bodies to the four angles within
|
|
48
98
|
* `maxYears`, by the given time key, sorted by years. */
|
|
49
99
|
export function primaryDirections(engine, natalJd, lat, lonEast, bodies = TRADITIONAL, key = "naibod", maxYears = 90, yearLength = YEAR_DAYS) {
|
package/dist/src/index.d.ts
CHANGED
package/dist/src/index.js
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* astroengine rajayoga -- the lordship-and-aspect layer for Vedic yogas, and the
|
|
3
|
+
* raja/dhana yogas built on it.
|
|
4
|
+
*
|
|
5
|
+
* Layers: house lordship (traditional ruler of each whole-sign house from the
|
|
6
|
+
* Ascendant); graha drishti (every planet aspects the 7th sign; Mars also 4/8,
|
|
7
|
+
* Jupiter 5/9, Saturn 3/10); association (conjunction, mutual aspect, or
|
|
8
|
+
* parivartana exchange); then raja yoga (a kendra lord 1/4/7/10 associated with
|
|
9
|
+
* a trikona lord 1/5/9), dhana yoga (two wealth-house 2/5/9/11 lords), and the
|
|
10
|
+
* yogakaraka (a planet ruling both a pure kendra 4/7/10 and a pure trikona 5/9).
|
|
11
|
+
* Definitions follow BPHS, validated against the named source in
|
|
12
|
+
* `validate_jyotish`. Mirrors the Python reference (astroengine/rajayoga.py).
|
|
13
|
+
*/
|
|
14
|
+
import { Engine, Zodiac } from "./chart.js";
|
|
15
|
+
/** Graha drishti: the house-distances (1-based) each planet aspects. */
|
|
16
|
+
export declare const DRISHTI: Record<string, number[]>;
|
|
17
|
+
export declare const KENDRAS: number[];
|
|
18
|
+
export declare const TRIKONAS: number[];
|
|
19
|
+
export declare const DHANA_HOUSES: number[];
|
|
20
|
+
/** The traditional ruler of a sign index (0 = Aries). */
|
|
21
|
+
export declare function signLord(sign: number): string;
|
|
22
|
+
/** The sign on the given whole-sign house (1-12) from the Ascendant. */
|
|
23
|
+
export declare function houseSign(ascSign: number, house: number): number;
|
|
24
|
+
/** The lord of the given whole-sign house from the Ascendant. */
|
|
25
|
+
export declare function houseLord(ascSign: number, house: number): string;
|
|
26
|
+
/** The whole-sign house (1-12) a sign falls in from the Ascendant. */
|
|
27
|
+
export declare function houseFromAsc(ascSign: number, sign: number): number;
|
|
28
|
+
/** Whether `planet` at `planetSign` casts a graha drishti onto `targetSign`. */
|
|
29
|
+
export declare function aspectsSign(planet: string, planetSign: number, targetSign: number): boolean;
|
|
30
|
+
/** Sign exchange: a sits in b's sign and b sits in a's sign. */
|
|
31
|
+
export declare function parivartana(planetA: string, signA: number, planetB: string, signB: number): boolean;
|
|
32
|
+
/** How two planets associate: "conjunction" | "exchange" | "aspect" | null. */
|
|
33
|
+
export declare function associationType(planetA: string, signA: number, planetB: string, signB: number): string | null;
|
|
34
|
+
/** Planets ruling both a pure kendra (4/7/10) and a pure trikona (5/9), sorted. */
|
|
35
|
+
export declare function yogakarakas(ascSign: number): string[];
|
|
36
|
+
export interface LordPairYoga {
|
|
37
|
+
lords: string[];
|
|
38
|
+
via: string;
|
|
39
|
+
}
|
|
40
|
+
/** Raja yogas: associations between a kendra lord and a trikona lord. */
|
|
41
|
+
export declare function rajaYogas(signs: Record<string, number>, ascSign: number): LordPairYoga[];
|
|
42
|
+
/** Dhana yogas: associations between two wealth-house (2/5/9/11) lords. */
|
|
43
|
+
export declare function dhanaYogas(signs: Record<string, number>, ascSign: number): LordPairYoga[];
|
|
44
|
+
/** Raja yogas of a natal chart, with the chart's yogakarakas. */
|
|
45
|
+
export declare function rajaYogasAt(engine: Engine, natalJd: number, lat: number, lonEast: number, zodiac?: Zodiac): {
|
|
46
|
+
raja: LordPairYoga[];
|
|
47
|
+
yogakarakas: string[];
|
|
48
|
+
};
|
|
49
|
+
/** Dhana yogas of a natal chart. */
|
|
50
|
+
export declare function dhanaYogasAt(engine: Engine, natalJd: number, lat: number, lonEast: number, zodiac?: Zodiac): LordPairYoga[];
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { SIGN_RULERS } from "./profections.js";
|
|
2
|
+
/** Graha drishti: the house-distances (1-based) each planet aspects. */
|
|
3
|
+
export const DRISHTI = {
|
|
4
|
+
sun: [7], moon: [7], mercury: [7], venus: [7],
|
|
5
|
+
mars: [4, 7, 8], jupiter: [5, 7, 9], saturn: [3, 7, 10],
|
|
6
|
+
};
|
|
7
|
+
export const KENDRAS = [1, 4, 7, 10];
|
|
8
|
+
export const TRIKONAS = [1, 5, 9];
|
|
9
|
+
export const DHANA_HOUSES = [2, 5, 9, 11];
|
|
10
|
+
const PURE_KENDRAS = [4, 7, 10];
|
|
11
|
+
const PURE_TRIKONAS = [5, 9];
|
|
12
|
+
// The seven classical grahas: all analytic, so always present in a chart.
|
|
13
|
+
const PLANETS = ["sun", "moon", "mars", "mercury", "jupiter", "venus", "saturn"];
|
|
14
|
+
/** The traditional ruler of a sign index (0 = Aries). */
|
|
15
|
+
export function signLord(sign) {
|
|
16
|
+
return SIGN_RULERS[((sign % 12) + 12) % 12];
|
|
17
|
+
}
|
|
18
|
+
/** The sign on the given whole-sign house (1-12) from the Ascendant. */
|
|
19
|
+
export function houseSign(ascSign, house) {
|
|
20
|
+
return (ascSign + house - 1) % 12;
|
|
21
|
+
}
|
|
22
|
+
/** The lord of the given whole-sign house from the Ascendant. */
|
|
23
|
+
export function houseLord(ascSign, house) {
|
|
24
|
+
return signLord(houseSign(ascSign, house));
|
|
25
|
+
}
|
|
26
|
+
/** The whole-sign house (1-12) a sign falls in from the Ascendant. */
|
|
27
|
+
export function houseFromAsc(ascSign, sign) {
|
|
28
|
+
return ((sign - ascSign) % 12 + 12) % 12 + 1;
|
|
29
|
+
}
|
|
30
|
+
/** Whether `planet` at `planetSign` casts a graha drishti onto `targetSign`. */
|
|
31
|
+
export function aspectsSign(planet, planetSign, targetSign) {
|
|
32
|
+
const dist = ((targetSign - planetSign) % 12 + 12) % 12 + 1;
|
|
33
|
+
return (DRISHTI[planet] ?? [7]).includes(dist);
|
|
34
|
+
}
|
|
35
|
+
/** Sign exchange: a sits in b's sign and b sits in a's sign. */
|
|
36
|
+
export function parivartana(planetA, signA, planetB, signB) {
|
|
37
|
+
return signLord(signA) === planetB && signLord(signB) === planetA;
|
|
38
|
+
}
|
|
39
|
+
/** How two planets associate: "conjunction" | "exchange" | "aspect" | null. */
|
|
40
|
+
export function associationType(planetA, signA, planetB, signB) {
|
|
41
|
+
if (planetA === planetB)
|
|
42
|
+
return null;
|
|
43
|
+
if (signA === signB)
|
|
44
|
+
return "conjunction";
|
|
45
|
+
if (parivartana(planetA, signA, planetB, signB))
|
|
46
|
+
return "exchange";
|
|
47
|
+
if (aspectsSign(planetA, signA, signB) && aspectsSign(planetB, signB, signA))
|
|
48
|
+
return "aspect";
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
/** Planets ruling both a pure kendra (4/7/10) and a pure trikona (5/9), sorted. */
|
|
52
|
+
export function yogakarakas(ascSign) {
|
|
53
|
+
const out = [];
|
|
54
|
+
for (const p of PLANETS) {
|
|
55
|
+
const ruled = new Set();
|
|
56
|
+
for (let h = 1; h <= 12; h++)
|
|
57
|
+
if (houseLord(ascSign, h) === p)
|
|
58
|
+
ruled.add(h);
|
|
59
|
+
if (PURE_KENDRAS.some((h) => ruled.has(h)) && PURE_TRIKONAS.some((h) => ruled.has(h)))
|
|
60
|
+
out.push(p);
|
|
61
|
+
}
|
|
62
|
+
return out.sort();
|
|
63
|
+
}
|
|
64
|
+
function lordPairYogas(ascSign, signs, housesA, housesB) {
|
|
65
|
+
const lordsA = [...new Set(housesA.map((h) => houseLord(ascSign, h)))].sort();
|
|
66
|
+
const lordsB = [...new Set(housesB.map((h) => houseLord(ascSign, h)))].sort();
|
|
67
|
+
const seen = new Map();
|
|
68
|
+
for (const la of lordsA) {
|
|
69
|
+
for (const lb of lordsB) {
|
|
70
|
+
const via = associationType(la, signs[la], lb, signs[lb]);
|
|
71
|
+
if (via === null)
|
|
72
|
+
continue;
|
|
73
|
+
const pair = [la, lb].sort().join("|");
|
|
74
|
+
if (!seen.has(pair))
|
|
75
|
+
seen.set(pair, via);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return [...seen.entries()].sort((a, b) => a[0].localeCompare(b[0]))
|
|
79
|
+
.map(([pair, via]) => ({ lords: pair.split("|"), via }));
|
|
80
|
+
}
|
|
81
|
+
/** Raja yogas: associations between a kendra lord and a trikona lord. */
|
|
82
|
+
export function rajaYogas(signs, ascSign) {
|
|
83
|
+
return lordPairYogas(ascSign, signs, KENDRAS, TRIKONAS);
|
|
84
|
+
}
|
|
85
|
+
/** Dhana yogas: associations between two wealth-house (2/5/9/11) lords. */
|
|
86
|
+
export function dhanaYogas(signs, ascSign) {
|
|
87
|
+
return lordPairYogas(ascSign, signs, DHANA_HOUSES, DHANA_HOUSES);
|
|
88
|
+
}
|
|
89
|
+
function signsOf(engine, natalJd, lat, lonEast, zodiac) {
|
|
90
|
+
const chart = engine.chartAt(natalJd, lat, lonEast, { zodiac });
|
|
91
|
+
const ascSign = Math.floor(chart.angles.asc / 30) % 12;
|
|
92
|
+
const signs = {};
|
|
93
|
+
for (const p of PLANETS)
|
|
94
|
+
signs[p] = Math.floor(chart.bodies[p].lon / 30) % 12;
|
|
95
|
+
return { signs, ascSign };
|
|
96
|
+
}
|
|
97
|
+
/** Raja yogas of a natal chart, with the chart's yogakarakas. */
|
|
98
|
+
export function rajaYogasAt(engine, natalJd, lat, lonEast, zodiac = "sidereal:lahiri") {
|
|
99
|
+
const { signs, ascSign } = signsOf(engine, natalJd, lat, lonEast, zodiac);
|
|
100
|
+
return { raja: rajaYogas(signs, ascSign), yogakarakas: yogakarakas(ascSign) };
|
|
101
|
+
}
|
|
102
|
+
/** Dhana yogas of a natal chart. */
|
|
103
|
+
export function dhanaYogasAt(engine, natalJd, lat, lonEast, zodiac = "sidereal:lahiri") {
|
|
104
|
+
const { signs, ascSign } = signsOf(engine, natalJd, lat, lonEast, zodiac);
|
|
105
|
+
return dhanaYogas(signs, ascSign);
|
|
106
|
+
}
|
package/dist/src/vargas.d.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { Engine, BodyId, Zodiac } from "./chart.js";
|
|
17
17
|
/** Supported divisions. */
|
|
18
|
-
export declare const VARGA_DIVISIONS: readonly [1, 3, 9, 10, 12];
|
|
18
|
+
export declare const VARGA_DIVISIONS: readonly [1, 2, 3, 9, 10, 12, 30];
|
|
19
19
|
export interface Varga {
|
|
20
20
|
varga: number;
|
|
21
21
|
rasi: string;
|
package/dist/src/vargas.js
CHANGED
|
@@ -17,10 +17,26 @@ import { BODIES, SIGNS } from "./chart.js";
|
|
|
17
17
|
/** Element start sign for the navamsa (fire, earth, air, water by rasi % 4). */
|
|
18
18
|
const NAVAMSA_START = [0, 9, 6, 3];
|
|
19
19
|
/** Supported divisions. */
|
|
20
|
-
export const VARGA_DIVISIONS = [1, 3, 9, 10, 12];
|
|
20
|
+
export const VARGA_DIVISIONS = [1, 2, 3, 9, 10, 12, 30];
|
|
21
|
+
// Trimsamsa (D30): five unequal degree-bands per sign mapping to a non-luminary's
|
|
22
|
+
// sign. Odd: Mars 0-5 -> Aries, Saturn 5-10 -> Aquarius, Jupiter 10-18 ->
|
|
23
|
+
// Sagittarius, Mercury 18-25 -> Gemini, Venus 25-30 -> Libra. Even reverses with
|
|
24
|
+
// the planets' even signs. Each is [upper-degree-bound, result sign index].
|
|
25
|
+
const TRIMSAMSA_ODD = [[5, 0], [10, 10], [18, 8], [25, 2], [30, 6]];
|
|
26
|
+
const TRIMSAMSA_EVEN = [[5, 1], [12, 5], [20, 11], [25, 9], [30, 7]];
|
|
27
|
+
function trimsamsa(rasi, within) {
|
|
28
|
+
const bands = rasi % 2 === 0 ? TRIMSAMSA_ODD : TRIMSAMSA_EVEN;
|
|
29
|
+
for (let i = 0; i < bands.length; i++)
|
|
30
|
+
if (within < bands[i][0])
|
|
31
|
+
return [bands[i][1], i + 1];
|
|
32
|
+
return [bands[bands.length - 1][1], 5];
|
|
33
|
+
}
|
|
21
34
|
function vargaSign(rasi, div, n) {
|
|
22
35
|
switch (n) {
|
|
23
36
|
case 1: return rasi;
|
|
37
|
+
// Parashari hora: odd sign first half -> Leo, second half -> Cancer; even
|
|
38
|
+
// sign reversed (odd sign == even rasi index).
|
|
39
|
+
case 2: return ((rasi % 2 === 0) === (div === 0)) ? 4 : 3;
|
|
24
40
|
case 3: return (rasi + 4 * div) % 12;
|
|
25
41
|
case 9: return (NAVAMSA_START[rasi % 4] + div) % 12;
|
|
26
42
|
case 10: return rasi % 2 === 0 ? (rasi + div) % 12 : (rasi + 8 + div) % 12;
|
|
@@ -33,11 +49,19 @@ export function varga(siderealLon, n) {
|
|
|
33
49
|
const lon = ((siderealLon % 360) + 360) % 360;
|
|
34
50
|
const rasi = Math.floor(lon / 30) % 12;
|
|
35
51
|
const within = lon - rasi * 30;
|
|
36
|
-
let
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
52
|
+
let s;
|
|
53
|
+
let division;
|
|
54
|
+
if (n === 30) { // trimsamsa: unequal bands
|
|
55
|
+
[s, division] = trimsamsa(rasi, within);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
let div = Math.floor(within / (30 / n));
|
|
59
|
+
if (div >= n)
|
|
60
|
+
div = n - 1; // guard a boundary rounding to n
|
|
61
|
+
s = vargaSign(rasi, div, n);
|
|
62
|
+
division = div + 1;
|
|
63
|
+
}
|
|
64
|
+
return { varga: n, rasi: SIGNS[rasi], rasi_index: rasi, sign: SIGNS[s], sign_index: s, division };
|
|
41
65
|
}
|
|
42
66
|
/** The varga D-n of a body (default the Moon) at jd, in a sidereal zodiac. */
|
|
43
67
|
export function vargaAt(engine, jdUt, n, body = "moon", zodiac = "sidereal:lahiri") {
|
package/dist/src/yogas.d.ts
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
* variant-laden yogas (Kemadruma, lordship-based raja/dhana) are left to a later
|
|
13
13
|
* step. Mirrors the Python reference (astroengine/yogas.py).
|
|
14
14
|
*/
|
|
15
|
-
import { Engine,
|
|
16
|
-
export declare const YOGA_PLANETS:
|
|
15
|
+
import { Engine, Zodiac, AlwaysBody } from "./chart.js";
|
|
16
|
+
export declare const YOGA_PLANETS: readonly AlwaysBody[];
|
|
17
17
|
export interface Yoga {
|
|
18
18
|
yoga: string;
|
|
19
19
|
planets: string[];
|
|
@@ -22,5 +22,16 @@ export interface Yoga {
|
|
|
22
22
|
* classical planets to its 0-based sign index; `ascSign` is the Ascendant's
|
|
23
23
|
* sign index. */
|
|
24
24
|
export declare function detectYogas(signs: Record<string, number>, ascSign: number): Yoga[];
|
|
25
|
+
export interface Kemadruma {
|
|
26
|
+
present: boolean;
|
|
27
|
+
planets_checked: string[];
|
|
28
|
+
}
|
|
29
|
+
/** Kemadruma yoga: the Moon is isolated -- no planet in the 2nd or 12th sign
|
|
30
|
+
* from it, nor conjunct it. The planet set is parameterized (texts vary): the
|
|
31
|
+
* default is the five tara grahas, `includeSun` adds the Sun, `includeNodes`
|
|
32
|
+
* adds Rahu/Ketu when present in `signs`. */
|
|
33
|
+
export declare function kemadruma(signs: Record<string, number>, includeSun?: boolean, includeNodes?: boolean): Kemadruma;
|
|
34
|
+
/** Kemadruma yoga of a natal chart, from the sidereal rasi positions. */
|
|
35
|
+
export declare function kemadrumaAt(engine: Engine, natalJd: number, lat: number, lonEast: number, includeSun?: boolean, includeNodes?: boolean, zodiac?: Zodiac): Kemadruma;
|
|
25
36
|
/** The placement yogas of a natal chart, from the sidereal rasi positions. */
|
|
26
37
|
export declare function yogasAt(engine: Engine, natalJd: number, lat: number, lonEast: number, zodiac?: Zodiac): Yoga[];
|
package/dist/src/yogas.js
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
|
-
|
|
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 { dignities } from "./chart.js";
|
|
2
16
|
/** Pancha Mahapurusha: [yoga name, planet]. */
|
|
3
17
|
const MAHAPURUSHA = [
|
|
4
18
|
["Ruchaka", "mars"], ["Bhadra", "mercury"], ["Hamsa", "jupiter"],
|
|
5
19
|
["Malavya", "venus"], ["Shasha", "saturn"],
|
|
6
20
|
];
|
|
7
21
|
const KENDRA = new Set([1, 4, 7, 10]);
|
|
22
|
+
// The seven classical grahas: all analytic, so always present in a chart.
|
|
8
23
|
export const YOGA_PLANETS = ["sun", "moon", "mars", "mercury", "jupiter", "venus", "saturn"];
|
|
9
24
|
/** The placement yogas present in a chart. `signs` maps each of the seven
|
|
10
25
|
* classical planets to its 0-based sign index; `ascSign` is the Ascendant's
|
|
@@ -28,6 +43,35 @@ export function detectYogas(signs, ascSign) {
|
|
|
28
43
|
out.push({ yoga: "Chandra-Mangala", planets: ["moon", "mars"] });
|
|
29
44
|
return out;
|
|
30
45
|
}
|
|
46
|
+
/** Kemadruma yoga: the Moon is isolated -- no planet in the 2nd or 12th sign
|
|
47
|
+
* from it, nor conjunct it. The planet set is parameterized (texts vary): the
|
|
48
|
+
* default is the five tara grahas, `includeSun` adds the Sun, `includeNodes`
|
|
49
|
+
* adds Rahu/Ketu when present in `signs`. */
|
|
50
|
+
export function kemadruma(signs, includeSun = false, includeNodes = false) {
|
|
51
|
+
let planets = ["mars", "mercury", "jupiter", "venus", "saturn"];
|
|
52
|
+
if (includeSun)
|
|
53
|
+
planets = ["sun", ...planets];
|
|
54
|
+
if (includeNodes)
|
|
55
|
+
planets = [...planets, "rahu", "ketu"];
|
|
56
|
+
planets = planets.filter((p) => p in signs);
|
|
57
|
+
const moon = signs.moon;
|
|
58
|
+
const occupied = new Set([((moon - 1) % 12 + 12) % 12, moon, (moon + 1) % 12]);
|
|
59
|
+
const present = !planets.some((p) => occupied.has(signs[p]));
|
|
60
|
+
return { present, planets_checked: planets };
|
|
61
|
+
}
|
|
62
|
+
/** Kemadruma yoga of a natal chart, from the sidereal rasi positions. */
|
|
63
|
+
export function kemadrumaAt(engine, natalJd, lat, lonEast, includeSun = false, includeNodes = false, zodiac = "sidereal:lahiri") {
|
|
64
|
+
const chart = engine.chartAt(natalJd, lat, lonEast, { zodiac });
|
|
65
|
+
const bodies = includeNodes ? [...YOGA_PLANETS, "mean_node"] : YOGA_PLANETS;
|
|
66
|
+
const signs = {};
|
|
67
|
+
for (const b of bodies)
|
|
68
|
+
signs[b] = Math.floor(chart.bodies[b].lon / 30) % 12;
|
|
69
|
+
if (includeNodes) {
|
|
70
|
+
signs.rahu = signs.mean_node;
|
|
71
|
+
signs.ketu = (signs.mean_node + 6) % 12;
|
|
72
|
+
}
|
|
73
|
+
return kemadruma(signs, includeSun, includeNodes);
|
|
74
|
+
}
|
|
31
75
|
/** The placement yogas of a natal chart, from the sidereal rasi positions. */
|
|
32
76
|
export function yogasAt(engine, natalJd, lat, lonEast, zodiac = "sidereal:lahiri") {
|
|
33
77
|
const chart = engine.chartAt(natalJd, lat, lonEast, { zodiac });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "caelus",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.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",
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
"test": "node dist/test/golden.test.js"
|
|
22
22
|
},
|
|
23
23
|
"license": "MIT",
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public",
|
|
26
|
+
"provenance": true
|
|
27
|
+
},
|
|
24
28
|
"devDependencies": {
|
|
25
29
|
"@types/node": "^25.9.2",
|
|
26
30
|
"typescript": "^6.0.3"
|