Suppose a feature changes from 359.060 degrees to 2.794 degrees.
A naive subtraction reports -356.266. The actual forward movement is +3.734 degrees. If those values are converted into 30-degree categories, the label changes as well—even though the underlying motion is smooth.
This boundary problem appears in headings, clock time, seasonal phase, hue, and any feature defined on a circle. I encountered it while testing how a ten-minute birth-time shift propagates through an astrological calculation pipeline.
The astrology context supplies a useful real example. The engineering problem is broader: how should a system preserve continuous circular data, categorical labels, and uncertainty at the same time?
The experiment
I held the synthetic date and public Tokyo coordinates constant and changed only local time.
date: 2000-01-01
timezone: Asia/Tokyo
latitude: 35.6762
longitude: 139.6503
times: 12:00, 12:10, 13:00, 14:00
sidereal mode: Lahiri
house system: Whole Sign
No personal birth data are involved.
The current GenesisCore calculation path produced these values:
| Local time | Sun | Moon | Ascendant |
|---|---|---|---|
| 12:00 | 256.130° | 194.948° | 359.060° |
| 12:10 | 256.137° | 195.032° | 2.794° |
| 13:00 | 256.172° | 195.451° | 20.340° |
| 14:00 | 256.215° | 195.954° | 38.752° |
The Sun moved about 0.085 degrees over two hours, the Moon about 1.006 degrees, and the Ascendant about 39.692 degrees around the circle.
Why ordinary subtraction fails
For values normalized to [0, 360), a forward angular difference can be calculated with modulo arithmetic:
def forward_delta(base: float, current: float) -> float:
return (current - base) % 360.0
print(forward_delta(359.060, 2.794))
# 3.734
If direction can be positive or negative, use the shortest signed distance:
def signed_circular_delta(base: float, current: float) -> float:
return ((current - base + 180.0) % 360.0) - 180.0
The right choice depends on the process. In this experiment, time only moves forward and the sampled Ascendant advances around the circle, so the forward delta is the relevant quantity.
Reproduce the source angles
The following function uses Swiss Ephemeris. It converts the local timestamp through an IANA timezone, calculates tropical Sun, Moon, and Ascendant values, and then applies Lahiri ayanāṃśa.
python -m pip install pyswisseph matplotlib
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
import swisseph as swe
LAT, LON = 35.6762, 139.6503
TZ = ZoneInfo("Asia/Tokyo")
swe.set_sid_mode(swe.SIDM_LAHIRI)
def sample(hour: int, minute: int) -> dict:
local_dt = datetime(2000, 1, 1, hour, minute, tzinfo=TZ)
utc_dt = local_dt.astimezone(timezone.utc)
utc_hour = utc_dt.hour + utc_dt.minute / 60 + utc_dt.second / 3600
jd = swe.julday(
utc_dt.year, utc_dt.month, utc_dt.day, utc_hour, swe.GREG_CAL
)
sun = swe.calc_ut(jd, swe.SUN, swe.FLG_SPEED)[0][0]
moon = swe.calc_ut(jd, swe.MOON, swe.FLG_SPEED)[0][0]
ayanamsa = swe.get_ayanamsa(jd)
_cusps, ascmc = swe.houses_ex(jd, LAT, LON, b"W", swe.FLG_SWIEPH)
return {
"minute": (hour - 12) * 60 + minute,
"sun": (sun - ayanamsa) % 360.0,
"moon": (moon - ayanamsa) % 360.0,
"ascendant": (ascmc[0] - ayanamsa) % 360.0,
}
rows = [sample(*time) for time in [(12, 0), (12, 10), (13, 0), (14, 0)]]
Plot change, not wrapped position
Plotting the raw Ascendant values would draw a large downward line between 359 and 2 degrees. That line is a coordinate artifact. Convert each series to forward change from the baseline first.
import matplotlib.pyplot as plt
def forward_delta(base: float, current: float) -> float:
return (current - base) % 360.0
minutes = [row["minute"] for row in rows]
for key, label in [
("sun", "Sun"),
("moon", "Moon"),
("ascendant", "Ascendant"),
]:
baseline = rows[0][key]
changes = [forward_delta(baseline, row[key]) for row in rows]
plt.plot(minutes, changes, marker="o", label=label)
plt.xlabel("Minutes after 12:00")
plt.ylabel("Forward angular change (degrees)")
plt.title("Input-time sensitivity of calculated chart features")
plt.grid(alpha=0.25)
plt.legend()
plt.tight_layout()
plt.show()
This representation preserves the process being measured. It also makes the different sensitivities immediately visible.
Feature engineering near a circular boundary
The example suggests four design rules.
Preserve the original angle
A category such as a zodiac sign or compass sector loses distance information. Store the normalized angle beside the label. If later analysis uses only categories, that should be an explicit modeling choice.
Add distance to the nearest boundary
For 30-degree segments:
def boundary_distance(angle: float, width: float = 30.0) -> float:
offset = angle % width
return min(offset, width - offset)
This makes boundary sensitivity measurable. A label derived from an angle 0.1 degrees from a boundary should not carry the same stability assumption as one 15 degrees from it.
Consider sine/cosine encoding
Many statistical and machine-learning models will treat 359 and 2 as far apart. Circular encoding keeps neighboring angles close:
import math
def encode_angle(angle: float) -> tuple[float, float]:
radians = math.radians(angle)
return math.sin(radians), math.cos(radians)
This does not solve every domain question, but it removes the artificial discontinuity at zero.
Propagate input uncertainty
When the timestamp is uncertain, sample the defensible time interval. A result can then be labeled:
- stable throughout the interval;
- continuous but boundary-sensitive;
- category-changing;
- unavailable because the input is insufficient.
A precise-looking category should not hide an imprecise timestamp.
The role of astrology in this example
An astronomical position can be reproducible while an astrological interpretation remains unverified. Keeping those layers separate is central to the design.
AETHERCORE develops GenesisCore with separate Western and Jyotish calculation paths. Shared birth records pass through different declared coordinate and rule systems. The public route-research project then asks whether frozen chart-derived features are associated with documented social classifications.
The circular-data lesson affects that research directly. A sign-membership feature, a degree-based feature, and a boundary-distance feature are three different hypotheses. Reproduction requires the code and configuration to say which one was tested.
Project and source links
- GenesisCore public route-research repository
- GenesisCore Western and Jyotish analysis
- Canonical Japanese article and full calculation record
- Swiss Ephemeris programming documentation
- IANA Time Zone Database
The next useful test is to apply the same stability labels to every time-sensitive output and show them directly in the result UI.

Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.