19 Commits
Author SHA1 Message Date
leonarski_f c999883eb1 model: --model is a hypothesis, and it decides nothing until it fits
A model supplied with --model rewrote the space group of every reflection
written out on the strength of its file having parsed. Measured on a rotation
dataset merged in P4(1)2(1)2: an unrelated protein and the correct model
rigidly rotated 90 degrees each produced a .mtz, .cif and _unmerged.mtz byte
for byte identical to what the crystal's own model produced - relabelled
P4(3)2(1)2 - at R-free 0.601 and 0.674 against the correct model's 0.591, with
no warning. The adoption was pure space-group-number arithmetic and ran before
the model had been fitted at all.

A model changes exactly two things on the rotation path, and both rewrite the
data: the enantiomorph label and the merohedral indexing. Both now wait for the
fit. Everything else a model produces - R-factors, maps, the rigid-body
placement - is a statement about the MODEL, cannot corrupt a reflection, and is
computed and reported either way.

The gate is not a threshold on R, because no threshold works: the classical
acentric random value is 0.586 at unit scale but 0.550 at the R-minimising
scale, observed nulls land at 0.599-0.615, and the value moves with the model's
atom count and B-factors as much as with the data. Instead the same model is
re-oriented at random about its own centroid five times and run through the
identical path - same scaling, same rigid-body placement, same R - and the real
fit is asked how far above that distribution it sits. R-work carries the
decision: nothing is refined against the working set here, and it has 12615
reflections to R-free's 709. Measured on the case above: the crystal's own model
+15.0 sigma, the unrelated protein +1.8, the 90-degree rotation -1.0, and the
two rejected runs now write files byte-identical to a run with no model.

The nulls are rigid-body refined like the real fit, or the comparison would be
between a placed model and unplaced nulls. That is what the null costs: about
12 s for the five replicates, on a --mode scale run that merges in 2 s.

The merohedral margin gets the same treatment - a random placement also picks a
winner, and measured, by a comparable lead - and the candidate operators are now
enumerated from the DATA's space group. Taking them from the model's enumerated
zero operators wherever the two groups differ, which is exactly the case the
probe exists for: a model in P4(3)2(1)2 against data merged in P4(3) probed
nothing at all, and now probes the twin law.

The anomalous difference map is the only measurement here sensitive to the hand
- inverting the model through the origin moves R-work by less than 1e-4, since
|F(h)| of the inverted structure is |F(-h)| - so where it says the hands
disagree it vetoes the adoption outright, fit or no fit.

Report: MODEL_FIT, MODEL_FIT_SIGMA and the null beside it, MODEL_DECISIONS_TAKEN,
the indexing margin against its null, and MODEL_VALIDATION= PERFORMED as the
counterpart of the failure line. SPACE_GROUP_ENANTIOMORPH= DETERMINED_FROM_MODEL
becomes ASSUMED_FROM_MODEL and is written only where the model was accepted:
nothing here measured the hand, the model asserted it. That is a reason code
changing name and meaning, so REPORT_VERSION is 6.

Stills are untouched: the per-image indexing hand a model can set at integration
time is not reachable on the rotation path and is not gated here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
2026-09-02 13:34:52 +02:00
leonarski_fandClaude Opus 5 6b738713e8 model validation: place the model, then weight the maps by sigma_A
--model re-fractionalized the model into the data cell and then left it there.
On a non-isomorphous pair that is a placement error, not a cell error: the box
is squeezed, the body inside it is not moved. Six parameters now recover it -
an angle-axis rotation about the model's centroid and a translation, refined
over a 6 / 4.5 / 3.5 A ladder, the scale (k_overall, anisotropic B, k_sol,
b_sol) re-fitted at every evaluation so the target measures the placement and
not the scale. The refinement sees only the working reflections and the step is
committed only if R-free, on the free set it never saw, drops; otherwise the
model goes back where it was read.

Measured on merged lysozyme data against a deposited lysozyme model whose cell
differs by 3.4% in c: R-work 0.559 -> 0.400, R-free 0.591 -> 0.383. Over the
same 3.5 A range the external arbiter (REFMAC rigid body through dimple) works
in, 0.524 -> 0.330 against REFMAC's 0.522 -> 0.355, and the recovered movement
agrees with REFMAC's to 0.25 deg and 0.03 A (3.05 deg / 1.04 A vs 2.76 / 0.98).
2.4 s of added wall clock, 234 structure-factor evaluations.

The map coefficients become 2mFo-DFc and mFo-DFc. sigma_A is estimated by
maximum likelihood per resolution shell on the free reflections only, with the
number of shells taken from the size of the free set so no shell is thin;
centric and acentric reflections carry their own likelihoods, and a centric
reflection's bias-free coefficient is mFo. Cross-checked against CCP4 SIGMAA
on the same reflections: mean FOM 0.404 against its 0.396, with the same
per-shell structure. The figure of merit is written to _maps.mtz so the
weighting can be undone.

