Read a dwarf with a type and no colour at its type's row of the dwarf sequence

A non-giant with a spectral type but no colour took its temperature off the textbook colour
spectralTypeToColorIndex gives its type, read on Pecaut & Mamajek's table, and its bolometric
correction off the old textbook anchors. The table puts those colours elsewhere: M8's B-V 1.88
is M5-M5.5 there, 3 001 K where M8 V is 2 570, and the anchors give M8 -3.92 where the table has
-5.65. GJ 3655 (M8, 14.35 pc) was drawn at 0.035 solar radii, a third of Jupiter, where its
type's row gives 0.106 (Mamajek's M8 V: 0.114). Every O type came out B0's 31 400 K.

Both now read dwarfSequenceAtType, which already existed for the giants, and a G magnitude is
carried to V at the type's G-V too. On the published catalogue, of the 835 non-giant stars with
a band, a type and no colour, those more than 200 K off their type's row go from 21 to 0, those
with a radius more than 1.5 times off it from 7 to 1 and a luminosity from 10 to 1 (the one left,
Oph 11, is a G-band star). GJ 3849 (dM9) goes from 0.030 to 0.089 solar radii, GJ 3855 (M6.5)
from 0.040 to 0.087.

Two tests pinned the textbook path and now read the table: M5Ve with no colour is 3 060 K, not
3 106, and K5 tints as B-V 1.15, its row, not 1.105. An O8 star is 35 100 K, its row, not B0's.
Controls: reading the temperature through the textbook colour again fails "is drawn at its
type's row of the dwarf sequence" (with the three updated tests); reading the correction off the
anchors again fails that test alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-30 15:41:01 +02:00
co-authored by Claude Opus 5.5
parent 89c2568018
commit 206e88ae85
3 changed files with 35 additions and 23 deletions
@@ -64,9 +64,9 @@ describe('colorIndexToRgb', () => {
}); });
it('matches the colour the same spectral type would give explicitly', () => { it('matches the colour the same spectral type would give explicitly', () => {
// K5 sits halfway between the K anchor (0.81) and the M anchor (1.40). // K5's row of the dwarf sequence is at B−V 1.15.
const derived = colorIndexToRgb(null, 'K5'); const derived = colorIndexToRgb(null, 'K5');
const explicit = colorIndexToRgb(1.105); const explicit = colorIndexToRgb(1.15);
expect(derived.r).toBeCloseTo(explicit.r, 6); expect(derived.r).toBeCloseTo(explicit.r, 6);
expect(derived.g).toBeCloseTo(explicit.g, 6); expect(derived.g).toBeCloseTo(explicit.g, 6);
+18 -5
View File
@@ -174,20 +174,20 @@ describe('effectiveTemperatureK', () => {
}); });
it('reads a colour in its own system, and a spectral type where there is no colour', () => { it('reads a colour in its own system, and a spectral type where there is no colour', () => {
// An M5 dwarf is 3 060 K at B−V 1.83 or BP−RP 3.35. Its type alone goes through the colour // An M5 dwarf is 3 060 K at B−V 1.83 or BP−RP 3.35, and at its type alone.
// `spectralTypeToColorIndex` gives it, B−V 1.70, and comes out a little warmer.
expect(effectiveTemperatureK({ magnitude: 11, distancePc: 5, colorIndex: 3.35, colorSystem: 'BP-RP' })).toBeCloseTo(3060, 0); expect(effectiveTemperatureK({ magnitude: 11, distancePc: 5, colorIndex: 3.35, colorSystem: 'BP-RP' })).toBeCloseTo(3060, 0);
expect(effectiveTemperatureK({ magnitude: 11, distancePc: 5, colorIndex: 1.83, colorSystem: 'B-V' })).toBeCloseTo(3060, 0); expect(effectiveTemperatureK({ magnitude: 11, distancePc: 5, colorIndex: 1.83, colorSystem: 'B-V' })).toBeCloseTo(3060, 0);
expect(effectiveTemperatureK({ magnitude: 11, distancePc: 5, spectralType: 'M5Ve', colorIndex: null })).toBeCloseTo(3106, 0); expect(effectiveTemperatureK({ magnitude: 11, distancePc: 5, spectralType: 'M5Ve', colorIndex: null })).toBeCloseTo(3060, 0);
expect(effectiveTemperatureK({ magnitude: 11, distancePc: 5, spectralType: 'Unknown', colorIndex: null })).toBeNull(); expect(effectiveTemperatureK({ magnitude: 11, distancePc: 5, spectralType: 'Unknown', colorIndex: null })).toBeNull();
}); });
it('reads a colour past the table at its end where there is no type, and the type where there is', () => { it('reads a colour past the table at its end where there is no type, and the type where there is', () => {
// An ultracool dwarf redder than M8.5, a white dwarf bluer than B9 at the 19 012 K Gentile // An ultracool dwarf redder than M8.5, a white dwarf bluer than B9 at the 19 012 K Gentile
// Fusillo et al. (2021) measure at its colour, not B9's 10 700, and an O star B−V puts at B0. // Fusillo et al. (2021) measure at its colour, not B9's 10 700, and an O star at its type's
// 35 100 K, where B−V puts every O star at B0's 31 400.
expect(effectiveTemperatureK({ magnitude: 14.005, distancePc: 4.005, spectralType: 'Unknown', magnitudeBand: 'G', colorIndex: 5.113, colorSystem: 'BP-RP' })).toBe(2420); expect(effectiveTemperatureK({ magnitude: 14.005, distancePc: 4.005, spectralType: 'Unknown', magnitudeBand: 'G', colorIndex: 5.113, colorSystem: 'BP-RP' })).toBe(2420);
expect(effectiveTemperatureK({ magnitude: 14, distancePc: 25, spectralType: 'Unknown', magnitudeBand: 'G', colorIndex: -0.25, colorSystem: 'BP-RP' })).toBeCloseTo(19012, 6); expect(effectiveTemperatureK({ magnitude: 14, distancePc: 25, spectralType: 'Unknown', magnitudeBand: 'G', colorIndex: -0.25, colorSystem: 'BP-RP' })).toBeCloseTo(19012, 6);
expect(effectiveTemperatureK({ magnitude: 7, distancePc: 121, spectralType: 'O8', colorIndex: -0.31, colorSystem: 'B-V' })).toBe(31400); expect(effectiveTemperatureK({ magnitude: 7, distancePc: 121, spectralType: 'O8', colorIndex: -0.31, colorSystem: 'B-V' })).toBe(35100);
// HD 49748, G5 V at B−V −0.32: the colour is the one that is wrong. // HD 49748, G5 V at B−V −0.32: the colour is the one that is wrong.
const g5 = effectiveTemperatureK({ magnitude: 9, distancePc: 184, spectralType: 'G5V', colorIndex: null })!; const g5 = effectiveTemperatureK({ magnitude: 9, distancePc: 184, spectralType: 'G5V', colorIndex: null })!;
expect(effectiveTemperatureK({ magnitude: 9, distancePc: 184, spectralType: 'G5V', colorIndex: -0.319, colorSystem: 'B-V' })).toBe(g5); expect(effectiveTemperatureK({ magnitude: 9, distancePc: 184, spectralType: 'G5V', colorIndex: -0.319, colorSystem: 'B-V' })).toBe(g5);
@@ -195,6 +195,19 @@ describe('effectiveTemperatureK', () => {
}); });
}); });
describe('a dwarf with a type and no colour', () => {
it("is drawn at its type's row of the dwarf sequence, not at the textbook colour of its type", () => {
// GJ 3655, M8 at V 19.57 and 14.35 pc: M8 V is 2 570 K and 0.114 R☉ (Mamajek's table, 2022.04.16).
// Through the textbook colour, B−V 1.88, the table's M5, and the textbook correction, −3.92
// where M8's is −5.65, it was 3 001 K and 0.035 R☉, a third of Jupiter.
const gj3655 = { magnitude: 19.57, distancePc: 14.35, spectralType: 'M8', magnitudeBand: 'V', colorIndex: null } as const;
expect(effectiveTemperatureK(gj3655)).toBeCloseTo(2570, 6);
const radius = radiusFromLuminositySolar(luminositySolar(gj3655)!, effectiveTemperatureK(gj3655)!);
expect(radius / 0.114).toBeGreaterThan(1 / 1.2);
expect(radius / 0.114).toBeLessThan(1.2);
});
});
describe('radiusFromLuminositySolar', () => { describe('radiusFromLuminositySolar', () => {
it('is one for the Sun', () => { it('is one for the Sun', () => {
expect(radiusFromLuminositySolar(1, SOLAR_EFFECTIVE_TEMPERATURE_K)).toBeCloseTo(1, 12); expect(radiusFromLuminositySolar(1, SOLAR_EFFECTIVE_TEMPERATURE_K)).toBeCloseTo(1, 12);
+15 -16
View File
@@ -1,4 +1,4 @@
import { DwarfSequencePoint, dwarfSequenceAtColor, dwarfSequenceAtTemperature, dwarfSequenceAtType, isGiant, parseSpectralClass, SpectralClass, spectralTypeToColorIndex } from './spectral'; import { DwarfSequencePoint, dwarfSequenceAtColor, dwarfSequenceAtTemperature, dwarfSequenceAtType, isGiant, parseSpectralClass, SpectralClass } from './spectral';
/** /**
* Stellar luminosity, derived from the two things the star catalogue actually measures. * Stellar luminosity, derived from the two things the star catalogue actually measures.
@@ -123,15 +123,17 @@ export function luminositySolar(star: StellarPhotometry): number | null {
} }
// Where the star has a colour the dwarf sequence covers, its correction is read off that colour, // Where the star has a colour the dwarf sequence covers, its correction is read off that colour,
// and a G magnitude is carried to V first; only otherwise is the spectral type used, and a G // and a G magnitude is carried to V first; otherwise both are read off the same table at its
// magnitude taken as V. Gaia classifies none of its stars, so every one of them used to be // spectral type, and only with neither is a G magnitude taken as V. Gaia classifies none of its
// given the Sun's correction, and TRAPPIST-1 came out at a seventh of its luminosity. Against // stars, so every one of them used to be given the Sun's correction, and TRAPPIST-1 came out at
// the archive's own figure for 1 449 hosts, the worst tenth was off by 0.29 dex or more, and is // a seventh of its luminosity. Against the archive's own figure for 1 449 hosts, the worst tenth
// now off by 0.12. // was off by 0.29 dex or more, and is now off by 0.12. A type with no colour took the textbook
// anchors below instead, M8 −3.92 where the table has −5.65: GJ 3655, M8, came out 8.9×10⁻⁵ L☉
// at 3 001 K and 0.035 R☉, where its type's row gives 2 570 K and 0.106.
// //
// Not for a star its type says is a giant, though, whose correction is read off its type along // Not for a star its type says is a giant, though, whose correction is read off its type along
// with its temperature: see giantSurface. // with its temperature: see giantSurface.
const sequence = sequenceAtColour(star); const sequence = sequenceAtColour(star) ?? dwarfSequenceAtType(star.spectralType);
const absoluteV = absolute - (star.magnitudeBand === 'G' ? (sequence?.gMinusV ?? 0) : 0); const absoluteV = absolute - (star.magnitudeBand === 'G' ? (sequence?.gMinusV ?? 0) : 0);
const correction = giantSurface(star.spectralType)?.bolometricCorrectionV ?? sequence?.bolometricCorrectionV ?? bolometricCorrection(star.spectralType); const correction = giantSurface(star.spectralType)?.bolometricCorrectionV ?? sequence?.bolometricCorrectionV ?? bolometricCorrection(star.spectralType);
const bolometric = absoluteV + correction; const bolometric = absoluteV + correction;
@@ -160,20 +162,17 @@ function sequenceAtColour(star: StellarPhotometry): DwarfSequencePoint | null {
export const SOLAR_EFFECTIVE_TEMPERATURE_K = 5772; export const SOLAR_EFFECTIVE_TEMPERATURE_K = 5772;
/** /**
* Effective temperature, off the dwarf sequence at the star's colour, or at the colour its * Effective temperature, off the dwarf sequence at the star's colour, or at its spectral type where
* spectral type implies where it has none; a giant's off its type (giantSurface). Exactly the * it has none; a giant's off its type (giantSurface). Exactly the Sun's for the Sun, which is at
* Sun's for the Sun, which is at zero distance here. * zero distance here. The type was read through the textbook colour `spectralTypeToColorIndex`
* gives it, which the table puts elsewhere: M8's 1.88 is M5's, 3 001 K where M8 is 2 570, and every
* O type came out B0's 31 400.
*/ */
export function effectiveTemperatureK(star: StellarPhotometry): number | null { export function effectiveTemperatureK(star: StellarPhotometry): number | null {
if (star.distancePc === 0) { if (star.distancePc === 0) {
return SOLAR_EFFECTIVE_TEMPERATURE_K; return SOLAR_EFFECTIVE_TEMPERATURE_K;
} }
// A type's colour past the table is an O star's, which B−V no longer tells apart from B0. return giantSurface(star.spectralType)?.temperatureK ?? (sequenceAtColour(star) ?? dwarfSequenceAtType(star.spectralType))?.temperatureK ?? null;
return (
giantSurface(star.spectralType)?.temperatureK ??
(sequenceAtColour(star) ?? dwarfSequenceAtColor(spectralTypeToColorIndex(star.spectralType), 'B-V', true))?.temperatureK ??
null
);
} }
/** /**