1
import math
from typing import List, Tuple, Any
# ---------- parsing helpers ----------
def _to_float(x: Any) -> float:
try:
return float(x)
except Exception:
return None
def parse_ra(ra: Any) -> float:
"""
Right ascension -> radians.
Accepts:
- decimal degrees (e.g., 217.427) -> degrees
- 'hh mm ss.s' or 'hh:mm:ss.s' -> hours
- '14h 29m 43.0s' -> hours
"""
f = _to_float(ra)
if f is not None:
deg = f
else:
s = str(ra).strip().lower().replace("h", " ").replace("m", " ").replace("s", " ")
s = s.replace(":", " ")
hh, mm, ss = [float(t) for t in s.split()]
hours = hh + mm/60 + ss/3600
deg = hours * 15.0
return math.radians(deg)
def parse_dec(dec: Any) -> float:
"""
Declination -> radians.
Accepts:
- decimal degrees (e.g., -62.6794)
- '±dd mm ss' or '±dd:mm:ss'
- '−62° 40′ 46″' (unicode symbols OK)
"""
f = _to_float(dec)
if f is not None:
deg = f
else:
s = str(dec).strip().lower()
# normalize unicode/degree markers to spaces
for ch in ["°", "º", "′", "’", "'", "″", '"']:
s = s.replace(ch, " ")
s = s.replace(":", " ")
sign = -1.0 if s.lstrip().startswith(("-", "−")) else 1.0
s = s.replace("−", "").replace("-", "") # strip minus for split
parts = [p for p in s.split() if p]
dd, mm, ss = [float(t) for t in parts[:3]]
deg = sign * (abs(dd) + mm/60 + ss/3600)
return math.radians(deg)
# ---------- coordinate conversion ----------
def radec_to_cartesian_ly(dist_ly: float, ra_rad: float, dec_rad: float) -> Tuple[float, float, float]:
# standard astronomical convention
x = dist_ly * math.cos(dec_rad) * math.cos(ra_rad)
y = dist_ly * math.cos(dec_rad) * math.sin(ra_rad)
z = dist_ly * math.sin(dec_rad)
return (x, y, z)
def euclid(a: Tuple[float, float, float], b: Tuple[float, float, float]) -> float:
return math.dist(a, b)
# ---------- main: nearest neighbors ----------
def nearest_neighbors(
stars: List[Tuple[str, float, Any, Any]],
top_k_examples: int = 10
):
"""
stars: list of [name, distance_ly, RA, Dec]
RA/Dec can be decimal deg or strings as shown above.
"""
names, xyz = [], []
for name, d_ly, ra, dec in stars:
ra_r = parse_ra(ra)
dec_r = parse_dec(dec)
names.append(name)
xyz.append(radec_to_cartesian_ly(d_ly, ra_r, dec_r))
n = len(names)
nn = [] # (i, j, dist)
for i in range(n):
best_j, best_d = None, float("inf")
for j in range(n):
if i == j: continue
d = euclid(xyz[i], xyz[j])
if d < best_d:
best_d, best_j = d, j
nn.append((i, best_j, best_d))
avg = sum(d for _, _, d in nn) / n if n else float("nan")
print(f"Average nearest-neighbor distance: {avg:.3f} ly")
# show a few examples
for i, (ii, jj, d) in enumerate(sorted(nn, key=lambda t: t[2])[:top_k_examples]):
print(f"{i+1:>2}. {names[ii]} ↔ {names[jj]} : {d:.3f} ly")
return nn, avg
# ---------- example usage ----------
stars = [
["Sun", 0, "0", "0"],
["Proxima Centauri", 4.2465, "14h 29m 43.0s", "−62° 40′ 46″"],
["Alpha Centauri A", 4.367, "14h 39m 36.5s", "−60° 50′ 02″"],
["Alpha Centauri B", 4.367, "14h 39m 35.1s", "−60° 50′ 14″"],
["Barnard's Star", 5.9629, "17h 57m 48.5s", "+04° 41′ 36″"],
# add as many as you like…
]
if __name__ == "__main__":
nearest_neighbors(stars)
For immediate assistance, please email our customer support: [email protected]