Skip to main content

Rust + PyO3 implementation of the Izzo Lambert solver

Project description

lambert_rs

High-performance Rust + PyO3 implementation of the Izzo Lambert solver with advanced optimization capabilities.

Installation

From PyPI

pip install lambert-rs

Features

  • Fast Lambert Problem Solving: Vectorized batch processing with parallel execution
  • Multiple Solution Methods: Single solutions, grid search, and Nelder-Mead optimization
  • Delta-V Calculations: Automatic computation of transfer impulses
  • Two-Body Propagation: Universal variable formulation for orbit propagation
  • Transfer Optimization: Find optimal departure/arrival times minimizing delta-v
  • Rendezvous Optimization: Optimize for full rendezvous (two burns) or transfer only (one burn)

Quick Start

Basic Lambert Problem

Solve a single Lambert problem:

import numpy as np
import lambert_rs

# Initial and final positions (km)
r1 = np.array([7000., 0., 0.])
r2 = np.array([0., -8000., 0.])

# Time of flight (seconds)
tof = 3600.0

# Solve Lambert problem
v1_list, v2_list, valid = lambert_rs.lambert_izzo_single(
    r1, r2, tof,
    max_rev=0,        # Maximum number of revolutions
    retrograde=False, # Prograde transfer
    mu=398600.4418,  # Earth's gravitational parameter (km^3/s^2)
    tol=1e-8,        # Tolerance
    maxiter=50       # Maximum iterations
)

# Get the first valid solution
if valid[0]:
    v1 = v1_list[0]
    v2 = v2_list[0]
    print(f"v1: {v1} km/s")
    print(f"v2: {v2} km/s")

Batch Processing

Process multiple Lambert problems in parallel:

import numpy as np
import lambert_rs

# Multiple position pairs: shape (N, 3)
r1_batch = np.array([
    [7000., 0., 0.],
    [8000., 0., 0.],
    [9000., 0., 0.],
])

r2_batch = np.array([
    [0., -8000., 0.],
    [0., -9000., 0.],
    [0., -10000., 0.],
])

# Multiple time-of-flight values
tof_array = np.array([3600., 7200., 10800.])

# Solve all combinations
v1_all, v2_all, valid_all = lambert_rs.lambert_izzo_vec(
    r1_batch, r2_batch, tof_array,
    max_rev=0,
    retrograde=False,
    mu=398600.4418,
    tol=1e-8,
    maxiter=50
)

# Results shape: (N, num_tof, num_solutions, 3)
print(f"v1_all shape: {v1_all.shape}")
print(f"v2_all shape: {v2_all.shape}")

Delta-V Calculations

Calculate transfer impulses for rendezvous:

import numpy as np
import lambert_rs

# Initial and target states: [x, y, z, vx, vy, vz]
s1 = np.array([7000., 0., 0., 0., 7.5, 0.])  # Initial state
s2 = np.array([0., -8000., 0., 0., 8.0, 0.])  # Target state

# Time of flight
tof = 3600.0

# Calculate delta-v for all solutions
dv1_mag, dv2_mag, dv_total, valid, dv1_vec, dv2_vec = lambert_rs.lambert_izzo_vec_dv(
    s1, s2, tof,
    max_rev=0,
    retrograde=False,
    mu=398600.4418,
    tol=1e-8,
    maxiter=50
)

# Find best solution (minimum total delta-v)
if np.any(valid):
    best_idx = np.nanargmin(dv_total[valid])
    print(f"Best delta-v: {dv_total[best_idx]:.3f} km/s")
    print(f"First burn: {dv1_vec[best_idx]} km/s")
    print(f"Second burn: {dv2_vec[best_idx]} km/s")

Find Best Delta-V Solution

Automatically find the best solution (prograde or retrograde):

import numpy as np
import lambert_rs

# States: shape (..., 6) for batch processing
s1 = np.array([7000., 0., 0., 0., 7.5, 0.])
s2 = np.array([0., -8000., 0., 0., 8.0, 0.])

# Time of flight (can be scalar or array)
tof = np.array([3600., 7200., 10800.])

