Skip to content

Class supporting queries for GMN catalog#856

Draft
g7gpr wants to merge 2 commits intoCroatianMeteorNetwork:prereleasefrom
g7gpr:feature/spherical_tree
Draft

Class supporting queries for GMN catalog#856
g7gpr wants to merge 2 commits intoCroatianMeteorNetwork:prereleasefrom
g7gpr:feature/spherical_tree

Conversation

@g7gpr
Copy link
Copy Markdown
Contributor

@g7gpr g7gpr commented Mar 20, 2026

Adds a Catalog class which on initialization reads in the GMN catalog, and exposes a query interface which can receive scalars or arrays for a tree search in a Euclidean space. Once the catalog is loaded, multiple queries can be executed very quickly.

Return is a list of arrays of results, one item for scalar inputs, multiple for array inputs. Results are ordered by increasing magnitude.

e.g.

cat = Catalog(config, lim_mag=10)
results = cat.queryRaDec(ra, dec, radius)

Also adds a command line interface.

(vRMS) :~/source/RMS$ python -m RMS.Formats.StarCatalog --radec 0.1 55.7537 1 6
2026/03/20 04:21:32-INFO-StarCatalog-line:877 - Querying RA=0.100 DEC=55.754 Radius=1.000 degrees Limiting mag=6.0
2026/03/20 04:21:38-INFO-StarCatalog-line:880 - 		Returned name: HD 3712              RA=  10.127  Dec=  56.537  Mag= 1.94  Sep= 5.636

Queries wrap around all edges

And a test routine.

(vRMS) :~/source/RMS$ python -m RMS.Formats.StarCatalog --test

