libstempo is a Python wrapper around the tempo2 pulsar timing package.
libstempo is installed most simply via conda as the tempo dependency
is bundled in the conda recipe. Simply use
conda install -c conda-forge libstempoTo use libstempo with pip (or from source), tempo2 must be installed as a prerequisite. Currently there are two recommended methods to do this.
- Install via script.
This will install the tempo2 library in a local directory (
curl -sSL https://raw.githubusercontent.com/vallis/libstempo/master/install_tempo2.sh | sh$HOME/.local). This method is recommended if you do not need to use tempo2 directly but just need the installation forlibstempo. You can also set the path to the install location. For example, to install in/usr/local, you could run:# need sudo if installing in a restricted location curl -sSL https://raw.githubusercontent.com/vallis/libstempo/master/install_tempo2.sh | sudo sh -s /usr/local
- Install via the instructions on the tempo2 homepage. If this method is used, the
TEMPO2environment variable will need to be set to uselibstempo.
In either case, it is best practice to set the TEMPO2 environment
variable so that it can be easily discovered by libstempo.
The libstempo package can be installed via pip:
pip install libstempoTo use astropy for units:
pip install libstempo[astropy]If you have installed tempo2 in a location that is not in your path or not the default from install_tempo2.sh, you will need to install
libstempo with an environment variable (e.g. if tempo2 is in /opt/local/bin)
TEMPO2_PREFIX=/opt/local pip install libstempoor
export TEMPO2_PREFIX=/opt/local
pip install libstempoSee Demo Notebook 1 for basic usage and Demo Notebook 2 for simulation usage.
libstempo includes a sandbox mode that provides crash isolation and automatic retry capabilities. This is particularly useful when working with problematic pulsars or long-running analyses where tempo2 crashes are common.
The sandbox provides a drop-in replacement for the standard tempopulsar class:
from libstempo.sandbox import tempopulsar
# Basic usage - same API as regular tempopulsar
psr = tempopulsar(parfile="J1713.par", timfile="J1713.tim", dofit=False)
residuals = psr.residuals()
design_matrix = psr.designmatrix()from libstempo.sandbox import tempopulsar, Policy, configure_logging
# Configure logging for debugging
configure_logging(level="DEBUG", log_file="tempo2.log")
# Configure retry and timeout policies
policy = Policy(
ctor_retry=5, # Retry constructor 5 times on failure
call_timeout_s=300.0, # 5-minute timeout per RPC call
max_calls_per_worker=1000, # Recycle worker after 1000 calls
max_age_s=3600, # Recycle worker after 1 hour
rss_soft_limit_mb=2048 # Recycle worker if memory exceeds 2GB
)
psr = tempopulsar(parfile="J1713.par", timfile="J1713.tim", policy=policy)The sandbox supports different Python environments:
# Use virtual/conda environment
psr = tempopulsar(parfile="J1713.par", timfile="J1713.tim", env_name="tempo2_intel")
# Use system Python with Rosetta (macOS)
psr = tempopulsar(parfile="J1713.par", timfile="J1713.tim", env_name="arch")
# Use explicit Python path
psr = tempopulsar(parfile="J1713.par", timfile="J1713.tim", env_name="python:/path/to/python")- Crash Isolation: Segfaults in tempo2 only kill the worker process, not your main kernel
- Automatic Retry: Built-in retry logic for transient failures
- Worker Recycling: Prevents memory leaks and resource accumulation
- Environment Flexibility: Support for conda, venv, and Rosetta environments
- Enhanced Logging: Comprehensive logging for debugging and monitoring
- Proactive TOA Handling: Automatically handles large TOA files to prevent "Too many TOAs" errors
The sandbox adds ~9x initialization overhead but only ~1.2x overhead for computational operations like residuals() and designmatrix(). For heavy computations, the overhead becomes negligible relative to the actual work. Use sandbox when stability is critical, direct libstempo when performance is paramount.
For processing many pulsars:
from libstempo.sandbox import load_many, Policy
pairs = [("J1713.par", "J1713.tim"), ("J1909.par", "J1909.tim"), ...]
policy = Policy(ctor_retry=3, call_timeout_s=120.0)
ok_by_name, retried_by_name, failed_list = load_many(pairs, policy=policy, parallel=8)
print(f"Successfully loaded: {len(ok_by_name)}")
print(f"Required retries: {len(retried_by_name)}")
print(f"Failed: {len(failed_list)}")The sandbox defines specific exception types:
from libstempo.sandbox import Tempo2Error, Tempo2Crashed, Tempo2Timeout
try:
psr = tempopulsar(parfile="problematic.par", timfile="problematic.tim")
except Tempo2Crashed:
print("Worker process crashed - likely a segfault")
except Tempo2Timeout:
print("Worker timed out")
except Tempo2Error as e:
print(f"Sandbox error: {e}")