# Full rendezvous: minimize total delta-v (dv1 + dv2)
# Use this when you need to match both position AND velocity at the target
dv1_rend, dv2_rend, dv_total_rend, valid_rend, dv1_vec_rend, dv2_vec_rend = lambert_rs.lambert_izzo_best_dv(
    s1, s2, tof,
    max_rev=0,
    mu=398600.4418,
    tol=1e-8,
    maxiter=50,
    rendezvous=True  # Default: minimize dv1 + dv2
)

# Transfer-only: minimize first impulse (dv1 only)
# Use this when you only need to reach the target position (not matching velocity)
dv1_trans, dv2_trans, dv_total_trans, valid_trans, dv1_vec_trans, dv2_vec_trans = lambert_rs.lambert_izzo_best_dv(
    s1, s2, tof,
    max_rev=0,
    mu=398600.4418,
    tol=1e-8,
    maxiter=50,
    rendezvous=False  # Minimize dv1 only
)

# Automatically selects best solution (prograde or retrograde)
print(f"Rendezvous total delta-v: {dv_total_rend} km/s")
print(f"Transfer-only total delta-v: {dv1_trans} km/s")

Two-Body Orbit Propagation

Propagate an orbit using universal variable formulation:

import numpy as np
import lambert_rs

# Initial state: [x, y, z, vx, vy, vz] (km, km/s)
initial_state = np.array([7000., 0., 0., 0., 7.5, 0.])

# Propagation time (seconds)
dt = 3600.0

# Propagate
r_final, v_final = lambert_rs.kepler_universal_2body_py(initial_state, dt)

print(f"Final position: {r_final} km")
print(f"Final velocity: {v_final} km/s")

Transfer Optimization (Grid Search)

Find optimal departure and arrival times using grid search:

import numpy as np
import lambert_rs

# Source and target initial states
source_state = np.array([7000., 0., 0., 0., 7.5, 0.])
target_state = np.array([0., -8000., 0., 0., 8.0, 0.])

# Time bounds: (min_departure, max_arrival) in seconds
time_bounds = (0., 86400.)  # 24 hours
dt_step = 3600.0  # 1 hour steps

# Find optimal transfer
best_t0, best_tf, best_dv = lambert_rs.optimize_lambert_transfer_py(
    source_state, target_state,
    time_bounds, dt_step,
    mu=398600.4418,
    retrograde=False
)

print(f"Optimal departure time: {best_t0:.1f} s")
print(f"Optimal arrival time: {best_tf:.1f} s")
print(f"Minimum delta-v: {best_dv:.3f} km/s")

Transfer Grid (All Solutions)

Get all transfer solutions in a time window:

import numpy as np
import lambert_rs

source_state = np.array([7000., 0., 0., 0., 7.5, 0.])
target_state = np.array([0., -8000., 0., 0., 8.0, 0.])

time_bounds = (0., 86400.)
dt_step = 3600.0

# Get all solutions: shape (N, 3) where columns are [t0, tf, delta_v]
grid = lambert_rs.optimize_lambert_transfer_grid_py(
    source_state, target_state,
    time_bounds, dt_step,
    mu=398600.4418,
    retrograde=False
)

# Plot or analyze all solutions
import matplotlib.pyplot as plt
plt.scatter(grid[:, 0], grid[:, 1], c=grid[:, 2], cmap='viridis')
plt.colorbar(label='Delta-V (km/s)')
plt.xlabel('Departure Time (s)')
plt.ylabel('Arrival Time (s)')
plt.show()

Nelder-Mead Optimization

Use gradient-free optimization to find optimal transfer:

import numpy as np
import lambert_rs

source_state = np.array([7000., 0., 0., 0., 7.5, 0.])
target_state = np.array([0., -8000., 0., 0., 8.0, 0.])

time_bounds = (0., 86400.)

# Optimize for transfer (single burn)
# Automatically selects best solution from both prograde and retrograde
times, cost, dv1, dv2 = lambert_rs.optimize_lambert_nm_py(
    source_state, target_state,
    time_bounds,
    mu=398600.4418,
    max_rev=0,
    optimize_rendezvous=False,  # Transfer only (one burn)
    max_iters=1000,
    initial_guess=None  # Optional: (t0, tf) guess
)

