Repository navigation
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, sha256c51f786881c1ed8d965b27b31c9d406e2b51a648f6246a45a84f3f09e2abced1) 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 viaconfig_for'sreplace()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.py5 cases). - golden CLI Rwp 50/50
3.4074156640940005unchanged (cancel_cb=Nonekeeps 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 nostatus='failed'fake record. - Also fixed a "happened to hold" existing assertion:
test_serialize_uses_relative_pathsoriginally asserted "same drive letter → relative path", but_rel()is the prefix semantics ofrelative_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 (
.exelayer) are byte-reproducible: after fixingSOURCE_DATE_EPOCH(to git commit time) andPYTHONHASHSEED=0, two builds of the same commit yield completely identicalXinRefinement.exe/XinRefinementCLI.exehashes (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.exeinside can be byte-matched. - One process constraint:
SOURCE_DATE_EPOCHtakes 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 tagv1.6.2), withSOURCE_DATE_EPOCH=1794219055fixed to that commit's commit time; inside the artifactXinRefinement.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.