2026/03/20 04:25:32-INFO-StarCatalog-line:810 - 
	New search for Sirius               of magnitude -1.46
		Returned name: HD 48915             RA= 101.281  Dec= -16.730  Mag=-1.44  Sep= 0.042
		Returned name: HD 49980             RA= 102.591  Dec= -17.085  Mag= 5.27  Sep= 1.339
		Returned name: HD 49048             RA= 101.497  Dec= -14.796  Mag= 5.27  Sep= 1.919

	New search for Canopus              of magnitude -0.74
		Returned name: HD 45348             RA=  95.988  Dec= -52.695  Mag=-0.63  Sep= 0.008
		Returned name: HD 47306             RA=  98.744  Dec= -52.976  Mag= 4.33  Sep= 1.680
		Returned name: HD 46569             RA=  97.828  Dec= -51.825  Mag= 5.45  Sep= 1.420

	New search for Alpha Centauri       of magnitude -0.27
		Returned name: HD 128621            RA= 219.843  Dec= -60.831  Mag= 1.35  Sep= 0.083
		Returned name: HD 127631            RA= 218.705  Dec= -60.964  Mag= 5.50  Sep= 0.651
		Returned name: HD 130206            RA= 222.312  Dec= -59.407  Mag= 5.93  Sep= 1.807

	New search for Arcturus             of magnitude -0.05
		Returned name: HD 124897            RA= 213.912  Dec=  19.177  Mag=-0.44  Sep= 0.086
		Returned name: HD 124897            RA= 213.944  Dec=  19.229  Mag= 0.88  Sep= 0.060
		Returned name: HD 124953            RA= 214.018  Dec=  18.912  Mag= 5.92  Sep= 0.289

	New search for Vega                 of magnitude 0.03
		Returned name: HD 172167            RA= 279.237  Dec=  38.786  Mag= 0.03  Sep= 0.018
		Returned name: HD 172380            RA= 279.527  Dec=  39.668  Mag= 4.23  Sep= 0.895
		Returned name: HD 173648            RA= 281.193  Dec=  37.605  Mag= 4.32  Sep= 1.939

	New search for Capella              of magnitude 0.08
		Returned name: HD 33167             RA=  77.679  Dec=  46.961  Mag= 5.56  Sep= 1.398
		Returned name: HD 34903             RA=  80.803  Dec=  47.022  Mag= 6.05  Sep= 1.673
		Returned name: HD 34498             RA=  80.010  Dec=  44.425  Mag= 6.05  Sep= 1.638

	New search for Rigel                of magnitude 0.18
		Returned name: HD 34085             RA=  78.634  Dec=  -8.202  Mag= 0.28  Sep= 0.114
		Returned name: HD 34503             RA=  79.401  Dec=  -6.844  Mag= 3.59  Sep= 1.502
		Returned name: HD 33328             RA=  77.287  Dec=  -8.754  Mag= 4.24  Sep= 1.550

	New search for Procyon              of magnitude 0.38
		Returned name: HD 61421             RA= 114.820  Dec=   5.218  Mag= 0.40  Sep= 0.072
		Returned name: HD 60803             RA= 114.144  Dec=   5.862  Mag= 5.75  Sep= 0.896
		Returned name: HD 61887             RA= 115.396  Dec=   3.625  Mag= 5.94  Sep= 1.702

	New search for Achernar             of magnitude 0.46
		Returned name: HD 10144             RA=  24.429  Dec= -57.237  Mag= 0.06  Sep= 0.053
		Returned name: HD 10052             RA=  24.187  Dec= -58.271  Mag= 5.19  Sep= 1.084
		Returned name: HD 10360             RA=  24.953  Dec= -56.193  Mag= 5.51  Sep= 1.037

	New search for Betelgeuse           of magnitude 0.50
		Returned name: HD 39801             RA=  88.793  Dec=   7.407  Mag= 0.57  Sep= 0.043
		Returned name: HD 38710             RA=  87.001  Dec=   6.454  Mag= 5.90  Sep= 1.977
		Returned name: TYC 128-1551-2       RA=  87.001  Dec=   6.454  Mag= 6.03  Sep= 1.977

	New search for Altair               of magnitude 0.77
		Returned name: HD 187642            RA= 297.700  Dec=   8.871  Mag= 0.93  Sep= 0.058
		Returned name: HD 188310            RA= 298.563  Dec=   8.461  Mag= 4.41  Sep= 0.916
		Returned name: HD 187691            RA= 297.759  Dec=  10.415  Mag= 4.98  Sep= 1.515

	New search for Aldebaran            of magnitude 0.85
		Returned name: HD 29139             RA=  68.981  Dec=  16.508  Mag= 0.99  Sep= 0.020
		Returned name: HD 28319             RA=  67.166  Dec=  15.871  Mag= 3.38  Sep= 1.870
		Returned name: HD 28307             RA=  67.145  Dec=  15.962  Mag= 3.58  Sep= 1.861

	New search for Antares              of magnitude 1.06
		Returned name: HD 148478            RA= 247.352  Dec= -26.432  Mag= 1.07  Sep= 0.097
		Returned name: HD 147165            RA= 245.297  Dec= -25.593  Mag= 2.92  Sep= 1.932
		Returned name: HD 148605            RA= 247.552  Dec= -25.115  Mag= 4.75  Sep= 1.313

	New search for Spica                of magnitude 1.04
		Returned name: HD 116658            RA= 201.298  Dec= -11.161  Mag= 0.58  Sep= 0.061
		Returned name: HD 116870            RA= 201.679  Dec= -12.708  Mag= 4.67  Sep= 1.565
		Returned name: HD 116175            RA= 200.534  Dec= -12.580  Mag= 6.18  Sep= 1.547

	New search for Fomalhaut            of magnitude 1.16
		Returned name: HD 216956            RA= 344.415  Dec= -29.623  Mag= 0.76  Sep= 0.077
		Returned name: HD 217484            RA= 345.331  Dec= -28.854  Mag= 5.10  Sep= 1.040
		Returned name: HD 217236            RA= 344.899  Dec= -29.462  Mag= 5.45  Sep= 0.373

	New search for Deneb                of magnitude 1.25
		Returned name: HD 197345            RA= 310.358  Dec=  45.280  Mag= 1.33  Sep= 0.078
		Returned name: HD 198478            RA= 312.235  Dec=  46.114  Mag= 4.67  Sep= 1.607
		Returned name: HD 197139            RA= 310.012  Dec=  43.458  Mag= 5.62  Sep= 1.849

	New search for Pollux               of magnitude 1.14
		Returned name: HD 62044             RA= 115.829  Dec=  28.882  Mag= 3.97  Sep= 0.957
		Returned name: HD 63138             RA= 117.120  Dec=  28.764  Mag= 6.60  Sep= 1.082
		Returned name: HD 63433b            RA= 117.479  Dec=  27.363  Mag= 6.74  Sep= 1.261

	New search for Castor               of magnitude 1.58
		Returned name: SAO 60198            RA= 113.649  Dec=  31.888  Mag= 2.92  Sep= 0.086
		Returned name: SAO 60198            RA= 113.649  Dec=  31.888  Mag= 2.92  Sep= 0.086
		Returned name: HD 58946             RA= 112.279  Dec=  31.786  Mag= 4.09  Sep= 1.255

	New search for Regulus              of magnitude 1.35
		Returned name: HD 87901             RA= 152.090  Dec=  11.967  Mag= 1.41  Sep= 0.094
		Returned name: HD 88355             RA= 152.909  Dec=  13.355  Mag= 6.34  Sep= 1.620
		Returned name: HD 88853             RA= 153.790  Dec=  11.674  Mag= 6.94  Sep= 1.782

	New search for Bellatrix            of magnitude 1.64
		Returned name: HD 35468             RA=  81.283  Dec=   6.350  Mag= 1.24  Sep= 0.059
		Returned name: HD 36267             RA=  82.696  Dec=   5.948  Mag= 4.44  Sep= 1.480
		Returned name: TYC 126-1899-2       RA=  82.696  Dec=   5.948  Mag= 5.82  Sep= 1.481

