+
Skip to content

pyOutfit is a Python binding for Outfit, a high-performance Rust orbit determination software. It brings fast Initial Orbit Determination, orbital element conversions, and multi-format astrometric data ingestion directly into Python.

License

Notifications You must be signed in to change notification settings

FusRoman/pyOutfit

Repository files navigation

pyOutfit

High-performance Python bindings for the Outfit orbit-determination engine (Initial Orbit Determination, observation ingestion, orbital element conversions & batch processing) powered by Rust + PyO3.

pyOutfit
CI PyPI version Documentation Python 3.12 Build (maturin) License: CeCILL-C

Upstream Outfit (Rust core)
crates.io docs.rs MSRV

✨ Overview

pyOutfit exposes the Rust Outfit crate to Python with a thin, typed interface. It enables:

  • Gauss-based Initial Orbit Determination (IOD) with configurable numerical & physical filters.
  • Manipulation of multiple orbital element representations (Keplerian, Equinoctial, Cometary).
  • Efficient ingest of astrometric observations (single trajectories or large batches) with zero-copy / single-conversion paths.
  • Parallel batch processing for thousands of trajectories (opt-in).
  • Access & registration of observatories (MPC code lookup & custom definitions).

Rust performs all heavy numerical work; Python orchestrates workflows with minimal overhead.

🔍 Feature Highlights

Area Highlights
IOD Gauss method with configurable solver tolerances & physical filters
Elements Keplerian / Equinoctial / Cometary conversions & wrappers
Observations NumPy ingestion in radians or degrees (with automatic conversion)
Performance Optional parallel batches, detached GIL region for compute-heavy steps
Safety Rust error types mapped to Python RuntimeError (idiomatic try/except)
Extensibility Builder pattern for IODParams & ergonomic container types

🚀 Quick Start

# (Recommended) Create & activate a virtual environment first
python3.12 -m venv .venv
source .venv/bin/activate

# Install build backend (only needed for local builds)
pip install --upgrade pip maturin

# Build and install the extension in development mode
maturin develop

Verify the module loads:

python -c "import py_outfit; print('Classes:', [c for c in dir(py_outfit) if c[0].isupper()])"

📦 Installation Options

Until wheels are published on PyPI, build from source:

git clone <this-repo-url>
cd pyOutfit
pip install maturin
maturin develop  # or: maturin build --release && pip install target/wheels/py_outfit-*.whl

System requirements:

  • Python 3.12 (matching the pyproject.toml requirement)
  • Rust toolchain (≥ 1.82)
  • C toolchain (e.g. build-essential on Debian/Ubuntu)

Example (Debian/Ubuntu):

sudo apt update
sudo apt install -y build-essential python3.12-dev pkg-config libssl-dev

Install Rust if needed: https://rustup.rs

🧪 Minimal End‑to‑End Example

Below: create an environment, register an observer, ingest synthetic observations, configure Gauss IOD, and estimate orbits.

# --8<-- [start: env_init]
from py_outfit import PyOutfit
env = PyOutfit("horizon:DE440", "FCCT14") # (ephemerides_id, error_model)
# --8<-- [end: env_init]
# --8<-- [start: observer_init]
from py_outfit import Observer
observer = Observer(
longitude=12.345, # degrees East
latitude=-5.0, # degrees North
elevation=1.5, # kilometers
name="DemoSite",
ra_accuracy=None,
dec_accuracy=None,
)
env.add_observer(observer)
# Alternatively using MPC code:
# observer = env.get_observer_from_mpc_code("I41")
# --8<-- [end: observer_init]
# --8<-- [start: minimal_data]
import numpy as np
trajectory_id = np.array([0, 0, 0], dtype=np.uint32) # single trajectory with ID 0
ra_deg = np.array(
[
20.9191548,
19.8927380,
18.2218784,
]
) # Right Ascension in degrees
dec_deg = np.array(
[
20.0550441,
20.2977473,
20.7096409,
]
) # Declination in degrees
mjd_tt = np.array(
[
58789.13709704,
58793.19047664,
58801.28714334,
]
) # Observation epochs (TT)
err_ra_arcsec = 0.5 # uniform RA uncertainty
err_dec_arcsec = 0.5 # uniform Dec uncertainty
# --8<-- [end: minimal_data]
# --8<-- [start: build_trajectoryset]
from py_outfit import TrajectorySet
traj_set = TrajectorySet.from_numpy_degrees(
env,
trajectory_id,
ra_deg.astype(float),
dec_deg.astype(float),
float(err_ra_arcsec),
float(err_dec_arcsec),
mjd_tt.astype(float),
observer,
)
obs = traj_set[0] # Observations wrapper for trajectory ID 0
print("Number of observations:", len(obs))
# --8<-- [end: build_trajectoryset]
# --8<-- [start: configure_iodparams]
from py_outfit import IODParams
params = (
IODParams.builder()
.n_noise_realizations(0) # purely geometric Gauss solve
.max_triplets(50) # limit combinatorial expansion
.build()
)
# --8<-- [end: configure_iodparams]
# --8<-- [start: estimate_orbit]
gauss_result, rms = obs.estimate_best_orbit(env, params, seed=42)
print("RMS:", rms)
print("Elements family:", gauss_result.elements_type())
# --8<-- [end: estimate_orbit]
# --8<-- [start: inspect_results]
kep = gauss_result.keplerian()
if kep is not None:
print("Semi-major axis (AU):", kep.semi_major_axis)
print("Eccentricity:", kep.eccentricity)
print("Inclination (rad):", kep.inclination)
else:
eq = gauss_result.equinoctial()
if eq is not None:
print("Equinoctial h,k:", eq.h, eq.k)
else:
com = gauss_result.cometary()
print("Cometary perihelion distance (AU):", com.perihelion_distance)
# --8<-- [end: inspect_results]