The per-shell scaling refusal in fit_model stands - Fobs is never rescaled and
the R-factors are untouched - but m and D are per-dataset, so maps from one
campaign are no longer scaled identically. That is argued at the code and in
docs/CPU_DATA_ANALYSIS_DECISIONS.md 14.4.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
2026-09-02 13:06:05 +02:00
leonarski_fandClaude Opus 5 67bf380401 docs: credit DENZO/SCALEPACK and MOSFLM for the 2D-then-merge architecture
Both programs were acknowledged for specifics (profile-fit variances, FFT
autoindexing, post-refinement practice) but not for the paradigm rugnux's
rotation pipeline is built on: integrate each image in 2D, then combine the
partials into fulls, as against XDS's 3D profiles. One paragraph names it;
the citations were already on the page.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
2026-09-02 09:19:16 +02:00
leonarski_fandClaude Opus 5 6081b6bc43 docs: credit the L test, FFT indexing, TORO, Niggli, peakfinder8 and SparseCCL
Six methods the pages name or describe carried no citation: Padilla & Yeates
(the L test), Steller, Bolotovsky & Rossmann (the projection/FFT autoindexing
MOSFLM implements), TORO (what ffbidx implements), Krivy & Gruber and the
ITA lattice-character table (the reduction and Bravais assignment), Cheetah's
peakfinder8 (the per-ring background statistics of the adaptive finder) and
Hennequin et al.'s SparseCCL (already credited to traccc, now also to its
authors). Each gets its ACKNOWLEDGEMENT.md paragraph, a References entry in
CPU_DATA_ANALYSIS.md, and a one-line credit at the algorithm. The
Sheriff & Hendrickson / Popov & Bourenkov entry is re-scoped so each claim
sits on the paper that supports it - P&B 2003 is titled, and credited for the
sigma-aware anisotropy estimation its statistic modelling contains, not for
the tensor and its constraints. All DOIs verified against the publishers;
the SparseCCL DOI resolves to IEEE document 9049184 (IEEE blocks content
scraping, so verified by the resolved document id plus two independent
sources).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
2026-09-02 09:18:36 +02:00
leonarski_fandClaude Opus 5 0e245d28cf docs: name the in-house SLS 2.0 data beside the public sets
The acknowledgement read as though the whole test corpus came from other people's beamlines. It does
not - in-house data collected at SLS 2.0 is tested alongside it. One clause, at the top of the
section; everything that follows still concerns the public data, which is the part that carries an
obligation to cite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
2026-09-01 09:58:40 +02:00
leonarski_f 931d7acc7c lattice: judge a structurally-zero scalar product against the size of the cell
The Niggli type of a reduced cell is the sign of its three scalar products, and
gemmi's reduction was asked to decide those signs against an absolute tolerance
of 1e-9 while the products themselves are 10^3 to 10^5 A^2 and the cell is held
in float. A product that is structurally zero therefore arrives carrying about
1e-4 A^2 of rounding and is read as definitely signed. The reduction lands on
the wrong side of the type-I/type-II boundary, the character written for the
other side matches nothing, and the lattice comes back with less symmetry than
it has.

It is not a corner case. Take an exactly body-centred tetragonal lattice with
c > a*sqrt(2) and merely ROTATE IT IN SPACE: 38 of 60 rotations lose the
4-fold, and it comes back C-centred. Every lattice this code classifies is a
refined, rotated one; the two tetragonal-I cases already in the tests are
axis-aligned, which is the one corner where the rounding vanishes.

The tolerance is now scaled by the cell's own magnitude. Over 14845 exact
lattices that puts it on a plateau three decades wide - demotions 461 -> 31 -
with the over-call count unchanged at every point of it, and with the three
fixes that follow the plateau is four decades wide and the demotions are zero.
The constant is 100x the one Grosse-Kunstleve et al. give because theirs is
calibrated for a double-precision cell and this one is float: measured, their
value recovers 6% of these lattices and this one 93%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
(cherry picked from commit b82f217719450e1e46e94be5b5a43ed8ffbf2ca4)
2026-08-31 07:17:00 +02:00
leonarski_fandClaude Opus 5 d35e8f680f symmetry: say when the cell metric hosts more symmetry than the group adopted
Every symmetry under-call in the corpus has the same signature: a lattice whose
metric carries rotations the adopted group does not, with nothing in the run
saying so. The user is left to notice that a P1 answer sits on a cell whose
axes are equal and whose angles are 60 degrees.

So a run that determines its own group now compares the two and says what it
sees. It decides nothing - no threshold, no promotion, no demotion, no
reprocessing - and the message says as much, because a pseudo-symmetric metric
is ordinary and only the intensities can settle whether the extra rotations are
real. Measured over 83 crystals it fires on 19 and covers 6 of the 7
point-group under-calls; on the crystal whose deposited group is F 4 3 2 and
which rugnux reports as P 1, the line reads "the cell metric is cubic - it
admits 24 rotations where P 1 has 1".

The metric symmetry is Le Page's, taken through GEMMI's implementation, at an
obliquity of 1 degree - the middle of the band over which the answer is stable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
2026-08-31 07:17:00 +02:00
leonarski_fandClaude Opus 5 fb18457e6f docs: bring the changelog and the method notes up to rc.166
The rc.166 changelog was missing fourteen user-visible changes and carried
rationale and measurements that belong here instead. Added: the native miniCBF
sweep reader, the third-party and firmware-1.x NXmx masters, the plain LZ4
filter, the image orientation taken from the file's module direction vectors,
the two beam-centre flags and their rescues, and the FFT reach past 500 A.
Trimmed the rest to one line each, moving the numbers out of the user-facing
file.

RUGNUX.md described the input as a single Jungfraujoch master file, which it
has not been since this branch; it now covers the foreign and legacy masters,
the accepted compression filters and the miniCBF sweep, including how a sweep
is collected from one named frame. Six options existed with no entry in the
table - --beam-center-check, --beam-center-search, --fft-min-unit-cell,
--min-indexed-spots, --rot3 and --no-p1-crosscheck - and -C now moves both FFT
cell bounds, which was not written down anywhere.

