Fixed stars
A "fixed" star is not fixed. What moves it, above everything else, is the reference frame: the Aries point goes back fifty arcseconds a year, so Regulus, which sat at 29 degrees of Leo in 2000, has been in Virgo since the end of 2011. On top of that comes the star's own proper motion, which in Arcturus or Sirius is a couple of arcseconds a year and in almost everything else is nothing at all, and on top of that the same corrections a planet gets: annual aberration, up to twenty arcseconds, and nutation, up to seventeen.
The catalogue is 1,099 objects from Hipparcos-2 and SIMBAD, and a position is built in five steps: proper motion from J2000 to the date, a rotation from ICRS equatorial into the J2000 ecliptic, precession to the ecliptic and equinox of the date with the same rotation a planet gets, annual aberration, and nutation in longitude.
use Astronomy\Stars; echo count(Stars::catalog()), " objects\n";
1099 objects
Finding a star: Stars::find()
use Astronomy\Stars; $regulus = Stars::find('Regulus'); echo $regulus->key, ' ', $regulus->designation, "\n"; echo Stars::find('regulus') === $regulus ? "case-insensitive: yes\n" : "no\n"; echo Stars::find(' Régulus ') ? "accent-insensitive: yes\n" : "no\n"; echo Stars::find($regulus->key) === $regulus ? "by its own key: yes\n" : "no\n";
regulus α Leo case-insensitive: yes accent-insensitive: yes by its own key: yes
A star's name is not unique the way its designation is. Ten names in the catalogue belong to two
different stars each, and π³ Ori and π⁴ Ori, both called Tabit, are one pair of them:
$tabit = Stars::find('Tabit'); echo $tabit->key, ' ', $tabit->designation, ' magnitude ', $tabit->magnitude, "\n"; $other = Stars::find('pi-4ori'); echo $other->key, ' ', $other->designation, ' magnitude ', $other->magnitude, "\n";
pi-3ori π³ Ori magnitude 3.19 pi-4ori π⁴ Ori magnitude 3.68
Asking for the shared name resolves to the brighter of the two, which is measured rather than
chosen: it is what anyone writing "Tabit" with no further qualifier means. Reaching the fainter
one takes its own designation reduced to a key, which is also how find() answers to whatever
the published list calls a star that has no name of its own (594 stars in the catalogue have none).
A star with no published magnitude never wins a shared name over one that has: unknown is not
bright.
Where a star falls: Stars::position()
use Astronomy\Stars; use Astronomy\Time; [$jdTT] = Time::fromClock(new DateTimeImmutable('1981-05-11 07:15:00', new DateTimeZone('UTC'))); $position = Stars::position(Stars::find('Regulus'), $jdTT); echo $position->formatted(), "\n"; // longitude on the ecliptic of the date echo $position->sign()->name(), "\n"; echo $position->formattedDeclination(), "\n"; // declination, for the parallel printf("%.4f %.4f\n", $position->rightAscension, $position->latitude);
29° 34' Leo Leo +12° 04' 151.8417 0.4643
Stars::position() takes Terrestrial Time, like a planet, and returns a StarPosition carrying
both coordinate systems at once rather than just one: ecliptic longitude, for the conjunction,
which is how a star has been read since Ptolemy, and declination, for the parallel, which is the
modern reading. A Position will not do here, because it is built around a Body, and a star
has no distance that matters and no speed worth reading.
formatted() rounds the whole longitude to minutes before splitting it into sign and
degrees, and not the other way round: rounding the minutes first and splitting after lets
29° 59.6' Leo carry to 30° 00' Leo, a degree that does not exist, instead of 0° 00' Virgo.
It happened to Regulus in the very month of its own ingress into Virgo, which is exactly when
someone is looking.
Mean position against apparent, and why an ingress needs the mean one
use Astronomy\Stars; use Astronomy\Time; $regulus = Stars::find('Regulus'); foreach (['2011-03-01', '2011-08-01', '2012-01-01'] as $day) { [$jdTT] = Time::fromClock(new DateTimeImmutable($day, new DateTimeZone('UTC'))); $apparent = Stars::position($regulus, $jdTT); $mean = Stars::position($regulus, $jdTT, apparent: false); printf("%s apparent %.5f %-5s mean %.5f %s\n", $day, $apparent->longitude, $apparent->sign()->name(), $mean->longitude, $mean->sign()->name()); }
2011-03-01 apparent 149.99514 Leo mean 149.98433 Leo 2011-08-01 apparent 149.99005 Leo mean 149.99016 Leo 2012-01-01 apparent 150.00442 Virgo mean 149.99598 Leo
The apparent longitude is not monotonic in time, and the reason is a race between two effects
running at different speeds: aberration swings up to twenty arcseconds back and forth over a
year, and precession only ever advances fifty arcseconds in that same year. Aberration wins
locally, so a star's apparent position can run backwards for months at a time and cross the same
degree three times within a season, which is exactly what Regulus does between March and August
2011 above. The mean position only carries proper motion and precession, so it only ever moves
forward, which is why Crossings and anything that dates an ingress asks for apparent: false.
The eighty-eight constellations, from the sky and not the letter
use Astronomy\Constellations; echo count(Constellations::all()), " constellations\n";
88 constellations
All eighty-eight of the IAU's list are here, not a subset built around the zodiac and its neighbours: a catalogue that only carries what has been needed so far is a catalogue that fails silently the first time something new arrives, and this one is a published, closed list.
Which constellation a star belongs to is not read off its Bayer letter. It is computed from the star's own position, taken back to the epoch B1875 and tested against the boundaries Eugène Delporte drew in 1930, from the table Nancy Roman published for exactly this purpose (PASP 99, 695, 1987): three hundred and fifty-seven rows of right ascension, declination and constellation, in the very frame that makes the boundaries parallels and meridians instead of the crooked lines they become in any other epoch.
That distinction is not academic. Bayer lettered his stars in 1603 and Flamsteed numbered his in 1712, well over a century before Delporte's boundaries existed, so a handful of stars ended up on the wrong side of a line that was not there yet when they were named:
use Astronomy\Stars; foreach (['nembus', 'intercrus'] as $key) { $star = Stars::catalog()[$key]; echo $star->designation, ' sits in ', $star->constellation, ", not where its Bayer letter points\n"; }
υ Per sits in Andromeda, not where its Bayer letter points 41 Lyn sits in Ursa Major, not where its Bayer letter points
Measured over the whole catalogue, exactly two stars out of the 1,080 that carry a Bayer or Flamsteed designation disagree with it this way, and the other 1,078 agree. Both exceptions are written down explicitly rather than silently allowed, so that a third disagreement stops the catalogue from being rebuilt: a new one showing up means either a position that moved or a transform that broke, and neither of those announces itself on its own. A star sitting in the wrong constellation reads perfectly well.
Stars::passes() and Stars::occultations()
A star goes through the same horizon machinery as a planet, Limb::Center and nothing else,
because a point of light has no disc for a limb to mean anything about:
use Astronomy\Place; use Astronomy\Stars; $madrid = new Place('Madrid', null, 'Spain', 'ES', 40.4165, -3.7026, 'Europe/Madrid'); $day = new DateTimeImmutable('2026-09-19', $madrid->timeZone()); $regulus = Stars::find('Regulus'); $passes = Stars::passes($regulus, $madrid, $day); echo $passes->rise?->date->format('H:i:s T'), "\n"; echo $passes->upperCulmination?->date->format('H:i:s T'), "\n"; echo $passes->set?->date->format('H:i:s T'), "\n";
05:48:02 CEST 12:31:11 CEST 19:14:21 CEST
Stars::occultations() is the Moon passing in front of a star rather than in front of a planet,
and it is discarded outright, before any search runs, for a star more than seven degrees from the
ecliptic: the Moon never strays past 5.3 degrees of latitude, and parallax and semi-diameter add
at most another 1.3, so anything further away could never be occulted and is not worth the search
time.
use Astronomy\Stars; use Astronomy\Time; $aldebaran = Stars::find('Aldebaran'); $from = Time::julianDay(new DateTimeImmutable('2017-01-01', new DateTimeZone('UTC'))); $to = Time::julianDay(new DateTimeImmutable('2018-01-01', new DateTimeZone('UTC'))); $list = Stars::occultations($aldebaran, $from, $to); echo count($list), " occultations of Aldebaran in 2017\n"; echo $list[0]->maximum->date->format('Y-m-d H:i'), ' UT ', $list[0]->type->name(), "\n";
14 occultations of Aldebaran in 2017 2017-01-09 14:25 UT Total
Aldebaran, close enough to the ecliptic to be occulted often and bright enough to matter, is covered by the Moon fourteen separate times in 2017 alone. Local circumstances from one place work the same way they do for a planet:
use Astronomy\Occultations; foreach ($list as $o) { $local = Occultations::local($o, $madrid); if ($local !== null) { echo $o->maximum->date->format('Y-m-d'), ' visible from Madrid, disappears ', $local->contact2->date->format('H:i:s T'), "\n"; } }
2017-02-05 visible from Madrid, disappears 22:57:56 CET 2017-04-28 visible from Madrid, disappears 20:19:45 CEST 2017-06-22 visible from Madrid, disappears 17:29:37 CEST 2017-08-16 visible from Madrid, disappears 08:20:09 CEST
Ten of the fourteen never reach Madrid at all, and Occultations::local() answers null for
those instead of a set of times with nothing behind them. Eclipses and occultations of planets
are covered in full in Eclipses and occultations.
Precision
Against the catalogue's own published positions, star by star, the median difference is 0.04 arcseconds at the year 2000 and about 0.3 at 1900 and 2100, which is a floor of precession model and not of the stars themselves: the model this engine precesses with agrees with a commercial one's own to a few hundredths of an arcsecond near 2000 and drifts by roughly 0.3 arcseconds per century away from it in either direction (see The sidereal zodiac for where that drift is measured directly, and Precision for how it is measured everywhere else in the engine).
Four things a planet gets that a star deliberately does not:
- Annual parallax. The nearest star in the catalogue, Toliman (α Centauri), has 0.75 arcseconds of it; Sirius has 0.38, and everything else less than 0.15. Smaller than the engine's own floor against a live check, and only in two stars.
- Radial velocity. It changes proper motion over centuries because a star is moving closer
or further away, and in Sirius, the worst case in the catalogue, that comes to two hundredths
of an arcsecond per century. Both parallax and radial velocity are kept on
Starin case a use for them ever comes up, and neither is applied yet. - The frame correction a planet gets.
Ephemeriscorrects the eight planets and the Moon towards a modern numerical reference frame, and that correction belongs to the analytic planetary series it is written against; a star from SIMBAD is not in that series' frame to begin with, it is already in ICRS, and the gap between ICRS and the mean equator of J2000 is two hundredths of an arcsecond, left uncorrected either way. - Gravitational deflection by the Sun. Thousandths of an arcsecond except within a degree of the Sun, where a star is not visible anyway.