diff ref struct Rietveld refinement for the Mac · powder diffraction with X-rays, synchrotron and neutrons · started October 2026

Log

Log

Every dated step of the project, newest first: findings, decisions taken with their reasons, and the work done. 445 entries. Generated from data/log.yml; do not edit by hand.

L4452026-11-02work

The family's pages published at jmigartua.github.io/diff-ref-struct-app, linking only to what is public

Two things, by Josu's word. First, the pages pointed at the project site (the bar's "Project site", the footer's "The project site", the App page's "Every release"); Josu does not want the pages to lead to the mother site, so the three links now go to the Web edition's guide and its releases page, the only public face of the project besides the Web edition itself. Checked on the rendered pages in Chrome: no link to diff-ref-struct-site remains. Second, the pages are published, so that Josu can share them: a public repository jmigartua/diff-ref-struct-app holds the generated out/ only (the seven pages, the images, the two films), as Ispilu's page is published; tools/publish.sh refreshes a clone of it in landing/site/ (ignored) and pushes; GitHub Pages serves main at https://jmigartua.github.io/diff-ref-struct-app/. Live at 08:32: the front page and the Web page rendered in Chrome, every relative link resolving, no word of web/private/guard.txt on the pages (checked before pushing). The sources (family.yml, pages/, shots/, films/, outputs/) stay here, private.

landing/tools/publish.sh · https://github.com/jmigartua/diff-ref-struct-app · https://jmigartua.github.io/diff-ref-struct-app/ · L444
L4442026-11-02work

The family's pages in herdr.dev's manner: one page per module, local, in landing/

Josu asked for pages for diff-ref-struct and the modules around it, like the herdr.dev page made for another app of his. Done in landing/ with the same landing kit (build.py and kit/, version 2: a family of pages over shared keys, a module's lighter page, releases filtered from one record): seven pages, App (the front page), Core, Web, Plots, Explore, Publish and Tutorial, one bar across them, each in herdr's order (a two-tone title, an install box, a window of views or one figure, a statement, a feature with notes, a README with a numbered contents). Versions are read from the crates' and the app's files and each page's releases from ecosystem/data/releases.yml by prefix. What a visitor can have today is said as it is: diff-ref-struct Web, public and archived; the core and the app not released, their licence open (Q7). Images: only the live Web 1.1.0 with its own example (synthetic, CC BY 4.0), shot again for the pages by landing/tools/shots.mjs, and the core's command line run on that example and on periclase's published cell (its four reflections fall on the example's peaks). The Plots and Explore mockups and the Publish examples are made from data in manuals/, so the pages show none of them, and none of the words of web/private/guard.txt appears (checked on the built pages). Checked in Chrome: every page at full width and at 390 px, the views of the window switching with every image loaded. The pages live outside ecosystem/, so no Action publishes them; out/ is not kept in git.

landing/ (family.yml, pages/, tools/shots.mjs, README.md) · https://herdr.dev/ · https://jmigartua.github.io/diff-ref-struct-web/
L4432026-10-27work

diff-ref-struct Web 1.1.0 released and archived: doi:10.5281/zenodo.23683238

After L442: publish-web.yml published 1.1.0 (build 1005d96, core 0.82.0, checked live); the tag web-v1.1.0 ran publish-source.yml (the public main 288dc6c, tagged v1.1.0); the release v1.1.0 created on it with the notes of the record; Zenodo archived it (its webhook's release event accepted) as 10.5281/zenodo.23683238, a version of the concept 10.5281/zenodo.23682726. The DOI written into the record (Appendix B), CITATION.cff, the guide's About and the project's releases; the guide and the source published again with it.

https://github.com/jmigartua/diff-ref-struct-web/releases/tag/v1.1.0 · https://doi.org/10.5281/zenodo.23683238 · L442
L4422026-10-27work

diff-ref-struct Web 1.1.0 on core 0.82.0: the 2\(\theta\) interval of the analysis in set files; the core branch merged into main

At Josu's word, after L441: the core branch (0.82.0, PL42, with main in it) merged into main through the web branch, and diff-ref-struct Web released as 1.1.0. The page's core package rebuilt at 0.82.0 (1307 kB); a set file's x_interval = [from, to] checked in the page on a copy of the \(\mathrm{Tb_{2}Pd_{2}Sn}\) folder cut to 24–36° (its Map from 24 to 36°, the cut set's colour range 2.982–4.605 log₁₀ counts). W11 met by 0.82.0 (the patterns' member); the page's rename kept as a fallback. The record (web/site/data/releases.yml 1.1.0, the roadmap: the page's control for the interval and a test of the core's shapes decided), the conventions (x interval), the manual (§13, §20), the quick reference, the hold recipe, the team's use case (the first degree cut by x_interval) and CITATION.cff updated; every PDF rebuilt. The release gate with the views: every line OK (versions, notes, PDFs, the guide's links and guard, the source's guard, licences, 39 tests, 9 of 9 views). Published by publish-web.yml and, by the tag web-v1.1.0, publish-source.yml; released as v1.1.0 on the public repository for Zenodo.

web/site/data/releases.yml 1.1.0 · core/CHANGELOG.md 0.82.0 · web/manual/user-manual.md §13 · L434, L435, L441
L4412026-10-27work

Main brought into the core branch before its merge: core 0.82.0 (PL42) on top of diff-ref-struct Web 1.0.0 and W13

At Josu's word, in the core's worktree: the branch (e436fadd, 0.82.0, L435) merged with main (L436–L440). The three files both sides changed (core/src/plots.rs, core/tests/plots_propose.rs, core/wasm/tests/map.test.mjs) merged without conflict; the log's two tails (L435 and L436–L440) kept in order, LOG.md and the log's page regenerated. 0.82.0's new test core/tests/plots_sets.rs took three of the ESRF run's names for the files it writes; made neutral as W13 made the others (scan_…), what it checks unchanged, so that the public source stays clean (web/tools/source.mjs: 0 of 8 guarded words). On the merge, with the run's folder named: plots_esrf 2 of 2, plots_propose 5 of 5, plots_sets 14 of 14, the package's tests 10 of 10 (PL42's among them). The branch is ready to merge into main; diff-ref-struct Web's next release then takes core 0.82.0 (its record, the release gate).

core branch (merge of main into e436fadd) · core/tests/plots_sets.rs · web/tools/source.mjs · L435, L439, L440
L4402026-10-27work

diff-ref-struct Web's source public under the Apache License 2.0, released as v1.0.0 and archived by Zenodo: doi:10.5281/zenodo.23682727 (every version: 10.5281/zenodo.23682726)

At Josu's word, after W13 (L439): publish-source.yml run by hand (its guard 0 of 8 words in 469 files, Pages serving gh-pages, the source pushed to the public repository's main as 95054a7; a fresh clone guarded again, no hit). Josu linked Zenodo to the repository (the switch on after a reload of Zenodo's GitHub page; its webhook active, on releases). The release v1.0.0 created on the public main with 1.0.0's notes from the record; Zenodo archived it as software, "Igartua, Josu M.", Apache-2.0, the source zip (10.2 MB), documented by the guide: version DOI 10.5281/zenodo.23682727, concept DOI 10.5281/zenodo.23682726 (it resolves to the latest version). The DOIs written into CITATION.cff, the release record (Appendix B of the manual, its PDF rebuilt), the guide's About, and the public README's badge; the guide and the source published again with them.

https://github.com/jmigartua/diff-ref-struct-web/releases/tag/v1.0.0 · https://doi.org/10.5281/zenodo.23682727 · web/public/CITATION.cff · web/site/data/releases.yml · L437, L438, L439
L4392026-10-27work

W13 met on main: the ESRF run's identifiers out of the core's sources and tests, so that diff-ref-struct Web's source can go public

At Josu's word ("do it for me", the publication of L438), done here rather than by the core stream, on core 0.81.0 (the core of diff-ref-struct Web 1.0.0): in core/tests/plots_esrf.rs the run's 101 names made neutral (scan_<seq>_T<temp>C_t<seconds>s_<class>.xye, the same structure, so W8's rules are tested as before), the scan's number read from the token before its temperature or background (the run's own names and the neutral alike), the folder taken from DRS_ESRF_RENAMED only and listed in name order; the same in core/wasm/tests/map.test.mjs (the background matched by its sequence and suffix) and core/tests/plots_propose.rs; two comments of core/src/plots.rs say "a team's ESRF run". With the folder named: plots_esrf 2 of 2, plots_propose 5 of 5, the package's tests 9 of 9; web/tools/source.mjs finds no guarded word, and web/site/tools/release.py says 1.0.0 is ready on every line. The core stream's 0.82.0 (e436fadd, L435) touches plots.rs, plots_propose.rs and map.test.mjs too: it merges main before its own merge.

core/tests/plots_esrf.rs · core/tests/plots_propose.rs · core/wasm/tests/map.test.mjs · core/src/plots.rs · web/REQUESTS.md W13 · web/site/tools/release.py · L435, L438
L4382026-10-27work

The web stream merged; diff-ref-struct Web 1.0.0 and its guide published on gh-pages; the source waits for W13

Josu asked the four publication steps of L437. Main fast-forwarded to the web branch (3c942b52, 16 commits; no other stream merged: the core's 0.82.0 (PL42) and Plots' step 24 wait for their own merge, so that 1.0.0 stays on core 0.81.0 as its record says). The push runs publish-web.yml, which writes the page and the guide to the public repository's new gh-pages branch; the Pages source switched from main to gh-pages; the page and the guide then checked live. The source is not published: publish-source.yml stops on the four core files of W13, by design. Zenodo needs Josu's login to link the repository (the DOI per release).

main 3c942b52 · .github/workflows/publish-web.yml · web/REQUESTS.md W13, W14 · L436, L437
L4372026-10-26decision

diff-ref-struct Web goes public and open source, by the open-tool kit: every recommendation taken, English only for now

Josu, 26 October 2026, on web/PROPOSAL-open-tool.md: "go for all your recommendations", the AI-assistance statement with a proposed wording, English only for now. Decided:

  • the code under the Apache License 2.0, the guide, manuals, screenshots and example data under CC BY 4.0; CITATION.cff and a Zenodo DOI per release. Set aside: MIT (no patent grant, no NOTICE), GPL-3.0 (no reuse in closed tools);
  • the public repository diff-ref-struct-web: its main holds the source subset (the core, the page's files of app/ui, web/) published by an Action, its gh-pages the built page and guide. Set aside: the whole private repository made public (other programs' manuals, a team's unpublished data);
  • requests as the public repository's issues (bug and feature forms) with an e-mail path; a third-party form set aside (users' words to a server we do not control);
  • the vocabulary appendix named Conventions (words, files and suffixes, naming scans, units, versions and ids);
  • semantic versions from diff-ref-struct Web 1.0.0, the core's version and the build beside it;
  • the example computed by the core; FullProf's tutorial files linked, not hosted (the ILL's to distribute);
  • the statement in NOTICE and the guide's About: written with AI assistance (Claude, through Claude Code) under Josu's direction, every number the core's and checked by its tests, the responsibility the author's;
  • the open-tool kit as the standard of every public tool of the family, with its skill _OPENTOOL.
web/PROPOSAL-open-tool.md · web/public/NOTICE · web/REQUESTS.md W14 · L436
L4362026-10-26work

The web stream's steps 6 and 7: diff-ref-struct Web's own site (the guide), its manuals in HTML and PDF, and 1.0.0 with the open-tool kit

The web stream (web branch, 16 commits from eb9d2813 to 3c942b52) wrote the user manual of the page (web/manual/user-manual.md, 22 sections), a one-page quick reference, and a team's worked use case of the ESRF ID22 run (web/manual/team/, PDF and one HTML page, never published). Then the guide: a Quarto site in the family kit (web/site/), published with the app at /guide/: overview, start in five minutes (a browser check, the \(\mathrm{Tb_{2}Pd_{2}Sn}\) tutorial folder), the manual and quick reference with their PDFs (LuaLaTeX in the kit's typefaces), five recipes, how it works, formats, conventions, releases, log, roadmap, requests, about, privacy and accessibility; screenshots taken from the app by web/site/tools/shots.mjs (Chrome over the DevTools protocol, each view at 3× in both themes). Version 1.0.0 applies the open-tool kit (web/PROPOSAL-open-tool.md): releases, roadmap and conventions as records rendered in the guide and in three appendices of every manual; the page's Help ▾ menu (the manual at the view shown, report a problem, ask for a feature, cite this view), Try the example (21 scans computed by drs simulate, web/examples/tools/make_example.py), the guide kept offline by the page's worker; open source files in web/public/ (Apache-2.0, CC BY 4.0, NOTICE, CITATION.cff, issue forms), the public selection web/tools/source.mjs guarded by web/private/guard.txt, the Actions publish-web.yml (page and guide to gh-pages) and publish-source.yml (source to main, by hand or a tag); the release gate web/site/tools/release.py. Found while taking the screenshots and fixed in the host: the Overlay drew no curves on the public page (0.4.0: the package's plots_patterns says index where the module reads member; W11 to the core), and Sum, Average, Difference of Members computed and never saved (now downloaded). The proposals against time open by temperature (W10, measured on core 0.81.0). The gate on 1.0.0: every line OK (versions, notes, PDFs, the guide's links and guard, licences, 39 tests, 9 of 9 views drawn) but the source's guard, which stops on four of the core's files that name the team's run (W13). Also the skill _OPENTOOL, created through CreateSkill, for the next public tool.

web branch eb9d2813…3c942b52 · web/README.md steps 6 and 7 · web/DESIGN.md · web/REQUESTS.md W10–W14 · web/PROPOSAL-open-tool.md · ~/.claude/skills/_OPENTOOL
L4352026-10-26work

Core 0.82.0: the x interval of the analysis set once and kept by the set file, every member cut to it on opening (PL42); drs-wasm follows the app's W9 (patterns, rebin)

The order of L434 for the core.

  • SetFile::x_interval (x_interval = [from, to], in the set file's x quantity; absent by default): read (a pair in order, refused backwards), written only when given, described ("the analysis cut to 2theta 2 to 20 on opening: every member's points outside it left out, the files untouched").
  • PlotSet::cut(interval, quantity): every member keeps its points from from to to and only those, its facts (points, range, step) the cut scan's. The interval is converted to a member's own x with its file's λ when the set file's quantity is another, as a region's bounds are (PL37), and refused by name when a λ is lacking. A member left with fewer than two points inside is refused by name ("its range, 21 to 27, does not reach the x interval 2 to 20"). A set cut again keeps the narrower interval. PlotSet keeps x_interval (and the quantity it is in), so SetFile::of writes it back.
  • Applied on opening:
  • SetFile::open* apply the file's interval before its rebin (W9).
  • open, open_from and open_buf keep their signatures: their callers cut on opening (drs plots, drs-wasm, and the app's bindings once Plots adds the argument).
  • propose(files, x_interval) cuts each file as it reads it, so the facts are the cut scans' and every proposal's set file carries the interval; a file the interval leaves without points is named.
  • Every function of plots then works on the cut set with no change of its own (the grid, map, difference map, displacements, projection, series, derive, CSV, page_data), with one exception, windows: a window outside the interval is refused as such ("window \"beamstop\", 0.5 to 1.5, lies outside the analysis's x interval 2 to 20"), where without the check it would fail on no points.
  • drs plots … --x-interval a:b on every plots command that opens a set (on a .drv, in its x quantity, after its own interval; save writes it; propose gives it to its proposals).
  • drs-wasm:
  • plots_open_from, plots_open_buffer_from, plots_open_set_from and plots_propose take xInterval: [a, b]; files added to a cut set are cut alike; save writes the interval.
  • The commands test found the app's plots_rebin (Plots' step 22) missing: ported. The browser has no files to read again, so it keeps the measured set the first time the step changes; a set opened rebinned refuses another step.
  • plots_patterns now gives the app's PatternView (member, the members shown only), since the app wrote its binding with that shape. Settings carries y_interval as the app's does.
  • 30 commands and describe, the app's list met.
  • For Plots, no new Tauri command, but the arguments of three:
  • plots_open_files, plots_open_buffer and plots_open_set take xInterval: Option<[f64; 2]> and call set.cut(iv, quantity) after opening: none for files, SetFile::x_quantity() for a set file;
  • plots_add cuts the added files to the set's interval (set.x_interval, set.x_interval_in);
  • save and plots_rebin keep it with no change (SetFile::of, then open, cut again).
  • For Publish: its figures read the set file through SetFile::open (publish/src/patterns.rs:42) and so draw the cut set already. What it must add is the note: one line per figure from op.file.x_interval, as it notes the displacement, so that the figure says the data are cut.
  • Tests:
  • plots_sets::pl42_the_x_interval_of_the_analysis:
  • three files with the ESRF names (0–27° in 0.01°, written there) cut to 2–20° give a map whose 1801 columns run from exactly 2 to 20, in °C from the names;
  • a window at 0.5–1.5° is refused and one inside is read; a member reaching only 21–27° is refused by name;
  • a .drv without the field reads and writes as before, and the proposals carry the interval;
  • \(\mathrm{Tb_{2}Pd_{2}Sn}\) cut to 18–34° gives a map equal to map_region's block of those columns on the whole map, every row to the bit, the range equal; drs plots map --x-interval 18:34 gives the same columns, and drs plots describe --x-interval says the cut.
  • core/wasm/tests/map.test.mjs, 10 cases: the cut map equal to drs plots map --x-interval --json to the bit; the interval saved, opened again and narrowed; refused out of range; carried by the proposals; plots_patterns and plots_rebin as the app's.
core/src/plots.rs (PlotSet::cut, interval_of, x_interval, x_interval_in; SetFile::x_interval; windows; propose) · core/src/main.rs (--x-interval) · core/tests/plots_sets.rs · core/wasm/src/host.rs, lib.rs, tests/map.test.mjs, tests/commands.rs, README.md · app/src-tauri/src/modules/plots.rs (plots_rebin, plots_patterns, Settings.y_interval) · publish/src/patterns.rs:42 · plots/REQUESTS.md PL42 · L434
L4342026-10-26decision

Josu's two asks on the map's analysis: the 2\(\theta\) interval set once for the whole analysis (PL42, the core; Plots' step 25), and the linear scale as the map's default

Josu, 26 October 2026, after the web app and app 0.54.0: an option to define the interval in 2\(\theta\) to be plotted, so that the complete analysis is done with that interval set; and the linear scale as the default. Decided and filed:

  • The interval is the core's: SetFile::x_interval (PL42), applied on opening so that every member is cut to it and every view, window, derived file, export and page sees the cut set; the files untouched, the cut said in the description and in Publish's notes; drs plots … --x-interval a:b the same. A zoom is a view; this is the analysis's domain, kept by the set file and carried by the proposals. Set aside: an interval kept by the module and applied view by view (a hundred places to keep in step, and Publish's figures would not know it).
  • Plots' step 25: a control beside the views, "2\(\theta\) interval…", typed or taken from the rectangle shown, kept by the set file through the core, applied to the whole tab (the map, the strips, the overlay, the series), and the map's default colour scale linear in place of log (mapScale in the module's tab state); a set file that says its scale keeps it, as the ESRF sets say √. In both hosts, since the module is shared. The core first (0.82.0 with PL42), Plots after it, with step 24 (L433) before or alongside.
plots/REQUESTS.md PL42 · app/ui/modules/plots/index.js (stateOf, mapScale) · core/src/main.rs (drs plots map --range) · esrf-datuak-irene F08 · L432, L433
L4332026-10-26finding

On the public web page the strips still do not take the map's frame, and the unit is still said twice: step 23's fix acts in the app and not in the browser host

Checked on the public page right after the Action published web a1c308d (Plots' step 23 in it; the page's viewer/map.js holds stripFrames, the live and the cached copy alike, the worker current): the \(\mathrm{Tb_{2}Pd_{2}Sn}\) files dropped, the Map shown, the cursors pinned at 29.9°. The map's frame ends at the colour bar (its right margin measured for the bar and the right-hand axis), the row strip's frame runs on to the canvas's edge less its own 16 px: in the capture the map's 44° sits where the strip draws 42°, and the strip's y label reads "counts (log) (log)"; the column strip's bottom is its own x axis, not the map's bottom. A forced resize and a wheel zoom changed nothing. So what the step's self-check measured in the Tauri host (the frames equal, the unit once) does not happen in the browser host: the two hosts differ in something the viewer's draw depends on (the order of the map's draw and the strips', the plot the host hands over, the frame call reaching the strips' plots), and the step's check ran in the app only. Capture: ~/Downloads/interceptor-capture-20261026-081600-60098.png. For Plots' step 24: reproduce in the browser host (serve the repository root, open app/ui/host-web/index.html, drop the Tb files through a DataTransfer in eval, pin a cursor) and fix where the hosts part; the web host's node tests gain a check of the strips' frames.

https://jmigartua.github.io/diff-ref-struct-web/ (web a1c308d, core 0.81.0) · ~/Downloads/interceptor-capture-20261026-081600-60098.png · app/ui/viewer/map.js · L431, L432
L4322026-10-26work

Plots step 23 merged: the strips' frames are the map's, the unit said once (L431)

Plots' step 23 (plots branch 6770438c), fast-forwarded into main. At each draw the row strip under the map takes the map plot's frame (plot.state.geom, the left fixed and the right measured for the colour bar and the right-hand axis) through the shared plot's new setAxes({ frame }), with the map's x view, so a peak sits under its column whatever room the bar and the second axis take; the frame is taken again on the map's resize and on every change of its view. The column strip is drawn with the variable up and the core's values across, its top and bottom and its y view the map's, so each point sits on its row. The scale's word is added once, by the shared plot ("counts (√)", "√counts"). Self-check 161 OK, 0 FAIL, relaunch 168 OK, 0 FAIL, Josu's files unchanged; the check gives the plot area \(960 \times 640\) px for its duration so that the strips are shown. On main: the UI suite 268, the viewer's assets fresh. The web app takes it at this push; the Mac app at its next release.

plots branch 6770438c (README twenty-third step, PL13s) · app/ui/viewer/map.js · app/ui/plot.js · app/ui/mapclip.js (stripFrames) · L431
L4312026-10-26finding

In the Map view's row strip the profile's frame is wider than the map's, so a peak does not sit under its column (Josu, on the web app); the column strip the same way

Josu, on diff-ref-struct Web with the ESRF temperature series: the strip under the map that draws the member under the pointer should share the map's vertical lines, so that what the user sees in the map coincides with the profile below; as it is there is a shift. Seen in his capture and in the viewer's code: the map's plot has its right margin measured for the right-hand axis (the second variable, t) and the colour bar, so its frame ends well before the canvas's edge, while the row strip is a second instance of the shared plot with its own margins (right: 16, its own measured left for its y ticks), so its frame runs to the edge: the two x scales differ and a reflection at 14.5° in the map sits over 15.5° in the strip. The column strip beside the map has the same fault in height against the map's rows. A second small thing in the capture: the strip's y label reads "counts (√) (√)", the unit's root said twice.

The fix is the viewer's (the shared module, both hosts): at each draw the row strip takes the map's frame edges (the map plot's measured left and right, plot.state.geom) as its own and the same x view, and the column strip the map's top and bottom and the same y view; the shared plot takes explicit margins as an additive option (PL13s). Plots' step 23, a short one; it reaches the web app at the next push and the Mac app at its next release.

Josu's capture of the web app, 26 October 2026 · app/ui/viewer/map.js:151–166 (the strips' createPlot) · app/ui/plot.js (the measured margins, PL30) · L406, L412
L4302026-10-25work

App 0.54.0 on core 0.81.0 built, self-checked and installed: Plots' steps 20 to 22 in the Mac app

The release cycle of L418 on main b26709c2: the self-check bundle built with its own identifier, Plots' self-check run on a copy to its quit and relaunched: 165 OK, 0 FAIL, then 150 OK, 0 FAIL; Josu's own files of the app unchanged. The release bundle built under eus.ehu.diffrefstruct as 0.54.0 and installed in /Applications, 0.53.1 to the Trash. Josu's app now holds, on core 0.81.0 and drs-publish 0.36.1, what the web round brought to the shared module (L425–L428): the y interval shared or own, the members table with icons, checkboxes, all/none/class/range and three orders, the right-hand axis from a member's second variable, the shell's host object, the patterns asked when drawn, a hundred raw scans opened and overlaid in seconds with the core's rebin offered. The desktop app and the web app are now the same module on the same core.

scratchpad self0540 (run.log) · app/src-tauri/target/release/bundle/macos · data/releases.yml app 0.54.0 · L418, L421, L429
L4292026-10-25work

Core 0.81.0, Plots' step 22 and the web host's steps 4 and 5 merged; diff-ref-struct Web published by its Action; the 101 ESRF files on the public page: the three sets proposed, the hold mapped in a second; I10 migrated

The three orders of L427 came back and are merged into main: core 0.81.0 (core branch f0cc2149, L428: the ESRF names read, the exposure classes by \(\sigma\)²/y, propose giving the run's three sets scan for scan as they were built by hand, a large set rebinned on opening, the patterns out of every SetView), Plots' step 22 (plots branch 616a660d: the patterns asked and not carried, the overlay in slices, the core's rebin offered on opening; the ESRF folder's overlay from 31.3 s with the window blocked to 1.69 s to the first frame; self-check 150 OK, 0 FAIL, relaunch 167 OK, 0 FAIL), the web host's steps 4 and 5 (web branch 6fbc5632: live mode growing a proposal's set from a folder read again, the service worker and the install manifest, the view in the address, the staged site with its licences, publish-web.yml with the deploy key, the first publication by hand).

Three seams closed here: the host's local function ask (a message to the worker) renamed, since the app's dialogs test reads every ask( as a dialog; the wasm test's 10 s budget for opening the run's 100 scans made a reported time with a 60 s ceiling (9.7 s in the cloud, 12.4 then 14.8 s on this throttled Mac: a budget, not a tolerance); and the Action's node --test web/tests/, which Node 22 on the runner does not expand, made a glob. Then every suite on main: core wasm 9, UI 267, web 38, app Rust 100, assets fresh. Pushed; the Action published the app from main for the first time (web 24d8619, core 0.81.0, 2 min 32 s; the first run failed on the glob).

Checked on the public page (Interceptor, the isolated profile; the 101 ESRF files served read-only from a local server with CORS and dropped on the page through a DataTransfer, so that the public origin read them as a user's drop would): the page first showed core 0.80.0 — the worker of the stream's hand publication kept serving its cache, and the raw-scan path of that build blocked the tab for minutes; a fresh tab with the registration updated showed 0.81.0 (the worker's take-over on a new version is for the web stream to make visible: a line and a reload). On 0.81.0: the 101 files (111.8 MB) listed by kind, opened as one set of 101 members in the Overlay with the core's difference of first and last scan below (the overlay drawn, the page answering throughout); through the host's own dispatcher, plots_propose on the 101 texts in 38 s gave the three titles of L428 exactly, the background scan left out; the hold opened rebinned in 1 s and mapped in under a second: 39 rows, 2699 columns, 0 to 28.26 √counts, the desktop's numbers (L021). Loose files dropped open as one set; the proposals come with a folder (Open Folder…), as the stream's own check with a directory handle showed. Capture: ~/Downloads/interceptor-capture-20261025-154334-44870.png.

I10 migrated with its version. For Josu: the core's choice of \(\sigma\)²/y for the exposure classes (L428) is his to accept; and the ESRF files are now analysable at the public address by anyone with the folder: Open Folder…, the three proposals, the Map with the clip and the regions.

core branch f0cc2149 (L428) · plots branch 616a660d (README twenty-second step) · web branch 6fbc5632 (README steps 4 and 5) · .github/workflows/publish-web.yml · app/ui/host-web/host.js · core/wasm/tests/map.test.mjs · https://jmigartua.github.io/diff-ref-struct-web/version.json · data/ideas.yml I10 · L427
L4282026-10-25work

Core 0.81.0: the ESRF run's three sets found by `drs plots propose` as they were built by hand (W2, W8), the exposure classes by the counts' scale \(\sigma\)²/y, a large set rebinned on opening and its patterns out of every SetView (W9)

The order of L427 for the core, in a cloud session. The ESRF files were within reach after all: the project's public site (jmigartua/esrf-hc6528-400-4, downloads/files/) publishes the 101 .xye, their file_index.csv and the sets built by hand (hc6528_400_4_drs-sets.zip); the session read them from a clone outside this repository, and only their names and the scan numbers of the hand-built sets are in the tests.

  • The test, one line: drs plots propose on the folder prints, as titles,
  • "25 scans by temperature, 900 to 1300 °C (the longest exposure at each temperature, of 3 exposure classes), rebinned to 0.01°";
  • "100 scans against time, 10.036 h from hc6528_400_4_Qu_001_T0900C_t00000s_long.xye (3 exposure classes), rebinned to 0.01°";
  • "the hold at 1270 °C: 39 scans against time, 166.433 min from hc6528_400_4_Qu_032_T1270C_t16530s_short.xye (exposure class 2 of 3, \(\sigma\)²/y 0.300 to 0.316); the class's other holds: 1240 °C (5 scans), 1245 °C (3 scans), 1250 °C (5 scans), 1275 °C (8 scans), 1280 °C (4 scans), 1300 °C (13 scans), rebinned to 0.01°". Their members are the hand-built sets' scan for scan (by-temperature-map.drv, all-scans.drv, hold-1270.drv), the run in hours since scan 001 (10.0356 h at scan 100) and the hold in minutes since scan 032 (166.43 at 070), as built. The background scan (101, no temperature in its name) is in no set and named.
  • W8: temperature_in_name reads T0900C, 900C and 900°C as °C (an upper-case C, not inside a word); elapsed_in_name reads t00000s (a lower-case t before the seconds, as T before a temperature). L426's assertion that hold_t12.5sec gave nothing ("t12.5 inside a word") was a guess the real names undo: changed in the test, named here. A temperature's unit follows its source (Found::unit): a name's °C is °C; the header, a buffer's comment, the title, a name's K and FullProf's temperatures file are K; a typed value has none. A set is in °C when every member's value with a unit is °C (the ESRF names), otherwise in K with the °C converted (Variable::in_unit, PlotSet::temperatures_in); a set file keeps its unit and converts to it. On the 101 names: every temperature and every elapsed time as file_index.csv gives them (plots_esrf).
  • The exposure classes, a decision for Josu to accept. The order asked for the median relative error \(\sigma\)/y above 2° (F02: 0.040 against 0.047, a ratio of 1.2), with a threshold justified from F02 and from \(\mathrm{Tb_{2}Pd_{2}Sn}\). It cannot be justified: in \(\sigma\)/y the ESRF step between the long and the short scans, once sorted, is 1.118, and \(\mathrm{Tb_{2}Pd_{2}Sn}\)'s 20 scans of one exposure step by up to 1.107 (t12 to t11: the magnetic peaks change the counts). The relative error moves with the intensity. Taken instead: the median of \(\sigma\)²/y above 2°, the counts' scale. For counts N scaled by c (y = cN, \(\sigma\) = c√N), \(\sigma\)²/y = c whatever the intensity, and for raw counts it is 1. Measured:
  • ESRF: long 0.227–0.237 (the 19 long scans and the background, whose name says long), short 0.300–0.316, and scan 085 (1290 °C, "short") 0.694 alone; steps between classes 1.265 and 2.195, none inside a class above 1.0075;
  • \(\mathrm{Tb_{2}Pd_{2}Sn}\): 1 for all 20 scans, one class. So CLASS_STEP = 1.1, a new class where a scale exceeds 1.1 times the one before. Option set aside: \(\sigma\)/y with a threshold between 1.107 and 1.118, which a fourth data set would break.
  • propose's rules, from the hand-built sets:
  • by temperature: one scan per temperature over all classes, of the longest exposure that has one there and the last of those in time order (the nearest to equilibrium: 015 at 1225 °C, 017 at 1240 °C, 070 at 1270 °C, 100 at 1300 °C), as by-temperature-map.drv takes them;
  • against time: every scan with a time, all classes; when most give a temperature, one that gives none is left out and named;
  • the hold: per class, the longest run of three or more at one temperature, against the time since its own first scan, the class's other holds named in its title. Time is shown in s below 2 min, min below 5 h, h above.
  • W9 (a): no SetView carries the members' x, y and \(\sigma\) any more; plots_patterns { id, members } gives them as the view shows them. plots_open_from takes rebin (a step in x, PlotSet::rebin: data_ops::rebin_step, averaged, \(\sigma\) propagated, as drs data op rebin step=… mode=average). A proposal whose members have more than LARGE_SCAN (5 000) points in 2\(\theta\) gets rebin = 0.01 in its set file (SetFile::rebin, read, written, described, applied on opening) and says so in its title. The rebin was chosen over a decimated overlay: the ESRF maps were made on scans rebinned to 0.01°, the counts and their \(\sigma\) stay right, and a decimation would be a second drawing of the same data the core would have to explain. A proposal names the reader its members share (Proposal::reader); given back to plots_open_from, the files are not identified again.
  • Speed in the browser's core (node, this container), on the ESRF folder's 101 files given as text (113 MB):
  • plots_propose 10.3 s;
  • plots_open_from rebin 0.01 on the run's 100 scans 9.7 s, and 2.4 s with the proposal's reader; the hold's 39 in 3.7 s; 0.08 MB of JSON back, where 0.79.0 gave 71 MB in 30 s. Most of the time is identifying the format: the table recogniser parsed every line seven times (four delimiters and two decimal marks). A delimiter absent from the data lines and a decimal comma without a comma are no longer tried; the answer is the same by construction (cells takes two fields or more; the point's pass comes first and keeps a tie). It went from 39 ms to 20 ms on one ESRF file.
  • drs-wasm follows the app again: plots_set_other and MemberView.others (Plots' step 21) ported. The commands test found plots_set_other missing, as it is meant to. plots_patterns is accepted there ahead of the app's binding, which is Plots' to write in app/src-tauri/src/modules/plots.rs (W9 (a)). The module draws from MemberView.x/y today: in the app and in the browser it must ask plots_patterns before it draws on 0.81.0's package (Plots' W9 (b)).
  • Tests:
  • plots_esrf (2 cases): W8 on the 101 names and on sets of those names; with the folder, the three sets. Without the folder it says so and checks the names only.
  • plots_propose (5 cases): the rules above on \(\mathrm{Tb_{2}Pd_{2}Sn}\) and on a run written there.
  • core/wasm/tests/map.test.mjs, 9 cases: the ESRF one skipped without the folder.
  • core/wasm/tests/commands.rs.
core/src/plots.rs (propose, counts_scale, CLASS_STEP, LARGE_SCAN, LARGE_STEP, Proposal::reader, ExposureClass::median_scale, temperature_in_name, elapsed_in_name, Found::unit, Variable::in_unit, PlotSet::temperatures_in, PlotSet::rebin, SetFile::rebin) · core/src/formats/table.rs (layout) · core/src/data_ops.rs (rebin_step public) · core/src/main.rs (drs plots propose) · core/tests/plots_esrf.rs, plots_propose.rs · core/wasm/src/host.rs, lib.rs, tests/map.test.mjs, tests/commands.rs · jmigartua/esrf-hc6528-400-4 downloads/files (file_index.csv, xye/, hc6528_400_4_drs-sets.zip) · web/REQUESTS.md W8, W9 · L021, F02 of esrf-datuak-irene · L427
L4272026-10-25work

Core 0.80.0 (W2, W5, PL41), Plots' step 21 (W6, W7) and the web host's step 3 merged; the app's line for core 0.80.0 completed; the series detector's first run on the ESRF folder

The three orders of L425 came back and are merged into main: core 0.80.0 (core branch 690bc324, L426: plots::propose, t from the names, W5's text commands and plots_names_in, y_interval and plots::extent, the package's commands equal to the app's by a test), Plots' step 21 (plots branch 8c8df733: the host taken from the page, plots.adopt, Save's words from the host, the right axis lit; self-check 164 OK, 0 FAIL, relaunch 166 OK, 0 FAIL), the web host's step 3 (web branch bf451a3d: buffers and set files routed to their _from commands, files described by the core and listed by kind, the proposals panel, the tests honest with 28 passing, and in its own browser check the Tb folder in every way and the ESRF folder's 101 files opened as one set; W8 and W9 filed).

Built in parallel on 0.79.0, Plots' step 21 typed a member's other variable without the unit core 0.80.0 gave Other, so the app's Rust side no longer compiled: one field completed here (an empty unit for a typed value), the app's Rust tests pass (100). On main after that: the UI suite 264, the viewer's assets fresh, the web host's tests 28, the wasm package built for the host (1251 kB) and its node tests 8 of 8 once the command line was rebuilt at 0.80.0 (the one failure was the stale binary, as in L425).

drs plots propose renamed/*.xye on the ESRF folder, the first run: one set offered, "101 scans against time, 10.609 h", and one exposure class. The ramp by temperature and the 1270 °C hold are not found because the core does not read T0900C (W8, temperature_in_name takes _300K and T300), and the two exposure classes of F02 are not told apart because the rule compares the esd's absolute medians (5.56 to 10.27 across the run, where F02's classes differ by 1.2 in the relative error above 2°, not by two). Both go to the core's next version with W9: the three sets of L021 are the test. The overlay of 101 raw scans blocking the browser (W9) is the other thing between the web app and the ESRF files: the desktop case was worked on scans rebinned to 0.01°, and the browser host must get the same from the core on opening.

core branch 690bc324 (L426) · plots branch 8c8df733 (README twenty-first step) · web branch bf451a3d (README step 3, W8, W9) · app/src-tauri/src/modules/plots.rs:764 · core/wasm/tests · drs plots propose · esrf-datuak-irene F02, F07, L021
L4262026-10-25work

Core 0.80.0: the series a run's files offer (W2, plots::propose), W3's t from the names, the path commands as text in the browser (W5), the y interval and the set's extent (PL41); the package's commands equal the app's again, by a test

The order of L425 for the core, in a cloud session.

  • W2, plots::propose(files) -> Proposals (Proposal, ExposureClass):
  • Each file is read alone; one that the registry cannot read is left out as "not read: …".
  • Each scan's facts are its temperature with PL2's source, its time and the median of its esd's. The time is the elapsed seconds the name gives (elapsed_in_name: a number followed by s, sec or secs, not inside a word), else the modification time the host gives (OpenedFile::modified).
  • Modification times are not taken when two files' fall within SAME_WRITE (1 s) of each other: files checked out or copied together carry the copy's time. Found on the repository's own \(\mathrm{Tb_{2}Pd_{2}Sn}\) files, whose checkout times first gave "20 scans against time, 0 s".
  • Exposure classes: the medians sorted, a new class where one is more than twice the one before.
  • Per class:
  • against time: three scans or more with a time, against the time since the class's first, in s, min or h by the span; T is each member's other variable (W3) with its source;
  • the holds: runs of three or more in time order at one temperature (within SAME_TEMPERATURE, 0.5);
  • by temperature: the first scan of each temperature, when there are three or more, the repeats left out naming the scan taken.
  • Each proposal is a SetFile the host opens as it is, with a title that says what it is and how many scans. A temperature that a re-read gives is not typed in it; a time is typed with its source.
  • The files in no set are named with why. A file is named as given, with its scan in brackets when the file holds several.
  • drs plots propose <data files… | file.buf> [--json] gives each file's modification time, as a browser host does.
  • W3's t: a set opened from files whose names all give elapsed seconds carries t in s, since the first member, on every member (PlotSet::fill_elapsed). Modification times are never taken on opening as t, only in propose. A member keeps its file's modification time (Member::modified: the disk's when opened by path, the host's when given), for W4's sort by time. plots_open_from's comparisons of a set by text with one by path leave that time aside. Other and RowOther gain unit.
  • PL41:
  • SetFile::y_interval (YInterval, shared or own), read, written only when own, and described.
  • plots::extent(set, scale, members): the smallest and largest of the members' counts on a scale, finite values only.
  • The browser package (drs-wasm 0.80.0):
  • W5: plots_names_in { path, text } → { files } gives the files a .buf or a .drv names. A set file is known by its content, else by its extension. Its members are joined to the folder above Plots/ (set_base), a buffer's names to its own folder, as the app joins real paths.
  • W5: plots_open_buffer_from and plots_open_set_from take { path, text, files, root } beside the earlier shapes. A file is found by the path joined to the folder, or by its bare name.
  • W5: plots_open_from names the members relative to the first file's folder, as the app's open_files does, and takes reader and title. plots_save_text returns { relative, path, text } with plots_save.
  • plots_propose relativises the names the same way, so a proposed set file opens from the folder's Plots/.
  • plots_extent ports Plots' step 20 (the app's other addition, MemberView.modified, is ported too).
  • tests/commands.rs reads the app's #[tauri::command]s and fails, naming it, on a difference. The app's 34 commands are 19 by the same name, 5 by their text form (_from), and 10 declared not in the browser with why. The package has 3 commands of its own beside describe: plots_names_in, plots_save_text and plots_propose.
  • Tests:
  • plots_propose (5 cases):
  • \(\mathrm{Tb_{2}Pd_{2}Sn}\)'s buffer gives one set by temperature, 19 scans 4 to 30 K, tb_t01 left out with why.
  • A run is written in the test: 11 xys scans 600 s apart, named by their seconds, a TEMP line in each header, a ramp, a hold of 4 at 1270 K and a cooling, plus two scans of a shorter exposure and a file nothing reads. It gives against time (11, 100 min), the hold (4, 30 min) and by temperature (8), two classes, and each set opened and mapped with T on the rows.
  • Times from the host's modification times, and none from a copy's.
  • W3's t from the names, and none from a mix.
  • drs plots propose.
  • plots_sets::pl41_the_y_interval_and_the_sets_extent: own read back equal, absent read as shared, the refusal, and extent on the log scale equal to the map's colour range and to drs plots map's.
  • core/wasm/tests/map.test.mjs, from 5 cases to 8: W5's buffer and set file by virtual path (/local/1_Tb2pd2sn/…), a missing member named, save as text; W2 on the Tb buffer; PL41's extent equal to the log map's range.
  • core/wasm/tests/commands.rs.
  • The .wasm built here is 1254 kB, without wasm-opt (1155 kB at 0.79.0).
  • Not done: W2's test on the ESRF run's 101 files (esrf-datuak-irene, its L021: 25 by temperature, 100 against time, the 1270 °C hold of 39). The files are read-only on the Mac and never copied into the repository. Their names (file_index.csv) did not reach this session, so the test waits for them. elapsed_in_name's pattern is therefore unchecked against those names; and the hold's temperature in °C groups within 0.5 of the files' unit.
core/src/plots.rs (propose, Proposal, Proposals, ExposureClass, SAME_TEMPERATURE, SAME_WRITE, elapsed_in_name, PlotSet::fill_elapsed, YInterval, SetFile::y_interval, extent, Member::modified, OpenedFile::modified, Other::unit) · core/src/main.rs (drs plots propose) · core/tests/plots_propose.rs · core/tests/plots_sets.rs · core/wasm/src/host.rs, src/lib.rs, tests/map.test.mjs, tests/commands.rs, README.md · web/REQUESTS.md W2, W3, W5 · plots/REQUESTS.md PL41 · app/src-tauri/src/modules/plots.rs (plots_extent, MemberView.modified) · L425
L4252026-10-25work

Core 0.79.0 (W1, W3), Plots' step 20 (W4) and the web host's step 2 merged; diff-ref-struct Web runs the Plots module on the wasm core in the browser, the \(\mathrm{Tb_{2}Pd_{2}Sn}\) map from dropped files

The three orders of L423 came back the same morning and are merged into main: core 0.79.0 (core branch 4c5fc3fe, L424: plots::open_from, a member's other variables, the crate drs-wasm with the Plots module's commands, 1184 kB of wasm, the \(\mathrm{Tb_{2}Pd_{2}Sn}\) map to the bit in node), Plots' step 20 (plots branch 440db714: the y interval shared or own, the members' list richer with icons, checkboxes, all/none/class/range and three orders, the right axis built for W3 and absent until 0.79.0 reaches the app; PL41 filed: y_interval in the set file and plots::extent), the web host's step 2 (web branch 1c2e68da: app/ui/host-web/, the shell's files and the module loaded from app/ui/ as they are behind dispatch.js, the inspector at the left with < and the separator, a folder picked or dropped, the stub refusing in one sentence; W5–W7 filed: the path commands as text and plots_names_in, the host taken from the page instead of a Tauri stand-in, plots.adopt and Save's words).

Checked on main: the app's UI suite (263), the viewer's assets fresh, the web host's node tests (16 of 17: the one that asks the stub's manifest fails only while a built package sits in wasm/, an environment matter for the stream to make explicit), the wasm package's node test (5 of 5 once the command line was rebuilt at 0.79.0: its two failures against a stale 0.78.0 binary were mine). Then the whole thing in the real browser (Interceptor): web/tools/build-wasm.sh put the package in app/ui/host-web/wasm/; the page loaded with "core 0.79.0 in WebAssembly"; the first call failed on one line of the dispatcher, which parsed the package's answer as JSON text while dispatch returns a promise of the parsed value (W5 asked for text, the core answered with values): patched here to await and take either, the stream told. Then, through the page's own dispatcher, the \(\mathrm{Tb_{2}Pd_{2}Sn}\) buffer and its twenty files opened in the wasm core and gave the map as the desktop does (19 rows, 301 columns, 2.957 to 4.837 on log, one member apart); and through the user's path, the twenty files dropped on the page opened as a set of 20 members with the seven views, the Map drawn by the core, the clip's marks and controls, the members table with its checkboxes, the line that nothing leaves the machine (~/Downloads/interceptor-capture-20261025-100052-92634.png). Two things found for the next orders: a .buf dropped with its files goes to the data reader and is refused (the host must route a buffer or a .drv through plots_open_buffer_from / plots_open_set_from, W5's plots_names_in telling it which files they name); and the package lacks plots_extent, which Plots' step 20 added to the app's Rust side after host.rs was ported from app 0.53.0, so the y toggle's shared interval is refused in the browser until the core ports it (PL41 (b) gives it properly). Next: core 0.80.0 (W2 the series detector, W5's text commands and plots_names_in, PL41, plots_extent, W3's t), the web host's step 3 (the routing, W6 and W7 with Plots, the proposals offered), then live mode and the public repository.

core branch 4c5fc3fe (L424) · plots branch 440db714 (README twentieth step, PL13p, PL41) · web branch 1c2e68da (README step 2, W5–W7) · app/ui/host-web/dispatch.js (the await) · core/wasm/tests/map.test.mjs · ~/Downloads/interceptor-capture-20261025-100052-92634.png · L423
L4242026-10-25work

Core 0.79.0: the core in the browser (W1) and a member's other variables (W3): plots::open_from, the crate drs-wasm with the Plots module's commands, the \(\mathrm{Tb_{2}Pd_{2}Sn}\) map to the bit in node

The web stream's W1 and W3 (I10, R34; L422, L423), in a cloud session: the wasm32-unknown-unknown target and wasm-pack 0.15.0 installed in the session's container, as they are on the Mac (L423).

  • W1 (a), a set from files given by name and content, with no path touched:
  • plots::open_from(&[OpenedFile { name, text, comment, variable }], reader, title) and open_from_with (Data's operations, the files they name taken by name); OpenedFile's comment is a buffer's text, variable a typed value with its note.
  • plots::open_with (and so open, open_buf) now reads the paths' bytes and calls the same reader, members_of, as open_from. It is not routed through open_from itself: a binary file (Bruker RAW, BRML) passed as text would not be its bytes.
  • SetFile::open_from gives a .drv's members from files given by name and refuses a missing one by name; open and open_from share open_by.
  • W3, a member's other variables with their sources:
  • Member::others (Other { name, value, found }); in the .drv, variables = { T = { value, source } } in a member's table, absent by default and written back by to_text and SetFile::of.
  • Refused: an other variable without its source, the set's own variable as an other, one given twice.
  • When the set's variable is not T and a member types no T, PL2's sources (the header, the buffer's comment, the title, the name) fill T with their source. t has no source in the core yet: W2's.
  • plots::map gives each row its member's others as RowOther { name, value, source }, the source in Found::words.
  • W1 (b), the crate core/wasm (drs-wasm 0.79.0, wasm-bindgen 0.2.129, js-sys):
  • src/host.rs is the app's app/src-tauri/src/modules/plots.rs (app 0.53.0) ported. The sets are kept by id and the view rule, SetView, Settings and RegionView are as the app's, generated from its lines with the changes asserted, so the two can be compared line for line.
  • Where a browser has no path: plots_open_from (for plots_open_files), plots_open_buffer_from, plots_add_from, plots_open_set_from, plots_temperatures_from take files by name and text. plots_save returns { relative, text } in place of writing, and plots_derive adds the derived file's text.
  • Not ported: Data's operations and a control file's λ (the project's), .seq, the results series, the CSV export, reload, write_text, figure widths.
  • describe { name, text } gives { kind, id, name, role, source } from formats::project::describe; the app has no such command to copy.
  • dispatch(cmd, argsJson) returns a promise of the parsed result, rejected with the core's words; each command is also exported by its name; commands() and version().
  • Built with wasm-pack build --target web --release: 1155 kB for the .wasm, without wasm-opt. The core's own crate compiles to wasm32 unchanged in what it computes.
  • Tests:
  • plots_open_from (3 cases): the \(\mathrm{Tb_{2}Pd_{2}Sn}\) files by name and text give the same set and the same map as by path; the buffer's comments as open_buf gives them; a typed value; an operation's file taken by name or refused; a .drv's members by name equal to SetFile::open from a project on disk; W3 read, written back, opened, carried by the map's rows, refused.
  • core/wasm/tests/map.test.mjs (node, npm test, 5 cases): the package loaded; the Tb files as text; the map equal to drs plots map --json's to the bit, linear and the √ difference map against tb_t20; a region (rows 8–14, columns 30–32), a warning, the set file saved as text and opened again, describe, the stack, the projection, the windows, a derived pattern; refusals in the core's words; the size printed.
  • plots_page's key list of a map row takes others beside the viewer contract's keys.
  • The Python package's version follows the core's (L411).
core/src/plots.rs (OpenedFile, open_from, open_from_with, members_of, Other, RowOther, SetMember::variables, SetFile::open_from, open_by) · core/wasm/ (Cargo.toml, src/lib.rs, src/host.rs, tests/map.test.mjs, package.json, README.md) · core/tests/plots_open_from.rs · app/src-tauri/src/modules/plots.rs (ported) · web/REQUESTS.md W1, W3 · L422, L423
L4232026-10-25decision

I10 green as R34: the analysis in the browser; the web stream opened; W1–W4 filed; three work orders at once for the morning

Josu, on the assessment of I10 (L422): yes to all, let's start, everything by the morning, the files are waiting to be analysed; the fifth ask answered: a stream of its own. So decided and done here:

  • R34 appended to the brief (a group of its own, "The analysis in the browser"); I10 green with it.
  • The web stream opened: web/ (README with the charter and the five steps, DESIGN.md with what is settled, REQUESTS.md), branch web, worktree ../20261009_diff-ref-str-web; it owns web/, app/ui/host-web/ and the Action publish-web.yml; CLAUDE.md's layout and streams table carry it. The public repository diff-ref-struct-web created empty for step 5, to hold the built app only.
  • Filed by this session: W1 to the core (a set from names and text, plots::open_from; a drs-wasm crate exporting the plots subset with the Tauri commands' names and JSON, built with wasm-pack; a node test that the \(\mathrm{Tb_{2}Pd_{2}Sn}\) map equals drs plots map's to the bit), W2 (the series detector plots::propose, tested on the ESRF run's 101 files and \(\mathrm{Tb_{2}Pd_{2}Sn}\)'s buffer), W3 (a second variable per member with its source, for the map's right-hand axis), W4 to Plots (step 0's controls: the shared/own y toggle, the right axis from W3, the richer members table).
  • The toolchain on the Mac: the wasm32-unknown-unknown target and wasm-pack installed for the core's local worktree. The order of work, for speed: three sessions at once — the core (W1 and W3 first, then W2, as 0.79.0 and 0.80.0), Plots (W4, its step 20, the right axis last when 0.79.0 is merged), the web host (step 2 scaffolded on the dispatcher's stub, the inspector, the file access, then wired to the wasm when W1 is merged); then steps 3 to 5 as the pieces land. Set aside: the Plots stream widened to the browser host (one stream carrying the module and two hosts would serialise what can run in parallel tonight).
data/ideas.yml I10 · data/brief.yml R34 · web/README.md, DESIGN.md, REQUESTS.md W1–W4 · CLAUDE.md · L422
L4222026-10-25work

I10 registered: the analysis in the browser, a public web app on the core compiled to WebAssembly, with Josu's asks on the present page assessed

Josu, after the ESRF series was worked with the interactive map (L421): a standalone, full HTML app, public on his repositories, giving anyone with the URL a UI for this kind of analysis, access to the user's local files and the possible map plots offered from them; and on the present page an inspector on the left with a shrink and a draggable separator, a toggle between a y interval adapted to the file shown and one fixed to the whole set, a toggle between √ and the raw values, the question whether the map's vertical axis should show temperatures rather than hours, and a richer file inspector. Written to the seed-place as I10 (status discussed), assessed: the one architecture that keeps L139 is the core compiled to WebAssembly and run in the browser, the Plots module and the viewer unchanged behind the single invoke they use, the files read in the browser and never uploaded (a static site, usable offline, a live mode on a folder in Chromium for the instrument's case); "the possible maps from the files" is a series detector of the core (plots::propose), what F07 and S01–S12 of esrf-datuak-irene found by hand; the hundred-scan map is against time because the holds share a temperature, so the temperature belongs on a right-hand axis from a second variable per member; the √ toggle is live in the web host and a matter of embedded scales in the static page. Five steps proposed and a step 0 for the three asks that need no web host (the shared/own y toggle, the second variable and the right axis, the richer members table). Facts checked: the core has no mandatory dependency, its readers work from text, the module's sixty core calls go through one function, the site's public repository is built by an Action from the private one. Nothing built; Josu's decision on the asks (the name and address, step 0 first, a stream of its own).

data/ideas.yml I10 · core/Cargo.toml · core/src/data.rs (read) · app/ui/core.js (invoke) · esrf-datuak-irene L021, F07, S01–S12 · L139, L412, L421
L4212026-10-25work

App 0.53.1 built, self-checked and installed: a region's colour range per view in Josu's app; nothing of I08 open

The correction of L420 released from main b183e14e, the cycle of L418: the self-check bundle built with its own identifier, Plots' self-check run on a copy to its quit and relaunched: 148 OK, 0 FAIL, then 160 OK, 0 FAIL (the step 19 line among them, a region saved on each map with its own range, the other map untouched); Josu's own files of the app unchanged. The release bundle built under eus.ehu.diffrefstruct as 0.53.1 and installed in /Applications, 0.53.0 to the Trash. Josu's app now holds core 0.78.0, drs-publish 0.36.1 and Plots' step 19 on top of 0.53.0's interactive map. P65 closed (L420); I08 migrated (L418); no request of the map's work is open in any stream's REQUESTS.

scratchpad self0531 (run.log, selfcheck-report.txt) · app/src-tauri/target/release/bundle/macos · data/releases.yml app 0.53.1 · L418, L420
L4202026-10-25work

P65 closed: core 0.78.0 and Plots' step 19 merged, the bridge completed, drs-publish 0.36.1; app 0.53.1 released

Josu ran the two P65 orders at the same time, so Plots' step 19 (plots branch c26524a7) was built on main 9ee529c9 with core 0.77.0, whose Region had no difference_range yet, while the core built 0.78.0 (core branch efa3086d, L419) with it. Merged here in that order. The step had done its part in full (a region's own range written from and applied to the view shown, the map's and the difference map's apart, in the viewer and the module; the Rust side's RegionView carrying both; the bundle regenerated) and left two lines for the core's arrival, which this session wrote at the merge: to_core and of_core carry difference_range, and Save's note that the range waited for the core is gone. Without them the bridge no longer compiled against 0.78.0, which is the error Josu suspected. Publish's own part done here too, drs-publish 0.36.1: F3's difference map takes a region's difference_range as its clip and the map its map_range, each left aside on the other without a note; the test maps gives "m1 own" both ranges. The core merge had first been refused by the generated core/CHANGELOG.md left modified by a build.py run; discarded and regenerated.

On main after the merges and the edits: the app's Rust tests pass (100), the UI suite (257), the viewer's assets are fresh (--check), Publish's maps pass (4); the core's plots_sets and plots_page passed in the core's session (L419). P65 marked met in publish/REQUESTS.md. App 0.53.1 released for the correction, the same cycle as 0.53.0 (L418): its self-check counts and the install are in L421.

core branch efa3086d (L419) · plots branch c26524a7 (README, nineteenth step) · app/src-tauri/src/modules/plots.rs (RegionView::to_core, of_core) · app/ui/modules/plots/index.js · publish/src/patterns.rs (map_shown) · publish/tests/maps.rs · data/releases.yml drs-publish 0.36.1, app 0.53.1
L4192026-10-25work

Core 0.78.0: Publish's P65, a region keeps a colour range per view, map_range on the map and difference_range on the difference map

P65 (L416, passed to the core and Plots in L417). A region kept one map_range, whichever view it was saved on, so the file could not say whether its range was on the map's scale (log₁₀ counts for the \(\mathrm{Tb_{2}Pd_{2}Sn}\) set) or the difference map's (√ counts).

  • plots::Region gains difference_range beside map_range, absent by default:
  • read from and written to a [[regions]] table after map_range;
  • refused, as the map's range is, when from is not below to;
  • a .drv and a JSON region written before read as before, their one range the map's.
  • drs plots describe says each range with the map it is on and that map's scale, the difference map's from difference_scale (√ for a log map): "its colour range on the map 3.434 to 4.661 (log scale), its colour range on the difference map -40 to 60 (sqrt scale)". PL37's test takes the new words.
  • drs plots region prints the region's range for the map it computes and names the other. With --reference it now draws the difference map on its own scale, as page_data does (the square root for a log map; before, it asked for the map's log scale, which map refuses with a reference).
  • page_data's regions carry difference_range beside map_range, in the page's JSON.
  • map_region's block does not depend on a region's ranges, which are views of the block: it is unchanged, and its doc says so.
  • Tests:
  • p65_a_regions_range_per_view (plots_sets): a region with both ranges read back equal, as text and through serde; one with only the difference map's; a file from before P65; the refusal; the words of describe; region on each map.
  • page_data_holds_the_map_and_the_regions and drs_plots_show_writes_one_page (plots_page): the page's JSON holds both ranges.
  • For the Plots stream (P65's other half) and the app's build:
  • app/src-tauri/src/modules/plots.rs:655 builds plots::Region field by field and does not compile against 0.78.0 until it names difference_range (RegionView gaining the field, or difference_range: None).
  • The viewer writes the range it has into the field of the view shown, and applies each to its own view.
  • The Python package's version follows the core's (L411).
core/src/plots.rs (Region, SetFile::read, to_text, words, PageRegion, map_region doc) · core/src/main.rs (drs plots region) · core/tests/plots_sets.rs · core/tests/plots_page.rs · publish/REQUESTS.md P65 · L407, L413, L416, L417
L4182026-10-24work

App 0.53.0 on core 0.77.0 and drs-publish 0.36.0 built, self-checked and installed: I08 migrated, the interactive map in Josu's app

The merging session's release of what I08 (R33) brought, from main e73e5b89: the self-check bundle built with its own identifier (PL36, plots/tools/build-selfcheck.sh, niced, eight jobs), Plots' self-check run on a copy to its quit and relaunched: 151 OK, 0 FAIL, then 146 OK, 0 FAIL; Josu's own files of the app unchanged (six snapshotted). Then the release bundle built under eus.ehu.diffrefstruct as 0.53.0 and installed in /Applications, 0.52.1 to the Trash. Josu's app now holds Plots' steps 16–18 (the clip on the colour bar and on the Surface's height, the row and column cursors, the zoom in the T–2\(\theta\) plane, range from the view, the regions saved in the set file, the Map viewer as one module), the launch onto a .drv with --view (PL39), on core 0.77.0 (map_range, difference_range, [[regions]], plots::map_region, plots::page_data, drs plots show) and drs-publish 0.36.0 (F3's map with the clip and a named region). The idea is migrated in the seed-place with its version. Known in the release entry: P65 (a region's own range does not say which map it is on; Publish takes it for the map only) and the strips' need of room. Not run this time: the core's whole suite and the app's Rust tests on main (the core's plots_page and plots_sets, Publish's maps and the UI suite were, L414, L417); the streams ran their own suites on the same commits.

scratchpad self0530 (selfcheck-report.txt, run.log) · app/src-tauri/target/release/bundle/macos · data/releases.yml app 0.53.0 · data/ideas.yml I08 · L405–L417
L4172026-10-24work

PL39 (the app launched onto a .drv with --view) and Publish's 0.35.0 and 0.36.0 (PL40) merged: every step of I08 built; app 0.53.0 prepared; P65 passed to the core and Plots

The app stream's PL39 (app branch 17d75cad): open -a diff-ref-struct <set.drv> --args --view map|difference|surface opens the set in Plots on that view; launch_view is asked once by the window that opens the file and runs Plots' own plots.view.<view>; a refused flag is said; the startup files' test takes a .drv and the flag; the manual's launch paragraph says it. The Publish stream's two versions (publish branch 68df867e): drs-publish 0.35.0, the checks before export as DESIGN.md §5.4 writes them (L415), and 0.36.0, F3's map and difference map taking the set's clip and a named region (PL40, L416). Both fast-forwarded or merged into main (72f39dec); on main the app's node suite passes (256 tests) and Publish's maps tests pass (4; test-runner). PL39 and PL40 marked met in plots/REQUESTS.md.

Publish's P65, found building PL40: a region's own map_range does not say which map it was set on (the viewer writes it from the view shown, log₁₀ counts on the map, √ counts on the difference map of the same set), and Publish takes it for the map only until it does. Passed on: the core's next step gives Region a difference_range beside map_range, as the set file has, and Plots' next step writes the one of the view shown and applies it to that view only. Not blocking the release: a region saved on the difference map is rare and its range is ignored by Publish rather than misapplied.

Every step of I08 (R33) is now built: the clip and the cursors (16), the zoom and the regions (17), the viewer and the page (18, core 0.77.0), the app launched onto a set (PL39), Publish's figure (PL40). None of it is in an installed app yet: app 0.53.0 on core 0.77.0 and drs-publish 0.36.0 is prepared here (the version in the three files, the release entry with its known items), to be built, self-checked under PL36 and installed by this session, after which the idea migrates (L418).

app branch 17d75cad · publish branch b9367c08, 68df867e · plots/REQUESTS.md PL39, PL40 · publish/REQUESTS.md P65 · data/releases.yml app 0.53.0 · L414–L416
L4162026-10-24work

Publish stream: drs-publish 0.36.0, F3's map and difference map take the plot set's clip and a named region (PL40, I08 step 2, R33); P65 filed

PL40 as filed (L414), on core 0.77.0 (DESIGN.md §46). F3's map is drawn on the set's map_scale (else y_scale, PL27), the scale its clip is written on; the difference map on the core's difference_scale of it. With map_range or difference_range, the colours run over the clip and every value beyond is drawn in the end colours, as the app's clipPlace does. The colour bar's axis is the clip's range: five ticks at its ends and evenly between (W5), never widened, every label with the same number of decimals (3.70, 3.89, 4.08, 4.27, 4.46), a phantom minus on a signed bar (G2). The build repeats the core's warning for a clip wholly outside the map's values, and warns when a difference clip is not symmetric about zero (C6). A figure's region = "…" draws the core's block of a region the set saves (plots::map_region with MapX::of; F3 draws a set in its files' quantity, so of_view waits for F3 to take a set's x in d or Q). The block's rows and columns only, the region's bounds as the axes, its own map_range as the clip when it has one. A region the core refuses refuses the figure in the core's words. The caption says the region, the range shown, what is saturated and the core's range; in Spanish and Basque too. F3's options in the app gain Region; the manual says it.

On the \(\mathrm{Tb_{2}Pd_{2}Sn}\) set with map_scale = "log", a clip of 3.7 to 4.46 and m1 (18.95–19.25°, 20–26 K), the caption of m1 as written: "Map of 20 patterns against $T$ on the logarithmic scale, on their common grid. The region m1: $2\theta$ from \qtyrange{18.95}{19.25}{\degree} and $T$ from \qtyrange{20}{26}{\kelvin}, the core's block of the map, its rows 9 to 15 and its columns 31 to 33. Colours from \num{3.7} to \num{4.46} on the logarithmic scale. Values below \num{3.7} and above \num{4.46} are drawn saturated in the end colours. The core's range in the region is \num{3.434} to \num{4.661}, on the whole map \num{2.957} to \num{4.837}. $T$ from the files' titles." The block is L407's and L414's (rows 8–14 and columns 30–32 counted from 0, cells 3.434 to 4.661). The new example tb2pd2sn-clip-tfg is built and checked with no error or warning. The PDFs were looked at: the whole map with the peaks saturated, m1 as its \(7 \times 3\) cells. tb2pd2sn-tfg rebuilt, its figures' PGF unchanged but the header.

Tests (tests/maps.rs): the_map_takes_the_sets_clip, a_figure_names_a_region, a_region_the_core_refuses_refuses_the_figure, a_set_without_them_draws_as_before (the example's map and difference map equal, line by line, to 0.34.0's PGF). The suite: 71 passed, none failed (21 unit, 44 building, 4 maps, 2 guards; test-runner).

Filed to the core and Plots, P65: a region keeps one map_range, which the viewer writes from and applies to whichever view is shown, so the file cannot say whether it is on the map's scale or the difference map's. F3 takes it for the map only and says so on a difference map. What is left of I08: PL39 (the app stream); then the idea migrates.

branch publish, the commit after b9367c08 · publish/DESIGN.md §46 · publish/src/patterns.rs, set.rs, module.rs · publish/tests/maps.rs · publish/REQUESTS.md P65 · plots/REQUESTS.md PL40 · L407, L414
L4152026-10-24work

Publish stream: drs-publish 0.35.0, the checks before export as DESIGN.md §5.4 writes them, found uncommitted in the worktree and committed

The Publish worktree held a whole build written on 21 October and never committed, logged or released: drs-publish 0.35.0, checks 2–5 of DESIGN.md §5.4 as written there (§45). Check 2 reads T1–T6 back row by row and compares each number with the results file's at the printed precision (readback.rs); check 3 reads every phase's block and applies checkCIF's powder tests; check 4 checks each figure's height, text sizes and line widths from its PGF and each raster's resolution (raster.rs); check 5 checks pixel sizes, one-line labels and insets over the data. Also publish/tools/selfcheck.sh, the stream's self-check under PL36. The branch had been fast-forwarded to main 33570f67 under it, and nothing of it was lost.

Its suite on its own gave 65 passed and 1 failed. The failure was the L139 guard (tests/nophysics.rs): check 2 took the unit of a number's last printed digit as 10f64.powi(-d). That unit is now read from its text ("1e-d" parsed), the same number; the guard and the unit tests pass. Committed on its own (publish b9367c08) before PL40's build (L416), so that the merging session can take or set it aside apart from PL40. The example sets were not rebuilt by it.

branch publish b9367c08 · publish/DESIGN.md §45 · publish/src/readback.rs, raster.rs, checks.rs · publish/tests/nophysics.rs
L4142026-10-24work

Core 0.77.0 merged and the map's page checked from the core alone in the browser; PL38 met, PL40 filed to Publish; what is left of I08

Core 0.77.0 (core branch e3f0c3a4, L413) fast-forwarded into main. Checked by this session on main: the core built in release and plots_page and plots_sets pass (17 tests, test-runner). Then the deliverable itself, end to end without the app: a set saved from the \(\mathrm{Tb_{2}Pd_{2}Sn}\) buffer (drs plots save), map_scale = "log", map_range = { from = 3.7, to = 4.46 } and a region m1 (18.95–19.25°, 20–26 K) written into the .drv; drs plots describe says the clip and the region; drs plots region gives rows 9–15 and columns 31–33 counted from 1 and the indices from 0, its cells 3.434 to 4.661; drs plots show Plots/tb.drv --browser --no-open writes Plots/tb.map.html (333 kB, nothing fetched) beside the set. Served locally and opened in the real browser (Interceptor, the isolated profile): the page draws the core's map on the log scale with the clip's marks on the colour bar and the strong peaks saturated, the row without a temperature apart (tb_t01.dat), the twenty members with their temperatures and the region listed; choosing the region zooms to it and the status reads the core's block; the clip stays the file's, since that region has no range of its own. Two small things for later, not findings: a set without a title is headed "plot set" (the file's stem would read better), and the region's name is cut short in the narrow sidebar.

PL38 marked met in plots/REQUESTS.md. PL40 filed to the Publish stream: F3's map and difference map take the set's clip and a named region, the caption saying the range shown and the saturation (I08 step 2). What is left of I08 (R33): PL39, the app launched onto a .drv with --view (the app stream; drs plots show already runs open -a diff-ref-struct <set.drv> --args --view <v> when the app is installed and says the flag waits for PL39), and PL40; then the idea migrates. The plots, core, app and publish branches brought up to main.

core branch e3f0c3a4 · core/tests/plots_page.rs · plots/REQUESTS.md PL38, PL40 · data/ideas.yml I08 · ~/Downloads/interceptor-capture-20261024-205302-49966.png · L412, L413
L4132026-10-24work

Core 0.77.0: Plots' PL38, the page's data in one JSON (plots::page_data) and drs plots show, the Map viewer's page without the app (I08 step 5, R33)

PL38 as filed (L409) and as the Plots stream's step 18 shaped it (L412): the viewer's header in app/ui/viewer/map.js is the contract, field by field.

  • plots::page_data(set, &SetFile, &MapX, reference, path, patterns) -> PageData, written as the viewer reads it:
  • title, path, core (the crate's version), written (the day, UTC, from formats::files::now_text);
  • variable { name, unit }; x { label, quantity } (the core's XQuantity::label, "2\(\theta\) (°)", "d (Å)", and its word); y_word ("normalised counts" when the file names a normalisation, as the app's yWord);
  • members [{ label, variable { value, source } | null, pattern { x, y } | null }], the source in Found::words;
  • maps { map, difference }, each { map { x, x_edges, rows, z, range }, apart [{ member, z }], scale, reference }, as the app's plots_map gives the viewer.
  • The map is on the file's map_scale (else y_scale), the difference map with the file's reference on difference_scale (the square root for a log map scale, the module's diffScaleOf), both on the overlap at the finest step as drs plots map.
  • clip { map, difference }; regions with blocks { map, difference } from map_region and refused in the core's words; regions_refused (check_regions); same_grid.
  • Refused in words when fewer than two members have a value of the variable.
  • Beyond the contract:
  • The scale is written as its word: Map's own serde writes the enum's variant, "Log", which the viewer does not read.
  • A view field ("map" or "difference", from --view). The shell of step 18 opens on the map and does not read it yet.
  • MapBlock::counted_from (always 0): the blocks' rows and columns are the arrays' indices from 0, while the page prints them from 1. drs plots region now prints both: "rows 9–15 … counted from 1, the indices from 0 being rows 8–14, columns 30–32", and its scale as the word.
  • plots::page_html builds the page from the shell and the bundle, embedded with include_str! from core/assets/viewer/ (generated by the app's bundler, L409, not edited here):
  • The JSON goes in at <!--DRS_PAGE_DATA--> with every < written \u003c.
  • The bundle goes in at <!--DRS_VIEWER_BUNDLE--> with </ written <\/. Its five </ are in template literals, where that reads back the same.
  • Each marker is replaced in place, whatever the other's content holds. A missing marker is refused in words.
  • drs plots show <set.drv> [--view map|difference] [--browser] [--out] [--no-open] [--no-patterns] [--reference k] [--wavelength λ]:
  • On macOS with the app installed (mdfind kMDItemCFBundleIdentifier == 'eus.ehu.diffrefstruct', or /Applications/diff-ref-struct.app) and without --browser, it runs open -a diff-ref-struct <set.drv> --args --view <v> and says that the app takes --view from PL39.
  • Otherwise it writes the page beside the set, <set>.map.html (or --out), and opens it with open, or xdg-open on Linux, unless --no-open.
  • The page takes the set as the view shows it: normalised as the file says, and in the file's x through x_view and MapX::of_view. A member left without a wavelength is refused by name (--wavelength).
  • It writes over its own pages and never over another file. --view difference without a reference is refused.
  • Sizes for the \(\mathrm{Tb_{2}Pd_{2}Sn}\) set (20 members, 19 rows \(\times 301\) columns, the shell and bundle 128 kB):
  • with the difference map: 445 kB with the patterns, 361 kB without;
  • without a reference: 334 kB and 249 kB.
  • The patterns are about 85 kB of the JSON, not two thirds (L412's estimate): the maps' cells, written to full precision, are most of it.
  • Test plots_page (6 cases):
  • page_data_holds_the_map_and_the_regions: the map's rows, range and cells as plots::map gives them; the difference map on sqrt; m1 (18.95–19.25°, 20–26 K) rows 8–14 and columns 30–32 from 0, equal to map_region on both maps; the region drawn in d refused (the files state no λ), the members named; the members' temperatures and sources; every key of the viewer's header, no more and no fewer.
  • no_reference_no_difference_map.
  • one_member_is_refused.
  • drs_plots_show_writes_one_page: the JSON and the bundle in it; no http but the two SVG namespaces; three </script>; --no-patterns smaller with the same maps; its own page rewritten, another file refused; the difference view without a reference and a one-member set refused.
  • the_viewers_assets_are_present_and_consistent: SOURCES.txt against the bundle's header, each source's part, createMapViewer, each marker once. The app's sources are not in core/, so the asset's agreement with them is the bundler's --check.
  • drs_plots_region_counts_from_one_and_says_the_indices.
  • The Python package's version follows the core's, 0.77.0 (L411).
core/src/plots.rs (PageData, page_data, difference_scale, page_html, MapBlock::counted_from) · core/src/main.rs (drs plots show, region) · core/tests/plots_page.rs · core/assets/viewer/ · app/ui/viewer/map.js (the header) · plots/REQUESTS.md PL38, PL39 · L409, L412
L4122026-10-24work

Plots step 18: the Map viewer factored out for the page without the app, bundled as a generated asset of the core (I08 step 5, R33), merged and checked in the browser

Plots' step 18 (plots branch c0118924, on main 4820d5ac), fast-forwarded into main the same day. The viewer, app/ui/viewer/map.js: the Map view's part of the module as one ES module under Plots' guard, importing only plot.js and mapclip.js (and through plot.js the other two shell files), holding steps 16 and 17 whole (the image series with its clip and marks, the cursors' strips, the T–2\(\theta\) zoom kept across redraws, range from the view, the regions and the jump to one); it never redraws itself, every change is told to the host, which draws again, so the app's note, sidebar and status stay in step; the Plots module is its app host on the shared plot and draws nothing of the map itself, which a test forbids. The bundler, app/ui/tools/bundle-viewer.mjs (npm run bundle-viewer): five sources in dependency order as one classic script, imports dropped and exports made plain declarations, a header naming each source and its sha256, plus the page shell and SOURCES.txt, into core/assets/viewer/ (generated, L409); deterministic, --check finds a stale asset. The page shell, page.html: markup, the least CSS, the kit's colours inlined for light and dark, a script that reads the JSON from the data element and mounts the viewer, nothing fetched; its host answers a block from the saved regions' precomputed blocks only and says so for any other rectangle. Viewer Page… (Dev) writes such a page from the set shown through plots_write_text. Sizes: the bundle 116 kB, the shell 12 kB, a page of the Tb set with the patterns 424 kB. Self-check under PL36: 161 OK, 0 FAIL, relaunch 152 OK, 0 FAIL, Josu's files unchanged; the stream's four node tests that looked for the map's code in the module were reworded to the viewer. On main after the merge: the node suite passes, 255 tests, and the bundler's --check finds the asset fresh (test-runner).

Checked by this session in the real browser (Interceptor, the isolated profile): a page built from the shell, the bundle and a JSON made by hand in the viewer's shape from drs plots map --json of the \(\mathrm{Tb_{2}Pd_{2}Sn}\) set, served locally. The map draws from the JSON alone on the core's log scale with the clip's marks at 3.7 and 4.46 on the bar and the strong peaks saturated, the row without a temperature apart, the members and the region listed; choosing the region zooms to 18.95–19.25° and 20–26 K and applies its own range, the status reading the block. Two notes: the page prints a block's rows and columns from 1 while drs plots region (L407) counts from 0; and the region's name, present in the DOM, did not show in the DOM-render capture (a known artefact of that capture path, not confirmed on screen). What the core's page_data must write, field by field, with the stream's notes (the scale as a word, the difference map's scale, the rows apart in the app's form, the patterns optional, the </ escaping, the SVG namespaces allowed), is written under PL38 for the core's session.

plots branch c0118924 · plots/README.md (eighteenth step) · plots/REQUESTS.md PL13n, PL38 · app/ui/viewer/map.js, app/ui/viewer/page.html, app/ui/tools/bundle-viewer.mjs, core/assets/viewer/ · ~/Downloads/interceptor-capture-20261024-174820-80732.png, …-174857-81579.png · L409
L4112026-10-24work

The Python package's version is the core's again (0.76.0), and clippy's two warnings in the .out reader from core 0.72.0 are cleared

Two fixes taken from the PL37 build that was set aside (L410). Neither changes what the core computes.

  • core/python: drs-python's version had stayed at 0.71.0 since core 0.72.0. The package's version is the core's (drs.__version__ is drs-python's CARGO_PKG_VERSION, and tests/test_drs.py compares it with drs --version), so it is 0.76.0 again, in Cargo.toml and Cargo.lock.
  • core/src/formats/fullprof_outputs.rs (read_out, from core 0.72.0, app request 49):
  • Out is built with its banner in the literal (clippy's field_reassign_with_default);
  • the last cycle is taken with next_back (clippy's double_ended_iterator_last).
  • What it reads is unchanged: the .out reader's tests pass (fullprof_outputs, fullprof_comparison, gsas2_comparison, paths_compared), and clippy shows again only data.rs:62 and results.rs:946. No version of the core: these are not a release.
core/python/Cargo.toml · core/python/Cargo.lock · core/src/formats/fullprof_outputs.rs · L381, L398, L410
L4102026-10-24decision

PL37 was built twice on the same base: the pushed core 0.76.0 (2ba8063f) is kept, the second set aside

Two core sessions built PL37 at the same time from main 4fde9f62, each as core 0.76.0 under L407. The one pushed first (2ba8063f) is the one merged into main (1881400d). The second, made in a cloud session and refused on push as not a fast-forward, was not pushed.

  • What the two share: the set file's text, read and written the same way. Both write map_range, difference_range and [[regions]] (name, x, x_quantity, variable, map_range, window), each absent by default, with the same refusals.
  • Where they differ:
  • The kept one takes a region's x_quantity as Plots' XQuantity (2\(\theta\), d, Q, sin \(\theta\)/λ, s: every quantity the map is shown in, PL10). The second took the core's Axis (2\(\theta\), d, Q, TOF, energy) and so could not keep a region drawn in sin \(\theta\)/λ or s.
  • The kept one gives the members' λ and labels to map_region in a MapX. The second added them to Map and MapRow, and gave each row its own columns. Josu, the same day: keep the pushed one. Of the second, only its two fixes outside PL37 are taken (L411). Set aside:
  • force-pushing the second over the first, which would have dropped a merged release;
  • parking the second on a branch of its own, which kept nothing the kept one lacks except the per-row columns.
core commit 2ba8063f · main 1881400d · plots/REQUESTS.md PL37 · L405–L407
L4092026-10-24decision

Where the Map viewer's bundle lives for the page without the app: a generated asset of the core, written from the app's UI by a bundler and checked against its sources (I08 step 5; PL38, PL39)

Plots' step 17 asked (its README) where the viewer's bundle lives for drs plots show, since the Map view's viewer leans on four shell files (plot.js, mapclip.js, ticks.js, svgexport.js) and the page the core writes must carry them. Decided: the viewer is factored out as one ES module, app/ui/viewer/map.js, with no import outside those shell files and app/ui/modules/plots/'s guard kept over it; a bundler of the app's own, app/ui/tools/bundle-viewer.mjs, writes it and its imports as one script into core/assets/viewer/map.js with SOURCES.txt naming the sources and their hash; the file is generated, never edited by hand (added to the CLAUDE.md rule), regenerated by the Plots stream in the step that touches the viewer, and the core's test finds a stale one. The core embeds it with include_str! and writes a self-contained page, the page's data in one JSON from plots::page_data (PL38). The core's crate thus builds alone, in a cloud session without the app, and the page is the app's map and not a second one (L139, PL14).

Set aside: the core reading the app's built bundle at run time (needs the app installed, the very case the page is for); a --viewer <dir> flag (manual and fragile, every page depending on a folder beside it); include_str! across to app/ui/ from core/ (the crate no longer builds alone, and the core's tree reaches into the app's, against core/'s rule of no UI imports). The order of work: Plots' step 18 first (the viewer factored out, the bundler, the asset generated, a node test that the bundle has no import left and the guard holds), then the core's PL38 (page_data, drs plots show, the embed and the staleness test), then the app stream's PL39 (the app launched onto a .drv with --view), then Publish's step for the clip and a region in F3 (I08 step 2).

plots/README.md (seventeenth step, what step 18 needs) · plots/REQUESTS.md PL38, PL39 · CLAUDE.md (Rules) · L404, L405, L408
L4082026-10-24work

Plots step 17: the zoom in the T–2\(\theta\) plane, range from the view and the saved regions; the clip saved and reopened (I08 step 4, R33, on core 0.76.0), merged

Plots' step 17 (plots branch 6112152a, on main 1881400d with core 0.76.0), fast-forwarded into main the same day. Save writes map_range and difference_range from the tab's clips, each only while its scale is the set's map scale, and Open Plot Set… restores them per view; the "kept in the tab" status line is gone and Save says what went with the file; the core's warning for a clip wholly outside the map's values is asked once for a typed or restored clip and shown in the note. A rectangle dragged zooms both axes; the wheel over the y axis's margin zooms T about the pointer, over the x axis's band 2\(\theta\); a double-click shows the whole map; the zoom is the tab's and survives every redraw, the clip's and the cursors' included; the view draws the columns in view and asks nothing of the core on a drag. Range from the View, a switch beside Cursors and in the menu: on, the clip follows the zoom with the core's block of the rectangle (plots_map_region, asked after each change of the view, debounced for the wheel); off puts the clip back. Save Region… names the rectangle shown, with the quantity the map is shown in and the clip when the switch is on, after the core accepts it; the regions are listed in the sidebar beside the windows; choosing one zooms to the core's block and applies its own colour range; Rename and Remove; saved in the set file and restored; a region the core refuses is said in the core's words, the set opening all the same.

The module's Rust side (app/src-tauri/src/modules/plots.rs): Settings gains the clip and regions fields, a region view in the window's words, map_region and range_warning with their two commands, two handler lines in main.rs. The shell under PL13m: three pure functions in mapclip.js, and in plot.js keepView on the axes, an axisWheel option and the column span in view. The manual's Map section has two new paragraphs. Self-check under PL36: as first built 142 OK, 2 FAIL, relaunch 160 OK, 2 FAIL; after the fix 144 OK, 0 FAIL, relaunch 162 OK, 0 FAIL; Josu's files unchanged. Both first-run FAILs were the check's own: it expected L407's block indices for the rectangle, but the run's set holds the members typed earlier, so the core gave the same cells with other indices (the range exactly L407's 2718 to 45849 counts); and a chosen region correctly applied its own saved range while the check compared it with the dragged clip. The check now asks that the block be the rows within the temperature range and no other, three columns within the 2\(\theta\) range, and that range. On main after the merge the node suite passes, 250 tests (test-runner). Not versioned and not installed by the stream.

plots branch 6112152a · plots/README.md (seventeenth step) · plots/REQUESTS.md PL13m · app/ui/modules/plots/index.js, app/ui/mapclip.js, app/ui/plot.js, app/src-tauri/src/modules/plots.rs, app/ui/tests/plots_regions.test.mjs · L405, L407
L4072026-10-23work

Core 0.76.0: the plot set file keeps the map's clip and its named regions, and a region's block of the map (Plots' PL37, I08, R33)

PL37 as amended (L405, L406), in the core only. The set file (plots::SetFile) takes map_range and difference_range (plots::Range { from, to }, on the scale the map is drawn on: map_scale, else y_scale) and [[regions]] (plots::Region: name, x = [from, to], x_quantity "2theta", "d", "q", "stl" or "s", the set's x when absent, variable = [from, to] in the variable's unit, an optional map_range of its own, an optional window naming the window it was drawn from), each absent by default: a .drv written before reads as before and is written back without a new key. Read, written and read back equal; drs plots describe says them in words (or the file as JSON).

Refused when the file is read, each with its place and in plain words: a range with from \(\ge\) to ("map_range: from = 20000 is not below to = 1200; a range is written from its lower end", the same for difference_range and a region's map_range); a region's x or variable reversed or empty ("region \"m1 transition\": x = [19.25, 18.95] runs backwards or is empty: the smaller end first"); two regions of one name; an unknown x_quantity or key. Refused when a region is used on a set or a map whose x is another quantity and the conversion needs a λ (one of the two is 2\(\theta\)) that members lack, those members named ("region \"m1 transition\" is in d and the map in 2theta: converting it needs each member's wavelength, and these state none: tb_t10.dat, …"; SetFile::check_regions for the set as its files give it, map_region for the map's rows); a region on a TOF or energy set in a quantity; a region with no row, no column or no value in the map. Not refused but said: a colour range wholly outside the matrix's values (plots::range_warning; drs plots map and drs plots region print it).

plots::map_region(&Map, &Region, &MapX) -> Result<MapBlock, String>: the rows inside (the row's value within variable), the columns inside (the column's x within the region's bounds converted to the map's quantity, row by row with that member's λ, through sin \(\theta\)/λ as x_view converts; their union when the λs differ), those bounds, and the smallest and largest value of the block's cells, each row over its own columns: a reading of the matrix already computed. One departure from PL37's form, the third argument: a Map does not say which quantity its columns are in nor each row's λ (the app's map in d is drawn from x_view's patterns while the members' facts stay in 2\(\theta\)), so the caller states it with MapX::of(set) (the files' quantity and λ) or MapX::of_view(set, &view) (the view's quantity, each member's λ the view used, else its file's). Options set aside: the quantity and λs as new fields of Map, filled from the members' facts, which would be silently wrong for the app's map in d unless the app overwrote them; and map_region working only in the map's own quantity, which leaves the conversion to the module (L139).

On the \(\mathrm{Tb_{2}Pd_{2}Sn}\) set (Tb_vs_T.buf, 19 rows 4–30 K by 301 columns 16–46°), the region 18.95–19.25° by 20–26 K is rows 8–14 and columns 30–32 (19.0, 19.1, 19.2°), its range 2718 to 45849 counts (the map's 905 to 68658); the same region in d with λ 2.4266 Å gives the same block, and in Q too; without a λ (the files state none) it is refused, naming the seven members of its rows. drs plots region <set.drv> --name <n> prints the block's rows and columns and its range. The app's Tauri commands are untouched (this step edits core/ only): reading, saving and the block are Plots' step 17, whose module keeps { from, to, scale } per view and writes map_range/difference_range as { from, to } only while its scale is the set's map scale.

core/src/plots.rs (Range, Region, MapX, MapBlock, map_region, range_warning, SetFile::map_range, difference_range, regions, check_regions, x_quantity, words), core/src/main.rs (drs plots describe, drs plots region, drs plots map's warning), core/tests/plots_sets.rs (pl37_the_maps_clip_and_its_regions), ecosystem/data/files.yml (plot_set) · plots/REQUESTS.md PL37 · data/ideas.yml I08 · L405, L406
L4062026-10-23work

Plots step 16: the clip on the colour bar and the row and column cursors (I08 steps 1 and 3, R33), merged; PL37 amended for a region's x quantity

Plots' step 16 (plots branch 27cde514, on main 3ab1e4f9 with no new core work), fast-forwarded into main the same day. In the Map and the Difference Map two marks on the colour bar set the range shown, dragged or typed (Set Colour Range…; a field left empty keeps the core's end; a pair wholly outside the core's range refused); values beyond are saturated at the end colours, the bar keeps the core's range as its axis and shows the saturation in its colours; the clip is per view and on the scale the map is drawn on, and a double-click on the bar or Reset Colour Range returns to the core's range; the same pair clips the Surface's height. The row under the pointer draws that member's pattern, as the set holds it, in a strip below the map (in the Difference Map the core's row of the difference); the column draws the matrix's column against T in a strip beside it; the cell is taken whole, never interpolated; a click pins both cursors, a click again releases them; cursors can be turned off, are off in the figure look and are not sent to Publish. Until the core has PL37 the clip lives in the tab and Save says so in the status line.

The layout is the shell's (PL13l): app/ui/mapclip.js new and pure (a value's clamped place, the bar's value at a height, a mark moved, the cell under a point), plot.js's bar.clip, onCell, onCellClick and setCellCursor, all additive, surfacegrid.js and surface3d.js taking the clip for the height, two paragraphs in the manual's Map section; a node test file for the pure parts and the module's guard. Self-check under PL36: as first built 145 OK, 0 FAIL, relaunch 158 OK, 2 FAIL; after the fix 158 OK, 0 FAIL, relaunch 148 OK, 0 FAIL, Josu's files unchanged; 246 node tests. Found: the reopened window's plot area is about 100 px tall for every view (the shell's saved window state, not the module's), so the strips took their share and left the difference map under the check's 40 px; the strips now collapse by container queries under 260 px tall or 520 px wide, and the check says so, so each strip is verified against the core's numbers in one run and not the other. Not versioned and not installed by the stream. On main after the merge the node suite passes, 246 tests (test-runner).

PL37 as filed fits; the stream's note under it says what the module will write ({ from, to, scale } per view, map_range only while its scale matches map_scale). One point beyond it, amended into PL37 here: a region's x carries the quantity it was drawn in (x_quantity, the set's x when absent), since the map may be shown in d or Q (Convert X…, PL10) while PL5's windows are ranges of the files' 2\(\theta\), and the core converts a region's bounds as plots::converted does for the CSV. Two shell notes for step 17, the stream's: the shared plot's wheel zooms x only, so a one-axis wheel zoom in T needs a plot.js addition; every redraw of the map resets the y zoom through setAxes, which a T–2\(\theta\) zoom with the clip following it will have to keep.

plots branch 27cde514 · plots/README.md (sixteenth step) · plots/REQUESTS.md PL13l, PL37 · app/ui/mapclip.js, app/ui/plot.js, app/ui/modules/plots/index.js, app/ui/tests/plots_clip.test.mjs · L405
L4052026-10-23decision

I08 green (R33): the interactive map goes to the Plots stream in three steps, the set file's part to the core (PL37), the merging session as the orchestrator

Josu, the same day, on the extended I08: start. The idea leaves the seed-place as R33 (the brief's last group, "Working with data as graphics") and is cut into work for two streams, this session keeping track of it:

  • Plots, step 16: I08's steps 1 and 3. Two draggable marks on the colour bar (the bounds of the range shown, the numbers typed too), everything beyond them saturated at the end colours, per view and on the scale shown, a double-click back to the core's range; the same clip on the Surface's height; a row cursor with that scan's pattern drawn below the map and a column cursor with that 2\(\theta\)'s intensity against T beside it, the two sections through the map that show a transition as a kink. The clip kept in memory until PL37, as the views' steps were before PL27.
  • Plots, step 17: I08's step 4, the zoom in the T–2\(\theta\) plane (a rectangle both axes, the wheel one, a double-click back), "range from the view", and the regions saved by name; after PL37.
  • Plots, step 18: the Map view's viewer factored out of the module under its guard, so that a page without the app can host it; then the core's drs plots show <set.drv>, the app launched onto the set when installed, a self-contained page beside the set and the browser otherwise or on --browser (I08's step 5).
  • The core, PL37 (filed now in plots/REQUESTS.md): the set file keeps map_range and difference_range and a [[regions]] table, each absent by default; plots::map_region gives a region's sub-block of the matrix and its range, so that the module reads nothing off the matrix itself (L139, PL14).
  • Publish, after: F3's map and difference map take the set's clip and a named region; the caption says the range and the saturation (I08's step 2). The asks of I08 answered as the assessment proposed, Josu to say otherwise: the clip is a view, never an operation; the range is kept per set file; the first host of the CLI's window is the browser page (the app launched onto the set costs one line, open -a, and comes in the same step); a saved region is a table of its own in the set file, not one of PL5's windows, since a window is a 2\(\theta\) interval whose quantities are computed per member and a region is a view of the map. Set aside: the core's own window (drs-view), kept as I08's step 6 for when the page is found wanting; and a clip as an operation on the data, refused because a figure must say "values above … saturated" over the measured matrix, never over a changed one. The Plots work runs in OpenCode in its worktree (L395, L397); the core's PL37 in a core session when the stream reaches step 17.
data/ideas.yml I08 · data/brief.yml R33 · plots/REQUESTS.md PL37 · L403, L404
L4042026-10-23work

I08 extended: the zoom in the T–2\(\theta\) plane with the clip following it, and the map opened from the CLI without the app (one viewer, three hosts)

Josu, the same day, on I08: the interactive map should also zoom in the temperature against 2\(\theta\) plane, to focus on the region wanted; and the CLI should open a window to work on it without the app being open, the browser perhaps, or a better option. Written into I08's summary, assessment and proposal: a rectangle zooms both axes, the wheel one, a "range from the view" switch clips to the rectangle's intensities, and a region (a 2\(\theta\) window times a range of the variable) is saved by name in the set file and taken by Publish's F3 map. For the window: drs plots show <set.drv> launches the installed app onto the set, or writes a self-contained HTML page beside the set (the core's matrix as data, the Map view's component as the viewer) and opens it in the browser; later, if wanted, a small window of the core's own. One component, factored out of the Plots module under its guard, hosted three ways, so that the standalone page is the app's map and not a second one.

esrf-datuak-irene (its L019) · data/ideas.yml I08
L4032026-10-23work

Two ideas from the ESRF ID22 series of sample 400_4: an interactive map clipped on its colour bar (I08), ticks outward (I09)

The esrf-datuak-irene project (23 October 2026) made its thirty figures of an in-situ series through the core's plot sets and drs-publish's F3 (projections and maps), the first case outside the examples. Reading the maps, Josu said two things for the app, written to the seed-place: I08, an interactive map whose shown range is selected on the colour-bar legend, so that the strong peaks are clipped and the weak details come up, for finding phase transitions (with a row and a column cursor proposed as the sections that show a transition); and I09, tick marks outward on maps, not into the plot, as the figures showed them. Two findings of that project for the streams, to be carried into their REQUESTS: the core panics with capacity overflow reading its own rebinned .xye output (drs data op … rebin --out x.xye; the .xys form reads fine), and drs-publish writes two figures whose ids differ only by case over each other on APFS without a word (its L013, L014).

esrf-datuak-irene ecosystem (L009–L014 there) · data/ideas.yml I08, I09
L4022026-10-21work

Plots step 15: the 3D surface, a Surface view of the core's map on the shared 3D canvas (merged)

Plots' step 15 (plots/DESIGN.md §8.1 item 6), merged into main with the core's 0.72.0–0.75.0 (L398–L401). A seventh view, Surface, draws the core's map (plots::map) in three dimensions on canvas3d.js: 2\(\theta\) across, each member's row at its own temperature in depth (unequal steps kept), the value up and in colour on the core's range and the map's ramps, each member's pattern drawn over it as a line; "of" chooses the map or the difference map; the pointer marks a cell and the readout gives the core's value; a member without a temperature is left out and named. Export ▸ Plot writes the view as an image, Copy Surface as Image and Send to Publish… work from it; three.js loads when the view is first shown and the GPU context is let go when another view is (L150). PL34 (a series sent to Publish) is used as Publish gives it.

The layout is screen work the module's guard (PL14) keeps out of app/ui/modules/plots/, so it is in two new shell files, app/ui/surfacegrid.js (pure, node-tested) and app/ui/surface3d.js, with plot.js's rampColour exported; PL13k lists them, and the merge brought exactly those (357 lines in app/ui outside the module). Every value drawn is the core's map; the shell files only place them.

Self-check under PL36: the first run 93 OK, 2 FAIL (the labels placed with Object.assign onto three.js's non-writable position, which threw and cut the check short); fixed, a drawing error now said rather than thrown, with a node test; then 140 OK, 0 FAIL, relaunch 145 OK, 0 FAIL, Josu's files unchanged; 240 node tests pass. Not versioned and not installed by the stream.

plots branch e3469dc5 · plots/REQUESTS.md PL13k, PL34 · app/ui/surfacegrid.js, app/ui/surface3d.js, app/ui/plot.js, app/ui/modules/plots/index.js, app/manual/plots.html, app/ui/tests/plots_surface.test.mjs · L150, L391
L4012026-10-21work

The cell's box for any phase, Cell::edges (Explore's E22; core 0.75.0)

Explore's E22 (explore/DESIGN.md §2.5: the cell drawn to scale beside its trace, for every phase, a Le Bail or profile phase included, where a cell drifting by \(10^{-3}\) shows a wrong zero or wavelength). The core drew the box only inside geometry::scene, which needs a structure, and the window may not place the corners itself (a matrix applied to fractions, L139).

Cell::edges() gives the twelve edges in Cartesian Å by crystal_to_orthonormal (x ∥ a, y in the ab plane), for any phase's cell; geometry::scene now takes its edges from it, so the scene's and the cell's are one calculation. Test explore_e22: a cubic cell's twelve edges all a long, those from the origin at right angles; a monoclinic cell (5.1, 6.2, 7.3 Å, β = 101.5°) gives back a, b, c and its three angles, each corner in three edges; \(\mathrm{Y_{2}O_{3}}\)'s scene's edges equal the cell's to the bit; \(\mathrm{MgAl_{2}O_{4}}\)'s Le Bail phase, without atoms, has its box.

explore/REQUESTS.md E22 · explore/DESIGN.md §2.5 · core/src/cell.rs (Cell::edges) · core/src/geometry.rs (scene) · core/tests/explore_e22.rs · L139
L4002026-10-21work

Per-format reading options and an order of preference among the readers, declared by the core and kept in the control file (the app's request 51; core 0.74.0)

The app's request 51 (app/DESIGN.md §11.2, APP_CONVENTIONS §4: "Data formats: the readers, the order in which they are tried, per-format options"). The app has no reader (L139), so its Settings ▸ Data Formats and Data ▸ Read As… are to be generated from what the core declares. New module formats::options:

  • Declared. Each Format carries its options, each with an id, a name, a kind (integer from a minimum, a choice of words, a number in a unit, a column), a default (empty where the reader decides from the file) and a line of help. Twenty readers take some: scan (a GSAS or ISIS file's bank, a Bruker file's range, an XRDML or RAS file's scan, a powder CIF's block, a table's series), wavelength for the formats that may state none (a wavelength the file states is kept), axis and tof_unit for the column formats, and the general table's layout (delimiter, decimal, header_lines, x_column, y_column, sigma_column), which its reader already took (TableOptions, L061) and now takes as options. drs data formats lists them.
  • Read with them. read_with(probe, format, &options, preference) checks the options against the reader's declarations (unknown, of the wrong kind or out of range: refused in words naming what the reader takes) and applies them; read is read_with with none, so nothing that read before reads otherwise.
  • A preference. identify_with(probe, preference): when no reader leads by MARGIN, the first of the tied readers in the preference is the format, and Identification::preferred says it was the preference that decided; a reader that leads is never overridden. On the example files: of 181, 165 are led by one reader and no preference moves them; the 16 that data_formats::recognition freezes as ambiguous (its 17 file–format pairs: xys with xye, and lamn_pol.dat with the general table) are read by the preferred reader.
  • Kept with the data. DataSource::reader_options, written reader_options = { … } under [pattern] reader, read back, checked against the reader at the value's place, applied when the control file is opened (data_scan_with) and by drs check; so a control file reads its data the same whatever the Settings say.
  • Checked (test data_format_options): the preference on all the example files; a GSAS file's bank 2 of 3 is the second bank of the whole reading (held to GSAS-II's by data_formats), a Bruker RAW file's range 2 its 3501 points (GSAS-II's 3501), a bank beyond the file's refused; a semicolon table with the decimal comma read with x in its second column, y in its fourth and no \(\sigma\) (\(\sigma\) = √y); every declaration's default of its kind; four wrong options refused; a control file's reader_options read, applied (the wavelength of an xys file that states none), written back the same, and three wrong ones refused.
app/REQUESTS.md 51 · app/DESIGN.md §11.2 · core/src/formats/options.rs · core/src/formats/mod.rs (Format::options, identify_with, read_with, read_path_with) · core/src/control/mod.rs (DataSource::reader_options, data_scan_with) · core/src/control/schema.rs · core/src/check.rs · core/src/main.rs (drs data formats, identify --prefer, read --option --prefer) · core/tests/data_format_options.rs · L061, L139, L372
L3992026-10-21work

A refinement compared with GSAS-II from the core: a GSASIIscriptable run prepared, its results read in the core's terms, checked on a pattern both programs fit (the app's request 50; core 0.73.0)

The app's request 50, the second half of Refine ▸ Compare with (app/DESIGN.md §11.1), with the Run and Comparison of L398. New submodule compare/gsas2.rs and a fixed script compare/gsas2.py:

  • Prepared. gsas2_prepare writes the data's weighted points as compare.xye, an instrument file, one CIF per phase, compare_input.json with every choice the core made (the refinement flags, the values the CIFs do not hold, the excluded regions, the cycles), the script and run.json. The script decides nothing: it builds the project with GSASIIscriptable, sets the flags, refines and writes compare_output.json (each varied value with its esd, the cells and atoms, the residuals, Ycal). The command is GSAS-II's Python with the GSAS-II folder on PYTHONPATH.
  • The model in GSAS-II's terms, each read from GSAS-II 5843's source, not assumed: its U, V, W are a Gaussian variance \(\sigma\)² in centideg\(^{2}\) (GetReflSigGamCW), the core's a FWHM\(^{2}\) in deg\(^{2}\), a factor \(10^{4}\)/(8 ln 2); its Lorentzian is X/cos \(\theta\) + Y tan \(\theta\), so its X is the core's Y and its Y the core's X, \(\times 100\); Zero adds to 2\(\theta\) in both (GetReflPos); its polarisation (1 − P)cos\(^{2}\)2\(\theta\) + P (Polarization) is the core's 1 + Cthm·cos\(^{2}\)2\(\theta\) for P = 1/(1 + Cthm); U_iso = B/8π²; the occupancy chemical (FullProf's Occ × M/m); Bragg–Brentano's shift and transparency are FullProf's SyCos and SySin at GSAS-II's 200 mm radius.
  • Two of GSAS-II's own defaults switched off, found by the first runs: the histogram's scale, refined by default and 100 % correlated with the phase fraction; and each phase's sample broadening (1 \(\mu\)m, 1000 microstrain), which made GSAS-II's X and Y go negative to compensate (the core's strain 0.0165 against GSAS-II's −0.039 before, 0.0175 after). This GSAS-II keeps no parmDict in its covariance; the final values are variables, aligned with varyList, and the script writes no NaN into its JSON.
  • Not carried, said in the manifest: the background (GSAS-II refines its own Chebyshev polynomial, one term per free background value of the core's, at most 12; not compared), Bérar–Baldinozzi's asymmetry, preferred orientation, μR, a resolution file's widths, and X and Y outside the Thompson–Cox–Hastings shape, where they are not widths (the pseudo-Voigt's η slope). The scales are refined but not compared (GSAS-II's phase fraction has its own normalisation). Refused: a phase without atoms, time of flight.
  • Checked (test gsas2_comparison, reference run manuals/gsas2/runs/y2o3_tch): a TCH pattern of \(\mathrm{Y_{2}O_{3}}\) simulated by the core with Poisson noise (X 0.03, Y 0.02, flat background, zero 0.01°), and a start moved off it with fourteen free parameters, refined by both: \(\chi^2\) 1.1313 against GSAS-II's 1.1333; U, V, W, X, Y, the zero, the coordinates and B's within GSAS-II's esd (the largest 0.80); the cell 4·\(10^{-5}\) Å apart, 1.00 of GSAS-II's esd, frozen at 1.01. The esd ratios are 1.00 within 1 % but the cell's (0.80) and the zero's (1.17). With GSAS2_PYTHON and GSAS2_PATH set the test runs GSAS-II on the prepared folder (19 s) and reads it: the same.
  • On FullProf's Y2O3 file itself (pseudo-Voigt, Bérar–Baldinozzi asymmetry, 62 background points) GSAS-II reaches \(\chi^2\) 1.74 against the core's 1.20 and none of six parameters agree: what GSAS-II cannot hold (η, the asymmetry, the background) moves everything else. The comparison says so through its manifest; it is the synthetic case, with only what both programs hold, that checks the translation.

Also: the core still builds without serde (--no-default-features): the GSAS-II half and drs compare-with need serde, the FullProf half's reading and comparing do not.

app/REQUESTS.md 50 · app/DESIGN.md §11.1 · core/src/compare/gsas2.rs · core/src/compare/gsas2.py · core/tests/gsas2_comparison.rs · manuals/gsas2/runs/README.md, y2o3_tch/ · ~/Applications/GSAS-II/GSAS-II/GSASII/GSASIIstrMath.py (GetReflSigGamCW, GetReflPos), GSASIIpwd.py (getWidthsCW, Polarization), GSASIIscriptable.py (get_Variable, residuals, HAPvalue), GSASIIstrMain.py (Rvals) · L398
L3982026-10-21work

A refinement compared with FullProf from the core: the run prepared, FullProf's outputs read, every parameter with both esd's (the app's request 49; core 0.72.0)

The app's request 49 (app/DESIGN.md §11.1, Josu's order L372): Refine ▸ Compare with ▸ FullProf, where the app only finds fp2k and runs a command, and the core does the rest. New module compare:

  • Prepared. compare::fullprof_prepare(&opened, dir, cycles) writes the run's folder: the .pcr by pcr_export (the export FullProf reproduces to its print in eleven runs, L069) with the cycles asked for and the output flags of run_fullprof.py (Prf = 3, Pcr = 1, Rpa = 0), the files it names, the data copied, the .pcr kept as written (compare.pcr.in, since FullProf writes its refined values over it) and run.json. The code is always compare, so no space or odd character reaches fp2k's arguments. The Run says the command (fp2k compare <data>, FULLPROF set to fp2k's folder) and carries a manifest: each value carried by a codeword, each parameter written fixed (by address, from a new Export::fixed) and each note of the export.
  • Read and compared. compare::fullprof_read(dir, &run, &opened) reads FullProf's .out (new fullprof_outputs::read_out: the banner, the parameters with their esd's after the last cycle, both statistics blocks, R_Bragg and R_F per phase, the cycles and the convergence), its .prf and its refined .pcr, imported into the default model as any .pcr is (L050). It refines a copy of the control file with the same free parameters and cycles, and returns one Comparison: the two programs and versions, \(\chi^2\), the R factors (also background-free) and Durbin–Watson's d, R_Bragg and R_F per phase, each with the difference and half a unit of FullProf's print; per parameter both values and esd's, \(\Delta\)/\(\sigma\), the esd ratio and the verdict by one rule, |\(\Delta\)| \(\le 1\) \(\sigma\) with \(\sigma\) FullProf's esd in the default model's terms; the parameters not compared with why; the notes; both calculated patterns and their difference at the data's points. FullProf's names are matched by output_names. FullProf's esd's are put into the default model's terms by the inverse of the export's translations (the X-ray scale's \(\times 2\), \(\sigma\)₁ + 3\(\sigma\)₂ for P1 + 3·P2, the axial pair's mean, a codeword's multiplier taken out, lsq::fullprof_esd). Two conventions measured in fullprof_refinements are left as FullProf has them and said: its esd's scaled by \(\chi^2\) over the points with Bragg contributions (a note with the factor), and half the esd of a Thompson–Cox–Hastings phase's cell and I_G (a note on each such row). compare::write and compare::read keep it as comparison.json in the folder; drs compare-with fullprof prepare|read is the command line.
  • Checked (test fullprof_comparison): on FullProf's stored Y2O3 run (y2o3_structure_conv.*) against the core's refinement of the same start, 72 of 72 parameters matched and within FullProf's esd (the largest, the zero shift, 0.998), \(\chi^2\) 1.1985 against 1.199, the esd ratios 1 within 2 % but the two background ends and the P1 + 3·P2 bound already frozen in fullprof_refinements. Frozen differences: R_F 1.626 against FullProf's 1.49 (not explained; R_Bragg 2.714 against 2.69), Rp 8.641 against 8.65 (the core refines its default model's reflection range and window, which fullprof_refinements sets to FullProf's). Egypt's .out read with its five phases. The folder prepared, its .pcr read back with the same 72 free parameters, run.json read back.
  • Run on this Mac (fp2k of February 2025 in ~/Applications/FullProf_dl/FullProf): the test with FULLPROF set runs FullProf on the prepared Y2O3 folder (4 s) and reads it: 72 of 72 agree. Also by hand on rutile-anatase's starting file (two phases, 8 cycles): 15 of 20 agree; three of the five are TCH cell parameters (\(\Delta\)/\(\sigma\) 1.6–2.2 with FullProf's halved esd, ratio 1.98, so within 1.1 of a doubled one), and FullProf stops unconverged after the file's 8 cycles while the core stops by its own test in 2, at \(\chi^2\) 1.3695 against 1.34. That is what a comparison is for; the reason is the peak window fullprof_refinements sets for this run.

Found and fixed while checking: a free parameter that moves several values was matched by its first value (the polynomial background's .1 showed as .0); it is matched by its own address.

Found by the suite in a fresh worktree, and fixed: background_terms::control_file_and_export failed there ("background profile y2o3_more2_two_1.bac not found"), and passed in the main folder only because other tests had left copies of y2o3.dat in target/. powder::find_file looked a file up by its name alone, in the folder, its parent and grandparent and their subfolders, never by the path the .pcr names: an exported .pcr in target/test-background-terms/ naming ../../../manuals/…/y2o3.dat found nothing. It now tries the name in the folder itself, then the path as named, then the wider search as before (a copy in the folder still wins over the named path, as paths_relative_across_the_root requires). Any fresh clone, a cloud session's included, would have failed the same way.

app/REQUESTS.md 49 · app/DESIGN.md §11.1 · core/src/compare.rs · core/src/formats/fullprof_outputs.rs (read_out) · core/src/control/pcr_export.rs (Export::fixed) · core/src/main.rs (drs compare-with) · core/tests/fullprof_comparison.rs · core/src/powder.rs (find_file) · core/tests/background_terms.rs · manuals/fullprof/runs/Y2O3, Egypt, rutile-anatase · L050, L052, L053, L069, L372
L3972026-10-21decision

Session practice in CLAUDE.md: one session per log entry, the session in its worktree, errands delegated, and the delegation written into the work orders

What the agents (L395) cost in discipline, so that the saving is habit and not goodwill: a new Session practice section in CLAUDE.md, between Every session and Rules, leaving the standards above it untouched.

  • One session per log entry. The log is the memory, never the chat: an entry is written and pushed, the session closes, the next task starts fresh and reads LOG.md. A chat kept for days carries files, test outputs and summaries step to step — slower, pricier, and memory the log already holds.
  • A session runs in the worktree it serves (OpenCode started from that folder), so it finds the branch and the agents.
  • Errands go to the agents. test-runner runs the suites and any script the task names — both files extended today for task-named scripts (a self-check under PL36 among them), reported as OK/FAIL counts with the first error verbatim — and explorer answers locations in at most 15 path:line pointers. Raw test output never reaches the model that decides; it reads the summary and does the thinking.
  • A session that writes a work order says so in the prompt: one line, errands to the agents, thinking and edits to the session itself. Standing rules live in CLAUDE.md; the line carries them to stream sessions that would not otherwise read the file. Josu's work orders stand as they are — the task is the task, and the delegation is the house rule, not his to repeat.
  • The streams split by cost (L395): Plots, tutorial and other UI work in OpenCode at no cost per token; core physics, design and merges with Claude as before.

Josu's question was whether the orchestrator can carry the delegation when it writes the prompts; it can, and that is the fourth bullet — the rule in the file for every session, the line in the prompt for the ones that arrive with work in hand.

Josu and the session of 21 October 2026 · CLAUDE.md (Session practice) · .claude/agents/test-runner.md, .claude/agents/explorer.md, .opencode/agents/test-runner.md, .opencode/agents/explorer.md · L395
L3962026-10-21work

build.py runs on the system python3 again: line 716 rewritten as concatenation, out of the f-string

python3 tools/build.py, the command the README gives, failed on this Mac with SyntaxError: f-string expression part cannot include a backslash on line 716, the references anchor written for L393: the backslash inside re.sub's pattern sat in an f-string expression, which Python 3.10 (the system python3 here) forbids and 3.12 allows. The Quarto pre-render hook called the same python3 and failed the same way.

Rewritten as string concatenation with the pattern unchanged (r"^https?://(www\\.)?", so the generated pages keep byte-identical text); python3 -m py_compile passes on 3.10, the build runs with the system interpreter, and Quarto renders. The session had run it once under uv run --python 3.12 before the fix, which is what exposed the line. Options set aside: moving the site's hook to uv or requiring a newer python — both make the README untrue; the compatibility fix keeps it.

the SyntaxError of 2026-10-21 · ecosystem/tools/build.py:716 · python3 -m py_compile on 3.10.9 · README.md:22
L3952026-10-21decision

Errands tiered to cheap models: a test-runner and an explorer agent for OpenCode and for Claude Code

Sessions on this repository were paying the main model for work that needs no main model: the raw output of cargo test and of the UI suite, and the question of where a thing is handled. Both are errands, and both were landing in full in the context that does the thinking. Four agent files, committed on main:

  • .opencode/agents/test-runner.md (deepseek-v4.1-flash-free) and .opencode/agents/explorer.md (glm-5.3-flash-free): the two errands inside OpenCode sessions, which cost nothing per token. Each answers in a fixed format — a verdict under 15 lines with the first error verbatim, or at most 15 path:line pointers — and neither ever edits, fixes or widens a tolerance; the session that asked does the thinking.
  • .claude/agents/test-runner.md and .claude/agents/explorer.md (model: haiku): the same two errands inside Claude Code sessions, so the raw test output is read by Haiku and the parent sees only the summary. Verified in this session: the explorer located the least-squares engine as core/src/lsq.rs:1916 and four pointers.

Both directories reach every stream worktree with the usual merge from main, and an OpenCode session must be started from the worktree it serves, since discovery walks up from the session's directory.

Tried and set aside: Claude Code delegating the errands to OpenCode's free models through the command line (opencode run --agent test-runner …). OpenCode refuses: "OpenCode's free tier can only be used from within OpenCode" — the free models are licensed only to sessions inside OpenCode. Kept for later: opencode auth login with a paid provider would let any shell call opencode run -m <provider>/<cheap model> at API prices. The streams are split accordingly: Plots, tutorial and other UI work can run in OpenCode at no cost, the core's physics stays with Claude.

OpenCode's refusal message and the session's tests (2026-10-21) · .opencode/agents/test-runner.md, .opencode/agents/explorer.md, .claude/agents/test-runner.md, .claude/agents/explorer.md · core/src/lsq.rs:1916 (the explorer's verified answer)
L3942026-10-21work

I07 registered: a project of its own for how a refinement finds its answer; its design, and the scaffold made locally

Josu, while L393 was being written: perhaps it is time to move the topic, I05, I06, the sessions they came out of and the audit, to a website of its own, to go deeper with all the references and what each claims, in an independent project, first for deepening and only after for implementing in the app, if it makes sense. Registered as I07 in the seed-place with its assessment and proposal; in short:

  • Form: the inverse-problems project of 14 October as the template (ecosystem/{data,tools,site}, the kit, the publish Action, sources/ private, code only in named folders when a proposal starts); a working name rietveld-minimisation, Josu's to change, since the subject is wider: the estimator and the minimiser, how a refinement finds its answer.
  • Matter: a library of every reference with its status, DOI and three fields (what it claims, how it was tested, what it means for an engine); a method catalogue (I06's A–D, the audit's additions, I05's three uses) with precedents, test and status per method; the three sessions exported turn by turn into a private folder and curated on the site after Josu's review; the bench's design and results; a bridge page to diff-ref-struct, one row per proposal with its test and numbers; the brief as questions Q01…, a roadmap, a log.
  • Prototypes: Python on the core's Python bindings (core 0.70.0, L381), so that every pattern is the core's; Rust in the core only for what wins on the bench. L139 kept on both sides.
  • Done today, locally, not published: the scaffold at ~/Desktop/2026/20261001/20261021_rietveld-minimisation with CLAUDE.md, README.md, the kit copied from the inverse project, data/sources.yml, data/methods.yml, data/brief.yml, data/nav.yml, data/log.yml (L001), the Action, tools/export_sessions.py and the three sessions in sessions/, the site rendered.
  • Josu decides: the name; the repositories (private, and the public -site with a deploy key) and the first push; whether the prototypes use the bindings; the first ten readings and their order.

I05 and I06 stay here as the record, each with a pointer; new notes on the topic go to the project once it is named; their status moves to migrated when something lands in the core.

Josu's message of 21 October 2026 · ecosystem/data/ideas.yml (I07) · ~/Desktop/2026/20261001/20261014_inverse_problems_configuration_space (CLAUDE.md, ecosystem/tools/build.py, .github/workflows/publish-site.yml, read 2026-10-21) · ~/Desktop/2026/20261001/20261021_rietveld-minimisation · L139, L286, L381, L393
L3932026-10-21finding

I05 and I06 audited: Bayesian optimisation belongs where a whole refinement is the black box; twelve precedents the first assessment missed; three more path methods; the references on the pages

Josu asked for an audit of I05 and I06 together, two days after their notes: can the minimisation be taken further and helped; does Bayesian optimisation, as the primer he sent presents it (codecut.ai), make sense; is there literature, is anyone working on this; and why were the references the session gave in its chat (seventeen sources on 19 October) nowhere on the site. The audit is I06's second workshop note; in short:

  • Bayesian optimisation (a Gaussian-process surrogate and an acquisition function) is the wrong tool for the minimisation proper: it wants an expensive, derivative-free objective in fewer than about twenty dimensions (Frazier 2018), and the core's problem is tens to hundreds of parameters at 5–40 ms with the exact Jacobian. It belongs where a whole refinement is the black box, and the field has put it there: BBO-Rietveld (Ozaki et al. 2020, Optuna's TPE over the recipe of a GSAS-II refinement, about 100 trials to beat an expert, \(\mathrm{Y_{2}O_{3}}\) as its case), CrystalMO-TuRBO and the pcVAE-assisted Bayesian optimisation (Agada, Biswas and others with Toby, 2025–26, over the structural parameters of a defective single-crystal pyrochlore against GSAS-II), and Spotlight (Biwer et al. 2025, Los Alamos, read in full: an ensemble of derivative-free optimisers and surrogates finding starting lattice parameters, 685,065 evaluations in 2 hours on 160 processors for GSAS-II's \(\mathrm{Al_{2}O_{3}}\) tutorial, and the rule that a lattice parameter off by less than 1 % can stop a refinement converging). For I06: Bayesian optimisation over the stage table and the schedules (a new step L6), with the bench's measures as objective and not Rwp; Spotlight's and BBO-Rietveld's public cases as the bench's external baselines; and the thesis sharpened into a measurable claim, that an open engine makes capture cost thousands of times less than a black box.
  • Precedents missed, by section: Antoniadis, Berruyer and Filhol 1990 (Poisson maximum likelihood) and David 2004 (J. Res. NIST: robust likelihood, impurity by positivity, cumulative \(\chi^2\)) for the likelihood; Gagin and Levin 2015 (systematic errors marginalised, GSAS_USE) for the misfit model; Coelho 2005 (TOPAS's bound-constrained conjugate gradient) and Toby and Von Dreele 2013 for the engines, Toby 2024 (the recipe problem solved by derivative tests) for the plan; David 2004 (J. Appl. Cryst. 37, 621) as the exact citation of the Pawley equivalence and Shankland 2004 for DASH's speed; Kerman et al. 2012 (average Rwp over random starts as a cost) and Spotlight's lattice grid as earlier forms of convergence measured from controlled starts, which the first note had said was unpublished; Segal et al. 2025 on the roughness of the pattern loss landscape; AiMat-DRX (2026), a differentiable Rietveld engine in JAX with calibrated uncertainty, the core's nearest neighbour; the agent papers (AutoXRD, AgentBuild) whose measured failures are what the normal matrix formalises; WPEM as a different estimator; HMC in scattering (Jiang et al. 2022); optimal transport in diffraction (LaueOT, PARA-X).
  • Three more path methods, each with its test: an optimal-transport misfit for the capture phase, borrowed from seismic inversion's cure for cycle skipping (Engquist and Froese 2014; in one dimension a least-squares residual between cumulative patterns, so the engine takes it as it is); the Poisson likelihood by reweighting; a parameter released by its derivative's significance, the stage trigger that replaces the hand-written plan.
  • I05: nothing found contradicts it; its references, until now only in the log's sources, are on its page.
  • The references on the pages: ideas.yml gains a references field (text, url, how it was checked: read, abstract, by search, believed), rendered by build.py at the foot of each idea's page; I06 lists 59, I05 18, the chat's seventeen among them. The rule: a claim's evidence is on the page that makes the claim.

Four asks added to I06's page. Where the steps are worked, in the core stream or in a project of its own, is the next entry.

ecosystem/data/ideas.yml (I05, I06: workshop note of 21 October 2026, references) · ecosystem/tools/build.py (write_ideas) · the session of 19 October 2026 (07ef4dd6) and its seventeen sources · web searches and readings of 21 October 2026: Biwer et al. 2025 Sci. Rep. 15, 8358 (PMC11897340, read in full); Ozaki et al. 2020 npj Comput. Mater. 6, 75 (hub.optuna.org/samplers/bbo_rietveld; github.com/quantumbeam/BBO-Rietveld); arXiv 2609.20592, 2601.00855, 2512.04036, 2602.16372, 2609.00070, 2606.12834, 2609.13619, 2404.06478, 2609.15894; Zenodo 10.5281/zenodo.22679432; Gagin and Levin 2015 J. Appl. Cryst. 48, 1201 (nist.gov; github.com/AntonGagin/GSAS_USE); Antoniadis et al. 1990 Acta Cryst. A46, 692; David 2004 J. Appl. Cryst. 37, 621 (10.1107/S0021889804013184) and J. Res. NIST 109, 107; Shankland 2004 J. Res. NIST 109, 143; Toby 2024 J. Appl. Cryst. 57 (10.1107/S1600576723011032); Coelho 2005 J. Appl. Cryst. 38, 455; Jiang et al. 2022 J. Synchrotron Rad. 29; Frazier 2018 arXiv 1807.02811; codecut.ai/bayesian-optimization-hyperameter-search · L286, L298, L301, L358, L363
L3922026-10-20work

App 0.52.1 on core 0.71.0 installed, no FAIL: version 2 complete on Josu's Mac, with Explore's and Plots' restarted steps

The merging session's gated run (main f17e3961, niced): core 393, Publish 62, app Rust 99 and UI 234 tests pass; built; three self-checks on a copy with its own identifier (PL36), its preferences domain emptied first, each to its quit: \(\mathrm{Y_{2}O_{3}}\) 135 OK 0 FAIL, the relaunch 174 OK 0 FAIL (P46: 2 windows and 3 tabs reopened), \(\mathrm{Tb_{2}Pd_{2}Sn}\) 151 OK 0 FAIL; Josu's files and preferences unchanged. Installed; 0.51.0 to the Trash. Josu's app now holds version 2 complete (the core's, the app's and Publish's sides, TLS included), Explore's Cell, Bonds and Microstructure views with the run's marks (L386, L390), Plots' windows on the plot and dimming (L385, L389), and Settings ▸ External Programs (L382). The run before it (L384) was stopped at the core's tests, its FAIL being the check's (L389).

scratchpad self0521a/r/b · L382, L384–L390
L3912026-10-20work

Publish stream: drs-publish 0.34.0, a series of refinements sent by Plots (PL34), F5 or F4, each temperature's source in the caption

Publish takes Plots' PL34 (L383): a series of refinements sent by Plots becomes a set with F5 or F4, each point's temperature kept in the set with its source (a header, a temperatures file) and said in the caption.

branch publish 4fa297a4 · plots/REQUESTS.md PL34 · L383, L389
L3902026-10-20work

Explore stream, fifteenth step: the run's record as marks (E15), the Cell view's strain reference and reflection ticks, Compare's cells side by side; E22 filed

(1) The run's record as marks (DESIGN.md §2.6; E15a–c, core 0.40.0): explore_marks gives the record's pauses, its stop as data and each cycle's steering notes; the traces, the time slider and the sidebar's cycles mark them, a jump ringed on its parameter's trace. (2) The Cell view: the strain against the start's cell or the end's; under the plots, Yobs with the phase's reflection ticks at the start and at the cycle shown, so a drifting cell moves its ticks. (3) Compare: the two runs' cells side by side, step by step. Filed: E22, the cell's box edges for any phase (Cell::edges), so that a Le Bail phase's box is drawn to scale. Tests: modules::explore 7, node 233. Self-check under PL36: \(\mathrm{Y_{2}O_{3}}\) 130 OK 0 FAIL (208 ticks at the start and the end; compare 72 of 72), its relaunch 150 OK 0 FAIL.

branch explore 180dce56 · app/ui/modules/explore/ · explore/REQUESTS.md E15, E22
L3892026-10-20work

Plots stream, fourteenth step: PL32 used; L384's tb_t03.dat FAIL was the check's; dimming by the pointer

PL32 (core 0.71.0, L383) used: case E's 453.14 K from t7.dat's header. The merging session's install-run FAIL (L384: tb_t03.dat 6 K from the title where 6.5 K from Tb.tem was expected) was the check's: that copy of the folder has no Tb.tem; fixed. Dimming by the pointer (hover, ⌥-click to solo). Self-check 130 OK 2 FAIL (the inspector's tooltips; Publish's first LuaLaTeX run in a fresh HOME), both fixed after; relaunch 132 OK 0 FAIL.

branch plots 2524117a · plots/REQUESTS.md PL32 · L383, L384
L3882026-10-20work

App 0.52.1: a project's first data file never an export of the app's; the self-check on a project with exports

From the Plots stream's relaunch: a project's first data file is never an export of the app's (a CSV of 20 scans in d had been opened first); the self-check's Start check notes the core's refusal instead of waiting; every window's self-check error noted with its message.

branch app 4d2c718d · L385
L3872026-10-20work

Publish stream: drs-publish 0.33.0, TLS read (P47 met), F10 against ρ (P59), relative paths (P61); Publish's version 2 items complete

P47's TLS (core 0.67.0, L367) read by T16 on the core's urea, hmt and pdd; F10 against ρ (P59) and relative paths (P61) from core 0.68.0 (L374); P47, P59 and P61 met: Publish's items for version 2 are complete. One self-check under PL36, no FAIL; Josu's app files unchanged.

branch publish 33debad1 · publish/REQUESTS.md P47, P59, P61 · L367, L374
L3862026-10-20work

Explore stream, fourteenth step (restarted, L372): the Cell, Bonds and Microstructure views, the dragged point and the \(\chi^2\) map on core 0.69.0's E18–E21

E18a: the Cell view's a, b, c, α, β, \(\gamma\), V with su's are the state's cell_su (\(\mathrm{Y_{2}O_{3}}\)'s V su 3a\(^{2}\)\(\sigma\)(a)). E18b: each cation–anion distance drawn against its plausible values at the cation's coordination (dashed, the bond-valence single-bond distance; dotted, the sum of the ionic radii); E18c: each atom's valence sum against |q| within its plausible band; the window states no verdict. E19: the Microstructure view's Williamson–Hall plot, β*_G and β*_L against d* (Å⁻¹). E20a: the dragged point (map and pair view) is lsq::point's state, a node and a point agreeing to the bit. E21: the map draws the conditional cut's contours at the 1\(\sigma\) and 2\(\sigma\) ellipses' levels, labelled. Tests: modules::explore 7 pass (Y–O at CN 6: 2.2755 Å by bond valence, 2.33 Å by ionic radii), node 233. Self-check under PL36 (Josu's home from dscl, NULL_GLOB, his files unchanged): \(\mathrm{Y_{2}O_{3}}\) 131 OK 0 FAIL (the map 121 of 121, dof 13019, contours drawn; compare 72 of 72), its relaunch 162 OK 0 FAIL. E18–E21 met.

branch explore 054122c2 · app/src-tauri/src/modules/explore.rs · app/ui/modules/explore/ · explore/REQUESTS.md E18–E21 · L376
L3852026-10-20work

Plots stream, thirteenth step: windows drawn and edited on the plot (DESIGN.md §4)

Draw Window makes the next drag across the overlay or the stack a window (w1, w2 …); a band's edges are dragged to move it; a click on a band or its row chooses its window, which Rename Window… names and Remove Window removes; the series view follows each change, the core taking the windows again; through the shared plot's editable bands, no shell line touched. Self-check under PL36: 132 OK 0 FAIL; its relaunch 159 OK 0 FAIL, with one "[data] error" line from the shell's own Data check on Plots' exported CSV, told to the app stream; m1 dragged, its integral \(21928.72 \to 22125.88\). 11 Plots Rust and 233 node tests pass.

branch plots 0ace0bad · plots/DESIGN.md §4
L3842026-10-20work

App 0.52.0 on core 0.70.0 not installed: one FAIL, Plots' check of tb_t03.dat's temperature (6 K from the title where 6.5 K from Tb.tem was expected)

The merging session's gated run (main a6ad56ca, niced): core 389, Publish 61, app Rust 99 and UI 233 tests pass; built; three self-checks on a copy with its own identifier (PL36), each to its quit; Josu's files and preferences unchanged; P46 reopened 2 windows and 3 tabs. \(\mathrm{Y_{2}O_{3}}\) 134 OK 0 FAIL, the relaunch 166 OK 0 FAIL, \(\mathrm{Tb_{2}Pd_{2}Sn}\) 134 OK 1 FAIL: "plots: Plots/selfcheck.drv read back by the core … tb_t03.dat 6 K from the title's "T.=.6.K" (PL23)", where Plots' step 12 check (L378) reads 6.5 K from Tb.tem. Core 0.71.0 (PL32, L383) changes how a WinPLOTR header's temperature is read, and arrived during the run; the install is run again on main with it, Plots' step 13 and Explore's step 14.

scratchpad self052a/r/b · L378, L383
L3832026-10-20work

Core 0.71.0: Plots' PL32, a WinPLOTR header's temperature read with its unit (case E: 179.99 °C = 453.14 K); PL34 is Publish's

Josu's order (L372), the Plots requests left before the bindings.

  • PL32: a header that WinPLOTR wrote ("File created by WinPLOTR", "File saved from WinPLOTR-2006") is searched, in its comment lines before the first line of numbers, for ! TEMPERATURE <value> [unit] (formats::winplotr_temperature). The registry applies it to any reader of the file's columns (xys, xye, table), because t7.dat is ambiguous between the three and the reader is the .pcr's or the user's.
  • The unit is the line's when one follows the number (K, C, °C, Celsius). Otherwise it comes from the header's source: a file named ! INITIAL .UXD FILE: is Bruker's, read as °C. This is Josu's word for case E (L243; Bruker's BRML states °C, bruker.rs).
  • Any other value is not read, never as kelvin by default (PL32). Its reason is kept in the scan's "temperature not read".
  • A read value is in K in Info.temperature, its line in "temperature as written". A plot set's member takes it with its source "the file's header", and a sequential refinement as its external parameter.
  • Case E's t7.dat gives 453.14 K through the three readers, the plot set and sequential::read of e.pcr.
  • Also fixed: the xys reader took "File", the first word of WinPLOTR's first line, as the instrument.
  • Command-line path: drs data read t7.dat --as xys prints temperature: 453.14 and the line as written.
  • Test winplotr_header (4 cases):
  • case E through the three readers;
  • the units, known and unknown, on synthetic headers;
  • \(\mathrm{Sr_{2}ScSbO_{6}}\)'s WinPLOTR header, which has no temperature line, gives none;
  • the plot set and the series;
  • the command line.
  • PL34 (a series of refinements sent to Publish) is under "To the Publish stream" and needs module::new_from_series in drs-publish and the app's hand-over. The core already gives everything it needs (the series CSV and CIF, P59, L317), so the core builds nothing for it.
core/src/formats/mod.rs (winplotr_temperature) · core/src/formats/fullprof.rs · core/tests/winplotr_header.rs · plots/REQUESTS.md PL32–PL34 · L243, L372
L3822026-10-20work

App 0.52.0: APP_CONVENTIONS §4's two large Mac items designed (comparison runs, per-format data options); External Programs checked by running them; Refine's Comparison view, the frame

Josu's order (L372). app/DESIGN.md §11: comparison runs against FullProf and GSAS-II (the core writes their input, reads their output and compares; the app runs the program and shows the core's Comparison), and per-format data options with an order of preference among readers. Requests 49 (FullProf), 50 (GSAS-II) and 51 (format options, preference) to the core. Built, needing no core: Settings ▸ External Programs (FullProf's fp2k, GSAS-II's installation and its Python), whose Check runs each as a comparison will, in its own scratch folder with a time limit, and Find, which looks in the Applications folders (FullProf 8.20 and GSAS-II 5843 answered in every run); Data Formats says what waits for request 51; Refine's Comparison view as a frame with no number, Compare with FullProf or GSAS-II waiting for 49, 50. Fixed: two Checks at once emptied each other's scratch folder. Self-check under PL36: fresh HOME 188 OK 0 FAIL, its relaunch 200/0, \(\mathrm{Tb_{2}Pd_{2}Sn}\) 124/0, its relaunch 142/1 (Plots' .drv, fixed on its branch, L378, not yet merged into the app's); the merging session's bare \(\mathrm{Y_{2}O_{3}}\) 162/0 and its relaunch 181/0. Rust 99, node 233.

branch app fe1642cf · app/DESIGN.md §11 · app/REQUESTS.md 49–51 · L372
L3812026-10-20work

Core 0.70.0: the Python bindings (P53), a wheel built and tested against the command line

The first of Josu's next steps (L372): P53's scripting interface, as L380 shapes it.

  • The crate. core/python: crate drs-python, package diff-ref-struct, module drs.
  • drs.run(args, check=True) returns Run(code, stdout, stderr) and raises DrsError on failure; drs.binary() gives the binary's path.
  • drs.open(path, base=None) gives a Project; drs.read_data(path, format=None); drs.__version__.
  • Project has fields, get, set, free, calculate, refine, point, state, check, to_pcr, results, save and control_text.
  • The wheel. core/python/build_wheel.sh builds the core's drs binary, puts it in the package, and builds the wheel with maturin.
  • Here: diff_ref_struct-0.70.0-cp39-abi3-manylinux_2_35_x86_64.whl (the native module 6.0 MB, the binary 9.0 MB).
  • On the Mac the same script builds the macOS wheel. Not built here.
  • Tests. core/python/tests/test_drs.py, 9 unittest cases, run against the wheel installed in a fresh virtual environment from outside the source tree: all pass on Python 3.13.
  • drs.run gives the binary's output to the byte (cell, sg, calc); the help names at least 40 commands; a failing command raises.
  • The package's version is drs --version's.
  • calculate gives drs calc's Ycal and background to the printed decimals and its \(\chi^2\) (over N, as drs calc counts no parameter); each phase's curve plus the background is Ycal.
  • refine(cycles=3) gives drs refine --cycles 3's values and esd's to the printed decimals, and its cycles (it converges in 2). The state carries a's esd as its su. A point leaves the model as it is. The results file names the free parameters.
  • Values by address: setting a cubic cell's a moves b and c; setting b is refused ("crystal system").
  • The control file saved elsewhere and opened with base gives the same text and pattern; the .pcr export; the checks show no error; read_data.
  • Python 3.9–3.12 are covered by the stable ABI but were not run here.
core/python/ · core/python/tests/test_drs.py · core/python/README.md · core/README.md · L021, L372, L380
L3802026-10-20decision

The Python bindings' shape: the command line in the wheel for drs.run, a native Project for scripting, JSON between them

Taken building P53's scripting interface (Q1, L021; Josu's order L372), within what L021 decided: PyO3 bindings over the Rust core, no physics in Python.

  • The same functions as the command line. drs.run(args) runs the drs binary built from the same core, which the wheel carries in drs/bin. Every command and option is the command line's, to the byte.
  • Set aside: calling the command line's code inside the Python process. It sets a SIGINT handler during a refinement, which would take Ctrl-C from Python for good. It keeps --base in a once-only static, so a second call could not change it. Its output goes to the process's standard output, which notebooks do not show.
  • Scripting in process. A native Project (drs._drs) holds a control file or an imported .pcr with its data. Its methods call the core directly: values by address, calculate, refine, point, state, check, export, results.
  • Between Rust and Python, JSON text. The native methods return JSON, which drs/__init__.py turns into dicts and lists. PyO3's surface stays small (strings, floats and tuples), no numpy is needed, and numpy.asarray takes the lists.
  • Set aside: numpy arrays across the boundary. They would add a dependency for no gain at these sizes (13 091 points cost milliseconds to pass).
  • The ABI and the names.
  • PyO3 0.23 with abi3-py39: one wheel for every Python from 3.9. PyO3 0.30 is out, but 0.23 is the release whose API this session knows.
  • Package diff-ref-struct, import name drs, version the core's.
  • The crate lives in core/python (the core stream's folder) and depends on the core by path. The core keeps no dependency on PyO3.
core/python/ (Cargo.toml, pyproject.toml, src/lib.rs, python/drs/__init__.py) · core/src/main.rs (the SIGINT handler and --base) · L021, L372 · proposal P53
L3792026-10-20work

App 0.51.1: request 48 met by core 0.68.0, the app's .bac workaround dropped; three self-check checks made robust

A save calls the core's background_file_to_write: a background file is written only when its points changed, so FullProf's own .bac is left as it is, byte for byte, and the core writes a file's b1–b4 points as held; 0.50.1's background_file_at removed; request 48 met. Self-check fixes: §1 judges the tab group only after a launch that reopened the last session (the Plots stream's relaunch, given a file, failed on it, L378); the shortcuts checked by the main window alone; §9's −0.5 typed once the inspector's field has settled. Self-check under PL36: fresh HOME 181 OK 0 FAIL, its relaunch 205/0, \(\mathrm{Tb_{2}Pd_{2}Sn}\) 120/0, its relaunch 135/1 (Plots' .drv, fixed on its branch, L378); the merging session's bare \(\mathrm{Y_{2}O_{3}}\) 156/0 and its relaunch 160/0; the .bac byte for byte the repository's after both. Rust 97, node 232.

branch app 7247b3ef · app/REQUESTS.md 48 · L370, L374, L378
L3782026-10-20work

Plots stream, twelfth step (restarted, L372): the relaunch's set-file FAIL was the check's; the launcher's home (L297); PL35

On a relaunch the app first reopens the last quit's tabs (P46); the active one was Plots/selfcheck.drv, and the check took it for the startup buffer's, saved into that very file and "opened it again" onto the same tab, whose y scale the series view had set to log. The check now takes the startup buffer's tab by its file, reads the saved file back through the core (plots_open_set) and needs a tab of its own for the opened set. A gap in Plots the case showed: saving over a set file another tab shows left that tab stale; it is now read again from the file. plots/tools/selfcheck.sh: Josu's home from dscl, NULL_GLOB, a FAIL when a snapshot holds no file, and a relaunch with the same HOME (L297). PL35 in plot.js: the measured right margin counts the point labels past the frame. PL23 and PL24 (core 0.36.0) used: tb_t03.dat's 6.5 K from Tb.tem read back from the set file; a .drv claimed by its content. Self-check 132 OK 0 FAIL; its relaunch 133 OK 1 FAIL, the shell's §1.

branch plots a56ab324 · plots/tools/selfcheck.sh · plots/REQUESTS.md PL23, PL24, PL35 · L297, L366, L370, L375
L3772026-10-20work

App 0.51.0 on core 0.68.0 installed, no FAIL: version 2 on Josu's Mac but for Publish's T16

The merging session's gated run (main aad43cc8, niced): core 384, Publish 61, app Rust 97 and UI 229 tests pass; built; three self-checks on a copy with its own identifier (PL36), its preferences domain emptied first, each to its quit: \(\mathrm{Y_{2}O_{3}}\) 133 OK 0 FAIL, the relaunch 161 OK 0 FAIL (P46: 2 windows and 3 tabs reopened; L373's three FAILs gone), \(\mathrm{Tb_{2}Pd_{2}Sn}\) 144 OK 0 FAIL; Josu's files and preferences unchanged. Installed; 0.47.0 to the Trash. Josu's app now holds every version-2 feature in Refine: guided refinement, Pawley, sequential and parametric with the series' table, joint refinement, time of flight, further background terms, texture along several axes, rigid groups and TLS. Left of version 2: Publish's T16 (TLS).

scratchpad self051a/r/b · L371, L372, L373, L375
L3762026-10-20work

Core 0.69.0: Explore's E18–E21 (the cell's su's and plausible ranges in a state, the Williamson–Hall axes, a state at given values, the \(\chi^2\) map's contours)

The second of Josu's three steps (L372): what the Explore stream needs before it restarts.

  • E18a. PhaseState::cell_su: a, b, c, α, β, \(\gamma\) and V with su's, propagated from the covariance given (0 without one) through geometry::Propagator, so that the cell's ties count.
  • \(\mathrm{Y_{2}O_{3}}\) at its end: a's su is the refined parameter's esd (3.7·\(10^{-5}\) Å, to \(10^{-6}\), the propagator's central differences). b and c are tied to it. V's su is 3a\(^{2}\)\(\sigma\)(a), 1.24·\(10^{-2}\) ų. The angles are 90° with none.
  • cif_write's volume_su (independent parameters) would give √3 a\(^{2}\)\(\sigma\)(a) there.
  • E18b. Contact::plausible: a cation–anion contact's plausible distances at the cation's coordination at that state, as state::plausible gives them (bond-valence single-bond distances, the sum of the ionic radii).
  • The coordination is the cation's in its valence sum, or else its contacts to atoms of the other sign. Other contacts have none.
  • \(\mathrm{Y_{2}O_{3}}\) (Y\(^{3}\)⁺, O\(^{2-}\)): 16 Y–O contacts at coordination 6 (2.2755 Å by bond valence, 2.33 Å by ionic radii); Y–Y and O–O have none.
  • E18c. ValenceSum::expected, |q| (Bond_Str compares the sum with the formal valence's magnitude), and ValenceSum::plausible, |q|(1 ∓ r).
  • r is bonds::VALENCE_RANGE (0.10, Josu's answer 5 to Explore's DESIGN.md §8) unless the phase's control-file key valence_sum_range (bonds::VALENCE_RANGE_KEY, Phase::valence_sum_range) gives another.
  • \(\mathrm{Y_{2}O_{3}}\)'s O: sum +1.965 against 2 within 1.8–2.2; with 0.05, within 1.9–2.1, through the control file and back.
  • E19. Breadths::hkl, d_star (Å⁻¹) and beta_g_star, beta_l_star, beta_tch_star (the sample's β*, in Å⁻¹): the Williamson–Hall plot's axes.
  • NdNi-HTO's first phase against FullProf's NdNi_HTO_calc1.mic, 204 reflections: s = 1/d, betaG and betaL within the print and 2·\(10^{-5}\), the worst at 0.90 of that (test fullprof_microstructure::williamson_hall_axes).
  • drs mic prints d* from it.
  • E20a. lsq::point(model, data, set, &[(name, value)]) returns a PointState: the model, Ycalc, Yobs − Ycal and the statistics.
  • Each parameter is set as a step sets it: its value moved, every field by its coefficient times the shift, then the links enforced.
  • chi2_grid's nodes are computed by it, so a node and a point agree to the bit.
  • Tested on \(\mathrm{Y_{2}O_{3}}\):
  • a parameter moving two fields by 1 and ½;
  • a variable moving one field by 2;
  • a value linked to another by ½;
  • a name that is no free parameter, refused.
  • drs state --at name=value,….
  • E21. Grid::contours(levels) returns, per level, a Contour { level, x, y }, its pieces separated by NaN as the shared plot draws them.
  • Marching squares over the cells whose four nodes are computed, a saddle split by the centre's mean. A node not computed leaves a break.
  • Grid::contours_at_sigma(&[1, 2]) gives the levels of the kσ ellipses. The esd's are scaled by \(\chi^2\) (S/(N − P + C)), so the kσ contour is \(\chi^2\)_min(1 + k\(^{2}\)/(N − P + C)) in the grid's \(\chi^2\), not \(\chi^2\)_min + k\(^{2}\).
  • Grid::dof records N − P + C from the nodes' statistics.
  • Test: a quadratic \(\chi^2\) on \(41 \times 41\) whose 1\(\sigma\) and 2\(\sigma\) contours are known ellipses: every point within linear interpolation's bound h\(^{2}\)Q''/8 (8.79·\(10^{-3}\)), each a closed line, broken where a node is missing. \(\mathrm{Y_{2}O_{3}}\)'s own 1\(\sigma\) contour on \(\pm\) 3\(\sigma\) is one closed line.
  • drs refine --map prints the 1\(\sigma\) and 2\(\sigma\) contours. Test file explore_e18_e21.
core/src/state.rs · core/src/bonds.rs · core/src/microstructure.rs · core/src/lsq.rs · core/src/powder.rs · core/src/control/schema.rs · core/src/main.rs (drs state, drs mic, drs refine --map) · core/tests/explore_e18_e21.rs · core/tests/fullprof_microstructure.rs · explore/REQUESTS.md E18–E21 · explore/DESIGN.md §8 (answer 5) · L230, L372
L3752026-10-20work

App 0.51.0: TLS in Refine (P41), version 2's last app side; L373's relaunch FAILs traced to a question left open

L373's smart zoom, 12 controls without a tooltip and a changed file not read again, believed one cause: the "changed on disk" question left open. The self-check's §11 typed in the Editor while Refine's auto-save of the same file was due; the Editor rightly asked, nobody answered, and the modal blocked the zoom, added its two buttons without a tooltip and held the disk watch. Not reproduced by the app stream (its runs of the merging session's two passed before the fix too); inferred from the code and notes. Fixes: §11 waits for each view's save; every question's button has a tooltip; the runner names an open question as a FAIL and answers it; the rebase never puts back an older stat; the disk watch's changes are noted with their window. TLS (core 0.67.0, L367): a TLS group's T, L and S as 21 refinable rows named by the core, with the centre they refer to; each atom's β marked by its group's TLS; 0.48.0's rigid item fixed (internal coordinates running into the TLS values, the core's mark overwritten, the sections' titles). Self-check under PL36: fresh HOME 181 OK 0 FAIL, its relaunch 197/0, \(\mathrm{Tb_{2}Pd_{2}Sn}\) 120/0, its relaunch 140/1 (Plots' .drv); the merging session's bare \(\mathrm{Y_{2}O_{3}}\) 156/0 and its relaunch 165/0. Rust 97, node 229.

branch app 505da4d3 · L367, L373
L3742026-10-20work

Core 0.68.0: the app's request 48 (a .bac with b1–b4 written as held, left alone when unchanged), Publish's P59 (D against ρ) and P61 (relative paths)

The first of Josu's three steps (L372): the core's small requests that close version 2.

  • Request 48. A background file with b1–b4 is written as its points are held.
  • Control::background_file_text writes the points at their own spacing whatever the data's points, when the background holds a file's b1–b4 (BackgroundTerms::file). Before, it wrote the background with b1–b4 at the data's points, which the next reading moved again: y2o3_bac_b3b4's first value 19.1834 became 19.0873.
  • Control::background_file_to_write(dir, x) gives None when the file in the control file's folder already holds those points, so a save that changed nothing leaves FullProf's own .bac as it is. drs background store uses it ("unchanged, left as it is").
  • Test background_terms::bac_with_b1_b4_written_as_held on y2o3_bac_b1b2 and y2o3_bac_b3b4:
  • the points come back as read, given the data's points or not;
  • FullProf's file is not written;
  • a copy saved and read again keeps its points and its pattern, and a second save writes nothing;
  • an edited point is written.
  • P59. March–Dollase's density against the angle: texture::march_dollase_at(r, ρ°) and texture::march_dollase_curve(r, n), which gives (ρ, D) at ρ = 0, 90/n, …, 90°.
  • Unit test the_curve_against_the_angle: D(0) = r\(^{-3}\) and D(90°) = r^{3/2}, and ∫ D sin ρ dρ = 1 over the curve within \(10^{-4}\).
  • drs texture prints its D at every 15° from it.
  • P61. control::relative returned a full path when two paths shared only "/": Publish's run in /private/tmp against a project under /Users. Now only paths without a common root stay absolute (another drive on Windows).
  • The control file's data and file, and with them _drs_bkg_file (the control file's own name for the background file), are relative to the control file, as relocate writes them.
  • Test background_terms::paths_relative_across_the_root: a project in the system's temporary folder, the run in the repository.
  • The app's finding L373 (the relaunch saw a file changed on disk, near 0.50.1's way of writing a background file) is believed to come from that rewrite on every save, which background_file_to_write avoids; not checked. The empty .ort files need nothing of the core: the app solved them (L370).
core/src/control/mod.rs · core/src/texture.rs · core/src/main.rs (drs background store, drs texture) · core/tests/background_terms.rs · app/REQUESTS.md 48 · publish/REQUESTS.md P59, P61 · L364, L370, L372
L3732026-10-20finding

App 0.50.1 on core 0.67.0 not installed: three FAILs on the relaunch run (smart zoom, a changed file's banner, the disk watch)

The merging session's gated run (main 7b42089d, niced): core 381, Publish 61, app Rust 96 and UI 227 tests pass; built; three self-checks on a copy with its own identifier (PL36), each to its quit; Josu's files and preferences unchanged; P46 reopened 2 windows and 3 tabs. \(\mathrm{Y_{2}O_{3}}\) 132 OK 0 FAIL, \(\mathrm{Tb_{2}Pd_{2}Sn}\) 132 OK 0 FAIL; the relaunch 155 OK 3 FAIL:

  • §6 smart zoom: "Refine's plot from 67.76047–74.35553 back to 67.76047–74.35553, its whole range 15–146.9012";
  • accessibility: 12 controls without a tooltip, among them Data's and Refine's "Read the File on Disk" and "Keep My Changes", the buttons of the banner shown when a file changed on disk;
  • disk: "y2o3_structure.pcr changed outside the app with no unsaved changes: read again no". All three on the relaunch, where the session's files are read again: the second and third point to a file seen as changed on disk at the relaunch, near 0.50.1's change to how a background file is written (L370). Not installed; Josu keeps 0.47.0. Passed to the app stream before TLS.
scratchpad self0501a/r/b · L340, L370
L3722026-10-20decision

Josu's order after the milestone (L371): close version 2, restart Explore and Plots, the tutorial last; then the Python bindings and the app's two large Mac items

From the merging session's proposal (L371), Josu chose on 20 October 2026: 1. Close version 2 as used: TLS in the app and in Publish (T16); the core's small requests (the app's 48, Publish's P59 and P61); one final tested install. 2. Restart the paused streams: Explore once the core has done E18–E21; Plots. The tutorial waits until everything else is finished. 3. Then the Python bindings (P53's scripting interface, Q1/L021), and the app's two large Mac items (APP_CONVENTIONS §4): per-format data options in Settings, and comparison runs against FullProf and GSAS-II. Set aside for now: the proposal's nine later features (P07, P08, P14, P19, P20, P26, P31, P34, P56) and the I06 stream of innovations (L363).

Josu's answer, 20 October 2026 · L021, L363, L371 · APP_CONVENTIONS.md §4
L3712026-10-20work

Milestone, 20 October 2026: version 2 complete in the core; the project's state and where to go; git tag milestone-2026-10-20

A mark asked by Josu. Version 1: 39 of 39 features (L264, L278). Version 2: all eleven in the core (P25, P45, P55, P36, P54, P59, P06, P05, P23, P30, P41; core 0.67.0, L368), each against FullProf's own runs, the differences it could not explain counted, frozen and accepted (L245, L258, L343, L357, L368). In the app and Publish ten of the eleven; P41's TLS not yet shown (Refine, T16). Releases at the mark: core 0.67.0, app 0.50.1 (merged; 0.47.0 installed, 0.50.1's tested install running), drs-publish 0.32.0. Explore paused after step 13 (E18–E21), Plots after step 11 (PL32, PL34), the tutorial at 15 of 27 scenes. The later list: nine features (P07, P19 partial; P08, P14, P20, P26, P31, P34, P56). Where to go, proposed: (1) close version 2 as used: TLS in the app and Publish, the core's requests 48, P59, P61, one tested install; (2) resume Explore (E18–E21 first), Plots (PL32, PL34, its self-check fixes) and the tutorial (after the install); (3) choose the next version among the later features, the Python bindings, the app's two large Mac items and I06's stream (L363). The milestone page is updated (version 4) and main is tagged milestone-2026-10-20.

ecosystem/data/log.yml L001–L370 · ecosystem/data/releases.yml · the milestone page · L153, L230, L264, L363, L368
L3702026-10-20work

App 0.50.1: request 47 used (b1–b4 in Refine; empty .ort files named, the frozen count \(15 \to 10\)); a .bac no longer moved by each save

Core 0.66.0 (L364): Nba = 1's b1–b4 (scale, offset, stretch, shift) refinable rows named by the core; request 47 met. The app had called empty files unknown itself, which kept FullProf's 11 empty .ort files unknown; it now asks the core about them too: the project test counts 10 unknown files of 4082, and the frozen count falls from 15 to 10 (L359 closed). Found by the self-check: each save wrote the .bac with b1–b4 applied and the next opening applied them again (relaunch \(\chi^2\) 1.2373 against 1.2113); fixed in the app (test a_files_background_survives_a_save), and request 48 asks the core to do it itself and leave an unchanged file alone. Self-check under PL36: fresh HOME 178 OK 0 FAIL, its relaunch 202/0, \(\mathrm{Tb_{2}Pd_{2}Sn}\) 120/0, its relaunch 133/1 (Plots' .drv). Rust 96, node 227.

branch app 8eab1f88 · app/REQUESTS.md 47, 48 · L359, L364
L3692026-10-20work

Publish stream: drs-publish 0.32.0, P57 met and used; P50's models per pattern used

P57 met by core 0.66.0 (L364) and used: T13's background file and b1–b4, D2 without the Mac's path (P61 asks the core for a relative path). P50 used: T13, T14, T17 of a joint refinement per pattern (pattern = k) under the joint names. One self-check under PL36, no FAIL; Josu's app files unchanged. The stream's Log: line said L368, taken by Josu's acceptance of TLS's points; renumbered L369.

branch publish 5d40d33c · publish/REQUESTS.md P50, P57, P61 · L364
L3682026-10-20decision

Josu accepts TLS's unexplained points (L367), frozen; version 2's eleven features done in the core

Core 0.67.0 (L367) gives TLS displacements of rigid groups, the last part of P41, against FullProf's rigid/ runs. Josu accepts, frozen as they are (L037), as with L343 and L357: (1) urea's TLS centre (FullProf prints z = 0.326, its β fit 0.3303, the centre of mass 0.3296; urea's β off by up to 5·\(10^{-5}\)); (2) urea_test's \(\chi^2\) 13.229 against FullProf's 13.27 (0.31 %); and the four frozen numbers the asymmetry at the Bragg angle moved (nac_3t2_calc and scor_rxn calc_2 one window-edge point more each, gabriela_calc_3's \(\chi^2\) 3.4·\(10^{-5}\), scor_rxn's high-angle difference −1.57 % to −1.61 %, refrozen at 1.62 %). With this the core holds all eleven features of version 2 (L153): P25, P45, P55, P36, P54, P59, P06, P05, P23, P30, P41. Left for version 2 as used: TLS in the app and in Publish (T16), and a final installed check. The app's project test still counts 23 files the core does not name, frozen at 15: eleven FullProf .ort files are empty (0 bytes: the Z-matrix and some rigid-body runs), and the core names an .ort by its ATOM and BETA lines; asked of the core to name them by their extension too.

Josu's answer, 20 October 2026 · L037, L153, L343, L357, L359, L367
L3672026-10-19work

Core 0.67.0: TLS displacements of rigid groups (P41's last part, Publish's P47.2 and T16) against FullProf's rigid/ runs

  • The model. A rigid group with P6 = 5.0x gives its atoms, and its satellites' atoms, anisotropic displacements U = T + A L Aᵀ + A S + Sᵀ Aᵀ (Schomaker & Trueblood 1968, as FullProf's manual p. 38 states them).
  • A = [[0, z, −y], [−z, 0, x], [y, −x, 0]] from r = M (x − centre), in the crystal's Cartesian frame.
  • FullProf's β = 2π² M\(^{-1}\) U M\(^{-}\)ᵀ. T in Ų, L in rad\(^{2}\), S in Å·rad, as the .pcr holds them; S's last three are S21, S31, S32.
  • The centre is the group's origin (P6 = 5.0), or the main group's centre of mass (5.01, "x = 1"), by CrysFML's atomic weights (Chem_Info's AtWe, now generated into scattering_table::ATOMIC_WEIGHT).
  • Against FullProf. Every atom's β against the BETA lines of the runs' .ort (test tls).
  • hmt and pdd's five runs (pdd, pdd_l, pdd_t, pdd_t_rot, pdd_calc; centre at the origin): within the print, 4.4–4.9·\(10^{-6}\).
  • Urea's six (centre of mass): off by up to 5.45·\(10^{-5}\) (ureac, ureacyl), frozen. FullProf's β fit only a centre at z 0.33032–0.33036. FullProf prints its centre of mass at z 0.326. The group's atoms by CrysFML's weights put it at 0.32957. No weighting tried gives either (masses with or without the hydrogens, atomic numbers, neutron scattering lengths). Not explained; for Josu.
  • urea_test's \(\chi^2\) 13.229 against FullProf's 13.27 (0.31 %), its profile within the print. With z = 0.33034 the core gives 13.278 (0.06 %). Frozen in fullprof_profiles; for Josu, with the centre.
  • fullprof_profiles compares the eleven TLS runs (126 compared). berar_factor: 13 of their 18 outputs compared, within 0.05 % or the .prf's rounding; hmt's two and urea's three left out with other Bragg points.
  • The asymmetry at the Bragg angle. pdd's profile was off by 1.3·\(10^{-2}\) at 10.6° and 35°.
  • Bérar–Baldinozzi's tan \(\theta\) and the AsyLim test were taken at the shifted position. FullProf takes them at the Bragg angle, without the zero and the sample's shifts, which is also the paper's \(\theta\). Now so in the default model too (Model::bragg_two_theta).
  • pdd's profile is now within 4·\(10^{-5}\). 32 runs agree better; 11 worse, within the print.
  • Three numbers it moved, refrozen in fullprof_profiles: nac_3t2_calc one more window-edge point (158), scor_rxn calc_2 one more (1307), gabriela_calc_3's \(\chi^2\) 3.4·\(10^{-5}\) further. In pattern_terms, the λ refinement's cycle 7 (L255's unexplained high-angle difference) went from −1.57 % to −1.61 %, refrozen at 1.62 %. For Josu.
  • Explore's y2o3 cases (zero 0.05° with P1, P2 refined) move in their \(\chi^2\)'s fourth or fifth figure; E's end \(\chi^2\) goes from 2.0011 to 1.9928. The core's tests carry the new numbers (paths_compared, explore_stops). explore/compare/cases/README.md is Explore's to update.
  • The committed results file of case A (tests/data/results) was written again. GSAS-II could not be run here (no SciPy); what it read, the points and the cell, did not change. Core 0.25.0's file stays beside it as a_modes_results_0.25.0.cif, the sample of a file written before (paths_compared).
  • The Jacobian. Exact derivatives of the TLS atoms' β with respect to the 21 values, the atoms' positions, the group's origin (the centre moves with it) and the cell (rigid_body::beta_derivative, dual numbers).
  • The Bragg angle is a profile variable of its own, so the zero and the shifts do not move the asymmetry's \(\theta\).
  • A satellite now follows its parent in the derivatives too (placement_derivative; P41's Jacobian left urea's hydrogens fixed when the parent moved).
  • Test tls::derivatives: urea, hmt and pdd, every group value, TLS value and cell edge against central differences within \(10^{-4}\) of the column.
  • Wiring.
  • T, L, S are values of Field::Rigid after the internal coordinates (dictionary rigid_tls_t, rigid_tls_l, rigid_tls_s), refined with the .pcr's Furth-line codewords. Ties are by sign, e.g. urea's s22 = −s11.
  • A TLS atom's β is fixed by "TLS". The values are kept in the control file (rigid_tls_t, _l, _s, tls_centre) and found by locate.
  • The .pcr export writes Furth = 21 and the "! TLS parameters:" lines (LINE 43). Several TLS groups in one phase are refused there.
  • The results file has _drs_rigid_tls_*: per group its centre; per value T Ų, L deg\(^{2}\), S Å·deg (FullProf's printed units) with its su, its free parameter and multiplier; per atom U11 … U23 with su's through the covariance. The structure CIF's _atom_site_aniso_U of TLS atoms carries those su's.
  • drs rigid prints T, L, S, the centre and the β.
  • FullProf 8.20's names t11 … s32 (T11 … in pdd's), X/Y/Z_RGB, Dm_In, TheIn, PhiIn and Or1 (Pref1) are mapped: output_names maps all 328 names of the 16 runs now built.
  • The rigid/ runs convert to control files and back (control_file), FP_tests' urea is read (waiting_for_runs), and fullprof_summary adds ten runs agreeing in all its first block. urea_test is named there: its .pcr refines none of the 17 codewords it carries.
  • For Josu: urea's TLS centre (FullProf's 0.326 printed, 0.3303 fitted, 0.3296 by mass); urea_test's \(\chi^2\) (0.31 %); the four refrozen numbers moved by the Bragg-angle asymmetry.
core/src/rigid_body.rs · core/src/powder.rs · core/src/jacobian.rs · core/src/refinable.rs · core/src/edit.rs · core/src/control/{schema,pcr_export,locate}.rs · core/src/results.rs · core/src/cif_write.rs · core/src/geometry.rs · core/src/output_names.rs · core/src/main.rs · core/tests/data/make_scattering_tables.py · core/tests/tls.rs · core/tests/fullprof_profiles.rs · manuals/fullprof/runs/rigid (L346, L351) · manuals/fullprof/FullProf_Manual.pdf pp. 38–39, p. 122 (LINE 43) · CrysFML CFML_Scattering_Chemical_Tables.f90 (AtWe) · Schomaker & Trueblood (1968), Acta Cryst. B24, 63 · publish/REQUESTS.md P41, P47.2
L3662026-10-19work

App 0.50.0: texture along several axes in Refine (P30)

P30 (core 0.65.0, L360) in Refine: each axis's direction, March ratio and fraction named by the core, the last fraction the core's 1 minus the others. The self-check's Free is sent in the core's form, and a refusal is now a FAIL. TLS and request 47 waited for the core then (core 0.66.0 has since met 47, L364).

branch app 40e232e3 · L359, L360, L364
L3652026-10-19work

Publish stream: drs-publish 0.31.0, texture along several axes read (P48 met); F10 and T14

P48 met by core 0.65.0 (L360) and read: T14 along several axes (Nor 2, Nor 3) and F10 the axes' March–Dollase pole densities (against cos ρ until the core gives ρ, P59), on the core's yb_nor2_one, yb_nor2, yb_nor2_g2 and yb_nor3; P47's TLS and P57 not yet given then. One self-check under PL36, no FAIL; Josu's app files unchanged. The stream's Log: line said L364, taken by the core's request 47; renumbered L365.

branch publish 4302021d · publish/REQUESTS.md P48, P59 · L360
L3642026-10-19work

Core 0.66.0: the app's request 47 (FullProf's .ort named; Nba = 1's b1–b4 as values) and Publish's P57

  • .ort named. Kind fullprof_ortep, "FullProf atoms for ORTEP (CODFILn.ort)", an output.
  • The source is the manual, p. 39: a rigid-body phase (Jbt = 4) writes "x#.ortep … reduced co-ordinates + atomic temperature parameters (ORTEP)". The .ort is believed to be its present name: its ATOM and BETA lines.
  • The 29 in the repository: 18 hold ATOM/BETA lines; 11 are empty, written for phases without groups (citric acid, \(\mathrm{PPH_{3}}\), ureaz).
  • make_file_kinds.py names them apart from the classifier. Its list, regenerated, now covers every file of L346's and L351's runs (file_kinds: no file named otherwise).
  • The app's count of files the core does not name falls by 26 (L359).
  • Nba = 1's b1–b4 as values. The .bac's points are kept as read (BackgroundModel::Points, no new variant).
  • b1–b4 are four values of the background, background_terms::FileScale (scale, offset, stretch, shift and the file's name). They are the last four of Field::BackgroundTerm.
  • The function is read at (1 + b3)·2\(\theta\) + b4, times b1, plus b2 (Model::background_x, background_function_at). FullProf's line below the first point is tested where the points are read, its step stretched with them.
  • Exact derivatives (Model::background_file_derivative: the function's slope for b3 and b4). The point heights' columns are scaled by b1.
  • Dictionary ids background_file_scale, _offset, _stretch, _shift. They are the control file's keys of [pattern.background], which keeps file = "….bac" now whatever b1–b4 are.
  • The .pcr's Nba = 1 codewords read as theirs; until now any code on them was refused. The export writes b1–b4 with their codewords.
  • Against FullProf, y2o3_bac_b1b2 and y2o3_bac_b3b4 stay within its print (background_terms).
  • y2o3_bac_b3b4 now goes through the .pcr export unchanged to the bit (control_file). Its moved points were rewritten at the data's points before.
  • Test background_terms::file_scale_values: derivatives against differences; b1–b4 refined back from moved values to \(10^{-5}\); the control file and the .pcr export read again to the same values, background and free parameters; the results file's items.
  • P57.
  • 1: _pd_proc_ls_background_function names the further terms. These are a file's b1–b4 ("… read from y2o3_bac_b3b4.bac, as b1 B[(1 + b3)2\(\theta\) + b4] + b2 with …"), the inverse powers, the Debye-like terms with their r, and the profiles' files with their coefficients.
  • 2: the results file's _drs_bkg_file and _drs_bkg_file_scale, _offset, _stretch, _shift with su's.
core/src/formats/project.rs · core/tests/data/make_file_kinds.py · core/src/background_terms.rs · core/src/powder.rs · core/src/jacobian.rs · core/src/control/{schema,pcr_export,locate,mod}.rs · core/src/results.rs · core/src/cif_write.rs · core/tests/background_terms.rs · app/REQUESTS.md 47 · publish/REQUESTS.md P57 · manuals/fullprof/FullProf_Manual.pdf p. 39, p. 46
L3632026-10-19work

I06 worked: the innovations in a stream of their own, the reference engine as every strategy's last stage, a bench with FullProf as baseline, and gains measured per phase

Josu, thinking out loud on I06: no friction with the work done; the innovations perhaps as extensions, perhaps a parallel stream for steps 0–2, kept as options; built and proved on the known cases and results; compared on the ease of reaching them, with gain parameters measured also in partial times; intermediate steps that depend on the case; the aim a proposal for a more robust Rietveld refinement. The first workshop note of I06 answers:

  • Form: not an I02 extension (not designed yet, and a minimiser is calculation, L139) but a stream like Publish: its own crate (drs-lab in lab/, the name Josu's), worktree, branch and requests, using the core as a library and never editing lsq.rs; a Lab switch in the app, off by default; migration only on the bench's evidence and Josu's decision.
  • The guarantee: every strategy ends with the core's Levenberg–Marquardt and FullProf's convergence test, so path methods change the path, never the result; information methods (energy, robust likelihood, misfit model) are shown beside the reference result.
  • The bench: the tutorials' own starts with converge_runs.py's references (L052), seeded perturbed starts, and synthetic graded cases with known truth; FullProf (fp2k, headless on the Mac) run from the same starts as the baseline.
  • Partial times: capture (the first iterate from which the reference engine alone succeeds), descent, polish, certification; costs in forward and Jacobian evaluations; reach (r₅₀), ease, tuning-freedom, agreement, failure taxonomy; performance and data profiles (Dolan and Moré 2002; Moré and Wild 2009).
  • Case-dependent steps: the core's PlanStep with two more fields, the method and the trigger, the triggers read from each Cycle plus a peak-overlap index; the best sequence per class of case is a result of the bench.
  • The contribution: robust to the start, to false minima, to the settings, to the user, to misfit, and reproducible; two papers first (the bench; the staged certified minimisation).

Steps L0–L5 replace I06's steps 0–2. Status of I06 to workshop.

ecosystem/data/ideas.yml (I06, workshop note of 19 October 2026) · manuals/fullprof/runs/ (run_fullprof.py, converge_runs.py) · core/src/steering.rs (PlanStep) · publish/Cargo.toml
L3622026-10-19work

App 0.49.0: a TOF bank refined from Refine (request 46 met); P23's further background terms in Refine

Request 46 met by core 0.64.0 (L354): a TOF bank refined from Refine. P23 (L353) in Refine: Debye-like terms, the 12-term polynomial's inverse powers and profiles from files, each named by the core; the Fourier filter refused with the core's reason. Request 47 to the core: FullProf's .ort files named (the 26 of L359, the app's frozen count kept at 15) and Nba = 1's b1–b4 kept as values.

branch app 7a751fbd · app/REQUESTS.md 46, 47 · L353, L354, L359
L3612026-10-19work

Publish stream: drs-publish 0.30.0, the background's parts read (P49 met); T13's further terms and F11

P49 met by core 0.63.0 (L353) and read: T13's further background terms (Debye-like, inverse powers, profiles from files) with su's, F11 the background and its parts, on the core's nac_3t2, y2o3_poly12_b12, y2o3_bac_b3b4 and y2o3_more2_two; P57 to the core; P48 (texture) and P47's TLS not yet given then. One self-check under PL36, no FAIL; Josu's app files unchanged. The stream's Log: line said L360, taken by the core's P30; renumbered L361.

branch publish 192b31e3 · publish/REQUESTS.md P49, P57 · L353
L3602026-10-19work

Core 0.65.0: P30, texture along several axes (FullProf's Nor = 2, 3) against texture/'s four runs; Publish's P48

Each axis j of a phase has a fraction f_j of its crystallites distributed about it by March's density with ratio r_j: P_h = Σ_j f_j D_j, averaged over the equivalent reflections as the one-axis function is; the last fraction is 1 minus the others. It is read from the .pcr (the NPOD line and one line per axis after line 30-1, FullProf's News of 12 October 2003), calculated, refined (Field::Texture, exact derivatives), and kept in the control file, the .pcr export and the results file (module texture, test texture; drs texture). L346's four runs:

  • Nor = 2 agrees with FullProf. yb_nor2 (110 r 0.85 f 0.6, 001 r 1.2 f 0.4), yb_nor2_g2 (the same with Pref2 0.3) and yb_nor2_one (one axis): FullProf's CORR, over yb_nor2_one's, at all 16 reflections within its print; the profiles rms 2.8·\(10^{-5}\) (fullprof_profiles), \(\chi^2\) 1.6684 against 1.668. FullProf's G2 does not enter Nor = 2: yb_nor2_g2's CORR is yb_nor2's, number for number.
  • Nor = 3 does not agree (for Josu). The News describes the model: an anvil cell, the beam along the force, FullProf integrating eqs. 4–6 of Wright, Nelmes, Belmonte & McMahon (1996), J. Synchrotron Rad. 3, 112–119. The paper is not in manuals/papers, and journals.iucr.org could not be reached from the cloud session. The core uses the believed form: the scattering vector at ψ = 90° − \(\theta\) from the sample's axis, axis j on a cone about it, cos ρ = cos α cos ψ + sin α sin ψ cos φ, D_j averaged over φ in NOrstep steps (20 when not given). FullProf's yb_nor3 CORR is below it at all 16 reflections, by up to 3.6 % (the core's up to 3.75 % above, at (3 3 3)). The profile is off by up to 2.5 % of the strongest peak; \(\chi^2\) 1.6636 against 1.663. Frozen in texture (16 reflections, 0.03752) and in fullprof_profiles (yb_nor3.prf, rms 5.68·\(10^{-3}\), max 2.48·\(10^{-2}\)).
  • What was tried for Nor = 3, none of it reaching FullProf's values:
  • ψ = 90° − \(\theta\), \(\theta\), 2\(\theta\) or 90° − 2\(\theta\); φ over π, 2π or π/2; weights 1 or sin φ; equivalents averaged or not.
  • The beam perpendicular to the force, integrated over the Debye ring.
  • Free factors on each axis's term.
  • For 13 of the 16 reflections FullProf's value lies outside what the cone integral gives at any ψ.
  • Two pairs of reflections at the same 2\(\theta\), (3 3 3)/(5 1 1) and (4 4 2)/(6 0 0), need different ψ, so no factor of \(\theta\) alone explains it.
  • A run with Nor = 3 and r = 1 (D = 1 everywhere) would show whether FullProf adds a factor of \(\theta\) beside the texture; a run with one axis would show each axis's term alone. Both are for the Mac if Josu wants Nor = 3 resolved, or the paper itself.
  • With three values free (\(r_1\), f₁, \(r_2\)) the 16 reflections of Fm-3m do not fix them. From \(r_1\) 0.9, f₁ 0.5, \(r_2\) 1.1 the engine reaches \(\chi^2\) 1.6·\(10^{-7}\) on the core's own pattern at \(r_1\) 0.764, f₁ 0.159, \(r_2\) 1.126. The test refines the two ratios and the scale, which it recovers to \(10^{-6}\).
  • Publish's P48:
  • 1 and 2: the results file's _drs_pref_orient_function (none, march-dollase, exponential, march-dollase axes), _geometry (reflection, anvil cell), _steps. One axis gives its h k l, G1 and G2 with su's. Several give a loop of each axis's h k l, r and fraction with su's, the last fraction's through the covariance of the others. _pd_proc_ls_pref_orient_corr's text names the axes.
  • 4: texture::march_dollase (D in multiples of a random distribution) and texture::of_phase (a phase's axes; one March–Dollase axis with G2 as a second axis of r = 1).
  • 3, spherical harmonics, waits for a FullProf or GSAS-II run that uses them.
  • Dictionary ids po_axis_ratio, po_axis_fraction, po_axis_steps. Control file: po_axis_direction, po_axis_ratio, po_axis_fraction, po_axis_steps. The .pcr export writes Nor = 2 or 3 and every phase's axes; a phase whose preferred orientation is not March–Dollase without G2 is refused under them.
  • Counts: fullprof_profiles compares 115 (4 more); field_labels meets the new kind in yb_nor2; control_file, berar_factor, model_text, phase_curves and waiting_for_runs take texture/'s files as read.
core/src/texture.rs · core/src/pcr.rs · core/src/powder.rs · core/src/jacobian.rs · core/src/results.rs · core/tests/texture.rs · manuals/fullprof/runs/texture · manuals/fullprof/news/Fullprof_News_2003.htm · publish/REQUESTS.md P48
L3592026-10-19finding

App 0.48.0 not built: the app's frozen count of files the core cannot name grew from 15 to 41, FullProf's .ort files from the rigid-body runs

The merging session's gated run (main 7b5a8777, core 0.64.0): core 368 and Publish 59 tests pass, UI 221 pass; the app's Rust test project::tests::every_run_and_tutorial_is_listed_by_role_with_the_cores_names failed: "41 misnamed files (frozen at 15)". The 26 new ones are FullProf's .ort files written by L346's and L351's rigid-body runs (rigid/…1.ort, one per run and pass), which core 0.62.0–0.64.0 took into manuals/ without naming the kind; the 15 frozen before are unchanged (winplotr.mlf \(\times 6\), a .zip, a .spr, a .py, a .cif …). Nothing built or installed; Josu keeps 0.47.0. The frozen count is not raised (L037): the core is asked to name FullProf's .ort in output_names (and in the file kinds the app reads), with its role.

app/src-tauri/src/project.rs (MISNAMED_FROZEN) · manuals/fullprof/runs/rigid · L346, L351
L3582026-10-19finding

I06 assessed: what molecular dynamics, DFT relaxations, Monte Carlo and machine learning can lend to the minimisation, keeping FullProf's results

Josu asked whether the minimisation itself can be improved, in reliability and in speed, with algorithms from molecular dynamics, DFT relaxations (SIESTA) or Monte Carlo, and then with machine learning, neural networks and physically informed models, still landing on the same results. The assessment is idea I06 in the seed-place.

  • Locally, the core's engine is already the better minimiser. DFT relaxers (SIESTA's CG, Broyden, FIRE) build curvature from gradients because the Hessian is unaffordable; the core has the exact Jacobian, so Gauss–Newton/Levenberg–Marquardt converges quadratically near the minimum. The default stays, and so do FullProf's results. The time is lost elsewhere: starts outside the basin, false minima, sloppy directions, large residuals, the human's plan, the cost of a cycle with many parameters.
  • The same minimum, faster (A): variable projection of the scale and background (Golub and Pereyra 1973); geodesic acceleration (Transtrum and Sethna 2012), with the second directional derivative by dual numbers; the NL2SOL large-residual term; continuation in resolution, in range and by annealed priors in place of a hand-written plan; reparameterisations (Cholesky ADPs); a robust likelihood; the adjoint and a sequential predictor.
  • The landscape (B): basin hopping with Levenberg–Marquardt as the relaxer and crystallographic moves; a temperature ladder whose cold end is the Rietveld minimum, whose β = 1 rung is L286's posterior and whose thermodynamic integral is its evidence; Hamiltonian Monte Carlo (molecular dynamics on \(\chi^2\)) with the normal matrix as the mass matrix; delayed acceptance on the Pawley surrogate (David and Sivia); the nudged elastic band between two minima as a view for Explore.
  • The energy (C): a Boltzmann prior from the bond-valence energy now and a machine-learned potential later, its weight inferred as MEM's α is (I05); ADPs from the potential's phonons (after Hoser and Madsen); a Landau prior over mode amplitudes, which answers L286's prior-width weakness; forces as a diagnostic. A result with an energy is not FullProf's and is shown beside the unrestrained one.
  • Machine learning (D), by one rule: the network proposes, the likelihood decides. Amortised posteriors as starts and proposals, corrected by importance sampling; a normalising flow as the sampler's geometry (NeuTra); a Gaussian-process model of the systematic misfit (Kennedy–O'Hagan), which makes the posterior honest; the core as a differentiable physics layer. Not worth it: a neural emulator of the forward model, a deep network on 47 materials, a network as the engine.
  • Precedents found by search today: Scatter2xyz (Anker, Gardner, Rosset, Goodwin and Deringer, arXiv 2510.05938) optimises walkers by Adam against scattering data and a machine-learned potential, for structure solution; Mun, Nam and Choi (J. Appl. Cryst. 2026) predict a refinement's start by a network in 2 ms, then refine by least squares; FOX's parallel tempering; TOPAS's potential penalties and robust refinement.

First step proposed: a bench of 100 perturbed starts per FullProf run, measuring success, cycles and time for the present engine, before any new algorithm. The asks are on I06's page.

ecosystem/data/ideas.yml (I06) · core/src/lsq.rs, steering.rs, jacobian.rs, dual.rs · arXiv 2510.05938, 1201.5885, 1903.03704, 2508.12939, 2512.16709 · J. Appl. Cryst. 2026 (Mun, Nam, Choi) · SIESTA tutorials (MD.TypeOfRun)
L3572026-10-19decision

Josu accepts P23's and P41's unexplained differences with FullProf (L347, L353), frozen as they are

Each counted and frozen in its test (L037: it fails if the count or the size grows); Josu accepts all five, as with time of flight (L343):

  • P23 (L353): (1) FullProf's Fourier-filter background (Nba = −2, manual eq. 3.7) not reproduced, so the core refuses that option by name; (2) nac_3t2 at 2\(\theta\) = 0.05°, inside the excluded region, FullProf's background 0.557 below the core's; (3) nac_3t2's residual at FullProf's last printed values 7 575.5 against 7 624 (0.65 %), the .prf believed to belong to the cycle before the last.
  • P41 (L347): (4) citric acid's \(\theta\) 5.2 esd (0.077°) from FullProf's; (5) \(\mathrm{PPH_{3}}\)'s \(\theta\), φ, \(\chi\) 1.4–1.8 esd. Set aside: keeping some or all open for the core to study before P30.
Josu's answer, 19 October 2026 · L037, L343, L347, L353
L3562026-10-19work

App 0.48.0: rigid groups in Refine (P41): a phase's groups shown and refined in place of their atoms' coordinates

P41 (core 0.62.0, L347): each group's origin, orientation and internal coordinates shown and refined in place of its atoms' coordinates, named by the core; \(\mathrm{PPH_{3}}\) refined from Refine; request 44 met for P41. Request 46, P30 and P23 waited for the core then (core 0.63.0 and 0.64.0 have since given P23 and request 46, L353, L354).

branch app 1f8941ed · app/REQUESTS.md 44 · L347
L3552026-10-19work

Publish stream: drs-publish 0.29.0, rigid groups read (P47's groups met); T15

P47's rigid groups (core 0.62.0, L347) read: T15 of each group's placement and its atoms' internal coordinates, T2's atoms marked G, on the core's \(\mathrm{PPh_{3}}\) and citric-acid refinements; TLS (T16) waits for the core. One self-check under PL36, no FAIL; Josu's app files unchanged. The stream's Log: line said L353, taken by the core's P23; renumbered L355.

branch publish 09d70370 · publish/REQUESTS.md P47 · L347
L3542026-10-19work

The app's request 46: the core's checks read a time-of-flight control file as the calculation does; an xys TOF file no longer read \(\times 100\)

Asked (app/REQUESTS.md 46): the checks Refine runs before every Run (check::check_path, R24) read arg_si's control file as constant wavelength: errors "the wavelength λ1 is 0 Å" and "the data run from 20 to 299.95, outside 0–180° 2\(\theta\)", and warnings on its range and on the µs of its zero and excluded regions. Refine stops on them.

Done (check, control):

  • The data. The checks read the data as the calculation reads them, through one function, control::data_scan.
  • No wavelength. A TOF bank has none, so none is checked.
  • The data's range is checked in µs (positive) rather than 0–180°.
  • No reflections. A phase without reflections in the data is reported in µs, against the bank's Dtt1.
  • Units. zero_shift and excluded_regions are expected in µs, as the writer writes them. The other 2\(\theta\) values, which a TOF bank does not use, keep their unit.
  • FullProf's range. Only TOF-min is compared with the data. A TOF bank's TOF-max is its background's period (P05: CeO2_PEARL.pcr's 19 380.78 µs against data to 19 400.19).

Found and fixed. control::open (Refine, drs calc, drs refine) put every TOF pattern's data on the GSAS axis, \(\times 100\). That is right for a GSAS CONST bank only. alum's and lamn_pol's xys files, already in µs, came out 100 times too far: drs calc alum.drc found 0 reflections in its data. Now only a GSAS bank is scaled. The .pcr import was right throughout.

Test fullprof_tof::tof_checks: nine single-pattern TOF runs (arg_si, abs1, abs3, invd, alum, CeO2_PEARL, lamn_pol, lamn_pol_irf, nac-osiris). Each control file, written in another folder, checks with no error and none of the wavelength, range or unit diagnostics, and opens to the same points and calculated pattern as its .pcr.

Also taken in. fullprof-v2's citric runs (55045130, L351). citric-acid-sls_1's two passes and citric_core are indexed in fullprof_profiles, and counted where the runs are counted; citric_free wrote no .prf.

  • Both patterns agree with the core's. \(\chi^2\) 64.648 against FullProf's 64.65 for citric-acid-sls_1_calc, and 64.565 against 64.57 for citric_core.
  • citric_core answers L347's question. FullProf's own \(\chi^2\) at the core's minimum (\(\theta\) 218.2704°) is 64.57, below the 64.64 at which it stopped. The core's \(\theta\), 5.2 esd from FullProf's, is the lower minimum by FullProf's own calculation. That frozen difference is for P41's next step to revisit.
  • fullprof_profiles takes a last point as FullProf's "background only" when its Ycal and Backg print within one unit, as two values each rounded to the unit can. It allowed half a unit before. citric-acid-sls_1_calc.prf prints 2109 and 1624 (+ the offset 484.33) at 118.388°, where the tutorial's run printed 1625.
core/src/check.rs · core/src/control/mod.rs · core/tests/fullprof_tof.rs · app/REQUESTS.md 46 · manuals/fullprof/runs/{arg_si,alum,CeO2_PEARL,lamn_pol,nac_osiris}
L3532026-10-19work

P23 with Publish's P49: Debye-like terms, the 12-term polynomial, Nba = 1's b3 and b4 and profiles from files calculated and refined against FullProf's 16 runs; FullProf's bounds on the background found; the Fourier filter refused; three differences frozen for Josu

Taken in first. L346's 42 runs (runs/rigid, texture, background_terms) are in the frozen counts:

  • fullprof_profiles indexes all 42 .prf (make_fullprof_profiles.py): 16 compared, the TLS and Nor \(\ge 2\) runs left out with their reason until their steps;
  • berar_factor, fullprof_outputs, output_names, control_file, model_text and phase_curves count them; a .pcr the reader refuses (Nor \(\ge 2\)) is counted as refused, not a failure.

Built (module background_terms, Model::background_terms):

  • Debye-like terms Σ A_j sin(Q r_j)/(Q r_j), Q = 4π sin \(\theta\)/λ₁ at the data's 2\(\theta\) (Nba = −1, manual eq. 3.4).
  • Inverse powers c₁/2\(\theta\) + c₂/2\(\theta\)² + c₃/2\(\theta\)³, 2\(\theta\) in degrees, after nine coefficients of the polynomial (Nba = −3). The News of 28 July 2003 gives the formula as a lost image: the form is read from the runs.
  • Profiles from files, each times its coefficient (MORE = 2, 3 on line 14; profile n from {data}_n.bac, as FullProf's .out names them), joined linearly.
  • Nba = 1 with b3, b4 (eq. 3.6): the .bac's point x stands at (x − b4)/(1 + b3). It was refused.
  • Refinement. Field::BackgroundTerm, with exact derivatives; the .pcr's codes for each, and FullProf's names Bck_12 … Bck_17 for the amplitudes.
  • Dictionary. background_debye_amplitude, background_debye_distance, background_inverse_power, background_profile.
  • Files. Kept in the control file ([pattern.background]; profile_files relative to it, moved by relocate) and located in it. Exported to the .pcr as Nba = −1, −3 and MORE = 2 with each profile written at the data's points.
  • Results file (P49.2). When the background has Debye-like terms or profiles: _drs_bkg_calc_smooth, _drs_bkg_calc_debye_n and _drs_bkg_calc_file_n, which add up to _pd_proc_intensity_bkg_calc.
  • Fixed. A polynomial whose coefficients are all 0 is now 0 whatever its origin (Bkpos = 0 gave NaN).
  • Fixed. The background-free Rp divides by Σ(y − b) as it is, as the manual defines it (p. 44: "in the denominators the quantity yᵢ is changed to yᵢ − bᵢ"). It was held above \(10^{-300}\), so a background above the counts gave \(10^{300}\) where FullProf prints −145 (y2o3_poly12_b7.sum); fullprof_summary now agrees on all 16 new runs' all-points blocks.

Found: FullProf's bounds on its background. FullProf sets its background to 0 wherever the function falls below 0 or above the data's largest count. Evidence:

  • nac_3t2_debye prints 0 at the 1 504 points where eq. 3.4's sum is negative. Its Ycal there is the Bragg part alone: within 0.46 of Bragg + max(b, 0), against 20 for max(Bragg + b, 0).
  • y2o3_poly12_b9 prints 0 from 144.95°, where B9's term first passes \(\mathrm{Y_{2}O_{3}}\)'s largest count, 11 248 (11 249.22 there, 11 240.66 at 144.94°).

The rule holds for the polynomial (with its terms), not for points: LBF_lmopr.prf prints −0.37 to −1.6 from 124.75° between its points, and runs/modes/D's d_calc.prf −167.6 at 0.79° on the line below its first point. FullProf's Chebyshev and Fourier series are not known to be bounded (no run takes them out of bounds).

The rule is in the FullProf layer (Convention::background_bounds, with Model::background_ceiling the data's largest count; Model::background_bounded limits it to a polynomial; derivatives 0 at the points it holds). The import drops it where it changes nothing at the data's points, which is every run in the material but these two.

Against FullProf (test background_terms):

  • Backgrounds. The core's background at the 13 091 or 2 510 points of 14 runs is FullProf's within its print (0.1, 0.01), with one offset, a quarter of the least background (L082). Runs: nac_3t2_calc, nac_3t2_debye, poly12 b1, b2, b7, b9, b10, b11, b12, bac_b1b2, bac_b3b4, more2, more3, more2_two.
  • B7 and B9 need FullProf's single precision as well: their x\(^{6}\) and x\(^{8}\) terms reach 1 700 and 12 000 counts. The bound adds the error of evaluating the function in single precision (derived in the test; 0.024 counts for B9 at 143.35°).
  • Whole patterns. fullprof_profiles compares the 14 patterns; all agree within its bounds. \(\chi^2\) 1.2966 against FullProf's 1.296 for bac_b1b2; 132.157 against 132.1 for more2_two; 3.6238 against 3.619 for nac_3t2_calc.
  • Bérar's factor. berar_factor compares 15 of the outputs: nac_3t2 −0.009 %, the others within 0.0005 % or within the .prf's rounding.
  • nac_3t2's refinement (50 parameters from FullProf's start, to the engine's convergence; \(\chi^2\) \(3.62 \to 3.20\)):
  • the eleven background values (the six of the polynomial, five amplitudes) are within 0.11 of FullProf's esd's, with esd ratios 0.93–0.99;
  • the other 37 values are within 0.37, with esd ratios 0.955–0.992;
  • the cell's a ends 1.50 of the core's esd from FullProf's. FullProf prints the cell's esd at half (L053); the ratio here is 1.98.
  • FullProf refines a 51st parameter, Ca1's β23, which it frees itself: the .pcr codes it 0.
  • Files. Five runs' control files read back to the same model, pattern and free parameters, from another folder. Every further term is located. The exports read back to the same background and free parameters. The results file's parts add up.

Refused, not reproduced. The Fourier filter (Nba = −2, manual eq. 3.7, y2o3_filter with FWINDOW 3000) did not reproduce FullProf's background: Numerical Recipes' SMOOFT at FWINDOW and at other windows gives rms 0.5–1.9 counts against FullProf's corrections of up to 6.3. Refused by the import, by name.

Not built: no FullProf reference. GSAS-II's background peaks and the Debye term's damping exp(−U Q\(^{2}\)) are not in FullProf. They wait for a GSAS-II reference.

For Josu: not explained, counted and frozen. 1. The Fourier filter (above): refused. 2. nac_3t2 at 2\(\theta\) = 0.05°. FullProf's background is 0.557 below the core's. The point is in the excluded region 0–5°, and at Q = 0.0045 Å⁻¹ sin(Qr)/(Qr) is within \(10^{-4}\) of 1. One point, frozen.

  • At 2\(\theta\) = 0 itself FullProf leaves the Debye-like terms out (31.033 = −Σ A_j), where their limit is 1. The core keeps the limit: explained, frozen. 3. nac_3t2's residual at FullProf's last printed values is 7 575.5 against FullProf's 7 624 (−0.64 %), where at the start it is +0.2 % (the 157 window-edge points of L035).
  • FullProf's pass-1 .prf gives its 7 624 (7 623.4 with the core's weights).
  • The core's background at the printed values departs from that .prf toward high angles: 0.005 at 10°, 1.01 at 124.85°.
  • So the .prf and its residual are believed to belong to the parameters of the cycle before the last, not to the printed ones. Not checked: frozen at 0.65 %.

Next (L346's runs): P30, texture beyond one axis (texture/), then P41's TLS (rigid/). citric_core and citric_free, named in L347, were not run then (they are since: L351).

Written as L348, which the Publish stream's drs-publish 0.27.0 took on main; renumbered L353 at the merge, in the core's lines only.

core/src/background_terms.rs · core/src/powder.rs · core/src/pcr.rs · core/src/jacobian.rs · core/src/refinable.rs · core/src/output_names.rs · core/src/control/{schema,mod,locate,relocate,pcr_export}.rs · core/src/results.rs · core/tests/background_terms.rs · core/tests/data/make_fullprof_profiles.py · ecosystem/data/parameters.yml · manuals/fullprof/runs/background_terms/*.{pcr,pcr.in,prf,out} · manuals/fullprof/FullProf_Manual.pdf p. 45 · manuals/fullprof/news/Fullprof_News_2003.htm · publish/REQUESTS.md P49
L3522026-10-19work

App 0.47.0 on core 0.62.0 installed, no FAIL

The merging session's gated run (main f785cf7d, niced): core 362, Publish 58, app Rust 92 and UI 220 tests pass; built; three self-checks on a copy with its own identifier (PL36), its preferences domain emptied first, each to its quit: \(\mathrm{Y_{2}O_{3}}\) 132 OK 0 FAIL (request 40's linked selection passing again, L344, L350), the relaunch 167 OK 0 FAIL (P46: 2 windows and 3 tabs reopened), \(\mathrm{Tb_{2}Pd_{2}Sn}\) 139 OK 0 FAIL; Josu's files and preferences unchanged. Installed; 0.45.1 to the Trash. Josu's app now has 0.46.0's conventions (one Open…, Export ▸ CIF…, Duplicate, Report an Issue, thin-line icons, smart zoom), time of flight in Refine (P05) and File ▸ Move To….

scratchpad self047a/r/b · L340, L344, L350
L3512026-10-19work

FullProf's citric-acid runs for P41 (L347): citric-acid-sls_1, citric_core and citric_free, on branch fullprof-v2

The runs core 0.62.0 named (L347), by version2_runs.py run, on branch fullprof-v2 (55045130) beside L346's: citric-acid-sls_1 in both passes, citric_core (NCY = 0 at the core's minimum, \(\theta\) 218.2704, φ 25.2426, \(\chi\) 24.0619) and citric_free (the group's origin X0 Y0 Z0 and A6's torsion refined, NCY = 20), all exit 0. citric_free's log says "Singular matrix!!, problem with PhiIn_A6_ph1 no.: 20": FullProf finds the torsion of A6 (parameter 20) singular when refined with the origin, for the core to read. The script's run rewrote L346's background and texture inputs on disk; they were put back as committed, so the branch holds L346's outputs unchanged.

branch fullprof-v2 55045130 · manuals/fullprof/runs/version2_runs.py · L346, L347
L3502026-10-19work

App 0.47.0: time of flight in Refine (P05), File ▸ Move To… through the core (request 45), L344's FAIL traced and fixed

L344: the self-check's completion probe in the Editor typed a half table that auto-save wrote to disk on a loaded Mac; the file then read as broken and request 40's linked selection found nothing. The probe is now never auto-saved and is typed and taken back outside the Editor's history. P05 (core 0.59.0, request 44 met for P05): a TOF pattern against time of flight (µs), in d and Q by the core's inverse of T(d); the bank (2\(\theta\), TOF-max, absorption), Zero, Dtt1, Dtt2, Dtt_1overd, Hodges' terms and each phase's back-to-back exponential profile as refinable rows named by the core; the ticks in µs; TOF banks in a joint refinement. Its Run is stopped by the core's checks, which read a TOF file as constant wavelength: request 46 to the core. File ▸ Move To… (core 0.60.0, request 45 met): control::relocate rewrites the paths. Self-check under PL36: fresh HOME 166 OK 0 FAIL, its relaunch 187/0, \(\mathrm{Tb_{2}Pd_{2}Sn}\) 120/0, its relaunch 137/1 (Plots' own .drv check), the merging session's first launch 161/0; Josu's state untouched. Rust 92, node 219.

branch app 8447f19e · app/REQUESTS.md 44, 45, 46 · L341, L342, L344
L3492026-10-19work

Publish stream: drs-publish 0.28.0, time of flight read (P05); P46 met

P46 met by core 0.59.0 and read: F1 against a bank's time of flight or d and Q, T1's bank, T17's TOF values with su's, T13 in T, D1 and D2, on the core's six TOF runs (arg_si, CeO2_PEARL, alum, lamn_pol, nac_osiris, gabriela's bank with two X-ray patterns). One self-check under PL36, no FAIL; Josu's app files unchanged. The stream's Log: line said L347, taken by the core's P41; renumbered L349.

branch publish 9b7d3809 · publish/REQUESTS.md P46 · L341
L3482026-10-19work

Publish stream: drs-publish 0.27.0, each item's state in the Publish sidebar (built, stale with its reason, failing, not built)

DESIGN.md §37, §6.6's sidebar, the largest item left of §25's list while the core's P46 was on its branch. drs-publish says built, stale or not built (module::states, the bridge's publish_states): the build writes into each item's header what the set says of it (module::item_digest: the set's shared lines and the item's own block, so that another item's options, the order or a comment leave it unchanged), beside its sources' checksums; an item is stale when either differs now, with the reason; failing comes from the last checks' errors. The sidebar shows the word in the theme's colour, the reason on hover, read again after every change, Undo included. Under nice -n 15, CARGO_BUILD_JOBS=8. Self-check under PL36, 240 s: 191 lines, 154 OK, no FAIL; Josu's app files unchanged. The stream's Log: line said L343, taken by Josu's acceptance of P05's differences; renumbered L348.

branch publish ebec058a · DESIGN.md §6.6, §25, §37
L3472026-10-19work

P41 with Publish's P47: a rigid group's values refined in place of its atoms' coordinates, against FullProf's refinements of \(\mathrm{PPH_{3}}\) and citric acid; one difference frozen and its FullProf run named: core 0.62.0

Built. A phase's rigid groups are now part of the model (Phase::rigid; L345's rigid_body placement):

  • Import. The groups are read from a Jbt = 4 .pcr (Furth = 0) and kept in degrees whatever the file's DEG. The atoms sit where the groups place them. fullprof_profiles and fullprof_structure_factors still pass for \(\mathrm{PPH_{3}}\) and citric acid.
  • Fields. The group's values are refined in place of its atoms' coordinates: Field::Rigid, with the origin X0 Y0 Z0, \(\theta\), φ, \(\chi\) and each atom's internal coordinates. Their derivatives through the atoms are exact (the placement written over the crate's dual numbers).
  • The cell. A group keeps its shape in Å, so its atoms' fractions follow the cell, and the cell's derivative carries them (Group::cell_derivative).
  • Codewords. The .pcr's codewords for \(\theta\) and φ (line 25-2) and for P9–P15 (line 25-4) are now read. Before, they were dropped: a rigid file's group was silently left fixed.
  • Dictionary. Four ids: rigid_origin, rigid_orientation, rigid_length, rigid_angle.
  • Files. Groups are kept in the control file ([[phase.rigid_group]] with kind, origin, orientation and a satellite's anchor; each atom's rigid_group, rigid_length, rigid_angle, rigid_connect) and located in it. They are exported to the .pcr (Jbt = 4, lines 25-1 to 25-4, DEG = 1). FullProf's names ThRGB, PhRGB, ChRGB are mapped (output_names: 3527 of 3563 names).
  • Results file (Publish's P47.1 and P47.3). _drs_rigid_group_* gives each group's kind, origin and \(\theta\), φ, \(\chi\) with su's and a satellite's anchor; _drs_rigid_atom_* gives each atom's internal coordinates with su's and N1 N2 N3. The atoms are flagged G (_atom_site_refinement_flags_posn), their positions with su's through the covariance (geometry::Propagator now counts the group's values).
  • P47.4. Answered by L345: a Furth = 0 file was read at FullProf's placement, not with its internal coordinates taken as fractions; now it is read with its groups.

Against FullProf (test rigid_refinement; FullProf 6.00's .out of PPH3-sls and citric-acid-sls_1, both "Convergence reached"):

  • Derivatives. The analytic derivatives of the origin, \(\theta\), φ, \(\chi\) and a torsion match central differences to \(10^{-9}\) of the largest; the cell's group part to \(10^{-4}\), the difference's own error.
  • \(\chi^2\) at FullProf's values. 42.626 and 64.647, against FullProf's conventional 42.63 and 64.64.
  • \(\mathrm{PPH_{3}}\). Refined to the minimum, \(\theta\), φ, \(\chi\) are 1.42, 1.46 and 1.78 of FullProf's esd's away (frozen). The esd ratios are 0.99, 0.97 and 0.99. The departure is believed to go with the 5·\(10^{-4}\) Å by which its written positions depart from its printed Z-matrix (L345); not checked.
  • Citric acid. φ 1.39 esd (frozen) and \(\chi\) 0.06 esd; esd ratios 1.02, 1.02 and 0.98. \(\theta\) ends 5.16 esd (0.077°) above FullProf's 218.193°, \(\chi^2\) 64.563 against FullProf's 64.64, which its three cycles did not lower.
  • On the core's calculated pattern, which matches FullProf's .prf to its print, S along \(\theta\) alone (the others at FullProf's values) falls from 2 077 024 at FullProf's \(\theta\) to 2 075 116 at +0.06°.
  • FullProf printed \(\theta\)'s applied shift as −0.000 with R_at = 0.08, so its own computed shift was below 0.4 esd. The core's is 4.8 esd.
  • Not explained: counted and frozen for Josu.
  • From a turned start. With \(\theta\), φ, \(\chi\) turned by 1° (65 esd), the undamped refinement returns to the same minimum within 0.003 esd in 4 cycles.
  • Files. The control file reads back to the same model, pattern and free parameters to the bit; every group value is located in it. The .pcr export reads back to the same model and free parameters. The results file has the group's loops and 21 atoms flagged G.
  • The deposition CIF against FullProf's (cif_deposition). FullProf's CIF prints the positions it wrote, and the core's atoms now sit where their group places them. Beyond the two prints: 27 of \(\mathrm{PPH_{3}}\)'s coordinates (up to 5.4·\(10^{-5}\), L345's frozen placement) and one of citric acid's (6·\(10^{-6}\), within L345's bound but beyond the print once the core writes su's). Frozen.

Not referenced, runs named (runs/version2_runs.py, folder rigid/; both passes of citric-acid-sls_1 added):

  • citric_core. FullProf at the core's \(\theta\), φ, \(\chi\) with NCY = 0: whether its own \(\chi^2\) is lower there.
  • citric_free. Refining the origin and A6's torsion too: no file in the material refines a group's origin or internal coordinates, which the core now refines with the same derivatives.
  • Satellites. A satellite's following of a turned parent is unchecked: only the urea files have satellites, all with unturned parents and TLS.
  • TLS. Still waits for L345's runs (urea, hmt, pdd); Publish's P47.2 with it.
core/src/rigid_body.rs · core/src/jacobian.rs · core/src/refinable.rs · core/src/control/schema.rs · core/src/control/locate.rs · core/src/control/pcr_export.rs · core/src/results.rs · core/src/cif_write.rs · core/tests/rigid_refinement.rs · ecosystem/data/parameters.yml · 9-SAnn-OrganicCompounds/{PPH3,CitricAcid_example}/Rietveld/*.out · publish/REQUESTS.md P47
L3462026-10-19work

FullProf's references for P41, P30 and P23 run on the Mac, on branch fullprof-v2

The runs L345 named, by manuals/fullprof/runs/version2_runs.py run, with FullProf (fp2k, February 2025 macOS build), every one exit 0 and none with a singular matrix or a NaN in its log. Rigid bodies and TLS (rigid/, 22 .prf): urea and its variants urea_conv, ureac, ureacyl, ureaz, urea_test with urea_notls; hmt; pdd with pdd_t, pdd_l and pdd_t_rot; PPH3-sls. Texture (texture/, 4 .prf): yb_nor2_one, yb_nor2, yb_nor2_g2, yb_nor3. Background terms (background_terms/, 16 .prf): nac_3t2 with nac_3t2_debye; y2o3_bac_b3b4, y2o3_bac_b1b2; y2o3_poly12 with the coefficients b1, b2, b7, b9–b12; y2o3_filter; y2o3_more2, y2o3_more3, y2o3_more2_two. 55 MB, kept on branch fullprof-v2 (4b180a13), not on main, until the core takes them in (L222).

branch fullprof-v2 4b180a13 · manuals/fullprof/runs/version2_runs.py · L222, L345
L3452026-10-19work

Publish's P50, a joint refinement's model per pattern from its results file; P41's rigid bodies placed as FullProf places them; TLS, texture beyond one axis (P30) and the further background terms (P23) refused and their FullProf runs named: core 0.61.0

P50. Each pattern block of a joint results file now holds that pattern's control file (_drs_control_file, Joint::pattern_control: its model, refinement and data source, its files named from the joint refinement's folder). JointResults::pattern_controls reads them back without the data; in a file written before 0.61.0 it imports the overall block's multi-pattern .pcr split by pattern (the resolution and hkl files from the folder given), or reads the patterns' control files a joint control file names. PatternResults::phases names each pattern's phases by their joint ids. drs joint <results.cif> [--pattern k] prints them. Test joint_refinement::models_per_pattern_without_the_data: scor_rxn refined one cycle, its two patterns' models, phases and free parameters read from the results file in an empty folder, equal to the joint's; from the .pcr and from the joint control file in older files.

P41, the survey (manuals and files; page numbers of FullProf_Manual.pdf and docs/FullProf-Restraints-RigidBodies.pdf). There are 17 rigid-body .pcr files under manuals/fullprof (Jbt = 4, none with −4). Only four have FullProf outputs: PPH3-sls and citric-acid-sls_1, Rietveld refinements of Z-matrix groups without TLS by FullProf 6.00, and two SAN files. No TLS file has any output, and runs/ has no rigid-body run. GSAS-II's rigid bodies (quaternions, TLS in the body's frame with 8 S terms) are not in the repository. The core read a Jbt = 4 phase's atoms at the x, y, z FullProf writes beside its groups (P1–P3, "for information", manual p. 35) and dropped line 25-3. Publish's DESIGN.md §34 and P47.4 say the internal coordinates are taken as fractional; they are not.

P41, built (module rigid_body, drs rigid). pcr now keeps each Jbt = 4 atom's numbers past Occ and its line 25-3 (PcrAtom::rigid). groups reads the groups: P6 1 rigid, 2.xx satellite of the xx-th group, 4 Z-matrix, 5 TLS; KIND 0–3; DEG; X0, \(\theta\), φ, \(\chi\); P16 = N1.N2. positions places every atom by r_C = R_z(φ) R_y(\(\theta\)) R_z(\(\chi\)) r_m + M X0 (RBdoc p. 8). satellite_axis gives a satellite's origin and the polar angles of its z axis, from the parent's atom N1 and from N2 (or the parent's origin) to N1. Found on FullProf's outputs, where the papers say nothing: in a Z-matrix the third atom (and any atom with N3 = 0) lies on the +y side of the xy plane, and the torsion has the opposite sense to IUPAC's. Either other choice puts citric acid's and \(\mathrm{PPH_{3}}\)'s atoms 0.5–1.2 Å from FullProf's.

P41, against FullProf (test rigid_bodies):

  • The Euler matrices FullProf prints are matched to their five decimals: citric acid's (.out lines 413–416) and urea's Z-matrix (urea.tpcr).
  • Citric acid's 21-atom Z-matrix in Cartesian, from the internal values the .out prints, is checked against FullProf's table. 19 atoms are within the print's 5·\(10^{-6}\) Å plus \(10^{-6}\) for FullProf's arithmetic (believed single-precision, not checked). A19 and A20, two oxygens placed through hydrogens, are at 1.6·\(10^{-5}\) and 2.8·\(10^{-5}\) Å: not explained, counted and frozen.
  • All 17 files' 235 atoms are placed against the P1–P3 FullProf wrote. The bound is computed per atom: each number the placement reads, half a unit of its last printed decimal through its derivative, summed, plus P1–P3's five decimals. 13 files are within it on every atom (the spherical, Cartesian, cylindrical and Z-matrix kinds, degrees and radians, satellites; worst 0.78 × the bound). Four depart and are frozen, with what was found:
  • urea_test (4 atoms). Its satellites' \(\chi\) are ∓0.2 rad, but its P1–P3 are those of \(\chi\) = 0, where every atom is within 6·\(10^{-5}\) Å: the \(\chi\) were written after FullProf placed the atoms.
  • naph_san (6 atoms, 4.5 ×, 1.7·\(10^{-3}\) Å). Its positions are not a rigid image of its internal values: the best orientation and origin fitted to them leave 3.9·\(10^{-4}\) Å (a scratch fit).
  • ureaz (3 atoms, 1.4 ×). FullProf's own Ur3 and Ur4 are not mirror images (0.14466 0.64466 against −0.14468 0.35536), where their internal values are (the same distance and angle, torsion 180°).
  • PPH3-sls and its backup (10 atoms, 1.4 ×, 5.2·\(10^{-4}\) Å): not explained.
  • The 16 satellites of the eight urea files have their X0, \(\theta\), φ as satellite_axis gives them from the parent.

The powder model still takes P1–P3. Refining a group's values (\(\theta\), φ, \(\chi\), X0, the internal coordinates) through its atoms is P41's next step. Its references are already here, PPH3-sls and citric-acid-sls_1 refined by FullProf, and it needs no run.

Refused until FullProf's runs (test waiting_for_runs). Each refusal is named, where before some of these were misread:

  • TLS displacements (Furth ≠ 0). The frame T and L are in is not settled: molecular, as RBdoc p. 9 defines R, or the crystal's, as the site-symmetry note of p. 14 suggests. urea and hmt, with zero angles, cannot tell them apart.
  • Nor = 2 and 3 (P30). The .pcr reader stops at line 4: before, the lines after line 30 would have been misread, and Nor \(\ge 2\) taken as the exponential. No file under manuals uses either.
  • MORE = 2 and 3 on line 14 (P23, profiles read from files). Before, they were misread as micro-absorption.
  • Nba = −1 (Debye-like terms), −2 (Fourier filter) and −3 (12 terms with inverse powers, whose formula was an image in the News of 28 July 2003, now lost). These now name the term instead of a Debug print.
  • FullProf has no background peaks and no damping of the Debye terms (GSAS-II's only, help/powderbkg.html), and no spherical-harmonic texture.

The runs named (manuals/fullprof/runs/version2_runs.py, prepare writes them all; each file reads in the core up to the refusal that names what it waits for):

  • rigid/, both passes: urea, urea_conv, ureac, ureacyl, ureaz, urea_test, hmt, pdd and PPH3-sls. Variants: urea_notls; pdd_t and pdd_l, each matrix alone; pdd_t_rot, pdd_t with \(\theta\) + 30°, for the frame.
  • texture/, from Yb2ZrWO8-PO-March, laid out as in the News of 12 October 2003: yb_nor2_one (one direction, against Nor = 1), yb_nor2 (1 1 0 and 0 0 1 with fractions 0.6 and 0.4, Dollase 1986 eq. 5), yb_nor2_g2, and yb_nor3 (multi-axial, 20 steps).
  • background_terms/: nac_3t2 (both passes) and nac_3t2_debye. From \(\mathrm{Y_{2}O_{3}}\)'s bacfirst: y2o3_bac_b3b4 and y2o3_bac_b1b2 (eq. 3.6); y2o3_poly12_b1…b12, one coefficient at a time; y2o3_filter (FWINDOW 3000, win_cyc 1); y2o3_more2, y2o3_more3 and y2o3_more2_two, with the profiles' lines believed after line 14.
  • Not runnable as they are: ndni_htr, whose magnetic phase the core does not model, and naph_san, a simulated annealing on integrated intensities.
  • runs/README.md also notes that L040's "no run uses preferred orientation" predates K2NaAgCN and highpress.
core/src/rigid_body.rs · core/src/joint.rs · core/src/pcr.rs · core/src/powder.rs · core/tests/rigid_bodies.rs · core/tests/waiting_for_runs.rs · core/tests/joint_refinement.rs · manuals/fullprof/runs/version2_runs.py · publish/REQUESTS.md P47–P50 · FullProf_Manual.pdf pp. 34–36, 85–86, 100, 104 · docs/FullProf-Restraints-RigidBodies.pdf pp. 5–15 · news/Fullprof_News_2003.htm
L3442026-10-19finding

App 0.46.0 not installed: request 40's linked selection selected nothing in the Editor on the first-launch run

The merging session's gated run (main dba52270, core 0.58.0, niced): core 343, Publish 56, app Rust 89 and UI 217 tests pass; built; three self-checks on a copy with its own identifier (PL36), each to its quit; Josu's files and preferences unchanged; P46 reopened 2 windows and 3 tabs. \(\mathrm{Y_{2}O_{3}}\) 132 OK 1 FAIL; the relaunch 171 OK 0 FAIL; \(\mathrm{Tb_{2}Pd_{2}Sn}\) 130 OK 0 FAIL. The FAIL: "request 40: phase1.cell_a chosen in Refine selects "" on line 245 of the Editor; Refine's value 10.604965499600409". 0.45.1 passed the same line (L339), so 0.46.0 likely broke it (its Open… panel, the Editor's font family or what opens at launch are near). Not installed: Josu keeps 0.45.1, which has the linked selection working. Passed to the app stream.

scratchpad self046a/r/b · L339, L340
L3432026-10-19decision

Josu accepts P05's six unexplained differences with FullProf (L341), frozen as they are

Core 0.59.0 (time of flight, L341) matches FullProf on L334's runs except six points it could not explain from FullProf's manual, the 2003 TOF document or the papers, each counted and frozen in its test (L037: it fails if the count or the size grows). Josu accepts all six as they are, as with the Bérar factor (L245) and the λ path (L258): (1) FullProf's TOF peak window, wider than ±Wdt·H (window-edge points frozen per file; arg_si's \(\chi^2\) 2.675 against FullProf's 2.637); (2) a RALF bank's \(\sigma\), FullProf's 100 times the core's; (3) Iwg = 1 in TOF, no variance rule found that gives FullProf's \(\chi^2\) (lamn_pol, nac-osiris); (4) alum's \(\chi^2\), 0.7 %; (5) gabriela's X-ray patterns, the points at excluded regions' ends FullProf calculates and the core leaves out; (6) arg_si's esd of Extinc, 1.908 times the common ratio. Set aside: keeping some or all open for the core to study before P41.

Josu's answer, 19 October 2026 · L037, L245, L258, L341
L3422026-10-19work

The app's request 45: a control file's paths rewritten when it is moved (File ▸ Move To…, L337): core 0.60.0

control::relocate(text, from, to) gives the text of a control file written in folder from as it reads from folder to. The paths it holds are the core's to know (relocate::paths): [pattern] data, [pattern.background] file, each setting of a [[pattern.operation]] whose unit is "path" in the operations' dictionary (file of add, subtract and merge), a mode phase's mode_reference = { file = … }, and in a joint control file each [[joint.pattern]] control. Each is resolved from from and written relative to to (absolute when the two share only the root, as on another volume, the rule control::relative already keeps); only the bytes of each value are replaced, so comments, order and the way each other value is written stay as they were. Refused, the file named, when a path names a file that is not there, when the text does not read (its paths cannot be found with certainty), and when the destination is not a folder. drs move <file.drc> <folder> writes the moved file and removes the old one (refused when the folder holds a file of that name). A joint refinement's patterns' control files, written by Joint::control_files, now carry the data operations' files relative to their folder too (they were written as the .pcr named them). The resolution file is not a path of the control file: the resolution is held in [pattern.resolution]'s values, read from the .irf when the .pcr was imported (the .irf's name stays in [pattern.fullprof] as FullProf's record, not read). Test control_relocate: \(\mathrm{Y_{2}O_{3}}\)'s control file in a/, its data in data/, its background points beside it and a subtracted file, with a comment and a value written in the person's way, moved to b/c/: three lines change (../../data/y2o3.dat, ../../a/y2o3 background.drb, ../../data/empty holder.dat), opened there it gives the same data and model, moved back it gives the text it had; A_modes' sign reference moved one folder down still reads its 12 amplitudes; scor_rxn's joint file moved to another folder opens with the same objective. Request 44's TOF in Refine is met by 0.59.0 (L341), but for a multi-bank GSAS file read as one pattern per bank.

core/src/control/relocate.rs · core/tests/control_relocate.rs · core/src/main.rs (drs move) · app/REQUESTS.md 44, 45 · L337
L3412026-10-19work

P05, time of flight, against L334's FullProf runs: calculated, refined, written and read; Publish's P46; the differences counted and frozen for Josu: core 0.59.0

What FullProf does, found on its runs (each to the print of its .out or .prf; the manual's page where it has one). Positions: Npr 9, T = Zero + Dtt1 d + Dtt2 d\(^{2}\) + Dtt_1overd/d (the .prf's ticks; its .hkl and .out print Dtt1·d alone); Npr 10 (Hodges, pp. 84–85), T = n Tₑ + (1 − n) Tₜ with n = ½ erfc(w(x_c − 1/d)), to 0.003 µs on gabriela's 1288 reflections. Rise and decay: α = α₀ + α₁/d + α_Q/√d, β = β₀ + β₁/d\(^{4}\) + β_Q/d\(^{2}\) (alum's .out to six digits); with Hodges' terms 1/α = n(α₀ + α₁d) + (1 − n)(α₀ₜ − α₁ₜ/d), the 2003 TOF document's form, where the manual (p. 112) mixes α itself. Widths: \(\sigma\)² = \(\sigma\)₀ + \(\sigma\)₁d\(^{2}\) + \(\sigma\)₂d\(^{4}\) (+ \(\sigma\)_Q d, GSAS-II's sig-q, believed), H_L = \(\gamma\)₀ + \(\gamma_1\)d + \(\gamma_2\)d\(^{2}\), H and η by TCH. Profile: CrysFML's back-to-back exponentials ⊗ pseudo-Voigt. Intensity: scale·m·F\(^{2}\)·d\(^{4}\) sin \(\theta\)_B·A·E (d\(^{4}\) sin \(\theta\)_B, \(\theta\)_B half the bank's 2\(\theta\): 0.9531/0.9533 for 144.8°, 0.9997 for 90°, 0.9943 for 29.0°). Absorption, x = A₁λ: Iabscor 1 (1 − e^{−2x})/(2x), 2 Hewat's cylinder, 3 e^{−x} (the manual says e^{−cλ²}). Extinction: Sabine's, x = Ext (λF/V)², 1/V\(^{2}\) to 5 digits on every reflection of ext10 and ext100. Fourier background in TOF: Σ B_j cos(jπT/ T_max) (the manual says C = T_max; C = T_max/π meets arg_si's background to its print). Chebyshev over TOF-min to TOF-max. A point is left out when its step forward reaches a region. FullProf's GSAS reading: a CONST bank's x \(\times 100\) and y \(\times 1000\), a RALF bank's y \(\times 100\)/Δt. Names: FullProf calls Zerot "Dtt_1overD" in an Npr 10 pattern (gabriela.out's parameter 9 = 23.96, line 16's codeword 91). The .prf's second line holds Zero, Dtt1, Dtt2 after the cycle where a constant wavelength's Zero, SyCos, SySin are. FullProf prints R −3 c's Laue class with its orientation, "−3m1".

Built (module tof, and through the core): the bank (Bank: Dtt1, Dtt2, Dtt_1overd, Hodges' terms, the bank's 2\(\theta\), Iabscor, T_max) and each phase's TOF values (Profile, 19), a TOF resolution file's part added (Instrument, Res 5/6, the keywords of FullProf's News of 2011); Field::Tof and Field::TofProfile refined, with their analytic derivatives (a dual number of 39 variables: the zero, the bank's 8, the cell, scale, Bov, G1, G2, the 19 profile values and F\(^{2}\), through which the atoms reach the extinction; the profile's own partials CrysFML's), their 29 dictionary ids, the .pcr's codewords read (with VARY back_n of a coded background term, a no-op: CeO2_PEARL.out lists the 20 coded parameters after VARY back_7), FullProf's symbolic names, the control file's keys and the .pcr export's lines 8, 9, 14, 16 and 27–33 (110 runs exported and read back to the bit, the TOF ones with them); the absorption's and extinction's slopes kept at 0, so that ABS1 or Extinc refined from 0 moves. drs calc, drs refine, drs convert, drs results and drs joint take a TOF .pcr. The reflection list is generated only as far beyond the data as the widest window reaches (the same 262 reflections for arg_si, calculate 7 times faster). e^z E₁(z), in the Lorentzian part, is taken by the continued fraction for Re z \(\ge 0\) from |z| = 2 on, where CrysFML takes its series up to 10: against mpmath at 40 digits the series' error grows to 1.2·\(10^{-8}\) at |z| = 10 for Re z > 0, the continued fraction holds 3·\(10^{-16}\); that noise had made the profile's finite differences miss its partials by \(10^{-4}\).

P46 (Publish's TOF items): the profile's abscissa as _pd_meas_time_of_flight and results::read saying which (Results::axis); the bank's _pd_meas_2theta_fixed and the TOF–d equation with its values in _pd_calibration_conversion_eqn; every TOF value of the bank and phases with its su, the resolution file's part and the total in _drs_tof_parameter_* (Results::tof_parameters); each reflection's TOF in _drs_refln_tof; the profile in d and Q (Results::abscissa_in, Model::d_spacing_at, T(d) inverted to \(10^{-12}\) Å); a multi-bank refinement as a joint results file, a block per bank (PatternResults::axis): gabriela's TOF bank and two X-ray patterns as the test, since FullProf's four-bank Ho2BaCuO5 test-tof.pcr is magnetic (its first phase in the magnetic space group P2₁'/c, Jbt = −6, with magnetic symmetry modes): the core refuses it, saying so. The flight path (_pd_instr_dist_spec/detc) is not written: no file of the runs gives it.

Tests. fullprof_profiles with the TOF runs (\(\chi^2\) equal to FullProf's on arg_si and its seven variants); fullprof_tof: tof_derivatives (526 derivatives of eight TOF runs, every kind of TOF field, against finite differences within 4·\(10^{-7}\)), tof_refinement (arg_si from its .pcr.in: two cycles, \(\chi^2\) at or below the core's at FullProf's values, all 16 parameters within FullProf's esd), tof_results (P46 read back); and the frozen counts taken in: control_file (the four runs not converted named), fullprof_outputs (243 .prf; the TOF ticks from the .hkl's Dtt1·d), berar_factor (176 outputs, 79 compared: arg_si_invd −0.007 %), fullprof_summary (84 runs), output_names (515 TOF names, all mapped), fullprof_spacegroups, parameter_dictionary, field_labels.

Not explained, frozen, for Josu (L037): (1) FullProf's TOF peak window: about Wdt·(5–8 µs) wider than ±Wdt·H on each side for Npr 9, and for Npr 10 shorter on the rise side and longer on the decay side; not the convolved peak's FWHM, not 1/α or 1/β, not CrysFML nFP's rule; the window-edge points frozen per file (arg_si_calc 586 points up to 10.8 %; lamn_pol 2163 up to 15.4 %; gabriela's bank 871 up to 58.7 %), and with them arg_si's \(\chi^2\) 2.675 against FullProf's 2.637 in the core's own statistics. (2) CeO2_PEARL's \(\sigma\): FullProf's are 100 times the core's for a RALF bank (its \(\chi^2\) 1/9984 of the core's), a reading not stated anywhere. (3) Iwg = 1 in TOF (lamn_pol, nac-osiris): no variance rule found that gives FullProf's \(\chi^2\) (frozen 3.450, 3.447, 5.502). (4) alum's \(\chi^2\), 0.7 %. (5) gabriela's X-ray patterns, the points at regions' ends FullProf calculates and the core leaves out. (6) arg_si's esd of Extinc: the esd's are FullProf's times one ratio, √(\(\chi^2\)/\(\chi^2\)_Bragg) = 0.726 (FullProf takes the \(\chi^2\) of the points with Bragg contributions, as known), but Extinc's is 1.908 times that ratio. Refused, with the reason: ABSCOR2 (FullProf's two runs with it stopped), the TOF size and strain terms (no run uses them), Npr 13. Seen and left for a later step: berar_factor skipped a constant-wavelength .prf point printed \(10^{-5}\)–\(10^{-4}\) below its data point; matched, PPH3's three outputs and modes E's two would be compared, PPH3-sls at −1.77 %.

core/src/tof.rs · core/src/jacobian.rs (tof_derivatives) · core/src/powder.rs · core/src/pcr.rs · core/src/refinable.rs · core/src/control/{schema,pcr_export,mod}.rs · core/src/results.rs · core/src/joint.rs · core/src/output_names.rs · core/src/cif_write.rs · core/tests/fullprof_tof.rs · core/tests/fullprof_profiles.rs · ecosystem/data/parameters.yml · manuals/fullprof/runs/{arg_si,CeO2_PEARL,alum,lamn_pol,nac_osiris,gabriela} · manuals/fullprof/docs/TOF_FullProf.pdf · publish/REQUESTS.md P46 · L331, L334
L3402026-10-19work

App 0.46.0: the audit's items that need neither Josu nor the core, with Josu's answers (L337)

§3: one File ▸ Open… (⌘O) for a folder or a file (AppKit's open panel); File ▸ Export ▸ CIF…, Publish's D1 written by drs-publish from the control file's newest results file; File ▸ Duplicate of a control file, a copy beside it with the files it names shared (Move To… waits for request 45, the core's rewriting of a control file's paths); Help ▸ Report an Issue… opens Mail to Josu with the versions and the diagnostics report (no paths); AppKit's own items seen by a check (Emoji & Symbols and Start Dictation repeated at each menu rebuild, fixed). §2: every icon in thin lines. §4: General ▸ At launch, open (the last session's windows, the most recent project or file, the welcome page); the Editor's font family; no updater and no minimap, decided. §6: native smart zoom fits the plot under the point. Fixed: Settings ▸ Shortcuts' Change on a relaunch. Self-check under PL36: fresh HOME 168 OK 0 FAIL, its relaunch 173/0, \(\mathrm{Tb_{2}Pd_{2}Sn}\) 120/0, its relaunch 148/1 (Plots' own .drv check); Josu's state untouched. Rust 89, node 217.

branch app 04233784 · app/ui/tests/v046.test.mjs · app/REQUESTS.md 44, 45 · L337
L3392026-10-19work

App 0.45.1 on core 0.58.0 installed, no FAIL

The merging session's gated run (main d4b29fc2, niced): core 343, Publish 55, app Rust 87 and UI 211 tests pass; built; three self-checks on a copy with its own identifier (PL36), its preferences domain emptied first, each to its quit: \(\mathrm{Y_{2}O_{3}}\) 127 OK 0 FAIL, the relaunch 152 OK 0 FAIL (P46: 2 windows and 3 tabs reopened), \(\mathrm{Tb_{2}Pd_{2}Sn}\) 134 OK 0 FAIL; Josu's files and preferences unchanged; the Copy checks now read back the app's own write (L336). Installed; 0.44.0 to the Trash. Josu's app now has the shared Undo (L333), joint refinements saved as our own control files, and Publish's sets on the Undo (L335).

scratchpad self0451a/r/b · L333, L335, L336
L3382026-10-19work

Publish stream: drs-publish 0.26.0, F7's distances, angles and bond-valence sums against T or composition; T4's sums with their su's

DESIGN.md §36, needing no new core work while the core is on P05. F7's structural quantities against T or composition from the core's results files (P18, met by core 0.27.0): phase1.Mn.distances draws each distinct bond of Mn as a series (keyed by the bonds' symmetry codes), phase1.Mn-O1-Mn.angle the angles at O1, mean_distance and valence_sum the core's ⟨d⟩ and BVS with their su's; a series without charges refused with the reason. T4 takes ⟨d⟩ and the sums with their su's from the results file (\(\mathrm{La_{2}CoMnO_{6}}\) at 453 K: ⟨Mn–O⟩ 2.04(4) Å, BVS(Mn) 2.8(3)). Example la0-geometry-tfg: \(\mathrm{LaMnO_{3}}\) on D20 refined again by the core with charges, the \(\mathrm{MnO_{6}}\) octahedron against T (Mn–O 1.906, 1.971, 2.19 Å; Mn–O–Mn 154.5°; BVS(Mn) 3.10–3.11). Under nice -n 15, CARGO_BUILD_JOBS=8. Self-check under PL36, 170 s: 171 lines, 133 OK, no FAIL; Josu's app files unchanged.

branch publish 826a7a51 · DESIGN.md §36 · P18
L3372026-10-19decision

Josu's answers to the app audit's open items: thin-line icons, Duplicate and Move To take the control file only, Report an Issue by email, no updater, no minimap

The five items of the APP_CONVENTIONS audit that waited for Josu (app/README.md, L300), decided on 19 October 2026:

  • Icons (§2): all thin-line; the two filled ones (Automatic's half-disc, Help's dot) redrawn.
  • Duplicate and Move To (§3): the control file only, with the paths inside it rewritten so that it still reaches the same data, resolution and background files where they are. Set aside: copying or moving the named files along (a self-contained copy), and asking each time. Rename was settled in L224: the control file only.
  • Help ▸ Report an Issue… (§3): opens Mail with a message to Josu, the app's and core's versions and the diagnostics report filled in. Set aside: the private repository's issues (only people with access), a public issues page (reports public), hiding the item.
  • Updates (§4): none for now; each version installed as now. Set aside: a check that only tells (a version number on the public site), and a full updater (an Apple Developer ID to sign, and hosting).
  • The Editor's minimap (§4): no; control files are short.
Josu's answers, 19 October 2026 · app/README.md (APP_CONVENTIONS audit) · L224, L300
L3362026-10-19work

App 0.45.1: the self-check's Copy checks read back what the app wrote (L330); request 44 to the core for version 2's rest in Refine

The Copy checks read the app's own record of its pasteboard write (kind, bytes, change count), so another program's copy meanwhile cannot fail them (L330). Request 44 to the core: what Refine needs to show a time-of-flight pattern (P05: the axis, banks, named fields, reflections in TOF), and the same for P41, P30, P23; core 0.58.0 gives none yet, so Refine shows nothing of them.

branch app 7495a972, 9a196b81 · app/REQUESTS.md 44 · L330
L3352026-10-19work

Publish stream: drs-publish 0.25.0, the set's changes on the shared Undo; T17, the instrument and profile parameters

Each of the Publish module's changes is one step on app 0.45.0's shared Undo (L333), named for Edit ▸ Undo (preset, typeface, language, an item added, removed or moved, an option set or reset, a plot dropped: "Undo Remove rietveld-2"), the set's text before and after put back by drs-publish (module::snapshot, restore). Nothing of P46–P50 has come from the core yet, so T17's constant-wavelength part was built: every instrument and profile field the core lists (Model::all_fields, get, refinable::address) with its dictionary symbol, unit and su, on \(\mathrm{K_{2}Na}\)[Ag(CN)₂]₃ (TCH), \(\mathrm{Y_{2}O_{3}}\) (pseudo-Voigt, the Kα doublet) and \(\mathrm{PbSO_{4}}\). Under nice -n 15, CARGO_BUILD_JOBS=8. Self-check under PL36, 160 s: 172 lines, 134 OK, no FAIL; Josu's app files unchanged.

branch publish e4195a54 · app/ui/undo.js · DESIGN.md · L333
L3342026-10-19work

FullProf's references for P05, time of flight: six TOF cases and eleven variants run on the Mac, on branch fullprof-p05

The runs L331 named, by manuals/fullprof/runs/tof_runs.py run, with FullProf (fp2k, February 2025 macOS build). Every base case refined and calculated (exit 0), its calculate pass's global \(\chi^2\) (Bragg): arg_si 7.95, CeO2_PEARL 0.524, alum 2.40, lamn_pol 1.96, nac_osiris 3.83, gabriela 4.25 (three patterns, a .prf each). Variants: arg_si's plain, plain90, abs1, abs3, ext10, ext100 and invd, alum_noq and lamn_pol_irf gave their .prf. Two did not: arg_si_abs2 and arg_si_abs2b (Iabscor 2, the cylinder) stopped with "Singular matrix!!, fix some parameters!" and wrote no .prf; and arg_si_plain90 and arg_si_invd print a Bragg R-factor and RF-factor of NaN, though their .prf is written. Both are for the core to read before using those variants. 122 MB, kept on branch fullprof-p05 (39b1ad0), not on main, until the core takes them in (L222).

branch fullprof-p05 39b1ad0 · manuals/fullprof/runs/tof_runs.py · L222, L331
L3332026-10-19work

App 0.45.0: Undo as a shared service (§11); a joint refinement saved as our own control files through the core (request 43 met)

ui/undo.js: one stack per document that every module uses; Edit ▸ Undo and Redo ask the active tab's document for its stack and name the change ("Undo Free phase1.cell_a", "Undo Typing on Line 12", "Undo Mask 20 to 21"). Refine's stack is the core's document's, the Editor's CodeMirror's history named by kind and line, Data's operations and a joint refinement's settings kept by the service; Explore's tab of a Refine document undoes that document's changes. Plots and Publish have none yet (their streams'). Request 43 (core 0.58.0, L329): a multi-pattern .pcr imported into our joint .drc and one .drc per pattern, a weight, flag or contribution set by the core, each one Undo step; File ▸ Save and auto-save through the core. Self-check under PL36: fresh HOME 157 OK 0 FAIL, its relaunch 172/0, \(\mathrm{Tb_{2}Pd_{2}Sn}\) 115/0, its relaunch 145/1 (Plots' own .drv check); Josu's state untouched. Rust 87, node 209 tests.

branch app deb53c5 · app/ui/undo.js · app/REQUESTS.md 43 · L329
L3322026-10-19work

Publish stream: drs-publish 0.24.0, the rest of version 2 designed (time of flight, rigid bodies and TLS, texture, background terms); T13 and T14 built

DESIGN.md §34, from FullProf's manual, its TOF and rigid-body documents, the news files, GSAS-II's help and Dollase (1986): F1, T1, a new T17 and D2 for time of flight (TOF and d axes, a panel per bank, the bank's 2\(\theta\), Zero, Dtt1, Dtt2, Dtt_1overd, the back-to-back exponential profile, pdCIF's TOF tags); T15 rigid groups and T16 TLS; F10 texture (March–Dollase's P(α), spherical-harmonic pole and inverse pole figures in MRD); F11 background contributions. Built on what the core gives now: T13, the background (five models, each point or term with its su) and T14, the preferred orientation (March–Dollase or the exponential, the axis, G1 and G2 with su's), on \(\mathrm{K_{2}Na}\)[Ag(CN)₂]₃ at 1.5 K (G1 = 0.9138(13) about [0 0 1]*, FullProf's 0.9137, \(\chi^2\) 2.89 as FullProf's), \(\mathrm{Y_{2}O_{3}}\)'s 62 points and \(\mathrm{PbSO_{4}}\)'s 24 Chebyshev terms. Asked of the core: P46 TOF, P47 rigid bodies and TLS, P48 texture, P49 background terms, P50 a joint refinement's model per pattern. Self-check under PL36, 170 s: 179 lines, 141 OK, no FAIL; Josu's app files unchanged. Tests: drs-publish 54, the app's 204 node and 86 Rust pass. The stream's Log: line said L330, taken by the 0.44.0 install; renumbered L332.

branch publish 283c1b0 · publish/REQUESTS.md P46–P51 · DESIGN.md §34 · Dollase 1986
L3312026-10-19finding

P05, time-of-flight neutrons: no FullProf output of a TOF pattern in the material; six of FullProf's TOF test files and eleven variants named for the Mac

P05 is next in version 2 (TOF patterns, d from Zero, Dtt1, Dtt2; back-to-back exponentials ⊗ pseudo-Voigt; a resolution file per bank; multi-bank files; TOF absorption). Its reference, by L037, is FullProf's own output, and the material has none for a TOF pattern: FP_tests/ holds fifteen TOF .pcr files (found by their TOF-min line) with data, no .prf or .out. The core refuses them today (pcr.rs: "TOF and energy-dispersive patterns (Uni ≠ 0) are not read yet"), and its GSAS reader takes arg_si.dat's BANK "CONST 2000. 5." ALT bank as 2\(\theta\) from 20° (it is TOF from 2000 µs, as FullProf reads it with Uni = 1): the data reader is to take the axis from the control file there.

What FullProf calculates is written in FullProf_Manual.pdf (LINE 4 Npr 9, 10, 13, p. 69; LINE 8n Bkpos, Wdt, Iabscor, pp. 75–76; LINE 14 Zero, Dtt1, Dtt2, 2SinTh, p. 82, and LINE 16 Hodges' terms, pp. 84–85; LINES 26–33 for TOF, pp. 108–112) and in docs/TOF_FullProf.pdf (Rodríguez-Carvajal, 2003): Npr 9 is a pseudo-Voigt (TCH from \(\sigma\)² = \(\sigma\)₀² + \(\sigma\)₁² d\(^{2}\) + \(\sigma\)₂² d\(^{4}\) and \(\gamma\) = \(\gamma\)₀ + \(\gamma_1\) d + \(\gamma_2\) d\(^{2}\)) convolved with back-to-back exponentials, α = α₀ + α₁/d, β = β₀ + β₁/d\(^{4}\), TOF = Zero + Dtt1 d + Dtt2 d\(^{2}\), the closed form with erfc and the exponential integral E₁ of a complex argument; Npr 10 mixes epithermal and thermal TOF(d) and α, β with n = ½ erfc(Width (x-cross − 1/d)). The two texts differ on Npr 10's α and β: the 2003 document mixes 1/α and 1/β (its α₀ᵉ, β₀ᵉ in µs, "reciprocal of those corresponding to the function NPROF=9"), the manual mixes α and β; gabriela's run decides. Not written anywhere in the material: the TOF Lorentz factor (the manual says only that 2SinTh enters it), the absorption of Iabscor 1 and 2 (only 3, exp(−c λ²), has its formula), the powder extinction in TOF (p. 63 names only SHELX's empirical correction), and the newer .pcr's Dtt_1overd, Sigma-Q, alphQ and betaQ.

Named for the merging session (manuals/fullprof/runs/tof_runs.py, both passes as run_fullprof.py, joint_runs.py's for the multi-pattern file; the edits checked without FullProf by its prepare mode): arg_si (Si, SEPD 144.845°, Fourier cosine background, GSAS ALT data), CeO2_PEARL (PEARL 90°, Chebyshev, GSAS RALF, VARY/FIX lines), alum (Al2O3, HRPD 174.02°, background points, the Q terms), lamn_pol (LaMnO3, POLARIS, polynomial background), nac_osiris (Na2Ca3Al2F14 + CaF2, OSIRIS, two phases) and gabriela (SnO2–In2O3, a TOF bank with Npr 10 joined to two synchrotron patterns, Ilo 3, Npr 12, whose data give \(\sigma\) = 0 on every point, which FullProf's \(\chi^2\) will show how it weights). Eleven variants, NCY = 0 from a refined file with one change each, isolate what is not written: arg_si_plain (no absorption, no extinction: the Lorentz factor from the .hkl's intensity over multiplicity·|F|²), arg_si_plain90 (the bank's angle in it), arg_si_abs1, _abs2, _abs3 and _abs2b (each absorption with ABSCOR1 0.15, and ABSCOR2 0.05), arg_si_ext10 and _ext100 (extinction alone, at two values for its form), arg_si_invd (Dtt_1overd 20, believed a 1/d term as GSAS's DIFB, not checked), alum_noq (the Q terms at 0), lamn_pol_irf (Res 5 with FP_tests/respol.irf, the POLARIS file made for this .pcr, the .pcr's values less the file's: the same pattern if they add, as the 2003 document says). Set aside: the WISH runs of Ho2BaCuO5 and the five banks of Dy2BaCuO5 (magnetic phases, Jbt = 10, which the core does not model; they are also the only multi-bank GSAS files), test.pcr (its data on its author's Windows disk), cecoal, cecual and lamn-pol (the same instrument and models as lamn_pol; lamn-pol's data file is not there), and Npr 13 (Ikeda–Carpenter), which no file uses. The core waits for the runs before building P05.

manuals/fullprof/FP_tests/{arg_si,CeO2_PEARL,alum,lamn_pol,nac-osiris,gabriela}.pcr · manuals/fullprof/FullProf_Manual.pdf pp. 63, 69, 75–76, 82, 84–85, 108–112 · manuals/fullprof/docs/TOF_FullProf.pdf · manuals/fullprof/runs/tof_runs.py · core/src/pcr.rs, core/src/formats/gsas.rs · L037
L3302026-10-19decision

App 0.44.0 installed on Josu's word with one check failing twice: the clipboard read as text after a plot was copied, the Mac's shared clipboard the likely cause

The merging session's gated run (main ab12fac, core 0.57.1, niced): core 340, Publish 53, app Rust 86 and UI 204 tests pass; built; three self-checks on a copy with its own identifier (PL36), each to its quit; Josu's files and preferences unchanged; P46 reopened 2 windows and 3 tabs. \(\mathrm{Y_{2}O_{3}}\) 127 OK 1 FAIL, the relaunch 151 OK 1 FAIL, \(\mathrm{Tb_{2}Pd_{2}Sn}\) 126 OK 0 FAIL. Both FAILs the same line: "copy: Edit ▸ Copy with a plot focused ran; the clipboard holds text/plain", while the app's status said "The plot is on the clipboard as an image (PNG)". The check reads the system pasteboard 1.2 s after the copy (app/ui/app.js, tour_clipboard_types), and the pasteboard is one for the whole login session, not isolated by HOME or by the bundle identifier: Josu was copying the streams' prompts while the run went on, the likely cause; the app stream's own run passed the check. Josu chose to install now. The app stream is asked to make the check read back what the app wrote (its change count), not whatever the pasteboard holds. 0.43.0 to the Trash.

scratchpad self044a/r/b · app/ui/app.js copy check · L325, L328
L3292026-10-18decision

A joint refinement's control file: a joint .drc naming one .drc per pattern, with the weights, flags, contributions and shared parameters; its settings through the core (the app's request 43): core 0.58.0

Decided (L322's "to decide": the control file of our own for several patterns). A joint refinement is saved as a joint control file, {control}.drc, of the same TOML subset with format = "diff-ref-struct joint 1": [[joint.pattern]] per pattern (its own control file, {control} pattern {k}.drc, the file dictionary's new pattern_control; its weight, refined flag and the joint phases its model holds), [[joint.phase]] the phases' names, and [[joint.tie]] each parameter several patterns share, its members "k: the address of the first value its free parameter moves" in pattern k's file. Each pattern's file is an ordinary control file of one pattern: its data, instrument, phases with their own scale, profile and cell, and free parameters; it opens on its own. Options set aside: one file with every pattern in it (its [pattern] and [[phase]] tables would repeat, which TOML does not allow, so a TOML reader could not read it), a new extension (the app and the editor know .drc), and keeping FullProf's NPATT and W_PAT in a .pcr the app rewrites (the app's 0.44.0 way: it held FullProf's keywords, and nothing set in the window outlived the document).

Built (joint): Joint::save, control_files, open, is_joint_control; set_weight (one pattern's weight against the others' as they are, all normalised to sum 1, as W_PAT is read), set_weights, set_refined (the last refined pattern stays refined), set_contribution (a phase out of a pattern: its model relieved of it, its parameters there gone; into a pattern: the phase as the first pattern it contributes to has it, its atoms' scattering taken again for the pattern's radiation, that pattern's free parameters of it with its structure's tied and its scale, profile and cell the pattern's own; the joint parameters tied again by each free parameter's first value, Field::with_phase, Field::is_structural). Each pattern keeps its refinement as the control file writes it, its set built from it. drs joint reads a joint control file as a multi-pattern .pcr, --save writes it; drs refine and drs results take one.

Test joint_control (3): scor_rxn written and read back, the same patterns, weights, flags, phases, joint parameters, models to the bit and objective, the text written again the same, each pattern's file opened alone, two cycles refined the same as from the .pcr (values to \(10^{-9}\)); the weights normalised and Q following them, a pattern calculated only leaving Q; \(\mathrm{Sr_{2}Co_{2}O_{5}}\) out of the X-ray pattern (its Ycal the old one less the phase's curve to \(10^{-9}\)) and back (its X-ray scattering the same, its atoms tied to pattern 1's again, its scale, profile and cell its own).

core/src/joint.rs · core/src/jacobian.rs (with_phase, is_structural) · core/tests/joint_control.rs · ecosystem/data/files.yml (pattern_control) · app/REQUESTS.md 43 · L322
L3282026-10-18work

App 0.44.0: joint refinement in Refine (P06); request 42 used; §8's shared table with the columns remembered

P06 (core 0.57.0 joint): a multi-pattern .pcr opens in Refine as a joint refinement read by the core: the patterns as rows (radiation, weight, refined or calculated only, phases, points, parameters, Rp, Rwp, Rexp, \(\chi^2\)), the chosen pattern's plot with its phases' ticks and difference, the joint \(\chi^2\), the joint parameters with su's and the patterns each moves. A weight or flag set in the window is read again by the core (W_PAT normalised). Run (off the main thread), Stop, Accept (the core's results file, P41), Discard. The .pcr is never written; request 43 asks the core for setters and a control file of our own for several patterns. Request 42 met by core 0.57.1 and used. §8: Free Parameters, Parameter Impact, the inspector's impact, restraints, the Refinement panel's parameters, the parametric fit, Data's peaks and formats are the shared table, the columns chosen kept by table. Self-check under PL36: fresh HOME 152 OK 0 FAIL, its relaunch 152/0 with the column choice remembered, \(\mathrm{Tb_{2}Pd_{2}Sn}\) 115/0, its relaunch 140/1 (Plots' own .drv check); Josu's state untouched. Rust 86, node 204 tests.

branch app 4ecb3a7 · app/ui/modules/refine/joint.js · app/REQUESTS.md 42, 43 · L322, L326
L3272026-10-18work

Publish stream: drs-publish 0.23.0, P06's joint refinement read; P43's volume su; F1 of every pattern

DESIGN.md §33, checked on the core's three joint runs (pbb, scor_rxn, prbaco). A joint file is read a pattern at a time through the core's own reader: F1 of one pattern (pattern = k), T1 with the model's rows once and a row group per pattern (probe, λ, range, weight, points used, R_p, R_wp, R_exp, \(\chi^2\), R_I, R_F), the joint \(\chi^2\) and FullProf's over Bragg points in the note, D2 with a block per pattern; nothing computed in Publish. P43: the CIF's volume su is the core's one; a sequential point's volume stays the series table's, unrounded (a fit to the CIF's rounded volumes moves a by 0.1 su). New: F1 of every pattern (pattern = "all"), a panel each with its own 2\(\theta\) axis. Fixed: a non-integer count in a formula (Co15O41.697Sr18). Under nice -n 15, CARGO_BUILD_JOBS=8. Self-check under PL36, 160 s: 167 lines, 131 OK, no FAIL; Josu's app files unchanged. Tests: drs-publish 53, the app's 202 node and 85 Rust pass. Shell lines in P45 (Cargo.lock, the Publish manual's paragraph on a joint refinement).

branch publish 2e9bcbe · publish/REQUESTS.md P43, P45 · DESIGN.md §33 · L322, L326
L3262026-10-18work

The app's request 42 (a parametric fit's coefficient at 0) and Publish's P43 (one cell-volume su): core 0.57.1

Request 42. parametric::fit took each coefficient's derivative by central differences with a step of \(10^{-6}\) times the coefficient: a coefficient that fits to 0 (a volume constant in T, app 0.43.0's self-check) left a step of \(10^{-14}\), a column of rounding, and the fit stopped as singular. The derivatives are now the parsed equation's, exact (Equation::derivative, forward mode through every operation and function). Test parametric_coefficient_at_zero: three equal volumes give a = 0; five near-flat points give the straight line's closed-form slope to \(10^{-9}\) of its su and its su to \(10^{-6}\); every function's derivative against central differences to \(10^{-7}\). The fits against SciPy (parametric_fits) pass as before.

P43. A cell volume's su was the covariance's in the series table (sequential::cell_volume) and the cell's su's alone in the results file's _cell_volume (cif_write::volume_su, the correlations left out): la0175's 0.0134 and 0.012 ų. One function now gives it, ParameterSet::cell_volume (the covariance, the cell's correlations and ties included): the results file's and the deposition CIF's _cell_volume (la0175 245.350(14), the su rounded up as every su the core writes), the series table's, and the run summary's ("volume … \(\pm\) …"); the cell's su's alone only without a covariance. series_outputs checks each point's _cell_volume against the table's. And drs sequential --leave-out-unmodelled (a .pcr) leaves out the phases the core does not model (magnetic, |Jbt| = 1) and says which (Pcr::leave_out_unmodelled): FullProf's la0_174.pcr on la0175–la0176 now runs from the command line.

core/src/parametric.rs · core/src/refinable.rs (cell_volume) · core/src/cif_write.rs · core/src/listing.rs · core/tests/sequential.rs · app/REQUESTS.md 42 · publish/REQUESTS.md P43
L3252026-10-18work

App 0.43.0 on core 0.57.0 installed: the first gated run with no FAIL since the self-checks took their own identifier

The merging session's gated run (main cb99321, niced): core 339, Publish 52, app Rust 85 and UI 202 tests pass; built; three self-checks on a copy with its own identifier (PL36), its preferences domain emptied first, each to its quit: \(\mathrm{Y_{2}O_{3}}\) 142 OK 0 FAIL, the relaunch 160 OK 0 FAIL (P46: 2 windows and 3 tabs reopened), \(\mathrm{Tb_{2}Pd_{2}Sn}\) 128 OK 0 FAIL; Josu's files and preferences unchanged. Installed in /Applications; 0.42.1 to the Trash. Josu's app now has version 2's Pawley fit (P36), sequential and parametric refinement with the series' table and CIF (P54, P59), and the guided refinement (P55).

scratchpad self043a/r/b · L321, L324
L3242026-10-18work

App 0.43.0: version 2 in Refine, the Pawley fit (P36) and a series of patterns with its table and parametric fit (P54, P59); L321's tab group fixed

Refine ▸ Pawley Fit for Phase… (core 0.55.0 pawley::setup): the phase's reflection intensities an item of their own, refined with the cell, profile and background, the coincident reflections marked as tied (pawley::ties). Refine ▸ Refine a Series… (core 0.56.0 sequential::run, off the main thread): data files refined in turn from one control file, each from the one before or from the file's, progress per cycle, Stop after the cycle in progress. The Series view: a row per pattern against its temperature or number, one value at a time with its su (cell volumes from sequential::cell_volume), plotted with su bars; a parametric fit to an equation in T (parametric::fit), its coefficients with su's and the core's curve; Series as CSV… and as CIF…. L321's tab group: a window of one tab made the quit record its project's folder twice; each window is now recorded once. Request 42 to the core: parametric::fit stops as singular when a coefficient fits to 0 (its derivative step). Self-check under PL36: fresh HOME 145 OK 0 FAIL, its relaunch 145/0, \(\mathrm{Tb_{2}Pd_{2}Sn}\) 115/0, its relaunch 130/1 (Plots' own .drv check); Josu's state untouched. Rust 85, node 202 tests.

branch app fa4c16d · app/ui/tests/v2.test.mjs · app/REQUESTS.md 42 · L315, L317, L321
L3232026-10-18work

Publish stream: drs-publish 0.22.0, the core's Pawley fit and sequential series read; P54's parametric fits

DESIGN.md §32: the items built for P36 and P54/P59 checked on the core's own files. Pawley (\(\mathrm{MgAl_{2}O_{4}}\) by drs pawley): the method told by its intensity parameters, \(\sigma\)(I) the covariance's, the 14 groups of tied reflections counted in T10 and marked in T11 (P39 filled), D9 the core's .int, T10 the cell parameters the system leaves free. Sequential (\(\mathrm{LaMnO_{3}}\) on D20, 6 patterns by drs sequential): [source] sequential splits the core's series CIF into its points for F4, F5, F7, T7 and D8; the cell volume's su the core's covariance one from the series table (P43 asks the core for one su everywhere). New: P54's parametric fits, the core's parametric::fit, as F7's line and T12. Under nice -n 15, CARGO_BUILD_JOBS=8. Self-check under PL36, 220 s: 171 lines, 136 OK, no FAIL; Josu's app files unchanged. Tests: drs-publish 52, the app's 199 node and 83 Rust pass. The stream's Log: line said L322, taken by the core's P06; renumbered L323.

branch publish 46805a8 · publish/REQUESTS.md P39, P43 · DESIGN.md §32 · L315, L317
L3222026-10-18work

P06, joint refinement, against FullProf's runs pbb, scor_rxn and prbaco (L320), with Publish's P41: core 0.57.0

Core 0.57.0; tests joint_refinement (5), and the joint runs taken into fullprof_profiles, berar_factor, output_names, fullprof_outputs, control_file, model_text and phase_curves (pattern by pattern), and counted apart in fullprof_summary (their N − P + C is FullProf's per-pattern P, below).

The file. A multi-pattern .pcr (NPATT, W_PAT; FullProf_Manual.pdf pp. 64–66, 88) is split into one single-pattern text per pattern (pcr::split_patterns, pcr::read_all): its own Job, Ipr, wavelengths, range, background, excluded regions, scattering factors, zero and background lines, and of each phase that contributes to it its own Irf, Npr, Pr1–Pr3, Brind and profile block, the atoms shared. FullProf writes NPATT also under a first line COMM (prbaco.pcr), so the NPATT line decides the format. Every pattern of the three runs is calculated and compared with its CODE_calc_k.prf in fullprof_profiles (76 compared, 6 more): pbb's two exact, \(\chi^2\) 3.2359 and 2.9762 against 3.236 and 2.976; scor_rxn's neutron pattern exact (\(\chi^2\) 3.7491 against 3.749).

Found in the runs.

  • FullProf's strain model 8 (Str = 0, hexagonal, manual p. 29) is Stephens' quartic form of the trigonal and hexagonal classes, S_400 = Saa\(^{2}\), S_004 = Scc\(^{2}\), S_112 = 2 Saa Scc Cac: with the manual's \(10^{-3}\) in its P scor_rxn_calc_1.prf is missed by up to 290%, with \(10^{-4}\) (\(\sigma\)² in \(10^{-8}\), as the quartic form) met to rms 7·\(10^{-5}\). Imported, refined (Str1–Str3 as S_400, S_004, S_112), exported back as model 8, named (Strain1).
  • Synchrotron polarisation (Ilo = 3) with Cthm = 0: FullProf takes cos\(^{2}\)2\(\theta\)_M = 1, 1 − K + K cos\(^{2}\)2\(\theta\); with 1 − K alone every peak of prbaco's BM16 pattern was 0.90 of FullProf's; now rms 2.7·\(10^{-4}\) (the dictionary's note).
  • A step that ends on an excluded region's edge does not reach into it, to the data's rounding (147.95° before 148.00°, step 0.05, calculated by FullProf). The .prf shows which points FullProf leaves out: their Yobs − Ycal is printed as the plot's offset alone (pbb's first point 10.00°, at a region's end, is left out, as the rule says).

The engine (joint). Each pattern its own model, data and free parameters; the patterns' parameters with the same FullProf number are one joint parameter, each pattern moving its fields by its own multipliers. The quantity minimised is Q = Σ_k W_k S_k: from pbb.pcr.in, where FullProf's first cycle shifts nothing, its minimum lies within 0.25 FullProf esd of FullProf's values for 51 of 63 parameters, every atom's within 0.11, where weighting the patterns' \(\chi^2\) (Σ W_k S_k/(N_k − P_k)) put the atoms up to 2.8 esd away (Pb's β33); options set aside: that weighting, and P_k as FullProf counts it (its N − P + C implies 30 of pbb's 63 parameters per pattern, 22 and 21 of scor_rxn's 44, 14 of prbaco's 31: not reproduced; the core's P_k is the parameters that move pattern k). The esd's: by default √(C Q/(N − P)), for one pattern the single engine's (test one_pattern_is_the_single_engine: values to \(10^{-9}\), esd's to \(10^{-6}\)); FullProf's are √(C Σ_k W_k \(\chi^2\)_k,Bragg) (Esd::FullProf), its single-pattern rule over the patterns, reproduced to 0.3% for every neutron and structural parameter of pbb (the TCH cell at q = 1.95, the factor of L053), about √2 times the default for two equal weights. FullProf's global \(\chi^2\) is Σ W_k \(\chi^2\)_k over the Bragg points (0.5·3.34 + 0.5·3.00 = 3.17); the one it prints at the end, 3.22, takes the parameters off twice (Σ W_k S_k/(N_k − 31 − 63) = 3.225).

Frozen, for Josu (L037):

  • pbb: the engine's minimum is below FullProf's end (Q 13067.2 against 13134.9) where both calculate the pattern alike: FullProf stops where its own sum is not at its minimum (believed: its derivatives of the X-ray pattern's widths; U V W of pattern 2 2.2–3.8 esd off). 12 parameters beyond 0.25 esd (largest 3.82), 7 esd's 3–13% off.
  • scor_rxn: its X-ray pattern's strong points 0.19% above FullProf's (Bérar–Baldinozzi asymmetry with axial divergence, which no other run combines; rms 9.95·\(10^{-4}\)), and phase 1's scale one parameter for both patterns (codeword 11: one shift for scales of 9.2·\(10^{-4}\) and 5.8·\(10^{-7}\)): the minima differ (\(\chi^2\) 2.897 against 3.018); 17 beyond 0.25 esd (largest 4.21), 14 esd's off (largest 14%).
  • prbaco: FullProf's own refinement rose (11.4, 13.4, …, 9.32) and was not saved; the engine falls every cycle and is at 8.56 after the same five. Its calculation: the synchrotron pattern's window edges (Wdt 35, 614 points up to 0.46, \(\chi^2\) 20.342 against 20.38); the neutron pattern's Σ is 0.13% below FullProf's without the cut at the .out's S(1/d)max and 0.9% above with it, which its .prf's Ycal follow: not found.
  • The Bérar factor of every pattern compared (10 of 12) within 0.016% of FullProf's.

P41, the results file of a joint refinement (Joint::results, joint::read_results; drs joint, and drs refine/drs results on a multi-pattern .pcr): an overall block with the joint \(\chi^2\) (all points, and the Bragg points: FullProf's global), R factors (each pattern's sums weighted by W_k), Q, the patterns with their blocks, data files, weights and refined flags, the phases' blocks, which phase contributes to which pattern, the joint parameters with su's and the source .pcr; a block per phase (|phase n), its diffractograms a loop; a block per pattern (|pattern k) with its weight, agreement (R_p, R_wp, R_exp, \(\chi^2\) over all and over the Bragg points), its phases' scale, R_Bragg, R_F and cell, then the profile and the reflections, each with its pattern id (_drs_refln_pattern_id) and joint phase. results::write now writes its pattern block through the same function; its files are unchanged to the byte (but the cycles' seconds).

Not in a joint refinement yet: Le Bail phases, restraints, the single engine's steering and plans, a control file of our own for several patterns (the .pcr is read; to decide with Josu).

core/src/joint.rs · core/src/pcr.rs (split_patterns, read_all) · core/src/powder.rs (hexagonal_strain, Ilo = 3) · core/tests/joint_refinement.rs · manuals/fullprof/runs/{pbb,scor_rxn,prbaco} · FullProf_Manual.pdf pp. 29, 53, 64–66, 88 · publish/REQUESTS.md P41 · L037, L053, L316, L320
L3212026-10-18decision

App 0.42.1 on core 0.56.0 installed on Josu's word with one FAIL: a tab group of 3 came back as 2 after a relaunch

The merging session's gated run (main 8a1c0a7, niced): core 334, Publish 51, app Rust 83 and UI 199 tests pass; built; three self-checks on a copy with its own identifier (PL36), its preferences domain emptied first, each to its quit; Josu's files and preferences unchanged; P46 reopened 3 windows and 3 tabs. \(\mathrm{Y_{2}O_{3}}\) 128 OK 0 FAIL; \(\mathrm{Tb_{2}Pd_{2}Sn}\) 130 OK 0 FAIL; the relaunch 153 OK 1 FAIL: "§1 restoration after the last session: the tab group of 3 rejoined: this window's group holds 2", the inspector's window and the split's width back as kept. Josu chose to install now rather than wait for a fix and another hour's run, as with 0.38.0 (L291): his app goes from 0.39.0 to 0.42.1. The tab group's FAIL goes to the app stream as known. The old copy is in the Trash.

scratchpad self0421a/r/b · L291, L319
L3202026-10-18work

FullProf's references for P06, joint refinement: pbb, scor_rxn and prbaco run on the Mac, on branch fullprof-p06

The three multi-pattern files L316 named, run by manuals/fullprof/runs/joint_runs.py with FullProf (fp2k, February 2025 macOS build) in both passes, each pattern's output flags set. Every case gives one .prf and one .hkl per pattern in its calculate pass:

  • pbb (\(\mathrm{PbSO_{4}}\), D1A neutrons + X-rays, one phase): global user-weighted \(\chi^2\) (Bragg) 3.22 after the refine pass, 3.17 in the calculate pass;
  • scor_rxn (\(\mathrm{Sr_{2}Co_{2}O_{5}}\) + CoO, neutrons + Cu Kα, two phases in both patterns): 3.01 and 3.01;
  • prbaco (\(\mathrm{PrBaCo_{2}O_{5}}\)₊δ, BM16 + D2B): 9.32 after the refine pass, 11.4 in the calculate pass (the calculate pass's outputs belong to the refined CODE.pcr's values, its one cycle moving them, L035). Kept on branch fullprof-p06 (5c16da3), not on main, so that the core's frozen counts over manuals/ do not change until the core takes the runs in (L222).
branch fullprof-p06 5c16da3 · manuals/fullprof/runs/joint_runs.py · L035, L222, L316
L3192026-10-18work

App 0.42.1: what kept 0.41.1 and 0.42.0 from being installed (L312), each fix seen in the app; the relaunch's hang found

L312's FAILs fixed and seen in the app:

  • spelling, the app's: a window behind another had no first responder; the actions now go to the window's web view;
  • §1 on the relaunch, the app's: the bottom panel goes left of the inspector only when that is the layout's full-height last column, else at the layout's edge; the sidebar and inspector dock back at the edges; dock.element, not in dockview's API, replaced by the container (which also mends ⧉ floating a group of tabs);
  • P55: Stop during Run the Plan to the End ends the plan, also between steps; the plan says why a step did not run;
  • the time slider and P48, the check's: its tab brought forward; P48 waits for states and takes a free site. The relaunch's hang (L314): with a full-screen tab group on screen or just left, AppKit never finished building the next window; a tab group now comes back without full screen, a lone window full screen. A window of one tab no longer reopens the project's panel windows. Under nice -n 15, CARGO_BUILD_JOBS=8, the app niced, one instance. Self-check under PL36 in four runs: fresh HOME with \(\mathrm{Y_{2}O_{3}}\) 136 OK 0 FAIL, its relaunch 141/0, \(\mathrm{Tb_{2}Pd_{2}Sn}\) 115/0, its relaunch 140/1 (Plots' own check of its saved set on a relaunch, left to Plots); every run quit by itself; Josu's 5 files unchanged. Tests: 83 Rust, 199 node.
branch app 6667f18 · L312, L314
L3182026-10-18work

Publish stream: drs-publish 0.21.0, for version 2: P54's series of refinements, P55's guided run by its steps; P06 designed

DESIGN.md §31, without new core work. F7 from refinements: one to four refined parameters against the series' variable, a panel each, each point's value and su the results file's, the phases as bands, labelled by the parameter dictionary; T7 of parameters, a column per point. T9 takes a stage per plan step the results file records (core 0.54.0 met P37), named by the core; example y2o3-guided-tfg, \(\mathrm{Y_{2}O_{3}}\) refined by drs plan and drs results --plan: 8 stages over 23 cycles, \(\chi^2\) 1.1922 to 1.1495. P06's items designed (F1 per pattern, T1 a column per pattern, the pdCIF a block per pattern); P41 asks the core for the results file they need. Under nice -n 15, CARGO_BUILD_JOBS=8. Self-check under PL36 (held until the app stream's bundle had quit), 270 s: 175 lines, 146 OK, no FAIL; Josu's app files unchanged. Tests: drs-publish 51, the app's 199 node and 83 Rust pass. The stream's Log: line said L315, taken by the core's P36; renumbered L318.

branch publish d74445c · publish/REQUESTS.md P41 · DESIGN.md §31 · L310, L315
L3172026-10-18work

Core 0.56.0: sequential and parametric refinement (P54) with a series' table and CIF (P59), against FullProf's sequential D20 run

P54 and P59's series outputs, while P06 waits for its runs (L316). The reference is FullProf's own sequential refinement in the material: manuals/fullprof/1-WinPLOTR/2_FP_sequential_refinements/LaMnO3_D20, 71 D20 patterns of \(\mathrm{LaMnO_{3}}\) from 68.9 to 168.1 K, each refined from the values the one before ended at (la0.log), each refined state in la0_NNN.pcr, the values and \(\sigma\)'s in la0.seq. Built:

  • sequential: each pattern read as the control file reads its own (reader, data operations), its temperature from its header (FullProf's PARVAL, all 16 equal); run refines them in turn, each from the one before (FullProf's sequential mode) or from the control file's values (Start::Each), a plan run on every pattern; each phase's cell volume with its su from the covariance; series_csv and series_cif (P59; the file dictionary's series_table and series_cif); drs sequential.
  • parametric: an equation the user writes (numbers, the variable, coefficients by name, + − × / ^, functions), fitted to a series column by weighted Levenberg–Marquardt, the su's √(C \(\chi^2\)_ν); drs parametric. Test sequential:
  • The chain: the run has a magnetic phase (Jbt = 1) the core does not model; above the Néel point FullProf refines its moment to 0.3 μB, its sign changing between patterns (noise; 3.4 μB at 68.9 K), so the 16 patterns above it (la0175–la0190, 146.1–168.1 K) are refined from FullProf's la0_174.pcr without it. Every value is FullProf's within its \(\sigma\) but one; \(\chi^2\) is 0.29–0.71 % below FullProf's (0.22 % of it at FullProf's own values of la0175, where the core's is 3.3298 against 3.3373: the moment left out); the cell volume is FullProf's within FullProf's \(\sigma\); √C (each esd over the root of its \(\chi^2\)) is FullProf's to 2.6 % but the cell's, 1.97–2.02 times FullProf's (FullProf halves the cell's esd's, L053, L113; its volume \(\sigma\) also leaves out the cell's correlations, 0.19–0.26), and the two end background points'. la0186 is compared on its values only: FullProf's fit ended at \(\chi^2\) 4.32 when its magnetic moment's shift jumped to 55\(\sigma\) in cycle 16, after cycles 4 to 15 at 3.25, the core's \(\chi^2\).
  • Frozen for Josu: the last background point (146.92°, the background flat beyond it to 150.3°, the same in both: FullProf's .prf background is the core's less its offset of 39.85) is the one value beyond FullProf's \(\sigma\) (up to 1.49\(\sigma\)) and its √C is 1/√2 of FullProf's to 1 % at every pattern (0.707–0.713); the core's would be FullProf's with the data beyond about 148.0° left out of its derivative (scanned on la0175), which no FullProf setting names. The second point (12.40°, next to the region excluded below 10°): √C 0.95–0.96 of FullProf's. Not settled.
  • The series' CSV and multi-block CIF read back point by point (block names unique, each point's results file).
  • Parametric fits: FullProf's own volumes and c of the 71 patterns fitted to V0(1 + a(T − 150) + b(T − 150)²), c0 + c1 T and V0 + A/(exp(\(\theta\)/T) − 1), against SciPy 1.18.1's curve_fit (absolute_sigma=False; tests/data/make_parametric_reference.py): \(\chi^2\) to \(10^{-9}\), every coefficient and su within \(10^{-4}\) of SciPy's su.
core/src/sequential.rs · core/src/parametric.rs · core/src/main.rs (drs sequential, drs parametric) · core/tests/sequential.rs · core/tests/data/make_parametric_reference.py, parametric_reference.txt · ecosystem/data/files.yml (series_table, series_cif) · manuals/fullprof/1-WinPLOTR/2_FP_sequential_refinements/LaMnO3_D20 (la0.log, la0.seq, la0_NNN.pcr, la0NNN.prf) · L053, L113, L316
L3162026-10-18finding

P06, joint refinement: no FullProf output of a multi-pattern refinement in the material; three runs named for the Mac

P06 is next in version 2 (several patterns in one refinement: X-rays with neutrons, a weight per pattern, which phase contributes to which pattern, patterns switched on or off). Its reference, by L037, is FullProf's own multi-pattern refinement, and the material has none: of its 26 multi-pattern .pcr files (FP_tests/, FullProf's multi-pattern format, FullProf_Manual.pdf pp. 66–88: no COMM on line 1; NPATT with each pattern's on/off flag; W_PAT; the job, output, wavelength, range, excluded regions and global parameters once per pattern; per phase, Contributions (0/1) to each pattern, p. 88, and for each pattern it contributes to its own Irf, Npr, Jtyp, scale, profile, cell and asymmetry, the atoms shared), none has an .out or .prf, and the "NPATT = 80" of the runs' .out files is FullProf's array size, not a multi-pattern run. The core's .pcr reader refuses the format (pcr.rs). Named for the merging session (manuals/fullprof/runs/joint_runs.py, both passes as run_fullprof.py, the output flags on every pattern's line, checked on the three files without FullProf): pbb.pcr (PbSO4, D1A neutrons + X-rays, one phase, W_PAT 0.5 0.5), scor_rxn.pcr (Sr2Co2O5 + CoO, neutrons 1.95 Å + Cu Kα, two phases in both patterns, each with its own cell per pattern: CoO a 4.257451 for the neutrons, 4.255837 for the X-rays), prbaco.pcr (PrBaCo2O5+δ, BM16 + D2B, background points in each pattern, Str = 1). Set aside: the PbSO4 + Ho2BaNiO5 files (pb_ho, pbho, pb_hob, p_p), the only ones with a phase that leaves out a pattern (Contributions 1 1 0 and 0 0 1), because Ho2BaNiO5 is magnetic (Jbt = 10), which the core does not model; a phase left out of a pattern is to be checked on a variant of scor_rxn.pcr (CoO out of the X-ray pattern) that the core's .pcr export writes once it reads and writes the format, since writing it by hand means renumbering CoO's codes 38–44 (a hole stops FullProf, L071). gabriela.pcr has a TOF pattern (P05) and test.pcr names data on its author's Windows disk. The core waits for the runs before building P06.

manuals/fullprof/FP_tests/{pbb,scor_rxn,prbaco,pb_ho,p_p}.pcr · manuals/fullprof/FullProf_Manual.pdf pp. 66, 88 · manuals/fullprof/runs/joint_runs.py · core/src/pcr.rs · L037, L071
L3152026-10-18work

Core 0.55.0: the Pawley fit (P36), against FullProf's Le Bail fits; what Publish reads of it (P39)

P36, version 2: a phase's reflection intensities refined by least squares with the cell, the profile and the background (Pawley 1981). FullProf has no Pawley fit (its manual names the method beside Le Bail's, p. 14), so no FullProf run can be its reference and none was asked of the Mac; GSAS-II has one (PWLref), but its profile functions are not the core's. The references are a synthetic pattern and FullProf's three Le Bail fits of the same data and profile. What was built:

  • Field::Intensity(phase, k), the k-th reflection of a list phase that is not a Le Bail phase, named by the dictionary's reflection_intensity (phase1.reflection_intensity.1_-1_0), \(\ge 0\), its derivative column the peak's profile (the pattern is linear in it); located in the control file's reflection_intensity rows.
  • pawley: coincident finds the reflections that coincide for every cell the group allows from the metric averaged over the group's rotations, without the crystal system's name; setup makes a phase a Pawley phase (a structure phase takes its list to 5° past the data), starts the intensities from Le Bail's partition, ties the coincident ones with equal |F|² (intensities in proportion to multiplicities, GSAS-II's GetPawleyConstr), frees every reflection whose window reaches the data, drops the scale (the list has none) and turns off the singular guard (\(\mathrm{LaMnO_{3}}\)'s 365, 482 and 127 overlap by accident near 150°, 1/d\(^{2}\) within 3·\(10^{-4}\); the combination is left out of the cycle, not the run stopped). drs pawley <file> --to <file.drc>.
  • Above 120 parameters the engine decomposes the normal matrix by Householder's reduction and the implicit QL iteration (EISPACK tred2/tql2, JAMA's port) instead of Jacobi's rotations, which took half a minute a cycle for \(\mathrm{LaMnO_{3}}\)'s 422; below 120, where every frozen FullProf comparison was made, the rotations are kept to the last bit. Unit test: on a \(150 \times 150\) matrix with a near-null direction, the same eigenvalues to \(10^{-11}\) of the largest. \(\mathrm{LaMnO_{3}}\)'s Pawley fit now takes 2 s a cycle.
  • The results file: a Pawley phase's I meas is the refined intensity and its \(\sigma\) the su from the covariance (the tied ones in proportion); the ties in _drs_pawley_tie_*, read back as Results::pawley_ties. The .pcr export writes the intensities fixed, with a note. Test pawley:
  • The spinel's own pattern, from known intensities: every intensity, the cell and W recovered to \(10^{-6}\) from Le Bail's partition of equal intensities with a and W moved; each intensity's column equal to the pattern's change.
  • The coincident reflections against GSAS-II's rule pair by pair on 14 groups (m-3m, m-3, 4/m twice, 4/mmm, 6/m, 6/mmm, -3, -3m and both rhombohedral settings, mmm, 2/m, -1; 22 million pairs). GSAS-II also ties, in -1, 2/m and mmm, reflections whose d differ by less than \(10^{-4}\) (accidental overlaps that part as the cell is refined): the core does not (option set aside: they stay apart only at one cell).
  • FullProf's Le Bail fits: \(\mathrm{LaMnO_{3}}\) (LBF_lmopr, 414 reflections, the 8 parameters FullProf refined and the intensities), the spinel (MgAl2O4s_LeBail_conv, 58 reflections, 14 groups tied) and Co-YBCO (Cocuc_LeBail_conv, 83), from FullProf's last values and intensities, refining what FullProf varied (none in the last two). The weighted sum of squares is below Le Bail's in all three (3045 against 3480; 3886 against 4015; 54647 against 66257). \(\mathrm{LaMnO_{3}}\)'s parameters are FullProf's within the Pawley fit's esd, which for the cell is 3.5 to 3.9 times FullProf's (Le Bail's covariance leaves the intensities out); frozen: b and c beyond FullProf's own esd (3.2 and 1.4 of it, 0.8 and 0.3 of the Pawley fit's). The isolated reflections' intensities differ from FullProf's by a median of 0.27\(\sigma\) (\(\mathrm{LaMnO_{3}}\)), 0.73\(\sigma\) (spinel), 2.0\(\sigma\) (Co-YBCO), the Pawley fit below Le Bail in 47 of 56, 20 of 28 and 36 of 37: the two estimators part where the model's profile misfits the peaks, Le Bail's partition being the background-subtracted area under the window and Pawley's the weighted least-squares value. Evidence: ten Le Bail partitions at the Pawley solution give FullProf's intensities back to a median of 0.04\(\sigma\) (largest 0.73\(\sigma\)) for \(\mathrm{LaMnO_{3}}\) and 0.17\(\sigma\) (1.49\(\sigma\)) for the spinel; one partition already moves \(\mathrm{LaMnO_{3}}\)'s 2 0 0 from 0.14 to 1.11 (FullProf 1.52). Frozen: 2, 4 and 19 isolated reflections beyond 2\(\sigma\).
  • For Josu (a P35 matter, not built on): the core's Le Bail partitions from FullProf's final intensities of Cocuc_LeBail_conv do not stay at them (median 0.97\(\sigma\), largest 6.4\(\sigma\) after ten), where FullProf's fit varied no parameter, ended at \(\chi^2\) 25.7 and swung between 24.5 and 25.7 within its last cycle. Whether FullProf's partition differs from the core's on that file, or its fit had not settled, is not decided; fullprof_le_bail does not yet read these two FullProf runs (L055's files, run since).
  • The results file as Publish reads it, and each intensity located in the control file. Publish's P39, answered: (1) yes, the intensities are refined parameters named phase{n}.reflection_intensity.h_k_l (the dictionary's id, which holds intensit), in _drs_parameter_* and the free parameters' loop; (2) yes, _refln_intensity_meas is the refined intensity and _refln_intensity_sigma its su from the covariance (tied reflections: the parameter's su times its multiplier); (3) the ties are in the results file's _drs_pawley_tie_id, _phase_id, _index_h/k/l and _multiplier (Results::pawley_ties), and in the control file as one parameter of several addresses. Where Pawley's fit differs from Le Bail's for Publish: \(\sigma\)(I) is the covariance's, not the counting statistics', and a weak reflection may be 0 (held at its limit).
core/src/pawley.rs · core/src/jacobian.rs (Field::Intensity) · core/src/lsq.rs (householder_ql, JACOBI_LIMIT) · core/src/results.rs · core/src/main.rs (drs pawley) · core/tests/pawley.rs · core/tests/field_labels.rs · ecosystem/data/parameters.yml (reflection_intensity, pawley1981) · manuals/fullprof/runs/MgAl2O4-LeBail, Co-YBCO-LeBail · 7-FullProf-Tutorial-SymmetryModes/LaMnO3/LBF_lmopr.out · GSAS-II GSASIIstrIO.GetPawleyConstr, GSASIIstrMath.penaltyFxn · publish/REQUESTS.md P39
L3142026-10-16work

App 0.42.0: §1 state restoration, macOS tab groups, panel windows, full screen and split sizes come back with the windows

At quit each window records full screen, its macOS tab group in tab order with the tab in front, and its panels in windows of their own with their frames; at launch the tab groups are rejoined with the same tab in front, full screen comes back on that tab, and each project window detaches its panels again where they were. A split's groups come back at their kept sizes (groupSizes in .drs/project.json). §8 and §11 not started. The app stream had the 0.42.0 prompt before the merging session's L312 list, so L312's six FAILs are not yet addressed. Two self-checks of three runs under PL36, niced, Josu's 5 files unchanged both times. The first ran beside the Explore worktree's instance (the script waited); its relaunch showed the tab group of 2 rejoined, the inspector's window at its frame and the split kept; the check's order and its full-screen target were wrong, fixed. The second: Y2O3 123 OK 2 FAIL (P48 and the spelling toggle); Tb2Pd2Sn 133 OK 0 FAIL (66 min, the Mac probably asleep); the relaunch ended after 56 lines at cycle 6 of 10 of a refinement, through the app's quit path (windows.json rewritten, ExitRequested with no code), no crash report, cause not found. Full screen's restoration not yet confirmed in the app. Tests: Rust 83, node 196.

branch app 38c5906 · app/ui/tests/restore042.test.mjs · L312
L3132026-10-16work

Publish stream: drs-publish 0.20.0, for version 2: a whole-pattern decomposition's tables and figure (P36)

Designed from FullProf's manual's whole-pattern decomposition (Le Bail's iterative partition, Jbt = 2; Pawley's least squares with constraints), built and tested on the core's Le Bail fit and read the same way for Pawley's when the core gives P36 (publish/REQUESTS.md P39 says what Publish will read). T10, the fit: method, crystal system, space group, cell with su's, range, reflections and their clusters, the agreement. T11, the extracted intensities: h k l, m, d, 2\(\theta\), FWHM, I, \(\sigma\)(I), the clusters of overlapped reflections (the core's lebail::clusters) ruled off. F9, the intensities as sticks with \(\sigma\) bars, the clusters in alternating colours. D9, FullProf's .int (the core's int_file) for structure solution. Example lamno3-lebail-tfg (\(\mathrm{LaMnO_{3}}\)'s Le Bail fit by core 0.53.0: 414 reflections in 122 clusters). Under nice -n 15, CARGO_BUILD_JOBS=8. Self-check under PL36: 144 lines, 118 OK, no FAIL; Josu's app files unchanged. Tests: drs-publish 50, the app's 193 node and 83 Rust pass.

branch publish cd6b33d · publish/src/decomposition.rs · publish/REQUESTS.md P39 · FullProf manual (Jbt = 2)
L3122026-10-16finding

App 0.41.1 on core 0.53.0 not installed: six FAILs of the shell, three of them the fixes made after the app stream's own check

The merging session's gated run (main ce8489b, niced): core 323, Publish 49, app Rust 83 and UI 193 pass; built; three self-checks on a copy with its own identifier (PL36), each to its quit; Josu's files and preferences unchanged; P46 reopened 2 windows and 3 tabs. Explore's relaunch FAIL of L309 did not appear. The six:

  • spelling, in all three runs: "Check Spelling While Typing … not taken", the action reaching no responder; every run's self-check opens What's New, and Explore found 8 shell FAILs on a first launch where What's New opens by itself (L311): the key window is likely not the Editor's;
  • §1 on the relaunch: View ▸ Bottom Panel on the Right not beside the main group (60,68 \(98 \times 219\));
  • P55's Run the Plan to the End: steps 1 and 2 accepted, step 3 stopped as "converged", the plan's result left pending;
  • services: Refine's run on the time slider: no slider. The last three are the fixes app 0.41.1 made after its own self-check (L309), seen in the app here for the first time. The self-check domain eus.ehu.diffrefstruct.selfcheck.merge holds WebContinuousSpellCheckingEnabled = 0 from these checks; Josu's domain is untouched. Installed stays 0.39.0. Passed to the app stream.
scratchpad self041a/r/b · app/src-tauri/src/native.rs spelling_send · L309, L311
L3112026-10-16work

Explore stream, thirteenth step: L309's relaunch FAIL was the services check's, fixed; a phase with no structure said once; the dragged point in the pair view (E20b)

Main (ce8489b) fast-forwarded. L309's "0 of 0 parameters" on 0.41.1's relaunch: the relaunch refined a Le Bail selfcheck.drc whose 101 free parameters are all background points, which Explore leaves out (W13), so the traces were rightly empty and the check, with nothing to choose, failed. Explore was right, the check wrong: such a run is now checked with the background points included (and says so), a run with none free is "not run". The core's refusal of a 3D scene for a Le Bail phase ("the phase has no structure") is kept for that state and said once, not asked at every redraw. The pair view's dragged point (E20b) on explore_point as the map's, with the pattern, difference, \(\chi^2\)ν and Rwp beside it. The compare check takes the newest .drc version, not a FullProf .pcr kept by Revert To, "not run" when there is none. Node tests 196 pass. One self-check under PL36 (eus.ehu.diffrefstruct.selfcheck.explore, niced, Josu's home from dscl, his preferences and four app files the same before and after): Y2O3 105 OK 9 FAIL, Explore's lines OK but compare (fixed after, tests only), 8 of the shell's on a first launch where What's New opens by itself; the relaunch 124 OK 1 FAIL, the shell's §1.

branch explore d347c44 · app/ui/modules/explore/index.js, README.md · app/ui/tests/explore_point.test.mjs · explore/REQUESTS.md E20b · L309
L3102026-10-16work

Core 0.54.0: a plan run recorded as one, each cycle with its step (Publish's P37)

Publish's T9 (L308) read a refinement's stages by grouping its recorded cycles by the parameters they freed, and named them by the control file's plan only when the counts matched; it asked the core for the plan's results file and each cycle's step (P37). Now:

  • lsq::run_plan(model, data, refinement, steps, on_step, on_cycle): the plan's steps ([[refine.step]], L110) run as one, each a session from the model the step before reached, the variables carried on, stopping after a step stopped by the user, diverging or singular, as drs refine --plan did; the refinement returned is the last step's with every step's cycles, numbered on over the steps, the pauses numbered the same way, and cycle_steps, each cycle's step number and name. drs refine --plan now runs through it (its cycle count is the run's, no longer the last step's).
  • The results file: the cycles' loop has _drs_cycle_step (1, 2, …) and _drs_cycle_step_name for a plan run, read back as Results::cycle_steps; a run without a plan writes neither.
  • drs results <file.drc> --plan: the results file, summary and listing of the control file's plan run. lsq::Cycle keeps its fields (the app builds it as a literal); the step is kept beside the history. Test guided_refinement::plan_run_into_the_results_file: \(\mathrm{Y_{2}O_{3}}\)'s guided plan (8 steps) run as one gives, to the last bit, the \(\chi^2\) of its steps run one by one; its 23 cycles are written and read back numbered 1–23, each with the step that ran it (1; 2–3; 4–5; 6–7; 8–12; 13–15; 16–20; 21–23) and that step's parameters, in order.
core/src/lsq.rs (run_plan, PlanStepRun, Refinement::cycle_steps) · core/src/results.rs · core/src/main.rs (drs results --plan) · core/tests/guided_refinement.rs · publish/REQUESTS.md P37 · L308
L3092026-10-16work

App 0.41.1: the three FAILs that kept 0.40.0 from being installed (L307); request 41 used and met

Passed by the merging session while Josu was teaching. The three FAILs of L307:

  • §1, the app's fault: the bottom panel took its own group for the tabs' group, and after a relaunch that group can hold file tabs, which moved with it. Now only the panel and its Log move, a floating panel is docked back first, and on the right it goes left of the inspector, right of every row of tabs.
  • §5, the check's fault: with no Refine tab, Data is in front and Data's inspector section is the right page; the check now expects the section of the module in front, or the module's own page where it has none.
  • §4, the app's fault: the diagnostics report's log lines quoted paths; status.js withoutPaths now leaves every path out. Request 41 (core 0.53.0, L305): editor_locate and editor_address_at take the file's folder (locate_in) and one reading gives every lookup (locate::all); selfcheck.drc is now placed; marked met. Under nice -n 15, CARGO_BUILD_JOBS=8. One self-check under PL36 in three runs, Josu's 5 files unchanged: Y2O3 125 OK 0 FAIL; the relaunch 120 OK 4 FAIL (§1 in a two-row layout, fixed and checked headless; the plan buttons' tooltips, fixed; P48's label, the check's, fixed; Explore's own services check "0 of 0 parameters" on the relaunch, in Explore's code, left to Explore); Tb2Pd2Sn 114 OK 1 FAIL (§5 for Plots, the check's, fixed). Those fixes not yet seen in the app. Tests: Rust 83, node 193 pass.
branch app eb35ceb · app/ui/status.js · app/src-tauri/src/modules/editor.rs · app/REQUESTS.md 41 · L305, L307
L3082026-10-16work

Publish stream: drs-publish 0.19.0, for version 2: the refinement's strategy as run (P55) and a series' CIF and CSV (P59, P54)

DESIGN.md §29, what version 2's features will need without new core work. T9, the refinement's strategy as run: its stages read from the cycles the results file records (each the cycles that freed the same parameters), named by the control file's plan when it matches, each with its cycles, parameters, \(\chi^2\), R_wp and (\(\Delta\)/\(\sigma\))_max, the core's; in the supplementary material too (the README's stages, strategy.csv with the parameters added and dropped by name). D8, a series' files: one multi-block CIF of every point's overall and phase blocks with its temperature or composition, and a CSV of every point's R factors, \(\chi^2\) and refined values with su's. Asked of the core (P37): drs results --plan and each cycle's step. Built and tested under nice -n 15, CARGO_BUILD_JOBS=8. Tests: drs-publish 49, the app's 188 node and 81 Rust pass. Self-check under PL36 (its domain emptied first, a scratch HOME, one instance, to its quit, 240 s): 143 lines, 116 OK, 2 FAIL, the shell's §5 and §4 (L307), none of Publish's; Josu's app files unchanged.

branch publish c2791a2 · publish/src/strategy.rs · publish/REQUESTS.md P37 · DESIGN.md §29 · L302, L307
L3072026-10-16finding

App 0.40.0 on core 0.52.0 not installed: three FAILs in its new conventions' checks

The merging session's gated run (main 9492c58, under nice -n 15): core 322, Publish 47, app Rust 81 and UI 188 tests pass; built; three self-checks on a copy with its own identifier (PL36), each to its quit; Josu's files and preferences unchanged; P46 reopened 2 windows and 3 tabs. Y2O3 110 OK 0 FAIL; the relaunch 120 OK 1 FAIL; Tb2Pd2Sn 113 OK 2 FAIL. The three, all in 0.40.0's new items (L303):

  • §1: View ▸ Bottom Panel on the Right left the panel below the main group (717,321 \(563 \times 219\)), not beside it;
  • §5: the inspector's ? with Refine in front opened Data's page (data § data-inspector);
  • §4: Advanced's diagnostics report (40 lines, the versions and a path in it) not copied whole. Installed stays 0.39.0. Passed to the app stream for 0.41.1; app 0.41.0 (L306), made meanwhile, does not touch them.
scratchpad self040a/r/b · app 0.40.0 (L303) · L296
L3062026-10-16work

App 0.41.0: P55 in Refine, the core's guided refinement as the plan, each step's values named, run step by step or to the end

Refine ▸ Guided Plan… shows core 0.52.0's guide::documented_plan step by step (its source, cycles or convergence, the values each step adds and drops by name), then writes guide::guided as the control file's plan (one undo step); the app's own two-step demo plan is replaced by the core's. Run the Plan to the End runs and accepts each step (a version each) and stops where drs refine --plan stops (user, diverging, singular). Request 41 and Pawley waited for the core then. Built and tested under nice -n 15 with CARGO_BUILD_JOBS=8 (Josu's Mac at 90–100 % CPU). Tests: 82 Rust, 192 node pass. One self-check under PL36 (Josu's home from dscl, 5 files unchanged, one instance niced, to its quit): 124 OK, 1 FAIL; Guided Plan, its 8 steps and two steps run and accepted confirmed; the section's Stop threw on its own variable and the time slider's check did not show the bottom panel, both fixed, not yet seen in the app.

branch app (0.41.0) · app/ui/tests/p55.test.mjs · core guide (L302)
L3052026-10-16work

Core 0.53.0: `locate` with the control file's folder, and every value's place from one reading (the app's request 41)

The fault (L299's locate). It read the text without the control file's folder. A file that keeps its background points in a file of their own ([pattern.background] file = …, as drs new writes it) and frees them then has no model. Its [refine] names values the model does not have, so locate answered None for every address. App 0.40.0's self-check met this on the \(\mathrm{Y_{2}O_{3}}\) project's selfcheck.drc. Also, locate read the file once per address: 2.4 s for that file's 101 values in the app.

What is added in control::locate:

  • locate_in(text, address, dir) reads as schema::read_with does, with the folder;
  • all(text, dir) gives every value the file writes, as (address, line, bytes), from one reading;
  • address_at(text, byte, dir) gives the address of the value written at a byte (from its first byte to just after its last);
  • locate(text, address) stays, reading without a folder.

drs locate reads with the file's folder: without an address it lists every value, and --at <byte> names the value at a byte.

Test control_locate::with_the_folder_and_read_once, on \(\mathrm{Y_{2}O_{3}}\)'s control file with its points in a file of their own and refined:

  • without the folder nothing is located; with it the cell is found on its line, with its value;
  • all gives the 39 values the file writes (every value but the points and the micro-absorption at zero), each as locate_in gives it: 1.6 ms for all of them against 62 ms one by one;
  • address_at gives each address back from its value's first, middle and past-the-end byte, and nothing from a key.
app/REQUESTS.md 41 · core/src/control/locate.rs, main.rs (drs locate) · test control_locate · L299, L303
L3042026-10-16work

Publish stream: drs-publish 0.18.0, the command line's commands for scripts

DESIGN.md §6.5, §28, the next largest item needing no core work. drs-publish new [<set.drp>] --control <file.drc>: a set with the default items of the control file's newest results file, refused over an existing file or before a results file exists; check <set.drp> --json; preview <set.drp> <item> [--out]: one item made alone in a temporary folder, the same bytes as in the set's build; figure|table|file <kind> <file.drc|results.cif> --preset <id> --out: one item without a set. Tests: drs-publish 47, the app's 180 node and 79 Rust pass. Self-check under PL36 (eus.ehu.diffrefstruct.selfcheck.publish, a scratch HOME, the real home from dscl, one instance, to its quit, 170 s): 133 lines, 108 OK, 1 FAIL, the Editor's spelling toggle, which the merging session's first run under its own identifier also met (L296) and its clean run did not; Josu's app files unchanged. The stream's Log: line said L302, taken by the core's P55; renumbered L304.

branch publish 668ff9d · publish/src/build.rs, checks.rs, main.rs, module.rs · publish/tests/build.rs · L296
L3032026-10-16work

App 0.40.0: request 40 used in the Editor and Refine; Refine's problems marked by their value (§9); the audit's small conventions

Core 0.51.0's control::locate (request 40, L299) used: Jump to Parameter, and the linked selection between Refine and the Editor. §9: Refine's problems marked on the outline and the inspector by their address, from the core's check_control while the file is edited. The audit's small items (L300): the bottom panel docked on the right, the module order changed by dragging, Paste and Match Style, the standard menu items in ⌘K, the context-menu commands in a menu, ? for the panel in front, Increase contrast, Advanced's diagnostics, Plots' marker size. The decimal separator marked met by Josu's decision (L224, L284, L285). Request 41 filed for the core: locate with the control file's folder (a file whose background points are in a file of their own, as drs new writes it, has no model without it) and one reading for every value. Self-check under PL36 (eus.ehu.diffrefstruct.selfcheck.app, Josu's home from dscl with NULL_GLOB, 5 of his files snapshotted and unchanged, a new scratch folder, its own PID, one instance, run to its quit): run 1 113 OK, 4 FAIL, fixed; run 2 120 OK, 1 FAIL, the bottom panel's check reading only its own pane while the Log was in front, the check fixed and confirmed in headless Chrome, not rerun in the app.

branch app d302bff · app/src-tauri/src/modules/editor.rs · app/ui/tests/conventions040.test.mjs · app/README.md · app/REQUESTS.md 40, 41 · L299, L300
L3022026-10-16work

Core 0.52.0, P55: guided refinement, the documented order of refinement offered for a model as a plan of steps, each naming the values it frees

The order is documented, not invented. It is FullProf's manual's "sensible sequence of refinement of a crystal structure" (FullProf_Manual.pdf, "The Rietveld Method in Practice", p. 14), which sends the reader to McCusker et al. (1999, in manuals/papers/) to complete it. From McCusker et al. §7 (pp. 45–46) come three additions: the positions of the heavier atoms before the lighter; "sets of two to five cycles"; and at the end all the parameters together to a largest shift/e.s.d. of 0.10, "to obtain correct estimated standard deviations".

What guide::documented_plan(&Model) gives: the steps for a model's values that the symmetry leaves free, cumulative as the manual's "add" says, each with its name, its source, its addresses, what it adds and drops, 5 cycles, and the last step's 0.1: 1. the scale; 2. the zero, the first background term and the cell. Background points are not refined, as the manual advises: "better fix the background using linear interpolation between a set of fixed points"; 3. the heavier atoms' positions; a mode phase frees its amplitudes here; 4. all positions and the overall B; 5. the peak shape and the asymmetry: Bérar–Baldinozzi P1–P4, or S/L and H/L for Thompson–Cox–Hastings. Over a resolution file, a phase's U, X and Y are its microstructure and wait for step 10; 6. the occupancies of partly occupied sites, the manual's "if required"; 7. each atom's B in place of the overall B; 8. the rest of the background series; 9. the betas of the atoms that have them; 10. Sycos and Sysin; 11. the microstructure: the Gaussian size, the quartic strain and its share, the anisotropic size; over a resolution file, U, X and Y; 12. all together.

A step that adds nothing is left out: \(\mathrm{Y_{2}O_{3}}\)'s plan has 8 steps, Co-YBCO's and NdNi-HTO's 11.

Rules where the sources leave a choice:

  • Heavier is scattering power: the X-ray f at s = 0, or |b| for neutrons, at least half the phase's largest. The split is made only when the phase has atoms below that.
  • Partly occupied means an atom that shares its position with another, or whose Occ/m is below that of the phase's fullest site. This does not depend on how the file normalises Occ: Pd3MnD's .pcr writes it ∝ m, which the scale absorbs. Its five partly occupied atoms are its four antisites and D.
  • The overall B is dropped in step 7 and kept at its value. The model's B is the overall B plus the atom's, so this is the manual's turning of the one into the others, and the last step never refines both, which would be degenerate.

guide::guided(&Model, &Refinement) adds to a refinement every value a step frees, each as its own parameter. The file's ties, constraints and steering are kept. The last step frees every parameter, including those the file refines outside the documented order. drs plan <file> [--to <file.drc>] prints the plan and writes it as [[refine.step]], and drs refine <file.drc> --plan runs it.

Test guided_refinement:

  • \(\mathrm{Y_{2}O_{3}}\)'s plan: each step's name, source, adds and drops, cycles and convergence.
  • Eight FullProf runs:
  • each plan is cumulative;
  • every value a step names is moved by a free parameter of that step. Through the file's own ties, NdNi-LTOa's O2 B moves with O1's;
  • each run gets the steps its model calls for: Pd3MnD's occupancies, Co-YBCO's S/L and H/L, NdNi-HTO's betas and its U, X, Y over its resolution file, prba10k's needles, NdNi-LTOa's quartic strain, case A's mode amplitudes;
  • \(\mathrm{Y_{2}O_{3}}\) has no occupancy, background or beta step.
  • The control file: Pd3MnD's guided refinement is written and read back with the same plan and parameters.
  • The plan run on \(\mathrm{Y_{2}O_{3}}\) from the tutorial's starting file:
  • no step diverges, and the last converges to every shift below 0.1 esd;
  • \(\chi^2\) is 1.1495 with 83 parameters, against 1.1985 for the file's own 72 codewords refined to convergence from the same start;
  • the starting file is already near the minimum (\(\chi^2\) 1.192 after the scale alone), so this does not test a start from scratch.
manuals/fullprof/FullProf_Manual.pdf p. 14 · manuals/papers/McCusker_1999.pdf §7 pp. 45–46 · core/src/guide.rs, main.rs (drs plan) · test guided_refinement · L110 (the plan), L230
L3012026-10-16work

I05 worked: the maximum entropy method's basis analysed, and a refinement coupled to the Bayesian inference proposed (one posterior over parameters and density, the entropy deficit, the distortion map projected on the irreps)

Josu asked for the method itself, not its history: its basis, and whether a refinement can be built on it with the inference of L286 or coupled to it. The first workshop note of I05 (the ideas page now has a Workshop section, dated notes rendered after the proposal; workshop: in ideas.yml) answers in five parts.

  • The basis. Entropy counts the arrangements of quanta of density over voxels; Skilling's axioms (1988) make \(S(f \mid m)\) the only consistent preference among positive additive maps; \(\exp(\alpha S)\) is the only consistent prior over them (Skilling 1989); \(\alpha\) is inferred from the evidence, and at its optimum \(-2\alpha S\) counts the measurements the data determine beyond the prior (Gull 1989, "classic MaxEnt"). Crystallography departed from this form: the \(C = 1\) stopping rule instead of the evidence, a flat default (the cause of the Si artefacts), phases from the model (the bias the REMEDY cycles fight).
  • The move that makes it a refinement. The parametric model as the default: \(m = \rho(\theta)\). The entropy then measures the information the data carry beyond the model. Bricogne's school did a cousin for proteins (Roversi et al. 2000, BUSTER); for a powder refinement it has not been done, by search.
  • The proposal. One posterior over \(\theta\), the density \(f\) and \(\alpha\), with the pattern as the data (no partitioned intensities): (a) refinement with \(f\) integrated out, an estimator that leaves the parameters undistorted where the model is wrong, to be tested against least squares on synthetic faults; (b) the entropy deficit \(D = -2\alpha S\) as an information-theoretic agreement figure beside \(\chi^2\), and the evidence as the subgroup comparison; (c) the parent as default, the map in the candidate symmetry, and the distortion \(f - m\) projected on the irreps' displacement fields (L263): a first amplitude for every irrep from the data alone, which starts L286's inference where least squares cannot (the singular saddle at zero amplitude).
  • Where it fails, and the tests: the default's smearing versus the displacement parameters; identifiability held by the evidence and checked by calibration; the de Vries test as the map's acceptance test; the cost in Fourier transforms, all in the core.
  • Six steps, each a test before the next, from drs mem in its Bayesian form to the magnetic channel of I04.

Status of I05: workshop. Four asks, the first being whether the entropic refinement is pursued as a research line beside the least-squares engine. Nothing is built.

Josu's ideas chat of 16 October 2026 · ecosystem/data/ideas.yml (I05, workshop note 1) · Skilling 1988, in Maximum-Entropy and Bayesian Methods in Science and Engineering · Skilling 1989 and Gull 1989, in Maximum Entropy and Bayesian Methods (Cambridge 1988) · Roversi, Blanc, Vonrhein, Evans, Bricogne 2000 Acta Cryst. D56, 1316 · Momma et al. 2013 Powder Diffr. 28, 184 · L263, L286, L298
L3002026-10-16work

App 0.39.1: parameters named by the core's Field::describe (L276 finished, request 39 met); the document types read by their keys; APP_CONVENTIONS re-audited, 26 items unmet or partial

param_label takes each value's phase, atom and component from core 0.50.0's Field::describe, so the app holds no list of the core's fields (L276 finished, request 39 met). bundle_document_types reads Info.plist by its keys, not by their order, the fault L296 met. Request 40 waited for the core then; core 0.51.0 (L299) now gives it. APP_CONVENTIONS.md was read again item by item: 26 items unmet or partly met, each with its size and whether Josu or the core comes first, in app/README.md ("what is still not met"). Self-check under PL36 (bundle identifier eus.ehu.diffrefstruct.selfcheck.app, the installed one refused, HOME a new scratch folder, its own PID, one instance, run to its quit): 107 OK, 0 FAIL (the types check OK with 6 types and 4 icons); Josu's preferences and app files unchanged.

branch app ab85a88 · app/src-tauri/src/modules/refine.rs, main.rs · app/README.md · app/REQUESTS.md 39 · L276 · L292 · L296
L2992026-10-16work

Core 0.51.0: where a control file writes a parameter (`control::locate`, the app's request 40), for the Editor's linked selection

App 0.38.0's linked selection (Explore's E8) selects a parameter chosen in one view in the others. The Editor needs the line that writes it, and only the core's reader knows the file's tables and keys (L139: the app does not guess by searching the text).

control::locate::locate(text, address) -> Option<(line, bytes)> works in four steps: 1. It reads the text as the schema reads it. 2. It resolves the address to its field (refinable::field_of). 3. It finds the item in the syntax tree. The keys are the parameter dictionary's ids. The table is [pattern], [pattern.background], the p-th [[phase]], or that phase's a-th [[phase.atom]] or k-th [[phase.mode]]. A component is an element of the value's array: x of atom_position, P2 of berar_baldinozzi, a background point's height, a quartic_strain row's value. 4. It returns the item's line and its bytes in the text.

It returns None when:

  • the text is not a control file;
  • the address names no value of the model;
  • the file does not write the value, e.g. the micro-absorption at zero, or background points kept in a file of their own.

The command line is drs locate <file.drc> <address>.

Test control_locate:

  • It covers the control files the core writes for eight FullProf runs (\(\mathrm{Y_{2}O_{3}}\), NdNi-LTOa, NdNi-HTO, prba10k, Pd3MnD, mode case A, Co-YBCO, LaB6).
  • For each of 620 values that the symmetry does not fix or tie, the located text is replaced by the value plus 0.125 and the file is read again. That value moves by 0.125, and no other value of the same phase moves. The line is the line of those bytes.
  • The second atom labelled Y is found by phase1.Y:2.atom_position.x.
  • The micro-absorption at zero, an address of no value, and text that is not a control file give None.
app/REQUESTS.md 40 · core/src/control/locate.rs, main.rs (drs locate) · test control_locate · L139, L292
L2982026-10-16finding

Two ideas of Josu's assessed in the seed-place: inference for magnetic structures with MAGNDATA as prior and test bed (I04); what became of the maximum entropy method and what of it belongs in the inference (I05)

Both from Josu's ideas chat, both derivatives of the inference assessed in L286; the full text is on the ideas' pages, with the sources. What was checked, by web search and on the pages themselves on 16 October 2026:

I04. MAGNDATA (Bilbao Crystallographic Server) holds "more than 2000" published magnetic structures as .mcif files, Shubnikov groups for the commensurate ones and magnetic superspace groups for the incommensurate ones (Gallego et al. 2016, parts I and II); 2241 commensurate entries as of June 2026 by a paper that trained on it; the whole database is downloadable after registering. It holds models, not diffraction data, so it is the measured prior for L286's magnetic case and the test bed (patterns simulated from solved structures, the inference asked to find them among k-SUBGROUPSMAG's candidates), not data to refine. A 2026 analysis finds 91 % of its entries compatible with a single-irrep Landau theory. The literature has networks that predict the magnetic order from the coordinates (Merker et al. 2022; the Magnetic Structure Network, 2026), BLAND's MCMC refinement (2016) and ANDiE's Bayes factor on the order parameter (2022), but no program that compares the candidate magnetic models by their evidence with a learned prior. The cost: the core has no magnetic scattering (magnetic structures are "later", L012), and that layer, with the 1651 groups, the irreps and .mcif, comes before any inference. Status: discussed, four asks, the first being whether magnetic structures enter the plan.

I05. The maximum entropy method (Collins 1982; Sakata and Sato 1990; Takata's MEM/Rietveld, Nature 1995 and the 2008 review) is alive as a density tool after refinement, in Dysnomia (2013, v1.0 2018), BayMEM (v2.01 2010, with the superspace extension of 2003) and Jana2020; it was displaced by charge flipping (2004) for structure solution; its non-nuclear maxima in Si and Be were shown to be artefacts of the flat prior (de Vries, Briels and Feil 1996) and cured by priors and the generalized F constraint; Bricogne's Bayesian programme became BUSTER, a macromolecular refinement system. It was never the refinement engine. In L286's language it is a maximum posterior in density space, the complement of the inference in parameter space. Derivable for this project as the residual density drawn in Explore (drs mem on the core's partitioned intensities, with the de Vries test as its test) and as the entropic prior relative to the parent for the inference where no family prior exists; not as the engine. Status: discussed, two asks.

Nothing is built; no requirement is appended.

Josu's ideas chat of 16 October 2026 · cryst.ehu.eus/magndata (front page, incommensurate index, download page, read 2026-10-16) · Gallego et al. 2016 J. Appl. Cryst. 49, 1750 and 1941 · arXiv 2605.16230, 2601.01617, 2509.05909 · Merker et al. 2022 iScience · Lesniewski et al. 2016 J. Appl. Cryst. 49, 2201 (BLAND) · Rodríguez-Carvajal, González-Platas, Katcho 2025 Acta Cryst. B81, 302 · Collins 1982 Nature 298, 49 · Sakata & Sato 1990 Acta Cryst. A46, 263 · Takata et al. 1995 Nature 377, 46 · Takata 2008 Acta Cryst. A64, 232 · Momma et al. 2013 Powder Diffr. 28, 184 (Dysnomia; jp-minerals.org) · van Smaalen, Palatinus, Schneider 2003 Acta Cryst. A59, 459 (BayMEM; crystal.uni-bayreuth.de) · Palatinus & van Smaalen 2002 Acta Cryst. A58, 559 · de Vries, Briels, Feil 1996 Phys. Rev. Lett. 77, 1719 · Gilmore 1996 Acta Cryst. A52, 561 · Bricogne 1997 CCP4 proceedings · Oszlányi & Sütő 2004 Acta Cryst. A60, 134 · Papoular & Gillon 1990 Europhys. Lett. 13, 429 · L012, L100, L263, L286
L2972026-10-16work

Publish stream: drs-publish 0.17.0, provenance in each file of a build and read back; its self-check under PL36

drs-publish 0.17.0 (DESIGN.md §5.3, §27): provenance in each file of a build (PDF XMP, comments, PNG iTXt, TIFF ImageDescription; the data files in the manifest) and drs-publish provenance to read it back, shown in the app's inspector; one self-check under PL36 (identifier eus.ehu.diffrefstruct.selfcheck.publish, a scratch HOME, one instance, to its quit, 370 s): 131 lines, 109 OK, no FAIL, Josu's app files unchanged (seven of them snapshotted). Tests: drs-publish 46, the app's 180 node and 78 Rust pass.

Found by Publish on the way: Plots' plots/tools/selfcheck.sh builds Josu's home as /Users/$(id -un), but on this Mac the account is kevin and the home is /Users/User (dscl NFSHomeDirectory). Without NOMATCH its before and after snapshots are both empty and it reports Josu's files unchanged without having read them; under zsh's default it stops at the glob. Publish took the home from dscl and set NULL_GLOB. The merging session's check (L296) reads $HOME (/Users/User) and is not affected; Plots is asked to fix its script at its next step.

branch publish e4b436c · publish/src/provenance.rs · publish/REQUESTS.md P35 · plots/tools/selfcheck.sh · L296
L2962026-10-16finding

Self-checks reached Josu's installed app through the shared bundle identifier; his app's state cleaned; every self-check now runs under an identifier of its own (PL36); app 0.39.0 installed

Josu's installed app 0.38.0 opened a project in the Plots stream's scratch folder. Its windows.json pointed there, and all ten entries of its recent_projects.json were scratch paths of the app, tutorial, Plots and merging sessions; settings.json held layouts for scratch files. The Plots stream found the cause (PL36, branch plots 821545b): every stream's self-check bundle, and the merging session's, carried the installed app's identifier eus.ehu.diffrefstruct, and macOS keeps preferences, saved window state and Launch Services registration by bundle identifier under the real user, whatever HOME says. Josu's preferences held WebContinuousSpellCheckingEnabled = 0, set by the shell's spelling self-check; he had not turned it off.

Done, with Josu's app closed. The three files were backed up into backup-20261016-before-scratch-cleanup/ in his Application Support folder, and every /private/tmp path was removed from them: the window list and the recent list are empty, his own settings are kept (13 \(578 \to 4\) 978 bytes). On his word the spelling preference was deleted, so the app follows the macOS default (on). The app has no saved window state on his Mac.

The rule from now on (PL36, sent to the app, Explore and Publish streams, which acknowledged it): a self-check bundle has an identifier of its own, eus.ehu.diffrefstruct.selfcheck.<stream>, and a check refuses the installed one. The merging session makes its self-check bundle by copying the built bundle, changing only the identifier's string and re-signing it ad hoc; the copy's Info.plist is checked identical to the built one except for that string. plutil -replace was tried first and set aside: it re-sorts the plist's keys, and the app's bundle_document_types reads the document types' icons by key order, so the types check FAILed in all three runs with no icon found. Kept versions are locked (uchg, L224), so each run takes a new scratch folder. The chain takes an md5 of Josu's preferences and app files before and after and installs only if they are unchanged.

App 0.39.0 (main 3192110): core 317, Publish 44, app Rust 78 and UI 179 tests pass; three self-checks on the copy with its own identifier, one instance at a time, each ended by the app's own quit: Y2O3 96 OK 0 FAIL, the relaunch 107 OK 0 FAIL (P46: 2 windows and 3 tabs reopened), Tb2Pd2Sn 106 OK 0 FAIL; Josu's files and preferences unchanged. Installed in /Applications (0.38.0 to the Trash). The first run under its own identifier had 6 FAILs: 3 the plutil key order above, auto-save and Explore's compare from a scratch folder with the earlier run's locked versions in it, and the spelling toggle not taken, which did not come back on the clean run.

branch plots 821545b (plots/tools/build-selfcheck.sh, selfcheck.sh, plots/REQUESTS.md PL36) · app/src-tauri/src/main.rs bundle_document_types · app/ui/app.js selftestStep35, selftestFinderAndPrint · L224 · L291
L2952026-10-16work

Plots stream, eleventh step: the module on app 0.38.0's shared plot (PL13, PL30 met): measured margins, a figure's text in points

Plots step 11: onto app 0.38.0's plot (PL13, PL30 met): the stack and the maps on the measured right margin, figures' text in points at the preset's sizes; the projection's margin kept until point labels are measured (PL35 filed); five views and the Linked Overlay unchanged; case E at 453 K (L243).

branch plots f65b53a · app/ui/modules/plots/ · plots/REQUESTS.md PL13, PL30 · L287
L2942026-10-16work

Explore stream, twelfth step: the module moved onto app 0.38.0's services (E8, E8a met), its own tick rule, symmetric-log axes, 3D set-up, cycle slider and sticky column dropped, with no change to what is shown

origin/main (07574c4, app 0.38.0, L287) merged. Every Explore plot is now on plot.js: the traces on its symlog scale (the \(\pm\)1\(\sigma\) band a filled patch, W11's marks and the cycle shown as vertical lines, a click on the trace under the pointer chooses its parameter), the pair with the core's ellipses as paths and the path numbered by cycle, the peak shape (H_G\(^{2}\) < 0 hatched), the spectrum on the log scale (the weakest directions' bars, which carry no axis, stay the module's), and Compare's both paths, difference (linked in x by linkAxes) and pair; draw.js, peaks.js and compare.js build series from the core's numbers, and the module's axisTicks, symlog transform and hand-drawn axes are gone. The \(\chi^2\) map was already on the image layer (PL13); its own contour lines would be the core's (E21 filed, waiting behind version 1, L230). The structure view fills canvas3d.js's group (renderer, camera, orbit, keyboard, theme, Copy are the canvas's; a drag starting in the ex-3d area is handed to it). The cycle is timeslider.js's (W11's marks on its track, the speed beside it setting its interval; the module's counter "2 of 2, \(\chi^2\) …" kept). Every Explore table names its rows in table.js's fixed first column (labels); the module's sticky rule gone. Linked selection: a trace or row chosen in Explore is selected in Refine (selectParameter), and one chosen in Refine is marked in Explore. An Explore pane in Settings (registerPane): the replay's speed, the background points, the \(\chi^2\) map's span for a new tab. Shell line (E10m): plot.js's additive xIntegers, so that cycle and step axes keep whole-number labels; load.test.mjs reads the 3D checks from canvas3d.js. Checked: node tests 178 pass (explore_services.test.mjs new; explore_replay, explore_stops, explore_tour, load updated); cargo test --release modules::explore 7 pass; traces, pair, map, cell, peak shape and compare headless against the committed views (WebGL is not available headless; the 3D view is the self-check's). One self-check of the built app 0.38.0 (not installed), the only instance, its own home, on a copy of Y2O3, ended by the app's own quit: 157 lines, 95 OK, 1 FAIL, the shell's print check (landscape printed on 2 pages where 1 is expected), outside Explore; Explore's lines all OK, among them "services OK: the traces on the shared plot (10 of 10 parameters, scale symlog); the cycle on the shared time slider (at 2 of 2, 1 W11 mark on its track); pattern1.zero_shift chosen in Refine is the one marked here (yes); the Settings pane "Explore", 3 settings", the structure with WebGL 2, compare 72 of 72 agreeing, the cycle restored and undone.

branch explore b0ec8ca · branch explore · app/ui/modules/explore/ · app/ui/plot.js (E10m) · explore/REQUESTS.md (E8, E8a met; E10m; E21)
L2932026-10-16work

App stream, 0.38.1: L290's two FAILs fixed and confirmed in a self-check without a FAIL; 0.38.0's splits, P45 and Merge All Windows seen in the app

app 0.38.1 (app stream): the two FAILs of 0.38.0's installed check (L290) fixed and confirmed in one self-check run to its quit without a FAIL (Refine's phase name field named and explained; a printed figure's width capped from its viewBox, landscape on one page); 0.38.0's split, P45 and window-tab fixes seen in the running app; Field::describe (request 39) and an address's line (request 40) still wait for the core.

branch app 651d735 · app/README.md (0.38.1) · L290, L291
L2922026-10-15work

Core 0.50.0: `Field::describe`, each refinable value labelled for display by the core (the app's request 39, L276), so the app keeps no list of the core's fields

Josu decided in L276 that the app keeps no list of the core's fields. Four core releases had added one, and each time main's app stopped compiling (the last was ShapeSize, L267). App 0.37.0 (L280) already takes the phase from Field::phase. It waits only for the names.

Field::describe(&Model) -> FieldLabel gives:

  • the parameter dictionary's id and name ("atom_position", "Atom position");
  • the phase, as Field::phase gives it;
  • the atom, its index in the phase and its label;
  • the component, where several fields share the dictionary's id:
  • x, y and z of a position; β₁₁ … β₂₃ of the betas; P1 … P4 of the Bérar–Baldinozzi asymmetry;
  • S400 … of the quartic strain, from its terms; a mode's name (A1_GM1+);
  • a background point's 2\(\theta\) ("at 15.48°") or a series' order ("order 3").

The new struct is FieldLabel, because refinable::FieldDescription already names what a free parameter moves.

Test field_labels:

  • Coverage: all 751 fields of eight FullProf runs (\(\mathrm{Y_{2}O_{3}}\), NdNi-LTOa, prba10k, Pd3MnD, mode case A, Co-YBCO, LaB6, NdNi-HTO), which between them hold all 29 kinds of Field.
  • For every field: the dictionary id equals refinable::dictionary_id, and the name is the dictionary's; the phase is Field::phase's; the atom is the structure's, and its label is in the address; the component is as listed above, and none for the other kinds.
  • New kinds: the test matches the kinds without a wildcard, so a kind the core adds without a description stops it compiling. Within the core, then, adding a field means describing it, and the app needs no change.
app/REQUESTS.md 39 · core/src/refinable.rs (FieldLabel, Field::describe) · test field_labels · L267, L276, L280
L2912026-10-16decision

Josu: app 0.38.0 installed despite the gate's two small FAILs; the next version goes through the full gate

The gate (L279) held app 0.38.0 for two FAILs (L290): a landscape print that gave 2 pages in one run of two, and one Refine input with no VoiceOver name. App 0.38.0 is clearly better than the installed 0.36.0. In 0.36.0 the splits were lost at every save in the Editor and P45's numbers dropped; 0.38.0 keeps the splits and reopens them, prints after a relaunch, and has the macOS window tabs and the document icons.

Josu chose to install 0.38.0 now. Set aside: keeping 0.36.0 until a version passes every check. The merging session installed the checked bundle, with 0.36.0 to the Trash.

The app stream has found both causes:

  • Refine's phase "Name" input had no label;
  • the printed figure's width was not capped, only its height. The fixes come in its next step, which goes through the full gate.
Josu, 16 October 2026 · /Applications/diff-ref-struct.app (0.38.0) · L279, L290
L2902026-10-15work

App 0.38.0 checked on its built bundle: the splits kept and reopened, P46 and printing after a relaunch work; not installed for two small FAILs; Explore and Plots resumed for one step

The gated run of app 0.38.0 (L287):

  • suites: the core 316, Publish 43, the app 78 Rust and 175 node tests pass;
  • the self-checks on the built bundle, one copy at a time, each to its quit: 1. Y2O3: 93 OK and 1 FAIL; 2. a relaunch on the same home: 105 OK and 1 FAIL; 3. the \(\mathrm{Tb_{2}Pd_{2}Sn}\) buffer: 105 OK, none FAIL.

Fixed and seen in the app:

  • the splits are kept, and the relaunch splits the group off as the last session kept it;
  • P46 reopens 2 windows and 3 tabs;
  • after the relaunch, printing gives 1 page in portrait and 1 in landscape;
  • the Editor prints all 412 lines on 8 pages.

Not installed under the gate (L279), for two FAILs, both the app's:

  • Page Setup in landscape gave 2 pages in the first run, though 1 in the relaunch's;
  • Refine has one input with neither a name nor a tooltip for VoiceOver. App 0.36.0 stays installed.

Explore and Plots resumed for one step each, with Josu's agreement, to move onto the shell's new services and drop their own copies (E8, E8a; PL13, PL30). Each runs one self-check (L193).

DRS_APP_SELFTEST reports of 15 October 2026 (the built bundle) · L193, L246, L279, L287
L2892026-10-15work

Publish stream, drs-publish 0.16.0: the decimal marker follows the language (Basque's comma, the point elsewhere, L284, L285); F1's options

drs-publish 0.16.0:

  • The decimal marker follows the set's language. Basque gets the comma, through siunitx's marker, with semicolons between numbers; Spanish, English and every other language get the point.
  • 0.15.0's decimal key may now only state the language's own marker; anything else is refused, citing L285.
  • Check 4 reads every .tex and .pgf of a build. A point in a Basque set, or a comma in any other set, is an error. The test covers both directions.
  • Choose Language… offers only the language. publish_set_language lost its comma argument.
  • F1's options: √I or log, d or Q, (Yobs − Ycalc)/\(\sigma\), the background, the cumulative \(\chi^2\), and the high angles magnified, each from the core.

The stream's self-check: no FAIL. The shell lines touched are listed in publish/REQUESTS.md P34.

branch publish 5737c94 · publish/ · publish/REQUESTS.md P34 · L284, L285
L2882026-10-15work

The seed-place: an Ideas section of the site, each idea with its status and its own log; Josu's first three ideas registered and assessed (I01 themes, I02 extensions, I03 the seed-place itself)

Josu opened a chat as a notebook for the ideas he wants to develop into features of the app, and asked for a place on the site to hold them as they are said: the day each was added, the discussion held, the decisions, a status and its migration to the app, the core or an extension, a log per idea; built like the rest of the site but in another palette; and perhaps a dedicated site, born with every project, where each idea has a workshop until it gets the green light to leave the incubator.

Built: ecosystem/data/ideas.yml (ids I01, I02 … that never change; said, status, home, depends, summary, assessment, proposal, asks, and the idea's log), rendered by build.py into site/ideas/index.qmd (the board: the lifecycle with its counts, one card per idea) and site/ideas/<id>/index.qmd (the workshop: the facts, the idea, the assessment, the proposal, what Josu decides, the idea's log); the ideas are listed under a new group of the left navigation, "Ideas" in the header ribbon and the footer. The status vocabulary: seed, discussed, workshop, green, migrated, with parked and discarded as the side exits; home names where the idea migrates. The build stops on a status or a kind outside the vocabulary, a log ref that is not an entry of log.yml, or a dependency on an idea that is not there. The incubator's palette (plum accent, warmer paper, its own dark values) is set on the body of any page holding an ideas block, so the header's line, the wordmark's accent and the navigation's marks change with it; the kit's type, grid and tags stay.

The three ideas, assessed (the full text on their pages):

  • I01, themes: the app already keeps its 46 colours as tokens with light and dark values, so a theme is a named set of values; the cost is three rules (two columns, the plot's meaning and colour-blind safety, Publish never follows the screen); a VS Code theme can be imported with its mapping stated; a theme is the first code-less extension once I02 exists. Status: discussed, three asks.
  • I02, extensions: the module interface and the shell's services (L287) are the extension interface in embryo, and Publish, Explore and Plots are extensions in all but packaging. L139 settles Josu's question: a view extension computes nothing; an engine extension is a program of its own (Rust by the crate, any language by the files and a JSON-over-stdio protocol), every number stamped with its name and version in the results file. The real cost is the frozen interface and the versioned core. Five steps proposed, drs-infer (L286) as the first engine. Status: discussed, four asks.
  • I03, the seed-place: a section of each project's site rather than a separate site, bound to the log, the brief and the streams; the generator and the palette to move into the family kit so that every project starts with it; the family's own incubator parked until an idea without a project appears. Status: workshop, three asks.

Rule added to CLAUDE.md: an idea goes to ideas.yml first, is assessed before anything is built, and enters the brief and a stream only on a logged decision. Nothing of I01 or I02 is built.

Josu's ideas chat of 15 October 2026 · ecosystem/data/ideas.yml · ecosystem/tools/build.py (write_ideas) · site/assets/styles/project.css · app/DESIGN.md §1, §6 · app/ui/style.css, app/ui/modules/index.js · L060, L139, L286, L287
L2872026-10-15work

App stream, 0.38.0: the split and print FAILs of L283 traced and fixed; macOS window tabs and the document icons (L281); the shell services Explore and Plots asked for; request 40

App 0.38.0 (branch app 6215158).

L283's FAILs, traced.

  • The splits: when the Editor saved a .drc, Refine reopened that document as a new one, closed the old tab and opened the new one in the main group. The check's own typing had been auto-saved. A user's split Refine tab also jumped back at every save in the Editor, and P45 and the panel window lost their document. The document is now read again in place (refine_reread, the same id), so its tab, its place in a split, its view and its plot stay. Tab groups are counted by where each tab's header sits.
  • The relaunch's "0 pages": the first run's PDF was still there, and AppKit asks before replacing a file, in a sheet nobody answers. The old file is now removed before printing.

L281:

  • every window has a tabbing identifier: the project windows share one, and the Settings and panel windows refuse tabs;
  • the Window menu has AppKit's Show Tab Bar, Show All Tabs, Move Tab to New Window and Merge All Windows. The app's own command is renamed Move File Tab to New Window;
  • the page moves under the tab bar while it shows;
  • the document icons are redrawn in the app's family: braces, stacked curves, a page with a figure, a pattern with tick marks.

The shell services Explore (E8, E8a) and Plots (PL13, PL30) asked for:

  • in plot.js: numbered paths, filled patches, given contours, a symlog scale, hatched spans, fillBelow, linkAxes, drawing per pixel column (100 series of 5000 points in 180 ms), text in points with measured margins, log ticks at 2 and 5;
  • table.js labels, timeslider.js, selectParameter for linked selection, registerPane for a module's Settings pane, and canvas3d.js. Explore and Plots can drop their own copies.

New request 40: an address's line in a control file. Field::describe (request 39) is not yet in the core.

The stream's self-check: 100 OK and 5 FAIL, all from the reopening above, fixed after it and not yet seen in the app. Merge All Windows is not yet exercised; the check now makes a second project window. Tests: 175 node and 78 Rust pass; clippy is clean.

branch app 6215158 · app/README.md (0.38.0) · app/ui/plot.js, table.js, timeslider.js, canvas3d.js · app/REQUESTS.md 39, 40 · L281, L283
L2862026-10-15finding

Josu's two ideas assessed: inference over the subgroups and amplitudes before a refinement, and the same search over a family's configuration space with the Perovskite Atlas as the prior

Josu, 15 October 2026, with the inverse-problems project (Iñigo González de Arrieta's ideas): first, before any refinement, from the data, the parent and the candidate low symmetries alone, a Bayesian search of the configuration space returning the regions where the parameters most probably are; second, the same search carried over the generalised configuration space of a family, the double perovskites first, along the chemical axes (B, B′, substitutions, chemical pressure), then other families. Asked to be critical and to report.

The assessment is the page design/inference/. In short:

  • Both make sense and are one idea. The first gives the subgroup choice a number (the evidence across the family's tree of twelve space groups, Howard, Kennedy and Woodward 2003) and the result a region, folds the domain-equivalent amplitude sets the core already enumerates, starts the refinement at the posterior's modes instead of "near the parent" (where the mode matrix is singular, Explore §6.2), and can say which measurement would decide. But its evidences depend on the amplitude prior's width (Lindley), and the second idea is where a measured prior comes from: the Perovskite Atlas holds the family as typed records (47 materials, 41 structures with their mode decompositions by irrep, 66 transitions, 13 series, every value sourced). The family's map c ↦ p(\(\theta\) | c) is a hierarchical model over tens of compounds, not "machine learning"; it is the prior of each refinement and each refinement updates it.
  • The risk named first: Rietveld's \(\chi^2\) is not a likelihood (correlated, structured residuals), so a posterior on it is as overconfident as the printed esd's. The design must declare the misfit model (tempered \(\chi^2\), or a discrepancy term after Kennedy and O'Hagan) and the toy must test calibration: 90 % regions holding the truth 90 % of the time over noise draws. If that fails, it is a search tool and must say so.
  • What exists, checked by search on 15 October 2026: Bayesian Rietveld for one structure (Fancher et al. 2016), extinction-symbol probabilities (Markvardsen et al. 2001), the symmetry-mode on/off search (Kerman et al. 2012, ISODISTORT + TOPAS), black-box tuning of a refinement's settings (Ozaki et al. 2020), a generative network (Riesel et al. 2024, 67 % on RRUFF). None composes the evidence across a tree in mode coordinates with the equivalents folded, the nuisances solved, a family prior and the design question, inside the program that refines. The core has the costly piece: the forward model at 5–40 ms with derivatives.
  • The shape proposed: a crate drs-infer beside drs-publish (stochastic, seeded, statistically tested code kept out of the bit-exact core; L139 kept, since it is core-side), a stream infer, the Atlas as the family's source by export with its commit as provenance, an app module that only draws. Order: the diagnostic (P1 of the inverse project, days) → the toy (P2, two weeks: a synthetic double perovskite, the twelve subgroups by multi-start + Laplace evidences, the calibration test) → the crate → the sampler where Laplace fails → the family model → the module. Multi-pattern likelihoods wait for the core's multi-pattern refinement; a general enumeration of isotropy subgroups is set aside.
  • Seven decisions for Josu on the page (§4): go ahead on the toy's calibration rule; crate or core module; the name (Infer proposed); the likelihood; the family and the Atlas's place; whether this is the inverse project's phase 1 (its Q2 and Q3), to put to Iñigo; whether R33 and R34 are appended now or when the toy passes. Nothing is built before.
Josu, 15 October 2026 · ecosystem/site/design/inference/index.qmd · explore/DESIGN.md §2.4, §6.2–6.3 · core/src/{mode_decomposition,modes,check,lsq}.rs (0.49.0) · ~/Claude/Projects/igartua/perovskite-atlas (read 2026-10-15) · inverse-problems-configuration-space (discourse, programme P1–P7, Q1–Q6) · web searches of 2026-10-15: Fancher et al. 2016 Sci. Rep. 6, 31625; Markvardsen et al. 2001 Acta Cryst. A57; Kerman et al. 2012 Acta Cryst. A68; Ozaki et al. 2020 npj Comput. Mater. 6, 75; Riesel et al. 2024 JACS; Howard, Kennedy and Woodward 2003 Acta Cryst. B59, 463
L2852026-10-15decision

Josu amends L284: the decimal comma kept for Basque; a point everywhere else, Spanish included

Josu, just after L284: "I wanted to maintain it, the comma I mean in Basque".

The rule now:

  • a decimal point everywhere: on the app's screen (L224), and in figures, tables and text in English, Spanish and every other language and preset;
  • the decimal comma in Basque only, for a thesis or a document written in Basque, as drs-publish 0.15.0 set it (L282).

L284's "theses in Spanish or Basque included" stands for Spanish and is withdrawn for Basque. Publish keeps the comma in its Basque preset, and takes it out of the Spanish one. CLAUDE.md says so.

Josu, 15 October 2026 · publish/ (strings.yml, the language presets) · L224, L282, L284
L2842026-10-15decision

Josu: numbers with a decimal point everywhere, theses in Spanish or Basque included

drs-publish 0.15.0 (L282) set a thesis in Spanish or Basque with the decimal comma, as those languages' typography has it. Josu: "point elsewhere, and I am more of a dot in Spanish". Every number is written with a decimal point: on the app's screen (L224), in figures, tables and text, and in every language and preset. The language of a thesis changes its words, not its numbers. Set aside: the comma for Spanish and Basque.

Publish is to drop the comma from its Spanish and Basque presets. CLAUDE.md's Publish paragraph states the rule.

Josu, 15 October 2026 · publish/ (strings.yml, the language presets) · L224, L282
L2832026-10-15work

App 0.37.0 checked on its built bundle and not installed: P46 reopens the tabs, but the splits are not kept and a relaunch printed no page

The app stream asked for 0.37.0 (L280) to be checked on its built bundle before it replaced the installed copy, and not installed if P46 failed to reopen the tabs.

The gated run:

  • the suites pass: the core 316, Publish 41, the app 78 Rust and 161 node tests;
  • the build is 0.37.0. The self-checks ran on the bundle, one copy at a time, each to "[main] quit:": 1. Y2O3 with a home of its own: 88 OK and 1 FAIL; 2. a second launch on the same home with no file asked for, which is P46's case: 88 OK and 3 FAIL; 3. the \(\mathrm{Tb_{2}Pd_{2}Sn}\) buffer: 99 OK, none FAIL.

P46 works: the second launch reopened 2 windows and all 4 tabs (y2o3.dat in Data, the .drc and the .pcr in the Editor, the .drc in Refine).

Not installed, because the relaunch was not clean. Its FAILs, the app's:

  • the splits are not kept: "View ▸ Split Down made a group … the project keeps 0 split(s)", in runs 1 and 2; L279's fix does not yet hold;
  • the relaunch splits no group off as the last session kept them;
  • printing the plot after the relaunch gave no page.

Passed to the app stream. App 0.36.0 stays installed.

DRS_APP_SELFTEST reports of 15 October 2026 (the built bundle) · L193, L265, L279, L280
L2822026-10-15work

Publish stream, drs-publish 0.15.0: EHU Sans found installed on the Mac, a TFG set and checked in it; DESIGN.md's open items listed and the largest built

drs-publish 0.15.0: EHU Sans found installed on the Mac first (L274), a TFG set and checked in it; DESIGN.md's open items listed with their sizes, and the largest without core work built: the thesis in Spanish or Basque with the decimal comma (strings.yml, 169 texts; Choose Language… in the app); one self-check, no FAIL.

At the merge (the merging session): the stream named this entry L281, which Josu's decision on the last two app conventions had taken. It is L282.

branch publish fdf2408 · publish/ · publish/DESIGN.md · L258, L262, L274
L2812026-10-15decision

Josu's last two app conventions: project windows as macOS tabs; document icons in one family with a pictogram each

The two items of the app's convention list that waited for Josu (L224, app 0.22.1):

Native window tabbing: project windows as macOS tabs. Each project window can join a macOS tab group: Window ▸ Merge All Windows, Move Tab to New Window, Show Tab Bar. The app's own file tabs stay inside each project window, as now. Panel windows (app 0.37.0) never join a tab group. Set aside: no macOS tab bar at all.

Document icons: one family, a pictogram each. The same page shape and colour family as the app's icon, each type with its own small pictogram:

  • braces for the control file, .drc;
  • stacked curves for the plot set, .drv;
  • a page with a figure for the publication set, .drp;
  • a pattern with tick marks for the results file. They replace app 0.33.0's placeholders. Set aside: the app's icon with the extension written on it; macOS's plain document icon.

With these, every item of APP_CONVENTIONS that waited on Josu is decided. The app stream takes both in its next step.

Josu, 15 October 2026 · APP_CONVENTIONS.md §1, §3, §7 · app/README.md (0.22.1, 0.33.0) · L224, L261, L280
L2802026-10-15work

App stream, 0.37.0: panels in macOS windows of their own; param_label keeps no list of the core's fields (L276, first part); L279's three FAILs and the flaky Plots tests fixed; request 39

App 0.37.0 (branch app 26eaa4a):

  • Panels in windows of their own (APP_CONVENTIONS §1, §3). The sidebar, the inspector or the bottom panel goes into a window of kind "panel", through the panel's ⧉ or Window ▸ … in a Window. It follows its owner's file and selection. A Refine document is shared through the core, and changes are told across windows. Closing the window, or Dock Back, returns the panel. Native window tabbing stays Josu's decision.
  • L276, first part: param_label takes the phase from Field::phase() and an atom's field from its address. The hand-kept list of every field is gone, so a new core field no longer breaks the build. The components' names wait for Field::describe, asked as request 39.
  • L279's FAILs fixed:
  • Default Set gave 9 of 10: the writing window reread the settings file over its own state. Broadcasts now carry their origin, and the writing window skips its own;
  • the split is kept at once, on dockview's group events;
  • the outline's menu: the check took the phases' group row and now takes a phase's row.
  • The flaky Plots tests: every test comparing with drs ran cargo run on core/ at once, racing on its build and Cargo.lock. drs is now built once (OnceLock) and run as a binary.
  • 0.36.0's own check (94 OK, 4 FAIL) found P45's numbers dropped whenever the pattern object was replaced. They now reset only when the core's numbers change.

The stream's self-check of 0.37.0 found a regression of this step: recording the groups at once also fired during the opening's layout restore, and saved "no tabs" over the last session. It is fixed (tabsReady), but not yet seen in the app. The stream asked the merging session to check, on the built bundle and before installing, that P46 reopens the tabs, and not to install if it does not.

Tests: 161 node and 78 Rust pass; clippy is clean.

branch app 26eaa4a · app/README.md (0.37.0) · app/REQUESTS.md 39 · L276, L279
L2792026-10-15work

Core 0.49.0 merged (L278: version 1's P13 closed on ISODISTORT's own file); app 0.36.0 installed, three FAILs of the app's own; two Plots tests failed once

Core 0.49.0 (L278) is merged as a fast-forward. P13 is closed on the file the merging session made in ISODISTORT (L275):

  • every irrep's amplitude agrees with ISODISTORT to its five printed decimals (GM4+ 0.82937 against 0.829371);
  • the 7 irreps, the 12 modes and each irrep's dimension agree;
  • the mode matrix had been read transposed in L263, on a fixture the core wrote itself; ISODISTORT's own file, read as GSAS-II reads it, corrected it.

Version 1 is done with no check owed. The milestone page was republished: version 1 at 39 of 39, version 2 with P25 and P45 done.

App 0.36.0 (L277), built on core 0.48.0, is installed, with 0.35.0 to the Trash. The self-check, run to its quit:

  • on a copy of the \(\mathrm{Tb_{2}Pd_{2}Sn}\) buffer: 99 OK, none FAIL;
  • on a copy of Y2O3: 83 OK and 3 FAIL, all the app's:
  • Customize Toolbar…'s Default Set gives back 9 of Refine's 10 buttons;
  • a split made with View ▸ Split Down is not kept with the project (0 splits kept);
  • the outline's context menu lacks an item ("Add Phase… | Open in Editor" only). Passed to the app stream.

Two of the app's Plots tests failed in the run before the build: the_csv_is_written_as_the_view_shows_it and normalise_convert_and_the_csv_are_the_cores. Run again, all 11 Plots tests pass. Either core 0.49.0's merge landed while the tests ran, or the two share a file. The app stream is asked to rule out the second.

The merging session's procedure is corrected. The chain installed although the tests had failed. From now on the install stops on any failed test, and nothing is merged into main while a test run is going on it.

core/tests (isodistort) · app/src-tauri/src/modules/plots.rs tests · DRS_APP_SELFTEST reports of 15 October 2026 · L275, L277, L278
L2782026-10-15work

Core 0.49.0: P13 closed. The ISODISTORT reader checked on ISODISTORT's own CIF of case A, and the core's decomposition agrees with every irrep and mode; the mode matrix gives the deltas from the modes, not the reverse that L263 read

The check owed since L263, on the files the merging session made in ISODISTORT 6.12.2 (L275, manuals/bcs/isodistort/). It is test isodistort, case_a_as_isodistort_wrote_it.

  • The file read: 12 displacive modes in 7 irreps (GM1+, GM3+, GM4+, GM5+, X2+, X3+, X5+), with their norms; 12 coordinate deltas; 18 coordinate formulas; 34 matrix entries. The labels split into their parts (GM4+: [0,0,0], (a,-a,0), O1, e, dsp, E).
  • The matrix, read wrong in L263. ISODISTORT's file states, above the loop, "deltacoords(lattice units) = matrix.(modeamplitudes*modenormfactors)": a row per delta, a column per mode. GSAS-II's importer reads it the same way: its Mode2VarMatrix, inverted for the modes (G2phase_CIF.py, GSAS-II master c67a261). L263 had read it the other way round, on a fixture the core stream wrote. IsoDistort::matrix_times_deltas is replaced by deltas_from_modes (δ = M·(A × N)) and modes_from_deltas (M solved).
  • Against the file: on case A the deltas from the modes agree with the file's within 3.6·\(10^{-6}\) (its print is \(10^{-5}\)). The amplitudes back from the deltas agree within 5.4·\(10^{-5}\), which is the deltas' print divided by norms down to 0.036.
  • Against the core's own decomposition (mode_decomposition::decompose) of ISODISTORT's input structures, with ISODISTORT's basis (1/2,1/2,0), (−1/2,1/2,0), (0,0,1):
  • the same 7 irreps and 12 modes;
  • each irrep's amplitude to ISODISTORT's five decimals. GM4+ is 0.82937 against the core's 0.829371, GM1+ 0.28394 against 0.283941, X3+ 0.39865 against 0.398651, X5+ 0.12390 against 0.123903, GM5+ 0.04594 against 0.045942, X2+ 0.00166 against 0.001658, GM3+ 0.00149 against 0.001486;
  • each orbit's modes (Sr1, O1), as |amplitudes| within 5·\(10^{-6}\);
  • each irrep's dimension equal to the number of components in ISODISTORT's order-parameter direction: GM1+ (a) is 1, GM3+ (a,0) is 2, GM4+ and GM5+ are 3, X2+ and X3+ (0;0;a) are 3, X5+ (0,0;0,0;a,a) is 6.
  • Mode basis: some modes' signs differ from ISODISTORT's, X5+'s Sr mode and X2+ among them, and so does GM5+'s order of components. Within an irrep the basis is a choice, and the irrep amplitudes do not depend on it.
  • The core's names: they stay computed (GM+[3]a and so on). Their matching to ISODISTORT's labels here is by k type, number of modes and amplitude, done in the test.
  • Not read: ISODISTORT's strain modes (_iso_strainmode_*, 4 in case A).
  • The command line: drs isodistort prints each irrep's amplitude and each delta beside the delta computed from the modes.

With this, version 1's P13 is done. The ISODISTORT reader and the computed modes are both checked against ISODISTORT, and the computed modes also against AMPLIMODES (L263).

manuals/bcs/isodistort/ (ISODISTORT 6.12.2, case A: caseA_Sr2ScSbO6_P21n_modes.cif, input_caseA_parent_Fm-3m.cif, input_caseA_distorted_P21n.cif; L275) · GSAS-II master c67a261 imports/G2phase_CIF.py L1145–1183 · core/src/isodistort.rs, main.rs (drs isodistort) · test isodistort · L203, L263, L273, L275
L2772026-10-15work

App stream, 0.36.0: P45 in Refine (parameter impact, the normal probability plot) on core 0.48.0; the core's requests 37 and 38 used

App 0.36.0 on core 0.48.0 (L272):

  • P45 through refine_impacts and refine_normal_probability, the core's impact::impacts, impact_of and normal_probability, run off the main thread. The inspector gains an impact section; Refine ▸ Parameter Impact ranks the parameters; one parameter's change of Ycal is drawn.
  • Request 37: a project's publication set found through the file dictionary's publication_set.
  • Request 38: Fit Peak's Kα doublet through PeakRequest::with_wavelength.

Tests: 154 node and 78 Rust pass; clippy is clean. Not installed.

At the merge (the merging session): the step's self-check had not run. Under L193 it waits for the installed copy to close, and Josu had it open; so did the merging session's install.

branch app 4bafa7d · app/README.md (0.36.0) · L193, L267, L272
L2762026-10-15decision

Josu: the app asks the core for each parameter's phase, atom and label, and keeps no list of its own (no more broken builds when the core adds a parameter)

Four times the core added a kind of refinable parameter (drs::jacobian::Field) and main's app stopped compiling:

  • after 0.37.0 the quartic strain (L228);
  • after 0.40.0 E15's fields;
  • after 0.41.0 the wavelength and micro-absorption (L237);
  • after 0.47.0 the anisotropic size (L270).

The cause: the app's param_label (app/src-tauri/src/modules/refine.rs) matches every Field by hand, and Rust requires the match to be complete.

Decision (Josu). The app asks the core. It uses Field::phase(), which exists (core/src/jacobian.rs:77), and the core gives the rest of what the label needs: whether the parameter is an atom's, the atom's label, and the component's name (x, y, z; β₁₁ …; P1, P2; S400 …; a mode's name). The app keeps no list of its own. A new parameter then needs no change in the app, and shows correctly from the start, as L139 asks: the core works it out, the app shows it.

Set aside: a catch-all arm in the app's match, which compiles but shows a new parameter with a generic label and no reminder to name it.

For the app stream's next step, and for the core if what the label needs is not yet public.

Josu, 15 October 2026 · app/src-tauri/src/modules/refine.rs (param_label) · core/src/jacobian.rs · L139, L228, L237, L270
L2752026-10-15work

ISODISTORT run on case A by the merging session: the mode CIF the core's reader was owed (P13), in manuals/bcs/isodistort/

The core's P13 (L263) reads ISODISTORT's mode CIF but had no file of ISODISTORT's own to check it on. Josu asked the merging session to drive ISODISTORT itself. It did so in the robot Chrome, the isolated profile under Interceptor's preflight, never Josu's browser.

The run (ISODISTORT 6.12.2, iso.byu.edu):

  • The parent: \(\mathrm{Sr_{2}ScSbO_{6}}\) in Fm-3m, a = 8.087481 Å, from the course's 0_1_P21nToCubicTransData.txt. ISODISTORT refuses a CIF without its symmetry operators, so its 192 were written by the core's drs sg "F m -3 m" --all.
  • Method 4, mode decomposition: the 300 K P2₁/n structure with Sb at the origin; ISODISTORT's proposed basis (1/2,1/2,0), (−1/2,1/2,0), (0,0,1); automatic origin; strain and all displacive distortions.
  • The outputs were fetched from within the page, nothing downloaded:
  • the subgroup's mode CIF, with _iso_displacivemode_* for 12 modes;
  • the modes details;
  • the complete modes details.

What it gives:

  • 7 irreps: GM1+, GM3+, GM4+, GM5+, X2+, X3+, X5+, with 12 displacive modes, the core's count for case A (L203);
  • GM4+ E(a) = 0.82937 Å. The core's computed modes give 0.8294 Å, and the mode refinement 0.829 (L273): three routes agree.

The files, the two input CIFs and a README are in manuals/bcs/isodistort/, which is private like the rest of manuals/. The core is to check its reader on them, which closes P13.

manuals/bcs/isodistort/ (README.md) · manuals/bcs/mode-crystallography-manual/course-um6p-2024/04_tools_materials/0_1_P21nToCubicTransData.txt · iso.byu.edu ISODISTORT 6.12.2 · L203, L263, L273
L2742026-10-15decision

Josu: the EHU fonts installed on the Mac, found by LaTeX by name; kept out of git

Josu put EHU's fonts in the Publish worktree's fonts/ folder: EHU Sans and EHU Serif, each in Light, Regular, Bold, Italic, Bold Italic and Black, as .otf and .woff2. Publish looked instead for Fonts/EHU Sans/ inside each project (L262).

Decision (Josu): the fonts are installed on the Mac. Set aside: a fonts folder named in Settings; a copy in each project.

  • The merging session copied the twelve .otf files into ~/Library/Fonts, as Font Book installs them.
  • luaotfload-tool --update was run.
  • LuaLaTeX finds "EHU Sans" by name (/Users/User/Library/Fonts/EHUSans-Light.otf). A test page with \setmainfont{EHU Sans} in Light, Bold, Italic and Bold Italic embeds the four faces.
  • Publish is to find EHU Sans by its name first, as an installed font, and keep a project's Fonts/ as a fallback.

The fonts are licensed and stay out of the repository. /fonts/ and /Fonts/ are in .gitignore, so that no stream sweeps them into a commit.

Josu, 15 October 2026 · ~/Library/Fonts/EHUSans-*.otf, EHUSerif-*.otf · .gitignore · publish/src/latex.rs:99–132 · L258, L262
L2732026-10-15work

Publish stream, drs-publish 0.14.0: plots dropped into Publish; T5 and F6 from the modes the core computes; EHU Sans's files looked for whatever their case

Publish 0.14.0: plots dragged from Refine and Data dropped on Publish made items (F1 of the newest results file, F3 of a one-file plot set); T5 and F6 from the core's computed modes for a phase with no AMPLIMODES file (case A's GM+[3]a 0.8294 Å, its mode refinement's GM4+ 0.829); EHU Sans's files not yet in a project (found whatever their case; a stale luaotfload cache of them forgotten); self-check 117 lines, 99 OK, 1 FAIL (the Editor's spelling).

At the merge (the merging session):

  • the computed modes agree with the mode refinement: case A's GM+[3]a at 0.8294 Å against GM4+'s 0.829 Å;
  • EHU Sans's files are still to be put into a project's Fonts/EHU Sans/ by Josu;
  • the one FAIL, the Editor's spelling, ran in the stream's own build. The merging session's check of app 0.35.0 passed the spelling items (L271).
branch publish 9301f72 · publish/README.md · app/ui/modules/publish/ · L262, L263, L265, L271
L2722026-10-15work

Core 0.48.0, P45: each parameter's impact on \(\chi^2\) and on the calculated pattern, and the normal probability plot of the weighted residuals, against GSAS-II's definitions

Version 2, step 1 of L267's list. The proposal's basis for P45 is GSAS-II's "Parameter impact" and "Normal probability plot". GSAS-II's code is the reference here: GitHub master c67a261, AllPrmDerivs in GSASIIstrMain.py and PlotDeltSig in GSASIIplot.py, with its help (GSAS-II-tutorials MDhelp). The two papers GSAS-II cites, Toby (2024, J. Appl. Cryst. 57, 175–180) and Abrahams & Keve (1971, Acta Cryst. A27, 157–165), are not in manuals/papers/, and the container's network reaches GitHub only. Their definitions are therefore taken from GSAS-II's implementation, not checked against the papers. Josu may want both papers added.

Impact (impact::impacts, impact_of; drs impact). Every parameter is weighed, refined or not: each free parameter of the run, and each value of the model that is neither refined nor fixed by the symmetry (its ties move with it).

  • The slope ∂\(\chi^2\)/∂p is analytic, from the engine's Jacobian. GSAS-II ranks by −∂rms/∂p, the rms weighted residual's slope, which it takes by central differences at a step of max(\(10^{-4}\)|p|, \(10^{-6}\)). The core's slope reproduces that number for all 21 unrefined parameters of \(\mathrm{Y_{2}O_{3}}\).
  • The ranking. A slope carries the parameter's unit, so the core ranks by what refining the parameter alone would do to first order, which has no unit. The shift is δ = Σwrg/Σwg\(^{2}\). In its own esd's that is δ/\(\sigma\)₁, with \(\sigma\)₁ = √(\(\chi^2\)/Σwg\(^{2}\)). \(\chi^2\) would fall by (δ/\(\sigma\)₁)²·\(\chi^2\)/(N − P + C).
  • Where it moves the calculated pattern: the change point by point, its largest value and 2\(\theta\), the change for one esd, and the 2\(\theta\) ranges where the parameter acts.
  • Checks (test refinement_diagnostics):
  • after the engine's convergence on \(\mathrm{Y_{2}O_{3}}\), every refined parameter would move less than 0.05 esd;
  • a zero shift of 0.01° left unrefined ranks first among the unrefined. Its predicted shift is −0.00993° (refined alone, −0.01000°) and its predicted fall of \(\chi^2\) 0.692 (refined alone, 0.699);
  • for a linear parameter (the scale, 1 % off), the predicted fall and shift are the engine's to \(10^{-6}\).
  • Degenerate columns. A column that is rounding noise moves nothing. \(\mathrm{Y_{2}O_{3}}\)'s March–Dollase G1 sits at 1, where the average over a cubic reflection's equivalents cancels its derivative; without this rule it showed a shift of \(10^{13}\).
  • On \(\mathrm{Y_{2}O_{3}}\)'s converged FullProf values, the wavelength ratio and the unrefined B's and occupancies would move by 6–8 of their own esd's if refined, but each lowers \(\chi^2\) by less than 0.006 (on 1.199).
  • Not included: the restraints' terms (P40) are not in the \(\chi^2\) weighed.

The normal probability plot (impact::normal_probability; drs normal).

  • What is plotted: the weighted residuals √w(y − yc) of the points used, sorted, against the expected values of the normal order statistics. GSAS-II includes excluded points as zeros; the core leaves them out.
  • The line: slope and intercept over the central part (expected value within \(\pm\)1, 68.3 % of the points), as GSAS-II fits it.
  • The expected values are computed exactly: the integral of each order statistic's density by the trapezoidal rule, with Φ from Marsaglia's series and the Mills-ratio continued fraction. They agree with mpmath's quadrature at 30 digits (core/tests/data/expected_normal_order_statistics.py) to \(10^{-12}\) for n = 10 and for orders 1 and 101 of 13000, and with \(\pm\)1/√π and \(\pm\)3/(2√π) for n = 2 and 3. Blom's approximation is 0.4 % off at the extremes.
  • Against GSAS-II. GSAS-II takes the expected values from Abramowitz & Stegun 26.2.23 at equally spaced probabilities. On \(\mathrm{Y_{2}O_{3}}\)'s own data, its line is slope 1.0592 and intercept 0.1199; the core's is 1.0530 and 0.1176. The difference is GSAS-II's approximation.
  • On synthetic data (the calculated pattern with Gaussian noise of the stated \(\sigma\)), the slope is 1.0148 and the intercept 0.0053, within their spread of 0.0106. With \(\sigma\) stated at half the noise's, the slope is 2.

For the app: drs impact … --json and drs normal … --json give what a diagnostics panel draws: a table ranked by the fall of \(\chi^2\), each parameter's change of Ycal (with --parameter), and the plot's points.

GSAS-II master c67a261 (GSASIIstrMain.py AllPrmDerivs, GSASIIplot.py PlotDeltSig, GSASIIdataGUI.py OnDerivCalc), GSAS-II-tutorials 924e9eb MDhelp/docs/mainmenu.md, powderparent.md · core/src/impact.rs, main.rs (drs impact, drs normal) · tests refinement_diagnostics, tests/data/expected_normal_order_statistics.py · manuals/fullprof/runs/Y2O3 · the proposal's P45 · L230, L267
L2712026-10-15work

App 0.35.0 installed: the full self-check, run to the app's quit, without a FAIL

App 0.35.0 (L269), with L270's fix for core 0.47.0, is built on core 0.47.0 and drs-publish 0.13.0. Suites: the core 312 pass, Publish 38, the app 77 Rust and 151 node tests. It is installed in /Applications, with 0.34.0 to the Trash.

The self-check, run to "[main] quit:" (L265):

  • on a copy of Y2O3: 85 OK, none FAIL, 143 lines;
  • on a copy of the \(\mathrm{Tb_{2}Pd_{2}Sn}\) buffer: 99 OK, none FAIL, 135 lines.

Seen in the running app:

  • landscape printing on one page of \(842 \times 595\) pt (L266);
  • Edit ▸ Spelling and Grammar's items, sent to the Editor's text;
  • Customize Toolbar…'s Remove and Default Set, kept in the settings;
  • the toolbar hidden and shown again with the layout.

The split groups' reopening is "not checked", since a fresh home has no last session; the app stream's own check, with a seeded session, covered it (L269).

The app branch's lock file for core 0.47.0 (214bdb0) is merged.

DRS_APP_SELFTEST reports of 15 October 2026 · L265–L270
L2702026-10-15work

Main compiles on core 0.47.0: the anisotropic size field named in the app

Core 0.47.0 (L267, P25) added Field::ShapeSize(_), a phase's anisotropic size. The app's param_label (app/src-tauri/src/modules/refine.rs) did not name it, so main did not compile, as after 0.37.0, 0.40.0 and 0.41.0. The merging session added it to the phase's arm, (Some(p), false), as the core's Field::phase gives it, and cargo check is clean. The app stream is told.

Suites before the fix: the core 312 pass, Publish 38.

app/src-tauri/src/modules/refine.rs · core/src/jacobian.rs:64, :77 · L211, L237, L267
L2692026-10-15work

App stream, 0.35.0: landscape printing on one page (L266); Spelling and Grammar; Customize Toolbar…; the tabs' split groups kept with the project

app 0.35.0 (app stream): L266's landscape print fixed (the figure within the page's height less Page Setup's margins; one page in the running app); Edit ▸ Spelling and Grammar with AppKit's own items and selectors (§3); View ▸ Customize Toolbar… (§1, §3); the tabs' split groups kept and restored with the windows (P46's rest); one self- check to its quit 90 OK, 4 FAIL (one the app's, a split kept late, and three the check's own), fixed after it.

At the merge (the merging session): the app's requests 37 and 38 are met by core 0.47.0 (L267). The app was on main before it, so it still reported them unmet.

branch app 63b4630 · app/README.md (0.35.0) · app/REQUESTS.md 37, 38 · L266, L267
L2682026-10-15work

Publish stream, drs-publish 0.13.0: Word manuscripts for the journals that take Word (Acta C, IUCrData, Powder Diffraction)

Publish 0.13.0: Word manuscripts for Acta C, IUCrData and Powder Diffraction (tables and text parts as .docx beside the LaTeX, figures in each journal's formats, Powder Diffraction's TIFF CMYK/1-bit); the Symbol-font Greek unchecked in Word itself (for Josu); self-check 105 lines, 84 OK, 5 FAIL all the shell's (⌘Q/⌘R recorder, Fit Peak on Tb2Pd2Sn, the plot's menu).

branch publish 6cef7a4 (e187503) · publish/ · L257, L262
L2672026-10-15work

Core 0.47.0: the app's requests 37 and 38; P25's rest against FullProf (prba10k's needles, the tetragonal and monoclinic strain of lac and cuf1k, Pd3MnD's antiphase domains, which are not spherical harmonics); FullProf's Sycos multiplies cos 2\(\theta\), not its manual's cos \(\theta\)

The app's request 37. The Publish module's document, Publish/{name}.drp, is in the file dictionary (files.yml, id publication_set, as publish/DESIGN.md §5.1 proposes). The core knows it by its format line ("drs-publish set 1") and by its extension. The placeholder {name} is new. Test file_dictionary::publication_set.

The app's request 38. peakfit::doublet_of(&Wavelength) gives (λ₂/λ₁, the intensity ratio) from the scan's own wavelengths, or nothing when the scan states one wavelength or a λ₂ without its ratio; no ratio is assumed. PeakRequest::with_wavelength and drs peak … --doublet scan use it. Test peak_fit::doublet_from_the_scan.

P25's rest. FullProf's runs of prba10k, cuf1k and lac (L216) are on branch fullprof-p25, which is already an ancestor of main (L222 took the runs off main to keep its counting tests green). A merge brings nothing back, so the three folders were restored from the branch. Every new file is counted. Each new piece was checked against FullProf 8.20, reflection by reflection, on the widths of the calculating pass's .out (Hw, HwG, HwL, ETA), within half a unit of the print and 2·\(10^{-5}\) (test fullprof_anisotropic_size). Each check bites: without the new term, 195, 14, 57 and 22 rows miss.

  • prba10k (P 4/m m m, λ = 0.44 Å): needles along [0 0 1] (Size-Model −1, LorSiz with g = sin φ) and the tetragonal quartic strain (S_400, S_004, S_220, S_202 and ξ). Its 207 rows are within 0.78 of the tolerance. The pattern gives \(\chi^2\) 18.5532 against FullProf's 18.55.
  • lac (P 4₂/n c m) and cuf1k (P 2₁/n): the tetragonal and monoclinic quartic strain, within 0.27 and 0.26 of the tolerance. Their patterns are not compared, because both have a magnetic phase.
  • Pd3MnD. L038 called its size model spherical harmonics. It is not: its Size-Model is −14, FullProf's rule for the antiphase domains of \(\mathrm{Cu_{3}Au}\). It broadens only the reflections whose h, k, l are of mixed parity. Its 33 rows are within 0.12 of the tolerance, and its \(\chi^2\) is 1.7008 against FullProf's 1.699. Spherical harmonics (Size-Models 15–22) are refused: no run in the repository uses them.
  • The quartic form for any Laue class. The core now builds it from the group's rotations (the orbit sum of each monomial). It gives FullProf's LINE 37 terms for 4/mmm, −3m, 6/m, 6/mmm, m−3 and m−3m (manual pp. 119–120). It also gives the invariants FullProf leaves out: 4/m's S_310 (h\(^{3}\)k − hk\(^{3}\)) and −3's S_121. The .pcr export refuses a non-zero S_310. The trigonal, hexagonal and cubic forms are not yet checked against a FullProf run.
  • FullProf's Ste. prba10k's \(\chi^2\) first came out twice FullProf's: FullProf reads every Ste-th point (Ste = 2, manual p. 64). The import now adds the data operation rebin with mode keep, and the .pcr export writes it back as Ste with the data as read.
  • The peak window (L035, L042). It now includes the anisotropic size, as Y/cos \(\theta\) is included. It leaves out the Lorentzian strain ξ·D_ST tan \(\theta\), as X tan \(\theta\) is left out. It takes the extra low point when X ≠ 0 or ξ ≠ 0. One rule for both runs fits only so far: single edge points stay, frozen in fullprof_profiles (prba10k: point 592 at 0.2337, Σ-relative 6.19·\(10^{-4}\); pdfinal: point 166 at 9.579·\(10^{-3}\), 1.273·\(10^{-3}\)). The Bérar factor of prba10k (0.156) is frozen in berar_factor, and pdfinal's Durbin–Watson d (1.1064 against 1.11) in fullprof_summary.
  • highpress stays out. Its Sol = 1 asks for hkl-dependent peak shifts (P26), which the import refuses. Before this, the reader had taken the shift line for the platelets' vector.

Finding: FullProf shifts by Sycos·cos 2\(\theta\). cuf1k's .hkl angles missed its .prf ticks by 0.0002°–0.0035°. NdNi-HTO's, left unresolved in L098, missed by 0.004°–0.164°. These are the only two runs with Sycos ≠ 0. A fit of Zero, Sycos and Sysin to the ticks with Sycos·cos \(\theta\) (the manual's eq. 3.37, p. 56) leaves 2.4·\(10^{-4}\)° and 0.019°. With Sycos·cos 2\(\theta\) it gives back the .prf's own values (−0.12701, −0.00316, 0.13128 and −0.12737, 0.14539, 0.06421) within \(10^{-4}\)°. The profile settles it. NdNi-HTO's nuclear phase against FullProf's NdNi_HTO_calc1.sub is 4.2·\(10^{-6}\) away (relative Σ of squares) with cos 2\(\theta\) and 5.1·\(10^{-2}\) with cos \(\theta\), up to 404 counts. By L043 the default keeps the manual's cos \(\theta\), which is the displacement aberration's own form. FullProf's rule is Convention::displacement: the control file's displacement in [convention], on with --fullprof. The import into the default rules keeps it where Sycos ≠ 0, since cos 2\(\theta\) has no exact translation into cos \(\theta\). Left over, frozen in test fullprof_displacement: 203 of 2990 points beyond the .sub's print (half a count), by up to 1.95 counts. 193 of them lie within 0.15° of a peak window's edge, and NdNi-HTO's windows (X and the D2B resolution file) are not FullProf's to the point. Not resolved. The .hkl tick check now covers both runs (fullprof_outputs).

Names in the outputs (request 36). FullProf's StrainN, LorStr and Ani-LSiz now name the N-th S_HKL in FullProf's order, ξ and the anisotropic size: 2730 of 2772 names map to their number's field. Pd3MnD has two atoms labelled MN, so Biso_MN_ph1 names parameters 4 and 6. The new output_names::fullprof_fields gives every atom a name can mean, and the parameter number tells them apart.

For the merging session: the app does not compile on core 0.47.0 until Field::ShapeSize(p) joins the phase fields of the match in app/src-tauri/src/modules/refine.rs (line 749), as L237 did for the wavelength and micro-absorption fields. Field::phase() gives the same answer for every field. Publish compiles.

ISODISTORT's mode CIF (L263) is still read only from a fixture written to GSAS-II's tags. A file of its own would settle it: ISODISTORT's CIF with modes ("Save … CIF", modes included) and its "mode details" page for one case of the course's AMPLIMODES analyses with the same parent and distorted structures, e.g. case A (\(\mathrm{Sr_{2}ScSbO_{6}}\), F m −3 m → P 2₁/n), saved in manuals/bcs/isodistort/. For the merging session to ask Josu.

manuals/fullprof/runs/{prba10k,cuf1k,lac} (branch fullprof-p25, L216), Pd3MnD, NdNi-HTO (NdNi_HTO_calc1.sub), highpress · FullProf_Manual.pdf pp. 23–24, 56, 64, 119–120 · core/src/microstructure.rs (SizeShape, ShapeSize, quartic_forms), powder.rs (Convention::displacement, size_of, peak_window_width), jacobian.rs, pcr.rs, data_ops.rs (RebinMode::Keep), control/pcr_export.rs, output_names.rs, peakfit.rs, formats/project.rs · tests fullprof_anisotropic_size, fullprof_displacement, fullprof_anisotropic_strain, fullprof_outputs, fullprof_profiles, berar_factor, fullprof_summary, output_names, engine_derivatives, file_dictionary, peak_fit · app/REQUESTS.md 37, 38 · L035, L038, L042, L043, L098, L216, L222, L263
L2662026-10-15work

App 0.34.0 installed; the first self-check run to the app's own quit: one FAIL, landscape printing gives two pages

App 0.34.0 (L265) is built on core 0.46.0 and drs-publish 0.12.0. Suites: Publish 36, the app 77 Rust and 146 node tests; the core passed 299 on its last change (L264).

The install waited for the Publish stream's own self-check to end, under L193's one copy of the app at a time. It is installed in /Applications, with 0.33.0 to the Trash.

The self-check, for the first time to "[main] quit:" (L265):

  • on a copy of Y2O3: 80 OK and 1 FAIL, 138 lines;
  • on a copy of the \(\mathrm{Tb_{2}Pd_{2}Sn}\) buffer: 94 OK and 1 FAIL, 130 lines.

Seen in the running app for the first time:

  • Copy as an image puts PNG and TIFF on the pasteboard;
  • the plot prints on one A4 page, its SVG with a viewBox and as wide as the page;
  • the Editor prints all 244 numbered lines of y2o3_structure.drc on 5 pages.

The FAIL, in both, is the app's: with Page Setup in landscape, Print… gives 2 pages of \(842 \times 595\) pt, the second blank. App 0.33.0 had fixed this in its tests only (L261, L264). Passed to the app stream.

DRS_APP_SELFTEST reports of 15 October 2026 · L261, L264, L265
L2652026-10-15work

App stream, 0.34.0: drag and drop between modules as a shared service; every control named for VoiceOver with a tooltip; the three FAILs of L264 fixed; the merging session's self-check found to stop too early

app 0.34.0 (app stream): the installed check's three FAILs (L264) fixed (Copy through AppKit's pasteboard; Fit Peak around a peak the core finds; Print and the drops before the long waits, the launcher's stop markers came before them); drag and drop between modules as a shared service (§7, §11); every control named for VoiceOver and with a tooltip, plots described in words (§5, §10); one self-check 84 OK, 5 FAIL, each fixed after it.

The merging session's self-checks stopped too early, found by the app stream. Its runs stopped the app 3 s after "[tab] done", or after Plots' "seq" lines. The main window's last checks come after both: Print, the Settings window, the disk watch and the quit. So the installs 0.30.0–0.33.0 were checked without them, which is also why 0.33.0's Print "did not run" (L264). From now on the merging session waits for "[main] quit:": the app quits by itself, 1–2 minutes after "[tab] done". It stops the process by its PID only as a timeout.

For Publish, Plots and Explore: each registers its own drop targets with dropTarget(el, { module, accepts, drop }) from ui/dnd.js. The convention's "a plot into Publish" is Publish's target, for the plot items that Data and Refine already carry.

branch app ac49503 · app/ui/dnd.js, a11y.js · app/README.md (0.34.0) · APP_CONVENTIONS.md §5, §7, §10, §11 · L264
L2642026-10-15work

Version 1's 13 partly done features met (L230); app 0.33.0 installed and self-checked, its three FAILs the check's own

Version 1 (L230). With core 0.46.0's step D (P13, L263), the 13 features the milestone found partly done are met. Version 1's 39 features are done.

| Feature | Where | |---|---| | P09 symmetry elements and the space-group view | core 0.45.0, app 0.26.0 and 0.33.0 | | P11 the user's scattering factors | core 0.41.0, 0.43.0 | | P13 modes computed from the structures, ISODISTORT read | core 0.46.0 | | P18 wavelength refinement | core 0.41.0, against FullProf L255 | | P29 surface micro-absorption | core 0.41.0, 0.43.0 | | P33 amorphous content | core 0.44.0 | | P35 extracted intensities | core 0.44.0 | | P43 the Bérar factor | core 0.42.0 | | P46 windows and tabs reopened | app 0.26.0 | | P48 atoms and phases added | app 0.28.0 | | P49 completion and the jump to a parameter | core 0.45.0, app 0.28.0 and 0.33.0 | | P51 Refine's plot in d/Q and √/log; single-peak fitting | app 0.26.0; core 0.45.0, app 0.33.0 | | P57 the listing and summary | core 0.44.0 |

One check is still owed: ISODISTORT's mode CIF is read but not checked on a file of its own, because the repository has none.

App 0.33.0 is built on core 0.45.0 with drs-publish 0.12.0. Suites: the core 299 pass, Publish 36, the app 77 Rust and 141 node tests. It is installed, with 0.32.0 to the Trash. The self-checks under L193:

  • on a copy of Y2O3: 50 OK and 1 FAIL. Fit Peak between 28.8 and 29.6° gives the position 29.1683(7)° and the FWHM 0.1426(19)°;
  • on a copy of the \(\mathrm{Tb_{2}Pd_{2}Sn}\) buffer: 66 OK and 3 FAIL.

None of the FAILs is a fault of the app; each is the check's own:

  • Copy as an image: the web view refused the clipboard (NotAllowedError). A page writes to the clipboard only right after a user's own action. ⌘C pressed by a user is one; the scripted check is not, and the window was not in front. The check should send ⌘C through the menu, or say "not checked" when it cannot.
  • Fit Peak on \(\mathrm{Tb_{2}Pd_{2}Sn}\): the check took Y2O3's window, 28.8–29.6°, where \(\mathrm{Tb_{2}Pd_{2}Sn}\) has no peak; its peaks are near 19, 26.5, 32.5 and 42°. Two lines fail. The check should fit around a peak the core finds.
  • Print and Page Setup did not run in this self-check, so app 0.33.0's three print fixes are not yet seen in the running app.

All three go to the app stream.

core 0.41.0–0.46.0 · app 0.26.0–0.33.0 · DRS_APP_SELFTEST reports of 15 October 2026 · L230, L255–L263
L2632026-10-15work

Core 0.46.0, version 1's step D (P13): symmetry modes computed from a parent and a distorted structure, as AMPLIMODES computes them on the course's files; ISODISTORT's mode CIF read, not yet checked on a file of its own

The modes from the structures. New module mode_decomposition (decompose; drs decompose). From the parent, the distorted structure and the transformation (P, p), it computes what AMPLIMODES computes: the reference structure, the displacements, and for each irrep of the parent's group G that the distorted group H keeps:

  • its k-vector star, parity and dimension;
  • its number of modes on each parent orbit;
  • its modes, with the amplitude of each, normalised in the distorted phase's primitive cell (AMPLIMODES's normalisation, L090).

It needs no table of irreps: 1. The displacements are taken over a supercell nZ\(^{3}\) that the whole of G keeps, n being the least integer that makes n·P\(^{-1}\) integral. 2. A symmetric combination of G's class sums, with fixed pseudo-random weights, has the isotypic components as its eigenspaces. The same weights on every orbit give the same eigenvalue to the same irrep. 3. The modes are the vectors that H's projector leaves fixed in each component.

The irrep's dimension comes from its character: Frobenius–Schur's (1/|G|)Σ\(\chi\)(g\(^{2}\)) and (1/|G|)Σ\(\chi^2\). Its star comes from the projectors of the translations.

Labels such as GM4+ need tables of irreps, so a computed irrep is named from its k, parity and dimension ("GM+[3]a"). It takes AMPLIMODES's name when AMPLIMODES's modes are given (name_by_modes: each of AMPLIMODES's modes lies in exactly one computed irrep).

Against AMPLIMODES (test mode_decomposition, the course's 04_tools_materials/0_1…0_4):

  • The ten analyses that give both structures: the 56 irreps of their summaries are matched one to one, with the same k, parity, dimension and number of modes. The number of modes on each orbit also matches.
  • Amplitudes from the input: the global distortion and 54 of the 56 amplitudes agree to the 4 decimals printed. Two exact halves count as agreeing: 0.503650 is printed 0.5036 and 0.029350 is printed 0.0294.
  • The 2 others: GM5+ in analyses 1 and 2 of 0_4, 0.0229 against 0.0226 and 0.0224 against 0.0221. The cause is AMPLIMODES's own adjustment of the input: its atom mappings place Sr at x = 0 and z = 0.24923, where the input has −0.00003 and 0.2492. From the positions of its atom mappings, all 56 agree. The 2 are frozen (FROM_THE_INPUT).
  • Analysis 2 of 0_1: its irreps are named by the 12 modes of analysis 3's block for FullProf. Each mode has norm 1 within 2·\(10^{-5}\) and lies in its computed irrep and orbit within 2·\(10^{-5}\). Each amplitude of A_MODES is the computed one within 2·\(10^{-6}\), up to the sign where the irrep has one mode on the orbit, and as the norm of the pair where it has two.
  • The computed modes as the refinement's Modes: they put every atom back where the distorted structure has it, and the refinement's own project gives the same amplitudes.
  • The block for FullProf: the block the core writes (atoms with FullProf's Occ = m/M, V_MODES, A_MODES), read back, names the same irreps.
  • A supercell case (n = 2): \(\mathrm{SrTiO_{3}}\)'s tilt, Pm-3m → I4/mcm, comes out as one mode at k = (1/2,1/2,1/2) of dimension 3, with the closed-form amplitude 4δa. It is odd with Sr at the parent's origin and even with Ti there, since the inversion moves by (1/2,1/2,1/2). The literature's labels for these, R5− and R4+, are believed and not checked: no table of irreps is in manuals/.
  • Refused: an origin that makes H no subgroup, and a structure that does not fill the reference's sites.

ISODISTORT. New module isodistort (drs isodistort) reads its mode CIF to the tags GSAS-II's importer reads:

  • the modes, with ID, label, amplitude and normalisation;
  • the deltas and the coordinate formulas;
  • the mode matrix.

It also splits the label into parent[k]irrep(direction)[atom:Wyckoff:kind]point-irrep. It is not checked on a file ISODISTORT wrote: none is in manuals/, and this session's network reaches only package registries (iso.byu.edu and GitHub outside this repository are refused). Its test (isodistort) reads a fixture written by the core stream and marked as such.

For Josu or the merging session: ISODISTORT's output for one of the course's cases, with the same parent and distorted structures as an AMPLIMODES analysis, both the CIF and the mode details as text, put in manuals/bcs/isodistort/. With it, the reader is checked against the file and against the decomposition. Until then, P13's ISODISTORT reading is partial.

What is not computed: the number in an irrep's label, its isotropy subgroup and the direction of its order parameter (AMPLIMODES's "Direction", "Isotropy Subgroup"). They need the irreps' matrices in the standard basis of the tables. A polar H's uniform translation is taken off the displacements (their mean); no polar case is in the files to check AMPLIMODES's choice against.

core/src/mode_decomposition.rs, isodistort.rs · core/tests/mode_decomposition.rs, isodistort.rs, data/isodistort_fixture.cif · manuals/bcs/mode-crystallography-manual/course-um6p-2024/04_tools_materials/0_1…0_4 (AMPLIMODES) · Orayech et al. 2015, J. Appl. Cryst. 48, 318 · Campbell et al. 2006, J. Appl. Cryst. 39, 607 · GSAS-II changeset 5067 (the ISODISTORT tags) · L090, L230, L260
L2622026-10-15work

Publish stream, drs-publish 0.12.0: each preset in its journal's or university's typeface, typeset by LaTeX (L258); EHU Sans's files needed from Josu

Publish 0.12.0: each preset in its journal's or university's typeface by LaTeX (Termes for Times, Heros for Helvetica, Arimo for Arial, EHU Sans for a TFG from Josu's files, Latin Modern otherwise); the check of L103 and check 8 follow the face; the module's diskChanged/reloadTab hooks; self-check 79 lines, 70 OK, no FAIL.

The faces are free equivalents:

  • TeX Gyre Termes for Times;
  • TeX Gyre Heros for Helvetica;
  • Arimo for Arial;
  • EHU Sans for a TFG, from a project's Fonts/EHU Sans/. No font file is in the repository: EHU Sans's files are Josu's to put there, and until then a TFG is set in Latin Modern, which the build says.
branch publish b3c1690 (866c02c) · publish/README.md, DESIGN.md §2.4 · L103, L258
L2612026-10-15work

App stream, 0.33.0: version 1's step C in the app (completion P49, Fit Peak P51, the symmetry elements P09, requests 35 and 36 closed); the document types in Finder and the Dock; Print and Page Setup; requests 37, 38

app 0.33.0 (app stream): version 1's step C in the window on core 0.45.0 (P49 completion, P51 single-peak fitting with esd's, P09 symmetry elements, request 36's names; requests 35 and 36 closed, 38 filed: the doublet's λ₂/λ₁); Publish's Settings FAIL fixed (timers of a window behind are slowed; the main window now drives the put-back); the app's document types and Finder/Dock opening (§7) and Print/Page Setup (§3); one self-check 81 OK, 1 FAIL in the check's own input, fixed.

branch app fcba3f7 · app/README.md (0.33.0) · app/REQUESTS.md 35–38 · APP_CONVENTIONS.md §3, §7 · L260
L2602026-10-15work

Core 0.45.0, version 1's step C: the symmetry elements of every space group, single-peak fitting with esd's, completion for the control file's editor, FullProf's and the CIF's names as the model's parameters

P09, the symmetry elements (the app's request 35). Module symmetry_elements: for each operator {R|t} of a group, its element as the International Tables, Vol. A, list it:

  • the kind (identity, translation, centre, rotation, screw, mirror, glide, rotoinversion), the symbol (1, −1, 2, 2₁, 3₁ …, m, a, b, c, n, d, g, −3 …) and the sense (+ or −) of 3-, 4- and 6-fold axes;
  • the intrinsic translation w = (1/k)ΣRʲt, and the location, the fixed points of {R|t − w}, written as ITA prints them ("0,y,1/4", "x,1/4,z"; a rotoinversion's axis and its inversion point, "0,0,z; 0,0,0");
  • a screw's subscript and a glide's letter from the shortest lattice vector along the axis or in the plane, the centring vectors included.

The independent source is cctbx (cctbx-base 2025.11, rot_mx_info and translation_part_info), run by tests/data/make_symmetry_elements.py over spglib's operators of the 530 settings, centring included (symmetry_elements_530.txt). Test symmetry_elements compares all 7388 operators: the rotation's order and sign, the axis (up to its sign) and the sense, the intrinsic translation exactly, and cctbx's point on the core's element. None differs. The screw and glide letters of the full Hermann–Mauguin symbols are among the elements' symbols in 527 of 529. The two apart are R 3 c and R −3 c in rhombohedral axes: their glide's translation is (1/2, 1/2, 1/2), which the core names n (ITA's diagonal glide, ½(a + b + c)), where the symbol keeps the c of the hexagonal axes. This count is frozen. P 2₁/c, P 2₁ 3, P −4 2₁ c and P n m a are checked line by line against ITA's lists. drs sg prints each operator's element.

P51, single-peak fitting. Module peakfit: one pseudo-Voigt (the core's, FullProf manual eq. 3.13) over a linear background on a 2\(\theta\) range.

  • η is refined, or held at 0 (Gaussian) or 1 (Lorentzian); a Kα₂ peak at the same width and shape is added when asked.
  • The fit is weighted least squares by Levenberg–Marquardt with exact derivatives (dual numbers).
  • The position, integrated intensity, FWHM, η and background each come with the esd √(C_kk \(\chi^2\)_ν), as the engine's. drs peak.

Test peak_fit:

  • The esd's are calibrated. On 400 synthetic peaks with counting noise, single and Kα doublet, each parameter lies within one esd of the truth in 62.5–70.8 % of the trials (68.3 % expected), with a mean deviation of at most 0.12 esd.
  • Against FullProf: \(\mathrm{Y_{2}O_{3}}\)'s isolated reflections above 30° (y2o3_structure_calc.out), fitted one by one with the doublet. The 23 peaks whose intensity is at 20 esd's or more lie within 4.2 esd's of FullProf's 2\(\theta\) + Zero. Their width is within 19.5 % of FullProf's Hw and their intensity within 8.8 % of its ΣIcalc. The fit is one free pseudo-Voigt per peak; FullProf's peaks come from U, V, W across the pattern, with Bérar–Baldinozzi asymmetry and η up to 1.0076. The bounds are frozen (L037).

P49, completion for the editor. Module control::complete (complete(text, line, column); drs complete) gives the names valid at a place of a .drc:

  • at a [: the tables, less the single ones already written;
  • at a key: the keys of the table the line is in, less those it holds, each with the dictionary's name, unit and meaning (an operation's own settings in [[pattern.operation]]);
  • at a value:
  • the closed choices;
  • the species of the scattering tables;
  • the 530 settings, each by a symbol that reads back as that setting (the short symbol, else the full one, else the short one with its choice);
  • the model's settable addresses in parameters, a step's free and a link's value and follows, on a line of a multi-line array too.

The tables and keys come from the reader's own lists (schema::TABLES, schema::choices). Test completion:

  • 144 keys over the 17 tables are offered, and each is read without "is not a key of";
  • the tables offered are each read;
  • \(\mathrm{Y_{2}O_{3}}\)'s 94 settable addresses are each read back by field_of;
  • every choice offered is read;
  • the 530 symbols are each read back as their setting.

Found on the way: the operation table's key is op, and preferred_orientation_function belongs to [pattern].

The app's request 36, the names of the outputs. Module output_names (drs name):

  • FullProf's symbolic names (Scale_ph1_pat1, Cell_A_ph1_pat1, X_O1_ph1_pat1, Biso_, Occ_, betNN_, Bck_n, Asym<n>, the modes' A<k>_<irrep>, P0_mabs, Cp_mabs, Tau_mabs);
  • the CIF's tags of a phase's block (_cell_length_a …, the _atom_site_ and _atom_site_aniso_ columns with the row's label, _diffrn_radiation_wavelength).

Each is mapped to the model's field and address.

Test output_names:

  • Of 126 .out files of manuals/ with a parameter list, the core builds 107 models.
  • Of their 2702 names, 2660 map to a field that their parameter number's codeword carries in the model, and none to another number's field.
  • The 42 others name what the core does not refine: λ₂'s own U, V and W, rigid bodies, and a reflection's own broadening.
  • In 12 files a number is none of the .pcr's codewords, each named with its reason: another version of the .pcr (citric acid); LaB6's VARY ratio (L071); YVO3a's run, which refined U where the .pcr beside it does not.
  • In the core's results file for \(\mathrm{Y_{2}O_{3}}\) (3 values) and rutile + anatase (13), every value with an su names a refined parameter.

The app's requests 35 and 36 are met in the core; the app stream closes them.

core/src/symmetry_elements.rs, peakfit.rs, control/complete.rs, output_names.rs · core/tests/symmetry_elements.rs, peak_fit.rs, completion.rs, output_names.rs · core/tests/data/make_symmetry_elements.py (cctbx-base 2025.11) · International Tables for Crystallography Vol. A (2016) §1.2.2 · app/REQUESTS.md 35, 36 · L037, L071, L230
L2592026-10-15work

App 0.32.0 built on core 0.44.0 with drs-publish 0.11.0, installed and self-checked: no FAIL

App 0.32.0 is built on core 0.44.0 (version 1's step B: P33, P35, P57; L256), with drs-publish 0.11.0 (L257).

Suites: the core 286 pass, Publish 35, the app 75 Rust and 133 node tests.

Installed in /Applications, 0.31.0 to the Trash. The self-checks under L193:

  • on a copy of Y2O3: 56 OK, none FAIL;
  • on a copy of the \(\mathrm{Tb_{2}Pd_{2}Sn}\) buffer: 72 OK, none FAIL.

Version 1 (L230):

  • done: P11, P18, P29, P33, P35, P43, P46, P48, P57;
  • partly done: P09 (the view done, the symmetry elements in the core's step C), P49 (the jump done, completion in step C), P51 (the plot done, peak fitting in step C);
  • P13 is the core's step D.
DRS_APP_SELFTEST reports of 15 October 2026 · L230, L256–L258
L2582026-10-15decision

Josu: a Publish preset follows its journal's or university's typeface, all still typeset by LaTeX (L103 amended); the λ path's difference accepted, frozen (L255)

The typeface (L103 amended). drs-publish 0.11.0 checked every preset against its journal's or university's own instructions (L257):

  • ACS requires Helvetica or Arial;
  • the IUCr and Elsevier list Arial, Helvetica or Times;
  • EHU's TFG regulation (July 2024, art. 4.1.a) asks students to try to use ("procurar") the university's typeface.

L103 had every character in Latin Modern, journals included. Josu decided: each preset follows its journal's or university's face:

  • ACS: Helvetica or Arial;
  • the IUCr and Elsevier: Times or Arial, as their instructions give;
  • EHU: its typeface for a TFG.

Everything is still typeset by LaTeX: every character of a figure or table, labels, ticks, legends, units, formulas and digits, with no text drawn outside LaTeX. Latin Modern stays where no journal or university names a face. Set aside: Latin Modern everywhere, with the journals' faces as known departures; the journal's face only where it is required. CLAUDE.md's Publish paragraph is changed to say so.

The λ path (L255). In FullProf's own refinement of λ, FullProf drifts to U < 0 and clamps the Gaussian width above 154°, ending at \(\chi^2\) 17.1. The core ends at 3.71 with the same λ, within 0.1 esd. Josu accepts the core's \(\chi^2\), 1.2–1.6 % below FullProf's from cycle 5, frozen in LAMBDA_PATH, the test failing if it grows (L037). Set aside: proving FullProf's clamping before step C.

Josu, 15 October 2026 · publish/DESIGN.md §2.4 (the presets' sources) · core/tests/pattern_terms.rs (LAMBDA_PATH) · L037, L103, L255, L257
L2572026-10-15work

Publish stream, drs-publish 0.11.0: SVG beside every PDF; tables as Markdown, HTML and CSV; every preset checked against its journal's or university's own instructions

Publish 0.11.0: SVG beside every PDF; tables as Markdown, HTML and CSV; scripts at the preset's minimum; every preset checked against its journal's or university's own instructions on 2026-10-15 and cited (for Josu: the EHU typeface in TFG figures, Word manuscripts at Acta C/IUCrData/Powder Diffraction, Powder Diffraction's publisher, APS's 1 mm points); self-check 87 lines, 75 OK, 1 FAIL (the shell's Settings round trip).

At the merge (the merging session): the stream's commit named this entry L253, the app's. It is L257. The self-check's one FAIL, the shell's Settings round trip, is passed to the app stream.

branch publish 6908555 (2a14871) · publish/DESIGN.md §2.4 · publish/README.md · L103, L248
L2562026-10-15work

Version 1's step B: the amorphous content with an internal standard (P33), the file of extracted intensities in FullProf's format (P35), the summary and listing of a run (P57), each against FullProf's or the paper's numbers

Core 0.44.0.

The amorphous content (P33). FullProf has no option for it: its manual covers only ATZ and the Brindley coefficient. Its QPA tutorial carries the paper, X. Orlhac et al., J. Appl. Cryst. 34, 114–118 (2001), so the paper decides (L043).

  • A phase can be the internal standard: the control file's internal_standard_weight_fraction, the weight fraction of it weighed into the sample.
  • qpa::amorphous_content gives the fractions in the mixture with the standard (eq. 3), the fractions in the sample without it (eq. 4), and the amorphous content of each. The esd's are propagated through the covariance; the paper's esd is the plain sum of the phases' (Table 3: 0.10 + 0.10 = 0.20, …), an upper bound.
  • Checked against the paper's Table 3: from its crystalline fractions and 8 wt% \(\mathrm{TiO_{2}}\), the core returns its amorphous column (98.04, 95.98, 96.76, 94.99 wt%) to its 0.01.
  • Checked on FullProf's rutile + anatase, with anatase as the standard. At its refined fraction the amorphous content is 0. At 0.9 of it, the closed form 1 − k(1 − W)/W (12.47 \(\pm\) 0.98 %), and the esd is the closed form's derivative times W's esd, to \(10^{-4}\).
  • drs refine prints the content. When the standard was weighed above the fraction the refinement gives it, it says that the content is negative and why.
  • Brindley's \(\tau\) is interpolated in the table FullProf's manual reproduces (p. 31). The paper's eq. 8 polynomial stays within 0.0043 of that table for |(μ_j − μ̄)R| \(\le 0.1\).

The file of extracted intensities (P35). FullProf's CODFILn_cltr.int (Jbt = 2, Jvi = 11), with the cluster rule its manual gives: reflections i and i + 1 are one cluster if ΔT < ½(H_i + H_i+1)·Rmub, or if ΔT < ½(H_i + H_i+1) and G_i+1 < G_sum·Rmuc (Rmub 1.0 and Rmuc 0.2 when the .pcr gives zeros).

  • Members of a cluster are written with G = −1 and their own \(\sigma\). The last reflection carries the sum, with the root of the sum of the \(\sigma\)²'s.
  • Against FullProf's four .int files whose .hkl is beside them, the core writes each from that .hkl: LaMnO3's 414 lines are the same. In PPH3 and the two citric-acid runs every line is the same except FullProf's floor and one tie:
  • FullProf writes a near-zero observation as G = 0.0001 with \(\sigma\) = 1.0 (14, 4 and 5 lines), a rule not in its manual. The core keeps the value, and writes FullProf's line only where \(\sigma\) is 0.
  • One PPH3 cluster boundary is a tie at the .hkl's print (ΔT = ½(H + H) = 0.0356).
  • The core's own extraction of LBF_lmopr, at FullProf's final values and intensities, gives FullProf's 122 clusters of its 414 reflections, every cluster's G within 0.26 of FullProf's \(\sigma\). Its \(\sigma\)'s are within −0.9 … +1.5 %, median +1.3 %, but two: 0 1 1 at the data's edge (−33 %) and one at 125° (+9.8 %). FullProf's \(\sigma\) formula is not in its manual (L100).
  • drs results --exports writes Exports/<control> <phase> intensities.int for each Le Bail phase (the file dictionary's export_intensities).

The summary and the listing (P57). listing::summary and summary_text: FullProf's .sum in the core's words. The agreement is given over all the points and over those with Bragg contributions: Rp, Rwp, Rexp, \(\chi^2\), the conventional R's, the deviance D and D/(N − P + C), the Durbin–Watson d and its bound Q_D (manual eq. 3.20), and the goodness of fit in \(\sigma\)'s. Then the global \(\chi^2\), the Bérar factor, each phase (reflections, R_Bragg, R_F, volume, weight fraction), the amorphous content and the parameters. listing_text adds each cycle: its agreement, the steps refused, the steering's notes, every parameter's value, shift, esd and shift/esd. drs results writes both, Results/<control> <time> summary.txt and listing.txt.

Against FullProf's .sum of the 68 runs of fullprof_profiles (test fullprof_summary):

  • The all-points block agrees in 55 runs, every item within FullProf's print and 0.2 %. The 13 others are named with their cause: P counted differently in the mode course's runs and case E; LaMnO3ref and YVO3a_calc, already apart in fullprof_profiles; LBF_lmopr's Le Bail intensities; a print's edge in citric acid; four window experiments.
  • FullProf's "N-sigma of the GoF" is (\(\chi^2\) − 1)/√(2/(N − P + C)), read from its values; its manual does not define it.
  • **Its "Dev\*"**, not defined either, is D/(N − P + C) in 50 runs, but D/N in \(\mathrm{Y_{2}O_{3}}\)'s: the core gives the first.
  • The Bragg block's d is, in FullProf, the numerator over all the points divided by S over the Bragg points (47 runs). The core keeps the paper's d over the block's own points.
  • Counted only: FullProf's Bragg points, its global \(\chi^2\) "(Bragg contrib.)" and, from them, the Bérar factor (25 within 0.05 % with the core's Ycal; 60 with FullProf's own, berar_factor) differ where its peak windows do (L042). R_Bragg and R_F agree in 63 of 148 phase values: FullProf takes I calc and I obs within its window, the core whole (L100).
  • The density FullProf prints is not given: the core has no atomic masses yet.

In the core: lsq::bragg_points and lsq::statistics_over.

manuals/fullprof/5-Quantitative_Phase_Analysis/Quant-amorphous.pdf (Orlhac et al. 2001, eqs. 3–4, 7–8, Table 3) · manuals/fullprof/FullProf_Manual.pdf (p. 31 Brindley's table; output files, CODFILn.int; note on Jvi = 11; eqs. 3.17–3.20) · the four _cltr.int files with their .hkl (LaMnO3, PPH3, citric acid \(\times 2\)) · the .sum of the 68 compared runs · core/src/qpa.rs, lebail.rs, listing.rs, lsq.rs, powder.rs, control/schema.rs, parameters.rs, main.rs · core/tests/amorphous_content.rs, extracted_intensities.rs, fullprof_summary.rs · ecosystem/data/files.yml · L042, L043, L100, L230
L2552026-10-15work

FullProf's runs of the core's two files taken in (L252): the refined λ reached, the user's f′ and f″ rebuilt exactly; FullProf's refinement of λ ends in a worse minimum, with U < 0

The branch fullprof-v1b (L252) is merged, and every new file is counted (L222).

The user's f′ and f″ (P11). y2o3_fprime_calc.prf (Y −2.0 and 4.0, O 0.5 and 0.1, far from the tables) is rebuilt exactly: Ycal rms 6.2·\(10^{-5}\), \(\chi^2\) 1.2384 against FullProf's 1.238.

The wavelength (P18). From nabd4_lambda.pcr with its own codewords (15 parameters, λ from 1.200 Å, the cell fixed), the engine reaches λ = 1.197042 \(\pm\) 0.000021 Å. FullProf's refining pass gives 1.197040 \(\pm\) 0.000018 Å (nabd4_lambda.out, "Lambda_pat1"): 0.1 of FullProf's esd apart. The test p18_wavelength_refinement reads FullProf's value from the .out and asserts the difference is within its esd.

FullProf's refinement goes elsewhere. The engine ends at \(\chi^2\) 3.71, about the \(\chi^2\) of FullProf's own converged \(\mathrm{NaBD_{4}}\) (3.727). FullProf ends at 17.09.

  • In cycle 2 FullProf's U jumps to 0.88. From cycle 3 on, U is −0.0552, V 0.1708, W 0.3066, with no shift and esd's of \(10^{-8}\). Its .out warns "Square of FWHM(G) < 0 at 2theta: 154.799 … Fixing to HG=1.0e-10"; H_G\(^{2}\) is negative from 154.25°.
  • FullProf writes nothing back: its output .pcr is the same file as its input, not only λ (L252 named λ).
  • The core's \(\chi^2\) at the values each FullProf cycle starts from (the end of the cycle before, as its .out prints them) against FullProf's \(\chi^2\) of that cycle (test p18_fullprof_cycles): cycles 1–4 within FullProf's print (−0.05, +0.18, −0.08, −0.21 %); cycles 5–9 1.2–1.6 % lower in the core. The difference is largest at 140–160° (Ycal −2 % at 155°, against FullProf's refining-pass .prf), with a 0.1 % offset below 100°. Between cycle 4 (agrees) and cycle 5, U, V and W do not move and Y falls from 0.116 to 0.049, so the high-angle peaks are narrower where FullProf clamps H_G. Believed to be FullProf's handling of those peaks, not checked. Frozen (LAMBDA_PATH) and listed for Josu.

The counts:

  • fullprof_profiles: 68 compared (66 + nabd4_lambda_calc, y2o3_fprime_calc), 71 for their data; the two refining passes left out as pass 1 of a run;
  • prf_files: 200 read (196 + 4);
  • berar_factor: 131 outputs print the factor (127 + 4), all four compared. nabd4_lambda_calc agrees to 0.002 % and nabd4_lambda to 0.04 %. The two y2o3_fprime runs (−0.25 %) are within their .prf's rounding (Ycal printed to one decimal).

nabd4_lambda_calc.prf, the same data as nabd4, repeats its last point like nabd4_calc.prf (L251). It is frozen in the same way, at the same size.

branch fullprof-v1b (L252) · manuals/fullprof/runs/nabd4_lambda/nabd4_lambda.out (cycles 1–9, SYMBOLIC NAMES), nabd4_lambda.prf · manuals/fullprof/runs/y2o3_fprime · core/tests/pattern_terms.rs (p18_wavelength_refinement, p18_fullprof_cycles), fullprof_profiles.rs, fullprof_outputs.rs, berar_factor.rs, tests/data/fullprof_profiles.txt · L222, L251, L252
L2542026-10-15work

App 0.31.0 built on core 0.43.0, installed and self-checked: no FAIL

App 0.31.0 (L253): files changed on disk noticed, and context menus. It is built on core 0.43.0, which took in FullProf's references for P29 and P11 (L251, L252).

Suites: the core 275 pass on this Mac; the app 75 Rust and 133 node tests.

Installed in /Applications, 0.30.0 to the Trash. The self-checks under L193, one instance at a time with a home of its own:

  • on a copy of Y2O3: 57 OK, none FAIL;
  • on a copy of the \(\mathrm{Tb_{2}Pd_{2}Sn}\) buffer: 71 OK, none FAIL.
DRS_APP_SELFTEST reports of 15 October 2026 · L251–L253
L2532026-10-15work

App stream, 0.31.0: files changed on disk noticed, read again or asked about, with the module's badge (§7, §2); context menus on the plot, tabs, outline, tables and the activity bar (§6)

app 0.31.0 (work), on main bea4e7c. §7: every two seconds the open tabs' files are looked at on disk (files_stat, time and size); the module tells its own save from a change (the Editor its text, Refine its document's text by refine_disk_differs, Data never writes a data file); with no unsaved changes the file is read again in its module (Data through the core with its operations, the Editor in place, Refine as after an Editor save), with unsaved changes the app asks (Read the File on Disk, Keep My Changes); modules without hooks (Explore, Plots, Publish) are read again by closing and opening their tab; the watcher re-reads its view after every save event. §2: the module's badge while a change waits and a moment after a file was read again. §6: one contextmenu handler: the plot (Copy as Image, Fit, Export, and the module's actions at the place: Refine's Who Is This Peak?, Add a Background Point Here, the axis; Data's analyses and selection), a tab (Close, Close Others, Split, Move, Show in Finder, Reveal), Refine's outline (Free, Fix, Add Atom, Space Group, Add Phase, Open in Editor) and inspector rows (Free, Fix, Copy the Address), a table (Copy, Columns, Find), the activity bar (Show, Manual, Hide from the Bar, shown again from the bar's menu); text keeps the system's menu. One self-check of the built app (seeded last session, own home, one instance): 70 OK, none failed, the app quit by itself; the outline's and a table's menus not run there (none on screen). 133 node and 75 Rust tests pass; clippy clean. Not installed.

At the merge (the merging session): Explore, Plots and Publish have no diskChanged hook yet. Their tabs are read again by closing and reopening them, and their own saves may be taken for an outside change. Each of those streams adds its hook in its next step; Explore and Plots are paused, so theirs waits.

branch app c33f727 · app/README.md (0.31.0) · APP_CONVENTIONS.md §2, §6, §7 · L250
L2522026-10-15work

Core 0.43.0 merged (FullProf's references for P29 and P11 taken in, L251); FullProf run on the core's two files for P18 and P11, kept on the branch fullprof-v1b

Core 0.43.0 (L251) merged:

  • FullProf's ce, ce2, nabd4 and highpress taken in from fullprof-v1. Surface micro-absorption and the user's scattering lengths are reproduced, and b's imaginary part and the Friedel average were found.
  • nabd4's Bérar factor agrees with FullProf's to 0.005 %.
  • The core wrote two reference .pcr files for the merging session to run.

FullProf on the core's two files, fp2k of February 2025, with the runs on the branch fullprof-v1b (pushed), as L222 keeps reference runs off main until the core takes them in:

  • nabd4_lambda, for P18: FullProf's converged \(\mathrm{NaBD_{4}}\) with λ started at 1.200 Å and refined, the cell fixed. FullProf's refining pass reaches λ = 1.197040 \(\pm\) 0.000018 Å, \(\chi^2\) 17.98. The core gave 1.197042 \(\pm\) 0.000021 Å (L251), about a tenth of an esd apart.
  • FullProf does not write the refined wavelength back into the .pcr: line 11 keeps 1.200000. So the calculating pass starts again from 1.2 Å, and its \(\chi^2\) of 97.6 belongs to that wavelength.
  • For λ, the reference is the refining pass's .out. The runs' README says so.
  • y2o3_fprime, for P11: FullProf's converged \(\mathrm{Y_{2}O_{3}}\) with the user's f′ and f″ (ITY = 2: Y −2.0 and 4.0, O 0.5 and 0.1). \(\chi^2\) 1.238 in both passes.
manuals/fullprof/runs/README.md, run_fullprof.py · branch fullprof-v1b 352de18 · nabd4_lambda.out (refined wavelength) · L222, L251
L2512026-10-15work

FullProf's runs of ce, ce2, nabd4 and highpress taken in (P29, P11): surface micro-absorption and the user's scattering lengths reproduced; b's imaginary part and the Friedel average found; the two reference files for λ and f′, f″ written

Core 0.43.0. The branch fullprof-v1 (L239) is merged, and every new file is counted.

Micro-absorption (P29) against FullProf's own calculation. ce_calc.prf (\(\mathrm{CeO_{2}}\), Cp 1.2304, \(\tau\) 0.134) is rebuilt exactly: Ycal to rms 1.6·\(10^{-5}\), \(\chi^2\) 5.2426 against FullProf's 5.242. ce2_calc.prf agrees as well, \(\chi^2\) 4.3971 against 4.396. Its 27 points just outside a window edge of its TCH phase with X ≠ 0 are the known class of L035, frozen as the others are. The formula of L231 (the manual's eqs. 3.35–3.36) is FullProf's.

The user's b (P11): two findings, both from FullProf's own run of nabd4.

  • b″ is used. FullProf takes DFPP as b's imaginary part for neutrons. Its nabd4_calc.out prints "Scattering lengths (real & imaginary parts) ==> For B 0.530000 −0.213000", though its manual (p. 79) says DFPP is ignored in the neutron case. Natural boron does absorb.
  • The powder intensity is the average over h and −h. With b complex, |F(h)|² ≠ |F(−h)|² in a group without a centre (P −4 2₁ c), and both fall in the same peak. One F(h) is off by up to \(\pm\)4.5 % on (2 1 1), and the sign of b″ moves it either way. The average leaves FullProf's intensities on a smooth trend within 0.25 %, the same for either sign.
  • So nabd4_calc.prf is rebuilt exactly: Ycal rms 1.3·\(10^{-4}\), \(\chi^2\) 3.7282 against FullProf's 3.727. Before, it was rms 7.1·\(10^{-3}\) with b real, and 2.3·\(10^{-3}\) with b complex but no average.
  • The fixed-point difference of L231 (the scale 6.5 esd from FullProf's values) came from b″.
  • One point frozen, for Josu. FullProf's last point, 164.90° (in the excluded region 157.05–164.90, which its .prf prints as 164.89999), repeats the point before it: Ycal 201.5 and Backg 156.2 at both 164.85° and 164.90°. The core's linear background there is the .pcr's last point, 202.244, which is 0.7 higher. That is a background deviation of 3.4·\(10^{-3}\) against the 2·\(10^{-3}\) allowed. No other of the 66 compared .prf files repeats its last point, so the rule is not found. It is frozen in FROZEN_BACKGROUND (rms 6.10·\(10^{-5}\), max 3.445·\(10^{-3}\)); the general tolerance is unchanged.

In the core: Scatterer::Neutron has b_imaginary (serde default 0), and UserScatterer has scattering_length_imaginary. The control file's [[pattern.scatterer]] takes the key of the same name, and the export writes it as DFPP. Structure::intensity and intensity_derivatives give the average over h and −h. They are |F(h)|² itself when no scatterer has an imaginary part, so no earlier result changes. calculate, the derivatives and the reflection rows use them. It holds for X-ray f″ as well.

highpress (Hg-1223, the user's f′ and f″ for five species) is left out of fullprof_profiles with its reason: Size-Model 1, P25's size models, version 2 (make_fullprof_profiles.py).

The counts:

  • fullprof_profiles: 66 compared (63 + ce, ce2, nabd4), 71 for their data;
  • prf_files: 196 read (188 + 8);
  • berar_factor: 127 outputs print the factor. 60 compared; nabd4's factor agrees with FullProf's to 0.005 %. ce and ce2 are counted among the runs whose Bragg points differ, and highpress among those not calculated.

The two files for the merging session to run, in manuals/fullprof/FP_tests/:

  • nabd4_lambda.pcr (data nabd4.dat), for P18. It is FullProf's converged \(\mathrm{NaBD_{4}}\) with λ₁ = λ₂ = Lambda started at 1.200 Å (0.003 above) and refined as parameter 2, the cell fixed, and the other codewords renumbered (15 parameters). The core refines it back to λ 1.197042 \(\pm\) 0.000021 Å.
  • y2o3_fprime.pcr (data y2o3.dat, copied from the tutorial), for P11. It is FullProf's converged \(\mathrm{Y_{2}O_{3}}\) with Nsc = 2 and line 12 giving f′ and f″ (ITY = 2) far from the tables: Y −2.0 and 4.0, O 0.5 and 0.1. run_fullprof.py (the merging session's) needs the two entries "nabd4_lambda": ("FP_tests", "nabd4_lambda.pcr", "nabd4.dat", []) and "y2o3_fprime": ("FP_tests", "y2o3_fprime.pcr", "y2o3.dat", []).
branch fullprof-v1 (L239) · manuals/fullprof/runs/{ce,ce2,nabd4,highpress} · nabd4_calc.out line 105 · manuals/fullprof/FullProf_Manual.pdf p. 79 · core/src/scattering.rs, structure_factor.rs, powder.rs, jacobian.rs, diagnostics.rs, control/schema.rs, control/pcr_export.rs · core/tests/fullprof_profiles.rs, fullprof_outputs.rs, berar_factor.rs, pattern_terms.rs, tests/data/make_fullprof_profiles.py · manuals/fullprof/FP_tests/nabd4_lambda.pcr, y2o3_fprime.pcr, y2o3.dat · L035, L222, L231, L239
L2502026-10-15work

App 0.30.0 built on core 0.42.0 with every finished stream merged, installed and self-checked: no FAIL

App 0.30.0 brings together:

  • app 0.28.0 (L240) and 0.30.0 (L247);
  • Explore's tenth and eleventh steps (L241, L249);
  • Plots' tenth step (L242);
  • drs-publish 0.10.0 (L248);
  • core 0.42.0, with the Bérar factor (L244, L245). The release planned as 0.29.0 was never built.

Suites: the core 275 pass, Publish 33, the app 75 Rust and 130 node tests.

Installed in /Applications, 0.27.0 to the Trash. The self-checks under L193:

  • on a copy of Y2O3: 57 OK, none FAIL. Among them: the Settings window's panes, each setting explained in a line; 25 settings found by ⌘K; a shortcut changed and given back; the accent colour.
  • on a copy of the \(\mathrm{Tb_{2}Pd_{2}Sn}\) buffer: 68 OK, none FAIL.

No copy of the app ran after the checks.

DRS_APP_SELFTEST reports of 15 October 2026 · L240–L249
L2492026-10-15work

Explore stream, eleventh step: DESIGN.md §5's seventh step, a point dragged by hand on the \(\chi^2\) map with the core's pattern and \(\chi^2\) following it; the sixth step (the microstructure) was the tenth's, its Williamson–Hall plot waiting for E19; E20 filed, waiting behind version 1 (L230)

origin/main (306c789) merged. §5's order of work: (6) the microstructure, built in the tenth step but for its Williamson–Hall plot (d* and β* asked of the core in E19); (7) the dragged point, not started, built now. On the \(\chi^2\) map a square point starts at the end's values (the grid's centre, the core's) and can be dragged; the module's Rust side (explore_point, point_of) takes the end's model, sets each field of the two parameters to the dragged value by the core's Model::set and the links by enforce_links, and returns the core's pattern (Model::calculate), Yobs − Ycal (diagnostics::difference) and statistics (lsq::statistics); beside the map, Yobs, Ycalc at the point and the difference strip on the shared plot, with \(\chi^2\)ν and Rwp; requests latest-wins, so the pattern follows the drag as fast as the core answers. A parameter whose fields move by coefficients other than 1, or a refinement's variable, is refused with the reason: scaling a shift is the core's (E20a asks lsq::point, which sets any free parameter as the engine does). The shared plot reports a handle while it is dragged (onHandleDrag, additive; E10l). E20: (a) lsq::point; (b) the same call then serves the pair view's point, the drag there being the window's. Checked: cargo test --release modules::explore 7 pass (Y2O3: at the end's values \(\chi^2\) equals the grid's centre to the bit; at a grid node the grid's \(\chi^2\) there within \(10^{-9}\), the cubic cell's a moving b and c, each field set to the value); node tests 124 pass (explore_point.test.mjs new); the window bundles; the map with its point headless on a recorded grid. One self-check of the built app (not installed), the only instance, its own home, on a copy of Y2O3: 101 lines, 47 OK, none FAIL, ended by the app's own quit; the map's line: the point at the end (Caglioti V, W) \(\chi^2\) 1.1985 (the run's 1.1985), 13091 points calculated; cell, micro (not TCH, said), compare and cycle OK. Shell lines, E10l: plot.js's onHandleDrag (one call, one phrase), one line in main.rs.

branch explore 8e9a81f · explore/DESIGN.md §2.7, §5 · explore/REQUESTS.md (E20, E10l) · app/src-tauri/src/modules/explore.rs (point_of) · app/ui/modules/explore/index.js, README.md · app/ui/plot.js · app/ui/tests/explore_point.test.mjs · core/src/powder.rs (Model::set, calculate), core/src/lsq.rs (statistics), core/src/diagnostics.rs (difference) · L193, L230
L2482026-10-15work

Publish stream, drs-publish 0.10.0: the checks before export against McCusker 1999 and Young 1982, the agreement factors after Toby 2006; F2, F8, T7, T8; case E at 453 K (L243)

Publish 0.10.0: the checks before export against McCusker et al. 1999 and Young et al. 1982 (check 9, check 6), T1's agreement factors named and defined after Toby 2006; F2 models compared, F8 correlations and paths, T7 a series, T8 AMPLIMODES's input; case E at 453 K (L243); self-check 71 lines, 61 OK, no FAIL.

At the merge (the merging session): the stream's commit named this entry L244, which main had given to P43's Bérar factor. It is L248, the next free id.

branch publish f3f9965 (1544332) · publish/ · app/manual/publish.html · publish/REQUESTS.md P27 · L238, L243
L2472026-10-15work

App stream, 0.30.0: the Settings window with APP_CONVENTIONS §4's eight panes, and a project's own settings

app 0.30.0 (work), on main 306c789. The Settings window (⌘,, one for the app, its own window): the eight panes of §4 (General, Appearance, Editor, Plots, Data formats, Refinement, External programs, Advanced), rows from one model (ui/settings.js: default, one-line explanation, overridable), every change applied at once in every window (settings_broadcast, settings-changed). New settings: the Editor's font size, tab width, line numbers, wrapping and saving by itself (a CodeMirror compartment); Refine's plot axis and the intensity scale of Data and Refine; a version before each run; the notification when a run ends (refine_run's notify); the recent projects kept (recent_add's limit); FullProf's and GSAS-II's places (kept; nothing runs them yet); the Log's length; Show in Finder; Reset All. Information rows where §4 names what the app does not offer (no update service, no minimap, the convergence the control file's, the publication style Publish's, the readers' order the core's). A window's save takes the preferences from disk except those it changed itself, so no window undoes another. File > Project Settings...: a project's own cycles, Pause every, plot axis and scale, Yobs as a line, tab width, kept in .drs/project.json, said in the status bar; read everywhere through setting(key). Fixed: the Pause every field's save hook did not exist; a setting put back to its default did not leave the file (found by the self-check, fixed after it: settingsWindowMerge, mergeForSave, tests). One self-check of the built app (seeded last session, own home, one instance): 64 OK, 1 failed (that bug), the eight panes and their explanations, a change reaching the main window, a project's own cycles in force and said, P48 on a structure phase 3 -> 4 atoms; the app quit by itself. 128 node and 75 Rust tests pass; clippy clean. Not installed.

At the merge (the merging session): a fast-forward. The release planned as 0.29.0 was never built, so its entry is folded into 0.30.0's.

branch app dcb82c9 (5bda423) · app/ui/settings.js · app/README.md (0.30.0) · APP_CONVENTIONS.md §4 · L246
L2462026-10-15decision

The Plots stream paused with Josu's agreement; the streams' work for version 1 set

Plots is paused. What is left on its list waits for the core (PL32 the header's temperature with its unit, PL34 a series in Publish) or is "later" by Josu's decision (the 3D surface, L169). The formats without an example file (PL16) wait for the files. Pausing it also spares the Mac's memory (L193). It is taken up again when the core meets PL32 and PL34 after version 1. Set aside: going on with the 3D surface now.

Where it stands. Ten steps: the five views, normalisation and conversion as views, CSV out and in, the plot set file, Figure Look and the journals' widths, Send to Publish for a set and a series (L180–L242).

The other streams, following L230:

  • the core: version 1's steps B (P33, P35, P57), C (P09's symmetry elements, P51's peak fitting, P49's completion source, the app's request 36) and D (P13), with FullProf's references from fullprof-v1;
  • the app: Settings with panes (APP_CONVENTIONS §4) until the core's step C lands, then completion and peak fitting first;
  • Explore: DESIGN.md §5's steps not yet started, on what the core already gives;
  • Publish: its current step, with case E at 453 K (L243);
  • the tutorial: paused (L193).
Josu, 15 October 2026 · plots/REQUESTS.md · L169, L193, L230, L242, L245
L2452026-10-15decision

Josu accepts the core's reading of the Bérar factor as the default, and the two runs frozen apart (P43, L244)

The core found that Bérar & Lelann (1991) is not consistent with itself. Its equations 10–12 and its appendix's code differ in three places. FullProf reproduces its own printed factor with parts of each: eq. 10's grouping, the appendix's 2π, each pair's z given to the earlier point, and only the points with Bragg contributions (L244).

Josu decided:

  • FullProf's reading is the core's default. Every part of it is the paper's, an equation or the appendix's code. It agrees with FullProf within 0.05 % on 34 of 58 runs, and within the rounding of the .prf on 17 more. Set aside: the paper's equations alone, 8–12 % below FullProf's factor.
  • The two unexplained runs are accepted, frozen: LBF_lmopr at +0.09 % and sr-ox-rest_conv at +0.88 %. The test berar_factor fails if either grows (L037). Set aside: investigating them before version 1 goes on.

P43 is met (core 0.42.0).

Josu, 15 October 2026 · core/tests/berar_factor.rs · manuals/papers/Berar_Lelann_1991.pdf · L037, L238, L244
L2442026-10-15decision

P43, the Bérar factor: FullProf's reading of Bérar & Lelann (1991) found and made the core's; its printed factor reproduced in 58 of the 119 outputs that carry it

Core 0.42.0, lsq::berar_factor, test berar_factor, from the paper of L238.

The paper. Bérar & Lelann (J. Appl. Cryst. 24, 1–5) correct the esd's for serial correlations of the residuals aᵢ = √wᵢ(yᵢ − ycᵢ). The method:

  • neighbouring residuals of the same sign are correlated with a probability zᵢ;
  • correlated residuals add linearly, the others quadratically: S″ = Σ(1 − zᵢ²)aᵢ² + Σ_groups(Σzᵢ|aᵢ|)² (eq. 10), and the factor is √(S″/S) (eq. 11);
  • zᵢ = q/(2 + q), q = [2(aᵢ² + a\(^{2}\)ᵢ₋₁)]^½ (eq. 12). The paper's appendix (DBW3.2's CHISQ loop) disagrees with its equations in three places:
  • it writes q = √(2π(…));
  • it gives each pair's z to the earlier point;
  • it groups the points otherwise. It also skips the points without a reflection (KR(I) = 0).

FullProf's reading, found against its printed factor ("Standard deviations have to be multiplied by"):

  • eq. 10's grouping;
  • eq. 12 with the appendix's 2π;
  • each pair's z given to its earlier point (the loop from the high-angle end);
  • only the points with Bragg contributions, a point left out breaking a run.

The weights are FullProf's: Σw(y − yc)² equals the "Residual" of the .out's two statistics blocks, all points and Bragg points. With every other reading tried, FullProf's factor is missed:

  • eq. 12's 2: −8 to −12 %;
  • the appendix's loop as printed: +3 to +8 %;
  • the forward direction: up to \(\pm\)0.5 % on the clean runs (Co-YBCO's three runs −0.32 to +0.28 %, where the reversed loop gives 0.002–0.004 %);
  • all the points instead of the Bragg ones: up to 11 %;
  • the exact \(\chi^2\) probabilities in place of eq. 12: +7 to +19 %.

Decision. This reading is the core's default. Each of its parts is the paper's, an equation or the appendix's code; the paper itself mixes them. FullProf is the only implementation in the repository to check it against. Set aside:

  • the equations alone (eq. 12's 2, z with the predecessor): 8–12 % below FullProf;
  • the appendix's loop alone: 3–8 % above. Josu may choose otherwise.

The test. All 119 .out files in manuals/ that print the factor are accounted for:

  • 58 compared: FullProf's Ycal from the .prf, the data as FullProf reads them, the core's weights and Bragg points equal to FullProf's (both residuals within 0.05 %);
  • 4 without a .prf; 9 with a model the core does not calculate (magnetic phases, size and strain models);
  • 11 whose weights differ (data read otherwise); 37 whose points with Bragg contributions differ (the core's peak windows are not FullProf's: the rules of L042, frozen in fullprof_profiles).

Of the 58 compared:

  • 34 agree within 0.05 %, most within 0.02 % (case A 0.002 %, Co-YBCO 0.004 %, the course's \(\mathrm{Sr_{2}ScSbO_{6}}\) in manuals/bcs/ 0.001 %).
  • 17 agree within the rounding of the .prf's Ycal: FullProf's value lies in the range of the factor for Ycal within half a unit of its last printed digit. The \(\mathrm{Y_{2}O_{3}}\) runs print Ycal to one decimal (−0.07 to −0.24 %); LaMnO₃ref is −0.21 %, \(\mathrm{K_{2}NaAgCN}\) −0.09 %.
  • 7 are frozen: five narrow-window experiments (−0.09 to −15 %), where the core's peak windows differ from FullProf's at their edges; LBF_lmopr (+0.09 %) and sr-ox-rest_conv (+0.88 %), not explained, listed for Josu.

In the core. Refinement::berar_factor, written to the results file (_drs_berar_factor, read back) and printed by drs refine ("Bérar factor …: the esd's times it estimate the probable errors").

manuals/papers/Berar_Lelann_1991.pdf (eqs. 10–12, appendix) · core/src/lsq.rs (berar_factor), results.rs, main.rs · core/tests/berar_factor.rs · the 119 .out files of manuals/ · L042, L222, L238
L2432026-10-15decision

Josu: case E (\(\mathrm{La_{2}CoMnO_{6}}\), R-3) was measured at 179.99 °C, 453 K; the course's 500 K label is corrected

Plots traced case E's temperature (PL33, L242). Its data file's header says TEMPERATURE 179.99, without a unit. The course folder and Publish's example series call it 500 K. Orayech et al. 2015 put the P2₁/n → R-3 transition at 450 K.

Josu: the measurement was at 179.99 °C = 453.14 K, just above the transition, so R-3 is right. Set aside: 500 K, which is the course folder's label.

What follows:

  • Publish corrects its example series: case E at 453 K, its source the data file's header read in °C, in the figure's x and its caption.
  • The core reads that header line as °C, with the source said (PL32). It waits behind version 1 (L230), as Plots filed it.
  • Plots' self-check types case E as 453 K, with this decision as its source, until the core reads it.
  • The course's folder name in manuals/bcs/ is a reference copy and stays as it is.
Josu, 15 October 2026 · plots/REQUESTS.md PL32, PL33 · manuals/fullprof/runs/modes/E/t7.dat · Orayech et al. 2015 (manuals/bcs/…/articles/c5dt01532d.pdf) · L156, L242
L2422026-10-15work

Plots stream, tenth step: Send Series to Publish… (F5, F4) with each temperature's source; case E's temperature traced, for Josu to say (PL33); PL34 filed

Plots step 10: Send Series to Publish… (F5, F4) with each temperature's source, enabled when Publish takes a series (PL34 filed); case E's temperature traced: 500 K the course's folder label, 179.99 the measurement's without a unit, R-3 only above 450 K (Orayech 2015), so Josu's to say (PL33), PL32 corrected.

branch plots 850d51a · app/ui/modules/plots/ · plots/REQUESTS.md PL32–PL34 · L236, L239
L2412026-10-15work

Explore stream, tenth step: the microstructure per cycle (the size and strain parameters, each reflection's breadths, apparent size and strain), and the wavelength and the surface micro-absorption, refinable since core 0.41.0, as a class of their own in the traces and the pair view; E19 filed, waiting behind version 1 (L230)

origin/main (eb28467) merged (the branch already at it). (1) Explore's classes gain "wavelength and micro- absorption" (pattern1.wavelength_1, microabsorption_p0/_cp/_tau; core 0.41.0, P18, P29) and "size and strain" (gaussian_size, lorentzian_size, lorentzian_strain): their own colour and legend key in the traces, their dictionary names in the pair view, as for every free parameter of the run's record. (2) The Microstructure tab (micro.js, explore_micro, micro_of in explore.rs): for every state, each reflection of the phase at the first wavelength with the core's Model::breadths at that state (H_G, H_L, β_G, β_L, the apparent size and the maximum strain); left, the run's size and strain parameters per cycle in \(\sigma\)_end (the core's path, as the Traces view); right, at the cycle shown, β_G and β_L against 2\(\theta\); the table, the parameters' values and esd's per cycle and the reflections' breadths. The core gives breadths for the Thompson–Cox–Hastings profile only; another profile says so. Commands, a phase select with a tour handle (select:uphase), replay, self-check, export; micro.js computes nothing (a test keeps it so). E19 (waits): d* and β* in Breadths for the Williamson–Hall plot, and each breadth's hkl. Checked: cargo test --release modules::explore 7 pass (the_microstructure_per_cycle new: rutile–anatase's TiO2, 25 reflections, (110) size 25442 Å and strain 4.99·\(10^{-4}\), equal to Model::breadths at the state to the bit; none for Y2O3's pseudo-Voigt; NdNi-HTO was tried first and refused, its magnetic phase 2 not calculated by the core); node tests 119 pass (explore_micro.test.mjs new); the window bundles; the tab headless on the test's breadths. One self-check of the built app (not installed), the only instance, its own home, on a copy of rutile–anatase: 99 lines, 46 OK, ended by the app's own quit. Explore's lines: micro, TCH, 4 states, 25 reflections at the end, 4 size and strain parameters free, plots drawn; cell a 4.59456(4) c 2.96032(3) V 62.4925(15); bonds, Ti 8 contacts and 28 angles, sums Ti 4.065, O 2.033 v.u.; compare OK (the default pair lorentzian strain–size, r −0.943); cycle OK. One FAIL, the shell's (P46: the launch reopened no window from the last quit). Shell lines, E10k: one line in main.rs.

branch explore a6fa31c · explore/REQUESTS.md (E19, E10k) · explore/DESIGN.md §2.5 · app/src-tauri/src/modules/explore.rs (micro_of) · app/ui/modules/explore/micro.js, draw.js (classes), index.js, README.md · app/ui/tests/explore_micro.test.mjs · core/src/microstructure.rs (Model::breadths) · manuals/fullprof/runs/rutile-anatase · L193, L230, L233
L2402026-10-15work

App stream, 0.28.0: adding atoms and phases in Refine (P48), the jump from an output line to its parameter (P49), P46's check without a last session; request 36

app 0.28.0 (work), on main eb28467 (L230 part B that does not wait for the core). P46's self-check (L239): a fresh home has no last session; the line now says the reopening is not checked there and checks the recording (session_now: what a quit would record) and the project's kept tabs. P48: Refine > Add Atom... (label, species, x y z, B, chemical occupancy; edit::add_atom; FullProf's Occ by the core's occupancy_from_chemical; the dictionary's defaults), Add Phase... (a symbol and a cell; edit::add_phase), Add Phase from CIF... (edit::add_phase_from_cif); one undo step each, written as new tables and read back; removing an atom or a phase not offered (the renumbering of parameters, links and steps is the core's). P49: Editor > Jump to Parameter (⌃⌘J): the address a line of the core's outputs names (refinable::field_of in the document's model), shown in Refine (outline item, inspector row); FullProf's .out symbolic names and the CIF's tags asked of the core (request 36). One self-check of the built app (seeded last session, own home, one instance): 51 OK, 1 failed (P48 ran on the self-check's profile-matching control file, where the core rightly refused the atom and the window said so; the self-check now uses the project's .pcr for P48, after this run); P46, P49 and 0.26.0's Problems panel OK; the app quit by itself. 119 node and 74 Rust tests pass; clippy clean. Not installed.

branch app 1129c11 · app/README.md (0.28.0) · app/REQUESTS.md 36 · L230, L239
L2392026-10-15work

FullProf run on ce, ce2, nabd4 and highpress for version 1's step A (P29, P11), kept on the branch fullprof-v1; app 0.27.0 installed and self-checked

FullProf references for version 1's step A. Josu agreed: "yes, run them". The core asked for them in L233. run_fullprof.py gained the four runs on main, and the runs themselves are on the branch fullprof-v1 (pushed). They stay off main because the core's tests count every FullProf file in manuals/ (L222); the core merges the branch when it takes them in.

fp2k of February 2025 finished both passes for the four. The calculating pass gave:

| Run | Rp | Rwp | Re | \(\chi^2\) | For | |---|---|---|---|---|---| | ce (\(\mathrm{CeO_{2}}\), CSIRO round robin) | 15.3 | 19.7 | 8.62 | 5.24 | P29 | | ce2 | 13.3 | 18.1 | 8.63 | 4.40 | P29 | | nabd4 (\(\mathrm{NaBD_{4}}\), 10 K, neutrons) | 14.7 | 12.2 | 6.31 | 3.73 | P11 | | highpress (Hg-1223 at 86.8 kbar) | 18.1 | 19.3 | 16.5 | 1.36 | P11 |

The two other runs the core asked for need a .pcr written for them: a model with the user's f′ and f″, and a single pattern refining λ with the cell fixed. The core writes the two files, and the merging session runs them.

App 0.27.0 was built on core 0.41.0 with every stream merged (L232–L237). Suites: the core 273 (the flaky E15a test is fixed), Publish 32, the app 73 Rust and 116 node tests. It is installed, with 0.25.0 to the Trash. The self-checks under L193:

  • on a copy of Y2O3: 46 OK and 1 FAIL;
  • on a copy of the \(\mathrm{Tb_{2}Pd_{2}Sn}\) buffer: 66 OK and 1 FAIL.

Both FAILs are P46's check, "the launch reopened 0 windows from the last quit": a fresh home folder has no last session to reopen. That is the check's precondition, not the app; the app stream's own check had seeded one. The check is to seed its own session or say "not checked". The Problems panel's rule, Explore's Cell view and the settings found by ⌘K pass.

manuals/fullprof/runs/run_fullprof.py, README.md · branch fullprof-v1 d3d8aea · DRS_APP_SELFTEST reports of 15 October 2026 · L222, L232–L238
L2382026-10-15work

Four papers in manuals/papers/: McCusker 1999, Young 1982 and Toby 2006 for Publish (P13), Bérar & Lelann 1991 for the Bérar factor (P43)

Josu downloaded the papers through UPV/EHU's subscriptions. They are private like the rest of manuals/.

  • McCusker, Von Dreele, Cox, Louër & Scardi (1999), J. Appl. Cryst. 32, 36–50. Josu's file mckausker_1999.pdf is renamed McCusker_1999.pdf, as the folder names its papers.
  • Young, Prince & Sparks (1982), J. Appl. Cryst. 15, 357–359. The guidelines are on its second page, after another laboratory note.
  • Toby (2006), Powder Diffraction 21, 67–70.
  • Bérar & Lelann (1991), J. Appl. Cryst. 24, 1–5, "E.S.D.'s and estimated probable error obtained in Rietveld refinements with local correlations": the paper P43 waited for (L233, L237).

Each title and volume was checked against the paper's first page. The README lists them with their DOIs; Bérar & Lelann's DOI is not printed on the scan and is marked unchecked. Publish's P13 is met. P43 can go to the core: the factor from the paper, tested against FullProf's printed factor in the 119 .out files that carry it.

manuals/papers/ (README.md) · publish/REQUESTS.md P13 · L233, L237
L2372026-10-15work

Main compiles on core 0.41.0 (the wavelength and micro-absorption fields named in the app); the core's version-1 step A asks for FullProf runs and Bérar's paper

Core 0.41.0 (L233) added two of the pattern's refinable fields, Field::Wavelength (P18) and Field::Micro(_) (P29). The app's param_label in app/src-tauri/src/modules/refine.rs:741 did not cover them. The merging session added both to the pattern's arm, (None, false), as the core's release notes give it, and cargo check is clean. The app stream is told.

The core's step A (P18, P11, P29) also left two things for the merging session and Josu:

  • P43, the Bérar factor, waits for its paper. J.-F. Bérar and P. Lelann, J. Appl. Cryst. 24, 1–5 (1991), and Bérar, NIST Special Publication 846, 63 (1992), are not in the repository, and FullProf's manual gives no formula. FullProf's printed factor in 119 .out files will be the test. The paper is for Josu to provide; it is never fetched around a paywall.
  • FullProf reference runs, made as runs/run_fullprof.py makes them:
  • P29: ce.pcr and ce2.pcr with ceo2.dat;
  • P11: nabd4.pcr and highpress.pcr, and one run with the user's f′ and f″ in a model the core calculates;
  • P18: a single-pattern run refining λ with the cell fixed.
app/src-tauri/src/modules/refine.rs:741 · core/src/jacobian.rs:64–66 · L233
L2362026-10-14work

Plots stream, ninth step: the set file's views kept (PL27), figures at the journals' widths from Publish's presets, a series of refinements against T; PL32 filed

Plots step 9: PL27 used (each view's steps and scales, the map's scale, the normalisation and x kept in the set file); Figure Size… from Publish's presets, stack and projection at IUCr's 88.5 and 180 mm; Open Results Series… (refinements against T from their data files); PL32 filed to wait; case E's 179.99 K against Publish's 500 K found.

branch plots 2a5b92a · app/ui/modules/plots/ · plots/REQUESTS.md PL32 · L225, L230
L2352026-10-14work

Publish stream, drs-publish 0.9.0: VESTA, the Bilbao server's format and the data (D5–D7); the checks before export in the Problems panel; figures found 1.2 mm too wide and fixed

Publish stream, drs-publish 0.9.0 (L230's order, no new core request): D5 VESTA, D6 the Bilbao server's format, D7 the data; the checks before export 1-7 of DESIGN.md §5.4 in the app's Problems panel; check 4 found every figure 1.2 mm wider than its class (fixed); self-check 58 OK, 1 FAIL (the panel behind the Log, fixed after); the four images of L229 confirmed; release entries 0.2.0-0.9.0 in publish/README.md.

branch publish ed17559 · publish/ · app/ui/modules/publish/ · publish/README.md (release entries 0.2.0–0.9.0) · L229, L230
L2342026-10-14work

Explore stream, ninth step: the Cell and the Bonds views per cycle (a, b, c, angles, V with esd's; distances, angles and bond-valence sums with esd's), every number the core's; E1–E7 marked met (L115); E18 filed, waiting behind version 1 (L230)

origin/main (29ae374) merged. Josu's L230 (version 1 first): the core works on its own gaps, Explore's requests wait; the views built are those the core already gives numbers for. explore_geometry (explore.rs geometry_of): every state summarised by state::summary_at with its cycle's covariance (the result's at the end, none at the start), contacts within the core's default 3.0 Å and the angles between them, the strain against the start's cell; the cell's su's (a, b, c, α, β, \(\gamma\), V) through the core's geometry::Propagator::of_phase. The Cell tab: a, b, c and V per cycle with esd bars (two shared plots), the table every cycle's cell with \(\sigma\) and the strain from the start. The Bonds tab: an atom chosen, its distance to each neighbour per cycle with esd's (each contact followed by the neighbour, operator and translation the core gives it), every atom's bond-valence sum per cycle, the table its contacts and angles at the cycle shown. geometry.js arranges and computes nothing (a test keeps it so). Commands Explore ▸ The Cell per Cycle and Bonds per Cycle, selects with tour handles (select:gphase, select:atom), the replay and the self- check through both, export. A redraw on resize now waits a frame (the note's height changed inside the observed area: a loop warning). E1–E7 marked met by core 0.25.0 (L115). E18 (waits): the cell's su's in the state itself, each contact's plausible distances, the value a valence sum is compared with and its plausible range (the core's formal valence is signed, −2 for O where its sum is +1.970, so no line is drawn for it). Checked: cargo test --release modules::explore 6 pass (Y2O3: the cell's su's from cycle 1, none at the start, a = b = c, the angles 90° with none, V = a\(^{3}\), the strain of a positive from the perturbed start; contacts, angles, su's; no sums for the tutorial's uncharged species and the core's reason; with Y+3, O-2 the sums at every state, Y1 2.949, Y2 2.957, O 1.970 v.u.); node tests 113 pass (explore_geometry.test.mjs new); the window bundles; both tabs headless on the test's numbers. One self-check of the built app (not installed), the only instance, its own home, on a copy of Y2O3: 93 lines, 42 OK, ended by the app's own quit. Explore's lines: cell, 3 states, esd's at 2, a 10.60497(4) V 1192.691(12), plots drawn; bonds, Y1 6 contacts and 15 angles, no sums (the reason said); traces, one mark (converged); compare OK; cycle OK (returned to cycle 1, Undo gave back "Return to Cycle 1": the eighth step's entry confirmed). Four FAILs, all the shell's (shortcut recording "not recording" \(\times 3\), ↓ in the sidebar), none Explore's. Shell lines, E10j: one line in main.rs.

branch explore 91fdea0 · explore/REQUESTS.md (E1–E7, E18, E10j) · explore/DESIGN.md §2.5 · app/src-tauri/src/modules/explore.rs (geometry_of) · app/ui/modules/explore/geometry.js, index.js, README.md · app/ui/tests/explore_geometry.test.mjs · core/src/state.rs (summary_at), core/src/geometry.rs (Propagator::of_phase) · L115, L193, L230
L2332026-10-14work

Version 1, step A: the wavelength refined (P18), the user's scattering factors (P11), Bragg–Brentano surface micro-absorption (P29); the Bérar factor (P43) waits for its paper

Core 0.41.0, test pattern_terms (3 tests). First, explore_stops gives each test its own copy of the cases: on the Mac e15a_the_pauses_in_the_record failed once in the full suite, its files copied while another test read them.

P18, the wavelength refined. Field::Wavelength (pattern1.wavelength_1) is refined like any other value. Its derivative is exact: the peak position's dual carries λ.

  • λ₂ moves with λ₁ when the pattern has one wavelength. FullProf's own files show the rule: in the five patterns of FP_tests whose line 14 refines Lambda and whose comments name the pattern (pb_ho, pbb, pb_sing, gabriela \(\times 2\)), FullProf writes λ₁ = λ₂ = Lambda.
  • A doublet's λ is refused, with the reason: FullProf refines a single wavelength (manual p. 83), and which line moves is not said.
  • Line 14's Lambda codeword is imported (it was refused) and exported; the control file refines it by address.
  • The dictionary gives the manual's advice (fix the cell, p. 83) as a correlation with the cell edges.

Checked on case A (runs/modes/A_modes, neutrons, λ 1.59509 Å):

  • ∂y/∂λ against a central difference to 6·\(10^{-9}\) at a step of \(10^{-8}\) Å. Larger steps move FullProf's hard window edges (kept in the default model) across data points: 7·\(10^{-3}\) at \(10^{-6}\) Å.
  • From FullProf's converged state with λ moved by 0.002 Å, the cell fixed, the zero and scale free: λ returns to 1.595097 \(\pm\) 0.000013, FullProf's 1.59509 within 0.5 esd.

No single-pattern FullProf run in the repository refines λ with its output, so the refined λ is not compared with FullProf's.

P29, surface micro-absorption. The manual's form (eqs. 3.35–3.36, p. 55; line 15, p. 84): S_r = 1 − P(h), P(h) = P0 + Cp (\(\tau\)/sin \(\theta\))(1 − \(\tau\)/sin \(\theta\)), multiplying the absorption factor. The manual's own refs. 25 and 26 are not in the repository; the manual's formula is the reference.

  • P0, Cp, \(\tau\) are refinable (Field::Micro; dictionary entries microabsorption_p0, _cp, _tau).
  • Line 15 is read with its codewords, now only for X-ray 2\(\theta\) patterns (Job 0 or 2, Uni 0, Ilo 0), as the manual says. Before, a neutron file with More ≠ 0 would have read a line that is not there.
  • The import refusal is gone. The control file has keys of the same names; the export writes More = 1 and line 15.

The reference is FullProf's own refinements of \(\mathrm{CeO_{2}}\) with micro-absorption (FP_tests/ce.pcr and ce2.pcr, data ceo2.dat): their line 15 holds FullProf's converged Cp and \(\tau\). Started from FullProf's values, the engine keeps them:

| | Cp: FullProf → engine | \(\tau\): FullProf → engine | |---|---|---| | ce | \(1.2304 \to 1.2288\) \(\pm\) 0.211 (0.01 esd) | \(0.1340 \to 0.13456\) \(\pm\) 0.0061 (0.09 esd) | | ce2 | \(1.4999 \to 1.5014\) \(\pm\) 0.160 (0.01 esd) | \(0.1451 \to 0.14615\) \(\pm\) 0.0024 (0.43 esd) |

Without line 15, \(\chi^2\) at FullProf's values rises from 5.24 to 20.55 (ce) and from 4.39 to 32.71 (ce2).

Frozen in FIXED_POINT_FROZEN and listed for Josu: other parameters move by more than the engine's esd.

  • ce: 3 parameters, largest 2.18 esd; \(\chi^2\) falls 0.13 %.
  • ce2: 8 parameters, largest 2.81 esd; \(\chi^2\) falls 0.56 %. They are the profile's U, V, W, η and asymmetry, and for ce2 the zero and the cell, which correlate with them. FullProf stopped at Eps 0.3 with 0.8 damping. Its esd's (no .out in the repository) are larger than the engine's (L053). ce1.pcr is left out: it varies λ₂/λ₁ (VARY ratio), which this engine does not refine.

P11, the user's scattering factors. Line 12 (Nsc ≠ 0, manual pp. 70, 79–80) is read: NAM, DFP, DFPP, ITY, and line 12b (nine X-ray coefficients, seven magnetic ones, or a table ended by −100). They become the pattern's UserScatterers, matched to the atoms' species case aside:

  • neutrons: b (ITY = 0); magnetic form factors (ITY = 1) are left out, since the core calculates no magnetic phase;
  • X-rays: f′ and f″ with the tables' f(s) (ITY = 2), with the nine coefficients (ITY = 0, Nsc > 0; "only A1, B1" when A2 = 0), or with a table (ITY = 0, Nsc < 0), interpolated linearly (Scatterer::XrayTable). Outside the table it is held at its end values: believed, the manual does not say.

In the control file, [[pattern.scatterer]] takes species, scattering_length or f_prime and f_double_prime, with form_factor or form_factor_table. It is written for each species whose scatterer is not the tables', and read back the same. An atom or phase added later takes the pattern's values. The export writes Nsc (negative when a table is given) and line 12.

Read in the five FP_tests files with Nsc ≠ 0: nabd4, highpress, c60a, hpress, and cedif4rf, which stops before line 12 on its user symmetry lines (not read).

No FullProf output in the repository has a user scattering factor that differs from the tables in a model the core calculates:

  • nabd4 gives B the tables' own b, 0.5300;
  • highpress has Size-Model 1, refused (P25);
  • cedif4rf's are magnetic. So P11 is checked against the manual's rules, not against FullProf's numbers.

nabd4 itself, started from FullProf's values, moves its scale by 6.5 esd and D1's x by 4.3 esd. Two causes were tried, without a resolution:

  • nabd4's line 12 gives B the tables' own b, so P11 does not change it;
  • with DFPP = −0.213 taken as an imaginary b (the manual says it is ignored for neutrons), the scale and B's B_iso agree but D1's x moves 13 esd the other way, with a higher \(\chi^2\). It is a finding for the merging session's run, not built on.

P43, the Bérar factor: not built. FullProf prints "SCOR = … {Berar's FORMULA}" in its .sum and "Standard deviations have to be multiplied by" in its .out, citing J.-F. Bérar & P. Lelann, J. Appl. Cryst. 24, 1–5 (1991) and Bérar, NIST Sp. Pub. 846, 63 (1992). Neither paper is in the repository, and the manual gives no formula (manuals/papers/ holds Bérar & Baldinozzi 1993, the asymmetry). It waits for the paper. FullProf's printed factor will be its test: 119 .out files in manuals/ print it, and 50 .sum files print Bérar's SCOR.

Asked of the merging session (FullProf runs, as runs/run_fullprof.py makes them):

  • P29: ce.pcr and ce2.pcr with ceo2.dat (a _calc run and the refinement), for FullProf's Ycal and esd's;
  • P11: nabd4.pcr and highpress.pcr (data present) as _calc runs, and one with user values that differ from the tables in a model the core calculates (for example Y2O3 with f′, f″ for Y and O, ITY = 2);
  • P18: a single-pattern run refining Lambda with the cell fixed (for example case A or Pd3MnD).

The app does not compile on 0.41.0: the match over Field in app/src-tauri/src/modules/refine.rs:741 needs Field::Wavelength | Field::Micro(_) with the pattern's fields ((None, false)).

core/src/jacobian.rs, powder.rs, pcr.rs, scattering.rs, refinable.rs, edit.rs, control/schema.rs, control/pcr_export.rs · core/tests/pattern_terms.rs, explore_stops.rs · manuals/fullprof/FullProf_Manual.pdf pp. 55, 70, 79–84 · manuals/fullprof/FP_tests (ce, ce1, ce2, ceo2.dat, nabd4, highpress, c60a, hpress, cedif4rf, pb_ho, pbb, pb_sing, gabriela) · GSAS-II GSASIIpwd.py L129–L140 (pinned commit) · L053, L211, L230
L2322026-10-14work

App stream, 0.26.0: L230's part A for the app: windows and tabs reopened at launch (P46), Refine's plot in 2\(\theta\), d or Q on a linear, √ or log scale (P51), a space-group view (P09); request 35

Merge remote-tracking branch 'origin/main' into app # Conflicts: # app/src-tauri/Cargo.lock

branch app 1f77678 · app/README.md (0.26.0) · app/REQUESTS.md 35 · L230
L2312026-10-14work

App 0.25.0 built on core 0.40.0 with every stream merged, installed and self-checked; one core test flaky in the full suite

App 0.25.0 brings together app 0.24.0 (L228), Explore's past cycle refined again (L227), drs-publish 0.8.0 (L229), Plots' eighth step (L226) and core 0.40.0 (L225).

Suites:

  • the core: 269 pass. explore_stops::e15a_the_pauses_in_the_record failed in the full suite twice, and passes alone three times out of three. It and e15b read their results files back through the same helper on the same control file, so a shared file between parallel tests is the likely cause. Passed to the core;
  • Publish: 30 pass;
  • the app: 70 Rust and 111 node tests pass.

Installed in /Applications, 0.23.0 to the Trash. The self-checks ran under L193:

  • on a copy of Y2O3: 46 OK, none FAIL. Auto-save wrote the edited file by itself, and the file on disk is the editor's text. The accent follows the system: teal while System Settings says multicolour (L224). Quit's path was taken;
  • on a copy of the \(\mathrm{Tb_{2}Pd_{2}Sn}\) buffer: 61 OK, none FAIL.

No copy of the app ran after the checks.

DRS_APP_SELFTEST reports of 14 October 2026 · core/tests/explore_stops.rs · L224–L230
L2302026-10-14decision

Milestone audit; Josu decides to finish version 1 first (its 13 partly done features), then version 2's three partly done ones

An item-by-item audit against the log, the releases and the tests, where "done" needs evidence: a log entry, a release, or a named test that passes.

The proposal's 59 features:

  • version 1, with P13 moved into it by L086: 39 features, 26 done, 13 partial, none not started;
  • version 2: 11 features, 3 partial (P25, P45, P55), 8 not started;
  • later: 9 features, 2 partial (P07, P19), 7 not started.

The brief: of 32 requirements, 18 are met and 14 partly met.

The roadmap: phases 0 and 1 are closed. Phase 2's goal is met: the four reference refinements are reproduced within FullProf's esd's, with Co-YBCO's lower \(\chi^2\) accepted in L055. Phase 3 is well advanced.

What is left, in steps of one prompt and report (estimates): about 65–90 in all. The core's version-2 queue of about 25–30 is the bottleneck.

Josu decided the order. 1. Version 1's 13 partly done features:

  • P09: symmetry elements and a space-group view;
  • P11: scattering factors given by the user;
  • P13: reading ISODISTORT, and modes computed from the structures;
  • P18: wavelength refinement;
  • P29: surface microabsorption;
  • P33: amorphous content;
  • P35: a file of extracted intensities;
  • P43: the Bérar factor;
  • P46: state restoration;
  • P48: adding phases and atoms;
  • P49: completion, and the jump from an output to its parameter;
  • P51: single-peak fitting, and Refine's plot in d/Q and √/log;
  • P57: the listing and summary. 2. Then version 2's three partly done: P25's rest, on the FullProf runs of branch fullprof-p25; P45, the parameters' impact and a normal probability plot; P55, guided refinement. 3. Then the rest of version 2.

The milestone was published for Josu as a page.

audit of 14 October 2026 · ecosystem/data/brief.yml · the proposal (L019, L020, L086) · roadmap · L001–L229
L2292026-10-14work

Publish stream, drs-publish 0.8.0: the publication set edited in the app; the CIF, pdCIF and supplementary material built from it (D1, D2, D4); the set nearest AMPLIMODES's reference named (E14b)

Publish stream, second step of the Publish module (drs-publish 0.8.0): the set edited in the app (items added, removed, moved; their options), the CIF for deposition, the pdCIF and the supplementary material built from the set (D1, D2, D4); T5, T6 and F6 report the set nearest AMPLIMODES's reference and name the operation (E14b); self-check 46 OK, 4 FAIL (the images, fixed after, to be seen at the next); P24 (one AMPLIMODES run read as one analysis) and P25 (PL28 b) filed.

branch publish 0eaa1c6 · publish/ · app/ui/modules/publish/ · publish/REQUESTS.md P23–P25 · L212, L220
L2282026-10-14work

App stream, 0.24.0: Josu's four decisions (L224), the small conventions, PL31, and main compiled again on core 0.40.0

app 0.24.0 (work), on main b90d5a9 (core 0.40.0). Josu's decisions (L224) and what APP_CONVENTIONS settles. Auto- save: a tab with changes (Editor, Refine) is written 2 s after its last change, and at once when its window closes, goes to the background or the app quits; a run in progress or a pending result is left alone; a project opened as it is (W9) is never written. File > Revert To lists Last Opened (replacing Last Saved) and the file's versions, snapshots and accepted refinements alike; what a revert replaces is kept as a version first. Numbers: the window already writes them with a point (toFixed, String); a test now keeps locale formatting out. Accent: the system's (AppleAccentColor, macOS's control colours, light and dark), the app's teal when multicolour, read again when a window comes forward. Rename...: the control file only, in its folder; its tabs reopen on the new name; files named by the old stem (versions, log, notes) keep that name. A hidden panel leaves a stub edge that brings it back; arrow keys in the sidebar's list and Refine's outline; files the app removes go to the Trash (/usr/bin/trash; removed under cargo test); Quit (⌘Q, the Dock, log-out) asks every window through the Rust side (RunEvent::ExitRequested, quit-check, a window silent for 5 s counted yes); Edit > Copy on the focused plot copies it as an image. PL31: Export > Plot as PDF at 96 px to the inch (it was 4/3 too large). Main did not compile on core 0.40.0 (E15's Cycle::steering and Trajectory's pauses and stop in modules/explore.rs): fixed. Tutorial note in tutorial/REQUESTS.md (Revert To > Last Opened, auto-save). One self-check of the built app before the merge of b90d5a9 (one instance, own home, a copy of the Y2O3 run): 44 OK, none failed, the app quit by itself through Quit's path. 108 node and 69 Rust tests pass; clippy clean. Not installed.

At the merge (the merging session): main at b90d5a9, with core 0.40.0, did not compile. Core 0.40.0 added lsq::Cycle::steering and Trajectory's pauses and stop (E15, L225), and the app's Explore bridge still built them without (explore.rs:28, :69), as the core's own release notes say. This step's fix, through Trajectory::new, ends it. cargo check of main after the merge is clean.

branch app 89de381 · app/README.md (0.24.0) · app/src-tauri/src/figure.rs (PL31) · L224, L225
L2272026-10-14work

Explore stream, eighth step: a past cycle opened and refined again from there (W11), from the core's state at the cycle; the Modes tab's naming on core 0.39.0's 4 sets; E16 (the run kept across a branch) to the core; E17 (the check before Run reads relative files from the wrong folder) to the app

origin/main (8c24d1f) merged. (1) P21 met (core 0.39.0, L222): the Modes tab's equivalent sets are the 4 of the same structure for case A; the case A test checks every cycle named by one of their operations, the end by -x,-y,z (the mirror), and the start, which the 24 sets named by a 3-fold about [1 0 1], is now named by the same 2-fold about [0 0 1]; the notes that P21 might change the list are gone (a test keeps them gone). (2) W11's next item, DESIGN.md §5's fifth step: a past cycle refined again. The Refine document takes the state of the cycle shown in Explore (the core's Trajectory::state_at, the variables at their values in that cycle) as one Undo step, "Return to Cycle k", a pending result discarded first after asking, as Refine's Discard does; then Refine runs from it (a new session, λ afresh). Explore ▸ Return the Document to This Cycle, Explore ▸ Refine Again from This Cycle, ⌘K, and "Refine from here" beside the cycle slider (data-tour ex-fromcycle). Shell (E10i): Document::cycle_restore in refine.rs beside history_restore, one line in main.rs; the window uses Refine's own setView, logNote and emptyRefine. Asked of the core, E16: the run kept across the branch (Trajectory::branch_at, a session continuing from it, the branch not taken kept), the branch's origin in the results file, the free parameters' values at a cycle (Trajectory::values_at). E15 passed to the core this round. (3) Found by the self-check, E17 to the app: the check before Run (editor::check, from Document::check and the editor) reads the text with no folder, so a file the control file names relative to itself (mode_reference's a_modes.sum) is not found, an error is reported at its line and Run is refused, though the document opens and refines (case A: 7 cycles, \(\chi^2\) 2.0654, in a test); the core's schema::read_with(…, Some(dir)) is the fix. Checked: cargo test --release modules::explore 6 pass (a_past_cycle_refined_again new: Y2O3 from a perturbed start, cycle 3 taken, the model the core's state at cycle 3 to the bit, the pending result dropped, the next run starting there, Undo giving the document back); node tests 105 pass (explore_cycle.test.mjs new); the window bundles. One self-check of the built app (not installed), the only instance, its own home, stopped by its PID, on a copy of case A with its sign reference and a perturbed start: 44 lines, 38 OK; it stopped at its wait for the run (timed out after "opened a_modes.drc"), the cause E17, so its Explore entries (the replay, the Modes naming, the compare and the new cycle entry) did not run. Shell lines touched, E10i.

branch explore 5ef8106 · explore/REQUESTS.md (E15, E16, E17, E10i) · explore/DESIGN.md §2.6, §5 · APP_WORKFLOWS.md W11 · app/src-tauri/src/modules/refine.rs (cycle_restore), explore.rs (explore_cycle_restore, the tests), main.rs · app/ui/modules/explore/index.js, README.md · app/ui/tests/explore_cycle.test.mjs, explore_modes.test.mjs · app/src-tauri/src/modules/editor.rs:90 · core/src/control/schema.rs (read_with) · L193, L218, L222
L2262026-10-14work

Plots stream, eighth step: Send to Publish hands the set to Publish, which builds it in LaTeX; the projection compared with Plots' own export; PL31 filed

Plots step 8: Send to Publish hands the set to Publish (PL29) and Publish builds it in LaTeX; the projection compared with Plots' Export ▸ Plot at 16 cm: Plots' PDF 4/3 too large (PL31, svg2pdf's dpi), the typeface, sizes, y axis, colours, labels, and Data's operations not applied by Publish (PL28 e); PL30's module part done, plot.js's needs listed.

branch plots a4d4393 · app/ui/modules/plots/ · plots/REQUESTS.md PL13h, PL28 e, PL30, PL31 · L220, L221
L2252026-10-14work

Explore's E15: a run's pauses, its stop's facts and the steering's notes kept as data and read back; Plots' PL27: the set file keeps each view's steps and scale

Core 0.40.0, tests explore_stops (E15) and pl27_each_views_steps_and_scale (plots_sets).

E15a, the pauses. Session keeps a Pause for each stop that is not the run's end:

  • the cycle after which it paused;
  • why: step (a step of n cycles ended) or caller (Session::pause() turns the caller's Stop::User into a pause, so the shell no longer undoes the Stop);
  • λ at that moment;
  • what changed before the run went on (Change::Value with its address and value, or Change::Parameters);
  • the λ the damping restarted at.

Trajectory::pauses carries them. The session text keeps them (pause …, change … lines), and so does the results file: one loop _drs_pause_after/_why/_lambda/_restarted/_changes and one of the changes as data (_drs_pause_change_*). results::read gives them back. A finished run drops the step's pause at its last cycle when nothing was changed there, since that is the run's end.

The test steps case A (explore/compare/cases/y2o3_a.drc) 2 + 2 + 2 cycles and sets pattern1.zero_shift = 0.05 in the second pause:

  • pauses after cycles 2 and 4;
  • the second pause is "changed, λ restarted" at the start's λ;
  • the session file and the results file give the same back;
  • a caller's Stop after cycle 3 becomes a (3, caller) pause, and the run goes on to cycle 4.

E15b, the stop's facts. Trajectory::stop is a Stopped: the Stop and the cycle after which it happened. Stop::facts() lists its facts:

  • diverging-jump: the parameter, the ratio and the cycle;
  • diverging-rising: the cycles, from and to;
  • diverging-impossible: what and the value;
  • singular: the combination and the ratio;
  • cycles: the count;
  • converged: Eps.

The results file writes them as _drs_stop_after and _drs_stop_{fact}, and Stop::from_facts reads them back. The singular stop's why and tip are words, not kept. Case E (y2o3_e.drc) stops diverging-jump on phase1.cell_a at cycle 5 (102 esd's), and its results file reads back the same parameter and cycle.

E15c, the steering's notes as data. Cycle::steering is a list of SteeringNote, one per note, beside the words in Cycle::notes. Each note has a kind and the fields that kind uses:

  • maximum-shift: the parameter's name and the maximum shift;
  • at-limit: the parameter and the bound;
  • reflected and wrapped: the field's address and the value;
  • refused: the step's λ and \(\chi^2\).

The results file keeps them in a loop _drs_steering_*, read back cycle by cycle, and drs refine --trace writes them per cycle, with the stop's facts and its cycle and the pauses. On case A:

  • cycle 1 shortens phase1.pv_eta_slope to its maximum shift;
  • cycle 2 shortens phase1.pv_eta0 and phase1.pv_eta_slope, as the words say;
  • the refused notes are the cycle's tries (3 in cycle 3).

A session resumed from its file has the earlier cycles' numbers but not their notes, words or data, as before.

PL27, the set file's views. plots::SetFile adds:

  • the scale the stack's step was set on (displacement = { …, scale });
  • the projection's steps apart from the stack's, with their scale (projection = { rule, dx, dy, scale });
  • the map's colour scale (map_scale);
  • the view's normalisation (normalisation = { by, … }, every rule of plots::Normalisation, a peak's background included).

x holds the view's quantity: SetFile::open no longer refuses one that is not 2\(\theta\), since the members are read in 2\(\theta\) and the view converts them (PL10, PL25). A file without these keys reads as before, and the defaults write none of them. On the command line:

  • drs plots normalise without --by takes the set file's normalisation;
  • drs plots save --by … writes one into the file.

PL28 is the Publish stream's (plots/REQUESTS.md, "To the Publish stream"): drs-publish new --plots, √ for a map on the log scale or map_scale, the member without a value at its measured 2\(\theta\), a scale bar. The core's part is PL27's data, which Publish reads through SetFile::read. Publish compiles on 0.40.0 unchanged.

The app does not compile on 0.40.0, at two literals in app/src-tauri/src/modules/explore.rs (Explore's):

  • line 28: the Cycle of cycle_of needs steering: Vec::new();
  • line 69: the Trajectory needs pauses: Vec::new(), stop: None, or Trajectory::new(start, set, cycles). E15a's shell part (drive to call Session::pause() in place of undoing a Stop) is the app stream's.
explore/REQUESTS.md E15 · plots/REQUESTS.md PL27, PL28 · core/src/lsq.rs, results.rs, plots.rs, main.rs · core/tests/explore_stops.rs, plots_sets.rs
L2242026-10-14decision

Josu decides four of the app's open conventions: auto-save with the versions kept; numbers with a point; the system's accent colour; Rename renames the file only

App 0.22.1 went through APP_CONVENTIONS.md and listed what waits on Josu (app/README.md). Josu decided four:

  • Auto-save, versions kept (§7, R22). Files are saved by themselves as in any Mac app. Snapshot and every accepted refinement still make a named version in Versions/, and Revert To ▸ lists both. Set aside: no auto-save; the window's state only.
  • Numbers on screen always with a decimal point (§10). That is how FullProf, CIFs and papers write them, so what is shown can be copied into a .pcr or a paper unchanged. Set aside: the system's separator.
  • The system's accent colour (§10). Buttons, selections and highlights take the accent chosen in System Settings, and the app's teal when the system says multicolour. Set aside: the teal everywhere.
  • Rename… renames the control file only (§3). The files it names stay as they are, and it still points to them. Set aside: renaming the whole family and updating the references.

The other small items follow APP_CONVENTIONS as a usual Mac app, without asking: a stub edge to bring back a collapsed panel; arrow keys in trees; Delete to the Trash; quitting with unsaved files; Copy as an image. The large ones are steps of their own. Still Josu's: the file types and their icons, and native window tabs.

Josu, 14 October 2026, morning · app/README.md (0.22.1) · APP_CONVENTIONS.md §3, §7, §10 · L217
L2232026-10-14work

App 0.23.0 installed and self-checked, the checks the streams could not run overnight among them

App 0.23.0 was built from main at 6172253; the commits after it changed tests and manuals/ only (L222). It is installed in /Applications, with 0.20.0 to the Trash.

The self-checks ran one after the other under L193, with the screen unlocked. They are the ones app 0.22.1, Explore, Plots and Publish could not run overnight, when a locked keychain held their launches.

  • On a copy of Y2O3: 40 OK, none FAIL.
  • What's New opened by itself after the update, with 40 releases marked new.
  • The Publish manual page has 6114 characters.
  • The Editor's Problems panel opened on a new problem in a .pcr, 1 problem listed (L208).
  • Explore's compare and Modes views pass, and a refinement is run and accepted.
  • On a copy of the \(\mathrm{Tb_{2}Pd_{2}Sn}\) buffer: 56 OK, none FAIL. Send to Publish wrote Plots/selfcheck publish.drv with the projection's step (Δx 0.22, Δy 2200 on the linear scale).

No copy of the app ran after the checks.

DRS_APP_SELFTEST reports of 14 October 2026 · L217–L222
L2222026-10-14work

Main green again: L216's FullProf runs moved to the branch fullprof-p25 for the core's P25 step; the counts of the 24 mirror sets moved to core 0.39.0's 4

The suites on main after the merges of L218–L221 failed in six tests, for two reasons, neither a fault of the code.

1. L216's three FullProf runs (prba10k, cuf1k, lac) raised the counts that four core tests keep of every FullProf file in manuals/:

  • fullprof_profiles: every .prf compared or left out with its reason, 191 against 197 on disk;
  • prf_files and hkl_files in fullprof_outputs (194 against 188);
  • control_round_trip in control_file.

Each new file is to be compared, or left out with its reason, by the core when it takes P25's rest. So the three folders live on the branch fullprof-p25 (pushed), and main keeps the script's entries, the README's rows and L216. The core merges fullprof-p25 in its P25 step and accounts for every new file there.

2. Core 0.39.0 met P21 (L218): the equivalent amplitude sets are only those of the same structure, 4 for case A where there were 24. Two tests had frozen the 24, and now count 4:

  • Explore's case_a_scene_with_irreps (app/src-tauri/src/modules/explore.rs);
  • Publish's the_mirror_solutions_are_counted_and_frozen (publish/tests/build.rs). Its own keep list, the sets that keep every irrep's amplitude, was exactly the four the core now gives: x,y,z; x,y+1/2,z+1/2; −x,−y,z; −y,−x,−z. The caption reads "one of 4 amplitude sets".

The count shrank because a fault was fixed, so no tolerance was widened. The Explore and Publish streams are told.

After:

  • the core: the four tests pass, and the other 261 had passed;
  • Publish: 27 pass;
  • the app: 67 Rust and 103 node tests pass.
core/tests/fullprof_profiles.rs:368, fullprof_outputs.rs:92, :140, control_file.rs:103 · app/src-tauri/src/modules/explore.rs:816 · publish/tests/build.rs:265 · branch fullprof-p25 · L216, L218
L2212026-10-14work

Plots stream, seventh step: Figure Look for all five views, figures at 8.5, 12 and 17 cm; files as the view shows them (PL25); PL30 filed

Plots step 7: Figure Look for all five views (on white, open axes, ink text, scale bars, colour bars' scales, Publish's window colours and shapes), figures exported at 8.5/12/17 cm; CSV and derived files as the view shows them (PL25); PL30 and PL13g filed; the self-check owed, blocked by an open system password prompt.

branch plots d40038b · app/ui/modules/plots/ · plots/REQUESTS.md PL13g, PL30 · L212
L2202026-10-14work

Publish stream: the Publish module's first step in the app (drs-publish 0.7.0 as the app's library), Send to Publish's hand-over

Publish stream, first step of the Publish module in the app (L207): drs-publish 0.7.0 as the app's library (items, preset, a set from a plot set PL28 a, an item's page at its printed size, build with progress and Stop); app/ui/modules/publish/ and its bridge; Send to Publish's hand-over window.__sendTo (PL29; Plots' one line to call it is the Plots stream's); guard, manual and ? topics; shell lines in publish/REQUESTS.md P22; the self-check held until the SecurityAgent prompt is answered.

branch publish aa8e314 · app/ui/modules/publish/ · app/src-tauri/src/modules/publish.rs · publish/REQUESTS.md P22 · L207
L2192026-10-14work

Explore stream, seventh step: the Modes tab on core 0.38.0's E14 (esd's at every cycle, the state named against the sign reference cycle by cycle, FullProf's reported amplitudes); P21's dependence said; the self-check not run, every launch held behind a system password dialog

origin/main (4920ba3) merged. (1) E14a: each cycle's state is summarised with its own covariance (state::summary_at; the result's at the end; none at the start or for a cycle read back), and each cycle says so (CycleState::esd): bars at every such cycle on both plots, an "esd at the cycle" column. (2) E14b: with the control file's mode_reference (FullProf's .sum, or an AMPLIMODES analysis) every state is named against it (Modes::reference_signs, Equivalent::words): a mark per cycle in the Q plot's tick band, red where an operation brings the state to the reference (the mirror); a table of the core's words per cycle; the start and the end in the note. (3) E14c: FullProf's .sum beside the control file, read by the core (read_fullprof_amplitudes): a tick at the end of each irrep's and mode's line, two columns in the table. (4) P21: the note and the equivalent sets say that the sets are the core's E7 over all the parent's operations, that P21 asks whether those that move amplitude between irreps belong among them, and that the list and the naming may change when it lands. Checked: cargo test --release modules::explore 5 pass; case A from GM4+ +0.4 Å and X3+ +0.2 Å, with FullProf's .sum as the reference and the parent from the course's AMPLIMODES analysis 1: esd's at cycles 1–4, none at the start; FullProf's 7 irreps and 12 modes, GM4+ 0.829(3); the start named "brought to the reference's setting by 3 about [1 0 1] (-y,-z,x)" (one of the sets P21 questions), the end "by 2 about [0 0 1] (-x,-y,z)", the mirror DESIGN.md §6.3 found, its amplitudes in the reference's setting FullProf's within 0.02 Å; on the Q plot FullProf's ticks stand in FullProf's setting (GM4+'s mode at −0.83 Å) where the core's line ends at +0.83 Å. Node tests 100 pass; the window bundles; modes.js run in node on the case's states (39 series, every cycle named). Not checked: headless Chrome hangs tonight on a plain static page (Chrome 155, its updater running), so no screenshot; and the self-check of the built app (not installed) did not run. The one launch (PID 45105, its own home, the only instance) stalled before the app's code ran (no settings folder made, 0.19 s of CPU in 17 min), as the Publish and Plots streams' did: a system password dialog (SecurityAgent, PID 11801, since 23:53:08) holds secure input and every launch since waits behind it. Stopped by its PID at the Publish stream's request; no other launch until Josu answers the dialog (the Publish and Plots streams hold theirs too). Shell lines, E10h: refine.rs:742, the arm of a field's phase, | Field::Quartic(p, _) | Field::QuarticShare(p) added as L212 describes it, without which the app does not build on core 0.37.0 or later; for the app stream to accept or replace.

branch explore ffd7de1 · explore/REQUESTS.md (E14, E10h) · publish/REQUESTS.md P21 · app/src-tauri/src/modules/explore.rs (summary_at, reference_of, FullProfView) · app/src-tauri/src/modules/refine.rs:742 · app/ui/modules/explore/ modes.js, index.js, README.md · app/ui/tests/explore_modes.test.mjs · core/src/modes.rs, core/src/state.rs · L193, L212
L2182026-10-13finding

Publish's P21: Modes::equivalent_amplitudes offered other structures as the same; the whole cell is now checked, and case A has 4 equivalent sets, not 24

Core 0.39.0, test p21_equivalent_sets_keep_each_irreps_amplitude (explore_requests).

The fault. equivalent_amplitudes (E7, L115) moved the distorted structure by each parent operation, matched each atom of the asymmetric unit with the nearest image of a moved atom (displacements) and projected the displacements on the modes; a residual below \(10^{-3}\) Å accepted the set. The match takes the images under the phase's own operations, so it imposes the phase's symmetry on the moved structure:

  • For a parent operation in the normaliser of P2₁/n (in this setting), the moved structure is P2₁/n-symmetric, and the set found is the same structure.
  • For any other operation, the moved structure is another domain, not symmetric under P2₁/n in this setting. Its asymmetric unit, symmetrised again, is a different P2₁/n structure. The modes describe every P2₁/n displacement, so its residual is 0 and it was accepted. Those are the 20 sets that moved amplitude between irreps (-y,x,z: GM4+ 0.8288 Å becoming 0.0363 Å). An operation of the parent's group maps each irrep's subspace onto itself, so it can change signs and mix modes within an irrep, never change an irrep's amplitude.

The fix. A set is now accepted only when the whole moved cell (every atom under every operation of the phase's group, then the parent operation) is the whole cell rebuilt from the amplitudes found, each atom within \(10^{-3}\) Å of one of the same element.

Case A now has 4 sets: x,y,z (the refinement's own), x,y+1/2,z+1/2, -x,-y,z (the mirror solution of Explore's +0.1 Å starts) and -y,-x,-z. These are the four P21 found keeping every irrep's amplitude. Each keeps every irrep's amplitude to \(10^{-6}\) and is the same structure (same_structure).

  • e6_e7_modes and e7_on_the_command_line asserted 24; they assert 4, with the reason.
  • E14b's per-state answer is unchanged (the identity, and -x,-y,z for the mirror).
  • Publish's the_mirror_solutions_are_counted_and_frozen (publish/tests/build.rs) froze the fault at (24, 4). With core 0.39.0 it is (4, 4) and its note "one of 24 amplitude sets" becomes "one of 4". The test is the Publish stream's to change.
core/src/modes.rs (equivalent_amplitudes) · core/tests/explore_requests.rs (e6_e7_modes, p21_equivalent_sets_keep_each_irreps_amplitude), core/tests/open_requests.rs (e7_on_the_command_line) · publish/REQUESTS.md P21 · publish/tests/build.rs · manuals/fullprof/runs/modes/A_modes · L115, L203, L214
L2172026-10-14work

App stream, 0.22.1: the quartic strain's parameters named (main compiles again); the Problems panel shown on a new problem; APP_CONVENTIONS gone through, nine small items met, the rest listed

app 0.22.1 (work), on main 4920ba3 (core 0.38.0). L208: the Editor shows the Problems panel when its check finds a problem the one before did not (the default layout hides the bottom panel); hidden again, it stays hidden until a new one. APP_CONVENTIONS.md gone through item by item, against the code (app/README.md 0.22.1 lists what is met, the small items left with what each needs or whose word, and the large ones). Met now: Close Window ⇧⌘W (the predefined item carried ⌘W, Close Tab's); Window > Bring All to Front; Edit > Use Selection for Find ⌘E in the Editor; Edit > Find also finds in the table shown; a window closed with unsaved files names them and asks (capabilities core:window:allow-close, allow-destroy, which Tauri's close-request handler needs); a click on the status bar shows the Log; the toolbar's visibility kept with the layout; the settings in ⌘K; a refinement runs at utility QoS (L150). Fixed: main's app did not compile on core 0.38.0 (Field::Quartic, QuarticShare not covered by param_label; a quartic term is named S<H><K><L>). The self-check (What's New from 0.21.0, the Problems panel, the small items) is written but did not run: the one instance stalled with no line in 10 minutes at 0 % CPU while five streams compiled the core (load ~51), and was stopped by its PID; nothing of 0.22.1 is confirmed in the built app yet. 102 node and 66 Rust tests pass; clippy clean. Not installed.

At the merge (the merging session, past midnight): the step's one self-check did not run. The instance stalled at 0 % CPU with the Mac's screen locked, so nothing of 0.22.1 is confirmed in the built app yet. Pushing waits for Josu: GitHub's token on this Mac is invalid (gh auth status) and the login keychain is locked, so every push and fetch hangs on its credential.

branch app 1800167 · app/README.md (0.22.1) · APP_CONVENTIONS.md · L208, L211
L2162026-10-13work

FullProf run on three of its own test files for the rest of P25 (needles, monoclinic and tetragonal strain); DyFeO3_SSpace crashes FullProf

The core found that the rest of P25 has no reference (L211): size by the quadratic form, spherical harmonics, platelets and needles; strain in the other Laue classes. It named four of FullProf's own test files that use them. Josu agreed to run FullProf on them, as L038 did for tutorial 6.

manuals/fullprof/runs/run_fullprof.py gained the four runs, and FullProf ran here in its two passes (refine; then NCY = 0 from the refined file). The program is fp2k of the macOS distribution of February 2025, ~/Applications/FullProf_dl/FullProf. Three runs finish:

  • prba10k (Pr₀.₅Ba₀.₅\(\mathrm{CoO_{3}}\)₋ₓ, P 4/m m m): anisotropic Lorentzian size, Size-Model −1 with the platelet–needle vector [0 0 1], and strain S_400, S_004, S_220, S_202. \(\chi^2\) 18.5 refined, 21.6 in the calculating pass;
  • cuf1k (\(\mathrm{CuF_{2}}\), P 2₁/n, D1A, over d1a_llb.irf): strain in the monoclinic class. \(\chi^2\) 4.12, then 4.13;
  • lac (\(\mathrm{La_{2}NiO_{4}}\), P4₂/ncm–Pccn, D1B, two phases): tetragonal strain. \(\chi^2\) 14.2, then 14.9.

No reference from DyFeO3_SSpace.pcr (size model 8). fp2k stops while reading it, in both passes, with a segmentation fault in the superspace group's reader (cfml_superspacegroups, forrtl error 174). It is left out, and the script and the runs' README say why.

Already there from L038, which the core may have missed: Pd3MnD, spherical-harmonic size (Size-Model −14).

manuals/fullprof/runs/run_fullprof.py, README.md, prba10k/, cuf1k/, lac/ · manuals/fullprof/FP_tests/ · L038, L211
L2152026-10-13work

Plots stream, sixth step: Send to Publish… builds Publish's F3 and series figures from a .drv; the projection's Figure Look with a scale bar; PL27–PL29 filed

Plots step 6: Send to Publish… writes a .drv that Publish builds into all five F3/series figures (the projection's steps as the set's displacement, the proposed step left out); the projection's Figure Look with a scale bar, exported by Export ▸ Plot; PL27–PL29 and PL13f filed.

branch plots 49ba137 · app/ui/modules/plots/ · plots/REQUESTS.md PL13f, PL27–PL29 · L204, L205
L2142026-10-13work

Publish stream, drs-publish 0.6.0: T5, T6 and F6 say which mirror solution they show; finding P21, most of the core's equivalent amplitude sets change an irrep's amplitude

Publish stream, drs-publish 0.6.0 on core 0.36.0: T5, T6 and F6 against Explore's mirror solutions (L203): each says it shows the refinement's own amplitude set, one of the core's equivalent sets (24 for case A), until E14b gives the one nearest AMPLIMODES's reference; finding P21: 20 of case A's 24 sets change an irrep's amplitude, which a parent operation should not; P2-P11 marked met.

P21 is passed to the core as the first item of its next step: an operation of the parent's group maps each irrep's subspace onto itself, so it may change the signs and mix the modes within an irrep, but not the irrep's amplitude. 20 of 24 sets that change it point to a fault in Modes::equivalent_amplitudes or in its use. Not checked by the merging session.

branch publish d2c8988 · publish/REQUESTS.md P21 · L203
L2132026-10-13work

Explore stream, sixth step: W11's stops marked on the path, from what the run's record gives; the pauses, which it does not keep, asked of the core (E15); the modes entry of the self-check confirmed in the built app

origin/main (2c98dd4) merged (the branch already at it). What the record gives, read: the run's stop at the end (the core's Stop, code and words); in each cycle the singular directions left out, the steps refused before the one taken (their λ) and the steering's notes (Cycle::singular, tries, notes); the session's own notes (links set, "after cycle k: … λ restarted" after a change in a pause), as prose. What it does not: the pauses. Session::step(n) pauses but keeps no list; the user's Pause reaches the core as a Stop by its caller, which the shell's drive undoes; Trajectory holds the start, the set and the cycles; the results file keeps the stop's code and words only. Built: marksOf (draw.js) turns those facts into marks, nothing worked out (L139): the end's stop in the shared words of stops.js (paused while the run can go on), S for singular directions, → for a cycle's steering notes (refused steps among them), λ for refused steps the core did not note. On the Traces view, a faint line at each marked cycle (red where the core says something went wrong) and a labelled handle along the frame's bottom, the words on hover, data- tour="mark:<cycle>:<kind>"; in the sidebar, each cycle's marks under it and the session's notes under the list, with "pauses before the last are not in the run's record yet (E15a)". The traces' note counts them; the tour's summary gives them (marks); the self-check's traces line reports them. E15 to the core: E15a the pauses in the record (after which cycle, why, λ, what changed and the damping restarted, as data; Session::pause in place of the caller's Stop; Trajectory::pauses; a loop in the results file; drs refine --session), with its shell part for the app stream (drive); E15b the stop's facts with its cycle (a jump's parameter, ratio and cycle; a rise's cycles; singular's combination) in the record and the results file; E15c the steering's notes as data (kind, parameter). Checked: node tests 94 pass (explore_stops.test.mjs new; nophysics among them); the window bundles; headless on the recorded Y2O3 run: six cycles marked → (the η's maximum shifts; cycle 3's three refused steps), the end ■ "Converged after 10 cycles". One self-check of the built app (not installed), the only instance, a home of its own, stopped by its PID, on a copy of case A: 66 lines, 27 OK, none FAIL. The modes entry, fixed after the last run, confirmed: 3 states, 7 irreps, 12 modes, A at the end GM1+ 0.283(5), GM4+ 0.829(3), X3+ 0.400(4), X5+ 0.122(6) (FullProf's .sum: 0.283(6), 0.829(3), 0.400(4), 0.122(6)), the parent not known from a .pcr (the core's reason), the plots drawn; the traces line: one mark, converged at cycle 2, one handle; compare OK. Shell lines touched, E10g: none.

branch explore 210994a · APP_WORKFLOWS.md W11 · explore/DESIGN.md §2.6 · explore/REQUESTS.md (E15, E10g) · app/ui/modules/explore/ draw.js (marksOf), index.js, README.md · app/ui/tests/explore_stops.test.mjs · app/ui/stops.js (plainStop) · core/src/lsq.rs (Session, Stop, Cycle) · app/src-tauri/src/modules/refine.rs (drive) · L193, L203
L2122026-10-13work

Plots' PL25 and PL26, Explore's E14a–c: files written as the view shows the set, the core's own CSV read back in d and Q, the modes' esd's at every cycle, the sign reference named per state, FullProf's reported amplitudes

Core 0.38.0, tests pl25_the_view_written_as_shown, pl26_the_cores_csv_read_back_in_d_and_q (plots_tables) and e14a_esds_at_every_cycle, e14b_the_sign_reference, e14c_fullprof_reported_amplitudes (explore_requests).

PL25, a file written as the view shows the set.

  • PlotSet::as_shown(rule) gives the set normalised and the lines a file carries to say so: "y normalised as the view shows it (the files are as read): the same area, 1, over 2\(\theta\) (°) 20 to 30", then "factor of tb_t02.dat: …" for each member. Normalisation::words writes the rule.
  • csv_with, csv_long_with and derived_text_with write those lines (# in a CSV, ! after the six header lines of the XYDATA file). csv, csv_long and derived_text keep their signatures and write none.
  • A rule that a member cannot follow (no monitor) is refused, naming the member: no file of half-normalised numbers.
  • drs plots export and drs plots derive take --by … as drs plots normalise does: normalised in the files' x, then converted.
  • \(\mathrm{Tb_{2}Pd_{2}Sn}\)'s long CSV with the same area over 20–30° is read back as the normalised set, each member's area 1 to \(10^{-12}\).

PL26, the core's own CSV read back in d and Q. The table reader knows d_angstrom and q_inverse_angstrom. \(\mathrm{Tb_{2}Pd_{2}Sn}\) written in d and in Q, wide and long, reads back as its 20 members in that quantity; the long form is equal to the bit, with each member's temperature from its line. The app's test normalise_convert_and_the_csv_are_the_cores, which froze the fault (2 members, 2\(\theta\)), now asserts the 20 members in d (app/src-tauri/src/modules/plots.rs, unfrozen at Josu's word in this step).

Found: core 0.37.0 (P25, L211) breaks the app's build. app/src-tauri/src/modules/refine.rs:742 matches every Field by name, and the new Field::Quartic and Field::QuarticShare are not covered. The app stream's fix is one line: add | Field::Quartic(p, _) | Field::QuarticShare(p) to the arm that gives (Some(p), false). It is not committed here: the app is the app stream's. With it applied locally (and removed again), the unfrozen test passes.

E14a, the esd's at every cycle. state::summary_at(model, data, set, covariance, opts) takes the covariance of the free parameters directly; summary is it with the refinement's. geometry::Propagator::from_covariance does the same for distances and angles. On case A's three cycles from FullProf's values:

  • every refined irrep has its esd at each cycle;
  • at the end, from the refinement's covariance given directly, the same to the bit;
  • at cycle 3 against the end, within 1.1·\(10^{-4}\) of each esd (X5+): the cycle's normal matrix is taken at its start values, the end's at its last;
  • an empty covariance (a cycle read back from a results file) gives none. The core's GM1+ esd, 5.268·\(10^{-3}\), is FullProf's 0.005271.

E14b, the sign reference.

  • The control file's mode_reference = { file = "…", analysis = k } per mode phase, read relative to the control file, holds the amplitudes by mode name (Modes::sign_reference, SignReference).
  • The file is an AMPLIMODES output (analysis k's block for FullProf, and its parent when the control file states none) or FullProf's .sum.
  • Modes::reference_signs gives a state's equivalent set nearest the reference, and Equivalent::words names it: "the reference's setting", or "brought to the reference's setting by 2 about [0 0 1] (-x,-y,z)".
  • On case A, with FullProf's amplitudes as the reference (a_modes.sum): FullProf's own state is the reference's setting; the mirror solution of Explore's +0.1 Å starts is brought back by the parent's 2-fold about c, as in e6_e7_modes.
  • Found: of case A's AMPLIMODES file, analysis 1 (the phase's P2₁/n) has no block for FullProf, so no amplitudes by mode name, and is refused so. Analysis 3 has one, but in its P2₁/c setting (e6_e7_modes): read (parent 225), but its signs are not the phase's basis. A reference in the phase's own setting needs an AMPLIMODES run on it, or FullProf's .sum.
  • drs modes <file.drc> prints the state against the reference.

E14c, FullProf's reported amplitudes. modes::read_fullprof_amplitudes reads a .sum (or .out): per mode phase, by FullProf's phase number, the "Amplitudes of symmetry modes" (name, value, sigma) and the "Global Amplitude of Representation" lines (0.283(6) read as 0.283 \(\pm\) 0.006).

  • Case A has 12 modes (A1–A12; the request said eleven) and 7 irreps, equal to the print.
  • a_modes_calc.sum prints the phase's results twice; it is read as one entry, the same values.
  • The .pcr holds the values FullProf read (it equals a_modes.pcr.in); the .sum holds those after its last cycle. They differ by at most 0.019\(\sigma\) (A10_X5+), within FullProf's own stop, Eps = 0.2 of \(\sigma\).
  • drs modes <file.pcr> prints them from the .sum beside the .pcr.
core/src/plots.rs (as_shown, Normalisation::words, csv_with, csv_long_with, derived_text_with), core/src/formats/table.rs (axis_of), core/src/state.rs (summary_at), core/src/geometry.rs (Propagator::from_covariance), core/src/modes.rs (SignReference, reference_signs, Equivalent::words, FullProfAmplitudes, read_fullprof_amplitudes), core/src/control/schema.rs (mode_reference), core/src/main.rs (drs plots export/derive --by, drs modes) · core/tests/plots_tables.rs, core/tests/explore_requests.rs · app/src-tauri/src/modules/plots.rs (the test unfrozen), app/src-tauri/src/modules/refine.rs:742 · plots/REQUESTS.md PL25, PL26 · explore/REQUESTS.md E14 · manuals/fullprof/runs/modes/A_modes (a_modes.pcr, a_modes.sum, a_modes_calc.sum) · manuals/bcs/mode-crystallography-manual/course-um6p-2024/04_tools_materials/0_1_P21nToCubicTransData.txt · L192, L195, L211
L2112026-10-13work

L153 step 3, version 2's physics: P25 begun, anisotropic strain by Stephens' quartic form, checked reflection by reflection on FullProf's run of \(\mathrm{Nd_{2}NiO_{4}}\); the rest of P25 has no FullProf reference in the repository

Core 0.37.0, test fullprof_anisotropic_strain (3), and NdNi-LTOa added to engine_derivatives.

The reference. Of the FullProf runs with anisotropic broadening and their output in manuals/fullprof/, one uses a model that the manual states as an equation: runs/NdNi-LTOa. It is \(\mathrm{Nd_{2}NiO_{4}}\) at 1.5 K on D2B:

  • Pccn, Laue class mmm, Str = 1, Strain-Model 3;
  • S_400 S_040 S_004 S_220 S_202 S_022 = 20.490307 16.256466 0 −36.733093 0 0, ξ = 0.13621;
  • over the resolution file, with U and X besides. The other two runs of L038 were looked at and not used:
  • NdNi-LTOb's strain model 6 (Str = 0) is described in words only ("fluctuations along a, b and c with corr(a, b) = −1, corr(a, c) = 1, corr(b, c) = 0", manual p. 29), not by an equation.
  • Pd3MnD's size model −14 is selective broadening (the antiphase domains of \(\mathrm{Cu_{3}Au}\), pdfinal_calc.out, lines 342–344): P26, not P25. runs/README.md and the profile index called it "spherical harmonics". The index's generator is corrected; the README is not the core's to edit. The FP_tests files with anisotropic models have a .pcr and data but no FullProf output.

The model (microstructure::QuarticStrain, Phase::quartic_strain):

  • \(\sigma\)²(M) = \(10^{-8}\) Σ S_HKL h^H k^K l^L, with M = 1/d\(^{2}\).
  • D_ST = √(8 ln 2)(180/π) \(\sigma\)(M)/M (manual p. 22).
  • H_G\(^{2}\) += [(1 − ξ)D_ST]² tan\(^{2}\)\(\theta\) and H_L += ξ D_ST tan \(\theta\): the split that the .mic header writes (lines 36–38).
  • The terms come from the group: each monomial invariant under its rotations. This is defined only for the Laue classes whose rotations are diagonal (−1, 2/m, mmm), where each S_HKL multiplies one monomial.
  • Refused, with the reason:
  • the classes whose terms are tied by Stephens' relations (tetragonal, trigonal, hexagonal, cubic);
  • −1 from a .pcr, because the manual lists S_301 twice and no S_310 (p. 119), so its order is not known.
  • Stephens' paper (J. Appl. Cryst. 32, 281, 1999) is not in the repository. The form is as the manual and the .mic header state it, so the default and FullProf's are one here, and no departure is known. Listed for Josu: the paper, to check the form and to read the other classes.
  • Manual p. 28 gives c = 2√(ln 2)(180/π)·\(10^{-3}\) for D_ST, in a part the manual itself says "needs to be corrected". FullProf's run follows p. 22.

Checked against FullProf (fullprof_anisotropic_strain), with the magnetic phase left out (it is not calculated):

  • The 366 reflections of phase 1 in NdNi_LTOa_calc.out (Hw, HwG, HwL, ETA): at 0.84 of the tolerance (half the print and 2·\(10^{-5}\), as fullprof_microstructure). The isotropic widths alone miss 359 of them.
  • The 366 rows of NdNi_LTOa_calc1.mic: widths and breadths at 1.00 (HLi, the resolution file's print, as in the isotropic runs); maximum strain at 0.66.
  • Found: FullProf takes the maximum strain from its refined U, X, S_HKL and ξ, but at the reflections' d of the cell it read. With the cell after the cycle, the strongest rows are 1.5·\(10^{-4}\) above its print, 2.4 times the effect of the other parameters' print; with the cell read, every row is within the print.
  • The whole pattern (.prf) is not compared: phase 2 is magnetic. So the peak window with ξ D_ST tan \(\theta\) (left out of the window as X tan \(\theta\) is) is not checked on a FullProf profile.

The engine and the files:

  • Each S_HKL and ξ are refinable fields (Field::Quartic, Field::QuarticShare; phase1.quartic_strain.400, phase1.quartic_strain_lorentzian, ξ held within 0–1) with analytic derivatives. They are checked against finite differences in engine_derivatives (worst 2·\(10^{-7}\)).
  • The Gaussian part is linear in the sum, so an S_HKL at 0 keeps its derivative there; the Lorentzian √sum has none at 0.
  • At \(10^{-6}\) of c, a reflection entering the list made NdNi-LTOa's finite difference jump at 92.7°, with or without the form. The cell takes \(10^{-8}\) there.
  • A pattern calculated with the run's form, the S_HKL and ξ moved by 20 %, is refined back to \(10^{-5}\).
  • The .pcr's LINE 37–38 and codewords are read (201, 151, 221, 211 free) and written back (Str = 1, Strain-Model 3) to the same values and free parameters.
  • The control file holds quartic_strain = [[H, K, L, S_HKL], …] and quartic_strain_lorentzian. A term the group does not allow is refused at its place.
  • Dictionary entries quartic_strain and quartic_strain_lorentzian, with their correlations with U and X. Their relaxation class is believed profile: LINE 9 names none.
  • drs mic lists the run's rows, leaving out the magnetic phase and saying so.
  • NdNi-LTOa is now imported (its nuclear phase). default_convention gives \(\chi^2\) 63.120871 under FullProf's rules and as imported (\(\Delta\) 0). Under the default model \(\chi^2\) moves by 6.990·\(10^{-3}\): the untranslated rules (peak window, reflections past the data, window edge, background below its first point), not the quartic form. It is frozen at 7.060·\(10^{-3}\) (the measured value + 1 %, as L050 froze the others).

The rest of P25 waits for a reference: size by the quadratic form, by spherical harmonics, platelets and needles; strain by the other classes. The repository has no FullProf run of them with its output. FullProf's manual has no spherical-harmonic strain at all. Running FullProf on FP_tests files that use them (prba10k needles, DyFeO3_SSpace size model 8, cuf1k 2/m, lac 4/m) would make the references, as L038 did.

core/src/microstructure.rs (QuarticStrain, quartic_terms, fullprof_strain_model, breadths), core/src/powder.rs (peak_width, strain_widths, quartic_of), core/src/jacobian.rs (Field::Quartic, QuarticShare, SUM, XI), core/src/pcr.rs (quartic, xi), core/src/refinable.rs, core/src/control/schema.rs, core/src/control/pcr_export.rs, core/src/main.rs (drs mic) · core/tests/fullprof_anisotropic_strain.rs, core/tests/engine_derivatives.rs, core/tests/data/make_fullprof_profiles.py · manuals/fullprof/runs/NdNi-LTOa (NdNi_LTOa.pcr, NdNi_LTOa_calc.out, NdNi_LTOa_calc1.mic, NdNi_LTOa_calc.pcr) · manuals/fullprof/FullProf_Manual.pdf pp. 22, 28–30, 108–110, 117–121 · runs/NdNi-LTOb, runs/Pd3MnD/pdfinal_calc.out · ecosystem/data/proposal.yml P25 · L037, L038, L039, L043, L044, L153
L2102026-10-13work

App stream, 0.21.1: Josu's shortcut decisions (L209), the clash count at zero

app 0.21.1 (work), on main 4341f41. Josu's decision on the two shortcut clashes app 0.21.0 found (L209): Refine > Run Next Plan Step is ⌃⌥⌘R (One Cycle keeps ⌥⇧⌘R); ⇧⌘D is File > Open Data File...'s only, and Data > Open Data File... runs the same command (file.opendata) with no shortcut of its own, its description corrected (an untitled project in its own window, not its folder as the project). No clash is left: shortcuts-settings.test.mjs asks for none and checks both shortcuts. The manual's two mentions of Run Next Plan Step's shortcut follow. 98 node tests pass; cargo check clean. No self-check (the merging session's word). Not installed.

branch app 33ad607 · L209
L2092026-10-13decision

Josu settles the two shortcut clashes app 0.21.0 found: Run Next Plan Step ⌃⌥⌘R; ⇧⌘D on File ▸ Open Data File… only

App 0.21.0's check of the shortcuts (L208) found two clashes:

  • ⌥⇧⌘R on both Refine ▸ One Cycle and Run Next Plan Step;
  • ⇧⌘D on both File ▸ Open Data File… and Data ▸ Open Data File…, two commands that do the same thing.

Josu decided:

  • Run Next Plan Step gets ⌃⌥⌘R. It stays an R beside Run (⌘R) and One Cycle (⌥⇧⌘R). Every other R combination is taken: ⇧⌘R, ⌃⌘R, and ⌥⌘R for Show in Finder. Set aside: no shortcut, and ⌘].
  • ⇧⌘D stays on File ▸ Open Data File… only. Data's menu item shows the same command without a second shortcut. Set aside: Data's only; one command listed in both menus.

For the app stream's next step. The frozen count of clashes in its test goes to zero.

Josu, 13 October 2026, evening · app/ui/modules/refine/index.js:453, :485 · app/ui/app.js:781 · app/ui/modules/data/index.js:372 · L208
L2082026-10-13work

App stream, 0.21.0: a .pcr checked by the core as it is typed (request 13); shortcuts that can be changed (Settings ▸ Shortcuts…); two shortcut clashes found for Josu

app 0.21.0 (work), on main 2c98dd4 (core 0.36.0). Request 13: a FullProf .pcr in the Editor is checked by the core as typed (check::check_pcr, as drs check file.pcr; its files found from its folder as FullProf finds them), its problems underlined and listed as a .drc's; a problem placed by the model's values only is put on line 1; the run level (derivatives) is left to Refine's Run. APP_CONVENTIONS §6: Settings > Shortcuts... changes a command's shortcut (Change waits for the keys, None, Default, Reset All); a new one needs a modifier or is a function key, is not the system's (ui/shortcuts.js SYSTEM, from Apple's Mac keyboard shortcuts and §3), nor another command's in the menu bar at the same time; the standard commands keep theirs; kept in the settings, read by every window's menu bar; the menus carry no key equivalents while one is recorded. Found for Josu: two shortcuts on two commands in the menu bar at once, ⇧⌘D (File and Data > Open Data File...) and ⌥⇧⌘R (Refine > One Cycle and Run Next Plan Step); the old test compared shortcuts as written and missed the second; frozen in shortcuts-settings.test.mjs. Manual: the Editor page has the .pcr's checks and the point-line link by the core's point_lines (stale since 0.19.1); the app page has Shortcuts. One self-check of the built app (DRS_APP_SELFTEST; one instance, own home seeded with lastVersion 0.19.0, a copy of the Y2O3 run, own PID): every line passed; What's New after the update from 0.19.0 marked app 0.19.1 and 0.20.0 new; a shortcut changed, kept, refused for ⌘Q and ⌘R, given back; y2o3_structure.pcr checked (0 errors, 1 warning), the cell line broken gave an error at line 109. The Problems panel's list was not seen: the default layout hides the bottom panel (as for a .drc). 98 node and 66 Rust tests pass; clippy clean. Not installed.

branch app 8852719 · app/REQUESTS.md 13 · APP_CONVENTIONS.md §6 · L168
L2072026-10-13decision

Josu accepts Publish's design: the Publish module comes into the app (P12)

Asked whether he accepts publish/DESIGN.md, on which P12 waits ("the Publish module, once DESIGN.md is accepted"), Josu: "yes, I accept". L106 had recorded the design's decisions, but not its acceptance.

What follows. The Publish module comes into the app as Explore and Plots did, in the order of DESIGN.md §6.6:

  • app/ui/modules/publish/ and a bridge app/src-tauri/src/modules/publish.rs, calling drs-publish's library as the other modules call the core;
  • the usual Mac app of APP_CONVENTIONS;
  • every character LaTeX in Latin Modern (L103);
  • no calculation in the window (L139).

The Publish stream takes the module, owning app/ui/modules/publish/ and its bridge as Explore and Plots own theirs. It lists the shell lines it touches for the merger. Its first step is planned in its next prompt.

Josu, 13 October 2026, evening · publish/DESIGN.md §6.6 · publish/REQUESTS.md P12 · L094, L106
L2062026-10-13work

App 0.20.0 built on core 0.36.0 with every finished stream merged, installed and self-checked; Publish made to compile on core 0.36.0

The merging session merged app 0.19.1 (L201), core 0.36.0 (L202, renumbered from L200), Explore's fifth step (L203), drs-publish 0.5.0 (L204) and Plots' fifth step (L205), and set the app's version to 0.20.0.

Publish did not compile on core 0.36.0. Core 0.36.0 added two sources of a member's value, Found::TemperatureFile and Found::CsvLine (PL21, PL8), and Publish's caption words matched only the older five (publish/src/patterns.rs:91). The merging session added the two words, "FullProf's temperatures file" and "the CSV's comment lines", in the style of the others. The Publish stream may reword them.

Suites:

  • the core: 256 pass, none fail;
  • Publish: 24 pass;
  • the app: 65 Rust and 92 node tests pass.

Installed in /Applications, 0.19.0 to the Trash. The self-checks ran one after the other under L193:

  • on a copy of Y2O3: 27 OK, none FAIL. Explore's Modes tab answered in 0.4 s and replayed cycles 0–2;
  • on a copy of the \(\mathrm{Tb_{2}Pd_{2}Sn}\) buffer: 40 OK, none FAIL. L200's failure is gone: the check gives tb_t20.dat a factor of 2 through Data's command. Normalise… by the same area over 20–30° normalised all 20 members. By the monitor it left all 20 out, with the core's reason "the file states no monitor".
publish/src/patterns.rs:91 · core/src/plots.rs (Found) · DRS_APP_SELFTEST reports of 13 October 2026 · L193, L201–L205
L2052026-10-13work

Plots stream, fifth step: Normalise… and Convert X… as views, a plot set as CSV out and in; PL25, PL26 filed; the self-check makes its own Data operation

Plots' fifth step, on core 0.34.0 and 0.35.0 (PL6, PL7, PL8, PL10; L195, L199):

  • Normalise… and Convert X… are views in every view, and each view says which it shows;
  • Export ▸ Plot Set as CSV…, and Open CSV as a Set…;
  • L200's failure is fixed: on a fresh copy that keeps no Data operations, the self-check gives tb_t20.dat a factor of 2 through Data's own command, then reads the members again. In a reference folder it says "not checked". The self-check of the built bundle, on a fresh copy: 42 OK, none FAIL.

Filed for the core:

  • PL25: a CSV or a derived file to record the view's normalisation;
  • PL26: the table reader does not know its own d_angstrom and q_inverse_angstrom columns, so the core's CSV in d reads back as 2\(\theta\), and the long form as two columns. Frozen in the test.

The shell lines are listed in PL13e.

branch plots 5cce224 · app/ui/modules/plots/ · app/src-tauri/src/modules/plots.rs · plots/REQUESTS.md PL13e, PL25, PL26 · L195, L199, L200
L2042026-10-13work

Publish stream, drs-publish 0.5.0: F3 and the series view drawn from a plot set file (PL15 met on Publish's side)

Publish stream, drs-publish 0.5.0 on core 0.33.0: PL15 met on Publish's side. F3 (stack, projection with hidden parts, map and difference map, of the whole pattern or of chosen windows) and the series view (windows' integrals, centroids, peaks, background against the variable; F4/F7's layout) drawn from a plot set file; every number the core's plots module; tested on the Tb2Pd2Sn buffer saved as a .drv; Publish's F2 (models compared) stays later, the projection being a view of F3.

branch publish 366df11 · publish/ · plots/REQUESTS.md PL15 · L192, L197
L2032026-10-13work

Explore stream, fifth step: the modes' subspace, each irrep's and each mode's amplitude per cycle with the end's esd's and the amplitude sets equivalent to the end's (the mirror solutions), from the core; E14 filed

origin/main (36093c7) merged (the branch already at it). Which item and why: of the three open items of R31 that DESIGN.md plans (a past cycle refined again, W11's stops on the path, the modes' subspace), the modes come first: DESIGN.md §5's order of work puts the modes' traces at its third step, beside the morph that Josu moved earlier and that is built; Josu's answer 6 to §8 (AMPLIMODES as the sign reference, the mirror named) belongs to it; and the core gives most of it now (state::summary per cycle, Modes::irreps, the end's esd's from the engine's covariance, Modes::equivalent_amplitudes, E7). The other two belong to §5's fifth step (time) and wait on the shell more than on the core: a past state set as the document's whole model (undoable) and a run started from it; the run's pauses kept in its record (today only its final stop). Built: the Modes tab (modes.js, two shared plots): each irrep's amplitude A and each mode's Q at every cycle, coloured by irrep, the end's esd's as bars; a phase select; the bottom panel lists the irreps and modes at the start, at the cycle shown and at the end with its esd, and the amplitude sets that describe the end's structure up to the parent's symmetry when the parent is known (for a .pcr it is not: the core's reason is shown); explore.view.modes in the menu and ⌘K; the self-check replays the tab. explore.rs: StatesView gains ModesInfo (each mode's irrep, whether the end's esd's are the engine's, the parent, the equivalent sets through the core's equivalent_amplitudes). E14 to the core: E14a the esd's at every cycle (summary from a cycle's covariance), E14b the sign reference from the AMPLIMODES analysis (a control-file key; per state the equivalent set nearest it and the operation, so that the mirror is named cycle by cycle), E14c FullProf's reported amplitudes (its .sum block) beside the core's. Checked: cargo test --release modules::explore 5 pass (case A from perturbed amplitudes: 7 irreps, 12 modes, the end's esd's the engine's, no parent from the .pcr and the core's reason; with the parent from the course's AMPLIMODES analysis, 24 equivalent sets, the identity's first and equal to the end); node tests 87 pass (explore_modes.test.mjs new; nophysics among them); the window bundles; headless on case A's states: GM4+ \(0.40 \to 0.83\) Å, two X5+ modes changing sign on the way (DESIGN.md §6.3's mirror, now seen cycle by cycle), the tables of 19 rows and 24 sets. One self-check of the built app (not installed), the only instance, a home of its own, stopped by its PID, on a copy of case A: 66 lines, 27 OK, 1 error, in the modes entry's own report line (it named the view's variables out of their scope); the view answered in 0.4 s and replayed; compare OK (17 in common, ends 17 agree, r −0.938). Fixed after the run (the line reads the view through its DOM; the error notes carry the message; a test keeps the view's variables out of the self-check); not run again, as L193 allows one self- check a step. Shell lines touched, E10f: none.

branch explore fc89d72 · explore/DESIGN.md §2.5, §5, §6.3, §8 · explore/REQUESTS.md (E14, E10f) · app/ui/modules/explore/modes.js, index.js, README.md · app/src-tauri/src/modules/explore.rs · app/ui/tests/explore_modes.test.mjs · core/src/modes.rs (equivalent_amplitudes), core/src/state.rs (summary) · L112, L193
L2022026-10-13work

Plots' PL23 and PL24: a temperatures file's value kept by the set file, a .drv known by its content

Core 0.36.0, tests pl23_a_temperatures_files_value_kept and pl24_a_set_file_by_its_content (plots_sets).

PL23. A member's value in the set file has one of two sources: typed (variable = { value = 1.5, source = "…" }) or a FullProf temperatures file (variable = { value = 6.5, temperatures = "Tb.tem" }). SetFile::of writes both; open sets the value with Found::TemperatureFile, and adds it to the member's candidates as reading the file did. SetMember::value is now (value, Found), with Found::Typed or Found::TemperatureFile.

  • The value is written, not read again from the temperatures file on open: the set opens the same when that file has moved or changed, and the value keeps its source (L169). Of the request's two ways this one was taken for that reason; reading the file again would make a set depend on a file it does not hold.
  • A value with both sources, or none, is refused with its place.
  • Plots' self-check: tb_t03.dat takes 6.5 K from Tb.tem, the set is saved and opened again, and tb_t03.dat is at 6.5 K from Tb.tem, no longer at the 6 K of its title (still a candidate).
  • drs plots save <set.drv> --temperatures <file>, and --set, wrote the set file as it was read and lost the values chosen on the command; they are now kept with their sources.

PL24. formats::project::PLOT_SET: a file whose format line is "drs plot set 1" is a plot set wherever it lies and whatever its name; a .drv is one by its extension too. drs data info names a set file away from Plots/ (for example, sent by a colleague as a .txt) a plot set, where it said "unknown".

core/src/plots.rs (SetMember::value, SetFile::read, to_text, open, of), core/src/formats/project.rs (PLOT_SET), core/src/main.rs (drs plots save) · core/tests/plots_sets.rs · plots/REQUESTS.md PL23, PL24 · ecosystem/data/files.yml (plot_set) · L169, L192, L195
L2012026-10-13work

App stream, 0.19.1: the core's requests 12, 15, 16, 31 and 32 used (export of the processed pattern, a point and its line, the analyses' words, files named by the core, FullProf exports); 6.1b confirmed

app 0.19.1 (work), on main 36093c7 (core 0.35.0). The core's answers used where the app had its own rule or nothing. 12: Data > Export the Processed Pattern (disabled since 0.6.0) writes the pattern after the operations by formats::write, proposed as the dictionary's Data/<data> processed.xys, in the formats the core writes for the scan's x. 15 (W6): a point selected in the plot selects its line in the Editor and the other way by formats::point_lines (FullProf's free format included, eight intensities a line); the Editor's own first-number index is gone; nothing is linked while operations are applied. 16: Find Peaks and Automatic Background worded from data_ops::settings (the analysis, each setting's name, unit, meaning, raise, lower, proposed, source); the HTML holds none of the app's own words. 31: the sidebar names files by describe_in (a project's file) and describe_path (a file alone); the app's rule ranking the dictionary's role over the content's is gone (the Versions and FullProf/ groups stay the app's, W5, W14). 32: Refine > Export for FullProf (new) writes the .pcr and its files by control::pcr_export into Exports/FullProf/ under the dictionary's names. One self-check of the built app (one instance, own home, own PID): 3 of 3 scenes pass; the processed pattern and the exported .pcr written and listed as outputs; 6.1b confirmed (a data file opened alone and saved as a project opens in Data, Start a Refinement enabled and its dialog shown). Not done: request 13's check_pcr in the Editor. 90 node and 64 Rust tests pass; clippy clean. Not installed.

branch app 417c5b5 · app/REQUESTS.md 12, 13, 15, 16, 31, 32 · L168, L199
L2002026-10-13work

App 0.19.0 built on core 0.35.0 with the app, Explore and Plots steps merged, installed and self-checked

The merging session merged app 0.18.1 (L198), Explore's fourth step (L196), Plots' fourth step (L197) and core 0.35.0 (L199), and set the app's version to 0.19.0.

Suites:

  • the core: 254 pass, none fail;
  • Publish: 21 pass;
  • the app: 60 Rust and 85 node tests pass.

Installed in /Applications, 0.18.0 to the Trash. The self-checks ran one after the other under L193:

  • on a copy of Y2O3: 27 OK, none FAIL, Explore's compare view among them;
  • on a copy of the \(\mathrm{Tb_{2}Pd_{2}Sn}\) buffer: 35 OK, 1 FAIL. The stack opened with the core's proposed step (2000). The set was saved as Plots/selfcheck.drv and opened again. The one FAIL is Plots' check of members after Data's operations, which fails when no file of the project has an operation kept. A fresh copy has none, so it is the check's precondition, not the app: the check is to make its own operation through Data, or say "not checked". Passed to the Plots stream.

The merging session's own test for a running copy of the app (pgrep -f drs-app) also matched the Plots stream's compiler, whose paths name the crate. The app's process is to be matched by its exact name.

DRS_APP_SELFTEST reports of 13 October 2026 · L193, L196–L199
L1992026-10-13work

A CSV of several columns read as a set (PL8), a set in d, Q, sin \(\theta\)/λ and 1/d as a view (PL10); the app's requests 31 and 32 found met by core 0.27.0, the command line's last gap closed, a results file named by its first line

Core 0.35.0, test plots_tables (5).

PL8, a CSV of several columns read as a set.

  • The table reader gives one scan per y column when the header names two or more. Each \(\sigma\) column is paired with its y column by the name left after the y or \(\sigma\) word ("y3" with "sigma3", "I_300K" with "esd_300K"); a y without its \(\sigma\) takes √y, and says so.
  • A table with a "member" column (PL7's long CSV) gives one scan per member.
  • An empty cell is a point that member does not have, not a row dropped.
  • PL7's # lines ("# y2: Data/tb_t02.dat (scan 1 of 1, free_steps), T = 4 K (the title's …)", and the prototype's shorter form) are read back as each scan's label, file, format and value of the variable with its source (Info::other, "member …").
  • In a set, such a member's variable comes from its line, with the source Found::CsvLine (the line's own source quoted). Nothing comes from the CSV's title or the CSV's name, which are the set's: BT, which its line says nothing of, has no value.
  • Every column not read is said ("columns not read: 2 of the file's 5 columns: column 4, column 5"), also when the user chooses one y in a file of several.
  • The mock-up's 41-column series CSV, read until now as one scan with the other 19 dropped silently, is 20 scans. Each equals its file's pattern, labelled by its file, its temperature from its line.
  • The core's own wide and long CSV of \(\mathrm{Tb_{2}Pd_{2}Sn}\) read back to the members' patterns to the bit, with their variables. Tb with D20 on the union grid gives 300 and 1535 points, as on_grid has them.

PL10, a set in another quantity, as a view.

  • plots::x_view(set, quantity, given, fallback) shows the set in 2\(\theta\), d, Q, sin \(\theta\)/λ or s = 1/d (WinPLOTR's x spaces, deck p. 43). Each member's λ is the one given for it (typed), else its file's, else a control file's λ1 or a typed one for all.
  • Members that need a λ and have none are listed. From d or Q no λ is needed, except to show 2\(\theta\).
  • The conversion goes through sin \(\theta\)/λ; points with no value in the new quantity are left out; y and \(\sigma\) are unchanged; the files and their operations are untouched.
  • Checked: Q and d against Bragg's law to \(10^{-12}\); s = 2 sin \(\theta\)/λ exactly; Tb's 2\(\theta\) → d \(\to 2\)\(\theta\) back to \(10^{-12}\).
  • drs plots view <set> --x … [--lambda k=λ] [--lambda-from <file.drc> | --wavelength λ].

The app's requests 31 and 32: met by core 0.27.0 (L168).

  • formats::project::describe_in(rel, probe) names a project's file by the file dictionary first, and drs data info --in <project> uses it.
  • FullProf's exports are in Exports/FullProf/, and kind_of("FullProf/…") names no export (tests app31_a_file_named_by_its_place, app32_fullprof_exports_in_exports_fullprof). app/REQUESTS.md still marks both open.
  • The command line's last gap is now closed: describe_path(path, probe) tries the file's place relative to each folder above it (up to four), so drs data info without --in names a file under Results/ a result.
  • Found: by its content alone a results file was named a *control file*, because it holds its control file inside (_drs_control_file) and that matched first. Its first line ("# The results of a refinement, written by diff-ref-struct …") now names it a result before that rule. The test app31_a_file_named_by_its_place, which asserted that by its content alone such a file was data, now asserts that it is a result.
core/src/formats/table.rs (cells, pairing_key, member_line, TableLayout::series, member, unread, read_with), core/src/plots.rs (Found::CsvLine, temperature_candidates, XQuantity, LambdaFrom, XMember, XView, x_view), core/src/formats/project.rs (describe_path, RESULTS), core/src/main.rs (drs plots view; drs data info without --in) · core/tests/plots_tables.rs · plots/REQUESTS.md PL8, PL10 · plots/mockups/out/tb2pd2sn_series.csv · plots/STUDY.md §10.3 · core/tests/open_requests.rs · app/REQUESTS.md 31, 32 · manuals/fullprof/1-WinPLOTR/FPS_dec2015_WinPLOTR_TR.pdf p. 43 · L123, L168, L195
L1982026-10-13work

App stream, 0.18.1: a plan step that frees nothing no longer stops every run; a Refine plot beside another tab acts on its own document; Save Project As opens its data

Three faults the tour found (TU10), each a fault for every user. (1) Scenes 3.2-3.6, no run started: the core frees in a plan step only the parameters listed as refined (Refinement::for_step) and makes a step that frees nothing an error of the whole file; the plan step "atoms" on a file with nothing free made that error, so Run, Run Next Plan Step and every run were refused ("Not run: 1 error"). Add Plan Step now lists the parameters the step names (addresses, scopes, prefixes; not "all" or classes) among the refined ones, leaving out what the symmetry fixes or the core's parameter set refuses; a step still freeing nothing is refused; a link takes its value out of the refined parameters (the core refuses both); any change that would leave a step of a valid plan freeing nothing is refused. Test a_plan_on_a_file_with_little_free_frees_what_its_steps_name. (2) Scene 2.3, the region not kept: the Refine plot's callbacks acted on the active document, so a drag on a Refine plot in a split group beside another active tab acted on nothing, the rejection unhandled and unseen. They now act on the plot's own document and say a refusal; a shown document is redrawn though not active; the tour drags on the active group's plot (it took the largest, the Data plot after 2.3's splits). Test plot-region.test.mjs. (3) Scene 6.1b: Save Project As marked the new project opened, so its data never opened and Start a Refinement was disabled; the new project now opens its data at its first opening. One self-check of the built app (one instance, own home, own PID): the plan scene (two steps, a link, Run Next Plan Step and Run converged, Fit) and the region scene (drawn after two splits, saved to the file) pass; the Save Project As scene was not reached (the check's script waited for a dialog the data window rightly does not open), so (3) is not yet confirmed in the built app. 84 node and 59 Rust tests pass; clippy clean. Not installed.

branch app 7f63644 · app/src-tauri/src/modules/refine.rs, project.rs · app/ui/tests/plot-region.test.mjs · L193
L1972026-10-13work

Plots stream, fourth step: the plot set file saved and opened, members after Data's operations, the core's proposed stack step, the temperatures file, open points below 2\(\sigma\); PL23, PL24 filed

Plots' fourth step, on core 0.33.0 and 0.34.0 (PL12, PL18–PL22; L192, L195). The set file (PL12): Save Plot Set (File ▸ Save, ⌘S), Save Plot Set As…, Open Plot Set…, a double-click on a .drv (the listing's dict_kind plot_set): Plots/{name}.drv through plots::SetFile, each member's file relative to the project's folder, its typed value with its source and its opacity, the order, the stack's rule, the y scale, the reference and the windows; opened again the same way. Data's operations (PL20): each member read after the operations Data keeps for its file (plots::open_buf_with, open_with, SetFile::open, through Data's own ops_load and Op::to_core) and saying so: "ops" in the sidebar, the tooltip, the inspector and the members' table; Reload Members. The stack opens with the core's proposed step (plots::proposed_step, PL19), its rule said under the plot; Set Displacement… overrides it. The difference map's row for a member without a temperature is the core's (PL18). Read Variable From… a temperatures file (plots::read_temperatures, variable_from_file, PL21): names given twice with two values attribute nothing and are said. The series draws a point the core flags below 2\(\sigma\) open (PL22). Filed: PL23 (measured: the set file keeps typed values only, so tb_t03.dat's 6.5 K from Tb.tem is back at its title's 6 K after Save and Open) and PL24 (a .drv known by its content). Shell: five handler lines in main.rs (PL13d). Tests: 59 Rust tests in release (the set file saved and opened with a typed value, Data's factor doubling tb_t20.dat and said, the proposed step, the temperatures file with a conflict and an unread line, the flag below 2\(\sigma\); the difference map's apart row), 80 node tests, clippy clean. One self-check of the built bundle (not installed), under L193: one instance, a home folder of its own, stopped by its own PID, none left; 38 lines OK, none FAIL; the pictures looked at. The app's version is left to the merger.

branch plots 5d9aaf5 · app/ui/modules/plots/ · app/src-tauri/src/modules/plots.rs, main.rs · plots/REQUESTS.md PL13d, PL23, PL24 · L192, L195
L1962026-10-13work

Explore stream, fourth step: the \(\chi^2\) map and every ellipse drawn from the core's E13 (Grid::map, Ellipse::outline), and the tour's ex-3d handle moved to the 3D area so that it exists as soon as the view is shown

origin/main (8caddd1, after ba96fb9) merged (the branch already at it). (1) E13, core 0.33.0 (L192): explore.rs builds the grid's map with lsq::Grid::map (the stand-in set map_of gone); explore_ellipse takes the centre and returns the core's ellipse with its outlines at 1\(\sigma\) and 2\(\sigma\) (Ellipse::outline, 91 points each), and explore_compare returns each run's ellipse of the pair with its outlines about its own end. The pair's view, the \(\chi^2\) map and the compare view draw those points; draw.js's ellipseOutline is gone and no sine or cosine is left in the module's drawing (a test keeps it so). Tests: the outlines equal the core's to the bit, the 2\(\sigma\) one twice the 1\(\sigma\); the grid's map assertions hold on Grid::map; the compare test checks each run's outlines. The five cases checked again against drs compare --json 0.33.0: every number equal on all six pairs. (2) L190: checked headless with the 3D view open. ex-look:a, b, c were there and visible; ex-3d was not: it sat on the three.js canvas, made only once the core's scene had come, so a tour that reached the view before the scene (or without one: L190's takes had no run) found nothing. The handle is now the 3D area's, from the view's markup; the orbit's drag starts anywhere in the area; every listener of the 3D view goes with it on dispose (an AbortController: the area outlives the view, and the window's listeners had piled up with each reopening). Headless, with a scene the module's test wrote (&scene=): one ex-3d handle, with or without a scene; a drag dispatched on it as the tour does it changes the picture; each look button too. (3) L193: one self-check of the built app (not installed), the only instance, a home folder of its own, stopped by its own PID: 63 lines, 27 OK, none FAIL; the structure drew the scenes of cycles 0–2 (92 atoms, WebGL 2); "explore: compare OK … 72 in common, ends 72 agree, the largest |difference| 0 \(\sigma\)". Josu's settings untouched. Shell lines touched, E10e: none (the module's files and its tests only). Checked: cargo test --release modules::explore 5 pass; node tests 81 pass (nophysics among them); the window bundles.

branch explore 4beb899 · explore/REQUESTS.md (E13, E10e) · app/src-tauri/src/modules/explore.rs · app/ui/modules/explore/ draw.js, map.js, compare.js, structure.js, index.js, README.md · app/ui/tests/explore_tour.test.mjs, explore_compare.test.mjs · L190, L192, L193
L1952026-10-13work

L170, step 5: normalisation as a view and the constant counting time of multi-scan files (PL6), a set as CSV and the writers in d and Q (PL7), FullProf's temperatures file (PL21), a window's value flagged below 2\(\sigma\) (PL22)

Core 0.34.0, test plots_views (5).

PL6, normalisation as a view.

  • plots::Normalisation and normalise(set, rule): each member's factor and its pattern scaled, \(\sigma\) alike, or why not. The rules are monitor, counting time, maximum, maximum within a range, the same area over a range (Josu, L169), a chosen peak's integral above its background (PL5's window_value), or a reference member's area over a range.
  • PlotSet::normalised gives the set as the view shows it, refused by name when a member cannot be normalised. No operation is written into a file's list, and the set itself is unchanged.
  • The factor is taken as exact: the uncertainty of the area or integral it divides by is left out.
  • \(\mathrm{Tb_{2}Pd_{2}Sn}\):
  • every member's area over 16–18° made 1000, to \(10^{-9}\);
  • to the 30 K member's area: its factor exactly 1;
  • by the 19.1° peak: refused for tb_t20 alone, whose integral is −8.9;
  • by monitor: refused, the files state none.
  • D20's monitors (71054, 70943) give y · to / monitor exactly.

PL6, the constant counting time of multi-scan files.

  • data_ops::constant_time(scans, time): each point's counts c (y, or y · t for a rate) and \(\sigma\) become c · time / t and \(\sigma\) · time / t, t its scan's counting time per point.
  • At the x of a point of an earlier scan, the earlier is kept. That is WinPLOTR's rule for overlapped scans ("only the first Y value is stored"; its news, manuals/fullprof/1-WinPLOTR/docs/WinPLOTR_news.pdf). The points are then sorted by x.
  • The readers now say when y is a rate: UXD's _CPS blocks and XRDML intensities in cps (data_ops::RATE in Info::other).
  • The time is given, not chosen by the core. WinPLOTR's own choice (Y · total time / total points / the scan's time, plots/REQUESTS.md PL6) comes from its note S12 (normalisation_uxd.pdf), which is not in the repository. The news and the deck (p. 42, UXD_NORMA, XRDML_NORMA) name the procedure without the time.
  • Tested on two-range UXDs, in counts and in cps (1 s and 4 s per point, overlapping on 10.5–10.9°), and on a two-scan XRDML, all by hand. A scan without a counting time is refused.
  • drs data constant-time <file> --time <s> --out <file.xys>, read back the same.

PL7, a set as CSV, and the writers in d and Q.

  • plots::csv (wide on the common grid) and csv_long (one row per member and point, on its own points). Each member's file, scan, format and value of the variable with its source are in # lines; the x column is named by its quantity (2theta_deg, d_angstrom, q_inverse_angstrom).
  • plots::converted(set, axis, λ) gives a set in d or Q, each member with its own λ or the one given, refused by name when neither gives one. \(\mathrm{Tb_{2}Pd_{2}Sn}\)'s files state none; the CSV in d uses G41's 2.426 Å from the demo, given.
  • formats::write writes xye in 2\(\theta\), d or Q, the quantity in its header line ("x = d (Angstrom), y, sigma"), which the core's reader takes back as the axis, and qye in Q. A d and a Q scan written and read back are the same axis and the same numbers.
  • xys stays 2\(\theta\) only: FullProf's Ins = 10 header knows INTER and TEMP and no word for d or Q (manual p. 136).
  • Two tests change with the rule: the writer's own (formats::write::tests) asserted that xye refuses d, and data_write wrote every 2\(\theta\) scan in every format written, now xys and xye (qye holds Q only).
  • drs plots export --csv [--long] [--x 2theta|d|q] [--wavelength λ].
  • Found on the way: the same page of the manual says "You can add comments to the data file if they start with the character ! in the first position of the line. These lines are ignored by the program". That answers what L175 (core 0.30.0) left not checked: FullProf skips the ! member lines of a derived .xys.

PL21, FullProf's temperatures file.

  • plots::read_temperatures: "name TSAMP value" per line (the keyword in any case); every other line kept in unread.
  • PlotSet::variable_from_file(name, file, replace_typed): each member whose file name with its extension coincides with a line takes its value, with the source Found::TemperatureFile(name), added to its candidates; a typed value is kept unless asked.
  • A conflict in FullProf's own example. Its news of 25 September 2013 (manuals/fullprof/news/FullProf_News_2013.htm) names data_23.dat twice, with 1.5 and 5.0, and does not say which FullProf takes. A name given twice with different values is listed in conflicts and takes none (never a guess, L169).
  • On D20: la0120.dat takes 70.5; la0121 without its extension and LA0122.DAT in capitals do not match, as "coincides (including the extension)" asks.
  • Every drs plots command takes --temperatures <file>.

PL22, a window's value below 2\(\sigma\). plots::WindowValue::significant and significance, with the threshold plots::WINDOW_SIGNIFICANCE = 2, the core's (as lsq::STRONG_CORRELATION). At 30 K the 19.1° and 32.6° windows are flagged, and the 26.7° and 42.2° windows, which hold nuclear peaks, are not.

Noticed, not done: D20's header gives ti (60.001), which the D20 reader does not take as a counting time. The manual's Ins = 3 code names it without a meaning (p. 133), so it stays unread.

core/src/plots.rs (Normalisation, normalise, PlotSet::normalised, read_temperatures, TemperatureFile, PlotSet::variable_from_file, Found::TemperatureFile, converted, csv, csv_long, WindowValue::significant, WINDOW_SIGNIFICANCE), core/src/data_ops.rs (constant_time, RATE), core/src/formats/write.rs (xye in d and Q, qye), core/src/formats/table.rs (the axis read back), core/src/formats/fullprof.rs and panalytical.rs (the rate marked), core/src/main.rs (drs plots normalise, export, --temperatures; drs data constant-time) · core/tests/plots_views.rs · plots/REQUESTS.md PL6, PL7, PL21, PL22 · manuals/fullprof/1-WinPLOTR/docs/WinPLOTR_news.pdf, manuals/fullprof/1-WinPLOTR/FPS_dec2015_WinPLOTR_TR.pdf p. 42 · manuals/fullprof/news/FullProf_News_2013.htm · manuals/fullprof/FullProf_Manual.pdf pp. 133, 136 · L145, L169, L175, L177, L183
L1942026-10-13work

App 0.18.0 built on core 0.33.0 with every stream merged, installed, and self-checked one instance at a time

The merging session merged core 0.33.0 (L192) after app 0.17.1 (L191), Explore's third step (L188) and Plots' third step (L189), and set the app's version to 0.18.0.

Suites on main:

  • the core: 244 pass, none fail; the two E11 tests of L187 pass on this Mac;
  • Publish: 21 pass;
  • the app: 58 Rust and 80 node tests pass.

Installed in /Applications, 0.17.0 to the Trash. Two self-checks, run one after the other under L193's rule (one instance at a time, a home folder of its own, stopped by its own PID):

  • on a copy of Y2O3: 27 OK, none FAIL. Explore's compare view sets the run against the version its Accept kept: 72 in common, every end agreeing;
  • on a copy of the \(\mathrm{Tb_{2}Pd_{2}Sn}\) buffer: 34 OK, none FAIL. The projection keeps tb_t01.dat apart, its 2\(\theta\) from 16 to 46° as measured.

No copy of the app ran after the checks.

app/src-tauri/target/release/bundle · DRS_APP_SELFTEST reports of 13 October 2026 · L188, L189, L191, L192, L193
L1932026-10-13decision

The tutorial stream paused by Josu: its takes open the app again and again, and the Mac's memory suffers

Josu: "for the moment, I will skip the Tutorial … once and again it opens the app, different instances, and the memory of the mac suffers".

Where it stands. The script plays 15 of 27 scenes in app 0.17.1 (L191). Still open:

  • in 3.2–3.6 no run starts;
  • 2.3's excluded region fails;
  • Start a Refinement… is disabled in the second window (6.1b);
  • Act 4 waits on Explore's handles.

TU10 holds the details. The tutorial stream gets no prompt until Josu takes it up again.

The rule kept for every stream (app, Explore, Plots and the merging session's own self-checks): one app instance at a time, run with a home folder of its own and stopped by its own PID before the next is started. No series of takes. A stream that needs the running app runs one self-check per step.

Set aside: running the takes in the cloud, which has no macOS and no WKWebView, and so no Tauri app (see the cloud's notes).

Josu, 13 October 2026, evening · tutorial/REQUESTS.md TU10 · L190, L191
L1922026-10-13work

The two E11 tests that failed on the Mac, explained and made one computation (L187); the plot set file (PL12), the map's rows apart (PL18), a proposed stack step (PL19), members after Data's operations (PL20); Explore's grid as a map and the ellipse's outline (E13a, E13b)

Core 0.33.0, tests plots_sets (7), paths_compared and plots changed.

The two E11 tests (L187), not widened (L037).

  • swapped. B against A must negate A against B exactly. Every difference did; only combined did not: −0.0003148862439571404 against −0.00031488624395714047 on the Mac. Its numerator negates exactly, so the denominator differed: f64::hypot(σA, σB) against hypot(σB, σA).
  • Rust's hypot is the C library's. glibc's orders its arguments; macOS's gave one bit more for one order. That is inferred from the numbers above, not read in Apple's source.
  • Made one computation: √(σA\(^{2}\) + σB\(^{2}\)) as two products, a sum and a root. Each is correctly rounded (IEEE 754) and the sum is commutative, so it gives the same bits in either order on every platform.
  • e11bc_the_results_file. A run read back from its results file has its start rebuilt from its end by taking each cycle's shift back (Trajectory::from_end). Floating-point subtraction does not undo the additions exactly.
  • Measured here: 28 of A's 72 starts and 15 of C's 71 rebuilt this way differ in some bit from the session's own, on Linux too. The zero shift was by chance not among them here, and was on the Mac (0.05000000000000001).
  • The test now reads the sessions back the same way (from_end on their ends and cycles), one computation on both sides, and says why. The results file holds the end's floats and the cycles' numbers to the bit, so both sides agree exactly on every platform.
  • from_end's documentation states the start's last-bit freedom and where it comes from: the run's numbers, and those come from the platform's libm.

PL12, the plot set file.

  • plots::SetFile reads and writes Plots/{set}.drv in the control file's TOML subset (format = "drs plot set 1", plots/DESIGN.md §2.2):
  • the set's title, the variable's name and unit, the x quantity, the y scale, the order (variable, name, given), the displacement rule, the reference member and the windows;
  • per [[member]]: the file, a scan, a typed value of the variable (its source required: never a value without one, L169), visible, opacity and colour.
  • What is not written is the core's default. Written and read back, it is the same file.
  • What is wrong is said with its place: a missing format, an unknown key, a typed value without its source, an opacity outside 0–1, a table that is not [[member]].
  • SetFile::open opens the members relative to the project's folder: the folder above Plots/ (set_base), or --base. Each is opened after Data's operations (PL20). A set in another x than 2\(\theta\) is refused until PL10. SetFile::of writes a set's file.
  • On the command line:
  • every drs plots command opens a .drv as it opens a .buf, and map, windows, displace and project take the file's reference, windows, displacement and scale unless given;
  • drs plots save writes the file (Plots/{set}.drv by default, never over a file, the files renamed from the new file's project folder);
  • drs plots export --csv writes the set as CSV, wide in 2\(\theta\) (PL7's first form): each column's file and value of the variable with its source in # lines, every value the member's own.
  • The file dictionary's kind plot_set (proposed; the extension is Josu's to confirm).

PL18. plots::Map::apart_z: the rows of the members without a value of the variable, on the grid and the scale, less the reference in a difference map.

  • The colour range now covers every value drawn, these rows included.
  • \(\mathrm{Tb_{2}Pd_{2}Sn}\): BT's largest count, 68658, is above the 19 other rows' 68455, so the map's range is 905 to 68658 and the difference map's \(\pm\)66204 (L175 gave 68455 and \(\pm\)66001). The plots test changes with it.

PL19. plots::proposed_step(set, scale).

  • The rule: the median, over the members, of each member's 90th less its 10th percentile on the scale shown (nearest rank), rounded up to 1, 2 or \(5 \times 10\)^k. The rule is returned as a sentence with each member's spread.
  • A member's bulk (its background and the feet of its peaks) stays clear of the next one's, while its strong peaks may reach above it, as the mock-up F1 shows; WinPLOTR's guide gives no rule.
  • \(\mathrm{Tb_{2}Pd_{2}Sn}\): median spread 1101.5 counts, step 2000 (F1 used 3000, F2 2200); on √, 20.
  • drs plots step.

PL20. plots::open_with and open_buf_with take the operations Data keeps for each file (Operations, the caller's: the app keeps them in its own state) and apply them as data_ops::apply_all does.

  • A file another operation names is read relative to the set's folder.
  • The member's facts are the scan's after the operations; its header's (format, λ, temperature) are as read.
  • Tested: tb_t02 with 16–18° deleted and normalised to its maximum is apply_all's, 280 points from 18.1°. Less tb_t20 it is the difference, point by point.
  • Member, Facts and PlotSet gain no field: the app builds them by struct literal (explore.rs, map_of).

E13a. lsq::Grid::map(scale): columns at a's values, rows at b's, the edges as plots::map makes them, \(\chi^2\) on the scale (None where not computed, or \(\le 0\) on log), the range over the points computed.

  • It goes through the rows plots::map itself takes (map_of_rows), so the app's stand-in set is no longer needed.
  • Equal to the stand-in's answer (map_of, copied into the test) to the bit:
  • on a \(5 \times 5\) grid with a point not computed and one at 0, on the three scales;
  • on \(\mathrm{Y_{2}O_{3}}\)'s \(5 \times 5\) \(\chi^2\) grid of V and W (1.19875 to 57.631).

E13b. lsq::Ellipse::outline(centre, k, n): n + 1 points, the last the first, by the window's parametrisation (draw.js, ellipseOutline). Each point lies on the kσ contour (x − c)ᵀC\(^{-1}\)(x − c) = k\(^{2}\) to \(10^{-9}\).

core/src/lsq.rs (compare's combined \(\sigma\), Trajectory::from_end, Grid::map, Ellipse::outline), core/src/plots.rs (map, map_of_rows, Map::apart_z, proposed_step, open_with, open_buf_with, SetFile, set_base, relative_to, csv), core/src/main.rs (drs plots save, export, step; .drv opened), ecosystem/data/files.yml (plot_set) · core/tests/paths_compared.rs, core/tests/plots_sets.rs, core/tests/plots.rs (pl4) · plots/REQUESTS.md PL12, PL18–PL20, plots/DESIGN.md §2.2 · explore/REQUESTS.md E13a, E13b · app/src-tauri/src/modules/explore.rs (map_of), app/ui/modules/explore/draw.js (ellipseOutline) · L169, L175, L184, L187
L1912026-10-13work

App stream, 0.17.1: TU10's items; the tour plays on its own settings; Save Project As keeps referenced data; request 34 used

TU10's items from the tutorial stream. The s.x.length throw in Refine was the excluded regions' bands in plot.js's xRange since app 0.9.0; main's fix (Plots step 3) is kept and plot-bands.test.mjs guards it. Run Next Plan Step is enabled when the plan has a step to run (the core still frees only listed parameters, L155); the plan's free list takes commas or spaces. Move Tab to New Window refuses an Explore tab with the reason; Reset Layout keeps the open tabs. A DRS_APP_TOUR launch keeps its settings in a fresh folder of its own; Help > Tour plays on default settings and layout. The player reads a run step's answer: in any window, the zone action, problems:fix:N as the N-th problem's Fix button (tagged in the Editor), returns to its window after a window: new scene, and writes <report>.timings.txt. Save Project As with the data referenced lost the reference at the next layout save (project.rs state_save now keeps external_data). Request 34 met by core 0.32.0: Convert to Another Wavelength on a scan without a wavelength offers the wavelength of a control file. Take 17 (isolated home, script from main 3907e6c): 15 of 27 scenes, 116 of 207 commands. Open: no run starts in 3.2-3.6 (not traced), 2.3's excluded region, Explore handles in Act 4, 6.1b's data.start still disabled. 80 node and 58 Rust tests pass; clippy clean. Not installed.

branch app a035626 · app/ui/plot.js, tour.js, app/ui/tests/plot-bands.test.mjs · app/src-tauri/src/project.rs · tutorial/REQUESTS.md TU10 · L189, L190
L1902026-10-13work

Tutorial stream, fourth step: the tour played on app 0.17.0 with a home of its own, 14 of 27 scenes; Acts 3–5 wait on Refine's throw

the tutorial stream's fourth step, on app 0.17.0 (TU8's Explore handles, TU9's tour following into a new window). Every take ran with HOME set to a folder of the stream's own, so Josu's settings and recent projects were neither read nor changed, and only the take's own process was stopped. Four takes (five to eight): the first died in 3.2 without a report; then 9, 11 and 14 of 27 scenes (Acts 0–2 whole, 6.1, 6.3, 6.4), 98 of 203 commands run, with the two fixes of the step before confirmed. Script changes: the plan steps' 'free' in the dialog's comma form (the script's spaces freed nothing, so no run could start before take seven); a second's wait between two prompts in a row (0.16.1 keeps the dialog open); 2.1 keeps the version and names the background file before the slip, and types the fix back until the Fix button has a handle; 2.3 waits for the Editor to re-read; 6.1 moves the tab last, since the player follows into the new window; the checker skips series ids and counts the Plots module's 24 commands (201 in the registry, 137 run by the script, 68 %). What still fails, in TU10: for the app, Refine's plot throws on 's.x.length' once a plan's steps free parameters, so no run starts and Acts 3–5 wait; the Problems panel's Fix button has no handle; after a 'window: new' scene the player stays in the new window and the file dialogs there are not answered from the step's 'answer:'; for the Explore stream, ex-3d and ex-look are still not found.

At the merge (the merging session): the throw on s.x.length is in app 0.17.0, which predates Plots' fix of plot.js's xRange for a series with no x (L189, merged after the build). Whether that fix ends it is for the app stream, which is on TU10. The tour's ex-3d and ex-look handles came with Explore's L185 and are in 0.17.0, so "not found" is to be checked with the 3D view open; Act 3 did not run.

branch tutorial a7e2eac · tutorial/script.yml, tools/check_script.py · tutorial/REQUESTS.md TU10 · L185, L187, L189
L1892026-10-13work

Plots stream, third step: the projection keeps a member without a temperature apart, the linked overlay (F6), the order and the variable's source; PL21, PL22 filed

Plots' third step, on core 0.32.0, after Josu's look at the exported pictures (L183). Projection: a member without a temperature stands apart: the core's own step in y, no shift in 2\(\theta\) (plots::displace's override), so tb_t01.dat's peaks stand where they were measured, off the oblique axis and labelled by its name; with BT typed the module's projection equals drs plots project --set 20=1.5 field by field. Overlay, F6: Linked Zoom, the whole range on the left and a window on the right; a drag on the left chooses it, a zoom or pan on the right moves its band, the pointer draws a cursor on both; the core's difference of two members under both (plots::derive, the first chosen or the set's first less the reference). Reverse Order (a reordering), Sort by Variable (PlotSet::sort_by_variable), Read Variable From… the file's name or the file as opened (the sources the core found, typed values kept); a temperatures file waits for PL21 (FullProf's "data_23.dat TSAMP 1.5" lines, news of 25 September 2013). Series: a point below 2\(\sigma\) is drawn open where the core flags it (PL22, filed: the threshold is the core's, as L145 made the correlation filter); until then every point is filled and the view says so. Found by the self-check and fixed in the shell: plot.js's xRange threw on a band series (ranges, no x), met by the overlay with windows; Refine's excluded regions take the same path. plot.js additive (open points, setCursor, onPointer, bands skipped in xRange); every shell line in plots/REQUESTS.md PL13c. Tests: 56 Rust tests in release (the projection apart and against the command line; the difference, reverse, sort and the variable read from a file named tb_5K.dat), 77 node tests, clippy clean. Self-check on the built bundle (not installed): 35 lines OK, none FAIL; the pictures looked at. The app's version is left to the merger.

At the merge (the merging session): the fix of plot.js's xRange for a series with no x is probably also the Refine view's throw on s.x.length after a plan step (TU10's first item), which the app stream was tracing; the app stream was told to merge main and keep one fix.

branch plots 6c997d8 · app/ui/modules/plots/ · app/ui/plot.js · plots/REQUESTS.md PL13c, PL21, PL22 · L183, L187
L1882026-10-13work

Explore stream, third step: two refinements' paths compared in the app (E12 over the core's E11), checked against drs compare on the five recorded cases and in the built app; TU8's selects

origin/main (4865802) merged (the branch already at it). (1) E12 in the module's Rust side (explore.rs): explore_compare(id, other: {doc}|{path}, reference, pair) returns the core's PathsCompared and PairCompared (lsq::compare, PathsCompared::pair) with the two files' names and ENDS_AGREE. Each side is a document's record with its engine result (pending, or accepted in the session), or a version (Versions/…drc) opened for the comparison only, its record and end read back from its results file: each free parameter's esd, the correlations, the stop (E11b–c), as drs compare reads them. (2) COMPARE_READY on: the view asks for the version chosen in the sidebar, keeps the answer by the run, the version, the reference and the pair, drops a stale one, and draws only the core's numbers (the ellipses' outline through draw.js's one helper); the note gives the counts, the stops and the last \(\chi^2\)'s. The module's test the_five_cases_compared refines the five cases as the app does and compares them as the window asks: A–A no difference; A–B 72 common, all agree, B held from step 8; A–C 71 common, the zero shift only in A, 10 ends apart, a by −83.5 combined \(\sigma\); A–D equal for D's 4 cycles; A–E stopped diverging-jump; B–A the negation; r(V, W) −0.871; and a version read back from its results file compares as the document did. explore/compare/check_compare.py compares those answers with drs compare --json (core 0.32.0) on all six pairs: every number equal (15 087–16 407 numbers per pair, none different even in the last bit on this Mac); the check is to a relative 1e-9 all the same (L187), nothing in the window asks for equality. prototype.py retired. (3) TU8: data-tour="select:<name>" on every select and slider of Explore (span, cycle, ref, a, b, speed, phase, hkl, sphase, arrows, t). (4) A self-check entry (explore.selftestCompare, one line in app.js after Accept): the run against the version its Accept kept, the two routes to one run. Built app (not installed), self-check with a home folder of its own on a copy of Y2O3: 61 lines, 27 OK, none FAIL; "explore: compare OK: … 72 in common, ends 72 agree, the largest |difference| 0 \(\sigma\), stops converged and converged, r −0.871 and −0.871, ellipses A and B, drawn"; only its own process stopped; Josu's settings untouched. Shell lines touched, E10d: main.rs (1 line, the handler), refine.rs (version_results_path pub(crate)), app.js (1 line). Checked: cargo test --release modules::explore 5 pass; node tests 78 pass (nophysics among them); the window bundles; the compare view screenshotted headless on the recorded A–B and A–C answers.

branch explore fccc231 · explore/REQUESTS.md (E11, E12, E10d) · app/src-tauri/src/modules/explore.rs · app/ui/modules/explore/ index.js, compare.js, README.md · explore/compare/check_compare.py · app/ui/tests/explore_compare.test.mjs, explore_tour.test.mjs · tutorial/REQUESTS.md TU8 · L184, L187
L1872026-10-13finding

App 0.17.0 built with every stream and installed; two of core 0.32.0's E11 tests fail on this Mac by the last bit

The merging session merged all six streams (L181–L186, core 0.32.0 as L184) and built app 0.17.0.

The build failed once. Core 0.32.0 gave Operation::Wavelength a source λ (from, app request 34), and the app's Data bridge still made it without one (app/src-tauri/src/modules/data.rs:107). The merging session passed from: None, which keeps the app as it was: the command is refused for a scan without a wavelength. Using the source λ is the app's.

Suites on the merged main:

  • Publish: 21 pass.
  • The app: 55 Rust tests and 77 node tests pass.
  • The core: every test passes but two, both new in core/tests/paths_compared.rs (E11):
  • swapped wants A against B to be B against A negated exactly; here one difference is −0.0003148862439571404 against −0.00031488624395714047;
  • e11bc_the_results_file reads the fixed zero shift back from the results file as 0.05, against the session's 0.05000000000000001.
  • Both pass in the cloud (Linux) and fail here (macOS, Intel). They ask equality to the bit of numbers computed by two paths. Not widened (L037): the core is to explain the difference and either make the two paths one or state the platform's last-bit freedom in the test, with the reason.

Installed. App 0.17.0 in /Applications, 0.16.0 to the Trash. The self-check on the installed copy, with a home folder of its own:

  • on a copy of Y2O3: 26 OK, none FAIL, Explore's replay among them;
  • on a copy of the \(\mathrm{Tb_{2}Pd_{2}Sn}\) buffer: 33 OK, none FAIL.

E11 is not yet used by the app: Explore's compare view stays off until its next step.

core/tests/paths_compared.rs:162, :224 · app/src-tauri/src/modules/data.rs:107 · core/src/data_ops.rs:77 · L184, L186
L1862026-10-13work

Tutorial stream, third step: TU7 answered; the script played on the installed app, 12 of 27 scenes; TU8–TU10 filed

the tutorial stream's third step, on TU7 (the app stream's findings on script.yml). The script, SCRIPT.md and the checkers are revised against the player as built (app 0.15.0): the app's names (atoms by label, the plan's 'free', the link's 'address', dlg-name, dlg-pick, dlg-bgpoint, dlg-excluded, dlg-start), the checks as the tourexpr.js subset over the API tour.js implements, the targets listed, answer: on the steps that open system dialogs, window: new on the scene that continues in another window (6.1b), help.here, explore.play and explore.view.compare run, help.tour and the Plots module's twenty commands listed as not run: 197 commands, 137 run (70 %), 60 listed. The data tool writes the wavelength into the simulated files' header, so the peaks have their d and the wavelength conversion works without a new handle. Played whole on the installed app 0.16.0 on the scratchpad's data (audit, fast), four takes and a probe script: 8, 10, 5 (Reset Layout closed the tabs), then 12 of 27 scenes pass (0.1–1.6, 2.2, 2.3, 6.1, 6.3, 6.4), 104 of 197 commands run. Two last fixes (two prompts in a row, the snapshot before the typed lines) are in the script and not yet played. What blocks the rest, for the app stream in TU10: the Refine view throws on 's.x.length' after a plan is added, so no run starts (3.2–5.2 wait on it); the inspector's sections collapsed by this Mac's settings hide every insp: row (a take should start from the defaults); Run Next Plan Step needs a refinable parameter first; the notes file is named 'y2o3 n'; Move Tab to New Window fails for an Explore tab; Reset Layout closes the tabs. For the Explore stream, TU8: the handles trace:, ex-3d, ex-look. For the app, TU9: the hand-over of a scene to the window it opens.

At the merge (the merging session):

  • one conflict, in tutorial/REQUESTS.md, where app 0.16.1's addendum to TU7 and the tutorial's answer met; both kept;
  • the takes were played on 0.16.0, before app 0.16.1, which already follows the tour into a new window (TU9, L182) and fixed 1.7's lost operations. TU8's handles came with Explore's step (L185);
  • the takes ran with this Mac's own settings (the inspector's sections collapsed), as the app stream's did before L182. A take runs with a home folder of its own and stops only its own process;
  • TU10's first item, the Refine view throwing on s.x.length once a plan is added, is in plot.js. The Plots module's first step (de4749f) last changed the lines that read it, so it is to be checked as a regression of app 0.16.0.
branch tutorial 57c9a6a · tutorial/script.yml, SCRIPT.md, tools/check_script.py, tools/make_data.py · tutorial/REQUESTS.md TU7–TU10 · L178, L182, L185
L1852026-10-13work

Explore stream: the tour's handles for Explore (TU7), and the \(\chi^2\) map moved onto the shared plot's image layer (PL13), every number of it the core's

origin/main (795d700) merged. (1) TU7: data-tour handles as TU5 gave the rest of the app — trace:<address> on each trace (an invisible 10 px handle at the trace's first point; a click on it selects the parameter, a user's click goes through to the canvas), ex-3d on the 3D view's canvas (a drag there orbits), ex-look:a|b|c on the look buttons; tour.explore() is now the module's tour(tab) (one line in tour.js), giving what scenes 4.1–4.3 check: cycles, grids ({ n, computed } per grid of the last Compute), the pair's ellipse with r, the scene as counts, the spectrum as a list. Checked headless: 10 handles on the Y2O3 run (background left out), a click on trace:phase1.cell_a selects it; the three look handles present. (2) The \(\chi^2\) map (map.js) is a set of series on the shared plot (createPlot, PL13's image layer): cells, colour bar, axes, zoom and the readout over a cell are the component's; the module's own viridis, colour bar, cell placement and axes are gone. The cells' edges, log10 \(\chi^2\) and the colour range come from the core's plots::map through the grid's new map (explore.rs map_of, on a stand-in set of one member per value of b; E13a asks lsq::Grid::map so that the stand-in goes; E13b asks the ellipse's outline points, drawn by one helper in draw.js since this step). The path, the marked cycle and the 1\(\sigma\)/2\(\sigma\) ellipses are path series, a flag added to plot.js (x in any order, clipped to the frame); a log colour bar narrower than one 1-2-5 step is labelled by the linear rule (\(\chi^2\)ν 2.90–2.99 had no label). The map opens on the grid's extent; a double-click shows the whole path; Export takes the component's SVG. One change seen: the component's viridis puts the minimum dark where the module's had it yellow. (3) Shell lines touched, E10c: tour.js (1 line), plot.js (path flag and the bar's fallback, additive), a new test explore_tour.test.mjs. Checked: cargo test --release modules::explore 4 pass (the grid's map checked against the grid: edges, log10 \(\chi^2\), range = chi2_range's on log10, uncomputed cells blank); node tests 73 pass (nophysics among them); the window bundles; the map and the pair screenshotted headless on a grid the module's test printed. Not installed; COMPARE_READY stays off until E11.

At the merge (the merging session): one conflict, in plot.js's comment, where Plots' second step and Explore each documented their additions; both kept. Core 0.32.0 (L184) has met E11, so the compare view can be switched on in Explore's next step.

branch explore 5f71dda · explore/REQUESTS.md (E10c, E13) · app/ui/modules/explore/ map.js, index.js, draw.js, structure.js, README.md · app/src-tauri/src/modules/explore.rs · app/ui/plot.js · app/ui/tour.js · app/ui/tests/explore_tour.test.mjs · tutorial/REQUESTS.md TU5, TU7 · tutorial/script.yml 4.1–4.3 · plots PL13 (app 0.16.0)
L1842026-10-13work

Explore's E11, E11a–c (two refinements' paths compared, the pair's ellipses, the correlations and the stop kept) and the app's request 34 (the wavelength operation from a given λ)

Core 0.32.0, tests paths_compared (9) and data_operations (app 34).

E11, two paths compared.

  • lsq::compare(a, b, reference) -> PathsCompared. Each Run is a Trajectory, its end esd's and correlations (by its own free parameters) and its stop. The reference is Side::A or Side::B.
  • It returns, by parameter address:
  • the parameters common to both runs and the parameters free in one run only (start and end values);
  • the values free in neither run whose starts differ (Model::all_fields; every field any free parameter moves is left out);
  • each common parameter's unit from the dictionary, and per state \(\chi^2\), values and esd's;
  • both paths on the reference's scale (its end the origin, its end esd's the unit);
  • the steps of the longer run, the shorter run held at its end;
  • (B − A)/scale at each step;
  • the ends compared in units, in the reference's \(\sigma\) and in the combined \(\sigma\) √(σA\(^{2}\) + σB\(^{2}\)). They agree within ENDS_AGREE = 3, the usual test of two independent estimates of one value;
  • a summary, with each run's last \(\chi^2\) and stop.
  • E11a. PathsCompared::pair(a, b) gives each run's path in the parameters' units, its end, its two \(\sigma\)'s, the correlation, and the ellipse from covariance_ellipse.
  • The five runs of explore/compare/cases/, each refined by a session in the test. Every number of the README's table is reproduced: 10, 7, 8, 4 and 5 cycles; \(\chi^2\) \(82.781 \to 1.1985\) … \(105.123 \to 2.0011\); a = 10.60497 Å and 10.60104 Å.
  • A vs A: every difference exactly 0, and the scaled path equals in_esd().from_end to the bit.
  • A vs B: 72 in common, 11 steps with B held from step 8, all 72 ends agree. B − A is 1.31·\(10^{-7}\) Å for a (0.0036 \(\sigma\)) and 1.53·\(10^{-6}\)° for 2\(\theta\)₀.
  • A vs C: 71 in common, only pattern1.zero_shift (0.05 at A's start) apart, no fixed value changed. Ten ends disagree, a by −83.5 combined \(\sigma\), with the scale and W also apart.
  • A vs D: D's 5 states are A's first 5 to the bit; the difference is 0 to step 4 and not after.
  • Swap: B against A with B as the reference gives the difference and the combined \(\sigma\) negated exactly.
  • A vs E: E stops with diverging-jump (a would move 102 esd's in cycle 5); 55 ends agree, 17 do not.
  • V and W: r = −0.871 in A, −0.887 in C.

E11b, E11c, the results file.

  • The overall block keeps _drs_stop_code and _drs_stop_text, in place of _drs_stop.
  • It keeps the free parameters at the end with value and esd (_drs_free_name, _value, _esd) and their correlations, the upper triangle (_drs_correlation_a, _b, _r): 2556 rows for \(\mathrm{Y_{2}O_{3}}\).
  • Read back into Results::stop_code, stop_text, free and correlations (correlation_matrix), to the bit.
  • An earlier file reads as before, its _drs_stop as the text.
  • Found: a field's su is not always the esd of the parameter named after it. \(\mathrm{Y_{2}O_{3}}\)'s second asymmetry parameter also moves P1 (\(\times 3\)), so the field P1's su is 0.00499 and the parameter's esd 0.00247. A comparison needs the parameters' own esd's, so the file now keeps them beside the fields' su's (E12 had named Results::refined for them).
  • A comparison of two results files read back (Trajectory::from_end) equals the two sessions' comparison: the same ends, combined \(\sigma\)'s, end esd's and correlations.

drs compare <a> <b> [--reference a|b] [--pair x y] [--json].

  • Each of a and b is a results file or a control file refined first.
  • It prints the parameters in common and apart, the fixed values changed, both stops, and the ends sorted by |combined \(\sigma\)|. --json writes PathsCompared.

App 34.

  • Operation::Wavelength { to, from: Option<f64> }: the scan's own λ, or from when the scan states none (the λ the user takes from a control file). A from that disagrees with the scan's own is refused rather than preferred.
  • The control file has the key from, and the operations' dictionary describes it.
  • Tested on the \(\mathrm{Y_{2}O_{3}}\) tutorial's data, which states no λ: from 1.5406 Å it is the same as the scan given that λ.
  • The app's own construction of the operation (app/src-tauri/src/modules/data.rs:107) needs from: None, which is the app stream's to add.
core/src/lsq.rs (ENDS_AGREE, Side, Run, EndDifference, CompareSummary, PathsCompared, PairCompared, compare, pair), core/src/results.rs (correlations_block, Results::stop_code, stop_text, free, correlations, correlation_matrix), core/src/data_ops.rs (Operation::Wavelength), core/src/main.rs (drs compare), ecosystem/data/operations.yml · core/tests/paths_compared.rs, core/tests/data_operations.rs (app34_wavelength_from_a_given_source) · explore/REQUESTS.md E11, E11a–c, E12 · explore/compare/cases/ (README, y2o3_a/b/c/e.drc) · app/REQUESTS.md 34 · L163, L180
L1832026-10-13work

Plots stream, second step: the projection with hidden parts, the series of windows, FullProf's .seq, on core 0.31.0; released as app 0.17.0

Plots' second step (DESIGN.md §10), on core 0.31.0 (PL5, PL11, PL17). Projection (F2): the members displaced (plots::displace) and the hidden parts worked out by the core (plots::project); the window draws the visible pieces and the oblique axis with each member's temperature (a member without one labelled by name), its own steps from Set Displacement… in the projection. Series (F5): Add Window…, Remove the Windows, Background at…; for every member and window the core's integral above the background with \(\sigma\), centroid with \(\sigma\) and strongest peak (plots::windows), drawn against the temperature with \(\sigma\) bars and a legend of the windows, the background automatic or the line between the window's ends; the windows as bands in the overlay and the stack. FullProf's .seq (PL11): Open Sequence (.seq)… or a double-click opens it in a Plots tab (seq::read, Seq::column): every refined parameter, \(\chi^2\) and each phase's R_Bragg, volume and weight fraction against PARVAL's external parameter or the file order, refinements read twice and NUMPAR disagreements said. Fixed: the stack's y axis named one file ("counts (tb_t02.dat)"); it reads "counts + each member's step". Found and fixed in the built app's pictures: a hidden legend leaked every member into Export ▸ Plot's figure (legend: false), a linear axis forced to zero flattened a cell parameter (opts.yFromZero), \(\sigma\) bars needed points drawn, the .seq tab had no figure. The manual's sections and ? topics for the projection, the series and the sequence. plot.js additive (pointLabels, legend: false, opts.yFromZero); every shell line in plots/REQUESTS.md PL13b. Tests: the projection, the windows and the .seq through the module against drs plots project, windows and seq --json, field by field; 55 Rust tests pass in release, 70 node tests, clippy clean. Self- check on the built bundle (not installed), the Tb2Pd2Sn buffer and LaMnO3's la0.seq in a copied project: 34 lines OK, none FAIL; m1's integral 21929 \(\pm\) 48 at 4 K and 7 \(\pm\) 17 at 30 K; Cell_A 5.5422 to 5.5403 over 71 refinements, its minimum near 150 as WinPLOTR's deck p. 22 shows. The app's version is left to the merger.

At the merge the merging session set the app's version to 0.17.0, the release with app 0.16.1's fixes and this step. The self-check on the built 0.17.0 ran with a home folder of its own and stopped only its own process (the fault L182 found in the app stream's launcher was in the merging session's runs too):

  • on a copy of Y2O3: 26 OK, none FAIL;
  • on a copy of the \(\mathrm{Tb_{2}Pd_{2}Sn}\) buffer: 33 OK, none FAIL. The projection drew 20 curves, 19 partly hidden, in 131 visible pieces. The series drew 20 members \(\times 4\) windows, m1 21929 \(\pm\) 48 at 4 K and 7 \(\pm\) 17 at 30 K.

Seen in the exported pictures, for the Plots stream:

  • the projection moves tb_t01.dat (no temperature) by the full Δx like a member behind 30 K, so its peaks stand 4° from where they are measured. A member without a value belongs apart, as the map and the stack draw it;
  • the series draws every point alike, where the design (F5) draws a point below 2\(\sigma\) open.

Not yet installed: Josu had 0.16.0 open.

branch plots 365067d · app/ui/modules/plots/ · app/src-tauri/src/modules/plots.rs · app/manual/plots.html · plots/REQUESTS.md PL13b · L177, L180
L1822026-10-13work

App stream, 0.16.1: two questions in a row kept again (Smooth, Mask a Range, Delete a Range); Revert leaves the file saved; the tour follows into a new window

App 0.16.1 on branch app, the step after L178. The tour's open faults traced. Scene 1.7 was the app's: Smooth, Mask a Range and Delete a Range ask two questions in a row through one dialog, and the dialog's close event, which arrives a moment after close(), cancelled the second question, so these commands never kept an operation, for anyone; a close while the dialog is open again is now ignored in askText, pickOne and askForm (test dialogs- sequence.test.mjs fails on the old code). Scene 2.1 was the app's and the script's: after Revert To > Last Saved the Editor still marked the file unsaved (the reload's text counted as an edit), so Snapshot asked to save and its note dialog never came, fixed; the script deletes a digit that leaves a valid value, and problems:fix:1 names a problem without a mechanical fix (the player now reads problems:fix:N as the N-th Fix button shown). Scene 2.2 was the script's: atoms go by label, and with insp:phase1.O1.atom_biso it passes; a target not found now lists the handles of its kind the window shows. Scene 6.1: the player follows the tour into the window in front (the focused one, else the one the app last opened or brought forward: windows::front, tour_front), which plays the step with its own player and answers through a mailbox on the Rust side (tour_send, tour_take; Tauri's per-window events did not reach the listener); the checks are read in the window that started the tour; a window the tour opens runs no self-check. The clipboard's types for the checks come from the pasteboard through the Rust side (tour_clipboard_types). TU7 in tutorial/REQUESTS.md gains an addendum (1.7's last step, 2.1's typo and Fix, 2.2's labels, 6.1's data.add file dialog answered only in a note). Takes in the built app (audit mode, an isolated home folder; the earlier takes' launcher also quit every running copy of the app and shared the user's settings, both corrected): tutorial/script.yml as it is passes 3 of 26 scenes and runs 50 of the 197 registry commands (the tour now follows into the second scan's window in 6.1 and the script breaks there, so 6.3 and 6.4 run in that window); a scratch copy with the script's mismatches corrected passes 8 of 26 and runs 73. TU7's corrections are not merged, so the whole tour in the installed app (item 3) waits for them. 74 node tests (nophysics included) and 54 Rust tests pass, clippy clean. Release entry in app/README.md. Not installed.

branch app 8e1b45a · app/ui/core.js, app/ui/tour.js, app/ui/tests/dialogs-sequence.test.mjs · tutorial/REQUESTS.md TU7 addendum · L178
L1812026-10-13work

Publish stream, drs-publish 0.4.0: P19 and P20 used (F6 for a phase refined by coordinates, T2's B with its su); 0.3.0's workarounds removed

Publish stream, drs-publish 0.4.0 on core 0.31.0: P19 and P20 used (geometry::decompose and Scene::reference_fract for F6, _drs_atom_site_B_iso_or_equiv for T2, U_eq/B_eq of anisotropic atoms); 0.3.0's workarounds removed; case A refined by coordinates drawn by its modes (residual 0); example results rewritten by the core, refinements unchanged.

branch publish fe65373 · publish/ · L171
L1802026-10-13work

Plots stream, first step: the module on core 0.30.0 (overlay, stack, map, difference map), its guard and the plot component's additions; released as app 0.16.0

The Plots module (R32; DESIGN.md §10's first step after Josu accepted the design, L169), on core 0.30.0's plots:: (PL1-PL4, PL9). The window app/ui/modules/plots/ (the fifth module of the activity bar, its menu, ⌘K, the manual page app/manual/plots.html with its ? topics) and the bridge app/src-tauri/src/modules/plots.rs, which keeps the sets by id and calls plots::open_buf, open, set_variable, displace, common_grid, map, on_grid, derive and derived_text only. Views: Overlay (dimming, Solo Member, Dim Others, Restore All, an opacity bar per member), Stack (the core's displacements, Set Displacement…, labels at the baselines), Map and Difference Map (the core's rows, edges, values and colour range; log for the map, signed √ for differences). Sum, Average and Difference of Members write Plots/{set} {operation}.xys through plots::derive, never overwriting. Josu's rules (L169): a file without a temperature is drawn apart until one is typed with its source (Set Temperature…, a note required); with no monitor every view says "no monitor: raw counts". The set lives in memory; Save Plot Set is off until the core's file (PL12). PL14 met: the no-physics guard reads every file of modules/plots/ and the bridges plots.rs and data.rs, checked by planting a computation of each kind. PL13 in plot.js, additive (opacity, scaled series with their values, labels at baselines thinned by priority, an image layer with a colour bar and rows apart, the readout's nearest curve); every shell line touched is listed in plots/REQUESTS.md PL13a for the merger. Requests filed: PL18 (the difference map's row for a member without a temperature), PL19 (a proposed stack step), PL20 (members after Data's operations). Tests: the Tb2Pd2Sn buffer through the module against drs plots open and map --json (members, temperatures, monitors; the log map and the difference map field by field), the stack, the derived file; 54 Rust tests pass in release (50 + 4), 58 node tests, clippy clean on the bridge. The self-check, with the buffer given at startup, opens the set and each view in the built bundle (not installed) and writes each view by Export ▸ Plot's path into the project's Exports/: 31 lines OK, none FAIL. The app's version is left to the merger.

At the merge (the merging session): one conflict, in app/ui/tests/manual.test.mjs, where app 0.15.0 added the tour's ? topic and Plots its views; both kept. plot.js merged by itself: the app's pixelOf and data-tour and Plots' PL13 additions are both in. The app's version set to 0.16.0, the release that carries the Plots module. Built and installed in /Applications (0.14.0 to the Trash). The self-check on the installed copy:

  • on a copy of Y2O3: 26 OK, none FAIL. Every Help item and manual page passes, the Plots manual included, and a refinement runs, is accepted and moves to a window of its own;
  • on a copy of the \(\mathrm{Tb_{2}Pd_{2}Sn}\) buffer: 31 OK, none FAIL. 20 members, tb_t01.dat apart, "no monitor: raw counts"; the overlay, the stack (baselines 0, 3000, 6000 …), the map (19 rows \(\times 301\) columns) and the difference map (\(\pm\)256.9 on the signed √ scale) each drawn and exported.
branch plots de4749f · app/ui/modules/plots/index.js · app/src-tauri/src/modules/plots.rs · app/manual/plots.html · app/ui/plot.js · plots/REQUESTS.md PL13a, PL18–PL20 · L169, L175
L1792026-10-13work

Explore stream: E11 filed (two refinements' paths compared), the compare view laid out to wait for it, and replay checked in the built Mac app by the self-check, which found two faults, fixed

origin/main (b2d7820) merged. (1) E11 in explore/REQUESTS.md, to the core: lsq::PathsCompared from two Trajectory with their end esd's and a reference run — the parameters aligned by address (common, only_a, only_b with start and end), the fixed values that differ between the two starts, units, each run's states, \(\chi^2\), values and esd's, both paths scaled by the reference's end value and esd, the steps of the longer run with the shorter held at its end (held_a, held_b), the difference per step in \(\sigma\) of the reference, each parameter's end difference (in its unit, in \(\sigma\) of the reference, in combined \(\sigma\), agree within ENDS_AGREE = 3), a summary; E11a the pair as two paths with each run's ellipse and \(\sigma\)'s; E11b the end's correlations kept in the results file (a version read back has none today); E11c the run's stop kept there; drs compare; tests on five Y2O3 runs recorded with the core's drs refine --trace (core 0.30.0), kept in explore/compare/cases/: A 10 cycles and B 7 cycles from two starts to the same minimum (\(\chi^2\) 1.1985, every end within 0.01 combined \(\sigma\)), C with the zero shift fixed (71 parameters, \(\chi^2\) 2.4335, cell a 84 \(\sigma\) away), D = A cut at 4 cycles (equal to A to the bit), E diverging. E12 asks the shell part (explore_compare over the document's record and a version's). (2) The compare view (compare.js; a Compare tab, the reference's choice in the controls' bar, the versions of the control file in the sidebar, the ends' agreement in the bottom panel) draws only E11's numbers; until E11 and E12 exist (COMPARE_READY = false) it shows its frames and says it waits. Laid out headless with an E11-shaped prototype (explore/compare/prototype.py, marked PROTOTYPE on screen; the app never loads it) on A–B and A–C. (3) The app built (not installed) and run with DRS_APP_SELFTEST on a perturbed Y2O3 copy; Explore's part of the self-check (explore.selftest, one call line in app.js's selftest) replays every view. First run (10 cycles): every view went through cycles 0–10, the 3D view has WebGL 2 in WKWebView, 92 atoms at cycle 10; two faults: the spectrum stayed at cycle 3 (a slow answer arriving after the replay had moved on was kept), and the peak shape had no states after ~8 s (it asked the core twice). Fixed: a stale answer is dropped and each view's cached answer counts only for the cycle shown; explore_states returns the strongest reflection's profile in one call; the path's commands run off the window's thread. Rerun on the fixed build with Josu's own preference "pause every 2 cycles" (not changed): a paused run of 2 cycles, every view replays 0–2, the peak shape's states in 1.4 s with (2 2 2), the spectrum of cycle 2, WebGL 2. Tests: the module's 4 Rust tests (one new assertion), the app's 59 node tests (explore_compare.test.mjs new; the nophysics guard among them) pass; the window bundles. Shell lines touched: E10b.

branch explore 33ddde1 · explore/REQUESTS.md (E10b, E11, E12) · explore/compare/cases/, prototype.py · app/ui/modules/explore/ compare.js, index.js · app/src-tauri/src/modules/explore.rs · app/ui/app.js (selftest) · app/ui/tests/explore_compare.test.mjs · DRS_APP_SELFTEST reports of 13 October 2026 · L139, L163, L165
L1782026-10-13work

App stream, 0.15.0: the tour of a refinement, the tutorial's script played in the app (TU1) on stable handles (TU5)

App 0.15.0 on branch app, step 3 of the merging session's plan. TU5: data-tour handles where the elements are made: every plot's canvas with plot.pixelOf(x, y) (three additive lines in plot.js), legend keys, the sidebar's files, each zone's divider (re-tagged after every layout change), the app's dialogs' buttons, Data's selects and background controls, Refine's outline and inspector rows, the Reflections view's controls. TU1: ui/tour.js and tour.css play tutorial/script.yml (bundled at build; Help > Tour of a Refinement) or DRS_APP_TOUR=<script.yml>: steps run, field, action (14 kinds), wait (SCRIPT.md's conditions), mac and note skipped; the checks over a read-only API; a caption bar with the narrator's line, a ring on the control in use, Pause, Step, Take Over (Escape); a report of every scene, step (with its time and the status line after it), check, command run by scene, command not run and system dialog answered, shown and written as JSON (DRS_APP_TOUR_REPORT). The window's CSP refuses eval and keeps doing so: the checks are read by an interpreter of a JavaScript subset (ui/tourexpr.js; no assignment, new or prototype access; every check of the script parses). js-yaml 4 vendored. The whole tour in the built app (audit mode, on make_data.py's data in the scratchpad): the script as it is passes 5 of 26 scenes and runs 50 of the app's 176 commands; a scratch copy with the script's own mismatches corrected passes 8 of 26 and runs 62. The script's mismatches are filed for the tutorial stream as TU7 in tutorial/REQUESTS.md (the only file of tutorial/ touched): field and dialog names, atoms by label, a 1.7 px selection box, a wavelength the data do not state, a typo that is valid, TOML point lines, Explore's elements without handles, a scene that continues in a new window, check_script.py's coverage now failing on help.here, help.tour and explore.play. Faults of the app the takes found, fixed: Close Tab on the text beside a plot left no tab active (the tab used before becomes active); Undo after a click on a plot undid a text field's typing (a click on a plot takes the focus); Convert to Another Wavelength on a scan with no wavelength added an operation that broke every later read (refused with the reason; request 34 for a source wavelength in the core). Still open, not yet traced: in scene 1.7 Smooth, Mask a Range and Delete a Range leave no operation; 2.1's Fix and 2.2's inspector row are not found. The manual's section "The tour of a refinement" and its ? topic. Tests: tour.test.mjs, tourexpr.test.mjs, the manual's topic; 65 node tests (nophysics included) and 50 Rust tests pass, clippy clean. Release entry in app/README.md. Not installed.

branch app e5c4a18 · app/ui/tour.js, tour.css, tourexpr.js · tutorial/REQUESTS.md TU7 · app/REQUESTS.md 34 · app/README.md
L1772026-10-13work

L170, step 4: Plots (c) and (d), windows against T (PL5), FullProf's .seq as a table (PL11), the projection's hidden parts (PL17); two findings of 0.29.0 closed

Plots' PL5, PL11 and PL17, and the two findings of L172. Core 0.31.0, test plots_series (7).

PL5, windows.

  • plots::windows(set, windows, background, at) and window_value: for each named x window and member, ∫ (y − b) dx over [from, to], each point's y − b taken over the part of its interval (midpoint to midpoint) inside the window, with \(\sigma\) through every y the sum depends on; the centroid Σ x (y − b) Δx / Σ (y − b) Δx with its \(\sigma\); the strongest peak (by prominence) of peak_search at the proposed prominence and 3\(\sigma\); the background at chosen x. Points of no weight are left out and counted.
  • The background: each member's automatic one (arPLS), given points (.drb or .bac, linear or spline), or the line between the member's own values at the window's two ends. For the line, \(\sigma\) includes the ends' y. The automatic background's and given points' own uncertainty is left out, as the mock-up's harness left it out.
  • WinPLOTR integrates between two clicks and writes an .int file; its guide gives no rule (WINPLOTR.pdf p. 30, §5.6.7–5.6.8). The rule here is ours.
  • Checked:
  • against the mock-up harness's sum Σ (y − b) h: equal once its two end points count half, for all 20 files and four windows, to \(10^{-9}\);
  • \(\sigma\) of the integral and of the centroid against finite differences over every point, for the line and for given points: to \(10^{-6}\) and \(10^{-4}\);
  • a Gaussian of area 100 on a slope: 100 to \(10^{-6}\), centre 10 to \(10^{-9}\).
  • \(\mathrm{Tb_{2}Pd_{2}Sn}\), F5's four windows:
  • 19.1° has 21929 \(\pm\) 48 counts·° at 4 K, 7158 \(\pm\) 31 at 22 K and 6.9 \(\pm\) 17.4 at 30 K; 32.6° falls alike to −2.4 \(\pm\) 17.3.
  • 26.7° and 42.2° keep 103 \(\pm\) 18 and 87 \(\pm\) 17 at 30 K: the nuclear peaks at 26.4° and 42.1°, which the peak search names.
  • The background at 16.5° rises from 1025 counts at 4 K to 2378 at 30 K.

PL11, FullProf's .seq.

  • seq::read: per block, NEW_REFINEMENT's file code, NUMPAR, N_PATT, NPHASE, PARVAL's index, external parameter and numor, GLOBAL_CHI_2, each phase's R_Bragg (with its second number), volume and weight fraction with \(\sigma\), and every refined parameter with its symbolic name, value and \(\sigma\). Seq::names, column, inconsistent (NUMPAR against the lines) and repeated: a run appended to an earlier one (FullProf news, 14 February 2008).
  • Lines not understood are kept in unread, not dropped.
  • The .seq itself is documented only as "the summary of effectively refined parameters together with their symbolic names" (news, 10 February 2004). The layout was read from la0.seq and checked against its run's own files:
  • all 71 blocks: PARVAL's index is 0–70, and its parameter and numor are the data file's sample temperature and numor (Ins = 3's tsamp and numor);
  • all 71 blocks: \(\chi^2\), phase 1's R_Bragg, its second number, the volume, the weight fraction and their \(\sigma\) agree with the .sum to the digits it prints;
  • GLOBAL_CHI_2 is the .sum's "Global user-weigthed Chi2 (Bragg contrib.)". The first "Chi2:" of its R-factors is another number (la0_122: 3.80 there, 3.81 here, 3.80951 in the .seq);
  • R_Bragg's second number is the .sum's Rf-factor for the nuclear phase. For the magnetic phase the .sum prints only the magnetic R-factor, the first number, and the second is not identified.
  • 46 parameters in every block. a runs from 5.54220(7) Å at 68.861 K to 5.54031(7) Å at 168.062 K.

PL17, the projection's hidden parts.

  • plots::project(displaced, order, floor): the members front to back, each curve hiding what lies under it, down to a floor raised by its own shift (or without end), across its own x range. Returned: the visible pieces of each curve as polylines, the share of its length they cover, and the oblique axis (each member's last x at the foot of its curtain). Deciding what hides what is the core's (L139); the app draws the pieces.
  • Checked on F2's geometry (20 files, BT nearest, 0.22° and 2200 counts a step, floor 400 below the lowest count):
  • the nearest curve is whole, in one piece;
  • 120000 samples of the others: each visible exactly where no curve in front covers it, by a brute-force test of every curtain;
  • the shares run from 0.95 down to 0.79;
  • here the floor changes nothing;
  • a tent before a line hides it from 0.5 to 1.5;
  • a curve below the front one's floor shows.

(a) The D20 wavelength: not read, and why.

  • FullProf reads Ins = 3's fourth line as xm, ti, x0, ome, chi, phi, tr1, tr2, step, tset, tcons, tsamp (manual p. 133) and takes no wavelength from it.
  • la0120.dat's 1.870 sits in tr2. D1B's hocu.dat has 0.000 there, while its .pcr gives λ = 2.524 Å. The title's "1.87AA" is free text typed at the instrument.
  • Reading either would be a guess, so the reader states none. The λ comes from the control file, as FullProf takes it from the .pcr. Frozen in a test.

(b) No weight in JSON.

  • Pattern's \(\sigma\) is written null for a point of no weight (infinite \(\sigma\)), and null reads back as no weight.
  • JSON holds no infinity, and the app already reads null so. The text writers keep \(10^{30}\) (formats::write).
  • Tested on a pattern, and on a set holding an xye file with a zero count, through drs plots open --json.
  • Still to do: plots::Resampled keeps \(\sigma\) as Option, where null means outside the member's range.

Command line: drs plots windows … --window name:from:to … [--background auto|line|<file>] [--at x,…], drs plots seq <file.seq> [--parameter name] and drs plots project … (--step | --per-unit) [--order] [--floor], each with --json.

core/src/plots.rs (Window, WindowBackground, windows, window_value, project), core/src/seq.rs, core/src/data.rs (no_weight), core/src/main.rs (drs plots windows, seq, project) · core/tests/plots_series.rs · plots/REQUESTS.md PL5, PL11, PL17 · plots/mockups/make_mockups.py (F2, F5), plots/mockups/dump/src/main.rs · manuals/fullprof/1-WinPLOTR/docs/WINPLOTR.pdf p. 30 · manuals/fullprof/news/Fullprof_News_2004.htm, Fullprof_News_2008.htm · manuals/fullprof/1-WinPLOTR/FPS_dec2015_WinPLOTR_TR.pdf pp. 18–23 · manuals/fullprof/FullProf_Manual.pdf p. 133 (Ins = 3) · manuals/fullprof/FP_tests/hocu.dat, hocu.pcr · manuals/fullprof/1-WinPLOTR/2_FP_sequential_refinements/LaMnO3_D20 (la0.seq, la0*.dat, la0_*.sum) · L139, L170, L172
L1762026-10-13work

App stream, 0.14.0: the manual, Help and What's New working; the whole manual written (L174)

The empty viewer: L174's cause, confirmed in the running app.

  • What's New and Keyboard Shortcuts share dlg-viewer, a flex column with only a maximum height.
  • Its body was flex: 1 (a starting height of 0 %), which WebKit keeps at zero.
  • Measured by the self-check: with the old rule the body was \(818 \times 0\) px while holding 101,729 characters of release notes. With flex: 1 1 auto it is \(818 \times 588\) px.

The manual has a window of its own with a fixed height: contents (pages and sections) at the left, the page at the right, a search field, links to the site opening in the browser, maths in KaTeX, offline. Its pages:

  • the app's manual (app/manual/app.html, 25 sections): the window, opening things, projects, the files by role, FullProf's files apart, reference folders, versions and results, Settings, the keyboard, long work, appearance, Help, and the workflows W1–W4, W6–W8 and W11–W14 step by step;
  • one manual per module (Data, Refine, Editor, Explore): every view, panel, dialog and field, with the core function behind it. The agreement factors, I obs and the convergence test are given as lsq::statistics and diagnostics compute them, in LaTeX;
  • the site's Files and Data operations pages, made at build by ecosystem/tools/build.py's own writers into a temporary folder, so that nothing is written into ecosystem/ or core/;
  • Parameters, from the core's dictionary.

Every command's table is drawn from the command registry at run time. tools/gen_manual.py writes ui/manual.json at build: 7 pages, 101 sections, about 14,000 words, generated and not committed.

Finding your way.

  • A ? on every panel's header, and Help ▸ Manual for What Is on Screen, open the module's manual at the section for its view.
  • ⌘K finds the manual's sections.
  • What's New lists every release, newest first by date. It opens by itself after an update, with the releases new since the version last used marked.

The self-check (DRS_APP_SELFTEST) opens What's New, the shortcuts, every manual page, each module's manual, the manual for what is on screen, ⌘K's manual search, the palette, and the activity bar's What's New, Manual and Help pop-up with each of its items. It writes FAIL on a body with no height or no text. On the built 0.14.0: 24 lines OK, none FAIL. Report an Issue is not run, because it opens the browser.

Also in 0.14.0. A version's data is read with the core's control::open_in (request 33, core 0.28.0), and the app's copy of it is gone.

Tests. ui/tests/manual.test.mjs; 56 node tests (nophysics included) and 50 Rust tests pass; clippy is clean.

The merging session at the merge:

  • added the missing release entry for app 0.13.1, never copied from app/README.md at its merge, with 0.14.0's;
  • rewrote the site's manual page, which said "Nothing is written yet", to say where the manual is now.
  • built 0.14.0 and installed it in /Applications (0.12.4 to the Trash), then ran the self-check on the installed copy: 24 Help lines OK, none FAIL (What's New \(818 \times 588\) px with 105,142 characters; the app's manual 31,350; Refine's 21,404), and a refinement of Y2O3 run, accepted and moved to a window of its own.
branch app 215d7ac · app/manual/*.html · app/tools/gen_manual.py · app/ui/tests/manual.test.mjs · app/README.md · ecosystem/site/manual/index.qmd · L174
L1752026-10-13work

L170, step 3: Plots (b), the common grid and the map of a set (PL4), the sum, average and difference of members (PL9)

Plots' PL4 and PL9. Core 0.30.0, test plots (now 6).

  • PL4 (a), a common grid. plots::same_grid says whether the members share one grid; common_grid(set, range, step) gives one: the overlap or the union of the ranges, the finest, the coarsest or a given step; the members' own x when they share a grid and the rule is the overlap at the finest step. resample(pattern, grid): at a grid point inside the member's range, its own point when it has one there (as measured), else linear interpolation where the grid is as fine as the member's step or finer (data_ops::interpolate_at, now public: \(\sigma\)² = (1−t)²\(\sigma\)₁² + t\(^{2}\)\(\sigma\)₂²), and the average over each bin [g − h/2, g + h/2] where it is coarser, by the shares of the points' midpoint intervals in the bin (the same shares as data_ops' rebin), \(\sigma\) = √(Σwᵢ²\(\sigma\)ᵢ²)/Σwᵢ; None outside the range. \(\mathrm{Tb_{2}Pd_{2}Sn}\)'s 20 files share one grid of 301 points (16–46°, 0.1°), kept as measured; a step of 0.25° bins them to 121 points. Tb's tb_t02.dat with \(\mathrm{LaMnO_{3}}\)'s la0128.dat (D20): the union is 1535 points from −3.101 to 150.299°, D20's step, 0.001° off Tb's points, so Tb is interpolated at 300 of them; the overlap is 16 to 46°.
  • PL4 (b), the map. plots::map(set, grid, scale, reference): the members by x on the grid, one row per member with a value of the variable, sorted, its edges at the midpoints to its neighbours (half the gap out at the ends); those without a value apart; the x edges at the grid's midpoints; the colour range on the linear, √ or log scale (positive values for log). With a reference member, the difference map: each member less the reference, the range symmetric about zero, log refused. \(\mathrm{Tb_{2}Pd_{2}Sn}\): 19 rows, 4 K from 3 to 5 K and 20 K from 19 to 20.5 K (its neighbours 18 and 21 K), tb_t01.dat apart, the colour range the smallest and largest count (905 to 68455); less the paramagnetic 30 K pattern (F4), \(\pm\)66001 and the 30 K row zero.
  • PL9, derived patterns. plots::derive(set, members, derivation, grid): the sum (\(\sigma\)² = Σ\(\sigma\)ᵢ²), the average (\(\sigma\) = √(Σ\(\sigma\)ᵢ²)/n), the \(\sigma\)-weighted average (wᵢ = 1/\(\sigma\)ᵢ², \(\sigma\) = 1/√Σwᵢ) and the difference of two members, the first less the second (\(\sigma\)² = \(\sigma\)₁² + \(\sigma\)₂²); a grid point where a member has no point or no weight is left out and counted. On Tb's counted data the sum's \(\sigma\) is √(y₁ + y₂) to \(10^{-9}\). derived_text writes it as FullProf's XYDATA (.xys, Ins = 10): the six header lines the core's writer writes, the derivation in them, each member as a '!' line with its file and its temperature with the source, then x, y, \(\sigma\). Read back by the registry, the pattern is the derived one, point for point.
  • The file dictionary names it: kind derived_data, Plots/{set} {operation}.xys, in Plots/ so that it is never taken for a measured file in Data/ (proposed). WinPLOTR writes its Summation, Difference and Average to a file of the user's name (its guide p. 30).
  • drs plots grid <set> [--range overlap|union] [--step finest|coarsest|<h>], drs plots map … [--scale] [--reference k] [--json], drs plots derive … (--sum | --average | --weighted-average | --difference) --members k,… [--out <file.xys>] [--name <set>], which refuses to overwrite a file or to write any other extension.
  • Decided: XYDATA, not xye. The first writer wrote xye; reading it back lost the difference: 267 of Tb's 301 points (4 K less 30 K) are negative, and the xye reader takes y \(\le 0\) as no weight, GSAS-II's rule. FullProf's XYDATA keeps negative y. Set aside: xye with the rule changed for derived files (a reader cannot tell them apart); a format of our own. The core's reader skips '!' lines after the header; whether FullProf does is not stated in its manual (not checked).
core/src/plots.rs (same_grid, common_grid, resample, on_grid, map, derive, derived_text), core/src/data_ops.rs (interpolate_at), core/src/main.rs (drs plots grid, map, derive), ecosystem/data/files.yml (derived_data) · core/tests/plots.rs (pl4_grid_and_map, pl9_derived_patterns) · plots/REQUESTS.md PL4, PL9 · manuals/fullprof/1-WinPLOTR/docs/WINPLOTR.pdf p. 30 · manuals/fullprof/1-WinPLOTR/1_Tb2pd2sn, 2_FP_sequential_refinements/LaMnO3_D20 · L170
L1742026-10-13finding

The manual and What's New open empty in the installed app; Josu asks for the whole manual, Help and the release notes working

What Josu saw. In app 0.12.4 the manual opens with its title and the Done button and nothing between. Josu reports that Help and the release notes do not work either. His words: "I would like to have everything there, in the manual, the Help functioning; the Release Notes functioning, also."

The probable cause, not yet checked in the running app.

  • The manual, What's New and Keyboard Shortcuts all share one viewer, dlg-viewer (app/ui/index.html:180, viewer() at app/ui/app.js:917).
  • Its dialog is a flex column with only a maximum height (dialog.wide, app/ui/style.css:286-287).
  • Its body is flex: 1; min-height: 0 (style.css:298). With flex: 1 the body's starting height is 0 %. In a column whose height is not fixed, WebKit, the engine of the app's window, keeps that at zero.
  • So the text is there but gets no height. About, a dialog without wide, shows.
  • The rules date from 10 October (06dad64f) and are unchanged in app 0.13.1.

What the app is to do. APP_CONVENTIONS §5, already required and not yet met:

  • the app's manual and one per module, the same text as the site's manual pages, bundled to work offline;
  • a ? button in each module's header that opens that module's manual at the page for what is on screen;
  • What's New opening by itself after an update;
  • every Help menu item working.

The module manuals today are one paragraph each. The app's own manual is a list of links to the site plus the parameter dictionary. "Everything there" is read as follows: every module, command, dialog and panel described, and the workflows of APP_WORKFLOWS.md written out step by step. The app's self-check is to open each viewer and check that its body has a height and text.

Passed to the app stream as its next step, before step 3 (TU5, TU1).

Josu's screenshots of app 0.12.4 (About; the manual) · app/ui/style.css:286-298 · app/ui/app.js:917-943 · app/ui/index.html:180-183 · APP_CONVENTIONS.md §2, §5
L1732026-10-13work

Core 0.29.0 merged (L172); WinPLOTR's documents put where the cloud can read them; the core's two findings passed on

The sources. The cloud, working PL1, said that the .buf syntax sources were not in the repository and that it would infer the format from the example files. It was right: the Plots study had read WinPLOTR's 2003 user's guide and its news from the local FullProf install, and neither was committed.

  • The merging session committed them to manuals/fullprof/1-WinPLOTR/docs/ (private): the user's guide (.buf on p. 19, §5.1.4), the news, the command-file slides, the EPDIC paper. It told the cloud session to check against them.
  • The cloud merged them and checked its reader against the guide and the news (commit 3c1fa36), citing them in L172.
  • Kept as a rule: before a cloud step, every source its requests cite is in the repository.
  • The news's HTML copy was then dropped and its PDF kept, which holds the same text, the line L172 relies on included. The core's file dictionary names no web page, so the app's project listing counted 16 unnamed files against its frozen 15 (every_run_and_tutorial_is_listed_by_role_with_the_cores_names). L172 now cites the PDF.

Checked at the merge:

  • drs plots open on \(\mathrm{Tb_{2}Pd_{2}Sn}\)'s Tb_vs_T.buf: 20 members of 301 points, sorted 4 to 30 K, each temperature with its source (the title's "T.=.4.K"), and tb_t01.dat last with "T: nothing says it".
  • The core's 218 tests, the app's 50 and Publish's 21 pass, the app's after the fix above.
  • PL1–PL3 are marked met in plots/REQUESTS.md.

The core's findings, open:

  • the D20 reader does not read the wavelength in the title (1.87 Å), to be checked against FullProf's description of that header line;
  • an infinite \(\sigma\) becomes null in the JSON, which reads back only in the app's JavaScript.
commits 9fe05b2, ad6a066, 939e931, 3c1fa36 (branch claude/amazing-allen-nzu7zz) · manuals/fullprof/1-WinPLOTR/docs/ · app/src-tauri/src/project.rs (MISNAMED_FROZEN) · plots/REQUESTS.md · L170, L172
L1722026-10-13work

L170, step 2: Plots (a), the plot set, each member's temperature with its source, the displacements (PL1–PL3)

The core's part of Plots' design (a), module plots. Core 0.29.0, test plots (4), on the two series of manuals/fullprof/1-WinPLOTR/.

  • PL1, the plot set. plots::open opens files as a set, every scan a member with the reader's facts (format, axis, points, range, mean step, λ, title, temperature, monitor, counting time) and its pattern; plots::read_buf and open_buf read WinPLOTR's buffer, as WinPLOTR's documents give it (now in manuals/fullprof/1-WinPLOTR/docs/, merged from main, d7d5a21):
  • one data file per line, "without the directory name", optionally followed by "informations about the files (description of the experiment, temperatures)": file_1.dat T=5K, file_2.dat T = 10 K (the user's guide, March 2003, p. 19, §5.1.4, examples 6 and 7, the study's S16);
  • "the title of the plot in the first line if begun by COMMENT or TITLE" (WinPLOTR's news, July 2013, S5);
  • empty lines allowed (the news, May 1998). Names are relative to the buffer. The documents and the example files agree: \(\mathrm{Tb_{2}Pd_{2}Sn}\)'s two buffers use only the names, which the documents allow. One rule is ours, not the documents': a line is a file's name whole when that file exists (a name with a blank), else its first word. Not taken from them: the buffer's one "common data file format" (the prompt's second argument, the news of September 2004); here each file is identified by the registry. Pattern, Scan and the set carry serde, so the app and drs share one shape. Both buffers of \(\mathrm{Tb_{2}Pd_{2}Sn}\) open as 20 members of 301 points, 16–46°, step 0.1.
  • PL2, the temperature and where it was found. In the order taken:
  • the reader's header (D20: la0128.dat 79.618 K, the request's example);
  • a buffer's comment ("T = 1.5 K");
  • the title (FullProf's free-format titles write "T.=.4.K", dots for blanks: read as 4 K);
  • the file name ("_300K", "1p5K", "_T300"; a lower-case "t01" is a file's index and is not read). None when nothing says it: tb_t01.dat says "BT" and has none. It goes after the others until a value is typed (set_variable, with the user's note), as Josu decided (L169). Every source that gave a value is kept, so a disagreement shows (a buffer's 4.2 K against the title's 4 K). \(\mathrm{Tb_{2}Pd_{2}Sn}\): 4, 6, … 30 K from the titles, sorted. \(\mathrm{LaMnO_{3}}\): 71 D20 files from 68.861 to 168.062 K from their headers, sorted.
  • PL3, the displacements. plots::displace: none; a step per member (k·dx, k·dy: a stack or a projection); or in proportion to the variable from the first member's (unequal steps in T show as unequal gaps; a member without a value is not placed). A member's own override. On the scale shown: linear, √ (the shift added to √y, −√|y| below zero) or log₁₀ (not a number where y \(\le 0\)). Each member's shift and baseline, and its background displaced alike.
  • drs plots open <set.buf | files…> [--set k=value:note] [--json] and drs plots displace … (--step | --per-unit) [--dx] [--scale].
  • Found: the D20 files' wavelength (1.87 Å, in their title) is not read by the D20 reader, so their λ is "not stated"; the reader is left as it is until FullProf's description of that header line is checked. A pattern's \(\sigma\) written as "no weight" (infinite) becomes null in the JSON, which reads back only in the app's JavaScript.
core/src/plots.rs, core/src/data.rs and formats/mod.rs (serde on Pattern and Scan), core/src/main.rs (drs plots) · core/tests/plots.rs · plots/REQUESTS.md PL1–PL3, plots/DESIGN.md §2, plots/STUDY.md §2.2, §10.1 · manuals/fullprof/1-WinPLOTR/docs/WINPLOTR.pdf p. 19 §5.1.4 (S16), WinPLOTR_news.pdf July 2013, May 1998, September 2004 (S5) · manuals/fullprof/1-WinPLOTR · L169, L170
L1712026-10-13work

L170, step 1: Publish's P19 and P20, the app's 33

The small requests of L170's first step. Core 0.28.0, test small_requests (3).

  • P20, B with its su. structure_factor::b_from_u(u, su) and u_from_b(b, su) (B = 8π²U, the su scaled the same way), public. The results file's phase block holds a loop _drs_atom_site_label, _drs_atom_site_B_iso_or_equiv: each atom's B, or B_eq = 8π²U_eq for an anisotropic one (P15), with its su through the covariance, by the CIF's labels. Y2O3 with its three B's refined: Y1 0.366(17), the engine's own B and esd to the digit.
  • App 33, a version opened from the project's folder. control::open_in(path, base): the data, the operations' files and the background file named from base; control::open(path) is open_in with the file's own folder. On the command line, --base <folder> for every command that reads a control file (thirteen places). A version in Versions/ naming y2o3.dat fails with open and opens with the project's folder; drs calc <version> --base <project> works.
  • P19, a phase refined by coordinates in the 3D scene. geometry::decompose(model, p, source, q, metric, shift): the refined positions compared with another file's mode phase in the same setting (its reference, by nearest image of the same element, modes::displacements) and projected on its modes (Modes::project); the phase returned as a mode phase with those amplitudes, and the residual the modes do not describe. scene then gives its reference and the irreps' displacements as for a mode phase. Scene::reference_fract: each image's reference position in fractions, so that a slab or a region is chosen on the structure drawn. Case A as coordinates, against its own mode file: residual 0, the amplitudes and the scene (reference, every irrep's displacements) the mode phase's to \(10^{-6}\); 12 of its 33 images lie more than 0.01 from their reference in some fraction. drs structure <file> --cartesian --modes <file> [--amplimodes <output>].
  • Found on the way: a blanket replace made --base's helper call itself when no base was given, which a release build turns into a loop; the step's command-line tests caught it before the commit.
core/src/structure_factor.rs (b_from_u, u_from_b), core/src/results.rs (structure_block), core/src/control/mod.rs (open_in), core/src/geometry.rs (decompose, Scene::reference_fract), core/src/main.rs (--base, drs structure --modes) · core/tests/small_requests.rs · publish/REQUESTS.md P19, P20 · app/REQUESTS.md 33 · L170
L1702026-10-13decision

The core's next work: Publish's P19 and P20 and the app's 33, then Plots' requests in the order of its accepted design

Josu: "let's go for the Core". The core stream (cloud) takes, one step at a time, each with its tests, a log entry, a release entry and a stop: 1. The small requests:

  • P20: B with its su in the results file, or a public B ↔ U with its su;
  • app 33: control::open_in(path, base), a control file opened with its files resolved from another folder (a version in Versions/);
  • P19: the reference structure and the irreps' displacements for a phase refined by coordinates, and the reference positions as fractions in geometry::scene. 2. Plots (a): the plot set. PL1 (several files opened together, WinPLOTR's .buf), PL2 (each member's series variable with its source; never a guessed value, L169), PL3 (displacements on the scale shown). 3. Plots (b): the map and the difference map. PL4 (a common grid with \(\sigma\), the map with rows at the variable's values, its colour range) and PL9 (sum, average, difference of members, written as a new file). 4. Plots (c) and (d): the series view and the projection.
  • PL5 (windows: integrals, centroids, peaks per member) and PL11 (FullProf's .seq, series of refinements);
  • the projection with hidden parts (F2), which Plots' requests leave out. Which part of each curve the ones in front hide is a calculation, so it is the core's (L139): filed as PL17 by the merging session. 5. Plots (e): PL7 (CSV out, xye/xys in d and Q), PL8 (CSV in as a set, never a silent drop of columns), PL6 (normalisation as a view, with the "same area over a range" of L169), PL10 (a set's conversions) and PL12 (the plot set file Plots/{name}.drv and drs plots). PL13–PL16 are the app's, Publish's and the formats' (later). Every step keeps L139: what the app shows is computed in the core.
Josu, 13 October 2026 · publish/REQUESTS.md P19, P20 · app/REQUESTS.md 33 · plots/REQUESTS.md PL1–PL12 · plots/DESIGN.md §8 · L139, L169
L1692026-10-13decision

Josu accepts the Plots design (R32): the name, both projections, the plot set as a project file, honest temperatures and counts

Josu answered the seven questions of plots/DESIGN.md §8 (evening of 12 October; commits 18aa1b7 and 8b30b6b of the Plots stream, merged). The design is accepted: 1. the name is Plots; 2. both projections: the pseudo-3D with hidden parts (F2) and the map (F3); 3. the plot set is a project file, Plots/{name}.drv; 4. a file that does not say its temperature (\(\mathrm{Tb_{2}Pd_{2}Sn}\)'s first file says "BT") is drawn apart until the user types its value. The typed value keeps a note of its source; Plots never invents one; 5. with no monitor, raw counts, with a visible note "no monitor: raw counts", and one command that scales every file to the same area over a range the user picks; 6. the 3D surface later; 7. the order of work as proposed in the design, (a) to (e). Next: the core requests PL1–PL12 (to the core stream), then the module in the app, as the design's §10 orders it.

Josu, 12 October 2026 · plots/DESIGN.md §8, §10 · commits 18aa1b7, 8b30b6b (branch plots) · L154, L167
L1682026-10-12work

L153, step 2: the open requests to the core, met or closed; TU6, drs simulate on a pattern that is not a number

(Logged by the core stream as L165; renumbered L168 at the merge, L165–L167 being the app, Publish and Plots steps.) The core stream's second step of L153 (L160): every open request to the core checked against the current core, then the missing ones built. Core 0.27.0, test open_requests (6) and simulate_not_a_number.

Already met, to be closed by the merging session:

  • app 12 (a data file written: formats::write, drs data read --out), 15 (the line of each point: formats::point_lines) and 16 (the analyses' settings: data_ops::settings, operations.yml): core 0.21.0, L088;
  • app 13 (the checks beyond the text: a .pcr checked as a .pcr, parameter holes, a parameter written twice, symmetry-fixed values, predicted correlations, ranges, missing files): core 0.18.0 (L076), with the guardrails of 0.24.0 (L110);
  • Publish P9 (AMPLIMODES's summary per irrep): core 0.25.0 (L114), test publish_requests;
  • Explore E7 (equivalent amplitude sets, the reference's signs): core 0.25.0 (L115), test explore_requests; its command line added now (below).

Built now:

  • TU6: Random::poisson draws 0 for a mean that is not a finite positive number, so its rejection loop can no longer run forever. Model::finite refuses a calculated pattern with a value that is not a number, naming the first point and every value of the model outside its limits. drs calc and drs simulate stop with it: Y2O3 with the March–Dollase G₁ = 0 gives "not a number at point 3 (2\(\theta\) = 33.8): outside their limits: phase1.po_g1 = 0 (limits 0.001, none)" at once, where simulate had never ended.
  • P15: U_eq of an anisotropic atom, ⅓ Σᵢⱼ U^ij a*ᵢ a*ⱼ aᵢ·aⱼ (Fischer & Tillmanns 1988; the CIF dictionary's), as Σ βᵢⱼ Gᵢⱼ/(6π²) from FullProf's betas, with its su through the covariance (geometry::u_equivalent, u_eq). The results file and the CIF write it for every Uani atom where they wrote 0: \(\mathrm{PbSO_{4}}\)'s Pb 0.01866(19). Checked against the trace of U in Cartesian axes on a triclinic cell, to \(10^{-14}\).
  • P17: a phase refined by its coordinates keeps its parent (Phase::parent, the control file's mode_parent_* keys, read and written without modes), and geometry::parent_cell reduces its cell. The results file writes the reduced cell for every structure phase with a parent, outside the modes' block where it was written only for a mode phase. Case A's structure as coordinates gives the mode phase's reduced cell to \(10^{-12}\), and with its cell refined _drs_parent_cell_a 8.04299(7).
  • P18: for every structure phase whose atoms all have a charge, the results file holds each atom's coordination, formal valence, mean distance and bond-valence sum with su's (_drs_valence_*; contacts within 3 Å, Bond_Str's tolerance 0.20; geometry::valence). The values are bonds::valence_sums's; the su's are propagated through the covariance with the contacts held, where valence_sums takes the distances as independent and without su's. Case A with Sr\(^{2}\)⁺, Sc\(^{3}\)⁺, Sb\(^{5}\)⁺, O\(^{2-}\) after one cycle: Sb1 CN 6, ⟨d⟩ 1.9788(16), BVS 5.43(3).
  • app 31: formats::project::describe_in(rel, probe) names a file of a project first by its place and name (the file dictionary), then by its content; the measured data (Data/{original}) keep the content's. A results file is "Results of a refinement", output, not data. drs data info <file> --in <project>.
  • app 32: FullProf's exports go to Exports/FullProf/ (Josu, L123); FullProf/ holds the original files and the dictionary names none of them an export. drs convert <file.drc> writes Exports/FullProf/<stem>.pcr.
  • E7 on the command line: drs modes <file> --equivalent [--reference <file>] lists the amplitude sets that describe the same structure (case A: 24) and the one nearest another file's amplitudes by mode name; --amplimodes gives the parent when the file holds none.

Not done: Publish's P19 and P20 are not filed yet (L160 names them: F6's scene, B ↔ U with its su).

core/src/powder.rs (Random::poisson, Model::finite, Phase::parent), core/src/geometry.rs (u_equivalent, u_eq, Valence, valence, parent_cell), core/src/cif_write.rs, core/src/results.rs (structure_block), core/src/control/schema.rs, core/src/formats/project.rs (describe_in), core/src/main.rs (drs calc, simulate, data info --in, modes --equivalent --reference, convert) · ecosystem/data/files.yml (fullprof_*_export) · core/tests/open_requests.rs, simulate_not_a_number.rs · app/REQUESTS.md 12, 13, 15, 16, 31, 32 · explore/REQUESTS.md E7 · publish/REQUESTS.md P9, P15, P17, P18 · tutorial/REQUESTS.md TU6 · L153, L160, L161
L1672026-10-12work

Plots stream: the study of WinPLOTR, the design for Josu's review, six mock-ups on the \(\mathrm{Tb_{2}Pd_{2}Sn}\) series

The Plots stream's first three deliverables (commits 723851e, e31640c, 0e4f10d; merged). No app code; the design waits for Josu.

  • plots/STUDY.md: WinPLOTR's features, each cited to its document and page. Sources: the FullProf School deck of December 2015 (46 slides), the 2003 user's guide, the news to September 2026, the EPDIC-7 paper in the authors' free copy (the paywalled version not read), the 2009 command-file slides, the current winplotr.set; also FullProfAPP 1.3.3 and GSAS-II's code.
  • Of WinPLOTR's 16 pattern formats, the core reads 13 (not CPI, .udf/.jcp, DIFFAX .spc; .buf and .seq recognised, not read).
  • Measured through the core: the \(\mathrm{Tb_{2}Pd_{2}Sn}\) files state their temperature only in their titles ("T.=.4.K"), which the core does not read. A 41-column CSV of the series is read as one pattern, and 19 columns are dropped without a word.
  • plots/DESIGN.md:
  • a plot set (members with label, variable and its source, opacity, colour, displacement, normalisation, λ; saved as Plots/{name}.drv; a WinPLOTR .buf opens as one);
  • five linked views: overlay with dimming, stack, WinPLOTR's projection with an oblique axis for the variable, a map with rows at the variable's true values and linked cuts (and a difference map), and a series view of window integrals against the variable;
  • boundaries: Data stays the one-file workbench, Refine keeps the Rietveld plot, figures for papers go through Publish, Explore untouched;
  • the name Plots kept; seven questions for Josu (§8). Requests PL1–PL16: twelve to the core, two to the app's shell, one to Publish, one on formats.
  • plots/mockups/: six figures through the core, drawn in LuaLaTeX (Latin Modern only):
  • F1, a stack with four files chosen and the rest dimmed; F2, the projection; F3, the map with two linked cuts; F4, the difference map; F5, four magnetic integrals and the background against T; F6, an overlay with a linked zoom;
  • what they show: magnetic peaks between 21 and 25 K, and the background doubling between 12 and 25 K (no monitor in the files to tell why).
commits 723851e, e31640c, 0e4f10d (branch plots) · plots/STUDY.md, DESIGN.md, REQUESTS.md, mockups/ · manuals/fullprof/1-WinPLOTR · L154
L1662026-10-12work

Publish stream, drs-publish 0.3.0: every calculation is the core's (L139); a bug in T2's B fixed; P19 and P20 filed

The Publish stream's plan after its own audit under L139 (commit 3dd181b, merged).

  • Moved to the core's functions: F1's difference (diagnostics::difference), T1's step (Pattern::mean_step, data::extent), F6's symmetry expansion, frame, neighbours and hulls (geometry::scene, modes::morph). T2 no longer turns U into B: a refined B is printed with its su, a fixed B as the control file states it.
  • A bug found on the way (in 0.2.0): T2 never found the refined B of atoms sharing a label (\(\mathrm{Y_{2}O_{3}}\)'s two Y), so it fell back to 8π²U and rounded the su twice.
  • The guard tests/nophysics.rs fails on square roots, π, trigonometry or symmetry operations anywhere in the crate but the code that draws on the page. Checked by planting a square root.
  • The five sets were rebuilt and pass the Latin Modern check. Visible changes are in T2 only: \(\mathrm{Y_{2}O_{3}}\)'s Y2 B 0.411(16) \(\to 0.411\)(15), the su rounded once; \(\mathrm{Sr_{2}ScSbO_{6}}\)'s fixed B's as the control file states them (0.1402, not 0.14).
  • Filed:
  • P19: F6 needs the reference structure and the irreps' displacements for a phase refined by coordinates, and the reference positions as fractions;
  • P20: B with its su in the results file, or a public B ↔ U function. Both are passed to the core stream. At the merge, the crate's 21 tests pass.
commit 3dd181b (branch publish) · publish/src, publish/tests/nophysics.rs, publish/examples/Publish/*, publish/REQUESTS.md P19, P20 · L139, L159, L160
L1652026-10-12work

App stream, 0.13.1: the run's record is the document's (E9's shell part), and a version opens from Versions/ again

The app stream's step 2 of L160 (commit af184f0, merged).

  • The document keeps the core's Trajectory. It is set when a run begins (a resumed run keeps its earlier cycles), with each cycle added as it comes, so Explore reads the path so far during a run. It is replaced by the session's whole record at the run's end, kept with the pending result and after Accept, and dropped on Discard.
  • A version opened from Versions/ reads its run back from the results file it was accepted with, through the core's Trajectory::from_end.
  • Explore's commands take the record from the document; the shifts the window used to send are now optional and ignored, so the Explore stream's window keeps working until it stops sending them. No shift travels through the window. E9 is answered in explore/REQUESTS.md.
  • Found and fixed: a version could not open from Versions/ at all. The core resolves the data beside the control file, and a version names it relative to the project's folder. The document now opens a version with its files resolved from the parent of Versions/. Request 33 asks the core for control::open_in(path, base). Tested: the_runs_record_is_the_documents_and_a_version_reads_it_back (Y2O3 from a perturbed start). At the merge, the app's 50 Rust and 49 node tests pass.
commit af184f0 (branch app) · app/src-tauri/src/modules/refine.rs, explore.rs · explore/REQUESTS.md E9 · app/REQUESTS.md 33 · L160
L1642026-10-12work

The site's builder parses on Python 3.10 again (Quarto's pre-render failed)

After the sessions restarted, the shell's python3 was Anaconda's 3.10, and Quarto's pre-render (python3 ../tools/build.py) stopped: one f-string in build.py held a backslash and a quote inside its expression, which only Python 3.12 accepts. The parameter page's correlation rows build their links in a function of their own (param_links), so the file parses on 3.10. The rendered rows are unchanged: each correlated parameter a link to its entry. An interrupted render had also left site_libs/ and eleven .html files in the site's sources; they were moved to the Bin.

ecosystem/tools/build.py (param_links) · ecosystem/site/_quarto.yml (pre-render) · Python 3.10.9
L1632026-10-12work

Explore stream: replay, the cycles played in order with every view following (R31)

The Explore stream's step 1 of L160 (commit 99a473d, merged); R31's replay (explore/DESIGN.md §2.6).

  • A play/pause button and a speed choice (½, 1, 2 or 4 cycles a second) beside the cycle slider.
  • One control sets the cycle, and the slider, the replay's timer and the sidebar's cycle list all drive it. The traces, the pair, the \(\chi^2\) map, the peak shape, the structure (its scene asked of the core again at each cycle) and the directions follow the same cycle.
  • A replay starts from the start when the end is shown and stops at the end. Nothing animates by itself.
  • Explore ▸ Play / Pause the Cycles, in the menu and ⌘K; the manual says it.
  • The demo page takes &explore=<view>&cycle=k&play=1, for headless checks on a recorded run.

Tested: explore_replay.test.mjs. At the merge, the app's 49 node tests pass (the nophysics guard among them) and the window bundles. Shown headless from a perturbed \(\mathrm{Y_{2}O_{3}}\) start (10 cycles, \(\chi^2\) \(28.0 \to 1.198\)); not yet run in the Mac app.

Next, step 2, two paths compared: what the core must add is filed as E11 (two paths aligned by parameter, scaled by one end's esd, their difference per cycle) before the window draws them.

commit 99a473d (branch explore) · app/ui/modules/explore/index.js, app/ui/tests/explore_replay.test.mjs · explore/DESIGN.md §2.6 · L160
L1622026-10-12work

App stream, 0.13.0: Start a Refinement from the data and the structure (W2, W3, TU4) on core 0.26.0's drs new

The app stream's step 1 of L160 (commit e6794e9, merged).

Data ▸ Start a Refinement… now offers three starts:

  • a previous control file (W3, as before);
  • the structure, as one CIF per phase;
  • a formula, a space group and a cell (a Le Bail start). The wavelength comes from the data file when it states one, typed (λ₁, λ₂, ratio), or an anode's Kα lines from the core's table; neutrons too.

Who does what:

  • The app's Rust side (modules/start.rs) only arranges the call: it reads the CIFs' text, turns the dialog's values into the core's Known and Instrument, asks start::start, and writes what it gives with Start::write. Nothing is overwritten, nothing is written into a reference folder.
  • ui/start.js parses the dialog's text into numbers and names every missing or wrong value.
  • Before the file opens, a dialog says what the core made: the phases, the free parameters, the background file, the data's range, the peaks and their median FWHM, and every default with its source. Open, or Discard (both files removed).

Tested:

  • a_refinement_started_from_data_and_a_cif, a_refinement_started_from_a_formula_a_group_and_a_cell and nothing_is_started_inside_a_reference_folder (Rust);
  • start.test.mjs (node); the nophysics guard still passes.
  • The built app checked by itself on a copy of Y2O3 (formula, I a -3, 10.60, Cu's lines): the core wrote 1 phase, 101 free background points and 24 peaks (median FWHM 0.1314°, L155's numbers), with 16 defaults listed. The file opened in Refine and the Editor.
  • At the merge: 46 node and 49 Rust tests pass on main.

Found: npm run build needs three.js installed (Explore's dependency), so npm install must come first.

commit e6794e9 (branch app) · app/src-tauri/src/modules/start.rs, app/ui/start.js, app/ui/modules/data/index.js, app/ui/tests/start.test.mjs, app/README.md · core 0.26.0 (start) · L155, L160
L1612026-10-12work

Tutorial stream: the script as data with its checker, the data made by the core, the subtitles; a bug found in drs simulate

The tutorial stream's second step (commit 34eb208, merged), on L160's working answers.

  • tutorial/script.yml and SCRIPT.md: the storyboard as data, in steps of five kinds (run, field, action, wait, mac). Its checks are expressions over a player API that the tour player (TU1) is to provide.
  • tools/check_script.py validates it against the registry: 26 scenes, about 18:40, 134 of 173 commands run (77 %), the 39 others each with a reason, no unknown id. Run again at the merge, the same.
  • Scene 2.1 has its variant in which the app writes the .drc from the structure.
  • tools/make_data.py: the tutorial's data made through the core only, never from manuals/.
  • The published \(\mathrm{Y_{2}O_{3}}\) and \(\mathrm{LaB_{6}}\) structures, through drs simulate (seeds 1–3) and drs new. The same seeds make the same files to the byte.
  • The output goes to tutorial/data/, which git does not keep, with a report. --install copies the folder to ~/Documents/diff-ref-struct.
  • Act 6's second pattern is a second \(\mathrm{Y_{2}O_{3}}\) scan (seed 2).
  • The structures' digits are marked to be checked against the paper before the film.
  • tools/make_srt.py: the subtitles, 112 cues, to be retimed from a take's report.
  • A correction: the storyboard has 26 scenes, not 31 (the stream's miscount, now corrected in L158).
  • Found in the core (TU6): drs simulate never ends when the model's pattern holds NaN. G₁ = 0 gives NaN at every reflection, and the Poisson sampler's rejection loop for λ \(\ge 30\) never accepts a NaN. Reproduced twice; drs calc prints the NaN. Asked of the core: refuse a pattern with NaN, and a Poisson draw of 0 for a non-finite mean. Passed to the core stream with L153's step 2. No film: it waits for the app's tour player (TU1).
commit 34eb208 (branch tutorial) · tutorial/script.yml, SCRIPT.md, tools/check_script.py, make_data.py, make_srt.py, subtitles.srt, REQUESTS.md TU6 · L158, L160
L1602026-10-12decision

The streams' next steps, set by the merging session on Josu's word ("go to all of them, following you")

After L159's audit Josu asked the merging session to set every stream going on its proposals. Sent to each session, each told to pull main first, work one step at a time, stop and report, and carry a Log: line:

  • Core (cloud): L153 step 2, the open core requests (app 12, 13, 15, 16, 31, 32; Explore E7; Publish P9, P15, P17, P18; Publish's P19 and P20 when filed). Josu pastes the message into the cloud session.
  • App:
  • "Start a Refinement from data + structure", the dialog on the core's drs new (P48, TU4);
  • then E9's shell part, the document keeping the session's trajectory;
  • then the tutorial's TU5 (stable handles) and TU1 (the tour player with its caption bar, built on the self-check).
  • Explore: what R31 still lacks. Replaying a run in every view, then two refinements' paths compared, with every calculation in the core (requests E11 …). A past cycle refined again waits for E9.
  • Publish: its own plan after its §0 audit:
  • F1 and T1 onto the core's functions;
  • F6 on geometry::scene (P19 for what does not fit);
  • P20 for B ↔ U with its su;
  • a guard like nophysics.test.mjs, and the five sets rebuilt.
  • Tutorial: the working answers to its questions, the merging session's on Josu's delegation and his to overturn.
  • Q1: \(\mathrm{Y_{2}O_{3}}\) simulated by the core with a fixed seed, said so, plus a simulated \(\mathrm{LaB_{6}}\) for the data-first start.
  • Q2: the .drc in the folder for the first take, with the variant where the app writes it from the CIF (drs new) written now. It becomes the default when filmable, being closer to "knowing something on the material".
  • Q3: Josu's ⌘⇧5 for the first take, the whole screen when he grants the permissions; subtitles only, for now. Then script.yml with its checker, the data and folder made by a script through the core, and the subtitles. No film until the tour player (TU1) exists.
  • Plots: busy on its study; untouched.
Josu, 12 October 2026 · messages to the app, Explore, Publish and tutorial sessions · L153, L158, L159
L1592026-10-12finding

The streams audited (all but Plots): where each stands and what waits for Josu

Josu asked how the chats are. Read from each one's branch, worktree and transcript, not from what it reported.

The repository. All six branches at main (06291b1), no uncommitted work in any worktree. Three stream steps had been merged without a log entry; they are now L156 (Publish 0.2.0), L157 (Explore after L122) and L158 (the storyboard). The merging session checks every Log: line at each merge from now on.

Each stream:

  • Core (cloud): step 1 of L153 done, core 0.26.0 with drs new (L155); its tests pass on main (204 core, 46 app). Next: step 2, the open core requests, when Josu says.
  • App (local, idle): pulled to main, read §0 and L139–L148, 38 node and 45 Rust tests passing; no work started, waiting for a task.
  • Explore (local, idle): pulled to main, read §0; waiting.
  • Publish (local, idle): pulled to main, then audited its own crate under §0 and found four calculations that belong in the core:
  • F1's difference curve (diagnostics::difference exists);
  • T1's 2\(\theta\) step, taken as the first gap and wrong for uneven steps (Pattern::mean_step exists);
  • T2's B = 8π²U with its su (no public core function: a request to file);
  • F6's own symmetry, Cartesian conversion, neighbours and hulls, a second copy of geometry::scene. It proposes switching to the core's functions, filing P19 and P20, a guard like nophysics.test.mjs, and the sets rebuilt. It waits for Josu's go.
  • Tutorial (local, idle): the storyboard done (L158); waits for Josu's three answers and the voice.
  • Plots (busy): not audited, as asked.

Requests to pass on: the tutorial's TU1–TU3 and TU5 to the app stream; TU4 (a control file from the material) is the core's drs new (0.26.0) plus the app's dialog (P48), also for the app stream. Publish's P19 and P20, once filed.

git: branches and worktrees · transcripts of the app, Explore, Publish and tutorial sessions (12 October 2026) · L152–L158
L1582026-10-12work

Tutorial stream: the storyboard of the film, 26 scenes, 134 of 173 commands (77 %), three questions for Josu

The tutorial stream's first step (commit a4d93fb, merged into main):

  • tutorial/STORYBOARD.md: seven acts, 26 scenes (31 in the stream's first count, corrected by L161), about 19 minutes. Each scene gives what is seen, the narrator's lines (the subtitles to come), the feature with its source in the conventions, workflows or brief, the registry commands in order, the actions a tour player must do itself, and a check;
  • coverage measured by tutorial/tools/check_storyboard.py: 173 command ids, 134 named in the scenes (77 %), the 39 others listed with their reasons;
  • requests TU1–TU5: the tour player and its caption bar, the out-of-app steps for the screen recording, the recording of the app's own windows, the control file written from the material, stable handles for the tour. The draft's assumptions, until Josu answers: \(\mathrm{Y_{2}O_{3}}\) simulated by the core; the .drc already in the folder; the whole screen recorded (route A), with ⌘⇧5 for the first take. No script and no film until Josu has reviewed it.
commit a4d93fb (branch tutorial) · tutorial/STORYBOARD.md, tutorial/tools/check_storyboard.py, tutorial/REQUESTS.md · L152
L1572026-10-12work

Explore stream after L122: the dictionary's names on every axis and list, refinements by file, a one-cycle path explained (logged late)

Done by the Explore stream on 11 October (commit 6da7644, merged into main on 11 October) and not logged then:

  • every parameter named from the dictionary ("Caglioti V (V)", "Atom position x of O (x)"), with the phase when there are several, and the address on hover;
  • a refinement listed by its file's name, with its title as the subtitle;
  • a run of one cycle that converged at its start says so, instead of drawing an empty path;
  • the module's Rust tests refine from perturbed starts (Y2O3 a = 10.59, zero 0.05; case A's \(\Gamma\)₄⁺ and X₃⁺). Its numbers were moved into the core afterwards by L139 and L148.
commit 6da7644 (branch explore) · app/ui/modules/explore/ · app/src-tauri/src/modules/explore.rs · L122
L1562026-10-12work

Publish stream: drs-publish 0.2.0, T4–T6 and F4–F6 (logged late, at the audit of the streams)

Done by the Publish stream on 11 October (commit 3db5bce, merged into main on 11 October) and not logged then; its own Log: line, written here at L159's audit. On core 0.25.0 (L114: P2–P11 met; the su rounded up, L116):

  • T4: the CIF's distances with su's and multiplicities, the linkage angles at an anion between the elements a set names (the B–O–B′ angles), ⟨d⟩ and bond-valence sums from the core's bonds::valence_sums with the set's charges, without su's (P18 asks for them);
  • T5: the irreps' amplitudes with su's and AMPLIMODES's k, direction, isotropy subgroup and number of modes;
  • T6: each mode's signed amplitude;
  • F4: the cell reduced to the parent's against temperature or composition, with su bars and the phases as bands named by their space groups. \(\mathrm{Sr_{2}ScSbO_{6}}\)'s series cannot be drawn yet (450 and 600 K are coordinate refinements, without a parent cell: P17), so the example is \(\mathrm{La_{2}CoMnO_{6}}\) at 300 K (P2₁/n) and 500 K (R-3);
  • F5: bars for one refinement, points against a series;
  • F6: a mode's displacement on the reference structure, projected, with arrows and polyhedra;
  • T1 prints R_F and the R's without the background (P16 met). Found: Latin Modern Math lacks three of the legend glyphs, which LuaLaTeX drops with only a log line. Every compilation is now refused when its log reports a missing character. 19 tests.
commit 3db5bce (branch publish) · publish/src, publish/tests/build.rs, publish/examples, publish/DESIGN.md §12, publish/REQUESTS.md P15–P18 · L114, L116, L118
L1552026-10-12work

L153, step 1: a refinement started from a data file and a structure (drs new), checked on Y2O3 against FullProf's run

(Logged by the core stream as L154; renumbered L155 at the merge, L154 being the Plots decision.) The core writes a control file from a data file and what is known of each phase, with no previous control file (W2, W3, the tutorial's start). Module start, command drs new.

What the file holds:

  • the data file relative to the control file, the registry's reader, its range;
  • the wavelength the data file states, or one given (--wavelength), or an anode's Kα1 and Kα2 from the core's table at the ratio 0.5 (--anode); without any, drs new refuses rather than guess;
  • one phase per CIF (Rietveld), or per formula, space group and cell (no atoms: Le Bail, every reflection the group allows to 5° past the data, its scale fixed). The cell is given by its free values in the crystal system's order (cubic a; tetragonal a, c; …);
  • the background: arPLS's points (GSAS-II's default smoothness, a point every N/100 of the scan, 101 for Y2O3), in {stem} background.drb, as the file dictionary names it;
  • the widths from the data: W = H\(^{2}\), H the median FWHM of the peaks the data show (24 for Y2O3, H = 0.131°);
  • each Rietveld phase's scale by linear least squares against the data less that background;
  • free: the scale of each phase with atoms and every background point; the rest fixed;
  • every value not given and not read from the data listed with its source (Start::stated) and written as a comment at the head of the file: the polarisation, the peak window and asymmetry limit (the medians of FullProf's files), the profile (TCH, a new phase's), the geometry, the conventions (the papers'), the cycles. Nothing is overwritten.

Tested on Y2O3 (test start). The data with a CIF holding the atoms of FullProf's run (that run does not refine them) and the cell rounded to 10.60 Å; the instrument as the run states it. The file is refined: its own refinement, then zero and cell, then U, V, W, η0, X, then the asymmetry, each to FullProf's Eps 0.2.

  • With FullProf's own 62 background points in its place, it reaches FullProf's minimum: \(\chi^2\) 1.1988 against 1.199, every refined parameter within 1.04 FullProf esd (the bound is 1.2: one esd and the 0.2 esd at which both stop).
  • With its own 101 automatic points, all refined, \(\chi^2\) is 1.1673 (−2.64 %), and five parameters are more than one esd from FullProf's: the scale (+8.39 esd), η0 (+7.48), X (−3.19), W (−2.20), the zero (+1.50). The more flexible background takes up part of the peaks' Lorentzian tails and of their intensity. Frozen in the test (L037), for Josu to accept: the difference is the background's, as the run with FullProf's points shows.
  • The checks (drs check) find no error in the file; Le Bail from a formula, "I a -3" and 10.60 refines (20 cycles, \(\chi^2\) \(44.2 \to 1.27\)); drs new writes byte for byte what start::start gives.

A finding on the polarisation. FullProf's manual writes Lp = (1 − K + K·Cthm·cos\(^{2}\) 2\(\theta\))/(2 sin\(^{2}\)\(\theta\) cos \(\theta\)) with Cthm = cos\(^{2}\) 2\(\theta\)_M (eq. 3.26, p. 53). Without a monochromator 2\(\theta\)_M = 0, so Cthm = 1, the polarisation of an unpolarised beam. The parameter dictionary said "0 without monochromator"; 0 leaves out the angle dependence. drs new takes 1 for X-rays without a monochromator, and the dictionary's text is corrected.

Decided and set aside: the file holds no plan. A plan step frees only parameters the file lists, so a plan would have meant listing every parameter and leaving free more than the scale and background. The order used in the test (zero and cell, the widths, the asymmetry) is the user's to write.

core/src/start.rs, core/src/main.rs (drs new), core/tests/start.rs, core/tests/data/start/y2o3.cif · ecosystem/data/parameters.yml (monochromator_polarisation) · manuals/fullprof/FullProf_Manual.pdf p. 53 · manuals/fullprof/runs/Y2O3 · L153
L1542026-10-12decision

R32: a module for data as graphics, the counterpart of WinPLOTR; a Plots stream of its own; the streams in one table

Josu asked for the counterpart of WinPLOTR, better and modern, with more features (R32, added to the brief). It opens every file WinPLOTR opens and CSV, and several files at once: from different sources, experiments or temperatures. Each file is dimmed at will and displaced to separate it, to build the stacked views and projections of a series that his publications show. WinPLOTR's features are to be studied and improved, not copied.

Decided:

  • A stream of its own, so as not to rub against the others: folder plots/, branch plots, worktree ../20261009_diff-ref-str-plots, requests in plots/REQUESTS.md (PL1 …). It owns app/ui/modules/plots/ once its design is accepted.
  • The name proposed: Plots (Josu decides). It is the broadest, and WinPLOTR's heir.
  • Set aside: Compare (its main use, but it undersells one file), Series (too narrow), Overlay and Stack (each one display mode).
  • Boundaries (plots/README.md):
  • Data (R21) stays the one-file workbench;
  • Refine keeps the Rietveld plot;
  • a figure for a paper composed in Plots goes out through Publish, in LaTeX (L103);
  • every offset, normalisation, sum, conversion or map is the core's (L139).
  • The first deliverables before any code:
  • STUDY.md: WinPLOTR's features with their sources, from the FullProf School deck of December 2015 (46 pages), the \(\mathrm{Tb_{2}Pd_{2}Sn}\) temperature series with WinPLOTR's buffer files, FullProfAPP's pages, and WinPLOTR's manual and paper (Roisnel and Rodríguez-Carvajal 2001);
  • DESIGN.md;
  • mock-ups on the \(\mathrm{Tb_{2}Pd_{2}Sn}\) series.
  • The streams in one table in CLAUDE.md (core, app, Explore, Publish, tutorial, Plots): what each owns, its branch, worktree and request prefix, and what the merging session does to keep them in step.
Josu, 12 October 2026 · ecosystem/data/brief.yml R32 · plots/README.md, plots/REQUESTS.md · CLAUDE.md · manuals/fullprof/1-WinPLOTR/
L1532026-10-12decision

The core's next work for the cloud sessions: a refinement from data and a structure, the open requests, then version 2's physics

Josu wants the core taken further on the cloud credit (until 5 November 2026). The roadmap's phases are all marked done; the proposal's version-1 features are met in the core. In order:

1. A refinement started from a data file and a structure, with no previous control file (the case of the tutorial, L152, and of W2/W3: a data file opened, something known of the material). The core writes a .drc from:

  • the data file (its format, its range, its wavelength or one given);
  • one or more structures: a CIF each, or a formula, space group and cell;
  • a starting refinement: scale and background free, the rest fixed, the background automatic (arPLS) kept in its file;
  • defaults said in the file. A command line (drs new), a test on Y2O3 against FullProf's own run, and later the app's dialog (P48). 2. The open requests to the core, each closed by the version that meets it:
  • app 13 (R24 checks beyond the text), 31 (the file dictionary's role in describe()), 32 (FullProf's exports under Exports/FullProf/), and 12, 15, 16 checked against what core 0.2x already does;
  • Explore E7 (the sign convention of mode amplitudes);
  • Publish P15 (U_eq), P17 (the parent cell of a phase refined by coordinates), P18 (bond-valence sums and mean distances in the results file), P9 if open. 3. Version 2's physics, one at a time, each with its FullProf or GSAS-II reference run and the gate rule (L037): P25 anisotropic size and strain, P36 Pawley, P06 joint multi-pattern refinement, P54 sequential and parametric refinement, P55 guided refinement, P05 time of flight, P41 rigid bodies, P30 texture, P23 further background terms. Every step in the core keeps L139: what the app shows is computed here.
Josu, 12 October 2026 · ecosystem/data/proposal.yml · app/REQUESTS.md, explore/REQUESTS.md, publish/REQUESTS.md · L139, L152
L1522026-10-12decision

A tutorial stream: a whole refinement in the app, a script the app plays, recorded as a film

Josu asked for a tutorial of a refinement, recorded as a film of the app in use. It would start from a folder with a data file and what is known of the material, keep the automatic background, run the first cycles from the .drc, and show every feature of the app on the way. It is to be thought through and done in a chat of its own.

Decided:

  • A stream of its own, as Explore and Publish: folder tutorial/, branch tutorial, worktree ../20261009_diff-ref-str-tutorial, requests in tutorial/REQUESTS.md (TU1 …).
  • The proposal is written in tutorial/README.md for Josu to accept or change first:
  • the film script is data (script.yml). Each scene names its narration, the registry's commands and a check;
  • the same script makes an in-app tour (Help ▸ Tour), the film with subtitles (and a voice if wanted), and a deck or a site page of stills;
  • every scene's check runs while it is filmed, so nothing broken is shown;
  • "all the features" is measured against the command registry, as coverage.
  • Three ways to record, from the most automatic: the session records the screen with ffmpeg (needs Screen Recording permission for the terminal, today refused on this Mac); the app records its own windows (no permission needed, the menu bar and Dock not seen); Josu presses ⌘⇧5 and starts the tour.
  • The data: the film is public, so the private FullProf files of manuals/ are not filmed without Josu's word. Options: data Josu may show, a public dataset, or a pattern simulated by the core, said so.
  • Open for Josu: whether the folder already holds the .drc, written from what is known of the material, or the tour writes it with the app.
Josu, 12 October 2026 · tutorial/README.md, tutorial/REQUESTS.md · CLAUDE.md (layout)
L1512026-10-12decision

Josu: a Modules menu in the menu bar, every module with its own actions

Josu proposed a menu-bar entry for the independent modules, Explore first: it follows a refinement, and its actions were in the menu bar only while Explore itself was the active module.

Decided and done (APP_CONVENTIONS §3):

  • A Modules menu, always there, between View and the active module's menu. Each module (Data, Refine, Editor, Explore) is a submenu: Show the module (⌘1 …), then all its actions.
  • Each copy runs its command and is enabled and disabled as the command is. The menu bar keeps the copies in their own list, refreshed with the rest.
  • The active module keeps its own menu beside it, as Xcode keeps its context menus. The toolbar already showed the active module's actions. Set aside: replacing the active module's menu by the Modules menu. That puts every action one level deeper for the module in use.

Checked in the running app (0.12.4, the self-check): the Modules menu holds 84 commands of 4 modules, 12 of them Explore's. With Refine active, "Show the Path of this Refinement" is enabled there. menus.test.mjs guards it. The app's 42 node tests pass.

Josu, 12 October 2026 · app/ui/app.js (buildMenuNow, mirrorItems, refreshEnabled, checkMenu, selftest) · APP_CONVENTIONS.md §3 · app/ui/tests/menus.test.mjs
L1502026-10-12finding

The Mac slowed down with the app open: measured, and two loads cut (the \(\chi^2\) map's processors, the 3D view's GPU)

Josu: with the app open the Mac seems stuck. Measured on his MacBook Pro (Intel i9-9880H, 8 cores and 16 logical processors, 16 GB, Intel UHD 630 and AMD Radeon Pro 5500M with automatic switching).

Measured, and not the cause:

  • idle with a Y2O3 document open: the app's process 70 MB, its window (WebKit) 101 MB, 0 % CPU after start-up;
  • during and after a refinement through the self-check: 86 MB and 127 MB; the CPU back to 0 when the run ends;
  • the window has no animation loop or timer: it redraws only on change and on resize.

Two loads found in the code:

  • Explore's \(\chi^2\) map computed as many patterns at once as the Mac has logical processors (16), at normal priority, so for the whole map nothing else ran well.
  • The 3D structure view created a WebGL context with the default power preference, which on a two-GPU Mac switches to the discrete GPU (heat, fans, pauses while it switches). It kept the context, and an extra copy of each frame (preserveDrawingBuffer), until the tab was closed, even with another view shown.

Changed:

  • The core's parallel work takes half the logical processors (lsq::worker_threads: 8 here).
  • The app runs the map in a thread of its own at macOS's "utility" QoS. The workers inherit it, so the system serves the user first.
  • Measured with a_grids_load_when_asked (\(41 \times 41\) points of Y2O3): 740–800 % CPU (8 processors) and about 11 MB, 1681 points in 24.6 s.
  • The 3D view asks for the low-power GPU and keeps no copy of the frame. It lets its context go on leaving the Structure view (made again on return, from the scene already in hand), and is made once however many redraws ask for it.
  • The rule is in APP_CONVENTIONS §9 and app/ui/tests/load.test.mjs.

Not found here: the Mac had swapped before the app ran (529 151 pageouts since start-up), and these sessions' own builds, tests and renders (cargo on all cores, headless Chrome, Quarto) also load the same Mac. Which of the app's views Josu had open when it stalled is to be told by him: if it was neither the \(\chi^2\) map nor the structure, the next step is to measure that view.

ps and top on the running app (0.12.2) · core/src/lsq.rs (worker_threads) · app/src-tauri/src/modules/explore.rs (in_background), Cargo.toml (libc) · app/ui/modules/explore/structure.js, index.js · APP_CONVENTIONS.md §9 · app/ui/tests/load.test.mjs
L1492026-10-12work

Josu: every bar button labelled, thin line icons, and a drag where two dividers meet moves both

Three requirements from Josu, written into APP_CONVENTIONS §1 and §2.

Labels. The module buttons at the top of the activity bar (Data, Refine, Editor, Explore) had icons only. Each now has its name under the icon, as the bottom ones had. The bar is 60 px wide (was 52) so that "What's New" fits whole.

Thin icons. The module icons keep their shapes, drawn at stroke 1.1 on their 20-unit grid (was 1.6; about 1.2 px on screen against 1.85). The bottom ones were text glyphs, ★ filled. They are now line icons at the same stroke (icons.js, BAR): a half-filled circle, sun or crescent for the appearance, a cog, a four-point sparkle, an open book, a circled question mark.

Crossing dividers. Where a vertical and a horizontal divider meet, at a crossing or a T, one drag moves both, as in VS Code and TRAE. Within 8 px of both dividers the pointer shows the move cursor. A press there starts dockview's own drag of each divider; dockview follows the pointer from the document, so one movement drives both. Checked with real (trusted) mouse events through Playwright: a drag of (+60, −50) px from the T where the bottom panel's divider meets the sidebar's widened the sidebar by 60 px and raised the bottom panel by 50 px. A drag on the sidebar's divider away from the T moved the sidebar only (−30 px).

Tested in activitybar.test.mjs: each module's name under its icon, the 1.1 stroke of the bar and of every module icon, no text glyph left in the bar, the crossings installed. The app's 39 node tests pass.

Josu, 12 October 2026 · APP_CONVENTIONS.md §1, §2 · app/ui/icons.js (BAR, BAR_STROKE), app.js (renderActivity), index.html, layout.js (installSashCorners), style.css, modules/*/index.js (icons) · app/ui/tests/activitybar.test.mjs
L1482026-10-12work

L140's calculations, last batch: Explore shows the core's numbers; L140 is done

The calculations L140 found in Explore's window are now the core's:

  • a reflection's profile on its background: diagnostics::on_background, sent with each state;
  • the 2\(\theta\) range where H_G\(^{2}\) < 0 at some state (hatched in the widths view): state::negative_gaussian_range;
  • the default reflection, the strongest: diagnostics::strongest;
  • the default pair, the most strongly correlated with the background points left out: lsq::strongest_pair, sent with the result;
  • the \(\chi^2\) map's colour bar, its smallest, geometric middle and largest \(\chi^2\): lsq::chi2_range, sent with the grid;
  • the structure's morph r = r\(^{0}\) + tΣu over the chosen irreps, the arrows' displacements and their lengths in Å: modes::morph, through explore_morph. Outside the app a mode phase shows the scene's own positions (t = 1). The \(\chi^2\) map's "about … s per point" was the window's clock averaged over the cycles and halved, a guess. It is removed rather than moved: the map reports its own time after Compute.

Tested:

  • the core's a_profile_on_its_background_and_the_strongest_reflection, the_widths_sampling, options_correlations_and_half_widths and a_morph_between_reference_and_distorted;
  • the app's Explore tests on the Y2O3 and case A runs: the totals, the strongest reflection, the H_G\(^{2}\) range and the \(\chi^2\) range against the core's functions;
  • the morph at t = 1 with every irrep lands on the scene's own positions of case A within \(10^{-6}\) Å;
  • nophysics.test.mjs: none of these in Explore's window. Headless, Explore's default pair is Caglioti V and W (r = −0.871), the core's.

With L145–L148 every calculation L140 listed, borderline ones included, is the core's. The node guard (nophysics.test.mjs, six tests) fails if any of them is written in the app again.

core/src/diagnostics.rs, state.rs, lsq.rs, modes.rs · app/src-tauri/src/modules/explore.rs, refine.rs, main.rs · app/ui/modules/explore/peaks.js, map.js, index.js, structure.js · app/ui/tests/nophysics.test.mjs · L140, L145–L147
L1472026-10-12work

L140's calculations, third batch: Data shows the core's numbers

The calculations L140 found in Data's window are now the core's:

  • the largest count and the prominence proposed for Find Peaks (a fiftieth of it, at least 1): Pattern::max_count and data::proposed_prominence, sent with the scan;
  • the selection's x range: data::extent, through data_extent;
  • the point a line of the file's text is about (Open as Text, W6): data::point_of_line, through data_point_of_line. It reads the line's first number (FORTRAN's D exponent too) and takes the nearest point within half a step. The window had parsed and searched itself.

Tested:

  • a_patterns_facts (core): the proposal, the extent, a line's point, the D exponent, a line too far, a comment;
  • the app's data test: the Y2O3 scan's largest count and proposal;
  • nophysics.test.mjs: none of these in Data's window. Headless: Find Peaks proposes 225 for the recorded Y2O3 data, whose highest count is 11248. The core's library tests, the app's 45 Rust and 37 node tests pass.
core/src/data.rs (proposed_prominence, extent, point_of_line) · app/src-tauri/src/modules/data.rs, main.rs · app/ui/modules/data/index.js · app/ui/tests/nophysics.test.mjs · L140
L1462026-10-12work

L140's calculations, second batch: Refine's window computes none of its numbers

The calculations L140 found in Refine's window, the borderline ones included, are now the core's. The app's Rust side sends them with the pattern, every cycle, the "who is this peak" card and the restraints:

  • Yobs − Ycal for the difference strip: diagnostics::difference, with the pattern and with each cycle;
  • the cumulative \(\chi^2\) on the plot's counts scale: Cumulative::on_counts, the fraction times the largest of Yobs and Ycal;
  • the shares at a point in per cent: Shares::in_percent;
  • the mean time per cycle: lsq::mean_seconds, sent with each cycle;
  • the restraints' (value − target)/\(\sigma\): RestraintState::deviation;
  • the heat map's outlined cells: the core's strong correlations (L145), sent as cells. The \(\chi^2\) sparkline now labels the first and the last \(\chi^2\) as the core gave them, not a maximum the window took.

Tested:

  • the_difference_the_cumulative_on_counts_and_shares_in_percent and a_restraints_deviation (core);
  • the app's refine test: each cycle's difference against diagnostics::difference of Yobs and that cycle's Ycal; the seconds per cycle against the cycles' own times; the pattern's difference and cumulative; every strong cell above 0.8;
  • nophysics.test.mjs: none of these written in Refine's window. The recorded run, rendered headless, draws the difference strip and the heat map, and says "0.29 s per cycle" and "\(\chi^2\) \(40.441 \to 1.198\)". The core's 197, the app's 45 Rust and 36 node tests pass.
core/src/diagnostics.rs (difference, Cumulative::on_counts, Shares::in_percent), lsq.rs (mean_seconds, RestraintState::deviation) · app/src-tauri/src/modules/refine.rs · app/ui/modules/refine/index.js, refine.js, outline.js · app/ui/tests/nophysics.test.mjs · L140, L145
L1452026-10-12work

L140's calculations, first batch: the app's Rust side computes nothing; nine moved into the core

The calculations L140 found in the app's Rust side (refine.rs, project.rs, explore.rs, background.rs), the borderline ones included, are now the core's. The app calls them and shows what they return:

  • the strong correlations: lsq::strong_correlations, above STRONG_CORRELATION = 0.8, the strongest first. The result also carries the strong cells, for the heat map's outlines;
  • "who is this peak": diagnostics::nearest_point and diagnostics::reaches, within 1.5 FWHM, or 0.05° without a width;
  • the data's facts: Pattern::mean_step, Pattern::max_count and Pattern::sigma_is_counting (the outline's "Step" and "\(\sigma\): √counts", the project card's "Step" and "Counts up to");
  • the points a pattern is calculated at without data: data::grid;
  • the run's defaults: lsq::Options::stated with DEFAULT_CYCLES = 10 and DEFAULT_EPS = 0.1, used in the three places that had them written in;
  • a value against its limits: refinable::LimitState, for "at its limit" and "outside its limits";
  • the \(\chi^2\) map's half-widths in the parameters' units: lsq::half_widths;
  • the widths' sampling for the peak-shape view: state::width_sampling, 120 steps of at least 0.1°;
  • a background function's range or origin from the data: background::chebyshev_over and polynomial_about. Each has its test in the core (six new tests). nophysics.test.mjs now also reads the app's Rust sources and fails if any of these calculations is written there again; it finds two of them in the previous refine.rs. The core's 195, the app's 45 Rust and 35 node tests pass.
core/src/data.rs, lsq.rs, diagnostics.rs, refinable.rs, state.rs, background.rs · app/src-tauri/src/modules/refine.rs, explore.rs, background.rs, project.rs · app/ui/tests/nophysics.test.mjs · L139, L140
L1442026-10-12work

Item 7 of L125's second group, §1: tabs split side by side or one above the other, and moved or dragged into a window of their own; the running app checked by itself

APP_CONVENTIONS §1: views in the main area are tabs that can be split side by side or one above the other (View ▸ Split) and dragged out into a window of their own. Dragging tabs between groups was already dockview's; the rest was missing (View ▸ Split was greyed "later").

  • View ▸ Split Right (⌃⌘→) and Split Down (⌃⌘↓) move the active tab into a new group beside or below its own. They are enabled when the group holds more than one tab. Checked headless: a Refine tab and a text file in one group (637 px wide) become two groups of 334 px side by side, or two of 395 px one above the other.
  • Window ▸ Move Tab to New Window opens a window of its own for the tab's file, with its project.
  • A Refine document moves as it is: the core keeps it open (its undo history, its unsaved edits) and the new window adopts it.
  • Another file is opened there from the disk.
  • A tab does not move while its refinement runs, while a result waits for Accept or Discard, or while a text file has unsaved changes: each says why.
  • A tab dragged outside the window does the same: a drag of a file tab that ends outside the window's frame. Checked headless with synthetic drags (a drop inside does nothing, a drop outside moves the tab). The real gesture is to be confirmed by Josu.

The running app checked by itself. Screen capture and UI scripting are not allowed on this Mac. The app gains a self-check, a test hook like DRS_APP_DEMO: with DRS_APP_SELFTEST=<file> and a project given by DRS_APP_OPEN, its windows drive their own commands and write what happened. Run on a copy of the Y2O3 run with app 0.12.0:

  • the Dock's progress was set at each cycle (7 %, 13 %) and cleared at the end, each call Ok;
  • with no window in front, the notification "Refinement finished" was sent (Ok). Whether macOS shows it for an unsigned app is to be seen by Josu;
  • Accept kept version 1 and its results file. The trend read a = 10.604965 with su 3.67·\(10^{-5}\) from them (L141);
  • a note on 222 was written to y2o3_structure notes.toml and read back (L142);
  • Move Tab to New Window: the tab left the window, and the new window adopted document 1 from the core, saved, with its project. Not seen: the trend panel's drawing, the notification on screen, the real drag-out gesture.

The app's 34 node and 45 Rust tests pass; the core's 189 pass.

app/ui/layout.js (canSplit, splitActive, onTabDraggedOut), app.js (view.splitright, view.splitdown, window.movetab, moveTabToWindow, openTabWindow, selftest), modules/refine/index.js (canMove, adopt), modules/editor/index.js (canMove) · app/src-tauri/src/windows.rs, main.rs (window_open's query, selftest_on, selftest_note) · APP_CONVENTIONS.md §1 · L125
L1432026-10-12work

Item 7 of L125's second group, §9 and §2: the run's progress in the Dock, a notification when it ends, Help in the bar

APP_CONVENTIONS §9 asks for a refinement's progress in the Dock icon and a system notification when it ends while the app is in the background.

  • Each cycle sets the Dock's progress bar to the share of the cycles asked for that have run: the core's lsq::fraction_done, so the app computes nothing (L139). The bar is cleared when the run ends or pauses.
  • When the run ends, pauses or fails and no window of the app is in front, a notification (tauri-plugin-notification) says so. It gives the core's reason for the stop, \(\chi^2\) and the cycles, and names the window.

§2 asks for Help at the bottom of the activity bar, "search and keyboard shortcuts". The bar now has Help under Manual. It opens a menu beside the button: Search and Commands… (⌘K), Keyboard Shortcuts, the Manual, Report an Issue.

Tested:

  • the_share_of_a_run_done (core);
  • app/ui/tests/activitybar.test.mjs: the five items of §2 in order, and Help opening search and the shortcuts;
  • Help's menu rendered (headless). The Dock and the notification show only in the running app; they are checked in the next build.
core/src/lsq.rs (fraction_done) · app/src-tauri/src/main.rs (refine_run, notify_if_away), Cargo.toml (tauri-plugin-notification 2), capabilities/default.json · app/ui/index.html, app.js · app/ui/tests/activitybar.test.mjs · APP_CONVENTIONS.md §2, §9 · L125
L1422026-10-12work

Item 7 of L125's second group, W8: notes on reflections, kept beside the control file and in the results file

W8: a note on a reflection ("impurity", "not in the model") is scientific content. It is kept in the visible file the file dictionary names, {control} notes.toml (reflection_notes, decided in L099 but never written), and carried into the results file.

  • The core gains notes: a note on a reflection (phase, h k l) or at a 2\(\theta\), with its text; read and written as TOML; set on a reflection, or removed with an empty text.
  • results::with_notes puts the notes as a loop in the results file's pattern block, beside the reflections, each on one line. results::read reads them back.
  • drs results carries the notes file beside the control file into the results file on its own; drs notes <control> lists the notes.
  • The app reads and sets notes through the core (refine_notes, refine_note_set; never into a reference folder) and adds them to every results file it writes.
  • The Reflections view has a "note" column and Refine ▸ Note on the Reflection… for the selected row.

Tested:

  • notes_written_and_read_back (core);
  • tests/notes.rs: notes beside a Y2O3 control file, drs results, the notes read back with the reflections, a line break kept to one line, drs notes, no loop without a notes file;
  • notes_on_reflections_are_kept_beside_the_control_file (app). The Reflections view renders with its note column (headless, 34 rows); the notes themselves are read in the app.
core/src/notes.rs, results.rs (with_notes, read), main.rs (drs notes, drs results), core/tests/notes.rs · ecosystem/data/files.yml (reflection_notes) · app/src-tauri/src/modules/refine.rs, main.rs · app/ui/modules/refine/index.js · APP_WORKFLOWS.md W8 · L099, L125
L1412026-10-12work

Item 7 of L125's second group, W7: a parameter across the versions, as a trend

W7 asks for "the same parameter across the versions as a trend" after a refinement. Each accepted refinement is a version: the control file kept in Versions/, and the results file the core wrote for it, named by the file dictionary with the version's time.

  • The core gains trend::trend. For each version it reads the parameter's value from the control file (the model it holds) and its su from that version's results file (results::read).
  • A version that lacks the parameter is an error at that version only.
  • The command line: drs trend <address> <control[:results]>….
  • The app's versions_trend lists the versions, finds each one's results file beside the control file, and asks the core for the trend.
  • Refine's bottom panel has "Across versions": a free parameter picked by its name, one point per version, filled where that version refined it, its su as a bar. Each version is listed with its time, value(su) and \(\chi^2\).
  • The trend is read again when a version is kept.

Tested:

  • tests/trend.rs (core): two versions of Y2O3, the second refined by drs results (3 cycles); values, su from the results file, the error at a version, and the command line;
  • a_parameter_across_the_versions (app): the window's Accept flow (a version, 4 cycles accepted and saved, a version, its results file), then both values and the su against the document's. The panel's drawing runs only in the app; it is checked in the running app (the next build).
core/src/trend.rs, core/src/main.rs (drs trend), core/tests/trend.rs · app/src-tauri/src/versions.rs (trend), main.rs (versions_trend), modules/refine.rs (test) · app/ui/modules/refine/refine.js, index.js · APP_WORKFLOWS.md W7 · L125
L1402026-10-12finding

The calculations still in the app, by L139's rule: 26 to move into the core, 9 more borderline

A read-only audit of app/ui and the app's Rust side after L139 (line numbers as of commit after L139). Each is to move into the core; none is fixed yet unless marked.

Explore

  • the morph's positions r = r\(^{0}\) + tΣu and the displacement sums (structure.js), shown as "at x, y, z Å";
  • a reflection's profile plus the background (peaks.js), drawn and setting the range;
  • the 2\(\theta\) range where H_G\(^{2}\) < 0, shown as "hatched a–b°" (peaks.js);
  • the seconds per point of the \(\chi^2\) map, averaged from the window's own clock (index.js);
  • borderline: the \(\chi^2\) range of the colour bar, the default reflection (the largest I_calc), the default pair (the largest |r|).

Refine

  • Yobs − Ycal for the difference strip (index.js);
  • the seconds per cycle, averaged in the window (refine.js);
  • the sparkline's largest \(\chi^2\) (refine.js);
  • the restraints' (value − target)/\(\sigma\) (outline.js);
  • borderline: the cumulative \(\chi^2\) scaled to the observed maximum; the shares as percentages; the |r| > 0.8 outline.

Data

  • the highest count and the proposed prominence (a fiftieth of it);
  • the selection's x range.

The app's Rust side

  • the correlations above 0.8, filtered and sorted (refine.rs);
  • "who is this peak": the nearest point and the reflections within 1.5 FWHM (refine.rs);
  • the data step (x_last − \(x_0\))/(n − 1), in refine.rs and project.rs;
  • whether \(\sigma\) is √counts or the file's (refine.rs);
  • the calculation grid from FullProf's range when there is no data (refine.rs);
  • "Counts up to", a maximum (project.rs);
  • the \(\chi^2\) map's span in esd's turned into the parameters' units (explore.rs);
  • borderline: the at/outside-limits tests, the default cycles and convergence, the width-sampling step, the default Chebyshev range and polynomial origin (background.rs).

Moved by L139: the traces' scaling, shift/esd in Explore and Refine, the map's span, the cycles' largest shift. The rest is the next group of fixes, one at a time, each with a guard in nophysics.test.mjs.

audit of app/ui and app/src-tauri/src after L139 · APP_CONVENTIONS.md §0
L1392026-10-12decision

Josu: every calculation is the core's, a must; the app only shows. Explore's path in esd units moved to the core

L138 kept the traces' (q − q_end)/\(\sigma\)_end in the window as "a unit of the drawing". Josu: "I want it in the core; I want any calculation in the core, the app only shows, this should be a must!!"

The rule is written where every session reads it:

  • APP_CONVENTIONS.md §0: every number the app shows that is derived from data, a model or a result is computed by the core, tested there, and sent ready to show. The window only turns numbers into pixels and text. A calculation found in the window is a fault.
  • CLAUDE.md's description of app/. Set aside: any reading of a computation as "just display"; L138's decision is withdrawn.

Moved to the core now:

  • lsq::Trajectory::in_esd (PathInEsd): for the start and each cycle, each parameter's (value − end)/\(\sigma\)_end and the cycle's shift/esd; each parameter's reach along the path; the end's values and esd's.
  • lsq::pair_span: the \(\chi^2\) map's span for "the path".
  • lsq::Cycle::largest_shift: the cycles' table's largest |shift|/esd. The app's Rust side had recomputed it. The app's Rust side sends the path so far with every cycle event, and the whole path with the run's final esd's with the result. Each parameter's tuple carries the core's shift/esd. The window looks these up for Explore's traces, its table's "shift / esd" and "(value − end)/esd_end" columns, the inspector's path, the map's span (explore_pair_span), and Refine's path caption and steering header.

A fault found on the way: the window took a run's start from the document's values. After the run those are the refined values, so every trace started at 0. The core's start is the model the run started from: in the recorded Y2O3 run, cell a starts 407.6 \(\sigma\) from its end.

Tested:

  • the_path_in_units_of_the_esd_at_its_end (core, three cycles of case A): every distance against the cycles' own numbers, the end at 0, the given esd's, pair_span, largest_shift;
  • the app's refine test: the events' paths and the result's;
  • nophysics.test.mjs: no (q − q_end)/\(\sigma\), shift/esd or span computed in Explore or Refine. The recorded run's cell a at cycle 1 is −462.003 \(\sigma\) from the core, the same as the old formula. The core's 185, the app's 43 Rust and 32 node tests pass; the app bundles.
Josu, 12 October 2026 · APP_CONVENTIONS.md §0 · CLAUDE.md · core/src/lsq.rs (PathInEsd, Trajectory::in_esd, pair_span, Cycle::largest_shift) · core/tests/explore_requests.rs · app/src-tauri/src/modules/refine.rs, explore.rs · app/ui/modules/explore/draw.js, index.js, refine/refine.js, outline.js · app/ui/tests/nophysics.test.mjs · L138
L1382026-10-12work

Item 6 of L125's second group: Data's d-spacings and Explore's covariance ellipse come from the core; the traces' scaling stays a unit of the drawing

L125 found physics computed in the window, which the app must not do.

Data's d-spacings. The inspector computed each peak's d by Bragg's law in JavaScript. It also ignored a wavelength taken from a control file (W7), so such a file showed no d-spacings.

  • The core gains data_ops::to_axis(from, to, x, λ): one value converted between 2\(\theta\), d and Q. The scan conversion convert now uses it, so there is one rule.
  • The app's peak search returns each peak's d from it, with the scan's λ or the one taken from a control file. Taking another λ searches again.
  • Tested: one_value_converted_by_braggs_law (core): Cu Kα₁ at 2\(\theta\) = 28.60° is d = 3.1186 Å, Q and back, no λ is an error, sin \(\theta\) > 1 has no image. In peaks_and_background_come_from_the_core (app), Y2O3's 222 peak has no d without λ, and with λ = 1.5406 Å its d is λ/(2 sin \(\theta\)) = a/√12 within 0.005 Å.

Explore's covariance ellipse. The pair view and the \(\chi^2\) map found the ellipse's axes and angle in JavaScript, from the \(2 \times 2\) covariance.

  • The core gains lsq::covariance_ellipse(σa, σb, r): the eigenvalues λ₁ \(\ge\) λ₂ of C = [[σa\(^{2}\), rσaσb], [rσaσb, σb\(^{2}\)]] and the angle of the first axis, the same closed form.
  • Explore asks for it through explore_ellipse, once per run and pair, and draws what comes back. The figure export draws it too.
  • In a browser, without the app, no ellipse is drawn.
  • Tested: the_covariance_ellipse_of_a_pair (core: no correlation; r = \(\pm\)0.9 at \(\pm\)45°, the angle taken modulo π; λ₁λ₂ = det C) and the_pairs_ellipse_is_the_cores (app, the recorded run's Caglioti V and W, r = −0.871). The pair view drawn with that ellipse shows the 1\(\sigma\) and 2\(\sigma\) contours leaning with the negative correlation; given no ellipse, it draws none.

app/ui/tests/nophysics.test.mjs keeps both out of the window: no Bragg's law in Data, no eigen-decomposition in Explore. It fails on the previous files. The core's 183 tests (184 with the new one), the app's 43 Rust and 31 node tests pass; the app bundles.

Decision: the traces' (q − q_end)/\(\sigma\)_end stays in the window. It divides the core's own numbers (a value, its value at the end, its esd at the end, all from the cycle events) to put every parameter on one axis. It makes no quantity of its own, and is of a kind with the plot's √ and log axes, which the window also draws.

  • Set aside: a core function sending each trace already scaled with every cycle. It doubles what every cycle event carries, for one subtraction and one division.
  • Josu can overrule this: the move is small.
core/src/data_ops.rs (to_axis), core/src/lsq.rs (covariance_ellipse, Ellipse) · app/src-tauri/src/modules/data.rs, explore.rs, main.rs · app/ui/modules/data/index.js, explore/draw.js, explore/index.js · app/ui/tests/nophysics.test.mjs · L125
L1372026-10-12work

Item 5 of L125's second group: Explore's controls wrap onto a bar of their own instead of running past the view's edge

Explore put its controls on the line of its six view tabs: the parameter selectors, the \(\chi^2\) map's span and Compute, the reflection, the structure's phase and arrows, the cycle slider and the title. That line cannot wrap.

Measured in headless Chrome with a recorded Y2O3 run, as how far the line's content ran past the view's right edge:

  • in a 1500 px window: A pair 438 px, \(\chi^2\) map 605 px, Structure 154 px, Peak shape 65 px;
  • in a 1280 px window: A pair 543 px, \(\chi^2\) map 710 px, Structure 259 px, Peak shape 170 px.

Now the tabs keep their line, and the controls have a bar of their own under them, which wraps onto further lines when the view is narrow:

  • a selector is at most 200 px wide, with a long name cut short in it and whole in its list;
  • the title takes what room is left and ends in "…". Measured again at 1500, 1280 and 1024 px, in all six views: nothing past the edge (at 1024 px the \(\chi^2\) map's bar takes four lines).

Also fixed in Explore's list of runs: the cycle count ran into the control file's title ("…PCR file)8 cycles"). It is now on its own line.

Tested: app/ui/tests/explore_layout.test.mjs (only the tabs on the tabs' line; every control and the title in the bar; the bar wraps; selectors capped). The app's 29 node tests pass, and it bundles.

app/ui/modules/explore/index.js · app/ui/style.css (.ex-controls) · app/ui/tests/explore_layout.test.mjs · L125
L1362026-10-12work

Item 4 of L125's second group: Refine names parameters by the dictionary, not by their address

The core names each parameter by its address in the model ("phase1.cell_a", "pattern1.background_points.12", "phase1.Y.atom_position.x"). Refine's bottom panel printed those addresses as they came.

Now each address has a name for the window, built by the app's Rust side from the core's own data:

  • field_of finds the value the address names, dictionary_id gives its dictionary entry, and the dictionary gives the name;
  • then come the component and the place: "Cell a", "Atom position x (Y)", "Bérar–Baldinozzi asymmetry P2", "Background at 15.48°" (a background point by its 2\(\theta\), not its index);
  • the phase is added when the model has several;
  • a name that is no address (a variable of the user's) is kept. The names go with every cycle, every result and the steering view (labels).

The window shows them everywhere a parameter appeared by its address:

  • the Parameters list;
  • the correlations and the correlation map's pointer;
  • the cycles' largest shift;
  • the parameter paths;
  • the combination a singular matrix cannot fix;
  • the Free parameters tab;
  • the steering inspector, its links and variables. The core's sentences (steering notes, why a run stopped) are put in the same words, the longest address first. The address stays the parameter's identity (fix, free, the control file) and shows on hover. In the panel the names are in the text font, on one line, and a long name gives way to the value(esd), which is never cut.

Tested:

  • refine_accept_discard_and_stop: the names of a Y2O3 run (Cell a, Zero shift, P2, a background point, the atoms Y:2 and O), no address left in any of them, and the same names on the cycles;
  • the_cores_sentences_are_put_in_words;
  • import_writes_the_control_file_and_opens_it: no outline label is an address;
  • app/ui/tests/labels.test.mjs: labelOf, and that refine.js and outline.js print an address only on hover (the check finds 7 bare addresses in the previous refine.js). A recorded run, rendered in headless Chrome, lists "Zero shift 0.0028(3)" and "Background at 15.48° 19.1(4)", and its notes read "Pseudo-Voigt η₀: the step shortened to Pseudo-Voigt η₀'s maximum shift". The app's 42 Rust and 27 node tests pass, and it bundles.
app/src-tauri/src/modules/refine.rs (param_label, param_parts, labels_of, in_words) · app/ui/labels.js · modules/refine/refine.js, outline.js, index.js · style.css · app/ui/tests/labels.test.mjs · L125
L1352026-10-12work

Item 3 of L125's second group: an unnamed phase is proposed as \(\mathrm{Y_{2}O_{3}}\), the core's formula, not "YO"

When a .pcr leaves its phase unnamed (FullProf's "phase 1:"), the app proposes a name (W7). It joined the element symbols in the file's order ("YO"). The comment beside that code said it was waiting for the core's formula (app/REQUESTS.md 22). The core has had that formula since 0.25.0: cif_write::phase_name (P10, L114) names the CIF block, the tables and the file names by it.

Now the app takes the core's name: the formula of the phase's content, Z taken out, electropositive element first (Y2O3, Sr2ScSbO6). It is shown with subscripts and marked as proposed: "\(\mathrm{Y_{2}O_{3}}\) (proposed)". The legend, the phase selector, the R table and the outline's Name row all use it. A phase without a structure is still proposed by its space group. A name the file gives is kept as written.

Tested in import_writes_the_control_file_and_opens_it: the Y2O3 run's phase reads "\(\mathrm{Y_{2}O_{3}}\) (proposed) (I a -3)", where the old code gave "YO (proposed) (I a -3)". The app's 41 Rust tests pass. Request 22 is marked met.

app/src-tauri/src/modules/refine.rs (display_name, subscripts) · core/src/cif_write.rs phase_name · app/REQUESTS.md 22 · L114, L125
L1342026-10-12work

Item 2 of L125's second group: File ▸ Export ▸ Plot writes the plot in view as PDF, SVG or PNG

APP_CONVENTIONS §8 asks that every plot can be exported as PDF, SVG and PNG. Until now only the clipboard copied a PNG of the screen.

Now File ▸ Export ▸ Plot… works in Data, Refine (its plot view) and Explore (every view):

  • The plot draws itself once more, with the same code that draws it on screen, into an SVG recorder (canvas2svg 1.0.16, MIT, vendored by tools/vendor.js). Its legend goes at the top. A module offers this through figure(), and the command is enabled only where a module has one.
  • The figure is always drawn in the light palette, on white, whatever the window's appearance; the window redraws in its own appearance straight after.
  • The app's Rust side (src-tauri/src/figure.rs) writes the SVG as it is, or converts it: to PDF with svg2pdf 0.13, to PNG at 300 dpi with resvg 0.45. Text uses the Mac's system fonts. The save dialog proposes <project>/Exports/<name>.pdf.
  • Every write passes the app's one guard: nothing goes into a reference folder.
  • Explore's figures:
  • traces and pair: with their legend;
  • \(\chi^2\) map;
  • peak shape: its two panels side by side;
  • directions;
  • the 3D structure: as an image.

Four faults found while checking the exports, each fixed:

  • canvas2svg has no dashed lines: they are recorded as stroke-dasharray.
  • Its clip() wrote an empty outline, so Refine's difference strip vanished. The outline is now written first.
  • Its cleared areas were always white: they take the plot's background.
  • A series kept the colour it was created with. In a dark window Ycal was white and did not show on paper. A series now names its colour ("--calc"), and the plot looks it up each time it draws. All 19 colours in Data and Refine were changed.

Checked:

  • Data, Refine, Explore's traces and pair were exported from a window in dark mode (headless Chrome), then turned into PDF and PNG by the app's own code and looked at: every curve, the difference strip and the legend show on white.
  • figure.rs tests: SVG written as is; a %PDF file; a \(400 \times 300\) figure at 300 dpi is a \(1250 \times 938\) PNG; .jpg refused; nothing written into a reference folder.
  • The app's 41 Rust and 24 node tests pass, and it bundles.

Correction to L125. L125 says tools/vendor.js exits 0 when a package is missing. It exits 1. The failure was hidden by the command I ran the build through (its output piped into grep, which reported the pipe's status, not the build's). The rule stays: after every build, the bundle's date and version are checked.

app/ui/svgexport.js, plot.js, app.js, modules/{data,refine,explore}/index.js · app/src-tauri/src/figure.rs, main.rs, Cargo.toml (svg2pdf 0.13, resvg 0.45) · app/tools/vendor.js, package.json (canvas2svg 1.0.16) · APP_CONVENTIONS.md §8 · L125
L1332026-10-12work

Item 1 of L125's second group: one tick rule for every plot; √ and log axes with round labels; Explore's cycle axis thinned

Three faults with one cause, two tick rules:

  • the √ and log axes placed their ticks in transformed units and printed the back-transformed values with three decimals ("2500.000");
  • Explore had its own copy of the tick function, which had drifted from the shared plot's;
  • Explore's cycle axis labelled every cycle.

Now there is one rule, app/ui/ticks.js, used by the shared plot (Data, Refine) and by Explore's views (traces, pair, \(\chi^2\) map, peak shape):

  • axisTicks: the linear axis, with the common factor and offset in the axis label and ticks thinned by measured width;
  • sqrtTicks: round counts (0, 2000, 4000…) placed on the √ scale, thinned where they crowd;
  • logTicks: decades, with 2× and 5× when fewer than three show, and 10ⁿ for long numbers;
  • labelStride: Explore's cycle axis labelled every k-th cycle, always including the last. Tested in app/ui/tests/ticks.test.mjs (round √ labels at least 26 px apart, decades, factor and offset, no touching labels, the cycle stride). Rendered: Data's plot on its √ axis reads 0 … 12000 and on its log axis 10 … 10000. The app's 24 node tests pass and it bundles.
app/ui/ticks.js, plot.js, modules/explore/draw.js · app/ui/tests/ticks.test.mjs · APP_CONVENTIONS.md §8 · L119, L125
L1322026-10-12work

App 0.11.3 installed in /Applications as the one copy to open; two copies labelled 0.11.2 had confused Josu

Josu, after L131: "what is the app version I am working on? I thought you had fixed all these." His Finder showed the app stream's bundle (../20261009_diff-ref-str-app/…, built 11 October 18:09, 0.11.2), which has none of L126–L131. The fixed bundle on main (12 October 06:26) was also labelled 0.11.2, because the fixes did not change the version.

Now:

  • the fixes are app 0.11.3;
  • the bundle built from main is copied to /Applications/diff-ref-struct.app, the one copy Josu opens (the previous one goes to the Bin);
  • every build given to Josu gets its own version number and is installed there after its tests pass. The app's 38 Rust and 19 node tests pass on 0.11.3.
app/package.json, src-tauri/Cargo.toml, tauri.conf.json (0.11.3) · /Applications/diff-ref-struct.app · L126–L131
L1312026-10-12work

A converged run says how far it got: "Cycles at most", "Converged after 2 of 15 cycles", and Convergence 0 to run them all

Josu, trying the build of L130 on a copy of Y2O3: "Pause every is off and still it has run only 2 cycles instead of 15". The run had done what the control file asked: Cycles is the maximum, and the run stops earlier when it converges, every shift below Convergence × its esd (0.2). Y2O3's tutorial file starts at its minimum, so it converged after 2 cycles. FullProf does the same (NCY the maximum, Eps stopping earlier). But the window did not make it plain:

  • the toolbar field is now "Cycles at most";
  • Convergence's tooltip says the run stops before its last cycle when every shift is below this many esd's, and that 0 runs all the cycles (the core never counts a run as converged with \(\varepsilon\) = 0, lsq.rs);
  • the message of a converged run says "Converged after 2 of 15 cycles: every shift below 0.2 esd. Cycles is a maximum: set Convergence to 0 to run all of them." The window gives the result the run's maximum. The stop messages are now a pure module, app/ui/stops.js (plainStop), tested word for word in app/ui/tests/stops.test.mjs. The app's 19 node tests and 38 Rust tests pass.
Josu's screenshot of 12 October (app 0.11.2, Y2O3 copy 3) · app/ui/stops.js, modules/refine/index.js, refine.js · app/ui/tests/stops.test.mjs · core/src/lsq.rs (the convergence test) · W11
L1302026-10-12work

Fix 5 of L125: a Continue no longer asks again about warnings already accepted in the run

Each Continue of a paused run went through the same checks as a fresh Run and asked the warnings dialog again, so "Pause every k cycles" with a warning brought back a stop-and-approve at every checkpoint, which is what Josu rejected in L119. The core's checks still run at each Continue (the file can change during a pause), and errors still stop a run.

The warnings to ask about are now one pure function, app/ui/runcheck.js (warningsToAsk):

  • a fresh Run asks about every warning and remembers those accepted;
  • a Continue asks only about warnings not accepted already, and adds them once accepted. A warning is known by its message, since its line moves when the file is edited. Tested in app/ui/tests/runcheck.test.mjs (a fresh Run, a Continue with the same warnings asks nothing, a Continue with a new one asks about that one only, errors are not asked about). The app's 16 node tests pass and the app bundles.
app/ui/runcheck.js, modules/refine/index.js (guardedRun) · app/ui/tests/runcheck.test.mjs · APP_WORKFLOWS.md W11 · L119, L125
L1292026-10-12work

Fix 4 of L125: the control file's Cycles decide a run; Settings' value is only a default for a file that states none

Every run sent Settings ▸ Cycles to the core when it was filled, and the core took it before the control file's [refine] cycles. So a value typed once in Settings silently overrode the Cycles field in the toolbar and in the file. APP_CONVENTIONS §4 makes Settings' refinement values defaults, not overrides.

Now the decision is one pure function, app/ui/cycles.js (runCycles), used by Refine:

  • the control file's Cycles decide;
  • Settings' value (now labelled "Default cycles", with a tooltip saying when it applies) is sent only when the control file states none;
  • a plan's step brings its own;
  • "cycle k of N" shows the same number. Tested in app/ui/tests/cycles.test.mjs: the file's 15 wins over Settings' 5; Settings' 5 applies to a file with none; with neither, 10; a plan's step ignores Settings. The app's 12 node tests pass and the module bundles.
app/ui/cycles.js, modules/refine/refine.js, index.html (Settings) · app/ui/tests/cycles.test.mjs · app/src-tauri/src/modules/refine.rs (cycles.or(r.cycles)) · APP_CONVENTIONS.md §4 · L125
L1282026-10-12work

Fix 3 of L125: no shortcut does two things; the macOS shortcuts keep their meaning; a test guards both

Every shortcut of every command was listed. 57 shortcuts, 9 claimed twice:

  • four harmless: the same action in two modules, where the visible one answers (Fit Plot ⇧⌘0, Open Data File ⇧⌘D, Pick Background Points, and Check ⇧⌘C, whose two titles "Check" and "Check Now" are now one);
  • five real conflicts, a module's command on another command's keys or on a standard macOS shortcut;
  • and ⌘⌫, Discard in Refine, moved out of caution. The standard keys stay, and the modules' commands moved:
  • One Cycle ⌥⌘R → ⌥⇧⌘R (⌥⌘R is Show in Finder);
  • Open as Text ⌥⌘T → ⌃⌘T (⌥⌘T is Show/Hide Toolbar);
  • Select Points ⇧⌘S → ⇧⌘L (⇧⌘S is Save As);
  • Pick Background Points ⇧⌘P → ⌥⌘B, beside Automatic Background ⇧⌘B (⇧⌘P is Page Setup);
  • Fix Selected ⇧⌘X → ⌥⇧⌘F, beside Free Selected ⇧⌘F (⇧⌘X stays Explore);
  • Discard ⌘⌫ → ⇧⌘⌫. The app's texts that named the old keys (Discard, Pick, Explore's "Run or Step (⌥⌘R)", the paused message's "Resume") now name the new ones and the button's word, Continue. W11 and the README's current description too.

The guard: app/ui/tests/shortcuts.test.mjs fails when one shortcut is bound to two different actions, or when a standard shortcut of APP_CONVENTIONS §3 is given another meaning. On the code before the fix it names the six; on the fixed code it passes. The native menu and the browser read each shortcut from the same declaration, so the test covers both. The app's 9 node tests and 38 Rust tests pass.

app/ui/app.js, modules/refine/, modules/data/, modules/editor/, modules/explore/ · app/ui/tests/shortcuts.test.mjs · APP_WORKFLOWS.md W11 · app/README.md · L125
L1272026-10-12work

Fix 2 of L125: Organise as a Project and Undo Organise in the File menu; Not now for this session only; a test that every File, Edit and View command is in its menu

The two commands were registered (⌘K found them) and announced by the README and the status bar, but the native File menu, built on its own in app.js, did not list them. "Not now" on the sidebar's strip saved a flag in the project's state, so the strip never came back. Now:

  • File ▸ Organise as a Project… and File ▸ Undo Organise sit after Import and Export, enabled when they can act;
  • Not now hides the strip for this session only, and opening the project again shows it.

The guard: app/ui/tests/menus.test.mjs fails when a command registered in the File, Edit or View group is missing from that menu of the menu bar (APP_CONVENTIONS §3). On the code before the fix it names file.organise and file.organise.undo; on the fixed code it passes, and Edit and View have no gap.

Driven in a browser (the demo project with an organise plan): the strip is shown at opening, Not now hides it, the commands stay, and the strip is shown again when the project is reopened. The native menu itself is drawn by macOS and was checked through the code that builds it, by the test.

app/ui/app.js (the File menu, the strip) · app/ui/tests/menus.test.mjs · L125
L1262026-10-12work

Fix 1 of L125: Data's range, smoothing, wavelength and file operations wait for their answers; a test guards every dialog question in the app

Data ▸ Mask a Range…, Delete a Range…, Smooth…, Convert to Another Wavelength… and the scale of Subtract, Add and Merge a File… called the app's number dialog without waiting for the answer. Each read the pending answer as "not a number" and returned before the user had typed anything. Rebin and Normalise waited correctly. The five commands now await their answers (app/ui/modules/data/index.js).

The guard: app/ui/tests/dialogs.test.mjs (npm test) reads every script of the window and fails on any call of a dialog helper (ask, askText, askNumber, askForm, pickOne, pickFile, pickFolder, choose, numberPrompt) that is neither awaited, returned nor followed by .then. On the code before the fix it names the 8 calls; on the fixed code it passes. No other such call is left in the app.

Driven as a click in a browser, through the app's command registry with its own dialog answered:

  • Mask a Range (20, 30), Delete a Range (40, 45), Smooth (7, 2) and Convert to Another Wavelength (1.2) ask every question in turn and reach the core's data_set_ops;
  • the browser then stops with "not running inside the app", the expected end outside the window. Before the fix each stopped after its first question. The file operations start with the system's file chooser, which a browser cannot show; they share the fix and the guard.
app/ui/modules/data/index.js · app/ui/tests/dialogs.test.mjs · L125
L1252026-10-12finding

Audit of the app against Josu's requirements: two faults of the merging session, and the app's gaps, claims and broken commands

Josu: "the app is not doing the things as asked, and this is the fourth time". The app was audited against APP_WORKFLOWS.md, APP_CONVENTIONS.md and L119–L124. The code was read, and the screens were rendered in a browser from a recorded refinement of Y2O3 started off its minimum.

The merging session's two faults, the main reason Josu saw old behaviour:

  • Explore's fixes for L119 and L122 (branch explore, 11 October 17:17–17:54) were not merged when app 0.11.2 was merged and built at 19:26: only the branch that reported was merged. Publish's 0.2.0 waited too. Both are merged now (app 38 + 3 tests, Publish 19 pass).
  • The build (npm run build) reported success without building when a dependency was missing (three.js, which Explore added): tools/vendor.js prints "not installed" and exits 0, so the old bundle stayed. Installed and rebuilt today: app 0.11.2 with Explore's second round. From now on, every branch is merged before a build, and a build that does not produce a new bundle is a failure.

Done as asked (rendered): W11's controls (Refine · Pause every k · Pause · Continue · Stop · Cycles · Convergence; "Step n" gone); W13 (the background left out, the table as rows with names from the dictionary, readable ticks); W5's icons, colours and F marks; results not under Data.

Broken:

  • Data ▸ Mask a Range…, Delete a Range…, Smooth…, Convert to Another Wavelength… and Subtract/Add/Merge a File… ask their question without awaiting the answer and return at once (modules/data/index.js), while Rebin and Normalise await it. The 0.10.1 note says the wavelength was fixed.
  • Organise as a Project and Undo Organise are not in the File menu, though the README and the status message say they are; "Not now" hides the strip for good.
  • Shortcut collisions: ⌥⌘R (One Cycle and Show in Finder), ⇧⌘X (Fix Selected and Explore), ⌥⌘T (toolbar and Open as Text), ⇧⌘S (Save Project As and Select Points), ⇧⌘P (Page Setup and Pick Points).
  • Settings ▸ Cycles silently overrides the control file's Cycles.
  • Every Continue asks the warnings again.

Short of what was asked:

  • §8 is not met everywhere: Explore draws its own ticks and its cycle axis labels every cycle; the √ and log axes write "2500.000"; no Export as PDF, SVG or PNG anywhere.
  • Refine's bottom panel names parameters by address.
  • The phase's proposed name is "YO (proposed)", not Y2O3; the inspector heads a document with its title line, not its file name.
  • Explore's control row overflows at a normal width.
  • Physics in the app, which is forbidden: d-spacings in Data's inspector; the covariance ellipse and (q − q_end)/\(\sigma\) in Explore.
  • Still missing: W7's trend across versions, W8's annotations, §1's tabs dragged out and split views, §2's Help icon, §9's Dock progress and notification.
app/ (main after merging explore 6da7644 and publish 3db5bce) · app/ui/modules/data/index.js, refine/, explore/, ui/plot.js, app.js · app/README.md · app/tools/vendor.js · headless renders of a recorded Y2O3 refinement · L119–L124
L1242026-10-11work

App 0.11.2 merged: Josu's four faults of 0.11.1 fixed; W14, FullProf's files organised apart with Undo

App 0.11.2 (branch app) merged into main; its 36 Rust tests and 3 node tests pass, clippy is clean, and the bundle built from main is version 0.11.2. The reference runs in manuals/ are untouched.

  • L122's faults:
  • the status bar's script error came from Explore's tab, which has no file; the status text is now a tested pure function;
  • results files are listed by the file dictionary's role before their content's format (request 31 asks the core's describe() to do the same);
  • our formats and FullProf's have different icons, every FullProf file marked with an F and said on hover;
  • a tab carries its module's icon and name.
  • W14: Organise as a Project lists every step first. FullProf's files move into FullProf/ as they are; the working .pcr is imported as {project}.drc; the data are copied into Data/ and the .irf into Instrument/; a .bac read by Nba = 1 becomes {project} background.drb. A .pcr the core cannot import is said so. Reference folders are refused, .drs/organise.json records the move, and File ▸ Undo Organise puts everything back. The sidebar shows FullProf/ as one closed group. Tests on temporary copies: Y2O3 organised and undone with FullProf's files byte-identical and nothing else written; NdNi-HTO's magnetic .pcr said not importable; a .bac as a .drb; the repository's run refused.
  • Requests to the core: 31 (describe() by the dictionary's role), 32 (exports for FullProf in Exports/FullProf/).
app/ (commit ca214f0 on branch app, merged into main) · app/README.md · app/REQUESTS.md 31–32 · L122, L123
L1232026-10-11decision

W14: FullProf's files kept apart in a FullProf/ folder, the project and everything new the app's, named in our vocabulary

Josu: separate what comes from FullProf from what the app makes. FullProf's files go in a folder of their own, the app has its own, with the starting files it needs taken from FullProf and renamed to our vocabulary; new files are created on the app's side. So everything is separated, nothing collides, and the app's files are in one place.

Decided (W14 of APP_WORKFLOWS.md):

  • the project is the app's, and FullProf's files live in FullProf/ inside it, untouched. Every project then has one layout (R27's), with FullProf/ added for those that came from FullProf;
  • Organise as a Project, offered when a folder holds FullProf's files at its top, lists what it will do: move FullProf's files into FullProf/ as they are, and import and rename what the app needs (the .pcr as the .drc, the data into Data/, the resolution file into Instrument/, a .bac as a .drb). It moves nothing without consent, can be undone, and works on a copy for a reference folder (W9);
  • new files are created on the app's side; FullProf's later outputs stay in FullProf/;
  • the app's exports for FullProf move from FullProf/ to Exports/FullProf/ in the file dictionary. Set aside: two sibling folders (FullProf's and the app's), which would give FullProf-origin projects a layout different from all others.
conversation of 11 October 2026 · APP_WORKFLOWS.md W14 · ecosystem/data/files.yml (the FullProf export kinds) · R27, W9
L1222026-10-11finding

Josu's try of app 0.11.1: a script error, results files listed as data, .drc and .pcr with one icon, Explore's labels by address

Josu opened app 0.11.1 on Y2O3 and refined it (one cycle: the tutorial's file starts at its minimum). Found in his screenshot:

  • an error in the status bar: "promise: null is not an object (evaluating 't.path.split')", something given a file with no path;
  • the results files (y2o3_structure <time> results.cif, pdCIF) listed under Data: their content is a powder CIF, which the registry reads as data, while the file dictionary makes them results;
  • .drc and .pcr with the same icon. Josu asks that our formats and FullProf's be told apart, now in W5;
  • two tabs named "y2o3_structure.drc" (Refine and the Editor) with nothing to tell them apart;
  • Explore's list naming the refinement by the file's title, not its name;
  • Explore's axes and table naming parameters by address (phase1.caglioti_v), not by the dictionary's names and symbols;
  • the table still sideways with the background points (W13, Explore's next round).
Josu's screenshot of 11 October 2026 (app 0.11.1, Explore ▸ A pair on Y2O3) · APP_WORKFLOWS.md W5, W13
L1212026-10-11work

App 0.11.1 merged: 0.11.0's questions in the app's own dialog and checked on the Mac; one Run with a pause every k cycles; ticks and tables as §8 asks; the sidebar's icons and colours

App 0.11.1 (branch app) merged into main; its 32 tests pass, clippy is clean, and no prompt() is left in the app's code.

  • 0.11.0's questions (the plan's steps, variables, links) are asked in the app's own dialog, and 0.11.0's features were checked in the built app on the Mac (L118's fault).
  • W11 as changed (L119): one Run, continuous by default, with "Pause every k cycles" and Continue at each pause; every cycle recorded; One Cycle stays.
  • §8's rules (L119) in the shared plot and table: ticks never crowd, with the common factor or offset in the axis label; the table's first column fixed.
  • W5's icons and colours (L120): an icon per kind, a colour per role, each file's state on its name. The bundle was built from main on the Mac: app/src-tauri/target/release/bundle/macos/diff-ref-struct.app, version 0.11.1.
app/ (commits 1f3204f, 111609d, a9749d5 on branch app, merged into main) · app/README.md · L118–L120
L1202026-10-11decision

The project's sidebar with an icon per kind of file and a colour per role, as code editors show files

Josu, pointing at VS Code's file explorer: a colour code or icons for the project's files would help to tell them apart. Written into W5 of APP_WORKFLOWS.md:

  • an icon per kind, keyed by the core's kinds;
  • a colour per role, the group's own, with light and dark values that colour-blind readers can tell apart;
  • each file's state on its name: unsaved, stale, missing, locked version, open in the window. Never colour alone (APP_CONVENTIONS.md §10): the icon, the group and the hover carry the same meaning.
Josu's screenshot of 11 October 2026 (VS Code's explorer) · APP_WORKFLOWS.md W5 · APP_CONVENTIONS.md §10
L1192026-10-11decision

After trying app 0.11.0 and Explore: one Run with a checkpoint every k cycles; Explore leaves the background out, lists parameters as rows, and never overlaps its tick labels

Josu tried the app built from main (0.11.0, with Explore's first step).

  • The cycle controls felt unnatural. "Step" with n = 1 under Cycles = 15 paused at every cycle and asked for acceptance each time. Wanted: run continuously, or pause every k cycles (5, say), every cycle recorded, deciding at each pause whether to continue, accept or stop. W11 is changed: one Run, continuous by default, with "Pause every [k] cycles" beside it; One Cycle stays; "Step n" goes.
  • Explore's pair plot: the tick labels overlapped (the scale factor's 0.00002596, 0.00002598, … run into each other).
  • Explore's table scrolled sideways without end: one column per parameter, 62 of them background points. Decided:
  • W13 of APP_WORKFLOWS.md: background points left out by default when refined, with a switch; the table a list with one row per parameter, its first column fixed, the cycle chosen with the slider.
  • Two general rules added to APP_CONVENTIONS.md §8:
  • tick labels never overlap or crowd: common factors and offsets go into the axis label, and fewer ticks are drawn when they would touch;
  • no table needs long sideways scrolling: many variables are rows, and choices that would add columns are controls. Josu has more changes in mind for Explore; this is the first set.
Josu's screenshots of 11 October 2026 (app 0.11.0: the Refine toolbar; Explore's pair plot and table) · APP_WORKFLOWS.md W11, W13 · APP_CONVENTIONS.md §8
L1182026-10-11work

Core 0.25.0, app 0.10.1 and 0.11.0, Explore's first step and drs-publish 0.1.0 merged; all suites pass; 0.11.0's questions use a browser dialog the Mac app does not show

Merged into main:

  • from the cloud, core 0.25.0 and app 0.11.0 (L113–L117), numbered after main's L112 with no clash;
  • from the app stream, app 0.10.1;
  • from the Explore stream, the module's first step (app/ui/modules/explore/: traces, a pair with its ellipse, the direction the data cannot fix, a cycle slider; Explore ▸ Show the Path of this Refinement, ⇧⌘X);
  • from the Publish stream, drs-publish 0.1.0 (F1 and T1–T3 from a results file, drs-publish build, the font check as a test). Two app branches had moved: the version (0.11.0 kept), the README's release entries (both kept) and the styles (both kept) were joined by hand.

On the Mac: the core's 182 tests, the app's 32 and the crate's 14 pass. One test of the crate failed as its own comment foresaw: it pinned the core's old su rule (2.287(3)). Since 0.25.0 the core rounds up, as Josu decided (L116), so it gives 2.287(4); the expected value was changed to match.

A fault for the app stream. App 0.11.0, built in the cloud where the Mac app cannot run, asks for a plan's steps, a new variable and a link with the browser's prompt(). App 0.10.1 found that prompt() shows nothing in Tauri's WebKit view and moved every question into the app's own dialog. In the window, 0.11.0's questions therefore do nothing until they are moved there too.

core 0.25.0, app 0.11.0 (commit 6333e39), app 0.10.1 (8af5aca), explore (3c34aef), publish (c12aafc) · app/ui/modules/refine/index.js (prompt) · publish/src/numbers.rs · L113–L117
L1172026-10-11work

App 0.11.0: the refinement paused and resumed with the engine's state, every stop the core's reason; W12's steering, restraints and plan in the window

Main merged (app 0.10.0, the Explore design, L112). App 0.11.0 is built on core 0.25.0 (its lsq::Session, steering and plan are core 0.24.0's); the app's requests 23–30 are closed.

  • A run is the core's session (W11, requests 23, 24). The window's run takes the lsq::Session out of the document and runs it without the documents' lock, then puts it back. A paused session stays in the document.
  • Pause (⌥⌘.) pauses after the cycle in progress; Resume (⌃⌘R, or Run) continues with the engine's state. Stepped 2 cycles and resumed, Y2O3 ends where the uninterrupted 6-cycle run ends, to the bit.
  • Step n continues a paused run. Stop ends a run in progress after its cycle, or a paused one where it is.
  • Found and fixed: until 0.10.0 a run held the documents' lock for its whole length, and Stop took the lock with try_lock, so it could not reach a run in progress. The stop and pause flags now live outside that lock.
  • Every stop is said from the core's code in plain words: converged, a minimum, the cycles out, diverging, singular (with the combination, why and what to do), stopped by you, paused.
  • Each cycle shows its λ, the core's time, the refused steps and the steering's notes.
  • During a pause what is free and how it is steered can change; the session takes it up on Resume with its damping restarted, as the core notes. A value cannot be set until Accept or Discard.
  • W12 in the inspector (requests 25–28). Every number is the core's (ParameterSet::describe, the control's refinement); every change is written to the control file by the core and is one undo step.
  • Steering: λ's start and Marquardt; each class's damping; each free parameter's last value, esd, shift and shift/esd, damping, and per value its bounds, behaviour at a bound, maximum shift and link.
  • Links and new variables, added, edited and removed.
  • Restraints: target, value, \(\Delta\)/\(\sigma\), weight, share of \(\chi^2\), and their part (Sr-ox: 8 restraints, 0.028 of \(\chi^2\) 2.09).
  • The plan: steps resolved to the parameters they free, run one at a time. Each accepted step becomes a version that names the step, and the next step becomes the one to run.
  • Request 29: the background file of a data file alone is the file dictionary's "{stem} background.drb", named by the core (L116). Request 30: the forbidden reflections are the core's diagnostics::forbidden_in.
  • Tests. The app's tests now build and run on Linux, so a cloud session can run them: WebKitGTK's development packages, and the title-bar style set on macOS only. 32 pass, five of them new:
  • pause and resume against the uninterrupted run;
  • a change during a pause;
  • the steering kept in the control file and read back;
  • the plan run step by step;
  • the restraints' shares.
  • The window, checked in Chromium. It was opened from the test's demo files (a run stepped two cycles and paused, with a plan, a damping and a link) and drawn without a page error. For Josu to try on the Mac before the next step.
app/src-tauri/src/modules/refine.rs (begin_run, drive, end_run, Store::run, steering, Change), app/src-tauri/src/main.rs (refine_run, refine_pause, refine_stop), app/ui/modules/refine/ (index.js, refine.js, outline.js), app/README.md, app/REQUESTS.md 23–30 · core 0.24.0–0.25.0 · L110, L112, L116
L1162026-10-11decision

Josu's decisions: one su rule, rounded up; one background file kind, {stem} background.drb; Co-YBCO's re-frozen 0.03324 accepted

Taken by Josu and recorded here.

  • The su rule (Publish P6, the stream's DESIGN.md §9): the IUCr's, the su rounded up (Statistical descriptors, Rec. 12), in cif_write::with_su and everywhere a su is printed (L114). Set aside: rounding to the nearest, FullProf's and the core's before; it can understate the uncertainty by up to half a unit.
  • The background file (the app's request 29, L110): one kind, "{stem} background.drb". {stem} is the data file's name without extension until a control file exists; the control file then takes the same stem, so the file keeps its name and its link. The file dictionary gains the placeholder {stem} (formats::files). Set aside: a second kind "{data} background.drb" beside the control file's, and keeping the points only inside the control file once one exists.
  • Co-YBCO: the frozen difference between the predicted and refined correlations, re-measured with the corrected axial pair at 0.03324 (L108), is accepted. Still open for Josu: FullProf's factor on the cell's step (L113), and case A's 40 Bond_Str standard deviations above its own formula (L114).
ecosystem/data/files.yml (background_points) · core/src/formats/files.rs · core/src/cif_write.rs · app/REQUESTS.md 29 · publish/REQUESTS.md P6 · L108, L110
L1152026-10-11work

The Explore stream's requests E1–E7 met: a refinement's trajectory kept and persisted, \(\chi^2\) on a grid, a state's bundle, the 3D scene, the eigen-spectrum, an irrep's displacement, the sign convention of mode amplitudes

explore/REQUESTS.md E1–E7, on case A (test explore_requests).

  • E1, the trajectory. Each cycle now carries:
  • its covariance (the correlations at every cycle, lsq::correlations_of);
  • the spectrum of the scaled normal matrix;
  • its time. lsq::Trajectory (a session's trajectory()) answers state_at(k), the model after k cycles rebuilt from the start and each cycle's applied shifts. Over three cycles of case A it is the engine's model to the bit. The results file writes every cycle (\(\chi^2\), R's, λ, the singular directions, the time; each parameter's value, shift, esd and computed shift) to the last bit and reads it back. Trajectory::from_end, given the control file written there and those cycles, rebuilds the states within 3.5·\(10^{-18}\). drs refine --trace <file.json> writes the record (results::trace_json).
  • E2, \(\chi^2\) on a grid. lsq::chi2_grid computes the points from coarse to fine (the centre and the corners first: coarse_to_fine), on every thread in batches, with a progress callback that can stop it. drs refine --map a b [--span 3esd] [--grid n] drives it. On \(5 \times 5\) of \(\Gamma\)₄⁺ and X₃⁺ about the converged case A (\(\pm\) 1 esd), the minimum is at the centre and the rest is the normal matrix's quadratic form within 2.3 %. drs refine on case A with its cycles and a \(5 \times 5\) map ran in 0.23 s here.
  • E3, a state's bundle. state::summary (and drs state, at any --cycle k) returns in one call:
  • the cell, the volume and the strain against a reference cell;
  • FWHM, η, the unclamped H_G\(^{2}\) (negative where U, V, W make it so) and H_L over a 2\(\theta\) range;
  • the reflections, and a reflection's own profile with the background apart (reflection_profile);
  • the contacts by atom, operator and translation with su's, and the valence sums;
  • the modes and irreps with su's, the breadths and the restraints. state::plausible gives the bond-valence single-bond distance d₀ − B₀ ln(V/N) for chosen N and the sum of ionic radii. Sb\(^{5}\)⁺–O (N = 6) is 2.0095 Å against case A's 1.9788; Sc\(^{3}\)⁺–O is 2.1055 against 2.0746.
  • E4, the scene. geometry::scene (and drs structure --cartesian) returns:
  • the images in a box, in fractions and Cartesian Å, each named by atom, operator and translation;
  • the bonds by rule (ionic radii, a distance, or element pairs);
  • the polyhedra as faces of the convex hull with volume and Baur's distortion;
  • the cell's edges;
  • each irrep's displacement on each image, with the reference positions. Case A's Sb1 octahedron has 8 faces and 10.3305 ų, against 10.3314 for the regular one of its mean bond.
  • E5. check::directions returns the whole spectrum of the scaled normal matrix with its vectors, sorted. It is the first cycle's spectrum at the start.
  • E6. Modes::displacement_of(amplitudes) and irrep_displacement(irrep). The irreps' displacements add up to the whole.
  • E7, the signs. Modes::equivalent_amplitudes applies every operation of the parent group, with every translation of its lattice, in the phase's basis. It compares the moved structure with the reference and projects it on the modes. Case A has 24 equivalent sets, one per domain. The Explore stream's mirror solution (\(\Gamma\)₄⁺ +0.8286, the X₅⁺ pair reversed) is the same structure by the parent's −x,−y,z. \(\Gamma\)₁⁺'s sign is never reversed. same_structure says whether two sets are one structure; nearest_to picks the set nearest a reference, the convention. AMPLIMODES's own FullProf block for case A is in its P2₁/c setting, whose X2+ sign and GM5+/X5+ bases differ from this phase's, so a reference must be in the phase's basis, as the analysis the control file's modes come from is.
core/src/lsq.rs (Cycle, Trajectory, state_at, chi2_grid, coarse_to_fine), results.rs (cycles' loops, trace_json), state.rs, geometry.rs (scene, hull), check.rs (directions), modes.rs (displacement_of, irrep_displacement, equivalent_amplitudes) · core/tests/explore_requests.rs · explore/REQUESTS.md E1–E7 · L112
L1142026-10-11work

The Publish stream's requests P2–P11 met: the results file and the CIF complete, the positions and bonds of the refined state with their su's, charges, one su rule, a phase's name, the parent cell

publish/REQUESTS.md P2–P11, each against FullProf where it prints the number (test publish_requests).

  • P2, the results file. R_F per phase; Rp, Rwp and Rexp without the background, as FullProf's "conventional" R's (manual p. 44: yᵢ − bᵢ for yᵢ in the denominators). The net counts are summed with their signs, as the manual writes and FullProf sums them. LaB6 gives 26.67/33.84 against FullProf's 26.7/33.7; with |yᵢ − bᵢ| it would be 23.9. Y2O3 gives 10.91/14.86 against 10.9/14.9. Also written: the mode amplitudes and the irreps' with su's, the reference structure, the cell reduced to the parent's, _refine_ls_shift/su_max, and _refine_special_details (how the su's were obtained, \(\chi^2\) included). The reflections' 2\(\theta\) with the zero and the displacement were already there since 0.23.0 (_drs_refln_2theta).
  • P3, a mode phase's positions and distances. The positions are reduced to [0, 1) and propagated through the amplitudes' covariance (module geometry, one Propagator for every su). Case A's Sb1–O11 is 1.9777(16); FullProf's Bond_Str prints 1.9778(17). Bond_Str reads the CFL FullProf writes, whose positions are rounded to their esd's digit and whose esd's are rounded too (O11's x, 0.000257, printed "(3)"). From that CFL the core gives 1.97777(170), FullProf's print to the digit. The core's own value at the unrounded positions differs from it by 5.7·\(10^{-5}\) Å, less than the 4.4·\(10^{-4}\) Å the CFL's rounding can move it.
  • Bond_Str on case A. The test bond_str now also reads case A's two .dis files. All 132 distances agree. 40 of their standard deviations stand above Bond_Str's own formula applied to the CFL's esd's, by up to 0.32 of a last digit beyond the print's half unit (Sb1–O11_2 0.0016 where the formula gives 0.00152). Neither the unrounded esd's nor one factor reproduces them: unexplained, counted and frozen, listed for Josu.
  • P4, bonds from the refined state. drs bonds on a .pcr or control file (--cycles n) gives the same numbers as the CIF.
  • P5, charges. charge = q on an atom in the control file (an integer from −8 to 8, not 0), or drs bonds --charges Y=3,O=-2. The species keeps its scattering. Y2O3's sums: Y 2.94, 2.95; O 1.96.
  • P6, one su rule (Josu's decision, L116). cif_write::with_su rounds the su up (IUCr, Rec. 12), keeps the 2–19 range, and rounds the value half away from zero: 2.2865 \(\pm\) 0.0032 is 2.287(4), case A's b 5.68003(11). It is now used everywhere a su is printed: the CIF, the results file, drs bonds on a CFL (FullProf's print only under --fullprof) and the weight fractions. The observed counts' \(\sigma\) in the results file keep four figures, so that the weights read back. That is a datum, not a result's su.
  • P7, nothing on fixed values. A value the symmetry fixes has no gradient, so it is written without su: Y2O3's O–Y1–O at 180°. On a CFL the angle had been 180.0000(9), the arc cosine's rounding; it is now taken by atan2 in Cartesian axes and is exact at 0° and 180°. Each bond is listed once, with _geom_*_publ_flag.
  • P8, the CIF's items. Profile, background and preferred-orientation functions, constraints, _refine_ls_R_Fsqd_factor, weighting, _refine_ls_shift/su_max, _pd_block_id with the diffractogram's, _computing_*. Labels are unique (Y1, Y2).
  • P9. drs modes --amplimodes prints AMPLIMODES's summary per irrep.
  • P10, a phase's name. A one-word name of its own is kept; otherwise the formula with the elements by increasing electronegativity (IUPAC Red Book 2005, IR-4.4.2): Y2O3, Sr2ScSbO6. The phase's CIF is "a_modes Sr2ScSbO6.cif".
  • P11, the parent cell. With the parent known (AMPLIMODES's analysis or the control file), case A reduces to 8.04299(11), 8.04299(11), 8.02621(15) Å, 89.964(3), 90.036(3), 90.1442(15)° (at the file's values). The committed results file of case A and GSAS-II's reading of it are regenerated. GSAS-II reads the same cell and atoms; their coordinates now sum differently, being reduced to the cell.
core/src/geometry.rs, cif_write.rs, results.rs, bonds.rs, lsq.rs (Statistics), control/schema.rs (charge) · core/tests/publish_requests.rs, bond_str.rs, cif_deposition.rs, results_file.rs · publish/REQUESTS.md P2–P11 · manuals/fullprof/runs/modes/A_modes/a_modes_1.cfl, a_modes_1.dis · FullProf manual p. 44 · IUCr Statistical descriptors (Schwarzenbach et al. 1995, Acta Cryst. A51, 565), Rec. 12
L1132026-10-11finding

FullProf's half step on the cell traced: a factor of FullProf's own, ½ with the pseudo-Voigt and ¼ with TCH, reproduced cycle by cycle; L109's freeze re-read without the cell

L111's run (shift_control/n3: Y2O3 converged, a, b and c moved by 3 esd, one cycle) traced in the derivatives.

  • Not the reciprocal metric. A Gauss–Newton step is the same to first order whether it is taken in a or in A = 1/a\(^{2}\): the core's step in a, 2.95 esd back, is what a step in A gives. The cell's codeword multiplier is 1. So neither explains FullProf's 1.44 esd.
  • A fraction of FullProf's own. Replayed in the core as a damping of the cell's parameters alone, the fraction that gives FullProf's cell is 0.488 in n3: with ½ the core's a is 0.034 esd from FullProf's, and every other parameter moves as the core's within 0.088 esd. Far from the minimum (m10, a 130 esd off) the core's half step gives a = 10.601563 Å, FullProf 10.601567.
  • ¼ with Thompson–Cox–Hastings. LaB6 over its 9 cycles and rutile–anatase over its 12 follow FullProf's cell at every cycle with the cell's step at ¼, within 2.7·\(10^{-6}\) and 2.6·\(10^{-5}\) Å (the whole step: 7.7·\(10^{-5}\) and 5.3·\(10^{-4}\)). There FullProf also prints the cell's esd at half of √(C_kk \(\chi^2\)) (L053): it behaves as if the cell's derivative were doubled, which halves the step once more.
  • Egypt's five TCH phases refined together are not reproduced by one fraction: 10 of its cell values follow FullProf better with ½, 5 with ¼. Counted. The factor is not in FullProf's manual. The core keeps the whole Gauss–Newton step as its default (it converges in fewer cycles to the same minimum, fullprof_refinements); a user who wants FullProf's path damps the cell (damping in [refine], W12). Counted and frozen in the test fullprof_cell_step, listed for Josu.

L109's freeze re-read. Its factor 2.00 on a was this half step. The cell is now left out of the first cycle's comparison in steering: 5 of the 6 other parameters differ by more than 10 % from the far start, by up to 4.29 (U 0.975, V 1.919, W 4.286, X 1.577, Zero 2.033, η 0.291), and near the minimum (n3) the same parameters move as FullProf's within 0.09 esd. So what remains of L109 belongs to the far start's non-linearity, not to the step.

core/tests/fullprof_cell_step.rs (near_the_minimum_half_a_step, replayed_cycle_by_cycle) · core/tests/steering.rs (FROZEN_COUNT 5, FROZEN_FACTOR 4.29) · manuals/fullprof/runs/shift_control/n3.out, m10.out · runs/LaB6, runs/rutile-anatase, runs/Egypt · L053, L109, L111
L1122026-10-11work

App 0.10.0 and the Explore design merged; the Publish crate built but not yet committed

App 0.10.0 (branch app) merged; its 27 tests pass. W7's diagnostics, every number from the core:

  • the difference in \(\sigma\) and the cumulative \(\chi^2\);
  • "who is this peak": a click gives each phase's share and the reflections under the point;
  • the correlation matrix as a heat map and each parameter's path over the cycles, after a run;
  • another version's Ycal overlaid. W3: Data ▸ Start a Refinement… copies a .drc or .pcr from anywhere into the project, pointed at the data by the core's rebase, with what does not fit listed before it is opened. Pause/Resume and W12's interface wait for core 0.24.0, now on main.

The Explore design (branch explore) merged: explore/DESIGN.md, REQUESTS.md and twelve mock-ups on Y2O3 and Sr2ScSbO6 by modes.

  • Checked in FullProf's and GSAS-II's manuals and code: neither shows a refinement's path, so Josu's belief holds.
  • Two findings from the mock-ups:
  • Y2O3's first cycle takes a away from its answer while \(\chi^2\) falls 3.5 times, the zero shift absorbing the step: seen in the pair view, not in \(\chi^2\);
  • the core's mode refinement of case A, started at +0.1 Å, reached the mirror of FullProf's solution (\(\Gamma\)₄⁺ and X₅⁺ amplitudes of opposite sign, the same \(\chi^2\) and irrep amplitudes). The \(\chi^2\) map shows the two minima across a ridge, with the parent at a saddle; a test of signs needs a stated convention.
  • Eight points are left for Josu (§8).

The Publish crate (publish/src, drs-publish build <set.drp>) is built in the stream's worktree; its 9 tests pass and its preview of Sr2ScSbO6's thesis set is in Latin Modern only. It is not committed: the stream is to commit and push it before it can be merged.

app/ (commit fb586f3 on branch app) · explore/ (commit c4938b1 on branch explore) · ../20261009_diff-ref-str-publish/publish (uncommitted) · L104, L106
L1112026-10-11finding

Core 0.24.0 merged (its entries renumbered L107–L110); 169 tests pass; FullProf moves the cell half a Gauss–Newton step near the minimum too

Core 0.24.0 merged into main. Its entries, written as L102–L105 on a branch that had merged an older main, are L107–L110, and the references in the files it changed were renumbered with them. The core's 169 tests pass on the Mac.

The run L109 asked for. FullProf 8.20 was run on Y2O3's converged file with a, b and c moved by 3 esd (\(10.604939 \to 10.604829\) Å, esd 3.668·\(10^{-5}\)), one cycle (manuals/fullprof/runs/shift_control/n3).

  • FullProf's a after the cycle is 10.604882: it moved 1.45 esd of the 3, about half the way back.
  • The core's Gauss–Newton step returns the whole way (to −0.04 esd, L109). So the factor of about 2 on the cell's shift (L109: the core's over FullProf's is 2.00 for a from the far start) is not the far start's: FullProf takes half a step on the cell near the minimum too. FullProf converges over more cycles to the same minimum (fullprof_refinements). Believed, not checked: the cause lies in how FullProf turns its step in the reciprocal metric A = 1/a\(^{2}\) back into a. To be traced by the core's stream with the derivatives, and the freeze of L109 re-read in this light.

Open for Josu from L108–L110:

  • Co-YBCO's frozen difference between predicted and refined correlations grew from 0.03169 to 0.03324 with a corrected parameter set (L108);
  • the name of a background file kept for a data file without a control file (app request 29, L110).
core 0.24.0 (commit 9367603) · manuals/fullprof/runs/shift_control/n3.out, README.md (FullProf 8.20) · L108–L110
L1102026-10-11work

Core 0.24.0: the refinement run as a session (W11) and steered from the control file (W12), with a plan of steps

W11, the engine (lsq::Session).

  • step(n) runs n cycles and pauses; run goes on to the end; between calls the session keeps the model, λ, the cycle count, the history and the count of rising cycles.
  • set_value and set_parameters during a pause restart λ, and the notes say so.
  • to_text and resume save and restore the state for scripts: every value moved, the variables, λ, the count and the history.
  • Every stop is data (Stop, with a stable code and a text): converged; minimum; cycles; diverging by \(\chi^2\) rising, a jump of many esd's or an impossible value (Divergence); singular, with the combination, its eigenvalue and the dictionary's why and tip (check::null_direction, shared with drs check); the user.
  • Each cycle gives, for every free parameter, its value, esd, shift, shift/esd and computed shift; and λ, the steps refused with their λ and \(\chi^2\), the restraints with their weight (c/\(\sigma\)²) and contribution to \(\chi^2\), and notes.
  • refine and refine_until are sessions run to the end, as before.

W12, the steering, all in the control file and checked as it is read (module steering):

  • in [refine]: lambda, marquardt, damping by class, divergence (rising, jump, jump_from, singular), steering (damping of a parameter, maximum shift of a value), and behaviour on limits;
  • the tables [[refine.link]] (value = coefficient × follows + offset), [[refine.variable]] (name, value, terms) and [[refine.step]] (the plan: name, free, damping, cycles, convergence);
  • the reader's checks: a damping not above 0 is an error and above 1 a warning (over-relaxation); a periodic bound needs two bounds; a link to itself, or of a refined value, is refused; a variable moving a refined value or a mode phase's coordinate is refused; a step that frees nothing is an error with its name; unknown classes and guards get the nearest name;
  • ParameterSet::describe gives each free parameter's class, damping, and for each value its coefficient, bounds, behaviour, maximum shift and link, for the app. The .pcr import reads R_at … R_gl and the multipliers by their meaning (L107); the export writes them back, and notes what FullProf has no place for.

The command line. drs refine runs a session.

  • --session <file> resumes from the file if it exists and saves the state after; with --cycles n it is Step n.
  • --plan runs the control file's steps in order, each from the values the previous step reached, and stops the plan at a divergence, a singular matrix or Ctrl-C; --step k runs one step.
  • --parameters prints each cycle's parameters; each cycle's notes are printed; the restraints are printed with their weight and contribution.

Tests (steering, 13):

  • shift_control against FullProf (L109);
  • the import of multipliers;
  • damping never changes an esd: damping 0.3 reaches damping 1's minimum within 0.007 esd, with esd's within 1.1·\(10^{-4}\), in 28 cycles instead of 7;
  • a maximum shift binds and the dictionary's defaults do not;
  • stop, reflect and periodic, to \(10^{-12}\);
  • the occupancy's bounds on Y2O3's three sites;
  • links giving Co-YBCO's shared codewords exactly (the same set; the same refinement to \(10^{-12}\), Cu1 + Co1 kept);
  • one value across phases;
  • case A's 5 refined amplitudes as variables moving the coordinates: the refinement of the amplitudes to 7·\(10^{-14}\) esd;
  • each guard;
  • a session stepped 2, saved, resumed and run 2 more, equal to 4 straight cycles to the last digit;
  • a plan of three steps reaching the single run's minimum within 0.003 esd, written and read back;
  • the restraints' weights and contributions;
  • the reader's checks;
  • the command line. The other suites pass, with the dictionary's maximum shifts, the occupancy bounds, the singular guard and the damping now imported.

The app's requests (branch app, filed while this was built):

  • 23–28, W11 and W12: answered by the above. Session::paths gives each parameter's path over the cycles (25); lsq::restraints_part gives the restraints' total part of \(\chi^2\) (27).
  • 30: diagnostics::forbidden_in(model, x) gives the forbidden reflections over the pattern's range, Zero added; drs reflections --forbidden uses it, and reflection_diagnostics checks it.
  • 29 is left for Josu. A name {data} background.drb could not be told from {control} background.drb by its name. Proposed: one kind, whose {control} is the data file's stem until a control file exists; the control file created from that data file (W4) then takes the same stem.
core/src/lsq.rs, steering.rs, refinable.rs, check.rs (null_direction), pcr.rs, control/schema.rs, control/pcr_export.rs, powder.rs (gaussian_h2), diagnostics.rs (forbidden_in), main.rs (drs refine) · core/tests/steering.rs, reflection_diagnostics.rs, check_diagnostics.rs · ecosystem/data/parameters.yml · app/REQUESTS.md 23–30 (branch app, 4e84548)
L1092026-10-11finding

Against FullProf's shift_control runs: the steering agrees parameter by parameter; the first cycle from that start does not, explained in part and frozen (for Josu)

Test steering::shift_control_against_fullprof, one cycle of each of FullProf 8.20's three runs (L097) from the same start.

What agrees.

  • For every one of the 72 parameters, FullProf's shift in m05 and in r05 over its shift in m10 is the core's. m05 halves the cell and nothing else; r05 halves the 9 profile-class parameters and none of the 63 others. FullProf's ratios hold to its print, the core's exactly.
  • The computed (undamped) shifts are the same in the three runs.
  • The core's esd is not changed by the damping: \(\sigma\)/√\(\chi^2\)_ν is the same in the three runs to \(10^{-9}\).
  • FullProf's esd's follow its own rules exactly. The ratio m05/m10 is 0.49989 = ½ (10.600783/10.601567)³, and r05/m10 is 0.99978 = (10.600783/10.601567)³. FullProf derives \(\sigma\)(a) from the reciprocal metric A = 1/a\(^{2}\) at the new a: it refines A (manual p. 19, the magnetic cell's multiplier 0.25).
  • The core's \(\sigma\)(a) at the start's \(\chi^2\) (7.544, the \(\chi^2\) FullProf uses after a cycle) is 9.095·\(10^{-5}\), against FullProf's 9.050·\(10^{-5}\): 0.5 %, FullProf's \(\chi^2\) being over the points with Bragg contributions (esd_profile). The core reports \(\sigma\) at the \(\chi^2\) of the values reached (1.86 here), hence its 4.52·\(10^{-5}\) after this cycle.

What does not agree: the first step itself. The core moves a by +0.003126 Å, FullProf by +0.001567. Over the parameters translated as they are, the core's shift over FullProf's is:

  • U 0.975, V 1.92, W 4.29, X 1.58, zero 2.03, a 2.00, η 0.29;
  • 6 of the 7 are beyond 10 %. The background points move less than their esd in both programs, and are left out.

Explained in part. FullProf's own Y2O3 run starts at its minimum (its cycle 1 moves nothing by more than \(10^{-6}\)). shift_control moves a by 0.005 Å, about 130 esd: at 2\(\theta\) = 140° the peaks are misplaced by about 0.15°, more than their width, far outside the range where a Gauss–Newton step is linear. There every detail of the derivatives changes the step: FullProf's cell parameter A = 1/a\(^{2}\), its hard peak window (with which the core's analytic derivative of U differs from finite differences by cos 0.964; with the smooth window, exactly), and others not identified. Both reach the same minimum (fullprof_refinements).

In the linear range the core's step is the Gauss–Newton step. From the minimum with a, b and c moved by 3 esd, one cycle brings a back to −0.04 esd.

Frozen in the test (it fails if more than 6 parameters, or a factor above 4.29, differ), and listed for Josu: one FullProf run would separate it, Y2O3's converged file with a moved by 3 esd (10.604939 − 0.00011, b and c tied), one cycle. If FullProf's step then returns a as the core's does, the difference belongs to the far start alone.

core/tests/steering.rs (shift_control_against_fullprof) · manuals/fullprof/runs/shift_control/*.out, README.md · manuals/fullprof/runs/Y2O3/y2o3_structure_conv.out (cycle 1) · FullProf_Manual.pdf pp. 19, 43 · L053, L055, L097
L1082026-10-11finding

How the core treated multipliers until 0.23.0, which compared runs carry relaxation factors, the scale's class measured, and the largest shifts per cycle measured

A lone multiplier until 0.23.0. It was taken as a change of variable f = a·q. Since the derivative carries a, the value's shift and its esd were the same as with a = 1: no damping, and the field's own esd, not FullProf's a·\(\sigma\).

No esd compared so far carries FullProf's scaling. No .pcr of the comparisons (fullprof_refinements, restraints, Le Bail, QPA, modes) has a lone multiplier other than \(\pm\)1. The one in the runs, YVO3/YVO3a_calc.pcr (Y \(\times 0.5\)), is used only for its zero. The \(\times 2\) and \(\times 0.5\) factors in parameter sets are the import's translations (X-ray scale, axial pair), not multipliers. Shared multipliers of \(\pm\)4 on occupancies occur in FP_tests/monte.pcr and prnio.pcr, which are not compared.

Relaxation factors were not read until 0.24.0. In 27 of the 456 .pcr files of manuals/fullprof/ they are not 1 (0.8 in 9, 0.85 in 5, R_at alone 0.08–0.5 in 6, …). Among the compared runs, mode case A (0.8, a_modes, a_coords) and LaMnO3 3T2 (0.85) were refined undamped, where FullProf damps them. Their tests pass with or without, since they compare converged values. The results file of case A after one cycle is regenerated (test results_file, its GSAS-II reading too): the cell moves by \(10^{-5}\) Å, a tenth of its su.

The classes, measured. In shift_control/r05 (R_pr = 0.5) FullProf halves the shifts of the scale, U, V, W, η, X, both asymmetry parameters and the cell (9), and leaves the zero and the 62 background points whole (63). The scale's class is not in the manual's list; it is the profile class, measured. Every other class is the manual's. Two classes are believed, not measured: the wavelength ratio (global) and the mode amplitudes (atomic, being coordinates). The dictionary has each entry's class and its source.

The largest shift per cycle, measured on the core's runs of the nine FullProf comparisons and mode case A (both parametrisations), from FullProf's starting files with no limit:

  • zero 0.22°, displacement 0.20°, transparency 0.06°;
  • U 0.034 deg\(^{2}\), V 7.8·\(10^{-4}\), W 8.1·\(10^{-4}\);
  • X 0.030, Y 0.010;
  • the cell 0.004 Å, β 0.0018°;
  • positions 3.8·\(10^{-4}\), B 0.15 Ų, overall B 0.23 Ų, occupancy 2.3·\(10^{-4}\), β_ij 4.5·\(10^{-4}\);
  • modes 2.8·\(10^{-4}\) Å, η₀ 2.5·\(10^{-4}\), the asymmetry 5·\(10^{-3}\), S/L 6·\(10^{-4}\), the ratio 1.6·\(10^{-3}\). The dictionary's defaults are physical guardrails, each at least 4 times the largest measured (in its unit):
  • zero, displacement and transparency 1°; U, V, W 0.5 deg\(^{2}\); the size and strain terms 0.5;
  • cell lengths 0.1 Å, angles 1°;
  • positions 0.05, B 1 Ų, overall B 1 Ų, occupancy 0.2, β_ij 0.01;
  • mode amplitudes 0.2 Å; η₀ 0.2, η slope 0.005/°; the asymmetry 0.5; S/L and D/L 0.02; G1 0.5, G2 0.2;
  • the ratio 0.05. The scale and the background (linear, in arbitrary units and counts) have none. No comparison changes with them.

A fault found and fixed. A value that the import's translations give a row, but that had no codeword of its own, was left out of its parameter. This was D/L when D_L < 0.0002 and S_L is refined (Co-YBCO's Cocus-asym: FullProf's S_L moves both S/L and H/L of the default model), and P1 when only P2 is coded. Found by the export's round trip; such a value now stays in its parameter. With it, Co-YBCO's frozen difference between the predicted correlations and those after refining (check_diagnostics::predicted_correlations, L076) grows from 0.03169 to 0.03323, the same pair (a – c) and the same single pair crossing 0.9. With the fix disabled it is 0.031683, so the growth is the corrected set's. Re-frozen at 0.03324, for Josu to accept.

core/src/refinable.rs (pcr_rows), pcr.rs (relax) · manuals/fullprof/runs/shift_control/r05.out, m10.out · the 456 .pcr files of manuals/fullprof/ (line 9) · ecosystem/data/parameters.yml (class, class_source, max_shift) · core/tests/steering.rs, parameter_dictionary.rs, control_file.rs (pcr_export_round_trip), results_file.rs
L1072026-10-11decision

W11 accepted; W12 built as asked: a lone multiplier is damping, shared ones are a link's coefficients, and no esd is ever scaled

Josu accepted W11 (L095) and asked for W12 (L097) to be built in the core, with the import of a .pcr's multipliers by their meaning. The choices made in building it, each with what was set aside:

  • A lone multiplier is damping. A codeword whose parameter number is carried by one value alone (the values the symmetry ties to it, such as a cubic cell's b and c, do not count) is that value refined with its shift multiplied by |a|, as the manual says (p. 13, "the factor by which the computed shift … will be multiplied"). The coefficient keeps a's sign. Shared multipliers stay the coefficients of a link (Co-YBCO's Cu1 and Co1 occupancies, 71.00 and −71.00).
  • The relaxation factors R_at, R_an, R_pr, R_gl (LINE 9, p. 77) are the damping of the four classes, and the damping of a parameter is its own times its class's.
  • No esd is changed by damping. \(\sigma\) = √((A\(^{-1}\))_jj \(\chi^2\)_ν) whatever the step. FullProf prints a·\(\sigma\) for a parameter with multiplier a (its eq. 3.7, p. 43; measured, L097). That reading is kept in the FullProf-compatibility layer, lsq::fullprof_esd, for comparisons only (L043). Set aside: printing FullProf's esd, which would make an uncertainty depend on how cautiously the value was approached.
  • Convergence is judged on the computed shift, before damping. A damped run converges to the same minimum and does not stop early because its applied steps are small.
  • The guards and their defaults. \(\chi^2\) rising 3 cycles in a row. A computed shift above 100 esd's, from cycle 3 (the first cycles from rough values legitimately move far). An impossible value (non-finite, a cell length or angle out of range, a negative Gaussian H\(^{2}\) at a reflection). A singular matrix, named as check names it (R24). Each threshold is in the control file, and 0 turns a guard off. FullProf's Dum = 2 stops at the first rise of \(\chi^2\) (manual p. 68); set aside as the default because a Le Bail extraction or maximum-likelihood weights can raise \(\chi^2\) once without diverging.
  • The step. λ's start defaults to 0, a Gauss–Newton step first, as before; GSAS-II starts at \(10^{-3}\). With marquardt = false every step is applied, as FullProf applies them, and the guards stop a divergence.
  • Bounds. Stop (held, P39), reflect, or periodic, the last two as FullProf's IBOUND 0/1 (LINE 47). The physical bounds come from the dictionary, now with the occupancy's upper bound, a full site (Occ \(\le\) m/M); the user's bounds go on top.
  • Maximum shifts are absolute, in each value's unit, as asked; a relative maximum was set aside. The defaults are in the dictionary (L108).
  • Links x = c·p + d are applied after the symmetry's ties, and their values are set from the followed value at the start of a run.
  • New variables are columns of their own, through the symmetry like any parameter. They may share a value with each other (two modes moving one coordinate), never with a parameter.
  • A plan is a list of steps, each with selectors (addresses, prefixes ending in *, variables, class words, "all"), its damping over the steering's, its cycles and its convergence. "One cell for two patterns" waits for multi-pattern refinement (one pattern today).
conversation of 11 October 2026 · APP_WORKFLOWS.md W11, W12 · L095, L097 · FullProf_Manual.pdf pp. 13, 43 (eq. 3.7), 68 (Dum), 77 (LINE 9), 126 (LINE 47) · GSAS-II fe944cd GSASIIstrMain.py (Marquardt), GSASIImapvars.py · core/src/steering.rs, refinable.rs, lsq.rs
L1062026-10-11work

Publish: the design's decisions recorded and the mock-ups redone under L103; every figure in Latin Modern only, checked

The Publish stream's second round merged into main (publish/ only). Josu's answers to DESIGN.md §9 are recorded:

  • the catalogue and first build accepted;
  • su's rounded up, with the core's cif_write to follow (request P6);
  • B in theses, U in journals, U in the CIF;
  • Latin Modern everywhere (L103), the Faculty's model kept as the TFG/TFM page layout;
  • the plot's colours, with a monochrome option;
  • the crate's own PGF compiled with LuaLaTeX;
  • .drp in Publish/;
  • checkCIF offline. New §2.5 sets one preamble for every figure and table: LuaLaTeX, fontspec, unicode-math with Latin Modern Math, siunitx, mhchem. mhchem was chosen because MathJax and KaTeX also read its \ce.

The mock-ups were redone from core 0.23.0's results files. pdffonts of all fifteen figures was checked here again: LMRoman (6, 8, 9, 10, 12) and LatinModernMath-Regular only, journals included.

§10 leaves three points for Josu: Word tables (which Word typesets, not LaTeX), tables in a document whose fonts are not Latin Modern, and HTML/Markdown tables.

publish/ (commit 3cc35fb on branch publish, merged into main) · publish/DESIGN.md §§2.5, 9, 10 · publish/mockups/out/ (pdffonts) · L103
L1052026-10-11work

App 0.9.0–0.9.2 merged: the background by hand, the Reflections view, the cycles in view; Work on a Copy's recursion found and fixed

App 0.9.0, 0.9.1 and 0.9.2 (branch app) merged into main; the app's 26 tests pass.

  • 0.9.0:
  • W4: background points picked on the plot, kept in a .drb or in the control file, compared with the automatic background and a fitted function;
  • W8: the Reflections view, every number from the core (diagnostics::reflections); I obs labelled as partitioned by the model; forbidden reflections on demand; FullProf's .prf/.hkl in the same view; CSV exports into Exports/; results files written on Accept;
  • W7: excluded regions drawn, dragged and removed on the plot;
  • W11's visible part: Cycles and Convergence, Step n, "cycle k of N" with \(\chi^2\) and time per cycle, every stop in plain words in the refinement log; only accepted results become versions.
  • The bug, found by Josu:
  • New Project had remembered a folder inside manuals/fullprof/runs/LaMnO3-3t2 as the projects folder, and 0.8.1's Work on a Copy copied that run into itself. copy_to walked the source while the copy grew inside it: 85 levels, 185 MB;
  • opening Y2O3 had also written Versions/ and a .drc into a reference folder. This session moved all of it to the Bin and reset the setting to ~/Documents/diff-ref-struct.
  • 0.9.1:
  • copy_to refuses a destination inside the source or inside a reference folder;
  • the projects folder is checked, and reset at launch when it breaks the rule;
  • New Project is refused inside a reference folder;
  • one guard refuses every write into a reference folder (state, import, versions, log, results, exports, background files, saves). Tests on a temporary git repository assert each refusal and that the folder's listing is unchanged.
  • 0.9.2, after Josu's try:
  • the Rietveld plot as WinPLOTR lays it out: the ticks in a band, Yobs − Ycal in a strip of its own with its own scale, so a zoom no longer squashes the pattern;
  • the panels always recoverable, with toggles in the toolbar.
app/ (commits 15125c7, 4e84548, 77a1cb4 on branch app, merged into main) · app/README.md · ~/Library/Application Support/eus.ehu.diffrefstruct/settings.json (projectsDir reset)
L1042026-10-11decision

R31: the Explore module, a fifth stream that shows the path of a refinement in configuration space and in physical subspaces

Josu proposed showing the refinement's path: the point in the space of the freed parameters as it moves, and the physical things the parameters build (the cell, the peak shape, the structure, the mode amplitudes) as they change, so that physical evolutions can be seen and judged. Audited and extended (R31):

  • the trajectory projected sensibly, since 72 free parameters cannot be drawn: traces scaled by esd, pairs with the covariance ellipse, and the directions the data cannot fix, from the core's eigen-decomposition (R24);
  • a \(\chi^2\) map on a grid around the current point for a chosen pair, with the path drawn on it;
  • the physical subspaces: the cell box, strain and ticks; a reflection's profile and FWHM(2\(\theta\)), marked where U, V, W become impossible; the structure with distances, angles and bond valences against plausible ranges; the mode amplitudes, with the structure morphed from the parent mode by mode; size and strain;
  • replay, a past cycle opened and refined again from there, two paths compared, W11's stops marked;
  • later, a point dragged by hand in a two-parameter subspace with the pattern and \(\chi^2\) following. Every number comes from the core; the module is a viewer.

Organisation, as for Publish (L094): a fifth stream in its own worktree (../20261009_diff-ref-str-explore, branch explore). It owns explore/ (design, prototypes, requests) and, later, app/ui/modules/explore/, and asks the core and the app's shell for anything else in explore/REQUESTS.md. It starts with a design for Josu: what FullProf and GSAS-II offer (checked), the projections, the 3D library bundled with the app, and mock-ups on Y2O3 and Sr2ScSbO6 by modes. Set aside:

  • drawing the full configuration space as such (not drawable);
  • computing the derived quantities in the app (against "no physics in the app");
  • adding the module to the app stream's list (it would wait behind W11, W12 and the fixes).
conversation of 11 October 2026 · ecosystem/data/brief.yml R31 · L094 (the Publish stream's organisation) · R24, W11, W12
L1032026-10-11decision

Publish: everything in LaTeX, in Latin Modern, in every figure and table and every preset

Josu reviewed the Publish stream's design and mock-ups (branch publish, commit afe2b05): "not bad", but everything must be LaTeX, in the figures, the labels, everything. The mock-ups used two typefaces in each figure:

  • the journal preset set words and digits in Helvetica, with only the symbols (Y_obs, 2\(\theta\)) in Latin Modern (pdffonts: Helvetica + LatinModernMath);
  • the thesis preset used EHU Sans, the Faculty template's body font, with the maths in Latin Modern. "\(\mathrm{Y_{2}O_{3}}\)" and "Intensity (\(10^{3}\) counts)" were typed text, not LaTeX chemistry and units.

Decided, a standing rule in CLAUDE.md: every character of a figure or a table is typeset by LaTeX in Latin Modern, text and maths alike, in every preset, journals included:

  • axis labels, tick labels (in math mode, with the true minus), legends, insets and annotations;
  • units through siunitx;
  • chemical formulas through a chemistry package (mhchem or chemformula);
  • irrep labels and symbols in math mode. A raster (TIFF, PNG) or EPS for a journal is made from that LaTeX-typeset PDF. The design's §9 points 4 (the thesis presets' fonts) and 5 (the digits) are answered by this rule. Set aside: Helvetica for journals; the body font of a template (EHU Sans) in figures; any text drawn outside LaTeX.
Josu, 11 October 2026 · publish/DESIGN.md §6.3, §9 (branch publish) · publish/mockups/out/*/y2o3_rietveld.pdf (pdffonts) · CLAUDE.md
L1022026-10-11work

Core 0.23.0 merged (its entries renumbered L098–L101); all 156 tests pass; GSAS-II on the Mac reads the results file as the cloud's checkout did

Core 0.23.0 merged into main. Its entries, written as L094–L097 on its branch while main's L094–L097 were being written, are L098–L101. The references in the files it changed were renumbered by topic: the mode cases L098; the names accepted and the pdCIF choice L099; FullProf's readers and the reflections' numbers L100; the results file L101.

The core's 156 tests pass on the Mac, among them the eight mode runs against FullProf's outputs (L098: the 1–6 % of L093 explained by the reflection limits, D's λ/2 widths and FullProf's background below its first point; the few edge points left frozen).

core/tests/data/make_gsas2_results.py was run again with the GSAS-II installed on the Mac (not the cloud's source checkout). Its reading of the results file and of the structure CIF is identical to the committed table: 3199 points, the cell, P2₁/n and the six atoms.

core 0.23.0 (commit a7d9653) · core/tests/data/make_gsas2_results.py, gsas2_results.txt · GSAS-II 5843 (local) · L098–L101
L1012026-10-11work

The results file written in pdCIF, read back and read by GSAS-II; one structure CIF per phase; the file dictionary updated

results::write writes the results file of a refinement as Josu chose (L092, L099): "{control} {time} results.cif" in Results/. It is a powder CIF of three kinds of data blocks, linked by _pd_block_id.

  • data_overall: Rp, Rwp, Rexp and the goodness of fit (_pd_proc_ls_prof_*, _refine_ls_goodness_of_fit_all), the numbers of points, parameters and restraints, the phases' blocks, every refined parameter with its value and standard uncertainty, and the control file as a text field.
  • One block per structure phase, the deposition CIF's (L083): cell, symmetry and atoms with their uncertainties, R_Bragg and the geometry, with _pd_phase_name.
  • data_pattern: the wavelengths, the excluded regions, the profile (_pd_meas_2theta_scan, _pd_meas_intensity_total with \(\sigma\) as su, _pd_proc_ls_weight, _pd_proc_intensity_bkg_calc, _pd_calc_intensity_total, each phase's curve), and the reflections (_pd_refln_phase_id, _refln_index_*, _refln_symmetry_multiplicity, _refln_d_spacing, _refln_F_squared_calc, _refln_intensity_calc, _refln_intensity_meas partitioned by the model, _refln_intensity_sigma). DDL1 names are used wherever the core or powder dictionary has one, and the local prefix _drs_ for the rest: the phases' curves, the reflections' 2\(\theta\) and FWHM, the parameters, \(\chi^2\), the control file.

Beside the results file, one structure CIF per phase ("{control} {phase}.cif"). On demand, the CSV exports of the reflections and of the profile (results::reflections_csv, profile_csv).

Read back and by GSAS-II.

  • results::read reads the file back. The CIF reader now keeps text fields and gives a value's su (cif::value_su).
  • GSAS-II (305a159) takes \(\sigma\) from the intensity's su and not from _pd_proc_ls_weight: its line that would read the weight is commented out (G2pwd_CIF.py). So Yobs is written with its \(\sigma\) to four significant digits.
  • tests/data/make_gsas2_results.py runs GSAS-II's own powder and phase importers on the committed file tests/data/results/a_modes_results.cif (case A, one cycle) and on its phase's CIF. The phase importer needs GSAS-II's compiled pyspg, built here with gfortran and f2py from its sources.
  • Test results_file:
  • the profile, the 519 reflections, the agreement factors and the 17 parameters with their uncertainties are the core's, to the seven figures written;
  • the control file read back writes the same text, and the phase's CIF gives the cell within half its su;
  • the committed file is what the core writes today;
  • GSAS-II reads every point with the core's Σx, Σy and Σw, the first and last points and the wavelength, and from both CIFs the cell, P2₁/n and the 6 atoms.

The dictionary (files.yml): the results kind decided (pdCIF; extension cif). New kinds: reflection_notes, export_reflections, export_profile. Every other kind decided (L099). The {results} placeholder removed. 21 kinds. Command: drs results <control> [--cycles n] [--project folder] [--time t] [--exports] writes them where the dictionary says.

core/src/results.rs, cif.rs, formats/files.rs, main.rs (drs results) · core/tests/results_file.rs, data/make_gsas2_results.py, data/gsas2_results.txt, data/results/ · GSAS-II 305a159 imports/G2pwd_CIF.py (l. 276–292), G2phase_CIF.py · COMCIFS cif_pd.dic, cif_core.dic (DDL1) · L083, L092, L099
L1002026-10-11work

Readers for FullProf's .prf and .hkl (app request 17); the core's side of W7–W8: per-reflection widths and partitioned intensities, forbidden reflections, a mode phase's parent indices and k-vectors, phase shares, the cumulative \(\chi^2\)

App request 17. formats::fullprof_outputs holds the two readers.

  • read_prf reads both layouts of FullProf's .prf:
  • the one with reflections on lines of their own and 0 w : p;
  • the older one with a Bragg column, where the phase is the tick's row. It returns title, \(\chi^2\), phases, wavelengths, Zero, SyCos, SySin, propagation vectors, excluded regions, the profile, and the background with FullProf's offset added back (Backg + least Backg/3, L082). h k l are read as FullProf's 3I3 fields, so "( 0 0-10)" is 0 0 −10.
  • read_hkl reads the blocks of a .hkl by the column names each prints. Hkl = \(\pm\)3, 2 and 1, and the magnetic lists, name their columns. The blocks profile matching writes (Irf = 2) do not: their columns are the manual's h, k, l, Mult, Intensity, then 2\(\theta\) and FWHM, checked against the same run's Hkl = 2 file.
  • Test fullprof_outputs, over every file in manuals/:
  • 188 .prf read and 3 of integrated intensities refused;
  • each phase's reflections match the count of line 3, and 65 files' excluded regions match their .out;
  • 168 .hkl read, each block no longer than its heading announces;
  • in 28 blocks, every 2T is a tick of the same pass's .prf.
  • fullprof_profiles now reads its 63 files with this reader and compares its background with the core's.
  • Command: drs fullprof <file.prf|file.hkl>.

W7–W8, in diagnostics, reflections and modes:

  • diagnostics::reflections: each reflection's d, 2\(\theta\), FWHM, η, |F|², I calc, I obs, \(\sigma\) and (I obs − I calc)/\(\sigma\). I obs is partitioned by the model as Rietveld's method shares the counts above the background, and is documented as such. Its \(\sigma\) is the points' \(\sigma\) propagated through the same partition.
  • Against the reflection tables of five FullProf runs' .out, the FWHM agrees to \(10^{-5}\).
  • For reflections of Icalc \(\ge 20\), I obs/I calc agrees within FullProf's print (0.005) on Y2O3, A and B. FullProf's \(\sigma\) differs by 2–6 %; its formula is not in the manual.
  • Frozen: rutile-anatase (TCH with axial divergence), 9 strong reflections lower by up to 0.13, not resolved; E's (0 1 2), whose window reaches into its out-of-order excluded region (L098).
  • reflections::forbidden: the systematically absent reflections in a range, each with the condition it breaks, written as the tables write it ("0k0: k = 2n", "h0l: l = 2n", "hkl: h + k + l = 2n", "00l: l = 4n"). The condition comes from the operator: the class of reflections it leaves fixed, and h·t integral on it.
  • modes::ModeParent::reflection: a mode phase's reflection in the parent's cell (h·P\(^{-1}\)), and its k-vector against the parent's reciprocal lattice, which the centring allows. The parent (group and P) comes from an AMPLIMODES analysis (Analysis::mode_parent) or the control file (mode_parent_group, mode_parent_basis, mode_parent_origin). Test symmetry_modes::parent_reflections:
  • case A by analysis 0_1: 96 reflections at \(\Gamma\), exactly those of the parent's lattice, and 62 at X, the k of its X irreps;
  • case E, R-3 on hexagonal axes: all at \(\Gamma\), with the obverse transformation a_h = (b − a)/2, b_h = (c − b)/2, c_h = a + b + c. The signs used in normalisation give the reverse setting, whose R centring is not F.
  • diagnostics::shares: the background's and each phase's share of Ycal at a point.
  • diagnostics::cumulative: Σw(y − yc)² from the first point, and √w(y − yc), with fraction() and GSAS-II's scaling (GSASIIpwdplot.py: the end at the observed maximum).
  • lsq::weights is public.
  • Test reflection_diagnostics; commands drs reflections [--forbidden] and drs calc --cumulative --shares.
core/src/formats/fullprof_outputs.rs, diagnostics.rs, reflections.rs, modes.rs · core/tests/fullprof_outputs.rs, reflection_diagnostics.rs, symmetry_modes.rs · FullProf_Manual.pdf pp. 9–11 (Prf, Hkl, Irf) · GSAS-II 305a159 GSASIIpwdplot.py · app/REQUESTS.md 17, 19–21 (branch app) · APP_WORKFLOWS.md W7–W8
L0992026-10-11decision

The file dictionary's names accepted, with two kinds added (reflection notes and Exports); the results file in pdCIF

Josu accepted the names of the file dictionary (L091). Every kind that was "proposed" is now "decided (L099)". He added two kinds:

  • reflection_notes, "{control} notes.toml" beside the control file. The notes are the user's own writing, so they are visible, not in .drs/ (L093).
  • Exports/, CSV written on demand: export_reflections ("{control} reflections.csv") and export_profile ("{control} profile.csv"). The record of a refinement is the results file; CSV is only an export.

He chose pdCIF for the results file (L092's recommendation). Its kind is now "{control} {time} results.cif" in Results/ (extension cif), and the placeholder {results}, kept while R28 was open, is gone. One structure CIF per phase is written beside it under the deposition kind's name, "{control} {phase}.cif". Set aside, as in L092: NeXus/HDF5 and a format of our own.

Josu, 2026-10-11 · ecosystem/data/files.yml · core/src/formats/files.rs · L091, L092, L093
L0982026-10-11finding

The mode cases B, D and E agree with FullProf: the 1–6 % of L093 came from the reflection limits, D's λ/2 widths and FullProf's rule for the background below its first point; what is left are single points at the edges of excluded regions, explained and frozen

L093 found Σw(y − yc)² 1–6 % away from FullProf on cases B, D and E. Before anything else was built (L037), the cause was found. There were three causes.

  • The reflection limits. The test took the .new files without FullProf's S(1/d)max (L035). It now takes each run's CODE.pcr (FullProf's refined values) with the limits of its CODE_calc.out. B and E then agree.
  • D's λ/2 widths. D gives Ratio < 0, and FullProf then reads U, V, W for the second wavelength on line 32 (manual p. 75: "If Ratio < 0 the parameters U,V,W … for the second wavelength are read separately"). The core had ignored them and kept the negative ratio, which made negative λ/2 peaks. Now Phase::uvw2 holds them: the import reads them with |Ratio|, the control file writes lambda2_uvw, the .pcr export writes them back, and the Jacobian holds them fixed. D's pattern now agrees with FullProf's to 1.3·\(10^{-5}\).
  • The linear background below its first point. FullProf reads a data point less than one step below the first background point on the line through the first two points. Every other point beyond the ends takes the end point's value. The step is FullProf's own, the "Step" of its .out. Evidence, from the .prf's Backg:
  • B and E: their points within the step are on the line;
  • A's point and LaMnO3_cub's (4.40° below 4.45°), exactly one step below: constant;
  • D's, exactly one data step below, but on the line, since FullProf's step there is a little longer than the data's. This is Convention::background_edge, with Model::fullprof_step set from the .out in the FullProf tests. On import it is kept rather than translated, since a translation would add background points and renumber the refined ones. The .prf's Backg offset (a quarter of the least background, L082) follows, since the extrapolated value can be the least (D).
  • Σ counts the points that get peaks. The core's statistics now leave out the points that its calculation leaves without peaks (Model::excluded_points, the rule of L033). Before, they used the plain regions. E's .out Σ leaves out a point that its .prf marks as counted. B's .out Σ is matched only when its two edge points are counted the way its calculation treats them.

What is left, frozen.

  • FullProf turns an excluded region into points with its own step, which for files of x, y, \(\sigma\) (Ins = 10) is not the data's: in B, D and E it is longer by 4·\(10^{-4}\) to 8·\(10^{-4}\) of a step. It also differs between two runs of the same data (\(\mathrm{Yb_{2}ZrWO_{8}}\): 0.040013 and 0.040016), so the core cannot derive it.
  • In B, the point at the low region's end has peaks and counts in FullProf, and the point before the high region has neither. The core treats both the other way.
  • In E, which lists its regions out of order (the high one first), FullProf calculates peaks inside the second region while leaving its points out of Σ, and counts them in N − P + C. symmetry_modes::EDGE_POINTS names these points. With FullProf's own Ycal over the core's point set, these points changed to FullProf's choice, Σ matches every .out within its print (four digits). The core's own Σ is within 0.03 % on all eight runs. Case A's window-edge points are the kind L035 froze.
  • The .prf's reflection ticks of D are at the Zero after FullProf's one cycle at NCY = 0. Its d_calc.pcr was not rewritten, so the test takes the .out's last Zero_pat1, as for the window experiments.
  • B's ticks past the data, near 180°, agree to 1.1·\(10^{-3}\)°, against 2.9·\(10^{-4}\)° within the data. Believed, not checked: FullProf's 2\(\theta\) in single precision near 180°. The tick tolerance applies within the data and 5° past it.
  • The magnetic run NdNi-HTO: its .hkl 2T and its .prf ticks differ from 0.004° to 0.164°. Not resolved, and left out of that cross-check.
  • Sent0 (D's \(\sigma\) factor below a given angle) is not implemented. D's Σ agrees without it.

Indexed. The sixteen .prf files of runs/modes/ are in fullprof_profiles: the eight CODE_calc.prf are compared (63 files now) and the eight pass-1 files are left out with their reason. Frozen there:

  • the window-edge points of A by modes and by coordinates (405 and 402, as srsclrrt's 404);
  • B's point at the high region's edge (Ycal max 0.065–0.145 there);
  • E's second region (Ycal max 0.358 inside it). All counts and sizes fail if they grow.
manuals/fullprof/runs/modes/ (FullProf 8.20: CODE.pcr, CODE_calc.prf/.out) · FullProf_Manual.pdf p. 75 (Ratio < 0, line 32) · core/src/powder.rs (Convention::background_edge, Model::fullprof_step, Phase::uvw2), lsq.rs (weights) · core/tests/symmetry_modes.rs (mode_runs_against_fullprof, EDGE_POINTS), fullprof_profiles.rs · L033, L035, L037, L082, L093
L0972026-10-11finding

How a refinement is steered in FullProf and GSAS-II, measured: FullProf's codeword multiplier halves the esd with the shift; a design for full control (W12, for Josu)

Josu asked for complete control of the refinement's path: the shifts, and the parameters linked to each other by symmetry or by choice.

FullProf.

  • Each codeword C = sign(a)(10p + |a|) carries a multiplier a, "the factor by which the computed shift … will be multiplied before use" (manual, codewords). Fields sharing p are one parameter, moved in the ratio of their multipliers.
  • Four relaxation factors by class (R_at atomic, R_an anisotropic, R_pr profile and cell, R_gl global; LINE 9) multiply the shifts.
  • Hard limits per parameter (Nre lines, LINE 47: number, low, high, step, IBOUND for hard or periodic bounds, the step for simulated annealing).
  • VARY/FIX commands with an optional multiplier "to limit the shift" (FP_COMMANDS).
  • The symmetry constraints, in automatic mode (Aut = 1).
  • Its advice: refine separately parameters correlated above 50 %.

Measured on the Mac (manuals/fullprof/runs/shift_control/, FullProf 8.20). One cycle of Y2O3 from a = 10.600 Å:

  • multiplier 1: shift +0.001567, esd 9.05·\(10^{-5}\);
  • multiplier 0.5: shift +0.000783 and esd 4.52·\(10^{-5}\);
  • R_pr = 0.5: shift +0.000783 and esd 9.05·\(10^{-5}\). So the multiplier damps the shift and scales the printed esd by the same factor, as the manual's \(\sigma\)(α_k) = a_k √((A\(^{-1}\))_kk \(\chi^2\)ν) implies when the derivatives do not carry a_k. The relaxation factor damps without touching the esd. A value's uncertainty cannot depend on how cautiously it is approached: an esd printed under a multiplier other than 1 is wrong by that factor.

GSAS-II (its code, commit fe944cd):

  • DefaultControls: 'max cyc', 'min dM/M' (convergence on the relative change of \(\chi^2\)), 'shift factor', 'SVDtol'; 'Marquardt' (the initial λ = 10^value, −3 by default) in GSASIIstrMain.py;
  • per-parameter minimum and maximum (parmMinDict, parmMaxDict), applied by setting a value back to its limit;
  • constraints of four kinds in GSASIImapvars.py: hold ('h'), equivalence with multipliers ('e'), a linear sum equal to a constant ('c'), and new variables ('f'), linear combinations refined in their parameters' place;
  • symmetry holds ("Fixed by symmetry", GSASIIstrIO.py) and equivalences generated automatically.

The core:

  • codewords' multipliers taken as the links' coefficients (how a lone multiplier is treated is to be checked against these runs);
  • the symmetry's ties;
  • limits that the engine only moves towards;
  • distance, angle and linear restraints;
  • Levenberg–Marquardt with SVD, cycles and convergence.

Proposed (APP_WORKFLOWS.md W12, for Josu): every control separate, visible and in the control file:

  • damping per parameter and per class, never touching an esd;
  • a maximum shift per cycle in each parameter's unit;
  • the Levenberg–Marquardt λ shown and settable;
  • the step shortened when \(\chi^2\) rises;
  • bounds with their behaviour (stop, reflect, periodic);
  • links: the symmetry's (shown, never editable); linear ones with an offset (shared sites, equal B's, one cell for two patterns); new variables such as mode amplitudes;
  • a .pcr's lone multiplier imported as damping, shared ones as coefficients, FullProf's esd scaling noted;
  • soft restraints with their weight and contribution to \(\chi^2\);
  • a refinement plan of steps;
  • each parameter's value, esd, shift, damping, bounds and links in view.
manuals/fullprof/FullProf_Manual.pdf (codewords; eq. 3.7; LINE 4 Nre, Cry; LINE 9; LINE 47) · manuals/fullprof/FP_COMMANDS.pdf (VARY/FIX) · manuals/fullprof/runs/shift_control/ (FullProf 8.20) · GSAS-II GSASIIobj.py, GSASIIstrMain.py, GSASIImapvars.py, GSASIIstrIO.py (local install) · core/src/lsq.rs, refinable.rs · APP_WORKFLOWS.md W12
L0962026-10-11work

App 0.8.1 merged: the audit's fixes

App 0.8.1 (branch app) merged into main; its 22 tests pass against core 0.22.0 and clippy is clean. It brings the fixes of the audit (L093; APP_WORKFLOWS.md W5 fixes, W7, W9):

  • tabs named by their file;
  • FullProf's copies and kept originals apart from the working control file;
  • the plot first (the bottom panel folded until a run, the inspector's summary when nothing is selected);
  • Yobs as markers;
  • the wavelength offered from the control file;
  • labelled icons;
  • Work on a Copy for reference folders. Requests 17 and 19–22 are filed with the core, and DESIGN.md §10 is replaced by W8.
app/ (commit b669131 on branch app, merged into main) · app/README.md · app/REQUESTS.md
L0952026-10-11decision

The cycles of a refinement seen, set, stepped, paused and resumed, with every stop explained (APP_WORKFLOWS.md W11)

Josu could not see the number of cycles anywhere, nor control it. What existed:

  • the control file's [refine] cycles and convergence (FullProf's NCY and Eps), editable only in the Editor;
  • Refine's Run (⌘R), One Cycle (⌥⌘R) and Stop (⌘.);
  • the cycles listed in the bottom panel during a run. A stopped refinement could not be resumed with its state: a new run restarts the Levenberg–Marquardt damping.

Accepted, as W11 of APP_WORKFLOWS.md:

  • the cycles and the convergence threshold visible and editable in the toolbar and the inspector, synced with the control file;
  • Run, Step (n cycles, then pause), Pause and Resume (the engine's state kept), and Stop;
  • progress ("cycle 3 of 10", \(\chi^2\) per cycle, time per cycle);
  • every stop with its reason: converged, cycles exhausted, diverging (\(\chi^2\) growing, a parameter jumping, an impossible value), a singular matrix named as R24 names it, or stopped by the user, recorded in the refinement log;
  • parameters fixed or freed during a pause, with the damping's restart said;
  • only accepted results become versions. The core gives the resumable refinement, the divergence criteria (documented thresholds, tested) and the stop reasons as data, with a command-line path; the app gives the controls and the display. Set aside: FullProf's way (NCY cycles, Eps, "Conv. not yet reached", no pause, no reason).
conversation of 11 October 2026 · APP_WORKFLOWS.md W11 · core/src/lsq.rs (refine_until), core/src/control/schema.rs ([refine] cycles, convergence) · app/ui/modules/refine/index.js (branch app: refine.run, refine.once)
L0942026-10-11decision

R30: the Publish module, a third stream with its own crate (publish/) and worktree

Josu proposed to start the module that exports everything for publication (R23) while the core and the app go on, and accepted this plan.

Why now. Everything a publication needs is built or being built: the CIF for deposition, distances, angles and bond valences (core 0.20.0), the refinement by modes (core 0.22.0), and the results file in pdCIF (R28, L092, in the cloud's round). A module that reads only the results file and the control file can be designed now and built as soon as that file exists. FullProf's runs come in through the core's reader of .prf and .hkl (app request 17).

What it does is appended to the brief as R30:

  • figures, tables, CIF, pdCIF and supplementary material;
  • a publication set rebuilt on export, with each item's provenance;
  • one rule for esd's;
  • targets as presets;
  • figures by Josu's ThesisFigures rules;
  • tables in LaTeX, Word, Markdown, HTML and CSV;
  • checks before export;
  • structures exported for VESTA. First: LaTeX tables and PDF/PGF figures for theses, then Word.

How it is organised.

  • A new crate publish/ (drs-publish) at the top of the repository, beside core/ and app/. It depends on the core and owns all formatting and rendering (tables, figures, fonts, Word, LaTeX), so the core stays physics only, with no font or PDF dependency. Its command line is drs-publish.
  • The app's Publish module lives in app/ and calls the crate as the other modules call the core.
  • A third stream in its own worktree (../20261009_diff-ref-str-publish, branch publish) edits only publish/ and asks the core and the app for anything else through requests (publish/REQUESTS.md), as the app does. This session merges it as it merges the others.
  • It starts with a design (publish/DESIGN.md), not code:
  • what publications contain, checked against the IUCr's Rietveld refinement guidelines (McCusker et al., J. Appl. Cryst. 32, 1999) and the journals' instructions;
  • the presets, and the publication set's format;
  • mock-ups made from real cases (\(\mathrm{Y_{2}O_{3}}\); \(\mathrm{Sr_{2}ScSbO_{6}}\) by modes) for Josu to accept.

Set aside:

  • the rendering in the core, which would bring fonts and PDF into the physics;
  • the module built in the app alone, with no command-line path;
  • waiting until the app is finished.
conversation of 11 October 2026 · ecosystem/data/brief.yml R30 · ~/.claude/skills/ThesisFigures/Rules.md (copied to publish/reference/) · L092, L093
L0932026-10-11finding

Core 0.22.0 merged and its mode cases run with FullProf: case B's two parents agree, cases B, D and E differ from FullProf by 1–6 %; the audit of app 0.8.0 accepted

Core 0.22.0 merged into main. Its entries, written as L089–L091 on its branch while main's L089 was being written, are L090–L092, and the references in the code, tests and pages were renumbered by topic: modes L090, file dictionary L091, results file L092.

The mode cases with FullProf (modes_runs.py, FullProf 8.20, outputs in runs/modes/). All eight runs went through both passes.

  • A_modes gives the course's \(\chi^2\) 2.07 again, and A_coords 2.05.
  • Case B's two parents, all amplitudes zero, give FullProf the same fit (\(\chi^2\) 100, Rwp 17.3), as the core predicted (97.7282 and 97.7281).
  • symmetry_modes::cases_b_d_e_patterns now compares the core with FullProf's outputs and fails: Σw(y − yc)² differs by 1–6 %. B_article 32519 against 32850; B_parents 259859 against 266000; B_thesis 81188 against 81420; D 140337 against 137000. In \(\chi^2\), E is 58.40 against 55.3. Under L037 this is to be explained before anything is built on it. Case A, which fullprof_profiles checks point by point, agrees.
  • fullprof_profiles fails because the sixteen new .prf files are not in its index (175 against 191), as it is meant to.

The audit of app 0.8.0, accepted by Josu and written into APP_WORKFLOWS.md (W7–W9 and the W5 fixes):

  • the Refine view with the plot as the main thing: Yobs as markers, excluded regions drawn and dragged, (Yobs − Ycal)/\(\sigma\) and the cumulative \(\chi^2\), "who is this peak", the correlation heat map and the parameters' paths, two versions overlaid;
  • tabs named by their file, a proposed name for an unnamed phase, the project's wavelength offered to a data file that lacks one;
  • the reflections view of DESIGN.md §10 amended. I obs is labelled "partitioned by the model". Forbidden reflections are shown on demand, with superstructure reflections labelled by k-vector. The record is R28's results file, not three files per refinement; CSV becomes an Export. Notes go in a visible file, not in .drs/;
  • "Work on a Copy" for reference folders;
  • FullProf's copies and kept originals not treated as working control files, and no false "missing". The app's leftovers in manuals/fullprof/runs/ (.drs/, "New Project" and "Untitled" folders, an imported .drc) were moved to the Bin in both checkouts.
core 0.22.0 · manuals/fullprof/runs/modes_runs.py, runs/modes/ (FullProf 8.20) · core/tests/symmetry_modes.rs, fullprof_profiles.rs · APP_WORKFLOWS.md W5, W7–W9 · app/DESIGN.md §10 (branch app) · L037, L090–L092
L0922026-10-11finding

The results file (R28): pdCIF, NeXus/HDF5 and a format of our own compared; pdCIF with a local prefix recommended, for Josu to choose

The three candidates were compared against what R28 lists and against who reads and writes them. The evidence is GSAS-II's code, FullProf's manual, News and *_prof.cif files, the IUCr powder dictionary as of 2026-09-08, the NeXus definitions as of 2026-10-05, and the code of pdCIFplotter, FOX, TOPAS-macros and EasyDiffraction. The journals' and databases' pages could not be opened from the session (the proxy refused them): what they ask for is quoted from search extracts and marked not checked.

  • pdCIF holds observed, \(\sigma\), calculated, background, reflections (hkl, phase, d, F\(^{2}\)), Rp, Rwp, Rexp, R_Bragg and the model with its uncertainties. Since the 2.5.0 dictionary (2026-09-08; whether the IUCr has approved it is not checked) it also holds each phase's curve, under DDLm names that no reader uses yet. It has no item for the difference, for \(\chi^2\) (only S), for a machine-readable excluded region (weight 0 at the points instead), for 2\(\theta\) positions of reflections (only through a peak table), or for the control file and the data file's name.
  • Writers: GSAS-II, without phase curves (G2export_CIF.py); FullProf with Rpa = −1, with R factors in percent where the dictionary has ratios and its \(\chi^2\) under a name outside the dictionary; TOPAS through user macros.
  • Readers: GSAS-II (the observed pattern only), pdCIFplotter (the whole fit), WinPLOTR, FOX.
  • NeXus/HDF5 has no definition for refinement results. No program was found that writes or reads one, and none of R28's items but the data has a standard name. It is binary, and in Rust it needs the HDF5 C library or young pure-Rust crates.
  • A format of our own holds everything and is read by nothing else.
  • Journals (not checked): the IUCr asks for "the results of the Rietveld refinement and the powder diffraction data … in CIF format", the profile deposited; Powder Diffraction asks for "the raw data file (as CIF)" and the Rietveld CIF; CCDC and ICSD take the refinement's CIF, with no profile asked for.

Recommended: pdCIF, written to the 2.5 dictionary with the DDL1 names wherever they exist, so that today's readers find the profile and the reflections. What it lacks goes in its own forms or under a local prefix: the difference is left to readers; \(\chi^2\) as S and under the prefix; excluded regions as weight 0 with the regions as text; the control file as a text field, with the data file's name and checksum, under the prefix (to be registered with COMCIFS). The reasons: it is what journals ask for, so the record is also the deposition; programs read it now; it is text; and the core already reads and writes CIF. Set aside: NeXus/HDF5 (no reader and no names for refinements, a C library) and a format of our own (read by no other program, against R28's "portable"). Nothing is built before Josu chooses; the page "The results file: the choice" has the comparison.

ecosystem/site/design/results-file/ · GSAS-II 305a159: imports/G2pwd_CIF.py, exports/G2export_CIF.py (WritePowderData, l. 2858), imports/G2pwd_HDF5.py · manuals/fullprof/news (2002–2019, Rpa = −1), 7-FullProf-Tutorial-SymmetryModes/LaMnO3/LaMnO3_prof.cif · COMCIFS/Powder_Dictionary 62bddcc (cif_pd.dic 2.5.0) · nexusformat/definitions d5c18f4 · rowlesmr/pdCIFplotter 960a13f, rowlesmr/TOPAS-macros dd84eb2
L0912026-10-11work

The file dictionary (R27): 18 kinds of file of a project started in the app, in the core and on the page Files; names proposed for Josu

ecosystem/data/files.yml names every kind of file of a project started in the app. For each kind it gives the folder and name pattern, the extension, the role, what the file holds, its format, the files that do the same in FullProf and GSAS-II (cited to the manual and to GSAS-II's code), what writes it, its status and the reason for its name. build.py turns it into core/src/formats/file_table.rs and the site page "Files". formats::files fills a pattern (path) and finds the kind of a path in a project (kind_of). A project from FullProf keeps FullProf's names, which formats::project names (L084).

Principles: readable names with no numeric codes; phases by their names; one folder per role; versions and results by their time; measured files keep their own names; spaces allowed, as in L080's versions.

The layout:

  • {project}.drc: the control file;
  • {control} background.drb: its background points;
  • Data/: the measured files as named, and {data} processed.xys;
  • Structures/: {phase}.cif and {phase} AMPLIMODES.txt;
  • Instrument/: {instrument}.irf;
  • Versions/: {control} {time}.drc and {control} refinement log.md;
  • Results/: {control} {time}.{results}, {control} {phase}.cif and {control} {phase} bonds.txt;
  • FullProf/: the export under FullProf's own names ({control}.pcr, .bac, {control}{n}.hkl, .irf, .xys), which FullProf requires;
  • .drs/: the app's state.

Of the 18 kinds, five are decided: the extensions .drc (L070) and .drb (L081), the versions and the refinement log (L080), and .drs/ (L067). The other 13 names are proposed for Josu to accept.

The core follows the dictionary. drs convert without --to writes {stem}.drc beside a .pcr, or FullProf/{stem}.pcr from a control file. drs files lists the dictionary or names the kind of a path.

Test file_dictionary:

  • the table is the YAML's, with every field filled and every placeholder known;
  • every pattern's filled name is recognised as its kind;
  • the .pcr exports of three FullProf runs write files of all five FullProf kinds, each named as the dictionary says;
  • a control file and a background file the core writes are named by project::describe with the dictionary's role.
core 0.22.0 · ecosystem/data/files.yml, tools/build.py, site/manual/files/ · core/src/formats/files.rs, formats/file_table.rs, main.rs · core/tests/file_dictionary.rs · APP_WORKFLOWS.md W5 · L067, L070, L080, L081, L084
L0902026-10-11work

Refinement by symmetry modes, step 1 (R29): a phase as a reference structure plus mode amplitudes, read from FullProf and AMPLIMODES, refined and checked against FullProf's runs

Josu's mode-crystallography material arrived on main (manuals/bcs, private: its README says what to trust; the book's chapters are not a reference). Step 1 of R29 is built on its data files and FullProf's runs, the theory primer, the description of FullProf's mode input, and the published papers' text. Nothing of the material is reproduced here: the cases are named by letter, and only the agreements are given.

The model (module modes). A phase keeps its reference structure (the parent written in the distorted phase's cell) and its modes, and its atoms sit at r\(^{0}\) + Σ_m Q_m·c_m·V_m. This is FullProf's rule, from its notes of 29 August 2008 – 16 January 2009, reproduced in its symmetry-mode tutorial. The amplitudes Q_m are refinable fields, with dictionary entries mode_amplitude and mode_vector. Their derivatives come by the chain rule through the coordinates. FullProf's Max_Amplitude is the limit |Q| \(\le\) value. Each irrep's amplitude is √(Σ Q\(^{2}\)) with polarisation vector Q/A (Orayech et al. 2015, J. Appl. Cryst. 48). Its esd is computed from the amplitudes' covariance, which reduces to FullProf's rule when the covariance is diagonal: FullProf neglects the correlations. So are the positions' esd's.

  • gram sums each mode over the distorted structure's primitive cell in a given metric.
  • project decomposes a structure given by its coordinates into the modes.
  • transform_cell gives the reference metric from a parent cell and AMPLIMODES's transformation.
  • displacements pairs a structure's atoms with the reference by symmetry.

The readers.

  • FullProf's mode .pcr: V_MODES with each vector's irrep; A_MODES with the codewords; Max_Amplitude.
  • The second integer of A_MODES (2 in every file of the material) is not a format flag. It is FullProf Studio's output: 2 puts each irrep's modes together as arrows, −2 as structures, ±N all modes one by one, −3 none, or a list (note of 11 January 2009).
  • AMPLIMODES's text output (read_amplimodes): parent and distorted structures, transformation, reference structure, atom mappings, summary of amplitudes, the block for FullProf. It handles tab-separated and space-separated copies, and repeated headings.
  • The control file has [[phase.mode]] (name, irrep, mode_amplitude, mode_vector). A mode phase's atom positions are its reference positions. The .pcr export writes Jbt = 6, Furth, V_MODES and A_MODES.
  • drs modes shows a phase's modes, irreps and positions. It can check the normalisation in an AMPLIMODES analysis's metric and project another structure onto the modes. drs refine reports each irrep's amplitude, esd and polarisation.

Tests (symmetry_modes, all passing).

  • Case A, positions: computed from FullProf's final amplitudes, they agree with its .out to the last printed digit. So do each irrep's own displacements (126 values), the lengths in Å, the irrep amplitudes with their esd's, and 10 positional esd's. The lengths are computed in the refined cell, as FullProf does.
  • Case A, refinement: the engine refines the .pcr's 17 parameters with N − P as FullProf's, and reaches its \(\chi^2\) within 0.1 %. Each amplitude is within 0.07 of FullProf's esd, and each esd within 0.3 % of FullProf's. This needs FullProf's reflections past the data, up to S(1/d)max from its .out, as fullprof_refinements does: without them \(\chi^2\) is 0.13 % low. A control-file round trip and the exported .pcr keep the modes and the 17 parameters.
  • Case A, profile: its two .prf files (by modes and by coordinates) are in fullprof_profiles, class exact (rms 4.6·\(10^{-5}\)). 404 window-edge points (TCH with X ≠ 0) up to 1.2·\(10^{-3}\) are frozen as in L035.
  • The normalisation: in AMPLIMODES's reference metric each mode sums to 1 Ų over the primitive cell and the modes are orthogonal, to 2.7·\(10^{-5}\) (the vectors' 6 printed decimals). This holds for case A, for both parents of case B, and for case E (R-3, hexagonal axes). Case E has no AMPLIMODES output; its parent edge is the one its La mode implies, which equals that of case B's second analysis to 5·\(10^{-5}\) Å, and its O modes then check.
  • AMPLIMODES: projecting the distorted structures it analysed (0_1, 0_4 \(\times 2\)) onto the .pcr files' modes gives its irrep amplitudes to the 4 printed decimals, with nothing left over (\(\le\) \(10^{-8}\) Å). For case A this also gives the per-mode amplitudes of its FullProf block, magnitudes only: a one-mode irrep's sign is a convention of the setting.
  • Case B: the two parents give the same positions (to 3.5·\(10^{-7}\)) and the same pattern. Every amplitude but GM1+'s scales with the metric, within the rounding's bound. GM1+'s also takes the shift of the reference O.
  • The coordinate refinement of case A projected onto the modes: every amplitude within 0.4 esd of the mode refinement's, and nothing left over.
  • Cases B, D and E are calculated from their .new files. FullProf's own outputs at those values come from manuals/fullprof/runs/modes_runs.py (to run on the Mac), which the test then compares.

Found on the way:

  • powder::find_file now also matches names case-insensitively. FullProf ran on file systems that ignore case, and case A's coordinate run names srsclrrt.dat for SrScLRRT.dat.
  • fullprof_profiles takes the .prf's scale from a point with counts on both sides; it took 0 from case A's first point.
  • FullProf prints Yobs 1.00 where the data have 0 counts. It happens at two points, both excluded; whether its reader or only its .prf does it is not known (believed: the .prf). For Josu: the 404 frozen window-edge points of case A; the run of modes_runs.py on the Mac.
core 0.22.0 · core/src/modes.rs, pcr.rs, powder.rs, jacobian.rs, refinable.rs, edit.rs, control/schema.rs, control/pcr_export.rs, main.rs · core/tests/symmetry_modes.rs, fullprof_profiles.rs, data/make_fullprof_profiles.py · ecosystem/data/parameters.yml (mode_amplitude, mode_vector) · manuals/fullprof/runs/modes_runs.py · manuals/fullprof/7-FullProf-Tutorial-SymmetryModes (FullProf's notes) · manuals/bcs (private)
L0892026-10-11work

Core 0.21.0 and app 0.8.0 merged; Josu's mode-crystallography material added, its book's chapters marked as no reference

Core 0.21.0 (L087, L088) and app 0.8.0 (L075, L080) merged into main. The core's 133 tests and the app's 21 pass, clippy is clean. The app's count of files the core does not name (frozen at 15) read 16 on the Mac because of a Python cache folder in manuals/fullprof/runs/ that git ignores; it is a local file, now deleted.

App 0.8.0, "the first contact", built in three passes after Josu's tries:

  • each project in its own window, switched in one gesture;
  • a data file opened first, with the formats listed by name; peaks and background suggested and explained, never done unasked;
  • the sidebar by role, with every file named by the core (request 14 closed: 15 of 2698 files unknown, 527 with core 0.17.0);
  • versions in a visible, locked Versions/ folder with a refinement log;
  • any file as text;
  • panels that detach and dock back.

manuals/bcs/ holds Josu's material on mode crystallography for R29 (L086):

  • the UM6P 2024 course's FullProf runs and AMPLIMODES outputs (five cases: \(\mathrm{Sr_{2}ScSbO_{6}}\), \(\mathrm{Sr_{2}CoTeO_{6}}\), \(\mathrm{La_{2}CoMnO_{6}}\) in P2₁/n and R-3);
  • the theory primer;
  • the course's page on FullProf's mode input;
  • the papers' text;
  • a book and a site drafted by agents. Case A (\(\mathrm{Sr_{2}ScSbO_{6}}\), Fm-3m → P2₁/n, 12 modes) was run again with FullProf 8.20: the stored \(\chi^2\) 2.07, R factors and amplitudes. A survey found errors in the book's chapters (an invented "MODES" block, wrong amplitudes and normalisation), so manuals/bcs/README.md names the data files and FullProf's runs as the references and the chapters as none. Josu's course repository is not copied (a repository of its own, its data the same as the UM6P files); its three reference pages are. The Prussian White inventory is unpublished and stays private.
core 0.21.0, app 0.8.0 (branch app, d0e11b3) · manuals/bcs/README.md (commit 3fc3938) · mode-crystallography-manual/course-um6p-2024/04_tools_materials/1_sr2scsbo6_300K_structure_014_data_LR (FullProf 8.20)
L0882026-10-10work

The data operations' settings in a dictionary (request 16), a data file written (request 12), the line of each point (request 15)

1. Request 16. ecosystem/data/operations.yml describes the ten data operations (mask, delete, smooth, rebin, normalize, add, subtract, merge, convert, wavelength) and the two analyses (peak_search, auto_background), as parameters.yml describes the parameters. For each operation it gives the meaning, how \(\sigma\) is carried and the definition it follows. For each of its 30 settings it gives the key, name, unit, meaning, range, default, proposed value, what raising and lowering it does, and the source. build.py writes the core's table operation_table.rs and the site page "Data operations: the settings". data_ops::settings(id) and keys_of(op) give it to the app. The parser now refuses a key that is not one of the operation's own (before, a key of another operation was accepted and ignored). drs data settings [<operation>] prints it, and --help after drs data op <operation>, peaks or background prints that operation's settings. The defaults are constants of data_ops (DEFAULT_MIN_SIGNIFICANCE 3, DEFAULT_MAX_ITER 10, …). Test operation_dictionary checks:

  • the dictionary against the table, the parser's keys and each operation's keys;
  • that no field is empty;
  • each default against its constant, and the arPLS λ against default_log_lambda. 2. Request 12. formats::write(scan, "xys" | "xye") writes a scan as text that the registry reads back: FullProf's XYDATA (Ins = 10) or the '#'-commented xye of GSAS-II and Topas. Numbers are written in their shortest round-trip form, and a point with no weight gets \(\sigma\) = \(10^{30}\), which read takes back as no weight. drs data read|op|apply --out f.xye|f.xys writes that format, and the .pcr export writes its processed data through it. Test data_write: 294 data files of the material read, 283 2\(\theta\) scans written in both formats and read back to the bit. The one exception is GSAS-II's rule for xye: a point with y \(\le 0\) is read as y = 0 with no weight (947 points). xys keeps them. 3. Request 15. formats::point_lines(text, scan) gives the line (from 1) of each point, or None when the file does not give it plainly. There are two cases:
  • one point a row: the next numeric line whose first value is the point's x and which holds its y;
  • FullProf's free format: Ti, step, Tf, then the intensities in order (y2o3.dat: eight a line, point 9 on line 4). Over the material: every free-format (41), xye (52) and PSI DMC (4) scan has its lines, as do 9 of 10 xys and 9 of 13 GSAS scans. The others give None, rather than a guess: GSAS RALF with x in its own units, an XYDATA file in TOF, and the ILL formats with a header to each block. 4. Requests 13 and 14 were done in core 0.18.0 and 0.20.0 (check, drs check with its data; formats::project::describe). app/REQUESTS.md belongs to the app stream, and main's copy does not yet hold requests 14–16, so it is not edited here: the app stream closes 12–16 by naming core 0.21.0 when it merges main. It has to merge anyway, since app 0.8.0 does not compile against core \(\ge 0.19.0\) (L085).
core 0.21.0 · ecosystem/data/operations.yml, tools/build.py, site/manual/operations/ · core/src/operation_table.rs, data_ops.rs, formats/write.rs, formats/mod.rs, control/pcr_export.rs, main.rs · core/tests/operation_dictionary.rs, data_write.rs · app/REQUESTS.md (branch app) 12–16
L0872026-10-10finding

FullProf's background settled on all eleven variants: the quarter within single precision, the spline's end point at the data's end and none past it; the \(\chi^2\) differences frozen per variant

background_points now checks every variant of runs/background/ and reports each one, not only up to the first failure (L085). All twelve FullProf runs it predicts pass; bacpairs is the asked-for mistake of L082 (FullProf's \(\chi^2\) 19.9, the core's prediction 19.926).

  • The quarter at Nba = −31. The least background is taken over the data's 13091 points, as before. The offset that the printed values allow is [3.092533, 3.092538]. The quarter of the least background is 1.1·\(10^{-7}\) below that interval, and single precision explains it. FullProf works in single precision: its least value is its own background at that point (|B|·2⁻²⁴ from the core's), and the quarter is rounded to single precision once more (|c|·2⁻²⁴). Together they allow 3.7·\(10^{-7}\). The rule is fixed with that allowance and is not frozen: the allowance is derived, not fitted. Every other variant puts the quarter inside the interval. No tolerance was widened: the bound on each point (L081) is unchanged.
  • spl62 (62 points): within the print; quarter inside.
  • spl6early (the points end before the data does): FullProf's own end point at the data's last x, with the last point's value, as in L081. Within the print.
  • spl6beyond (the last point past the data): no point is added, and the core's natural spline through the points is FullProf's. Within the print.
  • spl6thmax (Thmax before the data's end): FullProf still reads all 13091 points to 145.9° and puts its end point at the data's end, not at Thmax. Its .prf and \(\chi^2\) (1.2626) are the same as spl6's.
  • lin62: within the print; its \(\chi^2\) is bacfirst's, as it should be (the same background at the same points). The import (powder.rs) already did all of this; its comment now names L087.

\(\chi^2\) (the core's against FullProf's header, to its four decimals): lin16 −0.00006, spl16 −0.00002, spl6 −0.00104, lin6 −0.00092, spl8 −0.00055, spl31 +0.00022, spl62 +0.00018, spl6early +0.00021, lin62 +0.00017, spl6beyond −0.00082, spl6thmax −0.00104, bacfirst +0.00017. The background is not the cause: it agrees within the offset interval (widths of 5·\(10^{-6}\) to 6·\(10^{-5}\)). Two explanations were tested and rejected:

  • that FullProf rewrote the .pcr (it did not);
  • that it uses the values after its cycle (they do not match). The difference comes from the Bragg part of Ycal. It differs from FullProf's by about \(10^{-4}\) relative, the same in every variant, and each variant's residuals weight it differently. FullProf's own .prf columns give its header \(\chi^2\), and the core's Ycal agrees with FullProf's for y2o3_structure and lin62. Each difference is frozen in the test at its size, rounded up to \(10^{-5}\) (the test fails if one grows, or if a frozen value exceeds 5·\(10^{-4}\) + 1.5·\(10^{-3}\) \(\chi^2\)). These twelve frozen differences are listed for Josu to accept: below 0.1 % of \(\chi^2\), from a profile difference of about \(10^{-4}\).

fullprof_profiles indexes the ten new .prf files as compared in background_points (make_fullprof_profiles.py), and it passes.

core/tests/background_points.rs (fullprof_interpolation, FROZEN_CHI2), core/tests/data/make_fullprof_profiles.py, fullprof_profiles.txt · core/src/powder.rs, background.rs · manuals/fullprof/runs/background/ (FullProf 8.20) · L081, L082, L085
L0862026-10-10decision

R27–R29: our own file names in a file dictionary, a results file of our own in place of .prf, and refinement by symmetry modes

Josu asked for three things, appended to the brief.

  • R27, our own file names. A project started in the app names its files by a scheme of ours, written once in a file dictionary (pattern, extension, role, contents, format, FullProf and GSAS-II equivalents) and used by the core, the command line and the app, as the parameter dictionary is for parameters (L044). A project from FullProf keeps FullProf's names. formats::project (L084) already names 73 kinds of FullProf's files and is where the file dictionary's reading side lives.
  • R28, a results file in place of .prf. FullProf's .prf has an odd internal structure (a header, the profile with Backg offset by a quarter of its least value, then the reflections, L082). Josu weighed a profile "mounted" each time from the model and data against a new file that holds everything, and prefers the second, for taking the results to other programs. Both are kept: the app draws from the model, recomputed when it opens, so a view is never stale; the results file is the record of each refinement and the file for exchange; .prf remains an export. Its format is to be chosen with evidence. Candidates:
  • the IUCr's powder CIF (pdCIF), the standard for depositing powder profiles. GSAS-II reads it, and the core reads it through the registry (L062). Whether journals require it, and which programs read and write it, is to be measured, not assumed;
  • NeXus/HDF5, the facilities' format (binary, a library to depend on);
  • a format of our own. The choice is Josu's, on that comparison.
  • R29, refinement by symmetry modes (P13, which L020 placed "later", now next). The material holds FullProf's own mode refinements: manuals/fullprof/FP_tests/LMO_Modes_Pnma.pcr, LMO_Modes_Pbnm.pcr, LMO_Modes_Pbnm_ani.pcr, Ho2BaCuO5/TOF/Ho2BaCuO5_1p5K_modes.pcr and the sequential series in Ho2BaCuO5/CW/Sequential-Modes/; the FullProf News documents the mode input from 29 August 2008, with later changes in 2008–2010 and magnetic modes in 2016. The plan:
  • first, read the modes as AMPLIMODES, ISODISTORT and FullProf's .pcr give them and refine their amplitudes, checked against FullProf's runs of those files;
  • then compute the modes from the parent and the distorted structure, which needs the irreducible representations and is a question of data and licence (Bilbao, ISOTROPY);
  • the papers are for Josu to download (the cloud cannot reach the journals). Set aside: keeping FullProf's file names for new projects; keeping .prf as the results file.
conversation of 10 October 2026 · ecosystem/data/brief.yml R27–R29 · ecosystem/data/proposal.yml P13 · manuals/fullprof/FP_tests (mode .pcr files) · manuals/fullprof/news (2008–2010, 2016) · L020, L044, L062, L082, L084
L0852026-10-10finding

Core 0.20.0 reviewed with FullProf's new background runs: the .bac predicted exactly; the quarter rule fails for 31 spline points; app 0.8.0 not merged

Core 0.20.0 (L081–L084) merged into main. FullProf (February 2025) was run on the eleven new background variants of background_runs.py (runs/background/); their outputs are committed.

  • The .bac files: FullProf's \(\chi^2\) is 1.19 with the core's first-format file (bacfirst) and 19.9 with the same values as pairs (bacpairs), as the core predicted (1.193 and 19.926). The 1.80 of L079 came from the file written by hand for that check, not from FullProf, and is withdrawn.
  • background_points passes for Nba = 17, −17, −6, Nba = 6 linear and −8: the background within the printed precision, with an offset of a quarter of the least background. \(\chi^2\) as FullProf's to 0.0010 or better.
  • It fails at −31: the offset allowed by the printed values is [3.0925, 3.0925] and the quarter of the least background is 3.0925 to four decimals, yet outside the interval. The variants after it (−62, spl6beyond, spl6early, spl6thmax, lin62) are not reported, since the test stops there.
  • fullprof_profiles fails because ten .prf files are new on disk and not yet in its index (163 counted against 173), as that test is meant to do. The core's other tests pass (126 of 128).

App 0.8.0 (branch app) was merged and taken out again before pushing. It does not compile against core 0.19.0 and later: BackgroundModel::Spline is not matched in modules/refine.rs. With that matched, two of its tests fail against the core's names of 0.20.0:

  • .pcr.in files get the role "other" where the test expects "control";
  • Y2O3's data file is credited to y2o3_structure_conv.pcr instead of y2o3_structure.pcr. The app stream is to merge main, use the core's describe (request 14) and bring its tests to it.
manuals/fullprof/runs/background_runs.py, background/ (FullProf 8.20) · core/tests/background_points.rs, fullprof_profiles.rs · app/src-tauri/src/modules/refine.rs, project.rs (branch app, 42231a6) · L079, L082, L084
L0842026-10-10work

Core 0.20.0: FullProf's background, drs check with its data, every file of a project named, distances, angles, bond valences and a CIF for deposition

1. Background (L082): the .pcr import adds FullProf's point at the end of a spline and the export drops it; a .bac is written in FullProf's first format at the data's points (background::write_bac, write_bac_at, bac_text; the pairs format kept as write_bac_pairs); drs background convert --data; drs background store writes a .bac at the control file's data points. Test background_points: the bound of L081 on Nba = 17, −17 and −6 (fullprof_interpolation); fullprof_bac_pairs (19.926 against FullProf's 19.9). The .pcr export writes the points in <stem>.bac with Nba = 1 when the control file keeps them in a .bac, or when they are more than the .pcr's field holds (9999); the round trip of control_file gives the same pattern to the bit, and the two .bac in pairs come back as the background at the data's points. drs check warns of a .bac in pairs (bac-format), which FullProf misreads. 2. drs check <file.pcr> [<data>] (L079): the data as FullProf takes them, from the command line, else CODFIL.dat next to the .pcr; a file found only by the comment or in the folders around is used with a hint (data-elsewhere) that FullProf would not find it so. check::check_path_with_data, check_pcr_with_data. Test check_diagnostics::pcr_data; the broken .pcr cases are now checked with the Y2O3 data given, as broken_runs.py runs them. 3. The files of a project (W5, L080): formats::project names 73 kinds, each with its role (control, input, output, other, unknown) and the source that says what it is: FullProf's manual (Input files p. 8, Output files pp. 9–12), the News, the tutorials, the manuals of DICVOL, FAULTS, WinPLOTR, Bond_Str; "not documented" where only the content says. describe adds the registry's data and "unknown"; a FullProf .dat whose layout the registry cannot pick alone is still data. Test file_kinds: the 2649 files of manuals/fullprof/runs, the tutorials and FP_tests against tests/data/file_kinds.txt (written by make_file_kinds.py from a survey of every kind, apart from the classifier), none named a table. The survey found the old rules naming all 80 .log files listings, and finding 51 of 160 .prf and 48 of 142 .hkl. drs data info uses it. For the app: describe is what request 14 (W5) needs; the app still has its own extension map. 4. Distances, angles, bond-valence sums (P12) (L083): modules bonds and cfl (FullProf's CFL, with the standard deviations it prints), table bv_table (CrysFML's, by make_bv_table.py); drs bonds <file.cfl| .pcr|.drc>. Tests bond_str (77 .dis files; the runs' .pcr give the CFL's distances). 5. A CIF for deposition: cif_write::deposition (formula and Z, symmetry, cell with uncertainties, radiation, range, Rp, Rwp, Rexp, goodness of fit, R_Bragg, numbers of parameters, restraints and reflections, atoms with Wyckoff letters, U_ij, bonds and angles; "?" for the authors); drs cif <file> [--cycles n]. Test cif_deposition against FullProf's CIFs (L083). 6. FullProf's background outputs in the other tests (they arrived with L079): fullprof_metric leaves out an .out whose run stopped before the phases, only when its .run.log says why (y2o3_bac62: "end of file *.BAC"); fullprof_profiles lists the three background .prf as compared in background_points; the model text reads floats exactly (serde_json's float_roundtrip: one value of the 13091 of y2o3_bacfirst came back one ulp off).

core 0.20.0 · core/src/background.rs, check.rs, formats/project.rs, bonds.rs, cfl.rs, bv_table.rs, cif_write.rs, main.rs · core/tests/background_points.rs, check_diagnostics.rs, file_kinds.rs, bond_str.rs, cif_deposition.rs · manuals/fullprof/runs/background_runs.py
L0832026-10-10finding

Bond_Str's distances, angles and bond-valence sums reproduced on 77 .dis files; three of its rules that the default does not follow

FullProf lists distances, angles and bond-valence sums through its program Bond_Str (CODFILn.dis, CODFILn_sum.bvs; manual p. 11), from the CFL it writes after a refinement. The core's bonds module was written from CrysFML's source (the PyPI package crysfml 0.8.0: Calc_Dist_Angle_Sigma, Distance_and_Sigma, Angle_and_Sigma, Calc_BVS, Set_Atoms_Conf) and compared with the 77 .dis files of the material (LaMnO3 \(\times 2\), Yb2ZrWO8 \(\times 3\), the LaMnO3 D20 sequential series \(\times 72\)), within the printed precision:

  • 1474 distances, every neighbour at the same position; 2160 triangles of angles; 308 atoms' bond-valence sums with 1378 bonds, coordination, mean distance, distortion and the four instability indices; the d0, B0 and radii Bond_Str printed are those of CrysFML's table, written into core/src/bv_table.rs by a script.
  • Two numbers lose more than the print to single precision, with bounds derived in the test: an angle of a flat triangle (FullProf 0.03° where it is 0: √(2δ) at the arc cosine's end) and the distortion \(10^{4}\)(⟨d\(^{2}\)⟩/⟨d⟩² − 1), a difference of numbers near 1.

Bond_Str's rules that the default does not take (kept as bond_str_* functions, each checked): 1. Occupancies normalised to the first atom (Set_Atoms_Conf, fac1): the first atom is taken as full. Yb2ZrWO8's .pcr writes Occ as atoms per primitive cell (Yb 0.5, Zr 0.25, W 0.25 on 4a, O 2.0 on 8c of F m -3 m), 48 times FullProf's chemical × m/M; the scale factor absorbs it in the pattern. Bond_Str then takes Yb as 1 and O as 2.0 and prints Yb's sum as 7.500; with the chemical occupancies (Yb 0.5, O 1.0) it is 3.750, and O's 1.773 for Bond_Str's 3.547. The core reads such an Occ as atoms per primitive cell when the documented reading puts an atom above 1 and this one does not (bonds::chemical_occupancies); the occupancy check uses the same reading (it read LaB6's Occ "× m", which this reading includes for a primitive cell). 2. No term for the cell in a distance's standard deviation: an Mn–Mn distance of c/2 is printed without one. With that, every angle's standard deviation is Bond_Str's; the default's, the angle's own derivative with the cell's terms, differs at 2047 of 2160 (frozen count). 3. Two ends as independent: an atom bonded to its own image gets both ends' variances; the default moves both ends together. Six O1–O1 distances of LaMnO3_cub differ, all of that kind (frozen count). The default is the first-order propagation of the cell's and coordinates' standard deviations, each quantity differentiated as a whole; with no covariances given, they are taken as independent.

The CIF. Against the CIFs FullProf wrote (Rpa = −1) for LaMnO3 (two refinements), PPH3 and citric acid, at the values of their .pcr: 339 values (cell, coordinates, U_iso, occupancies) within both prints; U_iso's uncertainties as FullProf's; Rp and Rwp within 0.0007 % (FullProf writes them in %, the CIF dictionary as fractions); \(\chi^2\) within 0.5 % once counted over FullProf's number of parameters. Frozen, not explained: LaMnO3ref's Rp and Rwp are 0.22 % above FullProf's at its .pcr's values (5.14 % against 4.92 %); LaMnO3's cell uncertainties are up to 2.25 times FullProf's, which also refined seven symmetry-mode amplitudes (its positions' uncertainties come from those, and the core does not read modes; P13). The first is for Josu: LaMnO3ref is not in runs/, and a run of it there would say whether the .pcr matches its CIF.

CrysFML (crysfml 0.8.0 on PyPI, LGPL-3): Src/CFML_Geom/Geom_Distances.f90, Geom_Angles.f90, Src/CFML_EnBVS/EnBVS_CostF.f90, EnBVS_SetTab.f90, Src/CFML_Tables/Tab_Set_BVST.f90 · docs/bond_str.pdf · manuals/fullprof/*/*.dis · core/tests/bond_str.rs, cif_deposition.rs
L0822026-10-10finding

FullProf's background read: its spline has one point of its own at the end of the data; the .prf prints Backg less a quarter of its least value; a .bac is read in the first format only

From FullProf's own .prf, .sum and .out of runs/background/ (L079), with the bound of L081: FullProf's Backg is printed to 0.1 (h = 0.05), and a value computed in single precision carries |B|·2⁻²⁴ more, so one offset c must put every point within h + u of the core's; the interval of such c is found, not estimated.

Linear (Nba = 17). The core's linear interpolation fits: ours − FullProf's spans 0.1000 over 13091 points, and one offset fits. The 0.0503 of L079 was the error of the median offset, not of the background.

Splines (Nba = −17, −6). FullProf's cubic spline is the natural one through the points given and one point of its own at the data's last point (145.9°) with the last point's value. With it, Nba = −17 spans 0.0999 and Nba = −6 0.1000: both within the printed precision. Without it the natural spline spans 0.139–0.157 for −17 (the excess only in 129.96–144.97°, near the last two points, 144.14° and 144.97°) and 0.388 for −6. Tried and rejected: not-a-knot, clamped, PCHIP and Akima splines; splines in sin \(\theta\), tan \(\theta\) or d; knots rounded to 0.01°; an extra point at the first data point (worse); one at 180° (0.27 and 0.21). Nba = −6 is a spline: its .out says "Background interpolated by cubic splines between the 6 points given". The .pcr import now writes FullProf's point into the model, and the export drops it again (Nba = −(n − 1)); a spline that does not end with it is exported with a note that FullProf adds it.

The .prf's offset. FullProf prints Backg less a quarter of the background's least value: 3.5754 for the 17 points (least 14.30), 3.5627 for the spline of 17, 3.8206 for that of 6 (least 15.28). That is why Nba = −6's offset differed (−3.82 against −3.57): its least value differs. In each case the quarter falls inside the interval of offsets allowed, which is \(10^{-4}\) wide.

\(\chi^2\). At NCY = 0 the core's \(\chi^2\), counted over the .pcr's ten parameters, is the .prf's to its fourth decimal for Nba = 17 and −17 (0.00006 and 0.00003 away); for Nba = −6 it is 0.0010 lower (1.2616 against 1.2626), with the background within the printed precision. Not explained; frozen in the test.

The .bac file. The manual (p. 8) gives two formats, and its footnote says the second, a comment and pairs, "is not available at present". FullProf 8.20 reads any .bac as the first format: a first line start, step, end, then the intensities in order. Josu's pairs file at the 13091 data points therefore became the intensities x₁, y₁, x₂, y₂, … at successive points: the core's \(\chi^2\) under that reading is 19.926 (FullProf 19.9). The 62 pairs of L079 ran out of numbers ("end of file *.BAC"). The core now writes a .bac in the first format at the data's points. Not explained: FullProf's \(\chi^2\) 1.80 with the first format. The core's \(\chi^2\) with the same background at every point is 1.19 (FullProf's own runs give 1.20 with the points in the .pcr). Moving the background by 1, 2 or 5 steps, using the 17-point background, or using the .prf's printed Backg (1.50–1.64) does not give 1.80. Josu's file is not in the repository. background_runs.py now writes bacfirst (the core's first-format file) and bacpairs (the same values as pairs) for him to run; the core predicts 1.19 and 19.9.

New variants for the Mac (background_runs.py, to test the rules where they could fail): lin6 (the 6 points, linear: is the offset again a quarter of the least value?), spl8, spl31, spl62 (Nba = −8, −31, −62), spl6early (6 points ending at 120.79°, the extra point far from them), spl6beyond (last point at 150°, past the data: does FullProf still add one?), spl6thmax (Thmax 140°: is the point at Thmax or at the data's end?), lin62, bacfirst, bacpairs. The test asserts the predicted ones when their .prf appears and reports the others.

manuals/fullprof/runs/background/ (FullProf 8.20) · background_runs.py · core/tests/background_points.rs · manual p. 8 and its footnote, p. 69, p. 78
L0812026-10-10decision

Josu accepts the .drb background file and the bound for a reference printed with limited precision

Josu accepted two proposals of L078–L079:

  • the background-points file of our own, .drb (format = "diff-ref-struct background 1", interpolation, points), with that extension;
  • the rule for comparing with a reference printed to limited precision: the bound is set from the reference's precision (half its last digit) and from the error of what is estimated from the rounded values, derived and documented in the test, never chosen to make it pass. Applied first in background_points (L082): an offset estimated from rounded values carries the rounding itself, so the test asks only that one offset exists that puts every point within the printed precision.
Josu's message of 10 October 2026 · L078, L079
L0802026-10-10decision

The project's files shown by their role, versions in a visible folder with a refinement log, switching between projects, any file as text

Josu, after opening a FullProf run folder in app 0.7.0. He found:

  • .fou and .pcr.in named "Table of columns", .inp unnamed;
  • New Project creating a project inside the open one;
  • an alphabetical list with outputs mixed among the files that matter;
  • no way to open data and text files in the Editor.

His requirements, written into APP_WORKFLOWS.md:

  • W1: changing from one project to another in one gesture (⌘`, the project pop-up in the title bar with each project's state, ⌘K, native tabs, the Window menu by project name); New Project never nested;
  • W5: the sidebar by role (Data, Control, Versions, Inputs from what the control file names, Outputs with stale ones dimmed, Other), a switch to the folder as on disk, hover information and Quick Look; every file named by what it is, unknown when not known, checked by a test over every run folder and tutorial;
  • versions in a visible Versions/ folder, closed by default: each version the control file exactly as it was, locked, named with its time; its metadata kept in .drs/ and shown on hover with what changed; and a plain-text refinement log in the same folder, one appended entry per step. This replaces .drs/history of L067;
  • W6: any text file opens in the Editor, a data file as text beside its plot with the selection synced. Set aside: versions hidden in .drs/ (Josu wants to see them and keep them as files); metadata written into the version files (they must stay as they were).
conversation of 10 October 2026 · APP_WORKFLOWS.md (commit 8b91186) · screenshot of app 0.7.0's sidebar on manuals/fullprof/runs/NdNi-LTOa
L0792026-10-10finding

Core 0.18.0 and 0.19.0 reviewed with FullProf: the export holds on every run FullProf accepts; FullProf's background read; what FullProf says of the broken files

Cores 0.18.0 and 0.19.0 (L076–L078) merged into main; the core's 118 tests passed before FullProf's background runs were added. Run on the Mac with FullProf (February 2025).

The export. export_runs.py: all nine runs the core imports give FullProf's Rp, Rwp, Rexp and \(\chi^2\) exactly, LaB6 included (it now declares 18 and codes 1–18). The export to .pcr is claimed.

The background points (background_runs.py, outputs in runs/background/):

  • Nba = 17 (17 points): FullProf joins them linearly. Its .prf prints the background to 0.1 count; after the .prf's constant offset (−3.575) the core's linear background is within 0.050 of it at all 13091 points, rms 0.029, which is the rounding itself (0.1/√12 = 0.029). The test background_points now fails, by 0.0503 against a bound of 0.05: the bound is the reference's own precision, and the 0.0003 is the error of the offset estimated from rounded values. How to set the bound from the reference's precision, not widened, is for the cloud to write and Josu to accept.
  • Nba = −17: a cubic spline, natural (SciPy's CubicSpline(bc_type = "natural"): max 0.079, rms 0.030); the not-a-knot spline is 1.0 away and the linear 1.3. The core's reading of negative Nba as natural splines is right for −17; the 0.079 at some points is not explained.
  • Nba = −6 (6 points): no interpolation fits (linear rms 0.156, natural 0.075, not-a-knot 0.084, clamped 0.172), and its offset (−3.82) differs from the others'. What FullProf does with Nba = −6 is not explained.
  • Nba = 1 with the 62 points in a .bac: FullProf stops, "end of file *.BAC". A .bac needs a value at every data point: the same background interpolated at the 13091 points runs, in the first format (start, step, end, then the intensities) with \(\chi^2\) 1.80, and in the pairs format with \(\chi^2\) 19.9. The second is not explained. The core's export of a background file must write it at the data's points, in the first format.

The broken files (broken_runs.py, broken_check.txt): FullProf run on each .pcr of core/tests/data/broken/, against drs check of the same control file.

  • FullProf says nothing and writes no result: a wavelength of 0; a cell with no reflection in the range.
  • FullProf says nothing and refines to \(\chi^2\) 1.20: a coordinate fixed by the site set free; the Caglioti U given to two parameters; Thmin and Thmax of another pattern; a value in another unit.
  • FullProf refines without converging: two atoms filling one site twice (15 cycles, \(\chi^2\) 31.7); zero shift with sample displacement (8 cycles); the overall B with every atom's B (5 cycles); I_G with U and W (4 cycles).
  • FullProf names the problem in its own terms: G2 with G1 = 1, "Singular matrix!!, problem with Or2_ph1_pat1 no. 73"; a codeword hole, "Hole in the matrix!, missing element 3"; two parameters too many, elements 73 and 74; a missing .irf, by name.
  • The core reports each one before the run, at its place, with the reason, as R24 asks.
  • drs check of a .pcr cannot find its data: FullProf takes the data file from its command line, and the core reads only the .pcr. A .pcr check should take the data file as FullProf does.
manuals/fullprof/runs/export_runs.py, export_check.txt, background_runs.py, background/, broken_runs.py, broken_check.txt · core/tests/background_points.rs · FullProf.2k 8.20 (Feb 2025) · L076–L078
L0782026-10-10work

The core's part of R25 and R26: background points in a file or inline, splines, a function fitted to points, a control file rebased onto new data

Core 0.19.0. Main was merged first (app 0.7.0, L074; R25 and R26, L075). The entries of core 0.18.0, written as L074 and L075 on this branch, became L076 and L077.

Background points (R26, W4). Module background.

  • Files:
  • our own .drb, a strict subset of TOML like the control file (format = "diff-ref-struct background 1", interpolation, points), for Josu to accept with its extension;
  • FullProf's .bac in its two formats (manual p. 8): "start step end" then the intensities, or a comment line then pairs. Read and written; FullProf's own .bac files of NdNi-HTO and LaB6 are read.
  • The control file's [pattern.background] holds the points itself (background_points) or names a file (file = "…"), at the user's choice, with interpolation = "linear" or "spline". drs background store moves the points between the two.
  • A .pcr with Nba = 1 is now imported. Its <stem>.bac gives b1·B + b2 (manual eq. 3.6, p. 46), and its control file names the .bac. A stretched or shifted file (b3, b4 ≠ 0) and refining b1–b4 are refused for now.

Interpolation.

  • Linear between the points, as FullProf's Nba \(\ge 2\).
  • A natural cubic spline through them (new BackgroundModel::Spline, with analytic derivatives for refining the points). It agrees with SciPy's CubicSpline(bc_type = "natural") to 4·\(10^{-16}\) at 350 points of Y2O3's 62.
  • Which Nba FullProf reads as splines is not settled by its documents:
  • "If Nba<0 but ABS(Nba)>4" (manual p. 69);
  • "Nba \(\le\) −4" (p. 78);
  • while −4 is the Fourier series and, since the News of 17 June 2011, −5 the Chebyshev series. The core reads Nba \(\le\) −6 as splines and exports a spline of six points or more as Nba = −n.
  • manuals/fullprof/runs/background_runs.py writes Y2O3 with 17 points linear (Nba = 17), the same 17 as splines (Nba = −17), 6 as splines (Nba = −6), and all 62 in a .bac (Nba = 1). Run with FullProf on the Mac, its .prf files settle the rule and the kind of spline. The test background_points compares them as soon as they are there.

A function from the points. background::fit: a Chebyshev series over [from, to], or FullProf's polynomial in 2\(\theta\)/origin − 1 (eq. 3.4), fitted by least squares (Householder QR).

  • Against numpy's chebfit (6 and 12 terms) and polyfit (6 terms) on Y2O3's points: \(10^{-9}\).
  • drs background fit … --into file.drc makes the fitted function the control file's background, as starting values, with the file's comments kept.

Rebase (R25, W3). Module rebase; drs rebase <file.drc|.pcr> <data> --to <new.drc>.

  • The control file is copied and pointed at the new data (the data path relative to the new file, the reader identified). The old .pcr's range and regions are dropped, and a background file is copied next to it.
  • What does not fit is reported as R24 diagnostics with suggestions:
  • another wavelength, with the line to write; or a file that does not state one (a hint);
  • another axis;
  • the range, instrument and step against the old data;
  • excluded regions outside the data or over all of it;
  • background points that leave the new data's ends uncovered, or whose counts are not the new data's (a ratio outside 0.5–2);
  • a Chebyshev range of other angles, with from/to to write;
  • the old data's operations, not carried over.
  • Then the checks of the written file run.
  • Test rebase:
  • Y2O3 onto its own data keeps model, pattern and data, with only the hint that y2o3.dat does not state its wavelength;
  • Y2O3 onto LaB6's data reports range, step and background;
  • Co-YBCO onto Y2O3's reports the excluded region and the Chebyshev range (from = 15, to = 145.9);
  • each kind is checked on a scan made for it.

control_file now runs on 66 runs, the four background variants among them: identical both ways.

App request 13: the core reports a value that names two parameters as an error before the run (L076). A value moved by two parameters is not one: imports write that legitimately, e.g. Y2O3's Bérar–Baldinozzi P1 in its own parameter and as 3·P1 in P2's. A parameter written twice shows as a singular direction.

core/src/background.rs, core/src/rebase.rs, core/src/powder.rs (BackgroundModel::Spline, Nba = 1), core/src/jacobian.rs, core/src/control/ (schema.rs, mod.rs, pcr_export.rs), core/src/refinable.rs, core/src/main.rs · core/tests/background_points.rs, core/tests/rebase.rs, core/tests/data/make_background_references.py, background_references.txt · manuals/fullprof/runs/background_runs.py, runs/background/ · manuals/fullprof/FullProf_Manual.pdf pp. 8, 46, 69, 78; FullProf News 17 June 2011 · APP_WORKFLOWS.md W3, W4 · L075
L0772026-10-10finding

What FullProf and GSAS-II check before and during a run: neither warns of correlated parameters before it

Measured in FullProf's manual and News and GSAS-II's code (commit fe944cd), for R24. Manual pages are those of the PDF; GSAS-II's files are under GSASII/.

FullProf.

  • Codewords: C = sign(a)(10p + |a|), p from 1 to MAXS, the "Number of refined parameters" (manual p. 13). With Aut = 1 FullProf numbers them and there is no "hole in the matrix", yet "Sometimes the message 'hole in the matrix' still appears" (manual p. 69; News 2001, line 1013). The message itself is not explained. No other check of the numbering is documented. It is not in the runs' outputs; it was seen on LaB6's export (L071).
  • Site symmetry: positions, anisotropic B's and atoms sharing a site are constrained only in automatic mode (News 2002, lines 1164 and 1228; News 2003, line 1460). With codewords given by hand "the calculation may be wrong if the user fix some values violating the symmetry constraint rules" (News 2022, line 307): no check was found.
  • Input order: errors are found only when reading fails, and the messages "do not necessarily point to the initial error" (manual p. 16).
  • During the run: a parameter that goes negative is reset and its multiplier halved, with a warning (News 2003, lines 246–247; runs/YVO3/YVO3a.run.log:27, "WARNING, parameter Y < 0, fixed to 0.0001"). A singular matrix is named only as a symptom (manual p. 15).
  • After the run: with Mat = 1 the .out lists "Parameters with Correlation greater that 50%" (manual p. 72; runs/Co-YBCO/Cocus-asym.out:1573); the manual advises refining separately above 50 % (p. 14). With Ana = 1 the .sum adds an analysis of correlated parameters (runs/LaB6/LaB6rx.sum:252).
  • Not found: a check of occupancies, and any warning of correlated parameters before the run.

GSAS-II.

  • Before the run: Refine calls ReadCheckConstraints, and any error stops it with "Error in constraints" (GSASIIdataGUI.py:5749-5756; GSASIIstrIO.py:174-203). GenerateConstraints warns of parameters held, undefined, unvaried or with zero multipliers, and of duplicated constraints. It stops on more constraints than parameters ("Over-constrained input") and on linearly dependent ones ("Singular input") (GSASIImapvars.py:342-491); equivalences are checked the same way (GSASIImapvars.py:690-828).
  • Symmetry: coordinates fixed by a site are always held, "Fixed by symmetry" (GSASIIstrIO.py:1782-1797).
  • The scale with every phase fraction refined is warned of, "unstable refinement", with a constraint offered (GSASIIconstrGUI.py:1914-1940).
  • During the run:
  • parameters whose diagonal is below \(10^{-14}\) are dropped ("ouch #1 dropping singularities", GSASIImath.py:264-268);
  • the matrix is inverted by SVD, the singular values below \(10^{-6}\) of the largest zeroed (GSASIIobj.py:19-20; GSASIImath.py:104-109);
  • the parameters behind a singularity are named after the run (GSASIImath.py:150-162; GSASIIstrMain.py:61-112).
  • After the run: correlations above max(0.95, 0.99 · the largest) are listed as "highly correlated" (GSASIImath.py:131-148; GSASIIstrMain.py:113-139).
  • Not found: an occupancy check, a pre-run check of displacement parameters (negative ones are only coloured red in the Atoms table, GSASIIphsGUI.py:4888-4897), and any warning of correlated parameters before the run.

So. Neither program predicts correlated or redundant parameters before refining; both report them after. GSAS-II checks its constraints before the run and names singular parameters after it. FullProf has the "hole in the matrix" message and symmetry constraints in automatic mode. The core's checks (L076) do before the run what both do after it, from the same normal matrix, and explain each group from the dictionary.

The citations were gathered by a research pass over the files and spot-checked:

  • manual p. 69;
  • GSASIImapvars.py:448-451;
  • GSASIImath.py:143-147 and 264-268;
  • GSASIIdataGUI.py:5755;
  • GSASIIconstrGUI.py:1914;
  • News 2001, line 1013.
manuals/fullprof/FullProf_Manual.pdf pp. 13–16, 69, 72, 75; manuals/fullprof/news/ (2001, 2002, 2003, 2011, 2022) · manuals/fullprof/runs/*/ (.out, .sum, .run.log) · GSAS-II GSASIIdataGUI.py, GSASIIstrIO.py, GSASIIstrMain.py, GSASIImapvars.py, GSASIImath.py, GSASIIobj.py, GSASIIconstrGUI.py, GSASIIphsGUI.py (commit fe944cd)
L0762026-10-10work

LaB6's hole in the matrix fixed; the core's checks (R24): file, model, refinement and the normal matrix before a run

Core 0.18.0.

LaB6 (L071). The original LaB6rx.pcr codes 18 parameters (codes 1–3 and 5–19, number 4 unused) plus VARY ratio, declares 19, and has Aut = 1, so FullProf numbers them itself. The export took the ratio's column as parameter 11 and left a hole there.

The export now numbers the parameters that carry a codeword 1…N in order, leaving out the ratio's column and any column left without a written code. Line 13 declares N; LaB6 declares 18. Linear restraints follow the same numbers.

Test control_file checks on all 62 exported runs that the numbers are 1…N with no hole and N as declared (refinable::pcr_parameter_numbers; the .pcr reader now keeps line 13, Pcr::declared_parameters). Whether FullProf counts the ratio in line 13 is for the Mac run to show; the original counts it, the export does not.

The checks (R24), module check. drs check <file.drc|file.pcr> [--json] prints them; the app is to call the same check_path. Each diagnostic has:

  • a severity (error, warning, hint) and a stable code;
  • its place: line, column and span in a control file, or the model's addresses;
  • what is wrong, why, a suggestion, and the replacement text when the fix is mechanical;
  • the dictionary entries it concerns.

The control file's own diagnostics now carry a code and a replacement (syntax::Diagnostic). The checks run in four levels.

(a) The file:

  • syntax errors;
  • unknown names, with the nearest known one as the replacement;
  • a comment that names another unit of the dictionary than the value's;
  • values outside the engine's limits (P39). These are warnings: a value read outside a limit only moves towards it, as LaB6's refined B of −0.148 does.

(b) The model:

  • missing data or resolution files;
  • the wavelength;
  • the range against the data;
  • occupancies beyond a site. FullProf's Occ is the chemical occupancy times m, "can be normalised" to m/M (manual p. 102), so a phase is read as × m/M unless only × m keeps every atom at 1 or below (LaB6 writes La 1 and B 6);
  • values fixed by a site's symmetry or the crystal system that a parameter moves on its own;
  • phases without reflections in the data.

(c) The refinement:

  • a .pcr's parameter numbers with holes, or a declared count different from the highest number (Aut = 0 only: with Aut = 1 FullProf numbers them, manual p. 69);
  • a value naming two parameters in a control file;
  • a free parameter whose derivative is zero at every weighted point.

(d) Before the run: the normal matrix at the starting values (lsq::normal_matrix, as the engine builds it, restraints included), scaled to unit diagonal and decomposed.

  • Eigenvalues below \(10^{-12}\) of the largest are null directions (errors) and below \(10^{-8}\) near-null (warnings), each named as the parameters it combines.
  • The correlations of its pseudo-inverse at or above a threshold (0.9 by default) are reported. Connected pairs make one diagnostic per group, and pairs inside a null direction are not repeated.

Each group is explained by the dictionary's new correlations field (parameters.yml: with, why, tip, source; generated into parameter_table::Correlation and shown on the Parameters page), choosing the entry that covers the most members. A group no entry covers is a hint that says so. Twelve entries carry correlations:

  • zero shift with displacement and transparency (manual p. 56);
  • cell lengths with zero and displacement;
  • scale with overall B, occupancies and B's (p. 58);
  • overall B with the atoms' B's;
  • occupancy with B;
  • Caglioti U with V and W (Caglioti et al. 1958, eq. 11);
  • I_G with U and W (1/cos\(^{2}\)\(\theta\) = tan\(^{2}\)\(\theta\) + 1, an exact null direction);
  • Lorentzian size with strain (manual p. 49);
  • G₂ with the scale (Dollase 1986, p. 270);
  • the Chebyshev terms among themselves.

The last was found here. Egypt, with 5–6.7° of its 5–75° excluded, has its fifteen Chebyshev terms correlated up to 0.996. The engine reports the same after a refinement, and numpy gives 0.995 for the same points: the terms are independent over their whole range and the exclusion cuts off its end.

Test check_diagnostics:

  • broken files: 18 files under core/tests/data/broken/ (make_broken.py) each raise exactly one diagnostic, the one in their name; their bases (Y2O3's control file and export, Yb2ZrWO8's control file) raise none. The 15 control files are derived from Y2O3 (one from Yb2ZrWO8, for a TCH profile); the 3 .pcr files are parameter-hole, parameter-count and irf-missing. Codes covered: syntax, unknown-name, unit, range, data-missing, wavelength, range-data, occupancy, symmetry-fixed, no-reflections, refinement, no-effect, singular (I_G with U and W; the overall B with every atom's B), correlation (zero with displacement), parameter-hole, parameter-count, irf-missing;
  • good runs: the eight runs of L071, as .pcr and as control files, raise no error. Their warnings are correlations: U–V–W, Y–X, Egypt's background, Co-YBCO's;
  • predicted against refined: on the same runs, the predicted correlations against the engine's after refining from the refined .pcr, frozen. The largest differences are 0.0003 to 0.0098, except Co-YBCO, whose cell moves in its 4 cycles (0.032, and one pair crosses 0.9 after refining).

The .pcr exports of the broken control files are written next to them (12 of 15). Syntax, unknown-name and data-missing do not export: the export needs a file that reads. They are for FullProf on the Mac, to record what FullProf says of each mistake.

core/src/check.rs, core/src/control/pcr_export.rs, core/src/control/syntax.rs, core/src/control/schema.rs, core/src/lsq.rs, core/src/refinable.rs, core/src/pcr.rs, core/src/main.rs · ecosystem/data/parameters.yml (correlations), ecosystem/tools/build.py · core/tests/check_diagnostics.rs, core/tests/control_file.rs, core/tests/data/make_broken.py, core/tests/data/broken/ · L071, L073
L0752026-10-10decision

R25 and R26: a data file first, several projects in windows, a refinement from a previous control file, the background with the numbers in view

Josu tried app 0.7.0 and found it primitive: "I would like to open a data file; there is no option for that." The app could open one (File ▸ Open File… ⌥⌘O, Data ▸ Open Data File… ⇧⌘D), but the commands were out of sight, the dialog listed no formats, and opening a file closed the open project and made the file's folder the project. The design was organised around projects, not around what a user does first.

His requirements, appended to the brief as R25 and R26 and written step by step in APP_WORKFLOWS.md (read by every app session through CLAUDE.md):

  • W1, several projects at once, each in its own window, as VS Code-family editors open folders; APP_CONVENTIONS.md §1 already asked for it, and it is now needed before the later releases;
  • W2, a data file first: a welcome window; File ▸ Open Data File… listing the formats by name; the file opened in its own window without moving it; a first look with no model;
  • W3, a refinement started from a previous control file found anywhere on the disk, copied into the project, pointed at the new data, with what does not fit shown and checked (R24) before it is accepted;
  • W4, background points picked on the plot, shown as they are picked in a list and in the background file's text, kept in a file the control file names or in the control file at the user's choice, and compared with the automatic background and a refined function. FullProf reads points in the .pcr (Nba \(\ge 2\)) or a .bac file (Nba = 1); WinPLOTR saves picked points "to paste into .PCR file".
  • W5, proposals for Josu: a CIF dropped on the data to see its marks and simulated pattern; the instrument from the data's header and a library of resolution files by name; a refinement strategy assistant; two refinements compared; peaks passed on to indexing.

The rule for every workflow: compared with FullProf's and GSAS-II's, cited; fewer steps, no copying between windows, results shown as they are made, undoable. The app's next steps change: app 0.8.0 brings W1, W2 and W4; app 0.9.0 brings W3 and the rest of Refine on the umbrella. Set aside: keeping the project-first design, in which a data file could only be opened as part of a folder.

conversation of 10 October 2026 · APP_WORKFLOWS.md · ecosystem/data/brief.yml R25, R26 · app/ui/app.js (openFileDialog) · manuals/fullprof/FullProf_Manual.pdf (LINE 4, Nba) · manuals/fullprof/1-WinPLOTR/FPS_dec2015_WinPLOTR_TR.pdf (Background points selection)
L0742026-10-10work

App 0.7.0: the Editor and versions, with the guardrails of R24; Refine on the control file

The third step of L067, built by the app stream on core 0.17.0 and merged into main; the app's 19 tests pass.

  • The .drc clash of L072 fixed: open, save and import go through drs::control. A .pcr is imported by the core and its .drc written beside it; Save rewrites the .drc with the user's comments and order kept. The app's interim copies of the ties, the field list and the dictionary are deleted.
  • The Editor (CodeMirror 6, bundled, no network): a .drc checked by the core as it is typed (400 ms after the last key), each problem underlined with the core's message on hover, a Fix button when the core suggests a name, F8 to the next problem, a Problems panel, the count of errors on the Editor's icon; folding and an outline from the core's parser; Find and Replace; a .pcr coloured by FullProf's lines, not yet checked.
  • Versions (R22): snapshots in .drs/history/<file>/NNN_date_time.ext with a sidecar (trigger, note, \(\chi^2\), Rwp, cycles), listed, compared side by side, restored after a snapshot of what is replaced, annotated, removed; File ▸ Revert To.
  • Guardrails in Refine (R24): Run first asks the core to check the document; errors stop it and are listed with Open in Editor, warnings ask; then a snapshot before the refinement; Stop (⌘.) through lsq::refine_until. App request 13 opened: the checks of R24 beyond the file's text (a .pcr, holes in the refinement, the physics, missing files). Found while testing: two parameters moving one value are accepted silently by Refinement::build.
app/ (commit 3bb78ba on branch app, merged into main) · app/README.md · app/REQUESTS.md 13
L0732026-10-10decision

R24: guardrails, the model checked while it is edited and before every run

Josu, seeing FullProf stop LaB6's exported file with "Hole in the matrix, missing element 11" (L071): users make such mistakes, and refinement programs tell them after the run starts, in terms of their own internals, or not at all. He asked for guardrails, appended to the brief as R24:

  • a checker that watches the control file and the model as they are edited, as a Python editor marks a syntax error while it is typed, and marks each problem where it is edited, with a list of all of them;
  • each problem says what is wrong, why, and what to do, with a suggestion where one can be given;
  • Run first runs every check: errors stop it, warnings ask;
  • checks from the file (syntax, unknown names, ranges, missing files, codewords and ties with holes or conflicts, values fixed by a site's symmetry) to the physics: parameters that cannot be refined together, correlated or redundant, found before the run and named with the reason and a tip. The checks belong to the core, with a command-line path (drs check grows into them), so that the app and drs report the same problems; the app shows them (APP_CONVENTIONS §9). drs check already reports the control file's syntax errors at their line and column and suggests names for unknown keys (L066). Believed, not checked: GSAS-II checks its constraints before a refinement and names parameters after a singular matrix; whether any program warns of correlated parameters before running is to be measured, not assumed.
conversation of 10 October 2026 · ecosystem/data/brief.yml R24 · APP_CONVENTIONS.md §9 · L066, L071
L0722026-10-10work

App 0.6.0: the Data module

The second step of L067, built by the app stream on branch app and merged into main after core 0.17.0; the app's 17 tests pass there. App requests 8–11 closed, 12 opened (a writer for the processed pattern, for Export).

  • Any file the core reads opens in Data (double-click in the project, Open File…, Data ▸ Open Data File… ⇧⌘D, a drop on the window), read through drs::formats::read_path; a bad file gives a message naming it.
  • The shared plot (ui/plot.js): x in 2\(\theta\), d or Q, y linear, √ or log, box, wheel and pinch zoom, two-finger pan, double-click to fit, crosshair readout, a legend whose entries toggle, Copy as image; other files overlaid; a pop-up for files with several scans.
  • The shared table (ui/table.js): sortable, resizable and chosen columns, Find, multiple selection, Copy as tab-separated text, the selection synced with the plot.
  • Operations: the core's data_ops, kept as an ordered list in .drs/data/<file>.ops.json and applied on every read (mask, delete, smooth, rebin, normalise, add, subtract, merge, change of wavelength), listed in the sidebar and removable, with Undo and Redo; the measured file is never written (L067).
  • Find Peaks (⇧⌘K) and Automatic Background (⇧⌘B), the core's analyses, drawn and listed in the inspector. Not yet: export of the processed pattern (request 12); the operations' parameters are asked in plain prompts. App 0.5.0 saves the Refine model as <name>.drc with the core's model text, while .drc is now the control file (L070): the Refine and Editor steps must write .drc through drs::control.
app/ (commit 40600eb on branch app, merged into main) · app/README.md · app/REQUESTS.md 8–12
L0712026-10-10finding

Core 0.17.0 reviewed: FullProf gives its own numbers on eight exported runs; LaB6 has a fifth fault

Core 0.17.0 (L070) merged into main; the core's 105 tests pass on the Mac. export_runs.py was run again with FullProf (February 2025) on the corrected exports:

  • eight runs give FullProf's Rp, Rwp, Rexp and \(\chi^2\) exactly: Co-YBCO, Egypt, MgAl2O4-s, Y2O3, YVO3, Yb2ZrWO8, Yb2ZrWO8-PO-March, rutile-anatase. The four faults of L069 are fixed for them;
  • LaB6 is read by FullProf but stops with "Hole in the matrix!, missing element: 11". The original .pcr codes parameters 1–19 and declares 19, with VARY ratio outside them; the export declares 19 but codes 1–10 and 12–19, as if the ratio were parameter 11. FullProf numbers the ratio of VARY ratio itself, so the export must number the coded parameters 1–19 without it;
  • the four runs the core does not import are unchanged (NdNi-HTO, NdNi-LTOa, NdNi-LTOb, Pd3MnD). The export is claimed for the eight runs; LaB6 waits for the fix. export_runs.py now also reports FullProf's matrix errors.
manuals/fullprof/runs/export_runs.py, export_check.txt · FullProf.2k 8.20 (Feb 2025) · core/src/control/pcr_export.rs · L069, L070
L0702026-10-10work

Control files end .drc; the four faults of the .pcr export fixed; a refinement that can be stopped and each phase's curve

Core 0.17.0. Josu accepted the control file's syntax, the TOML subset of L066, with the extension .drc. The content stays valid TOML. drs convert, drs check, drs calc, drs refine, drs data info, the tests, the README and manuals/fullprof/runs/export_runs.py now use .drc.

The four faults of the .pcr export found with FullProf in L069, fixed:

  • the resolution file's name is alone on its line, after a comment line, as in FullProf's own files;
  • Thmin, Step and Thmax are written as the .pcr gave them (Co-YBCO's Thmax 150.0, Egypt's step 0.010002);
  • the excluded regions come out as they went in (Co-YBCO 0–5; Egypt 5.00–6.70 and 75–180);
  • VARY lines come straight after the wavelength line, as in FullProf's files (LaB6's VARY ratio).

How the range and the regions are kept. The import turns FullProf's regions into the data points FullProf leaves out (Co-YBCO's 0–5 becomes 0.069–5.019; the rule of L033 leaves out a point whose step reaches into a region). That loses the numbers that were written, and no arithmetic brings them back: widening 0.069–5.019 by half a step gives 0.044–5.044, and under the rule of L033 that would leave out one more point at each end.

So the control file keeps what the .pcr wrote in a table of its own, [pattern.fullprof]: range (Thmin, Step, Thmax) and excluded_regions. The export writes them back while the model's regions still leave out the same points.

Regions changed or added after the import are written half a step inside their first and last points, as the rule needs, and a step beyond the data at its ends, never clamped to the data. The export says so in a note. The range is the data's when the data are written processed.

Test control_file checks, on all 62 runs, the exported .pcr against the original, numbers compared as numbers:

  • the resolution line is one field, the file written;
  • Thmin, Step and Thmax are equal;
  • the excluded regions are equal;
  • VARY ratio is kept where the original had it, and every VARY/FIX line comes straight after the wavelength line.

The export now goes through the control file's text, as drs convert does. FullProf cannot run in the cloud: the exported files are for Josu to run again with export_runs.py on the Mac. The export is not claimed until FullProf reproduces every run it accepts (L069).

App request 6, a refinement that can be stopped. lsq::refine_until takes a callback that returns, after every cycle, whether to go on. Stopped, the refinement returns the model at that cycle's values, with esd's and correlations, and stop = "stopped by the user" (lsq::STOPPED). refine is unchanged for its callers. In drs refine, Ctrl-C stops after the cycle in progress and prints the results (a Le Bail run of MgAl2O4 stopped at cycle 376 of 500).

Test refinement_stop:

  • Y2O3 from FullProf's starting file, stopped after each cycle k = 1–5, is identical to a refinement of k cycles: the model's values, \(\chi^2\) and the esd's to the bit, and the correlations;
  • an AtomicBool set from another thread stops it after cycle 1.

App request 7, each phase's curve. Calculated::per_phase holds each phase's profile without the background, summed in the same pass as Ycalc. drs calc --phases writes them as columns.

Test phase_curves, on the three multi-phase runs: the background plus the phases' curves is Ycalc to \(10^{-12}\), and each curve is the pattern of that phase alone (the other scales set to zero) to \(10^{-9}\).

Finding, for Josu. FullProf writes these curves, with the background, in its .sub files (Ipr = 3). The only ones in the material are NdNi-HTO's, a run the core does not import (its magnetic phase). Their abscissa is the data's points corrected for Zero (10.1274 = 10.0 − (−0.12737)). The phase-1 file matches FullProf's own Ycal in the .prf to an rms of 2.4 counts.

The core's nuclear phase of that run has the right integrated intensity (within 0.06 %), but its peaks sit higher than FullProf's:

  • +0.0038° at (002), 15.15°, and +0.015° at (004), 30.56°, against the .prf's reflection list;
  • fitted against the .sub, the curve is offset by 0.03° near 30° and 0.07° near 70°.

The run is outside the verified ones, and its .irf is in the keyword format (WAVE, PROF, ASYM lines). This is not explained and nothing is built on it; the .sub comparison is not part of the test.

Proposed: run Egypt and rutile-anatase with Ipr = 3 on the Mac to give the verified multi-phase runs their .sub references.

core/src/control/ (mod.rs, schema.rs, pcr_export.rs), core/src/lsq.rs, core/src/powder.rs, core/src/main.rs, core/src/formats/project.rs · core/tests/control_file.rs, refinement_stop.rs, phase_curves.rs · manuals/fullprof/runs/export_runs.py, export_check.txt · manuals/fullprof/runs/NdNi-HTO/NdNi_HTO_calc1.sub, NdNi_HTO_calc.prf · app/REQUESTS.md 6, 7 · L066, L069
L0692026-10-10finding

Core 0.16.0 reviewed: FullProf refuses most of the exported .pcr files, four faults in the export

Core 0.16.0 (L066) merged into main; all the core's tests pass on the Mac. The cloud could not run FullProf on the .pcr files the core exports, only read them back with the core's own reader. This was done on the Mac with FullProf (February 2025): each run's refined .pcr imported into a control file, exported back, and FullProf run on the export with NCY = 0, its agreement factors compared with those of the run itself. The script is manuals/fullprof/runs/export_runs.py, its result export_check.txt.

  • Of the nine runs the core imports, two give FullProf's numbers exactly (Y2O3, MgAl2O4-s). Six are refused by FullProf, one differs (Co-YBCO: Rwp \(5.25 \to 5.32\), \(\chi^2\) \(1.55 \to 1.59\)).
  • Fault 1, the resolution file: the export writes rt.irf <--Resolution file on one line, and FullProf takes the whole line as the file's name ("The IRF file … does not exist"). It stops five runs (Egypt, YVO3, Yb2ZrWO8, Yb2ZrWO8-PO-March, rutile-anatase). With that line corrected by hand, YVO3, both Yb2ZrWO8 runs and rutile-anatase give FullProf's numbers exactly, so the .irf written by the core is right.
  • Fault 2, the excluded regions: they are narrowed again instead of widened back. Co-YBCO's 0–5 becomes 0.094–4.994, which lets the first point (2\(\theta\) = 0.069) into the fit; Egypt's 5.00–6.70 becomes 5.005–6.695, and its 75–180 becomes 75.005–74.995, a region that ends before it starts. Egypt's \(\chi^2\) then differs (\(4.15 \to 4.14\)).
  • Fault 3, the range: Co-YBCO's Thmax 150.0 is written as 150.019, the last point of the data; and steps are rounded (Egypt \(0.010002 \to 0.01\); YVO3's \(0.020010 \to 0.02\), harmless there because Ins = 10 takes the step from the data).
  • Fault 4, VARY ratio (LaB6): the command is written after the background block, where FullProf reads the phase's name as a number ("FindFMT: invalid character in a numeric field … line 35"). FullProf's own files put VARY and FIX lines straight after the wavelength line (Egypt's .pcr). The test control_file passes because the core's import reads what the export writes; it cannot see these faults. Under the gate rule (L037) the export is not claimed until FullProf reproduces every run it accepts. Josu has still to accept the control file's syntax (TOML subset) and its extension (L066).
manuals/fullprof/runs/export_runs.py, export_check.txt · FullProf.2k 8.20 (Feb 2025) · core/src/control/pcr_export.rs · L066
L0682026-10-10work

App 0.5.0: the frame, an umbrella of modules over a project folder

The first step of L067, built by the app stream on branch app and merged into main. Main was merged first (core 0.15.0, the reader registry). App 0.3.0 and 0.4.0, built on that branch on the first design, were never released; their Refine view is kept as the Refine module and their shell is deleted.

  • Project service (src-tauri/src/project.rs): a folder listed with every file's kind, the project's own files by extension and powder data by content through drs::formats::identify; the app's state in .drs/project.json; new projects; a single file's folder.
  • The activity bar: Data and Refine (⌘1, ⌘2, tooltips, the visible module highlighted, a badge while refining); light/dark/automatic, Settings, What's New and Manual at the bottom.
  • The dock on dockview-core 4.13.1 (vendored): sidebar, main, inspector and bottom panel (the module's panel and the Log); draggable dividers with minimum and maximum sizes, double-click to collapse, the layout saved per project; View ▸ Hide Sidebar ⌃⌘S, Inspector ⌥⌘I, Bottom Panel ⇧⌘Y, Toolbar ⌥⌘T; Window ▸ Reset Layout; file tabs reordered by dragging.
  • The menu bar of APP_CONVENTIONS §3 from the command registry, items enabled only when they can act and later ones disabled with their release; one menu per visible module; ⌘K over commands, files, the document's items, the dictionary, the manual and recent projects.
  • The module contract (icon, menu, commands, manual, sidebar section, inspector, bottom panel, views); the dictionary read from the core.
  • Refine: File ▸ Save writes the model beside the .pcr as <name>.drc (the placeholder of L067). Checked: 14 tests of the app, run again on main after the merge with core 0.16.0; the window rendered in headless Chrome; the bundle built and run on the Y2O3 folder. Josu tried the build and found it "quite good". Not yet: floating and detached panels, split tabs, native window tabbing (0.8.0); Settings is a dialog without panes; no file associations, no watcher for files changed on disk, no auto-save.
app/ (commit 06dad64 on branch app, merged into main) · app/README.md (conventions checklist) · app/DESIGN.md
L0672026-10-10decision

The app: the six questions of its design answered, and the Data module before the Editor

A review of the app branch found that app 0.4.0 had been built on the first design (one refinement document) before the design was reworked around the umbrella of modules (L060). The second design existed on paper only, with six questions open, and its plan put the Data module third and made 0.5.0 one very large step. Josu accepted these answers and the new order:

  • the control file does not take the extension .drs, which clashes with the hidden .drs/ folder of the app's state; the app uses .drc as a placeholder in one constant until the core's control-file syntax is accepted;
  • versions (snapshots) are all kept by default, with a setting to prune them by count or age;
  • masks, deleted points and other data edits are kept in .drs/; the measured file is never written;
  • the Editor has a .pcr mode first, the control file's mode once its syntax is accepted;
  • the dock is built on dockview-core, as in the difes shell;
  • the plan: app 0.5.0 the frame (activity bar, dock, menus, project folder, the module contract with Data and Refine registered); 0.6.0 the Data module (any file the core reads, overlaid patterns, 2\(\theta\)/d/Q, the shared plot and table, masking); 0.7.0 the Editor and versions; 0.8.0 Refine completed on the umbrella; then windows and Publish. Each step is built as a finished app and stops for Josu to try it. Set aside: the Editor before the Data module (Josu's first complaint was that the app opened only .pcr files, while the core already read 31 formats); one 0.5.0 holding the whole umbrella.
conversation of 10 October 2026 · app/DESIGN.md §7 and §9 (commit 06dad64) · APP_CONVENTIONS.md
L0662026-10-10work

Our own control file (a strict subset of TOML, for Josu to accept) and the data operations of the Data module

Core 0.16.0. Josu accepted L062's points: the five frozen \(\sigma\) sums and the question of the point at an excluded region's end, Ins 9 and Ins 14 read as believed, and the GSAS reader following the BANK record's NCHAN.

The control file (P47, Q3), for Josu to accept. The syntax is a strict subset of TOML 1.0, files ending .toml. Reasons, against the requirements:

  • readable and diffable: one key = value per line, a table per part of the model ([pattern], [pattern.background], [[phase]], [[phase.atom]], [[restraint]], [refine], [convention]), no indentation that carries meaning, so a diff shows the value that changed and nothing else;
  • typed values without guessing: strings are quoted, numbers are numbers, true/false are booleans;
  • comments with #, kept with the user's order and formatting when the file is written again after a change;
  • every file is valid TOML, so other tools read it (Python's tomllib read the 8 runs converted by the command line); our parser is our own (no dependency) so that it can keep the comments and give each key and value its line, column and span.

Left out of the subset, each reported where it occurs: dotted keys in key/value lines, multi-line strings and dates. This leaves one way to write each thing.

Set aside:

  • YAML: indentation carries meaning; implicit typing ("no" read as a boolean, version strings read as numbers); a large specification; keeping comments on rewrite needs a heavy library;
  • JSON: no comments and noisy to read by hand. It stays the app's interchange format for the model (L058);
  • a syntax of our own in the manner of CrysFML's CFL: readable, but no other tool reads it, and every rule would be ours to document;
  • the .pcr itself: positional columns that are not named.

The file holds:

  • a format line format = "diff-ref-struct 1";
  • the pattern: the data file by its path relative to the control file, the registry's reader id, wavelengths, instrument, corrections, excluded regions, background, resolution, and the data operations;
  • phases (space group as H-M and Hall symbols, cell, profile, preferred orientation, Le Bail flag) and atoms;
  • restraints;
  • the refinement: each parameter written as its terms, the first naming it; equalities with their multipliers; limits; linear restraints; cycles and convergence. The symmetry's ties are implied, not repeated;
  • the ten convention switches.

Keys are the parameter dictionary's ids (sample_displacement, not Sycos), with the dictionary's unit as a trailing comment; choices are words (debye-scherrer, thompson-cox-hastings, maximum-likelihood). Floats are written in the shortest form that reads back to the same bits. Unknown keys get a suggestion by edit distance ("did you mean zero_shift?").

Results (test control_file):

  • all 62 runs the core imports (the runs of manuals/fullprof/runs and the window experiments) were written, read back and written again: model identical to the bit, the same pattern and \(\chi^2\) (to the bit), the same parameter set as ParameterSet::from_pcr, the data file found again, the text identical. The three runs the import itself refuses (NdNi-LTOa, NdNi-LTOb, Pd3MnD: anisotropic size and strain, version 2) are not among them;
  • a file edited by hand (comments, moved keys, values in their own notation), updated after a change, keeps everything but the changed value;
  • 8 kinds of mistake are reported at their line and column. drs check prints them as file:line:column:, in the text's order, and the outline lists each table's lines for folding (app request 11).

The export to .pcr inverts the import's translations exactly: the scale halved, P1 − 3P2 and P3 − 3P4, the axial pair, the sign of the exponential G1, and the excluded regions narrowed by half a step. Codewords are C = sign(a)(10p + |a|). The .irf and the CODFILn.hkl of profile-matching phases are written next to the .pcr; processed data are written as .xys (Ins = 10).

All 62 runs were exported and imported again by the core's reader: identical models, zero pattern and \(\chi^2\) differences, and the same parameter sets. FullProf itself was not run (cloud).

What FullProf cannot express is listed as a note:

  • a value that moves with two parameters;
  • multipliers of 10 or more;
  • the user's limits;
  • the default model's taper of the peak window;
  • the reflection range;
  • the angle restraints' weight (3 runs);
  • processed data, or a reader with no Ins;
  • the Chebyshev range;
  • the λ2/λ1 ratio refined, written as FullProf's VARY ratio command (2 runs).

drs convert goes both ways; drs calc and drs refine read control files.

The data operations (R21; app request 10). Module data_ops: an ordered list of operations, written in the control file as [[pattern.operation]] tables. drs::control::open applies them to the scan read from the file; the file is never changed. The operations:

  • mask (\(\sigma\) = ∞) and delete, by range or by points;
  • Savitzky–Golay smoothing (SciPy's mode = "interp" at the ends), \(\sigma\) propagated;
  • rebinning by a factor or to a new step, summed (counts conserved) or averaged;
  • normalising to a monitor, a counting time, a maximum, or by a factor;
  • adding and subtracting another file's scan (linear interpolation, \(\sigma\) propagated), and merging (1/\(\sigma\)² weights);
  • conversion between 2\(\theta\), d and Q, and to another wavelength.

The analyses:

  • peak search: SciPy's prominence and width at half prominence;
  • GSAS-II's peak candidates;
  • the automatic background: arPLS with GSAS-II's λ and 10 iterations.

Test data_operations, on four real files (GSAS-II's AutoBkg tutorial scan, PbSO4 X-ray, garnet neutron data, Y2O3 of FullProf):

  • 669 smoothed points and their \(\sigma\) against SciPy (to 1e-9);
  • 348 peaks: indices exactly, prominences and widths against SciPy's find_peaks and peak_widths;
  • 538 peak candidates, the same indices as GSAS-II's own lines of OnAutoSearch;
  • 262 background points within 8.3·\(10^{-10}\) of the largest count of pybaselines' arPLS, as GSAS-II calls it, with the same number of iterations;
  • 205 rebinned sums against numpy.

Exact identities: d·Q = 2π; 2\(\theta\) → d \(\to 2\)\(\theta\) and 2\(\theta\) → Q \(\to 2\)\(\theta\); a change of wavelength keeps d; normalising scales y and \(\sigma\) alike; a scan added to itself gives \(\sigma\)√2, and merged with itself gives \(\sigma\)/√2; interpolated \(\sigma\); counts conserved by rebinning. GSAS-II's STD-bank count (NCHAN − 1, L062) is followed for PbSO4's references. drs data op | apply | peaks | background | info; info also names the other files of a project (formats::project: .pcr, .out, .sum, .prf, .hkl, .sym, .mic, .irf, CIF; app request 9).

App requests: 8 and 9 were closed by 0.15.0 (info completes 9); 10 and 11 are closed by this release.

For Josu:

  • the syntax and the .toml extension;
  • GSAS-II's rejection of picks closer than two FWHM is not reproduced: it needs the instrument's profile, and its loop deletes from the list it iterates over;
  • the parameters of a control file are written as their terms, before the symmetry's ties.
core/src/control/ (syntax.rs, schema.rs, update.rs, pcr_export.rs, mod.rs), core/src/data_ops.rs, core/src/formats/project.rs, core/src/refinable.rs, core/src/spacegroup.rs, core/src/main.rs · core/tests/control_file.rs, core/tests/data_operations.rs, core/tests/data/make_data_ops_references.py · TOML 1.0 (toml.io) · SciPy signal.savgol_filter, find_peaks, peak_widths · pybaselines 1.2.1 whittaker.arpls · GSAS-II GSASIIpwd.autoBkgCalc, GSASIIpwdGUI.OnAutoSearch (commit fe944cd) · app/REQUESTS.md 8–11
L0652026-10-10decision

APP_CONVENTIONS.md: Josu's standing requirements for a usual Mac app, read by every session

Josu found himself repeating the same requirements for the app in every conversation: resizable and movable panels, a light/dark switch, Settings, release notes and manuals at hand, one manual per module, everything as a usual Mac app. They are now written once in APP_CONVENTIONS.md at the top of the repository, and CLAUDE.md tells every session to read it before any work on the app, not to relax any item without Josu, and not to ask him to repeat them. It covers, following Apple's guidelines for macOS:

  • the window: activity bar, sidebar, main area, inspector, bottom panel, status bar; every divider draggable and collapsible, sizes remembered; tabs reordered, split, dragged out into windows; panels detached and docked; native window tabbing, full screen, state restored;
  • the activity bar: the modules at the top (⌘1…⌘9, badges); light/dark/automatic, Settings, What's New, Manual and Help at the bottom;
  • the complete menu bar with the standard shortcuts, one menu per module; Settings with its panes;
  • manuals: the app's and one per module, from the activity bar, the Help menu and a ? button in each module, bundled for offline use; tooltips from the parameter dictionary;
  • keyboard and ⌘K, files and Finder, versions, the shared plot and table components, long work that never blocks, appearance and accessibility, and the extensibility that gives a new module all of this. The app's design shows how it meets each item, and each release of the app lists the items it covers. Josu owns the file; corrections go into it. Set aside: keeping these requirements in conversations only.
conversation of 10 October 2026 · APP_CONVENTIONS.md · CLAUDE.md
L0642026-10-10work

0.15.0 reviewed and merged: GSAS-II's reading of the 48 banks regenerated on the Mac, identical

The reader registry of L062–L063 was reviewed on the Mac before it was merged into main: all 88 core tests pass, the crate builds without its dependency, clippy reports no warnings, the profile index regenerates the same, and the app's four tests pass with the new core. The reference of gsas2_reads (core/tests/data/gsas2_reads.txt, GSAS-II's own reading of 48 banks, 219573 points) was regenerated with the GSAS-II installed on the Mac (the team's Intel installer, version 5843, L043) instead of the source checkout the cloud used (commit fe944cd): byte for byte the same, and data_formats passes with it. The reading of the FullProf files is checked by the test against the .prf files themselves. For Josu, as L062 lists them: the five frozen \(\sigma\) sums and the excluded-region end-point question; Ins 9 and Ins 14 read as believed; the GSAS reader following the BANK record's NCHAN where GSAS-II does not.

core/src/formats/ · core/tests/data_formats.rs · core/tests/data/gsas2_reads.txt · GSAS-II 5843
L0632026-10-10decision

NeXus/HDF5 and MIDAS zarr are recognised, not read, until there is an example file

GSAS-II reads these two through Python libraries (h5py, zarr). Reading them in the core needs one of two things:

  • an HDF5 decoder: the superblock, object headers, B-trees, and chunked and deflated datasets;
  • zarr's Blosc codecs (LZ4, Zstandard).

No example of either was found in the GSAS-II tutorials or in manuals/. So the registry recognises them by signature: the HDF5 magic bytes, and a zip holding zarr metadata and MIDAS's arrays. It says why it does not read them. Set aside:

  • the hdf5 crate, which binds the HDF5 C library and so adds a system dependency to every build of the Mac app;
  • writing a decoder now with no file to check it against.

Pure-Rust HDF5 readers were not evaluated: the crates.io search was refused (HTTP 429) from this session.

core/src/formats/hdf.rs · GSAS-II G2pwd_HDF5.py, G2pwd_MIDAS.py
L0622026-10-10work

The reader registry: FullProf's formats, a general table reader and GSAS-II's, recognised by content and checked against both programs

Core 0.15.0, the core's part of the data module (R21 and its note, L061). Josu accepted all of L058's points: the π/180 weight of FullProf's angle restraints in the compatibility layer, the two frozen pattern differences (Sr-ox-test's Fourier background, LaMnO3-3t2 at 21–41°), the default window's taper, set_field for the request's set, and serde on by default.

One registry (formats), used by drs and the app, of 31 formats. Each has a readable id and name, its usual extensions, the source of its layout, a reader, and a function that gives a confidence for a file. FullProf's Ins numbers are only a translation: formats::by_ins, Format::ins, and data::read(text, Ins), the .pcr path.

  • FullProf's list (manual pp. 133–137), as the manual's Fortran reads each format:
  • Ins 0 to 12: free_steps, ill_d1a_d2b_old, ill_d1b_old, ill_d1b_d20, brookhaven, dbws (−4), two_axis, ill_d2b, ill_d4_d20l, psi_dmc, socabim_uxd, xys, variable_time, gsas;
  • Ins 14, isis_tof_banks, from the FullProf News of 24 September 2003: x, y, \(\sigma\) under BANK lines, y and \(\sigma\) times fac_y (1000 by default), divided by the step under RALF. That division is an equation the News page holds as an image lost from our copy; it is taken as x(i+1) − x(i). Believed, not checked.
  • The manual names Ins −4 (DBWS) without a layout, so it is recognised and not read.
  • Ins 9 (Socabim UXD) also has no layout in the manual. It is read as Bruker's UXD keyword file. Believed, not checked.
  • The general table, table (L061): it recognises, or takes from the user, the delimiter (, ; tab or blanks), the decimal mark, the header lines, and the x, y and \(\sigma\) columns (by name or position). x may be 2\(\theta\), d, Q or TOF, from the column's name; when no name says, 2\(\theta\) is assumed and noted. Options are TableOptions, and drs data read --delimiter --decimal --header --x --y --sigma --axis on the command line.
  • GSAS-II's formats, read as its importers (GSASII/imports/G2pwd_*.py, commit fe944cd) read them:
  • Bruker RAW version 1 (recognised; GSAS-II does not read it either), versions 2, 3 and 4, and BRML;
  • PANalytical XRDML;
  • Rigaku RAS, RASX and TXT;
  • xye, qye, Fit2D/pyFAI chi, and powder CIF.

No new dependency: the zip directory, DEFLATE (RFC 1951) and a small XML reader are in the core, 500 lines tested on the synthetic BRML and RASX zips (written by Python's zipfile) against GSAS-II's reading.

  • Recognition by content, then extension. A reader's confidence is:
  • 1, a signature only its format has;
  • 0.8, the whole structure reads with consistent counts;
  • 0.5, loose columns;
  • 0, the file does not read.

A usual extension adds 0.1. A file is identified when the best leads the next by 0.1; otherwise identify returns the candidates. The structure checks needed for real files: (I2,I6) fields are integers with their counters written; ILL's −1000/−10000 end marks; pairs past Tf; PSI's key=value lines after the data.

  • Each scan carries its axis (2\(\theta\), TOF, Q, d, E), its wavelength (λ1, λ2, ratio) and Info: title, instrument, date, sample, temperature, monitor, counting time, step and other keys. A point the reading program gives no weight has \(\sigma\) = ∞.
  • drs data formats, drs data identify <file> (each candidate's content confidence, extension and score), and drs data read <file> [--as <id>] [--scan n] [--out].

Tests against real files (data_formats, five tests; indexes written by make_fullprof_reads.py and make_gsas2_reads.py):

  • fullprof_reads: the 50 data files of manuals/fullprof/ that a single-pattern .prf was drawn from (Ins 0, 1, 3, 6, 8, 10). All 389266 points equal the .prf's 2\(\theta\) and Yobs.
  • FullProf holds Yobs in single precision: 32914.99875 is 32915.0 there and is printed 3292. \(\times 10\).
  • \(\sigma\) is checked on 49 files against the SumwYobsSQ of the .out written with the .prf (Σ w·y\(^{2}\), four digits), with the .pcr's weighting: Iwg = 1 is FullProf's maximum likelihood, w = 1/(Ycal·\(\sigma\)²/y). LaB6rx matched only with it.
  • Ins 0's \(\sigma\)² = max(y, 1), "not checked" since L033, is confirmed.
  • Frozen, five files. Egyptian make-up (3 files, 1.8·\(10^{-4}\)): FullProf keeps the point at 6.70, the end of the excluded region 5.0–6.7. In K2NaAgCN, NdNi and Pd3MnD it leaves out the points at the ends of their regions. The manual has no rule for an end point and these runs do not settle one.
  • Also frozen: the profile-matching tutorial's citric acid (2 files, 2.3·\(10^{-5}\), SumYobs equal). Not explained.
  • The one wavelength read from a header (K2NaAgCN's "lambda=") is the .pcr's.
  • gsas2_reads: GSAS-II's importers, run on the same bytes through its scripting loader, against 48 banks and 219573 points. The files: the GSAS-II tutorials' data files, its tests' PBSO4 files, and FullProf files GSAS-II also reads (Ins 0, 10, 12 and 14). Points, Σx, Σy, Σw, first and last points, and the wavelengths agree to \(10^{-9}\). Departures of GSAS-II's reading, counted and frozen:
  • it keeps NCHAN − 1 points of a STD bank (5 banks);
  • it reads the blank fields of a short line as zero-weight points (3);
  • it reads an ESD bank past its NCHAN (1);
  • it reads a free-format file to x \(\ge\) Tf, one point more than FullProf (1).

Two GSAS-II failures:

  • its RAW reader ends with repeat = False, so its loader reads only the first range of a version 2 or 3 file (Keokuk kaolinite.RAW has two; the reference asks for the second by number);
  • it cannot read FullProf's _prof.cif files: "_diffrn_measurement_device_type?" runs a tag and its value together, which PyCifRW refuses.
  • powder_cif_against_fullprof: those three CIFs, read by powder_cif, are their data files' points: 62542 points, by their point numbers, y and \(\sigma\) to the CIF's printing, 2\(\theta\) less the zero shift.
  • recognition: 182 example files. 165 are identified as their format; 17 are ambiguous with it among the candidates. All 17 are plain columns, which FullProf's Ins = 10, GSAS-II's xye and the table all read.
  • formats_without_example, 20 formats:
  • Checked against GSAS-II's reading of synthetic files (make_synthetic_formats.py, in manuals/gsas2/synthetic/): Bruker RAW 2 and 4 and BRML, XRDML, Rigaku RAS, RASX and TXT, qye, Fit2D chi, and the table.
  • Unit tests only: Ins 2, 4, 5, 7, 9 and 11.
  • Recognised and not read: Bruker RAW 1, DBWS, NeXus/HDF5, MIDAS zarr.
  • The GSAS-II tutorials' files and PBSO4 are in manuals/gsas2/ (22 files, 5 MB), with their source in manuals/README.md.

For Josu:

  • the five frozen \(\sigma\) sums and the end-point question;
  • Ins 9 and Ins 14 read as believed;
  • the GSAS reader follows the BANK record's NCHAN where GSAS-II does not.
core/src/formats/, core/src/data.rs, core/src/main.rs, core/tests/data_formats.rs, core/tests/data/make_fullprof_reads.py, make_gsas2_reads.py, make_synthetic_formats.py · FullProf manual pp. 133–137; FullProf News 2003 (24 September), 2011, 2012 · GSAS-II GSASII/imports/G2pwd_*.py and GSASIIscriptable.py (commit fe944cd); GSAS-II-tutorials (commit 924e9eb)
L0612026-10-10decision

The data formats in order: FullProf's list, a general CSV reader, GSAS-II's, then fer

Josu answers which formats the data module must read first: the files of the instruments he works with are those of FullProf's list of formats (manual pp. 133–137, the Ins codes), and there are many, so all of them come first, under readable names. Then a general CSV and table reader (delimiter, header and columns recognised or chosen); then the formats GSAS-II reads that FullProf does not; later the fer format of Josu's own ecosystem. Recorded as a note on R21. Set aside: starting from the laboratory vendors' formats only.

conversation of 10 October 2026 · Brief, R21
L0602026-10-10decision

The app is an extensible umbrella of modules for everything around structure refinement (R20–R23)

Josu redirects the app: not a refinement window but a modern, general Mac app that controls everything around the refinement of crystal structures, as an extensible umbrella of modules. Four requirements are added to the brief:

  • R20, the umbrella: a project is a folder on disk, shown as it is, each file opened by the module that handles it; the app's own state in a hidden .drs/ subfolder. A module declares the file types it opens and the commands, views, sidebar sections and inspector panes it adds, and uses only shared services (project and file index, versions, undo, selection, settings, the parameter dictionary, one plotting component); its computations are in the core with a drs command-line path, so it is testable and scriptable without the app.
  • R21, the data module: every format FullProf and GSAS-II read, recognised by content and then by extension, with readable names instead of FullProf's numeric codes; interactive, non-destructive work on the data on the plot and in a synced table; the usual operations of a data-analysis tool. Not a copy of WinPLOTR.
  • R22, the editor and versions: CodeMirror 6, synced both ways with the app; a history of every control file in .drs/history/<file>/<NNN>_<date>_<time>.<ext> with a sidecar (\(\chi^2\), Rwp, cycles, converged, note), a snapshot before every refinement and on demand, diff and restore; the working file keeps its name, so FullProf can read the same folder.
  • R23, publishing: figures for journals from the same plotting component, crystallographic tables, deposition CIFs, the Bilbao Crystallographic Server's formats; further modules as needed. What exists becomes the Refine module. The app stream reworks app/DESIGN.md around this before building further, and the core's next task is the reader registry for all the formats. Set aside: a single sealed project file with the data copied in (the app stream's first proposal); a document model centred on one refinement; versions as visible numbered files next to the original.
conversation of 10 October 2026 · Brief, R20–R23
L0592026-10-10work

0.14.0 reviewed and merged: FullProf's angle-restraint weight, the smooth window, the app's requests 1–5

The work of L058 was reviewed on the Mac before it was merged into main: all 74 core tests pass (none ignored now: fullprof_restraint_runs runs by default), the crate builds without its one dependency (--no-default-features), clippy reports no warnings, the profile index regenerates the same, and the app's four tests pass with the new core. Josu accepted L055's four decisions and its frozen differences, as L058 records. L058's own points for him (the inferred π/180 weight of FullProf's angle restraints in the compatibility layer, the two frozen pattern differences, the default window's taper, set_field for the request's set, serde on by default) are put to him. The app's requests 1–5 are provided on main; the app stream closes them in app/REQUESTS.md on its branch.

core/ · L058
L0582026-10-10work

FullProf's angle restraints pull at π/180 of their stated weight; a smooth peak window in the default model; the app's requests 1–5

Core 0.14.0. Josu accepted L055's frozen differences and its four decisions (this entry's request).

Restrained refinements against FullProf's runs (L056). FullProf's four restrained refinements each end at their starting point in one cycle: the tutorial files were already at FullProf's minimum. There O2–C1–O5 stands at −4.0\(\sigma\) of its restraint and Ag–C–N at +3.4\(\sigma\). The engine, with the stated weight, moves them to 1.65\(\sigma\) and 1.8\(\sigma\). So FullProf's angle restraints pull less than they say.

  • FullProf's listing states the weight in degrees: its "Sum{W(Ang_obs − Ang_cal)²}/4" is \(\chi^2\) times the mean of (\(\Delta\)/\(\sigma\))², with \(\Delta\) and \(\sigma\) in degrees (sr-ox-test: 11.3 printed; the distances' 1.33 the same way).
  • Scaling the angle rows' weight by π/180 makes FullProf's starting points the engine's minimum. Every restraint of the four runs then agrees with FullProf's within 0.73\(\sigma\). Every refined parameter of the two oxalate runs and of K2Na[Ag(CN)2]3 lies within FullProf's esd (at most 0.83 esd; FullProf's _conv.pcr imported for the comparison).
  • (π/180)² fits as well (0.72\(\sigma\)): both leave the angles nearly unrestrained. A radian derivative against degree residuals would give π/180; this is inferred from the results, not read. FullProf's least-squares code is not public, and CrysFML's restraint cost (cost_functions.f90) is in degrees throughout and has no derivatives.
  • It is FullProf's rule, so it lives in the compatibility layer: Convention::angle_restraints. The import keeps the default, the weight as stated: a .pcr's \(\sigma\) is the user's statement.
  • fullprof_restraint_runs now runs by default and passes with FullProf's rule. Two of these runs' calculated patterns differ from FullProf's at its own values; neither difference is caused by the restraints. Both are frozen:
  • Sr-ox-test: \(\chi^2\) 1.9605 against 1.931. Its Fourier background (18 coefficients) differs from FullProf's by a smooth term of up to 1.85 counts below 60°, constant beyond; Sr-ox-rest by 0.3 counts. Truncating the series to the manual's 12 terms makes it worse.
  • LaMnO3-3t2: 2.2416 against 2.137 (+4.9 %), all of it between 21° and 41°, around the peaks below AsyLim = 35°, with the background exact. The other F_b, H_G in z, and P2 moved or dropped were each tried and each makes it worse; there 7 parameters differ from FullProf's by up to 2.71 esd. make_fullprof_profiles.py now gives the CODE_conv.prf files their own reason: converged refinements, with no _calc pass, compared through \(\chi^2\) at FullProf's values in the tests it names.

A peak window that brings each peak smoothly to zero (L055's open point). The default model calculates each peak to ±Wdt·H as before, then multiplies it by a cosine taper that falls from 1 to 0, with zero slope, by \(\pm\)1.25·Wdt·H (profile::taper; the derivative path applies it as a function of the position and the FWHM). S is now continuous as widths and positions move. FullProf's cut at the edge is Convention::window_edge, and the import keeps it (no exact translation).

  • engine_derivatives: two more passes take the default model at the runs' own Wdt (rutile + anatase, Y2O3), 705 derivatives in all, the worst 5·\(10^{-7}\).
  • esd_profile, at each run's own Wdt with the taper: the two sides agree to 0.010, where FullProf's cut gave Y2O3's W 1.38 and 0.32. The ratios are those of Wdt = 400 to 0.004: Y2O3 0.888, Yb2ZrWO8 0.948, MgAl2O4 0.999, Egypt 1.011, rutile + anatase 0.969.
  • default_convention's table of how far the fully default model is from FullProf's patterns was re-measured: the added tails move most runs, e.g. Yb2ZrWO8 from 3·\(10^{-6}\) to 3.4·\(10^{-3}\).

The app's requests (branch app, app/REQUESTS.md). 1–5 are closed by core 0.14.0; 6–8 stay open.

  • 2: ParameterSet::from_addresses(free, equal, limits, model), with the symmetry applied as in from_pcr, and refinable::field_of, the inverse of address. Errors name the address. Equalities add up, so a field may move with several parameters (the import's P1 + 3·P2). User limits replace the engine's.
  • 3: Model::fields() -> Vec<FieldInfo> (address, dictionary id, value, limits, fixed_by: "crystal system" or "site symmetry"), Model::all_fields, and Model::set_field(field, value) -> Result. The tied values follow: a cubic cell's b and c; the coordinates a site expresses through the free one. The engine's Model::set stays unchecked, so set_field is the request's set.
  • 3, structural edits (module edit): add_phase (setting and cell), add_phase_from_cif, remove_phase (with its restraints), add_atom, remove_atom (refused while a restraint names it), set_background, set_background_terms, add_excluded, remove_excluded, set_shape, set_asymmetry (none, Bérar–Baldinozzi, axial divergence with TCH only), set_preferred_orientation_function, set_preferred_orientation.
  • 4: parameter_table.rs carries every field of every entry, and GROUPS; parameters::dictionary() and groups(). parameter_dictionary checks names, groups and kinds against the YAML.
  • 5: a name for each of the 47 entries ("Caglioti U", "Pseudo-Voigt η₀", "Preferred orientation G₁", …), shown on the Parameters page.
  • 1: Model::to_text and Model::from_text (JSON of the model, space groups as their Hall symbols and operators), behind the serde feature: the crate's first dependency, on by default so that cargo test covers it; --no-default-features builds with none. Tests:
  • model_editing: 1085 values of 11 runs, each address unique and field_of its inverse; from_addresses refines to from_pcr's \(\chi^2\) and values exactly on five runs; a user's limit held; the edits and the ones refused.
  • model_text: 62 imported runs written, read and written again, with identical text, patterns (to the bit) and values.

For Josu:

  • the inferred weight of FullProf's angle restraints (Convention::angle_restraints);
  • the two frozen pattern differences (Sr-ox-test's Fourier background, LaMnO3-3t2 at 21–41°);
  • the default window's taper;
  • set_field in place of the request's Model::set;
  • serde on by default.
core/src/edit.rs · core/src/text.rs · core/src/refinable.rs (from_addresses, field_of) · core/src/profile.rs (taper) · core/src/powder.rs (Convention) · core/src/lsq.rs · core/tests/fullprof_restraints.rs · core/tests/esd_profile.rs · core/tests/engine_derivatives.rs · core/tests/default_convention.rs · core/tests/model_editing.rs · core/tests/model_text.rs · core/tests/data/make_fullprof_profiles.py · ecosystem/data/parameters.yml (name) · ecosystem/tools/build.py · manuals/fullprof/runs/{Sr-ox-test,Sr-ox-rest,LaMnO3-3t2,K2NaAgCN}/*_conv.{out,prf,pcr} · app/REQUESTS.md (branch app)
L0572026-10-10work

App 0.2.0: Refine, with the plot redrawn after every cycle; the app moves to its own branch

The app's Refine button (made on the Mac, commit 0abce4f): the command refine_pcr runs the core's engine as drs refine does, off the window's thread, and sends the window an event after every cycle, so that Ycal and the difference are redrawn as the refinement goes (R08, Q5). A side panel lists the cycles (from the file's own values), the statistics, R_Bragg and R_F per phase, the refined parameters as value(esd) with those at or outside their limits marked, and the correlations above 0.8. The .pcr is not changed. Checked: four app tests (one refines Y2O3 through the command to the engine's \(\chi^2\) with one event per cycle); a recorded refinement replayed in the window's page. The Egyptian make-up from its starting file goes from \(\chi^2\) 7.60 to 4.1444 in 4 cycles.

From here the app is worked on by a separate local chat in its own git worktree, on branch app, which owns app/ and does not edit core/ or the log; its branch is reviewed and merged like the cloud's.

app/ · commit 0abce4f · branch app
L0562026-10-10work

0.13.0 reviewed and merged; FullProf's restraint and Le Bail runs made on the Mac; distances agree, two refined angles do not

The work of L055 was reviewed on the Mac before it was merged into main: all 68 core tests pass, the app's four pass with the new core, clippy reports no warnings, and the profile index regenerates the same.

FullProf was run on the six cases L055 asked for (manuals/fullprof/runs/restraint_lebail_runs.py): the two strontium oxalate files with 4 distance and 4 angle restraints, LaMnO3 on 3T2 (3 distances), K2Na[Ag(CN)2]3 (6 distances, 1 angle), and the MgAl2O4 and Co-YBCO Le Bail fits. All six converge. The test fullprof_restraint_runs then found no restraint block at all: FullProf writes "Distance restraints" with two spaces, and the test searched for one, so it read zeros from the top of the listing. It now matches the words. Against FullProf's refined structures:

  • every restrained distance agrees within 0.85\(\sigma\) of its restraint, most within 0.3\(\sigma\) (LaMnO3's La–Mn to 2·\(10^{-4}\) Å);
  • two angles do not: O2–C1–O5 of the oxalate refines to 127.94° against FullProf's 128.65° (2.4\(\sigma\); 127.99 against 128.84 in the second file), and Ag–C–N of K2Na[Ag(CN)2]3 to 0.18° against 0.34° (1.6\(\sigma\)). The angles themselves are calculated as FullProf calculates them (L055), so the difference is in the refinement, probably the weight given to the restraints; to be found. The test stays ignored by default and fails on these. Also: the six runs' .prf files had no line in the profile index, and fullprof_profiles, which counts the files on disk since L054, failed until the index was regenerated; its reason for these lines names a _conv_calc.prf that these runs do not have.
manuals/fullprof/runs/restraint_lebail_runs.py · manuals/fullprof/runs/*/…_conv.out · core/tests/fullprof_restraints.rs
L0552026-10-10work

Restraints, Le Bail fits and quantitative phase analysis in the engine, and the esd's confirmed by the \(\chi^2\) profile

Core 0.13.0, phase 2 step 5 with restraints (P40, P35, P33). Josu accepted L053's frozen differences, YVO3's report and its two decisions (this entry's request).

Restraints (P40). The .pcr reader now reads a phase's soft distance and angle constraints (Dis, Ang of line 19; lines 45 and 45-1, manual pp. 123–124; FullProf may write the angle lines' numbers without spaces) and the general linear restraints (NLI on line 7).

  • Module restraints: an atom of a restraint is taken through {R|T}. R comes from FullProf's table of rotations, numbered ITnum: 1–24 in non-hexagonal frames, 25–36 in hexagonal ones, negative for the product with the inversion; T is the whole translation. The table is FullProf's own (p. 123), not its list of the group's operators, so a restraint names an operator exactly.
  • Distances and angles are computed in the cell's metric over the dual numbers, so their derivatives with respect to the cell and the coordinates are exact.
  • Linear restraints Σ c·p \(\approx\) v \(\pm\) \(\sigma\) act on free parameters by name (refinable::LinearRestraint; FullProf's parameter numbers are its codewords').
  • In the engine each restraint is an observation with weight \(\chi^2\)_ν/\(\sigma\)², \(\chi^2\)_ν of the data at the start of the cycle, as FullProf's (restraints tutorial, p. 2). P is not reduced, and Rp, Rwp and \(\chi^2\) remain the data's.
  • drs refine prints each restraint's target, value and \(\Delta\)/\(\sigma\). Test fullprof_restraints:
  • Geometry against FullProf. Each run writes CFML_Restraints_1.tpcr, the structure's distances and angles as restraint lines with the values FullProf calculates. Read as restraints:
  • Yb2ZrWO8 (our run): 4 distances within the print (3·\(10^{-5}\) Å), among them O–O through ITnum −4;
  • LaMnO3 of the symmetry-modes tutorial (positions from 7 mode amplitudes; the core's agree with FullProf's "Final Complete Structure" to its 5 decimals): 23 distances up to 1.6·\(10^{-4}\) Å from FullProf's;
  • LaMnO3 on D20 (la0_190.pcr): 6 distances up to 1.5·\(10^{-4}\) Å and 31 angles up to 0.0135°. The LaMnO3 differences are not explained and are frozen. A wrong convention (ITnum, the central atom, which atom an operator moves) would be off by tenths of an ångström or by degrees.
  • Derivatives against Richardson differences: \(10^{-6}\) relative, or \(10^{-5}\) for k2na1p5's Ag–C–N angle at 0.18°, where acos is steep.
  • Restrained refinements of the three runs that carry restraints:
  • SrHC3O6·H2O (tutorial; two files): 8 restraints, \(\chi^2\) \(1.928 \to 1.9277\); O2–C1–O5 goes from +4.05\(\sigma\) to +1.65\(\sigma\);
  • LaMnO3 on 3T2: 3 restraints, all within 0.03\(\sigma\);
  • K2Na[Ag(CN)2]3: 7 restraints; the Ag–C–N angle goes from +3.4\(\sigma\) to +1.8\(\sigma\). All converge in 3 cycles, and every restraint ends within 3\(\sigma\) or nearer its target.
  • Linear restraint: U + W of Y2O3, added as NLI lines, is held to 0.02\(\sigma\) while both move.
  • FullProf's output of these refinements is not in the material. manuals/fullprof/runs/restraint_lebail_runs.py makes it on the Mac (with the two Le Bail files below), and the ignored test fullprof_restraint_runs compares with it. The restraints tutorial prints a FullProf run of sr-ox-test, but from a state other than the material's file, so it cannot be compared.

Le Bail fits (P35). Module lebail.

  • Every Jbt = 2 phase has its intensities replaced at every cycle by the Rietveld formula (manual eq. 3.15, p. 44): I_obs,h = I_calc,h Σ_i v_hi r_i / Σ_i v_hi, with r_i = (y_i − b_i)/(y_c,i − b_i).
  • With Irf = 0 and no .hkl file, the reflections the group allows are generated with equal intensities.
  • The engine then runs all its cycles, as FullProf does with Dum = 1 (p. 68), which all its Le Bail tutorials set.
  • The intensities are not counted in P: LBF_lmopr's N − P + C is 2461 with P = 8, in FullProf and in the core. A first version summed I·\(\Omega\) over the points without the step and came out 1/step too strong (Rp 750 %). Test fullprof_le_bail:
  • LBF_lmopr (LaMnO3, FullProf's tutorial .out). The engine starts from the values the .out lists before its first cycle and runs its 15 cycles. Every parameter ends within FullProf's esd (at most 0.80); the cell esd's are again twice FullProf's under TCH. \(\chi^2\) after 15 cycles is 1.3630 against 1.420 (frozen −4.0 %): the engine starts from the intensities of LBF_lmopr1.hkl, which FullProf wrote at the end, and FullProf's \(\chi^2\) is still falling by 0.006 per cycle. At FullProf's final values and .hkl intensities the core gives 1.4139 against 1.420 (frozen −0.43 %), and one more extraction gives 1.4080. The .hkl is believed to hold intensities one extraction later than FullProf's last \(\chi^2\); not checked.
  • MgAl2O4s_LeBail: ten extractions from equal intensities, nothing else refined. The header's "global user-weighted \(\chi^2\) (Bragg contrib.)" turns out to be S over all points divided by N − P + C over the points with Bragg contributions (Yb2ZrWO8_conv.out: 5357/3114 = 1.720, printed 1.72; Egypt: 28130/6723 = 4.184, printed 4.18). The core gives 1.8516 against 1.848 (frozen +0.20 %; FullProf's starting intensities and Bragg points are not known).
  • Cocuc_LeBail: its header (26.69) comes from an earlier run than its values (U = 0.086, a = 3.869). The engine's nine cycles (\(\chi^2\) \(21.6 \to 3.15\)) are reported, not compared.

Quantitative phase analysis (P33). Module qpa: W_φ = S·ATZ·V/t over the sum (Hill & Howard, as the manual writes it, p. 31; ATZ, p. 91). The phase carries ATZ and Brindley's t, now in the dictionary (qpa_atz, brindley_coefficient). Brindley's t is 1 in all 154 phases of the material that give it, so its use is unchecked. drs refine prints the fractions. Against FullProf's "Fract(%)", in fullprof_refinements:

  • Egypt's five phases: 39.911, 16.407, 22.020, 4.938, 16.725 % against 39.91, 16.41, 22.01, 4.94, 16.73;
  • rutile + anatase: 77.906 and 22.094 against 77.91 and 22.09;
  • each within 0.07 of FullProf's esd. The esd's differ. FullProf's are reproduced by treating the numerator and the sum as independent, \(\sigma\)²(W) = \(\mathrm{W}^{2}\)[(\(\sigma\)_φ/S_φ)² + Σ_ψ W_ψ²(\(\sigma\)_ψ/S_ψ)²]: from the engine's scale esd's, rutile 0.515 against the printed 0.52 and Egypt's first phase 0.255 against 0.25, all within 2 % and the print. It gives a single phase an esd (Y2O3: 100 \(\pm\) 0.23 %). The core propagates the covariance of scales and cells: 0 for one phase, and rutile \(\pm\)0.190 % for both phases.

Test esd_profile (L054). The test fixes one parameter at its value \(\pm\) its esd, refines the rest from the minimum, and measures ΔS/\(\chi^2\)_ν, which is 1 if the esd is the one the surface supports. There is one parameter of a different kind in each of the five runs that agree with FullProf.

  • At the runs' own Wdt the two sides disagree (Y2O3's W 1.38 and 0.32): S jumps when a peak's window edge crosses a point. With Wdt = 400 the surface is smooth and both sides agree to 0.011.
  • Ratios: Y2O3's W 0.88–0.89, Yb2ZrWO8's cell a 0.95 (the parameter of L054, now symmetric), MgAl2O4's O x 1.00, Egypt's scale 1.01, rutile + anatase's O z 0.97.
  • At \(\pm\)\(\sigma\)/2 each ratio is a quarter of these (Y2O3 0.22), so the surface is quadratic. What is short of 1 is the model's own curvature Σ r w ∂²y, which the Gauss–Newton matrix leaves out: the profile esd is 6 % larger for Y2O3's W and 3 % for Yb2ZrWO8's cell. Frozen per run. FullProf's cell esd's under TCH would give ratios near 4.

Decisions.

  • Weight-fraction esd's propagate the covariance. Set aside: FullProf's formula, which ignores that a phase's scale is also in the sum, and gives a lone phase an uncertainty.
  • Restraints are weighted by \(\chi^2\)_ν, as FullProf's. Set aside: weight 1 (restraints as observations of fixed quality), which pulls harder when the fit is poor.
  • With Le Bail phases the engine runs all its cycles. Set aside: stopping on the other parameters' shifts while the intensities still move.
  • With Irf = 0 and no .hkl, the reflections are generated with equal intensities. FullProf's starting intensities are not documented.

For Josu:

  • run manuals/fullprof/runs/restraint_lebail_runs.py on the Mac (four restraint and two Le Bail references);
  • the frozen differences: the LaMnO3 .tpcr distances (1.6·\(10^{-4}\) Å) and D20 angles (0.0135°); LBF_lmopr (−0.43 % at FullProf's values, −4.0 % after 15 cycles); MgAl2O4s_LeBail (+0.20 %); the esd-profile ratios;
  • the four decisions;
  • an open point for the default model: the peak window makes S discontinuous (the esd profiles), so a window that brings each peak to zero smoothly would help the engine.
core/src/restraints.rs · core/src/lebail.rs · core/src/qpa.rs · core/src/pcr.rs · core/src/lsq.rs · core/src/refinable.rs · core/src/main.rs · core/tests/fullprof_restraints.rs · core/tests/fullprof_le_bail.rs · core/tests/esd_profile.rs · core/tests/fullprof_refinements.rs · manuals/fullprof/runs/restraint_lebail_runs.py · manuals/fullprof/docs/FullProf-Restraints-RigidBodies.pdf pp. 2–4 · FullProf manual pp. 14, 31, 44, 68, 91, 123–124
L0542026-10-10finding

0.12.0 reviewed and merged: the engine's esd's are right where FullProf's are half, by the fit's own \(\chi^2\) surface

The engine of L053 was reviewed on the Mac before it was merged into main: all 64 core tests and the app's 3 pass, clippy reports no warnings, build.py runs.

The open question of L053, why the engine's esd's of the cell (and of I_G) are twice FullProf's in every TCH run, was put to a test that uses neither program's covariance: the profile of the fit. On Yb2ZrWO8, the cell a was fixed at its best value plus offsets and everything else refined again with drs refine; the rise of S = Σw(y − y_c)² over its minimum (5353.916, N − P = 3227, \(\chi^2\)_ν = 1.6591) is what an esd predicts, ΔS = \(\chi^2\)_ν (δ/\(\sigma\))²:

  • δ = \(\pm\)0.000082 Å (the engine's esd): ΔS = 1.336 on both sides, where the engine's esd predicts 1.659 and FullProf's (0.000041 Å) 6.636;
  • δ = +0.000164 Å: ΔS = 6.178, against 6.636 and 26.546. The \(\chi^2\) surface, calculated by a model that reproduces FullProf's patterns to \(10^{-5}\), agrees with the engine's esd within 10–20 % (the curvature is not exactly quadratic) and not with FullProf's: in these TCH refinements FullProf reports cell esd's about half of what its own fit supports.

Also found: fullprof_profiles checked a fixed count of 145 .prf files, not the files on disk, so the nine CODE_conv.prf of L052 had no index line and no test noticed. It now counts the files on disk; the index is regenerated (nine "left out" lines, with their reason) and the test passes. For Josu: L053's frozen differences, YVO3's report, and its two decisions (esd's scaled by \(\chi^2\) over all the points used; a value read outside its limits is reported and left where the user put it).

core/tests/fullprof_profiles.rs · core/tests/data/fullprof_profiles.txt · drs refine on manuals/fullprof/runs/Yb2ZrWO8/ybfluo_Riet_IRF_conv.pcr
L0532026-10-09work

The least-squares engine: named parameters, constraints, limits, Levenberg–Marquardt with analytic derivatives, and FullProf's refinements reproduced

Core 0.12.0, phase 2 step 4 (P37–P39, P42–P44). Restraints (P40), Le Bail and QPA are not part of it.

Derivatives. The peak calculation is generic over a number type (dual::Real): with forward-mode dual numbers it gives Ycal and its exact derivatives in one pass. Per phase, 21 variables are carried: zero, Sycos, Sysin, Ratio, the six cell parameters, U V W X Y I_G η0, G1 G2, B_ov and the scale. The structure factor's derivatives with respect to s and to each atom's x y z, B, Occ and betas are analytic. Model::derivatives reuses the peak selection and windows of calculate. Test engine_derivatives: on ten FullProf runs, every kind of field (592 derivatives, those below \(10^{-7}\) of Ycal counted as zero) is checked against Richardson-extrapolated central differences, to 2·\(10^{-5}\) of the largest derivative; the worst is 6.3·\(10^{-7}\). The dual path reproduces calculate to \(10^{-10}\). The cell needs a step of \(10^{-6}\)·a: a step of \(10^{-4}\) Å moves a peak far enough to change the number of terms of the FCJ quadrature (CrysFML's NTERMS), a jump in the pattern.

Parameters (P37–P39). refinable::ParameterSet::from_pcr turns a .pcr's codewords (C = sign(a)(10p + |a|), manual p. 13; with Aut = 1 a bare multiplier is a parameter of its own) into free parameters:

  • each is named by the dictionary, after the field its codeword was first written on: pattern1.zero_shift, phase1.cell_a, phase2.O.atom_position.z, phase1.berar_baldinozzi.1;
  • a codeword shared by several values is an equality constraint with its multipliers (P38);
  • the cell is constrained by the crystal system (monoclinic: the unique axis from the 2-fold or the mirror);
  • the import's translations are applied to the multipliers: X-ray scale \(\times 2\), P1 + 3P2, the axial pair, −G1. Refused, with the reason: a coded wavelength, Str1–3, LorSiz, Shape1 of a non-pV phase, and VARY keywords other than ratio. The limits (P39) are in refinable::limits and in the dictionary's new limits field (on the "Parameters" page):
  • \(\ge 0\): scale, B, Occ, I_G, Y, S/L, D/L and the wavelength ratio, and U and X of a TCH phase over a resolution file (there they are the sample's alone);
  • G2 within 0–1; March's G1 \(\ge\) \(10^{-3}\); cell lengths \(\ge\) \(10^{-3}\) Å. The test parameter_dictionary now also checks that every field the engine refines carries a refinable id of the dictionary, and that the dictionary's limits match the engine's.

Engine (P42). lsq::refine does Levenberg–Marquardt on the normal matrix scaled to unit diagonal, solved by a Jacobi eigen-decomposition (the SVD of a symmetric matrix), with eigenvalues below \(10^{-12}\)·λ_max left out and counted. Each cycle tries Gauss–Newton (μ = 0) first; if \(\chi^2\) rises, μ = \(10^{-3}\) and \(\times 10\) up to \(10^{8}\), then ÷10 after each accepted step. A step that would take a field past its limit is cut back to the bound for the parameter that alone moves it, and that parameter is held (left out of the solve) while the gradient pushes it out. It stops when every |shift| is below Eps·esd, the .pcr's Eps, as FullProf does (p. 76). Weights follow Iwg, the maximum-likelihood ones recomputed each cycle. Every cycle is an event (lsq::Cycle, Q5): values, shifts and esd's, Ycal, the statistics, μ, and the singular directions.

Statistics (P43, P44). Rp, Rwp, Rexp, \(\chi^2\) over N − P (+ C, which is 0: constraints reduce P), the Durbin–Watson d of the weighted residuals, and R_Bragg and R_F per phase with Rietveld's apportioning of the observed counts. Then the covariance, esd's and correlations, and the esd of any field through its multipliers (√(mᵀCm)). The esd is \(\sigma\)_k = √(C_kk \(\chi^2\)) with \(\chi^2\) over all the points used. FullProf takes \(\chi^2\) over the points with Bragg contributions (manual eqs. 3.7–3.8), so its esd's are larger by √(\(\chi^2\)_Bragg/\(\chi^2\)_all). Measured:

  • LaB6: 0.912 against √(12.86/15.47) = 0.9118;
  • rutile + anatase: 0.982 against 0.9825.

drs refine <file.pcr> [--cycles N] [--data <file>] imports the .pcr, refines its coded parameters and prints:

  • R-factors, \(\chi^2\), DW, μ and the largest shift/esd per cycle;
  • each parameter with its esd, and whether it is at its limit or outside its limits;
  • R_Bragg and R_F per phase;
  • the correlations above 0.8.

Test fullprof_refinements. The nine runs of L052 are refined from their starting files (CODE.pcr.in, imported), with FullProf's reflection limits and data. Each is compared with CODE_conv. Agreement in five runs:

  • Y2O3 (72 parameters): \(\chi^2\) 1.1985 against FullProf's 1.1990;
  • Yb2ZrWO8 (24): 1.6599 against 1.6600;
  • MgAl2O4 (45): 1.3823 against 1.3810;
  • the Egyptian make-up (44): 4.1445 against 4.1450;
  • rutile + anatase (20): 1.3387 against 1.3390. In all five every refined parameter is within FullProf's esd (the largest: Y2O3's zero, 0.997 esd), and N − P is FullProf's N − P + C. Rp agrees within 0.01, Rwp within FullProf's printed digits, DW within 0.001. R_Bragg agrees within 0.06, except MgAl2O4's MgO (10.84 against 10.5), not investigated. The engine converges in 2–4 cycles; FullProf took up to 11. \(\chi^2\) is compared within the tolerance of fullprof_profiles (5·\(10^{-4}\) + 0.15 %).

Differences found, explained where possible, frozen in the test:

  • FullProf's peak window changes with the number of cycles. In rutile + anatase's 11-cycle run, FullProf's .prf at the converged values matches windows of Wdt·H, H with the strain term X tan \(\theta\) (Σw(y − yc)² 10552.2; the core with Wdt·H gives 10551.5, and with the 1-cycle rule Wdt·H_noX of L041, 10810.3). The test refines that run with the default model's window, Wdt·H. In LaB6, FullProf's own .prf files of the 1-cycle and the 8-cycle run, from identical parameter values, give 107888 and 108157: the window differs, and neither rule reproduces the second. LaB6's \(\chi^2\) at FullProf's values is 12.942 against 12.86 (frozen 0.64 %). Its refined \(\chi^2\) is 12.891, and its cell is 1.29 FullProf esd's away (0.71 of the engine's). Rule not found.
  • Co-YBCO: FullProf's last point is not a minimum. The function agrees there (\(\chi^2\) 1.5239 against 1.524). The engine lowers it to 1.4833 from the start and to 1.4834 from FullProf's values. 17 parameters then differ by more than FullProf's esd (cell a by 25.6; zero, U V W, S/L). FullProf's own cycles show \(\chi^2\) climbing from 1.56 to 1.74 and still falling by 0.01 per cycle when it stopped (L052).
  • The esd's of the cell parameters, and of I_G (Yb2ZrWO8), are twice FullProf's (after the scaling above: q = 1.96–2.01) in every TCH run (Npr = 7). In Y2O3, the one pseudo-Voigt run, they agree (q = 1.001). The engine's derivative is checked against finite differences of a pattern that reproduces FullProf's (at Yb2ZrWO8's values, Σw(∂y/∂a)² agrees to \(10^{-6}\)). Why FullProf prints half is not known.
  • Seven other esd's are frozen by name, each with its measured q:
  • the end points of linear-interpolation backgrounds: Y2O3 0 and 61; Yb2ZrWO8 0 and 1; MgAl2O4 0 and 31; q from 0.67 to 1.25;
  • LaB6's wavelength ratio and scale (VARY ratio): q 0.56 and 0.48. Cause not found.
  • The Egyptian make-up: FullProf's N − P + C is one more than the points it fits (7001 points, 172 excluded, 44 parameters: 6785; FullProf 6786). Believed to be the last point (75.00°), excluded but counted; not checked.
  • NdNi-HTO: its magnetic phase is not calculated (FullProf's 42 parameters, the engine's 41), so it is not comparable (\(\chi^2\) at FullProf's values 26.0); frozen.
  • YVO3, as asked, reported without forcing agreement. FullProf does not converge in 100 cycles (L052). The engine converges in 6 cycles:
  • U is held at its limit 0, where FullProf takes it to −0.091 with an esd of 3·\(10^{-10}\);
  • Y goes to 0.0065 \(\pm\) 0.0037 (FullProf 9·\(10^{-5}\)), and X to 0.654 (FullProf 0.857);
  • \(\chi^2\) 2.29 against FullProf's 3.68 at its last values; FullProf's last values give 3.77 in the core (the window of L039).

Decisions.

  • esd's are scaled by \(\chi^2\) over all the points used, not FullProf's Bragg points. It is the standard \(\chi^2\)_ν of the fit; FullProf's choice is documented, and the ratio is predictable. Set aside: copying FullProf's scaling.
  • A value read outside its limits (LaB6's B Biso −0.148 in the tutorial) is not moved onto the limit; it may only move towards it, and drs refine says it is outside. Set aside: clamping on import, which would change the user's model silently.
  • The rutile + anatase comparison uses the default window, which is what FullProf's last cycles use there. The other runs keep the import's window, which FullProf's 1-cycle runs use. No run-history rule is built into the core until it is understood.

For Josu to accept:

  • the frozen differences above (LaB6's window, Co-YBCO, the cell and I_G esd's with TCH, seven background, ratio and scale esd's, Egypt's N count, NdNi's magnetic phase);
  • YVO3's report;
  • the two decisions on esd scaling and out-of-limit values.
core/src/dual.rs · core/src/jacobian.rs · core/src/refinable.rs · core/src/lsq.rs · core/src/main.rs (drs refine) · core/tests/engine_derivatives.rs · core/tests/fullprof_refinements.rs · core/tests/parameter_dictionary.rs · ecosystem/data/parameters.yml (limits) · manuals/fullprof/runs/*/CODE_conv.{out,prf,pcr} · FullProf manual pp. 13, 43–44, 76
L0522026-10-09work

FullProf's converged refinements, the references for the least-squares engine; YVO3 does not converge

The engine is to be tested on refinements FullProf has finished, and several tutorial runs stop before convergence (L034). FullProf was run again on the nine version-1 runs, from each tutorial's starting file, with up to 100 cycles, stopping at its own convergence test (manuals/fullprof/runs/converge_runs.py, outputs CODE_conv.*):

  • eight converge: Y2O3, Yb2ZrWO8 and NdNi-HTO in one cycle (their tutorial files were already refined), MgAl2O4 in 2, the Egyptian make-up in 7 (\(\chi^2\) \(7.65 \to 4.15\)), LaB6 in 8, rutile + anatase in 11 and Co-YBCO in 18 (\(\chi^2\) 1.56, up to 1.74, then 1.54);
  • YVO3 does not converge in 100 cycles: the shift of Y, the Lorentzian size term, stays at −9.8 × eps·\(\sigma\) while Y sits at 9·\(10^{-5}\), and the Gaussian strain term U goes to −0.091, below zero. FullProf then leaves the .pcr as read and writes its last values to YVO3a_conv.new. The refinement as the tutorial sets it is ill-conditioned: a case for the engine's limits (P39) and its handling of correlations. Found on the way: FullProf prints two \(\chi^2\) per cycle, the standard one and the "conventional Rietveld" one on background-subtracted counts; a first reading compared the two and took a decrease for an increase.
manuals/fullprof/runs/converge_runs.py · manuals/fullprof/runs/*/CODE_conv.out
L0512026-10-09work

0.11.0 reviewed and merged: Bérar & Baldinozzi read on the Mac; the app built with the translated import

The work of L050 was reviewed on the Mac before it was merged into main:

  • All 58 core tests pass, clippy reports no warnings, the profile index regenerates the same.
  • The app, changed in the cloud but not built there (no GTK or WebKit in the container), was built on the Mac: its three tests pass, clippy is clean and diff-ref-struct.app builds. Through the translated import it opens Y2O3 at \(\chi^2\) 1.1922 and the Egyptian make-up at 4.1184, the values under FullProf's rules, and says which of FullProf's rules it keeps (the peak window, the reflections past the data).
  • The Bérar–Baldinozzi equations were read in manuals/papers/Berar_Baldinozzi_1993.pdf: g = g₀[1 + A(\(\theta\))F_a + B(\(\theta\))F_b] (eq. 14), F_a(z) = 2z exp(−z\(^{2}\)) (eq. 12), F_b(z) = (8z\(^{3}\) − 12z) exp(−z\(^{2}\)) (eq. 13), A(\(\theta\)) = A₀/tan \(\theta\) + A₁/tan 2\(\theta\) (eq. 15). F_b = (4z\(^{2}\) − 6)F_a, so FullProf's (4z\(^{2}\) − 3)F_a is F_b + 3F_a, and the import's P1 + 3P2 is exact, as L050 says. The app's change (opening a .pcr through the translated import) is released as app 0.1.1.
manuals/papers/Berar_Baldinozzi_1993.pdf pp. 128–129 · core/ · app/
L0502026-10-09work

The papers decide the defaults, and a .pcr is translated into the default model on import

Core 0.11.0, with the six papers of manuals/papers/ read.

The papers against each rule (page and equation):

  • Bérar & Baldinozzi (1993), pp. 128–129. The paper writes g = g₀[1 + A(\(\theta\))F_a(dθ/w) + B(\(\theta\))F_b(dθ/w)] (eq. 14), with F_a = 2z exp(−z\(^{2}\)) (eq. 12), F_b = (8z\(^{3}\) − 12z) exp(−z\(^{2}\)) (eq. 13) and A(\(\theta\)) = A₀/tan \(\theta\) + A₁/tan 2\(\theta\) (eq. 15). Its dθ is positive on the low-angle side: the transfer functions of eqs. 1–3 (p. 128) are nonzero for dθ > 0, and axial divergence sends intensity only below the Bragg angle (Finger, Cox & Jephcoat, p. 895). So:
  • the sign of z is FullProf's; the manual's eq. 3.29 prints the opposite, and L046's provisional default (the manual's) was wrong there;
  • the paper's F_b is the manual's 2(2z\(^{2}\) − 3)F_a. FullProf's (4z\(^{2}\) − 3)F_a is not the paper's: it is the paper's + 3F_a. Default now: the paper's sign and F_b. FullProf's F_b stays behind Convention::berar_baldinozzi.
  • Finger, Cox & Jephcoat (1994), pp. 893–895. Their S and H are the sample and detector half-heights at distance L. 2φ_min (eq. 4), 2φ_infl (eq. 5) and the Gauss–Legendre sum (eq. 9) are what the core's port of CrysFML's Prof_Val computes with S/L and H/L as given. CrysFML (lines 1289–1294) also reads a pair with D_L < 0.0002 as (H/L + S/L, H/L − S/L), so S_L alone means S/L = H/L = S_L/2. The paper has no such rule. Default: the pair as given; CrysFML's reading stays behind Convention::axial_divergence.
  • Thompson, Cox & Hastings (1987), pp. 80–82. The pseudo-Voigt (eq. 1), η (eq. 2) and H (eq. 3) are the core's, coefficient for coefficient. The paper's widths are F_G = V tan \(\theta\) (with the general Caglioti form, eq. 4) and F_L = X/cos \(\theta\) (eqs. 8a–8b): its X is FullProf's Y. FullProf's X tan \(\theta\) comes from the microstructure paper (eq. 19). No change.
  • Caglioti, Paoletti & Ricci (1958), pp. 224–225. Eq. 11 gives the squared Gaussian FWHM as a quadratic in a = tan \(\theta\)/tan \(\theta\)_M (eq. 5), the U tan\(^{2}\)\(\theta\) + V tan \(\theta\) + W form. No change.
  • Dollase (1986), p. 268. Eq. 4 sums P over the m symmetry-equivalent planes, which is the average L042 found in FullProf. Table 1, no. 6 is March's (r\(^{2}\) cos\(^{2}\)α + sin\(^{2}\)α/r)^(−3/2), FullProf's eq. 3.32. Table 1, no. 5 is the exponential b + (1 − b) exp(−Gα²); FullProf's eq. 3.31 writes exp(+G1 α²). Default: Dollase's sign; FullProf's stays behind Convention::preferred_orientation.
  • March (1932), p. 291. The pole density of an affine deformation is the cube of the deformation ellipsoid's radius (ρ_max/ρ_min = (a/c)³); its axial case is Dollase's no. 6. No change. The X-ray Lorentz–polarisation is set by none of these papers: the default stays the manual's eq. 3.26.

Convention now goes rule by rule: eight switches (Lorentz–polarisation, Bérar–Baldinozzi, axial divergence, exponential preferred orientation, excluded points, peak window, reflections past the data, microstructure output), with DEFAULT and FULLPROF.

Importing a .pcr (L047). Model::from_pcr (used by drs calc and the app) translates the values, refined under FullProf's rules, into the default model. Each translation is exact:

  • X-ray scale \(\times 2\) (Lp, Ilo = 0, 1, 4; profile-matching intensities carry no Lp);
  • (P1, P3) + 3(P2, P4) (Bérar–Baldinozzi);
  • the axial pair through CrysFML's reading;
  • G1 → −G1 for the exponential preferred orientation;
  • the excluded regions snapped to the points FullProf leaves out, on the data's grid. Two rules have no exact translation, so FullProf's are kept and listed: the peak window (a width without X tan \(\theta\) and an extra low point) and the reflections generated past the data (FullProf's undocumented limit). The microstructure output convention has no effect on the pattern and is set to the default. Model::from_pcr_fullprof keeps the values as written under all of FullProf's rules; the FullProf tests and drs calc --fullprof use it. The app now opens a .pcr through the translated import and says which FullProf rules it keeps. Its code changed, but it could not be built here (no GTK/WebKit in the cloud container); its test expects Y2O3's \(\chi^2\) between 1.0 and 1.5, and the import gives 1.192154.

Test default_convention (pcr_import). Every FullProf run of manuals/fullprof/runs/ was imported: the ten runs, the preferred-orientation run and the 35 window experiments, 47 in all. Each gives the same \(\chi^2\) as the .pcr's values under FullProf's rules, over the same points and weights: |\(\Delta\)\(\chi^2\)| = 0 in all 47. NdNi-LTOa, NdNi-LTOb and Pd3MnD are refused at import as version 2 (anisotropic size and strain). What the two kept rules explain, as |\(\Delta\)\(\chi^2\)|/\(\chi^2\) when they are also switched to the default (frozen per run):

  • YVO3: 5.5·\(10^{-2}\);
  • LaB6: 3.0·\(10^{-2}\);
  • rutile + anatase: 2.4·\(10^{-2}\);
  • NdNi-HTO: 3.4·\(10^{-3}\);
  • Co-YBCO: 4.0·\(10^{-4}\);
  • MgAl2O4: 2.3·\(10^{-4}\);
  • Yb2ZrWO8-PO-March: 1.1·\(10^{-4}\);
  • Y2O3: 1.0·\(10^{-4}\);
  • Yb2ZrWO8: 3.4·\(10^{-6}\);
  • Egypt: 3.4·\(10^{-7}\);
  • the window experiments: 0 to 0.93. The window experiments are modified copies, not fits; at Wdt = 4 FullProf's window cuts the Lorentzian tails, and there the default fits better (yvo3_base_w4: \(\chi^2\) \(34.8 \to 3.2\)). The part of default_convention that checks rules, default_rules, checks the paper's B&B formula and the identity FullProf(P1, P2) = paper(P1 + 3P2, P2) to \(10^{-14}\).

The parameter dictionary was updated accordingly. Each paper's checked now cites its PDF in manuals/papers/ with page and equation, and the references carry their DOIs. The asymmetry and preferred-orientation entries carry the papers' equations, and every rule's note says how a .pcr's value is translated.

For Josu: the two rules kept from FullProf on import (peak window, reflections past the data) are the open compatibility points; L048 accepted their frozen differences. The least-squares engine is not started.

manuals/papers/*.pdf (Bérar & Baldinozzi 1993 pp. 128–129; Finger, Cox & Jephcoat 1994 pp. 893–895; Thompson, Cox & Hastings 1987 pp. 80–82; Caglioti, Paoletti & Ricci 1958 pp. 224–225; Dollase 1986 p. 268; March 1932 p. 291) · core/src/{powder,profile,microstructure,main}.rs · core/tests/default_convention.rs · app/src-tauri/src/main.rs · ecosystem/data/parameters.yml · CrysFML 8ed8670 CFML_PowderProfiles_Finger.f90 l. 1289–1294
L0492026-10-09work

The app's walking skeleton: a Tauri window that opens a .pcr and draws the core's pattern

The first build of the app (app 0.1.0), as L044 planned, on the Mac: a Tauri 2 window with the core linked as a library (no server, no Python). Its one command, open_pcr, reads a .pcr and its data through the core as drs calc does and returns the pattern, the reflections of each phase, the excluded regions and Rp, Rwp, \(\chi^2\). The window, plain HTML and JavaScript on a canvas with no framework and no network, draws Yobs, Ycal, the difference, a row of reflection markers per phase (λ2 fainter) and the excluded regions shaded, with zoom, pan and a readout under the cursor; light and dark follow the system. The bundle diff-ref-struct.app is 9.6 MB. Checked: three tests (a reference run opened through the command; a missing file gives a message), clippy clean; the drawing rendered in Chrome on Y2O3, rutile + anatase and the five-phase Egyptian make-up from what the command returns. Not checked by hand: the app's own window and its file dialog. The first drawing showed the calculated curve falling in Egypt's excluded region at 5–6.7°, where the profile is the background alone by design; the regions are now shaded and the difference is left out there.

app/ · app/README.md · commit 46a80a8
L0482026-10-09decision

L042's open points accepted as FullProf-compatibility details

Josu accepts the points L042 left frozen: YVO3 at Wdt = 40 (rms 2.3·\(10^{-2}\) against FullProf, where the same YVO3 at Wdt = 4 agrees to 5·\(10^{-6}\)), and the raised frozen maxima of the window-edge points of rutile + anatase (\(0.082 \to 0.127\)) and MgAl2O4 (\(0.014 \to 0.025\)), the side effect of the extra-low-point rule that fixed 35 experiments and LaB6. Under L043 they are details of FullProf's peak window, kept in the compatibility layer and frozen in their tests; whether they matter is judged by the refined parameters once the engine exists (the same structure within the esd's). Set aside: holding the engine until FullProf's window at large Wdt is reproduced exactly.

conversation of 9 October 2026 · L042 · core/tests/fullprof_profiles.rs
L0472026-10-09work

0.10.0 reviewed and merged: a .pcr must be translated into the default model, not recalculated under it; two references settled

The parameter dictionary and the default/FullProf split of L046 were reviewed on the Mac before they were merged: all 58 core tests pass, clippy reports no warnings, build.py writes the dictionary page and the generated table. Found in the review:

  • A .pcr read under the default convention keeps FullProf's refined values, but the default X-ray Lorentz–polarisation is half of FullProf's, so the fit is lost: Y2O3's \(\chi^2\) goes from 1.19 to 14.4 (the app's test caught it). The parameters of a .pcr were refined under FullProf's rules, so importing one must translate them into the default model (the scale factor \(\times 2\) for that rule, for instance), and keep FullProf's rules only where no exact translation exists. Until then the app opens a .pcr under the FullProf convention and says so.
  • The two page ranges L046 left open are settled from Crossref's records of the DOIs. Thompson, Cox & Hastings (1987) is J. Appl. Cryst. 20, 79–83, doi:10.1107/S0021889887087090; the dictionary cited ...087089, a different article on pp. 84–89, which was where 84–89 came from. Dollase (1986) is J. Appl. Cryst. 19, 267–272, doi:10.1107/S0021889886089458.
  • core/src/parameter_table.rs is added to CLAUDE.md's list of generated files. The six papers L046 asks for could not be downloaded from the Mac either: the publishers refuse scripted access (IUCr, Wiley, Elsevier: 403; De Gruyter: no PDF without a subscription). Josu is to add them from UPV/EHU's subscriptions to manuals/papers/.
core/src/powder.rs · app/src-tauri/src/main.rs · ecosystem/data/parameters.yml · api.crossref.org
L0462026-10-09work

The parameter dictionary, and the default model split from FullProf's rules; the papers themselves still unread

Two items of phase 2 (L043, L044), core 0.10.0.

What could not be done. The original papers could not be read. The session's network policy refused journals.iucr.org, doi.org, api.crossref.org and arxiv.org, and the repository holds none of the papers (only FullProf's manuals, tutorials and the microstructure paper). So:

  • each paper's full reference was checked against bibliographic records found by web search, linked in the dictionary; their equations were not read;
  • two page ranges disagree between sources and are left marked: Thompson, Cox & Hastings (1987), 79–83 in FullProf's manual and GSAS-II, 84–89 in an IUCr issue listing; Dollase (1986), 267–272 in most bibliographies, 273–274 in the listing;
  • March (1932) was not found, and is cited only through Dollase. The comparison of L036–L042's rules "with the original paper" is therefore made with FullProf's manual and with the microstructure paper, which are in the repository. Where FullProf departs from them, the core's default now follows the manual. Each such default is marked as not yet checked against its paper.

The default model and the FullProf convention (powder::Convention). A model read from a .pcr follows the manual. Convention::FullProf keeps FullProf's rules: the FullProf tests set it, and so do drs calc, drs simulate and drs mic with --fullprof. Rule by rule:

  • X-ray Lorentz–polarisation. Manual eq. 3.26 (p. 53) with K = ½ gives ½(1 + Cthm cos\(^{2}\)2\(\theta\))/(2 sin\(^{2}\)\(\theta\) cos \(\theta\)); FullProf computes twice that (L033). Default: eq. 3.26. A factor the scale absorbs: a default model's scale is twice FullProf's.
  • Bérar–Baldinozzi asymmetry. Manual eq. 3.29 (p. 54) prints z = (2\(\theta\)_i − 2\(\theta\)_h)/H and F_b = 2(2z\(^{2}\) − 3)F_a; FullProf computes −z and (4z\(^{2}\) − 3)F_a (L035). Default: eq. 3.29 as printed. The paper (Bérar & Baldinozzi 1993, J. Appl. Cryst. 26, 128) decides, under L043, and was not read: this default is provisional.
  • The peak window. The manual (p. 75) sets the profile to zero beyond Wdt × FWHM. FullProf counts the width without X tan \(\theta\) for TCH (L035) and takes one more low point when X ≠ 0 (L042). Default: ±Wdt·H, the points within it.
  • Reflections past the data. FullProf generates none below the first point and stops at a limit it does not document (L035). Default: every reflection whose window reaches the data, on either side.
  • Excluded regions. FullProf also drops a point whose step reaches into a region (L033). Default: the points within the regions.
  • Microstructure. The microstructure paper's eq. 14 (manuals/fullprof/docs/Microstructural_effects.pdf, p. 3) takes β* = β cos \(\theta\)/λ with the reflection's own λ; FullProf uses λ1 for the λ2 reflections (L039). Default: the reflection's λ. FullProf's √|U| for a negative U (L039) becomes "no strain" in the default.

Not changed, with the reason:

  • The March–Dollase average over equivalent reflections (L042) is not a FullProf departure: GSAS-II computes it the same way (GSASIIstrMath.py GetPrefOri, lines 3066–3078, commit 305a159), and the manual is silent.
  • Widths at the Bragg angle without Zero (L039): the manual's \(\theta\) is the reflection's.
  • Bérar–Baldinozzi on top of FCJ (L042): the manual (pp. 109, 112) does not make them exclusive.
  • The parametric resolution file's WAVE, CTHM and ASYM (L042): rules of reading FullProf's input; the 2001 manual does not describe those lines.
  • The .prf background offset and the background-only last point: they belong to reading FullProf's output and stay in the tests.

How far the default lies from FullProf's convention on the runs, as the peak part's rms and maximum over its largest value, with the Lp factor 2 taken out (test default_convention, frozen):

  • Y2O3: 1.3·\(10^{-3}\) and 4.5·\(10^{-2}\), at 29°;
  • Yb2ZrWO8: 1.7·\(10^{-3}\) and 2.9·\(10^{-2}\), at 29.6°;
  • every other run: at most 8.7·\(10^{-4}\) and 3.2·\(10^{-3}\). The largest differences sit below AsyLim: the Bérar–Baldinozzi sign. All FullProf tests pass unchanged under Convention::FullProf.

The parameter dictionary (ecosystem/data/parameters.yml), 45 entries, 39 of them refinable. Each entry gives:

  • a readable id (sample_displacement), the symbol, the unit and the meaning;
  • the equation in LaTeX, cited to the page where it is printed, with its paper;
  • FullProf's name and .pcr line;
  • GSAS-II's name, cited to its variable table (GSASIIobj.py lines 613–676, commit 305a159);
  • the range, measured on the 240 .pcr files of manuals/fullprof/ that the core reads, the window experiments left out;
  • the default, and when to refine, after the manual's sequence (p. 14). tools/build.py renders it as the manual page "Parameters: the theory" and writes the core's parameter_table.rs. The new test parameter_dictionary fails when a field of Model, Phase or Atom is neither mapped to an id nor named as no parameter (checked by removing Phase.y), and when the table is out of date. Found while writing it: GSAS-II's Lorentzian width is Z + X/cos \(\theta\) + Y tan \(\theta\) (GSASIIpwd.py line 929), so its X and Y are FullProf's Y and X; and its U, V, W give a Gaussian variance in centidegrees\(^{2}\), not H\(^{2}\).

For Josu:

  • To finish the comparison with the papers, the PDFs of the papers below could be added to the repository (for example manuals/papers/), or journals.iucr.org and doi.org allowed in the environment's network settings: Bérar & Baldinozzi 1993, Dollase 1986, March 1932, Thompson, Cox & Hastings 1987, Finger, Cox & Jephcoat 1994, Caglioti, Paoletti & Ricci 1958.
  • The Bérar–Baldinozzi default is the one that matters (up to 4.5% of a peak). It stays provisional until that paper is read. The least-squares engine is not started.
core/src/{powder,profile,microstructure,parameters,parameter_table}.rs · core/tests/{default_convention,parameter_dictionary}.rs · ecosystem/data/parameters.yml · manuals/fullprof/FullProf_Manual.pdf pp. 14, 53–56, 75 · manuals/fullprof/docs/Microstructural_effects.pdf eq. 14 · GSAS-II 305a159 GSASIIobj.py l. 613–676, GSASIIpwd.py l. 926–929, GSASIIstrMath.py l. 3066–3078
L0452026-10-09decision

The licence opened as design question Q7: the parts ported from CrysFML are LGPL-3

Several parts of the core are ported from CrysFML, which is under the LGPL-3: the Finger–Cox–Jephcoat profile (Prof_Val), the choice of unique reflections (Hkl_Uni, Asu_Hkl), the scattering tables and the profile range (Locate). A translation into Rust is a derivative work. While the repository is private nothing follows; a distributed app brings the LGPL's obligations for those parts. Opened as Q7 on the Design page, to be decided before any public release. The options: an LGPL-compatible licence for the project; keeping it private and undistributed; or rewriting those parts from the published papers instead of from CrysFML's code. Until then, every port from CrysFML keeps naming its source file and commit, so that the parts concerned can be found.

conversation of 9 October 2026 · Design, Q7 · core/README.md
L0442026-10-09decision

A parameter dictionary as the single source of names and theory; an early walking skeleton of the app

Two items added to the roadmap with Josu:

  • The parameter dictionary (phase 2, before our own control file): one entry per parameter with a readable name (sample_displacement, not Sycos), symbol and unit, its meaning in a sentence, the equation in LaTeX with the paper it comes from, the FullProf and GSAS-II equivalents, the usual range, the default and when to refine it. The core's names, the app's tooltips, the editor's hover and completion, and the theory part of the manual are all generated from it, so they cannot drift apart. FullProf's cryptic names and the missing theory behind its parameters are what this is meant to fix.
  • A walking skeleton of the app (phase 3, started early, alongside phase 2): a Tauri window that opens a .pcr, calls the core as a library, with no server, and draws Yobs, Ycal, the difference and the reflection markers. It tests the join between the Rust core and Tauri while that is still cheap to change, and is built on the Mac, since a cloud session cannot build the app. Set aside: names and help texts written separately in each place; waiting for phase 3 to join core and app.
conversation of 9 October 2026 · Roadmap
L0432026-10-09decision

FullProf and GSAS-II are references for results, not templates; GSAS-II installed on the Mac

Josu's concern, made a rule (CLAUDE.md; design question Q8): the aim is to obtain the results FullProf and GSAS-II obtain, with our own core, not to copy either program. So:

  • the core's own model is the default: parameters with physical names, equations taken from the original papers, algorithms chosen on their merits (a peak window set by an accuracy target, for example);
  • what FullProf does differently from its manual or from the papers (the sign and F_b of the Bérar–Baldinozzi asymmetry, the peak window and its extra low point, the .prf background offset, the March–Dollase average) lives in a FullProf-compatibility layer, documented and tested, used to validate against FullProf and to import .pcr files;
  • where FullProf departs from its manual, the original paper decides the default. Each rule accepted in L036–L042 is to be checked against its paper (roadmap, phase 2).

GSAS-II is the second reference, for refinement results above all. It was installed on the Mac on the same day from the GSAS-II team's installer for Intel Macs (gsas2main-Latest-MacOSX-x86_64.sh, GSAS-II-buildtools v1.0.1, GSAS-II version 5843) into ~/Applications/GSAS-II, apart from the repository; its scripting interface (GSASIIscriptable) was checked to run. conda-forge has no build for Intel Macs. Set aside: matching FullProf's numbers for their own sake; FullProf as the only reference.

conversation of 9 October 2026 · CLAUDE.md · Design, Q8
L0422026-10-09finding

The peak window: CrysFML's low edge, the parametric resolution file and March–Dollase found; YVO3 at Wdt = 40 still open

The 35 FullProf runs of manuals/fullprof/runs/window_experiments/ (L041) and Yb2ZrWO8-PO-March are now in fullprof_profiles as exact comparisons: 53 files compared (17 before), all passing; 23 of the experiments with no point set apart, 12 with 1–3 window-edge points counted and frozen. Core 0.9.0.

The window. CrysFML sets a profile range with Locate, which returns j with V(j) \(\le\) x < V(j+1) (deps-pycrysfml 2258e77, CFML/Testing/nFP/Src/nFP_Reflections.f90 lines 205–208, pos_left = pt - fwhm*wdt, ptr(1) = locate(xp, pos_left); CFML/Src/CFML_Maths/Math_Locate.f90 lines 64–68). So the low side starts one point below 2\(\theta\) − Wdt·H and the high side ends at the last point at or below 2\(\theta\) + Wdt·H. FullProf's windows show this extra low point for TCH phases with X ≠ 0 and not for X = 0. Without it, every Yb2ZrWO8 experiment with X ≠ 0 misses exactly one low point per peak (16 per file), and the YVO3 Wdt = 4 experiments miss 230–469. With it they all agree. The X = 0 experiments, PPH3, citric acid, Y2O3 and Yb2ZrWO8 agree to \(10^{-5}\) without it and worse with it. Why X decides it is not known. LaB6's window-edge points fall from 50 to 2.

L041 revised. The "shorter high side" with a parametric resolution file came from two inputs the measurement did not know FullProf takes from that file:

  • its WAVE and CTHM lines replace the .pcr's wavelengths, Ratio and Cthm. In yb_irf1_x0 the .pcr gives 1.5406, 1.5406, 1, 0.7899, and the .out prints 1.54056, 1.54440, 0.4374, 0.8000;
  • its ASYM S_L, D_L is added to the phase's: in yb_all_x0, 0.0637 + 0.0637. With either alone the profile is up to 53% off; with the sum it agrees to \(10^{-5}\). FullProf also applies the phase's Bérar–Baldinozzi factor on top of the FCJ profile when a phase has both: in yb_irf1_x0 (Asy1 = 0.00901 and the file's S_L) FCJ alone gives 2.9·\(10^{-2}\), the product \(10^{-5}\). With these, the window is the symmetric Wdt × H_noX, and all ten parametric-file experiments agree to rms \(\le 2.3\)·\(10^{-5}\).

The test also had to change in two ways:

  • the tick marks of the experiments use the Zero after the cycle, read from the .out, because their .pcr was not rewritten;
  • FullProf marks the λ2 positions even with Ratio = 0 (yvo3_noK2: 479 marks). find_file now also searches the grandparent and its folders, because the experiments name the data of runs/.

Preferred orientation. FullProf's P is the March–Dollase factor (eq. 3.32) averaged over the reflections equivalent to h, not taken at h alone. In Yb2ZrWO8-PO-March, G1 = 0.85 about [1 1 0]: P(2 2 0) = CORR/CORR without PO = 1.010306; the average over the 12 equivalents is 1.010305, h alone gives 1.628. All 16 reflections agree to \(10^{-6}\). The run is added to fullprof_corrections (1707 reflections of 11 runs) and its profile to fullprof_profiles (rms 3.6·\(10^{-5}\), \(\chi^2\) 1.7040 against FullProf's 1.704). The same average is assumed for the exponential form, which has no reference.

Not found, frozen in fullprof_profiles (count and size; the test fails if either grows), for Josu:

  • YVO3 itself at Wdt = 40 (YVO3a_calc.prf): still rms 2.34·\(10^{-2}\), max 0.193, 4005 window-edge points. The same YVO3 at Wdt = 4 agrees to rms 5·\(10^{-6}\) but for one point, so the difference comes with the wide window. A low side of Wdt·H with X and a high side of Wdt·H_noX gives rms 1.9·\(10^{-2}\) and leaves a smooth offset that grows with angle.
  • Rutile + anatase (106 points, up to 0.127; before 106 up to 0.082) and MgAl2O4 (42 up to 0.025; before 47 up to 0.014): FullProf's low edge lies between the bare edge and the extra point. In rutile 110 at 27.46°, the point at 26.93°, 0.59 step beyond, is left out, where the experiments take points up to 0.97 step beyond.
  • 15 points in 12 experiments: the high side of the lowest-angle reflection, always inside the asymmetry range (Yb2ZrWO8 111 below AsyLim with Bérar–Baldinozzi, YVO3 101 and LaB6 100 with FCJ; LaB6's 2 points are of this kind). Two more are at 139–141° from peaks past the data's end (w8_x0.645). The least-squares engine is not started: YVO3 is still frozen.
core/src/powder.rs · core/tests/fullprof_profiles.rs · core/tests/fullprof_corrections.rs · manuals/fullprof/runs/window_experiments/ · manuals/fullprof/runs/Yb2ZrWO8-PO-March/ · deps-pycrysfml 2258e77 CFML/Testing/nFP/Src/nFP_Reflections.f90 l. 205–208, CFML/Src/CFML_Maths/Math_Locate.f90 l. 64–68
L0412026-10-09finding

Where FullProf cuts a peak: symmetric Wdt × the width without X·tanθ, except with a parametric resolution file

FullProf was run on modified copies of the refined Yb2ZrWO8 run, one parameter at a time, and each isolated window measured in its .prf (scripts, results and method in manuals/fullprof/runs/window_experiments/):

  • With a tabulated resolution file, the half-window is Wdt × the TCH width computed without the phase's X·tanθ. It stays the same from X = 0.1 to 1.5 while the peak width triples, and holds from Wdt = 4 to 40 (with X = 0.645: 0.0750·Wdt against 0.0753 at 72.9°, 0.1504 against 0.1510 at 139.9°). With X ≠ 0 the low-angle edge reaches one step further.
  • The Kα2 line and the Finger–Cox–Jephcoat asymmetry leave the rule as it is; with the asymmetry FullProf's marker moves, not the window.
  • With YVO3's parametric resolution file (Res = 1, Xins = 0.0402) the window is no longer symmetric. Its low-angle side is Wdt × that width (1.008 to 1.05 of it from 140° to 30°), its high-angle side shorter, from 0.92 of it at 29.8° to 0.79 at 139.9°. The core's symmetric window cannot reproduce that: the likely cause of YVO3's differences. The rule for the high-angle side is still to be fitted.

Also made: a preferred-orientation reference, Yb2ZrWO8-PO-March (March–Dollase about [1 1 0], G1 = 0.85), since no tutorial run uses preferred orientation. The exponential function gave none: this FullProf build reads its G1 (0.30, −0.30), lists 0.0000 and calculates the pattern without preferred orientation.

manuals/fullprof/runs/window_experiments/ · manuals/fullprof/runs/po_runs.py · FullProf macOS February 2025
L0402026-10-09decision

0.8.0 merged; of L039, two differences accepted and the peak window kept open

0.8.0 (L039) was reviewed before it was merged: all 55 tests pass, clippy reports no warnings, the profile index regenerates the same, and every difference kept apart is frozen in its test (count and size).

Josu accepts two of the four points L039 listed:

  • the pseudo-Voigt η of Y2O3, which rises with angle 4·\(10^{-5}\) more slowly than FullProf's at 145° and leaves the profile unchanged;
  • Egypt's CORR column, which is not the factor FullProf applies (its profile agrees to 1.6·\(10^{-5}\)).

Not accepted as final: the peak window (rutile + anatase, MgAl2O4, LaB6) and YVO3, whose whole profile differs (rms 2.35·\(10^{-2}\), up to 0.19). They are one problem, and YVO3 is the version-1 size and strain case: an engine built on it would move strain and size to make up for it. FullProf is run on the Mac to find the rule (L041), and the least-squares engine waits for it. Set aside: accepting the window rule as unknown and starting the engine.

conversation of 9 October 2026 · core/tests/fullprof_profiles.rs · L039
L0392026-10-09work

Phase 2, step 3: isotropic size and strain, the corrections checked on the runs, four runs of tutorial 6 in the profile test

Size and strain (P24). New module microstructure and drs mic: per reflection, what FullProf writes in its .mic file. The sources are:

  • the sample's widths of eq. 19 of the microstructure paper (manuals/fullprof/docs/Microstructural_effects.pdf, p. 4), added to the resolution file's;
  • the split into size (√I_G/cos \(\theta\), Y/cos \(\theta\)) and strain (√U tan \(\theta\), X tan \(\theta\)) that the .mic header states;
  • the TCH (H, η) (eqs. 9–10, p. 2) and the integral breadth of the pseudo-Voigt (eq. 7);
  • apparent size D = 1/β* (Scherrer, eq. 14) and maximum strain e = ½β*d (eq. 17).

CrysFML08's microstructure routines (deps-pycrysfml commit 2258e77, Pow_MicroStructure.f90) cover only the anisotropic models. Found on the runs:

  • β* is taken with λ1 for the λ2 reflections too (strains larger by λ2/λ1, 61.0327 against 60.8810 in YVO3).
  • A negative U enters as √|U|.
  • Where the resolution file's H_G\(^{2}\) is negative (NdNi-HTO above 150°), FullProf prints HGi and betaG as NaN and Beta_TCH as 0.
  • FullProf prints the apparent sizes and strains of the values after its cycle, and the widths and breadths of those it read. YVO3's cycle moves U from 0 to −0.0914 and X from 0.645 to 0.821; its printed strain 60.8810 is that of the shifted values, where the values read give 44.2415.

LorSiz and the size and strain models are anisotropic: version 2 (P25), refused with a message.

Widths at the Bragg angle. FullProf evaluates the widths at the Bragg angle, without Zero and the displacement terms.

  • With this, its .out widths (Hw, HwG, HwL, ETA) are reproduced to 2·\(10^{-5}\), against up to 2.6·\(10^{-3}\) at the shifted angle (YVO3, Zero 0.0555°).
  • It also brought Co-YBCO from rms 4.8·\(10^{-4}\) to 1.5·\(10^{-4}\).
  • It cut the window-edge points kept apart in L035 from 181 to 106 (rutile + anatase) and from 49 to 47 (MgAl2O4); their freeze is tightened to these numbers.

Corrections (P27–P29). Found on the runs: the CORR column of FullProf's reflection lists is m · Lp · A · P, times Ratio for λ2.

  • New test fullprof_corrections compares it for 1691 reflections of 10 runs, all within the print of d and CORR.
  • It covers X-ray Lp with and without a monochromator, neutron Lp, λ2, and cylinder absorption with μR = 0.386 (the NdNi runs; without absorption the product would be off by 5–6%).
  • No run uses preferred orientation (Pref1 is neutral in every phase), so it is not checked.
  • The surface micro-absorption (line 15) is used by no run and is now refused with a message, where it was silently ignored.
  • Y2O3's pseudo-Voigt list prints CORR = 1 and is left out.

Weights. Iwg is now read: maximum likelihood (Iwg = 1, manual p. 43) weights by 1/\(\sigma\)²_calc. LaB6's \(\chi^2\) becomes 12.9486 against FullProf's 12.94 (with 1/\(\sigma\)²: 15.35).

Tests:

  • fullprof_microstructure:
  • the .out widths of 9 isotropic runs, 2710 reflections, at the Bragg angle of FullProf's printed d;
  • every row of the .mic files of YVO3 (468), Yb2ZrWO8 (16) and NdNi-HTO (204, nuclear phase): widths and breadths against CODE.pcr, size and strain against CODE_calc.pcr, within the print of its parameters.
  • fullprof_profiles:
  • Yb2ZrWO8 is exact (rms 3.4·\(10^{-5}\), \(\chi^2\) 1.6600 against 1.66).
  • LaB6 is exact (rms 7.7·\(10^{-5}\), \(\chi^2\) 12.9486 against 12.94), with 50 window-edge points frozen.
  • YVO3 is frozen (below).
  • The tick marks of a run are its Bragg angles plus the Zero after the cycle (YVO3: 20.4825 + 0.0619 = 20.5444; the refined cell would put them 0.1° off).
  • NdNi-HTO is left out for its magnetic phase; NdNi-LTOa, NdNi-LTOb and Pd3MnD as version 2.
  • fullprof_spacegroups now covers 49 blocks in 37 files (B m a b, P b n m, P c c n, P m -3 m new):
  • it compares Hall symbols as groups ("B m a b": the table's '-B 2ab 2' and FullProf's '-B 2bc 2' differ by a centring translation);
  • it skips the magnetic phase's block, which has no symbol.

Listed for Josu (the gate): differences not resolved, explained and frozen in their tests. 1. The peak window. Wdt × (the TCH width without X·tan \(\theta\)) reproduces FullProf's windows only to 0.5% rms (a fit gives 1.013 × that width). Neither a share of X·tan \(\theta\), nor a fraction of a step, nor Wdt·H fits rutile, MgAl2O4, LaB6 and YVO3 together. The points just outside the core's window are frozen:

  • rutile + anatase: 106 up to 0.082;
  • MgAl2O4: 47 up to 0.0136;
  • LaB6: 50 up to 0.407;
  • YVO3: 4006 up to 0.202. 2. YVO3. X = 0.645 makes FullProf's windows several times wider than that rule and narrower than Wdt·H. Frozen at rms 2.35·\(10^{-2}\), max 0.194, |\(\Delta\)\(\chi^2\)| 0.0105 (2.3001 against 2.29). 3. Y2O3's pseudo-Voigt η. It follows η0 + X·2\(\theta\) with a slope 7·\(10^{-5}\) below the X read, up to 4·\(10^{-5}\) at 145°; the profile is not affected. Frozen at 1.89 of its tolerance in fullprof_microstructure. 4. The Egyptian make-up's CORR. Off by 6% to a factor 17 from m·Lp·A, while its profile agrees to 1.6·\(10^{-5}\), so it is not the factor applied; its meaning there is not known. 5. Not implemented: magnetic structure factors (NdNi-HTO's profile is not compared) and the de Keijser "beta" column of the .mic.
core/src/{microstructure,powder}.rs · core/tests/fullprof_{microstructure,corrections,profiles,spacegroups}.rs · manuals/fullprof/docs/Microstructural_effects.pdf pp. 1–4 · manuals/fullprof/runs/*/CODE{,_calc}.{pcr,out,mic,prf} · FullProf manual pp. 43, 75 · deps-pycrysfml@2258e77 Pow_MicroStructure.f90
L0382026-10-09decision

L036 accepted; FullProf run on the microstructure tutorial for the size and strain step

Josu accepts L036: where FullProf's output departs from its manual (the sign and F_b of the Bérar–Baldinozzi asymmetry, the width the peak window is counted in, the reflections generated past the data), the core follows FullProf, each rule measured on its runs and stated in the code; the two rules not found (the reflection limit past Thmax, the last 0.06–2.7 % of the window when X ≠ 0) stay apart, counted and frozen in fullprof_profiles (L037).

For the next step of phase 2, size and strain, the material had no FullProf calculated pattern that exercises it, so FullProf was run on the Mac on seven .pcr files of tutorial 6 (6-Microstructure/), with the same two passes and output flags as L034, into manuals/fullprof/runs/:

  • isotropic size and strain over a resolution file, version 1 (P24): YVO3 (X-rays, X and Y), Yb2ZrWO8 (Lorentzian and Gaussian size), NdNi-HTO (neutrons, strain X, two phases);
  • the instrument's widths fitted on a standard without a resolution file: LaB6;
  • anisotropic size and strain, version 2 (P25), run now so that their references exist: NdNi-LTOa, NdNi-LTOb (strain models 3 and 6) and Pd3MnD (spherical-harmonic size). All seven finish in both passes. run_fullprof.py now takes run names, so the five runs of L034 were not redone.
conversation of 9 October 2026 · manuals/fullprof/runs/README.md · manuals/fullprof/6-Microstructure/
L0372026-10-09decision

0.7.0 reviewed and merged; the gate rule in CLAUDE.md; the window-edge points frozen in their test

The work of L035 and L036 was reviewed on the Mac before it was merged into main:

  • All 52 tests pass and clippy reports no warnings. The profile index was regenerated by its script: the same. The round did not change manuals/.
  • L035's finding about the FullProf runs (L034) is right and corrects them: asked for NCY = 0, FullProf still ran one cycle in every run ("Number of cycles: 1") and rewrote CODE_calc.pcr after it (anatase's Ti Biso \(0.538 \to 0.486\)). The outputs of CODE_calc belong to CODE.pcr. manuals/fullprof/runs/README.md and run_fullprof.py said otherwise and are corrected.
  • The window-edge points that L035 keeps apart (49 for MgAl2O4 up to 1.35·\(10^{-2}\), 181 for rutile + anatase up to 8.15·\(10^{-2}\)) were only printed by fullprof_profiles, so a change that made them more or worse would have passed. They are now frozen: the test fails if their count or their largest deviation grows. Checked by lowering the rutile count to 180, which fails.

Decided with Josu on the same day, and written into CLAUDE.md: nothing is built on an unexplained difference with FullProf. A step starts only when the tests below it pass; a difference that cannot be resolved is explained with evidence, counted and frozen in its test, and listed for Josu to accept; a tolerance is never widened to make a test pass. The least-squares engine (phase 2, step 4) starts only when fullprof_profiles passes on the five runs. Also in CLAUDE.md: the generated tables in core/src are not run through cargo fmt, so that they stay byte for byte as their scripts write them.

L036, the cloud session's decision to follow FullProf's output where it departs from its manual and to keep the two unresolved rules apart, is presented to Josu for acceptance.

CLAUDE.md · core/tests/fullprof_profiles.rs · manuals/fullprof/runs/README.md
L0362026-10-09decision

FullProf rules found only in its output are taken as found, and the unresolved ones are kept apart

Three things the core now does as FullProf does, not as its manual says: the sign and the F_b of the Bérar–Baldinozzi asymmetry, the width the peak window is counted in, and the reflections generated past the data. Each is measured on FullProf's runs and stated in the code with its evidence.

Two rules were not found:

  • The reflection limit past Thmax. The tests take FullProf's printed S(1/d)max.
  • The last 0.06–2.7% of the window when X ≠ 0. The test reports those points apart and keeps the exact tolerance on the rest.

Reason: the aim is FullProf's numbers, and these rules can only be read from them. Keeping the unresolved points apart, counted and bounded, shows them instead of hiding them in a looser tolerance.

Set aside:

  • keeping the manual's formulas and widening the tolerances (Y2O3 would pass at 0.5, which tests nothing);
  • asking FullProf's author: possible later, but no answer is needed to go on.
L035
L0352026-10-09work

Phase 1 closed against FullProf's own structure factors; the pattern differences of L034 found, one by one

Which .pcr the runs belong to. Asked for NCY = 0, FullProf still runs one cycle: CODE_calc.out says "Number of cycles: 1", and the shifted values are written back into CODE_calc.pcr. The .hkl, .prf and the R-factors of CODE_calc belong to the values FullProf read, those of CODE.pcr.

  • Evidence: with CODE_calc.pcr, anatase's F differ by up to 1.8% at high angle. The .out shows Ti's Biso moving from 0.538 to 0.486 in that cycle. With CODE.pcr, every F agrees to the print.
  • The same may be behind LaMnO3ref.prf (L033): a .pcr written after the cycle its .prf was drawn before. Not checked: the earlier state is not in the material.
  • Both tests read CODE.pcr, and the generator says why.

Structure factors (phase 1). The new test fullprof_reference_sf compares F, real and imaginary, with FullProf's CODE_calc.hkl (Hkl = −3, conventional cell). It covers all 11 phases of the five runs, 1992 reflections.

  • All agree within a bound derived from FullProf's print (half a unit of the fourth decimal) and its single precision. The phase 2πh·x carries the rounding of x times |h|+|k|+|l|, and f, Occ, T a few units of 2⁻²⁴.
  • The worst reflection is at 0.50 of the bound: Co-YBCO, where |ΔF| = 5·\(10^{-5}\) is the print.
  • The largest differences, 8·\(10^{-4}\) on |F| \(\approx 55\), are in the Pb phases of the Egyptian make-up, with |h|+|k|+|l| up to 15.
  • Phase 1 is done: the reflections (L028) and the structure factors of the four reference structures agree with FullProf's own output from the command line.

The pattern differences of L034. Against CODE_calc.prf, rebuilt from CODE.pcr, each cause was found from the files and checked on every run.

  • Bérar–Baldinozzi asymmetry (Y2O3: up to 0.48 near 20.7°, rms 1.7·\(10^{-2}\)).
  • FullProf does not compute eq. 3.29 (manual p. 54) as printed. The core's peaks leaned the wrong way.
  • With z of the opposite sign and F_b = (4z\(^{2}\) − 3)·F_a, in place of 2(2z\(^{2}\) − 3)·F_a, Y2O3 agrees to its print (rms 7·\(10^{-5}\)).
  • The same two changes were inferred from FullProf's output by the EDI project (enhantica/edi, commit 855a426, pd-neut-cwl_PbSO4_beba-asymmetry.py). CrysFML has no such routine.
  • P3 and P4 are not checked: all runs have them at zero.
  • Reflections past the data. Wdt = 40 lets Y2O3's reflections beyond 145.9° reach into the pattern, by up to 1.6% at the end.
  • FullProf generates, per phase, up to the S(1/d)max its .out prints, and nothing below Thmin (S(1/d)min is Thmin exactly, in every run).
  • The limit grows with the phase's own sample broadening. The two Egyptian phases with none stop exactly at Thmax.
  • Its rule was not found: no single multiple of any width fits the 11 phases.
  • The core takes the limit as Phase::reflection_limit, which the tests read from the .out. Unset, the core goes 5° past the data.
  • The peak window (rutile + anatase up to 0.18, MgAl2O4 up to 0.033). The manual (p. 75) says the profile is zero beyond Wdt × FWHM. The window edges show something else:
  • Each peak's window was fitted against the core's untruncated peaks.
  • For TCH, FullProf counts the window in a width that leaves out the phase's Lorentzian strain term X·tan \(\theta\). For 11 rutile reflections, windows of 0.525° to 1.244° agree with that width to 0.003°.
  • MgAl2O4, whose only Lorentzian term is X, gets Wdt·H_G. The Egyptian make-up and Co-YBCO (X = 0) get the whole FWHM.
  • The window is symmetric about 2\(\theta\)_h also for the Finger–Cox–Jephcoat profile. The core had extended it to CrysFML's emin.
  • Not resolved: where X ≠ 0, FullProf's window is still wider than this by 0.06% (rutile 110) to 2.7% (anatase 101). A share of X·tan \(\theta\), or a fixed part of a step, does not fit all the edges.
  • The test reports those points apart: 181 for rutile + anatase and 49 for MgAl2O4, all within 3% of the window plus one step beyond its edge. The exact tolerance holds on all the others.
  • Excluded regions. A point is excluded when a step from it, to the next point or from the previous one, reaches into the region. Co-YBCO gives the background alone at 5.019°, after a region ending at 5.00°.
  • Checked and not a cause: S_L and D_L of Co-YBCO (D_L = 0) are read as CrysFML reads them. The other readings give \(\chi^2\) of 95 and 467.

Result, Ycal relative to FullProf beyond its print (rms, max):

| Run | rms | max | |---|---|---| | Y2O3 | 7·\(10^{-5}\) | 1.4·\(10^{-3}\) | | Co-YBCO | 4.8·\(10^{-4}\) | 3.9·\(10^{-3}\) | | MgAl2O4 | 2·\(10^{-5}\) | 1.9·\(10^{-4}\) | | rutile + anatase | 9·\(10^{-5}\) | 1.1·\(10^{-3}\) | | Egyptian make-up | 1.6·\(10^{-5}\) | 4.8·\(10^{-4}\) |

In all five Backg agrees to 5·\(10^{-7}\) and every reflection position to 2.4·\(10^{-4}\)°. The five runs are in fullprof_profiles as exact, and their pass-1 .prf files are left out with the reason. The test now measures deviations beyond FullProf's print, which Y2O3's one decimal on counts near 18 requires. The earlier files still pass.

\(\chi^2\) of L033, against the runs. The core's \(\chi^2\) over FullProf's N − P + C, against FullProf's own in CODE_calc.out, is now checked in fullprof_profiles (tolerance print + 0.15%):

| Run | core | FullProf | |---|---|---| | Y2O3 | 1.1986 | 1.199 | | Co-YBCO | 1.5484 | 1.548 | | MgAl2O4 | 1.3813 | 1.381 | | rutile + anatase | 1.3737 | 1.372 | | Egyptian make-up | 4.1442 | 4.145 |

The rutile + anatase gap is the window edges.

  • Of the gaps in L033's table, only Y2O3's (1.31 against 1.20) was the core's: the asymmetry.
  • Cocus-asym, MgAl2O4s and rutana differed because their headers belong to earlier states.
  • Egypt in L033 was another file (Egypt_qa_database.pcr). Its Rietveld run agrees.
  • Cocus-firstpeak now gives 12.2 (16.6 before the asymmetry fix) against its header's 3.63. It has no run, so it stays unexplained.
core/tests/fullprof_reference_sf.rs · core/tests/fullprof_profiles.rs · core/src/{powder,profile}.rs · manuals/fullprof/runs/*/CODE{,_calc}.{pcr,out,hkl,prf} · FullProf manual pp. 54, 75 · enhantica/edi@855a426
L0342026-10-09work

FullProf run on the Mac for the reference refinements; 0.6.0 reviewed and merged; what the runs show

FullProf (macOS distribution of February 2025, fp2k, x86_64) was installed on the Mac with Josu's authorisation and run on the final .pcr of the reference tutorials (L020): Y2O3, Co-doped YBCO, MgAl2O4 with MgO, rutile with anatase, and the five-phase Egyptian make-up. manuals/fullprof/runs/run_fullprof.py redoes it. Each run has two passes: the refinement as the tutorial runs it, then a calculation only (NCY = 0) from the refined file, whose outputs (CODE_calc.prf, .hkl with Freal and Fimag, .out) belong exactly to the parameters in CODE_calc.pcr. Changes to the tutorials' files are output-only: Hkl = −3, Prf = 3, Rpa = 0 (this FullProf build stops with "syntax error in format" while writing a CIF); a resolution file named by a path on the author's Windows machine is pointed at the local copy. MgAl2O4b_Rietveld.pcr is left out: on its data it gives Rwp 98 % and \(\chi^2\) 590. The distribution also held FullProf-Restraints-RigidBodies.pdf and bond_str.pdf, now in manuals/fullprof/docs/.

0.6.0 (L033) was reviewed before it was merged:

  • All tests pass (51 with the fix below), clippy reports no warnings; fcj_table.rs and the profile index were regenerated from their sources: the same.
  • The new .out files of the runs broke fullprof_spacegroups, which expected exactly 13 files. Relaxed to "at least 13", it found that it looked FullProf's symbols up in the International Tables' order: for "F d -3 m" and "I 41/a m d" that is origin choice 1, where FullProf means choice 2. The test now reads the symbols with SpaceGroup::from_fullprof_symbol (L030) and passes on all 23 files, 35 blocks, 14 groups (F d -3 m, F m -3 m, I a -3, I 41/a m d, P 4/m m m, P 42/m n m, P 4/m b m, I 2/c, P c m n, P m c n and the original four). fullprof_metric passes on all 23 files as it was.

Run against the calculation-only references, the 0.6.0 pattern agrees for Co-YBCO (Ycal rms 1.4·\(10^{-3}\)) and the Egyptian make-up (8.9·\(10^{-4}\)), and differs for Y2O3 (rms 1.7·\(10^{-2}\), up to 0.48 near 20.7°, the 211 region), rutile with anatase (rms 2.2·\(10^{-2}\), up to 0.18 near 26.9°) and MgAl2O4 (rms 3.5·\(10^{-3}\), up to 0.033 near 53°). These are the \(\chi^2\) gaps of L033, now located point by point. They are not yet in the test, which would fail; finding their cause is the next step.

manuals/fullprof/runs/ · core/tests/fullprof_spacegroups.rs · core/tests/fullprof_profiles.rs
L0332026-10-09work

Phase 2, steps 1 and 2: the .pcr and data readers, and the calculated pattern as FullProf computes it

The core reads FullProf's inputs and calculates the powder pattern from them (core 0.6.0). New modules:

  • pcr: single-pattern .pcr files (COMM format). It reads the job, the flags by the names of their comment lines, wavelengths, background, excluded regions, the refined globals, and phases with atoms (Jbt 0, 2, 4, 6, with betas and symmetry modes). It also reads profile, asymmetry, preferred orientation and absorption, and resolution files. Codewords are kept as written. All 151 tutorial .pcr files are read, and 214 of the 282 files of manuals/fullprof/ in all. The others are multi-pattern (12), time-of-flight (8), Nsc ≠ 0 (5), and magnetic or superspace formats.
  • data: Ins 0, 1, 3, 5, 6, 8 and 10, ported from the Fortran the manual prints (pp. 133–137), with Fortran's blank-ignoring fixed fields kept. Ins 12 (GSAS) follows GSAS-II's G2pwd_fxye.py (commit c13e730). \(\sigma\) and variable steps are kept.
  • profile: Gaussian, Lorentzian, pseudo-Voigt and TCH (manual eqs. 3.8–3.17, pp. 47–48), and Bérar–Baldinozzi (eq. 3.29, p. 54). Finger–Cox–Jephcoat is ported from CrysFML's Prof_Val (CFML_PowderProfiles_Finger.f90, commit 8ed8670) with its Gauss–Legendre tables (fcj_table, generated by make_fcj_table.py).
  • powder: the model built from a .pcr (eqs. 3.1–3.2, p. 41), with:
  • positions with Zero, Sycos and Sysin (pp. 56–57);
  • Caglioti widths with the resolution file's terms added;
  • the overall B (p. 58), preferred orientation (eqs. 3.31–3.32) and absorption (eqs. 3.33–3.34);
  • backgrounds: points, polynomial (eq. 3.4), Chebyshev (Nba = −5) and Fourier (eq. 3.4′);
  • several phases with their scales, and excluded regions;
  • agreement factors, and Poisson simulation (no external crate: a SplitMix64 generator, Knuth's method below a mean of 30 and Hörmann's PTRS above).
  • drs calc <file.pcr> [--data f] [--out f] writes 2\(\theta\), Ycal, Backg (and Yobs, \(\sigma\)). drs simulate writes an XYDATA file with Poisson noise.

Refused with a message: size and strain broadening (the next step of phase 2), TLS, Cry ≠ 0, and magnetic phases.

Test fullprof_profiles: of the 85 .prf files of manuals/fullprof/, 9 are rebuilt from their .pcr. Ycal and Backg are compared point by point, as relative deviation |\(\Delta\)|/max(|Y_FP|, 1):

| File | Class | Ycal rms | Ycal max | |---|---|---|---| | PPH3-sls (Rietveld) | exact | 9.7·\(10^{-5}\) | 4.8·\(10^{-3}\) | | PPH3-sls_1 (profile matching, two settings) | exact | 1.2·\(10^{-4}\) and 1.9·\(10^{-4}\) | 5.8·\(10^{-3}\) | | citric-acid-sls_1 (Rietveld) | exact | 1.4·\(10^{-4}\) | 3.9·\(10^{-3}\) | | citric-acid-sls_1 (profile matching, two settings) | exact | 1.2·\(10^{-4}\) and 2.1·\(10^{-4}\) | 7.0·\(10^{-3}\) | | LaMnO3_cub | exact | 3.7·\(10^{-4}\) | 7.0·\(10^{-3}\) | | LBF_lmopr (Le Bail) | lebail | 2.7·\(10^{-3}\) | 6.1·\(10^{-2}\) | | LaMnO3ref | inconsistent | 1.6·\(10^{-2}\) | 9.7·\(10^{-2}\) |

In every file Backg agrees to 5·\(10^{-4}\) or better, and the reflection positions to 5·\(10^{-4}\)°.

  • LBF_lmopr: the weak reflections differ because the .hkl holds the intensities after the last cycle, while the .prf was drawn with those of the cycle before.
  • LaMnO3ref: its profile behaves as if Zero were about 0.0207, where its .pcr, .out and .prf header give 0.01671, and it differs at high angle. The .out reflection list agrees with the core: Icalc within the test's tolerance and FWHM within 3.4·\(10^{-4}\).
  • LaMnO3_cub: positions are off by up to 4.4·\(10^{-4}\)°, growing with k. FullProf's b is about 1.5·\(10^{-5}\) Å below its .pcr value, as in L028.

The other 76 files are left out:

  • 71 of the LaMnO3 D20 sequence (la0.prf, la0120.prf … la0190.prf), which carry a magnetic phase (Jbt = 1). The 70 per-temperature files still serve as data: their Yobs are read as FullProf read them.
  • 5 from simulated annealing: four list integrated intensities with no profile, and one (Lmo_san_prof.prf) holds the annealing cluster profile.

The observed data of 80 files, 301429 points, agree with the readers (Ins 3, 6 and 10). The index and its reasons are written by make_fullprof_profiles.py.

Found in the files, not in the manual:

  • The .prf Backg column is the background less a quarter of its minimum (LaMnO3_cub prints 11.789 for 15.719).
  • FullProf divides the whole .prf by a power of ten for large counts (citric acid: Yobs 7390 for 73902.4).
  • FullProf's last point carries the background only (PPH3: 4890 where the peaks add 110.25).
  • In excluded regions Ycal is the background. A point is excluded when x \(\le\) b and x + step > a.
  • λ2 adds peaks only when Ratio ≠ 0 and λ2 ≠ λ1 (Cocus-asym, a neutron file with Ratio = 1).
  • X-ray Lp is (1 + Cthm·cos\(^{2}\)2\(\theta\))/(2 sin\(^{2}\)\(\theta\) cos \(\theta\)), twice eq. 3.26 with K = ½. PPH3 (Cthm = 0) agrees exactly. Y2O3 (Cthm = 0.78) gives \(\chi^2\) 1.31 with this formula and 14.4 with the manual's, against FullProf's 1.20.
  • Profile matching takes the CODFILn.hkl intensities without the scale factor.
  • A phase with S_L = D_L = 0 takes the resolution file's ASYM values (rutana: \(\chi^2\) 1.50, against 1.68 before).
  • FCJ applies at all angles below AsyLim, above 90° too.
  • The .out Icalc is the intensity within the ±Wdt·H window, I·(1 − η(1 − (2/π)·atan(2·Wdt))).

Checked only through \(\chi^2\), against the \(\chi^2\) in each tutorial .pcr header (FullProf / core):

| File | FullProf | core | |---|---|---| | Y2O3 | 1.20 | 1.31 | | Cocus-asym | 1.48 | 1.56 | | Cocus-excl | 3.05 | 3.07 | | MgAl2O4b | 1.54 | 1.86 | | MgAl2O4s | 1.52 | 1.45 | | rutana | 1.68 | 1.50 | | YVO3 | 2.33 | 2.50 | | ybfluo | 1.72 | 1.77 | | CaTiO3 | 1.256 | 1.256 | | LaMnO3_cub | 2.63 | 2.66 | | PPH3 | 44.0 | 44.1 |

These cover the Bérar–Baldinozzi asymmetry, Sycos/Sysin, the polynomial background, the Ins 0 and 1 readers and the resolution file. The header \(\chi^2\) is not a firm reference: citric acid reproduces its .prf to \(10^{-4}\), yet its \(\chi^2\) is 57.1 against the header's 68.6. Not explained:

  • Egypt (7.80 against 4.42) and Sr-oxalate (3.20 against 2.09–2.15).
  • Cocus-firstpeak (16.6 against 3.63), whose data and model are those of Cocus-excl (which agrees) apart from asymmetry, Sycos, Zero and widths. Its \(\chi^2\) stays at about 14 above AsyLim, so the asymmetry is not the cause. Sycos variants did not explain it.

Not covered:

  • The material has no .xrdml, .raw or .brml file, so those readers are not written.
  • The GSAS files are time-of-flight data in FP_tests, so the Ins 12 reader is untested against FullProf. No tutorial uses Ins 5 or 8: those readers are checked only by their port from the manual's Fortran.
  • drs simulate is checked by the mean and variance of its Poisson generator (unit test), not against FullProf.
core/src/{pcr,data,profile,powder,fcj_table}.rs · core/tests/fullprof_profiles.rs · core/tests/data/make_fullprof_profiles.py, make_fcj_table.py · FullProf manual pp. 41–58, 65–137 · CrysFML@8ed8670 CFML_PowderProfiles_Finger.f90 · GSAS-II@c13e730 G2pwd_fxye.py
L0322026-10-09decision

Phase 2 written out: the calculated pattern first, then the engine, each step against FullProf

Phase 2 is detailed on the roadmap from the version-1 features of the proposal (L019, L020). It takes everything that computes and refines, in the core and on the command line, in six steps:

  • the inputs: FullProf .pcr import and the data formats of P02;
  • the calculated pattern as FullProf computes it (positions, profiles, asymmetry, resolution file, Lorentz–polarisation, background, several phases, simulation);
  • the sample terms (isotropic size and strain, preferred orientation, absorption);
  • the least-squares engine (named parameters, constraints, limits, restraints, Levenberg–Marquardt with SVD, cycle events, statistics, covariance);
  • Le Bail, quantitative analysis, automatic background points;
  • our own control file and the outputs (CIF, listings, distances, angles, bond valences).

The calculated pattern comes before the engine because it can be checked exactly: the 85 .prf files in manuals/fullprof/ hold FullProf's calculated profile and background point by point. Phase 2 is done when the four reference refinements are reproduced from the command line within FullProf's esd's. Josu authorised installing FullProf on the Mac to run them, which also closes phase 1 (L031). Set aside: starting with the engine on simulated data only (no outside check of the model it fits); the app's features, which wait for phase 3.

conversation of 9 October 2026 · Roadmap · data/proposal.yml
L0312026-10-09work

0.5.0 reviewed and merged: the three structure-factor references regenerated on the Mac

The structure-factor work of L030 was reviewed on the Mac before it was merged into main:

  • All 41 tests pass, clippy reports no warnings, and build.py passes its list guard.
  • The scattering tables (make_scattering_tables.py, from CrysFML's CFML_Scattering_Chemical_Tables.f90 at commit 8ed8670) and the FullProf F\(^{2}\) reference (make_fullprof_structure_factors.py) were regenerated: byte for byte the same.
  • The gemmi reference, regenerated with gemmi 0.7.5 on the Mac (Intel), differs from the cloud's in the last digits only: at most 5.6·\(10^{-7}\) relative, from gemmi's single-precision arithmetic on another processor. The test gemmi_structure_factors passes with either file, with the core within 5·\(10^{-8}\) of gemmi when both use the same tables.
  • The ICSD CIF the test cif_structures reads (galena) is one of the tutorial files in manuals/fullprof/; no licensed data was added.

Phase 1 stays open on one point, as L030 says: FullProf's own structure factors for the four reference structures are not in the material. They come from running FullProf with Hkl = −3 on the four tutorial .pcr files.

core/tests/data/make_*.py · CrysFML@8ed8670 · gemmi 0.7.5
L0302026-10-09work

Phase 1: structure factors with FullProf's conventions, checked against 12026 F\(^{2}\) of FullProf and against gemmi

The last item of phase 1, in core 0.5.0.

What the core computes:

  • F(h) = Σ_j Occ_j f_j(s) exp(−B_j \(\mathrm{s}^{2}\)) Σ_g T_jg exp(2πi h·(R_g x_j + t_g)), with s = sinθ/λ. The inner sum runs over all the operators of the group, centring and inversion included. T_jg is the anisotropic factor of the image, with betas R β Rᵀ.
  • Occ is FullProf's. The manual (p. 102) defines it as "chemical occupancy × site multiplicity (can be normalised to the multiplicity of the general position)", so a full site has Occ = m/M, and the sum over all M operators then counts each position once. Conversions to and from chemical occupancy use the site module.
  • X-ray form factors, with f′ and f″, come from CrysFML's tables (FullProf's library, LGPL). The anomalous terms are taken at the anode among Cr, Fe, Cu, Mo and Ag whose Kα1 is nearest to λ; that is CrysFML's rule (Create_Table_fabc_Xray), and FullProf prints the Mo values at λ = 1.001 Å.
  • Neutron scattering lengths are in \(10^{-12}\) cm.
  • The tables are generated by make_scattering_tables.py from CFML_Scattering_Chemical_Tables.f90 (commit 8ed8670): 214 form factors, ions included; f′ and f″ for 98 elements; 108 lengths.
  • A small CIF reader and drs sf (CIF, or symbol + cell + --atom; --xray λ|anode, --neutron) list h k l, mult, d, |F| and F\(^{2}\).
  • SpaceGroup::from_fullprof_symbol reads a symbol as FullProf does. "F d -3 m" and "I 41/a m d" are then origin choice 2, where the International Tables' order, followed so far, put choice 1 first.

Found about FullProf:

  • The F\(^{2}\) it prints (the .hkl column F2 written with Hkl=2, and StrFactor^2 in the .out) is |F|² of the full sum. It includes f′ and f″ and leaves out the overall B.
  • The manual's eq. 3.44 (p. 57) sums over the reduced set and applies NLAT = (LAT·ICEN)² (p. 58). The full sum contains both.
  • The reduced-set |F|² alone does not reproduce the printed values: for PPH3 (0 1 1) it gives 200.6 against 302.597.

Tests (all pass; the core has no external dependency):

  • fullprof_structure_factors: every F\(^{2}\) FullProf printed in the material, 12026 reflections. These are PPH3 (P 21/c, 34 atoms, X-rays, 7538) and citric acid (P 21/c, 21 atoms, 4074), from their .out and .hkl files, and LaMnO3 (P n m a, neutrons, atoms on 4c, 4b, 8d, 4c with Occ 0.5, 0.5, 1, 0.5; 414), from Lmo_san.out.
  • The reference file is written by make_fullprof_structure_factors.py with file and line. The atoms come from rigid bodies and symmetry modes and are printed to only 4–5 decimals. F\(^{2}\) is therefore compared within the uncertainty that this printing carries through F, plus the print of F\(^{2}\) and single precision. The largest differences reach 0.05, 0.25 and 0.37 of that bound.
  • Controls show the test can tell the conventions apart. Without f′ and f″, 4589 of 7538 (PPH3) and 943 of 4074 (citric acid) reflections fall outside the bound. With chemical occupancies in place of m/M, 413 of 414 do (LaMnO3).
  • The same test checks that the core's coefficients equal FullProf's printed ones to the fourth decimal, and that f′ and f″ equal them exactly.
  • gemmi_structure_factors: the four reference structures of L020 as their tutorial .pcr files give them: Y2O3, MgAl2O4, Co-doped YBCO, and rutile, anatase, laurionite, phosgenite, cerussite, galena and gypsum from the two QPA tutorials. That is 10 phases and 1435 reflections with d \(\ge 0.9\) Å, X-rays and neutrons.
  • gemmi 0.7.5 (pip, test-data step only, make_gemmi_reference.py) builds the groups from FullProf's Hall symbols and handles special positions itself. With gemmi's own coefficients the core agrees to 6·\(10^{-8}\) (X-rays; gemmi stores single precision) and 4·\(10^{-10}\) (neutrons) of Σ Occ·M·|f|. The unique reflections and the operators are the same.
  • Tables compared entry by entry: of 15 X-ray species and 14 neutron lengths, the only difference is O\(^{2-}\). CrysFML's entry is a two-Gaussian fit (a = 4.758, 3.637; b = 7.831, 30.05; c = 1.594), gemmi's a four-Gaussian one. It changes |F| of rutile and anatase by up to 7·\(10^{-4}\) of that scale. Where each entry comes from was not checked.
  • structure_factor_properties, in all 530 settings (182320 reflections), with an atom on the general position and one on the most special position, anisotropic:
  • F = 0 for the 56205 absent reflections;
  • F(−h) = F(h)* without anomalous terms;
  • Bijvoet differences in each of the 277 non-centrosymmetric settings and in none of the centrosymmetric ones;
  • real F with −1 at the origin;
  • equal |F| for equivalent reflections;
  • the same F as the atoms written out in P 1, which checks the Occ = m/M convention.
  • cif_structures: FullProf's CIFs for PPH3 and LaMnO3 give the structures of its .out files, and an ICSD CIF gives F m -3 m from its operators.

Not in the material: FullProf's own structure factors for the four reference structures. No .hkl or .out file of their tutorials prints F. Josu can produce them on the Mac by setting Hkl = −3 in each tutorial .pcr (manual p. 11: real and imaginary F of the conventional cell) and running FullProf. Until then the phase's "Done when" is met for reflections, while the structure factors of the four rest on gemmi and on FullProf's F\(^{2}\) for three other structures.

core/src/structure_factor.rs · core/src/scattering.rs · core/src/cif.rs · core/tests/fullprof_structure_factors.rs · core/tests/gemmi_structure_factors.rs · core/tests/structure_factor_properties.rs · core/tests/cif_structures.rs · core/tests/data/make_scattering_tables.py · core/tests/data/make_fullprof_structure_factors.py · core/tests/data/make_gemmi_reference.py · FullProf manual pp. 11, 57–58, 91, 102 · CrysFML@8ed8670 CFML_Scattering_Chemical_Tables.f90, CFML_Structure_Factors.f90
L0292026-10-09work

0.4.0 reviewed and merged: reflections checked in the cubic reference structures as well

The reflection work of L028 was reviewed on the Mac before it was merged into main:

  • All 29 tests pass, clippy reports no warnings, and build.py passes its list guard (L027).
  • The FullProf reference fullprof_reflections.txt was regenerated by its script: byte for byte the same.
  • The FullProf lists cover only the Laue classes mmm and 2/m, so the two cubic reference structures were checked by hand. MgAl2O4 (F d -3 m, m-3m) gives 111, 220, 311, 222, 400, 331, 422, 333 and 511 apart, 440, with multiplicities 8, 12, 24, 8, 6, 24, 24, 8, 24, 12; 222 stays in, as it is forbidden only by the special positions. Y2O3 (I a -3, m-3) gives 231 and 321 as two reflections of multiplicity 24, and 240 and 420 as two of 12, as the Laue class m-3 requires.
  • The one pair FullProf lists against its own printed d (PPH3-sls_1.out, line 1763) is still checked for indices, multiplicity and d; only its order is let pass, and a second such pair fails the test.
core/src/reflections.rs · core/tests/fullprof_reflections.rs · drs hkl
L0282026-10-09work

Reflections as FullProf lists them, checked against 14337 of FullProf's reflections and in all 530 settings

Phase 1, item 4. The module reflections gives, for a phase between two d-spacings (or a 2\(\theta\) range for a wavelength), its unique reflections with multiplicity, d and 2\(\theta\), done the way FullProf does it in CrysFML's CFML_Reflections_Utilities.f90 (read at commit 8ed8670; LGPL-3):

  • Absences come from the operators themselves, as in Hkl_Absent: h is absent when an operator {R|t} has h·R = h and h·t not an integer, which covers centring, screw axes and glide planes. Reflections forbidden only by atoms on special positions stay in the list (222 in F d -3 m, for example).
  • One reflection per set of equivalents, as in Hkl_Uni: the member that lies inside the loop's index box and for which Asu_Hkl(h) = h. Both depend on the space-group number and, for monoclinic settings, on the unique axis.
  • Multiplicity as Hkl_Mult: the distinct h·R and −h·R. Lists are sorted by decreasing d, then h, k, l.

The command line is drs hkl <symbol> <cell> --dmin d or --lambda λ --2theta min max. No external dependency was added.

Tests, all passing:

  • fullprof_reflections: the reflection lists of the FullProf .out files (9 phases, 14337 reflections, in P n m a, P 21/c, P 21/a and P 2/m), extracted with file and line by make_fullprof_reflections.py. Row for row the core gives the same h k l (FullProf's own choice of representative), the same multiplicity, d to the six printed decimals and 2\(\theta\) to the three printed, and no reflection more or fewer. 24 pairs come in the other order where FullProf's d values agree to its precision.
  • reflection_properties, over all 530 settings. For an atom on a general position, a structure factor summed directly over the operators vanishes for all 197980 reflections the core calls absent and for none of the 506920 it allows (|h_i| \(\le 5\)). The multiplicities add up to the number of allowed reflections counted one by one in a sphere. FullProf's rule picks exactly one representative per set in every standard setting.
  • Deliberate errors are caught. A wrong absence condition fails 6064 property checks, and a wrong index box gives 6575 disagreements with FullProf.

Found on the way:

  • The cell. FullProf computes the printed list in the last cycle, with the cell before that cycle's shifts, so the reference takes value − shift from the last cell block with a non-zero shift. LBF_lmopr.out gives d(200) = 2.874002 = (5.748007 − 0.000003)/2, and LaMnO3_cub.out gives d(020) = (7.694143 − 0.000014)/2. With the printed final cell every d was off by about 5·\(10^{-7}\) relative.
  • CrysFML's −3m1 region alone admits two members of some sets, e.g. (0 4 1) and (4 0 −1). FullProf's choice also needs the index box of its loop (l \(\ge 0\) there), which the port now applies.
  • Monoclinic settings named "-c2" and the like take their unique axis after the '-', as CrysFML reads it; without that, twelve centred settings overcounted their multiplicities.
  • In 42 non-standard settings, the monoclinic settings with unique axis a and the R groups on rhombohedral axes, FullProf's rule does not single out one member per set. CrysFML's own code calls the a-axis rule "Provisional (to be tested)". There the core uses a stated fallback (the largest h, k, l) and marks those reflections.
  • One pair in PPH3-sls_1.out (Prof_Match): (7 1 2), d = 1.489789, is listed before (1 10 0), d = 1.489798, against FullProf's own printed d and η, and with the starting cell as well. This could not be explained from the material; the test lists it and fails if a second such pair appears.

Not checked: FullProf's choice in Laue classes other than mmm and 2/m, which have no FullProf list in the material. There the port of CrysFML's rules and the property tests stand in.

core/src/reflections.rs · core/tests/fullprof_reflections.rs · core/tests/reflection_properties.rs · core/tests/data/make_fullprof_reflections.py · CrysFML@8ed8670 CFML_Reflections_Utilities.f90
L0272026-10-09work

0.3.0 reviewed and merged; lists after a lead-in line now render as lists on the site

The site work of L026 was reviewed on the Mac before it was merged into main:

  • All 25 tests pass, and clippy reports no warnings.
  • The four generated files were regenerated from the cited sources and are byte for byte the same: the Wyckoff table (spglib's Wyckoff.csv at commit e606dfa), FullProf's symbols (CrysFML's CFML_Symmetry_Tables.f90 at commit 8ed8670), the spglib Wyckoff reference (spglib 2.7.0) and the FullProf atom data.
  • The reference structures come out as in the International Tables: spinel Mg 8a (-43m), Al 16d (.-3m), O 32e (.3m); Y2O3 Y1 8b (.-3., with β11 = β22 = β33 and β12 = β13 = β23), Y2 24d (2.., with β12 = β13 = 0), O 48e.

Found in the review: in thirteen log entries (L011–L014, L019–L022, L024–L026), a lead-in line followed by bullets was shown on the site as one paragraph with the bullets run together, because para() in build.py recognised a list only when the paragraph began with a bullet. It now ends the paragraph at the first bullet. The guard against bullets folded into one line by YAML (L020's fix) missed L026, whose folded bullet began with a lower-case word; it now catches any "- " after punctuation, and L026 is written in the literal style.

ecosystem/tools/build.py · ecosystem/data/log.yml · core/tests/data/make_*.py
L0262026-10-09work

Sites: Wyckoff positions and special-position constraints, checked against spglib and FullProf

Phase 1, item 3. The module site takes a position in a space group and gives:

  • the site-symmetry operators (exact, with their lattice translation), the site point group, the multiplicity and the orbit in the cell;
  • the Wyckoff letter and oriented site-symmetry symbol, for any of the 530 settings;
  • the constraints the site puts on x, y, z, solved from (R − I)·x = −t, and on the betas, from R·β·Rᵀ = β.

Constraints are exact linear relations over free parameters, value = c + Σ a·p, with the earliest variables free as FullProf picks them (x, 1/4, z; x, 2x, z; β22 = β11, β12 = β11/2). That is the form the refinement engine will use. The command line is drs site <symbol> <x> <y> <z>. No external dependency was added.

Two tables come from outside, as names only, with their generators kept in core/tests/data:

  • the Wyckoff positions of the 530 settings (letter, multiplicity, site symbol, coordinates), from spglib's database/Wyckoff.csv (GitHub, commit e606dfa);
  • FullProf's space-group symbols with their Hall symbols, from CrysFML's Spgr_Info table (commit 8ed8670 of the copy on GitHub, LGPL-3). With it the core reads the older symbols of .pcr files, such as "B m a b" and "F m 3 m". A group given by any Hall symbol is matched to its table setting by its operators.

Tests, all passing:

  • spglib_wyckoff: every Wyckoff position of the standard setting of all 230 types (1731 positions, at two sets of free parameters: 3462). spglib's get_symmetry_dataset analysed a structure with an atom on the position and a second species on a general position (reference written in the test-data step by make_wyckoff_reference.py). The core finds the same multiplicity, letter and site symbol, also from four other images of each position. The site point group matches spglib's symbol. Multiplicity × site order = group order (the property test). The number of free coordinates is that of the Wyckoff triplet. The number of free betas equals the dimension of the invariant tensors, counted separately by the character formula.
  • u_ij_follow_beta_relations: in all 530 settings no rotation mixes axes of different length, so the U_ij of a CIF obey the same relations as the betas.
  • fullprof_sites: the atoms of the FullProf files, extracted with file and line by make_fullprof_sites.py. The core gives FullProf's multiplicity for all 96 atoms of the 13 .out files. For the 69 atoms refined with anisotropic betas in the .pcr files (P n m a, F m m m, P 21/a, I 21 3, B m a b, F m 3 m), the refined betas satisfy the core's relations to the printed decimals, and betas fixed by symmetry carry code 0. In the 16 cases where a tied beta and its partner both carry codes, the codes encode the same relation (same parameter, multipliers in the ratio).
  • crysfml_symbols: 583 of CrysFML's 612 symbols are table settings, with the right number and the same setting where the two tables share a name, and the lookup finds each from its FullProf name. The other 29 are cells outside the 530 (A 1, I -1, F 2/m, C 4 2 21, F 4/m m m, ":1" origins of groups without origin choices), listed by the test.
  • Deliberate errors are caught. A wrong sign in the beta equations fails all 3462 positions and 550 FullProf checks. A loose tolerance in the Wyckoff search fails 398 positions.

Found on the way:

  • spglib keeps a structure in its input setting only for the standard settings; in the other 300 settings it moves to the standard one, so Wyckoff letters were checked in the 230 standard settings.
  • spglib reduces triclinic cells, so the triclinic test cells are already reduced.
  • CrysFML writes the Hall symbol of P 3 2 1 as "P 3 2""" (Fortran's doubled quote); the first extraction cut it to "P 3 2" (which is P 3 1 2), and crysfml_symbols caught it.

Not checked: letters in non-standard settings against an outside analysis (the table's letters are used as given); the International Tables directly.

core/src/site.rs · core/src/wyckoff_table.rs · core/src/sg_fullprof.rs · core/tests/spglib_wyckoff.rs · core/tests/fullprof_sites.rs · spglib 2.7.0, spglib@e606dfa (Wyckoff.csv), CrysFML@8ed8670
L0252026-10-09work

0.2.0 reviewed and merged: the spglib reference regenerated independently; command-line fixes as 0.2.1

The space-group work of L024 was reviewed on the Mac before it was merged into main:

  • All 16 tests pass (13 unit tests, fullprof_metric, fullprof_spacegroups, spglib_530).
  • The reference core/tests/data/spglib_530.txt was regenerated with spglib 2.7.0 installed separately, by the session's own script: byte for byte the same 7920 lines. The reference is spglib's, not the core's.
  • The space groups of the four reference structures (L020) give the expected numbers of operations: I a -3 48, F d -3 m 192, P m m m 8, P 4/m m m 16, I 41/a m d 32, P 42/m n m 16.

Fixed and released as 0.2.1: --help inside drs sg and drs cell, a panic when the output goes to a pipe that closes early, and the eight clippy warnings (style only).

core/src/main.rs · core/src/symop.rs · core/src/spacegroup.rs · core/src/sgtable.rs · spglib 2.7.0
L0242026-10-09work

Space groups from Hall symbols: all 530 settings agree with spglib and with FullProf

Phase 1, item 2 (Q2, L022). Three modules in the core:

  • symop: general symmetry operators {R|t}, R an integer matrix and t exact in twelfths; product, kind of rotation, reading and writing "x,y,z" triplets.
  • spacegroup: a parser of Hall symbols (lattice symbol, up to four matrix symbols with Hall's default axes, change of origin). It builds the generators, closes the group, and derives the centring vectors, the inversion (and whether it is at the origin), the point group, the Laue class and the crystal system.
  • sgtable: the 530 settings (Hall number, number, choice, Hall and Hermann–Mauguin symbols), taken from spglib 2.7.0's table of settings by core/tests/data/make_sgtable.py.

Only names come from that table; the operators are generated by the core. The command line is drs sg <symbol>, with a number, a Hermann–Mauguin symbol (":choice" for a setting) or a Hall symbol, and drs sg --list. No external dependency was added. Tests:

  • spglib_530: for every one of the 530 settings, the generated set of operators is spglib's stored set (spglib's operator table, separate from its Hall symbols; 7388 operators compared exactly), and the point group, crystal system, centring and centrosymmetry agree. No disagreement. spglib was installed with pip only to write the reference file core/tests/data/spglib_530.txt.
  • fullprof_spacegroups: all 13 FullProf .out files (P n m a, P 21/c, P 21/a, P 2/m). FullProf's symbol is found in the table with FullProf's Hall symbol, number and setting choice. The crystal system, Laue class, point group, multiplicity and centrosymmetry agree, and FullProf's operators are the core's. No disagreement.
  • A deliberate error in one rotation matrix (the two-fold along y) is caught by both tests. Found on the way:
  • The first run agreed on 525 of 530. The five rhombohedral-axes settings ("P 3* 2", R32 to R-3c) need the two-fold after the three-fold along a+b+c to lie along a−b; the parser now does so.
  • spglib names every setting of a monoclinic type by the type's symbol ("P 2_1/c" for all nine settings of no. 14). The short symbol of each setting is therefore its full symbol with the 1s dropped, the International Tables' monoclinic rule, so that FullProf's "P 21/a" finds -P 2yab.
  • One unit-test value written from memory (18 settings of no. 14) was wrong; spglib's table has 9. Not checked: the International Tables, Vol. A, directly (no copy in the repository); FullProf covers only four groups.
core/src/spacegroup.rs · core/src/symop.rs · core/src/sgtable.rs · core/tests/spglib_530.rs · core/tests/fullprof_spacegroups.rs · spglib 2.7.0
L0232026-10-09work

Phase 1 opens: the Rust core, and the unit cell checked against all thirteen FullProf output files

The crate drs in core/: a library for the app and the drs command line, no external dependencies. Its first module is the unit cell: direct and reciprocal parameters, the metric tensors G and G*, the volume, d(hkl) and 2\(\theta\), FullProf's Cartesian frame (x along a, y in the ab-plane) and the Busing–Levy B matrix. Conventions are in core/README.md.

The named test fullprof_metric reads the "Metric information" block of every .out file in manuals/fullprof/ (13 files: LaMnO3 orthorhombic, and citric acid and PPH3 in P2₁/c, monoclinic), takes the cell FullProf printed, recomputes the 64 other numbers of the block and compares each with FullProf's: no disagreement. The tolerance is half a unit of the last printed digit plus 5·\(10^{-5}\) of the quantity's largest element, which covers starting from the rounded printed cell; on PPH3, the volume from the printed cell is 1456.935 ų against FullProf's 1456.927 ų. Seven unit tests add the cubic, hexagonal and triclinic cases, 2\(\theta\) at exactly 90° and 180°, and the refusal of impossible cells.

Found on the way: a test value typed from memory (Si 111 at 28.443°, which is the value for the mean Cu Kα, not Kα1) was replaced by exact geometric cases, and the core now accepts back-scattering at 2\(\theta\) = 180°, which rounding had put just beyond the limiting sphere.

core/src/cell.rs · core/tests/fullprof_metric.rs · manuals/fullprof/**/*.out
L0222026-10-09decision

Q2, Q4, Q5, Q6 decided; phase 0 closes

Josu takes the recommendations made in the conversation for the four design questions still open:

  • Q2, space groups: the 230 types in their 530 settings generated from Hall symbols, not typed in, each tested against spglib and the International Tables, Vol. A. Symmetry stored as general operators (rotation and translation), so that magnetic and superspace groups, "later" in L012, can be added without a rewrite.
  • Q4, the editor: CodeMirror 6 inside the Tauri window, with a language mode for our control file (L020).
  • Q5, the live plot: every least-squares cycle is an event from the core (parameters and shifts, calculated curve, agreement factors); the app redraws on each event, and the events form the refinement history.
  • Q6, the Mac app: document-based, as the difes shell: a project per window or tab, the full menu bar, Settings, Recent items, Undo and Redo of every model change, source list, inspector, ⌘K palette.

With Q1 (L021) and Q3 (L020), every design question has a logged decision, the FullProf inventory and the GSAS-II gap list are on the site, and the proposal's questions are answered: the "done when" of phase 0 is met and phase 0 closes. Phase 1, the crystallography core, is written out on the roadmap. Set aside: typed-in space-group tables (error-prone, and nothing to test them against but themselves); a native editor component (CodeMirror 6 already has completion, folding and diagnostics); drawing only at the end of a refinement.

conversation of 9 October 2026 · Design · Roadmap
L0212026-10-09decision

Q1: the core in Rust, with Python as its scripting interface

Josu decides design question Q1. The core is written in Rust; Python is the interface for scripting, through bindings to the Rust core (PyO3), in version 2 as the proposal has it (P48). No physics is written in Python. The reasons, from the comparison made in the conversation:

  • Tauri's side is Rust, so the core links straight into the Mac app: one signed binary, no interpreter or local server to ship and start (the difes-shell way, which works but is heavier).
  • The same crate builds the command-line program that every feature needs (CLAUDE.md), and its tests run with cargo test in a cloud session as well as on the Mac.
  • Most of the code is written by Claude sessions; Rust's compiler rejects whole classes of errors (types, memory, data races) before any test runs.
  • Speed is that of Fortran or C++ for the inner loop (profile, structure factors, derivatives).
  • Scripting, the strength of GSAS-II (L014), is kept: Python users drive the Rust core through the bindings.

The cost accepted: few crystallographic libraries in Rust. The crystallography is written in phase 1 and checked against independent sources (the FullProf test files in manuals/fullprof/, spglib, the International Tables). Set aside: a Python core with compiled kernels (the GSAS-II and difes route: the friendliest for Josu to read and change, but an interpreter inside the app); a Fortran core (FullProf's language and CrysFML, but awkward to link into Tauri and weak tooling); C++ (no advantage over Rust here, memory errors possible, complex builds).

conversation of 9 October 2026 · Design, Q1 · Proposal, P48
L0202026-10-09decision

The proposal's open questions decided: constant wavelength first, Levenberg–Marquardt, our own control file

Josu answers the six questions the proposal draft (L019) left open, taking the recommendations made in the conversation:

  • TOF (P05): version 2. Version 1 is constant wavelength only (laboratory X-rays, synchrotron, reactor neutrons).
  • Joint X-ray + neutron refinement (P06): version 2, since it needs the multi-pattern machinery of version 2; the version 1 data model allows several patterns from the start.
  • Rigid bodies and TLS (P41): version 2. Distance, angle and linear restraints stay in version 1.
  • The minimiser (P42): Levenberg–Marquardt with SVD against correlations, as GSAS-II. With correlated parameters it is more robust than FullProf's Gauss–Newton with relaxation factors, which need hand tuning.
  • The control file (P47, design question Q3): a new readable text format of our own, written and read by the core, with an importer for FullProf .pcr files so that FullProf users bring their work.
  • The reference refinements that define "version 1 is done": the four tutorials proposed, Y2O3, MgAl2O4, Co-doped YBCO and quantitative phase analysis, each to be reproduced within its esd's from the command line and the app.

Set aside: TOF, joint refinement and rigid bodies in version 1, which keeps version 1 to what a FullProf user does today on constant-wavelength data; Gauss–Newton with relaxation factors; the .pcr itself as the app's file format (fixed-format, numeric codewords, not readable without the manual). Still open: Q1, the language of the core, and with it P48 (Python scripting in version 1 or 2).

conversation of 9 October 2026 · Proposal · data/proposal.yml
L0192026-10-09work

The proposal drafted: 59 features, 38 in version 1

The last item of phase 0, written as a draft for Josu to decide, not as a decision. The app's feature set is drawn from the FullProf inventory (199 entries, L017) and the GSAS-II gap list (36 gaps, L018). There are 59 features in 11 groups; each names its sources (inventory entries, gaps, brief requirements), and build.py stops if a named entry does not exist. Draft tiers: 38 in version 1, 11 in version 2, 10 later; the L012 "later" list stays outside both versions.

  • Version 1: what a FullProf user does today on constant-wavelength data (laboratory X-rays, synchrotron, reactor neutrons), from pattern to CIF, one or several phases, quantitative analysis, Le Bail, constraints, limits, distance/angle/linear restraints. It runs inside the app the brief asks for: one project document with undo, a readable control file with .pcr import, builder, editor, live plot per cycle, background set on the plot.
  • Where GSAS-II does it better, the draft takes the GSAS-II way: named parameters instead of numeric codewords, constraint equations, the s.u. of any expression, accept/reject and a refinement history, vendor formats read directly, publication plots.
  • Version 2: TOF, joint multi-pattern refinement, sequential and parametric refinement, rigid bodies and TLS, anisotropic size and strain, texture beyond one axis, Pawley, refinement diagnostics, Python scripting.
  • Proposed test for "version 1 is done": the command line and the app reproduce four reference refinements from manuals/fullprof/ (Y2O3, MgAl2O4, Co-doped YBCO, the quantitative-analysis tutorial) within their esd's.

Open for Josu: TOF, joint X-ray + neutron and rigid bodies in version 1 or 2; the minimiser (Levenberg–Marquardt with SVD, or Gauss–Newton with relaxation); the control-file format (Q3); the reference refinements.

Proposal (draft) · data/proposal.yml
L0182026-10-09finding

GSAS-II gaps re-checked against FullProfAPP: none closed, five narrowed

The 36 gaps of L014 were re-checked against the FullProfAPP manual (version 1.3.3, June 2025; first release 1.0.0, July 2023; Windows and Linux only), which L014 could not reach. Each gap was also checked against the 41 capabilities added to the FullProf inventory in L017. The manual closes none and narrows five; each narrowed row now says what FullProfAPP does and what remains of the gap:

  • Automatic background: FullProfAPP finds points by asymmetric least squares and fits a Chebyshev series to them. What remains: other functions, and the automatic background used as a fixed background.
  • Project file: a .fpapp file saves the interface state and each run goes to a numbered folder. What remains: data, model, constraints, results and covariance in one file.
  • Python scripting: documented modules edit .pcr files and parse .sum, .prf and .mic files. What remains: running refinements from a script, the covariance, constraint equations.
  • Publication plots: plots can be saved from a context menu. What remains: named vector and program formats.
  • Sequential results: exported as XY text. What remains: a multi-block CIF of the series.

None of the L017 additions closes a gap. For example, spherical harmonics in FullProf are for anisotropic strain, not for preferred orientation, and the stored background profiles are not GSAS-II's background peaks. The gap list stays at 36.

GSAS-II · data/study/gsas2.yml · manuals/fullprof/docs/fullprofapp/
L0172026-10-09finding

The FullProf inventory brought up to date: 199 capabilities

The L013 inventory was written from the July 2001 manual and the tutorials and tests. It has now been checked against the material added in L016: the FullProf News pages 2001–2026, the TOF draft (December 2003), the microstructure text, GLOpSAnn, the MolPDF guide and the FullProfAPP manual (version 1.3.3, June 2025). Four readings proposed 53 additions and about 120 amendments. After merging duplicates, 41 capabilities were added and 80 existing entries amended. Each addition cites the News page or document and carries the date and version it appeared ("Since …", 62 entries). There are now 199 entries: data 18, structure 16, profile 28, background 12, size/strain 14, corrections 18, phases 6, Le Bail 6, constraints 15, engine 14, workflow 34, output 18.

Added within the scope, among others:

  • Profile: TOF lookup tables for α, β, \(\sigma\)², \(\gamma\) in the resolution file (December 2005, extended 2009 and 2011), TCH pseudo-Voigt for TOF data (January 2021, version 7.40), TOF_fit_LM to build resolution files (January 2021).
  • Background: a refined sum of up to 10 stored profiles (July 2003); automatic background points (FullProfAPP).
  • Microstructure: anisotropic strain by spherical harmonics (May 2006).
  • Corrections: multi-axial March–Dollase and the anvil-cell model (Nor=2, 3; October 2003), Lobanov–Alte da Veiga cylinder absorption and flat-plate transmission with a PSD (April 2002), a per-reflection correction file (2004).
  • Workflow: sequential multi-pattern refinement (April 2014), CIFs_to_PCR (May 2016), WinPLOTR-2006, and nine FullProfAPP capabilities (2023–2025: batch and multi-pattern grid, full-profile phase search against the COD, protocols saved as JSON, a parallel job scheduler).

Corrected: Res=5 has always been the keyword TOF resolution file (since January 2003), and the interpolated tables were added inside it later. VARY/FIX, DLIM and COMMANDS date from 2002–2005. The Howard asymmetry was removed in January 2001. Ikeda–Carpenter left the not-found list (the Npr=13 entry now has the formula). The texture item was reworded: no spherical-harmonic model, but multi-axial March–Dollase exists.

FullProf · data/study/fullprof.yml · manuals/fullprof/news/, manuals/fullprof/docs/
L0162026-10-09work

The FullProf News pages and four more FullProf documents in manuals/

The 2001 manual stops at FullProf.2k (L010, L013). What came later is recorded year by year on the FullProf News pages, so all of them were downloaded into manuals/fullprof/news/: the index and one page per year from 2001 to 2026 (27 files). The 2003 Word version linked from the index is missing on the ILL site (404); its .htm twin is there. The ILL documentation page also offers PDFs that were not in the material yet; the ones in or near the L012 scope went into manuals/fullprof/docs/: TOF_FullProf.pdf, Microstructural_effects.pdf, GLOpSAnn.pdf and Short_guide_to_MolPDF.pdf, together with the text of the FullProfAPP manual (13 pages from fullprofapp.readthedocs.io, which L014 found blocked from the cloud). Not found online: FullProf-Restraints-RigidBodies.pdf, App_fp2k_man.pdf and Bond_Str.pdf, which the News pages place in the FullProf installation (Help › Manuals). Left out as out of scope: the flipping-ratio note and the magnetic tutorials m1–m10. Next: the FullProf inventory is checked against the News pages and the new documents, and the GSAS-II gaps against the FullProfAPP manual.

manuals/README.md · manuals/fullprof/news/ · manuals/fullprof/docs/ · www2017.ill.eu/sites/fullprof
L0152026-10-09decision

Jana2020 dropped: the study is FullProf and GSAS-II

Josu drops Jana2020 from the study for good. Once magnetic and modulated structures were put out of scope (L012), what sets Jana apart (superspace refinement of modulated, composite and magnetic structures) was out of scope as well, and what remains of its powder side (Rietveld, restraints, TOF) is covered by FullProf and GSAS-II. Its documentation was also out of reach of the cloud session's network. So phase 0 closes on the FullProf inventory (L013) and the GSAS-II gap list (L014). Set aside: a Jana2020 gap list (the empty page and its navigation entry are removed); fetching its documentation into manuals/. Its L008 evidence stays in data/programs.yml with status excluded and this reason.

conversation of 9 October 2026 · data/programs.yml · Roadmap
L0142026-10-09finding

GSAS-II against the FullProf baseline: 36 gaps

GSAS-II, read from its source and tutorials repositories (L011), against the FullProf inventory (L013). Only what GSAS-II has and FullProf lacks within the L012 scope is kept. Each gap was checked in the GSAS-II file and line it cites, and the FullProf material (manual, FP_COMMANDS, tutorials, tests) was searched for it. Each row says what FullProf has instead. Per area: workflow 7, constraints 5, output 5, data 4, engine 4, background 3, corrections 3, profile 2, structure 1, microstructure 1, Le Bail 1. The gaps that matter for the design:

  • Workflow: one project file with a data tree instead of .pcr plus side files; a Python scripting API; accepting or rejecting a refinement; a project notebook; pseudo-variables and parametric fits of sequential results; limits and excluded regions set on the plot.
  • Constraints: equations and new-variable parameters, as against FullProf's shared codewords; histogram wildcards in sequential fits; general restraints as Python expressions; plane and chiral restraints; Mogul import.
  • Engine: Levenberg–Marquardt with SVD instead of damped Gauss–Newton; "parameter impact"; the s.u. of any expression from the full covariance; a normal probability plot.
  • Physics: Pawley (FullProf does Le Bail only); pink-beam profiles; instrument files derived from fundamental parameters; background peaks and automatic background; spherical-harmonic preferred orientation and texture; extinction for CW powder data; Gauss–Lorentz mixing of anisotropic size.

Rejected because FullProf has them too: per-histogram cells, rigid bodies with TLS, parameter limits, named parameters (VARY/EQUAL), Durbin–Watson, the live plot each cycle, waterfall plots, CIF export, symmetry-mode refinement, Fourier maps, parallel runs (fp2k_par). Limits: neither program was run, so GUI behaviour comes from help pages and code. The FullProf side is what manuals/fullprof/ documents; the FullProfAPP manual (fullprofapp.readthedocs.io, blocked here) may close some workflow gaps.

GSAS-II · data/study/gsas2.yml · GSAS-II@c13e730, GSAS-II-tutorials@924e9eb · manuals/fullprof/
L0132026-10-09finding

The FullProf inventory: 158 capabilities for powder structure refinement

FullProf's capabilities within the L012 scope, read from manuals/fullprof/: the July 2001 manual page by page, FP_COMMANDS, tutorials 1–11 and FP_tests (308 files, 130 .pcr). There are 158 entries, each saying what the capability does, where it is set in the .pcr, and its sources. All 435 citations were checked against the page or line they name, and 15 were corrected. Per area: data 16, structure 16, profile 23, background 9, size/strain 13, corrections 12, phases 5, Le Bail 6, constraints 14, engine 12, workflow 17, output 15. Six capabilities are kept for later, one line each: magnetic, superspace, stacking faults (FAULTS), indexing, structure solution, PDF. Out of scope: single crystal, flipping ratios, electron diffraction. Findings:

  • The manual describes FullProf.2k. Twenty-five capabilities are documented only by the tutorials and tests, not in the 2001 manual: symmetry-mode refinement (Jbt=6), Chebyshev and Fourier backgrounds (Nba=-5, -4), COMMANDS keywords, sequential runs, FullProfAPP, the Bérar factor printed in .out files.
  • Not found in the material: Pawley fitting (FullProf does Le Bail), surface-roughness corrections by name, thin-sample transparency (the manual says it "is not implemented"), texture beyond the exponential and March–Dollase functions, Sabine extinction for CW powders, fundamental-parameters peak shapes, hkl-dependent asymmetry, an amorphous phase as a phase, the Ikeda–Carpenter parameters (Npr=13 named only).
  • These "not found" items are what GSAS-II and Jana2020 are read against first.
FullProf · data/study/fullprof.yml · manuals/fullprof/
L0122026-10-09decision

FullProf as the baseline; GSAS-II and Jana2020 read only for what FullProf lacks

Josu revises the method of L009. The three programs are no longer read side by side on one template. Instead:

  • FullProf is the baseline. Its complete feature inventory for crystal-structure refinement from powder data is built from manuals/fullprof/: the manual, FP_COMMANDS, tutorials 1–11 and FP_tests. Each capability gets one entry saying what it does, with its .pcr reference and its source (path, page), not a copy of the .pcr format.
  • GSAS-II and Jana2020 are read only for what FullProf lacks within that scope, in physics and in workflow: project handling, sequential and parametric refinement, constraints and restraints, scripting, GUI. Each gap cites its source.
  • In scope: Rietveld refinement of crystal structures (atoms, occupancies, isotropic and anisotropic displacements, cell); Le Bail and Pawley; peak shapes and instrument description for laboratory X-rays, synchrotron, CW and TOF neutrons; background; size and strain broadening; preferred orientation, absorption, zero and displacement corrections; several phases per pattern, hence quantitative phase analysis; constraints, restraints, rigid bodies.
  • Later, one line each in the inventory saying which program has it and where: magnetic structures, modulated/superspace, stacking faults, indexing, structure solution, 2D image integration, PDF/total scattering.
  • Out: single crystal, 3D electron diffraction. The reason is that one baseline read in depth, plus differences, gives a feature list the app can be designed from directly, without a three-way matrix of overlapping entries. Set aside: the common-template page per program and the three-way feature matrix (roadmap items replaced). The GSAS-II page of L011 is kept as source material and rewritten as a gap list. The L011 findings on magnetic, superspace, PDF, small-angle and 2D images drop to one line each, or out of scope.
conversation of 9 October 2026 · Roadmap
L0112026-10-09finding

GSAS-II read on the common template

GSAS-II read from its own source and documentation: the GitHub repositories GSAS-II (commit c13e730) and GSAS-II-tutorials (commit 924e9eb), which is the source of the tutorial site Josu pointed to. That site itself is blocked by this session's network, so nothing needs downloading into manuals/. Each claim on the page links to the line it comes from. Findings that matter for the design:

  • There is no control file. Everything lives in one .gpx project shown as a data tree, and the same file is driven from Python (GSASIIscriptable). Every parameter has a name p:h:name:id.
  • The powder plot is updated after every least-squares cycle (a hook called inside the minimiser), and Rw is drawn per cycle: the FullProf-style live display of R14 exists here too.
  • Profiles: Finger–Cox–Jephcoat pseudo-Voigt with Thompson–Cox–Hastings widths (CW); back-to-back exponentials ⊗ pseudo-Voigt (TOF, pink beam); fundamental parameters are used only to make instrument files.
  • Background: eight functions plus Debye diffuse terms, background peaks, a measured background histogram, mouse-set fixed points and an automatic baseline (pybaselines).
  • Minimiser: Levenberg–Marquardt on an analytic Hessian summed per histogram, with SVD against correlations; parameter limits; a "parameter impact" tool; s.u. propagated into any expression.
  • Breadth: magnetic structures through Bilbao k-SUBGROUPSMAG, (3+1)D superspace, spherical-harmonic texture, rigid bodies, PDF, small-angle, reflectometry, 2D images, sequential and parametric fits. Stacking faults are simulated with DIFFaX but cannot yet be fitted.
  • Documentation is 62 tutorials and context help, with no printed manual.
GSAS-II · data/study/gsas2.yml · github.com/AdvancedPhotonSource/GSAS-II@c13e730, GSAS-II-tutorials@924e9eb
L0102026-10-09work

The FullProf manuals, tutorials and test files in the repository

Josu added the FullProf documentation to manuals/fullprof/ on main (commits dd656ce, 9fae9ad), outside ecosystem/, so the site Action never publishes it. This session's network blocks the ILL documentation pages. The folder holds the manual, the command list FP_COMMANDS.pdf (12 pages), eleven tutorial folders (1-WinPLOTR to 11-Tutorials_FAULTS, about 1,480 files with 26 PDFs) and FP_tests/ (222 files: .pcr control files with their data, resolution and output files). Found on arrival: FullProf_Manual.pdf and FullProf_Manual_1.pdf are byte-identical (139 pages). The manual is "An introduction to the program FullProf", version July 2001, written for FullProf 2000 with WinPLOTR (p. 1–2; PDF dated 2002). Features added since 2001 (FullProfAPP, symmetry modes, FAULTS) are documented only by the tutorials and the test files, which are read alongside the manual for that reason.

manuals/README.md · manuals/fullprof/FullProf_Manual.pdf, p. 1–2
L0092026-10-09decision

The study limited to three programs: FullProf, GSAS-II, Jana2020

Josu limits the study to FullProf, GSAS-II and Jana2020, and points to their documentation: the FullProf documentation page at the ILL, the GSAS-II tutorials site of the APS and the Jana site in Prague. The reason is the material: these are the three whose manuals, tutorials and examples he has at hand and wants mined for features. Both programs named in the brief (FullProf, GSAS) are in, and so is the superspace and magnetic specialist (Jana). Set aside: TOPAS and MAUD, shortlisted in L008. Their evidence stays in data/programs.yml, with status excluded and this reason. The brief's "five most used" (R05) is met at three by Josu's decision. Requirement texts are not edited.

conversation of 9 October 2026 · data/programs.yml · Background
L0082026-10-08decision

The five programs to study: FullProf, GSAS-II, TOPAS, Jana2020, MAUD

The five most used of the nine on the long list, chosen on measured evidence recorded per program in data/programs.yml and shown as two tables on the Background page. The evidence is citations of each program's reference paper(s) per database, what the neutron and synchrotron facilities name or ship, and what the instrument makers ship. No published survey of program use was found. The rule: rank by the citations of the program's own reference papers, and let facility and vendor evidence decide close cases. A program whose count could not be checked, or cannot be separated from another program's, is not shortlisted on this evidence. FullProf (1993 paper: 20,552 Google Scholar, 11,926 Web of Science; ILL, PSI, ALBA), GSAS-II (GSAS-II 2013: 3,393 and EXPGUI 2001: 7,720 on Semantic Scholar; APS 11-BM, Diamond I11, ALBA) and TOPAS (2018 paper: about 2,263 Google Scholar; the program Diamond I11 mainly supports; Bruker's package) lead clearly. Jana2020 is fourth (Jana2006 paper: 4,071 on Semantic Scholar, about 500 a year by its authors' account), with the caveat that Jana's counts mix single-crystal and powder use. It is kept because it is the only program on the list built around superspace (modulated, composite, magnetic) powder refinement. MAUD is fifth and the closest call (1999 paper: 1,336 Google Scholar, 549 Semantic Scholar; the only program on the list built around combined texture, stress and microstructure analysis). Set aside: RIETAN-FP, whose Google Scholar count (3,149) is higher than MAUD's but belongs to a paper that also introduces VESTA, so its own share cannot be separated; HighScore Plus, shipped by Malvern Panalytical but with no citation count that could be checked (the laboratory-vendor side is in the five through TOPAS); Profex/BGMN (94, Crossref, old snapshot); SmartLab Studio II (PDXL), Rigaku's package, with no peer-reviewed reference paper. Limits: the session's network policy blocked the citation APIs (Crossref, OpenAlex, Semantic Scholar, Europe PMC) and the facility and journal pages, so every number was read from a web-search result quoting the page, with the snapshot date unknown. Databases disagree widely (FullProf: 20,552 Google Scholar, 2,486 Semantic Scholar). ISIS and ESRF pages naming a program were not found. Missing values are marked "not checked". Before the choice is relied on beyond phase 0, it should be re-run from the databases directly, and HighScore Plus and RIETAN-FP are the first to re-examine.

data/programs.yml · Background · Semantic Scholar, Google Scholar author profiles, ILL list of J. Rodríguez-Carvajal's publications, Diamond I11 software FAQ, APS 11-BM user wiki, PSI DMC pages, ALBA MSPD software page, Bruker, Malvern Panalytical and Rigaku product pages (URLs per number in programs.yml)
L0072026-10-08decision

The site published from an empty public repository; the sources stay private

GitHub Pages cannot serve a private repository on the free plan, and the sources (core, app, data) are to stay private. So, as with the difes test ledger, the site is served from a separate public repository, diff-ref-struct-site, which holds nothing but the rendered pages. Unlike the difes twin, it is filled by an Action of the private repository, not by a script on the Mac: every push to main that touches ecosystem/ builds, checks and renders the site with Quarto 1.10.18 and replaces the public repository's content whole. That way a Claude session on the web, which may have no Quarto, still publishes by pushing. The Action writes with a deploy key limited to the public repository (secret SITE_DEPLOY_KEY). Set aside: a paid plan for Pages on the private repository; making the source repository public.

.github/workflows/publish-site.yml · jmigartua.github.io/diff-ref-struct-site
L0062026-10-08work

Repository created, site skeleton renders

Private repository jmigartua/diff-ref-struct created and pushed. The site has its overview, the brief (verbatim and organised), the long list of programs, the open design questions, the phases, the method page, the log and an empty release-notes page; CLAUDE.md at the root states the working method for every session.

github.com/jmigartua/diff-ref-struct
L0052026-10-08decision

Study before code; the core's language not chosen yet

Phase 0 is the study of the programs (R04–R06) and nothing is built before it closes. The language of the core is left open on purpose: Rust (shared with the Tauri side, fast, one binary), Python with compiled kernels (the GSAS-II route and the difes habit) and a Fortran or C++ kernel are the options, and the choice is a design decision taken after the study, with the reasons logged. The long list of programs holds nine candidates; the five to study are chosen with evidence, not from memory.

L0042026-10-08decision

The site machinery inherited from the FTIR ecosystem site

Copied unchanged: the family design kit (theme.css, site.css, the fonts and the reading-column include) and the Unicode-to-LaTeX helpers (mathtokens.py, mathify.py). Adapted: tools/build.py, which here generates the navigation, the organised brief, the program long list, the log, the release notes and core/CHANGELOG.md. Written new: the header, footer, home page and the first pages.

ecosystem/tools/build.py
L0032026-10-08decision

One repository with three folders, not three repositories

The work is to be done in Claude, and a Claude Code session on the web clones one repository. So the project is a single private repository, jmigartua/diff-ref-struct, whose three top folders play the parts of the three difes repositories: core/ (the refinement engine, as difes), app/ (the Tauri Mac app, as difes-shell) and ecosystem/ (data, tools, site and log, as ecosystem_site). The separation of core and app is kept by rule, not by repository: the core never imports the app.

L0022026-10-08finding

How the difes ecosystem is built, the pattern to follow

The difes project lives in three repositories side by side. difes is the physics core in Python: no GUI imports, every feature with a test and a command-line path, a README with conventions, a CHANGELOG generated from the site. difes-shell is a thin Tauri 2 window that starts the core as a local server and draws what it returns; no physics in the shell. ecosystem_site is a Quarto website (theme none, the family design kit) whose navigation, log, release notes and inventory are generated by tools/build.py from data/*.yml on every render, with a plain LOG.md next to it, a manual in two parts (using the program, theoretical basis), a test ledger, an algorithm registry and a download page.

ftir-ecosystem-site, difes, difes-shell (read on 2026-10-08)
L0012026-10-08work

The brief

Josu asks for a macOS app, built with Tauri and behaving as a Mac app, for Rietveld refinement of crystal structures from powder diffraction (laboratory X-rays, synchrotron, neutrons), in the line of FullProf and GSAS. Before any code: an inventory of the five most used refinement programs, what they do and how, the features they share and those unique to each, so that the feature set is the most ambitious one. Asked for by name: the refinement core, a live cycle-by-cycle plot as in FullProf, graphics for the data and for the results, interactive background selection, the complete space-group tables, a GUI that writes the control files, and a text editor made for the control and output files. Everything logged and reported, on a website holding the process, the release notes, the findings, the background and the manuals of the core and the app, worked the way the difes project is. The brief is kept verbatim and organised into nineteen requirements (R01–R19) on the Brief page.

The brief · conversation of 8 October 2026