libaditya 0.3.4__py2.py3-none-any.whl

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.
Files changed (131) hide show
  1. libaditya/__init__.py +46 -0
  2. libaditya/_version.py +29 -0
  3. libaditya/calc/__init__.py +9 -0
  4. libaditya/calc/api.py +30 -0
  5. libaditya/calc/avasthas.py +346 -0
  6. libaditya/calc/docs/jaimini-get-arch.md +95 -0
  7. libaditya/calc/hellenistic.py +37 -0
  8. libaditya/calc/jaimini.py +335 -0
  9. libaditya/calc/jaimini_get.py +240 -0
  10. libaditya/calc/kala.py +73 -0
  11. libaditya/calc/panchanga.py +422 -0
  12. libaditya/calc/rashi.py +612 -0
  13. libaditya/calc/returns.py +50 -0
  14. libaditya/calc/swe_functions.py +118 -0
  15. libaditya/calc/varga.py +287 -0
  16. libaditya/calc/vimshottari.py +431 -0
  17. libaditya/cards/__init__.py +5 -0
  18. libaditya/cards/card.py +90 -0
  19. libaditya/cards/cards_constants.py +115 -0
  20. libaditya/cards/cards_of_truth.py +323 -0
  21. libaditya/cards/cot.py +142 -0
  22. libaditya/cards/deck.py +52 -0
  23. libaditya/charts/__init__.py +6 -0
  24. libaditya/charts/api.py +36 -0
  25. libaditya/charts/bodygraph.py +80 -0
  26. libaditya/charts/chart.py +286 -0
  27. libaditya/charts/jaimini.py +83 -0
  28. libaditya/charts/tajika.py +36 -0
  29. libaditya/constants.py +1776 -0
  30. libaditya/draw/__init__.py +20 -0
  31. libaditya/draw/draw_bodygraph.py +489 -0
  32. libaditya/draw/draw_sbc.py +712 -0
  33. libaditya/draw/themes/default-theme.hd +53 -0
  34. libaditya/ephe/ast0/se00010s.se1 +0 -0
  35. libaditya/ephe/seas_00.se1 +0 -0
  36. libaditya/ephe/seas_06.se1 +0 -0
  37. libaditya/ephe/seas_12.se1 +0 -0
  38. libaditya/ephe/seas_18.se1 +0 -0
  39. libaditya/ephe/seas_24.se1 +0 -0
  40. libaditya/ephe/seas_30.se1 +0 -0
  41. libaditya/ephe/seas_36.se1 +0 -0
  42. libaditya/ephe/seas_42.se1 +0 -0
  43. libaditya/ephe/seas_48.se1 +0 -0
  44. libaditya/ephe/seasm06.se1 +0 -0
  45. libaditya/ephe/seasm12.se1 +0 -0
  46. libaditya/ephe/seasm18.se1 +0 -0
  47. libaditya/ephe/seasm24.se1 +0 -0
  48. libaditya/ephe/seasm30.se1 +0 -0
  49. libaditya/ephe/seasm36.se1 +0 -0
  50. libaditya/ephe/seasm42.se1 +0 -0
  51. libaditya/ephe/seasm48.se1 +0 -0
  52. libaditya/ephe/seasm54.se1 +0 -0
  53. libaditya/ephe/sefstars.txt +4245 -0
  54. libaditya/ephe/semo_00.se1 +0 -0
  55. libaditya/ephe/semo_06.se1 +0 -0
  56. libaditya/ephe/semo_12.se1 +0 -0
  57. libaditya/ephe/semo_18.se1 +0 -0
  58. libaditya/ephe/semo_24.se1 +0 -0
  59. libaditya/ephe/semo_30.se1 +0 -0
  60. libaditya/ephe/semo_36.se1 +0 -0
  61. libaditya/ephe/semo_42.se1 +0 -0
  62. libaditya/ephe/semo_48.se1 +0 -0
  63. libaditya/ephe/semom06.se1 +0 -0
  64. libaditya/ephe/semom12.se1 +0 -0
  65. libaditya/ephe/semom18.se1 +0 -0
  66. libaditya/ephe/semom24.se1 +0 -0
  67. libaditya/ephe/semom30.se1 +0 -0
  68. libaditya/ephe/semom36.se1 +0 -0
  69. libaditya/ephe/semom42.se1 +0 -0
  70. libaditya/ephe/semom48.se1 +0 -0
  71. libaditya/ephe/semom54.se1 +0 -0
  72. libaditya/ephe/sepl_00.se1 +0 -0
  73. libaditya/ephe/sepl_06.se1 +0 -0
  74. libaditya/ephe/sepl_12.se1 +0 -0
  75. libaditya/ephe/sepl_18.se1 +0 -0
  76. libaditya/ephe/sepl_24.se1 +0 -0
  77. libaditya/ephe/sepl_30.se1 +0 -0
  78. libaditya/ephe/sepl_36.se1 +0 -0
  79. libaditya/ephe/sepl_42.se1 +0 -0
  80. libaditya/ephe/sepl_48.se1 +0 -0
  81. libaditya/ephe/seplm06.se1 +0 -0
  82. libaditya/ephe/seplm12.se1 +0 -0
  83. libaditya/ephe/seplm18.se1 +0 -0
  84. libaditya/ephe/seplm24.se1 +0 -0
  85. libaditya/ephe/seplm30.se1 +0 -0
  86. libaditya/ephe/seplm36.se1 +0 -0
  87. libaditya/ephe/seplm42.se1 +0 -0
  88. libaditya/ephe/seplm48.se1 +0 -0
  89. libaditya/ephe/seplm54.se1 +0 -0
  90. libaditya/hd/__init__.py +8 -0
  91. libaditya/hd/calc.py +55 -0
  92. libaditya/hd/constants.py +127 -0
  93. libaditya/hd/longitude.py +271 -0
  94. libaditya/objects/__init__.py +19 -0
  95. libaditya/objects/celestial_object.py +206 -0
  96. libaditya/objects/context.py +133 -0
  97. libaditya/objects/cusps.py +259 -0
  98. libaditya/objects/julian_day.py +265 -0
  99. libaditya/objects/location.py +167 -0
  100. libaditya/objects/longitude.py +987 -0
  101. libaditya/objects/nakshatras.py +285 -0
  102. libaditya/objects/planets.py +2059 -0
  103. libaditya/objects/shadbala.py +398 -0
  104. libaditya/objects/signs.py +690 -0
  105. libaditya/objects/swe_functions.py +75 -0
  106. libaditya/print_functions.py +548 -0
  107. libaditya/read.py +665 -0
  108. libaditya/stars/__init__.py +4 -0
  109. libaditya/stars/fixed_star.py +216 -0
  110. libaditya/stars/make_swe_stars.py +407 -0
  111. libaditya/stars/star-sign-boundaries +44 -0
  112. libaditya/stars/stellarium/__init__.py +6 -0
  113. libaditya/stars/stellarium/remote_control/__init__.py +22 -0
  114. libaditya/stars/stellarium/remote_control/location.py +82 -0
  115. libaditya/stars/stellarium/remote_control/locationsearch.py +35 -0
  116. libaditya/stars/stellarium/remote_control/main.py +165 -0
  117. libaditya/stars/stellarium/remote_control/objects.py +63 -0
  118. libaditya/stars/stellarium/remote_control/scripts.py +81 -0
  119. libaditya/stars/stellarium/remote_control/simbad.py +20 -0
  120. libaditya/stars/stellarium/remote_control/stelaction.py +31 -0
  121. libaditya/stars/stellarium/remote_control/stelproperty.py +30 -0
  122. libaditya/stars/stellarium/remote_control/view.py +85 -0
  123. libaditya/stars/stellarium/stellarium.py +92 -0
  124. libaditya/stars/the_stars.py +522 -0
  125. libaditya/stars/utilities.py +539 -0
  126. libaditya/utils.py +431 -0
  127. libaditya/write.py +67 -0
  128. libaditya-0.3.4.dist-info/METADATA +588 -0
  129. libaditya-0.3.4.dist-info/RECORD +131 -0
  130. libaditya-0.3.4.dist-info/WHEEL +5 -0
  131. libaditya-0.3.4.dist-info/licenses/LICENSE +661 -0