CPU_DATA_ANALYSIS.md carried two statements this branch made false: 7.5 still
said pass 2 reuses pass 1's space group, and 13.1 still said centrings are
ranked by net absence count. Both now describe what the code does - the group
is determined after pass 2, and centrings are ranked by the same Beta-tail
likelihood the screw test uses. Also documents the per-zone screw scoring, the
coplanarity volume-fraction guard, the plane-normal transform, the FFT cell
bounds and the twelve refined candidates.

The miniCBF reader implements the x-CBF_BYTE_OFFSET scheme and reads the imgCIF
axis table from the specification alone. No CBF code is vendored or linked, so
there is no licence obligation, but reimplementing a published specification
carries one of credit: ACKNOWLEDGEMENT.md gains a section and the two
algorithms carry a one-line reference each. Both DOIs were resolved before
being written.

Rottger's initial was wrong where this branch first cited it - K, not A.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxrrPcxodNiXzhNiECCVp5
2026-08-30 21:15:31 +02:00
leonarski_fandClaude Opus 5 9924dd9fc3 docs: the open test battery, updated - 82 datasets, and not only non-SLS
The public data the pipeline is exercised on has grown from 59 datasets to 82,
77 of them with a released PDB entry and released structure factors, so the
page that credits the depositors and carries the DOI to cite for each is
brought up to date with what is actually run.

Renamed from NON_SLS_TEST_DATA to EXTERNAL_TEST_DATA, because the old title
stopped being true: a few of the sets were collected at SLS beamlines, where
the data are still written by someone else's detector and someone else's
acquisition system. What the battery tests is foreign files, not a foreign
facility.

Also rewritten from the current archives rather than the earlier sample:

- Multi-collection archives: eleven are not a single continuous rotation, not
  four. Seven IRRMC archives hold more than one collection; one sweep is kept
  in six of them, and both are kept in the one whose two sweeps are at
  different wavelengths. A repository project page is not a reliable guide
  here - one describes a 900-frame sweep its own tarball does not contain.
- Detector labels: 76 rows can be compared against the PDB entry. Seven
  genuinely conflict, and one of those the file settles outright - pixel
  count, pixel size, sensor thickness and firmware string agree with an
  EIGER2 9M against the entry's PILATUS4 4M. A further 29 differ only in how
  much they state, which is not a conflict.
- One archive ships 30 placeholder files named like images that are 64-byte
  text; named on the page so a reader that globs the directory is not
  surprised by them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxrrPcxodNiXzhNiECCVp5
2026-08-30 20:50:10 +02:00
leonarski_fandClaude Opus 5 bec2a10a3a docs: add the eight new public datasets to the non-SLS test list
Three IUCrData Raw Data Letters (Zenodo, CC-BY-4.0) and five SBGrid Data
Bank depositions (CC0) have been added to the data the pipeline is
exercised on. Same rules as the rest of the page: the source is the
repository and its own citable DOI, every one of which was resolved before
it was written down; beamline, resolution, space group and cell are the
values deposited with the PDB entry; the detector is read out of the image
files. None of the new detectors disagree with their PDB entry.

Two of them do not fit the page's one-row-per-sweep shape, so the shape is
described rather than flattened. The 6R72 Zenodo record holds two complete
360-degree collections on one crystal - a helical one that produced the
deposited structure and a low-dose one that has no PDB entry - and both are
listed, sharing a DOI, with the second in the no-PDB-entry table. The three
CHESS depositions are 4-11 wedges of 50 degrees per crystal plus a rotation
taken with the crystal translated out of the beam, tabulated in a new
section.

One SBGrid deposition is named but not in the table: its images are 1995 CCD
TIFFs, a format the reader does not support, so it is not processed here and
saying so is more useful than leaving it out.

ACKNOWLEDGEMENT gains the new per-repository counts and a pointer to the
Raw Data Letter citations; TESTS points at the page.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3yNBXk4wKdMZy1ak2NY7f
2026-08-29 23:40:10 +02:00
leonarski_fandClaude Opus 5 9d3c2787f8 docs: credit the public diffraction data the pipeline is tested against
Jungfraujoch is developed at one facility, so the reader and the reduction
pipeline are exercised on data collected elsewhere - other detectors, other
file formats, other conventions. That data was collected and published by
other people, and until now nothing in the repository said so.

NON_SLS_TEST_DATA lists all 51 datasets: the DOI to cite for each, the
repository it came from, and the experiment as deposited. Beamline,
resolution, space group and cell are the values deposited with the
corresponding PDB entry, read from the RCSB data API - not results measured
here; no quantity produced by this software appears on the page. The detector
is read out of the image file instead, because the detector named in a PDB
entry is often only approximate, and the twelve cases where the two disagree
are listed rather than silently reconciled.

ACKNOWLEDGEMENT gains a section for the repositories themselves, with the
IRRMC, SBGrid and Zenodo citations and the PDB citation for the metadata.
Every DOI on both pages was resolved before it was written down.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lc5JG6kJqZoCWaoZ43JGTW
2026-08-29 21:59:49 +02:00
leonarski_fandClaude Opus 5 6516bc96af Ice rings: carry the list past 1.5 A, where ice does not stop
The eleven measured bands end at 1.522 A because their source says so in its
own words - "pure hexagonal ice has 11 diffraction rings between 4 and 1.5 A
resolution" - and its subject was detecting ice in deposited data, not masking
it. On a detector that reaches further, the rings it does not list are the ones
left in the data: on the strong rotation set just added to the battery, 44% of
every image's spots sit in ice bands, and beyond 1.5 A the spot list is ice and
nothing else, which is why the resolution estimate read the ice rather than the
crystal.