print(f"Optimal times: t0={times[0]:.1f} s, tf={times[1]:.1f} s")
print(f"Cost (delta-v): {cost[0]:.3f} km/s")
print(f"First burn: {dv1} km/s")

# Optimize for rendezvous (two burns)
# Automatically selects best solution from both prograde and retrograde
times, cost, dv1, dv2 = lambert_rs.optimize_lambert_nm_py(
    source_state, target_state,
    time_bounds,
    mu=398600.4418,
    max_rev=0,
    optimize_rendezvous=True,  # Full rendezvous (two burns)
    max_iters=1000
)

print(f"Rendezvous cost: {cost[0]:.3f} km/s")
print(f"First burn: {dv1} km/s")
print(f"Second burn: {dv2} km/s")

Multi-Start Optimization

Find multiple local optima using parallel multi-start optimization:

import numpy as np
import lambert_rs

source_state = np.array([7000., 0., 0., 0., 7.5, 0.])
target_state = np.array([0., -8000., 0., 0., 8.0, 0.])

time_bounds = (0., 86400.)

# Run multiple optimizers in parallel
# Automatically selects best solution from both prograde and retrograde for each optimizer
times, costs, dv1_all, dv2_all = lambert_rs.optimize_lambert_nm_multi_py(
    source_state, target_state,
    time_bounds,
    mu=398600.4418,
    max_rev=0,
    optimize_rendezvous=True,
    num_solvers=10,        # Number of parallel optimizers
    return_best_only=False, # Return all solutions
    remove_duplicates=True, # Remove duplicate solutions
    max_iters=1000
)

# Analyze all solutions
print(f"Found {len(costs)} solutions")
print(f"Best cost: {np.min(costs):.3f} km/s")
print(f"Worst cost: {np.max(costs):.3f} km/s")

# Get the best solution
best_idx = np.argmin(costs)
print(f"\nBest solution:")
print(f"  Times: t0={times[best_idx, 0]:.1f} s, tf={times[best_idx, 1]:.1f} s")
print(f"  Cost: {costs[best_idx]:.3f} km/s")
print(f"  First burn: {dv1_all[best_idx]} km/s")
print(f"  Second burn: {dv2_all[best_idx]} km/s")

API Reference

Core Functions

  • lambert_izzo_single(r1, r2, tof, max_rev, retrograde, mu, tol, maxiter)

    • Solve a single Lambert problem
    • Returns: (v1_list, v2_list, valid)
  • lambert_izzo_vec(r1, r2, tof, max_rev, retrograde, mu, tol, maxiter)

    • Vectorized batch processing
    • Returns: (v1_all, v2_all, valid_all)
  • lambert_izzo_vec_dv(s1, s2, tof, max_rev, retrograde, mu, tol, maxiter)

    • Calculate delta-v for transfers
    • Returns: (dv1_mag, dv2_mag, dv_total, valid, dv1_vec, dv2_vec)
  • lambert_izzo_best_dv(s1, s2, tof, max_rev, mu, tol, maxiter, rendezvous)

    • Automatically find best solution (prograde or retrograde)
    • rendezvous=True (default): Minimize total delta-v (dv1 + dv2) for full rendezvous
    • rendezvous=False: Minimize first impulse (dv1) for transfer-only missions
    • Returns: (dv1_mag, dv2_mag, dv_total, valid, dv1_vec, dv2_vec)

Propagation

  • kepler_universal_2body_py(init_state_vec, dt_sec)
    • Two-body orbit propagation using universal variables
    • Returns: (r_final, v_final)

Optimization

  • optimize_lambert_transfer_py(source_state, target_state, time_bounds, dt_step, mu, retrograde)

    • Grid search for optimal transfer
    • Returns: (best_t0, best_tf, best_dv)
  • optimize_lambert_transfer_grid_py(source_state, target_state, time_bounds, dt_step, mu, retrograde)

    • Get all grid search solutions
    • Returns: grid array with columns [t0, tf, delta_v]
  • optimize_lambert_nm_py(source_state, target_state, time_bounds, mu, max_rev, optimize_rendezvous, max_iters, initial_guess)

    • Nelder-Mead optimization
    • Automatically selects best solution from both prograde and retrograde transfers
    • Returns: (times, cost, dv1, dv2)
  • optimize_lambert_nm_multi_py(source_state, target_state, time_bounds, mu, max_rev, optimize_rendezvous, num_solvers, return_best_only, remove_duplicates, max_iters)

    • Multi-start parallel optimization
    • Automatically selects best solution from both prograde and retrograde transfers for each optimizer
    • Returns: (times, costs, dv1_all, dv2_all)

