Skip to main content
Pyralysis logo

Pyralysis

PYthon Radio Astronomy anaLYSis and Image Synthesis

Simulate, optimize, and reconstruct — with a Python toolkit built for modern interferometry.


Pipeline Status codecov Documentation Status pre-commit License


PyPI Version Python Version PyPI Wheel PyPI Downloads Binder


GitLab stars GitLab forks GitLab open issues GitLab merge requests Docker


Dask Xarray NumPy Astropy Numba SciPy


pytest yapf isort Commitizen Read the Docs ReadMe


Why Pyralysis?

Whether you are prototyping a simulation, studying optimization for imaging, or pipelining large visibility sets, Pyralysis aims to meet you where you work: clear APIs, lazy evaluation where it matters, and documentation you can actually read.

  • Interferometric imaging and simulation in one coherent library.
  • Composable, flexible, and extensible — not the same thing (see below): plug pieces together, choose what fits your science, and add your own classes without forking the core.
  • Dask-friendly — large arrays and visibilities are handled as lazy, chunked work: Pyralysis builds a task graph instead of loading everything into RAM at once, then Dask runs those chunks in parallel on your laptop, a local cluster, or optional SLURM workers. You keep writing normal Python; scaling is mostly configuration, not a rewrite. Details: Pipelines and distributed Dask.
  • Documented user guides, API reference, and runnable examples — try them locally, on Binder, or from a full install (see below).

If you prefer to dive straight in, open the documentation on Read the Docs.


Flexible, composable, and extensible

These words get mixed up; in Pyralysis they mean different jobs:

Idea Meaning here
Composable Build a workflow from small, named pieces (objective terms, measurement operators, pipeline steps, I/O classes) instead of one black-box imager.
Flexible Choose among those pieces for your experiment — e.g. L1 vs TSV, nearest-neighbor vs degridding, components script vs pipeline, NumPy vs CuPy — without changing the rest of the stack.
Extensible Add your own piece (a new regularizer, sky model, injector, or Io backend) by subclassing / registering, without rewriting optimizers or pipelines.

The same ideas show up in scripts, notebooks, and pipelines.

You can… How (sketch) For more
Mix data fidelity + regularizers ObjectiveFunction + ChiSquared, L1Norm, TSV, … Objective functions, Regularization
Choose how visibilities are modeled NearestNeighbor, BilinearInterpolation, Degridding, … Measurement operator
Run explicit classes or a pipeline *_components.py vs *_pipeline.py Repository examples
Stay on CPU or move to GPU array_backend="cupy" — same imaging APIs Array backends
Scale with Dask Lazy chunked arrays; local or SLURM workers via pipeline / runtime helpers Pipelines and distributed Dask
Swap I/O and sky / noise models DaskMS / ZarrArray / FITS; injectors; sky sources I/O, Simulation

Show, don’t hide: adding or swapping a regularizer is a list change, not a fork of the optimizer. ChiSquared.model_visibility expects a measurement operator (e.g. BilinearInterpolation), not a separate model-visibility class:

from pyralysis.estimators import BilinearInterpolation
from pyralysis.optimization import ObjectiveFunction
from pyralysis.optimization.terms import ChiSquared, L1Norm, TSV

# MeasurementOperator subclass: image → model visibilities
model_visibility = BilinearInterpolation(input_data=dataset, image=image)

# Start simple…
terms = [
    ChiSquared(model_visibility=model_visibility, penalization_factor=1.0),
    L1Norm(penalization_factor=0.01),
]
# …or swap / extend without touching L-BFGS / FISTA wiring:
terms = [
    ChiSquared(model_visibility=model_visibility, penalization_factor=1.0),
    TSV(penalization_factor=0.001),  # replace L1, or append both
]
objective = ObjectiveFunction(terms=terms, parameter=image)

