The command line¶
rietx is a small command with six subcommands. The package is API-first, so
the terminal gets only the jobs that are genuinely terminal-shaped: asking what
the cell of a pattern is, watching a running refinement, rendering a saved
result, launching the two browser tools, and putting the agent skill where a
harness will find it.
$ rietx --help
usage: rietx <command> [...]
commands:
gui [PROJECT.rex] [--scratch] [--port N] [--no-open]
the refinement GUI (localhost)
watch [dir] [--port N] [--open] [--read-only]
list and watch the runs under a directory
html <result.json> <out.html> render a saved RefinementResult to HTML
index <pattern> --wavelength A [...]
determine the unit cell of an unknown
phase (rietx index --help)
compare [--data DIR] [--port N] [--open]
browser UI comparing refinement
settings on the bundled standards
skill [--path | --print [SECTION] | --install [DIR]]
the agent skill: where it is, its
text, or install it into a repo
(rietx skill --help)
An unknown command exits 2. Nothing here refines a structure. A refinement is a sequence of decisions about a model, and a command line is the wrong shape for it. Use the API (A first refinement) or the GUI.
For agents
Prefer the Python API (Calling rietx from a program) over shelling out. The one exception is
rietx index, whose exit status is a contract: 0 when a cell reached the
confidence gate, 1 when the result abstains. That is the same statement the
diagnostics make, in the one channel a shell pipeline can branch on without
parsing.
rietx index: what is this cell?¶
The only subcommand that computes an answer rather than displaying one. It belongs in a terminal because “what is this?” is a question you ask about a file you have just collected.
$ rietx index corundum.prn --wavelength 1.540596 --systems trigonal --total-budget 45
It prints the candidate list and never one cell. IndexingResult has no
.cell, and the CLI does not invent one. The list, each candidate’s confidence
grade, and the caveats holding that grade down are the answer.
Indexing an unknown cell is what each of those means. The last block is the run’s
diagnostics, which is where a truncated search says so.
Option |
Is |
|---|---|
|
required, in Å, and a single line. For a lab Kα doublet build the instrument in python and call |
|
|
|
comma-separated crystal systems; the default is all seven. A restricted search reports what it did not cover rather than concluding anything about it |
|
comma-separated engines; the default is all of them, and |
|
the principal d-spacing bounds of the search domain. Domain size is what an exhaustive search pays for |
|
wall-clock seconds per (engine × system) slice |
|
wall-clock ceiling for the whole run, search and validation together, overriding the preset’s |
|
|
|
print the cost arithmetic for these options and exit without searching |
|
a measured systematic 2θ allowance in degrees. Without one the engines assume a value and cap every candidate’s confidence, because a cell found inside a widened window absorbs the shift |
|
skip the whole-profile Le Bail validation, which caps every candidate at |
|
also write the whole |
Anything the pattern reader repaired or assumed goes to stderr before the answer, because a reversed scan or a dropped duplicate changes every number under it.
Ask what it will cost before you run it¶
--ceiling answers that from the options alone, and it separates the arithmetic
from the measurement, because a worst case is not an estimate:
$ rietx index corundum.prn --wavelength 1.540596 --ceiling
worst case: 1518 s (search 630 + probe 360, arithmetic on the per-system budgets;
+ validation 12 fits x 0.6-44 s, a measured range — Le Bail cost is data-dependent)
measured typical: 4-440 s per real dataset (searches finish their systems early far
more often than not)
a --total-budget binds within ~10 s (the longest uninterruptible stretch)
The gap between 1518 s and “4 to 440 s typical” is the point. The worst case is
what the budgets permit rather than what a real dataset costs. Narrowing --systems
to where the answer can live costs exponentially less than more time buys.
Reading the exit status¶
$ rietx index corundum.prn --wavelength 1.540596 --systems trigonal --total-budget 45
...
NO CELL: the result abstains — see the diagnostics below.
[warning] INDEX_ABSTAINED: no cell reached the confidence gate; the best
candidate (trigonal R, V = 127.5 ų) is low because of:
geometric_ambiguity, fom_panel_disagrees, not_validated,
indexed_fraction_low, search_incomplete, shift_allowance_assumed
$ echo $?
1
That run is an honest failure. The certified corundum cell is candidate 1 at
4.75950 Å, and the command still exits 1, because six caveats stand between it
and the gate. Exit 0 is the narrow claim “one candidate reached high with no
ambiguity partner”, and not “something was printed”.
rietx watch: a running refinement, live¶
$ rietx watch --port 8899 --open
With no directory it scans the working directory and lists every run beneath it, running and finished together. The list is one panel and the selected run is the other. The run panel carries a status line, the plot and the event console. With no run in the URL the page follows the newest run, so opening it beside an agent’s job shows what is happening now. Clicking a run pins it.
Two seams divide the page and both are yours to move. One runs between the list and the run, the other between the plot and the console. Drag a seam to size the panel beside it. Double-click it, or focus it and press Enter, to collapse that panel, and repeat the gesture to bring it back. A focused seam also takes the arrow keys, 16 px a press and ten times that with Shift, with Home and End for its two stops. The browser remembers both sizes and re-fits them to the window you next open in, so a width chosen on a wide screen does not leave a sliver on a narrow one.
A seam stops where the panel stops being readable. The list stops at the width its five declared columns need plus room for the run column’s own heading, 63 characters in all. The run panel keeps 340 px, below which the plot’s legend wraps to six rows and covers the top quarter of the picture.
The list has six columns. state is the liveness word below. run is what the
run is called: for a series member the pattern it fitted, otherwise the label.
The label is the word the caller passed as label=, and failing that the
project’s name or the directory the fit was launched from. stage
is the stage its writer last recorded, and Rwp and GoF the fit at that
point, Rwp as a percentage. started is a clock time, to the second for a run
started today and a date before that. Hovering a row gives the rest of the
record: the label, the run directory, the command line that launched it, and
where that was run.
An unnamed batch launched from one directory gives every run the same label, so
the second it started is what tells its rows apart. That is a fact about the
record rather than about the fit. Pass label= to the verb that starts the fit
and the row carries the work instead (Running a refinement).
The page holds still while the fit moves. The plot redraws in place as the fit writes each stage. Its 2θ and intensity axes are set by the pattern, so they change only when the data does, and a zoom survives a stage. The Δ/σ axis is symmetric and steps between fixed rungs (±3, ±5, ±10, ±20 and so on) as the residual tightens. The console tails the log from where it left off and follows it only while you are at the end. For a series the status line names the pattern being fitted, its pass and its stage.
The status line shows what the run panel is wide enough to hold, and it drops slots rather than cutting each of them a little. The GUI command and the free-parameter count go first, then the label, then the series. The state, the Rwp and the GoF are always there, and the stage shortens rather than going. Widening the window, dragging the seam, or collapsing the list brings the rest back.
Where the runs come from¶
Every fit writes one. Refinement.fit and its neighbours record a run directory
whether or not you asked, so rietx watch in the directory you are working in
usually has something to show. A fit that came from a project records into that
project’s live/; every other fit records under .rietx/runs/ in the working
directory.
Running a refinement is what a run holds and how to switch recording off.
A run is any directory holding an events.jsonl, which also covers a project’s
own live/ (Files and projects) and any directory you passed to a LiveSession
yourself. Pass one and the page opens straight onto that run instead of listing.
$ rietx watch ./my_sample.rex/live
What the liveness column means¶
A finished run says so. A run still being written is the interesting case, and
the watcher answers it from a lock the writing process holds for its life, which
the kernel releases however that process dies, kill -9 included.
Word |
Means |
|---|---|
|
a process holds the run’s lock |
|
the writer recorded its own last word |
|
the status says |
|
the question could not be answered here |
abandoned is a third answer and not a rounding of the other two. It is what a
killed process leaves behind, and it is the state you are looking for when a run
has stopped moving.
unknown arises three ways: the run was written on another host, so its pid
names one of our processes and not the writer’s; the lock is free and no state
was recorded, which is every run written before the recorder existed; or there
is no lock file to probe. A heartbeat age is reported beside all of this and
never decides it. A live process is evidence, and a clock is not.
Stopping a fit¶
A run that reads running has a stop button on its page. It takes two clicks,
and there is no keyboard shortcut for it. The confirmation says what is about to
happen in the other process.
The fit stops at its next residual evaluation. Its process sees
RefinementCancelled, the same exception it would have seen had it passed a
cancel= token of its own and set that. A script that does not catch the
exception prints a traceback and exits. Latency is one poll interval plus the
evaluation in flight. Measured on a 150-stage synthetic fit, the process exited
0.12 s after the request was written.
The stages that already finished are kept, and the working state stands at the
last of them. The stage in flight is abandoned. No history node is written for
it, no parameters are committed, and the structure and instrument go back to
where that stage found them. A cancelled run gets no summary.txt, because
there is no result to write one from.
Only a run being written on this machine can be stopped. A finished run, a run on another host, and a run whose writer holds no lock all refuse with 409. A request written into any of those would lie in the directory unread.
Afterwards the run reads cancelled, and its status.json carries
cancelled_by. A fit that its own caller stopped records the same state with no
cancelled_by. That field is the only place the two are told apart. The
exception does not distinguish them, and neither does the fit.
--read-only serves the same pages without the button:
$ rietx watch --read-only
rietx watch: 3 run(s) under /Users/yue/work/demo
http://127.0.0.1:8899/ (Ctrl-C to stop)
read-only: no stop button
The page draws no button, and the route refuses with 403.
The stop route also checks Origin and Referer, the way the GUI’s writing
routes do. A cross-origin POST needs no preflight, so without that check any
page open in another tab could stop a refinement, and a domain whose DNS
answers 127.0.0.1 could read the run ids first. A same-origin fetch sends no
Origin and a command-line client sends none either, so curl -X POST against
127.0.0.1 works unchanged.
Colours and the theme¶
The page draws in the GUI’s colours. The chrome takes the same tokens and the
plot takes the same curve colours, so a reader with the watcher and the GUI
open at once sees one fit rather than two colour schemes. The tokens come out
of the package, on a /tokens.css route. The GUI imports a committed copy
generated from the same module.
The theme is whichever the GUI stored, in ui.theme in
~/.rietx/settings.json ($RIETX_STATE_DIR moves that directory). Switch it
in the GUI and an open watch page follows on its next poll, 1.2 s, chrome and
plot together. This page has no theme control and writes the setting nowhere:
one writer per fact, and the GUI is it. With nothing stored the choice is
system, and the page follows the browser’s prefers-color-scheme.
The JSON underneath¶
Seven routes carry everything the page shows, and you can read any of them directly:
Route |
Returns |
|---|---|
|
every run under the scanned root, with its liveness |
|
one run’s row |
|
events from a byte offset, with the next offset; |
|
the stage’s curves, ticks and statistics as JSON |
|
a |
|
asks that run to stop; 403 under |
|
plotly out of the installed package, so the page works offline |
These are provisional by declaration, like the GUI’s (Compatibility). A route may be added, renamed or split in any release.
Stopping is the watcher’s only verb. Everything else reads. It never opens a project and never builds a refinement, so you can start and stop the watcher while a refinement runs. The stop writes a request file into a run directory the scan already found, and the fit’s own token is what acts on it. No model is edited from here and no project is touched.
The contrast worth knowing is Project.open, which writes an annotation into a
project before you have clicked anything. Looking at a project without changing
it is rietx gui --scratch. Looking at a run needs nothing.
rietx html: a saved result as a page¶
$ rietx html result.json fit.html
wrote fit.html
Takes a serialized RefinementResult and writes a standalone HTML page. Two
arguments exactly, and anything else exits 2. Because a RefinementResult
round-trips through JSON, this is the reporting path for a fit that ran
somewhere else: on a cluster, in CI, or in a notebook you have since closed.
rietx compare: did that correction help?¶
$ rietx compare --open
rietx compare — http://127.0.0.1:8730
data: /path/to/checkout/tests/data
standards available: srm660c, corundum, zincite, fluorite, brucite, nac, lab6_capillary
The browser front end for the comparison The fit report describes: pick a bundled standard, tick the settings variants, and read the cumulative Δχ²-against-the-reference panel rather than the Rwp.
It needs the standards, which are test data rather than package data, so
--data points it at a checkout’s tests/data. Started without them it says so
and lists nothing:
standards available: (none found)
hint: pass --data <dir> pointing at a checkout's tests/data
rietx.viz.compare.run is the same computation headless, and takes the same
standard and variant keys.
The variant colours¶
The chrome is the GUI’s, through the same /tokens.css the watcher links, and
the theme is read when the page is served. This page has no poll, so switching
in the GUI reaches it on a reload.
The ten variant curves keep colours of their own. A categorical set of ten is one the GUI has no answer to lend: its only categorical set is the history graph’s five lanes, and ten hues at one lightness cannot be told apart the way five at 72° can.
rietx gui: the refinement GUI¶
This section is the command. The application it starts is three chapters of its own: The GUI: a first fit for a first fit, The GUI panel by panel for the panels, and The GUI as a text document and a wire for the text document, the keyboard and the routes.
$ rietx gui my_sample.rex
Serves the GUI on 127.0.0.1:8731 and opens a browser. The project argument is
optional; without one it starts empty and you open or create a project from
inside. A project that will not open exits 2 and prints the reason, which is the
whole value of the refusal messages.
Option |
Is |
|---|---|
|
serve somewhere else |
|
do not open a browser |
|
open a copy of the project in a temporary directory; the one you named is not written to |
|
keep the recent list and the theme here instead of |
|
print one JSON boot line (url, port, project, pid, scratch_of) and nothing else, for a supervising process |
|
the Jacobian backend and the least-squares driver the session runs with, the same names |
Every GUI verb writes to the project as you click it, and opening one appends a
line to its log before you click anything. --scratch is how you look at a
project you do not want changed:
$ rietx gui my_sample.rex --scratch
rietx gui — http://127.0.0.1:8731/
project: /var/folders/8r/qnc8y_5j.../T/rietx-scratch-xe9arpn1/my_sample.rex
scratch copy — my_sample.rex is not written to
Ctrl-C to stop
The copy is byte-for-byte, so it opens exactly as the original does. Nothing deletes it: the point of a scratch run is usually to look at what happened.
The GUI needs the gui extra (Installation), which is plotly only: the built
front end is committed inside the package, so installing it never needs node.
rietx skill: the protocol, where your harness looks¶
The operating protocol ships as an Agent Skill, and this subcommand is how it
reaches a repository. The agent skill is the whole of it; the short form is
rietx skill --path to find it, --print to read it as text, and --install
to put it in .agents/skills/ with a link from each harness that reads
somewhere else.
There is deliberately no rietx[claude] or rietx[codex] extra. A pip extra
can only add a dependency, and it cannot write a file into your project, which
is the whole of what installing a skill means. So the install is a verb you run
rather than a package you resolve. It stays visible, reversible and yours to
re-run when the skill changes.