caelus 0.12.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/directions.d.ts +27 -0
- package/dist/src/directions.js +69 -0
- package/dist/src/firdaria.d.ts +49 -0
- package/dist/src/firdaria.js +62 -0
- 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/profections.d.ts +27 -0
- package/dist/src/profections.js +49 -0
- package/dist/src/releasing.d.ts +32 -0
- package/dist/src/releasing.js +109 -0
- 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/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, eighteen chart tools over stdio
|
package/accuracy.json
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Engine, BodyId } from "./chart.js";
|
|
2
|
+
/** Degrees of arc that equal one year of life, per time key. */
|
|
3
|
+
export declare const KEYS: Record<string, number>;
|
|
4
|
+
export declare const TRADITIONAL: BodyId[];
|
|
5
|
+
export interface DirectionArcs {
|
|
6
|
+
mc: number;
|
|
7
|
+
ic: number;
|
|
8
|
+
asc: number | null;
|
|
9
|
+
dsc: number | null;
|
|
10
|
+
}
|
|
11
|
+
/** Direct primary-direction arcs (degrees, [0, 360)) of a body at right
|
|
12
|
+
* ascension `alpha` and declination `delta` to the four angles, for latitude
|
|
13
|
+
* `phi` and right ascension of the MC `ramc`. `asc`/`dsc` are null when the
|
|
14
|
+
* body is circumpolar. */
|
|
15
|
+
export declare function directionArcs(alpha: number, delta: number, ramc: number, phi: number): DirectionArcs;
|
|
16
|
+
/** Years of life corresponding to an arc of direction under a time key. */
|
|
17
|
+
export declare function directionYears(arc: number, key?: string): number;
|
|
18
|
+
export interface PrimaryDirection {
|
|
19
|
+
body: string;
|
|
20
|
+
angle: "MC" | "IC" | "ASC" | "DSC";
|
|
21
|
+
arc: number;
|
|
22
|
+
years: number;
|
|
23
|
+
jd: number;
|
|
24
|
+
}
|
|
25
|
+
/** Direct primary directions of the bodies to the four angles within
|
|
26
|
+
* `maxYears`, by the given time key, sorted by years. */
|
|
27
|
+
export declare function primaryDirections(engine: Engine, natalJd: number, lat: number, lonEast: number, bodies?: BodyId[], key?: string, maxYears?: number, yearLength?: number): PrimaryDirection[];
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* astroengine directions -- primary directions to the angles.
|
|
3
|
+
*
|
|
4
|
+
* Primary (mundane) directions carry a planet, by the diurnal rotation of the
|
|
5
|
+
* sphere, to one of the four angles; the arc of rotation, converted by a time
|
|
6
|
+
* key, gives the age of the direction. This covers the well-defined subset:
|
|
7
|
+
* direct directions of a body to the MC, IC, Ascendant, and Descendant.
|
|
8
|
+
*
|
|
9
|
+
* For a body at right ascension alpha and declination delta, latitude phi, and
|
|
10
|
+
* right ascension of the MC (ramc): arc to MC = alpha - ramc; to IC = that
|
|
11
|
+
* - 180; to the Ascendant = alpha - AD - ramc - 90; to the Descendant =
|
|
12
|
+
* alpha + AD - ramc + 90, where AD = asin(tan phi * tan delta) is the
|
|
13
|
+
* ascensional difference. A circumpolar body (|tan phi * tan delta| > 1) has no
|
|
14
|
+
* oblique ascension, so its Ascendant/Descendant directions are undefined. Time
|
|
15
|
+
* keys: Ptolemy 1 deg = 1 year, Naibod 0.9856473 deg = 1 year. Mirrors the
|
|
16
|
+
* Python reference (astroengine/directions.py); the golden fixtures pin them.
|
|
17
|
+
*/
|
|
18
|
+
import { angles } from "./houses.js";
|
|
19
|
+
const RAD = Math.PI / 180;
|
|
20
|
+
const DEG = 180 / Math.PI;
|
|
21
|
+
/** Degrees of arc that equal one year of life, per time key. */
|
|
22
|
+
export const KEYS = { ptolemy: 1.0, naibod: 0.9856473 };
|
|
23
|
+
export const TRADITIONAL = ["sun", "moon", "mercury", "venus", "mars", "jupiter", "saturn"];
|
|
24
|
+
const YEAR_DAYS = 365.2422;
|
|
25
|
+
const mod360 = (x) => ((x % 360) + 360) % 360;
|
|
26
|
+
/** Direct primary-direction arcs (degrees, [0, 360)) of a body at right
|
|
27
|
+
* ascension `alpha` and declination `delta` to the four angles, for latitude
|
|
28
|
+
* `phi` and right ascension of the MC `ramc`. `asc`/`dsc` are null when the
|
|
29
|
+
* body is circumpolar. */
|
|
30
|
+
export function directionArcs(alpha, delta, ramc, phi) {
|
|
31
|
+
const arcMc = mod360(alpha - ramc);
|
|
32
|
+
const arcIc = mod360(alpha - ramc - 180);
|
|
33
|
+
const t = Math.tan(phi * RAD) * Math.tan(delta * RAD);
|
|
34
|
+
if (Math.abs(t) > 1)
|
|
35
|
+
return { mc: arcMc, ic: arcIc, asc: null, dsc: null };
|
|
36
|
+
const ad = Math.asin(t) * DEG;
|
|
37
|
+
return {
|
|
38
|
+
mc: arcMc, ic: arcIc,
|
|
39
|
+
asc: mod360(alpha - ad - ramc - 90),
|
|
40
|
+
dsc: mod360(alpha + ad - ramc + 90),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** Years of life corresponding to an arc of direction under a time key. */
|
|
44
|
+
export function directionYears(arc, key = "naibod") {
|
|
45
|
+
return arc / KEYS[key];
|
|
46
|
+
}
|
|
47
|
+
/** Direct primary directions of the bodies to the four angles within
|
|
48
|
+
* `maxYears`, by the given time key, sorted by years. */
|
|
49
|
+
export function primaryDirections(engine, natalJd, lat, lonEast, bodies = TRADITIONAL, key = "naibod", maxYears = 90, yearLength = YEAR_DAYS) {
|
|
50
|
+
const armc = angles(engine.data, natalJd, lat, lonEast)[2];
|
|
51
|
+
const ramc = armc * DEG;
|
|
52
|
+
const out = [];
|
|
53
|
+
const ANGLES = ["mc", "ic", "asc", "dsc"];
|
|
54
|
+
for (const b of bodies) {
|
|
55
|
+
const p = engine.position(b, natalJd);
|
|
56
|
+
const arcs = directionArcs(p.ra, p.dec, ramc, lat);
|
|
57
|
+
for (const angle of ANGLES) {
|
|
58
|
+
const arc = arcs[angle];
|
|
59
|
+
if (arc === null)
|
|
60
|
+
continue;
|
|
61
|
+
const years = directionYears(arc, key);
|
|
62
|
+
if (years <= maxYears) {
|
|
63
|
+
out.push({ body: b, angle: angle.toUpperCase(), arc, years, jd: natalJd + years * yearLength });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
out.sort((a, b) => a.years - b.years);
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* astroengine firdaria -- the Persian/medieval system of planetary time-lord
|
|
3
|
+
* periods (firdariyyat).
|
|
4
|
+
*
|
|
5
|
+
* Life divides into nine periods totalling 75 years: the seven planets in the
|
|
6
|
+
* firdaria order, then the two lunar nodes. A day chart begins with the Sun, a
|
|
7
|
+
* night chart with the Moon; both follow the same cycle (Sun, Venus, Mercury,
|
|
8
|
+
* Moon, Saturn, Jupiter, Mars) and close with the North and South Nodes. Each
|
|
9
|
+
* planetary period splits into seven equal sub-periods led by the seven planets
|
|
10
|
+
* from that period's lord; node periods have no sub-divisions. Pure time
|
|
11
|
+
* arithmetic on the natal moment and the chart's sect. Mirrors the Python
|
|
12
|
+
* reference (astroengine/firdaria.py); the golden fixtures pin the two together.
|
|
13
|
+
*/
|
|
14
|
+
import { Engine } from "./chart.js";
|
|
15
|
+
/** The firdaria cycle of the seven planets. */
|
|
16
|
+
export declare const FIRDARIA_ORDER: readonly ["sun", "venus", "mercury", "moon", "saturn", "jupiter", "mars"];
|
|
17
|
+
/** Period length in years for each of the seven planets. */
|
|
18
|
+
export declare const FIRDARIA_YEARS: Record<(typeof FIRDARIA_ORDER)[number], number>;
|
|
19
|
+
/** The two nodes close the sequence with no sub-periods (70 + 5 = 75 years). */
|
|
20
|
+
export declare const NODE_PERIODS: ReadonlyArray<readonly [string, number]>;
|
|
21
|
+
/** The nine major firdaria periods in order, as `[lord, years]` pairs. */
|
|
22
|
+
export declare function firdariaSequence(day: boolean): Array<[string, number]>;
|
|
23
|
+
export interface FirdariaSub {
|
|
24
|
+
lord: string;
|
|
25
|
+
start: number;
|
|
26
|
+
end: number;
|
|
27
|
+
}
|
|
28
|
+
export interface FirdariaPeriod {
|
|
29
|
+
lord: string;
|
|
30
|
+
years: number;
|
|
31
|
+
start: number;
|
|
32
|
+
end: number;
|
|
33
|
+
sub: FirdariaSub[];
|
|
34
|
+
}
|
|
35
|
+
/** The full firdaria timeline from birth. */
|
|
36
|
+
export declare function firdaria(day: boolean, natalJd: number, yearLength?: number): FirdariaPeriod[];
|
|
37
|
+
/** The major and sub firdar lord active at `targetJd`; both null outside the
|
|
38
|
+
* 75-year span. */
|
|
39
|
+
export declare function firdariaActive(day: boolean, natalJd: number, targetJd: number, yearLength?: number): {
|
|
40
|
+
major: string | null;
|
|
41
|
+
sub: string | null;
|
|
42
|
+
};
|
|
43
|
+
/** The active firdar at `targetJd`, taking the chart's sect from the natal
|
|
44
|
+
* moment and place. */
|
|
45
|
+
export declare function firdariaAt(engine: Engine, natalJd: number, targetJd: number, lat: number, lonEast: number, yearLength?: number): {
|
|
46
|
+
day: boolean;
|
|
47
|
+
major: string | null;
|
|
48
|
+
sub: string | null;
|
|
49
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { TROPICAL_YEAR, isDayChart } from "./derived.js";
|
|
2
|
+
/** The firdaria cycle of the seven planets. */
|
|
3
|
+
export const FIRDARIA_ORDER = [
|
|
4
|
+
"sun", "venus", "mercury", "moon", "saturn", "jupiter", "mars",
|
|
5
|
+
];
|
|
6
|
+
/** Period length in years for each of the seven planets. */
|
|
7
|
+
export const FIRDARIA_YEARS = {
|
|
8
|
+
sun: 10, venus: 8, mercury: 13, moon: 9, saturn: 11, jupiter: 12, mars: 7,
|
|
9
|
+
};
|
|
10
|
+
/** The two nodes close the sequence with no sub-periods (70 + 5 = 75 years). */
|
|
11
|
+
export const NODE_PERIODS = [
|
|
12
|
+
["north_node", 3], ["south_node", 2],
|
|
13
|
+
];
|
|
14
|
+
/** The nine major firdaria periods in order, as `[lord, years]` pairs. */
|
|
15
|
+
export function firdariaSequence(day) {
|
|
16
|
+
const start = day ? 0 : FIRDARIA_ORDER.indexOf("moon");
|
|
17
|
+
const planets = [];
|
|
18
|
+
for (let i = 0; i < 7; i++) {
|
|
19
|
+
const lord = FIRDARIA_ORDER[(start + i) % 7];
|
|
20
|
+
planets.push([lord, FIRDARIA_YEARS[lord]]);
|
|
21
|
+
}
|
|
22
|
+
return [...planets, ...NODE_PERIODS.map((p) => [p[0], p[1]])];
|
|
23
|
+
}
|
|
24
|
+
/** The full firdaria timeline from birth. */
|
|
25
|
+
export function firdaria(day, natalJd, yearLength = TROPICAL_YEAR) {
|
|
26
|
+
const out = [];
|
|
27
|
+
let t = natalJd;
|
|
28
|
+
for (const [lord, years] of firdariaSequence(day)) {
|
|
29
|
+
const span = years * yearLength;
|
|
30
|
+
const major = { lord, years, start: t, end: t + span, sub: [] };
|
|
31
|
+
const li = FIRDARIA_ORDER.indexOf(lord);
|
|
32
|
+
if (li >= 0) {
|
|
33
|
+
const subSpan = span / 7;
|
|
34
|
+
let st = t;
|
|
35
|
+
for (let k = 0; k < 7; k++) {
|
|
36
|
+
const sl = FIRDARIA_ORDER[(li + k) % 7];
|
|
37
|
+
major.sub.push({ lord: sl, start: st, end: st + subSpan });
|
|
38
|
+
st += subSpan;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
out.push(major);
|
|
42
|
+
t += span;
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
/** The major and sub firdar lord active at `targetJd`; both null outside the
|
|
47
|
+
* 75-year span. */
|
|
48
|
+
export function firdariaActive(day, natalJd, targetJd, yearLength = TROPICAL_YEAR) {
|
|
49
|
+
for (const major of firdaria(day, natalJd, yearLength)) {
|
|
50
|
+
if (major.start <= targetJd && targetJd < major.end) {
|
|
51
|
+
const sub = major.sub.find((s) => s.start <= targetJd && targetJd < s.end);
|
|
52
|
+
return { major: major.lord, sub: sub ? sub.lord : null };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { major: null, sub: null };
|
|
56
|
+
}
|
|
57
|
+
/** The active firdar at `targetJd`, taking the chart's sect from the natal
|
|
58
|
+
* moment and place. */
|
|
59
|
+
export function firdariaAt(engine, natalJd, targetJd, lat, lonEast, yearLength = TROPICAL_YEAR) {
|
|
60
|
+
const day = isDayChart(engine, natalJd, lat, lonEast);
|
|
61
|
+
return { day, ...firdariaActive(day, natalJd, targetJd, yearLength) };
|
|
62
|
+
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -15,3 +15,12 @@ export * from "./astrocartography.js";
|
|
|
15
15
|
export * from "./ephemeris.js";
|
|
16
16
|
export * from "./features.js";
|
|
17
17
|
export * from "./compiler.js";
|
|
18
|
+
export * from "./lots.js";
|
|
19
|
+
export * from "./profections.js";
|
|
20
|
+
export * from "./firdaria.js";
|
|
21
|
+
export * from "./releasing.js";
|
|
22
|
+
export * from "./vedic.js";
|
|
23
|
+
export * from "./directions.js";
|
|
24
|
+
export * from "./vargas.js";
|
|
25
|
+
export * from "./yogini.js";
|
|
26
|
+
export * from "./yogas.js";
|
package/dist/src/index.js
CHANGED
|
@@ -15,3 +15,12 @@ export * from "./astrocartography.js";
|
|
|
15
15
|
export * from "./ephemeris.js";
|
|
16
16
|
export * from "./features.js";
|
|
17
17
|
export * from "./compiler.js";
|
|
18
|
+
export * from "./lots.js";
|
|
19
|
+
export * from "./profections.js";
|
|
20
|
+
export * from "./firdaria.js";
|
|
21
|
+
export * from "./releasing.js";
|
|
22
|
+
export * from "./vedic.js";
|
|
23
|
+
export * from "./directions.js";
|
|
24
|
+
export * from "./vargas.js";
|
|
25
|
+
export * from "./yogini.js";
|
|
26
|
+
export * from "./yogas.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Engine, Zodiac } from "./chart.js";
|
|
2
|
+
/** The seven Hermetic lots, in their conventional order. */
|
|
3
|
+
export declare const HERMETIC_LOTS: readonly ["fortune", "spirit", "eros", "necessity", "courage", "victory", "nemesis"];
|
|
4
|
+
export type HermeticLot = (typeof HERMETIC_LOTS)[number];
|
|
5
|
+
/** Lot of Fortune: Asc + Moon - Sun by day, Asc + Sun - Moon by night. */
|
|
6
|
+
export declare function lotFortune(asc: number, sun: number, moon: number, day: boolean): number;
|
|
7
|
+
/** Lot of Spirit (the reverse of Fortune): Asc + Sun - Moon by day. */
|
|
8
|
+
export declare function lotSpirit(asc: number, sun: number, moon: number, day: boolean): number;
|
|
9
|
+
/** The seven Hermetic lots from the Ascendant, sect, and the seven planets'
|
|
10
|
+
* longitudes (degrees). Pure arithmetic. */
|
|
11
|
+
export declare function hermeticLots(asc: number, day: boolean, sun: number, moon: number, mercury: number, venus: number, mars: number, jupiter: number, saturn: number): Record<HermeticLot, number>;
|
|
12
|
+
export interface ChartLots extends Record<HermeticLot, number> {
|
|
13
|
+
/** True when the Sun is above the horizon (a diurnal chart). */
|
|
14
|
+
day: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** The seven Hermetic lots of a chart: compute the Ascendant and sect, then the
|
|
17
|
+
* lots from the seven planets' longitudes. */
|
|
18
|
+
export declare function lots(engine: Engine, jdUt: number, lat: number, lonEast: number, zodiac?: Zodiac): ChartLots;
|
package/dist/src/lots.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* astroengine lots -- Hellenistic lots (Arabic parts), sect-aware.
|
|
3
|
+
*
|
|
4
|
+
* A lot is an arc cast from the Ascendant equal to the arc between two chart
|
|
5
|
+
* points, reversing direction between a day and a night chart. Arithmetic on
|
|
6
|
+
* apparent longitudes already checked against Swiss Ephemeris. Mirrors the
|
|
7
|
+
* Python reference (astroengine/lots.py); the golden fixtures pin the two
|
|
8
|
+
* together. Fortune and Spirit are symmetric about the Ascendant, so
|
|
9
|
+
* `(fortune + spirit) === 2 * asc` (mod 360).
|
|
10
|
+
*/
|
|
11
|
+
import { mod } from "./core.js";
|
|
12
|
+
import { isDayChart } from "./derived.js";
|
|
13
|
+
/** The seven Hermetic lots, in their conventional order. */
|
|
14
|
+
export const HERMETIC_LOTS = [
|
|
15
|
+
"fortune", "spirit", "eros", "necessity", "courage", "victory", "nemesis",
|
|
16
|
+
];
|
|
17
|
+
/** Asc + (a - b) by day, Asc + (b - a) by night, wrapped to [0, 360). */
|
|
18
|
+
function lot(asc, a, b, day) {
|
|
19
|
+
return mod(asc + (day ? a - b : b - a), 360);
|
|
20
|
+
}
|
|
21
|
+
/** Lot of Fortune: Asc + Moon - Sun by day, Asc + Sun - Moon by night. */
|
|
22
|
+
export function lotFortune(asc, sun, moon, day) {
|
|
23
|
+
return lot(asc, moon, sun, day);
|
|
24
|
+
}
|
|
25
|
+
/** Lot of Spirit (the reverse of Fortune): Asc + Sun - Moon by day. */
|
|
26
|
+
export function lotSpirit(asc, sun, moon, day) {
|
|
27
|
+
return lot(asc, sun, moon, day);
|
|
28
|
+
}
|
|
29
|
+
/** The seven Hermetic lots from the Ascendant, sect, and the seven planets'
|
|
30
|
+
* longitudes (degrees). Pure arithmetic. */
|
|
31
|
+
export function hermeticLots(asc, day, sun, moon, mercury, venus, mars, jupiter, saturn) {
|
|
32
|
+
const fortune = lotFortune(asc, sun, moon, day);
|
|
33
|
+
const spirit = lotSpirit(asc, sun, moon, day);
|
|
34
|
+
return {
|
|
35
|
+
fortune,
|
|
36
|
+
spirit,
|
|
37
|
+
eros: lot(asc, venus, spirit, day),
|
|
38
|
+
necessity: lot(asc, fortune, mercury, day),
|
|
39
|
+
courage: lot(asc, fortune, mars, day),
|
|
40
|
+
victory: lot(asc, jupiter, spirit, day),
|
|
41
|
+
nemesis: lot(asc, fortune, saturn, day),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** The seven Hermetic lots of a chart: compute the Ascendant and sect, then the
|
|
45
|
+
* lots from the seven planets' longitudes. */
|
|
46
|
+
export function lots(engine, jdUt, lat, lonEast, zodiac = "tropical") {
|
|
47
|
+
const asc = engine.chartAt(jdUt, lat, lonEast, { zodiac }).angles.asc;
|
|
48
|
+
const day = isDayChart(engine, jdUt, lat, lonEast);
|
|
49
|
+
const lon = (b) => engine.longitude(b, jdUt, { zodiac });
|
|
50
|
+
const h = hermeticLots(asc, day, lon("sun"), lon("moon"), lon("mercury"), lon("venus"), lon("mars"), lon("jupiter"), lon("saturn"));
|
|
51
|
+
return { day, ...h };
|
|
52
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Engine, Zodiac } from "./chart.js";
|
|
2
|
+
/** Traditional (domicile) ruler of each sign, Aries..Pisces. */
|
|
3
|
+
export declare const SIGN_RULERS: readonly ["mars", "venus", "mercury", "moon", "sun", "mercury", "venus", "mars", "jupiter", "saturn", "saturn", "jupiter"];
|
|
4
|
+
/** Traditional (domicile) ruler of a sign index (0 = Aries). */
|
|
5
|
+
export declare function signRuler(sign: number): string;
|
|
6
|
+
export interface ProfectedSign {
|
|
7
|
+
sign: string;
|
|
8
|
+
sign_index: number;
|
|
9
|
+
/** 1-based whole-sign house from the natal Ascendant. */
|
|
10
|
+
house: number;
|
|
11
|
+
/** Traditional (domicile) lord of the profected sign. */
|
|
12
|
+
lord: string;
|
|
13
|
+
}
|
|
14
|
+
/** The whole-sign profection `steps` signs after the Ascendant sign. */
|
|
15
|
+
export declare function profectedSign(ascSign: number, steps: number): ProfectedSign;
|
|
16
|
+
export interface Profection {
|
|
17
|
+
age_years: number;
|
|
18
|
+
/** 1-based month within the profection year. */
|
|
19
|
+
month: number;
|
|
20
|
+
annual: ProfectedSign;
|
|
21
|
+
monthly: ProfectedSign;
|
|
22
|
+
}
|
|
23
|
+
/** Annual and monthly profection at `targetJd` for a natal Ascendant sign. */
|
|
24
|
+
export declare function profection(ascSign: number, natalJd: number, targetJd: number, yearLength?: number): Profection;
|
|
25
|
+
/** Profection from a natal chart: take the Ascendant sign from the natal chart,
|
|
26
|
+
* then profect to `targetJd`. */
|
|
27
|
+
export declare function profectionAt(engine: Engine, natalJd: number, targetJd: number, lat: number, lonEast: number, zodiac?: Zodiac, yearLength?: number): Profection;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* astroengine profections -- annual and monthly profections, a Hellenistic
|
|
3
|
+
* time-lord technique.
|
|
4
|
+
*
|
|
5
|
+
* The Ascendant advances one whole sign per year of life; the profected sign's
|
|
6
|
+
* traditional (domicile) ruler is the lord of the year. Within the year the
|
|
7
|
+
* monthly profection advances one further sign per 1/12 of the year. Pure
|
|
8
|
+
* arithmetic on a date difference and the natal Ascendant sign. Mirrors the
|
|
9
|
+
* Python reference (astroengine/profections.py); the golden fixtures pin the
|
|
10
|
+
* two together. Whole-sign frame: the sign N signs after the Ascendant is the
|
|
11
|
+
* (N+1)th house. The profection year is a fixed length (the tropical year by
|
|
12
|
+
* default); birthday-exact profections would key off the solar return.
|
|
13
|
+
*/
|
|
14
|
+
import { mod } from "./core.js";
|
|
15
|
+
import { SIGNS } from "./chart.js";
|
|
16
|
+
import { TROPICAL_YEAR } from "./derived.js";
|
|
17
|
+
/** Traditional (domicile) ruler of each sign, Aries..Pisces. */
|
|
18
|
+
export const SIGN_RULERS = [
|
|
19
|
+
"mars", "venus", "mercury", "moon", "sun", "mercury",
|
|
20
|
+
"venus", "mars", "jupiter", "saturn", "saturn", "jupiter",
|
|
21
|
+
];
|
|
22
|
+
/** Traditional (domicile) ruler of a sign index (0 = Aries). */
|
|
23
|
+
export function signRuler(sign) {
|
|
24
|
+
return SIGN_RULERS[mod(sign, 12)];
|
|
25
|
+
}
|
|
26
|
+
/** The whole-sign profection `steps` signs after the Ascendant sign. */
|
|
27
|
+
export function profectedSign(ascSign, steps) {
|
|
28
|
+
const sign = mod(ascSign + steps, 12);
|
|
29
|
+
return { sign: SIGNS[sign], sign_index: sign, house: mod(steps, 12) + 1, lord: signRuler(sign) };
|
|
30
|
+
}
|
|
31
|
+
/** Annual and monthly profection at `targetJd` for a natal Ascendant sign. */
|
|
32
|
+
export function profection(ascSign, natalJd, targetJd, yearLength = TROPICAL_YEAR) {
|
|
33
|
+
const age = (targetJd - natalJd) / yearLength;
|
|
34
|
+
const years = Math.floor(age);
|
|
35
|
+
const month = Math.floor((age - years) * 12); // 0..11
|
|
36
|
+
return {
|
|
37
|
+
age_years: years,
|
|
38
|
+
month: month + 1,
|
|
39
|
+
annual: profectedSign(ascSign, years),
|
|
40
|
+
monthly: profectedSign(ascSign, years + month),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** Profection from a natal chart: take the Ascendant sign from the natal chart,
|
|
44
|
+
* then profect to `targetJd`. */
|
|
45
|
+
export function profectionAt(engine, natalJd, targetJd, lat, lonEast, zodiac = "tropical", yearLength = TROPICAL_YEAR) {
|
|
46
|
+
const asc = engine.chartAt(natalJd, lat, lonEast, { zodiac }).angles.asc;
|
|
47
|
+
const ascSign = mod(Math.floor(asc / 30), 12);
|
|
48
|
+
return profection(ascSign, natalJd, targetJd, yearLength);
|
|
49
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -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[];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { dignities } from "./derived.js";
|
|
2
|
+
/** Pancha Mahapurusha: [yoga name, planet]. */
|
|
3
|
+
const MAHAPURUSHA = [
|
|
4
|
+
["Ruchaka", "mars"], ["Bhadra", "mercury"], ["Hamsa", "jupiter"],
|
|
5
|
+
["Malavya", "venus"], ["Shasha", "saturn"],
|
|
6
|
+
];
|
|
7
|
+
const KENDRA = new Set([1, 4, 7, 10]);
|
|
8
|
+
export const YOGA_PLANETS = ["sun", "moon", "mars", "mercury", "jupiter", "venus", "saturn"];
|
|
9
|
+
/** The placement yogas present in a chart. `signs` maps each of the seven
|
|
10
|
+
* classical planets to its 0-based sign index; `ascSign` is the Ascendant's
|
|
11
|
+
* sign index. */
|
|
12
|
+
export function detectYogas(signs, ascSign) {
|
|
13
|
+
const house = (sign) => ((sign - ascSign) % 12 + 12) % 12 + 1;
|
|
14
|
+
const out = [];
|
|
15
|
+
for (const [name, p] of MAHAPURUSHA) {
|
|
16
|
+
const dig = dignities(p, signs[p]);
|
|
17
|
+
if ((dig.includes("domicile") || dig.includes("exaltation")) && KENDRA.has(house(signs[p]))) {
|
|
18
|
+
out.push({ yoga: name, planets: [p] });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const jkFromMoon = ((signs.jupiter - signs.moon) % 12 + 12) % 12;
|
|
22
|
+
if (jkFromMoon === 0 || jkFromMoon === 3 || jkFromMoon === 6 || jkFromMoon === 9) {
|
|
23
|
+
out.push({ yoga: "Gajakesari", planets: ["jupiter", "moon"] });
|
|
24
|
+
}
|
|
25
|
+
if (signs.sun === signs.mercury)
|
|
26
|
+
out.push({ yoga: "Budha-Aditya", planets: ["sun", "mercury"] });
|
|
27
|
+
if (signs.moon === signs.mars)
|
|
28
|
+
out.push({ yoga: "Chandra-Mangala", planets: ["moon", "mars"] });
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
/** The placement yogas of a natal chart, from the sidereal rasi positions. */
|
|
32
|
+
export function yogasAt(engine, natalJd, lat, lonEast, zodiac = "sidereal:lahiri") {
|
|
33
|
+
const chart = engine.chartAt(natalJd, lat, lonEast, { zodiac });
|
|
34
|
+
const ascSign = Math.floor(chart.angles.asc / 30) % 12;
|
|
35
|
+
const signs = {};
|
|
36
|
+
for (const b of YOGA_PLANETS)
|
|
37
|
+
signs[b] = Math.floor(chart.bodies[b].lon / 30) % 12;
|
|
38
|
+
return detectYogas(signs, ascSign);
|
|
39
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* astroengine yogini -- the Yogini dasha, a 36-year nakshatra-based dasha cycle.
|
|
3
|
+
*
|
|
4
|
+
* Eight yoginis rule in a fixed order with periods 1..8 years (totalling 36):
|
|
5
|
+
* Mangala (Moon) 1, Pingala (Sun) 2, Dhanya (Jupiter) 3, Bhramari (Mars) 4,
|
|
6
|
+
* Bhadrika (Mercury) 5, Ulka (Saturn) 6, Siddha (Venus) 7, Sankata (Rahu) 8.
|
|
7
|
+
* The starting yogini comes from the Moon's birth nakshatra: (nakshatra number
|
|
8
|
+
* + 3) mod 8, a remainder of 0 meaning the 8th. As in Vimshottari, the elapsed
|
|
9
|
+
* portion of the first period is the fraction of the nakshatra the Moon has
|
|
10
|
+
* traversed, and each period subdivides into eight proportional sub-periods.
|
|
11
|
+
* Mirrors the Python reference (astroengine/yogini.py); the golden fixtures pin
|
|
12
|
+
* the two together.
|
|
13
|
+
*/
|
|
14
|
+
import { Engine, Zodiac } from "./chart.js";
|
|
15
|
+
export declare const YOGINIS: readonly ["Mangala", "Pingala", "Dhanya", "Bhramari", "Bhadrika", "Ulka", "Siddha", "Sankata"];
|
|
16
|
+
export declare const YOGINI_LORDS: Record<(typeof YOGINIS)[number], string>;
|
|
17
|
+
/** Period in years by yogini index (Mangala..Sankata), totalling 36. */
|
|
18
|
+
export declare const YOGINI_YEARS: readonly [1, 2, 3, 4, 5, 6, 7, 8];
|
|
19
|
+
/** 0-based starting yogini index from the Moon's nakshatra index (0-based):
|
|
20
|
+
* (nakshatra number + 3) mod 8, a remainder of 0 mapping to the 8th. */
|
|
21
|
+
export declare function startingYogini(nakIndex: number): number;
|
|
22
|
+
export interface YoginiSub {
|
|
23
|
+
yogini: string;
|
|
24
|
+
lord: string;
|
|
25
|
+
start: number;
|
|
26
|
+
end: number;
|
|
27
|
+
}
|
|
28
|
+
export interface YoginiPeriod {
|
|
29
|
+
level: number;
|
|
30
|
+
yogini: string;
|
|
31
|
+
lord: string;
|
|
32
|
+
years: number;
|
|
33
|
+
start: number;
|
|
34
|
+
end: number;
|
|
35
|
+
sub: YoginiSub[];
|
|
36
|
+
}
|
|
37
|
+
export interface YoginiTimeline {
|
|
38
|
+
start_yogini: string;
|
|
39
|
+
balance_years: number;
|
|
40
|
+
dashas: YoginiPeriod[];
|
|
41
|
+
}
|
|
42
|
+
/** The Yogini dasha timeline from the Moon's sidereal longitude. */
|
|
43
|
+
export declare function yoginiDashas(moonLon: number, natalJd: number, levels?: number, yearLength?: number, count?: number): YoginiTimeline;
|
|
44
|
+
export interface YoginiActive {
|
|
45
|
+
maha: string;
|
|
46
|
+
antar: string | null;
|
|
47
|
+
}
|
|
48
|
+
/** The maha and antar yogini active at targetJd; null before the first period. */
|
|
49
|
+
export declare function yoginiActive(moonLon: number, natalJd: number, targetJd: number, yearLength?: number): YoginiActive | null;
|
|
50
|
+
/** Yogini dasha active at targetJd, from the natal Moon's nakshatra. */
|
|
51
|
+
export declare function yoginiAt(engine: Engine, natalJd: number, targetJd: number, zodiac?: Zodiac, yearLength?: number): {
|
|
52
|
+
moon_nakshatra: string;
|
|
53
|
+
start_yogini: string;
|
|
54
|
+
} & Partial<YoginiActive>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { nakshatra, NAK_SPAN, DASHA_YEAR } from "./vedic.js";
|
|
2
|
+
export const YOGINIS = [
|
|
3
|
+
"Mangala", "Pingala", "Dhanya", "Bhramari", "Bhadrika", "Ulka", "Siddha", "Sankata",
|
|
4
|
+
];
|
|
5
|
+
export const YOGINI_LORDS = {
|
|
6
|
+
Mangala: "moon", Pingala: "sun", Dhanya: "jupiter", Bhramari: "mars",
|
|
7
|
+
Bhadrika: "mercury", Ulka: "saturn", Siddha: "venus", Sankata: "rahu",
|
|
8
|
+
};
|
|
9
|
+
/** Period in years by yogini index (Mangala..Sankata), totalling 36. */
|
|
10
|
+
export const YOGINI_YEARS = [1, 2, 3, 4, 5, 6, 7, 8];
|
|
11
|
+
const YOGINI_TOTAL = 36;
|
|
12
|
+
/** 0-based starting yogini index from the Moon's nakshatra index (0-based):
|
|
13
|
+
* (nakshatra number + 3) mod 8, a remainder of 0 mapping to the 8th. */
|
|
14
|
+
export function startingYogini(nakIndex) {
|
|
15
|
+
const y = (nakIndex + 1 + 3) % 8; // nakshatra number is 1-based
|
|
16
|
+
return ((y - 1) % 8 + 8) % 8; // remainder 0 -> 8th (index 7)
|
|
17
|
+
}
|
|
18
|
+
/** The Yogini dasha timeline from the Moon's sidereal longitude. */
|
|
19
|
+
export function yoginiDashas(moonLon, natalJd, levels = 2, yearLength = DASHA_YEAR, count = 8) {
|
|
20
|
+
const nak = nakshatra(moonLon);
|
|
21
|
+
const start = startingYogini(nak.index);
|
|
22
|
+
const elapsed = nak.pos / NAK_SPAN;
|
|
23
|
+
const y0 = YOGINI_YEARS[start];
|
|
24
|
+
let t = natalJd - elapsed * y0 * yearLength;
|
|
25
|
+
const dashas = [];
|
|
26
|
+
for (let k = 0; k < count; k++) {
|
|
27
|
+
const yi = (start + k) % 8;
|
|
28
|
+
const years = YOGINI_YEARS[yi];
|
|
29
|
+
const span = years * yearLength;
|
|
30
|
+
const maha = {
|
|
31
|
+
level: 1, yogini: YOGINIS[yi], lord: YOGINI_LORDS[YOGINIS[yi]],
|
|
32
|
+
years, start: t, end: t + span, sub: [],
|
|
33
|
+
};
|
|
34
|
+
if (levels >= 2) {
|
|
35
|
+
let st = t;
|
|
36
|
+
for (let j = 0; j < 8; j++) {
|
|
37
|
+
const sj = (yi + j) % 8;
|
|
38
|
+
const subSpan = (years * YOGINI_YEARS[sj] / YOGINI_TOTAL) * yearLength;
|
|
39
|
+
maha.sub.push({ yogini: YOGINIS[sj], lord: YOGINI_LORDS[YOGINIS[sj]], start: st, end: st + subSpan });
|
|
40
|
+
st += subSpan;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
dashas.push(maha);
|
|
44
|
+
t += span;
|
|
45
|
+
}
|
|
46
|
+
return { start_yogini: YOGINIS[start], balance_years: (1 - elapsed) * y0, dashas };
|
|
47
|
+
}
|
|
48
|
+
/** The maha and antar yogini active at targetJd; null before the first period. */
|
|
49
|
+
export function yoginiActive(moonLon, natalJd, targetJd, yearLength = DASHA_YEAR) {
|
|
50
|
+
const timeline = yoginiDashas(moonLon, natalJd, 2, yearLength, 24).dashas;
|
|
51
|
+
const maha = timeline.find((p) => p.start <= targetJd && targetJd < p.end);
|
|
52
|
+
if (!maha)
|
|
53
|
+
return null;
|
|
54
|
+
const antar = maha.sub.find((s) => s.start <= targetJd && targetJd < s.end);
|
|
55
|
+
return { maha: maha.yogini, antar: antar ? antar.yogini : null };
|
|
56
|
+
}
|
|
57
|
+
/** Yogini dasha active at targetJd, from the natal Moon's nakshatra. */
|
|
58
|
+
export function yoginiAt(engine, natalJd, targetJd, zodiac = "sidereal:lahiri", yearLength = DASHA_YEAR) {
|
|
59
|
+
const moonLon = engine.longitude("moon", natalJd, { zodiac });
|
|
60
|
+
const nak = nakshatra(moonLon);
|
|
61
|
+
const active = yoginiActive(moonLon, natalJd, targetJd, yearLength) ?? {};
|
|
62
|
+
return { moon_nakshatra: nak.name, start_yogini: YOGINIS[startingYogini(nak.index)], ...active };
|
|
63
|
+
}
|