Name-match example

FlyStar’s AlignmentFitCombine class matches stars across epochs by position (and optionally magnitude). For most fields this works well, but there are situations where you want to override the positional matching with an explicit star name:

  • A fast-moving star has traveled far enough between epochs that it falls outside the normal positional search radius dr_tol.

  • You have high-confidence external identifications (e.g. from a published catalog) that you want to preserve even when positions are ambiguous.

The name_match option pairs stars by name before or instead of the positional matching step. This notebook shows when and how to use it.

Contents

  1. Overview of name_match modes

  2. Example: fast-moving star outside dr_tol ('override' mode)

  3. Example: resolve a positional ambiguity ('tiebreak' mode)

  4. Excluding anonymous-prefix names

  5. Summary: which mode to choose

import numpy as np
import matplotlib.pyplot as plt
from astropy.table import Table
from flystar import starlists, align

1. Overview

name_match value

Behaviour

False (default)

Positions (and magnitudes) only — name columns are ignored.

'override' (or True)

Named stars are paired first, even if they are farther apart than dr_tol. Positional matching runs on the remaining stars.

'tiebreak'

Positional matching runs first; names resolve only cases that position + magnitude leave ambiguous. Named stars farther apart than dr_tol are not helped by this mode.

Required companion argument

Whenever name_match is on you must also supply name_match_dr_tol, the largest allowed separation (in reference coordinate units) for a same-name pair. You can give one value that applies to every iteration, or a list with one value per iteration (the last value is reused for the final matching step):

align.AlignmentFitCombine(
    ...               
    name_match='override',
    name_match_dr_tol=0.15,   # 150 mas if coordinates are in arcsec
)

When to choose 'override' vs 'tiebreak'

Use 'override' when:

  • A star moves fast enough to leave dr_tol between epochs.

  • You have reliable external identifications you want to pin.

Use 'tiebreak' when:

  • Stars are always within dr_tol, but crowding causes occasional ambiguous matches that names can resolve without otherwise altering the matching.

  • You want the most conservative use of names (names never override a clear positional match).

2. Override mode

The classic case: a fast-moving star has traveled well beyond dr_tol between two epochs. Without name_match='override' it would be treated as two separate objects, causing a spurious new entry in the reference table.

We construct a tiny synthetic dataset: one fast star and one slow background star observed at two epochs.

# ── synthetic starlists ───────────────────────────────────────────────────────
# Reference epoch (2010.0): positions in arcsec, magnitudes in K band.
ref = starlists.StarList.from_dict(
    name  = np.array(['S0-2',   'S0-5'  ]),
    x     = np.array([ 0.000,    0.500  ]),
    y     = np.array([ 0.000,    0.200  ]),
    m     = np.array([14.1,     15.3    ]),
    xe    = np.array([ 0.002,    0.002  ]),
    ye    = np.array([ 0.002,    0.002  ]),
    me    = np.array([ 0.05,     0.05   ]),
    t     = np.array([2010.0,   2010.0  ]),
)

# Second epoch (2022.0): S0-2 has moved 0.15" — well outside a typical dr_tol.
sl = starlists.StarList.from_dict(
    name  = np.array(['S0-2',   'S0-5'  ]),
    x     = np.array([ 0.130,    0.501  ]),  # S0-2 moved ~130 mas in x
    y     = np.array([-0.075,    0.199  ]),  # and ~75 mas in y
    m     = np.array([14.1,     15.3    ]),
    xe    = np.array([ 0.002,    0.002  ]),
    ye    = np.array([ 0.002,    0.002  ]),
    me    = np.array([ 0.05,     0.05   ]),
    t     = np.array([2022.0,   2022.0  ]),
)