libaditya/__init__.py ADDED
@@ -0,0 +1,46 @@
1
+ # This file is part of libaditya.
2
+ #
3
+ # Copyright (c) 2025 Josh Harper <humanhaven@substack.com>
4
+ #
5
+ # libaditya is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # libaditya is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Affero General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with libaditya. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ import swisseph as swe
19
+ import pathlib
20
+ from dataclasses import replace
21
+ from rich.console import Console
22
+
23
+ from libaditya.objects import *
24
+ from libaditya.calc import *
25
+ from libaditya.charts import *
26
+ from libaditya.hd import *
27
+ from libaditya.stars import *
28
+ from libaditya.cards import *
29
+ from libaditya import constants as const
30
+ from libaditya import utils
31
+ from libaditya import read
32
+ from libaditya import write
33
+ from libaditya import print_functions as printf
34
+
35
+
36
+ # base_path means for libaditya src itself
37
+ base_path = os.path.dirname(os.path.realpath(__file__))
38
+ # the
39
+ package_path = os.path.dirname(pathlib.Path(__file__).parent)+"/"
40
+
41
+ swe.set_ephe_path(base_path + "/ephe/")
42
+
43
+ console = Console()
44
+
45
+ # from ._version import __version__
46
+
libaditya/_version.py ADDED
@@ -0,0 +1,29 @@
1
+ # This file is part of libaditya.
2
+ #
3
+ # Copyright (c) 2025 Josh Harper <humanhaven@substack.com>
4
+ #
5
+ # libaditya is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # libaditya is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Affero General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with libaditya. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ # this sets __version__ for everyone
19
+
20
+ import toml
21
+ import os
22
+ import pathlib
23
+
24
+ package_path = os.path.dirname(pathlib.Path(__file__).parent)+"/"
25
+
26
+ with open(package_path+"pyproject.toml", 'r') as f:
27
+ config = toml.load(f)
28
+
29
+ __version__ = config["project"]["version"]
@@ -0,0 +1,9 @@
1
+ from libaditya.objects import *
2
+
3
+ from .panchanga import Panchanga
4
+ from .vimshottari import *
5
+ from .kala import *
6
+ from .varga import Varga
7
+ from .rashi import Rashi
8
+ from .jaimini import Jaimini
9
+ from .api import API
libaditya/calc/api.py ADDED
@@ -0,0 +1,30 @@
1
+ # This file is part of libaditya.
2
+ #
3
+ # Copyright (c) 2025 Josh Harper <humanhaven@substack.com>
4
+ #
5
+ # libaditya is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # libaditya is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Affero General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with libaditya. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ from libaditya.objects import Sign
19
+
20
+ class API:
21
+ """
22
+ a Mixin API for Varga
23
+ these are just functions to help make finding information easier
24
+ these are all simply wrappers for other functions
25
+ e.g., rashi_aspects are computed by Signs, but there is a function in here
26
+ to access them from Varga
27
+ """
28
+
29
+ def rashi_aspects_given_to(self, sign: Sign) -> [Sign]:
30
+ return self.signs().rashi_aspects_given_to(sign)
@@ -0,0 +1,346 @@
1
+ # This file is part of libaditya.
2
+ #
3
+ # Copyright (c) 2025 Josh Harper <humanhaven@substack.com>
4
+ #
5
+ # libaditya is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # libaditya is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Affero General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with libaditya. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ import math
19
+ import swisseph as swe
20
+
21
+ from libaditya import constants as const
22
+ from libaditya import utils
23
+
24
+ KARAKAS = ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn"]
25
+ GRAHAS = KARAKAS + ["Rahu", "Ketu"]
26
+ WATER_SIGNS = {4, 8, 12}
27
+
28
+ BALADI_STATES = ["Bala", "Kumara", "Yuva", "Vriddha", "Mrita"]
29
+ JAGRADADI_STATES = ["Jagrat", "Swapna", "Sushupti"]
30
+ DEEPTADI_STATES = ["Deepta", "Swastha", "Mudita", "Shanta", "Shakta",
31
+ "Peedita", "Deena", "Vikala", "Khala"]
32
+ SHAYANADI_STATES = ["Shayana", "Upavesha", "Netrapani", "Prakasha",
33
+ "Gamana", "Agamana", "Sabha", "Agama",
34
+ "Bhojana", "Nrityalipsa", "Kautuka", "Nidra"]
35
+ SHAYANADI_MULTIPLIERS = {"Sun": 5, "Moon": 2, "Mars": 2, "Mercury": 3,
36
+ "Jupiter": 5, "Venus": 3, "Saturn": 3}
37
+ COMBUST_ORBS = {"Moon": 12, "Mars": 17, "Mercury": 14, "Jupiter": 11,
38
+ "Venus": 10, "Saturn": 15}
39
+ BENEFIC_NAVAMSA_SIGNS = {2, 3, 4, 6, 7, 9, 12}
40
+ NATURAL_MALEFICS = {"Sun", "Mars", "Saturn", "Rahu", "Ketu"}
41
+
42
+
43
+ class LajjitaadiAvasthas:
44
+
45
+ def lajjitaadi_avasthas(self) -> dict:
46
+ """
47
+ Calculate Lajjitaadi Avasthas for the seven karakas.
48
+
49
+ Returns a dict keyed by planet name, each value being a dict of
50
+ avastha names mapped to lists of contributing factors. Each factor
51
+ has a source, strength (0-60), and relevant planet/lord info.
52
+
53
+ Ernst Wilhelm's version plus custom Healthy (Svastha) avastha.
54
+ """
55
+ planets = self.planets()
56
+ signs = self.signs()
57
+ cusps = self.cusps()
58
+ lagna = signs.lagna()
59
+ fifth_sign_num = lagna.astrological_signs_forward(5)
60
+
61
+ result = {}
62
+
63
+ for name in KARAKAS:
64
+ planet = planets[name]
65
+ avasthas = {}
66
+ sign_num = planet.sign()
67
+ sign_lord = const.lords[sign_num]
68
+
69
+ # precompute natural relationships and aspects from other planets
70
+ for other_name in GRAHAS:
71
+ if other_name == name:
72
+ continue
73
+ other = planets[other_name]
74
+ aspect = planet.parashara_aspect_from(other)
75
+ is_conjunction = (aspect == "Y")
76
+ aspect_strength = aspect if isinstance(aspect, (int, float)) else None
77
+ # natural relationship: how does planet feel about other?
78
+ if other_name in KARAKAS:
79
+ rel = planet.natural_relationship_from(other)
80
+ else:
81
+ rel = None
82
+ is_friend = rel in ("F", "OH")
83
+ is_enemy = rel == "E"
84
+
85
+ # --- Delighted ---
86
+ # conjunct Jupiter
87
+ if is_conjunction and other_name == "Jupiter":
88
+ _add(avasthas, "delighted", {"source": "conjunction", "planet": other_name, "strength": 60})
89
+ # conjunct natural friend (except Saturn)
90
+ if is_conjunction and is_friend and other_name != "Saturn":
91
+ _add(avasthas, "delighted", {"source": "conjunction", "planet": other_name, "strength": 60})
92
+ # aspected by natural friend
93
+ if aspect_strength and is_friend:
94
+ _add(avasthas, "delighted", {"source": "aspect", "planet": other_name, "strength": aspect_strength})
95
+
96
+ # --- Starved ---
97
+ # conjunct natural enemy
98
+ if is_conjunction and is_enemy:
99
+ _add(avasthas, "starved", {"source": "conjunction", "planet": other_name, "strength": 60})
100
+ # conjunct Saturn (for non-Saturn planets)
101
+ if is_conjunction and other_name == "Saturn":
102
+ _add(avasthas, "starved", {"source": "conjunction", "planet": "Saturn", "strength": 60})
103
+ # aspected by natural enemy
104
+ if aspect_strength and is_enemy:
105
+ _add(avasthas, "starved", {"source": "aspect", "planet": other_name, "strength": aspect_strength})
106
+
107
+ # --- Agitated ---
108
+ # conjunct Sun
109
+ if is_conjunction and other_name == "Sun":
110
+ _add(avasthas, "agitated", {"source": "conjunction", "planet": "Sun", "strength": 60})
111
+ # aspected by natural enemy who is also a natural malefic
112
+ if aspect_strength and is_enemy and other_name in KARAKAS:
113
+ if other.nature() == "Malefic":
114
+ _add(avasthas, "agitated", {"source": "aspect", "planet": other_name, "strength": aspect_strength})
115
+
116
+ # --- Thirsty ---
117
+ # in a water sign AND aspected by natural enemy
118
+ if sign_num in WATER_SIGNS and aspect_strength and is_enemy:
119
+ _add(avasthas, "thirsty", {"source": "aspect", "planet": other_name, "strength": aspect_strength})
120
+
121
+ # --- Delighted: in sign of natural friend ---
122
+ if sign_lord != name and sign_lord in KARAKAS:
123
+ lord_planet = planets[sign_lord]
124
+ rel_to_lord = planet.natural_relationship_from(lord_planet)
125
+ if rel_to_lord in ("F", "OH"):
126
+ _add(avasthas, "delighted", {"source": "sign", "lord": sign_lord, "strength": 60})
127
+
128
+ # --- Starved: in sign of natural enemy ---
129
+ if sign_lord != name and sign_lord in KARAKAS:
130
+ lord_planet = planets[sign_lord]
131
+ rel_to_lord = planet.natural_relationship_from(lord_planet)
132
+ if rel_to_lord == "E":
133
+ _add(avasthas, "starved", {"source": "sign", "lord": sign_lord, "strength": 60})
134
+
135
+ # --- Healthy: planet in own sign ---
136
+ if planet.is_oh():
137
+ _add(avasthas, "healthy", {"source": "sign", "strength": 60})
138
+
139
+ # --- Proud: planet is EX or MT ---
140
+ if planet.is_ex():
141
+ _add(avasthas, "proud", {"source": "dignity", "dignity": "EX", "strength": 60})
142
+ elif planet.is_mt():
143
+ _add(avasthas, "proud", {"source": "dignity", "dignity": "MT", "strength": 60})
144
+
145
+ # --- Thirsty: water sign check (base condition) ---
146
+ # already handled above in the per-other loop; the water sign is a
147
+ # prerequisite for the aspect trigger, not a standalone trigger
148
+
149
+ # --- Shamed ---
150
+ # conjunct Sun/Mars/Saturn AND (conjunct Rahu/Ketu OR in 5th sign OR conjunct 5th cusp)
151
+ conj_sun_mars_saturn = []
152
+ conj_rahu_ketu = False
153
+ for other_name in GRAHAS:
154
+ if other_name == name:
155
+ continue
156
+ other = planets[other_name]
157
+ aspect = planet.parashara_aspect_from(other)
158
+ if aspect == "Y":
159
+ if other_name in ("Sun", "Mars", "Saturn"):
160
+ conj_sun_mars_saturn.append(other_name)
161
+ if other_name in ("Rahu", "Ketu"):
162
+ conj_rahu_ketu = True
163
+
164
+ if conj_sun_mars_saturn:
165
+ in_fifth_sign = (sign_num == fifth_sign_num)
166
+ conj_fifth_cusp = (planet.sign() == cusps[5].sign())
167
+ if conj_rahu_ketu or in_fifth_sign or conj_fifth_cusp:
168
+ for trigger in conj_sun_mars_saturn:
169
+ _add(avasthas, "shamed", {"source": "conjunction", "planet": trigger, "strength": 60})
170
+ if conj_rahu_ketu:
171
+ _add(avasthas, "shamed", {"source": "condition", "detail": "conjunct Rahu/Ketu"})
172
+ if in_fifth_sign:
173
+ _add(avasthas, "shamed", {"source": "condition", "detail": "in 5th sign"})
174
+ if conj_fifth_cusp and not in_fifth_sign:
175
+ _add(avasthas, "shamed", {"source": "condition", "detail": "conjunct 5th cusp"})
176
+
177
+ planet.attributes["lajjitaadi_avasthas"] = avasthas
178
+ if avasthas:
179
+ result[name] = avasthas
180
+ sign_obj = signs[sign_num]
181
+ sign_obj._lajjitaadi_avasthas[name] = avasthas
182
+
183
+ # build giving and receiving dicts for each karaka
184
+ for name in KARAKAS:
185
+ planet = planets[name]
186
+ avasthas = planet.attributes["lajjitaadi_avasthas"]
187
+ # receiving: factors from my avasthas where another planet is the cause
188
+ receiving = {}
189
+ for avastha, factors in avasthas.items():
190
+ planet_factors = [f for f in factors if "planet" in f]
191
+ if planet_factors:
192
+ receiving[avastha] = planet_factors
193
+ planet.attributes["lajjitaadi_receiving"] = receiving
194
+ # giving: scan all other planets' avasthas for factors where I am the cause
195
+ giving = {}
196
+ for other_name in KARAKAS:
197
+ if other_name == name:
198
+ continue
199
+ other_avasthas = planets[other_name].attributes["lajjitaadi_avasthas"]
200
+ for avastha, factors in other_avasthas.items():
201
+ for f in factors:
202
+ if f.get("planet") == name:
203
+ _add(giving, avastha, {**f, "to": other_name})
204
+ planet.attributes["lajjitaadi_giving"] = giving
205
+
206
+ return result
207
+
208
+
209
+ def _add(avasthas, avastha_name, factor):
210
+ if avastha_name not in avasthas:
211
+ avasthas[avastha_name] = []
212
+ avasthas[avastha_name].append(factor)
213
+
214
+
215
+ class BaladiAvasthas:
216
+
217
+ def baladi_avasthas(self) -> dict:
218
+ planets = self.planets()
219
+ result = {}
220
+ for name in KARAKAS:
221
+ planet = planets[name]
222
+ degree = planet.real_in_sign_longitude()
223
+ b = math.floor(degree / 6.0)
224
+ if b > 4:
225
+ b = 4
226
+ if utils.even(planet.sign()):
227
+ b = 4 - b
228
+ state = BALADI_STATES[b]
229
+ planet.attributes["baladi_avastha"] = state
230
+ result[name] = state
231
+ return result
232
+
233
+
234
+ class JagradadiAvasthas:
235
+
236
+ def jagradadi_avasthas(self) -> dict:
237
+ planets = self.planets()
238
+ result = {}
239
+ for name in KARAKAS:
240
+ planet = planets[name]
241
+ if planet.is_ex() or planet.is_oh() or planet.is_mt():
242
+ state = "Jagrat"
243
+ elif planet.dignity() in ("F", "GF"):
244
+ state = "Swapna"
245
+ else:
246
+ state = "Sushupti"
247
+ planet.attributes["jagradadi_avastha"] = state
248
+ result[name] = state
249
+ return result
250
+
251
+
252
+ class DeeptadiAvasthas:
253
+
254
+ def deeptadi_avasthas(self) -> dict:
255
+ planets = self.planets()
256
+ sun = planets["Sun"]
257
+ result = {}
258
+ for name in KARAKAS:
259
+ planet = planets[name]
260
+ state = self._deeptadi_state(planet, name, planets, sun)
261
+ planet.attributes["deeptadi_avastha"] = state
262
+ result[name] = state
263
+ return result
264
+
265
+ def _deeptadi_state(self, planet, name, planets, sun):
266
+ # 1. Deepta - exalted
267
+ if planet.is_ex():
268
+ return "Deepta"
269
+ # 2. Swastha - own sign
270
+ if planet.is_oh():
271
+ return "Swastha"
272
+ # 3. Mudita - friend's sign
273
+ if planet.dignity() in ("F", "GF"):
274
+ return "Mudita"
275
+ # 4. Shanta - benefic navamsa sign
276
+ navamsa_sign = math.floor(planet.ecliptic_longitude() / (30 / 9)) % 12 + 1
277
+ if navamsa_sign in BENEFIC_NAVAMSA_SIGNS:
278
+ return "Shanta"
279
+ # 5. Shakta - retrograde
280
+ if planet.retrograde():
281
+ return "Shakta"
282
+ # 6. Peedita - conjunct a natural malefic
283
+ for other_name in GRAHAS:
284
+ if other_name == name:
285
+ continue
286
+ if other_name not in NATURAL_MALEFICS:
287
+ continue
288
+ other = planets[other_name]
289
+ aspect = planet.parashara_aspect_from(other)
290
+ if aspect == "Y":
291
+ return "Peedita"
292
+ # 7. Deena - debilitated
293
+ if planet.is_db():
294
+ return "Deena"
295
+ # 8. Vikala - combust
296
+ if name != "Sun" and name in COMBUST_ORBS:
297
+ sep = abs(planet.ecliptic_longitude() - sun.ecliptic_longitude())
298
+ if sep > 180:
299
+ sep = 360 - sep
300
+ orb = COMBUST_ORBS[name]
301
+ if name == "Mercury" and planet.retrograde():
302
+ orb = 12
303
+ if sep <= orb:
304
+ return "Vikala"
305
+ # 9. Khala - enemy's sign
306
+ if planet.dignity() in ("E", "GE"):
307
+ return "Khala"
308
+ # fallback (shouldn't normally happen - neutral sign, not retro, not combust, etc.)
309
+ return "Shanta"
310
+
311
+
312
+ class ShayanadiAvasthas:
313
+
314
+ def shayanadi_avasthas(self) -> dict:
315
+ planets = self.planets()
316
+ context = self.context
317
+ birth_jd = context.timeJD.jd_number()
318
+ # find the most recent sunrise before birth
319
+ # rise_trans finds the *next* rise, so start from ~1 day before birth
320
+ sunrise_jd = swe.rise_trans(
321
+ birth_jd - 1,
322
+ swe.SUN,
323
+ swe.CALC_RISE | swe.BIT_HINDU_RISING,
324
+ context.location.swe_location(),
325
+ )[1][0]
326
+ # if that sunrise is still after birth (shouldn't happen), go back further
327
+ if sunrise_jd > birth_jd:
328
+ sunrise_jd = swe.rise_trans(
329
+ birth_jd - 2,
330
+ swe.SUN,
331
+ swe.CALC_RISE | swe.BIT_HINDU_RISING,
332
+ context.location.swe_location(),
333
+ )[1][0]
334
+ birth_ghatis = (birth_jd - sunrise_jd) * 60
335
+
336
+ result = {}
337
+ for name in KARAKAS:
338
+ planet = planets[name]
339
+ nak_num = planet.nakshatra().index() + 1
340
+ multiplier = SHAYANADI_MULTIPLIERS[name]
341
+ navamsa_num = math.floor(planet.real_in_sign_longitude() / (30 / 9)) + 1
342
+ value = math.floor(nak_num * multiplier + navamsa_num + birth_ghatis) % 12
343
+ state = SHAYANADI_STATES[value]
344
+ planet.attributes["shayanadi_avastha"] = state
345
+ result[name] = state
346
+ return result
@@ -0,0 +1,95 @@
1
+ # jaimini_get.py — Architecture
2
+
3
+ ## What this does
4
+
5
+ In Jaimini astrology, interpretation often starts from a specific sign — usually derived from a karaka (significator) placement — and then asks: **what planets influence that sign?** Influence means conjunction (planets in the sign) and rashi aspects (planets in signs that aspect it).
6
+
7
+ The question varies by topic:
8
+
9
+ - **Spirituality**: what influences the 12th sign from the Atmakaraka?
10
+ - **Spouse**: what influences the 7th sign from the Atmakaraka?
11
+ - **Home**: what influences the 4th sign from the Atmakaraka?
12
+ - **Karakamshas**: what influences the sign the Atmakaraka itself sits in?
13
+ - **Farmer**: what influences the 3rd, 6th, and 9th house cusps?
14
+
15
+ Each of these is the same operation with different inputs. `jaimini_get.py` encodes those inputs declaratively and provides one general method to compute them all.
16
+
17
+ ## Gets: the declarative specs
18
+
19
+ The `Gets` class holds topic definitions as class-level dicts. Each has two keys:
20
+
21
+ - **`factor`**: a list of strings identifying which sign(s) to examine
22
+ - **`vargas`**: a list of divisional chart numbers to check, in priority order
23
+
24
+ ### Factor string format
25
+
26
+ Three shapes:
27
+
28
+ | Format | Example | Meaning |
29
+ |---|---|---|
30
+ | `"N away XK"` | `"12 away AK"` | Count N signs from karaka XK. Direction depends on odd/even: odd sign = forward, even sign = backward. |
31
+ | `"XK"` | `"AK"` | The sign the karaka occupies (no counting). |
32
+ | `"N"` | `"3"` | The sign of the Nth house cusp (no karaka involved). |
33
+
34
+ Karaka abbreviations map to `jaimini_karakas()` return order:
35
+
36
+ ```
37
+ 0: AK (Atmakaraka)
38
+ 1: AmK (Amatyakaraka)
39
+ 2: BK (Bhratrukaraka)
40
+ 3: MK (Matrukaraka / Putrakaraka)
41
+ 4: PiK (Pitrkaraka)
42
+ 5: GK (Gnatikaraka)
43
+ 6: DK (Darakaraka)
44
+ ```
45
+
46
+ ### Varga overrides
47
+
48
+ Some vargas have variants. The D24 (Chaturvimsamsha) has a standard version and the Siddhamsha (`-240`), where even signs start from Cancer in reverse. The `get()` method accepts `varga_overrides` to swap variants. Default: `{"24": "-240"}`.
49
+
50
+ ## JaiminiGet.get(): the general method
51
+
52
+ `get(spec, varga_overrides=None)` does the following:
53
+
54
+ 1. Resolves varga numbers through overrides
55
+ 2. Gets the jaimini karaka list once
56
+ 3. For each factor in the spec:
57
+ - For each varga:
58
+ - Finds the target sign (karaka placement + offset with direction, or cusp sign)
59
+ - Collects conjunctions and rashi aspects to that sign
60
+ 4. Returns a nested dict keyed by factor, then by varga
61
+
62
+ ### Return structure
63
+
64
+ ```python
65
+ {
66
+ "aspect_type": "quadrant", # which rashi aspect system is in use
67
+ "12 away AK": { # one key per factor
68
+ "9": { # one key per varga
69
+ "conjunction": [ # flat list of planet info strings
70
+ "Mercury,Benefic,Mercury,OH"
71
+ ],
72
+ "aspecting": [ # list of lists, grouped by aspecting sign
73
+ ["Sun,Malefic,Venus,DB", "Mars,Malefic,Venus,F"],
74
+ ["Saturn,Malefic,Saturn,OH"]
75
+ ]
76
+ },
77
+ "-240": { ... },
78
+ "1": { ... }
79
+ }
80
+ }
81
+ ```
82
+
83
+ Planet info strings come from `Planet.jaimini_info()`: `name,nature,lord,dignity`.
84
+
85
+ Aspecting is a list of lists because multiple signs can aspect the target — each inner list contains the planets from one aspecting sign, so the astrologer can see which planets aspect together from the same place.
86
+
87
+ ## Why it's built this way
88
+
89
+ The alternative was one function per topic (like the original `get_spiritual_planets()`). That would mean ~10 nearly identical functions differing only in which karaka, which offset, and which vargas. The declarative approach means:
90
+
91
+ - Adding a new topic is one dict in `Gets`, no new code
92
+ - The counting logic (odd/even direction, karaka lookup, cusp resolution) is tested once
93
+ - The output format is consistent across all topics, which makes downstream formatting and TOML export uniform
94
+
95
+ `get_spiritual_planets()` is kept as a thin wrapper for backward compatibility.
@@ -0,0 +1,37 @@
1
+ # This file is part of libaditya.
2
+ #
3
+ # Copyright (c) 2025 Josh Harper <humanhaven@substack.com>
4
+ #
5
+ # libaditya is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # libaditya is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Affero General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with libaditya. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ from libaditya.objects import Planet
19
+
20
+ class Hellenistic:
21
+ """
22
+ inherits unto Rashi
23
+ provides calculations used for hellenistic astrology
24
+
25
+ TODO:
26
+ first thing, profections
27
+ Chart().natal().profection() should list the current year lord
28
+ Chart().natal().profection(n) should list the year lord at age n
29
+ also implement monthly, daily, hourly profections
30
+ """
31
+
32
+ def profection(self, year=None) -> Planet:
33
+ """
34
+ return year lord for current year if year is None
35
+ otherwise, for the age year
36
+ """
37
+ pass