🔧 Working with IODParams

# Request parallel execution when supported by the build
from py_outfit import IODParams
params = (
IODParams.builder()
.do_parallel() # advisory flag consumed by higher-level APIs
.batch_size(8) # number of trajectories to schedule at once
.build()
)
print(params.do_parallel, params.batch_size)

📊 Accessing Observations

# Pick the first key in this tiny example
key = ts.keys()[0]
obs_view = ts[key]
res, rms = obs_view.estimate_best_orbit(env, params, seed=123)

🗂 API Surface (Python Names)

Class / Function Purpose
PyOutfit Global environment (ephemerides, error model, observatory catalog)
Observer Observatory definition / MPC lookup handle
IODParams / IODParams.builder() IOD configuration (physical filters, solver tolerances, parallelism)
TrajectorySet Mapping-like container of trajectories (IDs → Observations)
Observations Read-only per-trajectory access + NumPy export
GaussResult Result wrapper (preliminary / corrected orbit + element extraction)
KeplerianElements, EquinoctialElements, CometaryElements Different orbital element families

⚙️ Performance Notes

  • Core numerical routines run in Rust without the Python GIL (py.detach).
  • Batch ingestion uses zero-copy (radian path) or a single conversion (degree path).
  • Parallel processing is opt-in via IODParams.builder().do_parallel() to avoid contention when working with small data.
  • Deterministic runs are achievable by passing a seed to TrajectorySet.estimate_all_orbits.
  • Error propagation: all OutfitError variants surface as Python RuntimeError with descriptive messages.

🧭 Error Handling Pattern

try:
	env = PyOutfit("horizon:DE440", "VFCC17")
except RuntimeError as e:
	print("Failed to init environment:", e)

🧑‍💻 Development Workflow

# 1. (one time) Setup
pip install maturin pytest

# 2. Rebuild after Rust changes
maturin develop

# 3. Run Python tests
pytest -q

# 4. Optional: run Rust unit tests (if added)
cargo test

Project Layout

src/                 # Rust sources (PyO3 classes & bindings)
py_outfit/           # Generated Python package (stub .pyi + compiled extension)
tests/               # Python tests (pytest)
Cargo.toml           # Rust crate metadata
pyproject.toml       # Python build config (maturin backend)

🤝 Contributing

Contributions are welcome:

  1. Fork & create a feature branch.
  2. Add tests (Python or Rust) for new behavior.
  3. Keep public Python API backwards compatible when possible.
  4. Run pytest before opening a PR.

Feel free to open an issue for design discussions first.

📄 License

Distributed under the CeCILL-C license. See LICENSE for the full text.

🙌 Acknowledgements

Built on top of the Rust Outfit crate and the PyO3 + maturin ecosystem.


Questions, ideas, or issues? Open an issue or start a discussion – happy to help.

About

pyOutfit is a Python binding for Outfit, a high-performance Rust orbit determination software. It brings fast Initial Orbit Determination, orbital element conversions, and multi-format astrometric data ingestion directly into Python.

Resources

License

Stars

Watchers

Forks

Packages

No packages published
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载