Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
webresh
GitHub Repository: webresh/drik-panchanga
Path: blob/master/panchanga.py
956 views
1
#! /usr/bin/env python
2
3
# panchanga.py -- routines for computing tithi, vara, etc.
4
#
5
# Copyright (C) 2013 Satish BD <[email protected]>
6
# Downloaded from https://github.com/bdsatish/drik-panchanga
7
#
8
# This file is part of the "drik-panchanga" Python library
9
# for computing Hindu luni-solar calendar based on the Swiss ephemeris
10
#
11
# This program is free software: you can redistribute it and/or modify
12
# it under the terms of the GNU Affero General Public License as published by
13
# the Free Software Foundation, either version 3 of the License, or
14
# (at your option) any later version.
15
#
16
# This program is distributed in the hope that it will be useful,
17
# but WITHOUT ANY WARRANTY; without even the implied warranty of
18
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
# GNU Affero General Public License for more details.
20
#
21
# You should have received a copy of the GNU Affero General Public License
22
# along with this program. If not, see <http://www.gnu.org/licenses/>.
23
24
"""
25
Use Swiss ephemeris to calculate tithi, nakshatra, etc.
26
"""
27
28
from __future__ import division
29
from math import floor, ceil
30
from collections import namedtuple as struct
31
import swisseph as swe
32
33
Date = struct('Date', ['year', 'month', 'day'])
34
Place = struct('Location', ['latitude', 'longitude', 'timezone'])
35
36
37
# Convert 23d 30' 30" to 23.508333 degrees
38
from_dms = lambda degs, mins, secs: degs + mins/60 + secs/3600
39
40
# the inverse
41
def to_dms(deg):
42
d = int(deg)
43
mins = (deg - d) * 60
44
m = int(mins)
45
s = int(round((mins - m) * 60))
46
return [d, m, s]
47
48
def unwrap_angles(angles):
49
"""Add 360 to those elements in the input list so that
50
all elements are sorted in ascending order."""
51
result = angles
52
for i in range(1, len(angles)):
53
if result[i] < result[i-1]: result[i] += 360
54
55
assert(result == sorted(result))
56
return result
57
58
def inverse_lagrange(x, y, ya):
59
"""Given two lists x and y, find the value of x = xa when y = ya, i.e., f(xa) = ya"""
60
assert(len(x) == len(y))
61
total = 0
62
for i in range(len(x)):
63
numer = 1
64
denom = 1
65
for j in range(len(x)):
66
if j != i:
67
numer *= (ya - y[j])
68
denom *= (y[i] - y[j])
69
70
total += numer * x[i] / denom
71
72
return total
73
74
# Julian Day number as on (year, month, day) at 00:00 UTC
75
gregorian_to_jd = lambda date: swe.julday(date.year, date.month, date.day, 0.0)
76
jd_to_gregorian = lambda jd: swe.revjul(jd, swe.GREG_CAL) # returns (y, m, d, h, min, s)
77
78
def solar_longitude(jd):
79
"""Solar longitude at given instant (julian day) jd"""
80
data = swe.calc_ut(jd, swe.SUN, flag = swe.FLG_SWIEPH)
81
return data[0] # in degrees
82
83
def lunar_longitude(jd):
84
"""Lunar longitude at given instant (julian day) jd"""
85
data = swe.calc_ut(jd, swe.MOON, flag = swe.FLG_SWIEPH)
86
return data[0] # in degrees
87
88
def lunar_latitude(jd):
89
"""Lunar latitude at given instant (julian day) jd"""
90
data = swe.calc_ut(jd, swe.MOON, flag = swe.FLG_SWIEPH)
91
return data[1] # in degrees
92
93
def sunrise(jd, place):
94
"""Sunrise when centre of disc is at horizon for given date and place"""
95
lat, lon, tz = place
96
result = swe.rise_trans(jd - tz/24, swe.SUN, lon, lat, rsmi=swe.BIT_DISC_CENTER + swe.CALC_RISE)
97
rise = result[1][0] # julian-day number
98
# Convert to local time
99
return [rise + tz/24., to_dms((rise - jd) * 24 + tz)]
100
101
def sunset(jd, place):
102
"""Sunset when centre of disc is at horizon for given date and place"""
103
lat, lon, tz = place
104
result = swe.rise_trans(jd - tz/24, swe.SUN, lon, lat, rsmi=swe.BIT_DISC_CENTER + swe.CALC_SET)
105
setting = result[1][0] # julian-day number
106
# Convert to local time
107
return [setting + tz/24., to_dms((setting - jd) * 24 + tz)]
108
109
def moonrise(jd, place):
110
"""Moonrise when centre of disc is at horizon for given date and place"""
111
lat, lon, tz = place
112
result = swe.rise_trans(jd - tz/24, swe.MOON, lon, lat, rsmi=swe.BIT_DISC_CENTER + swe.CALC_RISE)
113
rise = result[1][0] # julian-day number
114
# Convert to local time
115
return to_dms((rise - jd) * 24 + tz)
116
117
def moonset(jd, place):
118
"""Moonset when centre of disc is at horizon for given date and place"""
119
lat, lon, tz = place
120
result = swe.rise_trans(jd - tz/24, swe.MOON, lon, lat, rsmi=swe.BIT_DISC_CENTER + swe.CALC_SET)
121
setting = result[1][0] # julian-day number
122
# Convert to local time
123
return to_dms((setting - jd) * 24 + tz)
124
125
# Tithi doesn't depend on Ayanamsa
126
def tithi(jd, place):
127
"""Tithi at sunrise for given date and place. Also returns tithi's end time."""
128
tz = place.timezone
129
# 1. Find time of sunrise
130
rise = sunrise(jd, place)[0] - tz / 24
131
132
# 2. Find tithi at this JDN
133
moon_phase = lunar_phase(rise)
134
today = ceil(moon_phase / 12)
135
degrees_left = today * 12 - moon_phase
136
137
# 3. Compute longitudinal differences at intervals of 0.25 days from sunrise
138
offsets = [0.25, 0.5, 0.75, 1.0]
139
lunar_long_diff = [ (lunar_longitude(rise + t) - lunar_longitude(rise)) % 360 for t in offsets ]
140
solar_long_diff = [ (solar_longitude(rise + t) - solar_longitude(rise)) % 360 for t in offsets ]
141
relative_motion = [ moon - sun for (moon, sun) in zip(lunar_long_diff, solar_long_diff) ]
142
143
# 4. Find end time by 4-point inverse Lagrange interpolation
144
y = relative_motion
145
x = offsets
146
# compute fraction of day (after sunrise) needed to traverse 'degrees_left'
147
approx_end = inverse_lagrange(x, y, degrees_left)
148
ends = (rise + approx_end -jd) * 24 + tz
149
answer = [int(today), to_dms(ends)]
150
151
# 5. Check for skipped tithi
152
moon_phase_tmrw = lunar_phase(rise + 1)
153
tomorrow = ceil(moon_phase_tmrw / 12)
154
isSkipped = (tomorrow - today) % 30 > 1
155
if isSkipped:
156
# interpolate again with same (x,y)
157
leap_tithi = today + 1
158
degrees_left = leap_tithi * 12 - moon_phase
159
approx_end = inverse_lagrange(x, y, degrees_left)
160
ends = (rise + approx_end -jd) * 24 + place.timezone
161
answer += [int(leap_tithi), to_dms(ends)]
162
163
return answer
164
165
166
def nakshatra(jd, place):
167
"""Current nakshatra as of julian day (jd)
168
1 = Asvini, 2 = Bharani, ..., 27 = Revati
169
"""
170
swe.set_sid_mode(swe.SIDM_LAHIRI)
171
# 1. Find time of sunrise
172
lat, lon, tz = place
173
rise = sunrise(jd, place)[0] - tz / 24. # Sunrise at UT 00:00
174
175
# Swiss Ephemeris always gives Sayana. So subtract ayanamsa to get Nirayana
176
offsets = [0.0, 0.25, 0.5, 0.75, 1.0]
177
longitudes = [ (lunar_longitude(rise + t) - swe.get_ayanamsa_ut(rise)) % 360 for t in offsets]
178
179
# 2. Today's nakshatra is when offset = 0
180
# There are 27 Nakshatras spanning 360 degrees
181
nak = ceil(longitudes[0] * 27 / 360)
182
183
# 3. Find end time by 5-point inverse Lagrange interpolation
184
y = unwrap_angles(longitudes)
185
x = offsets
186
approx_end = inverse_lagrange(x, y, nak * 360 / 27)
187
ends = (rise - jd + approx_end) * 24 + tz
188
answer = [int(nak), to_dms(ends)]
189
190
# 4. Check for skipped nakshatra
191
nak_tmrw = ceil(longitudes[-1] * 27 / 360)
192
isSkipped = (nak_tmrw - nak) % 27 > 1
193
if isSkipped:
194
leap_nak = nak + 1
195
approx_end = inverse_lagrange(offsets, longitudes, leap_nak * 360 / 27)
196
ends = (rise - jd + approx_end) * 24 + tz
197
answer += [int(leap_nak), to_dms(ends)]
198
199
return answer
200
201
202
def yoga(jd, place):
203
"""Yoga at given jd and place.
204
1 = Vishkambha, 2 = Priti, ..., 27 = Vaidhrti
205
"""
206
swe.set_sid_mode(swe.SIDM_LAHIRI)
207
# 1. Find time of sunrise
208
lat, lon, tz = place
209
rise = sunrise(jd, place)[0] - tz / 24. # Sunrise at UT 00:00
210
211
# 2. Find the Nirayana longitudes and add them
212
lunar_long = (lunar_longitude(rise) - swe.get_ayanamsa_ut(rise)) % 360
213
solar_long = (solar_longitude(rise) - swe.get_ayanamsa_ut(rise)) % 360
214
total = (lunar_long + solar_long) % 360
215
# There are 27 Yogas spanning 360 degrees
216
yog = ceil(total * 27 / 360)
217
218
# 3. Find how many longitudes is there left to be swept
219
degrees_left = yog * (360 / 27) - total
220
221
# 3. Compute longitudinal sums at intervals of 0.25 days from sunrise
222
offsets = [0.25, 0.5, 0.75, 1.0]
223
lunar_long_diff = [ (lunar_longitude(rise + t) - lunar_longitude(rise)) % 360 for t in offsets ]
224
solar_long_diff = [ (solar_longitude(rise + t) - solar_longitude(rise)) % 360 for t in offsets ]
225
total_motion = [ moon + sun for (moon, sun) in zip(lunar_long_diff, solar_long_diff) ]
226
227
# 4. Find end time by 4-point inverse Lagrange interpolation
228
y = total_motion
229
x = offsets
230
# compute fraction of day (after sunrise) needed to traverse 'degrees_left'
231
approx_end = inverse_lagrange(x, y, degrees_left)
232
ends = (rise + approx_end - jd) * 24 + tz
233
answer = [int(yog), to_dms(ends)]
234
235
# 5. Check for skipped yoga
236
lunar_long_tmrw = (lunar_longitude(rise + 1) - swe.get_ayanamsa_ut(rise + 1)) % 360
237
solar_long_tmrw = (solar_longitude(rise + 1) - swe.get_ayanamsa_ut(rise + 1)) % 360
238
total_tmrw = (lunar_long_tmrw + solar_long_tmrw) % 360
239
tomorrow = ceil(total_tmrw * 27 / 360)
240
isSkipped = (tomorrow - yog) % 27 > 1
241
if isSkipped:
242
# interpolate again with same (x,y)
243
leap_yog = yog + 1
244
degrees_left = leap_yog * (360 / 27) - total
245
approx_end = inverse_lagrange(x, y, degrees_left)
246
ends = (rise + approx_end - jd) * 24 + tz
247
answer += [int(leap_yog), to_dms(ends)]
248
249
return answer
250
251
252
def karana(jd, place):
253
"""Returns the karana and their ending times. (from 1 to 60)"""
254
# 1. Find time of sunrise
255
rise = sunrise(jd, place)[0]
256
257
# 2. Find karana at this JDN
258
solar_long = solar_longitude(rise)
259
lunar_long = lunar_longitude(rise)
260
moon_phase = (lunar_long - solar_long) % 360
261
today = ceil(moon_phase / 6)
262
degrees_left = today * 6 - moon_phase
263
264
return [int(today)]
265
266
def vaara(jd):
267
"""Weekday for given Julian day. 0 = Sunday, 1 = Monday,..., 6 = Saturday"""
268
return int(ceil(jd + 1) % 7)
269
270
def masa(jd, place):
271
"""Returns lunar month and if it is adhika or not.
272
1 = Chaitra, 2 = Vaisakha, ..., 12 = Phalguna"""
273
ti = tithi(jd, place)[0]
274
critical = sunrise(jd, place)[0] # - tz/24 ?
275
last_new_moon = new_moon(critical, ti, -1)
276
next_new_moon = new_moon(critical, ti, +1)
277
this_solar_month = raasi(last_new_moon)
278
next_solar_month = raasi(next_new_moon)
279
is_leap_month = (this_solar_month == next_solar_month)
280
maasa = this_solar_month + 1
281
if maasa > 12: maasa = (maasa % 12)
282
return [int(maasa), is_leap_month]
283
284
# epoch-midnight to given midnight
285
# Days elapsed since beginning of Kali Yuga
286
ahargana = lambda jd: jd - 588465.5
287
288
def elapsed_year(jd, maasa_num):
289
sidereal_year = 365.25636
290
ahar = ahargana(jd) # or (jd + sunrise(jd, place)[0])
291
kali = int((ahar + (4 - maasa_num) * 30) / sidereal_year)
292
saka = kali - 3179
293
vikrama = saka + 135
294
return kali, saka
295
296
# New moon day: sun and moon have same longitude (0 degrees = 360 degrees difference)
297
# Full moon day: sun and moon are 180 deg apart
298
def new_moon(jd, tithi_, opt = -1):
299
"""Returns JDN, where
300
opt = -1: JDN < jd such that lunar_phase(JDN) = 360 degrees
301
opt = +1: JDN >= jd such that lunar_phase(JDN) = 360 degrees
302
"""
303
if opt == -1: start = jd - tithi_ # previous new moon
304
if opt == +1: start = jd + (30 - tithi_) # next new moon
305
# Search within a span of (start +- 2) days
306
x = [ -2 + offset/4 for offset in range(17) ]
307
y = [lunar_phase(start + i) for i in x]
308
y = unwrap_angles(y)
309
y0 = inverse_lagrange(x, y, 360)
310
return start + y0
311
312
def raasi(jd):
313
"""Zodiac of given jd. 1 = Mesha, ... 12 = Meena"""
314
swe.set_sid_mode(swe.SIDM_LAHIRI)
315
s = solar_longitude(jd)
316
solar_nirayana = (solar_longitude(jd) - swe.get_ayanamsa_ut(jd)) % 360
317
# 12 rasis occupy 360 degrees, so each one is 30 degrees
318
return ceil(solar_nirayana / 30.)
319
320
def lunar_phase(jd):
321
solar_long = solar_longitude(jd)
322
lunar_long = lunar_longitude(jd)
323
moon_phase = (lunar_long - solar_long) % 360
324
return moon_phase
325
326
def samvatsara(jd, maasa_num):
327
kali = elapsed_year(jd, maasa_num)[0]
328
# Change 14 to 0 for North Indian tradition
329
# See the function "get_Jovian_Year_name_south" in pancanga.pl
330
if kali >= 4009: kali = (kali - 14) % 60
331
samvat = (kali + 27 + int((kali * 211 - 108) / 18000)) % 60
332
return samvat
333
334
def ritu(masa_num):
335
"""0 = Vasanta,...,5 = Shishira"""
336
return (masa_num - 1) // 2
337
338
def day_duration(jd, place):
339
srise = sunrise(jd, place)[0] # julian day num
340
sset = sunset(jd, place)[0] # julian day num
341
diff = (sset - srise) * 24 # In hours
342
return [diff, to_dms(diff)]
343
344
# ----- TESTS ------
345
def all_tests():
346
print(moonrise(date2, bangalore)) # Expected: 11:28:06
347
print(moonset(date2, bangalore)) # Expected: 24:12:48
348
print(sunrise(date2, bangalore)[1]) # Expected: 6:47:20
349
print(sunset(date2, bangalore)[1]) # Expected: 18:12:58
350
assert(vaara(date2) == 5)
351
print(sunrise(date4, shillong)[1]) # On this day, Nakshatra and Yoga are skipped!
352
assert(karana(date2, helsinki) == [14]) # Expected: 14, Vanija
353
return
354
355
def tithi_tests():
356
feb3 = gregorian_to_jd(Date(2013, 2, 3))
357
apr24 = gregorian_to_jd(Date(2010, 4, 24))
358
apr19 = gregorian_to_jd(Date(2013, 4, 19))
359
apr20 = gregorian_to_jd(Date(2013, 4, 20))
360
apr21 = gregorian_to_jd(Date(2013, 4, 21))
361
print(tithi(date1, bangalore)) # Expected: krishna ashtami (23), ends at 27:07:09
362
print(tithi(date2, bangalore)) # Expected: Saptami, ends at 16:24:04
363
print(tithi(date3, bangalore)) # Expected: Krishna Saptami, ends at 25:03:22
364
print(tithi(date2, helsinki)) # Expected: Shukla saptami until 12:54:04
365
print(tithi(apr24, bangalore)) # Expected: [10, [6,9,18], 11, [27, 33, 50]]
366
print(tithi(feb3, bangalore)) # Expected: [22, [8,13,52], 23, [30, 33, 6]]
367
print(tithi(apr19, helsinki)) # Expected: [9, [28, 44, 60]]
368
print(tithi(apr20, helsinki)) # Expected: [10, - ahoratra -]
369
print(tithi(apr21, helsinki)) # Expected: [10, [5, 22, 6]]
370
return
371
372
def nakshatra_tests():
373
print(nakshatra(date1, bangalore)) # Expected: 27 (Revati), ends at 17:06:24
374
print(nakshatra(date2, bangalore)) # Expected: 27 (Revati), ends at 19:22:54
375
print(nakshatra(date3, bangalore)) # Expecred: 24 (Shatabhisha) ends at 26:32:36
376
print(nakshatra(date4, shillong)) # Expected: [3, [5,0,59]] then [4,[26,31,00]]
377
return
378
379
def yoga_tests():
380
may22 = gregorian_to_jd(Date(2013, 5, 22))
381
print(yoga(date3, bangalore)) # Expected: Vishkambha (1), ends at 22:59:38
382
print(yoga(date2, bangalore)) # Expected: Siddha (21), ends at 29:10:40
383
print(yoga(may22, helsinki)) # [16, [6,20,25], 17, [27,21,53]]
384
385
def masa_tests():
386
jd = gregorian_to_jd(Date(2013, 2, 10))
387
aug17 = gregorian_to_jd(Date(2012, 8, 17))
388
aug18 = gregorian_to_jd(Date(2012, 8, 18))
389
sep19 = gregorian_to_jd(Date(2012, 9, 18))
390
may20 = gregorian_to_jd(Date(2012, 5, 20))
391
may21 = gregorian_to_jd(Date(2012, 5, 21))
392
print(masa(jd, bangalore)) # Pusya (10)
393
print(masa(aug17, bangalore)) # Shravana (5) amavasya
394
print(masa(aug18, bangalore)) # Adhika Bhadrapada [6, True]
395
print(masa(sep19, bangalore)) # Normal Bhadrapada [6, False]
396
print(masa(may20, helsinki)) # Vaisakha [2]
397
print(masa(may21, helsinki)) # Jyestha [3]
398
399
if __name__ == "__main__":
400
bangalore = Place(12.972, 77.594, +5.5)
401
shillong = Place(25.569, 91.883, +5.5)
402
helsinki = Place(60.17, 24.935, +2.0)
403
date1 = gregorian_to_jd(Date(2009, 7, 15))
404
date2 = gregorian_to_jd(Date(2013, 1, 18))
405
date3 = gregorian_to_jd(Date(1985, 6, 9))
406
date4 = gregorian_to_jd(Date(2009, 6, 21))
407
apr_8 = gregorian_to_jd(Date(2010, 4, 8))
408
apr_10 = gregorian_to_jd(Date(2010, 4, 10))
409
# all_tests()
410
# tithi_tests()
411
# nakshatra_tests()
412
# yoga_tests()
413
masa_tests()
414
# new_moon(jd)
415
416