@g7gpr g7gpr requested review from Cybis320 and dvida March 20, 2026 04:28
@g7gpr g7gpr self-assigned this Mar 20, 2026
@g7gpr g7gpr changed the title Class suppporting queries for GMN catalog Class supporting queries for GMN catalog Mar 20, 2026
@dvida dvida requested a review from Copilot March 23, 2026 17:40
@dvida
Copy link
Copy Markdown
Contributor

dvida commented Mar 23, 2026

This is brilliant - I haven't looked into the full details but can you make it so that:

  • Returns all stars in a given radius (I belive it already does this)
  • Returns all stars in a given range of ra,dec to ra2,dec2?
  • Returns all stars in a given spherical polygon (see Math.py for the spherical polygon selection function)

Brilliant work as always!

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a Catalog helper to load the GMN star catalog once, build a KD-tree for fast repeated spherical proximity searches, and exposes a CLI/test routine for querying by RA/Dec.

Changes:

  • Introduces Catalog class that loads the configured star catalog, builds a 3D unit-vector cKDTree, and provides queryRaDec.
  • Extends GMN catalog loading to support returning additional fields (e.g., preferred_name) for consumers.
  • Adds __main__ CLI entry points for --radec querying and --test output serialization.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +36 to +54
def __init__(self, config, catalogue_time=None, ra_col=0, dec_col=1, mag_col=2, name_col=0, lim_mag=None):

"""Initialise a catalog in a spherical tree object/

Arguments:
config[config]: RMS config instance.

Keyword Arguments:
catalogue_time: Time point for the catalogue generation if none build for now.
ra_col: Optional, default 0 - array column with ra data in degrees.
dec_col: Optional, default 1 - array column with dec data in degrees.
mag_col: Optional, default 2 - array column of magnitude data.
name_col: Optional, default 3 - array column of star names.

"""

self.ra_col, self.dec_col, self.mag_col = ra_col, dec_col, mag_col
self.name_col = name_col

Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The __init__ docstring and parameters disagree: name_col defaults to 0 in the signature but the docstring says default 3, and name_col isn't used in the implementation. Either implement name column selection or remove/rename these parameters so the API matches actual behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +818 to +834
return output

last_searched_name = None
for i, r in enumerate(results):
for name, ra, dec, mag, theta in r:
if star_mag is not None:
searched_mag = star_mag[i]
if star_names is not None:
searched_name = star_names[i]
if last_searched_name != searched_name:
output.append(f"\n\tNew search for {searched_name:20} of magnitude {float(searched_mag):4.2f}")
last_searched_name = searched_name


