7 Commits
Author SHA1 Message Date
leonarski_fandClaude Opus 5.5 78c2a69267 Merge broker-rugnux-live into rc176: rugnux in jfjoch_broker as a live thread during a DECTRIS rotation collection (off by default), the rugnux library without file I/O (results in ProcessResult::outputs, the CLI writes them through JFJochRugnuxOutput), END carrying the merged table, map coefficients and placed model
Branch reviewed at e16db07ff (review: owner's decisions accepted - _maps.mtz instead of .ccp4 in the
viewer, -o "" computes everything incl. MR/validation, Ctrl+C/error paths may leave fewer files, the
writer links gemmi/zlib) and merged at 55184eb09 with its review fixes (END size guard, frame drop as
in Lite, outputs released before re-runs, pool priority, docs, frontend gating).

Conflicts: rugnux/Rugnux.h (kept rc176's host_memory_budget_bytes, output_prefix removed as on the
branch), rugnux/RugnuxPipeline.h (MtzTable future + rc176's GPUMerge pointer), MolecularReplacement.h/.cpp
(rc176's GPU cost model, packed transform and split functions with the branch's RugnuxModel signature;
search_model with rc176's other_budgets, result.model_name). tests/RugnuxLargeTest.cpp: rc176's
host-budget test writes its files through WriteRugnuxOutputs now that the library writes none.
Generated code (broker/gen, frontend client, Python client docs, Redoc) regenerated from the merged
jfjoch_api.yaml with update_version.sh (version unchanged).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXc1zD1yxHqaR1DGdCTHL4
2026-10-17 21:01:38 +02:00
leonarski_fandClaude Opus 5.5 4bf41983ed rugnux: the model is parsed by the caller and handed in as RugnuxModel
The analysis read the --model file itself, four to five times per run
(the model kind, every validation, molecular replacement, the stills
reference intensities) and located the PAE sidecar from the file name.
The library is to do no file I/O, so the model now arrives in memory:

- RugnuxModel (image_analysis/structure_refinement/RugnuxModel.h): the
  gemmi structure as read, whether it is ModelCIF, the PAE matrix where a
  prediction came with one, and the name the report gives it.
- ParseRugnuxModel / ReadRugnuxModelFile (writer/RugnuxModelFile.h, in
  JFJochRugnuxFiles): bytes in, gzip by magic, PDB/mmCIF by content,
  _ma_qa_metric detection as before; the file reader takes the PAE JSON
  from the same place PaePathFor named.
- ProcessConfig::model replaces model_path; PrepareSearchModel,
  RunMolecularReplacement, ValidateAgainstModel and
  ModelReferenceIntensities take the model; result fields name it by
  model_name.
- rugnux reads the model before the run starts and stops with an error
  where the file holds no model (it used to run and report the model as
  not validated). The viewer reads it when the job is set up.
- The broker parses an uploaded model at once and answers 400 where it is
  not one; it keeps the parsed model in memory, with no temporary
  directory or file. It takes no PAE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 16:11:58 +02:00
leonarski_fandClaude Opus 5.5 80f634b641 MR GPU: engines spread over the cards; the transform packed where it is expected faster
Build Packages / Create release (push) Successful in 8s
Build Packages / build:portable:macos-arm64:nocuda (push) Successful in 5m31s
Build Packages / build:portable:windows-x86_64:nocuda (push) Successful in 12m45s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 37m25s
Build Packages / build:portable:linux-x86_64:cuda (push) Canceled after 0s
Build Packages / build:portable:linux-x86_64:nocuda (push) Canceled after 0s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Canceled after 0s
Build Packages / Validation Battery (push) Canceled after 0s
Build Packages / Unit tests (push) Canceled after 48m13s
Build Packages / build:jfjoch:rocky8:cuda (push) Canceled after 0s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Canceled after 0s
Build Packages / build:jfjoch:rocky8:nocuda (push) Canceled after 0s
Build Packages / build:jfjoch:rocky9:cuda (push) Canceled after 0s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Canceled after 0s
Build Packages / build:jfjoch:rocky9:nocuda (push) Canceled after 0s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Canceled after 0s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Canceled after 0s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Canceled after 0s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Canceled after 0s
Build Packages / Generate python client (push) Canceled after 0s
Build Packages / Build documentation (push) Canceled after 0s
Build Packages / build:portable:windows-x86_64:cuda (push) Canceled after 21m47s
Engine placement: the rigid-body engines past this card's first go one card after the other in turn, from
the next card and this one last, each within half of its budget. A small cell on four cards (24 engines of
0.09 GB) had put all of them on this card, which has room for them, where rc176 spread them; now 6/6/6/6.
A large cell on four 15.6 GB cards keeps T+1/T+2/T+2/T+2.

Layout: packing was refused wherever it cost any card an engine, which kept 4nwv plain on four T4s although
the packed rotation function on ONE card (15.1 s) already beat plain over four (18.5 s). Now the two layouts'
plans are compared on a small cost model, decided once before the search from sizes and counts alone:
  seconds = rotation interpolations (orientations x reflections x operators) x s/interpolation / cards with a copy
          + rigid-body fits at once x engine GB x s/(fit GB) / engines
Constants measured on 4nwv on Tesla T4 (17255 x 244183 x 4 interpolations): 75.8 s plain and 15.1 s packed on
one card; rescoring + null 28.5 s with one engine and 11.6 s with seven, 9 fits of 2.10 GB, so 19.7 s shared
among the engines. Packing must still keep the search, the scores and (where plain has it) the rigid body on
the GPU, and the budget must hold both grids while the packed one is made. Expected on four T4s: packed
T+1/T+1/T+1/T+1, 8.7 s against 21.8 s plain. One card is unchanged (the engines are the same either way, so
packed). The layout line now logs both expected times and the search's size.

Layout and engine count change where the work runs, never a number.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXc1zD1yxHqaR1DGdCTHL4
2026-10-17 10:34:09 +02:00
leonarski_fandClaude Opus 5.5 0f461a65a2 Merge mr-transform-layout: the corner-packed molecular transform beside MR on every card
Build Packages / Create release (push) Successful in 7s
Build Packages / build:portable:macos-arm64:nocuda (push) Successful in 4m38s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 8m58s
Build Packages / build:portable:linux-x86_64:nocuda (push) Successful in 14m20s
Build Packages / build:portable:linux-x86_64:cuda (push) Successful in 18m0s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 21m31s
Build Packages / build:portable:windows-x86_64:nocuda (push) Successful in 21m55s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 21m49s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 13m41s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m30s
Build Packages / build:portable:windows-x86_64:cuda (push) Successful in 26m7s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 15m22s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 21m54s
Build Packages / Generate python client (push) Successful in 31s
Build Packages / Build documentation (push) Successful in 1m13s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 18m45s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 23m54s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 25m56s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 25m47s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 20m33s
Build Packages / Unit tests (push) Failing after 1h28m5s
Build Packages / Validation Battery (push) Successful in 34m34s
Combines mr-multigpu (per-card engine budgets, a transform copy on each other
card, the rotation/translation functions split over the cards) with
mr-transform-layout (the GPU transform optionally held corner-packed, same
bits, 2.2x faster rotation function).

Conflict (MolecularReplacement.cpp): the two new functions side by side
(PackMolecularTransformGPU next to SplitRotationFunction/SplitTranslationFunction).
On top of the textual resolution:
- One layout decision for all cards, not one per card: the functions are split
  into equal slices, so a mix of packed and plain cards would run at the speed
  of the plain ones; either layout gives the same bits.
- PackMolecularTransformGPU now plans with the other cards' budgets too and packs
  only where every card keeps what the plain plan gives it: this card's
  transform, engines and score engine, and on every other card its copy and its
  engine count. A packed copy is made after the card's engines are reserved;
  copy and engines within half of that card's budget leave room for the plain
  grid it is made from.
- The copies on the other cards are made in the chosen layout, and the per-card
  plan (copies, engines, room) is made with the bytes actually placed.
- Test: four 15.6 GB cards stay plain (packed, each other card would fall from
  T+2 to T+1); with 24 GB cards beside, packed.

So on a 4x T4 4nwv runs split and plain, on one card packed; whether giving up
3 of 7 engines for the packed rotation function would pay there is left to a
server measurement.

Workstation (1x RTX 5080, --gpulock), against rc176: battery timing
--benchmark standard (9zlo, 9s02, 9gjx, 4nwv) and --benchmark inhouse (myob,
cytc, 4nwv) on GPU, inhouse also --no-gpu: p.hkl, p_model.pdb and the MR_
report block byte-identical on every set. 4nwv GPU: rotation 5.53 -> 2.50 s,
MR step 20.1 -> 16.5 s (packed, 2.28 GB in place of 0.84 GB).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXc1zD1yxHqaR1DGdCTHL4
2026-10-16 18:46:58 +02:00
leonarski_fandClaude Opus 5.5 3aa67a6059 MR GPU: corner-packed molecular transform for the rotation function
Each sample of the molecular transform interpolates over 8 grid corners that
lie in four u-rows of the half-l grid, 4-5 32-byte sectors per sample. On the
GPU the transform can now be held packed: every point (i, j, l) stores its four
(v, w) corners, so the two neighbouring records hold all eight, read from two
places. The packed grid is cut to |u| <= reach as the plain one already is in
v, and has the conjugate plane l = -1 in front; below l = -1 the eight corners
are read mirrored about -i0 - 1 and conjugated. Same floats, same accumulation
order: rotation and translation functions give the same bits (checked in
MolecularReplacement_GPUMatchesCPU).

The layout is chosen up front from the budget (PackMolecularTransformGPU):
packed where the answer merge's plan holds every part beside it that it holds
beside the plain transform, and the budget holds both grids while the packed
one is made from the plain one on the device; logged either way. The upload no
longer stages a host copy of the grid. The CPU At() is unchanged: a
microbenchmark gained 1.3x single-threaded and ~1.0-1.1x at 16-32 threads,
not worth 2.7x the host memory.

4nwv (moved model, timing benchmark), RTX 5080: rotation 5.56 -> 2.48 s, MR
step 20.9 -> 16.7 s; transform on the card 0.84 -> 2.28 GB. p.hkl, p_model.pdb
and the MR_ report block byte-identical on the timing sets (GPU and --no-gpu)
and on 4nwv, 8tyy, 6oel, 3inp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXc1zD1yxHqaR1DGdCTHL4
2026-10-16 17:55:30 +02:00
leonarski_fandClaude Opus 5.5 bfc6ca8487 MR and model validation: use every card's memory, split MR's functions over the cards
On a multi-GPU host the structure stage ran essentially on one card: the rotation
function's launches all went to the search's card, and the rigid-body engines of
model validation and MR were admitted only within half of that one card's budget,
although RigidBodyGPUPool already placed engines on other cards. On a 4x T4 server
4nwv's MR rotation function took 75.8 s on one card, validation queued its 10 fits
on one engine and MR's rescoring and null got one engine.

- PlanModelValidationGPU / PlanMolecularReplacementGPU: this card is planned
  exactly as on a machine with it alone (GPU-vs-CPU placement unchanged: 80% of the
  total, less what is resident). The rigid-body engines it has no room for go on the
  other cards in turn, each against its own budget (80% of its total, less the
  resident bytes, counted on every card) within half of it. MR also puts a copy of
  the molecular transform on each other card within half of its budget.
- RigidBodyGPUPool::Reserve takes per-card engine counts.
- Validation forwards the most it holds on any card as gpu_bytes to a validation
  beside, and may run one beside only where twice that fits every card's budget.
- MR splits every rotation and translation function into contiguous slices of
  the orientation list over the cards holding a transform copy (and room for the
  call's buffers), one thread per card, gathered back in order. Each orientation
  is scored by itself (one block / one map), nothing is summed across
  orientations, so N cards give the bytes of one.
- ScoreEngines keeps a room per card; score engines always go on the search's
  own card; function buffers are held on the card they run on.

Byte-identical at N=1 (workstation, one RTX 5080): timing benchmark (myob, cytc,
4nwv) and 4nwv, 8tyy, 6oel, 3inp with --model give the same p.hkl, MTZ data,
p_model.pdb, maps and report against rc176. New tests: per-card plans on four
15.6 GB cards (validation 1/3/3/3 engines, MR T+1/T+2/T+2/T+2), and the split
driven over 1-4 virtual cards on one device, bitwise equal to one card.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXc1zD1yxHqaR1DGdCTHL4
2026-10-16 17:16:28 +02:00
leonarski_fandFilip Leonarski 19b135186d v1.0.0-rc.175 (#85)
Build Packages / Create release (push) Successful in 7s
Build Packages / build:portable:macos-arm64:nocuda (push) Successful in 4m56s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 9m10s
Build Packages / build:portable:linux-x86_64:nocuda (push) Successful in 13m51s
Build Packages / build:portable:linux-x86_64:cuda (push) Successful in 16m56s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 20m59s
Build Packages / build:portable:windows-x86_64:nocuda (push) Successful in 21m25s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 21m17s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 14m14s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m59s
Build Packages / build:portable:windows-x86_64:cuda (push) Successful in 25m48s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 15m52s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 19m33s
Build Packages / Generate python client (push) Successful in 29s
Build Packages / Build documentation (push) Successful in 1m19s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 20m49s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 25m31s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 25m24s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 26m5s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 22m19s
Build Packages / Unit tests (push) Failing after 2h28m24s
Build Packages / Validation Battery (push) Successful in 40m33s
* Rugnux: Processes a collection live while jfjoch_broker writes it (`rugnux <master.h5> <broker URL>`).
* Rugnux: Processing starts on the first images and confirms its choices once all are in, so analysis can begin during collection.
* Rugnux: Faster processing, especially on servers with several GPUs and CPU sockets and on computers without a GPU (`--no-gpu`, macOS).
* Rugnux: Uses much less GPU memory, so large data sets stay on the GPU of smaller cards, and a data set too large for the card is processed on the CPU instead.
* Rugnux: On macOS, Bragg integration runs on the Apple GPU.
* Rugnux: Better handling of twinning and pseudo-symmetry, with the twin hypothesis written as `<prefix>_twin_<SG>.mtz`, and a wrong `--model` no longer confirms the data or decides the hand.
* Rugnux: `--model` accepts predicted (AlphaFold) models and falls back to molecular replacement when the model is not placed as a rigid body (experimental `--mr` forces it), settling space group, hand and twinning and writing a different answer as `<prefix>_mr.mtz`.
* Rugnux: Molecular replacement is faster and settles the space group in more cases with several copies in the asymmetric unit.
* Rugnux: Deposition-ready mmCIF, a richer report, and numbered steps on the terminal with the full log in `<prefix>_log.txt`; `_P1.mtz`, `--estimate-beam-center` and `--beam-center-search` are removed.
* Rugnux: The beam-stop shadow is found correctly on tilted detectors.
* Rugnux: Stills files that record a stationary goniometer axis get the default stills geometry refinement.
* jfjoch_viewer: "Analyze dataset" uses the same defaults as a bare `rugnux <file>`, and shows the results of processed CBF, marCCD and SMV sweeps.
* jfjoch_viewer: The Files panel refreshes on F5 and every few seconds, and the plot type and other selections stay put during live collection.
* jfjoch_writer: Memory stays bounded and old data files are closed efficiently on long runs, any failure ends the run in an error state the broker is told about, and the master file appears only after its data files; the ZeroMQ pusher logs when a stalled writer makes it drop images.
* Fixes: The TCP image pusher no longer blocks the broker's receiver, GPU out-of-memory failures stop with a clear message, and the XDS plugin masks module and chip edges.

---------

Co-authored-by: Filip Leonarski <jungfrau@filips-mac-mini.home>
Reviewed-on: #85
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-10-16 15:34:43 +02:00