Skip to content

Commit 0befacd

Browse files
authored
faster conjunction (#64)
* faster conjunction * fix ci
1 parent 24ac343 commit 0befacd

File tree

13 files changed

+567
-361
lines changed

13 files changed

+567
-361
lines changed

.github/workflows/ci.yaml

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -94,26 +94,21 @@ jobs:
9494
9595
- name: Test import and basic functionality
9696
run: |
97-
python -c "from astroz import Tle, Sgp4, __version__; print(f'astroz {__version__}')"
97+
python -c "from astroz import Tle, Constellation, propagate, __version__; print(f'astroz {__version__}')"
9898
python -c "
99-
from astroz import Tle, Sgp4
99+
from astroz import Tle, propagate
100100
import numpy as np
101101
102+
tle_text = '1 25544U 98067A 24127.82853009 .00015698 00000+0 27310-3 0 9995\n2 25544 51.6393 160.4574 0003580 140.6673 205.7250 15.50957674452123'
103+
102104
# Test TLE parsing
103-
tle = Tle('1 25544U 98067A 24127.82853009 .00015698 00000+0 27310-3 0 9995\n2 25544 51.6393 160.4574 0003580 140.6673 205.7250 15.50957674452123')
105+
tle = Tle(tle_text)
104106
print(f'Satellite: {tle.satellite_number}')
105107
106-
# Test single propagation
107-
sgp4 = Sgp4(tle)
108-
pos, vel = sgp4.propagate(30.0)
109-
print(f'Position: {pos}')
110-
111-
# Test batch propagation
108+
# Test propagation
112109
times = np.arange(100, dtype=np.float64)
113-
positions = np.empty((100, 3), dtype=np.float64)
114-
velocities = np.empty((100, 3), dtype=np.float64)
115-
sgp4.propagate_into(times, positions, velocities)
116-
print(f'Batch: {len(positions)} points')
110+
positions = propagate(tle_text, times)
111+
print(f'Batch: {positions.shape}')
117112
"
118113
119114
- name: Run Python example

.github/workflows/python.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,13 @@ jobs:
111111
- name: Test wheel
112112
run: |
113113
pip install bindings/python/dist/*.whl
114-
python -c "from astroz import Tle, Sgp4, __version__; print(f'astroz {__version__}')"
114+
python -c "from astroz import Tle, Constellation, propagate, __version__; print(f'astroz {__version__}')"
115115
python -c "
116-
from astroz import Tle, Sgp4
117-
tle = Tle('1 25544U 98067A 24127.82853009 .00015698 00000+0 27310-3 0 9995\n2 25544 51.6393 160.4574 0003580 140.6673 205.7250 15.50957674452123')
118-
sgp4 = Sgp4(tle)
119-
pos, vel = sgp4.propagate(30.0)
120-
print(f'Position: {pos}')
116+
from astroz import Tle, propagate
117+
tle_text = '1 25544U 98067A 24127.82853009 .00015698 00000+0 27310-3 0 9995\n2 25544 51.6393 160.4574 0003580 140.6673 205.7250 15.50957674452123'
118+
tle = Tle(tle_text)
119+
pos = propagate(tle_text, [30.0])
120+
print(f'Position: {pos[0, 0]}')
121121
"
122122
123123
- name: Upload wheels

README.md

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,31 +54,28 @@ pip install astroz
5454
```
5555

5656
```python
57-
from astroz import Constellation
57+
from astroz import propagate, Constellation
5858
import numpy as np
5959

6060
# Load and propagate - automatically optimized for maximum performance
61-
constellation = Constellation("starlink")
62-
positions = constellation.propagate(np.arange(1440)) # 1 day at 1-min intervals
61+
positions = propagate("starlink", np.arange(1440)) # 1 day at 1-min intervals
6362
# shape: (1440, num_satellites, 3) in km, ECEF coordinates
6463

6564
# With options
6665
from datetime import datetime, timezone
67-
positions = constellation.propagate(
66+
positions = propagate(
67+
"starlink",
6868
np.arange(1440),
6969
start_time=datetime(2024, 6, 15, tzinfo=timezone.utc),
7070
output="geodetic", # "ecef" (default), "teme", or "geodetic"
7171
)
7272

7373
# With velocities
74-
positions, velocities = constellation.propagate(np.arange(1440), velocities=True)
74+
positions, velocities = propagate("starlink", np.arange(1440), velocities=True)
7575

76-
# Single satellite
77-
from astroz import Tle, Sgp4
78-
79-
tle = Tle("1 25544U 98067A 24127.82853009 ...\n2 25544 51.6393 ...")
80-
sgp4 = Sgp4(tle)
81-
pos, vel = sgp4.propagate(30.0) # 30 min after epoch
76+
# For repeated propagation, pre-parse to avoid overhead
77+
c = Constellation("starlink")
78+
positions = propagate(c, np.arange(1440))
8279
```
8380

8481
### Usage

benchmarks/python_bench.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@
44
import time
55
import numpy as np
66

7+
ISS_TLE = """ISS (ZARYA)
8+
1 25544U 98067A 24127.82853009 .00015698 00000+0 27310-3 0 9995
9+
2 25544 51.6393 160.4574 0003580 140.6673 205.7250 15.50957674452123"""
10+
711
ISS_TLE_LINE1 = "1 25544U 98067A 24127.82853009 .00015698 00000+0 27310-3 0 9995"
812
ISS_TLE_LINE2 = "2 25544 51.6393 160.4574 0003580 140.6673 205.7250 15.50957674452123"
913

1014
SCENARIOS = [
1115
("1 day (minute)", 1440, 1.0),
1216
("1 week (minute)", 10080, 1.0),
1317
("2 weeks (minute)", 20160, 1.0),
14-
("2 weeks (second)", 1209600, 1.0 / 60.0),
1518
("1 month (minute)", 43200, 1.0),
1619
]
1720

@@ -20,27 +23,27 @@
2023

2124
def bench_astroz():
2225
try:
23-
from astroz import Tle, Sgp4
26+
from astroz import Constellation, propagate
2427
except ImportError:
2528
print(
2629
"astroz not installed... run: zig build python-bindings && pip install -e bindings/python/"
2730
)
2831
return None
2932

30-
tle = Tle(f"{ISS_TLE_LINE1}\n{ISS_TLE_LINE2}")
31-
sgp4 = Sgp4(tle)
32-
for i in range(100): # warmup
33-
sgp4.propagate(float(i))
33+
# Pre-parse constellation to avoid parsing overhead in benchmark
34+
c = Constellation(ISS_TLE)
35+
36+
# Warmup
37+
for _ in range(10):
38+
propagate(c, np.arange(100, dtype=np.float64))
3439

3540
results = {}
3641
for name, points, step in SCENARIOS:
3742
times = np.arange(points, dtype=np.float64) * step
38-
positions = np.empty((points, 3), dtype=np.float64)
39-
velocities = np.empty((points, 3), dtype=np.float64)
4043
total = 0
4144
for _ in range(ITERATIONS):
4245
start = time.perf_counter_ns()
43-
sgp4.propagate_into(times, positions, velocities)
46+
propagate(c, times)
4447
total += time.perf_counter_ns() - start
4548
results[name] = total / ITERATIONS / 1_000_000
4649
return results

bindings/python/README.md

Lines changed: 33 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -7,125 +7,93 @@ High-performance SGP4 satellite propagation, powered by Zig with SIMD accelerati
77
## Quick Start
88

99
```python
10-
from astroz import Constellation
10+
from astroz import propagate
1111
import numpy as np
1212

1313
# Load and propagate - automatically optimized for 300M+ props/sec
14-
constellation = Constellation("starlink")
15-
positions = constellation.propagate(np.arange(1440)) # 1 day at 1-min intervals
14+
positions = propagate("starlink", np.arange(1440)) # 1 day at 1-min intervals
1615
# positions: (1440, num_satellites, 3) in km, ECEF coordinates
1716
```
1817

19-
## Loading Constellations
18+
## Loading Sources
2019

2120
```python
22-
from astroz import Constellation
21+
from astroz import propagate, Constellation
2322

2423
# CelesTrak groups
25-
constellation = Constellation("starlink")
26-
constellation = Constellation("iss")
27-
constellation = Constellation("gps")
28-
constellation = Constellation("all") # ~13k active satellites
24+
positions = propagate("starlink", times)
25+
positions = propagate("iss", times)
26+
positions = propagate("gps", times)
27+
positions = propagate("all", times) # ~13k active satellites
2928

3029
# By NORAD ID
31-
constellation = Constellation(norad_id=25544) # ISS
32-
constellation = Constellation(norad_id=[25544, 48274]) # Multiple
30+
positions = propagate(None, times, norad_id=25544) # ISS
31+
positions = propagate(None, times, norad_id=[25544, 48274]) # Multiple
3332

3433
# Local file or URL
35-
constellation = Constellation("satellites.tle")
36-
constellation = Constellation("https://example.com/tles.txt")
34+
positions = propagate("satellites.tle", times)
35+
positions = propagate("https://example.com/tles.txt", times)
3736

38-
# With metadata (name, norad_id, inclination, period, etc.)
39-
constellation = Constellation("starlink", with_metadata=True)
40-
for sat in constellation.metadata:
41-
print(f"{sat['name']}: {sat['inclination']:.1f}° inc, {sat['period']:.1f} min period")
37+
# For repeated propagation, pre-parse to avoid overhead
38+
c = Constellation("starlink")
39+
positions1 = propagate(c, times1)
40+
positions2 = propagate(c, times2)
4241
```
4342

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

4645
## Propagation
4746

4847
```python
49-
from astroz import Constellation
48+
from astroz import propagate, Constellation
5049
from datetime import datetime, timezone
5150
import numpy as np
5251

53-
constellation = Constellation("starlink")
52+
times = np.arange(1440) # 1 day at 1-min intervals
5453

5554
# Simple (defaults: now UTC, ECEF output)
56-
positions = constellation.propagate(np.arange(1440))
55+
positions = propagate("starlink", times)
5756

5857
# With options
59-
positions = constellation.propagate(
58+
positions = propagate(
59+
"starlink",
6060
np.arange(14 * 1440), # 2 weeks
6161
start_time=datetime(2024, 6, 1, tzinfo=timezone.utc),
6262
output="geodetic", # "ecef" (default), "teme", or "geodetic"
6363
)
6464

6565
# With velocities
66-
positions, velocities = constellation.propagate(np.arange(1440), velocities=True)
66+
positions, velocities = propagate("starlink", times, velocities=True)
6767
```
6868

69-
## Single Satellite
69+
## Conjunction Screening
7070

7171
```python
72-
from astroz import Tle, Sgp4
72+
from astroz import screen
7373
import numpy as np
7474

75-
tle = Tle("""1 25544U 98067A 24127.82853009 .00015698 00000+0 27310-3 0 9995
76-
2 25544 51.6393 160.4574 0003580 140.6673 205.7250 15.50957674452123""")
75+
times = np.arange(1440)
7776

78-
sgp4 = Sgp4(tle)
79-
pos, vel = sgp4.propagate(30.0) # 30 min after epoch
80-
positions, velocities = sgp4.propagate_batch(np.arange(1440))
81-
```
82-
83-
## Collision Screening
84-
85-
```python
86-
from astroz import Constellation, coarse_screen, min_distances
87-
import numpy as np
88-
89-
constellation = Constellation("starlink")
90-
positions = constellation.propagate(np.arange(1440), output="teme")
77+
# Single target (fastest - uses fused propagate+screen, no full position array)
78+
min_dists, min_t_indices = screen("starlink", times, threshold=50.0, target=0)
79+
# Returns per-satellite minimum distance to target and time index
9180

92-
# Find pairs within 10km
93-
pairs, t_indices = coarse_screen(positions, threshold=10.0)
94-
95-
# Get exact minimum distances
96-
pairs_array = np.array(pairs, dtype=np.uint32)
97-
min_dists, min_times = min_distances(positions, pairs_array)
81+
# All-vs-all screening
82+
pairs, t_indices = screen("starlink", times, threshold=10.0)
83+
# Returns all conjunction events within threshold
9884
```
9985

10086
## Performance
10187

102-
| Constellation (13,478 sats × 1,440 steps) | Throughput |
88+
| Constellation (13,478 sats x 1,440 steps) | Throughput |
10389
|--------------------------------------------|------------|
10490
| 1 thread | 37.7M props/sec |
10591
| 16 threads | 303M props/sec |
10692

107-
*Benchmarked on AMD Ryzen 7 7840U with AVX512. The `Constellation` class automatically handles buffer reuse and warmup for optimal performance.*
93+
*Benchmarked on AMD Ryzen 7 7840U with AVX512.*
10894

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

111-
## Advanced: Pre-allocated Buffers
112-
113-
For maximum performance with repeated propagations, pre-allocate output buffers:
114-
115-
```python
116-
from astroz import Constellation
117-
import numpy as np
118-
119-
constellation = Constellation("starlink")
120-
times = np.arange(1440, dtype=np.float64)
121-
122-
# Pre-allocate buffer
123-
out = np.empty((len(times), constellation.num_satellites, 3), dtype=np.float64)
124-
positions = constellation.propagate(times, out=out)
125-
```
126-
127-
Note: The `Constellation` class automatically reuses buffers internally when the output shape matches previous calls.
128-
12997
## Building
13098

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

0 commit comments

Comments
 (0)