Install¶
Requirements¶
Python ≥ 3.11, jax ≥ 0.5, equinox ≥ 0.13. Distribution and import name are
the same: rheplicant.
Install¶
pip install rheplicant
limTOD comes with it. It is a dependency, not an extra — the sky engines
it carries are the forward model rather than an accessory — and since limTOD
1.10.0 reached PyPI the >= 1.10 floor resolves with no preparatory step.
Extras¶
Two of the integration extras name a requirement that is not on PyPI,
deliberately: the
package is developed alongside them and pinning a git URL in pyproject.toml
would make this project unpublishable. So the extra records what is needed,
and you install it yourself.
Extra |
Gives you |
Install |
|---|---|---|
|
NUTS, and every gradient posterior |
|
|
|
|
|
|
|
|
|
|
|
Read pyuvdata |
|
|
The packaged FastAPI + React configuration workbench and
|
|
Start the configuration workbench¶
The gui extra contains the production assets, so an installed wheel needs no
Node.js toolchain:
pip install "rheplicant[gui]"
rheplicant-gui # http://127.0.0.1:8000/
The launcher binds to loopback by default. A non-loopback bind is refused
unless --allow-remote is explicit, and that flag is only acknowledgement:
the application has no authentication, tenant isolation or sandbox. YAML may
load plugins and python: targets; resource/output fields are server paths;
jobs use the server account’s files and compute. Read the complete
workbench workflow and trust boundary before using remote
access or running a document from another person.
Development¶
git clone https://github.com/RHINO-Experiment/rheplicant
cd rheplicant
uv venv
uv pip install -e . --group dev
Warning
Neither uv sync nor uv run works in this project — with --frozen or
without it. Measured on a fresh clone, not assumed.
Without --frozen, each refuses with “your project’s requirements are
unsatisfiable”. uv resolves every declared extra when it locks, and
rheplicant[cal] → rhino-cal-jax is not on PyPI by design; rheplicant[rfi]
→ MomentRFI is the same shape. The pyproject.toml comment beside each says
why they name a requirement instead of resolving it.
With --frozen, each refuses with “Unable to find lockfile at uv.lock”.
This repository ships no lockfile and cannot: uv lock fails on the very
same unsatisfiable extra, so there is nothing to commit. --frozen applies only
where a uv.lock already exists in your working copy — which a fresh clone does
not have and cannot generate. Treat any uv.lock you find in an older checkout
as stale rather than as the missing piece.
uv pip install resolves only what you ask it for, never the whole declared
universe, which is why the two commands above work where the project-level ones
cannot. Nothing is removed either, so editable local checkouts of limTOD or
rhino-cal survive it — install those the usual way afterwards, in any order.
Optional local data¶
Two things this project compares itself against cannot be published: the RHINO
CST far-field exports, and the rhino-cal checkout whose numpy readers are the
reference implementation. Neither has a path that can be guessed, so neither is
guessed — you name yours, or the work that needs it stands down and says so.
Variable |
What it unlocks |
|---|---|
|
The directory of per-frequency CST far-field exports. Unlocks the beam tests
in |
|
A |
Neither is required, and nothing fails without them — the tests that need them skip with the variable named in the reason. Both used to be hard-coded paths under one person’s home directory, which meant those tests never ran anywhere else and said nothing about it.
Running the tests¶
There is CI — .github/workflows/test.yml has run the suite on every push
since d65785b (2026-08-24). Run the suite and the linter in the project venv
anyway before pushing: the runner is Linux and this is often not, and the
difference is not academic — several numeric assertions in this suite are
currently red there and green here.
The dev group carries what the tests need on top of the package — numpyro,
because five modules import it unguarded, and mdit-py-plugins, because the
docs-link guard computes anchors with myst’s own slugifier. Both are runtime
extras rather than dependencies, so nothing but the test suite pulls them in;
without them pytest stops at collection, or reports every documentation link
as broken.
.venv/bin/python -m pytest # ~12 min with coverage
.venv/bin/python -m pytest -n 16 -o addopts="" # the one to use while working
JAX_ENABLE_X64=1 .venv/bin/python -m pytest tests/evidence # the float64 half
.venv/bin/python -m ruff check src tests
The second line is the one to use while working. pytest-xdist is in the dev
group and the run is identical — same counts — because nothing here depends on
execution order.
How many workers, and why the number stops mattering. Measured on a
28-core machine: -n 8 183 s, -n 16 146 s, -n 24 150 s. The knee is
around 16 and past it the workers contend rather than help, so -n auto on a
big box is slower than picking a number. On a smaller machine use the core
count; the suite does not care.
What sets the floor is one test, not the worker count. tests/evidence/ runs
as a second pytest session with JAX_ENABLE_X64=1 (see the admonition above),
and inside it
test_chain_conditioning.py::test_the_accumulated_fisher_plus_the_prior_is_positive_definite[5.0-1000]
alone takes ~49 s. That session is itself parallelised — four workers, chosen
because 8 measured no faster and the parent is already an -n session — which
took it from 175 s to 78 s and the whole suite from 291 s to 146 s. Nothing
short of shrinking that one test moves the floor further.
Why parallel rather than fewer tests
The suite is not slow because it is large. Of a 692 s serial run, the four heaviest items account for 249 s; the other ~2700 tests come to roughly 290 s between them, a tenth of a second each. Deleting tests therefore buys almost nothing and costs coverage — and most of these tests are regression evidence for a specific failure, which is the last thing to trade for a minute.
The four are worth knowing, because they are where a slowdown would show up: the float64 subsession (159 s — a whole second pytest run, and the floor on any parallel time, since one worker must carry it alone), the beam-spill closure at two resolutions (70 s), the NPE training (28 s), and the tour executed as a script (16 s). Each verifies something nothing else can.
The second line is not optional work you might skip — plain pytest already
runs it for you, in a subprocess, via tests/test_evidence_session.py. It is
written out because that is how you run those tests directly when one of them
fails.
Why the suite is two sessions
The evidence layer needs float64: a stored factor’s offset scalar is the
time–bandwidth product, ~7.2e11 for one night, against a difference of ~1e5 —
which float32 annihilates rather than rounds. The rest of the suite must stay at
float32, because a population of tests assert refusals that only float32 forces
— tests/test_evidence_session.py names them and the command that reproduces
them. And
jax_enable_x64 is process-global, so the two cannot share an interpreter.
That split is also why the reported coverage is what it is: the second session
runs --no-cov in its own process, so its passing tests contribute nothing to
the default report, and most of the default report’s uncovered statements are
the seven evidence-layer files.
Check it worked¶
.venv/bin/python -c "import rheplicant; print(rheplicant.__version__)"
.venv/bin/python -c "from rheplicant.radio import RADIO_GRAPH; print(len(RADIO_GRAPH.nodes), 'nodes')"
The interpreter is named explicitly because nothing above activates the
environment — a bare python here reaches whichever one is on your PATH and
reports ModuleNotFoundError for an install that is in fact fine. Drop the
prefix if you have run source .venv/bin/activate.
The second line is the more useful one: it proves the radio layer imported and
the default signal-path template registered. If the extras are in place, these
import too — each is the module an operator reaches for, and an absent one
raises an ImportError naming what to install rather than failing later:
.venv/bin/python -c "import limtod_jax, rhino_cal_jax; print('sky engines and noise waves ready')"
Then read the guided tour, or ingestion if you have a recording in hand.