print('Reference:')
ref[['name','x','y','m']].pprint()
print('\nSecond epoch:')
sl[['name','x','y','m']].pprint()
# ── without name_match: S0-2 is treated as a new star ─────────────────────────
al_pos_only = align.AlignmentFitCombine(
    star_lists=[sl],
    ref_list=ref,
    n_iterations=1,
    dr_tol=0.05,          # 50 mas — S0-2 has moved 150 mas, so it won't match
    dm_tol=1.0,
    name_match=False,
    verbose=0,
)
al_pos_only.fit()
print('Stars in reference after positional-only alignment:', len(al_pos_only.ref_table))
print('  →', list(al_pos_only.ref_table['name']))
# ── with name_match='override': S0-2 is correctly linked ──────────────────────
al_name = align.AlignmentFitCombine(
    star_lists=[sl],
    ref_list=ref,
    n_iterations=1,
    dr_tol=0.05,
    dm_tol=1.0,
    name_match='override',
    name_match_dr_tol=0.20,  # up to 200 mas for a name-matched pair
    verbose=0,
)
al_name.fit()
print('Stars in reference after name-match alignment:', len(al_name.ref_table))
print('  →', list(al_name.ref_table['name']))

With name_match=False the reference grows to three entries because the displaced S0-2 is not recognised. With name_match='override' the reference stays at two, and S0-2’s motion is captured.

3. Tiebreak mode

'tiebreak' uses names only to resolve matches that position + magnitude leave ambiguous. Stars farther than dr_tol are not helped; this mode is suited for crowded fields where two stars fall within each other’s dr_tol and their magnitudes are similar, but their names differ.

# Two nearby, similar-magnitude stars that would be ambiguous on position alone.
ref2 = starlists.StarList.from_dict(
    name  = np.array(['S0-9',   'S0-10' ]),
    x     = np.array([ 0.000,    0.030  ]),  # only 30 mas apart
    y     = np.array([ 0.000,    0.010  ]),
    m     = np.array([15.0,     15.1    ]),   # nearly identical magnitudes
    xe    = np.array([ 0.002,    0.002  ]),
    ye    = np.array([ 0.002,    0.002  ]),
    me    = np.array([ 0.05,     0.05   ]),
    t     = np.array([2010.0,   2010.0  ]),
)

sl2 = starlists.StarList.from_dict(
    name  = np.array(['S0-9',   'S0-10' ]),
    x     = np.array([ 0.001,    0.029  ]),
    y     = np.array([ 0.001,    0.009  ]),
    m     = np.array([15.0,     15.1    ]),
    xe    = np.array([ 0.002,    0.002  ]),
    ye    = np.array([ 0.002,    0.002  ]),
    me    = np.array([ 0.05,     0.05   ]),
    t     = np.array([2022.0,   2022.0  ]),
)

al_tb = align.AlignmentFitCombine(
    star_lists=[sl2],
    ref_list=ref2,
    n_iterations=1,
    dr_tol=0.05,
    dm_tol=0.5,
    name_match='tiebreak',
    name_match_dr_tol=0.05,
    verbose=0,
)
al_tb.fit()
print('Stars after tiebreak alignment:', len(al_tb.ref_table))
print('  →', list(al_tb.ref_table['name']))

4. Excluding anonymous-prefix names

Not every name in a starlist is meaningful. FlyStar itself adds a numeric prefix (e.g. '  3_S0-2') to new stars it discovers during alignment; those entries should not drive name matching. The name_match_exclude argument accepts a tuple of string prefixes; any name beginning with one of them is treated as anonymous and is matched by position only.

The default is ('star',), which masks names like 'star_001' that generic source-extraction tools often produce. Override it if your pipeline uses a different convention:

# Exclude names starting with 'src' as well as the default 'star'.
al_excl = align.AlignmentFitCombine(
    star_lists=[sl],
    ref_list=ref,
    n_iterations=1,
    dr_tol=0.05,
    dm_tol=1.0,
    name_match='override',
    name_match_dr_tol=0.20,
    name_match_exclude=('star', 'src'),   # custom prefixes
    verbose=0,
)
al_excl.fit()
print('Done. Stars in reference:', len(al_excl.ref_table))

5. Summary: which mode to choose

Scenario

Recommended mode

Fast-moving star that leaves dr_tol between epochs

'override'

Reliable external catalogue identifications to preserve

'override'

Crowded field: stars within dr_tol but names resolve ambiguity

'tiebreak'

No named identifications, or names are unreliable

False (default)

Choosing name_match_dr_tol

Set name_match_dr_tol to roughly the largest displacement you expect for a named star between adjacent epochs. A value too small will fail to link the star; a value too large risks linking two different stars that happen to share a name after a catalogue cross-match error. For Galactic Centre work with typical proper motions of a few mas/yr and epoch gaps of 1–2 years, values of 50–200 mas are common.