Two ways to structure a workflow (same science, different abstraction — flexibility of how you orchestrate):

  • Components — you wire Dataset, operator, ObjectiveFunction, optimizer by hand (examples/scripts/*_components.py).
  • Pipelines — step + context orchestration (examples/scripts/*_pipeline.py).

Motivation and architecture (OOP, why not a monolith): Why Pyralysis?. Hands-on walkthroughs: Quickstart · Examples · Binder.


Try Pyralysis in your browser (Binder)

Binder builds a short-lived JupyterLab session with Pyralysis checked out from the release branch, so you can run the project without installing anything on your machine. After the environment starts, open the file browser and work through the notebooks under examples/notebooks/ — small simulations, toy datasets, and walkthroughs that mirror the written guides.

Binder is ideal when you want to peek at the API, follow a tutorial cell by cell, or share a reproducible link with a colleague. Sessions run on shared infrastructure with finite RAM and CPU, and the image tracks the release branch (not every commit on development), so treat it as exploration and teaching, not a substitute for HPC or production-scale imaging. For install paths, SLURM-backed Dask, and heavier workflows, use a local or cluster environment (see Install below) and the repository examples page on Read the Docs.

Launch: Open Pyralysis on Binder (same target as the Binder badge above).


Install in three steps

Stable releases live on PyPI (pin pyralysis==X.Y.Z when you need a reproducible version). For commits that are not yet released, install from GitLab (see the installation guide).

  1. Create a Python environment (Python 3.11–3.12; requires-python is >=3.11,<3.13). Use conda, mamba, micromamba, or plain venv — whatever fits your stack.

  2. Install from PyPI using the SKA extra index (needed so pip can resolve some dependencies).

pip install --extra-index-url https://artefact.skao.int/repository/pypi-internal/simple pyralysis[all]

This pulls the latest published release plus common optional pieces (FFT helpers, notebooks, tests). For GPU, prefer micromamba create -f environment_cuda13.yml (conda CUDA + CuPy, then pip install -e .); Pascal: environment_cuda12.yml. Pip-only CuPy wheels: pyralysis[cupy13] or [cupy12] — see the installation guide. For SLURM-backed Dask workers (dask-jobqueue, used when SetupDaskCluster uses dask_cluster_backend="slurm"), add pyralysis[slurm] (see Optional: SLURM).

Simulation and imaging pipelines can attach a local cluster, SLURM, or an existing Dask Client via context configuration — see Pipelines and distributed Dask.

For editable installs, conda environment files, or Docker images:

Want to explore first without pip? Use Binder above.

GPU imaging (optional; recommended for large images and datasets)

CUDA / CuPy is optional for small CPU workflows. For large measurement sets or large images, GPU imaging is recommended when a CUDA-capable environment is available.

With a CUDA-capable environment (environment_cuda13.yml / environment_cuda12.yml, or pyralysis[cupy13]), you can keep the same imaging APIs while visibilities live on CuPy-backed Dask collections:

from pyralysis.io import DaskMS

dataset = DaskMS("observation.ms").read(
    array_backend="cupy",
    calculate_psf=True,
)
dataset.calculate_theoretical_noise(per_field=True, per_spw=True)
# Forward model, Mask(dataset=...), ObjectiveFunction, optimizers — same as CPU.

The pipeline follows the measurement set: a CPU image is promoted to GPU during transform() / gradients. Simulation on GPU is not supported yet — simulate on CPU, then with_array_backend(..., "cupy") if needed.

  • Guide: Array backends
  • Notebook: examples/notebooks/optimization_sandbox_gpu.ipynb (masked χ² + LBFGS on a real MS)

Minimal example

Simulate a small dataset and add thermal noise:

from astropy import units as u

from pyralysis.io.antenna_config_io import AntennaConfigurationIo
from pyralysis.simulation import Simulator
from pyralysis.models.sky import PointSource
from pyralysis.injectors import ThermalNoiseInjector

# Load array configuration (packaged CFGs live under
# src/pyralysis/simulation/antenna_configs/, e.g. vla.c.cfg)
interferometer = AntennaConfigurationIo(input_name="path/to/array.cfg").read()
interferometer.configure_observation(
    min_frequency=1e9 * u.Hz,
    max_frequency=1.1e9 * u.Hz,
    frequency_step=1e7 * u.Hz,
    right_ascension="12h00m00s",
    declination="45d00m00s",
    integration_time=10 * u.s,
    observation_time="1h",
)

# Define a source and simulate
source = PointSource(
    reference_intensity=1.0 * u.Jy,
    sky_position="12h00m00s 45d00m00s",
    reference_frequency=1e9 * u.Hz,
)
sim = Simulator(interferometer=interferometer, sources=source)
dataset = sim.simulate(create_dataset=True)

# Add thermal noise
thermal = ThermalNoiseInjector(
    system_temperature=50,  # K
    integration_time=10,  # s
    channel_bandwidth=1e6,  # Hz
)
noisy_dataset = thermal.apply(dataset)

More simulation patterns (arrays, sky models, injectors, I/O):

Reconstruction example

Reconstruct an image from visibilities with a measurement operator, objective, and L-BFGS. ChiSquared takes a MeasurementOperator subclass (here BilinearInterpolation); there is no ModelVisibility class:

import dask.array as da
import numpy as np
import xarray as xr

from pyralysis.estimators import BilinearInterpolation
from pyralysis.optimization import ObjectiveFunction
from pyralysis.optimization.linesearch import BacktrackingArmijo
from pyralysis.optimization.optimizer import LBFGS
from pyralysis.optimization.terms import ChiSquared, L1Norm
from pyralysis.reconstruction import Image

# Image size / cellsize should follow the dataset (beam ~ theo_resolution)
imsize = 128
cellsize = noisy_dataset.theo_resolution / 3.5
image = Image(
    data=xr.DataArray(
        da.zeros((imsize, imsize), chunks=(64, 64), dtype=np.float32),
        dims=["x", "y"],
    ),
    cellsize=cellsize,
)

# Forward model: image → MODEL_DATA at observed UV coordinates
model_visibility = BilinearInterpolation(
    input_data=noisy_dataset,
    image=image,
)

terms = [
    ChiSquared(model_visibility=model_visibility, normalize=True),
    L1Norm(penalization_factor=0.01),
]
objective = ObjectiveFunction(terms=terms, parameter=image, persist_gradient=True)

linesearch = BacktrackingArmijo(objective_function=objective)
optimizer = LBFGS(
    objective_function=objective,
    parameter=image,
    linesearch=linesearch,
    max_iter=10,
)
reconstructed_image = optimizer.optimize()

Deeper reading:


Examples in this repository

Location What you will find
examples/notebooks/ Jupyter notebooks (CPU and GPU optimization sandboxes)
examples/notebooks/optimization_sandbox_gpu.ipynb CuPy MS read, mask, and masked optimization on HD142527
examples/images/ Demo + curated ngVLA FITS sky models (NonParametricSource)
examples/scripts/*_components.py Explicit class composition
examples/scripts/*_pipeline.py Pipeline-style orchestration

Paired scripts include dirtymapper, optimization, and simulation (components vs pipeline variants).


Learn more

Topic Link
User guide (home) pyralysis.readthedocs.io
Why / architecture (flexible · composable · extensible) context
Compose objectives and regularizers objective_functions, regularization
Components vs pipelines (examples) examples
NumPy / CuPy array backends (GPU imaging) array_backend
Pipelines and distributed Dask pipelines_distributed
I/O and simulation io_operations, simulation
Data model and measurement operator data_model, measurement_operator
Binder (JupyterLab, no install) Launch Binder
API reference api/index
Versioning policy versioning

Contributing and development

Resource Link
Contribution guide CONTRIBUTING.md
Changelog CHANGELOG.md
New issue Open an issue
Issue tracker GitLab issues
Testing and QA Testing docs
Versioning and releases Versioning

Pull requests and bug reports are welcome. If you are unsure where to start, open an issue and we can point you to the right part of the codebase.


Citation and license

If Pyralysis supports your research, a citation is appreciated:

@software{carcamo2021pyralysis,
  author = {Miguel Cárcamo},
  title = {Pyralysis: A Python framework for radio interferometric imaging and simulation},
  year = {2021},
  url = {https://gitlab.com/clirai/pyralysis},
  note = {https://pyralysis.readthedocs.io/}
}

Pyralysis is distributed under the GNU General Public License v3.0; see the LICENSE file in this repository.


Contact

Download files

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

Source Distribution

pyralysis-2.6.0.tar.gz (48.4 MB view details)

Uploaded Source

Built Distribution

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

pyralysis-2.6.0-py3-none-any.whl (1.1 MB view details)

Uploaded Python 3

File details

Details for the file pyralysis-2.6.0.tar.gz.

File metadata

  • Download URL: pyralysis-2.6.0.tar.gz
  • Upload date:
  • Size: 48.4 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.11.15

File hashes

Hashes for pyralysis-2.6.0.tar.gz
Algorithm Hash digest
SHA256 d25e72b3c48e60c73899c73bf37233dcad4028d418360b2c635c5816088344d6
MD5 f8efa70d42997a773f9eced499fe367c
BLAKE2b-256 80d187330976efddd137526a9fd43da94c6f87b1fa15afcf040053d0abd1a9fb

See more details on using hashes here.

File details

Details for the file pyralysis-2.6.0-py3-none-any.whl.

File metadata

  • Download URL: pyralysis-2.6.0-py3-none-any.whl
  • Upload date:
  • Size: 1.1 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.11.15

File hashes

Hashes for pyralysis-2.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a1f3fef1bfc0baf1e8608bdcdea7e9be1632f5d8456c786ead7ff60440dc632d
MD5 5780360fdb6176bda83523f8c19c77e8
BLAKE2b-256 199a92fe45ff6836274892f80127d355651b6a922bf9501a563d85802f662256

See more details on using hashes here.

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