ArtaModel IV — genderless, even (|Δθ|), every pair in both orders, every long-term relationship; edition III kept below
Browse files- README.md +88 -3
- artamodel.py +36 -8
- artamodel_iv.json +303 -0
- artamodel_iv.py +131 -0
- artamodel_iv_deployed.json +335 -0
- artamodel_score_iv.py +118 -0
- kerykeion_phases.py +23 -11
README.md
CHANGED
|
@@ -1,13 +1,98 @@
|
|
| 1 |
---
|
| 2 |
license: cc0-1.0
|
| 3 |
language: [en]
|
| 4 |
-
tags: [astrology, sidereal, jyotisha,
|
| 5 |
-
datasets: [artaquest-foundation/artamatch-sidereal]
|
| 6 |
metrics: [roc_auc]
|
| 7 |
library_name: numpy
|
| 8 |
---
|
| 9 |
|
| 10 |
-
# ArtaModel
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
**A sidereal phase model of a marriage**, named by Arash Ashrafnejad (ArtaQuest Foundation, 2026-08-18). Three dates
|
| 13 |
and two places in — his birth and birthplace, hers, and the wedding date — one probability out: did the marriage
|
|
|
|
| 1 |
---
|
| 2 |
license: cc0-1.0
|
| 3 |
language: [en]
|
| 4 |
+
tags: [astrology, sidereal, jyotisha, relationships, tabular, phase-model, genderless, artaquest]
|
| 5 |
+
datasets: [artaquest-foundation/artamatch-genderless, artaquest-foundation/artamatch-sidereal]
|
| 6 |
metrics: [roc_auc]
|
| 7 |
library_name: numpy
|
| 8 |
---
|
| 9 |
|
| 10 |
+
# ArtaModel IV — genderless
|
| 11 |
+
|
| 12 |
+
**A genderless sidereal phase model of a long-term relationship**, the fourth edition of ArtaModel (Arash
|
| 13 |
+
Ashrafnejad, ArtaQuest Foundation, 2026-08-19: "I want a genderless model from now on"). Two births and birthplaces
|
| 14 |
+
— **in no order** — and the date the relationship began, one probability out: did it last thirty years?
|
| 15 |
+
|
| 16 |
+
```
|
| 17 |
+
y = | b + Σᵢ aᵢ ·e^{i|θ1ᵢ − θ2ᵢ|} the absolute synastry angle (even under the swap)
|
| 18 |
+
+ t1ᵢ·e^{i|θtᵢ − θ1ᵢ|} the wedding sky to partner 1's chart
|
| 19 |
+
+ t2ᵢ·e^{i|θtᵢ − θ2ᵢ|} the wedding sky to partner 2's chart
|
| 20 |
+
+ n1ᵢ·e^{i θ1ᵢ} partner 1's own natal longitude
|
| 21 |
+
+ n2ᵢ·e^{i θ2ᵢ} partner 2's own natal longitude
|
| 22 |
+
+ tnᵢ·e^{i θtᵢ} |² the wedding sky itself
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
for each of fourteen bodies *i* (Sun, Moon, Mercury, Venus, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto, Rāhu,
|
| 26 |
+
Ketu, Chiron, Lilith). θ are **sidereal longitudes (Lahiri)** from Kerykeion (Swiss Ephemeris): births cast at
|
| 27 |
+
**09:00 local time at the birthplace** (nobody's birth time is recorded — the dataset's convention), the start at
|
| 28 |
+
12:00 UT. **Genderless, three ways:** no sex is read; every phase *difference* enters as its wrapped absolute value
|
| 29 |
+
|Δθ| ∈ [0°, 180°], so each term is an even function of the swap; the training data carries every pair in both
|
| 30 |
+
orders; and the scorer averages the two orders, so the answer is identical whichever way the partners are given.
|
| 31 |
+
**Every long-term relationship** in Wikidata is in the data — marriages of every kind (same-sex included),
|
| 32 |
+
unmarried partnerships, business and sporting partnerships, "significant person" pairs with family excluded.
|
| 33 |
+
**A term exists only when both of its phases exist**: an unknown start day drops the wedding-sky terms, an unknown
|
| 34 |
+
birth drops that partner's terms; a missing phase contributes exactly zero.
|
| 35 |
+
|
| 36 |
+
## The deployed model, term by term
|
| 37 |
+
|
| 38 |
+
Gradient boosting over **split single-sum fields**: each stage is one field `|bₖ + wₖ·e^{iφ}|²` on **one** phasor,
|
| 39 |
+
chosen greedily as the phasor that best explains the current residual (all 84 phasors of all six terms compete at
|
| 40 |
+
every stage), added to the logit as `stepₖ·(αₖ·u + cₖ)`. Fitted on **all the data — train and test rows with both
|
| 41 |
+
natal charts, 35,894 rows (every pair in both orders)** — for 8 stages (the number the
|
| 42 |
+
train-only fit chose on its inner temporal split). Of the 84 phasors offered it chose **3**:
|
| 43 |
+
|
| 44 |
+
| phasor | body | term | stages | contribution to the logit swing | phase at which the field peaks |
|
| 45 |
+
|---|---|---|---|---|---|
|
| 46 |
+
| `a_uranus` | Uranus | a | 4 | 0.151 | 176° |
|
| 47 |
+
| `t2_neptune` | Neptune | t2 | 2 | 0.099 | 195° |
|
| 48 |
+
| `t1_neptune` | Neptune | t1 | 2 | 0.081 | 331° |
|
| 49 |
+
|
| 50 |
+
Never chosen, at any stage:
|
| 51 |
+
- **a** (a·e^{i|θ1−θ2|}): 13 bodies never chosen — sun, moon, mercury, venus, mars, jupiter, saturn, neptune, pluto, true_node, true_south_node, chiron, mean_lilith
|
| 52 |
+
- **t1** (t1·e^{i|θt−θ1|}): 13 bodies never chosen — sun, moon, mercury, venus, mars, jupiter, saturn, uranus, pluto, true_node, true_south_node, chiron, mean_lilith
|
| 53 |
+
- **t2** (t2·e^{i|θt−θ2|}): 13 bodies never chosen — sun, moon, mercury, venus, mars, jupiter, saturn, uranus, pluto, true_node, true_south_node, chiron, mean_lilith
|
| 54 |
+
- **n1** (n1·e^{iθ1}): 14 bodies never chosen — sun, moon, mercury, venus, mars, jupiter, saturn, uranus, neptune, pluto, true_node, true_south_node, chiron, mean_lilith
|
| 55 |
+
- **n2** (n2·e^{iθ2}): 14 bodies never chosen — sun, moon, mercury, venus, mars, jupiter, saturn, uranus, neptune, pluto, true_node, true_south_node, chiron, mean_lilith
|
| 56 |
+
- **tn** (tn·e^{iθt}): 14 bodies never chosen — sun, moon, mercury, venus, mars, jupiter, saturn, uranus, neptune, pluto, true_node, true_south_node, chiron, mean_lilith
|
| 57 |
+
|
| 58 |
+
**Read plainly:** `a_uranus` is the absolute gap between the two births measured by Uranus (4.3°/yr);
|
| 59 |
+
`t1_neptune` and `t2_neptune` are each partner's age at the start measured by Neptune (2.2°/yr) — chosen as a pair,
|
| 60 |
+
as a genderless model should. No natal phase, no wedding-sky phase, no fast body.
|
| 61 |
+
|
| 62 |
+
## What it scores, honestly
|
| 63 |
+
|
| 64 |
+
| on pairs born after 1900 (temporal hold-out; 7,631 pairs, both orders, symmetrised) | AUC |
|
| 65 |
+
|---|---|
|
| 66 |
+
| ArtaModel IV, fitted on train alone (inner split 0.6326, 8 stages) | **0.6252** (public board 0.6101) |
|
| 67 |
+
| the plain columns — the two ages at the start, the absolute gap, the start year (LightGBM) | 0.6114 (public board 0.6000) |
|
| 68 |
+
| equal-weight rank average of the two | 0.6289 (public board 0.6144) |
|
| 69 |
+
| ArtaModel IV with the two ages held flat (AUC within 3-year age cells) | 0.5659 |
|
| 70 |
+
|
| 71 |
+
What the model reads is the two partners' ages at the start and the absolute gap between their births, through the
|
| 72 |
+
outer planets as clocks — the same finding as every edition before it, now without a sex anywhere in the model. It
|
| 73 |
+
is exactly invariant to the ayanāṁśa, the birth hour and the birthplace; the age-cell-matched row is what is left
|
| 74 |
+
once the ages are held flat. See `ARTAMODEL.md` for the study (editions III and IV).
|
| 75 |
+
|
| 76 |
+
## Use
|
| 77 |
+
|
| 78 |
+
```python
|
| 79 |
+
from artamodel_score_iv import predict # needs: numpy, kerykeion, timezonefinder
|
| 80 |
+
r = predict("1936-08-04", 37.943, 23.647, # partner 1: dob, lat, lon
|
| 81 |
+
"1924-05-14", 37.727, 26.909, # partner 2 — the order does not matter
|
| 82 |
+
"1968-06-15") # start date (YYYY-01-01 = year only -> wedding-sky terms dropped)
|
| 83 |
+
r["probability"], r["terms"], r["terms_swapped"]
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
`artamodel_iv_deployed.json` holds every stage's weights; `artamodel_iv.py` is the fit (with `artamodel.py`,
|
| 87 |
+
`artamodel_deploy.py`, `kerykeion_phases.py`); `artamodel_iv.json` the leaderboard numbers. Data:
|
| 88 |
+
[artaquest-foundation/artamatch-genderless](https://www.kaggle.com/datasets/artaquest-foundation/artamatch-genderless);
|
| 89 |
+
competition: [artamatch-genderless](https://www.kaggle.com/competitions/artamatch-genderless). CC0.
|
| 90 |
+
|
| 91 |
+
---
|
| 92 |
+
|
| 93 |
+
## Edition III (superseded 2026-08-19): the gendered model, kept for the record
|
| 94 |
+
|
| 95 |
+
### ArtaModel (third edition)
|
| 96 |
|
| 97 |
**A sidereal phase model of a marriage**, named by Arash Ashrafnejad (ArtaQuest Foundation, 2026-08-18). Three dates
|
| 98 |
and two places in — his birth and birthplace, hers, and the wedding date — one probability out: did the marriage
|
artamodel.py
CHANGED
|
@@ -36,6 +36,9 @@ from coherent_fit import Coherent, auc # noqa
|
|
| 36 |
|
| 37 |
TERMS = ("a", "m", "d", "mn", "dn", "tn")
|
| 38 |
TERMS8 = TERMS + ("c", "tc")
|
|
|
|
|
|
|
|
|
|
| 39 |
BODIES14 = ["sun", "moon", "mercury", "venus", "mars", "jupiter", "saturn", "uranus", "neptune", "pluto",
|
| 40 |
"true_node", "true_south_node", "chiron", "mean_lilith"]
|
| 41 |
ANGLES = ["ascendant", "medium_coeli"]
|
|
@@ -51,27 +54,52 @@ def composite(D, M):
|
|
| 51 |
return (D + diff / 2.0) % 360.0
|
| 52 |
|
| 53 |
|
| 54 |
-
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
col = {b: j for j, b in enumerate(all_bodies)}
|
| 57 |
P, lab = [], []
|
| 58 |
use = list(bodies) + (ANGLES if angles_in_natal else [])
|
| 59 |
-
|
|
|
|
|
|
|
| 60 |
for b in use:
|
| 61 |
j = col[b]
|
| 62 |
if t == "a":
|
| 63 |
-
P.append(M[:, j]
|
| 64 |
elif t == "c":
|
| 65 |
P.append(composite(D[:, j], M[:, j]))
|
| 66 |
elif t == "tc":
|
| 67 |
if b in ANGLES: continue
|
| 68 |
-
P.append(W[:, j]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
elif t == "m":
|
| 70 |
if b in ANGLES: continue
|
| 71 |
-
P.append(W[:, j]
|
| 72 |
elif t == "d":
|
| 73 |
if b in ANGLES: continue
|
| 74 |
-
P.append(W[:, j]
|
| 75 |
elif t == "mn":
|
| 76 |
P.append(M[:, j])
|
| 77 |
elif t == "dn":
|
|
@@ -81,7 +109,7 @@ def phase_matrix(D, M, W, all_bodies, bodies, terms, angles_in_natal=False):
|
|
| 81 |
P.append(W[:, j])
|
| 82 |
else:
|
| 83 |
raise ValueError(t)
|
| 84 |
-
lab.append(f"{
|
| 85 |
return (np.column_stack(P) if P else np.zeros((len(D), 0))), lab
|
| 86 |
|
| 87 |
|
|
|
|
| 36 |
|
| 37 |
TERMS = ("a", "m", "d", "mn", "dn", "tn")
|
| 38 |
TERMS8 = TERMS + ("c", "tc")
|
| 39 |
+
# Arash, 2026-08-19: "add the midpoint of natals as another term. also midpoint with their wedding. overall 3 more
|
| 40 |
+
# terms" -> c (midpoint of the two natals), mw (midpoint of mom's natal with the wedding), dw (dad's with the wedding)
|
| 41 |
+
TERMS9 = TERMS + ("c", "mw", "dw")
|
| 42 |
BODIES14 = ["sun", "moon", "mercury", "venus", "mars", "jupiter", "saturn", "uranus", "neptune", "pluto",
|
| 43 |
"true_node", "true_south_node", "chiron", "mean_lilith"]
|
| 44 |
ANGLES = ["ascendant", "medium_coeli"]
|
|
|
|
| 54 |
return (D + diff / 2.0) % 360.0
|
| 55 |
|
| 56 |
|
| 57 |
+
# FOURTH EDITION — GENDERLESS (operator 2026-08-19): "I want a genderless model from now on ... (a, b, 1) should
|
| 58 |
+
# also mean (b, a, 1) ... for each subtractive term add abs to ensure each term is an even function." The two
|
| 59 |
+
# natal charts are slot 1 and slot 2 with no meaning attached; the files carry every pair in BOTH orders; and
|
| 60 |
+
# every phase DIFFERENCE enters as its wrapped absolute value |Δθ| in [0°, 180°], so a term's value is unchanged
|
| 61 |
+
# when the partners swap (and the wedding-sky terms are even in the same sense: |θt − θ|). Term names for this
|
| 62 |
+
# edition: a (synastry |θ1 − θ2|), t1/t2 (the wedding sky to each partner, |θt − θ|), n1/n2 (each natal phase),
|
| 63 |
+
# tn (the wedding sky's own phase). They map onto the earlier a/m/d/mn/dn/tn computations with `even=True`.
|
| 64 |
+
TERMS_IV = ("a", "t1", "t2", "n1", "n2", "tn")
|
| 65 |
+
_IV_TO_III = {"t1": "d", "t2": "m", "n1": "dn", "n2": "mn"}
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def absdiff(x, y):
|
| 69 |
+
"""The wrapped absolute phase difference |x − y| in [0, 180] degrees — even in (x, y)."""
|
| 70 |
+
return np.abs((x - y + 180.0) % 360.0 - 180.0)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def phase_matrix(D, M, W, all_bodies, bodies, terms, angles_in_natal=False, even=False):
|
| 74 |
+
"""(n, K) phases in degrees (NaN = the term does not exist for that row) and K labels.
|
| 75 |
+
even=True: every subtractive term is the wrapped absolute difference (the genderless edition)."""
|
| 76 |
col = {b: j for j, b in enumerate(all_bodies)}
|
| 77 |
P, lab = [], []
|
| 78 |
use = list(bodies) + (ANGLES if angles_in_natal else [])
|
| 79 |
+
diff = absdiff if even else (lambda x, y: x - y)
|
| 80 |
+
for t0 in terms:
|
| 81 |
+
t = _IV_TO_III.get(t0, t0)
|
| 82 |
for b in use:
|
| 83 |
j = col[b]
|
| 84 |
if t == "a":
|
| 85 |
+
P.append(diff(M[:, j], D[:, j]))
|
| 86 |
elif t == "c":
|
| 87 |
P.append(composite(D[:, j], M[:, j]))
|
| 88 |
elif t == "tc":
|
| 89 |
if b in ANGLES: continue
|
| 90 |
+
P.append(diff(W[:, j], composite(D[:, j], M[:, j])))
|
| 91 |
+
elif t == "mw":
|
| 92 |
+
if b in ANGLES: continue
|
| 93 |
+
P.append(composite(M[:, j], W[:, j])) # midpoint of mom's natal and the wedding sky
|
| 94 |
+
elif t == "dw":
|
| 95 |
+
if b in ANGLES: continue
|
| 96 |
+
P.append(composite(D[:, j], W[:, j])) # midpoint of dad's natal and the wedding sky
|
| 97 |
elif t == "m":
|
| 98 |
if b in ANGLES: continue
|
| 99 |
+
P.append(diff(W[:, j], M[:, j]))
|
| 100 |
elif t == "d":
|
| 101 |
if b in ANGLES: continue
|
| 102 |
+
P.append(diff(W[:, j], D[:, j]))
|
| 103 |
elif t == "mn":
|
| 104 |
P.append(M[:, j])
|
| 105 |
elif t == "dn":
|
|
|
|
| 109 |
P.append(W[:, j])
|
| 110 |
else:
|
| 111 |
raise ValueError(t)
|
| 112 |
+
lab.append(f"{t0}_{b}")
|
| 113 |
return (np.column_stack(P) if P else np.zeros((len(D), 0))), lab
|
| 114 |
|
| 115 |
|
artamodel_iv.json
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"edition": "IV",
|
| 3 |
+
"n_train_rows": 181596,
|
| 4 |
+
"n_test_rows": 15262,
|
| 5 |
+
"n_test_pairs": 7631,
|
| 6 |
+
"n_full_chart_train_rows": 20632,
|
| 7 |
+
"plain": {
|
| 8 |
+
"held": 0.6113819044977837,
|
| 9 |
+
"age_cell_matched": 0.523580650656717
|
| 10 |
+
},
|
| 11 |
+
"artamodel": {
|
| 12 |
+
"held": 0.6251903718901581,
|
| 13 |
+
"held_raw": 0.6252101256366446,
|
| 14 |
+
"age_cell_matched": 0.565921651972106,
|
| 15 |
+
"inner": 0.632624037191737,
|
| 16 |
+
"stages": 8,
|
| 17 |
+
"phasors_used": [
|
| 18 |
+
"a_uranus",
|
| 19 |
+
"t1_neptune",
|
| 20 |
+
"t2_pluto",
|
| 21 |
+
"t2_neptune"
|
| 22 |
+
],
|
| 23 |
+
"pair_asymmetry": 0.006218895572088684
|
| 24 |
+
},
|
| 25 |
+
"ensemble": {
|
| 26 |
+
"held": 0.6288835478263907,
|
| 27 |
+
"age_cell_matched": 0.5594745705436661
|
| 28 |
+
},
|
| 29 |
+
"terms": {
|
| 30 |
+
"a": "|\u03b81 \u2212 \u03b82|: the absolute synastry angle between the two natal charts (even under swap)",
|
| 31 |
+
"t1": "|\u03b8t \u2212 \u03b81|: the wedding sky to partner 1's natal longitude",
|
| 32 |
+
"t2": "|\u03b8t \u2212 \u03b82|: the wedding sky to partner 2's natal longitude",
|
| 33 |
+
"n1": "partner 1's own natal longitude",
|
| 34 |
+
"n2": "partner 2's own natal longitude",
|
| 35 |
+
"tn": "the wedding-day longitude itself"
|
| 36 |
+
},
|
| 37 |
+
"labels": [
|
| 38 |
+
"a_sun",
|
| 39 |
+
"a_moon",
|
| 40 |
+
"a_mercury",
|
| 41 |
+
"a_venus",
|
| 42 |
+
"a_mars",
|
| 43 |
+
"a_jupiter",
|
| 44 |
+
"a_saturn",
|
| 45 |
+
"a_uranus",
|
| 46 |
+
"a_neptune",
|
| 47 |
+
"a_pluto",
|
| 48 |
+
"a_true_node",
|
| 49 |
+
"a_true_south_node",
|
| 50 |
+
"a_chiron",
|
| 51 |
+
"a_mean_lilith",
|
| 52 |
+
"t1_sun",
|
| 53 |
+
"t1_moon",
|
| 54 |
+
"t1_mercury",
|
| 55 |
+
"t1_venus",
|
| 56 |
+
"t1_mars",
|
| 57 |
+
"t1_jupiter",
|
| 58 |
+
"t1_saturn",
|
| 59 |
+
"t1_uranus",
|
| 60 |
+
"t1_neptune",
|
| 61 |
+
"t1_pluto",
|
| 62 |
+
"t1_true_node",
|
| 63 |
+
"t1_true_south_node",
|
| 64 |
+
"t1_chiron",
|
| 65 |
+
"t1_mean_lilith",
|
| 66 |
+
"t2_sun",
|
| 67 |
+
"t2_moon",
|
| 68 |
+
"t2_mercury",
|
| 69 |
+
"t2_venus",
|
| 70 |
+
"t2_mars",
|
| 71 |
+
"t2_jupiter",
|
| 72 |
+
"t2_saturn",
|
| 73 |
+
"t2_uranus",
|
| 74 |
+
"t2_neptune",
|
| 75 |
+
"t2_pluto",
|
| 76 |
+
"t2_true_node",
|
| 77 |
+
"t2_true_south_node",
|
| 78 |
+
"t2_chiron",
|
| 79 |
+
"t2_mean_lilith",
|
| 80 |
+
"n1_sun",
|
| 81 |
+
"n1_moon",
|
| 82 |
+
"n1_mercury",
|
| 83 |
+
"n1_venus",
|
| 84 |
+
"n1_mars",
|
| 85 |
+
"n1_jupiter",
|
| 86 |
+
"n1_saturn",
|
| 87 |
+
"n1_uranus",
|
| 88 |
+
"n1_neptune",
|
| 89 |
+
"n1_pluto",
|
| 90 |
+
"n1_true_node",
|
| 91 |
+
"n1_true_south_node",
|
| 92 |
+
"n1_chiron",
|
| 93 |
+
"n1_mean_lilith",
|
| 94 |
+
"n2_sun",
|
| 95 |
+
"n2_moon",
|
| 96 |
+
"n2_mercury",
|
| 97 |
+
"n2_venus",
|
| 98 |
+
"n2_mars",
|
| 99 |
+
"n2_jupiter",
|
| 100 |
+
"n2_saturn",
|
| 101 |
+
"n2_uranus",
|
| 102 |
+
"n2_neptune",
|
| 103 |
+
"n2_pluto",
|
| 104 |
+
"n2_true_node",
|
| 105 |
+
"n2_true_south_node",
|
| 106 |
+
"n2_chiron",
|
| 107 |
+
"n2_mean_lilith",
|
| 108 |
+
"tn_sun",
|
| 109 |
+
"tn_moon",
|
| 110 |
+
"tn_mercury",
|
| 111 |
+
"tn_venus",
|
| 112 |
+
"tn_mars",
|
| 113 |
+
"tn_jupiter",
|
| 114 |
+
"tn_saturn",
|
| 115 |
+
"tn_uranus",
|
| 116 |
+
"tn_neptune",
|
| 117 |
+
"tn_pluto",
|
| 118 |
+
"tn_true_node",
|
| 119 |
+
"tn_true_south_node",
|
| 120 |
+
"tn_chiron",
|
| 121 |
+
"tn_mean_lilith"
|
| 122 |
+
],
|
| 123 |
+
"leaderboard": {
|
| 124 |
+
"F0": -0.32983794552870865,
|
| 125 |
+
"stages": [
|
| 126 |
+
{
|
| 127 |
+
"stage": 1,
|
| 128 |
+
"phasor": 7,
|
| 129 |
+
"step": 0.4,
|
| 130 |
+
"alpha": -0.3025812663117348,
|
| 131 |
+
"c": 0.16726821685620108,
|
| 132 |
+
"w_re": -0.21057191284516086,
|
| 133 |
+
"w_im": -0.425876200333399,
|
| 134 |
+
"b_re": 0.6107863105989085,
|
| 135 |
+
"b_im": 0.5605056821561912
|
| 136 |
+
},
|
| 137 |
+
{
|
| 138 |
+
"stage": 2,
|
| 139 |
+
"phasor": 36,
|
| 140 |
+
"step": 0.4,
|
| 141 |
+
"alpha": 0.519453130834745,
|
| 142 |
+
"c": -0.4099180846986107,
|
| 143 |
+
"w_re": -0.22061897085028806,
|
| 144 |
+
"w_im": -0.24300062023722413,
|
| 145 |
+
"b_re": -0.7282549974830534,
|
| 146 |
+
"b_im": -0.3886675397665921
|
| 147 |
+
},
|
| 148 |
+
{
|
| 149 |
+
"stage": 3,
|
| 150 |
+
"phasor": 22,
|
| 151 |
+
"step": 0.4,
|
| 152 |
+
"alpha": 0.48893778356724893,
|
| 153 |
+
"c": -0.30557951847828757,
|
| 154 |
+
"w_re": 0.2760151368679704,
|
| 155 |
+
"w_im": 0.19036050251246772,
|
| 156 |
+
"b_re": 0.710970393549423,
|
| 157 |
+
"b_im": 0.1646654555251957
|
| 158 |
+
},
|
| 159 |
+
{
|
| 160 |
+
"stage": 4,
|
| 161 |
+
"phasor": 7,
|
| 162 |
+
"step": 0.4,
|
| 163 |
+
"alpha": 0.42082875033094663,
|
| 164 |
+
"c": -0.2170703928424133,
|
| 165 |
+
"w_re": -0.4347436393823073,
|
| 166 |
+
"w_im": -0.13803958644238642,
|
| 167 |
+
"b_re": -0.4352382943096409,
|
| 168 |
+
"b_im": 0.17857939138405746
|
| 169 |
+
},
|
| 170 |
+
{
|
| 171 |
+
"stage": 5,
|
| 172 |
+
"phasor": 22,
|
| 173 |
+
"step": 0.4,
|
| 174 |
+
"alpha": 0.36385820296400506,
|
| 175 |
+
"c": -0.2863029007149194,
|
| 176 |
+
"w_re": 0.37205652641101605,
|
| 177 |
+
"w_im": 0.0036864218033502306,
|
| 178 |
+
"b_re": 0.7643472890323912,
|
| 179 |
+
"b_im": -0.292744659830657
|
| 180 |
+
},
|
| 181 |
+
{
|
| 182 |
+
"stage": 6,
|
| 183 |
+
"phasor": 7,
|
| 184 |
+
"step": 0.4,
|
| 185 |
+
"alpha": 0.22575105498589784,
|
| 186 |
+
"c": -0.23018974010738902,
|
| 187 |
+
"w_re": -0.45212517836925703,
|
| 188 |
+
"w_im": 0.11202375136041444,
|
| 189 |
+
"b_re": -0.5403941985609879,
|
| 190 |
+
"b_im": 0.4898483443114326
|
| 191 |
+
},
|
| 192 |
+
{
|
| 193 |
+
"stage": 7,
|
| 194 |
+
"phasor": 37,
|
| 195 |
+
"step": 0.4,
|
| 196 |
+
"alpha": 0.6364948915292409,
|
| 197 |
+
"c": -0.40972401951564086,
|
| 198 |
+
"w_re": -0.3412319535146097,
|
| 199 |
+
"w_im": 0.2234795041673507,
|
| 200 |
+
"b_re": 0.12619931698751752,
|
| 201 |
+
"b_im": 0.7645164673177844
|
| 202 |
+
},
|
| 203 |
+
{
|
| 204 |
+
"stage": 8,
|
| 205 |
+
"phasor": 22,
|
| 206 |
+
"step": 0.4,
|
| 207 |
+
"alpha": 0.39102210850368074,
|
| 208 |
+
"c": -0.10963081383635781,
|
| 209 |
+
"w_re": -0.373384819083809,
|
| 210 |
+
"w_im": 0.14228082101266815,
|
| 211 |
+
"b_re": -0.3073528966491033,
|
| 212 |
+
"b_im": 0.28233056845889576
|
| 213 |
+
}
|
| 214 |
+
],
|
| 215 |
+
"inner_auc": 0.632624037191737,
|
| 216 |
+
"labels": [
|
| 217 |
+
"a_sun",
|
| 218 |
+
"a_moon",
|
| 219 |
+
"a_mercury",
|
| 220 |
+
"a_venus",
|
| 221 |
+
"a_mars",
|
| 222 |
+
"a_jupiter",
|
| 223 |
+
"a_saturn",
|
| 224 |
+
"a_uranus",
|
| 225 |
+
"a_neptune",
|
| 226 |
+
"a_pluto",
|
| 227 |
+
"a_true_node",
|
| 228 |
+
"a_true_south_node",
|
| 229 |
+
"a_chiron",
|
| 230 |
+
"a_mean_lilith",
|
| 231 |
+
"t1_sun",
|
| 232 |
+
"t1_moon",
|
| 233 |
+
"t1_mercury",
|
| 234 |
+
"t1_venus",
|
| 235 |
+
"t1_mars",
|
| 236 |
+
"t1_jupiter",
|
| 237 |
+
"t1_saturn",
|
| 238 |
+
"t1_uranus",
|
| 239 |
+
"t1_neptune",
|
| 240 |
+
"t1_pluto",
|
| 241 |
+
"t1_true_node",
|
| 242 |
+
"t1_true_south_node",
|
| 243 |
+
"t1_chiron",
|
| 244 |
+
"t1_mean_lilith",
|
| 245 |
+
"t2_sun",
|
| 246 |
+
"t2_moon",
|
| 247 |
+
"t2_mercury",
|
| 248 |
+
"t2_venus",
|
| 249 |
+
"t2_mars",
|
| 250 |
+
"t2_jupiter",
|
| 251 |
+
"t2_saturn",
|
| 252 |
+
"t2_uranus",
|
| 253 |
+
"t2_neptune",
|
| 254 |
+
"t2_pluto",
|
| 255 |
+
"t2_true_node",
|
| 256 |
+
"t2_true_south_node",
|
| 257 |
+
"t2_chiron",
|
| 258 |
+
"t2_mean_lilith",
|
| 259 |
+
"n1_sun",
|
| 260 |
+
"n1_moon",
|
| 261 |
+
"n1_mercury",
|
| 262 |
+
"n1_venus",
|
| 263 |
+
"n1_mars",
|
| 264 |
+
"n1_jupiter",
|
| 265 |
+
"n1_saturn",
|
| 266 |
+
"n1_uranus",
|
| 267 |
+
"n1_neptune",
|
| 268 |
+
"n1_pluto",
|
| 269 |
+
"n1_true_node",
|
| 270 |
+
"n1_true_south_node",
|
| 271 |
+
"n1_chiron",
|
| 272 |
+
"n1_mean_lilith",
|
| 273 |
+
"n2_sun",
|
| 274 |
+
"n2_moon",
|
| 275 |
+
"n2_mercury",
|
| 276 |
+
"n2_venus",
|
| 277 |
+
"n2_mars",
|
| 278 |
+
"n2_jupiter",
|
| 279 |
+
"n2_saturn",
|
| 280 |
+
"n2_uranus",
|
| 281 |
+
"n2_neptune",
|
| 282 |
+
"n2_pluto",
|
| 283 |
+
"n2_true_node",
|
| 284 |
+
"n2_true_south_node",
|
| 285 |
+
"n2_chiron",
|
| 286 |
+
"n2_mean_lilith",
|
| 287 |
+
"tn_sun",
|
| 288 |
+
"tn_moon",
|
| 289 |
+
"tn_mercury",
|
| 290 |
+
"tn_venus",
|
| 291 |
+
"tn_mars",
|
| 292 |
+
"tn_jupiter",
|
| 293 |
+
"tn_saturn",
|
| 294 |
+
"tn_uranus",
|
| 295 |
+
"tn_neptune",
|
| 296 |
+
"tn_pluto",
|
| 297 |
+
"tn_true_node",
|
| 298 |
+
"tn_true_south_node",
|
| 299 |
+
"tn_chiron",
|
| 300 |
+
"tn_mean_lilith"
|
| 301 |
+
]
|
| 302 |
+
}
|
| 303 |
+
}
|
artamodel_iv.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
artamodel_iv.py — ArtaModel, FOURTH EDITION: genderless and even.
|
| 3 |
+
|
| 4 |
+
Operator 2026-08-19: "I want a genderless model from now on. so duplicate all the train and test data. (a, b, 1)
|
| 5 |
+
should also mean (b, a, 1) and add any longterm relationship to the dataset (including gay marriages and business
|
| 6 |
+
partnerships). also for each subtractive terms add abs to ensure each term is an even function. then start over the
|
| 7 |
+
competition."
|
| 8 |
+
|
| 9 |
+
y = | b + Σᵢ aᵢ·e^{i|θ1ᵢ − θ2ᵢ|} + t1ᵢ·e^{i|θtᵢ − θ1ᵢ|} + t2ᵢ·e^{i|θtᵢ − θ2ᵢ|} + n1ᵢ·e^{iθ1ᵢ} + n2ᵢ·e^{iθ2ᵢ} + tnᵢ·e^{iθtᵢ} |²
|
| 10 |
+
|
| 11 |
+
θ1, θ2 = the two partners' sidereal (Lahiri) longitudes at 09:00 local at the birthplace, in the order the row
|
| 12 |
+
gives them (the files carry every pair in both orders, so the model is symmetric by the data); θt = the wedding
|
| 13 |
+
sky at 12:00 UT; every difference is the wrapped absolute value in [0°, 180°], so each term is an even function of
|
| 14 |
+
the swap; the presence rule is unchanged (a term exists only when both its phases exist).
|
| 15 |
+
|
| 16 |
+
Produces, from AQ_PHASES (kerykeion_phases.py on the edition-IV files):
|
| 17 |
+
submission_plain_iv.csv LightGBM on the two ages at the start, |gap|, start year -- the bar
|
| 18 |
+
submission_artamodel_iv.csv the 6-term boosted-over-split construction (greedy, train-only fit)
|
| 19 |
+
submission_ensemble_iv.csv equal-weight rank average of the two
|
| 20 |
+
artamodel_iv.json every number the pages need (held out, age-cell-matched, pair-symmetry check)
|
| 21 |
+
artamodel_iv_deployed.json the same construction fitted on train + test, term by term
|
| 22 |
+
Every submission is SYMMETRISED: the two rows of a pair (p<n>a, p<n>b) receive the mean of their two scores.
|
| 23 |
+
Usage: AQ_PHASES=/tmp/aq4feat/phases.npz AQ_SOL=/tmp/aq4comp/solution.csv AQ_OUT=/tmp/aq4sub python artamodel_iv.py
|
| 24 |
+
"""
|
| 25 |
+
import json
|
| 26 |
+
import os
|
| 27 |
+
import sys
|
| 28 |
+
import time
|
| 29 |
+
|
| 30 |
+
import numpy as np
|
| 31 |
+
import pandas as pd
|
| 32 |
+
from scipy.stats import rankdata
|
| 33 |
+
|
| 34 |
+
HERE = os.path.dirname(os.path.abspath(__file__)); sys.path.insert(0, HERE); sys.path.insert(0, os.path.join(HERE, "..", "coherent"))
|
| 35 |
+
from artamodel import BODIES14, TERMS_IV, auc, phase_matrix # noqa: E402
|
| 36 |
+
from artamodel_deploy import boost_recorded, score, BODY_TEXT # noqa: E402
|
| 37 |
+
from artamodel_full_stack import matched # noqa: E402
|
| 38 |
+
|
| 39 |
+
PH = os.environ.get("AQ_PHASES", "/tmp/aq4feat/phases.npz"); SOL = os.environ.get("AQ_SOL", "/tmp/aq4comp/solution.csv")
|
| 40 |
+
OUT = os.environ.get("AQ_OUT", "/tmp/aq4sub"); T0 = time.time()
|
| 41 |
+
log = lambda *a: print(f"[{time.time()-T0:6.0f}s]", *a, flush=True)
|
| 42 |
+
r01 = lambda v: (rankdata(v) - 1) / max(1.0, len(v) - 1)
|
| 43 |
+
TERM_TEXT_IV = {"a": "|θ1 − θ2|: the absolute synastry angle between the two natal charts (even under swap)",
|
| 44 |
+
"t1": "|θt − θ1|: the wedding sky to partner 1's natal longitude", "t2": "|θt − θ2|: the wedding sky to partner 2's natal longitude",
|
| 45 |
+
"n1": "partner 1's own natal longitude", "n2": "partner 2's own natal longitude", "tn": "the wedding-day longitude itself"}
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def pair_key(ids):
|
| 49 |
+
return np.array([i[:-1] if (i[-1] in "ab" and i[:-1].lstrip("p").isdigit()) else i for i in ids])
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def symmetrise(ids, s):
|
| 53 |
+
"""Mean of the two orders of a pair — the genderless prediction; the two rows then carry the same score."""
|
| 54 |
+
k = pair_key(ids); df = pd.DataFrame({"k": k, "s": s}); m = df.groupby("k")["s"].transform("mean").to_numpy()
|
| 55 |
+
return m
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def explain_iv(model, labels):
|
| 59 |
+
rows = {}
|
| 60 |
+
for st in model["stages"]:
|
| 61 |
+
lab = labels[st["phasor"]]; term, body = lab.split("_", 1)
|
| 62 |
+
w = complex(st["w_re"], st["w_im"]); b = complex(st["b_re"], st["b_im"])
|
| 63 |
+
r = rows.setdefault(lab, {"phasor": lab, "term": term, "body": BODY_TEXT.get(body, body), "meaning": f"{TERM_TEXT_IV[term]}, for {BODY_TEXT.get(body, body)}",
|
| 64 |
+
"stages": 0, "contribution": 0.0, "phase_deg": None})
|
| 65 |
+
r["stages"] += 1; r["contribution"] += abs(st["step"] * st["alpha"]) * abs(w) ** 2
|
| 66 |
+
r["phase_deg"] = float(np.degrees(np.angle(b) - np.angle(w)) % 360.0)
|
| 67 |
+
used = sorted(rows.values(), key=lambda r: -r["contribution"]); unused = [l for l in labels if l not in rows]
|
| 68 |
+
return used, unused
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def main():
|
| 72 |
+
import lightgbm as lgb
|
| 73 |
+
os.makedirs(OUT, exist_ok=True)
|
| 74 |
+
Z = np.load(PH, allow_pickle=True); s1, s2 = list(Z["slots"]); bodies = list(Z["bodies"]); ids = Z["id_test"]; y = Z["y_train"].astype(np.int64)
|
| 75 |
+
Atr, Btr, Wtr = Z[f"theta_{s1}_train"], Z[f"theta_{s2}_train"], Z["theta_wed_train"]; Ate, Bte, Wte = Z[f"theta_{s1}_test"], Z[f"theta_{s2}_test"], Z["theta_wed_test"]
|
| 76 |
+
ptr, pte, pn = Z["plain_train"], Z["plain_test"], list(Z["plain_names"]); later = Z["yr_train"].astype(int).max(1)
|
| 77 |
+
sol = pd.read_csv(SOL).set_index("id"); lab = [c for c in sol.columns if c != "Usage"][0]; yte = sol.loc[ids, lab].to_numpy().astype(int)
|
| 78 |
+
j1 = ptr[:, pn.index("start_is_jan1")] == 1.0; j1e = pte[:, pn.index("start_is_jan1")] == 1.0; Wtr = Wtr.copy(); Wte = Wte.copy(); Wtr[j1] = np.nan; Wte[j1e] = np.nan
|
| 79 |
+
log(f"train {len(y):,} rows · test {len(ids):,} rows ({len(set(pair_key(ids))):,} pairs) · slots {s1}/{s2}")
|
| 80 |
+
# ---- plain: two ages, |gap|, start year (even by construction of |gap| and by the doubled rows) ----
|
| 81 |
+
ia, ib, ig, iy = pn.index(f"age_{s1}_at_start"), pn.index(f"age_{s2}_at_start"), pn.index("age_gap"), pn.index("start_year")
|
| 82 |
+
X = np.column_stack([ptr[:, ia], ptr[:, ib], np.abs(ptr[:, ig]), ptr[:, iy]]); Xe = np.column_stack([pte[:, ia], pte[:, ib], np.abs(pte[:, ig]), pte[:, iy]])
|
| 83 |
+
prm = dict(n_estimators=400, learning_rate=0.03, num_leaves=15, min_child_samples=100, colsample_bytree=0.8, subsample=0.8, subsample_freq=1, reg_lambda=10.0, verbose=-1)
|
| 84 |
+
p_plain = np.zeros(len(Xe))
|
| 85 |
+
for sd in range(3):
|
| 86 |
+
c = lgb.LGBMClassifier(random_state=sd, **prm); c.fit(X, y); p_plain += c.predict_proba(Xe)[:, 1] / 3
|
| 87 |
+
p_plain_s = symmetrise(ids, p_plain)
|
| 88 |
+
ages = pte[:, [ia, ib]]; cell = (np.floor(np.nan_to_num(np.maximum(ages[:, 0], ages[:, 1])) / 3) * 1000 + np.floor(np.nan_to_num(np.minimum(ages[:, 0], ages[:, 1])) / 3)).astype(int)
|
| 89 |
+
log(f" plain: held {auc(yte, p_plain):.4f} (symmetrised {auc(yte, p_plain_s):.4f}) age-cell-matched {matched(yte, p_plain_s, cell):.4f}")
|
| 90 |
+
# ---- ArtaModel IV: even phase matrix, 6 terms x 14 bodies ----
|
| 91 |
+
B = [bodies.index(b) for b in BODIES14]
|
| 92 |
+
P, labels = phase_matrix(Atr, Btr, Wtr, bodies, BODIES14, TERMS_IV, even=True); Pe, _ = phase_matrix(Ate, Bte, Wte, bodies, BODIES14, TERMS_IV, even=True)
|
| 93 |
+
charts = np.isfinite(Atr[:, B]).all(1) & np.isfinite(Btr[:, B]).all(1)
|
| 94 |
+
lat = later[charts]; inner = lat > np.quantile(lat, 0.85)
|
| 95 |
+
lb = boost_recorded(P[charts], y[charts], inner, stages=4 * len(labels), nu=0.1)
|
| 96 |
+
s_lb = score(lb, Pe); s_lb_s = symmetrise(ids, s_lb)
|
| 97 |
+
log(f" ArtaModel IV (leaderboard fit, {int(charts.sum()):,} full-chart rows, {len(lb['stages'])} stages, inner {lb['inner_auc']:.4f}): "
|
| 98 |
+
f"held {auc(yte, s_lb):.4f} (symmetrised {auc(yte, s_lb_s):.4f}) age-cell-matched {matched(yte, s_lb_s, cell):.4f}")
|
| 99 |
+
# symmetry check: how far apart are the two orders' raw scores? (0 = perfectly even model)
|
| 100 |
+
k = pair_key(ids); df = pd.DataFrame({"k": k, "s": r01(s_lb)}); spread = df.groupby("k")["s"].agg(lambda v: abs(v.max() - v.min())).mean()
|
| 101 |
+
log(f" raw pair asymmetry of the ArtaModel ranks (mean |rank_a − rank_b|): {spread:.4f}")
|
| 102 |
+
p_ens = 0.5 * r01(p_plain_s) + 0.5 * r01(s_lb_s)
|
| 103 |
+
log(f" ENSEMBLE plain + ArtaModel IV (equal-weight rank average): held {auc(yte, p_ens):.4f} age-cell-matched {matched(yte, p_ens, cell):.4f}")
|
| 104 |
+
for nm, v in (("plain_iv", p_plain_s), ("artamodel_iv", s_lb_s), ("ensemble_iv", p_ens)):
|
| 105 |
+
pd.DataFrame({"id": ids, lab: r01(v)}).to_csv(os.path.join(OUT, f"submission_{nm}.csv"), index=False)
|
| 106 |
+
used, unused = explain_iv(lb, labels)
|
| 107 |
+
# ---- deployed: train + test full-chart rows, the number of stages the leaderboard fit chose ----
|
| 108 |
+
charts_te = np.isfinite(Ate[:, B]).all(1) & np.isfinite(Bte[:, B]).all(1)
|
| 109 |
+
Pall = np.vstack([P[charts], Pe[charts_te]]); yall = np.concatenate([y[charts], yte[charts_te]])
|
| 110 |
+
dep = boost_recorded(Pall, yall, np.zeros(len(yall), bool), stages=len(lb["stages"]), nu=0.1)
|
| 111 |
+
dused, dunused = explain_iv(dep, labels)
|
| 112 |
+
dep.update({"edition": "IV — genderless, even", "labels": labels, "terms": list(TERMS_IV), "bodies": BODIES14, "fitted_on": "train + test rows with both natal charts (every pair in both orders)",
|
| 113 |
+
"n_rows": int(len(yall)), "leaderboard_estimate": {"inner_auc": lb["inner_auc"], "held_out_auc": float(auc(yte, s_lb_s)), "note": "the same construction fitted on train alone, symmetrised over the pair"},
|
| 114 |
+
"phase_convention": {"zodiac": "sidereal, Lahiri", "engine": "Kerykeion 5.12.9 (Swiss Ephemeris)", "birth_time": "09:00 local at the birthplace", "wedding_time": "12:00 UT",
|
| 115 |
+
"even": "every phase difference is the wrapped absolute value |Δθ| in [0°, 180°]", "order": "slot 1 / slot 2 carry no meaning; score both orders and average",
|
| 116 |
+
"presence_rule": "a term exists only when both of its phases exist; a missing phase contributes zero"},
|
| 117 |
+
"explanation": {"used": dused, "unused": dunused}})
|
| 118 |
+
json.dump(dep, open(os.path.join(OUT, "artamodel_iv_deployed.json"), "w"), indent=1)
|
| 119 |
+
R = {"edition": "IV", "n_train_rows": int(len(y)), "n_test_rows": int(len(ids)), "n_test_pairs": int(len(set(k))), "n_full_chart_train_rows": int(charts.sum()),
|
| 120 |
+
"plain": {"held": float(auc(yte, p_plain_s)), "age_cell_matched": float(matched(yte, p_plain_s, cell))},
|
| 121 |
+
"artamodel": {"held": float(auc(yte, s_lb_s)), "held_raw": float(auc(yte, s_lb)), "age_cell_matched": float(matched(yte, s_lb_s, cell)), "inner": lb["inner_auc"], "stages": len(lb["stages"]),
|
| 122 |
+
"phasors_used": [u["phasor"] for u in used], "pair_asymmetry": float(spread)},
|
| 123 |
+
"ensemble": {"held": float(auc(yte, p_ens)), "age_cell_matched": float(matched(yte, p_ens, cell))},
|
| 124 |
+
"terms": {t: TERM_TEXT_IV[t] for t in TERMS_IV}, "labels": labels, "leaderboard": {**lb, "labels": labels}}
|
| 125 |
+
json.dump(R, open(os.path.join(OUT, "artamodel_iv.json"), "w"), indent=1)
|
| 126 |
+
log(f" deployed fit: {len(yall):,} rows · {len(dep['stages'])} stages · phasors used: {', '.join(u['phasor'] for u in dused)}")
|
| 127 |
+
log(f"wrote {OUT}/submission_{{plain,artamodel,ensemble}}_iv.csv, artamodel_iv.json, artamodel_iv_deployed.json")
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
if __name__ == "__main__":
|
| 131 |
+
main()
|
artamodel_iv_deployed.json
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"F0": -0.32088433382887727,
|
| 3 |
+
"stages": [
|
| 4 |
+
{
|
| 5 |
+
"stage": 1,
|
| 6 |
+
"phasor": 7,
|
| 7 |
+
"step": 0.4,
|
| 8 |
+
"alpha": -0.3376948209619891,
|
| 9 |
+
"c": 0.1874793060543171,
|
| 10 |
+
"w_re": -0.24321451372901814,
|
| 11 |
+
"w_im": -0.4426184203580808,
|
| 12 |
+
"b_re": 0.6339725314962403,
|
| 13 |
+
"b_im": 0.6026368179437613
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"stage": 2,
|
| 17 |
+
"phasor": 7,
|
| 18 |
+
"step": 0.4,
|
| 19 |
+
"alpha": -0.44634092077748555,
|
| 20 |
+
"c": 0.1116560749961018,
|
| 21 |
+
"w_re": 0.46754000603237206,
|
| 22 |
+
"w_im": 0.2560313440563457,
|
| 23 |
+
"b_re": -0.5437361440779536,
|
| 24 |
+
"b_im": -0.15134405303585938
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
"stage": 3,
|
| 28 |
+
"phasor": 22,
|
| 29 |
+
"step": 0.4,
|
| 30 |
+
"alpha": 0.4772890407464341,
|
| 31 |
+
"c": -0.24435517906829834,
|
| 32 |
+
"w_re": 0.49816339813880034,
|
| 33 |
+
"w_im": -0.06628727704385114,
|
| 34 |
+
"b_re": 0.4920847669878105,
|
| 35 |
+
"b_im": -0.4043385699859723
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"stage": 4,
|
| 39 |
+
"phasor": 36,
|
| 40 |
+
"step": 0.4,
|
| 41 |
+
"alpha": 0.4121201205056047,
|
| 42 |
+
"c": -0.22911907658681685,
|
| 43 |
+
"w_re": -0.4990958449581369,
|
| 44 |
+
"w_im": 0.04409056714697404,
|
| 45 |
+
"b_re": -0.552262102086733,
|
| 46 |
+
"b_im": 0.38921839858489915
|
| 47 |
+
},
|
| 48 |
+
{
|
| 49 |
+
"stage": 5,
|
| 50 |
+
"phasor": 7,
|
| 51 |
+
"step": 0.4,
|
| 52 |
+
"alpha": 0.2891143341130896,
|
| 53 |
+
"c": -0.23242638188558676,
|
| 54 |
+
"w_re": 0.29176287067569107,
|
| 55 |
+
"w_im": 0.4353249731528996,
|
| 56 |
+
"b_re": 0.5499183154598283,
|
| 57 |
+
"b_im": 0.17856648674940595
|
| 58 |
+
},
|
| 59 |
+
{
|
| 60 |
+
"stage": 6,
|
| 61 |
+
"phasor": 36,
|
| 62 |
+
"step": 0.4,
|
| 63 |
+
"alpha": -0.30350135615690277,
|
| 64 |
+
"c": 0.1719921813087553,
|
| 65 |
+
"w_re": 0.2898425417803235,
|
| 66 |
+
"w_im": -0.6250770077916671,
|
| 67 |
+
"b_re": -0.5147192423447787,
|
| 68 |
+
"b_im": 0.6150611274518173
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
"stage": 7,
|
| 72 |
+
"phasor": 22,
|
| 73 |
+
"step": 0.4,
|
| 74 |
+
"alpha": 0.35284123130265743,
|
| 75 |
+
"c": -0.20570391043323596,
|
| 76 |
+
"w_re": 0.08529216192335105,
|
| 77 |
+
"w_im": 0.4735197468892639,
|
| 78 |
+
"b_re": 0.44026506888173583,
|
| 79 |
+
"b_im": 0.5399124753716616
|
| 80 |
+
},
|
| 81 |
+
{
|
| 82 |
+
"stage": 8,
|
| 83 |
+
"phasor": 7,
|
| 84 |
+
"step": 0.4,
|
| 85 |
+
"alpha": -0.41087990743918523,
|
| 86 |
+
"c": 0.07256860068983159,
|
| 87 |
+
"w_re": 0.3823720953371966,
|
| 88 |
+
"w_im": -0.2417660418214181,
|
| 89 |
+
"b_re": -0.44376920363750716,
|
| 90 |
+
"b_im": 0.32639335063421915
|
| 91 |
+
}
|
| 92 |
+
],
|
| 93 |
+
"inner_auc": null,
|
| 94 |
+
"edition": "IV \u2014 genderless, even",
|
| 95 |
+
"labels": [
|
| 96 |
+
"a_sun",
|
| 97 |
+
"a_moon",
|
| 98 |
+
"a_mercury",
|
| 99 |
+
"a_venus",
|
| 100 |
+
"a_mars",
|
| 101 |
+
"a_jupiter",
|
| 102 |
+
"a_saturn",
|
| 103 |
+
"a_uranus",
|
| 104 |
+
"a_neptune",
|
| 105 |
+
"a_pluto",
|
| 106 |
+
"a_true_node",
|
| 107 |
+
"a_true_south_node",
|
| 108 |
+
"a_chiron",
|
| 109 |
+
"a_mean_lilith",
|
| 110 |
+
"t1_sun",
|
| 111 |
+
"t1_moon",
|
| 112 |
+
"t1_mercury",
|
| 113 |
+
"t1_venus",
|
| 114 |
+
"t1_mars",
|
| 115 |
+
"t1_jupiter",
|
| 116 |
+
"t1_saturn",
|
| 117 |
+
"t1_uranus",
|
| 118 |
+
"t1_neptune",
|
| 119 |
+
"t1_pluto",
|
| 120 |
+
"t1_true_node",
|
| 121 |
+
"t1_true_south_node",
|
| 122 |
+
"t1_chiron",
|
| 123 |
+
"t1_mean_lilith",
|
| 124 |
+
"t2_sun",
|
| 125 |
+
"t2_moon",
|
| 126 |
+
"t2_mercury",
|
| 127 |
+
"t2_venus",
|
| 128 |
+
"t2_mars",
|
| 129 |
+
"t2_jupiter",
|
| 130 |
+
"t2_saturn",
|
| 131 |
+
"t2_uranus",
|
| 132 |
+
"t2_neptune",
|
| 133 |
+
"t2_pluto",
|
| 134 |
+
"t2_true_node",
|
| 135 |
+
"t2_true_south_node",
|
| 136 |
+
"t2_chiron",
|
| 137 |
+
"t2_mean_lilith",
|
| 138 |
+
"n1_sun",
|
| 139 |
+
"n1_moon",
|
| 140 |
+
"n1_mercury",
|
| 141 |
+
"n1_venus",
|
| 142 |
+
"n1_mars",
|
| 143 |
+
"n1_jupiter",
|
| 144 |
+
"n1_saturn",
|
| 145 |
+
"n1_uranus",
|
| 146 |
+
"n1_neptune",
|
| 147 |
+
"n1_pluto",
|
| 148 |
+
"n1_true_node",
|
| 149 |
+
"n1_true_south_node",
|
| 150 |
+
"n1_chiron",
|
| 151 |
+
"n1_mean_lilith",
|
| 152 |
+
"n2_sun",
|
| 153 |
+
"n2_moon",
|
| 154 |
+
"n2_mercury",
|
| 155 |
+
"n2_venus",
|
| 156 |
+
"n2_mars",
|
| 157 |
+
"n2_jupiter",
|
| 158 |
+
"n2_saturn",
|
| 159 |
+
"n2_uranus",
|
| 160 |
+
"n2_neptune",
|
| 161 |
+
"n2_pluto",
|
| 162 |
+
"n2_true_node",
|
| 163 |
+
"n2_true_south_node",
|
| 164 |
+
"n2_chiron",
|
| 165 |
+
"n2_mean_lilith",
|
| 166 |
+
"tn_sun",
|
| 167 |
+
"tn_moon",
|
| 168 |
+
"tn_mercury",
|
| 169 |
+
"tn_venus",
|
| 170 |
+
"tn_mars",
|
| 171 |
+
"tn_jupiter",
|
| 172 |
+
"tn_saturn",
|
| 173 |
+
"tn_uranus",
|
| 174 |
+
"tn_neptune",
|
| 175 |
+
"tn_pluto",
|
| 176 |
+
"tn_true_node",
|
| 177 |
+
"tn_true_south_node",
|
| 178 |
+
"tn_chiron",
|
| 179 |
+
"tn_mean_lilith"
|
| 180 |
+
],
|
| 181 |
+
"terms": [
|
| 182 |
+
"a",
|
| 183 |
+
"t1",
|
| 184 |
+
"t2",
|
| 185 |
+
"n1",
|
| 186 |
+
"n2",
|
| 187 |
+
"tn"
|
| 188 |
+
],
|
| 189 |
+
"bodies": [
|
| 190 |
+
"sun",
|
| 191 |
+
"moon",
|
| 192 |
+
"mercury",
|
| 193 |
+
"venus",
|
| 194 |
+
"mars",
|
| 195 |
+
"jupiter",
|
| 196 |
+
"saturn",
|
| 197 |
+
"uranus",
|
| 198 |
+
"neptune",
|
| 199 |
+
"pluto",
|
| 200 |
+
"true_node",
|
| 201 |
+
"true_south_node",
|
| 202 |
+
"chiron",
|
| 203 |
+
"mean_lilith"
|
| 204 |
+
],
|
| 205 |
+
"fitted_on": "train + test rows with both natal charts (every pair in both orders)",
|
| 206 |
+
"n_rows": 35894,
|
| 207 |
+
"leaderboard_estimate": {
|
| 208 |
+
"inner_auc": 0.632624037191737,
|
| 209 |
+
"held_out_auc": 0.6251903718901581,
|
| 210 |
+
"note": "the same construction fitted on train alone, symmetrised over the pair"
|
| 211 |
+
},
|
| 212 |
+
"phase_convention": {
|
| 213 |
+
"zodiac": "sidereal, Lahiri",
|
| 214 |
+
"engine": "Kerykeion 5.12.9 (Swiss Ephemeris)",
|
| 215 |
+
"birth_time": "09:00 local at the birthplace",
|
| 216 |
+
"wedding_time": "12:00 UT",
|
| 217 |
+
"even": "every phase difference is the wrapped absolute value |\u0394\u03b8| in [0\u00b0, 180\u00b0]",
|
| 218 |
+
"order": "slot 1 / slot 2 carry no meaning; score both orders and average",
|
| 219 |
+
"presence_rule": "a term exists only when both of its phases exist; a missing phase contributes zero"
|
| 220 |
+
},
|
| 221 |
+
"explanation": {
|
| 222 |
+
"used": [
|
| 223 |
+
{
|
| 224 |
+
"phasor": "a_uranus",
|
| 225 |
+
"term": "a",
|
| 226 |
+
"body": "Uranus",
|
| 227 |
+
"meaning": "|\u03b81 \u2212 \u03b82|: the absolute synastry angle between the two natal charts (even under swap), for Uranus",
|
| 228 |
+
"stages": 4,
|
| 229 |
+
"contribution": 0.15058023776611307,
|
| 230 |
+
"phase_deg": 175.96975487087076
|
| 231 |
+
},
|
| 232 |
+
{
|
| 233 |
+
"phasor": "t2_neptune",
|
| 234 |
+
"term": "t2",
|
| 235 |
+
"body": "Neptune",
|
| 236 |
+
"meaning": "|\u03b8t \u2212 \u03b82|: the wedding sky to partner 2's natal longitude, for Neptune",
|
| 237 |
+
"stages": 2,
|
| 238 |
+
"contribution": 0.09901603485407229,
|
| 239 |
+
"phase_deg": 195.04784294272702
|
| 240 |
+
},
|
| 241 |
+
{
|
| 242 |
+
"phasor": "t1_neptune",
|
| 243 |
+
"term": "t1",
|
| 244 |
+
"body": "Neptune",
|
| 245 |
+
"meaning": "|\u03b8t \u2212 \u03b81|: the wedding sky to partner 1's natal longitude, for Neptune",
|
| 246 |
+
"stages": 2,
|
| 247 |
+
"contribution": 0.08089028752347999,
|
| 248 |
+
"phase_deg": 331.01573734961244
|
| 249 |
+
}
|
| 250 |
+
],
|
| 251 |
+
"unused": [
|
| 252 |
+
"a_sun",
|
| 253 |
+
"a_moon",
|
| 254 |
+
"a_mercury",
|
| 255 |
+
"a_venus",
|
| 256 |
+
"a_mars",
|
| 257 |
+
"a_jupiter",
|
| 258 |
+
"a_saturn",
|
| 259 |
+
"a_neptune",
|
| 260 |
+
"a_pluto",
|
| 261 |
+
"a_true_node",
|
| 262 |
+
"a_true_south_node",
|
| 263 |
+
"a_chiron",
|
| 264 |
+
"a_mean_lilith",
|
| 265 |
+
"t1_sun",
|
| 266 |
+
"t1_moon",
|
| 267 |
+
"t1_mercury",
|
| 268 |
+
"t1_venus",
|
| 269 |
+
"t1_mars",
|
| 270 |
+
"t1_jupiter",
|
| 271 |
+
"t1_saturn",
|
| 272 |
+
"t1_uranus",
|
| 273 |
+
"t1_pluto",
|
| 274 |
+
"t1_true_node",
|
| 275 |
+
"t1_true_south_node",
|
| 276 |
+
"t1_chiron",
|
| 277 |
+
"t1_mean_lilith",
|
| 278 |
+
"t2_sun",
|
| 279 |
+
"t2_moon",
|
| 280 |
+
"t2_mercury",
|
| 281 |
+
"t2_venus",
|
| 282 |
+
"t2_mars",
|
| 283 |
+
"t2_jupiter",
|
| 284 |
+
"t2_saturn",
|
| 285 |
+
"t2_uranus",
|
| 286 |
+
"t2_pluto",
|
| 287 |
+
"t2_true_node",
|
| 288 |
+
"t2_true_south_node",
|
| 289 |
+
"t2_chiron",
|
| 290 |
+
"t2_mean_lilith",
|
| 291 |
+
"n1_sun",
|
| 292 |
+
"n1_moon",
|
| 293 |
+
"n1_mercury",
|
| 294 |
+
"n1_venus",
|
| 295 |
+
"n1_mars",
|
| 296 |
+
"n1_jupiter",
|
| 297 |
+
"n1_saturn",
|
| 298 |
+
"n1_uranus",
|
| 299 |
+
"n1_neptune",
|
| 300 |
+
"n1_pluto",
|
| 301 |
+
"n1_true_node",
|
| 302 |
+
"n1_true_south_node",
|
| 303 |
+
"n1_chiron",
|
| 304 |
+
"n1_mean_lilith",
|
| 305 |
+
"n2_sun",
|
| 306 |
+
"n2_moon",
|
| 307 |
+
"n2_mercury",
|
| 308 |
+
"n2_venus",
|
| 309 |
+
"n2_mars",
|
| 310 |
+
"n2_jupiter",
|
| 311 |
+
"n2_saturn",
|
| 312 |
+
"n2_uranus",
|
| 313 |
+
"n2_neptune",
|
| 314 |
+
"n2_pluto",
|
| 315 |
+
"n2_true_node",
|
| 316 |
+
"n2_true_south_node",
|
| 317 |
+
"n2_chiron",
|
| 318 |
+
"n2_mean_lilith",
|
| 319 |
+
"tn_sun",
|
| 320 |
+
"tn_moon",
|
| 321 |
+
"tn_mercury",
|
| 322 |
+
"tn_venus",
|
| 323 |
+
"tn_mars",
|
| 324 |
+
"tn_jupiter",
|
| 325 |
+
"tn_saturn",
|
| 326 |
+
"tn_uranus",
|
| 327 |
+
"tn_neptune",
|
| 328 |
+
"tn_pluto",
|
| 329 |
+
"tn_true_node",
|
| 330 |
+
"tn_true_south_node",
|
| 331 |
+
"tn_chiron",
|
| 332 |
+
"tn_mean_lilith"
|
| 333 |
+
]
|
| 334 |
+
}
|
| 335 |
+
}
|
artamodel_score_iv.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
artamodel_score_iv.py — score a pair with the deployed ArtaModel IV (genderless, even), from three dates and two
|
| 3 |
+
places. numpy only for the model; Kerykeion for the phases (pip install kerykeion timezonefinder).
|
| 4 |
+
|
| 5 |
+
from artamodel_score_iv import predict
|
| 6 |
+
p = predict("1936-08-04", 37.943, 23.647, "1924-05-14", 37.727, 26.909, "1968-06-15") # partner 1, partner 2, start
|
| 7 |
+
|
| 8 |
+
The two partners carry no order: the model is scored in BOTH orders and the two logits are averaged, so the
|
| 9 |
+
answer is exactly the same whichever way round you give them (every phase difference already enters as |Δθ|).
|
| 10 |
+
Returns the probability that the relationship lasted thirty years and the term-by-term account of both orders.
|
| 11 |
+
See README.md / ARTAMODEL.md for what the model is and, honestly, what it reads.
|
| 12 |
+
"""
|
| 13 |
+
import json
|
| 14 |
+
import os
|
| 15 |
+
import warnings
|
| 16 |
+
|
| 17 |
+
import numpy as np
|
| 18 |
+
|
| 19 |
+
warnings.filterwarnings("ignore")
|
| 20 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 21 |
+
MODEL = json.load(open(os.path.join(HERE, "artamodel_iv_deployed.json")))
|
| 22 |
+
BODIES = MODEL["bodies"]; TERMS = MODEL["terms"]; LABELS = MODEL["labels"]
|
| 23 |
+
ANGLES = {"ascendant", "medium_coeli"}
|
| 24 |
+
SLOW = {"jupiter", "saturn", "uranus", "neptune", "pluto", "true_node", "true_south_node", "chiron", "mean_lilith"}
|
| 25 |
+
_TF = None
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def _tz(lat, lon):
|
| 29 |
+
global _TF
|
| 30 |
+
if _TF is None:
|
| 31 |
+
from timezonefinder import TimezoneFinder
|
| 32 |
+
_TF = TimezoneFinder()
|
| 33 |
+
return _TF.timezone_at(lng=lon, lat=lat) or "UTC"
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def _prec(d):
|
| 37 |
+
if not d or d == "0000-00-00":
|
| 38 |
+
return 0
|
| 39 |
+
return 1 if d.endswith("-00-00") else (2 if d.endswith("-00") else 3)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def theta(date, lat=None, lon=None, hour=9, natal=True):
|
| 43 |
+
"""Sidereal (Lahiri) longitudes of the model's bodies at 09:00 local (natal) or 12:00 UT (wedding); NaN where
|
| 44 |
+
the date's precision cannot place a body (year-only -> slow bodies only)."""
|
| 45 |
+
from kerykeion import AstrologicalSubject
|
| 46 |
+
out = np.full(len(BODIES), np.nan); p = _prec(date)
|
| 47 |
+
if p == 0 or (natal and (lat is None or lon is None)):
|
| 48 |
+
return out
|
| 49 |
+
y, m, d = int(date[:4]), max(1, int(date[5:7])), max(1, int(date[8:10]))
|
| 50 |
+
if natal:
|
| 51 |
+
s = AstrologicalSubject("x", y, m, d, hour, 0, lng=float(lon), lat=float(lat), tz_str=_tz(float(lat), float(lon)),
|
| 52 |
+
city="x", nation="XX", zodiac_type="Sidereal", sidereal_mode="LAHIRI", online=False)
|
| 53 |
+
else:
|
| 54 |
+
s = AstrologicalSubject("w", y, m, d, 12, 0, lng=0.0, lat=51.48, tz_str="UTC", city="G", nation="GB",
|
| 55 |
+
zodiac_type="Sidereal", sidereal_mode="LAHIRI", online=False)
|
| 56 |
+
for j, b in enumerate(BODIES):
|
| 57 |
+
if p == 1 and b not in SLOW:
|
| 58 |
+
continue
|
| 59 |
+
if p == 2 and b not in SLOW and b != "sun":
|
| 60 |
+
continue
|
| 61 |
+
try:
|
| 62 |
+
out[j] = float(getattr(s, b).abs_pos)
|
| 63 |
+
except Exception:
|
| 64 |
+
pass
|
| 65 |
+
return out
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def absdiff(x, y):
|
| 69 |
+
"""The wrapped absolute phase difference in [0, 180] degrees — even in (x, y)."""
|
| 70 |
+
return np.abs((x - y + 180.0) % 360.0 - 180.0)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def phases(t1, t2, tw):
|
| 74 |
+
"""The 84 phases in the model's label order (NaN = the term does not exist). Every difference is |Δθ|."""
|
| 75 |
+
P = np.full(len(LABELS), np.nan)
|
| 76 |
+
col = {b: j for j, b in enumerate(BODIES)}
|
| 77 |
+
for k, lab in enumerate(LABELS):
|
| 78 |
+
t, b = lab.split("_", 1); j = col[b]
|
| 79 |
+
if t == "a": P[k] = absdiff(t1[j], t2[j])
|
| 80 |
+
elif t == "t1": P[k] = absdiff(tw[j], t1[j])
|
| 81 |
+
elif t == "t2": P[k] = absdiff(tw[j], t2[j])
|
| 82 |
+
elif t == "n1": P[k] = t1[j]
|
| 83 |
+
elif t == "n2": P[k] = t2[j]
|
| 84 |
+
elif t == "tn": P[k] = tw[j]
|
| 85 |
+
return P
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def logit(P):
|
| 89 |
+
"""The deployed model's logit for one phase vector, with the per-stage account."""
|
| 90 |
+
rad = np.pi / 180.0
|
| 91 |
+
C, S = np.nan_to_num(np.cos(P * rad)), np.nan_to_num(np.sin(P * rad))
|
| 92 |
+
F = MODEL["F0"]; account = []
|
| 93 |
+
for st in MODEL["stages"]:
|
| 94 |
+
j = st["phasor"]
|
| 95 |
+
if not np.isfinite(P[j]):
|
| 96 |
+
account.append({"stage": st["stage"], "phasor": LABELS[j], "contribution": 0.0, "note": "term absent for this couple"}); continue
|
| 97 |
+
Zr = st["w_re"] * C[j] - st["w_im"] * S[j] + st["b_re"]; Zi = st["w_re"] * S[j] + st["w_im"] * C[j] + st["b_im"]
|
| 98 |
+
u = Zr * Zr + Zi * Zi; contrib = st["step"] * (st["alpha"] * u + st["c"])
|
| 99 |
+
F += contrib; account.append({"stage": st["stage"], "phasor": LABELS[j], "phase_deg": float(P[j] % 360), "contribution": float(contrib)})
|
| 100 |
+
return float(F), account
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def predict(dob_1, lat_1, lon_1, dob_2, lat_2, lon_2, start):
|
| 104 |
+
wed = start if start[5:] != "01-01" else start[:4] + "-00-00" # a 1 January start is a year-only record
|
| 105 |
+
t1, t2, tw = theta(dob_1, lat_1, lon_1), theta(dob_2, lat_2, lon_2), theta(wed, natal=False)
|
| 106 |
+
F12, acc12 = logit(phases(t1, t2, tw)); F21, acc21 = logit(phases(t2, t1, tw))
|
| 107 |
+
F = 0.5 * (F12 + F21)
|
| 108 |
+
return {"probability": float(1 / (1 + np.exp(-F))), "logit": F, "logit_order_12": F12, "logit_order_21": F21,
|
| 109 |
+
"terms": acc12, "terms_swapped": acc21}
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
if __name__ == "__main__":
|
| 113 |
+
r = predict("1936-08-04", 37.943, 23.647, "1924-05-14", 37.727, 26.909, "1968-06-15")
|
| 114 |
+
r2 = predict("1924-05-14", 37.727, 26.909, "1936-08-04", 37.943, 23.647, "1968-06-15")
|
| 115 |
+
assert abs(r["probability"] - r2["probability"]) < 1e-12, "the scorer is not symmetric"
|
| 116 |
+
print(f" p(lasted 30 years) = {r['probability']:.3f} logit {r['logit']:+.3f} (swapped: {r2['probability']:.3f} — identical)")
|
| 117 |
+
for t in r["terms"][:8]:
|
| 118 |
+
print(f" stage {t['stage']:>2} {t['phasor']:<12} " + (f"φ={t['phase_deg']:6.1f}° {t['contribution']:+.4f}" if "phase_deg" in t else t["note"]))
|
kerykeion_phases.py
CHANGED
|
@@ -100,8 +100,16 @@ def _work(args):
|
|
| 100 |
return i, theta(dd, latd, lond, 9, True), theta(dm, latm, lonm, 9, True), theta(wed, None, None, 12, False)
|
| 101 |
|
| 102 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
def build(df):
|
| 104 |
-
|
|
|
|
|
|
|
| 105 |
for i, r in enumerate(df.itertuples(index=False))]
|
| 106 |
with mp.Pool(max(1, mp.cpu_count() - 1)) as pool:
|
| 107 |
res = pool.map(_work, jobs, chunksize=256)
|
|
@@ -112,27 +120,31 @@ def build(df):
|
|
| 112 |
|
| 113 |
|
| 114 |
def main():
|
| 115 |
-
tr = pd.read_csv(f"{SRC}/train.csv", dtype=
|
| 116 |
-
|
| 117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
if LIMIT:
|
| 119 |
tr, te = tr.head(LIMIT), te.head(max(200, LIMIT // 4)); log(f"AQ_LIMIT={LIMIT}: DRY RUN")
|
| 120 |
log(f"train {len(tr):,} · test {len(te):,}")
|
| 121 |
Dtr, Mtr, Wtr = build(tr); log("train phases")
|
| 122 |
Dte, Mte, Wte = build(te); log("test phases")
|
| 123 |
def plain(df):
|
| 124 |
-
yd = pd.to_numeric(df.
|
| 125 |
-
ym = pd.to_numeric(df.
|
| 126 |
sy = df.start.str[:4].astype(float)
|
| 127 |
return np.column_stack([sy - yd, sy - ym, ym - yd, sy, (df.start.str[5:] == "01-01").astype(float)])
|
| 128 |
os.makedirs(OUT, exist_ok=True)
|
| 129 |
-
np.savez_compressed(f"{OUT}/phases.npz",
|
| 130 |
-
|
| 131 |
y_train=tr[LABEL].to_numpy().astype(np.int8), id_test=te.id.to_numpy() if "id" in te else np.arange(len(te)),
|
| 132 |
plain_train=plain(tr), plain_test=plain(te),
|
| 133 |
-
plain_names=np.array(["
|
| 134 |
-
yr_train=np.column_stack([pd.to_numeric(
|
| 135 |
-
pd.to_numeric(
|
| 136 |
full = np.isfinite(Dtr).all(1) & np.isfinite(Mtr).all(1)
|
| 137 |
log(f"wrote {OUT}/phases.npz · {len(BODIES)} bodies · train rows with BOTH natal charts complete: {full.sum():,} · "
|
| 138 |
f"wedding sky complete: {np.isfinite(Wtr[:, :10]).all(1).sum():,}")
|
|
|
|
| 100 |
return i, theta(dd, latd, lond, 9, True), theta(dm, latm, lonm, 9, True), theta(wed, None, None, 12, False)
|
| 101 |
|
| 102 |
|
| 103 |
+
def slots(df):
|
| 104 |
+
"""FOURTH EDITION (genderless, 2026-08-19): the files carry `dob_a/dob_b`; the earlier editions `dob_dad/dob_mom`.
|
| 105 |
+
The extractor reads whichever it is given and names its outputs by the same slot names."""
|
| 106 |
+
return ("a", "b") if "dob_a" in df.columns else ("dad", "mom")
|
| 107 |
+
|
| 108 |
+
|
| 109 |
def build(df):
|
| 110 |
+
s1, s2 = slots(df)
|
| 111 |
+
jobs = [(i, getattr(r, f"dob_{s1}"), getattr(r, f"lat_{s1}"), getattr(r, f"lon_{s1}"), getattr(r, f"dob_{s2}"),
|
| 112 |
+
getattr(r, f"lat_{s2}"), getattr(r, f"lon_{s2}"), r.start)
|
| 113 |
for i, r in enumerate(df.itertuples(index=False))]
|
| 114 |
with mp.Pool(max(1, mp.cpu_count() - 1)) as pool:
|
| 115 |
res = pool.map(_work, jobs, chunksize=256)
|
|
|
|
| 120 |
|
| 121 |
|
| 122 |
def main():
|
| 123 |
+
tr = pd.read_csv(f"{SRC}/train.csv", dtype=str); te = pd.read_csv(f"{SRC}/test.csv", dtype=str)
|
| 124 |
+
s1, s2 = slots(tr)
|
| 125 |
+
for df in (tr, te):
|
| 126 |
+
for c in (f"lat_{s1}", f"lon_{s1}", f"lat_{s2}", f"lon_{s2}"):
|
| 127 |
+
df[c] = pd.to_numeric(df[c], errors="coerce")
|
| 128 |
+
LABEL = [c for c in tr.columns if c not in {"id", f"dob_{s1}", f"dob_{s2}", f"lat_{s1}", f"lon_{s1}", f"lat_{s2}", f"lon_{s2}", "start"}][0]
|
| 129 |
+
d1, d2 = tr[f"dob_{s1}"], tr[f"dob_{s2}"]
|
| 130 |
if LIMIT:
|
| 131 |
tr, te = tr.head(LIMIT), te.head(max(200, LIMIT // 4)); log(f"AQ_LIMIT={LIMIT}: DRY RUN")
|
| 132 |
log(f"train {len(tr):,} · test {len(te):,}")
|
| 133 |
Dtr, Mtr, Wtr = build(tr); log("train phases")
|
| 134 |
Dte, Mte, Wte = build(te); log("test phases")
|
| 135 |
def plain(df):
|
| 136 |
+
yd = pd.to_numeric(df[f"dob_{s1}"].str[:4], errors="coerce").where(df[f"dob_{s1}"] != "0000-00-00")
|
| 137 |
+
ym = pd.to_numeric(df[f"dob_{s2}"].str[:4], errors="coerce").where(df[f"dob_{s2}"] != "0000-00-00")
|
| 138 |
sy = df.start.str[:4].astype(float)
|
| 139 |
return np.column_stack([sy - yd, sy - ym, ym - yd, sy, (df.start.str[5:] == "01-01").astype(float)])
|
| 140 |
os.makedirs(OUT, exist_ok=True)
|
| 141 |
+
np.savez_compressed(f"{OUT}/phases.npz", **{f"theta_{s1}_train": Dtr, f"theta_{s2}_train": Mtr, "theta_wed_train": Wtr,
|
| 142 |
+
f"theta_{s1}_test": Dte, f"theta_{s2}_test": Mte, "theta_wed_test": Wte}, bodies=np.array(BODIES, dtype=object), slots=np.array([s1, s2], dtype=object),
|
| 143 |
y_train=tr[LABEL].to_numpy().astype(np.int8), id_test=te.id.to_numpy() if "id" in te else np.arange(len(te)),
|
| 144 |
plain_train=plain(tr), plain_test=plain(te),
|
| 145 |
+
plain_names=np.array([f"age_{s1}_at_start", f"age_{s2}_at_start", "age_gap", "start_year", "start_is_jan1"], dtype=object),
|
| 146 |
+
yr_train=np.column_stack([pd.to_numeric(d1.str[:4], errors="coerce").fillna(0),
|
| 147 |
+
pd.to_numeric(d2.str[:4], errors="coerce").fillna(0)]).astype(np.int16))
|
| 148 |
full = np.isfinite(Dtr).all(1) & np.isfinite(Mtr).all(1)
|
| 149 |
log(f"wrote {OUT}/phases.npz · {len(BODIES)} bodies · train rows with BOTH natal charts complete: {full.sum():,} · "
|
| 150 |
f"wedding sky complete: {np.isfinite(Wtr[:, :10]).all(1).sum():,}")
|