output.append(
f"\t\tReturned name: {name:20s} RA={float(ra):8.3f} Dec={float(dec):8.3f} Mag={float(mag):5.2f} Sep={float(theta):6.3f}")

Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When no results are returned, serializeQueryResults returns a Python list, but in the non-empty case it returns a string. This inconsistent return type will break callers that always expect a string (e.g., logging). Return a string in the empty-results case as well.

Suggested change
return output
last_searched_name = None
for i, r in enumerate(results):
for name, ra, dec, mag, theta in r:
if star_mag is not None:
searched_mag = star_mag[i]
if star_names is not None:
searched_name = star_names[i]
if last_searched_name != searched_name:
output.append(f"\n\tNew search for {searched_name:20} of magnitude {float(searched_mag):4.2f}")
last_searched_name = searched_name
output.append(
f"\t\tReturned name: {name:20s} RA={float(ra):8.3f} Dec={float(dec):8.3f} Mag={float(mag):5.2f} Sep={float(theta):6.3f}")
else:
last_searched_name = None
for i, r in enumerate(results):
for name, ra, dec, mag, theta in r:
if star_mag is not None:
searched_mag = star_mag[i]
if star_names is not None:
searched_name = star_names[i]
if last_searched_name != searched_name:
output.append(f"\n\tNew search for {searched_name:20} of magnitude {float(searched_mag):4.2f}")
last_searched_name = searched_name
output.append(
f"\t\tReturned name: {name:20s} RA={float(ra):8.3f} Dec={float(dec):8.3f} Mag={float(mag):5.2f} Sep={float(theta):6.3f}")

Copilot uses AI. Check for mistakes.
Comment on lines +878 to +879
cat = Catalog(config, lim_mag=float(cml_args.radec[2]))
results = cat.queryRaDec(ra, dec, lim_mag)
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CLI wiring appears to mix up radius and lim_mag: Catalog(..., lim_mag=float(cml_args.radec[2])) uses the radius value as lim_mag, and cat.queryRaDec(ra, dec, lim_mag) passes the limiting magnitude as radius_deg. This will produce incorrect queries. Use lim_mag from cml_args.radec[3] when constructing the catalog, and pass radius to queryRaDec’s radius_deg parameter.

Suggested change
cat = Catalog(config, lim_mag=float(cml_args.radec[2]))
results = cat.queryRaDec(ra, dec, lim_mag)
cat = Catalog(config, lim_mag=lim_mag)
results = cat.queryRaDec(ra, dec, radius)

Copilot uses AI. Check for mistakes.
Comment on lines +70 to +73
pass

if not star_catalog_status:
print("Could not load star catalogue")
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If readStarCatalog fails, the code prints an error but continues and will raise when unpacking star_catalog_status. This should fail fast (raise an exception or return early) to avoid a confusing downstream traceback.

Suggested change
pass
if not star_catalog_status:
print("Could not load star catalogue")
if not star_catalog_status:
print("Could not load star catalogue")
raise RuntimeError("Could not load star catalogue from '{}' (file '{}')".format(
config.star_catalog_path, config.star_catalog_file))

Copilot uses AI. Check for mistakes.
Comment on lines +823 to +831
if star_mag is not None:
searched_mag = star_mag[i]
if star_names is not None:
searched_name = star_names[i]
if last_searched_name != searched_name:
output.append(f"\n\tNew search for {searched_name:20} of magnitude {float(searched_mag):4.2f}")
last_searched_name = searched_name


Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

serializeQueryResults formats searched_mag inside the star_names is not None branch, but searched_mag is only assigned when star_mag is not None. If callers pass star_names without star_mag, this will raise UnboundLocalError. Either require both, or guard the magnitude formatting when star_mag is missing.

Suggested change
if star_mag is not None:
searched_mag = star_mag[i]
if star_names is not None:
searched_name = star_names[i]
if last_searched_name != searched_name:
output.append(f"\n\tNew search for {searched_name:20} of magnitude {float(searched_mag):4.2f}")
last_searched_name = searched_name
searched_mag = star_mag[i] if star_mag is not None else None
searched_name = star_names[i] if star_names is not None else None
if searched_name is not None and last_searched_name != searched_name:
if searched_mag is not None:
output.append(f"\n\tNew search for {searched_name:20} of magnitude {float(searched_mag):4.2f}")
else:
output.append(f"\n\tNew search for {searched_name:20}")
last_searched_name = searched_name

