sailorfe

2025 Aug 3 – pyswisseph

I've been using Astrolog daily for most of this year. Because I built it from source and neither understand C++ nor where Makefiles come from (I tried), this requires me being in the same directory of my astrolog.as so it knows where the ephemeris files are, which I symlink into every subdirectory of my astro database. This is fine for doing readings for clients or any other focused astrological work, but sometimes I am in the middle of another task and don't want to break my concentration or add a tmux window just to navigate to my database folder in order to pull up the chart of the moment with astrolog -n.

The main thing I need this for is electional astrology, so the first and often times only thing I look at is the ascendant, its ruler, and the Moon. What this sounded like to me is:

This meant it's time for me to finally tangle with the Swiss Ephemeris. The first piece of "astrology software" I made was a sidereal time calculator that simply did arithmetic out of Bruce Scofield's Astrological Chart Calculations and required an astronomical (ha) amount of user input when there's a whole sweph function just for this.

I don't know who or what Astro Origin is, but their website that purportedly has documentation for the Python Swiss Ephemeris has been down for a minute. Happily, the function names are the same as in the original C library whose docs aren't going anywhere thanks to AstroDienst.

ascendant clock

This was an adventure in I don't understand astrology or astronomy enough to know what all these values I'm printing are, and realizing many astrologers I respect probably wouldn't have a clue, either. I also discovered just how many nested tuples I can specify and slice through.

The output of houses(), central to my ascendant clock, for example, made sense to me at first because I use whole sign house. It printed a list of two tuples, the first of which had 12 integers 30 degrees apart for the 30-degree signs. I was able to surmise the second one, with eight clearly decimal degree floats, at minimum included the points of the ascendant and midheaven within these whole sign houses. I guessed at least some of the others could be the midheaven's ascensions? You tell me from reading this and looking at this tuple:

(261.50028001940655, 182.4609321606867, 182.25809588867747, 131.66417052837792, 272.0719460498313, 282.50586769166114, 230.73433572771262, 102.50586769166115)

Whatever the case, I know that tropical Sagittarius's absolute longitude spans 240-70 and checked this against Astrolog and was reasonably confident the first value had to be the ascendant, which is all I need for electional astrology. The function that translates decimal degrees to human (astrologer)-readable sign degrees is split_deg, which prints yet another tuple that made more sense to me thanks to me self-imposed crash course in analog math with Bruce Scofield: (21, 30, 1, 0.008069863564742263, 8). Here index 4 is one of 0-11 for the signs, so the rest of my little clock program is just a long, convoluted elif trail translating these to truncated sign strings.

In practice, I wrote a script that prints this to my swaybar! It looks like this, to the left of my vanilla date output.

swaybar ascendant clock

(might be a bit illegible since my bar background matches my website styling!)

chart of the moment

A bit more comfortable with throwing function names at the wall, I set out to replicate Astro.com's Chart of the Moment, which you can get to by clicking the ☉☽☿ at the top right of their header.

chart of the moment

I wrote this beautiful succession of loops that made me appreciate indexing the planets and signs as integers:

    positions = []
    for planet in range(12):
        position = swe.calc_ut(jd, planet)
        positions.append(position)

    planets = []
    for position in range(12):
        planet = positions[position][0][0]
        planets.append(planet)

    dms = []
    for planet in range(12):
        split = swe.split_deg(planets[planet], 8)
        dms.append(split)

Because my first foolish impulse was to write string variables for each of them really early on just so I knew what I meant, but this was just add more and more lines of specifying each instead of using range(). I did make a multiline comment telling me what each of the planets' indices were, though.

Something I'm still not sure about is argparse. I knew I wanted it to use timedate.now(timezone.utc) if passed no arguments, but wasn't sure if that meant I needed to have timedate as the default value if I defined it in the else statement at the start of the function. It seems like I do, and I don't understand why. I probably just need to read the docs slowly, especially about nargs. I'm also annoyed datetime has the date.today() type but not a corresponding time type, so I had to slice through timedate.UTC.

The other thing is I knew I wanted to send the output to myself with notify-send from wmenu, so I couldn't have the pretty sect-based ANSI coloring I'm so proud of for the Mercury condition, so that's the origin of the --plain flag.

You can see example plain text outputs and a screenshot on the README. Relatedly, I'm making it a goal to collect Linux distribution birth information and wish everyone was like the Debian community.