Performance

  • Parallel Processing: Automatically uses N-1 CPU cores for batch operations
  • Vectorized Operations: Efficient NumPy array operations
  • Rust Performance: Core algorithms implemented in Rust for maximum speed

Units

  • Positions: kilometers (km)
  • Velocities: kilometers per second (km/s)
  • Time: seconds (s)
  • Gravitational Parameter (mu): km³/s²
    • Earth: 398600.4418 km³/s²

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

lambert_rs-0.0.5-cp38-abi3-win_amd64.whl (320.6 kB view details)

Uploaded CPython 3.8+Windows x86-64

lambert_rs-0.0.5-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (452.0 kB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ x86-64

lambert_rs-0.0.5-cp38-abi3-macosx_11_0_arm64.whl (438.5 kB view details)

Uploaded CPython 3.8+macOS 11.0+ ARM64

lambert_rs-0.0.5-cp38-abi3-macosx_10_12_x86_64.whl (450.0 kB view details)

Uploaded CPython 3.8+macOS 10.12+ x86-64

File details

Details for the file lambert_rs-0.0.5-cp38-abi3-win_amd64.whl.

File metadata

  • Download URL: lambert_rs-0.0.5-cp38-abi3-win_amd64.whl
  • Upload date:
  • Size: 320.6 kB
  • Tags: CPython 3.8+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for lambert_rs-0.0.5-cp38-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 7bdff3a3d97badd565b6708376b9cfc08cd87809e0b001f5fd71e4a85f3a8da0
MD5 767d3e313c83793503ca7eda2e37e2b9
BLAKE2b-256 b7ef0471c725dd5b74db150e1174d12259d252ecb10f405617df1cf15b16e6ec

See more details on using hashes here.

Provenance

The following attestation bundles were made for lambert_rs-0.0.5-cp38-abi3-win_amd64.whl:

Publisher: publish.yml on rengrub/lambert_rust

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file lambert_rs-0.0.5-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for lambert_rs-0.0.5-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 09a7786c1f2f46eb422829f13c36648e8a74e3cc1825e896bfb7dc7f22ac116c
MD5 908784ae38d3ab52cc9dd1a496e67c22
BLAKE2b-256 5e15a1c8cbf6fce0ed97484231050d0b2552bc11ead9231ae00ce9080b467446

See more details on using hashes here.

Provenance

The following attestation bundles were made for lambert_rs-0.0.5-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: publish.yml on rengrub/lambert_rust

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file lambert_rs-0.0.5-cp38-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for lambert_rs-0.0.5-cp38-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 82661d0a33fb4ed9951519aec53306b4041bab34fddea50d647060d80ccaab99
MD5 5fceaed32dacde8325a5e612d4a83d41
BLAKE2b-256 118b9eb47ec5910a60298412d5ffb5d8b7fd2616b350e7bdb6bc9818d0fd8eac

See more details on using hashes here.

Provenance

The following attestation bundles were made for lambert_rs-0.0.5-cp38-abi3-macosx_11_0_arm64.whl:

Publisher: publish.yml on rengrub/lambert_rust

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file lambert_rs-0.0.5-cp38-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for lambert_rs-0.0.5-cp38-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 a0ee5f84732676b38b41907ffbabe93ddc1f813f94e83ef96e3bf25f0217763f
MD5 291ce55da89cd3f1c13a1e880f16eeb4
BLAKE2b-256 464ebd542cee318466e5096502455f5163cae5192888fbda86f2bb1f85003925

See more details on using hashes here.

Provenance

The following attestation bundles were made for lambert_rs-0.0.5-cp38-abi3-macosx_10_12_x86_64.whl:

Publisher: publish.yml on rengrub/lambert_rust

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page