Copilot uses AI. Check for mistakes.
Comment on lines +809 to +810
cat = Catalog(config, lim_mag=6)
log.info(serializeQueryResults(cat.queryRaDec(test_star_ra_deg, test_star_dec_deg, radius_deg=2, n_brightest=3), test_star_names, test_star_mag))
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

testCatQueryRaDec depends on config and log globals that are only set in the __main__ block. This makes the function fragile when imported/called from elsewhere. Consider passing config and log in as parameters (or constructing a logger/config within the test function).

Copilot uses AI. Check for mistakes.
star_catalog_status = readStarCatalog(
config.star_catalog_path,
config.star_catalog_file,
lim_mag=np.inf,
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Catalog.__init__ accepts lim_mag but currently ignores it (hardcoded lim_mag=np.inf when calling readStarCatalog). This makes the public API misleading and can significantly increase memory/CPU when building the KD-tree. Pass the lim_mag argument through (or remove it from the signature if intentionally unsupported).

Suggested change
lim_mag=np.inf,
lim_mag=np.inf if lim_mag is None else lim_mag,

Copilot uses AI. Check for mistakes.
Comment on lines +70 to +71
pass

Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an extraneous pass statement here which has no effect and looks like leftover debug code. It should be removed.

Suggested change
pass

Copilot uses AI. Check for mistakes.
Comment on lines +70 to +77
pass

if not star_catalog_status:
print("Could not load star catalogue")

catalog_stars, _, config.star_catalog_band_ratios, extras = star_catalog_status


Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Catalog.__init__ unconditionally requests additional_fields and unpacks 4 return values from readStarCatalog, but readStarCatalog returns only 3 values for non-GMN catalogs (e.g., BSC/GAIA/Sky2000). If this class is GMN-only, add a clear validation/error when the configured catalog is not GMN; otherwise handle both return shapes.

Suggested change
pass
if not star_catalog_status:
print("Could not load star catalogue")
catalog_stars, _, config.star_catalog_band_ratios, extras = star_catalog_status
if not star_catalog_status:
print("Could not load star catalogue")
raise ValueError("Could not load star catalogue from path '{}' and file '{}'".format(
config.star_catalog_path, config.star_catalog_file))
# Handle both 3- and 4-element return values from readStarCatalog.
if isinstance(star_catalog_status, tuple):
if len(star_catalog_status) == 4:
catalog_stars, _, config.star_catalog_band_ratios, extras = star_catalog_status
elif len(star_catalog_status) == 3:
catalog_stars, _, config.star_catalog_band_ratios = star_catalog_status
# Construct a minimal extras dict with a preferred_name field so later code works.
n_stars = catalog_stars.shape[0]
if (self.name_col is not None and
0 <= self.name_col < catalog_stars.shape[1]):
preferred_names = catalog_stars[:, self.name_col]
else:
preferred_names = np.full(n_stars, "", dtype=object)
extras = {"preferred_name": preferred_names}
else:
raise ValueError("Unexpected number of values returned by readStarCatalog: {}".format(
len(star_catalog_status)))
else:
raise ValueError("Unexpected type returned by readStarCatalog: {}".format(
type(star_catalog_status)))

Copilot uses AI. Check for mistakes.
@g7gpr
Copy link
Copy Markdown
Contributor Author

g7gpr commented Mar 23, 2026

This is brilliant - I haven't looked into the full details but can you make it so that:

* Returns all stars in a given radius (I belive it already does this)

* Returns all stars in a given range of ra,dec to ra2,dec2?

* Returns all stars in a given spherical polygon (see Math.py for the spherical polygon selection function)

Brilliant work as always!

Thanks for the copilot review and feedback. The task is to find all the query types in RMS and add them into the catalog class, but using all the math tricks to make them computationally cheap. This class has to not only do the abstraction work, but also be high performance.

This version is a touch out of date, I'll merge in my latest version from the pipeline work in the next few days.

I can implement those queries, the plan is to implement all the queries into the class. What will take time is finding the maths tricks to make computationally cheap queries.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants