Skip to content

XinRefinement v1.6.2 — Stop button now actually stops

Latest

Choose a tag to compare

@PolyXRD PolyXRD released this 09 Nov 10:11
· 47 commits to master since this release

XinRefinement v1.6.2 — Stop button now actually stops

Baseline v1.6.1 | Theme: "Stop" taking effect only after the current step finishes is an implementation defect. After triggering one-click run-all, wanting to abort, you press stop but have to wait a long time—not because it computes slowly, but because the cancel flag is only checked between steps, and all the multiple-round fits inside a single step run to completion before returning to the loop top.

Numerics unchanged: golden 50/50 still 3.4074156640940005, CLI/API bit-identical.

Download

File Size SHA256
XinRefinement-1.6.2-setup.exe 75.9 MB 135cb1e87cf47522c25ba903f28cee244d83d8043b760ba13f6f6ee214c93b2f

Release installer (includes registration mechanism, zh/en/ja trilingual install UI). The internal evaluation portable package (XinRefinement-internal-1.6.2-portable.zip, 114.2 MB, sha256 c51f786881c1ed8d965b27b31c9d406e2b51a648f6246a45a84f3f09e2abced1) is for internal acceptance only and is not published with this Release.

The change at a glance

v1.6.1 v1.6.2
Press "Stop" Wait for current step to fully finish (cell step measured 19.8 s) Interrupt at next residual evaluation (~16 ms)
What cancel is recorded as May generate a "step failed" record + false alarm box Pass cancel through, log writes "Refinement stopped by user", no failed record left
Progress bar Stuck at "Executing 'Microstructure'…" Changed to "Stopped"
Already-computed results Discarded Partial results refilled as normal (pattern, Rwp, each results tab)

Why it was slow (root cause)

RefineWorker.run()'s loop is:

for step in todo:
    if self._cancel:        # ← only here checks the stop flag
        break
    result, rec = self._runner.run_step(step, ...)

So the moment stop takes effect = the moment the current step finishes, and a single step internally often does more than one fit:

Step What it internally actually does
Unit cell parameters solve → refine full cell → solve again with new cell (3 fits)
Microstructure PO probe (per phase × per candidate axis each a short fit) → formal solve → re-solve
Peak-shape parameters up to 4 LM (Rwp>10% always triggers restart chain)

More critically: the engine already had fine-grained hooks RefineConfig.cancel_cb + _check_cancel() (placed at the residual-function entry, covering both the in-house LM and scipy backends), but the GUI never wired them up—only one test in the whole repo used them, effectively dead code.

What changed

1. Three residual closures wired up to the cancel hook

  • Main solve: already had it.
  • Unit-cell engine refine_cell_full: independent residual closure, previously had no check at all—the cell step is one of the slowest in the chain, the place that most needed to be fast was instead the slowest.
  • Coordinate engine refine_sites: numerical Jacobian each iteration does O(n_free) "multiphase whole-pattern + linear layer" evaluations, so the xyz step can also stop instantly.

2. Recipe layer no longer records "cancel" as "failure"

Each engine call in the recipe was wrapped in except Exception → StepRecord("failed")—correct for genuine numeric failures (continue to next step), but wrong for user cancel. Now the 7 fallbacks first pass through RefineCancelled; run_all catches it inside the step and returns the completed portion (run-all semantics is "stop at the last completed step", not throwing the exception to the caller).

3. UI finishing

  • The cancel callback is hung into inputs.config, and via config_for's replace() automatically carries to each step's config.
  • After cancel: progress bar "Stopped", log appends "=== Refinement stopped by user (interrupted at residual evaluation) ===", already-computed partial results refilled as normal (pattern + Rwp + each results tab), and no failed-alarm box pops up.

Validation

  • Full regression 532 cases: 532 passed / 0 failed / 0 skipped, EXIT=0 (v1.6.1 was 527, added tests/test_v162_cancel.py 5 cases).
  • golden CLI Rwp 50/50 3.4074156640940005 unchanged (cancel_cb=None keeps the engine bit-identical, guarded by a dedicated test).
  • New tests guard behavior: cancel stops at the Nth residual evaluation (calls == 3), stop latency < 0.5 s, Rwp bit-identical with no callback, unit-cell engine independently effective, run-all cancel leaves no status='failed' fake record.
  • Also fixed a "happened to hold" existing assertion: test_serialize_uses_relative_paths originally asserted "same drive letter → relative path", but _rel() is the prefix semantics of relative_to, and it only passed because pytest's tmp happened to be on C: and the project on D:. Changed to test the real contract.

Build and Reproducibility

This release did a reproducibility check on the build chain, with conclusions in two layers:

  • PyInstaller artifacts (.exe layer) are byte-reproducible: after fixing SOURCE_DATE_EPOCH (to git commit time) and PYTHONHASHSEED=0, two builds of the same commit yield completely identical XinRefinement.exe / XinRefinementCLI.exe hashes (measured twice, including different --distpath). So the source version can be traced back from the artifact.
  • Installer (Inno Setup layer) is not byte-reproducible: same input, same SOURCE_DATE_EPOCH, two packagings yield same size, different bytes (PE header timestamp comes from the compiler stub, unaffected by epoch). So the SHA256 in the table above is valid only for this one asset of this Release, not representing "anyone rebuilding gets the same hash string"; a same-source rebuild's installer hash will differ, but the two .exe inside can be byte-matched.
  • One process constraint: SOURCE_DATE_EPOCH takes the HEAD commit time at build, so commit first, build after. If built before commit, the PE timestamp in the artifact points to the previous commit—content unaffected, but the source version can't be traced back from the artifact, and the rebuild hash won't match.
  • How this asset was actually built: built after 06b115c (the release commit pointed to by tag v1.6.2), with SOURCE_DATE_EPOCH=1794219055 fixed to that commit's commit time; inside the artifact XinRefinement.exe = 2b2436de…, XinRefinementCLI.exe = 16c99cb3…, both byte-reproducible from the same source.

Known Limitations

  • Stop is cooperative: the engine checks the flag at the residual evaluation, so the in-progress single evaluation will finish (sample 1 ~16 ms; longer for more phases and points). No thread hard-kill—that would leave numpy/BLAS internal state and temporary caches unreclaimable.
  • Sample 7 still not meeting the bar (+3.39 pt, cristobalite near-amorphous broad-packet starting-point problem), this release did not touch the engine.

Installation and Registration

Same as v1.6.1: installer embeds zh/en/ja trilingual UI, registration code interoperable with the XinMatch family (%APPDATA%\PolyXRD\Registration\XinRefinement.json). Internal evaluation build needs no registration or install, marked in red in the status bar.

License

This software uses the MIT license (LICENSE / LICENSE-CN, viewable in full from the help menu). Built-in crystal structure data comes from COD (CC0) and ICSD-derived public literature, containing no PDF cards or ICDD data.