Metadata-Version: 2.4
Name: astroz
Version: 0.15.1
Summary: Python bindings for astroz - high-performance astrodynamics library
License-Expression: MIT
Project-URL: Repository, https://github.com/ATTron/astroz
Keywords: astrodynamics,orbital-mechanics,sgp4,tle,satellite
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering :: Astronomy
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: numpy
Provides-Extra: dev
Requires-Dist: sgp4; extra == "dev"
Provides-Extra: docs
Requires-Dist: pdoc; extra == "docs"

# astroz Python Bindings

SGP4/SDP4 satellite propagation backed by the astroz Zig library, using SIMD (AVX512/AVX2) and threads. Near-earth and deep-space satellites are both handled.

Wheels: Linux (x86_64, arm64) and macOS (arm64), Python 3.10 to 3.14.

## Quick Start

### python-sgp4 compatible API

`astroz.api` follows the [python-sgp4](https://github.com/brandon-rhodes/python-sgp4) API:

```python
from astroz.api import Satrec, SatrecArray, jday, WGS72
import numpy as np

# Single satellite (same syntax as python-sgp4)
sat = Satrec.twoline2rv(line1, line2, WGS72)
jd, fr = jday(2024, 5, 6, 12, 0, 0.0)
error, position, velocity = sat.sgp4(jd, fr)

# Many satellites at once
sat_array = SatrecArray(satrecs)
e, r, v = sat_array.sgp4(jd, fr)  # Scalars or arrays

# Skip velocities if you only need positions (about 30% faster)
e, r, _ = sat_array.sgp4(jd_array, fr_array, velocities=False)
```

### High-level API

```python
from astroz import propagate
import numpy as np

positions = propagate("starlink", np.arange(1440))  # 1 day at 1-min intervals
# positions: (1440, num_satellites, 3) in km, ECEF coordinates
```

## Loading Sources

```python
from astroz import propagate, Constellation

# CelesTrak groups
positions = propagate("starlink", times)
positions = propagate("iss", times)
positions = propagate("gps", times)
positions = propagate("all", times)  # ~13k active satellites

# By NORAD ID
positions = propagate(None, times, norad_id=25544)  # ISS
positions = propagate(None, times, norad_id=[25544, 48274])  # Multiple

# Local file or URL (TLE or OMM JSON auto-detected)
positions = propagate("satellites.tle", times)
positions = propagate("https://example.com/tles.txt", times)

# OMM JSON (CCSDS 502.0-B-3) — supports 6+ digit NORAD IDs
positions = propagate('{"NORAD_CAT_ID":25544,"EPOCH":"2026-04-15T12:00:00.000000","MEAN_MOTION":15.489,...}', times)

# For repeated propagation, pre-parse to avoid overhead
c = Constellation("starlink")
positions1 = propagate(c, times1)
positions2 = propagate(c, times2)
```

Groups: `all`, `starlink`, `oneweb`, `planet`, `spire`, `gps`, `glonass`, `galileo`, `beidou`, `stations`/`iss`, `weather`, `geo`

## Propagation

```python
from astroz import propagate, Constellation
from datetime import datetime, timezone
import numpy as np

times = np.arange(1440)  # 1 day at 1-min intervals

# Simple (defaults: now UTC, ECEF output)
positions = propagate("starlink", times)

# With options
positions = propagate(
    "starlink",
    np.arange(14 * 1440),  # 2 weeks
    start_time=datetime(2024, 6, 1, tzinfo=timezone.utc),
    output="geodetic",  # "ecef" (default), "teme", or "geodetic"
)

# With velocities
positions, velocities = propagate("starlink", times, velocities=True)
```

## Conjunction Screening

```python
from astroz import screen
import numpy as np

times = np.arange(1440)

# Single target (propagates and screens in one pass, no full position array)
min_dists, min_t_indices = screen("starlink", times, threshold=50.0, target=0)
# Returns per-satellite minimum distance to target and time index

# All-vs-all screening
pairs, t_indices = screen("starlink", times, threshold=10.0)
# Returns all conjunction events within threshold
```

## Migrating from python-sgp4

Changing the import is enough for existing code to run:

```python
# Before
from sgp4.api import Satrec, SatrecArray, jday

# After
from astroz.api import Satrec, SatrecArray, jday
```

Most of the speedup comes from the batch methods, so if your code loops over times or satellites, switch to `sgp4_array` or `SatrecArray`:

```python
# Loop over times
for jd, fr in zip(jd_list, fr_list):
    e, r, v = sat.sgp4(jd, fr)
# becomes
e, r, v = sat.sgp4_array(np.array(jd_list), np.array(fr_list))

# Loop over satellites
for sat in satellites:
    e, r, v = sat.sgp4_array(jd, fr)
# becomes
e, r, v = SatrecArray(satellites).sgp4(jd, fr)
```

Pass `velocities=False` to `SatrecArray.sgp4` if you only need positions.

| Method | python-sgp4 | astroz | Speedup |
|--------|-------------|--------|---------|
| `sat.sgp4()` in a loop | 1.3M/s | 2.5M/s | 2x |
| `sat.sgp4_array()` | 2.7M/s | 15M/s | 5x |
| `SatrecArray.sgp4()` | 3M/s | 290M/s | 100x |

### Compatibility notes

These python-sgp4 `Satrec` attributes are not implemented:

- Elements after propagation: `Om`, `am`, `em`, `im`, `mm`, `nm`, `om`
- Element rates: `argpdot`, `mdot`, `nodedot`
- Gravity constants on the object: `j2`, `j3`, `j4`, `mu`, etc. Earth constants are available as `astroz.EARTH_MU`, `astroz.EARTH_J2` and `astroz.EARTH_R_EQ`.

## Performance

| Constellation (13,478 sats x 1,440 steps) | Throughput |
|--------------------------------------------|------------|
| 1 thread | 37.7M props/sec |
| 16 threads | 303M props/sec |

*Measured on an AMD Ryzen 7 7840U with AVX512.*

Set `ASTROZ_THREADS` to control thread count (defaults to all cores).

## Orbital Mechanics

```python
from astroz import (
    hohmann_transfer, bi_elliptic_transfer, lambert,
    orbital_velocity, orbital_period, escape_velocity,
    EARTH_MU,
)

# Hohmann transfer: LEO (400 km) to GEO
result = hohmann_transfer(EARTH_MU, 6778, 42164)
print(f"Total ΔV: {result['total_dv']:.3f} km/s")
print(f"Transfer time: {result['transfer_time_days']:.2f} days")

# Bi-elliptic transfer (cheaper than Hohmann for large radius ratios)
result = bi_elliptic_transfer(EARTH_MU, 6778, 42164, 100000)
print(f"Total ΔV: {result['total_dv']:.3f} km/s")

# Lambert solver: find transfer orbit between two positions
r1 = (7000.0, 0.0, 0.0)
r2 = (0.0, 7000.0, 0.0)
tof = 3600.0  # 1 hour
result = lambert(EARTH_MU, r1, r2, tof)
print(f"Departure velocity: {result['departure_velocity']}")

# Orbital velocity, period, escape velocity
v = orbital_velocity(EARTH_MU, 6778)          # Circular orbit at 400 km
T = orbital_period(EARTH_MU, 6778)            # Period in seconds
v_esc = escape_velocity(EARTH_MU, 6778)       # Escape velocity
```

## Numerical Propagation

Propagate orbits with perturbation models (J2, atmospheric drag) using RK4 or
Dormand-Prince 8(7) adaptive integrators. `r_eq` is required when using J2 or
drag perturbations. Drag uses an exponential Earth atmosphere model (sea-level
density 1.225 kg/m^3, scale height 7.249 km, cutoff altitude 1500 km).
`drag_area` is in m^2 and `drag_mass` in kg.

```python
from astroz import propagate_numerical, EARTH_MU, EARTH_J2, EARTH_R_EQ

# Initial state: LEO circular orbit [x, y, z, vx, vy, vz] (km, km/s)
state = (6778.0, 0.0, 0.0, 0.0, 7.668, 0.0)

# Two-body propagation (1 orbit, 60s steps)
times, states = propagate_numerical(state, 0.0, 5554.0, 60.0, EARTH_MU)

# With J2 perturbation
times, states = propagate_numerical(
    state, 0.0, 5554.0, 60.0, EARTH_MU,
    j2=EARTH_J2, r_eq=EARTH_R_EQ,
)

# With J2 + atmospheric drag
times, states = propagate_numerical(
    state, 0.0, 86400.0, 60.0, EARTH_MU,
    j2=EARTH_J2, r_eq=EARTH_R_EQ,
    drag_cd=2.2, drag_area=10.0, drag_mass=500.0,
    integrator="dp87",  # or "rk4"
)
```

## Constants

```python
from astroz import EARTH_MU, EARTH_R_EQ, EARTH_J2, SUN_MU, MOON_MU

print(f"Earth μ: {EARTH_MU} km³/s²")
print(f"Earth radius: {EARTH_R_EQ} km")
print(f"Earth J2: {EARTH_J2}")
print(f"Sun μ: {SUN_MU} km³/s²")
print(f"Moon μ: {MOON_MU} km³/s²")
```

## Building

Requires [Zig](https://ziglang.org/) and Python 3.10+.

```bash
cd bindings/python
pip install -e .
```
