476a849c0ec1e8a2b4add24a080da8cfef6c77d5
246
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
476a849c0e |
indexing: decide an axis harmonic on which cell explains more of the spots
When two first-pass schemes return cells whose primitive volumes differ by a small integer, the validation-FRAME count cannot tell them apart: a spurious axis multiple indexes every frame its true sub-cell does, so both reach 60/60 and the count saturates. The rule that then decided the pair was unconditionally against the larger cell, so on a crystal with a real pseudo-translation the true cell could not win in any scheme order. Ask the same question at the granularity where it does not saturate: how many of the validation frames' SPOTS does each cell account for? That comparison leans towards the smaller cell by construction, and needs no threshold to do so. Acceptance is a fractional-Miller test, so multiplying an axis by n multiplies that axis's residual by n: the larger cell places every shared reflection n times less accurately than the sub-cell does, and loses outright the spots that sit in the tolerance margin. The only thing that can pay for that loss is the class of reflections the larger cell ADDS - empty for a spurious multiple, the superstructure's satellite rows for a real one - so the larger cell wins the count only when the extra periodicity is really there. The count is taken over the validation frames' whole spot lists, which reach far deeper into each frame's intensity distribution than the first pass's own accumulation cap, and a superstructure layer is faintest exactly where that cap cuts. Measured over the five crystals of this corpus where the two schemes return an integer-related pair, the larger cell accounts for 1.37x and 1.98x the spots on the two whose true axis was being halved, and 0.30x, 0.36x and 0.71x on the three where the doubling is spurious. All five come out right: the two keep the true cell and its deposited space group, the three reproduce the answer the old rule gave, to the digit. (One of the three has a bistable first pass - four builds give three answers, one of them without this change at all - so it is not evidence either way; the other two are reproducible.) What the added class holds is computed and reported next to the decision, because it is the physics the count is a consequence of. It is deliberately NOT thresholded, and that is the part of this that took the measuring. Refuted along the way: - An occupancy floor, which is how this was first written. Over the five crystals the arbiter is asked about, the emptiest index-n class reads 48.2, 41.6, 37.3, 25.4 and 7.1 %. The two the larger cell should win are the 41.6 and the 37.3, so the three it should lose bracket them on both sides, and a real superstructure elsewhere on the corpus reads 3.4 %, below all five. Recomputing the same statistic on the merged intensities over a sweep of I/sigma cuts leaves the ordering unchanged, so this is a continuum and not two populations: no floor separates them, and no amount of extra data would. The bimodality a floor needs was an artefact of a calibration set that contained no failure. - Requiring the two cells to stand in a genuine sub/super-lattice relation, the change of basis being integral. Measured, all five pairs are index-n relations to within 0.016 of an integer - the volume ratio is not the weak link. - Deciding it on the merge, by integrating and merging both cells. The worst failure does announce itself there (CC1/2 0.9994 -> 0.9566, ISa 18.6 -> 1.4), but it costs a second full integrate-and-merge, and the successes lose 13-30 % of their ISa where another failure loses 27 %, so the metric does not separate them either. - Requiring the sub-lattice class to be the STRONGER of the two, which is the right mechanism but the wrong observable at this point in the run: the sign it turns on lives in integrated intensities, and the spot finder reports no spot at all where a class is absent, so at first pass the same ratio reads 0.90 against 0.39 and 0.27. The ordering survives, the sign does not. The occupancy is maximally wrong on the worst failure because that cell is not a superstructure at all. A beam-centre error along the spindle translates the derotated cloud rigidly, and a lattice shifted by half a spacing is indexable only on a doubled axis - the shift needed scales as 1/(2L), so a long axis is the easy one to half-offset. In that doubled setting the even class is empty and the zero layer reads a negative mean intensity, which no crystal can do, while the odd class carries everything. Such a cell fits no index-n sublattice at all, so its added class reads the chance value (n-1)/n, the largest the occupancy can take, and the occupancy test reports the artefact as more real than any genuine superstructure. The spot count sees it for what it is, at 0.30x. So the beam-centre warning below is now suppressed only when the larger cell WINS. Declining it is a fall back to the default, and that warning - which names the beam centre and offers --estimate-beam-center - is then the most useful thing the run can say; on the half-offset mode it is the correct diagnosis. The same question asked unconditionally of the committed cell, the halved-axis probe, is not included. Measured over the committed cells of 29 crystals, 19 of 189 axes read above the 2 % floor it would have used and the largest read 30 %, and those largest readings are on crystals whose committed cell is already wrong - where doubling an axis is the wrong response. It rescues one crystal whose superstructure layer reads 3.4 %. That is not worth the rest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
54f2de31bb |
grid scan: say how the stationary spindle angle is stated, and pin it
A grid scan is a set of stills at a stationary spindle, and the angle it stood at is what relates one grid to another taken elsewhere on the circle. Users were finding an all-zero omega in the file and concluding the angle could not be recorded at all. It already can, and has since the goniometer and the grid scan stopped being alternatives: send the axis with step 0 and its start angle, and that angle is written per image into the NXmx sample chain, read back by reader/, and taken by dials.import as a set of stills. Measured on a generated 12-image grid: the placeholder file carries omega = 0 x 12, the same file with the axis sent at step 0 carries omega = 90 x 12, and dials.import reports "still: 1, sweep: 0" for both. Nothing in the code needed changing, so nothing was; what was missing was that nobody could tell, and that no test held the behaviour down. So: the API and the HDF5 documentation now say it in as many words, and three tests pin the three legs the value crosses - the OpenAPI request (which used to drop the grid scan whenever an axis was present, unpinned until now), the CBOR start message, and the file round trip. Also corrects a claim two comments and the HDF5 page were making. NXmx can express "no rotation" perfectly well - a sample may depend_on "." - so the placeholder is not there for the standard's sake. It is there because dxtbx cannot read a sample chain of translations alone: strip the rotation axis from a grid scan master and dials.import dies in get_dxtbx_goniometer with a matmul dimension mismatch. Recorded so nobody removes the placeholder on the strength of the standard. One thing the change does not fix, because it cannot: a stationary angle is invisible to DIALS when a grid scan is present. dxtbx picks the first varying axis as the scan axis, which is a grid translation, so the oscillation reads (0, 0); and with exactly one rotation axis in the chain it builds a single-axis goniometer whose fixed rotation is the identity, never consulting the angle. The same angle IS visible when it is the only candidate (oscillation reads (90, 0)) or when a Smargon head puts a second rotation axis in the chain (the setting rotation then carries it). The value is in the file and correct either way. |
||
|
|
92a615085d |
grid scan: the snake reverses on the acquisition row, not the display row
GetElementPosFast_step took the snake parity from GetElementPosSlow_step,
which is the DISPLAY row: it is the acquisition row r = image / n_fast,
flipped to (n_slow-1) - r when the slow step is negative. So for a negative
slow step the parity it hands back is parity(n_slow-1) XOR parity(r), and
with an even n_slow that is inverted on every row - the whole raster comes
out mirrored along the fast axis. An odd n_slow leaves it correct, so the
same scan collected with 20 or 25 images disagreed about where image 0 sat:
n_fast=5, fast +1.5 um, slow -2.5 um, snake on gives images 0..4 at fast
index 4,3,2,1,0 with 4 rows and 0,1,2,3,4 with 5 rows. A positive slow step
was correct at both counts, and so was every non-snake configuration.
Snake means the stage reverses direction on alternate rows in acquisition
order, so the parity has to come from the acquisition row. Taking it from
image_number / n_fast directly makes the fast index independent of the slow
axis and of the row count, and drops the call into the display-row function
that caused the coupling. vertical_scan only relabels which axis is fast, so
it was wrong in exactly the same way and is fixed by the same line.
Affected files: written by an affected build, with snake on, a negative
grid slow step (step_y for a horizontal scan, step_x for a vertical one),
and an even number of rows. Their /entry/sample/transformations/grid_scan_x
or _y is mirrored along the fast axis, as was the grid map in the frontend
and the viewer - both mirrored together, which is why neither showed it.
Tests: the interaction of snake with the step signs was never asserted, only
each in isolation, so add a table over snake x {+,- fast step} x {+,- slow
step} x {even, odd row count} x {horizontal, vertical} asserting positions,
plus a case running one affected configuration through GetXContainer_m /
GetYContainer_m and Rearrange. Every pre-existing assertion is unchanged and
still passes; only the four negative-slow, even-row cells of the product
move.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
|
||
|
|
3be79fb1b6 |
report: name the direction the anisotropy warning is about
The warning said the diffraction limit "runs from 3.34 to 2.45 A depending on direction" and stopped there, so a reader was told the crystal is anisotropic and given no direction to act on. It fires on 19 of 113 datasets in the battery. The eigenvectors are already measured and already written to the merged mmCIF as `_reflns.pdbx_aniso_B_tensor_eigenvector_N_ortho`; they had simply never reached the human report. The tensor is fitted on s = frac.mat * (h,k,l), so in that Cartesian frame a*, b*, c* are the rows of frac.mat and naming the axis is one dot product per eigenvector. The label is exact in every Laue class that has a free tensor direction except triclinic, and the cosine is printed so a loose fit shows as one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
7b1caa6ce5 |
mmcif: the free set is _refln.status = f, not a column of our own
The merged mmCIF carried the R-free flag in `_refln.status_free` as 1/0 and
wrote `o` into `_refln.status` on every row. `_refln.status_free` is not in the
PDBx/mmCIF dictionary - it is absent from mmcif_pdbx v4.0, v5.0, v5.288 and
v5.362, from CCP4's and phenix's shipped copies, and its wwPDB item page is a
404 - and no deposited structure-factor file uses it. `_refln.status` is the
item that carries the free set, with `f` for a test reflection and `o` for a
working one; on a deposition that also carries `_refln.pdbx_r_free_flag` the
two agree exactly.
Measured on a real merged file this run wrote:
CCP4 cif2mtz refuses the file outright - "Unexpected context type for
category REFLN" from its dictionary-validating parser, exit 1,
a 12-byte truncated MTZ. Dropping the non-dictionary column is
what fixes it: the same file without it converts.
gemmi converts, but its cif2mtz spec knows only `status` and
`pdbx_r_free_flag`, so FreeR_flag comes out 1 everywhere and
the free set is silently lost - R-free would then be computed
on the working set.
phenix worked, but only by a filename heuristic matching the words
"status" and "free".
Writing `f` while keeping the extra column is worse than either, because phenix
then finds two candidate free-set arrays and refuses the file, so the column
goes in the same change. After it, all three read the same 5% test set.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
|
||
|
|
90d0d3c3f9 |
docs: the python client reference, for the two schemas that gained fields
docs/python_client/docs is generated from the API spec and copied in by update_version.sh, so it goes stale between releases. The calibration convergence gate and the beam size both added properties without it; this is the same generator run those commits should have carried. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
aa6073d41f |
api: chi is a reported angle, not a travel limit
The Smargon at the SLS 2.0 MX beamlines reports its own axis positions with readout noise, so a chi parked at zero comes back as about -1e-7 degrees. The schema bounded chi_deg to [0, 90], so an ordinary "chi is at zero" setup was refused - and the same holds at the other end of the arc, where a chi parked at 90 reads just above it. Both end-stops are exactly where a static positioner is left, so widening the range would only move the problem. The bound bought nothing. Chi never enters any computation: OpenAPIConvert puts it in SmargonPosition, DiffractionExperiment::BuildTransformationChain hands it to a rotation transformation verbatim, HDF5NXmx writes it and HDF5MetadataSource reads it back. No downstream reads its sign, and a rotation is defined for any angle. phi, the sibling angle in the same object with the same semantics, has never been bounded. What was left was a restatement of a hardware travel limit that the goniometer enforces itself, and its only observable effect was to refuse a value the instrument genuinely reported. It was also not enforced where a server-side check would matter: the cpp-pistache-server generator does not recurse into a nested object model, so Dataset_settings::validate never calls the Smargon model's. The rejection was raised by the generated python and TypeScript clients, which do check. Regenerated the C++ server model, the TypeScript client and redoc-static.html; python-client is gitignored and comes from gen_python_client.sh. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
8a04773d1d |
api: record the beam size at the sample, and write it where NXmx puts it
dataset_settings gains beam_size_x_um and beam_size_y_um, the horizontal and vertical size of the X-ray beam where it meets the sample. They follow the same route total_flux takes - OpenAPI, DatasetSettings, the CBOR start message, the HDF5 master, and back out of a stored file - and nothing consumes them; this is metadata a beamline can state and a downstream program can read. NXmx puts this in the application definition rather than the base class: not NXbeam's extent (rank 2, nP x 2, per scan point, always FWHM of a rectangular aperture) but NXmx's own incident_beam_size, a recommended rank-1 two-element array in the order x, y. Both are live and neither is deprecated, so the choice matters; the MX definition wins in an MX file. Written as one array with a units attribute of "m", like every other length in the master, so the settings hold micrometres and FillMessage converts once. The unit table of ReadLength_m becomes LengthUnitFactor so the array read can share it: a master written elsewhere may state this in millimetres. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
7890149ad6 |
writer: peakCountUnfiltered in the master, like its four siblings
/entry/MX/peakCountUnfiltered was written only by the data-file plugin. The four other per-image spot counts are written to both the data files and the master, so a reader holding just the master got every count except the unfiltered one. It was not only a missing dataset. HDF5MetadataSource already reads /entry/MX/peakCountUnfiltered from the master and falls back to /entry/MX/nPeaks when it is absent - and nPeaks is the number of spots *stored* for an image, i.e. after the spot budget truncates. On a VDS master the fallback therefore substituted the post-filter count for the unfiltered one silently, and the two differ precisely on the images that hit the budget. EndMessage::spot_count is the right member: it and the data-file plugin's spot_count_total both come from DataMessage::spot_count (the end-message copy via ScanResultElem::spot_count), and CountSpots() sets that from spots.size() before FilterSpotsByCount() applies the budget - "spots found before filtering", as docs/HDF5.md already described it. The CBOR end block already carried spot_count on both encode and decode, so no message or protocol change was needed. SaveVectorIfMissing keeps the NXmxIntegrated case correct, where the data-file plugin has already written the dataset into the same file. Verified by writing files in all three formats and dumping them: the new dataset appears in the legacy, VDS and integrated masters and matches its sibling peakCountLowRes in value, datatype and read-back precedence. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
ef5da29319 |
calibration: a fit that hands back the file's own tilt is not a calibration
A powder calibration is run because the file's geometry is in doubt, so a fit that quietly returns part of that file has answered nothing - and it is indistinguishable from one that worked, down to the residual and the sigmas arranged around it. On one of four LaB6 exposures of one detector the tilt came out at 2.93x its own sigma, a hundredth under the significance gate, so it was declined and pinned - at the master's hardcoded rot1 -0.08, rot2 -0.22 deg. That is eight times the tilt just refused, on no evidence, and worth 10 px of PONI at 190 mm. rugnux printed it to four decimal places, wrote the .poni, and exited 0. Judge the result on provenance instead of on any residual: a geometry is a measurement only if every parameter in it came from this data. Two ways out of the fits do not qualify - a covariance that never conditioned, so the fit cannot say what it determined, and a declined tilt pinned at a non-zero value from the file. A declined tilt over a file stating no tilt still qualifies, because reporting no tilt is then exactly what was measured; so does --no-refine-tilt, because a hold that was asked for is a stated choice and not a silent substitution. No single number separates the four. rms is 2.465 px against 1.44-1.64; the significance of all four lies between 2.93 and 4.47, so the gate is nearly a coin flip at these distances and moving it would only recalibrate on one population; and the failed fit has the TIGHTEST parameter sigmas of the set, because pinning the tilt removes the tilt/centre correlation that inflates a good fit's. The spot cross-check reads 13.5 px against 0.98-2.66, but 10.4 px of that is the pinned tilt moving the PONI - the same defect one step downstream, not independent evidence. On a failure rugnux says so, writes no .poni - a PONI file states where the detector is and has no field in which to say it does not know - writes the JSON with converged false and the reason beside it, and exits non-zero. The re-binning pass now prefers a converged refit over a non-converged one whatever its residual, so a tilt an earlier pass measured is not what a later one gets pinned at. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4bb3d44983 |
viewer: label the merge plot over the range it is drawn on
Build Packages / build:windows:nocuda (push) Successful in 16m58s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 19m38s
Build Packages / build:windows:cuda (push) Successful in 19m52s
Build Packages / build:viewer-tgz:cpu (push) Successful in 21m15s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 22m55s
Build Packages / build:viewer-tgz:cuda (push) Successful in 23m26s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 27m44s
Build Packages / build:rugnux:windows (push) Successful in 11m1s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 28m7s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 19m49s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 23m23s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 26m16s
Build Packages / build:rpm (rocky9) (push) Successful in 23m21s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 22m45s
Build Packages / Generate python client (push) Successful in 29s
Build Packages / build:rpm (rocky8) (push) Successful in 28m58s
Build Packages / Create release (push) Skipped
Build Packages / Build documentation (push) Successful in 1m34s
Build Packages / DIALS test (push) Successful in 26m3s
Build Packages / XDS test (durin plugin) (push) Successful in 10m42s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 28m4s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 11m1s
Build Packages / Unit tests (push) Successful in 2h8m52s
Build Packages / Unit tests (pull_request) Successful in 1h26m12s
Build Packages / build:windows:nocuda (pull_request) Successful in 16m5s
Build Packages / build:viewer-tgz:cpu (pull_request) Successful in 15m31s
Build Packages / build:viewer-tgz:cuda (pull_request) Successful in 17m5s
Build Packages / build:rugnux-tgz (x86_64) (pull_request) Successful in 15m7s
Build Packages / build:rugnux:windows (pull_request) Successful in 9m47s
Build Packages / build:rugnux:aarch64 (cross) (pull_request) Successful in 9m26s
Build Packages / build:rpm (rocky9_nocuda) (pull_request) Successful in 18m34s
Build Packages / build:rpm (ubuntu2204_nocuda) (pull_request) Successful in 24m53s
Build Packages / build:rpm (ubuntu2404_nocuda) (pull_request) Successful in 17m31s
Build Packages / build:rpm (rocky8_sls9) (pull_request) Successful in 26m29s
Build Packages / build:rpm (rocky9_sls9) (pull_request) Successful in 22m26s
Build Packages / build:rpm (rocky8) (pull_request) Successful in 25m4s
Build Packages / build:rpm (ubuntu2404) (pull_request) Successful in 19m14s
Build Packages / DIALS test (pull_request) Successful in 18m12s
Build Packages / XDS test (durin plugin) (pull_request) Successful in 11m23s
Build Packages / XDS test (JFJoch plugin) (pull_request) Successful in 11m9s
Build Packages / XDS test (neggia plugin) (pull_request) Successful in 9m51s
Build Packages / Generate python client (pull_request) Successful in 33s
Build Packages / Build documentation (pull_request) Successful in 42s
Build Packages / build:windows:cuda (pull_request) Successful in 14m44s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 9m11s
Build Packages / XDS test (neggia plugin) (push) Successful in 9m59s
Build Packages / build:rpm (rocky8_nocuda) (pull_request) Successful in 20m18s
Build Packages / build:rpm (rocky9) (pull_request) Successful in 21m42s
Build Packages / build:rpm (ubuntu2204) (pull_request) Successful in 23m24s
Build Packages / Create release (pull_request) Skipped
The merge-statistics window asked for an absolute y-axis - CC1/2 and CCref on 0..100, everything else from 0 - by setting the range on the chart's value axis after JFJochSimpleChartView::UpdateData had already built the chart. The visible tick labels are a separate QCategoryAxis whose entries UpdateData had generated from the range of the data, and those entries were not rebuilt. So the plot was drawn over 0..100 while the labels down its left edge covered only 91.5..100 and crowded into the top tenth; the numbers matching the drawn range appeared only on the right-hand grid axis, which the same call had made visible. CC1/2 showed it worst because its range is the narrowest, but every metric was affected. Give UpdateData the range instead, so the ticks and both axes come from one number. The side-panel azimuthal-integration chart passes no range and renders pixel-identically. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
34e1ec8fb9 |
docs: keep the analysis landing page and drop a trailing transition
The four-way split leaves CPU_DATA_ANALYSIS.md as the landing page the toctree points at, and one part ended on a horizontal rule, which docutils refuses at the end of a document. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
889c9b6cbb |
docs: one changelog line for the rugnux documentation work
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
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 |
||
|
|
ee4c23b9a2 |
docs: the phenix label override, for the mmCIF as well as the MTZ
Both merged formats hit the same refusal - the file carries the mean and the Bijvoet pairs - but the label vocabularies differ per format and the MTZ incantation fails on the mmCIF with a fresh error. Give both measured commands, and say it is one behaviour in two formats, not a difference between our files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
64182bb295 |
docs: an overview page - what a rugnux run does, in order
The page that did not exist: one paragraph per stage from opening the file to the written reflections, each linking into the data-analysis reference part that carries the depth, with the stills differences at the end. First entry after the landing page, and the landing page says to read it first. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
20f869c0b8 |
docs: split the data-analysis reference into four parts along the pipeline
CPU_DATA_ANALYSIS.md becomes a short landing page (scope, part map, references) over four parts in pipeline order - images to spots (0-3), indexing and geometry (4-7), integration/scaling/merging (8-12), space group and validation (13-14). Pure moves: the section numbering is continuous and unchanged, since the rest of the documentation and the source cite sections by number. Inbound topical links now land on the right part; the build has zero warnings and the rendered-HTML anchor check finds no dead link. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
376aa0a2e0 |
docs: split RUGNUX.md into one page per job, and put rugnux first
The 1274-line page becomes a landing page (quick start, the page map, where it fits) plus seven pages a reader can answer one question from: installing, what rugnux reads, running it, integration with other programs, the results report, advanced usage, and powder calibration. Content is moved, not rewritten - only the connective sentences at each page top are new. Every internal anchor is remapped to its new page and every inbound link (DEPLOYMENT, TOOLS, HDF5, CPU_DATA_ANALYSIS) updated; the built site has zero Sphinx warnings and an anchor check over the rendered HTML finds no dead link. index.rst leads with the rugnux group, then acquisition, FPGA, reference and project. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
784cf87cc8 |
docs: put the generated python-client reference behind one landing page
The OpenAPI Python client owned the sidebar: DefaultApi's 128 method anchors plus the 64 hidden-glob model pages were 195 of its 256 entries, because sphinx_material's globaltoc includes hidden toctrees by default. A new PYTHON_CLIENT.md landing page carries the links and a hidden glob toctree, and globaltoc_includehidden is off, so every generated page is still built and reachable (verified: 64 model pages + DefaultApi render, zero warnings) while the sidebar drops to 61 entries. docs/review/ joins exclude_patterns so a local, gitignored review report can never again be rendered into the published site. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
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 |
||
|
|
86f8edfabc |
docs: the FFT axis ceiling, one-sweep inputs, exit status and _anom.ccp4
The longest FFT axis (500 A, no flag; -C moves it) was implied twice and never stated, and its failure mode is a plausible sub-cell rather than a refusal. One input is one sweep - said affirmatively where inputs are described instead of in an aside about pointless. A default 50 A low-resolution cut discards real reflections on a very large cell; the option row says so. Exit status is documented for scripts (0 = completed, non-zero = stopped), and the --model output list gains _anom.ccp4, which was written but undocumented. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
87b1402c50 |
docs: hand the output onward - downstream commands and the three blocking facts
The page named phenix, REFMAC, POINTLESS, AIMLESS, careless and SHELXC a dozen times without one command line, and left unstated the three facts those programs stop on: the free-flag convention (0=work 1=free; REFMAC needs FREE 1), the phenix label choice the double intensity array forces, and the unmerged file's header symmetry and sort order (determined group, sorted H K L M/ISYM BATCH - WriteReflections.cpp sorts it). A new 'Taking the data onward' section carries the worked lines, the careless column renames (BGVAR is a variance), and the Phaser SGALTERNATIVE keywords for the enantiomorph the report leaves open. The POINTLESS series trap now names ALLOW OUTOFSEQUENCEFILES instead of telling users to touch their data, the report-grep block warns that SPACE_GROUP_NAME carries one member of an enantiomorphic pair by convention, and the P1 cross-check's free set is declared to be its own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
b1ae1ece67 |
docs: a default rotation run already writes real anomalous data
The -A row and FRIEDELS_LAW=TRUE together read as 'without -A your Friedel pairs were averaged', which is false: the rotation merge always keeps the Bijvoet split and the default .mtz/.hkl carry it (WriteReflections.cpp, BuildMergedRows). Say what -A actually changes - the counting basis and the error model - and give the merged MTZ's exact column labels, which scripting against phenix needs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
40cb5cd6db |
docs: the quick start tells the truth about inputs, outputs and the cutoff
A default rotation run writes seven files, not five - the two the list omitted are most of the bytes. The input is any NXmx/EIGER master or miniCBF sweep, which the page said only 350 lines later after twice implying Jungfraujoch data only. The CC1/2-0.30 resolution trim moves up to the quick start, and _image.dat's columns are finally named (ScalingResult.cpp writes a # header). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
40403d7beb |
docs: the direction grid bounds the longest findable axis; say so in 5.3
The ranking-not-sampling observation was measured on the coplanar-shortlist rescue and is scoped to it now. The angular-resolution bound theta < d_min/(2a) means the shipped 16384-direction grid resolves axes only to roughly 120-150 A, far below the 1200 A the accepted maximum admits, and the failure mode is a plausible sub-cell, not a refusal - the reader raising fft_max_unit_cell alone deserved to know it cannot work. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
32adb882eb |
docs: disambiguate the reused symbols and the bandwidth definition
sigma_bw is one physical smear written in reciprocal units in 8.2/11.1 and in pixels in 9; the two Delta-phi of the partiality formula are named; 13.5's |s| = 1/d is reconciled with the s = sin(theta)/lambda of 10.6/14.2; bandwidth is the rms spread, with the FWHM-input conversion (/2.355, BraggIntegrationEngine.cpp) stated. The ice-extinction clause now covers (104), not only (00l), and the AIMLESS <I/sigma> = 2 constant is named as AIMLESS's default rather than pointed at a criterion this project does not use. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
e94548c614 |
docs: the twinning exemption list, and why -1, 2/m and mmm are not on it
The stated criterion (a merohedral twin law exists) formally exempts every holohedral Laue class, but the code (TwinningAnalysis.cpp) deliberately keeps the low-symmetry ones eligible because pseudo-merohedral twinning through a special metric cannot be excluded there. Say both halves, and give the reference values of the statistics so the mmCIF numbers can be read. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
9814846ebd |
docs: state the q convention at every numeric q and on the q flags
Every q in the pipeline is 2*pi/d (Definitions.h, the azint bin mapping), but the numbers in 3.3, 7.6 and 10.10 and the --azim-* flags never said so, and a reader taking q = 1/d would set --azim-q-spacing or --azim-max-q wrong by 2*pi. The 7.6 ice triplet is spelled out so 'within 0.06 of one another' reads as the adjacent-ring spacing it is. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
14b79b0314 |
docs: fix the sigma-ratio, the profile-fit term and the outlier cut as written
The 1.109 in 9.2 is the variance ratio of the shipped 4/6/13 stencil (45/408 pixels), not the sigma ratio the sentence attached it to, and the effect is a bound attained on weak reflections, not uniform. The profile-fit background term is (sum P/v / sum P^2/v)^2 var(b), matching the code; the undefined w is gone. The 13.3 refit cut is N^2 times the model variance (RotationScaleMerge.cpp), not N*sigma^2. The sigma floor is 1 count. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
f68fd3d7af |
docs: pin the sign convention of the back-rotation in 7.3
R(phi) rotates the observations by +phi about the stored axis, which makes it the inverse of the crystal's own rotation - the reading under which the formula as written is correct, and the same convention that has the exported MTZ batch axis and the XDS echo negated. Said explicitly, so a reimplementation cannot take R(phi) for the crystal rotation and land at 2*phi. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
694e687fce |
docs: name the beam parameter as the PONI and state the laboratory frame
Section 1.1 called (x_beam, y_beam) the direct-beam position while constructing it as the point of normal incidence; on a tilted detector the two differ by D*tan(tilt), and a reader loading a header direct beam into it would be wrong by several pixels. Name it as the PONI (the system-wide convention, DETECTOR_GEOMETRY.md), say which point the construction pins, and state the laboratory frame's axes and handedness, which the rotation-sign, R-centring and Bijvoet-hand discussions all silently depended on. Two-theta is computed as a two-argument arctangent; say so. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
d3879d0112 |
docs: the change of hand is a label, only the ambiguity choice reindexes
Both pages said in one place that adopting a model's enantiomorph moves no reflection, and in another that it is applied to the merged reflections and exchanges the Bijvoet mates. The code (ModelValidation.cpp, AdoptModelFrame) does the former: the model's group is a label on the written files, I(+)/I(-) stay as measured, and only the merohedral-ambiguity reindex touches reflection indices. Say that once, consistently, in CPU_DATA_ANALYSIS.md §14.5 and in the two RUGNUX.md passages that carried the wrong reading. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
200a2d53b7 |
report: name the groups the data could not separate, as keys
SPACE_GROUP_NAME is a scalar and reads like a determination, and the docs nominate a grep of it as the interface. Where several groups predict the same absences it is one of them, picked by convention: the report said so in prose and nothing machine-readable carried it, so a script recorded a coin flip as an answer. Refining against a deposited model in the wrong enantiomorph gives R = 0.549. Four keys, none of which change which group is adopted: the alternatives the data cannot separate; whether the enantiomorph was determined, given, or is undetermined - decidable from the group number alone, so it is answered even where the search did not run; and the higher point group whose promotion was refused, with its reason, which until now existed only as prose. REPORT_VERSION is 5. No existing key changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
247130a841 |
-S: check the metric the fixed group needs, not only its centring
Both guards on a user-fixed space group keyed on the centring differing, so an axis permutation within the same centring passed: a run that indexed a=37.909 b=78.031 c=77.594 and was told -S P41212 - which needs a=b with the 4-fold along c - merged through operators that do not act on its own indices, and --mode scale on it reported COMPLETENESS= 195.3, an arithmetically impossible number, without complaint. MetricViolation asks the setting-independent question instead: a group's rotations must leave the cell's metric tensor invariant. The re-seating arm now runs on either failure, so a permuted cell can be reindexed into the setting the group needs rather than merely refused; the refusal arm catches what re-seating could not fix. --mode scale never reaches either arm, so it gets the same test where it fixes its cell and its group, which is the only place an impossible completeness could still be produced. The tolerance is a refusal bound, so it sits above what a correct answer reaches: over 113 corpus runs every group determined from its own cell scores under 0.032 and the permuted case scores 0.764. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
47ec939c39 |
merged MTZ: FreeR_flag in the convention its own column name carries
The column was written with 1 for the test set and 0 for the working set - the phenix/CNS numbering - under FreeR_flag, which is CCP4's column name. CCP4's own freerflag writes 0..19 with 0 as the test bin, and REFMAC5's default FREE 0 reads it that way, so REFMAC5 stopped on every file we wrote: "more than half of reflections are in free R set", then "Cannot switch free R flag", exit 1. phenix auto-detects either numbering with equal confidence (measured on both, score 3 each), and rugnux's own reference-MTZ reader already takes flag 0 as the test set, so 0 = test is the numbering that works everywhere and the one the rest of the code assumes. This changes every merged .mtz we write: a script that reads FreeR_flag == 1 as the test set has to be inverted. The mmCIF's _refln.status_free is a separate item with its own convention and is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
762c093b10 |
unmerged MTZ: export the events the merge would keep, not every event
The export declared its rows FULL - LDTYPE=2 in the batch header, M=0 in M/ISYM - while filtering them on --min-partiality alone (0.02), where the 3D combine also applies --min-captured-fraction (0.7 on rotation) to the same summed event. AIMLESS reads the FULL declaration, reports "all runs have only fulls" and ignores FRACTIONCALC, so an event that caught a twentieth of its rocking curve entered the merge whole, with a small sigma, and was weighted heavily. There was no cut for the reader to make: the column that would let it make one is the one the reading program has been told to ignore. Dropping those events moves AIMLESS's Rmerge at 1.8 A from 1.353 to 0.694 and CC(1/2) from 0.985 to 0.993. FRACTIONCALC itself is unchanged, values above 1 included. Each part's partiality is the erf pair the predictor computed on that frame, from that frame's own refined lattice and mosaicity, so the parts of one event do not tile the rocking curve exactly - the offset steps by the wedge to within 12% of it, and the sums land in a peak at 1.000 whose 95th percentile is 1.09. It is an honest estimate of a captured fraction, and it is not the number rugnux scales on, so clamping it would only hide the spread. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
b33cae9068 |
unmerged MTZ: a batch header for every image the sweep spans
The header set was built from the batches that produced an observation, so an image that indexed nothing left a hole in the phi series. AIMLESS starts a new run at such a discontinuity: measured on a 360 degree sweep with 67 unindexed images it made 23 runs of one sweep, its scale model diverged, Rmeas overflowed to -1266 and the result no longer correlated with rugnux's own merge (Pearson 0.0018). Renumbering the batches contiguously does not help - the split is on phi, not on numbering - so the fix is a header per image over the span the observations cover. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
9c19646f6a |
refine: fit the direction of the goniometer axis, not its length
The residual applies angle_rad * |rot_vec| and rot_vec is a free three-vector, so the first pass has been fitting a goniometer rotation SCALE nobody asked for. GoniometerAxis::Axis() then normalises it away on write-back, and RotationIndexer scores the candidate with the normalised axis - so the cell that won the fit is judged under a rotation model the fit did not use. Measured over 43 rotation datasets: the length reaches 1.2%, and the fit-vs-score disagreement a median 0.124 deg and up to 6.19 deg of goniometer angle, against rocking widths of 0.05-0.36 deg. That score picks the lattice class, which nothing later revisits. The fitted length is not a usable measurement of anything either: on synthetic data it recovers 54% of a known scale error, repeated first passes on one dataset disagree with each other in sign, 26 of 43 datasets disagree with themselves, and on the one dataset with a proven 1.3% stage fault it comes out negative. It is absorbing other systematics. The rotation scale is measured properly, once, with cross-validation and gates, in PostRefine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
d32c8d526b |
geometry: make which tilt component is restrained two numbers rather than a shape
Both directions of the tilt now go through the same restraint, with a budget each in shared direct-beam pixels; zero means free. Swapping which component is held, or holding both, is a change to two constants. The perpendicular budget is zero today, which is the arrangement the measurements support: it is the component whose conditioning tracks the 2theta the fit reaches, while the parallel one's does not move with 2theta at all - a conditioning number that ignores the data is the prior talking. This is a clarity change, not a precision one, and the record should not read otherwise: the standing 'freeing the tilt inflates both beam components' cost is the perpendicular component's doing, not the parallel one's, so restraining the parallel one recovers essentially none of it. What it buys is that a beam-centre error is no longer reported as an angle. |
||
|
|
5869a27fe2 |
docs: one line per behaviour change in the rc.166 changelog, named by program
Build Packages / build:windows:nocuda (push) Successful in 17m13s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 18m19s
Build Packages / build:viewer-tgz:cpu (push) Successful in 19m56s
Build Packages / build:windows:cuda (push) Successful in 20m10s
Build Packages / build:viewer-tgz:cuda (push) Successful in 22m46s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 23m24s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 28m9s
Build Packages / build:rugnux:windows (push) Successful in 11m22s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 28m51s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 20m32s
Build Packages / build:windows:nocuda (pull_request) Successful in 16m12s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 22m16s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 26m35s
Build Packages / build:rpm (rocky9) (push) Successful in 23m47s
Build Packages / build:windows:cuda (pull_request) Successful in 21m40s
Build Packages / build:rpm (rocky8) (push) Successful in 28m52s
Build Packages / Generate python client (push) Successful in 32s
Build Packages / build:rugnux:windows (pull_request) Successful in 16m1s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 23m42s
Build Packages / Create release (push) Skipped
Build Packages / XDS test (durin plugin) (push) Successful in 11m48s
Build Packages / Build documentation (push) Successful in 2m8s
Build Packages / DIALS test (push) Successful in 26m29s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 27m23s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 11m6s
Build Packages / build:rugnux:aarch64 (cross) (pull_request) Successful in 7m41s
Build Packages / build:viewer-tgz:cpu (pull_request) Successful in 16m45s
Build Packages / build:rugnux-tgz (x86_64) (pull_request) Successful in 17m26s
Build Packages / build:viewer-tgz:cuda (pull_request) Successful in 19m32s
Build Packages / build:rpm (rocky9_nocuda) (pull_request) Successful in 20m36s
Build Packages / build:rpm (ubuntu2204_nocuda) (pull_request) Successful in 20m10s
Build Packages / build:rpm (ubuntu2404_nocuda) (pull_request) Successful in 16m42s
Build Packages / build:rpm (rocky9_sls9) (pull_request) Successful in 20m5s
Build Packages / build:rpm (rocky8_sls9) (pull_request) Successful in 24m27s
Build Packages / build:rpm (rocky8) (pull_request) Successful in 25m42s
Build Packages / XDS test (durin plugin) (pull_request) Successful in 11m14s
Build Packages / build:rpm (ubuntu2404) (pull_request) Successful in 21m13s
Build Packages / Generate python client (pull_request) Successful in 16s
Build Packages / Build documentation (pull_request) Successful in 55s
Build Packages / XDS test (JFJoch plugin) (pull_request) Successful in 10m37s
Build Packages / XDS test (neggia plugin) (pull_request) Successful in 8m34s
Build Packages / DIALS test (pull_request) Successful in 19m12s
Build Packages / Unit tests (push) Failing after 2h4m37s
Build Packages / Unit tests (pull_request) Failing after 1h28m18s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 8m26s
Build Packages / XDS test (neggia plugin) (push) Successful in 9m18s
Build Packages / build:rpm (rocky8_nocuda) (pull_request) Successful in 22m46s
Build Packages / build:rpm (rocky9) (pull_request) Successful in 21m30s
Build Packages / Create release (pull_request) Skipped
Build Packages / build:rpm (ubuntu2204) (pull_request) Successful in 26m2s
The section had grown to eighteen entries, several of them describing the same report or carrying detail that belongs in a commit message. Consolidated to thirteen one-liners, each naming the program it concerns - rugnux, jfjoch_writer, jfjoch_broker, jfjoch_viewer - so a reader can find what changed in the part they use. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
51e90f5549 |
report: say what the measured detector tilt is worth, and what it is not
The report invited the reader to check REFINED_DETECTOR_TILT against a powder calibration. Measured over two detectors' worth of crystals, that check misleads: the rotation fit does one outer round, so it leaves its starting value by only a small and crystal-dependent fraction of the distance to the calibrated value, and on the worse of the two detectors the per-crystal median sits an order of magnitude further from the powder answer than either method's uncertainty. A user comparing one run against their own calibration would conclude the calibration was wrong. What survives the aliasing is the direct beam, which is already printed beside it, and the MEDIAN of the tilt over several crystals on one detector - enough to show up a placeholder or a stale value in the file, not enough to replace a calibration. Say that, and say not to feed one run's value back into the instrument. The conditioning law (VIF = 4.40 tan(2theta_95)^-1.66) was tested as a per-run gate and does NOT order the errors - the crystals whose fit stays put are found in the best-conditioned band as often as the worst - so no threshold is added here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
1a25d396a8 |
docs: the beam centre is the PONI, the direct beam is the beam
Document direct_beam_x/direct_beam_y in the CBOR start-message table and in the HDF5 detectorSpecific catalogue, and add the section that says what the distinction is: the PONI is the foot of the perpendicular, the direct beam is where the beam lands, they are distance*tan(tilt)/pixel apart, and XDS ORGX/ORGY wants the second. Also record that the same field is a statement about the user's geometry in a broker master and a measurement in a rugnux _process.h5. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
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 |
||
|
|
8935a59721 |
rugnux: the two-pass guard says when it has no completeness, instead of printing 0.0%
The guard that judges the post-refined pass against the header-geometry pass reads completeness off
each pass's space-group search merge. That merge does not count possible reflections, so the quotient
is 0.0 on both sides and every de-novo rotation report has been printing
completeness 0.0% vs 0.0%, CC1/2 before corrections 0.994 vs 0.993
as though a comparison had happened. On the 28-dataset in-house battery that is all 28 runs. The
number is not just uninformative, it is a test that did not run: "completeness above 100% means the
cell is wrong" is the guard's wrong-cell arm, and it cannot fire against a constant zero, so only the
CC1/2 arm decides - which is computed on whatever data the pass kept, so a pass that discards a large
part of the sweep can post an equal CC1/2 and win.
Carry a measured flag beside the number. The arm is skipped and the log and PASS_DECISION say
"completeness not measured" where there is nothing to read. No behaviour changes: the arm could not
fire before either. The next commit gives the merge the count so it can.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
|
||
|
|
5ce7eb3014 |
docs: one changelog line for the geometry reporting work
Build Packages / build:windows:nocuda (push) Successful in 16m59s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 18m46s
Build Packages / build:windows:cuda (push) Successful in 19m22s
Build Packages / build:viewer-tgz:cpu (push) Successful in 21m9s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 23m18s
Build Packages / build:viewer-tgz:cuda (push) Successful in 23m34s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 27m54s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 28m2s
Build Packages / build:rugnux:windows (push) Successful in 11m24s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 20m57s
Build Packages / build:windows:nocuda (pull_request) Successful in 16m16s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 21m32s
Build Packages / build:rpm (rocky9) (push) Successful in 19m36s
Build Packages / build:rpm (rocky8) (push) Successful in 24m40s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 21m15s
Build Packages / Generate python client (push) Successful in 36s
Build Packages / build:windows:cuda (pull_request) Successful in 22m2s
Build Packages / XDS test (durin plugin) (push) Successful in 10m48s
Build Packages / Create release (push) Skipped
Build Packages / Build documentation (push) Successful in 1m13s
Build Packages / build:rugnux:windows (pull_request) Successful in 16m4s
Build Packages / DIALS test (push) Successful in 22m55s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 25m0s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 9m55s
Build Packages / build:rugnux:aarch64 (cross) (pull_request) Successful in 8m55s
Build Packages / build:viewer-tgz:cpu (pull_request) Successful in 14m4s
Build Packages / build:rugnux-tgz (x86_64) (pull_request) Successful in 15m26s
Build Packages / build:viewer-tgz:cuda (pull_request) Successful in 16m8s
Build Packages / build:rpm (rocky9_nocuda) (pull_request) Successful in 14m38s
Build Packages / build:rpm (ubuntu2204_nocuda) (pull_request) Successful in 18m54s
Build Packages / build:rpm (ubuntu2404_nocuda) (pull_request) Successful in 16m48s
Build Packages / build:rpm (rocky8_sls9) (pull_request) Successful in 22m10s
Build Packages / build:rpm (rocky9_sls9) (pull_request) Successful in 18m19s
Build Packages / build:rpm (rocky8) (pull_request) Successful in 20m3s
Build Packages / XDS test (durin plugin) (pull_request) Successful in 9m52s
Build Packages / Generate python client (pull_request) Successful in 18s
Build Packages / build:rpm (ubuntu2404) (pull_request) Successful in 16m28s
Build Packages / Build documentation (pull_request) Successful in 48s
Build Packages / XDS test (JFJoch plugin) (pull_request) Successful in 7m58s
Build Packages / DIALS test (pull_request) Successful in 18m41s
Build Packages / XDS test (neggia plugin) (pull_request) Successful in 6m11s
Build Packages / Unit tests (push) Successful in 1h59m35s
Build Packages / Unit tests (pull_request) Successful in 1h24m17s
Build Packages / build:rpm (rocky9_sls9) (push) Failing after 3h10m51s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 8m26s
Build Packages / XDS test (neggia plugin) (push) Successful in 7m43s
Build Packages / build:rpm (rocky8_nocuda) (pull_request) Successful in 18m13s
Build Packages / build:rpm (rocky9) (pull_request) Successful in 18m14s
Build Packages / build:rpm (ubuntu2204) (pull_request) Successful in 21m19s
Build Packages / Create release (pull_request) Skipped
Three lines were appended over the course of the work - the tilt and direct beam in the report, the measured tilt beside them, and the post-refine beam bound. They are one change to a user: the report now describes one geometry and says which point is which, and the bound that governs it is measured against the run's own measurement rather than against the file it is meant to correct. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
f98442077e |
post-refine: bound the beam move on the measurement, not on the header
Build Packages / build:windows:nocuda (push) Successful in 16m23s
Build Packages / build:windows:cuda (push) Successful in 18m58s
Build Packages / build:rugnux:windows (push) Successful in 12m15s
Build Packages / build:viewer-tgz:cpu (push) Successful in 14m58s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 14m21s
Build Packages / build:viewer-tgz:cuda (push) Successful in 15m35s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 16m55s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 21m29s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 17m13s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 21m56s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 21m45s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 16m51s
Build Packages / build:rpm (rocky9) (push) Successful in 17m46s
Build Packages / build:rpm (rocky8) (push) Successful in 21m10s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 18m58s
Build Packages / Generate python client (push) Successful in 29s
Build Packages / Build documentation (push) Successful in 1m2s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 21m56s
Build Packages / XDS test (durin plugin) (push) Successful in 9m45s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 8m55s
Build Packages / DIALS test (push) Successful in 18m30s
Build Packages / Unit tests (push) Successful in 1h28m7s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 9m21s
Build Packages / Create release (push) Skipped
Build Packages / XDS test (neggia plugin) (push) Successful in 8m20s
PostRefine step B refuses a fitted beam centre more than 15 px from the geometry the run started from. Two things were wrong with that and they compounded. The 15 px was BOTH the Ceres search box and the acceptance threshold, so a fit that wanted to move further was pinned at the box face, landed at exactly hypot >= 15.0, and was then refused for being there. The gate never saw the fit it was judging. Measured on one crystal at three detector distances, the fit came back at 15.128, 15.133 and 15.130 px - the box corner, three times. And it was measured from the header, which is the value most worth correcting exactly when it is most wrong. The run already knows better: the default beam-centre check fits the centre from the isotropy of the scattered background on every rotation run, reported it, and then discarded it. The bound is now measured from whichever of the nominal centre and that measurement is nearer - accept a move that is small relative to something independent, rather than small relative to the header alone - and the search box is widened to match, so the gate decides rather than the box. The measurement is used only where it was precise enough to be adopted as a centre in its own right; a run without one is bounded exactly as before. Step B also now says WHICH of its four tests refused a fit, since all four leave the same geometry behind. Measured over 113 rotation datasets against the same corpus, one binary per arm, with a control arm that reproduced the stored battery on 112 of 112 reports: the bound fired and refused a real correction 1 6ukf the bound fired and caught a runaway 0 the bound never fired 112 On 6ukf the header centre is 22.1 px from what the background fit measures. The old fit, pinned at the box, improved the held-out positional residual 9-fold and was refused; the new fit lands 0.46 px from the measurement and improves it 418-fold. ISa 4.42 -> 6.71, R_meas 18.5 -> 12.8 %, CC1/2 0.987 -> 0.995, d_min 1.119 -> 0.973 A; at matched resolution, 1.68 A goes I/sigma 8.9 -> 15.1 and R_meas 28.8 -> 13.4 %. The refined cell moves from 0.4 % off the deposited one to under 0.1 %. It survives perturbation, and inverts it: over +-0.5 mm of detector distance the corrected run is flat (ISa 6.62 / 6.71 / 6.94) while the uncorrected one is bistable (4.02 / 4.42 / 6.90). The change removes an instability rather than exploiting one. No space group anywhere in the corpus moves, and no other dataset's report changes at all - 112 of 113 are identical. The distance half of the gate is untouched: it is separately argued, and it is what legitimately refuses the one dataset whose distance wants to move 1.05 %. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
efca844bce |
calibration: --no-refine-tilt holds the header tilt, it does not zero it
Build Packages / build:windows:nocuda (push) Successful in 16m28s
Build Packages / build:windows:cuda (push) Successful in 21m49s
Build Packages / build:rugnux:windows (push) Successful in 15m57s
Build Packages / build:viewer-tgz:cpu (push) Successful in 15m16s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 16m33s
Build Packages / build:viewer-tgz:cuda (push) Successful in 18m23s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 19m46s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 21m21s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 17m45s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 20m56s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 31m0s
Build Packages / build:rpm (rocky9) (push) Successful in 22m45s
Build Packages / build:rpm (rocky8) (push) Successful in 24m48s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 28m30s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 22m27s
Build Packages / Generate python client (push) Successful in 31s
Build Packages / Build documentation (push) Successful in 1m19s
Build Packages / Create release (push) Skipped
Build Packages / build:rpm (ubuntu2404) (push) Successful in 16m37s
Build Packages / XDS test (durin plugin) (push) Successful in 9m53s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 9m39s
Build Packages / DIALS test (push) Successful in 19m49s
Build Packages / Unit tests (push) Successful in 2h5m32s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 9m3s
Build Packages / XDS test (neggia plugin) (push) Successful in 8m58s
GuessInitialGeometry resets rot1/rot2/rot3 to zero before seeding, and the branch that puts the header's tilt back was guarded by `refine_tilt && !tilt_refined`. With --no-refine-tilt the tilt is never free, so tilt_refined is false, so the guard is false, so the restore never ran - and the fit reported a zero tilt while the CLI printed "held at the header value". The guard only needs !tilt_refined: a tilt that was declined and a tilt nobody asked to refine both want the header put back and the beam centre and distance refitted around it. That is what the branch already does. Measured on a LaB6 sweep with a tilt imposed on the command line and asked to be held, --calibration spots: before Rot1= +0.0000 deg (+0.001400 rad from the header) after Rot1= -0.0802 deg (+0.000000 rad from the header) The rings path reaches this only when the spot-derived start wins, which is why it does not show on a file whose profile start is chosen; the spots path always did. A calibration whose header tilt is already zero is unaffected - checked, byte-equal distance either way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
dcfcff5bc4 |
report: the measured tilt, and one geometry instead of two
Build Packages / build:windows:nocuda (push) Successful in 17m21s
Build Packages / build:windows:cuda (push) Successful in 19m41s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 19m57s
Build Packages / build:viewer-tgz:cpu (push) Successful in 20m58s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 22m43s
Build Packages / build:viewer-tgz:cuda (push) Successful in 23m28s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 27m46s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 27m47s
Build Packages / build:rugnux:windows (push) Successful in 11m26s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 19m24s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 22m12s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 26m56s
Build Packages / build:rpm (rocky9) (push) Successful in 23m46s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 23m48s
Build Packages / build:rpm (rocky8) (push) Successful in 29m28s
Build Packages / Generate python client (push) Successful in 34s
Build Packages / Create release (push) Skipped
Build Packages / Build documentation (push) Successful in 1m32s
Build Packages / XDS test (durin plugin) (push) Successful in 11m33s
Build Packages / DIALS test (push) Successful in 26m43s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 27m55s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 9m56s
Build Packages / Unit tests (push) Successful in 2h43m15s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 9m28s
Build Packages / XDS test (neggia plugin) (push) Successful in 9m18s
Adds REFINED_DETECTOR_TILT - rot1/rot2 in degrees as rotation indexing measured them - so the value can be checked against a powder calibration, which measures the tilt from ring shape and is the better instrument for it. rot3 is not carried: a rotation about the beam is an exact null of a single-axis rotation experiment, so the fit cannot move it and printing it would suggest otherwise. Also repairs two defects in the commit that added DETECTOR_TILT, found in review. The geometry was taken from the caller's copy of the experiment, which is the state BEFORE the run, while BEAM_CENTRE and DETECTOR_DISTANCE come from result.used_*, which is the state after - so on a rotation two-pass the report mixed a post-refined centre with the file's rotations and DIRECT_BEAM was off by the whole post-refinement shift. JFJOCH_DATASET_SETTINGS carried the same mixture, which is a geometry no pass ever ran at. The tilt and the direct beam are now taken from experiment_ alongside the other three, so the block describes one geometry. And the prose claimed the tilt "is not refined here". It is: rotation indexing refines rot1/rot2 on every rotation run. What is true is that the result is never written back onto the geometry, which is why it needs a key of its own. M_PI -> PI (common/JFJochMath.h). rugnux is in the forced-Windows viewer-only set and M_PI is not portable to MSVC; the file did not include <cmath> either. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N |
||
|
|
7c00731992 |
docs: publish the powder calibration schemas in the python client docs
docs/python_client is tracked and is what readthedocs publishes, but it is generated - update_version.sh copies it out of python-client after regenerating the client - so adding schemas to the spec left it four files behind. The README's model list did not name them and their pages did not exist. Copied the way update_version.sh copies them. The four new pages and four lines in the README, nothing else: the generator's README is built from the spec, so the stale Calibration* pages it had left on disk under the old names are absent from it and were removed before copying rather than published alongside their replacements. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NfuDvf5ipV3Hi8TiCUKD27 |
||
|
|
8691bf4876 |
calibration: write the result as JSON, not only as a PONI file
A calibration run produced one file, and it was a pyFAI PONI - which pyFAI and
its neighbours read and nothing in this system does. Carrying the answer back
into the instrument meant a person reading numbers off a printed report and
retyping them into a dataset_settings body, and the report is where the two
points a "beam centre" can mean are easiest to confuse.
So write <prefix>.json beside it. Its "dataset_settings" member holds the
geometry under the property names broker/jfjoch_api.yaml gives them and holds
nothing else, so it is a valid dataset_settings body as it stands:
curl -X POST -H 'Content-Type: application/json' \
-d "$(jq -c .dataset_settings det.json)" http://broker:5232/start
beam_x_pxl is the PONI, as everywhere here. The three poni_rot*_rad ride along
whenever any is non-zero and are left out when all are zero: a body without them
does not leave the tilt unstated, it states a FLAT detector, so they travel
together or not at all - the same rule the report's JFJOCH_DATASET_SETTINGS
block already follows.
The "calibration" member holds what the run knows about that geometry: the
residual, the fit's own sigmas and the correlation between the tilt and the beam
centre, whether the tilt cleared its significance test or was declined and
pinned, where the direct beam lands, and where the spots independently put the
beam. A calibration that has gone wrong looks exactly like one that has not until
those are read, and a machine-readable file that carried only the geometry would
be the easiest possible way to feed a bad one into an instrument.
Tested against the model generated from the spec rather than against a list of
field names written out by hand: the file's dataset_settings member is parsed
into org::openapitools::server::model::Dataset_settings and validated, so a
field renamed or newly required in jfjoch_api.yaml fails here rather than at
someone's POST. The tilted and untilted branches are both covered, and the
PONI/direct-beam distinction is asserted rather than assumed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NfuDvf5ipV3Hi8TiCUKD27
|