There is nothing measured to copy below 1.522 A, so the eight added bands are
calculated. Enumerating hkl is not enough and the code already said so: ice Ih
is P6_3/mmc with O on 4f, and most of what enumeration emits is extinguished by
the OXYGEN SUBLATTICE rather than by the space group - which is why (004) at
1.830 A and (104) at 1.657 A are missing from the measured list although they
sit inside its range and its reflection conditions allow them (for (00l) the
structure factor goes as cos(2*pi*l*z), and z ~ 1/16 kills l = 4). So compute
structure factors - oxygen only, the hydrogens being half-occupancy disordered
and weak to X-rays - and keep the lines reaching 3% of the strongest. That rule
REPRODUCES THE MEASURED ELEVEN EXACTLY and every line it drops inside their
range computes to zero, which is what makes it trustworthy below 1.522 A. It
stops at 1.170 A: below that the real lines fall to 2-3% while the extinct ones
rise to about 1%, and an oxygen-only calculation cannot separate them honestly.

Every added band was independently confirmed in the data - the spot-count
histogram of the strong set peaks at each of them and is empty between - and
every line the rule calls extinct is absent there too.

Costs, measured. The bands are inert above 1.6 A: on 38 of 39 battery sets the
profile ice score does not move at all, and the one that appeared to (a jet set,
1.25 -> 2.60) does not on the peak-excluded profile the score actually uses -
that was Bragg peaks in the plain profile, which is what the peak exclusion is
for. Where a detector does reach past 1.5 A the bands cover more of reciprocal
space: unchanged at 1.6 A, +7.4 points at 1.4 A, +16.5 at 1.18 A. On the strong
set that is 17% -> 27% of reflections held out of the scale fit, and it shows:
the spot resolution estimate improves from 1.33 to 1.46 A against a truth near
1.42, while CC1/2 falls 98.5 -> 97.6% and ISa 3.58 -> 3.37. Ice handling only
runs at all on a run that trips the ice gate, so a clean crystal pays nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FBumeJVx4oeXxiBRpkrE5H
2026-08-28 11:27:28 +02:00
leonarski_f 511be0c366 v1.0.0-rc.165 (#75)
Build Packages / build:rpm (rocky8) (push) Successful in 24m0s
Build Packages / Unit tests (push) Skipped
Build Packages / build:windows:nocuda (push) Successful in 16m54s
Build Packages / build:windows:cuda (push) Successful in 19m25s
Build Packages / build:viewer-tgz:cpu (push) Successful in 14m44s
Build Packages / build:viewer-tgz:cuda (push) Successful in 16m3s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 13m15s
Build Packages / build:rugnux:windows (push) Successful in 10m45s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 9m34s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 19m7s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 18m9s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 24m48s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 18m13s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 24m51s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 22m58s
Build Packages / build:rpm (rocky9) (push) Successful in 21m23s
Build Packages / Generate python client (push) Successful in 1m2s
Build Packages / Build documentation (push) Successful in 1m23s
Build Packages / Create release (push) Skipped
Build Packages / XDS test (durin plugin) (push) Successful in 9m45s
Build Packages / XDS test (neggia plugin) (push) Successful in 10m19s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 11m10s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 22m15s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 17m37s
Build Packages / DIALS test (push) Successful in 17m16s
* `rugnux --model` adopts the model's space group as a label where the data were merged in its enantiomorph, instead of reindexing the reflections - which swapped I(+) with I(-).
* `rugnux --model` warns, naming the atom, when the anomalous density at the model's atoms comes out inverted, which means the data and the model are in opposite hands.
* `rugnux --model` writes an anomalous difference map (`<prefix>_anom.ccp4`) when the merge kept the Bijvoet split, and names the ten model atoms it peaks highest on as `ANOMALOUS_SITE_01`..`_10`.
* `MEAN_ATOM_DENSITY_SIGMA` is read from the map by cubic rather than linear interpolation and comes out around a tenth higher; it is no longer comparable with the figure earlier versions printed.
* `rugnux --model` reads an mmCIF coordinate file as well as a PDB one, gzipped or not, taking the format from the file's content rather than its name.
* A model `rugnux --model` cannot use is reported as a `WARNING:` line in the results report instead of only in the log.
* The rugnux results report has a `10. MODEL VALIDATION` section when `--model` was given; `REPORT_VERSION` is 4, `WARNINGS` moves to section 11 and no existing key changed.
* The rugnux results report records how the run was invoked, what it cost and what it ran on: `COMMAND_LINE=`, `WALL_TIME=` and `GPU_COUNT=` / `GPU=`.
* rugnux says which GPUs it can see before it starts processing.
* `rugnux --export-unmerged` also writes `<prefix>_unmerged.mtz` on a `--no-merge` run, and is ignored on a run with no output prefix instead of writing a file called `_unmerged.mtz`.
* `/start` asks the writer whether the run can be written before the detector is armed, so a run whose master file already exists, or whose output directory cannot be created, is refused up front with the writer's own message. This needs the TCP image stream or the built-in HDF5 writer; the ZeroMQ stream is unchanged.
* A calibration that fails goes to `Error` carrying the reason instead of `Inactive`, so `/wait_till_done` and `/wait_until_running` report it; a cancelled calibration still goes to `Inactive`.
* `/wait_till_done` answers 500 with the message when a collection ended in an error. A cancelled collection and a collection that only triggered a warning still answer 200.
* A pending start failure is discarded by `/cancel` and `/deactivate`, as it already was by `/start` and `/initialize`.
* `/scan_result` no longer reports the previous run's images after a collection that failed to start, or after `/deactivate`.
* The TCP image stream protocol version is 4. `jfjoch_writer` and `jfjoch_broker` have to be of the same release, as before.

Reviewed-on: #75
2026-08-27 22:16:54 +02:00
leonarski_f 749db470ca v1.0.0-rc.164 (#74)
Build Packages / build:rpm (rocky9) (push) Successful in 19m56s
Build Packages / Unit tests (push) Skipped
Build Packages / build:windows:nocuda (push) Successful in 16m57s
Build Packages / build:windows:cuda (push) Successful in 19m18s
Build Packages / build:viewer-tgz:cpu (push) Successful in 14m48s
Build Packages / build:viewer-tgz:cuda (push) Successful in 16m18s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 14m19s
Build Packages / build:rugnux:windows (push) Successful in 10m34s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 8m49s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 20m55s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 17m4s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 20m48s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 19m15s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 24m26s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 20m32s
Build Packages / build:rpm (rocky8) (push) Successful in 23m39s
Build Packages / Generate python client (push) Successful in 46s
Build Packages / Build documentation (push) Successful in 1m45s
Build Packages / Create release (push) Skipped
Build Packages / XDS test (durin plugin) (push) Successful in 11m3s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 11m30s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 20m10s
Build Packages / XDS test (neggia plugin) (push) Successful in 10m17s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 23m12s
Build Packages / DIALS test (push) Successful in 20m12s
* rugnux now tells you whether a crystal diffracts anisotropically and how far it reaches in each direction, without a second program: a new `9. DIFFRACTION ANISOTROPY` section in `<prefix>_report.txt` and matching `_reflns.pdbx_aniso_B_tensor_*` / `_reflns.jfjoch_aniso_*` items in the merged mmCIF report the anisotropic deltaB, the diffraction limit along each principal direction, and a `NOT DETECTED` / `DETECTED` / `CANNOT DETERMINE` verdict measured against the data set's own systematic error. It is a description only - no intensity is corrected, no reflection is removed, and the merged data do not depend on direction.
* rugnux can hand its integrated observations to another scaling program: `--export-unmerged` writes `<prefix>_unmerged.mtz`, an unmerged MTZ readable by aimless, pointless, careless and `iotbx.merging_statistics`, in `--mode mx` and `--mode scale` alike. Each rotation reflection's partials are summed into one full; `--export-unmerged-partials` writes one row per image instead. Intensities carry the Lorentz-polarization factor and nothing else, since those programs scale the data themselves. Lattice-centring absences are not written; screw and glide absences are.
* rugnux integrates crystals with broad spots better - where it changes anything, per-shell mean I/sigma improves by up to 31% and R_meas by up to 24% - because on rotation data the integration signal radius is now taken from the crystal's own measured spot width instead of a fixed 4 px. `--adaptive-integration-radius=off` restores the fixed radius and an explicit `--integration-radius` still overrides both. The widened radius applies to the final integration pass only, and a pattern too dense for it is re-integrated at 4 px with a note in the log.
* rugnux discards fewer stills reflections for want of a background ring, improving per-shell R_meas over most of the signal-bearing range: the stills background ring now runs to 14 px instead of 12. The gain reverses in shells below a mean I/sigma of about 4.
* rugnux determines the space group with thresholds that mean the same thing on a weak crystal as on a strong one: symmetry operators are scored on resolution-normalised intensities (E squared) instead of raw merged intensities, and a reflection counts as genuinely present on its counting significance instead of on the merged I/sigma, which saturates at the merge's own ISa. The search resolution cut is no longer able to move the answer, and the twin-law H bound moves from 1.70 to 1.85, which stops one class of correct high-symmetry assignment being refused as twinning.
* rugnux says what the space-group search tested and what it could not: the twin-law disagreement H is printed for every operator together with the adopted point group's H ratio and its bound; alternatives that are not on the reported lattice are named with how their cell differs; and a lattice centring the data could not test - the crystal having been integrated on the primitive sub-cell, so the reflections it extinguishes were never measured - is marked `UNTESTED` and warned about where it is adopted, as coming from the lattice metric rather than from the intensities.
* rugnux `--mode scale` re-merges a `_process.h5` in the right symmetry without being told it: the file now records the space group on every run - a two-pass rotation run wrote none before, so re-merging defaulted to P1 - together with the change of basis under `/entry/MX/reindexMatrix` where the lattice was re-seated, and `--mode scale` also reports the Wilson B-factor estimate instead of `WILSON_B= nan`. A file written before this stops with a message naming the two cells and the override to use, instead of failing inside the merge. A third-party reader of a `_process.h5` must apply `reindexMatrix` where it is present.
* rugnux installs on its own, as a package called `rugnux` - `dnf install rugnux` or `apt install rugnux` - instead of arriving inside `jfjoch-viewer`. It pulls in none of the acquisition stack, so a machine that only processes data no longer has to carry the broker, the detector libraries or Qt to get it. Installing it over a `jfjoch-viewer` from rc.163 or earlier, which still owns `/usr/bin/rugnux`, upgrades cleanly rather than failing on the duplicate file.
* rugnux is also a standalone download, built for arm64 as well as x86_64: `rugnux-<version>-linux-{x86_64|aarch64}-cuda<major>.tgz` and `rugnux-<version>-win64-cuda<major>.zip` on the release page, for machines that are not managed by a package manager. The aarch64 build targets GH200 and DGX Spark, and is untested on hardware.
* Every portable Linux binary is now a single self-contained file: cuFFT is linked statically instead of being shipped beside the executable and found through an rpath, so `rugnux` and `jfjoch_viewer` need nothing but an NVIDIA driver, and only to use the GPU. The `.rpm`/`.deb` continue to take cuFFT from the distribution. The developer utilities `jfjoch_extract_hkl` and `jfjoch_recompress` are no longer packaged anywhere.
* Jungfraujoch needs six fewer shared libraries on the machine - libopenblas and libmetis, and libgfortran, libquadmath, libgomp and libz behind them - because the Ceres LAPACK, METIS and SuiteSparse back-ends are no longer built. Nothing in the code ever selected them, and results are unchanged.
* The PCIe driver DKMS package builds for the kernel it is being installed for instead of the running one, so a module built while a kernel update is being applied loads after the reboot.
* The PCIe driver builds on RHEL 9.5 and later, and on their CentOS Stream, Rocky and AlmaLinux equivalents, where the `vm_flags` kernel interface was backported into the 5.14 kernel.
* A data collection started with `async_start` that fails to start - a writer refusing to overwrite an existing file, for instance - is reported as an error by `/wait_until_running` and `/wait_till_done` instead of as a timeout and a successful collection respectively. The error message is the one the writer gave.
* A calibration that is cancelled or that fails to collect its pedestals is no longer reported as a successful one. The broker goes to `Inactive` with an error message and has to be initialized again, instead of sitting in `Idle` looking ready to measure while holding partial pedestals - data collected in that state was silently mis-converted.
* A failed `/initialize` is reported to `/wait_until_running` and `/wait_till_done` as soon as it happens, instead of when their timeout expires.
* `space_group_number` accepts space groups up to 230 in the API schema, so cubic space groups can be recorded. The broker always accepted them; the generated clients rejected them before the request was sent.
* The results report's `REPORT_VERSION` is 3, two sections having been added. Existing key names and table columns are unchanged.
* The merged statistics table has **9** resolution shells instead of 10, which is what XDS reports. The bins were already XDS's - equal steps in 1/d^2 between the lowest- and the highest-resolution reflection the merge kept - so at the same resolution limits the two tables now have the same shell boundaries and can be read row for row. `--resolution-shells` sets a different count.
* `rugnux --model` now settles the frame the merged reflections are written in, not only the frame the R-factors and the maps are computed in: the `.mtz`/`.cif`/`.hkl` come out in the model's indexing, and where the data were merged in the model's enantiomorph they take the model's hand and space group - which on anomalous data puts I(+) and I(-) the right way round. The indexing choice is logged with the winning R-free and the runner-up, so a decision made within noise is visible.
* `rugnux --model` can resolve the indexing ambiguity of a **serial stills** run, which a model could not do before: structure factors computed from the model become the per-image reference, the same role a reference MTZ plays. It needs the cell and space group up front (`-C` / `-S`). Without one or the other, a merohedral serial run still merges both hands together and says so.
* The rugnux documentation opens with a quick start - the default run, and runs with a reference MTZ, with a model, or with the space group and cell pinned - and explains the indexing ambiguity: what it costs on rotation and on serial data, and which of `-z` / `--model` resolves it in each case. The long reference pages now carry a table of contents.

Reviewed-on: #74
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-08-26 22:47:00 +02:00
leonarski_fandjungfrau 4dc2534dbf v1.0.0.rc-162 (#72)
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 18m57s
Build Packages / Unit tests (push) Skipped
Build Packages / build:windows:nocuda (push) Successful in 16m55s
Build Packages / build:windows:cuda (push) Successful in 18m48s
Build Packages / build:viewer-tgz:cpu (push) Successful in 13m10s
Build Packages / build:viewer-tgz:cuda (push) Successful in 14m45s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 22m23s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 20m12s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 23m7s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 20m43s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 23m9s
Build Packages / XDS test (durin plugin) (push) Successful in 12m26s
Build Packages / build:rpm (rocky9) (push) Successful in 24m58s
Build Packages / Generate python client (push) Successful in 50s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 23m20s
Build Packages / Create release (push) Skipped
Build Packages / XDS test (JFJoch plugin) (push) Successful in 12m37s
Build Packages / build:rpm (rocky8) (push) Successful in 27m58s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 25m38s
Build Packages / Build documentation (push) Successful in 59s
Build Packages / DIALS test (push) Successful in 23m16s
Build Packages / XDS test (neggia plugin) (push) Successful in 6m38s
**Files written by Jungfraujoch now import correctly in DIALS, XDS and pyFAI.** A tilted detector, a grid scan, a still recorded at a goniometer position, and saturated or unreadable pixels were each described in a way that a third-party program acted on wrongly. If you process Jungfraujoch data outside Jungfraujoch, prefer this release to any earlier one.

* HDF5: the detector tilt (`rot1`/`rot2`/`rot3`) is exported correctly in the NXmx transformation chain; untilted geometries are unaffected.
* HDF5: a still recorded at a goniometer position is no longer read back as a single image, and a grid scan records a stationary spindle so a program that requires a rotation axis can open it.
* HDF5: the sample transformation chain is written in mounting order, with a Smargon head position told apart from the spindle, one entry per image, `module_offset` as a float unit vector, and `offset_units` on every offset.
* HDF5: saturated, underloaded and unreadable pixels are described so a downstream program masks them - `saturation_value`, `underload_value`, `error_value` and `bit_depth_readout` are written correctly, and a data file missing next to a VDS master reads as the error marker rather than as zero counts.
* HDF5: the rotation axis is read back under whatever name it carries, and `mirror_y` records whether the assembled image is mirrored in Y relative to the detector's raw readout.
* A grid scan and a goniometer axis can both be set; they are no longer alternatives.
* `images_per_file` is chosen from the acquisition when it is not given: a rotation sweep of at most 20000 images goes into a single data file, a grid scan splits on whole fast-axis rows, and stills and serial keep 1000.
* The writer refuses a stream whose start message declares a different pixel format than its images carry, and a DECTRIS detector sending signed images is no longer declared unsigned.
* The image stream can carry the sample transformation chain (`transformations`, in the END message); a producer that does not send it gets the same chain built by the writer.
* rugnux: fixing the space group with `-S` no longer prevents the lattice from being found - a lattice indexed in a different setting is reindexed into that group's own setting, and a run whose crystal does not have that group's lattice stops and names the cell it indexed as, rather than reporting statistics that cannot describe it.
* rugnux: the per-image resolution estimate now predicts the resolution the merged data reach rather than the highest-resolution spot found, and is reported as `SPOT_RESOLUTION_ESTIMATE`.
* rugnux: two runs of the same command on the same images produce the same merged intensities; the azimuthal profile written alongside them is not yet reproducible in the same way.
* rugnux: the offline lattice refinement is bounded by iterations rather than by a wall clock, so a loaded machine can no longer refine to a different lattice; a live acquisition keeps its real-time bound.
* rugnux: the detector-frame modulation correction is fitted on a grid spanning the detector, so whether it is applied no longer depends on how far integration reached.
* rugnux: the geometry pre-pass no longer writes `<prefix>_01.mtz`, `_01.cif`, `_01.hkl` and `_01_image.dat`; the refined second pass writes those files under `<prefix>`, and that is the result to use.
* rugnux: `_process.h5` describes the pixel format of the images it links to, and is written on a thread of its own.
* rugnux: the detector geometry is also logged in XDS's convention (`ORGX`/`ORGY`, detector axis vectors, rotation axis), so it can be compared with an XDS refinement.
* rugnux: an image integrated in pyFAI through the `.poni` file written by `--mode calibration` comes out with the correct azimuth, and the file declares pyFAI's `orientation`, which needs pyFAI 2024.01 or newer. Radial integration is unchanged.
* rugnux: a rotation run is substantially faster throughout - beam-stop detection, first-pass indexing, geometry refinement, integration, scaling and merging - and observations outside the scaling resolution range are dropped as they are ingested. The refined geometry, the space group chosen and the merged statistics are unchanged.
* Faster spot finding and indexing, on the broker as well as in rugnux; the spots found and the lattices indexed are unchanged.
* A run reserves substantially less GPU memory: nothing is allocated for buffers that are never read, and a worker builds only the engines it uses.
* rugnux: with `-N` left at its default the per-image loop of `--mode mx` uses at most 16 workers per GPU, rather than one per hardware thread; an explicit `-N` is obeyed as given.
* CUDA 12 builds now contain device code for Volta, so the RHEL 8 packages and the portable Linux `.tgz` run on a V100; the CUDA 13 artefacts (RHEL 9, Ubuntu, Windows) remain Turing and newer.
* The build resolves a single Eigen for the whole project, and refuses to configure if Ceres picks up a different one; a build that mixed two Eigen versions was undefined behaviour and crashed at -O2.
* Documentation: a security page, and the supported GPU generations and minimum NVIDIA driver version of every released artefact.

**Breaking change to OpenAPI** - regenerate the client (`jfjoch-client` 1.0.0-rc.162, `frontend/src/client`):
* `dataset_settings.images_per_file` is no longer `default: 1000` and no longer accepts `0`; it is optional, and its minimum is 1. A client sending `0` (previously "one file for the whole run") is now rejected - omit the field instead, which for a rotation sweep gives the same single file.
* `file_writer_format` now defaults to `NXmxVDS`, matching the server's own default and the layout recommended for DIALS, XDS and CrystFEL. A generated client that fills in schema defaults and does not set the format explicitly will write VDS masters where it previously wrote legacy ones; set `NXmxLegacy` explicitly to keep them.

---------

Co-authored-by: jungfrau <jungfrau@mx-aare-test.psi.ch>
Reviewed-on: #72
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-08-25 08:21:39 +02:00
leonarski_f 538f3504d3 v1.0.0.rc-161 (#71)
Build Packages / build:windows:nocuda (push) Successful in 20m4s
Build Packages / Unit tests (push) Skipped
Build Packages / build:viewer-tgz:cpu (push) Successful in 16m5s
Build Packages / build:viewer-tgz:cuda (push) Successful in 17m26s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 27m46s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 20m17s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 26m13s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 23m17s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 28m11s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 19m30s
Build Packages / build:rpm (rocky8) (push) Successful in 24m34s
Build Packages / build:rpm (rocky9) (push) Successful in 21m30s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 23m33s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 20m18s
Build Packages / DIALS test (push) Successful in 18m23s
Build Packages / XDS test (durin plugin) (push) Successful in 11m30s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 10m16s
Build Packages / XDS test (neggia plugin) (push) Successful in 8m2s
Build Packages / Generate python client (push) Successful in 49s
Build Packages / Build documentation (push) Successful in 1m21s
Build Packages / Create release (push) Skipped
Build Packages / build:windows:cuda (push) Successful in 29m45s
This is an UNSTABLE release. It includes many experimental features, as well as many AI generated fixes. We recommend using rc.152 for production use.

* **rugnux: significantly better quality of results, and faster.** A large rework of integration, scaling, merging, geometry refinement and space-group determination, together with measurements the program previously made no attempt at - the direct beam before indexing, the beam stop, the goniometer rotation scale, and the stretches of a sweep the crystal did not deliver. A rotation dataset typically gains observations at better <I/sigma> and R_meas, and every `mx` and `scale` run writes a `<prefix>_report.txt` results report modelled on XDS's `CORRECT.LP`. Many defaults moved with it: spot detection is self-calibrating, beam-stop detection and rotation geometry post-refinement are on, resolution limits default to as far as the detector reaches, and ice-ring handling engages only where the crystal is measured to have ice.
* **jfjoch_viewer:** the beam-stop shadow, the detector calibration and the beam-centre measurement are reachable from "Analyze dataset"; the settings panel reports how the sample moved and how polarized the beam was; image rendering and interaction are faster.
* **Performance:** bitshuffle+LZ4 images are decoded on the GPU rather than on the host, with the bitshuffle inverse fused into preprocessing so the decompressed frame is never held in device memory.
* **Broker, writer, packaging and build:** image-slot lifetime and locking fixes, per-image datasets sized by the images actually written, the Debian/Ubuntu broker package renamed to `jfjoch`, and `image_analysis` compiling under MSVC again.

**Breaking change to the rugnux command line:**
* `--azint-only` and `--scale` are **removed**, replaced by `--mode azint` and `--mode scale`; the full pipeline is `--mode mx` and remains the default. A script passing the old flags now fails with the list of valid modes rather than silently running the wrong one.
* `-t`/`--stride` is **refused on rotation data**: skipping frames cuts every reflection's rocking curve, so the combined fulls and their partiality would be measured over frames the sweep never recorded. Select a contiguous range with `-s`/`-e` instead. `--mode azint` and `--force-still` still take a stride.

**Breaking changes to OpenAPI** - regenerate the client (`jfjoch-client` 1.0.0-rc.161, `frontend/src/client`) or read the affected fields as optional:
* `image_scale_b` is removed from the `plot_type` enum, so a client requesting that plot now gets an error rather than a curve.
* `azim_int_settings.high_q_recipA`, `spot_finding_settings.high_resolution_limit` and `spot_finding_settings.low_resolution_limit` are no longer `required`. All three mean "no limit at that end" when unset and are omitted from the response instead of carrying a placeholder value, which raises in a client generated from an rc.160-or-earlier spec. A value of 0 is still accepted and means the same thing.

**Breaking changes to the stored formats** - a consumer reading these fields must treat them as optional:
* The per-image image-scale B factor is no longer computed, so `/entry/MX/imageScaleBFactor` is absent from newly written HDF5 files and the corresponding key is absent from the CBOR DataMessage and END blocks. Files written by rc.160 and earlier still contain it and still open; nothing in the pipeline reads it any more.
* `_reflns.jfjoch_diffrn_ISa` now carries the whole-range `1/sqrt(a*b)` that XDS's ISa denotes, and the error-model `a` and `b` are reported in XDS's convention; the strong-reflection asymptote moves to `_reflns.jfjoch_diffrn_ISa_asymptotic`. **A file written by an earlier version carries the asymptote under the plain `ISa` name.**

Reviewed-on: #71
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-08-13 17:03:10 +02:00
leonarski_f 1c4dfd03e2 v1.0.0-rc.123 (#30)
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 10m22s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 11m30s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 11m41s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 12m32s
Build Packages / Generate python client (push) Successful in 18s
Build Packages / Build documentation (push) Successful in 54s
Build Packages / Create release (push) Has been skipped
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 9m44s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 8m53s
Build Packages / build:rpm (rocky8) (push) Successful in 9m40s
Build Packages / build:rpm (rocky9) (push) Successful in 10m37s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 9m54s
Build Packages / Unit tests (push) Successful in 1h6m33s
This is an UNSTABLE release.

* jfjoch_broker: Use newer version of Google Ceres for (potential) CUDA 13 compatibility
* jfjoch_broker: Improve performance of generating preview images, especially for large detectors (9M-16M)
* jfjoch_viewer: Improve performance of displaying images, especially for large detectors (9M-16M)
* jfjoch_viewer: Add more color schemes for better image readability
* HDF5: Common mutex for reading and writing HDF5 if both operations were to happen in the same executable
* HDF5: suppress warning if path (upstream group) doesn't exists when checking if leaf exists

Reviewed-on: #30
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
Co-committed-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-01-30 13:43:09 +01:00
leonarski_f 06c5b9cf7f 1.0.0-rc.65 2025-07-16 20:19:48 +02:00
leonarski_f 28d224afab version 1.0.0-rc.25 2024-11-22 21:25:20 +01:00