Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
41922e7
feat(leveling): add Gwyddion facet-level tilt parity
kegouro Aug 4, 2026
0e29d99
feat(scanline): add Gwydion line correction parity
kegouro Aug 4, 2026
8335369
feat(scanline): add Gwyddion scar detection and repair parity
kegouro Aug 4, 2026
448edb2
feat(scanline): add Gwyddion Step Block correction parity
kegouro Aug 4, 2026
0038095
feat(leveling): complete Gwydion Align Rows parity family
kegouro Aug 4, 2026
c5eab57
feat(filters): add Gwydion neighborhood filter parity
kegouro Aug 4, 2026
7befeac
feat(core): add capability ledger and operation registry
kegouro Aug 4, 2026
2a61991
feat(filters): add derivative filter parity
kegouro Aug 5, 2026
a6d8084
feat(core): register derivative filter capabilities
kegouro Aug 5, 2026
b923409
feat(force): add force spectroscopy foundation
kegouro Aug 5, 2026
357c510
feat(core): register force foundation capabilities
kegouro Aug 5, 2026
c64680e
feat(force): add contact mechanics reliability
kegouro Aug 5, 2026
f53a3ec
feat(core): register force mechanics capabilities
kegouro Aug 5, 2026
2af79ab
feat(force): add time-domain viscoelasticity
kegouro Aug 5, 2026
efa4076
feat(core): register viscoelastic capabilities
kegouro Aug 5, 2026
c9ae1d0
feat(force): add single-molecule force spectroscopy
kegouro Aug 6, 2026
9e11cb7
feat(core): register SMFS capabilities
kegouro Aug 6, 2026
49be186
fix(io): support JPK ForceScan 2.0 lcd-info calibration
kegouro Aug 6, 2026
83b93fb
feat(force): add acquisition-order path work
kegouro Aug 7, 2026
b28030c
feat(core): register acquisition path work
kegouro Aug 7, 2026
414b4d0
merge: integrate validated force and real-data stack (FS-F1..F4, JPK …
kegouro Aug 7, 2026
fb01661
style(validation): wrap force fixture contact description
kegouro Aug 7, 2026
149d0e2
ci: pin evidence numpy and gate external-reference witnesses
kegouro Aug 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ jobs:
- name: Create venv (Python ${{ matrix.python-version }})
run: uv venv --python ${{ matrix.python-version }}
- name: Install package + test deps
run: uv pip install -e ".[dev,hdf5,gwy,report,grains,jpk]"
# numpy 2.5.1 = el entorno en que se generó la evidencia de validación
# (fixtures byte-deterministas: regeneración e inventarios comparan bytes/valores)
run: uv pip install -e ".[dev,hdf5,gwy,report,grains,jpk]" numpy==2.5.1
- name: Run tests (core + ciencia; los de GUI se omiten sin Qt)
run: uv run pytest

Expand Down
2 changes: 1 addition & 1 deletion docs/FILE_FORMATS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ that dependency coverage is not mistaken for a native implementation.
| `.nhf` | NanoSurf HDF5 | Yes | No | Yes | No | `core/io/nhf.py` | `h5py` via `spmkit[hdf5]` | Experimental | Dataset layout varies; no broad public corpus |
| `.gwy` | Gwyddion | Yes | No | Yes | Yes | `core/io/gwy.py` | `gwyfile` via `spmkit[gwy]` | Implemented interoperability | Not a claim of feature parity or universal equivalence with Gwyddion |
| `.spm`; Nanoscope magic in numbered files | Bruker / Digital Instruments | Yes | No | Yes | No | `core/io/bruker_spm.py` | None | Partial; six demonstrated Nanoscope III files | Only demonstrated header/pixel variants; `.00N` family not broadly assessed |
| `.jpk-force`, `.jpk` | JPK | No | Single curve | Yes | No | `core/io/jpk.py` | None | Implemented with synthetic fixtures | Vendor variants and calibration metadata need more redistributable fixtures |
| `.jpk-force`, `.jpk` | JPK | No | Single curve | Yes | No | `core/io/jpk.py` | None | Two profiles: direct scaling (legacy) and ForceScan 2.0 `lcd-info` indirection; synthetic fixtures + 10 real CC0 files (figshare 11637675.v3, campaign green); see `examples/jpk_forcescan2_reader_golden_path.md` | Other JPK metadata layouts (XML-era variants beyond the demonstrated set) unassessed; time not reconstructed |
| TIFF detected by JPK private tags | JPK export | No | Yes | Yes | No | `core/io/jpk_tiff.py` | `tifffile` via `spmkit[jpk]` | Experimental, content detected | Generic TIFF is not treated as JPK data |
| `.jpk-qi-data`, `.jpk-force-map`, `.jpk-qi-series` | JPK | Adapter-dependent | Yes | Yes | No | `core/io/afmformats_reader.py` | `afmformats` via `spmkit[afm]` | Experimental adapter path | Capability follows installed `afmformats` version |
| `.ibw` | Asylum / Igor Binary Wave | Adapter-dependent | Adapter-dependent | Yes | No | `core/io/afmformats_reader.py` | `afmformats` via `spmkit[afm]` | Experimental adapter path | Not a native IBW implementation on the default branch |
Expand Down
100 changes: 100 additions & 0 deletions docs/architecture/FORCE_COORDINATE_SEMANTICS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Force coordinate semantics: acquisition order vs coordinate order

**Scope**: how the SPMKit force stack treats a 1-D trajectory (a force-curve
segment) and why some operations integrate over an *ordered coordinate* while
others integrate over the *acquisition order*.

**Trigger (real data)**: the PAAm hydrogel JPK dataset
(`10.6084/m9.figshare.11637675.v3`, CC0). Its tip-sample separation axis is
globally directed (net ≈ −8 µm per approach) but **not** strictly monotone:
56–74% of the per-step increments are negative at the nm scale (deflection
noise), with 76–84% backtracking fraction. The strict work integration
(`integrate_force_work`) correctly rejects such an axis with
`NONMONOTONIC_COORDINATE` instead of fabricating a value.

## Acquisition order vs coordinate order

A segment is a *sequence of acquired samples* `(z_i, F_i)`, `i = 0..n-1`.
Two different mathematical objects can be built from it:

- **Coordinate-ordered representation**: the function `F(z)` over the
travelled coordinate values. Requires a single-valued branch (each `z`
visited once per direction); local reversals make `F(z)` multivalued
without a branch choice.
- **Acquisition-ordered path**: the trajectory `i -> (z_i, F_i)` with signed
increments `dz_i = z_{i+1} - z_i`.

**Sorting is forbidden** as a silent repair: it reorders physics (the
force at a revisited coordinate belongs to a different acquisition time,
often a different contact state) and it hides the jitter that the user must
see. A *documented, explicit* reorder for a specific algorithm (e.g. the
SMFS pull-order search) is a deliberate design, not a hidden repair.

## Path work vs monotonic-coordinate integral

**Monotonic-coordinate integral** (`integrate_force_work`, unchanged, strict):
two-segment (approach + retract), contact-limited common overlap domain,
monotone interpolation onto a grid, trapezoidal arithmetic. Requires a
strictly (tolerance-classified) monotone axis; raises
`NONMONOTONIC_COORDINATE` otherwise.

**Acquisition-path work** (`integrate_force_path_work`, new):

W = sum_i 0.5 * (F_i + F_{i+1}) * (z_{i+1} - z_i)

evaluated in sample-acquisition order with deterministic float64
accumulation. Properties: signed `dz` retained; local reversals and closed
loops contribute their signed path work; repeated coordinates contribute
zero; coordinate translation leaves `W` unchanged; reversing acquisition
flips the sign; no monotonicity repair. A local reversal is **not
automatically invalid** — it is part of the acquired trajectory.

## Operation classification

| Operation | Coordinate consumed | Category |
|---|---|---|
| `integrate_force_work` (strict work) | separation/height, both segments | C — STRICTLY_MONOTONIC_REQUIRED (inverts/interpolates) |
| `integrate_force_path_work` (new) | single segment, acquisition order | A — PATH_ORDER_SAFE (signed path integral) |
| `coordinate_path_diagnostics` (new) | single segment | A — classification only, never alters integrals |
| `extract_force_events` | separation/height windows | A — ordered samples + value windows |
| `compute_tip_sample_separation` | elementwise height − deflection | A |
| contact-point methods (threshold/ROV/piecewise) | height/force samples | A — ordered samples, no inversion |
| baseline fit / correction | sample-index based | A |
| `dissipation_energy` (legacy forcecurve) | given-order trapezoid | A — already path-ordered |
| SMFS `_pull_order` search | separation | C — explicit documented coordinate reorder |
| `contact_mechanics` interp | monotone branch | C — inversion by construction |
| `validate_time_axis` (viscoelastic) | time | C — strictly increasing time (separate domain) |
| `_pspline` parameterization | fit parameter | C |

No operation outside the path-work pair was changed by FS-R1C.

## Diagnostics and tolerance

`CoordinatePathDiagnostics` classifies without touching any integral:

- `global_direction` derives from the **net displacement** sign
(`z[-1] - z[0]`); near-zero net → `closed_or_ambiguous`, never a forced
approach/retract label.
- `backtracking_fraction` = backward distance / total variation.
- `maximum_reverse_excursion` is a *path-level* cumulative excursion from
the running directional extremum (not a single-step statistic).
- `classification_tolerance` (default exactly 0.0, SI units) only affects
classification (direction, reversal counts, `strictly_monotonic`); it is
stored in provenance and **never** changes the numerical integral.

## Real PAAm diagnostic summary

Ten external CC0 files, verified against the committed manifest: all ten
approaches are globally directed `decreasing` (net −7.0…−8.2 µm), with
backtracking fractions 0.76–0.84, maximum reverse steps 2–9 nm and maximum
reverse excursions 2–13 nm. Acquisition-path work: −2.3…−2.5e-14 J
(documented as a path integral, **not** validated material energy).

## Non-claims

Local reversal is not proven to be only noise; path work is not automatically
adhesion energy; no energy-per-area result; no thermodynamic interpretation
without a process model; no physical validation; no automatic loop
correction; no smoothing or denoising; no guarantee for segments with
ambiguous global direction; no change to algorithms that require monotonic
inversion; no time-domain reconstruction; no universal real-curve policy.
Loading
Loading