The star field¶
The named stars and a seeded synthetic field, by right ascension, declination and magnitude.
- Requires
- the world's
Seed(an integer), the field'sCountof synthetic stars and theFaintestmagnitude it reaches. - Provides
- each star's
RightAscensionandDeclination(rad, J2000), itsMagnitude(V) and its name ("" for a synthetic star), the named stars first; and each synthetic star's galactic latitude (degrees). - Law
- the synthetic stars crowd the galactic plane, sin b = s u^3 (half within about 7 degrees of it), their longitude weighted towards the galactic centre, and their magnitudes follow the rising count of fainter stars, N(<m) as 10^(0.5 m); galactic to equatorial by the IAU pole. Each star is drawn from lusine.error's stream by the seed and its index, so a seed is one sky on every machine.
- Source
- the Yale Bright Star Catalogue, 5th ed. (the named stars); Blaauw et al., MNRAS 121 (1960) (the galactic pole); Allen's Astrophysical Quantities, 4th ed., ch. 17 (star counts).
- Proof
- invariant, reference, limit.
No Trait form: a tool, not a step law: a catalogue drawn once from the world's seed, the named stars and a synthetic field; it runs where a game prepares its data, never in a step, so it has no Trait form.
sky.stars(seed, count, faintest)Uses, without proving it here: sky.horizontal, sky.sidereal.
The named stars of the Yale catalogue and a synthetic field drawn from the world's seed, crowding the galactic plane.
import os, sys
root = os.getcwd()
while not os.path.isfile(os.path.join(root, 'docsite.json')):
root = os.path.dirname(root)
sys.path.insert(0, os.path.join(root, 'tools'))
import numpy as np
import matplotlib.pyplot as plt
from lusine import library
from lusine.kernels.sky._astro import deg, julian_day, to_deg
from lusine.kernels.sky.horizontal import horizontal
from lusine.kernels.sky.sidereal import sidereal
from lusine.kernels.sky.stars import CONSTELLATIONS, NAMED, stars
The reference¶
The kernel's reference, in numpy: the body under the declaration above, and what every section below runs. It stays as the kernel's test beside its Trait form.
library.reference(stars)
the reference, stars.py
def stars(seed, count=3000, faintest=6.5):
"""THE STAR FIELD: the named stars and a seeded synthetic field, by right
ascension, declination and magnitude.
Requires: the world's `Seed` (an integer), the field's `Count` of
synthetic stars and the `Faintest` magnitude it reaches.
Provides: each star's `RightAscension` and `Declination` (rad, J2000),
its `Magnitude` (V) and its name ("" for a synthetic star), the named
stars first; and each synthetic star's galactic latitude (degrees).
Law: the synthetic stars crowd the galactic plane, sin b = s u^3 (half
within about 7 degrees of it), their longitude weighted towards the
galactic centre, and their magnitudes follow the rising count of fainter
stars, N(<m) as 10^(0.5 m); galactic to equatorial by the IAU pole. Each
star is drawn from lusine.error's stream by the seed and its index, so a
seed is one sky on every machine.
Source: the Yale Bright Star Catalogue, 5th ed. (the named stars);
Blaauw et al., MNRAS 121 (1960) (the galactic pole); Allen's
Astrophysical Quantities, 4th ed., ch. 17 (star counts).
Proof: invariant, reference, limit."""
i = np.arange(count)
u1 = np.array([E.uniform(seed, int(k), "sky-b", 0) for k in i])
u2 = np.array([E.uniform(seed, int(k), "sky-l", 0) for k in i])
u3 = np.array([E.uniform(seed, int(k), "sky-m", 0) for k in i])
sign = np.where(u1 < 0.5, -1.0, 1.0)
sb = sign * ((np.abs(u1 - 0.5) * 2.0) ** 3)
b = to_deg(D.dasin(sb))
l = _wrap360(180.0 * (2.0 * u2 - 1.0) * np.abs(2.0 * u2 - 1.0)) # denser towards l = 0
lo = 3.0
# N(<m) as 10^(0.5 m): m = lo + 2 log10(1 + u (10^(0.5 (faintest - lo)) - 1)), log10 through dlog
mag = lo + 2.0 * D.dlog(1.0 + u3 * (10.0 ** (0.5 * (faintest - lo)) - 1.0)) / LN10
ra, dec = galactic_to_equatorial(l, b)
names = list(NAMED)
nra = deg([NAMED[n][0] for n in names])
ndec = deg([NAMED[n][1] for n in names])
nmag = np.array([NAMED[n][2] for n in names])
return {"ra": np.concatenate([nra, ra]), "dec": np.concatenate([ndec, dec]), "mag": np.concatenate([nmag, mag]),
"names": names + [""] * count, "galactic_b": np.concatenate([np.full(len(names), np.nan), b])}
Invariant: a seed is one sky¶
The same seed gives the same stars, star for star, and another seed another; the synthetic field crowds the galactic plane, most of it within 20 degrees.
a, b, c = stars(7, 2000), stars(7, 2000), stars(8, 2000)
assert np.array_equal(a['ra'], b['ra']) and np.array_equal(a['mag'], b['mag'])
assert not np.array_equal(a['ra'], c['ra'])
lat = a['galactic_b'][len(NAMED):]
near = float(np.mean(np.abs(lat) < 20.0))
assert near > 0.6, near
print(f'{100 * near:.0f}% of the field within 20 degrees of the galactic plane')
fig, ax = plt.subplots(figsize=(7, 3))
ax.hist(lat, bins=36, range=(-90, 90), color='k')
ax.set_xlabel('galactic latitude (degrees)')
ax.set_ylabel('stars')
plt.show()
69% of the field within 20 degrees of the galactic plane
Reference: the named stars where the catalogue puts them¶
Orion's belt on the celestial equator, Polaris within a degree of the pole, Sirius the brightest star in the sky.
f = stars(0, 0)
names = f['names']
belt = [names.index(n) for n in CONSTELLATIONS["Orion's belt"]]
assert np.all(np.abs(to_deg(f['dec'][belt])) < 2.0)
assert 89.0 < float(to_deg(f['dec'][names.index('Polaris')])) < 90.0
assert names[int(np.argmin(f['mag']))] == 'Sirius'
print('belt declinations', np.round(to_deg(f['dec'][belt]), 2))
belt declinations [-1.94 -1.2 -0.3 ]
Limit: at the pole the stars neither rise nor set¶
At the north pole a star's altitude is its declination, at every hour.
f = stars(0, 300)
for hour in (0.0, 6.0, 13.0):
jd = julian_day(2024, 1, 1, hour)
alt, az = horizontal(f['ra'], f['dec'], deg(90.0), sidereal(jd, 0.0))
assert np.allclose(alt, f['dec'], atol=1e-9)
print('fixed altitudes at the pole')
fig, ax = plt.subplots(figsize=(7, 3.5))
ax.scatter(to_deg(az), to_deg(alt), s=np.clip(6 - f['mag'], 0.3, 6), c='k')
ax.set_xlabel('azimuth (degrees)')
ax.set_ylabel('altitude (degrees)')
plt.show()
fixed altitudes at the pole