17DIA_DTYPES = [int, float, float, float, float, str, float, float]
127 Compute solar system object properties by joining and processing
128 SSSource, DiaSource, and MPC orbit data.
130 This function takes a pre-grouped SSSource table, joins it with
131 DiaSource data, computes per-object quantities, and calculates
132 additional orbital parameters like Tisserand J and Minimum Orbit
133 Intersection Distance (MOID) with Earth for matching objects.
137 sss : pandas.DataFrame
138 SSSource table, pre-grouped by 'ssObjectId'. Must be sorted by
139 'ssObjectId' for correct grouping. Contains columns like
140 'ssObjectId', 'diaSourceId', etc.
141 dia : pandas.DataFrame
142 DiaSource table with columns prefixed as 'dia_' in the join.
143 Must include 'dia_diaSourceId', 'dia_psfFlux', 'dia_psfFluxErr',
145 mpcorb : pandas.DataFrame
146 MPC orbit data with columns like
147 'unpacked_primary_provisional_designation', 'q', 'e', 'i',
153 Array of ssObject records with dtype schema.ssObjectDtype,
154 containing computed properties for each unique ssObjectId,
155 including magnitudes, orbital elements, Tisserand J, and
161 If 'sss' is not pre-grouped by 'ssObjectId', or if DiaSources
162 are missing after join.
166 - The function assumes 'sss' is large and avoids internal
167 sorting/copying for efficiency.
168 - Tisserand J and MOID are computed only for objects matching
169 designations in 'mpcorb'.
170 - MOID computation uses a MOIDSolver for each matched object.
174 assert util.values_grouped(sss[
"ssObjectId"]), (
175 "SSSource table must be pre-grouped by ssObjectId. "
176 "An easy way to do this is to sort by ssObjectId before calling compute_ssobject(). "
177 "The grouping is required for correct per-object computations, and since SSSource is "
178 "typically large and we want to avoid copies, it's not done internally."
183 dia_tmp = dia[DIA_COLUMNS].add_prefix(
"dia_")
189 dia_tmp[
"dia_diaSourceId"] = dia_tmp[
"dia_diaSourceId"].astype(
"int64[pyarrow]")
190 sss = sss.merge(dia_tmp, left_on=
"diaSourceId", right_on=
"dia_diaSourceId", how=
"inner")
191 assert num == len(sss), f
"{num - len(sss)} DiaSources found missing."
192 del sss[
"dia_diaSourceId"]
196 sss[
"dia_psfMag"] =
nJy_to_mag(sss[
"dia_psfFlux"])
200 totalNumObjects = np.unique(sss[
"ssObjectId"]).size
201 obj = np.zeros(totalNumObjects, dtype=schema.SSObjectDtype)
204 util.group_by([sss],
"ssObjectId", compute_ssobject_entry, out=obj)
211 if mpcorb
is not None:
216 oidx, midx = util.argjoin(
217 obj[
"designation"].astype(
"U"),
218 mpcorb[
"unpacked_primary_provisional_designation"].to_numpy().astype(
"U"),
221 mpcorb[
"unpacked_primary_provisional_designation"].take(midx)
222 == obj[
"designation"][oidx].astype(
"U")
224 q, e, i, node, argperi = util.unpack(mpcorb[
"q e i node argperi".split()].take(midx))
226 obj[
"tisserand_J"][oidx] = util.tisserand_jupiter(a, e, i)
230 for i, el_obj
in enumerate(zip(a, e, i, node, argperi)):
231 (moid, deltaV, eclon, trueEarth, trueObject) = solver.compute(earth_orbit_J2000(), el_obj)
233 row[
"MOIDEarth"] = moid
234 row[
"MOIDEarthDeltaV"] = deltaV
235 row[
"MOIDEarthEclipticLongitude"] = eclon
236 row[
"MOIDEarthTrueAnomaly"] = trueEarth
237 row[
"MOIDEarthTrueAnomalyObject"] = trueObject
244 CLI entry point for building SSObject table from SSSource,
245 DiaSource, and MPC orbit data.
247 parser = argparse.ArgumentParser(
248 description=
"Build SSObject table from SSSource, DiaSource, and MPC orbit Parquet files",
249 formatter_class=argparse.RawDescriptionHelpFormatter,
252 ssp-build-ssobject sssource.parquet dia_sources.parquet mpc_orbits.parquet --output ssobject.parquet
256 parser.add_argument(
"sssource_parquet", help=
"Path to SSSource Parquet file")
257 parser.add_argument(
"diasource_parquet", help=
"Path to DiaSource Parquet file")
258 parser.add_argument(
"mpcorb_parquet", help=
"Path to MPC orbits Parquet file")
259 parser.add_argument(
"--output",
"-o", required=
True, help=
"Path to output SSObject Parquet file")
263 help=
"Re-raise exceptions instead of exiting gracefully (for debugging)",
266 args = parser.parse_args()
270 print(f
"Loading SSSource from {args.sssource_parquet}...")
271 sss = pd.read_parquet(args.sssource_parquet, engine=
"pyarrow", dtype_backend=
"pyarrow").reset_index(
275 print(f
"Loaded {num:,} SSSource rows")
288 print(f
"Loading DiaSource from {args.diasource_parquet}...")
289 dia = pd.read_parquet(
290 args.diasource_parquet, engine=
"pyarrow", dtype_backend=
"pyarrow", columns=dia_columns
291 ).reset_index(drop=
True)
292 print(f
"Loaded {len(dia):,} DiaSource rows")
295 assert np.all(dia[
"diaSourceId"] >= 0)
296 dia[
"diaSourceId"] = dia[
"diaSourceId"].astype(
"uint64[pyarrow]")
300 "unpacked_primary_provisional_designation",
313 print(f
"Loading MPC orbits from {args.mpcorb_parquet}...")
314 mpcorb = pd.read_parquet(
315 args.mpcorb_parquet, engine=
"pyarrow", dtype_backend=
"pyarrow", columns=mpcorb_columns
316 ).reset_index(drop=
True)
317 print(f
"Loaded {len(mpcorb):,} MPC orbit rows")
320 print(
"Computing SSObject data...")
324 print(f
"Saving {len(obj):,} SSObject rows to {args.output}...")
325 util.struct_to_parquet(obj, args.output)
327 print(f
"Success! Created SSObject with {len(obj):,} objects")
328 print(f
"Row size: {obj.dtype.itemsize:,} bytes, Total size: {obj.nbytes:,} bytes")
330 except Exception
as e:
331 print(f
"Error: {e}", file=sys.stderr)