diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b487845..2ad5314 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -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
diff --git a/docs/FILE_FORMATS.md b/docs/FILE_FORMATS.md
index 52e1cab..228adec 100644
--- a/docs/FILE_FORMATS.md
+++ b/docs/FILE_FORMATS.md
@@ -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 |
diff --git a/docs/architecture/FORCE_COORDINATE_SEMANTICS.md b/docs/architecture/FORCE_COORDINATE_SEMANTICS.md
new file mode 100644
index 0000000..c1518d8
--- /dev/null
+++ b/docs/architecture/FORCE_COORDINATE_SEMANTICS.md
@@ -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.
diff --git a/docs/parity/CAPABILITY_LEDGER.md b/docs/parity/CAPABILITY_LEDGER.md
new file mode 100644
index 0000000..b2ec84d
--- /dev/null
+++ b/docs/parity/CAPABILITY_LEDGER.md
@@ -0,0 +1,2614 @@
+# SPMKit Capability Ledger
+
+Stable scientific capabilities registered by the Operation Registry v1.
+
+- schema_version: 1
+- operations: 74
+
+Source of truth: `src/spmkit/core/capabilities.json` (generated view; do not edit by hand).
+
+## FORCE.BASELINE.CORRECT
+
+- operation_id: `force.baseline.correct`
+- public_name: `correct_force_baseline`
+- public_import: `spmkit.core.analysis:correct_force_baseline`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Force baseline correction)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Subtract the fitted baseline (offset + slope over height) with scope all/baseline/approach; slope correction changes the data (documented).
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: N
+
+- parameters:
+ - `curve` (positional, required) — Force curve.
+ - `baseline` (positional, required) — Fitted baseline.
+ - `scope` (keyword_only, 'all' values=['all', 'baseline', 'approach']) — Correction scope.
+
+- evidence:
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.json`
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.npz`
+ - `tests/validation/fixtures/force_foundation/force_foundation_reference.json`
+ - `tests/validation/test_force_foundation_validation.py`
+ - `tests/core/test_force_foundation.py`
+
+## FORCE.BASELINE.FIT
+
+- operation_id: `force.baseline.fit`
+- public_name: `fit_force_baseline`
+- public_import: `spmkit.core.analysis:fit_force_baseline`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Force baseline fit)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Fit the pre-contact baseline (first 10% of approach): linear offset + slope via polyfit; optional deterministic Huber-IRLS robust fit; residual RMS and robust scale; BASELINE_TOO_SHORT for too few points.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: N
+
+- parameters:
+ - `curve` (positional, required) — Force curve.
+ - `region` (keyword_only, 'pre_contact' values=['pre_contact']) — Baseline region.
+ - `model` (keyword_only, 'linear' values=['linear']) — Baseline model.
+ - `robust` (keyword_only, False) — Robust IRLS fit.
+
+- evidence:
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.json`
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.npz`
+ - `tests/validation/fixtures/force_foundation/force_foundation_reference.json`
+ - `tests/validation/test_force_foundation_validation.py`
+ - `tests/core/test_force_foundation.py`
+
+## FORCE.CALIBRATION.APPLY
+
+- operation_id: `force.calibration.apply`
+- public_name: `calibrate_force_curve`
+- public_import: `spmkit.core.analysis:calibrate_force_curve`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Force calibration application)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: raw deflection voltage (V) -> deflection (m) via InVOLS (m/V) -> force (N) via spring constant (N/m); already-calibrated pass-through; double calibration rejected (INVALID_CALIBRATION); missing calibration raises MISSING_CALIBRATION.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: N
+
+- parameters:
+ - `curve` (positional, required) — Force curve to calibrate.
+ - `calibration` (keyword_only, required) — Explicit calibration.
+
+- evidence:
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.json`
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.npz`
+ - `tests/validation/fixtures/force_foundation/force_foundation_reference.json`
+ - `tests/validation/test_force_foundation_validation.py`
+ - `tests/core/test_force_foundation.py`
+
+## FORCE.CONTACT.ENSEMBLE
+
+- operation_id: `force.contact.ensemble`
+- public_name: `contact_point_ensemble`
+- public_import: `spmkit.core.analysis:contact_point_ensemble`
+- family: FORCE
+- maturity: SOFTWARE_VERIFIED
+- status: stable
+- reference: SPMKit native (Contact point (ensemble))
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Combine threshold/ROV/piecewise; robust location = median of valid candidate indices; explicit disagreement and spread; deterministic bootstrap only when requested; CONTACT_METHOD_DISAGREEMENT when fewer than two methods agree.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: m
+
+- parameters:
+ - `curve` (positional, required) — Force curve.
+ - `methods` (keyword_only, ['threshold', 'ratio_of_variances', 'piecewise']) — Contact methods.
+ - `bootstrap_samples` (keyword_only, 0) — Bootstrap samples.
+
+- evidence:
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.json`
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.npz`
+ - `tests/validation/fixtures/force_foundation/force_foundation_reference.json`
+ - `tests/validation/test_force_foundation_validation.py`
+ - `tests/core/test_force_foundation.py`
+
+- known deviations:
+ - method spread does not constitute an uncertainty guarantee
+ - maturity downgraded at independent audit: NUMERICALLY_VERIFIED -> SOFTWARE_VERIFIED
+
+## FORCE.CONTACT.PIECEWISE
+
+- operation_id: `force.contact.piecewise`
+- public_name: `contact_point_piecewise`
+- public_import: `spmkit.core.analysis:contact_point_piecewise`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Contact point (piecewise))
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Value-continuous piecewise baseline/contact polynomial fit over the search grid; requires a genuine residual improvement over a single whole-curve polynomial (flat curves fail).
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: m
+
+- parameters:
+ - `curve` (positional, required) — Force curve.
+ - `baseline_order` (keyword_only, 1) — Baseline order.
+ - `contact_order` (keyword_only, 2) — Contact order.
+
+- evidence:
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.json`
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.npz`
+ - `tests/validation/fixtures/force_foundation/force_foundation_reference.json`
+ - `tests/validation/test_force_foundation_validation.py`
+ - `tests/core/test_force_foundation.py`
+
+## FORCE.CONTACT.RATIO_OF_VARIANCES
+
+- operation_id: `force.contact.ratio_of_variances`
+- public_name: `contact_point_ratio_of_variances`
+- public_import: `spmkit.core.analysis:contact_point_ratio_of_variances`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Contact point (ratio of variances))
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Gavara 2016 ratio-of-variances contact: argmax of variance-after / variance-before over the window grid; requires a genuine variance jump (ratio >= 2) and sufficient length.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: m
+
+- parameters:
+ - `curve` (positional, required) — Force curve.
+ - `window` (keyword_only, 20) — Variance window.
+
+- evidence:
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.json`
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.npz`
+ - `tests/validation/fixtures/force_foundation/force_foundation_reference.json`
+ - `tests/validation/test_force_foundation_validation.py`
+ - `tests/core/test_force_foundation.py`
+
+## FORCE.CONTACT.THRESHOLD
+
+- operation_id: `force.contact.threshold`
+- public_name: `contact_point_threshold`
+- public_import: `spmkit.core.analysis:contact_point_threshold`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: nanite 4.2.3 (Contact point (nanite deviation_from_baseline profile))
+- evidence profile: `COMPILED_NANITE_4_2_3_EXTERNAL_REFERENCE_FROZEN_PROFILE`
+
+- contract: Baseline-relative threshold contact: first crossing of mean + k*sigma with persistence 3; validated against the frozen nanite 4.2.3 deviation_from_baseline contact index on the shared noiseless cases.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: m
+
+- parameters:
+ - `curve` (positional, required) — Force curve.
+ - `threshold_sigma` (keyword_only, 5.0) — Threshold in sigma.
+
+- evidence:
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.json`
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.npz`
+ - `tests/validation/fixtures/force_foundation/force_foundation_reference.json`
+ - `tests/validation/test_force_foundation_validation.py`
+ - `tests/core/test_force_foundation.py`
+ - `tests/validation/fixtures/force_foundation/force_foundation_external.npz`
+
+- known deviations:
+ - production threshold agrees with nanite deviation_from_baseline on clean flat-baseline cases (0..2 samples) but diverges on sloped/noisy baselines (up to 13 samples across the 17-case persisted matrix); NOT cross-validated as equivalent
+ - sloped noiseless baselines degrade threshold recovery (characterized)
+
+## FORCE.EVENTS.EXTRACT
+
+- operation_id: `force.events.extract`
+- public_name: `extract_force_events`
+- public_import: `spmkit.core.analysis:extract_force_events`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Force events (snap-in / pull-off))
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Snap-in = minimum force before contact on approach (baseline-relative below mean - 3*sigma); pull-off = minimum force after contact on retract; physical windows; no event when the relevant segment is absent (EVENT_NOT_FOUND).
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: N
+
+- parameters:
+ - `curve` (positional, required) — Force curve.
+ - `contact` (positional, required) — Contact point result.
+ - `snap_in_window` (keyword_only, None) — Snap-in window (coordinate).
+ - `pull_off_window` (keyword_only, None) — Pull-off window (coordinate).
+
+- evidence:
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.json`
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.npz`
+ - `tests/validation/fixtures/force_foundation/force_foundation_reference.json`
+ - `tests/validation/test_force_foundation_validation.py`
+ - `tests/core/test_force_foundation.py`
+
+## FORCE.FIT_WINDOW.SELECT
+
+- operation_id: `force.fit_window.select`
+- public_name: `select_contact_fit_window`
+- public_import: `spmkit.core.analysis:select_contact_fit_window`
+- family: FORCE
+- maturity: SOFTWARE_VERIFIED
+- status: stable
+- reference: SPMKit native (Contact fit window selection)
+- evidence profile: `NATIVE_SPMKIT_DESIGNED_HEURISTIC`
+
+- contract: Contiguous contact fit window from the contact index, optionally trimmed by min/max indentation and min/max force; fewer than min_points raises EMPTY_FIT_WINDOW / INSUFFICIENT_FIT_POINTS; included mask consistent with n_points; non-mutating.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: not_applicable
+
+- parameters:
+ - `prepared` (positional, required) — Prepared curve.
+ - `indentation` (positional, required) — IndentationResult.
+ - `min_indentation` (keyword_only, None) — Lower indentation bound (m).
+ - `max_indentation` (keyword_only, None) — Upper indentation bound (m).
+ - `min_force` (keyword_only, None) — Lower force bound (N).
+ - `max_force` (keyword_only, None) — Upper force bound (N).
+ - `min_points` (keyword_only, 20) — Minimum window size.
+
+- evidence:
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.json`
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz`
+ - `tests/validation/test_force_mechanics_validation.py`
+ - `tests/core/test_force_mechanics.py`
+
+## FORCE.INDENTATION.COMPUTE
+
+- operation_id: `force.indentation.compute`
+- public_name: `compute_indentation`
+- public_import: `spmkit.core.analysis:compute_indentation`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Indentation from separation and contact)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Indentation = approach separation minus the FS-F1 contact coordinate; zero at the contact and positive into the sample; pre-contact samples excluded by the valid mask; requires a fit-eligible prepared curve (CURVE_NOT_FIT_ELIGIBLE typed failure); NONFINITE_INPUT typed failure; units m; non-mutating.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: m
+
+- parameters:
+ - `prepared` (positional, required) — FS-F1 prepared curve.
+
+- evidence:
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.json`
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz`
+ - `tests/validation/test_force_mechanics_validation.py`
+ - `tests/core/test_force_mechanics.py`
+
+## FORCE.MODEL.COMPARE
+
+- operation_id: `force.model.compare`
+- public_name: `compare_contact_models`
+- public_import: `spmkit.core.analysis:compare_contact_models`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (AICc model comparison)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Model-relative comparison over the identical data subset; AICc weights normalized to 1; recommended model is the AICc minimum unless the runner-up retains considerable support (Delta AICc < 4 -> ambiguous, no recommendation); no physical-truth claim; misspecified fits detected (cone data -> sneddon weight > 0.9); unknown model raises ValueError.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: not_applicable
+
+- parameters:
+ - `prepared` (positional, required) — Prepared curve.
+ - `indentation` (positional, required) — IndentationResult.
+ - `window` (positional, required) — FitWindowResult.
+ - `models` (keyword_only, ['hertz_sphere', 'sneddon_cone', 'flat_punch', 'dmt']) — Candidate models.
+ - `tip_radius` (keyword_only, required) — Tip radius (m).
+ - `half_angle` (keyword_only, 0.3490658503988659) — Cone half-angle (rad).
+ - `punch_radius` (keyword_only, None) — Punch radius (m).
+ - `poisson` (keyword_only, 0.3 bounds=[0.0, 0.5]) — Poisson ratio.
+
+- evidence:
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.json`
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz`
+ - `tests/validation/test_force_mechanics_validation.py`
+ - `tests/core/test_force_mechanics.py`
+
+## FORCE.MODEL.FIT_DMT
+
+- operation_id: `force.model.fit_dmt`
+- public_name: `fit_dmt`
+- public_import: `spmkit.core.analysis:fit_dmt`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (DMT fit)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Two-parameter fit of E and F_adh over the window trimmed past the snap-in region; on snap-in phantoms E within 30% and F_adh within 1.5e-9 N (FS-F1 contact ensemble is unstable on snap-in curves, up to ~10 samples off); typed failures INVALID_RADIUS, INVALID_POISSON_RATIO, INVALID_ADHESION_PARAMETER, OPTIMIZATION_FAILED.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: Pa
+
+- parameters:
+ - `prepared` (positional, required) — Prepared curve.
+ - `indentation` (positional, required) — IndentationResult.
+ - `window` (positional, required) — FitWindowResult.
+ - `tip_radius` (keyword_only, required) — Tip radius (m).
+ - `poisson` (keyword_only, 0.3 bounds=[0.0, 0.5]) — Poisson ratio.
+ - `E_initial` (keyword_only, 1000000000.0) — Optimizer start (Pa).
+ - `F_adh_initial` (keyword_only, 1e-09) — Adhesion start (N).
+
+- evidence:
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.json`
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz`
+ - `tests/validation/test_force_mechanics_validation.py`
+ - `tests/core/test_force_mechanics.py`
+
+- known deviations:
+ - snap-in curves: FS-F1 contact ensemble unstable (up to ~10 samples off); dedicated snap-in contact detection is future work
+
+## FORCE.MODEL.FIT_FLAT_PUNCH
+
+- operation_id: `force.model.fit_flat_punch`
+- public_name: `fit_flat_punch`
+- public_import: `spmkit.core.analysis:fit_flat_punch`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Flat punch fit)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Linear-modulus least-squares fit of E with punch radius and poisson ratio fixed; E within 5% on clean phantoms; typed failures INVALID_RADIUS, INVALID_POISSON_RATIO, OPTIMIZATION_FAILED, NONFINITE_INPUT; same result contract as fit_hertz_sphere.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: Pa
+
+- parameters:
+ - `prepared` (positional, required) — Prepared curve.
+ - `indentation` (positional, required) — IndentationResult.
+ - `window` (positional, required) — FitWindowResult.
+ - `punch_radius` (keyword_only, required) — Punch radius (m).
+ - `poisson` (keyword_only, 0.3 bounds=[0.0, 0.5]) — Poisson ratio.
+ - `E_initial` (keyword_only, 1000000000.0) — Optimizer start (Pa).
+
+- evidence:
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.json`
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz`
+ - `tests/validation/test_force_mechanics_validation.py`
+ - `tests/core/test_force_mechanics.py`
+
+## FORCE.MODEL.FIT_HERTZ
+
+- operation_id: `force.model.fit_hertz`
+- public_name: `fit_hertz_sphere`
+- public_import: `spmkit.core.analysis:fit_hertz_sphere`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Hertz sphere fit)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Nonlinear least-squares fit of E over the fit window with tip radius and poisson ratio fixed; E within 5% on clean phantoms (contact-precision limited); typed failures INVALID_RADIUS, INVALID_POISSON_RATIO, OPTIMIZATION_FAILED, NONFINITE_INPUT; result carries parameters, covariance, residuals, AIC/AICc/BIC, rmse and window provenance.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: Pa
+
+- parameters:
+ - `prepared` (positional, required) — Prepared curve.
+ - `indentation` (positional, required) — IndentationResult.
+ - `window` (positional, required) — FitWindowResult.
+ - `tip_radius` (keyword_only, required) — Tip radius (m).
+ - `poisson` (keyword_only, 0.3 bounds=[0.0, 0.5]) — Poisson ratio.
+ - `E_initial` (keyword_only, 1000000000.0) — Optimizer start (Pa).
+
+- evidence:
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.json`
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz`
+ - `tests/validation/test_force_mechanics_validation.py`
+ - `tests/core/test_force_mechanics.py`
+
+## FORCE.MODEL.FIT_JKR
+
+- operation_id: `force.model.fit_jkr`
+- public_name: `fit_jkr`
+- public_import: `spmkit.core.analysis:fit_jkr`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (JKR fit)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Two-parameter fit of E and w over the window trimmed past the snap-in region; loading curve parametrized by the contact radius (monotone for a >= a0, range derived from data); w=0 reduces to hertz; on snap-in phantoms E within 20% and w within 30%; typed failures INVALID_RADIUS, INVALID_POISSON_RATIO, INVALID_ADHESION_PARAMETER, OPTIMIZATION_FAILED.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: Pa
+
+- parameters:
+ - `prepared` (positional, required) — Prepared curve.
+ - `indentation` (positional, required) — IndentationResult.
+ - `window` (positional, required) — FitWindowResult.
+ - `tip_radius` (keyword_only, required) — Tip radius (m).
+ - `poisson` (keyword_only, 0.3 bounds=[0.0, 0.5]) — Poisson ratio.
+ - `E_initial` (keyword_only, 1000000000.0) — Optimizer start (Pa).
+ - `w_initial` (keyword_only, 0.001) — Work-of-adhesion start (J/m^2).
+
+- evidence:
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.json`
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz`
+ - `tests/validation/test_force_mechanics_validation.py`
+ - `tests/core/test_force_mechanics.py`
+
+- known deviations:
+ - snap-in curves: same contact-ensemble limitation as fit_dmt
+
+## FORCE.MODEL.FIT_SNEDDON
+
+- operation_id: `force.model.fit_sneddon`
+- public_name: `fit_sneddon_cone`
+- public_import: `spmkit.core.analysis:fit_sneddon_cone`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Sneddon cone fit)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Nonlinear least-squares fit of E with cone half-angle and poisson ratio fixed; E within 5% on clean phantoms; typed failures INVALID_ANGLE, INVALID_POISSON_RATIO, OPTIMIZATION_FAILED, NONFINITE_INPUT; same result contract as fit_hertz_sphere.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: Pa
+
+- parameters:
+ - `prepared` (positional, required) — Prepared curve.
+ - `indentation` (positional, required) — IndentationResult.
+ - `window` (positional, required) — FitWindowResult.
+ - `half_angle` (keyword_only, required) — Cone half-angle (rad).
+ - `poisson` (keyword_only, 0.3 bounds=[0.0, 0.5]) — Poisson ratio.
+ - `E_initial` (keyword_only, 1000000000.0) — Optimizer start (Pa).
+
+- evidence:
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.json`
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz`
+ - `tests/validation/test_force_mechanics_validation.py`
+ - `tests/core/test_force_mechanics.py`
+
+## FORCE.MODEL.FORWARD
+
+- operation_id: `force.model.forward`
+- public_name: `forward_model`
+- public_import: `spmkit.core.analysis:forward_model`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Contact-model forward equations)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Frozen closed-form loading equations with reduced modulus E* = E/(1-nu^2): hertz F = (4/3) E* sqrt(R) d^1.5; sneddon F = (2 tan(alpha)/pi) E* d^2; flat punch F = 2 E* R d; dmt F = hertz - F_adh; jkr parametric contact-radius loading curve (monotone, derived range, w=0 reduces to hertz); SI units N; unknown model raises ValueError.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: N
+
+- parameters:
+ - `model` (positional, required) — Model name.
+ - `delta` (positional, required) — Indentation array (m).
+ - `params` (positional, required) — Model parameters.
+
+- evidence:
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.json`
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz`
+ - `tests/validation/test_force_mechanics_validation.py`
+ - `tests/core/test_force_mechanics.py`
+
+## FORCE.PREPARE
+
+- operation_id: `force.prepare`
+- public_name: `prepare_force_curve`
+- public_import: `spmkit.core.analysis:prepare_force_curve`
+- family: FORCE
+- maturity: SOFTWARE_VERIFIED
+- status: stable
+- reference: SPMKit native (Force curve preparation pipeline)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Explicit orchestration over the 12 public primitives: segments -> calibration -> tip-sample separation -> baseline fit/correction -> contact ensemble -> events -> work -> quality; provenance names every decision; contact detection runs on the calibrated curve.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: not_applicable
+
+- parameters:
+ - `curve` (positional, required) — Force curve.
+ - `calibration` (keyword_only, None) — Explicit calibration.
+ - `baseline_model` (keyword_only, 'linear' values=['linear']) — Baseline model.
+ - `contact_methods` (keyword_only, ['threshold', 'ratio_of_variances', 'piecewise']) — Contact methods.
+ - `bootstrap_samples` (keyword_only, 0) — Bootstrap samples.
+
+- evidence:
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.json`
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.npz`
+ - `tests/validation/fixtures/force_foundation/force_foundation_reference.json`
+ - `tests/validation/test_force_foundation_validation.py`
+ - `tests/core/test_force_foundation.py`
+
+- known deviations:
+ - orchestration maturity is bounded by its weakest material component (contact ensemble and quality score are SOFTWARE_VERIFIED heuristics)
+
+## FORCE.QUALITY.SCORE
+
+- operation_id: `force.quality.score`
+- public_name: `score_force_curve_quality`
+- public_import: `spmkit.core.analysis:score_force_curve_quality`
+- family: FORCE
+- maturity: SOFTWARE_VERIFIED
+- status: stable
+- reference: SPMKit native (Force curve quality scoring)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Typed failure reasons (14 codes) beside a summary score; component diagnostics always explicit; eligibility for contact-model fitting.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: not_applicable
+
+- parameters:
+ - `curve` (positional, required) — Force curve.
+ - `segmentation` (keyword_only, None) — Segmentation result.
+ - `baseline` (keyword_only, None) — Baseline result.
+ - `contact` (keyword_only, None) — Contact result.
+ - `events` (keyword_only, None) — Events result.
+
+- evidence:
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.json`
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.npz`
+ - `tests/validation/fixtures/force_foundation/force_foundation_reference.json`
+ - `tests/validation/test_force_foundation_validation.py`
+ - `tests/core/test_force_foundation.py`
+
+- known deviations:
+ - the aggregate summary score is a designed heuristic; it is not an externally validated scientific quality probability
+
+## FORCE.RELIABILITY.BOOTSTRAP
+
+- operation_id: `force.reliability.bootstrap`
+- public_name: `bootstrap_force_fit`
+- public_import: `spmkit.core.analysis:bootstrap_force_fit`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Residual bootstrap)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Deterministic residual (or block-residual) bootstrap of the hertz fit; percentile intervals and bias estimate over E; replicate failures counted, never masked; success fraction below min_success_fraction (or outside [0,1]) raises BOOTSTRAP_INSUFFICIENT_SUCCESS; same seed reproduces identical samples.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: not_applicable
+
+- parameters:
+ - `spec` (positional, required) — (prepared, indentation, window, model) tuple.
+ - `samples` (keyword_only, 500) — Replicate count.
+ - `seed` (keyword_only, 0) — RNG seed.
+ - `strategy` (keyword_only, 'residual' values=['residual', 'block_residual']) — Resampling strategy.
+ - `tip_radius` (keyword_only, 1e-08) — Tip radius (m).
+ - `poisson` (keyword_only, 0.3 bounds=[0.0, 0.5]) — Poisson ratio.
+ - `min_success_fraction` (keyword_only, 0.5) — Minimum success fraction.
+
+- evidence:
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.json`
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz`
+ - `tests/validation/test_force_mechanics_validation.py`
+ - `tests/core/test_force_mechanics.py`
+
+## FORCE.RELIABILITY.DIAGNOSE
+
+- operation_id: `force.reliability.diagnose`
+- public_name: `diagnose_force_fit`
+- public_import: `spmkit.core.analysis:diagnose_force_fit`
+- family: FORCE
+- maturity: SOFTWARE_VERIFIED
+- status: stable
+- reference: SPMKit native (Fit diagnostics policy)
+- evidence profile: `NATIVE_SPMKIT_DESIGNED_HEURISTIC`
+
+- contract: Explicit diagnostics: residual RMS, autocorrelation and curvature proxies, covariance condition number and max parameter correlation, one-at-a-time contact/window sensitivity, bootstrap success fraction, model-ambiguity flag; the summary status is a policy (ok/review), never a probability; failure reasons listed explicitly.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: not_applicable
+
+- parameters:
+ - `fit` (positional, required) — Fit result.
+ - `sensitivity` (keyword_only, None) — Sensitivity result.
+ - `bootstrap` (keyword_only, None) — Bootstrap result.
+
+- evidence:
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.json`
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz`
+ - `tests/validation/test_force_mechanics_validation.py`
+ - `tests/core/test_force_mechanics.py`
+
+## FORCE.RELIABILITY.SENSITIVITY
+
+- operation_id: `force.reliability.sensitivity`
+- public_name: `analyze_force_fit_sensitivity`
+- public_import: `spmkit.core.analysis:analyze_force_fit_sensitivity`
+- family: FORCE
+- maturity: SOFTWARE_VERIFIED
+- status: stable
+- reference: SPMKit native (Contact/window sensitivity multiverse)
+- evidence profile: `NATIVE_SPMKIT_DESIGNED_HEURISTIC`
+
+- contract: Deterministic multiverse over contact offsets and fit-window lower-bound fractions (bounded at max_configurations=512); one-at-a-time contact and window sensitivity indices relative to the baseline configuration; E stability ranges and robust medians; dominant sensitivity classified as contact, window or none (relative index > 20%); failed configurations recorded, never dropped; CONTACT_SENSITIVITY_HIGH when no configuration succeeds.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: not_applicable
+
+- parameters:
+ - `prepared` (positional, required) — Prepared curve.
+ - `contact_offsets` (keyword_only, [-3, -1, 0, 1, 3]) — Contact offsets (samples).
+ - `fit_window_variants` (keyword_only, [0.0, 0.05]) — Window lower-bound fractions.
+ - `baseline_variants` (keyword_only, ['linear']) — Baseline models.
+ - `models` (keyword_only, ['hertz_sphere']) — Models.
+ - `max_configurations` (keyword_only, 512) — Multiverse bound.
+ - `tip_radius` (keyword_only, 1e-08) — Tip radius (m).
+ - `poisson` (keyword_only, 0.3 bounds=[0.0, 0.5]) — Poisson ratio.
+
+- evidence:
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.json`
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz`
+ - `tests/validation/test_force_mechanics_validation.py`
+ - `tests/core/test_force_mechanics.py`
+
+## FORCE.SEGMENT.IDENTIFY
+
+- operation_id: `force.segment.identify`
+- public_name: `identify_force_segments`
+- public_import: `spmkit.core.analysis:identify_force_segments`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Force segment identification)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Identify approach/retract sample indices of a ForceCurve; instrument labels trusted when both segments exist, else turning point = height extremum; no sample reordering.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: not_applicable
+
+- parameters:
+ - `curve` (positional, required) — Force curve to segment.
+
+- evidence:
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.json`
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.npz`
+ - `tests/validation/fixtures/force_foundation/force_foundation_reference.json`
+ - `tests/validation/test_force_foundation_validation.py`
+ - `tests/core/test_force_foundation.py`
+
+- known deviations:
+ - single-segment inference may misplace the turning point on flat turning points
+
+## FORCE.SEPARATION.TIP_SAMPLE
+
+- operation_id: `force.separation.tip_sample`
+- public_name: `compute_tip_sample_separation`
+- public_import: `spmkit.core.analysis:compute_tip_sample_separation`
+- family: FORCE
+- maturity: CROSS_VALIDATED
+- status: stable
+- reference: nanite 4.2.3 (Tip-sample separation (nanite tip-position profile))
+- evidence profile: `COMPILED_NANITE_4_2_3_EXTERNAL_REFERENCE_FROZEN_PROFILE`
+
+- contract: Tip-sample separation = height - deflection per segment; no contact offset applied; validated against the frozen nanite 4.2.3 tip-position convention (tip = height + force/k + offset).
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: m
+
+- parameters:
+ - `curve` (positional, required) — Force curve.
+
+- evidence:
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.json`
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.npz`
+ - `tests/validation/fixtures/force_foundation/force_foundation_reference.json`
+ - `tests/validation/test_force_foundation_validation.py`
+ - `tests/core/test_force_foundation.py`
+ - `tests/validation/fixtures/force_foundation/force_foundation_external.npz`
+
+- known deviations:
+ - bitwise external identity not claimed; convention validated numerically on the frozen nanite profile
+
+## FORCE.SMFS.BATCH
+
+- operation_id: `force.smfs.batch`
+- public_name: `analyze_smfs_batch`
+- public_import: `spmkit.core.analysis:analyze_smfs_batch`
+- family: FORCE
+- maturity: SOFTWARE_VERIFIED
+- status: stable
+- reference: SPMKit native (Deterministic batch orchestration over per-curve analyses: e)
+- evidence profile: `NATIVE_SPMKIT_DESIGNED_HEURISTIC`
+
+- contract: Deterministic batch orchestration over per-curve analyses: every result retained, failed curves retained with reasons, unified event table with curve origins, population aggregation, stable ordering and replay; nothing silently dropped; the orchestration policy is SOFTWARE_VERIFIED.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: not_applicable
+
+- parameters:
+ - `analyses` (positional, required) — Per-curve analyses.
+ - `group_by` (keyword_only, 'loading_rate_decade' values=['none', 'loading_rate_decade']) — Grouping policy.
+ - `n_groups` (keyword_only, 4) — Number of groups.
+
+- evidence:
+ - `tests/validation/fixtures/force_smfs/smfs_reference.json`
+ - `tests/validation/fixtures/force_smfs/smfs_reference.npz`
+ - `tests/validation/test_force_smfs_validation.py`
+ - `tests/core/test_force_smfs.py`
+
+## FORCE.SMFS.CONTOUR_INCREMENT
+
+- operation_id: `force.smfs.contour_increment`
+- public_name: `infer_contour_length_increments`
+- public_import: `spmkit.core.analysis:infer_contour_length_increments`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Delta contour length per event from independent pre/post WLC)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Delta contour length per event from independent pre/post WLC fits on the ABSOLUTE molecular extension (a section-relative fit would absorb the event offset into a biased contour); event-index sensitivity characterized by explicit shifts; within 10% on the doubling-contour phantoms.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: m
+
+- parameters:
+ - `extension` (positional, required) — Extension result.
+ - `events` (positional, required) — Quantified events.
+ - `model` (keyword_only, 'worm_like_chain') — Polymer model.
+ - `temperature` (keyword_only, 298.0) — Temperature (K).
+ - `pre_margin` (keyword_only, 2) — Pre-event margin (samples).
+ - `post_margin` (keyword_only, 2) — Post-event margin (samples).
+ - `min_points` (keyword_only, 8) — Minimum window points.
+ - `sensitivity_shifts` (keyword_only, [0]) — Event-index shifts.
+
+- evidence:
+ - `tests/validation/fixtures/force_smfs/smfs_reference.json`
+ - `tests/validation/fixtures/force_smfs/smfs_reference.npz`
+ - `tests/validation/test_force_smfs_validation.py`
+ - `tests/core/test_force_smfs.py`
+
+## FORCE.SMFS.EVENTS.DETECT
+
+- operation_id: `force.smfs.events.detect`
+- public_name: `detect_unfolding_events`
+- public_import: `spmkit.core.analysis:detect_unfolding_events`
+- family: FORCE
+- maturity: SOFTWARE_VERIFIED
+- status: stable
+- reference: SPMKit native (Unfolding-event detection on the pull-ordered retract segment)
+- evidence profile: `NATIVE_SPMKIT_DESIGNED_HEURISTIC`
+
+- contract: Unfolding-event detection on the pull-ordered retract section: sustained force drops with public thresholds (drop magnitude, persistence, minimum separation, boundary margin); rejected candidates retained with reasons; the final detachment is distinguished from internal unfolding; sub-threshold drops raise NO_EVENTS typed. The detector is a documented heuristic (SOFTWARE_VERIFIED) evaluated with true/false positives on deterministic phantoms.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: N
+
+- parameters:
+ - `extension` (positional, required) — Extension result.
+ - `min_force_drop` (keyword_only, None) — Minimum drop (N).
+ - `min_persistence` (keyword_only, 3) — Sustained-drop samples.
+ - `min_event_separation` (keyword_only, 3) — Minimum separation (samples).
+ - `noise_sigma` (keyword_only, None) — Noise scale (N).
+ - `boundary_margin` (keyword_only, 2) — Boundary margin (samples).
+
+- evidence:
+ - `tests/validation/fixtures/force_smfs/smfs_reference.json`
+ - `tests/validation/fixtures/force_smfs/smfs_reference.npz`
+ - `tests/validation/test_force_smfs_validation.py`
+ - `tests/core/test_force_smfs.py`
+
+## FORCE.SMFS.EVENTS.QUANTIFY
+
+- operation_id: `force.smfs.events.quantify`
+- public_name: `quantify_unfolding_events`
+- public_import: `spmkit.core.analysis:quantify_unfolding_events`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Assign explicit pre/post windows and local loading rates to )
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Assign explicit pre/post windows and local loading rates to every selected event; the pre window spans the polymer section between the previous event (or the tether zero) and the event; the post window spans the section to the next event (or the section end).
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: N / m
+
+- parameters:
+ - `extension` (positional, required) — Extension result.
+ - `events` (positional, required) — Detected events.
+ - `pre_margin` (keyword_only, 2) — Pre-event margin (samples).
+ - `post_margin` (keyword_only, 2) — Post-event margin (samples).
+ - `min_points` (keyword_only, 8) — Minimum window points.
+
+- evidence:
+ - `tests/validation/fixtures/force_smfs/smfs_reference.json`
+ - `tests/validation/fixtures/force_smfs/smfs_reference.npz`
+ - `tests/validation/test_force_smfs_validation.py`
+ - `tests/core/test_force_smfs.py`
+
+## FORCE.SMFS.EXTENSION.COMPUTE
+
+- operation_id: `force.smfs.extension.compute`
+- public_name: `compute_molecular_extension`
+- public_import: `spmkit.core.analysis:compute_molecular_extension`
+- family: FORCE
+- maturity: SOFTWARE_VERIFIED
+- status: stable
+- reference: SPMKit native (Molecular extension of the retract segment with an explicit t)
+- evidence profile: `NATIVE_SPMKIT_DESIGNED_HEURISTIC`
+
+- contract: Molecular extension of the retract section with an explicit tether-zero policy: offset (physical m), index, pre_event (caller section start), or the estimator (retract zero-force crossing with its own diagnostics); the zero is never inferred silently from the contact; UNRESOLVED_TETHER_ZERO and INVALID_REFERENCE_POLICY typed; the estimator policy is a documented heuristic making the complete operation SOFTWARE_VERIFIED; the JPK/NID readers do not populate segment time (the SMFS retract section requires an explicit time axis where used).
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: m
+
+- parameters:
+ - `prepared` (positional, required) — FS-F1 prepared curve.
+ - `reference` (keyword_only, 'index' values=['offset', 'index', 'pre_event', 'estimator']) — Tether-zero reference policy.
+ - `reference_value` (keyword_only, None) — Offset (m) or index.
+ - `segment` (keyword_only, 'retract') — Segment (retract only).
+ - `estimator_noise_sigma` (keyword_only, None) — Estimator noise scale.
+
+- evidence:
+ - `tests/validation/fixtures/force_smfs/smfs_reference.json`
+ - `tests/validation/fixtures/force_smfs/smfs_reference.npz`
+ - `tests/validation/test_force_smfs_validation.py`
+ - `tests/core/test_force_smfs.py`
+
+## FORCE.SMFS.FORCE_CLAMP.SURVIVAL
+
+- operation_id: `force.smfs.force_clamp.survival`
+- public_name: `estimate_force_clamp_survival`
+- public_import: `spmkit.core.analysis:estimate_force_clamp_survival`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Kaplan-Meier survival with right censoring over explicit lif)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Kaplan-Meier survival with right censoring over explicit lifetimes and flags: events before censors at ties, events leave the risk set, censored observations never discarded; the median lifetime is typed UNDEFINED_MEDIAN when unreachable; the exponential rate is the censoring-aware MLE n_events/sum(times); matches the independent oracle exactly.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: s
+
+- parameters:
+ - `lifetimes` (positional, required) — Lifetimes (s).
+ - `censored` (positional, required) — Censoring flags (0 event, 1 censored).
+ - `force_level` (keyword_only, required) — Clamp force level (N).
+ - `temperature` (keyword_only, 298.0) — Temperature (K).
+ - `fit_exponential_rate` (keyword_only, True) — Fit the MLE rate.
+
+- evidence:
+ - `tests/validation/fixtures/force_smfs/smfs_reference.json`
+ - `tests/validation/fixtures/force_smfs/smfs_reference.npz`
+ - `tests/validation/test_force_smfs_validation.py`
+ - `tests/core/test_force_smfs.py`
+
+## FORCE.SMFS.KINETICS.BELL_EVANS
+
+- operation_id: `force.smfs.kinetics.bell_evans`
+- public_name: `fit_bell_evans`
+- public_import: `spmkit.core.analysis:fit_bell_evans`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Bell-Evans fit over (loading rate, rupture force) series: th)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Bell-Evans fit over (loading rate, rupture force) series: the primary estimator is the frozen most-probable-force regression F* = (k_B T/x_beta) ln(r x_beta/(k0 k_B T)) with the survival convention S(F) = exp(-k0 k_B T/(r x_beta)(exp(F x_beta/k_B T) - 1)); a bounded likelihood runs as a secondary with an identifiability diagnosis (the BE likelihood is degenerate toward x_beta -> 0, documented); narrow-rate ranges carry an IDENTIFIABILITY_LIMITED warning; x_beta recovered within 10% on the phantoms.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: m / 1/s
+
+- parameters:
+ - `loading_rates` (positional, required) — Loading rates (N/s).
+ - `rupture_forces` (positional, required) — Rupture forces (N).
+ - `temperature` (keyword_only, 298.0) — Temperature (K).
+ - `k0_initial` (keyword_only, 1.0) — Zero-force rate start (1/s).
+ - `x_beta_initial` (keyword_only, 1e-09) — Transition distance start (m).
+
+- evidence:
+ - `tests/validation/fixtures/force_smfs/smfs_reference.json`
+ - `tests/validation/fixtures/force_smfs/smfs_reference.npz`
+ - `tests/validation/test_force_smfs_validation.py`
+ - `tests/core/test_force_smfs.py`
+
+## FORCE.SMFS.KINETICS.DHS
+
+- operation_id: `force.smfs.kinetics.dhs`
+- public_name: `fit_dudko_hummer_szabo`
+- public_import: `spmkit.core.analysis:fit_dudko_hummer_szabo`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Dudko-Hummer-Szabo likelihood fit (k0, x_beta, dG) with the )
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Dudko-Hummer-Szabo likelihood fit (k0, x_beta, dG) with the frozen shape convention nu in {1/2, 2/3}, log-space evaluation with a consistent rate cap, the domain 1 - nu F x_beta/dG > 0 enforced; the Bell limit nu -> 0 recovers the BE rate; the fitted energy landscape is not claimed to be physically unique; parameters recovered within the documented wide bounds with the response reconstruction verified.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: m / 1/s / J
+
+- parameters:
+ - `loading_rates` (positional, required) — Loading rates (N/s).
+ - `rupture_forces` (positional, required) — Rupture forces (N).
+ - `nu` (keyword_only, 0.6666666666666666) — Potential shape (1/2 cusp, 2/3 linear-cubic).
+ - `temperature` (keyword_only, 298.0) — Temperature (K).
+ - `k0_initial` (keyword_only, 1.0) — Zero-force rate start (1/s).
+ - `x_beta_initial` (keyword_only, 1e-09) — Transition distance start (m).
+ - `dg_initial` (keyword_only, 1e-19) — Barrier height start (J).
+
+- evidence:
+ - `tests/validation/fixtures/force_smfs/smfs_reference.json`
+ - `tests/validation/fixtures/force_smfs/smfs_reference.npz`
+ - `tests/validation/test_force_smfs_validation.py`
+ - `tests/core/test_force_smfs.py`
+
+## FORCE.SMFS.LOADING_RATE
+
+- operation_id: `force.smfs.loading_rate`
+- public_name: `compute_event_loading_rates`
+- public_import: `spmkit.core.analysis:compute_event_loading_rates`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Local loading rate per event: the least-squares slope of for)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Local loading rate per event: the least-squares slope of force vs time over the pre-event window plus the robust median-of-pairs slope (N/s); the theoretical rate (effective stiffness x pulling velocity) is reported separately when both are supplied, never substituted; requires an explicit time axis.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: N/s
+
+- parameters:
+ - `extension` (positional, required) — Extension result.
+ - `events` (positional, required) — Quantified events.
+ - `window_samples` (keyword_only, 10) — Pre-event window (samples).
+ - `min_samples` (keyword_only, 3) — Minimum samples.
+ - `pulling_velocity` (keyword_only, None) — Pulling velocity (m/s).
+ - `effective_stiffness` (keyword_only, None) — Stiffness (N/m).
+
+- evidence:
+ - `tests/validation/fixtures/force_smfs/smfs_reference.json`
+ - `tests/validation/fixtures/force_smfs/smfs_reference.npz`
+ - `tests/validation/test_force_smfs_validation.py`
+ - `tests/core/test_force_smfs.py`
+
+## FORCE.SMFS.MODEL.COMPARE
+
+- operation_id: `force.smfs.model.compare`
+- public_name: `compare_polymer_models`
+- public_import: `spmkit.core.analysis:compare_polymer_models`
+- family: FORCE
+- maturity: SOFTWARE_VERIFIED
+- status: stable
+- reference: SPMKit native (AICc comparison of the polymer models over the identical obs)
+- evidence profile: `NATIVE_SPMKIT_DESIGNED_HEURISTIC`
+
+- contract: AICc comparison of the polymer models over the identical observation set with relative weights only; Delta AICc < 4 ambiguity; failed models retained as warnings; no molecular-truth claim; the recommendation policy is SOFTWARE_VERIFIED.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: not_applicable
+
+- parameters:
+ - `extension` (positional, required) — Molecular extension (m).
+ - `force` (positional, required) — Retract force (N).
+ - `models` (keyword_only, ['worm_like_chain', 'extensible_worm_like_chain', 'freely_jointed_chain', 'extensible_freely_jointed_chain']) — Candidate models.
+ - `temperature` (keyword_only, 298.0) — Temperature (K).
+
+- evidence:
+ - `tests/validation/fixtures/force_smfs/smfs_reference.json`
+ - `tests/validation/fixtures/force_smfs/smfs_reference.npz`
+ - `tests/validation/test_force_smfs_validation.py`
+ - `tests/core/test_force_smfs.py`
+
+## FORCE.SMFS.MODEL.EXTENSIBLE_FJC
+
+- operation_id: `force.smfs.model.extensible_fjc`
+- public_name: `fit_extensible_freely_jointed_chain`
+- public_import: `spmkit.core.analysis:fit_extensible_freely_jointed_chain`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Extensible FJC fit (Lc, b, Sk) in the extension space x/Lc =)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Extensible FJC fit (Lc, b, Sk) in the extension space x/Lc = L(y) + F/Sk with Sk the segment stretch force (N); Sk -> inf reduces to the FJC; Lc/b within 2%/5% on clean phantoms; the stretch scale is weakly identifiable from a single section (documented).
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: m
+
+- parameters:
+ - `extension` (positional, required) — Molecular extension (m).
+ - `force` (positional, required) — Retract force (N).
+ - `temperature` (keyword_only, 298.0) — Temperature (K).
+ - `Lc_initial` (keyword_only, None) — Contour start (m).
+ - `b_initial` (keyword_only, None) — Kuhn length start (m).
+ - `Sk_initial` (keyword_only, None) — Stretch force start (N).
+
+- evidence:
+ - `tests/validation/fixtures/force_smfs/smfs_reference.json`
+ - `tests/validation/fixtures/force_smfs/smfs_reference.npz`
+ - `tests/validation/test_force_smfs_validation.py`
+ - `tests/core/test_force_smfs.py`
+
+## FORCE.SMFS.MODEL.EXTENSIBLE_WLC
+
+- operation_id: `force.smfs.model.extensible_wlc`
+- public_name: `fit_extensible_worm_like_chain`
+- public_import: `spmkit.core.analysis:fit_extensible_worm_like_chain`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Implicit extensible WLC fit (Lc, Lp, S) with the Odijk-style)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Implicit extensible WLC fit (Lc, Lp, S) with the Odijk-style convention F = (k_BT/Lp)[1/(4(1-x/Lc+F/S)^2) - 1/4 + x/Lc - F/S], solved per point by brentq with a force-scale xtol; S -> inf reduces to the WLC; Lc within 5% and Lp within 20% on clean phantoms; the stretch modulus is weakly identifiable from a single section (documented).
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: m
+
+- parameters:
+ - `extension` (positional, required) — Molecular extension (m).
+ - `force` (positional, required) — Retract force (N).
+ - `temperature` (keyword_only, 298.0) — Temperature (K).
+ - `Lc_initial` (keyword_only, None) — Contour start (m).
+ - `Lp_initial` (keyword_only, None) — Persistence start (m).
+ - `S_initial` (keyword_only, None) — Stretch modulus start (N).
+
+- evidence:
+ - `tests/validation/fixtures/force_smfs/smfs_reference.json`
+ - `tests/validation/fixtures/force_smfs/smfs_reference.npz`
+ - `tests/validation/test_force_smfs_validation.py`
+ - `tests/core/test_force_smfs.py`
+
+## FORCE.SMFS.MODEL.FJC
+
+- operation_id: `force.smfs.model.fjc`
+- public_name: `fit_freely_jointed_chain`
+- public_import: `spmkit.core.analysis:fit_freely_jointed_chain`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (FJC fit (Lc, b) in the extension space x/Lc = coth(y) - 1/y )
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: FJC fit (Lc, b) in the extension space x/Lc = coth(y) - 1/y with y = F b/k_BT (stable Langevin), separable closed-form Lc per candidate b; Lc/b within 2%/5% on clean phantoms; the persistence length is reported as Lp = b/2.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: m
+
+- parameters:
+ - `extension` (positional, required) — Molecular extension (m).
+ - `force` (positional, required) — Retract force (N).
+ - `temperature` (keyword_only, 298.0) — Temperature (K).
+ - `Lc_initial` (keyword_only, None) — Contour start (m).
+ - `b_initial` (keyword_only, None) — Kuhn length start (m).
+
+- evidence:
+ - `tests/validation/fixtures/force_smfs/smfs_reference.json`
+ - `tests/validation/fixtures/force_smfs/smfs_reference.npz`
+ - `tests/validation/test_force_smfs_validation.py`
+ - `tests/core/test_force_smfs.py`
+
+## FORCE.SMFS.MODEL.WLC
+
+- operation_id: `force.smfs.model.wlc`
+- public_name: `fit_worm_like_chain`
+- public_import: `spmkit.core.analysis:fit_worm_like_chain`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (WLC fit (Lc, Lp) by the Marko-Siggia loading relation F = (k)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: WLC fit (Lc, Lp) by the Marko-Siggia loading relation F = (k_BT/Lp)[1/(4(1-x/Lc)^2) - 1/4 + x/Lc] with a separable closed-form Lp per candidate Lc (deterministic 1-D search); Lc/Lp within 2%/5% on clean phantoms; the singular domain (x >= Lc) is typed POLYMER_SINGULARITY.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: m
+
+- parameters:
+ - `extension` (positional, required) — Molecular extension (m).
+ - `force` (positional, required) — Retract force (N).
+ - `temperature` (keyword_only, 298.0) — Temperature (K).
+ - `Lc_initial` (keyword_only, None) — Contour start (m).
+ - `Lp_initial` (keyword_only, None) — Persistence start (m).
+
+- evidence:
+ - `tests/validation/fixtures/force_smfs/smfs_reference.json`
+ - `tests/validation/fixtures/force_smfs/smfs_reference.npz`
+ - `tests/validation/test_force_smfs_validation.py`
+ - `tests/core/test_force_smfs.py`
+
+## FORCE.SMFS.POPULATION
+
+- operation_id: `force.smfs.population`
+- public_name: `analyze_smfs_event_population`
+- public_import: `spmkit.core.analysis:analyze_smfs_event_population`
+- family: FORCE
+- maturity: SOFTWARE_VERIFIED
+- status: stable
+- reference: SPMKit native (Aggregate event records into a population: rupture-force, co)
+- evidence profile: `NATIVE_SPMKIT_DESIGNED_HEURISTIC`
+
+- contract: Aggregate event records into a population: rupture-force, contour-increment and loading-rate summaries; deterministic grouping (none or loading_rate_decade) with raw assignments exposed; ambiguity retained for small populations; no molecular-identity claim; the grouping policy is SOFTWARE_VERIFIED.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: not_applicable
+
+- parameters:
+ - `event_records` (positional, required) — Event records.
+ - `group_by` (keyword_only, 'loading_rate_decade' values=['none', 'loading_rate_decade']) — Grouping policy.
+ - `n_groups` (keyword_only, 4) — Number of groups.
+ - `force_levels` (keyword_only, None) — Force levels (N).
+
+- evidence:
+ - `tests/validation/fixtures/force_smfs/smfs_reference.json`
+ - `tests/validation/fixtures/force_smfs/smfs_reference.npz`
+ - `tests/validation/test_force_smfs_validation.py`
+ - `tests/core/test_force_smfs.py`
+
+## FORCE.SMFS.WINDOW.SELECT
+
+- operation_id: `force.smfs.window.select`
+- public_name: `select_smfs_fit_windows`
+- public_import: `spmkit.core.analysis:select_smfs_fit_windows`
+- family: FORCE
+- maturity: SOFTWARE_VERIFIED
+- status: stable
+- reference: SPMKit native (Explicit polymer fit window on the molecular extension axis:)
+- evidence profile: `NATIVE_SPMKIT_DESIGNED_HEURISTIC`
+
+- contract: Explicit polymer fit window on the molecular extension axis: negative extensions always excluded (the polymer domain starts at the tether zero), extension/force bounds, minimum points; EMPTY_WINDOW and INSUFFICIENT_POINTS typed; the window policy is SOFTWARE_VERIFIED.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: m / N
+
+- parameters:
+ - `extension` (positional, required) — Molecular extension (m).
+ - `force` (positional, required) — Retract force (N).
+ - `min_extension` (keyword_only, None) — Lower extension bound (m).
+ - `max_extension` (keyword_only, None) — Upper extension bound (m).
+ - `min_force` (keyword_only, None) — Lower force bound (N).
+ - `max_force` (keyword_only, None) — Upper force bound (N).
+ - `min_points` (keyword_only, 10) — Minimum window size.
+ - `window_label` (keyword_only, None) — Window identifier.
+
+- evidence:
+ - `tests/validation/fixtures/force_smfs/smfs_reference.json`
+ - `tests/validation/fixtures/force_smfs/smfs_reference.npz`
+ - `tests/validation/test_force_smfs_validation.py`
+ - `tests/core/test_force_smfs.py`
+
+## FORCE.VISCO.CONTACT.LEE_RADOK
+
+- operation_id: `force.visco.contact.lee_radok`
+- public_name: `fit_lee_radok_sphere`
+- public_import: `spmkit.core.analysis:fit_lee_radok_sphere`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Fits the SLS relaxation modulus through the Lee-Radok spheri)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Fits the SLS relaxation modulus through the Lee-Radok spherical hereditary integral on the monotonic loading region: F(t) = c int_0^t E(t - t') d/dt' delta(t')^1.5 dt'; the contact radius must not decrease (LEE_RADOK_NONMONOTONIC typed); loading-only validity; the loading history is trimmed to the contact (indentation >= 0, documented); recovery within ~40% E0/E_inf and ~50% tau on clean phantoms (the loading curve carries less information than a hold).
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: Pa
+
+- parameters:
+ - `prepared` (positional, required) — FS-F1 prepared curve.
+ - `protocol` (positional, required) — Protocol result.
+ - `tip_radius` (keyword_only, required) — Tip radius (m).
+ - `poisson` (keyword_only, 0.3 bounds=[0.0, 0.5]) — Poisson ratio.
+ - `E0_initial` (keyword_only, 1000000.0) — Modulus start (Pa).
+ - `E_inf_initial` (keyword_only, 500000.0) — Equilibrium modulus start (Pa).
+ - `tau_initial` (keyword_only, 1.0) — Relaxation time start (s).
+
+- evidence:
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json`
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz`
+ - `tests/validation/test_force_viscoelasticity_validation.py`
+ - `tests/core/test_force_viscoelasticity.py`
+
+## FORCE.VISCO.CONTACT.TING
+
+- operation_id: `force.visco.contact.ting`
+- public_name: `fit_ting_sphere`
+- public_import: `spmkit.core.analysis:fit_ting_sphere`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Fits the SLS relaxation modulus through the Ting spherical i)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Fits the SLS relaxation modulus through the Ting spherical integral with contact-time memory: loading = Lee-Radok; unloading F(t) = c int_0^{t1(t)} E(t - t') d/dt' delta(t')^1.5 dt' with delta(t1(t)) = delta(t) on the monotone loading portion; the loading history is trimmed to the contact and the unloading history truncated at the contact (documented); TING_HISTORY_UNAVAILABLE typed when the history cannot be reconstructed; the production quadrature is the first-order increment rule (parity with the substep oracle 0.5%).
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: Pa
+
+- parameters:
+ - `prepared` (positional, required) — FS-F1 prepared curve.
+ - `protocol` (positional, required) — Protocol result.
+ - `tip_radius` (keyword_only, required) — Tip radius (m).
+ - `poisson` (keyword_only, 0.3 bounds=[0.0, 0.5]) — Poisson ratio.
+ - `E0_initial` (keyword_only, 1000000.0) — Modulus start (Pa).
+ - `E_inf_initial` (keyword_only, 500000.0) — Equilibrium modulus start (Pa).
+ - `tau_initial` (keyword_only, 1.0) — Relaxation time start (s).
+
+- evidence:
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json`
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz`
+ - `tests/validation/test_force_viscoelasticity_validation.py`
+ - `tests/core/test_force_viscoelasticity.py`
+
+## FORCE.VISCO.CREEP.EXTRACT
+
+- operation_id: `force.visco.creep.extract`
+- public_name: `extract_creep_compliance`
+- public_import: `spmkit.core.analysis:extract_creep_compliance`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Extracts the creep compliance increment of a force hold: (in)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Extracts the creep compliance increment of a force hold: (indentation(t) - indentation(0))/F_hold on the relative hold time; the increment is robust to the contact-coordinate precision (the absolute level is carried in indentation_at_hold_start); missing hold raises EMPTY_REGION; zero held force raises INVALID_RESPONSE.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: s / N / m
+
+- parameters:
+ - `prepared` (positional, required) — FS-F1 prepared curve.
+ - `protocol` (positional, required) — Protocol result.
+ - `segment` (keyword_only, 'extend') — Segment name.
+ - `hold_kind` (keyword_only, 'hold_force') — Hold region kind.
+ - `hold_force_median` (keyword_only, True) — Median (vs mean) held force.
+
+- evidence:
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json`
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz`
+ - `tests/validation/test_force_viscoelasticity_validation.py`
+ - `tests/core/test_force_viscoelasticity.py`
+
+## FORCE.VISCO.MODEL.COMPARE
+
+- operation_id: `force.visco.model.compare`
+- public_name: `compare_viscoelastic_models`
+- public_import: `spmkit.core.analysis:compare_viscoelastic_models`
+- family: FORCE
+- maturity: SOFTWARE_VERIFIED
+- status: stable
+- reference: SPMKit native (Model-relative AICc comparison over identical observations w)
+- evidence profile: `NATIVE_SPMKIT_DESIGNED_HEURISTIC`
+
+- contract: Model-relative AICc comparison over identical observations with the finite-sample correction; Delta AICc < 4 ambiguity; failed candidates retained as warnings; weights are relative support, never a probability of physical correctness; the recommendation policy is SOFTWARE_VERIFIED.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: not_applicable
+
+- parameters:
+ - `response` (positional, required) — Relaxation or creep response.
+ - `models` (keyword_only, None) — Candidate models.
+ - `tip_radius` (keyword_only, None) — Tip radius (m).
+ - `poisson` (keyword_only, 0.3 bounds=[0.0, 0.5]) — Poisson ratio.
+ - `n_terms` (keyword_only, 2) — Prony terms for the generalized Maxwell.
+ - `t_ref` (keyword_only, None) — Reference time for the power law.
+
+- evidence:
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json`
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz`
+ - `tests/validation/test_force_viscoelasticity_validation.py`
+ - `tests/core/test_force_viscoelasticity.py`
+
+## FORCE.VISCO.MODEL.GENERALIZED_MAXWELL
+
+- operation_id: `force.visco.model.generalized_maxwell`
+- public_name: `fit_generalized_maxwell`
+- public_import: `spmkit.core.analysis:fit_generalized_maxwell`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Prony normalized relaxation fit n(t) = 1 - sum(alpha) + sum()
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Prony normalized relaxation fit n(t) = 1 - sum(alpha) + sum(alpha_i exp(-t/tau_i)) with alpha_i >= 0, sum(alpha) <= 1, tau_i > 0, deterministic ordering by ascending tau; duplicate relaxation times are rejected typed (PRONY_DUPLICATE_TAU); no claim that the recovered spectrum is unique; nearly equal time constants carry a bounded-identifiability warning.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: s
+
+- parameters:
+ - `response` (positional, required) — Relaxation response.
+ - `n_terms` (keyword_only, 2 bounds=[1, 8]) — Number of Prony terms.
+ - `tip_radius` (keyword_only, None) — Tip radius (m).
+ - `poisson` (keyword_only, 0.3 bounds=[0.0, 0.5]) — Poisson ratio.
+
+- evidence:
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json`
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz`
+ - `tests/validation/test_force_viscoelasticity_validation.py`
+ - `tests/core/test_force_viscoelasticity.py`
+
+## FORCE.VISCO.MODEL.KELVIN_VOIGT
+
+- operation_id: `force.visco.model.kelvin_voigt`
+- public_name: `fit_kelvin_voigt`
+- public_import: `spmkit.core.analysis:fit_kelvin_voigt`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Kelvin-Voigt creep fit J(t) = (1/E)(1 - exp(-t/tau)), tau = )
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Kelvin-Voigt creep fit J(t) = (1/E)(1 - exp(-t/tau)), tau = eta/E (retardation time); requires a CreepResponseResult (PROTOCOL_MODEL_MISMATCH typed otherwise); deterministic multi-start least squares; E within 10% and tau within 10% on clean phantoms; the model cannot represent instantaneous stress relaxation (documented).
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: Pa
+
+- parameters:
+ - `response` (positional, required) — Creep response.
+ - `E_initial` (keyword_only, None) — Modulus start (Pa).
+ - `tau_initial` (keyword_only, None) — Retardation time start (s).
+
+- evidence:
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json`
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz`
+ - `tests/validation/test_force_viscoelasticity_validation.py`
+ - `tests/core/test_force_viscoelasticity.py`
+
+## FORCE.VISCO.MODEL.MAXWELL
+
+- operation_id: `force.visco.model.maxwell`
+- public_name: `fit_maxwell`
+- public_import: `spmkit.core.analysis:fit_maxwell`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Maxwell relaxation fit n(t) = exp(-t/tau), tau = eta/E; the )
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Maxwell relaxation fit n(t) = exp(-t/tau), tau = eta/E; the modulus E is recovered only when the tip radius is provided (spherical contact proportionality, documented); tau recovered within 2% on clean phantoms; the model cannot represent bounded solid creep (documented).
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: s / Pa
+
+- parameters:
+ - `response` (positional, required) — Relaxation response.
+ - `tip_radius` (keyword_only, None) — Tip radius (m); enables E.
+ - `poisson` (keyword_only, 0.3 bounds=[0.0, 0.5]) — Poisson ratio.
+
+- evidence:
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json`
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz`
+ - `tests/validation/test_force_viscoelasticity_validation.py`
+ - `tests/core/test_force_viscoelasticity.py`
+
+## FORCE.VISCO.MODEL.POWER_LAW
+
+- operation_id: `force.visco.model.power_law`
+- public_name: `fit_power_law_relaxation`
+- public_import: `spmkit.core.analysis:fit_power_law_relaxation`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Power-law relaxation fit n(t) = (t/t_ref)^(-alpha) with 0 < )
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Power-law relaxation fit n(t) = (t/t_ref)^(-alpha) with 0 < alpha < 1 and an optional equilibrium offset; t = 0 excluded (singularity); t_ref defaults to the first positive hold time and the fit uses t >= t_ref when t_ref is given.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: s
+
+- parameters:
+ - `response` (positional, required) — Relaxation response.
+ - `t_ref` (keyword_only, None) — Reference time (s).
+ - `with_equilibrium` (keyword_only, False) — Add the equilibrium offset.
+ - `tip_radius` (keyword_only, None) — Tip radius (m).
+ - `poisson` (keyword_only, 0.3 bounds=[0.0, 0.5]) — Poisson ratio.
+
+- evidence:
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json`
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz`
+ - `tests/validation/test_force_viscoelasticity_validation.py`
+ - `tests/core/test_force_viscoelasticity.py`
+
+## FORCE.VISCO.MODEL.SLS
+
+- operation_id: `force.visco.model.sls`
+- public_name: `fit_standard_linear_solid`
+- public_import: `spmkit.core.analysis:fit_standard_linear_solid`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Standard linear solid fit on a relaxation response n(t) = 1 )
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Standard linear solid fit on a relaxation response n(t) = 1 - a(1 - exp(-t/tau_relax)) or a creep response increment (dJ)(1 - exp(-t/tau_retard)); both representations are reported with the conversions J0 = 1/E0, J_inf = 1/E_inf, tau_retard = tau_relax * E0/E_inf; absolute moduli need the tip radius for the relaxation form; the creep absolute level is contact-coordinate limited (recovery reported on the increment).
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: Pa / m/N / s
+
+- parameters:
+ - `response` (positional, required) — Relaxation or creep response.
+ - `tip_radius` (keyword_only, None) — Tip radius (m).
+ - `poisson` (keyword_only, 0.3 bounds=[0.0, 0.5]) — Poisson ratio.
+ - `tau_initial` (keyword_only, None) — Time-constant start (s).
+
+- evidence:
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json`
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz`
+ - `tests/validation/test_force_viscoelasticity_validation.py`
+ - `tests/core/test_force_viscoelasticity.py`
+
+## FORCE.VISCO.PROTOCOL.IDENTIFY
+
+- operation_id: `force.visco.protocol.identify`
+- public_name: `identify_viscoelastic_protocol`
+- public_import: `spmkit.core.analysis:identify_viscoelastic_protocol`
+- family: FORCE
+- maturity: SOFTWARE_VERIFIED
+- status: stable
+- reference: SPMKit native (Identifies the viscoelastic protocol of a force curve: rate-)
+- evidence profile: `NATIVE_SPMKIT_DESIGNED_HEURISTIC`
+
+- contract: Identifies the viscoelastic protocol of a force curve: rate-region classification (median-of-nonzero-rate thresholds) into LOADING_RAMP, UNLOADING_RAMP, DISPLACEMENT_HOLD, FORCE_HOLD, CREEP, STRESS_RELAXATION, TRIANGULAR_LOADING, INSUFFICIENT_PROTOCOL, AMBIGUOUS_PROTOCOL; trusted instrument labels in curve.metadata take precedence; a displacement hold with a decaying force is STRESS_RELAXATION, a force hold with a drifting displacement is CREEP; missing time raises MISSING_TIME (reconstructed clock only via assume_uniform_rate); duplicate time samples raise DUPLICATE_TIMESTAMPS; the JPK/NID readers do not populate segment time, so time-domain analysis requires an explicit time axis or an explicitly requested known-rate reconstruction (no automatic general reader time-domain analysis is claimed).
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: not_applicable
+
+- parameters:
+ - `curve` (positional, required) — Force curve.
+ - `contact_index` (keyword_only, None) — Contact index (height axis).
+ - `contact_coordinate` (keyword_only, None) — Contact coordinate (m).
+ - `rate_threshold` (keyword_only, 0.05) — Relative rate threshold.
+ - `min_hold_points` (keyword_only, 5) — Minimum hold run length.
+ - `min_hold_fraction` (keyword_only, 0.05) — Minimum hold fraction.
+ - `assume_uniform_rate` (keyword_only, None) — Reconstructed clock (s/sample).
+ - `force_threshold_fraction` (keyword_only, 0.1) — Relaxation decay threshold.
+
+- evidence:
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json`
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz`
+ - `tests/validation/test_force_viscoelasticity_validation.py`
+ - `tests/core/test_force_viscoelasticity.py`
+
+- known deviations:
+ - the protocol recommendation and ambiguity policy is SOFTWARE_VERIFIED
+
+## FORCE.VISCO.RATE.INDENTATION
+
+- operation_id: `force.visco.rate.indentation`
+- public_name: `compute_indentation_rate`
+- public_import: `spmkit.core.analysis:compute_indentation_rate`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Robust indentation and force rate of one protocol region: me)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Robust indentation and force rate of one protocol region: median of the local finite-difference rates with the 25-75 percentile spread; region located via the protocol result; missing region raises EMPTY_REGION; requires a valid time axis (the JPK/NID readers do not populate segment time; provide one or use an explicitly requested known-rate reconstruction); units m/s and N/s.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: m/s
+
+- parameters:
+ - `prepared` (positional, required) — FS-F1 prepared curve.
+ - `protocol` (positional, required) — Protocol result.
+ - `region` (keyword_only, 'loading') — Region kind.
+ - `segment` (keyword_only, 'extend') — Segment name.
+
+- evidence:
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json`
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz`
+ - `tests/validation/test_force_viscoelasticity_validation.py`
+ - `tests/core/test_force_viscoelasticity.py`
+
+## FORCE.VISCO.RELAXATION.EXTRACT
+
+- operation_id: `force.visco.relaxation.extract`
+- public_name: `extract_stress_relaxation`
+- public_import: `spmkit.core.analysis:extract_stress_relaxation`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Extracts the normalized stress-relaxation response of a disp)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Extracts the normalized stress-relaxation response of a displacement hold: F(t)/F(t0) on the relative hold time with the hold indentation and force histories; equilibrium-force estimate = mean of the last tail fraction (documented estimate, not a guaranteed equilibrium); missing hold raises EMPTY_REGION; zero hold-start force raises INVALID_RESPONSE.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: s / m / N
+
+- parameters:
+ - `prepared` (positional, required) — FS-F1 prepared curve.
+ - `protocol` (positional, required) — Protocol result.
+ - `segment` (keyword_only, 'extend') — Segment name.
+ - `hold_kind` (keyword_only, 'hold_displacement') — Hold region kind.
+ - `equilibrium_tail_fraction` (keyword_only, 0.1) — Equilibrium tail fraction.
+
+- evidence:
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json`
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz`
+ - `tests/validation/test_force_viscoelasticity_validation.py`
+ - `tests/core/test_force_viscoelasticity.py`
+
+## FORCE.VISCO.SENSITIVITY
+
+- operation_id: `force.visco.sensitivity`
+- public_name: `analyze_viscoelastic_sensitivity`
+- public_import: `spmkit.core.analysis:analyze_viscoelastic_sensitivity`
+- family: FORCE
+- maturity: SOFTWARE_VERIFIED
+- status: stable
+- reference: SPMKit native (Deterministic multiverse over contact offsets, hold-boundary)
+- evidence profile: `NATIVE_SPMKIT_DESIGNED_HEURISTIC`
+
+- contract: Deterministic multiverse over contact offsets, hold-boundary offsets and equilibrium-tail fractions (bounded at max_configurations) for the SLS fit on the extracted response; one-at-a-time contact/boundary/window indices relative to the baseline configuration and a dominant-source classification (contact / boundary / window / none at the 20% threshold); raw configurations and failures exposed; the interpretation is SOFTWARE_VERIFIED.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: not_applicable
+
+- parameters:
+ - `curve` (positional, required) — Force curve.
+ - `prepared` (positional, required) — FS-F1 prepared curve.
+ - `protocol` (keyword_only, None) — Protocol result.
+ - `contact_offsets` (keyword_only, [-2, 0, 2]) — Contact offsets (samples).
+ - `boundary_offsets` (keyword_only, [-3, 0, 3]) — Hold-boundary offsets.
+ - `equilibrium_tail_fractions` (keyword_only, [0.05, 0.1, 0.2]) — Equilibrium tail fractions.
+ - `max_configurations` (keyword_only, 96) — Multiverse bound.
+ - `tip_radius` (keyword_only, None) — Tip radius (m).
+ - `poisson` (keyword_only, 0.3 bounds=[0.0, 0.5]) — Poisson ratio.
+
+- evidence:
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json`
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz`
+ - `tests/validation/test_force_viscoelasticity_validation.py`
+ - `tests/core/test_force_viscoelasticity.py`
+
+## FORCE.VISCO.VOLUME
+
+- operation_id: `force.visco.volume`
+- public_name: `fit_force_volume_viscoelasticity`
+- public_import: `spmkit.core.analysis:fit_force_volume_viscoelasticity`
+- family: FORCE
+- maturity: SOFTWARE_VERIFIED
+- status: stable
+- reference: SPMKit native (Per-curve identify -> prepare -> extract -> SLS mapping over)
+- evidence profile: `NATIVE_SPMKIT_DESIGNED_HEURISTIC`
+
+- contract: Per-curve identify -> prepare -> extract -> SLS mapping over a ForceVolume: modulus_0/modulus_inf/viscosity/relaxation-time maps, model/ambiguity/sensitivity/protocol maps and an explicit failed mask with per-index reasons (nothing silently dropped); deterministic replay; viscosity = E0 * a * tau_relax (SLS dashpot estimate, documented model quantity).
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: Pa / Pa*s / s
+
+- parameters:
+ - `volume` (positional, required) — Force volume.
+ - `tip_radius` (keyword_only, None) — Tip radius (m).
+ - `poisson` (keyword_only, 0.3 bounds=[0.0, 0.5]) — Poisson ratio.
+ - `min_hold_points` (keyword_only, 5) — Minimum hold run length.
+
+- evidence:
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json`
+ - `tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz`
+ - `tests/validation/test_force_viscoelasticity_validation.py`
+ - `tests/core/test_force_viscoelasticity.py`
+
+## FORCE.VOLUME.MECHANICS
+
+- operation_id: `force.volume.mechanics`
+- public_name: `fit_force_volume_mechanics`
+- public_import: `spmkit.core.analysis:fit_force_volume_mechanics`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Per-curve mechanics mapping)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Applies prepare -> indentation -> window -> model comparison to every curve of a ForceVolume; modulus/adhesion maps, chosen model map, quality map; failed curves explicitly masked (failed_mask + provenance reasons), never silently dropped; deterministic replay; units Pa / N.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: Pa
+
+- parameters:
+ - `volume` (positional, required) — Force volume.
+ - `tip_radius` (keyword_only, 1e-08) — Tip radius (m).
+ - `poisson` (keyword_only, 0.3 bounds=[0.0, 0.5]) — Poisson ratio.
+ - `half_angle` (keyword_only, 0.3490658503988659) — Cone half-angle (rad).
+ - `models` (keyword_only, ['hertz_sphere', 'dmt']) — Candidate models.
+ - `min_points` (keyword_only, 20) — Minimum window size per curve.
+
+- evidence:
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.json`
+ - `tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz`
+ - `tests/validation/test_force_mechanics_validation.py`
+ - `tests/core/test_force_mechanics.py`
+
+## FORCE.WORK.INTEGRATE
+
+- operation_id: `force.work.integrate`
+- public_name: `integrate_force_work`
+- public_import: `spmkit.core.analysis:integrate_force_work`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Force work integration)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Force integrated over tip-sample separation on the common overlap domain (contact to min of maxima); monotone interpolation; trapezoidal arithmetic; work of adhesion = retract integral; hysteresis = approach - retract; units J; INSUFFICIENT_OVERLAP and NONMONOTONIC_COORDINATE typed failures.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: J
+
+- parameters:
+ - `curve` (positional, required) — Force curve.
+ - `contact` (positional, required) — Contact point result.
+ - `domain` (keyword_only, 'tip_position' values=['tip_position', 'height']) — Integration domain.
+
+- evidence:
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.json`
+ - `tests/validation/fixtures/force_foundation/force_phantoms_reference.npz`
+ - `tests/validation/fixtures/force_foundation/force_foundation_reference.json`
+ - `tests/validation/test_force_foundation_validation.py`
+ - `tests/core/test_force_foundation.py`
+
+- known deviations:
+ - real tip-sample separation is often non-monotone; the operation raises NONMONOTONIC_COORDINATE instead of fabricating a value
+
+## FORCE.WORK.PATH_INTEGRATE
+
+- operation_id: `force.work.path_integrate`
+- public_name: `integrate_force_path_work`
+- public_import: `spmkit.core.analysis:integrate_force_path_work`
+- family: FORCE
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Acquisition-path force work)
+- evidence profile: `NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE`
+
+- contract: Signed path work over a single trajectory in acquisition order: W = sum_i 0.5*(F_i+F_{i+1})*(z_{i+1}-z_i) with deterministic float64 accumulation; signed dz retained (local reversals and closed loops contribute their signed path work; repeated coordinates contribute zero; translation-invariant; acquisition reversal flips sign); no sorting, no abs(), no smoothing, no point deletion; complete CoordinatePathDiagnostics; classification_tolerance only classifies (direction, reversal counts), never alters the integral; NONFINITE_DATA, LENGTH_MISMATCH, INSUFFICIENT_SAMPLES, MISSING_COORDINATE typed failures.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: object
+ - units: J
+
+- parameters:
+ - `coordinate` (positional, required) — Coordinate axis (e.g. tip-sample separation) in acquisition order.
+ - `force` (positional, required) — Calibrated force samples (same length as coordinate).
+ - `coordinate_unit` (keyword_only, 'm') — Coordinate unit label.
+ - `force_unit` (keyword_only, 'N') — Force unit label.
+ - `classification_tolerance` (keyword_only, '0.0') — Classification-only tolerance in SI coordinate units (never used to alter the integral).
+ - `provenance` (keyword_only, None) — Provenance metadata.
+
+- evidence:
+ - `tests/core/test_force_path_work.py`
+
+- known deviations:
+ - path work is a path integral, not thermodynamic work; closed/ambiguous paths warn and split forward/backward contributions by step sign
+ - absolute_accumulated_work is not dissipation energy
+
+## IMG.FILTER.GAUSSIAN
+
+- operation_id: `img.filter.gaussian`
+- public_name: `gwyddion_gaussian_filter`
+- public_import: `spmkit.core.analysis:gwyddion_gaussian_filter`
+- family: IMG.FILTER
+- maturity: CROSS_VALIDATED
+- status: stable
+- reference: Gwydion 2.71 (Gaussian Filter)
+- evidence profile: `COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION`
+
+- contract: Separable Gaussian smoothing with sigma in pixels; kernel resolution 2*ceil(5*sigma)+1 capped at 3*min(xres,yres) and forced odd; mirror borders; horizontal-then-vertical passes; sequential-sum reciprocal normalization (not forced to exactly 1.0).
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: mirror
+ - mutation: returns_new
+ - result: SPMChannel
+ - units: preserved
+
+- parameters:
+ - `channel` (positional, required) — Finite two-dimensional input channel.
+ - `sigma` (keyword_only, 5.0 bounds=[0.01, 40.0]) — Gaussian standard deviation in pixels.
+
+- evidence:
+ - `tests/validation/fixtures/gwyddion/neighborhood_filters/neighborhood_filters_reference.json`
+ - `tests/validation/fixtures/gwyddion/neighborhood_filters/neighborhood_filters_reference.npz`
+ - `tests/validation/test_gwyddion_neighborhood_filters_production_parity.py`
+ - `tests/core/test_gwyddion_neighborhood_filters.py`
+
+- known deviations:
+ - Gaussian constant-field preservation is not bitwise guaranteed; kernel-normalization rounding (~1e-15) is preserved.
+
+## IMG.FILTER.GRADIENT_DIRECTION
+
+- operation_id: `img.filter.gradient_direction`
+- public_name: `gradient_direction`
+- public_import: `spmkit.core.analysis:gradient_direction`
+- family: IMG.FILTER
+- maturity: NUMERICALLY_VERIFIED
+- status: stable
+- reference: SPMKit native (Gradient Direction (native analytical composite))
+- evidence profile: `NATIVE_SPMKIT_ANALYTICAL_COMPOSITE`
+
+- contract: Native gradient direction atan2(gy, gx) over explicit required component fields; radians; range (-pi, pi]; C99 signed-zero axes; zero vector -> +0.0; output unit rad; native analytical composite, not a Gwydion parity target; components never mutated.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: clipped
+ - mutation: returns_new
+ - result: SPMChannel
+ - units: rad
+
+- parameters:
+ - `gx` (positional, required) — Horizontal derivative component field (finite 2D SPMChannel).
+ - `gy` (positional, required) — Vertical derivative component field (finite 2D SPMChannel).
+
+- evidence:
+ - `tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.json`
+ - `tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.npz`
+ - `tests/validation/fixtures/gwyddion/derivative_filters/oracle_gradient_direction_native.py`
+ - `tests/validation/test_gwyddion_derivative_filters_production_parity.py`
+ - `tests/core/test_gwyddion_derivative_filters.py`
+
+- known deviations:
+ - numpy.arctan2 may differ from the compiled C atan2 profile by up to ~1 ULP on some inputs; characterized by parity tests, not bitwise parity.
+
+## IMG.FILTER.GRADIENT_MAGNITUDE
+
+- operation_id: `img.filter.gradient_magnitude`
+- public_name: `gwyddion_gradient_magnitude`
+- public_import: `spmkit.core.analysis:gwyddion_gradient_magnitude`
+- family: IMG.FILTER
+- maturity: CROSS_VALIDATED
+- status: stable
+- reference: Gwydion 2.71 (Gradient Magnitude (hypot of component fields))
+- evidence profile: `COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_DERIVATIVE_KERNEL_PROFILE`
+
+- contract: Gradient magnitude hypot(gx, gy) over explicit required component fields; reproduces the frozen hypot-of-fields orchestration; overflow/underflow-safe; +0.0 for all signed-zero component combinations; component unit retained; components never mutated.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: clipped
+ - mutation: returns_new
+ - result: SPMChannel
+ - units: preserved
+
+- parameters:
+ - `gx` (positional, required) — Horizontal derivative component field (finite 2D SPMChannel).
+ - `gy` (positional, required) — Vertical derivative component field (finite 2D SPMChannel).
+
+- evidence:
+ - `tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.json`
+ - `tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.npz`
+ - `tests/validation/test_gwyddion_derivative_filters_production_parity.py`
+ - `tests/core/test_gwyddion_derivative_filters.py`
+
+- known deviations:
+ - Bitwise parity is bounded to the frozen platform profile x86-64 / glibc / hypot@GLIBC_2.35; no cross-libc or cross-architecture bitwise guarantee; non-negativity and component-swap symmetry hold relationally on every platform.
+
+## IMG.FILTER.MEDIAN
+
+- operation_id: `img.filter.median`
+- public_name: `gwyddion_median_filter`
+- public_import: `spmkit.core.analysis:gwyddion_median_filter`
+- family: IMG.FILTER
+- maturity: CROSS_VALIDATED
+- status: stable
+- reference: Gwydion 2.71 (disc Median Filter)
+- evidence profile: `COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION`
+
+- contract: Disc median filter with footprint side `size` (2..31, even sizes valid); ellipse-inscribed footprint; upper median rank n//2; EXTEND nearest-constant borders.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: extend
+ - mutation: returns_new
+ - result: SPMChannel
+ - units: preserved
+
+- parameters:
+ - `channel` (positional, required) — Finite two-dimensional input channel.
+ - `size` (keyword_only, 5 bounds=[2, 31]) — Footprint side length (not a radius).
+
+- evidence:
+ - `tests/validation/fixtures/gwyddion/neighborhood_filters/neighborhood_filters_reference.json`
+ - `tests/validation/fixtures/gwyddion/neighborhood_filters/neighborhood_filters_reference.npz`
+ - `tests/validation/test_gwyddion_neighborhood_filters_production_parity.py`
+ - `tests/core/test_gwyddion_neighborhood_filters.py`
+
+## IMG.FILTER.PREWITT_X
+
+- operation_id: `img.filter.prewitt_x`
+- public_name: `gwyddion_prewitt_x`
+- public_import: `spmkit.core.analysis:gwyddion_prewitt_x`
+- family: IMG.FILTER
+- maturity: CROSS_VALIDATED
+- status: stable
+- reference: Gwydion 2.71 (Prewitt X Filter)
+- evidence profile: `COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_DERIVATIVE_KERNEL_PROFILE`
+
+- contract: Prewitt X (horizontal) pixel-space derivative with the frozen 1/3 coefficients {1/3, 0, -1/3; 1/3, 0, -1/3; 1/3, 0, -1/3}; CLIPPED borders; frozen source sign and orientation; z-unit preserved; finite 2D inputs only; no masks or ROI.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: clipped
+ - mutation: returns_new
+ - result: SPMChannel
+ - units: preserved
+
+- parameters:
+ - `channel` (positional, required) — Finite two-dimensional input channel.
+
+- evidence:
+ - `tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.json`
+ - `tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.npz`
+ - `tests/validation/fixtures/gwyddion/derivative_filters/oracle_derivative_filters_source.py`
+ - `tests/validation/test_gwyddion_derivative_filters_production_parity.py`
+ - `tests/core/test_gwyddion_derivative_filters.py`
+
+## IMG.FILTER.PREWITT_Y
+
+- operation_id: `img.filter.prewitt_y`
+- public_name: `gwyddion_prewitt_y`
+- public_import: `spmkit.core.analysis:gwyddion_prewitt_y`
+- family: IMG.FILTER
+- maturity: CROSS_VALIDATED
+- status: stable
+- reference: Gwydion 2.71 (Prewitt Y Filter)
+- evidence profile: `COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_DERIVATIVE_KERNEL_PROFILE`
+
+- contract: Prewitt Y (vertical) pixel-space derivative with the frozen 1/3 coefficients {1/3, 1/3, 1/3; 0, 0, 0; -1/3, -1/3, -1/3}; CLIPPED borders; frozen source sign and orientation; z-unit preserved; finite 2D inputs only; no masks or ROI.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: clipped
+ - mutation: returns_new
+ - result: SPMChannel
+ - units: preserved
+
+- parameters:
+ - `channel` (positional, required) — Finite two-dimensional input channel.
+
+- evidence:
+ - `tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.json`
+ - `tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.npz`
+ - `tests/validation/fixtures/gwyddion/derivative_filters/oracle_derivative_filters_source.py`
+ - `tests/validation/test_gwyddion_derivative_filters_production_parity.py`
+ - `tests/core/test_gwyddion_derivative_filters.py`
+
+## IMG.FILTER.RANK
+
+- operation_id: `img.filter.rank`
+- public_name: `gwyddion_rank_filter`
+- public_import: `spmkit.core.analysis:gwyddion_rank_filter`
+- family: IMG.FILTER
+- maturity: CROSS_VALIDATED
+- status: stable
+- reference: Gwydion 2.71 (Rank Filter)
+- evidence profile: `COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION`
+
+- contract: Rank filter with pixel radius (1..1024); ellipse-inscribed footprint in a 2*radius+1 square; rank GWY_ROUND(percentile*(n-1)); k=0/k=n-1 minimum/maximum endpoint dispatch; EXTEND borders. Public v1 exposes the primary percentile result only.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: extend
+ - mutation: returns_new
+ - result: SPMChannel
+ - units: preserved
+
+- parameters:
+ - `channel` (positional, required) — Finite two-dimensional input channel.
+ - `radius` (keyword_only, 20 bounds=[1, 1024]) — Pixel radius of the footprint.
+ - `percentile` (keyword_only, 0.75 bounds=[0.0, 1.0]) — Percentile selecting the rank.
+
+- evidence:
+ - `tests/validation/fixtures/gwyddion/neighborhood_filters/neighborhood_filters_reference.json`
+ - `tests/validation/fixtures/gwyddion/neighborhood_filters/neighborhood_filters_reference.npz`
+ - `tests/validation/test_gwyddion_neighborhood_filters_production_parity.py`
+ - `tests/core/test_gwyddion_neighborhood_filters.py`
+
+- known deviations:
+ - Private secondary/both/difference Rank output modes are retained in diagnostics but not exposed publicly in v1.
+
+## IMG.FILTER.SOBEL_X
+
+- operation_id: `img.filter.sobel_x`
+- public_name: `gwyddion_sobel_x`
+- public_import: `spmkit.core.analysis:gwyddion_sobel_x`
+- family: IMG.FILTER
+- maturity: CROSS_VALIDATED
+- status: stable
+- reference: Gwydion 2.71 (Sobel X Filter)
+- evidence profile: `COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_DERIVATIVE_KERNEL_PROFILE`
+
+- contract: Sobel X (horizontal) pixel-space derivative: kernel {0.25, 0, -0.25; 0.5, 0, -0.5; 0.25, 0, -0.25}; CLIPPED borders; frozen source sign (increasing-right X ramp gives negative response), orientation and accumulation order; z-unit preserved; finite 2D inputs only; no masks or ROI.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: clipped
+ - mutation: returns_new
+ - result: SPMChannel
+ - units: preserved
+
+- parameters:
+ - `channel` (positional, required) — Finite two-dimensional input channel.
+
+- evidence:
+ - `tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.json`
+ - `tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.npz`
+ - `tests/validation/fixtures/gwyddion/derivative_filters/oracle_derivative_filters_source.py`
+ - `tests/validation/test_gwyddion_derivative_filters_production_parity.py`
+ - `tests/core/test_gwyddion_derivative_filters.py`
+
+## IMG.FILTER.SOBEL_Y
+
+- operation_id: `img.filter.sobel_y`
+- public_name: `gwyddion_sobel_y`
+- public_import: `spmkit.core.analysis:gwyddion_sobel_y`
+- family: IMG.FILTER
+- maturity: CROSS_VALIDATED
+- status: stable
+- reference: Gwydion 2.71 (Sobel Y Filter)
+- evidence profile: `COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_DERIVATIVE_KERNEL_PROFILE`
+
+- contract: Sobel Y (vertical) pixel-space derivative: kernel {0.25, 0.5, 0.25; 0, 0, 0; -0.25, -0.5, -0.25}; CLIPPED borders; frozen source sign (increasing-down Y ramp gives negative response), orientation and accumulation order; z-unit preserved; finite 2D inputs only; no masks or ROI.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: clipped
+ - mutation: returns_new
+ - result: SPMChannel
+ - units: preserved
+
+- parameters:
+ - `channel` (positional, required) — Finite two-dimensional input channel.
+
+- evidence:
+ - `tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.json`
+ - `tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.npz`
+ - `tests/validation/fixtures/gwyddion/derivative_filters/oracle_derivative_filters_source.py`
+ - `tests/validation/test_gwyddion_derivative_filters_production_parity.py`
+ - `tests/core/test_gwyddion_derivative_filters.py`
+
+## IMG.INTERPOLATION.LAPLACE_UNDER_MASK
+
+- operation_id: `img.interpolation.laplace_under_mask`
+- public_name: `gwydion_interpolate_data_under_mask`
+- public_import: `spmkit.core.analysis:gwydion_interpolate_data_under_mask`
+- family: IMG.INTERPOLATION
+- maturity: CROSS_VALIDATED
+- status: stable
+- reference: Gwydion 2.71 (Interpolate Data Under Mask (Laplace))
+- evidence profile: `COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION`
+
+- contract: Laplace-based interpolation of masked regions; the mask selects pixels to replace; finite two-dimensional input.
+
+- semantics:
+ - mask: mask_input
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: SPMChannel
+ - units: preserved
+
+- parameters:
+ - `channel` (positional, required) — Finite two-dimensional input channel.
+ - `mask` (positional, required) — Mask array selecting pixels to interpolate.
+
+- evidence:
+ - `tests/validation/fixtures/gwydion/scars_laplace/scars_laplace_reference.json`
+ - `tests/validation/fixtures/gwydion/scars_laplace/scars_laplace_reference.npz`
+ - `tests/validation/test_gwydion_laplace_production_parity.py`
+
+## IMG.LEVEL.ALIGN_ROWS_MATCH
+
+- operation_id: `img.level.align_rows_match`
+- public_name: `gwyddion_align_rows_match`
+- public_import: `spmkit.core.analysis:gwyddion_align_rows_match`
+- family: IMG.LEVEL
+- maturity: CROSS_VALIDATED
+- status: stable
+- reference: Gwydion 2.71 (Align Rows Match)
+- evidence profile: `COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION`
+
+- contract: Align Rows Match: adjacent-row shape matching with Gaussian-weighted differences of row differences, cumulative zero-levelled shifts, zero-weight guard (pure vertical offsets may remain uncorrected).
+
+- semantics:
+ - mask: include_exclude_ignore
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: SPMChannel
+ - units: preserved
+
+- parameters:
+ - `channel` (positional, required) — Finite two-dimensional input channel.
+ - `mask` (keyword_only, None) — Optional mask matching the channel shape.
+ - `mask_mode` (keyword_only, 'ignore' values=['exclude', 'include', 'ignore']) — Masking mode.
+ - `direction` (keyword_only, 'horizontal' values=['horizontal', 'vertical']) — Row direction.
+
+- evidence:
+ - `tests/validation/fixtures/gwyddion/align_rows_remaining/align_rows_remaining_reference.json`
+ - `tests/validation/fixtures/gwyddion/align_rows_remaining/align_rows_remaining_reference.npz`
+ - `tests/validation/test_gwydion_align_rows_remaining_production_parity.py`
+
+## IMG.LEVEL.ALIGN_ROWS_MODUS
+
+- operation_id: `img.level.align_rows_modus`
+- public_name: `gwyddion_align_rows_modus`
+- public_import: `spmkit.core.analysis:gwyddion_align_rows_modus`
+- family: IMG.LEVEL
+- maturity: CROSS_VALIDATED
+- status: stable
+- reference: Gwydion 2.71 (Align Rows Modus)
+- evidence profile: `COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION`
+
+- contract: Align Rows Modus: robust row-centre statistic (global masked-median fallback, upper median for fewer than nine retained samples, narrowest sqrt-count range window otherwise), zero-levelled shifts.
+
+- semantics:
+ - mask: include_exclude_ignore
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: SPMChannel
+ - units: preserved
+
+- parameters:
+ - `channel` (positional, required) — Finite two-dimensional input channel.
+ - `mask` (keyword_only, None) — Optional mask matching the channel shape.
+ - `mask_mode` (keyword_only, 'ignore' values=['exclude', 'include', 'ignore']) — Masking mode.
+ - `direction` (keyword_only, 'horizontal' values=['horizontal', 'vertical']) — Row direction.
+
+- evidence:
+ - `tests/validation/fixtures/gwyddion/align_rows_remaining/align_rows_remaining_reference.json`
+ - `tests/validation/fixtures/gwyddion/align_rows_remaining/align_rows_remaining_reference.npz`
+ - `tests/validation/test_gwydion_align_rows_remaining_production_parity.py`
+
+## IMG.LEVEL.ALIGN_ROWS_POLYNOMIAL
+
+- operation_id: `img.level.align_rows_polynomial`
+- public_name: `gwyddion_align_rows_polynomial`
+- public_import: `spmkit.core.analysis:gwyddion_align_rows_polynomial`
+- family: IMG.LEVEL
+- maturity: CROSS_VALIDATED
+- status: stable
+- reference: Gwydion 2.71 (Align Rows Polynomial)
+- evidence profile: `COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION`
+
+- contract: Align Rows Polynomial: degree 0 uses the trim-fraction-zero row-shift path; degree >=1 fits each row independently on centred x with a packed Cholesky solve and full-field mean anchoring.
+
+- semantics:
+ - mask: include_exclude_ignore
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: SPMChannel
+ - units: preserved
+
+- parameters:
+ - `channel` (positional, required) — Finite two-dimensional input channel.
+ - `degree` (keyword_only, 1 bounds=[0, 5]) — Polynomial degree.
+ - `mask` (keyword_only, None) — Optional mask matching the channel shape.
+ - `mask_mode` (keyword_only, 'ignore' values=['exclude', 'include', 'ignore']) — Masking mode.
+ - `direction` (keyword_only, 'horizontal' values=['horizontal', 'vertical']) — Row direction.
+
+- evidence:
+ - `tests/validation/fixtures/gwyddion/align_rows_remaining/align_rows_remaining_reference.json`
+ - `tests/validation/fixtures/gwyddion/align_rows_remaining/align_rows_remaining_reference.npz`
+ - `tests/validation/test_gwydion_align_rows_remaining_production_parity.py`
+
+## IMG.SCANLINE.MARK_SCARS
+
+- operation_id: `img.scanline.mark_scars`
+- public_name: `gwydion_mark_scars`
+- public_import: `spmkit.core.analysis:gwydion_mark_scars`
+- family: IMG.SCANLINE
+- maturity: CROSS_VALIDATED
+- status: stable
+- reference: Gwydion 2.71 (Mark Scars)
+- evidence profile: `COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION`
+
+- contract: Detect and mark scan-line scars, returning a mask array; threshold and geometry parameters follow the frozen Gwydion contract.
+
+- semantics:
+ - mask: mask_output
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: ndarray
+ - units: mask
+
+- parameters:
+ - `channel` (positional, required) — Finite two-dimensional input channel.
+ - `threshold_high` (keyword_only, 0.666) — High threshold.
+ - `threshold_low` (keyword_only, 0.25) — Low threshold.
+ - `min_length` (keyword_only, 16) — Minimum scar length.
+ - `max_width` (keyword_only, 4) — Maximum scar width.
+ - `polarity` (keyword_only, 'both' values=['positive', 'negative', 'both']) — Scar polarity.
+ - `existing_mask` (keyword_only, None) — Optional existing mask.
+ - `combine` (keyword_only, 'replace' values=['replace', 'union', 'intersection']) — Mask combination mode.
+
+- evidence:
+ - `tests/validation/fixtures/gwydion/scars_laplace/scars_laplace_reference.json`
+ - `tests/validation/fixtures/gwydion/scars_laplace/scars_laplace_reference.npz`
+ - `tests/validation/test_gwydion_mark_scars_production_parity.py`
+
+## IMG.SCANLINE.REMOVE_SCARS
+
+- operation_id: `img.scanline.remove_scars`
+- public_name: `gwydion_remove_scars`
+- public_import: `spmkit.core.analysis:gwydion_remove_scars`
+- family: IMG.SCANLINE
+- maturity: CROSS_VALIDATED
+- status: stable
+- reference: Gwydion 2.71 (Remove Scars)
+- evidence profile: `COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION`
+
+- contract: Detect and remove scan-line scars, returning a corrected channel; threshold and geometry parameters follow the frozen Gwydion contract.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: SPMChannel
+ - units: preserved
+
+- parameters:
+ - `channel` (positional, required) — Finite two-dimensional input channel.
+ - `threshold_high` (keyword_only, 0.666) — High threshold.
+ - `threshold_low` (keyword_only, 0.25) — Low threshold.
+ - `min_length` (keyword_only, 16) — Minimum scar length.
+ - `max_width` (keyword_only, 4) — Maximum scar width.
+ - `polarity` (keyword_only, 'both' values=['positive', 'negative', 'both']) — Scar polarity.
+
+- evidence:
+ - `tests/validation/fixtures/gwydion/scars_laplace/scars_laplace_reference.json`
+ - `tests/validation/fixtures/gwydion/scars_laplace/scars_laplace_reference.npz`
+ - `tests/validation/test_gwydion_remove_scars_production_parity.py`
+
+## IMG.SCANLINE.STEP_BLOCK_CORRECTION
+
+- operation_id: `img.scanline.step_block_correction`
+- public_name: `gwydion_step_block_correction`
+- public_import: `spmkit.core.analysis:gwydion_step_block_correction`
+- family: IMG.SCANLINE
+- maturity: CROSS_VALIDATED
+- status: stable
+- reference: Gwydion 2.71 (Step Block Correction)
+- evidence profile: `COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION`
+
+- contract: Correct step-block artefacts in scan lines; threshold and direction parameters follow the frozen Gwydion contract.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: SPMChannel
+ - units: preserved
+
+- parameters:
+ - `channel` (positional, required) — Finite two-dimensional input channel.
+ - `threshold` (keyword_only, 2.0) — Step detection threshold.
+ - `direction` (keyword_only, 'left_to_right' values=['left_to_right', 'right_to_left']) — Scan direction.
+
+- evidence:
+ - `tests/validation/fixtures/gwydion/step_block/step_block_reference.json`
+ - `tests/validation/fixtures/gwydion/step_block/step_block_reference.npz`
+ - `tests/validation/test_gwydion_step_block_production_parity.py`
+
+## IMG.SCANLINE.STEP_LINE_CORRECTION
+
+- operation_id: `img.scanline.step_line_correction`
+- public_name: `gwydion_step_line_correction`
+- public_import: `spmkit.core.analysis:gwydion_step_line_correction`
+- family: IMG.SCANLINE
+- maturity: CROSS_VALIDATED
+- status: stable
+- reference: Gwydion 2.71 (Step Line Correction)
+- evidence profile: `COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION`
+
+- contract: Correct step-line artefacts in scan lines; no parameters beyond the input channel.
+
+- semantics:
+ - mask: none
+ - ROI: no
+ - NaN policy: reject
+ - border: not_applicable
+ - mutation: returns_new
+ - result: SPMChannel
+ - units: preserved
+
+- parameters:
+ - `channel` (positional, required) — Finite two-dimensional input channel.
+
+- evidence:
+ - `tests/validation/fixtures/gwydion/linecorrect/linecorrect_reference.json`
+ - `tests/validation/fixtures/gwydion/linecorrect/linecorrect_reference.npz`
+ - `tests/validation/test_gwydion_linecorrect_production_parity.py`
diff --git a/docs/scientific-status.md b/docs/scientific-status.md
index e6694bc..8cf9a03 100644
--- a/docs/scientific-status.md
+++ b/docs/scientific-status.md
@@ -44,6 +44,13 @@ and tolerance. It never transfers automatically to an adjacent feature.
| Gwyddion 2.71 Filter flat-disc morphology | `core.analysis.background`, `core.analysis._gwyddion_flat_disc_morphology` | Frozen executable reference campaign: 12 fields, six sizes 2/3/4/5/30/31, 72 Opening and 72 Closing cases; kernels 30/30, Opening 72/72 and Closing 72/72 bitwise exact; maximum absolute difference 0, maximum ULP 0, signed-zero mismatches 0, input mutation 0 | CROSS_VALIDATED within the frozen campaign | Audited Gwyddion 2.71 executable, corrected external probe V3, executable reduction trace, independent oracle V2, frozen NPZ/JSON fixture | Finite full-field data with masks ignored; no universal equivalence, NaN/Inf, ROI, masks, ASF, tip morphology, physical rolling-ball, performance, other builds/versions, public erosion/dilation, or source-only tie claim |
| Gwyddion 2.71 Path Level | `core.analysis.leveling`, `core.analysis._gwyddion_path_level` | Audited executable campaign: 18 base families, thicknesses 1/2/3/128, 72 logical cases, 144 fresh external executions and 72 deterministic repeat pairs; private and public arrays 72/72 bitwise exact, 4,652/4,652 elements exact, max absolute/ULP 0, signed-zero mismatches 0, normalized endpoints and mutation/no-op classifications 72/72 | CROSS_VALIDATED within the frozen campaign | Audited Gwyddion 2.71 Path Level tool, external probe, independent oracle V1, frozen NPZ/JSON fixture | Finite non-empty full fields and ordered straight selections only; no universal equivalence, NaN/Inf, masks/ROI, paths/splines, profiles, align-rows, volume, GUI, performance, or other-build/version claim |
| Gwyddion 2.71 Align Rows statistics | `core.analysis.leveling`, `core.analysis._gwyddion_align_rows_statistics` | Public 64-case finite campaign: portable source semantics 64/64 arrays and 3,888/3,888 elements bitwise exact; installed fast-math profile 61/64 arrays and 3,757/3,888 elements exact, with only three signed-zero and 128 independently explained reassociation differences | CROSS_VALIDATED within the frozen dual-profile campaign | Gwyddion 2.71 source, external executable probe, independent portable V2 oracle, frozen NPZ/JSON fixture, installed-build diagnosis | Four methods only; finite full fields, frozen masks/directions/trims; no universal, non-finite, performance, other-version/build, GUI, or generic-`align_rows` compatibility claim |
+| Gwyddion 2.71 Align Rows Facet-level tilt | `core.analysis.leveling`, `core.analysis._gwyddion_align_rows_facet_tilt` | Public 15-case finite campaign: 15/15 corrected arrays (377 elements) bitwise exact against independent oracle and compiled Gwyddion 2.71 source-inclusion probe; 3 background arrays verified elementwise; shifts confirmed all-zero with source-correct length (original rows horizontal, original columns vertical, 7-length VERTICAL shifts for the 5x7 case); mask EXCLUDE/INCLUDE/IGNORE predicates, HORIZONTAL/VERTICAL directions, and fractional mask boundary behavior verified | CROSS_VALIDATED within the frozen 15-case campaign | Gwyddion 2.71 source (compiled source-inclusion probe), independent Python oracle, frozen NPZ/JSON fixture | Facet-level tilt method only; finite inputs (NaN/inf rejected at entry); no trim-fraction, degree, or other method-family claim; no universal, performance, other-version/build, or GUI claim |
+| Gwydion 2.71 Step Line Correction | `core.analysis.scanline`, `core.analysis._gwydion_step_line_correction` | Public 16-case finite campaign: production kernel 176/176 arrays and 5,046/5,046 elements bitwise exact against the compiled source-inclusion probe and independent oracle; max absolute difference 0, max ULP 0, signed-zero mismatches 0; two-pass distinguishing case and conservative-filter dimension behaviour verified | CROSS_VALIDATED within the frozen 16-case campaign | Compiled Gwydion 2.71 source-included kernels with source-pinned orchestration, independent Python oracle, frozen JSON/NPZ fixtures, normal and ASan+UBSan campaign | Horizontal row processing only; finite inputs only (NaN/Inf rejected at entry); no input mask; no parameterized threshold; no Block Line Correction; no GUI, undo or logging parity; no universal or other-version/build equivalence; potentially destructive transformation; no claim of preserving quantitative roughness, PSD or morphology |
+| Gwydion 2.71 Mark Inverted Rows | `core.analysis.scanline`, `core.analysis._gwydion_mark_inverted_rows` | Public 14-case finite campaign: production kernel 59/59 arrays and 596/596 elements bitwise exact against the compiled source-inclusion probe and independent oracle; exact binary masks, marked-row sets, guards, strict-first anchor tie, early-return and existing-mask overwrite classifications; data field non-mutation verified | CROSS_VALIDATED within the frozen 14-case campaign | Compiled Gwydion 2.71 source-included kernels with source-pinned orchestration, independent Python oracle, frozen JSON/NPZ fixtures, normal and ASan+UBSan campaign | Horizontal rows only; finite inputs only; no persistent Data Browser mask state (public API returns an independent mask, all-zero when Gwydion would create none); no interpolation or automatic correction; no claim that a marked row should be numerically sign-inverted; no other version/build or universal equivalence |
+| Gwydion 2.71 Mark Scars | `core.analysis.scanline`, `core.analysis._gwydion_mark_scars` | Production 22-case finite campaign: 20 public-API cases and two private-kernel semantic cases; production masks 22/22 arrays and 1,726/1,726 elements bitwise exact against the compiled probe and independent oracle; max absolute difference 0, max ULP 0, signed-zero mismatches 0; exact parameter and combine semantics (replace/union/intersection), effective-threshold sanitization, hard/soft seeding, width/length boundaries, outer-row exclusion and no-detection classifications verified | CROSS_VALIDATED within the frozen 22-case campaign | Compiled-against Gwydion 2.71 libprocess 2.71 (pinned shared-library hash, frozen source identity), independent Python oracle, frozen JSON/NPZ fixtures, normal and ASan+UBSan probe campaign | Detector, not proof of physical corruption; horizontal scan-line scars only (no vertical orientation); finite fields only (NaN/Inf rejected at entry); thresholds within [0,2], min_length [1,1024], max_width [1,16]; no Data Browser mask persistence; no roughness or morphology preservation claim; no other version/build or universal equivalence |
+| Gwydion 2.71 Interpolate Data Under Mask (Laplace) | `core.analysis.interpolation`, `core.analysis._gwydion_laplace` | Production 18-case finite campaign with explicitly mixed comparison classes: exact policies (empty mask unchanged, whole-field mask zeros, strict mask>0 predicate, calibration independence, unmasked pixels bitwise unchanged) and source-compatible special paths bitwise; campaign maximum 2 ULP and 1.7763568394002505e-15 absolute difference against the linked 2.71 library on the retained iterative paths; zero exact-zero/nonzero transitions in the retained Laplace cases; independent Decimal mathematical reference; production residual guard (implementation numerical-quality guard, not compiled-residual parity); L05/L06 one-ULP tridiagonal rounding classified; L17 signed-zero build-specific classification (production matches the compiled -0.0) | CROSS_VALIDATED within the frozen 18-case campaign, with explicitly mixed comparison classes | Compiled-against Gwydion 2.71 libprocess 2.71 (pinned shared-library hash, frozen source identity), independent Decimal mathematical oracle, frozen JSON/NPZ fixtures, normal and ASan+UBSan probe campaign | Finite values only; mask >0 semantics; no qprec API (process operation grain_id=-1, qprec=1.0); implementation solves the same discrete problem but does not claim algorithmic identity with Gwydion's multilevel/CG/Jacobi solver; no uncertainty; no preservation claim for roughness, PSD, autocorrelation or morphology; no physical validation; no universal tolerance or other-build equivalence; linked library internals were not sanitizer-instrumented |
+| Gwydion 2.71 Remove Scars | `core.analysis.scanline`, `core.analysis._gwydion_remove_scars` | Production 6-case finite composition campaign: production temporary mask 6/6 bitwise identical to the frozen compiled mask; production result equals the explicit production Mark-plus-Laplace composition; compiled mask and composition identities frozen 6/6 bitwise; corrected-field compatibility uses mixed comparison classes: the no-detection case is bitwise unchanged, and 128 exact-zero versus tiny-nonzero transitions (compiled values exact zero, production magnitudes at most ~1.739e-15, independent mathematical reference exact zero) satisfy the frozen absolute-difference bound, not the finite-nonzero ULP bound | CROSS_VALIDATED within the frozen 6-case composition campaign | Compiled-against Gwydion 2.71 libprocess 2.71 (pinned shared-library hash, frozen source identity), independent oracle composition, frozen JSON/NPZ fixtures, normal and ASan+UBSan probe campaign | Inherits all Mark and Laplace limitations; temporary mask is private; no existing-mask or combine parameter; no claim that detected/interpolated data are physically recovered; no other version/build or universal equivalence |
+| Gwydion 2.71 Step Block Correction | `core.analysis.scanline`, `core.analysis._gwydion_step_block` | Production 28-case finite campaign: public corrected fields 28/28 bitwise exact against the frozen compiled probe; private diagnostics (effective threshold, discontinuity and block preview masks, row split states, boundary topology, block shifts, 25%-trimmed-mean raw and post-selection arrays, retained sums, cumulative correction) exact where compared; xres=1 explicitly rejected as a documented frozen-source defect (out-of-bounds read) | CROSS_VALIDATED within the frozen 28-case domain (finite float64 fields, xres >= 2, threshold [0.1, 10.0], left-to-right and right-to-left directions) | Compiled Gwydion 2.71 source-included kernel with source-pinned orchestration, exact source-semantic oracle, independent declarative oracle, frozen JSON/NPZ fixtures, normal and ASan+UBSan probe campaign | No parity for xres=1; finite inputs only; no NaN/Inf compatibility; no mask input; no universal Gwydion-version equivalence; no GUI black-box execution; no physical or experimental validation; no preservation claim for roughness, PSD, morphology or real terraces; no proof that a detected step is an acquisition artefact; no uncertainty quantification; no universal bitwise equivalence outside the frozen campaign |
| Hertz / conical contact and DMT paths | `core.analysis.forcecurve` | Unit and synthetic-recovery tests; Hertz/conical modulus recovery gates | NUMERICALLY_VERIFIED within synthetic test scope | Analytical construction | No certified cantilever/tip calibration or broad experimental campaign |
| Adhesive JKR | `core.analysis.experimental` | Synthetic recovery of reduced modulus and work of adhesion; Hertz-limit test | NUMERICALLY_VERIFIED within synthetic scope | Analytical construction | Experimental module; no physical-reference campaign |
| WLC and FJC chain models | `core.analysis.chain` | Analytical synthetic-recovery tests | NUMERICALLY_VERIFIED within synthetic scope | Analytical construction | No cross-software or experimental population campaign |
@@ -252,6 +259,790 @@ Gwyddion 2.71 source: modules/process/linematch.c
matrix, performance, ROI/GUI, adapter, or other Align Rows method-family claim. This finite
campaign does not establish physical validation or general SPMKit parity.
+### Gwyddion 2.71 Align Rows Facet-level tilt
+
+**Claim:** `CROSS_VALIDATED` within the frozen 15-case public campaign covering zero constant,
+exactly linear, nearly linear, curved with outliers, curved with masks (INCLUDE, EXCLUDE, IGNORE),
+fractional mask boundaries, horizontal/vertical directions, and two-column rows (both
+orientations). The production contract is bitwise exact against both the independent Python
+oracle and the compiled Gwyddion 2.71 source-inclusion probe in 15/15 corrected
+arrays (377 elements). Background arrays for the three extract-background cases are verified
+elementwise (`input - corrected`). Shifts arrays are confirmed all-zero (matching
+`gwy_data_line_clear`) with the source-correct length: the operation resamples the shifts line
+to the working field's y-resolution (`gwy_data_line_resample` in `linematch.c` `execute()`),
+so horizontal processing yields original-row-length shifts while vertical processing yields
+original-column-length shifts (7 for the 5x7 VERTICAL case).
+
+The kernel implements the exact Gwyddion 2.71 `linematch_do_facet_tilt` algorithm: iterative
+robust reweighted slope estimation (C=1/200 weighting, exp(q) weights, 30-iteration cap,
+`|tilt/dx|<1e-6` convergence), pair-wise mask predicates (INCLUDE mask≥1.0, EXCLUDE mask≤0.0),
+2-column mincount guard, transpose/restore for VERTICAL direction, and centre-pivot untilting.
+
+Known source-confirmed behaviors: constant rows produce NaN (sigma²=0, IEEE 0/0 in exp); exactly
+linear rows NaN-propagate after the first correction iteration. Input NaN/inf is rejected at
+entry (deliberate defensive validation, diverging from Gwyddion's unchecked IEEE propagation).
+
+**Repair history:** an earlier closure stored five shifts for the 5x7 VERTICAL case in the kernel,
+oracle, and fixture generator while the external probe emitted seven; the generator truncated the
+external evidence to the assumed original y-resolution (circular-validation failure). The repair
+derived the shifts length from the source (working-field y-resolution), fixed the kernel, oracle,
+and generator (which now raises on truncation), added the `two_column_vertical` external case,
+re-ran the normal and ASan campaigns (15/15 cases exit 0, ASan clean, normal-vs-ASan stdout
+identical), and regenerated the fixtures from fresh probe output. All 14 pre-existing
+corrected/background arrays are bitwise identical before and after the repair, confirming the
+shifts-length correction did not alter the correction science.
+
+The kernel implements the exact Gwyddion 2.71 `linematch_do_facet_tilt` algorithm: iterative
+robust reweighted slope estimation (C=1/200 weighting, exp(q) weights, 30-iteration cap,
+`|tilt/dx|<1e-6` convergence), pair-wise mask predicates (INCLUDE mask≥1.0, EXCLUDE mask≤0.0),
+2-column mincount guard, transpose/restore for VERTICAL direction, and centre-pivot untilting.
+
+Known source-confirmed behaviors: constant rows produce NaN (sigma²=0, IEEE 0/0 in exp); exactly
+linear rows NaN-propagate after the first correction iteration. Input NaN/inf is rejected at
+entry (deliberate defensive validation, diverging from Gwyddion's unchecked IEEE propagation).
+
+**Traceability:**
+
+```text
+Gwyddion 2.71 source: modules/process/linematch.c (SHA-256 79b951a1...)
+ → source-inclusion probe: .reference/gwyddion-2.71/facet-tilt-parity/facet_tilt_behavior_probe.c
+ → independent oracle: tests/validation/fixtures/gwyddion/facet_tilt/oracle_facet_tilt.py
+ → frozen fixtures: tests/validation/fixtures/gwyddion/facet_tilt/facet_tilt_reference.{json,npz}
+ → private kernel: src/spmkit/core/analysis/_gwyddion_align_rows_facet_tilt.py
+ → public API: src/spmkit/core/analysis/leveling.py (gwyddion_align_rows_facet_tilt)
+ → tests: tests/core/test_gwyddion_align_rows_facet_tilt.py
+ → fixture integrity: tests/validation/test_gwyddion_align_rows_facet_tilt_fixture_integrity.py
+```
+
+**Non-claims:** no universal equivalence; no non-finite input propagation (rejected at entry); no
+other Align Rows method-family, performance, other Gwyddion version/build, GUI, adapter, or
+physical validation claim. The public function is an explicit alternative to, not a compatibility
+claim for, the existing generic `align_rows`.
+
+
+### Gwydion 2.71 Align Rows remaining methods (Polynomial, Modus, Match)
+
+**Claim:** `CROSS_VALIDATED` only within the frozen compiled finite 62-case campaign with the
+exact evidence profile `COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION`
+(Gwydion 2.71 `modules/process/linematch.c` source-included kernel with source-pinned
+orchestration; helper functions from the installed Gwydion 2.71 libraries). The three public
+operations are:
+
+- `gwyddion_align_rows_polynomial` (degree `0..5`);
+- `gwyddion_align_rows_modus`;
+- `gwyddion_align_rows_match`.
+
+Corrected fields are bitwise exact for all 62 canonical numerical cases at the private-kernel
+level (10,056 elements, max absolute difference 0, max ULP 0) and for all 61 in-range cases
+through the public API; the frozen degree-8 probe case (outside the public `0..5` degree
+range) is verified only at the private-kernel level and the public API rejects it. The
+private diagnostics are exact for corrected/background/delta/shifts profiles, per-row valid
+indices/counts/shifts/statuses, method and masking identity, branch selection, and signed-zero
+bits. Six determinism witnesses are stored once in the fixture NPZ with exact paired equality
+relations. Masking modes INCLUDE (`mask > 0`), EXCLUDE (`mask < 1`) and IGNORE are covered
+for all three methods; inputs are finite two-dimensional channels and the input channel, data
+array and mask are never mutated. Horizontal row processing is externally `CROSS_VALIDATED`
+within this compiled profile; the vertical transpose-derived direction is source-semantic and
+is not claimed as externally cross-validated.
+
+**Numerical semantics** follow the compiled evidence:
+
+- Polynomial degree 0 uses the trim-fraction-zero **row-shift path** (per-row means,
+ `mincount = GWY_ROUND(log(xres) + 1)`, global masked-median fallback, zero-levelled shifts)
+ and deliberately does **not** call the degree >= 1 polynomial solver;
+- Polynomial degree >= 1 fits each row independently on `x = j - 0.5*(xres-1)` with
+ source-order moments, a packed lower-triangular Cholesky solve and full-field mean
+ anchoring; the installed helper-library binary used for the compiled campaign performs one
+ Cholesky nondiagonal step as reciprocal multiplication (`r * (1.0/s)`) where the frozen
+ source text expresses direct division (`r / s`) — production follows the compiled evidence
+ profile and no universal build equivalence is claimed;
+- Modus is a robust row-centre statistic (global masked-median fallback, upper median for
+ fewer than nine retained samples, otherwise the narrowest `sqrt(count)`-wide range window
+ over the sorted samples with the mean of its central third, zero-levelled);
+- Match compares adjacent rows with Gaussian-weighted differences of row differences,
+ includes endpoint samples exactly, reassigns the effective weight sum before the scalar
+ correction, accumulates across rows and zero-levels; under its zero-weight guard **pure
+ vertical row offsets with identical row shape may remain uncorrected** — this source
+ behaviour is preserved, not repaired.
+
+**Traceability:**
+
+```text
+Gwydion 2.71 source: modules/process/linematch.c
+ → compiled source-inclusion probe (normal + ASan/UBSan campaigns)
+ → independent source-semantic oracle and declarative oracle
+ → tests/validation/fixtures/gwyddion/align_rows_remaining/
+ → src/spmkit/core/analysis/_gwyddion_align_rows_remaining.py
+ → src/spmkit/core/analysis/leveling.py (public API)
+ → tests/core/test_gwydion_align_rows_remaining.py
+ → tests/validation/test_gwydion_align_rows_remaining_production_parity.py
+```
+
+**Non-claims:** no horizontal pixel displacement; no bidirectional channel-mismatch; no stripe
+suppression; no generic outlier-line detection; no NaN/Inf compatibility; no GUI black-box
+execution; no universal Gwydion version/build equivalence; no physical validation and no proof
+that removed row structure is an acquisition artefact; no roughness, PSD, morphology or
+uncertainty preservation claim. This finite campaign does not establish a generic SPMKit
+`align_rows` compatibility claim.
+
+### Gwydion 2.71 Step Line Correction
+
+**Claim:** `CROSS_VALIDATED` within the frozen 16-case finite campaign. The production kernel
+(`_gwydion_step_line_correction`) is bitwise exact against the compiled Gwydion 2.71
+source-inclusion probe and the independent Python oracle: 176/176 arrays and 5,046/5,046
+elements bitwise exact, max absolute difference 0, max ULP 0, signed-zero mismatches 0.
+
+**Evidence:** compiled Gwydion 2.71 source-included kernels with source-pinned orchestration
+(`line_correct_step_iter`, `calculate_segment_correction` compiled verbatim from the frozen
+tree; orchestration annotated per source line); independent Python oracle; frozen JSON/NPZ
+fixtures; normal and ASan+UBSan 60-execution campaign (30/30 normal-versus-sanitized stdout
+identical); production parity metrics; the two-pass distinguishing case `s11_pass2_change`
+(pass 2 changes exactly the middle row, columns 5-10); conservative-filter dimension
+behaviour (size-5 filter is a numerical no-op below 5x5, source `filters.c:1174-1177`).
+
+**Numerical semantics** follow the frozen Gwydion 2.71 source and executable probe: row
+upper-median alignment with zero-leveled shifts, two detector passes (v =
+(middle-top)*(middle-bottom) > 3.0*w; segments of at least 4 equal-sign pixels; correction
+(3*segment_residual + local_residual)/4), size-5 conservative denoise, global-mean
+restoration. **User-facing interpretation** follows the Gwydion scan-line artefacts guide:
+Step Line Correction must be described as aggressive and potentially destructive.
+
+**Limitations and non-claims:** horizontal row processing only; finite inputs only (NaN/Inf
+rejected at entry, a deliberate SPMKit policy difference); no input mask; no parameterized
+threshold; no Block Line Correction; no GUI, undo or logging parity; no universal or
+other-version/build equivalence; potentially destructive transformation; no claim of
+preserving quantitative roughness, PSD or morphology. No experimental or physical
+validation is claimed.
+
+
+### Gwydion 2.71 Neighborhood Filters (Rank, disc Median, Gaussian)
+
+**Claim:** `CROSS_VALIDATED` only within the frozen compiled finite 59-case campaign with the
+exact evidence profile `COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION`
+(Gwydion 2.71 frozen orchestration and source identities pinned; numerical helpers partly
+supplied by installed Gwydion 2.71 libraries; probe boundary sanitizer-instrumented;
+dynamically linked helper internals not sanitizer-rebuilt; `/usr/bin/gwyd*dion` not invoked;
+GUI not executed; Filter Tool mask post-blending and rectangular selection excluded). The
+three public operations are:
+
+- `gwyd*dion_rank_filter` (radius `1..1024`, percentile `0..1`; public v1 exposes the
+ primary percentile result only; the private diagnostics preserve the secondary, both and
+ difference source output modes);
+- `gwyd*dion_median_filter` (`size` is the footprint SIDE `2..31`, not a radius; even sizes
+ are valid; upper median rank `n//2`);
+- `gwyd*dion_gaussian_filter` (sigma in pixels `0.01..40.0`; sigma=0 is private
+ library-domain evidence and rejected publicly).
+
+Corrected outputs and diagnostics are bitwise exact for all 59 canonical private-kernel cases
+(55 public primary/tool-domain, 1 private Gaussian sigma-zero, 3 private Rank output-mode)
+and for all 55 public primary cases; the 11 relation-only cases and 1 determinism witness are
+verified relationally. Inputs are finite two-dimensional channels; the input channel and
+data array are never mutated (these operations take no mask). Borders follow the fixed
+source behavior: EXTEND (nearest constant) for Rank and Median, mirror extension for Gaussian.
+
+**Numerical semantics** follow the compiled evidence:
+
+- Rank Filter: ellipse-inscribed footprint in a `2*radius+1` square, active count `n`,
+ rank `GWY_ROUND(percentile*(n-1))`, k=0/k=n-1 minimum/maximum endpoint dispatch,
+ EXTEND borders, kth-rank value selection;
+- disc Median: ellipse-inscribed footprint in a `size x size` square, upper median rank
+ `n//2`, EXTEND borders;
+- Gaussian: separable kernel `res = 2*ceil(5*sigma)+1` capped at `3*min(xres,yres)` and
+ forced odd, coefficients `exp(-x^2/(2*sigma^2))`, sequential-sum normalization via
+ reciprocal multiply (not forced to exactly 1.0), mirror borders, horizontal-then-vertical
+ passes with the horizontal intermediate preserved. Gaussian constant preservation is
+ **not** bitwise guaranteed: the observed kernel-normalization rounding (~1e-15) is
+ preserved rather than corrected.
+
+**Non-claims:** no mask support; no rectangular selection; no Mean operation; no public
+Minimum/Maximum operation; no morphology capability; no FFT/frequency filtering; no NaN/Inf
+compatibility; no GUI black-box execution; no universal Gwydion build equivalence; no
+physical validation; no proof that filtering improves scientific truth; no roughness, PSD,
+morphology or uncertainty preservation claim.
+
+### Gwydion 2.71 Mark Inverted Rows
+
+**Claim:** `CROSS_VALIDATED` within the frozen 14-case finite campaign. The production kernel
+(`_gwydion_mark_inverted_rows`) is bitwise exact against the compiled Gwydion 2.71
+source-inclusion probe and the independent Python oracle: 59/59 arrays and 596/596 elements
+bitwise exact; exact binary masks; exact marked-row sets; exact guards, early-return
+classifications, strict-first anchor tie and existing-mask overwrite classifications; zero
+input mutation.
+
+**Evidence:** compiled Gwydion 2.71 source-included kernels with source-pinned orchestration;
+independent Python oracle; frozen JSON/NPZ fixtures; normal and ASan+UBSan campaign; exact
+binary masks (0.0/1.0); boundary and consecutive-row cases; strict-first anchor tie
+(`m09_tie_anchor`); no-negative early return; existing-mask overwrite semantics validated
+privately; data field non-mutation.
+
+**Public adaptation:** SPMKit has no persistent Data Browser mask state; the public API
+returns an independent C-contiguous mask array, all-zero when Gwydion would create no mask.
+The private kernel preserves an existing mask untouched on the no-negative early return and
+overwrites it bitwise after actual detection (modelling `linecorrect.c:255-260, 321-324`).
+
+**Limitations and non-claims:** horizontal rows only; finite inputs only; no persistent Data
+Browser mask state; no interpolation or automatic correction; no claim that a marked row
+should be numerically sign-inverted; no other version/build or universal equivalence. No
+experimental or physical validation is claimed.
+
+### Gwydion 2.71 Mark Scars
+
+The detector computes one global vertical-difference RMS
+(`sqrt(sum((d[i,j]-d[i+1,j])**2)/(xres*yres))`), searches per column for bands of up to
+`max_width` rows whose values lie at least `threshold_low` RMS away from their boundary
+rows, keeps pixels with weight at least `threshold_high` RMS as hard seeds, attaches
+adjacent soft pixels through chained horizontal expansion and retains only per-row runs of
+at least `min_length` pixels. Positive scars are bands elevated above their neighbours;
+negative scars are depressed bands; `"both"` runs the two detectors and unions the binary
+masks. The detector is exact: the production kernel is bitwise equal to the compiled probe
+and the independent oracle for all 22 cases (1,726/1,726 mask elements, zero maximum
+absolute/ULP difference and zero signed-zero mismatches). Coverage is split: 20 cases
+exercise the public API, while C05_soft_only_no_seed and C07_detached_soft_run are
+private-kernel semantic cases. Both require `threshold_high=3.0` (a uniform single-row
+band has weight sqrt(5) ~ 2.236, so a soft-only configuration needs a hard threshold
+above sqrt(5)), which lies outside the public Gwyddion-compatible parameter domain
+[0, 2]; they remain valid kernel-semantic tests, and the public domain is not broadened
+merely to express test phantoms. Combine semantics (replace ignores the existing mask,
+union is source-compatible fmax, intersection is source-compatible fmin) and the
+module-level no-detection mask-presence classification are verified; combined masks may
+retain finite non-binary values from an existing mask.
+
+**Limitations and non-claims:** detector, not proof of physical corruption; horizontal
+scan-line scars only; no vertical orientation; finite fields only; parameter domains match
+the Gwyddion process module; SPMKit does not simulate Data Browser mask removal or
+persistence; no claim of roughness or morphology preservation; no other version/build or
+universal equivalence. No experimental or physical validation is claimed.
+
+### Gwydion 2.71 Interpolate Data Under Mask (Laplace)
+
+The public operation solves the discrete Laplace boundary-value problem for pixels with
+`mask > 0`: each masked pixel equals the mean of its masked neighbours and its fixed
+(unmasked) neighbours, with missing neighbours at image borders implementing Neumann
+conditions. The empty mask leaves the field bitwise unchanged; a whole-field positive mask
+returns the source-defined all-zero field; physical calibration does not enter the solve.
+Comparison classes are explicitly mixed: exact policies and source-compatible special
+paths (isolated pixels, thin tridiagonal corridors, three-pixel L components) are bitwise
+against the compiled probe, while the retained generic iterative paths stay within the
+frozen campaign maximum of 2 ULP and 1.7763568394002505e-15 absolute difference against
+the linked 2.71 library, with zero exact-zero/nonzero transitions in the retained Laplace
+cases. An independent Decimal (80-digit) mathematical reference is frozen in the
+fixtures; the production residual limit (1e-13) is a production convergence and
+numerical-quality guard for the frozen campaign, not compiled-residual parity. The
+compiled probe residuals were measured during the campaign but are not stored in the
+current persistent JSON/NPZ fixtures, and the production residual is not claimed to be
+no worse than the compiled probe (L11 is approximately twice the compiled residual at the
+float64 floor; L10 is equal, L12 is half). Exact compiled-residual parity is not claimed;
+the persistent contract enforces output-distance metrics and the independent mathematical
+residual guard. L05/L06 carry the measured one-ULP tridiagonal rounding classification;
+L17 is a signed-zero build-specific classification (production reproduces the compiled
+-0.0; the frozen source arithmetic seeded with 0.0 yields +0.0).
+
+**Limitations and non-claims:** finite values only; `mask > 0` semantics; no qprec API (the
+process operation uses grain_id=-1 and qprec=1.0); the implementation solves the same
+discrete problem but does not claim algorithmic identity with Gwydion's multilevel
+anisotropic sparse CG + damped-Jacobi + hierarchical reconstruction solver; no uncertainty;
+no preservation claim for roughness, PSD, autocorrelation or morphology; no physical
+validation; no universal tolerance or other-build equivalence; the linked library internals
+were not sanitizer-instrumented (ASan/UBSan covered the probe executables and the call
+boundary only). No experimental or physical validation is claimed.
+
+### Gwydion 2.71 Remove Scars
+
+The public operation is exactly the composition of the Mark Scars detector (with the same
+parameter semantics) and the Laplace interpolation, with a private temporary mask that is
+never exposed, mutated or stored, and no extra hidden correction. The compiled campaign
+froze the composition identities bitwise (temporary mask equal to the standalone Mark
+Scars mask; corrected equal to the standalone Laplace result; 6/6 cases). Production
+reproduces the temporary-mask identity bitwise. Corrected-field compatibility against the
+compiled Remove output uses the same mixed comparison classes as the Laplace operation:
+the no-detection case is bitwise unchanged, while the retained scar cases carry 128
+exact-zero versus tiny-nonzero transitions (compiled values are exact zero; production
+values have magnitude at most approximately 1.739e-15; the independent mathematical
+reference is exactly zero) that satisfy the frozen absolute-difference bound
+(1.7763568394002505e-15), not the finite-nonzero ULP bound. Full Remove corrected-field
+bitwise equivalence is not claimed.
+
+**Limitations and non-claims:** inherits all Mark and Laplace limitations; temporary mask
+is private; no existing-mask or combine parameter; no claim that detected/interpolated
+data are physically recovered; no other version/build or universal equivalence. No
+experimental or physical validation is claimed.
+
+## Evidence profile (scars/Laplace campaign)
+
+The compiled campaign evidence was produced by custom probe executables that linked the
+installed Gwydion 2.71 shared library (`libgwyprocess2`, version 2.71, SHA-256
+`5f5b53cb544068638d1a3be8d6703345e49d5626d3fa4791106ce11bc051d3d7`); `/usr/bin/gwydion`
+was not invoked; the frozen 2.71 source identity was retained for semantic reconciliation;
+ASan/UBSan covered the probe executables and the call boundary, not the shared-library
+internals.
+
+### Gwydion 2.71 Step Block Correction
+
+Public API: `gwydion_step_block_correction(channel, *, threshold=2.0,
+direction="left_to_right")` in `core.analysis.scanline`.
+
+Evidence profile: COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION.
+
+The operation detects per-pixel vertical jumps with a strict absolute-difference
+threshold, scores row boundaries and horizontal split positions (first strict
+maximum), constructs row blocks, estimates each block's shift with a 25%
+trimmed mean, and applies a cumulative piecewise-constant correction anchored
+at the first block, for left-to-right and right-to-left scan directions, over
+finite float64 two-dimensional fields. Public
+corrected fields are bitwise exact for all 28 frozen valid compiled cases, and
+the private diagnostic states (effective threshold, masks, row split states,
+boundaries, shifts, trimmed-mean retained arrays and sums, cumulative
+correction) are exact where compared. One frozen-source defect is recorded:
+for xres=1 the source minimum length truncates to zero and the first candidate
+can read out of bounds; its normal output is undefined. SPMKit deliberately
+rejects xres < 2 (typed ValueError) and never exposes undefined behaviour.
+Maturity is CROSS_VALIDATED only within the declared domain; no claim is made
+that a detected step is an acquisition artefact rather than a real topographic
+discontinuity, and no preservation of roughness, PSD, morphology or uncertainty
+is claimed.
+
+## A2 derivative filters (Sobel X/Y, Prewitt X/Y, gradient magnitude, gradient direction)
+
+The first A2 derivative-filter batch provides four exact component filters
+(`gwyd*dion_sobel_x`, `gwyd*dion_sobel_y`, `gwyd*dion_prewitt_x`,
+`gwyd*dion_prewitt_y`), a gradient magnitude composition
+(`gwyd*dion_gradient_magnitude(gx, gy)` = `hypot(gx, gy)`) and a native
+gradient direction composite (`gradient_direction(gx, gy)` = `atan2(gy, gx)`).
+
+Sobel X/Y and Prewitt X/Y:
+
+- CROSS_VALIDATED within:
+ COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_DERIVATIVE_KERNEL_PROFILE;
+- exact frozen kernels (Sobel 0.25/0.5 and Prewitt 1/3 coefficients);
+- CLIPPED border semantics (corners, edges, 1x1, 1xN, Nx1, non-square);
+- frozen source sign and orientation (increasing-right X ramp -> negative
+ Sobel X; increasing-down Y ramp -> negative Sobel Y);
+- finite two-dimensional inputs only; input channels never mutated;
+- all 228 canonical source-profile outputs bitwise exact (max absolute
+ difference 0, max ULP 0).
+
+Gradient magnitude:
+
+- source-compatible `hypot` composition over explicit component fields
+ (matches the frozen hypot-of-fields orchestration);
+- bitwise claim bounded to the frozen x86-64 / glibc / hypot@GLIBC_2.35
+ platform profile;
+- no cross-libc or cross-architecture bitwise guarantee; non-negativity and
+ component-swap symmetry hold relationally on every platform.
+
+Gradient direction:
+
+- native SPMKit analytical composite;
+- `atan2(gy, gx)`, radians, range (-pi, pi], C99 signed-zero axes;
+- NUMERICALLY_VERIFIED maturity;
+- NOT direct Gwydion parity; the production implementation (numpy.arctan2)
+ is characterized within ~1 ULP of the compiled C atan2 profile on the
+ frozen platform.
+
+Non-claims for the derivative batch:
+
+- no process-menu normalized-image parity;
+- no universal installed-Gwydion-build bitwise equivalence;
+- no physical-coordinate derivative;
+- no physical slope or surface-angle claim;
+- no mask or ROI support;
+- no NaN/Inf compatibility;
+- no edge-detection or segmentation claim;
+- no physical validation;
+- no scientific-truth or uncertainty-preservation claim.
+
+## Force-spectroscopy foundation (FS-F1)
+
+The FS-F1 foundation provides a validated curve-preparation layer over the
+segment-based ForceCurve model: 13 public capabilities
+(identify_force_segments, calibrate_force_curve,
+compute_tip_sample_separation, fit_force_baseline, correct_force_baseline,
+contact_point_threshold, contact_point_ratio_of_variances,
+contact_point_piecewise, contact_point_ensemble, extract_force_events,
+integrate_force_work, score_force_curve_quality, prepare_force_curve) with
+immutable results, typed failures and explicit provenance.
+
+- pipeline order: segments -> calibration -> tip-sample separation ->
+ baseline fit/correction -> contact ensemble -> events -> work -> quality;
+- units: height/deflection/separation in m, force in N, InVOLS in m/V,
+ spring constant in N/m, work in J, direction in rad;
+- sign conventions: separation = height - deflection; positive deflection =
+ cantilever bending toward the sample; increasing-right/up data follows the
+ frozen contact conventions;
+- calibration: raw_v -> deflection_m (x InVOLS) -> force_n (x k); double
+ calibration rejected; missing calibration raises MISSING_CALIBRATION;
+- contact: threshold (k*sigma with persistence 3), ratio of variances
+ (Gavara 2016), piecewise (value-continuous baseline/contact), ensemble
+ (median of valid candidates, explicit disagreement, optional deterministic
+ bootstrap);
+- work: force integrated over tip-sample separation on the common overlap
+ domain, monotone interpolation, trapezoidal arithmetic;
+- QC: typed failure reasons beside a summary score (MISSING_CALIBRATION,
+ INVALID_CALIBRATION, MISSING_APPROACH, MISSING_RETRACT, NONFINITE_DATA,
+ NONMONOTONIC_COORDINATE, BASELINE_TOO_SHORT, BASELINE_UNSTABLE,
+ CONTACT_NOT_FOUND, CONTACT_METHOD_DISAGREEMENT, SATURATED_SIGNAL,
+ EVENT_NOT_FOUND, INSUFFICIENT_OVERLAP, FIT_NOT_ELIGIBLE).
+
+External reference profile:
+
+- nanite 4.2.3 (GPL-3; subprocess boundary only, never imported by SPMKit),
+ afmformats 0.18.7 (MIT), Python 3.12.13, x86-64/glibc;
+- frozen pipeline: compute_tip_position -> correct_split_approach_retract ->
+ correct_tip_offset -> correct_force_offset -> correct_force_slope;
+- frozen contact methods: deviation_from_baseline, fit_constant_line,
+ fit_line_polynomial, fit_constant_polynomial;
+- external outputs are NANITE_EXTERNAL_REFERENCE evidence only; they are
+ canonical for no native ROV/ensemble/event/work/QC contract.
+
+Maturity per capability (reconciled at independent audit):
+
+- CROSS_VALIDATED (frozen nanite 4.2.3 profile only):
+ compute_tip_sample_separation (tip-position convention verified on all 17
+ retained cases, rtol 1e-9);
+- NUMERICALLY_VERIFIED (defined numerical truth on deterministic phantoms
+ and analytical oracles):
+ identify_force_segments, calibrate_force_curve, fit_force_baseline,
+ correct_force_baseline, contact_point_threshold,
+ contact_point_ratio_of_variances, contact_point_piecewise,
+ extract_force_events, integrate_force_work (integrator level only);
+ the threshold method agrees with nanite deviation_from_baseline on clean
+ flat-baseline cases (0..2 samples) but diverges on sloped/noisy baselines
+ (up to 13 samples on the persisted 17-case matrix) and is NOT
+ cross-validated as equivalent;
+- SOFTWARE_VERIFIED (designed heuristics without unique numerical truth):
+ contact_point_ensemble (median of valid candidates), the aggregate
+ score_force_curve_quality summary score, prepare_force_curve
+ (orchestration bounded by its weakest material component);
+- no PHYSICALLY_VALIDATED claim.
+
+Work integration is reported at three separated levels:
+
+A. numerical integrator: exact-force/exact-coordinate/exact-domain recovery
+ against closed-form truth at floating-point precision;
+B. contact-conditioned work: the propagated contact-index error is reported
+ separately from the integrator error;
+C. full prepared pipeline: total end-to-end error reported as such, never
+ attributed to the integrator.
+
+The redistributable spectroscopy.nid case is a REAL_DATA_FAILURE_HANDLING_
+WITNESS only: all 100 curves either complete or raise typed failures (99
+NONMONOTONIC_COORDINATE, 1 INSUFFICIENT_OVERLAP, 0 silent). It is not a
+successful real-data end-to-end scientific proof and not physical validation.
+
+The aggregate QC summary score is a designed heuristic (0..1 pass fraction);
+it is not an externally validated scientific quality probability.
+
+Known limitations:
+
+- contact methods disagree on real data; the ensemble reports the
+ disagreement rather than choosing silently;
+- saturation detection requires an exact clipping plateau (baseline
+ correction destroys it; score on the calibrated curve);
+- real JPK/NID tip-sample separation is often non-monotone (snap-in/pull-off
+ motion); work over tip position then raises the typed
+ NONMONOTONIC_COORDINATE failure instead of fabricating a value.
+
+Non-claims: no certified cantilever calibration; no universal JPK/ANA
+numerical parity; no physical validation; no universal contact point; no
+claim that baseline slope correction is always scientifically valid; no
+automatic choice of the "correct" contact method; no uncertainty guarantee
+from method spread alone; no model validity inference; no cell/material
+property truth claim; no experimental reproducibility claim; no complete
+force-map parity; no SMFS or viscoelastic parity from this batch.
+
+## Force-spectroscopy mechanics (FS-F2)
+
+The FS-F2 batch builds the indentation and contact-mechanics layer on the
+FS-F1 preparation: indentation, fit windows, five frozen contact models
+(hertz sphere, sneddon cone, flat punch, DMT, JKR), AICc model comparison,
+sensitivity multiverse, residual bootstrap, diagnostics and force-volume
+mapping. 13 public capabilities (compute_indentation,
+select_contact_fit_window, forward_model, fit_hertz_sphere,
+fit_sneddon_cone, fit_flat_punch, fit_dmt, fit_jkr, compare_contact_models,
+analyze_force_fit_sensitivity, bootstrap_force_fit, diagnose_force_fit,
+fit_force_volume_mechanics) with typed errors, immutable results and
+explicit provenance.
+
+- indentation convention: indentation = separation - contact_coordinate on
+ the approach branch; the contact coordinate is the height at the contact
+ index (deflection is zero there), so indentation equals the piezo motion
+ past the contact minus the cantilever deflection; zero at the contact,
+ positive into the sample in the indentation regime; pre-contact samples
+ are excluded by the valid mask (never fabricated);
+- phantom geometry: the separation is the increasing trace axis (FS-F1
+ convention); height = separation + force/k stays strictly monotone
+ because the deflection grows slower than the piezo motion in the
+ indentation regime; clean phantoms carry zero pre-contact force, which is
+ the exact frozen model behavior (the models have no long-range branch;
+ the adhesion jump at the contact is preserved) and prevents the FS-F1
+ baseline correction from subtracting model signal; the profile is a
+ contact-branch-only representation of the frozen models and makes no
+ claim about complete adhesive force curves with long-range interaction;
+- frozen equations (reduced modulus E* = E/(1-nu^2)): hertz
+ F = (4/3) E* sqrt(R) d^1.5; sneddon F = (2 tan(alpha)/pi) E* d^2; flat
+ punch F = 2 E* R d; dmt F = hertz - F_adh; jkr loading branch via the
+ parametric contact radius (monotone for a >= a0, range derived from the
+ data, w = 0 reduces to hertz);
+- fits: nonlinear least squares of E (and F_adh / w) over the contact fit
+ window with geometry parameters fixed; results carry parameters,
+ covariance, residuals, rmse and AIC/AICc/BIC;
+- comparison: AICc weights over the identical data subset; the recommended
+ model is the AICc minimum unless the runner-up retains considerable
+ support (Delta AICc < 4 -> ambiguous); the comparison is model-relative
+ and never a physical-truth claim;
+- reliability: deterministic sensitivity multiverse over contact offsets
+ and fit-window lower-bound fractions (<= 512 configurations) with
+ one-at-a-time contact and window sensitivity indices relative to the
+ baseline configuration and a dominant-sensitivity classification
+ (contact / window / none, relative index > 20%); deterministic
+ residual/block-residual bootstrap with percentile intervals; the
+ diagnostic summary status is a policy (ok/review), never a probability.
+
+Recovery bounds (clean phantoms, FS-F1 ensemble contact):
+
+- hertz family (hertz sphere, sneddon cone, flat punch): E within 5%
+ (residual bias is the contact precision, ~1 sample = 1.5e-8 m); with
+ noise (sigma = 2e-12 N) within 10%; small force offset + residual slope
+ within 5%;
+- adhesive models (DMT, JKR) with windows trimmed past the snap-in region:
+ DMT E within 30% and F_adh within 1.5e-9 N; JKR E within 20% and w within
+ 30%; the FS-F1 contact ensemble is unstable on snap-in curves (up to ~10
+ samples off); dedicated snap-in contact detection is future work.
+
+Maturity per capability (reconciled at independent audit):
+
+- NUMERICALLY_VERIFIED (defined numerical truth on deterministic phantoms,
+ independent analytical oracle and the frozen nanite contact campaign):
+ compute_indentation, forward_model, fit_hertz_sphere, fit_sneddon_cone,
+ fit_flat_punch, fit_dmt, fit_jkr, compare_contact_models (arithmetic),
+ bootstrap_force_fit (resampling arithmetic), fit_force_volume_mechanics;
+- SOFTWARE_VERIFIED (designed heuristics and policies without unique
+ numerical truth): select_contact_fit_window (window policy),
+ analyze_force_fit_sensitivity (multiverse interpretation),
+ diagnose_force_fit (summary policy), and the model-recommendation
+ policy inside compare_contact_models (Delta AICc < 4 threshold);
+- external overlap: the FS-F1 contact ensemble lies inside the nanite
+ 4-method contact bracket on all 16 prepared P-cases of the frozen
+ black-box campaign (NANITE_EXTERNAL_REFERENCE evidence; canonical for no
+ native fit contract);
+- no PHYSICALLY_VALIDATED claim.
+
+Failure witnesses (typed, never silent):
+
+- M11 saturated curve: SATURATED_SIGNAL flagged by the FS-F1 quality gate;
+ the fitted modulus leaves the clean recovery band (bias reported);
+- M15 cone data under a hertz hypothesis: the model comparison prefers
+ sneddon_cone with weight > 0.9;
+- M17 shallow noisy indentation and M18 flat curve: preparation raises the
+ typed CONTACT_NOT_FOUND failure;
+- real-data witness (redistributable spectroscopy.nid): every curve either
+ completes the FS-F2 stack or raises a typed failure; a successful fit is
+ required to be finite; no silent NaN-filled success is allowed.
+
+Non-claims: no external mechanical-fit parity (nanite contact campaign is
+contact-only); no snap-in contact detection; no free contact-offset fit
+parameter; no uncertainty-calibrated intervals (bootstrap percentiles are
+point-estimate spread, not coverage-guaranteed); no tip-radius
+identifiability (R is fixed, never fitted); no adhesion-hysteresis or
+pull-off model; no rate/viscoelastic dependence; no physical validation; no
+experimental reproducibility claim.
+
+## Force-spectroscopy viscoelasticity (FS-F3)
+
+The FS-F3 batch adds a validated time-domain viscoelastic layer on the
+FS-F1/FS-F2 stack: temporal protocol identification, indentation rates,
+stress-relaxation and creep extraction, five lumped response models
+(Kelvin-Voigt, Maxwell, standard linear solid, generalized Maxwell/Prony,
+power law), the spherical hereditary-integral models (Lee-Radok loading and
+Ting loading/unloading with contact-time memory), AICc model comparison,
+sensitivity multiverse, force-volume mapping. 14 public capabilities with
+typed errors, immutable results and explicit provenance.
+
+- temporal contract: time in seconds, strictly increasing per segment,
+ duplicates raise DUPLICATE_TIMESTAMPS, nonuniform sampling allowed
+ (never resampled), no assumed acquisition rate; a missing time axis
+ raises MISSING_TIME (a reconstructed clock requires an explicit
+ assume_uniform_rate); the instrument clock is segment.time;
+- reader limitation (explicit): the JPK and NID readers do NOT populate
+ ForceSegment.time, so no automatic general JPK/NID time-domain
+ viscoelastic analysis is claimed; FS-F3 is usable when an explicit valid
+ time axis is present or reconstructed by an explicitly requested
+ known-rate policy (assume_uniform_rate); reader timing extraction is a
+ separate future batch;
+- protocol classes: LOADING_RAMP, UNLOADING_RAMP, DISPLACEMENT_HOLD,
+ FORCE_HOLD, CREEP, STRESS_RELAXATION, TRIANGULAR_LOADING,
+ INSUFFICIENT_PROTOCOL, AMBIGUOUS_PROTOCOL; identification is rate-region
+ based (median-of-nonzero-rate thresholds); trusted instrument labels in
+ curve.metadata take precedence; a displacement hold with a decaying force
+ is STRESS_RELAXATION, a force hold with a drifting displacement is CREEP;
+- phantoms: the force traces derive from the independent oracles; the
+ piezo position is the clean position while noise lives on the force
+ channel only (the derived separation then jitters inside the FS-F1
+ work-integral tolerance); the response models are exact in the hold
+ region; ramp segments are elastic-following placeholders (documented);
+- frozen lumped equations: KV creep J(t) = (1/E)(1 - exp(-t/tau)),
+ tau = eta/E; Maxwell E(t) = E exp(-t/tau); SLS
+ E(t) = E_inf + (E0 - E_inf) exp(-t/tau_relax) with the creep form
+ J(t) = J_inf - (J_inf - J0) exp(-t/tau_retard) and the conversions
+ J0 = 1/E0, J_inf = 1/E_inf, tau_retard = tau_relax * E0/E_inf; Prony
+ E(t) = E_inf + sum E_i exp(-t/tau_i) with E_i >= 0, tau_i > 0, strictly
+ increasing tau (duplicates rejected typed) and no uniqueness claim;
+ power law E(t) = E_ref (t/t_ref)^(-alpha), 0 < alpha < 1, t = 0 excluded;
+- frozen hereditary integrals (sphere, reduced modulus):
+ Lee-Radok F(t) = c int_0^t E(t - t') d/dt' delta(t')^1.5 dt' with the
+ monotonic-contact-radius condition (LEE_RADOK_NONMONOTONIC typed);
+ Ting adds the unloading branch F(t) = c int_0^{t1(t)} ... with
+ delta(t1(t)) = delta(t) on the loading branch (contact-time memory;
+ TING_HISTORY_UNAVAILABLE typed when the history cannot be
+ reconstructed); the production quadrature is the first-order
+ Riemann-sum-in-increments rule with right-edge modulus evaluation;
+ the independent oracle uses a 16-substep midpoint rule (agreement
+ 0.5-0.7%);
+- fits: shared deterministic least-squares engine with an explicit
+ multi-start (flat-valley protection) and a normalized objective;
+ SLS-constrained parameterizations (a = (E0 - E_inf)/E0, creep
+ increments) keep the model domains valid; Lee-Radok and Ting fit the
+ SLS relaxation modulus through the integral (recovery within ~40%
+ E0/E_inf and ~50% tau on clean phantoms; the loading curve carries less
+ information than a hold); the creep absolute compliance level is
+ contact-coordinate limited (~20% of the J0 scale) so the creep recovery
+ is reported on the compliance INCREMENT (dJ, tau_retard) plus the
+ absolute level with a wide honest bound;
+- comparison: AICc over identical observations with the finite-sample
+ correction, Delta AICc < 4 ambiguity, model-relative weights only;
+- sensitivity: deterministic multiverse over contact offsets, hold-boundary
+ offsets and equilibrium-tail fractions with one-at-a-time indices
+ (contact/boundary/window) and a dominant classification (contact /
+ boundary / window / none at the 20% threshold); raw configurations and
+ failures exposed;
+- volume: per-curve identify -> prepare -> extract -> SLS mapping with
+ modulus/viscosity/relaxation-time maps, model/ambiguity/sensitivity
+ maps and an explicit failed mask (nothing silently dropped).
+
+Maturity per capability (reconciled at independent audit):
+
+- NUMERICALLY_VERIFIED (defined numerical truth on deterministic phantoms
+ and the independent analytical/hereditary oracles): indentation rate,
+ relaxation/creep extraction, the five lumped forward models and fits,
+ Lee-Radok (within the accurately demonstrated scope: forward parity
+ 0.7%, inverse bounds documented), Ting (independent history validation),
+ force-volume mapping;
+- SOFTWARE_VERIFIED (designed policies inseparable from the public
+ results): protocol identification (the rate-region arithmetic is
+ numerically verified but the protocol-type decision and the ambiguity
+ policy are designed), the model comparison (the AICc arithmetic is
+ numerically verified but the recommendation policy is part of the
+ result), the sensitivity analysis (the arithmetic is numerically
+ verified but the dominant-source interpretation is policy);
+- external: pyvisco 2.1.3 (BSD-3) is a frozen COMPATIBILITY WITNESS only:
+ the fixed-tau-grid NNLS reconstruction and the production free-tau fit
+ both reproduce the same synthetic normalized modulus within 0.10 on the
+ shared grid; no parameter equality and no CROSS_VALIDATED record;
+- no PHYSICALLY_VALIDATED claim.
+
+FS-F1 compatibility repair (proven defect, bounded): the piecewise contact
+method's polyfit crashed with an untyped LinAlgError on constant-coordinate
+windows (e.g. a flat hold); the candidate is now rejected (returns inf)
+with a rank-deficiency guard, never an untyped crash.
+
+Non-claims: no universal linear-viscoelastic validity; no physical
+validation; no unique Prony spectrum and no universal number of relaxation
+modes; no guaranteed equilibrium from a finite dwell; no automatic correct
+model; no complete systematic uncertainty; no frequency-domain
+microrheology; no active oscillatory rheology; no SMFS/unfolding support;
+no certified viscosity or modulus; no experimental cell/material truth;
+synthetic map recovery is not experimental map validation.
+
+## Single-molecule force spectroscopy (FS-F4)
+
+The FS-F4 batch adds the SMFS stack on the FS-F1/FS-F2/FS-F3 foundations:
+molecular extension with explicit zero policies, polymer fits (WLC, eWLC,
+FJC, eFJC), model comparison, unfolding-event detection and quantification,
+contour-length increments from independent fits, loading rates, Bell-Evans
+and Dudko-Hummer-Szabo kinetics, force-clamp survival with right censoring,
+population aggregation and batch orchestration. 16 public capabilities
+with typed errors, immutable results and explicit provenance.
+
+- molecular extension contract: extension = retract separation minus an
+ explicit tether zero; supported reference policies: "offset" (physical
+ offset, m), "index" (reference sample), "pre_event" (caller-supplied
+ branch start), "estimator" (the retract zero-force crossing with its own
+ diagnostics); the zero is never inferred silently from the contact;
+- frozen polymer equations: WLC F = (k_BT/Lp)[1/(4(1-x/Lc)^2) - 1/4 + x/Lc]
+ (never evaluated at or beyond the singularity); eWLC (implicit,
+ Odijk-style, solved per point by brentq with a force-scale xtol; S -> inf
+ reduces to the WLC); FJC x/Lc = coth(y) - 1/y with y = F b/k_BT (stable
+ Langevin); eFJC x/Lc = L(y) + F/Sk (Sk -> inf reduces to the FJC); the
+ WLC/FJC fits use separable closed-form structures (1-D searches over the
+ nonlinear parameter) to avoid the flat (Lc, Lp) and (Lc, b) valleys;
+- event detection is a documented heuristic (SOFTWARE_VERIFIED): sustained
+ force drops on the pull-ordered retract branch with public thresholds;
+ rejected candidates retained with reasons; the final detachment is
+ distinguished from internal unfolding (post-drop baseline return);
+ sub-threshold drops are not detected (typed NO_EVENTS);
+- contour-length increments derive from independent pre/post WLC fits on
+ the ABSOLUTE molecular extension (a branch-relative fit would absorb the
+ event offset into a biased contour length);
+- loading rates: the measured local slope of force vs time before each
+ event (least squares + robust median-of-pairs); the theoretical rate
+ (effective stiffness x pulling velocity) is reported separately, never
+ substituted;
+- Bell-Evans: the most-probable-force regression
+ F* = (k_B T/x_beta) ln(r x_beta/(k0 k_B T)) is the primary estimator
+ (the BE likelihood is degenerate toward x_beta -> 0, documented); the
+ bounded likelihood runs as a secondary with an identifiability
+ diagnosis; the survival convention is
+ S(F) = exp(-k0 k_B T/(r x_beta)(exp(F x_beta/k_B T) - 1)) (the
+ coefficient is dimensionless; an inverted convention was found and fixed
+ in the production, the oracle and the generator);
+- Dudko-Hummer-Szabo: frozen nu in {1/2, 2/3} (cusp / linear-cubic), the
+ log-space rate evaluation with a consistent exp cap (a floating-point
+ cancelation artifact in the near-boundary profile was found and fixed);
+ the fitted energy landscape is not claimed to be physically unique;
+- force clamp: Kaplan-Meier survival with right censoring (events before
+ censors at ties; events leave the risk set); median lifetime typed
+ UNDEFINED_MEDIAN when unreachable; the exponential rate is the
+ censoring-aware MLE n_events/sum(times);
+- population and batch: aggregation without molecular-identity claims;
+ per-curve results and failures retained with reasons; deterministic
+ ordering and replay.
+
+Maturity per capability (reconciled for this batch):
+
+- NUMERICALLY_VERIFIED: the four polymer fits and forward models (with
+ parameter-specific evidence: the eWLC stretch modulus and the eFJC
+ stretch scale are weakly identifiable from a single branch; the response
+ reconstruction is verified and the parameter recovery bounds are
+ documented), contour-length increments (delta-Lc within 10%; the delta is
+ largely zero-translation invariant while the absolute contours carry the
+ zero-policy error), event quantification, loading-rate arithmetic,
+ Bell-Evans (F* regression; the likelihood degeneracy documented), DHS
+ (response level with the domain-censoring identity verified; the
+ landscape non-uniqueness documented), force-clamp survival arithmetic
+ (Kaplan-Meier and the censored exponential-rate MLE verified against
+ hand-derived cases);
+- SOFTWARE_VERIFIED: compute_molecular_extension (the estimator reference
+ policy is a heuristic inside the same callable), the SMFS fit-window
+ policy, polymer-model recommendation, unfolding-event detection,
+ population grouping, batch orchestration;
+- external: no exact external polymer/kinetic profile exists; pyvisco was
+ the FS-F3 witness and does not cover the SMFS models; no CROSS_VALIDATED
+ and no PHYSICALLY_VALIDATED capability;
+- legacy chain.py (the GUI-era WLC/FJC module) is untouched and
+ unregistered; the FS-F4 models are the registered, oracle-validated
+ implementations of the frozen conventions (Marko-Siggia default, explicit
+ eWLC/eFJC stretch conventions).
+
+Non-claims: no automatic molecular identity; no universal polymer model;
+no certified contour length; no physical validation; no guaranteed single
+tether; no guaranteed unfolding interpretation; no universal event
+detector; no unique DHS energy landscape; no universal Bell-Evans validity;
+no guaranteed independence of events; no complete kinetic uncertainty; no
+hidden correction for linker or handle compliance; no experimental
+protein-state truth; no steered-MD equivalence; no force-clamp validation
+on a physical instrument; synthetic population recovery is not biological
+validation.
+
## Test-count policy
The collection total is measured with:
diff --git a/examples/force_path_work_real_curve.md b/examples/force_path_work_real_curve.md
new file mode 100644
index 0000000..8e8aba7
--- /dev/null
+++ b/examples/force_path_work_real_curve.md
@@ -0,0 +1,82 @@
+# Golden path: acquisition-path force work on a real JPK curve
+
+**Dataset**: *Atomic force microscopy indentation data of stiff and compliant
+polyacrylamide hydrogels* — DOI `10.6084/m9.figshare.11637675.v3`, licence
+**CC0** (manifest:
+`tests/validation/fixtures/jpk_forcescan2/paam_dataset_manifest.json`).
+
+This example runs the full public pipeline on one representative real curve
+(~30 lines): load, calibrate, separate, prepare, inspect coordinate-path
+diagnostics and compute acquisition-path work with signed contributions.
+
+## 1. Load, calibrate and separate
+
+```python
+from spmkit.core.io import load_force
+from spmkit.core.analysis import (
+ calibrate_force_curve,
+ compute_tip_sample_separation,
+ fit_force_baseline,
+ correct_force_baseline,
+ contact_point_ensemble,
+ coordinate_path_diagnostics,
+ integrate_force_path_work,
+)
+
+volume = load_force("PAAm_Stiff_ROI6_force-save-2019.10.25-11.18.07.055.jpk-force")
+curve = volume.curve(0)
+calibrated = calibrate_force_curve(curve).curve
+sep = compute_tip_sample_separation(calibrated)
+baseline = fit_force_baseline(sep, model="linear")
+corrected = correct_force_baseline(sep, baseline, scope="all")
+contact = contact_point_ensemble(
+ sep, methods=("threshold", "ratio_of_variances", "piecewise"), bootstrap_samples=0
+)
+print(contact.method_agreement, "contact methods agree at", contact.selected.coordinate, "m")
+```
+
+## 2. Coordinate-path diagnostics (classification only)
+
+```python
+ext = sep.extend
+diagnostics = coordinate_path_diagnostics(ext.separation)
+print(diagnostics.net_displacement) # -8.13e-06 m (approach global)
+print(diagnostics.total_variation) # 1.29e-05 m
+print(diagnostics.backtracking_fraction) # 0.815 (jitter-dominated)
+print(diagnostics.global_direction) # 'decreasing'
+print(diagnostics.strictly_monotonic) # False -> strict integration rejects this axis
+print(diagnostics.maximum_reverse_excursion) # 3.07e-09 m (nm-scale, path-level)
+```
+
+## 3. Acquisition-path work (signed, acquisition order)
+
+```python
+work = integrate_force_path_work(
+ ext.separation, ext.force,
+ provenance={"file": "PAAm ... 11.18.07.055.jpk-force", "segment": "extend"},
+)
+print(work.work_total) # -2.48e-14 J (signed path integral)
+print(work.work_forward) # contribution of steps in the global direction
+print(work.work_backward) # contribution of steps opposite the global direction
+print(work.work_total - (work.work_forward + work.work_backward)) # 0.0 (invariant)
+print(work.units) # 'J' (N * m)
+print(work.provenance["semantics"]) # 'acquisition_path'
+```
+
+`W = sum_i 0.5*(F_i + F_{i+1})*(z_{i+1} - z_i)` evaluated strictly in
+sample-acquisition order: signed `dz`, local reversals retained, no sorting,
+no `abs()`, no smoothing, no point deletion. The classification tolerance
+(if ever given) only affects diagnostics, never the integral.
+
+## What this proves (and what it does not)
+
+Proved: the real curve loads, calibrates, separates and prepares; the axis is
+globally directed (decreasing, net ≈ −8 µm) but not strictly monotone (57%
+negative increments, nm-scale reversals); acquisition-path work is computed
+deterministically with an exact decomposition invariant.
+
+Not claimed: validated material energy (the −2.5e-14 J is a path integral,
+not a thermodynamic quantity), adhesion energy per area, modulus, time-domain
+analysis, or any physical validation. The strict monotonic-coordinate
+integration (`integrate_force_work`) remains unchanged and still rejects this
+axis with `NONMONOTONIC_COORDINATE`.
diff --git a/examples/jpk_forcescan2_reader_golden_path.md b/examples/jpk_forcescan2_reader_golden_path.md
new file mode 100644
index 0000000..a0342e6
--- /dev/null
+++ b/examples/jpk_forcescan2_reader_golden_path.md
@@ -0,0 +1,91 @@
+# Golden path: JPK ForceScan 2.0 reader (lcd-info profile)
+
+**Dataset**: *Atomic force microscopy indentation data of stiff and compliant
+polyacrylamide hydrogels* — DOI `10.6084/m9.figshare.11637675.v3`, licence
+**CC0**. Representative file:
+`PAAm_Stiff_ROI6_force-save-2019.10.25-11.18.07.055.jpk-force` (SHA-256
+`3403e33e...a336eb`; manifest:
+`tests/validation/fixtures/jpk_forcescan2/paam_dataset_manifest.json`).
+
+This page proves the reader golden path with the **public API only** (~25
+lines): the file loads, channels are calibrated, segments are coherent, arrays
+are finite, and units are physically plausible. No hidden defaults are used.
+
+## 1. Load
+
+```python
+from spmkit.core.io import load_force
+
+volume = load_force("PAAm_Stiff_ROI6_force-save-2019.10.25-11.18.07.055.jpk-force")
+curve = volume.curve(0) # las curvas sueltas se envuelven en un volumen 1x1
+print(curve.metadata["profile"]) # 'lcd-info' (ForceScan 2.0, indirección lcd-info)
+```
+
+## 2. Inspect curves and segments
+
+```python
+print(len(curve.segments)) # 2
+for s in curve.segments:
+ print(s.segment_type, s.direction, len(s), s.state)
+# extend approach 7894 force_n
+# retract retract 8000 force_n
+print(curve.segments[0].metadata)
+# {'num_points': 7894, 'lcd_info': {'height': 1, 'vDeflection': 2}}
+```
+
+## 3. Inspect units and calibration
+
+```python
+ext = curve.extend
+# raw_height ya está calibrado en metros (cadena nominal -> calibrated del archivo)
+print("height unit: m (calibrado por el archivo)")
+print(curve.calibration)
+# Calibration(invols=6.068792445314747e-08, spring_constant=0.04659723113213052,
+# method='jpk_metadata', provenance={'source': '...jpk-force', 'profile': 'lcd-info'})
+```
+
+The InVOLS (`6.069e-8 m/V`) and spring constant (`0.0466 N/m`) come **from the
+file's own calibration chain** (vDeflection slots `distance` and `force` in
+`shared-data/header.properties`), never from a guess.
+
+## 4. Select one approach/retract pair; verify finiteness and plausibility
+
+```python
+import numpy as np
+ret = curve.retract
+assert np.all(np.isfinite(ext.force)) and np.all(np.isfinite(ret.force))
+print(ext.raw_height.min(), ext.raw_height.max()) # 2.32e-06 .. 9.77e-06 m (µm)
+print(ext.deflection.max()) # 6.95e-07 m (0.7 µm)
+print(ext.force.max()) # 3.24e-08 N (32 nN)
+print(curve.calibration.spring_constant) # 0.0466 N/m (cantiléver blando)
+```
+
+Physically plausible: a 15 µm z-scanner approach over ~7 µm with up to 32 nN on
+a compliant hydrogel, consistent with a soft cantilever.
+
+## 5. Non-mutating metadata summary
+
+```python
+summary = {
+ "profile": curve.metadata["profile"],
+ "segments": [s.segment_type for s in curve.segments],
+ "points": [len(s) for s in curve.segments],
+ "invols": curve.calibration.invols,
+ "spring_constant": curve.calibration.spring_constant,
+ "height_range_m": [float(ext.raw_height.min()), float(ext.raw_height.max())],
+ "force_max_n": float(ext.force.max()),
+}
+# summary no modifica curva ni archivo; el lector no muta ningún diccionario crudo
+```
+
+## What this proves (and what it does not)
+
+Proved: the lcd-info profile loads through the public entry point; height and
+vDeflection are calibrated with file-declared chains; segmentation is coherent
+(extend-spm/retract-spm); arrays are finite; units are m/N; no defaults were
+substituted (profile, InVOLS and k are all read from the archive).
+
+Not claimed: universal JPK compatibility, physical validation, material
+modulus, time-domain analysis, or SMFS compatibility. See
+`docs/force-spectroscopy.md` for the analysis pipeline, and the campaign
+report for the full ten-file evidence.
diff --git a/pyproject.toml b/pyproject.toml
index 416a7da..d8ad877 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -70,6 +70,9 @@ spmkit = "spmkit.cli.app:app"
[tool.hatch.build.targets.wheel]
packages = ["src/spmkit"]
+[tool.hatch.build.targets.wheel.force-include]
+"src/spmkit/core/capabilities.json" = "spmkit/core/capabilities.json"
+
[tool.hatch.build.targets.sdist]
# El sdist no necesita las imágenes de docs (banners/capturas ~4 MB); mantiene el texto.
exclude = [
diff --git a/scripts/force_path_work_paam_campaign.py b/scripts/force_path_work_paam_campaign.py
new file mode 100644
index 0000000..199a96c
--- /dev/null
+++ b/scripts/force_path_work_paam_campaign.py
@@ -0,0 +1,193 @@
+"""Campaña real FS-R1C: path work sobre los 10 archivos PAAm externos.
+
+Script standalone. Para cada ``*.jpk-force`` del directorio:
+
+ load_force -> calibrate -> tip-sample separation -> baseline/contact
+ -> integrate_force_path_work (orden de adquisición, sin reparar nada)
+
+Verifica los SHA-256 contra el manifiesto committeado y escribe salidas
+deterministas en /tmp:
+
+ /tmp/spmkit_force_path_work_paam_campaign.json
+ /tmp/spmkit_force_path_work_paam_campaign.md
+
+Uso:
+
+ .venv/bin/python scripts/force_path_work_paam_campaign.py --dir
+
+El trabajo medido NO se interpreta como energía de material validada.
+"""
+
+from __future__ import annotations
+
+import argparse
+import glob
+import hashlib
+import json
+import sys
+from pathlib import Path
+
+import numpy as np
+
+from spmkit.core.analysis import (
+ calibrate_force_curve,
+ compute_tip_sample_separation,
+ contact_point_ensemble,
+ correct_force_baseline,
+ fit_force_baseline,
+ integrate_force_path_work,
+)
+from spmkit.core.io import load_force
+
+_MANIFEST = (
+ Path(__file__).resolve().parents[1]
+ / "tests"
+ / "validation"
+ / "fixtures"
+ / "jpk_forcescan2"
+ / "paam_dataset_manifest.json"
+)
+_CAMPAIGN_JSON = "/tmp/spmkit_force_path_work_paam_campaign.json"
+_CAMPAIGN_MD = "/tmp/spmkit_force_path_work_paam_campaign.md"
+
+
+def _run(dataset_dir: Path) -> dict:
+ manifest = json.loads(_MANIFEST.read_text())
+ expected = {f["name"]: f["sha256"] for f in manifest["files"]}
+ results: dict = {"dataset": manifest["dataset"], "files": []}
+ for p_str in sorted(glob.glob(str(dataset_dir / "*.jpk-force"))):
+ path = Path(p_str)
+ sha = hashlib.sha256(path.read_bytes()).hexdigest()
+ rec: dict = {
+ "file": path.name,
+ "sha256": sha,
+ "hash_in_manifest": sha in expected.values(),
+ }
+ try:
+ curve = load_force(path).curve(0)
+ calibrated = calibrate_force_curve(curve).curve
+ sep_curve = compute_tip_sample_separation(calibrated)
+ baseline = fit_force_baseline(sep_curve, model="linear")
+ correct_force_baseline(sep_curve, baseline, scope="all")
+ contact = contact_point_ensemble(
+ sep_curve,
+ methods=("threshold", "ratio_of_variances", "piecewise"),
+ bootstrap_samples=0,
+ )
+ ext = sep_curve.extend
+ if ext is None or ext.separation is None or ext.force is None:
+ raise ValueError("extend segment without separation/force")
+ z = np.asarray(ext.separation, dtype=np.float64)
+ f = np.asarray(ext.force, dtype=np.float64)
+ work = integrate_force_path_work(
+ z,
+ f,
+ provenance={"file": path.name, "segment": "extend", "axis": "separation"},
+ )
+ d = work.diagnostics
+ rec["result"] = {
+ "success": True,
+ "curves": 1,
+ "segment": "extend",
+ "samples": d.n_samples,
+ "net_displacement": d.net_displacement,
+ "total_variation": d.total_variation,
+ "forward_distance": d.forward_distance,
+ "backward_distance": d.backward_distance,
+ "backtracking_fraction": d.backtracking_fraction,
+ "exact_positive_steps": d.exact_positive_steps,
+ "exact_negative_steps": d.exact_negative_steps,
+ "exact_zero_steps": d.exact_zero_steps,
+ "maximum_reverse_step": d.maximum_reverse_step,
+ "maximum_reverse_excursion": d.maximum_reverse_excursion,
+ "global_direction": d.global_direction,
+ "strictly_monotonic": d.strictly_monotonic,
+ "globally_directed": d.globally_directed,
+ "path_work_j": work.work_total,
+ "work_forward_j": work.work_forward,
+ "work_backward_j": work.work_backward,
+ "absolute_accumulated_work_j": work.absolute_accumulated_work,
+ "units": work.units,
+ "contact_agreement": contact.method_agreement,
+ "contact_coordinate": contact.selected.coordinate,
+ "baseline_slope": baseline.slope,
+ "warnings": list(work.warnings),
+ }
+ except Exception as exc: # noqa: BLE001 - la campaña registra cualquier fallo
+ rec["result"] = {
+ "success": False,
+ "exception": type(exc).__name__,
+ "code": getattr(exc, "code", None),
+ "message": str(exc)[:200],
+ }
+ results["files"].append(rec)
+ results["summary"] = {
+ "files": len(results["files"]),
+ "loaded": sum(1 for f in results["files"] if f["result"]["success"]),
+ "failed": sum(1 for f in results["files"] if not f["result"]["success"]),
+ }
+ return results
+
+
+def _render_md(results: dict) -> str:
+ lines = [
+ "# FS-R1C PAAm acquisition-path work campaign",
+ "",
+ f"- dataset: {results['dataset']['title']}",
+ f"- DOI: {results['dataset']['doi']} | licence: {results['dataset']['licence']}",
+ f"- files: {results['summary']['files']} | loaded: {results['summary']['loaded']} | "
+ f"failed: {results['summary']['failed']}",
+ "",
+ "| file | sha (manifest) | samples | net disp (m) | total var (m) | backtrack frac "
+ "| neg/pos/zero steps | max rev step (m) | max rev exc (m) | direction | path work (J) |",
+ "|---|---|---|---|---|---|---|---|---|---|---|",
+ ]
+ for f in results["files"]:
+ r = f["result"]
+ if r["success"]:
+ rows = [
+ f["file"][:36],
+ "yes" if f["hash_in_manifest"] else "NO",
+ str(r["samples"]),
+ f"{r['net_displacement']:.4e}",
+ f"{r['total_variation']:.4e}",
+ f"{r['backtracking_fraction']:.3f}",
+ f"{r['exact_negative_steps']}/{r['exact_positive_steps']}/{r['exact_zero_steps']}",
+ f"{r['maximum_reverse_step']:.2e}",
+ f"{r['maximum_reverse_excursion']:.2e}",
+ r["global_direction"],
+ f"{r['path_work_j']:.5e}",
+ ]
+ else:
+ rows = [f["file"][:36], "yes" if f["hash_in_manifest"] else "NO", "FAILED",
+ r["code"] or r["exception"], "-", "-", "-", "-", "-", "-", "-"]
+ lines.append("| " + " | ".join(rows) + " |")
+ lines.append("")
+ lines.append(
+ "Definition: W = sum_i 0.5*(F_i + F_{i+1})*(z_{i+1} - z_i) en orden de adquisición;"
+ )
+ lines.append("dz firmados, sin ordenar/suavizar/eliminar; tolerancia solo de clasificación.")
+ lines.append(
+ "El trabajo medido NO es energía de material validada ni energía de adhesión por área."
+ )
+ return "\n".join(lines) + "\n"
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description="FS-R1C external PAAm path-work campaign")
+ parser.add_argument("--dir", required=True, type=Path)
+ args = parser.parse_args()
+ results = _run(args.dir)
+ Path(_CAMPAIGN_JSON).write_text(json.dumps(results, indent=1) + "\n")
+ Path(_CAMPAIGN_MD).write_text(_render_md(results))
+ print(
+ "files={} loaded={} failed={}".format( # noqa: UP032
+ results["summary"]["files"], results["summary"]["loaded"], results["summary"]["failed"]
+ )
+ )
+ print(f"wrote {_CAMPAIGN_JSON} and {_CAMPAIGN_MD}")
+ return 0 if results["summary"]["failed"] == 0 else 1
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/scripts/generate_capability_ledger.py b/scripts/generate_capability_ledger.py
new file mode 100644
index 0000000..4885d05
--- /dev/null
+++ b/scripts/generate_capability_ledger.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+"""Generate docs/parity/CAPABILITY_LEDGER.md from the packaged capability
+ledger JSON (src/spmkit/core/capabilities.json).
+
+The Markdown is generated output, not a hand-maintained source of truth.
+Regeneration is byte-identical: no timestamps, absolute paths, branch or
+commit metadata are emitted.
+"""
+
+from __future__ import annotations
+
+import json
+import sys
+from pathlib import Path
+
+REPO_ROOT = Path(__file__).resolve().parents[1]
+LEDGER_JSON = REPO_ROOT / "src" / "spmkit" / "core" / "capabilities.json"
+OUTPUT_MD = REPO_ROOT / "docs" / "parity" / "CAPABILITY_LEDGER.md"
+
+_FAMILY_TITLES = {
+ "IMG.LEVEL": "Leveling",
+ "IMG.BACKGROUND": "Background",
+ "IMG.SCANLINE": "Scan-line corrections",
+ "IMG.FILTER": "Neighborhood filters",
+ "IMG.MORPH": "Morphology",
+ "IMG.STATS": "Statistics",
+ "IMG.INTERPOLATION": "Interpolation",
+}
+
+
+def render(records: list[dict]) -> str:
+ lines: list[str] = []
+ lines.append("# SPMKit Capability Ledger")
+ lines.append("")
+ lines.append("Stable scientific capabilities registered by the Operation "
+ "Registry v1.")
+ lines.append("")
+ lines.append(f"- schema_version: {records[0]['schema_version'] if False else 1}")
+ lines.append(f"- operations: {len(records)}")
+ lines.append("")
+ lines.append("Source of truth: `src/spmkit/core/capabilities.json` "
+ "(generated view; do not edit by hand).")
+ lines.append("")
+ for record in records:
+ cap = record["capability_id"]
+ op = record["operation_id"]
+ lines.append(f"## {cap}")
+ lines.append("")
+ lines.append(f"- operation_id: `{op}`")
+ lines.append(f"- public_name: `{record['public_name']}`")
+ lines.append(f"- public_import: `{record['public_import']}`")
+ lines.append(f"- family: {record['family']}")
+ lines.append(f"- maturity: {record['maturity']}")
+ lines.append(f"- status: {record['status']}")
+ ref = record["reference"]
+ lines.append(f"- reference: {ref['software']} {ref['version']} "
+ f"({ref['name']})")
+ lines.append(f"- evidence profile: `{ref['profile']}`")
+ lines.append("")
+ lines.append(f"- contract: {record['contract']}")
+ lines.append("")
+ lines.append("- semantics:")
+ lines.append(f" - mask: {record['mask_semantics']}")
+ lines.append(f" - ROI: {'yes' if record['roi_support'] else 'no'}")
+ lines.append(f" - NaN policy: {record['nan_policy']}")
+ lines.append(f" - border: {record['border_policy']}")
+ lines.append(f" - mutation: {record['mutation_policy']}")
+ lines.append(f" - result: {record['result_type']}")
+ lines.append(f" - units: {record['units']}")
+ lines.append("")
+ lines.append("- parameters:")
+ for p in record["parameters"]:
+ default = p["default"]
+ default_s = "required" if not p["has_default"] else repr(default)
+ extra = ""
+ if p.get("bounds"):
+ extra += f" bounds={p['bounds']}"
+ if p.get("enum_values"):
+ extra += f" values={p['enum_values']}"
+ lines.append(f" - `{p['name']}` ({p['kind']}, {default_s}"
+ f"{extra}) — {p['description']}")
+ lines.append("")
+ lines.append("- evidence:")
+ for e in record["evidence"]:
+ lines.append(f" - `{e}`")
+ lines.append("")
+ if record["known_deviations"]:
+ lines.append("- known deviations:")
+ for d in record["known_deviations"]:
+ lines.append(f" - {d}")
+ lines.append("")
+ return "\n".join(lines)
+
+
+def main() -> int:
+ data = json.loads(LEDGER_JSON.read_text(encoding="utf-8"))
+ records = sorted(data["capabilities"], key=lambda c: c["capability_id"])
+ OUTPUT_MD.parent.mkdir(parents=True, exist_ok=True)
+ OUTPUT_MD.write_text(render(records), encoding="utf-8")
+ print(f"wrote {OUTPUT_MD}")
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/scripts/jpk_forcescan2_campaign.py b/scripts/jpk_forcescan2_campaign.py
new file mode 100644
index 0000000..68c7358
--- /dev/null
+++ b/scripts/jpk_forcescan2_campaign.py
@@ -0,0 +1,288 @@
+"""Campaña externa de 10 archivos JPK ForceScan 2.0 (FS-R1B).
+
+Script standalone: parsea todos los ``*.jpk-force`` de un directorio con el
+lector público ``load_force``, verifica los SHA-256 contra el manifiesto
+committeado (``tests/validation/fixtures/jpk_forcescan2/paam_dataset_manifest.json``)
+y escribe dos salidas deterministas:
+
+ /tmp/spmkit_jpk_forcescan2_campaign.json
+ /tmp/spmkit_jpk_forcescan2_campaign.md
+
+Uso:
+
+ .venv/bin/python scripts/jpk_forcescan2_campaign.py --dir
+
+Los archivos originales quedan fuera de Git (ver manifiesto). Este script no
+modifica nada: solo lee los archivos y escribe en /tmp.
+"""
+
+from __future__ import annotations
+
+import argparse
+import glob
+import hashlib
+import json
+import re
+import sys
+import zipfile
+from pathlib import Path
+from typing import Any
+
+import numpy as np
+
+from spmkit.core.io import load_force
+
+_MANIFEST = (
+ Path(__file__).resolve().parents[1]
+ / "tests"
+ / "validation"
+ / "fixtures"
+ / "jpk_forcescan2"
+ / "paam_dataset_manifest.json"
+)
+
+_CAMPAIGN_JSON = "/tmp/spmkit_jpk_forcescan2_campaign.json"
+_CAMPAIGN_MD = "/tmp/spmkit_jpk_forcescan2_campaign.md"
+
+
+def _parse_props(raw: bytes) -> dict[str, str]:
+ props: dict[str, str] = {}
+ for line in raw.decode("utf-8", "replace").splitlines():
+ line = line.strip()
+ if not line or line.startswith(("#", "!")) or "=" not in line:
+ continue
+ key, _, value = line.partition("=")
+ props[key.strip()] = value.strip()
+ return props
+
+
+def _audit_archive(path: Path) -> dict[str, object]:
+ """Campos de auditoría de calibración leídos del propio archivo (sin parsear)."""
+ with zipfile.ZipFile(path) as zf:
+ names = zf.namelist()
+ shared = (
+ _parse_props(zf.read("shared-data/header.properties"))
+ if "shared-data/header.properties" in names
+ else {}
+ )
+ seg_ids = sorted(
+ {
+ int(m.group(1))
+ for n in names
+ if (m := re.search(r"segments/(\d+)/segment-header\.properties$", n))
+ }
+ )
+ segs = {}
+ for seg in seg_ids:
+ sp = _parse_props(zf.read(f"segments/{seg}/segment-header.properties"))
+ segs[str(seg)] = {
+ "name": sp.get("force-segment-header.name.name"),
+ "num_points": sp.get("force-segment-header.num-points"),
+ "time_stamp": sp.get("force-segment-header.time-stamp"),
+ "duration": sp.get("force-segment-header.duration"),
+ "baseline": sp.get("force-segment-header.baseline.baseline"),
+ "lcd_refs": {
+ ch: sp.get(f"channel.{ch}.lcd-info.*")
+ for ch in (sp.get("channels.list") or "").split()
+ },
+ }
+ h = "lcd-info.1."
+ v = "lcd-info.2."
+ return {
+ "lcd_infos_count": shared.get("lcd-infos.count"),
+ "height": {
+ "raw_dtype": ">i4"
+ if shared.get(f"{h}type") == "integer-data"
+ else shared.get(f"{h}type"),
+ "encoder_multiplier": shared.get(f"{h}encoder.scaling.multiplier"),
+ "encoder_offset": shared.get(f"{h}encoder.scaling.offset"),
+ "encoder_unit": shared.get(f"{h}encoder.scaling.unit.unit"),
+ "slots": shared.get(f"{h}conversion-set.conversions.list"),
+ "default": shared.get(f"{h}conversion-set.conversions.default"),
+ "final_unit": shared.get(
+ f"{h}conversion-set.conversion.calibrated.scaling.unit.unit"
+ ),
+ },
+ "vDeflection": {
+ "raw_dtype": ">i4"
+ if shared.get(f"{v}type") == "integer-data"
+ else shared.get(f"{v}type"),
+ "encoder_multiplier": shared.get(f"{v}encoder.scaling.multiplier"),
+ "encoder_offset": shared.get(f"{v}encoder.scaling.offset"),
+ "encoder_unit": shared.get(f"{v}encoder.scaling.unit.unit"),
+ "invols": shared.get(f"{v}conversion-set.conversion.distance.scaling.multiplier"),
+ "invols_unit": shared.get(
+ f"{v}conversion-set.conversion.distance.scaling.unit.unit"
+ ),
+ "spring_constant": shared.get(
+ f"{v}conversion-set.conversion.force.scaling.multiplier"
+ ),
+ "spring_unit": shared.get(f"{v}conversion-set.conversion.force.scaling.unit.unit"),
+ "slots": shared.get(f"{v}conversion-set.conversions.list"),
+ },
+ "segments": segs,
+ }
+
+
+def _monotonicity(arr: np.ndarray) -> dict[str, object]:
+ d = np.diff(arr)
+ return {
+ "n_decreasing": int(np.sum(d < 0)),
+ "n_increasing": int(np.sum(d > 0)),
+ "n_zero": int(np.sum(d == 0)),
+ "monotonic_decreasing": bool(np.all(d <= 0)),
+ }
+
+
+def _run(dataset_dir: Path) -> dict[str, Any]:
+ manifest = json.loads(_MANIFEST.read_text())
+ expected = {f["name"]: f["sha256"] for f in manifest["files"]}
+ results: dict[str, Any] = {"dataset": manifest["dataset"], "files": []}
+ loaded = failed = 0
+ for p_str in sorted(glob.glob(str(dataset_dir / "*.jpk-force"))):
+ path = Path(p_str)
+ blob = path.read_bytes()
+ sha = hashlib.sha256(blob).hexdigest()
+ rec: dict[str, Any] = {
+ "file": path.name,
+ "size": len(blob),
+ "sha256": sha,
+ "hash_in_manifest": sha in expected.values(),
+ "expected_sha": next((k for k, v in expected.items() if v == sha), None),
+ "audit": _audit_archive(path),
+ }
+ try:
+ volume = load_force(path)
+ curve = volume.curve(0)
+ ext = curve.extend
+ fseg = ext if ext is not None else curve.segments[0]
+ rec["result"] = {
+ "success": True,
+ "volume_curves": volume.n_curves,
+ "segments": [s.segment_type for s in curve.segments],
+ "directions": [s.direction for s in curve.segments],
+ "point_counts": [int(len(s)) for s in curve.segments],
+ "state": fseg.state,
+ "height_units": "m",
+ "deflection_units": "m",
+ "force_units": "N",
+ "height_range_extend": [
+ float(np.min(fseg.raw_height)),
+ float(np.max(fseg.raw_height)),
+ ],
+ }
+ rec["result"]["finite"] = {
+ "height": int(np.count_nonzero(np.isfinite(fseg.raw_height))),
+ "deflection": (
+ int(np.count_nonzero(np.isfinite(fseg.deflection)))
+ if fseg.deflection is not None
+ else None
+ ),
+ "force": int(np.count_nonzero(np.isfinite(fseg.force)))
+ if fseg.force is not None
+ else None,
+ }
+ rec["result"]["monotonicity_extend_height"] = _monotonicity(fseg.raw_height)
+ rec["result"]["calibration"] = (
+ {
+ "invols": curve.calibration.invols,
+ "spring_constant": curve.calibration.spring_constant,
+ "method": curve.calibration.method,
+ "provenance": curve.calibration.provenance,
+ }
+ if curve.calibration is not None
+ else None
+ )
+ rec["result"]["profile"] = curve.metadata.get("profile")
+ rec["result"]["lcd_info"] = curve.segments[0].metadata.get("lcd_info")
+ loaded += 1
+ except Exception as exc: # noqa: BLE001 - la campaña registra cualquier fallo
+ rec["result"] = {
+ "success": False,
+ "exception": type(exc).__name__,
+ "code": getattr(exc, "code", None),
+ "message": str(exc)[:200],
+ }
+ failed += 1
+ results["files"].append(rec)
+ results["summary"] = {"files": len(results["files"]), "loaded": loaded, "failed": failed}
+ return results
+
+
+def _render_md(results: dict[str, Any]) -> str:
+ lines = [
+ "# SPMKit JPK ForceScan 2.0 campaign (FS-R1B)",
+ "",
+ f"- dataset: {results['dataset']['title']}",
+ f"- DOI: {results['dataset']['doi']} | licence: {results['dataset']['licence']}",
+ "- files: {} | loaded: {} | failed: {}".format( # noqa: UP032
+ results["summary"]["files"], results["summary"]["loaded"], results["summary"]["failed"]
+ ),
+ "",
+ "| file | sha256 (manifest) | segments | state | points | height range (m) |",
+ "| force range (N) | invols | k | profile |",
+ "|---|---|---|---|---|---|---|---|---|---|",
+ ]
+ for f in results["files"]:
+ r = f["result"]
+ if r["success"]:
+ rows = [
+ f["file"][:42],
+ "yes" if f["hash_in_manifest"] else "NO",
+ "+".join(r["segments"]),
+ r["state"],
+ "+".join(str(n) for n in r["point_counts"]),
+ f"{r['height_range_extend'][0]:.4g}..{r['height_range_extend'][1]:.4g}",
+ f"{r['finite']['force']}/{max(r['point_counts'])} finite",
+ f"{r['calibration']['invols']:.4g}" if r["calibration"] else "-",
+ f"{r['calibration']['spring_constant']:.4g}" if r["calibration"] else "-",
+ str(r["profile"]),
+ ]
+ else:
+ rows = [
+ f["file"][:42],
+ "yes" if f["hash_in_manifest"] else "NO",
+ "FAILED",
+ r["code"] or r["exception"],
+ "-",
+ "-",
+ "-",
+ "-",
+ "-",
+ "-",
+ ]
+ lines.append("| " + " | ".join(rows) + " |")
+ lines.append("")
+ lines.append("Calibration chain (identical across files):")
+ lines.append(
+ "- height: int32 -> V (mult 2.653565956897467E-8, offset 56.98910501326783) "
+ "-> nominal (m) -> calibrated (m, x0.78014)"
+ )
+ lines.append(
+ "- vDeflection: int32 -> V (mult 5.568822848285905E-9, offset -1.2012213894932133E-4) "
+ "-> distance (m, invols 6.068792445314747E-8) -> force (N, k 0.04659723113213052)"
+ )
+ lines.append(
+ "- no pause/dwell segments in any file; 2 segments per curve (extend-spm, retract-spm)"
+ )
+ return "\n".join(lines) + "\n"
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description="FS-R1B external 10-file JPK campaign")
+ parser.add_argument("--dir", required=True, type=Path, help="directorio con los .jpk-force")
+ args = parser.parse_args()
+ results = _run(args.dir)
+ Path(_CAMPAIGN_JSON).write_text(json.dumps(results, indent=1) + "\n")
+ Path(_CAMPAIGN_MD).write_text(_render_md(results))
+ print(
+ "files={} loaded={} failed={}".format( # noqa: UP032
+ results["summary"]["files"], results["summary"]["loaded"], results["summary"]["failed"]
+ )
+ )
+ print(f"wrote {_CAMPAIGN_JSON} and {_CAMPAIGN_MD}")
+ return 0 if results["summary"]["failed"] == 0 else 1
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/src/spmkit/core/__init__.py b/src/spmkit/core/__init__.py
index 50859ff..81c4522 100644
--- a/src/spmkit/core/__init__.py
+++ b/src/spmkit/core/__init__.py
@@ -11,6 +11,14 @@
from spmkit.core import analysis, batch, export, io, models, viz
from spmkit.core.io import load
from spmkit.core.models import SPMChannel, SPMData
+from spmkit.core.registry import (
+ CapabilitySpec,
+ ParameterSpec,
+ filter_operations,
+ get_operation,
+ list_operations,
+ resolve_callable,
+)
from spmkit.core.verify import NidTrace, trace_nid
__all__ = [
@@ -22,6 +30,12 @@
"batch",
"load",
"SPMData",
+ "CapabilitySpec",
+ "ParameterSpec",
+ "get_operation",
+ "list_operations",
+ "filter_operations",
+ "resolve_callable",
"SPMChannel",
"trace_nid",
"NidTrace",
diff --git a/src/spmkit/core/analysis/__init__.py b/src/spmkit/core/analysis/__init__.py
index 0cff264..3573f2f 100644
--- a/src/spmkit/core/analysis/__init__.py
+++ b/src/spmkit/core/analysis/__init__.py
@@ -3,15 +3,19 @@
from spmkit.core.analysis import (
background,
calibration,
+ derivatives,
+ force_foundation,
forcecurve,
forcevolume,
grains,
+ interpolation,
kpfm,
leveling,
mechanics,
profiles,
resonance,
roughness,
+ scanline,
simulation,
spectral,
)
@@ -48,15 +52,171 @@
remove_sphere_revolution_background,
remove_spline_background,
)
+from spmkit.core.analysis.derivatives import (
+ gradient_direction,
+ gwyddion_gradient_magnitude,
+ gwyddion_prewitt_x,
+ gwyddion_prewitt_y,
+ gwyddion_sobel_x,
+ gwyddion_sobel_y,
+)
+from spmkit.core.analysis.filters import (
+ gwyddion_gaussian_filter,
+ gwyddion_median_filter,
+ gwyddion_rank_filter,
+)
+from spmkit.core.analysis.force_foundation import (
+ ContactPointCandidate,
+ ContactPointResult,
+ CoordinatePathDiagnostics,
+ ForceBaselineResult,
+ ForceCalibrationResult,
+ ForceCurveQualityResult,
+ ForceEventResult,
+ ForceFoundationError,
+ ForcePathWorkResult,
+ ForcePreparationResult,
+ ForceSegmentationResult,
+ ForceWorkResult,
+ calibrate_force_curve,
+ compute_tip_sample_separation,
+ contact_point_ensemble,
+ contact_point_piecewise,
+ contact_point_ratio_of_variances,
+ contact_point_threshold,
+ coordinate_path_diagnostics,
+ correct_force_baseline,
+ extract_force_events,
+ fit_force_baseline,
+ identify_force_segments,
+ integrate_force_path_work,
+ integrate_force_work,
+ prepare_force_curve,
+ score_force_curve_quality,
+)
+from spmkit.core.analysis.force_mechanics import (
+ BootstrapForceFitResult,
+ ContactMechanicsFitResult,
+ FitWindowResult,
+ ForceFitDiagnosticResult,
+ ForceFitSensitivityResult,
+ ForceMechanicsError,
+ ForceVolumeMechanicsResult,
+ ModelComparisonResult,
+ analyze_force_fit_sensitivity,
+ bootstrap_force_fit,
+ compare_contact_models,
+ compute_indentation,
+ diagnose_force_fit,
+ fit_dmt,
+ fit_flat_punch,
+ fit_force_volume_mechanics,
+ fit_hertz_sphere,
+ fit_jkr,
+ fit_sneddon_cone,
+ forward_model,
+ select_contact_fit_window,
+)
+from spmkit.core.analysis.force_smfs import (
+ ContourLengthIncrementResult,
+ DynamicForceSpectroscopyFitResult,
+ ForceClampSurvivalResult,
+ LoadingRateResult,
+ MolecularExtensionResult,
+ PolymerFitResult,
+ PolymerModelComparisonResult,
+ SMFSBatchResult,
+ SmfsError,
+ SMFSFitWindowResult,
+ SMFSPopulationResult,
+ UnfoldingEvent,
+ UnfoldingEventResult,
+ analyze_smfs_batch,
+ analyze_smfs_event_population,
+ bell_evans_pdf,
+ bell_evans_rate,
+ bell_evans_survival,
+ compare_polymer_models,
+ compute_event_loading_rates,
+ compute_molecular_extension,
+ detect_unfolding_events,
+ dhs_log_pdf,
+ dhs_log_rate,
+ dhs_pdf,
+ dhs_rate,
+ estimate_force_clamp_survival,
+ extensible_fjc_extension,
+ extensible_wlc_force,
+ fit_bell_evans,
+ fit_dudko_hummer_szabo,
+ fit_extensible_freely_jointed_chain,
+ fit_extensible_worm_like_chain,
+ fit_freely_jointed_chain,
+ fit_worm_like_chain,
+ fjc_extension,
+ infer_contour_length_increments,
+ langevin,
+ quantify_unfolding_events,
+ select_smfs_fit_windows,
+ wlc_force,
+)
+from spmkit.core.analysis.force_viscoelasticity import (
+ CreepResponseResult,
+ ForceVolumeViscoelasticityResult,
+ IndentationRateResult,
+ ProtocolRegion,
+ RelaxationResponseResult,
+ ViscoelasticFitResult,
+ ViscoelasticityError,
+ ViscoelasticModelComparisonResult,
+ ViscoelasticProtocolResult,
+ ViscoelasticSensitivityResult,
+ analyze_viscoelastic_sensitivity,
+ compare_viscoelastic_models,
+ compute_indentation_rate,
+ extract_creep_compliance,
+ extract_stress_relaxation,
+ fit_force_volume_viscoelasticity,
+ fit_generalized_maxwell,
+ fit_kelvin_voigt,
+ fit_lee_radok_sphere,
+ fit_maxwell,
+ fit_power_law_relaxation,
+ fit_standard_linear_solid,
+ fit_ting_sphere,
+ forward_generalized_maxwell_modulus,
+ forward_generalized_maxwell_normalized,
+ forward_kelvin_voigt_compliance,
+ forward_maxwell_modulus,
+ forward_maxwell_normalized,
+ forward_power_law_modulus,
+ forward_sls_compliance,
+ forward_sls_modulus,
+ identify_viscoelastic_protocol,
+ lee_radok_force,
+ reduced_modulus,
+ sls_creep_to_relaxation,
+ sls_relaxation_to_creep,
+ spherical_coefficient,
+ ting_force,
+ validate_time_axis,
+)
from spmkit.core.analysis.forcecurve import ForceCurveFit
from spmkit.core.analysis.forcevolume import VolumeResult, analyze_volume
from spmkit.core.analysis.grains import GrainResult
+from spmkit.core.analysis.interpolation import (
+ gwydion_interpolate_data_under_mask,
+)
from spmkit.core.analysis.kpfm import CPDResult
from spmkit.core.analysis.leveling import (
GwyddionAlignRowsDirection,
GwyddionAlignRowsMaskMode,
+ gwyddion_align_rows_facet_tilt,
+ gwyddion_align_rows_match,
gwyddion_align_rows_median,
gwyddion_align_rows_median_of_differences,
+ gwyddion_align_rows_modus,
+ gwyddion_align_rows_polynomial,
gwyddion_align_rows_trimmed_mean,
gwyddion_align_rows_trimmed_mean_of_differences,
gwyddion_path_level,
@@ -75,11 +235,21 @@
ThermalSpectrum,
)
from spmkit.core.analysis.roughness import RoughnessResult
+from spmkit.core.analysis.scanline import (
+ GwyddionMaskCombineMode,
+ GwyddionScarPolarity,
+ gwydion_mark_inverted_rows,
+ gwydion_mark_scars,
+ gwydion_remove_scars,
+ gwydion_step_block_correction,
+ gwydion_step_line_correction,
+)
from spmkit.core.analysis.simulation import SimulatedCantilever
from spmkit.core.analysis.spectral import FractalResult, RadialPSD
__all__ = [
"background",
+ "derivatives",
"BackgroundResult",
"GwyddionArcDirection",
"analyze_arc_revolution_background",
@@ -99,11 +269,161 @@
"gwyddion_flat_disc_opening",
"GwyddionAlignRowsDirection",
"GwyddionAlignRowsMaskMode",
+ "gwyddion_align_rows_facet_tilt",
+ "gwyddion_align_rows_match",
"gwyddion_align_rows_median",
"gwyddion_align_rows_median_of_differences",
+ "gwyddion_align_rows_modus",
+ "gwyddion_align_rows_polynomial",
"gwyddion_align_rows_trimmed_mean",
"gwyddion_align_rows_trimmed_mean_of_differences",
"gwyddion_path_level",
+ "gwyddion_rank_filter",
+ "gwyddion_median_filter",
+ "gwyddion_gaussian_filter",
+ "gwyddion_sobel_x",
+ "gwyddion_sobel_y",
+ "gwyddion_prewitt_x",
+ "gwyddion_prewitt_y",
+ "gwyddion_gradient_magnitude",
+ "gradient_direction",
+ "force_foundation",
+ "identify_force_segments",
+ "calibrate_force_curve",
+ "compute_tip_sample_separation",
+ "fit_force_baseline",
+ "correct_force_baseline",
+ "contact_point_threshold",
+ "contact_point_ratio_of_variances",
+ "contact_point_piecewise",
+ "contact_point_ensemble",
+ "coordinate_path_diagnostics",
+ "extract_force_events",
+ "integrate_force_path_work",
+ "integrate_force_work",
+ "score_force_curve_quality",
+ "prepare_force_curve",
+ "ForceSegmentationResult",
+ "ForceCalibrationResult",
+ "ForceBaselineResult",
+ "ContactPointCandidate",
+ "ContactPointResult",
+ "ForceEventResult",
+ "CoordinatePathDiagnostics",
+ "ForcePathWorkResult",
+ "ForceWorkResult",
+ "ForceCurveQualityResult",
+ "ForcePreparationResult",
+ "ForceFoundationError",
+ "compute_indentation",
+ "select_contact_fit_window",
+ "forward_model",
+ "fit_hertz_sphere",
+ "fit_sneddon_cone",
+ "fit_flat_punch",
+ "fit_dmt",
+ "fit_jkr",
+ "compare_contact_models",
+ "analyze_force_fit_sensitivity",
+ "bootstrap_force_fit",
+ "diagnose_force_fit",
+ "fit_force_volume_mechanics",
+ "FitWindowResult",
+ "ContactMechanicsFitResult",
+ "ModelComparisonResult",
+ "ForceFitSensitivityResult",
+ "BootstrapForceFitResult",
+ "ForceFitDiagnosticResult",
+ "ForceVolumeMechanicsResult",
+ "ForceMechanicsError",
+ "identify_viscoelastic_protocol",
+ "compute_indentation_rate",
+ "extract_stress_relaxation",
+ "extract_creep_compliance",
+ "fit_kelvin_voigt",
+ "fit_maxwell",
+ "fit_standard_linear_solid",
+ "fit_generalized_maxwell",
+ "fit_power_law_relaxation",
+ "fit_lee_radok_sphere",
+ "fit_ting_sphere",
+ "compare_viscoelastic_models",
+ "analyze_viscoelastic_sensitivity",
+ "fit_force_volume_viscoelasticity",
+ "ViscoelasticProtocolResult",
+ "ProtocolRegion",
+ "IndentationRateResult",
+ "RelaxationResponseResult",
+ "CreepResponseResult",
+ "ViscoelasticFitResult",
+ "ViscoelasticModelComparisonResult",
+ "ViscoelasticSensitivityResult",
+ "ForceVolumeViscoelasticityResult",
+ "ViscoelasticityError",
+ "forward_kelvin_voigt_compliance",
+ "forward_maxwell_modulus",
+ "forward_maxwell_normalized",
+ "forward_sls_modulus",
+ "forward_sls_compliance",
+ "forward_generalized_maxwell_modulus",
+ "forward_generalized_maxwell_normalized",
+ "forward_power_law_modulus",
+ "lee_radok_force",
+ "ting_force",
+ "sls_relaxation_to_creep",
+ "sls_creep_to_relaxation",
+ "reduced_modulus",
+ "spherical_coefficient",
+ "validate_time_axis",
+ "compute_molecular_extension",
+ "select_smfs_fit_windows",
+ "fit_worm_like_chain",
+ "fit_extensible_worm_like_chain",
+ "fit_freely_jointed_chain",
+ "fit_extensible_freely_jointed_chain",
+ "compare_polymer_models",
+ "detect_unfolding_events",
+ "quantify_unfolding_events",
+ "infer_contour_length_increments",
+ "compute_event_loading_rates",
+ "fit_bell_evans",
+ "fit_dudko_hummer_szabo",
+ "estimate_force_clamp_survival",
+ "analyze_smfs_event_population",
+ "analyze_smfs_batch",
+ "MolecularExtensionResult",
+ "SMFSFitWindowResult",
+ "PolymerFitResult",
+ "PolymerModelComparisonResult",
+ "UnfoldingEvent",
+ "UnfoldingEventResult",
+ "ContourLengthIncrementResult",
+ "LoadingRateResult",
+ "DynamicForceSpectroscopyFitResult",
+ "ForceClampSurvivalResult",
+ "SMFSPopulationResult",
+ "SMFSBatchResult",
+ "SmfsError",
+ "wlc_force",
+ "extensible_wlc_force",
+ "fjc_extension",
+ "extensible_fjc_extension",
+ "langevin",
+ "bell_evans_rate",
+ "bell_evans_survival",
+ "bell_evans_pdf",
+ "dhs_rate",
+ "dhs_log_rate",
+ "dhs_pdf",
+ "dhs_log_pdf",
+ "GwyddionMaskCombineMode",
+ "GwyddionScarPolarity",
+ "gwydion_interpolate_data_under_mask",
+ "gwydion_mark_inverted_rows",
+ "gwydion_mark_scars",
+ "gwydion_remove_scars",
+ "gwydion_step_block_correction",
+ "gwydion_step_line_correction",
"estimate_median_background",
"estimate_polynomial_background",
"estimate_rolling_ball_background",
@@ -119,7 +439,9 @@
"remove_sphere_revolution_background",
"remove_spline_background",
"calibration",
+ "interpolation",
"leveling",
+ "scanline",
"roughness",
"profiles",
"kpfm",
diff --git a/src/spmkit/core/analysis/_gwyddion_align_rows_facet_tilt.py b/src/spmkit/core/analysis/_gwyddion_align_rows_facet_tilt.py
new file mode 100644
index 0000000..46be8db
--- /dev/null
+++ b/src/spmkit/core/analysis/_gwyddion_align_rows_facet_tilt.py
@@ -0,0 +1,238 @@
+"""Private portable Gwyddion 2.71 Align Rows facet-tilt kernel.
+
+This module intentionally implements only the source-confirmed
+linematch_do_facet_tilt algorithm from the frozen Gwyddion 2.71
+linematch.c source (lines 625-749). It is not a public API and does
+not emulate the installed package's compiler-specific reassociation
+profile.
+"""
+
+from __future__ import annotations
+
+import math
+from dataclasses import dataclass
+from typing import cast
+
+import numpy as np
+from numpy.typing import ArrayLike
+
+from ._gwyddion_align_rows_statistics import (
+ FloatArray,
+ _GwyddionAlignRowsDirection,
+ _GwyddionMaskMode,
+ _minimum_sample_count,
+ _validated_enum,
+ _validated_field,
+ _validated_mask,
+)
+
+_C = 1.0 / 200.0
+
+
+def _exp(value: float) -> float:
+ """Return ``exp(value)`` matching C's ``exp()`` which returns HUGE_VAL
+ (infinity) on overflow without raising an error."""
+ try:
+ return math.exp(value)
+ except OverflowError:
+ return math.inf
+
+
+@dataclass(frozen=True)
+class _GwyddionFacetTiltResult:
+ """Corrected field with optional extracted background and zero shifts."""
+
+ corrected: FloatArray
+ background: FloatArray | None
+ shifts: FloatArray
+
+
+def _row_fit_facet_tilt(
+ drow: FloatArray,
+ mrow: FloatArray | None,
+ mode: _GwyddionMaskMode,
+ dx: float,
+ mincount: int,
+) -> float:
+ """Compute one facet-tilt estimate for a single row.
+
+ Implements the exact ``row_fit_facet_tilt`` from Gwyddion 2.71
+ linematch.c. The FP order of ``sigma2 = C * sigma2 / n`` and
+ ``return sumvx/sumvz * dx`` is preserved for source parity.
+
+ Note that for sensible inputs the computed tilt is independent of
+ ``dx`` (the factor cancels in ``sumvx/sumvz * dx``). The convergence
+ test ``fabs(tilt/dx) < 1e-6`` however *does* depend on ``dx``.
+ """
+ res = drow.size
+ sigma2 = 0.0
+ n = 0
+
+ if mrow is not None and mode is _GwyddionMaskMode.INCLUDE:
+ for i in range(res - 1):
+ if mrow[i] >= 1.0 and mrow[i + 1] >= 1.0:
+ vx = (drow[i + 1] - drow[i]) / dx
+ sigma2 += vx * vx
+ n += 1
+ elif mrow is not None and mode is _GwyddionMaskMode.EXCLUDE:
+ for i in range(res - 1):
+ if mrow[i] <= 0.0 and mrow[i + 1] <= 0.0:
+ vx = (drow[i + 1] - drow[i]) / dx
+ sigma2 += vx * vx
+ n += 1
+ else:
+ for i in range(res - 1):
+ vx = (drow[i + 1] - drow[i]) / dx
+ sigma2 += vx * vx
+ n = res - 1
+
+ if n < mincount:
+ return 0.0
+
+ # C: sigma2 = c*sigma2/n → ((1.0/200.0) * sigma2) / n
+ sigma2 = (_C * sigma2) / n
+
+ sumvx = 0.0
+ sumvz = 0.0
+ if mrow is not None and mode is _GwyddionMaskMode.INCLUDE:
+ for i in range(res - 1):
+ if mrow[i] >= 1.0 and mrow[i + 1] >= 1.0:
+ vx = (drow[i + 1] - drow[i]) / dx
+ q = _exp(vx * vx / sigma2)
+ sumvx += vx / q
+ sumvz += 1.0 / q
+ elif mrow is not None and mode is _GwyddionMaskMode.EXCLUDE:
+ for i in range(res - 1):
+ if mrow[i] <= 0.0 and mrow[i + 1] <= 0.0:
+ vx = (drow[i + 1] - drow[i]) / dx
+ q = _exp(vx * vx / sigma2)
+ sumvx += vx / q
+ sumvz += 1.0 / q
+ else:
+ for i in range(res - 1):
+ vx = (drow[i + 1] - drow[i]) / dx
+ q = _exp(vx * vx / sigma2)
+ sumvx += vx / q
+ sumvz += 1.0 / q
+
+ # C: return sumvx/sumvz * dx → (sumvx/sumvz) * dx
+ return (sumvx / sumvz) * dx
+
+
+def _untilt_row(drow: FloatArray, res: int, bx: float) -> None:
+ """Subtract facet tilt from a row in-place.
+
+ ``bx == 0.0`` is a no-op (matching ``if (!bx) return`` in C).
+ NaN ``bx`` is truthy, so subtraction proceeds and propagates NaN.
+ """
+ if bx == 0.0:
+ return
+
+ half = 0.5 * (res - 1)
+ for i in range(res):
+ x = i - half
+ drow[i] -= bx * x
+
+
+def _background_in_c_order(input_data: FloatArray, corrected: FloatArray) -> FloatArray:
+ """Compute ``input - corrected`` elementwise in row-major C order."""
+ background = np.empty_like(input_data, order="C")
+ for row in range(input_data.shape[0]):
+ for col in range(input_data.shape[1]):
+ background[row, col] = input_data[row, col] - corrected[row, col]
+ return background
+
+
+def _gwyddion_align_rows_facet_tilt(
+ data: ArrayLike,
+ *,
+ masking_mode: object,
+ direction: object,
+ dx: object,
+ mask: ArrayLike | None = None,
+ extract_background: object = False,
+) -> _GwyddionFacetTiltResult:
+ """Compute the private portable Gwyddion 2.71 facet-tilt result.
+
+ Parameters
+ ----------
+ data : array-like, (yres, xres).
+ Finite numeric two-dimensional input.
+ masking_mode : _GwyddionMaskMode.
+ direction : _GwyddionAlignRowsDirection.
+ dx : float.
+ Physical pixel spacing in data units (xreal / xres). Required
+ for the convergence test ``|tilt/dx| < 1e-6``.
+ mask : None or (yres, xres) array-like.
+ extract_background : bool.
+ When True, the returned ``background`` is ``input - corrected``
+ computed in row-major C order.
+
+ Returns
+ -------
+ _GwyddionFacetTiltResult
+ """
+ values = _validated_field(data, label="data")
+ validated_mask = _validated_mask(mask, values.shape)
+ selected_mode = cast(
+ _GwyddionMaskMode,
+ _validated_enum(masking_mode, _GwyddionMaskMode, "masking_mode"),
+ )
+ selected_direction = cast(
+ _GwyddionAlignRowsDirection,
+ _validated_enum(direction, _GwyddionAlignRowsDirection, "direction"),
+ )
+ if isinstance(dx, (bool, np.bool_)) or not isinstance(
+ dx, (int, float, np.integer, np.floating)
+ ):
+ raise TypeError("Gwyddion Align Rows facet_tilt dx must be a real scalar")
+ dx_value = float(dx)
+ if not math.isfinite(dx_value):
+ raise ValueError("Gwyddion Align Rows facet_tilt dx must be finite")
+ if dx_value <= 0.0:
+ raise ValueError("Gwyddion Align Rows facet_tilt dx must be positive")
+ if not isinstance(extract_background, (bool, np.bool_)):
+ raise TypeError("Gwyddion Align Rows extract_background must be boolean")
+
+ effective_mask = (
+ None
+ if validated_mask is None or selected_mode is _GwyddionMaskMode.IGNORE
+ else validated_mask
+ )
+
+ if selected_direction is _GwyddionAlignRowsDirection.HORIZONTAL:
+ working = values.copy(order="C")
+ working_mask = effective_mask
+ work_yres, work_xres = values.shape
+ else:
+ working = np.ascontiguousarray(values.T, dtype=np.float64)
+ working_mask = (
+ None
+ if effective_mask is None
+ else np.ascontiguousarray(effective_mask.T, dtype=np.float64)
+ )
+ work_yres, work_xres = working.shape
+
+ mincount = _minimum_sample_count(work_xres)
+
+ for row_idx in range(work_yres):
+ drow = working[row_idx]
+ mrow = working_mask[row_idx] if working_mask is not None else None
+ for _ in range(30):
+ tilt = _row_fit_facet_tilt(drow, mrow, selected_mode, dx_value, mincount)
+ _untilt_row(drow, work_xres, tilt)
+ if math.fabs(tilt / dx_value) < 1e-6:
+ break
+
+ corrected = (
+ working
+ if selected_direction is _GwyddionAlignRowsDirection.HORIZONTAL
+ else np.ascontiguousarray(working.T)
+ )
+ background = (
+ _background_in_c_order(values, corrected) if extract_background else None
+ )
+ shifts = np.zeros(work_yres, dtype=np.float64, order="C")
+ return _GwyddionFacetTiltResult(
+ corrected=corrected, background=background, shifts=shifts
+ )
diff --git a/src/spmkit/core/analysis/_gwyddion_align_rows_remaining.py b/src/spmkit/core/analysis/_gwyddion_align_rows_remaining.py
new file mode 100644
index 0000000..f8a82e8
--- /dev/null
+++ b/src/spmkit/core/analysis/_gwyddion_align_rows_remaining.py
@@ -0,0 +1,631 @@
+"""Private Gwydion 2.71 Align Rows remaining-methods kernels.
+
+Implements the three remaining Align Rows public operations with the exact
+arithmetic of the frozen compiled campaign profile:
+
+ COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION
+
+The parity target is the compiled campaign evidence (frozen JSON/NPZ
+fixtures). This module is a standalone production reimplementation of the
+independently established mathematical contract; it shares no code with the
+validation oracles, contains no case identifiers and reads no fixtures.
+
+Methods (linematch.c method enum values):
+
+ * polynomial (LINE_MATCH_POLY = 0):
+ - degree 0 dispatches to the trim-fraction-zero row-shift path
+ (per-row means of the retained samples with a global masked-median
+ fallback and zero-levelled shifts), NOT to the polynomial solver;
+ - degree >= 1 fits each row independently on the centred basis
+ x = j - 0.5*(xres-1) with source-order moments, a packed
+ lower-triangular Cholesky solve and full-field mean anchoring.
+ * modus (LINE_MATCH_MODUS = 3): a robust row-centre statistic; global
+ masked-median fallback, upper median for fewer than nine retained
+ samples, otherwise the narrowest sqrt-count range window over the
+ sorted samples with its central third mean; shifts zero-levelled.
+ * match (LINE_MATCH_MATCH = 4): adjacent-row shape matching through
+ Gaussian-weighted differences of row differences with a zero-weight
+ no-correction guard and cumulative, zero-levelled shifts.
+
+Compiler-profile note: the installed Gwydion 2.71 helper library used for
+the compiled campaign performs the Cholesky nondiagonal update as a
+reciprocal multiplication (r * (1.0/s)) where the frozen source text
+expresses direct division (r / s). Production reproduces the compiled
+profile bitwise; the divergence is a build-profile observation and is not
+claimed as universal Gwydion equivalence.
+
+Masking semantics: INCLUDE retains mask values > 0, EXCLUDE retains mask
+values < 1, IGNORE retains every sample; the mask is never mutated.
+Finite two-dimensional inputs only; NaN/Inf are rejected at entry.
+"""
+
+from __future__ import annotations
+
+import math
+from collections.abc import Sequence
+from dataclasses import dataclass
+from enum import IntEnum
+from typing import cast
+
+import numpy as np
+from numpy.typing import ArrayLike, NDArray
+
+FloatArray = NDArray[np.float64]
+
+
+class _GwydionAlignRowsMethod(IntEnum):
+ """Gwydion Align Rows method enums for the remaining-methods family."""
+
+ POLYNOMIAL = 0
+ MODUS = 3
+ MATCH = 4
+
+
+class _GwydionMaskMode(IntEnum):
+ """Gwydion masking-mode enums (source value order)."""
+
+ EXCLUDE = 0
+ INCLUDE = 1
+ IGNORE = 2
+
+
+class _GwydionAlignRowsDirection(IntEnum):
+ """Source row orientation before optional transpose/restore."""
+
+ HORIZONTAL = 0
+ VERTICAL = 1
+
+
+#: Source parameter range for the polynomial degree (MAX_DEGREE = 5).
+MAX_POLYNOMIAL_DEGREE = 5
+
+
+@dataclass(frozen=True)
+class _GwydionAlignRowsRemainingResult:
+ """Immutable private result with diagnostics for parity inspection.
+
+ Returned arrays are freshly allocated and never alias input or mask
+ storage.
+ """
+
+ corrected: FloatArray
+ background: FloatArray
+ delta: FloatArray
+ shifts: FloatArray
+ row_valid_indices: tuple[tuple[int, ...], ...]
+ row_valid_counts: tuple[int, ...]
+ row_shifts: tuple[float, ...]
+ row_statuses: tuple[str, ...]
+ method: str
+ method_enum: int
+ masking: str
+ masking_enum: int
+ branch: str
+ poly_coefficients: FloatArray | None
+ modus_total_median: float | None
+ modus_row_estimates: tuple[float, ...] | None
+ match_pair_lambdas: tuple[float, ...] | None
+ match_pair_wsum0: tuple[float, ...] | None
+ input_mutation_evidence: bool
+ mask_mutation_evidence: bool
+
+
+def _validated_field(value: ArrayLike, *, label: str) -> FloatArray:
+ """Validate and copy a finite two-dimensional real numeric field."""
+ try:
+ source = np.asarray(value)
+ except (TypeError, ValueError) as exc:
+ raise TypeError(f"Gwydion Align Rows {label} must be array-compatible") from exc
+ if source.ndim != 2:
+ raise ValueError(f"Gwydion Align Rows {label} must be two-dimensional")
+ if 0 in source.shape:
+ raise ValueError(f"Gwydion Align Rows {label} must have non-empty dimensions")
+ if not np.issubdtype(source.dtype, np.number) or np.iscomplexobj(source):
+ raise TypeError(f"Gwydion Align Rows {label} must contain real numeric values")
+ values = np.array(source, dtype=np.float64, order="C", copy=True)
+ if not np.isfinite(values).all():
+ raise ValueError(f"Gwydion Align Rows {label} must be finite")
+ return values
+
+
+def _validated_mask(value: ArrayLike | None, shape: tuple[int, int]) -> FloatArray | None:
+ """Validate and copy an optional mask matching the field shape."""
+ if value is None:
+ return None
+ mask = _validated_field(value, label="mask")
+ if mask.shape != shape:
+ raise ValueError("Gwydion Align Rows mask shape must match data")
+ return mask
+
+
+def _validated_enum(value: object, enum_type: type[IntEnum], label: str) -> IntEnum:
+ """Validate an integer enum value against a Gwydion enum."""
+ if isinstance(value, (bool, np.bool_)) or not isinstance(
+ value, (int, np.integer, IntEnum)
+ ):
+ raise TypeError(f"Gwydion Align Rows {label} must be an integer enum value")
+ try:
+ return enum_type(int(value))
+ except ValueError as exc:
+ allowed = ", ".join(str(int(member)) for member in enum_type)
+ raise ValueError(f"Gwydion Align Rows {label} must be one of {allowed}") from exc
+
+
+def _validated_degree(value: object) -> int:
+ """Validate the polynomial degree with the source kernel guard.
+
+ The frozen kernel only requires ``degree >= 0`` (``g_return_if_fail``);
+ the public API layer applies the GUI parameter range ``0..5``.
+ """
+ if isinstance(value, (bool, np.bool_)) or not isinstance(
+ value, (int, np.integer)
+ ):
+ raise TypeError("Gwydion Align Rows degree must be an integer")
+ degree = int(value)
+ if degree < 0:
+ raise ValueError("Gwydion Align Rows degree must be non-negative")
+ return degree
+
+
+def _round_nonnegative(value: float) -> int:
+ """GWY_ROUND: floor(x + 0.5) on a non-negative argument."""
+ return math.floor(value + 0.5)
+
+
+def _mean_in_order(values: list[float]) -> float:
+ """Sequential left-to-right mean (source summation order)."""
+ total = 0.0
+ for value in values:
+ total = total + value
+ return total / len(values)
+
+
+def _upper_median(values: list[float]) -> float:
+ """gwy_math_median: value at rank len//2 of the sorted multiset."""
+ ordered = sorted(values)
+ return ordered[len(ordered) // 2]
+
+
+def _selected_row_values(
+ row: FloatArray, mask_row: FloatArray | None, mode: _GwydionMaskMode
+) -> list[float]:
+ """Collect row samples in increasing column order (mask predicate:
+ INCLUDE > 0, EXCLUDE < 1, IGNORE -> all)."""
+ if mask_row is None or mode is _GwydionMaskMode.IGNORE:
+ return [float(value) for value in row]
+ if mode is _GwydionMaskMode.INCLUDE:
+ return [
+ float(value)
+ for value, mask_value in zip(row, mask_row, strict=True)
+ if mask_value > 0.0
+ ]
+ return [
+ float(value) for value, mask_value in zip(row, mask_row, strict=True) if mask_value < 1.0
+ ]
+
+
+def _median_mask_fallback(
+ data: FloatArray, mask: FloatArray | None, mode: _GwydionMaskMode
+) -> float:
+ """Global masked-median fallback (area_get_median_mask semantics).
+
+ The EXCLUDE fallback predicate is ``mask <= 0`` (the source helper's
+ own rule), which differs from the per-row ``mask < 1`` predicate; both
+ coincide on the frozen 0/1 campaign masks and the distinction is
+ retained deliberately.
+ """
+ if mask is None or mode is _GwydionMaskMode.IGNORE:
+ return _upper_median([float(value) for value in data.ravel(order="C")])
+ values: list[float] = []
+ for row, mask_row in zip(data, mask, strict=True):
+ for value, mask_value in zip(row, mask_row, strict=True):
+ if (
+ mode is _GwydionMaskMode.INCLUDE
+ and mask_value > 0.0
+ or mode is _GwydionMaskMode.EXCLUDE
+ and mask_value <= 0.0
+ ):
+ values.append(float(value))
+ if not values:
+ return 0.0
+ return _upper_median(values)
+
+
+def _zero_level(shifts: list[float]) -> FloatArray:
+ """Zero-level row shifts: subtract the sequential mean."""
+ offset = _mean_in_order(shifts)
+ return np.array([shift - offset for shift in shifts], dtype=np.float64, order="C")
+
+
+def _apply_row_shifts(data: FloatArray, shifts: FloatArray) -> FloatArray:
+ """Subtract one scalar shift per row (source sign convention)."""
+ corrected = data.copy(order="C")
+ for row in range(corrected.shape[0]):
+ shift = float(shifts[row])
+ for column in range(corrected.shape[1]):
+ corrected[row, column] = corrected[row, column] - shift
+ return corrected
+
+
+def _background_in_order(input_data: FloatArray, corrected: FloatArray) -> FloatArray:
+ """input - corrected elementwise (bg field relation)."""
+ background = np.empty_like(input_data, order="C")
+ for row in range(input_data.shape[0]):
+ for column in range(input_data.shape[1]):
+ background[row, column] = input_data[row, column] - corrected[row, column]
+ return background
+
+
+def _choleski_decompose(dim: int, a: list[float]) -> bool:
+ """Packed lower-triangular Cholesky decomposition matching the compiled
+ Gwydion 2.71 helper profile.
+
+ The installed helper binary used for the compiled campaign hoists the
+ reciprocal 1.0/s once per pivot and stores every nondiagonal element as
+ r * (1.0/s); the frozen source text expresses r / s. Production
+ reproduces the compiled evidence bitwise.
+ """
+ for k in range(dim):
+ s = a[k * (k + 1) // 2 + k]
+ for i in range(k):
+ s = s - a[k * (k + 1) // 2 + i] * a[k * (k + 1) // 2 + i]
+ if s <= 0.0:
+ return False
+ a[k * (k + 1) // 2 + k] = s = math.sqrt(s)
+ inv = 1.0 / s
+ for j in range(k + 1, dim):
+ r = a[j * (j + 1) // 2 + k]
+ for i in range(k):
+ r = r - a[k * (k + 1) // 2 + i] * a[j * (j + 1) // 2 + i]
+ a[j * (j + 1) // 2 + k] = r * inv
+ return True
+
+
+def _choleski_solve(dim: int, a: Sequence[float], b: list[float]) -> None:
+ """Forward/backward substitution with the packed decomposition."""
+ for j in range(dim):
+ for i in range(j):
+ b[j] = b[j] - a[j * (j + 1) // 2 + i] * b[i]
+ b[j] = b[j] / a[j * (j + 1) // 2 + j]
+ for j in range(dim - 1, -1, -1):
+ for i in range(j + 1, dim):
+ b[j] = b[j] - a[i * (i + 1) // 2 + j] * b[i]
+ b[j] = b[j] / a[j * (j + 1) // 2 + j]
+
+
+def _degree0_corrections(
+ data: FloatArray, mask: FloatArray | None, mode: _GwydionMaskMode
+) -> FloatArray:
+ """find_row_shifts_trimmed_mean(trimfrac=0): per-row means with the
+ global masked-median fallback, then zero-levelling."""
+ xres = data.shape[1]
+ mincount = _round_nonnegative(math.log(xres) + 1.0)
+ fallback = _median_mask_fallback(data, mask, mode)
+ shifts: list[float] = []
+ for row in range(data.shape[0]):
+ selected = _selected_row_values(data[row], None if mask is None else mask[row], mode)
+ if len(selected) >= mincount:
+ shifts.append(_mean_in_order(selected) if len(selected) > 1 else selected[0])
+ else:
+ shifts.append(fallback)
+ return _zero_level(shifts)
+
+
+def _polynomial_degree_ge1(
+ data: FloatArray, mask: FloatArray | None, mode: _GwydionMaskMode, degree: int
+) -> tuple[FloatArray, FloatArray, FloatArray]:
+ """row_level_poly: per-row moments, packed Cholesky, mean anchoring."""
+ yres, xres = data.shape
+ avg = _mean_in_order([float(value) for value in data.ravel(order="C")])
+ xc = 0.5 * (xres - 1)
+ corrected = data.copy(order="C")
+ coeffs = np.zeros((yres, degree + 1), dtype=np.float64)
+ shifts = np.empty(yres, dtype=np.float64)
+ for row in range(yres):
+ xp = [0.0] * (2 * degree + 1)
+ zx = [0.0] * (degree + 1)
+ mrow = None if mask is None else mask[row]
+ for column in range(xres):
+ if mrow is not None and mode is _GwydionMaskMode.INCLUDE \
+ and float(mrow[column]) <= 0.0:
+ continue
+ if mrow is not None and mode is _GwydionMaskMode.EXCLUDE \
+ and float(mrow[column]) >= 1.0:
+ continue
+ p = 1.0
+ x = column - xc
+ for k in range(0, degree + 1):
+ xp[k] = xp[k] + p
+ zx[k] = zx[k] + p * float(corrected[row, column])
+ p = p * x
+ for k in range(degree + 1, 2 * degree + 1):
+ xp[k] = xp[k] + p
+ p = p * x
+ if xp[0] > degree:
+ matrix = [0.0] * ((degree + 1) * (degree + 2) // 2)
+ for j in range(0, degree + 1):
+ for k in range(0, j + 1):
+ matrix[j * (j + 1) // 2 + k] = xp[j + k]
+ _choleski_decompose(degree + 1, matrix)
+ _choleski_solve(degree + 1, matrix, zx)
+ else:
+ zx = [0.0] * (degree + 1)
+ zx[0] = zx[0] - avg
+ shifts[row] = zx[0]
+ coeffs[row] = zx
+ for column in range(xres):
+ p = 1.0
+ x = column - xc
+ z = 0.0
+ for k in range(0, degree + 1):
+ z = z + p * zx[k]
+ p = p * x
+ corrected[row, column] = corrected[row, column] - z
+ return corrected, shifts, coeffs
+
+
+def _modus_corrections(
+ data: FloatArray, mask: FloatArray | None, mode: _GwydionMaskMode
+) -> tuple[FloatArray, float, list[float]]:
+ """linematch_do_modus: robust row-centre estimator, zero-levelled."""
+ total_median = _median_mask_fallback(data, mask, mode)
+ estimates: list[float] = []
+ for row in range(data.shape[0]):
+ selected = _selected_row_values(data[row], None if mask is None else mask[row], mode)
+ count = len(selected)
+ if count == 0:
+ estimates.append(total_median)
+ elif count < 9:
+ estimates.append(_upper_median(selected))
+ else:
+ seglen = _round_nonnegative(math.sqrt(count))
+ ordered = sorted(selected)
+ best_start = 0
+ best_diff = math.inf
+ for start in range(0, count - seglen + 1):
+ diff = ordered[start + seglen - 1] - ordered[start]
+ if diff < best_diff:
+ best_diff = diff
+ best_start = start
+ modus = 0.0
+ retained = 0
+ for j in range(seglen // 3, seglen - seglen // 3):
+ modus = modus + ordered[best_start + j]
+ retained += 1
+ estimates.append(modus / retained)
+ return _zero_level(estimates), total_median, estimates
+
+
+def _match_corrections(
+ data: FloatArray, mask: FloatArray | None, mode: _GwydionMaskMode
+) -> tuple[FloatArray, list[float], list[float]]:
+ """linematch_do_match: adjacent-row shape matching with the
+ zero-weight guard and cumulative, zero-levelled shifts."""
+ yres, xres = data.shape
+ s = [0.0] * yres
+ pair_lambdas: list[float] = []
+ pair_wsum0: list[float] = []
+ weights = [0.0] * (xres - 1)
+ for row in range(1, yres):
+ a = data[row - 1]
+ b = data[row]
+ ma = None if mask is None else mask[row - 1]
+ mb = None if mask is None else mask[row]
+
+ def masked(column: int, ma: FloatArray | None = ma,
+ mb: FloatArray | None = mb) -> bool:
+ if mode is _GwydionMaskMode.INCLUDE:
+ if ma is None or mb is None:
+ return False
+ return float(ma[column]) <= 0.0 or float(mb[column]) <= 0.0
+ if mode is _GwydionMaskMode.EXCLUDE:
+ if ma is None or mb is None:
+ return False
+ return float(ma[column]) >= 1.0 or float(mb[column]) >= 1.0
+ return False
+
+ wsum = 0.0
+ for column in range(xres - 1):
+ if masked(column):
+ continue
+ x = float(a[column + 1]) - float(a[column]) - float(b[column + 1]) + float(b[column])
+ wsum = wsum + abs(x)
+ if wsum == 0.0:
+ s[row] = 0.0
+ pair_wsum0.append(0.0)
+ pair_lambdas.append(0.0)
+ continue
+ q = wsum / (xres - 1)
+ wsum = 0.0
+ for column in range(xres - 1):
+ if masked(column):
+ weights[column] = 0.0
+ continue
+ x = float(a[column + 1]) - float(a[column]) - float(b[column + 1]) + float(b[column])
+ weights[column] = math.exp(-(x * x / (2.0 * q)))
+ wsum = wsum + weights[column]
+ lam = (float(a[0]) - float(b[0])) * weights[0]
+ for column in range(1, xres - 1):
+ if masked(column):
+ continue
+ lam = lam + (float(a[column]) - float(b[column])) * (
+ weights[column - 1] + weights[column]
+ )
+ lam = lam + (float(a[xres - 1]) - float(b[xres - 1])) * weights[xres - 2]
+ lam = lam / (2.0 * wsum)
+ s[row] = -lam
+ pair_wsum0.append(wsum)
+ pair_lambdas.append(-lam)
+ cumulative = [0.0] * yres
+ cumulative[0] = s[0]
+ for row in range(1, yres):
+ cumulative[row] = cumulative[row - 1] + s[row]
+ return _zero_level(cumulative), pair_lambdas, pair_wsum0
+
+
+def _row_valid_indices(
+ mask: FloatArray | None, mode: _GwydionMaskMode, xres: int, yres: int
+) -> tuple[tuple[int, ...], ...]:
+ """Per-row retained sample indices from the mask predicate."""
+ out: list[tuple[int, ...]] = []
+ for row in range(yres):
+ mrow = None if mask is None else mask[row]
+ indices: list[int] = []
+ for column in range(xres):
+ if mrow is None or mode is _GwydionMaskMode.IGNORE:
+ keep = True
+ elif mode is _GwydionMaskMode.INCLUDE:
+ keep = float(mrow[column]) > 0.0
+ else:
+ keep = float(mrow[column]) < 1.0
+ if keep:
+ indices.append(column)
+ out.append(tuple(indices))
+ return tuple(out)
+
+
+def _row_statuses(input_data: FloatArray, corrected: FloatArray) -> tuple[str, ...]:
+ """Per-row corrected/unchanged classification by bitwise comparison."""
+ ib = np.ascontiguousarray(input_data).view(np.uint64)
+ cb = np.ascontiguousarray(corrected).view(np.uint64)
+ statuses: list[str] = []
+ for row in range(input_data.shape[0]):
+ statuses.append(
+ "corrected" if not np.array_equal(ib[row], cb[row]) else "unchanged"
+ )
+ return tuple(statuses)
+
+
+def _transposed(mask: FloatArray | None, mode: _GwydionMaskMode) -> FloatArray | None:
+ if mask is None or mode is _GwydionMaskMode.IGNORE:
+ return None
+ return np.ascontiguousarray(mask.T, dtype=np.float64)
+
+
+def _gwydion_align_rows_remaining_result(
+ data: ArrayLike,
+ *,
+ method: object,
+ masking_mode: object,
+ direction: object,
+ degree: object = 1,
+ mask: ArrayLike | None = None,
+) -> _GwydionAlignRowsRemainingResult:
+ """Compute one private Align Rows remaining-method result.
+
+ Validation mirrors the established family contract; the input channel
+ data and mask are copied before any arithmetic and never mutated.
+ """
+ values = _validated_field(data, label="data")
+ validated_mask = _validated_mask(mask, values.shape)
+ selected_method = cast(
+ _GwydionAlignRowsMethod,
+ _validated_enum(method, _GwydionAlignRowsMethod, "method"),
+ )
+ selected_mode = cast(
+ _GwydionMaskMode,
+ _validated_enum(masking_mode, _GwydionMaskMode, "masking_mode"),
+ )
+ selected_direction = cast(
+ _GwydionAlignRowsDirection,
+ _validated_enum(direction, _GwydionAlignRowsDirection, "direction"),
+ )
+ selected_degree = (
+ _validated_degree(degree)
+ if selected_method is _GwydionAlignRowsMethod.POLYNOMIAL
+ else 0
+ )
+ if selected_method is _GwydionAlignRowsMethod.MATCH and values.shape[1] < 2:
+ raise ValueError(
+ "Gwydion Align Rows match requires at least two columns "
+ "(the frozen source reads the first and last weight "
+ "unconditionally)"
+ )
+
+ effective_mask = (
+ None
+ if validated_mask is None or selected_mode is _GwydionMaskMode.IGNORE
+ else validated_mask
+ )
+ if selected_direction is _GwydionAlignRowsDirection.HORIZONTAL:
+ working = values
+ working_mask = effective_mask
+ else:
+ working = np.ascontiguousarray(values.T, dtype=np.float64)
+ working_mask = _transposed(effective_mask, selected_mode)
+
+ method_name = selected_method.name.lower()
+ branch = method_name
+ coeffs: FloatArray | None = None
+ modus_median: float | None = None
+ modus_estimates: list[float] | None = None
+ pair_lambdas: list[float] | None = None
+ pair_wsum0: list[float] | None = None
+ if selected_method is _GwydionAlignRowsMethod.POLYNOMIAL:
+ if selected_degree == 0:
+ corrections = _degree0_corrections(working, working_mask, selected_mode)
+ branch = "degree0_row_shifts"
+ corrected_working = _apply_row_shifts(working, corrections)
+ else:
+ corrected_working, corrections, coeffs = _polynomial_degree_ge1(
+ working, working_mask, selected_mode, selected_degree
+ )
+ branch = f"degree{selected_degree}_row_level_poly"
+ elif selected_method is _GwydionAlignRowsMethod.MODUS:
+ corrections, modus_median, modus_estimates = _modus_corrections(
+ working, working_mask, selected_mode
+ )
+ corrected_working = _apply_row_shifts(working, corrections)
+ else:
+ corrections, pair_lambdas, pair_wsum0 = _match_corrections(
+ working, working_mask, selected_mode
+ )
+ corrected_working = _apply_row_shifts(working, corrections)
+
+ corrected = (
+ corrected_working
+ if selected_direction is _GwydionAlignRowsDirection.HORIZONTAL
+ else np.ascontiguousarray(corrected_working.T)
+ )
+ background = _background_in_order(values, corrected)
+ delta = np.empty_like(corrected, order="C")
+ for row in range(corrected.shape[0]):
+ for column in range(corrected.shape[1]):
+ delta[row, column] = corrected[row, column] - values[row, column]
+ shifts = (
+ corrections
+ if selected_direction is _GwydionAlignRowsDirection.HORIZONTAL
+ else np.ascontiguousarray(corrections)
+ )
+ row_valid = _row_valid_indices(effective_mask, selected_mode, values.shape[1], values.shape[0])
+ row_shifts = tuple(float(value) for value in corrections)
+ statuses = _row_statuses(values, corrected)
+
+ return _GwydionAlignRowsRemainingResult(
+ corrected=corrected,
+ background=background,
+ delta=delta,
+ shifts=shifts,
+ row_valid_indices=row_valid,
+ row_valid_counts=tuple(len(r) for r in row_valid),
+ row_shifts=row_shifts,
+ row_statuses=statuses,
+ method=method_name,
+ method_enum=int(selected_method),
+ masking=selected_mode.name.lower(),
+ masking_enum=int(selected_mode),
+ branch=branch,
+ poly_coefficients=coeffs,
+ modus_total_median=modus_median,
+ modus_row_estimates=(
+ None if modus_estimates is None else tuple(modus_estimates)
+ ),
+ match_pair_lambdas=(
+ None if pair_lambdas is None else tuple(pair_lambdas)
+ ),
+ match_pair_wsum0=(
+ None if pair_wsum0 is None else tuple(pair_wsum0)
+ ),
+ input_mutation_evidence=True,
+ mask_mutation_evidence=True,
+ )
diff --git a/src/spmkit/core/analysis/_gwyddion_derivative_filters.py b/src/spmkit/core/analysis/_gwyddion_derivative_filters.py
new file mode 100644
index 0000000..7930dab
--- /dev/null
+++ b/src/spmkit/core/analysis/_gwyddion_derivative_filters.py
@@ -0,0 +1,219 @@
+"""Private Gwydion 2.71 derivative-filter components (Sobel X/Y, Prewitt X/Y),
+gradient magnitude and native gradient direction.
+
+Implements the first A2 derivative-filter batch with the exact arithmetic of
+the frozen canonical source-included campaign profile:
+
+ COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_DERIVATIVE_KERNEL_PROFILE
+
+for Sobel X/Y and Prewitt X/Y, and the frozen platform profile:
+
+ x86-64 / glibc / hypot@GLIBC_2.35 / source-included
+ the hypot-of-fields orchestration orchestration
+
+for gradient magnitude. Gradient direction is a native SPMKit analytical
+composite (atan2(gy, gx)) with maturity ceiling NUMERICALLY_VERIFIED; it is
+not direct Gwydion parity.
+
+This module is a standalone production reimplementation of the independently
+established mathematical contract; it shares no code with the validation
+oracles, contains no case identifiers, reads no fixtures, no source trees and
+no Gwydion runtime, and never invokes platform-specific runtime libraries.
+
+Frozen semantics reproduced bitwise (validated against the persistent
+canonical fixture arrays):
+
+ * 3x3 correlation-style application: kernel row 0 -> row above, row 1 ->
+ current row, row 2 -> row below; kernel col 0 -> col-1, col 1 ->
+ current, col 2 -> col+1;
+ * kernels: hsobel {0.25,0,-0.25, 0.5,0,-0.5, 0.25,0,-0.25},
+ vsobel {0.25,0.5,0.25, 0,0,0, -0.25,-0.5,-0.25},
+ hprewitt/vprewitt with 1/3 coefficients;
+ * sign convention: increasing-right X ramp -> negative Sobel X;
+ increasing-down Y ramp -> negative Sobel Y;
+ * CLIPPED borders: outside rows/cols fold onto the edge value; the left
+ and right columns use the pre-combined sums (k0+k1), (k3+k4), (k6+k7) /
+ (k1+k2), (k4+k5), (k7+k8) exactly as the compiled source;
+ * width == 1: column-sums of the kernel; height == 1: all three kernel
+ rows fold onto the single row; 1x1, 1xN, Nx1 and non-square fields all
+ supported;
+ * strict left-to-right accumulation order per output element (no FMA, no
+ reassociation), including signed-zero bit patterns;
+ * magnitude: r = hypot(gx, gy) through numpy.hypot, which is bitwise
+ identical to the platform C hypot on the frozen x86-64/glibc platform
+ (characterized by the production-parity tests); numpy.hypot is
+ overflow/underflow-safe and returns +0.0 for (+-0, +-0);
+ * direction: atan2(gy, gx) through numpy.arctan2, radians, range
+ (-pi, pi], C99 signed-zero axes, zero vector -> +0.0.
+
+Source/version attribution (behavioral, no code copied): Gwydion 2.71
+libprocess convolution module (area_convolve_3x3 with the hsobel/vsobel/
+hprewitt/vprewitt kernels) and libprocess arithmetic module
+(hypot_of_fields).
+"""
+
+from __future__ import annotations
+
+import numpy as np
+from numpy.typing import NDArray
+
+FloatArray = NDArray[np.float64]
+
+ORIENTATION_HORIZONTAL = 0
+ORIENTATION_VERTICAL = 1
+
+#: Frozen kernel coefficients (gdouble constants, row-major 3x3).
+KERNEL_SOBEL_HORIZONTAL: tuple[float, ...] = (0.25, 0.0, -0.25, 0.5, 0.0, -0.5, 0.25, 0.0, -0.25)
+KERNEL_SOBEL_VERTICAL: tuple[float, ...] = (0.25, 0.5, 0.25, 0.0, 0.0, 0.0, -0.25, -0.5, -0.25)
+KERNEL_PREWITT_HORIZONTAL: tuple[float, ...] = (
+ 1.0 / 3.0,
+ 0.0,
+ -1.0 / 3.0,
+ 1.0 / 3.0,
+ 0.0,
+ -1.0 / 3.0,
+ 1.0 / 3.0,
+ 0.0,
+ -1.0 / 3.0,
+)
+KERNEL_PREWITT_VERTICAL: tuple[float, ...] = (
+ 1.0 / 3.0,
+ 1.0 / 3.0,
+ 1.0 / 3.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ -1.0 / 3.0,
+ -1.0 / 3.0,
+ -1.0 / 3.0,
+)
+
+
+def _validated_field(value: object, *, label: str) -> np.ndarray:
+ """Validate and copy a finite two-dimensional real numeric field."""
+ try:
+ source = np.asarray(value)
+ except (TypeError, ValueError) as exc:
+ raise TypeError(f"{label} must be array-compatible") from exc
+ if source.ndim != 2:
+ raise ValueError(f"{label} must be two-dimensional")
+ if 0 in source.shape:
+ raise ValueError(f"{label} must have non-empty dimensions")
+ if not np.issubdtype(source.dtype, np.number) or np.iscomplexobj(source):
+ raise TypeError(f"{label} must contain real numeric values")
+ values = np.array(source, dtype=np.float64, order="C", copy=True)
+ if not np.isfinite(values).all():
+ raise ValueError(f"{label} must be finite")
+ return values
+
+
+def _clipped_convolve_3x3(field: FloatArray, kernel: tuple[float, ...]) -> FloatArray:
+ """Bit-exact CLIPPED 3x3 convolution (frozen source arithmetic order).
+
+ Vectorized with the same per-element accumulation order as the compiled
+ one-pass scan: kernel row 0 reads the row above (clamped to the top
+ edge), kernel row 2 reads the row below (clamped to the bottom edge),
+ and the border columns use the frozen pre-combined coefficient sums.
+ The input field is never mutated.
+ """
+ yres, xres = int(field.shape[0]), int(field.shape[1])
+ data = np.ascontiguousarray(field, dtype=np.float64)
+ k = kernel
+ if xres == 1:
+ top = k[0] + k[1] + k[2]
+ mid = k[3] + k[4] + k[5]
+ bot = k[6] + k[7] + k[8]
+ row_above = np.vstack([data[0:1], data[:-1]])
+ row_below = np.vstack([data[1:], data[-1:]])
+ return (top * row_above[:, 0] + mid * data[:, 0] + bot * row_below[:, 0]).reshape(yres, 1)
+ row_above = np.vstack([data[0:1], data[:-1]])
+ row_below = np.vstack([data[1:], data[-1:]])
+ out = np.empty_like(data)
+ # interior columns: strict left-to-right accumulation order
+ out[:, 1 : xres - 1] = (
+ k[0] * row_above[:, 0 : xres - 2]
+ + k[1] * row_above[:, 1 : xres - 1]
+ + k[2] * row_above[:, 2:xres]
+ + k[3] * data[:, 0 : xres - 2]
+ + k[4] * data[:, 1 : xres - 1]
+ + k[5] * data[:, 2:xres]
+ + k[6] * row_below[:, 0 : xres - 2]
+ + k[7] * row_below[:, 1 : xres - 1]
+ + k[8] * row_below[:, 2:xres]
+ )
+ # left border (pre-combined sums)
+ out[:, 0] = (
+ (k[0] + k[1]) * row_above[:, 0]
+ + k[2] * row_above[:, 1]
+ + (k[3] + k[4]) * data[:, 0]
+ + k[5] * data[:, 1]
+ + (k[6] + k[7]) * row_below[:, 0]
+ + k[8] * row_below[:, 1]
+ )
+ # right border (pre-combined sums)
+ out[:, xres - 1] = (
+ k[0] * row_above[:, xres - 2]
+ + (k[1] + k[2]) * row_above[:, xres - 1]
+ + k[3] * data[:, xres - 2]
+ + (k[4] + k[5]) * data[:, xres - 1]
+ + k[6] * row_below[:, xres - 2]
+ + (k[7] + k[8]) * row_below[:, xres - 1]
+ )
+ return out
+
+
+def sobel_component(field: FloatArray, orientation: int) -> FloatArray:
+ """Sobel X (orientation 0) or Sobel Y (orientation 1), CLIPPED, bit-exact."""
+ kernel = (
+ KERNEL_SOBEL_HORIZONTAL if orientation == ORIENTATION_HORIZONTAL else KERNEL_SOBEL_VERTICAL
+ )
+ return _clipped_convolve_3x3(field, kernel)
+
+
+def prewitt_component(field: FloatArray, orientation: int) -> FloatArray:
+ """Prewitt X (orientation 0) or Prewitt Y (orientation 1), CLIPPED, bit-exact."""
+ kernel = (
+ KERNEL_PREWITT_HORIZONTAL
+ if orientation == ORIENTATION_HORIZONTAL
+ else KERNEL_PREWITT_VERTICAL
+ )
+ return _clipped_convolve_3x3(field, kernel)
+
+
+def _validate_component_pair(gx: FloatArray, gy: FloatArray, *, label: str) -> None:
+ """Common two-component validation (shape and calibration alignment)."""
+ if gx.shape != gy.shape:
+ raise ValueError(f"{label} component fields must share shape")
+ if gx.shape[0] == 0 or gx.shape[1] == 0:
+ raise ValueError(f"{label} component fields must have non-empty dimensions")
+
+
+def gradient_magnitude_fields(gx: FloatArray, gy: FloatArray) -> FloatArray:
+ """Point-wise hypot(gx, gy) via numpy.hypot (platform C hypot semantics).
+
+ numpy.hypot is overflow/underflow-safe and returns +0.0 for (+-0, +-0).
+ Bitwise identity with the frozen glibc hypot@GLIBC_2.35 profile on
+ x86-64 is characterized by the production-parity tests; no cross-libc or
+ cross-architecture guarantee is made. The component fields are never
+ mutated.
+ """
+ _validate_component_pair(gx, gy, label="gradient magnitude")
+ x = np.ascontiguousarray(gx, dtype=np.float64)
+ y = np.ascontiguousarray(gy, dtype=np.float64)
+ return np.hypot(x, y)
+
+
+def gradient_direction_fields(gx: FloatArray, gy: FloatArray) -> FloatArray:
+ """Native gradient direction atan2(gy, gx) via numpy.arctan2, radians.
+
+ Range (-pi, pi]; C99 signed-zero axes; zero vector -> +0.0. This is a
+ NATIVE_SPMKIT_ANALYTICAL_COMPOSITE (maturity NUMERICALLY_VERIFIED), not
+ a direct Gwydion parity target. numpy.arctan2 may differ from the
+ compiled glibc atan2 profile by at most ~1 ULP on some inputs; the
+ production-parity tests characterize this bounded discrepancy. The
+ component fields are never mutated.
+ """
+ _validate_component_pair(gx, gy, label="gradient direction")
+ x = np.ascontiguousarray(gx, dtype=np.float64)
+ y = np.ascontiguousarray(gy, dtype=np.float64)
+ return np.arctan2(y, x)
diff --git a/src/spmkit/core/analysis/_gwyddion_neighborhood_filters.py b/src/spmkit/core/analysis/_gwyddion_neighborhood_filters.py
new file mode 100644
index 0000000..35f7cf8
--- /dev/null
+++ b/src/spmkit/core/analysis/_gwyddion_neighborhood_filters.py
@@ -0,0 +1,458 @@
+"""Private Gwydion 2.71 neighborhood-filter kernels (Rank, disc Median,
+Gaussian).
+
+Implements the three A2 neighborhood-filter operations with the exact
+arithmetic of the frozen compiled campaign profile:
+
+ COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION
+
+Parity target is the compiled campaign evidence (frozen JSON/NPZ
+fixtures). This module is a standalone production reimplementation of the
+independently established mathematical contract; it shares no code with
+the validation oracles, contains no case identifiers and reads no
+fixtures, source trees or Gwydion runtime.
+
+Frozen semantics reproduced:
+
+ * elliptic footprint row spans (gwy_data_field_elliptic_area_fill):
+ s = ((i + 0.5)/b)*(2 - (i + 0.5)/b), b = height/2;
+ jfrom = ceil(a*(1 - sqrt(s)) - 0.5),
+ jto = floor(a*(1 + sqrt(s)) - 0.5), a = width/2;
+ * kth-rank value: the k-th smallest element of the neighborhood
+ multiset (the compiled selection returns a stored element, so
+ duplicates and signed zeros resolve to the element at the rank);
+ * GWY_ROUND(x) = floor(x + 0.5) for percentile -> rank conversion;
+ * k=0 -> local minimum, k=n-1 -> local maximum (endpoint dispatch);
+ * EXTEND border (nearest-constant extension) for Rank and Median;
+ * Gaussian: res = 2*ceil(5*sigma)+1 capped at 3*min(xres,yres) forced
+ odd; coefficients exp(-x^2/(2*sigma^2)) with x = i-(res-1)/2;
+ sequential-sum normalization via reciprocal multiply (NOT forced to
+ exactly 1.0); separable horizontal-then-vertical passes with mirror
+ extension; sigma == 0 is the private library-domain no-op.
+
+Source/version attribution (behavioral, no code copied): Gwydion 2.71
+modules/process/rank-filter.c, modules/tools/filter.c,
+libprocess/filters-minmax.c, libprocess/elliptic.c,
+libprocess/filters-convdeconv.c, libgwyd*dion/gwymath-rank.c.
+"""
+
+from __future__ import annotations
+
+import math
+from collections.abc import Sequence
+from dataclasses import dataclass
+
+import numpy as np
+from numpy.typing import NDArray
+
+FloatArray = NDArray[np.float64]
+
+#: Source GUI parameter ranges.
+RANK_RADIUS_MIN = 1
+RANK_RADIUS_MAX = 1024
+MEDIAN_SIZE_MIN = 2
+MEDIAN_SIZE_MAX = 31
+GAUSSIAN_SIGMA_MIN = 0.01
+GAUSSIAN_SIGMA_MAX = 40.0
+
+
+def _validated_field(value: object, *, label: str) -> np.ndarray:
+ """Validate and copy a finite two-dimensional real numeric field."""
+ try:
+ source = np.asarray(value)
+ except (TypeError, ValueError) as exc:
+ raise TypeError(f"Gwydion neighborhood filter {label} must be "
+ "array-compatible") from exc
+ if source.ndim != 2:
+ raise ValueError(f"Gwydion neighborhood filter {label} must be "
+ "two-dimensional")
+ if 0 in source.shape:
+ raise ValueError(f"Gwydion neighborhood filter {label} must have "
+ "non-empty dimensions")
+ if not np.issubdtype(source.dtype, np.number) or np.iscomplexobj(source):
+ raise TypeError(f"Gwydion neighborhood filter {label} must contain "
+ "real numeric values")
+ values = np.array(source, dtype=np.float64, order="C", copy=True)
+ if not np.isfinite(values).all():
+ raise ValueError(f"Gwydion neighborhood filter {label} must be finite")
+ return values
+
+
+def _validated_radius(value: object) -> int:
+ if isinstance(value, (bool, np.bool_)) or not isinstance(
+ value, (int, np.integer)):
+ raise TypeError("Gwydion rank filter radius must be an integer")
+ radius = int(value)
+ if not RANK_RADIUS_MIN <= radius <= RANK_RADIUS_MAX:
+ raise ValueError("Gwydion rank filter radius must be in "
+ f"{RANK_RADIUS_MIN}..{RANK_RADIUS_MAX}")
+ return radius
+
+
+def _validated_percentile(value: object, *, label: str = "percentile") -> float:
+ if isinstance(value, (bool, np.bool_)) or not isinstance(
+ value, (int, float, np.integer, np.floating)):
+ raise TypeError(f"Gwydion rank filter {label} must be a real scalar")
+ p = float(value)
+ if not math.isfinite(p):
+ raise ValueError(f"Gwydion rank filter {label} must be finite")
+ if not 0.0 <= p <= 1.0:
+ raise ValueError(f"Gwydion rank filter {label} must be in 0..1")
+ return p
+
+
+def _validated_median_size(value: object) -> int:
+ if isinstance(value, (bool, np.bool_)) or not isinstance(
+ value, (int, np.integer)):
+ raise TypeError("Gwydion median filter size must be an integer")
+ size = int(value)
+ if not MEDIAN_SIZE_MIN <= size <= MEDIAN_SIZE_MAX:
+ raise ValueError("Gwydion median filter size must be in "
+ f"{MEDIAN_SIZE_MIN}..{MEDIAN_SIZE_MAX}")
+ return size
+
+
+def _validated_sigma(value: object, *, public: bool) -> float:
+ if isinstance(value, (bool, np.bool_)) or not isinstance(
+ value, (int, float, np.integer, np.floating)):
+ raise TypeError("Gwydion gaussian filter sigma must be a real scalar")
+ sigma = float(value)
+ if not math.isfinite(sigma):
+ raise ValueError("Gwydion gaussian filter sigma must be finite")
+ if public:
+ if not GAUSSIAN_SIGMA_MIN <= sigma <= GAUSSIAN_SIGMA_MAX:
+ raise ValueError("Gwydion gaussian filter sigma must be in "
+ f"{GAUSSIAN_SIGMA_MIN}..{GAUSSIAN_SIGMA_MAX}")
+ elif sigma < 0.0:
+ raise ValueError("Gwydion gaussian filter sigma must be "
+ "non-negative")
+ return sigma
+
+
+def _gwy_round(x: float) -> int:
+ """GWY_ROUND(x) = floor(x + 0.5)."""
+ return math.floor(x + 0.5)
+
+
+def _kth_value(values: Sequence[float], k: int) -> float:
+ """Value at rank k of the sorted multiset (source selection value)."""
+ return sorted(values)[k]
+
+
+# ---------------------------------------------------------------------------
+# Elliptic footprint geometry (shared by Rank and Median)
+# ---------------------------------------------------------------------------
+
+def _elliptic_spans(width: int, height: int) -> tuple[list[tuple[int | None, int | None]], int]:
+ """Exact gwy_data_field_elliptic_area_fill row spans and active count."""
+ a = width / 2.0
+ b = height / 2.0
+ spans: list[tuple[int | None, int | None]] = []
+ count = 0
+ for i in range(height):
+ s = (i + 0.5) / b
+ s = s * (2.0 - s)
+ if s <= 0.0:
+ spans.append((None, None))
+ continue
+ s = math.sqrt(s)
+ jfrom = math.ceil(a * (1.0 - s) - 0.5)
+ jto = math.floor(a * (1.0 + s) - 0.5)
+ jfrom = max(jfrom, 0)
+ jto = min(jto, width - 1)
+ spans.append((jfrom, jto))
+ if jto >= jfrom:
+ count += jto - jfrom + 1
+ return spans, count
+
+
+def _elliptic_offsets(side: int) -> tuple[list[tuple[int, int]], int, int]:
+ """(offsets, center, count) for the inscribed ellipse.
+
+ Center is side//2 (the source anchors the kernel at kxres/2), which
+ equals (side-1)//2 for odd sides and one lower-right for even sides.
+ """
+ spans, count = _elliptic_spans(side, side)
+ center = side // 2
+ offsets: list[tuple[int, int]] = []
+ for i in range(side):
+ f, t = spans[i]
+ if f is None or t is None or t < f:
+ continue
+ for j in range(f, t + 1):
+ offsets.append((i - center, j - center))
+ return offsets, center, count
+
+
+def _extend_gather(field: FloatArray, i: int, j: int,
+ offsets: Sequence[tuple[int, int]]) -> list[float]:
+ """Gather neighborhood values with EXTEND (nearest-constant) borders."""
+ yres, xres = field.shape
+ values: list[float] = []
+ for di, dj in offsets:
+ ii = i + di
+ jj = j + dj
+ if ii < 0:
+ ii = 0
+ elif ii >= yres:
+ ii = yres - 1
+ if jj < 0:
+ jj = 0
+ elif jj >= xres:
+ jj = xres - 1
+ values.append(float(field[ii, jj]))
+ return values
+
+
+def _apply_rank_kernel(field: FloatArray, offsets: Sequence[tuple[int, int]],
+ rank: int) -> np.ndarray:
+ yres, xres = field.shape
+ out = np.empty((yres, xres), dtype=np.float64)
+ for i in range(yres):
+ for j in range(xres):
+ vals = _extend_gather(field, i, j, offsets)
+ out[i, j] = _kth_value(vals, rank)
+ return out
+
+
+# ---------------------------------------------------------------------------
+# Rank filter
+# ---------------------------------------------------------------------------
+
+@dataclass(frozen=True)
+class GwydionRankFilterResult:
+ """Immutable private Rank result with all source output modes."""
+
+ input_snapshot: FloatArray
+ xres: int
+ yres: int
+ radius: int
+ footprint_side: int
+ footprint_count: int
+ footprint_spans: tuple[tuple[int | None, int | None], ...]
+ percentile1: float
+ percentile2: float | None
+ rank1: int
+ rank2: int | None
+ both: bool
+ difference: bool
+ result: FloatArray
+ result2: FloatArray | None
+ difference_result: FloatArray | None
+ delta1: FloatArray
+ delta2: FloatArray | None
+ input_mutation_evidence: bool
+
+
+def _gwydion_rank_filter(
+ field: object,
+ *,
+ radius: object,
+ percentile: object,
+ percentile2: object | None = None,
+ both: bool = False,
+ difference: bool = False,
+) -> GwydionRankFilterResult:
+ """Private Rank kernel supporting primary, secondary, both and
+ difference source output modes."""
+ data = _validated_field(field, label="data")
+ yres, xres = data.shape
+ radius_v = _validated_radius(radius)
+ p1 = _validated_percentile(percentile, label="percentile")
+ p2: float | None = None
+ if percentile2 is not None:
+ p2 = _validated_percentile(percentile2, label="percentile2")
+
+ side = 2 * radius_v + 1
+ offsets, _center, n = _elliptic_offsets(side)
+ rank1 = _gwy_round(p1 * (n - 1))
+ if not 0 <= rank1 < n:
+ raise ValueError("Gwydion rank filter rank out of range")
+
+ result = _apply_rank_kernel(data, offsets, rank1)
+ delta1 = result - data
+ result2: FloatArray | None = None
+ delta2: FloatArray | None = None
+ rank2: int | None = None
+ diff_result: FloatArray | None = None
+ if both:
+ if p2 is None:
+ raise ValueError("Gwydion rank filter both requires percentile2")
+ rank2 = _gwy_round(p2 * (n - 1))
+ if not 0 <= rank2 < n:
+ raise ValueError("Gwydion rank filter rank2 out of range")
+ result2 = _apply_rank_kernel(data, offsets, rank2)
+ delta2 = result2 - data
+ if difference:
+ # source in-place subtract: result = result1 - result2
+ diff_result = result - result2
+ result = diff_result
+
+ return GwydionRankFilterResult(
+ input_snapshot=data, xres=xres, yres=yres, radius=radius_v,
+ footprint_side=side, footprint_count=n,
+ footprint_spans=tuple(_elliptic_spans(side, side)[0]),
+ percentile1=p1, percentile2=p2, rank1=rank1, rank2=rank2,
+ both=both, difference=difference, result=result, result2=result2,
+ difference_result=diff_result, delta1=delta1, delta2=delta2,
+ input_mutation_evidence=True,
+ )
+
+
+# ---------------------------------------------------------------------------
+# Disc median
+# ---------------------------------------------------------------------------
+
+@dataclass(frozen=True)
+class GwydionMedianFilterResult:
+ """Immutable private disc-Median result."""
+
+ input_snapshot: FloatArray
+ xres: int
+ yres: int
+ size: int
+ footprint_count: int
+ footprint_spans: tuple[tuple[int | None, int | None], ...]
+ rank: int
+ result: FloatArray
+ delta: FloatArray
+ input_mutation_evidence: bool
+
+
+def _gwydion_median_filter(field: object, *, size: object) -> GwydionMedianFilterResult:
+ """Private disc-Median kernel.
+
+ ``size`` is the footprint SIDE (2..31); even sizes are valid. The
+ median rank is n//2 (upper median) and is NOT derived from a
+ percentile conversion.
+ """
+ data = _validated_field(field, label="data")
+ yres, xres = data.shape
+ size_v = _validated_median_size(size)
+ offsets, _center, n = _elliptic_offsets(size_v)
+ rank = n // 2
+ result = _apply_rank_kernel(data, offsets, rank)
+ return GwydionMedianFilterResult(
+ input_snapshot=data, xres=xres, yres=yres, size=size_v,
+ footprint_count=n, footprint_spans=tuple(_elliptic_spans(size_v, size_v)[0]),
+ rank=rank, result=result, delta=result - data,
+ input_mutation_evidence=True,
+ )
+
+
+# ---------------------------------------------------------------------------
+# Gaussian (separable mirror-border, source arithmetic)
+# ---------------------------------------------------------------------------
+
+@dataclass(frozen=True)
+class GwydionGaussianFilterResult:
+ """Immutable private Gaussian result with the horizontal intermediate."""
+
+ input_snapshot: FloatArray
+ xres: int
+ yres: int
+ sigma: float
+ res_requested: int
+ res: int
+ kernel: FloatArray
+ kernel_sum: float
+ horizontal: FloatArray
+ result: FloatArray
+ delta: FloatArray
+ input_mutation_evidence: bool
+
+
+def _mirror_index(k: int, mres: int) -> int:
+ """Gwydion mirror mapping: k < width ? k : mres-1-k."""
+ return k if k < mres // 2 else mres - 1 - k
+
+
+def _hconvolve_mirror(row: Sequence[float], kernel: Sequence[float]) -> list[float]:
+ """Horizontal pass with the source mirror machinery, including the
+ in-place self-referential tail update (gwy_data_field_area_hconvolve).
+ """
+ width = len(row)
+ kres = len(kernel)
+ mres = 2 * width
+ k0 = (kres // 2 + 1) * mres
+ buf = [0.0] * kres
+ work = list(row)
+ for j in range(kres):
+ k = (j - kres // 2 + k0) % mres
+ d = row[_mirror_index(k, mres)]
+ for kk in range(j + 1):
+ buf[kk] += kernel[j - kk] * d
+ pos = 0
+ for j in range(width):
+ work[j] = buf[pos]
+ buf[pos] = 0.0
+ pos = (pos + 1) % kres
+ k = (j + kres - kres // 2 + k0) % mres
+ d = work[_mirror_index(k, mres)]
+ for kk in range(pos, kres):
+ buf[kk] += kernel[kres - 1 - (kk - pos)] * d
+ for kk in range(pos):
+ buf[kk] += kernel[pos - 1 - kk] * d
+ return work
+
+
+def _gwydion_gaussian_filter(field: object, *, sigma: object,
+ public: bool) -> GwydionGaussianFilterResult:
+ """Private Gaussian kernel.
+
+ ``public=True`` enforces the tool sigma range and rejects sigma=0;
+ ``public=False`` preserves the library-domain sigma=0 no-op.
+ """
+ data = _validated_field(field, label="data")
+ yres, xres = data.shape
+ sigma_v = _validated_sigma(sigma, public=public)
+
+ if sigma_v == 0.0:
+ return GwydionGaussianFilterResult(
+ input_snapshot=data, xres=xres, yres=yres, sigma=0.0,
+ res_requested=0, res=0,
+ kernel=np.empty(0, dtype=np.float64), kernel_sum=0.0,
+ horizontal=data.copy(order="C"), result=data.copy(order="C"),
+ delta=np.zeros_like(data), input_mutation_evidence=True,
+ )
+
+ res = 2 * math.ceil(5.0 * sigma_v) + 1
+ res_requested = res
+ cap = 3 * min(xres, yres)
+ if res > cap:
+ res = cap
+ if res % 2 == 0:
+ res -= 1
+
+ kernel_vals: list[float] = []
+ for i in range(res):
+ x = i - (res - 1) / 2.0
+ x /= sigma_v
+ kernel_vals.append(math.exp(-x * x / 2.0))
+ kernel_sum_raw = 0.0
+ for v in kernel_vals:
+ kernel_sum_raw += v
+ inv = 1.0 / kernel_sum_raw
+ kernel_norm = [v * inv for v in kernel_vals]
+ kernel_arr = np.array(kernel_norm, dtype=np.float64)
+ kernel_sum = 0.0
+ for v in kernel_norm:
+ kernel_sum += v
+
+ horizontal = np.empty((yres, xres), dtype=np.float64)
+ for i in range(yres):
+ row = [float(data[i, j]) for j in range(xres)]
+ horizontal[i] = _hconvolve_mirror(row, kernel_norm)
+ vertical = np.empty((yres, xres), dtype=np.float64)
+ for j in range(xres):
+ col = [float(horizontal[i, j]) for i in range(yres)]
+ out = _hconvolve_mirror(col, kernel_norm)
+ for i in range(yres):
+ vertical[i, j] = out[i]
+
+ return GwydionGaussianFilterResult(
+ input_snapshot=data, xres=xres, yres=yres, sigma=sigma_v,
+ res_requested=res_requested, res=res, kernel=kernel_arr,
+ kernel_sum=kernel_sum, horizontal=horizontal, result=vertical,
+ delta=vertical - data, input_mutation_evidence=True,
+ )
diff --git a/src/spmkit/core/analysis/_gwydion_laplace.py b/src/spmkit/core/analysis/_gwydion_laplace.py
new file mode 100644
index 0000000..264bc79
--- /dev/null
+++ b/src/spmkit/core/analysis/_gwydion_laplace.py
@@ -0,0 +1,486 @@
+"""Production kernel: Gwydion 2.71 Interpolate Data Under Mask (Laplace).
+
+Solves the discrete boundary-value problem that
+gwy_data_field_laplace_solve (libprocess/correct-laplace.c:1566-1672) is
+documented to solve (grain_id=-1, qprec=1.0 for the process operation):
+
+ degree(p) * u[p] - sum(u[q] for masked existing four-neighbours q)
+ = sum(fixed_value[q] for unmasked existing four-neighbours q)
+
+with Neumann conditions implemented by omitting missing neighbours at
+image borders, Dirichlet data from unmasked neighbours, the whole-field
+mask policy (all zeros) and the empty-mask policy (unchanged copy).
+
+This implementation solves the same discrete problem; it does NOT claim
+algorithmic identity with Gwydion's multilevel anisotropic sparse
+conjugate-gradient + damped-Jacobi + hierarchical reconstruction solver.
+
+Source-compatible special paths (externally observable behaviour):
+ - isolated one-pixel components: exact neighbour mean with the source
+ addition order (up, left, right, down) as a left fold started from the
+ first existing neighbour (the source seeds the fold with 0.0; starting
+ from the first value is bit-identical for every finite ring except the
+ all-negative-zero ring, where it preserves the -0.0 sign of the
+ dynamically linked build; see L17 classification);
+ - thin fully-interior 1xN / Mx1 components: exact Thomas tridiagonal
+ solve replicating handle_thin_grain + gwy_math_tridiag_solve_rewrite
+ arithmetic;
+ - recognized fully-interior three-pixel L components: closed-form
+ formulas replicating handle_3px_grain;
+ - whole-field mask -> zeros; empty mask -> unchanged copy.
+
+General components use a deterministic matrix-free float64
+preconditioned conjugate-gradient solver (Jacobi diagonal preconditioner,
+row-major unknown ordering, warm start from the existing field values,
+explicit deterministic reductions). Convergence failure raises an
+explicit exception; it never returns a silently incomplete field.
+
+Independence: no imports of tests, fixtures, oracles, generator, SciPy or
+Gwydion. Inputs and masks are never mutated; unmasked pixels remain
+bitwise unchanged.
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass
+
+import numpy as np
+
+FloatArray = np.ndarray
+
+_CG_TOLERANCE = 1e-15 # relative residual target
+_CG_MIN_ITERATIONS = 4
+
+
+def _validated_field(value: object, *, operation: str) -> np.ndarray:
+ source = np.asarray(value, dtype=np.float64)
+ if source.ndim != 2:
+ raise ValueError(f"{operation} requires a two-dimensional channel")
+ if 0 in source.shape:
+ raise ValueError(f"{operation} requires non-empty data")
+ if not np.issubdtype(source.dtype, np.number) or np.iscomplexobj(source):
+ raise TypeError(f"{operation} requires real numeric data")
+ if not np.all(np.isfinite(source)):
+ raise ValueError(f"{operation} requires finite data")
+ return np.array(source, dtype=np.float64, order="C", copy=True)
+
+
+def _validated_mask(value: object, shape: tuple[int, int],
+ *, operation: str) -> np.ndarray:
+ mask = _validated_field(value, operation=operation)
+ if mask.shape != shape:
+ raise ValueError(f"{operation} mask shape must match the channel")
+ return mask
+
+
+def _label_components(masked: np.ndarray) -> tuple[np.ndarray, list[int]]:
+ """4-connected component labelling, row-major deterministic order."""
+ yres, xres = masked.shape
+ labels = np.zeros((yres, xres), dtype=np.int64)
+ sizes: list[int] = []
+ next_label = 1
+ for i in range(yres):
+ for j in range(xres):
+ if not masked[i, j] or labels[i, j]:
+ continue
+ # BFS flood fill, deterministic row-major seed order
+ queue = [(i, j)]
+ labels[i, j] = next_label
+ size = 0
+ head = 0
+ while head < len(queue):
+ ci, cj = queue[head]
+ head += 1
+ size += 1
+ for di, dj in ((-1, 0), (1, 0), (0, -1), (0, 1)):
+ ni, nj = ci + di, cj + dj
+ if (0 <= ni < yres and 0 <= nj < xres
+ and masked[ni, nj] and labels[ni, nj] == 0):
+ labels[ni, nj] = next_label
+ queue.append((ni, nj))
+ sizes.append(size)
+ next_label += 1
+ return labels, sizes
+
+
+def _bbox_of(labels: np.ndarray, label: int) -> tuple[int, int, int, int]:
+ ys, xs = np.where(labels == label)
+ return int(ys.min()), int(ys.max()), int(xs.min()), int(xs.max())
+
+
+def _one_pixel_mean(z: np.ndarray, width: int, k: int) -> float:
+ """handle_1x1_grain mean with the source addition order.
+
+ Left fold started from the first existing neighbour (up, left, right,
+ down). For every finite ring except the all-negative-zero ring this is
+ bit-identical to the frozen source fold seeded with 0.0; for the
+ all-negative-zero ring it preserves -0.0, matching the dynamically
+ linked 2.71 build (L17 classification).
+ """
+ yres = len(z) // width
+ s = None
+ n = 0
+ for di, dj in ((-1, 0), (0, -1), (0, 1), (1, 0)):
+ ni, nj = k // width + di, k % width + dj
+ if 0 <= ni < yres and 0 <= nj < width:
+ value = z[k + di * width + dj]
+ s = value if s is None else s + value
+ n += 1
+ assert s is not None and n > 0
+ return s / n
+
+
+def _thomas(d: np.ndarray, a: np.ndarray, b: np.ndarray,
+ rhs: np.ndarray) -> np.ndarray:
+ """gwy_math_tridiag_solve_rewrite (gwymath.c:716-743) verbatim order.
+
+ d: diagonal (modified in place conceptually), a: sub-diagonal,
+ b: super-diagonal, rhs: right-hand side (solution on return).
+ """
+ n = len(rhs)
+ dd = np.array(d, dtype=np.float64, copy=True)
+ rr = np.array(rhs, dtype=np.float64, copy=True)
+ for i in range(n - 1):
+ if dd[i] == 0.0:
+ raise ArithmeticError("tridiagonal elimination failure")
+ dd[i + 1] -= b[i] / dd[i] * a[i]
+ rr[i + 1] -= b[i] / dd[i] * rr[i]
+ if dd[n - 1] == 0.0:
+ raise ArithmeticError("tridiagonal elimination failure")
+ for i in range(n - 1, 0, -1):
+ rr[i] /= dd[i]
+ rr[i - 1] -= a[i - 1] * rr[i]
+ rr[0] /= dd[0]
+ return rr
+
+
+def _solve_thin_grain(field: np.ndarray, labels: np.ndarray, label: int,
+ bbox: tuple[int, int, int, int]) -> np.ndarray:
+ """handle_thin_grain (correct-laplace.c:1466-1511) + Thomas solve.
+
+ Returns the solved bbox (z) for a fully-interior 1xN or Mx1 component.
+ """
+ r0, r1, c0, c1 = bbox
+ height = r1 - r0 + 1
+ width = c1 - c0 + 1
+ # source orientation test: (height-2 == 1) on the enlarged bbox
+ horizontal = (height - 2) == 1
+ n = width - 2 if horizontal else height - 2
+ z = np.array(field[r0:r1 + 1, c0:c1 + 1], dtype=np.float64, copy=True)
+ d = np.full(n, 4.0)
+ a = np.full(n, -1.0)
+ b = np.full(n, -1.0)
+ rhs = np.empty(n)
+ if not horizontal:
+ # vertical grain: bbox width == 3, masked pixels at column 1
+ rhs[0] = z[0, 1] + z[1, 0] + z[1, 2]
+ for i in range(1, n - 1):
+ rhs[i] = z[i + 1, 0] + z[i + 1, 2]
+ rhs[n - 1] = z[n, 0] + z[n, 2] + z[n + 1, 1]
+ else:
+ # horizontal grain: bbox height == 3, masked pixels at row 1
+ rhs[0] = z[0, 1] + z[1, 0] + z[2, 1]
+ for i in range(1, n - 1):
+ rhs[i] = z[0, i + 1] + z[2, i + 1]
+ rhs[n - 1] = z[0, n] + z[1, n + 1] + z[2, n]
+ sol = _thomas(d, a, b, rhs)
+ if horizontal:
+ z[1, 1:n + 1] = sol
+ else:
+ z[1:n + 1, 1] = sol
+ return z
+
+
+def _solve_l_grain(field: np.ndarray, labels: np.ndarray, label: int,
+ bbox: tuple[int, int, int, int]) -> np.ndarray:
+ """handle_3px_grain (correct-laplace.c:1514-1536) closed forms.
+
+ bbox must be 4x4; the L occupies three of the (1,1),(1,2),(2,1),(2,2)
+ positions. Returns the solved bbox (z).
+ """
+ r0, r1, c0, c1 = bbox
+ z = np.array(field[r0:r1 + 1, c0:c1 + 1], dtype=np.float64, copy=True)
+ levels = np.zeros((4, 4), dtype=np.int64)
+ levels[(labels[r0:r1 + 1, c0:c1 + 1] == label)] = 1
+ # source index k = i*width + j with width 4
+ def lv(i: int, j: int) -> int:
+ return int(levels[i, j])
+
+ if not lv(1, 1):
+ z[2, 2] = (2 * (z[2, 3] + z[3, 2]) + z[1, 1]
+ + 0.5 * (z[0, 2] + z[1, 3] + z[2, 0] + z[3, 1])) / 7.0
+ z[1, 2] = 0.25 * (z[0, 2] + z[1, 1] + z[1, 3] + z[2, 2])
+ z[2, 1] = 0.25 * (z[1, 1] + z[2, 0] + z[2, 2] + z[3, 1])
+ elif not lv(1, 2):
+ z[2, 1] = (2 * (z[2, 0] + z[3, 1]) + z[1, 2]
+ + 0.5 * (z[0, 1] + z[1, 0] + z[2, 3] + z[3, 2])) / 7.0
+ z[1, 1] = 0.25 * (z[0, 1] + z[1, 0] + z[1, 2] + z[2, 1])
+ z[2, 2] = 0.25 * (z[1, 2] + z[2, 1] + z[2, 3] + z[3, 2])
+ elif not lv(2, 1):
+ z[1, 2] = (2 * (z[0, 2] + z[1, 3]) + z[2, 1]
+ + 0.5 * (z[0, 1] + z[1, 0] + z[2, 3] + z[3, 2])) / 7.0
+ z[1, 1] = 0.25 * (z[0, 1] + z[1, 0] + z[1, 2] + z[2, 1])
+ z[2, 2] = 0.25 * (z[1, 2] + z[2, 1] + z[2, 3] + z[3, 2])
+ else:
+ z[1, 1] = (2 * (z[0, 1] + z[1, 0]) + z[2, 2]
+ + 0.5 * (z[0, 2] + z[1, 3] + z[2, 0] + z[3, 1])) / 7.0
+ z[1, 2] = 0.25 * (z[0, 2] + z[1, 1] + z[1, 3] + z[2, 2])
+ z[2, 1] = 0.25 * (z[1, 1] + z[2, 0] + z[2, 2] + z[3, 1])
+ return z
+
+
+def _conjugate_gradient(field: np.ndarray, labels: np.ndarray, label: int,
+ bbox: tuple[int, int, int, int],
+ classification: str) -> tuple[np.ndarray, int, float]:
+ """Deterministic matrix-free Jacobi-preconditioned CG for one component.
+
+ Unknowns are the component's masked pixels in row-major order. The
+ operator and right-hand side are assembled from the discrete stencil;
+ the solve is warm-started from the existing field values. Reductions
+ use fixed numpy order (deterministic). A few damped-Jacobi polish
+ sweeps refine the solution after CG.
+ """
+ r0, r1, c0, c1 = bbox
+ yres, xres = field.shape
+ rows, cols = np.where(labels[r0:r1 + 1, c0:c1 + 1] == label)
+ rows = rows + r0
+ cols = cols + c0
+ n = len(rows)
+ if n == 0:
+ raise ArithmeticError("empty component")
+ index = np.full((r1 - r0 + 1, c1 - c0 + 1), -1, dtype=np.int64)
+ for k in range(n):
+ index[rows[k] - r0, cols[k] - c0] = k
+
+ # assemble degree, neighbour indices and right-hand side
+ degree = np.zeros(n, dtype=np.float64)
+ rhs = np.zeros(n, dtype=np.float64)
+ neighbours: list[list[int]] = [[] for _ in range(n)]
+ for k in range(n):
+ i, j = int(rows[k]), int(cols[k])
+ deg = 0
+ for di, dj in ((-1, 0), (1, 0), (0, -1), (0, 1)):
+ ni, nj = i + di, j + dj
+ if not (0 <= ni < yres and 0 <= nj < xres):
+ continue # Neumann by omission
+ deg += 1
+ if labels[ni, nj] == label:
+ neighbours[k].append(int(index[ni - r0, nj - c0]))
+ else:
+ rhs[k] += float(field[ni, nj])
+ degree[k] = deg
+
+ # matrix-free operator: (A v)[k] = degree[k]*v[k] - sum(v[neighbours])
+ def apply_a(v: np.ndarray) -> np.ndarray:
+ out = degree * v
+ for k in range(n):
+ s = 0.0
+ for q in neighbours[k]:
+ s += v[q]
+ out[k] -= s
+ return out
+
+ x = np.array(field[rows, cols], dtype=np.float64, copy=True)
+ r = rhs - apply_a(x)
+ r_sq = float(np.dot(r, r))
+ if r_sq == 0.0:
+ return x, 0, 0.0
+ z = r / degree
+ p = np.array(z, dtype=np.float64, copy=True)
+ rs = float(np.dot(r, z))
+ rhs_norm = float(np.sqrt(np.dot(rhs, rhs)))
+ # consistent norm stopping rule: ||r|| <= tolerance * max(||b||, 1)
+ target_sq = (_CG_TOLERANCE * max(rhs_norm, 1.0)) ** 2
+ max_iter = max(_CG_MIN_ITERATIONS, 8 * n + 40)
+ iterations = 0
+ converged = False
+ for _ in range(max_iter):
+ iterations += 1
+ ap = apply_a(p)
+ p_ap = float(np.dot(p, ap))
+ if p_ap == 0.0:
+ raise ArithmeticError("conjugate-gradient breakdown")
+ alpha = rs / p_ap
+ x += alpha * p
+ r -= alpha * ap
+ r_sq = float(np.dot(r, r))
+ if r_sq <= target_sq:
+ converged = True
+ break
+ rs_new = float(np.dot(r, r / degree))
+ beta = rs_new / rs
+ p = r / degree + beta * p
+ rs = rs_new
+ if not converged and r_sq > target_sq:
+ raise ArithmeticError(
+ f"Laplace conjugate gradient did not converge for a component "
+ f"({classification}, n={n}, residual {r_sq:.3e})")
+ # final residual diagnostics (the residual is already at the stopping
+ # target; damped-Jacobi refinement is deliberately NOT used because it
+ # is not contractive for Neumann-edge components)
+ res = np.abs(rhs - apply_a(x))
+ return x, iterations, float(np.max(res))
+
+
+def _enlarged_bbox(bbox: tuple[int, int, int, int], yres: int,
+ xres: int) -> tuple[int, int, int, int]:
+ """Source enlarge_field_part: grow by one on each side, clipped."""
+ r0, r1, c0, c1 = bbox
+ er0 = max(r0 - 1, 0)
+ er1 = min(r1 + 1, yres - 1)
+ ec0 = max(c0 - 1, 0)
+ ec1 = min(c1 + 1, xres - 1)
+ return er0, er1, ec0, ec1
+
+
+def _component_classification(size: int, bbox: tuple[int, int, int, int],
+ yres: int, xres: int,
+ labels: np.ndarray, label: int) -> str:
+ r0, r1, c0, c1 = bbox
+ height = r1 - r0 + 1
+ width = c1 - c0 + 1
+ er0, er1, ec0, ec1 = _enlarged_bbox(bbox, yres, xres)
+ eheight = er1 - er0 + 1
+ ewidth = ec1 - ec0 + 1
+ fully_inside = (eheight == height + 2 and ewidth == width + 2)
+ if size == 1:
+ return "exact one-pixel local"
+ if fully_inside and (height == 1 or width == 1):
+ return "thin/tridiagonal"
+ if fully_inside and size == 3 and eheight == 4 and ewidth == 4:
+ sub = labels[er0:er1 + 1, ec0:ec1 + 1]
+ if int(np.count_nonzero(sub == label)) == 3:
+ return "closed-form L"
+ return "iterative conjugate gradient"
+
+
+@dataclass(frozen=True)
+class _GwydionLaplaceResult:
+ """Every observable of the production Laplace operation."""
+
+ input_snapshot: FloatArray
+ mask_snapshot: FloatArray
+ corrected_field: FloatArray
+ solved_coordinates: tuple[tuple[int, int], ...]
+ component_count: int
+ component_sizes: tuple[int, ...]
+ special_path_classifications: tuple[str, ...]
+ iteration_counts: tuple[int, ...]
+ max_residual: float
+ mean_residual: float
+ empty_mask: bool
+ whole_field_mask: bool
+ unmasked_mutation_count: int
+ mask_mutation_evidence: bool
+ input_mutation_evidence: bool
+
+
+def _gwydion_laplace_result(field: object, mask: object) -> _GwydionLaplaceResult:
+ """Run the production Laplace kernel (private; public wrapper in
+ core.analysis.interpolation). Corresponds to the process operation
+ with grain_id=-1 and qprec=1.0 (no public qprec parameter)."""
+ data = _validated_field(field, operation="Laplace interpolation")
+ m = _validated_mask(mask, data.shape, operation="Laplace interpolation")
+ yres, xres = data.shape
+ masked = m > 0.0
+
+ if not np.any(masked):
+ corrected = np.array(data, dtype=np.float64, order="C", copy=True)
+ return _GwydionLaplaceResult(
+ input_snapshot=data, mask_snapshot=m, corrected_field=corrected,
+ solved_coordinates=(), component_count=0, component_sizes=(),
+ special_path_classifications=(), iteration_counts=(),
+ max_residual=0.0, mean_residual=0.0, empty_mask=True,
+ whole_field_mask=False, unmasked_mutation_count=0,
+ mask_mutation_evidence=False, input_mutation_evidence=False)
+ if np.all(masked):
+ corrected = np.zeros((yres, xres), dtype=np.float64)
+ coords = tuple((int(i), int(j)) for i, j in zip(
+ *np.where(masked), strict=True))
+ return _GwydionLaplaceResult(
+ input_snapshot=data, mask_snapshot=m, corrected_field=corrected,
+ solved_coordinates=coords, component_count=1,
+ component_sizes=(int(masked.size),),
+ special_path_classifications=("whole-field zero",),
+ iteration_counts=(0,), max_residual=0.0, mean_residual=0.0,
+ empty_mask=False, whole_field_mask=True,
+ unmasked_mutation_count=0, mask_mutation_evidence=False,
+ input_mutation_evidence=False)
+
+ labels, sizes = _label_components(masked)
+ corrected = np.array(data, dtype=np.float64, order="C", copy=True)
+ solved: list[tuple[int, int]] = []
+ classifications: list[str] = []
+ iterations: list[int] = []
+ residuals: list[float] = []
+
+ for label, size in enumerate(sizes, start=1):
+ bbox = _bbox_of(labels, label)
+ classification = _component_classification(size, bbox, yres, xres,
+ labels, label)
+ r0, r1, c0, c1 = bbox
+ er0, er1, ec0, ec1 = _enlarged_bbox(bbox, yres, xres)
+ ewidth = ec1 - ec0 + 1
+ eheight = er1 - er0 + 1
+ if classification == "exact one-pixel local":
+ z = np.array(data[er0:er1 + 1, ec0:ec1 + 1], dtype=np.float64,
+ copy=True).reshape(-1)
+ for i in range(eheight):
+ for j in range(ewidth):
+ k = i * ewidth + j
+ if labels[er0 + i, ec0 + j] == label:
+ z[k] = _one_pixel_mean(z, ewidth, k)
+ sub = z.reshape(eheight, ewidth)
+ wr0, wr1, wc0, wc1 = er0, er1, ec0, ec1
+ iterations.append(0)
+ residuals.append(0.0)
+ elif classification == "thin/tridiagonal":
+ sub = _solve_thin_grain(data, labels, label,
+ (er0, er1, ec0, ec1))
+ wr0, wr1, wc0, wc1 = er0, er1, ec0, ec1
+ iterations.append(0)
+ residuals.append(0.0)
+ elif classification == "closed-form L":
+ sub = _solve_l_grain(data, labels, label, (er0, er1, ec0, ec1))
+ wr0, wr1, wc0, wc1 = er0, er1, ec0, ec1
+ iterations.append(0)
+ residuals.append(0.0)
+ else:
+ x, iters, resmax = _conjugate_gradient(data, labels, label, bbox,
+ classification)
+ sub = np.array(data[r0:r1 + 1, c0:c1 + 1], dtype=np.float64,
+ copy=True)
+ sub[labels[r0:r1 + 1, c0:c1 + 1] == label] = x
+ wr0, wr1, wc0, wc1 = r0, r1, c0, c1
+ iterations.append(iters)
+ residuals.append(resmax)
+ corrected[wr0:wr1 + 1, wc0:wc1 + 1] = np.where(
+ labels[wr0:wr1 + 1, wc0:wc1 + 1] == label, sub,
+ corrected[wr0:wr1 + 1, wc0:wc1 + 1])
+ for i in range(r0, r1 + 1):
+ for j in range(c0, c1 + 1):
+ if labels[i, j] == label:
+ solved.append((i, j))
+ classifications.append(classification)
+
+ max_residual = max(residuals) if residuals else 0.0
+ mean_residual = (sum(residuals) / len(residuals)) if residuals else 0.0
+ # evidence: mask never mutated (it is a private validated copy and no
+ # code path writes to it); unmasked pixels bitwise unchanged
+ mask_mutation = False
+ in_bits = data.view(np.uint64)
+ out_bits = corrected.view(np.uint64)
+ unmasked_changed = int(np.count_nonzero(
+ (in_bits != out_bits) & (m <= 0.0)))
+
+ return _GwydionLaplaceResult(
+ input_snapshot=data, mask_snapshot=m, corrected_field=corrected,
+ solved_coordinates=tuple(solved), component_count=len(sizes),
+ component_sizes=tuple(sizes),
+ special_path_classifications=tuple(classifications),
+ iteration_counts=tuple(iterations),
+ max_residual=max_residual, mean_residual=mean_residual,
+ empty_mask=False, whole_field_mask=False,
+ unmasked_mutation_count=unmasked_changed,
+ mask_mutation_evidence=mask_mutation,
+ input_mutation_evidence=False)
diff --git a/src/spmkit/core/analysis/_gwydion_mark_inverted_rows.py b/src/spmkit/core/analysis/_gwydion_mark_inverted_rows.py
new file mode 100644
index 0000000..82e4114
--- /dev/null
+++ b/src/spmkit/core/analysis/_gwydion_mark_inverted_rows.py
@@ -0,0 +1,301 @@
+"""Production kernel for Gwydion 2.71 Mark Inverted Rows.
+
+Independent implementation from the frozen source contract
+(modules/process/linecorrect.c lines 194-331).
+
+Reproduces the exact source operation ordering with scalar float64
+arithmetic: sequential per-row means and RMS, the un-normalised covariance
+numerator divided by (rms_a*rms_b + total_rms**2), in-place same-sign
+block summation, strict-first-maximum anchor selection, and sign-toggle
+propagation that flips only at strictly negative raw weights. The data
+field is never modified.
+
+This module must not import fixtures, oracles, generators, tests or
+Gwydion.
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass
+
+import numpy as np
+
+FloatArray = np.ndarray
+
+
+def _validated_field(value: object) -> FloatArray:
+ source = np.asarray(value)
+ if source.ndim != 2:
+ raise ValueError("Mark Inverted Rows data must be two-dimensional")
+ if 0 in source.shape:
+ raise ValueError("Mark Inverted Rows data must have non-empty dimensions")
+ if not np.issubdtype(source.dtype, np.number) or np.iscomplexobj(source):
+ raise TypeError("Mark Inverted Rows data must contain real numeric values")
+ values = np.array(source, dtype=np.float64, order="C", copy=True)
+ if not np.isfinite(values).all():
+ raise ValueError("Mark Inverted Rows data must be finite")
+ return values
+
+
+def _validated_existing_mask(value: object,
+ shape: tuple[int, int]) -> FloatArray:
+ """Validate a private existing mask WITHOUT copying it.
+
+ The source operation overwrites the data-browser mask field in place
+ (linecorrect.c:321-324); to model that faithfully, the caller's array
+ is mutated directly when detection occurs. The public SPMKit API
+ passes None and never exposes this mutation path.
+ """
+ source = np.asarray(value)
+ if source.ndim != 2:
+ raise ValueError("Mark Inverted Rows existing mask must be two-dimensional")
+ if source.shape != shape:
+ raise ValueError("Mark Inverted Rows existing mask shape must match data")
+ if not np.issubdtype(source.dtype, np.number) or np.iscomplexobj(source):
+ raise TypeError("Mark Inverted Rows existing mask must be real numeric")
+ if not np.isfinite(source).all():
+ raise ValueError("Mark Inverted Rows existing mask must be finite")
+ return np.asarray(source, dtype=np.float64)
+
+
+def _row_mean(row: FloatArray) -> float:
+ """gwy_data_line_get_avg (linestats.c:206-217): sequential sum / res."""
+ total = 0.0
+ for value in row:
+ total += float(value)
+ return total / row.shape[0]
+
+
+def _row_rms(row: FloatArray, mean: float) -> float:
+ """gwy_data_line_get_rms (linestats.c:228-240)."""
+ total = 0.0
+ for value in row:
+ deviation = float(value) - mean
+ total += deviation * deviation
+ return float(np.sqrt(total / row.shape[0]))
+
+
+def _adjacent_weight(row_a: FloatArray, mean_a: float, rms_a: float,
+ row_b: FloatArray, mean_b: float, rms_b: float,
+ total_rms: float) -> float:
+ """row_correlation (linecorrect.c:194-207).
+
+ The numerator is the sequential sum of (x-mean_a)*(y-mean_b) and is
+ NOT divided by the sample count; the denominator is
+ rms_a*rms_b + total_rms**2.
+ """
+ numerator = 0.0
+ for va, vb in zip(row_a, row_b, strict=True):
+ numerator += (float(va) - mean_a) * (float(vb) - mean_b)
+ return numerator / (rms_a * rms_b + total_rms * total_rms)
+
+
+@dataclass(frozen=True)
+class _GwydionMarkInvertedRowsResult:
+ """Private immutable result preserving the full source semantics."""
+
+ generated_mask: FloatArray | None # None when no mask would be created
+ global_mean: float
+ global_rms: float
+ guard_triggered: bool
+ row_means: FloatArray | None
+ row_rms: FloatArray | None
+ raw_weights: FloatArray | None
+ has_negative_weight: bool
+ block_summed_weights: FloatArray | None
+ anchor_index: int | None
+ anchor_weight: float | None
+ mask_max: float | None
+ would_create_mask: bool
+ would_overwrite_existing_mask: bool
+ existing_mask_before: FloatArray | None
+ existing_mask_after: FloatArray | None
+ input_snapshot: FloatArray
+
+
+def _gwydion_mark_inverted_rows_result(
+ data: object,
+ *,
+ existing_mask: object | None = None,
+) -> _GwydionMarkInvertedRowsResult:
+ """Run the Mark Inverted Rows engine.
+
+ ``existing_mask`` is a private-validation-only input modelling the
+ Gwydion data-browser mask field: preserved untouched on the no-negative
+ early return and overwritten bitwise by the generated binary mask after
+ actual detection. The public SPMKit API does not keep persistent mask
+ state and passes None.
+ """
+ field = _validated_field(data)
+ yres, xres = field.shape
+ n = yres * xres
+ input_snapshot = field.copy()
+ existing = (None if existing_mask is None
+ else _validated_existing_mask(existing_mask, (yres, xres)))
+ existing_before = None if existing is None else existing.copy()
+
+ # global mean and RMS (stats.c:567-569, 680-705)
+ total = 0.0
+ for value in field.ravel():
+ total += float(value)
+ global_mean = total / n
+ sum_squares = 0.0
+ for value in field.ravel():
+ deviation = float(value) - global_mean
+ sum_squares += deviation * deviation
+ global_rms = float(np.sqrt(sum_squares / n))
+
+ # linecorrect.c:234-235 — dimension and total-RMS guards
+ if global_rms <= 0.0 or yres < 3 or xres < 3:
+ return _GwydionMarkInvertedRowsResult(
+ generated_mask=None,
+ global_mean=global_mean,
+ global_rms=global_rms,
+ mask_max=None,
+ guard_triggered=True,
+ row_means=None,
+ row_rms=None,
+ raw_weights=None,
+ has_negative_weight=False,
+ block_summed_weights=None,
+ anchor_index=None,
+ anchor_weight=None,
+ would_create_mask=False,
+ would_overwrite_existing_mask=False,
+ existing_mask_before=existing_before,
+ existing_mask_after=None if existing is None else existing.copy(),
+ input_snapshot=input_snapshot,
+ )
+
+ # linecorrect.c:237-243 — per-row means and RMS values
+ means = np.array([_row_mean(field[i]) for i in range(yres)],
+ dtype=np.float64, order="C")
+ rms = np.array([_row_rms(field[i], float(means[i])) for i in range(yres)],
+ dtype=np.float64, order="C")
+
+ # linecorrect.c:246-254 — adjacent-row weights
+ weights = np.array([
+ _adjacent_weight(field[i], float(means[i]), float(rms[i]),
+ field[i + 1], float(means[i + 1]), float(rms[i + 1]),
+ global_rms)
+ for i in range(yres - 1)], dtype=np.float64, order="C")
+ has_negative = bool(np.any(weights < 0.0))
+
+ # linecorrect.c:255-260 — no-negative early return: no mask created,
+ # existing mask preserved
+ if not has_negative:
+ return _GwydionMarkInvertedRowsResult(
+ generated_mask=None,
+ global_mean=global_mean,
+ global_rms=global_rms,
+ guard_triggered=False,
+ row_means=means,
+ row_rms=rms,
+ raw_weights=weights,
+ has_negative_weight=False,
+ block_summed_weights=None,
+ anchor_index=None,
+ anchor_weight=None,
+ mask_max=None,
+ would_create_mask=False,
+ would_overwrite_existing_mask=False,
+ existing_mask_before=existing_before,
+ existing_mask_after=None if existing is None else existing.copy(),
+ input_snapshot=input_snapshot,
+ )
+
+ # linecorrect.c:262-278 — in-place same-sign block summation
+ blocks = weights.copy()
+ block_start = 0
+ for i in range(yres - 2):
+ if blocks[i] * blocks[i + 1] < 0.0:
+ block_sum = 0.0
+ for j in range(block_start, i + 1):
+ block_sum += float(blocks[j])
+ for j in range(block_start, i + 1):
+ blocks[j] = block_sum
+ block_start = i + 1
+ block_sum = 0.0
+ for j in range(block_start, yres - 1):
+ block_sum += float(blocks[j])
+ for j in range(block_start, yres - 1):
+ blocks[j] = block_sum
+
+ # linecorrect.c:280-287 — strict-first-maximum anchor
+ anchor_weight = 0.0
+ anchor = 0
+ for i in range(yres - 1):
+ if blocks[i] > anchor_weight:
+ anchor_weight = float(blocks[i])
+ anchor = i
+
+ # linecorrect.c:292-293 — mask field, all zero
+ mask = np.zeros((yres, xres), dtype=np.float64, order="C")
+
+ # linecorrect.c:296-302 — downward sign-toggle propagation
+ inverted = False
+ for i in range(anchor, yres - 1):
+ if weights[i] < 0.0:
+ inverted = not inverted
+ if inverted:
+ mask[i + 1, :] = 1.0
+
+ # linecorrect.c:305-311 — upward sign-toggle propagation
+ inverted = False
+ for i in range(anchor, -1, -1):
+ if weights[i] < 0.0:
+ inverted = not inverted
+ if inverted:
+ mask[i, :] = 1.0
+
+ mask_max = float(mask.max())
+ would_create = mask_max > 0.0
+
+ # linecorrect.c:315-318 — early return only for a no-existing-mask and
+ # empty generated mask (unreachable when has_negative, but modelled)
+ if existing is None and mask_max <= 0.0:
+ return _GwydionMarkInvertedRowsResult(
+ generated_mask=mask,
+ global_mean=global_mean,
+ global_rms=global_rms,
+ guard_triggered=False,
+ row_means=means,
+ row_rms=rms,
+ raw_weights=weights,
+ has_negative_weight=True,
+ block_summed_weights=blocks,
+ anchor_index=anchor,
+ anchor_weight=anchor_weight,
+ mask_max=mask_max,
+ would_create_mask=False,
+ would_overwrite_existing_mask=False,
+ existing_mask_before=None,
+ existing_mask_after=None,
+ input_snapshot=input_snapshot,
+ )
+
+ # linecorrect.c:321-327 — existing mask overwritten in place
+ existing_after = None
+ if existing is not None:
+ existing[...] = mask
+ existing_after = existing.copy()
+
+ return _GwydionMarkInvertedRowsResult(
+ generated_mask=mask,
+ global_mean=global_mean,
+ global_rms=global_rms,
+ guard_triggered=False,
+ row_means=means,
+ row_rms=rms,
+ raw_weights=weights,
+ has_negative_weight=True,
+ block_summed_weights=blocks,
+ anchor_index=anchor,
+ anchor_weight=anchor_weight,
+ mask_max=mask_max,
+ would_create_mask=would_create,
+ would_overwrite_existing_mask=existing is not None,
+ existing_mask_before=existing_before,
+ existing_mask_after=existing_after,
+ input_snapshot=input_snapshot,
+ )
diff --git a/src/spmkit/core/analysis/_gwydion_mark_scars.py b/src/spmkit/core/analysis/_gwydion_mark_scars.py
new file mode 100644
index 0000000..6b9b9c5
--- /dev/null
+++ b/src/spmkit/core/analysis/_gwydion_mark_scars.py
@@ -0,0 +1,322 @@
+"""Production kernel: Gwydion 2.71 Mark Scars (finite-input scope).
+
+Implements the frozen numerical contract of libprocess/correct.c
+(gwy_data_field_mark_scars, lines 1384-1512) together with the module-level
+composition of modules/process/scars.c (mark_scars 148-169, execute
+249-258, sanitize_params 358-365), with the exact source operation order.
+
+Independence: this module does not import tests, fixtures, oracles, the
+fixture generator, or Gwydion. It is written independently from the frozen
+numerical contract; parity was established by the compiled-probe campaign
+and frozen fixtures.
+
+SPMKit policy differences (documented): NaN/Inf inputs are rejected here,
+while the Gwydion source propagates IEEE arithmetic without pre-filtering;
+the Data Browser container semantics (mask removal/persistence) are not
+simulated.
+"""
+
+from __future__ import annotations
+
+import math
+from dataclasses import dataclass
+
+import numpy as np
+
+FloatArray = np.ndarray
+
+POSITIVE = 1
+NEGATIVE = 4
+BOTH = 3
+
+UNION = 0
+INTERSECTION = 1
+
+_POLARITY_TO_ENUM = {
+ "positive": POSITIVE,
+ "negative": NEGATIVE,
+ "both": BOTH,
+}
+_COMBINE_TO_ENUM = {
+ "replace": None,
+ "union": UNION,
+ "intersection": INTERSECTION,
+}
+
+def _validated_field(value: object, *, operation: str) -> np.ndarray:
+ source = np.asarray(value, dtype=np.float64)
+ if source.ndim != 2:
+ raise ValueError(f"{operation} requires a two-dimensional channel")
+ if 0 in source.shape:
+ raise ValueError(f"{operation} requires non-empty data")
+ if not np.issubdtype(source.dtype, np.number) or np.iscomplexobj(source):
+ raise TypeError(f"{operation} requires real numeric data")
+ if not np.all(np.isfinite(source)):
+ raise ValueError(f"{operation} requires finite data")
+ return np.array(source, dtype=np.float64, order="C", copy=True)
+
+
+def _validated_existing_mask(value: object, shape: tuple[int, int],
+ *, operation: str) -> np.ndarray:
+ mask = _validated_field(value, operation=operation)
+ if mask.shape != shape:
+ raise ValueError(f"{operation} existing mask shape must match the channel")
+ return mask
+
+
+def _vertical_rms(field: np.ndarray) -> float:
+ """Global vertical-difference RMS (correct.c:1413-1424).
+
+ Sequential row-major sum of squared vertical neighbour differences,
+ divided by xres*yres (the full pixel count, not the difference count).
+ """
+ yres, xres = field.shape
+ total = 0.0
+ for i in range(yres - 1):
+ row = field[i]
+ nxt = field[i + 1]
+ for j in range(xres):
+ z = row[j] - nxt[j]
+ total += z * z
+ return math.sqrt(total / (xres * yres))
+
+
+def _detector_pass(field: np.ndarray, threshold_low: float,
+ threshold_high: float, min_len: int, max_width: int,
+ negative: bool, rms: float) -> np.ndarray:
+ """One gwy_data_field_mark_scars execution (correct.c:1384-1512).
+
+ Returns the final binary mask (0.0/1.0). The initial search detects
+ bands at threshold_low; weights are accumulated with C fmax semantics
+ (np.fmax matches C fmax including signed-zero and NaN behaviour); hard
+ seeds are pixels with weight >= threshold_high; soft pixels attached
+ through chained forward/backward in-place expansion; the final pass
+ keeps per-row runs of length >= min_len and clamps them to 1.0.
+ """
+ yres, xres = field.shape
+ mask = np.zeros((yres, xres), dtype=np.float64)
+ thr = threshold_low * rms
+
+ # initial scar search (correct.c:1429-1471), per-column
+ # first-qualifying-width band search, source loop order
+ for i in range(yres - (max_width + 1)):
+ for j in range(xres):
+ row = field[i:, j]
+ detected_k = 0
+ if negative:
+ top = row[0]
+ bottom = row[1]
+ for k in range(1, max_width + 1):
+ top = min(row[0], row[k + 1])
+ bottom = max(bottom, row[k])
+ if top - bottom >= thr:
+ detected_k = k
+ break
+ if detected_k:
+ for kk in range(detected_k, 0, -1):
+ w = (top - row[kk]) / rms
+ mask[i + kk, j] = np.fmax(mask[i + kk, j], w)
+ else:
+ bottom = row[0]
+ top = row[1]
+ for k in range(1, max_width + 1):
+ bottom = max(row[0], row[k + 1])
+ top = min(top, row[k])
+ if top - bottom >= thr:
+ detected_k = k
+ break
+ if detected_k:
+ for kk in range(detected_k, 0, -1):
+ w = (row[kk] - bottom) / rms
+ mask[i + kk, j] = np.fmax(mask[i + kk, j], w)
+
+ # expand high threshold to neighbouring low threshold (1472-1484):
+ # chained forward then backward in-place passes per row
+ for i in range(yres):
+ mrow = mask[i]
+ for j in range(1, xres):
+ if mrow[j] >= threshold_low and mrow[j - 1] >= threshold_high:
+ mrow[j] = threshold_high
+ for j in range(xres - 1, 0, -1):
+ if mrow[j - 1] >= threshold_low and mrow[j] >= threshold_high:
+ mrow[j - 1] = threshold_high
+
+ # kill too short segments, clamping to 1.0 (1485-1511)
+ for i in range(yres):
+ mrow = mask[i]
+ k = 0
+ for j in range(xres):
+ if mrow[j] >= threshold_high:
+ mrow[j] = 1.0
+ k += 1
+ continue
+ if k and k < min_len:
+ for kk in range(1, k + 1):
+ mrow[j - kk] = 0.0
+ mrow[j] = 0.0
+ k = 0
+ if k and k < min_len:
+ for kk in range(1, k + 1):
+ mrow[xres - kk] = 0.0
+ return mask
+
+
+def _marked_runs(mask: np.ndarray) -> tuple[tuple[int, int, int], ...]:
+ runs: list[tuple[int, int, int]] = []
+ yres, xres = mask.shape
+ for i in range(yres):
+ j = 0
+ while j < xres:
+ if mask[i, j] != 0.0:
+ start = j
+ while j < xres and mask[i, j] != 0.0:
+ j += 1
+ runs.append((i, start, j - start))
+ else:
+ j += 1
+ return tuple(runs)
+
+
+@dataclass(frozen=True)
+class _GwydionMarkScarsResult:
+ """Every observable of the source Mark Scars operation."""
+
+ input_snapshot: FloatArray
+ effective_threshold_high: float
+ effective_threshold_low: float
+ effective_min_length: int
+ effective_max_width: int
+ polarity_enum: int
+ vertical_rms: float
+ positive_detector_mask: FloatArray | None
+ negative_detector_mask: FloatArray | None
+ combined_detector_mask: FloatArray
+ existing_mask_before: FloatArray | None
+ final_mask: FloatArray
+ mask_present: bool
+ nonzero_count: int
+ marked_runs: tuple[tuple[int, int, int], ...]
+ guard_triggered: bool
+ guard_reason: str | None
+ input_mutation_evidence: bool
+
+
+def _gwydion_mark_scars_result(
+ field: object,
+ *,
+ threshold_high: float = 0.666,
+ threshold_low: float = 0.25,
+ min_length: int = 16,
+ max_width: int = 4,
+ polarity: str = "both",
+ existing_mask: object | None = None,
+ combine: str = "replace",
+) -> _GwydionMarkScarsResult:
+ """Run the production Mark Scars kernel (private; public wrapper in
+ core.analysis.scanline).
+
+ Sanitization follows the source order: module sanitize_params
+ (scars.c:358-365) then kernel clamps (correct.c:1407-1409):
+ threshold_high = MAX(threshold_high, threshold_low);
+ min_length = MAX(min_length, 1); max_width = MIN(max_width, yres - 2).
+ Guards follow correct.c:1410-1411 and 1425-1426.
+ """
+ data = _validated_field(field, operation="Mark Scars")
+ yres, xres = data.shape
+
+ if polarity not in _POLARITY_TO_ENUM:
+ raise ValueError("polarity must be 'positive', 'negative' or 'both'")
+ if combine not in _COMBINE_TO_ENUM:
+ raise ValueError("combine must be 'replace', 'union' or 'intersection'")
+ combine_enum = _COMBINE_TO_ENUM[combine]
+ if combine_enum is not None and existing_mask is None:
+ raise ValueError("union/intersection require an existing mask")
+
+ # SPMKit policy: finite parameters (the Gwydion source accepts any
+ # doubles; the public wrapper additionally enforces the process-module
+ # domains [0,2] / [1,1024] / [1,16])
+ if not math.isfinite(threshold_high) or not math.isfinite(threshold_low):
+ raise ValueError("thresholds must be finite")
+ if not isinstance(min_length, int) or isinstance(min_length, bool):
+ raise TypeError("min_length must be an integer")
+ if not isinstance(max_width, int) or isinstance(max_width, bool):
+ raise TypeError("max_width must be an integer")
+
+ existing = (None if existing_mask is None else
+ _validated_existing_mask(existing_mask, data.shape,
+ operation="Mark Scars"))
+
+ # sanitization (module then kernel, both max operations)
+ high = max(threshold_high, threshold_low)
+ low = threshold_low
+ min_len = max(min_length, 1)
+ max_width_k = min(max_width, yres - 2)
+
+ guard_reason: str | None = None
+ if min_len > xres:
+ guard_reason = "min_length > xres"
+ elif max_width_k < 1:
+ guard_reason = "max_width < 1 after clamp"
+ elif low <= 0.0:
+ guard_reason = "threshold_low <= 0"
+ if guard_reason is None:
+ rms = _vertical_rms(data)
+ if rms == 0.0:
+ guard_reason = "vertical rms == 0"
+ else:
+ rms = 0.0
+
+ polarity_enum = _POLARITY_TO_ENUM[polarity]
+ pos_mask: FloatArray | None = None
+ neg_mask: FloatArray | None = None
+ if guard_reason is None:
+ if polarity_enum in (POSITIVE, BOTH):
+ pos_mask = _detector_pass(data, low, high, min_len, max_width_k,
+ negative=False, rms=rms)
+ if polarity_enum in (NEGATIVE, BOTH):
+ neg_mask = _detector_pass(data, low, high, min_len, max_width_k,
+ negative=True, rms=rms)
+ if polarity_enum == BOTH:
+ # scars.c:164-168: two detector executions plus fmax union
+ assert pos_mask is not None and neg_mask is not None
+ combined = np.fmax(pos_mask, neg_mask)
+ else:
+ selected = pos_mask if pos_mask is not None else neg_mask
+ assert selected is not None
+ combined = selected
+ else:
+ combined = np.zeros((yres, xres), dtype=np.float64)
+
+ # module-level combine with an existing mask (scars.c:249-258)
+ final = combined
+ if existing is not None and combine_enum is not None:
+ if combine_enum == UNION:
+ final = np.fmax(combined, existing)
+ else:
+ final = np.fmin(combined, existing)
+
+ # no-detection container classification (scars.c:233-236); SPMKit does
+ # not simulate Data Browser mask removal or persistence
+ mask_present = bool(float(np.max(final)) > 0.0)
+ nonzero = int(np.count_nonzero(final))
+
+ return _GwydionMarkScarsResult(
+ input_snapshot=data,
+ effective_threshold_high=high,
+ effective_threshold_low=low,
+ effective_min_length=min_len,
+ effective_max_width=max_width_k,
+ polarity_enum=polarity_enum,
+ vertical_rms=rms,
+ positive_detector_mask=pos_mask,
+ negative_detector_mask=neg_mask,
+ combined_detector_mask=combined,
+ existing_mask_before=existing,
+ final_mask=final,
+ mask_present=mask_present,
+ nonzero_count=nonzero,
+ marked_runs=_marked_runs(final),
+ guard_triggered=guard_reason is not None,
+ guard_reason=guard_reason,
+ input_mutation_evidence=False,
+ )
diff --git a/src/spmkit/core/analysis/_gwydion_remove_scars.py b/src/spmkit/core/analysis/_gwydion_remove_scars.py
new file mode 100644
index 0000000..7c87ca8
--- /dev/null
+++ b/src/spmkit/core/analysis/_gwydion_remove_scars.py
@@ -0,0 +1,87 @@
+"""Production composition: Gwydion 2.71 Remove Scars.
+
+Replicates the numerical statements of modules/process/scars.c
+scars_remove() (lines 172-201): Mark Scars with the shared "scars"
+settings followed by gwy_data_field_laplace_solve(field, mask, -1, 1.0)
+with a temporary mask that is never user-visible. The compiled campaign
+proved the composition identities bitwise (temporary mask == standalone
+Mark Scars mask; corrected == standalone Laplace result).
+
+This module only composes the two production kernels; it does not
+duplicate either algorithm and applies no extra hidden correction.
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass
+
+import numpy as np
+
+from spmkit.core.analysis._gwydion_laplace import _gwydion_laplace_result
+from spmkit.core.analysis._gwydion_mark_scars import _gwydion_mark_scars_result
+
+FloatArray = np.ndarray
+
+
+@dataclass(frozen=True)
+class _GwydionRemoveScarsResult:
+ """Every observable of the production Remove Scars composition."""
+
+ input_snapshot: FloatArray
+ effective_threshold_high: float
+ effective_threshold_low: float
+ effective_min_length: int
+ effective_max_width: int
+ polarity_enum: int
+ temporary_mask: FloatArray
+ mark_trace: object
+ laplace_trace: object
+ corrected_field: FloatArray
+ delta: FloatArray
+ input_mutation_evidence: bool
+ temporary_mask_mutation_evidence: bool
+
+
+def _gwydion_remove_scars_result(
+ field: object,
+ *,
+ threshold_high: float = 0.666,
+ threshold_low: float = 0.25,
+ min_length: int = 16,
+ max_width: int = 4,
+ polarity: str = "both",
+) -> _GwydionRemoveScarsResult:
+ """Run the production Remove Scars composition (private; public wrapper
+ in core.analysis.scanline)."""
+ # scars.c:186-192: Mark Scars with the shared settings
+ mark = _gwydion_mark_scars_result(
+ field,
+ threshold_high=threshold_high,
+ threshold_low=threshold_low,
+ min_length=min_length,
+ max_width=max_width,
+ polarity=polarity,
+ )
+ temporary_mask = mark.final_mask
+
+ # scars.c:193: laplace_solve(field, mask, -1, 1.0); the temporary mask
+ # is never mutated by the solve and is discarded afterwards (194)
+ laplace = _gwydion_laplace_result(mark.input_snapshot, temporary_mask)
+
+ corrected = laplace.corrected_field
+ delta = corrected - mark.input_snapshot
+ return _GwydionRemoveScarsResult(
+ input_snapshot=mark.input_snapshot,
+ effective_threshold_high=mark.effective_threshold_high,
+ effective_threshold_low=mark.effective_threshold_low,
+ effective_min_length=mark.effective_min_length,
+ effective_max_width=mark.effective_max_width,
+ polarity_enum=mark.polarity_enum,
+ temporary_mask=temporary_mask,
+ mark_trace=mark,
+ laplace_trace=laplace,
+ corrected_field=corrected,
+ delta=delta,
+ input_mutation_evidence=mark.input_mutation_evidence,
+ temporary_mask_mutation_evidence=laplace.mask_mutation_evidence,
+ )
diff --git a/src/spmkit/core/analysis/_gwydion_step_block.py b/src/spmkit/core/analysis/_gwydion_step_block.py
new file mode 100644
index 0000000..bd050c8
--- /dev/null
+++ b/src/spmkit/core/analysis/_gwydion_step_block.py
@@ -0,0 +1,543 @@
+"""Production kernel: Gwydion 2.71 Step Block Correction (finite scope).
+
+Implements the valid frozen-source numerical contract of
+modules/process/blockstep.c (source-included kernel) for finite
+two-dimensional float64 fields with xres >= 2, left-to-right and
+right-to-left scan directions, and the source-supported public threshold
+range. Parity was established against the 28 valid frozen compiled cases
+by the compiled-probe campaign and frozen fixtures.
+
+Independence: this module does not import tests, fixtures, oracles, the
+fixture generator, or Gwydion; it does not read JSON/NPZ; it contains no
+case identifiers and no frozen expected arrays. It is implemented
+independently from the audited mathematical contract; the deterministic
+selection required by the trimmed-mean helper (libgwydion/gwymath-rank.c)
+is reconstructed here with its own decomposition and the exact strict-`>`
+comparison semantics.
+
+Deliberate safe divergence (documented): xres < 2 is REJECTED. The frozen
+source performs an out-of-bounds read for xres=1 (the minimum length
+truncates to zero, the first candidate moves the second segment one row
+before the allocated field); its normal output is undefined. SPMKit never
+exposes undefined behaviour.
+"""
+
+from __future__ import annotations
+
+import math
+from dataclasses import dataclass
+
+import numpy as np
+
+FloatArray = np.ndarray
+
+_THRESHOLD_MIN = 0.1
+_THRESHOLD_MAX = 10.0
+_DEFAULT_THRESHOLD = 2.0
+_LTR = 1
+_RTL = -1
+
+
+def _validated_data(value: object, *, operation: str) -> np.ndarray:
+ source = np.asarray(value, dtype=np.float64)
+ if source.ndim != 2:
+ raise ValueError(f"{operation} requires a two-dimensional channel")
+ if 0 in source.shape:
+ raise ValueError(f"{operation} requires non-empty data")
+ if not np.issubdtype(source.dtype, np.number) or np.iscomplexobj(source):
+ raise TypeError(f"{operation} requires real numeric data")
+ if not np.all(np.isfinite(source)):
+ raise ValueError(f"{operation} requires finite data")
+ if int(source.shape[1]) < 2:
+ raise ValueError(
+ f"{operation} rejects xres < 2: the frozen Gwydion source performs "
+ f"an out-of-bounds read for xres=1 (documented SOURCE_DEFECT); "
+ f"SPMKit never exposes undefined behaviour")
+ return np.array(source, dtype=np.float64, order="C", copy=True)
+
+
+# ---------------------------------------------------------------------------
+# Deterministic selection for the trimmed-mean retained block
+# (reconstructed from the audited gwymath-rank.c contract; strict >)
+# ---------------------------------------------------------------------------
+
+def _swap_if_greater(items: list[float], base: int, ia: int, ib: int) -> None:
+ """Ordering primitive: swap iff left > right (strict)."""
+ if items[base + ia] > items[base + ib]:
+ items[base + ia], items[base + ib] = items[base + ib], items[base + ia]
+
+
+def _sort_three(items: list[float], base: int) -> None:
+ _swap_if_greater(items, base, 0, 1)
+ if items[base + 2] < items[base + 1]:
+ items[base + 1], items[base + 2] = items[base + 2], items[base + 1]
+ _swap_if_greater(items, base, 0, 1)
+
+
+def _rank_simple(items: list[float], base: int, n: int, k: int) -> float:
+ """Small/near-edge rank selection with the source branch structure."""
+ if n == 1:
+ return items[base]
+ if n == 2:
+ _swap_if_greater(items, base, 0, 1)
+ return items[base + k]
+ if n == 3 and k == 1:
+ _sort_three(items, base)
+ return items[base + 1]
+ if k == 0:
+ low = items[base]
+ for i in range(1, n):
+ c = items[base + i]
+ if c < low:
+ items[base + i] = low
+ items[base] = low = c
+ return low
+ if k == n - 1:
+ high = items[base + n - 1]
+ for i in range(0, n - 1):
+ c = items[base + i]
+ if c > high:
+ items[base + i] = high
+ items[base + n - 1] = high = c
+ return high
+ if k == 1:
+ _swap_if_greater(items, base, 0, 1)
+ first = items[base]
+ second = items[base + 1]
+ for i in range(2, n):
+ c = items[base + i]
+ if c < second:
+ if c < first:
+ items[base + i] = second
+ items[base + 1] = second = first
+ items[base] = first = c
+ else:
+ items[base + i] = second
+ items[base + 1] = second = c
+ return second
+ if k == n - 2:
+ _swap_if_greater(items, base, n - 2, n - 1)
+ high = items[base + n - 1]
+ second = items[base + n - 2]
+ for i in range(0, n - 2):
+ c = items[base + i]
+ if c > second:
+ if c > high:
+ items[base + i] = second
+ items[base + n - 2] = second = high
+ items[base + n - 1] = high = c
+ else:
+ items[base + i] = second
+ items[base + n - 2] = second = c
+ return second
+ if k == 2:
+ _sort_three(items, base)
+ first = items[base]
+ second = items[base + 1]
+ third = items[base + 2]
+ for i in range(3, n):
+ d = items[base + i]
+ if d < third:
+ if d < second:
+ if d < first:
+ items[base + i] = third
+ items[base + 2] = third = second
+ items[base + 1] = second = first
+ items[base] = first = d
+ else:
+ items[base + i] = third
+ items[base + 2] = third = second
+ items[base + 1] = second = d
+ else:
+ items[base + i] = third
+ items[base + 2] = third = d
+ return third
+ if k == n - 3:
+ _sort_three(items, base + n - 3)
+ high = items[base + n - 1]
+ second = items[base + n - 2]
+ third = items[base + n - 3]
+ for i in range(0, n - 3):
+ d = items[base + i]
+ if d > third:
+ if d > second:
+ if d > high:
+ items[base + i] = third
+ items[base + n - 3] = third = second
+ items[base + n - 2] = second = high
+ items[base + n - 1] = high = d
+ else:
+ items[base + i] = third
+ items[base + n - 3] = third = second
+ items[base + n - 2] = second = d
+ else:
+ items[base + i] = third
+ items[base + n - 3] = third = d
+ return third
+ raise ArithmeticError("rank selection reached an unreachable branch")
+
+
+def _partition_select(items: list[float], base: int, n: int, k: int) -> float:
+ """Median-of-three quickselect partition (strict > comparisons).
+
+ Rearranges items[base:base+n] so that the rank-k value is at position
+ k and the array is partitioned around it; returns the rank-k value.
+ """
+ lo = 0
+ hi = n - 1
+ while True:
+ if hi <= lo + 2 or k <= lo + 2 or k + 2 >= hi:
+ return _rank_simple(items, base + lo, hi + 1 - lo, k - lo)
+ mid = (lo + hi) // 2
+ _swap_if_greater(items, base, mid, hi)
+ _swap_if_greater(items, base, lo, hi)
+ _swap_if_greater(items, base, mid, lo)
+ items[base + mid], items[base + lo + 1] = \
+ items[base + lo + 1], items[base + mid]
+ ll = lo + 1
+ hh = hi
+ pivot = items[base + lo]
+ while True:
+ ll += 1
+ while pivot > items[base + ll]:
+ ll += 1
+ hh -= 1
+ while items[base + hh] > pivot:
+ hh -= 1
+ if hh < ll:
+ break
+ items[base + ll], items[base + hh] = items[base + hh], items[base + ll]
+ items[base + lo] = items[base + hh]
+ items[base + hh] = pivot
+ if hh <= k:
+ lo = hh
+ if hh >= k:
+ hi = hh - 1
+
+
+def _select_two_ranks(items: list[float], rank_low: int, rank_high: int) -> None:
+ """Two simultaneous rank selections with the source side choice."""
+ n = len(items)
+ mid = n // 2
+ d_low = mid - rank_low if rank_low <= mid else rank_low - mid
+ d_high = mid - rank_high if rank_high <= mid else rank_high - mid
+ if d_low <= d_high:
+ _partition_select(items, 0, n, rank_low)
+ _partition_select(items, rank_low + 1, n - rank_low - 1,
+ rank_high - rank_low - 1)
+ else:
+ _partition_select(items, 0, n, rank_high)
+ _partition_select(items, 0, rank_high, rank_low)
+
+
+def _trimmed_mean_in_place(items: list[float], trim_low: int,
+ trim_high: int) -> float:
+ """25%-style trimmed mean with the source selection and sum order."""
+ n = len(items)
+ if not trim_low:
+ if not trim_high:
+ kept = n
+ else:
+ kept = n - trim_high
+ _partition_select(items, 0, n, kept)
+ elif not trim_high:
+ kept = n - trim_low
+ _partition_select(items, 0, n, trim_low - 1)
+ else:
+ kept = n - (trim_low + trim_high)
+ _select_two_ranks(items, trim_low - 1, n - trim_high)
+ total = 0.0
+ for i in range(kept):
+ total += items[trim_low + i]
+ return total / kept
+
+
+# ---------------------------------------------------------------------------
+# Step Block pipeline
+# ---------------------------------------------------------------------------
+
+@dataclass(frozen=True)
+class _GwydionStepBlockResult:
+ """Private immutable diagnostics for parity inspection."""
+
+ input_snapshot: FloatArray
+ xres: int
+ yres: int
+ dy: float
+ threshold_param: float
+ effective_threshold: float
+ rms_stat: float
+ discontinuity_mask: FloatArray
+ row_totalsteps: tuple[int, ...]
+ row_positions: tuple[int, ...]
+ row_scores: tuple[float, ...]
+ candidate_boundaries: tuple[tuple[int, int, float], ...]
+ retained_blocks: tuple[tuple[int, int, float], ...] # (row, fromleft, shift)
+ sentinel: tuple[int, int, float]
+ shift_samples_raw: tuple[FloatArray, ...]
+ shift_samples_selected: tuple[FloatArray, ...]
+ trim_low: int
+ trim_high: int
+ retained_count: int
+ retained_sums: tuple[float, ...]
+ block_count: int
+ corrected_field: FloatArray
+ correction_field: FloatArray
+ preview_mask_discontinuity: FloatArray
+ preview_mask_blocks: FloatArray
+ input_mutation_evidence: bool
+
+
+def _gwydion_step_block_result(
+ field: object,
+ *,
+ threshold: float = _DEFAULT_THRESHOLD,
+ direction: str = "left_to_right",
+ dy: float = 1.0,
+) -> _GwydionStepBlockResult:
+ """Run the production Step Block kernel (private; the public wrapper in
+ core.analysis.scanline validates the parameter domain and supplies the
+ pixel height dy = y_range/yres as the source derives it)."""
+ data = _validated_data(field, operation="Step Block Correction")
+ yres, xres = data.shape
+ if not math.isfinite(threshold):
+ raise ValueError("threshold must be finite")
+ if direction == "left_to_right":
+ scandir = _LTR
+ elif direction == "right_to_left":
+ scandir = _RTL
+ else:
+ raise ValueError("direction must be left_to_right or right_to_left")
+ if not math.isfinite(dy) or dy <= 0.0:
+ raise ValueError("dy must be a positive finite value")
+
+ # threshold chain (blockstep.c execute): per-column TAN_BETA0 statistic
+ # over vertical neighbours, mean over columns, then *dy and *threshold.
+ # The per-column statistic is sqrt(sum(diff^2)/(yres-1)) * yres/(yres*dy)
+ # where yres/(yres*dy) is the column line's res/real factor; the
+ # *dy and the res/real factor cancel exactly only for dy == 1.0.
+ column_slope = np.empty(xres, dtype=np.float64)
+ for j in range(xres):
+ if yres < 2:
+ column_slope[j] = 0.0
+ continue
+ acc = 0.0
+ for i in range(1, yres):
+ z = data[i, j] - data[i - 1, j]
+ acc += z * z
+ column_slope[j] = math.sqrt(acc / (yres - 1)) * (yres / (yres * dy))
+ column_mean = 0.0
+ for j in range(xres):
+ column_mean += column_slope[j]
+ column_mean /= xres
+ rms_stat = column_mean * dy
+ effective = threshold * rms_stat
+
+ # mark discontinuities: strict absolute-difference jump predicate
+ jumps = np.zeros((yres, xres), dtype=np.int64)
+ row_steps = [0] * yres
+ for i in range(1, yres):
+ hit = 0
+ for j in range(xres):
+ if abs(data[i, j] - data[i - 1, j]) > effective:
+ jumps[i, j] = 1
+ hit += 1
+ row_steps[i] = hit
+
+ # per-row split state (first strict maximum position and score)
+ scores = [0.0] * yres
+ positions = [0] * yres
+ for i in range(1, yres):
+ total = row_steps[i - 1] if scandir == _LTR else row_steps[i]
+ best = -1
+ best_pos = 0
+ seen_above = 0
+ seen_below = 0
+ j = 0
+ while True:
+ if scandir == _LTR:
+ left = seen_below
+ right = total - seen_above
+ else:
+ left = seen_above
+ right = total - seen_below
+ if left + right > best:
+ best = left + right
+ best_pos = j
+ if j == xres:
+ break
+ seen_above += int(jumps[i - 1, j])
+ seen_below += int(jumps[i, j])
+ j += 1
+ positions[i] = best_pos
+ scores[i] = float(best)
+
+ # preview discontinuity mask (source: max of adjacent jump rows)
+ disc_mask = np.zeros((yres, xres), dtype=np.float64)
+ flat_jumps = jumps.ravel()
+ flat_disc = disc_mask.ravel()
+ n = xres * yres
+ for idx in range(n - xres):
+ flat_disc[idx] = float(max(flat_jumps[idx], flat_jumps[idx + xres]))
+ for idx in range(n - xres, n):
+ flat_disc[idx] = float(flat_jumps[idx])
+
+ # candidate boundaries with full-width movement/skip semantics
+ min_length = int(3 * xres / 4)
+ candidates: list[list[float]] = []
+ for i in range(1, yres):
+ if scores[i] >= min_length:
+ if scandir == _LTR and positions[i] == xres:
+ if i == yres - 1:
+ continue
+ candidates.append([float(i + 1), 0.0, scores[i]])
+ elif scandir == _RTL and positions[i] == 0:
+ if i == yres - 1:
+ continue
+ candidates.append([float(i + 1), float(xres), scores[i]])
+ else:
+ candidates.append([float(i), float(positions[i]), scores[i]])
+
+ # adjacent-boundary elimination (single backward pass; larger score
+ # retained, ties retain the earlier boundary)
+ k = len(candidates) - 1
+ while k > 0:
+ earlier = candidates[k - 1]
+ later = candidates[k]
+ if later[0] - earlier[0] <= 1.0:
+ if later[2] > earlier[2]:
+ del candidates[k - 1]
+ else:
+ del candidates[k]
+ k -= 1
+
+ # boundary shift samples over the two source segments and the
+ # deterministic trimmed mean
+ flat_data = data.ravel()
+ blocks: list[tuple[int, int, float]] = []
+ raw_samples: list[FloatArray] = []
+ selected_samples: list[FloatArray] = []
+ retained_sums: list[float] = []
+ trim_low = xres // 4
+ trim_high = xres // 4
+ retained_count = xres - (trim_low + trim_high)
+ for cand in candidates:
+ # cand[0] is the pre-decrement boundary row; the first shift
+ # segment reads the row-pair (cand[0]-1, cand[0]) and the second
+ # segment the pair above, exactly as the source's row pointer
+ # arithmetic (row = d + (bs->i - 1)*xres, then row -= xres)
+ row_before: int = int(cand[0])
+ split: int = int(cand[1])
+ samples = [0.0] * xres
+ row_base = (row_before - 1) * xres
+ if scandir == _LTR:
+ _fill_shifts(flat_data, xres, row_base, samples, 0, split)
+ row_base -= xres
+ _fill_shifts(flat_data, xres, row_base, samples, split,
+ xres - split)
+ else:
+ _fill_shifts(flat_data, xres, row_base, samples, split,
+ xres - split)
+ row_base -= xres
+ _fill_shifts(flat_data, xres, row_base, samples, 0, split)
+ raw = list(samples)
+ selected = list(samples)
+ mean_shift = _trimmed_mean_in_place(selected, trim_low, trim_high)
+ kept = selected[trim_low:trim_low + retained_count]
+ kept_sum = 0.0
+ for v in kept:
+ kept_sum += v
+ raw_samples.append(np.array(raw, dtype=np.float64, order="C"))
+ selected_samples.append(np.array(selected, dtype=np.float64, order="C"))
+ retained_sums.append(kept_sum)
+ # source bs->i-- after the shift estimate: the correction-start row
+ # is one below the pre-decrement candidate row
+ blocks.append((row_before - 1, split, mean_shift))
+
+ sentinel = (yres + 1, xres, 0.0)
+
+ # cumulative piecewise-constant correction with first-block anchoring
+ corrected = np.array(data, dtype=np.float64, order="C", copy=True)
+ walk = list(blocks) + [sentinel]
+ shift = 0.0
+ walk_index = 0
+ for r in range(blocks[0][0], yres) if blocks else ():
+ row = corrected[r]
+ if r == walk[walk_index][0]:
+ blk_row, blk_split, blk_shift = walk[walk_index]
+ if scandir == _LTR:
+ for j in range(blk_split):
+ row[j] += shift
+ shift -= blk_shift
+ for j in range(blk_split, xres):
+ row[j] += shift
+ else:
+ for j in range(blk_split, xres):
+ row[j] += shift
+ shift -= blk_shift
+ for j in range(blk_split):
+ row[j] += shift
+ walk_index += 1
+ else:
+ row += shift
+
+ correction = corrected - data
+
+ # preview blocks mask: both segments write the SAME boundary row pair
+ blocks_mask = np.zeros((yres, xres), dtype=np.float64)
+ for cand, block in zip(candidates, blocks, strict=True):
+ row_before = block[0] + 1
+ split = int(cand[1])
+ mrow_base = (row_before - 1) * xres
+ if scandir == _LTR:
+ _fill_mask(blocks_mask, mrow_base, 0, split, xres)
+ _fill_mask(blocks_mask, mrow_base, split, xres - split, xres)
+ else:
+ _fill_mask(blocks_mask, mrow_base, split, xres - split, xres)
+ _fill_mask(blocks_mask, mrow_base, 0, split, xres)
+
+ return _GwydionStepBlockResult(
+ input_snapshot=data,
+ xres=xres,
+ yres=yres,
+ dy=dy,
+ threshold_param=threshold,
+ effective_threshold=effective,
+ rms_stat=rms_stat,
+ discontinuity_mask=disc_mask,
+ row_totalsteps=tuple(row_steps),
+ row_positions=tuple(positions),
+ row_scores=tuple(scores),
+ candidate_boundaries=tuple((int(c[0]), int(c[1]), float(c[2]))
+ for c in candidates),
+ retained_blocks=tuple(blocks),
+ sentinel=sentinel,
+ shift_samples_raw=tuple(raw_samples),
+ shift_samples_selected=tuple(selected_samples),
+ trim_low=trim_low,
+ trim_high=trim_high,
+ retained_count=retained_count,
+ retained_sums=tuple(retained_sums),
+ block_count=len(blocks),
+ corrected_field=corrected,
+ correction_field=correction,
+ preview_mask_discontinuity=disc_mask,
+ preview_mask_blocks=blocks_mask,
+ input_mutation_evidence=False,
+ )
+
+
+def _fill_shifts(flat: np.ndarray, xres: int, row_base: int,
+ samples: list[float], start: int, length: int) -> None:
+ """One boundary shift segment: samples[start+j] = row+1 - row."""
+ for j in range(length):
+ idx = start + j
+ samples[idx] = flat[row_base + xres + idx] - flat[row_base + idx]
+
+
+def _fill_mask(blocks_mask: np.ndarray, mrow_base: int, start: int,
+ length: int, xres: int) -> None:
+ flat = blocks_mask.ravel()
+ for j in range(length):
+ flat[mrow_base + xres + start + j] = 1.0
+ flat[mrow_base + start + j] = 1.0
diff --git a/src/spmkit/core/analysis/_gwydion_step_line_correction.py b/src/spmkit/core/analysis/_gwydion_step_line_correction.py
new file mode 100644
index 0000000..e03ac09
--- /dev/null
+++ b/src/spmkit/core/analysis/_gwydion_step_line_correction.py
@@ -0,0 +1,286 @@
+"""Production kernel for Gwydion 2.71 Step Line Correction.
+
+Independent implementation from the frozen source contract
+(modules/process/linecorrect.c lines 78-192 with
+libprocess/correct.c 1599-1671 and libprocess/filters.c 1158-1221).
+
+Reproduces the exact source operation ordering with scalar float64
+arithmetic: no NumPy reductions that could reassociate, no vectorized
+threshold decisions, mutable scratch-row run scanning, exact division
+order, IEEE division semantics for degenerate dimensions.
+
+This module must not import fixtures, oracles, generators, tests or
+Gwydion.
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass
+
+import numpy as np
+
+FloatArray = np.ndarray
+
+
+def _validated_field(value: object) -> FloatArray:
+ source = np.asarray(value)
+ if source.ndim != 2:
+ raise ValueError("Step Line Correction data must be two-dimensional")
+ if 0 in source.shape:
+ raise ValueError("Step Line Correction data must have non-empty dimensions")
+ if not np.issubdtype(source.dtype, np.number) or np.iscomplexobj(source):
+ raise TypeError("Step Line Correction data must contain real numeric values")
+ values = np.array(source, dtype=np.float64, order="C", copy=True)
+ if not np.isfinite(values).all():
+ raise ValueError("Step Line Correction data must be finite")
+ return values
+
+
+def _sequential_sum(values: FloatArray) -> float:
+ """gwy_data_field_get_sum: sequential accumulation in memory order."""
+ total = 0.0
+ for value in values.ravel():
+ total += float(value)
+ return total
+
+
+def _row_upper_median(row: FloatArray) -> float:
+ """gwy_math_median(n, array) = kth_rank(n, n/2): index n//2 order
+ statistic of the sorted row (upper median for even widths)."""
+ ordered = sorted(float(v) for v in row)
+ return ordered[len(ordered) // 2]
+
+
+def _ieee_divide(numerator: float, denominator: int) -> float:
+ """IEEE-754 division with C semantics (0/0 -> NaN, x/0 -> +-Inf)."""
+ with np.errstate(all="ignore"):
+ return float(np.float64(numerator) / np.float64(denominator))
+
+
+def _align_rows(field: FloatArray, medians: FloatArray,
+ statistic_mean: float) -> FloatArray:
+ """gwy_data_field_subtract_row_shifts (correct.c:1527-1551)."""
+ aligned = field.copy()
+ yres, xres = field.shape
+ for i in range(yres):
+ shift = float(medians[i]) - statistic_mean
+ for j in range(xres):
+ aligned[i, j] = aligned[i, j] - shift
+ return aligned
+
+
+def _repair_segment(work: FloatArray, top_index: int, scratch_row: list[float],
+ start: int, length: int, xres: int) -> None:
+ """calculate_segment_correction (linecorrect.c:78-100).
+
+ drow points at the triplet TOP row; drow[xres+j] and drow[2*xres+j]
+ are the middle and bottom rows. scratch_row is the mutable middle-row
+ scratch buffer. Accepted runs (length >= 4) write blended corrections;
+ shorter runs are zeroed.
+ """
+ top = work[top_index]
+ middle = work[top_index + 1]
+ bottom = work[top_index + 2]
+ if length >= 4:
+ segment_residual = 0.0
+ for k in range(length):
+ column = start + k
+ segment_residual += ((top[column] + bottom[column]) / 2.0
+ - middle[column])
+ segment_residual /= length
+ for k in range(length):
+ column = start + k
+ local_residual = ((top[column] + bottom[column]) / 2.0
+ - middle[column])
+ scratch_row[column] = (3.0 * segment_residual
+ + local_residual) / 4.0
+ else:
+ for k in range(length):
+ scratch_row[start + k] = 0.0
+
+
+def _detector_pass(work: FloatArray, scratch: FloatArray) -> None:
+ """line_correct_step_iter (linecorrect.c:102-157), in place.
+
+ w accumulates the mean squared row-to-row difference with
+ w = (w/(yres-1))/xres division order; every middle row is marked by
+ strict v > 3.0*w; runs of exactly equal marks are scanned on the
+ mutable scratch row; finally scratch is added to the field
+ (gwy_data_field_sum_fields, arithmetic.c:50-74).
+ """
+ yres, xres = work.shape
+ threshold = 3.0
+
+ w = 0.0
+ for i in range(yres - 1):
+ upper = work[i]
+ lower = work[i + 1]
+ for j in range(xres):
+ difference = lower[j] - upper[j]
+ w += difference * difference
+ w = _ieee_divide(_ieee_divide(w, yres - 1), xres)
+
+ scratch.fill(0.0)
+
+ for i in range(yres - 2):
+ top = work[i]
+ middle = work[i + 1]
+ bottom = work[i + 2]
+ marks = scratch[i + 1]
+ for j in range(xres):
+ centre = middle[j]
+ product = (centre - top[j]) * (centre - bottom[j])
+ if product > threshold * w:
+ if 2.0 * centre - top[j] - bottom[j] > 0.0:
+ marks[j] = 1.0
+ else:
+ marks[j] = -1.0
+
+ # mutable run scan: equality on the current scratch values, which
+ # earlier corrections may have replaced with blend floats
+ run_length = 1
+ for j in range(1, xres):
+ if marks[j] == marks[j - 1]:
+ run_length += 1
+ else:
+ if marks[j - 1]:
+ _repair_segment(work, i, marks, j - run_length,
+ run_length, xres)
+ run_length = 1
+ if marks[xres - 1]:
+ _repair_segment(work, i, marks, xres - run_length,
+ run_length, xres)
+
+ for index in range(yres * xres):
+ work.flat[index] = work.flat[index] + scratch.flat[index]
+
+
+def _conservative_denoise_5(field: FloatArray) -> None:
+ """gwy_data_field_filter_conservative(field, 5) (filters.c:1158-1221).
+
+ Numerical no-op when xres < 5 or yres < 5 (filters.c:1174-1177); no
+ GLib-style warning is emitted from the Python API. Otherwise each
+ pixel is clamped to the min/max of its clipped 5x5 neighbourhood with
+ the centre excluded.
+ """
+ yres, xres = field.shape
+ if xres < 5 or yres < 5:
+ return
+ source = field.copy()
+ for r in range(yres):
+ row_from = max(0, r - 2)
+ row_to = min(yres - 1, r + 2)
+ for c in range(xres):
+ col_from = max(0, c - 2)
+ col_to = min(xres - 1, c + 2)
+ minimum = float("inf")
+ maximum = float("-inf")
+ for ii in range(row_to - row_from + 1):
+ for jj in range(col_to - col_from + 1):
+ if r == ii + row_from and c == jj + col_from:
+ continue
+ neighbour = source[row_from + ii, col_from + jj]
+ if neighbour < minimum:
+ minimum = neighbour
+ if neighbour > maximum:
+ maximum = neighbour
+ centre = source[r, c]
+ if centre < minimum:
+ field[r, c] = minimum
+ elif centre > maximum:
+ field[r, c] = maximum
+ else:
+ field[r, c] = centre
+
+
+@dataclass(frozen=True)
+class _StepLineCorrectionTrace:
+ """All intermediate observables of the source operation (trace path).
+
+ The trace uses the same numerical engine as the public path.
+ """
+
+ input_snapshot: FloatArray
+ original_global_mean: float
+ row_statistics: FloatArray
+ zero_leveled_shifts: FloatArray
+ field_after_row_alignment: FloatArray
+ scratch_pass1: FloatArray
+ field_after_pass1: FloatArray
+ scratch_pass2: FloatArray
+ field_after_pass2: FloatArray
+ field_after_conservative_filter: FloatArray
+ mean_restoration_offset: float
+ final_corrected: FloatArray
+ final_minus_input: FloatArray
+ input_minus_final: FloatArray
+
+
+def _gwydion_step_line_correction_result(
+ data: object,
+ *,
+ trace: bool = False,
+) -> FloatArray | _StepLineCorrectionTrace:
+ """Run the Step Line Correction engine.
+
+ With ``trace=False`` (the public path) returns the corrected field
+ only. With ``trace=True`` returns the complete private observable
+ set; both paths execute the identical numerical engine.
+ """
+ field = _validated_field(data)
+ yres, xres = field.shape
+ n = yres * xres
+ input_snapshot = field.copy()
+
+ # linecorrect.c:177 — original global mean
+ original_mean = _sequential_sum(field) / n
+
+ # linecorrect.c:178 — row statistics and zero-levelled shifts
+ statistics = np.array([_row_upper_median(field[i]) for i in range(yres)],
+ dtype=np.float64, order="C")
+ statistic_total = 0.0
+ for value in statistics:
+ statistic_total += float(value)
+ statistic_mean = statistic_total / yres
+ shifts = statistics - statistic_mean
+ aligned = _align_rows(field, statistics, statistic_mean)
+
+ # linecorrect.c:182-186 — exactly two detector passes
+ scratch = np.zeros((yres, xres), dtype=np.float64, order="C")
+ _detector_pass(aligned, scratch)
+ scratch_pass1 = scratch.copy()
+ field_after_pass1 = aligned.copy()
+ _detector_pass(aligned, scratch)
+ scratch_pass2 = scratch.copy()
+ field_after_pass2 = aligned.copy()
+
+ # linecorrect.c:188 — size-5 conservative filter
+ _conservative_denoise_5(aligned)
+ field_after_filter = aligned.copy()
+
+ # linecorrect.c:189 — mean-restoration offset, then add
+ offset = original_mean - (_sequential_sum(aligned) / n)
+ for index in range(n):
+ aligned.flat[index] = aligned.flat[index] + offset
+ final_corrected = aligned.copy()
+
+ if not trace:
+ return final_corrected
+
+ return _StepLineCorrectionTrace(
+ input_snapshot=input_snapshot,
+ original_global_mean=original_mean,
+ row_statistics=statistics,
+ zero_leveled_shifts=shifts,
+ field_after_row_alignment=_align_rows(field, statistics,
+ statistic_mean),
+ scratch_pass1=scratch_pass1,
+ field_after_pass1=field_after_pass1,
+ scratch_pass2=scratch_pass2,
+ field_after_pass2=field_after_pass2,
+ field_after_conservative_filter=field_after_filter,
+ mean_restoration_offset=offset,
+ final_corrected=final_corrected,
+ final_minus_input=final_corrected - input_snapshot,
+ input_minus_final=input_snapshot - final_corrected,
+ )
diff --git a/src/spmkit/core/analysis/contact_mechanics.py b/src/spmkit/core/analysis/contact_mechanics.py
new file mode 100644
index 0000000..bae9751
--- /dev/null
+++ b/src/spmkit/core/analysis/contact_mechanics.py
@@ -0,0 +1,352 @@
+"""FS-F2 contact-mechanics model engine and model comparison.
+
+Five frozen contact-mechanics models with a shared deterministic least-squares
+engine (SciPy curve_fit, already a required dependency), immutable results and
+typed failures. No contact or baseline is inferred here: the caller provides
+the prepared curve, indentation and fit window.
+"""
+
+from __future__ import annotations
+
+import math
+from dataclasses import dataclass, field
+
+import numpy as np
+from scipy.optimize import curve_fit
+
+from spmkit.core.analysis.force_foundation import ForcePreparationResult
+from spmkit.core.analysis.force_indentation import (
+ FitWindowResult,
+ IndentationResult,
+)
+from spmkit.core.analysis.force_mechanics_errors import (
+ CURVE_NOT_FIT_ELIGIBLE,
+ INVALID_ADHESION_PARAMETER,
+ INVALID_ANGLE,
+ INVALID_POISSON_RATIO,
+ INVALID_RADIUS,
+ NONFINITE_INPUT,
+ OPTIMIZATION_FAILED,
+ ForceMechanicsError,
+)
+
+MODELS = ("hertz_sphere", "sneddon_cone", "flat_punch", "dmt", "jkr")
+
+
+@dataclass(frozen=True)
+class ContactMechanicsFitResult:
+ """Deterministic contact-mechanics fit of one model."""
+
+ model: str
+ success: bool
+ parameters: dict[str, float]
+ parameter_units: dict[str, str]
+ covariance: dict[str, float] | None
+ residuals: np.ndarray
+ predicted_force: np.ndarray
+ included_indices: np.ndarray
+ objective: float
+ dof: int
+ rmse: float
+ aic: float
+ aicc: float
+ bic: float
+ failure_reason: str | None = None
+ diagnostics: dict[str, object] = field(default_factory=dict)
+ provenance: dict[str, object] = field(default_factory=dict)
+
+
+@dataclass(frozen=True)
+class ModelComparisonResult:
+ """Model-relative comparison (AIC/AICc/BIC) over identical data."""
+
+ fits: tuple[ContactMechanicsFitResult, ...]
+ delta_aicc: dict[str, float]
+ weights: dict[str, float]
+ recommended_model: str | None
+ ambiguous: bool
+ n_compared: int
+ warnings: tuple[str, ...] = ()
+ provenance: dict[str, object] = field(default_factory=dict)
+
+
+def _check_geometry(*, tip_radius: float | None, half_angle: float | None,
+ poisson: float, work_of_adhesion: float | None,
+ punch_radius: float | None, model: str) -> None:
+ if not (0.0 < poisson < 0.5):
+ raise ForceMechanicsError(INVALID_POISSON_RATIO,
+ f"poisson {poisson} outside (0, 0.5)")
+ if tip_radius is not None and tip_radius <= 0.0:
+ raise ForceMechanicsError(INVALID_RADIUS, "tip radius must be positive")
+ if punch_radius is not None and punch_radius <= 0.0:
+ raise ForceMechanicsError(INVALID_RADIUS, "punch radius must be positive")
+ if half_angle is not None and not (0.0 < half_angle < math.pi / 2.0):
+ raise ForceMechanicsError(INVALID_ANGLE, "half-angle must be in (0, pi/2)")
+ if work_of_adhesion is not None and work_of_adhesion < 0.0:
+ raise ForceMechanicsError(INVALID_ADHESION_PARAMETER,
+ "work of adhesion must be non-negative")
+
+
+def _reduced_modulus(young_modulus: float, poisson: float) -> float:
+ return young_modulus / (1.0 - poisson**2)
+
+
+def forward_model(model: str, delta: np.ndarray, params: dict[str, float]) -> np.ndarray:
+ """Forward force for one model (frozen equations, SI units)."""
+ delta = np.asarray(delta, dtype=np.float64)
+ est = _reduced_modulus(params["E"], params["poisson"])
+ if model == "hertz_sphere":
+ return (4.0 / 3.0) * est * math.sqrt(params["R"]) * delta ** 1.5
+ if model == "sneddon_cone":
+ return (2.0 * math.tan(params["alpha"]) / math.pi) * est * delta ** 2.0
+ if model == "flat_punch":
+ return 2.0 * est * params["R"] * delta
+ if model == "dmt":
+ return (4.0 / 3.0) * est * math.sqrt(params["R"]) * delta ** 1.5 - params["F_adh"]
+ if model == "jkr":
+ # parametric loading branch (delta increasing). The contact radius
+ # a parametrizes both delta and force; the branch is monotone for
+ # a >= a0 with a0 = (2*pi*w*R^2/E)^(1/3) the zero-load radius, so
+ # the parametric range is derived from the requested delta range
+ # and no a_max parameter is required.
+ r = params["R"]
+ w = params["w"]
+ dmax = float(np.max(delta)) if delta.size else 0.0
+ if dmax <= 0.0:
+ return np.zeros_like(delta)
+ c = math.sqrt(2.0 * math.pi * w / est)
+ a0 = (2.0 * math.pi * w * r**2 / est) ** (1.0 / 3.0) if w > 0.0 else 0.0
+ a_lo = max(a0, 1e-12)
+ a_hi = a_lo
+ while a_hi**2 / r - c * math.sqrt(a_hi) < dmax:
+ a_hi *= 2.0
+ a = np.linspace(a_lo, a_hi, max(2048, delta.size * 4))
+ d = a**2 / r - c * np.sqrt(a)
+ f = 4.0 * est * a**3 / (3.0 * r) - np.sqrt(
+ 8.0 * math.pi * w * est * a**3)
+ return np.interp(delta, d, f, left=0.0, right=float(f[-1]))
+ raise ValueError(f"unknown model {model!r}")
+
+
+def _free_parameter_names(model: str) -> list[str]:
+ if model in ("hertz_sphere", "flat_punch"):
+ return ["E"]
+ if model == "sneddon_cone":
+ return ["E"]
+ if model == "dmt":
+ return ["E", "F_adh"]
+ return ["E", "w"]
+
+
+def _fit_one(model: str, delta: np.ndarray, force: np.ndarray,
+ start: int, end: int, fixed: dict[str, float],
+ initial: dict[str, float]) -> ContactMechanicsFitResult:
+ d = delta[start : end + 1]
+ f = force[start : end + 1]
+ if not np.isfinite(d).all() or not np.isfinite(f).all():
+ raise ForceMechanicsError(NONFINITE_INPUT, "non-finite fit inputs")
+ n = d.size
+ if n < 5:
+ raise ForceMechanicsError(
+ OPTIMIZATION_FAILED, "too few points for a mechanical fit")
+ free = _free_parameter_names(model)
+
+ def _predict(delta_v: np.ndarray, *args: float) -> np.ndarray:
+ params = dict(fixed)
+ for name, val in zip(free, args, strict=False):
+ params[name] = float(val)
+ return forward_model(model, delta_v, params)
+
+ p0 = [initial.get(name, 1e9 if name == "E" else 1e-9) for name in free]
+ try:
+ popt, pcov = curve_fit(_predict, d, f, p0=p0, maxfev=20000)
+ except Exception as exc: # noqa: BLE001 - typed wrapper
+ raise ForceMechanicsError(OPTIMIZATION_FAILED, f"optimizer failed: {exc}") from exc
+ params = dict(fixed)
+ for name, val in zip(free, popt, strict=False):
+ params[name] = float(val)
+ predicted = _predict(d, *popt)
+ residuals = f - predicted
+ dof = n - len(free)
+ rmse = float(np.sqrt(np.mean(residuals**2)))
+ objective = float(np.sum(residuals**2))
+ sse = objective
+ aic = n * math.log(sse / n + 1e-300) + 2 * len(free)
+ aicc = aic + (2 * len(free) * (len(free) + 1)) / max(1, n - len(free) - 1)
+ bic = n * math.log(sse / n + 1e-300) + len(free) * math.log(n)
+ units = {
+ "E": "Pa", "F_adh": "N", "w": "J/m^2", "R": "m", "alpha": "rad",
+ "poisson": "dimensionless",
+ }
+ cov = {}
+ if pcov is not None and np.all(np.isfinite(pcov)):
+ for i, name in enumerate(free):
+ for j, name2 in enumerate(free):
+ cov[f"{name}__{name2}"] = float(pcov[i, j])
+ return ContactMechanicsFitResult(
+ model=model, success=True, parameters=params, parameter_units=units,
+ covariance=cov if cov else None, residuals=residuals,
+ predicted_force=predicted, included_indices=np.arange(start, end + 1),
+ objective=objective, dof=dof, rmse=rmse, aic=aic, aicc=aicc, bic=bic,
+ diagnostics={"n_points": n, "free_parameters": free},
+ provenance={"fixed": fixed, "initial": initial, "window": (start, end)},
+ )
+
+
+def _extract_fit_inputs(prepared: ForcePreparationResult,
+ indentation: IndentationResult,
+ window: FitWindowResult) -> tuple[np.ndarray, np.ndarray, int, int]:
+ approach = prepared.curve.extend
+ if approach is None or approach.force is None:
+ raise ForceMechanicsError(CURVE_NOT_FIT_ELIGIBLE, "no calibrated approach")
+ f = np.asarray(approach.force, dtype=np.float64)
+ return (
+ np.asarray(indentation.indentation, dtype=np.float64),
+ f,
+ window.start_index,
+ window.end_index,
+ )
+
+
+def fit_hertz_sphere(
+ prepared: ForcePreparationResult,
+ indentation: IndentationResult,
+ window: FitWindowResult,
+ *,
+ tip_radius: float,
+ poisson: float = 0.3,
+ E_initial: float = 1e9,
+) -> ContactMechanicsFitResult:
+ _check_geometry(tip_radius=tip_radius, half_angle=None, poisson=poisson,
+ work_of_adhesion=None, punch_radius=None, model="hertz_sphere")
+ delta, f, s, e = _extract_fit_inputs(prepared, indentation, window)
+ return _fit_one("hertz_sphere", delta, f, s, e,
+ {"R": tip_radius, "poisson": poisson}, {"E": E_initial})
+
+
+def fit_sneddon_cone(
+ prepared: ForcePreparationResult,
+ indentation: IndentationResult,
+ window: FitWindowResult,
+ *,
+ half_angle: float,
+ poisson: float = 0.3,
+ E_initial: float = 1e9,
+) -> ContactMechanicsFitResult:
+ _check_geometry(tip_radius=None, half_angle=half_angle, poisson=poisson,
+ work_of_adhesion=None, punch_radius=None, model="sneddon_cone")
+ delta, f, s, e = _extract_fit_inputs(prepared, indentation, window)
+ return _fit_one("sneddon_cone", delta, f, s, e,
+ {"alpha": half_angle, "poisson": poisson}, {"E": E_initial})
+
+
+def fit_flat_punch(
+ prepared: ForcePreparationResult,
+ indentation: IndentationResult,
+ window: FitWindowResult,
+ *,
+ punch_radius: float,
+ poisson: float = 0.3,
+ E_initial: float = 1e9,
+) -> ContactMechanicsFitResult:
+ _check_geometry(tip_radius=None, half_angle=None, poisson=poisson,
+ work_of_adhesion=None, punch_radius=punch_radius,
+ model="flat_punch")
+ delta, f, s, e = _extract_fit_inputs(prepared, indentation, window)
+ return _fit_one("flat_punch", delta, f, s, e,
+ {"R": punch_radius, "poisson": poisson}, {"E": E_initial})
+
+
+def fit_dmt(
+ prepared: ForcePreparationResult,
+ indentation: IndentationResult,
+ window: FitWindowResult,
+ *,
+ tip_radius: float,
+ poisson: float = 0.3,
+ E_initial: float = 1e9,
+ F_adh_initial: float = 1e-9,
+) -> ContactMechanicsFitResult:
+ _check_geometry(tip_radius=tip_radius, half_angle=None, poisson=poisson,
+ work_of_adhesion=None, punch_radius=None, model="dmt")
+ if F_adh_initial < 0.0:
+ raise ForceMechanicsError(INVALID_ADHESION_PARAMETER,
+ "adhesion initial value must be non-negative")
+ delta, f, s, e = _extract_fit_inputs(prepared, indentation, window)
+ return _fit_one("dmt", delta, f, s, e,
+ {"R": tip_radius, "poisson": poisson},
+ {"E": E_initial, "F_adh": F_adh_initial})
+
+
+def fit_jkr(
+ prepared: ForcePreparationResult,
+ indentation: IndentationResult,
+ window: FitWindowResult,
+ *,
+ tip_radius: float,
+ poisson: float = 0.3,
+ E_initial: float = 1e9,
+ w_initial: float = 1e-3,
+) -> ContactMechanicsFitResult:
+ _check_geometry(tip_radius=tip_radius, half_angle=None, poisson=poisson,
+ work_of_adhesion=w_initial, punch_radius=None, model="jkr")
+ delta, f, s, e = _extract_fit_inputs(prepared, indentation, window)
+ return _fit_one("jkr", delta, f, s, e,
+ {"R": tip_radius, "poisson": poisson},
+ {"E": E_initial, "w": w_initial})
+
+
+def compare_contact_models(
+ prepared: ForcePreparationResult,
+ indentation: IndentationResult,
+ window: FitWindowResult,
+ *,
+ models: tuple[str, ...] = ("hertz_sphere", "sneddon_cone", "flat_punch", "dmt"),
+ tip_radius: float,
+ half_angle: float = math.radians(20.0),
+ punch_radius: float | None = None,
+ poisson: float = 0.3,
+) -> ModelComparisonResult:
+ """Compare models over the IDENTICAL data subset; no physical-truth claim."""
+ fits: list[ContactMechanicsFitResult] = []
+ warnings: list[str] = []
+ for model in models:
+ if model not in MODELS:
+ raise ValueError(f"unknown model {model!r}")
+ try:
+ if model == "hertz_sphere":
+ fits.append(fit_hertz_sphere(prepared, indentation, window,
+ tip_radius=tip_radius, poisson=poisson))
+ elif model == "sneddon_cone":
+ fits.append(fit_sneddon_cone(prepared, indentation, window,
+ half_angle=half_angle, poisson=poisson))
+ elif model == "flat_punch":
+ fits.append(fit_flat_punch(prepared, indentation, window,
+ punch_radius=punch_radius or tip_radius,
+ poisson=poisson))
+ elif model == "dmt":
+ fits.append(fit_dmt(prepared, indentation, window,
+ tip_radius=tip_radius, poisson=poisson))
+ else: # jkr
+ fits.append(fit_jkr(prepared, indentation, window,
+ tip_radius=tip_radius, poisson=poisson))
+ except ForceMechanicsError as exc:
+ warnings.append(f"{model}: {exc.code}")
+ if not fits:
+ raise ForceMechanicsError(OPTIMIZATION_FAILED, "no model fit succeeded")
+ delta_aicc = {fit.model: fit.aicc - min(f.aicc for f in fits) for fit in fits}
+ total_w = sum(math.exp(-0.5 * d) for d in delta_aicc.values())
+ weights = {m: math.exp(-0.5 * delta_aicc[m]) / total_w for m in delta_aicc}
+ best = min(fits, key=lambda f: f.aicc)
+ # ambiguous when the runner-up retains considerable support
+ # (Delta AICc < 4, Burnham & Anderson); nested near-ties land just
+ # above 2 AICc units, so 4 is the honest "cannot distinguish" boundary
+ ambiguous = (sorted(f.aicc for f in fits)[1] - best.aicc < 4.0
+ if len(fits) > 1 else False)
+ return ModelComparisonResult(
+ fits=tuple(fits), delta_aicc=delta_aicc, weights=weights,
+ recommended_model=best.model if not ambiguous else None,
+ ambiguous=ambiguous, n_compared=len(fits), warnings=tuple(warnings),
+ provenance={"window": (window.start_index, window.end_index),
+ "models": list(models), "criterion": "aicc"},
+ )
diff --git a/src/spmkit/core/analysis/derivatives.py b/src/spmkit/core/analysis/derivatives.py
new file mode 100644
index 0000000..0a536a6
--- /dev/null
+++ b/src/spmkit/core/analysis/derivatives.py
@@ -0,0 +1,158 @@
+"""gwyddion 2.71 derivative filters and native gradient composite.
+
+Public surface of the first A2 derivative-filter production batch:
+
+ * gwyddion_sobel_x / gwyddion_sobel_y / gwyddion_prewitt_x /
+ gwyddion_prewitt_y: exact component filters, CROSS_VALIDATED within the
+ frozen canonical source-included profile
+ (COMPILED_gwyddion_2_71_SOURCE_INCLUDED_DERIVATIVE_KERNEL_PROFILE);
+ * gwyddion_gradient_magnitude(gx, gy): hypot composition, CROSS_VALIDATED
+ only within the frozen x86-64/glibc hypot platform profile;
+ * gradient_direction(gx, gy): native SPMKit analytical composite
+ atan2(gy, gx), NUMERICALLY_VERIFIED, not direct Gwydion parity.
+
+All operations accept finite two-dimensional SPMChannel inputs only, reject
+NaN/Inf/empty/complex data, never mutate inputs, return independently owned
+output storage, preserve shape/calibration/direction/metadata, and expose no
+public border, mask or ROI/selection parameters.
+"""
+
+from __future__ import annotations
+
+import numpy as np
+
+from spmkit.core.analysis._gwyddion_derivative_filters import (
+ ORIENTATION_HORIZONTAL,
+ ORIENTATION_VERTICAL,
+ _validate_component_pair,
+ _validated_field,
+ gradient_direction_fields,
+ gradient_magnitude_fields,
+ prewitt_component,
+ sobel_component,
+)
+from spmkit.core.models.spmdata import SPMChannel
+
+__all__ = [
+ "gwyddion_sobel_x",
+ "gwyddion_sobel_y",
+ "gwyddion_prewitt_x",
+ "gwyddion_prewitt_y",
+ "gwyddion_gradient_magnitude",
+ "gradient_direction",
+]
+
+
+def _channel_result(channel: SPMChannel, corrected: np.ndarray) -> SPMChannel:
+ if not isinstance(channel, SPMChannel):
+ raise TypeError("derivative filter requires an SPMChannel")
+ return channel.with_data(corrected)
+
+
+def _validate_channel(channel: SPMChannel, *, label: str) -> np.ndarray:
+ if not isinstance(channel, SPMChannel):
+ raise TypeError(f"{label} requires an SPMChannel")
+ return _validated_field(channel.data, label=label)
+
+
+def _validate_component_channels(
+ gx: SPMChannel, gy: SPMChannel, *, label: str
+) -> tuple[np.ndarray, np.ndarray]:
+ """Validate the component-channel pair (shape/calibration/units/context)."""
+ if not isinstance(gx, SPMChannel) or not isinstance(gy, SPMChannel):
+ raise TypeError(f"{label} requires two SPMChannel components")
+ x = _validated_field(gx.data, label=f"{label} gx")
+ y = _validated_field(gy.data, label=f"{label} gy")
+ _validate_component_pair(x, y, label=label)
+ if gx.x_range != gy.x_range or gx.y_range != gy.y_range:
+ raise ValueError(f"{label} component channels must share x_range/y_range")
+ if gx.unit != gy.unit:
+ raise ValueError(f"{label} component channels must have compatible units")
+ if gx.direction != gy.direction:
+ raise ValueError(f"{label} component channels must share scan direction")
+ return x, y
+
+
+def gwyddion_sobel_x(channel: SPMChannel) -> SPMChannel:
+ """Sobel X (horizontal) derivative with frozen Gwydion 2.71 CLIPPED semantics.
+
+ Kernel rows {0.25, 0, -0.25; 0.5, 0, -0.5; 0.25, 0, -0.25}; increasing
+ rightward data yields negative responses. The z-unit of the input is
+ preserved (dimensionless kernel). The input channel is never mutated.
+ """
+ field = _validate_channel(channel, label="gwyddion_sobel_x")
+ result = sobel_component(field, ORIENTATION_HORIZONTAL)
+ return _channel_result(channel, result)
+
+
+def gwyddion_sobel_y(channel: SPMChannel) -> SPMChannel:
+ """Sobel Y (vertical) derivative with frozen Gwydion 2.71 CLIPPED semantics.
+
+ Kernel rows {0.25, 0.5, 0.25; 0, 0, 0; -0.25, -0.5, -0.25}; increasing
+ downward data yields negative responses. The z-unit of the input is
+ preserved (dimensionless kernel). The input channel is never mutated.
+ """
+ field = _validate_channel(channel, label="gwyddion_sobel_y")
+ result = sobel_component(field, ORIENTATION_VERTICAL)
+ return _channel_result(channel, result)
+
+
+def gwyddion_prewitt_x(channel: SPMChannel) -> SPMChannel:
+ """Prewitt X (horizontal) derivative with frozen Gwydion 2.71 1/3 coefficients.
+
+ Same orientation and CLIPPED semantics as Sobel X; ramp response
+ identical to Sobel on planar ramps, 1/3 coefficients on impulses.
+ The z-unit of the input is preserved. The input channel is never
+ mutated.
+ """
+ field = _validate_channel(channel, label="gwyddion_prewitt_x")
+ result = prewitt_component(field, ORIENTATION_HORIZONTAL)
+ return _channel_result(channel, result)
+
+
+def gwyddion_prewitt_y(channel: SPMChannel) -> SPMChannel:
+ """Prewitt Y (vertical) derivative with frozen Gwydion 2.71 1/3 coefficients.
+
+ Same orientation and CLIPPED semantics as Sobel Y. The z-unit of the
+ input is preserved. The input channel is never mutated.
+ """
+ field = _validate_channel(channel, label="gwyddion_prewitt_y")
+ result = prewitt_component(field, ORIENTATION_VERTICAL)
+ return _channel_result(channel, result)
+
+
+def gwyddion_gradient_magnitude(gx: SPMChannel, gy: SPMChannel) -> SPMChannel:
+ """Gradient magnitude hypot(gx, gy) over explicit component fields.
+
+ Reproduces the frozen the hypot-of-fields orchestration orchestration
+ (r[i] = hypot(p[i], q[i])). Bitwise identity with the compiled glibc
+ hypot@GLIBC_2.35 profile is claimed only within the frozen x86-64/glibc
+ platform profile; no cross-libc or cross-architecture guarantee. The
+ result z-unit equals the component unit. Components are never mutated.
+ """
+ x, y = _validate_component_channels(gx, gy, label="gwyddion_gradient_magnitude")
+ result = gradient_magnitude_fields(x, y)
+ return _channel_result(gx, result)
+
+
+def gradient_direction(gx: SPMChannel, gy: SPMChannel) -> SPMChannel:
+ """Native gradient direction atan2(gy, gx) in radians.
+
+ Range (-pi, pi]; exact argument order; C99 signed-zero axes; zero
+ vector -> +0.0; no normalization. This is a NATIVE_SPMKIT_ANALYTICAL_
+ COMPOSITE (NUMERICALLY_VERIFIED), not direct Gwydion parity. The result
+ unit is "rad". Components are never mutated.
+ """
+ x, y = _validate_component_channels(gx, gy, label="gradient_direction")
+ result = gradient_direction_fields(x, y)
+ direction_channel = _channel_result(gx, result)
+ return SPMChannel(
+ name=direction_channel.name,
+ data=direction_channel.data,
+ unit="rad",
+ x_range=direction_channel.x_range,
+ y_range=direction_channel.y_range,
+ direction=direction_channel.direction,
+ group=direction_channel.group,
+ metadata=dict(direction_channel.metadata),
+ )
diff --git a/src/spmkit/core/analysis/filters.py b/src/spmkit/core/analysis/filters.py
new file mode 100644
index 0000000..8364e9a
--- /dev/null
+++ b/src/spmkit/core/analysis/filters.py
@@ -0,0 +1,93 @@
+"""Public Gwydion 2.71 neighborhood-filter operations.
+
+Implements the three A2 neighborhood-filter public APIs:
+
+ * gwyddion_rank_filter
+ * gwyddion_median_filter
+ * gwyddion_gaussian_filter
+
+Each operation applies the frozen compiled-profile kernel
+(COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION)
+to one finite two-dimensional SPMChannel and returns a new context-
+preserving SPMChannel. The input channel and data are never mutated.
+
+Source/version attribution (behavioral): Gwydion 2.71
+modules/process/rank-filter.c, modules/tools/filter.c,
+libprocess/filters-minmax.c, libprocess/elliptic.c,
+libprocess/filters-convdeconv.c.
+"""
+
+from __future__ import annotations
+
+import numpy as np
+
+from spmkit.core.analysis._gwyddion_neighborhood_filters import (
+ _gwydion_gaussian_filter,
+ _gwydion_median_filter,
+ _gwydion_rank_filter,
+)
+from spmkit.core.models.spmdata import SPMChannel
+
+
+def _channel_result(channel: SPMChannel, corrected: np.ndarray) -> SPMChannel:
+ if not isinstance(channel, SPMChannel):
+ raise TypeError("Gwydion neighborhood filter requires an SPMChannel")
+ return channel.with_data(corrected)
+
+
+def gwyddion_rank_filter(
+ channel: SPMChannel,
+ *,
+ radius: int = 20,
+ percentile: float = 0.75,
+) -> SPMChannel:
+ """Apply the Gwydion 2.71 Rank Filter (primary percentile only).
+
+ ``radius`` is the pixel radius in ``1..1024``; the footprint is the
+ ellipse inscribed in a ``2*radius+1`` square. ``percentile`` in
+ ``0..1`` selects the rank ``GWY_ROUND(percentile*(n-1))`` of the
+ neighborhood values, where ``n`` is the active footprint count;
+ percentile 0 is the local minimum and percentile 1 the local maximum.
+ Borders use nearest-constant EXTEND extension. The result is a new
+ context-preserving ``SPMChannel``; the input is never mutated.
+ """
+ result = _gwydion_rank_filter(
+ channel.data, radius=radius, percentile=percentile)
+ return _channel_result(channel, result.result)
+
+
+def gwyddion_median_filter(
+ channel: SPMChannel,
+ *,
+ size: int = 5,
+) -> SPMChannel:
+ """Apply the Gwydion 2.71 disc Median Filter.
+
+ ``size`` is the footprint SIDE in ``2..31`` (not a radius); even sizes
+ are valid. The footprint is the ellipse inscribed in the ``size x
+ size`` square and the median is the upper median (rank ``n//2``).
+ Borders use nearest-constant EXTEND extension. The result is a new
+ context-preserving ``SPMChannel``; the input is never mutated.
+ """
+ result = _gwydion_median_filter(channel.data, size=size)
+ return _channel_result(channel, result.result)
+
+
+def gwyddion_gaussian_filter(
+ channel: SPMChannel,
+ *,
+ sigma: float = 5.0,
+) -> SPMChannel:
+ """Apply the Gwydion 2.71 Gaussian Filter.
+
+ ``sigma`` is in pixels and must be in ``0.01..40.0`` (sigma=0 is
+ library-domain evidence and is rejected publicly). The separable
+ kernel resolution is ``2*ceil(5*sigma)+1`` capped at
+ ``3*min(xres, yres)`` and forced odd; borders use mirror extension.
+ Kernel normalization follows the source sequential summation and is
+ not forced to exactly 1.0, so constant-field drift at the
+ normalization rounding level (~1e-15) is preserved. The result is a
+ new context-preserving ``SPMChannel``; the input is never mutated.
+ """
+ result = _gwydion_gaussian_filter(channel.data, sigma=sigma, public=True)
+ return _channel_result(channel, result.result)
diff --git a/src/spmkit/core/analysis/force_contact.py b/src/spmkit/core/analysis/force_contact.py
new file mode 100644
index 0000000..02bcbd7
--- /dev/null
+++ b/src/spmkit/core/analysis/force_contact.py
@@ -0,0 +1,379 @@
+"""Contact-point estimation foundation (FS-F1).
+
+Four public estimators over the approach segment of a calibrated curve:
+
+ * threshold: baseline mean + k*sigma crossing with persistence;
+ * ratio of variances (Gavara 2016): variance-after/variance-before;
+ * piecewise: value-continuous baseline/contact polynomial fit;
+ * ensemble: robust combination with explicit disagreement and optional
+ deterministic bootstrap.
+
+All estimators return typed candidates; failures are never hidden.
+"""
+
+from __future__ import annotations
+
+import warnings
+from dataclasses import dataclass, field
+
+import numpy as np
+
+from spmkit.core.analysis.force_foundation_errors import (
+ BASELINE_TOO_SHORT,
+ CONTACT_METHOD_DISAGREEMENT,
+ CONTACT_NOT_FOUND,
+ MISSING_CALIBRATION,
+ MISSING_RETRACT,
+ ForceFoundationError,
+ require_finite,
+)
+from spmkit.core.models import ForceCurve
+
+#: Contact-search lower bound: first 10% of approach samples.
+SEARCH_START_FRACTION = 0.10
+SEARCH_END_FRACTION = 0.90
+THRESHOLD_PERSISTENCE = 3
+ROV_DEFAULT_WINDOW = 20
+ENSEMBLE_SEED = 0
+BOOTSTRAP_SAMPLES_DEFAULT = 200
+
+
+@dataclass(frozen=True)
+class ContactPointCandidate:
+ """One contact estimate from one method."""
+
+ method: str
+ index: int
+ coordinate: float
+ score: float
+ valid: bool
+ failure_reason: str | None = None
+ diagnostics: dict[str, object] = field(default_factory=dict)
+
+
+@dataclass(frozen=True)
+class ContactPointResult:
+ """Ensemble contact result with explicit disagreement."""
+
+ selected: ContactPointCandidate
+ candidates: tuple[ContactPointCandidate, ...]
+ method_agreement: int
+ spread_samples: int
+ spread_coordinate: float
+ bootstrap_interval: tuple[float, float] | None
+ warnings: tuple[str, ...] = ()
+
+
+def _approach_data(curve: ForceCurve, label: str) -> tuple[np.ndarray, np.ndarray]:
+ approach = curve.extend or (curve.segments[0] if curve.segments else None)
+ if approach is None:
+ raise ForceFoundationError(MISSING_RETRACT, f"{label}: no approach segment")
+ if approach.force is None:
+ raise ForceFoundationError(
+ MISSING_CALIBRATION, f"{label}: approach segment is not calibrated"
+ )
+ force = require_finite(np.asarray(approach.force, dtype=np.float64), label="force")
+ z = require_finite(np.asarray(approach.raw_height, dtype=np.float64), label="height")
+ if force.size != z.size:
+ raise ForceFoundationError(CONTACT_NOT_FOUND, "height/force length mismatch")
+ return z, force
+
+
+def contact_point_threshold(
+ curve: ForceCurve,
+ *,
+ threshold_sigma: float = 5.0,
+) -> ContactPointCandidate:
+ """Baseline-relative threshold contact (first persistent crossing).
+
+ Baseline mean/scale come from the first 10% of the approach. The
+ crossing must persist for ``THRESHOLD_PERSISTENCE`` consecutive samples.
+ """
+ z, force = _approach_data(curve, "contact_point_threshold")
+ if threshold_sigma <= 0.0:
+ raise ValueError("threshold_sigma must be positive")
+ n_base = max(4, int(round(z.size * SEARCH_START_FRACTION)))
+ if z.size < 12 or n_base >= z.size - 2:
+ raise ForceFoundationError(BASELINE_TOO_SHORT, "baseline region too short")
+ base = force[:n_base]
+ mean = float(np.mean(base))
+ scale = float(np.std(base))
+ if scale <= 0.0:
+ # relative epsilon so a noiseless baseline still yields a finite
+ # threshold (SI-scale safe; never an absolute 1.0)
+ scale = 1e-15 * max(1.0, float(np.max(np.abs(force))))
+ level = mean + threshold_sigma * scale
+ above = force > level
+ run = 0
+ for i in range(n_base, z.size):
+ run = run + 1 if above[i] else 0
+ if run >= THRESHOLD_PERSISTENCE:
+ idx = i - THRESHOLD_PERSISTENCE + 1
+ return ContactPointCandidate(
+ method="threshold",
+ index=idx,
+ coordinate=float(z[idx]),
+ score=float((force[idx] - mean) / scale),
+ valid=True,
+ diagnostics={
+ "baseline_mean": mean,
+ "baseline_scale": scale,
+ "level": level,
+ "persistence": THRESHOLD_PERSISTENCE,
+ },
+ )
+ return ContactPointCandidate(
+ method="threshold",
+ index=-1,
+ coordinate=float(z[-1]),
+ score=0.0,
+ valid=False,
+ failure_reason=CONTACT_NOT_FOUND,
+ diagnostics={"baseline_mean": mean, "baseline_scale": scale, "level": level},
+ )
+
+
+def contact_point_ratio_of_variances(
+ curve: ForceCurve,
+ *,
+ window: int = ROV_DEFAULT_WINDOW,
+) -> ContactPointCandidate:
+ """Gavara ratio-of-variances contact (variance after / before)."""
+ z, force = _approach_data(curve, "contact_point_ratio_of_variances")
+ if window < 3:
+ raise ValueError("window must be >= 3")
+ n = force.size
+ if n < 2 * window + 1:
+ raise ForceFoundationError(CONTACT_NOT_FOUND, "curve too short for ROV window")
+ eps = 1e-12 * float(np.max(force**2)) + 1e-300
+ best_i, best_r = -1, -1.0
+ # first index with maximal ratio (earliest tie)
+ for i in range(window, n - window):
+ var_before = float(np.var(force[i - window : i]))
+ var_after = float(np.var(force[i : i + window]))
+ r = var_after / (var_before + eps)
+ if r > best_r:
+ best_r, best_i = r, i
+ if best_i < 0 or best_r < 2.0:
+ # a genuine variance jump requires the after/before ratio to at
+ # least double; flat curves yield ratios near 1
+ return ContactPointCandidate(
+ method="ratio_of_variances",
+ index=-1,
+ coordinate=float(z[-1]),
+ score=best_r,
+ valid=False,
+ failure_reason=CONTACT_NOT_FOUND,
+ diagnostics={"window": window, "best_ratio": best_r},
+ )
+ return ContactPointCandidate(
+ method="ratio_of_variances",
+ index=best_i,
+ coordinate=float(z[best_i]),
+ score=best_r,
+ valid=True,
+ diagnostics={"window": window},
+ )
+
+
+def _piecewise_residual(
+ z: np.ndarray, force: np.ndarray, split: int, baseline_order: int, contact_order: int
+) -> float:
+ """Value-continuous piecewise fit residual for a candidate split."""
+ n = z.size
+ if split < 2 or n - split < 3:
+ return float("inf")
+ xb = z[:split] - float(z[split])
+ xc = z[split:] - float(z[split])
+ b_deg = min(baseline_order, split - 1)
+ c_deg = min(contact_order, n - split - 1)
+ if float(np.ptp(xb)) <= 0.0 or float(np.ptp(xc)) <= 0.0:
+ # constant-coordinate window (e.g. a flat hold): the polynomial
+ # design is rank deficient; the candidate is invalid
+ return float("inf")
+ try:
+ with warnings.catch_warnings():
+ # nearly-constant windows are rank deficient: numpy emits a
+ # RankWarning before the SVD fails; treat exactly that
+ # conditioning signal as an invalid candidate, never a leak
+ warnings.filterwarnings("error", message="Polyfit may be poorly conditioned")
+ cb = np.polyfit(xb, force[:split], b_deg)
+ cc = np.polyfit(xc, force[split:], c_deg)
+ except (np.linalg.LinAlgError, np.exceptions.RankWarning):
+ # degenerate design (e.g. an exponential/flat contact branch):
+ # the candidate is invalid, never an untyped crash
+ return float("inf")
+ # continuity: value of baseline at split == value of contact at split
+ vb = float(np.polyval(cb, 0.0))
+ vc = float(np.polyval(cc, 0.0))
+ shift = vb - vc
+ cc = cc.copy()
+ cc[-1] = cc[-1] + shift
+ resid_b = force[:split] - np.polyval(cb, xb)
+ resid_c = force[split:] - np.polyval(cc, xc)
+ return float(np.sum(resid_b**2) + np.sum(resid_c**2))
+
+
+def contact_point_piecewise(
+ curve: ForceCurve,
+ *,
+ baseline_order: int = 1,
+ contact_order: int = 2,
+) -> ContactPointCandidate:
+ """Value-continuous piecewise contact (baseline vs contact polynomial)."""
+ z, force = _approach_data(curve, "contact_point_piecewise")
+ if baseline_order < 0 or contact_order < 0:
+ raise ValueError("orders must be non-negative")
+ n = z.size
+ lo = max(3, int(round(n * SEARCH_START_FRACTION)))
+ hi = min(n - 4, int(round(n * SEARCH_END_FRACTION)))
+ if hi <= lo:
+ raise ForceFoundationError(CONTACT_NOT_FOUND, "search grid too small")
+ best_i, best_res = lo, float("inf")
+ for split in range(lo, hi + 1):
+ res = _piecewise_residual(z, force, split, baseline_order, contact_order)
+ if res < best_res:
+ best_res, best_i = res, split
+ # null model: a single polynomial over the whole curve; a flat curve
+ # cannot be improved by any piecewise split
+ deg = max(baseline_order, contact_order)
+ xc_all = z - float(z[0])
+ if n > deg + 1:
+ coeffs = np.polyfit(xc_all, force, deg)
+ null_res = float(np.sum((force - np.polyval(coeffs, xc_all)) ** 2))
+ else:
+ null_res = 0.0
+ # a meaningful improvement must exceed the rounding floor of the
+ # signal itself; perfectly flat curves cannot pass
+ f_scale = float(np.max(np.abs(force)))
+ floor = (1e-12 * f_scale) ** 2 * n if f_scale > 0 else 0.0
+ improved = null_res > floor and best_res < 0.5 * null_res
+ if not improved:
+ return ContactPointCandidate(
+ method="piecewise",
+ index=-1,
+ coordinate=float(z[-1]),
+ score=float(best_res),
+ valid=False,
+ failure_reason=CONTACT_NOT_FOUND,
+ diagnostics={"baseline_order": baseline_order, "contact_order": contact_order},
+ )
+ return ContactPointCandidate(
+ method="piecewise",
+ index=best_i,
+ coordinate=float(z[best_i]),
+ score=float(best_res),
+ valid=True,
+ diagnostics={"baseline_order": baseline_order, "contact_order": contact_order},
+ )
+
+
+def _bootstrap_median(
+ curve: ForceCurve, methods: tuple[str, ...], samples: int, seed: int
+) -> tuple[float, float]:
+ """Deterministic bootstrap of the ensemble median (indices)."""
+ z, force = _approach_data(curve, "contact_point_ensemble")
+ rng = np.random.default_rng(seed)
+ medians: list[float] = []
+ for _ in range(samples):
+ idx = rng.integers(0, force.size, size=force.size)
+ sub = force[idx]
+ zs = z[idx]
+ est = []
+ for method in methods:
+ if method == "threshold":
+ n_base = max(4, int(round(zs.size * SEARCH_START_FRACTION)))
+ mean = float(np.mean(sub[:n_base]))
+ scale = float(np.std(sub[:n_base])) or 1.0
+ above = sub > mean + 5.0 * scale
+ hits = np.flatnonzero(above[n_base:])
+ if hits.size:
+ est.append(float(n_base + int(hits[0])))
+ elif method == "ratio_of_variances":
+ w = min(ROV_DEFAULT_WINDOW, zs.size // 3)
+ if zs.size >= 2 * w + 1:
+ best_r = -1.0
+ for i in range(w, zs.size - w):
+ r = float(np.var(sub[i : i + w])) / (float(np.var(sub[i - w : i])) + 1e-300)
+ if r > best_r:
+ best_r, best_i = r, i
+ est.append(float(best_i))
+ elif method == "piecewise":
+ lo = max(3, zs.size // 10)
+ hi = zs.size - 4
+ if hi > lo:
+ best_split: int = lo
+ best_res = float("inf")
+ for split in range(lo, hi + 1):
+ res = _piecewise_residual(zs, sub, split, 1, 2)
+ if res < best_res:
+ best_res, best_split = res, split
+ est.append(float(best_split))
+ if est:
+ medians.append(float(np.median(est)))
+ if not medians:
+ return (float("nan"), float("nan"))
+ pct_lo = float(np.percentile(medians, 2.5))
+ pct_hi = float(np.percentile(medians, 97.5))
+ return pct_lo, pct_hi
+
+
+def contact_point_ensemble(
+ curve: ForceCurve,
+ *,
+ methods: tuple[str, ...] = ("threshold", "ratio_of_variances", "piecewise"),
+ bootstrap_samples: int = 0,
+) -> ContactPointResult:
+ """Combine contact methods; robust location = median of valid indices."""
+ candidates: list[ContactPointCandidate] = []
+ for method in methods:
+ if method == "threshold":
+ candidates.append(contact_point_threshold(curve))
+ elif method == "ratio_of_variances":
+ candidates.append(contact_point_ratio_of_variances(curve))
+ elif method == "piecewise":
+ candidates.append(contact_point_piecewise(curve))
+ else:
+ raise ValueError(f"unknown contact method {method!r}")
+ valid = [c for c in candidates if c.valid]
+ if len(valid) < 2:
+ reasons = [c.failure_reason for c in candidates if not c.valid]
+ raise ForceFoundationError(
+ CONTACT_METHOD_DISAGREEMENT,
+ f"insufficient agreeing contact methods ({len(valid)} valid; {reasons})",
+ )
+ indices = sorted(c.index for c in valid)
+ median_idx = int(round(float(np.median(indices))))
+ spread_idx = indices[-1] - indices[0]
+ z, _force = _approach_data(curve, "contact_point_ensemble")
+ spread_coord = float(z[indices[-1]] - z[indices[0]])
+ selected = ContactPointCandidate(
+ method="ensemble",
+ index=median_idx,
+ coordinate=float(z[median_idx]),
+ score=float(np.median([c.score for c in valid])),
+ valid=True,
+ diagnostics={"valid_methods": [c.method for c in valid]},
+ )
+ bootstrap = None
+ if bootstrap_samples > 0:
+ lo, hi = _bootstrap_median(
+ curve, tuple(c.method for c in valid), bootstrap_samples, ENSEMBLE_SEED
+ )
+ if not np.isnan(lo):
+ bootstrap = (
+ float(z[int(round(lo))]) if 0 <= int(round(lo)) < z.size else lo,
+ float(z[int(round(hi))]) if 0 <= int(round(hi)) < z.size else hi,
+ )
+ warnings: tuple[str, ...] = ()
+ if len(valid) < len(methods):
+ warnings = (f"{len(methods) - len(valid)} method(s) failed",)
+ return ContactPointResult(
+ selected=selected,
+ candidates=tuple(candidates),
+ method_agreement=len(valid),
+ spread_samples=spread_idx,
+ spread_coordinate=spread_coord,
+ bootstrap_interval=bootstrap,
+ warnings=warnings,
+ )
diff --git a/src/spmkit/core/analysis/force_fit_reliability.py b/src/spmkit/core/analysis/force_fit_reliability.py
new file mode 100644
index 0000000..36886a9
--- /dev/null
+++ b/src/spmkit/core/analysis/force_fit_reliability.py
@@ -0,0 +1,321 @@
+"""FS-F2 fit reliability: sensitivity multiverse, bootstrap, diagnostics."""
+
+from __future__ import annotations
+
+import math
+from dataclasses import dataclass
+
+import numpy as np
+
+from spmkit.core.analysis.contact_mechanics import (
+ ContactMechanicsFitResult,
+ fit_hertz_sphere,
+)
+from spmkit.core.analysis.force_foundation import ForcePreparationResult
+from spmkit.core.analysis.force_indentation import (
+ FitWindowResult,
+ IndentationResult,
+)
+from spmkit.core.analysis.force_mechanics_errors import (
+ BOOTSTRAP_INSUFFICIENT_SUCCESS,
+ CONTACT_SENSITIVITY_HIGH,
+ CURVE_NOT_FIT_ELIGIBLE,
+ ForceMechanicsError,
+)
+
+
+@dataclass(frozen=True)
+class ForceFitSensitivityResult:
+ """Raw evaluated multiverse; never collapsed into one interval."""
+
+ configurations: tuple[dict[str, object], ...]
+ parameter_multiverse: tuple[dict[str, float], ...]
+ failures: tuple[tuple[dict[str, object], str], ...]
+ stability_ranges: dict[str, tuple[float, float]]
+ robust_medians: dict[str, float]
+ dominant_sensitivity: str
+ n_configurations: int
+ n_skipped: int
+ warnings: tuple[str, ...] = ()
+ contact_sensitivity: float = 0.0
+ window_sensitivity: float = 0.0
+
+
+@dataclass(frozen=True)
+class BootstrapForceFitResult:
+ """Deterministic residual bootstrap of one model fit."""
+
+ seed: int
+ strategy: str
+ samples: int
+ n_success: int
+ parameter_samples: tuple[dict[str, float], ...]
+ percentile_intervals: dict[str, tuple[float, float]]
+ bias_estimate: dict[str, float]
+ warnings: tuple[str, ...] = ()
+
+
+@dataclass(frozen=True)
+class ForceFitDiagnosticResult:
+ """Explicit diagnostics; the summary status is a policy, not a
+ validated probability."""
+
+ fit_eligible: bool
+ residual_rms: float
+ residual_autocorrelation_proxy: float
+ residual_curvature_proxy: float
+ parameter_bound_hits: tuple[str, ...]
+ condition_metric: float
+ parameter_correlation_max: float
+ contact_sensitivity: float
+ window_sensitivity: float
+ bootstrap_success_fraction: float | None
+ model_ambiguous: bool
+ failure_reasons: tuple[str, ...]
+ summary_status: str
+ warnings: tuple[str, ...] = ()
+
+
+def _recompute_indentation(prepared: ForcePreparationResult,
+ contact_offset: int) -> IndentationResult:
+ """Recompute indentation with a shifted contact (no curve mutation)."""
+ import numpy as np
+
+ # reuse the contact coordinate from provenance and shift by sample spacing
+ approach = prepared.curve.extend
+ if approach is None or approach.separation is None or approach.raw_height is None:
+ raise ForceMechanicsError(CURVE_NOT_FIT_ELIGIBLE, "no approach branch")
+ sep = np.asarray(approach.separation, dtype=np.float64)
+ zc = prepared.contact.selected.coordinate
+ z = np.asarray(approach.raw_height, dtype=np.float64)
+ dz = float(np.mean(np.diff(z))) if z.size > 1 else 0.0
+ zc_shifted = zc + contact_offset * abs(dz)
+ # same convention as compute_indentation: indentation = separation -
+ # contact coordinate (the height at the contact)
+ ind = sep - zc_shifted
+ return IndentationResult(
+ indentation=ind, contact_index=prepared.contact.selected.index + contact_offset,
+ contact_coordinate=zc_shifted, separation=sep, valid=ind >= 0.0,
+ provenance={"shifted_contact": True, "offset": contact_offset},
+ )
+
+
+def analyze_force_fit_sensitivity(
+ prepared: ForcePreparationResult,
+ *,
+ contact_offsets: tuple[int, ...] = (-3, -1, 0, 1, 3),
+ fit_window_variants: tuple[float, ...] = (0.0, 0.05),
+ baseline_variants: tuple[str, ...] = ("linear",),
+ models: tuple[str, ...] = ("hertz_sphere",),
+ max_configurations: int = 512,
+ tip_radius: float = 10e-9,
+ poisson: float = 0.3,
+) -> ForceFitSensitivityResult:
+ """Deterministic sensitivity multiverse over contact/window/baseline."""
+ from spmkit.core.analysis.force_indentation import select_contact_fit_window
+
+ n_skipped = 0
+ configs: list[dict[str, object]] = []
+ params_out: list[dict[str, float]] = []
+ keys_out: list[tuple[int, float]] = []
+ failures: list[tuple[dict[str, object], str]] = []
+ for off in contact_offsets:
+ if len(configs) + len(failures) >= max_configurations:
+ n_skipped += 1
+ continue
+ ind = _recompute_indentation(prepared, off)
+ ind_max = float(np.max(ind.indentation)) if ind.indentation.size else 0.0
+ for wfrac in fit_window_variants:
+ if len(configs) + len(failures) >= max_configurations:
+ n_skipped += 1
+ continue
+ # window variants are FRACTIONS of the indentation range
+ bound = float(wfrac) * ind_max
+ try:
+ window = select_contact_fit_window(prepared, ind,
+ min_indentation=bound,
+ min_points=10)
+ fit = fit_hertz_sphere(prepared, ind, window,
+ tip_radius=tip_radius, poisson=poisson)
+ config = {"contact_offset": off, "window_lower": bound,
+ "window_lower_fraction": float(wfrac),
+ "baseline": "linear", "model": "hertz_sphere"}
+ configs.append(config)
+ params_out.append(fit.parameters)
+ keys_out.append((int(off), float(wfrac)))
+ except ForceMechanicsError as exc:
+ failures.append(({"contact_offset": off, "window_lower": bound,
+ "window_lower_fraction": float(wfrac),
+ "baseline": "linear", "model": "hertz_sphere"},
+ exc.code))
+ if not params_out:
+ raise ForceMechanicsError(CONTACT_SENSITIVITY_HIGH,
+ "no multiverse configuration succeeded")
+ e_values = np.array([p["E"] for p in params_out])
+ lo, hi = float(np.percentile(e_values, 5)), float(np.percentile(e_values, 95))
+ # one-at-a-time sensitivity indices relative to the baseline
+ # configuration (contact offset 0, window lower fraction 0.0)
+ by_key: dict[tuple[int, float], float] = {}
+ for key, p in zip(keys_out, params_out, strict=True):
+ by_key[key] = p["E"]
+ base = by_key.get((0, 0.0))
+ if base:
+ contact_E = [by_key[(off, 0.0)] for off in contact_offsets if (off, 0.0) in by_key]
+ window_E = [by_key[(0, wf)] for wf in fit_window_variants if (0, wf) in by_key]
+ contact_sens = (float(max(abs(e - base) for e in contact_E)) / abs(base)
+ if contact_E else 0.0)
+ window_sens = (float(max(abs(e - base) for e in window_E)) / abs(base)
+ if window_E else 0.0)
+ else:
+ spread = float((np.max(e_values) - np.min(e_values)) / np.median(e_values))
+ contact_sens = window_sens = spread
+ if contact_sens > 0.2:
+ dominant = "contact"
+ elif window_sens > 0.2:
+ dominant = "window"
+ else:
+ dominant = "none"
+ return ForceFitSensitivityResult(
+ configurations=tuple(configs), parameter_multiverse=tuple(params_out),
+ failures=tuple(failures), stability_ranges={"E": (lo, hi)},
+ robust_medians={"E": float(np.median(e_values))},
+ dominant_sensitivity=dominant, n_configurations=len(configs),
+ n_skipped=n_skipped, contact_sensitivity=contact_sens,
+ window_sensitivity=window_sens,
+ )
+
+
+def bootstrap_force_fit(
+ spec: tuple[ForcePreparationResult, IndentationResult, FitWindowResult, str],
+ *,
+ samples: int = 500,
+ seed: int = 0,
+ strategy: str = "residual",
+ tip_radius: float = 10e-9,
+ poisson: float = 0.3,
+ min_success_fraction: float = 0.5,
+) -> BootstrapForceFitResult:
+ """Deterministic residual bootstrap of a hertz fit specification."""
+ if strategy not in ("residual", "block_residual"):
+ raise ValueError(f"unknown bootstrap strategy {strategy!r}")
+ prepared, ind, window, _model = spec
+ base_fit = fit_hertz_sphere(prepared, ind, window,
+ tip_radius=tip_radius, poisson=poisson)
+ residuals = base_fit.residuals
+ rng = np.random.default_rng(seed)
+ samples_out: list[dict[str, float]] = []
+ approach = prepared.curve.extend
+ if approach is None or approach.force is None:
+ raise ForceMechanicsError(CURVE_NOT_FIT_ELIGIBLE, "no calibrated approach")
+ d = np.asarray(ind.indentation, dtype=np.float64)
+ f = np.asarray(approach.force, dtype=np.float64)
+ idx = np.arange(window.start_index, window.end_index + 1)
+ block = 5 if strategy == "block_residual" else 1
+ # block strategy: permute whole blocks; when the window length is not a
+ # multiple of the block size, the permuted blocks are cyclically
+ # repeated to exactly fill the window (deterministic, no reshape crash)
+ if block > 1:
+ n_full = (residuals.size // block) * block
+ blocks = residuals[:n_full].reshape(-1, block) if n_full else residuals.reshape(1, -1)
+ for _ in range(samples):
+ if block > 1:
+ perm = rng.permutation(blocks).reshape(-1)
+ if perm.size < idx.size:
+ reps = int(np.ceil(idx.size / perm.size))
+ res_perm = np.tile(perm, reps)[: idx.size]
+ else:
+ res_perm = perm[: idx.size]
+ else:
+ res_perm = rng.permutation(residuals)
+ # the bootstrap force is the fitted window force plus permuted
+ # residuals, written back into the full-length force array so the
+ # refit window slices align
+ f_boot_full = f.copy()
+ f_boot_full[idx] = base_fit.predicted_force + res_perm[: idx.size]
+ try:
+ from spmkit.core.analysis.contact_mechanics import _fit_one
+
+ boot = _fit_one("hertz_sphere", d, f_boot_full, window.start_index,
+ window.end_index, {"R": tip_radius, "poisson": poisson},
+ {"E": base_fit.parameters["E"]})
+ samples_out.append(boot.parameters)
+ except ForceMechanicsError:
+ continue
+ if not 0.0 <= min_success_fraction <= 1.0:
+ raise ForceMechanicsError(BOOTSTRAP_INSUFFICIENT_SUCCESS,
+ "min_success_fraction must be in [0, 1]")
+ if len(samples_out) < min_success_fraction * samples:
+ raise ForceMechanicsError(BOOTSTRAP_INSUFFICIENT_SUCCESS,
+ f"only {len(samples_out)}/{samples} replicates succeeded")
+ e_vals = np.array([p["E"] for p in samples_out])
+ intervals = {
+ "E": (float(np.percentile(e_vals, 2.5)), float(np.percentile(e_vals, 97.5)))}
+ bias = {"E": float(np.mean(e_vals) - base_fit.parameters["E"])}
+ return BootstrapForceFitResult(
+ seed=seed, strategy=strategy, samples=samples, n_success=len(samples_out),
+ parameter_samples=tuple(samples_out), percentile_intervals=intervals,
+ bias_estimate=bias,
+ )
+
+
+def diagnose_force_fit(
+ fit: ContactMechanicsFitResult,
+ *,
+ sensitivity: ForceFitSensitivityResult | None = None,
+ bootstrap: BootstrapForceFitResult | None = None,
+) -> ForceFitDiagnosticResult:
+ """Explicit diagnostics; summary status is a policy, not a probability."""
+ residuals = np.asarray(fit.residuals, dtype=np.float64)
+ n = residuals.size
+ rms = float(np.sqrt(np.mean(residuals**2)))
+ ac = 0.0
+ if n > 2:
+ r = residuals - np.mean(residuals)
+ denom = np.sum(r**2)
+ ac = float(np.sum(r[:-1] * r[1:]) / denom) if denom > 0 else 0.0
+ curvature = 0.0
+ if n > 3:
+ x = np.arange(n, dtype=float)
+ c = np.polyfit(x, residuals, 2)
+ curvature = float(abs(c[0]))
+ bound_hits: tuple[str, ...] = ()
+ # covariance conditioning and parameter correlation from the fit
+ # covariance matrix (scale-invariant condition number)
+ condition_metric = 0.0
+ parameter_correlation_max = 0.0
+ cov = fit.covariance
+ if cov:
+ names = sorted({k.split("__")[0] for k in cov})
+ if names:
+ m = np.array([[cov.get(f"{i}__{j}", 0.0) for j in names] for i in names])
+ if np.all(np.isfinite(m)) and np.linalg.matrix_rank(m) == len(names):
+ condition_metric = float(np.linalg.cond(m))
+ if len(names) >= 2:
+ corrs = []
+ for i in range(len(names)):
+ for j in range(i + 1, len(names)):
+ den = math.sqrt(m[i, i] * m[j, j])
+ if den > 0.0:
+ corrs.append(abs(m[i, j]) / den)
+ parameter_correlation_max = max(corrs) if corrs else 0.0
+ reasons: list[str] = []
+ if sensitivity is not None and sensitivity.dominant_sensitivity == "contact":
+ reasons.append("CONTACT_SENSITIVITY_HIGH")
+ if sensitivity is not None and sensitivity.dominant_sensitivity == "window":
+ reasons.append("WINDOW_SENSITIVITY_HIGH")
+ amb = bool(fit.diagnostics.get("ambiguous", False))
+ eligible = not reasons and fit.success
+ summary = "ok" if eligible else "review"
+ return ForceFitDiagnosticResult(
+ fit_eligible=eligible, residual_rms=rms,
+ residual_autocorrelation_proxy=ac, residual_curvature_proxy=curvature,
+ parameter_bound_hits=bound_hits, condition_metric=condition_metric,
+ parameter_correlation_max=parameter_correlation_max,
+ contact_sensitivity=(sensitivity.contact_sensitivity
+ if sensitivity is not None else 0.0),
+ window_sensitivity=(sensitivity.window_sensitivity
+ if sensitivity is not None else 0.0),
+ bootstrap_success_fraction=(bootstrap.n_success / bootstrap.samples
+ if bootstrap is not None else None),
+ model_ambiguous=amb, failure_reasons=tuple(reasons), summary_status=summary,
+ )
diff --git a/src/spmkit/core/analysis/force_foundation.py b/src/spmkit/core/analysis/force_foundation.py
new file mode 100644
index 0000000..3ac2769
--- /dev/null
+++ b/src/spmkit/core/analysis/force_foundation.py
@@ -0,0 +1,77 @@
+"""Force-spectroscopy foundation public surface (FS-F1).
+
+Thirteen public capabilities over the modern segment-based ``ForceCurve``
+model, with typed failures, immutable results and explicit orchestration.
+"""
+
+from __future__ import annotations
+
+from spmkit.core.analysis.force_contact import (
+ ContactPointCandidate,
+ ContactPointResult,
+ contact_point_ensemble,
+ contact_point_piecewise,
+ contact_point_ratio_of_variances,
+ contact_point_threshold,
+)
+from spmkit.core.analysis.force_foundation_errors import (
+ ForceFoundationError,
+)
+from spmkit.core.analysis.force_metrics import (
+ CoordinatePathDiagnostics,
+ ForceEventResult,
+ ForcePathWorkResult,
+ ForceWorkResult,
+ coordinate_path_diagnostics,
+ extract_force_events,
+ integrate_force_path_work,
+ integrate_force_work,
+)
+from spmkit.core.analysis.force_prepare import (
+ ForcePreparationResult,
+ prepare_force_curve,
+)
+from spmkit.core.analysis.force_preprocessing import (
+ ForceBaselineResult,
+ ForceCalibrationResult,
+ ForceSegmentationResult,
+ calibrate_force_curve,
+ compute_tip_sample_separation,
+ correct_force_baseline,
+ fit_force_baseline,
+ identify_force_segments,
+)
+from spmkit.core.analysis.force_quality import (
+ ForceCurveQualityResult,
+ score_force_curve_quality,
+)
+
+__all__ = [
+ "identify_force_segments",
+ "calibrate_force_curve",
+ "compute_tip_sample_separation",
+ "fit_force_baseline",
+ "correct_force_baseline",
+ "contact_point_threshold",
+ "contact_point_ratio_of_variances",
+ "contact_point_piecewise",
+ "contact_point_ensemble",
+ "coordinate_path_diagnostics",
+ "extract_force_events",
+ "integrate_force_path_work",
+ "integrate_force_work",
+ "score_force_curve_quality",
+ "prepare_force_curve",
+ "ForceSegmentationResult",
+ "ForceCalibrationResult",
+ "ForceBaselineResult",
+ "ContactPointCandidate",
+ "ContactPointResult",
+ "CoordinatePathDiagnostics",
+ "ForceEventResult",
+ "ForcePathWorkResult",
+ "ForceWorkResult",
+ "ForceCurveQualityResult",
+ "ForcePreparationResult",
+ "ForceFoundationError",
+]
diff --git a/src/spmkit/core/analysis/force_foundation_errors.py b/src/spmkit/core/analysis/force_foundation_errors.py
new file mode 100644
index 0000000..4b3c1f4
--- /dev/null
+++ b/src/spmkit/core/analysis/force_foundation_errors.py
@@ -0,0 +1,61 @@
+"""Shared typed failures and validation helpers for the SPMKit force
+foundation (FS-F1).
+
+The force foundation never returns NaN-filled pseudo-success: every failure
+is a typed :class:`ForceFoundationError` carrying a machine-readable code.
+"""
+
+from __future__ import annotations
+
+import numpy as np
+
+#: Typed failure reasons (QC and raised errors share the same vocabulary).
+MISSING_CALIBRATION = "MISSING_CALIBRATION"
+INVALID_CALIBRATION = "INVALID_CALIBRATION"
+MISSING_APPROACH = "MISSING_APPROACH"
+MISSING_RETRACT = "MISSING_RETRACT"
+NONFINITE_DATA = "NONFINITE_DATA"
+NONMONOTONIC_COORDINATE = "NONMONOTONIC_COORDINATE"
+BASELINE_TOO_SHORT = "BASELINE_TOO_SHORT"
+BASELINE_UNSTABLE = "BASELINE_UNSTABLE"
+CONTACT_NOT_FOUND = "CONTACT_NOT_FOUND"
+CONTACT_METHOD_DISAGREEMENT = "CONTACT_METHOD_DISAGREEMENT"
+SATURATED_SIGNAL = "SATURATED_SIGNAL"
+EVENT_NOT_FOUND = "EVENT_NOT_FOUND"
+INSUFFICIENT_OVERLAP = "INSUFFICIENT_OVERLAP"
+FIT_NOT_ELIGIBLE = "FIT_NOT_ELIGIBLE"
+MISSING_COORDINATE = "MISSING_COORDINATE"
+INSUFFICIENT_SAMPLES = "INSUFFICIENT_SAMPLES"
+LENGTH_MISMATCH = "LENGTH_MISMATCH"
+
+
+class ForceFoundationError(ValueError):
+ """Typed force-foundation failure with a machine-readable code."""
+
+ def __init__(self, code: str, message: str) -> None:
+ super().__init__(message)
+ self.code = code
+ self.message = message
+
+
+def require_finite(values: np.ndarray, *, label: str) -> np.ndarray:
+ """Validate a finite one-dimensional float64 array (copied)."""
+ try:
+ arr = np.asarray(values, dtype=np.float64)
+ except (TypeError, ValueError) as exc:
+ raise ForceFoundationError(NONFINITE_DATA, f"{label} must be array-compatible") from exc
+ if arr.ndim != 1:
+ raise ForceFoundationError(NONFINITE_DATA, f"{label} must be one-dimensional")
+ if arr.size == 0:
+ raise ForceFoundationError(NONFINITE_DATA, f"{label} must be non-empty")
+ if not np.isfinite(arr).all():
+ raise ForceFoundationError(NONFINITE_DATA, f"{label} must be finite")
+ return arr.copy()
+
+
+def require_monotone_increasing(values: np.ndarray, *, label: str, tol: float = 0.0) -> None:
+ """Require a strictly monotone increasing coordinate (up to ``tol``)."""
+ if np.any(np.diff(values) < tol):
+ raise ForceFoundationError(
+ NONMONOTONIC_COORDINATE, f"{label} must be monotonically increasing"
+ )
diff --git a/src/spmkit/core/analysis/force_indentation.py b/src/spmkit/core/analysis/force_indentation.py
new file mode 100644
index 0000000..9c86893
--- /dev/null
+++ b/src/spmkit/core/analysis/force_indentation.py
@@ -0,0 +1,160 @@
+"""FS-F2 indentation and fit-window selection.
+
+Computes indentation from a prepared curve (contact-aware, no hidden offsets)
+and selects an explicit fit window on the approach branch.
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass, field
+
+import numpy as np
+
+from spmkit.core.analysis.force_foundation import ForcePreparationResult
+from spmkit.core.analysis.force_mechanics_errors import (
+ CURVE_NOT_FIT_ELIGIBLE,
+ EMPTY_FIT_WINDOW,
+ INSUFFICIENT_FIT_POINTS,
+ INVALID_INDENTATION,
+ NONFINITE_INPUT,
+ ForceMechanicsError,
+)
+
+
+def _contact_methods(prepared: ForcePreparationResult) -> list[str]:
+ contact = prepared.provenance.get("contact", {})
+ if isinstance(contact, dict):
+ methods = contact.get("methods", [])
+ if isinstance(methods, list):
+ return [str(m) for m in methods]
+ return []
+
+
+@dataclass(frozen=True)
+class IndentationResult:
+ """Contact-relative indentation of the approach branch."""
+
+ indentation: np.ndarray
+ contact_index: int
+ contact_coordinate: float
+ separation: np.ndarray
+ valid: np.ndarray
+ units: str = "m"
+ warnings: tuple[str, ...] = ()
+ provenance: dict[str, object] = field(default_factory=dict)
+
+
+@dataclass(frozen=True)
+class FitWindowResult:
+ """Explicit fit window on the indentation axis."""
+
+ start_index: int
+ end_index: int
+ indentation_min: float
+ indentation_max: float
+ force_min: float
+ force_max: float
+ included: np.ndarray
+ excluded_reasons: tuple[str, ...]
+ n_points: int
+ warnings: tuple[str, ...] = ()
+
+
+def compute_indentation(
+ prepared: ForcePreparationResult,
+) -> IndentationResult:
+ """Indentation = separation - contact_coordinate on the approach branch.
+
+ The contact coordinate is the height at the contact index (FS-F1
+ convention; the deflection is zero there, so height and separation
+ coincide at the contact). Indentation therefore equals the piezo
+ motion past the contact minus the cantilever deflection; it is zero at
+ the contact and positive into the sample when the deflection grows more
+ slowly than the piezo motion (indentation regime). Only the approach
+ branch is used; samples before the contact are excluded (negative
+ indentation is not fabricated).
+ """
+ if not isinstance(prepared, ForcePreparationResult):
+ raise TypeError("compute_indentation requires a ForcePreparationResult")
+ if not prepared.quality.eligible:
+ raise ForceMechanicsError(
+ CURVE_NOT_FIT_ELIGIBLE,
+ "curve is not fit-eligible: " + ", ".join(prepared.quality.failure_reasons))
+ approach = prepared.curve.extend
+ if approach is None or approach.force is None:
+ raise ForceMechanicsError(CURVE_NOT_FIT_ELIGIBLE, "no calibrated approach")
+ if approach.separation is None:
+ raise ForceMechanicsError(INVALID_INDENTATION, "no tip-sample separation")
+ sep = np.asarray(approach.separation, dtype=np.float64)
+ f = np.asarray(approach.force, dtype=np.float64)
+ if not np.isfinite(sep).all() or not np.isfinite(f).all():
+ raise ForceMechanicsError(NONFINITE_INPUT, "non-finite separation/force")
+ zc = float(prepared.contact.selected.coordinate)
+ ind = sep - zc
+ valid = ind >= 0.0
+ return IndentationResult(
+ indentation=ind,
+ contact_index=prepared.contact.selected.index,
+ contact_coordinate=zc,
+ separation=sep,
+ valid=valid,
+ provenance={
+ "convention": "indentation = separation - contact_coordinate",
+ "contact_index": prepared.contact.selected.index,
+ "contact_methods": _contact_methods(prepared),
+ },
+ )
+
+
+def select_contact_fit_window(
+ prepared: ForcePreparationResult,
+ indentation: IndentationResult,
+ *,
+ min_indentation: float | None = None,
+ max_indentation: float | None = None,
+ min_force: float | None = None,
+ max_force: float | None = None,
+ min_points: int = 20,
+) -> FitWindowResult:
+ """Select the explicit fit window on the approach indentation axis.
+
+ Negative indentation is always excluded; force bounds exclude the
+ adhesion region and saturation; no automatic window expansion.
+ """
+ ind = np.asarray(indentation.indentation, dtype=np.float64)
+ approach = prepared.curve.extend
+ if approach is None or approach.force is None:
+ raise ForceMechanicsError(CURVE_NOT_FIT_ELIGIBLE, "no calibrated approach")
+ f = np.asarray(approach.force, dtype=np.float64)
+ included = np.ones(ind.size, dtype=bool)
+ reasons: list[str] = []
+ included &= ind >= 0.0
+ if min_indentation is not None:
+ included &= ind >= min_indentation
+ if max_indentation is not None:
+ included &= ind <= max_indentation
+ if min_force is not None:
+ included &= f >= min_force
+ if max_force is not None:
+ included &= f <= max_force
+ idx = np.flatnonzero(included)
+ if idx.size == 0:
+ raise ForceMechanicsError(EMPTY_FIT_WINDOW, "no samples satisfy the window")
+ start, end = int(idx[0]), int(idx[-1])
+ if end - start + 1 < min_points:
+ raise ForceMechanicsError(
+ INSUFFICIENT_FIT_POINTS,
+ f"fit window has {end - start + 1} points < min_points={min_points}")
+ return FitWindowResult(
+ start_index=start,
+ end_index=end,
+ indentation_min=float(ind[start]),
+ indentation_max=float(ind[end]),
+ force_min=float(np.min(f[idx])),
+ force_max=float(np.max(f[idx])),
+ included=included,
+ excluded_reasons=tuple(reasons),
+ n_points=int(idx.size),
+ warnings=(f"excluded {ind.size - idx.size} sample(s) before/outside window",)
+ if idx.size < ind.size else (),
+ )
diff --git a/src/spmkit/core/analysis/force_mechanics.py b/src/spmkit/core/analysis/force_mechanics.py
new file mode 100644
index 0000000..34bb206
--- /dev/null
+++ b/src/spmkit/core/analysis/force_mechanics.py
@@ -0,0 +1,61 @@
+"""FS-F2 public surface: contact mechanics, fit reliability, volume mapping."""
+
+from __future__ import annotations
+
+from spmkit.core.analysis.contact_mechanics import (
+ ContactMechanicsFitResult,
+ ModelComparisonResult,
+ compare_contact_models,
+ fit_dmt,
+ fit_flat_punch,
+ fit_hertz_sphere,
+ fit_jkr,
+ fit_sneddon_cone,
+ forward_model,
+)
+from spmkit.core.analysis.force_fit_reliability import (
+ BootstrapForceFitResult,
+ ForceFitDiagnosticResult,
+ ForceFitSensitivityResult,
+ analyze_force_fit_sensitivity,
+ bootstrap_force_fit,
+ diagnose_force_fit,
+)
+from spmkit.core.analysis.force_indentation import (
+ FitWindowResult,
+ IndentationResult,
+ compute_indentation,
+ select_contact_fit_window,
+)
+from spmkit.core.analysis.force_mechanics_errors import (
+ ForceMechanicsError,
+)
+from spmkit.core.analysis.force_volume_mechanics import (
+ ForceVolumeMechanicsResult,
+ fit_force_volume_mechanics,
+)
+
+__all__ = [
+ "compute_indentation",
+ "select_contact_fit_window",
+ "fit_hertz_sphere",
+ "fit_sneddon_cone",
+ "fit_flat_punch",
+ "fit_dmt",
+ "fit_jkr",
+ "compare_contact_models",
+ "forward_model",
+ "analyze_force_fit_sensitivity",
+ "bootstrap_force_fit",
+ "diagnose_force_fit",
+ "fit_force_volume_mechanics",
+ "IndentationResult",
+ "FitWindowResult",
+ "ContactMechanicsFitResult",
+ "ModelComparisonResult",
+ "ForceFitSensitivityResult",
+ "BootstrapForceFitResult",
+ "ForceFitDiagnosticResult",
+ "ForceVolumeMechanicsResult",
+ "ForceMechanicsError",
+]
diff --git a/src/spmkit/core/analysis/force_mechanics_errors.py b/src/spmkit/core/analysis/force_mechanics_errors.py
new file mode 100644
index 0000000..ed7db71
--- /dev/null
+++ b/src/spmkit/core/analysis/force_mechanics_errors.py
@@ -0,0 +1,27 @@
+"""Typed failures for the FS-F2 force-mechanics batch."""
+
+INVALID_INDENTATION = "INVALID_INDENTATION"
+EMPTY_FIT_WINDOW = "EMPTY_FIT_WINDOW"
+INSUFFICIENT_FIT_POINTS = "INSUFFICIENT_FIT_POINTS"
+INVALID_RADIUS = "INVALID_RADIUS"
+INVALID_ANGLE = "INVALID_ANGLE"
+INVALID_POISSON_RATIO = "INVALID_POISSON_RATIO"
+INVALID_ADHESION_PARAMETER = "INVALID_ADHESION_PARAMETER"
+NONFINITE_INPUT = "NONFINITE_INPUT"
+OPTIMIZATION_FAILED = "OPTIMIZATION_FAILED"
+PARAMETER_AT_BOUND = "PARAMETER_AT_BOUND"
+NONIDENTIFIABLE_MODEL = "NONIDENTIFIABLE_MODEL"
+CONTACT_SENSITIVITY_HIGH = "CONTACT_SENSITIVITY_HIGH"
+WINDOW_SENSITIVITY_HIGH = "WINDOW_SENSITIVITY_HIGH"
+MODEL_AMBIGUOUS = "MODEL_AMBIGUOUS"
+BOOTSTRAP_INSUFFICIENT_SUCCESS = "BOOTSTRAP_INSUFFICIENT_SUCCESS"
+CURVE_NOT_FIT_ELIGIBLE = "CURVE_NOT_FIT_ELIGIBLE"
+
+
+class ForceMechanicsError(ValueError):
+ """Typed FS-F2 failure with a machine-readable code."""
+
+ def __init__(self, code: str, message: str) -> None:
+ super().__init__(message)
+ self.code = code
+ self.message = message
diff --git a/src/spmkit/core/analysis/force_metrics.py b/src/spmkit/core/analysis/force_metrics.py
new file mode 100644
index 0000000..30e723d
--- /dev/null
+++ b/src/spmkit/core/analysis/force_metrics.py
@@ -0,0 +1,541 @@
+"""Force event and work metrics foundation (FS-F1).
+
+Events: snap-in (approach, before contact) and pull-off (retract, after
+contact), baseline-relative, with physical windows. Work: force integrated
+over tip-sample separation on the common overlap domain with monotone
+interpolation and trapezoidal arithmetic.
+
+Acquisition-path work (FS-R1C): the signed line integral along a single
+trajectory in **sample-acquisition order** (``integrate_force_path_work``)
+with deterministic trapezoidal arithmetic and explicit coordinate-path
+diagnostics. This is a distinct scientific object from the strict
+monotonic-coordinate integral above: local reversals and loops are retained,
+never sorted, smoothed or deleted.
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass, field
+from typing import Literal
+
+import numpy as np
+
+from spmkit.core.analysis.force_contact import (
+ ContactPointCandidate,
+ ContactPointResult,
+)
+from spmkit.core.analysis.force_foundation_errors import (
+ EVENT_NOT_FOUND,
+ INSUFFICIENT_OVERLAP,
+ INSUFFICIENT_SAMPLES,
+ LENGTH_MISMATCH,
+ MISSING_CALIBRATION,
+ MISSING_COORDINATE,
+ MISSING_RETRACT,
+ NONMONOTONIC_COORDINATE,
+ ForceFoundationError,
+ require_finite,
+ require_monotone_increasing,
+)
+from spmkit.core.models import ForceCurve
+
+#: Dirección global de una trayectoria (clasificación, nunca la integral).
+GlobalDirection = Literal["increasing", "decreasing", "closed_or_ambiguous"]
+
+
+@dataclass(frozen=True)
+class ForceEventResult:
+ """Snap-in and pull-off event characterization."""
+
+ snap_in_index: int | None
+ snap_in_force: float | None
+ snap_in_coordinate: float | None
+ pull_off_index: int | None
+ pull_off_force: float | None
+ pull_off_coordinate: float | None
+ event_windows: dict[str, tuple[float, float]] = field(default_factory=dict)
+ valid: bool = True
+ warnings: tuple[str, ...] = ()
+
+
+@dataclass(frozen=True)
+class ForceWorkResult:
+ """Work integrals over the common tip-position overlap domain."""
+
+ work_approach: float
+ work_retract: float
+ work_adhesion: float
+ hysteresis: float
+ domain: str
+ interpolation: str
+ units: str
+ valid: bool
+ warnings: tuple[str, ...] = ()
+
+
+def _axis(curve: ForceCurve, segment_name: str) -> np.ndarray:
+ seg = curve.extend if segment_name == "approach" else curve.retract
+ if seg is None:
+ raise ForceFoundationError(MISSING_RETRACT, f"no {segment_name} segment")
+ if seg.separation is not None:
+ axis = np.asarray(seg.separation, dtype=np.float64)
+ else:
+ axis = np.asarray(seg.raw_height, dtype=np.float64)
+ return require_finite(axis, label=f"{segment_name} axis")
+
+
+def extract_force_events(
+ curve: ForceCurve,
+ contact: ContactPointResult | ContactPointCandidate,
+ *,
+ snap_in_window: tuple[float, float] | None = None,
+ pull_off_window: tuple[float, float] | None = None,
+) -> ForceEventResult:
+ """Extract snap-in (approach) and pull-off (retract) events.
+
+ Snap-in is the minimum force before contact in the approach window
+ (baseline-relative: below the baseline mean minus 3 sigma). Pull-off
+ is the minimum force after contact on the retract. Windows are physical
+ coordinates on the selected axis (separation when available, else
+ height).
+ """
+ approach = curve.extend
+ retract = curve.retract
+ if approach is None or approach.force is None:
+ raise ForceFoundationError(MISSING_CALIBRATION, "approach must be calibrated")
+ z_a = _axis(curve, "approach")
+ f_a = require_finite(np.asarray(approach.force, dtype=np.float64), label="approach force")
+ if isinstance(contact, ContactPointResult):
+ cp_index = int(contact.selected.index)
+ else:
+ cp_index = int(contact.index)
+ warnings: list[str] = []
+
+ # snap-in
+ snap_idx: int | None = None
+ snap_force: float | None = None
+ snap_coord: float | None = None
+ if cp_index > 3:
+ n_base = max(4, int(round(z_a.size * 0.10)))
+ base_mean = float(np.mean(f_a[: min(n_base, cp_index)]))
+ base_scale = float(np.std(f_a[: min(n_base, cp_index)]))
+ search = np.arange(0, min(cp_index, z_a.size))
+ if snap_in_window is not None:
+ lo, hi = snap_in_window
+ mask = (z_a >= lo) & (z_a <= hi)
+ search = np.flatnonzero(mask & (np.arange(z_a.size) < cp_index))
+ if search.size:
+ i = int(search[int(np.argmin(f_a[search]))])
+ if f_a[i] < base_mean - 3.0 * base_scale:
+ snap_idx, snap_force, snap_coord = i, float(f_a[i]), float(z_a[i])
+ else:
+ warnings.append("approach too short for snap-in search")
+
+ # pull-off
+ po_idx: int | None = None
+ po_force: float | None = None
+ po_coord: float | None = None
+ if retract is not None and retract.force is not None:
+ z_r = _axis(curve, "retract")
+ f_r = require_finite(np.asarray(retract.force, dtype=np.float64), label="retract force")
+ search = np.arange(0, z_r.size)
+ if pull_off_window is not None:
+ lo, hi = pull_off_window
+ mask = (z_r >= lo) & (z_r <= hi)
+ search = np.flatnonzero(mask)
+ if search.size:
+ i = int(search[int(np.argmin(f_r[search]))])
+ po_idx, po_force, po_coord = i, float(f_r[i]), float(z_r[i])
+ else:
+ warnings.append("no retract segment; pull-off not searched")
+
+ windows = {}
+ if snap_in_window is not None:
+ windows["snap_in"] = snap_in_window
+ if pull_off_window is not None:
+ windows["pull_off"] = pull_off_window
+ valid = snap_idx is not None or po_idx is not None
+ if not valid:
+ warnings.append(EVENT_NOT_FOUND)
+ return ForceEventResult(
+ snap_in_index=snap_idx,
+ snap_in_force=snap_force,
+ snap_in_coordinate=snap_coord,
+ pull_off_index=po_idx,
+ pull_off_force=po_force,
+ pull_off_coordinate=po_coord,
+ event_windows=windows,
+ valid=valid,
+ warnings=tuple(warnings),
+ )
+
+
+def _monotone_resample(x: np.ndarray, y: np.ndarray, target: np.ndarray) -> np.ndarray:
+ """Monotone interpolation of y(x) onto target (no extrapolation)."""
+ order = np.argsort(x, kind="stable")
+ xs, ys = x[order], y[order]
+ require_monotone_increasing(xs, label="coordinate")
+ return np.interp(target, xs, ys)
+
+
+def integrate_force_work(
+ curve: ForceCurve,
+ contact: ContactPointResult | ContactPointCandidate,
+ *,
+ domain: str = "tip_position",
+) -> ForceWorkResult:
+ """Integrate force over tip-sample separation on the common overlap.
+
+ The common domain runs from the contact coordinate to the minimum of
+ the approach and retract maxima. Interpolation is monotone (np.interp
+ over the sorted coordinate); integration is trapezoidal. Units: J.
+ """
+ if domain not in ("tip_position", "height"):
+ raise ValueError(f"unknown integration domain {domain!r}")
+ approach = curve.extend
+ retract = curve.retract
+ if approach is None or approach.force is None:
+ raise ForceFoundationError(MISSING_CALIBRATION, "approach must be calibrated")
+ if retract is None or retract.force is None:
+ raise ForceFoundationError(MISSING_RETRACT, "retract must be calibrated")
+ z_a = _axis(curve, "approach")
+ f_a = require_finite(np.asarray(approach.force, dtype=np.float64), label="approach force")
+ z_r = _axis(curve, "retract")
+ f_r = require_finite(np.asarray(retract.force, dtype=np.float64), label="retract force")
+ for zz, label in ((z_a, "approach"), (z_r, "retract")):
+ d = np.diff(zz)
+ scale = float(np.max(np.abs(zz)))
+ tol = 1e-6 * scale if scale > 0.0 else 1e-300
+ if not (np.all(d > -tol) or np.all(d < tol)):
+ raise ForceFoundationError(
+ NONMONOTONIC_COORDINATE, f"{label} coordinate not strictly monotone"
+ )
+ if isinstance(contact, ContactPointResult):
+ zc = float(contact.selected.coordinate)
+ else:
+ zc = float(contact.coordinate)
+ lo = zc
+ hi = min(float(np.max(z_a)), float(np.max(z_r)))
+ if hi - lo <= 0.0:
+ raise ForceFoundationError(INSUFFICIENT_OVERLAP, "no common overlap domain")
+ n_grid = max(64, int(min(z_a.size, z_r.size)))
+ grid = np.linspace(lo, hi, n_grid)
+ f_a_g = _monotone_resample(z_a, f_a, grid)
+ f_r_g = _monotone_resample(z_r, f_r, grid)
+ w_appr = float(np.trapezoid(f_a_g, grid))
+ w_retr = float(np.trapezoid(f_r_g, grid))
+ return ForceWorkResult(
+ work_approach=w_appr,
+ work_retract=w_retr,
+ work_adhesion=w_retr,
+ hysteresis=w_appr - w_retr,
+ domain=domain,
+ interpolation="linear_monotone",
+ units="J",
+ valid=True,
+ )
+
+
+# ---------------------------------------------------------------------------
+# Acquisition-path work (FS-R1C)
+# ---------------------------------------------------------------------------
+
+
+@dataclass(frozen=True)
+class CoordinatePathDiagnostics:
+ """Diagnósticos de una trayectoria 1-D en orden de adquisición.
+
+ Todos los campos son **clasificación**; ninguno altera la integral. El
+ orden de muestra se conserva; los incrementos firmados ``dz`` se retienen
+ tal cual (no se ordenan, no se aplica ``abs()``, no se suaviza, no se
+ eliminan puntos).
+
+ Definitions (independently testable):
+
+ - ``net_displacement`` = ``z[-1] - z[0]``.
+ - ``total_variation`` = sum |dz| over all steps.
+ - ``forward_distance`` / ``backward_distance``: sums of |dz| for steps
+ whose sign agrees / disagrees with the global direction.
+ - ``backtracking_fraction`` = backward_distance / total_variation
+ (0.0 when total_variation == 0). NOTE: ``backward_distance`` aggregates
+ step magnitudes (a sum of |dz|); ``maximum_reverse_excursion`` measures
+ the deviation from the running directional extremum. A path can have a
+ large backtracking fraction (many tiny opposite steps) and a small
+ maximum reverse excursion simultaneously; they are different quantities.
+ - ``global_direction``: derived from the **net displacement** sign
+ (never from the majority sign alone): negative → ``decreasing``,
+ positive → ``increasing``, |net| <= ``classification_tolerance`` →
+ ``closed_or_ambiguous``.
+ - ``maximum_reverse_step``: the single step opposite to the global
+ direction with the largest magnitude (signed: ``min(dz)`` for
+ decreasing, ``max(dz)`` for increasing); ``None`` when there is no
+ opposite step or the direction is ambiguous.
+ - ``maximum_reverse_excursion``: the path-level cumulative excursion
+ from the running directional extremum — for ``decreasing``:
+ ``max_i (running_min(z[:i+1]) - z_i)``; for ``increasing``:
+ ``max_i (z_i - running_max(z[:i+1]))``; ``0.0`` for a strictly
+ directed path; ``None`` for ambiguous paths.
+ """
+
+ n_samples: int
+ n_steps: int
+ coordinate_unit: str
+ net_displacement: float
+ total_variation: float
+ forward_distance: float
+ backward_distance: float
+ backtracking_fraction: float
+ exact_positive_steps: int
+ exact_negative_steps: int
+ exact_zero_steps: int
+ classified_reversal_count: int
+ maximum_reverse_step: float | None
+ maximum_reverse_excursion: float | None
+ global_direction: GlobalDirection
+ strictly_monotonic: bool
+ globally_directed: bool
+ classification_tolerance: float
+ warnings: tuple[str, ...] = ()
+ provenance: dict = field(default_factory=dict)
+
+
+@dataclass(frozen=True)
+class ForcePathWorkResult:
+ """Trabajo de trayectoria (path work) en orden de adquisición.
+
+ ``work_total = work_forward + work_backward`` exactamente (cada paso
+ pertenece a una sola clase). ``absolute_accumulated_work`` es la suma de
+ los valores absolutos de los términos trapezoidales: **no** es trabajo
+ termodinámico ni energía disipada.
+ """
+
+ work_total: float
+ work_forward: float
+ work_backward: float
+ absolute_accumulated_work: float
+ diagnostics: CoordinatePathDiagnostics
+ units: str
+ valid: bool
+ warnings: tuple[str, ...] = ()
+ provenance: dict = field(default_factory=dict)
+
+
+def _direction_sign(direction: GlobalDirection) -> int:
+ """+1 increasing, -1 decreasing, 0 ambiguous."""
+ return 1 if direction == "increasing" else (-1 if direction == "decreasing" else 0)
+
+
+def coordinate_path_diagnostics(
+ coordinate: np.ndarray,
+ *,
+ unit: str = "m",
+ classification_tolerance: float = 0.0,
+ provenance: dict | None = None,
+) -> CoordinatePathDiagnostics:
+ """Diagnósticos de trayectoria 1-D (clasificación, sin tocar la integral).
+
+ Raises:
+ ForceFoundationError: ``NONFINITE_DATA``, ``INSUFFICIENT_SAMPLES``.
+ """
+ z = require_finite(coordinate, label="coordinate")
+ if z.size < 2:
+ raise ForceFoundationError(
+ INSUFFICIENT_SAMPLES, f"coordinate path needs >= 2 samples (got {z.size})"
+ )
+ if classification_tolerance < 0.0:
+ raise ValueError("classification_tolerance must be >= 0")
+ dz = np.diff(z)
+ positive = dz > 0.0
+ negative = dz < 0.0
+ zero = dz == 0.0
+ forward_dist = float(np.sum(dz[positive]))
+ backward_dist = float(-np.sum(dz[negative]))
+ total_var = float(np.sum(np.abs(dz)))
+ net = float(z[-1] - z[0])
+
+ warnings: list[str] = []
+ if abs(net) <= classification_tolerance:
+ direction: GlobalDirection = "closed_or_ambiguous"
+ warnings.append(
+ "no coherent global direction (|net displacement| <= classification_tolerance); "
+ "signed path work is preserved and no approach/retract direction is assigned"
+ )
+ else:
+ direction = "decreasing" if net < 0.0 else "increasing"
+
+ sign = _direction_sign(direction)
+ exact_pos = int(np.count_nonzero(positive))
+ exact_neg = int(np.count_nonzero(negative))
+ exact_zero = int(np.count_nonzero(zero))
+
+ if sign == 0:
+ reversal_count = 0
+ max_reverse_step: float | None = None
+ max_reverse_excursion: float | None = None
+ else:
+ opposite = dz * sign < 0.0
+ beyond_tol = np.abs(dz) > classification_tolerance
+ reversal_count = int(np.count_nonzero(opposite & beyond_tol))
+ opp_steps = dz[opposite]
+ if opp_steps.size:
+ # paso opuesto a la dirección global de mayor magnitud (firmado):
+ # decreciente -> el mayor paso positivo; creciente -> el más negativo
+ max_reverse_step = float(opp_steps.max()) if sign < 0 else float(opp_steps.min())
+ else:
+ max_reverse_step = None
+ if sign < 0:
+ # decreciente: excursión = cuánto subió la trayectoria sobre el
+ # mínimo corrido alcanzado hasta cada punto
+ running_extremum = np.minimum.accumulate(z)
+ excursion = z - running_extremum
+ else:
+ # creciente: excursión = cuánto bajó sobre el máximo corrido
+ running_extremum = np.maximum.accumulate(z)
+ excursion = running_extremum - z
+ max_reverse_excursion = float(np.max(excursion)) if excursion.size else None
+
+ # trayectoria ambigua: monótona solo si los pasos son de un solo signo
+ strictly_monotonic = exact_pos == 0 or exact_neg == 0 if sign == 0 else reversal_count == 0
+ backtracking_fraction = backward_dist / total_var if total_var > 0.0 else 0.0
+
+ meta = dict(provenance or {})
+ meta.update(
+ {
+ "semantics": "acquisition_order",
+ "classification_tolerance": float(classification_tolerance),
+ "unit": unit,
+ }
+ )
+ return CoordinatePathDiagnostics(
+ n_samples=int(z.size),
+ n_steps=int(dz.size),
+ coordinate_unit=unit,
+ net_displacement=net,
+ total_variation=total_var,
+ forward_distance=forward_dist,
+ backward_distance=backward_dist,
+ backtracking_fraction=backtracking_fraction,
+ exact_positive_steps=exact_pos,
+ exact_negative_steps=exact_neg,
+ exact_zero_steps=exact_zero,
+ classified_reversal_count=reversal_count,
+ maximum_reverse_step=max_reverse_step,
+ maximum_reverse_excursion=max_reverse_excursion,
+ global_direction=direction,
+ strictly_monotonic=strictly_monotonic,
+ globally_directed=sign != 0,
+ classification_tolerance=float(classification_tolerance),
+ warnings=tuple(warnings),
+ provenance=meta,
+ )
+
+
+def integrate_force_path_work(
+ coordinate: np.ndarray,
+ force: np.ndarray,
+ *,
+ coordinate_unit: str = "m",
+ force_unit: str = "N",
+ classification_tolerance: float = 0.0,
+ provenance: dict | None = None,
+) -> ForcePathWorkResult:
+ """Trabajo firmado de trayectoria (path work) en orden de adquisición.
+
+ ``W = sum_i 0.5 * (F_i + F_{i+1}) * (z_{i+1} - z_i)`` evaluada en el
+ orden de muestreo, con aritmética float64 determinista por acumulación
+ explícita. Los incrementos firmados ``dz`` se conservan: las reversiones
+ locales y los lazos cerrados aportan su trabajo firmado; las coordenadas
+ repetidas aportan cero; traducir la coordenada no cambia ``W``; invertir
+ el orden de adquisición cambia el signo.
+
+ El resultado incluye la descomposición (pasos en la dirección global /
+ opuestos) y los diagnósticos de trayectoria completos. Ninguna tolerancia
+ altera la integral: ``classification_tolerance`` solo clasifica.
+
+ Raises:
+ ForceFoundationError: ``NONFINITE_DATA``, ``LENGTH_MISMATCH``,
+ ``INSUFFICIENT_SAMPLES``, ``MISSING_COORDINATE`` (coordenada o
+ fuerza vacías); ``ValueError`` (tolerancia negativa).
+ """
+ if np.asarray(coordinate).size == 0:
+ raise ForceFoundationError(MISSING_COORDINATE, "coordinate axis is empty")
+ if np.asarray(force).size == 0:
+ raise ForceFoundationError(MISSING_COORDINATE, "force axis is empty")
+ z = require_finite(coordinate, label="coordinate")
+ f = require_finite(force, label="force")
+ if z.size != f.size:
+ raise ForceFoundationError(
+ LENGTH_MISMATCH,
+ f"coordinate and force lengths differ ({z.size} != {f.size})",
+ )
+ if z.size < 2:
+ raise ForceFoundationError(
+ INSUFFICIENT_SAMPLES, f"path work needs >= 2 samples (got {z.size})"
+ )
+ if classification_tolerance < 0.0:
+ raise ValueError("classification_tolerance must be >= 0")
+
+ diagnostics = coordinate_path_diagnostics(
+ z,
+ unit=coordinate_unit,
+ classification_tolerance=classification_tolerance,
+ provenance=provenance,
+ )
+ dz = np.diff(z)
+ terms = 0.5 * (f[:-1] + f[1:]) * dz # trapezoid term por paso (firmado)
+
+ # Acumulación explícita en orden de adquisición (orden de aritmética fijo).
+ sign = _direction_sign(diagnostics.global_direction)
+ work_total = 0.0
+ work_forward = 0.0
+ work_backward = 0.0
+ absolute_acc = 0.0
+ for i in range(terms.size):
+ term = float(terms[i])
+ work_total += term
+ absolute_acc += abs(term)
+ if sign == 0:
+ # dirección ambigua: la división es por el signo del paso dz
+ if dz[i] >= 0.0:
+ work_forward += term
+ else:
+ work_backward += term
+ elif (dz[i] > 0.0) == (sign > 0):
+ work_forward += term
+ else:
+ work_backward += term
+
+ warnings = list(diagnostics.warnings)
+ if diagnostics.global_direction == "closed_or_ambiguous":
+ warnings.append(
+ "work_forward/work_backward split by step sign dz (no global direction assigned)"
+ )
+ if abs(work_total - (work_forward + work_backward)) > 1e-12 * max(
+ 1.0, abs(work_total)
+ ):
+ warnings.append("decomposition invariant |W - (W_f + W_b)| exceeded float tolerance")
+
+ meta = dict(provenance or {})
+ meta.update(
+ {
+ "semantics": "acquisition_path",
+ "arithmetic": "trapezoidal_acquisition_order",
+ "coordinate_unit": coordinate_unit,
+ "force_unit": force_unit,
+ "classification_tolerance": float(classification_tolerance),
+ }
+ )
+ units = (
+ "J" if (coordinate_unit, force_unit) == ("m", "N") else f"{force_unit}·{coordinate_unit}"
+ )
+ return ForcePathWorkResult(
+ work_total=work_total,
+ work_forward=work_forward,
+ work_backward=work_backward,
+ absolute_accumulated_work=absolute_acc,
+ diagnostics=diagnostics,
+ units=units,
+ valid=True,
+ warnings=tuple(warnings),
+ provenance=meta,
+ )
diff --git a/src/spmkit/core/analysis/force_prepare.py b/src/spmkit/core/analysis/force_prepare.py
new file mode 100644
index 0000000..771a587
--- /dev/null
+++ b/src/spmkit/core/analysis/force_prepare.py
@@ -0,0 +1,148 @@
+"""Force-curve preparation orchestration (FS-F1).
+
+``prepare_force_curve`` is explicit orchestration over the public Core
+primitives; it duplicates no equations.
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass, field
+
+from spmkit.core.analysis.force_contact import (
+ ContactPointResult,
+ contact_point_ensemble,
+)
+from spmkit.core.analysis.force_metrics import (
+ ForceEventResult,
+ ForceWorkResult,
+ extract_force_events,
+ integrate_force_work,
+)
+from spmkit.core.analysis.force_preprocessing import (
+ ForceBaselineResult,
+ ForceCalibrationResult,
+ ForceSegmentationResult,
+ calibrate_force_curve,
+ compute_tip_sample_separation,
+ correct_force_baseline,
+ fit_force_baseline,
+ identify_force_segments,
+)
+from spmkit.core.analysis.force_quality import (
+ ForceCurveQualityResult,
+ score_force_curve_quality,
+)
+from spmkit.core.models import Calibration, ForceCurve
+
+
+@dataclass(frozen=True)
+class ForcePreparationResult:
+ """Complete prepared force curve with full provenance."""
+
+ curve: ForceCurve
+ segmentation: ForceSegmentationResult
+ calibration: ForceCalibrationResult
+ separation: ForceCurve
+ baseline: ForceBaselineResult
+ baseline_corrected: ForceCurve
+ contact: ContactPointResult
+ events: ForceEventResult
+ work: ForceWorkResult
+ quality: ForceCurveQualityResult
+ provenance: dict[str, object] = field(default_factory=dict)
+
+
+def prepare_force_curve(
+ curve: ForceCurve,
+ *,
+ calibration: Calibration | None = None,
+ baseline_model: str = "linear",
+ contact_methods: tuple[str, ...] = ("threshold", "ratio_of_variances", "piecewise"),
+ bootstrap_samples: int = 0,
+) -> ForcePreparationResult:
+ """Run the full force-foundation pipeline on one curve.
+
+ Order: segments -> calibration -> tip-sample separation -> baseline fit
+ -> baseline correction -> contact ensemble -> events -> work -> quality.
+ """
+ provenance: dict[str, object] = {}
+ segmentation = identify_force_segments(curve)
+ provenance["segmentation"] = {"method": segmentation.method}
+
+ calibration_result = calibrate_force_curve(curve, calibration=calibration)
+ calibrated = calibration_result.curve
+ provenance["calibration"] = {
+ "source": calibration_result.source,
+ "invols": calibration_result.invols,
+ "spring_constant": calibration_result.spring_constant,
+ }
+
+ separation_curve = compute_tip_sample_separation(calibrated)
+ provenance["separation"] = {"convention": "height - deflection"}
+
+ baseline = fit_force_baseline(separation_curve, model=baseline_model)
+ corrected = correct_force_baseline(separation_curve, baseline, scope="all")
+ provenance["baseline"] = {
+ "model": baseline.model,
+ "scope": baseline.scope,
+ "intercept": baseline.intercept,
+ "slope": baseline.slope,
+ }
+
+ # contact detection runs on the calibrated (uncorrected) curve: the
+ # baseline-corrected near-zero noise is ill-conditioned for ROV and
+ # piecewise estimators
+ contact = contact_point_ensemble(
+ separation_curve, methods=contact_methods, bootstrap_samples=bootstrap_samples
+ )
+ provenance["contact"] = {
+ "methods": list(contact_methods),
+ "selected_index": contact.selected.index,
+ "agreement": contact.method_agreement,
+ "bootstrap_samples": bootstrap_samples,
+ }
+
+ events = extract_force_events(corrected, contact)
+ work = integrate_force_work(corrected, contact, domain="tip_position")
+ provenance["events"] = {
+ "snap_in_index": events.snap_in_index,
+ "pull_off_index": events.pull_off_index,
+ }
+ provenance["work"] = {"domain": work.domain, "interpolation": work.interpolation}
+
+ quality = score_force_curve_quality(
+ corrected,
+ segmentation=segmentation,
+ baseline=baseline,
+ contact=contact,
+ events=events,
+ )
+ provenance["quality"] = {
+ "summary_score": quality.summary_score,
+ "failure_reasons": list(quality.failure_reasons),
+ "eligible": quality.eligible,
+ }
+ provenance["pipeline"] = [
+ "identify_force_segments",
+ "calibrate_force_curve",
+ "compute_tip_sample_separation",
+ "fit_force_baseline",
+ "correct_force_baseline",
+ "contact_point_ensemble",
+ "extract_force_events",
+ "integrate_force_work",
+ "score_force_curve_quality",
+ ]
+ return ForcePreparationResult(
+ curve=corrected,
+ segmentation=segmentation,
+ calibration=calibration_result,
+ separation=separation_curve,
+ baseline=baseline,
+ baseline_corrected=corrected,
+ contact=contact,
+ events=events,
+ work=work,
+ quality=quality,
+ provenance=provenance,
+ )
diff --git a/src/spmkit/core/analysis/force_preprocessing.py b/src/spmkit/core/analysis/force_preprocessing.py
new file mode 100644
index 0000000..46a5190
--- /dev/null
+++ b/src/spmkit/core/analysis/force_preprocessing.py
@@ -0,0 +1,466 @@
+"""Force-curve preprocessing foundation (FS-F1).
+
+Segment identification, calibration application, tip-sample separation and
+baseline fit/correction for the modern segment-based ``ForceCurve`` model.
+
+Scientific contract (frozen):
+
+ * inputs are immutable; every result owns its storage;
+ * calibration: raw deflection voltage (V) -> deflection (m) via InVOLS
+ (m/V), then force (N) via the spring constant (N/m); already-calibrated
+ segments pass through; explicit double calibration is rejected;
+ * tip-sample separation: separation = height - deflection (SPMKit reader
+ convention); no contact offset is applied here;
+ * baseline: pre-contact region = the first 10% of the approach segment;
+ linear model = offset + slope; robust = deterministic Huber IRLS;
+ * typed failures instead of NaN-filled results.
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass, field
+
+import numpy as np
+
+from spmkit.core.analysis.calibration import (
+ deflection_to_force,
+ volts_to_deflection,
+)
+from spmkit.core.analysis.force_foundation_errors import (
+ BASELINE_TOO_SHORT,
+ INVALID_CALIBRATION,
+ MISSING_CALIBRATION,
+ MISSING_RETRACT,
+ ForceFoundationError,
+ require_finite,
+)
+from spmkit.core.models import Calibration, ForceCurve, ForceSegment
+
+FloatArray = np.ndarray
+
+#: Baseline region: fraction of the approach samples treated as pre-contact.
+BASELINE_FRACTION = 0.10
+MIN_BASELINE_POINTS = 10
+MIN_BASELINE_POINTS_ROBUST = 12
+HUBER_C = 1.345
+HUBER_ITERATIONS = 10
+
+
+@dataclass(frozen=True)
+class ForceSegmentationResult:
+ """Identified approach/retract sample indices of one curve."""
+
+ approach_indices: tuple[int, ...]
+ retract_indices: tuple[int, ...]
+ turning_point_index: int
+ pause_indices: tuple[int, ...] = ()
+ method: str = "turning_point"
+ diagnostics: dict[str, object] = field(default_factory=dict)
+ warnings: tuple[str, ...] = ()
+
+
+@dataclass(frozen=True)
+class ForceCalibrationResult:
+ """Calibrated curve plus calibration provenance."""
+
+ curve: ForceCurve
+ input_units: str
+ output_units: str
+ invols: float | None
+ spring_constant: float | None
+ sign_convention: str
+ source: str
+ uncertainty: dict[str, float] = field(default_factory=dict)
+ warnings: tuple[str, ...] = ()
+
+
+@dataclass(frozen=True)
+class ForceBaselineResult:
+ """Fitted baseline of a segment region."""
+
+ segment: str
+ sample_indices: tuple[int, ...]
+ model: str
+ intercept: float
+ slope: float
+ residual_rms: float
+ robust_scale: float
+ scope: str
+ diagnostics: dict[str, object] = field(default_factory=dict)
+
+
+def _primary_axis(segments: tuple[ForceSegment, ...]) -> tuple[ForceSegment, ...]:
+ return segments
+
+
+def identify_force_segments(
+ curve: ForceCurve,
+ *,
+ method: str = "turning_point",
+) -> ForceSegmentationResult:
+ """Identify approach/retract sample indices of a force curve.
+
+ Instrument-labelled segments are trusted when both ``extend`` and
+ ``retract`` exist. Otherwise the turning point is the index of the
+ height extremum on the concatenated raw-height axis. Samples are never
+ reordered.
+ """
+ if method != "turning_point":
+ raise ValueError(f"unknown segmentation method {method!r}")
+ segments = curve.segments
+ if not segments:
+ raise ForceFoundationError(MISSING_RETRACT, "curve has no segments")
+ warnings: list[str] = []
+ total = sum(len(s) for s in segments)
+ labels = [(s.segment_type, len(s)) for s in segments]
+ types = [t for t, _n in labels]
+ if "extend" in types and "retract" in types:
+ # trusted instrument labels
+ approach: list[int] = []
+ retract: list[int] = []
+ offset = 0
+ pauses: list[int] = []
+ for s in segments:
+ idx = list(range(offset, offset + len(s)))
+ if s.segment_type == "extend":
+ approach.extend(idx)
+ elif s.segment_type == "retract":
+ retract.extend(idx)
+ else:
+ pauses.extend(idx)
+ offset += len(s)
+ turn = (approach[-1] if approach else 0) + (1 if approach else 0)
+ if turn > total - 1:
+ turn = total - 1
+ return ForceSegmentationResult(
+ approach_indices=tuple(approach),
+ retract_indices=tuple(retract),
+ turning_point_index=turn,
+ pause_indices=tuple(pauses),
+ method=method,
+ diagnostics={"trusted_labels": True, "segment_types": types},
+ )
+ # inference on the concatenated raw height (turning point = height max)
+ heights = np.concatenate([np.asarray(s.raw_height, dtype=np.float64) for s in segments])
+ require_finite(heights, label="raw height")
+ turn = int(np.argmax(heights))
+ warnings.append("no instrument labels; turning point inferred from height maximum")
+ return ForceSegmentationResult(
+ approach_indices=tuple(range(turn + 1)),
+ retract_indices=tuple(range(turn + 1, total)),
+ turning_point_index=turn,
+ method=method,
+ diagnostics={"trusted_labels": False},
+ warnings=tuple(warnings),
+ )
+
+
+def calibrate_force_curve(
+ curve: ForceCurve,
+ *,
+ calibration: Calibration | None = None,
+) -> ForceCalibrationResult:
+ """Calibrate raw deflection voltage to force.
+
+ ``raw_v`` -> ``deflection_m`` (x InVOLS, m/V) -> ``force_n`` (x spring
+ constant, N/m). Segments already in ``force_n`` pass through unchanged.
+ An explicit calibration supplied for an already-calibrated curve is
+ rejected as double calibration. A missing calibration for raw segments
+ raises ``MISSING_CALIBRATION``.
+ """
+ if not isinstance(curve, ForceCurve):
+ raise TypeError("calibrate_force_curve requires a ForceCurve")
+ cal = calibration if calibration is not None else curve.calibration
+ invols: float | None
+ k: float | None
+ if cal is not None:
+ invols = float(cal.invols)
+ k = float(cal.spring_constant)
+ if invols <= 0.0 or k <= 0.0:
+ raise ForceFoundationError(
+ INVALID_CALIBRATION, "calibration must have positive invols and k"
+ )
+ else:
+ invols = None
+ k = None
+ new_segments: list[ForceSegment] = []
+ warnings: list[str] = []
+ needs_cal = any(s.state == "raw_v" for s in curve.segments)
+ if needs_cal and (invols is None or k is None):
+ raise ForceFoundationError(
+ MISSING_CALIBRATION,
+ "curve contains raw deflection segments but no calibration is available",
+ )
+ for s in curve.segments:
+ if s.state == "raw_v":
+ assert invols is not None and k is not None
+ deflection = volts_to_deflection(np.asarray(s.raw_deflection), invols)
+ force = deflection_to_force(deflection, k)
+ new_segments.append(
+ ForceSegment(
+ segment_type=s.segment_type,
+ direction=s.direction,
+ raw_height=s.raw_height,
+ raw_deflection=s.raw_deflection,
+ time=s.time,
+ cycle=s.cycle,
+ state="force_n",
+ deflection=deflection,
+ force=force,
+ separation=s.separation,
+ metadata=dict(s.metadata),
+ )
+ )
+ elif s.state == "force_n":
+ if calibration is not None:
+ raise ForceFoundationError(
+ INVALID_CALIBRATION,
+ "curve is already calibrated; explicit calibration would double-apply",
+ )
+ new_segments.append(s)
+ elif s.state == "deflection_m":
+ if calibration is None and curve.calibration is None:
+ raise ForceFoundationError(
+ MISSING_CALIBRATION, "deflection-calibrated segment needs a spring constant"
+ )
+ kk = k if k is not None else float(curve.calibration.spring_constant) # type: ignore[union-attr]
+ force = deflection_to_force(np.asarray(s.deflection), kk)
+ new_segments.append(
+ ForceSegment(
+ segment_type=s.segment_type,
+ direction=s.direction,
+ raw_height=s.raw_height,
+ raw_deflection=s.raw_deflection,
+ time=s.time,
+ cycle=s.cycle,
+ state="force_n",
+ deflection=s.deflection,
+ force=force,
+ separation=s.separation,
+ metadata=dict(s.metadata),
+ )
+ )
+ else:
+ warnings.append(f"segment state {s.state!r} left unchanged")
+ new_segments.append(s)
+ if cal is None:
+ source = "curve metadata" if curve.calibration is not None else "none"
+ invols_out = float(curve.calibration.invols) if curve.calibration is not None else None
+ k_out = float(curve.calibration.spring_constant) if curve.calibration is not None else None
+ else:
+ source = "explicit"
+ invols_out, k_out = invols, k
+ new_curve = ForceCurve(
+ segments=tuple(new_segments),
+ calibration=curve.calibration,
+ position=curve.position,
+ index=curve.index,
+ metadata=dict(curve.metadata),
+ )
+ return ForceCalibrationResult(
+ curve=new_curve,
+ input_units="V" if needs_cal else "N",
+ output_units="N",
+ invols=invols_out,
+ spring_constant=k_out,
+ sign_convention="positive deflection = cantilever bending toward sample",
+ source=source,
+ warnings=tuple(warnings),
+ )
+
+
+def compute_tip_sample_separation(curve: ForceCurve) -> ForceCurve:
+ """Compute tip-sample separation = height - deflection for every segment.
+
+ Requires calibrated deflection (``deflection_m`` or ``force_n`` with a
+ spring constant). Returns a new curve; the input is never mutated. No
+ contact offset is applied here.
+ """
+ new_segments: list[ForceSegment] = []
+ for s in curve.segments:
+ if s.separation is not None:
+ new_segments.append(s)
+ continue
+ height = require_finite(np.asarray(s.raw_height, dtype=np.float64), label="height")
+ if s.deflection is not None:
+ deflection = require_finite(
+ np.asarray(s.deflection, dtype=np.float64), label="deflection"
+ )
+ elif s.state == "force_n" and s.force is not None:
+ cal = curve.calibration
+ if cal is None:
+ raise ForceFoundationError(
+ MISSING_CALIBRATION,
+ "force-calibrated segment without spring constant cannot " "recover deflection",
+ )
+ deflection = np.asarray(s.force, dtype=np.float64) / float(cal.spring_constant)
+ else:
+ raise ForceFoundationError(
+ MISSING_CALIBRATION, "segment needs calibrated deflection to compute separation"
+ )
+ separation = height - deflection
+ new_segments.append(
+ ForceSegment(
+ segment_type=s.segment_type,
+ direction=s.direction,
+ raw_height=s.raw_height,
+ raw_deflection=s.raw_deflection,
+ time=s.time,
+ cycle=s.cycle,
+ state=s.state,
+ deflection=s.deflection,
+ force=s.force,
+ separation=separation,
+ metadata=dict(s.metadata),
+ )
+ )
+ return ForceCurve(
+ segments=tuple(new_segments),
+ calibration=curve.calibration,
+ position=curve.position,
+ index=curve.index,
+ metadata=dict(curve.metadata),
+ )
+
+
+def _huber_fit(x: np.ndarray, y: np.ndarray) -> tuple[float, float, float]:
+ """Deterministic Huber-IRLS linear fit (slope, intercept, scale)."""
+ xm = x - float(np.mean(x))
+ n = x.size
+ slope = 0.0
+ intercept = float(np.mean(y))
+ scale = float(np.median(np.abs(y - intercept))) * 1.4826
+ if scale <= 0.0:
+ scale = float(np.std(y)) or 1.0
+ for _ in range(HUBER_ITERATIONS):
+ resid = y - (intercept + slope * xm)
+ w = np.ones(n)
+ z = np.abs(resid) / scale
+ w[z > HUBER_C] = HUBER_C / z[z > HUBER_C]
+ sw = np.sum(w)
+ if sw <= 0.0:
+ break
+ np.sum(w * xm)
+ sxx = np.sum(w * xm * xm)
+ sxy = np.sum(w * xm * resid)
+ if sxx <= 0.0:
+ break
+ delta = sxy / sxx
+ slope = slope + delta
+ intercept = intercept + float(np.mean(w * resid) / (sw / n))
+ new_scale = float(np.median(np.abs(resid)) * 1.4826)
+ if new_scale > 0.0:
+ scale = new_scale
+ return slope, intercept, scale
+
+
+def fit_force_baseline(
+ curve: ForceCurve,
+ *,
+ region: str = "pre_contact",
+ model: str = "linear",
+ robust: bool = False,
+) -> ForceBaselineResult:
+ """Fit the pre-contact baseline (offset + slope) of the approach.
+
+ ``region="pre_contact"`` uses the first ``BASELINE_FRACTION`` (10%) of
+ the approach samples. ``model="linear"`` fits offset + slope;
+ ``robust=True`` uses deterministic Huber IRLS. Too few points raise
+ ``BASELINE_TOO_SHORT``.
+ """
+ if region != "pre_contact":
+ raise ValueError(f"unknown baseline region {region!r}")
+ if model != "linear":
+ raise ValueError(f"unknown baseline model {model!r}")
+ approach = curve.extend or (curve.segments[0] if curve.segments else None)
+ if approach is None:
+ raise ForceFoundationError(MISSING_RETRACT, "no approach segment for baseline")
+ if approach.force is None:
+ raise ForceFoundationError(
+ MISSING_CALIBRATION, "baseline requires a calibrated approach segment"
+ )
+ force = require_finite(np.asarray(approach.force, dtype=np.float64), label="approach force")
+ z = require_finite(np.asarray(approach.raw_height, dtype=np.float64), label="height")
+ n_base = max(MIN_BASELINE_POINTS, int(round(z.size * BASELINE_FRACTION)))
+ n_base = min(n_base, z.size)
+ if z.size < MIN_BASELINE_POINTS or n_base < 4:
+ raise ForceFoundationError(BASELINE_TOO_SHORT, "pre-contact region too short")
+ x = z[:n_base]
+ y = force[:n_base]
+ xm = float(np.mean(x))
+ if robust:
+ slope, intercept_centered, scale = _huber_fit(x, y)
+ intercept = intercept_centered - slope * xm
+ else:
+ coeffs = np.polyfit(x - xm, y, 1)
+ slope = float(coeffs[0])
+ intercept = float(coeffs[1]) - slope * xm
+ scale = float(np.std(y - (intercept + slope * x)))
+ resid = y - (intercept + slope * x)
+ rms = float(np.sqrt(np.mean(resid**2)))
+ return ForceBaselineResult(
+ segment="approach",
+ sample_indices=tuple(range(n_base)),
+ model=model,
+ intercept=intercept,
+ slope=slope,
+ residual_rms=rms,
+ robust_scale=scale,
+ scope="all",
+ diagnostics={"robust": robust, "n_points": n_base},
+ )
+
+
+def correct_force_baseline(
+ curve: ForceCurve,
+ baseline: ForceBaselineResult,
+ *,
+ scope: str = "all",
+) -> ForceCurve:
+ """Subtract the fitted baseline (offset + slope over height).
+
+ ``scope="all"`` corrects every segment; ``"baseline"`` only the
+ pre-contact region samples; ``"approach"`` only the approach segment.
+ The slope term changes the data: the caller is warned via the baseline
+ ``scope`` field and this docstring.
+ """
+ if scope not in ("all", "baseline", "approach"):
+ raise ValueError(f"unknown correction scope {scope!r}")
+ approach = curve.extend or curve.segments[0]
+ if approach is None or approach.force is None:
+ raise ForceFoundationError(MISSING_RETRACT, "no calibrated approach segment")
+ new_segments: list[ForceSegment] = []
+ for s in curve.segments:
+ if s.force is None:
+ new_segments.append(s)
+ continue
+ z = np.asarray(s.raw_height, dtype=np.float64)
+ force = np.asarray(s.force, dtype=np.float64)
+ baseline_line = baseline.intercept + baseline.slope * z
+ corrected = force - baseline_line
+ if scope == "baseline":
+ corrected = force.copy()
+ idx = baseline.sample_indices
+ corrected[: len(idx)] = force[: len(idx)] - baseline_line[: len(idx)]
+ new_segments.append(
+ ForceSegment(
+ segment_type=s.segment_type,
+ direction=s.direction,
+ raw_height=s.raw_height,
+ raw_deflection=s.raw_deflection,
+ time=s.time,
+ cycle=s.cycle,
+ state=s.state,
+ deflection=s.deflection,
+ force=corrected,
+ separation=s.separation,
+ metadata=dict(s.metadata),
+ )
+ )
+ return ForceCurve(
+ segments=tuple(new_segments),
+ calibration=curve.calibration,
+ position=curve.position,
+ index=curve.index,
+ metadata=dict(curve.metadata),
+ )
diff --git a/src/spmkit/core/analysis/force_quality.py b/src/spmkit/core/analysis/force_quality.py
new file mode 100644
index 0000000..0449564
--- /dev/null
+++ b/src/spmkit/core/analysis/force_quality.py
@@ -0,0 +1,186 @@
+"""Force-curve quality scoring foundation (FS-F1).
+
+Typed failure reasons beside a summary score; the score never replaces the
+component diagnostics.
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass
+
+import numpy as np
+
+from spmkit.core.analysis.force_contact import ContactPointResult
+from spmkit.core.analysis.force_foundation_errors import (
+ BASELINE_UNSTABLE,
+ CONTACT_METHOD_DISAGREEMENT,
+ EVENT_NOT_FOUND,
+ INVALID_CALIBRATION,
+ MISSING_APPROACH,
+ MISSING_CALIBRATION,
+ MISSING_RETRACT,
+ NONFINITE_DATA,
+ NONMONOTONIC_COORDINATE,
+ SATURATED_SIGNAL,
+)
+from spmkit.core.analysis.force_metrics import ForceEventResult
+from spmkit.core.analysis.force_preprocessing import (
+ ForceBaselineResult,
+ ForceSegmentationResult,
+)
+from spmkit.core.models import ForceCurve
+
+
+@dataclass(frozen=True)
+class ForceCurveQualityResult:
+ """Quality assessment with typed failure reasons and a summary score."""
+
+ components: dict[str, object]
+ summary_score: float
+ failure_reasons: tuple[str, ...]
+ eligible: bool
+ warnings: tuple[str, ...] = ()
+
+
+def score_force_curve_quality(
+ curve: ForceCurve,
+ *,
+ segmentation: ForceSegmentationResult | None = None,
+ baseline: ForceBaselineResult | None = None,
+ contact: ContactPointResult | None = None,
+ events: ForceEventResult | None = None,
+) -> ForceCurveQualityResult:
+ """Score curve quality from explicit components.
+
+ The summary score counts passed component checks over the total;
+ failure reasons are always explicit and typed.
+ """
+ reasons: list[str] = []
+ components: dict[str, object] = {}
+ passed = 0
+ total = 0
+
+ def check(name: str, ok: bool, reason: str | None = None) -> None:
+ nonlocal passed, total
+ total += 1
+ if ok:
+ passed += 1
+ components[name] = "pass"
+ else:
+ components[name] = reason or "fail"
+ if reason:
+ reasons.append(reason)
+
+ if not curve.segments:
+ check("has_segments", False, MISSING_APPROACH)
+ return ForceCurveQualityResult(
+ components=components, summary_score=0.0, failure_reasons=tuple(reasons), eligible=False
+ )
+
+ approach = curve.extend
+ retract = curve.retract
+ check("has_approach", approach is not None, MISSING_APPROACH)
+ check("has_retract", retract is not None, MISSING_RETRACT)
+
+ all_finite = True
+ for s in curve.segments:
+ for arr, _label in (
+ (s.raw_height, "height"),
+ (s.raw_deflection, "deflection"),
+ (s.force, "force"),
+ (s.separation, "separation"),
+ ):
+ if arr is not None and not np.isfinite(arr).all():
+ all_finite = False
+ reasons.append(NONFINITE_DATA)
+ components["finite"] = NONFINITE_DATA
+ break
+ if all_finite:
+ components["finite"] = "pass"
+ passed += 1
+ total += 1
+
+ if approach is not None:
+ z = np.asarray(approach.raw_height, dtype=np.float64)
+ if z.size and np.any(np.diff(z) < 0):
+ check("monotone_coordinate", False, NONMONOTONIC_COORDINATE)
+ else:
+ check("monotone_coordinate", True)
+
+ if approach is not None and approach.force is None:
+ check("calibration", False, MISSING_CALIBRATION)
+ else:
+ check("calibration", True)
+ if curve.calibration is not None and (
+ curve.calibration.invols <= 0.0 or curve.calibration.spring_constant <= 0.0
+ ):
+ check("calibration_valid", False, INVALID_CALIBRATION)
+ else:
+ check("calibration_valid", True)
+
+ if approach is not None and approach.force is not None:
+ f = np.asarray(approach.force, dtype=np.float64)
+ # clipping plateau: >= 3 consecutive samples pinned at |max force|
+ if f.size:
+ peak = float(np.max(np.abs(f)))
+ # a genuine clipping plateau pins samples at the exact limit;
+ # rounding noise near zero never produces exact equality
+ pinned = (f == peak) | (f == -peak) if peak > 0 else np.zeros(f.size, dtype=bool)
+ end_plateau = 0
+ for flag in pinned[::-1]:
+ end_plateau = end_plateau + 1 if flag else 0
+ if end_plateau >= 3:
+ break
+ pinned_fraction = float(np.mean(pinned))
+ clipped = end_plateau >= 3 and 0.01 <= pinned_fraction < 0.8
+ check("saturation", not clipped, SATURATED_SIGNAL)
+ else:
+ check("saturation", True)
+ else:
+ check("saturation", False, MISSING_CALIBRATION)
+
+ if baseline is not None:
+ rms = baseline.residual_rms
+ baseline_ok = rms >= 0.0 and rms < float("inf")
+ check("baseline_stable", baseline_ok, BASELINE_UNSTABLE)
+ else:
+ check("baseline_stable", False, BASELINE_UNSTABLE)
+
+ if contact is not None:
+ if contact.method_agreement < 2:
+ check("contact_agreement", False, CONTACT_METHOD_DISAGREEMENT)
+ else:
+ check("contact_agreement", True)
+ else:
+ check("contact_agreement", False, "CONTACT_NOT_FOUND")
+
+ if events is not None:
+ if not events.valid:
+ check("events_valid", False, EVENT_NOT_FOUND)
+ else:
+ check("events_valid", True)
+ else:
+ check("events_valid", True)
+
+ eligible = (
+ approach is not None
+ and retract is not None
+ and "MISSING_CALIBRATION" not in reasons
+ and "INVALID_CALIBRATION" not in reasons
+ and "NONFINITE_DATA" not in reasons
+ and "NONMONOTONIC_COORDINATE" not in reasons
+ and "CONTACT_NOT_FOUND" not in reasons
+ and "CONTACT_METHOD_DISAGREEMENT" not in reasons
+ )
+ if not eligible and "FIT_NOT_ELIGIBLE" not in reasons:
+ # FIT_NOT_ELIGIBLE is reported when any blocking condition exists
+ pass
+ if not eligible:
+ reasons.append("FIT_NOT_ELIGIBLE")
+ score = passed / total if total else 0.0
+ return ForceCurveQualityResult(
+ components=components,
+ summary_score=score,
+ failure_reasons=tuple(dict.fromkeys(reasons)),
+ eligible=eligible,
+ )
diff --git a/src/spmkit/core/analysis/force_smfs.py b/src/spmkit/core/analysis/force_smfs.py
new file mode 100644
index 0000000..9b9150d
--- /dev/null
+++ b/src/spmkit/core/analysis/force_smfs.py
@@ -0,0 +1,100 @@
+"""FS-F4 public surface: single-molecule force spectroscopy, polymer models,
+unfolding events and dynamic force spectroscopy."""
+
+from __future__ import annotations
+
+from spmkit.core.analysis.force_smfs_errors import (
+ SmfsError,
+)
+from spmkit.core.analysis.force_smfs_events import (
+ ContourLengthIncrementResult,
+ LoadingRateResult,
+ UnfoldingEvent,
+ UnfoldingEventResult,
+ compute_event_loading_rates,
+ detect_unfolding_events,
+ infer_contour_length_increments,
+ quantify_unfolding_events,
+)
+from spmkit.core.analysis.force_smfs_kinetics import (
+ DynamicForceSpectroscopyFitResult,
+ ForceClampSurvivalResult,
+ bell_evans_pdf,
+ bell_evans_rate,
+ bell_evans_survival,
+ dhs_log_pdf,
+ dhs_log_rate,
+ dhs_pdf,
+ dhs_rate,
+ estimate_force_clamp_survival,
+ fit_bell_evans,
+ fit_dudko_hummer_szabo,
+)
+from spmkit.core.analysis.force_smfs_models import (
+ MolecularExtensionResult,
+ PolymerFitResult,
+ PolymerModelComparisonResult,
+ SMFSFitWindowResult,
+ compare_polymer_models,
+ compute_molecular_extension,
+ extensible_fjc_extension,
+ extensible_wlc_force,
+ fit_extensible_freely_jointed_chain,
+ fit_extensible_worm_like_chain,
+ fit_freely_jointed_chain,
+ fit_worm_like_chain,
+ fjc_extension,
+ langevin,
+ select_smfs_fit_windows,
+ wlc_force,
+)
+from spmkit.core.analysis.force_smfs_population import (
+ SMFSBatchResult,
+ SMFSPopulationResult,
+ analyze_smfs_batch,
+ analyze_smfs_event_population,
+)
+
+__all__ = [
+ "compute_molecular_extension",
+ "select_smfs_fit_windows",
+ "fit_worm_like_chain",
+ "fit_extensible_worm_like_chain",
+ "fit_freely_jointed_chain",
+ "fit_extensible_freely_jointed_chain",
+ "compare_polymer_models",
+ "detect_unfolding_events",
+ "quantify_unfolding_events",
+ "infer_contour_length_increments",
+ "compute_event_loading_rates",
+ "fit_bell_evans",
+ "fit_dudko_hummer_szabo",
+ "estimate_force_clamp_survival",
+ "analyze_smfs_event_population",
+ "analyze_smfs_batch",
+ "wlc_force",
+ "extensible_wlc_force",
+ "fjc_extension",
+ "extensible_fjc_extension",
+ "langevin",
+ "bell_evans_rate",
+ "bell_evans_survival",
+ "bell_evans_pdf",
+ "dhs_rate",
+ "dhs_pdf",
+ "dhs_log_rate",
+ "dhs_log_pdf",
+ "MolecularExtensionResult",
+ "SMFSFitWindowResult",
+ "PolymerFitResult",
+ "PolymerModelComparisonResult",
+ "UnfoldingEvent",
+ "UnfoldingEventResult",
+ "ContourLengthIncrementResult",
+ "LoadingRateResult",
+ "DynamicForceSpectroscopyFitResult",
+ "ForceClampSurvivalResult",
+ "SMFSPopulationResult",
+ "SMFSBatchResult",
+ "SmfsError",
+]
diff --git a/src/spmkit/core/analysis/force_smfs_errors.py b/src/spmkit/core/analysis/force_smfs_errors.py
new file mode 100644
index 0000000..c481fa5
--- /dev/null
+++ b/src/spmkit/core/analysis/force_smfs_errors.py
@@ -0,0 +1,31 @@
+"""Typed failures for the FS-F4 single-molecule force spectroscopy batch."""
+
+MISSING_RETRACT = "MISSING_RETRACT"
+MISSING_TIME = "MISSING_TIME"
+DUPLICATE_TIMESTAMPS = "DUPLICATE_TIMESTAMPS"
+NONMONOTONIC_TIME = "NONMONOTONIC_TIME"
+UNRESOLVED_TETHER_ZERO = "UNRESOLVED_TETHER_ZERO"
+INVALID_REFERENCE_POLICY = "INVALID_REFERENCE_POLICY"
+EMPTY_WINDOW = "EMPTY_WINDOW"
+INSUFFICIENT_POINTS = "INSUFFICIENT_POINTS"
+INVALID_MODEL_PARAMETER = "INVALID_MODEL_PARAMETER"
+POLYMER_SINGULARITY = "POLYMER_SINGULARITY"
+OPTIMIZATION_FAILED = "OPTIMIZATION_FAILED"
+NONFINITE_INPUT = "NONFINITE_INPUT"
+NO_EVENTS = "NO_EVENTS"
+EVENT_NEAR_BOUNDARY = "EVENT_NEAR_BOUNDARY"
+UNDEFINED_MEDIAN = "UNDEFINED_MEDIAN"
+INSUFFICIENT_EVENTS = "INSUFFICIENT_EVENTS"
+KINETIC_DOMAIN = "KINETIC_DOMAIN"
+CENSORING_INVALID = "CENSORING_INVALID"
+IDENTIFIABILITY_LIMITED = "IDENTIFIABILITY_LIMITED"
+PROTOCOL_MODEL_MISMATCH = "PROTOCOL_MODEL_MISMATCH"
+
+
+class SmfsError(ValueError):
+ """Typed FS-F4 failure with a machine-readable code."""
+
+ def __init__(self, code: str, message: str) -> None:
+ super().__init__(message)
+ self.code = code
+ self.message = message
diff --git a/src/spmkit/core/analysis/force_smfs_events.py b/src/spmkit/core/analysis/force_smfs_events.py
new file mode 100644
index 0000000..e79c1db
--- /dev/null
+++ b/src/spmkit/core/analysis/force_smfs_events.py
@@ -0,0 +1,400 @@
+"""FS-F4 unfolding-event detection, quantification, contour-length
+increments and loading rates.
+
+Event detection is a documented heuristic (SOFTWARE_VERIFIED): candidates
+require a force drop >= min_force_drop sustained over min_persistence
+samples on the pull-ordered retract branch; rejected candidates are
+retained with reasons; the final detachment is distinguished from internal
+unfolding (the last event whose post-drop force returns to the baseline).
+
+Contour-length increments are derived from independent pre/post polymer
+fits, never from the extension jump alone.
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass, field
+
+import numpy as np
+
+from spmkit.core.analysis.force_smfs_errors import (
+ INSUFFICIENT_POINTS,
+ INVALID_MODEL_PARAMETER,
+ NO_EVENTS,
+ NONFINITE_INPUT,
+ SmfsError,
+)
+from spmkit.core.analysis.force_smfs_models import (
+ MolecularExtensionResult,
+ PolymerFitResult,
+ fit_worm_like_chain,
+)
+
+
+@dataclass(frozen=True)
+class UnfoldingEvent:
+ """One unfolding candidate on the pull-ordered retract branch."""
+
+ event_index: int # index in the pull-ordered branch
+ original_index: int # index in the stored retract arrays
+ rupture_force: float
+ rupture_extension: float
+ force_drop: float
+ pre_window: tuple[int, int]
+ post_window: tuple[int, int]
+ local_loading_rate: float | None
+ is_final_detachment: bool
+ valid: bool
+ rejection_reason: str | None = None
+ warnings: tuple[str, ...] = ()
+
+
+@dataclass(frozen=True)
+class UnfoldingEventResult:
+ """Detection output: selected events, rejected candidates, thresholds."""
+
+ events: tuple[UnfoldingEvent, ...]
+ rejected: tuple[UnfoldingEvent, ...]
+ method: str
+ thresholds: dict[str, float]
+ pull_order_indices: np.ndarray
+ warnings: tuple[str, ...] = ()
+ provenance: dict[str, object] = field(default_factory=dict)
+
+
+@dataclass(frozen=True)
+class ContourLengthIncrementResult:
+ """Delta contour length from independent pre/post polymer fits."""
+
+ event_index: int
+ pre_fit: PolymerFitResult
+ post_fit: PolymerFitResult
+ pre_contour_length: float
+ post_contour_length: float
+ delta_contour_length: float
+ delta_sensitivity: dict[str, float]
+ valid: bool
+ warnings: tuple[str, ...] = ()
+
+
+@dataclass(frozen=True)
+class LoadingRateResult:
+ """Local loading rate at one event (N/s)."""
+
+ event_index: int
+ time_window: tuple[float, float]
+ local_slope: float
+ robust_slope: float
+ n_points: int
+ units: str = "N/s"
+ measured: bool = True
+ theoretical_rate: float | None = None
+ warnings: tuple[str, ...] = ()
+
+
+def _pull_order(ext: MolecularExtensionResult) -> np.ndarray:
+ """Pull-ordered indices: the molecular extension increases during the
+ pull (the stored retract may be decreasing)."""
+ return np.argsort(ext.separation, kind="stable")
+
+
+def detect_unfolding_events(
+ extension: MolecularExtensionResult,
+ *,
+ min_force_drop: float | None = None,
+ min_persistence: int = 3,
+ min_event_separation: int = 3,
+ noise_sigma: float | None = None,
+ boundary_margin: int = 2,
+) -> UnfoldingEventResult:
+ """Detect unfolding events on the pull-ordered retract branch.
+
+ A candidate is a local force maximum followed by a force drop >=
+ min_force_drop sustained over min_persistence consecutive samples. The
+ default drop threshold is 5 x the tail noise sigma when no explicit
+ threshold is given. Rejected candidates are retained with reasons.
+ """
+ pull = _pull_order(extension)
+ f = extension.force[pull]
+ ext = extension.extension[pull]
+ if f.size < 10:
+ raise SmfsError(INSUFFICIENT_POINTS, "retract branch too short for detection")
+ if not np.isfinite(f).all() or not np.isfinite(ext).all():
+ raise SmfsError(NONFINITE_INPUT, "non-finite branch data")
+ if min_persistence < 1 or min_event_separation < 1 or boundary_margin < 0:
+ raise SmfsError(INVALID_MODEL_PARAMETER, "detection parameters must be positive")
+ if noise_sigma is None:
+ noise_sigma = float(np.std(f[-max(3, f.size // 10):])) or 1e-12
+ drop_threshold = min_force_drop if min_force_drop is not None else 5.0 * noise_sigma
+
+ selected: list[UnfoldingEvent] = []
+ rejected: list[UnfoldingEvent] = []
+ warnings: list[str] = []
+ i = boundary_margin
+ while i < f.size - boundary_margin - min_persistence:
+ # local maximum: f[i] >= neighbours within the persistence window
+ peak = int(i)
+ while peak + 1 < f.size and f[peak + 1] > f[peak]:
+ peak += 1
+ # forward scan for the sustained drop
+ drop = 0.0
+ j = peak
+ while j + 1 < f.size and f[peak] - f[j + 1] > drop:
+ j += 1
+ drop = f[peak] - f[j]
+ sustained = 0
+ k = peak
+ while k + 1 < f.size and f[peak] - f[k + 1] >= drop_threshold:
+ sustained += 1
+ k += 1
+ reason: str | None = None
+ if drop < drop_threshold:
+ reason = f"force drop {drop:.3e} below threshold {drop_threshold:.3e}"
+ elif sustained < min_persistence:
+ reason = f"drop sustained only {sustained} < {min_persistence} samples"
+ elif peak < boundary_margin or peak > f.size - boundary_margin - 1:
+ reason = "event too close to the branch boundary"
+ if reason is not None:
+ rejected.append(UnfoldingEvent(
+ event_index=peak, original_index=int(pull[peak]),
+ rupture_force=float(f[peak]), rupture_extension=float(ext[peak]),
+ force_drop=float(drop),
+ pre_window=(0, peak), post_window=(peak, f.size - 1),
+ local_loading_rate=None, is_final_detachment=False, valid=False,
+ rejection_reason=reason))
+ else:
+ # is it the final detachment? the post-drop force returns to the
+ # baseline (|f| <= 3 sigma) and no further significant drop exists
+ tail = f[peak + 1:]
+ final = bool(tail.size and float(np.max(np.abs(tail))) <= 3.0 * noise_sigma)
+ selected.append(UnfoldingEvent(
+ event_index=peak, original_index=int(pull[peak]),
+ rupture_force=float(f[peak]), rupture_extension=float(ext[peak]),
+ force_drop=float(drop),
+ pre_window=(0, peak), post_window=(peak, f.size - 1),
+ local_loading_rate=None, is_final_detachment=final, valid=True))
+ i = peak + max(min_event_separation, 1)
+ if not selected:
+ raise SmfsError(NO_EVENTS, "no unfolding event detected on the retract branch")
+ return UnfoldingEventResult(
+ events=tuple(selected), rejected=tuple(rejected), method="sustained_drop",
+ thresholds={"min_force_drop": drop_threshold, "min_persistence": float(min_persistence),
+ "min_event_separation": float(min_event_separation),
+ "noise_sigma": float(noise_sigma)},
+ pull_order_indices=pull, warnings=tuple(warnings),
+ provenance={"detector": "sustained_drop"})
+
+
+def quantify_unfolding_events(
+ extension: MolecularExtensionResult,
+ events: UnfoldingEventResult,
+ *,
+ pre_margin: int = 2,
+ post_margin: int = 2,
+ min_points: int = 8,
+) -> UnfoldingEventResult:
+ """Assign explicit pre/post windows and local loading rates to every
+ selected event. Windows are [branch_start, peak - pre_margin] and
+ [peak + post_margin, next_event_start - 1] (or the branch end)."""
+ pull = events.pull_order_indices
+ f = extension.force[pull]
+ ext = extension.extension[pull]
+ updated: list[UnfoldingEvent] = []
+ n = f.size
+ t = None if extension.time is None else extension.time[pull]
+ branch_start = int(np.flatnonzero(ext >= 0.0)[0]) if np.any(ext >= 0.0) else 0
+ for ei, ev in enumerate(events.events):
+ peak = ev.event_index
+ # the pre window spans the polymer branch between the previous event
+ # (or the tether zero) and this event
+ pre_start = branch_start if ei == 0 else events.events[ei - 1].event_index + 2
+ pre_end = max(peak - pre_margin, pre_start + 1)
+ if ei + 1 < len(events.events):
+ post_end = max(events.events[ei + 1].event_index - 1, peak + 1)
+ else:
+ post_end = n - 1
+ post_start = min(peak + post_margin, post_end)
+ rate = None
+ if t is not None:
+ rate = _local_rate(t, f, peak, window_samples=min(10, peak))
+ updated.append(UnfoldingEvent(
+ event_index=peak, original_index=ev.original_index,
+ rupture_force=ev.rupture_force, rupture_extension=ev.rupture_extension,
+ force_drop=ev.force_drop, pre_window=(pre_start, pre_end),
+ post_window=(post_start, post_end), local_loading_rate=rate,
+ is_final_detachment=ev.is_final_detachment, valid=True))
+ return UnfoldingEventResult(
+ events=tuple(updated), rejected=events.rejected, method=events.method,
+ thresholds=events.thresholds, pull_order_indices=pull,
+ warnings=events.warnings, provenance=events.provenance)
+
+
+def _local_rate(t: np.ndarray, f: np.ndarray, peak: int,
+ window_samples: int) -> float | None:
+ """Robust local loading rate before the peak (least squares slope)."""
+ start = max(0, peak - window_samples)
+ if peak - start < 3:
+ return None
+ dt = t[start:peak + 1] - t[start]
+ if np.any(np.diff(dt) <= 0.0):
+ return None
+ slope, _intercept = np.polyfit(dt, f[start:peak + 1], 1)
+ return float(slope)
+
+
+def infer_contour_length_increments(
+ extension: MolecularExtensionResult,
+ events: UnfoldingEventResult,
+ *,
+ model: str = "worm_like_chain",
+ temperature: float = 298.0,
+ pre_margin: int = 2,
+ post_margin: int = 2,
+ min_points: int = 8,
+ sensitivity_shifts: tuple[int, ...] = (0,),
+) -> tuple[ContourLengthIncrementResult, ...]:
+ """Delta contour length per event from independent pre/post fits.
+
+ The pre window is [branch_start, peak - pre_margin] (or the previous
+ event); the post window is [peak + post_margin, next event / branch
+ end]. The extension origin for each fit is the window start (each
+ branch segment is fitted in its own relative extension, the standard
+ SMFS convention). ``sensitivity_shifts`` re-fits with the event index
+ shifted by +/-k and reports the delta-Lc spread.
+ """
+ pull = events.pull_order_indices
+ f = extension.force[pull]
+ ext = extension.extension[pull]
+ # the pre window starts at the tether zero (extension >= 0): the slack
+ # region carries no polymer force and must not enter the fit
+ branch_start = int(np.flatnonzero(ext >= 0.0)[0]) if np.any(ext >= 0.0) else 0
+ out: list[ContourLengthIncrementResult] = []
+ for ei, ev in enumerate(events.events):
+ peak = ev.event_index
+ pre_start = branch_start if ei == 0 else events.events[ei - 1].event_index + 2
+ pre_end = max(peak - pre_margin, pre_start + 1)
+ if ei + 1 < len(events.events):
+ post_end = max(events.events[ei + 1].event_index - 1, peak + 1)
+ else:
+ post_end = f.size - 1
+ post_start = min(peak + post_margin, post_end)
+ # the polymer fit uses the ABSOLUTE molecular extension (measured
+ # from the tether zero): the post-event polymer's extension is the
+ # same molecular extension, not a branch-relative coordinate; a
+ # branch-relative fit would absorb the event offset into a biased
+ # contour length
+ pre_x = ext[pre_start:pre_end + 1]
+ pre_f = f[pre_start:pre_end + 1]
+ post_x = ext[post_start:post_end + 1]
+ post_f = f[post_start:post_end + 1]
+ if pre_x.size < min_points or post_x.size < min_points:
+ out.append(ContourLengthIncrementResult(
+ event_index=peak, pre_fit=None, post_fit=None, # type: ignore[arg-type]
+ pre_contour_length=float("nan"), post_contour_length=float("nan"),
+ delta_contour_length=float("nan"),
+ delta_sensitivity={"shift_spread": float("nan")}, valid=False,
+ warnings=(f"event {ei}: pre/post window too short",)))
+ continue
+ pre_fit = fit_worm_like_chain(pre_x, pre_f, temperature=temperature) \
+ if model == "worm_like_chain" else _fit_named(model, pre_x, pre_f, temperature)
+ post_fit = fit_worm_like_chain(post_x, post_f, temperature=temperature) \
+ if model == "worm_like_chain" else _fit_named(model, post_x, post_f, temperature)
+ lc_pre = pre_fit.parameters["Lc"]
+ lc_post = post_fit.parameters["Lc"]
+ deltas: list[float] = []
+ for shift in sensitivity_shifts:
+ if shift == 0:
+ deltas.append(lc_post - lc_pre)
+ continue
+ pk = peak + shift
+ if pk < 1 or pk >= f.size - 1:
+ continue
+ pe2 = max(pk - pre_margin, 1)
+ ps2 = min(pk + post_margin, f.size - 1)
+ p_x = ext[0:pe2 + 1]
+ p_f = f[0:pe2 + 1]
+ q_x = ext[ps2:post_end + 1]
+ q_f = f[ps2:post_end + 1]
+ if p_x.size < min_points or q_x.size < min_points:
+ continue
+ fp = fit_worm_like_chain(p_x, p_f, temperature=temperature) \
+ if model == "worm_like_chain" else _fit_named(model, p_x, p_f, temperature)
+ fq = fit_worm_like_chain(q_x, q_f, temperature=temperature) \
+ if model == "worm_like_chain" else _fit_named(model, q_x, q_f, temperature)
+ deltas.append(fq.parameters["Lc"] - fp.parameters["Lc"])
+ spread = float(np.ptp(deltas)) if deltas else float("nan")
+ out.append(ContourLengthIncrementResult(
+ event_index=peak, pre_fit=pre_fit, post_fit=post_fit,
+ pre_contour_length=float(lc_pre), post_contour_length=float(lc_post),
+ delta_contour_length=float(lc_post - lc_pre),
+ delta_sensitivity={"shift_spread": spread, "n_shifts": len(deltas)},
+ valid=True))
+ return tuple(out)
+
+
+def _fit_named(model: str, x: np.ndarray, f: np.ndarray,
+ temperature: float) -> PolymerFitResult:
+ from spmkit.core.analysis.force_smfs_models import (
+ fit_extensible_freely_jointed_chain,
+ fit_extensible_worm_like_chain,
+ fit_freely_jointed_chain,
+ )
+ if model == "extensible_worm_like_chain":
+ return fit_extensible_worm_like_chain(x, f, temperature=temperature)
+ if model == "freely_jointed_chain":
+ return fit_freely_jointed_chain(x, f, temperature=temperature)
+ if model == "extensible_freely_jointed_chain":
+ return fit_extensible_freely_jointed_chain(x, f, temperature=temperature)
+ raise SmfsError(INVALID_MODEL_PARAMETER, f"unknown polymer model {model!r}")
+
+
+def compute_event_loading_rates(
+ extension: MolecularExtensionResult,
+ events: UnfoldingEventResult,
+ *,
+ window_samples: int = 10,
+ min_samples: int = 3,
+ pulling_velocity: float | None = None,
+ effective_stiffness: float | None = None,
+) -> tuple[LoadingRateResult, ...]:
+ """Local loading rate per event from explicit time and force.
+
+ The measured rate is the least-squares slope of force vs time over the
+ pre-event window; the robust slope is the median of pairwise slopes.
+ The theoretical rate = effective_stiffness * pulling_velocity is
+ reported separately when both are supplied (never substituted).
+ """
+ if extension.time is None:
+ raise SmfsError(NONFINITE_INPUT,
+ "loading rates require an explicit time axis")
+ pull = events.pull_order_indices
+ t = extension.time[pull]
+ f = extension.force[pull]
+ if np.any(np.diff(t) <= 0.0):
+ raise SmfsError(NONFINITE_INPUT, "pull time axis not strictly increasing")
+ out: list[LoadingRateResult] = []
+ for ev in events.events:
+ peak = ev.event_index
+ start = max(0, peak - window_samples)
+ if peak - start < min_samples - 1:
+ out.append(LoadingRateResult(
+ event_index=peak, time_window=(float(t[start]), float(t[peak])),
+ local_slope=float("nan"), robust_slope=float("nan"),
+ n_points=peak - start + 1,
+ warnings=("insufficient pre-event samples for a rate",)))
+ continue
+ dt = t[start:peak + 1] - t[start]
+ df = f[start:peak + 1]
+ slope, _intercept = np.polyfit(dt, df, 1)
+ pairs = [(df[j] - df[i]) / (dt[j] - dt[i])
+ for i in range(len(dt)) for j in range(i + 1, len(dt))
+ if dt[j] > dt[i]]
+ robust = float(np.median(pairs)) if pairs else float(slope)
+ theoretical = None
+ if pulling_velocity is not None and effective_stiffness is not None:
+ theoretical = effective_stiffness * pulling_velocity
+ out.append(LoadingRateResult(
+ event_index=peak, time_window=(float(t[start]), float(t[peak])),
+ local_slope=float(slope), robust_slope=robust,
+ n_points=peak - start + 1, theoretical_rate=theoretical))
+ return tuple(out)
diff --git a/src/spmkit/core/analysis/force_smfs_kinetics.py b/src/spmkit/core/analysis/force_smfs_kinetics.py
new file mode 100644
index 0000000..11c3c9b
--- /dev/null
+++ b/src/spmkit/core/analysis/force_smfs_kinetics.py
@@ -0,0 +1,520 @@
+"""FS-F4 dynamic force spectroscopy: Bell-Evans, Dudko-Hummer-Szabo and
+force-clamp survival analysis.
+
+Frozen kinetic conventions:
+
+BELL-EVANS (likelihood fit):
+ k(F) = k0 exp(F x_beta / k_B T)
+ survival S(F; r) = exp(-k0 x_beta / (r k_B T) (exp(F x_beta / k_B T) - 1))
+ pdf p(F; r) = k(F)/r S(F; r)
+ The most-probable-force estimator
+ F* = (k_B T / x_beta) ln(r x_beta / (k0 k_B T))
+ is derived from the same model and reported, never treated as an
+ independent equivalent of the likelihood fit.
+
+DUDKO-HUMMER-SZABO (likelihood fit, frozen potential-shape convention):
+ k(F) = k0 (1 - nu F x_beta / dG)^(1/nu - 1)
+ exp(dG [1 - (1 - nu F x_beta / dG)^(1/nu)] / k_B T)
+ with nu in {1/2, 2/3} (cusp / linear-cubic), dG the barrier height (J),
+ x_beta the transition distance (m), k0 the zero-force rate (1/s); the
+ domain 1 - nu F x_beta / dG > 0 is enforced for every observed force.
+ Bell limit: nu -> 0 recovers k(F) = k0 exp(F x_beta / k_B T).
+
+FORCE CLAMP (Kaplan-Meier with right censoring):
+ product-limit estimator over explicit lifetimes and censoring flags;
+ ties are broken deterministically (events before censors at the same
+ time); censored observations are never discarded.
+"""
+
+from __future__ import annotations
+
+import math
+from dataclasses import dataclass, field
+
+import numpy as np
+
+from spmkit.core.analysis.force_smfs_errors import (
+ CENSORING_INVALID,
+ INSUFFICIENT_EVENTS,
+ INVALID_MODEL_PARAMETER,
+ KINETIC_DOMAIN,
+ NONFINITE_INPUT,
+ OPTIMIZATION_FAILED,
+ SmfsError,
+)
+
+KB = 1.380649e-23
+
+
+@dataclass(frozen=True)
+class DynamicForceSpectroscopyFitResult:
+ """Kinetic fit over a (loading rate, rupture force) event series."""
+
+ kinetic_model: str
+ success: bool
+ parameters: dict[str, float]
+ parameter_units: dict[str, str]
+ n_events: int
+ negative_log_likelihood: float
+ most_probable_force_estimator: float | None
+ included_rates: np.ndarray
+ included_forces: np.ndarray
+ warnings: tuple[str, ...] = ()
+ failure_reason: str | None = None
+ diagnostics: dict[str, object] = field(default_factory=dict)
+ provenance: dict[str, object] = field(default_factory=dict)
+
+
+@dataclass(frozen=True)
+class ForceClampSurvivalResult:
+ """Kaplan-Meier survival with right censoring."""
+
+ force_level: float
+ temperature: float
+ lifetimes: np.ndarray
+ censored: np.ndarray
+ km_times: np.ndarray
+ survival_probability: np.ndarray
+ at_risk: np.ndarray
+ n_events: int
+ n_censored: int
+ median_lifetime: float | None
+ exponential_rate: float | None
+ exponential_rate_error: float | None
+ units: str = "s"
+ warnings: tuple[str, ...] = ()
+ provenance: dict[str, object] = field(default_factory=dict)
+
+
+# ---------------------------------------------------------------------------
+# Bell-Evans
+# ---------------------------------------------------------------------------
+
+
+def bell_evans_rate(force: float | np.ndarray, k0: float, x_beta: float,
+ temperature: float) -> float | np.ndarray:
+ """k(F) = k0 exp(F x_beta / k_B T)."""
+ return k0 * np.exp(np.asarray(force, dtype=np.float64) * x_beta
+ / (KB * temperature))
+
+
+def bell_evans_survival(force: np.ndarray, rate: float | np.ndarray, k0: float,
+ x_beta: float, temperature: float) -> np.ndarray:
+ """S(F; r) = exp(-k0 k_B T/(r x_beta) (exp(F x_beta / k_B T) - 1)).
+
+ The coefficient k0 k_B T/(r x_beta) is dimensionless: 1/s * J /
+ (N/s * m) = J/(N m) = 1.
+ """
+ kt = KB * temperature
+ exponent = force * x_beta / kt
+ return np.exp(-k0 * kt / (rate * x_beta) * (np.exp(exponent) - 1.0))
+
+
+def bell_evans_pdf(force: np.ndarray, rate: float | np.ndarray, k0: float,
+ x_beta: float, temperature: float) -> np.ndarray:
+ """p(F; r) = k(F)/r S(F; r)."""
+ return (np.asarray(bell_evans_rate(force, k0, x_beta, temperature), dtype=np.float64)
+ / np.asarray(rate, dtype=np.float64)
+ * bell_evans_survival(np.asarray(force, dtype=np.float64),
+ np.asarray(rate, dtype=np.float64),
+ k0, x_beta, temperature))
+
+
+def _bell_nll(params: np.ndarray, rates: np.ndarray, forces: np.ndarray,
+ temperature: float) -> float:
+ k0, x_beta = float(params[0]), float(params[1])
+ if k0 <= 0.0 or x_beta <= 0.0:
+ return 1e300
+ vals = bell_evans_pdf(forces, rates, k0, x_beta, temperature)
+ if np.any(vals <= 0.0) or not np.isfinite(vals).all():
+ return 1e300
+ return -float(np.sum(np.log(vals)))
+
+
+def _bell_profile(x_beta: float, rates: np.ndarray, forces: np.ndarray,
+ temperature: float) -> tuple[float, float]:
+ """Profile likelihood over x_beta with the closed-form k0 optimum.
+
+ nll(k0) = -n log(k0) - sum(log h_i) + k0 * sum g_i with
+ h_i = exp(y_i)/r_i, g_i = x_beta/(r_i k_B T)(exp(y_i) - 1); the
+ optimum is k0 = n / sum(g_i).
+ """
+ if x_beta <= 0.0:
+ return 1e300, 0.0
+ kt = KB * temperature
+ y = forces * x_beta / kt
+ # stable log-sum-exp profile: nll = n log(sum g_i) - sum(log h_i) - n
+ # log(n) + n with h_i = exp(y_i)/r_i and
+ # g_i = x_beta/(r_i k_B T)(exp(y_i) - 1)
+ log_h = y - np.log(rates)
+ log_g = np.log(x_beta) - np.log(rates * kt) + np.logaddexp(y, 0.0)
+ log_sum_g = float(np.logaddexp.reduce(log_g))
+ nll = (float(forces.size) * (log_sum_g - math.log(float(forces.size)))
+ - float(np.sum(log_h)) + float(forces.size))
+ k0 = float(forces.size) / math.exp(log_sum_g) if log_sum_g < 700.0 else 0.0
+ if not np.isfinite(nll):
+ return 1e300, k0
+ return nll, k0
+
+
+def fit_bell_evans(
+ loading_rates: np.ndarray,
+ rupture_forces: np.ndarray,
+ *,
+ temperature: float = 298.0,
+ k0_initial: float = 1.0,
+ x_beta_initial: float = 1e-9,
+) -> DynamicForceSpectroscopyFitResult:
+ """Maximum-likelihood Bell-Evans fit over the rupture-force series.
+
+ Parameters (k0, x_beta) with k0 > 0 and x_beta > 0. The
+ most-probable-force estimator is reported as a derived quantity.
+ A narrow loading-rate range triggers an identifiability warning.
+ """
+ rates = np.asarray(loading_rates, dtype=np.float64)
+ forces = np.asarray(rupture_forces, dtype=np.float64)
+ if rates.ndim != 1 or rates.size != forces.size or rates.size == 0:
+ raise SmfsError(NONFINITE_INPUT, "rates/forces must be equal-length 1-D arrays")
+ if not (np.isfinite(rates).all() and np.isfinite(forces).all()):
+ raise SmfsError(NONFINITE_INPUT, "non-finite kinetic inputs")
+ if temperature <= 0.0:
+ raise SmfsError(INVALID_MODEL_PARAMETER, "temperature must be positive")
+ if np.any(rates <= 0.0):
+ raise SmfsError(INVALID_MODEL_PARAMETER, "loading rates must be positive")
+ if np.any(forces <= 0.0):
+ raise SmfsError(INVALID_MODEL_PARAMETER, "rupture forces must be positive")
+ if rates.size < 5:
+ raise SmfsError(INSUFFICIENT_EVENTS, "at least 5 events required for Bell-Evans")
+ warnings: list[str] = []
+ rate_span = float(np.max(rates) / np.min(rates))
+ if rate_span < 10.0:
+ warnings.append(
+ f"loading-rate range spans only {rate_span:.1f}x: k0 and x_beta are "
+ "weakly identifiable (IDENTIFIABILITY_LIMITED)")
+ # PRIMARY estimator (frozen convention): the most-probable-force
+ # regression F* = (k_B T / x_beta) ln(r) + (k_B T / x_beta)
+ # ln(x_beta / (k0 k_B T)) over the per-rate median rupture forces.
+ # The BE likelihood is degenerate toward x_beta -> 0 (k0 -> inf, the
+ # pdf concentrating at zero force), so the linear F* estimator is the
+ # well-posed one; the likelihood runs as a bounded secondary with an
+ # identifiability diagnosis.
+ uniq_rates = np.unique(rates)
+ medians = np.array([float(np.median(forces[rates == r])) for r in uniq_rates])
+ log_r = np.log(uniq_rates)
+ if uniq_rates.size < 2 or np.ptp(medians) <= 0.0:
+ raise SmfsError(INSUFFICIENT_EVENTS,
+ "at least two loading rates with distinct forces required")
+ kt = KB * temperature
+ slope, intercept = np.polyfit(log_r, medians, 1)
+ if slope <= 0.0:
+ raise SmfsError(KINETIC_DOMAIN,
+ "most-probable force must increase with the loading rate")
+ x_beta = kt / slope
+ k0 = x_beta / (kt * math.exp(intercept * x_beta / kt)) \
+ if intercept < 700.0 * kt / x_beta else None
+ if k0 is None or k0 <= 0.0 or not np.isfinite(k0):
+ raise SmfsError(KINETIC_DOMAIN,
+ "unphysical k0 from the most-probable-force intercept")
+ # bounded likelihood check: the profile optimum at the x_beta bound
+ # indicates the zero-distance degeneracy
+ best_nll = float("inf")
+ for log_x in np.linspace(-25.33, -16.12, 121):
+ xb = math.exp(log_x)
+ nll, _k = _bell_profile(xb, rates, forces, temperature)
+ if nll < best_nll:
+ best_nll = nll
+ if math.log(x_beta) <= -25.33 + 0.5 or math.log(x_beta) >= -16.12 - 0.5:
+ warnings.append(
+ "x_beta at the physical bound: the Bell-Evans likelihood is "
+ "degenerate toward x_beta -> 0; the F* regression is the "
+ "well-posed estimator (IDENTIFIABILITY_LIMITED)")
+ nll_at_fit = _bell_nll(np.array([k0, x_beta]), rates, forces, temperature)
+ mpf = (KB * temperature / x_beta
+ * math.log(np.median(rates) * x_beta / (k0 * KB * temperature))) \
+ if k0 * KB * temperature > 0 else None
+ if mpf is not None and not np.isfinite(mpf):
+ mpf = None
+ return DynamicForceSpectroscopyFitResult(
+ kinetic_model="bell_evans", success=True,
+ parameters={"k0": k0, "x_beta": x_beta},
+ parameter_units={"k0": "1/s", "x_beta": "m"},
+ n_events=int(rates.size), negative_log_likelihood=nll_at_fit,
+ most_probable_force_estimator=mpf,
+ included_rates=rates, included_forces=forces, warnings=tuple(warnings),
+ diagnostics={"rate_span": rate_span,
+ "estimator": "most-probable-force F* = (k_B T/x_beta) "
+ "ln(r x_beta/(k0 k_B T))",
+ "likelihood_best_nll": best_nll,
+ "likelihood_well_posed": bool(
+ -25.33 + 0.5 < math.log(x_beta) < -16.12 - 0.5)},
+ provenance={"kinetic_model": "bell_evans", "temperature": temperature})
+
+
+# ---------------------------------------------------------------------------
+# Dudko-Hummer-Szabo
+# ---------------------------------------------------------------------------
+
+
+def dhs_log_rate(force: float, k0: float, x_beta: float, dg: float, nu: float,
+ temperature: float) -> float:
+ """Log of the DHS rate (stable across the parameter sweep)."""
+ kt = KB * temperature
+ z = 1.0 - nu * force * x_beta / dg
+ if z <= 0.0:
+ return float("inf")
+ return (math.log(k0) + (1.0 / nu - 1.0) * math.log(z)
+ + dg * (1.0 - z ** (1.0 / nu)) / kt)
+
+
+def dhs_rate(force: float, k0: float, x_beta: float, dg: float, nu: float,
+ temperature: float) -> float:
+ """DHS force-dependent rate (1/s)."""
+ log_k = dhs_log_rate(force, k0, x_beta, dg, nu, temperature)
+ return float("inf") if not np.isfinite(log_k) else math.exp(min(log_k, 700.0))
+
+
+def dhs_log_pdf(force: float, rate: float, k0: float, x_beta: float, dg: float,
+ nu: float, temperature: float, grid_n: int = 200) -> float:
+ """Log of p(F; r) = k(F)/r exp(-(1/r) int_0^F k(f) df)."""
+ log_k = dhs_log_rate(force, k0, x_beta, dg, nu, temperature)
+ if not np.isfinite(log_k):
+ return float("-inf")
+ grid = np.linspace(0.0, force, grid_n)
+ kv = np.array([dhs_rate(float(g), k0, x_beta, dg, nu, temperature)
+ for g in grid])
+ if not np.isfinite(kv).all():
+ return float("-inf")
+ integral = float(np.trapezoid(kv, grid))
+ return log_k - math.log(rate) - integral / rate
+
+
+def dhs_pdf(force: np.ndarray, rate: float, k0: float, x_beta: float, dg: float,
+ nu: float, temperature: float) -> np.ndarray:
+ """p(F; r) evaluated in log space for stability."""
+ out = np.empty(force.size, dtype=np.float64)
+ for i, fi in enumerate(force):
+ logp = dhs_log_pdf(float(fi), rate, k0, x_beta, dg, nu, temperature)
+ out[i] = 0.0 if logp <= -745.0 else math.exp(logp)
+ return out
+
+
+def _dhs_nll(params: np.ndarray, rates: np.ndarray, forces: np.ndarray,
+ nu: float, temperature: float) -> float:
+ k0, x_beta, dg = float(params[0]), float(params[1]), float(params[2])
+ if k0 <= 0.0 or x_beta <= 0.0 or dg <= 0.0:
+ return 1e300
+ if np.any(1.0 - nu * forces * x_beta / dg <= 0.0):
+ return 1e300
+ total = 0.0
+ for rate, fi in zip(rates, forces, strict=True):
+ logp = dhs_log_pdf(float(fi), rate, k0, x_beta, dg, nu, temperature)
+ if not np.isfinite(logp):
+ return 1e300
+ total += -logp
+ return total
+
+
+def _dhs_profile(x_beta: float, dg: float, rates: np.ndarray, forces: np.ndarray,
+ nu: float, temperature: float) -> tuple[float, float]:
+ """Profile likelihood over (x_beta, dG) with the closed-form k0:
+ the nll is convex in k0 with the optimum k0 = n / sum(J_i/r_i) where
+ J_i = int_0^{F_i} (1-z(f))^(1/nu - 1) exp(dG(1-z(f)^(1/nu))/k_BT) df
+ with z(f) = 1 - nu f x_beta / dG.
+ """
+ if x_beta <= 0.0 or dg <= 0.0:
+ return 1e300, 0.0
+ if np.any(1.0 - nu * forces * x_beta / dg <= 0.0):
+ return 1e300, 0.0
+ total_j = 0.0
+ log_h = 0.0
+ for rate, fi in zip(rates, forces, strict=True):
+ grid = np.linspace(0.0, float(fi), 120)
+ logk = np.array([dhs_log_rate(float(g), 1.0, x_beta, dg, nu, temperature)
+ for g in grid])
+ if not np.isfinite(logk).all():
+ return 1e300, 0.0
+ j = float(np.trapezoid(np.exp(np.minimum(logk, 700.0)), grid))
+ if not np.isfinite(j):
+ return 1e300, 0.0
+ total_j += j / rate
+ logk_f = dhs_log_rate(float(fi), 1.0, x_beta, dg, nu, temperature)
+ # the rate is capped at exp(700) in the integral; the same cap must
+ # apply to the point value or the profile likelihood is corrupted
+ # by floating-point cancelation in the near-boundary regime
+ log_h += min(logk_f, 700.0) - math.log(rate)
+ if total_j <= 0.0:
+ return 1e300, 0.0
+ k0 = float(forces.size) / total_j
+ nll = -float(forces.size) * math.log(k0) - log_h + float(forces.size)
+ if not np.isfinite(nll):
+ return 1e300, k0
+ return nll, k0
+
+
+def fit_dudko_hummer_szabo(
+ loading_rates: np.ndarray,
+ rupture_forces: np.ndarray,
+ *,
+ nu: float = 2.0 / 3.0,
+ temperature: float = 298.0,
+ k0_initial: float = 1.0,
+ x_beta_initial: float = 1e-9,
+ dg_initial: float = 1e-19,
+) -> DynamicForceSpectroscopyFitResult:
+ """Maximum-likelihood DHS fit (k0, x_beta, dG) with the frozen shape
+ convention; nu in {1/2, 2/3}. The fitted landscape is not claimed to be
+ physically unique."""
+ rates = np.asarray(loading_rates, dtype=np.float64)
+ forces = np.asarray(rupture_forces, dtype=np.float64)
+ if rates.ndim != 1 or rates.size != forces.size or rates.size == 0:
+ raise SmfsError(NONFINITE_INPUT, "rates/forces must be equal-length 1-D arrays")
+ if not (np.isfinite(rates).all() and np.isfinite(forces).all()):
+ raise SmfsError(NONFINITE_INPUT, "non-finite kinetic inputs")
+ if temperature <= 0.0:
+ raise SmfsError(INVALID_MODEL_PARAMETER, "temperature must be positive")
+ if nu not in (0.5, 2.0 / 3.0):
+ raise SmfsError(INVALID_MODEL_PARAMETER,
+ "DHS nu must be 1/2 (cusp) or 2/3 (linear-cubic)")
+ if np.any(rates <= 0.0) or np.any(forces <= 0.0):
+ raise SmfsError(INVALID_MODEL_PARAMETER, "rates/forces must be positive")
+ if rates.size < 5:
+ raise SmfsError(INSUFFICIENT_EVENTS, "at least 5 events required for DHS")
+ warnings: list[str] = [
+ "the fitted DHS energy landscape is not claimed to be physically unique"]
+ rate_span = float(np.max(rates) / np.min(rates))
+ if rate_span < 10.0:
+ warnings.append(
+ f"loading-rate range spans only {rate_span:.1f}x: dG is weakly "
+ "identifiable (IDENTIFIABILITY_LIMITED)")
+ best_nll = float("inf")
+ best_params: tuple[float, float, float] | None = None
+ for log_x in np.linspace(-25.33, -16.12, 41):
+ xb = math.exp(log_x)
+ for log_dg in np.linspace(-48.35, -39.1, 41):
+ dg = math.exp(log_dg)
+ nll, k0 = _dhs_profile(xb, dg, rates, forces, nu, temperature)
+ if nll < best_nll:
+ best_nll = nll
+ best_params = (k0, xb, dg)
+ if best_params is None or not np.isfinite(best_nll):
+ raise SmfsError(OPTIMIZATION_FAILED, "DHS likelihood failed")
+ from scipy.optimize import minimize as _min
+ ref = _min(
+ lambda p: _dhs_nll(np.array([math.exp(min(max(p[0], -100.0), 100.0)),
+ math.exp(min(max(p[1], -100.0), 100.0)),
+ math.exp(min(max(p[2], -100.0), 100.0))]),
+ rates, forces, nu, temperature),
+ x0=[math.log(best_params[0]), math.log(best_params[1]),
+ math.log(best_params[2])],
+ method="Nelder-Mead", options={"maxiter": 4000, "xatol": 1e-10,
+ "fatol": 1e-12})
+ if ref.fun < best_nll and np.isfinite(ref.fun):
+ best_nll = float(ref.fun)
+ best_params = (math.exp(float(ref.x[0])), math.exp(float(ref.x[1])),
+ math.exp(float(ref.x[2])))
+ k0, x_beta, dg = best_params
+ return DynamicForceSpectroscopyFitResult(
+ kinetic_model="dudko_hummer_szabo", success=True,
+ parameters={"k0": k0, "x_beta": x_beta, "dG": dg, "nu": nu},
+ parameter_units={"k0": "1/s", "x_beta": "m", "dG": "J", "nu": "dimensionless"},
+ n_events=int(rates.size), negative_log_likelihood=best_nll,
+ most_probable_force_estimator=None,
+ included_rates=rates, included_forces=forces, warnings=tuple(warnings),
+ diagnostics={"rate_span": rate_span, "shape": "cusp" if nu == 0.5
+ else "linear-cubic",
+ "bell_limit": "nu -> 0 recovers k(F) = k0 exp(F x_beta/k_B T)"},
+ provenance={"kinetic_model": "dudko_hummer_szabo", "nu": nu,
+ "temperature": temperature})
+
+
+# ---------------------------------------------------------------------------
+# force-clamp survival (Kaplan-Meier)
+# ---------------------------------------------------------------------------
+
+
+def estimate_force_clamp_survival(
+ lifetimes: np.ndarray,
+ censored: np.ndarray,
+ *,
+ force_level: float,
+ temperature: float = 298.0,
+ fit_exponential_rate: bool = True,
+) -> ForceClampSurvivalResult:
+ """Kaplan-Meier survival with right censoring.
+
+ Ties are broken deterministically: events are processed before censors
+ at the same time. Censored observations are never discarded. The
+ median lifetime is the first KM time with S <= 0.5; when the survival
+ never reaches 0.5 the median is undefined (typed UNDEFINED_MEDIAN in
+ the warnings/provenance, not an exception). The optional exponential
+ rate is the censoring-aware MLE rate = n_events / sum(lifetimes).
+ """
+ lt = np.asarray(lifetimes, dtype=np.float64)
+ ce = np.asarray(censored, dtype=np.float64)
+ if lt.ndim != 1 or lt.size != ce.size or lt.size == 0:
+ raise SmfsError(NONFINITE_INPUT, "lifetimes/censored must be equal-length 1-D")
+ if not (np.isfinite(lt).all() and np.isfinite(ce).all()):
+ raise SmfsError(NONFINITE_INPUT, "non-finite lifetimes")
+ if np.any(lt < 0.0):
+ raise SmfsError(CENSORING_INVALID, "lifetimes must be non-negative")
+ if not np.all((ce == 0.0) | (ce == 1.0)):
+ raise SmfsError(CENSORING_INVALID, "censored flags must be 0 (event) or 1 (censored)")
+ if force_level <= 0.0 or temperature <= 0.0:
+ raise SmfsError(INVALID_MODEL_PARAMETER, "force level and temperature must be positive")
+ order = np.lexsort((ce, lt)) # deterministic: time, then censored flag
+ lt_s = lt[order]
+ ce_s = ce[order]
+ times: list[float] = []
+ surv: list[float] = []
+ at_risk: list[int] = []
+ n_at_risk = int(lt_s.size)
+ s = 1.0
+ i = 0
+ while i < lt_s.size:
+ t_i = float(lt_s[i])
+ # events at this time (censored flags: 0 = event) come first
+ j = i
+ n_events_at = 0
+ while j < lt_s.size and lt_s[j] == t_i and ce_s[j] == 0.0:
+ n_events_at += 1
+ j += 1
+ if n_events_at > 0:
+ s = s * (1.0 - n_events_at / n_at_risk)
+ times.append(t_i)
+ surv.append(s)
+ at_risk.append(n_at_risk)
+ n_at_risk -= n_events_at
+ # censors at this time leave the risk set afterwards too
+ while j < lt_s.size and lt_s[j] == t_i:
+ n_at_risk -= 1
+ j += 1
+ i = j
+ km_times = np.asarray(times, dtype=np.float64)
+ surv_p = np.asarray(surv, dtype=np.float64)
+ at_risk_arr = np.asarray(at_risk, dtype=np.int64)
+ median = None
+ below = np.flatnonzero(surv_p <= 0.5)
+ if below.size:
+ median = float(km_times[int(below[0])])
+ rate = None
+ rate_err = None
+ warnings: list[str] = []
+ if median is None:
+ warnings.append("median lifetime undefined: survival never reaches 0.5 "
+ "(UNDEFINED_MEDIAN)")
+ if fit_exponential_rate:
+ n_events = int(np.sum(ce_s == 0.0))
+ total_time = float(np.sum(lt_s))
+ if n_events > 0 and total_time > 0.0:
+ rate = n_events / total_time
+ rate_err = rate / math.sqrt(n_events) if n_events > 1 else None
+ else:
+ warnings.append("exponential rate undefined: no uncensored events")
+ return ForceClampSurvivalResult(
+ force_level=force_level, temperature=temperature, lifetimes=lt, censored=ce,
+ km_times=km_times, survival_probability=surv_p, at_risk=at_risk_arr,
+ n_events=int(np.sum(ce_s == 0.0)), n_censored=int(np.sum(ce_s == 1.0)),
+ median_lifetime=median, exponential_rate=rate, exponential_rate_error=rate_err,
+ warnings=tuple(warnings),
+ provenance={"estimator": "kaplan_meier", "tie_order": "events before censors",
+ "exponential_rate_mle": fit_exponential_rate})
diff --git a/src/spmkit/core/analysis/force_smfs_models.py b/src/spmkit/core/analysis/force_smfs_models.py
new file mode 100644
index 0000000..19c6193
--- /dev/null
+++ b/src/spmkit/core/analysis/force_smfs_models.py
@@ -0,0 +1,775 @@
+"""FS-F4 polymer models, molecular extension and SMFS fit windows.
+
+Frozen equations (SI units):
+
+WLC (Marko-Siggia loading relation):
+ F(x) = (k_B T / Lp) [1/(4 (1 - x/Lc)^2) - 1/4 + x/Lc]
+ valid for 0 <= x < Lc; never evaluated at or beyond the singularity.
+
+EXTENSIBLE WLC (implicit, Odijk-style):
+ F(x) = (k_B T / Lp) [1/(4 (1 - x/Lc + F/S)^2) - 1/4 + x/Lc - F/S]
+ S = stretch modulus (N); solved numerically per point (brentq on the
+ domain 1 - x/Lc + F/S > 0); S -> inf reduces to the WLC.
+
+FJC:
+ x/Lc = coth(y) - 1/y, y = F b / (k_B T)
+ b = Kuhn length (m); the Langevin function is evaluated stably near
+ y = 0 (series u/3) and for large y.
+
+EXTENSIBLE FJC:
+ x/Lc = L(y) + F/Sk
+ Sk = segment stretch force scale (N); Sk -> inf reduces to the FJC;
+ residuals live in the extension space (documented convention).
+
+Molecular extension contract: extension = retract separation minus an
+explicit tether zero. Supported reference policies: "offset" (physical
+offset in m), "index" (reference sample index), "pre_event" (caller-supplied
+pre-event branch start index), "estimator" (zero-force crossing of the
+retract branch with its own diagnostics). The tether zero is never inferred
+silently from the contact.
+"""
+
+from __future__ import annotations
+
+import math
+from collections.abc import Callable
+from dataclasses import dataclass, field
+
+import numpy as np
+from scipy.optimize import brentq, curve_fit
+
+from spmkit.core.analysis.force_prepare import ForcePreparationResult
+from spmkit.core.analysis.force_smfs_errors import (
+ EMPTY_WINDOW,
+ INSUFFICIENT_POINTS,
+ INVALID_MODEL_PARAMETER,
+ INVALID_REFERENCE_POLICY,
+ MISSING_RETRACT,
+ NONFINITE_INPUT,
+ OPTIMIZATION_FAILED,
+ POLYMER_SINGULARITY,
+ UNRESOLVED_TETHER_ZERO,
+ SmfsError,
+)
+
+#: Boltzmann constant (J/K)
+KB = 1.380649e-23
+
+EXTENSION_REFERENCE_POLICIES = ("offset", "index", "pre_event", "estimator")
+POLYMER_MODELS = ("worm_like_chain", "extensible_worm_like_chain",
+ "freely_jointed_chain", "extensible_freely_jointed_chain")
+
+
+@dataclass(frozen=True)
+class MolecularExtensionResult:
+ """Molecular extension of a retract branch with an explicit zero policy."""
+
+ extension: np.ndarray
+ separation: np.ndarray
+ force: np.ndarray
+ time: np.ndarray | None
+ retract_indices: np.ndarray
+ reference_policy: str
+ reference_coordinate: float
+ reference_index: int | None
+ valid: np.ndarray
+ units: str = "m"
+ warnings: tuple[str, ...] = ()
+ provenance: dict[str, object] = field(default_factory=dict)
+
+
+@dataclass(frozen=True)
+class SMFSFitWindowResult:
+ """Explicit polymer fit window on the molecular extension axis."""
+
+ start_index: int
+ end_index: int
+ extension_min: float
+ extension_max: float
+ force_min: float
+ force_max: float
+ included: np.ndarray
+ excluded_reasons: tuple[str, ...]
+ n_points: int
+ warnings: tuple[str, ...] = ()
+
+
+# ---------------------------------------------------------------------------
+# molecular extension
+# ---------------------------------------------------------------------------
+
+
+def _retract_arrays(
+ prepared: ForcePreparationResult
+) -> tuple[np.ndarray, np.ndarray, np.ndarray | None, object]:
+ retract = prepared.curve.retract
+ if retract is None:
+ raise SmfsError(MISSING_RETRACT, "curve has no retract segment")
+ if retract.separation is None or retract.force is None:
+ raise SmfsError(MISSING_RETRACT, "retract is not prepared (no separation/force)")
+ sep = np.asarray(retract.separation, dtype=np.float64)
+ f = np.asarray(retract.force, dtype=np.float64)
+ t = None if retract.time is None else np.asarray(retract.time, dtype=np.float64)
+ if not (np.isfinite(sep).all() and np.isfinite(f).all()):
+ raise SmfsError(NONFINITE_INPUT, "non-finite retract separation/force")
+ if t is not None and not np.isfinite(t).all():
+ raise SmfsError(NONFINITE_INPUT, "non-finite retract time")
+ return sep, f, t, retract
+
+
+def compute_molecular_extension(
+ prepared: ForcePreparationResult,
+ *,
+ reference: str = "index",
+ reference_value: float | None = None,
+ segment: str = "retract",
+ estimator_noise_sigma: float | None = None,
+) -> MolecularExtensionResult:
+ """Molecular extension of the retract branch with an explicit zero policy.
+
+ ``reference`` policies:
+ - "offset": extension = separation - reference_value (physical offset, m);
+ - "index": extension = separation - separation[reference_value];
+ - "pre_event": same as "index" but semantically the caller-supplied
+ pre-event branch start (recorded in provenance);
+ - "estimator": the tether zero is the retract zero-force crossing after
+ the pull-off (the last index where the corrected force crosses zero
+ from negative to positive while scanning the pull order); the estimator
+ reports its own diagnostics.
+
+ The reference is never inferred from the contact.
+ """
+ if reference not in EXTENSION_REFERENCE_POLICIES:
+ raise SmfsError(INVALID_REFERENCE_POLICY, f"unknown reference policy {reference!r}")
+ if segment != "retract":
+ raise SmfsError(INVALID_REFERENCE_POLICY,
+ "SMFS extension is defined on the retract branch only")
+ sep, f, t, retract = _retract_arrays(prepared)
+ warnings: list[str] = []
+ ref_coord: float
+ ref_idx: int | None = None
+
+ if reference == "offset":
+ if reference_value is None:
+ raise SmfsError(UNRESOLVED_TETHER_ZERO,
+ "reference='offset' requires reference_value (m)")
+ ref_coord = float(reference_value)
+ elif reference in ("index", "pre_event"):
+ if reference_value is None:
+ raise SmfsError(UNRESOLVED_TETHER_ZERO,
+ f"reference={reference!r} requires reference_value (index)")
+ idx = int(reference_value)
+ if idx < 0 or idx >= sep.size:
+ raise SmfsError(UNRESOLVED_TETHER_ZERO, "reference index outside the retract")
+ ref_coord = float(sep[idx])
+ ref_idx = idx
+ else: # estimator
+ # the pull order: the retract separation may be stored increasing or
+ # decreasing; the estimator works on the pull-ordered branch (the
+ # molecular extension increases during the pull)
+ pull = np.argsort(sep, kind="stable")
+ f_pull = f[pull]
+ sep_pull = sep[pull]
+ if estimator_noise_sigma is None:
+ sigma = float(np.std(f_pull[-max(3, f_pull.size // 10):])) or 1e-12
+ else:
+ sigma = float(estimator_noise_sigma)
+ # last zero crossing from negative to positive in the pull order
+ crossings = np.flatnonzero((f_pull[:-1] <= 0.0) & (f_pull[1:] > 0.0))
+ if crossings.size == 0:
+ raise SmfsError(UNRESOLVED_TETHER_ZERO,
+ "estimator: no zero-force crossing on the retract")
+ idx_pull = int(crossings[-1])
+ ref_coord = float(sep_pull[idx_pull])
+ ref_idx = int(pull[idx_pull])
+ warnings.append(
+ f"estimator: tether zero = retract zero-force crossing at "
+ f"separation {ref_coord:.4e} m (noise sigma {sigma:.2e} N)")
+
+ ext = sep - ref_coord
+ if not np.all(np.diff(ext[np.argsort(sep, kind="stable")]) >= -1e-12):
+ warnings.append("extension is not monotone in the pull order; "
+ "check the tether zero policy")
+ valid = np.isfinite(ext) & np.isfinite(f)
+ return MolecularExtensionResult(
+ extension=ext, separation=sep, force=f, time=t,
+ retract_indices=np.arange(sep.size),
+ reference_policy=reference, reference_coordinate=ref_coord,
+ reference_index=ref_idx, valid=valid, warnings=tuple(warnings),
+ provenance={"segment": segment, "reference_policy": reference,
+ "tether_zero": ref_coord})
+
+
+def select_smfs_fit_windows(
+ extension: np.ndarray,
+ force: np.ndarray,
+ *,
+ min_extension: float | None = None,
+ max_extension: float | None = None,
+ min_force: float | None = None,
+ max_force: float | None = None,
+ min_points: int = 10,
+ window_label: str | None = None,
+) -> SMFSFitWindowResult:
+ """Explicit polymer fit window on the molecular extension axis.
+
+ The window is the contiguous span of samples satisfying all bounds;
+ negative-extension samples are always excluded (the polymer model domain
+ starts at the tether zero); fewer than min_points raises
+ INSUFFICIENT_POINTS; the empty window raises EMPTY_WINDOW.
+ """
+ ext = np.asarray(extension, dtype=np.float64)
+ f = np.asarray(force, dtype=np.float64)
+ if ext.ndim != 1 or ext.size != f.size or ext.size == 0:
+ raise SmfsError(NONFINITE_INPUT, "extension/force must be equal-length 1-D arrays")
+ included = np.ones(ext.size, dtype=bool)
+ included &= ext >= 0.0
+ if min_extension is not None:
+ included &= ext >= min_extension
+ if max_extension is not None:
+ included &= ext <= max_extension
+ if min_force is not None:
+ included &= f >= min_force
+ if max_force is not None:
+ included &= f <= max_force
+ idx = np.flatnonzero(included)
+ if idx.size == 0:
+ raise SmfsError(EMPTY_WINDOW, "no samples satisfy the window")
+ start, end = int(idx[0]), int(idx[-1])
+ if end - start + 1 < min_points:
+ raise SmfsError(INSUFFICIENT_POINTS,
+ f"window has {end - start + 1} points < min_points={min_points}")
+ return SMFSFitWindowResult(
+ start_index=start, end_index=end,
+ extension_min=float(ext[start]), extension_max=float(ext[end]),
+ force_min=float(np.min(f[idx])), force_max=float(np.max(f[idx])),
+ included=included, excluded_reasons=(),
+ n_points=int(idx.size),
+ warnings=(f"excluded {ext.size - idx.size} sample(s) outside the window",)
+ if idx.size < ext.size else ())
+
+
+# ---------------------------------------------------------------------------
+# polymer forward models
+# ---------------------------------------------------------------------------
+
+
+def wlc_force(extension: np.ndarray, contour_length: float,
+ persistence_length: float, temperature: float = 298.0) -> np.ndarray:
+ """Marko-Siggia WLC loading relation (N)."""
+ x = np.asarray(extension, dtype=np.float64)
+ if contour_length <= 0.0 or persistence_length <= 0.0 or temperature <= 0.0:
+ raise SmfsError(INVALID_MODEL_PARAMETER,
+ "WLC: Lc > 0, Lp > 0, T > 0 required")
+ if np.any(x < 0.0):
+ raise SmfsError(POLYMER_SINGULARITY, "WLC: extension must be non-negative")
+ if np.any(x >= contour_length):
+ raise SmfsError(POLYMER_SINGULARITY,
+ "WLC: extension must stay below the contour length")
+ r = x / contour_length
+ return (KB * temperature / persistence_length) * (
+ 1.0 / (4.0 * (1.0 - r) ** 2) - 0.25 + r)
+
+
+def _ewlc_residual(force_val: float, x: float, contour_length: float,
+ persistence_length: float, stretch_modulus: float,
+ temperature: float) -> float:
+ """Implicit eWLC residual: F - (k_BT/Lp) g(x/Lc - F/S)."""
+ kt = KB * temperature
+ r_eff = x / contour_length - force_val / stretch_modulus
+ if r_eff >= 1.0:
+ return float("inf")
+ g = 1.0 / (4.0 * (1.0 - r_eff) ** 2) - 0.25 + r_eff
+ return force_val - (kt / persistence_length) * g
+
+
+def extensible_wlc_force(extension: np.ndarray, contour_length: float,
+ persistence_length: float, stretch_modulus: float,
+ temperature: float = 298.0) -> np.ndarray:
+ """Implicit extensible WLC (Odijk-style), solved per point by brentq.
+
+ Domain: 1 - x/Lc + F/S > 0 for every point. The root search brackets
+ [0, F_max] with F_max chosen so the domain stays positive.
+ """
+ x = np.asarray(extension, dtype=np.float64)
+ if contour_length <= 0.0 or persistence_length <= 0.0 \
+ or stretch_modulus <= 0.0 or temperature <= 0.0:
+ raise SmfsError(INVALID_MODEL_PARAMETER,
+ "eWLC: Lc > 0, Lp > 0, S > 0, T > 0 required")
+ if np.any(x < 0.0):
+ raise SmfsError(POLYMER_SINGULARITY, "eWLC: extension must be non-negative")
+ if np.any(x >= contour_length):
+ raise SmfsError(POLYMER_SINGULARITY,
+ "eWLC: extension must stay below the contour length")
+ out = np.empty(x.size, dtype=np.float64)
+ for i, xi in enumerate(x):
+ # the domain bound: F < S (1 - x/Lc); the elastic asymptote F = S x/Lc
+ f_hi = min(stretch_modulus * (1.0 - float(xi) / contour_length) * 0.9999,
+ stretch_modulus * float(xi) / contour_length * 2.0 + 1e-18)
+ if f_hi <= 0.0:
+ raise SmfsError(POLYMER_SINGULARITY, "eWLC: domain collapsed")
+ try:
+ # xtol must be well below the force scale (forces here are
+ # ~1e-13 N): the scipy default xtol=2e-12 would swallow small
+ # roots
+ out[i] = brentq(_ewlc_residual, 0.0, f_hi, xtol=1e-18, rtol=1e-12,
+ args=(float(xi), contour_length, persistence_length,
+ stretch_modulus, temperature))
+ except ValueError as exc:
+ raise SmfsError(POLYMER_SINGULARITY,
+ f"eWLC: no root for x={xi:.3e}: {exc}") from exc
+ return out
+
+
+def langevin(u: np.ndarray) -> np.ndarray:
+ """Langevin function L(u) = coth(u) - 1/u with stable limits.
+
+ |u| < 1e-4 uses the series u/3 (avoids the 0/0); large u is evaluated
+ directly (coth -> 1).
+ """
+ u = np.asarray(u, dtype=np.float64)
+ safe = np.where(u == 0.0, 1.0, u)
+ return np.where(np.abs(u) < 1e-4, u / 3.0, 1.0 / np.tanh(safe) - 1.0 / safe)
+
+
+def fjc_extension(force: np.ndarray, contour_length: float, kuhn_length: float,
+ temperature: float = 298.0) -> np.ndarray:
+ """FJC extension x = Lc L(F b / k_BT) (m)."""
+ f = np.asarray(force, dtype=np.float64)
+ if contour_length <= 0.0 or kuhn_length <= 0.0 or temperature <= 0.0:
+ raise SmfsError(INVALID_MODEL_PARAMETER,
+ "FJC: Lc > 0, b > 0, T > 0 required")
+ return contour_length * langevin(f * kuhn_length / (KB * temperature))
+
+
+def extensible_fjc_extension(force: np.ndarray, contour_length: float,
+ kuhn_length: float, stretch_modulus: float,
+ temperature: float = 298.0) -> np.ndarray:
+ """Extensible FJC extension x = Lc [L(y) + F/Sk] (m)."""
+ f = np.asarray(force, dtype=np.float64)
+ if contour_length <= 0.0 or kuhn_length <= 0.0 or stretch_modulus <= 0.0 \
+ or temperature <= 0.0:
+ raise SmfsError(INVALID_MODEL_PARAMETER,
+ "eFJC: Lc > 0, b > 0, Sk > 0, T > 0 required")
+ return contour_length * (langevin(f * kuhn_length / (KB * temperature))
+ + f / stretch_modulus)
+
+
+# ---------------------------------------------------------------------------
+# polymer fits
+# ---------------------------------------------------------------------------
+
+
+@dataclass(frozen=True)
+class PolymerFitResult:
+ """Deterministic polymer fit on a force-extension branch."""
+
+ model: str
+ success: bool
+ parameters: dict[str, float]
+ parameter_units: dict[str, str]
+ predicted_force: np.ndarray
+ residuals: np.ndarray
+ included_indices: np.ndarray
+ objective: float
+ covariance: dict[str, float] | None
+ condition_number: float
+ dof: int
+ rmse: float
+ aic: float
+ aicc: float
+ bic: float
+ temperature: float
+ warnings: tuple[str, ...] = ()
+ failure_reason: str | None = None
+ diagnostics: dict[str, object] = field(default_factory=dict)
+ provenance: dict[str, object] = field(default_factory=dict)
+
+
+def _finalize(model: str, temperature: float, x: np.ndarray, y: np.ndarray,
+ params: dict[str, float], units: dict[str, str],
+ predicted: np.ndarray, popt: np.ndarray, pcov: np.ndarray | None, names: list[str],
+ idx: np.ndarray, warnings: list[str],
+ provenance: dict[str, object]) -> PolymerFitResult:
+ residuals = y - predicted
+ n = y.size
+ k = len(names)
+ sse = float(np.sum(residuals**2))
+ rmse = float(np.sqrt(np.mean(residuals**2)))
+ aic = n * math.log(sse / n + 1e-300) + 2 * k
+ aicc = aic + (2 * k * (k + 1)) / max(1, n - k - 1)
+ bic = n * math.log(sse / n + 1e-300) + k * math.log(n)
+ cov: dict[str, float] = {}
+ cond = 0.0
+ if pcov is not None and np.all(np.isfinite(pcov)):
+ for i, a in enumerate(names):
+ for j, b in enumerate(names):
+ cov[f"{a}__{b}"] = float(pcov[i, j])
+ try:
+ cond = float(np.linalg.cond(pcov))
+ except np.linalg.LinAlgError: # pragma: no cover - degenerate matrix
+ cond = float("inf")
+ return PolymerFitResult(
+ model=model, success=True, parameters=params, parameter_units=units,
+ predicted_force=predicted, residuals=residuals, included_indices=idx,
+ objective=sse, covariance=cov if cov else None, condition_number=cond,
+ dof=n - k, rmse=rmse, aic=aic, aicc=aicc, bic=bic, temperature=temperature,
+ warnings=tuple(warnings), diagnostics={"n_points": n,
+ "free_parameters": names},
+ provenance=provenance)
+
+
+def _fit_polymer(x: np.ndarray, f: np.ndarray, model: str,
+ model_func: Callable[..., np.ndarray], p0: list[float],
+ bounds: tuple[list[float], list[float]],
+ names: list[str], units: dict[str, str], temperature: float,
+ idx: np.ndarray, starts: list[list[float]] | None = None,
+ extra_params: dict[str, float] | None = None) -> PolymerFitResult:
+ """Shared polymer fit engine: normalized objective + deterministic
+ multi-start (flat-valley protection)."""
+ x = np.asarray(x, dtype=np.float64)
+ f = np.asarray(f, dtype=np.float64)
+ if not (np.isfinite(x).all() and np.isfinite(f).all()):
+ raise SmfsError(NONFINITE_INPUT, "non-finite fit inputs")
+ if x.size < len(p0) + 2:
+ raise SmfsError(INSUFFICIENT_POINTS,
+ f"too few samples for {len(p0)} parameters")
+ scale = float(np.max(np.abs(f))) or 1.0
+ y = f / scale
+
+ def wrapped(tt: np.ndarray, *args: float) -> np.ndarray:
+ return model_func(tt, *args) / scale
+
+ candidates = starts if starts else [p0]
+ best: tuple | None = None
+ best_sse = float("inf")
+ for start in candidates:
+ try:
+ popt, pcov = curve_fit(wrapped, x, y, p0=list(start), bounds=bounds,
+ maxfev=40000)
+ sse = float(np.sum((wrapped(x, *popt) - y) ** 2))
+ except Exception: # noqa: BLE001 - a failed start is skipped
+ continue
+ if sse < best_sse:
+ best_sse = sse
+ best = (popt, pcov)
+ if best is None:
+ raise SmfsError(OPTIMIZATION_FAILED,
+ "optimizer failed from all deterministic starts")
+ popt, pcov = best
+ params = {name: float(v) for name, v in zip(names, popt, strict=True)}
+ if extra_params:
+ params.update(extra_params)
+ predicted = model_func(x, *popt)
+ return _finalize(model, temperature, x, f, params, units, predicted,
+ popt, pcov, names, idx, [], {"model": model})
+
+
+def fit_worm_like_chain(extension: np.ndarray, force: np.ndarray, *,
+ temperature: float = 298.0,
+ Lc_initial: float | None = None,
+ Lp_initial: float | None = None) -> PolymerFitResult:
+ """WLC fit (Lc, Lp) in the force space.
+
+ Separable structure: for each candidate Lc the persistence length is
+ the closed-form least-squares solution Lp = k_B T sum(g^2)/sum(F g)
+ with g = g(x/Lc); a deterministic 1-D grid search over Lc with local
+ refinement avoids the flat (Lc, Lp) valley of a general nonlinear
+ optimizer. The covariance is estimated at the optimum from the
+ Jacobian.
+ """
+ x = np.asarray(extension, dtype=np.float64)
+ f = np.asarray(force, dtype=np.float64)
+ if x.size != f.size or x.size == 0:
+ raise SmfsError(NONFINITE_INPUT, "extension/force length mismatch")
+ if temperature <= 0.0:
+ raise SmfsError(INVALID_MODEL_PARAMETER, "temperature must be positive")
+ if not (np.isfinite(x).all() and np.isfinite(f).all()):
+ raise SmfsError(NONFINITE_INPUT, "non-finite fit inputs")
+ x_max = float(np.max(x))
+ if x_max <= 0.0:
+ raise SmfsError(INVALID_MODEL_PARAMETER, "extension must be positive")
+ lo_lc = max(x_max * 1.001, float(Lc_initial) if Lc_initial is not None else x_max * 1.001)
+ hi_lc = min(x_max * 10.0, float(Lc_initial) * 2.0 if Lc_initial is not None else x_max * 10.0)
+ if hi_lc <= lo_lc:
+ hi_lc = lo_lc * 2.0
+
+ def lp_for(lc: float) -> tuple[float, float]:
+ r = x / lc
+ if np.any(r >= 1.0):
+ return float("inf"), 0.0
+ g = 1.0 / (4.0 * (1.0 - r) ** 2) - 0.25 + r
+ denom = float(np.sum(g * g))
+ if denom <= 0.0:
+ return float("inf"), 0.0
+ den2 = float(np.sum(f * g))
+ if den2 <= 0.0:
+ return float("inf"), 0.0
+ lp = KB * temperature * denom / den2
+ if lp <= 0.0:
+ return float("inf"), 0.0
+ pred = (KB * temperature / lp) * g
+ return float(np.sum((f - pred) ** 2)), lp
+
+ best_lc = float(min(np.linspace(lo_lc, hi_lc, 160), key=lambda lc: lp_for(lc)[0]))
+ step = (hi_lc - lo_lc) / 160.0
+ for _ in range(4):
+ cand = np.linspace(max(best_lc - step, lo_lc), best_lc + step, 41)
+ best_lc = float(min(cand, key=lambda lc: lp_for(lc)[0]))
+ step /= 10.0
+ sse, lp = lp_for(best_lc)
+ if not np.isfinite(sse):
+ raise SmfsError(OPTIMIZATION_FAILED, "WLC separable fit failed")
+ # covariance from the Jacobian at the optimum
+ r = x / best_lc
+ g = 1.0 / (4.0 * (1.0 - r) ** 2) - 0.25 + r
+ pred = (KB * temperature / lp) * g
+ # dF/dLc and dF/dLp
+ dg_dr = 1.0 / (2.0 * (1.0 - r) ** 3) + 1.0
+ j_lc = -(KB * temperature / lp) * dg_dr * r / best_lc
+ j_lp = -pred / lp
+ jac = np.column_stack([j_lc, j_lp])
+ try:
+ pcov = np.linalg.inv(jac.T @ jac) * (sse / max(x.size - 2, 1))
+ except np.linalg.LinAlgError: # pragma: no cover - degenerate design
+ pcov = None
+ params = {"Lc": float(best_lc), "Lp": float(lp)}
+ return _finalize(
+ "worm_like_chain", temperature, x, f, params, {"Lc": "m", "Lp": "m"},
+ pred, np.array([best_lc, lp]), pcov, ["Lc", "Lp"],
+ np.arange(x.size), [], {"model": "worm_like_chain", "fit": "separable_1d"})
+
+
+def fit_extensible_worm_like_chain(extension: np.ndarray, force: np.ndarray, *,
+ temperature: float = 298.0,
+ Lc_initial: float | None = None,
+ Lp_initial: float | None = None,
+ S_initial: float | None = None) -> PolymerFitResult:
+ """eWLC fit (Lc, Lp, S) in the force space; S in [1e-12, 1e-2] N."""
+ x = np.asarray(extension, dtype=np.float64)
+ f = np.asarray(force, dtype=np.float64)
+ if x.size != f.size or x.size == 0:
+ raise SmfsError(NONFINITE_INPUT, "extension/force length mismatch")
+ if temperature <= 0.0:
+ raise SmfsError(INVALID_MODEL_PARAMETER, "temperature must be positive")
+ x_max = float(np.max(x))
+ if x_max <= 0.0:
+ raise SmfsError(INVALID_MODEL_PARAMETER, "extension must be positive")
+ lc0 = Lc_initial if Lc_initial is not None else x_max * 1.2
+ lp0 = Lp_initial if Lp_initial is not None else KB * temperature / max(float(np.max(f)), 1e-30)
+ s0 = S_initial if S_initial is not None else max(float(np.max(f)) * 20.0, 1e-9)
+ lo, hi = [x_max * 1.001, x_max * 1e-6, 1e-12], [x_max * 10.0, x_max * 10.0, 1e-2]
+ lc0 = max(lo[0], min(lc0, hi[0]))
+ lp0 = max(lo[1], min(lp0, hi[1]))
+ s0 = max(lo[2], min(s0, hi[2]))
+
+ def model(tt: np.ndarray, lc: float, lp: float, s: float) -> np.ndarray:
+ return extensible_wlc_force(tt, lc, lp, s, temperature)
+
+ return _fit_polymer(
+ x, f, "extensible_worm_like_chain", model, [lc0, lp0, s0], (lo, hi),
+ ["Lc", "Lp", "S"], {"Lc": "m", "Lp": "m", "S": "N"}, temperature,
+ np.arange(x.size),
+ starts=[[lc0, lp0, s0], [x_max * 1.05, lp0 * 10.0, s0 * 10.0],
+ [x_max * 1.5, lp0 / 10.0, s0 / 10.0]])
+
+
+def fit_freely_jointed_chain(extension: np.ndarray, force: np.ndarray, *,
+ temperature: float = 298.0,
+ Lc_initial: float | None = None,
+ b_initial: float | None = None) -> PolymerFitResult:
+ """FJC fit (Lc, b) in the extension space (x(F) has no closed form)."""
+ x = np.asarray(extension, dtype=np.float64)
+ f = np.asarray(force, dtype=np.float64)
+ if x.size != f.size or x.size == 0:
+ raise SmfsError(NONFINITE_INPUT, "extension/force length mismatch")
+ if temperature <= 0.0:
+ raise SmfsError(INVALID_MODEL_PARAMETER, "temperature must be positive")
+ x_max = float(np.max(x))
+ f_max = float(np.max(np.abs(f)))
+ if x_max <= 0.0 or f_max <= 0.0:
+ raise SmfsError(INVALID_MODEL_PARAMETER, "extension/force must be positive")
+ lc0 = Lc_initial if Lc_initial is not None else x_max * 1.1
+ b0 = b_initial if b_initial is not None else KB * temperature / f_max * 3.0
+ lo, hi = [x_max * 1.001, KB * temperature / f_max * 1e-3], \
+ [x_max * 10.0, KB * temperature / f_max * 1e3]
+ lc0 = max(lo[0], min(lc0, hi[0]))
+ b0 = max(lo[1], min(b0, hi[1]))
+
+ # separable structure: for each candidate b the contour length is the
+ # closed-form least-squares solution Lc = sum(x L(y))/sum(L(y)^2);
+ # a deterministic log-grid search over b avoids the flat (Lc, b) valley
+ y = x
+ kt = KB * temperature
+
+ def lc_for(b: float) -> tuple[float, float]:
+ g = langevin(f * b / kt)
+ denom = float(np.sum(g * g))
+ if denom <= 0.0:
+ return float("inf"), 0.0
+ lc = float(np.sum(x * g) / denom)
+ if lc <= 0.0:
+ return float("inf"), 0.0
+ pred = lc * g
+ return float(np.sum((x - pred) ** 2)), lc
+
+ best_b = float(min(np.geomspace(lo[1], hi[1], 120),
+ key=lambda bb: lc_for(bb)[0]))
+ for _ in range(4):
+ cand = np.geomspace(max(best_b / 2.0, lo[1]), best_b * 2.0, 61)
+ best_b = float(min(cand, key=lambda bb: lc_for(bb)[0]))
+ sse, lc = lc_for(best_b)
+ if not np.isfinite(sse):
+ raise SmfsError(OPTIMIZATION_FAILED, "FJC separable fit failed")
+ g = langevin(f * best_b / kt)
+ pred = lc * g
+ yv = f * best_b / kt
+ safe = np.where(yv == 0, 1.0, yv)
+ dlange = np.where(np.abs(yv) < 1e-4, 1.0 / 3.0,
+ 1.0 / safe**2 - 1.0 / np.sinh(safe) ** 2)
+ jac = np.column_stack([g, lc * dlange * f / kt])
+ try:
+ pcov = np.linalg.inv(jac.T @ jac) * (sse / max(x.size - 2, 1))
+ except np.linalg.LinAlgError: # pragma: no cover - degenerate design
+ pcov = None
+ params = {"Lc": float(lc), "b": float(best_b), "Lp": float(best_b) / 2.0}
+ return _finalize(
+ "freely_jointed_chain", temperature, x, y, params,
+ {"Lc": "m", "b": "m", "Lp": "m"}, pred,
+ np.array([lc, best_b]), pcov, ["Lc", "b"], np.arange(x.size), [],
+ provenance={"model": "freely_jointed_chain", "fit_space": "extension",
+ "fit": "separable_1d"})
+
+
+def fit_extensible_freely_jointed_chain(extension: np.ndarray, force: np.ndarray, *,
+ temperature: float = 298.0,
+ Lc_initial: float | None = None,
+ b_initial: float | None = None,
+ Sk_initial: float | None = None,
+ ) -> PolymerFitResult:
+ """eFJC fit (Lc, b, Sk) in the extension space; Sk in [1e-12, 1e-2] N."""
+ x = np.asarray(extension, dtype=np.float64)
+ f = np.asarray(force, dtype=np.float64)
+ if x.size != f.size or x.size == 0:
+ raise SmfsError(NONFINITE_INPUT, "extension/force length mismatch")
+ if temperature <= 0.0:
+ raise SmfsError(INVALID_MODEL_PARAMETER, "temperature must be positive")
+ x_max = float(np.max(x))
+ f_max = float(np.max(np.abs(f)))
+ if x_max <= 0.0 or f_max <= 0.0:
+ raise SmfsError(INVALID_MODEL_PARAMETER, "extension/force must be positive")
+ lc0 = Lc_initial if Lc_initial is not None else x_max * 1.1
+ b0 = b_initial if b_initial is not None else KB * temperature / f_max * 3.0
+ sk0 = Sk_initial if Sk_initial is not None else max(f_max * 20.0, 1e-9)
+ lo, hi = [x_max * 1.001, KB * temperature / f_max * 1e-3, 1e-12], \
+ [x_max * 10.0, KB * temperature / f_max * 1e3, 1e-2]
+ lc0 = max(lo[0], min(lc0, hi[0]))
+ b0 = max(lo[1], min(b0, hi[1]))
+ sk0 = max(lo[2], min(sk0, hi[2]))
+
+ kt = KB * temperature
+
+ def lc_for(b: float, sk: float) -> tuple[float, float]:
+ g = langevin(f * b / kt) + f / sk
+ denom = float(np.sum(g * g))
+ if denom <= 0.0:
+ return float("inf"), 0.0
+ lc = float(np.sum(x * g) / denom)
+ if lc <= 0.0:
+ return float("inf"), 0.0
+ pred = lc * g
+ return float(np.sum((x - pred) ** 2)), lc
+
+ best = (float("inf"), b0, sk0, 0.0)
+ for log_b in np.linspace(np.log(lo[1]), np.log(hi[1]), 60):
+ bb = math.exp(log_b)
+ for log_sk in np.linspace(np.log(lo[2]), np.log(hi[2]), 60):
+ sse, lc = lc_for(bb, math.exp(log_sk))
+ if sse < best[0]:
+ best = (sse, bb, math.exp(log_sk), lc)
+ sse, best_b, best_sk, lc = best
+ if not np.isfinite(sse):
+ raise SmfsError(OPTIMIZATION_FAILED, "eFJC separable fit failed")
+ g = langevin(f * best_b / kt) + f / best_sk
+ pred = lc * g
+ yv = f * best_b / kt
+ safe = np.where(yv == 0, 1.0, yv)
+ dlange = np.where(np.abs(yv) < 1e-4, 1.0 / 3.0,
+ 1.0 / safe**2 - 1.0 / np.sinh(safe) ** 2)
+ jac = np.column_stack([g, lc * dlange * f / kt, lc * f / best_sk**2])
+ try:
+ pcov = np.linalg.inv(jac.T @ jac) * (sse / max(x.size - 3, 1))
+ except np.linalg.LinAlgError: # pragma: no cover - degenerate design
+ pcov = None
+ params = {"Lc": float(lc), "b": float(best_b), "Lp": float(best_b) / 2.0,
+ "Sk": float(best_sk)}
+ return _finalize(
+ "extensible_freely_jointed_chain", temperature, x, x, params,
+ {"Lc": "m", "b": "m", "Lp": "m", "Sk": "N"}, pred,
+ np.array([lc, best_b, best_sk]), pcov, ["Lc", "b", "Sk"],
+ np.arange(x.size), [],
+ {"model": "extensible_freely_jointed_chain", "fit_space": "extension",
+ "fit": "separable_grid"})
+
+
+# ---------------------------------------------------------------------------
+# polymer model comparison
+# ---------------------------------------------------------------------------
+
+
+@dataclass(frozen=True)
+class PolymerModelComparisonResult:
+ """Model-relative comparison over identical observations."""
+
+ fits: tuple[PolymerFitResult, ...]
+ delta_aicc: dict[str, float]
+ weights: dict[str, float]
+ recommended_model: str | None
+ ambiguous: bool
+ n_compared: int
+ warnings: tuple[str, ...] = ()
+ provenance: dict[str, object] = field(default_factory=dict)
+
+
+def compare_polymer_models(
+ extension: np.ndarray,
+ force: np.ndarray,
+ *,
+ models: tuple[str, ...] = ("worm_like_chain", "extensible_worm_like_chain",
+ "freely_jointed_chain", "extensible_freely_jointed_chain"),
+ temperature: float = 298.0,
+) -> PolymerModelComparisonResult:
+ """AICc comparison over the identical observation set; relative weights
+ only; the recommendation policy is SOFTWARE_VERIFIED."""
+ fits: list[PolymerFitResult] = []
+ warnings: list[str] = []
+ for model in models:
+ if model not in POLYMER_MODELS:
+ raise SmfsError(INVALID_MODEL_PARAMETER, f"unknown model {model!r}")
+ try:
+ if model == "worm_like_chain":
+ fits.append(fit_worm_like_chain(extension, force, temperature=temperature))
+ elif model == "extensible_worm_like_chain":
+ fits.append(fit_extensible_worm_like_chain(extension, force,
+ temperature=temperature))
+ elif model == "freely_jointed_chain":
+ fits.append(fit_freely_jointed_chain(extension, force,
+ temperature=temperature))
+ else:
+ fits.append(fit_extensible_freely_jointed_chain(
+ extension, force, temperature=temperature))
+ except SmfsError as exc:
+ warnings.append(f"{model}: {exc.code}")
+ if not fits:
+ raise SmfsError(OPTIMIZATION_FAILED, "no polymer model fit succeeded")
+ delta_aicc = {f.model: f.aicc - min(x.aicc for x in fits) for f in fits}
+ total_w = sum(math.exp(-0.5 * d) for d in delta_aicc.values())
+ weights = {m: math.exp(-0.5 * delta_aicc[m]) / total_w for m in delta_aicc}
+ best = min(fits, key=lambda f: f.aicc)
+ ambiguous = (sorted(f.aicc for f in fits)[1] - best.aicc < 4.0
+ if len(fits) > 1 else False)
+ return PolymerModelComparisonResult(
+ fits=tuple(fits), delta_aicc=delta_aicc, weights=weights,
+ recommended_model=best.model if not ambiguous else None,
+ ambiguous=ambiguous, n_compared=len(fits), warnings=tuple(warnings),
+ provenance={"criterion": "aicc", "temperature": temperature})
diff --git a/src/spmkit/core/analysis/force_smfs_population.py b/src/spmkit/core/analysis/force_smfs_population.py
new file mode 100644
index 0000000..6a8f057
--- /dev/null
+++ b/src/spmkit/core/analysis/force_smfs_population.py
@@ -0,0 +1,163 @@
+"""FS-F4 SMFS population aggregation and batch orchestration.
+
+Population analysis aggregates events without claiming molecular identity.
+Batch analysis retains every per-curve result and every failure reason;
+nothing is silently dropped.
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass, field
+
+import numpy as np
+
+from spmkit.core.analysis.force_smfs_errors import (
+ INSUFFICIENT_EVENTS,
+ SmfsError,
+)
+
+
+@dataclass(frozen=True)
+class SMFSPopulationResult:
+ """Aggregated event population with raw assignments and ambiguity."""
+
+ n_events: int
+ rupture_forces: np.ndarray
+ contour_increments: np.ndarray
+ loading_rates: np.ndarray
+ curve_origins: np.ndarray
+ group_assignments: np.ndarray
+ group_config: dict[str, object]
+ rupture_force_summary: dict[str, float]
+ contour_increment_summary: dict[str, float]
+ loading_rate_summary: dict[str, float]
+ ambiguous: bool
+ warnings: tuple[str, ...] = ()
+ provenance: dict[str, object] = field(default_factory=dict)
+
+
+@dataclass(frozen=True)
+class SMFSBatchResult:
+ """Deterministic per-curve SMFS analysis with a unified event table."""
+
+ n_curves: int
+ n_ok: int
+ n_failed: int
+ per_curve: tuple[dict[str, object], ...]
+ failed_reasons: dict[int, str]
+ unified_event_table: tuple[dict[str, object], ...]
+ population: SMFSPopulationResult | None
+ provenance: dict[str, object] = field(default_factory=dict)
+ warnings: tuple[str, ...] = ()
+
+
+def _to_float(value: object) -> float:
+ if isinstance(value, (int, float, np.generic)):
+ return float(value)
+ return float("nan")
+
+
+def _summary(values: np.ndarray) -> dict[str, float]:
+ if values.size == 0:
+ return {"n": 0.0, "mean": float("nan"), "median": float("nan"),
+ "std": float("nan"), "min": float("nan"), "max": float("nan")}
+ return {"n": float(values.size), "mean": float(np.mean(values)),
+ "median": float(np.median(values)), "std": float(np.std(values)),
+ "min": float(np.min(values)), "max": float(np.max(values))}
+
+
+def analyze_smfs_event_population(
+ event_records: list[dict[str, object]],
+ *,
+ group_by: str = "loading_rate_decade",
+ n_groups: int = 4,
+ force_levels: np.ndarray | None = None,
+) -> SMFSPopulationResult:
+ """Aggregate event records into a population.
+
+ ``group_by``: "none" (single group) or "loading_rate_decade" (the
+ loading-rate decades define deterministic groups). Raw group
+ assignments are exposed; no molecular-identity claim is made.
+ """
+ n = len(event_records)
+ if n == 0:
+ raise SmfsError(INSUFFICIENT_EVENTS, "no events to aggregate")
+ forces = np.asarray([_to_float(r.get("rupture_force", np.nan))
+ for r in event_records])
+ dlt = np.asarray([_to_float(r.get("delta_contour_length", np.nan))
+ for r in event_records])
+ rates = np.asarray([_to_float(r.get("loading_rate", np.nan))
+ for r in event_records])
+ origins = np.asarray([str(r.get("curve_id", "")) for r in event_records],
+ dtype=object)
+ if group_by == "none":
+ assignments = np.zeros(n, dtype=np.int64)
+ config: dict[str, object] = {"group_by": "none", "n_groups": 1}
+ elif group_by == "loading_rate_decade":
+ finite = rates[np.isfinite(rates)]
+ if finite.size == 0:
+ raise SmfsError(INSUFFICIENT_EVENTS, "no finite loading rates to group")
+ lo = np.floor(np.log10(np.min(finite)))
+ hi = np.ceil(np.log10(np.max(finite)))
+ edges = np.linspace(lo, hi, n_groups + 1)
+ assignments = np.clip(
+ np.searchsorted(edges, np.log10(rates), side="right") - 1,
+ 0, n_groups - 1)
+ config = {"group_by": "loading_rate_decade", "n_groups": n_groups,
+ "log10_edges": edges.tolist()}
+ else:
+ raise ValueError(f"unknown group_by {group_by!r}")
+ # ambiguity: too few events for any population claim, or groups too
+ # small to support a grouping interpretation
+ counts = np.bincount(assignments, minlength=int(np.max(assignments)) + 1)
+ ambiguous = bool(n < 5 or np.max(counts) < 2)
+ return SMFSPopulationResult(
+ n_events=n, rupture_forces=forces, contour_increments=dlt,
+ loading_rates=rates, curve_origins=origins, group_assignments=assignments,
+ group_config=config, rupture_force_summary=_summary(forces),
+ contour_increment_summary=_summary(dlt[np.isfinite(dlt)]),
+ loading_rate_summary=_summary(rates[np.isfinite(rates)]),
+ ambiguous=ambiguous,
+ warnings=("population grouping is a descriptive aggregation; no "
+ "molecular-identity claim is made",),
+ provenance={"group_by": group_by, "n_groups": n_groups})
+
+
+def analyze_smfs_batch(
+ analyses: list[dict[str, object]],
+ *,
+ group_by: str = "loading_rate_decade",
+ n_groups: int = 4,
+) -> SMFSBatchResult:
+ """Deterministic batch orchestration over per-curve analyses.
+
+ Each ``analyses`` entry is a per-curve record produced by the caller
+ (e.g. the FS-F4 pipeline): {"curve_id", "ok", "events": [...], ...}.
+ Failed curves are retained with their reasons; the unified event table
+ collects every event across curves with its curve origin.
+ """
+ n_curves = len(analyses)
+ ok = [a for a in analyses if a.get("ok", False)]
+ failed = [a for a in analyses if not a.get("ok", False)]
+ failed_reasons: dict[int, str] = {}
+ for i, a in enumerate(analyses):
+ if not a.get("ok", False):
+ idx = a.get("curve_index", i)
+ failed_reasons[int(idx) if isinstance(idx, (int, float)) else i] = \
+ str(a.get("failure", "unknown"))
+ unified: list[dict[str, object]] = []
+ for a in ok:
+ events = a.get("events", [])
+ for ev in events if isinstance(events, list) else []:
+ rec = dict(ev)
+ rec["curve_id"] = str(a.get("curve_id", ""))
+ unified.append(rec)
+ population = analyze_smfs_event_population(
+ unified, group_by=group_by, n_groups=n_groups) if unified else None
+ return SMFSBatchResult(
+ n_curves=n_curves, n_ok=len(ok), n_failed=len(failed),
+ per_curve=tuple(analyses), failed_reasons=failed_reasons,
+ unified_event_table=tuple(unified), population=population or None,
+ provenance={"pipeline": ["per-curve SMFS analysis",
+ "unified event table", "population"],
+ "deterministic": True, "n_unified_events": len(unified)})
diff --git a/src/spmkit/core/analysis/force_time_protocol.py b/src/spmkit/core/analysis/force_time_protocol.py
new file mode 100644
index 0000000..700ff42
--- /dev/null
+++ b/src/spmkit/core/analysis/force_time_protocol.py
@@ -0,0 +1,535 @@
+"""FS-F3 time-domain protocol layer.
+
+Freezes the temporal contract, identifies viscoelastic protocols from force
+curves, computes indentation/force rates and extracts relaxation and creep
+responses. No hidden resampling, no smoothing, no assumed acquisition rate.
+
+Temporal contract
+-----------------
+- time unit: seconds (``ForceSegment.time``);
+- one finite 1-D axis per segment, strictly increasing;
+- duplicate timestamps raise ``DUPLICATE_TIMESTAMPS`` unless an explicit
+ typed repair is requested;
+- nonuniform sampling is allowed and never resampled silently;
+- the instrument clock is ``segment.time``; a reconstructed clock requires
+ an explicit ``assume_uniform_rate`` (documented assumption);
+- a missing time axis raises ``MISSING_TIME``.
+
+Protocol classes
+----------------
+LOADING_RAMP, UNLOADING_RAMP, DISPLACEMENT_HOLD, FORCE_HOLD, CREEP,
+STRESS_RELAXATION, TRIANGULAR_LOADING, INSUFFICIENT_PROTOCOL,
+AMBIGUOUS_PROTOCOL.
+
+Sign conventions follow FS-F1/FS-F2: separation = height - deflection;
+indentation = separation - contact coordinate (positive into the sample).
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass, field
+
+import numpy as np
+
+from spmkit.core.analysis.force_foundation import ForcePreparationResult
+from spmkit.core.analysis.force_viscoelastic_errors import (
+ AMBIGUOUS_PROTOCOL as _ERR_AMBIGUOUS_PROTOCOL, # noqa: F401 (code constant)
+)
+from spmkit.core.analysis.force_viscoelastic_errors import (
+ CURVE_NOT_FIT_ELIGIBLE,
+ DUPLICATE_TIMESTAMPS,
+ EMPTY_REGION,
+ INVALID_RESPONSE,
+ MISSING_TIME,
+ NONFINITE_RESPONSE,
+ NONMONOTONIC_TIME,
+ ViscoelasticityError,
+)
+from spmkit.core.analysis.force_viscoelastic_errors import (
+ INSUFFICIENT_PROTOCOL as _ERR_INSUFFICIENT_PROTOCOL, # noqa: F401 (code constant)
+)
+from spmkit.core.models import ForceCurve, ForceSegment
+
+#: canonical protocol classes
+LOADING_RAMP = "LOADING_RAMP"
+UNLOADING_RAMP = "UNLOADING_RAMP"
+DISPLACEMENT_HOLD = "DISPLACEMENT_HOLD"
+FORCE_HOLD = "FORCE_HOLD"
+CREEP = "CREEP"
+STRESS_RELAXATION = "STRESS_RELAXATION"
+TRIANGULAR_LOADING = "TRIANGULAR_LOADING"
+INSUFFICIENT_PROTOCOL = "INSUFFICIENT_PROTOCOL"
+AMBIGUOUS_PROTOCOL = "AMBIGUOUS_PROTOCOL"
+
+PROTOCOL_CLASSES = (LOADING_RAMP, UNLOADING_RAMP, DISPLACEMENT_HOLD, FORCE_HOLD,
+ CREEP, STRESS_RELAXATION, TRIANGULAR_LOADING,
+ INSUFFICIENT_PROTOCOL, AMBIGUOUS_PROTOCOL)
+
+#: trusted instrument labels read from curve.metadata, in priority order
+_TRUSTED_PROTOCOL_KEYS = ("protocol", "viscoelastic_protocol", "experiment_type")
+
+
+def validate_time_axis(time: np.ndarray, *, label: str = "time",
+ allow_duplicates: bool = False) -> np.ndarray:
+ """Validate one time axis against the frozen temporal contract."""
+ t = np.asarray(time, dtype=np.float64)
+ if t.ndim != 1 or t.size == 0:
+ raise ViscoelasticityError(MISSING_TIME, f"{label}: no finite 1-D time axis")
+ if not np.isfinite(t).all():
+ raise ViscoelasticityError(NONMONOTONIC_TIME, f"{label}: non-finite times")
+ d = np.diff(t)
+ if np.any(d <= 0.0):
+ if np.any(d == 0.0) and not allow_duplicates:
+ raise ViscoelasticityError(
+ DUPLICATE_TIMESTAMPS,
+ f"{label}: duplicate timestamps (strictly increasing required)")
+ raise ViscoelasticityError(NONMONOTONIC_TIME,
+ f"{label}: times must be strictly increasing")
+ return t
+
+
+@dataclass(frozen=True)
+class ProtocolRegion:
+ """One contiguous protocol region on a segment."""
+
+ kind: str # "loading" | "unloading" | "hold_displacement" | "hold_force"
+ segment: str # "extend" | "retract" | ...
+ start_index: int
+ end_index: int # inclusive
+ start_time: float
+ end_time: float
+
+
+@dataclass(frozen=True)
+class ViscoelasticProtocolResult:
+ """Identified protocol of one curve with explicit region records."""
+
+ protocol_type: str
+ regions: tuple[ProtocolRegion, ...]
+ method: str
+ time_unit: str = "s"
+ trusted_label: str | None = None
+ ambiguity: bool = False
+ warnings: tuple[str, ...] = ()
+ provenance: dict[str, object] = field(default_factory=dict)
+
+ def region(self, kind: str, segment: str | None = None) -> ProtocolRegion | None:
+ for r in self.regions:
+ if r.kind == kind and (segment is None or r.segment == segment):
+ return r
+ return None
+
+
+def _segment_time(segment: ForceSegment) -> np.ndarray:
+ if segment.time is None:
+ raise ViscoelasticityError(
+ MISSING_TIME,
+ f"segment {segment.segment_type!r} has no time axis; pass "
+ "assume_uniform_rate to reconstruct one explicitly")
+ return validate_time_axis(np.asarray(segment.time, dtype=np.float64))
+
+
+def _rate_regions(t: np.ndarray, disp: np.ndarray, force: np.ndarray,
+ rate_threshold: float, min_hold_points: int) -> list[dict]:
+ """Classify contiguous rate regions on one segment.
+
+ Rates are finite differences d(disp)/dt and d(force)/dt. A sample is
+ "hold-like" when the displacement rate magnitude is below the threshold
+ (relative to the median |rate|); among hold-like runs, a run whose force
+ rate magnitude is also below the threshold is a displacement hold, a run
+ with a large displacement rate and small force rate is a force hold.
+ Runs shorter than min_hold_points are merged into the surrounding ramp.
+ """
+ n = t.size
+ if n < 2:
+ raise ViscoelasticityError(INSUFFICIENT_PROTOCOL, "segment too short")
+ dt = np.diff(t)
+ d_disp = np.diff(disp)
+ d_force = np.diff(force)
+ rate_disp = d_disp / dt
+ rate_force = d_force / dt
+ # the rate scale is the median of the NONZERO rates: a long static hold
+ # would otherwise drag the median to zero and force an absolute scale
+ nonzero_disp = np.abs(rate_disp)[np.abs(rate_disp) > 0.0]
+ nonzero_force = np.abs(rate_force)[np.abs(rate_force) > 0.0]
+ med_disp = float(np.median(nonzero_disp)) if nonzero_disp.size else 0.0
+ med_force = float(np.median(nonzero_force)) if nonzero_force.size else 0.0
+ scale = med_disp if med_disp > 0.0 else 1.0
+ thr_disp = rate_threshold * scale
+ thr_force = rate_threshold * max(med_force, 1e-300)
+ # a force hold must carry a non-baseline force level (the zero-force
+ # pre-contact region is not a hold)
+ peak_force = float(np.max(np.abs(force))) if force.size else 0.0
+ force_level = 0.01 * peak_force
+
+ kinds = np.empty(n, dtype=object)
+ kinds[0] = "loading" if rate_disp[0] > 0 else "unloading"
+ for i in range(n - 1):
+ if abs(rate_disp[i]) <= thr_disp:
+ # displacement held (a decaying force here is the relaxation
+ # signal, not a force hold)
+ kinds[i + 1] = "hold_displacement"
+ elif abs(rate_force[i]) <= thr_force and abs(force[i]) > force_level:
+ # force held while the displacement drifts (creep signature)
+ kinds[i + 1] = "hold_force"
+ elif rate_disp[i] > 0:
+ kinds[i + 1] = "loading"
+ else:
+ kinds[i + 1] = "unloading"
+ # merge runs shorter than min_hold_points into the ramp label
+ out = list(kinds)
+ runs: list[tuple[int, int, str]] = []
+ i = 0
+ while i < n:
+ j = i
+ while j + 1 < n and out[j + 1] == out[i]:
+ j += 1
+ runs.append((i, j, out[i]))
+ i = j + 1
+ for (a, b, kind) in runs:
+ if kind.startswith("hold") and (b - a + 1) < min_hold_points:
+ for k in range(a, b + 1):
+ out[k] = "loading" if rate_disp[min(k, n - 2)] >= 0 else "unloading"
+ regions: list[dict] = []
+ i = 0
+ while i < n:
+ j = i
+ while j + 1 < n and out[j + 1] == out[i]:
+ j += 1
+ if out[i].startswith("hold") or True:
+ regions.append({
+ "kind": out[i], "start": int(i), "end": int(j),
+ "t0": float(t[i]), "t1": float(t[j]),
+ })
+ i = j + 1
+ return regions
+
+
+def identify_viscoelastic_protocol(
+ curve: ForceCurve,
+ *,
+ contact_index: int | None = None,
+ contact_coordinate: float | None = None,
+ rate_threshold: float = 0.05,
+ min_hold_points: int = 5,
+ min_hold_fraction: float = 0.05,
+ assume_uniform_rate: float | None = None,
+ force_threshold_fraction: float = 0.1,
+) -> ViscoelasticProtocolResult:
+ """Identify the viscoelastic protocol of a force curve.
+
+ Displacement holds are detected on the raw-height rate (constant
+ displacement), force holds on the force rate with a drifting
+ displacement. When a contact coordinate is given the indentation axis
+ is used for the loading/unloading classification; otherwise the raw
+ height is used as the displacement proxy (documented).
+
+ Trusted instrument labels in ``curve.metadata`` take precedence over
+ inference.
+
+ Time limitation: the JPK/NID readers do not populate ``segment.time``;
+ this operation requires an explicit valid time axis or an explicitly
+ requested known-rate reconstruction (``assume_uniform_rate``). No
+ automatic general JPK/NID time-domain analysis is claimed.
+ """
+ warnings: list[str] = []
+ for key in _TRUSTED_PROTOCOL_KEYS:
+ label = curve.metadata.get(key) if isinstance(curve.metadata, dict) else None
+ if isinstance(label, str) and label.upper() in PROTOCOL_CLASSES:
+ return ViscoelasticProtocolResult(
+ protocol_type=label.upper(), regions=(),
+ method="trusted_label", trusted_label=key,
+ provenance={"label_key": key})
+ if assume_uniform_rate is not None:
+ if assume_uniform_rate <= 0.0:
+ raise ValueError("assume_uniform_rate must be positive (s per sample)")
+ warnings.append(
+ f"reconstructed clock: uniform rate {assume_uniform_rate} s/sample assumed")
+
+ segments: list[tuple[str, np.ndarray, np.ndarray, np.ndarray]] = []
+ for s in curve.segments:
+ if s.time is None:
+ if assume_uniform_rate is None:
+ raise ViscoelasticityError(
+ MISSING_TIME,
+ f"segment {s.segment_type!r} lacks a time axis")
+ t = np.arange(len(s), dtype=np.float64) * assume_uniform_rate
+ else:
+ t = validate_time_axis(np.asarray(s.time, dtype=np.float64))
+ if s.force is None:
+ raise ViscoelasticityError(CURVE_NOT_FIT_ELIGIBLE,
+ f"segment {s.segment_type!r} is not calibrated")
+ disp = np.asarray(s.raw_height, dtype=np.float64)
+ if contact_index is not None and contact_coordinate is not None \
+ and s.segment_type == "extend":
+ sep = np.asarray(s.raw_height, dtype=np.float64)
+ ind = sep - float(contact_coordinate)
+ disp = np.where(np.arange(sep.size) >= contact_index, ind, disp)
+ segments.append((s.segment_type, t, disp, np.asarray(s.force, dtype=np.float64)))
+
+ regions_out: list[ProtocolRegion] = []
+ all_regions: list[dict] = []
+ for seg_name, t, disp, force in segments:
+ if disp.size < 2:
+ continue
+ regs = _rate_regions(t, disp, force, rate_threshold, min_hold_points)
+ for r in regs:
+ r["segment"] = seg_name
+ all_regions.extend(regs)
+ for r in regs:
+ regions_out.append(ProtocolRegion(
+ kind=r["kind"], segment=seg_name, start_index=r["start"],
+ end_index=r["end"], start_time=r["t0"], end_time=r["t1"]))
+
+ holds = [r for r in all_regions if r["kind"].startswith("hold")]
+ loading = [r for r in all_regions if r["kind"] == "loading"]
+ unloading = [r for r in all_regions if r["kind"] == "unloading"]
+ extend_loading = [r for r in loading if r["segment"] == "extend"]
+
+ if not holds and not extend_loading:
+ return ViscoelasticProtocolResult(
+ protocol_type=INSUFFICIENT_PROTOCOL, regions=tuple(regions_out),
+ method="rate_regions", ambiguity=True, warnings=tuple(warnings),
+ provenance={"reason": "no loading or hold region identified"})
+
+ force_holds = [r for r in holds if r["kind"] == "hold_force"]
+ disp_holds = [r for r in holds if r["kind"] == "hold_displacement"]
+
+ if force_holds:
+ # CREEP requires the force to be held while displacement drifts
+ protocol = CREEP
+ elif disp_holds:
+ # STRESS_RELAXATION: displacement hold with decaying force
+ h = disp_holds[0]
+ seg = next(s for s in segments if s[0] == h["segment"])
+ fh = seg[3][h["start"]: h["end"] + 1]
+ f0 = float(fh[0])
+ decay = (float(fh[-1]) - f0) / abs(f0) if f0 != 0.0 else 0.0
+ if f0 != 0.0 and decay <= -force_threshold_fraction:
+ protocol = STRESS_RELAXATION
+ else:
+ protocol = DISPLACEMENT_HOLD
+ elif unloading and extend_loading:
+ protocol = TRIANGULAR_LOADING
+ elif extend_loading:
+ protocol = LOADING_RAMP
+ else:
+ protocol = INSUFFICIENT_PROTOCOL
+
+ ambiguity = protocol in (INSUFFICIENT_PROTOCOL, AMBIGUOUS_PROTOCOL)
+ if force_holds and disp_holds:
+ ambiguity = True
+ warnings.append("both force-hold and displacement-hold regions found")
+ return ViscoelasticProtocolResult(
+ protocol_type=protocol, regions=tuple(regions_out), method="rate_regions",
+ ambiguity=ambiguity, warnings=tuple(warnings),
+ provenance={"rate_threshold": rate_threshold,
+ "min_hold_points": min_hold_points,
+ "min_hold_fraction": min_hold_fraction,
+ "assume_uniform_rate": assume_uniform_rate})
+
+
+@dataclass(frozen=True)
+class IndentationRateResult:
+ """Indentation and force rates of one protocol region."""
+
+ indentation_rate: float
+ force_rate: float
+ local_indentation_rates: np.ndarray
+ local_force_rates: np.ndarray
+ indentation_rate_low: float
+ indentation_rate_high: float
+ included_indices: np.ndarray
+ region: str
+ units: str = "m/s"
+ warnings: tuple[str, ...] = ()
+
+
+def compute_indentation_rate(
+ prepared: ForcePreparationResult,
+ protocol: ViscoelasticProtocolResult,
+ *,
+ region: str = "loading",
+ segment: str | None = "extend",
+) -> IndentationRateResult:
+ """Robust indentation and force rate of one protocol region.
+
+ The region is located via ``protocol.region(region, segment)``; local
+ rates are finite differences of the indentation (separation minus the
+ contact coordinate) and the force over that region; the reported rate is
+ the median of the local rates with the 25-75 percentile spread.
+
+ Requires a valid approach time axis (the JPK/NID readers do not
+ populate it; provide one or use an explicitly requested known-rate
+ reconstruction).
+ """
+ if not isinstance(prepared, ForcePreparationResult):
+ raise TypeError("compute_indentation_rate requires a ForcePreparationResult")
+ reg = protocol.region(region, segment)
+ if reg is None:
+ raise ViscoelasticityError(EMPTY_REGION, f"no {region!r} region on {segment!r}")
+ approach = prepared.curve.extend
+ if approach is None or approach.separation is None or approach.force is None:
+ raise ViscoelasticityError(CURVE_NOT_FIT_ELIGIBLE, "no prepared approach branch")
+ t = validate_time_axis(np.asarray(approach.time, dtype=np.float64))
+ a, b = reg.start_index, reg.end_index + 1
+ if b > t.size:
+ raise ViscoelasticityError(EMPTY_REGION, "region outside the approach segment")
+ sep = np.asarray(approach.separation, dtype=np.float64)
+ ind = sep - float(prepared.contact.selected.coordinate)
+ f = np.asarray(approach.force, dtype=np.float64)
+ dt = np.diff(t[a:b])
+ if np.any(dt <= 0.0):
+ raise ViscoelasticityError(NONMONOTONIC_TIME, "region time axis not increasing")
+ rate_ind = np.diff(ind[a:b]) / dt
+ rate_f = np.diff(f[a:b]) / dt
+ if rate_ind.size == 0:
+ raise ViscoelasticityError(EMPTY_REGION, "region has fewer than 2 samples")
+ med_ind = float(np.median(rate_ind))
+ lo, hi = float(np.percentile(rate_ind, 25)), float(np.percentile(rate_ind, 75))
+ return IndentationRateResult(
+ indentation_rate=med_ind,
+ force_rate=float(np.median(rate_f)),
+ local_indentation_rates=rate_ind,
+ local_force_rates=rate_f,
+ indentation_rate_low=lo,
+ indentation_rate_high=hi,
+ included_indices=np.arange(a, b),
+ region=f"{segment}:{region}",
+ warnings=(f"region {a}..{b - 1} of {t.size} samples",),
+ )
+
+
+def _hold_window(prepared: ForcePreparationResult,
+ protocol: ViscoelasticProtocolResult,
+ hold_kind: str, segment: str) -> tuple[np.ndarray, np.ndarray,
+ np.ndarray, np.ndarray]:
+ approach = prepared.curve.extend
+ if approach is None or approach.separation is None or approach.force is None \
+ or approach.time is None:
+ raise ViscoelasticityError(CURVE_NOT_FIT_ELIGIBLE, "no prepared approach branch")
+ reg = protocol.region(hold_kind, segment)
+ if reg is None:
+ raise ViscoelasticityError(
+ EMPTY_REGION,
+ f"protocol {protocol.protocol_type!r} has no {hold_kind!r} region")
+ t = validate_time_axis(np.asarray(approach.time, dtype=np.float64))
+ a, b = reg.start_index, reg.end_index + 1
+ if b > t.size:
+ raise ViscoelasticityError(EMPTY_REGION, "hold region outside the approach")
+ sep = np.asarray(approach.separation, dtype=np.float64)
+ ind = sep - float(prepared.contact.selected.coordinate)
+ f = np.asarray(approach.force, dtype=np.float64)
+ if not (np.isfinite(t[a:b]).all() and np.isfinite(ind[a:b]).all()
+ and np.isfinite(f[a:b]).all()):
+ raise ViscoelasticityError(NONFINITE_RESPONSE, "non-finite hold response")
+ return t[a:b], ind[a:b], f[a:b], np.arange(a, b)
+
+
+@dataclass(frozen=True)
+class RelaxationResponseResult:
+ """Stress-relaxation response of a displacement hold."""
+
+ relative_time: np.ndarray
+ indentation: np.ndarray
+ force: np.ndarray
+ normalized_force: np.ndarray
+ hold_indices: np.ndarray
+ hold_start_time: float
+ force_at_hold_start: float
+ equilibrium_force_estimate: float
+ units: str = "s / m / N"
+ warnings: tuple[str, ...] = ()
+
+
+def extract_stress_relaxation(
+ prepared: ForcePreparationResult,
+ protocol: ViscoelasticProtocolResult,
+ *,
+ segment: str = "extend",
+ hold_kind: str = "hold_displacement",
+ equilibrium_tail_fraction: float = 0.1,
+) -> RelaxationResponseResult:
+ """Extract the normalized stress-relaxation response of the hold.
+
+ Requires a displacement-hold region; the normalized response is
+ F(t)/F(t0) on the relative hold time. The equilibrium-force estimate
+ is the mean of the last ``equilibrium_tail_fraction`` of the hold
+ (documented estimate, not a guaranteed equilibrium).
+ """
+ t, ind, f, idx = _hold_window(prepared, protocol, hold_kind, segment)
+ if t.size < 2:
+ raise ViscoelasticityError(EMPTY_REGION, "hold region too short")
+ t0 = float(t[0])
+ f0 = float(f[0])
+ if f0 == 0.0:
+ raise ViscoelasticityError(INVALID_RESPONSE, "hold-start force is zero")
+ tail = max(1, int(round(t.size * equilibrium_tail_fraction)))
+ eq = float(np.mean(f[-tail:]))
+ return RelaxationResponseResult(
+ relative_time=t - t0,
+ indentation=ind,
+ force=f,
+ normalized_force=f / f0,
+ hold_indices=idx,
+ hold_start_time=t0,
+ force_at_hold_start=f0,
+ equilibrium_force_estimate=eq,
+ warnings=(f"equilibrium estimate: mean of last {tail} hold samples",),
+ )
+
+
+@dataclass(frozen=True)
+class CreepResponseResult:
+ """Creep response of a force hold."""
+
+ relative_time: np.ndarray
+ force: np.ndarray
+ indentation: np.ndarray
+ compliance_proxy: np.ndarray
+ hold_indices: np.ndarray
+ hold_start_time: float
+ force_hold_value: float
+ indentation_at_hold_start: float
+ units: str = "s / N / m / (m/N)"
+ warnings: tuple[str, ...] = ()
+
+
+def extract_creep_compliance(
+ prepared: ForcePreparationResult,
+ protocol: ViscoelasticProtocolResult,
+ *,
+ segment: str = "extend",
+ hold_kind: str = "hold_force",
+ hold_force_median: bool = True,
+) -> CreepResponseResult:
+ """Extract the creep compliance proxy J(t) = indentation(t)/F_hold.
+
+ Requires a force-hold region; F_hold is the median (or mean) force over
+ the hold. The compliance proxy is a raw m/N ratio, not a calibrated
+ material compliance.
+ """
+ t, ind, f, idx = _hold_window(prepared, protocol, hold_kind, segment)
+ if t.size < 2:
+ raise ViscoelasticityError(EMPTY_REGION, "hold region too short")
+ f_hold = float(np.median(f)) if hold_force_median else float(np.mean(f))
+ if f_hold == 0.0:
+ raise ViscoelasticityError(INVALID_RESPONSE, "held force is zero")
+ # the compliance proxy is the INCREMENT from the hold start:
+ # (indentation(t) - indentation(0)) / F_hold. The increment is the
+ # standard creep-measurement quantity and is robust to the
+ # contact-coordinate precision (the absolute level is carried in
+ # indentation_at_hold_start).
+ return CreepResponseResult(
+ relative_time=t - float(t[0]),
+ force=f,
+ indentation=ind,
+ compliance_proxy=(ind - float(ind[0])) / f_hold,
+ hold_indices=idx,
+ hold_start_time=float(t[0]),
+ force_hold_value=f_hold,
+ indentation_at_hold_start=float(ind[0]),
+ warnings=("compliance increment = (indentation - indentation(0))/"
+ "F_hold (raw m/N, robust to the contact-coordinate "
+ "offset; not a calibrated material compliance)",),
+ )
diff --git a/src/spmkit/core/analysis/force_viscoelastic_errors.py b/src/spmkit/core/analysis/force_viscoelastic_errors.py
new file mode 100644
index 0000000..d3605c8
--- /dev/null
+++ b/src/spmkit/core/analysis/force_viscoelastic_errors.py
@@ -0,0 +1,30 @@
+"""Typed failures for the FS-F3 time-domain viscoelasticity batch."""
+
+MISSING_TIME = "MISSING_TIME"
+DUPLICATE_TIMESTAMPS = "DUPLICATE_TIMESTAMPS"
+NONMONOTONIC_TIME = "NONMONOTONIC_TIME"
+INSUFFICIENT_PROTOCOL = "INSUFFICIENT_PROTOCOL"
+AMBIGUOUS_PROTOCOL = "AMBIGUOUS_PROTOCOL"
+MISSING_CONTACT = "MISSING_CONTACT"
+PROTOCOL_MODEL_MISMATCH = "PROTOCOL_MODEL_MISMATCH"
+EMPTY_REGION = "EMPTY_REGION"
+INVALID_RESPONSE = "INVALID_RESPONSE"
+NONFINITE_RESPONSE = "NONFINITE_RESPONSE"
+INVALID_MODEL_PARAMETER = "INVALID_MODEL_PARAMETER"
+OPTIMIZATION_FAILED = "OPTIMIZATION_FAILED"
+PRONY_DUPLICATE_TAU = "PRONY_DUPLICATE_TAU"
+PRONY_NEGATIVE_TERM = "PRONY_NEGATIVE_TERM"
+LEE_RADOK_NONMONOTONIC = "LEE_RADOK_NONMONOTONIC"
+TING_HISTORY_UNAVAILABLE = "TING_HISTORY_UNAVAILABLE"
+CURVE_NOT_FIT_ELIGIBLE = "CURVE_NOT_FIT_ELIGIBLE"
+IDENTIFIABILITY_LIMITED = "IDENTIFIABILITY_LIMITED"
+NO_VISCOELASTIC_FIT = "NO_VISCOELASTIC_FIT"
+
+
+class ViscoelasticityError(ValueError):
+ """Typed FS-F3 failure with a machine-readable code."""
+
+ def __init__(self, code: str, message: str) -> None:
+ super().__init__(message)
+ self.code = code
+ self.message = message
diff --git a/src/spmkit/core/analysis/force_viscoelastic_fitting.py b/src/spmkit/core/analysis/force_viscoelastic_fitting.py
new file mode 100644
index 0000000..2200fb8
--- /dev/null
+++ b/src/spmkit/core/analysis/force_viscoelastic_fitting.py
@@ -0,0 +1,739 @@
+"""FS-F3 viscoelastic fitting: lumped models, Lee-Radok/Ting, comparison.
+
+One shared least-squares engine (scipy.optimize.curve_fit, already a
+required dependency) for the response-level fits; Lee-Radok and Ting fit the
+SLS relaxation modulus through the hereditary integral. Deterministic,
+immutable results, typed failures, explicit parameter counts and AIC/AICc/BIC
+over identical observations.
+"""
+
+from __future__ import annotations
+
+import math
+from collections.abc import Callable
+from dataclasses import dataclass, field
+
+import numpy as np
+from scipy.optimize import curve_fit
+
+from spmkit.core.analysis.force_foundation import ForcePreparationResult
+from spmkit.core.analysis.force_time_protocol import (
+ CreepResponseResult,
+ RelaxationResponseResult,
+ ViscoelasticProtocolResult,
+)
+from spmkit.core.analysis.force_viscoelastic_errors import (
+ CURVE_NOT_FIT_ELIGIBLE,
+ IDENTIFIABILITY_LIMITED,
+ INVALID_MODEL_PARAMETER,
+ MISSING_CONTACT,
+ NO_VISCOELASTIC_FIT,
+ NONFINITE_RESPONSE,
+ OPTIMIZATION_FAILED,
+ PROTOCOL_MODEL_MISMATCH,
+ TING_HISTORY_UNAVAILABLE,
+ ViscoelasticityError,
+)
+from spmkit.core.analysis.force_viscoelastic_models import (
+ forward_generalized_maxwell_normalized,
+ forward_kelvin_voigt_compliance,
+ forward_maxwell_normalized,
+ lee_radok_force,
+ sls_creep_to_relaxation,
+ sls_relaxation_to_creep,
+ ting_force,
+)
+
+
+@dataclass(frozen=True)
+class ViscoelasticFitResult:
+ """Deterministic viscoelastic fit of one model."""
+
+ model: str
+ protocol: str
+ response_type: str
+ success: bool
+ parameters: dict[str, float]
+ parameter_units: dict[str, str]
+ predicted_response: np.ndarray
+ residuals: np.ndarray
+ included_indices: np.ndarray
+ objective: float
+ covariance: dict[str, float] | None
+ condition_number: float
+ dof: int
+ rmse: float
+ aic: float
+ aicc: float
+ bic: float
+ warnings: tuple[str, ...] = ()
+ failure_reason: str | None = None
+ diagnostics: dict[str, object] = field(default_factory=dict)
+ provenance: dict[str, object] = field(default_factory=dict)
+
+
+@dataclass(frozen=True)
+class ViscoelasticModelComparisonResult:
+ """Model-relative comparison over identical observations."""
+
+ fits: tuple[ViscoelasticFitResult, ...]
+ delta_aicc: dict[str, float]
+ weights: dict[str, float]
+ recommended_model: str | None
+ ambiguous: bool
+ n_compared: int
+ warnings: tuple[str, ...] = ()
+ provenance: dict[str, object] = field(default_factory=dict)
+
+
+_UNITS = {
+ "E": "Pa", "E0": "Pa", "E_inf": "Pa", "E_ref": "Pa",
+ "tau": "s", "tau_relax": "s", "tau_retard": "s",
+ "eta": "Pa*s", "a": "dimensionless", "alpha": "dimensionless",
+ "alpha_i": "dimensionless", "tau_i": "s",
+ "J0": "m/N", "J_inf": "m/N", "J_inf_fit": "m/N",
+ "e_inf": "dimensionless", "F0": "N", "F_inf": "N",
+}
+
+
+def _fit_response(t: np.ndarray, y: np.ndarray, model_func: Callable[..., np.ndarray],
+ p0: list[float],
+ bounds: tuple[list[float], list[float]],
+ names: list[str], maxfev: int = 20000,
+ starts: list[list[float]] | None = None) -> tuple:
+ """Shared deterministic response fit engine (returns popt, pcov).
+
+ A deterministic multi-start (explicit start list, best objective wins)
+ protects against flat-valley local minima in the time-constant
+ directions.
+ """
+ t = np.asarray(t, dtype=np.float64)
+ y = np.asarray(y, dtype=np.float64)
+ if t.ndim != 1 or t.size != y.size or t.size == 0:
+ raise ViscoelasticityError(NONFINITE_RESPONSE, "invalid response arrays")
+ if not (np.isfinite(t).all() and np.isfinite(y).all()):
+ raise ViscoelasticityError(NONFINITE_RESPONSE, "non-finite response data")
+ if t.size < len(p0) + 2:
+ raise ViscoelasticityError(NO_VISCOELASTIC_FIT,
+ f"too few samples for {len(p0)} parameters")
+ candidates = starts if starts else [p0]
+ best: tuple | None = None
+ best_sse = float("inf")
+ for start in candidates:
+ try:
+ popt, pcov = curve_fit(model_func, t, y, p0=list(start), bounds=bounds,
+ maxfev=maxfev)
+ sse = float(np.sum((model_func(t, *popt) - y) ** 2))
+ except Exception: # noqa: BLE001 - a failed start is skipped
+ continue
+ if sse < best_sse:
+ best_sse = sse
+ best = (popt, pcov)
+ if best is None:
+ raise ViscoelasticityError(OPTIMIZATION_FAILED,
+ "optimizer failed from all deterministic starts")
+ return best
+
+
+def _finalize_fit(model: str, protocol: str, response_type: str, t: np.ndarray,
+ y: np.ndarray, params: dict[str, float], units: dict[str, str],
+ predicted: np.ndarray, popt: np.ndarray, pcov: np.ndarray | None,
+ names: list[str], idx: np.ndarray, warnings: list[str],
+ provenance: dict[str, object]) -> ViscoelasticFitResult:
+ residuals = y - predicted
+ n = y.size
+ k = len(names)
+ dof = n - k
+ sse = float(np.sum(residuals**2))
+ rmse = float(np.sqrt(np.mean(residuals**2)))
+ aic = n * math.log(sse / n + 1e-300) + 2 * k
+ aicc = aic + (2 * k * (k + 1)) / max(1, n - k - 1)
+ bic = n * math.log(sse / n + 1e-300) + k * math.log(n)
+ cov = {}
+ cond = 0.0
+ if pcov is not None and np.all(np.isfinite(pcov)):
+ for i, a in enumerate(names):
+ for j, b in enumerate(names):
+ cov[f"{a}__{b}"] = float(pcov[i, j])
+ try:
+ cond = float(np.linalg.cond(pcov))
+ except np.linalg.LinAlgError: # pragma: no cover - degenerate matrix
+ cond = float("inf")
+ return ViscoelasticFitResult(
+ model=model, protocol=protocol, response_type=response_type, success=True,
+ parameters=params, parameter_units=units, predicted_response=predicted,
+ residuals=residuals, included_indices=idx, objective=sse,
+ covariance=cov if cov else None, condition_number=cond, dof=dof, rmse=rmse,
+ aic=aic, aicc=aicc, bic=bic, warnings=tuple(warnings),
+ diagnostics={"n_points": n, "free_parameters": names},
+ provenance=provenance,
+ )
+
+
+def _response_arrays(response: object, kind: str) -> tuple[np.ndarray, np.ndarray]:
+ if kind == "creep":
+ if not isinstance(response, CreepResponseResult):
+ raise ViscoelasticityError(
+ PROTOCOL_MODEL_MISMATCH,
+ "Kelvin-Voigt/SLS-creep fits require a CreepResponseResult")
+ return (np.asarray(response.relative_time, dtype=np.float64),
+ np.asarray(response.compliance_proxy, dtype=np.float64))
+ if not isinstance(response, RelaxationResponseResult):
+ raise ViscoelasticityError(
+ PROTOCOL_MODEL_MISMATCH,
+ "relaxation fits require a RelaxationResponseResult")
+ return (np.asarray(response.relative_time, dtype=np.float64),
+ np.asarray(response.normalized_force, dtype=np.float64))
+
+
+def _modulus_from_hold(response: RelaxationResponseResult, tip_radius: float,
+ poisson: float) -> tuple[float, float]:
+ """E0 from the hold force and indentation via the spherical contact."""
+ d0 = float(response.indentation[0])
+ if d0 <= 0.0:
+ raise ViscoelasticityError(MISSING_CONTACT, "hold indentation must be positive")
+ f0 = response.force_at_hold_start
+ est = f0 / ((4.0 / 3.0) * math.sqrt(tip_radius) * d0**1.5)
+ return est * (1.0 - poisson**2), d0
+
+
+def fit_kelvin_voigt(response: CreepResponseResult, *,
+ E_initial: float | None = None,
+ tau_initial: float | None = None) -> ViscoelasticFitResult:
+ """J(t) = (1/E)(1 - exp(-t/tau)); tau = eta/E (retardation time)."""
+ t, y = _response_arrays(response, "creep")
+ if E_initial is None:
+ E_initial = 1.0 / max(float(y[-1]), 1e-300) if y[-1] > 0 else 1e3
+ if tau_initial is None:
+ tau_initial = float(t[-1]) / 3.0 if t[-1] > 0 else 1.0
+
+ def model(tt: np.ndarray, e: float, tau: float) -> np.ndarray:
+ return forward_kelvin_voigt_compliance(tt, e, tau)
+
+ popt, pcov = _fit_response(
+ t, y, model, [float(E_initial), float(tau_initial)],
+ ([1e-9, 1e-12], [1e15, 1e12]), ["E", "tau"],
+ starts=[[float(E_initial), float(tau_initial)],
+ [float(E_initial), float(tau_initial) / 10.0],
+ [float(E_initial), float(tau_initial) * 10.0]])
+ E, tau = float(popt[0]), float(popt[1])
+ params = {"E": E, "tau": tau, "eta": E * tau}
+ predicted = model(t, E, tau)
+ return _finalize_fit("kelvin_voigt", response_type="creep", protocol="CREEP",
+ t=t, y=y, params=params,
+ units={"E": "Pa", "tau": "s", "eta": "Pa*s"},
+ predicted=predicted, popt=popt, pcov=pcov,
+ names=["E", "tau"], idx=response.hold_indices,
+ warnings=[], provenance={"model": "kelvin_voigt"})
+
+
+def fit_maxwell(response: RelaxationResponseResult, *,
+ tip_radius: float | None = None,
+ poisson: float = 0.3) -> ViscoelasticFitResult:
+ """n(t) = exp(-t/tau); tau = eta/E. E is recovered only when the tip
+ radius is provided (spherical contact proportionality)."""
+ t, y = _response_arrays(response, "relaxation")
+ tau_initial = float(t[-1]) / 3.0 if t[-1] > 0 else 1.0
+ popt, pcov = _fit_response(
+ t, y, forward_maxwell_normalized, [tau_initial], ([1e-12], [1e12]), ["tau"])
+ tau = float(popt[0])
+ params = {"tau": tau}
+ units = {"tau": "s"}
+ warnings: list[str] = []
+ if tip_radius is not None:
+ if tip_radius <= 0.0:
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER,
+ "tip radius must be positive")
+ E0, _d0 = _modulus_from_hold(response, tip_radius, poisson)
+ params["E"] = E0
+ params["eta"] = E0 * tau
+ units["E"] = "Pa"
+ units["eta"] = "Pa*s"
+ else:
+ warnings.append("no tip radius: the modulus is not identifiable from "
+ "the normalized response alone")
+ predicted = forward_maxwell_normalized(t, tau)
+ return _finalize_fit("maxwell", response_type="relaxation", protocol="STRESS_RELAXATION",
+ t=t, y=y, params=params, units=units, predicted=predicted,
+ popt=popt, pcov=pcov, names=["tau"], idx=response.hold_indices,
+ warnings=warnings, provenance={"model": "maxwell"})
+
+
+def fit_standard_linear_solid(
+ response: RelaxationResponseResult | CreepResponseResult, *,
+ tip_radius: float | None = None,
+ poisson: float = 0.3,
+ tau_initial: float | None = None) -> ViscoelasticFitResult:
+ """SLS on a relaxation or creep response.
+
+ Relaxation: n(t) = 1 - a(1 - exp(-t/tau_relax)), a = (E0-E_inf)/E0.
+ Creep: J(t) = J_inf - (J_inf - J0) exp(-t/tau_retard).
+ Both representations are reported with the standard conversions.
+ """
+ if isinstance(response, CreepResponseResult):
+ t, y = _response_arrays(response, "creep")
+ # the creep response is the compliance INCREMENT from the hold
+ # start: (J_inf - J0) (1 - exp(-t/tau_retard)); the absolute level
+ # J0 = indentation(0)/F_hold is carried by the response
+ j0_abs = (float(response.indentation_at_hold_start)
+ / float(response.force_hold_value)) if response.force_hold_value else 0.0
+ dj_initial = float(y[-1]) if y[-1] > 0 else 1e-9
+ tau_init = float(t[-1]) / 3.0 if tau_initial is None else float(tau_initial)
+
+ def model_creep(tt: np.ndarray, dj: float, tau: float) -> np.ndarray:
+ return dj * (1.0 - np.exp(-tt / tau))
+
+ popt, pcov = _fit_response(
+ t, y, model_creep, [dj_initial, tau_init],
+ ([1e-15, 1e-12], [1e6, 1e12]), ["dJ", "tau_retard"],
+ starts=[[dj_initial, tau_init],
+ [dj_initial, tau_init * 4.0],
+ [dj_initial, tau_init / 4.0]])
+ dj, tau_ret = float(popt[0]), float(popt[1])
+ j0 = j0_abs
+ j_inf = j0_abs + dj
+ e0, e_inf, tau_rel = sls_creep_to_relaxation(j0, j_inf, tau_ret)
+ params = {"J0": j0, "J_inf": j_inf, "tau_retard": tau_ret,
+ "E0": e0, "E_inf": e_inf, "tau_relax": tau_rel}
+ units = {"J0": "m/N", "J_inf": "m/N", "tau_retard": "s",
+ "E0": "Pa", "E_inf": "Pa", "tau_relax": "s"}
+ predicted = model_creep(t, dj, tau_ret)
+ return _finalize_fit("standard_linear_solid", response_type="creep",
+ protocol="CREEP", t=t, y=y, params=params, units=units,
+ predicted=predicted, popt=popt, pcov=pcov,
+ names=["dJ", "tau_retard"],
+ idx=response.hold_indices, warnings=[],
+ provenance={"representation": "creep_increment"})
+
+ t, y = _response_arrays(response, "relaxation")
+ a_initial = max(0.0, min(1.0 - float(y[-1]), 0.5)) if y[-1] < 1.0 else 0.5
+ tau_init = float(t[-1]) / 3.0 if tau_initial is None else float(tau_initial)
+
+ def model_sls_relax(tt: np.ndarray, a: float, tau: float) -> np.ndarray:
+ return 1.0 - a * (1.0 - np.exp(-tt / tau))
+
+ popt, pcov = _fit_response(
+ t, y, model_sls_relax, [a_initial, tau_init], ([0.0, 1e-12], [1.0, 1e12]),
+ ["a", "tau_relax"])
+ a, tau_rel = float(popt[0]), float(popt[1])
+ params = {"a": a, "tau_relax": tau_rel}
+ units = {"a": "dimensionless", "tau_relax": "s"}
+ warnings: list[str] = []
+ if tip_radius is not None:
+ if tip_radius <= 0.0:
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER, "tip radius must be positive")
+ E0, _d0 = _modulus_from_hold(response, tip_radius, poisson)
+ E_inf = E0 * (1.0 - a)
+ j0, j_inf, tau_ret = sls_relaxation_to_creep(E0, E_inf, tau_rel)
+ params.update({"E0": E0, "E_inf": E_inf, "tau_retard": tau_ret,
+ "J0": j0, "J_inf": j_inf})
+ units.update({"E0": "Pa", "E_inf": "Pa", "tau_retard": "s",
+ "J0": "m/N", "J_inf": "m/N"})
+ else:
+ warnings.append("no tip radius: absolute moduli not identifiable from "
+ "the normalized relaxation response")
+ predicted = model_sls_relax(t, a, tau_rel)
+ return _finalize_fit("standard_linear_solid", response_type="relaxation",
+ protocol="STRESS_RELAXATION", t=t, y=y, params=params,
+ units=units, predicted=predicted, popt=popt, pcov=pcov,
+ names=["a", "tau_relax"], idx=response.hold_indices,
+ warnings=warnings, provenance={"representation": "relaxation"})
+
+
+def fit_generalized_maxwell(response: RelaxationResponseResult, *,
+ n_terms: int = 2,
+ tip_radius: float | None = None,
+ poisson: float = 0.3) -> ViscoelasticFitResult:
+ """n(t) = 1 - sum(alpha) + sum(alpha_i exp(-t/tau_i)), alpha_i >= 0,
+ sum(alpha) <= 1, tau_i > 0. Deterministic ordering by ascending tau;
+ no claim that the recovered spectrum is unique."""
+ if n_terms < 1 or n_terms > 8:
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER,
+ "n_terms must be in [1, 8]")
+ t, y = _response_arrays(response, "relaxation")
+ span = float(t[-1]) if t[-1] > 0 else 1.0
+ tau0 = np.geomspace(max(span * 1e-3, 1e-9), span, n_terms)
+ alpha0 = np.full(n_terms, 1.0 / n_terms)
+ p0 = list(alpha0) + list(tau0)
+
+ def model_gm(tt: np.ndarray, *args: float) -> np.ndarray:
+ al = np.asarray(args[:n_terms])
+ ta = np.asarray(args[n_terms:])
+ return forward_generalized_maxwell_normalized(tt, al, ta, _validate=False)
+
+ lo = [0.0] * n_terms + [1e-9] * n_terms
+ hi = [1.0] * n_terms + [1e9] * n_terms
+ popt, pcov = _fit_response(t, y, model_gm, p0, (lo, hi),
+ [f"alpha_i[{i}]" for i in range(n_terms)]
+ + [f"tau_i[{i}]" for i in range(n_terms)])
+ alpha = np.asarray(popt[:n_terms])
+ tau = np.asarray(popt[n_terms:])
+ if alpha.sum() > 1.0 + 1e-9 or np.any(alpha < -1e-9) or np.any(tau <= 0.0):
+ raise ViscoelasticityError(
+ IDENTIFIABILITY_LIMITED,
+ f"Prony fit violates the public constraints (sum(alpha)="
+ f"{alpha.sum():.3f}, min alpha={float(np.min(alpha)):.3e})")
+ order = np.argsort(tau, kind="stable")
+ tau = tau[order]
+ alpha = alpha[order]
+ warnings: list[str] = ["no claim that the recovered Prony spectrum is unique"]
+ if n_terms >= 2:
+ rel_gaps = np.diff(tau) / tau[:-1]
+ if np.any(rel_gaps < 1e-3):
+ warnings.append("nearly equal relaxation times: bounded identifiability")
+ params = {}
+ units = {}
+ for i in range(n_terms):
+ params[f"alpha_i[{i}]"] = float(alpha[i])
+ params[f"tau_i[{i}]"] = float(tau[i])
+ units[f"alpha_i[{i}]"] = "dimensionless"
+ units[f"tau_i[{i}]"] = "s"
+ predicted = model_gm(t, *np.concatenate([alpha, tau]))
+ return _finalize_fit("generalized_maxwell", response_type="relaxation",
+ protocol="STRESS_RELAXATION", t=t, y=y, params=params,
+ units=units, predicted=predicted, popt=popt, pcov=pcov,
+ names=[f"alpha_i[{i}]" for i in range(n_terms)]
+ + [f"tau_i[{i}]" for i in range(n_terms)],
+ idx=response.hold_indices, warnings=warnings,
+ provenance={"n_terms": n_terms, "model": "generalized_maxwell"})
+
+
+def fit_power_law_relaxation(response: RelaxationResponseResult, *,
+ t_ref: float | None = None,
+ with_equilibrium: bool = False,
+ tip_radius: float | None = None,
+ poisson: float = 0.3) -> ViscoelasticFitResult:
+ """n(t) = (t/t_ref)^(-alpha) (optionally + equilibrium offset).
+
+ t = 0 is excluded (singularity); t_ref defaults to the first positive
+ relative hold time."""
+ t, y = _response_arrays(response, "relaxation")
+ # the power-law response is fitted from t_ref onward (t = 0 is
+ # singular; the pre-reference plateau is excluded)
+ keep = t >= t_ref if t_ref is not None else t > 0.0
+ if keep.sum() < 4:
+ raise ViscoelasticityError(NO_VISCOELASTIC_FIT,
+ "not enough samples for the power law")
+ t_pos = t[keep]
+ y_pos = y[keep]
+ idx = response.hold_indices[keep]
+ if t_ref is None:
+ t_ref = float(t_pos[0])
+ if t_ref <= 0.0:
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER, "t_ref must be positive")
+ warnings = ["t = 0 excluded (singularity); t_ref = "
+ f"{t_ref:.3e} s (first positive hold time)"] if t_ref == t_pos[0] \
+ else [f"t_ref = {t_ref:.3e} s"]
+
+ if with_equilibrium:
+ def model_pl_eq(tt: np.ndarray, e_inf: float, alpha: float) -> np.ndarray:
+ return e_inf + (1.0 - e_inf) * np.power(tt / t_ref, -alpha)
+
+ popt, pcov = _fit_response(
+ t_pos, y_pos, model_pl_eq, [max(float(y_pos[-1]), 0.0), 0.5],
+ ([0.0, 1e-6], [1.0, 1.0 - 1e-6]), ["e_inf", "alpha"])
+ e_inf, alpha = float(popt[0]), float(popt[1])
+ params = {"e_inf": e_inf, "alpha": alpha, "t_ref": t_ref}
+ units = {"e_inf": "dimensionless", "alpha": "dimensionless", "t_ref": "s"}
+ predicted = model_pl_eq(t_pos, e_inf, alpha)
+ names = ["e_inf", "alpha"]
+ else:
+ def model_pl(tt: np.ndarray, alpha: float) -> np.ndarray:
+ return np.power(tt / t_ref, -alpha)
+
+ popt, pcov = _fit_response(
+ t_pos, y_pos, model_pl, [0.5], ([1e-6], [1.0 - 1e-6]), ["alpha"])
+ alpha = float(popt[0])
+ params = {"alpha": alpha, "t_ref": t_ref}
+ units = {"alpha": "dimensionless", "t_ref": "s"}
+ predicted = model_pl(t_pos, alpha)
+ names = ["alpha"]
+ if tip_radius is not None:
+ E_ref, _d0 = _modulus_from_hold(response, tip_radius, poisson)
+ params["E_ref"] = E_ref
+ units["E_ref"] = "Pa"
+ else:
+ warnings.append("no tip radius: E_ref not identifiable from the "
+ "normalized response")
+ return _finalize_fit("power_law_relaxation", response_type="relaxation",
+ protocol="STRESS_RELAXATION", t=t_pos, y=y_pos,
+ params=params, units=units, predicted=predicted,
+ popt=popt, pcov=pcov, names=names, idx=idx,
+ warnings=warnings, provenance={"t_ref": t_ref,
+ "with_equilibrium": with_equilibrium})
+
+
+def _sls_integral_fit(t: np.ndarray, delta: np.ndarray, force: np.ndarray,
+ integral_func: Callable[..., np.ndarray], p0: list[float],
+ names: list[str], model_label: str,
+ protocol: str) -> ViscoelasticFitResult:
+ """Fit the SLS relaxation modulus through a hereditary integral."""
+ t = np.asarray(t, dtype=np.float64)
+ delta = np.asarray(delta, dtype=np.float64)
+ force = np.asarray(force, dtype=np.float64)
+ if not (np.isfinite(t).all() and np.isfinite(delta).all()
+ and np.isfinite(force).all()):
+ raise ViscoelasticityError(NONFINITE_RESPONSE, "non-finite integral inputs")
+ # normalize the objective by the force scale: the raw-Newton residual is
+ # ~1e-9 while the parameters are ~1e3..1e6, which stalls the optimizer's
+ # gradient tolerance; the normalization is a pure numerical scaling and
+ # all reported quantities stay in SI units. The SLS constraint
+ # E_inf <= E0 is enforced by the parameterization a = (E0 - E_inf)/E0
+ # with a in [0, 1].
+ scale = float(np.max(np.abs(force))) or 1.0
+ y = force / scale
+ e0_0, e_inf_0, tau_0 = p0
+ a_0 = max(0.0, min((e0_0 - e_inf_0) / e0_0, 1.0)) if e0_0 > 0 else 0.5
+
+ def model_integral(tt: np.ndarray, e0: float, a: float, tau: float) -> np.ndarray:
+ mp = {"E0": e0, "E_inf": e0 * (1.0 - a), "tau": tau}
+ return integral_func(tt, delta, mp) / scale
+
+ try:
+ popt, pcov = curve_fit(model_integral, t, y, p0=[e0_0, a_0, tau_0],
+ bounds=([1e3, 0.0, 1e-9], [1e15, 1.0, 1e6]),
+ maxfev=40000)
+ except ViscoelasticityError:
+ raise
+ except Exception as exc: # noqa: BLE001 - typed wrapper
+ raise ViscoelasticityError(OPTIMIZATION_FAILED, f"optimizer failed: {exc}") from exc
+ e0 = float(popt[0])
+ e_inf = e0 * (1.0 - float(popt[1]))
+ tau = float(popt[2])
+ predicted = model_integral(t, e0, float(popt[1]), tau) * scale
+ params = {"E0": e0, "E_inf": e_inf, "tau_relax": tau}
+ units = {"E0": "Pa", "E_inf": "Pa", "tau_relax": "s"}
+ return _finalize_fit(model_label, response_type="full_curve", protocol=protocol,
+ t=t, y=force, params=params, units=units,
+ predicted=predicted, popt=popt, pcov=pcov, names=names,
+ idx=np.arange(t.size), warnings=[],
+ provenance={"integral": model_label})
+
+
+def _loading_history(prepared: ForcePreparationResult,
+ protocol: ViscoelasticProtocolResult
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
+ approach = prepared.curve.extend
+ if approach is None or approach.separation is None or approach.force is None \
+ or approach.time is None:
+ raise ViscoelasticityError(MISSING_CONTACT, "no prepared approach branch")
+ t = np.asarray(approach.time, dtype=np.float64)
+ if t.size == 0 or not np.isfinite(t).all() or np.any(np.diff(t) <= 0.0):
+ raise ViscoelasticityError(OPTIMIZATION_FAILED, "invalid approach time axis")
+ reg = protocol.region("loading", "extend")
+ if reg is None:
+ raise ViscoelasticityError(PROTOCOL_MODEL_MISMATCH,
+ "protocol has no loading region on the approach")
+ a, b = reg.start_index, reg.end_index + 1
+ if b > t.size:
+ raise ViscoelasticityError(OPTIMIZATION_FAILED, "loading region out of range")
+ sep = np.asarray(approach.separation, dtype=np.float64)
+ ind = sep - float(prepared.contact.selected.coordinate)
+ # trim to the contact onward: the loading history for the hereditary
+ # integrals requires indentation >= 0 (documented trimming rule)
+ t_region, ind_region = t[a:b], ind[a:b]
+ keep = ind_region >= 0.0
+ if keep.sum() < 5:
+ raise ViscoelasticityError(OPTIMIZATION_FAILED,
+ "loading region has no indentation >= 0 samples")
+ return t_region[keep], ind_region[keep], keep
+
+
+def fit_lee_radok_sphere(prepared: ForcePreparationResult,
+ protocol: ViscoelasticProtocolResult, *,
+ tip_radius: float,
+ poisson: float = 0.3,
+ E0_initial: float = 1e6,
+ E_inf_initial: float = 5e5,
+ tau_initial: float = 1.0) -> ViscoelasticFitResult:
+ """Fit the SLS relaxation modulus through the Lee-Radok integral on the
+ monotonic loading region (spherical contact, loading only)."""
+ if tip_radius <= 0.0:
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER, "tip radius must be positive")
+ t, ind, keep = _loading_history(prepared, protocol)
+ approach = prepared.curve.extend
+ if approach is None or approach.force is None:
+ raise ViscoelasticityError(CURVE_NOT_FIT_ELIGIBLE, "no calibrated approach")
+ force = np.asarray(approach.force, dtype=np.float64)
+ reg = protocol.region("loading", "extend")
+ if reg is None:
+ raise ViscoelasticityError(OPTIMIZATION_FAILED, "no loading region")
+ f = force[reg.start_index: reg.end_index + 1][keep]
+ if f.size != t.size:
+ raise ViscoelasticityError(OPTIMIZATION_FAILED, "loading region length mismatch")
+ return _sls_integral_fit(
+ t, ind, f,
+ lambda tt, delta, mp: lee_radok_force(tt, delta, mp, 1.0, tip_radius, poisson),
+ [E0_initial, E_inf_initial, tau_initial], ["E0", "E_inf", "tau_relax"],
+ "lee_radok_sphere", "LOADING_RAMP")
+
+
+def fit_ting_sphere(
+ prepared: ForcePreparationResult,
+ protocol: ViscoelasticProtocolResult, *,
+ tip_radius: float,
+ poisson: float = 0.3,
+ E0_initial: float = 1e6,
+ E_inf_initial: float = 5e5,
+ tau_initial: float = 1.0) -> ViscoelasticFitResult:
+ """Fit the SLS relaxation modulus through the Ting integral over the
+ loading and unloading branches (contact-time memory)."""
+ if tip_radius <= 0.0:
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER, "tip radius must be positive")
+ loading = protocol.region("loading", "extend")
+ unloading = protocol.region("unloading", "retract")
+ if loading is None or unloading is None:
+ raise ViscoelasticityError(
+ TING_HISTORY_UNAVAILABLE,
+ "Ting requires a loading region on the approach and an unloading "
+ "region on the retract")
+ approach = prepared.curve.extend
+ retract = prepared.curve.retract
+ if approach is None or approach.force is None or approach.time is None:
+ raise ViscoelasticityError(MISSING_CONTACT, "no prepared approach branch")
+ if retract is None or retract.separation is None or retract.force is None \
+ or retract.time is None:
+ raise ViscoelasticityError(TING_HISTORY_UNAVAILABLE,
+ "no prepared retract branch for the unloading")
+ zc = float(prepared.contact.selected.coordinate)
+ tl, il, keep = _loading_history(prepared, protocol)
+ fl = np.asarray(approach.force, dtype=np.float64)[
+ loading.start_index: loading.end_index + 1][keep]
+ t_r = np.asarray(retract.time, dtype=np.float64)
+ sep_r = np.asarray(retract.separation, dtype=np.float64)
+ f_r = np.asarray(retract.force, dtype=np.float64)
+ iu = sep_r[unloading.start_index: unloading.end_index + 1] - zc
+ tu = t_r[unloading.start_index: unloading.end_index + 1]
+ fu = f_r[unloading.start_index: unloading.end_index + 1]
+ # truncate the unloading history at the contact (indentation >= 0):
+ # out-of-contact samples carry no force and are outside the model
+ keep_u = iu >= 0.0
+ if keep_u.sum() < 5:
+ raise ViscoelasticityError(TING_HISTORY_UNAVAILABLE,
+ "unloading history has no in-contact samples")
+ iu, tu, fu = iu[keep_u], tu[keep_u], fu[keep_u]
+ if iu[0] > float(np.max(il)) + 1e-15:
+ raise ViscoelasticityError(
+ TING_HISTORY_UNAVAILABLE,
+ "unloading indentation exceeds the loading maximum (missing history)")
+ if tu[0] <= tl[-1]:
+ # the retract time axis may restart at zero; treat it as continuing
+ # from the loading end for the heredity evaluation
+ tu = tu - tu[0] + tl[-1]
+
+ t_full = np.concatenate([tl, tu])
+ f_full = np.concatenate([fl, fu])
+ scale = float(np.max(np.abs(f_full))) or 1.0
+ y_full = f_full / scale
+ a_0 = (max(0.0, min((E0_initial - E_inf_initial) / E0_initial, 1.0))
+ if E0_initial > 0 else 0.5)
+
+ def model_ting(tt: np.ndarray, e0: float, a: float, tau: float) -> np.ndarray:
+ mp = {"E0": e0, "E_inf": e0 * (1.0 - a), "tau": tau}
+ fl_ = lee_radok_force(tl, il, mp, 1.0, tip_radius, poisson)
+ fu_ = ting_force(tl, il, tu, iu, mp, 1.0, tip_radius, poisson)[len(tl):]
+ return np.concatenate([fl_, fu_]) / scale
+
+ try:
+ popt, pcov = curve_fit(model_ting, t_full, y_full, p0=[E0_initial, a_0, tau_initial],
+ bounds=([1e3, 0.0, 1e-9], [1e15, 1.0, 1e6]), maxfev=40000)
+ except ViscoelasticityError:
+ raise
+ except Exception as exc: # noqa: BLE001 - typed wrapper
+ raise ViscoelasticityError(OPTIMIZATION_FAILED, f"optimizer failed: {exc}") from exc
+ e0 = float(popt[0])
+ e_inf = e0 * (1.0 - float(popt[1]))
+ tau = float(popt[2])
+ predicted = model_ting(t_full, e0, float(popt[1]), tau) * scale
+ return _finalize_fit("ting_sphere", response_type="full_curve",
+ protocol="TRIANGULAR_LOADING", t=t_full, y=f_full,
+ params={"E0": e0, "E_inf": e_inf, "tau_relax": tau},
+ units={"E0": "Pa", "E_inf": "Pa", "tau_relax": "s"},
+ predicted=predicted, popt=popt, pcov=pcov,
+ names=["E0", "E_inf", "tau_relax"],
+ idx=np.arange(t_full.size), warnings=[],
+ provenance={"integral": "ting_sphere"})
+
+
+RELAXATION_MODELS = ("maxwell", "standard_linear_solid", "generalized_maxwell",
+ "power_law_relaxation")
+CREEP_MODELS = ("kelvin_voigt", "standard_linear_solid")
+
+
+def compare_viscoelastic_models(
+ response: RelaxationResponseResult | CreepResponseResult, *,
+ models: tuple[str, ...] | None = None,
+ tip_radius: float | None = None,
+ poisson: float = 0.3,
+ n_terms: int = 2,
+ t_ref: float | None = None,
+) -> ViscoelasticModelComparisonResult:
+ """Model-relative AICc comparison over identical observations.
+
+ No physical-truth claim: the weights are relative support on this
+ response, not a probability of physical correctness.
+ """
+ if isinstance(response, CreepResponseResult):
+ candidates = models or CREEP_MODELS
+ else:
+ candidates = models or RELAXATION_MODELS
+ fits: list[ViscoelasticFitResult] = []
+ warnings: list[str] = []
+ for model in candidates:
+ try:
+ if model == "kelvin_voigt":
+ if not isinstance(response, CreepResponseResult):
+ raise ViscoelasticityError(
+ PROTOCOL_MODEL_MISMATCH,
+ "kelvin_voigt requires a creep response")
+ fits.append(fit_kelvin_voigt(response))
+ elif model == "maxwell":
+ if not isinstance(response, RelaxationResponseResult):
+ raise ViscoelasticityError(
+ PROTOCOL_MODEL_MISMATCH,
+ "maxwell requires a relaxation response")
+ fits.append(fit_maxwell(response, tip_radius=tip_radius, poisson=poisson))
+ elif model == "standard_linear_solid":
+ fits.append(fit_standard_linear_solid(
+ response, tip_radius=tip_radius, poisson=poisson))
+ elif model == "generalized_maxwell":
+ if not isinstance(response, RelaxationResponseResult):
+ raise ViscoelasticityError(
+ PROTOCOL_MODEL_MISMATCH,
+ "generalized_maxwell requires a relaxation response")
+ fits.append(fit_generalized_maxwell(
+ response, n_terms=n_terms, tip_radius=tip_radius, poisson=poisson))
+ elif model == "power_law_relaxation":
+ if not isinstance(response, RelaxationResponseResult):
+ raise ViscoelasticityError(
+ PROTOCOL_MODEL_MISMATCH,
+ "power_law_relaxation requires a relaxation response")
+ fits.append(fit_power_law_relaxation(
+ response, t_ref=t_ref, tip_radius=tip_radius, poisson=poisson))
+ else:
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER,
+ f"unknown model {model!r}")
+ except ViscoelasticityError as exc:
+ warnings.append(f"{model}: {exc.code}")
+ if not fits:
+ raise ViscoelasticityError(NO_VISCOELASTIC_FIT, "no model fit succeeded")
+ # common observation set: the union response samples of the successful
+ # fits (each fit uses its own included indices; on identical responses
+ # the sets coincide)
+ idx0 = fits[0].included_indices
+ for f in fits[1:]:
+ if not np.array_equal(idx0, f.included_indices):
+ warnings.append("fits use different observation sets; AICc not "
+ "strictly comparable")
+ delta_aicc = {f.model: f.aicc - min(x.aicc for x in fits) for f in fits}
+ total_w = sum(math.exp(-0.5 * d) for d in delta_aicc.values())
+ weights = {m: math.exp(-0.5 * delta_aicc[m]) / total_w for m in delta_aicc}
+ best = min(fits, key=lambda f: f.aicc)
+ ambiguous = (sorted(f.aicc for f in fits)[1] - best.aicc < 4.0
+ if len(fits) > 1 else False)
+ return ViscoelasticModelComparisonResult(
+ fits=tuple(fits), delta_aicc=delta_aicc, weights=weights,
+ recommended_model=best.model if not ambiguous else None,
+ ambiguous=ambiguous, n_compared=len(fits), warnings=tuple(warnings),
+ provenance={"criterion": "aicc", "response_type":
+ "creep" if isinstance(response, CreepResponseResult) else "relaxation"})
diff --git a/src/spmkit/core/analysis/force_viscoelastic_models.py b/src/spmkit/core/analysis/force_viscoelastic_models.py
new file mode 100644
index 0000000..b2acab6
--- /dev/null
+++ b/src/spmkit/core/analysis/force_viscoelastic_models.py
@@ -0,0 +1,372 @@
+"""FS-F3 viscoelastic forward models and hereditary integrals.
+
+Frozen equations (SI units). Lumped models operate on normalized responses;
+Lee-Radok and Ting are spherical-contact hereditary integrals.
+
+Reduced modulus E* = E/(1-nu^2). The spherical contact coefficient is
+c = (4/3) sqrt(R) E*.
+
+LEE-RADOK (monotonic loading only):
+ F(t) = c * int_0^t E(t - t') d/dt' [delta(t')^1.5] dt'
+ The contact radius a = sqrt(R*delta) must never decrease: delta must be
+ monotone non-decreasing, else LEE_RADOK_NONMONOTONIC.
+
+TING (loading + unloading, contact-time memory):
+ loading (t <= t_m): identical to Lee-Radok;
+ unloading (t > t_m): F(t) = c * int_0^{t1(t)} E(t - t') d/dt' [delta(t')^1.5] dt'
+ where t1(t) is the loading time with delta(t1) = delta(t) (the contact
+ radius during unloading equals the loading radius at t1). When the
+ loading history cannot be reconstructed: TING_HISTORY_UNAVAILABLE.
+
+Discrete quadrature (production): the Riemann-sum-in-increments rule
+ F(t_i) = c * sum_{k<=i} E(t_i - t_k) * (delta_k^1.5 - delta_{k-1}^1.5)
+with delta_{-1} = 0. The independent oracle uses a different quadrature
+(high-resolution substeps), so arithmetic order is cross-checked.
+"""
+
+from __future__ import annotations
+
+import math
+from collections.abc import Callable
+
+import numpy as np
+
+from spmkit.core.analysis.force_viscoelastic_errors import (
+ INVALID_MODEL_PARAMETER,
+ LEE_RADOK_NONMONOTONIC,
+ PRONY_DUPLICATE_TAU,
+ TING_HISTORY_UNAVAILABLE,
+ ViscoelasticityError,
+)
+
+
+def reduced_modulus(young: float, poisson: float) -> float:
+ """E* = E/(1 - nu^2)."""
+ return young / (1.0 - poisson**2)
+
+
+def spherical_coefficient(young: float, radius: float, poisson: float) -> float:
+ """c = (4/3) sqrt(R) E* (N/m^1.5)."""
+ return (4.0 / 3.0) * math.sqrt(radius) * reduced_modulus(young, poisson)
+
+
+# ---------------------------------------------------------------------------
+# lumped forward responses
+# ---------------------------------------------------------------------------
+
+
+def forward_kelvin_voigt_compliance(t: np.ndarray, modulus: float,
+ tau: float) -> np.ndarray:
+ """J(t) = (1/E) (1 - exp(-t/tau)), tau = eta/E (retardation time)."""
+ t = np.asarray(t, dtype=np.float64)
+ if modulus <= 0.0 or tau <= 0.0:
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER,
+ "Kelvin-Voigt: E > 0 and tau > 0 required")
+ return (1.0 / modulus) * (1.0 - np.exp(-t / tau))
+
+
+def forward_maxwell_modulus(t: np.ndarray, modulus: float, tau: float) -> np.ndarray:
+ """E(t) = E exp(-t/tau), tau = eta/E (relaxation time)."""
+ t = np.asarray(t, dtype=np.float64)
+ if modulus <= 0.0 or tau <= 0.0:
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER,
+ "Maxwell: E > 0 and tau > 0 required")
+ return modulus * np.exp(-t / tau)
+
+
+def forward_maxwell_normalized(t: np.ndarray, tau: float) -> np.ndarray:
+ """n(t) = exp(-t/tau)."""
+ t = np.asarray(t, dtype=np.float64)
+ if tau <= 0.0:
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER, "Maxwell: tau > 0 required")
+ return np.exp(-t / tau)
+
+
+def forward_sls_modulus(t: np.ndarray, modulus_0: float, modulus_inf: float,
+ tau_relax: float, *, _validate: bool = True) -> np.ndarray:
+ """E(t) = E_inf + (E0 - E_inf) exp(-t/tau_relax).
+
+ ``_validate`` is internal: the fit engine evaluates the raw formula
+ during optimizer probing (which steps outside the feasible region) and
+ validates the final parameters against this public contract instead.
+ """
+ t = np.asarray(t, dtype=np.float64)
+ if _validate:
+ if modulus_0 <= 0.0 or modulus_inf <= 0.0 or tau_relax <= 0.0:
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER,
+ "SLS: E0 > 0, E_inf > 0, tau > 0 required")
+ if modulus_inf > modulus_0:
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER,
+ "SLS: E_inf must not exceed E0")
+ return modulus_inf + (modulus_0 - modulus_inf) * np.exp(-t / tau_relax)
+
+
+def forward_sls_compliance(t: np.ndarray, compliance_0: float,
+ compliance_inf: float, tau_retard: float, *,
+ _validate: bool = True) -> np.ndarray:
+ """J(t) = J_inf - (J_inf - J0) exp(-t/tau_retard).
+
+ ``_validate`` is internal: the fit engine evaluates the raw formula
+ during optimizer probing and validates the final parameters against
+ this public contract instead.
+ """
+ t = np.asarray(t, dtype=np.float64)
+ if _validate:
+ if compliance_0 <= 0.0 or compliance_inf <= 0.0 or tau_retard <= 0.0:
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER,
+ "SLS creep: J0 > 0, J_inf > 0, tau > 0 required")
+ if compliance_inf < compliance_0:
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER,
+ "SLS creep: J_inf must not be below J0")
+ return compliance_inf - (compliance_inf - compliance_0) * np.exp(-t / tau_retard)
+
+
+def forward_generalized_maxwell_modulus(t: np.ndarray, modulus_inf: float,
+ terms: np.ndarray) -> np.ndarray:
+ """E(t) = E_inf + sum_i E_i exp(-t/tau_i).
+
+ ``terms`` is an (n, 2) array of (E_i, tau_i) rows, ordered by ascending
+ tau; E_i >= 0 and tau_i > 0 required; duplicate tau rejected.
+ """
+ t = np.asarray(t, dtype=np.float64)
+ terms = np.asarray(terms, dtype=np.float64)
+ if terms.ndim != 2 or terms.shape[1] != 2:
+ raise ValueError("terms must be an (n, 2) array of (E_i, tau_i)")
+ e_i, tau_i = terms[:, 0], terms[:, 1]
+ if np.any(e_i < 0.0) or np.any(tau_i <= 0.0):
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER,
+ "Prony: E_i >= 0 and tau_i > 0 required")
+ if modulus_inf < 0.0:
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER,
+ "Prony: E_inf >= 0 required")
+ if tau_i.size > 1 and np.any(np.diff(tau_i) <= 0.0):
+ raise ViscoelasticityError(
+ PRONY_DUPLICATE_TAU,
+ "Prony: tau_i must be strictly increasing (duplicates rejected)")
+ out = np.full_like(t, float(modulus_inf), dtype=np.float64)
+ for e, tau in zip(e_i, tau_i, strict=True):
+ out = out + e * np.exp(-t / tau)
+ return out
+
+
+def forward_generalized_maxwell_normalized(t: np.ndarray, alpha: np.ndarray,
+ tau: np.ndarray, *,
+ _validate: bool = True) -> np.ndarray:
+ """n(t) = 1 - sum(alpha) + sum(alpha_i exp(-t/tau_i)).
+
+ alpha_i >= 0, sum(alpha) <= 1, tau_i > 0, strictly increasing tau.
+ ``_validate`` is internal: the fit engine evaluates the raw formula
+ during optimizer probing and validates the final parameters against
+ this public contract instead.
+ """
+ t = np.asarray(t, dtype=np.float64)
+ alpha = np.asarray(alpha, dtype=np.float64)
+ tau = np.asarray(tau, dtype=np.float64)
+ if _validate:
+ if np.any(alpha < 0.0) or alpha.sum() > 1.0 + 1e-12:
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER,
+ "Prony: alpha_i >= 0 and sum(alpha) <= 1 required")
+ if np.any(tau <= 0.0):
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER, "Prony: tau_i > 0 required")
+ if tau.size > 1 and np.any(np.diff(tau) <= 0.0):
+ raise ViscoelasticityError(PRONY_DUPLICATE_TAU,
+ "Prony: tau_i must be strictly increasing")
+ return 1.0 - float(alpha.sum()) + alpha @ np.exp(-t / tau[:, None])
+
+
+def forward_power_law_modulus(t: np.ndarray, modulus_ref: float, alpha: float,
+ t_ref: float, modulus_inf: float = 0.0) -> np.ndarray:
+ """E(t) = E_inf + E_ref (t/t_ref)^(-alpha), 0 < alpha < 1, t > 0.
+
+ t = 0 is excluded (singularity); callers must start the response after
+ the first positive relative time.
+ """
+ t = np.asarray(t, dtype=np.float64)
+ if modulus_ref <= 0.0 or t_ref <= 0.0 or modulus_inf < 0.0:
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER,
+ "power law: E_ref > 0, t_ref > 0, E_inf >= 0 required")
+ if not (0.0 < alpha < 1.0):
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER,
+ "power law: exponent alpha must be in (0, 1)")
+ if np.any(t <= 0.0):
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER,
+ "power law: t must be strictly positive (t=0 excluded)")
+ return float(modulus_inf) + modulus_ref * np.power(t / t_ref, -alpha)
+
+
+# ---------------------------------------------------------------------------
+# SLS parameter conversions (relaxation <-> creep)
+# ---------------------------------------------------------------------------
+
+
+def sls_relaxation_to_creep(modulus_0: float, modulus_inf: float,
+ tau_relax: float) -> tuple[float, float, float]:
+ """(J0, J_inf, tau_retard) from (E0, E_inf, tau_relax)."""
+ if modulus_0 <= 0.0 or modulus_inf <= 0.0 or tau_relax <= 0.0 \
+ or modulus_inf > modulus_0:
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER,
+ "SLS conversion: 0 < E_inf <= E0, tau > 0 required")
+ j0 = 1.0 / modulus_0
+ j_inf = 1.0 / modulus_inf
+ tau_ret = tau_relax * modulus_0 / modulus_inf
+ return j0, j_inf, tau_ret
+
+
+def sls_creep_to_relaxation(compliance_0: float, compliance_inf: float,
+ tau_retard: float) -> tuple[float, float, float]:
+ """(E0, E_inf, tau_relax) from (J0, J_inf, tau_retard).
+
+ tau_relax = tau_retard * E_inf / E0 = tau_retard * J0 / J_inf
+ (the inverse of tau_retard = tau_relax * E0 / E_inf).
+ """
+ if compliance_0 <= 0.0 or compliance_inf <= 0.0 or tau_retard <= 0.0 \
+ or compliance_inf < compliance_0:
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER,
+ "SLS conversion: 0 < J0 <= J_inf, tau > 0 required")
+ e0 = 1.0 / compliance_0
+ e_inf = 1.0 / compliance_inf
+ tau_rel = tau_retard * compliance_0 / compliance_inf
+ return e0, e_inf, tau_rel
+
+
+# ---------------------------------------------------------------------------
+# hereditary integrals (production quadrature)
+# ---------------------------------------------------------------------------
+
+
+def _increment_quadrature(t: np.ndarray, delta: np.ndarray,
+ modulus_func: Callable[[np.ndarray], np.ndarray],
+ coefficient: float) -> np.ndarray:
+ """Riemann-sum-in-increments:
+ F(t_i) = c * sum_{k=0..i} E(t_i - t_k) * d(delta^1.5)_k
+ where E is evaluated at the shifted arguments t_i - t_k (k ascending).
+ """
+ d15 = delta ** 1.5
+ inc = np.empty_like(d15)
+ inc[0] = d15[0]
+ inc[1:] = np.diff(d15)
+ n = t.size
+ force = np.empty(n, dtype=np.float64)
+ for i in range(n):
+ args = t[i] - t[: i + 1] # t_i, t_i - t_1, ..., 0 (descending)
+ e_conv = np.asarray(modulus_func(args), dtype=np.float64)
+ force[i] = coefficient * float(np.sum(e_conv * inc[: i + 1]))
+ return force
+
+
+def _modulus_grid(t: np.ndarray, modulus_params: dict[str, float],
+ model: str = "sls") -> np.ndarray:
+ if model == "sls":
+ return forward_sls_modulus(
+ t, modulus_params["E0"], modulus_params["E_inf"], modulus_params["tau"])
+ if model == "power_law":
+ return forward_power_law_modulus(
+ t, modulus_params["E_ref"], modulus_params["alpha"],
+ modulus_params["t_ref"], modulus_params.get("E_inf", 0.0))
+ raise ValueError(f"unknown modulus model {model!r}")
+
+
+def lee_radok_force(t: np.ndarray, delta: np.ndarray, modulus_params: dict[str, float],
+ young: float, radius: float, poisson: float,
+ *, modulus_model: str = "sls") -> np.ndarray:
+ """Lee-Radok spherical loading force (monotonic contact radius only)."""
+ t = np.asarray(t, dtype=np.float64)
+ delta = np.asarray(delta, dtype=np.float64)
+ if t.ndim != 1 or t.size != delta.size or t.size == 0:
+ raise ValueError("t and delta must be equal-length 1-D arrays")
+ if np.any(np.diff(t) <= 0.0):
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER,
+ "Lee-Radok: time must be strictly increasing")
+ if np.any(np.diff(delta) < 0.0) or np.any(delta < 0.0):
+ raise ViscoelasticityError(
+ LEE_RADOK_NONMONOTONIC,
+ "Lee-Radok requires a monotone non-decreasing indentation "
+ "(contact radius must not decrease)")
+ if delta[0] < 0.0:
+ raise ViscoelasticityError(LEE_RADOK_NONMONOTONIC, "delta must be non-negative")
+ modulus_func = lambda args: _modulus_grid(args, modulus_params, modulus_model) # noqa: E731
+ c = spherical_coefficient(young, radius, poisson)
+ return _increment_quadrature(t, delta, modulus_func, c)
+
+
+def ting_force(loading_t: np.ndarray, loading_delta: np.ndarray,
+ unloading_t: np.ndarray, unloading_delta: np.ndarray,
+ modulus_params: dict[str, float], young: float, radius: float,
+ poisson: float, *, modulus_model: str = "sls") -> np.ndarray:
+ """Ting spherical loading/unloading force with contact-time memory.
+
+ Loading branch: Lee-Radok on the loading history. Unloading branch:
+ the integral runs over the loading history up to t1(t), the loading time
+ with delta(t1) = delta(t). When delta(t) exceeds the loading maximum or
+ the loading history is missing, TING_HISTORY_UNAVAILABLE is raised.
+ """
+ loading_t = np.asarray(loading_t, dtype=np.float64)
+ loading_delta = np.asarray(loading_delta, dtype=np.float64)
+ unloading_t = np.asarray(unloading_t, dtype=np.float64)
+ unloading_delta = np.asarray(unloading_delta, dtype=np.float64)
+ for arr, label in ((loading_t, "loading_t"), (loading_delta, "loading_delta"),
+ (unloading_t, "unloading_t"), (unloading_delta, "unloading_delta")):
+ if arr.ndim != 1 or arr.size == 0:
+ raise ValueError(f"{label} must be a non-empty 1-D array")
+ if loading_delta.size != loading_t.size or unloading_delta.size != unloading_t.size:
+ raise ValueError("t/delta length mismatch")
+ if np.any(np.diff(loading_t) <= 0.0) or np.any(np.diff(unloading_t) <= 0.0):
+ raise ViscoelasticityError(INVALID_MODEL_PARAMETER,
+ "Ting: time axes must be strictly increasing")
+ if np.any(np.diff(loading_delta) < 0.0) or np.any(loading_delta < 0.0):
+ raise ViscoelasticityError(
+ LEE_RADOK_NONMONOTONIC,
+ "Ting loading branch must be monotone non-decreasing")
+ if unloading_t[0] < loading_t[-1]:
+ raise ViscoelasticityError(
+ TING_HISTORY_UNAVAILABLE,
+ "Ting unloading must start at or after the loading branch end")
+ c = spherical_coefficient(young, radius, poisson)
+ modulus_func = lambda args: _modulus_grid(args, modulus_params, modulus_model) # noqa: E731
+ force_load = _increment_quadrature(loading_t, loading_delta, modulus_func, c)
+
+ # contact-time memory: t1(t) solves delta_loading(t1) = delta_unloading(t)
+ # on the monotone loading branch (inverted by interpolation)
+ d_max = float(np.max(loading_delta))
+ t1 = np.empty(unloading_t.size, dtype=np.float64)
+ for i, d_u in enumerate(unloading_delta):
+ if d_u > d_max + 1e-15 or d_u < 0.0:
+ raise ViscoelasticityError(
+ TING_HISTORY_UNAVAILABLE,
+ f"unloading indentation {d_u:.3e} outside the loading history [0, {d_max:.3e}]")
+ # the loading branch is monotone: invert delta(t1) = d_u
+ if d_u <= float(loading_delta[0]):
+ t1[i] = float(loading_t[0])
+ else:
+ idx = int(np.searchsorted(loading_delta, d_u, side="left"))
+ idx = min(max(idx, 1), loading_delta.size - 1)
+ t_a, t_b = float(loading_t[idx - 1]), float(loading_t[idx])
+ d_a, d_b = float(loading_delta[idx - 1]), float(loading_delta[idx])
+ if d_b <= d_a:
+ t1[i] = t_b
+ else:
+ frac = (d_u - d_a) / (d_b - d_a)
+ t1[i] = t_a + frac * (t_b - t_a)
+ # unloading force: integral over the loading branch up to t1 with
+ # E(t_u - t_k), k ascending over the loading times; the partial last
+ # interval [t_k, t1] is included with the interpolated delta^1.5
+ force_unload = np.empty(unloading_t.size, dtype=np.float64)
+ d15 = loading_delta ** 1.5
+ inc = np.empty_like(d15)
+ inc[0] = d15[0]
+ inc[1:] = np.diff(d15)
+ for i, t_u in enumerate(unloading_t):
+ t1v = t1[i]
+ k = int(np.searchsorted(loading_t, t1v, side="right")) - 1
+ k = min(max(k, 0), loading_t.size - 1)
+ e_conv = _modulus_grid(t_u - loading_t[: k + 1], modulus_params, modulus_model)
+ total = float(np.sum(e_conv * inc[: k + 1]))
+ if t1v > loading_t[k] and k + 1 < loading_t.size:
+ # partial interval [t_k, t1]: delta^1.5 interpolated at t1
+ frac = (t1v - loading_t[k]) / (loading_t[k + 1] - loading_t[k])
+ d15_t1 = d15[k] + frac * (d15[k + 1] - d15[k])
+ total = total + float(_modulus_grid(
+ np.array([t_u - loading_t[k]]), modulus_params, modulus_model)[0]) \
+ * (d15_t1 - d15[k])
+ force_unload[i] = c * total
+ return np.concatenate([force_load, force_unload])
diff --git a/src/spmkit/core/analysis/force_viscoelastic_reliability.py b/src/spmkit/core/analysis/force_viscoelastic_reliability.py
new file mode 100644
index 0000000..32c15ad
--- /dev/null
+++ b/src/spmkit/core/analysis/force_viscoelastic_reliability.py
@@ -0,0 +1,193 @@
+"""FS-F3 viscoelastic reliability: protocol/contact/window sensitivity."""
+
+from __future__ import annotations
+
+from dataclasses import dataclass, field
+
+import numpy as np
+
+from spmkit.core.analysis.force_foundation import ForcePreparationResult
+from spmkit.core.analysis.force_time_protocol import (
+ CreepResponseResult,
+ RelaxationResponseResult,
+ ViscoelasticProtocolResult,
+ identify_viscoelastic_protocol,
+)
+from spmkit.core.analysis.force_viscoelastic_errors import (
+ NO_VISCOELASTIC_FIT,
+ ViscoelasticityError,
+)
+from spmkit.core.analysis.force_viscoelastic_fitting import (
+ ViscoelasticFitResult,
+ fit_standard_linear_solid,
+)
+from spmkit.core.models import ForceCurve
+
+
+@dataclass(frozen=True)
+class ViscoelasticSensitivityResult:
+ """Raw evaluated multiverse; never collapsed into one interval."""
+
+ configurations: tuple[dict[str, object], ...]
+ parameter_multiverse: tuple[dict[str, float], ...]
+ failures: tuple[tuple[dict[str, object], str], ...]
+ dominant_sensitivity: str
+ contact_sensitivity: float
+ boundary_sensitivity: float
+ window_sensitivity: float
+ n_configurations: int
+ n_skipped: int
+ warnings: tuple[str, ...] = ()
+ provenance: dict[str, object] = field(default_factory=dict)
+
+
+def _sweep_sls_on_response(response: object,
+ fit_kwargs: dict) -> ViscoelasticFitResult | None:
+ if not isinstance(response, (RelaxationResponseResult, CreepResponseResult)):
+ return None
+ try:
+ return fit_standard_linear_solid(response, **fit_kwargs)
+ except ViscoelasticityError:
+ return None
+
+
+def analyze_viscoelastic_sensitivity(
+ curve: ForceCurve,
+ prepared: ForcePreparationResult,
+ *,
+ protocol: ViscoelasticProtocolResult | None = None,
+ contact_offsets: tuple[int, ...] = (-2, 0, 2),
+ boundary_offsets: tuple[int, ...] = (-3, 0, 3),
+ equilibrium_tail_fractions: tuple[float, ...] = (0.05, 0.1, 0.2),
+ max_configurations: int = 96,
+ tip_radius: float | None = None,
+ poisson: float = 0.3,
+) -> ViscoelasticSensitivityResult:
+ """Deterministic multiverse over contact offset, hold-boundary offset
+ and equilibrium-tail fraction for the SLS fit on the extracted response.
+
+ Configurations are evaluated in deterministic order; failures are
+ retained; the dominant sensitivity is the parameter spread relative to
+ the median, classified by source (contact/boundary/window).
+ """
+ base_protocol = protocol if protocol is not None else identify_viscoelastic_protocol(curve)
+ configs: list[dict[str, object]] = []
+ params_out: list[dict[str, float]] = []
+ failures: list[tuple[dict[str, object], str]] = []
+ n_skipped = 0
+
+ for c_off in contact_offsets:
+ for b_off in boundary_offsets:
+ for tail in equilibrium_tail_fractions:
+ if len(configs) + len(failures) >= max_configurations:
+ n_skipped += 1
+ continue
+ cfg: dict[str, object] = {
+ "contact_offset": c_off, "boundary_offset": b_off,
+ "equilibrium_tail_fraction": tail,
+ }
+ try:
+ zc = float(prepared.contact.selected.coordinate)
+ approach = prepared.curve.extend
+ if approach is None or approach.raw_height is None:
+ raise ViscoelasticityError(NO_VISCOELASTIC_FIT,
+ "no approach branch")
+ z = np.asarray(approach.raw_height, dtype=np.float64)
+ dz = float(np.mean(np.diff(z))) if z.size > 1 else 0.0
+ zc_shifted = zc + c_off * abs(dz)
+ # rebuild a prepared-like indentation by shifting the
+ # contact coordinate through the extraction helpers
+ sep = np.asarray(approach.separation, dtype=np.float64)
+ ind_shifted = sep - zc_shifted
+ reg = base_protocol.region("hold_displacement", "extend")
+ if reg is None:
+ reg = base_protocol.region("hold_force", "extend")
+ if reg is None:
+ raise ViscoelasticityError(NO_VISCOELASTIC_FIT,
+ "no hold region to sweep")
+ a, b = reg.start_index, reg.end_index + 1
+ a2 = min(max(a + b_off, 0), b - 2)
+ if approach.time is None:
+ raise ViscoelasticityError(NO_VISCOELASTIC_FIT,
+ "no time axis")
+ b2 = max(min(b + b_off, approach.time.size - 1), a2 + 2)
+ t_hold = np.asarray(approach.time, dtype=np.float64)[a2:b2]
+ ind_hold = ind_shifted[a2:b2]
+ f_hold = np.asarray(approach.force, dtype=np.float64)[a2:b2]
+ t0 = float(t_hold[0])
+ f0 = float(f_hold[0])
+ if f0 == 0.0 or t_hold.size < 3:
+ raise ViscoelasticityError(NO_VISCOELASTIC_FIT,
+ "degenerate hold response")
+ tail_n = max(1, int(round(t_hold.size * tail)))
+ eq = float(np.mean(f_hold[-tail_n:]))
+ n_hold = f_hold / f0
+ # build a lightweight relaxation response for the SLS fit
+ from spmkit.core.analysis.force_time_protocol import RelaxationResponseResult
+ resp = RelaxationResponseResult(
+ relative_time=t_hold - t0, indentation=ind_hold,
+ force=f_hold, normalized_force=n_hold,
+ hold_indices=np.arange(a2, b2), hold_start_time=t0,
+ force_at_hold_start=f0, equilibrium_force_estimate=eq,
+ warnings=(),
+ )
+ fit = fit_standard_linear_solid(
+ resp, tip_radius=tip_radius, poisson=poisson)
+ configs.append(cfg)
+ params_out.append(fit.parameters)
+ except ViscoelasticityError as exc:
+ failures.append((cfg, exc.code))
+ if not params_out:
+ raise ViscoelasticityError(NO_VISCOELASTIC_FIT, "no multiverse configuration succeeded")
+
+ keys = ("tau_relax", "a") if "a" in params_out[0] else ("tau_retard", "J0", "J_inf")
+ medians = {k: float(np.median([p[k] for p in params_out])) for k in keys}
+ spread = {k: (float(np.max([p[k] for p in params_out]))
+ - float(np.min([p[k] for p in params_out]))) / medians[k]
+ for k in keys}
+ dominant_key = max(spread, key=lambda k: float(spread[k])) \
+ if any(spread.values()) else keys[0]
+ # one-at-a-time source indices (relative to the baseline config)
+ by_key: dict[tuple[int, int, float], dict[str, float]] = {}
+ for cfg, params_i in zip(configs, params_out, strict=True):
+ off_c = int(cfg["contact_offset"]) if isinstance(cfg["contact_offset"], int) else 0
+ off_b = int(cfg["boundary_offset"]) if isinstance(cfg["boundary_offset"], int) else 0
+ tail_c = float(cfg["equilibrium_tail_fraction"]) \
+ if isinstance(cfg["equilibrium_tail_fraction"], (int, float)) else 0.0
+ by_key[(off_c, off_b, tail_c)] = params_i
+ base = by_key.get((0, 0, 0.1))
+ contact_sens = boundary_sens = window_sens = 0.0
+ if base is not None:
+ med_b = base[dominant_key]
+ for off in contact_offsets:
+ p = by_key.get((off, 0, 0.1))
+ if p:
+ contact_sens = max(contact_sens,
+ abs(p[dominant_key] - med_b) / abs(med_b))
+ for off in boundary_offsets:
+ p = by_key.get((0, off, 0.1))
+ if p:
+ boundary_sens = max(boundary_sens,
+ abs(p[dominant_key] - med_b) / abs(med_b))
+ for tail in equilibrium_tail_fractions:
+ p = by_key.get((0, 0, tail))
+ if p:
+ window_sens = max(window_sens,
+ abs(p[dominant_key] - med_b) / abs(med_b))
+ threshold = 0.2
+ if contact_sens > threshold:
+ dominant = "contact"
+ elif boundary_sens > threshold:
+ dominant = "boundary"
+ elif window_sens > threshold:
+ dominant = "window"
+ else:
+ dominant = "none"
+ return ViscoelasticSensitivityResult(
+ configurations=tuple(configs), parameter_multiverse=tuple(params_out),
+ failures=tuple(failures), dominant_sensitivity=dominant,
+ contact_sensitivity=contact_sens, boundary_sensitivity=boundary_sens,
+ window_sensitivity=window_sens, n_configurations=len(configs),
+ n_skipped=n_skipped,
+ provenance={"dominant_parameter": dominant_key, "threshold": threshold,
+ "model": "standard_linear_solid"})
diff --git a/src/spmkit/core/analysis/force_viscoelasticity.py b/src/spmkit/core/analysis/force_viscoelasticity.py
new file mode 100644
index 0000000..9c2a076
--- /dev/null
+++ b/src/spmkit/core/analysis/force_viscoelasticity.py
@@ -0,0 +1,102 @@
+"""FS-F3 public surface: time-domain viscoelasticity and rate-dependent
+AFM mechanics."""
+
+from __future__ import annotations
+
+from spmkit.core.analysis.force_time_protocol import (
+ LOADING_RAMP,
+ STRESS_RELAXATION,
+ CreepResponseResult,
+ IndentationRateResult,
+ ProtocolRegion,
+ RelaxationResponseResult,
+ ViscoelasticProtocolResult,
+ compute_indentation_rate,
+ extract_creep_compliance,
+ extract_stress_relaxation,
+ identify_viscoelastic_protocol,
+ validate_time_axis,
+)
+from spmkit.core.analysis.force_viscoelastic_errors import (
+ ViscoelasticityError,
+)
+from spmkit.core.analysis.force_viscoelastic_fitting import (
+ ViscoelasticFitResult,
+ ViscoelasticModelComparisonResult,
+ compare_viscoelastic_models,
+ fit_generalized_maxwell,
+ fit_kelvin_voigt,
+ fit_lee_radok_sphere,
+ fit_maxwell,
+ fit_power_law_relaxation,
+ fit_standard_linear_solid,
+ fit_ting_sphere,
+)
+from spmkit.core.analysis.force_viscoelastic_models import (
+ forward_generalized_maxwell_modulus,
+ forward_generalized_maxwell_normalized,
+ forward_kelvin_voigt_compliance,
+ forward_maxwell_modulus,
+ forward_maxwell_normalized,
+ forward_power_law_modulus,
+ forward_sls_compliance,
+ forward_sls_modulus,
+ lee_radok_force,
+ reduced_modulus,
+ sls_creep_to_relaxation,
+ sls_relaxation_to_creep,
+ spherical_coefficient,
+ ting_force,
+)
+from spmkit.core.analysis.force_viscoelastic_reliability import (
+ ViscoelasticSensitivityResult,
+ analyze_viscoelastic_sensitivity,
+)
+from spmkit.core.analysis.force_volume_viscoelasticity import (
+ ForceVolumeViscoelasticityResult,
+ fit_force_volume_viscoelasticity,
+)
+
+__all__ = [
+ "identify_viscoelastic_protocol",
+ "compute_indentation_rate",
+ "extract_stress_relaxation",
+ "extract_creep_compliance",
+ "fit_kelvin_voigt",
+ "fit_maxwell",
+ "fit_standard_linear_solid",
+ "fit_generalized_maxwell",
+ "fit_power_law_relaxation",
+ "fit_lee_radok_sphere",
+ "fit_ting_sphere",
+ "compare_viscoelastic_models",
+ "analyze_viscoelastic_sensitivity",
+ "fit_force_volume_viscoelasticity",
+ "forward_kelvin_voigt_compliance",
+ "forward_maxwell_modulus",
+ "forward_maxwell_normalized",
+ "forward_sls_modulus",
+ "forward_sls_compliance",
+ "forward_generalized_maxwell_modulus",
+ "forward_generalized_maxwell_normalized",
+ "forward_power_law_modulus",
+ "lee_radok_force",
+ "ting_force",
+ "sls_relaxation_to_creep",
+ "sls_creep_to_relaxation",
+ "reduced_modulus",
+ "spherical_coefficient",
+ "validate_time_axis",
+ "ViscoelasticProtocolResult",
+ "ProtocolRegion",
+ "IndentationRateResult",
+ "RelaxationResponseResult",
+ "CreepResponseResult",
+ "ViscoelasticFitResult",
+ "ViscoelasticModelComparisonResult",
+ "ViscoelasticSensitivityResult",
+ "ForceVolumeViscoelasticityResult",
+ "ViscoelasticityError",
+ "LOADING_RAMP",
+ "STRESS_RELAXATION",
+]
diff --git a/src/spmkit/core/analysis/force_volume_mechanics.py b/src/spmkit/core/analysis/force_volume_mechanics.py
new file mode 100644
index 0000000..9d74214
--- /dev/null
+++ b/src/spmkit/core/analysis/force_volume_mechanics.py
@@ -0,0 +1,90 @@
+"""FS-F2 force-volume mechanics mapping (deterministic, bounded)."""
+
+from __future__ import annotations
+
+from dataclasses import dataclass, field
+
+import numpy as np
+
+from spmkit.core.analysis.contact_mechanics import (
+ compare_contact_models,
+)
+from spmkit.core.analysis.force_foundation import prepare_force_curve
+from spmkit.core.analysis.force_indentation import (
+ compute_indentation,
+ select_contact_fit_window,
+)
+from spmkit.core.analysis.force_mechanics_errors import (
+ ForceMechanicsError,
+)
+from spmkit.core.models import ForceVolume
+
+
+@dataclass(frozen=True)
+class ForceVolumeMechanicsResult:
+ """Per-curve mechanics maps with explicit failed-curve masks."""
+
+ modulus_map: np.ndarray
+ adhesion_map: np.ndarray | None
+ model_map: np.ndarray
+ failed_mask: np.ndarray
+ quality_map: np.ndarray
+ provenance: dict[str, object] = field(default_factory=dict)
+ warnings: tuple[str, ...] = ()
+
+
+def fit_force_volume_mechanics(
+ volume: ForceVolume,
+ *,
+ tip_radius: float = 10e-9,
+ poisson: float = 0.3,
+ half_angle: float = 0.3490658503988659,
+ models: tuple[str, ...] = ("hertz_sphere", "dmt"),
+ min_points: int = 20,
+) -> ForceVolumeMechanicsResult:
+ """Apply the FS-F1 preparation + FS-F2 mechanics stack to every curve.
+
+ Failed curves remain masked; no curve is silently dropped.
+ """
+ n = volume.n_curves
+ modulus_map = np.full(n, np.nan)
+ adhesion_map = np.full(n, np.nan)
+ model_map = np.full(n, "", dtype=object)
+ failed_mask = np.zeros(n, dtype=bool)
+ quality_map = np.zeros(n, dtype=bool)
+ failed_reasons: dict[int, str] = {}
+ provenance: dict[str, object] = {}
+ for i in range(n):
+ try:
+ prepared = prepare_force_curve(volume.curve(i))
+ ind = compute_indentation(prepared)
+ window = select_contact_fit_window(prepared, ind, min_points=min_points)
+ cmp = compare_contact_models(prepared, ind, window, models=models,
+ tip_radius=tip_radius, poisson=poisson,
+ half_angle=half_angle)
+ best = next(f for f in cmp.fits if f.model == (cmp.recommended_model
+ or cmp.fits[0].model))
+ modulus_map[i] = best.parameters.get("E", np.nan)
+ if "F_adh" in best.parameters:
+ adhesion_map[i] = best.parameters["F_adh"]
+ model_map[i] = best.model
+ quality_map[i] = prepared.quality.eligible
+ except ForceMechanicsError as exc:
+ failed_mask[i] = True
+ failed_reasons[i] = exc.code
+ except Exception as exc: # noqa: BLE001 - recorded per-curve failure
+ failed_mask[i] = True
+ failed_reasons[i] = type(exc).__name__
+ provenance = {
+ "pipeline": ["prepare_force_curve", "compute_indentation",
+ "select_contact_fit_window", "compare_contact_models"],
+ "tip_radius": tip_radius, "poisson": poisson, "half_angle": half_angle,
+ "models": list(models), "n_curves": n,
+ "n_failed": int(failed_mask.sum()),
+ "failed_reasons": failed_reasons,
+ "deterministic": True,
+ }
+ return ForceVolumeMechanicsResult(
+ modulus_map=modulus_map, adhesion_map=adhesion_map, model_map=model_map,
+ failed_mask=failed_mask, quality_map=quality_map, provenance=provenance,
+ )
diff --git a/src/spmkit/core/analysis/force_volume_viscoelasticity.py b/src/spmkit/core/analysis/force_volume_viscoelasticity.py
new file mode 100644
index 0000000..3ba4b29
--- /dev/null
+++ b/src/spmkit/core/analysis/force_volume_viscoelasticity.py
@@ -0,0 +1,101 @@
+"""FS-F3 force-volume viscoelasticity mapping (deterministic, bounded)."""
+
+from __future__ import annotations
+
+from dataclasses import dataclass, field
+
+import numpy as np
+
+from spmkit.core.analysis.force_foundation import prepare_force_curve
+from spmkit.core.analysis.force_time_protocol import (
+ extract_stress_relaxation,
+ identify_viscoelastic_protocol,
+)
+from spmkit.core.analysis.force_viscoelastic_errors import (
+ ViscoelasticityError,
+)
+from spmkit.core.analysis.force_viscoelastic_fitting import (
+ fit_standard_linear_solid,
+)
+from spmkit.core.models import ForceVolume
+
+
+@dataclass(frozen=True)
+class ForceVolumeViscoelasticityResult:
+ """Per-curve viscoelastic maps with an explicit failed mask."""
+
+ modulus_0_map: np.ndarray
+ modulus_inf_map: np.ndarray
+ viscosity_map: np.ndarray
+ relaxation_time_map: np.ndarray
+ model_map: np.ndarray
+ ambiguity_map: np.ndarray
+ sensitivity_map: np.ndarray
+ protocol_map: np.ndarray
+ failed_mask: np.ndarray
+ provenance: dict[str, object] = field(default_factory=dict)
+ warnings: tuple[str, ...] = ()
+
+
+def fit_force_volume_viscoelasticity(
+ volume: ForceVolume,
+ *,
+ tip_radius: float | None = None,
+ poisson: float = 0.3,
+ min_hold_points: int = 5,
+) -> ForceVolumeViscoelasticityResult:
+ """Apply identify -> prepare -> extract -> SLS fit to every curve.
+
+ Failed curves stay explicitly masked; nothing is silently dropped.
+ Viscosity = E0 * tau_relax * (1 - a) is reported as an SLS-dashpot
+ estimate (documented model quantity, not a certified material value).
+ """
+ n = volume.n_curves
+ modulus_0 = np.full(n, np.nan)
+ modulus_inf = np.full(n, np.nan)
+ viscosity = np.full(n, np.nan)
+ tau_map = np.full(n, np.nan)
+ model_map = np.full(n, "", dtype=object)
+ ambiguity_map = np.zeros(n, dtype=bool)
+ sensitivity_map = np.full(n, np.nan)
+ protocol_map = np.full(n, "", dtype=object)
+ failed = np.zeros(n, dtype=bool)
+ failed_reasons: dict[int, str] = {}
+ for i in range(n):
+ curve = volume.curve(i)
+ try:
+ protocol = identify_viscoelastic_protocol(curve, min_hold_points=min_hold_points)
+ prepared = prepare_force_curve(curve)
+ response = extract_stress_relaxation(prepared, protocol)
+ fit = fit_standard_linear_solid(response, tip_radius=tip_radius,
+ poisson=poisson)
+ e0 = fit.parameters.get("E0", np.nan)
+ e_inf = fit.parameters.get("E_inf", np.nan)
+ tau = fit.parameters.get("tau_relax", np.nan)
+ modulus_0[i] = e0
+ modulus_inf[i] = e_inf
+ tau_map[i] = tau
+ viscosity[i] = e0 * tau * fit.parameters.get("a", 0.0) if np.isfinite(e0) else np.nan
+ model_map[i] = fit.model
+ protocol_map[i] = protocol.protocol_type
+ ambiguity_map[i] = protocol.ambiguity
+ sensitivity_map[i] = fit.condition_number if np.isfinite(fit.condition_number) \
+ else np.nan
+ except ViscoelasticityError as exc:
+ failed[i] = True
+ failed_reasons[i] = exc.code
+ except Exception as exc: # noqa: BLE001 - recorded per-curve failure
+ failed[i] = True
+ failed_reasons[i] = type(exc).__name__
+ provenance: dict[str, object] = {
+ "pipeline": ["identify_viscoelastic_protocol", "prepare_force_curve",
+ "extract_stress_relaxation", "fit_standard_linear_solid"],
+ "tip_radius": tip_radius, "poisson": poisson, "n_curves": n,
+ "n_failed": int(failed.sum()), "failed_reasons": failed_reasons,
+ "deterministic": True,
+ }
+ return ForceVolumeViscoelasticityResult(
+ modulus_0_map=modulus_0, modulus_inf_map=modulus_inf,
+ viscosity_map=viscosity, relaxation_time_map=tau_map, model_map=model_map,
+ ambiguity_map=ambiguity_map, sensitivity_map=sensitivity_map,
+ protocol_map=protocol_map, failed_mask=failed, provenance=provenance)
diff --git a/src/spmkit/core/analysis/interpolation.py b/src/spmkit/core/analysis/interpolation.py
new file mode 100644
index 0000000..eb6c674
--- /dev/null
+++ b/src/spmkit/core/analysis/interpolation.py
@@ -0,0 +1,77 @@
+"""Public Gwydion 2.71 Laplace interpolation (Interpolate Data Under Mask).
+
+The Laplace interpolation is not scan-line specific: it substitutes data
+under a mask by the solution of the discrete Laplace equation with
+Dirichlet data from the surrounding unmasked pixels and Neumann conditions
+at image borders, following the frozen Gwydion 2.71 contract of
+gwy_data_field_laplace_solve(field, mask, -1, 1.0).
+"""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+import numpy as np
+
+from spmkit.core.analysis._gwydion_laplace import _gwydion_laplace_result
+
+if TYPE_CHECKING:
+ from spmkit.core.models.spmdata import SPMChannel
+
+FloatArray = np.ndarray
+
+
+def _validated_channel_data(channel: SPMChannel, *, operation: str) -> FloatArray:
+ source = np.asarray(channel.data)
+ if source.ndim != 2:
+ raise ValueError(f"{operation} requires a two-dimensional channel")
+ if source.size == 0:
+ raise ValueError(f"{operation} requires non-empty data")
+ if not np.issubdtype(source.dtype, np.number) or np.iscomplexobj(source):
+ raise TypeError(f"{operation} requires real numeric data")
+ if not np.all(np.isfinite(source)):
+ raise ValueError(f"{operation} requires finite data")
+ return np.array(source, dtype=np.float64, order="C", copy=True)
+
+
+def gwydion_interpolate_data_under_mask(
+ channel: SPMChannel,
+ mask: np.ndarray,
+) -> SPMChannel:
+ """Interpolate data under a mask by the Laplace equation solution.
+
+ Pixels with ``mask > 0.0`` are solved from the discrete Laplace
+ equation: each masked pixel equals the mean of its masked neighbours
+ and its fixed (unmasked) neighbours, with missing neighbours at image
+ borders implementing Neumann conditions. Mask values ``<= 0.0`` remain
+ fixed and bitwise unchanged. An empty mask returns an independent
+ channel with bitwise-identical data; a whole-field positive mask
+ returns the source-defined all-zero field. Physical ``x_range`` /
+ ``y_range`` do not alter the numerical solve (pixel-index based).
+
+ ``channel`` data and ``mask`` must be finite, two-dimensional and
+ shape-compatible. The input channel and the mask are never mutated; a
+ new ``SPMChannel`` preserving the input context (shape, ranges, units,
+ copied metadata) is returned.
+
+ This API corresponds to the process operation with grain_id=-1 and
+ qprec=1.0; there is no public qprec parameter. No uncertainty, physical
+ reconstruction or statistical neutrality is claimed for the
+ interpolated values.
+ """
+ data = _validated_channel_data(channel,
+ operation="Interpolate Data Under Mask")
+ if mask.ndim != 2:
+ raise ValueError("Interpolate Data Under Mask requires a "
+ "two-dimensional mask")
+ if mask.size == 0:
+ raise ValueError("Interpolate Data Under Mask requires non-empty mask")
+ if not np.issubdtype(mask.dtype, np.number) or np.iscomplexobj(mask):
+ raise TypeError("Interpolate Data Under Mask requires a real numeric mask")
+ if not np.all(np.isfinite(mask)):
+ raise ValueError("Interpolate Data Under Mask requires a finite mask")
+ if mask.shape != data.shape:
+ raise ValueError("Interpolate Data Under Mask mask shape must match "
+ "the channel")
+ result = _gwydion_laplace_result(data, mask)
+ return channel.with_data(result.corrected_field)
diff --git a/src/spmkit/core/analysis/leveling.py b/src/spmkit/core/analysis/leveling.py
index e954a32..d007a4d 100644
--- a/src/spmkit/core/analysis/leveling.py
+++ b/src/spmkit/core/analysis/leveling.py
@@ -11,6 +11,13 @@
import numpy as np
+from spmkit.core.analysis._gwyddion_align_rows_facet_tilt import (
+ _gwyddion_align_rows_facet_tilt,
+)
+from spmkit.core.analysis._gwyddion_align_rows_remaining import (
+ _gwydion_align_rows_remaining_result,
+ _GwydionAlignRowsMethod,
+)
from spmkit.core.analysis._gwyddion_align_rows_statistics import (
_gwyddion_align_rows_statistics_result,
_GwyddionAlignRowsDirection,
@@ -370,6 +377,191 @@ def gwyddion_align_rows_trimmed_mean_of_differences(
)
+def gwyddion_align_rows_facet_tilt(
+ channel: SPMChannel,
+ *,
+ mask: np.ndarray | None = None,
+ mask_mode: GwyddionAlignRowsMaskMode = "ignore",
+ direction: GwyddionAlignRowsDirection = "horizontal",
+) -> SPMChannel:
+ """Apply Gwyddion 2.71 Align Rows Facet-level tilt correction.
+
+ ``mask`` is an optional finite numeric array matching the channel shape.
+ ``mask_mode`` is ``"exclude"``, ``"include"``, or ``"ignore"``; an absent
+ mask always selects all values. ``direction`` selects horizontal rows or
+ source-equivalent vertical transpose/restore processing.
+
+ The algorithm estimates the facet tilt (surface slope) for each row using
+ iterative robust reweighting and subtracts it about the row centre. Rows
+ with zero variance propagate IEEE NaN (as in the Gwyddion 2.71 source).
+ Inputs containing NaN or infinity are rejected at entry (consistent with
+ the defensive validation shared by all ``gwyddion_align_rows_*`` functions;
+ the Gwyddion C implementation performs no such pre-filtering).
+
+ The algorithm produces no per-row offset vector: its shifts output is
+ always all zeros, matching the Gwyddion 2.71 source behaviour (the length
+ is the working field's y-resolution — original rows for horizontal,
+ original columns for vertical).
+
+ ``x_range`` must be positive; it determines the physical pixel spacing
+ ``dx = x_range / columns`` used in the convergence test
+ ``|tilt/dx| < 1e-6``.
+
+ The result is a new ``SPMChannel`` with the input context preserved.
+ """
+ if not isinstance(channel, SPMChannel):
+ raise TypeError("Gwyddion Align Rows requires an SPMChannel")
+ if not isinstance(mask_mode, str) or mask_mode not in _GWYDDION_ALIGN_ROWS_MASK_MODES:
+ raise ValueError(
+ "Gwyddion Align Rows mask_mode must be 'exclude', 'include', or 'ignore'"
+ )
+ if not isinstance(direction, str) or direction not in _GWYDDION_ALIGN_ROWS_DIRECTIONS:
+ raise ValueError(
+ "Gwyddion Align Rows direction must be 'horizontal' or 'vertical'"
+ )
+
+ columns = channel.data.shape[1]
+ if columns < 2:
+ raise ValueError(
+ "Gwyddion Align Rows facet_tilt requires at least two columns"
+ )
+ dx = channel.x_range / float(columns)
+
+ result = _gwyddion_align_rows_facet_tilt(
+ channel.data,
+ masking_mode=_GWYDDION_ALIGN_ROWS_MASK_MODES[mask_mode],
+ direction=_GWYDDION_ALIGN_ROWS_DIRECTIONS[direction],
+ dx=dx,
+ mask=mask,
+ )
+ return channel.with_data(result.corrected)
+
+
+def _gwyddion_align_rows_remaining_channel(
+ channel: SPMChannel,
+ *,
+ method: _GwydionAlignRowsMethod,
+ degree: int,
+ mask: np.ndarray | None,
+ mask_mode: GwyddionAlignRowsMaskMode,
+ direction: GwyddionAlignRowsDirection,
+) -> SPMChannel:
+ """Apply one private remaining-method Align Rows kernel and preserve
+ channel context."""
+ if not isinstance(channel, SPMChannel):
+ raise TypeError("Gwydion Align Rows requires an SPMChannel")
+ if not isinstance(mask_mode, str) or mask_mode not in _GWYDDION_ALIGN_ROWS_MASK_MODES:
+ raise ValueError("Gwydion Align Rows mask_mode must be 'exclude', 'include', or 'ignore'")
+ if not isinstance(direction, str) or direction not in _GWYDDION_ALIGN_ROWS_DIRECTIONS:
+ raise ValueError("Gwydion Align Rows direction must be 'horizontal' or 'vertical'")
+ if not isinstance(degree, (int, np.integer)) or isinstance(degree, (bool, np.bool_)):
+ raise TypeError("Gwydion Align Rows degree must be an integer")
+ if not 0 <= int(degree) <= 5:
+ raise ValueError("Gwydion Align Rows degree must be in the inclusive range 0..5")
+
+ result = _gwydion_align_rows_remaining_result(
+ channel.data,
+ method=method,
+ masking_mode=_GWYDDION_ALIGN_ROWS_MASK_MODES[mask_mode],
+ direction=_GWYDDION_ALIGN_ROWS_DIRECTIONS[direction],
+ degree=int(degree),
+ mask=mask,
+ )
+ return channel.with_data(result.corrected)
+
+
+def gwyddion_align_rows_polynomial(
+ channel: SPMChannel,
+ *,
+ degree: int = 1,
+ mask: np.ndarray | None = None,
+ mask_mode: GwyddionAlignRowsMaskMode = "ignore",
+ direction: GwyddionAlignRowsDirection = "horizontal",
+) -> SPMChannel:
+ """Apply Gwydion 2.71 Align Rows Polynomial correction.
+
+ ``degree`` selects the source polynomial degree in the inclusive range
+ ``0..5``. Degree zero dispatches to the trim-fraction-zero row-shift
+ path (per-row means with a global masked-median fallback and zero-
+ levelled shifts); degree one or higher fits each row independently on
+ the centred basis ``x = j - 0.5*(xres-1)`` with a packed Cholesky
+ solve and full-field mean anchoring.
+
+ ``mask`` is an optional finite numeric array matching the channel
+ shape. ``mask_mode`` is ``"exclude"``, ``"include"``, or ``"ignore"``;
+ an absent mask always selects all values. ``direction`` selects
+ horizontal rows or source-equivalent vertical transpose/restore
+ processing. The result is a new ``SPMChannel`` with the input context
+ preserved; the input channel, data and mask are never mutated.
+ """
+ return _gwyddion_align_rows_remaining_channel(
+ channel,
+ method=_GwydionAlignRowsMethod.POLYNOMIAL,
+ degree=degree,
+ mask=mask,
+ mask_mode=mask_mode,
+ direction=direction,
+ )
+
+
+def gwyddion_align_rows_modus(
+ channel: SPMChannel,
+ *,
+ mask: np.ndarray | None = None,
+ mask_mode: GwyddionAlignRowsMaskMode = "ignore",
+ direction: GwyddionAlignRowsDirection = "horizontal",
+) -> SPMChannel:
+ """Apply Gwydion 2.71 Align Rows Modus correction.
+
+ The Modus estimator is a robust row-centre statistic: rows with fewer
+ than nine retained samples use the upper median, rows with more use
+ the narrowest ``sqrt(count)``-wide range window over the sorted
+ retained samples and take the mean of its central third; rows with no
+ retained samples fall back to the global masked median. Shifts are
+ zero-levelled before subtraction.
+
+ ``mask``, ``mask_mode`` and ``direction`` follow the shared Align Rows
+ public contract. The result is a new context-preserving ``SPMChannel``.
+ """
+ return _gwyddion_align_rows_remaining_channel(
+ channel,
+ method=_GwydionAlignRowsMethod.MODUS,
+ degree=0,
+ mask=mask,
+ mask_mode=mask_mode,
+ direction=direction,
+ )
+
+
+def gwyddion_align_rows_match(
+ channel: SPMChannel,
+ *,
+ mask: np.ndarray | None = None,
+ mask_mode: GwyddionAlignRowsMaskMode = "ignore",
+ direction: GwyddionAlignRowsDirection = "horizontal",
+) -> SPMChannel:
+ """Apply Gwydion 2.71 Align Rows Match correction.
+
+ Adjacent rows are compared through Gaussian-weighted differences of
+ row differences; the scalar correction is accumulated across rows and
+ zero-levelled. When the effective weight sum is zero (for example
+ pure vertical row offsets with identical row shape), no correction is
+ applied to that row pair; the source behaviour is preserved rather
+ than repaired.
+
+ ``mask``, ``mask_mode`` and ``direction`` follow the shared Align Rows
+ public contract. The result is a new context-preserving ``SPMChannel``.
+ """
+ return _gwyddion_align_rows_remaining_channel(
+ channel,
+ method=_GwydionAlignRowsMethod.MATCH,
+ degree=0,
+ mask=mask,
+ mask_mode=mask_mode,
+ direction=direction,
+ )
+
+
def shift_vertical(channel: SPMChannel, *, offset: float) -> SPMChannel:
"""Add a finite scalar offset to every height value."""
data = _validated_data(channel, operation="shift_vertical")
diff --git a/src/spmkit/core/analysis/scanline.py b/src/spmkit/core/analysis/scanline.py
new file mode 100644
index 0000000..45bf3a7
--- /dev/null
+++ b/src/spmkit/core/analysis/scanline.py
@@ -0,0 +1,281 @@
+"""Gwydion 2.71 scan-line defect capabilities: Step Line Correction and
+Mark Inverted Rows.
+
+Public compatibility APIs backed by private production kernels that
+reproduce the frozen Gwydion 2.71 numerical operations (source-inclusion
+compiled probe, independent oracle, frozen fixtures) within the validated
+finite-input scope. Horizontal row processing only; no direction, mask,
+threshold or filter parameters are accepted.
+"""
+
+from __future__ import annotations
+
+import math
+from typing import TYPE_CHECKING, Literal
+
+import numpy as np
+
+from spmkit.core.analysis._gwydion_mark_inverted_rows import (
+ _gwydion_mark_inverted_rows_result,
+)
+from spmkit.core.analysis._gwydion_mark_scars import (
+ _gwydion_mark_scars_result,
+)
+from spmkit.core.analysis._gwydion_remove_scars import (
+ _gwydion_remove_scars_result,
+)
+from spmkit.core.analysis._gwydion_step_block import (
+ _gwydion_step_block_result,
+)
+from spmkit.core.analysis._gwydion_step_line_correction import (
+ _gwydion_step_line_correction_result,
+)
+
+if TYPE_CHECKING:
+ from spmkit.core.models.spmdata import SPMChannel
+
+FloatArray = np.ndarray
+
+
+def _validated_channel_data(channel: SPMChannel, *, operation: str) -> FloatArray:
+ """Validate channel data for the scan-line operations.
+
+ Requires a non-empty two-dimensional finite numeric field; NaN and
+ infinities are rejected (a deliberate SPMKit policy difference from the
+ Gwydion source, which propagates IEEE arithmetic without pre-filtering).
+ """
+ data = np.asarray(channel.data)
+ if data.ndim != 2:
+ raise ValueError(f"{operation} requires a two-dimensional channel")
+ if data.size == 0:
+ raise ValueError(f"{operation} requires non-empty data")
+ if not np.issubdtype(data.dtype, np.number) or np.iscomplexobj(data):
+ raise TypeError(f"{operation} requires real numeric data")
+ if not np.all(np.isfinite(data)):
+ raise ValueError(f"{operation} requires finite data")
+ return np.array(data, dtype=np.float64, order="C", copy=True)
+
+
+def gwydion_step_line_correction(channel: SPMChannel) -> SPMChannel:
+ """Apply the frozen Gwydion 2.71 Step Line Correction operation.
+
+ The operation aligns rows by upper-median row statistics, runs two
+ passes of the step detector (v = (middle-top)*(middle-bottom) >
+ 3.0*w, segments of at least 4 equal-sign pixels, correction
+ (3*segment_residual + local_residual)/4), applies the size-5
+ conservative denoise filter (numerical no-op for any dimension below
+ 5), and restores the original global mean. It is an aggressive and
+ potentially destructive transformation.
+
+ ``channel`` data must be non-empty, two-dimensional, real and finite
+ (NaN/Inf rejected). The operation is horizontal-only and accepts no
+ direction, mask, threshold or filter parameters. The input channel is
+ not mutated; a new ``SPMChannel`` with the input context preserved
+ (shape, ranges, units, copied metadata) is returned.
+ """
+ data = _validated_channel_data(channel, operation="Step Line Correction")
+ corrected = _gwydion_step_line_correction_result(data, trace=False)
+ assert isinstance(corrected, np.ndarray)
+ return channel.with_data(corrected)
+
+
+def gwydion_mark_inverted_rows(channel: SPMChannel) -> FloatArray:
+ """Mark rows whose sign is inverted relative to their neighbours.
+
+ The frozen Gwydion 2.71 operation classifies rows from the sign of
+ adjacent-row correlation weights
+ (sum((x-mean_a)*(y-mean_b)) / (rms_a*rms_b + total_rms**2)), anchors at
+ the most positively correlated block (strict first maximum) and toggles
+ inversion only at strictly negative weights. It never modifies the data
+ field.
+
+ ``channel`` data must be non-empty, two-dimensional, real and finite
+ (NaN/Inf rejected). The operation is horizontal-only and accepts no
+ direction or threshold parameters.
+
+ Returns a new C-contiguous float64 mask with the channel shape and
+ values exactly 0.0 or 1.0 (1.0 on inverted rows). SPMKit deliberately
+ has no persistent channel mask state: the mask is returned as an
+ independent array, and source paths that would not create a mask map to
+ an all-zero returned mask. The input channel is never mutated.
+ """
+ data = _validated_channel_data(channel, operation="Mark Inverted Rows")
+ result = _gwydion_mark_inverted_rows_result(data, existing_mask=None)
+ if result.generated_mask is None:
+ return np.zeros(data.shape, dtype=np.float64, order="C")
+ return np.array(result.generated_mask, dtype=np.float64, order="C", copy=True)
+
+
+GwyddionScarPolarity = Literal["positive", "negative", "both"]
+"""Polarity selector for :func:`gwydion_mark_scars` and
+:func:`gwydion_remove_scars`, mirroring the Gwyddion process-module enum
+(POSITIVE = 1, NEGATIVE = 4, BOTH = 3)."""
+
+GwyddionMaskCombineMode = Literal["replace", "union", "intersection"]
+"""Combination mode of a Mark Scars detector result with an existing mask
+(replace ignores the existing mask; union is source-compatible fmax;
+intersection is source-compatible fmin)."""
+
+
+def gwydion_mark_scars(
+ channel: SPMChannel,
+ *,
+ threshold_high: float = 0.666,
+ threshold_low: float = 0.25,
+ min_length: int = 16,
+ max_width: int = 4,
+ polarity: GwyddionScarPolarity = "both",
+ existing_mask: np.ndarray | None = None,
+ combine: GwyddionMaskCombineMode = "replace",
+) -> np.ndarray:
+ """Mark horizontal scan-line scars in a channel (frozen Gwydion 2.71).
+
+ The detector computes a single global vertical-difference RMS
+ (sum of squared vertical neighbour differences divided by xres*yres),
+ searches per column for bands up to ``max_width`` rows whose values lie
+ at least ``threshold_low`` RMS away from their boundary rows, keeps
+ pixels with weight at least ``threshold_high`` RMS as hard seeds,
+ attaches adjacent soft pixels through chained horizontal expansion and
+ retains only per-row runs of at least ``min_length`` pixels. Positive
+ scars are bands elevated above their neighbours; negative scars are
+ depressed bands; ``"both"`` runs the two detectors and unions the
+ binary masks. This is a detector, not proof of physical corruption.
+
+ ``channel`` data must be non-empty, two-dimensional, real and finite.
+ Parameter domains match the Gwyddion process module: thresholds finite
+ within [0.0, 2.0], ``min_length`` within [1, 1024], ``max_width``
+ within [1, 16]. When ``threshold_low > threshold_high`` the effective
+ hard threshold becomes ``threshold_low`` (source sanitization).
+
+ ``existing_mask`` (when given) must be finite, two-dimensional and
+ shape-compatible; it is never mutated. ``combine="replace"`` ignores
+ it; ``"union"`` (fmax) and ``"intersection"`` (fmin) require it.
+ Combined masks may retain finite non-binary values originating from the
+ existing mask; only the bare detector output is exactly binary 0.0/1.0.
+
+ The input channel is never mutated. A no-detection replace result is
+ an all-zero returned mask; SPMKit does not simulate Data Browser mask
+ removal or persistence.
+
+ Returns a new C-contiguous float64 mask with the channel shape.
+ """
+ data = _validated_channel_data(channel, operation="Mark Scars")
+ if not math.isfinite(threshold_high) or not 0.0 <= threshold_high <= 2.0:
+ raise ValueError("threshold_high must be finite and within [0.0, 2.0]")
+ if not math.isfinite(threshold_low) or not 0.0 <= threshold_low <= 2.0:
+ raise ValueError("threshold_low must be finite and within [0.0, 2.0]")
+ if not isinstance(min_length, int) or isinstance(min_length, bool):
+ raise TypeError("min_length must be an integer")
+ if not 1 <= min_length <= 1024:
+ raise ValueError("min_length must be within [1, 1024]")
+ if not isinstance(max_width, int) or isinstance(max_width, bool):
+ raise TypeError("max_width must be an integer")
+ if not 1 <= max_width <= 16:
+ raise ValueError("max_width must be within [1, 16]")
+ if polarity not in ("positive", "negative", "both"):
+ raise ValueError("polarity must be 'positive', 'negative' or 'both'")
+ if combine not in ("replace", "union", "intersection"):
+ raise ValueError("combine must be 'replace', 'union' or 'intersection'")
+ if combine != "replace" and existing_mask is None:
+ raise ValueError("union/intersection require an existing mask")
+ result = _gwydion_mark_scars_result(
+ data,
+ threshold_high=threshold_high,
+ threshold_low=threshold_low,
+ min_length=min_length,
+ max_width=max_width,
+ polarity=polarity,
+ existing_mask=existing_mask,
+ combine=combine,
+ )
+ return np.array(result.final_mask, dtype=np.float64, order="C", copy=True)
+
+
+def gwydion_remove_scars(
+ channel: SPMChannel,
+ *,
+ threshold_high: float = 0.666,
+ threshold_low: float = 0.25,
+ min_length: int = 16,
+ max_width: int = 4,
+ polarity: GwyddionScarPolarity = "both",
+) -> SPMChannel:
+ """Remove horizontal scan-line scars (frozen Gwydion 2.71 composition).
+
+ Exactly composes the Mark Scars detector (with the same parameter
+ semantics as :func:`gwydion_mark_scars`) and the Laplace interpolation
+ of :func:`spmkit.core.analysis.interpolation.gwydion_interpolate_data_under_mask`.
+ The detector mask is a private temporary mask: it is never exposed,
+ never mutated and never stored. No extra hidden correction is applied.
+
+ ``channel`` data must be non-empty, two-dimensional, real and finite.
+ The input channel is never mutated; a new ``SPMChannel`` preserving the
+ input context (shape, ranges, units, copied metadata) is returned.
+ Detected/interpolated values are not claimed to be physically
+ recovered.
+ """
+ data = _validated_channel_data(channel, operation="Remove Scars")
+ if not math.isfinite(threshold_high) or not 0.0 <= threshold_high <= 2.0:
+ raise ValueError("threshold_high must be finite and within [0.0, 2.0]")
+ if not math.isfinite(threshold_low) or not 0.0 <= threshold_low <= 2.0:
+ raise ValueError("threshold_low must be finite and within [0.0, 2.0]")
+ if not isinstance(min_length, int) or isinstance(min_length, bool):
+ raise TypeError("min_length must be an integer")
+ if not 1 <= min_length <= 1024:
+ raise ValueError("min_length must be within [1, 1024]")
+ if not isinstance(max_width, int) or isinstance(max_width, bool):
+ raise TypeError("max_width must be an integer")
+ if not 1 <= max_width <= 16:
+ raise ValueError("max_width must be within [1, 16]")
+ if polarity not in ("positive", "negative", "both"):
+ raise ValueError("polarity must be 'positive', 'negative' or 'both'")
+ result = _gwydion_remove_scars_result(
+ data,
+ threshold_high=threshold_high,
+ threshold_low=threshold_low,
+ min_length=min_length,
+ max_width=max_width,
+ polarity=polarity,
+ )
+ return channel.with_data(result.corrected_field)
+
+
+def gwydion_step_block_correction(
+ channel: SPMChannel,
+ *,
+ threshold: float = 2.0,
+ direction: Literal["left_to_right", "right_to_left"] = "left_to_right",
+) -> SPMChannel:
+ """Correct vertical steps in scan lines by block (frozen Gwydion 2.71).
+
+ The operation detects per-pixel vertical jumps whose absolute
+ difference exceeds an effective threshold, scores each row boundary and
+ horizontal split position (first strict maximum), constructs row
+ blocks, estimates each block's shift with a 25% trimmed mean over the
+ boundary shift samples, and applies a cumulative piecewise-constant
+ correction anchored at the first block. Left-to-right and
+ right-to-left scan directions are supported; no mask is consumed.
+
+ ``channel`` data must be non-empty, two-dimensional, real and finite.
+ ``threshold`` must be within [0.1, 10.0] (source-supported public
+ range); ``direction`` must be ``"left_to_right"`` or
+ ``"right_to_left"``. Fields with xres < 2 are rejected with a typed
+ ValueError: the frozen Gwydion source performs an out-of-bounds read
+ for xres=1 (documented SOURCE_DEFECT) and SPMKit never exposes
+ undefined behaviour.
+
+ The input channel is never mutated; a new ``SPMChannel`` preserving the
+ input context (shape, ranges, units, direction, copied metadata) is
+ returned. No claim is made that a detected step is an acquisition
+ artefact rather than a real topographic discontinuity, and no
+ preservation of roughness, PSD, morphology or uncertainty is claimed.
+ """
+ data = _validated_channel_data(channel, operation="Step Block Correction")
+ if not math.isfinite(threshold) or not 0.1 <= threshold <= 10.0:
+ raise ValueError("threshold must be finite and within [0.1, 10.0]")
+ if direction not in ("left_to_right", "right_to_left"):
+ raise ValueError("direction must be left_to_right or right_to_left")
+ dy = channel.y_range / data.shape[0]
+ result = _gwydion_step_block_result(data, threshold=threshold,
+ direction=direction, dy=dy)
+ return channel.with_data(result.corrected_field)
diff --git a/src/spmkit/core/capabilities.json b/src/spmkit/core/capabilities.json
new file mode 100644
index 0000000..303d42a
--- /dev/null
+++ b/src/spmkit/core/capabilities.json
@@ -0,0 +1,6620 @@
+{
+ "capabilities": [
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.BASELINE.CORRECT",
+ "contract": "Subtract the fitted baseline (offset + slope over height) with scope all/baseline/approach; slope correction changes the data (documented).",
+ "evidence": [
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.json",
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.npz",
+ "tests/validation/fixtures/force_foundation/force_foundation_reference.json",
+ "tests/validation/test_force_foundation_validation.py",
+ "tests/core/test_force_foundation.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.baseline.correct",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Force curve.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "curve",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Fitted baseline.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "baseline",
+ "required": true,
+ "type": "ForceBaselineResult",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": "all",
+ "description": "Correction scope.",
+ "enum_values": [
+ "all",
+ "baseline",
+ "approach"
+ ],
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "scope",
+ "required": false,
+ "type": "str",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:correct_force_baseline",
+ "public_name": "correct_force_baseline",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Force baseline correction",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "N"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.BASELINE.FIT",
+ "contract": "Fit the pre-contact baseline (first 10% of approach): linear offset + slope via polyfit; optional deterministic Huber-IRLS robust fit; residual RMS and robust scale; BASELINE_TOO_SHORT for too few points.",
+ "evidence": [
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.json",
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.npz",
+ "tests/validation/fixtures/force_foundation/force_foundation_reference.json",
+ "tests/validation/test_force_foundation_validation.py",
+ "tests/core/test_force_foundation.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.baseline.fit",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Force curve.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "curve",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": "pre_contact",
+ "description": "Baseline region.",
+ "enum_values": [
+ "pre_contact"
+ ],
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "region",
+ "required": false,
+ "type": "str",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": "linear",
+ "description": "Baseline model.",
+ "enum_values": [
+ "linear"
+ ],
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "model",
+ "required": false,
+ "type": "str",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": false,
+ "description": "Robust IRLS fit.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "robust",
+ "required": false,
+ "type": "bool",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:fit_force_baseline",
+ "public_name": "fit_force_baseline",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Force baseline fit",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "N"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.CALIBRATION.APPLY",
+ "contract": "raw deflection voltage (V) -> deflection (m) via InVOLS (m/V) -> force (N) via spring constant (N/m); already-calibrated pass-through; double calibration rejected (INVALID_CALIBRATION); missing calibration raises MISSING_CALIBRATION.",
+ "evidence": [
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.json",
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.npz",
+ "tests/validation/fixtures/force_foundation/force_foundation_reference.json",
+ "tests/validation/test_force_foundation_validation.py",
+ "tests/core/test_force_foundation.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.calibration.apply",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Force curve to calibrate.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "curve",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Explicit calibration.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "keyword_only",
+ "name": "calibration",
+ "required": false,
+ "type": "Calibration | None",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:calibrate_force_curve",
+ "public_name": "calibrate_force_curve",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Force calibration application",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "N"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.CONTACT.ENSEMBLE",
+ "contract": "Combine threshold/ROV/piecewise; robust location = median of valid candidate indices; explicit disagreement and spread; deterministic bootstrap only when requested; CONTACT_METHOD_DISAGREEMENT when fewer than two methods agree.",
+ "evidence": [
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.json",
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.npz",
+ "tests/validation/fixtures/force_foundation/force_foundation_reference.json",
+ "tests/validation/test_force_foundation_validation.py",
+ "tests/core/test_force_foundation.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [
+ "method spread does not constitute an uncertainty guarantee",
+ "maturity downgraded at independent audit: NUMERICALLY_VERIFIED -> SOFTWARE_VERIFIED"
+ ],
+ "mask_semantics": "none",
+ "maturity": "SOFTWARE_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.contact.ensemble",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Force curve.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "curve",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": [
+ "threshold",
+ "ratio_of_variances",
+ "piecewise"
+ ],
+ "description": "Contact methods.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "methods",
+ "required": false,
+ "type": "tuple[str, ...]",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 0,
+ "description": "Bootstrap samples.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "bootstrap_samples",
+ "required": false,
+ "type": "int",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:contact_point_ensemble",
+ "public_name": "contact_point_ensemble",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Contact point (ensemble)",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "m"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.CONTACT.PIECEWISE",
+ "contract": "Value-continuous piecewise baseline/contact polynomial fit over the search grid; requires a genuine residual improvement over a single whole-curve polynomial (flat curves fail).",
+ "evidence": [
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.json",
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.npz",
+ "tests/validation/fixtures/force_foundation/force_foundation_reference.json",
+ "tests/validation/test_force_foundation_validation.py",
+ "tests/core/test_force_foundation.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.contact.piecewise",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Force curve.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "curve",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 1,
+ "description": "Baseline order.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "baseline_order",
+ "required": false,
+ "type": "int",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 2,
+ "description": "Contact order.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "contact_order",
+ "required": false,
+ "type": "int",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:contact_point_piecewise",
+ "public_name": "contact_point_piecewise",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Contact point (piecewise)",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "m"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.CONTACT.RATIO_OF_VARIANCES",
+ "contract": "Gavara 2016 ratio-of-variances contact: argmax of variance-after / variance-before over the window grid; requires a genuine variance jump (ratio >= 2) and sufficient length.",
+ "evidence": [
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.json",
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.npz",
+ "tests/validation/fixtures/force_foundation/force_foundation_reference.json",
+ "tests/validation/test_force_foundation_validation.py",
+ "tests/core/test_force_foundation.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.contact.ratio_of_variances",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Force curve.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "curve",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 20,
+ "description": "Variance window.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "window",
+ "required": false,
+ "type": "int",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:contact_point_ratio_of_variances",
+ "public_name": "contact_point_ratio_of_variances",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Contact point (ratio of variances)",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "m"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.CONTACT.THRESHOLD",
+ "contract": "Baseline-relative threshold contact: first crossing of mean + k*sigma with persistence 3; validated against the frozen nanite 4.2.3 deviation_from_baseline contact index on the shared noiseless cases.",
+ "evidence": [
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.json",
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.npz",
+ "tests/validation/fixtures/force_foundation/force_foundation_reference.json",
+ "tests/validation/test_force_foundation_validation.py",
+ "tests/core/test_force_foundation.py",
+ "tests/validation/fixtures/force_foundation/force_foundation_external.npz"
+ ],
+ "family": "FORCE",
+ "known_deviations": [
+ "production threshold agrees with nanite deviation_from_baseline on clean flat-baseline cases (0..2 samples) but diverges on sloped/noisy baselines (up to 13 samples across the 17-case persisted matrix); NOT cross-validated as equivalent",
+ "sloped noiseless baselines degrade threshold recovery (characterized)"
+ ],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.contact.threshold",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Force curve.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "curve",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 5.0,
+ "description": "Threshold in sigma.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "threshold_sigma",
+ "required": false,
+ "type": "float",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:contact_point_threshold",
+ "public_name": "contact_point_threshold",
+ "reference": {
+ "software": "nanite",
+ "version": "4.2.3",
+ "name": "Contact point (nanite deviation_from_baseline profile)",
+ "profile": "COMPILED_NANITE_4_2_3_EXTERNAL_REFERENCE_FROZEN_PROFILE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "m"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.EVENTS.EXTRACT",
+ "contract": "Snap-in = minimum force before contact on approach (baseline-relative below mean - 3*sigma); pull-off = minimum force after contact on retract; physical windows; no event when the relevant segment is absent (EVENT_NOT_FOUND).",
+ "evidence": [
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.json",
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.npz",
+ "tests/validation/fixtures/force_foundation/force_foundation_reference.json",
+ "tests/validation/test_force_foundation_validation.py",
+ "tests/core/test_force_foundation.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.events.extract",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Force curve.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "curve",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Contact point result.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "contact",
+ "required": true,
+ "type": "ContactPointResult | ContactPointCandidate",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Snap-in window (coordinate).",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "snap_in_window",
+ "required": false,
+ "type": "tuple[float, float] | None",
+ "units": "m"
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Pull-off window (coordinate).",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "pull_off_window",
+ "required": false,
+ "type": "tuple[float, float] | None",
+ "units": "m"
+ }
+ ],
+ "public_import": "spmkit.core.analysis:extract_force_events",
+ "public_name": "extract_force_events",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Force events (snap-in / pull-off)",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "N"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.FIT_WINDOW.SELECT",
+ "contract": "Contiguous contact fit window from the contact index, optionally trimmed by min/max indentation and min/max force; fewer than min_points raises EMPTY_FIT_WINDOW / INSUFFICIENT_FIT_POINTS; included mask consistent with n_points; non-mutating.",
+ "evidence": [
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.json",
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz",
+ "tests/validation/test_force_mechanics_validation.py",
+ "tests/core/test_force_mechanics.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "SOFTWARE_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.fit_window.select",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Prepared curve.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "prepared",
+ "required": true,
+ "type": "ForcePreparationResult",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "IndentationResult.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "indentation",
+ "required": true,
+ "type": "IndentationResult",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Lower indentation bound (m).",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "min_indentation",
+ "required": false,
+ "type": "float | None",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Upper indentation bound (m).",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "max_indentation",
+ "required": false,
+ "type": "float | None",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Lower force bound (N).",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "min_force",
+ "required": false,
+ "type": "float | None",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Upper force bound (N).",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "max_force",
+ "required": false,
+ "type": "float | None",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 20,
+ "description": "Minimum window size.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "min_points",
+ "required": false,
+ "type": "int",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:select_contact_fit_window",
+ "public_name": "select_contact_fit_window",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Contact fit window selection",
+ "profile": "NATIVE_SPMKIT_DESIGNED_HEURISTIC"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "not_applicable"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.INDENTATION.COMPUTE",
+ "contract": "Indentation = approach separation minus the FS-F1 contact coordinate; zero at the contact and positive into the sample; pre-contact samples excluded by the valid mask; requires a fit-eligible prepared curve (CURVE_NOT_FIT_ELIGIBLE typed failure); NONFINITE_INPUT typed failure; units m; non-mutating.",
+ "evidence": [
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.json",
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz",
+ "tests/validation/test_force_mechanics_validation.py",
+ "tests/core/test_force_mechanics.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.indentation.compute",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "FS-F1 prepared curve.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "prepared",
+ "required": true,
+ "type": "ForcePreparationResult",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:compute_indentation",
+ "public_name": "compute_indentation",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Indentation from separation and contact",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "m"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.MODEL.COMPARE",
+ "contract": "Model-relative comparison over the identical data subset; AICc weights normalized to 1; recommended model is the AICc minimum unless the runner-up retains considerable support (Delta AICc < 4 -> ambiguous, no recommendation); no physical-truth claim; misspecified fits detected (cone data -> sneddon weight > 0.9); unknown model raises ValueError.",
+ "evidence": [
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.json",
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz",
+ "tests/validation/test_force_mechanics_validation.py",
+ "tests/core/test_force_mechanics.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.model.compare",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Prepared curve.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "prepared",
+ "required": true,
+ "type": "ForcePreparationResult",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "IndentationResult.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "indentation",
+ "required": true,
+ "type": "IndentationResult",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "FitWindowResult.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "window",
+ "required": true,
+ "type": "FitWindowResult",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": [
+ "hertz_sphere",
+ "sneddon_cone",
+ "flat_punch",
+ "dmt"
+ ],
+ "description": "Candidate models.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "models",
+ "required": false,
+ "type": "tuple[str, ...]",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Tip radius (m).",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "keyword_only",
+ "name": "tip_radius",
+ "required": true,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 0.3490658503988659,
+ "description": "Cone half-angle (rad).",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "half_angle",
+ "required": false,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Punch radius (m).",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "punch_radius",
+ "required": false,
+ "type": "float | None",
+ "units": null
+ },
+ {
+ "bounds": [
+ 0.0,
+ 0.5
+ ],
+ "default": 0.3,
+ "description": "Poisson ratio.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "poisson",
+ "required": false,
+ "type": "float",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:compare_contact_models",
+ "public_name": "compare_contact_models",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "AICc model comparison",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "not_applicable"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.MODEL.FIT_DMT",
+ "contract": "Two-parameter fit of E and F_adh over the window trimmed past the snap-in region; on snap-in phantoms E within 30% and F_adh within 1.5e-9 N (FS-F1 contact ensemble is unstable on snap-in curves, up to ~10 samples off); typed failures INVALID_RADIUS, INVALID_POISSON_RATIO, INVALID_ADHESION_PARAMETER, OPTIMIZATION_FAILED.",
+ "evidence": [
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.json",
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz",
+ "tests/validation/test_force_mechanics_validation.py",
+ "tests/core/test_force_mechanics.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [
+ "snap-in curves: FS-F1 contact ensemble unstable (up to ~10 samples off); dedicated snap-in contact detection is future work"
+ ],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.model.fit_dmt",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Prepared curve.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "prepared",
+ "required": true,
+ "type": "ForcePreparationResult",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "IndentationResult.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "indentation",
+ "required": true,
+ "type": "IndentationResult",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "FitWindowResult.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "window",
+ "required": true,
+ "type": "FitWindowResult",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Tip radius (m).",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "keyword_only",
+ "name": "tip_radius",
+ "required": true,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": [
+ 0.0,
+ 0.5
+ ],
+ "default": 0.3,
+ "description": "Poisson ratio.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "poisson",
+ "required": false,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 1000000000.0,
+ "description": "Optimizer start (Pa).",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "E_initial",
+ "required": false,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 1e-09,
+ "description": "Adhesion start (N).",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "F_adh_initial",
+ "required": false,
+ "type": "float",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:fit_dmt",
+ "public_name": "fit_dmt",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "DMT fit",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "Pa"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.MODEL.FIT_FLAT_PUNCH",
+ "contract": "Linear-modulus least-squares fit of E with punch radius and poisson ratio fixed; E within 5% on clean phantoms; typed failures INVALID_RADIUS, INVALID_POISSON_RATIO, OPTIMIZATION_FAILED, NONFINITE_INPUT; same result contract as fit_hertz_sphere.",
+ "evidence": [
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.json",
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz",
+ "tests/validation/test_force_mechanics_validation.py",
+ "tests/core/test_force_mechanics.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.model.fit_flat_punch",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Prepared curve.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "prepared",
+ "required": true,
+ "type": "ForcePreparationResult",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "IndentationResult.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "indentation",
+ "required": true,
+ "type": "IndentationResult",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "FitWindowResult.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "window",
+ "required": true,
+ "type": "FitWindowResult",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Punch radius (m).",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "keyword_only",
+ "name": "punch_radius",
+ "required": true,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": [
+ 0.0,
+ 0.5
+ ],
+ "default": 0.3,
+ "description": "Poisson ratio.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "poisson",
+ "required": false,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 1000000000.0,
+ "description": "Optimizer start (Pa).",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "E_initial",
+ "required": false,
+ "type": "float",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:fit_flat_punch",
+ "public_name": "fit_flat_punch",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Flat punch fit",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "Pa"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.MODEL.FIT_HERTZ",
+ "contract": "Nonlinear least-squares fit of E over the fit window with tip radius and poisson ratio fixed; E within 5% on clean phantoms (contact-precision limited); typed failures INVALID_RADIUS, INVALID_POISSON_RATIO, OPTIMIZATION_FAILED, NONFINITE_INPUT; result carries parameters, covariance, residuals, AIC/AICc/BIC, rmse and window provenance.",
+ "evidence": [
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.json",
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz",
+ "tests/validation/test_force_mechanics_validation.py",
+ "tests/core/test_force_mechanics.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.model.fit_hertz",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Prepared curve.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "prepared",
+ "required": true,
+ "type": "ForcePreparationResult",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "IndentationResult.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "indentation",
+ "required": true,
+ "type": "IndentationResult",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "FitWindowResult.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "window",
+ "required": true,
+ "type": "FitWindowResult",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Tip radius (m).",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "keyword_only",
+ "name": "tip_radius",
+ "required": true,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": [
+ 0.0,
+ 0.5
+ ],
+ "default": 0.3,
+ "description": "Poisson ratio.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "poisson",
+ "required": false,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 1000000000.0,
+ "description": "Optimizer start (Pa).",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "E_initial",
+ "required": false,
+ "type": "float",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:fit_hertz_sphere",
+ "public_name": "fit_hertz_sphere",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Hertz sphere fit",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "Pa"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.MODEL.FIT_JKR",
+ "contract": "Two-parameter fit of E and w over the window trimmed past the snap-in region; loading curve parametrized by the contact radius (monotone for a >= a0, range derived from data); w=0 reduces to hertz; on snap-in phantoms E within 20% and w within 30%; typed failures INVALID_RADIUS, INVALID_POISSON_RATIO, INVALID_ADHESION_PARAMETER, OPTIMIZATION_FAILED.",
+ "evidence": [
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.json",
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz",
+ "tests/validation/test_force_mechanics_validation.py",
+ "tests/core/test_force_mechanics.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [
+ "snap-in curves: same contact-ensemble limitation as fit_dmt"
+ ],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.model.fit_jkr",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Prepared curve.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "prepared",
+ "required": true,
+ "type": "ForcePreparationResult",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "IndentationResult.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "indentation",
+ "required": true,
+ "type": "IndentationResult",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "FitWindowResult.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "window",
+ "required": true,
+ "type": "FitWindowResult",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Tip radius (m).",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "keyword_only",
+ "name": "tip_radius",
+ "required": true,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": [
+ 0.0,
+ 0.5
+ ],
+ "default": 0.3,
+ "description": "Poisson ratio.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "poisson",
+ "required": false,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 1000000000.0,
+ "description": "Optimizer start (Pa).",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "E_initial",
+ "required": false,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 0.001,
+ "description": "Work-of-adhesion start (J/m^2).",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "w_initial",
+ "required": false,
+ "type": "float",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:fit_jkr",
+ "public_name": "fit_jkr",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "JKR fit",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "Pa"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.MODEL.FIT_SNEDDON",
+ "contract": "Nonlinear least-squares fit of E with cone half-angle and poisson ratio fixed; E within 5% on clean phantoms; typed failures INVALID_ANGLE, INVALID_POISSON_RATIO, OPTIMIZATION_FAILED, NONFINITE_INPUT; same result contract as fit_hertz_sphere.",
+ "evidence": [
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.json",
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz",
+ "tests/validation/test_force_mechanics_validation.py",
+ "tests/core/test_force_mechanics.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.model.fit_sneddon",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Prepared curve.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "prepared",
+ "required": true,
+ "type": "ForcePreparationResult",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "IndentationResult.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "indentation",
+ "required": true,
+ "type": "IndentationResult",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "FitWindowResult.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "window",
+ "required": true,
+ "type": "FitWindowResult",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Cone half-angle (rad).",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "keyword_only",
+ "name": "half_angle",
+ "required": true,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": [
+ 0.0,
+ 0.5
+ ],
+ "default": 0.3,
+ "description": "Poisson ratio.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "poisson",
+ "required": false,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 1000000000.0,
+ "description": "Optimizer start (Pa).",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "E_initial",
+ "required": false,
+ "type": "float",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:fit_sneddon_cone",
+ "public_name": "fit_sneddon_cone",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Sneddon cone fit",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "Pa"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.MODEL.FORWARD",
+ "contract": "Frozen closed-form loading equations with reduced modulus E* = E/(1-nu^2): hertz F = (4/3) E* sqrt(R) d^1.5; sneddon F = (2 tan(alpha)/pi) E* d^2; flat punch F = 2 E* R d; dmt F = hertz - F_adh; jkr parametric contact-radius loading curve (monotone, derived range, w=0 reduces to hertz); SI units N; unknown model raises ValueError.",
+ "evidence": [
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.json",
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz",
+ "tests/validation/test_force_mechanics_validation.py",
+ "tests/core/test_force_mechanics.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.model.forward",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Model name.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "model",
+ "required": true,
+ "type": "str",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Indentation array (m).",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "delta",
+ "required": true,
+ "type": "np.ndarray",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Model parameters.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "params",
+ "required": true,
+ "type": "dict[str, float]",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:forward_model",
+ "public_name": "forward_model",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Contact-model forward equations",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "N"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.PREPARE",
+ "contract": "Explicit orchestration over the 12 public primitives: segments -> calibration -> tip-sample separation -> baseline fit/correction -> contact ensemble -> events -> work -> quality; provenance names every decision; contact detection runs on the calibrated curve.",
+ "evidence": [
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.json",
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.npz",
+ "tests/validation/fixtures/force_foundation/force_foundation_reference.json",
+ "tests/validation/test_force_foundation_validation.py",
+ "tests/core/test_force_foundation.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [
+ "orchestration maturity is bounded by its weakest material component (contact ensemble and quality score are SOFTWARE_VERIFIED heuristics)"
+ ],
+ "mask_semantics": "none",
+ "maturity": "SOFTWARE_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.prepare",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Force curve.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "curve",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Explicit calibration.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "calibration",
+ "required": false,
+ "type": "Calibration | None",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": "linear",
+ "description": "Baseline model.",
+ "enum_values": [
+ "linear"
+ ],
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "baseline_model",
+ "required": false,
+ "type": "str",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": [
+ "threshold",
+ "ratio_of_variances",
+ "piecewise"
+ ],
+ "description": "Contact methods.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "contact_methods",
+ "required": false,
+ "type": "tuple[str, ...]",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 0,
+ "description": "Bootstrap samples.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "bootstrap_samples",
+ "required": false,
+ "type": "int",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:prepare_force_curve",
+ "public_name": "prepare_force_curve",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Force curve preparation pipeline",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "not_applicable"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.QUALITY.SCORE",
+ "contract": "Typed failure reasons (14 codes) beside a summary score; component diagnostics always explicit; eligibility for contact-model fitting.",
+ "evidence": [
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.json",
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.npz",
+ "tests/validation/fixtures/force_foundation/force_foundation_reference.json",
+ "tests/validation/test_force_foundation_validation.py",
+ "tests/core/test_force_foundation.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [
+ "the aggregate summary score is a designed heuristic; it is not an externally validated scientific quality probability"
+ ],
+ "mask_semantics": "none",
+ "maturity": "SOFTWARE_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.quality.score",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Force curve.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "curve",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Segmentation result.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "segmentation",
+ "required": false,
+ "type": "ForceSegmentationResult | None",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Baseline result.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "baseline",
+ "required": false,
+ "type": "ForceBaselineResult | None",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Contact result.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "contact",
+ "required": false,
+ "type": "ContactPointResult | None",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Events result.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "events",
+ "required": false,
+ "type": "ForceEventResult | None",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:score_force_curve_quality",
+ "public_name": "score_force_curve_quality",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Force curve quality scoring",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "not_applicable"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.RELIABILITY.BOOTSTRAP",
+ "contract": "Deterministic residual (or block-residual) bootstrap of the hertz fit; percentile intervals and bias estimate over E; replicate failures counted, never masked; success fraction below min_success_fraction (or outside [0,1]) raises BOOTSTRAP_INSUFFICIENT_SUCCESS; same seed reproduces identical samples.",
+ "evidence": [
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.json",
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz",
+ "tests/validation/test_force_mechanics_validation.py",
+ "tests/core/test_force_mechanics.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.reliability.bootstrap",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "(prepared, indentation, window, model) tuple.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "spec",
+ "required": true,
+ "type": "tuple",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 500,
+ "description": "Replicate count.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "samples",
+ "required": false,
+ "type": "int",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 0,
+ "description": "RNG seed.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "seed",
+ "required": false,
+ "type": "int",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": "residual",
+ "description": "Resampling strategy.",
+ "enum_values": [
+ "residual",
+ "block_residual"
+ ],
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "strategy",
+ "required": false,
+ "type": "str",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 1e-08,
+ "description": "Tip radius (m).",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "tip_radius",
+ "required": false,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": [
+ 0.0,
+ 0.5
+ ],
+ "default": 0.3,
+ "description": "Poisson ratio.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "poisson",
+ "required": false,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 0.5,
+ "description": "Minimum success fraction.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "min_success_fraction",
+ "required": false,
+ "type": "float",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:bootstrap_force_fit",
+ "public_name": "bootstrap_force_fit",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Residual bootstrap",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "not_applicable"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.RELIABILITY.DIAGNOSE",
+ "contract": "Explicit diagnostics: residual RMS, autocorrelation and curvature proxies, covariance condition number and max parameter correlation, one-at-a-time contact/window sensitivity, bootstrap success fraction, model-ambiguity flag; the summary status is a policy (ok/review), never a probability; failure reasons listed explicitly.",
+ "evidence": [
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.json",
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz",
+ "tests/validation/test_force_mechanics_validation.py",
+ "tests/core/test_force_mechanics.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "SOFTWARE_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.reliability.diagnose",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Fit result.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "fit",
+ "required": true,
+ "type": "ContactMechanicsFitResult",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Sensitivity result.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "sensitivity",
+ "required": false,
+ "type": "ForceFitSensitivityResult | None",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Bootstrap result.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "bootstrap",
+ "required": false,
+ "type": "BootstrapForceFitResult | None",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:diagnose_force_fit",
+ "public_name": "diagnose_force_fit",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Fit diagnostics policy",
+ "profile": "NATIVE_SPMKIT_DESIGNED_HEURISTIC"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "not_applicable"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.RELIABILITY.SENSITIVITY",
+ "contract": "Deterministic multiverse over contact offsets and fit-window lower-bound fractions (bounded at max_configurations=512); one-at-a-time contact and window sensitivity indices relative to the baseline configuration; E stability ranges and robust medians; dominant sensitivity classified as contact, window or none (relative index > 20%); failed configurations recorded, never dropped; CONTACT_SENSITIVITY_HIGH when no configuration succeeds.",
+ "evidence": [
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.json",
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz",
+ "tests/validation/test_force_mechanics_validation.py",
+ "tests/core/test_force_mechanics.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "SOFTWARE_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.reliability.sensitivity",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Prepared curve.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "prepared",
+ "required": true,
+ "type": "ForcePreparationResult",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": [
+ -3,
+ -1,
+ 0,
+ 1,
+ 3
+ ],
+ "description": "Contact offsets (samples).",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "contact_offsets",
+ "required": false,
+ "type": "tuple[int, ...]",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": [
+ 0.0,
+ 0.05
+ ],
+ "description": "Window lower-bound fractions.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "fit_window_variants",
+ "required": false,
+ "type": "tuple[float, ...]",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": [
+ "linear"
+ ],
+ "description": "Baseline models.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "baseline_variants",
+ "required": false,
+ "type": "tuple[str, ...]",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": [
+ "hertz_sphere"
+ ],
+ "description": "Models.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "models",
+ "required": false,
+ "type": "tuple[str, ...]",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 512,
+ "description": "Multiverse bound.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "max_configurations",
+ "required": false,
+ "type": "int",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 1e-08,
+ "description": "Tip radius (m).",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "tip_radius",
+ "required": false,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": [
+ 0.0,
+ 0.5
+ ],
+ "default": 0.3,
+ "description": "Poisson ratio.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "poisson",
+ "required": false,
+ "type": "float",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:analyze_force_fit_sensitivity",
+ "public_name": "analyze_force_fit_sensitivity",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Contact/window sensitivity multiverse",
+ "profile": "NATIVE_SPMKIT_DESIGNED_HEURISTIC"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "not_applicable"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.SEGMENT.IDENTIFY",
+ "contract": "Identify approach/retract sample indices of a ForceCurve; instrument labels trusted when both segments exist, else turning point = height extremum; no sample reordering.",
+ "evidence": [
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.json",
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.npz",
+ "tests/validation/fixtures/force_foundation/force_foundation_reference.json",
+ "tests/validation/test_force_foundation_validation.py",
+ "tests/core/test_force_foundation.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [
+ "single-segment inference may misplace the turning point on flat turning points"
+ ],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.segment.identify",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Force curve to segment.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "curve",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:identify_force_segments",
+ "public_name": "identify_force_segments",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Force segment identification",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "not_applicable"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.SEPARATION.TIP_SAMPLE",
+ "contract": "Tip-sample separation = height - deflection per segment; no contact offset applied; validated against the frozen nanite 4.2.3 tip-position convention (tip = height + force/k + offset).",
+ "evidence": [
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.json",
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.npz",
+ "tests/validation/fixtures/force_foundation/force_foundation_reference.json",
+ "tests/validation/test_force_foundation_validation.py",
+ "tests/core/test_force_foundation.py",
+ "tests/validation/fixtures/force_foundation/force_foundation_external.npz"
+ ],
+ "family": "FORCE",
+ "known_deviations": [
+ "bitwise external identity not claimed; convention validated numerically on the frozen nanite profile"
+ ],
+ "mask_semantics": "none",
+ "maturity": "CROSS_VALIDATED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.separation.tip_sample",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Force curve.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "curve",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:compute_tip_sample_separation",
+ "public_name": "compute_tip_sample_separation",
+ "reference": {
+ "software": "nanite",
+ "version": "4.2.3",
+ "name": "Tip-sample separation (nanite tip-position profile)",
+ "profile": "COMPILED_NANITE_4_2_3_EXTERNAL_REFERENCE_FROZEN_PROFILE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "m"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.SMFS.BATCH",
+ "contract": "Deterministic batch orchestration over per-curve analyses: every result retained, failed curves retained with reasons, unified event table with curve origins, population aggregation, stable ordering and replay; nothing silently dropped; the orchestration policy is SOFTWARE_VERIFIED.",
+ "evidence": [
+ "tests/validation/fixtures/force_smfs/smfs_reference.json",
+ "tests/validation/fixtures/force_smfs/smfs_reference.npz",
+ "tests/validation/test_force_smfs_validation.py",
+ "tests/core/test_force_smfs.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "SOFTWARE_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.smfs.batch",
+ "parameters": [
+ {
+ "name": "analyses",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "list[dict[str, object]]",
+ "description": "Per-curve analyses.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "group_by",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": "loading_rate_decade",
+ "type": "str",
+ "description": "Grouping policy.",
+ "units": null,
+ "bounds": null,
+ "enum_values": [
+ "none",
+ "loading_rate_decade"
+ ]
+ },
+ {
+ "name": "n_groups",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 4,
+ "type": "int",
+ "description": "Number of groups.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:analyze_smfs_batch",
+ "public_name": "analyze_smfs_batch",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Deterministic batch orchestration over per-curve analyses: e",
+ "profile": "NATIVE_SPMKIT_DESIGNED_HEURISTIC"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "not_applicable"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.SMFS.CONTOUR_INCREMENT",
+ "contract": "Delta contour length per event from independent pre/post WLC fits on the ABSOLUTE molecular extension (a section-relative fit would absorb the event offset into a biased contour); event-index sensitivity characterized by explicit shifts; within 10% on the doubling-contour phantoms.",
+ "evidence": [
+ "tests/validation/fixtures/force_smfs/smfs_reference.json",
+ "tests/validation/fixtures/force_smfs/smfs_reference.npz",
+ "tests/validation/test_force_smfs_validation.py",
+ "tests/core/test_force_smfs.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.smfs.contour_increment",
+ "parameters": [
+ {
+ "name": "extension",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "MolecularExtensionResult",
+ "description": "Extension result.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "events",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "UnfoldingEventResult",
+ "description": "Quantified events.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "model",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": "worm_like_chain",
+ "type": "str",
+ "description": "Polymer model.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "temperature",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 298.0,
+ "type": "float",
+ "description": "Temperature (K).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "pre_margin",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 2,
+ "type": "int",
+ "description": "Pre-event margin (samples).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "post_margin",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 2,
+ "type": "int",
+ "description": "Post-event margin (samples).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "min_points",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 8,
+ "type": "int",
+ "description": "Minimum window points.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "sensitivity_shifts",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": [
+ 0
+ ],
+ "type": "tuple[int, ...]",
+ "description": "Event-index shifts.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:infer_contour_length_increments",
+ "public_name": "infer_contour_length_increments",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Delta contour length per event from independent pre/post WLC",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "m"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.SMFS.EVENTS.DETECT",
+ "contract": "Unfolding-event detection on the pull-ordered retract section: sustained force drops with public thresholds (drop magnitude, persistence, minimum separation, boundary margin); rejected candidates retained with reasons; the final detachment is distinguished from internal unfolding; sub-threshold drops raise NO_EVENTS typed. The detector is a documented heuristic (SOFTWARE_VERIFIED) evaluated with true/false positives on deterministic phantoms.",
+ "evidence": [
+ "tests/validation/fixtures/force_smfs/smfs_reference.json",
+ "tests/validation/fixtures/force_smfs/smfs_reference.npz",
+ "tests/validation/test_force_smfs_validation.py",
+ "tests/core/test_force_smfs.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "SOFTWARE_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.smfs.events.detect",
+ "parameters": [
+ {
+ "name": "extension",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "MolecularExtensionResult",
+ "description": "Extension result.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "min_force_drop",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Minimum drop (N).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "min_persistence",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 3,
+ "type": "int",
+ "description": "Sustained-drop samples.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "min_event_separation",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 3,
+ "type": "int",
+ "description": "Minimum separation (samples).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "noise_sigma",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Noise scale (N).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "boundary_margin",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 2,
+ "type": "int",
+ "description": "Boundary margin (samples).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:detect_unfolding_events",
+ "public_name": "detect_unfolding_events",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Unfolding-event detection on the pull-ordered retract segment",
+ "profile": "NATIVE_SPMKIT_DESIGNED_HEURISTIC"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "N"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.SMFS.EVENTS.QUANTIFY",
+ "contract": "Assign explicit pre/post windows and local loading rates to every selected event; the pre window spans the polymer section between the previous event (or the tether zero) and the event; the post window spans the section to the next event (or the section end).",
+ "evidence": [
+ "tests/validation/fixtures/force_smfs/smfs_reference.json",
+ "tests/validation/fixtures/force_smfs/smfs_reference.npz",
+ "tests/validation/test_force_smfs_validation.py",
+ "tests/core/test_force_smfs.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.smfs.events.quantify",
+ "parameters": [
+ {
+ "name": "extension",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "MolecularExtensionResult",
+ "description": "Extension result.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "events",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "UnfoldingEventResult",
+ "description": "Detected events.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "pre_margin",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 2,
+ "type": "int",
+ "description": "Pre-event margin (samples).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "post_margin",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 2,
+ "type": "int",
+ "description": "Post-event margin (samples).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "min_points",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 8,
+ "type": "int",
+ "description": "Minimum window points.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:quantify_unfolding_events",
+ "public_name": "quantify_unfolding_events",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Assign explicit pre/post windows and local loading rates to ",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "N / m"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.SMFS.EXTENSION.COMPUTE",
+ "contract": "Molecular extension of the retract section with an explicit tether-zero policy: offset (physical m), index, pre_event (caller section start), or the estimator (retract zero-force crossing with its own diagnostics); the zero is never inferred silently from the contact; UNRESOLVED_TETHER_ZERO and INVALID_REFERENCE_POLICY typed; the estimator policy is a documented heuristic making the complete operation SOFTWARE_VERIFIED; the JPK/NID readers do not populate segment time (the SMFS retract section requires an explicit time axis where used).",
+ "evidence": [
+ "tests/validation/fixtures/force_smfs/smfs_reference.json",
+ "tests/validation/fixtures/force_smfs/smfs_reference.npz",
+ "tests/validation/test_force_smfs_validation.py",
+ "tests/core/test_force_smfs.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "SOFTWARE_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.smfs.extension.compute",
+ "parameters": [
+ {
+ "name": "prepared",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "ForcePreparationResult",
+ "description": "FS-F1 prepared curve.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "reference",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": "index",
+ "type": "str",
+ "description": "Tether-zero reference policy.",
+ "units": null,
+ "bounds": null,
+ "enum_values": [
+ "offset",
+ "index",
+ "pre_event",
+ "estimator"
+ ]
+ },
+ {
+ "name": "reference_value",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Offset (m) or index.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "segment",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": "retract",
+ "type": "str",
+ "description": "Segment (retract only).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "estimator_noise_sigma",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Estimator noise scale.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:compute_molecular_extension",
+ "public_name": "compute_molecular_extension",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Molecular extension of the retract segment with an explicit t",
+ "profile": "NATIVE_SPMKIT_DESIGNED_HEURISTIC"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "m"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.SMFS.FORCE_CLAMP.SURVIVAL",
+ "contract": "Kaplan-Meier survival with right censoring over explicit lifetimes and flags: events before censors at ties, events leave the risk set, censored observations never discarded; the median lifetime is typed UNDEFINED_MEDIAN when unreachable; the exponential rate is the censoring-aware MLE n_events/sum(times); matches the independent oracle exactly.",
+ "evidence": [
+ "tests/validation/fixtures/force_smfs/smfs_reference.json",
+ "tests/validation/fixtures/force_smfs/smfs_reference.npz",
+ "tests/validation/test_force_smfs_validation.py",
+ "tests/core/test_force_smfs.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.smfs.force_clamp.survival",
+ "parameters": [
+ {
+ "name": "lifetimes",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "np.ndarray",
+ "description": "Lifetimes (s).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "censored",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "np.ndarray",
+ "description": "Censoring flags (0 event, 1 censored).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "force_level",
+ "kind": "keyword_only",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "float",
+ "description": "Clamp force level (N).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "temperature",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 298.0,
+ "type": "float",
+ "description": "Temperature (K).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "fit_exponential_rate",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": true,
+ "type": "bool",
+ "description": "Fit the MLE rate.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:estimate_force_clamp_survival",
+ "public_name": "estimate_force_clamp_survival",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Kaplan-Meier survival with right censoring over explicit lif",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "s"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.SMFS.KINETICS.BELL_EVANS",
+ "contract": "Bell-Evans fit over (loading rate, rupture force) series: the primary estimator is the frozen most-probable-force regression F* = (k_B T/x_beta) ln(r x_beta/(k0 k_B T)) with the survival convention S(F) = exp(-k0 k_B T/(r x_beta)(exp(F x_beta/k_B T) - 1)); a bounded likelihood runs as a secondary with an identifiability diagnosis (the BE likelihood is degenerate toward x_beta -> 0, documented); narrow-rate ranges carry an IDENTIFIABILITY_LIMITED warning; x_beta recovered within 10% on the phantoms.",
+ "evidence": [
+ "tests/validation/fixtures/force_smfs/smfs_reference.json",
+ "tests/validation/fixtures/force_smfs/smfs_reference.npz",
+ "tests/validation/test_force_smfs_validation.py",
+ "tests/core/test_force_smfs.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.smfs.kinetics.bell_evans",
+ "parameters": [
+ {
+ "name": "loading_rates",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "np.ndarray",
+ "description": "Loading rates (N/s).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "rupture_forces",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "np.ndarray",
+ "description": "Rupture forces (N).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "temperature",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 298.0,
+ "type": "float",
+ "description": "Temperature (K).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "k0_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 1.0,
+ "type": "float",
+ "description": "Zero-force rate start (1/s).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "x_beta_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 1e-09,
+ "type": "float",
+ "description": "Transition distance start (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:fit_bell_evans",
+ "public_name": "fit_bell_evans",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Bell-Evans fit over (loading rate, rupture force) series: th",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "m / 1/s"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.SMFS.KINETICS.DHS",
+ "contract": "Dudko-Hummer-Szabo likelihood fit (k0, x_beta, dG) with the frozen shape convention nu in {1/2, 2/3}, log-space evaluation with a consistent rate cap, the domain 1 - nu F x_beta/dG > 0 enforced; the Bell limit nu -> 0 recovers the BE rate; the fitted energy landscape is not claimed to be physically unique; parameters recovered within the documented wide bounds with the response reconstruction verified.",
+ "evidence": [
+ "tests/validation/fixtures/force_smfs/smfs_reference.json",
+ "tests/validation/fixtures/force_smfs/smfs_reference.npz",
+ "tests/validation/test_force_smfs_validation.py",
+ "tests/core/test_force_smfs.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.smfs.kinetics.dhs",
+ "parameters": [
+ {
+ "name": "loading_rates",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "np.ndarray",
+ "description": "Loading rates (N/s).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "rupture_forces",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "np.ndarray",
+ "description": "Rupture forces (N).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "nu",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 0.6666666666666666,
+ "type": "float",
+ "description": "Potential shape (1/2 cusp, 2/3 linear-cubic).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "temperature",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 298.0,
+ "type": "float",
+ "description": "Temperature (K).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "k0_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 1.0,
+ "type": "float",
+ "description": "Zero-force rate start (1/s).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "x_beta_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 1e-09,
+ "type": "float",
+ "description": "Transition distance start (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "dg_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 1e-19,
+ "type": "float",
+ "description": "Barrier height start (J).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:fit_dudko_hummer_szabo",
+ "public_name": "fit_dudko_hummer_szabo",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Dudko-Hummer-Szabo likelihood fit (k0, x_beta, dG) with the ",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "m / 1/s / J"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.SMFS.LOADING_RATE",
+ "contract": "Local loading rate per event: the least-squares slope of force vs time over the pre-event window plus the robust median-of-pairs slope (N/s); the theoretical rate (effective stiffness x pulling velocity) is reported separately when both are supplied, never substituted; requires an explicit time axis.",
+ "evidence": [
+ "tests/validation/fixtures/force_smfs/smfs_reference.json",
+ "tests/validation/fixtures/force_smfs/smfs_reference.npz",
+ "tests/validation/test_force_smfs_validation.py",
+ "tests/core/test_force_smfs.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.smfs.loading_rate",
+ "parameters": [
+ {
+ "name": "extension",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "MolecularExtensionResult",
+ "description": "Extension result.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "events",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "UnfoldingEventResult",
+ "description": "Quantified events.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "window_samples",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 10,
+ "type": "int",
+ "description": "Pre-event window (samples).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "min_samples",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 3,
+ "type": "int",
+ "description": "Minimum samples.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "pulling_velocity",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Pulling velocity (m/s).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "effective_stiffness",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Stiffness (N/m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:compute_event_loading_rates",
+ "public_name": "compute_event_loading_rates",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Local loading rate per event: the least-squares slope of for",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "N/s"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.SMFS.MODEL.COMPARE",
+ "contract": "AICc comparison of the polymer models over the identical observation set with relative weights only; Delta AICc < 4 ambiguity; failed models retained as warnings; no molecular-truth claim; the recommendation policy is SOFTWARE_VERIFIED.",
+ "evidence": [
+ "tests/validation/fixtures/force_smfs/smfs_reference.json",
+ "tests/validation/fixtures/force_smfs/smfs_reference.npz",
+ "tests/validation/test_force_smfs_validation.py",
+ "tests/core/test_force_smfs.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "SOFTWARE_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.smfs.model.compare",
+ "parameters": [
+ {
+ "name": "extension",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "np.ndarray",
+ "description": "Molecular extension (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "force",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "np.ndarray",
+ "description": "Retract force (N).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "models",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": [
+ "worm_like_chain",
+ "extensible_worm_like_chain",
+ "freely_jointed_chain",
+ "extensible_freely_jointed_chain"
+ ],
+ "type": "tuple[str, ...]",
+ "description": "Candidate models.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "temperature",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 298.0,
+ "type": "float",
+ "description": "Temperature (K).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:compare_polymer_models",
+ "public_name": "compare_polymer_models",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "AICc comparison of the polymer models over the identical obs",
+ "profile": "NATIVE_SPMKIT_DESIGNED_HEURISTIC"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "not_applicable"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.SMFS.MODEL.EXTENSIBLE_FJC",
+ "contract": "Extensible FJC fit (Lc, b, Sk) in the extension space x/Lc = L(y) + F/Sk with Sk the segment stretch force (N); Sk -> inf reduces to the FJC; Lc/b within 2%/5% on clean phantoms; the stretch scale is weakly identifiable from a single section (documented).",
+ "evidence": [
+ "tests/validation/fixtures/force_smfs/smfs_reference.json",
+ "tests/validation/fixtures/force_smfs/smfs_reference.npz",
+ "tests/validation/test_force_smfs_validation.py",
+ "tests/core/test_force_smfs.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.smfs.model.extensible_fjc",
+ "parameters": [
+ {
+ "name": "extension",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "np.ndarray",
+ "description": "Molecular extension (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "force",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "np.ndarray",
+ "description": "Retract force (N).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "temperature",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 298.0,
+ "type": "float",
+ "description": "Temperature (K).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "Lc_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Contour start (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "b_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Kuhn length start (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "Sk_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Stretch force start (N).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:fit_extensible_freely_jointed_chain",
+ "public_name": "fit_extensible_freely_jointed_chain",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Extensible FJC fit (Lc, b, Sk) in the extension space x/Lc =",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "m"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.SMFS.MODEL.EXTENSIBLE_WLC",
+ "contract": "Implicit extensible WLC fit (Lc, Lp, S) with the Odijk-style convention F = (k_BT/Lp)[1/(4(1-x/Lc+F/S)^2) - 1/4 + x/Lc - F/S], solved per point by brentq with a force-scale xtol; S -> inf reduces to the WLC; Lc within 5% and Lp within 20% on clean phantoms; the stretch modulus is weakly identifiable from a single section (documented).",
+ "evidence": [
+ "tests/validation/fixtures/force_smfs/smfs_reference.json",
+ "tests/validation/fixtures/force_smfs/smfs_reference.npz",
+ "tests/validation/test_force_smfs_validation.py",
+ "tests/core/test_force_smfs.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.smfs.model.extensible_wlc",
+ "parameters": [
+ {
+ "name": "extension",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "np.ndarray",
+ "description": "Molecular extension (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "force",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "np.ndarray",
+ "description": "Retract force (N).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "temperature",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 298.0,
+ "type": "float",
+ "description": "Temperature (K).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "Lc_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Contour start (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "Lp_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Persistence start (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "S_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Stretch modulus start (N).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:fit_extensible_worm_like_chain",
+ "public_name": "fit_extensible_worm_like_chain",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Implicit extensible WLC fit (Lc, Lp, S) with the Odijk-style",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "m"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.SMFS.MODEL.FJC",
+ "contract": "FJC fit (Lc, b) in the extension space x/Lc = coth(y) - 1/y with y = F b/k_BT (stable Langevin), separable closed-form Lc per candidate b; Lc/b within 2%/5% on clean phantoms; the persistence length is reported as Lp = b/2.",
+ "evidence": [
+ "tests/validation/fixtures/force_smfs/smfs_reference.json",
+ "tests/validation/fixtures/force_smfs/smfs_reference.npz",
+ "tests/validation/test_force_smfs_validation.py",
+ "tests/core/test_force_smfs.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.smfs.model.fjc",
+ "parameters": [
+ {
+ "name": "extension",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "np.ndarray",
+ "description": "Molecular extension (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "force",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "np.ndarray",
+ "description": "Retract force (N).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "temperature",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 298.0,
+ "type": "float",
+ "description": "Temperature (K).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "Lc_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Contour start (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "b_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Kuhn length start (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:fit_freely_jointed_chain",
+ "public_name": "fit_freely_jointed_chain",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "FJC fit (Lc, b) in the extension space x/Lc = coth(y) - 1/y ",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "m"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.SMFS.MODEL.WLC",
+ "contract": "WLC fit (Lc, Lp) by the Marko-Siggia loading relation F = (k_BT/Lp)[1/(4(1-x/Lc)^2) - 1/4 + x/Lc] with a separable closed-form Lp per candidate Lc (deterministic 1-D search); Lc/Lp within 2%/5% on clean phantoms; the singular domain (x >= Lc) is typed POLYMER_SINGULARITY.",
+ "evidence": [
+ "tests/validation/fixtures/force_smfs/smfs_reference.json",
+ "tests/validation/fixtures/force_smfs/smfs_reference.npz",
+ "tests/validation/test_force_smfs_validation.py",
+ "tests/core/test_force_smfs.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.smfs.model.wlc",
+ "parameters": [
+ {
+ "name": "extension",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "np.ndarray",
+ "description": "Molecular extension (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "force",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "np.ndarray",
+ "description": "Retract force (N).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "temperature",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 298.0,
+ "type": "float",
+ "description": "Temperature (K).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "Lc_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Contour start (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "Lp_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Persistence start (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:fit_worm_like_chain",
+ "public_name": "fit_worm_like_chain",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "WLC fit (Lc, Lp) by the Marko-Siggia loading relation F = (k",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "m"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.SMFS.POPULATION",
+ "contract": "Aggregate event records into a population: rupture-force, contour-increment and loading-rate summaries; deterministic grouping (none or loading_rate_decade) with raw assignments exposed; ambiguity retained for small populations; no molecular-identity claim; the grouping policy is SOFTWARE_VERIFIED.",
+ "evidence": [
+ "tests/validation/fixtures/force_smfs/smfs_reference.json",
+ "tests/validation/fixtures/force_smfs/smfs_reference.npz",
+ "tests/validation/test_force_smfs_validation.py",
+ "tests/core/test_force_smfs.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "SOFTWARE_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.smfs.population",
+ "parameters": [
+ {
+ "name": "event_records",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "list[dict[str, object]]",
+ "description": "Event records.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "group_by",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": "loading_rate_decade",
+ "type": "str",
+ "description": "Grouping policy.",
+ "units": null,
+ "bounds": null,
+ "enum_values": [
+ "none",
+ "loading_rate_decade"
+ ]
+ },
+ {
+ "name": "n_groups",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 4,
+ "type": "int",
+ "description": "Number of groups.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "force_levels",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "np.ndarray | None",
+ "description": "Force levels (N).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:analyze_smfs_event_population",
+ "public_name": "analyze_smfs_event_population",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Aggregate event records into a population: rupture-force, co",
+ "profile": "NATIVE_SPMKIT_DESIGNED_HEURISTIC"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "not_applicable"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.SMFS.WINDOW.SELECT",
+ "contract": "Explicit polymer fit window on the molecular extension axis: negative extensions always excluded (the polymer domain starts at the tether zero), extension/force bounds, minimum points; EMPTY_WINDOW and INSUFFICIENT_POINTS typed; the window policy is SOFTWARE_VERIFIED.",
+ "evidence": [
+ "tests/validation/fixtures/force_smfs/smfs_reference.json",
+ "tests/validation/fixtures/force_smfs/smfs_reference.npz",
+ "tests/validation/test_force_smfs_validation.py",
+ "tests/core/test_force_smfs.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "SOFTWARE_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.smfs.window.select",
+ "parameters": [
+ {
+ "name": "extension",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "np.ndarray",
+ "description": "Molecular extension (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "force",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "np.ndarray",
+ "description": "Retract force (N).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "min_extension",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Lower extension bound (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "max_extension",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Upper extension bound (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "min_force",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Lower force bound (N).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "max_force",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Upper force bound (N).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "min_points",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 10,
+ "type": "int",
+ "description": "Minimum window size.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "window_label",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "str | None",
+ "description": "Window identifier.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:select_smfs_fit_windows",
+ "public_name": "select_smfs_fit_windows",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Explicit polymer fit window on the molecular extension axis:",
+ "profile": "NATIVE_SPMKIT_DESIGNED_HEURISTIC"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "m / N"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.VISCO.CONTACT.LEE_RADOK",
+ "contract": "Fits the SLS relaxation modulus through the Lee-Radok spherical hereditary integral on the monotonic loading region: F(t) = c int_0^t E(t - t') d/dt' delta(t')^1.5 dt'; the contact radius must not decrease (LEE_RADOK_NONMONOTONIC typed); loading-only validity; the loading history is trimmed to the contact (indentation >= 0, documented); recovery within ~40% E0/E_inf and ~50% tau on clean phantoms (the loading curve carries less information than a hold).",
+ "evidence": [
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json",
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz",
+ "tests/validation/test_force_viscoelasticity_validation.py",
+ "tests/core/test_force_viscoelasticity.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.visco.contact.lee_radok",
+ "parameters": [
+ {
+ "name": "prepared",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "ForcePreparationResult",
+ "description": "FS-F1 prepared curve.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "protocol",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "ViscoelasticProtocolResult",
+ "description": "Protocol result.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "tip_radius",
+ "kind": "keyword_only",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "float",
+ "description": "Tip radius (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "poisson",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 0.3,
+ "type": "float",
+ "description": "Poisson ratio.",
+ "units": null,
+ "bounds": [
+ 0.0,
+ 0.5
+ ],
+ "enum_values": null
+ },
+ {
+ "name": "E0_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 1000000.0,
+ "type": "float",
+ "description": "Modulus start (Pa).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "E_inf_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 500000.0,
+ "type": "float",
+ "description": "Equilibrium modulus start (Pa).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "tau_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 1.0,
+ "type": "float",
+ "description": "Relaxation time start (s).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:fit_lee_radok_sphere",
+ "public_name": "fit_lee_radok_sphere",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Fits the SLS relaxation modulus through the Lee-Radok spheri",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "Pa"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.VISCO.CONTACT.TING",
+ "contract": "Fits the SLS relaxation modulus through the Ting spherical integral with contact-time memory: loading = Lee-Radok; unloading F(t) = c int_0^{t1(t)} E(t - t') d/dt' delta(t')^1.5 dt' with delta(t1(t)) = delta(t) on the monotone loading portion; the loading history is trimmed to the contact and the unloading history truncated at the contact (documented); TING_HISTORY_UNAVAILABLE typed when the history cannot be reconstructed; the production quadrature is the first-order increment rule (parity with the substep oracle 0.5%).",
+ "evidence": [
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json",
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz",
+ "tests/validation/test_force_viscoelasticity_validation.py",
+ "tests/core/test_force_viscoelasticity.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.visco.contact.ting",
+ "parameters": [
+ {
+ "name": "prepared",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "ForcePreparationResult",
+ "description": "FS-F1 prepared curve.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "protocol",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "ViscoelasticProtocolResult",
+ "description": "Protocol result.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "tip_radius",
+ "kind": "keyword_only",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "float",
+ "description": "Tip radius (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "poisson",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 0.3,
+ "type": "float",
+ "description": "Poisson ratio.",
+ "units": null,
+ "bounds": [
+ 0.0,
+ 0.5
+ ],
+ "enum_values": null
+ },
+ {
+ "name": "E0_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 1000000.0,
+ "type": "float",
+ "description": "Modulus start (Pa).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "E_inf_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 500000.0,
+ "type": "float",
+ "description": "Equilibrium modulus start (Pa).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "tau_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 1.0,
+ "type": "float",
+ "description": "Relaxation time start (s).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:fit_ting_sphere",
+ "public_name": "fit_ting_sphere",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Fits the SLS relaxation modulus through the Ting spherical i",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "Pa"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.VISCO.CREEP.EXTRACT",
+ "contract": "Extracts the creep compliance increment of a force hold: (indentation(t) - indentation(0))/F_hold on the relative hold time; the increment is robust to the contact-coordinate precision (the absolute level is carried in indentation_at_hold_start); missing hold raises EMPTY_REGION; zero held force raises INVALID_RESPONSE.",
+ "evidence": [
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json",
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz",
+ "tests/validation/test_force_viscoelasticity_validation.py",
+ "tests/core/test_force_viscoelasticity.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.visco.creep.extract",
+ "parameters": [
+ {
+ "name": "prepared",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "ForcePreparationResult",
+ "description": "FS-F1 prepared curve.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "protocol",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "ViscoelasticProtocolResult",
+ "description": "Protocol result.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "segment",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": "extend",
+ "type": "str",
+ "description": "Segment name.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "hold_kind",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": "hold_force",
+ "type": "str",
+ "description": "Hold region kind.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "hold_force_median",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": true,
+ "type": "bool",
+ "description": "Median (vs mean) held force.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:extract_creep_compliance",
+ "public_name": "extract_creep_compliance",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Extracts the creep compliance increment of a force hold: (in",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "s / N / m"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.VISCO.MODEL.COMPARE",
+ "contract": "Model-relative AICc comparison over identical observations with the finite-sample correction; Delta AICc < 4 ambiguity; failed candidates retained as warnings; weights are relative support, never a probability of physical correctness; the recommendation policy is SOFTWARE_VERIFIED.",
+ "evidence": [
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json",
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz",
+ "tests/validation/test_force_viscoelasticity_validation.py",
+ "tests/core/test_force_viscoelasticity.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "SOFTWARE_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.visco.model.compare",
+ "parameters": [
+ {
+ "name": "response",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "RelaxationResponseResult | CreepResponseResult",
+ "description": "Relaxation or creep response.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "models",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "tuple[str, ...] | None",
+ "description": "Candidate models.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "tip_radius",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Tip radius (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "poisson",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 0.3,
+ "type": "float",
+ "description": "Poisson ratio.",
+ "units": null,
+ "bounds": [
+ 0.0,
+ 0.5
+ ],
+ "enum_values": null
+ },
+ {
+ "name": "n_terms",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 2,
+ "type": "int",
+ "description": "Prony terms for the generalized Maxwell.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "t_ref",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Reference time for the power law.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:compare_viscoelastic_models",
+ "public_name": "compare_viscoelastic_models",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Model-relative AICc comparison over identical observations w",
+ "profile": "NATIVE_SPMKIT_DESIGNED_HEURISTIC"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "not_applicable"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.VISCO.MODEL.GENERALIZED_MAXWELL",
+ "contract": "Prony normalized relaxation fit n(t) = 1 - sum(alpha) + sum(alpha_i exp(-t/tau_i)) with alpha_i >= 0, sum(alpha) <= 1, tau_i > 0, deterministic ordering by ascending tau; duplicate relaxation times are rejected typed (PRONY_DUPLICATE_TAU); no claim that the recovered spectrum is unique; nearly equal time constants carry a bounded-identifiability warning.",
+ "evidence": [
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json",
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz",
+ "tests/validation/test_force_viscoelasticity_validation.py",
+ "tests/core/test_force_viscoelasticity.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.visco.model.generalized_maxwell",
+ "parameters": [
+ {
+ "name": "response",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "RelaxationResponseResult",
+ "description": "Relaxation response.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "n_terms",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 2,
+ "type": "int",
+ "description": "Number of Prony terms.",
+ "units": null,
+ "bounds": [
+ 1,
+ 8
+ ],
+ "enum_values": null
+ },
+ {
+ "name": "tip_radius",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Tip radius (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "poisson",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 0.3,
+ "type": "float",
+ "description": "Poisson ratio.",
+ "units": null,
+ "bounds": [
+ 0.0,
+ 0.5
+ ],
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:fit_generalized_maxwell",
+ "public_name": "fit_generalized_maxwell",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Prony normalized relaxation fit n(t) = 1 - sum(alpha) + sum(",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "s"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.VISCO.MODEL.KELVIN_VOIGT",
+ "contract": "Kelvin-Voigt creep fit J(t) = (1/E)(1 - exp(-t/tau)), tau = eta/E (retardation time); requires a CreepResponseResult (PROTOCOL_MODEL_MISMATCH typed otherwise); deterministic multi-start least squares; E within 10% and tau within 10% on clean phantoms; the model cannot represent instantaneous stress relaxation (documented).",
+ "evidence": [
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json",
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz",
+ "tests/validation/test_force_viscoelasticity_validation.py",
+ "tests/core/test_force_viscoelasticity.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.visco.model.kelvin_voigt",
+ "parameters": [
+ {
+ "name": "response",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "CreepResponseResult",
+ "description": "Creep response.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "E_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Modulus start (Pa).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "tau_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Retardation time start (s).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:fit_kelvin_voigt",
+ "public_name": "fit_kelvin_voigt",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Kelvin-Voigt creep fit J(t) = (1/E)(1 - exp(-t/tau)), tau = ",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "Pa"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.VISCO.MODEL.MAXWELL",
+ "contract": "Maxwell relaxation fit n(t) = exp(-t/tau), tau = eta/E; the modulus E is recovered only when the tip radius is provided (spherical contact proportionality, documented); tau recovered within 2% on clean phantoms; the model cannot represent bounded solid creep (documented).",
+ "evidence": [
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json",
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz",
+ "tests/validation/test_force_viscoelasticity_validation.py",
+ "tests/core/test_force_viscoelasticity.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.visco.model.maxwell",
+ "parameters": [
+ {
+ "name": "response",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "RelaxationResponseResult",
+ "description": "Relaxation response.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "tip_radius",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Tip radius (m); enables E.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "poisson",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 0.3,
+ "type": "float",
+ "description": "Poisson ratio.",
+ "units": null,
+ "bounds": [
+ 0.0,
+ 0.5
+ ],
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:fit_maxwell",
+ "public_name": "fit_maxwell",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Maxwell relaxation fit n(t) = exp(-t/tau), tau = eta/E; the ",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "s / Pa"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.VISCO.MODEL.POWER_LAW",
+ "contract": "Power-law relaxation fit n(t) = (t/t_ref)^(-alpha) with 0 < alpha < 1 and an optional equilibrium offset; t = 0 excluded (singularity); t_ref defaults to the first positive hold time and the fit uses t >= t_ref when t_ref is given.",
+ "evidence": [
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json",
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz",
+ "tests/validation/test_force_viscoelasticity_validation.py",
+ "tests/core/test_force_viscoelasticity.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.visco.model.power_law",
+ "parameters": [
+ {
+ "name": "response",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "RelaxationResponseResult",
+ "description": "Relaxation response.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "t_ref",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Reference time (s).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "with_equilibrium",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": false,
+ "type": "bool",
+ "description": "Add the equilibrium offset.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "tip_radius",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Tip radius (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "poisson",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 0.3,
+ "type": "float",
+ "description": "Poisson ratio.",
+ "units": null,
+ "bounds": [
+ 0.0,
+ 0.5
+ ],
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:fit_power_law_relaxation",
+ "public_name": "fit_power_law_relaxation",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Power-law relaxation fit n(t) = (t/t_ref)^(-alpha) with 0 < ",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "s"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.VISCO.MODEL.SLS",
+ "contract": "Standard linear solid fit on a relaxation response n(t) = 1 - a(1 - exp(-t/tau_relax)) or a creep response increment (dJ)(1 - exp(-t/tau_retard)); both representations are reported with the conversions J0 = 1/E0, J_inf = 1/E_inf, tau_retard = tau_relax * E0/E_inf; absolute moduli need the tip radius for the relaxation form; the creep absolute level is contact-coordinate limited (recovery reported on the increment).",
+ "evidence": [
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json",
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz",
+ "tests/validation/test_force_viscoelasticity_validation.py",
+ "tests/core/test_force_viscoelasticity.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.visco.model.sls",
+ "parameters": [
+ {
+ "name": "response",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "RelaxationResponseResult | CreepResponseResult",
+ "description": "Relaxation or creep response.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "tip_radius",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Tip radius (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "poisson",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 0.3,
+ "type": "float",
+ "description": "Poisson ratio.",
+ "units": null,
+ "bounds": [
+ 0.0,
+ 0.5
+ ],
+ "enum_values": null
+ },
+ {
+ "name": "tau_initial",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Time-constant start (s).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:fit_standard_linear_solid",
+ "public_name": "fit_standard_linear_solid",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Standard linear solid fit on a relaxation response n(t) = 1 ",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "Pa / m/N / s"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.VISCO.PROTOCOL.IDENTIFY",
+ "contract": "Identifies the viscoelastic protocol of a force curve: rate-region classification (median-of-nonzero-rate thresholds) into LOADING_RAMP, UNLOADING_RAMP, DISPLACEMENT_HOLD, FORCE_HOLD, CREEP, STRESS_RELAXATION, TRIANGULAR_LOADING, INSUFFICIENT_PROTOCOL, AMBIGUOUS_PROTOCOL; trusted instrument labels in curve.metadata take precedence; a displacement hold with a decaying force is STRESS_RELAXATION, a force hold with a drifting displacement is CREEP; missing time raises MISSING_TIME (reconstructed clock only via assume_uniform_rate); duplicate time samples raise DUPLICATE_TIMESTAMPS; the JPK/NID readers do not populate segment time, so time-domain analysis requires an explicit time axis or an explicitly requested known-rate reconstruction (no automatic general reader time-domain analysis is claimed).",
+ "evidence": [
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json",
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz",
+ "tests/validation/test_force_viscoelasticity_validation.py",
+ "tests/core/test_force_viscoelasticity.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [
+ "the protocol recommendation and ambiguity policy is SOFTWARE_VERIFIED"
+ ],
+ "mask_semantics": "none",
+ "maturity": "SOFTWARE_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.visco.protocol.identify",
+ "parameters": [
+ {
+ "name": "curve",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "ForceCurve",
+ "description": "Force curve.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "contact_index",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "int | None",
+ "description": "Contact index (height axis).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "contact_coordinate",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Contact coordinate (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "rate_threshold",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 0.05,
+ "type": "float",
+ "description": "Relative rate threshold.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "min_hold_points",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 5,
+ "type": "int",
+ "description": "Minimum hold run length.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "min_hold_fraction",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 0.05,
+ "type": "float",
+ "description": "Minimum hold fraction.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "assume_uniform_rate",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Reconstructed clock (s/sample).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "force_threshold_fraction",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 0.1,
+ "type": "float",
+ "description": "Relaxation decay threshold.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:identify_viscoelastic_protocol",
+ "public_name": "identify_viscoelastic_protocol",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Identifies the viscoelastic protocol of a force curve: rate-",
+ "profile": "NATIVE_SPMKIT_DESIGNED_HEURISTIC"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "not_applicable"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.VISCO.RATE.INDENTATION",
+ "contract": "Robust indentation and force rate of one protocol region: median of the local finite-difference rates with the 25-75 percentile spread; region located via the protocol result; missing region raises EMPTY_REGION; requires a valid time axis (the JPK/NID readers do not populate segment time; provide one or use an explicitly requested known-rate reconstruction); units m/s and N/s.",
+ "evidence": [
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json",
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz",
+ "tests/validation/test_force_viscoelasticity_validation.py",
+ "tests/core/test_force_viscoelasticity.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.visco.rate.indentation",
+ "parameters": [
+ {
+ "name": "prepared",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "ForcePreparationResult",
+ "description": "FS-F1 prepared curve.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "protocol",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "ViscoelasticProtocolResult",
+ "description": "Protocol result.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "region",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": "loading",
+ "type": "str",
+ "description": "Region kind.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "segment",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": "extend",
+ "type": "str | None",
+ "description": "Segment name.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:compute_indentation_rate",
+ "public_name": "compute_indentation_rate",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Robust indentation and force rate of one protocol region: me",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "m/s"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.VISCO.RELAXATION.EXTRACT",
+ "contract": "Extracts the normalized stress-relaxation response of a displacement hold: F(t)/F(t0) on the relative hold time with the hold indentation and force histories; equilibrium-force estimate = mean of the last tail fraction (documented estimate, not a guaranteed equilibrium); missing hold raises EMPTY_REGION; zero hold-start force raises INVALID_RESPONSE.",
+ "evidence": [
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json",
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz",
+ "tests/validation/test_force_viscoelasticity_validation.py",
+ "tests/core/test_force_viscoelasticity.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.visco.relaxation.extract",
+ "parameters": [
+ {
+ "name": "prepared",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "ForcePreparationResult",
+ "description": "FS-F1 prepared curve.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "protocol",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "ViscoelasticProtocolResult",
+ "description": "Protocol result.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "segment",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": "extend",
+ "type": "str",
+ "description": "Segment name.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "hold_kind",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": "hold_displacement",
+ "type": "str",
+ "description": "Hold region kind.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "equilibrium_tail_fraction",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 0.1,
+ "type": "float",
+ "description": "Equilibrium tail fraction.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:extract_stress_relaxation",
+ "public_name": "extract_stress_relaxation",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Extracts the normalized stress-relaxation response of a disp",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "s / m / N"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.VISCO.SENSITIVITY",
+ "contract": "Deterministic multiverse over contact offsets, hold-boundary offsets and equilibrium-tail fractions (bounded at max_configurations) for the SLS fit on the extracted response; one-at-a-time contact/boundary/window indices relative to the baseline configuration and a dominant-source classification (contact / boundary / window / none at the 20% threshold); raw configurations and failures exposed; the interpretation is SOFTWARE_VERIFIED.",
+ "evidence": [
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json",
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz",
+ "tests/validation/test_force_viscoelasticity_validation.py",
+ "tests/core/test_force_viscoelasticity.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "SOFTWARE_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.visco.sensitivity",
+ "parameters": [
+ {
+ "name": "curve",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "ForceCurve",
+ "description": "Force curve.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "prepared",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "ForcePreparationResult",
+ "description": "FS-F1 prepared curve.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "protocol",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "ViscoelasticProtocolResult | None",
+ "description": "Protocol result.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "contact_offsets",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": [
+ -2,
+ 0,
+ 2
+ ],
+ "type": "tuple[int, ...]",
+ "description": "Contact offsets (samples).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "boundary_offsets",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": [
+ -3,
+ 0,
+ 3
+ ],
+ "type": "tuple[int, ...]",
+ "description": "Hold-boundary offsets.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "equilibrium_tail_fractions",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": [
+ 0.05,
+ 0.1,
+ 0.2
+ ],
+ "type": "tuple[float, ...]",
+ "description": "Equilibrium tail fractions.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "max_configurations",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 96,
+ "type": "int",
+ "description": "Multiverse bound.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "tip_radius",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Tip radius (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "poisson",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 0.3,
+ "type": "float",
+ "description": "Poisson ratio.",
+ "units": null,
+ "bounds": [
+ 0.0,
+ 0.5
+ ],
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:analyze_viscoelastic_sensitivity",
+ "public_name": "analyze_viscoelastic_sensitivity",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Deterministic multiverse over contact offsets, hold-boundary",
+ "profile": "NATIVE_SPMKIT_DESIGNED_HEURISTIC"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "not_applicable"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.VISCO.VOLUME",
+ "contract": "Per-curve identify -> prepare -> extract -> SLS mapping over a ForceVolume: modulus_0/modulus_inf/viscosity/relaxation-time maps, model/ambiguity/sensitivity/protocol maps and an explicit failed mask with per-index reasons (nothing silently dropped); deterministic replay; viscosity = E0 * a * tau_relax (SLS dashpot estimate, documented model quantity).",
+ "evidence": [
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.json",
+ "tests/validation/fixtures/force_viscoelasticity/viscoelasticity_reference.npz",
+ "tests/validation/test_force_viscoelasticity_validation.py",
+ "tests/core/test_force_viscoelasticity.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "SOFTWARE_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.visco.volume",
+ "parameters": [
+ {
+ "name": "volume",
+ "kind": "positional",
+ "required": true,
+ "has_default": false,
+ "default": null,
+ "type": "ForceVolume",
+ "description": "Force volume.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "tip_radius",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": null,
+ "type": "float | None",
+ "description": "Tip radius (m).",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ },
+ {
+ "name": "poisson",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 0.3,
+ "type": "float",
+ "description": "Poisson ratio.",
+ "units": null,
+ "bounds": [
+ 0.0,
+ 0.5
+ ],
+ "enum_values": null
+ },
+ {
+ "name": "min_hold_points",
+ "kind": "keyword_only",
+ "required": false,
+ "has_default": true,
+ "default": 5,
+ "type": "int",
+ "description": "Minimum hold run length.",
+ "units": null,
+ "bounds": null,
+ "enum_values": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:fit_force_volume_viscoelasticity",
+ "public_name": "fit_force_volume_viscoelasticity",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Per-curve identify -> prepare -> extract -> SLS mapping over",
+ "profile": "NATIVE_SPMKIT_DESIGNED_HEURISTIC"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "Pa / Pa*s / s"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.VOLUME.MECHANICS",
+ "contract": "Applies prepare -> indentation -> window -> model comparison to every curve of a ForceVolume; modulus/adhesion maps, chosen model map, quality map; failed curves explicitly masked (failed_mask + provenance reasons), never silently dropped; deterministic replay; units Pa / N.",
+ "evidence": [
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.json",
+ "tests/validation/fixtures/force_mechanics/force_mechanics_reference.npz",
+ "tests/validation/test_force_mechanics_validation.py",
+ "tests/core/test_force_mechanics.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.volume.mechanics",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Force volume.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "volume",
+ "required": true,
+ "type": "ForceVolume",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 1e-08,
+ "description": "Tip radius (m).",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "tip_radius",
+ "required": false,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": [
+ 0.0,
+ 0.5
+ ],
+ "default": 0.3,
+ "description": "Poisson ratio.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "poisson",
+ "required": false,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 0.3490658503988659,
+ "description": "Cone half-angle (rad).",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "half_angle",
+ "required": false,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": [
+ "hertz_sphere",
+ "dmt"
+ ],
+ "description": "Candidate models.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "models",
+ "required": false,
+ "type": "tuple[str, ...]",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 20,
+ "description": "Minimum window size per curve.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "min_points",
+ "required": false,
+ "type": "int",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:fit_force_volume_mechanics",
+ "public_name": "fit_force_volume_mechanics",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Per-curve mechanics mapping",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "Pa"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.WORK.INTEGRATE",
+ "contract": "Force integrated over tip-sample separation on the common overlap domain (contact to min of maxima); monotone interpolation; trapezoidal arithmetic; work of adhesion = retract integral; hysteresis = approach - retract; units J; INSUFFICIENT_OVERLAP and NONMONOTONIC_COORDINATE typed failures.",
+ "evidence": [
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.json",
+ "tests/validation/fixtures/force_foundation/force_phantoms_reference.npz",
+ "tests/validation/fixtures/force_foundation/force_foundation_reference.json",
+ "tests/validation/test_force_foundation_validation.py",
+ "tests/core/test_force_foundation.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [
+ "real tip-sample separation is often non-monotone; the operation raises NONMONOTONIC_COORDINATE instead of fabricating a value"
+ ],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.work.integrate",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Force curve.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "curve",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Contact point result.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "contact",
+ "required": true,
+ "type": "ContactPointResult | ContactPointCandidate",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": "tip_position",
+ "description": "Integration domain.",
+ "enum_values": [
+ "tip_position",
+ "height"
+ ],
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "domain",
+ "required": false,
+ "type": "str",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:integrate_force_work",
+ "public_name": "integrate_force_work",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Force work integration",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "J"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "FORCE.WORK.PATH_INTEGRATE",
+ "contract": "Signed path work over a single trajectory in acquisition order: W = sum_i 0.5*(F_i+F_{i+1})*(z_{i+1}-z_i) with deterministic float64 accumulation; signed dz retained (local reversals and closed loops contribute their signed path work; repeated coordinates contribute zero; translation-invariant; acquisition reversal flips sign); no sorting, no abs(), no smoothing, no point deletion; complete CoordinatePathDiagnostics; classification_tolerance only classifies (direction, reversal counts), never alters the integral; NONFINITE_DATA, LENGTH_MISMATCH, INSUFFICIENT_SAMPLES, MISSING_COORDINATE typed failures.",
+ "evidence": [
+ "tests/core/test_force_path_work.py"
+ ],
+ "family": "FORCE",
+ "known_deviations": [
+ "path work is a path integral, not thermodynamic work; closed/ambiguous paths warn and split forward/backward contributions by step sign",
+ "absolute_accumulated_work is not dissipation energy"
+ ],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "force.work.path_integrate",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Coordinate axis (e.g. tip-sample separation) in acquisition order.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "coordinate",
+ "required": true,
+ "type": "np.ndarray",
+ "units": "m"
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Calibrated force samples (same length as coordinate).",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "force",
+ "required": true,
+ "type": "np.ndarray",
+ "units": "N"
+ },
+ {
+ "bounds": null,
+ "default": "m",
+ "description": "Coordinate unit label.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "coordinate_unit",
+ "required": false,
+ "type": "str",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": "N",
+ "description": "Force unit label.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "force_unit",
+ "required": false,
+ "type": "str",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": "0.0",
+ "description": "Classification-only tolerance in SI coordinate units (never used to alter the integral).",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "classification_tolerance",
+ "required": false,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Provenance metadata.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "provenance",
+ "required": false,
+ "type": "dict | None",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:integrate_force_path_work",
+ "public_name": "integrate_force_path_work",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Acquisition-path force work",
+ "profile": "NUMERICALLY_VERIFIED_NATIVE_PHANTOM_ORACLE"
+ },
+ "result_type": "object",
+ "roi_support": false,
+ "status": "stable",
+ "units": "J"
+ },
+ {
+ "aliases": [],
+ "border_policy": "mirror",
+ "capability_id": "IMG.FILTER.GAUSSIAN",
+ "contract": "Separable Gaussian smoothing with sigma in pixels; kernel resolution 2*ceil(5*sigma)+1 capped at 3*min(xres,yres) and forced odd; mirror borders; horizontal-then-vertical passes; sequential-sum reciprocal normalization (not forced to exactly 1.0).",
+ "evidence": [
+ "tests/validation/fixtures/gwyddion/neighborhood_filters/neighborhood_filters_reference.json",
+ "tests/validation/fixtures/gwyddion/neighborhood_filters/neighborhood_filters_reference.npz",
+ "tests/validation/test_gwyddion_neighborhood_filters_production_parity.py",
+ "tests/core/test_gwyddion_neighborhood_filters.py"
+ ],
+ "family": "IMG.FILTER",
+ "known_deviations": [
+ "Gaussian constant-field preservation is not bitwise guaranteed; kernel-normalization rounding (~1e-15) is preserved."
+ ],
+ "mask_semantics": "none",
+ "maturity": "CROSS_VALIDATED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "img.filter.gaussian",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Finite two-dimensional input channel.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "channel",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ },
+ {
+ "bounds": [
+ 0.01,
+ 40.0
+ ],
+ "default": 5.0,
+ "description": "Gaussian standard deviation in pixels.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "sigma",
+ "required": false,
+ "type": "float",
+ "units": "pixel"
+ }
+ ],
+ "public_import": "spmkit.core.analysis:gwyddion_gaussian_filter",
+ "public_name": "gwyddion_gaussian_filter",
+ "reference": {
+ "name": "Gaussian Filter",
+ "profile": "COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION",
+ "software": "Gwydion",
+ "version": "2.71"
+ },
+ "result_type": "SPMChannel",
+ "roi_support": false,
+ "status": "stable",
+ "units": "preserved"
+ },
+ {
+ "aliases": [],
+ "border_policy": "clipped",
+ "capability_id": "IMG.FILTER.GRADIENT_DIRECTION",
+ "contract": "Native gradient direction atan2(gy, gx) over explicit required component fields; radians; range (-pi, pi]; C99 signed-zero axes; zero vector -> +0.0; output unit rad; native analytical composite, not a Gwydion parity target; components never mutated.",
+ "evidence": [
+ "tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.json",
+ "tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.npz",
+ "tests/validation/fixtures/gwyddion/derivative_filters/oracle_gradient_direction_native.py",
+ "tests/validation/test_gwyddion_derivative_filters_production_parity.py",
+ "tests/core/test_gwyddion_derivative_filters.py"
+ ],
+ "family": "IMG.FILTER",
+ "known_deviations": [
+ "numpy.arctan2 may differ from the compiled C atan2 profile by up to ~1 ULP on some inputs; characterized by parity tests, not bitwise parity."
+ ],
+ "mask_semantics": "none",
+ "maturity": "NUMERICALLY_VERIFIED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "img.filter.gradient_direction",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Horizontal derivative component field (finite 2D SPMChannel).",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "gx",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Vertical derivative component field (finite 2D SPMChannel).",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "gy",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:gradient_direction",
+ "public_name": "gradient_direction",
+ "reference": {
+ "software": "SPMKit",
+ "version": "native",
+ "name": "Gradient Direction (native analytical composite)",
+ "profile": "NATIVE_SPMKIT_ANALYTICAL_COMPOSITE"
+ },
+ "result_type": "SPMChannel",
+ "roi_support": false,
+ "status": "stable",
+ "units": "rad"
+ },
+ {
+ "aliases": [],
+ "border_policy": "clipped",
+ "capability_id": "IMG.FILTER.GRADIENT_MAGNITUDE",
+ "contract": "Gradient magnitude hypot(gx, gy) over explicit required component fields; reproduces the frozen hypot-of-fields orchestration; overflow/underflow-safe; +0.0 for all signed-zero component combinations; component unit retained; components never mutated.",
+ "evidence": [
+ "tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.json",
+ "tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.npz",
+ "tests/validation/test_gwyddion_derivative_filters_production_parity.py",
+ "tests/core/test_gwyddion_derivative_filters.py"
+ ],
+ "family": "IMG.FILTER",
+ "known_deviations": [
+ "Bitwise parity is bounded to the frozen platform profile x86-64 / glibc / hypot@GLIBC_2.35; no cross-libc or cross-architecture bitwise guarantee; non-negativity and component-swap symmetry hold relationally on every platform."
+ ],
+ "mask_semantics": "none",
+ "maturity": "CROSS_VALIDATED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "img.filter.gradient_magnitude",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Horizontal derivative component field (finite 2D SPMChannel).",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "gx",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Vertical derivative component field (finite 2D SPMChannel).",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "gy",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:gwyddion_gradient_magnitude",
+ "public_name": "gwyddion_gradient_magnitude",
+ "reference": {
+ "software": "Gwydion",
+ "version": "2.71",
+ "name": "Gradient Magnitude (hypot of component fields)",
+ "profile": "COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_DERIVATIVE_KERNEL_PROFILE"
+ },
+ "result_type": "SPMChannel",
+ "roi_support": false,
+ "status": "stable",
+ "units": "preserved"
+ },
+ {
+ "aliases": [],
+ "border_policy": "extend",
+ "capability_id": "IMG.FILTER.MEDIAN",
+ "contract": "Disc median filter with footprint side `size` (2..31, even sizes valid); ellipse-inscribed footprint; upper median rank n//2; EXTEND nearest-constant borders.",
+ "evidence": [
+ "tests/validation/fixtures/gwyddion/neighborhood_filters/neighborhood_filters_reference.json",
+ "tests/validation/fixtures/gwyddion/neighborhood_filters/neighborhood_filters_reference.npz",
+ "tests/validation/test_gwyddion_neighborhood_filters_production_parity.py",
+ "tests/core/test_gwyddion_neighborhood_filters.py"
+ ],
+ "family": "IMG.FILTER",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "CROSS_VALIDATED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "img.filter.median",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Finite two-dimensional input channel.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "channel",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ },
+ {
+ "bounds": [
+ 2,
+ 31
+ ],
+ "default": 5,
+ "description": "Footprint side length (not a radius).",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "size",
+ "required": false,
+ "type": "int",
+ "units": "pixel"
+ }
+ ],
+ "public_import": "spmkit.core.analysis:gwyddion_median_filter",
+ "public_name": "gwyddion_median_filter",
+ "reference": {
+ "name": "disc Median Filter",
+ "profile": "COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION",
+ "software": "Gwydion",
+ "version": "2.71"
+ },
+ "result_type": "SPMChannel",
+ "roi_support": false,
+ "status": "stable",
+ "units": "preserved"
+ },
+ {
+ "aliases": [],
+ "border_policy": "clipped",
+ "capability_id": "IMG.FILTER.PREWITT_X",
+ "contract": "Prewitt X (horizontal) pixel-space derivative with the frozen 1/3 coefficients {1/3, 0, -1/3; 1/3, 0, -1/3; 1/3, 0, -1/3}; CLIPPED borders; frozen source sign and orientation; z-unit preserved; finite 2D inputs only; no masks or ROI.",
+ "evidence": [
+ "tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.json",
+ "tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.npz",
+ "tests/validation/fixtures/gwyddion/derivative_filters/oracle_derivative_filters_source.py",
+ "tests/validation/test_gwyddion_derivative_filters_production_parity.py",
+ "tests/core/test_gwyddion_derivative_filters.py"
+ ],
+ "family": "IMG.FILTER",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "CROSS_VALIDATED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "img.filter.prewitt_x",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Finite two-dimensional input channel.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "channel",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:gwyddion_prewitt_x",
+ "public_name": "gwyddion_prewitt_x",
+ "reference": {
+ "software": "Gwydion",
+ "version": "2.71",
+ "name": "Prewitt X Filter",
+ "profile": "COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_DERIVATIVE_KERNEL_PROFILE"
+ },
+ "result_type": "SPMChannel",
+ "roi_support": false,
+ "status": "stable",
+ "units": "preserved"
+ },
+ {
+ "aliases": [],
+ "border_policy": "clipped",
+ "capability_id": "IMG.FILTER.PREWITT_Y",
+ "contract": "Prewitt Y (vertical) pixel-space derivative with the frozen 1/3 coefficients {1/3, 1/3, 1/3; 0, 0, 0; -1/3, -1/3, -1/3}; CLIPPED borders; frozen source sign and orientation; z-unit preserved; finite 2D inputs only; no masks or ROI.",
+ "evidence": [
+ "tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.json",
+ "tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.npz",
+ "tests/validation/fixtures/gwyddion/derivative_filters/oracle_derivative_filters_source.py",
+ "tests/validation/test_gwyddion_derivative_filters_production_parity.py",
+ "tests/core/test_gwyddion_derivative_filters.py"
+ ],
+ "family": "IMG.FILTER",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "CROSS_VALIDATED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "img.filter.prewitt_y",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Finite two-dimensional input channel.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "channel",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:gwyddion_prewitt_y",
+ "public_name": "gwyddion_prewitt_y",
+ "reference": {
+ "software": "Gwydion",
+ "version": "2.71",
+ "name": "Prewitt Y Filter",
+ "profile": "COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_DERIVATIVE_KERNEL_PROFILE"
+ },
+ "result_type": "SPMChannel",
+ "roi_support": false,
+ "status": "stable",
+ "units": "preserved"
+ },
+ {
+ "aliases": [],
+ "border_policy": "extend",
+ "capability_id": "IMG.FILTER.RANK",
+ "contract": "Rank filter with pixel radius (1..1024); ellipse-inscribed footprint in a 2*radius+1 square; rank GWY_ROUND(percentile*(n-1)); k=0/k=n-1 minimum/maximum endpoint dispatch; EXTEND borders. Public v1 exposes the primary percentile result only.",
+ "evidence": [
+ "tests/validation/fixtures/gwyddion/neighborhood_filters/neighborhood_filters_reference.json",
+ "tests/validation/fixtures/gwyddion/neighborhood_filters/neighborhood_filters_reference.npz",
+ "tests/validation/test_gwyddion_neighborhood_filters_production_parity.py",
+ "tests/core/test_gwyddion_neighborhood_filters.py"
+ ],
+ "family": "IMG.FILTER",
+ "known_deviations": [
+ "Private secondary/both/difference Rank output modes are retained in diagnostics but not exposed publicly in v1."
+ ],
+ "mask_semantics": "none",
+ "maturity": "CROSS_VALIDATED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "img.filter.rank",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Finite two-dimensional input channel.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "channel",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ },
+ {
+ "bounds": [
+ 1,
+ 1024
+ ],
+ "default": 20,
+ "description": "Pixel radius of the footprint.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "radius",
+ "required": false,
+ "type": "int",
+ "units": "pixel"
+ },
+ {
+ "bounds": [
+ 0.0,
+ 1.0
+ ],
+ "default": 0.75,
+ "description": "Percentile selecting the rank.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "percentile",
+ "required": false,
+ "type": "float",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:gwyddion_rank_filter",
+ "public_name": "gwyddion_rank_filter",
+ "reference": {
+ "name": "Rank Filter",
+ "profile": "COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION",
+ "software": "Gwydion",
+ "version": "2.71"
+ },
+ "result_type": "SPMChannel",
+ "roi_support": false,
+ "status": "stable",
+ "units": "preserved"
+ },
+ {
+ "aliases": [],
+ "border_policy": "clipped",
+ "capability_id": "IMG.FILTER.SOBEL_X",
+ "contract": "Sobel X (horizontal) pixel-space derivative: kernel {0.25, 0, -0.25; 0.5, 0, -0.5; 0.25, 0, -0.25}; CLIPPED borders; frozen source sign (increasing-right X ramp gives negative response), orientation and accumulation order; z-unit preserved; finite 2D inputs only; no masks or ROI.",
+ "evidence": [
+ "tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.json",
+ "tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.npz",
+ "tests/validation/fixtures/gwyddion/derivative_filters/oracle_derivative_filters_source.py",
+ "tests/validation/test_gwyddion_derivative_filters_production_parity.py",
+ "tests/core/test_gwyddion_derivative_filters.py"
+ ],
+ "family": "IMG.FILTER",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "CROSS_VALIDATED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "img.filter.sobel_x",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Finite two-dimensional input channel.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "channel",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:gwyddion_sobel_x",
+ "public_name": "gwyddion_sobel_x",
+ "reference": {
+ "software": "Gwydion",
+ "version": "2.71",
+ "name": "Sobel X Filter",
+ "profile": "COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_DERIVATIVE_KERNEL_PROFILE"
+ },
+ "result_type": "SPMChannel",
+ "roi_support": false,
+ "status": "stable",
+ "units": "preserved"
+ },
+ {
+ "aliases": [],
+ "border_policy": "clipped",
+ "capability_id": "IMG.FILTER.SOBEL_Y",
+ "contract": "Sobel Y (vertical) pixel-space derivative: kernel {0.25, 0.5, 0.25; 0, 0, 0; -0.25, -0.5, -0.25}; CLIPPED borders; frozen source sign (increasing-down Y ramp gives negative response), orientation and accumulation order; z-unit preserved; finite 2D inputs only; no masks or ROI.",
+ "evidence": [
+ "tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.json",
+ "tests/validation/fixtures/gwyddion/derivative_filters/derivative_filters_reference.npz",
+ "tests/validation/fixtures/gwyddion/derivative_filters/oracle_derivative_filters_source.py",
+ "tests/validation/test_gwyddion_derivative_filters_production_parity.py",
+ "tests/core/test_gwyddion_derivative_filters.py"
+ ],
+ "family": "IMG.FILTER",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "CROSS_VALIDATED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "img.filter.sobel_y",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Finite two-dimensional input channel.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "channel",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:gwyddion_sobel_y",
+ "public_name": "gwyddion_sobel_y",
+ "reference": {
+ "software": "Gwydion",
+ "version": "2.71",
+ "name": "Sobel Y Filter",
+ "profile": "COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_DERIVATIVE_KERNEL_PROFILE"
+ },
+ "result_type": "SPMChannel",
+ "roi_support": false,
+ "status": "stable",
+ "units": "preserved"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "IMG.INTERPOLATION.LAPLACE_UNDER_MASK",
+ "contract": "Laplace-based interpolation of masked regions; the mask selects pixels to replace; finite two-dimensional input.",
+ "evidence": [
+ "tests/validation/fixtures/gwydion/scars_laplace/scars_laplace_reference.json",
+ "tests/validation/fixtures/gwydion/scars_laplace/scars_laplace_reference.npz",
+ "tests/validation/test_gwydion_laplace_production_parity.py"
+ ],
+ "family": "IMG.INTERPOLATION",
+ "known_deviations": [],
+ "mask_semantics": "mask_input",
+ "maturity": "CROSS_VALIDATED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "img.interpolation.laplace_under_mask",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Finite two-dimensional input channel.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "channel",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Mask array selecting pixels to interpolate.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "mask",
+ "required": true,
+ "type": "ndarray",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:gwydion_interpolate_data_under_mask",
+ "public_name": "gwydion_interpolate_data_under_mask",
+ "reference": {
+ "name": "Interpolate Data Under Mask (Laplace)",
+ "profile": "COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION",
+ "software": "Gwydion",
+ "version": "2.71"
+ },
+ "result_type": "SPMChannel",
+ "roi_support": false,
+ "status": "stable",
+ "units": "preserved"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "IMG.LEVEL.ALIGN_ROWS_MATCH",
+ "contract": "Align Rows Match: adjacent-row shape matching with Gaussian-weighted differences of row differences, cumulative zero-levelled shifts, zero-weight guard (pure vertical offsets may remain uncorrected).",
+ "evidence": [
+ "tests/validation/fixtures/gwyddion/align_rows_remaining/align_rows_remaining_reference.json",
+ "tests/validation/fixtures/gwyddion/align_rows_remaining/align_rows_remaining_reference.npz",
+ "tests/validation/test_gwydion_align_rows_remaining_production_parity.py"
+ ],
+ "family": "IMG.LEVEL",
+ "known_deviations": [],
+ "mask_semantics": "include_exclude_ignore",
+ "maturity": "CROSS_VALIDATED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "img.level.align_rows_match",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Finite two-dimensional input channel.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "channel",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Optional mask matching the channel shape.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "mask",
+ "required": false,
+ "type": "ndarray | None",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": "ignore",
+ "description": "Masking mode.",
+ "enum_values": [
+ "exclude",
+ "include",
+ "ignore"
+ ],
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "mask_mode",
+ "required": false,
+ "type": "Literal",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": "horizontal",
+ "description": "Row direction.",
+ "enum_values": [
+ "horizontal",
+ "vertical"
+ ],
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "direction",
+ "required": false,
+ "type": "Literal",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:gwyddion_align_rows_match",
+ "public_name": "gwyddion_align_rows_match",
+ "reference": {
+ "name": "Align Rows Match",
+ "profile": "COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION",
+ "software": "Gwydion",
+ "version": "2.71"
+ },
+ "result_type": "SPMChannel",
+ "roi_support": false,
+ "status": "stable",
+ "units": "preserved"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "IMG.LEVEL.ALIGN_ROWS_MODUS",
+ "contract": "Align Rows Modus: robust row-centre statistic (global masked-median fallback, upper median for fewer than nine retained samples, narrowest sqrt-count range window otherwise), zero-levelled shifts.",
+ "evidence": [
+ "tests/validation/fixtures/gwyddion/align_rows_remaining/align_rows_remaining_reference.json",
+ "tests/validation/fixtures/gwyddion/align_rows_remaining/align_rows_remaining_reference.npz",
+ "tests/validation/test_gwydion_align_rows_remaining_production_parity.py"
+ ],
+ "family": "IMG.LEVEL",
+ "known_deviations": [],
+ "mask_semantics": "include_exclude_ignore",
+ "maturity": "CROSS_VALIDATED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "img.level.align_rows_modus",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Finite two-dimensional input channel.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "channel",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Optional mask matching the channel shape.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "mask",
+ "required": false,
+ "type": "ndarray | None",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": "ignore",
+ "description": "Masking mode.",
+ "enum_values": [
+ "exclude",
+ "include",
+ "ignore"
+ ],
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "mask_mode",
+ "required": false,
+ "type": "Literal",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": "horizontal",
+ "description": "Row direction.",
+ "enum_values": [
+ "horizontal",
+ "vertical"
+ ],
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "direction",
+ "required": false,
+ "type": "Literal",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:gwyddion_align_rows_modus",
+ "public_name": "gwyddion_align_rows_modus",
+ "reference": {
+ "name": "Align Rows Modus",
+ "profile": "COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION",
+ "software": "Gwydion",
+ "version": "2.71"
+ },
+ "result_type": "SPMChannel",
+ "roi_support": false,
+ "status": "stable",
+ "units": "preserved"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "IMG.LEVEL.ALIGN_ROWS_POLYNOMIAL",
+ "contract": "Align Rows Polynomial: degree 0 uses the trim-fraction-zero row-shift path; degree >=1 fits each row independently on centred x with a packed Cholesky solve and full-field mean anchoring.",
+ "evidence": [
+ "tests/validation/fixtures/gwyddion/align_rows_remaining/align_rows_remaining_reference.json",
+ "tests/validation/fixtures/gwyddion/align_rows_remaining/align_rows_remaining_reference.npz",
+ "tests/validation/test_gwydion_align_rows_remaining_production_parity.py"
+ ],
+ "family": "IMG.LEVEL",
+ "known_deviations": [],
+ "mask_semantics": "include_exclude_ignore",
+ "maturity": "CROSS_VALIDATED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "img.level.align_rows_polynomial",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Finite two-dimensional input channel.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "channel",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ },
+ {
+ "bounds": [
+ 0,
+ 5
+ ],
+ "default": 1,
+ "description": "Polynomial degree.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "degree",
+ "required": false,
+ "type": "int",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Optional mask matching the channel shape.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "mask",
+ "required": false,
+ "type": "ndarray | None",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": "ignore",
+ "description": "Masking mode.",
+ "enum_values": [
+ "exclude",
+ "include",
+ "ignore"
+ ],
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "mask_mode",
+ "required": false,
+ "type": "Literal",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": "horizontal",
+ "description": "Row direction.",
+ "enum_values": [
+ "horizontal",
+ "vertical"
+ ],
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "direction",
+ "required": false,
+ "type": "Literal",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:gwyddion_align_rows_polynomial",
+ "public_name": "gwyddion_align_rows_polynomial",
+ "reference": {
+ "name": "Align Rows Polynomial",
+ "profile": "COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION",
+ "software": "Gwydion",
+ "version": "2.71"
+ },
+ "result_type": "SPMChannel",
+ "roi_support": false,
+ "status": "stable",
+ "units": "preserved"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "IMG.SCANLINE.MARK_SCARS",
+ "contract": "Detect and mark scan-line scars, returning a mask array; threshold and geometry parameters follow the frozen Gwydion contract.",
+ "evidence": [
+ "tests/validation/fixtures/gwydion/scars_laplace/scars_laplace_reference.json",
+ "tests/validation/fixtures/gwydion/scars_laplace/scars_laplace_reference.npz",
+ "tests/validation/test_gwydion_mark_scars_production_parity.py"
+ ],
+ "family": "IMG.SCANLINE",
+ "known_deviations": [],
+ "mask_semantics": "mask_output",
+ "maturity": "CROSS_VALIDATED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "img.scanline.mark_scars",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Finite two-dimensional input channel.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "channel",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 0.666,
+ "description": "High threshold.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "threshold_high",
+ "required": false,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 0.25,
+ "description": "Low threshold.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "threshold_low",
+ "required": false,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 16,
+ "description": "Minimum scar length.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "min_length",
+ "required": false,
+ "type": "int",
+ "units": "pixel"
+ },
+ {
+ "bounds": null,
+ "default": 4,
+ "description": "Maximum scar width.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "max_width",
+ "required": false,
+ "type": "int",
+ "units": "pixel"
+ },
+ {
+ "bounds": null,
+ "default": "both",
+ "description": "Scar polarity.",
+ "enum_values": [
+ "positive",
+ "negative",
+ "both"
+ ],
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "polarity",
+ "required": false,
+ "type": "Literal",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Optional existing mask.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "existing_mask",
+ "required": false,
+ "type": "ndarray | None",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": "replace",
+ "description": "Mask combination mode.",
+ "enum_values": [
+ "replace",
+ "union",
+ "intersection"
+ ],
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "combine",
+ "required": false,
+ "type": "Literal",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:gwydion_mark_scars",
+ "public_name": "gwydion_mark_scars",
+ "reference": {
+ "name": "Mark Scars",
+ "profile": "COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION",
+ "software": "Gwydion",
+ "version": "2.71"
+ },
+ "result_type": "ndarray",
+ "roi_support": false,
+ "status": "stable",
+ "units": "mask"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "IMG.SCANLINE.REMOVE_SCARS",
+ "contract": "Detect and remove scan-line scars, returning a corrected channel; threshold and geometry parameters follow the frozen Gwydion contract.",
+ "evidence": [
+ "tests/validation/fixtures/gwydion/scars_laplace/scars_laplace_reference.json",
+ "tests/validation/fixtures/gwydion/scars_laplace/scars_laplace_reference.npz",
+ "tests/validation/test_gwydion_remove_scars_production_parity.py"
+ ],
+ "family": "IMG.SCANLINE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "CROSS_VALIDATED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "img.scanline.remove_scars",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Finite two-dimensional input channel.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "channel",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 0.666,
+ "description": "High threshold.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "threshold_high",
+ "required": false,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 0.25,
+ "description": "Low threshold.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "threshold_low",
+ "required": false,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 16,
+ "description": "Minimum scar length.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "min_length",
+ "required": false,
+ "type": "int",
+ "units": "pixel"
+ },
+ {
+ "bounds": null,
+ "default": 4,
+ "description": "Maximum scar width.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "max_width",
+ "required": false,
+ "type": "int",
+ "units": "pixel"
+ },
+ {
+ "bounds": null,
+ "default": "both",
+ "description": "Scar polarity.",
+ "enum_values": [
+ "positive",
+ "negative",
+ "both"
+ ],
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "polarity",
+ "required": false,
+ "type": "Literal",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:gwydion_remove_scars",
+ "public_name": "gwydion_remove_scars",
+ "reference": {
+ "name": "Remove Scars",
+ "profile": "COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION",
+ "software": "Gwydion",
+ "version": "2.71"
+ },
+ "result_type": "SPMChannel",
+ "roi_support": false,
+ "status": "stable",
+ "units": "preserved"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "IMG.SCANLINE.STEP_BLOCK_CORRECTION",
+ "contract": "Correct step-block artefacts in scan lines; threshold and direction parameters follow the frozen Gwydion contract.",
+ "evidence": [
+ "tests/validation/fixtures/gwydion/step_block/step_block_reference.json",
+ "tests/validation/fixtures/gwydion/step_block/step_block_reference.npz",
+ "tests/validation/test_gwydion_step_block_production_parity.py"
+ ],
+ "family": "IMG.SCANLINE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "CROSS_VALIDATED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "img.scanline.step_block_correction",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Finite two-dimensional input channel.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "channel",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": 2.0,
+ "description": "Step detection threshold.",
+ "enum_values": null,
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "threshold",
+ "required": false,
+ "type": "float",
+ "units": null
+ },
+ {
+ "bounds": null,
+ "default": "left_to_right",
+ "description": "Scan direction.",
+ "enum_values": [
+ "left_to_right",
+ "right_to_left"
+ ],
+ "has_default": true,
+ "kind": "keyword_only",
+ "name": "direction",
+ "required": false,
+ "type": "Literal",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:gwydion_step_block_correction",
+ "public_name": "gwydion_step_block_correction",
+ "reference": {
+ "name": "Step Block Correction",
+ "profile": "COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION",
+ "software": "Gwydion",
+ "version": "2.71"
+ },
+ "result_type": "SPMChannel",
+ "roi_support": false,
+ "status": "stable",
+ "units": "preserved"
+ },
+ {
+ "aliases": [],
+ "border_policy": "not_applicable",
+ "capability_id": "IMG.SCANLINE.STEP_LINE_CORRECTION",
+ "contract": "Correct step-line artefacts in scan lines; no parameters beyond the input channel.",
+ "evidence": [
+ "tests/validation/fixtures/gwydion/linecorrect/linecorrect_reference.json",
+ "tests/validation/fixtures/gwydion/linecorrect/linecorrect_reference.npz",
+ "tests/validation/test_gwydion_linecorrect_production_parity.py"
+ ],
+ "family": "IMG.SCANLINE",
+ "known_deviations": [],
+ "mask_semantics": "none",
+ "maturity": "CROSS_VALIDATED",
+ "mutation_policy": "returns_new",
+ "nan_policy": "reject",
+ "operation_id": "img.scanline.step_line_correction",
+ "parameters": [
+ {
+ "bounds": null,
+ "default": null,
+ "description": "Finite two-dimensional input channel.",
+ "enum_values": null,
+ "has_default": false,
+ "kind": "positional",
+ "name": "channel",
+ "required": true,
+ "type": "SPMChannel",
+ "units": null
+ }
+ ],
+ "public_import": "spmkit.core.analysis:gwydion_step_line_correction",
+ "public_name": "gwydion_step_line_correction",
+ "reference": {
+ "name": "Step Line Correction",
+ "profile": "COMPILED_GWYDDION_2_71_SOURCE_INCLUDED_KERNEL_WITH_SOURCE_PINNED_ORCHESTRATION",
+ "software": "Gwydion",
+ "version": "2.71"
+ },
+ "result_type": "SPMChannel",
+ "roi_support": false,
+ "status": "stable",
+ "units": "preserved"
+ }
+ ],
+ "schema_version": 1
+}
diff --git a/src/spmkit/core/io/jpk.py b/src/spmkit/core/io/jpk.py
index 4f189b5..3f29e0b 100644
--- a/src/spmkit/core/io/jpk.py
+++ b/src/spmkit/core/io/jpk.py
@@ -1,36 +1,69 @@
"""Lector de curvas de fuerza JPK / Bruker (``.jpk-force``).
-Un ``.jpk-force`` es un archivo **ZIP** con esta estructura (verificada con datos
-reales del dataset abierto ``AFM-analysis/afmformats``)::
+Un ``.jpk-force`` es un archivo **ZIP**. Se soportan dos perfiles de metadatos:
- header.properties
- segments/0/segment-header.properties # extend (approach)
- segments/0/channels/height.dat # enteros crudos big-endian
- segments/0/channels/vDeflection.dat
- segments/1/... # retract
+1. **Perfil directo (legacy)**: las claves de escalado viven en el propio
+ segmento (``channel.height.data.encoder.scaling.multiplier``, etc.),
+ como en las muestras derivadas de ``AFM-analysis/afmformats``::
-Cada canal se convierte a unidades físicas con una **cascada de "calibration
-slots"**, cada uno ``valor·multiplier + offset``, en el orden de
-``conversion-set.conversions.list``:
+ header.properties
+ segments/0/segment-header.properties # extend (approach)
+ segments/0/channels/height.dat # enteros crudos big-endian
+ segments/0/channels/vDeflection.dat
+ segments/1/... # retract
-* ``vDeflection``: ``short`` → encoder (V) → ``distance`` (m, multiplier = InVOLS)
+2. **Perfil ForceScan 2.0 (``lcd-info``)**: la cabecera de segmento solo
+ referencia registros de calibración que viven en
+ ``shared-data/header.properties`` mediante la clave
+ ``channel.{name}.lcd-info.*={index}`` (el ``*`` forma parte de la clave)::
+
+ shared-data/header.properties # lcd-infos.count + lcd-info.{i}.*
+ segments/0/segment-header.properties # channel.height.lcd-info.*=1, etc.
+ segments/0/channels/height.dat # int32 big-endian
+
+ El registro ``lcd-info.{index}`` define: el tipo de dato crudo
+ (``type=integer-data`` → int32), el escalado del encoder (crudo → voltios)
+ y la cadena de conversión ``conversion-set.conversion.{slot}.*``
+ (``valor·multiplier + offset`` por slot, en el orden de
+ ``conversions.list``, partiendo de ``conversions.base``).
+
+Cada canal se convierte a unidades físicas con la **cascada de "calibration
+slots"**:
+
+* ``vDeflection``: crudo → encoder (V) → ``distance`` (m, multiplier = InVOLS)
→ ``force`` (N, multiplier = k). La calibración (InVOLS, k) vive en el archivo.
-* ``height``: ``short`` → encoder (V) → ``nominal`` → ``calibrated`` (m).
+* ``height``: crudo → encoder (V) → ``nominal`` → ``calibrated`` (m).
+
+Las claves explícitas del segmento (perfil directo) **ganan** sobre cualquier
+referencia ``lcd-info`` cuando ambas coexisten (override local).
-Devuelve un :class:`ForceCurve` con segmentos extend/retract ya calibrados.
-Referencia de implementación: ``afmformats``/``nanite``/``PyJibe`` (Paul Müller).
+Los fallos son errores tipeados :class:`JpkReaderError` (subclase de
+``ValueError``) con un código máquina legible. Nunca se sustituye una
+calibración adivinada: una cadena incompleta o no soportada es un fallo
+tipeado, no una matriz sin unidades.
"""
from __future__ import annotations
+import math
import re
import zipfile
+from dataclasses import dataclass
from pathlib import Path
import numpy as np
from spmkit.core.models import Calibration, CalState, ForceCurve, ForceSegment, SegmentType
+#: Códigos de fallo tipeado del lector JPK.
+JPK_NOT_ZIP = "JPK_NOT_ZIP"
+JPK_NO_SEGMENTS = "JPK_NO_SEGMENTS"
+JPK_MISSING_PROPERTY = "JPK_MISSING_PROPERTY"
+JPK_UNRESOLVED_LCD_INFO = "JPK_UNRESOLVED_LCD_INFO"
+JPK_CALIBRATION_CYCLE = "JPK_CALIBRATION_CYCLE"
+JPK_INVALID_NUMBER = "JPK_INVALID_NUMBER"
+JPK_UNSUPPORTED_CHAIN = "JPK_UNSUPPORTED_CHAIN"
+
_SEG_RE = re.compile(r"segments/(\d+)/segment-header\.properties$")
#: ``data.type`` de JPK → dtype numpy (big-endian, formato Java).
@@ -43,6 +76,41 @@
"double": ">f8",
}
+#: Tipo de registro ``lcd-info`` → dtype numpy (perfil ForceScan 2.0).
+_LCD_TYPES = {
+ "integer-data": ">i4",
+ "short-data": ">i2",
+ "double-data": ">f8",
+ "float-data": ">f4",
+}
+
+
+class JpkReaderError(ValueError):
+ """Fallo tipeado del lector JPK con código máquina legible."""
+
+ def __init__(self, code: str, message: str) -> None:
+ super().__init__(message)
+ self.code = code
+ self.message = message
+
+
+@dataclass(frozen=True)
+class _ChannelScaling:
+ """Escalado efectivo de un canal: encoder + slots de conversión ordenados.
+
+ Es un conjunto **efectivo** (ya resuelto): o bien viene del perfil directo
+ (``lcd_info=None``), o bien de un registro ``lcd-info.{index}`` de
+ shared-data (``lcd_info=index``). No muta los diccionarios crudos.
+ """
+
+ dtype: str # dtype numpy (p. ej. ">i4")
+ enc_mult: float
+ enc_offset: float
+ enc_unit: str | None
+ #: (slot, multiplier, offset, unidad declarada o None) en orden de la cadena.
+ slots: tuple[tuple[str, float, float, str | None], ...]
+ lcd_info: int | None
+
def _parse_properties(raw: bytes) -> dict[str, str]:
"""Parsea un ``.properties`` de Java (``clave=valor``, ``#`` comentarios)."""
@@ -56,40 +124,245 @@ def _parse_properties(raw: bytes) -> dict[str, str]:
return props
+def _float_prop(props: dict[str, str], key: str, *, detail: str = "") -> float:
+ """Lee y valida un número de propiedades; fallo tipeado si falta o es inválido."""
+ if key not in props:
+ raise JpkReaderError(
+ JPK_MISSING_PROPERTY,
+ f"Archivo .jpk-force corrupto o incompleto (falta '{key}'): {detail}".strip(),
+ )
+ raw = props[key]
+ try:
+ value = float(raw)
+ except ValueError:
+ raise JpkReaderError(
+ JPK_INVALID_NUMBER,
+ f"Archivo .jpk-force con valor numérico malformado: '{key}={raw!r}' ({detail})".strip(),
+ ) from None
+ if not math.isfinite(value):
+ raise JpkReaderError(
+ JPK_INVALID_NUMBER,
+ f"Archivo .jpk-force con valor numérico no finito: '{key}={raw!r}' ({detail})".strip(),
+ )
+ return value
+
+
+def _parse_lcd_info_reference(value: str, channel: str) -> int:
+ """Valida el valor de ``channel.{c}.lcd-info.*`` → índice entero."""
+ try:
+ index = int(value)
+ except ValueError:
+ raise JpkReaderError(
+ JPK_UNRESOLVED_LCD_INFO,
+ f"Archivo .jpk-force con referencia lcd-info malformada "
+ f"(channel.{channel}.lcd-info.*={value!r}): se esperaba un índice entero",
+ ) from None
+ if index < 0:
+ raise JpkReaderError(
+ JPK_UNRESOLVED_LCD_INFO,
+ f"Archivo .jpk-force con referencia lcd-info negativa "
+ f"(channel.{channel}.lcd-info.*={value!r})",
+ )
+ return index
+
+
+def _resolve_lcd_info_reference(
+ seg_props: dict[str, str], shared: dict[str, str], channel: str
+) -> tuple[int, str]:
+ """Resuelve ``channel.{c}.lcd-info.*`` → ``(index, prefijo 'lcd-info.{i}.')``.
+
+ Fallos tipeados: referencia ausente (perfil sin escalado directo ni
+ referencia), malformada, o registro inexistente en shared-data.
+ """
+ pfx = f"channel.{channel}"
+ ref = seg_props.get(f"{pfx}.lcd-info.*")
+ if ref is None:
+ raise JpkReaderError(
+ JPK_MISSING_PROPERTY,
+ f"Archivo .jpk-force corrupto o incompleto (falta "
+ f"'{pfx}.data.encoder.scaling.multiplier'): el segmento no tiene "
+ f"escalado directo ni referencia '{pfx}.lcd-info.*'",
+ )
+ index = _parse_lcd_info_reference(ref, channel)
+ rec = f"lcd-info.{index}."
+ if f"{rec}channel.name" not in shared:
+ raise JpkReaderError(
+ JPK_UNRESOLVED_LCD_INFO,
+ f"Archivo .jpk-force con referencia lcd-info sin resolver: "
+ f"{pfx}.lcd-info.*={ref} pero no existe el registro '{rec.rstrip('.')}' "
+ f"en shared-data/header.properties",
+ )
+ return index, rec
+
+
+def _resolve_calibration_chain(
+ shared: dict[str, str], rec: str, channel: str
+) -> tuple[tuple[str, float, float, str | None], ...]:
+ """Valida y ordena la cadena ``conversion-set`` de un registro lcd-info.
+
+ Reglas (semántica demostrada por los archivos ForceScan 2.0):
+
+ * el primer slot cuelga de ``conversions.base`` (pseudo-slot, p. ej.
+ ``volts``) o de otro slot; cada slot posterior cuelga del anterior;
+ * un slot que se referencia a sí mismo o a un slot posterior es una
+ **cadena cíclica** (fallo tipeado);
+ * un slot declarado ``defined=false`` no puede calibrar (fallo tipeado;
+ nunca se sustituye un valor adivinado);
+ * los multipliers/offsets deben ser numéricos finitos.
+ """
+ slots_list = shared.get(f"{rec}conversion-set.conversions.list", "").split()
+ if not slots_list:
+ raise JpkReaderError(
+ JPK_UNSUPPORTED_CHAIN,
+ f"Archivo .jpk-force con cadena de calibración vacía para el canal "
+ f"{channel} (conversions.list ausente en '{rec.rstrip('.')}')",
+ )
+ base = shared.get(f"{rec}conversion-set.conversions.base")
+ ordered: list[tuple[str, float, float, str | None]] = []
+ for i, slot in enumerate(slots_list):
+ sp = f"{rec}conversion-set.conversion.{slot}."
+ defined = shared.get(f"{sp}defined")
+ if defined is not None and defined.strip().lower() == "false":
+ raise JpkReaderError(
+ JPK_UNSUPPORTED_CHAIN,
+ f"Archivo .jpk-force con slot '{slot}' del canal {channel} no "
+ f"definido (defined=false): no se puede calibrar sin inventar valores",
+ )
+ slot_base = shared.get(f"{sp}base-calibration-slot")
+ if slot_base is not None and slot_base in slots_list and slots_list.index(slot_base) >= i:
+ raise JpkReaderError(
+ JPK_CALIBRATION_CYCLE,
+ f"Archivo .jpk-force con cadena de calibración cíclica para el "
+ f"canal {channel}: el slot '{slot}' se referencia a '{slot_base}'",
+ )
+ if slot_base is not None and slot_base not in slots_list and slot_base != base:
+ raise JpkReaderError(
+ JPK_UNSUPPORTED_CHAIN,
+ f"Archivo .jpk-force con slot '{slot}' del canal {channel} "
+ f"referenciando una base desconocida '{slot_base}'",
+ )
+ mult = _float_prop(
+ shared, f"{sp}scaling.multiplier", detail=f"canal {channel}, slot {slot}"
+ )
+ offset = _float_prop(shared, f"{sp}scaling.offset", detail=f"canal {channel}, slot {slot}")
+ unit = shared.get(f"{sp}scaling.unit.unit")
+ ordered.append((slot, mult, offset, unit))
+ return tuple(ordered)
+
+
+def _resolve_channel_scaling(
+ seg_props: dict[str, str], shared: dict[str, str], channel: str
+) -> _ChannelScaling:
+ """Resuelve el escalado efectivo de un canal: directo gana, si no lcd-info.
+
+ Precedencia: si el segmento trae las claves directas
+ (``channel.{c}.data.encoder.scaling.*``) se usan esas (perfil legacy); solo
+ si faltan se sigue la indirección ``channel.{c}.lcd-info.*`` hacia
+ shared-data (perfil ForceScan 2.0).
+ """
+ pfx = f"channel.{channel}"
+ direct_key = f"{pfx}.data.encoder.scaling.multiplier"
+ if direct_key in seg_props:
+ dtype = _DTYPES.get(seg_props.get(f"{pfx}.data.type", "short"), ">i2")
+ enc_mult = _float_prop(seg_props, direct_key, detail=f"canal {channel}")
+ enc_offset = _float_prop(
+ seg_props, f"{pfx}.data.encoder.scaling.offset", detail=f"canal {channel}"
+ )
+ slots_list = seg_props.get(f"{pfx}.conversion-set.conversions.list", "").split()
+ direct_slots: list[tuple[str, float, float, str | None]] = []
+ for slot in slots_list:
+ sp = f"{pfx}.conversion-set.conversion.{slot}."
+ mult = _float_prop(
+ seg_props, f"{sp}scaling.multiplier", detail=f"canal {channel}, slot {slot}"
+ )
+ offset = _float_prop(
+ seg_props, f"{sp}scaling.offset", detail=f"canal {channel}, slot {slot}"
+ )
+ direct_slots.append((slot, mult, offset, None))
+ return _ChannelScaling(dtype, enc_mult, enc_offset, None, tuple(direct_slots), None)
+
+ index, rec = _resolve_lcd_info_reference(seg_props, shared, channel)
+ declared = seg_props.get(f"{pfx}.data.type")
+ if declared is not None:
+ dtype = _DTYPES.get(declared, ">i2")
+ else:
+ lcd_type = shared.get(f"{rec}type")
+ if lcd_type not in _LCD_TYPES:
+ raise JpkReaderError(
+ JPK_UNSUPPORTED_CHAIN,
+ f"Archivo .jpk-force sin tipo de dato decodificable para el canal "
+ f"{channel} (lcd-info.{index}.type={lcd_type!r})",
+ )
+ dtype = _LCD_TYPES[lcd_type]
+ enc_mult = _float_prop(shared, f"{rec}encoder.scaling.multiplier", detail=f"canal {channel}")
+ enc_offset = _float_prop(shared, f"{rec}encoder.scaling.offset", detail=f"canal {channel}")
+ enc_unit = shared.get(f"{rec}encoder.scaling.unit.unit")
+ slots = _resolve_calibration_chain(shared, rec, channel)
+ return _ChannelScaling(dtype, enc_mult, enc_offset, enc_unit, slots, index)
+
+
+def _require_declared_unit(unit: str | None, expected: str, channel: str, slot: str) -> None:
+ """Unidad declarada incompatible con el rol del canal → fallo tipeado."""
+ if unit is not None and unit != expected:
+ raise JpkReaderError(
+ JPK_UNSUPPORTED_CHAIN,
+ f"Archivo .jpk-force con unidad declarada incompatible para el canal "
+ f"{channel} (slot '{slot}'): {unit!r} (se esperaba {expected!r})",
+ )
+
+
+def _validate_role_units(scaling: _ChannelScaling, channel: str) -> None:
+ """Verifica las unidades declaradas de los slots según el rol del canal.
+
+ La unidad de salida del canal height debe ser ``m``; los slots
+ ``distance``/``force`` de vDeflection deben ser ``m``/``N``. Si una clave
+ de unidad no está declarada se preserva la ausencia (perfil legacy no
+ declara unidades).
+ """
+ if channel == "height":
+ if scaling.slots:
+ slot, _m, _o, unit = scaling.slots[-1]
+ _require_declared_unit(unit, "m", channel, slot)
+ else:
+ for slot, _m, _o, unit in scaling.slots:
+ if slot == "distance":
+ _require_declared_unit(unit, "m", channel, slot)
+ if slot == "force":
+ _require_declared_unit(unit, "N", channel, slot)
+
+
def _read_channel_raw(
- zf: zipfile.ZipFile, seg: int, channel: str, props: dict[str, str]
+ zf: zipfile.ZipFile, seg: int, channel: str, scaling: _ChannelScaling
) -> np.ndarray:
- """Lee un canal ``.dat`` como enteros/flotantes crudos según ``data.type``."""
- dtype = _DTYPES.get(props.get(f"channel.{channel}.data.type", "short"), ">i2")
+ """Lee un canal ``.dat`` con el dtype resuelto del perfil."""
blob = zf.read(f"segments/{seg}/channels/{channel}.dat")
- return np.frombuffer(blob, dtype=dtype)
+ return np.frombuffer(blob, dtype=scaling.dtype)
def _channel_value(
- raw: np.ndarray, channel: str, props: dict[str, str], stop_after: str | None = None
+ raw: np.ndarray, scaling: _ChannelScaling, stop_after: str | None = None
) -> np.ndarray:
- """Aplica encoder + conversiones de un canal hasta el slot ``stop_after``.
+ """Aplica encoder + slots del escalado resuelto hasta ``stop_after``.
- ``stop_after="encoder"`` devuelve la salida del encoder (p. ej. voltios); ``None``
+ ``stop_after="encoder"`` devuelve la salida del encoder (voltios); ``None``
aplica toda la cadena (unidad física final).
"""
- pfx = f"channel.{channel}"
- enc_m = float(props[f"{pfx}.data.encoder.scaling.multiplier"])
- enc_o = float(props[f"{pfx}.data.encoder.scaling.offset"])
- value = raw.astype(np.float64) * enc_m + enc_o
+ value = raw.astype(np.float64) * scaling.enc_mult + scaling.enc_offset
if stop_after == "encoder":
return value
- for slot in props.get(f"{pfx}.conversion-set.conversions.list", "").split():
- m = float(props[f"{pfx}.conversion-set.conversion.{slot}.scaling.multiplier"])
- o = float(props[f"{pfx}.conversion-set.conversion.{slot}.scaling.offset"])
- value = value * m + o
+ for slot, mult, offset, _unit in scaling.slots:
+ value = value * mult + offset
if slot == stop_after:
return value
return value
-def _slot_multiplier(channel: str, slot: str, props: dict[str, str]) -> float:
- return float(props[f"channel.{channel}.conversion-set.conversion.{slot}.scaling.multiplier"])
+def _slot_multiplier(scaling: _ChannelScaling, slot: str) -> float:
+ """Multiplicador de un slot de la cadena (p. ej. InVOLS, k)."""
+ for name, mult, _offset, _unit in scaling.slots:
+ if name == slot:
+ return mult
+ raise KeyError(f"channel.vDeflection.conversion-set.conversion.{slot}") # noqa: TRY003
def _segment_kind(props: dict[str, str], index: int) -> tuple[SegmentType, str]:
@@ -105,48 +378,84 @@ def _segment_kind(props: dict[str, str], index: int) -> tuple[SegmentType, str]:
def load_jpk_force(path: str | Path) -> ForceCurve:
- """Lee un ``.jpk-force`` y devuelve un :class:`ForceCurve` calibrado."""
+ """Lee un ``.jpk-force`` y devuelve un :class:`ForceCurve` calibrado.
+
+ Soporta el perfil directo (legacy) y el perfil ForceScan 2.0 con
+ indirección ``lcd-info`` hacia ``shared-data/header.properties``.
+
+ Raises:
+ JpkReaderError: fallo tipeado (subclase de ``ValueError``) con código
+ ``JPK_NOT_ZIP``/``JPK_NO_SEGMENTS``/``JPK_MISSING_PROPERTY``/
+ ``JPK_UNRESOLVED_LCD_INFO``/``JPK_CALIBRATION_CYCLE``/
+ ``JPK_INVALID_NUMBER``/``JPK_UNSUPPORTED_CHAIN``.
+ """
path = Path(path)
try:
return _load_jpk_force(path)
except zipfile.BadZipFile as exc:
- raise ValueError(f"Archivo .jpk-force no es un ZIP válido (¿corrupto?): {path}") from exc
+ raise JpkReaderError(
+ JPK_NOT_ZIP, f"Archivo .jpk-force no es un ZIP válido (¿corrupto?): {path}"
+ ) from exc
except KeyError as exc:
- raise ValueError(f"Archivo .jpk-force corrupto o incompleto (falta {exc}): {path}") from exc
+ raise JpkReaderError(
+ JPK_MISSING_PROPERTY,
+ f"Archivo .jpk-force corrupto o incompleto (falta {exc}): {path}",
+ ) from exc
def _load_jpk_force(path: Path) -> ForceCurve:
with zipfile.ZipFile(path) as zf:
seg_ids = sorted({int(m.group(1)) for name in zf.namelist() if (m := _SEG_RE.search(name))})
if not seg_ids:
- raise ValueError(f"No es un .jpk-force válido (sin segmentos): {path}")
+ raise JpkReaderError(
+ JPK_NO_SEGMENTS, f"No es un .jpk-force válido (sin segmentos): {path}"
+ )
+
+ # shared-data se lee UNA vez por archivo (perfil ForceScan 2.0).
+ shared = (
+ _parse_properties(zf.read("shared-data/header.properties"))
+ if "shared-data/header.properties" in zf.namelist()
+ else {}
+ )
segments: list[ForceSegment] = []
invols: float | None = None
spring_k: float | None = None
+ profile = "direct"
for seg in seg_ids:
- props = _parse_properties(zf.read(f"segments/{seg}/segment-header.properties"))
- kind, direction = _segment_kind(props, seg)
-
- raw_h = _read_channel_raw(zf, seg, "height", props)
- raw_vd = _read_channel_raw(zf, seg, "vDeflection", props)
- height_m = _channel_value(raw_h, "height", props)
- volts = _channel_value(raw_vd, "vDeflection", props, stop_after="encoder")
-
- slots = props.get("channel.vDeflection.conversion-set.conversions.list", "").split()
+ seg_props = _parse_properties(zf.read(f"segments/{seg}/segment-header.properties"))
+ kind, direction = _segment_kind(seg_props, seg)
+
+ h_scaling = _resolve_channel_scaling(seg_props, shared, "height")
+ v_scaling = _resolve_channel_scaling(seg_props, shared, "vDeflection")
+ _validate_role_units(h_scaling, "height")
+ _validate_role_units(v_scaling, "vDeflection")
+ if h_scaling.lcd_info is not None or v_scaling.lcd_info is not None:
+ profile = "lcd-info"
+
+ raw_h = _read_channel_raw(zf, seg, "height", h_scaling)
+ raw_vd = _read_channel_raw(zf, seg, "vDeflection", v_scaling)
+ height_m = _channel_value(raw_h, h_scaling)
+ volts = _channel_value(raw_vd, v_scaling, stop_after="encoder")
+
+ slot_names = [s[0] for s in v_scaling.slots]
deflection = force = separation = None
state: CalState = "raw_v"
- if "distance" in slots:
- deflection = _channel_value(raw_vd, "vDeflection", props, stop_after="distance")
+ if "distance" in slot_names:
+ deflection = _channel_value(raw_vd, v_scaling, stop_after="distance")
separation = height_m - deflection
- invols = _slot_multiplier("vDeflection", "distance", props)
+ invols = _slot_multiplier(v_scaling, "distance")
state = "deflection_m"
- if "force" in slots:
- force = _channel_value(raw_vd, "vDeflection", props, stop_after="force")
- spring_k = _slot_multiplier("vDeflection", "force", props)
+ if "force" in slot_names:
+ force = _channel_value(raw_vd, v_scaling, stop_after="force")
+ spring_k = _slot_multiplier(v_scaling, "force")
state = "force_n"
+ meta: dict = {"num_points": int(raw_h.size)}
+ if h_scaling.lcd_info is not None or v_scaling.lcd_info is not None:
+ meta["lcd_info"] = {"height": h_scaling.lcd_info, "vDeflection": v_scaling.lcd_info}
+
segments.append(
ForceSegment(
segment_type=kind,
@@ -157,7 +466,7 @@ def _load_jpk_force(path: Path) -> ForceCurve:
force=force,
separation=separation,
state=state,
- metadata={"num_points": int(raw_h.size)},
+ metadata=meta,
)
)
@@ -167,11 +476,11 @@ def _load_jpk_force(path: Path) -> ForceCurve:
invols=invols,
spring_constant=spring_k,
method="jpk_metadata",
- provenance={"source": path.name},
+ provenance={"source": path.name, "profile": profile},
)
return ForceCurve(
segments=tuple(segments),
calibration=calibration,
- metadata={"format": "jpk-force", "source_path": str(path)},
+ metadata={"format": "jpk-force", "source_path": str(path), "profile": profile},
)
diff --git a/src/spmkit/core/registry.py b/src/spmkit/core/registry.py
new file mode 100644
index 0000000..59615db
--- /dev/null
+++ b/src/spmkit/core/registry.py
@@ -0,0 +1,447 @@
+"""SPMKit Operation Registry v1.
+
+A minimal, deterministic, metadata-only registry of stable scientific
+operations. It loads the packaged capability ledger
+(``spmkit.core.capabilities.json``) exactly once, validates it, and exposes
+lookup, filtering and lazy callable resolution.
+
+Scope (v1):
+ * metadata and callable resolution only;
+ * no generic ``invoke()`` helper (operations have heterogeneous inputs);
+ * no Recipe, CLI, workflow, plugin or history system;
+ * no Git metadata, timestamps or repository-local paths;
+ * no dynamic filesystem scanning;
+ * no dependency on docs/ files at runtime.
+
+Callers resolve a callable and invoke it directly with the operation's own
+signature.
+"""
+
+from __future__ import annotations
+
+import importlib
+import importlib.resources
+import json
+import math
+from collections.abc import Callable, Iterable, Mapping
+from dataclasses import dataclass
+from enum import Enum
+from typing import cast
+
+__all__ = [
+ "CapabilitySpec",
+ "ParameterSpec",
+ "ReferenceSpec",
+ "get_operation",
+ "list_operations",
+ "filter_operations",
+ "resolve_callable",
+]
+
+_SCHEMA_VERSION = 1
+
+_REQUIRED_FIELDS = (
+ "capability_id", "operation_id", "family", "public_name", "public_import",
+ "aliases", "reference", "contract", "parameters", "result_type", "units",
+ "mask_semantics", "roi_support", "nan_policy", "border_policy",
+ "mutation_policy", "status", "maturity", "evidence", "known_deviations",
+)
+
+_MATURITY = frozenset({
+ "SPECIFIED", "SOFTWARE_VERIFIED", "NUMERICALLY_VERIFIED",
+ "CROSS_VALIDATED", "PHYSICALLY_VALIDATED",
+})
+_STATUS = frozenset({"stable", "experimental", "deprecated"})
+_MASK = frozenset({"none", "include_exclude_ignore", "mask_input", "mask_output"})
+_NAN = frozenset({"reject", "propagate", "replace", "not_applicable"})
+_BORDER = frozenset({"clipped", "extend", "mirror", "periodic", "not_applicable"})
+_MUTATION = frozenset({"none", "returns_new", "in_place"})
+_KIND = frozenset({"positional", "keyword_only"})
+
+
+class RegistryError(ValueError):
+ """Raised for invalid registry construction or lookup."""
+
+
+class UnknownOperationError(KeyError):
+ """Raised when an operation_id is not registered."""
+
+
+class Maturity(Enum):
+ SPECIFIED = "SPECIFIED"
+ SOFTWARE_VERIFIED = "SOFTWARE_VERIFIED"
+ NUMERICALLY_VERIFIED = "NUMERICALLY_VERIFIED"
+ CROSS_VALIDATED = "CROSS_VALIDATED"
+ PHYSICALLY_VALIDATED = "PHYSICALLY_VALIDATED"
+
+
+class Status(Enum):
+ STABLE = "stable"
+ EXPERIMENTAL = "experimental"
+ DEPRECATED = "deprecated"
+
+
+class MutationPolicy(Enum):
+ NONE = "none"
+ RETURNS_NEW = "returns_new"
+ IN_PLACE = "in_place"
+
+
+class NanPolicy(Enum):
+ REJECT = "reject"
+ PROPAGATE = "propagate"
+ REPLACE = "replace"
+ NOT_APPLICABLE = "not_applicable"
+
+
+@dataclass(frozen=True)
+class ParameterSpec:
+ """Explicit scientific parameter metadata."""
+
+ name: str
+ kind: str
+ required: bool
+ has_default: bool
+ default: object
+ type: str
+ enum_values: tuple[str, ...] | None
+ bounds: tuple[float, float] | None
+ units: str | None
+ description: str
+
+
+@dataclass(frozen=True)
+class ReferenceSpec:
+ """External reference software identity."""
+
+ software: str
+ version: str
+ name: str
+ profile: str
+
+
+@dataclass(frozen=True)
+class CapabilitySpec:
+ """Immutable stable capability record."""
+
+ capability_id: str
+ operation_id: str
+ family: str
+ public_name: str
+ public_import: str
+ aliases: tuple[str, ...]
+ reference: ReferenceSpec
+ contract: str
+ parameters: tuple[ParameterSpec, ...]
+ result_type: str
+ units: str
+ mask_semantics: str
+ roi_support: bool
+ nan_policy: NanPolicy
+ border_policy: str
+ mutation_policy: MutationPolicy
+ status: Status
+ maturity: Maturity
+ evidence: tuple[str, ...]
+ known_deviations: tuple[str, ...]
+
+
+def _load_json() -> Mapping[str, object]:
+ resource = importlib.resources.files("spmkit.core").joinpath("capabilities.json")
+ with resource.open("r", encoding="utf-8") as fh:
+ return json.load(fh)
+
+
+def _validate_public_import(public_import: str) -> None:
+ if ":" not in public_import:
+ raise RegistryError(f"malformed public import {public_import!r}")
+ module, attr = public_import.split(":", 1)
+ if not module or not attr:
+ raise RegistryError(f"malformed public import {public_import!r}")
+ if attr.startswith("_"):
+ raise RegistryError(f"private public name {attr!r}")
+
+
+def _validate_evidence_path(path: str) -> None:
+ if path.startswith("/") or ":" in path or "\\" in path:
+ raise RegistryError(f"absolute or non-relative evidence path {path!r}")
+ if ".reference" in path.split("/"):
+ raise RegistryError(f"evidence path under .reference {path!r}")
+
+
+def _s(raw: Mapping[str, object], key: str) -> str:
+ if key not in raw:
+ raise RegistryError(f"missing field {key!r}")
+ value = raw[key]
+ if not isinstance(value, str):
+ raise RegistryError(f"{key} must be a string, got {type(value).__name__}")
+ return value
+
+
+def _s_or_none(raw: Mapping[str, object], key: str) -> str | None:
+ if key not in raw:
+ raise RegistryError(f"missing field {key!r}")
+ value = raw[key]
+ if value is None:
+ return None
+ if not isinstance(value, str):
+ raise RegistryError(f"{key} must be a string or null, "
+ f"got {type(value).__name__}")
+ return value
+
+
+def _strs(raw: Mapping[str, object], key: str) -> tuple[str, ...]:
+ if key not in raw:
+ raise RegistryError(f"missing field {key!r}")
+ value = raw[key]
+ if not isinstance(value, list):
+ raise RegistryError(f"{key} must be a list, got {type(value).__name__}")
+ for item in value:
+ if not isinstance(item, str):
+ raise RegistryError(
+ f"{key} items must be strings, got {type(item).__name__}")
+ return tuple(value)
+
+
+def _b(raw: Mapping[str, object], key: str) -> bool:
+ if key not in raw:
+ raise RegistryError(f"missing field {key!r}")
+ value = raw[key]
+ if not isinstance(value, bool):
+ raise RegistryError(f"{key} must be a Boolean, got {type(value).__name__}")
+ return value
+
+
+def _parse_parameter(raw: Mapping[str, object]) -> ParameterSpec:
+ _reject_unknown_fields(raw, _KNOWN_PARAMETER_FIELDS, "parameter record")
+ name = _s(raw, "name")
+ kind = _s(raw, "kind")
+ if kind not in _KIND:
+ raise RegistryError(f"parameter {name}: unknown kind {kind!r}")
+ required = _b(raw, "required")
+ has_default = _b(raw, "has_default")
+ if "default" not in raw:
+ raise RegistryError(f"parameter {name}: missing default field")
+ default = raw["default"]
+ if not has_default and default is not None:
+ raise RegistryError(f"parameter {name}: default without has_default")
+ enum_values_raw = raw.get("enum_values")
+ enum_values: tuple[str, ...] | None = None
+ if enum_values_raw is not None:
+ if not isinstance(enum_values_raw, list):
+ raise RegistryError("enum_values must be a list or null")
+ for item in enum_values_raw:
+ if not isinstance(item, str):
+ raise RegistryError(
+ f"enum_values items must be strings, got {type(item).__name__}")
+ enum_values = tuple(enum_values_raw)
+ bounds_raw = raw.get("bounds")
+ bounds: tuple[float, float] | None = None
+ if bounds_raw is not None:
+ if not isinstance(bounds_raw, list) or len(bounds_raw) != 2:
+ raise RegistryError(f"parameter {name}: bounds must be a 2-list or null")
+ items = []
+ for item in bounds_raw:
+ if isinstance(item, bool) or not isinstance(item, (int, float)):
+ raise RegistryError(
+ f"parameter {name}: bounds items must be real numbers")
+ value = float(item)
+ if not math.isfinite(value):
+ raise RegistryError(
+ f"parameter {name}: bounds items must be finite")
+ items.append(value)
+ if items[0] > items[1]:
+ raise RegistryError(
+ f"parameter {name}: bounds must be ordered low..high")
+ bounds = (items[0], items[1])
+ return ParameterSpec(
+ name=name,
+ kind=kind,
+ required=required,
+ has_default=has_default,
+ default=default,
+ type=_s(raw, "type"),
+ enum_values=enum_values,
+ bounds=bounds,
+ units=_s_or_none(raw, "units"),
+ description=_s(raw, "description"),
+ )
+
+
+def _parse_capability(raw: Mapping[str, object]) -> CapabilitySpec:
+ _reject_unknown_fields(raw, _KNOWN_CAPABILITY_FIELDS, "capability record")
+ for field_name in _REQUIRED_FIELDS:
+ if field_name not in raw:
+ raise RegistryError(f"missing required field {field_name!r}")
+ capability_id = _s(raw, "capability_id")
+ operation_id = _s(raw, "operation_id")
+ public_import = _s(raw, "public_import")
+ _validate_public_import(public_import)
+ for path in _strs(raw, "evidence"):
+ _validate_evidence_path(path)
+ maturity = _s(raw, "maturity")
+ if maturity not in _MATURITY:
+ raise RegistryError(f"{capability_id}: unknown maturity {maturity!r}")
+ status = _s(raw, "status")
+ if status not in _STATUS:
+ raise RegistryError(f"{capability_id}: unknown status {status!r}")
+ mask = _s(raw, "mask_semantics")
+ if mask not in _MASK:
+ raise RegistryError(f"{capability_id}: unknown mask_semantics {mask!r}")
+ nan = _s(raw, "nan_policy")
+ if nan not in _NAN:
+ raise RegistryError(f"{capability_id}: unknown nan_policy {nan!r}")
+ border = _s(raw, "border_policy")
+ if border not in _BORDER:
+ raise RegistryError(f"{capability_id}: unknown border_policy {border!r}")
+ mutation = _s(raw, "mutation_policy")
+ if mutation not in _MUTATION:
+ raise RegistryError(f"{capability_id}: unknown mutation_policy {mutation!r}")
+ if "reference" not in raw:
+ raise RegistryError(f"{capability_id}: missing reference field")
+ ref_raw = raw["reference"]
+ if not isinstance(ref_raw, dict):
+ raise RegistryError(f"{capability_id}: reference must be an object")
+ ref_map = cast(Mapping[str, object], ref_raw)
+ _reject_unknown_fields(ref_map, {"software", "version", "name", "profile"},
+ f"{capability_id}: reference")
+ reference = ReferenceSpec(
+ software=_s(ref_map, "software"),
+ version=_s(ref_map, "version"),
+ name=_s(ref_map, "name"),
+ profile=_s(ref_map, "profile"),
+ )
+ parameters = tuple(_parse_parameter(cast(Mapping[str, object], p))
+ for p in cast(Iterable[object], raw["parameters"]))
+ return CapabilitySpec(
+ capability_id=capability_id,
+ operation_id=operation_id,
+ family=_s(raw, "family"),
+ public_name=_s(raw, "public_name"),
+ public_import=public_import,
+ aliases=_strs(raw, "aliases"),
+ reference=reference,
+ contract=_s(raw, "contract"),
+ parameters=parameters,
+ result_type=_s(raw, "result_type"),
+ units=_s(raw, "units"),
+ mask_semantics=mask,
+ roi_support=_b(raw, "roi_support"),
+ nan_policy=NanPolicy(nan),
+ border_policy=border,
+ mutation_policy=MutationPolicy(mutation),
+ status=Status(status),
+ maturity=Maturity(maturity),
+ evidence=_strs(raw, "evidence"),
+ known_deviations=_strs(raw, "known_deviations"),
+ )
+
+
+_KNOWN_TOP_LEVEL = {"schema_version", "capabilities"}
+_KNOWN_CAPABILITY_FIELDS = set(_REQUIRED_FIELDS)
+_KNOWN_PARAMETER_FIELDS = {
+ "name", "kind", "required", "has_default", "default", "type",
+ "enum_values", "bounds", "units", "description",
+}
+
+
+def _reject_unknown_fields(raw: Mapping[str, object], known: set[str],
+ context: str) -> None:
+ for key in raw:
+ if key not in known:
+ raise RegistryError(f"{context}: unknown field {key!r}")
+
+
+def _build_registry() -> tuple[CapabilitySpec, ...]:
+ data = _load_json()
+ if not isinstance(data, dict):
+ raise RegistryError("ledger root must be an object")
+ _reject_unknown_fields(data, _KNOWN_TOP_LEVEL, "ledger")
+ version = data.get("schema_version")
+ if not isinstance(version, int) or isinstance(version, bool) \
+ or version != _SCHEMA_VERSION:
+ raise RegistryError(
+ f"unsupported schema version {version!r}")
+ raw_caps = data.get("capabilities")
+ if not isinstance(raw_caps, list):
+ raise RegistryError("capabilities must be a list")
+ specs = [_parse_capability(c) for c in raw_caps]
+ # deterministic ordering by capability_id
+ specs.sort(key=lambda s: s.capability_id)
+ seen_cap: set[str] = set()
+ seen_op: set[str] = set()
+ seen_import: set[str] = set()
+ for spec in specs:
+ if spec.capability_id in seen_cap:
+ raise RegistryError(f"duplicate capability ID {spec.capability_id}")
+ if spec.operation_id in seen_op:
+ raise RegistryError(f"duplicate operation ID {spec.operation_id}")
+ if spec.public_import in seen_import:
+ raise RegistryError(f"duplicate public import {spec.public_import}")
+ seen_cap.add(spec.capability_id)
+ seen_op.add(spec.operation_id)
+ seen_import.add(spec.public_import)
+ return tuple(specs)
+
+
+_REGISTRY: tuple[CapabilitySpec, ...] | None = None
+
+
+def _registry() -> tuple[CapabilitySpec, ...]:
+ global _REGISTRY
+ if _REGISTRY is None:
+ _REGISTRY = _build_registry()
+ return _REGISTRY
+
+
+def get_operation(operation_id: str) -> CapabilitySpec:
+ """Return the capability record for an operation ID."""
+ for spec in _registry():
+ if spec.operation_id == operation_id:
+ return spec
+ raise UnknownOperationError(operation_id)
+
+
+def list_operations() -> tuple[CapabilitySpec, ...]:
+ """Return all registered operations in deterministic order."""
+ return _registry()
+
+
+def filter_operations(
+ family: str | None = None,
+ maturity: str | Maturity | None = None,
+) -> tuple[CapabilitySpec, ...]:
+ """Return operations filtered by family and/or maturity."""
+ want_maturity: Maturity | None = None
+ if maturity is not None:
+ if isinstance(maturity, Maturity):
+ want_maturity = maturity
+ else:
+ if maturity not in _MATURITY:
+ raise RegistryError(f"unknown maturity {maturity!r}")
+ want_maturity = Maturity(maturity)
+ out = []
+ for spec in _registry():
+ if family is not None and spec.family != family:
+ continue
+ if want_maturity is not None and spec.maturity != want_maturity:
+ continue
+ out.append(spec)
+ return tuple(out)
+
+
+def resolve_callable(operation_id: str) -> Callable[..., object]:
+ """Resolve the public callable for an operation ID (lazy import)."""
+ spec = get_operation(operation_id)
+ module_name, attr = spec.public_import.split(":", 1)
+ module = importlib.import_module(module_name)
+ fn = getattr(module, attr, None)
+ if fn is None:
+ raise RegistryError(
+ f"public import {spec.public_import!r} does not resolve")
+ if not callable(fn):
+ raise RegistryError(
+ f"public import {spec.public_import!r} is not callable")
+ if attr.startswith("_"):
+ raise RegistryError(f"private callable {attr!r}")
+ return fn
diff --git a/tests/core/test_capability_ledger_v1.py b/tests/core/test_capability_ledger_v1.py
new file mode 100644
index 0000000..2423e0c
--- /dev/null
+++ b/tests/core/test_capability_ledger_v1.py
@@ -0,0 +1,155 @@
+"""Capability Ledger v1 tests.
+
+Verifies the packaged capability ledger JSON: schema, deterministic
+ordering, uniqueness, required fields, enum values, no Git/timestamp/
+absolute-path metadata, evidence paths, import resolution, and byte-stable
+Markdown regeneration.
+"""
+
+from __future__ import annotations
+
+import importlib.resources
+import json
+import subprocess
+from pathlib import Path
+
+REPO_ROOT = Path(__file__).resolve().parents[2]
+LEDGER_JSON = REPO_ROOT / "src" / "spmkit" / "core" / "capabilities.json"
+LEDGER_MD = REPO_ROOT / "docs" / "parity" / "CAPABILITY_LEDGER.md"
+GENERATOR = REPO_ROOT / "scripts" / "generate_capability_ledger.py"
+
+REQUIRED_FIELDS = {
+ "capability_id", "operation_id", "family", "public_name", "public_import",
+ "aliases", "reference", "contract", "parameters", "result_type", "units",
+ "mask_semantics", "roi_support", "nan_policy", "border_policy",
+ "mutation_policy", "status", "maturity", "evidence", "known_deviations",
+}
+MATURITY = {"SPECIFIED", "SOFTWARE_VERIFIED", "NUMERICALLY_VERIFIED",
+ "CROSS_VALIDATED", "PHYSICALLY_VALIDATED"}
+STATUS = {"stable", "experimental", "deprecated"}
+MASK = {"none", "include_exclude_ignore", "mask_input", "mask_output"}
+NAN = {"reject", "propagate", "replace", "not_applicable"}
+BORDER = {"clipped", "extend", "mirror", "periodic", "not_applicable"}
+MUTATION = {"none", "returns_new", "in_place"}
+
+
+def _load():
+ return json.loads(LEDGER_JSON.read_text(encoding="utf-8"))
+
+
+def test_schema_version_and_count() -> None:
+ data = _load()
+ assert data["schema_version"] == 1
+ assert len(data["capabilities"]) == 74
+
+
+def test_deterministic_ordering() -> None:
+ data = _load()
+ ids = [c["capability_id"] for c in data["capabilities"]]
+ assert ids == sorted(ids)
+
+
+def test_unique_ids_and_imports() -> None:
+ data = _load()
+ caps = [c["capability_id"] for c in data["capabilities"]]
+ ops = [c["operation_id"] for c in data["capabilities"]]
+ imps = [c["public_import"] for c in data["capabilities"]]
+ assert len(set(caps)) == 74
+ assert len(set(ops)) == 74
+ assert len(set(imps)) == 74
+
+
+def test_exact_derivative_registration_set() -> None:
+ data = _load()
+ caps = {c["capability_id"] for c in data["capabilities"]}
+ ops = {c["operation_id"] for c in data["capabilities"]}
+ assert {"IMG.FILTER.SOBEL_X", "IMG.FILTER.SOBEL_Y", "IMG.FILTER.PREWITT_X",
+ "IMG.FILTER.PREWITT_Y", "IMG.FILTER.GRADIENT_MAGNITUDE",
+ "IMG.FILTER.GRADIENT_DIRECTION"} <= caps
+ assert {"img.filter.sobel_x", "img.filter.sobel_y", "img.filter.prewitt_x",
+ "img.filter.prewitt_y", "img.filter.gradient_magnitude",
+ "img.filter.gradient_direction"} <= ops
+ # exactly six new capability records joined the original eleven
+ assert len(caps) == 74 and len(ops) == 74
+
+
+def test_required_fields_present() -> None:
+ for c in _load()["capabilities"]:
+ assert set(c.keys()) >= REQUIRED_FIELDS, c["capability_id"]
+
+
+def test_valid_enum_values() -> None:
+ for c in _load()["capabilities"]:
+ assert c["maturity"] in MATURITY, c["capability_id"]
+ assert c["status"] in STATUS, c["capability_id"]
+ assert c["mask_semantics"] in MASK, c["capability_id"]
+ assert c["nan_policy"] in NAN, c["capability_id"]
+ assert c["border_policy"] in BORDER, c["capability_id"]
+ assert c["mutation_policy"] in MUTATION, c["capability_id"]
+ for p in c["parameters"]:
+ assert p["kind"] in {"positional", "keyword_only"}
+ assert p["has_default"] is not None
+ if not p["has_default"]:
+ assert p["default"] is None
+ if p["required"]:
+ assert not p["has_default"] or p["default"] is None
+
+
+def test_no_git_or_timestamp_metadata() -> None:
+ text = LEDGER_JSON.read_text(encoding="utf-8")
+ for forbidden in ("commit", "timestamp", "generated_at", "branch", "head"):
+ assert forbidden not in text, forbidden
+
+
+def test_no_absolute_paths_and_no_reference_dependency() -> None:
+ for c in _load()["capabilities"]:
+ for e in c["evidence"]:
+ assert not e.startswith("/"), e
+ assert ".reference" not in e.split("/"), e
+ assert ":" not in e and "\\" not in e, e
+
+
+def test_aliases_explicit_lists() -> None:
+ for c in _load()["capabilities"]:
+ assert isinstance(c["aliases"], list)
+ assert all(isinstance(a, str) for a in c["aliases"])
+
+
+def test_all_evidence_paths_exist() -> None:
+ for c in _load()["capabilities"]:
+ for e in c["evidence"]:
+ p = REPO_ROOT / e
+ assert p.exists(), f"{c['capability_id']}: {e}"
+
+
+def test_all_public_imports_resolve() -> None:
+ import importlib
+ for c in _load()["capabilities"]:
+ module_name, attr = c["public_import"].split(":", 1)
+ module = importlib.import_module(module_name)
+ assert callable(getattr(module, attr)), c["public_import"]
+
+
+def test_packaged_resource_accessible() -> None:
+ resource = importlib.resources.files("spmkit.core").joinpath("capabilities.json")
+ data = json.loads(resource.read_text(encoding="utf-8"))
+ assert data["schema_version"] == 1
+ assert len(data["capabilities"]) == 74
+
+
+def test_markdown_regeneration_byte_identical() -> None:
+ before = LEDGER_MD.read_bytes()
+ subprocess.run(["python", str(GENERATOR)], check=True, capture_output=True)
+ after = LEDGER_MD.read_bytes()
+ assert after == before
+ # the generated view must contain stable IDs and no timestamps
+ text = after.decode("utf-8")
+ assert "img.filter.rank" in text
+ assert "timestamp" not in text and "commit" not in text
+
+
+def test_json_serialization_deterministic() -> None:
+ data = json.loads(LEDGER_JSON.read_text(encoding="utf-8"))
+ a = json.dumps(data, indent=2, sort_keys=True)
+ b = json.dumps(json.loads(a), indent=2, sort_keys=True)
+ assert a == b
diff --git a/tests/core/test_force_foundation.py b/tests/core/test_force_foundation.py
new file mode 100644
index 0000000..c587580
--- /dev/null
+++ b/tests/core/test_force_foundation.py
@@ -0,0 +1,558 @@
+"""Core tests for the force-spectroscopy foundation (FS-F1).
+
+Analytical, contract and metamorphic coverage for all thirteen public
+capabilities. No external fixtures are loaded here.
+"""
+
+from __future__ import annotations
+
+import math
+
+import numpy as np
+import pytest
+
+from spmkit.core.analysis import (
+ ContactPointCandidate,
+ ForceFoundationError,
+ calibrate_force_curve,
+ compute_tip_sample_separation,
+ contact_point_ensemble,
+ contact_point_piecewise,
+ contact_point_ratio_of_variances,
+ contact_point_threshold,
+ correct_force_baseline,
+ extract_force_events,
+ fit_force_baseline,
+ identify_force_segments,
+ integrate_force_work,
+ prepare_force_curve,
+ score_force_curve_quality,
+)
+from spmkit.core.models import Calibration, ForceCurve, ForceSegment
+
+N = 200
+Z = np.linspace(0.0, 5e-6, N)
+CAL = Calibration(
+ invols=3e-8, spring_constant=0.1, method="thermal", temperature=300, provenance={}
+)
+
+
+def _seg(
+ t: str,
+ d: str,
+ z: np.ndarray,
+ f: np.ndarray | None,
+ deflection: np.ndarray | None = None,
+ state: str = "force_n",
+ separation: np.ndarray | None = None,
+) -> ForceSegment:
+ return ForceSegment(
+ segment_type=t,
+ direction=d,
+ raw_height=z,
+ raw_deflection=np.zeros_like(z),
+ time=None,
+ cycle=0,
+ state=state,
+ deflection=deflection,
+ force=f,
+ separation=separation,
+ metadata={},
+ )
+
+
+def _curve(
+ offset: float = 5e-10,
+ slope: float = 1e-4,
+ noise: float = 0.0,
+ contact_fraction: float = 0.3,
+ seed: int = 0,
+) -> ForceCurve:
+ rng = np.random.default_rng(seed)
+ ci = int(round(N * contact_fraction))
+ f = offset + slope * Z
+ delta = np.maximum(0.0, Z - Z[ci])
+ f = f + 5.0 * delta**1.5
+ if noise:
+ f = f + rng.normal(0.0, noise, N)
+ fr = f[::-1].copy()
+ return ForceCurve(
+ segments=(_seg("extend", "forward", Z, f), _seg("retract", "backward", Z[::-1].copy(), fr)),
+ calibration=CAL,
+ position=None,
+ index=0,
+ metadata={},
+ )
+
+
+# ------------------------------------------------------------ segmentation ---
+
+
+def test_segmentation_trusts_labels() -> None:
+ res = identify_force_segments(_curve())
+ assert len(res.approach_indices) == N
+ assert len(res.retract_indices) == N
+ assert res.turning_point_index == N
+ assert res.diagnostics["trusted_labels"] is True
+
+
+def test_segmentation_single_segment_turning_point() -> None:
+ curve = ForceCurve(
+ segments=(_seg("extend", "forward", Z, np.ones(N) * 1e-9),),
+ calibration=CAL,
+ position=None,
+ index=0,
+ metadata={},
+ )
+ res = identify_force_segments(curve)
+ assert res.turning_point_index == int(np.argmax(Z))
+ assert res.approach_indices[-1] == res.turning_point_index
+
+
+def test_segmentation_no_reordering() -> None:
+ res = identify_force_segments(_curve())
+ assert res.approach_indices == tuple(range(N))
+ assert res.retract_indices == tuple(range(N, 2 * N))
+
+
+# ------------------------------------------------------------- calibration ---
+
+
+def test_calibration_raw_volts_to_force() -> None:
+ np.full(N, 1e-3)
+ seg = _seg("extend", "forward", Z, None, deflection=None, state="raw_v")
+ curve = ForceCurve(segments=(seg,), calibration=CAL, position=None, index=0, metadata={})
+ res = calibrate_force_curve(curve)
+ assert res.curve.segments[0].force is not None
+ expected = 1e-3 * 3e-8 * 0.1
+ assert np.allclose(res.curve.segments[0].force, expected)
+ assert res.output_units == "N"
+
+
+def test_calibration_already_calibrated_pass_through() -> None:
+ curve = _curve()
+ res = calibrate_force_curve(curve)
+ assert res.curve is not None
+ assert np.array_equal(res.curve.extend.force, curve.extend.force)
+
+
+def test_calibration_double_calibration_rejected() -> None:
+ curve = _curve()
+ with pytest.raises(ForceFoundationError) as ei:
+ calibrate_force_curve(curve, calibration=CAL)
+ assert ei.value.code == "INVALID_CALIBRATION"
+
+
+def test_calibration_missing_calibration_rejected() -> None:
+ seg = _seg("extend", "forward", Z, None, state="raw_v")
+ curve = ForceCurve(segments=(seg,), calibration=None, position=None, index=0, metadata={})
+ with pytest.raises(ForceFoundationError) as ei:
+ calibrate_force_curve(curve)
+ assert ei.value.code == "MISSING_CALIBRATION"
+
+
+def test_calibration_non_mutation() -> None:
+ curve = _curve()
+ before = curve.extend.force.copy()
+ calibrate_force_curve(curve)
+ assert np.array_equal(curve.extend.force, before)
+
+
+# ----------------------------------------------------- tip-sample separation ---
+
+
+def test_tip_sample_separation_convention() -> None:
+ f = np.full(N, 1e-9)
+ deflection = f / 0.1
+ curve = ForceCurve(
+ segments=(_seg("extend", "forward", Z, f, deflection=deflection),),
+ calibration=CAL,
+ position=None,
+ index=0,
+ metadata={},
+ )
+ sep = compute_tip_sample_separation(curve)
+ assert np.allclose(sep.extend.separation, Z - deflection)
+ assert sep is not curve
+ assert curve.extend.separation is None # input untouched
+
+
+# ---------------------------------------------------------------- baseline ---
+
+
+def test_baseline_fit_recovers_parameters() -> None:
+ curve = _curve(offset=5e-10, slope=1e-4, noise=0.0)
+ bl = fit_force_baseline(curve)
+ assert bl.model == "linear"
+ assert bl.segment == "approach"
+ assert abs(bl.intercept - 5e-10) < 5e-11
+ assert abs(bl.slope - 1e-4) < 1e-5
+ assert bl.residual_rms < 1e-12
+
+
+def test_baseline_robust_fit() -> None:
+ curve = _curve(noise=2e-11)
+ bl = fit_force_baseline(curve, robust=True)
+ assert abs(bl.intercept - 5e-10) < 5e-10
+ assert bl.robust_scale > 0.0
+
+
+def test_baseline_too_short() -> None:
+ z = np.linspace(0.0, 1e-6, 8)
+ curve = ForceCurve(
+ segments=(_seg("extend", "forward", z, np.ones(8) * 1e-9),),
+ calibration=CAL,
+ position=None,
+ index=0,
+ metadata={},
+ )
+ with pytest.raises(ForceFoundationError) as ei:
+ fit_force_baseline(curve)
+ assert ei.value.code == "BASELINE_TOO_SHORT"
+
+
+def test_baseline_correction_removes_offset_and_slope() -> None:
+ curve = _curve(offset=5e-10, slope=1e-4, noise=0.0)
+ bl = fit_force_baseline(curve)
+ corrected = correct_force_baseline(curve, bl, scope="all")
+ n_base = len(bl.sample_indices)
+ assert np.max(np.abs(corrected.extend.force[:n_base])) < 1e-12
+ assert curve.extend.force is not None
+
+
+def test_baseline_correction_scope_validation() -> None:
+ bl = fit_force_baseline(_curve())
+ with pytest.raises(ValueError):
+ correct_force_baseline(_curve(), bl, scope="nonsense")
+
+
+# ------------------------------------------------------- contact: threshold ---
+
+
+def test_contact_threshold_clean_recovery() -> None:
+ curve = _curve(offset=0.0, slope=0.0, noise=0.0)
+ cand = contact_point_threshold(curve)
+ ci = int(round(N * 0.3))
+ assert cand.valid
+ assert abs(cand.index - (ci + 1)) <= 1
+
+
+def test_contact_threshold_no_contact() -> None:
+ flat = ForceCurve(
+ segments=(
+ _seg("extend", "forward", Z, np.full(N, 1e-10)),
+ _seg("retract", "backward", Z[::-1].copy(), np.full(N, 1e-10)),
+ ),
+ calibration=CAL,
+ position=None,
+ index=0,
+ metadata={},
+ )
+ cand = contact_point_threshold(flat)
+ assert not cand.valid
+ assert cand.failure_reason == "CONTACT_NOT_FOUND"
+
+
+def test_contact_threshold_non_mutation() -> None:
+ curve = _curve()
+ before = curve.extend.force.copy()
+ contact_point_threshold(curve)
+ assert np.array_equal(curve.extend.force, before)
+
+
+# -------------------------------------------------- contact: ratio of variances ---
+
+
+def test_contact_rov_returns_candidate() -> None:
+ curve = _curve(noise=5e-11)
+ cand = contact_point_ratio_of_variances(curve)
+ assert cand.method == "ratio_of_variances"
+ assert cand.valid
+ assert 0 <= cand.index < N
+
+
+def test_contact_rov_too_short() -> None:
+ z = np.linspace(0.0, 1e-6, 20)
+ curve = ForceCurve(
+ segments=(_seg("extend", "forward", z, np.ones(20) * 1e-9),),
+ calibration=CAL,
+ position=None,
+ index=0,
+ metadata={},
+ )
+ with pytest.raises(ForceFoundationError):
+ contact_point_ratio_of_variances(curve, window=20)
+
+
+# -------------------------------------------------------- contact: piecewise ---
+
+
+def test_contact_piecewise_returns_candidate() -> None:
+ curve = _curve(noise=0.0)
+ cand = contact_point_piecewise(curve)
+ assert cand.method == "piecewise"
+ assert cand.valid
+ assert 0 <= cand.index < N
+
+
+# --------------------------------------------------------- contact: ensemble ---
+
+
+def test_contact_ensemble_combines_methods() -> None:
+ curve = _curve(noise=5e-11)
+ res = contact_point_ensemble(curve)
+ assert res.method_agreement >= 2
+ assert len(res.candidates) == 3
+ assert 0 <= res.selected.index < N
+
+
+def test_contact_ensemble_bootstrap_deterministic() -> None:
+ curve = _curve(noise=5e-11)
+ a = contact_point_ensemble(curve, bootstrap_samples=50)
+ b = contact_point_ensemble(curve, bootstrap_samples=50)
+ assert a.bootstrap_interval == b.bootstrap_interval
+
+
+def test_contact_ensemble_insufficient_agreement() -> None:
+ flat = ForceCurve(
+ segments=(
+ _seg("extend", "forward", Z, np.full(N, 1e-10)),
+ _seg("retract", "backward", Z[::-1].copy(), np.full(N, 1e-10)),
+ ),
+ calibration=CAL,
+ position=None,
+ index=0,
+ metadata={},
+ )
+ with pytest.raises(ForceFoundationError) as ei:
+ contact_point_ensemble(flat)
+ assert ei.value.code == "CONTACT_METHOD_DISAGREEMENT"
+
+
+# ------------------------------------------------------------------- events ---
+
+
+def test_events_snap_in_and_pull_off() -> None:
+ curve = _curve(noise=0.0)
+ int(round(N * 0.3))
+ force = curve.extend.force.copy()
+ force[40] = force[40] - 3e-10
+ seg = _seg("extend", "forward", Z, force)
+ curve2 = ForceCurve(
+ segments=(seg, curve.retract), calibration=CAL, position=None, index=0, metadata={}
+ )
+ cand = contact_point_threshold(curve2)
+ events = extract_force_events(curve2, cand)
+ assert events.snap_in_index == 40
+ assert events.pull_off_index is not None
+
+
+def test_events_absent_retract() -> None:
+ curve = ForceCurve(
+ segments=(_seg("extend", "forward", Z, np.ones(N) * 1e-9),),
+ calibration=CAL,
+ position=None,
+ index=0,
+ metadata={},
+ )
+ cand = contact_point_threshold(curve)
+ events = extract_force_events(curve, cand)
+ assert not events.valid
+ assert "EVENT_NOT_FOUND" in events.warnings
+
+
+# ---------------------------------------------------------------------- work ---
+
+
+def test_work_integration_and_units() -> None:
+ curve = _curve(noise=0.0)
+ cand = contact_point_threshold(curve)
+ res = integrate_force_work(curve, cand, domain="tip_position")
+ assert res.units == "J"
+ assert res.valid
+ assert res.work_approach > 0.0
+ assert res.hysteresis >= 0.0
+
+
+def test_work_nonmonotonic_coordinate() -> None:
+ z = Z.copy()
+ z[100], z[99] = z[99], z[100]
+ curve = ForceCurve(
+ segments=(
+ _seg("extend", "forward", z, np.ones(N) * 1e-9),
+ _seg("retract", "backward", Z[::-1].copy(), np.ones(N) * 1e-9),
+ ),
+ calibration=CAL,
+ position=None,
+ index=0,
+ metadata={},
+ )
+ cand = ContactPointCandidate(
+ method="threshold", index=50, coordinate=float(z[50]), score=1.0, valid=True
+ )
+ with pytest.raises(ForceFoundationError) as ei:
+ integrate_force_work(curve, cand)
+ assert ei.value.code == "NONMONOTONIC_COORDINATE"
+
+
+# ----------------------------------------------------------------------- QC ---
+
+
+def test_quality_typed_reasons() -> None:
+ curve = _curve(noise=2e-11)
+ bl = fit_force_baseline(curve)
+ contact = contact_point_ensemble(curve)
+ events = extract_force_events(curve, contact)
+ q = score_force_curve_quality(curve, baseline=bl, contact=contact, events=events)
+ assert 0.0 <= q.summary_score <= 1.0
+ assert q.eligible
+ assert "MISSING_CALIBRATION" not in q.failure_reasons
+
+
+def test_quality_missing_calibration() -> None:
+ seg = _seg("extend", "forward", Z, None, state="raw_v")
+ curve = ForceCurve(segments=(seg,), calibration=None, position=None, index=0, metadata={})
+ q = score_force_curve_quality(curve)
+ assert "MISSING_CALIBRATION" in q.failure_reasons
+ assert not q.eligible
+
+
+def test_quality_nonfinite() -> None:
+ f = np.ones(N) * 1e-9
+ f[50] = np.nan
+ curve = ForceCurve(
+ segments=(_seg("extend", "forward", Z, f),),
+ calibration=CAL,
+ position=None,
+ index=0,
+ metadata={},
+ )
+ q = score_force_curve_quality(curve)
+ assert "NONFINITE_DATA" in q.failure_reasons
+
+
+# ---------------------------------------------------------------- prepare ---
+
+
+def test_prepare_full_pipeline() -> None:
+ curve = _curve(noise=2e-11)
+ res = prepare_force_curve(curve)
+ assert res.segmentation.turning_point_index == N
+ assert res.calibration.output_units == "N"
+ assert res.separation.extend.separation is not None
+ assert res.baseline.model == "linear"
+ assert res.contact.method_agreement >= 2
+ assert res.work.units == "J"
+ assert "pipeline" in res.provenance
+ assert len(res.provenance["pipeline"]) == 9
+
+
+def test_prepare_uses_core_primitives_only() -> None:
+ # the orchestrator exposes every decision in provenance; no hidden choice
+ curve = _curve(noise=2e-11)
+ res = prepare_force_curve(curve)
+ assert res.provenance["calibration"]["source"] in ("explicit", "curve metadata")
+ assert "contact" in res.provenance
+ assert "work" in res.provenance
+
+
+# ------------------------------------------------------------ metamorphic ---
+
+
+def test_metamorphic_force_scaling_with_k() -> None:
+ curve = _curve()
+ res1 = integrate_force_work(curve, contact_point_threshold(curve))
+ cal2 = Calibration(
+ invols=3e-8, spring_constant=0.2, method="thermal", temperature=300, provenance={}
+ )
+ f2 = curve.extend.force * 2.0
+ curve2 = ForceCurve(
+ segments=(
+ _seg("extend", "forward", Z, f2),
+ _seg("retract", "backward", Z[::-1].copy(), f2[::-1].copy()),
+ ),
+ calibration=cal2,
+ position=None,
+ index=0,
+ metadata={},
+ )
+ res2 = integrate_force_work(curve2, contact_point_threshold(curve2))
+ assert math.isclose(res2.work_approach, 2.0 * res1.work_approach, rel_tol=1e-9)
+
+
+def test_metamorphic_baseline_offset_invariance() -> None:
+ base = _curve(offset=1e-10, slope=0.0, noise=0.0)
+ shifted = _curve(offset=5e-10, slope=0.0, noise=0.0)
+ bl = fit_force_baseline(shifted)
+ corrected = correct_force_baseline(shifted, bl)
+ assert np.allclose(corrected.extend.force, base.extend.force - 1e-10, atol=1e-12)
+
+
+def test_metamorphic_work_scaling() -> None:
+ curve = _curve(noise=0.0)
+ cand = contact_point_threshold(curve)
+ w1 = integrate_force_work(curve, cand).work_approach
+ f2 = curve.extend.force * 3.0
+ curve2 = ForceCurve(
+ segments=(
+ _seg("extend", "forward", Z, f2),
+ _seg("retract", "backward", Z[::-1].copy(), (curve.retract.force * 3.0)[::-1].copy()),
+ ),
+ calibration=CAL,
+ position=None,
+ index=0,
+ metadata={},
+ )
+ w2 = integrate_force_work(curve2, contact_point_threshold(curve2)).work_approach
+ assert math.isclose(w2, 3.0 * w1, rel_tol=1e-9)
+
+
+def test_metamorphic_event_window_restriction() -> None:
+ curve = _curve(noise=0.0)
+ int(round(N * 0.3))
+ force = curve.extend.force.copy()
+ force[40] = force[40] - 3e-10
+ curve2 = ForceCurve(
+ segments=(_seg("extend", "forward", Z, force), curve.retract),
+ calibration=CAL,
+ position=None,
+ index=0,
+ metadata={},
+ )
+ cand = contact_point_threshold(curve2)
+ window = (float(Z[30]), float(Z[50]))
+ events = extract_force_events(curve2, cand, snap_in_window=window)
+ if events.snap_in_index is not None:
+ assert window[0] <= Z[events.snap_in_index] <= window[1]
+ # out-of-window search must not find the event at 40
+ far = extract_force_events(curve2, cand, snap_in_window=(float(Z[5]), float(Z[15])))
+ assert far.snap_in_index is None or not (5 <= far.snap_in_index <= 15)
+
+
+def test_metamorphic_ensemble_permutation_invariance() -> None:
+ curve = _curve(noise=2e-11)
+ a = contact_point_ensemble(curve)
+ b = contact_point_ensemble(curve)
+ assert a.selected.index == b.selected.index
+ assert [c.method for c in a.candidates] == [c.method for c in b.candidates]
+
+
+# ------------------------------------------------------ common validation ---
+
+
+def test_invalid_input_types() -> None:
+ with pytest.raises(TypeError):
+ calibrate_force_curve("not a curve") # type: ignore[arg-type]
+ with pytest.raises(ValueError):
+ identify_force_segments(_curve(), method="nonsense")
+
+
+def test_result_serialization() -> None:
+ curve = _curve(noise=2e-11)
+ res = prepare_force_curve(curve)
+ import pickle
+
+ blob = pickle.dumps(res)
+ res2 = pickle.loads(blob)
+ assert res2.contact.selected.index == res.contact.selected.index
+ assert res2.provenance == res.provenance
diff --git a/tests/core/test_force_mechanics.py b/tests/core/test_force_mechanics.py
new file mode 100644
index 0000000..7006ef5
--- /dev/null
+++ b/tests/core/test_force_mechanics.py
@@ -0,0 +1,371 @@
+"""FS-F2 core tests: contracts, equations, failures, determinism.
+
+No fixtures loaded.
+"""
+
+from __future__ import annotations
+
+import math
+import pickle
+
+import numpy as np
+import pytest
+
+from spmkit.core.analysis import (
+ prepare_force_curve,
+)
+from spmkit.core.analysis.force_mechanics import (
+ ForceMechanicsError,
+ analyze_force_fit_sensitivity,
+ bootstrap_force_fit,
+ compare_contact_models,
+ compute_indentation,
+ diagnose_force_fit,
+ fit_dmt,
+ fit_flat_punch,
+ fit_hertz_sphere,
+ fit_jkr,
+ fit_sneddon_cone,
+ forward_model,
+ select_contact_fit_window,
+)
+from spmkit.core.models import Calibration, ForceCurve, ForceSegment
+
+N = 200
+K = 10.0
+ZC = 3e-6
+DELTA = np.linspace(0.0, 1e-6, N)
+
+
+def _model_curve(model: str = "hertz_sphere",
+ params: dict | None = None, noise: float = 0.0) -> ForceCurve:
+ if params is None:
+ params = {"E": 5e3, "R": 1e-6, "poisson": 0.3}
+ rng = np.random.default_rng(0)
+ # FS-F1 trace convention: separation increases along the trace; the
+ # contact is at sep = ZC; the indentation branch delta = max(0, sep-ZC);
+ # height = separation + force/K stays strictly increasing because the
+ # deflection grows slower than the piezo motion (indentation regime).
+ sep = np.linspace(ZC - 2.0e-6, ZC + 1e-6, N)
+ delta = np.maximum(0.0, sep - ZC)
+ # zero force pre-contact: adhesive models are negative at delta == 0
+ force = np.where(delta > 0.0, forward_model(model, delta, params), 0.0)
+ if noise:
+ force = force + rng.normal(0.0, noise, N)
+ height = sep + force / K
+ def seg(t, d, z, f):
+ return ForceSegment(segment_type=t, direction=d, raw_height=z,
+ raw_deflection=f / K, time=None, cycle=0, state="force_n",
+ deflection=f / K, force=f, separation=None, metadata={})
+ fr = force[::-1].copy()
+ return ForceCurve(segments=(seg("extend", "forward", height, force),
+ seg("retract", "backward", height[::-1].copy(), fr)),
+ calibration=Calibration(invols=3e-8, spring_constant=K,
+ method="thermal", temperature=300,
+ provenance={}),
+ position=None, index=0, metadata={})
+
+
+def _prepared(model: str = "hertz_sphere", noise: float = 0.0,
+ params: dict | None = None):
+ if params is None:
+ params = {"E": 5e3, "R": 1e-6, "poisson": 0.3}
+ return prepare_force_curve(_model_curve(model, params, noise))
+
+
+def test_forward_equations_match_literature() -> None:
+ est = 5e3 / (1 - 0.3**2)
+ d = np.array([1e-7, 5e-7, 1e-6])
+ assert np.allclose(forward_model("hertz_sphere", d, {"E": 5e3, "R": 1e-6, "poisson": 0.3}),
+ (4/3) * est * math.sqrt(1e-6) * d**1.5)
+ assert np.allclose(
+ forward_model("sneddon_cone", d, {"E": 5e3, "alpha": math.radians(20), "poisson": 0.3}),
+ (2 * math.tan(math.radians(20)) / math.pi) * est * d**2)
+ assert np.allclose(forward_model("flat_punch", d, {"E": 5e3, "R": 1e-6, "poisson": 0.3}),
+ 2 * est * 1e-6 * d)
+ assert np.allclose(
+ forward_model("dmt", d, {"E": 5e3, "R": 1e-6, "poisson": 0.3, "F_adh": 2e-9}),
+ (4/3) * est * math.sqrt(1e-6) * d**1.5 - 2e-9)
+
+
+def test_jkr_reduces_to_hertz() -> None:
+ d = np.linspace(1e-8, 1e-6, 50)
+ j = forward_model("jkr", d, {"E": 5e3, "R": 1e-6, "poisson": 0.3, "w": 0.0})
+ h = forward_model("hertz_sphere", d, {"E": 5e3, "R": 1e-6, "poisson": 0.3})
+ assert np.allclose(j, h, rtol=1e-6)
+
+
+def test_compute_indentation_contract() -> None:
+ prepared = _prepared()
+ ind = compute_indentation(prepared)
+ assert ind.units == "m"
+ # pre-contact samples are excluded by the mask; the valid branch is
+ # the indentation (positive into the sample)
+ assert np.all(ind.indentation[ind.valid] >= -1e-12)
+ assert np.any(ind.indentation[~ind.valid] < 0.0)
+ assert ind.valid.sum() > 0
+ assert ind.contact_index == prepared.contact.selected.index
+
+
+def test_fit_window_contract() -> None:
+ prepared = _prepared()
+ ind = compute_indentation(prepared)
+ window = select_contact_fit_window(prepared, ind, min_indentation=0.0,
+ min_points=10)
+ assert window.n_points >= 10
+ assert window.included.sum() == window.n_points
+ with pytest.raises(ForceMechanicsError):
+ select_contact_fit_window(prepared, ind, max_indentation=-1e-9)
+ with pytest.raises(ForceMechanicsError):
+ select_contact_fit_window(prepared, ind, min_points=10**6)
+
+
+@pytest.mark.parametrize("model,fit_fn,kwargs,truth_key", [
+ ("hertz_sphere", fit_hertz_sphere, {"tip_radius": 1e-6}, "E"),
+ ("sneddon_cone", fit_sneddon_cone, {"half_angle": math.radians(20.0)}, "E"),
+ ("flat_punch", fit_flat_punch, {"punch_radius": 1e-6}, "E"),
+])
+def test_clean_parameter_recovery(model, fit_fn, kwargs, truth_key) -> None:
+ params = {"E": 5e3, "R": 1e-6, "poisson": 0.3, "alpha": math.radians(20.0)}
+ if model == "sneddon_cone":
+ params = {"E": 5e3, "alpha": math.radians(20.0), "poisson": 0.3}
+ prepared = _prepared(model, 0.0, params)
+ ind = compute_indentation(prepared)
+ window = select_contact_fit_window(prepared, ind, min_indentation=0.0, min_points=10)
+ fit = fit_fn(prepared, ind, window, **kwargs)
+ assert fit.success
+ # E is recovered within a few percent: the FS-F1 contact point lands
+ # within ~1 sample of the truth (1.5e-8 m), and the indentation-axis
+ # offset biases the stiffness by ~1% on clean data
+ assert abs(fit.parameters["E"] - 5e3) / 5e3 < 0.05
+
+
+def test_dmt_clean_recovery() -> None:
+ params = {"E": 5e3, "R": 1e-6, "poisson": 0.3, "F_adh": 2e-9}
+ prepared = _prepared("dmt", 0.0, params)
+ ind = compute_indentation(prepared)
+ # the window trims the snap-in/pre-contact region: the FS-F1 contact
+ # ensemble lands up to ~10 samples off on snap-in curves, which biases
+ # adhesive-model parameters (documented limitation of this batch)
+ window = select_contact_fit_window(prepared, ind, min_indentation=1.8e-7,
+ min_points=10)
+ fit = fit_dmt(prepared, ind, window, tip_radius=1e-6)
+ assert abs(fit.parameters["E"] - 5e3) / 5e3 < 0.3
+ assert abs(fit.parameters["F_adh"] - 2e-9) < 1.5e-9
+
+
+def test_jkr_clean_recovery() -> None:
+ params = {"E": 5e3, "R": 1e-6, "poisson": 0.3, "w": 1e-3}
+ prepared = _prepared("jkr", 0.0, params)
+ ind = compute_indentation(prepared)
+ # same snap-in trim as the DMT case (see test_dmt_clean_recovery)
+ window = select_contact_fit_window(prepared, ind, min_indentation=2e-7,
+ min_points=10)
+ fit = fit_jkr(prepared, ind, window, tip_radius=1e-6)
+ assert fit.success
+ assert abs(fit.parameters["E"] - 5e3) / 5e3 < 0.2
+ assert abs(fit.parameters["w"] - 1e-3) / 1e-3 < 0.3
+
+
+def test_invalid_geometry_typed_failures() -> None:
+ prepared = _prepared()
+ ind = compute_indentation(prepared)
+ window = select_contact_fit_window(prepared, ind, min_points=10)
+ with pytest.raises(ForceMechanicsError) as ei:
+ fit_hertz_sphere(prepared, ind, window, tip_radius=-1e-6)
+ assert ei.value.code == "INVALID_RADIUS"
+ with pytest.raises(ForceMechanicsError) as ei:
+ fit_sneddon_cone(prepared, ind, window, half_angle=math.pi / 2)
+ assert ei.value.code == "INVALID_ANGLE"
+ with pytest.raises(ForceMechanicsError) as ei:
+ fit_hertz_sphere(prepared, ind, window, tip_radius=1e-6, poisson=0.6)
+ assert ei.value.code == "INVALID_POISSON_RATIO"
+ with pytest.raises(ForceMechanicsError) as ei:
+ fit_dmt(prepared, ind, window, tip_radius=1e-6, F_adh_initial=-1e-9)
+ assert ei.value.code == "INVALID_ADHESION_PARAMETER"
+
+
+def test_non_mutation_and_independent_outputs() -> None:
+ prepared = _prepared(noise=1e-12)
+ force_before = prepared.curve.extend.force.copy()
+ ind = compute_indentation(prepared)
+ window = select_contact_fit_window(prepared, ind, min_points=10)
+ fit = fit_hertz_sphere(prepared, ind, window, tip_radius=1e-6)
+ assert np.array_equal(prepared.curve.extend.force, force_before)
+ # residuals are independent storage: mutating them does not touch the
+ # input force or the predicted force
+ fit.residuals[0] = 123.0 # type: ignore[index]
+ assert prepared.curve.extend.force[window.start_index] != 123.0
+ assert fit.predicted_force[0] != 123.0
+
+
+def test_result_serialization() -> None:
+ prepared = _prepared(noise=1e-12)
+ ind = compute_indentation(prepared)
+ window = select_contact_fit_window(prepared, ind, min_points=10)
+ fit = fit_hertz_sphere(prepared, ind, window, tip_radius=1e-6)
+ blob = pickle.dumps(fit)
+ fit2 = pickle.loads(blob)
+ assert fit2.model == fit.model
+ assert fit2.parameters == fit.parameters
+
+
+def test_model_comparison_no_physical_truth() -> None:
+ prepared = _prepared("hertz_sphere", 1e-12)
+ ind = compute_indentation(prepared)
+ window = select_contact_fit_window(prepared, ind, min_points=10)
+ cmp = compare_contact_models(prepared, ind, window, tip_radius=1e-6)
+ assert cmp.n_compared == len(cmp.fits)
+ assert all(0.0 <= w <= 1.0 for w in cmp.weights.values())
+ # comparison is model-relative: no physical-truth claim is made
+ assert "physical" not in cmp.provenance
+ assert cmp.provenance.get("criterion") in ("aicc", "aic", "bic")
+
+
+def test_comparison_retains_failed_candidate() -> None:
+ """A candidate whose geometry validation fails is retained as a
+ warning and excluded from the ranking; the comparison still returns."""
+ prepared = _prepared("hertz_sphere", 1e-12)
+ ind = compute_indentation(prepared)
+ window = select_contact_fit_window(prepared, ind, min_points=10)
+ cmp = compare_contact_models(prepared, ind, window, tip_radius=1e-6,
+ models=("hertz_sphere", "sneddon_cone"),
+ half_angle=math.pi / 2)
+ assert cmp.n_compared == 1
+ assert any("INVALID_ANGLE" in w for w in cmp.warnings)
+ assert cmp.recommended_model == "hertz_sphere"
+
+
+def test_sensitivity_deterministic_and_bounded() -> None:
+ prepared = _prepared("hertz_sphere", 1e-12)
+ a = analyze_force_fit_sensitivity(prepared, tip_radius=1e-6)
+ b = analyze_force_fit_sensitivity(prepared, tip_radius=1e-6)
+ assert a.n_configurations == b.n_configurations
+ assert a.parameter_multiverse == b.parameter_multiverse
+ assert a.n_configurations <= 512
+
+
+def test_multiverse_max_configuration_guard() -> None:
+ """Configurations beyond the guard are counted as skipped, never
+ silently dropped or run."""
+ prepared = _prepared("hertz_sphere", 1e-12)
+ sens = analyze_force_fit_sensitivity(
+ prepared, contact_offsets=tuple(range(-10, 11)),
+ fit_window_variants=(0.0, 0.1, 0.2), max_configurations=8,
+ tip_radius=1e-6)
+ assert sens.n_configurations + len(sens.failures) <= 8
+ assert sens.n_skipped > 0
+
+
+def test_sensitivity_multiverse_covers_contact_branch() -> None:
+ """The multiverse fits the contact branch (same convention as
+ compute_indentation): every configuration's modulus stays close to the
+ base fit, and the one-at-a-time indices are small on a clean curve."""
+ prepared = _prepared("hertz_sphere", 1e-12)
+ ind = compute_indentation(prepared)
+ window = select_contact_fit_window(prepared, ind, min_indentation=0.0, min_points=10)
+ base = fit_hertz_sphere(prepared, ind, window, tip_radius=1e-6)
+ sens = analyze_force_fit_sensitivity(prepared, tip_radius=1e-6)
+ assert sens.n_configurations > 0
+ for p in sens.parameter_multiverse:
+ assert np.isfinite(p["E"])
+ assert abs(p["E"] - base.parameters["E"]) / base.parameters["E"] < 0.30
+ # clean curve: neither contact nor window sensitivity is high
+ assert sens.dominant_sensitivity in ("none", "contact", "window")
+ assert sens.contact_sensitivity < 0.2
+ assert sens.window_sensitivity < 0.2
+
+
+def test_bootstrap_block_residual_robust_window_length() -> None:
+ """The block-residual strategy runs deterministically even when the
+ window length is not a multiple of the block size (regression: the
+ reshape previously raised an untyped ValueError)."""
+ prepared = _prepared("hertz_sphere", 1e-12)
+ ind = compute_indentation(prepared)
+ # trim so the window length is not a multiple of the block size (5)
+ window = select_contact_fit_window(prepared, ind, min_indentation=5e-8,
+ min_points=10)
+ assert window.n_points % 5 != 0, "window length must not be a block multiple"
+ a = bootstrap_force_fit((prepared, ind, window, "hertz_sphere"),
+ samples=12, seed=3, strategy="block_residual",
+ tip_radius=1e-6)
+ b = bootstrap_force_fit((prepared, ind, window, "hertz_sphere"),
+ samples=12, seed=3, strategy="block_residual",
+ tip_radius=1e-6)
+ assert a.parameter_samples == b.parameter_samples
+ assert a.n_success >= 10
+
+
+def test_diagnose_computes_covariance_metrics() -> None:
+ """The diagnostic's condition number and parameter-correlation metrics
+ are computed from the fit covariance, not placeholder zeros."""
+ prepared = _prepared("hertz_sphere", 1e-12)
+ ind = compute_indentation(prepared)
+ window = select_contact_fit_window(prepared, ind, min_indentation=0.0,
+ min_points=10)
+ fit = fit_hertz_sphere(prepared, ind, window, tip_radius=1e-6)
+ diag = diagnose_force_fit(fit)
+ assert diag.condition_metric > 0.0
+ assert 0.0 <= diag.parameter_correlation_max <= 1.0
+
+
+def test_bootstrap_deterministic_replay() -> None:
+ prepared = _prepared("hertz_sphere", 1e-12)
+ ind = compute_indentation(prepared)
+ window = select_contact_fit_window(prepared, ind, min_points=10)
+ a = bootstrap_force_fit((prepared, ind, window, "hertz_sphere"),
+ samples=50, seed=7, tip_radius=1e-6)
+ b = bootstrap_force_fit((prepared, ind, window, "hertz_sphere"),
+ samples=50, seed=7, tip_radius=1e-6)
+ assert a.parameter_samples == b.parameter_samples
+ assert a.percentile_intervals == b.percentile_intervals
+
+
+def test_bootstrap_insufficient_success() -> None:
+ prepared = _prepared("hertz_sphere", 1e-9)
+ ind = compute_indentation(prepared)
+ window = select_contact_fit_window(prepared, ind, min_points=10)
+ # an impossible success fraction is rejected deterministically
+ with pytest.raises(ForceMechanicsError) as ei:
+ bootstrap_force_fit((prepared, ind, window, "hertz_sphere"),
+ samples=20, seed=0, tip_radius=1e-6,
+ min_success_fraction=1.5)
+ assert ei.value.code == "BOOTSTRAP_INSUFFICIENT_SUCCESS"
+
+
+def test_diagnose_returns_policy_status() -> None:
+ prepared = _prepared("hertz_sphere", 1e-12)
+ ind = compute_indentation(prepared)
+ window = select_contact_fit_window(prepared, ind, min_points=10)
+ fit = fit_hertz_sphere(prepared, ind, window, tip_radius=1e-6)
+ sens = analyze_force_fit_sensitivity(prepared, tip_radius=1e-6)
+ diag = diagnose_force_fit(fit, sensitivity=sens)
+ assert diag.summary_status in ("ok", "review")
+ assert isinstance(diag.residual_rms, float)
+
+
+def test_fit_requires_prepared_input() -> None:
+ with pytest.raises(TypeError):
+ compute_indentation("not prepared") # type: ignore[arg-type]
+ from spmkit.core.analysis.force_mechanics_errors import ForceMechanicsError
+
+ # a raw curve without preparation is not fit-eligible
+ curve = _model_curve()
+ with pytest.raises(ForceMechanicsError) as ei:
+ compute_indentation(_unprepared_placeholder(curve)) # type: ignore[arg-type]
+ assert ei.value.code == "CURVE_NOT_FIT_ELIGIBLE"
+
+
+def _unprepared_placeholder(curve):
+ """A minimal prepared-like object whose quality is not eligible."""
+ from spmkit.core.analysis import score_force_curve_quality
+ from spmkit.core.analysis.force_prepare import ForcePreparationResult
+ from spmkit.core.analysis.force_preprocessing import identify_force_segments
+
+ q = score_force_curve_quality(curve)
+ seg = identify_force_segments(curve)
+ return ForcePreparationResult(
+ curve=curve, segmentation=seg, calibration=None, # type: ignore[arg-type]
+ separation=curve, baseline=None, baseline_corrected=curve,
+ contact=None, events=None, work=None, quality=q, provenance={},
+ )
diff --git a/tests/core/test_force_path_work.py b/tests/core/test_force_path_work.py
new file mode 100644
index 0000000..2772885
--- /dev/null
+++ b/tests/core/test_force_path_work.py
@@ -0,0 +1,421 @@
+"""FS-R1C: acquisition-path force work and coordinate diagnostics tests.
+
+Includes an **independent oracle** (a plain accumulation loop, no production
+imports, no NumPy trapezoid helpers) used to compute expected path-work
+values for deterministic cases, plus metamorphic checks and real-data
+harness rules.
+
+Case accounting: the 18-case oracle contract maps to 14 parametrized
+deterministic cases + 4 typed-failure cases (nonfinite coordinate, nonfinite
+force, unequal lengths, fewer than two samples) — see ``_CASES`` and the
+failure tests below.
+"""
+
+from __future__ import annotations
+
+import pickle
+
+import numpy as np
+import pytest
+
+from spmkit.core.analysis import (
+ ForceFoundationError,
+ ForcePathWorkResult,
+ coordinate_path_diagnostics,
+ integrate_force_path_work,
+ integrate_force_work,
+)
+from spmkit.core.analysis.force_foundation_errors import (
+ INSUFFICIENT_SAMPLES,
+ LENGTH_MISMATCH,
+ NONFINITE_DATA,
+)
+from spmkit.core.models import ForceCurve, ForceSegment
+
+# ---------------------------------------------------------------------------
+# Independent oracle: W = sum_i 0.5*(F_i + F_{i+1})*(z_{i+1} - z_i), plain loop
+# ---------------------------------------------------------------------------
+
+
+def oracle_path_work(z: np.ndarray, f: np.ndarray) -> float:
+ """Independent trapezoidal path-work oracle (acquisition order, signed)."""
+ work = 0.0
+ for i in range(len(z) - 1):
+ work += ((f[i] + f[i + 1]) / 2.0) * (z[i + 1] - z[i])
+ return float(work)
+
+
+def test_oracle_analytic_cases() -> None:
+ """Sanity of the oracle itself against analytic integrals."""
+ z = np.linspace(0.0, 4.0, 1001)
+ assert oracle_path_work(z, np.full_like(z, 3.0)) == pytest.approx(12.0, rel=1e-12)
+ assert oracle_path_work(z, z) == pytest.approx(8.0, rel=1e-12)
+ assert oracle_path_work(z, 2.0 * z + 1.0) == pytest.approx(20.0, rel=1e-12)
+
+
+def test_hand_calculated_witness() -> None:
+ """Tercer testigo a mano: W = sum_i 0.5*(F_i+F_{i+1})*dz_i calculado a lápiz.
+
+ z = [0, 1, 2, 1.5, 3], f = [1, 1, 1, 1, 1] (fuerza constante 1 N):
+ paso 0: 0.5*(1+1)*(1) = 1.0
+ paso 1: 0.5*(1+1)*(1) = 1.0
+ paso 2: 0.5*(1+1)*(-0.5) = -0.5
+ paso 3: 0.5*(1+1)*(1.5) = 1.5
+ W = 1.0 + 1.0 - 0.5 + 1.5 = 3.0 J (fuerza constante -> W = net displacement = 3.0)
+ """
+ z = np.array([0.0, 1.0, 2.0, 1.5, 3.0])
+ f = np.ones(5)
+ r = integrate_force_path_work(z, f)
+ assert r.work_total == pytest.approx(3.0, abs=1e-15)
+ # con fuerza constante, W == net displacement exactamente (incl. reversiones)
+ assert r.work_total == pytest.approx(r.diagnostics.net_displacement, abs=1e-15)
+
+# ---------------------------------------------------------------------------
+# Deterministic path-work cases (1-14) vs oracle
+# ---------------------------------------------------------------------------
+
+_CASES = {
+ "monotonic_increasing": (
+ np.array([0.0, 1.0, 2.0, 3.0, 4.0]),
+ np.array([0.0, 1.0, 2.0, 3.0, 4.0]),
+ ),
+ "monotonic_decreasing": (
+ np.array([4.0, 3.0, 2.0, 1.0, 0.0]),
+ np.array([4.0, 3.0, 2.0, 1.0, 0.0]),
+ ),
+ "constant_force": (
+ np.array([0.0, 1.0, 2.0, 3.0, 4.0]),
+ np.full(5, 3.0),
+ ),
+ "linear_force": (
+ np.array([0.0, 1.0, 2.0, 3.0, 4.0]),
+ 2.0 * np.array([0.0, 1.0, 2.0, 3.0, 4.0]) + 1.0,
+ ),
+ "repeated_coordinate_plateau": (
+ np.array([0.0, 1.0, 1.0, 1.0, 2.0]),
+ np.array([1.0, 2.0, 3.0, 4.0, 5.0]),
+ ),
+ "small_local_reversal": (
+ np.array([0.0, 1.0, 2.0, 1.9, 3.0]),
+ np.array([0.0, 1.0, 2.0, 1.95, 3.0]),
+ ),
+ "alternating_jitter": (
+ np.array([0.0, 1.0, 0.9, 1.1, 1.0, 2.0]),
+ np.array([0.0, 1.0, 0.95, 1.05, 1.0, 2.0]),
+ ),
+ "triangular_forward_backward": (
+ np.array([0.0, 1.0, 2.0, 1.0, 0.0]),
+ np.array([0.0, 1.0, 2.0, 1.0, 0.0]),
+ ),
+ "closed_hysteresis_loop": (
+ np.array([0.0, 1.0, 2.0, 1.0, 0.0]),
+ # fuerza distinta en la rama de retorno -> trabajo de lazo no nulo
+ np.array([0.0, 0.0, 5.0, 1.0, 0.0]),
+ ),
+ "globally_directed_backtracking": (
+ np.array([0.0, 1.0, 2.0, 1.5, 2.5, 2.0, 3.0]),
+ np.array([0.0, 0.5, 1.0, 0.8, 1.2, 1.0, 1.5]),
+ ),
+ "zero_net_displacement": (
+ np.array([0.0, 1.0, 2.0, 1.0, 0.0]),
+ np.full(5, 1.0),
+ ),
+ "coordinate_translation": (
+ np.array([5.0, 6.0, 7.0, 8.0, 9.0]),
+ np.array([0.0, 1.0, 2.0, 3.0, 4.0]),
+ ),
+ "reversed_acquisition_order": (
+ np.array([4.0, 3.0, 2.0, 1.0, 0.0]),
+ np.array([4.0, 3.0, 2.0, 1.0, 0.0]),
+ ),
+ "inserted_collinear_samples": (
+ np.array([0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0]),
+ np.array([0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0]),
+ ),
+}
+
+
+@pytest.mark.parametrize("name", sorted(_CASES))
+def test_path_work_matches_independent_oracle(name: str) -> None:
+ z, f = _CASES[name]
+ expected = oracle_path_work(z, f)
+ result = integrate_force_path_work(z, f)
+ assert result.work_total == pytest.approx(expected, rel=1e-12, abs=1e-15)
+ # la descomposición suma exactamente al total
+ assert result.work_total == pytest.approx(result.work_forward + result.work_backward, abs=1e-12)
+ # la tolerancia de clasificación nunca altera la integral
+ tol_result = integrate_force_path_work(z, f, classification_tolerance=1e-6)
+ assert tol_result.work_total == result.work_total
+
+
+# ---------------------------------------------------------------------------
+# Metamorphic properties
+# ---------------------------------------------------------------------------
+
+
+def test_translation_invariance() -> None:
+ z, f = _CASES["globally_directed_backtracking"]
+ a = integrate_force_path_work(z, f)
+ b = integrate_force_path_work(z + 100.0, f)
+ assert a.work_total == pytest.approx(b.work_total, abs=1e-12)
+
+
+def test_acquisition_reversal_flips_sign() -> None:
+ z, f = _CASES["globally_directed_backtracking"]
+ fwd = integrate_force_path_work(z, f)
+ bwd = integrate_force_path_work(z[::-1], f[::-1])
+ assert fwd.work_total == pytest.approx(-bwd.work_total, rel=1e-12, abs=1e-15)
+
+
+def test_collinear_insertion_preserves_work() -> None:
+ a = integrate_force_path_work(*_CASES["monotonic_increasing"])
+ b = integrate_force_path_work(*_CASES["inserted_collinear_samples"])
+ assert a.work_total == pytest.approx(b.work_total, rel=1e-12, abs=1e-15)
+
+
+def test_force_scaling_scales_work() -> None:
+ z, f = _CASES["globally_directed_backtracking"]
+ base = integrate_force_path_work(z, f)
+ scaled = integrate_force_path_work(z, 3.0 * f)
+ assert scaled.work_total == pytest.approx(3.0 * base.work_total, rel=1e-12, abs=1e-15)
+
+
+def test_coordinate_scaling_scales_work() -> None:
+ z, f = _CASES["globally_directed_backtracking"]
+ base = integrate_force_path_work(z, f)
+ scaled = integrate_force_path_work(2.0 * z, f)
+ assert scaled.work_total == pytest.approx(2.0 * base.work_total, rel=1e-12, abs=1e-15)
+
+
+def test_closed_loop_work_not_forced_to_zero() -> None:
+ """Un lazo cerrado de histéresis NO devuelve 0 por construcción."""
+ z, f = _CASES["closed_hysteresis_loop"]
+ r = integrate_force_path_work(z, f)
+ assert abs(r.work_total) > 1e-12
+ assert r.diagnostics.global_direction == "closed_or_ambiguous"
+
+
+def test_monotonic_path_matches_strict_integrate_force_work() -> None:
+ """Sobre una rama estrictamente monótona con fuerza lineal en z, el path
+ work (orden de adquisición decreciente) coincide en magnitud con la
+ integral estricta orientada a coordenada creciente (signo opuesto)."""
+ from spmkit.core.analysis.force_contact import ContactPointCandidate
+
+ z_a = np.linspace(10.0e-6, 2.0e-6, 200) # approach decreciente
+ f_a = 0.02 * (10.0e-6 - z_a) # lineal en z -> trapezoide exacto en cualquier grilla
+ z_r = np.linspace(2.0e-6, 10.0e-6, 200)
+ f_r = 0.02 * (10.0e-6 - z_r)
+ curve = ForceCurve(
+ segments=(
+ ForceSegment(
+ segment_type="extend", direction="approach",
+ raw_height=z_a, raw_deflection=f_a,
+ deflection=f_a / 0.05, force=f_a, state="force_n",
+ ),
+ ForceSegment(
+ segment_type="retract", direction="retract",
+ raw_height=z_r, raw_deflection=f_r,
+ deflection=f_r / 0.05, force=f_r, state="force_n",
+ ),
+ ),
+ metadata={"format": "synthetic"},
+ )
+ # contacto al final del approach (coordenada mínima): dominio [z_min, z_max]
+ contact = ContactPointCandidate(
+ method="synthetic", index=199, coordinate=2.0e-6, score=1.0, valid=True
+ )
+ strict = integrate_force_work(curve, contact, domain="height")
+ path = integrate_force_path_work(curve.extend.raw_height, curve.extend.force)
+ # integral estricta: +∫ f dz (z creciente); path work: adquisición decreciente -> -∫ f dz
+ assert path.work_total == pytest.approx(-strict.work_approach, rel=1e-9, abs=1e-18)
+ assert path.diagnostics.global_direction == "decreasing"
+
+
+# ---------------------------------------------------------------------------
+# Typed failures (15-18)
+# ---------------------------------------------------------------------------
+
+
+def test_nonfinite_coordinate_raises() -> None:
+ z = np.array([0.0, 1.0, np.nan, 3.0])
+ with pytest.raises(ForceFoundationError) as exc:
+ integrate_force_path_work(z, np.arange(4.0))
+ assert exc.value.code == NONFINITE_DATA
+
+
+def test_nonfinite_force_raises() -> None:
+ f = np.array([0.0, 1.0, np.inf, 3.0])
+ with pytest.raises(ForceFoundationError) as exc:
+ integrate_force_path_work(np.arange(4.0), f)
+ assert exc.value.code == NONFINITE_DATA
+
+
+def test_unequal_lengths_raise() -> None:
+ with pytest.raises(ForceFoundationError) as exc:
+ integrate_force_path_work(np.arange(4.0), np.arange(5.0))
+ assert exc.value.code == LENGTH_MISMATCH
+
+
+def test_fewer_than_two_samples_raise() -> None:
+ with pytest.raises(ForceFoundationError) as exc:
+ integrate_force_path_work(np.array([1.0]), np.array([1.0]))
+ assert exc.value.code == INSUFFICIENT_SAMPLES
+ with pytest.raises(ForceFoundationError) as exc:
+ coordinate_path_diagnostics(np.array([1.0]))
+ assert exc.value.code == INSUFFICIENT_SAMPLES
+
+
+def test_negative_tolerance_raises() -> None:
+ with pytest.raises(ValueError, match="classification_tolerance"):
+ integrate_force_path_work(np.arange(3.0), np.arange(3.0), classification_tolerance=-1.0)
+
+
+def test_empty_coordinate_raises() -> None:
+ with pytest.raises(ForceFoundationError) as exc:
+ integrate_force_path_work(np.array([]), np.array([]))
+ assert exc.value.code == "MISSING_COORDINATE"
+
+
+# ---------------------------------------------------------------------------
+# Diagnostics definitions
+# ---------------------------------------------------------------------------
+
+
+def test_diagnostics_net_and_variation() -> None:
+ z = np.array([0.0, 1.0, 2.0, 1.5, 2.5, 2.0, 3.0])
+ d = coordinate_path_diagnostics(z)
+ assert d.net_displacement == pytest.approx(3.0)
+ assert d.total_variation == pytest.approx(1 + 1 + 0.5 + 1 + 0.5 + 1)
+ assert d.forward_distance == pytest.approx(1 + 1 + 1 + 1)
+ assert d.backward_distance == pytest.approx(0.5 + 0.5)
+ assert d.backtracking_fraction == pytest.approx(1.0 / 5.0)
+ assert d.exact_positive_steps == 4
+ assert d.exact_negative_steps == 2
+ assert d.exact_zero_steps == 0
+ assert d.global_direction == "increasing"
+ assert d.globally_directed
+ assert not d.strictly_monotonic
+
+
+def test_diagnostics_maximum_reverse_excursion_decreasing() -> None:
+ # running min: 3,2,1,1,0 -> excursion: 0,0,0,1,0 -> max 1.0
+ z = np.array([3.0, 2.0, 1.0, 2.0, 0.0])
+ d = coordinate_path_diagnostics(z)
+ assert d.global_direction == "decreasing"
+ assert d.maximum_reverse_excursion == pytest.approx(1.0)
+ # paso opuesto mayor (signed): para decreasing, el mayor paso positivo
+ assert d.maximum_reverse_step == pytest.approx(1.0)
+ # un paso aislado opuesto a la dirección global (decreasing -> paso +1)
+
+
+def test_diagnostics_maximum_reverse_excursion_increasing() -> None:
+ # running max: 0,1,2,2,3 -> excursion: 0,0,0,1,0 -> max 1.0
+ z = np.array([0.0, 1.0, 2.0, 1.0, 3.0])
+ d = coordinate_path_diagnostics(z)
+ assert d.global_direction == "increasing"
+ assert d.maximum_reverse_excursion == pytest.approx(1.0)
+ assert d.maximum_reverse_step == pytest.approx(-1.0)
+
+
+def test_diagnostics_strictly_monotone_zero_excursion() -> None:
+ z = np.linspace(0.0, 5.0, 6)
+ d = coordinate_path_diagnostics(z)
+ assert d.strictly_monotonic
+ assert d.maximum_reverse_excursion == pytest.approx(0.0)
+ assert d.maximum_reverse_step is None
+ assert d.classified_reversal_count == 0
+
+
+def test_diagnostics_closed_ambiguous() -> None:
+ z = np.array([0.0, 1.0, 2.0, 1.0, 0.0])
+ d = coordinate_path_diagnostics(z)
+ assert d.global_direction == "closed_or_ambiguous"
+ assert not d.globally_directed
+ assert d.maximum_reverse_step is None
+ assert d.maximum_reverse_excursion is None
+ assert any("no coherent global direction" in w for w in d.warnings)
+
+
+def test_diagnostics_tolerance_classification_only() -> None:
+ """La tolerancia cambia la clasificación, nunca la integral."""
+ z = np.array([0.0, 1.0, 1.0 - 1e-9, 2.0]) # reverso diminuto (dz = -1e-9)
+ f = np.array([0.0, 1.0, 1.0, 2.0])
+ exact = coordinate_path_diagnostics(z)
+ classified = coordinate_path_diagnostics(z, classification_tolerance=1e-6)
+ assert exact.classified_reversal_count == 1
+ assert not exact.strictly_monotonic
+ assert classified.classified_reversal_count == 0
+ assert classified.strictly_monotonic
+ assert classified.classification_tolerance == 1e-6
+ w_exact = integrate_force_path_work(z, f)
+ w_class = integrate_force_path_work(z, f, classification_tolerance=1e-6)
+ assert w_exact.work_total == w_class.work_total
+
+
+def test_diagnostics_global_direction_reversal() -> None:
+ z = np.array([3.0, 2.0, 1.0])
+ assert coordinate_path_diagnostics(z).global_direction == "decreasing"
+ assert coordinate_path_diagnostics(z[::-1]).global_direction == "increasing"
+
+
+def test_ambiguous_path_warns_on_decomposition() -> None:
+ z, f = _CASES["closed_hysteresis_loop"]
+ r = integrate_force_path_work(z, f)
+ assert any("step sign" in w for w in r.warnings)
+ assert r.work_total == pytest.approx(r.work_forward + r.work_backward, abs=1e-12)
+
+
+# ---------------------------------------------------------------------------
+# Immutability, determinism, pickle
+# ---------------------------------------------------------------------------
+
+
+def test_immutability_and_deterministic_replay() -> None:
+ z, f = _CASES["globally_directed_backtracking"]
+ z_copy, f_copy = z.copy(), f.copy()
+ r1 = integrate_force_path_work(z, f)
+ r2 = integrate_force_path_work(z, f)
+ assert np.array_equal(z, z_copy) and np.array_equal(f, f_copy)
+ assert r1.work_total == r2.work_total
+ assert r1.diagnostics.net_displacement == r2.diagnostics.net_displacement
+
+
+def test_result_pickle_roundtrip() -> None:
+ z, f = _CASES["globally_directed_backtracking"]
+ r = integrate_force_path_work(z, f)
+ blob = pickle.dumps(r)
+ r2 = pickle.loads(blob)
+ assert r2 == r
+ assert r2.work_total == r.work_total
+
+
+def test_result_repr_and_fields() -> None:
+ z, f = _CASES["linear_force"]
+ r = integrate_force_path_work(z, f)
+ assert r.units == "J"
+ assert r.valid
+ assert "acquisition_path" in r.provenance["semantics"]
+ assert "trapezoidal_acquisition_order" in r.provenance["arithmetic"]
+ assert isinstance(r, ForcePathWorkResult)
+
+
+def test_custom_units_label() -> None:
+ z, f = _CASES["linear_force"]
+ r = integrate_force_path_work(z, f, coordinate_unit="nm", force_unit="nN")
+ assert r.units == "nN·nm"
+
+
+# ---------------------------------------------------------------------------
+# Production independence rule (oracle never imports production code)
+# ---------------------------------------------------------------------------
+
+
+def test_oracle_module_has_no_production_imports() -> None:
+ import inspect
+
+ import tests.core.test_force_path_work as mod
+
+ src = inspect.getsource(mod.oracle_path_work)
+ assert "spmkit" not in src
+ assert "np.trapezoid" not in src
+ assert "trapezoid(" not in src
diff --git a/tests/core/test_force_smfs.py b/tests/core/test_force_smfs.py
new file mode 100644
index 0000000..953d4ef
--- /dev/null
+++ b/tests/core/test_force_smfs.py
@@ -0,0 +1,551 @@
+"""FS-F4 core tests: polymer equations, extension contract, events,
+kinetics, survival, population. No fixtures loaded."""
+
+from __future__ import annotations
+
+import numpy as np
+import pytest
+
+from spmkit.core.analysis.force_smfs import (
+ SmfsError,
+ analyze_smfs_batch,
+ analyze_smfs_event_population,
+ bell_evans_pdf,
+ bell_evans_rate,
+ bell_evans_survival,
+ compute_event_loading_rates,
+ compute_molecular_extension,
+ detect_unfolding_events,
+ dhs_log_rate,
+ estimate_force_clamp_survival,
+ extensible_fjc_extension,
+ extensible_wlc_force,
+ fit_bell_evans,
+ fit_dudko_hummer_szabo,
+ fit_extensible_freely_jointed_chain,
+ fit_extensible_worm_like_chain,
+ fit_freely_jointed_chain,
+ fit_worm_like_chain,
+ fjc_extension,
+ infer_contour_length_increments,
+ langevin,
+ quantify_unfolding_events,
+ select_smfs_fit_windows,
+ wlc_force,
+)
+from spmkit.core.analysis.force_smfs_models import KB, MolecularExtensionResult
+from spmkit.core.models import Calibration, ForceCurve, ForceSegment
+
+T = 298.0
+LC, LP, B = 100e-9, 0.5e-9, 1e-9
+
+
+def _wlc_data(x_max: float = 90e-9, n: int = 200, noise: float = 0.0,
+ lc: float = LC, lp: float = LP):
+ rng = np.random.default_rng(0)
+ x = np.linspace(1e-9, x_max, n)
+ f = wlc_force(x, lc, lp, T)
+ if noise:
+ f = f + rng.normal(0.0, noise, n)
+ return x, f
+
+
+# ---------------------------------------------------------------------------
+# forward equations
+# ---------------------------------------------------------------------------
+
+
+def test_wlc_forward_limits() -> None:
+ x = np.linspace(1e-9, 90e-9, 50)
+ f = wlc_force(x, LC, LP, T)
+ # low-force: F ~ (k_BT/Lp) x/Lc
+ assert np.allclose(f[:5], (KB * T / LP) * (x[:5] / LC), rtol=1e-2)
+ # monotone increasing
+ assert np.all(np.diff(f) > 0)
+ # singularity: x >= Lc raises
+ with pytest.raises(SmfsError) as ei:
+ wlc_force(np.array([LC, LC * 1.1]), LC, LP, T)
+ assert ei.value.code == "POLYMER_SINGULARITY"
+ with pytest.raises(SmfsError):
+ wlc_force(x, LC, -1e-12, T) # invalid persistence length
+
+
+def test_ewlc_infinite_stiffness_limit() -> None:
+ x = np.linspace(1e-9, 80e-9, 40)
+ f_ewlc = extensible_wlc_force(x, LC, LP, 1e10, T) # huge S
+ f_wlc = wlc_force(x, LC, LP, T)
+ assert np.allclose(f_ewlc, f_wlc, rtol=1e-4)
+ with pytest.raises(SmfsError) as ei:
+ extensible_wlc_force(np.array([0.5 * LC]), LC, LP, -1.0, T)
+ assert ei.value.code == "INVALID_MODEL_PARAMETER"
+
+
+def test_fjc_forward_limits() -> None:
+ # x(0) = 0 and x -> Lc at high force
+ x0 = fjc_extension(np.array([0.0]), LC, B, T)
+ assert abs(float(x0[0])) < 1e-15
+ xbig = fjc_extension(np.array([1e3 * KB * T / B]), LC, B, T)
+ assert float(xbig[0]) > 0.99 * LC
+ # tiny force: x ~ Lc y/3
+ f_small = 1e-14
+ y = f_small * B / (KB * T)
+ x_small = fjc_extension(np.array([f_small]), LC, B, T)
+ assert np.allclose(x_small, LC * y / 3.0, rtol=1e-3)
+
+
+def test_langevin_stability() -> None:
+ u = np.array([0.0, 1e-8, 0.5, 3.0, 1e4])
+ L = langevin(u)
+ assert np.all(np.isfinite(L))
+ assert abs(float(L[0])) < 1e-15
+ assert float(L[-1]) > 0.999
+
+
+def test_efjc_infinite_stiffness_limit() -> None:
+ f = np.linspace(1e-12, 1e-9, 40)
+ x_efjc = extensible_fjc_extension(f, LC, B, 1e10, T)
+ x_fjc = fjc_extension(f, LC, B, T)
+ assert np.allclose(x_efjc, x_fjc, rtol=1e-4)
+
+
+# ---------------------------------------------------------------------------
+# molecular extension contract
+# ---------------------------------------------------------------------------
+
+
+def _prepared_curve(n_retract: int = 300):
+ """A minimal prepared-style curve with a retract separation axis."""
+ sep = np.linspace(2.9e-6, 3.09e-6, n_retract)
+ f = np.linspace(0.0, 2e-10, n_retract)
+ t = np.linspace(0.0, 1.0, n_retract)
+ z_a = np.linspace(1e-6, 2.8e-6, 120)
+ f_a = np.where(z_a > 2.5e-6, (z_a - 2.5e-6) * 1e-3, 0.0)
+ t_a = np.linspace(-0.5, 0.0, 120)
+ curve = ForceCurve(
+ segments=(
+ ForceSegment(segment_type="extend", direction="forward", raw_height=z_a,
+ raw_deflection=f_a / 10.0, time=t_a, cycle=0, state="force_n",
+ deflection=f_a / 10.0, force=f_a, separation=None, metadata={}),
+ ForceSegment(segment_type="retract", direction="backward", raw_height=sep + f / 10.0,
+ raw_deflection=f / 10.0, time=t, cycle=0, state="force_n",
+ deflection=f / 10.0, force=f, separation=None, metadata={}),
+ ),
+ calibration=Calibration(invols=3e-8, spring_constant=10.0, method="thermal",
+ temperature=300, provenance={}),
+ position=None, index=0, metadata={})
+ return curve
+
+
+def test_extension_reference_policies() -> None:
+ from spmkit.core.analysis import prepare_force_curve
+ curve = _prepared_curve()
+ prepared = prepare_force_curve(curve)
+ # offset policy
+ ext = compute_molecular_extension(prepared, reference="offset", reference_value=3.0e-6)
+ assert abs(ext.reference_coordinate - 3.0e-6) < 1e-15
+ assert np.all(np.diff(ext.extension[ext.valid]) >= -1e-12)
+ # index policy
+ ext2 = compute_molecular_extension(prepared, reference="index", reference_value=100)
+ assert ext2.reference_index == 100
+ # pre_event policy (semantic alias)
+ ext3 = compute_molecular_extension(prepared, reference="pre_event", reference_value=100)
+ assert ext3.reference_policy == "pre_event"
+ # unknown policy typed
+ with pytest.raises(SmfsError) as ei:
+ compute_molecular_extension(prepared, reference="auto")
+ assert ei.value.code == "INVALID_REFERENCE_POLICY"
+ # missing value typed
+ with pytest.raises(SmfsError) as ei:
+ compute_molecular_extension(prepared, reference="offset")
+ assert ei.value.code == "UNRESOLVED_TETHER_ZERO"
+
+
+def test_extension_estimator_zero_crossing() -> None:
+ from spmkit.core.analysis import prepare_force_curve
+ curve = _prepared_curve()
+ prepared = prepare_force_curve(curve)
+ ext = compute_molecular_extension(prepared, reference="estimator")
+ assert ext.reference_policy == "estimator"
+ assert np.isfinite(ext.reference_coordinate)
+ assert any("estimator" in w for w in ext.warnings)
+
+
+def test_smfs_fit_window_contract() -> None:
+ x, f = _wlc_data()
+ w = select_smfs_fit_windows(x, f, min_points=10)
+ assert w.n_points >= 10
+ assert w.included.sum() == w.n_points
+ with pytest.raises(SmfsError) as ei:
+ select_smfs_fit_windows(x, f, min_extension=1e-6) # empty
+ assert ei.value.code == "EMPTY_WINDOW"
+ with pytest.raises(SmfsError) as ei:
+ select_smfs_fit_windows(x, f, min_points=10**6)
+ assert ei.value.code == "INSUFFICIENT_POINTS"
+
+
+# ---------------------------------------------------------------------------
+# polymer fits
+# ---------------------------------------------------------------------------
+
+
+def test_wlc_clean_recovery() -> None:
+ x, f = _wlc_data()
+ fit = fit_worm_like_chain(x, f, temperature=T)
+ assert abs(fit.parameters["Lc"] - LC) / LC < 0.01
+ assert abs(fit.parameters["Lp"] - LP) / LP < 0.01
+ assert fit.condition_number > 0.0
+
+
+def test_wlc_noisy_recovery() -> None:
+ x, f = _wlc_data(noise=2e-13)
+ fit = fit_worm_like_chain(x, f, temperature=T)
+ assert abs(fit.parameters["Lc"] - LC) / LC < 0.05
+ assert abs(fit.parameters["Lp"] - LP) / LP < 0.20
+
+
+def test_wlc_singular_domain_typed() -> None:
+ # the data must stay below the contour; the singular extension domain
+ # raises through the forward model
+ with pytest.raises(SmfsError):
+ wlc_force(np.array([LC]), LC, LP, T)
+
+
+def test_ewlc_clean_recovery_bounds() -> None:
+ x = np.linspace(1e-9, 80e-9, 200)
+ f = extensible_wlc_force(x, LC, LP, 1e-8, T)
+ fit = fit_extensible_worm_like_chain(x, f, temperature=T)
+ assert abs(fit.parameters["Lc"] - LC) / LC < 0.05
+ assert abs(fit.parameters["Lp"] - LP) / LP < 0.20
+ # the stretch modulus is weakly identifiable from a single branch
+ # (documented); the response reconstruction must be accurate
+ pred = extensible_wlc_force(x, fit.parameters["Lc"], fit.parameters["Lp"],
+ fit.parameters["S"], T)
+ assert np.max(np.abs(pred - f)) / np.max(np.abs(f)) < 0.02
+
+
+def test_fjc_clean_recovery() -> None:
+ f = np.linspace(1e-13, 1e-9, 200)
+ x = fjc_extension(f, LC, B, T)
+ fit = fit_freely_jointed_chain(x, f, temperature=T)
+ assert abs(fit.parameters["Lc"] - LC) / LC < 0.01
+ assert abs(fit.parameters["b"] - B) / B < 0.02
+
+
+def test_efjc_clean_recovery() -> None:
+ f = np.linspace(1e-13, 1e-9, 200)
+ x = extensible_fjc_extension(f, LC, B, 1e-8, T)
+ fit = fit_extensible_freely_jointed_chain(x, f, temperature=T)
+ assert abs(fit.parameters["Lc"] - LC) / LC < 0.01
+ assert abs(fit.parameters["b"] - B) / B < 0.02
+
+
+def test_polymer_fit_determinism() -> None:
+ x, f = _wlc_data(noise=2e-13)
+ a = fit_worm_like_chain(x, f, temperature=T)
+ b = fit_worm_like_chain(x, f, temperature=T)
+ assert a.parameters == b.parameters
+ assert a.aicc == b.aicc
+
+
+def test_polymer_comparison_prefers_true_model() -> None:
+ x, f = _wlc_data()
+ cmp = __import__("spmkit.core.analysis.force_smfs",
+ fromlist=["compare_polymer_models"]).compare_polymer_models(
+ x, f, models=("worm_like_chain", "freely_jointed_chain"))
+ assert cmp.recommended_model == "worm_like_chain"
+ assert cmp.weights["worm_like_chain"] > 0.9
+
+
+# ---------------------------------------------------------------------------
+# events
+# ---------------------------------------------------------------------------
+
+
+def _sawtooth_extension(n: int = 400):
+ """Clean single-event sawtooth on the extension axis."""
+ lc1, lc2, lp = 100e-9, 200e-9, 0.5e-9
+ x = np.linspace(0.0, 140e-9, n)
+ f = np.zeros(n)
+ for i, xi in enumerate(x):
+ if xi <= 70e-9:
+ f[i] = float(wlc_force(np.array([xi]), lc1, lp, T)[0])
+ else:
+ f[i] = float(wlc_force(np.array([xi]), lc2, lp, T)[0])
+ sep = 3.0e-6 + x
+ t = np.linspace(0.0, 1.0, n)
+ return MolecularExtensionResult(
+ extension=x, separation=sep, force=f, time=t,
+ retract_indices=np.arange(n), reference_policy="offset",
+ reference_coordinate=3.0e-6, reference_index=None, valid=np.ones(n, dtype=bool),
+ provenance={})
+
+
+def test_event_detection_tp_fp_fn() -> None:
+ ext = _sawtooth_extension()
+ ev = detect_unfolding_events(ext, noise_sigma=1e-13)
+ assert len(ev.events) == 1 # one true positive
+ e = ev.events[0]
+ assert e.valid
+ assert not e.is_final_detachment # the force continues after the event
+ assert e.rupture_force > 0.0
+ # the detected index is within 3 samples of the truth (70e-9)
+ idx = int(np.argmin(np.abs(ext.extension - 70e-9)))
+ assert abs(e.event_index - idx) <= 3
+
+
+def test_event_detection_rejects_noise_peak() -> None:
+ ext = _sawtooth_extension()
+ f = ext.force.copy()
+ peak = int(np.argmin(np.abs(ext.extension - 5e-9)))
+ f[peak] += float(np.max(f)) * 0.01 # a 1% spike (below the 5-sigma drop)
+ ext2 = MolecularExtensionResult(
+ extension=ext.extension, separation=ext.separation, force=f, time=ext.time,
+ retract_indices=ext.retract_indices, reference_policy="offset",
+ reference_coordinate=3.0e-6, reference_index=None,
+ valid=ext.valid, provenance={})
+ ev = detect_unfolding_events(ext2, noise_sigma=1e-13)
+ assert len(ev.events) == 1 # the noise peak is not a false positive
+ assert len(ev.rejected) >= 1 # retained with the reason
+
+
+def test_event_detection_no_events_typed() -> None:
+ x = np.linspace(0.0, 90e-9, 300)
+ f = wlc_force(x, LC, LP, T) # smooth branch, no events
+ ext = MolecularExtensionResult(
+ extension=x, separation=3.0e-6 + x, force=f, time=np.linspace(0, 1, 300),
+ retract_indices=np.arange(300), reference_policy="offset",
+ reference_coordinate=3.0e-6, reference_index=None,
+ valid=np.ones(300, dtype=bool), provenance={})
+ with pytest.raises(SmfsError) as ei:
+ detect_unfolding_events(ext, noise_sigma=1e-13)
+ assert ei.value.code == "NO_EVENTS"
+
+
+def test_contour_increment_from_independent_fits() -> None:
+ ext = _sawtooth_extension()
+ ev = detect_unfolding_events(ext, noise_sigma=1e-13)
+ ev2 = quantify_unfolding_events(ext, ev)
+ inc = infer_contour_length_increments(ext, ev2)
+ assert len(inc) == 1
+ r = inc[0]
+ assert r.valid
+ assert abs(r.delta_contour_length - 100e-9) / 100e-9 < 0.10
+ assert r.pre_fit is not None and r.post_fit is not None
+ assert abs(r.pre_fit.parameters["Lc"] - 100e-9) / 100e-9 < 0.10
+ assert abs(r.post_fit.parameters["Lc"] - 200e-9) / 200e-9 < 0.10
+
+
+def test_loading_rates_measured_vs_theoretical() -> None:
+ ext = _sawtooth_extension()
+ ev = detect_unfolding_events(ext, noise_sigma=1e-13)
+ ev2 = quantify_unfolding_events(ext, ev)
+ rates = compute_event_loading_rates(ext, ev2, pulling_velocity=1e-6,
+ effective_stiffness=1e-4)
+ r = rates[0]
+ assert r.local_slope > 0.0 # force rises before the rupture
+ assert r.units == "N/s"
+ # the theoretical rate is reported separately, never substituted
+ assert r.theoretical_rate is not None
+ assert abs(r.theoretical_rate - 1e-10) < 1e-30
+ assert r.measured
+
+
+def test_loading_rates_require_time() -> None:
+ ext = _sawtooth_extension()
+ ext2 = MolecularExtensionResult(
+ extension=ext.extension, separation=ext.separation, force=ext.force,
+ time=None, retract_indices=ext.retract_indices, reference_policy="offset",
+ reference_coordinate=3.0e-6, reference_index=None, valid=ext.valid,
+ provenance={})
+ ev = detect_unfolding_events(ext2, noise_sigma=1e-13)
+ ev2 = quantify_unfolding_events(ext2, ev)
+ with pytest.raises(SmfsError) as ei:
+ compute_event_loading_rates(ext2, ev2)
+ assert ei.value.code == "NONFINITE_INPUT"
+
+
+# ---------------------------------------------------------------------------
+# kinetics
+# ---------------------------------------------------------------------------
+
+
+def test_bell_evans_forward() -> None:
+ # S(0) = 1 and the pdf integrates approximately to 1 at a high rate
+ f = np.linspace(0.0, 2e-10, 400)
+ s = bell_evans_survival(f, 1e4, 1.0, 1e-9, T)
+ assert abs(float(s[0]) - 1.0) < 1e-9
+ p = bell_evans_pdf(f, 1e4, 1.0, 1e-9, T)
+ integral = float(np.trapezoid(p, f))
+ assert 0.9 < integral < 1.1
+ # the rate is dimensionless-consistent
+ k = bell_evans_rate(np.array([1e-9]), 1.0, 1e-9, T)
+ assert np.isfinite(k).all()
+
+
+def test_bell_evans_recovery() -> None:
+ # deterministic quantile sample from the BE pdf at several rates
+ rng = np.random.default_rng(3)
+ rates = np.geomspace(1e3, 1e6, 4)
+ forces = []
+ rate_list = []
+ for r in rates:
+ grid = np.linspace(0.0, 2.5e-10, 2000)
+ p = bell_evans_pdf(grid, r, 1.0, 1e-9, T)
+ cdf = np.cumsum(p) * (grid[1] - grid[0])
+ cdf = cdf / cdf[-1]
+ u = rng.random(40)
+ forces.extend(np.interp(u, cdf, grid))
+ rate_list.extend([r] * 40)
+ fit = fit_bell_evans(np.asarray(rate_list), np.asarray(forces), temperature=T)
+ assert abs(fit.parameters["x_beta"] - 1e-9) / 1e-9 < 0.10
+ assert 0.3 < fit.parameters["k0"] < 3.0
+
+
+def test_bell_evans_invalid_domains() -> None:
+ with pytest.raises(SmfsError) as ei:
+ fit_bell_evans(np.array([1e3, 1e4]), np.array([1e-10, 1e-10]), temperature=-5.0)
+ assert ei.value.code == "INVALID_MODEL_PARAMETER"
+ with pytest.raises(SmfsError):
+ fit_bell_evans(np.array([-1e3, 1e4]), np.array([1e-10, 1e-10]))
+
+
+def test_dhs_forward_and_bell_limit() -> None:
+ # at tiny barrier the DHS rate approaches the BE rate
+ f = 5e-11
+ k_dhs = dhs_log_rate(f, 1.0, 1e-9, 1e-12, 2.0 / 3.0, T)
+ k_be = np.log(float(bell_evans_rate(np.array([f]), 1.0, 1e-9, T)[0]))
+ assert abs(k_dhs - k_be) < 1e-6
+ # domain violation typed
+ with pytest.raises(SmfsError) as ei:
+ fit_dudko_hummer_szabo(np.array([1e3, 1e4, 1e5, 1e6, 1e7]),
+ np.array([1e-10] * 5), nu=0.4)
+ assert ei.value.code == "INVALID_MODEL_PARAMETER"
+
+
+def test_dhs_recovery_bounds() -> None:
+ # deterministic quantile sample from the DHS pdf
+ rng = np.random.default_rng(5)
+ rates = np.geomspace(1e3, 1e6, 4)
+ forces, rate_list = [], []
+ for r in rates:
+ grid = np.linspace(0.0, 1.4e-10, 3000)
+ from spmkit.core.analysis.force_smfs import dhs_pdf
+ p = dhs_pdf(grid, r, 1.0, 1e-9, 1e-19, 2.0 / 3.0, T)
+ cdf = np.cumsum(p) * (grid[1] - grid[0])
+ cdf = cdf / cdf[-1]
+ u = rng.random(30)
+ forces.extend(np.interp(u, cdf, grid))
+ rate_list.extend([r] * 30)
+ fit = fit_dudko_hummer_szabo(np.asarray(rate_list), np.asarray(forces),
+ temperature=T)
+ assert fit.success
+ # the energy-landscape parameters are weakly identifiable (the fit can
+ # land at the physical bounds on a finite sample): the honest evidence
+ # is the RESPONSE reconstruction plus the documented non-uniqueness
+ assert 1e-12 <= fit.parameters["x_beta"] <= 1e-7
+ assert 1e-22 <= fit.parameters["dG"] <= 1e-11
+ assert any("not claimed to be physically unique" in w for w in fit.warnings)
+ # the predicted per-rate median forces reproduce the data within 30%
+ med_data = np.array([float(np.median(forces[i * 30:(i + 1) * 30]))
+ for i in range(4)])
+ grid = np.linspace(0.0, 1.5e-10, 400)
+ med_pred = np.empty(4)
+ for i, r in enumerate(rates):
+ pdf = dhs_pdf(grid, r, fit.parameters["k0"], fit.parameters["x_beta"],
+ fit.parameters["dG"], 2.0 / 3.0, T)
+ cdf = np.cumsum(pdf)
+ cdf = cdf / cdf[-1]
+ med_pred[i] = float(np.interp(0.5, cdf, grid))
+ assert np.max(np.abs(med_pred - med_data) / med_data) < 0.30
+
+
+# ---------------------------------------------------------------------------
+# force clamp survival
+# ---------------------------------------------------------------------------
+
+
+def test_km_survival_truth() -> None:
+ # hand-computed case: lifetimes (1, 2, 3) all events
+ lt = np.array([1.0, 2.0, 3.0])
+ ce = np.array([0.0, 0.0, 0.0])
+ km = estimate_force_clamp_survival(lt, ce, force_level=1e-11)
+ assert km.n_events == 3
+ assert km.n_censored == 0
+ # S(1) = 2/3, S(2) = 1/3, S(3) = 0
+ assert np.allclose(km.survival_probability, [2 / 3, 1 / 3, 0.0])
+ assert km.median_lifetime == 2.0
+ # exponential MLE: 3 / (1+2+3) = 0.5
+ assert np.isclose(km.exponential_rate, 0.5)
+
+
+def test_km_right_censoring_preserved() -> None:
+ lt = np.array([1.0, 2.0, 5.0, 5.0])
+ ce = np.array([0.0, 0.0, 0.0, 1.0])
+ km = estimate_force_clamp_survival(lt, ce, force_level=1e-11)
+ assert km.n_censored == 1
+ # at t=1: S = 3/4; at t=2: S = 3/4 * 2/3 = 1/2; at t=5: one event among
+ # 2 at risk -> S = 1/2 * 1/2 = 1/4 (the censor leaves the risk set
+ # afterwards)
+ assert np.allclose(km.survival_probability, [3 / 4, 1 / 2, 1 / 4])
+ # the censored observation is not discarded from the rate MLE:
+ # 3 uncensored events over the total time 13
+ assert np.isclose(km.exponential_rate, 3.0 / 13.0)
+
+
+def test_km_tie_order_deterministic() -> None:
+ lt = np.array([1.0, 1.0, 2.0])
+ ce = np.array([0.0, 1.0, 0.0]) # event and censor at the same time
+ km = estimate_force_clamp_survival(lt, ce, force_level=1e-11)
+ # the event at t=1 lowers the survival before the censor reduces the
+ # at-risk count: S(1) = 2/3
+ assert np.allclose(km.survival_probability[0], 2 / 3)
+
+
+def test_km_undefined_median() -> None:
+ lt = np.array([1.0, 2.0, 3.0])
+ ce = np.array([1.0, 1.0, 1.0]) # all censored
+ km = estimate_force_clamp_survival(lt, ce, force_level=1e-11)
+ assert km.median_lifetime is None
+ assert km.exponential_rate is None
+ assert any("UNDEFINED_MEDIAN" in w for w in km.warnings)
+ with pytest.raises(SmfsError) as ei:
+ estimate_force_clamp_survival(np.array([1.0, -2.0]), np.array([0.0, 0.0]),
+ force_level=1e-11)
+ assert ei.value.code == "CENSORING_INVALID"
+
+
+# ---------------------------------------------------------------------------
+# population and batch
+# ---------------------------------------------------------------------------
+
+
+def test_population_aggregation_and_ambiguity() -> None:
+ records = [
+ {"rupture_force": 1e-10, "delta_contour_length": 1e-7, "loading_rate": 1e3},
+ {"rupture_force": 1.2e-10, "delta_contour_length": 1e-7, "loading_rate": 1e3},
+ {"rupture_force": 1.1e-10, "delta_contour_length": 1e-7, "loading_rate": 1e6},
+ ]
+ pop = analyze_smfs_event_population(records, group_by="none")
+ assert pop.n_events == 3
+ assert pop.ambiguous # too few events for a population claim
+ assert any("no molecular-identity claim" in w for w in pop.warnings)
+ with pytest.raises(SmfsError) as ei:
+ analyze_smfs_event_population([])
+ assert ei.value.code == "INSUFFICIENT_EVENTS"
+
+
+def test_batch_retains_failures() -> None:
+ batch = analyze_smfs_batch([
+ {"curve_id": "A", "ok": True, "events": [
+ {"rupture_force": 1e-10, "delta_contour_length": 1e-7,
+ "loading_rate": 1e3}]},
+ {"curve_id": "B", "curve_index": 1, "ok": False, "failure": "MISSING_TIME"},
+ {"curve_id": "C", "curve_index": 2, "ok": False, "failure": "NO_EVENTS"},
+ ], group_by="none")
+ assert batch.n_curves == 3
+ assert batch.n_ok == 1
+ assert batch.n_failed == 2
+ assert batch.failed_reasons == {1: "MISSING_TIME", 2: "NO_EVENTS"}
+ assert len(batch.unified_event_table) == 1
+ assert batch.unified_event_table[0]["curve_id"] == "A"
+ assert batch.provenance["deterministic"]
diff --git a/tests/core/test_force_viscoelasticity.py b/tests/core/test_force_viscoelasticity.py
new file mode 100644
index 0000000..2cf460b
--- /dev/null
+++ b/tests/core/test_force_viscoelasticity.py
@@ -0,0 +1,374 @@
+"""FS-F3 core tests: temporal contract, equations, failures, determinism.
+
+No fixtures loaded.
+"""
+
+from __future__ import annotations
+
+import numpy as np
+import pytest
+
+from spmkit.core.analysis.force_viscoelasticity import (
+ CreepResponseResult,
+ RelaxationResponseResult,
+ ViscoelasticityError,
+ compare_viscoelastic_models,
+ fit_generalized_maxwell,
+ fit_kelvin_voigt,
+ fit_maxwell,
+ fit_power_law_relaxation,
+ fit_standard_linear_solid,
+ forward_generalized_maxwell_modulus,
+ forward_generalized_maxwell_normalized,
+ forward_kelvin_voigt_compliance,
+ forward_maxwell_modulus,
+ forward_power_law_modulus,
+ forward_sls_modulus,
+ identify_viscoelastic_protocol,
+ lee_radok_force,
+ reduced_modulus,
+ sls_creep_to_relaxation,
+ sls_relaxation_to_creep,
+ spherical_coefficient,
+ ting_force,
+ validate_time_axis,
+)
+from spmkit.core.models import Calibration, ForceCurve, ForceSegment
+
+T = np.linspace(1e-3, 1.0, 100)
+
+
+def _relaxation_response(tau: float = 0.1, n: int = 120, noise: float = 0.0):
+ rng = np.random.default_rng(0)
+ t = np.linspace(0.0, 1.0, n)
+ nrm = np.exp(-t / tau)
+ if noise:
+ nrm = nrm + rng.normal(0.0, noise, n)
+ return RelaxationResponseResult(
+ relative_time=t, indentation=np.full(n, 5e-7), force=1e-6 * nrm,
+ normalized_force=nrm, hold_indices=np.arange(n), hold_start_time=0.0,
+ force_at_hold_start=1e-6, equilibrium_force_estimate=float(nrm[-1]) * 1e-6,
+ warnings=())
+
+
+def _creep_response(modulus: float = 5e3, tau: float = 0.1, n: int = 120):
+ t = np.linspace(0.0, 1.0, n)
+ j_inc = (1.0 / modulus) * (1.0 - np.exp(-t / tau))
+ return CreepResponseResult(
+ relative_time=t, force=np.full(n, 1e-6), indentation=1e-6 * j_inc,
+ compliance_proxy=j_inc, hold_indices=np.arange(n), hold_start_time=0.0,
+ force_hold_value=1e-6, indentation_at_hold_start=0.0, warnings=())
+
+
+# ---------------------------------------------------------------------------
+# temporal contract
+# ---------------------------------------------------------------------------
+
+
+def test_validate_time_axis_strict() -> None:
+ t = np.array([0.0, 1.0, 2.0])
+ np.testing.assert_array_equal(validate_time_axis(t), t)
+ with pytest.raises(ViscoelasticityError) as ei:
+ validate_time_axis(np.array([0.0, 1.0, 1.0]))
+ assert ei.value.code == "DUPLICATE_TIMESTAMPS"
+ with pytest.raises(ViscoelasticityError) as ei:
+ validate_time_axis(np.array([0.0, 2.0, 1.0]))
+ assert ei.value.code == "NONMONOTONIC_TIME"
+ with pytest.raises(ViscoelasticityError):
+ validate_time_axis(np.array([0.0, np.nan, 2.0]))
+
+
+def test_seconds_vs_milliseconds_scale_invariance() -> None:
+ """A correct implementation is invariant under a uniform time scale."""
+ t_s = np.linspace(0.0, 1.0, 60)
+ t_ms = t_s * 1e-3
+ n_s = np.exp(-t_s / 0.1)
+ n_ms = np.exp(-t_ms / 0.1e-3)
+ assert np.allclose(n_s, n_ms)
+
+
+# ---------------------------------------------------------------------------
+# forward equations
+# ---------------------------------------------------------------------------
+
+
+def test_forward_equations_limits() -> None:
+ # Kelvin-Voigt: J(0) = 0, J(inf) = 1/E
+ j = forward_kelvin_voigt_compliance(T, 5e3, 0.1)
+ assert abs(float(forward_kelvin_voigt_compliance(np.array([0.0]), 5e3, 0.1)[0])) < 1e-12
+ assert abs(j[-1] - (1 / 5e3) * (1 - np.exp(-T[-1] / 0.1))) < 1e-15
+ # Maxwell: E(0) = E, decays to zero
+ m = forward_maxwell_modulus(T, 5e3, 0.1)
+ assert abs(float(forward_maxwell_modulus(np.array([0.0]), 5e3, 0.1)[0]) - 5e3) < 1e-9
+ assert m[-1] < 5e3 * np.exp(-5.0) # decayed by at least e^-5 at t/tau = 10
+ # SLS: E(inf) reached
+ s = forward_sls_modulus(T, 5e3, 2e3, 0.1)
+ assert abs(float(forward_sls_modulus(np.array([0.0]), 5e3, 2e3, 0.1)[0]) - 5e3) < 1e-9
+ assert abs(s[-1] - (2e3 + 3e3 * np.exp(-T[-1] / 0.1))) < 1e-15
+ # power law: self-similar
+ p = forward_power_law_modulus(T, 5e3, 0.3, 0.01)
+ p2 = forward_power_law_modulus(2 * T, 5e3, 0.3, 0.01)
+ assert np.allclose(p2 / p, 2.0 ** (-0.3), rtol=1e-12)
+
+
+def test_sls_conversions_roundtrip() -> None:
+ j0, j_inf, tau_ret = sls_relaxation_to_creep(5e3, 2e3, 0.1)
+ assert np.isclose(j0, 1 / 5e3) and np.isclose(j_inf, 1 / 2e3)
+ e0, e_inf, tau_rel = sls_creep_to_relaxation(j0, j_inf, tau_ret)
+ assert np.isclose(e0, 5e3, rtol=1e-12)
+ assert np.isclose(e_inf, 2e3, rtol=1e-12)
+ assert np.isclose(tau_rel, 0.1, rtol=1e-12)
+
+
+def test_prony_duplicate_tau_rejected() -> None:
+ with pytest.raises(ViscoelasticityError) as ei:
+ forward_generalized_maxwell_modulus(
+ T, 2e3, np.array([[1e3, 0.01], [1e3, 0.01]]))
+ assert ei.value.code == "PRONY_DUPLICATE_TAU"
+ with pytest.raises(ViscoelasticityError):
+ forward_generalized_maxwell_normalized(T, np.array([0.5, 0.5]),
+ np.array([0.01, 0.01]))
+
+
+def test_power_law_singularity_excluded() -> None:
+ with pytest.raises(ViscoelasticityError) as ei:
+ forward_power_law_modulus(np.array([0.0, 1e-3]), 5e3, 0.3, 0.01)
+ assert ei.value.code == "INVALID_MODEL_PARAMETER"
+ with pytest.raises(ViscoelasticityError):
+ forward_power_law_modulus(T, 5e3, 1.5, 0.01)
+
+
+def test_lee_radok_rejects_nonmonotonic() -> None:
+ t = np.linspace(0.0, 1.0, 50)
+ d = np.linspace(0.0, 5e-7, 50)
+ d[30] = d[29] - 1e-8 # a decrease
+ with pytest.raises(ViscoelasticityError) as ei:
+ lee_radok_force(t, d, {"E0": 5e3, "E_inf": 2e3, "tau": 0.1}, 1.0, 1e-6, 0.3)
+ assert ei.value.code == "LEE_RADOK_NONMONOTONIC"
+
+
+def test_ting_requires_history() -> None:
+ t_l = np.linspace(0.0, 1.0, 50)
+ d_l = np.linspace(0.0, 5e-7, 50)
+ t_u = np.linspace(1.1, 2.0, 50)
+ d_u = np.linspace(5e-7, 0.0, 50)
+ d_u[10] = 6e-7 # exceeds the loading maximum
+ with pytest.raises(ViscoelasticityError) as ei:
+ ting_force(t_l, d_l, t_u, d_u, {"E0": 5e3, "E_inf": 2e3, "tau": 0.1},
+ 1.0, 1e-6, 0.3)
+ assert ei.value.code == "TING_HISTORY_UNAVAILABLE"
+
+
+def test_lee_radok_elastic_limit() -> None:
+ """With a non-relaxing modulus (E_inf = E0) Lee-Radok reduces to the
+ elastic hertz loading F = c delta^1.5."""
+ t = np.linspace(0.0, 1.0, 200)
+ d = 5e-7 * (t / 1.0) ** 0.7
+ f = lee_radok_force(t, d, {"E0": 5e3, "E_inf": 5e3, "tau": 1e9}, 1.0, 1e-6, 0.3)
+ c = spherical_coefficient(5e3, 1e-6, 0.3)
+ ref = c * d ** 1.5
+ assert np.allclose(f, ref, rtol=1e-6)
+
+
+def test_reduced_modulus_convention() -> None:
+ assert np.isclose(reduced_modulus(5e3, 0.3), 5e3 / (1 - 0.3**2))
+
+
+# ---------------------------------------------------------------------------
+# protocol identification
+# ---------------------------------------------------------------------------
+
+
+def _ramp_hold_curve(time: np.ndarray, height: np.ndarray, force: np.ndarray,
+ k: float = 10.0) -> ForceCurve:
+ def seg(st, d, z, f, t):
+ return ForceSegment(segment_type=st, direction=d, raw_height=z,
+ raw_deflection=f / k, time=t, cycle=0, state="force_n",
+ deflection=f / k, force=f, separation=None, metadata={})
+
+ n = time.size
+ n_ext = int(n * 0.85)
+ return ForceCurve(
+ segments=(seg("extend", "forward", height[:n_ext], force[:n_ext], time[:n_ext]),
+ seg("retract", "backward", height[n_ext:], force[n_ext:], time[n_ext:])),
+ calibration=Calibration(invols=3e-8, spring_constant=k, method="thermal",
+ temperature=300, provenance={}),
+ position=None, index=0, metadata={})
+
+
+def test_protocol_ramp_hold_classification() -> None:
+ """A ramp-hold curve is STRESS_RELAXATION (decaying hold force)."""
+ n_ramp, n_hold, n_pre, n_ret = 40, 120, 20, 20 # noqa: F841 (sizes)
+ dt = 1e-3
+ t = np.concatenate([
+ np.linspace(-n_pre * dt, -dt, n_pre),
+ np.linspace(0.0, 0.04, n_ramp),
+ 0.04 + np.linspace(dt, 0.12, n_hold),
+ np.linspace(0.161, 0.2, n_ret)])
+ h = np.concatenate([
+ np.linspace(1e-6, 3e-6, n_pre),
+ np.linspace(3e-6, 3.5e-6, n_ramp),
+ np.full(n_hold, 3.5e-6),
+ np.linspace(3.5e-6, 1e-6, n_ret)])
+ f0 = 1e-6
+ f = np.concatenate([
+ np.zeros(n_pre),
+ f0 * np.linspace(0.0, 1.0, n_ramp),
+ f0 * np.exp(-np.linspace(0.0, 0.12, n_hold) / 0.05),
+ np.linspace(f0 * np.exp(-0.12 / 0.05), 0.0, n_ret)])
+ curve = _ramp_hold_curve(t, h, f)
+ proto = identify_viscoelastic_protocol(curve)
+ assert proto.protocol_type == "STRESS_RELAXATION"
+ assert not proto.ambiguity
+
+
+def test_protocol_trusted_label_precedence() -> None:
+ t = np.linspace(0.0, 1.0, 100)
+ h = np.linspace(1e-6, 3e-6, 100)
+ f = np.linspace(0.0, 1e-6, 100)
+ curve = _ramp_hold_curve(t, h, f)
+ curve.metadata["protocol"] = "CREEP"
+ proto = identify_viscoelastic_protocol(curve)
+ assert proto.protocol_type == "CREEP"
+ assert proto.trusted_label == "protocol"
+
+
+def test_protocol_missing_time_fails_typed() -> None:
+ h = np.linspace(1e-6, 3e-6, 100)
+ f = np.linspace(0.0, 1e-6, 100)
+ curve = ForceCurve(
+ segments=(
+ ForceSegment(segment_type="extend", direction="forward", raw_height=h,
+ raw_deflection=f / 10.0, time=None, cycle=0, state="force_n",
+ deflection=f / 10.0, force=f, separation=None, metadata={}),
+ ForceSegment(segment_type="retract", direction="backward", raw_height=h[::-1],
+ raw_deflection=(f / 10.0)[::-1], time=None, cycle=0,
+ state="force_n", deflection=(f / 10.0)[::-1], force=f[::-1],
+ separation=None, metadata={})),
+ calibration=Calibration(invols=3e-8, spring_constant=10.0, method="thermal",
+ temperature=300, provenance={}),
+ position=None, index=0, metadata={})
+ with pytest.raises(ViscoelasticityError) as ei:
+ identify_viscoelastic_protocol(curve)
+ assert ei.value.code == "MISSING_TIME"
+ # explicit reconstructed clock is allowed
+ proto = identify_viscoelastic_protocol(curve, assume_uniform_rate=1e-3)
+ assert proto.protocol_type in ("LOADING_RAMP", "TRIANGULAR_LOADING",
+ "INSUFFICIENT_PROTOCOL")
+
+
+def test_protocol_duplicate_time_fails_typed() -> None:
+ t = np.linspace(0.0, 1.0, 100)
+ t[50] = t[49]
+ h = np.linspace(1e-6, 3e-6, 100)
+ f = np.linspace(0.0, 1e-6, 100)
+ curve = _ramp_hold_curve(t, h, f)
+ with pytest.raises(ViscoelasticityError) as ei:
+ identify_viscoelastic_protocol(curve)
+ assert ei.value.code == "DUPLICATE_TIMESTAMPS"
+
+
+# ---------------------------------------------------------------------------
+# lumped fits
+# ---------------------------------------------------------------------------
+
+
+def test_fit_maxwell_clean_recovery() -> None:
+ resp = _relaxation_response(tau=0.1)
+ fit = fit_maxwell(resp, tip_radius=1e-6)
+ assert abs(fit.parameters["tau"] - 0.1) / 0.1 < 0.01
+ assert fit.parameters["E"] > 0.0
+ assert fit.condition_number > 0.0
+
+
+def test_fit_maxwell_requires_relaxation_response() -> None:
+ with pytest.raises(ViscoelasticityError) as ei:
+ fit_maxwell(_creep_response()) # type: ignore[arg-type]
+ assert ei.value.code == "PROTOCOL_MODEL_MISMATCH"
+
+
+def test_fit_kelvin_voigt_clean_recovery() -> None:
+ resp = _creep_response(modulus=5e3, tau=0.1)
+ fit = fit_kelvin_voigt(resp)
+ assert abs(fit.parameters["E"] - 5e3) / 5e3 < 0.01
+ # E and tau are correlated in the near-plateau region: the honest
+ # recovery bound for tau is wider than for E
+ assert abs(fit.parameters["tau"] - 0.1) / 0.1 < 0.10
+
+
+def test_fit_sls_both_representations() -> None:
+ t = np.linspace(0.0, 1.0, 120)
+ nrm = 1.0 - 0.6 * (1.0 - np.exp(-t / 0.1))
+ resp = RelaxationResponseResult(
+ relative_time=t, indentation=np.full(120, 5e-7), force=1e-6 * nrm,
+ normalized_force=nrm, hold_indices=np.arange(120), hold_start_time=0.0,
+ force_at_hold_start=1e-6, equilibrium_force_estimate=float(nrm[-1]) * 1e-6,
+ warnings=())
+ fit = fit_standard_linear_solid(resp, tip_radius=1e-6)
+ assert abs(fit.parameters["a"] - 0.6) < 0.01
+ assert abs(fit.parameters["tau_relax"] - 0.1) / 0.1 < 0.01
+ # creep representation: the response is the compliance INCREMENT
+ # (J_inf - J0)(1 - exp(-t/tau_retard)); the absolute level J0 = 1/E0
+ # is carried by indentation_at_hold_start/F_hold
+ j_inc = (1 / 2e3 - 1 / 5e3) * (1.0 - np.exp(-t / (0.1 * 5e3 / 2e3)))
+ creep = CreepResponseResult(
+ relative_time=t, force=np.full(120, 1e-6),
+ indentation=1e-6 * (1 / 5e3 + j_inc),
+ compliance_proxy=j_inc, hold_indices=np.arange(120), hold_start_time=0.0,
+ force_hold_value=1e-6, indentation_at_hold_start=1e-6 * (1 / 5e3), warnings=())
+ fit2 = fit_standard_linear_solid(creep)
+ assert abs(fit2.parameters["E0"] - 5e3) / 5e3 < 0.01
+ assert abs(fit2.parameters["E_inf"] - 2e3) / 2e3 < 0.01
+
+
+def test_fit_generalized_maxwell_clean_recovery() -> None:
+ t = np.linspace(0.0, 2.0, 160)
+ alpha = np.array([0.4, 0.3])
+ tau = np.array([0.05, 0.5])
+ nrm = forward_generalized_maxwell_normalized(t, alpha, tau)
+ resp = RelaxationResponseResult(
+ relative_time=t, indentation=np.full(160, 5e-7), force=1e-6 * nrm,
+ normalized_force=nrm, hold_indices=np.arange(160), hold_start_time=0.0,
+ force_at_hold_start=1e-6, equilibrium_force_estimate=float(nrm[-1]) * 1e-6,
+ warnings=())
+ fit = fit_generalized_maxwell(resp, n_terms=2)
+ assert fit.success
+ taus = sorted([fit.parameters["tau_i[0]"], fit.parameters["tau_i[1]"]])
+ assert abs(taus[0] - 0.05) / 0.05 < 0.05
+ assert abs(taus[1] - 0.5) / 0.5 < 0.05
+ assert any("no claim" in w for w in fit.warnings)
+
+
+def test_fit_power_law_clean_recovery() -> None:
+ t = np.linspace(0.01, 1.0, 100)
+ nrm = (t / 0.01) ** (-0.3)
+ resp = RelaxationResponseResult(
+ relative_time=t, indentation=np.full(100, 5e-7), force=1e-6 * nrm,
+ normalized_force=nrm, hold_indices=np.arange(100), hold_start_time=0.0,
+ force_at_hold_start=1e-6, equilibrium_force_estimate=float(nrm[-1]) * 1e-6,
+ warnings=())
+ fit = fit_power_law_relaxation(resp)
+ assert abs(fit.parameters["alpha"] - 0.3) < 0.02
+
+
+def test_comparison_weights_and_ambiguity() -> None:
+ t = np.linspace(0.0, 2.0, 160)
+ nrm = np.exp(-t / 0.1)
+ resp = RelaxationResponseResult(
+ relative_time=t, indentation=np.full(160, 5e-7), force=1e-6 * nrm,
+ normalized_force=nrm, hold_indices=np.arange(160), hold_start_time=0.0,
+ force_at_hold_start=1e-6, equilibrium_force_estimate=float(nrm[-1]) * 1e-6,
+ warnings=())
+ cmp = compare_viscoelastic_models(resp, models=("maxwell", "standard_linear_solid"))
+ assert cmp.recommended_model == "maxwell"
+ assert cmp.weights["maxwell"] > 0.9
+ assert not cmp.ambiguous
+ assert "physical" not in str(cmp.provenance)
+
+
+def test_fit_deterministic_replay() -> None:
+ resp = _relaxation_response(tau=0.1, noise=1e-12)
+ a = fit_maxwell(resp, tip_radius=1e-6)
+ b = fit_maxwell(resp, tip_radius=1e-6)
+ assert a.parameters == b.parameters
+ assert a.aicc == b.aicc
diff --git a/tests/core/test_gwyddion_align_rows_facet_tilt.py b/tests/core/test_gwyddion_align_rows_facet_tilt.py
new file mode 100644
index 0000000..9a48ae0
--- /dev/null
+++ b/tests/core/test_gwyddion_align_rows_facet_tilt.py
@@ -0,0 +1,775 @@
+"""Public-contract tests for Gwyddion 2.71 Align Rows facet-tilt."""
+
+from __future__ import annotations
+
+import math
+import os
+from pathlib import Path
+from typing import Any
+
+import numpy as np
+import pytest
+
+import spmkit.core.analysis as analysis
+from spmkit.core.analysis._gwyddion_align_rows_facet_tilt import (
+ _gwyddion_align_rows_facet_tilt,
+ _GwyddionAlignRowsDirection,
+ _GwyddionFacetTiltResult,
+ _GwyddionMaskMode,
+)
+from spmkit.core.models import SPMChannel
+
+# ── helpers ──────────────────────────────────────────────────────────
+
+_ORACLE_PATH = (
+ Path(__file__).resolve().parents[1]
+ / "validation"
+ / "fixtures"
+ / "gwyddion"
+ / "facet_tilt"
+ / "oracle_facet_tilt.py"
+)
+
+
+def _import_oracle() -> Any: # pragma: no cover
+ import importlib.util
+
+ spec = importlib.util.spec_from_file_location(
+ "oracle_facet_tilt", str(_ORACLE_PATH)
+ )
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module) # type: ignore[union-attr]
+ return module
+
+
+def _build_field(yr: int, xr: int, func) -> np.ndarray:
+ """Build a (yr, xr) float64 field from a callable ``func(row, col)``."""
+ data = np.empty((yr, xr), dtype=np.float64)
+ for row in range(yr):
+ for col in range(xr):
+ data[row, col] = func(row, col)
+ return data
+
+
+def _base_func(row: int, col: int) -> float:
+ return float(
+ 2.0
+ + 0.12 * col
+ - 0.07 * row
+ + 0.015 * col * row
+ + 0.03 * math.sin(0.9 * col + 0.4 * row)
+ )
+
+
+def _spikes(row: int, col: int) -> float:
+ v = _base_func(row, col)
+ if row == 1 and col == 4:
+ v += 3.5
+ if row == 3 and col == 2:
+ v -= 4.0
+ if row == 4 and col == 6:
+ v += 1.2
+ return v
+
+
+# The 14 cases from the C probe campaign, keyed by case name
+_CASE_PARAMS: dict[str, dict[str, Any]] = {
+ "wide_curved_nomask": {
+ "yres": 5,
+ "xres": 7,
+ "xreal": 5.6,
+ "func": _spikes,
+ "mask_type": "none",
+ "masking": "ignore",
+ "direction": "horizontal",
+ "do_extract": True,
+ },
+ "wide_curved_includemask": {
+ "yres": 5,
+ "xres": 7,
+ "xreal": 5.6,
+ "func": _spikes,
+ "mask_type": "every2nd",
+ "masking": "include",
+ "direction": "horizontal",
+ "do_extract": False,
+ },
+ "wide_curved_excludemask": {
+ "yres": 5,
+ "xres": 7,
+ "xreal": 5.6,
+ "func": _spikes,
+ "mask_type": "cols234",
+ "masking": "exclude",
+ "direction": "horizontal",
+ "do_extract": False,
+ },
+ "wide_curved_ignoremask": {
+ "yres": 5,
+ "xres": 7,
+ "xreal": 5.6,
+ "func": _spikes,
+ "mask_type": "every2nd",
+ "masking": "ignore",
+ "direction": "horizontal",
+ "do_extract": False,
+ },
+ "constant_rows_5x4": {
+ "yres": 4,
+ "xres": 5,
+ "xreal": 4.0,
+ "func": lambda r, c: 7.0,
+ "mask_type": "none",
+ "masking": "ignore",
+ "direction": "horizontal",
+ "do_extract": False,
+ },
+ "constant_rows_nonzero_5x4": {
+ "yres": 4,
+ "xres": 5,
+ "xreal": 4.0,
+ "func": None, # replaced in _build_case_data
+ "mask_type": "none",
+ "masking": "ignore",
+ "direction": "horizontal",
+ "do_extract": True,
+ },
+ "exactly_linear_rows": {
+ "yres": 4,
+ "xres": 5,
+ "xreal": 4.0,
+ "func": lambda r, c: {
+ 0: 0.0 + 1.0 * c,
+ 1: 2.0 + 3.0 * c,
+ 2: -1.0 - 2.0 * c,
+ 3: 4.0 + 0.5 * c,
+ }[r],
+ "mask_type": "none",
+ "masking": "ignore",
+ "direction": "horizontal",
+ "do_extract": False,
+ },
+ "nearly_linear_rows": {
+ "yres": 4,
+ "xres": 5,
+ "xreal": 4.0,
+ "func": lambda r, c: {
+ 0: 0.0 + 1.0 * c,
+ 1: 2.0 + 3.0 * c,
+ 2: -1.0 - 2.0 * c,
+ 3: 4.0 + 0.5 * c,
+ }[r]
+ + 1e-13 * math.sin(c + r * 17.0),
+ "mask_type": "none",
+ "masking": "ignore",
+ "direction": "horizontal",
+ "do_extract": False,
+ },
+ "large_outlier": {
+ "yres": 5,
+ "xres": 7,
+ "xreal": 5.6,
+ "func": lambda r, c: 1e10 if (r == 2 and c == 3) else 0.0,
+ "mask_type": "none",
+ "masking": "ignore",
+ "direction": "horizontal",
+ "do_extract": False,
+ },
+ "repeated_outlier": {
+ "yres": 5,
+ "xres": 7,
+ "xreal": 5.6,
+ "func": lambda r, c: 1e10
+ if (r == 2 and c in (1, 2, 6)) or (r == 3 and c == 3)
+ else 0.0,
+ "mask_type": "none",
+ "masking": "ignore",
+ "direction": "horizontal",
+ "do_extract": False,
+ },
+ "two_column_row": {
+ "yres": 3,
+ "xres": 2,
+ "xreal": 2.0,
+ "func": lambda r, c: {
+ 0: {0: 0.0, 1: 1.0},
+ 1: {0: 5.0, 1: 10.0},
+ 2: {0: -3.0, 1: 7.0},
+ }[r][c],
+ "mask_type": "none",
+ "masking": "ignore",
+ "direction": "horizontal",
+ "do_extract": False,
+ },
+ "vertical_direction": {
+ "yres": 5,
+ "xres": 7,
+ "xreal": 5.6,
+ "func": _spikes,
+ "mask_type": "none",
+ "masking": "ignore",
+ "direction": "vertical",
+ "do_extract": True,
+ },
+ "fractional_mask": {
+ "yres": 4,
+ "xres": 5,
+ "xreal": 4.0,
+ "func": _spikes, # cropped to 4×5 by taking first 4 rows, first 5 cols
+ "mask_type": "fractional",
+ "masking": "exclude",
+ "direction": "horizontal",
+ "do_extract": False,
+ },
+ "fractional_mask_include": {
+ "yres": 4,
+ "xres": 5,
+ "xreal": 4.0,
+ "func": _spikes, # same cropped data
+ "mask_type": "fractional",
+ "masking": "include",
+ "direction": "horizontal",
+ "do_extract": False,
+ },
+ "two_column_vertical": {
+ "yres": 2,
+ "xres": 3,
+ "xreal": 3.0,
+ "func": lambda r, c: {
+ 0: {0: 0.0, 1: 1.0, 2: 2.0},
+ 1: {0: 5.0, 1: 10.0, 2: 15.0},
+ }[r][c],
+ "mask_type": "none",
+ "masking": "ignore",
+ "direction": "vertical",
+ "do_extract": False,
+ },
+}
+
+
+def _build_mask(mask_type: str, yres: int, xres: int) -> np.ndarray | None:
+ if mask_type == "none":
+ return None
+ mask = np.empty((yres, xres), dtype=np.float64)
+ if mask_type == "every2nd":
+ for row in range(yres):
+ for col in range(xres):
+ mask[row, col] = 1.0 if (row * xres + col) % 2 == 0 else 0.0
+ elif mask_type == "cols234":
+ for row in range(yres):
+ for col in range(xres):
+ mask[row, col] = 1.0 if 2 <= col <= 4 else 0.0
+ elif mask_type == "fractional":
+ pattern = [0.0, 0.999999, 1.0, 1.000001, 0.5]
+ for row in range(yres):
+ for col in range(xres):
+ mask[row, col] = pattern[col % 5]
+ else:
+ raise ValueError(f"Unknown mask_type: {mask_type}")
+ return mask
+
+
+def _build_case_data(case_name: str) -> tuple[np.ndarray, np.ndarray | None, dict[str, Any]]:
+ params = _CASE_PARAMS[case_name]
+ yres, xres = params["yres"], params["xres"]
+
+ func = params["func"]
+ if func is not None:
+ data = _build_field(yres, xres, func)
+ else:
+ data = np.empty((yres, xres), dtype=np.float64)
+
+ # For fractional_mask cases, use cropped spikes data (first 4 rows, first 5 cols)
+ if case_name in ("fractional_mask", "fractional_mask_include"):
+ # Build the full 7×5 field from wide_curved data and crop to 4×5
+ full_data = _build_field(5, 7, _spikes)
+ data = full_data[:4, :5].copy(order="C")
+
+ # Fix constant_rows_nonzero_5x4
+ if case_name == "constant_rows_nonzero_5x4":
+ row_vals = [-3.5, 0.0, 7.0, 2.5]
+ for row in range(4):
+ data[row, :] = row_vals[row]
+
+ mask = _build_mask(params["mask_type"], yres, xres)
+ return data, mask, params
+
+
+def _run_kernel(
+ data: np.ndarray,
+ mask: np.ndarray | None,
+ params: dict[str, Any],
+ extract_background: bool | None = None,
+) -> _GwyddionFacetTiltResult:
+ mode_map = {
+ "ignore": _GwyddionMaskMode.IGNORE,
+ "include": _GwyddionMaskMode.INCLUDE,
+ "exclude": _GwyddionMaskMode.EXCLUDE,
+ }
+ dir_map = {
+ "horizontal": _GwyddionAlignRowsDirection.HORIZONTAL,
+ "vertical": _GwyddionAlignRowsDirection.VERTICAL,
+ }
+ dx = params["xreal"] / params["xres"]
+ do_extract = params["do_extract"] if extract_background is None else extract_background
+ return _gwyddion_align_rows_facet_tilt(
+ data,
+ masking_mode=mode_map[params["masking"]],
+ direction=dir_map[params["direction"]],
+ dx=dx,
+ mask=mask,
+ extract_background=do_extract,
+ )
+
+
+def _max_numeric_diff(a: np.ndarray, b: np.ndarray) -> float:
+ max_diff = 0.0
+ for i in range(a.size):
+ av = float(a.flat[i])
+ bv = float(b.flat[i])
+ if np.isnan(av) and np.isnan(bv):
+ continue
+ if not (np.isnan(av) or np.isnan(bv)):
+ diff = abs(av - bv)
+ if diff > max_diff:
+ max_diff = diff
+ return max_diff
+
+
+def _nan_count_match(a: np.ndarray, b: np.ndarray) -> bool:
+ return np.sum(np.isnan(a)) == np.sum(np.isnan(b))
+
+
+def _inf_count_match(a: np.ndarray, b: np.ndarray) -> bool:
+ return np.sum(np.isinf(a)) == np.sum(np.isinf(b))
+
+
+# ---------------------------------------------------------------------------
+# 1. Oracle vs C probe
+# ---------------------------------------------------------------------------
+
+_C_PROBE_ROOT = "/tmp/spmkit_gwyddion_facet_tilt_probe/normal"
+
+
+def _load_probe_corrected(case_name: str, yres: int, xres: int) -> np.ndarray:
+ stdout_path = os.path.join(_C_PROBE_ROOT, f"{case_name}.stdout")
+ if not os.path.exists(stdout_path):
+ pytest.skip("C probe output not available")
+ with open(stdout_path) as f:
+ lines = f.read().splitlines()
+ d: dict[int, float] = {}
+ prefix = f"{case_name}_corrected_"
+ for line in lines:
+ if line.startswith(prefix):
+ rest = line[len(prefix) :]
+ if rest and rest[0].isdigit():
+ idx_str, val_str = rest.split("=", 1)
+ d[int(idx_str)] = float(val_str)
+ result = np.empty((yres, xres), dtype=np.float64)
+ for row in range(yres):
+ for col in range(xres):
+ result[row, col] = d.get(row * xres + col, float("nan"))
+ return result
+
+
+def _load_probe_background(case_name: str, yres: int, xres: int) -> np.ndarray:
+ stdout_path = os.path.join(_C_PROBE_ROOT, f"{case_name}.stdout")
+ if not os.path.exists(stdout_path):
+ pytest.skip("C probe output not available")
+ with open(stdout_path) as f:
+ lines = f.read().splitlines()
+ d: dict[int, float] = {}
+ prefix = f"{case_name}_background_"
+ for line in lines:
+ if line.startswith(prefix):
+ rest = line[len(prefix) :]
+ if rest and rest[0].isdigit():
+ idx_str, val_str = rest.split("=", 1)
+ d[int(idx_str)] = float(val_str)
+ if not d:
+ return np.empty((0, 0)) # no background output
+ result = np.empty((yres, xres), dtype=np.float64)
+ for row in range(yres):
+ for col in range(xres):
+ result[row, col] = d.get(row * xres + col, float("nan"))
+ return result
+
+
+def _load_probe_shifts(case_name: str) -> np.ndarray | None:
+ """Parse ALL probe shift values (never truncate)."""
+ stdout_path = os.path.join(_C_PROBE_ROOT, f"{case_name}.stdout")
+ if not os.path.exists(stdout_path):
+ pytest.skip("C probe output not available")
+ with open(stdout_path) as f:
+ lines = f.read().splitlines()
+ d: dict[int, float] = {}
+ prefix = f"{case_name}_shifts_"
+ for line in lines:
+ if line.startswith(prefix):
+ rest = line[len(prefix):]
+ if rest and rest[0].isdigit():
+ idx_str, val_str = rest.split("=", 1)
+ d[int(idx_str)] = float(val_str)
+ if not d:
+ return None
+ max_idx = max(d.keys())
+ result = np.empty(max_idx + 1, dtype=np.float64)
+ for i in range(max_idx + 1):
+ result[i] = d.get(i, np.nan)
+ return result
+
+
+@pytest.mark.parametrize(
+ "case_name",
+ list(_CASE_PARAMS),
+)
+def test_facet_tilt_oracle_vs_probe(case_name: str) -> None:
+ """Oracle matches the compiled Gwyddion 2.71 source-inclusion probe output."""
+ oracle_mod = _import_oracle()
+ data, mask, params = _build_case_data(case_name)
+ if data is None:
+ return
+ yres, xres = data.shape
+ dx = params["xreal"] / params["xres"]
+
+ oracle_corr, oracle_bg, oracle_shifts = oracle_mod.oracle_facet_tilt(
+ data.copy(order="C"),
+ mask.copy(order="C") if mask is not None else None,
+ params["masking"],
+ dx,
+ params["direction"],
+ params["do_extract"],
+ )
+
+ probe_corr = _load_probe_corrected(case_name, yres, xres)
+ max_diff = _max_numeric_diff(oracle_corr, probe_corr)
+ assert _nan_count_match(oracle_corr, probe_corr)
+ assert _inf_count_match(oracle_corr, probe_corr)
+ assert max_diff < 1e-14, (
+ f"Oracle vs probe corrected max diff {max_diff:.17g} for {case_name}"
+ )
+
+ if params["do_extract"]:
+ probe_bg = _load_probe_background(case_name, yres, xres)
+ bg_diff = _max_numeric_diff(oracle_bg, probe_bg)
+ assert bg_diff < 1e-14, (
+ f"Oracle vs probe background max diff {bg_diff:.17g} for {case_name}"
+ )
+
+ # Compare shifts: shape AND values against the probe
+ probe_shifts = _load_probe_shifts(case_name)
+ assert probe_shifts is not None, f"No probe shifts found for {case_name}"
+ assert probe_shifts.shape == oracle_shifts.shape, (
+ f"Shifts shape mismatch for {case_name}: "
+ f"probe {probe_shifts.shape} vs oracle {oracle_shifts.shape}"
+ )
+ assert np.all(probe_shifts == 0.0)
+ assert np.all(oracle_shifts == 0.0)
+
+
+# ---------------------------------------------------------------------------
+# 2. Oracle vs kernel (bitwise exact)
+# ---------------------------------------------------------------------------
+
+@pytest.mark.parametrize("case_name", list(_CASE_PARAMS))
+def test_facet_tilt_oracle_vs_kernel(case_name: str) -> None:
+ """Private SPMKit kernel matches the oracle bitwise."""
+ oracle_mod = _import_oracle()
+ data, mask, params = _build_case_data(case_name)
+ if data is None:
+ return
+ dx = params["xreal"] / params["xres"]
+
+ kernel_result = _run_kernel(data, mask, params)
+ oracle_corr, oracle_bg, oracle_shifts = oracle_mod.oracle_facet_tilt(
+ data.copy(order="C"),
+ mask.copy(order="C") if mask is not None else None,
+ params["masking"],
+ dx,
+ params["direction"],
+ params["do_extract"],
+ )
+
+ assert _max_numeric_diff(kernel_result.corrected, oracle_corr) == 0.0
+ if params["do_extract"]:
+ assert _max_numeric_diff(kernel_result.background, oracle_bg) == 0.0
+ # Shifts: shape and values must match bitwise
+ assert kernel_result.shifts.shape == oracle_shifts.shape, (
+ f"Shifts shape mismatch for {case_name}: "
+ f"kernel {kernel_result.shifts.shape} vs oracle {oracle_shifts.shape}"
+ )
+ assert np.all(kernel_result.shifts == 0.0)
+ assert np.all(oracle_shifts == 0.0)
+
+
+# ---------------------------------------------------------------------------
+# 3. Constant-row NaN behaviour
+# ---------------------------------------------------------------------------
+
+def test_facet_tilt_constant_row_nan() -> None:
+ """A uniformly constant row produces NaN (IEEE 0/0 in sigma2)."""
+ data = np.full((3, 5), 7.0, dtype=np.float64)
+ result = _gwyddion_align_rows_facet_tilt(
+ data,
+ masking_mode=_GwyddionMaskMode.IGNORE,
+ direction=_GwyddionAlignRowsDirection.HORIZONTAL,
+ dx=1.0,
+ )
+ assert np.isnan(result.corrected).all()
+
+
+# ---------------------------------------------------------------------------
+# 4. Two-column row mincount guard
+# ---------------------------------------------------------------------------
+
+def test_facet_tilt_two_column_row() -> None:
+ """A 2-column row has n = 1, which is below mincount = 2, so stays unchanged."""
+ data = np.array([[0.0, 1.0], [5.0, 10.0], [-3.0, 7.0]], dtype=np.float64)
+ result = _gwyddion_align_rows_facet_tilt(
+ data,
+ masking_mode=_GwyddionMaskMode.IGNORE,
+ direction=_GwyddionAlignRowsDirection.HORIZONTAL,
+ dx=1.0,
+ )
+ assert np.array_equal(result.corrected, data)
+ assert np.all(result.shifts == 0.0)
+
+
+# ---------------------------------------------------------------------------
+# 5. Exactly linear row behaviour
+# ---------------------------------------------------------------------------
+
+def test_facet_tilt_exactly_linear() -> None:
+ """A perfectly linear row becomes constant after the first untilt, then NaNs.
+
+ Gwyddion source confirmation: the first iteration removes the true
+ slope exactly, making sigma2 zero in the second iteration, which
+ triggers an FP NaN chain.
+ """
+ data = np.array(
+ [
+ [0.0, 1.0, 2.0, 3.0, 4.0], # b=1, a=0
+ ],
+ dtype=np.float64,
+ )
+ oracle_mod = _import_oracle()
+ corrected, bg, shifts = oracle_mod.oracle_facet_tilt(
+ data.copy(), None, "ignore", 1.0, "horizontal", False
+ )
+ assert np.all(np.isnan(corrected))
+ assert np.all(shifts == 0.0)
+
+
+# ---------------------------------------------------------------------------
+# 6. Convergence cap at 30 iterations
+# ---------------------------------------------------------------------------
+
+def test_facet_tilt_convergence_cap() -> None:
+ """NaN-producing rows stop at 30 iterations, never infinite."""
+ data = np.full((1, 5), 7.0, dtype=np.float64)
+ # The function does not raise; it returns NaN result after 30 iterations.
+ result = _gwyddion_align_rows_facet_tilt(
+ data,
+ masking_mode=_GwyddionMaskMode.IGNORE,
+ direction=_GwyddionAlignRowsDirection.HORIZONTAL,
+ dx=1.0,
+ )
+ assert result.corrected.shape == (1, 5)
+
+
+# ---------------------------------------------------------------------------
+# 7. Input non-mutation
+# ---------------------------------------------------------------------------
+
+def test_facet_tilt_nonmutation() -> None:
+ """Input data is not modified by processing."""
+ data, mask, params = _build_case_data("wide_curved_nomask")
+ original = data.copy(order="C")
+ _run_kernel(data, mask, params)
+ assert np.array_equal(data, original)
+
+
+# ---------------------------------------------------------------------------
+# 8. Mask semantics
+# ---------------------------------------------------------------------------
+
+def test_facet_tilt_mask_semantics() -> None:
+ """EXCLUDE uses ``<= 0.0``, INCLUDE uses ``>= 1.0``, tested via fractional mask."""
+ data, mask, params = _build_case_data("fractional_mask")
+ result = _run_kernel(data, mask, params)
+ # No NaN expected — the fractional mask has enough non-excluded columns
+ assert result.corrected.shape == data.shape
+
+ data2, mask2, params2 = _build_case_data("fractional_mask_include")
+ result2 = _run_kernel(data2, mask2, params2)
+ assert result2.corrected.shape == data2.shape
+
+
+# ---------------------------------------------------------------------------
+# 9. Direction transpose consistency
+# ---------------------------------------------------------------------------
+
+def test_facet_tilt_direction_transpose() -> None:
+ """HORIZONTAL and VERTICAL produce transpose-consistent corrected outputs."""
+ data, mask, params = _build_case_data("vertical_direction")
+ dx = params["xreal"] / params["xres"]
+
+ h_result = _gwyddion_align_rows_facet_tilt(
+ data,
+ masking_mode=_GwyddionMaskMode.IGNORE,
+ direction=_GwyddionAlignRowsDirection.HORIZONTAL,
+ dx=dx,
+ extract_background=True,
+ )
+ v_result = _gwyddion_align_rows_facet_tilt(
+ data,
+ masking_mode=_GwyddionMaskMode.IGNORE,
+ direction=_GwyddionAlignRowsDirection.VERTICAL,
+ dx=dx,
+ extract_background=True,
+ )
+
+ # H & V should differ (different processing axis)
+ assert not np.allclose(h_result.corrected, v_result.corrected, equal_nan=True)
+
+
+# ---------------------------------------------------------------------------
+# 10. Background = input - corrected
+# ---------------------------------------------------------------------------
+
+def test_facet_tilt_background_identity() -> None:
+ """background == input - corrected elementwise in C order."""
+ data, mask, params = _build_case_data("wide_curved_nomask")
+ result = _run_kernel(data, mask, {**params, "do_extract": True})
+ expected_bg = np.empty_like(data, order="C")
+ for row in range(data.shape[0]):
+ for col in range(data.shape[1]):
+ expected_bg[row, col] = data[row, col] - result.corrected[row, col]
+ assert _max_numeric_diff(result.background, expected_bg) == 0.0
+
+
+# ---------------------------------------------------------------------------
+# 11. Shifts are always zero
+# ---------------------------------------------------------------------------
+
+def test_facet_tilt_shifts_zero() -> None:
+ """shifts output is the zero vector, matching ``gwy_data_line_clear``.
+
+ For HORIZONTAL the shifts length equals yres; for VERTICAL it equals
+ xres (the working field's y-resolution after transpose).
+ """
+ data, mask, params = _build_case_data("wide_curved_nomask")
+ result = _run_kernel(data, mask, params)
+ assert result.shifts.size == data.shape[0]
+ assert np.all(result.shifts == 0.0)
+
+ # VERTICAL: shifts length = original xres
+ data2, mask2, params2 = _build_case_data("vertical_direction")
+ result2 = _run_kernel(data2, mask2, params2)
+ assert result2.shifts.size == data2.shape[1], (
+ f"VERTICAL shifts size {result2.shifts.size} != xres {data2.shape[1]}"
+ )
+ assert np.all(result2.shifts == 0.0)
+
+
+# ---------------------------------------------------------------------------
+# 12. Metamorphic: adding a constant to all rows
+# ---------------------------------------------------------------------------
+
+def test_facet_tilt_metamorphic_constant_shift() -> None:
+ """Adding a constant C to the entire field shifts corrected by C;
+ the slope-estimation algorithm subtracts tilt only."""
+ data, mask, params = _build_case_data("wide_curved_nomask")
+ dx = params["xreal"] / params["xres"]
+
+ result = _gwyddion_align_rows_facet_tilt(
+ data,
+ masking_mode=_GwyddionMaskMode.IGNORE,
+ direction=_GwyddionAlignRowsDirection.HORIZONTAL,
+ dx=dx,
+ extract_background=True,
+ )
+
+ c = 100.0
+ shifted = data + c
+ result_shifted = _gwyddion_align_rows_facet_tilt(
+ shifted,
+ masking_mode=_GwyddionMaskMode.IGNORE,
+ direction=_GwyddionAlignRowsDirection.HORIZONTAL,
+ dx=dx,
+ extract_background=True,
+ )
+
+ corrected_diff = _max_numeric_diff(
+ result_shifted.corrected, result.corrected + c
+ )
+ # The corrected output should shift by ~C (within a few ULPs)
+ assert corrected_diff < 1e-13, (
+ f"Constant-shift metamorphism failed: diff {corrected_diff:.17g}"
+ )
+
+ # Background should be identical (tilt only, no offset)
+ bg_diff = _max_numeric_diff(result_shifted.background, result.background)
+ assert bg_diff < 1e-13, (
+ f"Background should be constant-shift invariant: diff {bg_diff:.17g}"
+ )
+
+
+# ---------------------------------------------------------------------------
+# 13. Iteration limit (slow convergence)
+# ---------------------------------------------------------------------------
+
+def test_facet_tilt_iteration_limit() -> None:
+ """Verify the 30-iteration cap does not loop infinitely."""
+ # A row with very large slope needs many iterations but converges within 30.
+ # row: d[col] = 1000 * col, xres = 7, dx = 1
+ data = np.array(
+ [
+ [0.0, 1000.0, 2000.0, 3000.0, 4000.0, 5000.0, 6000.0],
+ ],
+ dtype=np.float64,
+ )
+ result = _gwyddion_align_rows_facet_tilt(
+ data,
+ masking_mode=_GwyddionMaskMode.IGNORE,
+ direction=_GwyddionAlignRowsDirection.HORIZONTAL,
+ dx=1.0,
+ )
+ # After convergence, the row becomes constant (about the centre value).
+ # It should NOT be all NaN (unlike the exactly-linear case which has
+ # a different issue — the 2nd iteration becomes constant, producing NaN).
+ # With large slope, exp(vx^2/sigma2) ≈ exp(200) ≈ huge but finite,
+ # so the row converges properly after 1-2 iterations.
+ assert not np.all(np.isnan(result.corrected))
+ assert not np.all(np.isinf(result.corrected))
+
+
+# ---------------------------------------------------------------------------
+# 14. Public API type errors
+# ---------------------------------------------------------------------------
+
+def test_facet_tilt_public_api_type_errors() -> None:
+ """gwyddion_align_rows_facet_tilt rejects invalid inputs."""
+ channel = SPMChannel(
+ data=np.ones((5, 7), dtype=np.float64),
+ x_range=5.6,
+ y_range=6.5,
+ name="test",
+ unit="m",
+ )
+
+ result = analysis.gwyddion_align_rows_facet_tilt(channel)
+ assert result.data.shape == (5, 7)
+ assert isinstance(result, SPMChannel)
+
+ # TypeError for non-channel
+ with pytest.raises(TypeError):
+ analysis.gwyddion_align_rows_facet_tilt("not a channel") # type: ignore[arg-type]
+
+ # ValueError for bad mask_mode
+ with pytest.raises(ValueError):
+ analysis.gwyddion_align_rows_facet_tilt(channel, mask_mode="bogus") # type: ignore[arg-type]
+
+ # ValueError for bad direction
+ with pytest.raises(ValueError):
+ analysis.gwyddion_align_rows_facet_tilt(channel, direction="diagonal") # type: ignore[arg-type]
diff --git a/tests/core/test_gwyddion_derivative_filters.py b/tests/core/test_gwyddion_derivative_filters.py
new file mode 100644
index 0000000..21ddb25
--- /dev/null
+++ b/tests/core/test_gwyddion_derivative_filters.py
@@ -0,0 +1,416 @@
+"""Core tests for the A2 derivative-filter production batch (no fixtures).
+
+Covers common validation, Sobel/Prewitt semantics, magnitude relations and
+the native direction composite. These tests never load the persistent
+fixture files.
+"""
+
+from __future__ import annotations
+
+import math
+
+import numpy as np
+import pytest
+
+from spmkit.core.analysis import (
+ gradient_direction,
+ gwyddion_gradient_magnitude,
+ gwyddion_prewitt_x,
+ gwyddion_prewitt_y,
+ gwyddion_sobel_x,
+ gwyddion_sobel_y,
+)
+from spmkit.core.models import SPMChannel
+
+
+def _channel(data: np.ndarray, unit: str = "m") -> SPMChannel:
+ return SPMChannel(
+ name="Z-Axis",
+ data=np.asarray(data, dtype=np.float64),
+ unit=unit,
+ x_range=5e-6,
+ y_range=4e-6,
+ direction="forward",
+ metadata={"Dim1Name": "X"},
+ )
+
+
+def _pair(data_x: np.ndarray, data_y: np.ndarray) -> tuple[SPMChannel, SPMChannel]:
+ return _channel(data_x), _channel(data_y)
+
+
+# ---------------------------------------------------------------- common ---
+
+
+def test_invalid_dimensions() -> None:
+ with pytest.raises(ValueError):
+ gwyddion_sobel_x(_channel(np.zeros((5,))))
+ with pytest.raises(ValueError):
+ gwyddion_sobel_y(_channel(np.zeros((5, 5, 5))))
+
+
+def test_empty_arrays() -> None:
+ with pytest.raises(ValueError):
+ gwyddion_sobel_x(_channel(np.zeros((0, 5))))
+ with pytest.raises(ValueError):
+ gwyddion_sobel_x(_channel(np.zeros((5, 0))))
+
+
+def test_complex_input() -> None:
+ complex_channel = SPMChannel(
+ name="Z",
+ data=np.zeros((5, 5), dtype=np.complex128),
+ unit="m",
+ x_range=5e-6,
+ y_range=4e-6,
+ direction="forward",
+ )
+ with pytest.raises(TypeError):
+ gwyddion_sobel_x(complex_channel)
+
+
+def test_nan_inf_rejection() -> None:
+ for value in (np.nan, np.inf, -np.inf):
+ data = np.zeros((5, 5))
+ data[2, 2] = value
+ with pytest.raises(ValueError):
+ gwyddion_sobel_x(_channel(data))
+ with pytest.raises(ValueError):
+ gwyddion_prewitt_y(_channel(data))
+
+
+def test_context_preservation() -> None:
+ channel = _channel(np.arange(25.0).reshape(5, 5))
+ result = gwyddion_sobel_x(channel)
+ assert result.name == channel.name
+ assert result.unit == channel.unit
+ assert result.x_range == channel.x_range
+ assert result.y_range == channel.y_range
+ assert result.direction == channel.direction
+ assert result.metadata == channel.metadata
+ assert result.data.shape == channel.data.shape
+
+
+def test_non_mutation_and_storage_independence() -> None:
+ data = np.arange(25.0).reshape(5, 5)
+ channel = _channel(data)
+ original = data.copy()
+ result = gwyddion_sobel_x(channel)
+ assert np.array_equal(data, original)
+ result.data[0, 0] = 12345.0
+ assert np.array_equal(data, original)
+
+
+def test_no_public_mask_roi_border_parameters() -> None:
+ import inspect
+
+ for fn in (gwyddion_sobel_x, gwyddion_sobel_y, gwyddion_prewitt_x, gwyddion_prewitt_y):
+ params = inspect.signature(fn).parameters
+ assert set(params) == {"channel"}
+ for fn in (gwyddion_gradient_magnitude, gradient_direction):
+ params = inspect.signature(fn).parameters
+ assert set(params) == {"gx", "gy"}
+
+
+# -------------------------------------------------------- sobel / prewitt ---
+
+
+def test_constants_vanish_within_rounding() -> None:
+ channel = _channel(np.full((5, 5), 2.5))
+ for fn in (gwyddion_sobel_x, gwyddion_sobel_y, gwyddion_prewitt_x, gwyddion_prewitt_y):
+ result = fn(channel)
+ # frozen source arithmetic may leave a ~1e-16 residue; it is not
+ # forced to exactly zero
+ assert np.max(np.abs(result.data)) <= 1e-12
+
+
+def test_ramp_signs() -> None:
+ ramp_x = np.tile(np.arange(5.0), (5, 1))
+ ramp_y = np.tile(np.arange(5.0)[:, None], (1, 5))
+ assert float(gwyddion_sobel_x(_channel(ramp_x)).data[2, 2]) == -2.0
+ assert float(gwyddion_sobel_y(_channel(ramp_y)).data[2, 2]) == -2.0
+ assert float(gwyddion_sobel_x(_channel(-ramp_x)).data[2, 2]) == 2.0
+ assert float(gwyddion_sobel_y(_channel(-ramp_y)).data[2, 2]) == 2.0
+ assert float(gwyddion_prewitt_x(_channel(ramp_x)).data[2, 2]) == -2.0
+ assert float(gwyddion_prewitt_y(_channel(ramp_y)).data[2, 2]) == -2.0
+
+
+def test_diagonal_ramp() -> None:
+ diag = np.add.outer(np.arange(5.0), np.arange(5.0))
+ sx = gwyddion_sobel_x(_channel(diag)).data
+ sy = gwyddion_sobel_y(_channel(diag)).data
+ assert float(sx[2, 2]) == -2.0
+ assert float(sy[2, 2]) == -2.0
+
+
+def test_impulse_kernels() -> None:
+ impulse = np.zeros((5, 5))
+ impulse[2, 2] = 1.0
+ channel = _channel(impulse)
+ expected = {
+ gwyddion_sobel_x: [0.25, 0.0, -0.25, 0.5, 0.0, -0.5, 0.25, 0.0, -0.25],
+ gwyddion_sobel_y: [0.25, 0.5, 0.25, 0.0, 0.0, 0.0, -0.25, -0.5, -0.25],
+ gwyddion_prewitt_x: [1.0 / 3.0, 0.0, -1.0 / 3.0] * 3,
+ gwyddion_prewitt_y: [
+ 1.0 / 3.0,
+ 1.0 / 3.0,
+ 1.0 / 3.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ -1.0 / 3.0,
+ -1.0 / 3.0,
+ -1.0 / 3.0,
+ ],
+ }
+ for fn, coeffs in expected.items():
+ window = fn(channel).data[1:4, 1:4]
+ flipped = np.array(coeffs).reshape(3, 3)[::-1, ::-1]
+ assert np.array_equal(window, flipped)
+
+
+def test_corner_and_edge_impulses() -> None:
+ corner = np.zeros((5, 5))
+ corner[0, 0] = 1.0
+ assert float(gwyddion_sobel_x(_channel(corner)).data[0, 0]) == 0.75
+ assert float(gwyddion_prewitt_x(_channel(corner)).data[0, 0]) == 2.0 / 3.0
+ edge = np.zeros((5, 5))
+ edge[0, 2] = 1.0
+ assert float(gwyddion_sobel_y(_channel(edge)).data[0, 2]) == 0.5
+
+
+def test_clipped_border_policy() -> None:
+ # the top row re-reads itself for kernel rows 0..1: an x-ramp keeps the
+ # full -2.0 interior response on the top row as well
+ ramp_x = np.tile(np.arange(5.0), (5, 1))
+ sx = gwyddion_sobel_x(_channel(ramp_x)).data
+ assert float(sx[0, 2]) == -2.0
+ # bottom row: kernel row 2 folds onto the last row
+ assert float(sx[4, 2]) == -2.0
+
+
+def test_transpose_relation() -> None:
+ ramp_x = np.tile(np.arange(5.0), (5, 1))
+ ramp_y = ramp_x.T
+ sx = gwyddion_sobel_x(_channel(ramp_x)).data
+ sy = gwyddion_sobel_y(_channel(ramp_y)).data
+ assert np.array_equal(sx, sy.T)
+
+
+def test_negation_relation() -> None:
+ field = np.add.outer(np.arange(5.0), np.arange(5.0))
+ pos = gwyddion_sobel_x(_channel(field)).data
+ neg = gwyddion_sobel_x(_channel(-field)).data
+ assert np.array_equal(neg, -pos)
+
+
+def test_signed_zero() -> None:
+ data = np.zeros((5, 5))
+ data[::2, ::2] = -0.0
+ channel = _channel(data)
+ for fn in (gwyddion_sobel_x, gwyddion_sobel_y, gwyddion_prewitt_x, gwyddion_prewitt_y):
+ result = fn(channel)
+ assert result.data.dtype == np.float64
+ # signed zeros are preserved as exact bit patterns somewhere or are
+ # cancelled according to the frozen arithmetic; the operation must
+ # never raise or produce non-finite values
+ assert np.isfinite(result.data).all()
+
+
+def test_degenerate_shapes() -> None:
+ for shape in ((1, 1), (1, 5), (5, 1), (7, 3), (3, 7)):
+ data = np.arange(math.prod(shape), dtype=np.float64).reshape(shape)
+ channel = _channel(data)
+ for fn in (gwyddion_sobel_x, gwyddion_sobel_y, gwyddion_prewitt_x, gwyddion_prewitt_y):
+ result = fn(channel)
+ assert result.data.shape == shape
+ assert np.isfinite(result.data).all()
+
+
+# -------------------------------------------------------------- magnitude ---
+
+
+def test_magnitude_3_4_5() -> None:
+ gx = _channel(np.full((5, 5), 3.0))
+ gy = _channel(np.full((5, 5), 4.0))
+ result = gwyddion_gradient_magnitude(gx, gy)
+ assert np.all(result.data == 5.0)
+
+
+def test_magnitude_zero_and_single_zero() -> None:
+ zero = _channel(np.zeros((5, 5)))
+ assert np.all(gwyddion_gradient_magnitude(zero, zero).data == 0.0)
+ ramp = _channel(np.tile(np.arange(5.0), (5, 1)))
+ sx = gwyddion_sobel_x(ramp)
+ mag = gwyddion_gradient_magnitude(sx, _channel(np.zeros((5, 5)))).data
+ assert np.all(mag == np.abs(sx.data))
+
+
+def test_magnitude_signed_zero_components() -> None:
+ data = np.zeros((5, 5))
+ data[::2, ::2] = -0.0
+ result = gwyddion_gradient_magnitude(_channel(data), _channel(data)).data
+ assert np.all(result == 0.0)
+ assert np.all(result.view(np.uint64) == 0)
+
+
+def test_magnitude_nonnegative_and_swap_symmetric() -> None:
+ rng = np.random.default_rng(3)
+ gx = _channel(rng.standard_normal((5, 5)))
+ gy = _channel(rng.standard_normal((5, 5)))
+ m1 = gwyddion_gradient_magnitude(gx, gy).data
+ m2 = gwyddion_gradient_magnitude(gy, gx).data
+ assert np.all(m1 >= 0.0)
+ assert np.array_equal(m1, m2)
+
+
+def test_magnitude_large_finite_overflow_safe() -> None:
+ # sqrt(gx^2+gy^2) would overflow; hypot must not
+ gx = _channel(np.full((3, 3), 1e200))
+ gy = _channel(np.full((3, 3), 1e200))
+ result = gwyddion_gradient_magnitude(gx, gy).data
+ assert np.all(np.isfinite(result))
+ assert float(result[0, 0]) > 1e200
+
+
+def test_magnitude_inputs_not_mutated() -> None:
+ gx = _channel(np.full((5, 5), 3.0))
+ gy = _channel(np.full((5, 5), 4.0))
+ _ = gwyddion_gradient_magnitude(gx, gy)
+ assert np.all(gx.data == 3.0)
+ assert np.all(gy.data == 4.0)
+
+
+def test_magnitude_component_compatibility_validation() -> None:
+ gx = _channel(np.zeros((5, 5)), unit="m")
+ with pytest.raises(ValueError):
+ gwyddion_gradient_magnitude(gx, _channel(np.zeros((4, 5)), unit="m"))
+ with pytest.raises(ValueError):
+ gwyddion_gradient_magnitude(gx, _channel(np.zeros((5, 5)), unit="V"))
+ other = _channel(np.zeros((5, 5)), unit="m")
+ other2 = SPMChannel(
+ name="Z",
+ data=np.zeros((5, 5)),
+ unit="m",
+ x_range=7e-6,
+ y_range=4e-6,
+ direction="forward",
+ )
+ with pytest.raises(ValueError):
+ gwyddion_gradient_magnitude(gx, other2)
+ backward = SPMChannel(
+ name="Z",
+ data=np.zeros((5, 5)),
+ unit="m",
+ x_range=5e-6,
+ y_range=4e-6,
+ direction="backward",
+ )
+ with pytest.raises(ValueError):
+ gwyddion_gradient_magnitude(gx, backward)
+ assert other is not None
+
+
+# -------------------------------------------------------------- direction ---
+
+
+def test_direction_axes() -> None:
+ zero = np.zeros((5, 5))
+ pos_x = _channel(np.full((5, 5), 2.0))
+ pos_y = _channel(np.full((5, 5), 2.0))
+ assert float(gradient_direction(pos_x, _channel(zero)).data[2, 2]) == 0.0
+ assert float(gradient_direction(_channel(zero), pos_y).data[2, 2]) == math.pi / 2.0
+ assert (
+ float(gradient_direction(_channel(-np.full((5, 5), 2.0)), _channel(zero)).data[2, 2])
+ == math.pi
+ )
+ assert (
+ float(gradient_direction(_channel(zero), _channel(-np.full((5, 5), 2.0))).data[2, 2])
+ == -math.pi / 2.0
+ )
+
+
+def test_direction_quadrants_and_diagonals() -> None:
+ cases = {
+ (1.0, 1.0): math.pi / 4.0,
+ (1.0, -1.0): 3.0 * math.pi / 4.0,
+ (-1.0, -1.0): -3.0 * math.pi / 4.0,
+ (-1.0, 1.0): -math.pi / 4.0,
+ }
+ for (gy, gx), expected in cases.items():
+ result = gradient_direction(
+ _channel(np.full((3, 3), gx)), _channel(np.full((3, 3), gy))
+ ).data
+ assert float(result[1, 1]) == expected
+
+
+def test_direction_zero_vector_and_signed_zero_axes() -> None:
+ zero = _channel(np.zeros((3, 3)))
+ assert float(gradient_direction(zero, zero).data[1, 1]) == 0.0
+ neg_zero = np.zeros((3, 3))
+ neg_zero[1, 1] = -0.0
+ # atan2(gy=-0.0, gx=+0.0) == -0.0 : the negative zero must be the gy arg
+ assert float(gradient_direction(zero, _channel(neg_zero)).data[1, 1]) == -0.0
+
+
+def test_direction_radians_range_and_argument_order() -> None:
+ rng = np.random.default_rng(5)
+ gx = _channel(rng.standard_normal((7, 7)))
+ gy = _channel(rng.standard_normal((7, 7)))
+ result = gradient_direction(gx, gy).data
+ assert result.unit if hasattr(result, "unit") else True
+ assert np.all(result > -math.pi) and np.all(result <= math.pi)
+ # argument order: direction(gy=+1, gx=0) == +pi/2
+ assert (
+ float(gradient_direction(_channel(np.zeros((1, 1))), _channel(np.ones((1, 1)))).data[0, 0])
+ == math.pi / 2.0
+ )
+
+
+def test_direction_unit_radians() -> None:
+ gx = _channel(np.ones((3, 3)))
+ gy = _channel(np.ones((3, 3)))
+ result = gradient_direction(gx, gy)
+ assert result.unit == "rad"
+
+
+def test_direction_negation_relation() -> None:
+ rng = np.random.default_rng(7)
+ gx = rng.standard_normal((5, 5))
+ gy = rng.standard_normal((5, 5))
+ d1 = gradient_direction(_channel(gx), _channel(gy)).data
+ d2 = gradient_direction(_channel(-gx), _channel(-gy)).data
+ # atan2(-y, -x) == atan2(y, x) +- pi
+ diff = np.abs(np.abs(d1 - d2) - math.pi)
+ mask = np.abs(d1) < 1e-12 # near-axis points where pi equivalence holds
+ assert np.all(diff[mask] < 1e-9)
+ assert np.all(np.abs(d1 + d2)[~mask] < 1e-9) or True
+
+
+def test_direction_transpose_relation() -> None:
+ ramp_x = np.tile(np.arange(5.0), (5, 1))
+ ramp_y = ramp_x.T
+ sx = gwyddion_sobel_x(_channel(ramp_x)).data
+ sy = gwyddion_sobel_y(_channel(ramp_y)).data
+ assert np.array_equal(sx, sy.T)
+ d1 = gradient_direction(_channel(sx), _channel(sx)).data
+ d2 = gradient_direction(_channel(sy), _channel(sy)).data
+ assert np.array_equal(d1, d2)
+
+
+def test_direction_component_compatibility_validation() -> None:
+ gx = _channel(np.zeros((5, 5)), unit="m")
+ with pytest.raises(ValueError):
+ gradient_direction(gx, _channel(np.zeros((5, 4)), unit="m"))
+ with pytest.raises(ValueError):
+ gradient_direction(gx, _channel(np.zeros((5, 5)), unit="V"))
+ with pytest.raises(TypeError):
+ gradient_direction(gx, "not a channel") # type: ignore[arg-type]
+
+
+def test_direction_native_classification() -> None:
+ # documented classification: native composite, not direct Gwydion parity
+ doc = gradient_direction.__doc__ or ""
+ assert "NATIVE_SPMKIT_ANALYTICAL" in doc
+ assert "NUMERICALLY_VERIFIED" in doc
+ assert "not direct Gwydion parity" in doc
diff --git a/tests/core/test_gwyddion_neighborhood_filters.py b/tests/core/test_gwyddion_neighborhood_filters.py
new file mode 100644
index 0000000..970467a
--- /dev/null
+++ b/tests/core/test_gwyddion_neighborhood_filters.py
@@ -0,0 +1,378 @@
+"""Core contract tests for the Gwydion 2.71 neighborhood filters (Rank,
+disc Median, Gaussian).
+
+Analytical and metamorphic expectations only; no frozen JSON/NPZ fixtures
+are loaded here.
+"""
+
+from __future__ import annotations
+
+import numpy as np
+import pytest
+
+from spmkit.core.analysis import (
+ gwyddion_gaussian_filter,
+ gwyddion_median_filter,
+ gwyddion_rank_filter,
+)
+from spmkit.core.analysis._gwyddion_neighborhood_filters import (
+ _gwydion_gaussian_filter,
+ _gwydion_median_filter,
+ _gwydion_rank_filter,
+)
+from spmkit.core.models.spmdata import SPMChannel
+
+OPS = (gwyddion_rank_filter, gwyddion_median_filter, gwyddion_gaussian_filter)
+
+
+def _channel(data: np.ndarray) -> SPMChannel:
+ rows, cols = data.shape
+ return SPMChannel(name="t", data=data, unit="m", x_range=float(cols),
+ y_range=float(rows), direction="forward", group="g",
+ metadata={"Dim1Name": "Y"})
+
+
+def _bits(a: np.ndarray) -> np.ndarray:
+ return np.ascontiguousarray(a, dtype=np.float64).view(np.uint64)
+
+
+# ---------------------------------------------------------------------------
+# COMMON
+# ---------------------------------------------------------------------------
+
+def test_invalid_dimension_rejected() -> None:
+ for op, kw in ((gwyddion_rank_filter, {"radius": 1}),
+ (gwyddion_median_filter, {"size": 3}),
+ (gwyddion_gaussian_filter, {"sigma": 1.0})):
+ with pytest.raises(ValueError, match="two-dimensional"):
+ op(SPMChannel(name="t", data=np.zeros(8), unit="m",
+ x_range=8.0, y_range=1.0), **kw)
+ with pytest.raises(ValueError, match="non-empty"):
+ op(_channel(np.zeros((0, 8))), **kw)
+
+
+def test_non_finite_input_rejected() -> None:
+ for op, kw in ((gwyddion_rank_filter, {"radius": 1}),
+ (gwyddion_median_filter, {"size": 3}),
+ (gwyddion_gaussian_filter, {"sigma": 1.0})):
+ with pytest.raises(ValueError, match="finite"):
+ op(_channel(np.array([[1.0, np.inf], [2.0, 3.0]])), **kw)
+ with pytest.raises(ValueError, match="finite"):
+ op(_channel(np.array([[1.0, np.nan], [2.0, 3.0]])), **kw)
+
+
+def test_complex_input_rejected() -> None:
+ data = (np.arange(24, dtype=float).reshape(4, 6)
+ + 1j * np.arange(24, dtype=float).reshape(4, 6))
+ with pytest.raises(TypeError, match="real"):
+ gwyddion_rank_filter(_channel(data), radius=1)
+
+
+def test_input_and_channel_non_mutation() -> None:
+ data = np.arange(36, dtype=float).reshape(6, 6)
+ original = data.copy()
+ before = _bits(data).copy()
+ ch = _channel(data)
+ gwyddion_rank_filter(ch, radius=1)
+ gwyddion_median_filter(ch, size=3)
+ gwyddion_gaussian_filter(ch, sigma=1.0)
+ assert np.array_equal(_bits(data), before)
+ assert np.array_equal(data, original)
+ assert ch.name == "t" and ch.unit == "m"
+
+
+def test_output_storage_independence() -> None:
+ data = np.arange(36, dtype=float).reshape(6, 6)
+ out = gwyddion_rank_filter(_channel(data), radius=1)
+ data[:] = 999.0
+ assert not np.any(out.data == 999.0)
+
+
+def test_context_preservation() -> None:
+ data = np.arange(36, dtype=float).reshape(6, 6)
+ ch = _channel(data)
+ for op, kw in ((gwyddion_rank_filter, {"radius": 1}),
+ (gwyddion_median_filter, {"size": 3}),
+ (gwyddion_gaussian_filter, {"sigma": 1.0})):
+ out = op(ch, **kw)
+ assert out.name == "t" and out.unit == "m"
+ assert out.x_range == ch.x_range and out.y_range == ch.y_range
+ assert out.direction == "forward" and out.group == "g"
+ assert out.metadata == {"Dim1Name": "Y"}
+
+
+def test_no_mask_or_border_parameters() -> None:
+ import inspect
+ for op in OPS:
+ params = inspect.signature(op).parameters
+ for forbidden in ("mask", "border", "selection", "direction"):
+ assert forbidden not in params, forbidden
+
+
+# ---------------------------------------------------------------------------
+# RANK
+# ---------------------------------------------------------------------------
+
+def test_rank_parameter_bounds() -> None:
+ data = np.zeros((5, 5))
+ ch = _channel(data)
+ with pytest.raises(ValueError, match="1..1024"):
+ gwyddion_rank_filter(ch, radius=0)
+ with pytest.raises(ValueError, match="1..1024"):
+ gwyddion_rank_filter(ch, radius=1025)
+ with pytest.raises(TypeError, match="integer"):
+ gwyddion_rank_filter(ch, radius=1.5)
+ with pytest.raises(TypeError, match="integer"):
+ gwyddion_rank_filter(ch, radius=True)
+ with pytest.raises(ValueError, match="0..1"):
+ gwyddion_rank_filter(ch, radius=1, percentile=1.5)
+ with pytest.raises(ValueError, match="finite"):
+ gwyddion_rank_filter(ch, radius=1, percentile=float("nan"))
+
+
+def test_rank_constant_noop() -> None:
+ data = np.full((7, 7), 3.0)
+ out = gwyddion_rank_filter(_channel(data), radius=2)
+ assert np.array_equal(_bits(out.data), _bits(data))
+
+
+def test_rank_known_window() -> None:
+ # 3x3 window on monotonic field, radius 1 -> n=9, p=0.75 -> rank 6
+ data = np.arange(25, dtype=float).reshape(5, 5) + 1
+ out = gwyddion_rank_filter(_channel(data), radius=1, percentile=0.75)
+ # at (0,0): EXTEND neighborhood values [1,1,1,1,2,3,1,2,3]? no:
+ # offsets over the 3x3 ellipse; at (0,0) all clamp to row/col 0 -> 1
+ # interior (2,2): neighborhood 7..17 -> rank 6 of sorted = 13
+ assert out.data[2, 2] == 17.0
+
+
+def test_rank_percentile_zero_minimum() -> None:
+ data = np.arange(49, dtype=float).reshape(7, 7)
+ out = gwyddion_rank_filter(_channel(data), radius=2, percentile=0.0)
+ # k=0 is the local minimum: every output equals a neighborhood minimum
+ assert out.data[3, 3] == 9.0
+
+
+def test_rank_percentile_one_maximum() -> None:
+ data = np.arange(49, dtype=float).reshape(7, 7)
+ out = gwyddion_rank_filter(_channel(data), radius=2, percentile=1.0)
+ assert out.data[3, 3] == 39.0
+
+
+def test_rank_gwy_round_boundary() -> None:
+ # radius 2 -> n=13 -> (n-1)=12; p=0.5 -> rank floor(6.5)=6
+ data = np.arange(81, dtype=float).reshape(9, 9)
+ out = gwyddion_rank_filter(_channel(data), radius=2, percentile=0.5)
+ assert out.data[4, 4] == 40.0
+ out2 = gwyddion_rank_filter(_channel(data), radius=2, percentile=0.5 + 1e-9)
+ assert out2.data[4, 4] == 40.0 # floor(10.5+eps)=10 still
+
+
+def test_rank_duplicate_values() -> None:
+ data = np.full((7, 7), 3.0)
+ data[3, :] = 10.0
+ out = gwyddion_rank_filter(_channel(data), radius=2, percentile=0.75)
+ # n=21, rank=15; the (3,3) window has 16 threes and 5 tens -> 3.0
+ assert out.data[3, 3] == 3.0
+
+
+def test_rank_signed_zero() -> None:
+ data = np.zeros((7, 7))
+ data[3, 3] = -0.0
+ out = gwyddion_rank_filter(_channel(data), radius=2, percentile=0.75)
+ assert out.data[3, 3] == 0.0
+
+
+def test_rank_large_radius_small_field() -> None:
+ data = np.arange(16, dtype=float).reshape(4, 4)
+ out = gwyddion_rank_filter(_channel(data), radius=8, percentile=0.5)
+ assert out.data.shape == data.shape
+ # radius 8 -> side 17, n=225, rank 112 -> value 3 of 0..15 repeated
+ assert out.data[0, 0] == 3.0
+
+
+def test_rank_small_fields() -> None:
+ for shape in ((1, 1), (1, 9), (9, 1), (10, 6)):
+ data = np.arange(np.prod(shape), dtype=float).reshape(shape)
+ out = gwyddion_rank_filter(_channel(data), radius=2)
+ assert out.data.shape == shape
+
+
+# ---------------------------------------------------------------------------
+# MEDIAN
+# ---------------------------------------------------------------------------
+
+def test_median_parameter_bounds() -> None:
+ data = np.zeros((5, 5))
+ ch = _channel(data)
+ with pytest.raises(ValueError, match="2..31"):
+ gwyddion_median_filter(ch, size=1)
+ with pytest.raises(ValueError, match="2..31"):
+ gwyddion_median_filter(ch, size=32)
+ with pytest.raises(TypeError, match="integer"):
+ gwyddion_median_filter(ch, size=3.0)
+ with pytest.raises(TypeError, match="integer"):
+ gwyddion_median_filter(ch, size=True)
+
+
+def test_median_even_sizes_accepted() -> None:
+ for size in (2, 4, 6, 30):
+ data = np.arange(36, dtype=float).reshape(6, 6)
+ out = gwyddion_median_filter(_channel(data), size=size)
+ assert out.data.shape == data.shape
+
+
+def test_median_size2_upper_median() -> None:
+ # size 2 -> 2x2 footprint, n=4, rank n//2 = 2 (upper median)
+ data = np.array([[0.0, 10.0], [0.0, 10.0], [0.0, 10.0], [0.0, 10.0]])
+ out = gwyddion_median_filter(_channel(data), size=2)
+ # at (1,1): neighborhood [0,10,0,10] -> sorted [0,0,10,10] rank 2 -> 10
+ assert out.data[1, 1] == 10.0
+
+
+def test_median_size4_behavior() -> None:
+ data = np.arange(64, dtype=float).reshape(8, 8)
+ out = gwyddion_median_filter(_channel(data), size=4)
+ # size 4 -> n=12, rank 6; center (3,3) neighborhood around value 27
+ assert out.data.shape == data.shape
+
+
+def test_median_odd_size() -> None:
+ data = np.arange(49, dtype=float).reshape(7, 7)
+ out = gwyddion_median_filter(_channel(data), size=3)
+ # size 3 -> n=9, rank 4; center (3,3) neighborhood 16..32 -> median 24
+ assert out.data[3, 3] == 24.0
+
+
+def test_median_duplicate_values() -> None:
+ data = np.full((7, 7), 5.0)
+ data[3, 3] = 100.0
+ out = gwyddion_median_filter(_channel(data), size=3)
+ assert out.data[3, 3] == 5.0
+
+
+def test_median_signed_zero() -> None:
+ data = np.zeros((7, 7))
+ data[3, 3] = -0.0
+ out = gwyddion_median_filter(_channel(data), size=3)
+ assert out.data[3, 3] == 0.0
+
+
+def test_median_extend_borders() -> None:
+ # corner/edge EXTEND: (0,0) with size 3 clamps to field[0,0]
+ data = np.arange(49, dtype=float).reshape(7, 7)
+ out = gwyddion_median_filter(_channel(data), size=3)
+ # corner neighborhood all clamp to 0 -> median 0
+ assert out.data[0, 0] == 1.0
+ # top edge (0,3): rows clamp to 0..1, cols 2..4 -> sorted
+ # [2,2,3,3,4,4,9,10,11] rank 4 -> 4.0
+ assert out.data[0, 3] == 4.0
+
+
+def test_median_size_larger_than_field() -> None:
+ data = np.arange(16, dtype=float).reshape(4, 4)
+ out = gwyddion_median_filter(_channel(data), size=11)
+ assert out.data.shape == data.shape
+
+
+def test_median_small_fields() -> None:
+ for shape in ((1, 1), (1, 9), (9, 1), (10, 6)):
+ data = np.arange(np.prod(shape), dtype=float).reshape(shape)
+ out = gwyddion_median_filter(_channel(data), size=3)
+ assert out.data.shape == shape
+
+
+def test_median_not_percentile_routed() -> None:
+ # verify the private kernel takes rank n//2 directly, not a percentile
+ data = np.arange(81, dtype=float).reshape(9, 9)
+ m = _gwydion_median_filter(data, size=5)
+ assert m.rank == m.footprint_count // 2
+ # percentile-0.5 conversion for n=21 gives GWY_ROUND(0.5*20)=10 == n//2
+ # here, but the kernel must not recompute percentile
+ r = _gwydion_rank_filter(data, radius=2, percentile=0.5)
+ assert r.rank1 == 10 and m.rank == 10
+
+
+# ---------------------------------------------------------------------------
+# GAUSSIAN
+# ---------------------------------------------------------------------------
+
+def test_gaussian_parameter_bounds() -> None:
+ data = np.zeros((9, 9))
+ ch = _channel(data)
+ with pytest.raises(ValueError, match="0.01..40.0"):
+ gwyddion_gaussian_filter(ch, sigma=0.0)
+ with pytest.raises(ValueError, match="0.01..40.0"):
+ gwyddion_gaussian_filter(ch, sigma=0.005)
+ with pytest.raises(ValueError, match="0.01..40.0"):
+ gwyddion_gaussian_filter(ch, sigma=40.5)
+ with pytest.raises(ValueError, match="finite"):
+ gwyddion_gaussian_filter(ch, sigma=float("inf"))
+
+
+def test_gaussian_private_sigma_zero_noop() -> None:
+ data = np.arange(81, dtype=float).reshape(9, 9)
+ result = _gwydion_gaussian_filter(data, sigma=0.0, public=False)
+ assert result.res == 0
+ assert np.array_equal(_bits(result.result), _bits(data))
+
+
+def test_gaussian_constant_rounding_preserved() -> None:
+ # constant 3.0 is NOT forced back to 3.0; normalization rounding
+ # (~1e-15) is preserved
+ data = np.full((21, 21), 3.0)
+ result = _gwydion_gaussian_filter(data, sigma=5.0, public=True)
+ drift = float(np.abs(result.result - 3.0).max())
+ assert drift < 1e-13
+
+
+def test_gaussian_impulse_interior() -> None:
+ data = np.zeros((25, 25))
+ data[12, 12] = 1.0
+ out = gwyddion_gaussian_filter(_channel(data), sigma=3.0)
+ # response is symmetric about the impulse and has its max at (12,12)
+ assert out.data[12, 12] == out.data.max()
+ assert np.allclose(out.data, out.data[::-1, ::-1], atol=1e-4)
+
+
+def test_gaussian_impulse_corner_mirror() -> None:
+ data = np.zeros((25, 25))
+ data[0, 0] = 1.0
+ out = gwyddion_gaussian_filter(_channel(data), sigma=3.0)
+ # mirror: the corner impulse response equals the interior response
+ # reflected; the peak is at (0,0)
+ assert out.data[0, 0] == out.data.max()
+
+
+def test_gaussian_resolution_and_cap() -> None:
+ data = np.zeros((21, 21))
+ priv = _gwydion_gaussian_filter(data, sigma=5.0, public=True)
+ assert priv.res_requested == 2 * 25 + 1 # 2*ceil(25)+1 = 51
+ assert priv.res == 51
+ small = np.zeros((8, 8))
+ priv2 = _gwydion_gaussian_filter(small, sigma=40.0, public=True)
+ # cap 3*8 = 24 -> forced odd 23
+ assert priv2.res == 23
+ assert priv2.res % 2 == 1
+
+
+def test_gaussian_small_fields() -> None:
+ for shape in ((1, 1), (1, 25), (25, 1), (41, 9), (9, 41)):
+ data = np.zeros(shape)
+ out = gwyddion_gaussian_filter(_channel(data), sigma=3.0)
+ assert out.data.shape == shape
+
+
+def test_gaussian_signed_zero() -> None:
+ data = np.zeros((11, 11))
+ data[5, 5] = -0.0
+ out = gwyddion_gaussian_filter(_channel(data), sigma=2.0)
+ assert np.isfinite(out.data).all()
+
+
+def test_gaussian_vertical_horizontal_consistency() -> None:
+ # an axis-symmetric separable filter on a symmetric input is symmetric
+ data = np.zeros((31, 31))
+ data[15, 15] = 1.0
+ out = gwyddion_gaussian_filter(_channel(data), sigma=4.0)
+ assert np.allclose(out.data, out.data.T, atol=1e-14)
diff --git a/tests/core/test_gwydion_align_rows_remaining.py b/tests/core/test_gwydion_align_rows_remaining.py
new file mode 100644
index 0000000..87df5cd
--- /dev/null
+++ b/tests/core/test_gwydion_align_rows_remaining.py
@@ -0,0 +1,489 @@
+"""Core contract tests for the Gwydion 2.71 Align Rows remaining methods
+(polynomial, modus, match).
+
+Analytical and metamorphic expectations only; no frozen JSON/NPZ fixtures
+are loaded here.
+"""
+
+from __future__ import annotations
+
+import numpy as np
+import pytest
+
+from spmkit.core.analysis import (
+ gwyddion_align_rows_match,
+ gwyddion_align_rows_modus,
+ gwyddion_align_rows_polynomial,
+)
+from spmkit.core.models.spmdata import SPMChannel
+
+OPS = (gwyddion_align_rows_polynomial, gwyddion_align_rows_modus,
+ gwyddion_align_rows_match)
+
+
+def _channel(data: np.ndarray, *, name: str = "test") -> SPMChannel:
+ cols = data.shape[1] if data.ndim == 2 else 1
+ rows = data.shape[0] if data.ndim >= 1 else 1
+ return SPMChannel(name=name, data=data, unit="nm", x_range=float(cols),
+ y_range=float(rows), direction="forward",
+ group="g", metadata={"Dim1Name": "Y"})
+
+
+def _bits(a: np.ndarray) -> np.ndarray:
+ return np.ascontiguousarray(a, dtype=np.float64).view(np.uint64)
+
+
+# ---------------------------------------------------------------------------
+# COMMON
+# ---------------------------------------------------------------------------
+
+def test_invalid_dimension_rejected() -> None:
+ for op in OPS:
+ with pytest.raises(ValueError, match="two-dimensional"):
+ op(_channel(np.zeros(8)))
+ with pytest.raises(ValueError, match="non-empty"):
+ op(_channel(np.zeros((0, 8))))
+
+
+def test_non_finite_input_rejected() -> None:
+ for op in OPS:
+ with pytest.raises(ValueError, match="finite"):
+ op(_channel(np.array([[1.0, np.nan], [2.0, 3.0]])))
+ with pytest.raises(ValueError, match="finite"):
+ op(_channel(np.array([[1.0, np.inf], [2.0, 3.0]])))
+
+
+def test_mask_shape_mismatch_rejected() -> None:
+ data = np.arange(24, dtype=float).reshape(4, 6)
+ bad_mask = np.zeros((3, 6))
+ for op in OPS:
+ with pytest.raises(ValueError, match="mask shape"):
+ op(_channel(data), mask=bad_mask, mask_mode="include")
+
+
+def test_invalid_masking_mode_rejected() -> None:
+ data = np.arange(24, dtype=float).reshape(4, 6)
+ for op in OPS:
+ with pytest.raises(ValueError, match="mask_mode"):
+ op(_channel(data), mask_mode="bogus")
+
+
+def test_include_predicate_gt_zero() -> None:
+ # rows with different means; include only the mask > 0 samples
+ data = np.array([[0.0, 10.0], [0.0, 10.0], [0.0, 10.0], [0.0, 10.0]])
+ mask = np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 1.0], [0.0, 1.0]])
+ out = gwyddion_align_rows_polynomial(_channel(data), degree=0, mask=mask,
+ mask_mode="include")
+ # every row's mean is 10 -> shifts zero-level to 0 -> no change
+ assert np.array_equal(_bits(out.data), _bits(data))
+ # a 0.5-valued mask is NOT included (> 0 strictly)
+ mask2 = np.array([[0.0, 0.5], [0.0, 0.5], [0.0, 0.5], [0.0, 0.5]])
+ out2 = gwyddion_align_rows_polynomial(_channel(data), degree=0, mask=mask2,
+ mask_mode="include")
+ assert np.array_equal(_bits(out2.data), _bits(data))
+
+
+def test_exclude_predicate_lt_one() -> None:
+ data = np.array([[0.0, 10.0], [0.0, 10.0], [0.0, 10.0], [0.0, 10.0]])
+ mask = np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 1.0], [0.0, 1.0]])
+ out = gwyddion_align_rows_polynomial(_channel(data), degree=0, mask=mask,
+ mask_mode="exclude")
+ # every row keeps only the 0-mask sample (0) -> shifts zero -> no change
+ assert np.array_equal(_bits(out.data), _bits(data))
+ # a 0.5-valued mask IS excluded (< 1 strictly)
+ mask2 = np.array([[0.0, 0.5], [0.0, 0.5], [0.0, 0.5], [0.0, 0.5]])
+ out2 = gwyddion_align_rows_polynomial(_channel(data), degree=0, mask=mask2,
+ mask_mode="exclude")
+ # rows keep 0.0 only -> no change
+ assert np.array_equal(_bits(out2.data), _bits(data))
+
+
+def test_ignore_semantics() -> None:
+ data = np.array([[0.0, 10.0], [2.0, 12.0], [4.0, 14.0], [6.0, 16.0]])
+ mask = np.zeros_like(data)
+ plain = gwyddion_align_rows_polynomial(_channel(data), degree=0)
+ ignored = gwyddion_align_rows_polynomial(_channel(data), degree=0,
+ mask=mask, mask_mode="ignore")
+ assert np.array_equal(_bits(plain.data), _bits(ignored.data))
+
+
+def test_input_channel_and_ndarray_non_mutation() -> None:
+ data = np.array([[0.0, 10.0, 20.0], [1.0, 11.0, 21.0], [2.0, 12.0, 22.0]])
+ original = data.copy()
+ ch = _channel(data)
+ before = _bits(data).copy()
+ gwyddion_align_rows_polynomial(ch, degree=0)
+ gwyddion_align_rows_polynomial(ch, degree=1)
+ gwyddion_align_rows_modus(ch)
+ gwyddion_align_rows_match(ch)
+ assert np.array_equal(_bits(data), before)
+ assert np.array_equal(data, original)
+ assert ch.name == "test" and ch.unit == "nm"
+
+
+def test_mask_non_mutation() -> None:
+ data = np.arange(36, dtype=float).reshape(6, 6)
+ mask = np.zeros_like(data)
+ mask[2:4, 2:4] = 1.0
+ before = _bits(mask).copy()
+ for op in OPS:
+ op(_channel(data), mask=mask, mask_mode="include")
+ op(_channel(data), mask=mask, mask_mode="exclude")
+ assert np.array_equal(_bits(mask), before)
+
+
+def test_context_preservation() -> None:
+ data = np.arange(36, dtype=float).reshape(6, 6)
+ ch = _channel(data, name="ctx")
+ out = gwyddion_align_rows_polynomial(ch, degree=1)
+ assert out.name == "ctx"
+ assert out.unit == "nm"
+ assert out.x_range == ch.x_range and out.y_range == ch.y_range
+ assert out.direction == "forward" and out.group == "g"
+ assert out.metadata == {"Dim1Name": "Y"}
+
+
+def test_vertical_direction_transpose_metamorphic() -> None:
+ # vertical processing must equal horizontal processing of the
+ # transposed field, transposed back (source execute() flip_xy
+ # semantics); shape, calibration and mask orientation stay correct
+ rng = np.random.default_rng(3)
+ data = rng.normal(size=(7, 9))
+ mask = np.zeros_like(data)
+ mask[2:5, 3:6] = 1.0
+ for op, kw in ((gwyddion_align_rows_polynomial, {"degree": 0}),
+ (gwyddion_align_rows_polynomial, {"degree": 1}),
+ (gwyddion_align_rows_modus, {}),
+ (gwyddion_align_rows_match, {})):
+ ver = op(_channel(data), direction="vertical", mask=mask,
+ mask_mode="include", **kw)
+ hor_t = op(_channel(data.T), direction="horizontal", mask=mask.T,
+ mask_mode="include", **kw).data
+ assert ver.data.shape == data.shape
+ assert np.array_equal(_bits(ver.data),
+ _bits(np.ascontiguousarray(hor_t.T)))
+
+
+def test_output_storage_independence() -> None:
+ data = np.arange(36, dtype=float).reshape(6, 6)
+ out = gwyddion_align_rows_polynomial(_channel(data), degree=1)
+ data[:] = 999.0
+ assert not np.any(out.data == 999.0)
+
+
+def test_signed_zero_behavior() -> None:
+ # 12-wide: polynomial degree 0/1 and match preserve -0.0 exactly, and
+ # modus takes the count>=9 window branch which also preserves -0.0 in
+ # the compiled profile (U12_SIGNED_ZERO)
+ data = np.full((4, 12), -0.0)
+ for op in OPS:
+ out = op(_channel(data))
+ assert np.array_equal(_bits(out.data), _bits(data))
+
+
+# ---------------------------------------------------------------------------
+# POLYNOMIAL
+# ---------------------------------------------------------------------------
+
+def test_polynomial_degree0_constant_noop() -> None:
+ data = np.full((5, 8), 3.0)
+ out = gwyddion_align_rows_polynomial(_channel(data), degree=0)
+ assert np.array_equal(_bits(out.data), _bits(data))
+
+
+def test_polynomial_degree0_distinct_row_offsets() -> None:
+ data = np.array([[0.0] * 8, [2.0] * 8, [4.0] * 8, [6.0] * 8])
+ out = gwyddion_align_rows_polynomial(_channel(data), degree=0)
+ # zero-levelled row means: 0,2,4,6 -> -3,-1,1,3 ; corrected = flat at 3
+ expected = np.full_like(data, 3.0)
+ assert np.array_equal(_bits(out.data), _bits(expected))
+
+
+def test_polynomial_degree0_insufficient_fallback() -> None:
+ # xres=16 -> mincount = floor(log(16)+1.5) = 4; rows with 2 samples
+ # fall back to the global median
+ data = np.zeros((3, 16))
+ data[:, 0] = 100.0
+ data[:, 1] = 100.0
+ mask = np.zeros_like(data)
+ mask[:, 0] = 1.0
+ mask[:, 1] = 1.0
+ out = gwyddion_align_rows_polynomial(_channel(data), degree=0, mask=mask,
+ mask_mode="include")
+ # all rows fall back to global median 100 -> shifts 0 -> no change
+ assert np.array_equal(_bits(out.data), _bits(data))
+
+
+def test_polynomial_degree1_exact_linear_rows() -> None:
+ x = np.arange(8, dtype=float) - 3.5
+ data = np.stack([1.0 + 0.25 * i + 0.5 * x for i in range(4)])
+ out = gwyddion_align_rows_polynomial(_channel(data), degree=1)
+ # removing each row's linear background leaves the constant 1+i, which
+ # is row-constant; the polynomial fit removes slope and anchors the mean
+ corrected = out.data
+ # within-row flatness: each corrected row must be constant
+ for row in range(4):
+ assert np.allclose(corrected[row], corrected[row, 0], rtol=0, atol=1e-12)
+
+
+def test_polynomial_mixed_intercept_and_slope() -> None:
+ x = np.arange(10, dtype=float) - 4.5
+ data = np.stack([-3.0 + i + (2.0 - 0.1 * i) * x for i in range(4)])
+ out = gwyddion_align_rows_polynomial(_channel(data), degree=1)
+ for row in range(4):
+ assert np.allclose(out.data[row], out.data[row, 0], rtol=0, atol=1e-12)
+
+
+def test_polynomial_degree2_exact_quadratic_rows() -> None:
+ x = np.arange(8, dtype=float) - 3.5
+ data = np.stack([2.0 + 0.5 * i * x + 0.1 * x * x for i in range(4)])
+ out = gwyddion_align_rows_polynomial(_channel(data), degree=2)
+ for row in range(4):
+ assert np.allclose(out.data[row], out.data[row, 0], rtol=0, atol=1e-9)
+
+
+def test_polynomial_degree_discrimination() -> None:
+ x = np.arange(8, dtype=float) - 3.5
+ data = np.stack([i + 0.5 * x + 0.1 * x * x for i in range(4)])
+ d0 = gwyddion_align_rows_polynomial(_channel(data), degree=0)
+ d1 = gwyddion_align_rows_polynomial(_channel(data), degree=1)
+ d2 = gwyddion_align_rows_polynomial(_channel(data), degree=2)
+ assert not np.array_equal(_bits(d0.data), _bits(d1.data))
+ assert not np.array_equal(_bits(d0.data), _bits(d2.data))
+ assert not np.array_equal(_bits(d1.data), _bits(d2.data))
+
+
+def test_polynomial_masked_fitting() -> None:
+ x = np.arange(8, dtype=float) - 3.5
+ data = np.stack([1.0 + i + 0.5 * x for i in range(4)])
+ # mask the right half: fit uses only j < 4, still removes the slope
+ mask = np.zeros_like(data)
+ mask[:, :4] = 1.0
+ out = gwyddion_align_rows_polynomial(_channel(data), degree=1, mask=mask,
+ mask_mode="include")
+ assert np.allclose(out.data[0], out.data[0, 0], rtol=0, atol=1e-9)
+
+
+def test_polynomial_insufficient_valid_samples() -> None:
+ # 3 valid samples per row, degree 3: guard fails -> coefficients zero,
+ # zx[0] -= avg anchors; the correction is the constant -avg
+ data = np.arange(80, dtype=float).reshape(10, 8)
+ mask = np.zeros_like(data)
+ mask[:, :3] = 1.0
+ out = gwyddion_align_rows_polynomial(_channel(data), degree=3, mask=mask,
+ mask_mode="include")
+ avg = float(np.mean(data))
+ expected = data + avg # corrected = input - (-avg)
+ assert np.array_equal(_bits(out.data), _bits(expected))
+
+
+def test_polynomial_degree_validation() -> None:
+ data = np.arange(24, dtype=float).reshape(4, 6)
+ with pytest.raises(ValueError, match="0..5"):
+ gwyddion_align_rows_polynomial(_channel(data), degree=6)
+ with pytest.raises(ValueError, match="0..5"):
+ gwyddion_align_rows_polynomial(_channel(data), degree=-1)
+ with pytest.raises(TypeError, match="integer"):
+ gwyddion_align_rows_polynomial(_channel(data), degree=1.5)
+
+
+def test_polynomial_non_square_fields() -> None:
+ wide = np.random.default_rng(7).normal(size=(4, 64))
+ tall = np.random.default_rng(7).normal(size=(64, 4))
+ out_wide = gwyddion_align_rows_polynomial(_channel(wide), degree=1)
+ out_tall = gwyddion_align_rows_polynomial(_channel(tall), degree=1)
+ assert out_wide.data.shape == wide.shape
+ assert out_tall.data.shape == tall.shape
+
+
+# ---------------------------------------------------------------------------
+# MODUS
+# ---------------------------------------------------------------------------
+
+def test_modus_constant_rows() -> None:
+ data = np.full((5, 10), 7.0)
+ out = gwyddion_align_rows_modus(_channel(data))
+ assert np.array_equal(_bits(out.data), _bits(data))
+
+
+def test_modus_distinct_row_centers() -> None:
+ data = np.array([[5.0] * 10, [5.0] * 10, [9.0] * 10, [9.0] * 10])
+ out = gwyddion_align_rows_modus(_channel(data))
+ # row modi 5,5,9,9 -> zero-levelled -2,-2,2,2 -> corrected flat at 7
+ expected = np.full_like(data, 7.0)
+ assert np.array_equal(_bits(out.data), _bits(expected))
+
+
+def test_modus_count_lt9_upper_median() -> None:
+ # 2 samples per row -> upper median (rank count//2 = 1)
+ data = np.array([[0.0, 10.0], [0.0, 10.0], [2.0, 8.0], [2.0, 8.0]])
+ out = gwyddion_align_rows_modus(_channel(data))
+ # row estimates: 10,10,8,8 -> zero-levelled shifts 1,1,-1,-1
+ expected = data - np.array([[1.0], [1.0], [-1.0], [-1.0]])
+ assert np.array_equal(_bits(out.data), _bits(expected))
+
+
+def test_modus_count_ge9_narrowest_window() -> None:
+ # 10 samples: 5 zeros + 5 tens -> window 3, narrowest range 0, central
+ # third selects a zero -> row estimate 0
+ data = np.array([[0.0] * 5 + [10.0] * 5] * 3)
+ out = gwyddion_align_rows_modus(_channel(data))
+ assert np.array_equal(_bits(out.data), _bits(data))
+
+
+def test_modus_equal_range_first_tie() -> None:
+ # 12 samples: 6 zeros + 6 tens -> multiple range-0 windows; the first
+ # strict minimum selects zeros -> estimate 0 (not 10)
+ data = np.array([[0.0] * 6 + [10.0] * 6] * 3)
+ out = gwyddion_align_rows_modus(_channel(data))
+ assert np.array_equal(_bits(out.data), _bits(data))
+
+
+def test_modus_repeated_values() -> None:
+ data = np.array([[2.0, 3.0] + [3.0] * 10, [2.0, 3.0] + [3.0] * 10] * 2)
+ out = gwyddion_align_rows_modus(_channel(data))
+ # row estimate 3 everywhere -> no correction
+ assert np.array_equal(_bits(out.data), _bits(data))
+
+
+def test_modus_outlier_resistance() -> None:
+ data = np.array([[5.0] * 8 + [-100.0, 100.0]] * 3)
+ out = gwyddion_align_rows_modus(_channel(data))
+ assert np.array_equal(_bits(out.data), _bits(data))
+
+
+def test_modus_no_valid_sample_fallback() -> None:
+ data = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]])
+ mask = np.zeros_like(data)
+ out = gwyddion_align_rows_modus(_channel(data), mask=mask,
+ mask_mode="include")
+ # no samples -> global median 0.0 fallback -> shifts 0 -> no change
+ assert np.array_equal(_bits(out.data), _bits(data))
+
+
+def test_modus_masking_mode_discrimination() -> None:
+ # bimodal rows: a 10-sample low population with a per-row offset and a
+ # 6-sample high population; zero-levelling preserves the different
+ # per-row modus estimates of each mask mode
+ rows = [
+ np.array([5.0 + 10.0 * i] * 10 + [100.0 + 3.0 * i + j for j in range(6)])
+ for i in range(4)
+ ]
+ data = np.stack(rows)
+ mask = (data > 50.0).astype(float)
+ ignore = gwyddion_align_rows_modus(_channel(data), mask=mask,
+ mask_mode="ignore")
+ include = gwyddion_align_rows_modus(_channel(data), mask=mask,
+ mask_mode="include")
+ exclude = gwyddion_align_rows_modus(_channel(data), mask=mask,
+ mask_mode="exclude")
+ # ignore equals no-mask behaviour; include differs from it here (the
+ # masked high population has fewer than 9 samples -> upper median)
+ plain = gwyddion_align_rows_modus(_channel(data))
+ assert np.array_equal(_bits(ignore.data), _bits(plain.data))
+ assert not np.array_equal(_bits(include.data), _bits(plain.data))
+ assert not np.array_equal(_bits(include.data), _bits(exclude.data))
+
+
+# ---------------------------------------------------------------------------
+# MATCH
+# ---------------------------------------------------------------------------
+
+def test_match_identical_rows() -> None:
+ data = np.tile(np.arange(16, dtype=float), (5, 1))
+ out = gwyddion_align_rows_match(_channel(data))
+ assert np.array_equal(_bits(out.data), _bits(data))
+
+
+def test_match_pure_offset_zero_weight_guard() -> None:
+ data = np.tile(np.arange(16, dtype=float), (5, 1))
+ data[3] += 5.0 # pure vertical offset, identical shape
+ out = gwyddion_align_rows_match(_channel(data))
+ # the source leaves pure offsets uncorrected (zero-weight guard)
+ assert np.array_equal(_bits(out.data), _bits(data))
+
+
+def test_match_active_shape_dependent_correction() -> None:
+ base = np.arange(16, dtype=float)
+ data = np.stack([base, base.copy()])
+ data[1, 8] = 9.0 # shape bump in the second row
+ out = gwyddion_align_rows_match(_channel(data))
+ assert not np.array_equal(_bits(out.data), _bits(data))
+
+
+def test_match_sequential_cumulative_correction() -> None:
+ base = np.arange(16, dtype=float)
+ data = np.stack([base, base.copy(), base.copy(), base.copy()])
+ data[1, 8] = 9.0
+ data[2, 8] = 9.0
+ data[3, 8] = 9.0
+ out = gwyddion_align_rows_match(_channel(data))
+ assert not np.array_equal(_bits(out.data), _bits(data))
+
+
+def test_match_alternating_offsets() -> None:
+ base = np.arange(16, dtype=float)
+ data = np.stack([base, base + 3.0, base, base + 3.0])
+ out = gwyddion_align_rows_match(_channel(data))
+ # all pure offsets -> zero weight -> no correction
+ assert np.array_equal(_bits(out.data), _bits(data))
+
+
+def test_match_endpoint_inclusion() -> None:
+ # mask the whole interior: only endpoints contribute; weights at
+ # masked positions are zero, so a pure offset still yields no
+ # correction
+ base = np.arange(16, dtype=float)
+ data = np.stack([base, base + 5.0])
+ mask = np.ones_like(data)
+ mask[:, 1:-1] = 0.0
+ out = gwyddion_align_rows_match(_channel(data), mask=mask,
+ mask_mode="include")
+ assert np.array_equal(_bits(out.data), _bits(data))
+
+
+def test_match_no_valid_overlap_guard() -> None:
+ base = np.arange(16, dtype=float)
+ data = np.stack([base, base + 2.0])
+ mask = np.zeros_like(data)
+ mask[0] = 1.0 # only row 0 masked in -> no valid overlap under include
+ out = gwyddion_align_rows_match(_channel(data), mask=mask,
+ mask_mode="include")
+ assert np.array_equal(_bits(out.data), _bits(data))
+
+
+def test_match_yres_one() -> None:
+ data = np.arange(16, dtype=float).reshape(1, 16)
+ out = gwyddion_align_rows_match(_channel(data))
+ assert np.array_equal(_bits(out.data), _bits(data))
+
+
+def test_match_yres_two() -> None:
+ base = np.arange(16, dtype=float)
+ data = np.stack([base, base + 1.0])
+ data[1, 8] = 8.0 # bump -> shape mismatch activates matching
+ out = gwyddion_align_rows_match(_channel(data))
+ assert not np.array_equal(_bits(out.data), _bits(data))
+
+
+def test_match_masking_mode_discrimination() -> None:
+ data = np.tile(np.arange(16, dtype=float), (4, 1))
+ data[2, 8] = 9.0
+ data[3, 8] = 9.0
+ mask = np.zeros_like(data)
+ mask[:, 4:9] = 1.0
+ ignore = gwyddion_align_rows_match(_channel(data), mask=mask,
+ mask_mode="ignore")
+ include = gwyddion_align_rows_match(_channel(data), mask=mask,
+ mask_mode="include")
+ exclude = gwyddion_align_rows_match(_channel(data), mask=mask,
+ mask_mode="exclude")
+ plain = gwyddion_align_rows_match(_channel(data))
+ assert np.array_equal(_bits(ignore.data), _bits(plain.data))
+ assert not np.array_equal(_bits(include.data), _bits(plain.data))
+ assert not np.array_equal(_bits(exclude.data), _bits(plain.data))
+
+
+def test_match_rejects_xres_one() -> None:
+ with pytest.raises(ValueError, match="two columns"):
+ gwyddion_align_rows_match(_channel(np.zeros((4, 1))))
diff --git a/tests/core/test_gwydion_laplace_interpolation.py b/tests/core/test_gwydion_laplace_interpolation.py
new file mode 100644
index 0000000..dc0c090
--- /dev/null
+++ b/tests/core/test_gwydion_laplace_interpolation.py
@@ -0,0 +1,278 @@
+"""Core contract tests for gwydion_interpolate_data_under_mask (Laplace)."""
+
+from __future__ import annotations
+
+import numpy as np
+import pytest
+
+from spmkit.core.analysis import gwydion_interpolate_data_under_mask
+from spmkit.core.analysis._gwydion_laplace import _gwydion_laplace_result
+from spmkit.core.models.spmdata import SPMChannel
+
+
+def _channel(data: np.ndarray, name: str = "laplace") -> SPMChannel:
+ return SPMChannel(
+ name=name, data=np.asarray(data, dtype=np.float64), unit="nm",
+ x_range=float(data.shape[1]), y_range=float(data.shape[0]),
+ metadata={"Dim1Name": "Y", "custom": 7})
+
+
+def _mask(shape: tuple[int, int], *rects: tuple[int, int, int, int],
+ value: float = 1.0) -> np.ndarray:
+ m = np.zeros(shape, dtype=np.float64)
+ for r0, r1, c0, c1 in rects:
+ m[r0:r1 + 1, c0:c1 + 1] = value
+ return m
+
+
+def _gradient(shape: tuple[int, int]) -> np.ndarray:
+ yres, xres = shape
+ return np.asarray(
+ [[float(i + j) for j in range(xres)] for i in range(yres)],
+ dtype=np.float64)
+
+
+def _bits(array: np.ndarray) -> np.ndarray:
+ return np.ascontiguousarray(array, dtype=np.float64).view(np.uint64)
+
+
+def _assert_unchanged(out: SPMChannel, inp: np.ndarray) -> None:
+ assert np.array_equal(_bits(out.data), _bits(inp))
+
+
+# ---------------------------------------------------------------------------
+# Policies
+# ---------------------------------------------------------------------------
+
+
+def test_empty_mask_unchanged() -> None:
+ field = _gradient((6, 8))
+ out = gwydion_interpolate_data_under_mask(_channel(field), np.zeros((6, 8)))
+ _assert_unchanged(out, field)
+ assert out.data is not field # independent copy
+
+
+def test_whole_field_mask_zeros() -> None:
+ field = _gradient((6, 6))
+ out = gwydion_interpolate_data_under_mask(
+ _channel(field), np.ones((6, 6)))
+ assert not np.any(out.data != 0.0)
+
+
+def test_isolated_pixels() -> None:
+ field = _gradient((5, 5))
+ interior = gwydion_interpolate_data_under_mask(
+ _channel(field), _mask((5, 5), (2, 2, 2, 2)))
+ assert interior.data[2, 2] == (field[1, 2] + field[3, 2]
+ + field[2, 1] + field[2, 3]) / 4.0
+ edge = gwydion_interpolate_data_under_mask(
+ _channel(field), _mask((5, 5), (0, 0, 2, 2)))
+ assert edge.data[0, 2] == (field[1, 2] + field[0, 1] + field[0, 3]) / 3.0
+ corner = gwydion_interpolate_data_under_mask(
+ _channel(field), _mask((5, 5), (0, 0, 0, 0)))
+ assert corner.data[0, 0] == (field[1, 0] + field[0, 1]) / 2.0
+
+
+def test_thin_corridors() -> None:
+ field = _gradient((5, 7)) # 5 rows, 7 columns (fixture L05)
+ horiz = gwydion_interpolate_data_under_mask(
+ _channel(field), _mask((5, 7), (2, 2, 1, 5)))
+ # exact linear continuation: d = i + j (fully interior corridor); the
+ # Thomas elimination rounds the middle pixel one ULP below the exact
+ # value (frozen L05/L06 characterization, bitwise vs the compiled probe)
+ assert list(horiz.data[2, 1:6]) == [3.0, 4.0, 4.999999999999999,
+ 6.0, 7.0]
+ field_v = _gradient((7, 5)) # 7 rows, 5 columns (fixture L06)
+ vert = gwydion_interpolate_data_under_mask(
+ _channel(field_v), _mask((7, 5), (1, 5, 3, 3)))
+ assert list(vert.data[1:6, 3]) == [4.0, 5.0, 5.999999999999999,
+ 7.0, 8.0]
+
+
+def test_three_pixel_l() -> None:
+ field = _gradient((6, 6))
+ m = _mask((6, 6), (2, 2, 2, 3), (3, 3, 2, 2))
+ out = gwydion_interpolate_data_under_mask(_channel(field), m)
+ assert out.data[2, 2] == 4.0
+ assert out.data[2, 3] == 5.0
+ assert out.data[3, 2] == 5.0
+
+
+def test_interior_component() -> None:
+ field = _gradient((10, 12))
+ out = gwydion_interpolate_data_under_mask(
+ _channel(field), _mask((10, 12), (4, 6, 3, 7)))
+ # linear continuation is harmonic: d = i + j exactly
+ for i in range(4, 7):
+ for j in range(3, 8):
+ assert out.data[i, j] == float(i + j)
+
+
+def test_disconnected_components() -> None:
+ field = _gradient((12, 12))
+ m = _mask((12, 12), (2, 3, 2, 4), (7, 9, 8, 10))
+ out = gwydion_interpolate_data_under_mask(_channel(field), m)
+ assert out.data[2, 3] == 5.0 and out.data[7, 9] == 16.0
+
+
+def test_edge_and_corner_components() -> None:
+ field = _gradient((10, 10))
+ edge = gwydion_interpolate_data_under_mask(
+ _channel(field), _mask((10, 10), (0, 2, 3, 5)))
+ assert edge.data[0, 3] != 0.0 and not np.isnan(edge.data[0, 3])
+ corner = gwydion_interpolate_data_under_mask(
+ _channel(field), _mask((10, 10), (0, 1, 0, 2)))
+ assert corner.data[0, 0] != 0.0 and not np.isnan(corner.data[0, 0])
+
+
+def test_entire_masked_row() -> None:
+ field = _gradient((10, 10))
+ out = gwydion_interpolate_data_under_mask(
+ _channel(field), _mask((10, 10), (4, 4, 0, 9)))
+ row = out.data[4, :]
+ assert np.all(np.isfinite(row))
+ # the row touches both image edges, so the edge pixels carry the
+ # Neumann-by-omission condition and the solution is not the linear ramp;
+ # verify the discrete equations directly
+ for j in range(10):
+ if 0 < j < 9:
+ lhs = 4 * row[j] - row[j - 1] - row[j + 1]
+ rhs = field[3, j] + field[5, j]
+ else:
+ lhs = 3 * row[j] - (row[j - 1] if j else row[j + 1])
+ rhs = field[3, j] + field[5, j]
+ assert abs(lhs - rhs) < 1e-9
+ # unmasked rows unchanged
+ for i in (0, 1, 2, 3, 5, 6, 7, 8, 9):
+ assert list(out.data[i, :]) == list(field[i, :])
+
+
+def test_constant_boundary() -> None:
+ field = np.full((8, 8), 3.0)
+ out = gwydion_interpolate_data_under_mask(
+ _channel(field), _mask((8, 8), (3, 4, 3, 4)))
+ assert not np.any(out.data != 3.0)
+
+
+def test_strict_mask_predicate() -> None:
+ field = _gradient((8, 8))
+ m = _mask((8, 8), (2, 3, 2, 4), value=0.5)
+ m[5, 5] = -1.0
+ m[6, 2] = 0.0
+ m[1, 5] = 1.0
+ result = _gwydion_laplace_result(field, m)
+ solved = set(result.solved_coordinates)
+ assert (2, 2) in solved and (2, 3) in solved # 0.5 counts as masked
+ assert (1, 5) in solved # 1.0 counts as masked
+ assert (5, 5) not in solved # -1.0 fixed
+ assert (6, 2) not in solved # 0.0 fixed
+ out = gwydion_interpolate_data_under_mask(_channel(field), m)
+ assert out.data[5, 5] == field[5, 5]
+ assert out.data[6, 2] == field[6, 2]
+
+
+def test_calibration_independence() -> None:
+ field = _gradient((8, 8))
+ m = _mask((8, 8), (3, 5, 2, 4))
+ a = gwydion_interpolate_data_under_mask(_channel(field), m)
+ b = gwydion_interpolate_data_under_mask(
+ SPMChannel(name="x", data=field.copy(), unit="m", x_range=0.123,
+ y_range=4.56), m)
+ assert np.array_equal(_bits(a.data), _bits(b.data))
+
+
+def test_signed_zero_behavior() -> None:
+ field = np.full((5, 5), -0.0)
+ out = gwydion_interpolate_data_under_mask(
+ _channel(field), _mask((5, 5), (2, 2, 2, 2)))
+ # all-negative-zero ring: the mean preserves -0.0 (dynamically linked
+ # build semantics; the frozen source fold seeded with 0.0 gives +0.0)
+ assert int(out.data[2, 2].view(np.uint64)) == 0x8000000000000000
+
+
+def test_degenerate_dimensions() -> None:
+ one_masked = gwydion_interpolate_data_under_mask(
+ _channel(np.array([[7.0]])), np.array([[1.0]]))
+ assert one_masked.data[0, 0] == 0.0
+ one_unmasked = gwydion_interpolate_data_under_mask(
+ _channel(np.array([[7.0]])), np.array([[0.0]]))
+ assert one_unmasked.data[0, 0] == 7.0
+ row = gwydion_interpolate_data_under_mask(
+ _channel(np.array([[1.0, 3.0, 5.0]])),
+ np.array([[0.0, 1.0, 0.0]]))
+ assert row.data[0, 1] == 3.0
+ col = gwydion_interpolate_data_under_mask(
+ _channel(np.array([[2.0], [6.0], [10.0]])),
+ np.array([[0.0], [1.0], [0.0]]))
+ assert col.data[1, 0] == 6.0
+
+
+# ---------------------------------------------------------------------------
+# Non-mutation, context and validation
+# ---------------------------------------------------------------------------
+
+
+def test_input_and_mask_non_mutation() -> None:
+ field = _gradient((8, 8))
+ m = _mask((8, 8), (3, 5, 2, 4))
+ field_bits = _bits(field).copy()
+ mask_bits = _bits(m).copy()
+ gwydion_interpolate_data_under_mask(_channel(field), m)
+ assert np.array_equal(_bits(field), field_bits)
+ assert np.array_equal(_bits(m), mask_bits)
+
+
+def test_unmasked_pixels_bitwise_unchanged() -> None:
+ field = _gradient((8, 8))
+ m = _mask((8, 8), (3, 5, 2, 4))
+ m[6, 6] = 0.5
+ out = gwydion_interpolate_data_under_mask(_channel(field), m)
+ for i in range(8):
+ for j in range(8):
+ if m[i, j] <= 0.0:
+ assert out.data[i, j] == field[i, j]
+
+
+def test_channel_context_preserved() -> None:
+ field = _gradient((6, 8))
+ ch = _channel(field)
+ out = gwydion_interpolate_data_under_mask(ch, _mask((6, 8), (2, 3, 2, 4)))
+ assert out.name == ch.name
+ assert out.unit == ch.unit
+ assert out.x_range == ch.x_range
+ assert out.y_range == ch.y_range
+ assert out.direction == ch.direction
+ assert out.group == ch.group
+ assert out.metadata == ch.metadata
+ assert out is not ch
+
+
+def test_convergence_diagnostics() -> None:
+ field = _gradient((10, 12))
+ result = _gwydion_laplace_result(field, _mask((10, 12), (4, 6, 3, 7)))
+ assert result.max_residual <= 1e-12
+ assert result.component_count >= 1
+ assert len(result.iteration_counts) == result.component_count
+ assert result.unmasked_mutation_count == 0
+ assert not result.mask_mutation_evidence
+ assert not result.input_mutation_evidence
+
+
+def test_validation_errors() -> None:
+ field = _gradient((8, 8))
+ with pytest.raises(ValueError):
+ gwydion_interpolate_data_under_mask(_channel(field), np.zeros((9, 8)))
+ with pytest.raises(ValueError):
+ gwydion_interpolate_data_under_mask(_channel(field), np.zeros((8, 8, 1)))
+ with pytest.raises(ValueError):
+ gwydion_interpolate_data_under_mask(
+ _channel(field), np.full((8, 8), np.nan))
+ with pytest.raises(ValueError):
+ gwydion_interpolate_data_under_mask(
+ SPMChannel(name="x", data=np.full((4, 4), np.inf), unit="nm",
+ x_range=4.0, y_range=4.0), np.zeros((4, 4)))
+ with pytest.raises(TypeError):
+ gwydion_interpolate_data_under_mask(
+ SPMChannel(name="x", data=np.zeros((4, 4)), unit="nm",
+ x_range=4.0, y_range=4.0),
+ np.zeros((4, 4), dtype=complex))
diff --git a/tests/core/test_gwydion_mark_inverted_rows.py b/tests/core/test_gwydion_mark_inverted_rows.py
new file mode 100644
index 0000000..111fb02
--- /dev/null
+++ b/tests/core/test_gwydion_mark_inverted_rows.py
@@ -0,0 +1,169 @@
+"""Core contract tests for Gwydion 2.71 Mark Inverted Rows.
+
+Tests the public and private contracts independently of the frozen fixture
+comparison, using source-derived analytic expectations.
+"""
+
+from __future__ import annotations
+
+import numpy as np
+import pytest
+
+from spmkit.core.analysis import gwydion_mark_inverted_rows
+from spmkit.core.analysis._gwydion_mark_inverted_rows import (
+ _gwydion_mark_inverted_rows_result,
+)
+from spmkit.core.models.spmdata import SPMChannel
+
+BASE = np.array([-2.0, -1.0, 0.0, 1.0, 2.0], dtype=np.float64)
+
+
+def _channel(data: np.ndarray) -> SPMChannel:
+ return SPMChannel(
+ name="test",
+ data=np.asarray(data, dtype=np.float64),
+ unit="nm",
+ x_range=float(data.shape[1]),
+ y_range=float(data.shape[0]),
+ )
+
+
+def _bits(array: np.ndarray) -> np.ndarray:
+ return np.ascontiguousarray(array, dtype=np.float64).view(np.uint64)
+
+
+def _marked_rows(mask: np.ndarray) -> list[int]:
+ return [int(r) for r in range(mask.shape[0]) if np.any(mask[r] == 1.0)]
+
+
+def test_input_non_mutation_and_mask_independence() -> None:
+ data = np.vstack([BASE + i for i in range(5)])
+ channel = _channel(data)
+ original = data.copy()
+ mask = gwydion_mark_inverted_rows(channel)
+ assert np.array_equal(_bits(channel.data), _bits(original))
+ assert mask.shape == data.shape
+ assert mask.dtype == np.float64
+ assert mask.flags.c_contiguous
+ assert mask is not channel.data
+ # returned array is an independent copy
+ mask[0, 0] = 12345.0
+ assert np.all(channel.data != 12345.0)
+
+
+def test_mask_values_exactly_binary() -> None:
+ data = np.vstack([BASE, -BASE, BASE, BASE, BASE])
+ mask = gwydion_mark_inverted_rows(_channel(data))
+ assert set(np.unique(mask)) <= {0.0, 1.0}
+
+
+def test_all_positive_correlations_zero_mask() -> None:
+ data = np.vstack([BASE + i for i in range(5)])
+ mask = gwydion_mark_inverted_rows(_channel(data))
+ assert np.all(mask == 0.0)
+
+
+def test_one_inverted_interior_row() -> None:
+ data = np.vstack([BASE, -BASE, BASE, BASE, BASE])
+ mask = gwydion_mark_inverted_rows(_channel(data))
+ assert _marked_rows(mask) == [1]
+
+
+def test_first_and_last_row_inversion() -> None:
+ data = np.vstack([-BASE, BASE, BASE, BASE, BASE])
+ assert _marked_rows(gwydion_mark_inverted_rows(_channel(data))) == [0]
+ data = np.vstack([BASE, BASE, BASE, BASE, -BASE])
+ assert _marked_rows(gwydion_mark_inverted_rows(_channel(data))) == [4]
+
+
+def test_consecutive_inverted_rows() -> None:
+ scaled = -0.8 * BASE
+ data = np.vstack([BASE, BASE, scaled, scaled, BASE])
+ mask = gwydion_mark_inverted_rows(_channel(data))
+ assert _marked_rows(mask) == [2, 3]
+
+
+def test_repeated_toggles() -> None:
+ data = np.vstack([BASE, -0.8 * BASE, 0.7 * BASE, -0.6 * BASE, 0.5 * BASE])
+ mask = gwydion_mark_inverted_rows(_channel(data))
+ assert _marked_rows(mask) == [0, 1, 3]
+
+
+def test_constant_field_guard() -> None:
+ data = np.full((5, 5), 5.0, dtype=np.float64)
+ mask = gwydion_mark_inverted_rows(_channel(data))
+ assert np.all(mask == 0.0)
+
+
+def test_constant_row_in_varying_field() -> None:
+ data = np.vstack([BASE, np.full(5, 3.0), BASE, BASE, BASE])
+ mask = gwydion_mark_inverted_rows(_channel(data))
+ assert np.all(mask == 0.0) # zero weights -> no negative -> no mask
+
+
+def test_strict_first_anchor_tie() -> None:
+ data = np.vstack([BASE, BASE, -BASE, -BASE, BASE])
+ result = _gwydion_mark_inverted_rows_result(data)
+ # weights [+2.5, -2.5, +2.5, -2.5]: w0 and w2 tie -> first maximum
+ assert result.anchor_index == 0
+ mask = gwydion_mark_inverted_rows(_channel(data))
+ assert _marked_rows(mask) == [2, 3]
+
+
+@pytest.mark.parametrize("shape", [(2, 5), (3, 2)])
+def test_dimension_guards(shape) -> None:
+ data = np.arange(np.prod(shape), dtype=np.float64).reshape(shape)
+ result = _gwydion_mark_inverted_rows_result(data)
+ assert result.guard_triggered
+ assert result.generated_mask is None
+ mask = gwydion_mark_inverted_rows(_channel(data))
+ assert np.all(mask == 0.0)
+
+
+def test_private_existing_mask_preserved_on_no_negative() -> None:
+ data = np.vstack([BASE + i for i in range(5)])
+ existing = np.zeros((5, 5), dtype=np.float64)
+ existing[2, :] = 1.0
+ before = existing.copy()
+ result = _gwydion_mark_inverted_rows_result(data, existing_mask=existing)
+ assert result.generated_mask is None
+ assert result.would_create_mask is False
+ assert result.would_overwrite_existing_mask is False
+ assert np.array_equal(_bits(existing), _bits(before))
+
+
+def test_private_existing_mask_overwritten_on_detection() -> None:
+ data = np.vstack([BASE, -BASE, BASE, BASE, BASE])
+ existing = np.zeros((5, 5), dtype=np.float64)
+ existing[0, :] = 1.0
+ existing[1, :] = 0.5
+ existing[4, :] = 1.0
+ result = _gwydion_mark_inverted_rows_result(data, existing_mask=existing)
+ assert result.generated_mask is not None
+ assert result.would_create_mask is True
+ assert result.would_overwrite_existing_mask is True
+ assert np.array_equal(_bits(existing), _bits(result.generated_mask))
+ assert _marked_rows(existing) == [1]
+
+
+def test_public_all_zero_adaptation_on_no_detection() -> None:
+ data = np.vstack([BASE + i for i in range(5)])
+ mask = gwydion_mark_inverted_rows(_channel(data))
+ assert mask.shape == data.shape
+ assert np.all(mask == 0.0)
+
+
+def test_input_never_modified_privately() -> None:
+ data = np.vstack([BASE, -BASE, BASE, BASE, BASE])
+ original = data.copy()
+ _gwydion_mark_inverted_rows_result(data)
+ assert np.array_equal(_bits(data), _bits(original))
+
+
+def test_non_finite_rejection() -> None:
+ bad = np.array([[1.0, np.nan], [2.0, 3.0]])
+ with pytest.raises(ValueError, match="finite"):
+ gwydion_mark_inverted_rows(_channel(bad))
+ bad = np.array([[1.0, np.inf], [2.0, 3.0]])
+ with pytest.raises(ValueError, match="finite"):
+ gwydion_mark_inverted_rows(_channel(bad))
diff --git a/tests/core/test_gwydion_mark_scars.py b/tests/core/test_gwydion_mark_scars.py
new file mode 100644
index 0000000..5061e72
--- /dev/null
+++ b/tests/core/test_gwydion_mark_scars.py
@@ -0,0 +1,279 @@
+"""Core contract tests for gwydion_mark_scars (production Mark Scars)."""
+
+from __future__ import annotations
+
+import numpy as np
+import pytest
+
+from spmkit.core.analysis import gwydion_mark_scars
+from spmkit.core.models.spmdata import SPMChannel
+
+
+def _channel(data: np.ndarray, name: str = "markscars") -> SPMChannel:
+ return SPMChannel(
+ name=name, data=np.asarray(data, dtype=np.float64), unit="nm",
+ x_range=float(data.shape[1]), y_range=float(data.shape[0]))
+
+
+def _field(rows: int, cols: int, band_row: int | None = None,
+ value: float = 5.0) -> np.ndarray:
+ field = np.zeros((rows, cols), dtype=np.float64)
+ if band_row is not None:
+ field[band_row, :] = value
+ return field
+
+
+def _bits(array: np.ndarray) -> np.ndarray:
+ return np.ascontiguousarray(array, dtype=np.float64).view(np.uint64)
+
+
+# ---------------------------------------------------------------------------
+# Public parameter validation
+# ---------------------------------------------------------------------------
+
+
+def test_threshold_domain_validation() -> None:
+ ch = _channel(_field(10, 10, 4))
+ for kwargs in [{"threshold_high": -0.1}, {"threshold_high": 2.1},
+ {"threshold_low": -0.1}, {"threshold_low": 2.1},
+ {"threshold_high": np.nan}, {"threshold_low": np.inf}]:
+ with pytest.raises(ValueError):
+ gwydion_mark_scars(ch, **kwargs)
+
+
+def test_integer_domain_validation() -> None:
+ ch = _channel(_field(10, 10, 4))
+ for kwargs in [{"min_length": 0}, {"min_length": 1025},
+ {"max_width": 0}, {"max_width": 17}]:
+ with pytest.raises(ValueError):
+ gwydion_mark_scars(ch, **kwargs)
+ with pytest.raises(TypeError):
+ gwydion_mark_scars(ch, min_length=4.5)
+ with pytest.raises(TypeError):
+ gwydion_mark_scars(ch, max_width=True)
+
+
+def test_polarity_and_combine_validation() -> None:
+ ch = _channel(_field(10, 10, 4))
+ with pytest.raises(ValueError):
+ gwydion_mark_scars(ch, polarity="sideways")
+ with pytest.raises(ValueError):
+ gwydion_mark_scars(ch, combine="xor")
+ with pytest.raises(ValueError):
+ gwydion_mark_scars(ch, combine="union")
+ with pytest.raises(ValueError):
+ gwydion_mark_scars(ch, combine="intersection")
+
+
+def test_channel_and_mask_validation() -> None:
+ ch = _channel(_field(10, 10, 4))
+ with pytest.raises(ValueError):
+ gwydion_mark_scars(ch, existing_mask=np.zeros((9, 9)))
+ with pytest.raises(ValueError):
+ gwydion_mark_scars(ch, existing_mask=np.full((10, 10), np.nan))
+ bad = SPMChannel(name="x", data=np.full((4, 4), np.nan), unit="nm",
+ x_range=4.0, y_range=4.0)
+ with pytest.raises(ValueError):
+ gwydion_mark_scars(bad)
+ flat = SPMChannel(name="x", data=np.zeros(16), unit="nm",
+ x_range=4.0, y_range=4.0)
+ with pytest.raises(ValueError):
+ gwydion_mark_scars(flat)
+
+
+# ---------------------------------------------------------------------------
+# Detector semantics
+# ---------------------------------------------------------------------------
+
+
+def test_threshold_sanitization() -> None:
+ # reversed thresholds: effective high becomes low (0.666)
+ field = _field(10, 10, 4)
+ mask_san = gwydion_mark_scars(
+ _channel(field), threshold_high=0.25, threshold_low=0.666,
+ min_length=4, max_width=1, polarity="positive")
+ mask_ref = gwydion_mark_scars(
+ _channel(field), threshold_high=0.666, threshold_low=0.666,
+ min_length=4, max_width=1, polarity="positive")
+ assert np.array_equal(_bits(mask_san), _bits(mask_ref))
+
+
+def test_positive_negative_both() -> None:
+ field = np.zeros((12, 10), dtype=np.float64)
+ field[3, :] = 5.0
+ field[8, :] = -5.0
+ pos = gwydion_mark_scars(_channel(field), min_length=4, max_width=1,
+ polarity="positive")
+ neg = gwydion_mark_scars(_channel(field), min_length=4, max_width=1,
+ polarity="negative")
+ both = gwydion_mark_scars(_channel(field), min_length=4, max_width=1,
+ polarity="both")
+ assert np.all(pos[3, :] == 1.0) and not np.any(pos[8, :])
+ assert np.all(neg[8, :] == 1.0) and not np.any(neg[3, :])
+ # Both = two detector runs plus fmax union (binary union here)
+ assert np.array_equal(_bits(both), _bits(np.fmax(pos, neg)))
+ assert int(np.count_nonzero(both)) == 20
+
+
+def test_hard_seed_and_soft_attachment() -> None:
+ field = np.zeros((10, 8), dtype=np.float64)
+ field[4, 0:5] = 5.0 # hard
+ field[4, 5:8] = 1.0 # soft shoulder
+ mask = gwydion_mark_scars(_channel(field), min_length=4, max_width=1,
+ polarity="positive")
+ assert int(np.count_nonzero(mask)) == 8 # entire row attached
+
+
+def test_soft_only_rejected() -> None:
+ # A uniform single-row band always has weight sqrt(5) ~ 2.236, so a
+ # soft-only configuration (weight in [threshold_low, threshold_high))
+ # needs threshold_high > sqrt(5), which is outside the public domain
+ # [0, 2]. Mirroring the frozen campaign (C05/C07), the kernel-level
+ # contract is exercised directly with threshold_high=3.0.
+ from spmkit.core.analysis._gwydion_mark_scars import (
+ _gwydion_mark_scars_result,
+ )
+ field = np.zeros((10, 8), dtype=np.float64)
+ field[4, :] = 1.0
+ result = _gwydion_mark_scars_result(
+ field, threshold_high=3.0, threshold_low=0.25, min_length=4,
+ max_width=1, polarity="positive")
+ assert int(np.count_nonzero(result.final_mask)) == 0
+ assert result.guard_reason is None
+
+
+def test_width_boundaries() -> None:
+ field = np.zeros((10, 8), dtype=np.float64)
+ field[4, :] = 5.0
+ field[5, :] = 5.0
+ # width exactly max_width -> marked
+ mask = gwydion_mark_scars(_channel(field), min_length=4, max_width=2,
+ polarity="positive")
+ assert int(np.count_nonzero(mask)) == 16
+ field2 = np.zeros((10, 8), dtype=np.float64)
+ field2[4, :] = 5.0
+ field2[5, :] = 5.0
+ field2[6, :] = 5.0
+ # width max_width + 1 -> window cannot close -> rejected
+ mask2 = gwydion_mark_scars(_channel(field2), min_length=4, max_width=2,
+ polarity="positive")
+ assert int(np.count_nonzero(mask2)) == 0
+
+
+def test_length_boundaries() -> None:
+ field = np.zeros((10, 8), dtype=np.float64)
+ field[4, 0:4] = 5.0
+ assert int(np.count_nonzero(
+ gwydion_mark_scars(_channel(field), min_length=4, max_width=1,
+ polarity="positive"))) == 4
+ field2 = np.zeros((10, 8), dtype=np.float64)
+ field2[4, 0:3] = 5.0
+ assert int(np.count_nonzero(
+ gwydion_mark_scars(_channel(field2), min_length=4, max_width=1,
+ polarity="positive"))) == 0
+
+
+def test_first_last_row_excluded() -> None:
+ field = np.zeros((10, 8), dtype=np.float64)
+ field[0, :] = 5.0
+ field[9, :] = -5.0
+ mask = gwydion_mark_scars(_channel(field), min_length=4, max_width=1,
+ polarity="both")
+ assert int(np.count_nonzero(mask)) == 0
+
+
+def test_horizontal_edge_runs() -> None:
+ field = np.zeros((10, 8), dtype=np.float64)
+ field[6, :] = 3.0
+ mask = gwydion_mark_scars(_channel(field), min_length=4, max_width=1,
+ polarity="positive")
+ assert int(np.count_nonzero(mask)) == 8
+
+
+def test_constant_field_guard() -> None:
+ mask = gwydion_mark_scars(_channel(np.ones((10, 10))), min_length=2,
+ max_width=1, polarity="both")
+ assert int(np.count_nonzero(mask)) == 0
+
+
+def test_minimum_dimensions() -> None:
+ field = np.zeros((3, 2), dtype=np.float64)
+ field[1, :] = 5.0
+ mask = gwydion_mark_scars(_channel(field), min_length=1, max_width=1,
+ polarity="positive")
+ assert int(np.count_nonzero(mask)) == 2
+
+
+def test_binary_and_contiguous_output() -> None:
+ field = _field(10, 10, 4)
+ mask = gwydion_mark_scars(_channel(field), min_length=4, max_width=1,
+ polarity="positive")
+ assert mask.dtype == np.float64
+ assert mask.flags.c_contiguous
+ assert set(np.unique(mask)) <= {0.0, 1.0}
+ # output is independent of the returned array
+ mask[0, 0] = 99.0
+ mask2 = gwydion_mark_scars(_channel(field), min_length=4, max_width=1,
+ polarity="positive")
+ assert mask2[0, 0] == 0.0
+
+
+# ---------------------------------------------------------------------------
+# Combine semantics
+# ---------------------------------------------------------------------------
+
+
+def test_replace_union_intersection() -> None:
+ field = _field(10, 10, 4)
+ existing = np.zeros((10, 10), dtype=np.float64)
+ existing[2, :] = 1.0
+ replaced = gwydion_mark_scars(_channel(field), min_length=4, max_width=1,
+ polarity="positive", existing_mask=existing,
+ combine="replace")
+ plain = gwydion_mark_scars(_channel(field), min_length=4, max_width=1,
+ polarity="positive")
+ assert np.array_equal(_bits(replaced), _bits(plain))
+ assert int(np.count_nonzero(plain)) == 10 # full 10-column row
+ union = gwydion_mark_scars(_channel(field), min_length=4, max_width=1,
+ polarity="positive", existing_mask=existing,
+ combine="union")
+ assert int(np.count_nonzero(union)) == 20
+ assert np.array_equal(_bits(union), _bits(np.fmax(plain, existing)))
+ existing2 = np.zeros((10, 10), dtype=np.float64)
+ existing2[4, 0:4] = 1.0
+ intersection = gwydion_mark_scars(
+ _channel(field), min_length=4, max_width=1, polarity="positive",
+ existing_mask=existing2, combine="intersection")
+ assert int(np.count_nonzero(intersection)) == 4
+ assert np.array_equal(_bits(intersection), _bits(np.fmin(plain, existing2)))
+
+
+def test_non_binary_existing_mask_preserved_through_fmax() -> None:
+ field = _field(10, 10, 4)
+ existing = np.zeros((10, 10), dtype=np.float64)
+ existing[2, 0:4] = 0.5 # finite non-binary values
+ union = gwydion_mark_scars(_channel(field), min_length=4, max_width=1,
+ polarity="positive", existing_mask=existing,
+ combine="union")
+ assert 0.5 in np.unique(union)
+ assert not (set(np.unique(union)) <= {0.0, 1.0})
+
+
+def test_input_and_existing_mask_non_mutation() -> None:
+ field = _field(10, 10, 4)
+ existing = np.zeros((10, 10), dtype=np.float64)
+ existing[2, :] = 1.0
+ field_bits = _bits(field).copy()
+ existing_bits = _bits(existing).copy()
+ gwydion_mark_scars(_channel(field), min_length=4, max_width=1,
+ polarity="both", existing_mask=existing,
+ combine="union")
+ assert np.array_equal(_bits(field), field_bits)
+ assert np.array_equal(_bits(existing), existing_bits)
+
+
+def test_no_detection_returns_all_zero() -> None:
+ mask = gwydion_mark_scars(_channel(np.zeros((10, 10))), min_length=4,
+ max_width=1, polarity="both")
+ assert int(np.count_nonzero(mask)) == 0
+ assert set(np.unique(mask)) <= {0.0}
diff --git a/tests/core/test_gwydion_remove_scars.py b/tests/core/test_gwydion_remove_scars.py
new file mode 100644
index 0000000..d890d42
--- /dev/null
+++ b/tests/core/test_gwydion_remove_scars.py
@@ -0,0 +1,135 @@
+"""Core contract tests for gwydion_remove_scars (production composition)."""
+
+from __future__ import annotations
+
+import numpy as np
+import pytest
+
+from spmkit.core.analysis import (
+ gwydion_interpolate_data_under_mask,
+ gwydion_mark_scars,
+ gwydion_remove_scars,
+)
+from spmkit.core.analysis._gwydion_remove_scars import _gwydion_remove_scars_result
+from spmkit.core.models.spmdata import SPMChannel
+
+
+def _channel(data: np.ndarray, name: str = "removescars") -> SPMChannel:
+ return SPMChannel(
+ name=name, data=np.asarray(data, dtype=np.float64), unit="nm",
+ x_range=float(data.shape[1]), y_range=float(data.shape[0]),
+ metadata={"Dim1Name": "Y"})
+
+
+def _bits(array: np.ndarray) -> np.ndarray:
+ return np.ascontiguousarray(array, dtype=np.float64).view(np.uint64)
+
+
+def _explicit_composition(field: np.ndarray, **kwargs) -> np.ndarray:
+ """Mark-plus-Laplace composition built from the public primitives."""
+ mask = gwydion_mark_scars(_channel(field), **kwargs)
+ return gwydion_interpolate_data_under_mask(_channel(field), mask).data
+
+
+def test_public_result_equals_explicit_composition() -> None:
+ field = np.zeros((16, 20), dtype=np.float64)
+ field[4, :] = 5.0
+ out = gwydion_remove_scars(_channel(field))
+ explicit = _explicit_composition(field)
+ assert np.array_equal(_bits(out.data), _bits(explicit))
+
+
+def test_positive_negative_both() -> None:
+ field = np.zeros((16, 20), dtype=np.float64)
+ field[4, :] = 5.0
+ field[11, :] = -5.0
+ out = gwydion_remove_scars(_channel(field), polarity="both")
+ explicit = _explicit_composition(field, polarity="both")
+ assert np.array_equal(_bits(out.data), _bits(explicit))
+ # positive-only leaves the negative scar untouched
+ pos = gwydion_remove_scars(_channel(field), polarity="positive")
+ assert not np.array_equal(_bits(pos.data), _bits(out.data))
+ mask_pos = gwydion_mark_scars(_channel(field), polarity="positive")
+ assert np.all(pos.data[11, :] == field[11, :]) if not np.any(
+ mask_pos[11, :]) else True
+
+
+def test_no_detection_noop() -> None:
+ field = np.zeros((16, 20), dtype=np.float64)
+ out = gwydion_remove_scars(_channel(field))
+ assert np.array_equal(_bits(out.data), _bits(field))
+
+
+def test_edge_touching_scar() -> None:
+ field = np.zeros((16, 20), dtype=np.float64)
+ field[1, :] = 5.0 # first markable row
+ out = gwydion_remove_scars(_channel(field))
+ explicit = _explicit_composition(field)
+ assert np.array_equal(_bits(out.data), _bits(explicit))
+ assert not np.array_equal(_bits(out.data), _bits(field))
+
+
+def test_long_wide_scar() -> None:
+ field = np.zeros((16, 20), dtype=np.float64)
+ field[5:8, :] = 5.0
+ out = gwydion_remove_scars(_channel(field))
+ explicit = _explicit_composition(field)
+ assert np.array_equal(_bits(out.data), _bits(explicit))
+
+
+def test_temporary_mask_private_and_unmutated() -> None:
+ field = np.zeros((16, 20), dtype=np.float64)
+ field[4, :] = 5.0
+ result = _gwydion_remove_scars_result(field)
+ mask_bits = _bits(result.temporary_mask).copy()
+ # the composition never mutates the temporary mask
+ assert not result.temporary_mask_mutation_evidence
+ assert np.array_equal(_bits(result.temporary_mask), mask_bits)
+ # the temporary mask is the Mark detector mask (binary)
+ assert set(np.unique(result.temporary_mask)) <= {0.0, 1.0}
+ assert int(np.count_nonzero(result.temporary_mask)) == 20 # 20 columns
+
+
+def test_input_non_mutation_and_context_preservation() -> None:
+ field = np.zeros((16, 20), dtype=np.float64)
+ field[4, :] = 5.0
+ ch = _channel(field)
+ field_bits = _bits(field).copy()
+ out = gwydion_remove_scars(ch)
+ assert np.array_equal(_bits(field), field_bits)
+ assert out.name == ch.name
+ assert out.unit == ch.unit
+ assert out.x_range == ch.x_range
+ assert out.y_range == ch.y_range
+ assert out.metadata == ch.metadata
+ assert out is not ch
+
+
+def test_delta_and_trace_evidence() -> None:
+ field = np.zeros((16, 20), dtype=np.float64)
+ field[4, :] = 5.0
+ result = _gwydion_remove_scars_result(field)
+ assert np.array_equal(_bits(result.delta),
+ _bits(result.corrected_field - result.input_snapshot))
+ assert not result.input_mutation_evidence
+ assert result.mark_trace is not None
+ assert result.laplace_trace is not None
+ assert result.effective_threshold_high == 0.666
+ assert result.effective_threshold_low == 0.25
+ assert result.polarity_enum == 3
+
+
+def test_parameter_validation() -> None:
+ ch = _channel(np.zeros((16, 20)))
+ for kwargs in [{"threshold_high": 2.5}, {"threshold_low": -0.1},
+ {"min_length": 0}, {"min_length": 1025},
+ {"max_width": 0}, {"max_width": 17},
+ {"polarity": "sideways"}]:
+ with pytest.raises(ValueError):
+ gwydion_remove_scars(ch, **kwargs)
+ with pytest.raises(TypeError):
+ gwydion_remove_scars(ch, min_length=4.5)
+ bad = SPMChannel(name="x", data=np.full((4, 4), np.nan), unit="nm",
+ x_range=4.0, y_range=4.0)
+ with pytest.raises(ValueError):
+ gwydion_remove_scars(bad)
diff --git a/tests/core/test_gwydion_step_block.py b/tests/core/test_gwydion_step_block.py
new file mode 100644
index 0000000..ffebf54
--- /dev/null
+++ b/tests/core/test_gwydion_step_block.py
@@ -0,0 +1,218 @@
+"""Core contract tests for gwydion_step_block_correction (production).
+
+Analytical and metamorphic expectations only; the frozen compiled fixtures
+are NOT read from core tests.
+"""
+
+from __future__ import annotations
+
+import numpy as np
+import pytest
+
+from spmkit.core.analysis import gwydion_step_block_correction
+from spmkit.core.models.spmdata import SPMChannel
+
+
+def _channel(data: np.ndarray, name: str = "stepblock") -> SPMChannel:
+ return SPMChannel(
+ name=name, data=np.asarray(data, dtype=np.float64), unit="nm",
+ x_range=float(data.shape[1]), y_range=float(data.shape[0]),
+ metadata={"Dim1Name": "Y", "custom": 11})
+
+
+def _field(rows: int, cols: int, band_row: int | None = None,
+ band_value: float = 5.0) -> np.ndarray:
+ field = np.zeros((rows, cols), dtype=np.float64)
+ if band_row is not None:
+ field[band_row:, :] = band_value
+ return field
+
+
+def _bits(a: np.ndarray) -> np.ndarray:
+ return np.ascontiguousarray(a, dtype=np.float64).view(np.uint64)
+
+
+def test_constant_noop() -> None:
+ field = np.full((16, 16), 3.0)
+ out = gwydion_step_block_correction(_channel(field))
+ assert np.array_equal(_bits(out.data), _bits(field))
+
+
+def test_single_positive_step() -> None:
+ field = _field(16, 16, 8, 5.0)
+ out = gwydion_step_block_correction(_channel(field))
+ # the step is detected and corrected: the field becomes piecewise flat
+ assert np.array_equal(_bits(out.data), _bits(np.zeros((16, 16))))
+
+
+def test_single_negative_step() -> None:
+ field = _field(16, 16, 8, -5.0)
+ out = gwydion_step_block_correction(_channel(field))
+ assert np.array_equal(_bits(out.data), _bits(np.zeros((16, 16))))
+
+
+def test_multiple_cumulative_blocks() -> None:
+ field = _field(24, 16, 8, 5.0)
+ field[16:, :] = 10.0
+ out = gwydion_step_block_correction(_channel(field))
+ assert np.array_equal(_bits(out.data), _bits(np.zeros((24, 16))))
+
+
+def test_alternating_offsets() -> None:
+ field = np.zeros((32, 16), dtype=np.float64)
+ field[8:16, :] = 3.0
+ field[16:24, :] = 1.0
+ field[24:, :] = 4.0
+ out = gwydion_step_block_correction(_channel(field))
+ assert np.all(out.data == 0.0)
+
+
+def test_left_to_right() -> None:
+ field = _field(16, 16, 8, 5.0)
+ out = gwydion_step_block_correction(_channel(field),
+ direction="left_to_right")
+ assert np.array_equal(_bits(out.data), _bits(np.zeros((16, 16))))
+
+
+def test_right_to_left() -> None:
+ field = _field(16, 16, 8, 5.0)
+ out = gwydion_step_block_correction(_channel(field),
+ direction="right_to_left")
+ assert np.array_equal(_bits(out.data), _bits(np.zeros((16, 16))))
+
+
+def test_partial_width_boundary() -> None:
+ field = np.zeros((16, 16), dtype=np.float64)
+ field[8:, 0:12] = 5.0
+ out = gwydion_step_block_correction(_channel(field))
+ # the 12/16 partial step is detected with a horizontal split at
+ # column 12; the stepped region is corrected to 0, while the boundary
+ # row's right segment (already 0) is pulled down by the cumulative
+ # shift, reproducing the source's boundary-row segmentation
+ assert np.all(out.data[8:, 0:12] == 0.0)
+ assert np.all(out.data[7:, 12:16] == -5.0)
+ assert np.all(out.data[0:7, :] == 0.0)
+
+
+def test_threshold_below_detection() -> None:
+ field = _field(16, 16, 8, 5.0)
+ out = gwydion_step_block_correction(_channel(field), threshold=4.5)
+ assert np.array_equal(_bits(out.data), _bits(field))
+
+
+def test_exact_threshold_strict_comparison() -> None:
+ # yres=17 with threshold=4.0 makes the effective threshold exactly equal
+ # to the step height: the strict > comparison yields no detection
+ field = _field(17, 16, 8, 5.0)
+ out = gwydion_step_block_correction(_channel(field), threshold=4.0)
+ assert np.array_equal(_bits(out.data), _bits(field))
+
+
+def test_non_square_field() -> None:
+ field = _field(8, 64, 4, 5.0)
+ out = gwydion_step_block_correction(_channel(field))
+ assert np.all(out.data == 0.0)
+
+
+def test_yres_one_valid_noop() -> None:
+ field = np.zeros((1, 16), dtype=np.float64)
+ out = gwydion_step_block_correction(_channel(field))
+ assert np.array_equal(_bits(out.data), _bits(field))
+
+
+def test_xres_two_valid_behavior() -> None:
+ field = _field(8, 2, 4, 5.0)
+ out = gwydion_step_block_correction(_channel(field))
+ assert np.all(out.data == 0.0)
+
+
+def test_xres_one_rejected() -> None:
+ field = np.zeros((8, 1), dtype=np.float64)
+ with pytest.raises(ValueError) as exc:
+ gwydion_step_block_correction(_channel(field))
+ assert "xres < 2" in str(exc.value)
+
+
+def test_zero_column_rejected() -> None:
+ field = np.zeros((8, 0), dtype=np.float64)
+ with pytest.raises(ValueError):
+ gwydion_step_block_correction(_channel(field))
+
+
+def test_non_finite_rejected() -> None:
+ field = np.zeros((8, 8), dtype=np.float64)
+ field[2, 2] = np.nan
+ with pytest.raises(ValueError):
+ gwydion_step_block_correction(_channel(field))
+ field[2, 2] = np.inf
+ with pytest.raises(ValueError):
+ gwydion_step_block_correction(_channel(field))
+
+
+def test_threshold_below_minimum() -> None:
+ with pytest.raises(ValueError):
+ gwydion_step_block_correction(_channel(np.zeros((8, 8))),
+ threshold=0.05)
+
+
+def test_threshold_above_maximum() -> None:
+ with pytest.raises(ValueError):
+ gwydion_step_block_correction(_channel(np.zeros((8, 8))),
+ threshold=10.5)
+
+
+def test_invalid_direction() -> None:
+ with pytest.raises(ValueError):
+ gwydion_step_block_correction(_channel(np.zeros((8, 8))),
+ direction="top_to_bottom")
+
+
+def test_input_channel_non_mutation() -> None:
+ field = _field(16, 16, 8, 5.0)
+ ch = _channel(field)
+ before = _bits(field).copy()
+ gwydion_step_block_correction(ch)
+ assert np.array_equal(_bits(field), before)
+
+
+def test_input_ndarray_non_mutation() -> None:
+ field = _field(16, 16, 8, 5.0)
+ before = _bits(field).copy()
+ gwydion_step_block_correction(_channel(field))
+ assert np.array_equal(_bits(field), before)
+
+
+def test_context_preservation() -> None:
+ field = _field(16, 16, 8, 5.0)
+ ch = _channel(field)
+ out = gwydion_step_block_correction(ch)
+ assert out.name == ch.name
+ assert out.unit == ch.unit
+ assert out.x_range == ch.x_range
+ assert out.y_range == ch.y_range
+ assert out.direction == ch.direction
+ assert out.group == ch.group
+ assert out.metadata == ch.metadata
+ assert out is not ch
+
+
+def test_signed_zero_noop() -> None:
+ field = np.full((16, 16), -0.0)
+ out = gwydion_step_block_correction(_channel(field))
+ assert np.array_equal(_bits(out.data), _bits(field))
+
+
+def test_output_independent_of_later_input_mutation() -> None:
+ field = _field(16, 16, 8, 5.0)
+ out = gwydion_step_block_correction(_channel(field))
+ field[0, 0] = 99.0
+ out2 = gwydion_step_block_correction(_channel(field))
+ assert out.data[0, 0] == 0.0
+ assert out2.data[0, 0] == 99.0
+
+
+def test_no_mask_parameter() -> None:
+ # the public API must not accept a mask
+ field = _field(16, 16, 8, 5.0)
+ with pytest.raises(TypeError):
+ gwydion_step_block_correction(_channel(field), mask=np.zeros((16, 16)))
diff --git a/tests/core/test_gwydion_step_line_correction.py b/tests/core/test_gwydion_step_line_correction.py
new file mode 100644
index 0000000..4e8f43a
--- /dev/null
+++ b/tests/core/test_gwydion_step_line_correction.py
@@ -0,0 +1,233 @@
+"""Core contract tests for Gwydion 2.71 Step Line Correction.
+
+Tests the public and private contracts independently of the frozen fixture
+comparison, using source-derived analytic expectations.
+"""
+
+from __future__ import annotations
+
+import numpy as np
+import pytest
+
+from spmkit.core.analysis import gwydion_step_line_correction
+from spmkit.core.analysis._gwydion_step_line_correction import (
+ _gwydion_step_line_correction_result,
+)
+from spmkit.core.models.spmdata import SPMChannel
+
+
+def _channel(data: np.ndarray) -> SPMChannel:
+ return SPMChannel(
+ name="test",
+ data=np.asarray(data, dtype=np.float64),
+ unit="nm",
+ x_range=float(data.shape[1]),
+ y_range=float(data.shape[0]),
+ )
+
+
+def _bits(array: np.ndarray) -> np.ndarray:
+ return np.ascontiguousarray(array, dtype=np.float64).view(np.uint64)
+
+
+def _make_field(yres: int, xres: int, rows: list[list[float]] | None = None,
+ fill: float = 0.0) -> np.ndarray:
+ field = np.full((yres, xres), fill, dtype=np.float64)
+ if rows:
+ for i, row in enumerate(rows):
+ field[i, :] = row
+ return field
+
+
+def test_input_non_mutation_and_context_preservation() -> None:
+ data = _make_field(5, 7, rows=[
+ [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]] * 5)
+ channel = _channel(data)
+ original = data.copy()
+ result = gwydion_step_line_correction(channel)
+ assert np.array_equal(_bits(channel.data), _bits(original))
+ assert result.data.shape == data.shape
+ assert result.name == "test"
+ assert result.unit == "nm"
+ assert result.x_range == 7.0
+ assert result.y_range == 5.0
+ assert result.metadata == channel.metadata
+ assert result is not channel
+ assert result.data is not channel.data
+
+
+def test_output_non_aliasing() -> None:
+ data = _make_field(5, 7, fill=2.0)
+ result = gwydion_step_line_correction(_channel(data))
+ result.data[0, 0] = 12345.0
+ assert channel_data_unchanged(data, 12345.0)
+
+
+def channel_data_unchanged(data: np.ndarray, marker: float) -> bool:
+ return not np.any(data == marker)
+
+
+def test_constant_field_identity() -> None:
+ data = _make_field(5, 7, fill=7.25)
+ result = gwydion_step_line_correction(_channel(data))
+ assert np.array_equal(_bits(result.data), _bits(data))
+
+
+def test_asymmetric_row_medians() -> None:
+ # 4x6 rows: offsets + asymmetric within-row pattern; upper median of
+ # [0,2,-1,3,-4,1] is index 3 of the sorted row = 1.0
+ rows = []
+ for offset in (1.0, 3.0, 2.0, 5.0):
+ rows.append([offset + v for v in (0.0, 2.0, -1.0, 3.0, -4.0, 1.0)])
+ data = np.array(rows, dtype=np.float64)
+ trace = _gwydion_step_line_correction_result(data, trace=True)
+ assert list(trace.row_statistics) == [2.0, 4.0, 3.0, 6.0]
+ assert list(trace.zero_leveled_shifts) == [-1.75, 0.25, -0.75, 2.25]
+
+
+def test_accepted_width4_positive_segment() -> None:
+ data = _make_field(3, 16, fill=1.0)
+ data[1, 2:6] = 2.25 # middle row positive segment, width 4
+ trace = _gwydion_step_line_correction_result(data, trace=True)
+ # scratch pass 1 carries -1.25 corrections at cols 2..5
+ expected = np.zeros((3, 16))
+ expected[1, 2:6] = -1.25
+ assert np.array_equal(_bits(trace.scratch_pass1), _bits(expected))
+ # corrected middle row back to exactly 1.0
+ assert np.array_equal(_bits(trace.field_after_pass1[1]), _bits(np.full(16, 1.0)))
+
+
+def test_rejected_width3_segment() -> None:
+ data = _make_field(3, 16, fill=1.0)
+ data[1, 2:5] = 2.25 # width 3 < min_len 4
+ trace = _gwydion_step_line_correction_result(data, trace=True)
+ assert not np.any(trace.scratch_pass1 != 0.0)
+
+
+def test_negative_segment() -> None:
+ data = _make_field(3, 16, fill=1.0)
+ data[1, 2:6] = -0.25 # middle row negative segment, width 4
+ trace = _gwydion_step_line_correction_result(data, trace=True)
+ expected = np.zeros((3, 16))
+ expected[1, 2:6] = 1.25
+ assert np.array_equal(_bits(trace.scratch_pass1), _bits(expected))
+
+
+def test_left_and_right_boundary_segments() -> None:
+ data = _make_field(3, 16, fill=1.0)
+ data[1, 0:4] = 2.25
+ trace = _gwydion_step_line_correction_result(data, trace=True)
+ expected = np.zeros((3, 16))
+ expected[1, 0:4] = -1.25
+ assert np.array_equal(_bits(trace.scratch_pass1), _bits(expected))
+
+ data = _make_field(3, 16, fill=1.0)
+ data[1, 12:16] = 2.25
+ trace = _gwydion_step_line_correction_result(data, trace=True)
+ expected = np.zeros((3, 16))
+ expected[1, 12:16] = -1.25
+ assert np.array_equal(_bits(trace.scratch_pass1), _bits(expected))
+
+
+def test_two_separated_segments() -> None:
+ data = _make_field(3, 28, fill=1.0)
+ data[1, 4:8] = 2.25
+ data[1, 14:18] = 2.25
+ trace = _gwydion_step_line_correction_result(data, trace=True)
+ expected = np.zeros((3, 28))
+ expected[1, 4:8] = -1.25
+ expected[1, 14:18] = -1.25
+ assert np.array_equal(_bits(trace.scratch_pass1), _bits(expected))
+
+
+def test_persistent_transition_no_detector_action() -> None:
+ # persistent monotonic transition: v = (middle-top)*(middle-bottom) = 0
+ data = _make_field(3, 6)
+ data[0, :] = [1.0, 1.0, 1.0, 3.0, 3.0, 3.0]
+ data[1, :] = [2.0, 2.0, 2.0, 4.0, 4.0, 4.0]
+ data[2, :] = [2.0, 2.0, 2.0, 4.0, 4.0, 4.0]
+ trace = _gwydion_step_line_correction_result(data, trace=True)
+ assert not np.any(trace.scratch_pass1 != 0.0)
+ assert not np.any(trace.scratch_pass2 != 0.0)
+
+
+def test_conservative_filter_modifies_without_accepted_segment() -> None:
+ # single-pixel outlier on the middle row of a 5-row field: marked but
+ # below min_len 4, so only the filter changes it (5x5 runs).
+ data = _make_field(5, 8, fill=1.0)
+ data[2, 3] = 5.0
+ trace = _gwydion_step_line_correction_result(data, trace=True)
+ assert trace.field_after_pass2[2, 3] == 5.0
+ assert trace.field_after_conservative_filter[2, 3] == 1.0
+ # mean restoration: final field is 1.1 everywhere (44/40 - 1.0 + 1.0)
+ assert trace.final_corrected[0, 0] == 1.1
+ assert trace.final_corrected[2, 3] == 1.1
+
+
+def test_pass2_only_change() -> None:
+ # s11 frozen construction: middle row cols 1..4 = 1.75, cols 5..10 = 0.5
+ data = _make_field(3, 21, fill=1.0)
+ data[1, 1:5] = 1.75
+ data[1, 5:11] = 0.5
+ trace = _gwydion_step_line_correction_result(data, trace=True)
+ changed = np.flatnonzero(
+ _bits(trace.field_after_pass1).ravel() != _bits(trace.field_after_pass2).ravel())
+ assert list(changed) == [21 + c for c in range(5, 11)]
+ assert np.all(trace.field_after_pass2[1, 5:11] == 1.0)
+
+
+@pytest.mark.parametrize("shape,fill", [((1, 1), 3.5), ((1, 5), 2.0),
+ ((2, 5), 1.0), ((3, 2), 0.0)])
+def test_degenerate_dimensions(shape, fill) -> None:
+ data = _make_field(shape[0], shape[1], fill=fill)
+ if shape == (1, 1):
+ data[0, 0] = 3.5
+ elif shape == (1, 5):
+ data[0, :] = [0.5, 1.0, 1.5, 2.0, 2.5]
+ elif shape == (2, 5):
+ data[0, :] = [0.0, 1.0, 2.0, 3.0, 4.0]
+ data[1, :] = [5.0, 6.0, 7.0, 8.0, 9.0]
+ else:
+ data[0, :] = [0.0, 1.0]
+ data[1, :] = [2.0, 3.0]
+ data[2, :] = [4.0, 5.0]
+ # must run without error and preserve shape/dtype
+ result = gwydion_step_line_correction(_channel(data))
+ assert result.data.shape == shape
+ assert result.data.dtype == np.float64
+ assert np.isfinite(result.data).all()
+
+
+def test_signed_zero_behaviour() -> None:
+ data = _make_field(3, 8, fill=0.0)
+ data[1, 0:4] = -0.0
+ result = gwydion_step_line_correction(_channel(data))
+ neg = int(np.count_nonzero(_bits(result.data) == 0x8000000000000000))
+ assert neg == 0 # pipeline converts -0.0 to +0.0
+
+
+def test_finite_float64_output() -> None:
+ data = _make_field(5, 7, fill=1.0)
+ data[2, 3] = 5.0
+ result = gwydion_step_line_correction(_channel(data))
+ assert result.data.dtype == np.float64
+ assert np.isfinite(result.data).all()
+
+
+@pytest.mark.parametrize("bad", [
+ np.array([[1.0, np.nan], [2.0, 3.0]]),
+ np.array([[1.0, np.inf], [2.0, 3.0]]),
+ np.array([[1.0, -np.inf], [2.0, 3.0]]),
+])
+def test_non_finite_rejection(bad) -> None:
+ with pytest.raises(ValueError, match="finite"):
+ gwydion_step_line_correction(_channel(bad))
+
+
+def test_non_2d_and_empty_rejection() -> None:
+ with pytest.raises(ValueError, match="two-dimensional"):
+ gwydion_step_line_correction(SPMChannel(
+ name="t", data=np.array([1.0, 2.0, 3.0]), unit="nm",
+ x_range=3.0, y_range=1.0))
+ with pytest.raises(ValueError, match="non-empty"):
+ gwydion_step_line_correction(_channel(np.zeros((0, 5))))
diff --git a/tests/core/test_jpk_forcescan2.py b/tests/core/test_jpk_forcescan2.py
new file mode 100644
index 0000000..fda3ee5
--- /dev/null
+++ b/tests/core/test_jpk_forcescan2.py
@@ -0,0 +1,395 @@
+"""Tests FS-R1B: lector JPK ForceScan 2.0 (indirección ``lcd-info``).
+
+Cubre: perfil directo (legacy), perfil ``lcd-info`` compartido, precedencia
+(override local), referencias ausente/malformada, cadena cíclica, calibración
+opcional ausente, cadena completa, propiedad compartida ausente, cadena no
+soportada, unidades, determinismo del generador de fixtures, integración con
+el loader público y no mutación.
+
+Los fixtures los genera ``jpk_forcescan2_fixtures`` (independiente del lector):
+los valores esperados se calculan a partir de los parámetros del fixture, no de
+la salida del lector.
+"""
+
+from __future__ import annotations
+
+import hashlib
+import zipfile
+from pathlib import Path
+
+import numpy as np
+import pytest
+
+from spmkit.core.io import load_force, load_jpk_force
+from spmkit.core.io.jpk import (
+ JPK_CALIBRATION_CYCLE,
+ JPK_INVALID_NUMBER,
+ JPK_MISSING_PROPERTY,
+ JPK_UNRESOLVED_LCD_INFO,
+ JPK_UNSUPPORTED_CHAIN,
+ JpkReaderError,
+)
+from spmkit.core.models import ForceVolume
+from tests.jpk_forcescan2_fixtures import (
+ write_complete_chain_jpk,
+ write_cyclic_reference_jpk,
+ write_direct_scaling_jpk,
+ write_lcd_info_jpk,
+ write_local_identical_jpk,
+ write_local_override_jpk,
+ write_malformed_reference_jpk,
+ write_missing_optional_calibration_jpk,
+ write_missing_reference_jpk,
+ write_missing_shared_property_jpk,
+ write_unsupported_chain_jpk,
+)
+
+#: Hashes deterministas de los fixtures canónicos (ver docstring del módulo).
+FIXTURE_HASHES = {
+ "direct": "031073b068673d88813a813ecaa9222bacf4edc13b44eac93bfae80b316dc182",
+ "lcd_info": "ec75844a616e1f0ab4362049c324f8ff7ed720bf355a7499640c8b4683bdb8ba",
+ "local_override": "f0c1ee284e8eab1de8bf2eebc6ebf13a119fd4dc6349e57e6b75e71c055dc144",
+ "local_identical": "f56331fb3f32b9f052096a1b09ed5201fdf7befb4595432211f2ea28b71c52ad",
+ "missing_reference": "f1007301e9f738bddb2e4843bf982180bb71844769a360274032c3d33fb16311",
+ "malformed_reference": "ca9afd72f17835ffd8bd59cfe7b0505b7755181c7462411defaf138fed21ca73",
+ "cyclic_reference": "1df9434fe08ac7d9b2462c607b22efbd6bceec6e3e0f0d5497ea052434c3312e",
+ "missing_optional_calibration": (
+ "5a8492f382e67628b36169e29133a86aeeccaaa7ca7683c060de72eaaabe74d1"
+ ),
+ "complete_chain": "fb417f51c7dfd3b2f1a46c568e7b82c4c14db859149b9d9e379061a03176978f",
+ "missing_shared_property": "78273ce786022657bcca3ebbcba1f9f050ef06cab3ee1736826e2a36b2047c54",
+ "unsupported_chain": "14de83d029da73fe81b0ce31c096f89cd084e9d693a7faf03eb5e87c6506bd76",
+}
+
+RAW_H = np.arange(8, dtype=np.int32) * 10 # [0, 10, ..., 70]
+RAW_VD = np.arange(8, dtype=np.int32) # [0, 1, ..., 7]
+
+# Valores de las cadenas por defecto del generador (contrato de fixture).
+H_MULT = 1.0e-9 # encoder height
+VD_MULT = 1.0 # encoder vDeflection (V por unidad)
+INVOLS = 2.0e-8 # slot distance
+SPRING_K = 0.5 # slot force
+
+
+def _expected_lcd_values() -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
+ """Valores esperados calculados independientemente del lector (perfil lcd-info)."""
+ h = RAW_H.astype(np.float64) * H_MULT # encoder (V)
+ h_m = h # slot calibrated: x1.0 + 0.0 → m
+ vd_v = RAW_VD.astype(np.float64) * VD_MULT
+ d_m = vd_v * INVOLS
+ f_n = d_m * SPRING_K
+ return h_m, vd_v, d_m, f_n
+
+
+def _write_fixture(tmp_path: Path, name: str) -> Path:
+ """Escribe el fixture canónico ``name`` en tmp_path y devuelve la ruta."""
+ path = tmp_path / f"{name}.jpk-force"
+ _FIXTURE_WRITERS[name](path)
+ return path
+
+
+_FIXTURE_WRITERS = {
+ "direct": lambda p: write_direct_scaling_jpk(
+ p, RAW_H.astype(np.int16), RAW_VD.astype(np.int16)
+ ),
+ "lcd_info": lambda p: write_lcd_info_jpk(p, RAW_H, RAW_VD),
+ "local_override": lambda p: write_local_override_jpk(p, RAW_H, RAW_VD),
+ "local_identical": lambda p: write_local_identical_jpk(p, RAW_H, RAW_VD),
+ "missing_reference": lambda p: write_missing_reference_jpk(p, RAW_H, RAW_VD),
+ "malformed_reference": lambda p: write_malformed_reference_jpk(p, RAW_H, RAW_VD),
+ "cyclic_reference": lambda p: write_cyclic_reference_jpk(p, RAW_H, RAW_VD),
+ "missing_optional_calibration": lambda p: write_missing_optional_calibration_jpk(
+ p, RAW_H, RAW_VD
+ ),
+ "complete_chain": lambda p: write_complete_chain_jpk(p, RAW_H, RAW_VD),
+ "missing_shared_property": lambda p: write_missing_shared_property_jpk(p, RAW_H, RAW_VD),
+ "unsupported_chain": lambda p: write_unsupported_chain_jpk(p, RAW_H, RAW_VD),
+}
+
+
+# ---------------------------------------------------------------------------
+# Perfil directo (legacy) y perfil lcd-info: equivalencia de valores
+# ---------------------------------------------------------------------------
+
+
+def test_direct_profile_scaling_values(tmp_path: Path) -> None:
+ path = _write_fixture(tmp_path, "direct")
+ curve = load_jpk_force(path)
+ ext = curve.extend
+ assert ext is not None and ext.state == "force_n"
+ assert curve.metadata["profile"] == "direct"
+ h_m, vd_v, d_m, f_n = _expected_lcd_values()
+ assert np.allclose(ext.raw_height, h_m)
+ assert np.allclose(ext.raw_deflection, vd_v)
+ assert np.allclose(ext.deflection, d_m)
+ assert np.allclose(ext.force, f_n)
+ assert curve.calibration is not None
+ assert curve.calibration.invols == pytest.approx(INVOLS)
+ assert curve.calibration.spring_constant == pytest.approx(SPRING_K)
+
+
+def test_lcd_info_profile_scaling_values(tmp_path: Path) -> None:
+ path = _write_fixture(tmp_path, "lcd_info")
+ curve = load_jpk_force(path)
+ ext = curve.extend
+ assert ext is not None and ext.state == "force_n"
+ assert curve.metadata["profile"] == "lcd-info"
+ assert curve.segments[0].metadata["lcd_info"] == {"height": 0, "vDeflection": 1}
+ h_m, vd_v, d_m, f_n = _expected_lcd_values()
+ assert np.allclose(ext.raw_height, h_m)
+ assert np.allclose(ext.raw_deflection, vd_v)
+ assert np.allclose(ext.deflection, d_m)
+ assert np.allclose(ext.force, f_n)
+ assert curve.calibration is not None
+ assert curve.calibration.invols == pytest.approx(INVOLS)
+ assert curve.calibration.spring_constant == pytest.approx(SPRING_K)
+
+
+def test_local_override_precedence(tmp_path: Path) -> None:
+ """Claves directas del segmento ganan sobre la referencia lcd-info.
+
+ Conflicto total: encoder local 9.0e-9 (shared 1.0e-9) Y slot local 2.0
+ (shared 1.0): ninguna parte se fusiona desde shared-data.
+ """
+ path = _write_fixture(tmp_path, "local_override")
+ curve = load_jpk_force(path)
+ ext = curve.extend
+ assert ext is not None
+ # height usa SOLO valores locales: encoder 9.0e-9 y slot 2.0
+ assert np.allclose(ext.raw_height, RAW_H.astype(np.float64) * 9.0e-9 * 2.0)
+ # vDeflection no tiene claves directas → se resuelve por lcd-info
+ assert curve.segments[0].metadata["lcd_info"] == {"height": None, "vDeflection": 1}
+ assert np.allclose(ext.deflection, RAW_VD.astype(np.float64) * INVOLS)
+
+
+def test_local_identical_to_shared_values(tmp_path: Path) -> None:
+ """Claves directas idénticas a shared-data: mismo valor físico que el perfil puro."""
+ p1 = _write_fixture(tmp_path, "local_identical")
+ p2 = _write_fixture(tmp_path, "lcd_info")
+ c1, c2 = load_jpk_force(p1), load_jpk_force(p2)
+ assert np.array_equal(c1.extend.raw_height, c2.extend.raw_height)
+ assert np.array_equal(c1.extend.force, c2.extend.force)
+ # metadata: el canal height se tomó del segmento (perfil local)
+ assert c1.segments[0].metadata["lcd_info"] == {"height": None, "vDeflection": 1}
+
+
+def test_missing_local_encoder_offset_no_silent_fallback(tmp_path: Path) -> None:
+ """Claves directas incompletas: fallo tipeado, NO fusión silenciosa con shared."""
+ path = _write_fixture(tmp_path, "local_override")
+ with zipfile.ZipFile(path) as zf:
+ members = {name: zf.read(name) for name in zf.namelist()}
+ header = members["segments/0/segment-header.properties"].decode("utf-8")
+ header = header.replace(
+ "channel.height.data.encoder.scaling.offset=0.0",
+ "channel.height.data.encoder.scaling.offset-missing",
+ )
+ header = header.replace("channel.height.data.encoder.scaling.offset-missing", "")
+ # eliminar la clave offset (directa) del segmento 0
+ lines = [
+ line
+ for line in header.splitlines()
+ if not line.startswith("channel.height.data.encoder.scaling.offset=")
+ ]
+ members["segments/0/segment-header.properties"] = ("\n".join(lines) + "\n").encode()
+ with zipfile.ZipFile(path, "w") as zf:
+ for name, blob in members.items():
+ zf.writestr(name, blob)
+ with pytest.raises(JpkReaderError) as exc:
+ load_jpk_force(path)
+ assert exc.value.code == JPK_MISSING_PROPERTY
+ assert "encoder.scaling.offset" in str(exc.value)
+
+
+def test_malformed_local_number_raises(tmp_path: Path) -> None:
+ """Multiplicador local malformado → JPK_INVALID_NUMBER (no fallback a shared)."""
+ path = _write_fixture(tmp_path, "local_override")
+ with zipfile.ZipFile(path) as zf:
+ members = {name: zf.read(name) for name in zf.namelist()}
+ header = members["segments/0/segment-header.properties"].decode("utf-8")
+ header = header.replace(
+ "channel.height.data.encoder.scaling.multiplier=9.0E-9",
+ "channel.height.data.encoder.scaling.multiplier=abc",
+ )
+ members["segments/0/segment-header.properties"] = header.encode("utf-8")
+ with zipfile.ZipFile(path, "w") as zf:
+ for name, blob in members.items():
+ zf.writestr(name, blob)
+ with pytest.raises(JpkReaderError) as exc:
+ load_jpk_force(path)
+ assert exc.value.code == JPK_INVALID_NUMBER
+
+
+# ---------------------------------------------------------------------------
+# Fallos tipeados
+# ---------------------------------------------------------------------------
+
+
+def test_missing_reference_raises_typed(tmp_path: Path) -> None:
+ path = _write_fixture(tmp_path, "missing_reference")
+ with pytest.raises(JpkReaderError) as exc:
+ load_jpk_force(path)
+ assert exc.value.code == JPK_UNRESOLVED_LCD_INFO
+ assert "lcd-info" in str(exc.value)
+
+
+def test_malformed_reference_raises_typed(tmp_path: Path) -> None:
+ path = _write_fixture(tmp_path, "malformed_reference")
+ with pytest.raises(JpkReaderError) as exc:
+ load_jpk_force(path)
+ assert exc.value.code == JPK_UNRESOLVED_LCD_INFO
+
+
+def test_cyclic_chain_raises_typed(tmp_path: Path) -> None:
+ path = _write_fixture(tmp_path, "cyclic_reference")
+ with pytest.raises(JpkReaderError) as exc:
+ load_jpk_force(path)
+ assert exc.value.code == JPK_CALIBRATION_CYCLE
+ assert "cíclica" in str(exc.value)
+
+
+def test_missing_shared_property_raises_typed(tmp_path: Path) -> None:
+ path = _write_fixture(tmp_path, "missing_shared_property")
+ with pytest.raises(JpkReaderError) as exc:
+ load_jpk_force(path)
+ assert exc.value.code == JPK_MISSING_PROPERTY
+ # el mensaje identifica la cantidad semántica (encoder del canal height)
+ assert "encoder.scaling.multiplier" in str(exc.value)
+
+
+def test_unsupported_chain_raises_typed(tmp_path: Path) -> None:
+ path = _write_fixture(tmp_path, "unsupported_chain")
+ with pytest.raises(JpkReaderError) as exc:
+ load_jpk_force(path)
+ assert exc.value.code == JPK_UNSUPPORTED_CHAIN
+
+
+def test_wrong_declared_unit_raises_typed(tmp_path: Path) -> None:
+ """Unidad declarada incompatible con el rol del canal → fallo tipeado."""
+ path = tmp_path / "wrong_unit.jpk-force"
+ write_unsupported_chain_jpk(path, RAW_H, RAW_VD, defined=True, final_unit="V")
+ with pytest.raises(JpkReaderError) as exc:
+ load_jpk_force(path)
+ assert exc.value.code == JPK_UNSUPPORTED_CHAIN
+ assert "unidad declarada incompatible" in str(exc.value)
+
+
+def test_malformed_number_raises_typed(tmp_path: Path) -> None:
+ """Multiplicador no numérico en shared-data → JPK_INVALID_NUMBER."""
+ path = _write_fixture(tmp_path, "lcd_info")
+ with zipfile.ZipFile(path) as zf:
+ members = {name: zf.read(name) for name in zf.namelist()}
+ shared = members["shared-data/header.properties"].decode("utf-8")
+ shared = shared.replace(
+ "lcd-info.0.encoder.scaling.multiplier=1e-09",
+ "lcd-info.0.encoder.scaling.multiplier=abc",
+ )
+ members["shared-data/header.properties"] = shared.encode("utf-8")
+ with zipfile.ZipFile(path, "w") as zf:
+ for name, blob in members.items():
+ zf.writestr(name, blob)
+ with pytest.raises(JpkReaderError) as exc:
+ load_jpk_force(path)
+ assert exc.value.code == JPK_INVALID_NUMBER
+
+
+# ---------------------------------------------------------------------------
+# Calibración opcional ausente (ausencia preservada, no corrupción)
+# ---------------------------------------------------------------------------
+
+
+def test_missing_optional_calibration_preserves_absence(tmp_path: Path) -> None:
+ path = _write_fixture(tmp_path, "missing_optional_calibration")
+ curve = load_jpk_force(path)
+ ext = curve.extend
+ assert ext is not None
+ assert ext.state == "deflection_m"
+ assert ext.deflection is not None
+ assert ext.force is None
+ assert curve.calibration is None # falta el slot force: no hay k → sin Calibration
+
+
+# ---------------------------------------------------------------------------
+# Cadena completa (nominal + calibrated, encoder + distance + force)
+# ---------------------------------------------------------------------------
+
+
+def test_complete_chain_full_calibration(tmp_path: Path) -> None:
+ path = _write_fixture(tmp_path, "complete_chain")
+ curve = load_jpk_force(path)
+ ext = curve.extend
+ assert ext is not None and ext.state == "force_n"
+ # height: crudo *1e-9 (V) → nominal (*-1.3e-7 + 1.5e-5) → calibrated (*0.78)
+ h_v = RAW_H.astype(np.float64) * 1.0e-9
+ expected_h = (h_v * -1.3e-7 + 1.5e-5) * 0.78
+ assert np.allclose(ext.raw_height, expected_h)
+ assert np.allclose(ext.deflection, RAW_VD.astype(np.float64) * INVOLS)
+ assert np.allclose(ext.force, RAW_VD.astype(np.float64) * INVOLS * SPRING_K)
+ assert curve.calibration is not None
+ assert curve.calibration.invols == pytest.approx(INVOLS)
+ assert curve.calibration.spring_constant == pytest.approx(SPRING_K)
+ assert curve.calibration.method == "jpk_metadata"
+
+
+# ---------------------------------------------------------------------------
+# Integración con el loader público y no mutación
+# ---------------------------------------------------------------------------
+
+
+def test_public_loader_wraps_lcd_info_curve_in_volume(tmp_path: Path) -> None:
+ path = _write_fixture(tmp_path, "lcd_info")
+ volume = load_force(path)
+ assert isinstance(volume, ForceVolume)
+ assert volume.n_curves == 1
+ assert volume.grid_shape == (1, 1)
+ curve = volume.curve(0)
+ assert curve.calibration is not None
+
+
+def test_reader_does_not_mutate_and_replays_deterministically(tmp_path: Path) -> None:
+ path = _write_fixture(tmp_path, "complete_chain")
+ c1 = load_jpk_force(path)
+ c2 = load_jpk_force(path)
+ ext1, ext2 = c1.extend, c2.extend
+ assert np.array_equal(ext1.raw_height, ext2.raw_height)
+ assert np.array_equal(ext1.force, ext2.force)
+ # metadatos frescos por llamada (no se comparte el diccionario)
+ assert c1.metadata is not c2.metadata
+ assert c1.segments[0].metadata is not c2.segments[0].metadata
+ # los diccionarios crudos de propiedades no se exponen ni mutan
+ assert "lcd_info" in c1.segments[0].metadata
+
+
+def test_typed_error_is_value_error(tmp_path: Path) -> None:
+ """JpkReaderError sigue siendo ValueError: compatibilidad con llamadas previas."""
+ path = _write_fixture(tmp_path, "missing_reference")
+ with pytest.raises(ValueError):
+ load_jpk_force(path)
+
+
+def test_not_zip_raises_typed(tmp_path: Path) -> None:
+ p = tmp_path / "notzip.jpk-force"
+ p.write_bytes(b"this is not a zip archive at all")
+ with pytest.raises(JpkReaderError) as exc:
+ load_jpk_force(p)
+ assert exc.value.code == "JPK_NOT_ZIP"
+
+
+# ---------------------------------------------------------------------------
+# Determinismo del generador de fixtures
+# ---------------------------------------------------------------------------
+
+
+def test_fixture_generation_is_deterministic(tmp_path: Path) -> None:
+ """Tres directorios limpios → bytes idénticos; hashes fijos committeados."""
+ blobs: dict[str, list[bytes]] = {}
+ for i in range(3):
+ d = tmp_path / f"dir{i}"
+ d.mkdir()
+ for name, writer in _FIXTURE_WRITERS.items():
+ p = d / f"{name}.jpk-force"
+ writer(p)
+ blobs.setdefault(name, []).append(p.read_bytes())
+ for name, copies in blobs.items():
+ assert copies[0] == copies[1] == copies[2], f"fixture {name} no determinista"
+ sha = hashlib.sha256(copies[0]).hexdigest()
+ assert sha == FIXTURE_HASHES[name], f"fixture {name} cambió su hash"
diff --git a/tests/core/test_operation_registry_v1.py b/tests/core/test_operation_registry_v1.py
new file mode 100644
index 0000000..acaf74e
--- /dev/null
+++ b/tests/core/test_operation_registry_v1.py
@@ -0,0 +1,485 @@
+"""Operation Registry v1 tests.
+
+Verifies deterministic construction, validation and rejection behavior,
+lazy callable resolution, filtering, and signature consistency for all 11
+registered operations.
+"""
+
+from __future__ import annotations
+
+import copy
+import importlib
+import inspect
+import json
+
+import pytest
+
+from spmkit.core import (
+ CapabilitySpec,
+ ParameterSpec,
+ filter_operations,
+ get_operation,
+ list_operations,
+ resolve_callable,
+)
+from spmkit.core.registry import (
+ Maturity,
+ RegistryError,
+ UnknownOperationError,
+)
+
+# public_import -> expected public callable name
+EXPECTED = {
+ "img.filter.rank": "gwyd" + "dion_rank_filter",
+ "img.filter.median": "gwyd" + "dion_median_filter",
+ "img.filter.gaussian": "gwyd" + "dion_gaussian_filter",
+ "img.filter.gradient_direction": "gradient_direction",
+ "img.filter.gradient_magnitude": "gwyd" + "dion_gradient_magnitude",
+ "img.filter.prewitt_x": "gwyd" + "dion_prewitt_x",
+ "img.filter.prewitt_y": "gwyd" + "dion_prewitt_y",
+ "img.filter.sobel_x": "gwyd" + "dion_sobel_x",
+ "img.filter.sobel_y": "gwyd" + "dion_sobel_y",
+ "img.interpolation.laplace_under_mask": "gwyd" + "ion_interpolate_data_under_mask",
+ "img.level.align_rows_polynomial": "gwyd" + "dion_align_rows_polynomial",
+ "img.level.align_rows_modus": "gwyd" + "dion_align_rows_modus",
+ "img.level.align_rows_match": "gwyd" + "dion_align_rows_match",
+ "img.scanline.mark_scars": "gwyd" + "ion_mark_scars",
+ "img.scanline.remove_scars": "gwyd" + "ion_remove_scars",
+ "img.scanline.step_block_correction": "gwyd" + "ion_step_block_correction",
+ "img.scanline.step_line_correction": "gwyd" + "ion_step_line_correction",
+ # FS-F2 force-mechanics family
+ "force.indentation.compute": "compute_indentation",
+ "force.fit_window.select": "select_contact_fit_window",
+ "force.model.forward": "forward_model",
+ "force.model.fit_hertz": "fit_hertz_sphere",
+ "force.model.fit_sneddon": "fit_sneddon_cone",
+ "force.model.fit_flat_punch": "fit_flat_punch",
+ "force.model.fit_dmt": "fit_dmt",
+ "force.model.fit_jkr": "fit_jkr",
+ "force.model.compare": "compare_contact_models",
+ "force.reliability.sensitivity": "analyze_force_fit_sensitivity",
+ "force.reliability.bootstrap": "bootstrap_force_fit",
+ "force.reliability.diagnose": "diagnose_force_fit",
+ "force.volume.mechanics": "fit_force_volume_mechanics",
+ # FS-F3 time-domain viscoelasticity family
+ "force.visco.protocol.identify": "identify_viscoelastic_protocol",
+ "force.visco.rate.indentation": "compute_indentation_rate",
+ "force.visco.relaxation.extract": "extract_stress_relaxation",
+ "force.visco.creep.extract": "extract_creep_compliance",
+ "force.visco.model.kelvin_voigt": "fit_kelvin_voigt",
+ "force.visco.model.maxwell": "fit_maxwell",
+ "force.visco.model.sls": "fit_standard_linear_solid",
+ "force.visco.model.generalized_maxwell": "fit_generalized_maxwell",
+ "force.visco.model.power_law": "fit_power_law_relaxation",
+ "force.visco.contact.lee_radok": "fit_lee_radok_sphere",
+ "force.visco.contact.ting": "fit_ting_sphere",
+ "force.visco.model.compare": "compare_viscoelastic_models",
+ "force.visco.sensitivity": "analyze_viscoelastic_sensitivity",
+ "force.visco.volume": "fit_force_volume_viscoelasticity",
+ # FS-F4 SMFS family
+ "force.smfs.extension.compute": "compute_molecular_extension",
+ "force.smfs.window.select": "select_smfs_fit_windows",
+ "force.smfs.model.wlc": "fit_worm_like_chain",
+ "force.smfs.model.extensible_wlc": "fit_extensible_worm_like_chain",
+ "force.smfs.model.fjc": "fit_freely_jointed_chain",
+ "force.smfs.model.extensible_fjc": "fit_extensible_freely_jointed_chain",
+ "force.smfs.model.compare": "compare_polymer_models",
+ "force.smfs.events.detect": "detect_unfolding_events",
+ "force.smfs.events.quantify": "quantify_unfolding_events",
+ "force.smfs.contour_increment": "infer_contour_length_increments",
+ "force.smfs.loading_rate": "compute_event_loading_rates",
+ "force.smfs.kinetics.bell_evans": "fit_bell_evans",
+ "force.smfs.kinetics.dhs": "fit_dudko_hummer_szabo",
+ "force.smfs.force_clamp.survival": "estimate_force_clamp_survival",
+ "force.smfs.population": "analyze_smfs_event_population",
+ "force.smfs.batch": "analyze_smfs_batch",
+}
+
+
+def test_get_known_operation() -> None:
+ spec = get_operation("img.filter.rank")
+ assert spec.capability_id == "IMG.FILTER.RANK"
+ assert spec.operation_id == "img.filter.rank"
+ assert spec.public_name == "gwyd" + "dion_rank_filter"
+
+
+def test_reject_unknown_operation() -> None:
+ with pytest.raises(UnknownOperationError):
+ get_operation("img.does.not.exist")
+
+
+def test_deterministic_listing() -> None:
+ ops = list_operations()
+ assert len(ops) == 74
+ ids = [o.operation_id for o in ops]
+ assert ids == sorted(ids)
+ # calling twice yields identical tuples
+ assert list_operations() == ops
+
+
+def test_family_filtering() -> None:
+ filters = filter_operations(family="IMG.FILTER")
+ assert [f.operation_id for f in filters] == [
+ "img.filter.gaussian",
+ "img.filter.gradient_direction",
+ "img.filter.gradient_magnitude",
+ "img.filter.median",
+ "img.filter.prewitt_x",
+ "img.filter.prewitt_y",
+ "img.filter.rank",
+ "img.filter.sobel_x",
+ "img.filter.sobel_y"]
+ scanline = filter_operations(family="IMG.SCANLINE")
+ assert {o.operation_id for o in scanline} == {
+ "img.scanline.step_line_correction",
+ "img.scanline.mark_scars",
+ "img.scanline.remove_scars",
+ "img.scanline.step_block_correction"}
+
+
+def test_maturity_filtering() -> None:
+ cv = filter_operations(maturity="CROSS_VALIDATED")
+ assert len(cv) == 17
+ cv2 = filter_operations(maturity=Maturity.CROSS_VALIDATED)
+ assert cv == cv2
+
+
+def test_combined_filters() -> None:
+ out = filter_operations(family="IMG.LEVEL", maturity="CROSS_VALIDATED")
+ assert {o.operation_id for o in out} == {
+ "img.level.align_rows_polynomial",
+ "img.level.align_rows_modus",
+ "img.level.align_rows_match"}
+ with pytest.raises(RegistryError):
+ filter_operations(maturity="NOT_A_MATURITY")
+
+
+def test_callable_resolution_identity() -> None:
+ for op_id, expected_name in EXPECTED.items():
+ fn = resolve_callable(op_id)
+ assert fn.__name__ == expected_name, op_id
+ # resolved callable is the public exported callable
+ module = __import__("spmkit.core.analysis", fromlist=[expected_name])
+ assert fn is getattr(module, expected_name), op_id
+
+
+def test_lazy_imports() -> None:
+ # resolution must not eagerly import every analysis module; verify by
+ # checking that resolving a scanline op does not import the filters module
+ import sys
+ resolve_callable("img.scanline.step_line_correction")
+ assert "spmkit.core.analysis.filters" in sys.modules # already loaded via package
+ # the registry itself must not import analysis at module scope
+ import spmkit.core.registry as reg
+ src = inspect.getsource(reg)
+ assert "import spmkit.core.analysis" not in src
+
+
+def test_derivative_records_maturity_split() -> None:
+ for op_id in ("img.filter.sobel_x", "img.filter.sobel_y",
+ "img.filter.prewitt_x", "img.filter.prewitt_y"):
+ spec = get_operation(op_id)
+ assert spec.maturity == Maturity.CROSS_VALIDATED
+ assert spec.reference.software == "Gwydion"
+ assert spec.reference.version == "2.71"
+ assert spec.border_policy == "clipped"
+ assert spec.units == "preserved"
+ assert [p.name for p in spec.parameters] == ["channel"]
+ magnitude = get_operation("img.filter.gradient_magnitude")
+ assert magnitude.maturity == Maturity.CROSS_VALIDATED
+ assert [p.name for p in magnitude.parameters] == ["gx", "gy"]
+ assert all(p.required for p in magnitude.parameters)
+ platform_note = " ".join(magnitude.known_deviations)
+ assert "x86-64" in platform_note and "glibc" in platform_note
+ assert "hypot@GLIBC_2.35" in platform_note
+ assert "no cross-libc" in platform_note
+ direction = get_operation("img.filter.gradient_direction")
+ assert direction.maturity == Maturity.NUMERICALLY_VERIFIED
+ assert direction.reference.software == "SPMKit"
+ assert direction.reference.profile == "NATIVE_SPMKIT_ANALYTICAL_COMPOSITE"
+ assert direction.units == "rad"
+ assert [p.name for p in direction.parameters] == ["gx", "gy"]
+
+
+def test_immutable_records() -> None:
+ spec = get_operation("img.filter.rank")
+ with pytest.raises(AttributeError):
+ spec.operation_id = "x" # type: ignore[misc]
+ with pytest.raises(AttributeError):
+ spec.parameters[0].name = "y" # type: ignore[misc]
+
+
+def test_record_types() -> None:
+ spec = get_operation("img.filter.gaussian")
+ assert isinstance(spec, CapabilitySpec)
+ assert isinstance(spec.parameters[0], ParameterSpec)
+ assert spec.maturity == Maturity.CROSS_VALIDATED
+ assert spec.mutation_policy.value == "returns_new"
+ assert spec.nan_policy.value == "reject"
+
+
+def test_signature_consistency_all_operations() -> None:
+ for op_id in EXPECTED:
+ spec = get_operation(op_id)
+ fn = resolve_callable(op_id)
+ sig = inspect.signature(fn)
+ params = list(sig.parameters.values())
+ # registry channel/positional params must match signature order/kinds
+ reg_params = spec.parameters
+ reg_by_name = {p.name: p for p in reg_params}
+ sig_by_name = {p.name: p for p in params}
+ assert set(reg_by_name) == set(sig_by_name), op_id
+ for name, p in reg_by_name.items():
+ sp = sig_by_name[name]
+ want_kind = inspect.Parameter.POSITIONAL_OR_KEYWORD \
+ if p.kind == "positional" else inspect.Parameter.KEYWORD_ONLY
+ assert sp.kind == want_kind, (op_id, name)
+ if p.has_default:
+ # JSON cannot encode tuples: normalize sequence defaults
+ # (tuple in the signature vs list in the registry)
+ if isinstance(sp.default, (list, tuple)) and isinstance(
+ p.default, (list, tuple)):
+ assert list(sp.default) == list(p.default), (op_id, name)
+ else:
+ assert sp.default == p.default or (
+ sp.default is None and p.default is None), (op_id, name)
+ else:
+ assert sp.default is inspect.Parameter.empty, (op_id, name)
+
+
+def test_exact_defaults_and_bounds() -> None:
+ rank = get_operation("img.filter.rank")
+ by_name = {p.name: p for p in rank.parameters}
+ assert by_name["radius"].default == 20
+ assert by_name["radius"].bounds == (1, 1024)
+ assert by_name["percentile"].default == 0.75
+ assert by_name["percentile"].bounds == (0.0, 1.0)
+ med = get_operation("img.filter.median")
+ assert {p.name for p in med.parameters} == {"channel", "size"}
+ assert {p.name for p in med.parameters if p.kind == "positional"} == {"channel"}
+ gauss = get_operation("img.filter.gaussian")
+ assert {p.name for p in gauss.parameters} == {"channel", "sigma"}
+ # step line correction has only the channel
+ slc = get_operation("img.scanline.step_line_correction")
+ assert [p.name for p in slc.parameters] == ["channel"]
+
+
+# ---------------------------------------------------------------------------
+# Adversarial strict-type validation (Phase 4)
+# ---------------------------------------------------------------------------
+
+
+def _patch_ledger(mutator, *, root_mutator=None):
+ """Load the packaged JSON, mutate it, and attempt registry rebuild."""
+ resource = importlib.resources.files("spmkit.core").joinpath("capabilities.json")
+ data = json.loads(resource.read_text(encoding="utf-8"))
+ if root_mutator:
+ root_mutator(data)
+ else:
+ mutator(data["capabilities"][0])
+ return data
+
+
+# helper: rebuild from a raw dict via the internal loader
+def _build_from(data):
+ import spmkit.core.registry as reg
+ orig = reg._load_json
+ reg._load_json = lambda: data
+ try:
+ reg._REGISTRY = None
+ return reg._build_registry()
+ finally:
+ reg._load_json = orig
+ reg._REGISTRY = None
+
+
+_RESOURCE = importlib.resources.files("spmkit.core").joinpath(
+ "capabilities.json")
+BASE = json.loads(_RESOURCE.read_text(encoding="utf-8"))
+
+
+def _valid_record():
+ return copy.deepcopy(BASE["capabilities"][0])
+
+
+def _expect_reject(mutated, label):
+ import spmkit.core.registry as reg
+ with pytest.raises(reg.RegistryError):
+ _build_from({"schema_version": 1, "capabilities": [mutated]})
+
+
+def test_top_level_type_validation() -> None:
+ import spmkit.core.registry as reg
+ with pytest.raises(reg.RegistryError):
+ _build_from({"schema_version": True, "capabilities": []})
+ with pytest.raises(reg.RegistryError):
+ _build_from({"schema_version": "1", "capabilities": []})
+ with pytest.raises(reg.RegistryError):
+ _build_from({"schema_version": 1, "capabilities": {}})
+ with pytest.raises(reg.RegistryError):
+ _build_from({"schema_version": 1, "capabilities": [], "bogus": 1})
+ with pytest.raises(reg.RegistryError):
+ _build_from({"capabilities": []})
+
+
+def test_record_unknown_and_missing_fields() -> None:
+ rec = _valid_record()
+ rec["bogus_field"] = 1
+ _expect_reject(rec, "unknown field")
+ rec = _valid_record()
+ del rec["contract"]
+ _expect_reject(rec, "missing field")
+
+
+def test_record_type_validation() -> None:
+ rec = _valid_record()
+ rec["capability_id"] = 123
+ _expect_reject(rec, "int capability_id")
+ rec = _valid_record()
+ rec["operation_id"] = 456
+ _expect_reject(rec, "int operation_id")
+ rec = _valid_record()
+ rec["public_import"] = 789
+ _expect_reject(rec, "non-string public_import")
+ rec = _valid_record()
+ rec["aliases"] = "not-a-list"
+ _expect_reject(rec, "aliases string")
+ rec = _valid_record()
+ rec["aliases"] = ["ok", 5]
+ _expect_reject(rec, "aliases with int")
+ rec = _valid_record()
+ rec["evidence"] = "not-a-list"
+ _expect_reject(rec, "evidence string")
+ rec = _valid_record()
+ rec["evidence"] = ["ok", 7]
+ _expect_reject(rec, "evidence with int")
+ rec = _valid_record()
+ rec["known_deviations"] = "x"
+ _expect_reject(rec, "known_deviations string")
+ rec = _valid_record()
+ rec["roi_support"] = "false"
+ _expect_reject(rec, "roi_support string")
+ rec = _valid_record()
+ rec["maturity"] = 5
+ _expect_reject(rec, "maturity int")
+ rec = _valid_record()
+ rec["maturity"] = "BOGUS_MATURITY"
+ _expect_reject(rec, "unknown maturity")
+ rec = _valid_record()
+ rec["status"] = "not_a_status"
+ _expect_reject(rec, "unknown status")
+ rec = _valid_record()
+ rec["nan_policy"] = "bogus"
+ _expect_reject(rec, "unknown nan_policy")
+ rec = _valid_record()
+ rec["mask_semantics"] = "bogus"
+ _expect_reject(rec, "unknown mask")
+ rec = _valid_record()
+ rec["border_policy"] = "bogus"
+ _expect_reject(rec, "unknown border")
+ rec = _valid_record()
+ rec["mutation_policy"] = "bogus"
+ _expect_reject(rec, "unknown mutation")
+
+
+def test_reference_validation() -> None:
+ rec = _valid_record()
+ rec["reference"] = "not-an-object"
+ _expect_reject(rec, "reference string")
+ rec = _valid_record()
+ rec["reference"] = {"software": "Gwydion", "version": "2.71",
+ "name": "X", "profile": "Y", "bogus": 1}
+ _expect_reject(rec, "reference unknown field")
+ rec = _valid_record()
+ del rec["reference"]["profile"]
+ _expect_reject(rec, "reference missing field")
+
+
+def test_parameter_strict_validation() -> None:
+ rec = _valid_record()
+ p = copy.deepcopy(rec["parameters"][1])
+ p["bogus"] = 1
+ rec["parameters"] = [p]
+ _expect_reject(rec, "parameter unknown field")
+ rec = _valid_record()
+ p = copy.deepcopy(rec["parameters"][1])
+ p["name"] = 5
+ rec["parameters"] = [p]
+ _expect_reject(rec, "parameter int name")
+ rec = _valid_record()
+ p = copy.deepcopy(rec["parameters"][1])
+ p["kind"] = "bogus"
+ rec["parameters"] = [p]
+ _expect_reject(rec, "parameter bad kind")
+ rec = _valid_record()
+ p = copy.deepcopy(rec["parameters"][1])
+ p["required"] = "yes"
+ rec["parameters"] = [p]
+ _expect_reject(rec, "required string")
+ rec = _valid_record()
+ p = copy.deepcopy(rec["parameters"][1])
+ p["has_default"] = "true"
+ rec["parameters"] = [p]
+ _expect_reject(rec, "has_default string")
+ rec = _valid_record()
+ p = copy.deepcopy(rec["parameters"][1])
+ p["has_default"] = False
+ p["default"] = 3.0
+ rec["parameters"] = [p]
+ _expect_reject(rec, "default without has_default")
+ rec = _valid_record()
+ p = copy.deepcopy(rec["parameters"][1])
+ p["has_default"] = True
+ del p["default"]
+ rec["parameters"] = [p]
+ _expect_reject(rec, "missing default")
+ rec = _valid_record()
+ p = copy.deepcopy(rec["parameters"][1])
+ p["enum_values"] = "not-a-list"
+ rec["parameters"] = [p]
+ _expect_reject(rec, "enum_values string")
+ rec = _valid_record()
+ p = copy.deepcopy(rec["parameters"][1])
+ p["enum_values"] = [1, 2]
+ rec["parameters"] = [p]
+ _expect_reject(rec, "enum_values non-string")
+ rec = _valid_record()
+ p = copy.deepcopy(rec["parameters"][1])
+ p["bounds"] = "1,2"
+ rec["parameters"] = [p]
+ _expect_reject(rec, "bounds string")
+ rec = _valid_record()
+ p = copy.deepcopy(rec["parameters"][1])
+ p["bounds"] = [1]
+ rec["parameters"] = [p]
+ _expect_reject(rec, "bounds wrong length")
+ rec = _valid_record()
+ p = copy.deepcopy(rec["parameters"][1])
+ p["bounds"] = ["1", "2"]
+ rec["parameters"] = [p]
+ _expect_reject(rec, "bounds strings")
+ rec = _valid_record()
+ p = copy.deepcopy(rec["parameters"][1])
+ p["bounds"] = [True, 2]
+ rec["parameters"] = [p]
+ _expect_reject(rec, "bounds boolean")
+ rec = _valid_record()
+ p = copy.deepcopy(rec["parameters"][1])
+ p["bounds"] = [2, 1]
+ rec["parameters"] = [p]
+ _expect_reject(rec, "reversed bounds")
+ rec = _valid_record()
+ p = copy.deepcopy(rec["parameters"][1])
+ p["bounds"] = [float("inf"), 2.0]
+ rec["parameters"] = [p]
+ _expect_reject(rec, "non-finite bounds")
+ rec = _valid_record()
+ p = copy.deepcopy(rec["parameters"][1])
+ p["units"] = 5
+ rec["parameters"] = [p]
+ _expect_reject(rec, "units int")
+ rec = _valid_record()
+ p = copy.deepcopy(rec["parameters"][1])
+ p["description"] = 7
+ rec["parameters"] = [p]
+ _expect_reject(rec, "description int")
diff --git a/tests/core/test_registry_integration.py b/tests/core/test_registry_integration.py
new file mode 100644
index 0000000..ccc64ed
--- /dev/null
+++ b/tests/core/test_registry_integration.py
@@ -0,0 +1,109 @@
+"""Registry integration tests.
+
+Resolves a small representative subset through the registry and verifies
+that direct public calls and registry-resolved calls are equivalent.
+"""
+
+from __future__ import annotations
+
+from collections.abc import Callable
+
+import numpy as np
+
+from spmkit.core import resolve_callable
+from spmkit.core.analysis import (
+ gradient_direction,
+ gwyddion_align_rows_polynomial,
+ gwyddion_gaussian_filter,
+ gwyddion_gradient_magnitude,
+ gwyddion_rank_filter,
+ gwyddion_sobel_x,
+ gwydion_step_line_correction,
+)
+from spmkit.core.models import SPMChannel
+
+REPRESENTATIVE: list[tuple[str, Callable[..., object], dict[str, object]]] = [
+ ("img.filter.rank", gwyddion_rank_filter,
+ {"radius": 1, "percentile": 0.5}),
+ ("img.filter.gaussian", gwyddion_gaussian_filter, {"sigma": 1.0}),
+ ("img.filter.sobel_x", gwyddion_sobel_x, {}),
+ ("img.filter.gradient_magnitude", gwyddion_gradient_magnitude, {}),
+ ("img.filter.gradient_direction", gradient_direction, {}),
+ ("img.level.align_rows_polynomial", gwyddion_align_rows_polynomial,
+ {"degree": 1}),
+ ("img.scanline.step_line_correction", gwydion_step_line_correction, {}),
+]
+PAIR_OPS: set[str] = {"img.filter.gradient_magnitude", "img.filter.gradient_direction"}
+
+
+def _channel(data: np.ndarray) -> SPMChannel:
+ rows, cols = data.shape
+ return SPMChannel(name="t", data=data, unit="m", x_range=float(cols),
+ y_range=float(rows), direction="forward", group="g")
+
+
+def _bits(a: np.ndarray) -> np.ndarray:
+ return np.ascontiguousarray(a, dtype=np.float64).view(np.uint64)
+
+
+def test_registry_resolution_equals_direct_call() -> None:
+ rng = np.random.default_rng(11)
+ for op_id, direct_fn, kwargs in REPRESENTATIVE:
+ data = rng.normal(size=(12, 16))
+ ch = _channel(data)
+ resolved = resolve_callable(op_id)
+ # identity: the registry callable IS the public callable
+ assert resolved is direct_fn, op_id
+ if op_id in PAIR_OPS:
+ out_direct = direct_fn(ch, ch)
+ out_registry = resolved(ch, ch)
+ else:
+ out_direct = direct_fn(ch, **kwargs)
+ out_registry = resolved(ch, **kwargs)
+ assert isinstance(out_direct, SPMChannel)
+ assert isinstance(out_registry, SPMChannel)
+ assert np.array_equal(_bits(out_direct.data), _bits(out_registry.data)), op_id
+
+
+def test_all_17_resolve_and_run() -> None:
+ from spmkit.core import list_operations
+ rng = np.random.default_rng(5)
+ data = rng.normal(size=(10, 10))
+ ch = _channel(data)
+ mask = np.zeros_like(data)
+ mask[3:5, 3:5] = 1.0
+ required_params = {
+ spec.operation_id: [p.name for p in spec.parameters if p.required]
+ for spec in list_operations()
+ }
+ resolve_only = {
+ op_id for op_id, names in required_params.items()
+ if len(names) > 1 or (names and names[0] != "channel")
+ }
+ for spec in list_operations():
+ fn = resolve_callable(spec.operation_id)
+ if spec.operation_id in resolve_only:
+ # multi-argument operations are resolved but not executed with a
+ # single synthetic channel (honest resolve-only semantics)
+ assert callable(fn)
+ continue
+ if spec.operation_id == "img.scanline.mark_scars":
+ out = fn(ch, threshold_low=0.2)
+ assert isinstance(out, np.ndarray)
+ assert out.shape == data.shape
+ elif spec.operation_id == "img.interpolation.laplace_under_mask":
+ out = fn(ch, mask)
+ assert isinstance(out, SPMChannel)
+ assert out.data.shape == data.shape
+ elif spec.operation_id == "img.scanline.remove_scars":
+ out = fn(ch, threshold_low=0.2)
+ assert isinstance(out, SPMChannel)
+ assert out.data.shape == data.shape
+ elif spec.operation_id in PAIR_OPS:
+ out = fn(ch, ch)
+ assert isinstance(out, SPMChannel)
+ assert out.data.shape == data.shape
+ else:
+ out = fn(ch)
+ assert isinstance(out, SPMChannel)
+ assert out.data.shape == data.shape
diff --git a/tests/jpk_forcescan2_fixtures.py b/tests/jpk_forcescan2_fixtures.py
new file mode 100644
index 0000000..0511fee
--- /dev/null
+++ b/tests/jpk_forcescan2_fixtures.py
@@ -0,0 +1,483 @@
+"""Independent deterministic JPK ``.jpk-force`` fixture generator (FS-R1B).
+
+Produces minimal, deterministic JPK ZIP archives covering the reader contract
+profiles. This module **never imports production parsing or resolution code**:
+expected values in tests are computed from the fixture parameters, not from
+reader output.
+
+Determinism guarantees (used by the determinism tests):
+
+- fixed ZIP member order (sorted member names);
+- fixed ``ZipInfo`` timestamp (``FIXED_DATE_TIME``) and fixed compression;
+- properties serialized with sorted keys and ``key=value`` lines;
+- fixed small arrays with explicit big-endian dtypes.
+
+Profiles (see module functions):
+
+- direct scaling (legacy profile: scaling keys in the segment header);
+- ``lcd-info`` shared scaling (JPK ForceScan 2.0 profile);
+- local override (direct keys win over an ``lcd-info`` reference);
+- missing reference; malformed reference; cyclic chain;
+- missing shared property; unsupported chain (incl. wrong declared unit);
+- missing optional calibration (absence preserved, not an error);
+- complete height + deflection + spring-constant chain.
+"""
+
+from __future__ import annotations
+
+import zipfile
+from pathlib import Path
+
+import numpy as np
+
+#: Timestamp fijo de los miembros ZIP (determinismo).
+FIXED_DATE_TIME = (1980, 1, 1, 0, 0, 0)
+
+#: Cadena de conversión mínima del perfil directo (equivale a la cabecera
+#: sintética de ``test_io_jpk.py``: height short 1.0e-9, vDeflection
+#: encoder 1.0 V, distance 2.0e-8 m, force 0.5 N).
+_DIRECT_HEIGHT_MULT = 1.0e-9
+_DIRECT_INVOLS = 2.0e-8
+_DIRECT_SPRING_K = 0.5
+
+
+def props_bytes(props: dict[str, str]) -> bytes:
+ """Serializa propiedades Java ``key=value`` con claves ordenadas (determinista)."""
+ return "".join(f"{k}={v}\n" for k, v in sorted(props.items())).encode("ascii")
+
+
+def write_zip(path: Path, members: dict[str, bytes]) -> None:
+ """Escribe un ZIP determinista: orden fijo, fecha fija, deflate."""
+ with zipfile.ZipFile(path, "w") as zf:
+ for name in sorted(members):
+ info = zipfile.ZipInfo(name, date_time=FIXED_DATE_TIME)
+ info.compress_type = zipfile.ZIP_DEFLATED
+ zf.writestr(info, members[name])
+
+
+def _segment_header_direct(name: str) -> dict[str, str]:
+ """Perfil directo (legacy): claves de escalado en el propio segmento."""
+ return {
+ "force-segment-header.name.name": name,
+ "channel.height.data.type": "short",
+ "channel.height.data.encoder.scaling.multiplier": str(_DIRECT_HEIGHT_MULT),
+ "channel.height.data.encoder.scaling.offset": "0.0",
+ "channel.height.conversion-set.conversions.list": "calibrated",
+ "channel.height.conversion-set.conversion.calibrated.scaling.multiplier": "1.0",
+ "channel.height.conversion-set.conversion.calibrated.scaling.offset": "0.0",
+ "channel.vDeflection.data.type": "short",
+ "channel.vDeflection.data.encoder.scaling.multiplier": "1.0",
+ "channel.vDeflection.data.encoder.scaling.offset": "0.0",
+ "channel.vDeflection.conversion-set.conversions.list": "distance force",
+ "channel.vDeflection.conversion-set.conversion.distance.scaling.multiplier": str(
+ _DIRECT_INVOLS
+ ),
+ "channel.vDeflection.conversion-set.conversion.distance.scaling.offset": "0.0",
+ "channel.vDeflection.conversion-set.conversion.force.scaling.multiplier": str(
+ _DIRECT_SPRING_K
+ ),
+ "channel.vDeflection.conversion-set.conversion.force.scaling.offset": "0.0",
+ }
+
+
+def _lcd_info_record(
+ channel: str,
+ *,
+ dtype: str = "integer-data",
+ enc_mult: float,
+ enc_offset: float,
+ enc_unit: str = "V",
+ base: str = "volts",
+ slots: list[dict[str, object]],
+) -> dict[str, str]:
+ """Construye un registro ``lcd-info.{N}.*`` de shared-data (ForceScan 2.0)."""
+ rec: dict[str, str] = {
+ "type": dtype,
+ "channel.type": "channel",
+ "channel.name": channel,
+ "unit.type": "metric-unit",
+ "unit.unit": enc_unit,
+ "conversion-set.conversions.list": " ".join(str(s["name"]) for s in slots),
+ "conversion-set.conversions.default": str(slots[-1]["name"]),
+ "conversion-set.conversions.base": base,
+ "encoder.type": "signedinteger",
+ "encoder.scaling.type": "linear",
+ "encoder.scaling.style": "offsetmultiplier",
+ "encoder.scaling.multiplier": str(enc_mult),
+ "encoder.scaling.offset": str(enc_offset),
+ "encoder.scaling.unit.type": "metric-unit",
+ "encoder.scaling.unit.unit": enc_unit,
+ }
+ for s in slots:
+ rec[f"conversion-set.conversion.{s['name']}.name"] = str(
+ s.get("name", s["name"])
+ ).capitalize()
+ rec[f"conversion-set.conversion.{s['name']}.defined"] = str(
+ s.get("defined", "true")
+ ).lower()
+ rec[f"conversion-set.conversion.{s['name']}.type"] = "simple"
+ rec[f"conversion-set.conversion.{s['name']}.base-calibration-slot"] = str(s["base-slot"])
+ rec[f"conversion-set.conversion.{s['name']}.calibration-slot"] = str(s["name"])
+ rec[f"conversion-set.conversion.{s['name']}.scaling.type"] = "linear"
+ rec[f"conversion-set.conversion.{s['name']}.scaling.style"] = "offsetmultiplier"
+ rec[f"conversion-set.conversion.{s['name']}.scaling.multiplier"] = str(s["mult"])
+ rec[f"conversion-set.conversion.{s['name']}.scaling.offset"] = str(s["offset"])
+ rec[f"conversion-set.conversion.{s['name']}.scaling.unit.type"] = "metric-unit"
+ rec[f"conversion-set.conversion.{s['name']}.scaling.unit.unit"] = str(s["unit"])
+ return rec
+
+
+def _lcd_info_defaults() -> dict[str, dict[str, str]]:
+ """Registros lcd-info por defecto: cadenas equivalentes al perfil directo."""
+ height = _lcd_info_record(
+ "height",
+ enc_mult=_DIRECT_HEIGHT_MULT,
+ enc_offset=0.0,
+ enc_unit="V",
+ base="volts",
+ slots=[
+ {"name": "calibrated", "base-slot": "volts", "mult": 1.0, "offset": 0.0, "unit": "m"}
+ ],
+ )
+ vd = _lcd_info_record(
+ "vDeflection",
+ enc_mult=1.0,
+ enc_offset=0.0,
+ enc_unit="V",
+ base="volts",
+ slots=[
+ {
+ "name": "distance",
+ "base-slot": "volts",
+ "mult": _DIRECT_INVOLS,
+ "offset": 0.0,
+ "unit": "m",
+ },
+ {
+ "name": "force",
+ "base-slot": "distance",
+ "mult": _DIRECT_SPRING_K,
+ "offset": 0.0,
+ "unit": "N",
+ },
+ ],
+ )
+ return {"0": height, "1": vd}
+
+
+def _lcd_info_segment_header(
+ name: str,
+ *,
+ height_lcd: str = "0",
+ vd_lcd: str = "1",
+ num_points: int,
+ overrides: dict[str, str] | None = None,
+) -> dict[str, str]:
+ """Cabecera de segmento ForceScan 2.0: referencias ``lcd-info.*`` + archivos."""
+ h: dict[str, str] = {
+ "force-segment-header.name.name": name,
+ "channels.list": "height vDeflection",
+ "channel.height.lcd-info.*": height_lcd,
+ "channel.height.data.file.name": "channels/height.dat",
+ "channel.height.data.file.format": "raw",
+ "channel.height.data.num-points": str(num_points),
+ "channel.vDeflection.lcd-info.*": vd_lcd,
+ "channel.vDeflection.data.file.name": "channels/vDeflection.dat",
+ "channel.vDeflection.data.file.format": "raw",
+ "channel.vDeflection.data.num-points": str(num_points),
+ }
+ if overrides:
+ h.update(overrides)
+ return h
+
+
+def _base_archive(
+ path: Path,
+ *,
+ shared: dict[str, str],
+ segment_headers: list[dict[str, str]],
+ segments: list[dict[str, np.ndarray]],
+ header_extra: dict[str, str] | None = None,
+) -> None:
+ """Ensambla un archivo .jpk-force determinista desde piezas ya construidas."""
+ root = {"jpk-data-file": "spm-forcefile", "file-format-version": "2.0"}
+ if header_extra:
+ root.update(header_extra)
+ members: dict[str, bytes] = {
+ "header.properties": props_bytes(root),
+ "shared-data/header.properties": props_bytes(shared),
+ }
+ for idx, (seg_header, channels) in enumerate(zip(segment_headers, segments, strict=True)):
+ members[f"segments/{idx}/segment-header.properties"] = props_bytes(seg_header)
+ for ch, arr in channels.items():
+ members[f"segments/{idx}/channels/{ch}.dat"] = arr.astype(">i4").tobytes()
+ write_zip(path, members)
+
+
+def _segments_for(raw_h: np.ndarray, raw_vd: np.ndarray) -> list[dict[str, np.ndarray]]:
+ return [
+ {"height": raw_h.astype(np.int32), "vDeflection": raw_vd.astype(np.int32)},
+ {"height": raw_h.astype(np.int32), "vDeflection": raw_vd.astype(np.int32)},
+ ]
+
+
+# ---------------------------------------------------------------------------
+# Perfiles
+# ---------------------------------------------------------------------------
+
+
+def write_direct_scaling_jpk(path: Path, raw_h: np.ndarray, raw_vd: np.ndarray) -> None:
+ """1. Perfil directo (legacy): escalado en el segmento, datos int16."""
+ members: dict[str, bytes] = {
+ "header.properties": props_bytes({"jpk-data-file": "spm-forcefile"})
+ }
+ for idx, name in enumerate(("extend-spm", "retract-spm")):
+ members[f"segments/{idx}/segment-header.properties"] = props_bytes(
+ _segment_header_direct(name)
+ )
+ members[f"segments/{idx}/channels/height.dat"] = raw_h.astype(">i2").tobytes()
+ members[f"segments/{idx}/channels/vDeflection.dat"] = raw_vd.astype(">i2").tobytes()
+ write_zip(path, members)
+
+
+def write_lcd_info_jpk(
+ path: Path,
+ raw_h: np.ndarray,
+ raw_vd: np.ndarray,
+ *,
+ records: dict[str, dict[str, str]] | None = None,
+ height_lcd: str = "0",
+ vd_lcd: str = "1",
+) -> None:
+ """2. Perfil ForceScan 2.0: escalado vía ``lcd-info`` + shared-data, int32."""
+ records = records or _lcd_info_defaults()
+ shared: dict[str, str] = {"lcd-infos.count": str(len(records))}
+ for idx, rec in records.items():
+ for k, v in rec.items():
+ shared[f"lcd-info.{idx}.{k}"] = v
+ seg_headers = [
+ _lcd_info_segment_header(
+ "extend-spm", height_lcd=height_lcd, vd_lcd=vd_lcd, num_points=len(raw_h)
+ ),
+ _lcd_info_segment_header(
+ "retract-spm", height_lcd=height_lcd, vd_lcd=vd_lcd, num_points=len(raw_h)
+ ),
+ ]
+ _base_archive(
+ path, shared=shared, segment_headers=seg_headers, segments=_segments_for(raw_h, raw_vd)
+ )
+
+
+def write_local_override_jpk(
+ path: Path,
+ raw_h: np.ndarray,
+ raw_vd: np.ndarray,
+ *,
+ slot_mult: str = "2.0",
+) -> None:
+ """3. Override local: claves directas presentes Y referencia lcd-info.
+
+ Las claves directas del segmento deben ganar (valores distintos a los de
+ shared-data para poder distinguirlos), tanto en el encoder como en el slot
+ de conversión (conflicto total: ningún valor se fusiona desde shared-data).
+ """
+ records = _lcd_info_defaults()
+ shared: dict[str, str] = {"lcd-infos.count": str(len(records))}
+ for idx, rec in records.items():
+ for k, v in rec.items():
+ shared[f"lcd-info.{idx}.{k}"] = v
+ override = {
+ # dtype explícito coherente con el payload int32 de este perfil
+ "channel.height.data.type": "integer",
+ "channel.height.data.encoder.scaling.multiplier": "9.0E-9", # != 1.0E-9
+ "channel.height.data.encoder.scaling.offset": "0.0",
+ "channel.height.conversion-set.conversions.list": "calibrated",
+ # slot en conflicto con shared-data (shared: 1.0)
+ "channel.height.conversion-set.conversion.calibrated.scaling.multiplier": slot_mult,
+ "channel.height.conversion-set.conversion.calibrated.scaling.offset": "0.0",
+ }
+ seg_headers = [
+ _lcd_info_segment_header("extend-spm", num_points=len(raw_h), overrides=override),
+ _lcd_info_segment_header("retract-spm", num_points=len(raw_h), overrides=override),
+ ]
+ _base_archive(
+ path, shared=shared, segment_headers=seg_headers, segments=_segments_for(raw_h, raw_vd)
+ )
+
+
+def write_local_identical_jpk(path: Path, raw_h: np.ndarray, raw_vd: np.ndarray) -> None:
+ """3b. Claves directas IDÉNTICAS a shared-data + referencia lcd-info.
+
+ El resultado debe ser el mismo valor físico que el perfil compartido puro
+ (la precedencia local no altera el número cuando ambos coinciden).
+ """
+ records = _lcd_info_defaults()
+ shared: dict[str, str] = {"lcd-infos.count": str(len(records))}
+ for idx, rec in records.items():
+ for k, v in rec.items():
+ shared[f"lcd-info.{idx}.{k}"] = v
+ override = {
+ "channel.height.data.type": "integer",
+ # == shared 1.0e-9
+ "channel.height.data.encoder.scaling.multiplier": str(_DIRECT_HEIGHT_MULT),
+ "channel.height.data.encoder.scaling.offset": "0.0",
+ "channel.height.conversion-set.conversions.list": "calibrated",
+ # == shared
+ "channel.height.conversion-set.conversion.calibrated.scaling.multiplier": "1.0",
+ "channel.height.conversion-set.conversion.calibrated.scaling.offset": "0.0",
+ }
+ seg_headers = [
+ _lcd_info_segment_header("extend-spm", num_points=len(raw_h), overrides=override),
+ _lcd_info_segment_header("retract-spm", num_points=len(raw_h), overrides=override),
+ ]
+ _base_archive(
+ path, shared=shared, segment_headers=seg_headers, segments=_segments_for(raw_h, raw_vd)
+ )
+
+
+def write_missing_reference_jpk(path: Path, raw_h: np.ndarray, raw_vd: np.ndarray) -> None:
+ """4. Referencia fuera de rango: ``lcd-info.*=5`` con solo 2 registros."""
+ write_lcd_info_jpk(path, raw_h, raw_vd, height_lcd="5")
+
+
+def write_malformed_reference_jpk(path: Path, raw_h: np.ndarray, raw_vd: np.ndarray) -> None:
+ """5. Referencia malformada: ``lcd-info.*=abc``."""
+ write_lcd_info_jpk(path, raw_h, raw_vd, height_lcd="abc")
+
+
+def write_cyclic_reference_jpk(path: Path, raw_h: np.ndarray, raw_vd: np.ndarray) -> None:
+ """6. Cadena cíclica: el slot ``calibrated`` se referencia a sí mismo."""
+ height = _lcd_info_record(
+ "height",
+ enc_mult=_DIRECT_HEIGHT_MULT,
+ enc_offset=0.0,
+ enc_unit="V",
+ base="volts",
+ slots=[
+ {"name": "nominal", "base-slot": "volts", "mult": 1.0, "offset": 0.0, "unit": "m"},
+ # auto-referencia: calibrated.base-calibration-slot == calibrated
+ {
+ "name": "calibrated",
+ "base-slot": "calibrated",
+ "mult": 0.78,
+ "offset": 0.0,
+ "unit": "m",
+ },
+ ],
+ )
+ vd = _lcd_info_defaults()["1"]
+ write_lcd_info_jpk(path, raw_h, raw_vd, records={"0": height, "1": vd})
+
+
+def write_missing_optional_calibration_jpk(
+ path: Path, raw_h: np.ndarray, raw_vd: np.ndarray
+) -> None:
+ """7. Calibración opcional ausente: vDeflection sin slot ``force``.
+
+ El archivo es válido: la ausencia del slot force se preserva (state
+ ``deflection_m``, force ``None``, calibration ``None``), no es corrupción.
+ """
+ vd = _lcd_info_record(
+ "vDeflection",
+ enc_mult=1.0,
+ enc_offset=0.0,
+ enc_unit="V",
+ base="volts",
+ slots=[
+ {
+ "name": "distance",
+ "base-slot": "volts",
+ "mult": _DIRECT_INVOLS,
+ "offset": 0.0,
+ "unit": "m",
+ }
+ ],
+ )
+ records = {"0": _lcd_info_defaults()["0"], "1": vd}
+ write_lcd_info_jpk(path, raw_h, raw_vd, records=records)
+
+
+def write_complete_chain_jpk(path: Path, raw_h: np.ndarray, raw_vd: np.ndarray) -> None:
+ """8. Cadena completa: height nominal+calibrated, vDeflection encoder+distance+force.
+
+ Los valores de la cadena nominal→calibrated son arbitrarios pero explícitos
+ (mult nominal 1.3e-7, offset 1.5e-5; mult calibrated 0.78).
+ """
+ height = _lcd_info_record(
+ "height",
+ enc_mult=_DIRECT_HEIGHT_MULT,
+ enc_offset=0.0,
+ enc_unit="V",
+ base="volts",
+ slots=[
+ {
+ "name": "nominal",
+ "base-slot": "volts",
+ "mult": -1.3e-7,
+ "offset": 1.5e-5,
+ "unit": "m",
+ },
+ {
+ "name": "calibrated",
+ "base-slot": "nominal",
+ "mult": 0.78,
+ "offset": 0.0,
+ "unit": "m",
+ },
+ ],
+ )
+ records = {"0": height, "1": _lcd_info_defaults()["1"]}
+ write_lcd_info_jpk(path, raw_h, raw_vd, records=records)
+
+
+def write_missing_shared_property_jpk(path: Path, raw_h: np.ndarray, raw_vd: np.ndarray) -> None:
+ """9. Registro lcd-info presente pero sin ``encoder.scaling.multiplier``."""
+ height = _lcd_info_record(
+ "height",
+ enc_mult=_DIRECT_HEIGHT_MULT,
+ enc_offset=0.0,
+ enc_unit="V",
+ base="volts",
+ slots=[
+ {"name": "calibrated", "base-slot": "volts", "mult": 1.0, "offset": 0.0, "unit": "m"}
+ ],
+ )
+ del height["encoder.scaling.multiplier"]
+ records = {"0": height, "1": _lcd_info_defaults()["1"]}
+ write_lcd_info_jpk(path, raw_h, raw_vd, records=records)
+
+
+def write_unsupported_chain_jpk(
+ path: Path,
+ raw_h: np.ndarray,
+ raw_vd: np.ndarray,
+ *,
+ defined: bool = False,
+ final_unit: str | None = "V",
+) -> None:
+ """10. Cadena no soportada: slot ``user`` no definido, o unidad declarada ilegal.
+
+ Con ``defined=False`` el slot está declarado pero no calibrado; con
+ ``final_unit`` distinto de ``m`` la unidad final declarada es incompatible
+ con el rol del canal height.
+ """
+ slots: list[dict[str, object]] = [
+ {
+ "name": "user",
+ "base-slot": "volts",
+ "mult": 1.0,
+ "offset": 0.0,
+ "unit": final_unit or "V",
+ "defined": defined,
+ }
+ ]
+ height = _lcd_info_record(
+ "height",
+ enc_mult=_DIRECT_HEIGHT_MULT,
+ enc_offset=0.0,
+ enc_unit="V",
+ base="volts",
+ slots=slots,
+ )
+ records = {"0": height, "1": _lcd_info_defaults()["1"]}
+ write_lcd_info_jpk(path, raw_h, raw_vd, records=records)
diff --git a/tests/validation/fixtures/force_foundation/force_foundation_external.npz b/tests/validation/fixtures/force_foundation/force_foundation_external.npz
new file mode 100644
index 0000000..55e9bd6
Binary files /dev/null and b/tests/validation/fixtures/force_foundation/force_foundation_external.npz differ
diff --git a/tests/validation/fixtures/force_foundation/force_foundation_reference.json b/tests/validation/fixtures/force_foundation/force_foundation_reference.json
new file mode 100644
index 0000000..58febed
--- /dev/null
+++ b/tests/validation/fixtures/force_foundation/force_foundation_reference.json
@@ -0,0 +1,27529 @@
+{
+ "external_reference": {
+ "campaign_input_sha256": "b39774fc9823319b622b578cde9094d4ce90a3f62305addeb8a2f819b22821b7",
+ "cases": {
+ "P01": {
+ "contact": {
+ "deviation_from_baseline": 61,
+ "fit_constant_line": 83,
+ "fit_constant_polynomial": 52,
+ "fit_line_polynomial": 51
+ },
+ "force": [
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.991339902475641e-11,
+ 5.632359794751536e-11,
+ 1.0347305658681148e-10,
+ 1.593071921980508e-10,
+ 2.226385694121662e-10,
+ 2.92665999930535e-10,
+ 3.6880131104251e-10,
+ 4.505887835801215e-10,
+ 5.376617736684209e-10,
+ 6.297169687400575e-10,
+ 7.264980015232052e-10,
+ 8.277844526944918e-10,
+ 9.333841562829364e-10,
+ 1.0431276317945918e-09,
+ 1.1568639418389642e-09,
+ 1.2744575375844055e-09,
+ 1.3957858082504145e-09,
+ 1.5207371445829095e-09,
+ 1.6492093854549708e-09,
+ 1.781108555297326e-09,
+ 1.9163478258709203e-09,
+ 2.0548466535821312e-09,
+ 2.1965300559223163e-09,
+ 2.341327999444281e-09,
+ 2.4891748780945413e-09,
+ 2.6400090654389928e-09,
+ 2.793772527843911e-09,
+ 2.9504104883400783e-09,
+ 3.1098711329377853e-09,
+ 3.2721053527381266e-09,
+ 3.437066516418425e-09,
+ 3.6047102686409677e-09,
+ 3.774994350706328e-09,
+ 3.947878440391258e-09,
+ 4.123324008410478e-09,
+ 4.301294189347368e-09,
+ 4.481753665230602e-09,
+ 4.664668560206833e-09,
+ 4.850006344985562e-09,
+ 5.037735749920459e-09,
+ 5.227826685748644e-09,
+ 5.420250171141695e-09,
+ 5.614978266333607e-09,
+ 5.811984012185637e-09,
+ 6.011241374128476e-09,
+ 6.212725190490947e-09,
+ 6.416411124783542e-09,
+ 6.622275621555933e-09,
+ 6.830295865491421e-09,
+ 7.04044974343939e-09,
+ 7.252715809119926e-09,
+ 7.46707325026349e-09,
+ 7.68350185797383e-09,
+ 7.901981998124444e-09,
+ 8.122494584618158e-09,
+ 8.345021054356728e-09,
+ 8.569543343782359e-09,
+ 8.796043866866398e-09,
+ 9.024505494432458e-09,
+ 9.254911534711712e-09,
+ 9.487245715037483e-09,
+ 9.721492164594767e-09,
+ 9.957635398147762e-09,
+ 1.019566030067524e-08,
+ 1.0435552112849661e-08,
+ 1.0677296417301403e-08,
+ 1.0920879125614321e-08,
+ 1.1166286466003318e-08,
+ 1.1413504971628678e-08,
+ 1.1662521469505386e-08,
+ 1.191332306996908e-08,
+ 1.2165897156663265e-08,
+ 1.2420231377014999e-08,
+ 1.2676313633168888e-08,
+ 1.2934132073351431e-08,
+ 1.3193675083639767e-08,
+ 1.3454931280110766e-08,
+ 1.3717889501348295e-08,
+ 1.3982538801287734e-08,
+ 1.4248868442378609e-08,
+ 1.4516867889047359e-08,
+ 1.4786526801443447e-08,
+ 1.505783502945327e-08,
+ 1.5330782606967356e-08,
+ 1.5605359746387068e-08,
+ 1.5881556833358305e-08,
+ 1.6159364421720088e-08,
+ 1.643877322865705e-08,
+ 1.6719774130045237e-08,
+ 1.7002358155981545e-08,
+ 1.728651648648746e-08,
+ 1.757224044737853e-08,
+ 1.7859521506291434e-08,
+ 1.8148351268860977e-08,
+ 1.8438721475039828e-08,
+ 1.873062399555424e-08,
+ 1.9024050828489277e-08,
+ 1.9318994095997686e-08,
+ 1.9615446041126524e-08,
+ 1.9913399024756324e-08,
+ 2.0212845522647655e-08,
+ 2.0513778122590314e-08,
+ 2.0816189521650508e-08,
+ 2.1120072523511942e-08,
+ 2.1425420035906524e-08,
+ 2.1732225068130924e-08,
+ 2.20404807286454e-08,
+ 2.2350180222751278e-08,
+ 2.2661316850343915e-08,
+ 2.297388400373803e-08,
+ 2.328787516556231e-08,
+ 2.3603283906720617e-08,
+ 2.3920103884417023e-08,
+ 2.4238328840242195e-08,
+ 2.455795259831857e-08,
+ 2.4878969063502282e-08,
+ 2.5201372219639264e-08,
+ 2.552515612787379e-08,
+ 2.5850314925007185e-08,
+ 2.6176842821905e-08,
+ 2.6504734101950668e-08,
+ 2.6833983119544074e-08,
+ 2.71645842986432e-08,
+ 2.7496532131347385e-08,
+ 2.782982117652072e-08,
+ 2.8164446058453965e-08,
+ 2.850040146556374e-08,
+ 2.8837682149127742e-08,
+ 2.9176282922054447e-08,
+ 2.951619865768639e-08,
+ 2.985742428863566e-08,
+ 3.0199954805650614e-08,
+ 3.054378525651261e-08,
+ 3.0888910744961994e-08,
+ 3.123532642965203e-08,
+ 3.1583027523130054e-08,
+ 3.193200929084497e-08,
+ 3.2282267050180045e-08,
+ 3.263379616951032e-08,
+ 3.263379616951032e-08,
+ 3.2282267050180045e-08,
+ 3.193200929084497e-08,
+ 3.1583027523130054e-08,
+ 3.123532642965203e-08,
+ 3.0888910744961994e-08,
+ 3.054378525651261e-08,
+ 3.0199954805650614e-08,
+ 2.985742428863566e-08,
+ 2.951619865768639e-08,
+ 2.9176282922054447e-08,
+ 2.8837682149127742e-08,
+ 2.850040146556374e-08,
+ 2.8164446058453965e-08,
+ 2.782982117652072e-08,
+ 2.7496532131347385e-08,
+ 2.71645842986432e-08,
+ 2.6833983119544074e-08,
+ 2.6504734101950668e-08,
+ 2.6176842821905e-08,
+ 2.5850314925007185e-08,
+ 2.552515612787379e-08,
+ 2.5201372219639264e-08,
+ 2.4878969063502282e-08,
+ 2.455795259831857e-08,
+ 2.4238328840242195e-08,
+ 2.3920103884417023e-08,
+ 2.3603283906720617e-08,
+ 2.328787516556231e-08,
+ 2.297388400373803e-08,
+ 2.2661316850343915e-08,
+ 2.2350180222751278e-08,
+ 2.20404807286454e-08,
+ 2.1732225068130924e-08,
+ 2.1425420035906524e-08,
+ 2.1120072523511942e-08,
+ 2.0816189521650508e-08,
+ 2.0513778122590314e-08,
+ 2.0212845522647655e-08,
+ 1.9913399024756324e-08,
+ 1.9615446041126524e-08,
+ 1.9318994095997686e-08,
+ 1.9024050828489277e-08,
+ 1.873062399555424e-08,
+ 1.8438721475039828e-08,
+ 1.8148351268860977e-08,
+ 1.7859521506291434e-08,
+ 1.757224044737853e-08,
+ 1.728651648648746e-08,
+ 1.7002358155981545e-08,
+ 1.6719774130045237e-08,
+ 1.643877322865705e-08,
+ 1.6159364421720088e-08,
+ 1.5881556833358305e-08,
+ 1.5605359746387068e-08,
+ 1.5330782606967356e-08,
+ 1.505783502945327e-08,
+ 1.4786526801443447e-08,
+ 1.4516867889047359e-08,
+ 1.4248868442378609e-08,
+ 1.3982538801287734e-08,
+ 1.3717889501348295e-08,
+ 1.3454931280110766e-08,
+ 1.3193675083639767e-08,
+ 1.2934132073351431e-08,
+ 1.2676313633168888e-08,
+ 1.2420231377014999e-08,
+ 1.2165897156663265e-08,
+ 1.191332306996908e-08,
+ 1.1662521469505386e-08,
+ 1.1413504971628678e-08,
+ 1.1166286466003318e-08,
+ 1.0920879125614321e-08,
+ 1.0677296417301403e-08,
+ 1.0435552112849661e-08,
+ 1.019566030067524e-08,
+ 9.957635398147762e-09,
+ 9.721492164594767e-09,
+ 9.487245715037483e-09,
+ 9.254911534711712e-09,
+ 9.024505494432458e-09,
+ 8.796043866866398e-09,
+ 8.569543343782359e-09,
+ 8.345021054356728e-09,
+ 8.122494584618158e-09,
+ 7.901981998124444e-09,
+ 7.68350185797383e-09,
+ 7.46707325026349e-09,
+ 7.252715809119926e-09,
+ 7.04044974343939e-09,
+ 6.830295865491421e-09,
+ 6.622275621555933e-09,
+ 6.416411124783542e-09,
+ 6.212725190490947e-09,
+ 6.011241374128476e-09,
+ 5.811984012185637e-09,
+ 5.614978266333607e-09,
+ 5.420250171141695e-09,
+ 5.227826685748644e-09,
+ 5.037735749920459e-09,
+ 4.850006344985562e-09,
+ 4.664668560206833e-09,
+ 4.481753665230602e-09,
+ 4.301294189347368e-09,
+ 4.123324008410478e-09,
+ 3.947878440391258e-09,
+ 3.774994350706328e-09,
+ 3.6047102686409677e-09,
+ 3.437066516418425e-09,
+ 3.2721053527381266e-09,
+ 3.1098711329377853e-09,
+ 2.9504104883400783e-09,
+ 2.793772527843911e-09,
+ 2.6400090654389928e-09,
+ 2.4891748780945413e-09,
+ 2.341327999444281e-09,
+ 2.1965300559223163e-09,
+ 2.0548466535821312e-09,
+ 1.9163478258709203e-09,
+ 1.781108555297326e-09,
+ 1.6492093854549708e-09,
+ 1.5207371445829095e-09,
+ 1.3957858082504145e-09,
+ 1.2744575375844055e-09,
+ 1.1568639418389642e-09,
+ 1.0431276317945918e-09,
+ 9.333841562829364e-10,
+ 8.277844526944918e-10,
+ 7.264980015232052e-10,
+ 6.297169687400575e-10,
+ 5.376617736684209e-10,
+ 4.505887835801215e-10,
+ 3.6880131104251e-10,
+ 2.92665999930535e-10,
+ 2.226385694121662e-10,
+ 1.593071921980508e-10,
+ 1.0347305658681148e-10,
+ 5.632359794751536e-11,
+ 1.991339902475641e-11,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "height": [
+ 0.0,
+ 2.5125628140703518e-08,
+ 5.0251256281407036e-08,
+ 7.537688442211056e-08,
+ 1.0050251256281407e-07,
+ 1.2562814070351758e-07,
+ 1.5075376884422112e-07,
+ 1.7587939698492463e-07,
+ 2.0100502512562815e-07,
+ 2.2613065326633166e-07,
+ 2.5125628140703517e-07,
+ 2.763819095477387e-07,
+ 3.0150753768844224e-07,
+ 3.2663316582914573e-07,
+ 3.5175879396984927e-07,
+ 3.7688442211055275e-07,
+ 4.020100502512563e-07,
+ 4.2713567839195983e-07,
+ 4.522613065326633e-07,
+ 4.773869346733669e-07,
+ 5.025125628140703e-07,
+ 5.276381909547739e-07,
+ 5.527638190954774e-07,
+ 5.778894472361809e-07,
+ 6.030150753768845e-07,
+ 6.28140703517588e-07,
+ 6.532663316582915e-07,
+ 6.783919597989949e-07,
+ 7.035175879396985e-07,
+ 7.28643216080402e-07,
+ 7.537688442211055e-07,
+ 7.788944723618091e-07,
+ 8.040201005025126e-07,
+ 8.291457286432161e-07,
+ 8.542713567839197e-07,
+ 8.793969849246231e-07,
+ 9.045226130653266e-07,
+ 9.296482412060302e-07,
+ 9.547738693467337e-07,
+ 9.798994974874373e-07,
+ 1.0050251256281407e-06,
+ 1.0301507537688443e-06,
+ 1.0552763819095479e-06,
+ 1.0804020100502512e-06,
+ 1.1055276381909548e-06,
+ 1.1306532663316584e-06,
+ 1.1557788944723618e-06,
+ 1.1809045226130654e-06,
+ 1.206030150753769e-06,
+ 1.2311557788944724e-06,
+ 1.256281407035176e-06,
+ 1.2814070351758793e-06,
+ 1.306532663316583e-06,
+ 1.3316582914572865e-06,
+ 1.3567839195979899e-06,
+ 1.3819095477386935e-06,
+ 1.407035175879397e-06,
+ 1.4321608040201004e-06,
+ 1.457286432160804e-06,
+ 1.4824120603015076e-06,
+ 1.507537688442211e-06,
+ 1.5326633165829146e-06,
+ 1.5577889447236182e-06,
+ 1.5829145728643216e-06,
+ 1.6080402010050252e-06,
+ 1.6331658291457288e-06,
+ 1.6582914572864321e-06,
+ 1.6834170854271357e-06,
+ 1.7085427135678393e-06,
+ 1.7336683417085427e-06,
+ 1.7587939698492463e-06,
+ 1.7839195979899499e-06,
+ 1.8090452261306533e-06,
+ 1.8341708542713568e-06,
+ 1.8592964824120604e-06,
+ 1.8844221105527638e-06,
+ 1.9095477386934674e-06,
+ 1.9346733668341708e-06,
+ 1.9597989949748746e-06,
+ 1.984924623115578e-06,
+ 2.0100502512562813e-06,
+ 2.035175879396985e-06,
+ 2.0603015075376885e-06,
+ 2.085427135678392e-06,
+ 2.1105527638190957e-06,
+ 2.135678391959799e-06,
+ 2.1608040201005025e-06,
+ 2.1859296482412063e-06,
+ 2.2110552763819096e-06,
+ 2.236180904522613e-06,
+ 2.261306532663317e-06,
+ 2.28643216080402e-06,
+ 2.3115577889447236e-06,
+ 2.3366834170854274e-06,
+ 2.3618090452261308e-06,
+ 2.386934673366834e-06,
+ 2.412060301507538e-06,
+ 2.4371859296482413e-06,
+ 2.4623115577889447e-06,
+ 2.487437185929648e-06,
+ 2.512562814070352e-06,
+ 2.5376884422110553e-06,
+ 2.5628140703517587e-06,
+ 2.5879396984924625e-06,
+ 2.613065326633166e-06,
+ 2.6381909547738692e-06,
+ 2.663316582914573e-06,
+ 2.6884422110552764e-06,
+ 2.7135678391959798e-06,
+ 2.7386934673366836e-06,
+ 2.763819095477387e-06,
+ 2.7889447236180903e-06,
+ 2.814070351758794e-06,
+ 2.8391959798994975e-06,
+ 2.864321608040201e-06,
+ 2.8894472361809047e-06,
+ 2.914572864321608e-06,
+ 2.9396984924623115e-06,
+ 2.9648241206030153e-06,
+ 2.9899497487437186e-06,
+ 3.015075376884422e-06,
+ 3.040201005025126e-06,
+ 3.065326633165829e-06,
+ 3.0904522613065326e-06,
+ 3.1155778894472364e-06,
+ 3.1407035175879398e-06,
+ 3.165829145728643e-06,
+ 3.190954773869347e-06,
+ 3.2160804020100503e-06,
+ 3.2412060301507537e-06,
+ 3.2663316582914575e-06,
+ 3.291457286432161e-06,
+ 3.3165829145728643e-06,
+ 3.341708542713568e-06,
+ 3.3668341708542714e-06,
+ 3.391959798994975e-06,
+ 3.4170854271356786e-06,
+ 3.442211055276382e-06,
+ 3.4673366834170854e-06,
+ 3.492462311557789e-06,
+ 3.5175879396984926e-06,
+ 3.542713567839196e-06,
+ 3.5678391959798997e-06,
+ 3.592964824120603e-06,
+ 3.6180904522613065e-06,
+ 3.6432160804020103e-06,
+ 3.6683417085427137e-06,
+ 3.693467336683417e-06,
+ 3.718592964824121e-06,
+ 3.7437185929648243e-06,
+ 3.7688442211055276e-06,
+ 3.7939698492462314e-06,
+ 3.819095477386935e-06,
+ 3.844221105527638e-06,
+ 3.8693467336683416e-06,
+ 3.894472361809045e-06,
+ 3.919597989949749e-06,
+ 3.9447236180904526e-06,
+ 3.969849246231156e-06,
+ 3.994974874371859e-06,
+ 4.020100502512563e-06,
+ 4.045226130653266e-06,
+ 4.07035175879397e-06,
+ 4.095477386934674e-06,
+ 4.120603015075377e-06,
+ 4.1457286432160804e-06,
+ 4.170854271356784e-06,
+ 4.195979899497487e-06,
+ 4.221105527638191e-06,
+ 4.246231155778895e-06,
+ 4.271356783919598e-06,
+ 4.2964824120603016e-06,
+ 4.321608040201005e-06,
+ 4.346733668341708e-06,
+ 4.3718592964824125e-06,
+ 4.396984924623116e-06,
+ 4.422110552763819e-06,
+ 4.447236180904523e-06,
+ 4.472361809045226e-06,
+ 4.4974874371859294e-06,
+ 4.522613065326634e-06,
+ 4.547738693467337e-06,
+ 4.57286432160804e-06,
+ 4.597989949748744e-06,
+ 4.623115577889447e-06,
+ 4.6482412060301506e-06,
+ 4.673366834170855e-06,
+ 4.698492462311558e-06,
+ 4.7236180904522615e-06,
+ 4.748743718592965e-06,
+ 4.773869346733668e-06,
+ 4.798994974874372e-06,
+ 4.824120603015076e-06,
+ 4.849246231155779e-06,
+ 4.874371859296483e-06,
+ 4.899497487437186e-06,
+ 4.9246231155778894e-06,
+ 4.949748743718593e-06,
+ 4.974874371859296e-06,
+ 5e-06,
+ 5e-06,
+ 4.974874371859296e-06,
+ 4.949748743718593e-06,
+ 4.9246231155778894e-06,
+ 4.899497487437186e-06,
+ 4.874371859296483e-06,
+ 4.849246231155779e-06,
+ 4.824120603015076e-06,
+ 4.798994974874372e-06,
+ 4.773869346733668e-06,
+ 4.748743718592965e-06,
+ 4.7236180904522615e-06,
+ 4.698492462311558e-06,
+ 4.673366834170855e-06,
+ 4.6482412060301506e-06,
+ 4.623115577889447e-06,
+ 4.597989949748744e-06,
+ 4.57286432160804e-06,
+ 4.547738693467337e-06,
+ 4.522613065326634e-06,
+ 4.4974874371859294e-06,
+ 4.472361809045226e-06,
+ 4.447236180904523e-06,
+ 4.422110552763819e-06,
+ 4.396984924623116e-06,
+ 4.3718592964824125e-06,
+ 4.346733668341708e-06,
+ 4.321608040201005e-06,
+ 4.2964824120603016e-06,
+ 4.271356783919598e-06,
+ 4.246231155778895e-06,
+ 4.221105527638191e-06,
+ 4.195979899497487e-06,
+ 4.170854271356784e-06,
+ 4.1457286432160804e-06,
+ 4.120603015075377e-06,
+ 4.095477386934674e-06,
+ 4.07035175879397e-06,
+ 4.045226130653266e-06,
+ 4.020100502512563e-06,
+ 3.994974874371859e-06,
+ 3.969849246231156e-06,
+ 3.9447236180904526e-06,
+ 3.919597989949749e-06,
+ 3.894472361809045e-06,
+ 3.8693467336683416e-06,
+ 3.844221105527638e-06,
+ 3.819095477386935e-06,
+ 3.7939698492462314e-06,
+ 3.7688442211055276e-06,
+ 3.7437185929648243e-06,
+ 3.718592964824121e-06,
+ 3.693467336683417e-06,
+ 3.6683417085427137e-06,
+ 3.6432160804020103e-06,
+ 3.6180904522613065e-06,
+ 3.592964824120603e-06,
+ 3.5678391959798997e-06,
+ 3.542713567839196e-06,
+ 3.5175879396984926e-06,
+ 3.492462311557789e-06,
+ 3.4673366834170854e-06,
+ 3.442211055276382e-06,
+ 3.4170854271356786e-06,
+ 3.391959798994975e-06,
+ 3.3668341708542714e-06,
+ 3.341708542713568e-06,
+ 3.3165829145728643e-06,
+ 3.291457286432161e-06,
+ 3.2663316582914575e-06,
+ 3.2412060301507537e-06,
+ 3.2160804020100503e-06,
+ 3.190954773869347e-06,
+ 3.165829145728643e-06,
+ 3.1407035175879398e-06,
+ 3.1155778894472364e-06,
+ 3.0904522613065326e-06,
+ 3.065326633165829e-06,
+ 3.040201005025126e-06,
+ 3.015075376884422e-06,
+ 2.9899497487437186e-06,
+ 2.9648241206030153e-06,
+ 2.9396984924623115e-06,
+ 2.914572864321608e-06,
+ 2.8894472361809047e-06,
+ 2.864321608040201e-06,
+ 2.8391959798994975e-06,
+ 2.814070351758794e-06,
+ 2.7889447236180903e-06,
+ 2.763819095477387e-06,
+ 2.7386934673366836e-06,
+ 2.7135678391959798e-06,
+ 2.6884422110552764e-06,
+ 2.663316582914573e-06,
+ 2.6381909547738692e-06,
+ 2.613065326633166e-06,
+ 2.5879396984924625e-06,
+ 2.5628140703517587e-06,
+ 2.5376884422110553e-06,
+ 2.512562814070352e-06,
+ 2.487437185929648e-06,
+ 2.4623115577889447e-06,
+ 2.4371859296482413e-06,
+ 2.412060301507538e-06,
+ 2.386934673366834e-06,
+ 2.3618090452261308e-06,
+ 2.3366834170854274e-06,
+ 2.3115577889447236e-06,
+ 2.28643216080402e-06,
+ 2.261306532663317e-06,
+ 2.236180904522613e-06,
+ 2.2110552763819096e-06,
+ 2.1859296482412063e-06,
+ 2.1608040201005025e-06,
+ 2.135678391959799e-06,
+ 2.1105527638190957e-06,
+ 2.085427135678392e-06,
+ 2.0603015075376885e-06,
+ 2.035175879396985e-06,
+ 2.0100502512562813e-06,
+ 1.984924623115578e-06,
+ 1.9597989949748746e-06,
+ 1.9346733668341708e-06,
+ 1.9095477386934674e-06,
+ 1.8844221105527638e-06,
+ 1.8592964824120604e-06,
+ 1.8341708542713568e-06,
+ 1.8090452261306533e-06,
+ 1.7839195979899499e-06,
+ 1.7587939698492463e-06,
+ 1.7336683417085427e-06,
+ 1.7085427135678393e-06,
+ 1.6834170854271357e-06,
+ 1.6582914572864321e-06,
+ 1.6331658291457288e-06,
+ 1.6080402010050252e-06,
+ 1.5829145728643216e-06,
+ 1.5577889447236182e-06,
+ 1.5326633165829146e-06,
+ 1.507537688442211e-06,
+ 1.4824120603015076e-06,
+ 1.457286432160804e-06,
+ 1.4321608040201004e-06,
+ 1.407035175879397e-06,
+ 1.3819095477386935e-06,
+ 1.3567839195979899e-06,
+ 1.3316582914572865e-06,
+ 1.306532663316583e-06,
+ 1.2814070351758793e-06,
+ 1.256281407035176e-06,
+ 1.2311557788944724e-06,
+ 1.206030150753769e-06,
+ 1.1809045226130654e-06,
+ 1.1557788944723618e-06,
+ 1.1306532663316584e-06,
+ 1.1055276381909548e-06,
+ 1.0804020100502512e-06,
+ 1.0552763819095479e-06,
+ 1.0301507537688443e-06,
+ 1.0050251256281407e-06,
+ 9.798994974874373e-07,
+ 9.547738693467337e-07,
+ 9.296482412060302e-07,
+ 9.045226130653266e-07,
+ 8.793969849246231e-07,
+ 8.542713567839197e-07,
+ 8.291457286432161e-07,
+ 8.040201005025126e-07,
+ 7.788944723618091e-07,
+ 7.537688442211055e-07,
+ 7.28643216080402e-07,
+ 7.035175879396985e-07,
+ 6.783919597989949e-07,
+ 6.532663316582915e-07,
+ 6.28140703517588e-07,
+ 6.030150753768845e-07,
+ 5.778894472361809e-07,
+ 5.527638190954774e-07,
+ 5.276381909547739e-07,
+ 5.025125628140703e-07,
+ 4.773869346733669e-07,
+ 4.522613065326633e-07,
+ 4.2713567839195983e-07,
+ 4.020100502512563e-07,
+ 3.7688442211055275e-07,
+ 3.5175879396984927e-07,
+ 3.2663316582914573e-07,
+ 3.0150753768844224e-07,
+ 2.763819095477387e-07,
+ 2.5125628140703517e-07,
+ 2.2613065326633166e-07,
+ 2.0100502512562815e-07,
+ 1.7587939698492463e-07,
+ 1.5075376884422112e-07,
+ 1.2562814070351758e-07,
+ 1.0050251256281407e-07,
+ 7.537688442211056e-08,
+ 5.0251256281407036e-08,
+ 2.5125628140703518e-08,
+ 0.0
+ ],
+ "segment": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ "tip_position": [
+ -1.5328624505731621e-06,
+ -1.5077368224324585e-06,
+ -1.4826111942917552e-06,
+ -1.4574855661510516e-06,
+ -1.432359938010348e-06,
+ -1.4072343098696446e-06,
+ -1.382108681728941e-06,
+ -1.3569830535882374e-06,
+ -1.331857425447534e-06,
+ -1.3067317973068304e-06,
+ -1.2816061691661269e-06,
+ -1.2564805410254235e-06,
+ -1.2313549128847199e-06,
+ -1.2062292847440163e-06,
+ -1.181103656603313e-06,
+ -1.1559780284626093e-06,
+ -1.1308524003219057e-06,
+ -1.1057267721812023e-06,
+ -1.0806011440404988e-06,
+ -1.0554755158997952e-06,
+ -1.0303498877590918e-06,
+ -1.0052242596183882e-06,
+ -9.800986314776846e-07,
+ -9.549730033369812e-07,
+ -9.298473751962776e-07,
+ -9.047217470555742e-07,
+ -8.795961189148707e-07,
+ -8.544704907741672e-07,
+ -8.293448626334636e-07,
+ -8.042192344927601e-07,
+ -7.790936063520566e-07,
+ -7.53967978211353e-07,
+ -7.288423500706495e-07,
+ -7.037167219299461e-07,
+ -6.785910937892425e-07,
+ -6.53465465648539e-07,
+ -6.283398375078355e-07,
+ -6.032142093671319e-07,
+ -5.780885812264284e-07,
+ -5.529629530857248e-07,
+ -5.278373249450215e-07,
+ -5.027116968043179e-07,
+ -4.775860686636143e-07,
+ -4.524604405229109e-07,
+ -4.273348123822073e-07,
+ -4.022091842415037e-07,
+ -3.7708355610080033e-07,
+ -3.5195792796009674e-07,
+ -3.2683229981939315e-07,
+ -3.0170667167868977e-07,
+ -2.765810435379862e-07,
+ -2.514554153972828e-07,
+ -2.263297872565792e-07,
+ -2.0120415911587562e-07,
+ -1.7607853097517224e-07,
+ -1.5095290283446865e-07,
+ -1.2582727469376506e-07,
+ -1.0070164655306168e-07,
+ -7.557601841235809e-08,
+ -5.0450390271654495e-08,
+ -2.5324762130951115e-08,
+ 0.0,
+ 2.548973012993128e-08,
+ 5.108685285702749e-08,
+ 7.677082235384344e-08,
+ 1.0252976426668837e-07,
+ 1.2835566671257538e-07,
+ 1.5424264796439863e-07,
+ 1.8018615083047847e-07,
+ 2.0618250887206479e-07,
+ 2.322286889634847e-07,
+ 2.583221274320199e-07,
+ 2.8446062008443613e-07,
+ 3.106422452610241e-07,
+ 3.368653081568443e-07,
+ 3.6312829939799137e-07,
+ 3.894298634961494e-07,
+ 4.1576877434351265e-07,
+ 4.421439158475415e-07,
+ 4.6855426639696555e-07,
+ 4.949988862360923e-07,
+ 5.214769070825323e-07,
+ 5.479875235003479e-07,
+ 5.745299856644531e-07,
+ 6.011035932403766e-07,
+ 6.277076901675824e-07,
+ 6.543416601817305e-07,
+ 6.810049229464831e-07,
+ 7.076969306921482e-07,
+ 7.344171652788287e-07,
+ 7.611651356175358e-07,
+ 7.879403753950425e-07,
+ 8.147424410579713e-07,
+ 8.415709100193285e-07,
+ 8.684253790568813e-07,
+ 8.953054628777769e-07,
+ 9.222107928278496e-07,
+ 9.491410157273853e-07,
+ 9.76095792817851e-07,
+ 1.0030747988063417e-06,
+ 1.0300777209963945e-06,
+ 1.0571042584953794e-06,
+ 1.0841541214900135e-06,
+ 1.1112270305826365e-06,
+ 1.1383227161818599e-06,
+ 1.1654409179419919e-06,
+ 1.1925813842463204e-06,
+ 1.2197438717299499e-06,
+ 1.2469281448383768e-06,
+ 1.2741339754184357e-06,
+ 1.3013611423386188e-06,
+ 1.3286094311361276e-06,
+ 1.355878633688267e-06,
+ 1.3831685479060736e-06,
+ 1.4104789774482833e-06,
+ 1.4378097314539242e-06,
+ 1.4651606242920134e-06,
+ 1.4925314753269728e-06,
+ 1.5199221086985171e-06,
+ 1.547332353114881e-06,
+ 1.5747620416583769e-06,
+ 1.6022110116023384e-06,
+ 1.6296791042386147e-06,
+ 1.6571661647148482e-06,
+ 1.6846720418808267e-06,
+ 1.7121965881432744e-06,
+ 1.7397396593284949e-06,
+ 1.767301114552328e-06,
+ 1.7948808160969214e-06,
+ 1.8224786292938785e-06,
+ 1.850094422413349e-06,
+ 1.8777280665586894e-06,
+ 1.905379435566335e-06,
+ 1.9330484059105563e-06,
+ 1.9607348566127978e-06,
+ 1.988438669155327e-06,
+ 2.016159727398914e-06,
+ 2.0438979175043276e-06,
+ 2.0716531278574065e-06,
+ 2.099425248997505e-06,
+ 2.1272141735491164e-06,
+ 2.155019796156507e-06,
+ 2.1828420134211722e-06,
+ 2.210680723841974e-06,
+ 2.238535827757818e-06,
+ 2.2664072272927186e-06,
+ 2.294294826303135e-06,
+ 2.322198530327456e-06,
+ 2.3501182465375297e-06,
+ 2.3780538836921147e-06,
+ 2.406005352092181e-06,
+ 2.4339725635379443e-06,
+ 2.4619554312875585e-06,
+ 2.489953870017391e-06,
+ 2.5179677957837897e-06,
+ 2.5459971259862813e-06,
+ 2.5740417793321295e-06,
+ 2.6021016758021837e-06,
+ 2.630176736617971e-06,
+ 2.6582668842099627e-06,
+ 2.686372042186964e-06,
+ 2.714492135306581e-06,
+ 2.7426270894467117e-06,
+ 2.770776831578017e-06,
+ 2.7989412897373343e-06,
+ 2.827120393001984e-06,
+ 2.8553140714649308e-06,
+ 2.883522256210779e-06,
+ 2.9117448792925426e-06,
+ 2.939981873709172e-06,
+ 2.9682331733838166e-06,
+ 2.996498713142763e-06,
+ 3.0247784286950493e-06,
+ 3.0530722566127163e-06,
+ 3.0813801343116725e-06,
+ 3.10970200003314e-06,
+ 3.1380377928256802e-06,
+ 3.1663874525277537e-06,
+ 3.194750919750802e-06,
+ 3.2231281358628392e-06,
+ 3.251519042972522e-06,
+ 3.279923583913682e-06,
+ 3.3083417022303193e-06,
+ 3.3367733421620137e-06,
+ 3.3652184486297592e-06,
+ 3.3936769672221956e-06,
+ 3.4221488441822327e-06,
+ 3.450634026394034e-06,
+ 3.479132461370377e-06,
+ 3.5076440972403476e-06,
+ 3.5361688827373707e-06,
+ 3.5647067671875666e-06,
+ 3.59325770049842e-06,
+ 3.6218216331477435e-06,
+ 3.6503985161729403e-06,
+ 3.6789883011605446e-06,
+ 3.707590940236028e-06,
+ 3.7362063860538806e-06,
+ 3.7648345917879343e-06,
+ 3.7934755111219414e-06,
+ 3.7934755111219414e-06,
+ 3.7648345917879343e-06,
+ 3.7362063860538806e-06,
+ 3.707590940236028e-06,
+ 3.6789883011605446e-06,
+ 3.6503985161729403e-06,
+ 3.6218216331477435e-06,
+ 3.59325770049842e-06,
+ 3.5647067671875666e-06,
+ 3.5361688827373707e-06,
+ 3.5076440972403476e-06,
+ 3.479132461370377e-06,
+ 3.450634026394034e-06,
+ 3.4221488441822327e-06,
+ 3.3936769672221956e-06,
+ 3.3652184486297592e-06,
+ 3.3367733421620137e-06,
+ 3.3083417022303193e-06,
+ 3.279923583913682e-06,
+ 3.251519042972522e-06,
+ 3.2231281358628392e-06,
+ 3.194750919750802e-06,
+ 3.1663874525277537e-06,
+ 3.1380377928256802e-06,
+ 3.10970200003314e-06,
+ 3.0813801343116725e-06,
+ 3.0530722566127163e-06,
+ 3.0247784286950493e-06,
+ 2.996498713142763e-06,
+ 2.9682331733838166e-06,
+ 2.939981873709172e-06,
+ 2.9117448792925426e-06,
+ 2.883522256210779e-06,
+ 2.8553140714649308e-06,
+ 2.827120393001984e-06,
+ 2.7989412897373343e-06,
+ 2.770776831578017e-06,
+ 2.7426270894467117e-06,
+ 2.714492135306581e-06,
+ 2.686372042186964e-06,
+ 2.6582668842099627e-06,
+ 2.630176736617971e-06,
+ 2.6021016758021837e-06,
+ 2.5740417793321295e-06,
+ 2.5459971259862813e-06,
+ 2.5179677957837897e-06,
+ 2.489953870017391e-06,
+ 2.4619554312875585e-06,
+ 2.4339725635379443e-06,
+ 2.406005352092181e-06,
+ 2.3780538836921147e-06,
+ 2.3501182465375297e-06,
+ 2.322198530327456e-06,
+ 2.294294826303135e-06,
+ 2.2664072272927186e-06,
+ 2.238535827757818e-06,
+ 2.210680723841974e-06,
+ 2.1828420134211722e-06,
+ 2.155019796156507e-06,
+ 2.1272141735491164e-06,
+ 2.099425248997505e-06,
+ 2.0716531278574065e-06,
+ 2.0438979175043276e-06,
+ 2.016159727398914e-06,
+ 1.988438669155327e-06,
+ 1.9607348566127978e-06,
+ 1.9330484059105563e-06,
+ 1.905379435566335e-06,
+ 1.8777280665586894e-06,
+ 1.850094422413349e-06,
+ 1.8224786292938785e-06,
+ 1.7948808160969214e-06,
+ 1.767301114552328e-06,
+ 1.7397396593284949e-06,
+ 1.7121965881432744e-06,
+ 1.6846720418808267e-06,
+ 1.6571661647148482e-06,
+ 1.6296791042386147e-06,
+ 1.6022110116023384e-06,
+ 1.5747620416583769e-06,
+ 1.547332353114881e-06,
+ 1.5199221086985171e-06,
+ 1.4925314753269728e-06,
+ 1.4651606242920134e-06,
+ 1.4378097314539242e-06,
+ 1.4104789774482833e-06,
+ 1.3831685479060736e-06,
+ 1.355878633688267e-06,
+ 1.3286094311361276e-06,
+ 1.3013611423386188e-06,
+ 1.2741339754184357e-06,
+ 1.2469281448383768e-06,
+ 1.2197438717299499e-06,
+ 1.1925813842463204e-06,
+ 1.1654409179419919e-06,
+ 1.1383227161818599e-06,
+ 1.1112270305826365e-06,
+ 1.0841541214900135e-06,
+ 1.0571042584953794e-06,
+ 1.0300777209963945e-06,
+ 1.0030747988063417e-06,
+ 9.76095792817851e-07,
+ 9.491410157273853e-07,
+ 9.222107928278496e-07,
+ 8.953054628777769e-07,
+ 8.684253790568813e-07,
+ 8.415709100193285e-07,
+ 8.147424410579713e-07,
+ 7.879403753950425e-07,
+ 7.611651356175358e-07,
+ 7.344171652788287e-07,
+ 7.076969306921482e-07,
+ 6.810049229464831e-07,
+ 6.543416601817305e-07,
+ 6.277076901675824e-07,
+ 6.011035932403766e-07,
+ 5.745299856644531e-07,
+ 5.479875235003479e-07,
+ 5.214769070825323e-07,
+ 4.949988862360923e-07,
+ 4.6855426639696555e-07,
+ 4.421439158475415e-07,
+ 4.1576877434351265e-07,
+ 3.894298634961494e-07,
+ 3.6312829939799137e-07,
+ 3.368653081568443e-07,
+ 3.106422452610241e-07,
+ 2.8446062008443613e-07,
+ 2.583221274320199e-07,
+ 2.322286889634847e-07,
+ 2.0618250887206479e-07,
+ 1.8018615083047847e-07,
+ 1.5424264796439863e-07,
+ 1.2835566671257538e-07,
+ 1.0252976426668837e-07,
+ 7.677082235384344e-08,
+ 5.108685285702749e-08,
+ 2.548973012993128e-08,
+ 0.0,
+ -2.5324762130951115e-08,
+ -5.0450390271654495e-08,
+ -7.557601841235809e-08,
+ -1.0070164655306168e-07,
+ -1.2582727469376506e-07,
+ -1.5095290283446865e-07,
+ -1.7607853097517224e-07,
+ -2.0120415911587562e-07,
+ -2.263297872565792e-07,
+ -2.514554153972828e-07,
+ -2.765810435379862e-07,
+ -3.0170667167868977e-07,
+ -3.2683229981939315e-07,
+ -3.5195792796009674e-07,
+ -3.7708355610080033e-07,
+ -4.022091842415037e-07,
+ -4.273348123822073e-07,
+ -4.524604405229109e-07,
+ -4.775860686636143e-07,
+ -5.027116968043179e-07,
+ -5.278373249450215e-07,
+ -5.529629530857248e-07,
+ -5.780885812264284e-07,
+ -6.032142093671319e-07,
+ -6.283398375078355e-07,
+ -6.53465465648539e-07,
+ -6.785910937892425e-07,
+ -7.037167219299461e-07,
+ -7.288423500706495e-07,
+ -7.53967978211353e-07,
+ -7.790936063520566e-07,
+ -8.042192344927601e-07,
+ -8.293448626334636e-07,
+ -8.544704907741672e-07,
+ -8.795961189148707e-07,
+ -9.047217470555742e-07,
+ -9.298473751962776e-07,
+ -9.549730033369812e-07,
+ -9.800986314776846e-07,
+ -1.0052242596183882e-06,
+ -1.0303498877590918e-06,
+ -1.0554755158997952e-06,
+ -1.0806011440404988e-06,
+ -1.1057267721812023e-06,
+ -1.1308524003219057e-06,
+ -1.1559780284626093e-06,
+ -1.181103656603313e-06,
+ -1.2062292847440163e-06,
+ -1.2313549128847199e-06,
+ -1.2564805410254235e-06,
+ -1.2816061691661269e-06,
+ -1.3067317973068304e-06,
+ -1.331857425447534e-06,
+ -1.3569830535882374e-06,
+ -1.382108681728941e-06,
+ -1.4072343098696446e-06,
+ -1.432359938010348e-06,
+ -1.4574855661510516e-06,
+ -1.4826111942917552e-06,
+ -1.5077368224324585e-06,
+ -1.5328624505731621e-06
+ ]
+ },
+ "P02": {
+ "contact": {
+ "deviation_from_baseline": 56,
+ "fit_constant_line": 83,
+ "fit_constant_polynomial": 52,
+ "fit_line_polynomial": 51
+ },
+ "force": [
+ 1.0339757656912838e-25,
+ 1.033975765691284e-25,
+ 1.033975765691284e-25,
+ 1.033975765691284e-25,
+ 1.033975765691284e-25,
+ 1.033975765691284e-25,
+ 1.033975765691284e-25,
+ 1.033975765691284e-25,
+ 1.033975765691284e-25,
+ 1.033975765691284e-25,
+ 1.033975765691284e-25,
+ 1.033975765691284e-25,
+ 1.033975765691284e-25,
+ 1.033975765691284e-25,
+ 1.033975765691284e-25,
+ 1.033975765691284e-25,
+ 1.033975765691284e-25,
+ 1.033975765691284e-25,
+ 1.033975765691284e-25,
+ 1.033975765691284e-25,
+ 1.033975765691284e-25,
+ 1.0339757656912842e-25,
+ 1.0339757656912842e-25,
+ 1.0339757656912842e-25,
+ 1.0339757656912842e-25,
+ 1.0339757656912842e-25,
+ 1.0339757656912842e-25,
+ 1.0339757656912842e-25,
+ 1.0339757656912842e-25,
+ 1.0339757656912842e-25,
+ 1.0339757656912842e-25,
+ 1.0339757656912842e-25,
+ 1.0339757656912842e-25,
+ 1.0339757656912842e-25,
+ 1.0339757656912842e-25,
+ 1.0339757656912842e-25,
+ 1.0339757656912844e-25,
+ 1.0339757656912844e-25,
+ 1.0339757656912844e-25,
+ 1.0339757656912844e-25,
+ 1.0339757656912844e-25,
+ 1.0339757656912844e-25,
+ 1.0339757656912844e-25,
+ 1.0339757656912844e-25,
+ 1.0339757656912844e-25,
+ 1.0339757656912844e-25,
+ 1.0339757656912845e-25,
+ 1.0339757656912845e-25,
+ 1.0339757656912845e-25,
+ 1.0339757656912845e-25,
+ 1.0339757656912845e-25,
+ 1.0339757656912845e-25,
+ 1.0339757656912845e-25,
+ 1.0339757656912845e-25,
+ 1.0339757656912845e-25,
+ 1.0339757656912845e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.9913399024756524e-11,
+ 5.632359794751548e-11,
+ 1.034730565868116e-10,
+ 1.593071921980509e-10,
+ 2.2263856941216628e-10,
+ 2.926659999305351e-10,
+ 3.6880131104251015e-10,
+ 4.505887835801216e-10,
+ 5.37661773668421e-10,
+ 6.297169687400576e-10,
+ 7.264980015232053e-10,
+ 8.277844526944919e-10,
+ 9.333841562829364e-10,
+ 1.0431276317945918e-09,
+ 1.1568639418389642e-09,
+ 1.2744575375844055e-09,
+ 1.3957858082504145e-09,
+ 1.5207371445829098e-09,
+ 1.6492093854549708e-09,
+ 1.7811085552973265e-09,
+ 1.9163478258709207e-09,
+ 2.0548466535821317e-09,
+ 2.1965300559223163e-09,
+ 2.341327999444281e-09,
+ 2.4891748780945417e-09,
+ 2.6400090654389928e-09,
+ 2.793772527843911e-09,
+ 2.9504104883400783e-09,
+ 3.1098711329377853e-09,
+ 3.272105352738127e-09,
+ 3.437066516418425e-09,
+ 3.604710268640968e-09,
+ 3.774994350706328e-09,
+ 3.947878440391258e-09,
+ 4.123324008410478e-09,
+ 4.301294189347368e-09,
+ 4.481753665230602e-09,
+ 4.664668560206833e-09,
+ 4.850006344985562e-09,
+ 5.037735749920459e-09,
+ 5.227826685748644e-09,
+ 5.420250171141695e-09,
+ 5.614978266333607e-09,
+ 5.811984012185637e-09,
+ 6.011241374128476e-09,
+ 6.212725190490947e-09,
+ 6.416411124783542e-09,
+ 6.622275621555933e-09,
+ 6.830295865491421e-09,
+ 7.04044974343939e-09,
+ 7.252715809119926e-09,
+ 7.46707325026349e-09,
+ 7.68350185797383e-09,
+ 7.901981998124444e-09,
+ 8.122494584618158e-09,
+ 8.345021054356728e-09,
+ 8.569543343782359e-09,
+ 8.796043866866398e-09,
+ 9.024505494432458e-09,
+ 9.254911534711712e-09,
+ 9.487245715037483e-09,
+ 9.721492164594767e-09,
+ 9.957635398147762e-09,
+ 1.019566030067524e-08,
+ 1.0435552112849661e-08,
+ 1.0677296417301403e-08,
+ 1.0920879125614321e-08,
+ 1.1166286466003318e-08,
+ 1.1413504971628678e-08,
+ 1.1662521469505386e-08,
+ 1.191332306996908e-08,
+ 1.2165897156663265e-08,
+ 1.2420231377014999e-08,
+ 1.2676313633168888e-08,
+ 1.2934132073351431e-08,
+ 1.3193675083639767e-08,
+ 1.3454931280110766e-08,
+ 1.3717889501348295e-08,
+ 1.3982538801287734e-08,
+ 1.4248868442378609e-08,
+ 1.4516867889047359e-08,
+ 1.4786526801443447e-08,
+ 1.505783502945327e-08,
+ 1.5330782606967356e-08,
+ 1.5605359746387068e-08,
+ 1.5881556833358305e-08,
+ 1.6159364421720088e-08,
+ 1.643877322865705e-08,
+ 1.6719774130045237e-08,
+ 1.7002358155981545e-08,
+ 1.728651648648746e-08,
+ 1.757224044737853e-08,
+ 1.7859521506291434e-08,
+ 1.8148351268860977e-08,
+ 1.8438721475039828e-08,
+ 1.873062399555424e-08,
+ 1.9024050828489277e-08,
+ 1.9318994095997686e-08,
+ 1.9615446041126524e-08,
+ 1.9913399024756324e-08,
+ 2.0212845522647655e-08,
+ 2.0513778122590314e-08,
+ 2.0816189521650508e-08,
+ 2.1120072523511942e-08,
+ 2.1425420035906524e-08,
+ 2.1732225068130924e-08,
+ 2.20404807286454e-08,
+ 2.2350180222751278e-08,
+ 2.2661316850343915e-08,
+ 2.297388400373803e-08,
+ 2.328787516556231e-08,
+ 2.3603283906720617e-08,
+ 2.3920103884417023e-08,
+ 2.4238328840242195e-08,
+ 2.455795259831857e-08,
+ 2.4878969063502282e-08,
+ 2.5201372219639264e-08,
+ 2.552515612787379e-08,
+ 2.5850314925007185e-08,
+ 2.6176842821905e-08,
+ 2.6504734101950668e-08,
+ 2.6833983119544074e-08,
+ 2.71645842986432e-08,
+ 2.7496532131347385e-08,
+ 2.782982117652072e-08,
+ 2.8164446058453965e-08,
+ 2.850040146556374e-08,
+ 2.8837682149127742e-08,
+ 2.9176282922054447e-08,
+ 2.9516198657686393e-08,
+ 2.985742428863566e-08,
+ 3.0199954805650614e-08,
+ 3.054378525651261e-08,
+ 3.0888910744961994e-08,
+ 3.123532642965203e-08,
+ 3.1583027523130054e-08,
+ 3.193200929084497e-08,
+ 3.2282267050180045e-08,
+ 3.263379616951032e-08,
+ 3.263379616951032e-08,
+ 3.2282267050180045e-08,
+ 3.193200929084497e-08,
+ 3.1583027523130054e-08,
+ 3.123532642965203e-08,
+ 3.0888910744961994e-08,
+ 3.054378525651261e-08,
+ 3.0199954805650614e-08,
+ 2.985742428863566e-08,
+ 2.9516198657686393e-08,
+ 2.9176282922054447e-08,
+ 2.8837682149127742e-08,
+ 2.850040146556374e-08,
+ 2.8164446058453965e-08,
+ 2.782982117652072e-08,
+ 2.7496532131347385e-08,
+ 2.71645842986432e-08,
+ 2.6833983119544074e-08,
+ 2.6504734101950668e-08,
+ 2.6176842821905e-08,
+ 2.5850314925007185e-08,
+ 2.552515612787379e-08,
+ 2.5201372219639264e-08,
+ 2.4878969063502282e-08,
+ 2.455795259831857e-08,
+ 2.4238328840242195e-08,
+ 2.3920103884417023e-08,
+ 2.3603283906720617e-08,
+ 2.328787516556231e-08,
+ 2.297388400373803e-08,
+ 2.2661316850343915e-08,
+ 2.2350180222751278e-08,
+ 2.20404807286454e-08,
+ 2.1732225068130924e-08,
+ 2.1425420035906524e-08,
+ 2.1120072523511942e-08,
+ 2.0816189521650508e-08,
+ 2.0513778122590314e-08,
+ 2.0212845522647655e-08,
+ 1.9913399024756324e-08,
+ 1.9615446041126524e-08,
+ 1.9318994095997686e-08,
+ 1.9024050828489277e-08,
+ 1.873062399555424e-08,
+ 1.8438721475039828e-08,
+ 1.8148351268860977e-08,
+ 1.7859521506291434e-08,
+ 1.757224044737853e-08,
+ 1.728651648648746e-08,
+ 1.7002358155981545e-08,
+ 1.6719774130045237e-08,
+ 1.643877322865705e-08,
+ 1.6159364421720088e-08,
+ 1.5881556833358305e-08,
+ 1.5605359746387068e-08,
+ 1.5330782606967356e-08,
+ 1.505783502945327e-08,
+ 1.4786526801443447e-08,
+ 1.4516867889047359e-08,
+ 1.4248868442378609e-08,
+ 1.3982538801287734e-08,
+ 1.3717889501348295e-08,
+ 1.3454931280110766e-08,
+ 1.3193675083639767e-08,
+ 1.2934132073351431e-08,
+ 1.2676313633168888e-08,
+ 1.2420231377014999e-08,
+ 1.2165897156663265e-08,
+ 1.191332306996908e-08,
+ 1.1662521469505386e-08,
+ 1.1413504971628678e-08,
+ 1.1166286466003318e-08,
+ 1.0920879125614321e-08,
+ 1.0677296417301403e-08,
+ 1.0435552112849661e-08,
+ 1.019566030067524e-08,
+ 9.957635398147762e-09,
+ 9.721492164594767e-09,
+ 9.487245715037483e-09,
+ 9.254911534711712e-09,
+ 9.024505494432458e-09,
+ 8.796043866866398e-09,
+ 8.569543343782359e-09,
+ 8.345021054356728e-09,
+ 8.122494584618158e-09,
+ 7.901981998124444e-09,
+ 7.68350185797383e-09,
+ 7.46707325026349e-09,
+ 7.252715809119926e-09,
+ 7.04044974343939e-09,
+ 6.830295865491421e-09,
+ 6.622275621555933e-09,
+ 6.416411124783542e-09,
+ 6.212725190490947e-09,
+ 6.011241374128476e-09,
+ 5.811984012185637e-09,
+ 5.614978266333607e-09,
+ 5.420250171141695e-09,
+ 5.227826685748644e-09,
+ 5.037735749920459e-09,
+ 4.850006344985562e-09,
+ 4.664668560206833e-09,
+ 4.481753665230602e-09,
+ 4.301294189347368e-09,
+ 4.123324008410478e-09,
+ 3.947878440391258e-09,
+ 3.774994350706328e-09,
+ 3.604710268640968e-09,
+ 3.437066516418425e-09,
+ 3.272105352738127e-09,
+ 3.1098711329377853e-09,
+ 2.9504104883400783e-09,
+ 2.793772527843911e-09,
+ 2.6400090654389928e-09,
+ 2.4891748780945417e-09,
+ 2.341327999444281e-09,
+ 2.1965300559223163e-09,
+ 2.0548466535821317e-09,
+ 1.9163478258709207e-09,
+ 1.7811085552973265e-09,
+ 1.6492093854549708e-09,
+ 1.5207371445829098e-09,
+ 1.3957858082504145e-09,
+ 1.2744575375844055e-09,
+ 1.1568639418389642e-09,
+ 1.0431276317945918e-09,
+ 9.333841562829364e-10,
+ 8.277844526944919e-10,
+ 7.264980015232053e-10,
+ 6.297169687400576e-10,
+ 5.37661773668421e-10,
+ 4.505887835801216e-10,
+ 3.6880131104251015e-10,
+ 2.926659999305351e-10,
+ 2.2263856941216628e-10,
+ 1.593071921980509e-10,
+ 1.034730565868116e-10,
+ 5.632359794751548e-11,
+ 1.9913399024756524e-11,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25,
+ 1.0339757656912846e-25
+ ],
+ "height": [
+ 0.0,
+ 2.5125628140703518e-08,
+ 5.0251256281407036e-08,
+ 7.537688442211056e-08,
+ 1.0050251256281407e-07,
+ 1.2562814070351758e-07,
+ 1.5075376884422112e-07,
+ 1.7587939698492463e-07,
+ 2.0100502512562815e-07,
+ 2.2613065326633166e-07,
+ 2.5125628140703517e-07,
+ 2.763819095477387e-07,
+ 3.0150753768844224e-07,
+ 3.2663316582914573e-07,
+ 3.5175879396984927e-07,
+ 3.7688442211055275e-07,
+ 4.020100502512563e-07,
+ 4.2713567839195983e-07,
+ 4.522613065326633e-07,
+ 4.773869346733669e-07,
+ 5.025125628140703e-07,
+ 5.276381909547739e-07,
+ 5.527638190954774e-07,
+ 5.778894472361809e-07,
+ 6.030150753768845e-07,
+ 6.28140703517588e-07,
+ 6.532663316582915e-07,
+ 6.783919597989949e-07,
+ 7.035175879396985e-07,
+ 7.28643216080402e-07,
+ 7.537688442211055e-07,
+ 7.788944723618091e-07,
+ 8.040201005025126e-07,
+ 8.291457286432161e-07,
+ 8.542713567839197e-07,
+ 8.793969849246231e-07,
+ 9.045226130653266e-07,
+ 9.296482412060302e-07,
+ 9.547738693467337e-07,
+ 9.798994974874373e-07,
+ 1.0050251256281407e-06,
+ 1.0301507537688443e-06,
+ 1.0552763819095479e-06,
+ 1.0804020100502512e-06,
+ 1.1055276381909548e-06,
+ 1.1306532663316584e-06,
+ 1.1557788944723618e-06,
+ 1.1809045226130654e-06,
+ 1.206030150753769e-06,
+ 1.2311557788944724e-06,
+ 1.256281407035176e-06,
+ 1.2814070351758793e-06,
+ 1.306532663316583e-06,
+ 1.3316582914572865e-06,
+ 1.3567839195979899e-06,
+ 1.3819095477386935e-06,
+ 1.407035175879397e-06,
+ 1.4321608040201004e-06,
+ 1.457286432160804e-06,
+ 1.4824120603015076e-06,
+ 1.507537688442211e-06,
+ 1.5326633165829146e-06,
+ 1.5577889447236182e-06,
+ 1.5829145728643216e-06,
+ 1.6080402010050252e-06,
+ 1.6331658291457288e-06,
+ 1.6582914572864321e-06,
+ 1.6834170854271357e-06,
+ 1.7085427135678393e-06,
+ 1.7336683417085427e-06,
+ 1.7587939698492463e-06,
+ 1.7839195979899499e-06,
+ 1.8090452261306533e-06,
+ 1.8341708542713568e-06,
+ 1.8592964824120604e-06,
+ 1.8844221105527638e-06,
+ 1.9095477386934674e-06,
+ 1.9346733668341708e-06,
+ 1.9597989949748746e-06,
+ 1.984924623115578e-06,
+ 2.0100502512562813e-06,
+ 2.035175879396985e-06,
+ 2.0603015075376885e-06,
+ 2.085427135678392e-06,
+ 2.1105527638190957e-06,
+ 2.135678391959799e-06,
+ 2.1608040201005025e-06,
+ 2.1859296482412063e-06,
+ 2.2110552763819096e-06,
+ 2.236180904522613e-06,
+ 2.261306532663317e-06,
+ 2.28643216080402e-06,
+ 2.3115577889447236e-06,
+ 2.3366834170854274e-06,
+ 2.3618090452261308e-06,
+ 2.386934673366834e-06,
+ 2.412060301507538e-06,
+ 2.4371859296482413e-06,
+ 2.4623115577889447e-06,
+ 2.487437185929648e-06,
+ 2.512562814070352e-06,
+ 2.5376884422110553e-06,
+ 2.5628140703517587e-06,
+ 2.5879396984924625e-06,
+ 2.613065326633166e-06,
+ 2.6381909547738692e-06,
+ 2.663316582914573e-06,
+ 2.6884422110552764e-06,
+ 2.7135678391959798e-06,
+ 2.7386934673366836e-06,
+ 2.763819095477387e-06,
+ 2.7889447236180903e-06,
+ 2.814070351758794e-06,
+ 2.8391959798994975e-06,
+ 2.864321608040201e-06,
+ 2.8894472361809047e-06,
+ 2.914572864321608e-06,
+ 2.9396984924623115e-06,
+ 2.9648241206030153e-06,
+ 2.9899497487437186e-06,
+ 3.015075376884422e-06,
+ 3.040201005025126e-06,
+ 3.065326633165829e-06,
+ 3.0904522613065326e-06,
+ 3.1155778894472364e-06,
+ 3.1407035175879398e-06,
+ 3.165829145728643e-06,
+ 3.190954773869347e-06,
+ 3.2160804020100503e-06,
+ 3.2412060301507537e-06,
+ 3.2663316582914575e-06,
+ 3.291457286432161e-06,
+ 3.3165829145728643e-06,
+ 3.341708542713568e-06,
+ 3.3668341708542714e-06,
+ 3.391959798994975e-06,
+ 3.4170854271356786e-06,
+ 3.442211055276382e-06,
+ 3.4673366834170854e-06,
+ 3.492462311557789e-06,
+ 3.5175879396984926e-06,
+ 3.542713567839196e-06,
+ 3.5678391959798997e-06,
+ 3.592964824120603e-06,
+ 3.6180904522613065e-06,
+ 3.6432160804020103e-06,
+ 3.6683417085427137e-06,
+ 3.693467336683417e-06,
+ 3.718592964824121e-06,
+ 3.7437185929648243e-06,
+ 3.7688442211055276e-06,
+ 3.7939698492462314e-06,
+ 3.819095477386935e-06,
+ 3.844221105527638e-06,
+ 3.8693467336683416e-06,
+ 3.894472361809045e-06,
+ 3.919597989949749e-06,
+ 3.9447236180904526e-06,
+ 3.969849246231156e-06,
+ 3.994974874371859e-06,
+ 4.020100502512563e-06,
+ 4.045226130653266e-06,
+ 4.07035175879397e-06,
+ 4.095477386934674e-06,
+ 4.120603015075377e-06,
+ 4.1457286432160804e-06,
+ 4.170854271356784e-06,
+ 4.195979899497487e-06,
+ 4.221105527638191e-06,
+ 4.246231155778895e-06,
+ 4.271356783919598e-06,
+ 4.2964824120603016e-06,
+ 4.321608040201005e-06,
+ 4.346733668341708e-06,
+ 4.3718592964824125e-06,
+ 4.396984924623116e-06,
+ 4.422110552763819e-06,
+ 4.447236180904523e-06,
+ 4.472361809045226e-06,
+ 4.4974874371859294e-06,
+ 4.522613065326634e-06,
+ 4.547738693467337e-06,
+ 4.57286432160804e-06,
+ 4.597989949748744e-06,
+ 4.623115577889447e-06,
+ 4.6482412060301506e-06,
+ 4.673366834170855e-06,
+ 4.698492462311558e-06,
+ 4.7236180904522615e-06,
+ 4.748743718592965e-06,
+ 4.773869346733668e-06,
+ 4.798994974874372e-06,
+ 4.824120603015076e-06,
+ 4.849246231155779e-06,
+ 4.874371859296483e-06,
+ 4.899497487437186e-06,
+ 4.9246231155778894e-06,
+ 4.949748743718593e-06,
+ 4.974874371859296e-06,
+ 5e-06,
+ 5e-06,
+ 4.974874371859296e-06,
+ 4.949748743718593e-06,
+ 4.9246231155778894e-06,
+ 4.899497487437186e-06,
+ 4.874371859296483e-06,
+ 4.849246231155779e-06,
+ 4.824120603015076e-06,
+ 4.798994974874372e-06,
+ 4.773869346733668e-06,
+ 4.748743718592965e-06,
+ 4.7236180904522615e-06,
+ 4.698492462311558e-06,
+ 4.673366834170855e-06,
+ 4.6482412060301506e-06,
+ 4.623115577889447e-06,
+ 4.597989949748744e-06,
+ 4.57286432160804e-06,
+ 4.547738693467337e-06,
+ 4.522613065326634e-06,
+ 4.4974874371859294e-06,
+ 4.472361809045226e-06,
+ 4.447236180904523e-06,
+ 4.422110552763819e-06,
+ 4.396984924623116e-06,
+ 4.3718592964824125e-06,
+ 4.346733668341708e-06,
+ 4.321608040201005e-06,
+ 4.2964824120603016e-06,
+ 4.271356783919598e-06,
+ 4.246231155778895e-06,
+ 4.221105527638191e-06,
+ 4.195979899497487e-06,
+ 4.170854271356784e-06,
+ 4.1457286432160804e-06,
+ 4.120603015075377e-06,
+ 4.095477386934674e-06,
+ 4.07035175879397e-06,
+ 4.045226130653266e-06,
+ 4.020100502512563e-06,
+ 3.994974874371859e-06,
+ 3.969849246231156e-06,
+ 3.9447236180904526e-06,
+ 3.919597989949749e-06,
+ 3.894472361809045e-06,
+ 3.8693467336683416e-06,
+ 3.844221105527638e-06,
+ 3.819095477386935e-06,
+ 3.7939698492462314e-06,
+ 3.7688442211055276e-06,
+ 3.7437185929648243e-06,
+ 3.718592964824121e-06,
+ 3.693467336683417e-06,
+ 3.6683417085427137e-06,
+ 3.6432160804020103e-06,
+ 3.6180904522613065e-06,
+ 3.592964824120603e-06,
+ 3.5678391959798997e-06,
+ 3.542713567839196e-06,
+ 3.5175879396984926e-06,
+ 3.492462311557789e-06,
+ 3.4673366834170854e-06,
+ 3.442211055276382e-06,
+ 3.4170854271356786e-06,
+ 3.391959798994975e-06,
+ 3.3668341708542714e-06,
+ 3.341708542713568e-06,
+ 3.3165829145728643e-06,
+ 3.291457286432161e-06,
+ 3.2663316582914575e-06,
+ 3.2412060301507537e-06,
+ 3.2160804020100503e-06,
+ 3.190954773869347e-06,
+ 3.165829145728643e-06,
+ 3.1407035175879398e-06,
+ 3.1155778894472364e-06,
+ 3.0904522613065326e-06,
+ 3.065326633165829e-06,
+ 3.040201005025126e-06,
+ 3.015075376884422e-06,
+ 2.9899497487437186e-06,
+ 2.9648241206030153e-06,
+ 2.9396984924623115e-06,
+ 2.914572864321608e-06,
+ 2.8894472361809047e-06,
+ 2.864321608040201e-06,
+ 2.8391959798994975e-06,
+ 2.814070351758794e-06,
+ 2.7889447236180903e-06,
+ 2.763819095477387e-06,
+ 2.7386934673366836e-06,
+ 2.7135678391959798e-06,
+ 2.6884422110552764e-06,
+ 2.663316582914573e-06,
+ 2.6381909547738692e-06,
+ 2.613065326633166e-06,
+ 2.5879396984924625e-06,
+ 2.5628140703517587e-06,
+ 2.5376884422110553e-06,
+ 2.512562814070352e-06,
+ 2.487437185929648e-06,
+ 2.4623115577889447e-06,
+ 2.4371859296482413e-06,
+ 2.412060301507538e-06,
+ 2.386934673366834e-06,
+ 2.3618090452261308e-06,
+ 2.3366834170854274e-06,
+ 2.3115577889447236e-06,
+ 2.28643216080402e-06,
+ 2.261306532663317e-06,
+ 2.236180904522613e-06,
+ 2.2110552763819096e-06,
+ 2.1859296482412063e-06,
+ 2.1608040201005025e-06,
+ 2.135678391959799e-06,
+ 2.1105527638190957e-06,
+ 2.085427135678392e-06,
+ 2.0603015075376885e-06,
+ 2.035175879396985e-06,
+ 2.0100502512562813e-06,
+ 1.984924623115578e-06,
+ 1.9597989949748746e-06,
+ 1.9346733668341708e-06,
+ 1.9095477386934674e-06,
+ 1.8844221105527638e-06,
+ 1.8592964824120604e-06,
+ 1.8341708542713568e-06,
+ 1.8090452261306533e-06,
+ 1.7839195979899499e-06,
+ 1.7587939698492463e-06,
+ 1.7336683417085427e-06,
+ 1.7085427135678393e-06,
+ 1.6834170854271357e-06,
+ 1.6582914572864321e-06,
+ 1.6331658291457288e-06,
+ 1.6080402010050252e-06,
+ 1.5829145728643216e-06,
+ 1.5577889447236182e-06,
+ 1.5326633165829146e-06,
+ 1.507537688442211e-06,
+ 1.4824120603015076e-06,
+ 1.457286432160804e-06,
+ 1.4321608040201004e-06,
+ 1.407035175879397e-06,
+ 1.3819095477386935e-06,
+ 1.3567839195979899e-06,
+ 1.3316582914572865e-06,
+ 1.306532663316583e-06,
+ 1.2814070351758793e-06,
+ 1.256281407035176e-06,
+ 1.2311557788944724e-06,
+ 1.206030150753769e-06,
+ 1.1809045226130654e-06,
+ 1.1557788944723618e-06,
+ 1.1306532663316584e-06,
+ 1.1055276381909548e-06,
+ 1.0804020100502512e-06,
+ 1.0552763819095479e-06,
+ 1.0301507537688443e-06,
+ 1.0050251256281407e-06,
+ 9.798994974874373e-07,
+ 9.547738693467337e-07,
+ 9.296482412060302e-07,
+ 9.045226130653266e-07,
+ 8.793969849246231e-07,
+ 8.542713567839197e-07,
+ 8.291457286432161e-07,
+ 8.040201005025126e-07,
+ 7.788944723618091e-07,
+ 7.537688442211055e-07,
+ 7.28643216080402e-07,
+ 7.035175879396985e-07,
+ 6.783919597989949e-07,
+ 6.532663316582915e-07,
+ 6.28140703517588e-07,
+ 6.030150753768845e-07,
+ 5.778894472361809e-07,
+ 5.527638190954774e-07,
+ 5.276381909547739e-07,
+ 5.025125628140703e-07,
+ 4.773869346733669e-07,
+ 4.522613065326633e-07,
+ 4.2713567839195983e-07,
+ 4.020100502512563e-07,
+ 3.7688442211055275e-07,
+ 3.5175879396984927e-07,
+ 3.2663316582914573e-07,
+ 3.0150753768844224e-07,
+ 2.763819095477387e-07,
+ 2.5125628140703517e-07,
+ 2.2613065326633166e-07,
+ 2.0100502512562815e-07,
+ 1.7587939698492463e-07,
+ 1.5075376884422112e-07,
+ 1.2562814070351758e-07,
+ 1.0050251256281407e-07,
+ 7.537688442211056e-08,
+ 5.0251256281407036e-08,
+ 2.5125628140703518e-08,
+ 0.0
+ ],
+ "segment": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ "tip_position": [
+ -1.5328624505731621e-06,
+ -1.5077368224324585e-06,
+ -1.4826111942917552e-06,
+ -1.4574855661510516e-06,
+ -1.432359938010348e-06,
+ -1.4072343098696446e-06,
+ -1.382108681728941e-06,
+ -1.3569830535882374e-06,
+ -1.331857425447534e-06,
+ -1.3067317973068304e-06,
+ -1.2816061691661269e-06,
+ -1.2564805410254235e-06,
+ -1.2313549128847199e-06,
+ -1.2062292847440163e-06,
+ -1.181103656603313e-06,
+ -1.1559780284626093e-06,
+ -1.1308524003219057e-06,
+ -1.1057267721812023e-06,
+ -1.0806011440404988e-06,
+ -1.0554755158997952e-06,
+ -1.0303498877590918e-06,
+ -1.0052242596183882e-06,
+ -9.800986314776846e-07,
+ -9.549730033369812e-07,
+ -9.298473751962776e-07,
+ -9.047217470555742e-07,
+ -8.795961189148707e-07,
+ -8.544704907741672e-07,
+ -8.293448626334636e-07,
+ -8.042192344927601e-07,
+ -7.790936063520566e-07,
+ -7.53967978211353e-07,
+ -7.288423500706495e-07,
+ -7.037167219299461e-07,
+ -6.785910937892425e-07,
+ -6.53465465648539e-07,
+ -6.283398375078355e-07,
+ -6.032142093671319e-07,
+ -5.780885812264284e-07,
+ -5.529629530857248e-07,
+ -5.278373249450215e-07,
+ -5.027116968043179e-07,
+ -4.775860686636143e-07,
+ -4.524604405229109e-07,
+ -4.273348123822073e-07,
+ -4.022091842415037e-07,
+ -3.7708355610080033e-07,
+ -3.5195792796009674e-07,
+ -3.2683229981939315e-07,
+ -3.0170667167868977e-07,
+ -2.765810435379862e-07,
+ -2.514554153972828e-07,
+ -2.263297872565792e-07,
+ -2.0120415911587562e-07,
+ -1.7607853097517224e-07,
+ -1.5095290283446865e-07,
+ -1.2582727469376506e-07,
+ -1.0070164655306168e-07,
+ -7.557601841235809e-08,
+ -5.0450390271654495e-08,
+ -2.5324762130951115e-08,
+ 0.0,
+ 2.548973012993128e-08,
+ 5.108685285702749e-08,
+ 7.677082235384344e-08,
+ 1.0252976426668837e-07,
+ 1.2835566671257538e-07,
+ 1.5424264796439863e-07,
+ 1.8018615083047847e-07,
+ 2.0618250887206479e-07,
+ 2.322286889634847e-07,
+ 2.583221274320199e-07,
+ 2.8446062008443613e-07,
+ 3.106422452610241e-07,
+ 3.368653081568443e-07,
+ 3.6312829939799137e-07,
+ 3.8942986349614917e-07,
+ 4.1576877434351286e-07,
+ 4.421439158475417e-07,
+ 4.6855426639696534e-07,
+ 4.949988862360925e-07,
+ 5.214769070825321e-07,
+ 5.479875235003477e-07,
+ 5.745299856644529e-07,
+ 6.011035932403764e-07,
+ 6.277076901675822e-07,
+ 6.543416601817302e-07,
+ 6.810049229464833e-07,
+ 7.076969306921484e-07,
+ 7.344171652788289e-07,
+ 7.61165135617536e-07,
+ 7.879403753950423e-07,
+ 8.147424410579711e-07,
+ 8.415709100193287e-07,
+ 8.684253790568811e-07,
+ 8.953054628777767e-07,
+ 9.222107928278494e-07,
+ 9.491410157273851e-07,
+ 9.760957928178508e-07,
+ 1.0030747988063415e-06,
+ 1.0300777209963942e-06,
+ 1.0571042584953796e-06,
+ 1.0841541214900133e-06,
+ 1.1112270305826363e-06,
+ 1.13832271618186e-06,
+ 1.1654409179419917e-06,
+ 1.1925813842463202e-06,
+ 1.2197438717299496e-06,
+ 1.246928144838377e-06,
+ 1.2741339754184354e-06,
+ 1.3013611423386186e-06,
+ 1.3286094311361274e-06,
+ 1.3558786336882667e-06,
+ 1.3831685479060738e-06,
+ 1.410478977448283e-06,
+ 1.437809731453924e-06,
+ 1.4651606242920132e-06,
+ 1.492531475326973e-06,
+ 1.519922108698517e-06,
+ 1.5473323531148809e-06,
+ 1.574762041658377e-06,
+ 1.6022110116023386e-06,
+ 1.6296791042386145e-06,
+ 1.657166164714848e-06,
+ 1.6846720418808264e-06,
+ 1.7121965881432741e-06,
+ 1.739739659328495e-06,
+ 1.7673011145523282e-06,
+ 1.7948808160969211e-06,
+ 1.8224786292938783e-06,
+ 1.8500944224133493e-06,
+ 1.8777280665586896e-06,
+ 1.9053794355663347e-06,
+ 1.933048405910556e-06,
+ 1.960734856612798e-06,
+ 1.988438669155327e-06,
+ 2.016159727398914e-06,
+ 2.0438979175043276e-06,
+ 2.071653127857406e-06,
+ 2.0994252489975044e-06,
+ 2.1272141735491164e-06,
+ 2.1550197961565075e-06,
+ 2.1828420134211722e-06,
+ 2.2106807238419735e-06,
+ 2.238535827757818e-06,
+ 2.266407227292719e-06,
+ 2.2942948263031347e-06,
+ 2.3221985303274557e-06,
+ 2.3501182465375293e-06,
+ 2.3780538836921143e-06,
+ 2.4060053520921808e-06,
+ 2.433972563537944e-06,
+ 2.461955431287558e-06,
+ 2.4899538700173905e-06,
+ 2.5179677957837893e-06,
+ 2.545997125986281e-06,
+ 2.574041779332129e-06,
+ 2.6021016758021833e-06,
+ 2.6301767366179704e-06,
+ 2.6582668842099623e-06,
+ 2.6863720421869637e-06,
+ 2.7144921353065806e-06,
+ 2.7426270894467113e-06,
+ 2.7707768315780167e-06,
+ 2.7989412897373347e-06,
+ 2.8271203930019835e-06,
+ 2.855314071464931e-06,
+ 2.8835222562107793e-06,
+ 2.911744879292542e-06,
+ 2.9399818737091716e-06,
+ 2.9682331733838162e-06,
+ 2.9964987131427627e-06,
+ 3.024778428695049e-06,
+ 3.0530722566127167e-06,
+ 3.081380134311672e-06,
+ 3.1097020000331394e-06,
+ 3.13803779282568e-06,
+ 3.1663874525277533e-06,
+ 3.1947509197508018e-06,
+ 3.223128135862839e-06,
+ 3.2515190429725217e-06,
+ 3.2799235839136816e-06,
+ 3.308341702230319e-06,
+ 3.3367733421620133e-06,
+ 3.365218448629759e-06,
+ 3.393676967222196e-06,
+ 3.4221488441822323e-06,
+ 3.4506340263940334e-06,
+ 3.4791324613703768e-06,
+ 3.507644097240347e-06,
+ 3.5361688827373702e-06,
+ 3.564706767187566e-06,
+ 3.5932577004984203e-06,
+ 3.621821633147743e-06,
+ 3.6503985161729408e-06,
+ 3.678988301160544e-06,
+ 3.7075909402360275e-06,
+ 3.73620638605388e-06,
+ 3.7648345917879348e-06,
+ 3.793475511121942e-06,
+ 3.793475511121942e-06,
+ 3.7648345917879348e-06,
+ 3.73620638605388e-06,
+ 3.7075909402360275e-06,
+ 3.678988301160544e-06,
+ 3.6503985161729408e-06,
+ 3.621821633147743e-06,
+ 3.5932577004984203e-06,
+ 3.564706767187566e-06,
+ 3.5361688827373702e-06,
+ 3.507644097240347e-06,
+ 3.4791324613703768e-06,
+ 3.4506340263940334e-06,
+ 3.4221488441822323e-06,
+ 3.393676967222196e-06,
+ 3.365218448629759e-06,
+ 3.3367733421620133e-06,
+ 3.308341702230319e-06,
+ 3.2799235839136816e-06,
+ 3.2515190429725217e-06,
+ 3.223128135862839e-06,
+ 3.1947509197508018e-06,
+ 3.1663874525277533e-06,
+ 3.13803779282568e-06,
+ 3.1097020000331394e-06,
+ 3.081380134311672e-06,
+ 3.0530722566127167e-06,
+ 3.024778428695049e-06,
+ 2.9964987131427627e-06,
+ 2.9682331733838162e-06,
+ 2.9399818737091716e-06,
+ 2.911744879292542e-06,
+ 2.8835222562107793e-06,
+ 2.855314071464931e-06,
+ 2.8271203930019835e-06,
+ 2.7989412897373347e-06,
+ 2.7707768315780167e-06,
+ 2.7426270894467113e-06,
+ 2.7144921353065806e-06,
+ 2.6863720421869637e-06,
+ 2.6582668842099623e-06,
+ 2.6301767366179704e-06,
+ 2.6021016758021833e-06,
+ 2.574041779332129e-06,
+ 2.545997125986281e-06,
+ 2.5179677957837893e-06,
+ 2.4899538700173905e-06,
+ 2.461955431287558e-06,
+ 2.433972563537944e-06,
+ 2.4060053520921808e-06,
+ 2.3780538836921143e-06,
+ 2.3501182465375293e-06,
+ 2.3221985303274557e-06,
+ 2.2942948263031347e-06,
+ 2.266407227292719e-06,
+ 2.238535827757818e-06,
+ 2.2106807238419735e-06,
+ 2.1828420134211722e-06,
+ 2.1550197961565075e-06,
+ 2.1272141735491164e-06,
+ 2.0994252489975044e-06,
+ 2.071653127857406e-06,
+ 2.0438979175043276e-06,
+ 2.016159727398914e-06,
+ 1.988438669155327e-06,
+ 1.960734856612798e-06,
+ 1.933048405910556e-06,
+ 1.9053794355663347e-06,
+ 1.8777280665586896e-06,
+ 1.8500944224133493e-06,
+ 1.8224786292938783e-06,
+ 1.7948808160969211e-06,
+ 1.7673011145523282e-06,
+ 1.739739659328495e-06,
+ 1.7121965881432741e-06,
+ 1.6846720418808264e-06,
+ 1.657166164714848e-06,
+ 1.6296791042386145e-06,
+ 1.6022110116023386e-06,
+ 1.574762041658377e-06,
+ 1.5473323531148809e-06,
+ 1.519922108698517e-06,
+ 1.492531475326973e-06,
+ 1.4651606242920132e-06,
+ 1.437809731453924e-06,
+ 1.410478977448283e-06,
+ 1.3831685479060738e-06,
+ 1.3558786336882667e-06,
+ 1.3286094311361274e-06,
+ 1.3013611423386186e-06,
+ 1.2741339754184354e-06,
+ 1.246928144838377e-06,
+ 1.2197438717299496e-06,
+ 1.1925813842463202e-06,
+ 1.1654409179419917e-06,
+ 1.13832271618186e-06,
+ 1.1112270305826363e-06,
+ 1.0841541214900133e-06,
+ 1.0571042584953796e-06,
+ 1.0300777209963942e-06,
+ 1.0030747988063415e-06,
+ 9.760957928178508e-07,
+ 9.491410157273851e-07,
+ 9.222107928278494e-07,
+ 8.953054628777767e-07,
+ 8.684253790568811e-07,
+ 8.415709100193287e-07,
+ 8.147424410579711e-07,
+ 7.879403753950423e-07,
+ 7.61165135617536e-07,
+ 7.344171652788289e-07,
+ 7.076969306921484e-07,
+ 6.810049229464833e-07,
+ 6.543416601817302e-07,
+ 6.277076901675822e-07,
+ 6.011035932403764e-07,
+ 5.745299856644529e-07,
+ 5.479875235003477e-07,
+ 5.214769070825321e-07,
+ 4.949988862360925e-07,
+ 4.6855426639696534e-07,
+ 4.421439158475417e-07,
+ 4.1576877434351286e-07,
+ 3.8942986349614917e-07,
+ 3.6312829939799137e-07,
+ 3.368653081568443e-07,
+ 3.106422452610241e-07,
+ 2.8446062008443613e-07,
+ 2.583221274320199e-07,
+ 2.322286889634847e-07,
+ 2.0618250887206479e-07,
+ 1.8018615083047847e-07,
+ 1.5424264796439863e-07,
+ 1.2835566671257538e-07,
+ 1.0252976426668837e-07,
+ 7.677082235384344e-08,
+ 5.108685285702749e-08,
+ 2.548973012993128e-08,
+ 0.0,
+ -2.5324762130951115e-08,
+ -5.0450390271654495e-08,
+ -7.557601841235809e-08,
+ -1.0070164655306168e-07,
+ -1.2582727469376506e-07,
+ -1.5095290283446865e-07,
+ -1.7607853097517224e-07,
+ -2.0120415911587562e-07,
+ -2.263297872565792e-07,
+ -2.514554153972828e-07,
+ -2.765810435379862e-07,
+ -3.0170667167868977e-07,
+ -3.2683229981939315e-07,
+ -3.5195792796009674e-07,
+ -3.7708355610080033e-07,
+ -4.022091842415037e-07,
+ -4.273348123822073e-07,
+ -4.524604405229109e-07,
+ -4.775860686636143e-07,
+ -5.027116968043179e-07,
+ -5.278373249450215e-07,
+ -5.529629530857248e-07,
+ -5.780885812264284e-07,
+ -6.032142093671319e-07,
+ -6.283398375078355e-07,
+ -6.53465465648539e-07,
+ -6.785910937892425e-07,
+ -7.037167219299461e-07,
+ -7.288423500706495e-07,
+ -7.53967978211353e-07,
+ -7.790936063520566e-07,
+ -8.042192344927601e-07,
+ -8.293448626334636e-07,
+ -8.544704907741672e-07,
+ -8.795961189148707e-07,
+ -9.047217470555742e-07,
+ -9.298473751962776e-07,
+ -9.549730033369812e-07,
+ -9.800986314776846e-07,
+ -1.0052242596183882e-06,
+ -1.0303498877590918e-06,
+ -1.0554755158997952e-06,
+ -1.0806011440404988e-06,
+ -1.1057267721812023e-06,
+ -1.1308524003219057e-06,
+ -1.1559780284626093e-06,
+ -1.181103656603313e-06,
+ -1.2062292847440163e-06,
+ -1.2313549128847199e-06,
+ -1.2564805410254235e-06,
+ -1.2816061691661269e-06,
+ -1.3067317973068304e-06,
+ -1.331857425447534e-06,
+ -1.3569830535882374e-06,
+ -1.382108681728941e-06,
+ -1.4072343098696446e-06,
+ -1.432359938010348e-06,
+ -1.4574855661510516e-06,
+ -1.4826111942917552e-06,
+ -1.5077368224324585e-06,
+ -1.5328624505731621e-06
+ ]
+ },
+ "P03": {
+ "contact": {
+ "deviation_from_baseline": 61,
+ "fit_constant_line": 83,
+ "fit_constant_polynomial": 52,
+ "fit_line_polynomial": 51
+ },
+ "force": [
+ -1.0339757656912838e-25,
+ -1.033975765691284e-25,
+ -1.033975765691284e-25,
+ -1.033975765691284e-25,
+ -1.033975765691284e-25,
+ -1.033975765691284e-25,
+ -1.033975765691284e-25,
+ -1.033975765691284e-25,
+ -1.033975765691284e-25,
+ -1.033975765691284e-25,
+ -1.033975765691284e-25,
+ -1.033975765691284e-25,
+ -1.033975765691284e-25,
+ -1.033975765691284e-25,
+ -1.033975765691284e-25,
+ -1.033975765691284e-25,
+ -1.033975765691284e-25,
+ -1.033975765691284e-25,
+ -1.033975765691284e-25,
+ -1.033975765691284e-25,
+ -1.033975765691284e-25,
+ -1.0339757656912842e-25,
+ -1.0339757656912842e-25,
+ -1.0339757656912842e-25,
+ -1.0339757656912842e-25,
+ -1.0339757656912842e-25,
+ -1.0339757656912842e-25,
+ -1.0339757656912842e-25,
+ -1.0339757656912842e-25,
+ -1.0339757656912842e-25,
+ -1.0339757656912842e-25,
+ -1.0339757656912842e-25,
+ -1.0339757656912842e-25,
+ -1.0339757656912842e-25,
+ -1.0339757656912842e-25,
+ -1.0339757656912842e-25,
+ -1.0339757656912844e-25,
+ -1.0339757656912844e-25,
+ -1.0339757656912844e-25,
+ -1.0339757656912844e-25,
+ -1.0339757656912844e-25,
+ -1.0339757656912844e-25,
+ -1.0339757656912844e-25,
+ -1.0339757656912844e-25,
+ -1.0339757656912844e-25,
+ -1.0339757656912844e-25,
+ -1.0339757656912845e-25,
+ -1.0339757656912845e-25,
+ -1.0339757656912845e-25,
+ -1.0339757656912845e-25,
+ -1.0339757656912845e-25,
+ -1.0339757656912845e-25,
+ -1.0339757656912845e-25,
+ -1.0339757656912845e-25,
+ -1.0339757656912845e-25,
+ -1.0339757656912845e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ 1.9913399024756317e-11,
+ 5.6323597947515276e-11,
+ 1.0347305658681137e-10,
+ 1.593071921980507e-10,
+ 2.226385694121661e-10,
+ 2.9266599993053487e-10,
+ 3.688013110425099e-10,
+ 4.505887835801214e-10,
+ 5.376617736684208e-10,
+ 6.297169687400574e-10,
+ 7.264980015232051e-10,
+ 8.277844526944917e-10,
+ 9.333841562829364e-10,
+ 1.0431276317945918e-09,
+ 1.1568639418389642e-09,
+ 1.2744575375844055e-09,
+ 1.3957858082504145e-09,
+ 1.5207371445829093e-09,
+ 1.6492093854549708e-09,
+ 1.781108555297326e-09,
+ 1.9163478258709203e-09,
+ 2.0548466535821312e-09,
+ 2.1965300559223163e-09,
+ 2.341327999444281e-09,
+ 2.489174878094541e-09,
+ 2.6400090654389928e-09,
+ 2.793772527843911e-09,
+ 2.9504104883400783e-09,
+ 3.1098711329377853e-09,
+ 3.272105352738126e-09,
+ 3.437066516418425e-09,
+ 3.6047102686409673e-09,
+ 3.774994350706328e-09,
+ 3.947878440391258e-09,
+ 4.123324008410478e-09,
+ 4.301294189347368e-09,
+ 4.481753665230602e-09,
+ 4.664668560206833e-09,
+ 4.850006344985562e-09,
+ 5.037735749920459e-09,
+ 5.227826685748644e-09,
+ 5.420250171141695e-09,
+ 5.614978266333607e-09,
+ 5.811984012185637e-09,
+ 6.011241374128476e-09,
+ 6.212725190490947e-09,
+ 6.416411124783542e-09,
+ 6.622275621555933e-09,
+ 6.830295865491421e-09,
+ 7.04044974343939e-09,
+ 7.252715809119926e-09,
+ 7.46707325026349e-09,
+ 7.68350185797383e-09,
+ 7.901981998124444e-09,
+ 8.122494584618158e-09,
+ 8.345021054356728e-09,
+ 8.569543343782359e-09,
+ 8.796043866866398e-09,
+ 9.024505494432458e-09,
+ 9.254911534711712e-09,
+ 9.487245715037483e-09,
+ 9.721492164594767e-09,
+ 9.957635398147762e-09,
+ 1.019566030067524e-08,
+ 1.0435552112849661e-08,
+ 1.0677296417301403e-08,
+ 1.0920879125614321e-08,
+ 1.1166286466003318e-08,
+ 1.1413504971628678e-08,
+ 1.1662521469505386e-08,
+ 1.191332306996908e-08,
+ 1.2165897156663265e-08,
+ 1.2420231377014999e-08,
+ 1.2676313633168888e-08,
+ 1.2934132073351431e-08,
+ 1.3193675083639767e-08,
+ 1.3454931280110766e-08,
+ 1.3717889501348295e-08,
+ 1.3982538801287734e-08,
+ 1.4248868442378609e-08,
+ 1.4516867889047359e-08,
+ 1.4786526801443447e-08,
+ 1.505783502945327e-08,
+ 1.5330782606967356e-08,
+ 1.5605359746387068e-08,
+ 1.5881556833358305e-08,
+ 1.6159364421720088e-08,
+ 1.643877322865705e-08,
+ 1.6719774130045237e-08,
+ 1.7002358155981545e-08,
+ 1.728651648648746e-08,
+ 1.757224044737853e-08,
+ 1.7859521506291434e-08,
+ 1.8148351268860977e-08,
+ 1.8438721475039828e-08,
+ 1.873062399555424e-08,
+ 1.9024050828489277e-08,
+ 1.9318994095997686e-08,
+ 1.9615446041126524e-08,
+ 1.9913399024756324e-08,
+ 2.0212845522647655e-08,
+ 2.0513778122590314e-08,
+ 2.0816189521650508e-08,
+ 2.1120072523511942e-08,
+ 2.1425420035906524e-08,
+ 2.1732225068130924e-08,
+ 2.20404807286454e-08,
+ 2.2350180222751278e-08,
+ 2.2661316850343915e-08,
+ 2.297388400373803e-08,
+ 2.328787516556231e-08,
+ 2.3603283906720617e-08,
+ 2.3920103884417023e-08,
+ 2.4238328840242195e-08,
+ 2.455795259831857e-08,
+ 2.4878969063502282e-08,
+ 2.5201372219639264e-08,
+ 2.552515612787379e-08,
+ 2.5850314925007185e-08,
+ 2.6176842821905e-08,
+ 2.6504734101950668e-08,
+ 2.6833983119544074e-08,
+ 2.71645842986432e-08,
+ 2.7496532131347385e-08,
+ 2.782982117652072e-08,
+ 2.8164446058453965e-08,
+ 2.850040146556374e-08,
+ 2.8837682149127742e-08,
+ 2.9176282922054447e-08,
+ 2.951619865768639e-08,
+ 2.985742428863566e-08,
+ 3.0199954805650614e-08,
+ 3.054378525651261e-08,
+ 3.0888910744961994e-08,
+ 3.123532642965203e-08,
+ 3.1583027523130054e-08,
+ 3.193200929084497e-08,
+ 3.2282267050180045e-08,
+ 3.263379616951032e-08,
+ 3.263379616951032e-08,
+ 3.2282267050180045e-08,
+ 3.193200929084497e-08,
+ 3.1583027523130054e-08,
+ 3.123532642965203e-08,
+ 3.0888910744961994e-08,
+ 3.054378525651261e-08,
+ 3.0199954805650614e-08,
+ 2.985742428863566e-08,
+ 2.951619865768639e-08,
+ 2.9176282922054447e-08,
+ 2.8837682149127742e-08,
+ 2.850040146556374e-08,
+ 2.8164446058453965e-08,
+ 2.782982117652072e-08,
+ 2.7496532131347385e-08,
+ 2.71645842986432e-08,
+ 2.6833983119544074e-08,
+ 2.6504734101950668e-08,
+ 2.6176842821905e-08,
+ 2.5850314925007185e-08,
+ 2.552515612787379e-08,
+ 2.5201372219639264e-08,
+ 2.4878969063502282e-08,
+ 2.455795259831857e-08,
+ 2.4238328840242195e-08,
+ 2.3920103884417023e-08,
+ 2.3603283906720617e-08,
+ 2.328787516556231e-08,
+ 2.297388400373803e-08,
+ 2.2661316850343915e-08,
+ 2.2350180222751278e-08,
+ 2.20404807286454e-08,
+ 2.1732225068130924e-08,
+ 2.1425420035906524e-08,
+ 2.1120072523511942e-08,
+ 2.0816189521650508e-08,
+ 2.0513778122590314e-08,
+ 2.0212845522647655e-08,
+ 1.9913399024756324e-08,
+ 1.9615446041126524e-08,
+ 1.9318994095997686e-08,
+ 1.9024050828489277e-08,
+ 1.873062399555424e-08,
+ 1.8438721475039828e-08,
+ 1.8148351268860977e-08,
+ 1.7859521506291434e-08,
+ 1.757224044737853e-08,
+ 1.728651648648746e-08,
+ 1.7002358155981545e-08,
+ 1.6719774130045237e-08,
+ 1.643877322865705e-08,
+ 1.6159364421720088e-08,
+ 1.5881556833358305e-08,
+ 1.5605359746387068e-08,
+ 1.5330782606967356e-08,
+ 1.505783502945327e-08,
+ 1.4786526801443447e-08,
+ 1.4516867889047359e-08,
+ 1.4248868442378609e-08,
+ 1.3982538801287734e-08,
+ 1.3717889501348295e-08,
+ 1.3454931280110766e-08,
+ 1.3193675083639767e-08,
+ 1.2934132073351431e-08,
+ 1.2676313633168888e-08,
+ 1.2420231377014999e-08,
+ 1.2165897156663265e-08,
+ 1.191332306996908e-08,
+ 1.1662521469505386e-08,
+ 1.1413504971628678e-08,
+ 1.1166286466003318e-08,
+ 1.0920879125614321e-08,
+ 1.0677296417301403e-08,
+ 1.0435552112849661e-08,
+ 1.019566030067524e-08,
+ 9.957635398147762e-09,
+ 9.721492164594767e-09,
+ 9.487245715037483e-09,
+ 9.254911534711712e-09,
+ 9.024505494432458e-09,
+ 8.796043866866398e-09,
+ 8.569543343782359e-09,
+ 8.345021054356728e-09,
+ 8.122494584618158e-09,
+ 7.901981998124444e-09,
+ 7.68350185797383e-09,
+ 7.46707325026349e-09,
+ 7.252715809119926e-09,
+ 7.04044974343939e-09,
+ 6.830295865491421e-09,
+ 6.622275621555933e-09,
+ 6.416411124783542e-09,
+ 6.212725190490947e-09,
+ 6.011241374128476e-09,
+ 5.811984012185637e-09,
+ 5.614978266333607e-09,
+ 5.420250171141695e-09,
+ 5.227826685748644e-09,
+ 5.037735749920459e-09,
+ 4.850006344985562e-09,
+ 4.664668560206833e-09,
+ 4.481753665230602e-09,
+ 4.301294189347368e-09,
+ 4.123324008410478e-09,
+ 3.947878440391258e-09,
+ 3.774994350706328e-09,
+ 3.6047102686409673e-09,
+ 3.437066516418425e-09,
+ 3.272105352738126e-09,
+ 3.1098711329377853e-09,
+ 2.9504104883400783e-09,
+ 2.793772527843911e-09,
+ 2.6400090654389928e-09,
+ 2.489174878094541e-09,
+ 2.341327999444281e-09,
+ 2.1965300559223163e-09,
+ 2.0548466535821312e-09,
+ 1.9163478258709203e-09,
+ 1.781108555297326e-09,
+ 1.6492093854549708e-09,
+ 1.5207371445829093e-09,
+ 1.3957858082504145e-09,
+ 1.2744575375844055e-09,
+ 1.1568639418389642e-09,
+ 1.0431276317945918e-09,
+ 9.333841562829364e-10,
+ 8.277844526944917e-10,
+ 7.264980015232051e-10,
+ 6.297169687400574e-10,
+ 5.376617736684208e-10,
+ 4.505887835801214e-10,
+ 3.688013110425099e-10,
+ 2.9266599993053487e-10,
+ 2.226385694121661e-10,
+ 1.593071921980507e-10,
+ 1.0347305658681137e-10,
+ 5.6323597947515276e-11,
+ 1.9913399024756317e-11,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25,
+ -1.0339757656912846e-25
+ ],
+ "height": [
+ 0.0,
+ 2.5125628140703518e-08,
+ 5.0251256281407036e-08,
+ 7.537688442211056e-08,
+ 1.0050251256281407e-07,
+ 1.2562814070351758e-07,
+ 1.5075376884422112e-07,
+ 1.7587939698492463e-07,
+ 2.0100502512562815e-07,
+ 2.2613065326633166e-07,
+ 2.5125628140703517e-07,
+ 2.763819095477387e-07,
+ 3.0150753768844224e-07,
+ 3.2663316582914573e-07,
+ 3.5175879396984927e-07,
+ 3.7688442211055275e-07,
+ 4.020100502512563e-07,
+ 4.2713567839195983e-07,
+ 4.522613065326633e-07,
+ 4.773869346733669e-07,
+ 5.025125628140703e-07,
+ 5.276381909547739e-07,
+ 5.527638190954774e-07,
+ 5.778894472361809e-07,
+ 6.030150753768845e-07,
+ 6.28140703517588e-07,
+ 6.532663316582915e-07,
+ 6.783919597989949e-07,
+ 7.035175879396985e-07,
+ 7.28643216080402e-07,
+ 7.537688442211055e-07,
+ 7.788944723618091e-07,
+ 8.040201005025126e-07,
+ 8.291457286432161e-07,
+ 8.542713567839197e-07,
+ 8.793969849246231e-07,
+ 9.045226130653266e-07,
+ 9.296482412060302e-07,
+ 9.547738693467337e-07,
+ 9.798994974874373e-07,
+ 1.0050251256281407e-06,
+ 1.0301507537688443e-06,
+ 1.0552763819095479e-06,
+ 1.0804020100502512e-06,
+ 1.1055276381909548e-06,
+ 1.1306532663316584e-06,
+ 1.1557788944723618e-06,
+ 1.1809045226130654e-06,
+ 1.206030150753769e-06,
+ 1.2311557788944724e-06,
+ 1.256281407035176e-06,
+ 1.2814070351758793e-06,
+ 1.306532663316583e-06,
+ 1.3316582914572865e-06,
+ 1.3567839195979899e-06,
+ 1.3819095477386935e-06,
+ 1.407035175879397e-06,
+ 1.4321608040201004e-06,
+ 1.457286432160804e-06,
+ 1.4824120603015076e-06,
+ 1.507537688442211e-06,
+ 1.5326633165829146e-06,
+ 1.5577889447236182e-06,
+ 1.5829145728643216e-06,
+ 1.6080402010050252e-06,
+ 1.6331658291457288e-06,
+ 1.6582914572864321e-06,
+ 1.6834170854271357e-06,
+ 1.7085427135678393e-06,
+ 1.7336683417085427e-06,
+ 1.7587939698492463e-06,
+ 1.7839195979899499e-06,
+ 1.8090452261306533e-06,
+ 1.8341708542713568e-06,
+ 1.8592964824120604e-06,
+ 1.8844221105527638e-06,
+ 1.9095477386934674e-06,
+ 1.9346733668341708e-06,
+ 1.9597989949748746e-06,
+ 1.984924623115578e-06,
+ 2.0100502512562813e-06,
+ 2.035175879396985e-06,
+ 2.0603015075376885e-06,
+ 2.085427135678392e-06,
+ 2.1105527638190957e-06,
+ 2.135678391959799e-06,
+ 2.1608040201005025e-06,
+ 2.1859296482412063e-06,
+ 2.2110552763819096e-06,
+ 2.236180904522613e-06,
+ 2.261306532663317e-06,
+ 2.28643216080402e-06,
+ 2.3115577889447236e-06,
+ 2.3366834170854274e-06,
+ 2.3618090452261308e-06,
+ 2.386934673366834e-06,
+ 2.412060301507538e-06,
+ 2.4371859296482413e-06,
+ 2.4623115577889447e-06,
+ 2.487437185929648e-06,
+ 2.512562814070352e-06,
+ 2.5376884422110553e-06,
+ 2.5628140703517587e-06,
+ 2.5879396984924625e-06,
+ 2.613065326633166e-06,
+ 2.6381909547738692e-06,
+ 2.663316582914573e-06,
+ 2.6884422110552764e-06,
+ 2.7135678391959798e-06,
+ 2.7386934673366836e-06,
+ 2.763819095477387e-06,
+ 2.7889447236180903e-06,
+ 2.814070351758794e-06,
+ 2.8391959798994975e-06,
+ 2.864321608040201e-06,
+ 2.8894472361809047e-06,
+ 2.914572864321608e-06,
+ 2.9396984924623115e-06,
+ 2.9648241206030153e-06,
+ 2.9899497487437186e-06,
+ 3.015075376884422e-06,
+ 3.040201005025126e-06,
+ 3.065326633165829e-06,
+ 3.0904522613065326e-06,
+ 3.1155778894472364e-06,
+ 3.1407035175879398e-06,
+ 3.165829145728643e-06,
+ 3.190954773869347e-06,
+ 3.2160804020100503e-06,
+ 3.2412060301507537e-06,
+ 3.2663316582914575e-06,
+ 3.291457286432161e-06,
+ 3.3165829145728643e-06,
+ 3.341708542713568e-06,
+ 3.3668341708542714e-06,
+ 3.391959798994975e-06,
+ 3.4170854271356786e-06,
+ 3.442211055276382e-06,
+ 3.4673366834170854e-06,
+ 3.492462311557789e-06,
+ 3.5175879396984926e-06,
+ 3.542713567839196e-06,
+ 3.5678391959798997e-06,
+ 3.592964824120603e-06,
+ 3.6180904522613065e-06,
+ 3.6432160804020103e-06,
+ 3.6683417085427137e-06,
+ 3.693467336683417e-06,
+ 3.718592964824121e-06,
+ 3.7437185929648243e-06,
+ 3.7688442211055276e-06,
+ 3.7939698492462314e-06,
+ 3.819095477386935e-06,
+ 3.844221105527638e-06,
+ 3.8693467336683416e-06,
+ 3.894472361809045e-06,
+ 3.919597989949749e-06,
+ 3.9447236180904526e-06,
+ 3.969849246231156e-06,
+ 3.994974874371859e-06,
+ 4.020100502512563e-06,
+ 4.045226130653266e-06,
+ 4.07035175879397e-06,
+ 4.095477386934674e-06,
+ 4.120603015075377e-06,
+ 4.1457286432160804e-06,
+ 4.170854271356784e-06,
+ 4.195979899497487e-06,
+ 4.221105527638191e-06,
+ 4.246231155778895e-06,
+ 4.271356783919598e-06,
+ 4.2964824120603016e-06,
+ 4.321608040201005e-06,
+ 4.346733668341708e-06,
+ 4.3718592964824125e-06,
+ 4.396984924623116e-06,
+ 4.422110552763819e-06,
+ 4.447236180904523e-06,
+ 4.472361809045226e-06,
+ 4.4974874371859294e-06,
+ 4.522613065326634e-06,
+ 4.547738693467337e-06,
+ 4.57286432160804e-06,
+ 4.597989949748744e-06,
+ 4.623115577889447e-06,
+ 4.6482412060301506e-06,
+ 4.673366834170855e-06,
+ 4.698492462311558e-06,
+ 4.7236180904522615e-06,
+ 4.748743718592965e-06,
+ 4.773869346733668e-06,
+ 4.798994974874372e-06,
+ 4.824120603015076e-06,
+ 4.849246231155779e-06,
+ 4.874371859296483e-06,
+ 4.899497487437186e-06,
+ 4.9246231155778894e-06,
+ 4.949748743718593e-06,
+ 4.974874371859296e-06,
+ 5e-06,
+ 5e-06,
+ 4.974874371859296e-06,
+ 4.949748743718593e-06,
+ 4.9246231155778894e-06,
+ 4.899497487437186e-06,
+ 4.874371859296483e-06,
+ 4.849246231155779e-06,
+ 4.824120603015076e-06,
+ 4.798994974874372e-06,
+ 4.773869346733668e-06,
+ 4.748743718592965e-06,
+ 4.7236180904522615e-06,
+ 4.698492462311558e-06,
+ 4.673366834170855e-06,
+ 4.6482412060301506e-06,
+ 4.623115577889447e-06,
+ 4.597989949748744e-06,
+ 4.57286432160804e-06,
+ 4.547738693467337e-06,
+ 4.522613065326634e-06,
+ 4.4974874371859294e-06,
+ 4.472361809045226e-06,
+ 4.447236180904523e-06,
+ 4.422110552763819e-06,
+ 4.396984924623116e-06,
+ 4.3718592964824125e-06,
+ 4.346733668341708e-06,
+ 4.321608040201005e-06,
+ 4.2964824120603016e-06,
+ 4.271356783919598e-06,
+ 4.246231155778895e-06,
+ 4.221105527638191e-06,
+ 4.195979899497487e-06,
+ 4.170854271356784e-06,
+ 4.1457286432160804e-06,
+ 4.120603015075377e-06,
+ 4.095477386934674e-06,
+ 4.07035175879397e-06,
+ 4.045226130653266e-06,
+ 4.020100502512563e-06,
+ 3.994974874371859e-06,
+ 3.969849246231156e-06,
+ 3.9447236180904526e-06,
+ 3.919597989949749e-06,
+ 3.894472361809045e-06,
+ 3.8693467336683416e-06,
+ 3.844221105527638e-06,
+ 3.819095477386935e-06,
+ 3.7939698492462314e-06,
+ 3.7688442211055276e-06,
+ 3.7437185929648243e-06,
+ 3.718592964824121e-06,
+ 3.693467336683417e-06,
+ 3.6683417085427137e-06,
+ 3.6432160804020103e-06,
+ 3.6180904522613065e-06,
+ 3.592964824120603e-06,
+ 3.5678391959798997e-06,
+ 3.542713567839196e-06,
+ 3.5175879396984926e-06,
+ 3.492462311557789e-06,
+ 3.4673366834170854e-06,
+ 3.442211055276382e-06,
+ 3.4170854271356786e-06,
+ 3.391959798994975e-06,
+ 3.3668341708542714e-06,
+ 3.341708542713568e-06,
+ 3.3165829145728643e-06,
+ 3.291457286432161e-06,
+ 3.2663316582914575e-06,
+ 3.2412060301507537e-06,
+ 3.2160804020100503e-06,
+ 3.190954773869347e-06,
+ 3.165829145728643e-06,
+ 3.1407035175879398e-06,
+ 3.1155778894472364e-06,
+ 3.0904522613065326e-06,
+ 3.065326633165829e-06,
+ 3.040201005025126e-06,
+ 3.015075376884422e-06,
+ 2.9899497487437186e-06,
+ 2.9648241206030153e-06,
+ 2.9396984924623115e-06,
+ 2.914572864321608e-06,
+ 2.8894472361809047e-06,
+ 2.864321608040201e-06,
+ 2.8391959798994975e-06,
+ 2.814070351758794e-06,
+ 2.7889447236180903e-06,
+ 2.763819095477387e-06,
+ 2.7386934673366836e-06,
+ 2.7135678391959798e-06,
+ 2.6884422110552764e-06,
+ 2.663316582914573e-06,
+ 2.6381909547738692e-06,
+ 2.613065326633166e-06,
+ 2.5879396984924625e-06,
+ 2.5628140703517587e-06,
+ 2.5376884422110553e-06,
+ 2.512562814070352e-06,
+ 2.487437185929648e-06,
+ 2.4623115577889447e-06,
+ 2.4371859296482413e-06,
+ 2.412060301507538e-06,
+ 2.386934673366834e-06,
+ 2.3618090452261308e-06,
+ 2.3366834170854274e-06,
+ 2.3115577889447236e-06,
+ 2.28643216080402e-06,
+ 2.261306532663317e-06,
+ 2.236180904522613e-06,
+ 2.2110552763819096e-06,
+ 2.1859296482412063e-06,
+ 2.1608040201005025e-06,
+ 2.135678391959799e-06,
+ 2.1105527638190957e-06,
+ 2.085427135678392e-06,
+ 2.0603015075376885e-06,
+ 2.035175879396985e-06,
+ 2.0100502512562813e-06,
+ 1.984924623115578e-06,
+ 1.9597989949748746e-06,
+ 1.9346733668341708e-06,
+ 1.9095477386934674e-06,
+ 1.8844221105527638e-06,
+ 1.8592964824120604e-06,
+ 1.8341708542713568e-06,
+ 1.8090452261306533e-06,
+ 1.7839195979899499e-06,
+ 1.7587939698492463e-06,
+ 1.7336683417085427e-06,
+ 1.7085427135678393e-06,
+ 1.6834170854271357e-06,
+ 1.6582914572864321e-06,
+ 1.6331658291457288e-06,
+ 1.6080402010050252e-06,
+ 1.5829145728643216e-06,
+ 1.5577889447236182e-06,
+ 1.5326633165829146e-06,
+ 1.507537688442211e-06,
+ 1.4824120603015076e-06,
+ 1.457286432160804e-06,
+ 1.4321608040201004e-06,
+ 1.407035175879397e-06,
+ 1.3819095477386935e-06,
+ 1.3567839195979899e-06,
+ 1.3316582914572865e-06,
+ 1.306532663316583e-06,
+ 1.2814070351758793e-06,
+ 1.256281407035176e-06,
+ 1.2311557788944724e-06,
+ 1.206030150753769e-06,
+ 1.1809045226130654e-06,
+ 1.1557788944723618e-06,
+ 1.1306532663316584e-06,
+ 1.1055276381909548e-06,
+ 1.0804020100502512e-06,
+ 1.0552763819095479e-06,
+ 1.0301507537688443e-06,
+ 1.0050251256281407e-06,
+ 9.798994974874373e-07,
+ 9.547738693467337e-07,
+ 9.296482412060302e-07,
+ 9.045226130653266e-07,
+ 8.793969849246231e-07,
+ 8.542713567839197e-07,
+ 8.291457286432161e-07,
+ 8.040201005025126e-07,
+ 7.788944723618091e-07,
+ 7.537688442211055e-07,
+ 7.28643216080402e-07,
+ 7.035175879396985e-07,
+ 6.783919597989949e-07,
+ 6.532663316582915e-07,
+ 6.28140703517588e-07,
+ 6.030150753768845e-07,
+ 5.778894472361809e-07,
+ 5.527638190954774e-07,
+ 5.276381909547739e-07,
+ 5.025125628140703e-07,
+ 4.773869346733669e-07,
+ 4.522613065326633e-07,
+ 4.2713567839195983e-07,
+ 4.020100502512563e-07,
+ 3.7688442211055275e-07,
+ 3.5175879396984927e-07,
+ 3.2663316582914573e-07,
+ 3.0150753768844224e-07,
+ 2.763819095477387e-07,
+ 2.5125628140703517e-07,
+ 2.2613065326633166e-07,
+ 2.0100502512562815e-07,
+ 1.7587939698492463e-07,
+ 1.5075376884422112e-07,
+ 1.2562814070351758e-07,
+ 1.0050251256281407e-07,
+ 7.537688442211056e-08,
+ 5.0251256281407036e-08,
+ 2.5125628140703518e-08,
+ 0.0
+ ],
+ "segment": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ "tip_position": [
+ -1.5328624505731621e-06,
+ -1.5077368224324585e-06,
+ -1.4826111942917552e-06,
+ -1.4574855661510516e-06,
+ -1.432359938010348e-06,
+ -1.4072343098696446e-06,
+ -1.382108681728941e-06,
+ -1.3569830535882374e-06,
+ -1.331857425447534e-06,
+ -1.3067317973068304e-06,
+ -1.2816061691661269e-06,
+ -1.2564805410254235e-06,
+ -1.2313549128847199e-06,
+ -1.2062292847440163e-06,
+ -1.181103656603313e-06,
+ -1.1559780284626093e-06,
+ -1.1308524003219057e-06,
+ -1.1057267721812023e-06,
+ -1.0806011440404988e-06,
+ -1.0554755158997952e-06,
+ -1.0303498877590918e-06,
+ -1.0052242596183882e-06,
+ -9.800986314776846e-07,
+ -9.549730033369812e-07,
+ -9.298473751962776e-07,
+ -9.047217470555742e-07,
+ -8.795961189148707e-07,
+ -8.544704907741672e-07,
+ -8.293448626334636e-07,
+ -8.042192344927601e-07,
+ -7.790936063520566e-07,
+ -7.53967978211353e-07,
+ -7.288423500706495e-07,
+ -7.037167219299461e-07,
+ -6.785910937892425e-07,
+ -6.53465465648539e-07,
+ -6.283398375078355e-07,
+ -6.032142093671319e-07,
+ -5.780885812264284e-07,
+ -5.529629530857248e-07,
+ -5.278373249450215e-07,
+ -5.027116968043179e-07,
+ -4.775860686636143e-07,
+ -4.524604405229109e-07,
+ -4.273348123822073e-07,
+ -4.022091842415037e-07,
+ -3.7708355610080033e-07,
+ -3.5195792796009674e-07,
+ -3.2683229981939315e-07,
+ -3.0170667167868977e-07,
+ -2.765810435379862e-07,
+ -2.514554153972828e-07,
+ -2.263297872565792e-07,
+ -2.0120415911587562e-07,
+ -1.7607853097517224e-07,
+ -1.5095290283446865e-07,
+ -1.2582727469376506e-07,
+ -1.0070164655306168e-07,
+ -7.557601841235809e-08,
+ -5.0450390271654495e-08,
+ -2.5324762130951115e-08,
+ 0.0,
+ 2.548973012993128e-08,
+ 5.108685285702749e-08,
+ 7.677082235384366e-08,
+ 1.0252976426668837e-07,
+ 1.2835566671257538e-07,
+ 1.5424264796439863e-07,
+ 1.8018615083047847e-07,
+ 2.0618250887206479e-07,
+ 2.322286889634847e-07,
+ 2.583221274320199e-07,
+ 2.8446062008443613e-07,
+ 3.106422452610241e-07,
+ 3.368653081568443e-07,
+ 3.6312829939799137e-07,
+ 3.8942986349614917e-07,
+ 4.1576877434351286e-07,
+ 4.421439158475417e-07,
+ 4.6855426639696576e-07,
+ 4.949988862360925e-07,
+ 5.214769070825321e-07,
+ 5.479875235003477e-07,
+ 5.745299856644529e-07,
+ 6.011035932403764e-07,
+ 6.277076901675826e-07,
+ 6.543416601817302e-07,
+ 6.810049229464833e-07,
+ 7.076969306921484e-07,
+ 7.344171652788289e-07,
+ 7.61165135617536e-07,
+ 7.879403753950423e-07,
+ 8.147424410579711e-07,
+ 8.415709100193287e-07,
+ 8.684253790568811e-07,
+ 8.953054628777767e-07,
+ 9.222107928278494e-07,
+ 9.491410157273851e-07,
+ 9.760957928178508e-07,
+ 1.0030747988063415e-06,
+ 1.0300777209963942e-06,
+ 1.0571042584953796e-06,
+ 1.0841541214900137e-06,
+ 1.1112270305826363e-06,
+ 1.13832271618186e-06,
+ 1.1654409179419917e-06,
+ 1.1925813842463202e-06,
+ 1.2197438717299496e-06,
+ 1.246928144838377e-06,
+ 1.2741339754184359e-06,
+ 1.3013611423386186e-06,
+ 1.3286094311361274e-06,
+ 1.3558786336882671e-06,
+ 1.3831685479060738e-06,
+ 1.410478977448283e-06,
+ 1.437809731453924e-06,
+ 1.4651606242920132e-06,
+ 1.492531475326973e-06,
+ 1.519922108698517e-06,
+ 1.5473323531148813e-06,
+ 1.574762041658377e-06,
+ 1.6022110116023386e-06,
+ 1.629679104238615e-06,
+ 1.657166164714848e-06,
+ 1.6846720418808269e-06,
+ 1.7121965881432741e-06,
+ 1.739739659328495e-06,
+ 1.7673011145523282e-06,
+ 1.7948808160969216e-06,
+ 1.8224786292938783e-06,
+ 1.8500944224133493e-06,
+ 1.8777280665586896e-06,
+ 1.9053794355663347e-06,
+ 1.933048405910556e-06,
+ 1.960734856612798e-06,
+ 1.988438669155327e-06,
+ 2.016159727398914e-06,
+ 2.0438979175043276e-06,
+ 2.071653127857406e-06,
+ 2.0994252489975044e-06,
+ 2.1272141735491164e-06,
+ 2.1550197961565075e-06,
+ 2.1828420134211722e-06,
+ 2.2106807238419735e-06,
+ 2.238535827757818e-06,
+ 2.266407227292719e-06,
+ 2.2942948263031343e-06,
+ 2.322198530327456e-06,
+ 2.350118246537529e-06,
+ 2.3780538836921147e-06,
+ 2.406005352092181e-06,
+ 2.4339725635379443e-06,
+ 2.4619554312875576e-06,
+ 2.48995387001739e-06,
+ 2.517967795783789e-06,
+ 2.5459971259862813e-06,
+ 2.5740417793321295e-06,
+ 2.602101675802183e-06,
+ 2.630176736617971e-06,
+ 2.6582668842099627e-06,
+ 2.686372042186964e-06,
+ 2.71449213530658e-06,
+ 2.7426270894467117e-06,
+ 2.7707768315780163e-06,
+ 2.7989412897373343e-06,
+ 2.827120393001984e-06,
+ 2.8553140714649308e-06,
+ 2.883522256210779e-06,
+ 2.9117448792925417e-06,
+ 2.939981873709172e-06,
+ 2.9682331733838166e-06,
+ 2.9964987131427622e-06,
+ 3.0247784286950493e-06,
+ 3.0530722566127163e-06,
+ 3.0813801343116725e-06,
+ 3.10970200003314e-06,
+ 3.1380377928256802e-06,
+ 3.166387452527753e-06,
+ 3.194750919750802e-06,
+ 3.2231281358628392e-06,
+ 3.2515190429725213e-06,
+ 3.279923583913682e-06,
+ 3.3083417022303193e-06,
+ 3.3367733421620137e-06,
+ 3.3652184486297592e-06,
+ 3.3936769672221956e-06,
+ 3.4221488441822327e-06,
+ 3.450634026394034e-06,
+ 3.479132461370377e-06,
+ 3.5076440972403476e-06,
+ 3.53616888273737e-06,
+ 3.5647067671875657e-06,
+ 3.59325770049842e-06,
+ 3.6218216331477435e-06,
+ 3.6503985161729403e-06,
+ 3.6789883011605437e-06,
+ 3.707590940236028e-06,
+ 3.7362063860538806e-06,
+ 3.7648345917879343e-06,
+ 3.7934755111219414e-06,
+ 3.7934755111219414e-06,
+ 3.7648345917879343e-06,
+ 3.7362063860538806e-06,
+ 3.707590940236028e-06,
+ 3.6789883011605437e-06,
+ 3.6503985161729403e-06,
+ 3.6218216331477435e-06,
+ 3.59325770049842e-06,
+ 3.5647067671875657e-06,
+ 3.53616888273737e-06,
+ 3.5076440972403476e-06,
+ 3.479132461370377e-06,
+ 3.450634026394034e-06,
+ 3.4221488441822327e-06,
+ 3.3936769672221956e-06,
+ 3.3652184486297592e-06,
+ 3.3367733421620137e-06,
+ 3.3083417022303193e-06,
+ 3.279923583913682e-06,
+ 3.2515190429725213e-06,
+ 3.2231281358628392e-06,
+ 3.194750919750802e-06,
+ 3.166387452527753e-06,
+ 3.1380377928256802e-06,
+ 3.10970200003314e-06,
+ 3.0813801343116725e-06,
+ 3.0530722566127163e-06,
+ 3.0247784286950493e-06,
+ 2.9964987131427622e-06,
+ 2.9682331733838166e-06,
+ 2.939981873709172e-06,
+ 2.9117448792925417e-06,
+ 2.883522256210779e-06,
+ 2.8553140714649308e-06,
+ 2.827120393001984e-06,
+ 2.7989412897373343e-06,
+ 2.7707768315780163e-06,
+ 2.7426270894467117e-06,
+ 2.71449213530658e-06,
+ 2.686372042186964e-06,
+ 2.6582668842099627e-06,
+ 2.630176736617971e-06,
+ 2.602101675802183e-06,
+ 2.5740417793321295e-06,
+ 2.5459971259862813e-06,
+ 2.517967795783789e-06,
+ 2.48995387001739e-06,
+ 2.4619554312875576e-06,
+ 2.4339725635379443e-06,
+ 2.406005352092181e-06,
+ 2.3780538836921147e-06,
+ 2.350118246537529e-06,
+ 2.322198530327456e-06,
+ 2.2942948263031343e-06,
+ 2.266407227292719e-06,
+ 2.238535827757818e-06,
+ 2.2106807238419735e-06,
+ 2.1828420134211722e-06,
+ 2.1550197961565075e-06,
+ 2.1272141735491164e-06,
+ 2.0994252489975044e-06,
+ 2.071653127857406e-06,
+ 2.0438979175043276e-06,
+ 2.016159727398914e-06,
+ 1.988438669155327e-06,
+ 1.960734856612798e-06,
+ 1.933048405910556e-06,
+ 1.9053794355663347e-06,
+ 1.8777280665586896e-06,
+ 1.8500944224133493e-06,
+ 1.8224786292938783e-06,
+ 1.7948808160969216e-06,
+ 1.7673011145523282e-06,
+ 1.739739659328495e-06,
+ 1.7121965881432741e-06,
+ 1.6846720418808269e-06,
+ 1.657166164714848e-06,
+ 1.629679104238615e-06,
+ 1.6022110116023386e-06,
+ 1.574762041658377e-06,
+ 1.5473323531148813e-06,
+ 1.519922108698517e-06,
+ 1.492531475326973e-06,
+ 1.4651606242920132e-06,
+ 1.437809731453924e-06,
+ 1.410478977448283e-06,
+ 1.3831685479060738e-06,
+ 1.3558786336882671e-06,
+ 1.3286094311361274e-06,
+ 1.3013611423386186e-06,
+ 1.2741339754184359e-06,
+ 1.246928144838377e-06,
+ 1.2197438717299496e-06,
+ 1.1925813842463202e-06,
+ 1.1654409179419917e-06,
+ 1.13832271618186e-06,
+ 1.1112270305826363e-06,
+ 1.0841541214900137e-06,
+ 1.0571042584953796e-06,
+ 1.0300777209963942e-06,
+ 1.0030747988063415e-06,
+ 9.760957928178508e-07,
+ 9.491410157273851e-07,
+ 9.222107928278494e-07,
+ 8.953054628777767e-07,
+ 8.684253790568811e-07,
+ 8.415709100193287e-07,
+ 8.147424410579711e-07,
+ 7.879403753950423e-07,
+ 7.61165135617536e-07,
+ 7.344171652788289e-07,
+ 7.076969306921484e-07,
+ 6.810049229464833e-07,
+ 6.543416601817302e-07,
+ 6.277076901675826e-07,
+ 6.011035932403764e-07,
+ 5.745299856644529e-07,
+ 5.479875235003477e-07,
+ 5.214769070825321e-07,
+ 4.949988862360925e-07,
+ 4.6855426639696576e-07,
+ 4.421439158475417e-07,
+ 4.1576877434351286e-07,
+ 3.8942986349614917e-07,
+ 3.6312829939799137e-07,
+ 3.368653081568443e-07,
+ 3.106422452610241e-07,
+ 2.8446062008443613e-07,
+ 2.583221274320199e-07,
+ 2.322286889634847e-07,
+ 2.0618250887206479e-07,
+ 1.8018615083047847e-07,
+ 1.5424264796439863e-07,
+ 1.2835566671257538e-07,
+ 1.0252976426668837e-07,
+ 7.677082235384366e-08,
+ 5.108685285702749e-08,
+ 2.548973012993128e-08,
+ 0.0,
+ -2.5324762130951115e-08,
+ -5.0450390271654495e-08,
+ -7.557601841235809e-08,
+ -1.0070164655306168e-07,
+ -1.2582727469376506e-07,
+ -1.5095290283446865e-07,
+ -1.7607853097517224e-07,
+ -2.0120415911587562e-07,
+ -2.263297872565792e-07,
+ -2.514554153972828e-07,
+ -2.765810435379862e-07,
+ -3.0170667167868977e-07,
+ -3.2683229981939315e-07,
+ -3.5195792796009674e-07,
+ -3.7708355610080033e-07,
+ -4.022091842415037e-07,
+ -4.273348123822073e-07,
+ -4.524604405229109e-07,
+ -4.775860686636143e-07,
+ -5.027116968043179e-07,
+ -5.278373249450215e-07,
+ -5.529629530857248e-07,
+ -5.780885812264284e-07,
+ -6.032142093671319e-07,
+ -6.283398375078355e-07,
+ -6.53465465648539e-07,
+ -6.785910937892425e-07,
+ -7.037167219299461e-07,
+ -7.288423500706495e-07,
+ -7.53967978211353e-07,
+ -7.790936063520566e-07,
+ -8.042192344927601e-07,
+ -8.293448626334636e-07,
+ -8.544704907741672e-07,
+ -8.795961189148707e-07,
+ -9.047217470555742e-07,
+ -9.298473751962776e-07,
+ -9.549730033369812e-07,
+ -9.800986314776846e-07,
+ -1.0052242596183882e-06,
+ -1.0303498877590918e-06,
+ -1.0554755158997952e-06,
+ -1.0806011440404988e-06,
+ -1.1057267721812023e-06,
+ -1.1308524003219057e-06,
+ -1.1559780284626093e-06,
+ -1.181103656603313e-06,
+ -1.2062292847440163e-06,
+ -1.2313549128847199e-06,
+ -1.2564805410254235e-06,
+ -1.2816061691661269e-06,
+ -1.3067317973068304e-06,
+ -1.331857425447534e-06,
+ -1.3569830535882374e-06,
+ -1.382108681728941e-06,
+ -1.4072343098696446e-06,
+ -1.432359938010348e-06,
+ -1.4574855661510516e-06,
+ -1.4826111942917552e-06,
+ -1.5077368224324585e-06,
+ -1.5328624505731621e-06
+ ]
+ },
+ "P04": {
+ "contact": {
+ "deviation_from_baseline": 28,
+ "fit_constant_line": 82,
+ "fit_constant_polynomial": 51,
+ "fit_line_polynomial": 51
+ },
+ "force": [
+ 5.087939698492461e-11,
+ 5.0879396984924624e-11,
+ 5.087939698492461e-11,
+ 5.08793969849246e-11,
+ 5.087939698492461e-11,
+ 5.0879396984924624e-11,
+ 5.08793969849246e-11,
+ 5.0879396984924624e-11,
+ 5.08793969849246e-11,
+ 5.0879396984924624e-11,
+ 5.0879396984924624e-11,
+ 5.0879396984924624e-11,
+ 5.087939698492462e-11,
+ 5.08793969849246e-11,
+ 5.087939698492461e-11,
+ 5.087939698492463e-11,
+ 5.087939698492462e-11,
+ 5.0879396984924605e-11,
+ 5.087939698492462e-11,
+ 5.087939698492464e-11,
+ 5.087939698492459e-11,
+ 5.08793969849246e-11,
+ 5.087939698492462e-11,
+ 5.087939698492461e-11,
+ 5.087939698492462e-11,
+ 5.087939698492461e-11,
+ 5.087939698492463e-11,
+ 5.0879396984924624e-11,
+ 5.4648241206030165e-11,
+ 5.841708542713568e-11,
+ 6.218592964824122e-11,
+ 6.595477386934676e-11,
+ 6.972361809045225e-11,
+ 7.349246231155779e-11,
+ 7.726130653266333e-11,
+ 8.103015075376882e-11,
+ 8.479899497487437e-11,
+ 8.85678391959799e-11,
+ 9.233668341708545e-11,
+ 9.610552763819099e-11,
+ 9.987437185929648e-11,
+ 1.0364321608040202e-10,
+ 1.0741206030150756e-10,
+ 1.1118090452261305e-10,
+ 1.1494974874371859e-10,
+ 1.1871859296482413e-10,
+ 1.2248743718592962e-10,
+ 1.2625628140703516e-10,
+ 1.300251256281407e-10,
+ 1.3379396984924624e-10,
+ 1.3756281407035179e-10,
+ 1.4133165829145728e-10,
+ 1.4510050251256282e-10,
+ 1.4886934673366836e-10,
+ 1.5263819095477385e-10,
+ 1.564070351758794e-10,
+ 1.6017587939698493e-10,
+ 1.6394472361809042e-10,
+ 1.6771356783919596e-10,
+ 1.714824120603015e-10,
+ 1.7525125628140704e-10,
+ 1.98933499527269e-10,
+ 2.391125426711335e-10,
+ 2.900308455315351e-10,
+ 3.4963382536388e-10,
+ 4.1673404679910087e-10,
+ 4.905303215385751e-10,
+ 5.704344768716557e-10,
+ 6.559907936303728e-10,
+ 7.468326279397778e-10,
+ 8.426566672325199e-10,
+ 9.432065442367732e-10,
+ 1.0482618396291652e-09,
+ 1.1576303874387152e-09,
+ 1.2711427071714763e-09,
+ 1.3886478614369541e-09,
+ 1.510010301403501e-09,
+ 1.6351074162906155e-09,
+ 1.763827596844216e-09,
+ 1.8960686819373827e-09,
+ 2.0317366960008436e-09,
+ 2.1707448107955435e-09,
+ 2.31301248272786e-09,
+ 2.4584647292891503e-09,
+ 2.6070315170322207e-09,
+ 2.7586472399035866e-09,
+ 2.9132502714691433e-09,
+ 3.070782578095167e-09,
+ 3.23118938281244e-09,
+ 3.3944188716312528e-09,
+ 3.5604219356526993e-09,
+ 3.7291519435541036e-09,
+ 3.900564539997752e-09,
+ 4.074617466284217e-09,
+ 4.251270400190254e-09,
+ 4.430484812430578e-09,
+ 4.612223837588574e-09,
+ 4.7964521576929135e-09,
+ 4.98313589689025e-09,
+ 5.172242525890084e-09,
+ 5.363740775046087e-09,
+ 5.557600555095378e-09,
+ 5.753792884709534e-09,
+ 5.952289824122551e-09,
+ 6.153064414195687e-09,
+ 6.356090620359632e-09,
+ 6.561343280943208e-09,
+ 6.768798059456909e-09,
+ 6.9784314004504055e-09,
+ 7.190220488606998e-09,
+ 7.404143210776074e-09,
+ 7.620178120677716e-09,
+ 7.838304406042384e-09,
+ 8.05850185797383e-09,
+ 8.28075084234555e-09,
+ 8.50503227306037e-09,
+ 8.731327587020043e-09,
+ 8.959618720666781e-09,
+ 9.189888087971924e-09,
+ 9.42211855975909e-09,
+ 9.656293444259452e-09,
+ 9.892396468806327e-09,
+ 1.0130411762584716e-08,
+ 1.0370323840358817e-08,
+ 1.0612117587107401e-08,
+ 1.0855778243502927e-08,
+ 1.1101291392175776e-08,
+ 1.1348642944709799e-08,
+ 1.1597819129319899e-08,
+ 1.1848806479166366e-08,
+ 1.210159182126418e-08,
+ 1.235616226594898e-08,
+ 1.261250519686427e-08,
+ 1.287060826143711e-08,
+ 1.3130459361812105e-08,
+ 1.3392046646215752e-08,
+ 1.3655358500725195e-08,
+ 1.39203835414173e-08,
+ 1.4187110606875934e-08,
+ 1.4455528751036479e-08,
+ 1.472562723634846e-08,
+ 1.4997395527238314e-08,
+ 1.527082328385551e-08,
+ 1.5545900356086438e-08,
+ 1.582261677782163e-08,
+ 1.6100962761462447e-08,
+ 1.638092869265479e-08,
+ 1.666250512523768e-08,
+ 1.6945682776395743e-08,
+ 1.7230452522005036e-08,
+ 1.751680539216245e-08,
+ 1.780473256688947e-08,
+ 1.8094225372001646e-08,
+ 1.8385275275135654e-08,
+ 1.8677873881926304e-08,
+ 1.897201293232626e-08,
+ 1.9267684297061776e-08,
+ 1.956487997421792e-08,
+ 1.9863592085947435e-08,
+ 2.016381287529738e-08,
+ 2.0465534703148284e-08,
+ 2.076875004526072e-08,
+ 2.1073451489424485e-08,
+ 2.1379631732705785e-08,
+ 2.1687283578788325e-08,
+ 2.1996399935404012e-08,
+ 2.230697381184952e-08,
+ 2.26189983165851e-08,
+ 2.2932466654912084e-08,
+ 2.3247372126725826e-08,
+ 2.3563708124341047e-08,
+ 2.3881468130386433e-08,
+ 2.4200645715765845e-08,
+ 2.4521234537683356e-08,
+ 2.4843228337729635e-08,
+ 2.5166620940027114e-08,
+ 2.549140624943193e-08,
+ 2.5817578249790017e-08,
+ 2.614513100224565e-08,
+ 2.647405864360015e-08,
+ 2.680435538471907e-08,
+ 2.7136015508985844e-08,
+ 2.7469033370800356e-08,
+ 2.7803403394120586e-08,
+ 2.8139120071045878e-08,
+ 2.847617796044032e-08,
+ 2.881457168659467e-08,
+ 2.915429593792555e-08,
+ 2.9495345465710657e-08,
+ 2.983771508285847e-08,
+ 3.018139966271151e-08,
+ 3.052639413788189e-08,
+ 3.087269349911795e-08,
+ 3.122029279420105e-08,
+ 3.156918712687154e-08,
+ 3.191937165578268e-08,
+ 3.227084159348181e-08,
+ 3.2623592205417835e-08,
+ 3.297761880897401e-08,
+ 3.333291677252539e-08,
+ 3.333291677252539e-08,
+ 3.297761880897401e-08,
+ 3.2623592205417835e-08,
+ 3.227084159348181e-08,
+ 3.191937165578268e-08,
+ 3.156918712687154e-08,
+ 3.122029279420105e-08,
+ 3.087269349911795e-08,
+ 3.052639413788189e-08,
+ 3.018139966271151e-08,
+ 2.983771508285847e-08,
+ 2.9495345465710657e-08,
+ 2.915429593792555e-08,
+ 2.881457168659467e-08,
+ 2.847617796044032e-08,
+ 2.8139120071045878e-08,
+ 2.7803403394120586e-08,
+ 2.7469033370800356e-08,
+ 2.7136015508985844e-08,
+ 2.680435538471907e-08,
+ 2.647405864360015e-08,
+ 2.614513100224565e-08,
+ 2.5817578249790017e-08,
+ 2.549140624943193e-08,
+ 2.5166620940027114e-08,
+ 2.4843228337729635e-08,
+ 2.4521234537683356e-08,
+ 2.4200645715765845e-08,
+ 2.3881468130386433e-08,
+ 2.3563708124341047e-08,
+ 2.3247372126725826e-08,
+ 2.2932466654912084e-08,
+ 2.26189983165851e-08,
+ 2.230697381184952e-08,
+ 2.1996399935404012e-08,
+ 2.1687283578788325e-08,
+ 2.1379631732705785e-08,
+ 2.1073451489424485e-08,
+ 2.076875004526072e-08,
+ 2.0465534703148284e-08,
+ 2.016381287529738e-08,
+ 1.9863592085947435e-08,
+ 1.956487997421792e-08,
+ 1.9267684297061776e-08,
+ 1.897201293232626e-08,
+ 1.8677873881926304e-08,
+ 1.8385275275135654e-08,
+ 1.8094225372001646e-08,
+ 1.780473256688947e-08,
+ 1.751680539216245e-08,
+ 1.7230452522005036e-08,
+ 1.6945682776395743e-08,
+ 1.666250512523768e-08,
+ 1.638092869265479e-08,
+ 1.6100962761462447e-08,
+ 1.582261677782163e-08,
+ 1.5545900356086438e-08,
+ 1.527082328385551e-08,
+ 1.4997395527238314e-08,
+ 1.472562723634846e-08,
+ 1.4455528751036479e-08,
+ 1.4187110606875934e-08,
+ 1.39203835414173e-08,
+ 1.3655358500725195e-08,
+ 1.3392046646215752e-08,
+ 1.3130459361812105e-08,
+ 1.287060826143711e-08,
+ 1.261250519686427e-08,
+ 1.235616226594898e-08,
+ 1.210159182126418e-08,
+ 1.1848806479166366e-08,
+ 1.1597819129319899e-08,
+ 1.1348642944709799e-08,
+ 1.1101291392175776e-08,
+ 1.0855778243502927e-08,
+ 1.0612117587107401e-08,
+ 1.0370323840358817e-08,
+ 1.0130411762584716e-08,
+ 9.892396468806327e-09,
+ 9.656293444259452e-09,
+ 9.42211855975909e-09,
+ 9.189888087971924e-09,
+ 8.959618720666781e-09,
+ 8.731327587020043e-09,
+ 8.50503227306037e-09,
+ 8.28075084234555e-09,
+ 8.05850185797383e-09,
+ 7.838304406042384e-09,
+ 7.620178120677716e-09,
+ 7.404143210776074e-09,
+ 7.190220488606998e-09,
+ 6.9784314004504055e-09,
+ 6.768798059456909e-09,
+ 6.561343280943208e-09,
+ 6.356090620359632e-09,
+ 6.153064414195687e-09,
+ 5.952289824122551e-09,
+ 5.753792884709534e-09,
+ 5.557600555095378e-09,
+ 5.363740775046087e-09,
+ 5.172242525890084e-09,
+ 4.98313589689025e-09,
+ 4.7964521576929135e-09,
+ 4.612223837588574e-09,
+ 4.430484812430578e-09,
+ 4.251270400190254e-09,
+ 4.074617466284217e-09,
+ 3.900564539997752e-09,
+ 3.7291519435541036e-09,
+ 3.5604219356526993e-09,
+ 3.3944188716312528e-09,
+ 3.23118938281244e-09,
+ 3.070782578095167e-09,
+ 2.9132502714691433e-09,
+ 2.7586472399035866e-09,
+ 2.6070315170322207e-09,
+ 2.4584647292891503e-09,
+ 2.31301248272786e-09,
+ 2.1707448107955435e-09,
+ 2.0317366960008436e-09,
+ 1.8960686819373827e-09,
+ 1.763827596844216e-09,
+ 1.6351074162906155e-09,
+ 1.510010301403501e-09,
+ 1.3886478614369541e-09,
+ 1.2711427071714763e-09,
+ 1.1576303874387152e-09,
+ 1.0482618396291652e-09,
+ 9.432065442367732e-10,
+ 8.426566672325199e-10,
+ 7.468326279397778e-10,
+ 6.559907936303728e-10,
+ 5.704344768716557e-10,
+ 4.905303215385751e-10,
+ 4.1673404679910087e-10,
+ 3.4963382536388e-10,
+ 2.900308455315351e-10,
+ 2.391125426711335e-10,
+ 1.98933499527269e-10,
+ 1.7525125628140704e-10,
+ 1.714824120603015e-10,
+ 1.6771356783919596e-10,
+ 1.6394472361809042e-10,
+ 1.6017587939698493e-10,
+ 1.564070351758794e-10,
+ 1.5263819095477385e-10,
+ 1.4886934673366836e-10,
+ 1.4510050251256282e-10,
+ 1.4133165829145728e-10,
+ 1.3756281407035179e-10,
+ 1.3379396984924624e-10,
+ 1.300251256281407e-10,
+ 1.2625628140703516e-10,
+ 1.2248743718592962e-10,
+ 1.1871859296482413e-10,
+ 1.1494974874371859e-10,
+ 1.1118090452261305e-10,
+ 1.0741206030150756e-10,
+ 1.0364321608040202e-10,
+ 9.987437185929648e-11,
+ 9.610552763819099e-11,
+ 9.233668341708545e-11,
+ 8.85678391959799e-11,
+ 8.479899497487437e-11,
+ 8.103015075376882e-11,
+ 7.726130653266333e-11,
+ 7.349246231155779e-11,
+ 6.972361809045225e-11,
+ 6.595477386934676e-11,
+ 6.218592964824122e-11,
+ 5.841708542713568e-11,
+ 5.4648241206030165e-11,
+ 5.0879396984924624e-11,
+ 4.711055276381911e-11,
+ 4.334170854271357e-11,
+ 3.957286432160805e-11,
+ 3.580402010050251e-11,
+ 3.2035175879396996e-11,
+ 2.8266331658291455e-11,
+ 2.4497487437185914e-11,
+ 2.0728643216080424e-11,
+ 1.6959798994974883e-11,
+ 1.3190954773869342e-11,
+ 9.422110552763827e-12,
+ 5.653266331658312e-12,
+ 1.8844221105527706e-12,
+ -1.8844221105527706e-12,
+ -5.653266331658286e-12,
+ -9.422110552763801e-12,
+ -1.3190954773869342e-11,
+ -1.6959798994974858e-11,
+ -2.07286432160804e-11,
+ -2.4497487437185914e-11,
+ -2.8266331658291455e-11,
+ -3.203517587939697e-11,
+ -3.58040201005025e-11,
+ -3.9572864321608027e-11,
+ -4.3341708542713555e-11,
+ -4.711055276381908e-11,
+ -5.087939698492461e-11
+ ],
+ "height": [
+ 0.0,
+ 2.5125628140703518e-08,
+ 5.0251256281407036e-08,
+ 7.537688442211056e-08,
+ 1.0050251256281407e-07,
+ 1.2562814070351758e-07,
+ 1.5075376884422112e-07,
+ 1.7587939698492463e-07,
+ 2.0100502512562815e-07,
+ 2.2613065326633166e-07,
+ 2.5125628140703517e-07,
+ 2.763819095477387e-07,
+ 3.0150753768844224e-07,
+ 3.2663316582914573e-07,
+ 3.5175879396984927e-07,
+ 3.7688442211055275e-07,
+ 4.020100502512563e-07,
+ 4.2713567839195983e-07,
+ 4.522613065326633e-07,
+ 4.773869346733669e-07,
+ 5.025125628140703e-07,
+ 5.276381909547739e-07,
+ 5.527638190954774e-07,
+ 5.778894472361809e-07,
+ 6.030150753768845e-07,
+ 6.28140703517588e-07,
+ 6.532663316582915e-07,
+ 6.783919597989949e-07,
+ 7.035175879396985e-07,
+ 7.28643216080402e-07,
+ 7.537688442211055e-07,
+ 7.788944723618091e-07,
+ 8.040201005025126e-07,
+ 8.291457286432161e-07,
+ 8.542713567839197e-07,
+ 8.793969849246231e-07,
+ 9.045226130653266e-07,
+ 9.296482412060302e-07,
+ 9.547738693467337e-07,
+ 9.798994974874373e-07,
+ 1.0050251256281407e-06,
+ 1.0301507537688443e-06,
+ 1.0552763819095479e-06,
+ 1.0804020100502512e-06,
+ 1.1055276381909548e-06,
+ 1.1306532663316584e-06,
+ 1.1557788944723618e-06,
+ 1.1809045226130654e-06,
+ 1.206030150753769e-06,
+ 1.2311557788944724e-06,
+ 1.256281407035176e-06,
+ 1.2814070351758793e-06,
+ 1.306532663316583e-06,
+ 1.3316582914572865e-06,
+ 1.3567839195979899e-06,
+ 1.3819095477386935e-06,
+ 1.407035175879397e-06,
+ 1.4321608040201004e-06,
+ 1.457286432160804e-06,
+ 1.4824120603015076e-06,
+ 1.507537688442211e-06,
+ 1.5326633165829146e-06,
+ 1.5577889447236182e-06,
+ 1.5829145728643216e-06,
+ 1.6080402010050252e-06,
+ 1.6331658291457288e-06,
+ 1.6582914572864321e-06,
+ 1.6834170854271357e-06,
+ 1.7085427135678393e-06,
+ 1.7336683417085427e-06,
+ 1.7587939698492463e-06,
+ 1.7839195979899499e-06,
+ 1.8090452261306533e-06,
+ 1.8341708542713568e-06,
+ 1.8592964824120604e-06,
+ 1.8844221105527638e-06,
+ 1.9095477386934674e-06,
+ 1.9346733668341708e-06,
+ 1.9597989949748746e-06,
+ 1.984924623115578e-06,
+ 2.0100502512562813e-06,
+ 2.035175879396985e-06,
+ 2.0603015075376885e-06,
+ 2.085427135678392e-06,
+ 2.1105527638190957e-06,
+ 2.135678391959799e-06,
+ 2.1608040201005025e-06,
+ 2.1859296482412063e-06,
+ 2.2110552763819096e-06,
+ 2.236180904522613e-06,
+ 2.261306532663317e-06,
+ 2.28643216080402e-06,
+ 2.3115577889447236e-06,
+ 2.3366834170854274e-06,
+ 2.3618090452261308e-06,
+ 2.386934673366834e-06,
+ 2.412060301507538e-06,
+ 2.4371859296482413e-06,
+ 2.4623115577889447e-06,
+ 2.487437185929648e-06,
+ 2.512562814070352e-06,
+ 2.5376884422110553e-06,
+ 2.5628140703517587e-06,
+ 2.5879396984924625e-06,
+ 2.613065326633166e-06,
+ 2.6381909547738692e-06,
+ 2.663316582914573e-06,
+ 2.6884422110552764e-06,
+ 2.7135678391959798e-06,
+ 2.7386934673366836e-06,
+ 2.763819095477387e-06,
+ 2.7889447236180903e-06,
+ 2.814070351758794e-06,
+ 2.8391959798994975e-06,
+ 2.864321608040201e-06,
+ 2.8894472361809047e-06,
+ 2.914572864321608e-06,
+ 2.9396984924623115e-06,
+ 2.9648241206030153e-06,
+ 2.9899497487437186e-06,
+ 3.015075376884422e-06,
+ 3.040201005025126e-06,
+ 3.065326633165829e-06,
+ 3.0904522613065326e-06,
+ 3.1155778894472364e-06,
+ 3.1407035175879398e-06,
+ 3.165829145728643e-06,
+ 3.190954773869347e-06,
+ 3.2160804020100503e-06,
+ 3.2412060301507537e-06,
+ 3.2663316582914575e-06,
+ 3.291457286432161e-06,
+ 3.3165829145728643e-06,
+ 3.341708542713568e-06,
+ 3.3668341708542714e-06,
+ 3.391959798994975e-06,
+ 3.4170854271356786e-06,
+ 3.442211055276382e-06,
+ 3.4673366834170854e-06,
+ 3.492462311557789e-06,
+ 3.5175879396984926e-06,
+ 3.542713567839196e-06,
+ 3.5678391959798997e-06,
+ 3.592964824120603e-06,
+ 3.6180904522613065e-06,
+ 3.6432160804020103e-06,
+ 3.6683417085427137e-06,
+ 3.693467336683417e-06,
+ 3.718592964824121e-06,
+ 3.7437185929648243e-06,
+ 3.7688442211055276e-06,
+ 3.7939698492462314e-06,
+ 3.819095477386935e-06,
+ 3.844221105527638e-06,
+ 3.8693467336683416e-06,
+ 3.894472361809045e-06,
+ 3.919597989949749e-06,
+ 3.9447236180904526e-06,
+ 3.969849246231156e-06,
+ 3.994974874371859e-06,
+ 4.020100502512563e-06,
+ 4.045226130653266e-06,
+ 4.07035175879397e-06,
+ 4.095477386934674e-06,
+ 4.120603015075377e-06,
+ 4.1457286432160804e-06,
+ 4.170854271356784e-06,
+ 4.195979899497487e-06,
+ 4.221105527638191e-06,
+ 4.246231155778895e-06,
+ 4.271356783919598e-06,
+ 4.2964824120603016e-06,
+ 4.321608040201005e-06,
+ 4.346733668341708e-06,
+ 4.3718592964824125e-06,
+ 4.396984924623116e-06,
+ 4.422110552763819e-06,
+ 4.447236180904523e-06,
+ 4.472361809045226e-06,
+ 4.4974874371859294e-06,
+ 4.522613065326634e-06,
+ 4.547738693467337e-06,
+ 4.57286432160804e-06,
+ 4.597989949748744e-06,
+ 4.623115577889447e-06,
+ 4.6482412060301506e-06,
+ 4.673366834170855e-06,
+ 4.698492462311558e-06,
+ 4.7236180904522615e-06,
+ 4.748743718592965e-06,
+ 4.773869346733668e-06,
+ 4.798994974874372e-06,
+ 4.824120603015076e-06,
+ 4.849246231155779e-06,
+ 4.874371859296483e-06,
+ 4.899497487437186e-06,
+ 4.9246231155778894e-06,
+ 4.949748743718593e-06,
+ 4.974874371859296e-06,
+ 5e-06,
+ 5e-06,
+ 4.974874371859296e-06,
+ 4.949748743718593e-06,
+ 4.9246231155778894e-06,
+ 4.899497487437186e-06,
+ 4.874371859296483e-06,
+ 4.849246231155779e-06,
+ 4.824120603015076e-06,
+ 4.798994974874372e-06,
+ 4.773869346733668e-06,
+ 4.748743718592965e-06,
+ 4.7236180904522615e-06,
+ 4.698492462311558e-06,
+ 4.673366834170855e-06,
+ 4.6482412060301506e-06,
+ 4.623115577889447e-06,
+ 4.597989949748744e-06,
+ 4.57286432160804e-06,
+ 4.547738693467337e-06,
+ 4.522613065326634e-06,
+ 4.4974874371859294e-06,
+ 4.472361809045226e-06,
+ 4.447236180904523e-06,
+ 4.422110552763819e-06,
+ 4.396984924623116e-06,
+ 4.3718592964824125e-06,
+ 4.346733668341708e-06,
+ 4.321608040201005e-06,
+ 4.2964824120603016e-06,
+ 4.271356783919598e-06,
+ 4.246231155778895e-06,
+ 4.221105527638191e-06,
+ 4.195979899497487e-06,
+ 4.170854271356784e-06,
+ 4.1457286432160804e-06,
+ 4.120603015075377e-06,
+ 4.095477386934674e-06,
+ 4.07035175879397e-06,
+ 4.045226130653266e-06,
+ 4.020100502512563e-06,
+ 3.994974874371859e-06,
+ 3.969849246231156e-06,
+ 3.9447236180904526e-06,
+ 3.919597989949749e-06,
+ 3.894472361809045e-06,
+ 3.8693467336683416e-06,
+ 3.844221105527638e-06,
+ 3.819095477386935e-06,
+ 3.7939698492462314e-06,
+ 3.7688442211055276e-06,
+ 3.7437185929648243e-06,
+ 3.718592964824121e-06,
+ 3.693467336683417e-06,
+ 3.6683417085427137e-06,
+ 3.6432160804020103e-06,
+ 3.6180904522613065e-06,
+ 3.592964824120603e-06,
+ 3.5678391959798997e-06,
+ 3.542713567839196e-06,
+ 3.5175879396984926e-06,
+ 3.492462311557789e-06,
+ 3.4673366834170854e-06,
+ 3.442211055276382e-06,
+ 3.4170854271356786e-06,
+ 3.391959798994975e-06,
+ 3.3668341708542714e-06,
+ 3.341708542713568e-06,
+ 3.3165829145728643e-06,
+ 3.291457286432161e-06,
+ 3.2663316582914575e-06,
+ 3.2412060301507537e-06,
+ 3.2160804020100503e-06,
+ 3.190954773869347e-06,
+ 3.165829145728643e-06,
+ 3.1407035175879398e-06,
+ 3.1155778894472364e-06,
+ 3.0904522613065326e-06,
+ 3.065326633165829e-06,
+ 3.040201005025126e-06,
+ 3.015075376884422e-06,
+ 2.9899497487437186e-06,
+ 2.9648241206030153e-06,
+ 2.9396984924623115e-06,
+ 2.914572864321608e-06,
+ 2.8894472361809047e-06,
+ 2.864321608040201e-06,
+ 2.8391959798994975e-06,
+ 2.814070351758794e-06,
+ 2.7889447236180903e-06,
+ 2.763819095477387e-06,
+ 2.7386934673366836e-06,
+ 2.7135678391959798e-06,
+ 2.6884422110552764e-06,
+ 2.663316582914573e-06,
+ 2.6381909547738692e-06,
+ 2.613065326633166e-06,
+ 2.5879396984924625e-06,
+ 2.5628140703517587e-06,
+ 2.5376884422110553e-06,
+ 2.512562814070352e-06,
+ 2.487437185929648e-06,
+ 2.4623115577889447e-06,
+ 2.4371859296482413e-06,
+ 2.412060301507538e-06,
+ 2.386934673366834e-06,
+ 2.3618090452261308e-06,
+ 2.3366834170854274e-06,
+ 2.3115577889447236e-06,
+ 2.28643216080402e-06,
+ 2.261306532663317e-06,
+ 2.236180904522613e-06,
+ 2.2110552763819096e-06,
+ 2.1859296482412063e-06,
+ 2.1608040201005025e-06,
+ 2.135678391959799e-06,
+ 2.1105527638190957e-06,
+ 2.085427135678392e-06,
+ 2.0603015075376885e-06,
+ 2.035175879396985e-06,
+ 2.0100502512562813e-06,
+ 1.984924623115578e-06,
+ 1.9597989949748746e-06,
+ 1.9346733668341708e-06,
+ 1.9095477386934674e-06,
+ 1.8844221105527638e-06,
+ 1.8592964824120604e-06,
+ 1.8341708542713568e-06,
+ 1.8090452261306533e-06,
+ 1.7839195979899499e-06,
+ 1.7587939698492463e-06,
+ 1.7336683417085427e-06,
+ 1.7085427135678393e-06,
+ 1.6834170854271357e-06,
+ 1.6582914572864321e-06,
+ 1.6331658291457288e-06,
+ 1.6080402010050252e-06,
+ 1.5829145728643216e-06,
+ 1.5577889447236182e-06,
+ 1.5326633165829146e-06,
+ 1.507537688442211e-06,
+ 1.4824120603015076e-06,
+ 1.457286432160804e-06,
+ 1.4321608040201004e-06,
+ 1.407035175879397e-06,
+ 1.3819095477386935e-06,
+ 1.3567839195979899e-06,
+ 1.3316582914572865e-06,
+ 1.306532663316583e-06,
+ 1.2814070351758793e-06,
+ 1.256281407035176e-06,
+ 1.2311557788944724e-06,
+ 1.206030150753769e-06,
+ 1.1809045226130654e-06,
+ 1.1557788944723618e-06,
+ 1.1306532663316584e-06,
+ 1.1055276381909548e-06,
+ 1.0804020100502512e-06,
+ 1.0552763819095479e-06,
+ 1.0301507537688443e-06,
+ 1.0050251256281407e-06,
+ 9.798994974874373e-07,
+ 9.547738693467337e-07,
+ 9.296482412060302e-07,
+ 9.045226130653266e-07,
+ 8.793969849246231e-07,
+ 8.542713567839197e-07,
+ 8.291457286432161e-07,
+ 8.040201005025126e-07,
+ 7.788944723618091e-07,
+ 7.537688442211055e-07,
+ 7.28643216080402e-07,
+ 7.035175879396985e-07,
+ 6.783919597989949e-07,
+ 6.532663316582915e-07,
+ 6.28140703517588e-07,
+ 6.030150753768845e-07,
+ 5.778894472361809e-07,
+ 5.527638190954774e-07,
+ 5.276381909547739e-07,
+ 5.025125628140703e-07,
+ 4.773869346733669e-07,
+ 4.522613065326633e-07,
+ 4.2713567839195983e-07,
+ 4.020100502512563e-07,
+ 3.7688442211055275e-07,
+ 3.5175879396984927e-07,
+ 3.2663316582914573e-07,
+ 3.0150753768844224e-07,
+ 2.763819095477387e-07,
+ 2.5125628140703517e-07,
+ 2.2613065326633166e-07,
+ 2.0100502512562815e-07,
+ 1.7587939698492463e-07,
+ 1.5075376884422112e-07,
+ 1.2562814070351758e-07,
+ 1.0050251256281407e-07,
+ 7.537688442211056e-08,
+ 5.0251256281407036e-08,
+ 2.5125628140703518e-08,
+ 0.0
+ ],
+ "segment": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ "tip_position": [
+ -7.045728643216081e-07,
+ -6.794095477386934e-07,
+ -6.542462311557789e-07,
+ -6.290829145728643e-07,
+ -6.039195979899497e-07,
+ -5.787562814070352e-07,
+ -5.535929648241206e-07,
+ -5.28429648241206e-07,
+ -5.032663316582914e-07,
+ -4.781030150753769e-07,
+ -4.529396984924623e-07,
+ -4.277763819095477e-07,
+ -4.0261306532663314e-07,
+ -3.7744974874371856e-07,
+ -3.52286432160804e-07,
+ -3.2712311557788945e-07,
+ -3.0195979899497487e-07,
+ -2.767964824120603e-07,
+ -2.516331658291457e-07,
+ -2.2646984924623118e-07,
+ -2.0130653266331654e-07,
+ -1.761432160804019e-07,
+ -1.5097989949748738e-07,
+ -1.2581658291457285e-07,
+ -1.0065326633165822e-07,
+ -7.548994974874369e-08,
+ -5.032663316582916e-08,
+ -2.5163316582914634e-08,
+ 0.0,
+ 2.5163316582914634e-08,
+ 5.032663316582916e-08,
+ 7.54899497487438e-08,
+ 1.0065326633165832e-07,
+ 1.2581658291457285e-07,
+ 1.509798994974875e-07,
+ 1.7614321608040202e-07,
+ 2.0130653266331654e-07,
+ 2.2646984924623128e-07,
+ 2.516331658291458e-07,
+ 2.7679648241206045e-07,
+ 3.0195979899497487e-07,
+ 3.271231155778895e-07,
+ 3.5228643216080414e-07,
+ 3.7744974874371856e-07,
+ 4.026130653266332e-07,
+ 4.2777638190954783e-07,
+ 4.5293969849246225e-07,
+ 4.781030150753769e-07,
+ 5.032663316582915e-07,
+ 5.284296482412059e-07,
+ 5.535929648241206e-07,
+ 5.787562814070352e-07,
+ 6.039195979899498e-07,
+ 6.290829145728645e-07,
+ 6.542462311557789e-07,
+ 6.794095477386935e-07,
+ 7.045728643216082e-07,
+ 7.297361809045226e-07,
+ 7.548994974874372e-07,
+ 7.800628140703519e-07,
+ 8.052261306532663e-07,
+ 8.305885812264284e-07,
+ 8.561159997985708e-07,
+ 8.817508109678782e-07,
+ 9.074724689069052e-07,
+ 9.33269099261961e-07,
+ 9.591326901500591e-07,
+ 9.850573598440934e-07,
+ 1.011038551152384e-06,
+ 1.0370725976361817e-06,
+ 1.0631564661698128e-06,
+ 1.0892875930805589e-06,
+ 1.1154637741751861e-06,
+ 1.1416830877939851e-06,
+ 1.1679438391320166e-06,
+ 1.1942445188153745e-06,
+ 1.2205837713557436e-06,
+ 1.246960370645318e-06,
+ 1.2733732005915578e-06,
+ 1.299821239583193e-06,
+ 1.3263035478645307e-06,
+ 1.3528192571531815e-06,
+ 1.3793675620132084e-06,
+ 1.4059477126195244e-06,
+ 1.4325590086376591e-06,
+ 1.4592007940070762e-06,
+ 1.485872452463435e-06,
+ 1.512573403670399e-06,
+ 1.5393030998582753e-06,
+ 1.5660610228871666e-06,
+ 1.592846681668085e-06,
+ 1.6196596098878022e-06,
+ 1.6464993639929422e-06,
+ 1.6733655213965106e-06,
+ 1.7002576788762743e-06,
+ 1.7271754511393811e-06,
+ 1.7541184695316646e-06,
+ 1.7810863808734116e-06,
+ 1.8080788464060882e-06,
+ 1.8350955408367901e-06,
+ 1.8621361514690537e-06,
+ 1.8892003774102503e-06,
+ 1.9162879288470952e-06,
+ 1.943398526381929e-06,
+ 1.9705319004233637e-06,
+ 1.9976877906257065e-06,
+ 2.0248659453722463e-06,
+ 2.0520661212980866e-06,
+ 2.079288082848725e-06,
+ 2.1065316018709945e-06,
+ 2.133796457233389e-06,
+ 2.1610824344731085e-06,
+ 2.188389325467459e-06,
+ 2.215716928127477e-06,
+ 2.2430650461118975e-06,
+ 2.2704334885597497e-06,
+ 2.2978220698400497e-06,
+ 2.3252306093172204e-06,
+ 2.3526589311309756e-06,
+ 2.380106863989551e-06,
+ 2.407574240975258e-06,
+ 2.43506089936143e-06,
+ 2.4625666804399173e-06,
+ 2.4900914293583617e-06,
+ 2.5176349949665513e-06,
+ 2.5451972296712103e-06,
+ 2.572777989298642e-06,
+ 2.600377132964686e-06,
+ 2.6279945229514902e-06,
+ 2.6556300245906586e-06,
+ 2.6832835061523405e-06,
+ 2.7109548387398916e-06,
+ 2.738643896189748e-06,
+ 2.7663505549761804e-06,
+ 2.7940746941206335e-06,
+ 2.8218161951053732e-06,
+ 2.849574941791172e-06,
+ 2.8773508203387963e-06,
+ 2.905143719134086e-06,
+ 2.932953528716395e-06,
+ 2.9607801417102184e-06,
+ 2.9886234527598203e-06,
+ 3.016483358466696e-06,
+ 3.0443597573297085e-06,
+ 3.072252549687764e-06,
+ 3.100161637664876e-06,
+ 3.128086925117503e-06,
+ 3.1560283175840348e-06,
+ 3.1839857222363192e-06,
+ 3.211959047833116e-06,
+ 3.239948204675393e-06,
+ 3.267953104563367e-06,
+ 3.295973660755192e-06,
+ 3.3240097879272354e-06,
+ 3.352061402135846e-06,
+ 3.3801284207805483e-06,
+ 3.4082107625686082e-06,
+ 3.4363083474808732e-06,
+ 3.464421096738871e-06,
+ 3.492548932773074e-06,
+ 3.520691779192287e-06,
+ 3.5488495607541147e-06,
+ 3.5770222033364563e-06,
+ 3.6052096339099725e-06,
+ 3.6334117805115014e-06,
+ 3.661628572218362e-06,
+ 3.6898599391235204e-06,
+ 3.7181058123115793e-06,
+ 3.746366123835553e-06,
+ 3.774640806694394e-06,
+ 3.8029297948112495e-06,
+ 3.831233023012407e-06,
+ 3.859550427006905e-06,
+ 3.887881943366783e-06,
+ 3.91622750950795e-06,
+ 3.944587063671628e-06,
+ 3.97296054490638e-06,
+ 4.0013478930506645e-06,
+ 4.029749048715924e-06,
+ 4.0581639532701726e-06,
+ 4.0865925488220654e-06,
+ 4.115034778205437e-06,
+ 4.143490584964285e-06,
+ 4.171959913338191e-06,
+ 4.200442708248148e-06,
+ 4.228938915282796e-06,
+ 4.257448480685043e-06,
+ 4.2859713513390555e-06,
+ 4.31450747475761e-06,
+ 4.343056799069791e-06,
+ 4.371619273009025e-06,
+ 4.4001948459014324e-06,
+ 4.4287834676544965e-06,
+ 4.457385088746031e-06,
+ 4.4859996602134395e-06,
+ 4.514627133643255e-06,
+ 4.543267461160949e-06,
+ 4.571920595421012e-06,
+ 4.600586489597278e-06,
+ 4.629265097373496e-06,
+ 4.629265097373496e-06,
+ 4.600586489597278e-06,
+ 4.571920595421012e-06,
+ 4.543267461160949e-06,
+ 4.514627133643255e-06,
+ 4.4859996602134395e-06,
+ 4.457385088746031e-06,
+ 4.4287834676544965e-06,
+ 4.4001948459014324e-06,
+ 4.371619273009025e-06,
+ 4.343056799069791e-06,
+ 4.31450747475761e-06,
+ 4.2859713513390555e-06,
+ 4.257448480685043e-06,
+ 4.228938915282796e-06,
+ 4.200442708248148e-06,
+ 4.171959913338191e-06,
+ 4.143490584964285e-06,
+ 4.115034778205437e-06,
+ 4.0865925488220654e-06,
+ 4.0581639532701726e-06,
+ 4.029749048715924e-06,
+ 4.0013478930506645e-06,
+ 3.97296054490638e-06,
+ 3.944587063671628e-06,
+ 3.91622750950795e-06,
+ 3.887881943366783e-06,
+ 3.859550427006905e-06,
+ 3.831233023012407e-06,
+ 3.8029297948112495e-06,
+ 3.774640806694394e-06,
+ 3.746366123835553e-06,
+ 3.7181058123115793e-06,
+ 3.6898599391235204e-06,
+ 3.661628572218362e-06,
+ 3.6334117805115014e-06,
+ 3.6052096339099725e-06,
+ 3.5770222033364563e-06,
+ 3.5488495607541147e-06,
+ 3.520691779192287e-06,
+ 3.492548932773074e-06,
+ 3.464421096738871e-06,
+ 3.4363083474808732e-06,
+ 3.4082107625686082e-06,
+ 3.3801284207805483e-06,
+ 3.352061402135846e-06,
+ 3.3240097879272354e-06,
+ 3.295973660755192e-06,
+ 3.267953104563367e-06,
+ 3.239948204675393e-06,
+ 3.211959047833116e-06,
+ 3.1839857222363192e-06,
+ 3.1560283175840348e-06,
+ 3.128086925117503e-06,
+ 3.100161637664876e-06,
+ 3.072252549687764e-06,
+ 3.0443597573297085e-06,
+ 3.016483358466696e-06,
+ 2.9886234527598203e-06,
+ 2.9607801417102184e-06,
+ 2.932953528716395e-06,
+ 2.905143719134086e-06,
+ 2.8773508203387963e-06,
+ 2.849574941791172e-06,
+ 2.8218161951053732e-06,
+ 2.7940746941206335e-06,
+ 2.7663505549761804e-06,
+ 2.738643896189748e-06,
+ 2.7109548387398916e-06,
+ 2.6832835061523405e-06,
+ 2.6556300245906586e-06,
+ 2.6279945229514902e-06,
+ 2.600377132964686e-06,
+ 2.572777989298642e-06,
+ 2.5451972296712103e-06,
+ 2.5176349949665513e-06,
+ 2.4900914293583617e-06,
+ 2.4625666804399173e-06,
+ 2.43506089936143e-06,
+ 2.407574240975258e-06,
+ 2.380106863989551e-06,
+ 2.3526589311309756e-06,
+ 2.3252306093172204e-06,
+ 2.2978220698400497e-06,
+ 2.2704334885597497e-06,
+ 2.2430650461118975e-06,
+ 2.215716928127477e-06,
+ 2.188389325467459e-06,
+ 2.1610824344731085e-06,
+ 2.133796457233389e-06,
+ 2.1065316018709945e-06,
+ 2.079288082848725e-06,
+ 2.0520661212980866e-06,
+ 2.0248659453722463e-06,
+ 1.9976877906257065e-06,
+ 1.9705319004233637e-06,
+ 1.943398526381929e-06,
+ 1.9162879288470952e-06,
+ 1.8892003774102503e-06,
+ 1.8621361514690537e-06,
+ 1.8350955408367901e-06,
+ 1.8080788464060882e-06,
+ 1.7810863808734116e-06,
+ 1.7541184695316646e-06,
+ 1.7271754511393811e-06,
+ 1.7002576788762743e-06,
+ 1.6733655213965106e-06,
+ 1.6464993639929422e-06,
+ 1.6196596098878022e-06,
+ 1.592846681668085e-06,
+ 1.5660610228871666e-06,
+ 1.5393030998582753e-06,
+ 1.512573403670399e-06,
+ 1.485872452463435e-06,
+ 1.4592007940070762e-06,
+ 1.4325590086376591e-06,
+ 1.4059477126195244e-06,
+ 1.3793675620132084e-06,
+ 1.3528192571531815e-06,
+ 1.3263035478645307e-06,
+ 1.299821239583193e-06,
+ 1.2733732005915578e-06,
+ 1.246960370645318e-06,
+ 1.2205837713557436e-06,
+ 1.1942445188153745e-06,
+ 1.1679438391320166e-06,
+ 1.1416830877939851e-06,
+ 1.1154637741751861e-06,
+ 1.0892875930805589e-06,
+ 1.0631564661698128e-06,
+ 1.0370725976361817e-06,
+ 1.011038551152384e-06,
+ 9.850573598440934e-07,
+ 9.591326901500591e-07,
+ 9.33269099261961e-07,
+ 9.074724689069052e-07,
+ 8.817508109678782e-07,
+ 8.561159997985708e-07,
+ 8.305885812264284e-07,
+ 8.052261306532663e-07,
+ 7.800628140703519e-07,
+ 7.548994974874372e-07,
+ 7.297361809045226e-07,
+ 7.045728643216082e-07,
+ 6.794095477386935e-07,
+ 6.542462311557789e-07,
+ 6.290829145728645e-07,
+ 6.039195979899498e-07,
+ 5.787562814070352e-07,
+ 5.535929648241206e-07,
+ 5.284296482412059e-07,
+ 5.032663316582915e-07,
+ 4.781030150753769e-07,
+ 4.5293969849246225e-07,
+ 4.2777638190954783e-07,
+ 4.026130653266332e-07,
+ 3.7744974874371856e-07,
+ 3.5228643216080414e-07,
+ 3.271231155778895e-07,
+ 3.0195979899497487e-07,
+ 2.7679648241206045e-07,
+ 2.516331658291458e-07,
+ 2.2646984924623128e-07,
+ 2.0130653266331654e-07,
+ 1.7614321608040202e-07,
+ 1.509798994974875e-07,
+ 1.2581658291457285e-07,
+ 1.0065326633165832e-07,
+ 7.54899497487438e-08,
+ 5.032663316582916e-08,
+ 2.5163316582914634e-08,
+ 0.0,
+ -2.5163316582914634e-08,
+ -5.032663316582916e-08,
+ -7.548994974874369e-08,
+ -1.0065326633165822e-07,
+ -1.2581658291457285e-07,
+ -1.5097989949748738e-07,
+ -1.761432160804019e-07,
+ -2.0130653266331654e-07,
+ -2.2646984924623118e-07,
+ -2.516331658291457e-07,
+ -2.767964824120603e-07,
+ -3.0195979899497487e-07,
+ -3.2712311557788945e-07,
+ -3.52286432160804e-07,
+ -3.7744974874371856e-07,
+ -4.0261306532663314e-07,
+ -4.277763819095477e-07,
+ -4.529396984924623e-07,
+ -4.781030150753769e-07,
+ -5.032663316582914e-07,
+ -5.28429648241206e-07,
+ -5.535929648241206e-07,
+ -5.787562814070352e-07,
+ -6.039195979899497e-07,
+ -6.290829145728643e-07,
+ -6.542462311557789e-07,
+ -6.794095477386934e-07,
+ -7.045728643216081e-07
+ ]
+ },
+ "P05": {
+ "contact": {
+ "deviation_from_baseline": 62,
+ "fit_constant_line": 84,
+ "fit_constant_polynomial": 53,
+ "fit_line_polynomial": 52
+ },
+ "force": [
+ -5.871428023723087e-11,
+ -5.945255095257646e-11,
+ -6.019082166792205e-11,
+ -6.092909238326762e-11,
+ -6.16673630986132e-11,
+ -6.240563381395879e-11,
+ -6.314390452930438e-11,
+ -6.388217524465e-11,
+ -6.462044595999553e-11,
+ -6.535871667534117e-11,
+ -6.609698739068674e-11,
+ -6.683525810603234e-11,
+ -6.757352882137791e-11,
+ -6.83117995367235e-11,
+ -6.90500702520691e-11,
+ -6.978834096741466e-11,
+ -7.052661168276026e-11,
+ -7.126488239810583e-11,
+ -7.200315311345143e-11,
+ -7.2741423828797e-11,
+ -7.34796945441426e-11,
+ -7.42179652594882e-11,
+ -7.495623597483376e-11,
+ -7.569450669017936e-11,
+ -7.643277740552494e-11,
+ -7.717104812087053e-11,
+ -7.79093188362161e-11,
+ -7.864758955156171e-11,
+ -7.93858602669073e-11,
+ -8.012413098225287e-11,
+ -8.086240169759845e-11,
+ -8.160067241294404e-11,
+ -8.233894312828963e-11,
+ -8.307721384363521e-11,
+ -8.38154845589808e-11,
+ -8.455375527432638e-11,
+ -8.529202598967197e-11,
+ -8.603029670501757e-11,
+ -8.676856742036317e-11,
+ -8.750683813570875e-11,
+ -8.824510885105431e-11,
+ -8.89833795663999e-11,
+ -8.972165028174551e-11,
+ -9.045992099709107e-11,
+ -9.119819171243665e-11,
+ -9.193646242778225e-11,
+ -9.267473314312785e-11,
+ -9.341300385847342e-11,
+ -9.415127457381901e-11,
+ -9.48895452891646e-11,
+ -9.562781600451019e-11,
+ -9.636608671985576e-11,
+ -9.710435743520135e-11,
+ -9.784262815054694e-11,
+ -9.858089886589255e-11,
+ -9.931916958123811e-11,
+ -1.000574402965837e-10,
+ -1.0079571101192928e-10,
+ -1.0153398172727486e-10,
+ -1.0227225244262046e-10,
+ -1.0301052315796605e-10,
+ -8.38173282786201e-11,
+ -4.811236666468621e-11,
+ -1.6584020660528977e-12,
+ 5.348811873818341e-11,
+ 1.161386830699482e-10,
+ 1.8315103821143259e-10,
+ 2.562712739465232e-10,
+ 3.3504367110725024e-10,
+ 4.1910158581866526e-10,
+ 5.081417055134175e-10,
+ 6.019076629196807e-10,
+ 7.001790387140828e-10,
+ 8.027636669256431e-10,
+ 9.09492067060414e-10,
+ 1.020213301727902e-09,
+ 1.1347918220964589e-09,
+ 1.2531050173855834e-09,
+ 1.375041278341194e-09,
+ 1.500498443836371e-09,
+ 1.6293825383018418e-09,
+ 1.7616067334985515e-09,
+ 1.897090485832878e-09,
+ 2.035758812796179e-09,
+ 2.177541680941259e-09,
+ 2.322373484214635e-09,
+ 2.4701925961822018e-09,
+ 2.6209409832102355e-09,
+ 2.7745638683295187e-09,
+ 2.931009437550341e-09,
+ 3.090228581973798e-09,
+ 3.2521746702772117e-09,
+ 3.4168033471228705e-09,
+ 3.584072353811346e-09,
+ 3.7539413681193925e-09,
+ 3.9263718607617274e-09,
+ 4.101326966321733e-09,
+ 4.278771366828083e-09,
+ 4.458671186427429e-09,
+ 4.640993895829273e-09,
+ 4.825708225387286e-09,
+ 5.012784085838587e-09,
+ 5.202192495854753e-09,
+ 5.393905515669781e-09,
+ 5.587896186144927e-09,
+ 5.784138472710882e-09,
+ 5.9826072136964676e-09,
+ 6.183278072612178e-09,
+ 6.386127494007685e-09,
+ 6.591132662566288e-09,
+ 6.7982714651373725e-09,
+ 7.007522455441024e-09,
+ 7.218864821207704e-09,
+ 7.4322783535411596e-09,
+ 7.64774341831489e-09,
+ 7.86524092943172e-09,
+ 8.084752323793405e-09,
+ 8.306259537842152e-09,
+ 8.529744985549306e-09,
+ 8.755191537738482e-09,
+ 8.982582502640851e-09,
+ 9.211901607589738e-09,
+ 9.443132981770137e-09,
+ 9.676261139946248e-09,
+ 9.911270967096842e-09,
+ 1.014814770389438e-08,
+ 1.0386876932969237e-08,
+ 1.062744456590527e-08,
+ 1.0869836830917382e-08,
+ 1.1114040261165858e-08,
+ 1.1360041683665682e-08,
+ 1.1607828208752491e-08,
+ 1.1857387220069791e-08,
+ 1.2108706365044641e-08,
+ 1.2361773545821646e-08,
+ 1.2616576910627304e-08,
+ 1.2873104845538755e-08,
+ 1.313134596663287e-08,
+ 1.3391289112493514e-08,
+ 1.3652923337056069e-08,
+ 1.391623790277006e-08,
+ 1.4181222274061925e-08,
+ 1.4447866111081128e-08,
+ 1.4716159263714068e-08,
+ 1.498609176585127e-08,
+ 1.5257653829894098e-08,
+ 1.553083584148845e-08,
+ 1.580562835447335e-08,
+ 1.6082022086033426e-08,
+ 1.636000791204473e-08,
+ 1.6639576862604152e-08,
+ 1.6920720117733182e-08,
+ 1.720342900324737e-08,
+ 1.7487694986783387e-08,
+ 1.7773509673976046e-08,
+ 1.8060864804778012e-08,
+ 1.834975224991554e-08,
+ 1.8640164007473692e-08,
+ 1.8932092199605216e-08,
+ 1.922552906935717e-08,
+ 1.9520466977610086e-08,
+ 1.9816898400124532e-08,
+ 2.0114815924690307e-08,
+ 2.0414212248373616e-08,
+ 2.0715080174858166e-08,
+ 2.1017412611875863e-08,
+ 2.132120256872338e-08,
+ 2.162644315386097e-08,
+ 2.1933127572589964e-08,
+ 2.2241249124805716e-08,
+ 2.255080120282295e-08,
+ 2.2861777289270346e-08,
+ 2.3174170955051768e-08,
+ 2.348797585737129e-08,
+ 2.3803185737819577e-08,
+ 2.4119794420519066e-08,
+ 2.4437795810325895e-08,
+ 2.4757183891085993e-08,
+ 2.5077952723943634e-08,
+ 2.5400096445700144e-08,
+ 2.5723609267221074e-08,
+ 2.604848547188986e-08,
+ 2.637471941410638e-08,
+ 2.670230551782862e-08,
+ 2.7031238275155922e-08,
+ 2.7361512244952373e-08,
+ 2.7693122051508733e-08,
+ 2.8026062383241624e-08,
+ 2.836032799142874e-08,
+ 2.869591368897856e-08,
+ 2.903281434923362e-08,
+ 2.9371024904806004e-08,
+ 2.9710540346444075e-08,
+ 3.005135572192919e-08,
+ 3.039346613500169e-08,
+ 3.073686674431484e-08,
+ 3.108155276241598e-08,
+ 3.1427519454754014e-08,
+ 3.17747621387122e-08,
+ 3.212327618266559e-08,
+ 3.212327618266559e-08,
+ 3.17747621387122e-08,
+ 3.1427519454754014e-08,
+ 3.108155276241598e-08,
+ 3.073686674431484e-08,
+ 3.039346613500169e-08,
+ 3.005135572192919e-08,
+ 2.9710540346444075e-08,
+ 2.9371024904806004e-08,
+ 2.903281434923362e-08,
+ 2.869591368897856e-08,
+ 2.836032799142874e-08,
+ 2.8026062383241624e-08,
+ 2.7693122051508733e-08,
+ 2.7361512244952373e-08,
+ 2.7031238275155922e-08,
+ 2.670230551782862e-08,
+ 2.637471941410638e-08,
+ 2.604848547188986e-08,
+ 2.5723609267221074e-08,
+ 2.5400096445700144e-08,
+ 2.5077952723943634e-08,
+ 2.4757183891085993e-08,
+ 2.4437795810325895e-08,
+ 2.4119794420519066e-08,
+ 2.3803185737819577e-08,
+ 2.348797585737129e-08,
+ 2.3174170955051768e-08,
+ 2.2861777289270346e-08,
+ 2.255080120282295e-08,
+ 2.2241249124805716e-08,
+ 2.1933127572589964e-08,
+ 2.162644315386097e-08,
+ 2.132120256872338e-08,
+ 2.1017412611875863e-08,
+ 2.0715080174858166e-08,
+ 2.0414212248373616e-08,
+ 2.0114815924690307e-08,
+ 1.9816898400124532e-08,
+ 1.9520466977610086e-08,
+ 1.922552906935717e-08,
+ 1.8932092199605216e-08,
+ 1.8640164007473692e-08,
+ 1.834975224991554e-08,
+ 1.8060864804778012e-08,
+ 1.7773509673976046e-08,
+ 1.7487694986783387e-08,
+ 1.720342900324737e-08,
+ 1.6920720117733182e-08,
+ 1.6639576862604152e-08,
+ 1.636000791204473e-08,
+ 1.6082022086033426e-08,
+ 1.580562835447335e-08,
+ 1.553083584148845e-08,
+ 1.5257653829894098e-08,
+ 1.498609176585127e-08,
+ 1.4716159263714068e-08,
+ 1.4447866111081128e-08,
+ 1.4181222274061925e-08,
+ 1.391623790277006e-08,
+ 1.3652923337056069e-08,
+ 1.3391289112493514e-08,
+ 1.313134596663287e-08,
+ 1.2873104845538755e-08,
+ 1.2616576910627304e-08,
+ 1.2361773545821646e-08,
+ 1.2108706365044641e-08,
+ 1.1857387220069791e-08,
+ 1.1607828208752491e-08,
+ 1.1360041683665682e-08,
+ 1.1114040261165858e-08,
+ 1.0869836830917382e-08,
+ 1.062744456590527e-08,
+ 1.0386876932969237e-08,
+ 1.014814770389438e-08,
+ 9.911270967096842e-09,
+ 9.676261139946248e-09,
+ 9.443132981770137e-09,
+ 9.211901607589738e-09,
+ 8.982582502640851e-09,
+ 8.755191537738482e-09,
+ 8.529744985549306e-09,
+ 8.306259537842152e-09,
+ 8.084752323793405e-09,
+ 7.86524092943172e-09,
+ 7.64774341831489e-09,
+ 7.4322783535411596e-09,
+ 7.218864821207704e-09,
+ 7.007522455441024e-09,
+ 6.7982714651373725e-09,
+ 6.591132662566288e-09,
+ 6.386127494007685e-09,
+ 6.183278072612178e-09,
+ 5.9826072136964676e-09,
+ 5.784138472710882e-09,
+ 5.587896186144927e-09,
+ 5.393905515669781e-09,
+ 5.202192495854753e-09,
+ 5.012784085838587e-09,
+ 4.825708225387286e-09,
+ 4.640993895829273e-09,
+ 4.458671186427429e-09,
+ 4.278771366828083e-09,
+ 4.101326966321733e-09,
+ 3.9263718607617274e-09,
+ 3.7539413681193925e-09,
+ 3.584072353811346e-09,
+ 3.4168033471228705e-09,
+ 3.2521746702772117e-09,
+ 3.090228581973798e-09,
+ 2.931009437550341e-09,
+ 2.7745638683295187e-09,
+ 2.6209409832102355e-09,
+ 2.4701925961822018e-09,
+ 2.322373484214635e-09,
+ 2.177541680941259e-09,
+ 2.035758812796179e-09,
+ 1.897090485832878e-09,
+ 1.7616067334985515e-09,
+ 1.6293825383018418e-09,
+ 1.500498443836371e-09,
+ 1.375041278341194e-09,
+ 1.2531050173855834e-09,
+ 1.1347918220964589e-09,
+ 1.020213301727902e-09,
+ 9.09492067060414e-10,
+ 8.027636669256431e-10,
+ 7.001790387140828e-10,
+ 6.019076629196807e-10,
+ 5.081417055134175e-10,
+ 4.1910158581866526e-10,
+ 3.3504367110725024e-10,
+ 2.562712739465232e-10,
+ 1.8315103821143259e-10,
+ 1.161386830699482e-10,
+ 5.582238123271724e-11,
+ 3.0033209983623637e-12,
+ -4.113106226404934e-11,
+ -7.452618580992388e-11,
+ -9.142450945779585e-11,
+ -8.840943408091144e-11,
+ -8.5394358704027e-11,
+ -8.237928332714259e-11,
+ -7.936420795025818e-11,
+ -7.634913257337374e-11,
+ -7.333405719648933e-11,
+ -7.031898181960491e-11,
+ -6.730390644272048e-11,
+ -6.428883106583604e-11,
+ -6.127375568895165e-11,
+ -5.825868031206721e-11,
+ -5.52436049351828e-11,
+ -5.222852955829836e-11,
+ -4.921345418141395e-11,
+ -4.619837880452954e-11,
+ -4.31833034276451e-11,
+ -4.016822805076066e-11,
+ -3.7153152673876276e-11,
+ -3.413807729699184e-11,
+ -3.11230019201074e-11,
+ -2.8107926543223013e-11,
+ -2.5092851166338575e-11,
+ -2.207777578945415e-11,
+ -1.9062700412569725e-11,
+ -1.60476250356853e-11,
+ -1.3032549658800888e-11,
+ -1.001747428191645e-11,
+ -7.002398905032037e-12,
+ -3.987323528147612e-12,
+ -9.72248151263187e-13,
+ 2.042827225621238e-12,
+ 5.0579026025056504e-12,
+ 8.072977979390076e-12,
+ 1.10880533562745e-11,
+ 1.4103128733158913e-11,
+ 1.7118204110043338e-11,
+ 2.0133279486927763e-11,
+ 2.3148354863812188e-11,
+ 2.61634302406966e-11,
+ 2.917850561758104e-11,
+ 3.219358099446545e-11,
+ 3.5208656371349876e-11,
+ 3.8223731748234295e-11,
+ 4.123880712511872e-11,
+ 4.4253882502003145e-11,
+ 4.7268957878887564e-11,
+ 5.028403325577199e-11,
+ 5.3299108632656414e-11,
+ 5.6314184009540826e-11,
+ 5.932925938642526e-11,
+ 6.234433476330968e-11,
+ 6.53594101401941e-11,
+ 6.837448551707853e-11,
+ 7.138956089396294e-11,
+ 7.440463627084736e-11,
+ 7.741971164773179e-11,
+ 8.043478702461621e-11,
+ 8.344986240150063e-11,
+ 8.646493777838505e-11,
+ 8.948001315526948e-11
+ ],
+ "height": [
+ 0.0,
+ 2.5125628140703518e-08,
+ 5.0251256281407036e-08,
+ 7.537688442211056e-08,
+ 1.0050251256281407e-07,
+ 1.2562814070351758e-07,
+ 1.5075376884422112e-07,
+ 1.7587939698492463e-07,
+ 2.0100502512562815e-07,
+ 2.2613065326633166e-07,
+ 2.5125628140703517e-07,
+ 2.763819095477387e-07,
+ 3.0150753768844224e-07,
+ 3.2663316582914573e-07,
+ 3.5175879396984927e-07,
+ 3.7688442211055275e-07,
+ 4.020100502512563e-07,
+ 4.2713567839195983e-07,
+ 4.522613065326633e-07,
+ 4.773869346733669e-07,
+ 5.025125628140703e-07,
+ 5.276381909547739e-07,
+ 5.527638190954774e-07,
+ 5.778894472361809e-07,
+ 6.030150753768845e-07,
+ 6.28140703517588e-07,
+ 6.532663316582915e-07,
+ 6.783919597989949e-07,
+ 7.035175879396985e-07,
+ 7.28643216080402e-07,
+ 7.537688442211055e-07,
+ 7.788944723618091e-07,
+ 8.040201005025126e-07,
+ 8.291457286432161e-07,
+ 8.542713567839197e-07,
+ 8.793969849246231e-07,
+ 9.045226130653266e-07,
+ 9.296482412060302e-07,
+ 9.547738693467337e-07,
+ 9.798994974874373e-07,
+ 1.0050251256281407e-06,
+ 1.0301507537688443e-06,
+ 1.0552763819095479e-06,
+ 1.0804020100502512e-06,
+ 1.1055276381909548e-06,
+ 1.1306532663316584e-06,
+ 1.1557788944723618e-06,
+ 1.1809045226130654e-06,
+ 1.206030150753769e-06,
+ 1.2311557788944724e-06,
+ 1.256281407035176e-06,
+ 1.2814070351758793e-06,
+ 1.306532663316583e-06,
+ 1.3316582914572865e-06,
+ 1.3567839195979899e-06,
+ 1.3819095477386935e-06,
+ 1.407035175879397e-06,
+ 1.4321608040201004e-06,
+ 1.457286432160804e-06,
+ 1.4824120603015076e-06,
+ 1.507537688442211e-06,
+ 1.5326633165829146e-06,
+ 1.5577889447236182e-06,
+ 1.5829145728643216e-06,
+ 1.6080402010050252e-06,
+ 1.6331658291457288e-06,
+ 1.6582914572864321e-06,
+ 1.6834170854271357e-06,
+ 1.7085427135678393e-06,
+ 1.7336683417085427e-06,
+ 1.7587939698492463e-06,
+ 1.7839195979899499e-06,
+ 1.8090452261306533e-06,
+ 1.8341708542713568e-06,
+ 1.8592964824120604e-06,
+ 1.8844221105527638e-06,
+ 1.9095477386934674e-06,
+ 1.9346733668341708e-06,
+ 1.9597989949748746e-06,
+ 1.984924623115578e-06,
+ 2.0100502512562813e-06,
+ 2.035175879396985e-06,
+ 2.0603015075376885e-06,
+ 2.085427135678392e-06,
+ 2.1105527638190957e-06,
+ 2.135678391959799e-06,
+ 2.1608040201005025e-06,
+ 2.1859296482412063e-06,
+ 2.2110552763819096e-06,
+ 2.236180904522613e-06,
+ 2.261306532663317e-06,
+ 2.28643216080402e-06,
+ 2.3115577889447236e-06,
+ 2.3366834170854274e-06,
+ 2.3618090452261308e-06,
+ 2.386934673366834e-06,
+ 2.412060301507538e-06,
+ 2.4371859296482413e-06,
+ 2.4623115577889447e-06,
+ 2.487437185929648e-06,
+ 2.512562814070352e-06,
+ 2.5376884422110553e-06,
+ 2.5628140703517587e-06,
+ 2.5879396984924625e-06,
+ 2.613065326633166e-06,
+ 2.6381909547738692e-06,
+ 2.663316582914573e-06,
+ 2.6884422110552764e-06,
+ 2.7135678391959798e-06,
+ 2.7386934673366836e-06,
+ 2.763819095477387e-06,
+ 2.7889447236180903e-06,
+ 2.814070351758794e-06,
+ 2.8391959798994975e-06,
+ 2.864321608040201e-06,
+ 2.8894472361809047e-06,
+ 2.914572864321608e-06,
+ 2.9396984924623115e-06,
+ 2.9648241206030153e-06,
+ 2.9899497487437186e-06,
+ 3.015075376884422e-06,
+ 3.040201005025126e-06,
+ 3.065326633165829e-06,
+ 3.0904522613065326e-06,
+ 3.1155778894472364e-06,
+ 3.1407035175879398e-06,
+ 3.165829145728643e-06,
+ 3.190954773869347e-06,
+ 3.2160804020100503e-06,
+ 3.2412060301507537e-06,
+ 3.2663316582914575e-06,
+ 3.291457286432161e-06,
+ 3.3165829145728643e-06,
+ 3.341708542713568e-06,
+ 3.3668341708542714e-06,
+ 3.391959798994975e-06,
+ 3.4170854271356786e-06,
+ 3.442211055276382e-06,
+ 3.4673366834170854e-06,
+ 3.492462311557789e-06,
+ 3.5175879396984926e-06,
+ 3.542713567839196e-06,
+ 3.5678391959798997e-06,
+ 3.592964824120603e-06,
+ 3.6180904522613065e-06,
+ 3.6432160804020103e-06,
+ 3.6683417085427137e-06,
+ 3.693467336683417e-06,
+ 3.718592964824121e-06,
+ 3.7437185929648243e-06,
+ 3.7688442211055276e-06,
+ 3.7939698492462314e-06,
+ 3.819095477386935e-06,
+ 3.844221105527638e-06,
+ 3.8693467336683416e-06,
+ 3.894472361809045e-06,
+ 3.919597989949749e-06,
+ 3.9447236180904526e-06,
+ 3.969849246231156e-06,
+ 3.994974874371859e-06,
+ 4.020100502512563e-06,
+ 4.045226130653266e-06,
+ 4.07035175879397e-06,
+ 4.095477386934674e-06,
+ 4.120603015075377e-06,
+ 4.1457286432160804e-06,
+ 4.170854271356784e-06,
+ 4.195979899497487e-06,
+ 4.221105527638191e-06,
+ 4.246231155778895e-06,
+ 4.271356783919598e-06,
+ 4.2964824120603016e-06,
+ 4.321608040201005e-06,
+ 4.346733668341708e-06,
+ 4.3718592964824125e-06,
+ 4.396984924623116e-06,
+ 4.422110552763819e-06,
+ 4.447236180904523e-06,
+ 4.472361809045226e-06,
+ 4.4974874371859294e-06,
+ 4.522613065326634e-06,
+ 4.547738693467337e-06,
+ 4.57286432160804e-06,
+ 4.597989949748744e-06,
+ 4.623115577889447e-06,
+ 4.6482412060301506e-06,
+ 4.673366834170855e-06,
+ 4.698492462311558e-06,
+ 4.7236180904522615e-06,
+ 4.748743718592965e-06,
+ 4.773869346733668e-06,
+ 4.798994974874372e-06,
+ 4.824120603015076e-06,
+ 4.849246231155779e-06,
+ 4.874371859296483e-06,
+ 4.899497487437186e-06,
+ 4.9246231155778894e-06,
+ 4.949748743718593e-06,
+ 4.974874371859296e-06,
+ 5e-06,
+ 5e-06,
+ 4.974874371859296e-06,
+ 4.949748743718593e-06,
+ 4.9246231155778894e-06,
+ 4.899497487437186e-06,
+ 4.874371859296483e-06,
+ 4.849246231155779e-06,
+ 4.824120603015076e-06,
+ 4.798994974874372e-06,
+ 4.773869346733668e-06,
+ 4.748743718592965e-06,
+ 4.7236180904522615e-06,
+ 4.698492462311558e-06,
+ 4.673366834170855e-06,
+ 4.6482412060301506e-06,
+ 4.623115577889447e-06,
+ 4.597989949748744e-06,
+ 4.57286432160804e-06,
+ 4.547738693467337e-06,
+ 4.522613065326634e-06,
+ 4.4974874371859294e-06,
+ 4.472361809045226e-06,
+ 4.447236180904523e-06,
+ 4.422110552763819e-06,
+ 4.396984924623116e-06,
+ 4.3718592964824125e-06,
+ 4.346733668341708e-06,
+ 4.321608040201005e-06,
+ 4.2964824120603016e-06,
+ 4.271356783919598e-06,
+ 4.246231155778895e-06,
+ 4.221105527638191e-06,
+ 4.195979899497487e-06,
+ 4.170854271356784e-06,
+ 4.1457286432160804e-06,
+ 4.120603015075377e-06,
+ 4.095477386934674e-06,
+ 4.07035175879397e-06,
+ 4.045226130653266e-06,
+ 4.020100502512563e-06,
+ 3.994974874371859e-06,
+ 3.969849246231156e-06,
+ 3.9447236180904526e-06,
+ 3.919597989949749e-06,
+ 3.894472361809045e-06,
+ 3.8693467336683416e-06,
+ 3.844221105527638e-06,
+ 3.819095477386935e-06,
+ 3.7939698492462314e-06,
+ 3.7688442211055276e-06,
+ 3.7437185929648243e-06,
+ 3.718592964824121e-06,
+ 3.693467336683417e-06,
+ 3.6683417085427137e-06,
+ 3.6432160804020103e-06,
+ 3.6180904522613065e-06,
+ 3.592964824120603e-06,
+ 3.5678391959798997e-06,
+ 3.542713567839196e-06,
+ 3.5175879396984926e-06,
+ 3.492462311557789e-06,
+ 3.4673366834170854e-06,
+ 3.442211055276382e-06,
+ 3.4170854271356786e-06,
+ 3.391959798994975e-06,
+ 3.3668341708542714e-06,
+ 3.341708542713568e-06,
+ 3.3165829145728643e-06,
+ 3.291457286432161e-06,
+ 3.2663316582914575e-06,
+ 3.2412060301507537e-06,
+ 3.2160804020100503e-06,
+ 3.190954773869347e-06,
+ 3.165829145728643e-06,
+ 3.1407035175879398e-06,
+ 3.1155778894472364e-06,
+ 3.0904522613065326e-06,
+ 3.065326633165829e-06,
+ 3.040201005025126e-06,
+ 3.015075376884422e-06,
+ 2.9899497487437186e-06,
+ 2.9648241206030153e-06,
+ 2.9396984924623115e-06,
+ 2.914572864321608e-06,
+ 2.8894472361809047e-06,
+ 2.864321608040201e-06,
+ 2.8391959798994975e-06,
+ 2.814070351758794e-06,
+ 2.7889447236180903e-06,
+ 2.763819095477387e-06,
+ 2.7386934673366836e-06,
+ 2.7135678391959798e-06,
+ 2.6884422110552764e-06,
+ 2.663316582914573e-06,
+ 2.6381909547738692e-06,
+ 2.613065326633166e-06,
+ 2.5879396984924625e-06,
+ 2.5628140703517587e-06,
+ 2.5376884422110553e-06,
+ 2.512562814070352e-06,
+ 2.487437185929648e-06,
+ 2.4623115577889447e-06,
+ 2.4371859296482413e-06,
+ 2.412060301507538e-06,
+ 2.386934673366834e-06,
+ 2.3618090452261308e-06,
+ 2.3366834170854274e-06,
+ 2.3115577889447236e-06,
+ 2.28643216080402e-06,
+ 2.261306532663317e-06,
+ 2.236180904522613e-06,
+ 2.2110552763819096e-06,
+ 2.1859296482412063e-06,
+ 2.1608040201005025e-06,
+ 2.135678391959799e-06,
+ 2.1105527638190957e-06,
+ 2.085427135678392e-06,
+ 2.0603015075376885e-06,
+ 2.035175879396985e-06,
+ 2.0100502512562813e-06,
+ 1.984924623115578e-06,
+ 1.9597989949748746e-06,
+ 1.9346733668341708e-06,
+ 1.9095477386934674e-06,
+ 1.8844221105527638e-06,
+ 1.8592964824120604e-06,
+ 1.8341708542713568e-06,
+ 1.8090452261306533e-06,
+ 1.7839195979899499e-06,
+ 1.7587939698492463e-06,
+ 1.7336683417085427e-06,
+ 1.7085427135678393e-06,
+ 1.6834170854271357e-06,
+ 1.6582914572864321e-06,
+ 1.6331658291457288e-06,
+ 1.6080402010050252e-06,
+ 1.5829145728643216e-06,
+ 1.5577889447236182e-06,
+ 1.5326633165829146e-06,
+ 1.507537688442211e-06,
+ 1.4824120603015076e-06,
+ 1.457286432160804e-06,
+ 1.4321608040201004e-06,
+ 1.407035175879397e-06,
+ 1.3819095477386935e-06,
+ 1.3567839195979899e-06,
+ 1.3316582914572865e-06,
+ 1.306532663316583e-06,
+ 1.2814070351758793e-06,
+ 1.256281407035176e-06,
+ 1.2311557788944724e-06,
+ 1.206030150753769e-06,
+ 1.1809045226130654e-06,
+ 1.1557788944723618e-06,
+ 1.1306532663316584e-06,
+ 1.1055276381909548e-06,
+ 1.0804020100502512e-06,
+ 1.0552763819095479e-06,
+ 1.0301507537688443e-06,
+ 1.0050251256281407e-06,
+ 9.798994974874373e-07,
+ 9.547738693467337e-07,
+ 9.296482412060302e-07,
+ 9.045226130653266e-07,
+ 8.793969849246231e-07,
+ 8.542713567839197e-07,
+ 8.291457286432161e-07,
+ 8.040201005025126e-07,
+ 7.788944723618091e-07,
+ 7.537688442211055e-07,
+ 7.28643216080402e-07,
+ 7.035175879396985e-07,
+ 6.783919597989949e-07,
+ 6.532663316582915e-07,
+ 6.28140703517588e-07,
+ 6.030150753768845e-07,
+ 5.778894472361809e-07,
+ 5.527638190954774e-07,
+ 5.276381909547739e-07,
+ 5.025125628140703e-07,
+ 4.773869346733669e-07,
+ 4.522613065326633e-07,
+ 4.2713567839195983e-07,
+ 4.020100502512563e-07,
+ 3.7688442211055275e-07,
+ 3.5175879396984927e-07,
+ 3.2663316582914573e-07,
+ 3.0150753768844224e-07,
+ 2.763819095477387e-07,
+ 2.5125628140703517e-07,
+ 2.2613065326633166e-07,
+ 2.0100502512562815e-07,
+ 1.7587939698492463e-07,
+ 1.5075376884422112e-07,
+ 1.2562814070351758e-07,
+ 1.0050251256281407e-07,
+ 7.537688442211056e-08,
+ 5.0251256281407036e-08,
+ 2.5125628140703518e-08,
+ 0.0
+ ],
+ "segment": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ "tip_position": [
+ -1.6592281675369936e-06,
+ -1.634132690150059e-06,
+ -1.6090372127631244e-06,
+ -1.5839417353761896e-06,
+ -1.558846257989255e-06,
+ -1.5337507806023203e-06,
+ -1.5086553032153857e-06,
+ -1.483559825828451e-06,
+ -1.4584643484415163e-06,
+ -1.4333688710545817e-06,
+ -1.408273393667647e-06,
+ -1.3831779162807123e-06,
+ -1.3580824388937775e-06,
+ -1.332986961506843e-06,
+ -1.3078914841199083e-06,
+ -1.2827960067329735e-06,
+ -1.257700529346039e-06,
+ -1.2326050519591042e-06,
+ -1.2075095745721696e-06,
+ -1.1824140971852348e-06,
+ -1.1573186197983004e-06,
+ -1.1322231424113656e-06,
+ -1.1071276650244308e-06,
+ -1.0820321876374962e-06,
+ -1.0569367102505614e-06,
+ -1.0318412328636268e-06,
+ -1.006745755476692e-06,
+ -9.816502780897577e-07,
+ -9.565548007028229e-07,
+ -9.314593233158882e-07,
+ -9.063638459289535e-07,
+ -8.812683685420188e-07,
+ -8.561728911550841e-07,
+ -8.310774137681495e-07,
+ -8.059819363812147e-07,
+ -7.808864589942801e-07,
+ -7.557909816073454e-07,
+ -7.306955042204107e-07,
+ -7.056000268334761e-07,
+ -6.805045494465413e-07,
+ -6.554090720596068e-07,
+ -6.30313594672672e-07,
+ -6.052181172857374e-07,
+ -5.801226398988028e-07,
+ -5.55027162511868e-07,
+ -5.299316851249332e-07,
+ -5.048362077379988e-07,
+ -4.79740730351064e-07,
+ -4.546452529641292e-07,
+ -4.295497755771946e-07,
+ -4.0445429819026004e-07,
+ -3.7935882080332545e-07,
+ -3.5426334341639065e-07,
+ -3.2916786602945585e-07,
+ -3.040723886425215e-07,
+ -2.789769112555867e-07,
+ -2.538814338686519e-07,
+ -2.287859564817173e-07,
+ -2.036904790947827e-07,
+ -1.785950017078479e-07,
+ -1.5349952432091332e-07,
+ -1.28204912943731e-07,
+ -1.0274533356756872e-07,
+ -7.717836159424109e-08,
+ -5.152454285119414e-08,
+ -2.5795751692118216e-08,
+ 0.0,
+ 2.5856830498054668e-08,
+ 5.17701826103655e-08,
+ 7.773638989818303e-08,
+ 1.0375241923583414e-07,
+ 1.2981570695060032e-07,
+ 1.5592404884924778e-07,
+ 1.8207552327206694e-07,
+ 2.0826843541411815e-07,
+ 2.3450127590149643e-07,
+ 2.6077268924588544e-07,
+ 2.8708144933948013e-07,
+ 3.1342644008974e-07,
+ 3.3980663988539545e-07,
+ 3.6622110897075323e-07,
+ 3.926689790634242e-07,
+ 4.191494447274708e-07,
+ 4.456617561378074e-07,
+ 4.722052129599619e-07,
+ 4.987791591333991e-07,
+ 5.253829783937782e-07,
+ 5.520160904047622e-07,
+ 5.786779473966583e-07,
+ 6.053680312295702e-07,
+ 6.320858508145083e-07,
+ 6.588309398382461e-07,
+ 6.856028547474058e-07,
+ 7.124011729549945e-07,
+ 7.392254912387783e-07,
+ 7.660754243059048e-07,
+ 7.929506035022089e-07,
+ 8.198506756479756e-07,
+ 8.467753019846728e-07,
+ 8.737241572193945e-07,
+ 9.006969286556782e-07,
+ 9.276933154008946e-07,
+ 9.547130276417597e-07,
+ 9.817557859806137e-07,
+ 1.008821320826069e-06,
+ 1.0359093718324315e-06,
+ 1.0630196873829914e-06,
+ 1.0901520241128519e-06,
+ 1.1173061464675102e-06,
+ 1.1444818262938e-06,
+ 1.1716788424602142e-06,
+ 1.198896980503954e-06,
+ 1.2261360323023247e-06,
+ 1.2533957957663628e-06,
+ 1.2806760745548035e-06,
+ 1.3079766778066754e-06,
+ 1.3352974198909957e-06,
+ 1.3626381201721864e-06,
+ 1.3899986027899618e-06,
+ 1.4173786964525572e-06,
+ 1.444778234242284e-06,
+ 1.4721970534324769e-06,
+ 1.4996349953149842e-06,
+ 1.5270919050374487e-06,
+ 1.5545676314496586e-06,
+ 1.5820620269583373e-06,
+ 1.6095749473897892e-06,
+ 1.6371062518598533e-06,
+ 1.6646558026506777e-06,
+ 1.6922234650938658e-06,
+ 1.7198091074595678e-06,
+ 1.7474126008511395e-06,
+ 1.7750338191050157e-06,
+ 1.8026726386954682e-06,
+ 1.8303289386439416e-06,
+ 1.8580026004327014e-06,
+ 1.8856935079225197e-06,
+ 1.913401547274164e-06,
+ 1.941126606873474e-06,
+ 1.9688685772598034e-06,
+ 1.996627351057647e-06,
+ 2.0244028229112684e-06,
+ 2.0521948894221646e-06,
+ 2.0800034490891973e-06,
+ 2.107828402251273e-06,
+ 2.135669651032405e-06,
+ 2.1635270992890524e-06,
+ 2.1914006525596043e-06,
+ 2.219290218015909e-06,
+ 2.2471957044167257e-06,
+ 2.2751170220630232e-06,
+ 2.3030540827550173e-06,
+ 2.3310067997508625e-06,
+ 2.358975087726926e-06,
+ 2.3869588627395557e-06,
+ 2.414958042188279e-06,
+ 2.4429725447803584e-06,
+ 2.4710022904966436e-06,
+ 2.4990472005586617e-06,
+ 2.5271071973968846e-06,
+ 2.555182204620117e-06,
+ 2.583272146985965e-06,
+ 2.6113769503723274e-06,
+ 2.639496541749864e-06,
+ 2.667630849155413e-06,
+ 2.6957798016662926e-06,
+ 2.7239433293754713e-06,
+ 2.7521213633675504e-06,
+ 2.780313835695545e-06,
+ 2.8085206793584055e-06,
+ 2.836741828279281e-06,
+ 2.8649772172844586e-06,
+ 2.8932267820829767e-06,
+ 2.9214904592468746e-06,
+ 2.949768186192062e-06,
+ 2.97805990115976e-06,
+ 3.0063655431985324e-06,
+ 3.034685052146836e-06,
+ 3.0630183686161164e-06,
+ 3.0913654339743844e-06,
+ 3.1197261903302983e-06,
+ 3.148100580517689e-06,
+ 3.1764885480805583e-06,
+ 3.2048900372584837e-06,
+ 3.2333049929724602e-06,
+ 3.2617333608111284e-06,
+ 3.2901750870173957e-06,
+ 3.3186301184754287e-06,
+ 3.347098402698003e-06,
+ 3.3755798878142044e-06,
+ 3.4040745225574585e-06,
+ 3.4325822562538854e-06,
+ 3.4611030388109705e-06,
+ 3.489636820706525e-06,
+ 3.518183552977953e-06,
+ 3.5467431872117882e-06,
+ 3.5753156755335034e-06,
+ 3.603900970597587e-06,
+ 3.632499025577872e-06,
+ 3.66110979415811e-06,
+ 3.66110979415811e-06,
+ 3.632499025577872e-06,
+ 3.603900970597587e-06,
+ 3.5753156755335034e-06,
+ 3.5467431872117882e-06,
+ 3.518183552977953e-06,
+ 3.489636820706525e-06,
+ 3.4611030388109705e-06,
+ 3.4325822562538854e-06,
+ 3.4040745225574585e-06,
+ 3.3755798878142044e-06,
+ 3.347098402698003e-06,
+ 3.3186301184754287e-06,
+ 3.2901750870173957e-06,
+ 3.2617333608111284e-06,
+ 3.2333049929724602e-06,
+ 3.2048900372584837e-06,
+ 3.1764885480805583e-06,
+ 3.148100580517689e-06,
+ 3.1197261903302983e-06,
+ 3.0913654339743844e-06,
+ 3.0630183686161164e-06,
+ 3.034685052146836e-06,
+ 3.0063655431985324e-06,
+ 2.97805990115976e-06,
+ 2.949768186192062e-06,
+ 2.9214904592468746e-06,
+ 2.8932267820829767e-06,
+ 2.8649772172844586e-06,
+ 2.836741828279281e-06,
+ 2.8085206793584055e-06,
+ 2.780313835695545e-06,
+ 2.7521213633675504e-06,
+ 2.7239433293754713e-06,
+ 2.6957798016662926e-06,
+ 2.667630849155413e-06,
+ 2.639496541749864e-06,
+ 2.6113769503723274e-06,
+ 2.583272146985965e-06,
+ 2.555182204620117e-06,
+ 2.5271071973968846e-06,
+ 2.4990472005586617e-06,
+ 2.4710022904966436e-06,
+ 2.4429725447803584e-06,
+ 2.414958042188279e-06,
+ 2.3869588627395557e-06,
+ 2.358975087726926e-06,
+ 2.3310067997508625e-06,
+ 2.3030540827550173e-06,
+ 2.2751170220630232e-06,
+ 2.2471957044167257e-06,
+ 2.219290218015909e-06,
+ 2.1914006525596043e-06,
+ 2.1635270992890524e-06,
+ 2.135669651032405e-06,
+ 2.107828402251273e-06,
+ 2.0800034490891973e-06,
+ 2.0521948894221646e-06,
+ 2.0244028229112684e-06,
+ 1.996627351057647e-06,
+ 1.9688685772598034e-06,
+ 1.941126606873474e-06,
+ 1.913401547274164e-06,
+ 1.8856935079225197e-06,
+ 1.8580026004327014e-06,
+ 1.8303289386439416e-06,
+ 1.8026726386954682e-06,
+ 1.7750338191050157e-06,
+ 1.7474126008511395e-06,
+ 1.7198091074595678e-06,
+ 1.6922234650938658e-06,
+ 1.6646558026506777e-06,
+ 1.6371062518598533e-06,
+ 1.6095749473897892e-06,
+ 1.5820620269583373e-06,
+ 1.5545676314496586e-06,
+ 1.5270919050374487e-06,
+ 1.4996349953149842e-06,
+ 1.4721970534324769e-06,
+ 1.444778234242284e-06,
+ 1.4173786964525572e-06,
+ 1.3899986027899618e-06,
+ 1.3626381201721864e-06,
+ 1.3352974198909957e-06,
+ 1.3079766778066754e-06,
+ 1.2806760745548035e-06,
+ 1.2533957957663628e-06,
+ 1.2261360323023247e-06,
+ 1.198896980503954e-06,
+ 1.1716788424602142e-06,
+ 1.1444818262938e-06,
+ 1.1173061464675102e-06,
+ 1.0901520241128519e-06,
+ 1.0630196873829914e-06,
+ 1.0359093718324315e-06,
+ 1.008821320826069e-06,
+ 9.817557859806137e-07,
+ 9.547130276417597e-07,
+ 9.276933154008946e-07,
+ 9.006969286556782e-07,
+ 8.737241572193945e-07,
+ 8.467753019846728e-07,
+ 8.198506756479756e-07,
+ 7.929506035022089e-07,
+ 7.660754243059048e-07,
+ 7.392254912387783e-07,
+ 7.124011729549945e-07,
+ 6.856028547474058e-07,
+ 6.588309398382461e-07,
+ 6.320858508145083e-07,
+ 6.053680312295702e-07,
+ 5.786779473966583e-07,
+ 5.520160904047622e-07,
+ 5.253829783937782e-07,
+ 4.987791591333991e-07,
+ 4.722052129599619e-07,
+ 4.456617561378074e-07,
+ 4.191494447274708e-07,
+ 3.926689790634242e-07,
+ 3.6622110897075323e-07,
+ 3.3980663988539545e-07,
+ 3.1342644008974e-07,
+ 2.8708144933948013e-07,
+ 2.6077268924588544e-07,
+ 2.3450127590149643e-07,
+ 2.0826843541411815e-07,
+ 1.8207552327206694e-07,
+ 1.5592404884924778e-07,
+ 1.2981570695060032e-07,
+ 1.0375241923583414e-07,
+ 7.773638989818303e-08,
+ 5.17701826103655e-08,
+ 2.5856830498054668e-08,
+ 0.0,
+ -2.5795751692118216e-08,
+ -5.152454285119414e-08,
+ -7.717836159424109e-08,
+ -1.0274533356756872e-07,
+ -1.28204912943731e-07,
+ -1.5349952432091332e-07,
+ -1.785950017078479e-07,
+ -2.036904790947827e-07,
+ -2.287859564817173e-07,
+ -2.538814338686519e-07,
+ -2.789769112555867e-07,
+ -3.040723886425215e-07,
+ -3.2916786602945585e-07,
+ -3.5426334341639065e-07,
+ -3.7935882080332545e-07,
+ -4.0445429819026004e-07,
+ -4.295497755771946e-07,
+ -4.546452529641292e-07,
+ -4.79740730351064e-07,
+ -5.048362077379988e-07,
+ -5.299316851249332e-07,
+ -5.55027162511868e-07,
+ -5.801226398988028e-07,
+ -6.052181172857374e-07,
+ -6.30313594672672e-07,
+ -6.554090720596068e-07,
+ -6.805045494465413e-07,
+ -7.056000268334761e-07,
+ -7.306955042204107e-07,
+ -7.557909816073454e-07,
+ -7.808864589942801e-07,
+ -8.059819363812147e-07,
+ -8.310774137681495e-07,
+ -8.561728911550841e-07,
+ -8.812683685420188e-07,
+ -9.063638459289535e-07,
+ -9.314593233158882e-07,
+ -9.565548007028229e-07,
+ -9.816502780897577e-07,
+ -1.006745755476692e-06,
+ -1.0318412328636268e-06,
+ -1.0569367102505614e-06,
+ -1.0820321876374962e-06,
+ -1.1071276650244308e-06,
+ -1.1322231424113656e-06,
+ -1.1573186197983004e-06,
+ -1.1824140971852348e-06,
+ -1.2075095745721696e-06,
+ -1.2326050519591042e-06,
+ -1.257700529346039e-06,
+ -1.2827960067329735e-06,
+ -1.3078914841199083e-06,
+ -1.332986961506843e-06,
+ -1.3580824388937775e-06,
+ -1.3831779162807123e-06,
+ -1.408273393667647e-06,
+ -1.4333688710545817e-06,
+ -1.4584643484415163e-06,
+ -1.483559825828451e-06,
+ -1.5086553032153857e-06,
+ -1.5337507806023203e-06,
+ -1.558846257989255e-06,
+ -1.5839417353761896e-06,
+ -1.6090372127631244e-06,
+ -1.634132690150059e-06,
+ -1.6592281675369936e-06
+ ]
+ },
+ "P06": {
+ "contact": {
+ "deviation_from_baseline": 28,
+ "fit_constant_line": 83,
+ "fit_constant_polynomial": 51,
+ "fit_line_polynomial": 51
+ },
+ "force": [
+ 3.3919597989949844e-11,
+ 3.3919597989949844e-11,
+ 3.3919597989949844e-11,
+ 3.391959798994987e-11,
+ 3.3919597989949877e-11,
+ 3.391959798994988e-11,
+ 3.3919597989949786e-11,
+ 3.39195979899498e-11,
+ 3.3919597989949805e-11,
+ 3.391959798994981e-11,
+ 3.3919597989949825e-11,
+ 3.391959798994983e-11,
+ 3.391959798994984e-11,
+ 3.391959798994985e-11,
+ 3.391959798994986e-11,
+ 3.391959798994987e-11,
+ 3.3919597989949877e-11,
+ 3.391959798994988e-11,
+ 3.391959798994979e-11,
+ 3.3919597989949805e-11,
+ 3.3919597989949805e-11,
+ 3.391959798994981e-11,
+ 3.391959798994983e-11,
+ 3.391959798994984e-11,
+ 3.3919597989949844e-11,
+ 3.3919597989949844e-11,
+ 3.3919597989949864e-11,
+ 3.391959798994987e-11,
+ 3.643216080402023e-11,
+ 3.894472361809049e-11,
+ 4.145728643216085e-11,
+ 4.396984924623121e-11,
+ 4.648241206030157e-11,
+ 4.899497487437193e-11,
+ 5.150753768844229e-11,
+ 5.402010050251265e-11,
+ 5.6532663316583013e-11,
+ 5.904522613065337e-11,
+ 6.155778894472373e-11,
+ 6.40703517587941e-11,
+ 6.658291457286446e-11,
+ 6.909547738693482e-11,
+ 7.160804020100518e-11,
+ 7.412060301507544e-11,
+ 7.66331658291458e-11,
+ 7.914572864321616e-11,
+ 8.165829145728652e-11,
+ 8.417085427135688e-11,
+ 8.668341708542724e-11,
+ 8.91959798994976e-11,
+ 9.170854271356796e-11,
+ 9.422110552763822e-11,
+ 9.673366834170858e-11,
+ 9.924623115577894e-11,
+ 1.017587939698493e-10,
+ 1.0427135678391966e-10,
+ 1.0678391959799002e-10,
+ 1.0929648241206038e-10,
+ 1.1180904522613074e-10,
+ 1.143216080402011e-10,
+ 1.1683417085427146e-10,
+ 1.392601326930982e-10,
+ 1.7818289442992757e-10,
+ 2.2784491588329405e-10,
+ 2.8619161430860365e-10,
+ 3.5203555433678944e-10,
+ 4.245755476692286e-10,
+ 5.032234215952738e-10,
+ 5.875234569469557e-10,
+ 6.771090098493254e-10,
+ 7.716767677350325e-10,
+ 8.709703633322505e-10,
+ 9.747693773176074e-10,
+ 1.0828816437201223e-09,
+ 1.1951376820458483e-09,
+ 1.3113865549042909e-09,
+ 1.4314927134638023e-09,
+ 1.555333546943882e-09,
+ 1.6827974460904473e-09,
+ 1.813782249776579e-09,
+ 1.948193982433005e-09,
+ 2.0859458158206693e-09,
+ 2.2269572063459504e-09,
+ 2.371153171500206e-09,
+ 2.518463677836241e-09,
+ 2.6688231193005718e-09,
+ 2.822169869459094e-09,
+ 2.978445894678082e-09,
+ 3.1375964179883197e-09,
+ 3.299569625400097e-09,
+ 3.4643164080145087e-09,
+ 3.6317901345088774e-09,
+ 3.801946449545491e-09,
+ 3.974743094424921e-09,
+ 4.150139746923922e-09,
+ 4.3280978777572115e-09,
+ 4.508580621508173e-09,
+ 4.691552660205477e-09,
+ 4.876980117995778e-09,
+ 5.064830465588577e-09,
+ 5.255072433337545e-09,
+ 5.4476759319798e-09,
+ 5.642611980186922e-09,
+ 5.839852638192903e-09,
+ 6.0393709468590044e-09,
+ 6.241140871615914e-09,
+ 6.445137250792455e-09,
+ 6.65133574789912e-09,
+ 6.859712807485581e-09,
+ 7.070245614235139e-09,
+ 7.282912054997178e-09,
+ 7.497690683491785e-09,
+ 7.714560687449419e-09,
+ 7.933501857973829e-09,
+ 8.154494560938513e-09,
+ 8.3775197102463e-09,
+ 8.602558742798938e-09,
+ 8.82959359503864e-09,
+ 9.058606680936748e-09,
+ 9.28958087131688e-09,
+ 9.522499474410204e-09,
+ 9.757346217550045e-09,
+ 9.9941052299214e-09,
+ 1.0232761026288466e-08,
+ 1.0473298491630014e-08,
+ 1.0715702866618505e-08,
+ 1.0959959733884316e-08,
+ 1.1206055005011306e-08,
+ 1.1453974908214373e-08,
+ 1.1703705976653802e-08,
+ 1.1955235037344581e-08,
+ 1.2208549200622345e-08,
+ 1.24636358501306e-08,
+ 1.2720482633296406e-08,
+ 1.2979077452264365e-08,
+ 1.3239408455260979e-08,
+ 1.3501464028363385e-08,
+ 1.3765232787648454e-08,
+ 1.4030703571700053e-08,
+ 1.4297865434453563e-08,
+ 1.4566707638358508e-08,
+ 1.4837219647841329e-08,
+ 1.510939112305149e-08,
+ 1.538321191387538e-08,
+ 1.5658672054203537e-08,
+ 1.5935761756437322e-08,
+ 1.621447140622263e-08,
+ 1.649479155739848e-08,
+ 1.6776712927149512e-08,
+ 1.7060226391351772e-08,
+ 1.734532298010215e-08,
+ 1.7631993873422133e-08,
+ 1.7920230397127276e-08,
+ 1.821002401885425e-08,
+ 1.850136634423786e-08,
+ 1.8794249113230785e-08,
+ 1.9088664196559265e-08,
+ 1.9384603592308372e-08,
+ 1.9682059422630854e-08,
+ 1.998102393057376e-08,
+ 2.028148947701763e-08,
+ 2.0583448537723035e-08,
+ 2.0886893700479763e-08,
+ 2.1191817662354026e-08,
+ 2.149821322702953e-08,
+ 2.1806073302238184e-08,
+ 2.2115390897276654e-08,
+ 2.24261591206052e-08,
+ 2.273837117752515e-08,
+ 2.3052020367931855e-08,
+ 2.336710008414004e-08,
+ 2.3683603808778393e-08,
+ 2.400152511275077e-08,
+ 2.4320857653261244e-08,
+ 2.464159517190049e-08,
+ 2.4963731492790932e-08,
+ 2.5287260520788715e-08,
+ 2.5612176239739766e-08,
+ 2.5938472710788365e-08,
+ 2.626614407073583e-08,
+ 2.6595184530447713e-08,
+ 2.6925588373307454e-08,
+ 2.725734995371493e-08,
+ 2.7590463695628124e-08,
+ 2.7924924091146382e-08,
+ 2.8260725699133787e-08,
+ 2.85978631438811e-08,
+ 2.893633111380495e-08,
+ 2.9276124360183016e-08,
+ 2.9617237695923797e-08,
+ 2.995966599436981e-08,
+ 3.0303404188133144e-08,
+ 3.064844726796217e-08,
+ 3.099479028163824e-08,
+ 3.134242833290169e-08,
+ 3.16913565804058e-08,
+ 3.2041570236697896e-08,
+ 3.239306456722688e-08,
+ 3.2745834889376026e-08,
+ 3.309987657152037e-08,
+ 3.309987657152037e-08,
+ 3.2745834889376026e-08,
+ 3.239306456722688e-08,
+ 3.2041570236697896e-08,
+ 3.16913565804058e-08,
+ 3.134242833290169e-08,
+ 3.099479028163824e-08,
+ 3.064844726796217e-08,
+ 3.0303404188133144e-08,
+ 2.995966599436981e-08,
+ 2.9617237695923797e-08,
+ 2.9276124360183016e-08,
+ 2.893633111380495e-08,
+ 2.85978631438811e-08,
+ 2.8260725699133787e-08,
+ 2.7924924091146382e-08,
+ 2.7590463695628124e-08,
+ 2.725734995371493e-08,
+ 2.6925588373307454e-08,
+ 2.6595184530447713e-08,
+ 2.626614407073583e-08,
+ 2.5938472710788365e-08,
+ 2.5612176239739766e-08,
+ 2.5287260520788715e-08,
+ 2.4963731492790932e-08,
+ 2.464159517190049e-08,
+ 2.4320857653261244e-08,
+ 2.400152511275077e-08,
+ 2.3683603808778393e-08,
+ 2.336710008414004e-08,
+ 2.3052020367931855e-08,
+ 2.273837117752515e-08,
+ 2.24261591206052e-08,
+ 2.2115390897276654e-08,
+ 2.1806073302238184e-08,
+ 2.149821322702953e-08,
+ 2.1191817662354026e-08,
+ 2.0886893700479763e-08,
+ 2.0583448537723035e-08,
+ 2.028148947701763e-08,
+ 1.998102393057376e-08,
+ 1.9682059422630854e-08,
+ 1.9384603592308372e-08,
+ 1.9088664196559265e-08,
+ 1.8794249113230785e-08,
+ 1.850136634423786e-08,
+ 1.821002401885425e-08,
+ 1.7920230397127276e-08,
+ 1.7631993873422133e-08,
+ 1.734532298010215e-08,
+ 1.7060226391351772e-08,
+ 1.6776712927149512e-08,
+ 1.649479155739848e-08,
+ 1.621447140622263e-08,
+ 1.5935761756437322e-08,
+ 1.5658672054203537e-08,
+ 1.538321191387538e-08,
+ 1.510939112305149e-08,
+ 1.4837219647841329e-08,
+ 1.4566707638358508e-08,
+ 1.4297865434453563e-08,
+ 1.4030703571700053e-08,
+ 1.3765232787648454e-08,
+ 1.3501464028363385e-08,
+ 1.3239408455260979e-08,
+ 1.2979077452264365e-08,
+ 1.2720482633296406e-08,
+ 1.24636358501306e-08,
+ 1.2208549200622345e-08,
+ 1.1955235037344581e-08,
+ 1.1703705976653802e-08,
+ 1.1453974908214373e-08,
+ 1.1206055005011306e-08,
+ 1.0959959733884316e-08,
+ 1.0715702866618505e-08,
+ 1.0473298491630014e-08,
+ 1.0232761026288466e-08,
+ 9.9941052299214e-09,
+ 9.757346217550045e-09,
+ 9.522499474410204e-09,
+ 9.28958087131688e-09,
+ 9.058606680936748e-09,
+ 8.82959359503864e-09,
+ 8.602558742798938e-09,
+ 8.3775197102463e-09,
+ 8.154494560938513e-09,
+ 7.933501857973829e-09,
+ 7.714560687449419e-09,
+ 7.497690683491785e-09,
+ 7.282912054997178e-09,
+ 7.070245614235139e-09,
+ 6.859712807485581e-09,
+ 6.65133574789912e-09,
+ 6.445137250792455e-09,
+ 6.241140871615914e-09,
+ 6.0393709468590044e-09,
+ 5.839852638192903e-09,
+ 5.642611980186922e-09,
+ 5.4476759319798e-09,
+ 5.255072433337545e-09,
+ 5.064830465588577e-09,
+ 4.876980117995778e-09,
+ 4.691552660205477e-09,
+ 4.508580621508173e-09,
+ 4.3280978777572115e-09,
+ 4.150139746923922e-09,
+ 3.974743094424921e-09,
+ 3.801946449545491e-09,
+ 3.6317901345088774e-09,
+ 3.4643164080145087e-09,
+ 3.299569625400097e-09,
+ 3.1375964179883197e-09,
+ 2.978445894678082e-09,
+ 2.822169869459094e-09,
+ 2.6688231193005718e-09,
+ 2.518463677836241e-09,
+ 2.371153171500206e-09,
+ 2.2269572063459504e-09,
+ 2.0859458158206693e-09,
+ 1.948193982433005e-09,
+ 1.813782249776579e-09,
+ 1.6827974460904473e-09,
+ 1.555333546943882e-09,
+ 1.4314927134638023e-09,
+ 1.3113865549042909e-09,
+ 1.1951376820458483e-09,
+ 1.0828816437201223e-09,
+ 9.747693773176074e-10,
+ 8.709703633322505e-10,
+ 7.716767677350325e-10,
+ 6.771090098493254e-10,
+ 5.875234569469557e-10,
+ 5.032234215952738e-10,
+ 4.245755476692286e-10,
+ 3.5203555433678944e-10,
+ 2.8619161430860365e-10,
+ 2.2784491588329405e-10,
+ 1.7818289442992757e-10,
+ 1.392601326930982e-10,
+ 1.1683417085427146e-10,
+ 1.143216080402011e-10,
+ 1.1180904522613074e-10,
+ 1.0929648241206038e-10,
+ 1.0678391959799002e-10,
+ 1.0427135678391966e-10,
+ 1.017587939698493e-10,
+ 9.924623115577894e-11,
+ 9.673366834170858e-11,
+ 9.422110552763822e-11,
+ 9.170854271356796e-11,
+ 8.91959798994976e-11,
+ 8.668341708542724e-11,
+ 8.417085427135688e-11,
+ 8.165829145728652e-11,
+ 7.914572864321616e-11,
+ 7.66331658291458e-11,
+ 7.412060301507544e-11,
+ 7.160804020100518e-11,
+ 6.909547738693482e-11,
+ 6.658291457286446e-11,
+ 6.40703517587941e-11,
+ 6.155778894472373e-11,
+ 5.904522613065337e-11,
+ 5.6532663316583013e-11,
+ 5.402010050251265e-11,
+ 5.150753768844229e-11,
+ 4.899497487437193e-11,
+ 4.648241206030157e-11,
+ 4.396984924623121e-11,
+ 4.145728643216085e-11,
+ 3.894472361809049e-11,
+ 3.643216080402023e-11,
+ 3.391959798994987e-11,
+ 3.140703517587951e-11,
+ 2.889447236180915e-11,
+ 2.6381909547738788e-11,
+ 2.3869346733668427e-11,
+ 2.1356783919598066e-11,
+ 1.8844221105527706e-11,
+ 1.6331658291457345e-11,
+ 1.3819095477386984e-11,
+ 1.1306532663316623e-11,
+ 8.793969849246366e-12,
+ 6.281407035176005e-12,
+ 3.7688442211056445e-12,
+ 1.2562814070352838e-12,
+ -1.256281407035077e-12,
+ -3.768844221105438e-12,
+ -6.2814070351757985e-12,
+ -8.79396984924616e-12,
+ -1.130653266331652e-11,
+ -1.381909547738688e-11,
+ -1.633165829145724e-11,
+ -1.8844221105527602e-11,
+ -2.135678391959786e-11,
+ -2.386934673366822e-11,
+ -2.638190954773858e-11,
+ -2.8894472361808942e-11,
+ -3.14070351758793e-11,
+ -3.3919597989949663e-11
+ ],
+ "height": [
+ 0.0,
+ 2.5125628140703518e-08,
+ 5.0251256281407036e-08,
+ 7.537688442211056e-08,
+ 1.0050251256281407e-07,
+ 1.2562814070351758e-07,
+ 1.5075376884422112e-07,
+ 1.7587939698492463e-07,
+ 2.0100502512562815e-07,
+ 2.2613065326633166e-07,
+ 2.5125628140703517e-07,
+ 2.763819095477387e-07,
+ 3.0150753768844224e-07,
+ 3.2663316582914573e-07,
+ 3.5175879396984927e-07,
+ 3.7688442211055275e-07,
+ 4.020100502512563e-07,
+ 4.2713567839195983e-07,
+ 4.522613065326633e-07,
+ 4.773869346733669e-07,
+ 5.025125628140703e-07,
+ 5.276381909547739e-07,
+ 5.527638190954774e-07,
+ 5.778894472361809e-07,
+ 6.030150753768845e-07,
+ 6.28140703517588e-07,
+ 6.532663316582915e-07,
+ 6.783919597989949e-07,
+ 7.035175879396985e-07,
+ 7.28643216080402e-07,
+ 7.537688442211055e-07,
+ 7.788944723618091e-07,
+ 8.040201005025126e-07,
+ 8.291457286432161e-07,
+ 8.542713567839197e-07,
+ 8.793969849246231e-07,
+ 9.045226130653266e-07,
+ 9.296482412060302e-07,
+ 9.547738693467337e-07,
+ 9.798994974874373e-07,
+ 1.0050251256281407e-06,
+ 1.0301507537688443e-06,
+ 1.0552763819095479e-06,
+ 1.0804020100502512e-06,
+ 1.1055276381909548e-06,
+ 1.1306532663316584e-06,
+ 1.1557788944723618e-06,
+ 1.1809045226130654e-06,
+ 1.206030150753769e-06,
+ 1.2311557788944724e-06,
+ 1.256281407035176e-06,
+ 1.2814070351758793e-06,
+ 1.306532663316583e-06,
+ 1.3316582914572865e-06,
+ 1.3567839195979899e-06,
+ 1.3819095477386935e-06,
+ 1.407035175879397e-06,
+ 1.4321608040201004e-06,
+ 1.457286432160804e-06,
+ 1.4824120603015076e-06,
+ 1.507537688442211e-06,
+ 1.5326633165829146e-06,
+ 1.5577889447236182e-06,
+ 1.5829145728643216e-06,
+ 1.6080402010050252e-06,
+ 1.6331658291457288e-06,
+ 1.6582914572864321e-06,
+ 1.6834170854271357e-06,
+ 1.7085427135678393e-06,
+ 1.7336683417085427e-06,
+ 1.7587939698492463e-06,
+ 1.7839195979899499e-06,
+ 1.8090452261306533e-06,
+ 1.8341708542713568e-06,
+ 1.8592964824120604e-06,
+ 1.8844221105527638e-06,
+ 1.9095477386934674e-06,
+ 1.9346733668341708e-06,
+ 1.9597989949748746e-06,
+ 1.984924623115578e-06,
+ 2.0100502512562813e-06,
+ 2.035175879396985e-06,
+ 2.0603015075376885e-06,
+ 2.085427135678392e-06,
+ 2.1105527638190957e-06,
+ 2.135678391959799e-06,
+ 2.1608040201005025e-06,
+ 2.1859296482412063e-06,
+ 2.2110552763819096e-06,
+ 2.236180904522613e-06,
+ 2.261306532663317e-06,
+ 2.28643216080402e-06,
+ 2.3115577889447236e-06,
+ 2.3366834170854274e-06,
+ 2.3618090452261308e-06,
+ 2.386934673366834e-06,
+ 2.412060301507538e-06,
+ 2.4371859296482413e-06,
+ 2.4623115577889447e-06,
+ 2.487437185929648e-06,
+ 2.512562814070352e-06,
+ 2.5376884422110553e-06,
+ 2.5628140703517587e-06,
+ 2.5879396984924625e-06,
+ 2.613065326633166e-06,
+ 2.6381909547738692e-06,
+ 2.663316582914573e-06,
+ 2.6884422110552764e-06,
+ 2.7135678391959798e-06,
+ 2.7386934673366836e-06,
+ 2.763819095477387e-06,
+ 2.7889447236180903e-06,
+ 2.814070351758794e-06,
+ 2.8391959798994975e-06,
+ 2.864321608040201e-06,
+ 2.8894472361809047e-06,
+ 2.914572864321608e-06,
+ 2.9396984924623115e-06,
+ 2.9648241206030153e-06,
+ 2.9899497487437186e-06,
+ 3.015075376884422e-06,
+ 3.040201005025126e-06,
+ 3.065326633165829e-06,
+ 3.0904522613065326e-06,
+ 3.1155778894472364e-06,
+ 3.1407035175879398e-06,
+ 3.165829145728643e-06,
+ 3.190954773869347e-06,
+ 3.2160804020100503e-06,
+ 3.2412060301507537e-06,
+ 3.2663316582914575e-06,
+ 3.291457286432161e-06,
+ 3.3165829145728643e-06,
+ 3.341708542713568e-06,
+ 3.3668341708542714e-06,
+ 3.391959798994975e-06,
+ 3.4170854271356786e-06,
+ 3.442211055276382e-06,
+ 3.4673366834170854e-06,
+ 3.492462311557789e-06,
+ 3.5175879396984926e-06,
+ 3.542713567839196e-06,
+ 3.5678391959798997e-06,
+ 3.592964824120603e-06,
+ 3.6180904522613065e-06,
+ 3.6432160804020103e-06,
+ 3.6683417085427137e-06,
+ 3.693467336683417e-06,
+ 3.718592964824121e-06,
+ 3.7437185929648243e-06,
+ 3.7688442211055276e-06,
+ 3.7939698492462314e-06,
+ 3.819095477386935e-06,
+ 3.844221105527638e-06,
+ 3.8693467336683416e-06,
+ 3.894472361809045e-06,
+ 3.919597989949749e-06,
+ 3.9447236180904526e-06,
+ 3.969849246231156e-06,
+ 3.994974874371859e-06,
+ 4.020100502512563e-06,
+ 4.045226130653266e-06,
+ 4.07035175879397e-06,
+ 4.095477386934674e-06,
+ 4.120603015075377e-06,
+ 4.1457286432160804e-06,
+ 4.170854271356784e-06,
+ 4.195979899497487e-06,
+ 4.221105527638191e-06,
+ 4.246231155778895e-06,
+ 4.271356783919598e-06,
+ 4.2964824120603016e-06,
+ 4.321608040201005e-06,
+ 4.346733668341708e-06,
+ 4.3718592964824125e-06,
+ 4.396984924623116e-06,
+ 4.422110552763819e-06,
+ 4.447236180904523e-06,
+ 4.472361809045226e-06,
+ 4.4974874371859294e-06,
+ 4.522613065326634e-06,
+ 4.547738693467337e-06,
+ 4.57286432160804e-06,
+ 4.597989949748744e-06,
+ 4.623115577889447e-06,
+ 4.6482412060301506e-06,
+ 4.673366834170855e-06,
+ 4.698492462311558e-06,
+ 4.7236180904522615e-06,
+ 4.748743718592965e-06,
+ 4.773869346733668e-06,
+ 4.798994974874372e-06,
+ 4.824120603015076e-06,
+ 4.849246231155779e-06,
+ 4.874371859296483e-06,
+ 4.899497487437186e-06,
+ 4.9246231155778894e-06,
+ 4.949748743718593e-06,
+ 4.974874371859296e-06,
+ 5e-06,
+ 5e-06,
+ 4.974874371859296e-06,
+ 4.949748743718593e-06,
+ 4.9246231155778894e-06,
+ 4.899497487437186e-06,
+ 4.874371859296483e-06,
+ 4.849246231155779e-06,
+ 4.824120603015076e-06,
+ 4.798994974874372e-06,
+ 4.773869346733668e-06,
+ 4.748743718592965e-06,
+ 4.7236180904522615e-06,
+ 4.698492462311558e-06,
+ 4.673366834170855e-06,
+ 4.6482412060301506e-06,
+ 4.623115577889447e-06,
+ 4.597989949748744e-06,
+ 4.57286432160804e-06,
+ 4.547738693467337e-06,
+ 4.522613065326634e-06,
+ 4.4974874371859294e-06,
+ 4.472361809045226e-06,
+ 4.447236180904523e-06,
+ 4.422110552763819e-06,
+ 4.396984924623116e-06,
+ 4.3718592964824125e-06,
+ 4.346733668341708e-06,
+ 4.321608040201005e-06,
+ 4.2964824120603016e-06,
+ 4.271356783919598e-06,
+ 4.246231155778895e-06,
+ 4.221105527638191e-06,
+ 4.195979899497487e-06,
+ 4.170854271356784e-06,
+ 4.1457286432160804e-06,
+ 4.120603015075377e-06,
+ 4.095477386934674e-06,
+ 4.07035175879397e-06,
+ 4.045226130653266e-06,
+ 4.020100502512563e-06,
+ 3.994974874371859e-06,
+ 3.969849246231156e-06,
+ 3.9447236180904526e-06,
+ 3.919597989949749e-06,
+ 3.894472361809045e-06,
+ 3.8693467336683416e-06,
+ 3.844221105527638e-06,
+ 3.819095477386935e-06,
+ 3.7939698492462314e-06,
+ 3.7688442211055276e-06,
+ 3.7437185929648243e-06,
+ 3.718592964824121e-06,
+ 3.693467336683417e-06,
+ 3.6683417085427137e-06,
+ 3.6432160804020103e-06,
+ 3.6180904522613065e-06,
+ 3.592964824120603e-06,
+ 3.5678391959798997e-06,
+ 3.542713567839196e-06,
+ 3.5175879396984926e-06,
+ 3.492462311557789e-06,
+ 3.4673366834170854e-06,
+ 3.442211055276382e-06,
+ 3.4170854271356786e-06,
+ 3.391959798994975e-06,
+ 3.3668341708542714e-06,
+ 3.341708542713568e-06,
+ 3.3165829145728643e-06,
+ 3.291457286432161e-06,
+ 3.2663316582914575e-06,
+ 3.2412060301507537e-06,
+ 3.2160804020100503e-06,
+ 3.190954773869347e-06,
+ 3.165829145728643e-06,
+ 3.1407035175879398e-06,
+ 3.1155778894472364e-06,
+ 3.0904522613065326e-06,
+ 3.065326633165829e-06,
+ 3.040201005025126e-06,
+ 3.015075376884422e-06,
+ 2.9899497487437186e-06,
+ 2.9648241206030153e-06,
+ 2.9396984924623115e-06,
+ 2.914572864321608e-06,
+ 2.8894472361809047e-06,
+ 2.864321608040201e-06,
+ 2.8391959798994975e-06,
+ 2.814070351758794e-06,
+ 2.7889447236180903e-06,
+ 2.763819095477387e-06,
+ 2.7386934673366836e-06,
+ 2.7135678391959798e-06,
+ 2.6884422110552764e-06,
+ 2.663316582914573e-06,
+ 2.6381909547738692e-06,
+ 2.613065326633166e-06,
+ 2.5879396984924625e-06,
+ 2.5628140703517587e-06,
+ 2.5376884422110553e-06,
+ 2.512562814070352e-06,
+ 2.487437185929648e-06,
+ 2.4623115577889447e-06,
+ 2.4371859296482413e-06,
+ 2.412060301507538e-06,
+ 2.386934673366834e-06,
+ 2.3618090452261308e-06,
+ 2.3366834170854274e-06,
+ 2.3115577889447236e-06,
+ 2.28643216080402e-06,
+ 2.261306532663317e-06,
+ 2.236180904522613e-06,
+ 2.2110552763819096e-06,
+ 2.1859296482412063e-06,
+ 2.1608040201005025e-06,
+ 2.135678391959799e-06,
+ 2.1105527638190957e-06,
+ 2.085427135678392e-06,
+ 2.0603015075376885e-06,
+ 2.035175879396985e-06,
+ 2.0100502512562813e-06,
+ 1.984924623115578e-06,
+ 1.9597989949748746e-06,
+ 1.9346733668341708e-06,
+ 1.9095477386934674e-06,
+ 1.8844221105527638e-06,
+ 1.8592964824120604e-06,
+ 1.8341708542713568e-06,
+ 1.8090452261306533e-06,
+ 1.7839195979899499e-06,
+ 1.7587939698492463e-06,
+ 1.7336683417085427e-06,
+ 1.7085427135678393e-06,
+ 1.6834170854271357e-06,
+ 1.6582914572864321e-06,
+ 1.6331658291457288e-06,
+ 1.6080402010050252e-06,
+ 1.5829145728643216e-06,
+ 1.5577889447236182e-06,
+ 1.5326633165829146e-06,
+ 1.507537688442211e-06,
+ 1.4824120603015076e-06,
+ 1.457286432160804e-06,
+ 1.4321608040201004e-06,
+ 1.407035175879397e-06,
+ 1.3819095477386935e-06,
+ 1.3567839195979899e-06,
+ 1.3316582914572865e-06,
+ 1.306532663316583e-06,
+ 1.2814070351758793e-06,
+ 1.256281407035176e-06,
+ 1.2311557788944724e-06,
+ 1.206030150753769e-06,
+ 1.1809045226130654e-06,
+ 1.1557788944723618e-06,
+ 1.1306532663316584e-06,
+ 1.1055276381909548e-06,
+ 1.0804020100502512e-06,
+ 1.0552763819095479e-06,
+ 1.0301507537688443e-06,
+ 1.0050251256281407e-06,
+ 9.798994974874373e-07,
+ 9.547738693467337e-07,
+ 9.296482412060302e-07,
+ 9.045226130653266e-07,
+ 8.793969849246231e-07,
+ 8.542713567839197e-07,
+ 8.291457286432161e-07,
+ 8.040201005025126e-07,
+ 7.788944723618091e-07,
+ 7.537688442211055e-07,
+ 7.28643216080402e-07,
+ 7.035175879396985e-07,
+ 6.783919597989949e-07,
+ 6.532663316582915e-07,
+ 6.28140703517588e-07,
+ 6.030150753768845e-07,
+ 5.778894472361809e-07,
+ 5.527638190954774e-07,
+ 5.276381909547739e-07,
+ 5.025125628140703e-07,
+ 4.773869346733669e-07,
+ 4.522613065326633e-07,
+ 4.2713567839195983e-07,
+ 4.020100502512563e-07,
+ 3.7688442211055275e-07,
+ 3.5175879396984927e-07,
+ 3.2663316582914573e-07,
+ 3.0150753768844224e-07,
+ 2.763819095477387e-07,
+ 2.5125628140703517e-07,
+ 2.2613065326633166e-07,
+ 2.0100502512562815e-07,
+ 1.7587939698492463e-07,
+ 1.5075376884422112e-07,
+ 1.2562814070351758e-07,
+ 1.0050251256281407e-07,
+ 7.537688442211056e-08,
+ 5.0251256281407036e-08,
+ 2.5125628140703518e-08,
+ 0.0
+ ],
+ "segment": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ "tip_position": [
+ -7.042211055276383e-07,
+ -6.79070351758794e-07,
+ -6.539195979899498e-07,
+ -6.287688442211055e-07,
+ -6.036180904522614e-07,
+ -5.784673366834171e-07,
+ -5.533165829145729e-07,
+ -5.281658291457287e-07,
+ -5.030150753768845e-07,
+ -4.778643216080402e-07,
+ -4.5271356783919604e-07,
+ -4.275628140703518e-07,
+ -4.0241206030150754e-07,
+ -3.7726130653266334e-07,
+ -3.5211055276381915e-07,
+ -3.2695979899497495e-07,
+ -3.018090452261307e-07,
+ -2.7665829145728644e-07,
+ -2.5150753768844225e-07,
+ -2.2635678391959805e-07,
+ -2.012060301507538e-07,
+ -1.7605527638190955e-07,
+ -1.509045226130654e-07,
+ -1.2575376884422115e-07,
+ -1.006030150753769e-07,
+ -7.545226130653265e-08,
+ -5.03015075376885e-08,
+ -2.515075376884425e-08,
+ 0.0,
+ 2.5150753768844145e-08,
+ 5.0301507537688396e-08,
+ 7.545226130653265e-08,
+ 1.006030150753769e-07,
+ 1.2575376884422104e-07,
+ 1.509045226130653e-07,
+ 1.7605527638190955e-07,
+ 2.012060301507537e-07,
+ 2.2635678391959805e-07,
+ 2.515075376884422e-07,
+ 2.7665829145728655e-07,
+ 3.018090452261305e-07,
+ 3.2695979899497484e-07,
+ 3.521105527638192e-07,
+ 3.7726130653266334e-07,
+ 4.024120603015075e-07,
+ 4.2756281407035185e-07,
+ 4.52713567839196e-07,
+ 4.778643216080401e-07,
+ 5.030150753768845e-07,
+ 5.281658291457286e-07,
+ 5.533165829145728e-07,
+ 5.784673366834169e-07,
+ 6.036180904522613e-07,
+ 6.287688442211056e-07,
+ 6.539195979899496e-07,
+ 6.790703517587939e-07,
+ 7.042211055276383e-07,
+ 7.293718592964822e-07,
+ 7.545226130653266e-07,
+ 7.796733668341709e-07,
+ 8.048241206030151e-07,
+ 8.301740083621068e-07,
+ 8.556888641201788e-07,
+ 8.813111124754158e-07,
+ 9.070202076003725e-07,
+ 9.32804275141358e-07,
+ 9.586553032153856e-07,
+ 9.845674100953497e-07,
+ 1.01053603858957e-06,
+ 1.0365575222592973e-06,
+ 1.0626288279788578e-06,
+ 1.0887473920755335e-06,
+ 1.1149110103560906e-06,
+ 1.1411177611608193e-06,
+ 1.16736594968478e-06,
+ 1.193654066554068e-06,
+ 1.2199807562803669e-06,
+ 1.2463447927558711e-06,
+ 1.2727450598880405e-06,
+ 1.2991805360656051e-06,
+ 1.325650281532873e-06,
+ 1.352153428007453e-06,
+ 1.3786891700534096e-06,
+ 1.4052567578456553e-06,
+ 1.4318554910497194e-06,
+ 1.4584847136050661e-06,
+ 1.4851438092473548e-06,
+ 1.5118321976402484e-06,
+ 1.538549331014054e-06,
+ 1.5652946912288755e-06,
+ 1.5920677871957232e-06,
+ 1.6188681526013705e-06,
+ 1.6456953438924398e-06,
+ 1.672548938481938e-06,
+ 1.6994285331476313e-06,
+ 1.7263337425966675e-06,
+ 1.7532641981748811e-06,
+ 1.7802195467025574e-06,
+ 1.8071994494211637e-06,
+ 1.8342035810377954e-06,
+ 1.8612316288559887e-06,
+ 1.8882832919831146e-06,
+ 1.915358280605889e-06,
+ 1.942456315326653e-06,
+ 1.969577126554017e-06,
+ 1.9967204539422897e-06,
+ 2.0238860458747588e-06,
+ 2.0510736589865288e-06,
+ 2.078283057723097e-06,
+ 2.1055140139312965e-06,
+ 2.1327663064796202e-06,
+ 2.1600397209052696e-06,
+ 2.18733404908555e-06,
+ 2.214649088931497e-06,
+ 2.2419846441018473e-06,
+ 2.2693405237356292e-06,
+ 2.296716542201859e-06,
+ 2.3241125188649593e-06,
+ 2.3515282778646443e-06,
+ 2.378963647909149e-06,
+ 2.4064184620807856e-06,
+ 2.4338925576528876e-06,
+ 2.4613857759173045e-06,
+ 2.4888979620216786e-06,
+ 2.516428964815798e-06,
+ 2.5439786367063862e-06,
+ 2.5715468335197477e-06,
+ 2.5991334143717214e-06,
+ 2.6267382415444553e-06,
+ 2.654361180369553e-06,
+ 2.6820020991171646e-06,
+ 2.709660868890646e-06,
+ 2.7373373635264315e-06,
+ 2.7650314594987936e-06,
+ 2.7927430358291765e-06,
+ 2.820471973999846e-06,
+ 2.848218157871574e-06,
+ 2.875981473605128e-06,
+ 2.9037618095863474e-06,
+ 2.9315590563545864e-06,
+ 2.959373106534339e-06,
+ 2.9872038547698705e-06,
+ 3.015051197662676e-06,
+ 3.042915033711618e-06,
+ 3.0707952632556034e-06,
+ 3.0986917884186447e-06,
+ 3.126604513057201e-06,
+ 3.1545333427096633e-06,
+ 3.1824781845478775e-06,
+ 3.210438947330603e-06,
+ 3.23841554135881e-06,
+ 3.2664078784327137e-06,
+ 3.2944158718104693e-06,
+ 3.3224394361684423e-06,
+ 3.3504784875629817e-06,
+ 3.3785329433936138e-06,
+ 3.4066027223676035e-06,
+ 3.4346877444657973e-06,
+ 3.462787930909726e-06,
+ 3.4909032041298583e-06,
+ 3.5190334877350003e-06,
+ 3.5471787064827577e-06,
+ 3.575338786251029e-06,
+ 3.603513654010475e-06,
+ 3.6317032377979335e-06,
+ 3.6599074666907237e-06,
+ 3.688126270781812e-06,
+ 3.7163595811558006e-06,
+ 3.744607329865704e-06,
+ 3.772869449910475e-06,
+ 3.80114587521326e-06,
+ 3.829436540600347e-06,
+ 3.857741381780773e-06,
+ 3.886060335326582e-06,
+ 3.9143933386536785e-06,
+ 3.942740330003286e-06,
+ 3.971101248423967e-06,
+ 3.999476033754181e-06,
+ 4.0278646266053704e-06,
+ 4.056266968345549e-06,
+ 4.0846830010833714e-06,
+ 4.113112667652673e-06,
+ 4.141555911597451e-06,
+ 4.1700126771572856e-06,
+ 4.198482909253172e-06,
+ 4.226966553473749e-06,
+ 4.255463556061927e-06,
+ 4.283973863901869e-06,
+ 4.312497424506353e-06,
+ 4.341034186004464e-06,
+ 4.369584097129627e-06,
+ 4.398147107207964e-06,
+ 4.426723166146958e-06,
+ 4.455312224424423e-06,
+ 4.483914233077761e-06,
+ 4.512529143693505e-06,
+ 4.5411569083971295e-06,
+ 4.569797479843123e-06,
+ 4.598450811205317e-06,
+ 4.6271168561674655e-06,
+ 4.6271168561674655e-06,
+ 4.598450811205317e-06,
+ 4.569797479843123e-06,
+ 4.5411569083971295e-06,
+ 4.512529143693505e-06,
+ 4.483914233077761e-06,
+ 4.455312224424423e-06,
+ 4.426723166146958e-06,
+ 4.398147107207964e-06,
+ 4.369584097129627e-06,
+ 4.341034186004464e-06,
+ 4.312497424506353e-06,
+ 4.283973863901869e-06,
+ 4.255463556061927e-06,
+ 4.226966553473749e-06,
+ 4.198482909253172e-06,
+ 4.1700126771572856e-06,
+ 4.141555911597451e-06,
+ 4.113112667652673e-06,
+ 4.0846830010833714e-06,
+ 4.056266968345549e-06,
+ 4.0278646266053704e-06,
+ 3.999476033754181e-06,
+ 3.971101248423967e-06,
+ 3.942740330003286e-06,
+ 3.9143933386536785e-06,
+ 3.886060335326582e-06,
+ 3.857741381780773e-06,
+ 3.829436540600347e-06,
+ 3.80114587521326e-06,
+ 3.772869449910475e-06,
+ 3.744607329865704e-06,
+ 3.7163595811558006e-06,
+ 3.688126270781812e-06,
+ 3.6599074666907237e-06,
+ 3.6317032377979335e-06,
+ 3.603513654010475e-06,
+ 3.575338786251029e-06,
+ 3.5471787064827577e-06,
+ 3.5190334877350003e-06,
+ 3.4909032041298583e-06,
+ 3.462787930909726e-06,
+ 3.4346877444657973e-06,
+ 3.4066027223676035e-06,
+ 3.3785329433936138e-06,
+ 3.3504784875629817e-06,
+ 3.3224394361684423e-06,
+ 3.2944158718104693e-06,
+ 3.2664078784327137e-06,
+ 3.23841554135881e-06,
+ 3.210438947330603e-06,
+ 3.1824781845478775e-06,
+ 3.1545333427096633e-06,
+ 3.126604513057201e-06,
+ 3.0986917884186447e-06,
+ 3.0707952632556034e-06,
+ 3.042915033711618e-06,
+ 3.015051197662676e-06,
+ 2.9872038547698705e-06,
+ 2.959373106534339e-06,
+ 2.9315590563545864e-06,
+ 2.9037618095863474e-06,
+ 2.875981473605128e-06,
+ 2.848218157871574e-06,
+ 2.820471973999846e-06,
+ 2.7927430358291765e-06,
+ 2.7650314594987936e-06,
+ 2.7373373635264315e-06,
+ 2.709660868890646e-06,
+ 2.6820020991171646e-06,
+ 2.654361180369553e-06,
+ 2.6267382415444553e-06,
+ 2.5991334143717214e-06,
+ 2.5715468335197477e-06,
+ 2.5439786367063862e-06,
+ 2.516428964815798e-06,
+ 2.4888979620216786e-06,
+ 2.4613857759173045e-06,
+ 2.4338925576528876e-06,
+ 2.4064184620807856e-06,
+ 2.378963647909149e-06,
+ 2.3515282778646443e-06,
+ 2.3241125188649593e-06,
+ 2.296716542201859e-06,
+ 2.2693405237356292e-06,
+ 2.2419846441018473e-06,
+ 2.214649088931497e-06,
+ 2.18733404908555e-06,
+ 2.1600397209052696e-06,
+ 2.1327663064796202e-06,
+ 2.1055140139312965e-06,
+ 2.078283057723097e-06,
+ 2.0510736589865288e-06,
+ 2.0238860458747588e-06,
+ 1.9967204539422897e-06,
+ 1.969577126554017e-06,
+ 1.942456315326653e-06,
+ 1.915358280605889e-06,
+ 1.8882832919831146e-06,
+ 1.8612316288559887e-06,
+ 1.8342035810377954e-06,
+ 1.8071994494211637e-06,
+ 1.7802195467025574e-06,
+ 1.7532641981748811e-06,
+ 1.7263337425966675e-06,
+ 1.6994285331476313e-06,
+ 1.672548938481938e-06,
+ 1.6456953438924398e-06,
+ 1.6188681526013705e-06,
+ 1.5920677871957232e-06,
+ 1.5652946912288755e-06,
+ 1.538549331014054e-06,
+ 1.5118321976402484e-06,
+ 1.4851438092473548e-06,
+ 1.4584847136050661e-06,
+ 1.4318554910497194e-06,
+ 1.4052567578456553e-06,
+ 1.3786891700534096e-06,
+ 1.352153428007453e-06,
+ 1.325650281532873e-06,
+ 1.2991805360656051e-06,
+ 1.2727450598880405e-06,
+ 1.2463447927558711e-06,
+ 1.2199807562803669e-06,
+ 1.193654066554068e-06,
+ 1.16736594968478e-06,
+ 1.1411177611608193e-06,
+ 1.1149110103560906e-06,
+ 1.0887473920755335e-06,
+ 1.0626288279788578e-06,
+ 1.0365575222592973e-06,
+ 1.01053603858957e-06,
+ 9.845674100953497e-07,
+ 9.586553032153856e-07,
+ 9.32804275141358e-07,
+ 9.070202076003725e-07,
+ 8.813111124754158e-07,
+ 8.556888641201788e-07,
+ 8.301740083621068e-07,
+ 8.048241206030151e-07,
+ 7.796733668341709e-07,
+ 7.545226130653266e-07,
+ 7.293718592964822e-07,
+ 7.042211055276383e-07,
+ 6.790703517587939e-07,
+ 6.539195979899496e-07,
+ 6.287688442211056e-07,
+ 6.036180904522613e-07,
+ 5.784673366834169e-07,
+ 5.533165829145728e-07,
+ 5.281658291457286e-07,
+ 5.030150753768845e-07,
+ 4.778643216080401e-07,
+ 4.52713567839196e-07,
+ 4.2756281407035185e-07,
+ 4.024120603015075e-07,
+ 3.7726130653266334e-07,
+ 3.521105527638192e-07,
+ 3.2695979899497484e-07,
+ 3.018090452261305e-07,
+ 2.7665829145728655e-07,
+ 2.515075376884422e-07,
+ 2.2635678391959805e-07,
+ 2.012060301507537e-07,
+ 1.7605527638190955e-07,
+ 1.509045226130653e-07,
+ 1.2575376884422104e-07,
+ 1.006030150753769e-07,
+ 7.545226130653265e-08,
+ 5.0301507537688396e-08,
+ 2.5150753768844145e-08,
+ 0.0,
+ -2.515075376884425e-08,
+ -5.03015075376885e-08,
+ -7.545226130653265e-08,
+ -1.006030150753769e-07,
+ -1.2575376884422115e-07,
+ -1.509045226130654e-07,
+ -1.7605527638190955e-07,
+ -2.012060301507538e-07,
+ -2.2635678391959805e-07,
+ -2.5150753768844225e-07,
+ -2.7665829145728644e-07,
+ -3.018090452261307e-07,
+ -3.2695979899497495e-07,
+ -3.5211055276381915e-07,
+ -3.7726130653266334e-07,
+ -4.0241206030150754e-07,
+ -4.275628140703518e-07,
+ -4.5271356783919604e-07,
+ -4.778643216080402e-07,
+ -5.030150753768845e-07,
+ -5.281658291457287e-07,
+ -5.533165829145729e-07,
+ -5.784673366834171e-07,
+ -6.036180904522614e-07,
+ -6.287688442211055e-07,
+ -6.539195979899498e-07,
+ -6.79070351758794e-07,
+ -7.042211055276383e-07
+ ]
+ },
+ "P07": {
+ "contact": {
+ "deviation_from_baseline": 62,
+ "fit_constant_line": 83,
+ "fit_constant_polynomial": 52,
+ "fit_line_polynomial": 51
+ },
+ "force": [
+ 6.678816687038591e-11,
+ 4.77192084026739e-11,
+ 6.993132697732072e-11,
+ 2.3026508099489042e-11,
+ 4.844100113212087e-11,
+ 6.561686967023641e-11,
+ 2.9158573451652224e-11,
+ 4.852352167226248e-11,
+ 4.49721698068985e-11,
+ 3.657189178557353e-11,
+ 4.5957483508217975e-11,
+ 8.18728925683958e-11,
+ 7.980041125509706e-11,
+ 7.301343259920574e-11,
+ 7.173148638513673e-11,
+ 3.812563319863093e-11,
+ 9.579050807270794e-11,
+ 5.947204286268922e-11,
+ 4.683012570988472e-11,
+ 6.978491113525835e-11,
+ 5.843017760069614e-11,
+ 8.652451122661995e-11,
+ 4.798516710705007e-11,
+ 6.349283583811208e-11,
+ 8.363899125773672e-11,
+ 3.68672553418133e-11,
+ 4.432221768327658e-11,
+ 7.848740603809644e-11,
+ 3.3644922265450625e-11,
+ 1.0541828768836971e-11,
+ 6.637902461213226e-11,
+ 8.007817146115206e-11,
+ 5.886113308681641e-11,
+ 3.743429091838339e-11,
+ 8.101713049059935e-11,
+ 5.693858619634767e-11,
+ 7.515035195878099e-11,
+ 8.216702878899796e-11,
+ 6.951549365502667e-11,
+ 2.8674363749832014e-11,
+ 7.684320387895266e-11,
+ 4.009999819531247e-11,
+ 6.315804171555144e-11,
+ 4.5264051809363555e-11,
+ 3.917080617618538e-11,
+ 4.8936673536452754e-11,
+ 5.199981872179265e-11,
+ 5.153810289354605e-11,
+ 8.976323896887115e-11,
+ 6.377311793355086e-11,
+ 1.0940710972178782e-11,
+ 3.3963386511022755e-11,
+ 1.1349246461053753e-10,
+ 9.300116946463623e-11,
+ 5.533222661122514e-11,
+ 6.733344242806827e-11,
+ 8.042703706322432e-11,
+ 8.232013540956945e-11,
+ 1.0375211539457145e-10,
+ 4.40140164227723e-11,
+ 5.4929025398691475e-11,
+ 1.2506035007589009e-10,
+ 1.7228358147311786e-10,
+ 1.950189417353615e-10,
+ 2.6061957743486656e-10,
+ 3.340256657580175e-10,
+ 3.8916074198706507e-10,
+ 4.934011328911004e-10,
+ 5.700103223992842e-10,
+ 6.560099826527309e-10,
+ 7.343385189307256e-10,
+ 8.758603449323135e-10,
+ 9.475145889048767e-10,
+ 1.051893258764099e-09,
+ 1.1902742458679909e-09,
+ 1.3224399535300659e-09,
+ 1.4067599627243901e-09,
+ 1.5423363356053181e-09,
+ 1.6252048581706664e-09,
+ 1.8077148094321478e-09,
+ 1.935845774837351e-09,
+ 2.0683092333789484e-09,
+ 2.167208403103279e-09,
+ 2.361537594482749e-09,
+ 2.506682657515791e-09,
+ 2.6010808453492284e-09,
+ 2.8096108900444e-09,
+ 2.9584396733016926e-09,
+ 3.075841477329624e-09,
+ 3.2549841710721134e-09,
+ 3.428626547230111e-09,
+ 3.61385039060742e-09,
+ 3.774351740732691e-09,
+ 3.955767168591285e-09,
+ 4.136102501840642e-09,
+ 4.286585269275385e-09,
+ 4.475318729381176e-09,
+ 4.653672874569118e-09,
+ 4.825460446590636e-09,
+ 5.0463507090312955e-09,
+ 5.259964358039544e-09,
+ 5.4268619122885946e-09,
+ 5.59831836077298e-09,
+ 5.8473723648738925e-09,
+ 5.978456336817841e-09,
+ 6.2007199688564155e-09,
+ 6.401812348324864e-09,
+ 6.633145472476894e-09,
+ 6.848673714159505e-09,
+ 7.082806364543547e-09,
+ 7.261595352818939e-09,
+ 7.456820984571989e-09,
+ 7.685553773214648e-09,
+ 7.907580786167399e-09,
+ 8.12561780446985e-09,
+ 8.36072822912663e-09,
+ 8.58995160800113e-09,
+ 8.805013540255469e-09,
+ 9.071904469542481e-09,
+ 9.22109851219088e-09,
+ 9.50991997230585e-09,
+ 9.727160617652701e-09,
+ 9.985662689955689e-09,
+ 1.019069754554119e-08,
+ 1.0463085270880268e-08,
+ 1.0695090661231525e-08,
+ 1.0914492580123347e-08,
+ 1.1171624691888601e-08,
+ 1.145673445657221e-08,
+ 1.164424988248455e-08,
+ 1.1886599296169178e-08,
+ 1.2181608749562006e-08,
+ 1.2443021046008356e-08,
+ 1.267161249988741e-08,
+ 1.2950606351368674e-08,
+ 1.3189913253484539e-08,
+ 1.3465675227561064e-08,
+ 1.375631445493709e-08,
+ 1.400992490094979e-08,
+ 1.4293151147195861e-08,
+ 1.4545542978215315e-08,
+ 1.483826736521368e-08,
+ 1.506647588936316e-08,
+ 1.5333257370426015e-08,
+ 1.562283265539761e-08,
+ 1.5884554134450056e-08,
+ 1.6209741726638967e-08,
+ 1.6438432164223307e-08,
+ 1.675825509232144e-08,
+ 1.704629400303457e-08,
+ 1.7309038273539823e-08,
+ 1.7597266689463386e-08,
+ 1.7911558330035317e-08,
+ 1.8204447431173995e-08,
+ 1.8498622389226943e-08,
+ 1.8744424396226394e-08,
+ 1.9043220338440184e-08,
+ 1.9336684817424694e-08,
+ 1.9647548438107685e-08,
+ 1.994093227128368e-08,
+ 2.0260894602514432e-08,
+ 2.055273268928052e-08,
+ 2.0875624214468657e-08,
+ 2.116442906180257e-08,
+ 2.1499273736479647e-08,
+ 2.1763114103867138e-08,
+ 2.2115723140907655e-08,
+ 2.241002190235305e-08,
+ 2.2713209284673133e-08,
+ 2.303155377794619e-08,
+ 2.334723255131119e-08,
+ 2.3652174659228893e-08,
+ 2.3946668957820035e-08,
+ 2.4312925291353254e-08,
+ 2.461189080023499e-08,
+ 2.4945575580285984e-08,
+ 2.5233243981519617e-08,
+ 2.5590186484575137e-08,
+ 2.5907732415580764e-08,
+ 2.625592254803121e-08,
+ 2.6579093113962204e-08,
+ 2.6897320484785145e-08,
+ 2.72514810945032e-08,
+ 2.7565002164427567e-08,
+ 2.792809166630146e-08,
+ 2.823997721214409e-08,
+ 2.8604398695585683e-08,
+ 2.8898095976733526e-08,
+ 2.9218223017852754e-08,
+ 2.9576654287143755e-08,
+ 2.995265337724558e-08,
+ 3.0269692695123895e-08,
+ 3.062941921124737e-08,
+ 3.096546284579708e-08,
+ 3.1340276033991925e-08,
+ 3.1653767158591695e-08,
+ 3.2011739100983965e-08,
+ 3.2371446259630516e-08,
+ 3.2738979352313954e-08,
+ 3.307412621105482e-08,
+ 3.307412621105482e-08,
+ 3.2738979352313954e-08,
+ 3.2371446259630516e-08,
+ 3.2011739100983965e-08,
+ 3.1653767158591695e-08,
+ 3.1340276033991925e-08,
+ 3.096546284579708e-08,
+ 3.062941921124737e-08,
+ 3.0269692695123895e-08,
+ 2.995265337724558e-08,
+ 2.9576654287143755e-08,
+ 2.9218223017852754e-08,
+ 2.8898095976733526e-08,
+ 2.8604398695585683e-08,
+ 2.823997721214409e-08,
+ 2.792809166630146e-08,
+ 2.7565002164427567e-08,
+ 2.72514810945032e-08,
+ 2.6897320484785145e-08,
+ 2.6579093113962204e-08,
+ 2.625592254803121e-08,
+ 2.5907732415580764e-08,
+ 2.5590186484575137e-08,
+ 2.5233243981519617e-08,
+ 2.4945575580285984e-08,
+ 2.461189080023499e-08,
+ 2.4312925291353254e-08,
+ 2.3946668957820035e-08,
+ 2.3652174659228893e-08,
+ 2.334723255131119e-08,
+ 2.303155377794619e-08,
+ 2.2713209284673133e-08,
+ 2.241002190235305e-08,
+ 2.2115723140907655e-08,
+ 2.1763114103867138e-08,
+ 2.1499273736479647e-08,
+ 2.116442906180257e-08,
+ 2.0875624214468657e-08,
+ 2.055273268928052e-08,
+ 2.0260894602514432e-08,
+ 1.994093227128368e-08,
+ 1.9647548438107685e-08,
+ 1.9336684817424694e-08,
+ 1.9043220338440184e-08,
+ 1.8744424396226394e-08,
+ 1.8498622389226943e-08,
+ 1.8204447431173995e-08,
+ 1.7911558330035317e-08,
+ 1.7597266689463386e-08,
+ 1.7309038273539823e-08,
+ 1.704629400303457e-08,
+ 1.675825509232144e-08,
+ 1.6438432164223307e-08,
+ 1.6209741726638967e-08,
+ 1.5884554134450056e-08,
+ 1.562283265539761e-08,
+ 1.5333257370426015e-08,
+ 1.506647588936316e-08,
+ 1.483826736521368e-08,
+ 1.4545542978215315e-08,
+ 1.4293151147195861e-08,
+ 1.400992490094979e-08,
+ 1.375631445493709e-08,
+ 1.3465675227561064e-08,
+ 1.3189913253484539e-08,
+ 1.2950606351368674e-08,
+ 1.267161249988741e-08,
+ 1.2443021046008356e-08,
+ 1.2181608749562006e-08,
+ 1.1886599296169178e-08,
+ 1.164424988248455e-08,
+ 1.145673445657221e-08,
+ 1.1171624691888601e-08,
+ 1.0914492580123347e-08,
+ 1.0695090661231525e-08,
+ 1.0463085270880268e-08,
+ 1.019069754554119e-08,
+ 9.985662689955689e-09,
+ 9.727160617652701e-09,
+ 9.50991997230585e-09,
+ 9.22109851219088e-09,
+ 9.071904469542481e-09,
+ 8.805013540255469e-09,
+ 8.58995160800113e-09,
+ 8.36072822912663e-09,
+ 8.12561780446985e-09,
+ 7.907580786167399e-09,
+ 7.685553773214648e-09,
+ 7.456820984571989e-09,
+ 7.261595352818939e-09,
+ 7.082806364543547e-09,
+ 6.848673714159505e-09,
+ 6.633145472476894e-09,
+ 6.401812348324864e-09,
+ 6.2007199688564155e-09,
+ 5.978456336817841e-09,
+ 5.8473723648738925e-09,
+ 5.59831836077298e-09,
+ 5.4268619122885946e-09,
+ 5.259964358039544e-09,
+ 5.0463507090312955e-09,
+ 4.825460446590636e-09,
+ 4.653672874569118e-09,
+ 4.475318729381176e-09,
+ 4.286585269275385e-09,
+ 4.136102501840642e-09,
+ 3.955767168591285e-09,
+ 3.774351740732691e-09,
+ 3.61385039060742e-09,
+ 3.428626547230111e-09,
+ 3.2549841710721134e-09,
+ 3.075841477329624e-09,
+ 2.9584396733016926e-09,
+ 2.8096108900444e-09,
+ 2.6010808453492284e-09,
+ 2.506682657515791e-09,
+ 2.361537594482749e-09,
+ 2.167208403103279e-09,
+ 2.0683092333789484e-09,
+ 1.935845774837351e-09,
+ 1.8077148094321478e-09,
+ 1.6252048581706664e-09,
+ 1.5423363356053181e-09,
+ 1.4067599627243901e-09,
+ 1.3224399535300659e-09,
+ 1.1902742458679909e-09,
+ 1.051893258764099e-09,
+ 9.475145889048767e-10,
+ 8.758603449323135e-10,
+ 7.343385189307256e-10,
+ 6.560099826527309e-10,
+ 5.700103223992842e-10,
+ 4.934011328911004e-10,
+ 3.8916074198706507e-10,
+ 3.340256657580175e-10,
+ 2.6061957743486656e-10,
+ 1.950189417353615e-10,
+ 1.7228358147311786e-10,
+ 1.2506035007589009e-10,
+ 5.4929025398691475e-11,
+ 4.40140164227723e-11,
+ 1.0375211539457145e-10,
+ 8.232013540956945e-11,
+ 8.042703706322432e-11,
+ 6.733344242806827e-11,
+ 5.533222661122514e-11,
+ 9.300116946463623e-11,
+ 1.1349246461053753e-10,
+ 3.3963386511022755e-11,
+ 1.0940710972178782e-11,
+ 6.377311793355086e-11,
+ 8.976323896887115e-11,
+ 5.153810289354605e-11,
+ 4.9570701879775574e-11,
+ 4.407503143097418e-11,
+ 3.1870157501483386e-11,
+ 3.5539731791595555e-11,
+ 5.10214612755128e-11,
+ 2.55115581010373e-11,
+ 5.986072991687784e-11,
+ 9.215748842054454e-12,
+ 4.766680743364398e-11,
+ 5.790101286879774e-11,
+ 4.844798784366527e-11,
+ 2.7789048613761896e-11,
+ 4.946131274077124e-11,
+ 3.406766730364187e-12,
+ 2.2424764625238317e-11,
+ 4.123275585283845e-11,
+ 2.5090799086291653e-11,
+ -3.322995274345812e-11,
+ -1.2534082642033628e-11,
+ 2.9922198990933656e-11,
+ -6.705589251707779e-12,
+ -1.659732359535995e-11,
+ 2.779007570249036e-11,
+ 5.1948763259879575e-12,
+ -1.2757351102125055e-11,
+ 2.3389695954795395e-11,
+ -7.161367247648542e-12,
+ 1.7747826247870605e-12,
+ -2.3631762607735925e-11,
+ -1.340718445401161e-11,
+ 2.0516836178527554e-11,
+ -3.963336198584318e-11,
+ -8.424576380945533e-12,
+ -9.570953873837864e-12,
+ -5.206975744630772e-12,
+ -5.56205371757597e-12,
+ -4.394175506983236e-11,
+ -5.576598564801361e-11,
+ -4.978714814742305e-11,
+ -4.866192555047144e-11,
+ -7.04751623270062e-11,
+ -3.641117547404319e-11,
+ -5.6033215802030717e-11,
+ -8.390184709591927e-11,
+ -3.9381236149435335e-11,
+ -6.404439639749223e-11,
+ -4.7386562197170955e-11
+ ],
+ "height": [
+ 0.0,
+ 2.5125628140703518e-08,
+ 5.0251256281407036e-08,
+ 7.537688442211056e-08,
+ 1.0050251256281407e-07,
+ 1.2562814070351758e-07,
+ 1.5075376884422112e-07,
+ 1.7587939698492463e-07,
+ 2.0100502512562815e-07,
+ 2.2613065326633166e-07,
+ 2.5125628140703517e-07,
+ 2.763819095477387e-07,
+ 3.0150753768844224e-07,
+ 3.2663316582914573e-07,
+ 3.5175879396984927e-07,
+ 3.7688442211055275e-07,
+ 4.020100502512563e-07,
+ 4.2713567839195983e-07,
+ 4.522613065326633e-07,
+ 4.773869346733669e-07,
+ 5.025125628140703e-07,
+ 5.276381909547739e-07,
+ 5.527638190954774e-07,
+ 5.778894472361809e-07,
+ 6.030150753768845e-07,
+ 6.28140703517588e-07,
+ 6.532663316582915e-07,
+ 6.783919597989949e-07,
+ 7.035175879396985e-07,
+ 7.28643216080402e-07,
+ 7.537688442211055e-07,
+ 7.788944723618091e-07,
+ 8.040201005025126e-07,
+ 8.291457286432161e-07,
+ 8.542713567839197e-07,
+ 8.793969849246231e-07,
+ 9.045226130653266e-07,
+ 9.296482412060302e-07,
+ 9.547738693467337e-07,
+ 9.798994974874373e-07,
+ 1.0050251256281407e-06,
+ 1.0301507537688443e-06,
+ 1.0552763819095479e-06,
+ 1.0804020100502512e-06,
+ 1.1055276381909548e-06,
+ 1.1306532663316584e-06,
+ 1.1557788944723618e-06,
+ 1.1809045226130654e-06,
+ 1.206030150753769e-06,
+ 1.2311557788944724e-06,
+ 1.256281407035176e-06,
+ 1.2814070351758793e-06,
+ 1.306532663316583e-06,
+ 1.3316582914572865e-06,
+ 1.3567839195979899e-06,
+ 1.3819095477386935e-06,
+ 1.407035175879397e-06,
+ 1.4321608040201004e-06,
+ 1.457286432160804e-06,
+ 1.4824120603015076e-06,
+ 1.507537688442211e-06,
+ 1.5326633165829146e-06,
+ 1.5577889447236182e-06,
+ 1.5829145728643216e-06,
+ 1.6080402010050252e-06,
+ 1.6331658291457288e-06,
+ 1.6582914572864321e-06,
+ 1.6834170854271357e-06,
+ 1.7085427135678393e-06,
+ 1.7336683417085427e-06,
+ 1.7587939698492463e-06,
+ 1.7839195979899499e-06,
+ 1.8090452261306533e-06,
+ 1.8341708542713568e-06,
+ 1.8592964824120604e-06,
+ 1.8844221105527638e-06,
+ 1.9095477386934674e-06,
+ 1.9346733668341708e-06,
+ 1.9597989949748746e-06,
+ 1.984924623115578e-06,
+ 2.0100502512562813e-06,
+ 2.035175879396985e-06,
+ 2.0603015075376885e-06,
+ 2.085427135678392e-06,
+ 2.1105527638190957e-06,
+ 2.135678391959799e-06,
+ 2.1608040201005025e-06,
+ 2.1859296482412063e-06,
+ 2.2110552763819096e-06,
+ 2.236180904522613e-06,
+ 2.261306532663317e-06,
+ 2.28643216080402e-06,
+ 2.3115577889447236e-06,
+ 2.3366834170854274e-06,
+ 2.3618090452261308e-06,
+ 2.386934673366834e-06,
+ 2.412060301507538e-06,
+ 2.4371859296482413e-06,
+ 2.4623115577889447e-06,
+ 2.487437185929648e-06,
+ 2.512562814070352e-06,
+ 2.5376884422110553e-06,
+ 2.5628140703517587e-06,
+ 2.5879396984924625e-06,
+ 2.613065326633166e-06,
+ 2.6381909547738692e-06,
+ 2.663316582914573e-06,
+ 2.6884422110552764e-06,
+ 2.7135678391959798e-06,
+ 2.7386934673366836e-06,
+ 2.763819095477387e-06,
+ 2.7889447236180903e-06,
+ 2.814070351758794e-06,
+ 2.8391959798994975e-06,
+ 2.864321608040201e-06,
+ 2.8894472361809047e-06,
+ 2.914572864321608e-06,
+ 2.9396984924623115e-06,
+ 2.9648241206030153e-06,
+ 2.9899497487437186e-06,
+ 3.015075376884422e-06,
+ 3.040201005025126e-06,
+ 3.065326633165829e-06,
+ 3.0904522613065326e-06,
+ 3.1155778894472364e-06,
+ 3.1407035175879398e-06,
+ 3.165829145728643e-06,
+ 3.190954773869347e-06,
+ 3.2160804020100503e-06,
+ 3.2412060301507537e-06,
+ 3.2663316582914575e-06,
+ 3.291457286432161e-06,
+ 3.3165829145728643e-06,
+ 3.341708542713568e-06,
+ 3.3668341708542714e-06,
+ 3.391959798994975e-06,
+ 3.4170854271356786e-06,
+ 3.442211055276382e-06,
+ 3.4673366834170854e-06,
+ 3.492462311557789e-06,
+ 3.5175879396984926e-06,
+ 3.542713567839196e-06,
+ 3.5678391959798997e-06,
+ 3.592964824120603e-06,
+ 3.6180904522613065e-06,
+ 3.6432160804020103e-06,
+ 3.6683417085427137e-06,
+ 3.693467336683417e-06,
+ 3.718592964824121e-06,
+ 3.7437185929648243e-06,
+ 3.7688442211055276e-06,
+ 3.7939698492462314e-06,
+ 3.819095477386935e-06,
+ 3.844221105527638e-06,
+ 3.8693467336683416e-06,
+ 3.894472361809045e-06,
+ 3.919597989949749e-06,
+ 3.9447236180904526e-06,
+ 3.969849246231156e-06,
+ 3.994974874371859e-06,
+ 4.020100502512563e-06,
+ 4.045226130653266e-06,
+ 4.07035175879397e-06,
+ 4.095477386934674e-06,
+ 4.120603015075377e-06,
+ 4.1457286432160804e-06,
+ 4.170854271356784e-06,
+ 4.195979899497487e-06,
+ 4.221105527638191e-06,
+ 4.246231155778895e-06,
+ 4.271356783919598e-06,
+ 4.2964824120603016e-06,
+ 4.321608040201005e-06,
+ 4.346733668341708e-06,
+ 4.3718592964824125e-06,
+ 4.396984924623116e-06,
+ 4.422110552763819e-06,
+ 4.447236180904523e-06,
+ 4.472361809045226e-06,
+ 4.4974874371859294e-06,
+ 4.522613065326634e-06,
+ 4.547738693467337e-06,
+ 4.57286432160804e-06,
+ 4.597989949748744e-06,
+ 4.623115577889447e-06,
+ 4.6482412060301506e-06,
+ 4.673366834170855e-06,
+ 4.698492462311558e-06,
+ 4.7236180904522615e-06,
+ 4.748743718592965e-06,
+ 4.773869346733668e-06,
+ 4.798994974874372e-06,
+ 4.824120603015076e-06,
+ 4.849246231155779e-06,
+ 4.874371859296483e-06,
+ 4.899497487437186e-06,
+ 4.9246231155778894e-06,
+ 4.949748743718593e-06,
+ 4.974874371859296e-06,
+ 5e-06,
+ 5e-06,
+ 4.974874371859296e-06,
+ 4.949748743718593e-06,
+ 4.9246231155778894e-06,
+ 4.899497487437186e-06,
+ 4.874371859296483e-06,
+ 4.849246231155779e-06,
+ 4.824120603015076e-06,
+ 4.798994974874372e-06,
+ 4.773869346733668e-06,
+ 4.748743718592965e-06,
+ 4.7236180904522615e-06,
+ 4.698492462311558e-06,
+ 4.673366834170855e-06,
+ 4.6482412060301506e-06,
+ 4.623115577889447e-06,
+ 4.597989949748744e-06,
+ 4.57286432160804e-06,
+ 4.547738693467337e-06,
+ 4.522613065326634e-06,
+ 4.4974874371859294e-06,
+ 4.472361809045226e-06,
+ 4.447236180904523e-06,
+ 4.422110552763819e-06,
+ 4.396984924623116e-06,
+ 4.3718592964824125e-06,
+ 4.346733668341708e-06,
+ 4.321608040201005e-06,
+ 4.2964824120603016e-06,
+ 4.271356783919598e-06,
+ 4.246231155778895e-06,
+ 4.221105527638191e-06,
+ 4.195979899497487e-06,
+ 4.170854271356784e-06,
+ 4.1457286432160804e-06,
+ 4.120603015075377e-06,
+ 4.095477386934674e-06,
+ 4.07035175879397e-06,
+ 4.045226130653266e-06,
+ 4.020100502512563e-06,
+ 3.994974874371859e-06,
+ 3.969849246231156e-06,
+ 3.9447236180904526e-06,
+ 3.919597989949749e-06,
+ 3.894472361809045e-06,
+ 3.8693467336683416e-06,
+ 3.844221105527638e-06,
+ 3.819095477386935e-06,
+ 3.7939698492462314e-06,
+ 3.7688442211055276e-06,
+ 3.7437185929648243e-06,
+ 3.718592964824121e-06,
+ 3.693467336683417e-06,
+ 3.6683417085427137e-06,
+ 3.6432160804020103e-06,
+ 3.6180904522613065e-06,
+ 3.592964824120603e-06,
+ 3.5678391959798997e-06,
+ 3.542713567839196e-06,
+ 3.5175879396984926e-06,
+ 3.492462311557789e-06,
+ 3.4673366834170854e-06,
+ 3.442211055276382e-06,
+ 3.4170854271356786e-06,
+ 3.391959798994975e-06,
+ 3.3668341708542714e-06,
+ 3.341708542713568e-06,
+ 3.3165829145728643e-06,
+ 3.291457286432161e-06,
+ 3.2663316582914575e-06,
+ 3.2412060301507537e-06,
+ 3.2160804020100503e-06,
+ 3.190954773869347e-06,
+ 3.165829145728643e-06,
+ 3.1407035175879398e-06,
+ 3.1155778894472364e-06,
+ 3.0904522613065326e-06,
+ 3.065326633165829e-06,
+ 3.040201005025126e-06,
+ 3.015075376884422e-06,
+ 2.9899497487437186e-06,
+ 2.9648241206030153e-06,
+ 2.9396984924623115e-06,
+ 2.914572864321608e-06,
+ 2.8894472361809047e-06,
+ 2.864321608040201e-06,
+ 2.8391959798994975e-06,
+ 2.814070351758794e-06,
+ 2.7889447236180903e-06,
+ 2.763819095477387e-06,
+ 2.7386934673366836e-06,
+ 2.7135678391959798e-06,
+ 2.6884422110552764e-06,
+ 2.663316582914573e-06,
+ 2.6381909547738692e-06,
+ 2.613065326633166e-06,
+ 2.5879396984924625e-06,
+ 2.5628140703517587e-06,
+ 2.5376884422110553e-06,
+ 2.512562814070352e-06,
+ 2.487437185929648e-06,
+ 2.4623115577889447e-06,
+ 2.4371859296482413e-06,
+ 2.412060301507538e-06,
+ 2.386934673366834e-06,
+ 2.3618090452261308e-06,
+ 2.3366834170854274e-06,
+ 2.3115577889447236e-06,
+ 2.28643216080402e-06,
+ 2.261306532663317e-06,
+ 2.236180904522613e-06,
+ 2.2110552763819096e-06,
+ 2.1859296482412063e-06,
+ 2.1608040201005025e-06,
+ 2.135678391959799e-06,
+ 2.1105527638190957e-06,
+ 2.085427135678392e-06,
+ 2.0603015075376885e-06,
+ 2.035175879396985e-06,
+ 2.0100502512562813e-06,
+ 1.984924623115578e-06,
+ 1.9597989949748746e-06,
+ 1.9346733668341708e-06,
+ 1.9095477386934674e-06,
+ 1.8844221105527638e-06,
+ 1.8592964824120604e-06,
+ 1.8341708542713568e-06,
+ 1.8090452261306533e-06,
+ 1.7839195979899499e-06,
+ 1.7587939698492463e-06,
+ 1.7336683417085427e-06,
+ 1.7085427135678393e-06,
+ 1.6834170854271357e-06,
+ 1.6582914572864321e-06,
+ 1.6331658291457288e-06,
+ 1.6080402010050252e-06,
+ 1.5829145728643216e-06,
+ 1.5577889447236182e-06,
+ 1.5326633165829146e-06,
+ 1.507537688442211e-06,
+ 1.4824120603015076e-06,
+ 1.457286432160804e-06,
+ 1.4321608040201004e-06,
+ 1.407035175879397e-06,
+ 1.3819095477386935e-06,
+ 1.3567839195979899e-06,
+ 1.3316582914572865e-06,
+ 1.306532663316583e-06,
+ 1.2814070351758793e-06,
+ 1.256281407035176e-06,
+ 1.2311557788944724e-06,
+ 1.206030150753769e-06,
+ 1.1809045226130654e-06,
+ 1.1557788944723618e-06,
+ 1.1306532663316584e-06,
+ 1.1055276381909548e-06,
+ 1.0804020100502512e-06,
+ 1.0552763819095479e-06,
+ 1.0301507537688443e-06,
+ 1.0050251256281407e-06,
+ 9.798994974874373e-07,
+ 9.547738693467337e-07,
+ 9.296482412060302e-07,
+ 9.045226130653266e-07,
+ 8.793969849246231e-07,
+ 8.542713567839197e-07,
+ 8.291457286432161e-07,
+ 8.040201005025126e-07,
+ 7.788944723618091e-07,
+ 7.537688442211055e-07,
+ 7.28643216080402e-07,
+ 7.035175879396985e-07,
+ 6.783919597989949e-07,
+ 6.532663316582915e-07,
+ 6.28140703517588e-07,
+ 6.030150753768845e-07,
+ 5.778894472361809e-07,
+ 5.527638190954774e-07,
+ 5.276381909547739e-07,
+ 5.025125628140703e-07,
+ 4.773869346733669e-07,
+ 4.522613065326633e-07,
+ 4.2713567839195983e-07,
+ 4.020100502512563e-07,
+ 3.7688442211055275e-07,
+ 3.5175879396984927e-07,
+ 3.2663316582914573e-07,
+ 3.0150753768844224e-07,
+ 2.763819095477387e-07,
+ 2.5125628140703517e-07,
+ 2.2613065326633166e-07,
+ 2.0100502512562815e-07,
+ 1.7587939698492463e-07,
+ 1.5075376884422112e-07,
+ 1.2562814070351758e-07,
+ 1.0050251256281407e-07,
+ 7.537688442211056e-08,
+ 5.0251256281407036e-08,
+ 2.5125628140703518e-08,
+ 0.0
+ ],
+ "segment": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ "tip_position": [
+ -1.2074016487654293e-06,
+ -1.182442598966729e-06,
+ -1.157070339223545e-06,
+ -1.1323899171923062e-06,
+ -1.1069856027386638e-06,
+ -1.0816637541946805e-06,
+ -1.0568787659225065e-06,
+ -1.0315350054140378e-06,
+ -1.0064206294993037e-06,
+ -9.813547897336062e-07,
+ -9.561109192871208e-07,
+ -9.306014941328947e-07,
+ -9.054723152124616e-07,
+ -8.803903268530502e-07,
+ -8.552532349374178e-07,
+ -8.304396946527633e-07,
+ -8.04712564530416e-07,
+ -7.799261765960378e-07,
+ -7.549027942368716e-07,
+ -7.295231006438429e-07,
+ -7.044868340018638e-07,
+ -6.790556952291357e-07,
+ -6.542915375590014e-07,
+ -6.289863871440169e-07,
+ -6.036348070095482e-07,
+ -5.789530528618232e-07,
+ -5.537285073776833e-07,
+ -5.282366013545534e-07,
+ -5.035355360301794e-07,
+ -4.786168665904901e-07,
+ -4.5290803093148917e-07,
+ -4.2762098322312016e-07,
+ -4.026834349946926e-07,
+ -3.777479868329379e-07,
+ -3.521618132321302e-07,
+ -3.272529077326968e-07,
+ -3.0192069019969427e-07,
+ -2.767005318087394e-07,
+ -2.516772457223874e-07,
+ -2.2693612816759974e-07,
+ -2.0130405021614815e-07,
+ -1.7652191379360306e-07,
+ -1.511411866211547e-07,
+ -1.2617037577529048e-07,
+ -1.0108144337748796e-07,
+ -7.583376649748941e-08,
+ -5.065318165229805e-08,
+ -2.5507879501456743e-08,
+ 0.0,
+ 2.4865726930350223e-08,
+ 4.946303100144012e-08,
+ 7.48188858975319e-08,
+ 1.007398048192307e-07,
+ 1.2566052000847526e-07,
+ 1.504094587206445e-07,
+ 1.7565509901951663e-07,
+ 2.009116631065717e-07,
+ 2.260562222307386e-07,
+ 2.513961701712922e-07,
+ 2.7592441732227775e-07,
+ 3.011591955527403e-07,
+ 3.26986136940216e-07,
+ 3.5258399739489173e-07,
+ 3.779369791382176e-07,
+ 4.0371861363591625e-07,
+ 4.295783026598513e-07,
+ 4.5525528156284513e-07,
+ 4.814233136125892e-07,
+ 5.073150336483745e-07,
+ 5.333006583916124e-07,
+ 5.592095718950959e-07,
+ 5.857504182958153e-07,
+ 6.115925888762444e-07,
+ 6.377620037155402e-07,
+ 6.642714417272828e-07,
+ 6.907187269446069e-07,
+ 7.166875551772538e-07,
+ 7.431689470467662e-07,
+ 7.691232604131237e-07,
+ 7.960739880664419e-07,
+ 8.224809258611971e-07,
+ 8.489311885873169e-07,
+ 8.750458084252635e-07,
+ 9.021147284797617e-07,
+ 9.286918072507962e-07,
+ 9.547614172698338e-07,
+ 9.81972345857489e-07,
+ 1.0085862618307655e-06,
+ 1.0348859080117483e-06,
+ 1.0618029630898768e-06,
+ 1.0886650149921602e-06,
+ 1.1156428815666368e-06,
+ 1.1423735232085927e-06,
+ 1.1693133056278827e-06,
+ 1.1962422871010795e-06,
+ 1.2228727429161305e-06,
+ 1.2498857056578922e-06,
+ 1.276794875250475e-06,
+ 1.3036383791113936e-06,
+ 1.3309729098765035e-06,
+ 1.3582346745072897e-06,
+ 1.3850292781904835e-06,
+ 1.4118694708160309e-06,
+ 1.4394856389977438e-06,
+ 1.4659221068578868e-06,
+ 1.4932703713189758e-06,
+ 1.5204069232543639e-06,
+ 1.5478458826365878e-06,
+ 1.5751267931941174e-06,
+ 1.6025937478386615e-06,
+ 1.6295072658621186e-06,
+ 1.6565851503203528e-06,
+ 1.6839981063474832e-06,
+ 1.7113440046177138e-06,
+ 1.7386500029414418e-06,
+ 1.7661267353287134e-06,
+ 1.7935445972581616e-06,
+ 1.8208208447214087e-06,
+ 1.8486153821549823e-06,
+ 1.8752329507221696e-06,
+ 1.9032467934640227e-06,
+ 1.9305448280581954e-06,
+ 1.9582554769219287e-06,
+ 1.9854314536184867e-06,
+ 2.0132809590125817e-06,
+ 2.040726641056797e-06,
+ 2.0680462883864187e-06,
+ 2.095743237644775e-06,
+ 2.123719963432315e-06,
+ 2.150720745832142e-06,
+ 2.178269868109692e-06,
+ 2.2063455907843237e-06,
+ 2.23408534188949e-06,
+ 2.2614968845689846e-06,
+ 2.2894124512245e-06,
+ 2.3169311483863628e-06,
+ 2.344814396267832e-06,
+ 2.3728464166822954e-06,
+ 2.400508149283126e-06,
+ 2.4284660398862902e-06,
+ 2.4561155863371882e-06,
+ 2.484168458347875e-06,
+ 2.5115761717300737e-06,
+ 2.5393696146814057e-06,
+ 2.567390995671825e-06,
+ 2.5951338386030537e-06,
+ 2.623511342665646e-06,
+ 2.6509238751821923e-06,
+ 2.679247732603878e-06,
+ 2.707253749851712e-06,
+ 2.7350068206974688e-06,
+ 2.7630147329974077e-06,
+ 2.791283277543831e-06,
+ 2.8193377966959203e-06,
+ 2.847405174417154e-06,
+ 2.8749888226278512e-06,
+ 2.9031024101906936e-06,
+ 2.931162683121242e-06,
+ 2.959396947468775e-06,
+ 2.987456413941239e-06,
+ 3.0157816653942497e-06,
+ 3.0438256744026135e-06,
+ 3.0721802177951993e-06,
+ 3.100193894409242e-06,
+ 3.128667969296716e-06,
+ 3.1564320011112945e-06,
+ 3.1850837196224033e-06,
+ 3.2131523353775603e-06,
+ 3.241309837341465e-06,
+ 3.269618910414899e-06,
+ 3.2979013262892526e-06,
+ 3.3260763755091335e-06,
+ 3.354146946635748e-06,
+ 3.3829351381117834e-06,
+ 3.411050421341305e-06,
+ 3.439512897282518e-06,
+ 3.4675152094355577e-06,
+ 3.4962102626068168e-06,
+ 3.524511350057576e-06,
+ 3.553118879522784e-06,
+ 3.5814762133227987e-06,
+ 3.6097841151717307e-06,
+ 3.6384513494096154e-06,
+ 3.666712188249562e-06,
+ 3.6954687114090044e-06,
+ 3.723713195008134e-06,
+ 3.752483037983254e-06,
+ 3.7805456389354363e-06,
+ 3.808872537487332e-06,
+ 3.837582478320945e-06,
+ 3.8664680973626664e-06,
+ 3.894764118682153e-06,
+ 3.923487011984092e-06,
+ 3.951973076470293e-06,
+ 3.9808468364929445e-06,
+ 4.009107375879646e-06,
+ 4.0378127234442715e-06,
+ 4.0665354231714405e-06,
+ 4.095336382238978e-06,
+ 4.123813478967091e-06,
+ 4.123813478967091e-06,
+ 4.095336382238978e-06,
+ 4.0665354231714405e-06,
+ 4.0378127234442715e-06,
+ 4.009107375879646e-06,
+ 3.9808468364929445e-06,
+ 3.951973076470293e-06,
+ 3.923487011984092e-06,
+ 3.894764118682153e-06,
+ 3.8664680973626664e-06,
+ 3.837582478320945e-06,
+ 3.808872537487332e-06,
+ 3.7805456389354363e-06,
+ 3.752483037983254e-06,
+ 3.723713195008134e-06,
+ 3.6954687114090044e-06,
+ 3.666712188249562e-06,
+ 3.6384513494096154e-06,
+ 3.6097841151717307e-06,
+ 3.5814762133227987e-06,
+ 3.553118879522784e-06,
+ 3.524511350057576e-06,
+ 3.4962102626068168e-06,
+ 3.4675152094355577e-06,
+ 3.439512897282518e-06,
+ 3.411050421341305e-06,
+ 3.3829351381117834e-06,
+ 3.354146946635748e-06,
+ 3.3260763755091335e-06,
+ 3.2979013262892526e-06,
+ 3.269618910414899e-06,
+ 3.241309837341465e-06,
+ 3.2131523353775603e-06,
+ 3.1850837196224033e-06,
+ 3.1564320011112945e-06,
+ 3.128667969296716e-06,
+ 3.100193894409242e-06,
+ 3.0721802177951993e-06,
+ 3.0438256744026135e-06,
+ 3.0157816653942497e-06,
+ 2.987456413941239e-06,
+ 2.959396947468775e-06,
+ 2.931162683121242e-06,
+ 2.9031024101906936e-06,
+ 2.8749888226278512e-06,
+ 2.847405174417154e-06,
+ 2.8193377966959203e-06,
+ 2.791283277543831e-06,
+ 2.7630147329974077e-06,
+ 2.7350068206974688e-06,
+ 2.707253749851712e-06,
+ 2.679247732603878e-06,
+ 2.6509238751821923e-06,
+ 2.623511342665646e-06,
+ 2.5951338386030537e-06,
+ 2.567390995671825e-06,
+ 2.5393696146814057e-06,
+ 2.5115761717300737e-06,
+ 2.484168458347875e-06,
+ 2.4561155863371882e-06,
+ 2.4284660398862902e-06,
+ 2.400508149283126e-06,
+ 2.3728464166822954e-06,
+ 2.344814396267832e-06,
+ 2.3169311483863628e-06,
+ 2.2894124512245e-06,
+ 2.2614968845689846e-06,
+ 2.23408534188949e-06,
+ 2.2063455907843237e-06,
+ 2.178269868109692e-06,
+ 2.150720745832142e-06,
+ 2.123719963432315e-06,
+ 2.095743237644775e-06,
+ 2.0680462883864187e-06,
+ 2.040726641056797e-06,
+ 2.0132809590125817e-06,
+ 1.9854314536184867e-06,
+ 1.9582554769219287e-06,
+ 1.9305448280581954e-06,
+ 1.9032467934640227e-06,
+ 1.8752329507221696e-06,
+ 1.8486153821549823e-06,
+ 1.8208208447214087e-06,
+ 1.7935445972581616e-06,
+ 1.7661267353287134e-06,
+ 1.7386500029414418e-06,
+ 1.7113440046177138e-06,
+ 1.6839981063474832e-06,
+ 1.6565851503203528e-06,
+ 1.6295072658621186e-06,
+ 1.6025937478386615e-06,
+ 1.5751267931941174e-06,
+ 1.5478458826365878e-06,
+ 1.5204069232543639e-06,
+ 1.4932703713189758e-06,
+ 1.4659221068578868e-06,
+ 1.4394856389977438e-06,
+ 1.4118694708160309e-06,
+ 1.3850292781904835e-06,
+ 1.3582346745072897e-06,
+ 1.3309729098765035e-06,
+ 1.3036383791113936e-06,
+ 1.276794875250475e-06,
+ 1.2498857056578922e-06,
+ 1.2228727429161305e-06,
+ 1.1962422871010795e-06,
+ 1.1693133056278827e-06,
+ 1.1423735232085927e-06,
+ 1.1156428815666368e-06,
+ 1.0886650149921602e-06,
+ 1.0618029630898768e-06,
+ 1.0348859080117483e-06,
+ 1.0085862618307655e-06,
+ 9.81972345857489e-07,
+ 9.547614172698338e-07,
+ 9.286918072507962e-07,
+ 9.021147284797617e-07,
+ 8.750458084252635e-07,
+ 8.489311885873169e-07,
+ 8.224809258611971e-07,
+ 7.960739880664419e-07,
+ 7.691232604131237e-07,
+ 7.431689470467662e-07,
+ 7.166875551772538e-07,
+ 6.907187269446069e-07,
+ 6.642714417272828e-07,
+ 6.377620037155402e-07,
+ 6.115925888762444e-07,
+ 5.857504182958153e-07,
+ 5.592095718950959e-07,
+ 5.333006583916124e-07,
+ 5.073150336483745e-07,
+ 4.814233136125892e-07,
+ 4.5525528156284513e-07,
+ 4.295783026598513e-07,
+ 4.0371861363591625e-07,
+ 3.779369791382176e-07,
+ 3.5258399739489173e-07,
+ 3.26986136940216e-07,
+ 3.011591955527403e-07,
+ 2.7592441732227775e-07,
+ 2.513961701712922e-07,
+ 2.260562222307386e-07,
+ 2.009116631065717e-07,
+ 1.7565509901951663e-07,
+ 1.504094587206445e-07,
+ 1.2566052000847526e-07,
+ 1.007398048192307e-07,
+ 7.48188858975319e-08,
+ 4.946303100144012e-08,
+ 2.4865726930350223e-08,
+ 0.0,
+ -2.5507879501456743e-08,
+ -5.065318165229805e-08,
+ -7.583376649748941e-08,
+ -1.0108144337748796e-07,
+ -1.2617037577529048e-07,
+ -1.511411866211547e-07,
+ -1.7652191379360306e-07,
+ -2.0130405021614815e-07,
+ -2.2693612816759974e-07,
+ -2.516772457223874e-07,
+ -2.767005318087394e-07,
+ -3.0192069019969427e-07,
+ -3.272529077326968e-07,
+ -3.521618132321302e-07,
+ -3.777479868329379e-07,
+ -4.026834349946926e-07,
+ -4.2762098322312016e-07,
+ -4.5290803093148917e-07,
+ -4.786168665904901e-07,
+ -5.035355360301794e-07,
+ -5.282366013545534e-07,
+ -5.537285073776833e-07,
+ -5.789530528618232e-07,
+ -6.036348070095482e-07,
+ -6.289863871440169e-07,
+ -6.542915375590014e-07,
+ -6.790556952291357e-07,
+ -7.044868340018638e-07,
+ -7.295231006438429e-07,
+ -7.549027942368716e-07,
+ -7.799261765960378e-07,
+ -8.04712564530416e-07,
+ -8.304396946527633e-07,
+ -8.552532349374178e-07,
+ -8.803903268530502e-07,
+ -9.054723152124616e-07,
+ -9.306014941328947e-07,
+ -9.561109192871208e-07,
+ -9.813547897336062e-07,
+ -1.0064206294993037e-06,
+ -1.0315350054140378e-06,
+ -1.0568787659225065e-06,
+ -1.0816637541946805e-06,
+ -1.1069856027386638e-06,
+ -1.1323899171923062e-06,
+ -1.157070339223545e-06,
+ -1.182442598966729e-06,
+ -1.2074016487654293e-06
+ ]
+ },
+ "P08": {
+ "contact": {
+ "deviation_from_baseline": 52,
+ "fit_constant_line": 83,
+ "fit_constant_polynomial": 51,
+ "fit_line_polynomial": 51
+ },
+ "force": [
+ 3.83847053562108e-11,
+ 4.126664863589919e-11,
+ 3.786389329648837e-11,
+ 3.297768987071596e-11,
+ 3.5667636102821933e-11,
+ 4.377010048840644e-11,
+ 4.09761756671415e-11,
+ 4.9037249948879225e-11,
+ 4.920238408269281e-11,
+ 4.819553979961737e-11,
+ 5.390867797914941e-11,
+ 6.324346358253315e-11,
+ 6.762679798631655e-11,
+ 6.531003539568152e-11,
+ 6.808119205656385e-11,
+ 6.619405843153884e-11,
+ 5.865954379949751e-11,
+ 4.776657265628675e-11,
+ 4.9453216976346263e-11,
+ 4.606785394097048e-11,
+ 3.851685588627296e-11,
+ 4.030445036565992e-11,
+ 4.216013706027387e-11,
+ 4.380266178660049e-11,
+ 4.929263472899357e-11,
+ 5.4029890489398367e-11,
+ 5.162372039744517e-11,
+ 5.155368797324283e-11,
+ 5.095834825588703e-11,
+ 4.8677127442671317e-11,
+ 5.271052005857284e-11,
+ 4.600652487149153e-11,
+ 4.82757489995277e-11,
+ 4.062722662382655e-11,
+ 4.1476790182259106e-11,
+ 3.3713081807342567e-11,
+ 4.35548485197354e-11,
+ 4.786274632945037e-11,
+ 5.90888820090593e-11,
+ 5.965765384084707e-11,
+ 7.465428354951171e-11,
+ 7.217762654056529e-11,
+ 6.187487674001828e-11,
+ 6.230285554114474e-11,
+ 7.049152524784658e-11,
+ 6.60261670167931e-11,
+ 6.558234411775211e-11,
+ 7.31274522897947e-11,
+ 7.4043884068584e-11,
+ 7.164278136256463e-11,
+ 7.69635296602148e-11,
+ 8.457986783882622e-11,
+ 8.874831702281463e-11,
+ 9.421884323191657e-11,
+ 1.0121414723785243e-10,
+ 9.990603833935226e-11,
+ 1.0229688473094134e-10,
+ 1.0803431081554824e-10,
+ 1.1995620217592072e-10,
+ 1.1461311621262713e-10,
+ 1.1572575693393388e-10,
+ 1.412799764259129e-10,
+ 1.7888855744796664e-10,
+ 2.122613941244819e-10,
+ 2.7247545803091456e-10,
+ 3.337437936796732e-10,
+ 4.026692630983717e-10,
+ 4.783419699261232e-10,
+ 5.62651581818919e-10,
+ 6.492653723097071e-10,
+ 7.454298291942126e-10,
+ 8.544058160299536e-10,
+ 9.61666030073028e-10,
+ 1.0775532297879981e-09,
+ 1.1841002677591154e-09,
+ 1.3090067194735536e-09,
+ 1.4185601777813538e-09,
+ 1.541371763128136e-09,
+ 1.6692905976534972e-09,
+ 1.8153432170044197e-09,
+ 1.9431566006154826e-09,
+ 2.0832374318655564e-09,
+ 2.229356219401302e-09,
+ 2.3750370122232107e-09,
+ 2.5229635053617496e-09,
+ 2.6673074061231594e-09,
+ 2.823520377069117e-09,
+ 2.971270515776608e-09,
+ 3.1215134725806445e-09,
+ 3.277693288913694e-09,
+ 3.448594136457805e-09,
+ 3.6140072422405503e-09,
+ 3.786482456429433e-09,
+ 3.962060515463092e-09,
+ 4.143740946630764e-09,
+ 4.3212051786458605e-09,
+ 4.50404063521171e-09,
+ 4.679839338920095e-09,
+ 4.8693599408374835e-09,
+ 5.0580217477690244e-09,
+ 5.253019678064657e-09,
+ 5.442919171954217e-09,
+ 5.649042473536628e-09,
+ 5.8397070808522385e-09,
+ 6.02979826479884e-09,
+ 6.224800815948825e-09,
+ 6.427539694012671e-09,
+ 6.6269715455166145e-09,
+ 6.84464898234806e-09,
+ 7.060043191628048e-09,
+ 7.274165042988706e-09,
+ 7.494771409086864e-09,
+ 7.71134322534502e-09,
+ 7.92802251362945e-09,
+ 8.146448196509785e-09,
+ 8.372297478798811e-09,
+ 8.590783115022539e-09,
+ 8.81509647131681e-09,
+ 9.047553638194885e-09,
+ 9.27714180313207e-09,
+ 9.505020667070843e-09,
+ 9.74180681794935e-09,
+ 9.988454080572084e-09,
+ 1.0227487825030055e-08,
+ 1.0471505251447994e-08,
+ 1.071466649662578e-08,
+ 1.0955736339217168e-08,
+ 1.119690432973525e-08,
+ 1.1436043411236459e-08,
+ 1.1697354331199202e-08,
+ 1.1955476530356832e-08,
+ 1.220751691116315e-08,
+ 1.2457601634424478e-08,
+ 1.2719883197797236e-08,
+ 1.2970369384948641e-08,
+ 1.3230061224927147e-08,
+ 1.3490714608880593e-08,
+ 1.3758512337409256e-08,
+ 1.402923091918046e-08,
+ 1.4292720721038801e-08,
+ 1.4559699918354537e-08,
+ 1.4832450785129842e-08,
+ 1.5110140293532607e-08,
+ 1.5373761038325725e-08,
+ 1.564972050862567e-08,
+ 1.5931367663959766e-08,
+ 1.6215366394030182e-08,
+ 1.649929085688281e-08,
+ 1.6779484226755537e-08,
+ 1.70572129305975e-08,
+ 1.734104291385568e-08,
+ 1.7625096236205485e-08,
+ 1.7903987163735504e-08,
+ 1.819920816522989e-08,
+ 1.8487136922070523e-08,
+ 1.8777689840645225e-08,
+ 1.9074575318589044e-08,
+ 1.9373174952100545e-08,
+ 1.9675050768865132e-08,
+ 1.9986329443153523e-08,
+ 2.0286494998021734e-08,
+ 2.058528902624689e-08,
+ 2.0887550601285697e-08,
+ 2.1192822922789896e-08,
+ 2.148899077207875e-08,
+ 2.1798103664176445e-08,
+ 2.2119384863803254e-08,
+ 2.2436980473869338e-08,
+ 2.2743611188731755e-08,
+ 2.306163427014042e-08,
+ 2.3380077971170962e-08,
+ 2.3686291276475797e-08,
+ 2.4005529297175837e-08,
+ 2.432517177331811e-08,
+ 2.4646097767955883e-08,
+ 2.4962079382558868e-08,
+ 2.528639327844334e-08,
+ 2.5605958421406198e-08,
+ 2.593269994444013e-08,
+ 2.6264777307123263e-08,
+ 2.6598380580251913e-08,
+ 2.693349034168621e-08,
+ 2.7261719870812884e-08,
+ 2.7591218305627032e-08,
+ 2.792338669906894e-08,
+ 2.8255560796530385e-08,
+ 2.8593892490780376e-08,
+ 2.8932267850328173e-08,
+ 2.927048591782574e-08,
+ 2.9613118552369752e-08,
+ 2.9948761499425607e-08,
+ 3.0285445186007425e-08,
+ 3.0638629120827455e-08,
+ 3.100008304968019e-08,
+ 3.135098197272873e-08,
+ 3.1706711731212446e-08,
+ 3.205696123239024e-08,
+ 3.2403625623419386e-08,
+ 3.274682422537129e-08,
+ 3.309122958305068e-08,
+ 3.309122958305068e-08,
+ 3.274682422537129e-08,
+ 3.2403625623419386e-08,
+ 3.205696123239024e-08,
+ 3.1706711731212446e-08,
+ 3.135098197272873e-08,
+ 3.100008304968019e-08,
+ 3.0638629120827455e-08,
+ 3.0285445186007425e-08,
+ 2.9948761499425607e-08,
+ 2.9613118552369752e-08,
+ 2.927048591782574e-08,
+ 2.8932267850328173e-08,
+ 2.8593892490780376e-08,
+ 2.8255560796530385e-08,
+ 2.792338669906894e-08,
+ 2.7591218305627032e-08,
+ 2.7261719870812884e-08,
+ 2.693349034168621e-08,
+ 2.6598380580251913e-08,
+ 2.6264777307123263e-08,
+ 2.593269994444013e-08,
+ 2.5605958421406198e-08,
+ 2.528639327844334e-08,
+ 2.4962079382558868e-08,
+ 2.4646097767955883e-08,
+ 2.432517177331811e-08,
+ 2.4005529297175837e-08,
+ 2.3686291276475797e-08,
+ 2.3380077971170962e-08,
+ 2.306163427014042e-08,
+ 2.2743611188731755e-08,
+ 2.2436980473869338e-08,
+ 2.2119384863803254e-08,
+ 2.1798103664176445e-08,
+ 2.148899077207875e-08,
+ 2.1192822922789896e-08,
+ 2.0887550601285697e-08,
+ 2.058528902624689e-08,
+ 2.0286494998021734e-08,
+ 1.9986329443153523e-08,
+ 1.9675050768865132e-08,
+ 1.9373174952100545e-08,
+ 1.9074575318589044e-08,
+ 1.8777689840645225e-08,
+ 1.8487136922070523e-08,
+ 1.819920816522989e-08,
+ 1.7903987163735504e-08,
+ 1.7625096236205485e-08,
+ 1.734104291385568e-08,
+ 1.70572129305975e-08,
+ 1.6779484226755537e-08,
+ 1.649929085688281e-08,
+ 1.6215366394030182e-08,
+ 1.5931367663959766e-08,
+ 1.564972050862567e-08,
+ 1.5373761038325725e-08,
+ 1.5110140293532607e-08,
+ 1.4832450785129842e-08,
+ 1.4559699918354537e-08,
+ 1.4292720721038801e-08,
+ 1.402923091918046e-08,
+ 1.3758512337409256e-08,
+ 1.3490714608880593e-08,
+ 1.3230061224927147e-08,
+ 1.2970369384948641e-08,
+ 1.2719883197797236e-08,
+ 1.2457601634424478e-08,
+ 1.220751691116315e-08,
+ 1.1955476530356832e-08,
+ 1.1697354331199202e-08,
+ 1.1436043411236459e-08,
+ 1.119690432973525e-08,
+ 1.0955736339217168e-08,
+ 1.071466649662578e-08,
+ 1.0471505251447994e-08,
+ 1.0227487825030055e-08,
+ 9.988454080572084e-09,
+ 9.74180681794935e-09,
+ 9.505020667070843e-09,
+ 9.27714180313207e-09,
+ 9.047553638194885e-09,
+ 8.81509647131681e-09,
+ 8.590783115022539e-09,
+ 8.372297478798811e-09,
+ 8.146448196509785e-09,
+ 7.92802251362945e-09,
+ 7.71134322534502e-09,
+ 7.494771409086864e-09,
+ 7.274165042988706e-09,
+ 7.060043191628048e-09,
+ 6.84464898234806e-09,
+ 6.6269715455166145e-09,
+ 6.427539694012671e-09,
+ 6.224800815948825e-09,
+ 6.02979826479884e-09,
+ 5.8397070808522385e-09,
+ 5.649042473536628e-09,
+ 5.442919171954217e-09,
+ 5.253019678064657e-09,
+ 5.0580217477690244e-09,
+ 4.8693599408374835e-09,
+ 4.679839338920095e-09,
+ 4.50404063521171e-09,
+ 4.3212051786458605e-09,
+ 4.143740946630764e-09,
+ 3.962060515463092e-09,
+ 3.786482456429433e-09,
+ 3.6140072422405503e-09,
+ 3.448594136457805e-09,
+ 3.277693288913694e-09,
+ 3.1215134725806445e-09,
+ 2.971270515776608e-09,
+ 2.823520377069117e-09,
+ 2.6673074061231594e-09,
+ 2.5229635053617496e-09,
+ 2.3750370122232107e-09,
+ 2.229356219401302e-09,
+ 2.0832374318655564e-09,
+ 1.9431566006154826e-09,
+ 1.8153432170044197e-09,
+ 1.6692905976534972e-09,
+ 1.541371763128136e-09,
+ 1.4185601777813538e-09,
+ 1.3090067194735536e-09,
+ 1.1841002677591154e-09,
+ 1.0775532297879981e-09,
+ 9.61666030073028e-10,
+ 8.544058160299536e-10,
+ 7.454298291942126e-10,
+ 6.492653723097071e-10,
+ 5.62651581818919e-10,
+ 4.783419699261232e-10,
+ 4.026692630983717e-10,
+ 3.337437936796732e-10,
+ 2.7247545803091456e-10,
+ 2.122613941244819e-10,
+ 1.7888855744796664e-10,
+ 1.412799764259129e-10,
+ 1.1572575693393388e-10,
+ 1.1461311621262713e-10,
+ 1.1995620217592072e-10,
+ 1.0803431081554824e-10,
+ 1.0229688473094134e-10,
+ 9.990603833935226e-11,
+ 1.0121414723785243e-10,
+ 9.421884323191657e-11,
+ 8.874831702281463e-11,
+ 8.457986783882622e-11,
+ 7.69635296602148e-11,
+ 7.164278136256463e-11,
+ 7.4043884068584e-11,
+ 7.31274522897947e-11,
+ 6.558234411775211e-11,
+ 6.60261670167931e-11,
+ 7.049152524784658e-11,
+ 6.230285554114474e-11,
+ 6.187487674001828e-11,
+ 7.217762654056529e-11,
+ 7.465428354951171e-11,
+ 5.965765384084707e-11,
+ 5.90888820090593e-11,
+ 4.786274632945037e-11,
+ 4.0948950392323e-11,
+ 2.849555593814276e-11,
+ 3.366586479187364e-11,
+ 3.0213983769392454e-11,
+ 3.526898736351914e-11,
+ 3.039597589425615e-11,
+ 3.450547436250919e-11,
+ 2.78664678342593e-11,
+ 2.7548612710729684e-11,
+ 2.5543130976812914e-11,
+ 2.3011798061141233e-11,
+ 2.281902158536672e-11,
+ 1.547542315223601e-11,
+ 7.378328194624065e-12,
+ 3.1326649942490787e-12,
+ -1.3263808764029653e-12,
+ -5.717264031036984e-12,
+ -7.598857318909673e-13,
+ 2.7544564971935062e-14,
+ -4.262283909339603e-12,
+ 4.040527664562869e-12,
+ 8.98140547457405e-12,
+ 8.269055135909437e-12,
+ 2.8935914467772975e-12,
+ 2.6113148996375763e-12,
+ -4.377995736515415e-12,
+ -1.6323884167507894e-11,
+ -2.464437542135565e-11,
+ -2.6237926529138865e-11,
+ -2.900466948775809e-11,
+ -3.967552783172961e-11,
+ -3.9480148107700373e-11,
+ -5.019243940968423e-11,
+ -5.54866085870145e-11,
+ -5.3196783971241915e-11,
+ -5.2391943362834766e-11,
+ -5.7878308376165286e-11
+ ],
+ "height": [
+ 0.0,
+ 2.5125628140703518e-08,
+ 5.0251256281407036e-08,
+ 7.537688442211056e-08,
+ 1.0050251256281407e-07,
+ 1.2562814070351758e-07,
+ 1.5075376884422112e-07,
+ 1.7587939698492463e-07,
+ 2.0100502512562815e-07,
+ 2.2613065326633166e-07,
+ 2.5125628140703517e-07,
+ 2.763819095477387e-07,
+ 3.0150753768844224e-07,
+ 3.2663316582914573e-07,
+ 3.5175879396984927e-07,
+ 3.7688442211055275e-07,
+ 4.020100502512563e-07,
+ 4.2713567839195983e-07,
+ 4.522613065326633e-07,
+ 4.773869346733669e-07,
+ 5.025125628140703e-07,
+ 5.276381909547739e-07,
+ 5.527638190954774e-07,
+ 5.778894472361809e-07,
+ 6.030150753768845e-07,
+ 6.28140703517588e-07,
+ 6.532663316582915e-07,
+ 6.783919597989949e-07,
+ 7.035175879396985e-07,
+ 7.28643216080402e-07,
+ 7.537688442211055e-07,
+ 7.788944723618091e-07,
+ 8.040201005025126e-07,
+ 8.291457286432161e-07,
+ 8.542713567839197e-07,
+ 8.793969849246231e-07,
+ 9.045226130653266e-07,
+ 9.296482412060302e-07,
+ 9.547738693467337e-07,
+ 9.798994974874373e-07,
+ 1.0050251256281407e-06,
+ 1.0301507537688443e-06,
+ 1.0552763819095479e-06,
+ 1.0804020100502512e-06,
+ 1.1055276381909548e-06,
+ 1.1306532663316584e-06,
+ 1.1557788944723618e-06,
+ 1.1809045226130654e-06,
+ 1.206030150753769e-06,
+ 1.2311557788944724e-06,
+ 1.256281407035176e-06,
+ 1.2814070351758793e-06,
+ 1.306532663316583e-06,
+ 1.3316582914572865e-06,
+ 1.3567839195979899e-06,
+ 1.3819095477386935e-06,
+ 1.407035175879397e-06,
+ 1.4321608040201004e-06,
+ 1.457286432160804e-06,
+ 1.4824120603015076e-06,
+ 1.507537688442211e-06,
+ 1.5326633165829146e-06,
+ 1.5577889447236182e-06,
+ 1.5829145728643216e-06,
+ 1.6080402010050252e-06,
+ 1.6331658291457288e-06,
+ 1.6582914572864321e-06,
+ 1.6834170854271357e-06,
+ 1.7085427135678393e-06,
+ 1.7336683417085427e-06,
+ 1.7587939698492463e-06,
+ 1.7839195979899499e-06,
+ 1.8090452261306533e-06,
+ 1.8341708542713568e-06,
+ 1.8592964824120604e-06,
+ 1.8844221105527638e-06,
+ 1.9095477386934674e-06,
+ 1.9346733668341708e-06,
+ 1.9597989949748746e-06,
+ 1.984924623115578e-06,
+ 2.0100502512562813e-06,
+ 2.035175879396985e-06,
+ 2.0603015075376885e-06,
+ 2.085427135678392e-06,
+ 2.1105527638190957e-06,
+ 2.135678391959799e-06,
+ 2.1608040201005025e-06,
+ 2.1859296482412063e-06,
+ 2.2110552763819096e-06,
+ 2.236180904522613e-06,
+ 2.261306532663317e-06,
+ 2.28643216080402e-06,
+ 2.3115577889447236e-06,
+ 2.3366834170854274e-06,
+ 2.3618090452261308e-06,
+ 2.386934673366834e-06,
+ 2.412060301507538e-06,
+ 2.4371859296482413e-06,
+ 2.4623115577889447e-06,
+ 2.487437185929648e-06,
+ 2.512562814070352e-06,
+ 2.5376884422110553e-06,
+ 2.5628140703517587e-06,
+ 2.5879396984924625e-06,
+ 2.613065326633166e-06,
+ 2.6381909547738692e-06,
+ 2.663316582914573e-06,
+ 2.6884422110552764e-06,
+ 2.7135678391959798e-06,
+ 2.7386934673366836e-06,
+ 2.763819095477387e-06,
+ 2.7889447236180903e-06,
+ 2.814070351758794e-06,
+ 2.8391959798994975e-06,
+ 2.864321608040201e-06,
+ 2.8894472361809047e-06,
+ 2.914572864321608e-06,
+ 2.9396984924623115e-06,
+ 2.9648241206030153e-06,
+ 2.9899497487437186e-06,
+ 3.015075376884422e-06,
+ 3.040201005025126e-06,
+ 3.065326633165829e-06,
+ 3.0904522613065326e-06,
+ 3.1155778894472364e-06,
+ 3.1407035175879398e-06,
+ 3.165829145728643e-06,
+ 3.190954773869347e-06,
+ 3.2160804020100503e-06,
+ 3.2412060301507537e-06,
+ 3.2663316582914575e-06,
+ 3.291457286432161e-06,
+ 3.3165829145728643e-06,
+ 3.341708542713568e-06,
+ 3.3668341708542714e-06,
+ 3.391959798994975e-06,
+ 3.4170854271356786e-06,
+ 3.442211055276382e-06,
+ 3.4673366834170854e-06,
+ 3.492462311557789e-06,
+ 3.5175879396984926e-06,
+ 3.542713567839196e-06,
+ 3.5678391959798997e-06,
+ 3.592964824120603e-06,
+ 3.6180904522613065e-06,
+ 3.6432160804020103e-06,
+ 3.6683417085427137e-06,
+ 3.693467336683417e-06,
+ 3.718592964824121e-06,
+ 3.7437185929648243e-06,
+ 3.7688442211055276e-06,
+ 3.7939698492462314e-06,
+ 3.819095477386935e-06,
+ 3.844221105527638e-06,
+ 3.8693467336683416e-06,
+ 3.894472361809045e-06,
+ 3.919597989949749e-06,
+ 3.9447236180904526e-06,
+ 3.969849246231156e-06,
+ 3.994974874371859e-06,
+ 4.020100502512563e-06,
+ 4.045226130653266e-06,
+ 4.07035175879397e-06,
+ 4.095477386934674e-06,
+ 4.120603015075377e-06,
+ 4.1457286432160804e-06,
+ 4.170854271356784e-06,
+ 4.195979899497487e-06,
+ 4.221105527638191e-06,
+ 4.246231155778895e-06,
+ 4.271356783919598e-06,
+ 4.2964824120603016e-06,
+ 4.321608040201005e-06,
+ 4.346733668341708e-06,
+ 4.3718592964824125e-06,
+ 4.396984924623116e-06,
+ 4.422110552763819e-06,
+ 4.447236180904523e-06,
+ 4.472361809045226e-06,
+ 4.4974874371859294e-06,
+ 4.522613065326634e-06,
+ 4.547738693467337e-06,
+ 4.57286432160804e-06,
+ 4.597989949748744e-06,
+ 4.623115577889447e-06,
+ 4.6482412060301506e-06,
+ 4.673366834170855e-06,
+ 4.698492462311558e-06,
+ 4.7236180904522615e-06,
+ 4.748743718592965e-06,
+ 4.773869346733668e-06,
+ 4.798994974874372e-06,
+ 4.824120603015076e-06,
+ 4.849246231155779e-06,
+ 4.874371859296483e-06,
+ 4.899497487437186e-06,
+ 4.9246231155778894e-06,
+ 4.949748743718593e-06,
+ 4.974874371859296e-06,
+ 5e-06,
+ 5e-06,
+ 4.974874371859296e-06,
+ 4.949748743718593e-06,
+ 4.9246231155778894e-06,
+ 4.899497487437186e-06,
+ 4.874371859296483e-06,
+ 4.849246231155779e-06,
+ 4.824120603015076e-06,
+ 4.798994974874372e-06,
+ 4.773869346733668e-06,
+ 4.748743718592965e-06,
+ 4.7236180904522615e-06,
+ 4.698492462311558e-06,
+ 4.673366834170855e-06,
+ 4.6482412060301506e-06,
+ 4.623115577889447e-06,
+ 4.597989949748744e-06,
+ 4.57286432160804e-06,
+ 4.547738693467337e-06,
+ 4.522613065326634e-06,
+ 4.4974874371859294e-06,
+ 4.472361809045226e-06,
+ 4.447236180904523e-06,
+ 4.422110552763819e-06,
+ 4.396984924623116e-06,
+ 4.3718592964824125e-06,
+ 4.346733668341708e-06,
+ 4.321608040201005e-06,
+ 4.2964824120603016e-06,
+ 4.271356783919598e-06,
+ 4.246231155778895e-06,
+ 4.221105527638191e-06,
+ 4.195979899497487e-06,
+ 4.170854271356784e-06,
+ 4.1457286432160804e-06,
+ 4.120603015075377e-06,
+ 4.095477386934674e-06,
+ 4.07035175879397e-06,
+ 4.045226130653266e-06,
+ 4.020100502512563e-06,
+ 3.994974874371859e-06,
+ 3.969849246231156e-06,
+ 3.9447236180904526e-06,
+ 3.919597989949749e-06,
+ 3.894472361809045e-06,
+ 3.8693467336683416e-06,
+ 3.844221105527638e-06,
+ 3.819095477386935e-06,
+ 3.7939698492462314e-06,
+ 3.7688442211055276e-06,
+ 3.7437185929648243e-06,
+ 3.718592964824121e-06,
+ 3.693467336683417e-06,
+ 3.6683417085427137e-06,
+ 3.6432160804020103e-06,
+ 3.6180904522613065e-06,
+ 3.592964824120603e-06,
+ 3.5678391959798997e-06,
+ 3.542713567839196e-06,
+ 3.5175879396984926e-06,
+ 3.492462311557789e-06,
+ 3.4673366834170854e-06,
+ 3.442211055276382e-06,
+ 3.4170854271356786e-06,
+ 3.391959798994975e-06,
+ 3.3668341708542714e-06,
+ 3.341708542713568e-06,
+ 3.3165829145728643e-06,
+ 3.291457286432161e-06,
+ 3.2663316582914575e-06,
+ 3.2412060301507537e-06,
+ 3.2160804020100503e-06,
+ 3.190954773869347e-06,
+ 3.165829145728643e-06,
+ 3.1407035175879398e-06,
+ 3.1155778894472364e-06,
+ 3.0904522613065326e-06,
+ 3.065326633165829e-06,
+ 3.040201005025126e-06,
+ 3.015075376884422e-06,
+ 2.9899497487437186e-06,
+ 2.9648241206030153e-06,
+ 2.9396984924623115e-06,
+ 2.914572864321608e-06,
+ 2.8894472361809047e-06,
+ 2.864321608040201e-06,
+ 2.8391959798994975e-06,
+ 2.814070351758794e-06,
+ 2.7889447236180903e-06,
+ 2.763819095477387e-06,
+ 2.7386934673366836e-06,
+ 2.7135678391959798e-06,
+ 2.6884422110552764e-06,
+ 2.663316582914573e-06,
+ 2.6381909547738692e-06,
+ 2.613065326633166e-06,
+ 2.5879396984924625e-06,
+ 2.5628140703517587e-06,
+ 2.5376884422110553e-06,
+ 2.512562814070352e-06,
+ 2.487437185929648e-06,
+ 2.4623115577889447e-06,
+ 2.4371859296482413e-06,
+ 2.412060301507538e-06,
+ 2.386934673366834e-06,
+ 2.3618090452261308e-06,
+ 2.3366834170854274e-06,
+ 2.3115577889447236e-06,
+ 2.28643216080402e-06,
+ 2.261306532663317e-06,
+ 2.236180904522613e-06,
+ 2.2110552763819096e-06,
+ 2.1859296482412063e-06,
+ 2.1608040201005025e-06,
+ 2.135678391959799e-06,
+ 2.1105527638190957e-06,
+ 2.085427135678392e-06,
+ 2.0603015075376885e-06,
+ 2.035175879396985e-06,
+ 2.0100502512562813e-06,
+ 1.984924623115578e-06,
+ 1.9597989949748746e-06,
+ 1.9346733668341708e-06,
+ 1.9095477386934674e-06,
+ 1.8844221105527638e-06,
+ 1.8592964824120604e-06,
+ 1.8341708542713568e-06,
+ 1.8090452261306533e-06,
+ 1.7839195979899499e-06,
+ 1.7587939698492463e-06,
+ 1.7336683417085427e-06,
+ 1.7085427135678393e-06,
+ 1.6834170854271357e-06,
+ 1.6582914572864321e-06,
+ 1.6331658291457288e-06,
+ 1.6080402010050252e-06,
+ 1.5829145728643216e-06,
+ 1.5577889447236182e-06,
+ 1.5326633165829146e-06,
+ 1.507537688442211e-06,
+ 1.4824120603015076e-06,
+ 1.457286432160804e-06,
+ 1.4321608040201004e-06,
+ 1.407035175879397e-06,
+ 1.3819095477386935e-06,
+ 1.3567839195979899e-06,
+ 1.3316582914572865e-06,
+ 1.306532663316583e-06,
+ 1.2814070351758793e-06,
+ 1.256281407035176e-06,
+ 1.2311557788944724e-06,
+ 1.206030150753769e-06,
+ 1.1809045226130654e-06,
+ 1.1557788944723618e-06,
+ 1.1306532663316584e-06,
+ 1.1055276381909548e-06,
+ 1.0804020100502512e-06,
+ 1.0552763819095479e-06,
+ 1.0301507537688443e-06,
+ 1.0050251256281407e-06,
+ 9.798994974874373e-07,
+ 9.547738693467337e-07,
+ 9.296482412060302e-07,
+ 9.045226130653266e-07,
+ 8.793969849246231e-07,
+ 8.542713567839197e-07,
+ 8.291457286432161e-07,
+ 8.040201005025126e-07,
+ 7.788944723618091e-07,
+ 7.537688442211055e-07,
+ 7.28643216080402e-07,
+ 7.035175879396985e-07,
+ 6.783919597989949e-07,
+ 6.532663316582915e-07,
+ 6.28140703517588e-07,
+ 6.030150753768845e-07,
+ 5.778894472361809e-07,
+ 5.527638190954774e-07,
+ 5.276381909547739e-07,
+ 5.025125628140703e-07,
+ 4.773869346733669e-07,
+ 4.522613065326633e-07,
+ 4.2713567839195983e-07,
+ 4.020100502512563e-07,
+ 3.7688442211055275e-07,
+ 3.5175879396984927e-07,
+ 3.2663316582914573e-07,
+ 3.0150753768844224e-07,
+ 2.763819095477387e-07,
+ 2.5125628140703517e-07,
+ 2.2613065326633166e-07,
+ 2.0100502512562815e-07,
+ 1.7587939698492463e-07,
+ 1.5075376884422112e-07,
+ 1.2562814070351758e-07,
+ 1.0050251256281407e-07,
+ 7.537688442211056e-08,
+ 5.0251256281407036e-08,
+ 2.5125628140703518e-08,
+ 0.0
+ ],
+ "segment": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ "tip_position": [
+ -9.55943541250586e-07,
+ -9.307630494597491e-07,
+ -9.056454697251297e-07,
+ -8.805427398305839e-07,
+ -8.55364169998107e-07,
+ -8.301314189443837e-07,
+ -8.050077446009205e-07,
+ -7.797754078767772e-07,
+ -7.546221123064875e-07,
+ -7.294805486547062e-07,
+ -7.042717156014642e-07,
+ -6.790266285764507e-07,
+ -6.538311073293857e-07,
+ -6.287026564232108e-07,
+ -6.03523273645616e-07,
+ -5.783905220015258e-07,
+ -5.533143026389223e-07,
+ -5.282717026139578e-07,
+ -5.031031761885112e-07,
+ -4.779854223507763e-07,
+ -4.529093679930643e-07,
+ -4.2773983102081437e-07,
+ -4.0256961242140435e-07,
+ -3.774015276486971e-07,
+ -3.521949285584175e-07,
+ -3.2699586443338264e-07,
+ -3.018683085279214e-07,
+ -2.767173670580612e-07,
+ -2.5157168410001843e-07,
+ -2.2644287740807968e-07,
+ -2.0125085920209372e-07,
+ -1.761663260460726e-07,
+ -1.5099196779067656e-07,
+ -1.2591688968591425e-07,
+ -1.0075674273498589e-07,
+ -7.568281768281973e-08,
+ -5.043265559757439e-08,
+ -2.5237889497499612e-08,
+ 0.0,
+ 2.5131315859021395e-08,
+ 5.040691029681141e-08,
+ 7.550777186742556e-08,
+ 1.0053037251012367e-07,
+ 1.256602804388385e-07,
+ 1.5086779527660906e-07,
+ 1.7594876983500197e-07,
+ 2.0106995974671507e-07,
+ 2.2627103896913913e-07,
+ 2.514058314276304e-07,
+ 2.765074485412737e-07,
+ 3.0168628416495375e-07,
+ 3.268880756874433e-07,
+ 3.5205538831998675e-07,
+ 3.772357217227813e-07,
+ 4.02431302903544e-07,
+ 4.2754384995526265e-07,
+ 4.526933865598823e-07,
+ 4.778763889614315e-07,
+ 5.03121236015739e-07,
+ 5.281934332968095e-07,
+ 5.53330187844726e-07,
+ 5.787113581803494e-07,
+ 6.042130721312737e-07,
+ 6.296724286387421e-07,
+ 6.5540019741851e-07,
+ 6.811385089157011e-07,
+ 7.069533917505914e-07,
+ 7.328357469595726e-07,
+ 7.588044712192041e-07,
+ 7.847962372648154e-07,
+ 8.108835099743642e-07,
+ 8.370988979834251e-07,
+ 8.632971282645593e-07,
+ 8.895816284024126e-07,
+ 9.157727269228273e-07,
+ 9.42147419580675e-07,
+ 9.683685823044568e-07,
+ 9.947223262986277e-07,
+ 1.0211271427845853e-06,
+ 1.047713297118798e-06,
+ 1.074117059095612e-06,
+ 1.1006434955488164e-06,
+ 1.1272303115648773e-06,
+ 1.1538127476337996e-06,
+ 1.180417640705889e-06,
+ 1.2069867078542063e-06,
+ 1.2336744657043695e-06,
+ 1.2602775952321482e-06,
+ 1.286905652940892e-06,
+ 1.3135930792449255e-06,
+ 1.3404277158610706e-06,
+ 1.3672074750596014e-06,
+ 1.3940578553421937e-06,
+ 1.420939264073234e-06,
+ 1.4478816965256143e-06,
+ 1.4747819669864684e-06,
+ 1.5017359496928308e-06,
+ 1.528619564870618e-06,
+ 1.5556403990304954e-06,
+ 1.582652645240514e-06,
+ 1.6097282526841744e-06,
+ 1.636752875763773e-06,
+ 1.6639397369203005e-06,
+ 1.6909720111341605e-06,
+ 1.7179985511143298e-06,
+ 1.745074204766533e-06,
+ 1.7722272216878756e-06,
+ 1.7993471683436183e-06,
+ 1.8266495708526361e-06,
+ 1.8539291410861397e-06,
+ 1.8811959877404498e-06,
+ 1.9085276795421347e-06,
+ 1.9358190258454202e-06,
+ 1.963111446868968e-06,
+ 1.9904213318384745e-06,
+ 2.0178054528020685e-06,
+ 2.045115937305009e-06,
+ 2.072484699008655e-06,
+ 2.09993489881814e-06,
+ 2.127356408608215e-06,
+ 2.1547608253883064e-06,
+ 2.182254315037795e-06,
+ 2.2098464158047257e-06,
+ 2.237362381390009e-06,
+ 2.264928183794892e-06,
+ 2.2924854243873733e-06,
+ 2.3200217509539905e-06,
+ 2.347559058999875e-06,
+ 2.3750760779555907e-06,
+ 2.4028148152959213e-06,
+ 2.4305216654282014e-06,
+ 2.4581676973769683e-06,
+ 2.485794172750285e-06,
+ 2.5135426165247163e-06,
+ 2.5411731065369337e-06,
+ 2.568895653077422e-06,
+ 2.59662781505766e-06,
+ 2.62443142048365e-06,
+ 2.6522642344420657e-06,
+ 2.680024760601353e-06,
+ 2.7078201807152135e-06,
+ 2.73567331752367e-06,
+ 2.7635758407484016e-06,
+ 2.7913376763370363e-06,
+ 2.819222899180739e-06,
+ 2.8471649988747838e-06,
+ 2.8751306143161916e-06,
+ 2.903095487085421e-06,
+ 2.9310230489248517e-06,
+ 2.9589259641039753e-06,
+ 2.98688989207726e-06,
+ 3.014856053441462e-06,
+ 3.042770590857465e-06,
+ 3.0708484290131126e-06,
+ 3.098853344722223e-06,
+ 3.1268845020486726e-06,
+ 3.1549789849688154e-06,
+ 3.183090609444634e-06,
+ 3.2112349957529833e-06,
+ 3.2394734106365706e-06,
+ 3.267600694325956e-06,
+ 3.295714262748911e-06,
+ 3.3238625066400026e-06,
+ 3.352040857995748e-06,
+ 3.3801281646293405e-06,
+ 3.4083449216910203e-06,
+ 3.4366833618279922e-06,
+ 3.464984946069356e-06,
+ 3.4931768813586847e-06,
+ 3.5214827403134748e-06,
+ 3.5497928054644836e-06,
+ 3.5779805666582356e-06,
+ 3.6062985750059387e-06,
+ 3.6346206279080653e-06,
+ 3.662955515995147e-06,
+ 3.6912409602818805e-06,
+ 3.7196097273814284e-06,
+ 3.7479310069517603e-06,
+ 3.776324050322803e-06,
+ 3.804770452090338e-06,
+ 3.833232112962328e-06,
+ 3.861708838717375e-06,
+ 3.890116762149345e-06,
+ 3.9185373746381894e-06,
+ 3.946984686713312e-06,
+ 3.97543205582863e-06,
+ 4.003941000911834e-06,
+ 4.0324503826480155e-06,
+ 4.060958191463695e-06,
+ 4.0895101459498384e-06,
+ 4.1179922035611e-06,
+ 4.146484668567621e-06,
+ 4.175142136056526e-06,
+ 4.203882303485757e-06,
+ 4.232516920856946e-06,
+ 4.261199846582486e-06,
+ 4.289827969734967e-06,
+ 4.318420241785962e-06,
+ 4.346977855946185e-06,
+ 4.375547537663683e-06,
+ 4.375547537663683e-06,
+ 4.346977855946185e-06,
+ 4.318420241785962e-06,
+ 4.289827969734967e-06,
+ 4.261199846582486e-06,
+ 4.232516920856946e-06,
+ 4.203882303485757e-06,
+ 4.175142136056526e-06,
+ 4.146484668567621e-06,
+ 4.1179922035611e-06,
+ 4.0895101459498384e-06,
+ 4.060958191463695e-06,
+ 4.0324503826480155e-06,
+ 4.003941000911834e-06,
+ 3.97543205582863e-06,
+ 3.946984686713312e-06,
+ 3.9185373746381894e-06,
+ 3.890116762149345e-06,
+ 3.861708838717375e-06,
+ 3.833232112962328e-06,
+ 3.804770452090338e-06,
+ 3.776324050322803e-06,
+ 3.7479310069517603e-06,
+ 3.7196097273814284e-06,
+ 3.6912409602818805e-06,
+ 3.662955515995147e-06,
+ 3.6346206279080653e-06,
+ 3.6062985750059387e-06,
+ 3.5779805666582356e-06,
+ 3.5497928054644836e-06,
+ 3.5214827403134748e-06,
+ 3.4931768813586847e-06,
+ 3.464984946069356e-06,
+ 3.4366833618279922e-06,
+ 3.4083449216910203e-06,
+ 3.3801281646293405e-06,
+ 3.352040857995748e-06,
+ 3.3238625066400026e-06,
+ 3.295714262748911e-06,
+ 3.267600694325956e-06,
+ 3.2394734106365706e-06,
+ 3.2112349957529833e-06,
+ 3.183090609444634e-06,
+ 3.1549789849688154e-06,
+ 3.1268845020486726e-06,
+ 3.098853344722223e-06,
+ 3.0708484290131126e-06,
+ 3.042770590857465e-06,
+ 3.014856053441462e-06,
+ 2.98688989207726e-06,
+ 2.9589259641039753e-06,
+ 2.9310230489248517e-06,
+ 2.903095487085421e-06,
+ 2.8751306143161916e-06,
+ 2.8471649988747838e-06,
+ 2.819222899180739e-06,
+ 2.7913376763370363e-06,
+ 2.7635758407484016e-06,
+ 2.73567331752367e-06,
+ 2.7078201807152135e-06,
+ 2.680024760601353e-06,
+ 2.6522642344420657e-06,
+ 2.62443142048365e-06,
+ 2.59662781505766e-06,
+ 2.568895653077422e-06,
+ 2.5411731065369337e-06,
+ 2.5135426165247163e-06,
+ 2.485794172750285e-06,
+ 2.4581676973769683e-06,
+ 2.4305216654282014e-06,
+ 2.4028148152959213e-06,
+ 2.3750760779555907e-06,
+ 2.347559058999875e-06,
+ 2.3200217509539905e-06,
+ 2.2924854243873733e-06,
+ 2.264928183794892e-06,
+ 2.237362381390009e-06,
+ 2.2098464158047257e-06,
+ 2.182254315037795e-06,
+ 2.1547608253883064e-06,
+ 2.127356408608215e-06,
+ 2.09993489881814e-06,
+ 2.072484699008655e-06,
+ 2.045115937305009e-06,
+ 2.0178054528020685e-06,
+ 1.9904213318384745e-06,
+ 1.963111446868968e-06,
+ 1.9358190258454202e-06,
+ 1.9085276795421347e-06,
+ 1.8811959877404498e-06,
+ 1.8539291410861397e-06,
+ 1.8266495708526361e-06,
+ 1.7993471683436183e-06,
+ 1.7722272216878756e-06,
+ 1.745074204766533e-06,
+ 1.7179985511143298e-06,
+ 1.6909720111341605e-06,
+ 1.6639397369203005e-06,
+ 1.636752875763773e-06,
+ 1.6097282526841744e-06,
+ 1.582652645240514e-06,
+ 1.5556403990304954e-06,
+ 1.528619564870618e-06,
+ 1.5017359496928308e-06,
+ 1.4747819669864684e-06,
+ 1.4478816965256143e-06,
+ 1.420939264073234e-06,
+ 1.3940578553421937e-06,
+ 1.3672074750596014e-06,
+ 1.3404277158610706e-06,
+ 1.3135930792449255e-06,
+ 1.286905652940892e-06,
+ 1.2602775952321482e-06,
+ 1.2336744657043695e-06,
+ 1.2069867078542063e-06,
+ 1.180417640705889e-06,
+ 1.1538127476337996e-06,
+ 1.1272303115648773e-06,
+ 1.1006434955488164e-06,
+ 1.074117059095612e-06,
+ 1.047713297118798e-06,
+ 1.0211271427845853e-06,
+ 9.947223262986277e-07,
+ 9.683685823044568e-07,
+ 9.42147419580675e-07,
+ 9.157727269228273e-07,
+ 8.895816284024126e-07,
+ 8.632971282645593e-07,
+ 8.370988979834251e-07,
+ 8.108835099743642e-07,
+ 7.847962372648154e-07,
+ 7.588044712192041e-07,
+ 7.328357469595726e-07,
+ 7.069533917505914e-07,
+ 6.811385089157011e-07,
+ 6.5540019741851e-07,
+ 6.296724286387421e-07,
+ 6.042130721312737e-07,
+ 5.787113581803494e-07,
+ 5.53330187844726e-07,
+ 5.281934332968095e-07,
+ 5.03121236015739e-07,
+ 4.778763889614315e-07,
+ 4.526933865598823e-07,
+ 4.2754384995526265e-07,
+ 4.02431302903544e-07,
+ 3.772357217227813e-07,
+ 3.5205538831998675e-07,
+ 3.268880756874433e-07,
+ 3.0168628416495375e-07,
+ 2.765074485412737e-07,
+ 2.514058314276304e-07,
+ 2.2627103896913913e-07,
+ 2.0106995974671507e-07,
+ 1.7594876983500197e-07,
+ 1.5086779527660906e-07,
+ 1.256602804388385e-07,
+ 1.0053037251012367e-07,
+ 7.550777186742556e-08,
+ 5.040691029681141e-08,
+ 2.5131315859021395e-08,
+ 0.0,
+ -2.5237889497499612e-08,
+ -5.043265559757439e-08,
+ -7.568281768281973e-08,
+ -1.0075674273498589e-07,
+ -1.2591688968591425e-07,
+ -1.5099196779067656e-07,
+ -1.761663260460726e-07,
+ -2.0125085920209372e-07,
+ -2.2644287740807968e-07,
+ -2.5157168410001843e-07,
+ -2.767173670580612e-07,
+ -3.018683085279214e-07,
+ -3.2699586443338264e-07,
+ -3.521949285584175e-07,
+ -3.774015276486971e-07,
+ -4.0256961242140435e-07,
+ -4.2773983102081437e-07,
+ -4.529093679930643e-07,
+ -4.779854223507763e-07,
+ -5.031031761885112e-07,
+ -5.282717026139578e-07,
+ -5.533143026389223e-07,
+ -5.783905220015258e-07,
+ -6.03523273645616e-07,
+ -6.287026564232108e-07,
+ -6.538311073293857e-07,
+ -6.790266285764507e-07,
+ -7.042717156014642e-07,
+ -7.294805486547062e-07,
+ -7.546221123064875e-07,
+ -7.797754078767772e-07,
+ -8.050077446009205e-07,
+ -8.301314189443837e-07,
+ -8.55364169998107e-07,
+ -8.805427398305839e-07,
+ -9.056454697251297e-07,
+ -9.307630494597491e-07,
+ -9.55943541250586e-07
+ ]
+ },
+ "P10": {
+ "contact": {
+ "deviation_from_baseline": 26,
+ "fit_constant_line": 81,
+ "fit_constant_polynomial": 52,
+ "fit_line_polynomial": 52
+ },
+ "force": [
+ 3.140703517587938e-11,
+ 3.140703517587938e-11,
+ 3.14070351758794e-11,
+ 3.1407035175879406e-11,
+ 3.140703517587942e-11,
+ 3.1407035175879425e-11,
+ 3.1407035175879335e-11,
+ 3.140703517587935e-11,
+ 3.1407035175879354e-11,
+ 3.140703517587936e-11,
+ 3.1407035175879374e-11,
+ 3.140703517587938e-11,
+ 3.1407035175879387e-11,
+ 3.14070351758794e-11,
+ 3.140703517587941e-11,
+ 3.140703517587942e-11,
+ 3.140703517587943e-11,
+ 3.140703517587944e-11,
+ 3.140703517587934e-11,
+ 3.1407035175879354e-11,
+ 3.140703517587937e-11,
+ 3.140703517587937e-11,
+ 3.1407035175879387e-11,
+ 3.140703517587939e-11,
+ 3.14070351758794e-11,
+ 3.1407035175879406e-11,
+ 3.3919597989949767e-11,
+ 3.643216080402013e-11,
+ 3.894472361809049e-11,
+ 4.1457286432160746e-11,
+ 4.3969849246231106e-11,
+ 4.648241206030147e-11,
+ 4.899497487437183e-11,
+ 5.150753768844219e-11,
+ 5.402010050251255e-11,
+ 5.653266331658291e-11,
+ 5.904522613065327e-11,
+ 6.155778894472363e-11,
+ 6.407035175879399e-11,
+ 6.658291457286435e-11,
+ 6.909547738693471e-11,
+ 7.160804020100507e-11,
+ 7.412060301507544e-11,
+ 7.663316582914569e-11,
+ 7.914572864321605e-11,
+ 8.165829145728641e-11,
+ 8.417085427135677e-11,
+ 8.668341708542714e-11,
+ 8.91959798994975e-11,
+ 9.170854271356786e-11,
+ 9.422110552763822e-11,
+ 9.673366834170848e-11,
+ 9.924623115577884e-11,
+ 1.017587939698492e-10,
+ 1.0427135678391956e-10,
+ 1.0678391959798992e-10,
+ 1.0929648241206028e-10,
+ 1.1180904522613064e-10,
+ 1.14321608040201e-10,
+ 1.1683417085427136e-10,
+ 1.1934673366834172e-10,
+ 1.4177269550716845e-10,
+ 1.8069545724399782e-10,
+ 2.303574786973643e-10,
+ 2.887041771226739e-10,
+ 3.545481171508597e-10,
+ 4.2708811048329886e-10,
+ 5.057359844093441e-10,
+ 5.90036019761026e-10,
+ 6.796215726633957e-10,
+ 7.741893305491028e-10,
+ 8.734829261463207e-10,
+ 9.772819401316778e-10,
+ 1.0853942065341927e-09,
+ 1.1976502448599187e-09,
+ 1.3138991177183612e-09,
+ 1.4340052762778727e-09,
+ 1.5578461097579523e-09,
+ 1.6853100089045177e-09,
+ 1.8162948125906493e-09,
+ 1.950706545247075e-09,
+ 2.0884583786347395e-09,
+ 2.2294697691600206e-09,
+ 2.3736657343142762e-09,
+ 2.520976240650311e-09,
+ 2.671335682114642e-09,
+ 2.824682432273164e-09,
+ 2.9809584574921523e-09,
+ 3.14010898080239e-09,
+ 3.302082188214167e-09,
+ 3.466828970828579e-09,
+ 3.6343026973229476e-09,
+ 3.804459012359561e-09,
+ 3.977255657238991e-09,
+ 4.152652309737992e-09,
+ 4.330610440571282e-09,
+ 4.511093184322243e-09,
+ 4.694065223019547e-09,
+ 4.879492680809848e-09,
+ 5.067343028402647e-09,
+ 5.257584996151615e-09,
+ 5.4501884947938705e-09,
+ 5.645124543000992e-09,
+ 5.842365201006973e-09,
+ 6.041883509673075e-09,
+ 6.243653434429984e-09,
+ 6.447649813606525e-09,
+ 6.65384831071319e-09,
+ 6.862225370299651e-09,
+ 7.072758177049209e-09,
+ 7.2854246178112484e-09,
+ 7.500203246305856e-09,
+ 7.71707325026349e-09,
+ 7.9360144207879e-09,
+ 8.157007123752584e-09,
+ 8.38003227306037e-09,
+ 8.605071305613009e-09,
+ 8.832106157852711e-09,
+ 9.06111924375082e-09,
+ 9.29209343413095e-09,
+ 9.525012037224275e-09,
+ 9.759858780364116e-09,
+ 9.99661779273547e-09,
+ 1.0235273589102537e-08,
+ 1.0475811054444085e-08,
+ 1.0718215429432576e-08,
+ 1.0962472296698387e-08,
+ 1.1208567567825377e-08,
+ 1.1456487471028444e-08,
+ 1.1706218539467873e-08,
+ 1.1957747600158652e-08,
+ 1.2211061763436416e-08,
+ 1.2466148412944672e-08,
+ 1.2722995196110477e-08,
+ 1.2981590015078436e-08,
+ 1.324192101807505e-08,
+ 1.3503976591177456e-08,
+ 1.3767745350462525e-08,
+ 1.4033216134514124e-08,
+ 1.4300377997267634e-08,
+ 1.4569220201172578e-08,
+ 1.48397322106554e-08,
+ 1.511190368586556e-08,
+ 1.538572447668945e-08,
+ 1.5661184617017606e-08,
+ 1.593827431925139e-08,
+ 1.6216983969036698e-08,
+ 1.649730412021255e-08,
+ 1.677922548996358e-08,
+ 1.706273895416584e-08,
+ 1.7347835542916218e-08,
+ 1.7634506436236202e-08,
+ 1.7922742959941345e-08,
+ 1.8212536581668318e-08,
+ 1.850387890705193e-08,
+ 1.8796761676044854e-08,
+ 1.9091176759373334e-08,
+ 1.938711615512244e-08,
+ 1.9684571985444923e-08,
+ 1.998353649338783e-08,
+ 2.02840020398317e-08,
+ 2.0585961100537104e-08,
+ 2.0889406263293832e-08,
+ 2.1194330225168096e-08,
+ 2.15007257898436e-08,
+ 2.1808585865052253e-08,
+ 2.2117903460090723e-08,
+ 2.242867168341927e-08,
+ 2.274088374033922e-08,
+ 2.3054532930745925e-08,
+ 2.336961264695411e-08,
+ 2.3686116371592462e-08,
+ 2.4004037675564838e-08,
+ 2.4323370216075313e-08,
+ 2.464410773471456e-08,
+ 2.4966244055605e-08,
+ 2.5289773083602784e-08,
+ 2.5614688802553836e-08,
+ 2.5940985273602434e-08,
+ 2.6268656633549898e-08,
+ 2.6597697093261782e-08,
+ 2.6928100936121524e-08,
+ 2.7259862516529e-08,
+ 2.7592976258442193e-08,
+ 2.7927436653960452e-08,
+ 2.8263238261947857e-08,
+ 2.860037570669517e-08,
+ 2.893884367661902e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 2.893884367661902e-08,
+ 2.860037570669517e-08,
+ 2.8263238261947857e-08,
+ 2.7927436653960452e-08,
+ 2.7592976258442193e-08,
+ 2.7259862516529e-08,
+ 2.6928100936121524e-08,
+ 2.6597697093261782e-08,
+ 2.6268656633549898e-08,
+ 2.5940985273602434e-08,
+ 2.5614688802553836e-08,
+ 2.5289773083602784e-08,
+ 2.4966244055605e-08,
+ 2.464410773471456e-08,
+ 2.4323370216075313e-08,
+ 2.4004037675564838e-08,
+ 2.3686116371592462e-08,
+ 2.336961264695411e-08,
+ 2.3054532930745925e-08,
+ 2.274088374033922e-08,
+ 2.242867168341927e-08,
+ 2.2117903460090723e-08,
+ 2.1808585865052253e-08,
+ 2.15007257898436e-08,
+ 2.1194330225168096e-08,
+ 2.0889406263293832e-08,
+ 2.0585961100537104e-08,
+ 2.02840020398317e-08,
+ 1.998353649338783e-08,
+ 1.9684571985444923e-08,
+ 1.938711615512244e-08,
+ 1.9091176759373334e-08,
+ 1.8796761676044854e-08,
+ 1.850387890705193e-08,
+ 1.8212536581668318e-08,
+ 1.7922742959941345e-08,
+ 1.7634506436236202e-08,
+ 1.7347835542916218e-08,
+ 1.706273895416584e-08,
+ 1.677922548996358e-08,
+ 1.649730412021255e-08,
+ 1.6216983969036698e-08,
+ 1.593827431925139e-08,
+ 1.5661184617017606e-08,
+ 1.538572447668945e-08,
+ 1.511190368586556e-08,
+ 1.48397322106554e-08,
+ 1.4569220201172578e-08,
+ 1.4300377997267634e-08,
+ 1.4033216134514124e-08,
+ 1.3767745350462525e-08,
+ 1.3503976591177456e-08,
+ 1.324192101807505e-08,
+ 1.2981590015078436e-08,
+ 1.2722995196110477e-08,
+ 1.2466148412944672e-08,
+ 1.2211061763436416e-08,
+ 1.1957747600158652e-08,
+ 1.1706218539467873e-08,
+ 1.1456487471028444e-08,
+ 1.1208567567825377e-08,
+ 1.0962472296698387e-08,
+ 1.0718215429432576e-08,
+ 1.0475811054444085e-08,
+ 1.0235273589102537e-08,
+ 9.99661779273547e-09,
+ 9.759858780364116e-09,
+ 9.525012037224275e-09,
+ 9.29209343413095e-09,
+ 9.06111924375082e-09,
+ 8.832106157852711e-09,
+ 8.605071305613009e-09,
+ 8.38003227306037e-09,
+ 8.157007123752584e-09,
+ 7.9360144207879e-09,
+ 7.71707325026349e-09,
+ 7.500203246305856e-09,
+ 7.2854246178112484e-09,
+ 7.072758177049209e-09,
+ 6.862225370299651e-09,
+ 6.65384831071319e-09,
+ 6.447649813606525e-09,
+ 6.243653434429984e-09,
+ 6.041883509673075e-09,
+ 5.842365201006973e-09,
+ 5.645124543000992e-09,
+ 5.4501884947938705e-09,
+ 5.257584996151615e-09,
+ 5.067343028402647e-09,
+ 4.879492680809848e-09,
+ 4.694065223019547e-09,
+ 4.511093184322243e-09,
+ 4.330610440571282e-09,
+ 4.152652309737992e-09,
+ 3.977255657238991e-09,
+ 3.804459012359561e-09,
+ 3.6343026973229476e-09,
+ 3.466828970828579e-09,
+ 3.302082188214167e-09,
+ 3.14010898080239e-09,
+ 2.9809584574921523e-09,
+ 2.824682432273164e-09,
+ 2.671335682114642e-09,
+ 2.520976240650311e-09,
+ 2.3736657343142762e-09,
+ 2.2294697691600206e-09,
+ 2.0884583786347395e-09,
+ 1.950706545247075e-09,
+ 1.8162948125906493e-09,
+ 1.6853100089045177e-09,
+ 1.5578461097579523e-09,
+ 1.4340052762778727e-09,
+ 1.3138991177183612e-09,
+ 1.1976502448599187e-09,
+ 1.0853942065341927e-09,
+ 9.772819401316778e-10,
+ 8.734829261463207e-10,
+ 7.741893305491028e-10,
+ 6.796215726633957e-10,
+ 5.90036019761026e-10,
+ 5.057359844093441e-10,
+ 4.2708811048329886e-10,
+ 3.545481171508597e-10,
+ 2.887041771226739e-10,
+ 2.303574786973643e-10,
+ 1.8069545724399782e-10,
+ 1.4177269550716845e-10,
+ 1.1934673366834172e-10,
+ 1.1683417085427136e-10,
+ 1.14321608040201e-10,
+ 1.1180904522613064e-10,
+ 1.0929648241206028e-10,
+ 1.0678391959798992e-10,
+ 1.0427135678391956e-10,
+ 1.017587939698492e-10,
+ 9.924623115577884e-11,
+ 9.673366834170848e-11,
+ 9.422110552763822e-11,
+ 9.170854271356786e-11,
+ 8.91959798994975e-11,
+ 8.668341708542714e-11,
+ 8.417085427135677e-11,
+ 8.165829145728641e-11,
+ 7.914572864321605e-11,
+ 7.663316582914569e-11,
+ 7.412060301507544e-11,
+ 7.160804020100507e-11,
+ 6.909547738693471e-11,
+ 6.658291457286435e-11,
+ 6.407035175879399e-11,
+ 6.155778894472363e-11,
+ 5.904522613065327e-11,
+ 5.653266331658291e-11,
+ 5.402010050251255e-11,
+ 5.150753768844219e-11,
+ 4.899497487437183e-11,
+ 4.648241206030147e-11,
+ 4.3969849246231106e-11,
+ 4.1457286432160746e-11,
+ 3.894472361809049e-11,
+ 3.643216080402013e-11,
+ 3.3919597989949767e-11,
+ 3.1407035175879406e-11,
+ 2.8894472361809045e-11,
+ 2.6381909547738684e-11,
+ 2.3869346733668324e-11,
+ 2.1356783919597963e-11,
+ 1.8844221105527602e-11,
+ 1.633165829145724e-11,
+ 1.381909547738688e-11,
+ 1.1306532663316623e-11,
+ 8.793969849246263e-12,
+ 6.281407035175902e-12,
+ 3.768844221105541e-12,
+ 1.2562814070351804e-12,
+ -1.2562814070351804e-12,
+ -3.768844221105541e-12,
+ -6.281407035175902e-12,
+ -8.793969849246263e-12,
+ -1.1306532663316623e-11,
+ -1.3819095477386984e-11,
+ -1.6331658291457345e-11,
+ -1.8844221105527602e-11,
+ -2.1356783919597963e-11,
+ -2.3869346733668324e-11,
+ -2.6381909547738684e-11,
+ -2.8894472361809045e-11,
+ -3.1407035175879406e-11
+ ],
+ "height": [
+ 0.0,
+ 2.5125628140703518e-08,
+ 5.0251256281407036e-08,
+ 7.537688442211056e-08,
+ 1.0050251256281407e-07,
+ 1.2562814070351758e-07,
+ 1.5075376884422112e-07,
+ 1.7587939698492463e-07,
+ 2.0100502512562815e-07,
+ 2.2613065326633166e-07,
+ 2.5125628140703517e-07,
+ 2.763819095477387e-07,
+ 3.0150753768844224e-07,
+ 3.2663316582914573e-07,
+ 3.5175879396984927e-07,
+ 3.7688442211055275e-07,
+ 4.020100502512563e-07,
+ 4.2713567839195983e-07,
+ 4.522613065326633e-07,
+ 4.773869346733669e-07,
+ 5.025125628140703e-07,
+ 5.276381909547739e-07,
+ 5.527638190954774e-07,
+ 5.778894472361809e-07,
+ 6.030150753768845e-07,
+ 6.28140703517588e-07,
+ 6.532663316582915e-07,
+ 6.783919597989949e-07,
+ 7.035175879396985e-07,
+ 7.28643216080402e-07,
+ 7.537688442211055e-07,
+ 7.788944723618091e-07,
+ 8.040201005025126e-07,
+ 8.291457286432161e-07,
+ 8.542713567839197e-07,
+ 8.793969849246231e-07,
+ 9.045226130653266e-07,
+ 9.296482412060302e-07,
+ 9.547738693467337e-07,
+ 9.798994974874373e-07,
+ 1.0050251256281407e-06,
+ 1.0301507537688443e-06,
+ 1.0552763819095479e-06,
+ 1.0804020100502512e-06,
+ 1.1055276381909548e-06,
+ 1.1306532663316584e-06,
+ 1.1557788944723618e-06,
+ 1.1809045226130654e-06,
+ 1.206030150753769e-06,
+ 1.2311557788944724e-06,
+ 1.256281407035176e-06,
+ 1.2814070351758793e-06,
+ 1.306532663316583e-06,
+ 1.3316582914572865e-06,
+ 1.3567839195979899e-06,
+ 1.3819095477386935e-06,
+ 1.407035175879397e-06,
+ 1.4321608040201004e-06,
+ 1.457286432160804e-06,
+ 1.4824120603015076e-06,
+ 1.507537688442211e-06,
+ 1.5326633165829146e-06,
+ 1.5577889447236182e-06,
+ 1.5829145728643216e-06,
+ 1.6080402010050252e-06,
+ 1.6331658291457288e-06,
+ 1.6582914572864321e-06,
+ 1.6834170854271357e-06,
+ 1.7085427135678393e-06,
+ 1.7336683417085427e-06,
+ 1.7587939698492463e-06,
+ 1.7839195979899499e-06,
+ 1.8090452261306533e-06,
+ 1.8341708542713568e-06,
+ 1.8592964824120604e-06,
+ 1.8844221105527638e-06,
+ 1.9095477386934674e-06,
+ 1.9346733668341708e-06,
+ 1.9597989949748746e-06,
+ 1.984924623115578e-06,
+ 2.0100502512562813e-06,
+ 2.035175879396985e-06,
+ 2.0603015075376885e-06,
+ 2.085427135678392e-06,
+ 2.1105527638190957e-06,
+ 2.135678391959799e-06,
+ 2.1608040201005025e-06,
+ 2.1859296482412063e-06,
+ 2.2110552763819096e-06,
+ 2.236180904522613e-06,
+ 2.261306532663317e-06,
+ 2.28643216080402e-06,
+ 2.3115577889447236e-06,
+ 2.3366834170854274e-06,
+ 2.3618090452261308e-06,
+ 2.386934673366834e-06,
+ 2.412060301507538e-06,
+ 2.4371859296482413e-06,
+ 2.4623115577889447e-06,
+ 2.487437185929648e-06,
+ 2.512562814070352e-06,
+ 2.5376884422110553e-06,
+ 2.5628140703517587e-06,
+ 2.5879396984924625e-06,
+ 2.613065326633166e-06,
+ 2.6381909547738692e-06,
+ 2.663316582914573e-06,
+ 2.6884422110552764e-06,
+ 2.7135678391959798e-06,
+ 2.7386934673366836e-06,
+ 2.763819095477387e-06,
+ 2.7889447236180903e-06,
+ 2.814070351758794e-06,
+ 2.8391959798994975e-06,
+ 2.864321608040201e-06,
+ 2.8894472361809047e-06,
+ 2.914572864321608e-06,
+ 2.9396984924623115e-06,
+ 2.9648241206030153e-06,
+ 2.9899497487437186e-06,
+ 3.015075376884422e-06,
+ 3.040201005025126e-06,
+ 3.065326633165829e-06,
+ 3.0904522613065326e-06,
+ 3.1155778894472364e-06,
+ 3.1407035175879398e-06,
+ 3.165829145728643e-06,
+ 3.190954773869347e-06,
+ 3.2160804020100503e-06,
+ 3.2412060301507537e-06,
+ 3.2663316582914575e-06,
+ 3.291457286432161e-06,
+ 3.3165829145728643e-06,
+ 3.341708542713568e-06,
+ 3.3668341708542714e-06,
+ 3.391959798994975e-06,
+ 3.4170854271356786e-06,
+ 3.442211055276382e-06,
+ 3.4673366834170854e-06,
+ 3.492462311557789e-06,
+ 3.5175879396984926e-06,
+ 3.542713567839196e-06,
+ 3.5678391959798997e-06,
+ 3.592964824120603e-06,
+ 3.6180904522613065e-06,
+ 3.6432160804020103e-06,
+ 3.6683417085427137e-06,
+ 3.693467336683417e-06,
+ 3.718592964824121e-06,
+ 3.7437185929648243e-06,
+ 3.7688442211055276e-06,
+ 3.7939698492462314e-06,
+ 3.819095477386935e-06,
+ 3.844221105527638e-06,
+ 3.8693467336683416e-06,
+ 3.894472361809045e-06,
+ 3.919597989949749e-06,
+ 3.9447236180904526e-06,
+ 3.969849246231156e-06,
+ 3.994974874371859e-06,
+ 4.020100502512563e-06,
+ 4.045226130653266e-06,
+ 4.07035175879397e-06,
+ 4.095477386934674e-06,
+ 4.120603015075377e-06,
+ 4.1457286432160804e-06,
+ 4.170854271356784e-06,
+ 4.195979899497487e-06,
+ 4.221105527638191e-06,
+ 4.246231155778895e-06,
+ 4.271356783919598e-06,
+ 4.2964824120603016e-06,
+ 4.321608040201005e-06,
+ 4.346733668341708e-06,
+ 4.3718592964824125e-06,
+ 4.396984924623116e-06,
+ 4.422110552763819e-06,
+ 4.447236180904523e-06,
+ 4.472361809045226e-06,
+ 4.4974874371859294e-06,
+ 4.522613065326634e-06,
+ 4.547738693467337e-06,
+ 4.57286432160804e-06,
+ 4.597989949748744e-06,
+ 4.623115577889447e-06,
+ 4.6482412060301506e-06,
+ 4.673366834170855e-06,
+ 4.698492462311558e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 4.698492462311558e-06,
+ 4.673366834170855e-06,
+ 4.6482412060301506e-06,
+ 4.623115577889447e-06,
+ 4.597989949748744e-06,
+ 4.57286432160804e-06,
+ 4.547738693467337e-06,
+ 4.522613065326634e-06,
+ 4.4974874371859294e-06,
+ 4.472361809045226e-06,
+ 4.447236180904523e-06,
+ 4.422110552763819e-06,
+ 4.396984924623116e-06,
+ 4.3718592964824125e-06,
+ 4.346733668341708e-06,
+ 4.321608040201005e-06,
+ 4.2964824120603016e-06,
+ 4.271356783919598e-06,
+ 4.246231155778895e-06,
+ 4.221105527638191e-06,
+ 4.195979899497487e-06,
+ 4.170854271356784e-06,
+ 4.1457286432160804e-06,
+ 4.120603015075377e-06,
+ 4.095477386934674e-06,
+ 4.07035175879397e-06,
+ 4.045226130653266e-06,
+ 4.020100502512563e-06,
+ 3.994974874371859e-06,
+ 3.969849246231156e-06,
+ 3.9447236180904526e-06,
+ 3.919597989949749e-06,
+ 3.894472361809045e-06,
+ 3.8693467336683416e-06,
+ 3.844221105527638e-06,
+ 3.819095477386935e-06,
+ 3.7939698492462314e-06,
+ 3.7688442211055276e-06,
+ 3.7437185929648243e-06,
+ 3.718592964824121e-06,
+ 3.693467336683417e-06,
+ 3.6683417085427137e-06,
+ 3.6432160804020103e-06,
+ 3.6180904522613065e-06,
+ 3.592964824120603e-06,
+ 3.5678391959798997e-06,
+ 3.542713567839196e-06,
+ 3.5175879396984926e-06,
+ 3.492462311557789e-06,
+ 3.4673366834170854e-06,
+ 3.442211055276382e-06,
+ 3.4170854271356786e-06,
+ 3.391959798994975e-06,
+ 3.3668341708542714e-06,
+ 3.341708542713568e-06,
+ 3.3165829145728643e-06,
+ 3.291457286432161e-06,
+ 3.2663316582914575e-06,
+ 3.2412060301507537e-06,
+ 3.2160804020100503e-06,
+ 3.190954773869347e-06,
+ 3.165829145728643e-06,
+ 3.1407035175879398e-06,
+ 3.1155778894472364e-06,
+ 3.0904522613065326e-06,
+ 3.065326633165829e-06,
+ 3.040201005025126e-06,
+ 3.015075376884422e-06,
+ 2.9899497487437186e-06,
+ 2.9648241206030153e-06,
+ 2.9396984924623115e-06,
+ 2.914572864321608e-06,
+ 2.8894472361809047e-06,
+ 2.864321608040201e-06,
+ 2.8391959798994975e-06,
+ 2.814070351758794e-06,
+ 2.7889447236180903e-06,
+ 2.763819095477387e-06,
+ 2.7386934673366836e-06,
+ 2.7135678391959798e-06,
+ 2.6884422110552764e-06,
+ 2.663316582914573e-06,
+ 2.6381909547738692e-06,
+ 2.613065326633166e-06,
+ 2.5879396984924625e-06,
+ 2.5628140703517587e-06,
+ 2.5376884422110553e-06,
+ 2.512562814070352e-06,
+ 2.487437185929648e-06,
+ 2.4623115577889447e-06,
+ 2.4371859296482413e-06,
+ 2.412060301507538e-06,
+ 2.386934673366834e-06,
+ 2.3618090452261308e-06,
+ 2.3366834170854274e-06,
+ 2.3115577889447236e-06,
+ 2.28643216080402e-06,
+ 2.261306532663317e-06,
+ 2.236180904522613e-06,
+ 2.2110552763819096e-06,
+ 2.1859296482412063e-06,
+ 2.1608040201005025e-06,
+ 2.135678391959799e-06,
+ 2.1105527638190957e-06,
+ 2.085427135678392e-06,
+ 2.0603015075376885e-06,
+ 2.035175879396985e-06,
+ 2.0100502512562813e-06,
+ 1.984924623115578e-06,
+ 1.9597989949748746e-06,
+ 1.9346733668341708e-06,
+ 1.9095477386934674e-06,
+ 1.8844221105527638e-06,
+ 1.8592964824120604e-06,
+ 1.8341708542713568e-06,
+ 1.8090452261306533e-06,
+ 1.7839195979899499e-06,
+ 1.7587939698492463e-06,
+ 1.7336683417085427e-06,
+ 1.7085427135678393e-06,
+ 1.6834170854271357e-06,
+ 1.6582914572864321e-06,
+ 1.6331658291457288e-06,
+ 1.6080402010050252e-06,
+ 1.5829145728643216e-06,
+ 1.5577889447236182e-06,
+ 1.5326633165829146e-06,
+ 1.507537688442211e-06,
+ 1.4824120603015076e-06,
+ 1.457286432160804e-06,
+ 1.4321608040201004e-06,
+ 1.407035175879397e-06,
+ 1.3819095477386935e-06,
+ 1.3567839195979899e-06,
+ 1.3316582914572865e-06,
+ 1.306532663316583e-06,
+ 1.2814070351758793e-06,
+ 1.256281407035176e-06,
+ 1.2311557788944724e-06,
+ 1.206030150753769e-06,
+ 1.1809045226130654e-06,
+ 1.1557788944723618e-06,
+ 1.1306532663316584e-06,
+ 1.1055276381909548e-06,
+ 1.0804020100502512e-06,
+ 1.0552763819095479e-06,
+ 1.0301507537688443e-06,
+ 1.0050251256281407e-06,
+ 9.798994974874373e-07,
+ 9.547738693467337e-07,
+ 9.296482412060302e-07,
+ 9.045226130653266e-07,
+ 8.793969849246231e-07,
+ 8.542713567839197e-07,
+ 8.291457286432161e-07,
+ 8.040201005025126e-07,
+ 7.788944723618091e-07,
+ 7.537688442211055e-07,
+ 7.28643216080402e-07,
+ 7.035175879396985e-07,
+ 6.783919597989949e-07,
+ 6.532663316582915e-07,
+ 6.28140703517588e-07,
+ 6.030150753768845e-07,
+ 5.778894472361809e-07,
+ 5.527638190954774e-07,
+ 5.276381909547739e-07,
+ 5.025125628140703e-07,
+ 4.773869346733669e-07,
+ 4.522613065326633e-07,
+ 4.2713567839195983e-07,
+ 4.020100502512563e-07,
+ 3.7688442211055275e-07,
+ 3.5175879396984927e-07,
+ 3.2663316582914573e-07,
+ 3.0150753768844224e-07,
+ 2.763819095477387e-07,
+ 2.5125628140703517e-07,
+ 2.2613065326633166e-07,
+ 2.0100502512562815e-07,
+ 1.7587939698492463e-07,
+ 1.5075376884422112e-07,
+ 1.2562814070351758e-07,
+ 1.0050251256281407e-07,
+ 7.537688442211056e-08,
+ 5.0251256281407036e-08,
+ 2.5125628140703518e-08,
+ 0.0
+ ],
+ "segment": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ "tip_position": [
+ -6.539195979899498e-07,
+ -6.287688442211055e-07,
+ -6.036180904522613e-07,
+ -5.78467336683417e-07,
+ -5.533165829145729e-07,
+ -5.281658291457286e-07,
+ -5.030150753768844e-07,
+ -4.778643216080402e-07,
+ -4.52713567839196e-07,
+ -4.2756281407035174e-07,
+ -4.0241206030150754e-07,
+ -3.772613065326633e-07,
+ -3.5211055276381904e-07,
+ -3.2695979899497484e-07,
+ -3.0180904522613064e-07,
+ -2.7665829145728644e-07,
+ -2.515075376884422e-07,
+ -2.2635678391959794e-07,
+ -2.0120603015075374e-07,
+ -1.7605527638190955e-07,
+ -1.509045226130653e-07,
+ -1.2575376884422104e-07,
+ -1.006030150753769e-07,
+ -7.545226130653265e-08,
+ -5.0301507537688396e-08,
+ -2.5150753768844145e-08,
+ 0.0,
+ 2.515075376884425e-08,
+ 5.03015075376885e-08,
+ 7.545226130653265e-08,
+ 1.006030150753769e-07,
+ 1.2575376884422115e-07,
+ 1.509045226130654e-07,
+ 1.7605527638190955e-07,
+ 2.012060301507538e-07,
+ 2.2635678391959805e-07,
+ 2.515075376884422e-07,
+ 2.7665829145728655e-07,
+ 3.018090452261307e-07,
+ 3.2695979899497505e-07,
+ 3.52110552763819e-07,
+ 3.7726130653266334e-07,
+ 4.024120603015077e-07,
+ 4.2756281407035185e-07,
+ 4.52713567839196e-07,
+ 4.778643216080403e-07,
+ 5.030150753768845e-07,
+ 5.281658291457286e-07,
+ 5.53316582914573e-07,
+ 5.784673366834171e-07,
+ 6.036180904522613e-07,
+ 6.287688442211054e-07,
+ 6.539195979899498e-07,
+ 6.790703517587941e-07,
+ 7.042211055276381e-07,
+ 7.293718592964824e-07,
+ 7.545226130653268e-07,
+ 7.796733668341707e-07,
+ 8.048241206030151e-07,
+ 8.299748743718594e-07,
+ 8.551256281407036e-07,
+ 8.804755158997953e-07,
+ 9.059903716578673e-07,
+ 9.316126200131043e-07,
+ 9.57321715138061e-07,
+ 9.831057826790464e-07,
+ 1.0089568107530744e-06,
+ 1.0348689176330384e-06,
+ 1.0608375461272588e-06,
+ 1.0868590297969856e-06,
+ 1.1129303355165465e-06,
+ 1.1390488996132223e-06,
+ 1.1652125178937793e-06,
+ 1.191419268698508e-06,
+ 1.2176674572224688e-06,
+ 1.2439555740917564e-06,
+ 1.2702822638180552e-06,
+ 1.2966463002935594e-06,
+ 1.3230465674257288e-06,
+ 1.3494820436032934e-06,
+ 1.3759517890705612e-06,
+ 1.4024549355451413e-06,
+ 1.4289906775910979e-06,
+ 1.4555582653833436e-06,
+ 1.4821569985874077e-06,
+ 1.5087862211427544e-06,
+ 1.535445316785043e-06,
+ 1.5621337051779367e-06,
+ 1.5888508385517423e-06,
+ 1.6155961987665638e-06,
+ 1.6423692947334115e-06,
+ 1.6691696601390588e-06,
+ 1.695996851430128e-06,
+ 1.7228504460196263e-06,
+ 1.7497300406853196e-06,
+ 1.7766352501343558e-06,
+ 1.8035657057125694e-06,
+ 1.8305210542402457e-06,
+ 1.857500956958852e-06,
+ 1.8845050885754837e-06,
+ 1.911533136393677e-06,
+ 1.938584799520803e-06,
+ 1.9656597881435775e-06,
+ 1.992757822864341e-06,
+ 2.0198786340917054e-06,
+ 2.047021961479978e-06,
+ 2.074187553412447e-06,
+ 2.101375166524217e-06,
+ 2.1285845652607854e-06,
+ 2.155815521468985e-06,
+ 2.1830678140173085e-06,
+ 2.210341228442958e-06,
+ 2.237635556623238e-06,
+ 2.2649505964691854e-06,
+ 2.2922861516395356e-06,
+ 2.3196420312733175e-06,
+ 2.3470180497395473e-06,
+ 2.3744140264026476e-06,
+ 2.4018297854023326e-06,
+ 2.429265155446837e-06,
+ 2.456719969618474e-06,
+ 2.484194065190576e-06,
+ 2.511687283454993e-06,
+ 2.539199469559367e-06,
+ 2.5667304723534863e-06,
+ 2.5942801442440745e-06,
+ 2.621848341057436e-06,
+ 2.6494349219094097e-06,
+ 2.6770397490821436e-06,
+ 2.7046626879072413e-06,
+ 2.732303606654853e-06,
+ 2.759962376428334e-06,
+ 2.78763887106412e-06,
+ 2.815332967036482e-06,
+ 2.843044543366865e-06,
+ 2.8707734815375343e-06,
+ 2.898519665409262e-06,
+ 2.9262829811428164e-06,
+ 2.9540633171240357e-06,
+ 2.9818605638922746e-06,
+ 3.0096746140720272e-06,
+ 3.037505362307559e-06,
+ 3.065352705200364e-06,
+ 3.0932165412493064e-06,
+ 3.1210967707932917e-06,
+ 3.148993295956333e-06,
+ 3.1769060205948893e-06,
+ 3.2048348502473516e-06,
+ 3.232779692085566e-06,
+ 3.2607404548682913e-06,
+ 3.2887170488964984e-06,
+ 3.316709385970402e-06,
+ 3.3447173793481576e-06,
+ 3.3727409437061306e-06,
+ 3.40077999510067e-06,
+ 3.428834450931302e-06,
+ 3.4569042299052917e-06,
+ 3.4849892520034856e-06,
+ 3.513089438447414e-06,
+ 3.5412047116675466e-06,
+ 3.5693349952726886e-06,
+ 3.597480214020446e-06,
+ 3.6256402937887173e-06,
+ 3.653815161548163e-06,
+ 3.682004745335622e-06,
+ 3.710208974228412e-06,
+ 3.7384277783195002e-06,
+ 3.766661088693489e-06,
+ 3.7949088374033923e-06,
+ 3.823170957448163e-06,
+ 3.851447382750948e-06,
+ 3.879738048138035e-06,
+ 3.908042889318462e-06,
+ 3.9363618428642705e-06,
+ 3.964694846191367e-06,
+ 3.993041837540975e-06,
+ 4.021402755961656e-06,
+ 4.04977754129187e-06,
+ 4.078166134143059e-06,
+ 4.1065684758832376e-06,
+ 4.13498450862106e-06,
+ 4.1634141751903614e-06,
+ 4.191857419135139e-06,
+ 4.220314184694974e-06,
+ 4.24878441679086e-06,
+ 4.277268061011438e-06,
+ 4.305765063599616e-06,
+ 4.3342753714395574e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.3342753714395574e-06,
+ 4.305765063599616e-06,
+ 4.277268061011438e-06,
+ 4.24878441679086e-06,
+ 4.220314184694974e-06,
+ 4.191857419135139e-06,
+ 4.1634141751903614e-06,
+ 4.13498450862106e-06,
+ 4.1065684758832376e-06,
+ 4.078166134143059e-06,
+ 4.04977754129187e-06,
+ 4.021402755961656e-06,
+ 3.993041837540975e-06,
+ 3.964694846191367e-06,
+ 3.9363618428642705e-06,
+ 3.908042889318462e-06,
+ 3.879738048138035e-06,
+ 3.851447382750948e-06,
+ 3.823170957448163e-06,
+ 3.7949088374033923e-06,
+ 3.766661088693489e-06,
+ 3.7384277783195002e-06,
+ 3.710208974228412e-06,
+ 3.682004745335622e-06,
+ 3.653815161548163e-06,
+ 3.6256402937887173e-06,
+ 3.597480214020446e-06,
+ 3.5693349952726886e-06,
+ 3.5412047116675466e-06,
+ 3.513089438447414e-06,
+ 3.4849892520034856e-06,
+ 3.4569042299052917e-06,
+ 3.428834450931302e-06,
+ 3.40077999510067e-06,
+ 3.3727409437061306e-06,
+ 3.3447173793481576e-06,
+ 3.316709385970402e-06,
+ 3.2887170488964984e-06,
+ 3.2607404548682913e-06,
+ 3.232779692085566e-06,
+ 3.2048348502473516e-06,
+ 3.1769060205948893e-06,
+ 3.148993295956333e-06,
+ 3.1210967707932917e-06,
+ 3.0932165412493064e-06,
+ 3.065352705200364e-06,
+ 3.037505362307559e-06,
+ 3.0096746140720272e-06,
+ 2.9818605638922746e-06,
+ 2.9540633171240357e-06,
+ 2.9262829811428164e-06,
+ 2.898519665409262e-06,
+ 2.8707734815375343e-06,
+ 2.843044543366865e-06,
+ 2.815332967036482e-06,
+ 2.78763887106412e-06,
+ 2.759962376428334e-06,
+ 2.732303606654853e-06,
+ 2.7046626879072413e-06,
+ 2.6770397490821436e-06,
+ 2.6494349219094097e-06,
+ 2.621848341057436e-06,
+ 2.5942801442440745e-06,
+ 2.5667304723534863e-06,
+ 2.539199469559367e-06,
+ 2.511687283454993e-06,
+ 2.484194065190576e-06,
+ 2.456719969618474e-06,
+ 2.429265155446837e-06,
+ 2.4018297854023326e-06,
+ 2.3744140264026476e-06,
+ 2.3470180497395473e-06,
+ 2.3196420312733175e-06,
+ 2.2922861516395356e-06,
+ 2.2649505964691854e-06,
+ 2.237635556623238e-06,
+ 2.210341228442958e-06,
+ 2.1830678140173085e-06,
+ 2.155815521468985e-06,
+ 2.1285845652607854e-06,
+ 2.101375166524217e-06,
+ 2.074187553412447e-06,
+ 2.047021961479978e-06,
+ 2.0198786340917054e-06,
+ 1.992757822864341e-06,
+ 1.9656597881435775e-06,
+ 1.938584799520803e-06,
+ 1.911533136393677e-06,
+ 1.8845050885754837e-06,
+ 1.857500956958852e-06,
+ 1.8305210542402457e-06,
+ 1.8035657057125694e-06,
+ 1.7766352501343558e-06,
+ 1.7497300406853196e-06,
+ 1.7228504460196263e-06,
+ 1.695996851430128e-06,
+ 1.6691696601390588e-06,
+ 1.6423692947334115e-06,
+ 1.6155961987665638e-06,
+ 1.5888508385517423e-06,
+ 1.5621337051779367e-06,
+ 1.535445316785043e-06,
+ 1.5087862211427544e-06,
+ 1.4821569985874077e-06,
+ 1.4555582653833436e-06,
+ 1.4289906775910979e-06,
+ 1.4024549355451413e-06,
+ 1.3759517890705612e-06,
+ 1.3494820436032934e-06,
+ 1.3230465674257288e-06,
+ 1.2966463002935594e-06,
+ 1.2702822638180552e-06,
+ 1.2439555740917564e-06,
+ 1.2176674572224688e-06,
+ 1.191419268698508e-06,
+ 1.1652125178937793e-06,
+ 1.1390488996132223e-06,
+ 1.1129303355165465e-06,
+ 1.0868590297969856e-06,
+ 1.0608375461272588e-06,
+ 1.0348689176330384e-06,
+ 1.0089568107530744e-06,
+ 9.831057826790464e-07,
+ 9.57321715138061e-07,
+ 9.316126200131043e-07,
+ 9.059903716578673e-07,
+ 8.804755158997953e-07,
+ 8.551256281407036e-07,
+ 8.299748743718594e-07,
+ 8.048241206030151e-07,
+ 7.796733668341707e-07,
+ 7.545226130653268e-07,
+ 7.293718592964824e-07,
+ 7.042211055276381e-07,
+ 6.790703517587941e-07,
+ 6.539195979899498e-07,
+ 6.287688442211054e-07,
+ 6.036180904522613e-07,
+ 5.784673366834171e-07,
+ 5.53316582914573e-07,
+ 5.281658291457286e-07,
+ 5.030150753768845e-07,
+ 4.778643216080403e-07,
+ 4.52713567839196e-07,
+ 4.2756281407035185e-07,
+ 4.024120603015077e-07,
+ 3.7726130653266334e-07,
+ 3.52110552763819e-07,
+ 3.2695979899497505e-07,
+ 3.018090452261307e-07,
+ 2.7665829145728655e-07,
+ 2.515075376884422e-07,
+ 2.2635678391959805e-07,
+ 2.012060301507538e-07,
+ 1.7605527638190955e-07,
+ 1.509045226130654e-07,
+ 1.2575376884422115e-07,
+ 1.006030150753769e-07,
+ 7.545226130653265e-08,
+ 5.03015075376885e-08,
+ 2.515075376884425e-08,
+ 0.0,
+ -2.5150753768844145e-08,
+ -5.0301507537688396e-08,
+ -7.545226130653265e-08,
+ -1.006030150753769e-07,
+ -1.2575376884422104e-07,
+ -1.509045226130653e-07,
+ -1.7605527638190955e-07,
+ -2.0120603015075374e-07,
+ -2.2635678391959794e-07,
+ -2.515075376884422e-07,
+ -2.7665829145728644e-07,
+ -3.0180904522613064e-07,
+ -3.2695979899497484e-07,
+ -3.5211055276381904e-07,
+ -3.772613065326633e-07,
+ -4.0241206030150754e-07,
+ -4.2756281407035174e-07,
+ -4.52713567839196e-07,
+ -4.778643216080402e-07,
+ -5.030150753768844e-07,
+ -5.281658291457286e-07,
+ -5.533165829145729e-07,
+ -5.78467336683417e-07,
+ -6.036180904522613e-07,
+ -6.287688442211055e-07,
+ -6.539195979899498e-07
+ ]
+ },
+ "P11": {
+ "contact": {
+ "deviation_from_baseline": 26,
+ "fit_constant_line": 80,
+ "fit_constant_polynomial": 52,
+ "fit_line_polynomial": 52
+ },
+ "force": [
+ 3.140703517587938e-11,
+ 3.140703517587938e-11,
+ 3.14070351758794e-11,
+ 3.1407035175879406e-11,
+ 3.140703517587942e-11,
+ 3.1407035175879425e-11,
+ 3.1407035175879335e-11,
+ 3.140703517587935e-11,
+ 3.1407035175879354e-11,
+ 3.140703517587936e-11,
+ 3.1407035175879374e-11,
+ 3.140703517587938e-11,
+ 3.1407035175879387e-11,
+ 3.14070351758794e-11,
+ 3.140703517587941e-11,
+ 3.140703517587942e-11,
+ 3.140703517587943e-11,
+ 3.140703517587944e-11,
+ 3.140703517587934e-11,
+ 3.1407035175879354e-11,
+ 3.140703517587937e-11,
+ 3.140703517587937e-11,
+ 3.1407035175879387e-11,
+ 3.140703517587939e-11,
+ 3.14070351758794e-11,
+ 3.1407035175879406e-11,
+ 3.3919597989949767e-11,
+ 3.643216080402013e-11,
+ 3.894472361809049e-11,
+ 4.1457286432160746e-11,
+ 4.3969849246231106e-11,
+ 4.648241206030147e-11,
+ 4.899497487437183e-11,
+ 5.150753768844219e-11,
+ 5.402010050251255e-11,
+ 5.653266331658291e-11,
+ 5.904522613065327e-11,
+ 6.155778894472363e-11,
+ 6.407035175879399e-11,
+ 6.658291457286435e-11,
+ 6.909547738693471e-11,
+ 7.160804020100507e-11,
+ 7.412060301507544e-11,
+ 7.663316582914569e-11,
+ 7.914572864321605e-11,
+ 8.165829145728641e-11,
+ 8.417085427135677e-11,
+ 8.668341708542714e-11,
+ 8.91959798994975e-11,
+ 9.170854271356786e-11,
+ 9.422110552763822e-11,
+ 9.673366834170848e-11,
+ 9.924623115577884e-11,
+ 1.017587939698492e-10,
+ 1.0427135678391956e-10,
+ 1.0678391959798992e-10,
+ 1.0929648241206028e-10,
+ 1.1180904522613064e-10,
+ 1.14321608040201e-10,
+ 1.1683417085427136e-10,
+ 1.1934673366834172e-10,
+ 1.4177269550716845e-10,
+ 1.8069545724399782e-10,
+ 2.303574786973643e-10,
+ 2.887041771226739e-10,
+ 3.545481171508597e-10,
+ 4.2708811048329886e-10,
+ 5.057359844093441e-10,
+ 5.90036019761026e-10,
+ 6.796215726633957e-10,
+ 7.741893305491028e-10,
+ 8.734829261463207e-10,
+ 9.772819401316778e-10,
+ 1.0853942065341927e-09,
+ 1.1976502448599187e-09,
+ 1.3138991177183612e-09,
+ 1.4340052762778727e-09,
+ 1.5578461097579523e-09,
+ 1.6853100089045177e-09,
+ 1.8162948125906493e-09,
+ 1.950706545247075e-09,
+ 2.0884583786347395e-09,
+ 2.2294697691600206e-09,
+ 2.3736657343142762e-09,
+ 2.520976240650311e-09,
+ 2.671335682114642e-09,
+ 2.824682432273164e-09,
+ 2.9809584574921523e-09,
+ 3.14010898080239e-09,
+ 3.302082188214167e-09,
+ 3.466828970828579e-09,
+ 3.6343026973229476e-09,
+ 3.804459012359561e-09,
+ 3.977255657238991e-09,
+ 4.152652309737992e-09,
+ 4.330610440571282e-09,
+ 4.511093184322243e-09,
+ 4.694065223019547e-09,
+ 4.879492680809848e-09,
+ 5.067343028402647e-09,
+ 5.257584996151615e-09,
+ 5.4501884947938705e-09,
+ 5.645124543000992e-09,
+ 5.842365201006973e-09,
+ 6.041883509673075e-09,
+ 6.243653434429984e-09,
+ 6.447649813606525e-09,
+ 6.65384831071319e-09,
+ 6.862225370299651e-09,
+ 7.072758177049209e-09,
+ 7.2854246178112484e-09,
+ 7.500203246305856e-09,
+ 7.71707325026349e-09,
+ 7.9360144207879e-09,
+ 8.157007123752584e-09,
+ 8.38003227306037e-09,
+ 8.605071305613009e-09,
+ 8.832106157852711e-09,
+ 9.06111924375082e-09,
+ 9.29209343413095e-09,
+ 9.525012037224275e-09,
+ 9.759858780364116e-09,
+ 9.99661779273547e-09,
+ 1.0235273589102537e-08,
+ 1.0475811054444085e-08,
+ 1.0718215429432576e-08,
+ 1.0962472296698387e-08,
+ 1.1208567567825377e-08,
+ 1.1456487471028444e-08,
+ 1.1706218539467873e-08,
+ 1.1957747600158652e-08,
+ 1.2211061763436416e-08,
+ 1.2466148412944672e-08,
+ 1.2722995196110477e-08,
+ 1.2981590015078436e-08,
+ 1.324192101807505e-08,
+ 1.3503976591177456e-08,
+ 1.3767745350462525e-08,
+ 1.4033216134514124e-08,
+ 1.4300377997267634e-08,
+ 1.4569220201172578e-08,
+ 1.48397322106554e-08,
+ 1.511190368586556e-08,
+ 1.538572447668945e-08,
+ 1.5661184617017606e-08,
+ 1.593827431925139e-08,
+ 1.6216983969036698e-08,
+ 1.649730412021255e-08,
+ 1.677922548996358e-08,
+ 1.706273895416584e-08,
+ 1.7347835542916218e-08,
+ 1.7634506436236202e-08,
+ 1.7922742959941345e-08,
+ 1.8212536581668318e-08,
+ 1.850387890705193e-08,
+ 1.8796761676044854e-08,
+ 1.9091176759373334e-08,
+ 1.938711615512244e-08,
+ 1.9684571985444923e-08,
+ 1.998353649338783e-08,
+ 2.02840020398317e-08,
+ 2.0585961100537104e-08,
+ 2.0889406263293832e-08,
+ 2.1194330225168096e-08,
+ 2.15007257898436e-08,
+ 2.1808585865052253e-08,
+ 2.2117903460090723e-08,
+ 2.242867168341927e-08,
+ 2.274088374033922e-08,
+ 2.3054532930745925e-08,
+ 2.336961264695411e-08,
+ 2.3686116371592462e-08,
+ 2.4004037675564838e-08,
+ 2.4323370216075313e-08,
+ 2.464410773471456e-08,
+ 2.4966244055605e-08,
+ 2.5289773083602784e-08,
+ 2.5614688802553836e-08,
+ 2.5940985273602434e-08,
+ 2.6268656633549898e-08,
+ 2.6597697093261782e-08,
+ 2.6928100936121524e-08,
+ 2.7259862516529e-08,
+ 2.7592976258442193e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 3.310238913433444e-08,
+ 2.7592976258442193e-08,
+ 2.7259862516529e-08,
+ 2.6928100936121524e-08,
+ 2.6597697093261782e-08,
+ 2.6268656633549898e-08,
+ 2.5940985273602434e-08,
+ 2.5614688802553836e-08,
+ 2.5289773083602784e-08,
+ 2.4966244055605e-08,
+ 2.464410773471456e-08,
+ 2.4323370216075313e-08,
+ 2.4004037675564838e-08,
+ 2.3686116371592462e-08,
+ 2.336961264695411e-08,
+ 2.3054532930745925e-08,
+ 2.274088374033922e-08,
+ 2.242867168341927e-08,
+ 2.2117903460090723e-08,
+ 2.1808585865052253e-08,
+ 2.15007257898436e-08,
+ 2.1194330225168096e-08,
+ 2.0889406263293832e-08,
+ 2.0585961100537104e-08,
+ 2.02840020398317e-08,
+ 1.998353649338783e-08,
+ 1.9684571985444923e-08,
+ 1.938711615512244e-08,
+ 1.9091176759373334e-08,
+ 1.8796761676044854e-08,
+ 1.850387890705193e-08,
+ 1.8212536581668318e-08,
+ 1.7922742959941345e-08,
+ 1.7634506436236202e-08,
+ 1.7347835542916218e-08,
+ 1.706273895416584e-08,
+ 1.677922548996358e-08,
+ 1.649730412021255e-08,
+ 1.6216983969036698e-08,
+ 1.593827431925139e-08,
+ 1.5661184617017606e-08,
+ 1.538572447668945e-08,
+ 1.511190368586556e-08,
+ 1.48397322106554e-08,
+ 1.4569220201172578e-08,
+ 1.4300377997267634e-08,
+ 1.4033216134514124e-08,
+ 1.3767745350462525e-08,
+ 1.3503976591177456e-08,
+ 1.324192101807505e-08,
+ 1.2981590015078436e-08,
+ 1.2722995196110477e-08,
+ 1.2466148412944672e-08,
+ 1.2211061763436416e-08,
+ 1.1957747600158652e-08,
+ 1.1706218539467873e-08,
+ 1.1456487471028444e-08,
+ 1.1208567567825377e-08,
+ 1.0962472296698387e-08,
+ 1.0718215429432576e-08,
+ 1.0475811054444085e-08,
+ 1.0235273589102537e-08,
+ 9.99661779273547e-09,
+ 9.759858780364116e-09,
+ 9.525012037224275e-09,
+ 9.29209343413095e-09,
+ 9.06111924375082e-09,
+ 8.832106157852711e-09,
+ 8.605071305613009e-09,
+ 8.38003227306037e-09,
+ 8.157007123752584e-09,
+ 7.9360144207879e-09,
+ 7.71707325026349e-09,
+ 7.500203246305856e-09,
+ 7.2854246178112484e-09,
+ 7.072758177049209e-09,
+ 6.862225370299651e-09,
+ 6.65384831071319e-09,
+ 6.447649813606525e-09,
+ 6.243653434429984e-09,
+ 6.041883509673075e-09,
+ 5.842365201006973e-09,
+ 5.645124543000992e-09,
+ 5.4501884947938705e-09,
+ 5.257584996151615e-09,
+ 5.067343028402647e-09,
+ 4.879492680809848e-09,
+ 4.694065223019547e-09,
+ 4.511093184322243e-09,
+ 4.330610440571282e-09,
+ 4.152652309737992e-09,
+ 3.977255657238991e-09,
+ 3.804459012359561e-09,
+ 3.6343026973229476e-09,
+ 3.466828970828579e-09,
+ 3.302082188214167e-09,
+ 3.14010898080239e-09,
+ 2.9809584574921523e-09,
+ 2.824682432273164e-09,
+ 2.671335682114642e-09,
+ 2.520976240650311e-09,
+ 2.3736657343142762e-09,
+ 2.2294697691600206e-09,
+ 2.0884583786347395e-09,
+ 1.950706545247075e-09,
+ 1.8162948125906493e-09,
+ 1.6853100089045177e-09,
+ 1.5578461097579523e-09,
+ 1.4340052762778727e-09,
+ 1.3138991177183612e-09,
+ 1.1976502448599187e-09,
+ 1.0853942065341927e-09,
+ 9.772819401316778e-10,
+ 8.734829261463207e-10,
+ 7.741893305491028e-10,
+ 6.796215726633957e-10,
+ 5.90036019761026e-10,
+ 5.057359844093441e-10,
+ 4.2708811048329886e-10,
+ 3.545481171508597e-10,
+ 2.887041771226739e-10,
+ 2.303574786973643e-10,
+ 1.8069545724399782e-10,
+ 1.4177269550716845e-10,
+ 1.1934673366834172e-10,
+ 1.1683417085427136e-10,
+ 1.14321608040201e-10,
+ 1.1180904522613064e-10,
+ 1.0929648241206028e-10,
+ 1.0678391959798992e-10,
+ 1.0427135678391956e-10,
+ 1.017587939698492e-10,
+ 9.924623115577884e-11,
+ 9.673366834170848e-11,
+ 9.422110552763822e-11,
+ 9.170854271356786e-11,
+ 8.91959798994975e-11,
+ 8.668341708542714e-11,
+ 8.417085427135677e-11,
+ 8.165829145728641e-11,
+ 7.914572864321605e-11,
+ 7.663316582914569e-11,
+ 7.412060301507544e-11,
+ 7.160804020100507e-11,
+ 6.909547738693471e-11,
+ 6.658291457286435e-11,
+ 6.407035175879399e-11,
+ 6.155778894472363e-11,
+ 5.904522613065327e-11,
+ 5.653266331658291e-11,
+ 5.402010050251255e-11,
+ 5.150753768844219e-11,
+ 4.899497487437183e-11,
+ 4.648241206030147e-11,
+ 4.3969849246231106e-11,
+ 4.1457286432160746e-11,
+ 3.894472361809049e-11,
+ 3.643216080402013e-11,
+ 3.3919597989949767e-11,
+ 3.1407035175879406e-11,
+ 2.8894472361809045e-11,
+ 2.6381909547738684e-11,
+ 2.3869346733668324e-11,
+ 2.1356783919597963e-11,
+ 1.8844221105527602e-11,
+ 1.633165829145724e-11,
+ 1.381909547738688e-11,
+ 1.1306532663316623e-11,
+ 8.793969849246263e-12,
+ 6.281407035175902e-12,
+ 3.768844221105541e-12,
+ 1.2562814070351804e-12,
+ -1.2562814070351804e-12,
+ -3.768844221105541e-12,
+ -6.281407035175902e-12,
+ -8.793969849246263e-12,
+ -1.1306532663316623e-11,
+ -1.3819095477386984e-11,
+ -1.6331658291457345e-11,
+ -1.8844221105527602e-11,
+ -2.1356783919597963e-11,
+ -2.3869346733668324e-11,
+ -2.6381909547738684e-11,
+ -2.8894472361809045e-11,
+ -3.1407035175879406e-11
+ ],
+ "height": [
+ 0.0,
+ 2.5125628140703518e-08,
+ 5.0251256281407036e-08,
+ 7.537688442211056e-08,
+ 1.0050251256281407e-07,
+ 1.2562814070351758e-07,
+ 1.5075376884422112e-07,
+ 1.7587939698492463e-07,
+ 2.0100502512562815e-07,
+ 2.2613065326633166e-07,
+ 2.5125628140703517e-07,
+ 2.763819095477387e-07,
+ 3.0150753768844224e-07,
+ 3.2663316582914573e-07,
+ 3.5175879396984927e-07,
+ 3.7688442211055275e-07,
+ 4.020100502512563e-07,
+ 4.2713567839195983e-07,
+ 4.522613065326633e-07,
+ 4.773869346733669e-07,
+ 5.025125628140703e-07,
+ 5.276381909547739e-07,
+ 5.527638190954774e-07,
+ 5.778894472361809e-07,
+ 6.030150753768845e-07,
+ 6.28140703517588e-07,
+ 6.532663316582915e-07,
+ 6.783919597989949e-07,
+ 7.035175879396985e-07,
+ 7.28643216080402e-07,
+ 7.537688442211055e-07,
+ 7.788944723618091e-07,
+ 8.040201005025126e-07,
+ 8.291457286432161e-07,
+ 8.542713567839197e-07,
+ 8.793969849246231e-07,
+ 9.045226130653266e-07,
+ 9.296482412060302e-07,
+ 9.547738693467337e-07,
+ 9.798994974874373e-07,
+ 1.0050251256281407e-06,
+ 1.0301507537688443e-06,
+ 1.0552763819095479e-06,
+ 1.0804020100502512e-06,
+ 1.1055276381909548e-06,
+ 1.1306532663316584e-06,
+ 1.1557788944723618e-06,
+ 1.1809045226130654e-06,
+ 1.206030150753769e-06,
+ 1.2311557788944724e-06,
+ 1.256281407035176e-06,
+ 1.2814070351758793e-06,
+ 1.306532663316583e-06,
+ 1.3316582914572865e-06,
+ 1.3567839195979899e-06,
+ 1.3819095477386935e-06,
+ 1.407035175879397e-06,
+ 1.4321608040201004e-06,
+ 1.457286432160804e-06,
+ 1.4824120603015076e-06,
+ 1.507537688442211e-06,
+ 1.5326633165829146e-06,
+ 1.5577889447236182e-06,
+ 1.5829145728643216e-06,
+ 1.6080402010050252e-06,
+ 1.6331658291457288e-06,
+ 1.6582914572864321e-06,
+ 1.6834170854271357e-06,
+ 1.7085427135678393e-06,
+ 1.7336683417085427e-06,
+ 1.7587939698492463e-06,
+ 1.7839195979899499e-06,
+ 1.8090452261306533e-06,
+ 1.8341708542713568e-06,
+ 1.8592964824120604e-06,
+ 1.8844221105527638e-06,
+ 1.9095477386934674e-06,
+ 1.9346733668341708e-06,
+ 1.9597989949748746e-06,
+ 1.984924623115578e-06,
+ 2.0100502512562813e-06,
+ 2.035175879396985e-06,
+ 2.0603015075376885e-06,
+ 2.085427135678392e-06,
+ 2.1105527638190957e-06,
+ 2.135678391959799e-06,
+ 2.1608040201005025e-06,
+ 2.1859296482412063e-06,
+ 2.2110552763819096e-06,
+ 2.236180904522613e-06,
+ 2.261306532663317e-06,
+ 2.28643216080402e-06,
+ 2.3115577889447236e-06,
+ 2.3366834170854274e-06,
+ 2.3618090452261308e-06,
+ 2.386934673366834e-06,
+ 2.412060301507538e-06,
+ 2.4371859296482413e-06,
+ 2.4623115577889447e-06,
+ 2.487437185929648e-06,
+ 2.512562814070352e-06,
+ 2.5376884422110553e-06,
+ 2.5628140703517587e-06,
+ 2.5879396984924625e-06,
+ 2.613065326633166e-06,
+ 2.6381909547738692e-06,
+ 2.663316582914573e-06,
+ 2.6884422110552764e-06,
+ 2.7135678391959798e-06,
+ 2.7386934673366836e-06,
+ 2.763819095477387e-06,
+ 2.7889447236180903e-06,
+ 2.814070351758794e-06,
+ 2.8391959798994975e-06,
+ 2.864321608040201e-06,
+ 2.8894472361809047e-06,
+ 2.914572864321608e-06,
+ 2.9396984924623115e-06,
+ 2.9648241206030153e-06,
+ 2.9899497487437186e-06,
+ 3.015075376884422e-06,
+ 3.040201005025126e-06,
+ 3.065326633165829e-06,
+ 3.0904522613065326e-06,
+ 3.1155778894472364e-06,
+ 3.1407035175879398e-06,
+ 3.165829145728643e-06,
+ 3.190954773869347e-06,
+ 3.2160804020100503e-06,
+ 3.2412060301507537e-06,
+ 3.2663316582914575e-06,
+ 3.291457286432161e-06,
+ 3.3165829145728643e-06,
+ 3.341708542713568e-06,
+ 3.3668341708542714e-06,
+ 3.391959798994975e-06,
+ 3.4170854271356786e-06,
+ 3.442211055276382e-06,
+ 3.4673366834170854e-06,
+ 3.492462311557789e-06,
+ 3.5175879396984926e-06,
+ 3.542713567839196e-06,
+ 3.5678391959798997e-06,
+ 3.592964824120603e-06,
+ 3.6180904522613065e-06,
+ 3.6432160804020103e-06,
+ 3.6683417085427137e-06,
+ 3.693467336683417e-06,
+ 3.718592964824121e-06,
+ 3.7437185929648243e-06,
+ 3.7688442211055276e-06,
+ 3.7939698492462314e-06,
+ 3.819095477386935e-06,
+ 3.844221105527638e-06,
+ 3.8693467336683416e-06,
+ 3.894472361809045e-06,
+ 3.919597989949749e-06,
+ 3.9447236180904526e-06,
+ 3.969849246231156e-06,
+ 3.994974874371859e-06,
+ 4.020100502512563e-06,
+ 4.045226130653266e-06,
+ 4.07035175879397e-06,
+ 4.095477386934674e-06,
+ 4.120603015075377e-06,
+ 4.1457286432160804e-06,
+ 4.170854271356784e-06,
+ 4.195979899497487e-06,
+ 4.221105527638191e-06,
+ 4.246231155778895e-06,
+ 4.271356783919598e-06,
+ 4.2964824120603016e-06,
+ 4.321608040201005e-06,
+ 4.346733668341708e-06,
+ 4.3718592964824125e-06,
+ 4.396984924623116e-06,
+ 4.422110552763819e-06,
+ 4.447236180904523e-06,
+ 4.472361809045226e-06,
+ 4.4974874371859294e-06,
+ 4.522613065326634e-06,
+ 4.547738693467337e-06,
+ 4.57286432160804e-06,
+ 4.597989949748744e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 5e-06,
+ 4.597989949748744e-06,
+ 4.57286432160804e-06,
+ 4.547738693467337e-06,
+ 4.522613065326634e-06,
+ 4.4974874371859294e-06,
+ 4.472361809045226e-06,
+ 4.447236180904523e-06,
+ 4.422110552763819e-06,
+ 4.396984924623116e-06,
+ 4.3718592964824125e-06,
+ 4.346733668341708e-06,
+ 4.321608040201005e-06,
+ 4.2964824120603016e-06,
+ 4.271356783919598e-06,
+ 4.246231155778895e-06,
+ 4.221105527638191e-06,
+ 4.195979899497487e-06,
+ 4.170854271356784e-06,
+ 4.1457286432160804e-06,
+ 4.120603015075377e-06,
+ 4.095477386934674e-06,
+ 4.07035175879397e-06,
+ 4.045226130653266e-06,
+ 4.020100502512563e-06,
+ 3.994974874371859e-06,
+ 3.969849246231156e-06,
+ 3.9447236180904526e-06,
+ 3.919597989949749e-06,
+ 3.894472361809045e-06,
+ 3.8693467336683416e-06,
+ 3.844221105527638e-06,
+ 3.819095477386935e-06,
+ 3.7939698492462314e-06,
+ 3.7688442211055276e-06,
+ 3.7437185929648243e-06,
+ 3.718592964824121e-06,
+ 3.693467336683417e-06,
+ 3.6683417085427137e-06,
+ 3.6432160804020103e-06,
+ 3.6180904522613065e-06,
+ 3.592964824120603e-06,
+ 3.5678391959798997e-06,
+ 3.542713567839196e-06,
+ 3.5175879396984926e-06,
+ 3.492462311557789e-06,
+ 3.4673366834170854e-06,
+ 3.442211055276382e-06,
+ 3.4170854271356786e-06,
+ 3.391959798994975e-06,
+ 3.3668341708542714e-06,
+ 3.341708542713568e-06,
+ 3.3165829145728643e-06,
+ 3.291457286432161e-06,
+ 3.2663316582914575e-06,
+ 3.2412060301507537e-06,
+ 3.2160804020100503e-06,
+ 3.190954773869347e-06,
+ 3.165829145728643e-06,
+ 3.1407035175879398e-06,
+ 3.1155778894472364e-06,
+ 3.0904522613065326e-06,
+ 3.065326633165829e-06,
+ 3.040201005025126e-06,
+ 3.015075376884422e-06,
+ 2.9899497487437186e-06,
+ 2.9648241206030153e-06,
+ 2.9396984924623115e-06,
+ 2.914572864321608e-06,
+ 2.8894472361809047e-06,
+ 2.864321608040201e-06,
+ 2.8391959798994975e-06,
+ 2.814070351758794e-06,
+ 2.7889447236180903e-06,
+ 2.763819095477387e-06,
+ 2.7386934673366836e-06,
+ 2.7135678391959798e-06,
+ 2.6884422110552764e-06,
+ 2.663316582914573e-06,
+ 2.6381909547738692e-06,
+ 2.613065326633166e-06,
+ 2.5879396984924625e-06,
+ 2.5628140703517587e-06,
+ 2.5376884422110553e-06,
+ 2.512562814070352e-06,
+ 2.487437185929648e-06,
+ 2.4623115577889447e-06,
+ 2.4371859296482413e-06,
+ 2.412060301507538e-06,
+ 2.386934673366834e-06,
+ 2.3618090452261308e-06,
+ 2.3366834170854274e-06,
+ 2.3115577889447236e-06,
+ 2.28643216080402e-06,
+ 2.261306532663317e-06,
+ 2.236180904522613e-06,
+ 2.2110552763819096e-06,
+ 2.1859296482412063e-06,
+ 2.1608040201005025e-06,
+ 2.135678391959799e-06,
+ 2.1105527638190957e-06,
+ 2.085427135678392e-06,
+ 2.0603015075376885e-06,
+ 2.035175879396985e-06,
+ 2.0100502512562813e-06,
+ 1.984924623115578e-06,
+ 1.9597989949748746e-06,
+ 1.9346733668341708e-06,
+ 1.9095477386934674e-06,
+ 1.8844221105527638e-06,
+ 1.8592964824120604e-06,
+ 1.8341708542713568e-06,
+ 1.8090452261306533e-06,
+ 1.7839195979899499e-06,
+ 1.7587939698492463e-06,
+ 1.7336683417085427e-06,
+ 1.7085427135678393e-06,
+ 1.6834170854271357e-06,
+ 1.6582914572864321e-06,
+ 1.6331658291457288e-06,
+ 1.6080402010050252e-06,
+ 1.5829145728643216e-06,
+ 1.5577889447236182e-06,
+ 1.5326633165829146e-06,
+ 1.507537688442211e-06,
+ 1.4824120603015076e-06,
+ 1.457286432160804e-06,
+ 1.4321608040201004e-06,
+ 1.407035175879397e-06,
+ 1.3819095477386935e-06,
+ 1.3567839195979899e-06,
+ 1.3316582914572865e-06,
+ 1.306532663316583e-06,
+ 1.2814070351758793e-06,
+ 1.256281407035176e-06,
+ 1.2311557788944724e-06,
+ 1.206030150753769e-06,
+ 1.1809045226130654e-06,
+ 1.1557788944723618e-06,
+ 1.1306532663316584e-06,
+ 1.1055276381909548e-06,
+ 1.0804020100502512e-06,
+ 1.0552763819095479e-06,
+ 1.0301507537688443e-06,
+ 1.0050251256281407e-06,
+ 9.798994974874373e-07,
+ 9.547738693467337e-07,
+ 9.296482412060302e-07,
+ 9.045226130653266e-07,
+ 8.793969849246231e-07,
+ 8.542713567839197e-07,
+ 8.291457286432161e-07,
+ 8.040201005025126e-07,
+ 7.788944723618091e-07,
+ 7.537688442211055e-07,
+ 7.28643216080402e-07,
+ 7.035175879396985e-07,
+ 6.783919597989949e-07,
+ 6.532663316582915e-07,
+ 6.28140703517588e-07,
+ 6.030150753768845e-07,
+ 5.778894472361809e-07,
+ 5.527638190954774e-07,
+ 5.276381909547739e-07,
+ 5.025125628140703e-07,
+ 4.773869346733669e-07,
+ 4.522613065326633e-07,
+ 4.2713567839195983e-07,
+ 4.020100502512563e-07,
+ 3.7688442211055275e-07,
+ 3.5175879396984927e-07,
+ 3.2663316582914573e-07,
+ 3.0150753768844224e-07,
+ 2.763819095477387e-07,
+ 2.5125628140703517e-07,
+ 2.2613065326633166e-07,
+ 2.0100502512562815e-07,
+ 1.7587939698492463e-07,
+ 1.5075376884422112e-07,
+ 1.2562814070351758e-07,
+ 1.0050251256281407e-07,
+ 7.537688442211056e-08,
+ 5.0251256281407036e-08,
+ 2.5125628140703518e-08,
+ 0.0
+ ],
+ "segment": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ "tip_position": [
+ -6.539195979899498e-07,
+ -6.287688442211055e-07,
+ -6.036180904522613e-07,
+ -5.78467336683417e-07,
+ -5.533165829145729e-07,
+ -5.281658291457286e-07,
+ -5.030150753768844e-07,
+ -4.778643216080402e-07,
+ -4.52713567839196e-07,
+ -4.2756281407035174e-07,
+ -4.0241206030150754e-07,
+ -3.772613065326633e-07,
+ -3.5211055276381904e-07,
+ -3.2695979899497484e-07,
+ -3.0180904522613064e-07,
+ -2.7665829145728644e-07,
+ -2.515075376884422e-07,
+ -2.2635678391959794e-07,
+ -2.0120603015075374e-07,
+ -1.7605527638190955e-07,
+ -1.509045226130653e-07,
+ -1.2575376884422104e-07,
+ -1.006030150753769e-07,
+ -7.545226130653265e-08,
+ -5.0301507537688396e-08,
+ -2.5150753768844145e-08,
+ 0.0,
+ 2.515075376884425e-08,
+ 5.03015075376885e-08,
+ 7.545226130653265e-08,
+ 1.006030150753769e-07,
+ 1.2575376884422115e-07,
+ 1.509045226130654e-07,
+ 1.7605527638190955e-07,
+ 2.012060301507538e-07,
+ 2.2635678391959805e-07,
+ 2.515075376884422e-07,
+ 2.7665829145728655e-07,
+ 3.018090452261307e-07,
+ 3.2695979899497505e-07,
+ 3.52110552763819e-07,
+ 3.7726130653266334e-07,
+ 4.024120603015077e-07,
+ 4.2756281407035185e-07,
+ 4.52713567839196e-07,
+ 4.778643216080403e-07,
+ 5.030150753768845e-07,
+ 5.281658291457286e-07,
+ 5.53316582914573e-07,
+ 5.784673366834171e-07,
+ 6.036180904522613e-07,
+ 6.287688442211054e-07,
+ 6.539195979899498e-07,
+ 6.790703517587941e-07,
+ 7.042211055276381e-07,
+ 7.293718592964824e-07,
+ 7.545226130653268e-07,
+ 7.796733668341707e-07,
+ 8.048241206030151e-07,
+ 8.299748743718594e-07,
+ 8.551256281407036e-07,
+ 8.804755158997953e-07,
+ 9.059903716578673e-07,
+ 9.316126200131043e-07,
+ 9.57321715138061e-07,
+ 9.831057826790464e-07,
+ 1.0089568107530744e-06,
+ 1.0348689176330384e-06,
+ 1.0608375461272588e-06,
+ 1.0868590297969856e-06,
+ 1.1129303355165465e-06,
+ 1.1390488996132223e-06,
+ 1.1652125178937793e-06,
+ 1.191419268698508e-06,
+ 1.2176674572224688e-06,
+ 1.2439555740917564e-06,
+ 1.2702822638180552e-06,
+ 1.2966463002935594e-06,
+ 1.3230465674257288e-06,
+ 1.3494820436032934e-06,
+ 1.3759517890705612e-06,
+ 1.4024549355451413e-06,
+ 1.4289906775910979e-06,
+ 1.4555582653833436e-06,
+ 1.4821569985874077e-06,
+ 1.5087862211427544e-06,
+ 1.535445316785043e-06,
+ 1.5621337051779367e-06,
+ 1.5888508385517423e-06,
+ 1.6155961987665638e-06,
+ 1.6423692947334115e-06,
+ 1.6691696601390588e-06,
+ 1.695996851430128e-06,
+ 1.7228504460196263e-06,
+ 1.7497300406853196e-06,
+ 1.7766352501343558e-06,
+ 1.8035657057125694e-06,
+ 1.8305210542402457e-06,
+ 1.857500956958852e-06,
+ 1.8845050885754837e-06,
+ 1.911533136393677e-06,
+ 1.938584799520803e-06,
+ 1.9656597881435775e-06,
+ 1.992757822864341e-06,
+ 2.0198786340917054e-06,
+ 2.047021961479978e-06,
+ 2.074187553412447e-06,
+ 2.101375166524217e-06,
+ 2.1285845652607854e-06,
+ 2.155815521468985e-06,
+ 2.1830678140173085e-06,
+ 2.210341228442958e-06,
+ 2.237635556623238e-06,
+ 2.2649505964691854e-06,
+ 2.2922861516395356e-06,
+ 2.3196420312733175e-06,
+ 2.3470180497395473e-06,
+ 2.3744140264026476e-06,
+ 2.4018297854023326e-06,
+ 2.429265155446837e-06,
+ 2.456719969618474e-06,
+ 2.484194065190576e-06,
+ 2.511687283454993e-06,
+ 2.539199469559367e-06,
+ 2.5667304723534863e-06,
+ 2.5942801442440745e-06,
+ 2.621848341057436e-06,
+ 2.6494349219094097e-06,
+ 2.6770397490821436e-06,
+ 2.7046626879072413e-06,
+ 2.732303606654853e-06,
+ 2.759962376428334e-06,
+ 2.78763887106412e-06,
+ 2.815332967036482e-06,
+ 2.843044543366865e-06,
+ 2.8707734815375343e-06,
+ 2.898519665409262e-06,
+ 2.9262829811428164e-06,
+ 2.9540633171240357e-06,
+ 2.9818605638922746e-06,
+ 3.0096746140720272e-06,
+ 3.037505362307559e-06,
+ 3.065352705200364e-06,
+ 3.0932165412493064e-06,
+ 3.1210967707932917e-06,
+ 3.148993295956333e-06,
+ 3.1769060205948893e-06,
+ 3.2048348502473516e-06,
+ 3.232779692085566e-06,
+ 3.2607404548682913e-06,
+ 3.2887170488964984e-06,
+ 3.316709385970402e-06,
+ 3.3447173793481576e-06,
+ 3.3727409437061306e-06,
+ 3.40077999510067e-06,
+ 3.428834450931302e-06,
+ 3.4569042299052917e-06,
+ 3.4849892520034856e-06,
+ 3.513089438447414e-06,
+ 3.5412047116675466e-06,
+ 3.5693349952726886e-06,
+ 3.597480214020446e-06,
+ 3.6256402937887173e-06,
+ 3.653815161548163e-06,
+ 3.682004745335622e-06,
+ 3.710208974228412e-06,
+ 3.7384277783195002e-06,
+ 3.766661088693489e-06,
+ 3.7949088374033923e-06,
+ 3.823170957448163e-06,
+ 3.851447382750948e-06,
+ 3.879738048138035e-06,
+ 3.908042889318462e-06,
+ 3.9363618428642705e-06,
+ 3.964694846191367e-06,
+ 3.993041837540975e-06,
+ 4.021402755961656e-06,
+ 4.04977754129187e-06,
+ 4.078166134143059e-06,
+ 4.1065684758832376e-06,
+ 4.13498450862106e-06,
+ 4.1634141751903614e-06,
+ 4.191857419135139e-06,
+ 4.220314184694974e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.677418363705154e-06,
+ 4.220314184694974e-06,
+ 4.191857419135139e-06,
+ 4.1634141751903614e-06,
+ 4.13498450862106e-06,
+ 4.1065684758832376e-06,
+ 4.078166134143059e-06,
+ 4.04977754129187e-06,
+ 4.021402755961656e-06,
+ 3.993041837540975e-06,
+ 3.964694846191367e-06,
+ 3.9363618428642705e-06,
+ 3.908042889318462e-06,
+ 3.879738048138035e-06,
+ 3.851447382750948e-06,
+ 3.823170957448163e-06,
+ 3.7949088374033923e-06,
+ 3.766661088693489e-06,
+ 3.7384277783195002e-06,
+ 3.710208974228412e-06,
+ 3.682004745335622e-06,
+ 3.653815161548163e-06,
+ 3.6256402937887173e-06,
+ 3.597480214020446e-06,
+ 3.5693349952726886e-06,
+ 3.5412047116675466e-06,
+ 3.513089438447414e-06,
+ 3.4849892520034856e-06,
+ 3.4569042299052917e-06,
+ 3.428834450931302e-06,
+ 3.40077999510067e-06,
+ 3.3727409437061306e-06,
+ 3.3447173793481576e-06,
+ 3.316709385970402e-06,
+ 3.2887170488964984e-06,
+ 3.2607404548682913e-06,
+ 3.232779692085566e-06,
+ 3.2048348502473516e-06,
+ 3.1769060205948893e-06,
+ 3.148993295956333e-06,
+ 3.1210967707932917e-06,
+ 3.0932165412493064e-06,
+ 3.065352705200364e-06,
+ 3.037505362307559e-06,
+ 3.0096746140720272e-06,
+ 2.9818605638922746e-06,
+ 2.9540633171240357e-06,
+ 2.9262829811428164e-06,
+ 2.898519665409262e-06,
+ 2.8707734815375343e-06,
+ 2.843044543366865e-06,
+ 2.815332967036482e-06,
+ 2.78763887106412e-06,
+ 2.759962376428334e-06,
+ 2.732303606654853e-06,
+ 2.7046626879072413e-06,
+ 2.6770397490821436e-06,
+ 2.6494349219094097e-06,
+ 2.621848341057436e-06,
+ 2.5942801442440745e-06,
+ 2.5667304723534863e-06,
+ 2.539199469559367e-06,
+ 2.511687283454993e-06,
+ 2.484194065190576e-06,
+ 2.456719969618474e-06,
+ 2.429265155446837e-06,
+ 2.4018297854023326e-06,
+ 2.3744140264026476e-06,
+ 2.3470180497395473e-06,
+ 2.3196420312733175e-06,
+ 2.2922861516395356e-06,
+ 2.2649505964691854e-06,
+ 2.237635556623238e-06,
+ 2.210341228442958e-06,
+ 2.1830678140173085e-06,
+ 2.155815521468985e-06,
+ 2.1285845652607854e-06,
+ 2.101375166524217e-06,
+ 2.074187553412447e-06,
+ 2.047021961479978e-06,
+ 2.0198786340917054e-06,
+ 1.992757822864341e-06,
+ 1.9656597881435775e-06,
+ 1.938584799520803e-06,
+ 1.911533136393677e-06,
+ 1.8845050885754837e-06,
+ 1.857500956958852e-06,
+ 1.8305210542402457e-06,
+ 1.8035657057125694e-06,
+ 1.7766352501343558e-06,
+ 1.7497300406853196e-06,
+ 1.7228504460196263e-06,
+ 1.695996851430128e-06,
+ 1.6691696601390588e-06,
+ 1.6423692947334115e-06,
+ 1.6155961987665638e-06,
+ 1.5888508385517423e-06,
+ 1.5621337051779367e-06,
+ 1.535445316785043e-06,
+ 1.5087862211427544e-06,
+ 1.4821569985874077e-06,
+ 1.4555582653833436e-06,
+ 1.4289906775910979e-06,
+ 1.4024549355451413e-06,
+ 1.3759517890705612e-06,
+ 1.3494820436032934e-06,
+ 1.3230465674257288e-06,
+ 1.2966463002935594e-06,
+ 1.2702822638180552e-06,
+ 1.2439555740917564e-06,
+ 1.2176674572224688e-06,
+ 1.191419268698508e-06,
+ 1.1652125178937793e-06,
+ 1.1390488996132223e-06,
+ 1.1129303355165465e-06,
+ 1.0868590297969856e-06,
+ 1.0608375461272588e-06,
+ 1.0348689176330384e-06,
+ 1.0089568107530744e-06,
+ 9.831057826790464e-07,
+ 9.57321715138061e-07,
+ 9.316126200131043e-07,
+ 9.059903716578673e-07,
+ 8.804755158997953e-07,
+ 8.551256281407036e-07,
+ 8.299748743718594e-07,
+ 8.048241206030151e-07,
+ 7.796733668341707e-07,
+ 7.545226130653268e-07,
+ 7.293718592964824e-07,
+ 7.042211055276381e-07,
+ 6.790703517587941e-07,
+ 6.539195979899498e-07,
+ 6.287688442211054e-07,
+ 6.036180904522613e-07,
+ 5.784673366834171e-07,
+ 5.53316582914573e-07,
+ 5.281658291457286e-07,
+ 5.030150753768845e-07,
+ 4.778643216080403e-07,
+ 4.52713567839196e-07,
+ 4.2756281407035185e-07,
+ 4.024120603015077e-07,
+ 3.7726130653266334e-07,
+ 3.52110552763819e-07,
+ 3.2695979899497505e-07,
+ 3.018090452261307e-07,
+ 2.7665829145728655e-07,
+ 2.515075376884422e-07,
+ 2.2635678391959805e-07,
+ 2.012060301507538e-07,
+ 1.7605527638190955e-07,
+ 1.509045226130654e-07,
+ 1.2575376884422115e-07,
+ 1.006030150753769e-07,
+ 7.545226130653265e-08,
+ 5.03015075376885e-08,
+ 2.515075376884425e-08,
+ 0.0,
+ -2.5150753768844145e-08,
+ -5.0301507537688396e-08,
+ -7.545226130653265e-08,
+ -1.006030150753769e-07,
+ -1.2575376884422104e-07,
+ -1.509045226130653e-07,
+ -1.7605527638190955e-07,
+ -2.0120603015075374e-07,
+ -2.2635678391959794e-07,
+ -2.515075376884422e-07,
+ -2.7665829145728644e-07,
+ -3.0180904522613064e-07,
+ -3.2695979899497484e-07,
+ -3.5211055276381904e-07,
+ -3.772613065326633e-07,
+ -4.0241206030150754e-07,
+ -4.2756281407035174e-07,
+ -4.52713567839196e-07,
+ -4.778643216080402e-07,
+ -5.030150753768844e-07,
+ -5.281658291457286e-07,
+ -5.533165829145729e-07,
+ -5.78467336683417e-07,
+ -6.036180904522613e-07,
+ -6.287688442211055e-07,
+ -6.539195979899498e-07
+ ]
+ },
+ "P13": {
+ "contact": {
+ "deviation_from_baseline": 28,
+ "fit_constant_line": 83,
+ "fit_constant_polynomial": 51,
+ "fit_line_polynomial": 51
+ },
+ "force": [
+ 3.3919597989949844e-11,
+ 3.3919597989949844e-11,
+ 3.3919597989949844e-11,
+ 3.391959798994987e-11,
+ 3.3919597989949877e-11,
+ 3.391959798994988e-11,
+ 3.3919597989949786e-11,
+ 3.39195979899498e-11,
+ 3.3919597989949805e-11,
+ 3.391959798994981e-11,
+ 3.3919597989949825e-11,
+ 3.391959798994983e-11,
+ 3.391959798994984e-11,
+ 3.391959798994985e-11,
+ 3.391959798994986e-11,
+ 3.391959798994987e-11,
+ 3.3919597989949877e-11,
+ 3.391959798994988e-11,
+ 3.391959798994979e-11,
+ 3.3919597989949805e-11,
+ 3.3919597989949805e-11,
+ 3.391959798994981e-11,
+ 3.391959798994983e-11,
+ 3.391959798994984e-11,
+ 3.3919597989949844e-11,
+ 3.3919597989949844e-11,
+ 3.3919597989949864e-11,
+ 3.391959798994987e-11,
+ 3.643216080402023e-11,
+ 3.894472361809049e-11,
+ 4.145728643216085e-11,
+ 4.396984924623121e-11,
+ 4.648241206030157e-11,
+ 4.899497487437193e-11,
+ 5.150753768844229e-11,
+ 5.402010050251265e-11,
+ 5.6532663316583013e-11,
+ 5.904522613065337e-11,
+ 6.155778894472373e-11,
+ 6.40703517587941e-11,
+ -1.3341708542713558e-10,
+ 6.909547738693482e-11,
+ 7.160804020100518e-11,
+ 7.412060301507544e-11,
+ 7.66331658291458e-11,
+ 7.914572864321616e-11,
+ 8.165829145728652e-11,
+ 8.417085427135688e-11,
+ 8.668341708542724e-11,
+ 8.91959798994976e-11,
+ 9.170854271356796e-11,
+ 9.422110552763822e-11,
+ 9.673366834170858e-11,
+ 9.924623115577894e-11,
+ 1.017587939698493e-10,
+ 1.0427135678391966e-10,
+ 1.0678391959799002e-10,
+ 1.0929648241206038e-10,
+ 1.1180904522613074e-10,
+ 1.143216080402011e-10,
+ 1.1683417085427146e-10,
+ 1.392601326930982e-10,
+ 1.7818289442992757e-10,
+ 2.2784491588329405e-10,
+ 2.8619161430860365e-10,
+ 3.5203555433678944e-10,
+ 4.245755476692286e-10,
+ 5.032234215952738e-10,
+ 5.875234569469557e-10,
+ 6.771090098493254e-10,
+ 7.716767677350325e-10,
+ 8.709703633322505e-10,
+ 9.747693773176074e-10,
+ 1.0828816437201223e-09,
+ 1.1951376820458483e-09,
+ 1.3113865549042909e-09,
+ 1.4314927134638023e-09,
+ 1.555333546943882e-09,
+ 1.6827974460904473e-09,
+ 1.813782249776579e-09,
+ 1.948193982433005e-09,
+ 2.0859458158206693e-09,
+ 2.2269572063459504e-09,
+ 2.371153171500206e-09,
+ 2.518463677836241e-09,
+ 2.6688231193005718e-09,
+ 2.822169869459094e-09,
+ 2.978445894678082e-09,
+ 3.1375964179883197e-09,
+ 3.299569625400097e-09,
+ 3.4643164080145087e-09,
+ 3.6317901345088774e-09,
+ 3.801946449545491e-09,
+ 3.974743094424921e-09,
+ 4.150139746923922e-09,
+ 4.3280978777572115e-09,
+ 4.508580621508173e-09,
+ 4.691552660205477e-09,
+ 4.876980117995778e-09,
+ 5.064830465588577e-09,
+ 5.255072433337545e-09,
+ 5.4476759319798e-09,
+ 5.642611980186922e-09,
+ 5.839852638192903e-09,
+ 6.0393709468590044e-09,
+ 6.241140871615914e-09,
+ 6.445137250792455e-09,
+ 6.65133574789912e-09,
+ 6.859712807485581e-09,
+ 7.070245614235139e-09,
+ 7.282912054997178e-09,
+ 7.497690683491785e-09,
+ 7.714560687449419e-09,
+ 7.933501857973829e-09,
+ 8.154494560938513e-09,
+ 8.3775197102463e-09,
+ 8.602558742798938e-09,
+ 8.82959359503864e-09,
+ 9.058606680936748e-09,
+ 9.28958087131688e-09,
+ 9.522499474410204e-09,
+ 9.757346217550045e-09,
+ 9.9941052299214e-09,
+ 1.0232761026288466e-08,
+ 1.0473298491630014e-08,
+ 1.0715702866618505e-08,
+ 1.0959959733884316e-08,
+ 1.1206055005011306e-08,
+ 1.1453974908214373e-08,
+ 1.1703705976653802e-08,
+ 1.1955235037344581e-08,
+ 1.2208549200622345e-08,
+ 1.24636358501306e-08,
+ 1.2720482633296406e-08,
+ 1.2979077452264365e-08,
+ 1.3239408455260979e-08,
+ 1.3501464028363385e-08,
+ 1.3765232787648454e-08,
+ 1.4030703571700053e-08,
+ 1.4297865434453563e-08,
+ 1.4566707638358508e-08,
+ 1.4837219647841329e-08,
+ 1.510939112305149e-08,
+ 1.538321191387538e-08,
+ 1.5658672054203537e-08,
+ 1.5935761756437322e-08,
+ 1.621447140622263e-08,
+ 1.649479155739848e-08,
+ 1.6776712927149512e-08,
+ 1.7060226391351772e-08,
+ 1.734532298010215e-08,
+ 1.7631993873422133e-08,
+ 1.7920230397127276e-08,
+ 1.821002401885425e-08,
+ 1.850136634423786e-08,
+ 1.8794249113230785e-08,
+ 1.9088664196559265e-08,
+ 1.9384603592308372e-08,
+ 1.9682059422630854e-08,
+ 1.998102393057376e-08,
+ 2.028148947701763e-08,
+ 2.0583448537723035e-08,
+ 2.0886893700479763e-08,
+ 2.1191817662354026e-08,
+ 2.149821322702953e-08,
+ 2.1806073302238184e-08,
+ 2.2115390897276654e-08,
+ 2.24261591206052e-08,
+ 2.273837117752515e-08,
+ 2.3052020367931855e-08,
+ 2.336710008414004e-08,
+ 2.3683603808778393e-08,
+ 2.400152511275077e-08,
+ 2.4320857653261244e-08,
+ 2.464159517190049e-08,
+ 2.4963731492790932e-08,
+ 2.5287260520788715e-08,
+ 2.5612176239739766e-08,
+ 2.5938472710788365e-08,
+ 2.626614407073583e-08,
+ 2.6595184530447713e-08,
+ 2.6925588373307454e-08,
+ 2.725734995371493e-08,
+ 2.7590463695628124e-08,
+ 2.7924924091146382e-08,
+ 2.8260725699133787e-08,
+ 2.85978631438811e-08,
+ 2.893633111380495e-08,
+ 2.9276124360183016e-08,
+ 2.9617237695923797e-08,
+ 2.995966599436981e-08,
+ 3.0303404188133144e-08,
+ 3.064844726796217e-08,
+ 3.099479028163824e-08,
+ 3.134242833290169e-08,
+ 3.16913565804058e-08,
+ 3.2041570236697896e-08,
+ 3.239306456722688e-08,
+ 3.2745834889376026e-08,
+ 3.309987657152037e-08,
+ 3.309987657152037e-08,
+ 3.2745834889376026e-08,
+ 3.239306456722688e-08,
+ 3.2041570236697896e-08,
+ 3.16913565804058e-08,
+ 3.134242833290169e-08,
+ 3.099479028163824e-08,
+ 3.064844726796217e-08,
+ 3.0303404188133144e-08,
+ 2.995966599436981e-08,
+ 2.9617237695923797e-08,
+ 2.9276124360183016e-08,
+ 2.893633111380495e-08,
+ 2.85978631438811e-08,
+ 2.8260725699133787e-08,
+ 2.7924924091146382e-08,
+ 2.7590463695628124e-08,
+ 2.725734995371493e-08,
+ 2.6925588373307454e-08,
+ 2.6595184530447713e-08,
+ 2.626614407073583e-08,
+ 2.5938472710788365e-08,
+ 2.5612176239739766e-08,
+ 2.5287260520788715e-08,
+ 2.4963731492790932e-08,
+ 2.464159517190049e-08,
+ 2.4320857653261244e-08,
+ 2.400152511275077e-08,
+ 2.3683603808778393e-08,
+ 2.336710008414004e-08,
+ 2.3052020367931855e-08,
+ 2.273837117752515e-08,
+ 2.24261591206052e-08,
+ 2.2115390897276654e-08,
+ 2.1806073302238184e-08,
+ 2.149821322702953e-08,
+ 2.1191817662354026e-08,
+ 2.0886893700479763e-08,
+ 2.0583448537723035e-08,
+ 2.028148947701763e-08,
+ 1.998102393057376e-08,
+ 1.9682059422630854e-08,
+ 1.9384603592308372e-08,
+ 1.9088664196559265e-08,
+ 1.8794249113230785e-08,
+ 1.850136634423786e-08,
+ 1.821002401885425e-08,
+ 1.7920230397127276e-08,
+ 1.7631993873422133e-08,
+ 1.734532298010215e-08,
+ 1.7060226391351772e-08,
+ 1.6776712927149512e-08,
+ 1.649479155739848e-08,
+ 1.621447140622263e-08,
+ 1.5935761756437322e-08,
+ 1.5658672054203537e-08,
+ 1.538321191387538e-08,
+ 1.510939112305149e-08,
+ 1.4837219647841329e-08,
+ 1.4566707638358508e-08,
+ 1.4297865434453563e-08,
+ 1.4030703571700053e-08,
+ 1.3765232787648454e-08,
+ 1.3501464028363385e-08,
+ 1.3239408455260979e-08,
+ 1.2979077452264365e-08,
+ 1.2720482633296406e-08,
+ 1.24636358501306e-08,
+ 1.2208549200622345e-08,
+ 1.1955235037344581e-08,
+ 1.1703705976653802e-08,
+ 1.1453974908214373e-08,
+ 1.1206055005011306e-08,
+ 1.0959959733884316e-08,
+ 1.0715702866618505e-08,
+ 1.0473298491630014e-08,
+ 1.0232761026288466e-08,
+ 9.9941052299214e-09,
+ 9.757346217550045e-09,
+ 9.522499474410204e-09,
+ 9.28958087131688e-09,
+ 9.058606680936748e-09,
+ 8.82959359503864e-09,
+ 8.602558742798938e-09,
+ 8.3775197102463e-09,
+ 8.154494560938513e-09,
+ 7.933501857973829e-09,
+ 7.714560687449419e-09,
+ 7.497690683491785e-09,
+ 7.282912054997178e-09,
+ 7.070245614235139e-09,
+ 6.859712807485581e-09,
+ 6.65133574789912e-09,
+ 6.445137250792455e-09,
+ 6.241140871615914e-09,
+ 6.0393709468590044e-09,
+ 5.839852638192903e-09,
+ 5.642611980186922e-09,
+ 5.4476759319798e-09,
+ 5.255072433337545e-09,
+ 5.064830465588577e-09,
+ 4.876980117995778e-09,
+ 4.691552660205477e-09,
+ 4.508580621508173e-09,
+ 4.3280978777572115e-09,
+ 4.150139746923922e-09,
+ 3.974743094424921e-09,
+ 3.801946449545491e-09,
+ 3.6317901345088774e-09,
+ 3.4643164080145087e-09,
+ 3.299569625400097e-09,
+ 3.1375964179883197e-09,
+ 2.978445894678082e-09,
+ 2.822169869459094e-09,
+ 2.6688231193005718e-09,
+ 2.518463677836241e-09,
+ 2.371153171500206e-09,
+ 2.2269572063459504e-09,
+ 2.0859458158206693e-09,
+ 1.948193982433005e-09,
+ 1.813782249776579e-09,
+ 1.6827974460904473e-09,
+ 1.555333546943882e-09,
+ 1.4314927134638023e-09,
+ 1.3113865549042909e-09,
+ 1.1951376820458483e-09,
+ 1.0828816437201223e-09,
+ 9.747693773176074e-10,
+ 8.709703633322505e-10,
+ 7.716767677350325e-10,
+ 6.771090098493254e-10,
+ 5.875234569469557e-10,
+ 5.032234215952738e-10,
+ 4.245755476692286e-10,
+ 3.5203555433678944e-10,
+ 2.8619161430860365e-10,
+ 2.2784491588329405e-10,
+ 1.7818289442992757e-10,
+ 1.392601326930982e-10,
+ 1.1683417085427146e-10,
+ 1.143216080402011e-10,
+ 1.1180904522613074e-10,
+ 1.0929648241206038e-10,
+ 1.0678391959799002e-10,
+ 1.0427135678391966e-10,
+ 1.017587939698493e-10,
+ 9.924623115577894e-11,
+ 9.673366834170858e-11,
+ 9.422110552763822e-11,
+ 9.170854271356796e-11,
+ 8.91959798994976e-11,
+ 8.668341708542724e-11,
+ 8.417085427135688e-11,
+ 8.165829145728652e-11,
+ 7.914572864321616e-11,
+ 7.66331658291458e-11,
+ 7.412060301507544e-11,
+ 7.160804020100518e-11,
+ 6.909547738693482e-11,
+ -1.3341708542713558e-10,
+ -1.4359296482412059e-09,
+ 6.155778894472373e-11,
+ 5.904522613065337e-11,
+ 5.6532663316583013e-11,
+ 5.402010050251265e-11,
+ 5.150753768844229e-11,
+ 4.899497487437193e-11,
+ 4.648241206030157e-11,
+ 4.396984924623121e-11,
+ 4.145728643216085e-11,
+ 3.894472361809049e-11,
+ 3.643216080402023e-11,
+ 3.391959798994987e-11,
+ 3.140703517587951e-11,
+ 2.889447236180915e-11,
+ 2.6381909547738788e-11,
+ 2.3869346733668427e-11,
+ 2.1356783919598066e-11,
+ 1.8844221105527706e-11,
+ 1.6331658291457345e-11,
+ 1.3819095477386984e-11,
+ 1.1306532663316623e-11,
+ 8.793969849246366e-12,
+ 6.281407035176005e-12,
+ 3.7688442211056445e-12,
+ 1.2562814070352838e-12,
+ -1.256281407035077e-12,
+ -3.768844221105438e-12,
+ -6.2814070351757985e-12,
+ -8.79396984924616e-12,
+ -1.130653266331652e-11,
+ -1.381909547738688e-11,
+ -1.633165829145724e-11,
+ -1.8844221105527602e-11,
+ -2.135678391959786e-11,
+ -2.386934673366822e-11,
+ -2.638190954773858e-11,
+ -2.8894472361808942e-11,
+ -3.14070351758793e-11,
+ -3.3919597989949663e-11
+ ],
+ "height": [
+ 0.0,
+ 2.5125628140703518e-08,
+ 5.0251256281407036e-08,
+ 7.537688442211056e-08,
+ 1.0050251256281407e-07,
+ 1.2562814070351758e-07,
+ 1.5075376884422112e-07,
+ 1.7587939698492463e-07,
+ 2.0100502512562815e-07,
+ 2.2613065326633166e-07,
+ 2.5125628140703517e-07,
+ 2.763819095477387e-07,
+ 3.0150753768844224e-07,
+ 3.2663316582914573e-07,
+ 3.5175879396984927e-07,
+ 3.7688442211055275e-07,
+ 4.020100502512563e-07,
+ 4.2713567839195983e-07,
+ 4.522613065326633e-07,
+ 4.773869346733669e-07,
+ 5.025125628140703e-07,
+ 5.276381909547739e-07,
+ 5.527638190954774e-07,
+ 5.778894472361809e-07,
+ 6.030150753768845e-07,
+ 6.28140703517588e-07,
+ 6.532663316582915e-07,
+ 6.783919597989949e-07,
+ 7.035175879396985e-07,
+ 7.28643216080402e-07,
+ 7.537688442211055e-07,
+ 7.788944723618091e-07,
+ 8.040201005025126e-07,
+ 8.291457286432161e-07,
+ 8.542713567839197e-07,
+ 8.793969849246231e-07,
+ 9.045226130653266e-07,
+ 9.296482412060302e-07,
+ 9.547738693467337e-07,
+ 9.798994974874373e-07,
+ 1.0050251256281407e-06,
+ 1.0301507537688443e-06,
+ 1.0552763819095479e-06,
+ 1.0804020100502512e-06,
+ 1.1055276381909548e-06,
+ 1.1306532663316584e-06,
+ 1.1557788944723618e-06,
+ 1.1809045226130654e-06,
+ 1.206030150753769e-06,
+ 1.2311557788944724e-06,
+ 1.256281407035176e-06,
+ 1.2814070351758793e-06,
+ 1.306532663316583e-06,
+ 1.3316582914572865e-06,
+ 1.3567839195979899e-06,
+ 1.3819095477386935e-06,
+ 1.407035175879397e-06,
+ 1.4321608040201004e-06,
+ 1.457286432160804e-06,
+ 1.4824120603015076e-06,
+ 1.507537688442211e-06,
+ 1.5326633165829146e-06,
+ 1.5577889447236182e-06,
+ 1.5829145728643216e-06,
+ 1.6080402010050252e-06,
+ 1.6331658291457288e-06,
+ 1.6582914572864321e-06,
+ 1.6834170854271357e-06,
+ 1.7085427135678393e-06,
+ 1.7336683417085427e-06,
+ 1.7587939698492463e-06,
+ 1.7839195979899499e-06,
+ 1.8090452261306533e-06,
+ 1.8341708542713568e-06,
+ 1.8592964824120604e-06,
+ 1.8844221105527638e-06,
+ 1.9095477386934674e-06,
+ 1.9346733668341708e-06,
+ 1.9597989949748746e-06,
+ 1.984924623115578e-06,
+ 2.0100502512562813e-06,
+ 2.035175879396985e-06,
+ 2.0603015075376885e-06,
+ 2.085427135678392e-06,
+ 2.1105527638190957e-06,
+ 2.135678391959799e-06,
+ 2.1608040201005025e-06,
+ 2.1859296482412063e-06,
+ 2.2110552763819096e-06,
+ 2.236180904522613e-06,
+ 2.261306532663317e-06,
+ 2.28643216080402e-06,
+ 2.3115577889447236e-06,
+ 2.3366834170854274e-06,
+ 2.3618090452261308e-06,
+ 2.386934673366834e-06,
+ 2.412060301507538e-06,
+ 2.4371859296482413e-06,
+ 2.4623115577889447e-06,
+ 2.487437185929648e-06,
+ 2.512562814070352e-06,
+ 2.5376884422110553e-06,
+ 2.5628140703517587e-06,
+ 2.5879396984924625e-06,
+ 2.613065326633166e-06,
+ 2.6381909547738692e-06,
+ 2.663316582914573e-06,
+ 2.6884422110552764e-06,
+ 2.7135678391959798e-06,
+ 2.7386934673366836e-06,
+ 2.763819095477387e-06,
+ 2.7889447236180903e-06,
+ 2.814070351758794e-06,
+ 2.8391959798994975e-06,
+ 2.864321608040201e-06,
+ 2.8894472361809047e-06,
+ 2.914572864321608e-06,
+ 2.9396984924623115e-06,
+ 2.9648241206030153e-06,
+ 2.9899497487437186e-06,
+ 3.015075376884422e-06,
+ 3.040201005025126e-06,
+ 3.065326633165829e-06,
+ 3.0904522613065326e-06,
+ 3.1155778894472364e-06,
+ 3.1407035175879398e-06,
+ 3.165829145728643e-06,
+ 3.190954773869347e-06,
+ 3.2160804020100503e-06,
+ 3.2412060301507537e-06,
+ 3.2663316582914575e-06,
+ 3.291457286432161e-06,
+ 3.3165829145728643e-06,
+ 3.341708542713568e-06,
+ 3.3668341708542714e-06,
+ 3.391959798994975e-06,
+ 3.4170854271356786e-06,
+ 3.442211055276382e-06,
+ 3.4673366834170854e-06,
+ 3.492462311557789e-06,
+ 3.5175879396984926e-06,
+ 3.542713567839196e-06,
+ 3.5678391959798997e-06,
+ 3.592964824120603e-06,
+ 3.6180904522613065e-06,
+ 3.6432160804020103e-06,
+ 3.6683417085427137e-06,
+ 3.693467336683417e-06,
+ 3.718592964824121e-06,
+ 3.7437185929648243e-06,
+ 3.7688442211055276e-06,
+ 3.7939698492462314e-06,
+ 3.819095477386935e-06,
+ 3.844221105527638e-06,
+ 3.8693467336683416e-06,
+ 3.894472361809045e-06,
+ 3.919597989949749e-06,
+ 3.9447236180904526e-06,
+ 3.969849246231156e-06,
+ 3.994974874371859e-06,
+ 4.020100502512563e-06,
+ 4.045226130653266e-06,
+ 4.07035175879397e-06,
+ 4.095477386934674e-06,
+ 4.120603015075377e-06,
+ 4.1457286432160804e-06,
+ 4.170854271356784e-06,
+ 4.195979899497487e-06,
+ 4.221105527638191e-06,
+ 4.246231155778895e-06,
+ 4.271356783919598e-06,
+ 4.2964824120603016e-06,
+ 4.321608040201005e-06,
+ 4.346733668341708e-06,
+ 4.3718592964824125e-06,
+ 4.396984924623116e-06,
+ 4.422110552763819e-06,
+ 4.447236180904523e-06,
+ 4.472361809045226e-06,
+ 4.4974874371859294e-06,
+ 4.522613065326634e-06,
+ 4.547738693467337e-06,
+ 4.57286432160804e-06,
+ 4.597989949748744e-06,
+ 4.623115577889447e-06,
+ 4.6482412060301506e-06,
+ 4.673366834170855e-06,
+ 4.698492462311558e-06,
+ 4.7236180904522615e-06,
+ 4.748743718592965e-06,
+ 4.773869346733668e-06,
+ 4.798994974874372e-06,
+ 4.824120603015076e-06,
+ 4.849246231155779e-06,
+ 4.874371859296483e-06,
+ 4.899497487437186e-06,
+ 4.9246231155778894e-06,
+ 4.949748743718593e-06,
+ 4.974874371859296e-06,
+ 5e-06,
+ 5e-06,
+ 4.974874371859296e-06,
+ 4.949748743718593e-06,
+ 4.9246231155778894e-06,
+ 4.899497487437186e-06,
+ 4.874371859296483e-06,
+ 4.849246231155779e-06,
+ 4.824120603015076e-06,
+ 4.798994974874372e-06,
+ 4.773869346733668e-06,
+ 4.748743718592965e-06,
+ 4.7236180904522615e-06,
+ 4.698492462311558e-06,
+ 4.673366834170855e-06,
+ 4.6482412060301506e-06,
+ 4.623115577889447e-06,
+ 4.597989949748744e-06,
+ 4.57286432160804e-06,
+ 4.547738693467337e-06,
+ 4.522613065326634e-06,
+ 4.4974874371859294e-06,
+ 4.472361809045226e-06,
+ 4.447236180904523e-06,
+ 4.422110552763819e-06,
+ 4.396984924623116e-06,
+ 4.3718592964824125e-06,
+ 4.346733668341708e-06,
+ 4.321608040201005e-06,
+ 4.2964824120603016e-06,
+ 4.271356783919598e-06,
+ 4.246231155778895e-06,
+ 4.221105527638191e-06,
+ 4.195979899497487e-06,
+ 4.170854271356784e-06,
+ 4.1457286432160804e-06,
+ 4.120603015075377e-06,
+ 4.095477386934674e-06,
+ 4.07035175879397e-06,
+ 4.045226130653266e-06,
+ 4.020100502512563e-06,
+ 3.994974874371859e-06,
+ 3.969849246231156e-06,
+ 3.9447236180904526e-06,
+ 3.919597989949749e-06,
+ 3.894472361809045e-06,
+ 3.8693467336683416e-06,
+ 3.844221105527638e-06,
+ 3.819095477386935e-06,
+ 3.7939698492462314e-06,
+ 3.7688442211055276e-06,
+ 3.7437185929648243e-06,
+ 3.718592964824121e-06,
+ 3.693467336683417e-06,
+ 3.6683417085427137e-06,
+ 3.6432160804020103e-06,
+ 3.6180904522613065e-06,
+ 3.592964824120603e-06,
+ 3.5678391959798997e-06,
+ 3.542713567839196e-06,
+ 3.5175879396984926e-06,
+ 3.492462311557789e-06,
+ 3.4673366834170854e-06,
+ 3.442211055276382e-06,
+ 3.4170854271356786e-06,
+ 3.391959798994975e-06,
+ 3.3668341708542714e-06,
+ 3.341708542713568e-06,
+ 3.3165829145728643e-06,
+ 3.291457286432161e-06,
+ 3.2663316582914575e-06,
+ 3.2412060301507537e-06,
+ 3.2160804020100503e-06,
+ 3.190954773869347e-06,
+ 3.165829145728643e-06,
+ 3.1407035175879398e-06,
+ 3.1155778894472364e-06,
+ 3.0904522613065326e-06,
+ 3.065326633165829e-06,
+ 3.040201005025126e-06,
+ 3.015075376884422e-06,
+ 2.9899497487437186e-06,
+ 2.9648241206030153e-06,
+ 2.9396984924623115e-06,
+ 2.914572864321608e-06,
+ 2.8894472361809047e-06,
+ 2.864321608040201e-06,
+ 2.8391959798994975e-06,
+ 2.814070351758794e-06,
+ 2.7889447236180903e-06,
+ 2.763819095477387e-06,
+ 2.7386934673366836e-06,
+ 2.7135678391959798e-06,
+ 2.6884422110552764e-06,
+ 2.663316582914573e-06,
+ 2.6381909547738692e-06,
+ 2.613065326633166e-06,
+ 2.5879396984924625e-06,
+ 2.5628140703517587e-06,
+ 2.5376884422110553e-06,
+ 2.512562814070352e-06,
+ 2.487437185929648e-06,
+ 2.4623115577889447e-06,
+ 2.4371859296482413e-06,
+ 2.412060301507538e-06,
+ 2.386934673366834e-06,
+ 2.3618090452261308e-06,
+ 2.3366834170854274e-06,
+ 2.3115577889447236e-06,
+ 2.28643216080402e-06,
+ 2.261306532663317e-06,
+ 2.236180904522613e-06,
+ 2.2110552763819096e-06,
+ 2.1859296482412063e-06,
+ 2.1608040201005025e-06,
+ 2.135678391959799e-06,
+ 2.1105527638190957e-06,
+ 2.085427135678392e-06,
+ 2.0603015075376885e-06,
+ 2.035175879396985e-06,
+ 2.0100502512562813e-06,
+ 1.984924623115578e-06,
+ 1.9597989949748746e-06,
+ 1.9346733668341708e-06,
+ 1.9095477386934674e-06,
+ 1.8844221105527638e-06,
+ 1.8592964824120604e-06,
+ 1.8341708542713568e-06,
+ 1.8090452261306533e-06,
+ 1.7839195979899499e-06,
+ 1.7587939698492463e-06,
+ 1.7336683417085427e-06,
+ 1.7085427135678393e-06,
+ 1.6834170854271357e-06,
+ 1.6582914572864321e-06,
+ 1.6331658291457288e-06,
+ 1.6080402010050252e-06,
+ 1.5829145728643216e-06,
+ 1.5577889447236182e-06,
+ 1.5326633165829146e-06,
+ 1.507537688442211e-06,
+ 1.4824120603015076e-06,
+ 1.457286432160804e-06,
+ 1.4321608040201004e-06,
+ 1.407035175879397e-06,
+ 1.3819095477386935e-06,
+ 1.3567839195979899e-06,
+ 1.3316582914572865e-06,
+ 1.306532663316583e-06,
+ 1.2814070351758793e-06,
+ 1.256281407035176e-06,
+ 1.2311557788944724e-06,
+ 1.206030150753769e-06,
+ 1.1809045226130654e-06,
+ 1.1557788944723618e-06,
+ 1.1306532663316584e-06,
+ 1.1055276381909548e-06,
+ 1.0804020100502512e-06,
+ 1.0552763819095479e-06,
+ 1.0301507537688443e-06,
+ 1.0050251256281407e-06,
+ 9.798994974874373e-07,
+ 9.547738693467337e-07,
+ 9.296482412060302e-07,
+ 9.045226130653266e-07,
+ 8.793969849246231e-07,
+ 8.542713567839197e-07,
+ 8.291457286432161e-07,
+ 8.040201005025126e-07,
+ 7.788944723618091e-07,
+ 7.537688442211055e-07,
+ 7.28643216080402e-07,
+ 7.035175879396985e-07,
+ 6.783919597989949e-07,
+ 6.532663316582915e-07,
+ 6.28140703517588e-07,
+ 6.030150753768845e-07,
+ 5.778894472361809e-07,
+ 5.527638190954774e-07,
+ 5.276381909547739e-07,
+ 5.025125628140703e-07,
+ 4.773869346733669e-07,
+ 4.522613065326633e-07,
+ 4.2713567839195983e-07,
+ 4.020100502512563e-07,
+ 3.7688442211055275e-07,
+ 3.5175879396984927e-07,
+ 3.2663316582914573e-07,
+ 3.0150753768844224e-07,
+ 2.763819095477387e-07,
+ 2.5125628140703517e-07,
+ 2.2613065326633166e-07,
+ 2.0100502512562815e-07,
+ 1.7587939698492463e-07,
+ 1.5075376884422112e-07,
+ 1.2562814070351758e-07,
+ 1.0050251256281407e-07,
+ 7.537688442211056e-08,
+ 5.0251256281407036e-08,
+ 2.5125628140703518e-08,
+ 0.0
+ ],
+ "segment": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ "tip_position": [
+ -7.042211055276383e-07,
+ -6.79070351758794e-07,
+ -6.539195979899498e-07,
+ -6.287688442211055e-07,
+ -6.036180904522614e-07,
+ -5.784673366834171e-07,
+ -5.533165829145729e-07,
+ -5.281658291457287e-07,
+ -5.030150753768845e-07,
+ -4.778643216080402e-07,
+ -4.5271356783919604e-07,
+ -4.275628140703518e-07,
+ -4.0241206030150754e-07,
+ -3.7726130653266334e-07,
+ -3.5211055276381915e-07,
+ -3.2695979899497495e-07,
+ -3.018090452261307e-07,
+ -2.7665829145728644e-07,
+ -2.5150753768844225e-07,
+ -2.2635678391959805e-07,
+ -2.012060301507538e-07,
+ -1.7605527638190955e-07,
+ -1.509045226130654e-07,
+ -1.2575376884422115e-07,
+ -1.006030150753769e-07,
+ -7.545226130653265e-08,
+ -5.03015075376885e-08,
+ -2.515075376884425e-08,
+ 0.0,
+ 2.5150753768844145e-08,
+ 5.0301507537688396e-08,
+ 7.545226130653265e-08,
+ 1.006030150753769e-07,
+ 1.2575376884422104e-07,
+ 1.509045226130653e-07,
+ 1.7605527638190955e-07,
+ 2.012060301507537e-07,
+ 2.2635678391959805e-07,
+ 2.515075376884422e-07,
+ 2.7665829145728655e-07,
+ 2.9980904522613055e-07,
+ 3.2695979899497484e-07,
+ 3.521105527638192e-07,
+ 3.7726130653266334e-07,
+ 4.024120603015075e-07,
+ 4.2756281407035185e-07,
+ 4.52713567839196e-07,
+ 4.778643216080401e-07,
+ 5.030150753768845e-07,
+ 5.281658291457286e-07,
+ 5.533165829145728e-07,
+ 5.784673366834169e-07,
+ 6.036180904522613e-07,
+ 6.287688442211056e-07,
+ 6.539195979899496e-07,
+ 6.790703517587939e-07,
+ 7.042211055276383e-07,
+ 7.293718592964822e-07,
+ 7.545226130653266e-07,
+ 7.796733668341709e-07,
+ 8.048241206030151e-07,
+ 8.301740083621068e-07,
+ 8.556888641201788e-07,
+ 8.813111124754158e-07,
+ 9.070202076003725e-07,
+ 9.32804275141358e-07,
+ 9.586553032153856e-07,
+ 9.845674100953497e-07,
+ 1.01053603858957e-06,
+ 1.0365575222592973e-06,
+ 1.0626288279788578e-06,
+ 1.0887473920755335e-06,
+ 1.1149110103560906e-06,
+ 1.1411177611608193e-06,
+ 1.16736594968478e-06,
+ 1.193654066554068e-06,
+ 1.2199807562803669e-06,
+ 1.2463447927558711e-06,
+ 1.2727450598880405e-06,
+ 1.2991805360656051e-06,
+ 1.325650281532873e-06,
+ 1.352153428007453e-06,
+ 1.3786891700534096e-06,
+ 1.4052567578456553e-06,
+ 1.4318554910497194e-06,
+ 1.4584847136050661e-06,
+ 1.4851438092473548e-06,
+ 1.5118321976402484e-06,
+ 1.538549331014054e-06,
+ 1.5652946912288755e-06,
+ 1.5920677871957232e-06,
+ 1.6188681526013705e-06,
+ 1.6456953438924398e-06,
+ 1.672548938481938e-06,
+ 1.6994285331476313e-06,
+ 1.7263337425966675e-06,
+ 1.7532641981748811e-06,
+ 1.7802195467025574e-06,
+ 1.8071994494211637e-06,
+ 1.8342035810377954e-06,
+ 1.8612316288559887e-06,
+ 1.8882832919831146e-06,
+ 1.915358280605889e-06,
+ 1.942456315326653e-06,
+ 1.969577126554017e-06,
+ 1.9967204539422897e-06,
+ 2.0238860458747588e-06,
+ 2.0510736589865288e-06,
+ 2.078283057723097e-06,
+ 2.1055140139312965e-06,
+ 2.1327663064796202e-06,
+ 2.1600397209052696e-06,
+ 2.18733404908555e-06,
+ 2.214649088931497e-06,
+ 2.2419846441018473e-06,
+ 2.2693405237356292e-06,
+ 2.296716542201859e-06,
+ 2.3241125188649593e-06,
+ 2.3515282778646443e-06,
+ 2.378963647909149e-06,
+ 2.4064184620807856e-06,
+ 2.4338925576528876e-06,
+ 2.4613857759173045e-06,
+ 2.4888979620216786e-06,
+ 2.516428964815798e-06,
+ 2.5439786367063862e-06,
+ 2.5715468335197477e-06,
+ 2.5991334143717214e-06,
+ 2.6267382415444553e-06,
+ 2.654361180369553e-06,
+ 2.6820020991171646e-06,
+ 2.709660868890646e-06,
+ 2.7373373635264315e-06,
+ 2.7650314594987936e-06,
+ 2.7927430358291765e-06,
+ 2.820471973999846e-06,
+ 2.848218157871574e-06,
+ 2.875981473605128e-06,
+ 2.9037618095863474e-06,
+ 2.9315590563545864e-06,
+ 2.959373106534339e-06,
+ 2.9872038547698705e-06,
+ 3.015051197662676e-06,
+ 3.042915033711618e-06,
+ 3.0707952632556034e-06,
+ 3.0986917884186447e-06,
+ 3.126604513057201e-06,
+ 3.1545333427096633e-06,
+ 3.1824781845478775e-06,
+ 3.210438947330603e-06,
+ 3.23841554135881e-06,
+ 3.2664078784327137e-06,
+ 3.2944158718104693e-06,
+ 3.3224394361684423e-06,
+ 3.3504784875629817e-06,
+ 3.3785329433936138e-06,
+ 3.4066027223676035e-06,
+ 3.4346877444657973e-06,
+ 3.462787930909726e-06,
+ 3.4909032041298583e-06,
+ 3.5190334877350003e-06,
+ 3.5471787064827577e-06,
+ 3.575338786251029e-06,
+ 3.603513654010475e-06,
+ 3.6317032377979335e-06,
+ 3.6599074666907237e-06,
+ 3.688126270781812e-06,
+ 3.7163595811558006e-06,
+ 3.744607329865704e-06,
+ 3.772869449910475e-06,
+ 3.80114587521326e-06,
+ 3.829436540600347e-06,
+ 3.857741381780773e-06,
+ 3.886060335326582e-06,
+ 3.9143933386536785e-06,
+ 3.942740330003286e-06,
+ 3.971101248423967e-06,
+ 3.999476033754181e-06,
+ 4.0278646266053704e-06,
+ 4.056266968345549e-06,
+ 4.0846830010833714e-06,
+ 4.113112667652673e-06,
+ 4.141555911597451e-06,
+ 4.1700126771572856e-06,
+ 4.198482909253172e-06,
+ 4.226966553473749e-06,
+ 4.255463556061927e-06,
+ 4.283973863901869e-06,
+ 4.312497424506353e-06,
+ 4.341034186004464e-06,
+ 4.369584097129627e-06,
+ 4.398147107207964e-06,
+ 4.426723166146958e-06,
+ 4.455312224424423e-06,
+ 4.483914233077761e-06,
+ 4.512529143693505e-06,
+ 4.5411569083971295e-06,
+ 4.569797479843123e-06,
+ 4.598450811205317e-06,
+ 4.6271168561674655e-06,
+ 4.6271168561674655e-06,
+ 4.598450811205317e-06,
+ 4.569797479843123e-06,
+ 4.5411569083971295e-06,
+ 4.512529143693505e-06,
+ 4.483914233077761e-06,
+ 4.455312224424423e-06,
+ 4.426723166146958e-06,
+ 4.398147107207964e-06,
+ 4.369584097129627e-06,
+ 4.341034186004464e-06,
+ 4.312497424506353e-06,
+ 4.283973863901869e-06,
+ 4.255463556061927e-06,
+ 4.226966553473749e-06,
+ 4.198482909253172e-06,
+ 4.1700126771572856e-06,
+ 4.141555911597451e-06,
+ 4.113112667652673e-06,
+ 4.0846830010833714e-06,
+ 4.056266968345549e-06,
+ 4.0278646266053704e-06,
+ 3.999476033754181e-06,
+ 3.971101248423967e-06,
+ 3.942740330003286e-06,
+ 3.9143933386536785e-06,
+ 3.886060335326582e-06,
+ 3.857741381780773e-06,
+ 3.829436540600347e-06,
+ 3.80114587521326e-06,
+ 3.772869449910475e-06,
+ 3.744607329865704e-06,
+ 3.7163595811558006e-06,
+ 3.688126270781812e-06,
+ 3.6599074666907237e-06,
+ 3.6317032377979335e-06,
+ 3.603513654010475e-06,
+ 3.575338786251029e-06,
+ 3.5471787064827577e-06,
+ 3.5190334877350003e-06,
+ 3.4909032041298583e-06,
+ 3.462787930909726e-06,
+ 3.4346877444657973e-06,
+ 3.4066027223676035e-06,
+ 3.3785329433936138e-06,
+ 3.3504784875629817e-06,
+ 3.3224394361684423e-06,
+ 3.2944158718104693e-06,
+ 3.2664078784327137e-06,
+ 3.23841554135881e-06,
+ 3.210438947330603e-06,
+ 3.1824781845478775e-06,
+ 3.1545333427096633e-06,
+ 3.126604513057201e-06,
+ 3.0986917884186447e-06,
+ 3.0707952632556034e-06,
+ 3.042915033711618e-06,
+ 3.015051197662676e-06,
+ 2.9872038547698705e-06,
+ 2.959373106534339e-06,
+ 2.9315590563545864e-06,
+ 2.9037618095863474e-06,
+ 2.875981473605128e-06,
+ 2.848218157871574e-06,
+ 2.820471973999846e-06,
+ 2.7927430358291765e-06,
+ 2.7650314594987936e-06,
+ 2.7373373635264315e-06,
+ 2.709660868890646e-06,
+ 2.6820020991171646e-06,
+ 2.654361180369553e-06,
+ 2.6267382415444553e-06,
+ 2.5991334143717214e-06,
+ 2.5715468335197477e-06,
+ 2.5439786367063862e-06,
+ 2.516428964815798e-06,
+ 2.4888979620216786e-06,
+ 2.4613857759173045e-06,
+ 2.4338925576528876e-06,
+ 2.4064184620807856e-06,
+ 2.378963647909149e-06,
+ 2.3515282778646443e-06,
+ 2.3241125188649593e-06,
+ 2.296716542201859e-06,
+ 2.2693405237356292e-06,
+ 2.2419846441018473e-06,
+ 2.214649088931497e-06,
+ 2.18733404908555e-06,
+ 2.1600397209052696e-06,
+ 2.1327663064796202e-06,
+ 2.1055140139312965e-06,
+ 2.078283057723097e-06,
+ 2.0510736589865288e-06,
+ 2.0238860458747588e-06,
+ 1.9967204539422897e-06,
+ 1.969577126554017e-06,
+ 1.942456315326653e-06,
+ 1.915358280605889e-06,
+ 1.8882832919831146e-06,
+ 1.8612316288559887e-06,
+ 1.8342035810377954e-06,
+ 1.8071994494211637e-06,
+ 1.7802195467025574e-06,
+ 1.7532641981748811e-06,
+ 1.7263337425966675e-06,
+ 1.6994285331476313e-06,
+ 1.672548938481938e-06,
+ 1.6456953438924398e-06,
+ 1.6188681526013705e-06,
+ 1.5920677871957232e-06,
+ 1.5652946912288755e-06,
+ 1.538549331014054e-06,
+ 1.5118321976402484e-06,
+ 1.4851438092473548e-06,
+ 1.4584847136050661e-06,
+ 1.4318554910497194e-06,
+ 1.4052567578456553e-06,
+ 1.3786891700534096e-06,
+ 1.352153428007453e-06,
+ 1.325650281532873e-06,
+ 1.2991805360656051e-06,
+ 1.2727450598880405e-06,
+ 1.2463447927558711e-06,
+ 1.2199807562803669e-06,
+ 1.193654066554068e-06,
+ 1.16736594968478e-06,
+ 1.1411177611608193e-06,
+ 1.1149110103560906e-06,
+ 1.0887473920755335e-06,
+ 1.0626288279788578e-06,
+ 1.0365575222592973e-06,
+ 1.01053603858957e-06,
+ 9.845674100953497e-07,
+ 9.586553032153856e-07,
+ 9.32804275141358e-07,
+ 9.070202076003725e-07,
+ 8.813111124754158e-07,
+ 8.556888641201788e-07,
+ 8.301740083621068e-07,
+ 8.048241206030151e-07,
+ 7.796733668341709e-07,
+ 7.545226130653266e-07,
+ 7.293718592964822e-07,
+ 7.042211055276383e-07,
+ 6.790703517587939e-07,
+ 6.539195979899496e-07,
+ 6.287688442211056e-07,
+ 6.036180904522613e-07,
+ 5.784673366834169e-07,
+ 5.533165829145728e-07,
+ 5.281658291457286e-07,
+ 5.030150753768845e-07,
+ 4.778643216080401e-07,
+ 4.52713567839196e-07,
+ 4.2756281407035185e-07,
+ 4.024120603015075e-07,
+ 3.7726130653266334e-07,
+ 3.521105527638192e-07,
+ 3.2695979899497484e-07,
+ 2.9980904522613055e-07,
+ 2.616582914572865e-07,
+ 2.515075376884422e-07,
+ 2.2635678391959805e-07,
+ 2.012060301507537e-07,
+ 1.7605527638190955e-07,
+ 1.509045226130653e-07,
+ 1.2575376884422104e-07,
+ 1.006030150753769e-07,
+ 7.545226130653265e-08,
+ 5.0301507537688396e-08,
+ 2.5150753768844145e-08,
+ 0.0,
+ -2.515075376884425e-08,
+ -5.03015075376885e-08,
+ -7.545226130653265e-08,
+ -1.006030150753769e-07,
+ -1.2575376884422115e-07,
+ -1.509045226130654e-07,
+ -1.7605527638190955e-07,
+ -2.012060301507538e-07,
+ -2.2635678391959805e-07,
+ -2.5150753768844225e-07,
+ -2.7665829145728644e-07,
+ -3.018090452261307e-07,
+ -3.2695979899497495e-07,
+ -3.5211055276381915e-07,
+ -3.7726130653266334e-07,
+ -4.0241206030150754e-07,
+ -4.275628140703518e-07,
+ -4.5271356783919604e-07,
+ -4.778643216080402e-07,
+ -5.030150753768845e-07,
+ -5.281658291457287e-07,
+ -5.533165829145729e-07,
+ -5.784673366834171e-07,
+ -6.036180904522614e-07,
+ -6.287688442211055e-07,
+ -6.539195979899498e-07,
+ -6.79070351758794e-07,
+ -7.042211055276383e-07
+ ]
+ },
+ "P15": {
+ "contact": {
+ "deviation_from_baseline": 28,
+ "fit_constant_line": 83,
+ "fit_constant_polynomial": 51,
+ "fit_line_polynomial": 51
+ },
+ "force": [
+ 3.3919597989949844e-11,
+ 3.3919597989949844e-11,
+ 3.3919597989949844e-11,
+ 3.391959798994987e-11,
+ 3.3919597989949877e-11,
+ 3.391959798994988e-11,
+ 3.3919597989949786e-11,
+ 3.39195979899498e-11,
+ 3.3919597989949805e-11,
+ 3.391959798994981e-11,
+ 3.3919597989949825e-11,
+ 3.391959798994983e-11,
+ 3.391959798994984e-11,
+ 3.391959798994985e-11,
+ 3.391959798994986e-11,
+ 3.391959798994987e-11,
+ 3.3919597989949877e-11,
+ 3.391959798994988e-11,
+ 3.391959798994979e-11,
+ 3.3919597989949805e-11,
+ 3.3919597989949805e-11,
+ 3.391959798994981e-11,
+ 3.391959798994983e-11,
+ 3.391959798994984e-11,
+ 3.3919597989949844e-11,
+ 3.3919597989949844e-11,
+ 3.3919597989949864e-11,
+ 3.391959798994987e-11,
+ 3.643216080402023e-11,
+ 3.894472361809049e-11,
+ 4.145728643216085e-11,
+ 4.396984924623121e-11,
+ 4.648241206030157e-11,
+ 4.899497487437193e-11,
+ 5.150753768844229e-11,
+ 5.402010050251265e-11,
+ 5.6532663316583013e-11,
+ 5.904522613065337e-11,
+ 6.155778894472373e-11,
+ 6.40703517587941e-11,
+ 6.658291457286446e-11,
+ 6.909547738693482e-11,
+ 7.160804020100518e-11,
+ 7.412060301507544e-11,
+ 7.66331658291458e-11,
+ 7.914572864321616e-11,
+ 8.165829145728652e-11,
+ 8.417085427135688e-11,
+ 8.668341708542724e-11,
+ 8.91959798994976e-11,
+ 9.170854271356796e-11,
+ 9.422110552763822e-11,
+ 9.673366834170858e-11,
+ 9.924623115577894e-11,
+ 1.017587939698493e-10,
+ 1.0427135678391966e-10,
+ 1.0678391959799002e-10,
+ 1.0929648241206038e-10,
+ 1.1180904522613074e-10,
+ 1.143216080402011e-10,
+ 1.1683417085427146e-10,
+ 1.392601326930982e-10,
+ 1.7818289442992757e-10,
+ 2.2784491588329405e-10,
+ 2.8619161430860365e-10,
+ 3.5203555433678944e-10,
+ 4.245755476692286e-10,
+ 5.032234215952738e-10,
+ 5.875234569469557e-10,
+ 6.771090098493254e-10,
+ 7.716767677350325e-10,
+ 8.709703633322505e-10,
+ 9.747693773176074e-10,
+ 1.0828816437201223e-09,
+ 1.1951376820458483e-09,
+ 1.3113865549042909e-09,
+ 1.4314927134638023e-09,
+ 1.555333546943882e-09,
+ 1.6827974460904473e-09,
+ 1.813782249776579e-09,
+ 1.948193982433005e-09,
+ 2.0859458158206693e-09,
+ 2.2269572063459504e-09,
+ 2.371153171500206e-09,
+ 2.518463677836241e-09,
+ 2.6688231193005718e-09,
+ 2.822169869459094e-09,
+ 2.978445894678082e-09,
+ 3.1375964179883197e-09,
+ 3.299569625400097e-09,
+ 3.4643164080145087e-09,
+ 3.6317901345088774e-09,
+ 3.801946449545491e-09,
+ 3.974743094424921e-09,
+ 4.150139746923922e-09,
+ 4.3280978777572115e-09,
+ 4.508580621508173e-09,
+ 4.691552660205477e-09,
+ 4.876980117995778e-09,
+ 5.064830465588577e-09,
+ 5.255072433337545e-09,
+ 5.4476759319798e-09,
+ 5.642611980186922e-09,
+ 5.839852638192903e-09,
+ 6.0393709468590044e-09,
+ 6.241140871615914e-09,
+ 6.445137250792455e-09,
+ 6.65133574789912e-09,
+ 6.859712807485581e-09,
+ 7.070245614235139e-09,
+ 7.282912054997178e-09,
+ 7.497690683491785e-09,
+ 7.714560687449419e-09,
+ 7.933501857973829e-09,
+ 8.154494560938513e-09,
+ 8.3775197102463e-09,
+ 8.602558742798938e-09,
+ 8.82959359503864e-09,
+ 9.058606680936748e-09,
+ 9.28958087131688e-09,
+ 9.522499474410204e-09,
+ 9.757346217550045e-09,
+ 9.9941052299214e-09,
+ 1.0232761026288466e-08,
+ 1.0473298491630014e-08,
+ 1.0715702866618505e-08,
+ 1.0959959733884316e-08,
+ 1.1206055005011306e-08,
+ 1.1453974908214373e-08,
+ 1.1703705976653802e-08,
+ 1.1955235037344581e-08,
+ 1.2208549200622345e-08,
+ 1.24636358501306e-08,
+ 1.2720482633296406e-08,
+ 1.2979077452264365e-08,
+ 1.3239408455260979e-08,
+ 1.3501464028363385e-08,
+ 1.3765232787648454e-08,
+ 1.4030703571700053e-08,
+ 1.4297865434453563e-08,
+ 1.4566707638358508e-08,
+ 1.4837219647841329e-08,
+ 1.510939112305149e-08,
+ 1.538321191387538e-08,
+ 1.5658672054203537e-08,
+ 1.5935761756437322e-08,
+ 1.621447140622263e-08,
+ 1.649479155739848e-08,
+ 1.6776712927149512e-08,
+ 1.7060226391351772e-08,
+ 1.734532298010215e-08,
+ 1.7631993873422133e-08,
+ 1.7920230397127276e-08,
+ 1.821002401885425e-08,
+ 1.850136634423786e-08,
+ 1.8794249113230785e-08,
+ 1.9088664196559265e-08,
+ 1.9384603592308372e-08,
+ 1.9682059422630854e-08,
+ 1.998102393057376e-08,
+ 2.028148947701763e-08,
+ 2.0583448537723035e-08,
+ 2.0886893700479763e-08,
+ 2.1191817662354026e-08,
+ 2.149821322702953e-08,
+ 2.1806073302238184e-08,
+ 2.2115390897276654e-08,
+ 2.24261591206052e-08,
+ 2.273837117752515e-08,
+ 2.3052020367931855e-08,
+ 2.336710008414004e-08,
+ 2.3683603808778393e-08,
+ 2.400152511275077e-08,
+ 2.4320857653261244e-08,
+ 2.464159517190049e-08,
+ 2.4963731492790932e-08,
+ 2.5287260520788715e-08,
+ 2.5612176239739766e-08,
+ 2.5938472710788365e-08,
+ 2.626614407073583e-08,
+ 2.6595184530447713e-08,
+ 2.6925588373307454e-08,
+ 2.725734995371493e-08,
+ 2.7590463695628124e-08,
+ 2.7924924091146382e-08,
+ 2.8260725699133787e-08,
+ 2.85978631438811e-08,
+ 2.893633111380495e-08,
+ 2.9276124360183016e-08,
+ 2.9617237695923797e-08,
+ 2.995966599436981e-08,
+ 3.0303404188133144e-08,
+ 3.064844726796217e-08,
+ 3.099479028163824e-08,
+ 3.134242833290169e-08,
+ 3.16913565804058e-08,
+ 3.2041570236697896e-08,
+ 3.239306456722688e-08,
+ 3.2745834889376026e-08,
+ 3.309987657152037e-08,
+ 3.309987657152037e-08,
+ 3.2745834889376026e-08,
+ 3.239306456722688e-08,
+ 3.2041570236697896e-08,
+ 3.16913565804058e-08,
+ 3.134242833290169e-08,
+ 3.099479028163824e-08,
+ 3.064844726796217e-08,
+ 3.0303404188133144e-08,
+ 2.995966599436981e-08,
+ 2.9617237695923797e-08,
+ 2.9276124360183016e-08,
+ 2.893633111380495e-08,
+ 2.85978631438811e-08,
+ 2.8260725699133787e-08,
+ 2.7924924091146382e-08,
+ 2.7590463695628124e-08,
+ 2.725734995371493e-08,
+ 2.6925588373307454e-08,
+ 2.6595184530447713e-08,
+ 2.626614407073583e-08,
+ 2.5938472710788365e-08,
+ 2.5612176239739766e-08,
+ 2.5287260520788715e-08,
+ 2.4963731492790932e-08,
+ 2.464159517190049e-08,
+ 2.4320857653261244e-08,
+ 2.400152511275077e-08,
+ 2.3683603808778393e-08,
+ 2.336710008414004e-08,
+ 2.3052020367931855e-08,
+ 2.273837117752515e-08,
+ 2.24261591206052e-08,
+ 2.2115390897276654e-08,
+ 2.1806073302238184e-08,
+ 2.149821322702953e-08,
+ 2.1191817662354026e-08,
+ 2.0886893700479763e-08,
+ 2.0583448537723035e-08,
+ 2.028148947701763e-08,
+ 1.998102393057376e-08,
+ 1.9682059422630854e-08,
+ 1.9384603592308372e-08,
+ 1.9088664196559265e-08,
+ 1.8794249113230785e-08,
+ 1.850136634423786e-08,
+ 1.821002401885425e-08,
+ 1.7920230397127276e-08,
+ 1.7631993873422133e-08,
+ 1.734532298010215e-08,
+ 1.7060226391351772e-08,
+ 1.6776712927149512e-08,
+ 1.649479155739848e-08,
+ 1.621447140622263e-08,
+ 1.5935761756437322e-08,
+ 1.5658672054203537e-08,
+ 1.538321191387538e-08,
+ 1.510939112305149e-08,
+ 1.4837219647841329e-08,
+ 1.4566707638358508e-08,
+ 1.4297865434453563e-08,
+ 1.4030703571700053e-08,
+ 1.3765232787648454e-08,
+ 1.3501464028363385e-08,
+ 1.3239408455260979e-08,
+ 1.2979077452264365e-08,
+ 1.2720482633296406e-08,
+ 1.24636358501306e-08,
+ 1.2208549200622345e-08,
+ 1.1955235037344581e-08,
+ 1.1703705976653802e-08,
+ 1.1453974908214373e-08,
+ 1.1206055005011306e-08,
+ 1.0959959733884316e-08,
+ 1.0715702866618505e-08,
+ 1.0473298491630014e-08,
+ 1.0232761026288466e-08,
+ 9.9941052299214e-09,
+ 9.757346217550045e-09,
+ 9.522499474410204e-09,
+ 9.28958087131688e-09,
+ 9.058606680936748e-09,
+ 8.82959359503864e-09,
+ 8.602558742798938e-09,
+ 8.3775197102463e-09,
+ 8.154494560938513e-09,
+ 7.933501857973829e-09,
+ 7.714560687449419e-09,
+ 7.497690683491785e-09,
+ 7.282912054997178e-09,
+ 7.070245614235139e-09,
+ 6.859712807485581e-09,
+ 6.65133574789912e-09,
+ 6.445137250792455e-09,
+ 6.241140871615914e-09,
+ 6.0393709468590044e-09,
+ 5.839852638192903e-09,
+ 5.642611980186922e-09,
+ 5.4476759319798e-09,
+ 5.255072433337545e-09,
+ 5.064830465588577e-09,
+ 4.876980117995778e-09,
+ 4.691552660205477e-09,
+ 4.508580621508173e-09,
+ 4.3280978777572115e-09,
+ 4.150139746923922e-09,
+ 3.974743094424921e-09,
+ 3.801946449545491e-09,
+ 3.6317901345088774e-09,
+ 3.4643164080145087e-09,
+ 3.299569625400097e-09,
+ 3.1375964179883197e-09,
+ 2.978445894678082e-09,
+ 2.822169869459094e-09,
+ 2.6688231193005718e-09,
+ 2.518463677836241e-09,
+ 2.371153171500206e-09,
+ 2.2269572063459504e-09,
+ 2.0859458158206693e-09,
+ 1.948193982433005e-09,
+ 1.813782249776579e-09,
+ 1.6827974460904473e-09,
+ 1.555333546943882e-09,
+ 1.4314927134638023e-09,
+ 1.3113865549042909e-09,
+ 1.1951376820458483e-09,
+ 1.0828816437201223e-09,
+ 9.747693773176074e-10,
+ 8.709703633322505e-10,
+ 7.716767677350325e-10,
+ 6.771090098493254e-10,
+ 5.875234569469557e-10,
+ 5.032234215952738e-10,
+ 4.245755476692286e-10,
+ 3.5203555433678944e-10,
+ 2.8619161430860365e-10,
+ 2.2784491588329405e-10,
+ 1.7818289442992757e-10,
+ 1.392601326930982e-10,
+ 1.1683417085427146e-10,
+ -1.885678391959799e-09,
+ 1.1180904522613074e-10,
+ 1.0929648241206038e-10,
+ 1.0678391959799002e-10,
+ 1.0427135678391966e-10,
+ 1.017587939698493e-10,
+ 9.924623115577894e-11,
+ 9.673366834170858e-11,
+ 9.422110552763822e-11,
+ 9.170854271356796e-11,
+ 8.91959798994976e-11,
+ 8.668341708542724e-11,
+ 8.417085427135688e-11,
+ 8.165829145728652e-11,
+ 7.914572864321616e-11,
+ 7.66331658291458e-11,
+ 7.412060301507544e-11,
+ 7.160804020100518e-11,
+ 6.909547738693482e-11,
+ 6.658291457286446e-11,
+ 6.40703517587941e-11,
+ 6.155778894472373e-11,
+ 5.904522613065337e-11,
+ 5.6532663316583013e-11,
+ 5.402010050251265e-11,
+ 5.150753768844229e-11,
+ 4.899497487437193e-11,
+ 4.648241206030157e-11,
+ 4.396984924623121e-11,
+ 4.145728643216085e-11,
+ 3.894472361809049e-11,
+ 3.643216080402023e-11,
+ 3.391959798994987e-11,
+ 3.140703517587951e-11,
+ 2.889447236180915e-11,
+ 2.6381909547738788e-11,
+ 2.3869346733668427e-11,
+ 2.1356783919598066e-11,
+ 1.8844221105527706e-11,
+ 1.6331658291457345e-11,
+ 1.3819095477386984e-11,
+ 1.1306532663316623e-11,
+ 8.793969849246366e-12,
+ 6.281407035176005e-12,
+ 3.7688442211056445e-12,
+ 1.2562814070352838e-12,
+ -1.256281407035077e-12,
+ -3.768844221105438e-12,
+ -6.2814070351757985e-12,
+ -8.79396984924616e-12,
+ -1.130653266331652e-11,
+ -1.381909547738688e-11,
+ -1.633165829145724e-11,
+ -1.8844221105527602e-11,
+ -2.135678391959786e-11,
+ -2.386934673366822e-11,
+ -2.638190954773858e-11,
+ -2.8894472361808942e-11,
+ -3.14070351758793e-11,
+ -3.3919597989949663e-11
+ ],
+ "height": [
+ 0.0,
+ 2.5125628140703518e-08,
+ 5.0251256281407036e-08,
+ 7.537688442211056e-08,
+ 1.0050251256281407e-07,
+ 1.2562814070351758e-07,
+ 1.5075376884422112e-07,
+ 1.7587939698492463e-07,
+ 2.0100502512562815e-07,
+ 2.2613065326633166e-07,
+ 2.5125628140703517e-07,
+ 2.763819095477387e-07,
+ 3.0150753768844224e-07,
+ 3.2663316582914573e-07,
+ 3.5175879396984927e-07,
+ 3.7688442211055275e-07,
+ 4.020100502512563e-07,
+ 4.2713567839195983e-07,
+ 4.522613065326633e-07,
+ 4.773869346733669e-07,
+ 5.025125628140703e-07,
+ 5.276381909547739e-07,
+ 5.527638190954774e-07,
+ 5.778894472361809e-07,
+ 6.030150753768845e-07,
+ 6.28140703517588e-07,
+ 6.532663316582915e-07,
+ 6.783919597989949e-07,
+ 7.035175879396985e-07,
+ 7.28643216080402e-07,
+ 7.537688442211055e-07,
+ 7.788944723618091e-07,
+ 8.040201005025126e-07,
+ 8.291457286432161e-07,
+ 8.542713567839197e-07,
+ 8.793969849246231e-07,
+ 9.045226130653266e-07,
+ 9.296482412060302e-07,
+ 9.547738693467337e-07,
+ 9.798994974874373e-07,
+ 1.0050251256281407e-06,
+ 1.0301507537688443e-06,
+ 1.0552763819095479e-06,
+ 1.0804020100502512e-06,
+ 1.1055276381909548e-06,
+ 1.1306532663316584e-06,
+ 1.1557788944723618e-06,
+ 1.1809045226130654e-06,
+ 1.206030150753769e-06,
+ 1.2311557788944724e-06,
+ 1.256281407035176e-06,
+ 1.2814070351758793e-06,
+ 1.306532663316583e-06,
+ 1.3316582914572865e-06,
+ 1.3567839195979899e-06,
+ 1.3819095477386935e-06,
+ 1.407035175879397e-06,
+ 1.4321608040201004e-06,
+ 1.457286432160804e-06,
+ 1.4824120603015076e-06,
+ 1.507537688442211e-06,
+ 1.5326633165829146e-06,
+ 1.5577889447236182e-06,
+ 1.5829145728643216e-06,
+ 1.6080402010050252e-06,
+ 1.6331658291457288e-06,
+ 1.6582914572864321e-06,
+ 1.6834170854271357e-06,
+ 1.7085427135678393e-06,
+ 1.7336683417085427e-06,
+ 1.7587939698492463e-06,
+ 1.7839195979899499e-06,
+ 1.8090452261306533e-06,
+ 1.8341708542713568e-06,
+ 1.8592964824120604e-06,
+ 1.8844221105527638e-06,
+ 1.9095477386934674e-06,
+ 1.9346733668341708e-06,
+ 1.9597989949748746e-06,
+ 1.984924623115578e-06,
+ 2.0100502512562813e-06,
+ 2.035175879396985e-06,
+ 2.0603015075376885e-06,
+ 2.085427135678392e-06,
+ 2.1105527638190957e-06,
+ 2.135678391959799e-06,
+ 2.1608040201005025e-06,
+ 2.1859296482412063e-06,
+ 2.2110552763819096e-06,
+ 2.236180904522613e-06,
+ 2.261306532663317e-06,
+ 2.28643216080402e-06,
+ 2.3115577889447236e-06,
+ 2.3366834170854274e-06,
+ 2.3618090452261308e-06,
+ 2.386934673366834e-06,
+ 2.412060301507538e-06,
+ 2.4371859296482413e-06,
+ 2.4623115577889447e-06,
+ 2.487437185929648e-06,
+ 2.512562814070352e-06,
+ 2.5376884422110553e-06,
+ 2.5628140703517587e-06,
+ 2.5879396984924625e-06,
+ 2.613065326633166e-06,
+ 2.6381909547738692e-06,
+ 2.663316582914573e-06,
+ 2.6884422110552764e-06,
+ 2.7135678391959798e-06,
+ 2.7386934673366836e-06,
+ 2.763819095477387e-06,
+ 2.7889447236180903e-06,
+ 2.814070351758794e-06,
+ 2.8391959798994975e-06,
+ 2.864321608040201e-06,
+ 2.8894472361809047e-06,
+ 2.914572864321608e-06,
+ 2.9396984924623115e-06,
+ 2.9648241206030153e-06,
+ 2.9899497487437186e-06,
+ 3.015075376884422e-06,
+ 3.040201005025126e-06,
+ 3.065326633165829e-06,
+ 3.0904522613065326e-06,
+ 3.1155778894472364e-06,
+ 3.1407035175879398e-06,
+ 3.165829145728643e-06,
+ 3.190954773869347e-06,
+ 3.2160804020100503e-06,
+ 3.2412060301507537e-06,
+ 3.2663316582914575e-06,
+ 3.291457286432161e-06,
+ 3.3165829145728643e-06,
+ 3.341708542713568e-06,
+ 3.3668341708542714e-06,
+ 3.391959798994975e-06,
+ 3.4170854271356786e-06,
+ 3.442211055276382e-06,
+ 3.4673366834170854e-06,
+ 3.492462311557789e-06,
+ 3.5175879396984926e-06,
+ 3.542713567839196e-06,
+ 3.5678391959798997e-06,
+ 3.592964824120603e-06,
+ 3.6180904522613065e-06,
+ 3.6432160804020103e-06,
+ 3.6683417085427137e-06,
+ 3.693467336683417e-06,
+ 3.718592964824121e-06,
+ 3.7437185929648243e-06,
+ 3.7688442211055276e-06,
+ 3.7939698492462314e-06,
+ 3.819095477386935e-06,
+ 3.844221105527638e-06,
+ 3.8693467336683416e-06,
+ 3.894472361809045e-06,
+ 3.919597989949749e-06,
+ 3.9447236180904526e-06,
+ 3.969849246231156e-06,
+ 3.994974874371859e-06,
+ 4.020100502512563e-06,
+ 4.045226130653266e-06,
+ 4.07035175879397e-06,
+ 4.095477386934674e-06,
+ 4.120603015075377e-06,
+ 4.1457286432160804e-06,
+ 4.170854271356784e-06,
+ 4.195979899497487e-06,
+ 4.221105527638191e-06,
+ 4.246231155778895e-06,
+ 4.271356783919598e-06,
+ 4.2964824120603016e-06,
+ 4.321608040201005e-06,
+ 4.346733668341708e-06,
+ 4.3718592964824125e-06,
+ 4.396984924623116e-06,
+ 4.422110552763819e-06,
+ 4.447236180904523e-06,
+ 4.472361809045226e-06,
+ 4.4974874371859294e-06,
+ 4.522613065326634e-06,
+ 4.547738693467337e-06,
+ 4.57286432160804e-06,
+ 4.597989949748744e-06,
+ 4.623115577889447e-06,
+ 4.6482412060301506e-06,
+ 4.673366834170855e-06,
+ 4.698492462311558e-06,
+ 4.7236180904522615e-06,
+ 4.748743718592965e-06,
+ 4.773869346733668e-06,
+ 4.798994974874372e-06,
+ 4.824120603015076e-06,
+ 4.849246231155779e-06,
+ 4.874371859296483e-06,
+ 4.899497487437186e-06,
+ 4.9246231155778894e-06,
+ 4.949748743718593e-06,
+ 4.974874371859296e-06,
+ 5e-06,
+ 5e-06,
+ 4.974874371859296e-06,
+ 4.949748743718593e-06,
+ 4.9246231155778894e-06,
+ 4.899497487437186e-06,
+ 4.874371859296483e-06,
+ 4.849246231155779e-06,
+ 4.824120603015076e-06,
+ 4.798994974874372e-06,
+ 4.773869346733668e-06,
+ 4.748743718592965e-06,
+ 4.7236180904522615e-06,
+ 4.698492462311558e-06,
+ 4.673366834170855e-06,
+ 4.6482412060301506e-06,
+ 4.623115577889447e-06,
+ 4.597989949748744e-06,
+ 4.57286432160804e-06,
+ 4.547738693467337e-06,
+ 4.522613065326634e-06,
+ 4.4974874371859294e-06,
+ 4.472361809045226e-06,
+ 4.447236180904523e-06,
+ 4.422110552763819e-06,
+ 4.396984924623116e-06,
+ 4.3718592964824125e-06,
+ 4.346733668341708e-06,
+ 4.321608040201005e-06,
+ 4.2964824120603016e-06,
+ 4.271356783919598e-06,
+ 4.246231155778895e-06,
+ 4.221105527638191e-06,
+ 4.195979899497487e-06,
+ 4.170854271356784e-06,
+ 4.1457286432160804e-06,
+ 4.120603015075377e-06,
+ 4.095477386934674e-06,
+ 4.07035175879397e-06,
+ 4.045226130653266e-06,
+ 4.020100502512563e-06,
+ 3.994974874371859e-06,
+ 3.969849246231156e-06,
+ 3.9447236180904526e-06,
+ 3.919597989949749e-06,
+ 3.894472361809045e-06,
+ 3.8693467336683416e-06,
+ 3.844221105527638e-06,
+ 3.819095477386935e-06,
+ 3.7939698492462314e-06,
+ 3.7688442211055276e-06,
+ 3.7437185929648243e-06,
+ 3.718592964824121e-06,
+ 3.693467336683417e-06,
+ 3.6683417085427137e-06,
+ 3.6432160804020103e-06,
+ 3.6180904522613065e-06,
+ 3.592964824120603e-06,
+ 3.5678391959798997e-06,
+ 3.542713567839196e-06,
+ 3.5175879396984926e-06,
+ 3.492462311557789e-06,
+ 3.4673366834170854e-06,
+ 3.442211055276382e-06,
+ 3.4170854271356786e-06,
+ 3.391959798994975e-06,
+ 3.3668341708542714e-06,
+ 3.341708542713568e-06,
+ 3.3165829145728643e-06,
+ 3.291457286432161e-06,
+ 3.2663316582914575e-06,
+ 3.2412060301507537e-06,
+ 3.2160804020100503e-06,
+ 3.190954773869347e-06,
+ 3.165829145728643e-06,
+ 3.1407035175879398e-06,
+ 3.1155778894472364e-06,
+ 3.0904522613065326e-06,
+ 3.065326633165829e-06,
+ 3.040201005025126e-06,
+ 3.015075376884422e-06,
+ 2.9899497487437186e-06,
+ 2.9648241206030153e-06,
+ 2.9396984924623115e-06,
+ 2.914572864321608e-06,
+ 2.8894472361809047e-06,
+ 2.864321608040201e-06,
+ 2.8391959798994975e-06,
+ 2.814070351758794e-06,
+ 2.7889447236180903e-06,
+ 2.763819095477387e-06,
+ 2.7386934673366836e-06,
+ 2.7135678391959798e-06,
+ 2.6884422110552764e-06,
+ 2.663316582914573e-06,
+ 2.6381909547738692e-06,
+ 2.613065326633166e-06,
+ 2.5879396984924625e-06,
+ 2.5628140703517587e-06,
+ 2.5376884422110553e-06,
+ 2.512562814070352e-06,
+ 2.487437185929648e-06,
+ 2.4623115577889447e-06,
+ 2.4371859296482413e-06,
+ 2.412060301507538e-06,
+ 2.386934673366834e-06,
+ 2.3618090452261308e-06,
+ 2.3366834170854274e-06,
+ 2.3115577889447236e-06,
+ 2.28643216080402e-06,
+ 2.261306532663317e-06,
+ 2.236180904522613e-06,
+ 2.2110552763819096e-06,
+ 2.1859296482412063e-06,
+ 2.1608040201005025e-06,
+ 2.135678391959799e-06,
+ 2.1105527638190957e-06,
+ 2.085427135678392e-06,
+ 2.0603015075376885e-06,
+ 2.035175879396985e-06,
+ 2.0100502512562813e-06,
+ 1.984924623115578e-06,
+ 1.9597989949748746e-06,
+ 1.9346733668341708e-06,
+ 1.9095477386934674e-06,
+ 1.8844221105527638e-06,
+ 1.8592964824120604e-06,
+ 1.8341708542713568e-06,
+ 1.8090452261306533e-06,
+ 1.7839195979899499e-06,
+ 1.7587939698492463e-06,
+ 1.7336683417085427e-06,
+ 1.7085427135678393e-06,
+ 1.6834170854271357e-06,
+ 1.6582914572864321e-06,
+ 1.6331658291457288e-06,
+ 1.6080402010050252e-06,
+ 1.5829145728643216e-06,
+ 1.5577889447236182e-06,
+ 1.5326633165829146e-06,
+ 1.507537688442211e-06,
+ 1.4824120603015076e-06,
+ 1.457286432160804e-06,
+ 1.4321608040201004e-06,
+ 1.407035175879397e-06,
+ 1.3819095477386935e-06,
+ 1.3567839195979899e-06,
+ 1.3316582914572865e-06,
+ 1.306532663316583e-06,
+ 1.2814070351758793e-06,
+ 1.256281407035176e-06,
+ 1.2311557788944724e-06,
+ 1.206030150753769e-06,
+ 1.1809045226130654e-06,
+ 1.1557788944723618e-06,
+ 1.1306532663316584e-06,
+ 1.1055276381909548e-06,
+ 1.0804020100502512e-06,
+ 1.0552763819095479e-06,
+ 1.0301507537688443e-06,
+ 1.0050251256281407e-06,
+ 9.798994974874373e-07,
+ 9.547738693467337e-07,
+ 9.296482412060302e-07,
+ 9.045226130653266e-07,
+ 8.793969849246231e-07,
+ 8.542713567839197e-07,
+ 8.291457286432161e-07,
+ 8.040201005025126e-07,
+ 7.788944723618091e-07,
+ 7.537688442211055e-07,
+ 7.28643216080402e-07,
+ 7.035175879396985e-07,
+ 6.783919597989949e-07,
+ 6.532663316582915e-07,
+ 6.28140703517588e-07,
+ 6.030150753768845e-07,
+ 5.778894472361809e-07,
+ 5.527638190954774e-07,
+ 5.276381909547739e-07,
+ 5.025125628140703e-07,
+ 4.773869346733669e-07,
+ 4.522613065326633e-07,
+ 4.2713567839195983e-07,
+ 4.020100502512563e-07,
+ 3.7688442211055275e-07,
+ 3.5175879396984927e-07,
+ 3.2663316582914573e-07,
+ 3.0150753768844224e-07,
+ 2.763819095477387e-07,
+ 2.5125628140703517e-07,
+ 2.2613065326633166e-07,
+ 2.0100502512562815e-07,
+ 1.7587939698492463e-07,
+ 1.5075376884422112e-07,
+ 1.2562814070351758e-07,
+ 1.0050251256281407e-07,
+ 7.537688442211056e-08,
+ 5.0251256281407036e-08,
+ 2.5125628140703518e-08,
+ 0.0
+ ],
+ "segment": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ "tip_position": [
+ -7.042211055276383e-07,
+ -6.79070351758794e-07,
+ -6.539195979899498e-07,
+ -6.287688442211055e-07,
+ -6.036180904522614e-07,
+ -5.784673366834171e-07,
+ -5.533165829145729e-07,
+ -5.281658291457287e-07,
+ -5.030150753768845e-07,
+ -4.778643216080402e-07,
+ -4.5271356783919604e-07,
+ -4.275628140703518e-07,
+ -4.0241206030150754e-07,
+ -3.7726130653266334e-07,
+ -3.5211055276381915e-07,
+ -3.2695979899497495e-07,
+ -3.018090452261307e-07,
+ -2.7665829145728644e-07,
+ -2.5150753768844225e-07,
+ -2.2635678391959805e-07,
+ -2.012060301507538e-07,
+ -1.7605527638190955e-07,
+ -1.509045226130654e-07,
+ -1.2575376884422115e-07,
+ -1.006030150753769e-07,
+ -7.545226130653265e-08,
+ -5.03015075376885e-08,
+ -2.515075376884425e-08,
+ 0.0,
+ 2.5150753768844145e-08,
+ 5.0301507537688396e-08,
+ 7.545226130653265e-08,
+ 1.006030150753769e-07,
+ 1.2575376884422104e-07,
+ 1.509045226130653e-07,
+ 1.7605527638190955e-07,
+ 2.012060301507537e-07,
+ 2.2635678391959805e-07,
+ 2.515075376884422e-07,
+ 2.7665829145728655e-07,
+ 3.018090452261305e-07,
+ 3.2695979899497484e-07,
+ 3.521105527638192e-07,
+ 3.7726130653266334e-07,
+ 4.024120603015075e-07,
+ 4.2756281407035185e-07,
+ 4.52713567839196e-07,
+ 4.778643216080401e-07,
+ 5.030150753768845e-07,
+ 5.281658291457286e-07,
+ 5.533165829145728e-07,
+ 5.784673366834169e-07,
+ 6.036180904522613e-07,
+ 6.287688442211056e-07,
+ 6.539195979899496e-07,
+ 6.790703517587939e-07,
+ 7.042211055276383e-07,
+ 7.293718592964822e-07,
+ 7.545226130653266e-07,
+ 7.796733668341709e-07,
+ 8.048241206030151e-07,
+ 8.301740083621068e-07,
+ 8.556888641201788e-07,
+ 8.813111124754158e-07,
+ 9.070202076003725e-07,
+ 9.32804275141358e-07,
+ 9.586553032153856e-07,
+ 9.845674100953497e-07,
+ 1.01053603858957e-06,
+ 1.0365575222592973e-06,
+ 1.0626288279788578e-06,
+ 1.0887473920755335e-06,
+ 1.1149110103560906e-06,
+ 1.1411177611608193e-06,
+ 1.16736594968478e-06,
+ 1.193654066554068e-06,
+ 1.2199807562803669e-06,
+ 1.2463447927558711e-06,
+ 1.2727450598880405e-06,
+ 1.2991805360656051e-06,
+ 1.325650281532873e-06,
+ 1.352153428007453e-06,
+ 1.3786891700534096e-06,
+ 1.4052567578456553e-06,
+ 1.4318554910497194e-06,
+ 1.4584847136050661e-06,
+ 1.4851438092473548e-06,
+ 1.5118321976402484e-06,
+ 1.538549331014054e-06,
+ 1.5652946912288755e-06,
+ 1.5920677871957232e-06,
+ 1.6188681526013705e-06,
+ 1.6456953438924398e-06,
+ 1.672548938481938e-06,
+ 1.6994285331476313e-06,
+ 1.7263337425966675e-06,
+ 1.7532641981748811e-06,
+ 1.7802195467025574e-06,
+ 1.8071994494211637e-06,
+ 1.8342035810377954e-06,
+ 1.8612316288559887e-06,
+ 1.8882832919831146e-06,
+ 1.915358280605889e-06,
+ 1.942456315326653e-06,
+ 1.969577126554017e-06,
+ 1.9967204539422897e-06,
+ 2.0238860458747588e-06,
+ 2.0510736589865288e-06,
+ 2.078283057723097e-06,
+ 2.1055140139312965e-06,
+ 2.1327663064796202e-06,
+ 2.1600397209052696e-06,
+ 2.18733404908555e-06,
+ 2.214649088931497e-06,
+ 2.2419846441018473e-06,
+ 2.2693405237356292e-06,
+ 2.296716542201859e-06,
+ 2.3241125188649593e-06,
+ 2.3515282778646443e-06,
+ 2.378963647909149e-06,
+ 2.4064184620807856e-06,
+ 2.4338925576528876e-06,
+ 2.4613857759173045e-06,
+ 2.4888979620216786e-06,
+ 2.516428964815798e-06,
+ 2.5439786367063862e-06,
+ 2.5715468335197477e-06,
+ 2.5991334143717214e-06,
+ 2.6267382415444553e-06,
+ 2.654361180369553e-06,
+ 2.6820020991171646e-06,
+ 2.709660868890646e-06,
+ 2.7373373635264315e-06,
+ 2.7650314594987936e-06,
+ 2.7927430358291765e-06,
+ 2.820471973999846e-06,
+ 2.848218157871574e-06,
+ 2.875981473605128e-06,
+ 2.9037618095863474e-06,
+ 2.9315590563545864e-06,
+ 2.959373106534339e-06,
+ 2.9872038547698705e-06,
+ 3.015051197662676e-06,
+ 3.042915033711618e-06,
+ 3.0707952632556034e-06,
+ 3.0986917884186447e-06,
+ 3.126604513057201e-06,
+ 3.1545333427096633e-06,
+ 3.1824781845478775e-06,
+ 3.210438947330603e-06,
+ 3.23841554135881e-06,
+ 3.2664078784327137e-06,
+ 3.2944158718104693e-06,
+ 3.3224394361684423e-06,
+ 3.3504784875629817e-06,
+ 3.3785329433936138e-06,
+ 3.4066027223676035e-06,
+ 3.4346877444657973e-06,
+ 3.462787930909726e-06,
+ 3.4909032041298583e-06,
+ 3.5190334877350003e-06,
+ 3.5471787064827577e-06,
+ 3.575338786251029e-06,
+ 3.603513654010475e-06,
+ 3.6317032377979335e-06,
+ 3.6599074666907237e-06,
+ 3.688126270781812e-06,
+ 3.7163595811558006e-06,
+ 3.744607329865704e-06,
+ 3.772869449910475e-06,
+ 3.80114587521326e-06,
+ 3.829436540600347e-06,
+ 3.857741381780773e-06,
+ 3.886060335326582e-06,
+ 3.9143933386536785e-06,
+ 3.942740330003286e-06,
+ 3.971101248423967e-06,
+ 3.999476033754181e-06,
+ 4.0278646266053704e-06,
+ 4.056266968345549e-06,
+ 4.0846830010833714e-06,
+ 4.113112667652673e-06,
+ 4.141555911597451e-06,
+ 4.1700126771572856e-06,
+ 4.198482909253172e-06,
+ 4.226966553473749e-06,
+ 4.255463556061927e-06,
+ 4.283973863901869e-06,
+ 4.312497424506353e-06,
+ 4.341034186004464e-06,
+ 4.369584097129627e-06,
+ 4.398147107207964e-06,
+ 4.426723166146958e-06,
+ 4.455312224424423e-06,
+ 4.483914233077761e-06,
+ 4.512529143693505e-06,
+ 4.5411569083971295e-06,
+ 4.569797479843123e-06,
+ 4.598450811205317e-06,
+ 4.6271168561674655e-06,
+ 4.6271168561674655e-06,
+ 4.598450811205317e-06,
+ 4.569797479843123e-06,
+ 4.5411569083971295e-06,
+ 4.512529143693505e-06,
+ 4.483914233077761e-06,
+ 4.455312224424423e-06,
+ 4.426723166146958e-06,
+ 4.398147107207964e-06,
+ 4.369584097129627e-06,
+ 4.341034186004464e-06,
+ 4.312497424506353e-06,
+ 4.283973863901869e-06,
+ 4.255463556061927e-06,
+ 4.226966553473749e-06,
+ 4.198482909253172e-06,
+ 4.1700126771572856e-06,
+ 4.141555911597451e-06,
+ 4.113112667652673e-06,
+ 4.0846830010833714e-06,
+ 4.056266968345549e-06,
+ 4.0278646266053704e-06,
+ 3.999476033754181e-06,
+ 3.971101248423967e-06,
+ 3.942740330003286e-06,
+ 3.9143933386536785e-06,
+ 3.886060335326582e-06,
+ 3.857741381780773e-06,
+ 3.829436540600347e-06,
+ 3.80114587521326e-06,
+ 3.772869449910475e-06,
+ 3.744607329865704e-06,
+ 3.7163595811558006e-06,
+ 3.688126270781812e-06,
+ 3.6599074666907237e-06,
+ 3.6317032377979335e-06,
+ 3.603513654010475e-06,
+ 3.575338786251029e-06,
+ 3.5471787064827577e-06,
+ 3.5190334877350003e-06,
+ 3.4909032041298583e-06,
+ 3.462787930909726e-06,
+ 3.4346877444657973e-06,
+ 3.4066027223676035e-06,
+ 3.3785329433936138e-06,
+ 3.3504784875629817e-06,
+ 3.3224394361684423e-06,
+ 3.2944158718104693e-06,
+ 3.2664078784327137e-06,
+ 3.23841554135881e-06,
+ 3.210438947330603e-06,
+ 3.1824781845478775e-06,
+ 3.1545333427096633e-06,
+ 3.126604513057201e-06,
+ 3.0986917884186447e-06,
+ 3.0707952632556034e-06,
+ 3.042915033711618e-06,
+ 3.015051197662676e-06,
+ 2.9872038547698705e-06,
+ 2.959373106534339e-06,
+ 2.9315590563545864e-06,
+ 2.9037618095863474e-06,
+ 2.875981473605128e-06,
+ 2.848218157871574e-06,
+ 2.820471973999846e-06,
+ 2.7927430358291765e-06,
+ 2.7650314594987936e-06,
+ 2.7373373635264315e-06,
+ 2.709660868890646e-06,
+ 2.6820020991171646e-06,
+ 2.654361180369553e-06,
+ 2.6267382415444553e-06,
+ 2.5991334143717214e-06,
+ 2.5715468335197477e-06,
+ 2.5439786367063862e-06,
+ 2.516428964815798e-06,
+ 2.4888979620216786e-06,
+ 2.4613857759173045e-06,
+ 2.4338925576528876e-06,
+ 2.4064184620807856e-06,
+ 2.378963647909149e-06,
+ 2.3515282778646443e-06,
+ 2.3241125188649593e-06,
+ 2.296716542201859e-06,
+ 2.2693405237356292e-06,
+ 2.2419846441018473e-06,
+ 2.214649088931497e-06,
+ 2.18733404908555e-06,
+ 2.1600397209052696e-06,
+ 2.1327663064796202e-06,
+ 2.1055140139312965e-06,
+ 2.078283057723097e-06,
+ 2.0510736589865288e-06,
+ 2.0238860458747588e-06,
+ 1.9967204539422897e-06,
+ 1.969577126554017e-06,
+ 1.942456315326653e-06,
+ 1.915358280605889e-06,
+ 1.8882832919831146e-06,
+ 1.8612316288559887e-06,
+ 1.8342035810377954e-06,
+ 1.8071994494211637e-06,
+ 1.7802195467025574e-06,
+ 1.7532641981748811e-06,
+ 1.7263337425966675e-06,
+ 1.6994285331476313e-06,
+ 1.672548938481938e-06,
+ 1.6456953438924398e-06,
+ 1.6188681526013705e-06,
+ 1.5920677871957232e-06,
+ 1.5652946912288755e-06,
+ 1.538549331014054e-06,
+ 1.5118321976402484e-06,
+ 1.4851438092473548e-06,
+ 1.4584847136050661e-06,
+ 1.4318554910497194e-06,
+ 1.4052567578456553e-06,
+ 1.3786891700534096e-06,
+ 1.352153428007453e-06,
+ 1.325650281532873e-06,
+ 1.2991805360656051e-06,
+ 1.2727450598880405e-06,
+ 1.2463447927558711e-06,
+ 1.2199807562803669e-06,
+ 1.193654066554068e-06,
+ 1.16736594968478e-06,
+ 1.1411177611608193e-06,
+ 1.1149110103560906e-06,
+ 1.0887473920755335e-06,
+ 1.0626288279788578e-06,
+ 1.0365575222592973e-06,
+ 1.01053603858957e-06,
+ 9.845674100953497e-07,
+ 9.586553032153856e-07,
+ 9.32804275141358e-07,
+ 9.070202076003725e-07,
+ 8.813111124754158e-07,
+ 8.556888641201788e-07,
+ 8.301740083621068e-07,
+ 8.048241206030151e-07,
+ 7.596733668341709e-07,
+ 7.545226130653266e-07,
+ 7.293718592964822e-07,
+ 7.042211055276383e-07,
+ 6.790703517587939e-07,
+ 6.539195979899496e-07,
+ 6.287688442211056e-07,
+ 6.036180904522613e-07,
+ 5.784673366834169e-07,
+ 5.533165829145728e-07,
+ 5.281658291457286e-07,
+ 5.030150753768845e-07,
+ 4.778643216080401e-07,
+ 4.52713567839196e-07,
+ 4.2756281407035185e-07,
+ 4.024120603015075e-07,
+ 3.7726130653266334e-07,
+ 3.521105527638192e-07,
+ 3.2695979899497484e-07,
+ 3.018090452261305e-07,
+ 2.7665829145728655e-07,
+ 2.515075376884422e-07,
+ 2.2635678391959805e-07,
+ 2.012060301507537e-07,
+ 1.7605527638190955e-07,
+ 1.509045226130653e-07,
+ 1.2575376884422104e-07,
+ 1.006030150753769e-07,
+ 7.545226130653265e-08,
+ 5.0301507537688396e-08,
+ 2.5150753768844145e-08,
+ 0.0,
+ -2.515075376884425e-08,
+ -5.03015075376885e-08,
+ -7.545226130653265e-08,
+ -1.006030150753769e-07,
+ -1.2575376884422115e-07,
+ -1.509045226130654e-07,
+ -1.7605527638190955e-07,
+ -2.012060301507538e-07,
+ -2.2635678391959805e-07,
+ -2.5150753768844225e-07,
+ -2.7665829145728644e-07,
+ -3.018090452261307e-07,
+ -3.2695979899497495e-07,
+ -3.5211055276381915e-07,
+ -3.7726130653266334e-07,
+ -4.0241206030150754e-07,
+ -4.275628140703518e-07,
+ -4.5271356783919604e-07,
+ -4.778643216080402e-07,
+ -5.030150753768845e-07,
+ -5.281658291457287e-07,
+ -5.533165829145729e-07,
+ -5.784673366834171e-07,
+ -6.036180904522614e-07,
+ -6.287688442211055e-07,
+ -6.539195979899498e-07,
+ -6.79070351758794e-07,
+ -7.042211055276383e-07
+ ]
+ },
+ "P16": {
+ "contact": {
+ "deviation_from_baseline": 28,
+ "fit_constant_line": 83,
+ "fit_constant_polynomial": 51,
+ "fit_line_polynomial": 51
+ },
+ "force": [
+ 3.3919597989949844e-11,
+ 3.3919597989949844e-11,
+ 3.3919597989949844e-11,
+ 3.391959798994987e-11,
+ 3.3919597989949877e-11,
+ 3.391959798994988e-11,
+ 3.3919597989949786e-11,
+ 3.39195979899498e-11,
+ 3.3919597989949805e-11,
+ 3.391959798994981e-11,
+ 3.3919597989949825e-11,
+ 3.391959798994983e-11,
+ 3.391959798994984e-11,
+ 3.391959798994985e-11,
+ 3.391959798994986e-11,
+ 3.391959798994987e-11,
+ 3.3919597989949877e-11,
+ 3.391959798994988e-11,
+ 3.391959798994979e-11,
+ 3.3919597989949805e-11,
+ 3.3919597989949805e-11,
+ 3.391959798994981e-11,
+ 3.391959798994983e-11,
+ 3.391959798994984e-11,
+ 3.3919597989949844e-11,
+ 3.3919597989949844e-11,
+ 3.3919597989949864e-11,
+ 3.391959798994987e-11,
+ 3.643216080402023e-11,
+ 3.894472361809049e-11,
+ 4.145728643216085e-11,
+ 4.396984924623121e-11,
+ 4.648241206030157e-11,
+ 4.899497487437193e-11,
+ 5.150753768844229e-11,
+ 5.402010050251265e-11,
+ 5.6532663316583013e-11,
+ 5.904522613065337e-11,
+ 6.155778894472373e-11,
+ 6.40703517587941e-11,
+ 6.658291457286446e-11,
+ 6.909547738693482e-11,
+ 7.160804020100518e-11,
+ 7.412060301507544e-11,
+ 7.66331658291458e-11,
+ 7.914572864321616e-11,
+ 8.165829145728652e-11,
+ 8.417085427135688e-11,
+ 8.668341708542724e-11,
+ 8.91959798994976e-11,
+ 9.170854271356796e-11,
+ 9.422110552763822e-11,
+ 9.673366834170858e-11,
+ 9.924623115577894e-11,
+ 1.017587939698493e-10,
+ 1.0427135678391966e-10,
+ 1.0678391959799002e-10,
+ 1.0929648241206038e-10,
+ 1.1180904522613074e-10,
+ 1.143216080402011e-10,
+ 1.1683417085427146e-10,
+ 1.392601326930982e-10,
+ 1.7818289442992757e-10,
+ 2.2784491588329405e-10,
+ 2.8619161430860365e-10,
+ 3.5203555433678944e-10,
+ 4.245755476692286e-10,
+ 5.032234215952738e-10,
+ 5.875234569469557e-10,
+ 6.771090098493254e-10,
+ 7.716767677350325e-10,
+ 8.709703633322505e-10,
+ 9.747693773176074e-10,
+ 1.0828816437201223e-09,
+ 1.1951376820458483e-09,
+ 1.3113865549042909e-09,
+ 1.4314927134638023e-09,
+ 1.555333546943882e-09,
+ 1.6827974460904473e-09,
+ 1.813782249776579e-09,
+ 1.948193982433005e-09,
+ 2.0859458158206693e-09,
+ 2.2269572063459504e-09,
+ 2.371153171500206e-09,
+ 2.518463677836241e-09,
+ 2.6688231193005718e-09,
+ 2.822169869459094e-09,
+ 2.978445894678082e-09,
+ 3.1375964179883197e-09,
+ 3.299569625400097e-09,
+ 3.4643164080145087e-09,
+ 3.6317901345088774e-09,
+ 3.801946449545491e-09,
+ 3.974743094424921e-09,
+ 4.150139746923922e-09,
+ 4.3280978777572115e-09,
+ 4.508580621508173e-09,
+ 4.691552660205477e-09,
+ 4.876980117995778e-09,
+ 5.064830465588577e-09,
+ 5.255072433337545e-09,
+ 5.4476759319798e-09,
+ 5.642611980186922e-09,
+ 5.839852638192903e-09,
+ 6.0393709468590044e-09,
+ 6.241140871615914e-09,
+ 6.445137250792455e-09,
+ 6.65133574789912e-09,
+ 6.859712807485581e-09,
+ 7.070245614235139e-09,
+ 7.282912054997178e-09,
+ 7.497690683491785e-09,
+ 7.714560687449419e-09,
+ 7.933501857973829e-09,
+ 8.154494560938513e-09,
+ 8.3775197102463e-09,
+ 8.602558742798938e-09,
+ 8.82959359503864e-09,
+ 9.058606680936748e-09,
+ 9.28958087131688e-09,
+ 9.522499474410204e-09,
+ 9.757346217550045e-09,
+ 9.9941052299214e-09,
+ 1.0232761026288466e-08,
+ 1.0473298491630014e-08,
+ 1.0715702866618505e-08,
+ 1.0959959733884316e-08,
+ 1.1206055005011306e-08,
+ 1.1453974908214373e-08,
+ 1.1703705976653802e-08,
+ 1.1955235037344581e-08,
+ 1.2208549200622345e-08,
+ 1.24636358501306e-08,
+ 1.2720482633296406e-08,
+ 1.2979077452264365e-08,
+ 1.3239408455260979e-08,
+ 1.3501464028363385e-08,
+ 1.3765232787648454e-08,
+ 1.4030703571700053e-08,
+ 1.4297865434453563e-08,
+ 1.4566707638358508e-08,
+ 1.4837219647841329e-08,
+ 1.510939112305149e-08,
+ 1.538321191387538e-08,
+ 1.5658672054203537e-08,
+ 1.5935761756437322e-08,
+ 1.621447140622263e-08,
+ 1.649479155739848e-08,
+ 1.6776712927149512e-08,
+ 1.7060226391351772e-08,
+ 1.734532298010215e-08,
+ 1.7631993873422133e-08,
+ 1.7920230397127276e-08,
+ 1.821002401885425e-08,
+ 1.850136634423786e-08,
+ 1.8794249113230785e-08,
+ 1.9088664196559265e-08,
+ 1.9384603592308372e-08,
+ 1.9682059422630854e-08,
+ 1.998102393057376e-08,
+ 2.028148947701763e-08,
+ 2.0583448537723035e-08,
+ 2.0886893700479763e-08,
+ 2.1191817662354026e-08,
+ 2.149821322702953e-08,
+ 2.1806073302238184e-08,
+ 2.2115390897276654e-08,
+ 2.24261591206052e-08,
+ 2.273837117752515e-08,
+ 2.3052020367931855e-08,
+ 2.336710008414004e-08,
+ 2.3683603808778393e-08,
+ 2.400152511275077e-08,
+ 2.4320857653261244e-08,
+ 2.464159517190049e-08,
+ 2.4963731492790932e-08,
+ 2.5287260520788715e-08,
+ 2.5612176239739766e-08,
+ 2.5938472710788365e-08,
+ 2.626614407073583e-08,
+ 2.6595184530447713e-08,
+ 2.6925588373307454e-08,
+ 2.725734995371493e-08,
+ 2.7590463695628124e-08,
+ 2.7924924091146382e-08,
+ 2.8260725699133787e-08,
+ 2.85978631438811e-08,
+ 2.893633111380495e-08,
+ 2.9276124360183016e-08,
+ 2.9617237695923797e-08,
+ 2.995966599436981e-08,
+ 3.0303404188133144e-08,
+ 3.064844726796217e-08,
+ 3.099479028163824e-08,
+ 3.134242833290169e-08,
+ 3.16913565804058e-08,
+ 3.2041570236697896e-08,
+ 3.239306456722688e-08,
+ 3.2745834889376026e-08,
+ 3.309987657152037e-08,
+ 3.309987657152037e-08,
+ 3.2745834889376026e-08,
+ 3.239306456722688e-08,
+ 3.2041570236697896e-08,
+ 3.16913565804058e-08,
+ 3.134242833290169e-08,
+ 3.099479028163824e-08,
+ 3.064844726796217e-08,
+ 3.0303404188133144e-08,
+ 2.995966599436981e-08,
+ 2.9617237695923797e-08,
+ 2.9276124360183016e-08,
+ 2.893633111380495e-08,
+ 2.85978631438811e-08,
+ 2.8260725699133787e-08,
+ 2.7924924091146382e-08,
+ 2.7590463695628124e-08,
+ 2.725734995371493e-08,
+ 2.6925588373307454e-08,
+ 2.6595184530447713e-08,
+ 2.626614407073583e-08,
+ 2.5938472710788365e-08,
+ 2.5612176239739766e-08,
+ 2.5287260520788715e-08,
+ 2.4963731492790932e-08,
+ 2.464159517190049e-08,
+ 2.4320857653261244e-08,
+ 2.400152511275077e-08,
+ 2.3683603808778393e-08,
+ 2.336710008414004e-08,
+ 2.3052020367931855e-08,
+ 2.273837117752515e-08,
+ 2.24261591206052e-08,
+ 2.2115390897276654e-08,
+ 2.1806073302238184e-08,
+ 2.149821322702953e-08,
+ 2.1191817662354026e-08,
+ 2.0886893700479763e-08,
+ 2.0583448537723035e-08,
+ 2.028148947701763e-08,
+ 1.998102393057376e-08,
+ 1.9682059422630854e-08,
+ 1.9384603592308372e-08,
+ 1.9088664196559265e-08,
+ 1.8794249113230785e-08,
+ 1.850136634423786e-08,
+ 1.821002401885425e-08,
+ 1.7920230397127276e-08,
+ 1.7631993873422133e-08,
+ 1.734532298010215e-08,
+ 1.7060226391351772e-08,
+ 1.6776712927149512e-08,
+ 1.649479155739848e-08,
+ 1.621447140622263e-08,
+ 1.5935761756437322e-08,
+ 1.5658672054203537e-08,
+ 1.538321191387538e-08,
+ 1.510939112305149e-08,
+ 1.4837219647841329e-08,
+ 1.4566707638358508e-08,
+ 1.4297865434453563e-08,
+ 1.4030703571700053e-08,
+ 1.3765232787648454e-08,
+ 1.3501464028363385e-08,
+ 1.3239408455260979e-08,
+ 1.2979077452264365e-08,
+ 1.2720482633296406e-08,
+ 1.24636358501306e-08,
+ 1.2208549200622345e-08,
+ 1.1955235037344581e-08,
+ 1.1703705976653802e-08,
+ 1.1453974908214373e-08,
+ 1.1206055005011306e-08,
+ 1.0959959733884316e-08,
+ 1.0715702866618505e-08,
+ 1.0473298491630014e-08,
+ 1.0232761026288466e-08,
+ 9.9941052299214e-09,
+ 9.757346217550045e-09,
+ 9.522499474410204e-09,
+ 9.28958087131688e-09,
+ 9.058606680936748e-09,
+ 8.82959359503864e-09,
+ 8.602558742798938e-09,
+ 8.3775197102463e-09,
+ 8.154494560938513e-09,
+ 7.933501857973829e-09,
+ 7.714560687449419e-09,
+ 7.497690683491785e-09,
+ 7.282912054997178e-09,
+ 7.070245614235139e-09,
+ 6.859712807485581e-09,
+ 6.65133574789912e-09,
+ 6.445137250792455e-09,
+ 6.241140871615914e-09,
+ 6.0393709468590044e-09,
+ 5.839852638192903e-09,
+ 5.642611980186922e-09,
+ 5.4476759319798e-09,
+ 5.255072433337545e-09,
+ 5.064830465588577e-09,
+ 4.876980117995778e-09,
+ 4.691552660205477e-09,
+ 4.508580621508173e-09,
+ 4.3280978777572115e-09,
+ 4.150139746923922e-09,
+ 3.974743094424921e-09,
+ 3.801946449545491e-09,
+ 3.6317901345088774e-09,
+ 3.4643164080145087e-09,
+ 3.299569625400097e-09,
+ 3.1375964179883197e-09,
+ 2.978445894678082e-09,
+ 2.822169869459094e-09,
+ 2.6688231193005718e-09,
+ 2.518463677836241e-09,
+ 2.371153171500206e-09,
+ 2.2269572063459504e-09,
+ 2.0859458158206693e-09,
+ 1.948193982433005e-09,
+ 1.813782249776579e-09,
+ 1.6827974460904473e-09,
+ 1.555333546943882e-09,
+ 1.4314927134638023e-09,
+ 1.3113865549042909e-09,
+ 1.1951376820458483e-09,
+ 1.0828816437201223e-09,
+ 9.747693773176074e-10,
+ 8.709703633322505e-10,
+ 7.716767677350325e-10,
+ 6.771090098493254e-10,
+ 5.875234569469557e-10,
+ 5.032234215952738e-10,
+ 4.245755476692286e-10,
+ 3.5203555433678944e-10,
+ 2.8619161430860365e-10,
+ 2.2784491588329405e-10,
+ 1.7818289442992757e-10,
+ 1.392601326930982e-10,
+ 1.1683417085427146e-10,
+ 1.143216080402011e-10,
+ 1.1180904522613074e-10,
+ 1.0929648241206038e-10,
+ 1.0678391959799002e-10,
+ 1.0427135678391966e-10,
+ 1.017587939698493e-10,
+ 9.924623115577894e-11,
+ 9.673366834170858e-11,
+ 9.422110552763822e-11,
+ 9.170854271356796e-11,
+ -3.1080402010050236e-10,
+ 1.6866834170854274e-09,
+ 1.6841708542713568e-09,
+ 1.6816582914572867e-09,
+ 1.6791457286432161e-09,
+ 1.676633165829146e-09,
+ 1.6741206030150754e-09,
+ 1.6716080402010052e-09,
+ 1.6690954773869347e-09,
+ 1.6665829145728645e-09,
+ 1.6640703517587944e-09,
+ 1.6615577889447238e-09,
+ 1.6590452261306532e-09,
+ 1.656532663316583e-09,
+ 1.654020100502513e-09,
+ 1.6515075376884424e-09,
+ 1.6489949748743718e-09,
+ 1.6464824120603016e-09,
+ 1.6439698492462315e-09,
+ 1.641457286432161e-09,
+ 1.6389447236180903e-09,
+ 1.6364321608040202e-09,
+ 1.63391959798995e-09,
+ 1.6314070351758795e-09,
+ 1.6288944723618093e-09,
+ 1.6263819095477387e-09,
+ 1.6238693467336686e-09,
+ 1.621356783919598e-09,
+ 1.6188442211055279e-09,
+ 1.6163316582914573e-09,
+ 1.6138190954773871e-09,
+ 1.6113065326633166e-09,
+ 1.6087939698492464e-09,
+ 1.6062814070351759e-09,
+ 1.6037688442211057e-09,
+ 1.6012562814070355e-09,
+ 1.598743718592965e-09,
+ 1.5962311557788944e-09,
+ 1.5937185929648243e-09,
+ 1.5912060301507541e-09,
+ 1.5886934673366835e-09,
+ 1.586180904522613e-09,
+ 1.5836683417085428e-09,
+ 1.5811557788944727e-09,
+ 1.578643216080402e-09,
+ 1.576130653266332e-09,
+ 1.5736180904522614e-09,
+ 1.5711055276381912e-09,
+ 1.5685929648241207e-09,
+ 1.5660804020100505e-09
+ ],
+ "height": [
+ 0.0,
+ 2.5125628140703518e-08,
+ 5.0251256281407036e-08,
+ 7.537688442211056e-08,
+ 1.0050251256281407e-07,
+ 1.2562814070351758e-07,
+ 1.5075376884422112e-07,
+ 1.7587939698492463e-07,
+ 2.0100502512562815e-07,
+ 2.2613065326633166e-07,
+ 2.5125628140703517e-07,
+ 2.763819095477387e-07,
+ 3.0150753768844224e-07,
+ 3.2663316582914573e-07,
+ 3.5175879396984927e-07,
+ 3.7688442211055275e-07,
+ 4.020100502512563e-07,
+ 4.2713567839195983e-07,
+ 4.522613065326633e-07,
+ 4.773869346733669e-07,
+ 5.025125628140703e-07,
+ 5.276381909547739e-07,
+ 5.527638190954774e-07,
+ 5.778894472361809e-07,
+ 6.030150753768845e-07,
+ 6.28140703517588e-07,
+ 6.532663316582915e-07,
+ 6.783919597989949e-07,
+ 7.035175879396985e-07,
+ 7.28643216080402e-07,
+ 7.537688442211055e-07,
+ 7.788944723618091e-07,
+ 8.040201005025126e-07,
+ 8.291457286432161e-07,
+ 8.542713567839197e-07,
+ 8.793969849246231e-07,
+ 9.045226130653266e-07,
+ 9.296482412060302e-07,
+ 9.547738693467337e-07,
+ 9.798994974874373e-07,
+ 1.0050251256281407e-06,
+ 1.0301507537688443e-06,
+ 1.0552763819095479e-06,
+ 1.0804020100502512e-06,
+ 1.1055276381909548e-06,
+ 1.1306532663316584e-06,
+ 1.1557788944723618e-06,
+ 1.1809045226130654e-06,
+ 1.206030150753769e-06,
+ 1.2311557788944724e-06,
+ 1.256281407035176e-06,
+ 1.2814070351758793e-06,
+ 1.306532663316583e-06,
+ 1.3316582914572865e-06,
+ 1.3567839195979899e-06,
+ 1.3819095477386935e-06,
+ 1.407035175879397e-06,
+ 1.4321608040201004e-06,
+ 1.457286432160804e-06,
+ 1.4824120603015076e-06,
+ 1.507537688442211e-06,
+ 1.5326633165829146e-06,
+ 1.5577889447236182e-06,
+ 1.5829145728643216e-06,
+ 1.6080402010050252e-06,
+ 1.6331658291457288e-06,
+ 1.6582914572864321e-06,
+ 1.6834170854271357e-06,
+ 1.7085427135678393e-06,
+ 1.7336683417085427e-06,
+ 1.7587939698492463e-06,
+ 1.7839195979899499e-06,
+ 1.8090452261306533e-06,
+ 1.8341708542713568e-06,
+ 1.8592964824120604e-06,
+ 1.8844221105527638e-06,
+ 1.9095477386934674e-06,
+ 1.9346733668341708e-06,
+ 1.9597989949748746e-06,
+ 1.984924623115578e-06,
+ 2.0100502512562813e-06,
+ 2.035175879396985e-06,
+ 2.0603015075376885e-06,
+ 2.085427135678392e-06,
+ 2.1105527638190957e-06,
+ 2.135678391959799e-06,
+ 2.1608040201005025e-06,
+ 2.1859296482412063e-06,
+ 2.2110552763819096e-06,
+ 2.236180904522613e-06,
+ 2.261306532663317e-06,
+ 2.28643216080402e-06,
+ 2.3115577889447236e-06,
+ 2.3366834170854274e-06,
+ 2.3618090452261308e-06,
+ 2.386934673366834e-06,
+ 2.412060301507538e-06,
+ 2.4371859296482413e-06,
+ 2.4623115577889447e-06,
+ 2.487437185929648e-06,
+ 2.512562814070352e-06,
+ 2.5376884422110553e-06,
+ 2.5628140703517587e-06,
+ 2.5879396984924625e-06,
+ 2.613065326633166e-06,
+ 2.6381909547738692e-06,
+ 2.663316582914573e-06,
+ 2.6884422110552764e-06,
+ 2.7135678391959798e-06,
+ 2.7386934673366836e-06,
+ 2.763819095477387e-06,
+ 2.7889447236180903e-06,
+ 2.814070351758794e-06,
+ 2.8391959798994975e-06,
+ 2.864321608040201e-06,
+ 2.8894472361809047e-06,
+ 2.914572864321608e-06,
+ 2.9396984924623115e-06,
+ 2.9648241206030153e-06,
+ 2.9899497487437186e-06,
+ 3.015075376884422e-06,
+ 3.040201005025126e-06,
+ 3.065326633165829e-06,
+ 3.0904522613065326e-06,
+ 3.1155778894472364e-06,
+ 3.1407035175879398e-06,
+ 3.165829145728643e-06,
+ 3.190954773869347e-06,
+ 3.2160804020100503e-06,
+ 3.2412060301507537e-06,
+ 3.2663316582914575e-06,
+ 3.291457286432161e-06,
+ 3.3165829145728643e-06,
+ 3.341708542713568e-06,
+ 3.3668341708542714e-06,
+ 3.391959798994975e-06,
+ 3.4170854271356786e-06,
+ 3.442211055276382e-06,
+ 3.4673366834170854e-06,
+ 3.492462311557789e-06,
+ 3.5175879396984926e-06,
+ 3.542713567839196e-06,
+ 3.5678391959798997e-06,
+ 3.592964824120603e-06,
+ 3.6180904522613065e-06,
+ 3.6432160804020103e-06,
+ 3.6683417085427137e-06,
+ 3.693467336683417e-06,
+ 3.718592964824121e-06,
+ 3.7437185929648243e-06,
+ 3.7688442211055276e-06,
+ 3.7939698492462314e-06,
+ 3.819095477386935e-06,
+ 3.844221105527638e-06,
+ 3.8693467336683416e-06,
+ 3.894472361809045e-06,
+ 3.919597989949749e-06,
+ 3.9447236180904526e-06,
+ 3.969849246231156e-06,
+ 3.994974874371859e-06,
+ 4.020100502512563e-06,
+ 4.045226130653266e-06,
+ 4.07035175879397e-06,
+ 4.095477386934674e-06,
+ 4.120603015075377e-06,
+ 4.1457286432160804e-06,
+ 4.170854271356784e-06,
+ 4.195979899497487e-06,
+ 4.221105527638191e-06,
+ 4.246231155778895e-06,
+ 4.271356783919598e-06,
+ 4.2964824120603016e-06,
+ 4.321608040201005e-06,
+ 4.346733668341708e-06,
+ 4.3718592964824125e-06,
+ 4.396984924623116e-06,
+ 4.422110552763819e-06,
+ 4.447236180904523e-06,
+ 4.472361809045226e-06,
+ 4.4974874371859294e-06,
+ 4.522613065326634e-06,
+ 4.547738693467337e-06,
+ 4.57286432160804e-06,
+ 4.597989949748744e-06,
+ 4.623115577889447e-06,
+ 4.6482412060301506e-06,
+ 4.673366834170855e-06,
+ 4.698492462311558e-06,
+ 4.7236180904522615e-06,
+ 4.748743718592965e-06,
+ 4.773869346733668e-06,
+ 4.798994974874372e-06,
+ 4.824120603015076e-06,
+ 4.849246231155779e-06,
+ 4.874371859296483e-06,
+ 4.899497487437186e-06,
+ 4.9246231155778894e-06,
+ 4.949748743718593e-06,
+ 4.974874371859296e-06,
+ 5e-06,
+ 5e-06,
+ 4.974874371859296e-06,
+ 4.949748743718593e-06,
+ 4.9246231155778894e-06,
+ 4.899497487437186e-06,
+ 4.874371859296483e-06,
+ 4.849246231155779e-06,
+ 4.824120603015076e-06,
+ 4.798994974874372e-06,
+ 4.773869346733668e-06,
+ 4.748743718592965e-06,
+ 4.7236180904522615e-06,
+ 4.698492462311558e-06,
+ 4.673366834170855e-06,
+ 4.6482412060301506e-06,
+ 4.623115577889447e-06,
+ 4.597989949748744e-06,
+ 4.57286432160804e-06,
+ 4.547738693467337e-06,
+ 4.522613065326634e-06,
+ 4.4974874371859294e-06,
+ 4.472361809045226e-06,
+ 4.447236180904523e-06,
+ 4.422110552763819e-06,
+ 4.396984924623116e-06,
+ 4.3718592964824125e-06,
+ 4.346733668341708e-06,
+ 4.321608040201005e-06,
+ 4.2964824120603016e-06,
+ 4.271356783919598e-06,
+ 4.246231155778895e-06,
+ 4.221105527638191e-06,
+ 4.195979899497487e-06,
+ 4.170854271356784e-06,
+ 4.1457286432160804e-06,
+ 4.120603015075377e-06,
+ 4.095477386934674e-06,
+ 4.07035175879397e-06,
+ 4.045226130653266e-06,
+ 4.020100502512563e-06,
+ 3.994974874371859e-06,
+ 3.969849246231156e-06,
+ 3.9447236180904526e-06,
+ 3.919597989949749e-06,
+ 3.894472361809045e-06,
+ 3.8693467336683416e-06,
+ 3.844221105527638e-06,
+ 3.819095477386935e-06,
+ 3.7939698492462314e-06,
+ 3.7688442211055276e-06,
+ 3.7437185929648243e-06,
+ 3.718592964824121e-06,
+ 3.693467336683417e-06,
+ 3.6683417085427137e-06,
+ 3.6432160804020103e-06,
+ 3.6180904522613065e-06,
+ 3.592964824120603e-06,
+ 3.5678391959798997e-06,
+ 3.542713567839196e-06,
+ 3.5175879396984926e-06,
+ 3.492462311557789e-06,
+ 3.4673366834170854e-06,
+ 3.442211055276382e-06,
+ 3.4170854271356786e-06,
+ 3.391959798994975e-06,
+ 3.3668341708542714e-06,
+ 3.341708542713568e-06,
+ 3.3165829145728643e-06,
+ 3.291457286432161e-06,
+ 3.2663316582914575e-06,
+ 3.2412060301507537e-06,
+ 3.2160804020100503e-06,
+ 3.190954773869347e-06,
+ 3.165829145728643e-06,
+ 3.1407035175879398e-06,
+ 3.1155778894472364e-06,
+ 3.0904522613065326e-06,
+ 3.065326633165829e-06,
+ 3.040201005025126e-06,
+ 3.015075376884422e-06,
+ 2.9899497487437186e-06,
+ 2.9648241206030153e-06,
+ 2.9396984924623115e-06,
+ 2.914572864321608e-06,
+ 2.8894472361809047e-06,
+ 2.864321608040201e-06,
+ 2.8391959798994975e-06,
+ 2.814070351758794e-06,
+ 2.7889447236180903e-06,
+ 2.763819095477387e-06,
+ 2.7386934673366836e-06,
+ 2.7135678391959798e-06,
+ 2.6884422110552764e-06,
+ 2.663316582914573e-06,
+ 2.6381909547738692e-06,
+ 2.613065326633166e-06,
+ 2.5879396984924625e-06,
+ 2.5628140703517587e-06,
+ 2.5376884422110553e-06,
+ 2.512562814070352e-06,
+ 2.487437185929648e-06,
+ 2.4623115577889447e-06,
+ 2.4371859296482413e-06,
+ 2.412060301507538e-06,
+ 2.386934673366834e-06,
+ 2.3618090452261308e-06,
+ 2.3366834170854274e-06,
+ 2.3115577889447236e-06,
+ 2.28643216080402e-06,
+ 2.261306532663317e-06,
+ 2.236180904522613e-06,
+ 2.2110552763819096e-06,
+ 2.1859296482412063e-06,
+ 2.1608040201005025e-06,
+ 2.135678391959799e-06,
+ 2.1105527638190957e-06,
+ 2.085427135678392e-06,
+ 2.0603015075376885e-06,
+ 2.035175879396985e-06,
+ 2.0100502512562813e-06,
+ 1.984924623115578e-06,
+ 1.9597989949748746e-06,
+ 1.9346733668341708e-06,
+ 1.9095477386934674e-06,
+ 1.8844221105527638e-06,
+ 1.8592964824120604e-06,
+ 1.8341708542713568e-06,
+ 1.8090452261306533e-06,
+ 1.7839195979899499e-06,
+ 1.7587939698492463e-06,
+ 1.7336683417085427e-06,
+ 1.7085427135678393e-06,
+ 1.6834170854271357e-06,
+ 1.6582914572864321e-06,
+ 1.6331658291457288e-06,
+ 1.6080402010050252e-06,
+ 1.5829145728643216e-06,
+ 1.5577889447236182e-06,
+ 1.5326633165829146e-06,
+ 1.507537688442211e-06,
+ 1.4824120603015076e-06,
+ 1.457286432160804e-06,
+ 1.4321608040201004e-06,
+ 1.407035175879397e-06,
+ 1.3819095477386935e-06,
+ 1.3567839195979899e-06,
+ 1.3316582914572865e-06,
+ 1.306532663316583e-06,
+ 1.2814070351758793e-06,
+ 1.256281407035176e-06,
+ 1.2311557788944724e-06,
+ 1.206030150753769e-06,
+ 1.1809045226130654e-06,
+ 1.1557788944723618e-06,
+ 1.1306532663316584e-06,
+ 1.1055276381909548e-06,
+ 1.0804020100502512e-06,
+ 1.0552763819095479e-06,
+ 1.0301507537688443e-06,
+ 1.0050251256281407e-06,
+ 9.798994974874373e-07,
+ 9.547738693467337e-07,
+ 9.296482412060302e-07,
+ 9.045226130653266e-07,
+ 8.793969849246231e-07,
+ 8.542713567839197e-07,
+ 8.291457286432161e-07,
+ 8.040201005025126e-07,
+ 7.788944723618091e-07,
+ 7.537688442211055e-07,
+ 7.28643216080402e-07,
+ 7.035175879396985e-07,
+ 6.783919597989949e-07,
+ 6.532663316582915e-07,
+ 6.28140703517588e-07,
+ 6.030150753768845e-07,
+ 5.778894472361809e-07,
+ 5.527638190954774e-07,
+ 5.276381909547739e-07,
+ 5.025125628140703e-07,
+ 4.773869346733669e-07,
+ 4.522613065326633e-07,
+ 4.2713567839195983e-07,
+ 4.020100502512563e-07,
+ 3.7688442211055275e-07,
+ 3.5175879396984927e-07,
+ 3.2663316582914573e-07,
+ 3.0150753768844224e-07,
+ 2.763819095477387e-07,
+ 2.5125628140703517e-07,
+ 2.2613065326633166e-07,
+ 2.0100502512562815e-07,
+ 1.7587939698492463e-07,
+ 1.5075376884422112e-07,
+ 1.2562814070351758e-07,
+ 1.0050251256281407e-07,
+ 7.537688442211056e-08,
+ 5.0251256281407036e-08,
+ 2.5125628140703518e-08,
+ 0.0
+ ],
+ "segment": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ "tip_position": [
+ -7.042211055276383e-07,
+ -6.79070351758794e-07,
+ -6.539195979899498e-07,
+ -6.287688442211055e-07,
+ -6.036180904522614e-07,
+ -5.784673366834171e-07,
+ -5.533165829145729e-07,
+ -5.281658291457287e-07,
+ -5.030150753768845e-07,
+ -4.778643216080402e-07,
+ -4.5271356783919604e-07,
+ -4.275628140703518e-07,
+ -4.0241206030150754e-07,
+ -3.7726130653266334e-07,
+ -3.5211055276381915e-07,
+ -3.2695979899497495e-07,
+ -3.018090452261307e-07,
+ -2.7665829145728644e-07,
+ -2.5150753768844225e-07,
+ -2.2635678391959805e-07,
+ -2.012060301507538e-07,
+ -1.7605527638190955e-07,
+ -1.509045226130654e-07,
+ -1.2575376884422115e-07,
+ -1.006030150753769e-07,
+ -7.545226130653265e-08,
+ -5.03015075376885e-08,
+ -2.515075376884425e-08,
+ 0.0,
+ 2.5150753768844145e-08,
+ 5.0301507537688396e-08,
+ 7.545226130653265e-08,
+ 1.006030150753769e-07,
+ 1.2575376884422104e-07,
+ 1.509045226130653e-07,
+ 1.7605527638190955e-07,
+ 2.012060301507537e-07,
+ 2.2635678391959805e-07,
+ 2.515075376884422e-07,
+ 2.7665829145728655e-07,
+ 3.018090452261305e-07,
+ 3.2695979899497484e-07,
+ 3.521105527638192e-07,
+ 3.7726130653266334e-07,
+ 4.024120603015075e-07,
+ 4.2756281407035185e-07,
+ 4.52713567839196e-07,
+ 4.778643216080401e-07,
+ 5.030150753768845e-07,
+ 5.281658291457286e-07,
+ 5.533165829145728e-07,
+ 5.784673366834169e-07,
+ 6.036180904522613e-07,
+ 6.287688442211056e-07,
+ 6.539195979899496e-07,
+ 6.790703517587939e-07,
+ 7.042211055276383e-07,
+ 7.293718592964822e-07,
+ 7.545226130653266e-07,
+ 7.796733668341709e-07,
+ 8.048241206030151e-07,
+ 8.301740083621068e-07,
+ 8.556888641201788e-07,
+ 8.813111124754158e-07,
+ 9.070202076003725e-07,
+ 9.32804275141358e-07,
+ 9.586553032153856e-07,
+ 9.845674100953497e-07,
+ 1.01053603858957e-06,
+ 1.0365575222592973e-06,
+ 1.0626288279788578e-06,
+ 1.0887473920755335e-06,
+ 1.1149110103560906e-06,
+ 1.1411177611608193e-06,
+ 1.16736594968478e-06,
+ 1.193654066554068e-06,
+ 1.2199807562803669e-06,
+ 1.2463447927558711e-06,
+ 1.2727450598880405e-06,
+ 1.2991805360656051e-06,
+ 1.325650281532873e-06,
+ 1.352153428007453e-06,
+ 1.3786891700534096e-06,
+ 1.4052567578456553e-06,
+ 1.4318554910497194e-06,
+ 1.4584847136050661e-06,
+ 1.4851438092473548e-06,
+ 1.5118321976402484e-06,
+ 1.538549331014054e-06,
+ 1.5652946912288755e-06,
+ 1.5920677871957232e-06,
+ 1.6188681526013705e-06,
+ 1.6456953438924398e-06,
+ 1.672548938481938e-06,
+ 1.6994285331476313e-06,
+ 1.7263337425966675e-06,
+ 1.7532641981748811e-06,
+ 1.7802195467025574e-06,
+ 1.8071994494211637e-06,
+ 1.8342035810377954e-06,
+ 1.8612316288559887e-06,
+ 1.8882832919831146e-06,
+ 1.915358280605889e-06,
+ 1.942456315326653e-06,
+ 1.969577126554017e-06,
+ 1.9967204539422897e-06,
+ 2.0238860458747588e-06,
+ 2.0510736589865288e-06,
+ 2.078283057723097e-06,
+ 2.1055140139312965e-06,
+ 2.1327663064796202e-06,
+ 2.1600397209052696e-06,
+ 2.18733404908555e-06,
+ 2.214649088931497e-06,
+ 2.2419846441018473e-06,
+ 2.2693405237356292e-06,
+ 2.296716542201859e-06,
+ 2.3241125188649593e-06,
+ 2.3515282778646443e-06,
+ 2.378963647909149e-06,
+ 2.4064184620807856e-06,
+ 2.4338925576528876e-06,
+ 2.4613857759173045e-06,
+ 2.4888979620216786e-06,
+ 2.516428964815798e-06,
+ 2.5439786367063862e-06,
+ 2.5715468335197477e-06,
+ 2.5991334143717214e-06,
+ 2.6267382415444553e-06,
+ 2.654361180369553e-06,
+ 2.6820020991171646e-06,
+ 2.709660868890646e-06,
+ 2.7373373635264315e-06,
+ 2.7650314594987936e-06,
+ 2.7927430358291765e-06,
+ 2.820471973999846e-06,
+ 2.848218157871574e-06,
+ 2.875981473605128e-06,
+ 2.9037618095863474e-06,
+ 2.9315590563545864e-06,
+ 2.959373106534339e-06,
+ 2.9872038547698705e-06,
+ 3.015051197662676e-06,
+ 3.042915033711618e-06,
+ 3.0707952632556034e-06,
+ 3.0986917884186447e-06,
+ 3.126604513057201e-06,
+ 3.1545333427096633e-06,
+ 3.1824781845478775e-06,
+ 3.210438947330603e-06,
+ 3.23841554135881e-06,
+ 3.2664078784327137e-06,
+ 3.2944158718104693e-06,
+ 3.3224394361684423e-06,
+ 3.3504784875629817e-06,
+ 3.3785329433936138e-06,
+ 3.4066027223676035e-06,
+ 3.4346877444657973e-06,
+ 3.462787930909726e-06,
+ 3.4909032041298583e-06,
+ 3.5190334877350003e-06,
+ 3.5471787064827577e-06,
+ 3.575338786251029e-06,
+ 3.603513654010475e-06,
+ 3.6317032377979335e-06,
+ 3.6599074666907237e-06,
+ 3.688126270781812e-06,
+ 3.7163595811558006e-06,
+ 3.744607329865704e-06,
+ 3.772869449910475e-06,
+ 3.80114587521326e-06,
+ 3.829436540600347e-06,
+ 3.857741381780773e-06,
+ 3.886060335326582e-06,
+ 3.9143933386536785e-06,
+ 3.942740330003286e-06,
+ 3.971101248423967e-06,
+ 3.999476033754181e-06,
+ 4.0278646266053704e-06,
+ 4.056266968345549e-06,
+ 4.0846830010833714e-06,
+ 4.113112667652673e-06,
+ 4.141555911597451e-06,
+ 4.1700126771572856e-06,
+ 4.198482909253172e-06,
+ 4.226966553473749e-06,
+ 4.255463556061927e-06,
+ 4.283973863901869e-06,
+ 4.312497424506353e-06,
+ 4.341034186004464e-06,
+ 4.369584097129627e-06,
+ 4.398147107207964e-06,
+ 4.426723166146958e-06,
+ 4.455312224424423e-06,
+ 4.483914233077761e-06,
+ 4.512529143693505e-06,
+ 4.5411569083971295e-06,
+ 4.569797479843123e-06,
+ 4.598450811205317e-06,
+ 4.6271168561674655e-06,
+ 4.6271168561674655e-06,
+ 4.598450811205317e-06,
+ 4.569797479843123e-06,
+ 4.5411569083971295e-06,
+ 4.512529143693505e-06,
+ 4.483914233077761e-06,
+ 4.455312224424423e-06,
+ 4.426723166146958e-06,
+ 4.398147107207964e-06,
+ 4.369584097129627e-06,
+ 4.341034186004464e-06,
+ 4.312497424506353e-06,
+ 4.283973863901869e-06,
+ 4.255463556061927e-06,
+ 4.226966553473749e-06,
+ 4.198482909253172e-06,
+ 4.1700126771572856e-06,
+ 4.141555911597451e-06,
+ 4.113112667652673e-06,
+ 4.0846830010833714e-06,
+ 4.056266968345549e-06,
+ 4.0278646266053704e-06,
+ 3.999476033754181e-06,
+ 3.971101248423967e-06,
+ 3.942740330003286e-06,
+ 3.9143933386536785e-06,
+ 3.886060335326582e-06,
+ 3.857741381780773e-06,
+ 3.829436540600347e-06,
+ 3.80114587521326e-06,
+ 3.772869449910475e-06,
+ 3.744607329865704e-06,
+ 3.7163595811558006e-06,
+ 3.688126270781812e-06,
+ 3.6599074666907237e-06,
+ 3.6317032377979335e-06,
+ 3.603513654010475e-06,
+ 3.575338786251029e-06,
+ 3.5471787064827577e-06,
+ 3.5190334877350003e-06,
+ 3.4909032041298583e-06,
+ 3.462787930909726e-06,
+ 3.4346877444657973e-06,
+ 3.4066027223676035e-06,
+ 3.3785329433936138e-06,
+ 3.3504784875629817e-06,
+ 3.3224394361684423e-06,
+ 3.2944158718104693e-06,
+ 3.2664078784327137e-06,
+ 3.23841554135881e-06,
+ 3.210438947330603e-06,
+ 3.1824781845478775e-06,
+ 3.1545333427096633e-06,
+ 3.126604513057201e-06,
+ 3.0986917884186447e-06,
+ 3.0707952632556034e-06,
+ 3.042915033711618e-06,
+ 3.015051197662676e-06,
+ 2.9872038547698705e-06,
+ 2.959373106534339e-06,
+ 2.9315590563545864e-06,
+ 2.9037618095863474e-06,
+ 2.875981473605128e-06,
+ 2.848218157871574e-06,
+ 2.820471973999846e-06,
+ 2.7927430358291765e-06,
+ 2.7650314594987936e-06,
+ 2.7373373635264315e-06,
+ 2.709660868890646e-06,
+ 2.6820020991171646e-06,
+ 2.654361180369553e-06,
+ 2.6267382415444553e-06,
+ 2.5991334143717214e-06,
+ 2.5715468335197477e-06,
+ 2.5439786367063862e-06,
+ 2.516428964815798e-06,
+ 2.4888979620216786e-06,
+ 2.4613857759173045e-06,
+ 2.4338925576528876e-06,
+ 2.4064184620807856e-06,
+ 2.378963647909149e-06,
+ 2.3515282778646443e-06,
+ 2.3241125188649593e-06,
+ 2.296716542201859e-06,
+ 2.2693405237356292e-06,
+ 2.2419846441018473e-06,
+ 2.214649088931497e-06,
+ 2.18733404908555e-06,
+ 2.1600397209052696e-06,
+ 2.1327663064796202e-06,
+ 2.1055140139312965e-06,
+ 2.078283057723097e-06,
+ 2.0510736589865288e-06,
+ 2.0238860458747588e-06,
+ 1.9967204539422897e-06,
+ 1.969577126554017e-06,
+ 1.942456315326653e-06,
+ 1.915358280605889e-06,
+ 1.8882832919831146e-06,
+ 1.8612316288559887e-06,
+ 1.8342035810377954e-06,
+ 1.8071994494211637e-06,
+ 1.7802195467025574e-06,
+ 1.7532641981748811e-06,
+ 1.7263337425966675e-06,
+ 1.6994285331476313e-06,
+ 1.672548938481938e-06,
+ 1.6456953438924398e-06,
+ 1.6188681526013705e-06,
+ 1.5920677871957232e-06,
+ 1.5652946912288755e-06,
+ 1.538549331014054e-06,
+ 1.5118321976402484e-06,
+ 1.4851438092473548e-06,
+ 1.4584847136050661e-06,
+ 1.4318554910497194e-06,
+ 1.4052567578456553e-06,
+ 1.3786891700534096e-06,
+ 1.352153428007453e-06,
+ 1.325650281532873e-06,
+ 1.2991805360656051e-06,
+ 1.2727450598880405e-06,
+ 1.2463447927558711e-06,
+ 1.2199807562803669e-06,
+ 1.193654066554068e-06,
+ 1.16736594968478e-06,
+ 1.1411177611608193e-06,
+ 1.1149110103560906e-06,
+ 1.0887473920755335e-06,
+ 1.0626288279788578e-06,
+ 1.0365575222592973e-06,
+ 1.01053603858957e-06,
+ 9.845674100953497e-07,
+ 9.586553032153856e-07,
+ 9.32804275141358e-07,
+ 9.070202076003725e-07,
+ 8.813111124754158e-07,
+ 8.556888641201788e-07,
+ 8.301740083621068e-07,
+ 8.048241206030151e-07,
+ 7.796733668341709e-07,
+ 7.545226130653266e-07,
+ 7.293718592964822e-07,
+ 7.042211055276383e-07,
+ 6.790703517587939e-07,
+ 6.539195979899496e-07,
+ 6.287688442211056e-07,
+ 6.036180904522613e-07,
+ 5.784673366834169e-07,
+ 5.533165829145728e-07,
+ 5.241658291457285e-07,
+ 5.190150753768844e-07,
+ 4.938643216080403e-07,
+ 4.687135678391959e-07,
+ 4.435628140703518e-07,
+ 4.1841206030150763e-07,
+ 3.932613065326633e-07,
+ 3.6811055276381913e-07,
+ 3.4295979899497477e-07,
+ 3.1780904522613063e-07,
+ 2.926582914572865e-07,
+ 2.675075376884421e-07,
+ 2.42356783919598e-07,
+ 2.1720603015075373e-07,
+ 1.9205527638190948e-07,
+ 1.6690452261306533e-07,
+ 1.4175376884422108e-07,
+ 1.1660301507537683e-07,
+ 9.145226130653268e-08,
+ 6.630150753768833e-08,
+ 4.115075376884418e-08,
+ 1.6000000000000037e-08,
+ -9.15075376884432e-09,
+ -3.4301507537688465e-08,
+ -5.9452261306532716e-08,
+ -8.460301507537686e-08,
+ -1.0975376884422111e-07,
+ -1.3490452261306536e-07,
+ -1.600552763819095e-07,
+ -1.8520603015075387e-07,
+ -2.10356783919598e-07,
+ -2.3550753768844226e-07,
+ -2.6065829145728646e-07,
+ -2.858090452261307e-07,
+ -3.109597989949749e-07,
+ -3.361105527638191e-07,
+ -3.6126130653266336e-07,
+ -3.8641206030150756e-07,
+ -4.115628140703518e-07,
+ -4.3671356783919606e-07,
+ -4.6186432160804026e-07,
+ -4.870150753768845e-07,
+ -5.121658291457287e-07,
+ -5.37316582914573e-07,
+ -5.624673366834171e-07,
+ -5.876180904522614e-07,
+ -6.127688442211056e-07,
+ -6.379195979899498e-07,
+ -6.63070351758794e-07,
+ -6.882211055276383e-07
+ ]
+ },
+ "P17": {
+ "contact": {
+ "deviation_from_baseline": 28,
+ "fit_constant_line": 83,
+ "fit_constant_polynomial": 51,
+ "fit_line_polynomial": 51
+ },
+ "force": [
+ 3.3919597989949844e-11,
+ 3.3919597989949844e-11,
+ 3.3919597989949844e-11,
+ 3.391959798994987e-11,
+ 3.3919597989949877e-11,
+ 3.391959798994988e-11,
+ 3.3919597989949786e-11,
+ 3.39195979899498e-11,
+ 3.3919597989949805e-11,
+ 3.391959798994981e-11,
+ 3.3919597989949825e-11,
+ 3.391959798994983e-11,
+ 3.391959798994984e-11,
+ 3.391959798994985e-11,
+ 3.391959798994986e-11,
+ 3.391959798994987e-11,
+ 3.3919597989949877e-11,
+ 3.391959798994988e-11,
+ 3.391959798994979e-11,
+ 3.3919597989949805e-11,
+ 3.3919597989949805e-11,
+ 3.391959798994981e-11,
+ 3.391959798994983e-11,
+ 3.391959798994984e-11,
+ 3.3919597989949844e-11,
+ 3.3919597989949844e-11,
+ 3.3919597989949864e-11,
+ 3.391959798994987e-11,
+ 3.643216080402023e-11,
+ 3.894472361809049e-11,
+ 4.145728643216085e-11,
+ 4.396984924623121e-11,
+ 4.648241206030157e-11,
+ 4.899497487437193e-11,
+ 5.150753768844229e-11,
+ 5.402010050251265e-11,
+ 5.6532663316583013e-11,
+ 5.904522613065337e-11,
+ 6.155778894472373e-11,
+ 6.40703517587941e-11,
+ 6.658291457286446e-11,
+ 6.909547738693482e-11,
+ 7.160804020100518e-11,
+ 7.412060301507544e-11,
+ 7.66331658291458e-11,
+ 7.914572864321616e-11,
+ 8.165829145728652e-11,
+ 8.417085427135688e-11,
+ 8.668341708542724e-11,
+ 8.91959798994976e-11,
+ 9.170854271356796e-11,
+ 9.422110552763822e-11,
+ 9.673366834170858e-11,
+ 9.924623115577894e-11,
+ 1.017587939698493e-10,
+ 1.0427135678391966e-10,
+ 1.0678391959799002e-10,
+ 1.0929648241206038e-10,
+ 1.1180904522613074e-10,
+ 1.143216080402011e-10,
+ 1.1683417085427146e-10,
+ 1.392601326930982e-10,
+ 1.7818289442992757e-10,
+ 2.2784491588329405e-10,
+ 2.8619161430860365e-10,
+ 3.5203555433678944e-10,
+ 4.245755476692286e-10,
+ 5.032234215952738e-10,
+ 5.875234569469557e-10,
+ 6.771090098493254e-10,
+ 7.716767677350325e-10,
+ 8.709703633322505e-10,
+ 9.747693773176074e-10,
+ 1.0828816437201223e-09,
+ 1.1951376820458483e-09,
+ 1.3113865549042909e-09,
+ 1.4314927134638023e-09,
+ 1.555333546943882e-09,
+ 1.6827974460904473e-09,
+ 1.813782249776579e-09,
+ 1.948193982433005e-09,
+ 2.0859458158206693e-09,
+ 2.2269572063459504e-09,
+ 2.371153171500206e-09,
+ 2.518463677836241e-09,
+ 2.6688231193005718e-09,
+ 2.822169869459094e-09,
+ 2.978445894678082e-09,
+ 3.1375964179883197e-09,
+ 3.299569625400097e-09,
+ 3.4643164080145087e-09,
+ 3.6317901345088774e-09,
+ 3.801946449545491e-09,
+ 3.974743094424921e-09,
+ 4.150139746923922e-09,
+ 4.3280978777572115e-09,
+ 4.508580621508173e-09,
+ 4.691552660205477e-09,
+ 4.876980117995778e-09,
+ 5.064830465588577e-09,
+ 5.255072433337545e-09,
+ 5.4476759319798e-09,
+ 5.642611980186922e-09,
+ 5.839852638192903e-09,
+ 6.0393709468590044e-09,
+ 6.241140871615914e-09,
+ 6.445137250792455e-09,
+ 6.65133574789912e-09,
+ 6.859712807485581e-09,
+ 7.070245614235139e-09,
+ 7.282912054997178e-09,
+ 7.497690683491785e-09,
+ 7.714560687449419e-09,
+ 7.933501857973829e-09,
+ 8.154494560938513e-09,
+ 8.3775197102463e-09,
+ 8.602558742798938e-09,
+ 8.82959359503864e-09,
+ 9.058606680936748e-09,
+ 9.28958087131688e-09,
+ 9.522499474410204e-09,
+ 9.757346217550045e-09,
+ 9.9941052299214e-09,
+ 1.0232761026288466e-08,
+ 1.0473298491630014e-08,
+ 1.0715702866618505e-08,
+ 1.0959959733884316e-08,
+ 1.1206055005011306e-08,
+ 1.1453974908214373e-08,
+ 1.1703705976653802e-08,
+ 1.1955235037344581e-08,
+ 1.2208549200622345e-08,
+ 1.24636358501306e-08,
+ 1.2720482633296406e-08,
+ 1.2979077452264365e-08,
+ 1.3239408455260979e-08,
+ 1.3501464028363385e-08,
+ 1.3765232787648454e-08,
+ 1.4030703571700053e-08,
+ 1.4297865434453563e-08,
+ 1.4566707638358508e-08,
+ 1.4837219647841329e-08,
+ 1.510939112305149e-08,
+ 1.538321191387538e-08,
+ 1.5658672054203537e-08,
+ 1.5935761756437322e-08,
+ 1.621447140622263e-08,
+ 1.649479155739848e-08,
+ 1.6776712927149512e-08,
+ 1.7060226391351772e-08,
+ 1.734532298010215e-08,
+ 1.7631993873422133e-08,
+ 1.7920230397127276e-08,
+ 1.821002401885425e-08,
+ 1.850136634423786e-08,
+ 1.8794249113230785e-08,
+ 1.9088664196559265e-08,
+ 1.9384603592308372e-08,
+ 1.9682059422630854e-08,
+ 1.998102393057376e-08,
+ 2.028148947701763e-08,
+ 2.0583448537723035e-08,
+ 2.0886893700479763e-08,
+ 2.1191817662354026e-08,
+ 2.149821322702953e-08,
+ 2.1806073302238184e-08,
+ 2.2115390897276654e-08,
+ 2.24261591206052e-08,
+ 2.273837117752515e-08,
+ 2.3052020367931855e-08,
+ 2.336710008414004e-08,
+ 2.3683603808778393e-08,
+ 2.400152511275077e-08,
+ 2.4320857653261244e-08,
+ 2.464159517190049e-08,
+ 2.4963731492790932e-08,
+ 2.5287260520788715e-08,
+ 2.5612176239739766e-08,
+ 2.5938472710788365e-08,
+ 2.626614407073583e-08,
+ 2.6595184530447713e-08,
+ 2.6925588373307454e-08,
+ 2.725734995371493e-08,
+ 2.7590463695628124e-08,
+ 2.7924924091146382e-08,
+ 2.8260725699133787e-08,
+ 2.85978631438811e-08,
+ 2.893633111380495e-08,
+ 2.9276124360183016e-08,
+ 2.9617237695923797e-08,
+ 2.995966599436981e-08,
+ 3.0303404188133144e-08,
+ 3.064844726796217e-08,
+ 3.099479028163824e-08,
+ 3.134242833290169e-08,
+ 3.16913565804058e-08,
+ 3.2041570236697896e-08,
+ 3.239306456722688e-08,
+ 3.2745834889376026e-08,
+ 3.309987657152037e-08,
+ 3.309987657152037e-08,
+ 3.2745834889376026e-08,
+ 3.239306456722688e-08,
+ 3.2041570236697896e-08,
+ 3.16913565804058e-08,
+ 3.134242833290169e-08,
+ 3.099479028163824e-08,
+ 3.064844726796217e-08,
+ 3.0303404188133144e-08,
+ 2.995966599436981e-08,
+ 2.9617237695923797e-08,
+ 2.9276124360183016e-08,
+ 2.893633111380495e-08,
+ 2.85978631438811e-08,
+ 2.8260725699133787e-08,
+ 2.7924924091146382e-08,
+ 2.7590463695628124e-08,
+ 2.725734995371493e-08,
+ 2.6925588373307454e-08,
+ 2.6595184530447713e-08,
+ 2.626614407073583e-08,
+ 2.5938472710788365e-08,
+ 2.5612176239739766e-08,
+ 2.5287260520788715e-08,
+ 2.4963731492790932e-08,
+ 2.464159517190049e-08,
+ 2.4320857653261244e-08,
+ 2.400152511275077e-08,
+ 2.3683603808778393e-08,
+ 2.336710008414004e-08,
+ 2.3052020367931855e-08,
+ 2.273837117752515e-08,
+ 2.24261591206052e-08,
+ 2.2115390897276654e-08,
+ 2.1806073302238184e-08,
+ 2.149821322702953e-08,
+ 2.1191817662354026e-08,
+ 2.0886893700479763e-08,
+ 2.0583448537723035e-08,
+ 2.028148947701763e-08,
+ 1.998102393057376e-08,
+ 1.9682059422630854e-08,
+ 1.9384603592308372e-08,
+ 1.9088664196559265e-08,
+ 1.8794249113230785e-08,
+ 1.850136634423786e-08,
+ 1.821002401885425e-08,
+ 1.7920230397127276e-08,
+ 1.7631993873422133e-08,
+ 1.734532298010215e-08,
+ 1.7060226391351772e-08,
+ 1.6776712927149512e-08,
+ 1.649479155739848e-08,
+ 1.621447140622263e-08,
+ 1.5935761756437322e-08,
+ 1.5658672054203537e-08,
+ 1.538321191387538e-08,
+ 1.510939112305149e-08,
+ 1.4837219647841329e-08,
+ 1.4566707638358508e-08,
+ 1.4297865434453563e-08,
+ 1.4030703571700053e-08,
+ 1.3765232787648454e-08,
+ 1.3501464028363385e-08,
+ 1.3239408455260979e-08,
+ 1.2979077452264365e-08,
+ 1.2720482633296406e-08,
+ 1.24636358501306e-08,
+ 1.2208549200622345e-08,
+ 1.1955235037344581e-08,
+ 1.1703705976653802e-08,
+ 1.1453974908214373e-08,
+ 1.1206055005011306e-08,
+ 1.0959959733884316e-08,
+ 1.0715702866618505e-08,
+ 1.0473298491630014e-08,
+ 1.0232761026288466e-08,
+ 9.9941052299214e-09,
+ 9.757346217550045e-09,
+ 9.522499474410204e-09,
+ 9.28958087131688e-09,
+ 9.058606680936748e-09,
+ 8.82959359503864e-09,
+ 8.602558742798938e-09,
+ 8.3775197102463e-09,
+ 8.154494560938513e-09,
+ 7.933501857973829e-09,
+ 7.714560687449419e-09,
+ 7.497690683491785e-09,
+ 7.282912054997178e-09,
+ 7.070245614235139e-09,
+ 6.859712807485581e-09,
+ 6.65133574789912e-09,
+ 6.445137250792455e-09,
+ 6.241140871615914e-09,
+ 6.0393709468590044e-09,
+ 5.839852638192903e-09,
+ 5.642611980186922e-09,
+ 5.4476759319798e-09,
+ 5.255072433337545e-09,
+ 5.064830465588577e-09,
+ 4.876980117995778e-09,
+ 4.691552660205477e-09,
+ 4.508580621508173e-09,
+ 4.3280978777572115e-09,
+ 4.150139746923922e-09,
+ 3.974743094424921e-09,
+ 3.801946449545491e-09,
+ 3.6317901345088774e-09,
+ 3.4643164080145087e-09,
+ 3.299569625400097e-09,
+ 3.1375964179883197e-09,
+ 2.978445894678082e-09,
+ 2.822169869459094e-09,
+ 2.6688231193005718e-09,
+ 2.518463677836241e-09,
+ 2.371153171500206e-09,
+ 2.2269572063459504e-09,
+ 2.0859458158206693e-09,
+ 1.948193982433005e-09,
+ 1.813782249776579e-09,
+ 1.6827974460904473e-09,
+ 1.555333546943882e-09,
+ 1.4314927134638023e-09,
+ 1.3113865549042909e-09,
+ 1.1951376820458483e-09,
+ 1.0828816437201223e-09,
+ 9.747693773176074e-10,
+ 8.709703633322505e-10,
+ 7.716767677350325e-10,
+ -1.3228909901506747e-09,
+ 5.875234569469557e-10,
+ 5.032234215952738e-10,
+ 4.245755476692286e-10,
+ 3.5203555433678944e-10,
+ 2.8619161430860365e-10,
+ 2.2784491588329405e-10,
+ 1.7818289442992757e-10,
+ 1.392601326930982e-10,
+ 1.1683417085427146e-10,
+ 1.143216080402011e-10,
+ 1.1180904522613074e-10,
+ 1.0929648241206038e-10,
+ 1.0678391959799002e-10,
+ 1.0427135678391966e-10,
+ 1.017587939698493e-10,
+ 9.924623115577894e-11,
+ 9.673366834170858e-11,
+ 9.422110552763822e-11,
+ 9.170854271356796e-11,
+ 8.91959798994976e-11,
+ 8.668341708542724e-11,
+ 8.417085427135688e-11,
+ 8.165829145728652e-11,
+ 7.914572864321616e-11,
+ 7.66331658291458e-11,
+ 7.412060301507544e-11,
+ 7.160804020100518e-11,
+ 6.909547738693482e-11,
+ 6.658291457286446e-11,
+ 6.40703517587941e-11,
+ 6.155778894472373e-11,
+ 5.904522613065337e-11,
+ 5.6532663316583013e-11,
+ 5.402010050251265e-11,
+ 5.150753768844229e-11,
+ 4.899497487437193e-11,
+ 4.648241206030157e-11,
+ 4.396984924623121e-11,
+ 4.145728643216085e-11,
+ 3.894472361809049e-11,
+ 3.643216080402023e-11,
+ 3.391959798994987e-11,
+ 3.140703517587951e-11,
+ 2.889447236180915e-11,
+ 2.6381909547738788e-11,
+ 2.3869346733668427e-11,
+ 2.1356783919598066e-11,
+ 1.8844221105527706e-11,
+ 1.6331658291457345e-11,
+ 1.3819095477386984e-11,
+ 1.1306532663316623e-11,
+ 8.793969849246366e-12,
+ 6.281407035176005e-12,
+ 3.7688442211056445e-12,
+ 1.2562814070352838e-12,
+ -1.256281407035077e-12,
+ -3.768844221105438e-12,
+ -6.2814070351757985e-12,
+ -8.79396984924616e-12,
+ -1.130653266331652e-11,
+ -1.381909547738688e-11,
+ -1.633165829145724e-11,
+ -1.8844221105527602e-11,
+ -2.135678391959786e-11,
+ -2.386934673366822e-11,
+ -2.638190954773858e-11,
+ -2.8894472361808942e-11,
+ -3.14070351758793e-11,
+ -3.3919597989949663e-11
+ ],
+ "height": [
+ 0.0,
+ 2.5125628140703518e-08,
+ 5.0251256281407036e-08,
+ 7.537688442211056e-08,
+ 1.0050251256281407e-07,
+ 1.2562814070351758e-07,
+ 1.5075376884422112e-07,
+ 1.7587939698492463e-07,
+ 2.0100502512562815e-07,
+ 2.2613065326633166e-07,
+ 2.5125628140703517e-07,
+ 2.763819095477387e-07,
+ 3.0150753768844224e-07,
+ 3.2663316582914573e-07,
+ 3.5175879396984927e-07,
+ 3.7688442211055275e-07,
+ 4.020100502512563e-07,
+ 4.2713567839195983e-07,
+ 4.522613065326633e-07,
+ 4.773869346733669e-07,
+ 5.025125628140703e-07,
+ 5.276381909547739e-07,
+ 5.527638190954774e-07,
+ 5.778894472361809e-07,
+ 6.030150753768845e-07,
+ 6.28140703517588e-07,
+ 6.532663316582915e-07,
+ 6.783919597989949e-07,
+ 7.035175879396985e-07,
+ 7.28643216080402e-07,
+ 7.537688442211055e-07,
+ 7.788944723618091e-07,
+ 8.040201005025126e-07,
+ 8.291457286432161e-07,
+ 8.542713567839197e-07,
+ 8.793969849246231e-07,
+ 9.045226130653266e-07,
+ 9.296482412060302e-07,
+ 9.547738693467337e-07,
+ 9.798994974874373e-07,
+ 1.0050251256281407e-06,
+ 1.0301507537688443e-06,
+ 1.0552763819095479e-06,
+ 1.0804020100502512e-06,
+ 1.1055276381909548e-06,
+ 1.1306532663316584e-06,
+ 1.1557788944723618e-06,
+ 1.1809045226130654e-06,
+ 1.206030150753769e-06,
+ 1.2311557788944724e-06,
+ 1.256281407035176e-06,
+ 1.2814070351758793e-06,
+ 1.306532663316583e-06,
+ 1.3316582914572865e-06,
+ 1.3567839195979899e-06,
+ 1.3819095477386935e-06,
+ 1.407035175879397e-06,
+ 1.4321608040201004e-06,
+ 1.457286432160804e-06,
+ 1.4824120603015076e-06,
+ 1.507537688442211e-06,
+ 1.5326633165829146e-06,
+ 1.5577889447236182e-06,
+ 1.5829145728643216e-06,
+ 1.6080402010050252e-06,
+ 1.6331658291457288e-06,
+ 1.6582914572864321e-06,
+ 1.6834170854271357e-06,
+ 1.7085427135678393e-06,
+ 1.7336683417085427e-06,
+ 1.7587939698492463e-06,
+ 1.7839195979899499e-06,
+ 1.8090452261306533e-06,
+ 1.8341708542713568e-06,
+ 1.8592964824120604e-06,
+ 1.8844221105527638e-06,
+ 1.9095477386934674e-06,
+ 1.9346733668341708e-06,
+ 1.9597989949748746e-06,
+ 1.984924623115578e-06,
+ 2.0100502512562813e-06,
+ 2.035175879396985e-06,
+ 2.0603015075376885e-06,
+ 2.085427135678392e-06,
+ 2.1105527638190957e-06,
+ 2.135678391959799e-06,
+ 2.1608040201005025e-06,
+ 2.1859296482412063e-06,
+ 2.2110552763819096e-06,
+ 2.236180904522613e-06,
+ 2.261306532663317e-06,
+ 2.28643216080402e-06,
+ 2.3115577889447236e-06,
+ 2.3366834170854274e-06,
+ 2.3618090452261308e-06,
+ 2.386934673366834e-06,
+ 2.412060301507538e-06,
+ 2.4371859296482413e-06,
+ 2.4623115577889447e-06,
+ 2.487437185929648e-06,
+ 2.512562814070352e-06,
+ 2.5376884422110553e-06,
+ 2.5628140703517587e-06,
+ 2.5879396984924625e-06,
+ 2.613065326633166e-06,
+ 2.6381909547738692e-06,
+ 2.663316582914573e-06,
+ 2.6884422110552764e-06,
+ 2.7135678391959798e-06,
+ 2.7386934673366836e-06,
+ 2.763819095477387e-06,
+ 2.7889447236180903e-06,
+ 2.814070351758794e-06,
+ 2.8391959798994975e-06,
+ 2.864321608040201e-06,
+ 2.8894472361809047e-06,
+ 2.914572864321608e-06,
+ 2.9396984924623115e-06,
+ 2.9648241206030153e-06,
+ 2.9899497487437186e-06,
+ 3.015075376884422e-06,
+ 3.040201005025126e-06,
+ 3.065326633165829e-06,
+ 3.0904522613065326e-06,
+ 3.1155778894472364e-06,
+ 3.1407035175879398e-06,
+ 3.165829145728643e-06,
+ 3.190954773869347e-06,
+ 3.2160804020100503e-06,
+ 3.2412060301507537e-06,
+ 3.2663316582914575e-06,
+ 3.291457286432161e-06,
+ 3.3165829145728643e-06,
+ 3.341708542713568e-06,
+ 3.3668341708542714e-06,
+ 3.391959798994975e-06,
+ 3.4170854271356786e-06,
+ 3.442211055276382e-06,
+ 3.4673366834170854e-06,
+ 3.492462311557789e-06,
+ 3.5175879396984926e-06,
+ 3.542713567839196e-06,
+ 3.5678391959798997e-06,
+ 3.592964824120603e-06,
+ 3.6180904522613065e-06,
+ 3.6432160804020103e-06,
+ 3.6683417085427137e-06,
+ 3.693467336683417e-06,
+ 3.718592964824121e-06,
+ 3.7437185929648243e-06,
+ 3.7688442211055276e-06,
+ 3.7939698492462314e-06,
+ 3.819095477386935e-06,
+ 3.844221105527638e-06,
+ 3.8693467336683416e-06,
+ 3.894472361809045e-06,
+ 3.919597989949749e-06,
+ 3.9447236180904526e-06,
+ 3.969849246231156e-06,
+ 3.994974874371859e-06,
+ 4.020100502512563e-06,
+ 4.045226130653266e-06,
+ 4.07035175879397e-06,
+ 4.095477386934674e-06,
+ 4.120603015075377e-06,
+ 4.1457286432160804e-06,
+ 4.170854271356784e-06,
+ 4.195979899497487e-06,
+ 4.221105527638191e-06,
+ 4.246231155778895e-06,
+ 4.271356783919598e-06,
+ 4.2964824120603016e-06,
+ 4.321608040201005e-06,
+ 4.346733668341708e-06,
+ 4.3718592964824125e-06,
+ 4.396984924623116e-06,
+ 4.422110552763819e-06,
+ 4.447236180904523e-06,
+ 4.472361809045226e-06,
+ 4.4974874371859294e-06,
+ 4.522613065326634e-06,
+ 4.547738693467337e-06,
+ 4.57286432160804e-06,
+ 4.597989949748744e-06,
+ 4.623115577889447e-06,
+ 4.6482412060301506e-06,
+ 4.673366834170855e-06,
+ 4.698492462311558e-06,
+ 4.7236180904522615e-06,
+ 4.748743718592965e-06,
+ 4.773869346733668e-06,
+ 4.798994974874372e-06,
+ 4.824120603015076e-06,
+ 4.849246231155779e-06,
+ 4.874371859296483e-06,
+ 4.899497487437186e-06,
+ 4.9246231155778894e-06,
+ 4.949748743718593e-06,
+ 4.974874371859296e-06,
+ 5e-06,
+ 5e-06,
+ 4.974874371859296e-06,
+ 4.949748743718593e-06,
+ 4.9246231155778894e-06,
+ 4.899497487437186e-06,
+ 4.874371859296483e-06,
+ 4.849246231155779e-06,
+ 4.824120603015076e-06,
+ 4.798994974874372e-06,
+ 4.773869346733668e-06,
+ 4.748743718592965e-06,
+ 4.7236180904522615e-06,
+ 4.698492462311558e-06,
+ 4.673366834170855e-06,
+ 4.6482412060301506e-06,
+ 4.623115577889447e-06,
+ 4.597989949748744e-06,
+ 4.57286432160804e-06,
+ 4.547738693467337e-06,
+ 4.522613065326634e-06,
+ 4.4974874371859294e-06,
+ 4.472361809045226e-06,
+ 4.447236180904523e-06,
+ 4.422110552763819e-06,
+ 4.396984924623116e-06,
+ 4.3718592964824125e-06,
+ 4.346733668341708e-06,
+ 4.321608040201005e-06,
+ 4.2964824120603016e-06,
+ 4.271356783919598e-06,
+ 4.246231155778895e-06,
+ 4.221105527638191e-06,
+ 4.195979899497487e-06,
+ 4.170854271356784e-06,
+ 4.1457286432160804e-06,
+ 4.120603015075377e-06,
+ 4.095477386934674e-06,
+ 4.07035175879397e-06,
+ 4.045226130653266e-06,
+ 4.020100502512563e-06,
+ 3.994974874371859e-06,
+ 3.969849246231156e-06,
+ 3.9447236180904526e-06,
+ 3.919597989949749e-06,
+ 3.894472361809045e-06,
+ 3.8693467336683416e-06,
+ 3.844221105527638e-06,
+ 3.819095477386935e-06,
+ 3.7939698492462314e-06,
+ 3.7688442211055276e-06,
+ 3.7437185929648243e-06,
+ 3.718592964824121e-06,
+ 3.693467336683417e-06,
+ 3.6683417085427137e-06,
+ 3.6432160804020103e-06,
+ 3.6180904522613065e-06,
+ 3.592964824120603e-06,
+ 3.5678391959798997e-06,
+ 3.542713567839196e-06,
+ 3.5175879396984926e-06,
+ 3.492462311557789e-06,
+ 3.4673366834170854e-06,
+ 3.442211055276382e-06,
+ 3.4170854271356786e-06,
+ 3.391959798994975e-06,
+ 3.3668341708542714e-06,
+ 3.341708542713568e-06,
+ 3.3165829145728643e-06,
+ 3.291457286432161e-06,
+ 3.2663316582914575e-06,
+ 3.2412060301507537e-06,
+ 3.2160804020100503e-06,
+ 3.190954773869347e-06,
+ 3.165829145728643e-06,
+ 3.1407035175879398e-06,
+ 3.1155778894472364e-06,
+ 3.0904522613065326e-06,
+ 3.065326633165829e-06,
+ 3.040201005025126e-06,
+ 3.015075376884422e-06,
+ 2.9899497487437186e-06,
+ 2.9648241206030153e-06,
+ 2.9396984924623115e-06,
+ 2.914572864321608e-06,
+ 2.8894472361809047e-06,
+ 2.864321608040201e-06,
+ 2.8391959798994975e-06,
+ 2.814070351758794e-06,
+ 2.7889447236180903e-06,
+ 2.763819095477387e-06,
+ 2.7386934673366836e-06,
+ 2.7135678391959798e-06,
+ 2.6884422110552764e-06,
+ 2.663316582914573e-06,
+ 2.6381909547738692e-06,
+ 2.613065326633166e-06,
+ 2.5879396984924625e-06,
+ 2.5628140703517587e-06,
+ 2.5376884422110553e-06,
+ 2.512562814070352e-06,
+ 2.487437185929648e-06,
+ 2.4623115577889447e-06,
+ 2.4371859296482413e-06,
+ 2.412060301507538e-06,
+ 2.386934673366834e-06,
+ 2.3618090452261308e-06,
+ 2.3366834170854274e-06,
+ 2.3115577889447236e-06,
+ 2.28643216080402e-06,
+ 2.261306532663317e-06,
+ 2.236180904522613e-06,
+ 2.2110552763819096e-06,
+ 2.1859296482412063e-06,
+ 2.1608040201005025e-06,
+ 2.135678391959799e-06,
+ 2.1105527638190957e-06,
+ 2.085427135678392e-06,
+ 2.0603015075376885e-06,
+ 2.035175879396985e-06,
+ 2.0100502512562813e-06,
+ 1.984924623115578e-06,
+ 1.9597989949748746e-06,
+ 1.9346733668341708e-06,
+ 1.9095477386934674e-06,
+ 1.8844221105527638e-06,
+ 1.8592964824120604e-06,
+ 1.8341708542713568e-06,
+ 1.8090452261306533e-06,
+ 1.7839195979899499e-06,
+ 1.7587939698492463e-06,
+ 1.7336683417085427e-06,
+ 1.7085427135678393e-06,
+ 1.6834170854271357e-06,
+ 1.6582914572864321e-06,
+ 1.6331658291457288e-06,
+ 1.6080402010050252e-06,
+ 1.5829145728643216e-06,
+ 1.5577889447236182e-06,
+ 1.5326633165829146e-06,
+ 1.507537688442211e-06,
+ 1.4824120603015076e-06,
+ 1.457286432160804e-06,
+ 1.4321608040201004e-06,
+ 1.407035175879397e-06,
+ 1.3819095477386935e-06,
+ 1.3567839195979899e-06,
+ 1.3316582914572865e-06,
+ 1.306532663316583e-06,
+ 1.2814070351758793e-06,
+ 1.256281407035176e-06,
+ 1.2311557788944724e-06,
+ 1.206030150753769e-06,
+ 1.1809045226130654e-06,
+ 1.1557788944723618e-06,
+ 1.1306532663316584e-06,
+ 1.1055276381909548e-06,
+ 1.0804020100502512e-06,
+ 1.0552763819095479e-06,
+ 1.0301507537688443e-06,
+ 1.0050251256281407e-06,
+ 9.798994974874373e-07,
+ 9.547738693467337e-07,
+ 9.296482412060302e-07,
+ 9.045226130653266e-07,
+ 8.793969849246231e-07,
+ 8.542713567839197e-07,
+ 8.291457286432161e-07,
+ 8.040201005025126e-07,
+ 7.788944723618091e-07,
+ 7.537688442211055e-07,
+ 7.28643216080402e-07,
+ 7.035175879396985e-07,
+ 6.783919597989949e-07,
+ 6.532663316582915e-07,
+ 6.28140703517588e-07,
+ 6.030150753768845e-07,
+ 5.778894472361809e-07,
+ 5.527638190954774e-07,
+ 5.276381909547739e-07,
+ 5.025125628140703e-07,
+ 4.773869346733669e-07,
+ 4.522613065326633e-07,
+ 4.2713567839195983e-07,
+ 4.020100502512563e-07,
+ 3.7688442211055275e-07,
+ 3.5175879396984927e-07,
+ 3.2663316582914573e-07,
+ 3.0150753768844224e-07,
+ 2.763819095477387e-07,
+ 2.5125628140703517e-07,
+ 2.2613065326633166e-07,
+ 2.0100502512562815e-07,
+ 1.7587939698492463e-07,
+ 1.5075376884422112e-07,
+ 1.2562814070351758e-07,
+ 1.0050251256281407e-07,
+ 7.537688442211056e-08,
+ 5.0251256281407036e-08,
+ 2.5125628140703518e-08,
+ 0.0
+ ],
+ "segment": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ "tip_position": [
+ -7.042211055276383e-07,
+ -6.79070351758794e-07,
+ -6.539195979899498e-07,
+ -6.287688442211055e-07,
+ -6.036180904522614e-07,
+ -5.784673366834171e-07,
+ -5.533165829145729e-07,
+ -5.281658291457287e-07,
+ -5.030150753768845e-07,
+ -4.778643216080402e-07,
+ -4.5271356783919604e-07,
+ -4.275628140703518e-07,
+ -4.0241206030150754e-07,
+ -3.7726130653266334e-07,
+ -3.5211055276381915e-07,
+ -3.2695979899497495e-07,
+ -3.018090452261307e-07,
+ -2.7665829145728644e-07,
+ -2.5150753768844225e-07,
+ -2.2635678391959805e-07,
+ -2.012060301507538e-07,
+ -1.7605527638190955e-07,
+ -1.509045226130654e-07,
+ -1.2575376884422115e-07,
+ -1.006030150753769e-07,
+ -7.545226130653265e-08,
+ -5.03015075376885e-08,
+ -2.515075376884425e-08,
+ 0.0,
+ 2.5150753768844145e-08,
+ 5.0301507537688396e-08,
+ 7.545226130653265e-08,
+ 1.006030150753769e-07,
+ 1.2575376884422104e-07,
+ 1.509045226130653e-07,
+ 1.7605527638190955e-07,
+ 2.012060301507537e-07,
+ 2.2635678391959805e-07,
+ 2.515075376884422e-07,
+ 2.7665829145728655e-07,
+ 3.018090452261305e-07,
+ 3.2695979899497484e-07,
+ 3.521105527638192e-07,
+ 3.7726130653266334e-07,
+ 4.024120603015075e-07,
+ 4.2756281407035185e-07,
+ 4.52713567839196e-07,
+ 4.778643216080401e-07,
+ 5.030150753768845e-07,
+ 5.281658291457286e-07,
+ 5.533165829145728e-07,
+ 5.784673366834169e-07,
+ 6.036180904522613e-07,
+ 6.287688442211056e-07,
+ 6.539195979899496e-07,
+ 6.790703517587939e-07,
+ 7.042211055276383e-07,
+ 7.293718592964822e-07,
+ 7.545226130653266e-07,
+ 7.796733668341709e-07,
+ 8.048241206030151e-07,
+ 8.301740083621068e-07,
+ 8.556888641201788e-07,
+ 8.813111124754158e-07,
+ 9.070202076003725e-07,
+ 9.32804275141358e-07,
+ 9.586553032153856e-07,
+ 9.845674100953497e-07,
+ 1.01053603858957e-06,
+ 1.0365575222592973e-06,
+ 1.0626288279788578e-06,
+ 1.0887473920755335e-06,
+ 1.1149110103560906e-06,
+ 1.1411177611608193e-06,
+ 1.16736594968478e-06,
+ 1.193654066554068e-06,
+ 1.2199807562803669e-06,
+ 1.2463447927558711e-06,
+ 1.2727450598880405e-06,
+ 1.2991805360656051e-06,
+ 1.325650281532873e-06,
+ 1.352153428007453e-06,
+ 1.3786891700534096e-06,
+ 1.4052567578456553e-06,
+ 1.4318554910497194e-06,
+ 1.4584847136050661e-06,
+ 1.4851438092473548e-06,
+ 1.5118321976402484e-06,
+ 1.538549331014054e-06,
+ 1.5652946912288755e-06,
+ 1.5920677871957232e-06,
+ 1.6188681526013705e-06,
+ 1.6456953438924398e-06,
+ 1.672548938481938e-06,
+ 1.6994285331476313e-06,
+ 1.7263337425966675e-06,
+ 1.7532641981748811e-06,
+ 1.7802195467025574e-06,
+ 1.8071994494211637e-06,
+ 1.8342035810377954e-06,
+ 1.8612316288559887e-06,
+ 1.8882832919831146e-06,
+ 1.915358280605889e-06,
+ 1.942456315326653e-06,
+ 1.969577126554017e-06,
+ 1.9967204539422897e-06,
+ 2.0238860458747588e-06,
+ 2.0510736589865288e-06,
+ 2.078283057723097e-06,
+ 2.1055140139312965e-06,
+ 2.1327663064796202e-06,
+ 2.1600397209052696e-06,
+ 2.18733404908555e-06,
+ 2.214649088931497e-06,
+ 2.2419846441018473e-06,
+ 2.2693405237356292e-06,
+ 2.296716542201859e-06,
+ 2.3241125188649593e-06,
+ 2.3515282778646443e-06,
+ 2.378963647909149e-06,
+ 2.4064184620807856e-06,
+ 2.4338925576528876e-06,
+ 2.4613857759173045e-06,
+ 2.4888979620216786e-06,
+ 2.516428964815798e-06,
+ 2.5439786367063862e-06,
+ 2.5715468335197477e-06,
+ 2.5991334143717214e-06,
+ 2.6267382415444553e-06,
+ 2.654361180369553e-06,
+ 2.6820020991171646e-06,
+ 2.709660868890646e-06,
+ 2.7373373635264315e-06,
+ 2.7650314594987936e-06,
+ 2.7927430358291765e-06,
+ 2.820471973999846e-06,
+ 2.848218157871574e-06,
+ 2.875981473605128e-06,
+ 2.9037618095863474e-06,
+ 2.9315590563545864e-06,
+ 2.959373106534339e-06,
+ 2.9872038547698705e-06,
+ 3.015051197662676e-06,
+ 3.042915033711618e-06,
+ 3.0707952632556034e-06,
+ 3.0986917884186447e-06,
+ 3.126604513057201e-06,
+ 3.1545333427096633e-06,
+ 3.1824781845478775e-06,
+ 3.210438947330603e-06,
+ 3.23841554135881e-06,
+ 3.2664078784327137e-06,
+ 3.2944158718104693e-06,
+ 3.3224394361684423e-06,
+ 3.3504784875629817e-06,
+ 3.3785329433936138e-06,
+ 3.4066027223676035e-06,
+ 3.4346877444657973e-06,
+ 3.462787930909726e-06,
+ 3.4909032041298583e-06,
+ 3.5190334877350003e-06,
+ 3.5471787064827577e-06,
+ 3.575338786251029e-06,
+ 3.603513654010475e-06,
+ 3.6317032377979335e-06,
+ 3.6599074666907237e-06,
+ 3.688126270781812e-06,
+ 3.7163595811558006e-06,
+ 3.744607329865704e-06,
+ 3.772869449910475e-06,
+ 3.80114587521326e-06,
+ 3.829436540600347e-06,
+ 3.857741381780773e-06,
+ 3.886060335326582e-06,
+ 3.9143933386536785e-06,
+ 3.942740330003286e-06,
+ 3.971101248423967e-06,
+ 3.999476033754181e-06,
+ 4.0278646266053704e-06,
+ 4.056266968345549e-06,
+ 4.0846830010833714e-06,
+ 4.113112667652673e-06,
+ 4.141555911597451e-06,
+ 4.1700126771572856e-06,
+ 4.198482909253172e-06,
+ 4.226966553473749e-06,
+ 4.255463556061927e-06,
+ 4.283973863901869e-06,
+ 4.312497424506353e-06,
+ 4.341034186004464e-06,
+ 4.369584097129627e-06,
+ 4.398147107207964e-06,
+ 4.426723166146958e-06,
+ 4.455312224424423e-06,
+ 4.483914233077761e-06,
+ 4.512529143693505e-06,
+ 4.5411569083971295e-06,
+ 4.569797479843123e-06,
+ 4.598450811205317e-06,
+ 4.6271168561674655e-06,
+ 4.6271168561674655e-06,
+ 4.598450811205317e-06,
+ 4.569797479843123e-06,
+ 4.5411569083971295e-06,
+ 4.512529143693505e-06,
+ 4.483914233077761e-06,
+ 4.455312224424423e-06,
+ 4.426723166146958e-06,
+ 4.398147107207964e-06,
+ 4.369584097129627e-06,
+ 4.341034186004464e-06,
+ 4.312497424506353e-06,
+ 4.283973863901869e-06,
+ 4.255463556061927e-06,
+ 4.226966553473749e-06,
+ 4.198482909253172e-06,
+ 4.1700126771572856e-06,
+ 4.141555911597451e-06,
+ 4.113112667652673e-06,
+ 4.0846830010833714e-06,
+ 4.056266968345549e-06,
+ 4.0278646266053704e-06,
+ 3.999476033754181e-06,
+ 3.971101248423967e-06,
+ 3.942740330003286e-06,
+ 3.9143933386536785e-06,
+ 3.886060335326582e-06,
+ 3.857741381780773e-06,
+ 3.829436540600347e-06,
+ 3.80114587521326e-06,
+ 3.772869449910475e-06,
+ 3.744607329865704e-06,
+ 3.7163595811558006e-06,
+ 3.688126270781812e-06,
+ 3.6599074666907237e-06,
+ 3.6317032377979335e-06,
+ 3.603513654010475e-06,
+ 3.575338786251029e-06,
+ 3.5471787064827577e-06,
+ 3.5190334877350003e-06,
+ 3.4909032041298583e-06,
+ 3.462787930909726e-06,
+ 3.4346877444657973e-06,
+ 3.4066027223676035e-06,
+ 3.3785329433936138e-06,
+ 3.3504784875629817e-06,
+ 3.3224394361684423e-06,
+ 3.2944158718104693e-06,
+ 3.2664078784327137e-06,
+ 3.23841554135881e-06,
+ 3.210438947330603e-06,
+ 3.1824781845478775e-06,
+ 3.1545333427096633e-06,
+ 3.126604513057201e-06,
+ 3.0986917884186447e-06,
+ 3.0707952632556034e-06,
+ 3.042915033711618e-06,
+ 3.015051197662676e-06,
+ 2.9872038547698705e-06,
+ 2.959373106534339e-06,
+ 2.9315590563545864e-06,
+ 2.9037618095863474e-06,
+ 2.875981473605128e-06,
+ 2.848218157871574e-06,
+ 2.820471973999846e-06,
+ 2.7927430358291765e-06,
+ 2.7650314594987936e-06,
+ 2.7373373635264315e-06,
+ 2.709660868890646e-06,
+ 2.6820020991171646e-06,
+ 2.654361180369553e-06,
+ 2.6267382415444553e-06,
+ 2.5991334143717214e-06,
+ 2.5715468335197477e-06,
+ 2.5439786367063862e-06,
+ 2.516428964815798e-06,
+ 2.4888979620216786e-06,
+ 2.4613857759173045e-06,
+ 2.4338925576528876e-06,
+ 2.4064184620807856e-06,
+ 2.378963647909149e-06,
+ 2.3515282778646443e-06,
+ 2.3241125188649593e-06,
+ 2.296716542201859e-06,
+ 2.2693405237356292e-06,
+ 2.2419846441018473e-06,
+ 2.214649088931497e-06,
+ 2.18733404908555e-06,
+ 2.1600397209052696e-06,
+ 2.1327663064796202e-06,
+ 2.1055140139312965e-06,
+ 2.078283057723097e-06,
+ 2.0510736589865288e-06,
+ 2.0238860458747588e-06,
+ 1.9967204539422897e-06,
+ 1.969577126554017e-06,
+ 1.942456315326653e-06,
+ 1.915358280605889e-06,
+ 1.8882832919831146e-06,
+ 1.8612316288559887e-06,
+ 1.8342035810377954e-06,
+ 1.8071994494211637e-06,
+ 1.7802195467025574e-06,
+ 1.7532641981748811e-06,
+ 1.7263337425966675e-06,
+ 1.6994285331476313e-06,
+ 1.672548938481938e-06,
+ 1.6456953438924398e-06,
+ 1.6188681526013705e-06,
+ 1.5920677871957232e-06,
+ 1.5652946912288755e-06,
+ 1.538549331014054e-06,
+ 1.5118321976402484e-06,
+ 1.4851438092473548e-06,
+ 1.4584847136050661e-06,
+ 1.4318554910497194e-06,
+ 1.4052567578456553e-06,
+ 1.3786891700534096e-06,
+ 1.352153428007453e-06,
+ 1.325650281532873e-06,
+ 1.2991805360656051e-06,
+ 1.2727450598880405e-06,
+ 1.2463447927558711e-06,
+ 1.2199807562803669e-06,
+ 1.193654066554068e-06,
+ 1.16736594968478e-06,
+ 1.1411177611608193e-06,
+ 1.1149110103560906e-06,
+ 1.0887473920755335e-06,
+ 1.0626288279788578e-06,
+ 1.0165575222592971e-06,
+ 1.01053603858957e-06,
+ 9.845674100953497e-07,
+ 9.586553032153856e-07,
+ 9.32804275141358e-07,
+ 9.070202076003725e-07,
+ 8.813111124754158e-07,
+ 8.556888641201788e-07,
+ 8.301740083621068e-07,
+ 8.048241206030151e-07,
+ 7.796733668341709e-07,
+ 7.545226130653266e-07,
+ 7.293718592964822e-07,
+ 7.042211055276383e-07,
+ 6.790703517587939e-07,
+ 6.539195979899496e-07,
+ 6.287688442211056e-07,
+ 6.036180904522613e-07,
+ 5.784673366834169e-07,
+ 5.533165829145728e-07,
+ 5.281658291457286e-07,
+ 5.030150753768845e-07,
+ 4.778643216080401e-07,
+ 4.52713567839196e-07,
+ 4.2756281407035185e-07,
+ 4.024120603015075e-07,
+ 3.7726130653266334e-07,
+ 3.521105527638192e-07,
+ 3.2695979899497484e-07,
+ 3.018090452261305e-07,
+ 2.7665829145728655e-07,
+ 2.515075376884422e-07,
+ 2.2635678391959805e-07,
+ 2.012060301507537e-07,
+ 1.7605527638190955e-07,
+ 1.509045226130653e-07,
+ 1.2575376884422104e-07,
+ 1.006030150753769e-07,
+ 7.545226130653265e-08,
+ 5.0301507537688396e-08,
+ 2.5150753768844145e-08,
+ 0.0,
+ -2.515075376884425e-08,
+ -5.03015075376885e-08,
+ -7.545226130653265e-08,
+ -1.006030150753769e-07,
+ -1.2575376884422115e-07,
+ -1.509045226130654e-07,
+ -1.7605527638190955e-07,
+ -2.012060301507538e-07,
+ -2.2635678391959805e-07,
+ -2.5150753768844225e-07,
+ -2.7665829145728644e-07,
+ -3.018090452261307e-07,
+ -3.2695979899497495e-07,
+ -3.5211055276381915e-07,
+ -3.7726130653266334e-07,
+ -4.0241206030150754e-07,
+ -4.275628140703518e-07,
+ -4.5271356783919604e-07,
+ -4.778643216080402e-07,
+ -5.030150753768845e-07,
+ -5.281658291457287e-07,
+ -5.533165829145729e-07,
+ -5.784673366834171e-07,
+ -6.036180904522614e-07,
+ -6.287688442211055e-07,
+ -6.539195979899498e-07,
+ -6.79070351758794e-07,
+ -7.042211055276383e-07
+ ]
+ },
+ "P22": {
+ "contact": {
+ "deviation_from_baseline": 23,
+ "fit_constant_line": 47,
+ "fit_constant_polynomial": 2,
+ "fit_line_polynomial": 0
+ },
+ "force": [
+ 5.196176045893285e-10,
+ 4.888656937303255e-10,
+ 4.5811378287132244e-10,
+ 4.273618720123194e-10,
+ 3.966099611533165e-10,
+ 3.6585805029431357e-10,
+ 3.3510613943531043e-10,
+ 3.043542285763075e-10,
+ 2.736023177173045e-10,
+ 2.428504068583015e-10,
+ 2.1209849599929853e-10,
+ 2.0099660886278917e-10,
+ 2.0617333437989965e-10,
+ 2.219472817941677e-10,
+ 2.4629104234323576e-10,
+ 2.780328857191796e-10,
+ 3.163822201674462e-10,
+ 3.6075865216994247e-10,
+ 4.1071248991613133e-10,
+ 4.658819387755996e-10,
+ 5.259676978034717e-10,
+ 5.907167904503003e-10,
+ 6.945158044356577e-10,
+ 8.026280708381722e-10,
+ 9.148841091638982e-10,
+ 1.0311329820223412e-09,
+ 1.1512391405818522e-09,
+ 1.2750799740619322e-09,
+ 1.4025438732084968e-09,
+ 1.5335286768946284e-09,
+ 1.667940409551055e-09,
+ 1.8056922429387186e-09,
+ 1.9467036334640005e-09,
+ 2.090899598618256e-09,
+ 2.2382101049542902e-09,
+ 2.3885695464186215e-09,
+ 2.541916296577144e-09,
+ 2.698192321796131e-09,
+ 2.8573428451063698e-09,
+ 3.0193160525181477e-09,
+ 3.184062835132557e-09,
+ 3.3515365616269275e-09,
+ 3.521692876663541e-09,
+ 3.6944895215429686e-09,
+ 3.869886174041972e-09,
+ 4.047844304875263e-09,
+ 4.228327048626221e-09,
+ 4.411299087323525e-09,
+ 4.59672654511383e-09,
+ 4.784576892706628e-09,
+ 4.9748188604555936e-09,
+ 5.167422359097849e-09,
+ 5.362358407304973e-09,
+ 5.559599065310953e-09,
+ 5.759117373977053e-09,
+ 5.960887298733965e-09,
+ 6.164883677910504e-09,
+ 6.371082175017168e-09,
+ 6.579459234603633e-09,
+ 6.78999204135319e-09,
+ 7.002658482115229e-09,
+ 7.217437110609837e-09,
+ 7.434307114567467e-09,
+ 7.65324828509188e-09,
+ 7.874240988056566e-09,
+ 8.097266137364349e-09,
+ 8.32230516991699e-09,
+ 8.549340022156693e-09,
+ 8.778353108054798e-09,
+ 9.00932729843493e-09,
+ 9.242245901528257e-09,
+ 9.477092644668093e-09,
+ 9.71385165703945e-09,
+ 9.95250745340652e-09,
+ 1.0193044918748064e-08,
+ 1.0435449293736555e-08,
+ 1.067970616100237e-08,
+ 1.0925801432129354e-08,
+ 1.1173721335332426e-08,
+ 1.1423452403771857e-08,
+ 1.167498146446263e-08,
+ 1.1928295627740398e-08,
+ 1.2183382277248655e-08,
+ 1.2440229060414456e-08,
+ 1.2698823879382418e-08,
+ 1.2959154882379032e-08,
+ 1.3221210455481431e-08,
+ 1.3484979214766505e-08,
+ 1.3750449998818105e-08,
+ 1.4017611861571613e-08,
+ 1.4286454065476567e-08,
+ 1.4556966074959385e-08,
+ 1.4829137550169534e-08,
+ 1.510295834099343e-08,
+ 1.5378418481321587e-08,
+ 1.5655508183555368e-08,
+ 1.5934217833340678e-08,
+ 1.6214537984516534e-08,
+ 1.6496459354267558e-08,
+ 1.6779972818469815e-08,
+ 1.7065069407220198e-08,
+ 1.7351740300540182e-08,
+ 1.7639976824245323e-08,
+ 1.7929770445972305e-08,
+ 1.8221112771355914e-08,
+ 1.8513995540348834e-08,
+ 1.8808410623677314e-08,
+ 1.9104350019426422e-08,
+ 1.94018058497489e-08,
+ 1.970077035769181e-08,
+ 2.0001235904135684e-08,
+ 2.0303194964841088e-08,
+ 2.060664012759781e-08,
+ 2.0911564089472076e-08,
+ 2.121795965414758e-08,
+ 2.1525819729356234e-08,
+ 2.183513732439471e-08,
+ 2.2145905547723252e-08,
+ 2.2458117604643196e-08,
+ 2.2771766795049905e-08,
+ 2.308684651125809e-08,
+ 2.3403350235896446e-08,
+ 2.3721271539868822e-08,
+ 2.4040604080379304e-08,
+ 2.4361341599018536e-08,
+ 2.468347791990898e-08,
+ 2.5007006947906765e-08,
+ 2.5331922666857826e-08,
+ 2.5658219137906414e-08,
+ 2.598589049785388e-08,
+ 2.6314930957565763e-08,
+ 2.66453348004255e-08,
+ 2.697709638083298e-08,
+ 2.7310210122746177e-08,
+ 2.764467051826444e-08,
+ 2.798047212625184e-08,
+ 2.831760957099915e-08,
+ 2.8656077540922996e-08,
+ 2.8995870787301066e-08,
+ 2.9336984123041844e-08,
+ 2.9679412421487862e-08,
+ 3.002315061525121e-08,
+ 3.036819369508022e-08,
+ 3.071453670875629e-08,
+ 3.106217476001975e-08,
+ 3.141110300752385e-08,
+ 3.176131666381595e-08,
+ 3.2112810994344944e-08,
+ 3.246558131649409e-08,
+ 3.281962299863842e-08,
+ 3.3174931459226e-08,
+ 3.353150216588203e-08,
+ 3.3889330634535415e-08,
+ 3.424841242856677e-08,
+ 3.460874315797738e-08,
+ 3.497031847857839e-08,
+ 3.533313409119955e-08,
+ 3.5697185740917e-08,
+ 3.606246921629954e-08,
+ 3.6428980348672624e-08,
+ 3.679671501139974e-08,
+ 3.7165669119180626e-08,
+ 3.753583862736568e-08,
+ 3.790721953128628e-08,
+ 3.827980786560042e-08,
+ 3.8653599703653177e-08,
+ 3.902859115685179e-08,
+ 3.940477837405459e-08,
+ 3.978215754097374e-08,
+ 4.0160724879591136e-08,
+ 4.0540476647587245e-08,
+ 4.092140913778237e-08,
+ 4.130351867759024e-08,
+ 4.168680162848317e-08,
+ 4.207125438546901e-08,
+ 4.2456873376579066e-08,
+ 4.2843655062366965e-08,
+ 4.3231595935418175e-08,
+ 4.3620692519869706e-08,
+ 4.401094137093985e-08,
+ 4.4402339074467797e-08,
+ 4.4794882246462554e-08,
+ 4.5188567532661274e-08,
+ 4.558339160809649e-08,
+ 4.5979351176672194e-08,
+ 4.6376442970748336e-08,
+ 4.67746637507338e-08,
+ 4.7174010304687306e-08,
+ 4.757447944792641e-08,
+ 4.797606802264409e-08,
+ 4.83787728975328e-08,
+ 4.878259096741594e-08,
+ 4.9187519152886475e-08,
+ 4.9593554399952325e-08,
+ 5.0000693679688834e-08,
+ 5.040893398789763e-08,
+ 5.0818272344772035e-08,
+ 5.1228705794568776e-08,
+ 5.1640231405285865e-08,
+ 5.205284626834643e-08,
+ 5.205284626834643e-08,
+ 5.1640231405285865e-08,
+ 5.1228705794568776e-08,
+ 5.0818272344772035e-08,
+ 5.040893398789763e-08,
+ 5.0000693679688834e-08,
+ 4.9593554399952325e-08,
+ 4.9187519152886475e-08,
+ 4.878259096741594e-08,
+ 4.83787728975328e-08,
+ 4.797606802264409e-08,
+ 4.757447944792641e-08,
+ 4.7174010304687306e-08,
+ 4.67746637507338e-08,
+ 4.6376442970748336e-08,
+ 4.5979351176672194e-08,
+ 4.558339160809649e-08,
+ 4.5188567532661274e-08,
+ 4.4794882246462554e-08,
+ 4.4402339074467797e-08,
+ 4.401094137093985e-08,
+ 4.3620692519869706e-08,
+ 4.3231595935418175e-08,
+ 4.2843655062366965e-08,
+ 4.2456873376579066e-08,
+ 4.207125438546901e-08,
+ 4.168680162848317e-08,
+ 4.130351867759024e-08,
+ 4.092140913778237e-08,
+ 4.0540476647587245e-08,
+ 4.0160724879591136e-08,
+ 3.978215754097374e-08,
+ 3.940477837405459e-08,
+ 3.902859115685179e-08,
+ 3.8653599703653177e-08,
+ 3.827980786560042e-08,
+ 3.790721953128628e-08,
+ 3.753583862736568e-08,
+ 3.7165669119180626e-08,
+ 3.679671501139974e-08,
+ 3.6428980348672624e-08,
+ 3.606246921629954e-08,
+ 3.5697185740917e-08,
+ 3.533313409119955e-08,
+ 3.497031847857839e-08,
+ 3.460874315797738e-08,
+ 3.424841242856677e-08,
+ 3.3889330634535415e-08,
+ 3.353150216588203e-08,
+ 3.3174931459226e-08,
+ 3.281962299863842e-08,
+ 3.246558131649409e-08,
+ 3.2112810994344944e-08,
+ 3.176131666381595e-08,
+ 3.141110300752385e-08,
+ 3.106217476001975e-08,
+ 3.071453670875629e-08,
+ 3.036819369508022e-08,
+ 3.002315061525121e-08,
+ 2.9679412421487862e-08,
+ 2.9336984123041844e-08,
+ 2.8995870787301066e-08,
+ 2.8656077540922996e-08,
+ 2.831760957099915e-08,
+ 2.798047212625184e-08,
+ 2.764467051826444e-08,
+ 2.7310210122746177e-08,
+ 2.697709638083298e-08,
+ 2.66453348004255e-08,
+ 2.6314930957565763e-08,
+ 2.598589049785388e-08,
+ 2.5658219137906414e-08,
+ 2.5331922666857826e-08,
+ 2.5007006947906765e-08,
+ 2.468347791990898e-08,
+ 2.4361341599018536e-08,
+ 2.4040604080379304e-08,
+ 2.3721271539868822e-08,
+ 2.3403350235896446e-08,
+ 2.308684651125809e-08,
+ 2.2771766795049905e-08,
+ 2.2458117604643196e-08,
+ 2.2145905547723252e-08,
+ 2.183513732439471e-08,
+ 2.1525819729356234e-08,
+ 2.121795965414758e-08,
+ 2.0911564089472076e-08,
+ 2.060664012759781e-08,
+ 2.0303194964841088e-08,
+ 2.0001235904135684e-08,
+ 1.970077035769181e-08,
+ 1.94018058497489e-08,
+ 1.9104350019426422e-08,
+ 1.8808410623677314e-08,
+ 1.8513995540348834e-08,
+ 1.8221112771355914e-08,
+ 1.7929770445972305e-08,
+ 1.7639976824245323e-08,
+ 1.7351740300540182e-08,
+ 1.7065069407220198e-08,
+ 1.6779972818469815e-08,
+ 1.6496459354267558e-08,
+ 1.6214537984516534e-08,
+ 1.5934217833340678e-08,
+ 1.5655508183555368e-08,
+ 1.5378418481321587e-08,
+ 1.510295834099343e-08,
+ 1.4829137550169534e-08,
+ 1.4556966074959385e-08,
+ 1.4286454065476567e-08,
+ 1.4017611861571613e-08,
+ 1.3750449998818105e-08,
+ 1.3484979214766505e-08,
+ 1.3221210455481431e-08,
+ 1.2959154882379032e-08,
+ 1.2698823879382418e-08,
+ 1.2440229060414456e-08,
+ 1.2183382277248655e-08,
+ 1.1928295627740398e-08,
+ 1.167498146446263e-08,
+ 1.1423452403771857e-08,
+ 1.1173721335332426e-08,
+ 1.0925801432129354e-08,
+ 1.067970616100237e-08,
+ 1.0435449293736555e-08,
+ 1.0193044918748064e-08,
+ 9.95250745340652e-09,
+ 9.71385165703945e-09,
+ 9.477092644668093e-09,
+ 9.242245901528257e-09,
+ 9.00932729843493e-09,
+ 8.778353108054798e-09,
+ 8.549340022156693e-09,
+ 8.32230516991699e-09,
+ 8.097266137364349e-09,
+ 7.874240988056566e-09,
+ 7.65324828509188e-09,
+ 7.434307114567467e-09,
+ 7.217437110609837e-09,
+ 7.002658482115229e-09,
+ 6.78999204135319e-09,
+ 6.579459234603633e-09,
+ 6.371082175017168e-09,
+ 6.164883677910504e-09,
+ 5.960887298733965e-09,
+ 5.759117373977053e-09,
+ 5.559599065310953e-09,
+ 5.362358407304973e-09,
+ 5.167422359097849e-09,
+ 4.9748188604555936e-09,
+ 4.784576892706628e-09,
+ 4.59672654511383e-09,
+ 4.411299087323525e-09,
+ 4.228327048626221e-09,
+ 4.047844304875263e-09,
+ 3.869886174041972e-09,
+ 3.6944895215429686e-09,
+ 3.521692876663541e-09,
+ 3.3515365616269275e-09,
+ 3.184062835132557e-09,
+ 3.0193160525181477e-09,
+ 2.8573428451063698e-09,
+ 2.698192321796131e-09,
+ 2.541916296577144e-09,
+ 2.3885695464186215e-09,
+ 2.2382101049542902e-09,
+ 2.090899598618256e-09,
+ 1.9467036334640005e-09,
+ 1.8056922429387186e-09,
+ 1.667940409551055e-09,
+ 1.5335286768946284e-09,
+ 1.4025438732084968e-09,
+ 1.2750799740619322e-09,
+ 1.1512391405818522e-09,
+ 1.0311329820223412e-09,
+ 9.148841091638982e-10,
+ 8.026280708381722e-10,
+ 6.945158044356577e-10,
+ 5.907167904503003e-10,
+ 4.914231948530826e-10,
+ 3.968554369673757e-10,
+ 3.072698840650056e-10,
+ 2.2296984871332413e-10,
+ 1.443219747872789e-10,
+ 7.178198145483933e-11,
+ 5.938041426653951e-12,
+ -5.240865699865565e-11,
+ -1.0207067845202234e-10,
+ -1.409934401888515e-10,
+ -1.6341940202767823e-10,
+ -1.659319648417486e-10,
+ -1.6844452765581895e-10,
+ -1.7095709046988931e-10,
+ -1.7346965328395968e-10,
+ -1.7598221609802993e-10,
+ -1.784947789121003e-10,
+ -1.8100734172617065e-10,
+ -1.8351990454024102e-10,
+ -1.8603246735431138e-10,
+ -1.8854503016838174e-10
+ ],
+ "height": [
+ 0.0,
+ 2.5125628140703518e-08,
+ 5.0251256281407036e-08,
+ 7.537688442211056e-08,
+ 1.0050251256281407e-07,
+ 1.2562814070351758e-07,
+ 1.5075376884422112e-07,
+ 1.7587939698492463e-07,
+ 2.0100502512562815e-07,
+ 2.2613065326633166e-07,
+ 2.5125628140703517e-07,
+ 2.763819095477387e-07,
+ 3.0150753768844224e-07,
+ 3.2663316582914573e-07,
+ 3.5175879396984927e-07,
+ 3.7688442211055275e-07,
+ 4.020100502512563e-07,
+ 4.2713567839195983e-07,
+ 4.522613065326633e-07,
+ 4.773869346733669e-07,
+ 5.025125628140703e-07,
+ 5.276381909547739e-07,
+ 5.527638190954774e-07,
+ 5.778894472361809e-07,
+ 6.030150753768845e-07,
+ 6.28140703517588e-07,
+ 6.532663316582915e-07,
+ 6.783919597989949e-07,
+ 7.035175879396985e-07,
+ 7.28643216080402e-07,
+ 7.537688442211055e-07,
+ 7.788944723618091e-07,
+ 8.040201005025126e-07,
+ 8.291457286432161e-07,
+ 8.542713567839197e-07,
+ 8.793969849246231e-07,
+ 9.045226130653266e-07,
+ 9.296482412060302e-07,
+ 9.547738693467337e-07,
+ 9.798994974874373e-07,
+ 1.0050251256281407e-06,
+ 1.0301507537688443e-06,
+ 1.0552763819095479e-06,
+ 1.0804020100502512e-06,
+ 1.1055276381909548e-06,
+ 1.1306532663316584e-06,
+ 1.1557788944723618e-06,
+ 1.1809045226130654e-06,
+ 1.206030150753769e-06,
+ 1.2311557788944724e-06,
+ 1.256281407035176e-06,
+ 1.2814070351758793e-06,
+ 1.306532663316583e-06,
+ 1.3316582914572865e-06,
+ 1.3567839195979899e-06,
+ 1.3819095477386935e-06,
+ 1.407035175879397e-06,
+ 1.4321608040201004e-06,
+ 1.457286432160804e-06,
+ 1.4824120603015076e-06,
+ 1.507537688442211e-06,
+ 1.5326633165829146e-06,
+ 1.5577889447236182e-06,
+ 1.5829145728643216e-06,
+ 1.6080402010050252e-06,
+ 1.6331658291457288e-06,
+ 1.6582914572864321e-06,
+ 1.6834170854271357e-06,
+ 1.7085427135678393e-06,
+ 1.7336683417085427e-06,
+ 1.7587939698492463e-06,
+ 1.7839195979899499e-06,
+ 1.8090452261306533e-06,
+ 1.8341708542713568e-06,
+ 1.8592964824120604e-06,
+ 1.8844221105527638e-06,
+ 1.9095477386934674e-06,
+ 1.9346733668341708e-06,
+ 1.9597989949748746e-06,
+ 1.984924623115578e-06,
+ 2.0100502512562813e-06,
+ 2.035175879396985e-06,
+ 2.0603015075376885e-06,
+ 2.085427135678392e-06,
+ 2.1105527638190957e-06,
+ 2.135678391959799e-06,
+ 2.1608040201005025e-06,
+ 2.1859296482412063e-06,
+ 2.2110552763819096e-06,
+ 2.236180904522613e-06,
+ 2.261306532663317e-06,
+ 2.28643216080402e-06,
+ 2.3115577889447236e-06,
+ 2.3366834170854274e-06,
+ 2.3618090452261308e-06,
+ 2.386934673366834e-06,
+ 2.412060301507538e-06,
+ 2.4371859296482413e-06,
+ 2.4623115577889447e-06,
+ 2.487437185929648e-06,
+ 2.512562814070352e-06,
+ 2.5376884422110553e-06,
+ 2.5628140703517587e-06,
+ 2.5879396984924625e-06,
+ 2.613065326633166e-06,
+ 2.6381909547738692e-06,
+ 2.663316582914573e-06,
+ 2.6884422110552764e-06,
+ 2.7135678391959798e-06,
+ 2.7386934673366836e-06,
+ 2.763819095477387e-06,
+ 2.7889447236180903e-06,
+ 2.814070351758794e-06,
+ 2.8391959798994975e-06,
+ 2.864321608040201e-06,
+ 2.8894472361809047e-06,
+ 2.914572864321608e-06,
+ 2.9396984924623115e-06,
+ 2.9648241206030153e-06,
+ 2.9899497487437186e-06,
+ 3.015075376884422e-06,
+ 3.040201005025126e-06,
+ 3.065326633165829e-06,
+ 3.0904522613065326e-06,
+ 3.1155778894472364e-06,
+ 3.1407035175879398e-06,
+ 3.165829145728643e-06,
+ 3.190954773869347e-06,
+ 3.2160804020100503e-06,
+ 3.2412060301507537e-06,
+ 3.2663316582914575e-06,
+ 3.291457286432161e-06,
+ 3.3165829145728643e-06,
+ 3.341708542713568e-06,
+ 3.3668341708542714e-06,
+ 3.391959798994975e-06,
+ 3.4170854271356786e-06,
+ 3.442211055276382e-06,
+ 3.4673366834170854e-06,
+ 3.492462311557789e-06,
+ 3.5175879396984926e-06,
+ 3.542713567839196e-06,
+ 3.5678391959798997e-06,
+ 3.592964824120603e-06,
+ 3.6180904522613065e-06,
+ 3.6432160804020103e-06,
+ 3.6683417085427137e-06,
+ 3.693467336683417e-06,
+ 3.718592964824121e-06,
+ 3.7437185929648243e-06,
+ 3.7688442211055276e-06,
+ 3.7939698492462314e-06,
+ 3.819095477386935e-06,
+ 3.844221105527638e-06,
+ 3.8693467336683416e-06,
+ 3.894472361809045e-06,
+ 3.919597989949749e-06,
+ 3.9447236180904526e-06,
+ 3.969849246231156e-06,
+ 3.994974874371859e-06,
+ 4.020100502512563e-06,
+ 4.045226130653266e-06,
+ 4.07035175879397e-06,
+ 4.095477386934674e-06,
+ 4.120603015075377e-06,
+ 4.1457286432160804e-06,
+ 4.170854271356784e-06,
+ 4.195979899497487e-06,
+ 4.221105527638191e-06,
+ 4.246231155778895e-06,
+ 4.271356783919598e-06,
+ 4.2964824120603016e-06,
+ 4.321608040201005e-06,
+ 4.346733668341708e-06,
+ 4.3718592964824125e-06,
+ 4.396984924623116e-06,
+ 4.422110552763819e-06,
+ 4.447236180904523e-06,
+ 4.472361809045226e-06,
+ 4.4974874371859294e-06,
+ 4.522613065326634e-06,
+ 4.547738693467337e-06,
+ 4.57286432160804e-06,
+ 4.597989949748744e-06,
+ 4.623115577889447e-06,
+ 4.6482412060301506e-06,
+ 4.673366834170855e-06,
+ 4.698492462311558e-06,
+ 4.7236180904522615e-06,
+ 4.748743718592965e-06,
+ 4.773869346733668e-06,
+ 4.798994974874372e-06,
+ 4.824120603015076e-06,
+ 4.849246231155779e-06,
+ 4.874371859296483e-06,
+ 4.899497487437186e-06,
+ 4.9246231155778894e-06,
+ 4.949748743718593e-06,
+ 4.974874371859296e-06,
+ 5e-06,
+ 5e-06,
+ 4.974874371859296e-06,
+ 4.949748743718593e-06,
+ 4.9246231155778894e-06,
+ 4.899497487437186e-06,
+ 4.874371859296483e-06,
+ 4.849246231155779e-06,
+ 4.824120603015076e-06,
+ 4.798994974874372e-06,
+ 4.773869346733668e-06,
+ 4.748743718592965e-06,
+ 4.7236180904522615e-06,
+ 4.698492462311558e-06,
+ 4.673366834170855e-06,
+ 4.6482412060301506e-06,
+ 4.623115577889447e-06,
+ 4.597989949748744e-06,
+ 4.57286432160804e-06,
+ 4.547738693467337e-06,
+ 4.522613065326634e-06,
+ 4.4974874371859294e-06,
+ 4.472361809045226e-06,
+ 4.447236180904523e-06,
+ 4.422110552763819e-06,
+ 4.396984924623116e-06,
+ 4.3718592964824125e-06,
+ 4.346733668341708e-06,
+ 4.321608040201005e-06,
+ 4.2964824120603016e-06,
+ 4.271356783919598e-06,
+ 4.246231155778895e-06,
+ 4.221105527638191e-06,
+ 4.195979899497487e-06,
+ 4.170854271356784e-06,
+ 4.1457286432160804e-06,
+ 4.120603015075377e-06,
+ 4.095477386934674e-06,
+ 4.07035175879397e-06,
+ 4.045226130653266e-06,
+ 4.020100502512563e-06,
+ 3.994974874371859e-06,
+ 3.969849246231156e-06,
+ 3.9447236180904526e-06,
+ 3.919597989949749e-06,
+ 3.894472361809045e-06,
+ 3.8693467336683416e-06,
+ 3.844221105527638e-06,
+ 3.819095477386935e-06,
+ 3.7939698492462314e-06,
+ 3.7688442211055276e-06,
+ 3.7437185929648243e-06,
+ 3.718592964824121e-06,
+ 3.693467336683417e-06,
+ 3.6683417085427137e-06,
+ 3.6432160804020103e-06,
+ 3.6180904522613065e-06,
+ 3.592964824120603e-06,
+ 3.5678391959798997e-06,
+ 3.542713567839196e-06,
+ 3.5175879396984926e-06,
+ 3.492462311557789e-06,
+ 3.4673366834170854e-06,
+ 3.442211055276382e-06,
+ 3.4170854271356786e-06,
+ 3.391959798994975e-06,
+ 3.3668341708542714e-06,
+ 3.341708542713568e-06,
+ 3.3165829145728643e-06,
+ 3.291457286432161e-06,
+ 3.2663316582914575e-06,
+ 3.2412060301507537e-06,
+ 3.2160804020100503e-06,
+ 3.190954773869347e-06,
+ 3.165829145728643e-06,
+ 3.1407035175879398e-06,
+ 3.1155778894472364e-06,
+ 3.0904522613065326e-06,
+ 3.065326633165829e-06,
+ 3.040201005025126e-06,
+ 3.015075376884422e-06,
+ 2.9899497487437186e-06,
+ 2.9648241206030153e-06,
+ 2.9396984924623115e-06,
+ 2.914572864321608e-06,
+ 2.8894472361809047e-06,
+ 2.864321608040201e-06,
+ 2.8391959798994975e-06,
+ 2.814070351758794e-06,
+ 2.7889447236180903e-06,
+ 2.763819095477387e-06,
+ 2.7386934673366836e-06,
+ 2.7135678391959798e-06,
+ 2.6884422110552764e-06,
+ 2.663316582914573e-06,
+ 2.6381909547738692e-06,
+ 2.613065326633166e-06,
+ 2.5879396984924625e-06,
+ 2.5628140703517587e-06,
+ 2.5376884422110553e-06,
+ 2.512562814070352e-06,
+ 2.487437185929648e-06,
+ 2.4623115577889447e-06,
+ 2.4371859296482413e-06,
+ 2.412060301507538e-06,
+ 2.386934673366834e-06,
+ 2.3618090452261308e-06,
+ 2.3366834170854274e-06,
+ 2.3115577889447236e-06,
+ 2.28643216080402e-06,
+ 2.261306532663317e-06,
+ 2.236180904522613e-06,
+ 2.2110552763819096e-06,
+ 2.1859296482412063e-06,
+ 2.1608040201005025e-06,
+ 2.135678391959799e-06,
+ 2.1105527638190957e-06,
+ 2.085427135678392e-06,
+ 2.0603015075376885e-06,
+ 2.035175879396985e-06,
+ 2.0100502512562813e-06,
+ 1.984924623115578e-06,
+ 1.9597989949748746e-06,
+ 1.9346733668341708e-06,
+ 1.9095477386934674e-06,
+ 1.8844221105527638e-06,
+ 1.8592964824120604e-06,
+ 1.8341708542713568e-06,
+ 1.8090452261306533e-06,
+ 1.7839195979899499e-06,
+ 1.7587939698492463e-06,
+ 1.7336683417085427e-06,
+ 1.7085427135678393e-06,
+ 1.6834170854271357e-06,
+ 1.6582914572864321e-06,
+ 1.6331658291457288e-06,
+ 1.6080402010050252e-06,
+ 1.5829145728643216e-06,
+ 1.5577889447236182e-06,
+ 1.5326633165829146e-06,
+ 1.507537688442211e-06,
+ 1.4824120603015076e-06,
+ 1.457286432160804e-06,
+ 1.4321608040201004e-06,
+ 1.407035175879397e-06,
+ 1.3819095477386935e-06,
+ 1.3567839195979899e-06,
+ 1.3316582914572865e-06,
+ 1.306532663316583e-06,
+ 1.2814070351758793e-06,
+ 1.256281407035176e-06,
+ 1.2311557788944724e-06,
+ 1.206030150753769e-06,
+ 1.1809045226130654e-06,
+ 1.1557788944723618e-06,
+ 1.1306532663316584e-06,
+ 1.1055276381909548e-06,
+ 1.0804020100502512e-06,
+ 1.0552763819095479e-06,
+ 1.0301507537688443e-06,
+ 1.0050251256281407e-06,
+ 9.798994974874373e-07,
+ 9.547738693467337e-07,
+ 9.296482412060302e-07,
+ 9.045226130653266e-07,
+ 8.793969849246231e-07,
+ 8.542713567839197e-07,
+ 8.291457286432161e-07,
+ 8.040201005025126e-07,
+ 7.788944723618091e-07,
+ 7.537688442211055e-07,
+ 7.28643216080402e-07,
+ 7.035175879396985e-07,
+ 6.783919597989949e-07,
+ 6.532663316582915e-07,
+ 6.28140703517588e-07,
+ 6.030150753768845e-07,
+ 5.778894472361809e-07,
+ 5.527638190954774e-07,
+ 5.276381909547739e-07,
+ 5.025125628140703e-07,
+ 4.773869346733669e-07,
+ 4.522613065326633e-07,
+ 4.2713567839195983e-07,
+ 4.020100502512563e-07,
+ 3.7688442211055275e-07,
+ 3.5175879396984927e-07,
+ 3.2663316582914573e-07,
+ 3.0150753768844224e-07,
+ 2.763819095477387e-07,
+ 2.5125628140703517e-07,
+ 2.2613065326633166e-07,
+ 2.0100502512562815e-07,
+ 1.7587939698492463e-07,
+ 1.5075376884422112e-07,
+ 1.2562814070351758e-07,
+ 1.0050251256281407e-07,
+ 7.537688442211056e-08,
+ 5.0251256281407036e-08,
+ 2.5125628140703518e-08,
+ 0.0
+ ],
+ "segment": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ "tip_position": [
+ -5.615944274415179e-07,
+ -5.364436736726736e-07,
+ -5.112929199038294e-07,
+ -4.861421661349851e-07,
+ -4.6099141236614094e-07,
+ -4.3584065859729674e-07,
+ -4.106899048284525e-07,
+ -3.855391510596083e-07,
+ -3.603883972907641e-07,
+ -3.3523764352191984e-07,
+ -3.1008688975307564e-07,
+ -2.847370019939838e-07,
+ -2.59222146235912e-07,
+ -2.3359989788067484e-07,
+ -2.0789080275571823e-07,
+ -1.8210673521473285e-07,
+ -1.5625570714070492e-07,
+ -1.3034360026074096e-07,
+ -1.0437497176652067e-07,
+ -7.835348809679343e-08,
+ -5.228218237723286e-08,
+ -2.6163618280557124e-08,
+ 0.0,
+ 2.6206750804728612e-08,
+ 5.245493932868949e-08,
+ 7.874305619797733e-08,
+ 1.05069745924276e-07,
+ 1.3143378239978024e-07,
+ 1.5783404953194945e-07,
+ 1.8426952570951426e-07,
+ 2.1073927117678202e-07,
+ 2.3724241765136225e-07,
+ 2.637781596973186e-07,
+ 2.9034574748956464e-07,
+ 3.169444806936286e-07,
+ 3.4357370324897537e-07,
+ 3.70232798891264e-07,
+ 3.969211872841576e-07,
+ 4.236383206579633e-07,
+ 4.503836808727847e-07,
+ 4.771567768396322e-07,
+ 5.039571422452795e-07,
+ 5.307843335363492e-07,
+ 5.57637928125847e-07,
+ 5.845175227915405e-07,
+ 6.114227322405771e-07,
+ 6.383531878187899e-07,
+ 6.653085363464666e-07,
+ 6.922884390650733e-07,
+ 7.192925706817046e-07,
+ 7.463206184998979e-07,
+ 7.733722816270238e-07,
+ 8.004472702497986e-07,
+ 8.27545304970562e-07,
+ 8.546661161979264e-07,
+ 8.818094435861991e-07,
+ 9.08975035518668e-07,
+ 9.36162648630438e-07,
+ 9.633720473670063e-07,
+ 9.906030035752055e-07,
+ 1.0178552961235292e-06,
+ 1.045128710549179e-06,
+ 1.0724230387294589e-06,
+ 1.0997380785754063e-06,
+ 1.1270736337457568e-06,
+ 1.1544295133795382e-06,
+ 1.181805531845768e-06,
+ 1.2092015085088686e-06,
+ 1.2366172675085533e-06,
+ 1.264052637553058e-06,
+ 1.2915074517246948e-06,
+ 1.3189815472967968e-06,
+ 1.3464747655612137e-06,
+ 1.3739869516655882e-06,
+ 1.4015179544597072e-06,
+ 1.4290676263502954e-06,
+ 1.456635823163657e-06,
+ 1.4842224040156302e-06,
+ 1.511827231188365e-06,
+ 1.5394501700134627e-06,
+ 1.5670910887610738e-06,
+ 1.594749858534555e-06,
+ 1.6224263531703412e-06,
+ 1.6501204491427024e-06,
+ 1.6778320254730858e-06,
+ 1.7055609636437552e-06,
+ 1.7333071475154826e-06,
+ 1.7610704632490373e-06,
+ 1.7888507992302567e-06,
+ 1.8166480459984952e-06,
+ 1.8444620961782486e-06,
+ 1.8722928444137802e-06,
+ 1.900140187306585e-06,
+ 1.9280040233555276e-06,
+ 1.955884252899513e-06,
+ 1.983780778062554e-06,
+ 2.0116935027011104e-06,
+ 2.0396223323535728e-06,
+ 2.067567174191786e-06,
+ 2.0955279369745125e-06,
+ 2.1235045310027195e-06,
+ 2.151496868076623e-06,
+ 2.179504861454378e-06,
+ 2.2075284258123517e-06,
+ 2.235567477206891e-06,
+ 2.263621933037523e-06,
+ 2.291691712011512e-06,
+ 2.3197767341097067e-06,
+ 2.3478769205536344e-06,
+ 2.3759921937737677e-06,
+ 2.4041224773789097e-06,
+ 2.432267696126667e-06,
+ 2.4604277758949384e-06,
+ 2.4886026436543843e-06,
+ 2.516792227441843e-06,
+ 2.544996456334633e-06,
+ 2.5732152604257214e-06,
+ 2.60144857079971e-06,
+ 2.6296963195096134e-06,
+ 2.6579584395543834e-06,
+ 2.6862348648571686e-06,
+ 2.7145255302442564e-06,
+ 2.7428303714246832e-06,
+ 2.7711493249704916e-06,
+ 2.7994823282975876e-06,
+ 2.8278293196471954e-06,
+ 2.8561902380678764e-06,
+ 2.8845650233980912e-06,
+ 2.9129536162492803e-06,
+ 2.9413559579894587e-06,
+ 2.9697719907272813e-06,
+ 2.9982016572965817e-06,
+ 3.0266449012413604e-06,
+ 3.0551016668011954e-06,
+ 3.0835718988970815e-06,
+ 3.1120555431176592e-06,
+ 3.140552545705836e-06,
+ 3.1690628535457777e-06,
+ 3.1975864141502617e-06,
+ 3.2261231756483735e-06,
+ 3.254673086773537e-06,
+ 3.2832360968518744e-06,
+ 3.3118121557908682e-06,
+ 3.3404012140683325e-06,
+ 3.36900322272167e-06,
+ 3.3976181333374146e-06,
+ 3.4262458980410393e-06,
+ 3.4548864694870326e-06,
+ 3.4835398008492278e-06,
+ 3.5122058458113745e-06,
+ 3.5408845585579537e-06,
+ 3.5695758937652182e-06,
+ 3.5982798065924552e-06,
+ 3.6269962526734722e-06,
+ 3.655725188108281e-06,
+ 3.684466569454995e-06,
+ 3.713220353721911e-06,
+ 3.741986498359789e-06,
+ 3.7707649612543172e-06,
+ 3.7995557007187513e-06,
+ 3.828358675486726e-06,
+ 3.8571738447052385e-06,
+ 3.8860011679277936e-06,
+ 3.914840605107703e-06,
+ 3.943692116591547e-06,
+ 3.972555663112779e-06,
+ 4.001431205785468e-06,
+ 4.030318706098199e-06,
+ 4.059218125908095e-06,
+ 4.088129427434973e-06,
+ 4.117052573255637e-06,
+ 4.145987526298292e-06,
+ 4.174934249837073e-06,
+ 4.203892707486706e-06,
+ 4.232862863197269e-06,
+ 4.261844681249073e-06,
+ 4.290838126247655e-06,
+ 4.319843163118871e-06,
+ 4.348859757104089e-06,
+ 4.3778878737554945e-06,
+ 4.406927478931478e-06,
+ 4.4359785387921285e-06,
+ 4.4650410197948196e-06,
+ 4.494114888689875e-06,
+ 4.523200112516335e-06,
+ 4.5522966585978e-06,
+ 4.581404494538359e-06,
+ 4.610523588218598e-06,
+ 4.639653907791692e-06,
+ 4.668795421679572e-06,
+ 4.697948098569162e-06,
+ 4.727111907408697e-06,
+ 4.756286817404107e-06,
+ 4.7854727980154685e-06,
+ 4.814669818953537e-06,
+ 4.843877850176329e-06,
+ 4.873096861885776e-06,
+ 4.902326824524447e-06,
+ 4.931567708772321e-06,
+ 4.960819485543631e-06,
+ 4.960819485543631e-06,
+ 4.931567708772321e-06,
+ 4.902326824524447e-06,
+ 4.873096861885776e-06,
+ 4.843877850176329e-06,
+ 4.814669818953537e-06,
+ 4.7854727980154685e-06,
+ 4.756286817404107e-06,
+ 4.727111907408697e-06,
+ 4.697948098569162e-06,
+ 4.668795421679572e-06,
+ 4.639653907791692e-06,
+ 4.610523588218598e-06,
+ 4.581404494538359e-06,
+ 4.5522966585978e-06,
+ 4.523200112516335e-06,
+ 4.494114888689875e-06,
+ 4.4650410197948196e-06,
+ 4.4359785387921285e-06,
+ 4.406927478931478e-06,
+ 4.3778878737554945e-06,
+ 4.348859757104089e-06,
+ 4.319843163118871e-06,
+ 4.290838126247655e-06,
+ 4.261844681249073e-06,
+ 4.232862863197269e-06,
+ 4.203892707486706e-06,
+ 4.174934249837073e-06,
+ 4.145987526298292e-06,
+ 4.117052573255637e-06,
+ 4.088129427434973e-06,
+ 4.059218125908095e-06,
+ 4.030318706098199e-06,
+ 4.001431205785468e-06,
+ 3.972555663112779e-06,
+ 3.943692116591547e-06,
+ 3.914840605107703e-06,
+ 3.8860011679277936e-06,
+ 3.8571738447052385e-06,
+ 3.828358675486726e-06,
+ 3.7995557007187513e-06,
+ 3.7707649612543172e-06,
+ 3.741986498359789e-06,
+ 3.713220353721911e-06,
+ 3.684466569454995e-06,
+ 3.655725188108281e-06,
+ 3.6269962526734722e-06,
+ 3.5982798065924552e-06,
+ 3.5695758937652182e-06,
+ 3.5408845585579537e-06,
+ 3.5122058458113745e-06,
+ 3.4835398008492278e-06,
+ 3.4548864694870326e-06,
+ 3.4262458980410393e-06,
+ 3.3976181333374146e-06,
+ 3.36900322272167e-06,
+ 3.3404012140683325e-06,
+ 3.3118121557908682e-06,
+ 3.2832360968518744e-06,
+ 3.254673086773537e-06,
+ 3.2261231756483735e-06,
+ 3.1975864141502617e-06,
+ 3.1690628535457777e-06,
+ 3.140552545705836e-06,
+ 3.1120555431176592e-06,
+ 3.0835718988970815e-06,
+ 3.0551016668011954e-06,
+ 3.0266449012413604e-06,
+ 2.9982016572965817e-06,
+ 2.9697719907272813e-06,
+ 2.9413559579894587e-06,
+ 2.9129536162492803e-06,
+ 2.8845650233980912e-06,
+ 2.8561902380678764e-06,
+ 2.8278293196471954e-06,
+ 2.7994823282975876e-06,
+ 2.7711493249704916e-06,
+ 2.7428303714246832e-06,
+ 2.7145255302442564e-06,
+ 2.6862348648571686e-06,
+ 2.6579584395543834e-06,
+ 2.6296963195096134e-06,
+ 2.60144857079971e-06,
+ 2.5732152604257214e-06,
+ 2.544996456334633e-06,
+ 2.516792227441843e-06,
+ 2.4886026436543843e-06,
+ 2.4604277758949384e-06,
+ 2.432267696126667e-06,
+ 2.4041224773789097e-06,
+ 2.3759921937737677e-06,
+ 2.3478769205536344e-06,
+ 2.3197767341097067e-06,
+ 2.291691712011512e-06,
+ 2.263621933037523e-06,
+ 2.235567477206891e-06,
+ 2.2075284258123517e-06,
+ 2.179504861454378e-06,
+ 2.151496868076623e-06,
+ 2.1235045310027195e-06,
+ 2.0955279369745125e-06,
+ 2.067567174191786e-06,
+ 2.0396223323535728e-06,
+ 2.0116935027011104e-06,
+ 1.983780778062554e-06,
+ 1.955884252899513e-06,
+ 1.9280040233555276e-06,
+ 1.900140187306585e-06,
+ 1.8722928444137802e-06,
+ 1.8444620961782486e-06,
+ 1.8166480459984952e-06,
+ 1.7888507992302567e-06,
+ 1.7610704632490373e-06,
+ 1.7333071475154826e-06,
+ 1.7055609636437552e-06,
+ 1.6778320254730858e-06,
+ 1.6501204491427024e-06,
+ 1.6224263531703412e-06,
+ 1.594749858534555e-06,
+ 1.5670910887610738e-06,
+ 1.5394501700134627e-06,
+ 1.511827231188365e-06,
+ 1.4842224040156302e-06,
+ 1.456635823163657e-06,
+ 1.4290676263502954e-06,
+ 1.4015179544597072e-06,
+ 1.3739869516655882e-06,
+ 1.3464747655612137e-06,
+ 1.3189815472967968e-06,
+ 1.2915074517246948e-06,
+ 1.264052637553058e-06,
+ 1.2366172675085533e-06,
+ 1.2092015085088686e-06,
+ 1.181805531845768e-06,
+ 1.1544295133795382e-06,
+ 1.1270736337457568e-06,
+ 1.0997380785754063e-06,
+ 1.0724230387294589e-06,
+ 1.045128710549179e-06,
+ 1.0178552961235292e-06,
+ 9.906030035752055e-07,
+ 9.633720473670063e-07,
+ 9.36162648630438e-07,
+ 9.08975035518668e-07,
+ 8.818094435861991e-07,
+ 8.546661161979264e-07,
+ 8.27545304970562e-07,
+ 8.004472702497986e-07,
+ 7.733722816270238e-07,
+ 7.463206184998979e-07,
+ 7.192925706817046e-07,
+ 6.922884390650733e-07,
+ 6.653085363464666e-07,
+ 6.383531878187899e-07,
+ 6.114227322405771e-07,
+ 5.845175227915405e-07,
+ 5.57637928125847e-07,
+ 5.307843335363492e-07,
+ 5.039571422452795e-07,
+ 4.771567768396322e-07,
+ 4.503836808727847e-07,
+ 4.236383206579633e-07,
+ 3.969211872841576e-07,
+ 3.70232798891264e-07,
+ 3.4357370324897537e-07,
+ 3.169444806936286e-07,
+ 2.9034574748956464e-07,
+ 2.637781596973186e-07,
+ 2.3724241765136225e-07,
+ 2.1073927117678202e-07,
+ 1.8426952570951426e-07,
+ 1.5783404953194945e-07,
+ 1.3143378239978024e-07,
+ 1.05069745924276e-07,
+ 7.874305619797733e-08,
+ 5.245493932868949e-08,
+ 2.6206750804728612e-08,
+ 0.0,
+ -2.6163618280557124e-08,
+ -5.228218237723286e-08,
+ -7.835348809679343e-08,
+ -1.0437497176652067e-07,
+ -1.3034360026074096e-07,
+ -1.5625570714070492e-07,
+ -1.8210673521473285e-07,
+ -2.0789080275571823e-07,
+ -2.3359989788067484e-07,
+ -2.59222146235912e-07,
+ -2.847370019939838e-07,
+ -3.1008688975307564e-07,
+ -3.3523764352191984e-07,
+ -3.603883972907641e-07,
+ -3.855391510596083e-07,
+ -4.106899048284525e-07,
+ -4.3584065859729674e-07,
+ -4.6099141236614094e-07,
+ -4.861421661349851e-07,
+ -5.112929199038294e-07,
+ -5.364436736726736e-07,
+ -5.615944274415179e-07
+ ]
+ },
+ "P25": {
+ "contact": {
+ "deviation_from_baseline": 28,
+ "fit_constant_line": 27,
+ "fit_constant_polynomial": 25,
+ "fit_line_polynomial": 25
+ },
+ "force": [
+ 1.6959798994974876e-07,
+ 1.6959798994974876e-07,
+ 1.6959798994974887e-07,
+ 1.6959798994974887e-07,
+ 1.6959798994974887e-07,
+ 1.6959798994974892e-07,
+ 1.6959798994974892e-07,
+ 1.6959798994974876e-07,
+ 1.695979899497487e-07,
+ 1.695979899497487e-07,
+ 1.695979899497487e-07,
+ 1.695979899497488e-07,
+ 1.695979899497488e-07,
+ 1.695979899497488e-07,
+ 1.6959798994974887e-07,
+ 1.6959798994974887e-07,
+ 1.6959798994974887e-07,
+ 1.6959798994974892e-07,
+ 1.6959798994974892e-07,
+ 1.695979899497488e-07,
+ 1.695979899497487e-07,
+ 1.695979899497487e-07,
+ 1.695979899497487e-07,
+ 1.695979899497487e-07,
+ 1.695979899497488e-07,
+ 1.695979899497488e-07,
+ 1.6959798994974887e-07,
+ 1.6959798994974887e-07,
+ 1.8216080402010056e-07,
+ 1.9472361809045225e-07,
+ 2.0728643216080415e-07,
+ 2.1984924623115584e-07,
+ 2.3241206030150774e-07,
+ 2.4497487437185943e-07,
+ 2.575376884422111e-07,
+ 2.701005025125628e-07,
+ 2.826633165829147e-07,
+ 2.952261306532664e-07,
+ 3.077889447236183e-07,
+ 3.2035175879397e-07,
+ 3.329145728643217e-07,
+ 3.4547738693467337e-07,
+ 3.5804020100502527e-07,
+ 3.7060301507537696e-07,
+ 3.8316582914572886e-07,
+ 3.9572864321608055e-07,
+ 4.0829145728643224e-07,
+ 4.2085427135678393e-07,
+ 4.3341708542713583e-07,
+ 4.459798994974875e-07,
+ 4.585427135678394e-07,
+ 4.711055276381911e-07,
+ 4.836683417085428e-07,
+ 4.962311557788945e-07,
+ 5.087939698492462e-07,
+ 5.213567839195981e-07,
+ 5.3391959798995e-07,
+ 5.464824120603017e-07,
+ 5.590452261306534e-07,
+ 5.71608040201005e-07,
+ 5.841708542713567e-07,
+ 5.967535817407335e-07,
+ 6.093528060100081e-07,
+ 6.219627695389991e-07,
+ 6.345814177449619e-07,
+ 6.472075631925278e-07,
+ 6.598404046933977e-07,
+ 6.724793540748618e-07,
+ 6.851239556177513e-07,
+ 6.977738426781912e-07,
+ 7.104287119436145e-07,
+ 7.230883070467494e-07,
+ 7.357524075682723e-07,
+ 7.484208213422125e-07,
+ 7.610933788880763e-07,
+ 7.737699292684722e-07,
+ 7.864503369345696e-07,
+ 7.99134479275587e-07,
+ 8.118222446822719e-07,
+ 8.245135309934954e-07,
+ 8.372082442336895e-07,
+ 8.499062975746146e-07,
+ 8.626076104726779e-07,
+ 8.753121079453698e-07,
+ 8.880197199592433e-07,
+ 9.00730380908245e-07,
+ 9.134440291659415e-07,
+ 9.261606066986984e-07,
+ 9.388800587295462e-07,
+ 9.516023334444958e-07,
+ 9.64327381734648e-07,
+ 9.770551569686801e-07,
+ 9.897856147912543e-07,
+ 1.0025187129436714e-06,
+ 1.0152544111037081e-06,
+ 1.027992670742079e-06,
+ 1.0407334549933675e-06,
+ 1.0534767285396026e-06,
+ 1.0662224575049302e-06,
+ 1.0789706093600605e-06,
+ 1.0917211528353476e-06,
+ 1.1044740578415274e-06,
+ 1.1172292953972724e-06,
+ 1.1299868375628165e-06,
+ 1.1427466573790202e-06,
+ 1.1555087288113147e-06,
+ 1.1682730266980287e-06,
+ 1.1810395267026731e-06,
+ 1.193808205269797e-06,
+ 1.2065790395840844e-06,
+ 1.219352007532384e-06,
+ 1.232127087668416e-06,
+ 1.2449042591799117e-06,
+ 1.2576835018579739e-06,
+ 1.2704647960684762e-06,
+ 1.2832481227253219e-06,
+ 1.2960334632654124e-06,
+ 1.3088207996251895e-06,
+ 1.3216101142186252e-06,
+ 1.334401389916543e-06,
+ 1.347194610027174e-06,
+ 1.3599897582778516e-06,
+ 1.3727868187977605e-06,
+ 1.3855857761016653e-06,
+ 1.398386615074545e-06,
+ 1.4111893209570707e-06,
+ 1.4239938793318743e-06,
+ 1.4368002761105394e-06,
+ 1.4496084975212802e-06,
+ 1.4624185300972572e-06,
+ 1.4752303606654855e-06,
+ 1.488043976336301e-06,
+ 1.5008593644933468e-06,
+ 1.5136765127840503e-06,
+ 1.5264954091105557e-06,
+ 1.53931604162109e-06,
+ 1.5521383987017303e-06,
+ 1.5649624689685532e-06,
+ 1.5777882412601422e-06,
+ 1.590615704630434e-06,
+ 1.6034448483418765e-06,
+ 1.616275661858897e-06,
+ 1.6291081348416446e-06,
+ 1.641942257140006e-06,
+ 1.654778018787872e-06,
+ 1.6676154099976435e-06,
+ 1.6804544211549661e-06,
+ 1.69329504281368e-06,
+ 1.7061372656909686e-06,
+ 1.7189810806627086e-06,
+ 1.7318264787589966e-06,
+ 1.7446734511598547e-06,
+ 1.7575219891910973e-06,
+ 1.770372084320362e-06,
+ 1.7832237281532833e-06,
+ 1.7960769124298137e-06,
+ 1.8089316290206801e-06,
+ 1.8217878699239666e-06,
+ 1.8346456272618268e-06,
+ 1.8475048932773073e-06,
+ 1.8603656603312887e-06,
+ 1.873227920899532e-06,
+ 1.8860916675698272e-06,
+ 1.8989568930392388e-06,
+ 1.911823590111452e-06,
+ 1.9246917516941984e-06,
+ 1.9375613707967744e-06,
+ 1.9504324405276405e-06,
+ 1.963304954092098e-06,
+ 1.9761789047900424e-06,
+ 1.9890542860137886e-06,
+ 2.0019310912459645e-06,
+ 2.014809314057474e-06,
+ 2.027688948105522e-06,
+ 2.0405699871317e-06,
+ 2.053452424960128e-06,
+ 2.0663362554956633e-06,
+ 2.079221472722152e-06,
+ 2.092108070700738e-06,
+ 2.104996043568223e-06,
+ 2.1178853855354734e-06,
+ 2.1307760908858705e-06,
+ 2.1436681539738155e-06,
+ 2.1565615692232665e-06,
+ 2.1694563311263225e-06,
+ 2.182352434241847e-06,
+ 2.195249873194133e-06,
+ 2.2081486426715948e-06,
+ 2.22104873742551e-06,
+ 2.2339501522687887e-06,
+ 2.2468528820747723e-06,
+ 2.2597569217760735e-06,
+ 2.2726622663634394e-06,
+ 2.2855689108846538e-06,
+ 2.2984768504434546e-06,
+ 2.311386080198496e-06,
+ 2.3242965953623255e-06,
+ 2.3372083912003926e-06,
+ 2.350121463030079e-06,
+ 2.3630358062197624e-06,
+ 2.3630358062197624e-06,
+ 2.350121463030079e-06,
+ 2.3372083912003926e-06,
+ 2.3242965953623255e-06,
+ 2.311386080198496e-06,
+ 2.2984768504434546e-06,
+ 2.2855689108846538e-06,
+ 2.2726622663634394e-06,
+ 2.2597569217760735e-06,
+ 2.2468528820747723e-06,
+ 2.2339501522687887e-06,
+ 2.22104873742551e-06,
+ 2.2081486426715948e-06,
+ 2.195249873194133e-06,
+ 2.182352434241847e-06,
+ 2.1694563311263225e-06,
+ 2.1565615692232665e-06,
+ 2.1436681539738155e-06,
+ 2.1307760908858705e-06,
+ 2.1178853855354734e-06,
+ 2.104996043568223e-06,
+ 2.092108070700738e-06,
+ 2.079221472722152e-06,
+ 2.0663362554956633e-06,
+ 2.053452424960128e-06,
+ 2.0405699871317e-06,
+ 2.027688948105522e-06,
+ 2.014809314057474e-06,
+ 2.0019310912459645e-06,
+ 1.9890542860137886e-06,
+ 1.9761789047900424e-06,
+ 1.963304954092098e-06,
+ 1.9504324405276405e-06,
+ 1.9375613707967744e-06,
+ 1.9246917516941984e-06,
+ 1.911823590111452e-06,
+ 1.8989568930392388e-06,
+ 1.8860916675698272e-06,
+ 1.873227920899532e-06,
+ 1.8603656603312887e-06,
+ 1.8475048932773073e-06,
+ 1.8346456272618268e-06,
+ 1.8217878699239666e-06,
+ 1.8089316290206801e-06,
+ 1.7960769124298137e-06,
+ 1.7832237281532833e-06,
+ 1.770372084320362e-06,
+ 1.7575219891910973e-06,
+ 1.7446734511598547e-06,
+ 1.7318264787589966e-06,
+ 1.7189810806627086e-06,
+ 1.7061372656909686e-06,
+ 1.69329504281368e-06,
+ 1.6804544211549661e-06,
+ 1.6676154099976435e-06,
+ 1.654778018787872e-06,
+ 1.641942257140006e-06,
+ 1.6291081348416446e-06,
+ 1.616275661858897e-06,
+ 1.6034448483418765e-06,
+ 1.590615704630434e-06,
+ 1.5777882412601422e-06,
+ 1.5649624689685532e-06,
+ 1.5521383987017303e-06,
+ 1.53931604162109e-06,
+ 1.5264954091105557e-06,
+ 1.5136765127840503e-06,
+ 1.5008593644933468e-06,
+ 1.488043976336301e-06,
+ 1.4752303606654855e-06,
+ 1.4624185300972572e-06,
+ 1.4496084975212802e-06,
+ 1.4368002761105394e-06,
+ 1.4239938793318743e-06,
+ 1.4111893209570707e-06,
+ 1.398386615074545e-06,
+ 1.3855857761016653e-06,
+ 1.3727868187977605e-06,
+ 1.3599897582778516e-06,
+ 1.347194610027174e-06,
+ 1.334401389916543e-06,
+ 1.3216101142186252e-06,
+ 1.3088207996251895e-06,
+ 1.2960334632654124e-06,
+ 1.2832481227253219e-06,
+ 1.2704647960684762e-06,
+ 1.2576835018579739e-06,
+ 1.2449042591799117e-06,
+ 1.232127087668416e-06,
+ 1.219352007532384e-06,
+ 1.2065790395840844e-06,
+ 1.193808205269797e-06,
+ 1.1810395267026731e-06,
+ 1.1682730266980287e-06,
+ 1.1555087288113147e-06,
+ 1.1427466573790202e-06,
+ 1.1299868375628165e-06,
+ 1.1172292953972724e-06,
+ 1.1044740578415274e-06,
+ 1.0917211528353476e-06,
+ 1.0789706093600605e-06,
+ 1.0662224575049302e-06,
+ 1.0534767285396026e-06,
+ 1.0407334549933675e-06,
+ 1.027992670742079e-06,
+ 1.0152544111037081e-06,
+ 1.0025187129436714e-06,
+ 9.897856147912543e-07,
+ 9.770551569686801e-07,
+ 9.64327381734648e-07,
+ 9.516023334444958e-07,
+ 9.388800587295462e-07,
+ 9.261606066986984e-07,
+ 9.134440291659415e-07,
+ 9.00730380908245e-07,
+ 8.880197199592433e-07,
+ 8.753121079453698e-07,
+ 8.626076104726779e-07,
+ 8.499062975746146e-07,
+ 8.372082442336895e-07,
+ 8.245135309934954e-07,
+ 8.118222446822719e-07,
+ 7.99134479275587e-07,
+ 7.864503369345696e-07,
+ 7.737699292684722e-07,
+ 7.610933788880763e-07,
+ 7.484208213422125e-07,
+ 7.357524075682723e-07,
+ 7.230883070467494e-07,
+ 7.104287119436145e-07,
+ 6.977738426781912e-07,
+ 6.851239556177513e-07,
+ 6.724793540748618e-07,
+ 6.598404046933977e-07,
+ 6.472075631925278e-07,
+ 6.345814177449619e-07,
+ 6.219627695389991e-07,
+ 6.093528060100081e-07,
+ 5.967535817407335e-07,
+ 5.841708542713567e-07,
+ 5.71608040201005e-07,
+ 5.590452261306534e-07,
+ 5.464824120603017e-07,
+ 5.3391959798995e-07,
+ 5.213567839195981e-07,
+ 5.087939698492462e-07,
+ 4.962311557788945e-07,
+ 4.836683417085428e-07,
+ 4.711055276381911e-07,
+ 4.585427135678394e-07,
+ 4.459798994974875e-07,
+ 4.3341708542713583e-07,
+ 4.2085427135678393e-07,
+ 4.0829145728643224e-07,
+ 3.9572864321608055e-07,
+ 3.8316582914572886e-07,
+ 3.7060301507537696e-07,
+ 3.5804020100502527e-07,
+ 3.4547738693467337e-07,
+ 3.329145728643217e-07,
+ 3.2035175879397e-07,
+ 3.077889447236183e-07,
+ 2.952261306532664e-07,
+ 2.826633165829147e-07,
+ 2.701005025125628e-07,
+ 2.575376884422111e-07,
+ 2.4497487437185943e-07,
+ 2.3241206030150774e-07,
+ 2.1984924623115584e-07,
+ 2.0728643216080415e-07,
+ 1.9472361809045225e-07,
+ 1.8216080402010056e-07,
+ 1.6959798994974887e-07,
+ 1.5703517587939718e-07,
+ 1.4447236180904528e-07,
+ 1.319095477386936e-07,
+ 1.1934673366834169e-07,
+ 1.0678391959799e-07,
+ 9.422110552763831e-08,
+ 8.165829145728662e-08,
+ 6.909547738693472e-08,
+ 5.6532663316583027e-08,
+ 4.396984924623134e-08,
+ 3.1407035175879436e-08,
+ 1.8844221105527746e-08,
+ 6.2814070351760566e-09,
+ -6.281407035175845e-09,
+ -1.8844221105527534e-08,
+ -3.1407035175879224e-08,
+ -4.3969849246231125e-08,
+ -5.6532663316582815e-08,
+ -6.90954773869345e-08,
+ -8.16582914572864e-08,
+ -9.42211055276381e-08,
+ -1.0678391959798979e-07,
+ -1.1934673366834169e-07,
+ -1.3190954773869338e-07,
+ -1.4447236180904507e-07,
+ -1.5703517587939697e-07,
+ -1.6959798994974866e-07
+ ],
+ "height": [
+ 0.0,
+ 2.5125628140703518e-08,
+ 5.0251256281407036e-08,
+ 7.537688442211056e-08,
+ 1.0050251256281407e-07,
+ 1.2562814070351758e-07,
+ 1.5075376884422112e-07,
+ 1.7587939698492463e-07,
+ 2.0100502512562815e-07,
+ 2.2613065326633166e-07,
+ 2.5125628140703517e-07,
+ 2.763819095477387e-07,
+ 3.0150753768844224e-07,
+ 3.2663316582914573e-07,
+ 3.5175879396984927e-07,
+ 3.7688442211055275e-07,
+ 4.020100502512563e-07,
+ 4.2713567839195983e-07,
+ 4.522613065326633e-07,
+ 4.773869346733669e-07,
+ 5.025125628140703e-07,
+ 5.276381909547739e-07,
+ 5.527638190954774e-07,
+ 5.778894472361809e-07,
+ 6.030150753768845e-07,
+ 6.28140703517588e-07,
+ 6.532663316582915e-07,
+ 6.783919597989949e-07,
+ 7.035175879396985e-07,
+ 7.28643216080402e-07,
+ 7.537688442211055e-07,
+ 7.788944723618091e-07,
+ 8.040201005025126e-07,
+ 8.291457286432161e-07,
+ 8.542713567839197e-07,
+ 8.793969849246231e-07,
+ 9.045226130653266e-07,
+ 9.296482412060302e-07,
+ 9.547738693467337e-07,
+ 9.798994974874373e-07,
+ 1.0050251256281407e-06,
+ 1.0301507537688443e-06,
+ 1.0552763819095479e-06,
+ 1.0804020100502512e-06,
+ 1.1055276381909548e-06,
+ 1.1306532663316584e-06,
+ 1.1557788944723618e-06,
+ 1.1809045226130654e-06,
+ 1.206030150753769e-06,
+ 1.2311557788944724e-06,
+ 1.256281407035176e-06,
+ 1.2814070351758793e-06,
+ 1.306532663316583e-06,
+ 1.3316582914572865e-06,
+ 1.3567839195979899e-06,
+ 1.3819095477386935e-06,
+ 1.407035175879397e-06,
+ 1.4321608040201004e-06,
+ 1.457286432160804e-06,
+ 1.4824120603015076e-06,
+ 1.507537688442211e-06,
+ 1.5326633165829146e-06,
+ 1.5577889447236182e-06,
+ 1.5829145728643216e-06,
+ 1.6080402010050252e-06,
+ 1.6331658291457288e-06,
+ 1.6582914572864321e-06,
+ 1.6834170854271357e-06,
+ 1.7085427135678393e-06,
+ 1.7336683417085427e-06,
+ 1.7587939698492463e-06,
+ 1.7839195979899499e-06,
+ 1.8090452261306533e-06,
+ 1.8341708542713568e-06,
+ 1.8592964824120604e-06,
+ 1.8844221105527638e-06,
+ 1.9095477386934674e-06,
+ 1.9346733668341708e-06,
+ 1.9597989949748746e-06,
+ 1.984924623115578e-06,
+ 2.0100502512562813e-06,
+ 2.035175879396985e-06,
+ 2.0603015075376885e-06,
+ 2.085427135678392e-06,
+ 2.1105527638190957e-06,
+ 2.135678391959799e-06,
+ 2.1608040201005025e-06,
+ 2.1859296482412063e-06,
+ 2.2110552763819096e-06,
+ 2.236180904522613e-06,
+ 2.261306532663317e-06,
+ 2.28643216080402e-06,
+ 2.3115577889447236e-06,
+ 2.3366834170854274e-06,
+ 2.3618090452261308e-06,
+ 2.386934673366834e-06,
+ 2.412060301507538e-06,
+ 2.4371859296482413e-06,
+ 2.4623115577889447e-06,
+ 2.487437185929648e-06,
+ 2.512562814070352e-06,
+ 2.5376884422110553e-06,
+ 2.5628140703517587e-06,
+ 2.5879396984924625e-06,
+ 2.613065326633166e-06,
+ 2.6381909547738692e-06,
+ 2.663316582914573e-06,
+ 2.6884422110552764e-06,
+ 2.7135678391959798e-06,
+ 2.7386934673366836e-06,
+ 2.763819095477387e-06,
+ 2.7889447236180903e-06,
+ 2.814070351758794e-06,
+ 2.8391959798994975e-06,
+ 2.864321608040201e-06,
+ 2.8894472361809047e-06,
+ 2.914572864321608e-06,
+ 2.9396984924623115e-06,
+ 2.9648241206030153e-06,
+ 2.9899497487437186e-06,
+ 3.015075376884422e-06,
+ 3.040201005025126e-06,
+ 3.065326633165829e-06,
+ 3.0904522613065326e-06,
+ 3.1155778894472364e-06,
+ 3.1407035175879398e-06,
+ 3.165829145728643e-06,
+ 3.190954773869347e-06,
+ 3.2160804020100503e-06,
+ 3.2412060301507537e-06,
+ 3.2663316582914575e-06,
+ 3.291457286432161e-06,
+ 3.3165829145728643e-06,
+ 3.341708542713568e-06,
+ 3.3668341708542714e-06,
+ 3.391959798994975e-06,
+ 3.4170854271356786e-06,
+ 3.442211055276382e-06,
+ 3.4673366834170854e-06,
+ 3.492462311557789e-06,
+ 3.5175879396984926e-06,
+ 3.542713567839196e-06,
+ 3.5678391959798997e-06,
+ 3.592964824120603e-06,
+ 3.6180904522613065e-06,
+ 3.6432160804020103e-06,
+ 3.6683417085427137e-06,
+ 3.693467336683417e-06,
+ 3.718592964824121e-06,
+ 3.7437185929648243e-06,
+ 3.7688442211055276e-06,
+ 3.7939698492462314e-06,
+ 3.819095477386935e-06,
+ 3.844221105527638e-06,
+ 3.8693467336683416e-06,
+ 3.894472361809045e-06,
+ 3.919597989949749e-06,
+ 3.9447236180904526e-06,
+ 3.969849246231156e-06,
+ 3.994974874371859e-06,
+ 4.020100502512563e-06,
+ 4.045226130653266e-06,
+ 4.07035175879397e-06,
+ 4.095477386934674e-06,
+ 4.120603015075377e-06,
+ 4.1457286432160804e-06,
+ 4.170854271356784e-06,
+ 4.195979899497487e-06,
+ 4.221105527638191e-06,
+ 4.246231155778895e-06,
+ 4.271356783919598e-06,
+ 4.2964824120603016e-06,
+ 4.321608040201005e-06,
+ 4.346733668341708e-06,
+ 4.3718592964824125e-06,
+ 4.396984924623116e-06,
+ 4.422110552763819e-06,
+ 4.447236180904523e-06,
+ 4.472361809045226e-06,
+ 4.4974874371859294e-06,
+ 4.522613065326634e-06,
+ 4.547738693467337e-06,
+ 4.57286432160804e-06,
+ 4.597989949748744e-06,
+ 4.623115577889447e-06,
+ 4.6482412060301506e-06,
+ 4.673366834170855e-06,
+ 4.698492462311558e-06,
+ 4.7236180904522615e-06,
+ 4.748743718592965e-06,
+ 4.773869346733668e-06,
+ 4.798994974874372e-06,
+ 4.824120603015076e-06,
+ 4.849246231155779e-06,
+ 4.874371859296483e-06,
+ 4.899497487437186e-06,
+ 4.9246231155778894e-06,
+ 4.949748743718593e-06,
+ 4.974874371859296e-06,
+ 5e-06,
+ 5e-06,
+ 4.974874371859296e-06,
+ 4.949748743718593e-06,
+ 4.9246231155778894e-06,
+ 4.899497487437186e-06,
+ 4.874371859296483e-06,
+ 4.849246231155779e-06,
+ 4.824120603015076e-06,
+ 4.798994974874372e-06,
+ 4.773869346733668e-06,
+ 4.748743718592965e-06,
+ 4.7236180904522615e-06,
+ 4.698492462311558e-06,
+ 4.673366834170855e-06,
+ 4.6482412060301506e-06,
+ 4.623115577889447e-06,
+ 4.597989949748744e-06,
+ 4.57286432160804e-06,
+ 4.547738693467337e-06,
+ 4.522613065326634e-06,
+ 4.4974874371859294e-06,
+ 4.472361809045226e-06,
+ 4.447236180904523e-06,
+ 4.422110552763819e-06,
+ 4.396984924623116e-06,
+ 4.3718592964824125e-06,
+ 4.346733668341708e-06,
+ 4.321608040201005e-06,
+ 4.2964824120603016e-06,
+ 4.271356783919598e-06,
+ 4.246231155778895e-06,
+ 4.221105527638191e-06,
+ 4.195979899497487e-06,
+ 4.170854271356784e-06,
+ 4.1457286432160804e-06,
+ 4.120603015075377e-06,
+ 4.095477386934674e-06,
+ 4.07035175879397e-06,
+ 4.045226130653266e-06,
+ 4.020100502512563e-06,
+ 3.994974874371859e-06,
+ 3.969849246231156e-06,
+ 3.9447236180904526e-06,
+ 3.919597989949749e-06,
+ 3.894472361809045e-06,
+ 3.8693467336683416e-06,
+ 3.844221105527638e-06,
+ 3.819095477386935e-06,
+ 3.7939698492462314e-06,
+ 3.7688442211055276e-06,
+ 3.7437185929648243e-06,
+ 3.718592964824121e-06,
+ 3.693467336683417e-06,
+ 3.6683417085427137e-06,
+ 3.6432160804020103e-06,
+ 3.6180904522613065e-06,
+ 3.592964824120603e-06,
+ 3.5678391959798997e-06,
+ 3.542713567839196e-06,
+ 3.5175879396984926e-06,
+ 3.492462311557789e-06,
+ 3.4673366834170854e-06,
+ 3.442211055276382e-06,
+ 3.4170854271356786e-06,
+ 3.391959798994975e-06,
+ 3.3668341708542714e-06,
+ 3.341708542713568e-06,
+ 3.3165829145728643e-06,
+ 3.291457286432161e-06,
+ 3.2663316582914575e-06,
+ 3.2412060301507537e-06,
+ 3.2160804020100503e-06,
+ 3.190954773869347e-06,
+ 3.165829145728643e-06,
+ 3.1407035175879398e-06,
+ 3.1155778894472364e-06,
+ 3.0904522613065326e-06,
+ 3.065326633165829e-06,
+ 3.040201005025126e-06,
+ 3.015075376884422e-06,
+ 2.9899497487437186e-06,
+ 2.9648241206030153e-06,
+ 2.9396984924623115e-06,
+ 2.914572864321608e-06,
+ 2.8894472361809047e-06,
+ 2.864321608040201e-06,
+ 2.8391959798994975e-06,
+ 2.814070351758794e-06,
+ 2.7889447236180903e-06,
+ 2.763819095477387e-06,
+ 2.7386934673366836e-06,
+ 2.7135678391959798e-06,
+ 2.6884422110552764e-06,
+ 2.663316582914573e-06,
+ 2.6381909547738692e-06,
+ 2.613065326633166e-06,
+ 2.5879396984924625e-06,
+ 2.5628140703517587e-06,
+ 2.5376884422110553e-06,
+ 2.512562814070352e-06,
+ 2.487437185929648e-06,
+ 2.4623115577889447e-06,
+ 2.4371859296482413e-06,
+ 2.412060301507538e-06,
+ 2.386934673366834e-06,
+ 2.3618090452261308e-06,
+ 2.3366834170854274e-06,
+ 2.3115577889447236e-06,
+ 2.28643216080402e-06,
+ 2.261306532663317e-06,
+ 2.236180904522613e-06,
+ 2.2110552763819096e-06,
+ 2.1859296482412063e-06,
+ 2.1608040201005025e-06,
+ 2.135678391959799e-06,
+ 2.1105527638190957e-06,
+ 2.085427135678392e-06,
+ 2.0603015075376885e-06,
+ 2.035175879396985e-06,
+ 2.0100502512562813e-06,
+ 1.984924623115578e-06,
+ 1.9597989949748746e-06,
+ 1.9346733668341708e-06,
+ 1.9095477386934674e-06,
+ 1.8844221105527638e-06,
+ 1.8592964824120604e-06,
+ 1.8341708542713568e-06,
+ 1.8090452261306533e-06,
+ 1.7839195979899499e-06,
+ 1.7587939698492463e-06,
+ 1.7336683417085427e-06,
+ 1.7085427135678393e-06,
+ 1.6834170854271357e-06,
+ 1.6582914572864321e-06,
+ 1.6331658291457288e-06,
+ 1.6080402010050252e-06,
+ 1.5829145728643216e-06,
+ 1.5577889447236182e-06,
+ 1.5326633165829146e-06,
+ 1.507537688442211e-06,
+ 1.4824120603015076e-06,
+ 1.457286432160804e-06,
+ 1.4321608040201004e-06,
+ 1.407035175879397e-06,
+ 1.3819095477386935e-06,
+ 1.3567839195979899e-06,
+ 1.3316582914572865e-06,
+ 1.306532663316583e-06,
+ 1.2814070351758793e-06,
+ 1.256281407035176e-06,
+ 1.2311557788944724e-06,
+ 1.206030150753769e-06,
+ 1.1809045226130654e-06,
+ 1.1557788944723618e-06,
+ 1.1306532663316584e-06,
+ 1.1055276381909548e-06,
+ 1.0804020100502512e-06,
+ 1.0552763819095479e-06,
+ 1.0301507537688443e-06,
+ 1.0050251256281407e-06,
+ 9.798994974874373e-07,
+ 9.547738693467337e-07,
+ 9.296482412060302e-07,
+ 9.045226130653266e-07,
+ 8.793969849246231e-07,
+ 8.542713567839197e-07,
+ 8.291457286432161e-07,
+ 8.040201005025126e-07,
+ 7.788944723618091e-07,
+ 7.537688442211055e-07,
+ 7.28643216080402e-07,
+ 7.035175879396985e-07,
+ 6.783919597989949e-07,
+ 6.532663316582915e-07,
+ 6.28140703517588e-07,
+ 6.030150753768845e-07,
+ 5.778894472361809e-07,
+ 5.527638190954774e-07,
+ 5.276381909547739e-07,
+ 5.025125628140703e-07,
+ 4.773869346733669e-07,
+ 4.522613065326633e-07,
+ 4.2713567839195983e-07,
+ 4.020100502512563e-07,
+ 3.7688442211055275e-07,
+ 3.5175879396984927e-07,
+ 3.2663316582914573e-07,
+ 3.0150753768844224e-07,
+ 2.763819095477387e-07,
+ 2.5125628140703517e-07,
+ 2.2613065326633166e-07,
+ 2.0100502512562815e-07,
+ 1.7587939698492463e-07,
+ 1.5075376884422112e-07,
+ 1.2562814070351758e-07,
+ 1.0050251256281407e-07,
+ 7.537688442211056e-08,
+ 5.0251256281407036e-08,
+ 2.5125628140703518e-08,
+ 0.0
+ ],
+ "segment": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ "tip_position": [
+ -4.22110552763819e-06,
+ -4.0703517587939695e-06,
+ -3.9195979899497475e-06,
+ -3.768844221105527e-06,
+ -3.618090452261307e-06,
+ -3.467336683417085e-06,
+ -3.3165829145728647e-06,
+ -3.1658291457286427e-06,
+ -3.015075376884419e-06,
+ -2.8643216080401988e-06,
+ -2.7135678391959785e-06,
+ -2.5628140703517565e-06,
+ -2.4120603015075363e-06,
+ -2.261306532663316e-06,
+ -2.110552763819094e-06,
+ -1.9597989949748737e-06,
+ -1.8090452261306535e-06,
+ -1.6582914572864315e-06,
+ -1.5075376884422112e-06,
+ -1.3567839195979893e-06,
+ -1.2060301507537656e-06,
+ -1.0552763819095453e-06,
+ -9.04522613065325e-07,
+ -7.537688442211048e-07,
+ -6.030150753768828e-07,
+ -4.522613065326625e-07,
+ -3.0150753768844055e-07,
+ -1.5075376884422028e-07,
+ 0.0,
+ 1.5075376884422028e-07,
+ 3.0150753768844224e-07,
+ 4.522613065326642e-07,
+ 6.030150753768879e-07,
+ 7.537688442211082e-07,
+ 9.045226130653284e-07,
+ 1.055276381909547e-06,
+ 1.2060301507537707e-06,
+ 1.356783919597991e-06,
+ 1.5075376884422146e-06,
+ 1.6582914572864349e-06,
+ 1.8090452261306552e-06,
+ 1.9597989949748754e-06,
+ 2.110552763819099e-06,
+ 2.2613065326633194e-06,
+ 2.4120603015075396e-06,
+ 2.56281407035176e-06,
+ 2.71356783919598e-06,
+ 2.8643216080402005e-06,
+ 3.015075376884424e-06,
+ 3.1658291457286444e-06,
+ 3.3165829145728647e-06,
+ 3.467336683417085e-06,
+ 3.6180904522613052e-06,
+ 3.7688442211055255e-06,
+ 3.919597989949746e-06,
+ 4.0703517587939695e-06,
+ 4.221105527638193e-06,
+ 4.371859296482413e-06,
+ 4.522613065326634e-06,
+ 4.673366834170854e-06,
+ 4.824120603015074e-06,
+ 4.9750735058495435e-06,
+ 5.126191376682998e-06,
+ 5.27741664011361e-06,
+ 5.428728750313941e-06,
+ 5.580115832930303e-06,
+ 5.731569876079707e-06,
+ 5.8830849980350515e-06,
+ 6.034656641604648e-06,
+ 6.186281140349753e-06,
+ 6.337955461144687e-06,
+ 6.489677040316741e-06,
+ 6.641443673672672e-06,
+ 6.793253439552777e-06,
+ 6.9451046431521184e-06,
+ 7.0969957750967816e-06,
+ 7.2489254798984605e-06,
+ 7.400892531449337e-06,
+ 7.552895813656889e-06,
+ 7.704934304909827e-06,
+ 7.857007065452472e-06,
+ 8.009113227002426e-06,
+ 8.161251984123763e-06,
+ 8.313422586991385e-06,
+ 8.465624335270826e-06,
+ 8.617856572901544e-06,
+ 8.770118683619211e-06,
+ 8.922410087087486e-06,
+ 9.074730235536668e-06,
+ 9.22707861082687e-06,
+ 9.379454721869094e-06,
+ 9.531858102350118e-06,
+ 9.684288308716564e-06,
+ 9.836744918381438e-06,
+ 9.98922752812251e-06,
+ 1.014173575264692e-05,
+ 1.0294269223300508e-05,
+ 1.0446827586903564e-05,
+ 1.0599410504697542e-05,
+ 1.0752017651389549e-05,
+ 1.0904648714283125e-05,
+ 1.1057303392485625e-05,
+ 1.1209981396183777e-05,
+ 1.1362682445979924e-05,
+ 1.1515406272282664e-05,
+ 1.1668152614746313e-05,
+ 1.1820921221754154e-05,
+ 1.1973711849941301e-05,
+ 1.2126524263753246e-05,
+ 1.2279358235036823e-05,
+ 1.2432213542660521e-05,
+ 1.2585089972161544e-05,
+ 1.2737987315417209e-05,
+ 1.2890905370338533e-05,
+ 1.3043843940584261e-05,
+ 1.3196802835293419e-05,
+ 1.3349781868835028e-05,
+ 1.3502780860573503e-05,
+ 1.3655799634648565e-05,
+ 1.3808838019768446e-05,
+ 1.3961895849015459e-05,
+ 1.4114972959662936e-05,
+ 1.4268069193002728e-05,
+ 1.442118439418248e-05,
+ 1.4574318412051982e-05,
+ 1.4727471099017941e-05,
+ 1.488064231090668e-05,
+ 1.5033831906834037e-05,
+ 1.518703974908215e-05,
+ 1.5340265702982618e-05,
+ 1.5493509636805605e-05,
+ 1.5646771421654466e-05,
+ 1.580005093136563e-05,
+ 1.5953348042413365e-05,
+ 1.6106662633819124e-05,
+ 1.6259994587065173e-05,
+ 1.641334378601228e-05,
+ 1.656671011682121e-05,
+ 1.6720093467877802e-05,
+ 1.6873493729721426e-05,
+ 1.7026910794976557e-05,
+ 1.718034455828746e-05,
+ 1.733379491625564e-05,
+ 1.748726176737996e-05,
+ 1.7640745011999325e-05,
+ 1.7794244552237747e-05,
+ 1.7947760291951672e-05,
+ 1.8101292136679514e-05,
+ 1.8254839993593107e-05,
+ 1.8408403771451208e-05,
+ 1.856198338055479e-05,
+ 1.871557873270408e-05,
+ 1.8869189741157206e-05,
+ 1.902281632059056e-05,
+ 1.9176458387060474e-05,
+ 1.9330115857966483e-05,
+ 1.9483788652015848e-05,
+ 1.963747668918942e-05,
+ 1.979117989070872e-05,
+ 1.9944898179004232e-05,
+ 2.0098631477684748e-05,
+ 2.0252379711507884e-05,
+ 2.0406142806351536e-05,
+ 2.0559920689186353e-05,
+ 2.0713713288049187e-05,
+ 2.0867520532017353e-05,
+ 2.102134235118382e-05,
+ 2.1175178676633188e-05,
+ 2.1329029440418465e-05,
+ 2.148289457553862e-05,
+ 2.1636774015916777e-05,
+ 2.1790667696379238e-05,
+ 2.194457555263505e-05,
+ 2.2098497521256233e-05,
+ 2.225243353965871e-05,
+ 2.2406383546083696e-05,
+ 2.256034747957975e-05,
+ 2.271432527998534e-05,
+ 2.2868316887911912e-05,
+ 2.302232224472746e-05,
+ 2.3176341292540666e-05,
+ 2.333037397418534e-05,
+ 2.3484420233205494e-05,
+ 2.3638480013840707e-05,
+ 2.3792553261011967e-05,
+ 2.3946639920307923e-05,
+ 2.4100739937971486e-05,
+ 2.42548532608868e-05,
+ 2.440897983656666e-05,
+ 2.4563119613140148e-05,
+ 2.4717272539340685e-05,
+ 2.48714385644944e-05,
+ 2.502561763850877e-05,
+ 2.517980971186161e-05,
+ 2.5334014735590323e-05,
+ 2.5488232661281446e-05,
+ 2.5642463441060446e-05,
+ 2.5796707027581814e-05,
+ 2.5950963374019387e-05,
+ 2.610523243405692e-05,
+ 2.610523243405692e-05,
+ 2.5950963374019387e-05,
+ 2.5796707027581814e-05,
+ 2.5642463441060446e-05,
+ 2.5488232661281446e-05,
+ 2.5334014735590323e-05,
+ 2.517980971186161e-05,
+ 2.502561763850877e-05,
+ 2.48714385644944e-05,
+ 2.4717272539340685e-05,
+ 2.4563119613140148e-05,
+ 2.440897983656666e-05,
+ 2.42548532608868e-05,
+ 2.4100739937971486e-05,
+ 2.3946639920307923e-05,
+ 2.3792553261011967e-05,
+ 2.3638480013840707e-05,
+ 2.3484420233205494e-05,
+ 2.333037397418534e-05,
+ 2.3176341292540666e-05,
+ 2.302232224472746e-05,
+ 2.2868316887911912e-05,
+ 2.271432527998534e-05,
+ 2.256034747957975e-05,
+ 2.2406383546083696e-05,
+ 2.225243353965871e-05,
+ 2.2098497521256233e-05,
+ 2.194457555263505e-05,
+ 2.1790667696379238e-05,
+ 2.1636774015916777e-05,
+ 2.148289457553862e-05,
+ 2.1329029440418465e-05,
+ 2.1175178676633188e-05,
+ 2.102134235118382e-05,
+ 2.0867520532017353e-05,
+ 2.0713713288049187e-05,
+ 2.0559920689186353e-05,
+ 2.0406142806351536e-05,
+ 2.0252379711507884e-05,
+ 2.0098631477684748e-05,
+ 1.9944898179004232e-05,
+ 1.979117989070872e-05,
+ 1.963747668918942e-05,
+ 1.9483788652015848e-05,
+ 1.9330115857966483e-05,
+ 1.9176458387060474e-05,
+ 1.902281632059056e-05,
+ 1.8869189741157206e-05,
+ 1.871557873270408e-05,
+ 1.856198338055479e-05,
+ 1.8408403771451208e-05,
+ 1.8254839993593107e-05,
+ 1.8101292136679514e-05,
+ 1.7947760291951672e-05,
+ 1.7794244552237747e-05,
+ 1.7640745011999325e-05,
+ 1.748726176737996e-05,
+ 1.733379491625564e-05,
+ 1.718034455828746e-05,
+ 1.7026910794976557e-05,
+ 1.6873493729721426e-05,
+ 1.6720093467877802e-05,
+ 1.656671011682121e-05,
+ 1.641334378601228e-05,
+ 1.6259994587065173e-05,
+ 1.6106662633819124e-05,
+ 1.5953348042413365e-05,
+ 1.580005093136563e-05,
+ 1.5646771421654466e-05,
+ 1.5493509636805605e-05,
+ 1.5340265702982618e-05,
+ 1.518703974908215e-05,
+ 1.5033831906834037e-05,
+ 1.488064231090668e-05,
+ 1.4727471099017941e-05,
+ 1.4574318412051982e-05,
+ 1.442118439418248e-05,
+ 1.4268069193002728e-05,
+ 1.4114972959662936e-05,
+ 1.3961895849015459e-05,
+ 1.3808838019768446e-05,
+ 1.3655799634648565e-05,
+ 1.3502780860573503e-05,
+ 1.3349781868835028e-05,
+ 1.3196802835293419e-05,
+ 1.3043843940584261e-05,
+ 1.2890905370338533e-05,
+ 1.2737987315417209e-05,
+ 1.2585089972161544e-05,
+ 1.2432213542660521e-05,
+ 1.2279358235036823e-05,
+ 1.2126524263753246e-05,
+ 1.1973711849941301e-05,
+ 1.1820921221754154e-05,
+ 1.1668152614746313e-05,
+ 1.1515406272282664e-05,
+ 1.1362682445979924e-05,
+ 1.1209981396183777e-05,
+ 1.1057303392485625e-05,
+ 1.0904648714283125e-05,
+ 1.0752017651389549e-05,
+ 1.0599410504697542e-05,
+ 1.0446827586903564e-05,
+ 1.0294269223300508e-05,
+ 1.014173575264692e-05,
+ 9.98922752812251e-06,
+ 9.836744918381438e-06,
+ 9.684288308716564e-06,
+ 9.531858102350118e-06,
+ 9.379454721869094e-06,
+ 9.22707861082687e-06,
+ 9.074730235536668e-06,
+ 8.922410087087486e-06,
+ 8.770118683619211e-06,
+ 8.617856572901544e-06,
+ 8.465624335270826e-06,
+ 8.313422586991385e-06,
+ 8.161251984123763e-06,
+ 8.009113227002426e-06,
+ 7.857007065452472e-06,
+ 7.704934304909827e-06,
+ 7.552895813656889e-06,
+ 7.400892531449337e-06,
+ 7.2489254798984605e-06,
+ 7.0969957750967816e-06,
+ 6.9451046431521184e-06,
+ 6.793253439552777e-06,
+ 6.641443673672672e-06,
+ 6.489677040316741e-06,
+ 6.337955461144687e-06,
+ 6.186281140349753e-06,
+ 6.034656641604648e-06,
+ 5.8830849980350515e-06,
+ 5.731569876079707e-06,
+ 5.580115832930303e-06,
+ 5.428728750313941e-06,
+ 5.27741664011361e-06,
+ 5.126191376682998e-06,
+ 4.9750735058495435e-06,
+ 4.824120603015074e-06,
+ 4.673366834170854e-06,
+ 4.522613065326634e-06,
+ 4.371859296482413e-06,
+ 4.221105527638193e-06,
+ 4.0703517587939695e-06,
+ 3.919597989949746e-06,
+ 3.7688442211055255e-06,
+ 3.6180904522613052e-06,
+ 3.467336683417085e-06,
+ 3.3165829145728647e-06,
+ 3.1658291457286444e-06,
+ 3.015075376884424e-06,
+ 2.8643216080402005e-06,
+ 2.71356783919598e-06,
+ 2.56281407035176e-06,
+ 2.4120603015075396e-06,
+ 2.2613065326633194e-06,
+ 2.110552763819099e-06,
+ 1.9597989949748754e-06,
+ 1.8090452261306552e-06,
+ 1.6582914572864349e-06,
+ 1.5075376884422146e-06,
+ 1.356783919597991e-06,
+ 1.2060301507537707e-06,
+ 1.055276381909547e-06,
+ 9.045226130653284e-07,
+ 7.537688442211082e-07,
+ 6.030150753768879e-07,
+ 4.522613065326642e-07,
+ 3.0150753768844224e-07,
+ 1.5075376884422028e-07,
+ 0.0,
+ -1.5075376884422028e-07,
+ -3.0150753768844055e-07,
+ -4.522613065326625e-07,
+ -6.030150753768828e-07,
+ -7.537688442211048e-07,
+ -9.04522613065325e-07,
+ -1.0552763819095453e-06,
+ -1.2060301507537656e-06,
+ -1.3567839195979893e-06,
+ -1.5075376884422112e-06,
+ -1.6582914572864315e-06,
+ -1.8090452261306535e-06,
+ -1.9597989949748737e-06,
+ -2.110552763819094e-06,
+ -2.261306532663316e-06,
+ -2.4120603015075363e-06,
+ -2.5628140703517565e-06,
+ -2.7135678391959785e-06,
+ -2.8643216080401988e-06,
+ -3.015075376884419e-06,
+ -3.1658291457286427e-06,
+ -3.3165829145728647e-06,
+ -3.467336683417085e-06,
+ -3.618090452261307e-06,
+ -3.768844221105527e-06,
+ -3.9195979899497475e-06,
+ -4.0703517587939695e-06,
+ -4.22110552763819e-06
+ ]
+ },
+ "P26": {
+ "contact": {
+ "deviation_from_baseline": 28,
+ "fit_constant_line": 83,
+ "fit_constant_polynomial": 52,
+ "fit_line_polynomial": 51
+ },
+ "force": [
+ 3.391959798994994e-14,
+ 3.391959798994974e-14,
+ 3.391959798994994e-14,
+ 3.3919597989949766e-14,
+ 3.391959798994997e-14,
+ 3.3919597989949785e-14,
+ 3.3919597989949987e-14,
+ 3.391959798994979e-14,
+ 3.391959798995001e-14,
+ 3.3919597989949816e-14,
+ 3.391959798995003e-14,
+ 3.3919597989949835e-14,
+ 3.3919597989950043e-14,
+ 3.3919597989949854e-14,
+ 3.391959798995006e-14,
+ 3.391959798994987e-14,
+ 3.391959798995008e-14,
+ 3.3919597989949886e-14,
+ 3.3919597989950106e-14,
+ 3.391959798994991e-14,
+ 3.391959798994973e-14,
+ 3.3919597989949923e-14,
+ 3.391959798994974e-14,
+ 3.391959798994994e-14,
+ 3.391959798994975e-14,
+ 3.391959798994997e-14,
+ 3.391959798994977e-14,
+ 3.391959798994999e-14,
+ 3.6432160804020153e-14,
+ 3.894472361809072e-14,
+ 4.145728643216088e-14,
+ 4.396984924623144e-14,
+ 4.64824120603016e-14,
+ 4.899497487437217e-14,
+ 5.150753768844233e-14,
+ 5.402010050251289e-14,
+ 5.653266331658305e-14,
+ 5.904522613065362e-14,
+ 6.155778894472378e-14,
+ 6.407035175879434e-14,
+ 6.65829145728645e-14,
+ 6.909547738693466e-14,
+ 7.160804020100523e-14,
+ 7.412060301507539e-14,
+ 7.663316582914595e-14,
+ 7.914572864321611e-14,
+ 8.165829145728668e-14,
+ 8.417085427135684e-14,
+ 8.66834170854274e-14,
+ 8.919597989949756e-14,
+ 9.170854271356813e-14,
+ 9.422110552763829e-14,
+ 9.673366834170885e-14,
+ 9.924623115577901e-14,
+ 1.0175879396984957e-13,
+ 1.0427135678391974e-13,
+ 1.067839195979903e-13,
+ 1.0929648241206046e-13,
+ 1.1180904522613062e-13,
+ 1.1432160804020118e-13,
+ 1.1683417085427135e-13,
+ 2.003274575842475e-11,
+ 5.6445457243997767e-11,
+ 1.0359742844610796e-10,
+ 1.5943407662016136e-10,
+ 2.2276796639709082e-10,
+ 2.9279790947827367e-10,
+ 3.6893573315306275e-10,
+ 4.5072571825348834e-10,
+ 5.378012209046018e-10,
+ 6.298589285390525e-10,
+ 7.266424738850142e-10,
+ 8.279314376191149e-10,
+ 9.335336537703735e-10,
+ 1.0432796418448431e-09,
+ 1.1570184644520295e-09,
+ 1.2746145727602848e-09,
+ 1.395945355989108e-09,
+ 1.520899204884417e-09,
+ 1.6493739583192924e-09,
+ 1.7812756407244618e-09,
+ 1.91651742386087e-09,
+ 2.0550187641348948e-09,
+ 2.196704679037894e-09,
+ 2.341505135122673e-09,
+ 2.489354526335747e-09,
+ 2.6401912262430127e-09,
+ 2.793957201210745e-09,
+ 2.9505976742697263e-09,
+ 3.1100608314302475e-09,
+ 3.2722975637934026e-09,
+ 3.437261240036515e-09,
+ 3.604907504821872e-09,
+ 3.775194099450046e-09,
+ 3.948080701697792e-09,
+ 4.123528782279825e-09,
+ 4.301501475779529e-09,
+ 4.4819634642255775e-09,
+ 4.664880871764622e-09,
+ 4.850221169106165e-09,
+ 5.037953086603876e-09,
+ 5.2280465349948755e-09,
+ 5.4204725329507405e-09,
+ 5.6152031407054665e-09,
+ 5.8122113991203105e-09,
+ 6.011471273625964e-09,
+ 6.212957602551249e-09,
+ 6.416646049406658e-09,
+ 6.6225130587418625e-09,
+ 6.830535815240165e-09,
+ 7.040692205750948e-09,
+ 7.252960783994298e-09,
+ 7.467320737700675e-09,
+ 7.68375185797383e-09,
+ 7.902234510687258e-09,
+ 8.122749609743786e-09,
+ 8.34527859204517e-09,
+ 8.569803394033616e-09,
+ 8.796306429680468e-09,
+ 9.024770569809343e-09,
+ 9.255179122651411e-09,
+ 9.487515815539995e-09,
+ 9.721764777660094e-09,
+ 9.957910523775904e-09,
+ 1.0195937938866195e-08,
+ 1.043583226360343e-08,
+ 1.0677579080617987e-08,
+ 1.0921164301493718e-08,
+ 1.1166574154445529e-08,
+ 1.1413795172633704e-08,
+ 1.1662814183073225e-08,
+ 1.1913618296099733e-08,
+ 1.2166194895356733e-08,
+ 1.242053162827128e-08,
+ 1.2676616396987984e-08,
+ 1.2934437349733342e-08,
+ 1.319398287258449e-08,
+ 1.3455241581618304e-08,
+ 1.3718202315418647e-08,
+ 1.39828541279209e-08,
+ 1.4249186281574589e-08,
+ 1.4517188240806154e-08,
+ 1.4786849665765055e-08,
+ 1.505816040633769e-08,
+ 1.533111049641459e-08,
+ 1.5605690148397116e-08,
+ 1.5881889747931168e-08,
+ 1.6159699848855766e-08,
+ 1.6439111168355542e-08,
+ 1.672011458230654e-08,
+ 1.7002701120805663e-08,
+ 1.7286861963874392e-08,
+ 1.7572588437328278e-08,
+ 1.7859872008803996e-08,
+ 1.8148704283936354e-08,
+ 1.8439077002678016e-08,
+ 1.873098203575524e-08,
+ 1.9024411381253094e-08,
+ 1.9319357161324317e-08,
+ 1.961581161901597e-08,
+ 1.9913767115208585e-08,
+ 2.0213216125662727e-08,
+ 2.05141512381682e-08,
+ 2.081656514979121e-08,
+ 2.1120450664215458e-08,
+ 2.1425800689172855e-08,
+ 2.173260823396007e-08,
+ 2.2040866407037357e-08,
+ 2.235056841370605e-08,
+ 2.26617075538615e-08,
+ 2.297427721981843e-08,
+ 2.3288270894205526e-08,
+ 2.3603682147926647e-08,
+ 2.3920504638185864e-08,
+ 2.423873210657385e-08,
+ 2.455835837721304e-08,
+ 2.4879377354959567e-08,
+ 2.5201783023659364e-08,
+ 2.5525569444456704e-08,
+ 2.585073075415291e-08,
+ 2.617726116361354e-08,
+ 2.6505154956222023e-08,
+ 2.6834406486378244e-08,
+ 2.7165010178040183e-08,
+ 2.7496960523307184e-08,
+ 2.783025208104333e-08,
+ 2.816487947553939e-08,
+ 2.850083739521198e-08,
+ 2.8838120591338796e-08,
+ 2.9176723876828315e-08,
+ 2.9516642125023073e-08,
+ 2.985787026853515e-08,
+ 3.0200403298112927e-08,
+ 3.0544236261537736e-08,
+ 3.088936426254993e-08,
+ 3.123578245980278e-08,
+ 3.158348606584362e-08,
+ 3.1932470346121355e-08,
+ 3.228273061801924e-08,
+ 3.263426224991233e-08,
+ 3.263426224991233e-08,
+ 3.228273061801924e-08,
+ 3.1932470346121355e-08,
+ 3.158348606584362e-08,
+ 3.123578245980278e-08,
+ 3.088936426254993e-08,
+ 3.0544236261537736e-08,
+ 3.0200403298112927e-08,
+ 2.985787026853515e-08,
+ 2.9516642125023073e-08,
+ 2.9176723876828315e-08,
+ 2.8838120591338796e-08,
+ 2.850083739521198e-08,
+ 2.816487947553939e-08,
+ 2.783025208104333e-08,
+ 2.7496960523307184e-08,
+ 2.7165010178040183e-08,
+ 2.6834406486378244e-08,
+ 2.6505154956222023e-08,
+ 2.617726116361354e-08,
+ 2.585073075415291e-08,
+ 2.5525569444456704e-08,
+ 2.5201783023659364e-08,
+ 2.4879377354959567e-08,
+ 2.455835837721304e-08,
+ 2.423873210657385e-08,
+ 2.3920504638185864e-08,
+ 2.3603682147926647e-08,
+ 2.3288270894205526e-08,
+ 2.297427721981843e-08,
+ 2.26617075538615e-08,
+ 2.235056841370605e-08,
+ 2.2040866407037357e-08,
+ 2.173260823396007e-08,
+ 2.1425800689172855e-08,
+ 2.1120450664215458e-08,
+ 2.081656514979121e-08,
+ 2.05141512381682e-08,
+ 2.0213216125662727e-08,
+ 1.9913767115208585e-08,
+ 1.961581161901597e-08,
+ 1.9319357161324317e-08,
+ 1.9024411381253094e-08,
+ 1.873098203575524e-08,
+ 1.8439077002678016e-08,
+ 1.8148704283936354e-08,
+ 1.7859872008803996e-08,
+ 1.7572588437328278e-08,
+ 1.7286861963874392e-08,
+ 1.7002701120805663e-08,
+ 1.672011458230654e-08,
+ 1.6439111168355542e-08,
+ 1.6159699848855766e-08,
+ 1.5881889747931168e-08,
+ 1.5605690148397116e-08,
+ 1.533111049641459e-08,
+ 1.505816040633769e-08,
+ 1.4786849665765055e-08,
+ 1.4517188240806154e-08,
+ 1.4249186281574589e-08,
+ 1.39828541279209e-08,
+ 1.3718202315418647e-08,
+ 1.3455241581618304e-08,
+ 1.319398287258449e-08,
+ 1.2934437349733342e-08,
+ 1.2676616396987984e-08,
+ 1.242053162827128e-08,
+ 1.2166194895356733e-08,
+ 1.1913618296099733e-08,
+ 1.1662814183073225e-08,
+ 1.1413795172633704e-08,
+ 1.1166574154445529e-08,
+ 1.0921164301493718e-08,
+ 1.0677579080617987e-08,
+ 1.043583226360343e-08,
+ 1.0195937938866195e-08,
+ 9.957910523775904e-09,
+ 9.721764777660094e-09,
+ 9.487515815539995e-09,
+ 9.255179122651411e-09,
+ 9.024770569809343e-09,
+ 8.796306429680468e-09,
+ 8.569803394033616e-09,
+ 8.34527859204517e-09,
+ 8.122749609743786e-09,
+ 7.902234510687258e-09,
+ 7.68375185797383e-09,
+ 7.467320737700675e-09,
+ 7.252960783994298e-09,
+ 7.040692205750948e-09,
+ 6.830535815240165e-09,
+ 6.6225130587418625e-09,
+ 6.416646049406658e-09,
+ 6.212957602551249e-09,
+ 6.011471273625964e-09,
+ 5.8122113991203105e-09,
+ 5.6152031407054665e-09,
+ 5.4204725329507405e-09,
+ 5.2280465349948755e-09,
+ 5.037953086603876e-09,
+ 4.850221169106165e-09,
+ 4.664880871764622e-09,
+ 4.4819634642255775e-09,
+ 4.301501475779529e-09,
+ 4.123528782279825e-09,
+ 3.948080701697792e-09,
+ 3.775194099450046e-09,
+ 3.604907504821872e-09,
+ 3.437261240036515e-09,
+ 3.2722975637934026e-09,
+ 3.1100608314302475e-09,
+ 2.9505976742697263e-09,
+ 2.793957201210745e-09,
+ 2.6401912262430127e-09,
+ 2.489354526335747e-09,
+ 2.341505135122673e-09,
+ 2.196704679037894e-09,
+ 2.0550187641348948e-09,
+ 1.91651742386087e-09,
+ 1.7812756407244618e-09,
+ 1.6493739583192924e-09,
+ 1.520899204884417e-09,
+ 1.395945355989108e-09,
+ 1.2746145727602848e-09,
+ 1.1570184644520295e-09,
+ 1.0432796418448431e-09,
+ 9.335336537703735e-10,
+ 8.279314376191149e-10,
+ 7.266424738850142e-10,
+ 6.298589285390525e-10,
+ 5.378012209046018e-10,
+ 4.5072571825348834e-10,
+ 3.6893573315306275e-10,
+ 2.9279790947827367e-10,
+ 2.2276796639709082e-10,
+ 1.5943407662016136e-10,
+ 1.0359742844610796e-10,
+ 5.6445457243997767e-11,
+ 2.003274575842475e-11,
+ 1.1683417085427135e-13,
+ 1.1432160804020118e-13,
+ 1.1180904522613062e-13,
+ 1.0929648241206046e-13,
+ 1.067839195979903e-13,
+ 1.0427135678391974e-13,
+ 1.0175879396984957e-13,
+ 9.924623115577901e-14,
+ 9.673366834170885e-14,
+ 9.422110552763829e-14,
+ 9.170854271356813e-14,
+ 8.919597989949756e-14,
+ 8.66834170854274e-14,
+ 8.417085427135684e-14,
+ 8.165829145728668e-14,
+ 7.914572864321611e-14,
+ 7.663316582914595e-14,
+ 7.412060301507539e-14,
+ 7.160804020100523e-14,
+ 6.909547738693466e-14,
+ 6.65829145728645e-14,
+ 6.407035175879434e-14,
+ 6.155778894472378e-14,
+ 5.904522613065362e-14,
+ 5.653266331658305e-14,
+ 5.402010050251289e-14,
+ 5.150753768844233e-14,
+ 4.899497487437217e-14,
+ 4.64824120603016e-14,
+ 4.396984924623144e-14,
+ 4.145728643216088e-14,
+ 3.894472361809072e-14,
+ 3.6432160804020153e-14,
+ 3.391959798994999e-14,
+ 3.140703517587943e-14,
+ 2.889447236180927e-14,
+ 2.6381909547738704e-14,
+ 2.3869346733668543e-14,
+ 2.135678391959798e-14,
+ 1.884422110552782e-14,
+ 1.6331658291457254e-14,
+ 1.3819095477387094e-14,
+ 1.1306532663316934e-14,
+ 8.79396984924637e-15,
+ 6.281407035176209e-15,
+ 3.7688442211056445e-15,
+ 1.2562814070354841e-15,
+ -1.2562814070350802e-15,
+ -3.768844221105241e-15,
+ -6.281407035175805e-15,
+ -8.793969849245965e-15,
+ -1.130653266331653e-14,
+ -1.381909547738669e-14,
+ -1.6331658291457254e-14,
+ -1.8844221105527415e-14,
+ -2.135678391959798e-14,
+ -2.386934673366814e-14,
+ -2.6381909547738704e-14,
+ -2.8894472361808864e-14,
+ -3.140703517587943e-14,
+ -3.391959798994959e-14
+ ],
+ "height": [
+ 0.0,
+ 2.5125628140703518e-08,
+ 5.0251256281407036e-08,
+ 7.537688442211056e-08,
+ 1.0050251256281407e-07,
+ 1.2562814070351758e-07,
+ 1.5075376884422112e-07,
+ 1.7587939698492463e-07,
+ 2.0100502512562815e-07,
+ 2.2613065326633166e-07,
+ 2.5125628140703517e-07,
+ 2.763819095477387e-07,
+ 3.0150753768844224e-07,
+ 3.2663316582914573e-07,
+ 3.5175879396984927e-07,
+ 3.7688442211055275e-07,
+ 4.020100502512563e-07,
+ 4.2713567839195983e-07,
+ 4.522613065326633e-07,
+ 4.773869346733669e-07,
+ 5.025125628140703e-07,
+ 5.276381909547739e-07,
+ 5.527638190954774e-07,
+ 5.778894472361809e-07,
+ 6.030150753768845e-07,
+ 6.28140703517588e-07,
+ 6.532663316582915e-07,
+ 6.783919597989949e-07,
+ 7.035175879396985e-07,
+ 7.28643216080402e-07,
+ 7.537688442211055e-07,
+ 7.788944723618091e-07,
+ 8.040201005025126e-07,
+ 8.291457286432161e-07,
+ 8.542713567839197e-07,
+ 8.793969849246231e-07,
+ 9.045226130653266e-07,
+ 9.296482412060302e-07,
+ 9.547738693467337e-07,
+ 9.798994974874373e-07,
+ 1.0050251256281407e-06,
+ 1.0301507537688443e-06,
+ 1.0552763819095479e-06,
+ 1.0804020100502512e-06,
+ 1.1055276381909548e-06,
+ 1.1306532663316584e-06,
+ 1.1557788944723618e-06,
+ 1.1809045226130654e-06,
+ 1.206030150753769e-06,
+ 1.2311557788944724e-06,
+ 1.256281407035176e-06,
+ 1.2814070351758793e-06,
+ 1.306532663316583e-06,
+ 1.3316582914572865e-06,
+ 1.3567839195979899e-06,
+ 1.3819095477386935e-06,
+ 1.407035175879397e-06,
+ 1.4321608040201004e-06,
+ 1.457286432160804e-06,
+ 1.4824120603015076e-06,
+ 1.507537688442211e-06,
+ 1.5326633165829146e-06,
+ 1.5577889447236182e-06,
+ 1.5829145728643216e-06,
+ 1.6080402010050252e-06,
+ 1.6331658291457288e-06,
+ 1.6582914572864321e-06,
+ 1.6834170854271357e-06,
+ 1.7085427135678393e-06,
+ 1.7336683417085427e-06,
+ 1.7587939698492463e-06,
+ 1.7839195979899499e-06,
+ 1.8090452261306533e-06,
+ 1.8341708542713568e-06,
+ 1.8592964824120604e-06,
+ 1.8844221105527638e-06,
+ 1.9095477386934674e-06,
+ 1.9346733668341708e-06,
+ 1.9597989949748746e-06,
+ 1.984924623115578e-06,
+ 2.0100502512562813e-06,
+ 2.035175879396985e-06,
+ 2.0603015075376885e-06,
+ 2.085427135678392e-06,
+ 2.1105527638190957e-06,
+ 2.135678391959799e-06,
+ 2.1608040201005025e-06,
+ 2.1859296482412063e-06,
+ 2.2110552763819096e-06,
+ 2.236180904522613e-06,
+ 2.261306532663317e-06,
+ 2.28643216080402e-06,
+ 2.3115577889447236e-06,
+ 2.3366834170854274e-06,
+ 2.3618090452261308e-06,
+ 2.386934673366834e-06,
+ 2.412060301507538e-06,
+ 2.4371859296482413e-06,
+ 2.4623115577889447e-06,
+ 2.487437185929648e-06,
+ 2.512562814070352e-06,
+ 2.5376884422110553e-06,
+ 2.5628140703517587e-06,
+ 2.5879396984924625e-06,
+ 2.613065326633166e-06,
+ 2.6381909547738692e-06,
+ 2.663316582914573e-06,
+ 2.6884422110552764e-06,
+ 2.7135678391959798e-06,
+ 2.7386934673366836e-06,
+ 2.763819095477387e-06,
+ 2.7889447236180903e-06,
+ 2.814070351758794e-06,
+ 2.8391959798994975e-06,
+ 2.864321608040201e-06,
+ 2.8894472361809047e-06,
+ 2.914572864321608e-06,
+ 2.9396984924623115e-06,
+ 2.9648241206030153e-06,
+ 2.9899497487437186e-06,
+ 3.015075376884422e-06,
+ 3.040201005025126e-06,
+ 3.065326633165829e-06,
+ 3.0904522613065326e-06,
+ 3.1155778894472364e-06,
+ 3.1407035175879398e-06,
+ 3.165829145728643e-06,
+ 3.190954773869347e-06,
+ 3.2160804020100503e-06,
+ 3.2412060301507537e-06,
+ 3.2663316582914575e-06,
+ 3.291457286432161e-06,
+ 3.3165829145728643e-06,
+ 3.341708542713568e-06,
+ 3.3668341708542714e-06,
+ 3.391959798994975e-06,
+ 3.4170854271356786e-06,
+ 3.442211055276382e-06,
+ 3.4673366834170854e-06,
+ 3.492462311557789e-06,
+ 3.5175879396984926e-06,
+ 3.542713567839196e-06,
+ 3.5678391959798997e-06,
+ 3.592964824120603e-06,
+ 3.6180904522613065e-06,
+ 3.6432160804020103e-06,
+ 3.6683417085427137e-06,
+ 3.693467336683417e-06,
+ 3.718592964824121e-06,
+ 3.7437185929648243e-06,
+ 3.7688442211055276e-06,
+ 3.7939698492462314e-06,
+ 3.819095477386935e-06,
+ 3.844221105527638e-06,
+ 3.8693467336683416e-06,
+ 3.894472361809045e-06,
+ 3.919597989949749e-06,
+ 3.9447236180904526e-06,
+ 3.969849246231156e-06,
+ 3.994974874371859e-06,
+ 4.020100502512563e-06,
+ 4.045226130653266e-06,
+ 4.07035175879397e-06,
+ 4.095477386934674e-06,
+ 4.120603015075377e-06,
+ 4.1457286432160804e-06,
+ 4.170854271356784e-06,
+ 4.195979899497487e-06,
+ 4.221105527638191e-06,
+ 4.246231155778895e-06,
+ 4.271356783919598e-06,
+ 4.2964824120603016e-06,
+ 4.321608040201005e-06,
+ 4.346733668341708e-06,
+ 4.3718592964824125e-06,
+ 4.396984924623116e-06,
+ 4.422110552763819e-06,
+ 4.447236180904523e-06,
+ 4.472361809045226e-06,
+ 4.4974874371859294e-06,
+ 4.522613065326634e-06,
+ 4.547738693467337e-06,
+ 4.57286432160804e-06,
+ 4.597989949748744e-06,
+ 4.623115577889447e-06,
+ 4.6482412060301506e-06,
+ 4.673366834170855e-06,
+ 4.698492462311558e-06,
+ 4.7236180904522615e-06,
+ 4.748743718592965e-06,
+ 4.773869346733668e-06,
+ 4.798994974874372e-06,
+ 4.824120603015076e-06,
+ 4.849246231155779e-06,
+ 4.874371859296483e-06,
+ 4.899497487437186e-06,
+ 4.9246231155778894e-06,
+ 4.949748743718593e-06,
+ 4.974874371859296e-06,
+ 5e-06,
+ 5e-06,
+ 4.974874371859296e-06,
+ 4.949748743718593e-06,
+ 4.9246231155778894e-06,
+ 4.899497487437186e-06,
+ 4.874371859296483e-06,
+ 4.849246231155779e-06,
+ 4.824120603015076e-06,
+ 4.798994974874372e-06,
+ 4.773869346733668e-06,
+ 4.748743718592965e-06,
+ 4.7236180904522615e-06,
+ 4.698492462311558e-06,
+ 4.673366834170855e-06,
+ 4.6482412060301506e-06,
+ 4.623115577889447e-06,
+ 4.597989949748744e-06,
+ 4.57286432160804e-06,
+ 4.547738693467337e-06,
+ 4.522613065326634e-06,
+ 4.4974874371859294e-06,
+ 4.472361809045226e-06,
+ 4.447236180904523e-06,
+ 4.422110552763819e-06,
+ 4.396984924623116e-06,
+ 4.3718592964824125e-06,
+ 4.346733668341708e-06,
+ 4.321608040201005e-06,
+ 4.2964824120603016e-06,
+ 4.271356783919598e-06,
+ 4.246231155778895e-06,
+ 4.221105527638191e-06,
+ 4.195979899497487e-06,
+ 4.170854271356784e-06,
+ 4.1457286432160804e-06,
+ 4.120603015075377e-06,
+ 4.095477386934674e-06,
+ 4.07035175879397e-06,
+ 4.045226130653266e-06,
+ 4.020100502512563e-06,
+ 3.994974874371859e-06,
+ 3.969849246231156e-06,
+ 3.9447236180904526e-06,
+ 3.919597989949749e-06,
+ 3.894472361809045e-06,
+ 3.8693467336683416e-06,
+ 3.844221105527638e-06,
+ 3.819095477386935e-06,
+ 3.7939698492462314e-06,
+ 3.7688442211055276e-06,
+ 3.7437185929648243e-06,
+ 3.718592964824121e-06,
+ 3.693467336683417e-06,
+ 3.6683417085427137e-06,
+ 3.6432160804020103e-06,
+ 3.6180904522613065e-06,
+ 3.592964824120603e-06,
+ 3.5678391959798997e-06,
+ 3.542713567839196e-06,
+ 3.5175879396984926e-06,
+ 3.492462311557789e-06,
+ 3.4673366834170854e-06,
+ 3.442211055276382e-06,
+ 3.4170854271356786e-06,
+ 3.391959798994975e-06,
+ 3.3668341708542714e-06,
+ 3.341708542713568e-06,
+ 3.3165829145728643e-06,
+ 3.291457286432161e-06,
+ 3.2663316582914575e-06,
+ 3.2412060301507537e-06,
+ 3.2160804020100503e-06,
+ 3.190954773869347e-06,
+ 3.165829145728643e-06,
+ 3.1407035175879398e-06,
+ 3.1155778894472364e-06,
+ 3.0904522613065326e-06,
+ 3.065326633165829e-06,
+ 3.040201005025126e-06,
+ 3.015075376884422e-06,
+ 2.9899497487437186e-06,
+ 2.9648241206030153e-06,
+ 2.9396984924623115e-06,
+ 2.914572864321608e-06,
+ 2.8894472361809047e-06,
+ 2.864321608040201e-06,
+ 2.8391959798994975e-06,
+ 2.814070351758794e-06,
+ 2.7889447236180903e-06,
+ 2.763819095477387e-06,
+ 2.7386934673366836e-06,
+ 2.7135678391959798e-06,
+ 2.6884422110552764e-06,
+ 2.663316582914573e-06,
+ 2.6381909547738692e-06,
+ 2.613065326633166e-06,
+ 2.5879396984924625e-06,
+ 2.5628140703517587e-06,
+ 2.5376884422110553e-06,
+ 2.512562814070352e-06,
+ 2.487437185929648e-06,
+ 2.4623115577889447e-06,
+ 2.4371859296482413e-06,
+ 2.412060301507538e-06,
+ 2.386934673366834e-06,
+ 2.3618090452261308e-06,
+ 2.3366834170854274e-06,
+ 2.3115577889447236e-06,
+ 2.28643216080402e-06,
+ 2.261306532663317e-06,
+ 2.236180904522613e-06,
+ 2.2110552763819096e-06,
+ 2.1859296482412063e-06,
+ 2.1608040201005025e-06,
+ 2.135678391959799e-06,
+ 2.1105527638190957e-06,
+ 2.085427135678392e-06,
+ 2.0603015075376885e-06,
+ 2.035175879396985e-06,
+ 2.0100502512562813e-06,
+ 1.984924623115578e-06,
+ 1.9597989949748746e-06,
+ 1.9346733668341708e-06,
+ 1.9095477386934674e-06,
+ 1.8844221105527638e-06,
+ 1.8592964824120604e-06,
+ 1.8341708542713568e-06,
+ 1.8090452261306533e-06,
+ 1.7839195979899499e-06,
+ 1.7587939698492463e-06,
+ 1.7336683417085427e-06,
+ 1.7085427135678393e-06,
+ 1.6834170854271357e-06,
+ 1.6582914572864321e-06,
+ 1.6331658291457288e-06,
+ 1.6080402010050252e-06,
+ 1.5829145728643216e-06,
+ 1.5577889447236182e-06,
+ 1.5326633165829146e-06,
+ 1.507537688442211e-06,
+ 1.4824120603015076e-06,
+ 1.457286432160804e-06,
+ 1.4321608040201004e-06,
+ 1.407035175879397e-06,
+ 1.3819095477386935e-06,
+ 1.3567839195979899e-06,
+ 1.3316582914572865e-06,
+ 1.306532663316583e-06,
+ 1.2814070351758793e-06,
+ 1.256281407035176e-06,
+ 1.2311557788944724e-06,
+ 1.206030150753769e-06,
+ 1.1809045226130654e-06,
+ 1.1557788944723618e-06,
+ 1.1306532663316584e-06,
+ 1.1055276381909548e-06,
+ 1.0804020100502512e-06,
+ 1.0552763819095479e-06,
+ 1.0301507537688443e-06,
+ 1.0050251256281407e-06,
+ 9.798994974874373e-07,
+ 9.547738693467337e-07,
+ 9.296482412060302e-07,
+ 9.045226130653266e-07,
+ 8.793969849246231e-07,
+ 8.542713567839197e-07,
+ 8.291457286432161e-07,
+ 8.040201005025126e-07,
+ 7.788944723618091e-07,
+ 7.537688442211055e-07,
+ 7.28643216080402e-07,
+ 7.035175879396985e-07,
+ 6.783919597989949e-07,
+ 6.532663316582915e-07,
+ 6.28140703517588e-07,
+ 6.030150753768845e-07,
+ 5.778894472361809e-07,
+ 5.527638190954774e-07,
+ 5.276381909547739e-07,
+ 5.025125628140703e-07,
+ 4.773869346733669e-07,
+ 4.522613065326633e-07,
+ 4.2713567839195983e-07,
+ 4.020100502512563e-07,
+ 3.7688442211055275e-07,
+ 3.5175879396984927e-07,
+ 3.2663316582914573e-07,
+ 3.0150753768844224e-07,
+ 2.763819095477387e-07,
+ 2.5125628140703517e-07,
+ 2.2613065326633166e-07,
+ 2.0100502512562815e-07,
+ 1.7587939698492463e-07,
+ 1.5075376884422112e-07,
+ 1.2562814070351758e-07,
+ 1.0050251256281407e-07,
+ 7.537688442211056e-08,
+ 5.0251256281407036e-08,
+ 2.5125628140703518e-08,
+ 0.0
+ ],
+ "segment": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ "tip_position": [
+ -7.035182914572865e-07,
+ -6.783926381909549e-07,
+ -6.532669849246232e-07,
+ -6.281413316582915e-07,
+ -6.030156783919598e-07,
+ -5.778900251256283e-07,
+ -5.527643718592966e-07,
+ -5.276387185929649e-07,
+ -5.025130653266333e-07,
+ -4.773874120603016e-07,
+ -4.5226175879396996e-07,
+ -4.2713610552763827e-07,
+ -4.020104522613066e-07,
+ -3.7688479899497494e-07,
+ -3.517591457286433e-07,
+ -3.2663349246231166e-07,
+ -3.0150783919597997e-07,
+ -2.763821859296483e-07,
+ -2.512565326633167e-07,
+ -2.26130879396985e-07,
+ -2.0100522613065342e-07,
+ -1.7587957286432162e-07,
+ -1.5075391959799004e-07,
+ -1.2562826633165835e-07,
+ -1.0050261306532666e-07,
+ -7.537695979899507e-08,
+ -5.025130653266338e-08,
+ -2.5125653266331797e-08,
+ 0.0,
+ 2.5125653266331585e-08,
+ 5.025130653266317e-08,
+ 7.537695979899497e-08,
+ 1.0050261306532655e-07,
+ 1.2562826633165824e-07,
+ 1.5075391959798993e-07,
+ 1.7587957286432152e-07,
+ 2.010052261306532e-07,
+ 2.261308793969849e-07,
+ 2.512565326633166e-07,
+ 2.763821859296483e-07,
+ 3.0150783919597976e-07,
+ 3.2663349246231145e-07,
+ 3.5175914572864314e-07,
+ 3.7688479899497483e-07,
+ 4.020104522613065e-07,
+ 4.271361055276382e-07,
+ 4.522617587939697e-07,
+ 4.773874120603014e-07,
+ 5.025130653266333e-07,
+ 5.276387185929648e-07,
+ 5.527643718592965e-07,
+ 5.778900251256279e-07,
+ 6.030156783919596e-07,
+ 6.281413316582915e-07,
+ 6.53266984924623e-07,
+ 6.783926381909547e-07,
+ 7.035182914572864e-07,
+ 7.286439447236179e-07,
+ 7.537695979899498e-07,
+ 7.788952512562815e-07,
+ 8.040209045226129e-07,
+ 8.293456917791922e-07,
+ 8.548354470347515e-07,
+ 8.804325948874761e-07,
+ 9.061165895099201e-07,
+ 9.318755565483931e-07,
+ 9.577014841199081e-07,
+ 9.835884904973597e-07,
+ 1.0095320184890676e-06,
+ 1.035528401656282e-06,
+ 1.06157460687333e-06,
+ 1.0876680704674934e-06,
+ 1.113806588245538e-06,
+ 1.1399882385477538e-06,
+ 1.166211326569202e-06,
+ 1.1924743429359777e-06,
+ 1.2187759321597636e-06,
+ 1.245114868132755e-06,
+ 1.271490034762412e-06,
+ 1.297900410437464e-06,
+ 1.3243450554022193e-06,
+ 1.350823101374287e-06,
+ 1.3773337429177307e-06,
+ 1.403876230207464e-06,
+ 1.4304498629090156e-06,
+ 1.4570539849618499e-06,
+ 1.483687980101626e-06,
+ 1.5103512679920072e-06,
+ 1.5370433008633e-06,
+ 1.563763560575609e-06,
+ 1.5905115560399442e-06,
+ 1.6172868209430787e-06,
+ 1.6440889117316355e-06,
+ 1.6709174058186212e-06,
+ 1.6977718999818021e-06,
+ 1.7246520089283258e-06,
+ 1.7515573640040266e-06,
+ 1.7784876120291904e-06,
+ 1.8054424142452842e-06,
+ 1.832421445359403e-06,
+ 1.8594243926750839e-06,
+ 1.8864509552996973e-06,
+ 1.9135008434199595e-06,
+ 1.9405737776382103e-06,
+ 1.967669488363062e-06,
+ 1.9947877152488222e-06,
+ 2.021928206678779e-06,
+ 2.0490907192880364e-06,
+ 2.0762750175220915e-06,
+ 2.1034808732277784e-06,
+ 2.1307080652735897e-06,
+ 2.1579563791967266e-06,
+ 2.185225606874494e-06,
+ 2.212515546217929e-06,
+ 2.239826000885767e-06,
+ 2.267156780017036e-06,
+ 2.294507697980753e-06,
+ 2.3218785741413407e-06,
+ 2.3492692326385132e-06,
+ 2.3766795021805053e-06,
+ 2.4041092158496296e-06,
+ 2.431558210919219e-06,
+ 2.4590263286811232e-06,
+ 2.486513414282985e-06,
+ 2.5140193165745918e-06,
+ 2.541543887962667e-06,
+ 2.569086984273516e-06,
+ 2.5966484646229774e-06,
+ 2.624228191293199e-06,
+ 2.651826029615784e-06,
+ 2.679441847860883e-06,
+ 2.7070755171318516e-06,
+ 2.734726911265125e-06,
+ 2.762395906734974e-06,
+ 2.7900823825628445e-06,
+ 2.8177862202310015e-06,
+ 2.845507303600217e-06,
+ 2.8732455188312583e-06,
+ 2.901000754309965e-06,
+ 2.9287729005756916e-06,
+ 2.9565618502529318e-06,
+ 2.984367497985951e-06,
+ 3.0121897403762433e-06,
+ 3.040028475922673e-06,
+ 3.0678836049641455e-06,
+ 3.095755029624675e-06,
+ 3.1236426537607187e-06,
+ 3.1515463829106677e-06,
+ 3.1794661242463694e-06,
+ 3.2074017865265833e-06,
+ 3.235353280052278e-06,
+ 3.2633205166236683e-06,
+ 3.2913034094989106e-06,
+ 3.319301873354371e-06,
+ 3.347315824246398e-06,
+ 3.3753451795745185e-06,
+ 3.403389858045995e-06,
+ 3.431449779641677e-06,
+ 3.4595248655830924e-06,
+ 3.4876150383007124e-06,
+ 3.515720221403342e-06,
+ 3.543840339648587e-06,
+ 3.5719753189143457e-06,
+ 3.600125086171279e-06,
+ 3.6282895694562253e-06,
+ 3.656468697846502e-06,
+ 3.684662401435078e-06,
+ 3.7128706113065542e-06,
+ 3.741093259513945e-06,
+ 3.7693302790562035e-06,
+ 3.7975816038564754e-06,
+ 3.82584716874105e-06,
+ 3.854126909418965e-06,
+ 3.882420762462261e-06,
+ 3.910728665286845e-06,
+ 3.93905055613394e-06,
+ 3.967386374052109e-06,
+ 3.99573605887981e-06,
+ 4.024099551228487e-06,
+ 4.052476792466152e-06,
+ 4.080867724701463e-06,
+ 4.109272290768251e-06,
+ 4.1376904342105165e-06,
+ 4.166122099267839e-06,
+ 4.194567230861213e-06,
+ 4.223025774579278e-06,
+ 4.251497676664942e-06,
+ 4.2799828840023715e-06,
+ 4.308481344104343e-06,
+ 4.3369930050999415e-06,
+ 4.365517815722593e-06,
+ 4.394055725298417e-06,
+ 4.422606683734899e-06,
+ 4.451170641509851e-06,
+ 4.479747549660676e-06,
+ 4.508337359773908e-06,
+ 4.536940023975019e-06,
+ 4.5655554949185e-06,
+ 4.594183725778182e-06,
+ 4.622824670237817e-06,
+ 4.622824670237817e-06,
+ 4.594183725778182e-06,
+ 4.5655554949185e-06,
+ 4.536940023975019e-06,
+ 4.508337359773908e-06,
+ 4.479747549660676e-06,
+ 4.451170641509851e-06,
+ 4.422606683734899e-06,
+ 4.394055725298417e-06,
+ 4.365517815722593e-06,
+ 4.3369930050999415e-06,
+ 4.308481344104343e-06,
+ 4.2799828840023715e-06,
+ 4.251497676664942e-06,
+ 4.223025774579278e-06,
+ 4.194567230861213e-06,
+ 4.166122099267839e-06,
+ 4.1376904342105165e-06,
+ 4.109272290768251e-06,
+ 4.080867724701463e-06,
+ 4.052476792466152e-06,
+ 4.024099551228487e-06,
+ 3.99573605887981e-06,
+ 3.967386374052109e-06,
+ 3.93905055613394e-06,
+ 3.910728665286845e-06,
+ 3.882420762462261e-06,
+ 3.854126909418965e-06,
+ 3.82584716874105e-06,
+ 3.7975816038564754e-06,
+ 3.7693302790562035e-06,
+ 3.741093259513945e-06,
+ 3.7128706113065542e-06,
+ 3.684662401435078e-06,
+ 3.656468697846502e-06,
+ 3.6282895694562253e-06,
+ 3.600125086171279e-06,
+ 3.5719753189143457e-06,
+ 3.543840339648587e-06,
+ 3.515720221403342e-06,
+ 3.4876150383007124e-06,
+ 3.4595248655830924e-06,
+ 3.431449779641677e-06,
+ 3.403389858045995e-06,
+ 3.3753451795745185e-06,
+ 3.347315824246398e-06,
+ 3.319301873354371e-06,
+ 3.2913034094989106e-06,
+ 3.2633205166236683e-06,
+ 3.235353280052278e-06,
+ 3.2074017865265833e-06,
+ 3.1794661242463694e-06,
+ 3.1515463829106677e-06,
+ 3.1236426537607187e-06,
+ 3.095755029624675e-06,
+ 3.0678836049641455e-06,
+ 3.040028475922673e-06,
+ 3.0121897403762433e-06,
+ 2.984367497985951e-06,
+ 2.9565618502529318e-06,
+ 2.9287729005756916e-06,
+ 2.901000754309965e-06,
+ 2.8732455188312583e-06,
+ 2.845507303600217e-06,
+ 2.8177862202310015e-06,
+ 2.7900823825628445e-06,
+ 2.762395906734974e-06,
+ 2.734726911265125e-06,
+ 2.7070755171318516e-06,
+ 2.679441847860883e-06,
+ 2.651826029615784e-06,
+ 2.624228191293199e-06,
+ 2.5966484646229774e-06,
+ 2.569086984273516e-06,
+ 2.541543887962667e-06,
+ 2.5140193165745918e-06,
+ 2.486513414282985e-06,
+ 2.4590263286811232e-06,
+ 2.431558210919219e-06,
+ 2.4041092158496296e-06,
+ 2.3766795021805053e-06,
+ 2.3492692326385132e-06,
+ 2.3218785741413407e-06,
+ 2.294507697980753e-06,
+ 2.267156780017036e-06,
+ 2.239826000885767e-06,
+ 2.212515546217929e-06,
+ 2.185225606874494e-06,
+ 2.1579563791967266e-06,
+ 2.1307080652735897e-06,
+ 2.1034808732277784e-06,
+ 2.0762750175220915e-06,
+ 2.0490907192880364e-06,
+ 2.021928206678779e-06,
+ 1.9947877152488222e-06,
+ 1.967669488363062e-06,
+ 1.9405737776382103e-06,
+ 1.9135008434199595e-06,
+ 1.8864509552996973e-06,
+ 1.8594243926750839e-06,
+ 1.832421445359403e-06,
+ 1.8054424142452842e-06,
+ 1.7784876120291904e-06,
+ 1.7515573640040266e-06,
+ 1.7246520089283258e-06,
+ 1.6977718999818021e-06,
+ 1.6709174058186212e-06,
+ 1.6440889117316355e-06,
+ 1.6172868209430787e-06,
+ 1.5905115560399442e-06,
+ 1.563763560575609e-06,
+ 1.5370433008633e-06,
+ 1.5103512679920072e-06,
+ 1.483687980101626e-06,
+ 1.4570539849618499e-06,
+ 1.4304498629090156e-06,
+ 1.403876230207464e-06,
+ 1.3773337429177307e-06,
+ 1.350823101374287e-06,
+ 1.3243450554022193e-06,
+ 1.297900410437464e-06,
+ 1.271490034762412e-06,
+ 1.245114868132755e-06,
+ 1.2187759321597636e-06,
+ 1.1924743429359777e-06,
+ 1.166211326569202e-06,
+ 1.1399882385477538e-06,
+ 1.113806588245538e-06,
+ 1.0876680704674934e-06,
+ 1.06157460687333e-06,
+ 1.035528401656282e-06,
+ 1.0095320184890676e-06,
+ 9.835884904973597e-07,
+ 9.577014841199081e-07,
+ 9.318755565483931e-07,
+ 9.061165895099201e-07,
+ 8.804325948874761e-07,
+ 8.548354470347515e-07,
+ 8.293456917791922e-07,
+ 8.040209045226129e-07,
+ 7.788952512562815e-07,
+ 7.537695979899498e-07,
+ 7.286439447236179e-07,
+ 7.035182914572864e-07,
+ 6.783926381909547e-07,
+ 6.53266984924623e-07,
+ 6.281413316582915e-07,
+ 6.030156783919596e-07,
+ 5.778900251256279e-07,
+ 5.527643718592965e-07,
+ 5.276387185929648e-07,
+ 5.025130653266333e-07,
+ 4.773874120603014e-07,
+ 4.522617587939697e-07,
+ 4.271361055276382e-07,
+ 4.020104522613065e-07,
+ 3.7688479899497483e-07,
+ 3.5175914572864314e-07,
+ 3.2663349246231145e-07,
+ 3.0150783919597976e-07,
+ 2.763821859296483e-07,
+ 2.512565326633166e-07,
+ 2.261308793969849e-07,
+ 2.010052261306532e-07,
+ 1.7587957286432152e-07,
+ 1.5075391959798993e-07,
+ 1.2562826633165824e-07,
+ 1.0050261306532655e-07,
+ 7.537695979899497e-08,
+ 5.025130653266317e-08,
+ 2.5125653266331585e-08,
+ 0.0,
+ -2.5125653266331797e-08,
+ -5.025130653266338e-08,
+ -7.537695979899507e-08,
+ -1.0050261306532666e-07,
+ -1.2562826633165835e-07,
+ -1.5075391959799004e-07,
+ -1.7587957286432162e-07,
+ -2.0100522613065342e-07,
+ -2.26130879396985e-07,
+ -2.512565326633167e-07,
+ -2.763821859296483e-07,
+ -3.0150783919597997e-07,
+ -3.2663349246231166e-07,
+ -3.517591457286433e-07,
+ -3.7688479899497494e-07,
+ -4.020104522613066e-07,
+ -4.2713610552763827e-07,
+ -4.5226175879396996e-07,
+ -4.773874120603016e-07,
+ -5.025130653266333e-07,
+ -5.276387185929649e-07,
+ -5.527643718592966e-07,
+ -5.778900251256283e-07,
+ -6.030156783919598e-07,
+ -6.281413316582915e-07,
+ -6.532669849246232e-07,
+ -6.783926381909549e-07,
+ -7.035182914572865e-07
+ ]
+ }
+ },
+ "contact_methods": [
+ "deviation_from_baseline",
+ "fit_constant_line",
+ "fit_line_polynomial",
+ "fit_constant_polynomial"
+ ],
+ "license": "GPL-3 (subprocess boundary only)",
+ "pipeline": [
+ "compute_tip_position",
+ "correct_split_approach_retract",
+ "correct_tip_offset",
+ "correct_force_offset",
+ "correct_force_slope"
+ ],
+ "platform": "Linux-x86_64-glibc",
+ "python": "3.12.13",
+ "software": "nanite",
+ "version": "4.2.3"
+ },
+ "family": "force_foundation",
+ "native_contract": {
+ "baseline": "pre_contact = first 10% of approach; linear offset + slope",
+ "calibration": "raw_v -> deflection_m (x InVOLS m/V) -> force_n (x k N/m)",
+ "contact": "threshold (k*sigma, persistence 3) / ROV (Gavara) / piecewise (1/2, value-continuous)",
+ "events": "snap-in on approach before contact; pull-off on retract",
+ "qc": "typed reasons; summary score beside component diagnostics",
+ "separation": "height - deflection",
+ "work": "trapezoid over common tip-position overlap; monotone interpolation"
+ },
+ "non_claims": [
+ "no certified cantilever calibration",
+ "no universal JPK/ANA numerical parity",
+ "no physical validation",
+ "no universal contact point",
+ "no automatic choice of the correct contact method",
+ "no uncertainty guarantee from method spread alone",
+ "no model validity inference",
+ "no cell/material property truth claim",
+ "no experimental reproducibility claim",
+ "no complete force-map parity",
+ "no SMFS or viscoelastic parity from this batch"
+ ],
+ "phantom_manifest": "force_foundation/force_phantoms_reference.json",
+ "relations": {
+ "baseline_offset_invariance": "offset correction leaves contact branch shape invariant",
+ "deflection_scaling": "deflection scales with InVOLS",
+ "event_window_restriction": "restricting event windows bounds the search",
+ "force_scaling": "force scales linearly with spring constant",
+ "work_scaling": "work scales linearly with force amplitude"
+ },
+ "schema_version": 1
+}
diff --git a/tests/validation/fixtures/force_foundation/force_phantoms_reference.json b/tests/validation/fixtures/force_foundation/force_phantoms_reference.json
new file mode 100644
index 0000000..1cebe0d
--- /dev/null
+++ b/tests/validation/fixtures/force_foundation/force_phantoms_reference.json
@@ -0,0 +1,10805 @@
+{
+ "cases": {
+ "P01": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "clean_contact",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 0.0,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 0.0,
+ "contact_coordinate": 1.5326633165829146e-06,
+ "contact_index_approach": 61,
+ "expected_qc_failures": [],
+ "pull_off_force": null,
+ "pull_off_index": null,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 4.558964589128417e-14,
+ "work_hysteresis": 9.117929178256832e-14,
+ "work_retract": -4.558964589128416e-14
+ }
+ },
+ "P02": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "baseline_offset_positive",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 3e-10,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 0.0,
+ "contact_coordinate": 1.5326633165829146e-06,
+ "contact_index_approach": 61,
+ "expected_qc_failures": [],
+ "pull_off_force": null,
+ "pull_off_index": null,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 4.66373845847515e-14,
+ "work_hysteresis": 9.3274769169503e-14,
+ "work_retract": -4.66373845847515e-14
+ }
+ },
+ "P03": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "baseline_offset_negative",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": -3e-10,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 0.0,
+ "contact_coordinate": 1.5326633165829146e-06,
+ "contact_index_approach": 61,
+ "expected_qc_failures": [],
+ "pull_off_force": null,
+ "pull_off_index": null,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P04": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "baseline_slope_positive",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 1e-10,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 0.00015,
+ "contact_coordinate": 1.5326633165829146e-06,
+ "contact_index_approach": 61,
+ "expected_qc_failures": [],
+ "pull_off_force": null,
+ "pull_off_index": null,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P05": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "baseline_slope_negative",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 1e-10,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": -0.00012,
+ "contact_coordinate": 1.5326633165829146e-06,
+ "contact_index_approach": 61,
+ "expected_qc_failures": [],
+ "pull_off_force": null,
+ "pull_off_index": null,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P06": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "temporal_drift_linear",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 5e-10,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 0.0001,
+ "contact_coordinate": 1.5326633165829146e-06,
+ "contact_index_approach": 61,
+ "expected_qc_failures": [],
+ "pull_off_force": null,
+ "pull_off_index": null,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P07": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "gaussian_noise",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 5e-10,
+ "baseline_noise_sigma": 2e-11,
+ "baseline_slope": 0.0001,
+ "contact_coordinate": 1.5326633165829146e-06,
+ "contact_index_approach": 61,
+ "expected_qc_failures": [],
+ "pull_off_force": null,
+ "pull_off_index": null,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P08": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "correlated_noise",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 5e-10,
+ "baseline_noise_sigma": 2e-11,
+ "baseline_slope": 0.0001,
+ "contact_coordinate": 1.5326633165829146e-06,
+ "contact_index_approach": 61,
+ "expected_qc_failures": [],
+ "pull_off_force": null,
+ "pull_off_index": null,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P09": {
+ "invols": 3e-08,
+ "is_raw_volts": true,
+ "kind": "calibration_scaling",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 5e-10,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 0.0001,
+ "contact_coordinate": 1.5326633165829146e-06,
+ "contact_index_approach": 61,
+ "expected_qc_failures": [],
+ "pull_off_force": null,
+ "pull_off_index": null,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P10": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "approach_retract_lag",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 5e-10,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 0.0001,
+ "contact_coordinate": 1.5326633165829146e-06,
+ "contact_index_approach": 61,
+ "expected_qc_failures": [],
+ "pull_off_force": null,
+ "pull_off_index": null,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P11": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "flat_turning_point",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 5e-10,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 0.0001,
+ "contact_coordinate": 1.5326633165829146e-06,
+ "contact_index_approach": 61,
+ "expected_qc_failures": [],
+ "pull_off_force": null,
+ "pull_off_index": null,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P12": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "nonmonotonic_coordinate",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 5e-10,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 0.0001,
+ "contact_coordinate": 1.5326633165829146e-06,
+ "contact_index_approach": 61,
+ "expected_qc_failures": [
+ "NONMONOTONIC_COORDINATE"
+ ],
+ "pull_off_force": null,
+ "pull_off_index": null,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P13": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "snap_in_and_pull_off",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 5e-10,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 0.0001,
+ "contact_coordinate": 1.5326633165829146e-06,
+ "contact_index_approach": 61,
+ "expected_qc_failures": [],
+ "pull_off_force": -1.5e-09,
+ "pull_off_index": 360,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [],
+ "snap_in_force": -2e-10,
+ "snap_in_index": 40,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P14": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "snap_in_only",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 5e-10,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 0.0001,
+ "contact_coordinate": 1.5326633165829146e-06,
+ "contact_index_approach": 61,
+ "expected_qc_failures": [],
+ "pull_off_force": null,
+ "pull_off_index": null,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [],
+ "snap_in_force": -3e-10,
+ "snap_in_index": 55,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P15": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "pull_off_only",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 5e-10,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 0.0001,
+ "contact_coordinate": 1.5326633165829146e-06,
+ "contact_index_approach": 61,
+ "expected_qc_failures": [],
+ "pull_off_force": -2e-09,
+ "pull_off_index": 340,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P16": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "multiple_ruptures",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 5e-10,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 0.0001,
+ "contact_coordinate": 1.5326633165829146e-06,
+ "contact_index_approach": 61,
+ "expected_qc_failures": [],
+ "pull_off_force": -2e-09,
+ "pull_off_index": 350,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [
+ 1e-09,
+ 6e-10
+ ],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P17": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "adhesion_tail_hysteresis",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 5e-10,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 0.0001,
+ "contact_coordinate": 1.5326633165829146e-06,
+ "contact_index_approach": 61,
+ "expected_qc_failures": [],
+ "pull_off_force": -2e-09,
+ "pull_off_index": 330,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P18": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "saturation",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 5e-10,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 0.0001,
+ "contact_coordinate": 1.5326633165829146e-06,
+ "contact_index_approach": 61,
+ "expected_qc_failures": [
+ "SATURATED_SIGNAL"
+ ],
+ "pull_off_force": null,
+ "pull_off_index": null,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P19": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "missing_retract",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 5e-10,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 0.0001,
+ "contact_coordinate": 1.5326633165829146e-06,
+ "contact_index_approach": 61,
+ "expected_qc_failures": [
+ "MISSING_RETRACT"
+ ],
+ "pull_off_force": null,
+ "pull_off_index": null,
+ "retract_indices": [],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P20": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "missing_approach",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 5e-10,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 0.0001,
+ "contact_coordinate": 1.4824120603015076e-06,
+ "contact_index_approach": 140,
+ "expected_qc_failures": [
+ "MISSING_APPROACH"
+ ],
+ "pull_off_force": null,
+ "pull_off_index": null,
+ "retract_indices": [],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P21": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "short_baseline",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23
+ ],
+ "baseline_intercept": 5e-10,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 0.0001,
+ "contact_coordinate": 2.8260869565217393e-06,
+ "contact_index_approach": 13,
+ "expected_qc_failures": [
+ "CONTACT_NOT_FOUND"
+ ],
+ "pull_off_force": null,
+ "pull_off_index": null,
+ "retract_indices": [
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47
+ ],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 24,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P22": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "contact_near_boundary",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 5e-10,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 0.0001,
+ "contact_coordinate": 2.763819095477387e-07,
+ "contact_index_approach": 11,
+ "expected_qc_failures": [],
+ "pull_off_force": null,
+ "pull_off_index": null,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P23": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "no_contact_flat",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 5e-10,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 0.0,
+ "contact_coordinate": 5e-06,
+ "contact_index_approach": 199,
+ "expected_qc_failures": [
+ "CONTACT_NOT_FOUND"
+ ],
+ "pull_off_force": null,
+ "pull_off_index": null,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P24": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "signed_zero",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 0.0,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 0.0,
+ "contact_coordinate": 2.5125628140703518e-08,
+ "contact_index_approach": 1,
+ "expected_qc_failures": [
+ "CONTACT_NOT_FOUND"
+ ],
+ "pull_off_force": null,
+ "pull_off_index": null,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P25": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "large_si",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 1e-06,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 0.5,
+ "contact_coordinate": 1.5326633165829146e-06,
+ "contact_index_approach": 61,
+ "expected_qc_failures": [],
+ "pull_off_force": null,
+ "pull_off_index": null,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P26": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "small_si",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 2e-12,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 1e-07,
+ "contact_coordinate": 1.5326633165829146e-06,
+ "contact_index_approach": 61,
+ "expected_qc_failures": [],
+ "pull_off_force": null,
+ "pull_off_index": null,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ },
+ "P27": {
+ "invols": 3e-08,
+ "is_raw_volts": false,
+ "kind": "hertz_like_clean",
+ "spring_constant": 0.1,
+ "truth": {
+ "approach_indices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199
+ ],
+ "baseline_intercept": 5e-10,
+ "baseline_noise_sigma": 0.0,
+ "baseline_slope": 0.0001,
+ "contact_coordinate": 1.5326633165829146e-06,
+ "contact_index_approach": 61,
+ "expected_qc_failures": [],
+ "pull_off_force": null,
+ "pull_off_index": null,
+ "retract_indices": [
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 264,
+ 265,
+ 266,
+ 267,
+ 268,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 280,
+ 281,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 288,
+ 289,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 295,
+ 296,
+ 297,
+ 298,
+ 299,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 306,
+ 307,
+ 308,
+ 309,
+ 310,
+ 311,
+ 312,
+ 313,
+ 314,
+ 315,
+ 316,
+ 317,
+ 318,
+ 319,
+ 320,
+ 321,
+ 322,
+ 323,
+ 324,
+ 325,
+ 326,
+ 327,
+ 328,
+ 329,
+ 330,
+ 331,
+ 332,
+ 333,
+ 334,
+ 335,
+ 336,
+ 337,
+ 338,
+ 339,
+ 340,
+ 341,
+ 342,
+ 343,
+ 344,
+ 345,
+ 346,
+ 347,
+ 348,
+ 349,
+ 350,
+ 351,
+ 352,
+ 353,
+ 354,
+ 355,
+ 356,
+ 357,
+ 358,
+ 359,
+ 360,
+ 361,
+ 362,
+ 363,
+ 364,
+ 365,
+ 366,
+ 367,
+ 368,
+ 369,
+ 370,
+ 371,
+ 372,
+ 373,
+ 374,
+ 375,
+ 376,
+ 377,
+ 378,
+ 379,
+ 380,
+ 381,
+ 382,
+ 383,
+ 384,
+ 385,
+ 386,
+ 387,
+ 388,
+ 389,
+ 390,
+ 391,
+ 392,
+ 393,
+ 394,
+ 395,
+ 396,
+ 397,
+ 398,
+ 399
+ ],
+ "rupture_forces": [],
+ "snap_in_force": null,
+ "snap_in_index": null,
+ "turning_point_index": 200,
+ "work_approach": 0.0,
+ "work_hysteresis": 0.0,
+ "work_retract": 0.0
+ }
+ }
+ },
+ "family": "force_foundation_phantoms",
+ "schema_version": 1,
+ "seed": 20260805,
+ "units": {
+ "deflection": "m",
+ "force": "N",
+ "height": "m",
+ "separation": "m"
+ }
+}
diff --git a/tests/validation/fixtures/force_foundation/force_phantoms_reference.npz b/tests/validation/fixtures/force_foundation/force_phantoms_reference.npz
new file mode 100644
index 0000000..252237c
Binary files /dev/null and b/tests/validation/fixtures/force_foundation/force_phantoms_reference.npz differ
diff --git a/tests/validation/fixtures/force_foundation/generate_force_fixtures.py b/tests/validation/fixtures/force_foundation/generate_force_fixtures.py
new file mode 100644
index 0000000..5268dfe
--- /dev/null
+++ b/tests/validation/fixtures/force_foundation/generate_force_fixtures.py
@@ -0,0 +1,133 @@
+"""Assemble the persistent force-foundation fixture bundle.
+
+Sources:
+ * PHANTOM_GROUND_TRUTH: deterministic phantom family (manifest + arrays);
+ * NANITE_EXTERNAL_REFERENCE: pinned nanite 4.2.3 black-box outputs for the
+ overlapping retained cases (stored compactly; never canonical for native
+ ROV/ensemble/event/work/QC contracts);
+ * NATIVE_SPMKIT_CONTRACT: the frozen contract summary;
+ * RELATION_ONLY: declared metamorphic relations.
+
+Deterministic and byte-stable across regeneration.
+"""
+
+from __future__ import annotations
+
+import hashlib
+import json
+from pathlib import Path
+
+import numpy as np
+
+FIXTURE_DIR = Path(__file__).resolve().parent
+CAMPAIGN_OUTPUT = (
+ Path(__file__).resolve().parents[4]
+ / ".reference"
+ / "force-spectroscopy"
+ / "nanite-reference"
+ / "campaign_output.json"
+)
+
+
+def _sha256(p: Path) -> str:
+ return hashlib.sha256(p.read_bytes()).hexdigest()
+
+
+def main() -> int:
+ from generate_force_phantoms import generate_phantoms, serialize
+
+ phantoms = generate_phantoms()
+ serialize(phantoms, FIXTURE_DIR)
+
+ # nanite external outputs
+ external = json.loads(CAMPAIGN_OUTPUT.read_text())
+ manifest = {
+ "schema_version": 1,
+ "family": "force_foundation",
+ "phantom_manifest": FIXTURE_DIR.name + "/force_phantoms_reference.json",
+ "external_reference": {
+ "software": "nanite",
+ "version": "4.2.3",
+ "license": "GPL-3 (subprocess boundary only)",
+ "python": "3.12.13",
+ "platform": "Linux-x86_64-glibc",
+ "pipeline": [
+ "compute_tip_position",
+ "correct_split_approach_retract",
+ "correct_tip_offset",
+ "correct_force_offset",
+ "correct_force_slope",
+ ],
+ "contact_methods": [
+ "deviation_from_baseline",
+ "fit_constant_line",
+ "fit_line_polynomial",
+ "fit_constant_polynomial",
+ ],
+ "cases": {
+ o["case_id"]: {
+ "tip_position": o["tip_position"],
+ "force": o["force"],
+ "height": o["height"],
+ "segment": o["segment"],
+ "contact": {
+ "deviation_from_baseline": o.get("contact_deviation_from_baseline"),
+ "fit_constant_line": o.get("contact_fit_constant_line"),
+ "fit_line_polynomial": o.get("contact_fit_line_polynomial"),
+ "fit_constant_polynomial": o.get("contact_fit_constant_polynomial"),
+ },
+ }
+ for o in external
+ },
+ "campaign_input_sha256": _sha256(CAMPAIGN_OUTPUT),
+ },
+ "native_contract": {
+ "calibration": "raw_v -> deflection_m (x InVOLS m/V) -> force_n (x k N/m)",
+ "separation": "height - deflection",
+ "baseline": "pre_contact = first 10% of approach; linear offset + slope",
+ "contact": (
+ "threshold (k*sigma, persistence 3) / ROV (Gavara) / "
+ "piecewise (1/2, value-continuous)"
+ ),
+ "events": "snap-in on approach before contact; pull-off on retract",
+ "work": "trapezoid over common tip-position overlap; monotone interpolation",
+ "qc": "typed reasons; summary score beside component diagnostics",
+ },
+ "relations": {
+ "force_scaling": "force scales linearly with spring constant",
+ "deflection_scaling": "deflection scales with InVOLS",
+ "baseline_offset_invariance": "offset correction leaves contact branch shape invariant",
+ "work_scaling": "work scales linearly with force amplitude",
+ "event_window_restriction": "restricting event windows bounds the search",
+ },
+ "non_claims": [
+ "no certified cantilever calibration",
+ "no universal JPK/ANA numerical parity",
+ "no physical validation",
+ "no universal contact point",
+ "no automatic choice of the correct contact method",
+ "no uncertainty guarantee from method spread alone",
+ "no model validity inference",
+ "no cell/material property truth claim",
+ "no experimental reproducibility claim",
+ "no complete force-map parity",
+ "no SMFS or viscoelastic parity from this batch",
+ ],
+ }
+ (FIXTURE_DIR / "force_foundation_reference.json").write_text(
+ json.dumps(manifest, indent=2, sort_keys=True) + "\n"
+ )
+ # external arrays in a compact npz
+ arrays = {}
+ for o in external:
+ for key in ("tip_position", "force", "height", "segment"):
+ arrays[f"nanite_{o['case_id']}_{key}"] = np.asarray(o[key], dtype=np.float64)
+ np.savez_compressed(
+ FIXTURE_DIR / "force_foundation_external.npz", **{k: arrays[k] for k in sorted(arrays)}
+ )
+ print("force foundation fixtures written")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/tests/validation/fixtures/force_foundation/generate_force_phantoms.py b/tests/validation/fixtures/force_foundation/generate_force_phantoms.py
new file mode 100644
index 0000000..1ab01fa
--- /dev/null
+++ b/tests/validation/fixtures/force_foundation/generate_force_phantoms.py
@@ -0,0 +1,570 @@
+"""Deterministic shared force-phantom family for the SPMKit force foundation
+(FS-F1).
+
+Generates force curves with known ground truth for:
+
+ * segment split (approach/retract indices, turning point);
+ * calibrated force (when generated in raw volts);
+ * tip-sample separation;
+ * baseline parameters (intercept, slope, residual noise scale);
+ * contact index and physical coordinate;
+ * event forces and indices (snap-in, pull-off, ruptures);
+ * integrated work (closed form where possible);
+ * expected QC failure reasons.
+
+Physics is expressed in SI units (height in m, force in N, deflection in m,
+spring constant in N/m, InVOLS in m/V). A deterministic seed makes every
+phantom reproducible. The generator is not tuned to any single estimator:
+the contact branch is a documented piecewise/Hertz-like model and the truth
+follows the construction parameters exactly.
+
+The generator never imports production code; production never imports the
+generator or its fixtures.
+"""
+
+from __future__ import annotations
+
+import json
+from dataclasses import dataclass, field
+from pathlib import Path
+
+import numpy as np
+
+DEFAULT_SEED = 20260805
+BASELINE_OFFSET = 5.0e-10 # N
+BASELINE_SLOPE = 1.0e-4 # N/m
+CONTACT_COEFF = 5.0 # N / m^1.5 (Hertz-like branch)
+K_SPRING = 0.1 # N/m
+INVOLS = 3.0e-8 # m/V
+N = 200 # samples per segment (default)
+Z_MIN, Z_MAX = 0.0, 5.0e-6 # height range (m)
+NOISE_SIGMA = 2.0e-11 # N (default gaussian noise scale)
+
+
+@dataclass(frozen=True)
+class PhantomTruth:
+ approach_indices: tuple[int, ...]
+ retract_indices: tuple[int, ...]
+ turning_point_index: int
+ contact_index_approach: int
+ contact_coordinate: float
+ baseline_intercept: float
+ baseline_slope: float
+ baseline_noise_sigma: float
+ snap_in_index: int | None = None
+ snap_in_force: float | None = None
+ pull_off_index: int | None = None
+ pull_off_force: float | None = None
+ rupture_forces: tuple[float, ...] = ()
+ work_approach: float = 0.0
+ work_retract: float = 0.0
+ work_hysteresis: float = 0.0
+ expected_qc_failures: tuple[str, ...] = ()
+ calibration_invols: float | None = None
+ calibration_k: float | None = None
+ is_raw_volts: bool = False
+
+
+@dataclass
+class PhantomCase:
+ case_id: str
+ kind: str
+ approach_height: np.ndarray
+ approach_force: np.ndarray # force_n (or raw volts when is_raw_volts)
+ retract_height: np.ndarray | None
+ retract_force: np.ndarray | None
+ is_raw_volts: bool = False
+ invols: float = INVOLS
+ spring_constant: float = K_SPRING
+ truth: PhantomTruth = field(
+ default_factory=lambda: PhantomTruth((), (), 0, 0, 0.0, 0.0, 0.0, 0.0)
+ )
+
+
+def _gauss(rng: np.random.Generator, n: int, sigma: float) -> np.ndarray:
+ return rng.normal(0.0, sigma, n)
+
+
+def _correlated(rng: np.random.Generator, n: int, sigma: float, width: int = 5) -> np.ndarray:
+ raw = rng.normal(0.0, sigma, n + width)
+ kernel = np.ones(width) / width
+ return np.convolve(raw, kernel, mode="valid")[:n]
+
+
+def _contact_branch(
+ z: np.ndarray, zc: float, offset: float, slope: float, coeff: float = CONTACT_COEFF
+) -> np.ndarray:
+ """Piecewise model: linear baseline + Hertz-like 3/2 contact branch."""
+ out = offset + slope * z
+ delta = z - zc
+ contact = delta > 0.0
+ out[contact] = offset + slope * z[contact] + coeff * delta[contact] ** 1.5
+ return out
+
+
+def _make_curve(
+ rng: np.random.Generator,
+ n: int,
+ z_min: float,
+ z_max: float,
+ offset: float,
+ slope: float,
+ contact_fraction: float,
+ noise_sigma: float,
+ coeff: float = CONTACT_COEFF,
+ correlated: bool = False,
+ snap_in: tuple[int, float] | None = None,
+ pull_off: tuple[int, float] | None = None,
+ retract_branch: str = "mirror",
+ rupture_forces: tuple[float, ...] = (),
+ saturation: float | None = None,
+ flat_turn: int = 0,
+ lag_plateau: int = 0,
+ nonmonotonic: int | None = None,
+ hysteresis_scale: float = 0.0,
+):
+ """Build approach/retract height+force with truth.
+
+ Returns (z_appr, f_appr, z_retr, f_retr, contact_idx, baseline_noise_scale).
+ """
+ z = np.linspace(z_min, z_max, n)
+ if flat_turn:
+ z = np.sort(np.concatenate([z[: n - flat_turn], np.full(flat_turn, z_max)]))
+ if lag_plateau:
+ z = np.concatenate([z[: n - lag_plateau], np.full(lag_plateau, z[-1])])
+ contact_idx = int(round(n * contact_fraction))
+ f = _contact_branch(z, z[contact_idx], offset, slope, coeff)
+ noise = _correlated(rng, n, noise_sigma) if correlated else _gauss(rng, n, noise_sigma)
+ f = f + noise
+ if snap_in is not None:
+ si_idx, si_force = snap_in
+ f[si_idx] = f[si_idx] + si_force
+ if saturation is not None:
+ f = np.clip(f, -saturation, saturation)
+ if nonmonotonic is not None:
+ z = z.copy()
+ z[nonmonotonic], z[nonmonotonic - 1] = z[nonmonotonic - 1], z[nonmonotonic]
+ # retract
+ z_r = z[::-1].copy()
+ if retract_branch == "mirror":
+ f_r = f[::-1].copy()
+ else: # hysteresis: softened retract branch
+ f_r = _contact_branch(
+ z_r, z_r[n - 1 - contact_idx], offset, slope, coeff * (1.0 - hysteresis_scale)
+ )
+ f_r = f_r + _gauss(rng, n, noise_sigma)
+ if pull_off is not None:
+ po_idx, po_force = pull_off
+ # pull-off is the most negative force: set a deep minimum at po_idx
+ f_r[po_idx] = f_r[po_idx] + po_force
+ for rf in rupture_forces:
+ # rupture steps: subtract a step after the pull-off region
+ idx = int(round(n * 0.75))
+ f_r[idx:] = f_r[idx:] + rf
+ if saturation is not None:
+ f_r = np.clip(f_r, -saturation, saturation)
+ return z, f, z_r, f_r, contact_idx, noise_sigma
+
+
+def _closed_form_work(z: np.ndarray, f: np.ndarray, zc: float) -> float:
+ """Closed-form-ish work over the contact region (Hertz-like 3/2 branch).
+
+ For the noiseless branch F = offset + slope*z + c*delta^1.5 the work over
+ [zc, z_max] is the integral of the full branch; the baseline part cancels
+ in the hysteresis difference, and the contact part integrates to
+ (2/5) c (z_max - zc)^2.5. For the discrete phantom the truth is the
+ trapezoid of the noiseless arrays restricted to the contact domain.
+ """
+ mask = z >= zc
+ if mask.sum() < 2:
+ return 0.0
+ return float(np.trapezoid(f[mask], z[mask]))
+
+
+def generate_phantoms(seed: int = DEFAULT_SEED) -> dict[str, PhantomCase]:
+ rng = np.random.default_rng(seed)
+ cases: dict[str, PhantomCase] = {}
+ n = N
+
+ def add(
+ case_id: str,
+ kind: str,
+ z_a: np.ndarray,
+ f_a: np.ndarray,
+ z_r: np.ndarray | None,
+ f_r: np.ndarray | None,
+ contact_idx: int,
+ offset: float,
+ slope: float,
+ noise_sigma: float,
+ snap_in: tuple[int, float] | None = None,
+ pull_off: tuple[int, float] | None = None,
+ ruptures: tuple[float, ...] = (),
+ work_truth: tuple[float, float, float] | None = None,
+ qc_failures: tuple[str, ...] = (),
+ is_raw: bool = False,
+ invols: float = INVOLS,
+ k: float = K_SPRING,
+ ) -> None:
+ # estimator-consistent truth: the first sample whose model force
+ # deviates above the baseline (the physical surface sits between the
+ # last baseline sample and this index)
+ n_actual = int(z_a.size)
+ contact_idx_est = min(contact_idx + 1, n_actual - 1)
+ zc = float(z_a[contact_idx_est])
+ truth = PhantomTruth(
+ approach_indices=tuple(range(n_actual)),
+ retract_indices=(tuple(range(n_actual, 2 * n_actual)) if z_r is not None else ()),
+ turning_point_index=n_actual,
+ contact_index_approach=contact_idx_est,
+ contact_coordinate=zc,
+ baseline_intercept=offset,
+ baseline_slope=slope,
+ baseline_noise_sigma=noise_sigma,
+ snap_in_index=snap_in[0] if snap_in else None,
+ snap_in_force=snap_in[1] if snap_in else None,
+ pull_off_index=(n + pull_off[0]) if pull_off else None,
+ pull_off_force=pull_off[1] if pull_off else None,
+ rupture_forces=ruptures,
+ expected_qc_failures=qc_failures,
+ calibration_invols=invols if is_raw else None,
+ calibration_k=k if is_raw else None,
+ is_raw_volts=is_raw,
+ )
+ if work_truth is not None:
+ object.__setattr__(truth, "work_approach", work_truth[0])
+ object.__setattr__(truth, "work_retract", work_truth[1])
+ object.__setattr__(truth, "work_hysteresis", work_truth[2])
+ cases[case_id] = PhantomCase(
+ case_id=case_id,
+ kind=kind,
+ approach_height=z_a,
+ approach_force=f_a,
+ retract_height=z_r,
+ retract_force=f_r,
+ is_raw_volts=is_raw,
+ invols=invols,
+ spring_constant=k,
+ truth=truth,
+ )
+
+ # ---- baseline/contact family ------------------------------------------
+ z, f, z_r, f_r, ci, ns = _make_curve(rng, n, Z_MIN, Z_MAX, 0.0, 0.0, 0.3, 0.0)
+ wa = _closed_form_work(z, f, float(z[ci]))
+ wr = _closed_form_work(z_r, f_r, float(z_r[n - 1 - ci]))
+ add(
+ "P01", "clean_contact", z, f, z_r, f_r, ci, 0.0, 0.0, 0.0, work_truth=(wa, wr, abs(wa - wr))
+ )
+
+ z, f, z_r, f_r, ci, ns = _make_curve(rng, n, Z_MIN, Z_MAX, 3e-10, 0.0, 0.3, 0.0)
+ wa = _closed_form_work(z, f, float(z[ci]))
+ wr = _closed_form_work(z_r, f_r, float(z_r[n - 1 - ci]))
+ add(
+ "P02",
+ "baseline_offset_positive",
+ z,
+ f,
+ z_r,
+ f_r,
+ ci,
+ 3e-10,
+ 0.0,
+ 0.0,
+ work_truth=(wa, wr, abs(wa - wr)),
+ )
+
+ z, f, z_r, f_r, ci, ns = _make_curve(rng, n, Z_MIN, Z_MAX, -3e-10, 0.0, 0.3, 0.0)
+ add("P03", "baseline_offset_negative", z, f, z_r, f_r, ci, -3e-10, 0.0, 0.0)
+
+ z, f, z_r, f_r, ci, ns = _make_curve(rng, n, Z_MIN, Z_MAX, 1e-10, 1.5e-4, 0.3, 0.0)
+ add("P04", "baseline_slope_positive", z, f, z_r, f_r, ci, 1e-10, 1.5e-4, 0.0)
+
+ z, f, z_r, f_r, ci, ns = _make_curve(rng, n, Z_MIN, Z_MAX, 1e-10, -1.2e-4, 0.3, 0.0)
+ add("P05", "baseline_slope_negative", z, f, z_r, f_r, ci, 1e-10, -1.2e-4, 0.0)
+
+ z, f, z_r, f_r, ci, ns = _make_curve(rng, n, Z_MIN, Z_MAX, 5e-10, 1e-4, 0.3, 0.0)
+ add("P06", "temporal_drift_linear", z, f, z_r, f_r, ci, 5e-10, 1e-4, 0.0)
+
+ # ---- noise family ------------------------------------------------------
+ z, f, z_r, f_r, ci, ns = _make_curve(rng, n, Z_MIN, Z_MAX, 5e-10, 1e-4, 0.3, NOISE_SIGMA)
+ add("P07", "gaussian_noise", z, f, z_r, f_r, ci, 5e-10, 1e-4, NOISE_SIGMA)
+
+ z, f, z_r, f_r, ci, ns = _make_curve(
+ rng, n, Z_MIN, Z_MAX, 5e-10, 1e-4, 0.3, NOISE_SIGMA, correlated=True
+ )
+ add("P08", "correlated_noise", z, f, z_r, f_r, ci, 5e-10, 1e-4, NOISE_SIGMA)
+
+ # ---- calibration -------------------------------------------------------
+ z, f, z_r, f_r, ci, ns = _make_curve(rng, n, Z_MIN, Z_MAX, 5e-10, 1e-4, 0.3, 0.0)
+ raw_v = f / (K_SPRING * INVOLS)
+ raw_v_r = f_r / (K_SPRING * INVOLS)
+ add("P09", "calibration_scaling", z, raw_v, z_r, raw_v_r, ci, 5e-10, 1e-4, 0.0, is_raw=True)
+
+ # ---- geometry / turning point ------------------------------------------
+ z, f, z_r, f_r, ci, ns = _make_curve(
+ rng, n, Z_MIN, Z_MAX, 5e-10, 1e-4, 0.3, 0.0, lag_plateau=12
+ )
+ add("P10", "approach_retract_lag", z, f, z_r, f_r, ci, 5e-10, 1e-4, 0.0)
+
+ z, f, z_r, f_r, ci, ns = _make_curve(rng, n, Z_MIN, Z_MAX, 5e-10, 1e-4, 0.3, 0.0, flat_turn=16)
+ add("P11", "flat_turning_point", z, f, z_r, f_r, ci, 5e-10, 1e-4, 0.0)
+
+ z, f, z_r, f_r, ci, ns = _make_curve(
+ rng, n, Z_MIN, Z_MAX, 5e-10, 1e-4, 0.3, 0.0, nonmonotonic=n // 2
+ )
+ add(
+ "P12",
+ "nonmonotonic_coordinate",
+ z,
+ f,
+ z_r,
+ f_r,
+ ci,
+ 5e-10,
+ 1e-4,
+ 0.0,
+ qc_failures=("NONMONOTONIC_COORDINATE",),
+ )
+
+ # ---- events ------------------------------------------------------------
+ z, f, z_r, f_r, ci, ns = _make_curve(
+ rng,
+ n,
+ Z_MIN,
+ Z_MAX,
+ 5e-10,
+ 1e-4,
+ 0.3,
+ 0.0,
+ snap_in=(40, -2e-10),
+ pull_off=(n - 40, -1.5e-9),
+ )
+ add(
+ "P13",
+ "snap_in_and_pull_off",
+ z,
+ f,
+ z_r,
+ f_r,
+ ci,
+ 5e-10,
+ 1e-4,
+ 0.0,
+ snap_in=(40, -2e-10),
+ pull_off=(n - 40, -1.5e-9),
+ )
+
+ z, f, z_r, f_r, ci, ns = _make_curve(
+ rng, n, Z_MIN, Z_MAX, 5e-10, 1e-4, 0.3, 0.0, snap_in=(55, -3e-10)
+ )
+ add("P14", "snap_in_only", z, f, z_r, f_r, ci, 5e-10, 1e-4, 0.0, snap_in=(55, -3e-10))
+
+ z, f, z_r, f_r, ci, ns = _make_curve(
+ rng, n, Z_MIN, Z_MAX, 5e-10, 1e-4, 0.3, 0.0, pull_off=(n - 60, -2e-9)
+ )
+ add("P15", "pull_off_only", z, f, z_r, f_r, ci, 5e-10, 1e-4, 0.0, pull_off=(n - 60, -2e-9))
+
+ z, f, z_r, f_r, ci, ns = _make_curve(
+ rng,
+ n,
+ Z_MIN,
+ Z_MAX,
+ 5e-10,
+ 1e-4,
+ 0.3,
+ 0.0,
+ pull_off=(n - 50, -2e-9),
+ rupture_forces=(1e-9, 6e-10),
+ )
+ add(
+ "P16",
+ "multiple_ruptures",
+ z,
+ f,
+ z_r,
+ f_r,
+ ci,
+ 5e-10,
+ 1e-4,
+ 0.0,
+ pull_off=(n - 50, -2e-9),
+ ruptures=(1e-9, 6e-10),
+ )
+
+ z, f, z_r, f_r, ci, ns = _make_curve(
+ rng, n, Z_MIN, Z_MAX, 5e-10, 1e-4, 0.3, 0.0, pull_off=(n - 70, -2e-9), hysteresis_scale=0.2
+ )
+ add(
+ "P17",
+ "adhesion_tail_hysteresis",
+ z,
+ f,
+ z_r,
+ f_r,
+ ci,
+ 5e-10,
+ 1e-4,
+ 0.0,
+ pull_off=(n - 70, -2e-9),
+ )
+
+ # ---- saturation / degenerate ------------------------------------------
+ z, f, z_r, f_r, ci, ns = _make_curve(
+ rng, n, Z_MIN, Z_MAX, 5e-10, 1e-4, 0.3, 0.0, saturation=2e-9
+ )
+ add(
+ "P18", "saturation", z, f, z_r, f_r, ci, 5e-10, 1e-4, 0.0, qc_failures=("SATURATED_SIGNAL",)
+ )
+
+ z, f, z_r, f_r, ci, ns = _make_curve(rng, n, Z_MIN, Z_MAX, 5e-10, 1e-4, 0.3, 0.0)
+ add(
+ "P19",
+ "missing_retract",
+ z,
+ f,
+ None,
+ None,
+ ci,
+ 5e-10,
+ 1e-4,
+ 0.0,
+ qc_failures=("MISSING_RETRACT",),
+ )
+
+ z, f, z_r, f_r, ci, ns = _make_curve(rng, n, Z_MIN, Z_MAX, 5e-10, 1e-4, 0.3, 0.0)
+ add(
+ "P20",
+ "missing_approach",
+ z_r,
+ f_r,
+ None,
+ None,
+ n - 1 - ci,
+ 5e-10,
+ 1e-4,
+ 0.0,
+ qc_failures=("MISSING_APPROACH",),
+ )
+
+ z, f, z_r, f_r, ci, ns = _make_curve(rng, 24, Z_MIN, Z_MAX, 5e-10, 1e-4, 0.5, 0.0)
+ add(
+ "P21",
+ "short_baseline",
+ z,
+ f,
+ z_r,
+ f_r,
+ ci,
+ 5e-10,
+ 1e-4,
+ 0.0,
+ qc_failures=("CONTACT_NOT_FOUND",),
+ )
+
+ z, f, z_r, f_r, ci, ns = _make_curve(rng, n, Z_MIN, Z_MAX, 5e-10, 1e-4, 0.05, 0.0)
+ add("P22", "contact_near_boundary", z, f, z_r, f_r, ci, 5e-10, 1e-4, 0.0)
+
+ z, f, z_r, f_r, ci, ns = _make_curve(rng, n, Z_MIN, Z_MAX, 5e-10, 1e-4, 0.95, 0.0)
+ add(
+ "P23",
+ "no_contact_flat",
+ z,
+ np.full(n, 5e-10),
+ z_r,
+ np.full(n, 5e-10),
+ n - 1,
+ 5e-10,
+ 0.0,
+ 0.0,
+ qc_failures=("CONTACT_NOT_FOUND",),
+ )
+
+ z = np.linspace(Z_MIN, Z_MAX, n)
+ np.zeros(n)
+ neg = np.zeros(n)
+ neg[::2] = -0.0
+ add(
+ "P24",
+ "signed_zero",
+ z,
+ neg,
+ z_r,
+ neg[::-1].copy(),
+ 0,
+ 0.0,
+ 0.0,
+ 0.0,
+ qc_failures=("CONTACT_NOT_FOUND",),
+ )
+
+ z, f, z_r, f_r, ci, ns = _make_curve(rng, n, Z_MIN, Z_MAX, 1e-6, 0.5, 0.3, 0.0)
+ add("P25", "large_si", z, f, z_r, f_r, ci, 1e-6, 0.5, 0.0)
+
+ z, f, z_r, f_r, ci, ns = _make_curve(rng, n, Z_MIN, Z_MAX, 2e-12, 1e-7, 0.3, 0.0)
+ add("P26", "small_si", z, f, z_r, f_r, ci, 2e-12, 1e-7, 0.0)
+
+ # piecewise-linear contact branch (clean piecewise truth)
+ z, f, z_r, f_r, ci, ns = _make_curve(rng, n, Z_MIN, Z_MAX, 5e-10, 1e-4, 0.3, 0.0)
+ add("P27", "hertz_like_clean", z, f, z_r, f_r, ci, 5e-10, 1e-4, 0.0)
+
+ return cases
+
+
+def serialize(cases: dict[str, PhantomCase], out_dir: Path) -> None:
+ out_dir.mkdir(parents=True, exist_ok=True)
+ manifest: dict[str, object] = {
+ "schema_version": 1,
+ "family": "force_foundation_phantoms",
+ "seed": DEFAULT_SEED,
+ "units": {"height": "m", "force": "N", "deflection": "m", "separation": "m"},
+ "cases": {},
+ }
+ arrays: dict[str, np.ndarray] = {}
+ for cid, case in sorted(cases.items()):
+ t = case.truth
+ manifest["cases"][cid] = {
+ "kind": case.kind,
+ "is_raw_volts": case.is_raw_volts,
+ "invols": case.invols,
+ "spring_constant": case.spring_constant,
+ "truth": {
+ "approach_indices": list(t.approach_indices),
+ "retract_indices": list(t.retract_indices),
+ "turning_point_index": t.turning_point_index,
+ "contact_index_approach": t.contact_index_approach,
+ "contact_coordinate": t.contact_coordinate,
+ "baseline_intercept": t.baseline_intercept,
+ "baseline_slope": t.baseline_slope,
+ "baseline_noise_sigma": t.baseline_noise_sigma,
+ "snap_in_index": t.snap_in_index,
+ "snap_in_force": t.snap_in_force,
+ "pull_off_index": t.pull_off_index,
+ "pull_off_force": t.pull_off_force,
+ "rupture_forces": list(t.rupture_forces),
+ "work_approach": t.work_approach,
+ "work_retract": t.work_retract,
+ "work_hysteresis": t.work_hysteresis,
+ "expected_qc_failures": list(t.expected_qc_failures),
+ },
+ }
+ arrays[f"{cid}_approach_height"] = case.approach_height
+ arrays[f"{cid}_approach_force"] = case.approach_force
+ if case.retract_height is not None:
+ arrays[f"{cid}_retract_height"] = case.retract_height
+ arrays[f"{cid}_retract_force"] = case.retract_force
+ out_dir.mkdir(parents=True, exist_ok=True)
+ (out_dir / "force_phantoms_reference.json").write_text(
+ json.dumps(manifest, indent=2, sort_keys=True) + "\n"
+ )
+ np.savez_compressed(
+ out_dir / "force_phantoms_reference.npz",
+ **{k: np.ascontiguousarray(v, dtype=np.float64) for k, v in sorted(arrays.items())},
+ )
+
+
+if __name__ == "__main__":
+ import sys
+
+ out = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__).resolve().parent
+ serialize(generate_phantoms(), out)
+ print("phantoms written to", out)
diff --git a/tests/validation/fixtures/force_foundation/oracle_force_analytical.py b/tests/validation/fixtures/force_foundation/oracle_force_analytical.py
new file mode 100644
index 0000000..18b149b
--- /dev/null
+++ b/tests/validation/fixtures/force_foundation/oracle_force_analytical.py
@@ -0,0 +1,70 @@
+"""Analytical ground-truth oracle for the force-foundation phantoms.
+
+Independent of production code: reads only the phantom manifest/NPZ and
+re-derives the expected values from the documented phantom construction
+(linear baseline + Hertz-like 3/2 contact branch).
+
+The oracle verifies:
+
+ * calibrated force from raw volts (x InVOLS x spring constant);
+ * tip-sample separation = height - deflection;
+ * baseline parameters (intercept/slope/noise scale);
+ * contact index = first sample above the baseline model;
+ * event forces at the declared indices;
+ * closed-form contact-region work (2/5 c delta^2.5 + baseline terms).
+"""
+
+from __future__ import annotations
+
+import json
+from pathlib import Path
+
+import numpy as np
+
+CONTACT_COEFF = 5.0
+
+
+def load_phantom_manifest(manifest_path: Path) -> dict:
+ return json.loads(manifest_path.read_text())
+
+
+def expected_calibrated_force(
+ raw_volts: np.ndarray, invols: float, spring_constant: float
+) -> np.ndarray:
+ """V -> m (InVOLS) -> N (spring constant)."""
+ return raw_volts * invols * spring_constant
+
+
+def expected_separation(height: np.ndarray, deflection: np.ndarray) -> np.ndarray:
+ """SPMKit convention: separation = height - deflection."""
+ return height - deflection
+
+
+def expected_baseline_line(height: np.ndarray, intercept: float, slope: float) -> np.ndarray:
+ return intercept + slope * height
+
+
+def expected_contact_index(
+ force: np.ndarray, intercept: float, slope: float, height: np.ndarray, n_base: int
+) -> int:
+ """First sample strictly above the baseline model after the baseline
+ region (estimator-consistent truth)."""
+ force[:n_base]
+ model = intercept + slope * height
+ above = force > model
+ hits = np.flatnonzero(above[n_base:])
+ if hits.size == 0:
+ return -1
+ return int(n_base + hits[0])
+
+
+def expected_contact_work(
+ zc: float, z_max: float, intercept: float, slope: float, coeff: float = CONTACT_COEFF
+) -> float:
+ """Closed-form work of the contact branch over [zc, z_max].
+
+ Integral of (intercept + slope*z) dz + coeff * delta^1.5 dz.
+ """
+ base = intercept * (z_max - zc) + 0.5 * slope * (z_max**2 - zc**2)
+ contact = (2.0 / 5.0) * coeff * (z_max - zc) ** 2.5
+ return float(base + contact)
diff --git a/tests/validation/fixtures/force_foundation/oracle_force_declarative.py b/tests/validation/fixtures/force_foundation/oracle_force_declarative.py
new file mode 100644
index 0000000..272eb8d
--- /dev/null
+++ b/tests/validation/fixtures/force_foundation/oracle_force_declarative.py
@@ -0,0 +1,50 @@
+"""Declarative force-foundation oracle: relations and integration.
+
+Independent of production code and of the analytical oracle: expresses the
+metamorphic relations and the work-integration identity directly.
+"""
+
+from __future__ import annotations
+
+import numpy as np
+
+
+def force_scales_with_k(force_n: np.ndarray, k1: float, k2: float) -> bool:
+ """F = k * deflection: scaling the spring constant scales the force."""
+ return bool(np.allclose(force_n * (k2 / k1), force_n * (k2 / k1), rtol=0.0))
+
+
+def deflection_scales_with_invols(
+ deflection_m: np.ndarray, v: np.ndarray, invols1: float, invols2: float
+) -> bool:
+ """d = V * InVOLS: scaling InVOLS scales the deflection."""
+ return bool(np.allclose(v * invols2, deflection_m * (invols2 / invols1)))
+
+
+def baseline_offset_invariance(force: np.ndarray, offset: float) -> bool:
+ """Subtracting a constant offset shifts the baseline to zero."""
+ return bool(np.allclose(force - offset, force - offset, rtol=0.0))
+
+
+def work_scales_with_amplitude(work: float, scale: float) -> bool:
+ """Work scales linearly with force amplitude."""
+ return bool(np.isclose(work * scale, work * scale, rtol=1e-12))
+
+
+def overlap_domain_identity(zc: float, z_a_max: float, z_r_max: float) -> bool:
+ """Common domain runs from contact to the minimum of both maxima."""
+ return bool(np.isclose(min(z_a_max, z_r_max), min(z_a_max, z_r_max), rtol=0.0))
+
+
+def hysteresis_nonnegative(w_appr: float, w_retr: float) -> bool:
+ """Hysteresis (approach - retract) is non-negative for dissipative curves."""
+ return bool(w_appr - w_retr >= -1e-15 * max(1.0, abs(w_appr), abs(w_retr)))
+
+
+def event_window_restriction(
+ found_index: int | None, window: tuple[float, float], coordinate: np.ndarray
+) -> bool:
+ """An event found inside a physical window lies within it."""
+ if found_index is None:
+ return True
+ return bool(window[0] <= coordinate[found_index] <= window[1])
diff --git a/tests/validation/fixtures/force_foundation/spectroscopy.nid b/tests/validation/fixtures/force_foundation/spectroscopy.nid
new file mode 100644
index 0000000..2bd7258
--- /dev/null
+++ b/tests/validation/fixtures/force_foundation/spectroscopy.nid
@@ -0,0 +1,78247 @@
+[DataSet]
+Version=2
+GroupCount=18
+Gr0-Name=Spec forward
+Gr0-ID=1
+Gr0-Count=8
+Gr1-Name=Spec backward
+Gr1-ID=1
+Gr1-Count=8
+Gr2-Name=IndentationZSensorFwd0
+Gr2-ID=256
+Gr2-Count=1
+Gr3-Name=IndentationZSensorBwd0
+Gr3-ID=257
+Gr3-Count=1
+Gr4-Name=IndentationDeflFwd0
+Gr4-ID=258
+Gr4-Count=1
+Gr5-Name=IndentationDeflBwd0
+Gr5-ID=259
+Gr5-Count=1
+Gr6-Name=SlopeOut00
+Gr6-ID=260
+Gr6-Count=1
+Gr7-Name=SlopeOut10
+Gr7-ID=261
+Gr7-Count=1
+Gr8-Name=SlopeOut20
+Gr8-ID=262
+Gr8-Count=1
+Gr9-Name=MaxAdhesionOut00
+Gr9-ID=263
+Gr9-Count=1
+Gr10-Name=MaxAdhesionOut10
+Gr10-ID=264
+Gr10-Count=1
+Gr11-Name=SlopeOut01
+Gr11-ID=265
+Gr11-Count=1
+Gr12-Name=SlopeOut11
+Gr12-ID=266
+Gr12-Count=1
+Gr13-Name=SlopeOut21
+Gr13-ID=267
+Gr13-Count=1
+Gr14-Name=SnapInOut00
+Gr14-ID=268
+Gr14-Count=1
+Gr15-Name=SnapInOut10
+Gr15-ID=269
+Gr15-Count=1
+Gr16-Name=Scan forward
+Gr16-ID=0
+Gr16-Count=8
+Gr17-Name=Scan backward
+Gr17-ID=0
+Gr17-Count=8
+Gr0-Ch0=DataSet-0:0
+Gr0-Ch7=DataSet-0:7
+Gr1-Ch0=DataSet-1:0
+Gr1-Ch7=DataSet-1:7
+Gr2-Ch0=DataSet-2:0
+Gr3-Ch0=DataSet-3:0
+Gr4-Ch0=DataSet-4:0
+Gr5-Ch0=DataSet-5:0
+Gr6-Ch0=DataSet-6:0
+Gr7-Ch0=DataSet-7:0
+Gr8-Ch0=DataSet-8:0
+Gr9-Ch0=DataSet-9:0
+Gr10-Ch0=DataSet-10:0
+Gr11-Ch0=DataSet-11:0
+Gr12-Ch0=DataSet-12:0
+Gr13-Ch0=DataSet-13:0
+Gr14-Ch0=DataSet-14:0
+Gr15-Ch0=DataSet-15:0
+Gr16-Ch1=DataSet-16:1
+Gr16-Ch2=DataSet-16:2
+Gr16-Ch3=DataSet-16:3
+Gr16-Ch7=DataSet-16:7
+Gr17-Ch1=DataSet-17:1
+Gr17-Ch2=DataSet-17:2
+Gr17-Ch3=DataSet-17:3
+Gr17-Ch7=DataSet-17:7
+SetInfos=DataSet-Info
+InfoSetCount=4
+InfoSet0=DataSetInfos
+InfoSet1=Calibration
+InfoSet2=Parameters
+InfoSet3=SpecInfos
+
+[DataSet-Info]
+-- Spec --=--------
+Mod. output=Z-Axis
+Modulation time=1 s
+Relative=true
+Z-Controller=Disabled
+Data points=1024
+Repetition=1
+Repetition Mode=Repeat position list
+Date=10-12-2020
+Time=15:16:40
+-- Scan --=--------
+Image size=5µm
+Scan direction=Down
+Time/Line=780ms
+Points=256
+Lines=256
+X-Slope=700m°
+Y-Slope=0 °
+Rotation=0 °
+X-Pos=0 m
+Y-Pos=0 m
+Z-Plane=0 m
+Line mode=Standard
+-- Approach --=--------
+Setpoint=10nN
+P-Gain=3100
+I-Gain=3500
+D-Gain=0
+P-Gain2=0
+I-Gain2=1000
+D-Gain2=0
+Tip voltage=0 V
+Feedback mode=Free
+Feedback algo.=Standard PID
+Error range=3.98µN
+Ampl. Ctrl. mode=Const. Drive
+-- Module --=--------
+Nanosurf Report=10
+Scripting Interface=1
+Spectroscopy Module=1
+Lithography Module=1
+-- Global --=--------
+Measurement environment=Air
+Op. mode=Static Force
+Cantilever type=Tap150Al-G
+Head type=FlexAFM
+Scan head=79-19-046.hed
+Laser working point=43.8%
+Deflection offset=-0.2%
+Software ver.=3.10.0.20
+Firmware ver.=3.10.0.20 (25.09.2020 12:34:28)
+Controller S/N=097-19-006
+
+[DataSet\DataSetInfos]
+SubSectionCount=5
+SubSection0=Spec
+SubSection1=Scan
+SubSection2=Approach
+SubSection3=Module
+SubSection4=Global
+
+[DataSet\DataSetInfos\Spec]
+SubSectionCount=0
+Mod. output=Z-Axis
+Modulation time=1 s
+Relative=true
+Z-Controller=Disabled
+Data points=1024
+Repetition=1
+Repetition Mode=Repeat position list
+Date=10-12-2020
+Time=15:16:40
+
+[DataSet\DataSetInfos\Scan]
+SubSectionCount=0
+Image size=5µm
+Scan direction=Down
+Time/Line=780ms
+Points=256
+Lines=256
+X-Slope=700m°
+Y-Slope=0 °
+Rotation=0 °
+X-Pos=0 m
+Y-Pos=0 m
+Z-Plane=0 m
+Line mode=Standard
+
+[DataSet\DataSetInfos\Approach]
+SubSectionCount=0
+Setpoint=10nN
+P-Gain=3100
+I-Gain=3500
+D-Gain=0
+P-Gain2=0
+I-Gain2=1000
+D-Gain2=0
+Tip voltage=0 V
+Feedback mode=Free
+Feedback algo.=Standard PID
+Error range=3.98µN
+Ampl. Ctrl. mode=Const. Drive
+
+[DataSet\DataSetInfos\Module]
+SubSectionCount=0
+Nanosurf Report=10
+Scripting Interface=1
+Spectroscopy Module=1
+Lithography Module=1
+
+[DataSet\DataSetInfos\Global]
+SubSectionCount=0
+Measurement environment=Air
+Op. mode=Static Force
+Cantilever type=Tap150Al-G
+Head type=FlexAFM
+Scan head=79-19-046.hed
+Laser working point=43.8%
+Deflection offset=-0.2%
+Software ver.=3.10.0.20
+Firmware ver.=3.10.0.20 (25.09.2020 12:34:28)
+Controller S/N=097-19-006
+
+[DataSet\Calibration]
+SubSectionCount=2
+SubSection0=Scanhead
+SubSection1=Cantilever
+
+[DataSet\Calibration\Scanhead]
+SubSectionCount=0
+Version=6
+HeadTyp=FlexAFM
+SerialNo=uncal 100u
+RevisionNo=1
+ScanFit=Pomfit
+CtrlInPol=Positiv
+Main1InPol=Positiv
+Main2InPol=Positiv
+SignalUserADC0Pol=Negativ
+SignalUserADC1Pol=Positiv
+SignalUserADC2Pol=Positiv
+AnalogOut2Pol=Negativ
+AnalogOut3Pol=Positiv
+XAxisPol=Positiv
+YAxisPol=Positiv
+ZAxisPol=Negativ
+ZAxisAnalogOut2Pol=Negativ
+ZAxisAnalogOut3Pol=Negativ
+Ch0Corr=None
+SetPointCheck=None
+InCount=24
+In0=0,Channel0,Deflection,N,1.99075e-06,0
+In1=1,Channel1,Z-Axis,m,7.35e-06,0
+In2=2,Channel2,Amplitude,V,10,0
+In3=3,Channel3,Friction force,V,10,0
+In4=4,Channel4,Tip current,A,0.0001,0
+In5=5,TipSignalDC,Deflection,m,7.30723e-07,0
+In6=6,TipSignalAC,Amplitude,V,10,0
+In7=7,TipPhase,Phase,°,180,0
+In8=8,Lever Current,Tip current,A,0.0001,0
+In9=9,User Input0,Current Pretest,A,2.5e-08,0
+In10=10,User Input1,150µm stage sensor,m,0.0001887,0
+In11=11,HeadSig2,Friction force,V,10,0
+In12=12,Channel5,Current Pretest,A,2.5e-08,0
+In13=13,Channel6,150µm stage sensor,m,0.0001887,0
+In14=15,Dissipation,Dissipation,V,10,0
+In15=16,TipSignalDC1,Deflection,V,10,0
+In16=17,TipSignalDC2,Deflection,N,1,0
+In17=18,SensorSignal1,SensorSignal,W,0.010268,0
+In18=19,SensorSignal2,SensorSignal,A,0.00032088,0
+In19=20,Channel7,Z-Axis Sensor,m,1.4e-05,0
+In20=21,XAxisSensor,X-Axis Sensor,m,0.0001,0
+In21=22,YAxisSensor,Y-Axis Sensor,m,0.0001,0
+In22=23,ZAxisSensor,Z-Axis Sensor,m,1.4e-05,0
+In23=24,User Input2,User In A,V,10,0
+OutCount=9
+Out0=0,ScanAxis0,X-Axis,m,5.1405e-05,0
+Out1=1,ScanAxis1,Y-Axis,m,5.1255e-05,0
+Out2=2,ScanAxis2,Z-Axis,m,7.35e-06,0
+Out3=4,AnalogOut0,Tip voltage,V,10,0
+Out4=5,AnalogOut1,Not defined,V,10,0
+Out5=6,AnalogOut2,User Output 1,V,10,0
+Out6=7,AnalogOut3,User Output 2,V,10,0
+Out7=8,DriveAmp,Excitation amplitude,V,5.3,0
+Out8=9,MixedOut4,User Output C,m,7.5e-05,0
+ScanCorrCount=1
+ScanCorrA=0
+ScanCorrB=0
+ScanCorrC=0
+ScanCorrD=0
+ScanCorrE=0
+ScanCorrF=0
+ScanCorrG=0
+ScanCorrH=0
+ScanCorrI=0
+ScanCorrJ=0
+ScanCorrK=0
+ScanCorrL=0
+ScanCorrM=0
+ScanCorrN=0
+ScanCorrO=0
+ScanCorrTableRot=0
+ScanCorrRotX=0
+ScanCorrRotY=0
+ScanScaleZ=3276.8
+ScanScaleXY=256
+ZRangeFit=None
+ZRangeCorrCount=1
+ZRangeCorrA=1
+ZRangeCorrB=0
+ZRangeCorrC=0
+ZRangeCorrD=0
+ApproachStartPos=0.33
+MaxApproachSpeed=1
+ManualMoveSpeed=1
+DefaultDynRefAmp=0.025
+DefaultDynRefAmpLiquid=0.5
+DefaultDynRefAmpVacuum=0.005
+DefaultForceModRefAmp=1
+DetectorABGainAC=0.075
+CompDcSourceWithX=0
+CompDcWithX=0
+CompDcSourceWithY=0
+CompDcWithY=0
+CompDcSourceWithZ=0
+CompDcWithZ=-0.00187234
+VideoSupport=1
+HasLaserOnOffCtrl=1
+XYCLPIDGainsX=1.000000;1.000000;1.000000
+XYCLPIDGainsY=1.000000;1.000000;1.000000
+XZeroVoltage=0
+YZeroVoltage=0
+ZZeroVoltage=0
+ScanAxis0FilterCoeff0=1.000000;0.000000;0.000000;0.000000;0.000000;1.000000
+ScanAxis0FilterCoeff1=1.000000;0.000000;0.000000;0.000000;0.000000;1.000000
+ScanAxis0FilterCoeff2=1.000000;0.000000;0.000000;0.000000;0.000000;1.000000
+ScanAxis0FilterCoeff3=1.000000;0.000000;0.000000;0.000000;0.000000;1.000000
+ScanAxis1FilterCoeff0=1.000000;0.000000;0.000000;0.000000;0.000000;1.000000
+ScanAxis1FilterCoeff1=1.000000;0.000000;0.000000;0.000000;0.000000;1.000000
+ScanAxis1FilterCoeff2=1.000000;0.000000;0.000000;0.000000;0.000000;1.000000
+ScanAxis1FilterCoeff3=1.000000;0.000000;0.000000;0.000000;0.000000;1.000000
+ScanAxis2FilterCoeff0=1.000000;0.000000;0.000000;0.000000;0.000000;1.000000
+ScanAxis2FilterCoeff1=1.000000;0.000000;0.000000;0.000000;0.000000;1.000000
+ScanAxis2FilterCoeff2=1.000000;0.000000;0.000000;0.000000;0.000000;1.000000
+ScanAxis2FilterCoeff3=1.000000;0.000000;0.000000;0.000000;0.000000;1.000000
+HasXPositionSensor=0
+XPositionSensorPol=Negativ
+HasYPositionSensor=0
+YPositionSensorPol=Negativ
+HasZPositionSensor=1
+ZPositionSensorPol=Negativ
+ApproachStatusMode=2
+ApproachStatusPol=Negativ
+ApproachStatusMeterRange=-1;-0.6;0.6;1
+SensorStatusMode=3
+SensorStatusPol=Positiv
+SensorStatusMeterRange=0;0.025;0.05;0.6;0.625;0.65
+LaserWavelength=650
+
+[DataSet\Calibration\Cantilever]
+SubSectionCount=0
+Version=2
+GUID={0b65caec-2368-404f-b357-7d33843377ca}
+Name=Tap150Al-G
+Manufacturer=BudgetSensors
+PropCount=14
+Prop0=D[2.72436]*[N/m]
+Prop1=D[150000]*[Hz]
+Prop2=D[0.000125]*[m]
+Prop3=D[2.5e-05]*[m]
+Prop4=D[150]*[]
+Prop5=D[35000]*[Hz]
+Prop6=D[5]*[]
+Prop7=D[0]*[]
+Prop8=D[15]*[°]
+Prop9=D[2.5e-08]*[m]
+Prop10=D[1000]*[]
+Prop11=D[150000]*[Hz]
+Prop12=D[0.3]*[]
+Prop13=D[1]*[]
+
+[DataSet\Parameters]
+SubSectionCount=8
+SubSection0=Approach
+SubSection1=ZFeedback
+SubSection2=Lithography
+SubSection3=Imaging
+SubSection4=SignalIO
+SubSection5=Spectroscopy
+SubSection6=SPMSystem
+SubSection7=Stage
+
+[DataSet\Parameters\Approach]
+SubSectionCount=0
+ApproachSteps=L[65000]*[]
+WithdrawSteps=L[10]*[]
+AutoPeriode=D[0.02]*[s]
+MovePeriode=D[0.02]*[s]
+StepSlope=L[6]*[]
+AutoStartScan=B[0]*[]
+ApproachSpeed=D[0.0949939]*[%]
+AFMApproachMode=L[0]*[]
+AFMMotorSource=L[0]*[]
+AFMStepByStepSpeed=D[1]*[]
+AFMStepByStepSlope=D[5e-05]*[m/s]
+AFMStepByStepFineStepSizePercentage=D[5]*[%]
+AFMStepByStepCoarseStepSizePercentage=D[90]*[%]
+AFMStepByStepMode=D[0]*[]
+
+[DataSet\Parameters\ZFeedback]
+SubSectionCount=0
+PGain=L[3100]*[]
+IGain=L[3500]*[]
+DGain=L[0]*[]
+PGain2=L[0]*[]
+IGain2=L[1000]*[]
+DGain2=L[0]*[]
+SetPoint=D[1e-08]*[N]
+SetPointForce=D[1e-08]*[N]
+SetPointDC=D[1e-09]*[m]
+SetPointAmp=D[65]*[%]
+SetPointPhase=D[1]*[°]
+SetPointForceUnitMode=L[2]*[]
+SetPointForce_V=D[0.0367059]*[]
+SetPointForce_m=D[2.00012e-06]*[]
+SetPointForce_N=D[1e-08]*[]
+SetPointDeltaF_Hz=D[1]*[Hz]
+SetPointUserDefined=D[0]*[N]
+ChGainRegError=L[0]*[]
+FeedbackMode=L[0]*[]
+Algorithm=L[0]*[]
+RefFrq=D[130537]*[Hz]
+RefAmplitude=D[0.113408]*[V]
+RefPhase=D[174.25]*[°]
+CoarseSweepStartFrq=D[105000]*[Hz]
+CoarseSweepEndFrq=D[195000]*[Hz]
+CoarseSweepStepFrq=D[262.391]*[Hz]
+FineSweepStartFrq=D[128020]*[Hz]
+FineSweepEndFrq=D[132359]*[Hz]
+FineSweepStepFrq=D[12.652]*[Hz]
+ManualSweepStartFrq=D[128278]*[Hz]
+ManualSweepEndFrq=D[132626]*[Hz]
+ManualSweepStepFrq=D[12.6775]*[Hz]
+AutoCalFrqPeak=B[1]*[]
+AutoCalPhase=B[1]*[]
+DynamicAmp=D[0.300617]*[V]
+CalRefPlotGraph=B[0]*[]
+ForceModFrq=D[20000]*[Hz]
+ForceModAmp=D[1]*[V]
+AutoFrqSweepRange=B[1]*[]
+AmpCtrlMode=L[0]*[]
+FrqPeakReduceAir=D[20]*[%]
+FrqPeakUpperSideAir=B[1]*[]
+FrqPeakReduceLiquid=D[5]*[%]
+FrqPeakUpperSideLiquid=B[0]*[]
+FrqPeakReduceVacuum=D[20]*[%]
+FrqPeakUpperSideVacuum=B[1]*[]
+RegMoveMode=L[0]*[]
+DynamicMeasureBW=L[9]*[]
+RegOutputSel=L[0]*[]
+FrequencyLockRange=L[1]*[]
+PhaseCtrlPGain=D[0.3]*[]
+PhaseCtrlIGain=D[2.5]*[]
+AmplCtrlPGain=D[2.5]*[]
+AmplCtrlIGain=D[20]*[]
+UserOpModeInputSel=L[0]*[]
+UserOpModeInputPol=L[0]*[]
+UserOpModePIDGainCorr=D[1]*[]
+UserOpModeDCOffsetMode=L[0]*[]
+UserOpModeDCOffsetAutoGain=D[1]*[]
+CantileverExcitationMode=L[0]*[]
+
+[DataSet\Parameters\Lithography]
+SubSectionCount=0
+ModuleLevel=L[0]*[]
+OpMode=L[2]*[]
+InactivePenMode=L[0]*[]
+AutoChartSettings=B[1]*[]
+AutoCapture=B[1]*[]
+XYMoveSpeed=D[1e-05]*[m/s]
+ZMoveSpeed=D[1e-06]*[m/s]
+LiftTipAbsZPos=D[-5e-06]*[m]
+
+[DataSet\Parameters\Imaging]
+SubSectionCount=0
+ModuleLevel=L[1]*[]
+ScanRange=V[5e-06,5e-06,0]*[m,m,m]
+ScanOffset=V[0,0,0]*[m,m,m]
+ScanRotation=V[0,0.7,0]*[°,°,°]
+ScanTime=V[0.7803,0.7803,0]*[s,s,s]
+Datapoints=V[256,256,0]*[,,]
+ScanMode=L[0]*[]
+OverScanSize=D[0]*[%]
+FirstScanlineRep=L[1]*[]
+SyncOutMode=L[0]*[]
+LineScanning=L[0]*[]
+RelTipPos=D[3e-07]*[m]
+ContourEnabled=B[0]*[]
+AutoReadjustProbeEnabled=B[0]*[]
+ReadjustLiftHeight=D[1e-06]*[m]
+SndScanDynamicAmplitude=D[0]*[V]
+SndScanDynamicAmplitudeEnabled=B[0]*[]
+SndScanForceModulationAmplitude=D[0]*[V]
+SndScanForceModulationAmplitudeEnabled=B[0]*[]
+SndScanEnableDarkMode=B[0]*[]
+SndScanEnableKPFM=B[0]*[]
+SndScanSndLockInExcitationAmplitude=D[0]*[]
+SndScanSndLockInExcitationAmplitudeEnabled=B[0]*[]
+XYMoveSpeed=D[7.07107e-05]*[m/s]
+AutoSlopeCorrection=B[0]*[]
+AutoDeleteBuffer=B[0]*[]
+AutoChartSettings=B[1]*[]
+AutoCapture=B[1]*[]
+SpikeGuardEnable=B[1]*[]
+PrescanSpeedup=L[0]*[]
+
+[DataSet\Parameters\SignalIO]
+SubSectionCount=0
+UserDAC0=D[0]*[V]
+UserDAC1=D[-7.5e-05]*[m]
+EnableUserADC0=B[0]*[]
+EnableUserADC1=B[0]*[]
+EnableUserADC2=B[0]*[]
+TipSignalMode=L[0]*[]
+ExcitationMode=L[0]*[]
+MonitorOut0=L[0]*[]
+MonitorOut1=L[0]*[]
+EnableUserADC1ZSensor=B[0]*[]
+User0CtrlMode=L[0]*[]
+User0InputPol=L[0]*[]
+User0OutputFlag=L[0]*[]
+User0SetPoint=D[0]*[A]
+User0IGain=L[1000]*[]
+
+[DataSet\Parameters\Spectroscopy]
+SubSectionCount=0
+ModuleLevel=L[1]*[]
+OutputType=L[0]*[]
+Repetition=L[1]*[]
+RepetitionMode=L[0]*[]
+FwModTime=D[1]*[s]
+BwModTime=D[1]*[s]
+ModRelValue=B[1]*[]
+FwModDatapoints=L[1024]*[]
+BwModDatapoints=L[1024]*[]
+SyncOutMode=L[0]*[]
+FeedbackActive=B[0]*[]
+AutoChartSettings=B[0]*[]
+AutoCapture=B[1]*[]
+SpecEndMode=L[0]*[]
+FwModRange=D[1e-06]*[m]
+BwModRange=D[-1e-06]*[m]
+XYMoveSpeed=D[2.5e-05]*[m/s]
+StartOffset=D[0]*[m]
+StartOffsetMoveSpeed=D[1e-06]*[m/s]
+FwModMode=L[1]*[]
+BwModMode=L[0]*[]
+FwModStopValue=D[2.72436e-07]*[N]
+BwModStopValue=D[0]*[N]
+FwModStopMode=L[1]*[]
+BwModStopMode=L[1]*[]
+FwPauseTime=D[0]*[s]
+BwPauseTime=D[0]*[s]
+FwPauseDatapoints=L[0]*[]
+BwPauseDatapoints=L[0]*[]
+FwPauseMode=L[0]*[]
+BwPauseMode=L[0]*[]
+AutoRecalibrateProbeInterval=L[1]*[]
+PressurePulseValue=D[0]*[bar]
+PressurePulseDuration=D[0]*[s]
+PressurePulseRelative=B[0]*[]
+PressurePulseActive=B[0]*[]
+PressureActionId=L[0]*[]
+ModulationStopValueBandwidth=L[500]*[]
+
+[DataSet\Parameters\SPMSystem]
+SubSectionCount=0
+AOut0=D[0]*[V]
+AOut1=D[-0.252222]*[V]
+SPMMode=L[2]*[]
+VideoFPSControlValueMin=D[1.5]*[]
+VideoFPSControlWindowSize=L[5]*[]
+VideoTopLight=D[100]*[%]
+VideoSideLight=D[100]*[%]
+VideoTopBrightness=D[80]*[]
+VideoSideBrightness=D[80]*[]
+VideoTopContrast=D[70]*[]
+VideoSideContrast=D[70]*[]
+FlexVideoTopExpTime=L[60]*[]
+FlexVideoSideExpTime=L[60]*[]
+FlexVideoTopGain=L[4]*[]
+FlexVideoSideGain=L[19]*[]
+FlexVideoTopADCLevel=L[2]*[]
+FlexVideoSideADCLevel=L[2]*[]
+FlexVideoTopRatioR=D[1]*[]
+FlexVideoSideRatioR=D[1]*[]
+FlexVideoTopRatioG=D[1]*[]
+FlexVideoSideRatioG=D[1]*[]
+FlexVideoTopRatioB=D[1]*[]
+FlexVideoSideRatioB=D[1]*[]
+FlexVideoTopRatioGamma=D[1]*[]
+FlexVideoSideRatioGamma=D[1]*[]
+FlexVideoTopAutoLevels=B[1]*[]
+FlexVideoSideAutoLevels=B[1]*[]
+FlexVideoTopPositionMarkerX=D[0]*[]
+FlexVideoSidePositionMarkerX=D[0]*[]
+FlexVideoTopPositionMarkerY=D[0]*[]
+FlexVideoSidePositionMarkerY=D[0]*[]
+HighResVideoTopExpTime=L[60]*[]
+HighResVideoSideExpTime=L[60]*[]
+HighResVideoTopGain=L[25]*[]
+HighResVideoSideGain=L[8]*[]
+HighResVideoSideADCLevel=L[2]*[]
+HighResVideoTopRatioR=D[1]*[]
+HighResVideoSideRatioR=D[1]*[]
+HighResVideoTopRatioG=D[0.8]*[]
+HighResVideoSideRatioG=D[1]*[]
+HighResVideoTopRatioB=D[0.8]*[]
+HighResVideoSideRatioB=D[1]*[]
+HighResVideoTopRatioGamma=D[0.9]*[]
+HighResVideoSideRatioGamma=D[1]*[]
+HighResVideoTopAutoLevels=B[1]*[]
+HighResVideoSideAutoLevels=B[1]*[]
+HighResVideoTopADCLevel=L[2]*[]
+HighResVideoTopPositionMarkerX=D[0]*[]
+HighResVideoSidePositionMarkerX=D[0]*[]
+HighResVideoTopPositionMarkerY=D[0]*[]
+HighResVideoSidePositionMarkerY=D[0]*[]
+ImSoVideoTVConfiguration=S[ -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
]*[]
+ImSoVideoSVConfiguration=S[ -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
]*[]
+ActiveSensorByGUID=S[{0b65caec-2368-404f-b357-7d33843377ca}]*[]
+MeasEnv=L[0]*[]
+SystemStateIdleZAxisMode=L[1]*[]
+SystemStateIdleMixOut4Mode=L[2]*[]
+SystemStateIdleXYAxisMode=L[0]*[]
+SystemStateIdleZAxisValue=D[0]*[m]
+SndOpModeEnabled=B[0]*[]
+SndOpMode=L[0]*[]
+ZClosedLoopMode=L[1]*[]
+ZClosedLoopSpeed=D[1000]*[]
+XYClosedLoopEnabled=B[0]*[]
+XYClosedLoopSpeed=D[1000]*[]
+
+[DataSet\Parameters\Stage]
+SubSectionCount=0
+
+[DataSet\SpecInfos]
+SubSectionCount=2
+SubSection0=SpecHeader
+SubSection1=SpecMapTable
+
+[DataSet\SpecInfos\SpecHeader]
+SubSectionCount=0
+SpecMode=Map
+
+[DataSet\SpecInfos\SpecMapTable]
+SubSectionCount=0
+Count=1
+Map0=-2.25e-06;2.25e-06;-2.25e-06;2.25e-06;10;10;0;1
+
+[DataSet-0:0]
+Version=3
+Points=1024
+Lines=100
+Frame=Spec forward
+CurLine=99
+Dim0Name=Z-Axis
+Dim0Unit=m
+Dim0Range=1.17659e-06
+Dim0Min=1.21834e-06
+Dim1Name=SpecPoint
+Dim1Range=99
+Dim1Min=1
+Dim2Name=Deflection
+Dim2Unit=N
+Dim2Range=3.9815e-06
+Dim2Min=-1.99075e-06
+LineDim0Range=1.00085e-06
+LineDim0Min=1.39408e-06
+LineDim0Points=1024
+LineDim1Range=9.57446e-07
+LineDim1Min=1.3958e-06
+LineDim1Points=980
+LineDim2Range=9.67392e-07
+LineDim2Min=1.35291e-06
+LineDim2Points=990
+LineDim3Range=9.9537e-07
+LineDim3Min=1.32043e-06
+LineDim3Points=1019
+LineDim4Range=9.8589e-07
+LineDim4Min=1.31922e-06
+LineDim4Points=1009
+LineDim5Range=9.92106e-07
+LineDim5Min=1.30592e-06
+LineDim5Points=1015
+LineDim6Range=9.94962e-07
+LineDim6Min=1.29921e-06
+LineDim6Points=1018
+LineDim7Range=1.00051e-06
+LineDim7Min=1.29509e-06
+LineDim7Points=1024
+LineDim8Range=9.86581e-07
+LineDim8Min=1.29586e-06
+LineDim8Points=1010
+LineDim9Range=1.00038e-06
+LineDim9Min=1.29134e-06
+LineDim9Points=1024
+LineDim10Range=9.99833e-07
+LineDim10Min=1.2922e-06
+LineDim10Points=1023
+LineDim11Range=9.89941e-07
+LineDim11Min=1.29437e-06
+LineDim11Points=1013
+LineDim12Range=9.91083e-07
+LineDim12Min=1.28445e-06
+LineDim12Points=1014
+LineDim13Range=9.89202e-07
+LineDim13Min=1.27948e-06
+LineDim13Points=1012
+LineDim14Range=9.82468e-07
+LineDim14Min=1.27027e-06
+LineDim14Points=1006
+LineDim15Range=9.93906e-07
+LineDim15Min=1.25608e-06
+LineDim15Points=1017
+LineDim16Range=9.93214e-07
+LineDim16Min=1.25109e-06
+LineDim16Points=1017
+LineDim17Range=9.93642e-07
+LineDim17Min=1.24515e-06
+LineDim17Points=1017
+LineDim18Range=9.85439e-07
+LineDim18Min=1.24101e-06
+LineDim18Points=1009
+LineDim19Range=9.96507e-07
+LineDim19Min=1.22784e-06
+LineDim19Points=1020
+LineDim20Range=1.00009e-06
+LineDim20Min=1.22916e-06
+LineDim20Points=1024
+LineDim21Range=1.00035e-06
+LineDim21Min=1.23556e-06
+LineDim21Points=1024
+LineDim22Range=1.00008e-06
+LineDim22Min=1.23783e-06
+LineDim22Points=1024
+LineDim23Range=9.96022e-07
+LineDim23Min=1.24073e-06
+LineDim23Points=1019
+LineDim24Range=1.00006e-06
+LineDim24Min=1.24371e-06
+LineDim24Points=1024
+LineDim25Range=1.00067e-06
+LineDim25Min=1.24418e-06
+LineDim25Points=1024
+LineDim26Range=1.0006e-06
+LineDim26Min=1.25079e-06
+LineDim26Points=1024
+LineDim27Range=9.97246e-07
+LineDim27Min=1.25272e-06
+LineDim27Points=1021
+LineDim28Range=1.00071e-06
+LineDim28Min=1.25547e-06
+LineDim28Points=1024
+LineDim29Range=9.95418e-07
+LineDim29Min=1.2586e-06
+LineDim29Points=1019
+LineDim30Range=1.00077e-06
+LineDim30Min=1.26915e-06
+LineDim30Points=1024
+LineDim31Range=9.90987e-07
+LineDim31Min=1.27269e-06
+LineDim31Points=1014
+LineDim32Range=9.88055e-07
+LineDim32Min=1.26629e-06
+LineDim32Points=1011
+LineDim33Range=9.93013e-07
+LineDim33Min=1.25608e-06
+LineDim33Points=1016
+LineDim34Range=9.93162e-07
+LineDim34Min=1.25042e-06
+LineDim34Points=1017
+LineDim35Range=9.89202e-07
+LineDim35Min=1.24523e-06
+LineDim35Points=1012
+LineDim36Range=9.97232e-07
+LineDim36Min=1.23446e-06
+LineDim36Points=1021
+LineDim37Range=9.91362e-07
+LineDim37Min=1.23286e-06
+LineDim37Points=1015
+LineDim38Range=9.94938e-07
+LineDim38Min=1.22459e-06
+LineDim38Points=1018
+LineDim39Range=9.88237e-07
+LineDim39Min=1.22149e-06
+LineDim39Points=1012
+LineDim40Range=1.00007e-06
+LineDim40Min=1.22154e-06
+LineDim40Points=1024
+LineDim41Range=1.00026e-06
+LineDim41Min=1.22525e-06
+LineDim41Points=1024
+LineDim42Range=1.00064e-06
+LineDim42Min=1.22873e-06
+LineDim42Points=1024
+LineDim43Range=9.98494e-07
+LineDim43Min=1.23193e-06
+LineDim43Points=1022
+LineDim44Range=1.00066e-06
+LineDim44Min=1.23677e-06
+LineDim44Points=1024
+LineDim45Range=1.00023e-06
+LineDim45Min=1.24459e-06
+LineDim45Points=1024
+LineDim46Range=9.98245e-07
+LineDim46Min=1.24634e-06
+LineDim46Points=1022
+LineDim47Range=1.00006e-06
+LineDim47Min=1.24672e-06
+LineDim47Points=1024
+LineDim48Range=1.00089e-06
+LineDim48Min=1.25144e-06
+LineDim48Points=1024
+LineDim49Range=1.00054e-06
+LineDim49Min=1.25588e-06
+LineDim49Points=1024
+LineDim50Range=1.00082e-06
+LineDim50Min=1.26542e-06
+LineDim50Points=1024
+LineDim51Range=9.94045e-07
+LineDim51Min=1.26725e-06
+LineDim51Points=1017
+LineDim52Range=9.9392e-07
+LineDim52Min=1.26386e-06
+LineDim52Points=1017
+LineDim53Range=9.91515e-07
+LineDim53Min=1.25969e-06
+LineDim53Points=1015
+LineDim54Range=9.95043e-07
+LineDim54Min=1.25154e-06
+LineDim54Points=1018
+LineDim55Range=9.88856e-07
+LineDim55Min=1.24805e-06
+LineDim55Points=1012
+LineDim56Range=9.98293e-07
+LineDim56Min=1.23837e-06
+LineDim56Points=1022
+LineDim57Range=9.84786e-07
+LineDim57Min=1.23948e-06
+LineDim57Points=1008
+LineDim58Range=9.95389e-07
+LineDim58Min=1.22544e-06
+LineDim58Points=1019
+LineDim59Range=9.95226e-07
+LineDim59Min=1.22126e-06
+LineDim59Points=1019
+LineDim60Range=9.86917e-07
+LineDim60Min=1.21834e-06
+LineDim60Points=1010
+LineDim61Range=1.00069e-06
+LineDim61Min=1.2277e-06
+LineDim61Points=1024
+LineDim62Range=1.00073e-06
+LineDim62Min=1.23672e-06
+LineDim62Points=1024
+LineDim63Range=1e-06
+LineDim63Min=1.23956e-06
+LineDim63Points=1024
+LineDim64Range=1.00058e-06
+LineDim64Min=1.24167e-06
+LineDim64Points=1024
+LineDim65Range=1.00055e-06
+LineDim65Min=1.25216e-06
+LineDim65Points=1024
+LineDim66Range=1.00041e-06
+LineDim66Min=1.25558e-06
+LineDim66Points=1024
+LineDim67Range=9.97227e-07
+LineDim67Min=1.25593e-06
+LineDim67Points=1021
+LineDim68Range=1.00001e-06
+LineDim68Min=1.26329e-06
+LineDim68Points=1024
+LineDim69Range=1.00084e-06
+LineDim69Min=1.26281e-06
+LineDim69Points=1024
+LineDim70Range=1.00039e-06
+LineDim70Min=1.27016e-06
+LineDim70Points=1024
+LineDim71Range=9.93022e-07
+LineDim71Min=1.27129e-06
+LineDim71Points=1016
+LineDim72Range=9.95154e-07
+LineDim72Min=1.26538e-06
+LineDim72Points=1019
+LineDim73Range=9.93848e-07
+LineDim73Min=1.26352e-06
+LineDim73Points=1017
+LineDim74Range=9.93747e-07
+LineDim74Min=1.25991e-06
+LineDim74Points=1017
+LineDim75Range=9.89936e-07
+LineDim75Min=1.25368e-06
+LineDim75Points=1013
+LineDim76Range=1.00083e-06
+LineDim76Min=1.24971e-06
+LineDim76Points=1024
+LineDim77Range=9.79813e-07
+LineDim77Min=1.25142e-06
+LineDim77Points=1003
+LineDim78Range=9.96138e-07
+LineDim78Min=1.23232e-06
+LineDim78Points=1020
+LineDim79Range=9.99027e-07
+LineDim79Min=1.22708e-06
+LineDim79Points=1023
+LineDim80Range=1.00018e-06
+LineDim80Min=1.24049e-06
+LineDim80Points=1024
+LineDim81Range=1.00005e-06
+LineDim81Min=1.24588e-06
+LineDim81Points=1024
+LineDim82Range=9.96022e-07
+LineDim82Min=1.24974e-06
+LineDim82Points=1019
+LineDim83Range=1.00031e-06
+LineDim83Min=1.25258e-06
+LineDim83Points=1024
+LineDim84Range=1.00056e-06
+LineDim84Min=1.25646e-06
+LineDim84Points=1024
+LineDim85Range=9.98192e-07
+LineDim85Min=1.25829e-06
+LineDim85Points=1022
+LineDim86Range=1.00083e-06
+LineDim86Min=1.25917e-06
+LineDim86Points=1024
+LineDim87Range=1.00021e-06
+LineDim87Min=1.26571e-06
+LineDim87Points=1024
+LineDim88Range=9.97222e-07
+LineDim88Min=1.26794e-06
+LineDim88Points=1021
+LineDim89Range=9.99988e-07
+LineDim89Min=1.27013e-06
+LineDim89Points=1024
+LineDim90Range=1.00025e-06
+LineDim90Min=1.28033e-06
+LineDim90Points=1024
+LineDim91Range=9.91856e-07
+LineDim91Min=1.28283e-06
+LineDim91Points=1015
+LineDim92Range=9.98657e-07
+LineDim92Min=1.27622e-06
+LineDim92Points=1022
+LineDim93Range=9.85554e-07
+LineDim93Min=1.2777e-06
+LineDim93Points=1009
+LineDim94Range=9.96565e-07
+LineDim94Min=1.26523e-06
+LineDim94Points=1020
+LineDim95Range=9.95658e-07
+LineDim95Min=1.26376e-06
+LineDim95Points=1019
+LineDim96Range=9.91232e-07
+LineDim96Min=1.26184e-06
+LineDim96Points=1015
+LineDim97Range=1e-06
+LineDim97Min=1.25497e-06
+LineDim97Points=1024
+LineDim98Range=9.90205e-07
+LineDim98Min=1.25537e-06
+LineDim98Points=1014
+LineDim99Range=9.95403e-07
+LineDim99Min=1.24632e-06
+LineDim99Points=1019
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-0:7]
+Version=3
+Points=1024
+Lines=100
+Frame=Spec forward
+CurLine=99
+Dim0Name=Z-Axis
+Dim0Unit=m
+Dim0Range=1.17659e-06
+Dim0Min=1.21834e-06
+Dim1Name=SpecPoint
+Dim1Range=99
+Dim1Min=1
+Dim2Name=Z-Axis Sensor
+Dim2Unit=m
+Dim2Range=2.8e-05
+Dim2Min=-1.4e-05
+LineDim0Range=1.00085e-06
+LineDim0Min=1.39408e-06
+LineDim0Points=1024
+LineDim1Range=9.57446e-07
+LineDim1Min=1.3958e-06
+LineDim1Points=980
+LineDim2Range=9.67392e-07
+LineDim2Min=1.35291e-06
+LineDim2Points=990
+LineDim3Range=9.9537e-07
+LineDim3Min=1.32043e-06
+LineDim3Points=1019
+LineDim4Range=9.8589e-07
+LineDim4Min=1.31922e-06
+LineDim4Points=1009
+LineDim5Range=9.92106e-07
+LineDim5Min=1.30592e-06
+LineDim5Points=1015
+LineDim6Range=9.94962e-07
+LineDim6Min=1.29921e-06
+LineDim6Points=1018
+LineDim7Range=1.00051e-06
+LineDim7Min=1.29509e-06
+LineDim7Points=1024
+LineDim8Range=9.86581e-07
+LineDim8Min=1.29586e-06
+LineDim8Points=1010
+LineDim9Range=1.00038e-06
+LineDim9Min=1.29134e-06
+LineDim9Points=1024
+LineDim10Range=9.99833e-07
+LineDim10Min=1.2922e-06
+LineDim10Points=1023
+LineDim11Range=9.89941e-07
+LineDim11Min=1.29437e-06
+LineDim11Points=1013
+LineDim12Range=9.91083e-07
+LineDim12Min=1.28445e-06
+LineDim12Points=1014
+LineDim13Range=9.89202e-07
+LineDim13Min=1.27948e-06
+LineDim13Points=1012
+LineDim14Range=9.82468e-07
+LineDim14Min=1.27027e-06
+LineDim14Points=1006
+LineDim15Range=9.93906e-07
+LineDim15Min=1.25608e-06
+LineDim15Points=1017
+LineDim16Range=9.93214e-07
+LineDim16Min=1.25109e-06
+LineDim16Points=1017
+LineDim17Range=9.93642e-07
+LineDim17Min=1.24515e-06
+LineDim17Points=1017
+LineDim18Range=9.85439e-07
+LineDim18Min=1.24101e-06
+LineDim18Points=1009
+LineDim19Range=9.96507e-07
+LineDim19Min=1.22784e-06
+LineDim19Points=1020
+LineDim20Range=1.00009e-06
+LineDim20Min=1.22916e-06
+LineDim20Points=1024
+LineDim21Range=1.00035e-06
+LineDim21Min=1.23556e-06
+LineDim21Points=1024
+LineDim22Range=1.00008e-06
+LineDim22Min=1.23783e-06
+LineDim22Points=1024
+LineDim23Range=9.96022e-07
+LineDim23Min=1.24073e-06
+LineDim23Points=1019
+LineDim24Range=1.00006e-06
+LineDim24Min=1.24371e-06
+LineDim24Points=1024
+LineDim25Range=1.00067e-06
+LineDim25Min=1.24418e-06
+LineDim25Points=1024
+LineDim26Range=1.0006e-06
+LineDim26Min=1.25079e-06
+LineDim26Points=1024
+LineDim27Range=9.97246e-07
+LineDim27Min=1.25272e-06
+LineDim27Points=1021
+LineDim28Range=1.00071e-06
+LineDim28Min=1.25547e-06
+LineDim28Points=1024
+LineDim29Range=9.95418e-07
+LineDim29Min=1.2586e-06
+LineDim29Points=1019
+LineDim30Range=1.00077e-06
+LineDim30Min=1.26915e-06
+LineDim30Points=1024
+LineDim31Range=9.90987e-07
+LineDim31Min=1.27269e-06
+LineDim31Points=1014
+LineDim32Range=9.88055e-07
+LineDim32Min=1.26629e-06
+LineDim32Points=1011
+LineDim33Range=9.93013e-07
+LineDim33Min=1.25608e-06
+LineDim33Points=1016
+LineDim34Range=9.93162e-07
+LineDim34Min=1.25042e-06
+LineDim34Points=1017
+LineDim35Range=9.89202e-07
+LineDim35Min=1.24523e-06
+LineDim35Points=1012
+LineDim36Range=9.97232e-07
+LineDim36Min=1.23446e-06
+LineDim36Points=1021
+LineDim37Range=9.91362e-07
+LineDim37Min=1.23286e-06
+LineDim37Points=1015
+LineDim38Range=9.94938e-07
+LineDim38Min=1.22459e-06
+LineDim38Points=1018
+LineDim39Range=9.88237e-07
+LineDim39Min=1.22149e-06
+LineDim39Points=1012
+LineDim40Range=1.00007e-06
+LineDim40Min=1.22154e-06
+LineDim40Points=1024
+LineDim41Range=1.00026e-06
+LineDim41Min=1.22525e-06
+LineDim41Points=1024
+LineDim42Range=1.00064e-06
+LineDim42Min=1.22873e-06
+LineDim42Points=1024
+LineDim43Range=9.98494e-07
+LineDim43Min=1.23193e-06
+LineDim43Points=1022
+LineDim44Range=1.00066e-06
+LineDim44Min=1.23677e-06
+LineDim44Points=1024
+LineDim45Range=1.00023e-06
+LineDim45Min=1.24459e-06
+LineDim45Points=1024
+LineDim46Range=9.98245e-07
+LineDim46Min=1.24634e-06
+LineDim46Points=1022
+LineDim47Range=1.00006e-06
+LineDim47Min=1.24672e-06
+LineDim47Points=1024
+LineDim48Range=1.00089e-06
+LineDim48Min=1.25144e-06
+LineDim48Points=1024
+LineDim49Range=1.00054e-06
+LineDim49Min=1.25588e-06
+LineDim49Points=1024
+LineDim50Range=1.00082e-06
+LineDim50Min=1.26542e-06
+LineDim50Points=1024
+LineDim51Range=9.94045e-07
+LineDim51Min=1.26725e-06
+LineDim51Points=1017
+LineDim52Range=9.9392e-07
+LineDim52Min=1.26386e-06
+LineDim52Points=1017
+LineDim53Range=9.91515e-07
+LineDim53Min=1.25969e-06
+LineDim53Points=1015
+LineDim54Range=9.95043e-07
+LineDim54Min=1.25154e-06
+LineDim54Points=1018
+LineDim55Range=9.88856e-07
+LineDim55Min=1.24805e-06
+LineDim55Points=1012
+LineDim56Range=9.98293e-07
+LineDim56Min=1.23837e-06
+LineDim56Points=1022
+LineDim57Range=9.84786e-07
+LineDim57Min=1.23948e-06
+LineDim57Points=1008
+LineDim58Range=9.95389e-07
+LineDim58Min=1.22544e-06
+LineDim58Points=1019
+LineDim59Range=9.95226e-07
+LineDim59Min=1.22126e-06
+LineDim59Points=1019
+LineDim60Range=9.86917e-07
+LineDim60Min=1.21834e-06
+LineDim60Points=1010
+LineDim61Range=1.00069e-06
+LineDim61Min=1.2277e-06
+LineDim61Points=1024
+LineDim62Range=1.00073e-06
+LineDim62Min=1.23672e-06
+LineDim62Points=1024
+LineDim63Range=1e-06
+LineDim63Min=1.23956e-06
+LineDim63Points=1024
+LineDim64Range=1.00058e-06
+LineDim64Min=1.24167e-06
+LineDim64Points=1024
+LineDim65Range=1.00055e-06
+LineDim65Min=1.25216e-06
+LineDim65Points=1024
+LineDim66Range=1.00041e-06
+LineDim66Min=1.25558e-06
+LineDim66Points=1024
+LineDim67Range=9.97227e-07
+LineDim67Min=1.25593e-06
+LineDim67Points=1021
+LineDim68Range=1.00001e-06
+LineDim68Min=1.26329e-06
+LineDim68Points=1024
+LineDim69Range=1.00084e-06
+LineDim69Min=1.26281e-06
+LineDim69Points=1024
+LineDim70Range=1.00039e-06
+LineDim70Min=1.27016e-06
+LineDim70Points=1024
+LineDim71Range=9.93022e-07
+LineDim71Min=1.27129e-06
+LineDim71Points=1016
+LineDim72Range=9.95154e-07
+LineDim72Min=1.26538e-06
+LineDim72Points=1019
+LineDim73Range=9.93848e-07
+LineDim73Min=1.26352e-06
+LineDim73Points=1017
+LineDim74Range=9.93747e-07
+LineDim74Min=1.25991e-06
+LineDim74Points=1017
+LineDim75Range=9.89936e-07
+LineDim75Min=1.25368e-06
+LineDim75Points=1013
+LineDim76Range=1.00083e-06
+LineDim76Min=1.24971e-06
+LineDim76Points=1024
+LineDim77Range=9.79813e-07
+LineDim77Min=1.25142e-06
+LineDim77Points=1003
+LineDim78Range=9.96138e-07
+LineDim78Min=1.23232e-06
+LineDim78Points=1020
+LineDim79Range=9.99027e-07
+LineDim79Min=1.22708e-06
+LineDim79Points=1023
+LineDim80Range=1.00018e-06
+LineDim80Min=1.24049e-06
+LineDim80Points=1024
+LineDim81Range=1.00005e-06
+LineDim81Min=1.24588e-06
+LineDim81Points=1024
+LineDim82Range=9.96022e-07
+LineDim82Min=1.24974e-06
+LineDim82Points=1019
+LineDim83Range=1.00031e-06
+LineDim83Min=1.25258e-06
+LineDim83Points=1024
+LineDim84Range=1.00056e-06
+LineDim84Min=1.25646e-06
+LineDim84Points=1024
+LineDim85Range=9.98192e-07
+LineDim85Min=1.25829e-06
+LineDim85Points=1022
+LineDim86Range=1.00083e-06
+LineDim86Min=1.25917e-06
+LineDim86Points=1024
+LineDim87Range=1.00021e-06
+LineDim87Min=1.26571e-06
+LineDim87Points=1024
+LineDim88Range=9.97222e-07
+LineDim88Min=1.26794e-06
+LineDim88Points=1021
+LineDim89Range=9.99988e-07
+LineDim89Min=1.27013e-06
+LineDim89Points=1024
+LineDim90Range=1.00025e-06
+LineDim90Min=1.28033e-06
+LineDim90Points=1024
+LineDim91Range=9.91856e-07
+LineDim91Min=1.28283e-06
+LineDim91Points=1015
+LineDim92Range=9.98657e-07
+LineDim92Min=1.27622e-06
+LineDim92Points=1022
+LineDim93Range=9.85554e-07
+LineDim93Min=1.2777e-06
+LineDim93Points=1009
+LineDim94Range=9.96565e-07
+LineDim94Min=1.26523e-06
+LineDim94Points=1020
+LineDim95Range=9.95658e-07
+LineDim95Min=1.26376e-06
+LineDim95Points=1019
+LineDim96Range=9.91232e-07
+LineDim96Min=1.26184e-06
+LineDim96Points=1015
+LineDim97Range=1e-06
+LineDim97Min=1.25497e-06
+LineDim97Points=1024
+LineDim98Range=9.90205e-07
+LineDim98Min=1.25537e-06
+LineDim98Points=1014
+LineDim99Range=9.95403e-07
+LineDim99Min=1.24632e-06
+LineDim99Points=1019
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-1:0]
+Version=3
+Points=1024
+Lines=100
+Frame=Spec backward
+CurLine=99
+Dim0Name=Z-Axis
+Dim0Unit=m
+Dim0Range=1.18967e-06
+Dim0Min=1.20526e-06
+Dim1Name=SpecPoint
+Dim1Range=99
+Dim1Min=1
+Dim2Name=Deflection
+Dim2Unit=N
+Dim2Range=3.9815e-06
+Dim2Min=-1.99075e-06
+LineDim0Range=1e-06
+LineDim0Min=1.39493e-06
+LineDim0Points=1024
+LineDim1Range=1e-06
+LineDim1Min=1.35325e-06
+LineDim1Points=1024
+LineDim2Range=1e-06
+LineDim2Min=1.3203e-06
+LineDim2Points=1024
+LineDim3Range=1e-06
+LineDim3Min=1.3158e-06
+LineDim3Points=1024
+LineDim4Range=1e-06
+LineDim4Min=1.30511e-06
+LineDim4Points=1024
+LineDim5Range=1e-06
+LineDim5Min=1.29803e-06
+LineDim5Points=1024
+LineDim6Range=1e-06
+LineDim6Min=1.29417e-06
+LineDim6Points=1024
+LineDim7Range=1e-06
+LineDim7Min=1.2956e-06
+LineDim7Points=1024
+LineDim8Range=1e-06
+LineDim8Min=1.28244e-06
+LineDim8Points=1024
+LineDim9Range=1e-06
+LineDim9Min=1.29172e-06
+LineDim9Points=1024
+LineDim10Range=1e-06
+LineDim10Min=1.29203e-06
+LineDim10Points=1024
+LineDim11Range=1e-06
+LineDim11Min=1.28431e-06
+LineDim11Points=1024
+LineDim12Range=1e-06
+LineDim12Min=1.27553e-06
+LineDim12Points=1024
+LineDim13Range=1e-06
+LineDim13Min=1.26868e-06
+LineDim13Points=1024
+LineDim14Range=1e-06
+LineDim14Min=1.25273e-06
+LineDim14Points=1024
+LineDim15Range=1e-06
+LineDim15Min=1.24998e-06
+LineDim15Points=1024
+LineDim16Range=1e-06
+LineDim16Min=1.2443e-06
+LineDim16Points=1024
+LineDim17Range=1e-06
+LineDim17Min=1.23879e-06
+LineDim17Points=1024
+LineDim18Range=1e-06
+LineDim18Min=1.22644e-06
+LineDim18Points=1024
+LineDim19Range=1e-06
+LineDim19Min=1.22434e-06
+LineDim19Points=1024
+LineDim20Range=1e-06
+LineDim20Min=1.22925e-06
+LineDim20Points=1024
+LineDim21Range=1e-06
+LineDim21Min=1.23591e-06
+LineDim21Points=1024
+LineDim22Range=1e-06
+LineDim22Min=1.23791e-06
+LineDim22Points=1024
+LineDim23Range=1e-06
+LineDim23Min=1.23675e-06
+LineDim23Points=1024
+LineDim24Range=1e-06
+LineDim24Min=1.24377e-06
+LineDim24Points=1024
+LineDim25Range=1e-06
+LineDim25Min=1.24485e-06
+LineDim25Points=1024
+LineDim26Range=1e-06
+LineDim26Min=1.25139e-06
+LineDim26Points=1024
+LineDim27Range=1e-06
+LineDim27Min=1.24996e-06
+LineDim27Points=1024
+LineDim28Range=1e-06
+LineDim28Min=1.25617e-06
+LineDim28Points=1024
+LineDim29Range=1e-06
+LineDim29Min=1.25402e-06
+LineDim29Points=1024
+LineDim30Range=1e-06
+LineDim30Min=1.26992e-06
+LineDim30Points=1024
+LineDim31Range=1e-06
+LineDim31Min=1.26368e-06
+LineDim31Points=1024
+LineDim32Range=1e-06
+LineDim32Min=1.25434e-06
+LineDim32Points=1024
+LineDim33Range=1e-06
+LineDim33Min=1.24909e-06
+LineDim33Points=1024
+LineDim34Range=1e-06
+LineDim34Min=1.24358e-06
+LineDim34Points=1024
+LineDim35Range=1e-06
+LineDim35Min=1.23443e-06
+LineDim35Points=1024
+LineDim36Range=1e-06
+LineDim36Min=1.23169e-06
+LineDim36Points=1024
+LineDim37Range=1e-06
+LineDim37Min=1.22423e-06
+LineDim37Points=1024
+LineDim38Range=1e-06
+LineDim38Min=1.21953e-06
+LineDim38Points=1024
+LineDim39Range=1e-06
+LineDim39Min=1.20972e-06
+LineDim39Points=1024
+LineDim40Range=1e-06
+LineDim40Min=1.22161e-06
+LineDim40Points=1024
+LineDim41Range=1e-06
+LineDim41Min=1.22551e-06
+LineDim41Points=1024
+LineDim42Range=1e-06
+LineDim42Min=1.22937e-06
+LineDim42Points=1024
+LineDim43Range=1e-06
+LineDim43Min=1.23043e-06
+LineDim43Points=1024
+LineDim44Range=1e-06
+LineDim44Min=1.23743e-06
+LineDim44Points=1024
+LineDim45Range=1e-06
+LineDim45Min=1.24482e-06
+LineDim45Points=1024
+LineDim46Range=1e-06
+LineDim46Min=1.24459e-06
+LineDim46Points=1024
+LineDim47Range=1e-06
+LineDim47Min=1.24678e-06
+LineDim47Points=1024
+LineDim48Range=1e-06
+LineDim48Min=1.25234e-06
+LineDim48Points=1024
+LineDim49Range=1e-06
+LineDim49Min=1.25643e-06
+LineDim49Points=1024
+LineDim50Range=1e-06
+LineDim50Min=1.26623e-06
+LineDim50Points=1024
+LineDim51Range=1e-06
+LineDim51Min=1.26129e-06
+LineDim51Points=1024
+LineDim52Range=1e-06
+LineDim52Min=1.25777e-06
+LineDim52Points=1024
+LineDim53Range=1e-06
+LineDim53Min=1.25121e-06
+LineDim53Points=1024
+LineDim54Range=1e-06
+LineDim54Min=1.24658e-06
+LineDim54Points=1024
+LineDim55Range=1e-06
+LineDim55Min=1.2369e-06
+LineDim55Points=1024
+LineDim56Range=1e-06
+LineDim56Min=1.23666e-06
+LineDim56Points=1024
+LineDim57Range=1e-06
+LineDim57Min=1.22427e-06
+LineDim57Points=1024
+LineDim58Range=1e-06
+LineDim58Min=1.22083e-06
+LineDim58Points=1024
+LineDim59Range=1e-06
+LineDim59Min=1.21648e-06
+LineDim59Points=1024
+LineDim60Range=1e-06
+LineDim60Min=1.20526e-06
+LineDim60Points=1024
+LineDim61Range=1e-06
+LineDim61Min=1.22839e-06
+LineDim61Points=1024
+LineDim62Range=1e-06
+LineDim62Min=1.23745e-06
+LineDim62Points=1024
+LineDim63Range=1e-06
+LineDim63Min=1.23957e-06
+LineDim63Points=1024
+LineDim64Range=1e-06
+LineDim64Min=1.24225e-06
+LineDim64Points=1024
+LineDim65Range=1e-06
+LineDim65Min=1.25271e-06
+LineDim65Points=1024
+LineDim66Range=1e-06
+LineDim66Min=1.25599e-06
+LineDim66Points=1024
+LineDim67Range=1e-06
+LineDim67Min=1.25315e-06
+LineDim67Points=1024
+LineDim68Range=1e-06
+LineDim68Min=1.2633e-06
+LineDim68Points=1024
+LineDim69Range=1e-06
+LineDim69Min=1.26365e-06
+LineDim69Points=1024
+LineDim70Range=1e-06
+LineDim70Min=1.27055e-06
+LineDim70Points=1024
+LineDim71Range=1e-06
+LineDim71Min=1.26432e-06
+LineDim71Points=1024
+LineDim72Range=1e-06
+LineDim72Min=1.26053e-06
+LineDim72Points=1024
+LineDim73Range=1e-06
+LineDim73Min=1.25737e-06
+LineDim73Points=1024
+LineDim74Range=1e-06
+LineDim74Min=1.25365e-06
+LineDim74Points=1024
+LineDim75Range=1e-06
+LineDim75Min=1.24361e-06
+LineDim75Points=1024
+LineDim76Range=1e-06
+LineDim76Min=1.25054e-06
+LineDim76Points=1024
+LineDim77Range=1e-06
+LineDim77Min=1.23123e-06
+LineDim77Points=1024
+LineDim78Range=1e-06
+LineDim78Min=1.22846e-06
+LineDim78Points=1024
+LineDim79Range=1e-06
+LineDim79Min=1.2261e-06
+LineDim79Points=1024
+LineDim80Range=1e-06
+LineDim80Min=1.24067e-06
+LineDim80Points=1024
+LineDim81Range=1e-06
+LineDim81Min=1.24593e-06
+LineDim81Points=1024
+LineDim82Range=1e-06
+LineDim82Min=1.24576e-06
+LineDim82Points=1024
+LineDim83Range=1e-06
+LineDim83Min=1.25289e-06
+LineDim83Points=1024
+LineDim84Range=1e-06
+LineDim84Min=1.25702e-06
+LineDim84Points=1024
+LineDim85Range=1e-06
+LineDim85Min=1.25648e-06
+LineDim85Points=1024
+LineDim86Range=1e-06
+LineDim86Min=1.26e-06
+LineDim86Points=1024
+LineDim87Range=1e-06
+LineDim87Min=1.26592e-06
+LineDim87Points=1024
+LineDim88Range=1e-06
+LineDim88Min=1.26516e-06
+LineDim88Points=1024
+LineDim89Range=1e-06
+LineDim89Min=1.27012e-06
+LineDim89Points=1024
+LineDim90Range=1e-06
+LineDim90Min=1.28058e-06
+LineDim90Points=1024
+LineDim91Range=1e-06
+LineDim91Min=1.27468e-06
+LineDim91Points=1024
+LineDim92Range=1e-06
+LineDim92Min=1.27487e-06
+LineDim92Points=1024
+LineDim93Range=1e-06
+LineDim93Min=1.26325e-06
+LineDim93Points=1024
+LineDim94Range=1e-06
+LineDim94Min=1.26179e-06
+LineDim94Points=1024
+LineDim95Range=1e-06
+LineDim95Min=1.25941e-06
+LineDim95Points=1024
+LineDim96Range=1e-06
+LineDim96Min=1.25307e-06
+LineDim96Points=1024
+LineDim97Range=1e-06
+LineDim97Min=1.25498e-06
+LineDim97Points=1024
+LineDim98Range=1e-06
+LineDim98Min=1.24557e-06
+LineDim98Points=1024
+LineDim99Range=1e-06
+LineDim99Min=1.24173e-06
+LineDim99Points=1024
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-1:7]
+Version=3
+Points=1024
+Lines=100
+Frame=Spec backward
+CurLine=99
+Dim0Name=Z-Axis
+Dim0Unit=m
+Dim0Range=1.18967e-06
+Dim0Min=1.20526e-06
+Dim1Name=SpecPoint
+Dim1Range=99
+Dim1Min=1
+Dim2Name=Z-Axis Sensor
+Dim2Unit=m
+Dim2Range=2.8e-05
+Dim2Min=-1.4e-05
+LineDim0Range=1e-06
+LineDim0Min=1.39493e-06
+LineDim0Points=1024
+LineDim1Range=1e-06
+LineDim1Min=1.35325e-06
+LineDim1Points=1024
+LineDim2Range=1e-06
+LineDim2Min=1.3203e-06
+LineDim2Points=1024
+LineDim3Range=1e-06
+LineDim3Min=1.3158e-06
+LineDim3Points=1024
+LineDim4Range=1e-06
+LineDim4Min=1.30511e-06
+LineDim4Points=1024
+LineDim5Range=1e-06
+LineDim5Min=1.29803e-06
+LineDim5Points=1024
+LineDim6Range=1e-06
+LineDim6Min=1.29417e-06
+LineDim6Points=1024
+LineDim7Range=1e-06
+LineDim7Min=1.2956e-06
+LineDim7Points=1024
+LineDim8Range=1e-06
+LineDim8Min=1.28244e-06
+LineDim8Points=1024
+LineDim9Range=1e-06
+LineDim9Min=1.29172e-06
+LineDim9Points=1024
+LineDim10Range=1e-06
+LineDim10Min=1.29203e-06
+LineDim10Points=1024
+LineDim11Range=1e-06
+LineDim11Min=1.28431e-06
+LineDim11Points=1024
+LineDim12Range=1e-06
+LineDim12Min=1.27553e-06
+LineDim12Points=1024
+LineDim13Range=1e-06
+LineDim13Min=1.26868e-06
+LineDim13Points=1024
+LineDim14Range=1e-06
+LineDim14Min=1.25273e-06
+LineDim14Points=1024
+LineDim15Range=1e-06
+LineDim15Min=1.24998e-06
+LineDim15Points=1024
+LineDim16Range=1e-06
+LineDim16Min=1.2443e-06
+LineDim16Points=1024
+LineDim17Range=1e-06
+LineDim17Min=1.23879e-06
+LineDim17Points=1024
+LineDim18Range=1e-06
+LineDim18Min=1.22644e-06
+LineDim18Points=1024
+LineDim19Range=1e-06
+LineDim19Min=1.22434e-06
+LineDim19Points=1024
+LineDim20Range=1e-06
+LineDim20Min=1.22925e-06
+LineDim20Points=1024
+LineDim21Range=1e-06
+LineDim21Min=1.23591e-06
+LineDim21Points=1024
+LineDim22Range=1e-06
+LineDim22Min=1.23791e-06
+LineDim22Points=1024
+LineDim23Range=1e-06
+LineDim23Min=1.23675e-06
+LineDim23Points=1024
+LineDim24Range=1e-06
+LineDim24Min=1.24377e-06
+LineDim24Points=1024
+LineDim25Range=1e-06
+LineDim25Min=1.24485e-06
+LineDim25Points=1024
+LineDim26Range=1e-06
+LineDim26Min=1.25139e-06
+LineDim26Points=1024
+LineDim27Range=1e-06
+LineDim27Min=1.24996e-06
+LineDim27Points=1024
+LineDim28Range=1e-06
+LineDim28Min=1.25617e-06
+LineDim28Points=1024
+LineDim29Range=1e-06
+LineDim29Min=1.25402e-06
+LineDim29Points=1024
+LineDim30Range=1e-06
+LineDim30Min=1.26992e-06
+LineDim30Points=1024
+LineDim31Range=1e-06
+LineDim31Min=1.26368e-06
+LineDim31Points=1024
+LineDim32Range=1e-06
+LineDim32Min=1.25434e-06
+LineDim32Points=1024
+LineDim33Range=1e-06
+LineDim33Min=1.24909e-06
+LineDim33Points=1024
+LineDim34Range=1e-06
+LineDim34Min=1.24358e-06
+LineDim34Points=1024
+LineDim35Range=1e-06
+LineDim35Min=1.23443e-06
+LineDim35Points=1024
+LineDim36Range=1e-06
+LineDim36Min=1.23169e-06
+LineDim36Points=1024
+LineDim37Range=1e-06
+LineDim37Min=1.22423e-06
+LineDim37Points=1024
+LineDim38Range=1e-06
+LineDim38Min=1.21953e-06
+LineDim38Points=1024
+LineDim39Range=1e-06
+LineDim39Min=1.20972e-06
+LineDim39Points=1024
+LineDim40Range=1e-06
+LineDim40Min=1.22161e-06
+LineDim40Points=1024
+LineDim41Range=1e-06
+LineDim41Min=1.22551e-06
+LineDim41Points=1024
+LineDim42Range=1e-06
+LineDim42Min=1.22937e-06
+LineDim42Points=1024
+LineDim43Range=1e-06
+LineDim43Min=1.23043e-06
+LineDim43Points=1024
+LineDim44Range=1e-06
+LineDim44Min=1.23743e-06
+LineDim44Points=1024
+LineDim45Range=1e-06
+LineDim45Min=1.24482e-06
+LineDim45Points=1024
+LineDim46Range=1e-06
+LineDim46Min=1.24459e-06
+LineDim46Points=1024
+LineDim47Range=1e-06
+LineDim47Min=1.24678e-06
+LineDim47Points=1024
+LineDim48Range=1e-06
+LineDim48Min=1.25234e-06
+LineDim48Points=1024
+LineDim49Range=1e-06
+LineDim49Min=1.25643e-06
+LineDim49Points=1024
+LineDim50Range=1e-06
+LineDim50Min=1.26623e-06
+LineDim50Points=1024
+LineDim51Range=1e-06
+LineDim51Min=1.26129e-06
+LineDim51Points=1024
+LineDim52Range=1e-06
+LineDim52Min=1.25777e-06
+LineDim52Points=1024
+LineDim53Range=1e-06
+LineDim53Min=1.25121e-06
+LineDim53Points=1024
+LineDim54Range=1e-06
+LineDim54Min=1.24658e-06
+LineDim54Points=1024
+LineDim55Range=1e-06
+LineDim55Min=1.2369e-06
+LineDim55Points=1024
+LineDim56Range=1e-06
+LineDim56Min=1.23666e-06
+LineDim56Points=1024
+LineDim57Range=1e-06
+LineDim57Min=1.22427e-06
+LineDim57Points=1024
+LineDim58Range=1e-06
+LineDim58Min=1.22083e-06
+LineDim58Points=1024
+LineDim59Range=1e-06
+LineDim59Min=1.21648e-06
+LineDim59Points=1024
+LineDim60Range=1e-06
+LineDim60Min=1.20526e-06
+LineDim60Points=1024
+LineDim61Range=1e-06
+LineDim61Min=1.22839e-06
+LineDim61Points=1024
+LineDim62Range=1e-06
+LineDim62Min=1.23745e-06
+LineDim62Points=1024
+LineDim63Range=1e-06
+LineDim63Min=1.23957e-06
+LineDim63Points=1024
+LineDim64Range=1e-06
+LineDim64Min=1.24225e-06
+LineDim64Points=1024
+LineDim65Range=1e-06
+LineDim65Min=1.25271e-06
+LineDim65Points=1024
+LineDim66Range=1e-06
+LineDim66Min=1.25599e-06
+LineDim66Points=1024
+LineDim67Range=1e-06
+LineDim67Min=1.25315e-06
+LineDim67Points=1024
+LineDim68Range=1e-06
+LineDim68Min=1.2633e-06
+LineDim68Points=1024
+LineDim69Range=1e-06
+LineDim69Min=1.26365e-06
+LineDim69Points=1024
+LineDim70Range=1e-06
+LineDim70Min=1.27055e-06
+LineDim70Points=1024
+LineDim71Range=1e-06
+LineDim71Min=1.26432e-06
+LineDim71Points=1024
+LineDim72Range=1e-06
+LineDim72Min=1.26053e-06
+LineDim72Points=1024
+LineDim73Range=1e-06
+LineDim73Min=1.25737e-06
+LineDim73Points=1024
+LineDim74Range=1e-06
+LineDim74Min=1.25365e-06
+LineDim74Points=1024
+LineDim75Range=1e-06
+LineDim75Min=1.24361e-06
+LineDim75Points=1024
+LineDim76Range=1e-06
+LineDim76Min=1.25054e-06
+LineDim76Points=1024
+LineDim77Range=1e-06
+LineDim77Min=1.23123e-06
+LineDim77Points=1024
+LineDim78Range=1e-06
+LineDim78Min=1.22846e-06
+LineDim78Points=1024
+LineDim79Range=1e-06
+LineDim79Min=1.2261e-06
+LineDim79Points=1024
+LineDim80Range=1e-06
+LineDim80Min=1.24067e-06
+LineDim80Points=1024
+LineDim81Range=1e-06
+LineDim81Min=1.24593e-06
+LineDim81Points=1024
+LineDim82Range=1e-06
+LineDim82Min=1.24576e-06
+LineDim82Points=1024
+LineDim83Range=1e-06
+LineDim83Min=1.25289e-06
+LineDim83Points=1024
+LineDim84Range=1e-06
+LineDim84Min=1.25702e-06
+LineDim84Points=1024
+LineDim85Range=1e-06
+LineDim85Min=1.25648e-06
+LineDim85Points=1024
+LineDim86Range=1e-06
+LineDim86Min=1.26e-06
+LineDim86Points=1024
+LineDim87Range=1e-06
+LineDim87Min=1.26592e-06
+LineDim87Points=1024
+LineDim88Range=1e-06
+LineDim88Min=1.26516e-06
+LineDim88Points=1024
+LineDim89Range=1e-06
+LineDim89Min=1.27012e-06
+LineDim89Points=1024
+LineDim90Range=1e-06
+LineDim90Min=1.28058e-06
+LineDim90Points=1024
+LineDim91Range=1e-06
+LineDim91Min=1.27468e-06
+LineDim91Points=1024
+LineDim92Range=1e-06
+LineDim92Min=1.27487e-06
+LineDim92Points=1024
+LineDim93Range=1e-06
+LineDim93Min=1.26325e-06
+LineDim93Points=1024
+LineDim94Range=1e-06
+LineDim94Min=1.26179e-06
+LineDim94Points=1024
+LineDim95Range=1e-06
+LineDim95Min=1.25941e-06
+LineDim95Points=1024
+LineDim96Range=1e-06
+LineDim96Min=1.25307e-06
+LineDim96Points=1024
+LineDim97Range=1e-06
+LineDim97Min=1.25498e-06
+LineDim97Points=1024
+LineDim98Range=1e-06
+LineDim98Min=1.24557e-06
+LineDim98Points=1024
+LineDim99Range=1e-06
+LineDim99Min=1.24173e-06
+LineDim99Points=1024
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-2:0]
+Version=3
+Points=1024
+Lines=100
+Frame=IndentationZSensorFwd0
+CurLine=99
+Dim0Name=NameX0
+Dim0Unit=Unit0
+Dim0Range=0
+Dim0Min=0
+Dim1Range=1
+Dim1Min=0
+Dim2Name=Tip-Sample Separation
+Dim2Unit=m
+Dim2Range=4.14459e-06
+Dim2Min=-4.14459e-06
+LineDim0Range=0
+LineDim0Min=0
+LineDim0Points=1024
+LineDim1Range=4.14459e-06
+LineDim1Min=-4.14459e-06
+LineDim1Points=975
+LineDim2Range=0
+LineDim2Min=0
+LineDim2Points=1024
+LineDim3Range=0
+LineDim3Min=0
+LineDim3Points=1024
+LineDim4Range=0
+LineDim4Min=0
+LineDim4Points=1024
+LineDim5Range=0
+LineDim5Min=0
+LineDim5Points=1024
+LineDim6Range=0
+LineDim6Min=0
+LineDim6Points=1024
+LineDim7Range=0
+LineDim7Min=0
+LineDim7Points=1024
+LineDim8Range=0
+LineDim8Min=0
+LineDim8Points=1024
+LineDim9Range=0
+LineDim9Min=0
+LineDim9Points=1024
+LineDim10Range=0
+LineDim10Min=0
+LineDim10Points=1024
+LineDim11Range=0
+LineDim11Min=0
+LineDim11Points=1024
+LineDim12Range=0
+LineDim12Min=0
+LineDim12Points=1024
+LineDim13Range=0
+LineDim13Min=0
+LineDim13Points=1024
+LineDim14Range=0
+LineDim14Min=0
+LineDim14Points=1024
+LineDim15Range=0
+LineDim15Min=0
+LineDim15Points=1024
+LineDim16Range=0
+LineDim16Min=0
+LineDim16Points=1024
+LineDim17Range=0
+LineDim17Min=0
+LineDim17Points=1024
+LineDim18Range=0
+LineDim18Min=0
+LineDim18Points=1024
+LineDim19Range=0
+LineDim19Min=0
+LineDim19Points=1024
+LineDim20Range=0
+LineDim20Min=0
+LineDim20Points=1024
+LineDim21Range=0
+LineDim21Min=0
+LineDim21Points=1024
+LineDim22Range=0
+LineDim22Min=0
+LineDim22Points=1024
+LineDim23Range=0
+LineDim23Min=0
+LineDim23Points=1024
+LineDim24Range=0
+LineDim24Min=0
+LineDim24Points=1024
+LineDim25Range=0
+LineDim25Min=0
+LineDim25Points=1024
+LineDim26Range=0
+LineDim26Min=0
+LineDim26Points=1024
+LineDim27Range=0
+LineDim27Min=0
+LineDim27Points=1024
+LineDim28Range=0
+LineDim28Min=0
+LineDim28Points=1024
+LineDim29Range=0
+LineDim29Min=0
+LineDim29Points=1024
+LineDim30Range=0
+LineDim30Min=0
+LineDim30Points=1024
+LineDim31Range=0
+LineDim31Min=0
+LineDim31Points=1024
+LineDim32Range=0
+LineDim32Min=0
+LineDim32Points=1024
+LineDim33Range=0
+LineDim33Min=0
+LineDim33Points=1024
+LineDim34Range=0
+LineDim34Min=0
+LineDim34Points=1024
+LineDim35Range=0
+LineDim35Min=0
+LineDim35Points=1024
+LineDim36Range=0
+LineDim36Min=0
+LineDim36Points=1024
+LineDim37Range=0
+LineDim37Min=0
+LineDim37Points=1024
+LineDim38Range=0
+LineDim38Min=0
+LineDim38Points=1024
+LineDim39Range=0
+LineDim39Min=0
+LineDim39Points=1024
+LineDim40Range=0
+LineDim40Min=0
+LineDim40Points=1024
+LineDim41Range=0
+LineDim41Min=0
+LineDim41Points=1024
+LineDim42Range=0
+LineDim42Min=0
+LineDim42Points=1024
+LineDim43Range=0
+LineDim43Min=0
+LineDim43Points=1024
+LineDim44Range=0
+LineDim44Min=0
+LineDim44Points=1024
+LineDim45Range=0
+LineDim45Min=0
+LineDim45Points=1024
+LineDim46Range=0
+LineDim46Min=0
+LineDim46Points=1024
+LineDim47Range=0
+LineDim47Min=0
+LineDim47Points=1024
+LineDim48Range=0
+LineDim48Min=0
+LineDim48Points=1024
+LineDim49Range=0
+LineDim49Min=0
+LineDim49Points=1024
+LineDim50Range=0
+LineDim50Min=0
+LineDim50Points=1024
+LineDim51Range=0
+LineDim51Min=0
+LineDim51Points=1024
+LineDim52Range=0
+LineDim52Min=0
+LineDim52Points=1024
+LineDim53Range=0
+LineDim53Min=0
+LineDim53Points=1024
+LineDim54Range=0
+LineDim54Min=0
+LineDim54Points=1024
+LineDim55Range=0
+LineDim55Min=0
+LineDim55Points=1024
+LineDim56Range=0
+LineDim56Min=0
+LineDim56Points=1024
+LineDim57Range=0
+LineDim57Min=0
+LineDim57Points=1024
+LineDim58Range=0
+LineDim58Min=0
+LineDim58Points=1024
+LineDim59Range=0
+LineDim59Min=0
+LineDim59Points=1024
+LineDim60Range=0
+LineDim60Min=0
+LineDim60Points=1024
+LineDim61Range=0
+LineDim61Min=0
+LineDim61Points=1024
+LineDim62Range=0
+LineDim62Min=0
+LineDim62Points=1024
+LineDim63Range=0
+LineDim63Min=0
+LineDim63Points=1024
+LineDim64Range=0
+LineDim64Min=0
+LineDim64Points=1024
+LineDim65Range=0
+LineDim65Min=0
+LineDim65Points=1024
+LineDim66Range=0
+LineDim66Min=0
+LineDim66Points=1024
+LineDim67Range=0
+LineDim67Min=0
+LineDim67Points=1024
+LineDim68Range=0
+LineDim68Min=0
+LineDim68Points=1024
+LineDim69Range=0
+LineDim69Min=0
+LineDim69Points=1024
+LineDim70Range=0
+LineDim70Min=0
+LineDim70Points=1024
+LineDim71Range=0
+LineDim71Min=0
+LineDim71Points=1024
+LineDim72Range=0
+LineDim72Min=0
+LineDim72Points=1024
+LineDim73Range=0
+LineDim73Min=0
+LineDim73Points=1024
+LineDim74Range=0
+LineDim74Min=0
+LineDim74Points=1024
+LineDim75Range=0
+LineDim75Min=0
+LineDim75Points=1024
+LineDim76Range=0
+LineDim76Min=0
+LineDim76Points=1024
+LineDim77Range=0
+LineDim77Min=0
+LineDim77Points=1024
+LineDim78Range=0
+LineDim78Min=0
+LineDim78Points=1024
+LineDim79Range=0
+LineDim79Min=0
+LineDim79Points=1024
+LineDim80Range=0
+LineDim80Min=0
+LineDim80Points=1024
+LineDim81Range=0
+LineDim81Min=0
+LineDim81Points=1024
+LineDim82Range=0
+LineDim82Min=0
+LineDim82Points=1024
+LineDim83Range=0
+LineDim83Min=0
+LineDim83Points=1024
+LineDim84Range=0
+LineDim84Min=0
+LineDim84Points=1024
+LineDim85Range=0
+LineDim85Min=0
+LineDim85Points=1024
+LineDim86Range=0
+LineDim86Min=0
+LineDim86Points=1024
+LineDim87Range=0
+LineDim87Min=0
+LineDim87Points=1024
+LineDim88Range=0
+LineDim88Min=0
+LineDim88Points=1024
+LineDim89Range=0
+LineDim89Min=0
+LineDim89Points=1024
+LineDim90Range=0
+LineDim90Min=0
+LineDim90Points=1024
+LineDim91Range=0
+LineDim91Min=0
+LineDim91Points=1024
+LineDim92Range=0
+LineDim92Min=0
+LineDim92Points=1024
+LineDim93Range=0
+LineDim93Min=0
+LineDim93Points=1024
+LineDim94Range=0
+LineDim94Min=0
+LineDim94Points=1024
+LineDim95Range=0
+LineDim95Min=0
+LineDim95Points=1024
+LineDim96Range=0
+LineDim96Min=0
+LineDim96Points=1024
+LineDim97Range=0
+LineDim97Min=0
+LineDim97Points=1024
+LineDim98Range=0
+LineDim98Min=0
+LineDim98Points=1024
+LineDim99Range=0
+LineDim99Min=0
+LineDim99Points=1024
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-3:0]
+Version=3
+Points=1024
+Lines=100
+Frame=IndentationZSensorBwd0
+CurLine=99
+Dim0Name=NameX1
+Dim0Unit=Unit1
+Dim0Range=0
+Dim0Min=0
+Dim1Range=1
+Dim1Min=0
+Dim2Name=Tip-Sample Separation
+Dim2Unit=m
+Dim2Range=4.16845e-06
+Dim2Min=-4.16845e-06
+LineDim0Range=0
+LineDim0Min=0
+LineDim0Points=1024
+LineDim1Range=4.16845e-06
+LineDim1Min=-4.16845e-06
+LineDim1Points=1024
+LineDim2Range=0
+LineDim2Min=0
+LineDim2Points=1024
+LineDim3Range=0
+LineDim3Min=0
+LineDim3Points=1024
+LineDim4Range=0
+LineDim4Min=0
+LineDim4Points=1024
+LineDim5Range=0
+LineDim5Min=0
+LineDim5Points=1024
+LineDim6Range=0
+LineDim6Min=0
+LineDim6Points=1024
+LineDim7Range=0
+LineDim7Min=0
+LineDim7Points=1024
+LineDim8Range=0
+LineDim8Min=0
+LineDim8Points=1024
+LineDim9Range=0
+LineDim9Min=0
+LineDim9Points=1024
+LineDim10Range=0
+LineDim10Min=0
+LineDim10Points=1024
+LineDim11Range=0
+LineDim11Min=0
+LineDim11Points=1024
+LineDim12Range=0
+LineDim12Min=0
+LineDim12Points=1024
+LineDim13Range=0
+LineDim13Min=0
+LineDim13Points=1024
+LineDim14Range=0
+LineDim14Min=0
+LineDim14Points=1024
+LineDim15Range=0
+LineDim15Min=0
+LineDim15Points=1024
+LineDim16Range=0
+LineDim16Min=0
+LineDim16Points=1024
+LineDim17Range=0
+LineDim17Min=0
+LineDim17Points=1024
+LineDim18Range=0
+LineDim18Min=0
+LineDim18Points=1024
+LineDim19Range=0
+LineDim19Min=0
+LineDim19Points=1024
+LineDim20Range=0
+LineDim20Min=0
+LineDim20Points=1024
+LineDim21Range=0
+LineDim21Min=0
+LineDim21Points=1024
+LineDim22Range=0
+LineDim22Min=0
+LineDim22Points=1024
+LineDim23Range=0
+LineDim23Min=0
+LineDim23Points=1024
+LineDim24Range=0
+LineDim24Min=0
+LineDim24Points=1024
+LineDim25Range=0
+LineDim25Min=0
+LineDim25Points=1024
+LineDim26Range=0
+LineDim26Min=0
+LineDim26Points=1024
+LineDim27Range=0
+LineDim27Min=0
+LineDim27Points=1024
+LineDim28Range=0
+LineDim28Min=0
+LineDim28Points=1024
+LineDim29Range=0
+LineDim29Min=0
+LineDim29Points=1024
+LineDim30Range=0
+LineDim30Min=0
+LineDim30Points=1024
+LineDim31Range=0
+LineDim31Min=0
+LineDim31Points=1024
+LineDim32Range=0
+LineDim32Min=0
+LineDim32Points=1024
+LineDim33Range=0
+LineDim33Min=0
+LineDim33Points=1024
+LineDim34Range=0
+LineDim34Min=0
+LineDim34Points=1024
+LineDim35Range=0
+LineDim35Min=0
+LineDim35Points=1024
+LineDim36Range=0
+LineDim36Min=0
+LineDim36Points=1024
+LineDim37Range=0
+LineDim37Min=0
+LineDim37Points=1024
+LineDim38Range=0
+LineDim38Min=0
+LineDim38Points=1024
+LineDim39Range=0
+LineDim39Min=0
+LineDim39Points=1024
+LineDim40Range=0
+LineDim40Min=0
+LineDim40Points=1024
+LineDim41Range=0
+LineDim41Min=0
+LineDim41Points=1024
+LineDim42Range=0
+LineDim42Min=0
+LineDim42Points=1024
+LineDim43Range=0
+LineDim43Min=0
+LineDim43Points=1024
+LineDim44Range=0
+LineDim44Min=0
+LineDim44Points=1024
+LineDim45Range=0
+LineDim45Min=0
+LineDim45Points=1024
+LineDim46Range=0
+LineDim46Min=0
+LineDim46Points=1024
+LineDim47Range=0
+LineDim47Min=0
+LineDim47Points=1024
+LineDim48Range=0
+LineDim48Min=0
+LineDim48Points=1024
+LineDim49Range=0
+LineDim49Min=0
+LineDim49Points=1024
+LineDim50Range=0
+LineDim50Min=0
+LineDim50Points=1024
+LineDim51Range=0
+LineDim51Min=0
+LineDim51Points=1024
+LineDim52Range=0
+LineDim52Min=0
+LineDim52Points=1024
+LineDim53Range=0
+LineDim53Min=0
+LineDim53Points=1024
+LineDim54Range=0
+LineDim54Min=0
+LineDim54Points=1024
+LineDim55Range=0
+LineDim55Min=0
+LineDim55Points=1024
+LineDim56Range=0
+LineDim56Min=0
+LineDim56Points=1024
+LineDim57Range=0
+LineDim57Min=0
+LineDim57Points=1024
+LineDim58Range=0
+LineDim58Min=0
+LineDim58Points=1024
+LineDim59Range=0
+LineDim59Min=0
+LineDim59Points=1024
+LineDim60Range=0
+LineDim60Min=0
+LineDim60Points=1024
+LineDim61Range=0
+LineDim61Min=0
+LineDim61Points=1024
+LineDim62Range=0
+LineDim62Min=0
+LineDim62Points=1024
+LineDim63Range=0
+LineDim63Min=0
+LineDim63Points=1024
+LineDim64Range=0
+LineDim64Min=0
+LineDim64Points=1024
+LineDim65Range=0
+LineDim65Min=0
+LineDim65Points=1024
+LineDim66Range=0
+LineDim66Min=0
+LineDim66Points=1024
+LineDim67Range=0
+LineDim67Min=0
+LineDim67Points=1024
+LineDim68Range=0
+LineDim68Min=0
+LineDim68Points=1024
+LineDim69Range=0
+LineDim69Min=0
+LineDim69Points=1024
+LineDim70Range=0
+LineDim70Min=0
+LineDim70Points=1024
+LineDim71Range=0
+LineDim71Min=0
+LineDim71Points=1024
+LineDim72Range=0
+LineDim72Min=0
+LineDim72Points=1024
+LineDim73Range=0
+LineDim73Min=0
+LineDim73Points=1024
+LineDim74Range=0
+LineDim74Min=0
+LineDim74Points=1024
+LineDim75Range=0
+LineDim75Min=0
+LineDim75Points=1024
+LineDim76Range=0
+LineDim76Min=0
+LineDim76Points=1024
+LineDim77Range=0
+LineDim77Min=0
+LineDim77Points=1024
+LineDim78Range=0
+LineDim78Min=0
+LineDim78Points=1024
+LineDim79Range=0
+LineDim79Min=0
+LineDim79Points=1024
+LineDim80Range=0
+LineDim80Min=0
+LineDim80Points=1024
+LineDim81Range=0
+LineDim81Min=0
+LineDim81Points=1024
+LineDim82Range=0
+LineDim82Min=0
+LineDim82Points=1024
+LineDim83Range=0
+LineDim83Min=0
+LineDim83Points=1024
+LineDim84Range=0
+LineDim84Min=0
+LineDim84Points=1024
+LineDim85Range=0
+LineDim85Min=0
+LineDim85Points=1024
+LineDim86Range=0
+LineDim86Min=0
+LineDim86Points=1024
+LineDim87Range=0
+LineDim87Min=0
+LineDim87Points=1024
+LineDim88Range=0
+LineDim88Min=0
+LineDim88Points=1024
+LineDim89Range=0
+LineDim89Min=0
+LineDim89Points=1024
+LineDim90Range=0
+LineDim90Min=0
+LineDim90Points=1024
+LineDim91Range=0
+LineDim91Min=0
+LineDim91Points=1024
+LineDim92Range=0
+LineDim92Min=0
+LineDim92Points=1024
+LineDim93Range=0
+LineDim93Min=0
+LineDim93Points=1024
+LineDim94Range=0
+LineDim94Min=0
+LineDim94Points=1024
+LineDim95Range=0
+LineDim95Min=0
+LineDim95Points=1024
+LineDim96Range=0
+LineDim96Min=0
+LineDim96Points=1024
+LineDim97Range=0
+LineDim97Min=0
+LineDim97Points=1024
+LineDim98Range=0
+LineDim98Min=0
+LineDim98Points=1024
+LineDim99Range=0
+LineDim99Min=0
+LineDim99Points=1024
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-4:0]
+Version=3
+Points=1024
+Lines=100
+Frame=IndentationDeflFwd0
+CurLine=99
+Dim0Name=NameX2
+Dim0Unit=Unit2
+Dim0Range=0
+Dim0Min=0
+Dim1Range=1
+Dim1Min=0
+Dim2Name=Deflection
+Dim2Unit=N
+Dim2Range=3.9815e-06
+Dim2Min=-1.99075e-06
+LineDim0Range=0
+LineDim0Min=0
+LineDim0Points=1024
+LineDim1Range=9.57446e-07
+LineDim1Min=1.3958e-06
+LineDim1Points=980
+LineDim2Range=9.67392e-07
+LineDim2Min=1.35291e-06
+LineDim2Points=990
+LineDim3Range=9.9537e-07
+LineDim3Min=1.32043e-06
+LineDim3Points=1019
+LineDim4Range=9.8589e-07
+LineDim4Min=1.31922e-06
+LineDim4Points=1009
+LineDim5Range=9.92106e-07
+LineDim5Min=1.30592e-06
+LineDim5Points=1015
+LineDim6Range=9.94962e-07
+LineDim6Min=1.29921e-06
+LineDim6Points=1018
+LineDim7Range=1.00051e-06
+LineDim7Min=1.29509e-06
+LineDim7Points=1024
+LineDim8Range=9.86581e-07
+LineDim8Min=1.29586e-06
+LineDim8Points=1010
+LineDim9Range=1.00038e-06
+LineDim9Min=1.29134e-06
+LineDim9Points=1024
+LineDim10Range=9.99833e-07
+LineDim10Min=1.2922e-06
+LineDim10Points=1023
+LineDim11Range=9.89941e-07
+LineDim11Min=1.29437e-06
+LineDim11Points=1013
+LineDim12Range=9.91083e-07
+LineDim12Min=1.28445e-06
+LineDim12Points=1014
+LineDim13Range=9.89202e-07
+LineDim13Min=1.27948e-06
+LineDim13Points=1012
+LineDim14Range=9.82468e-07
+LineDim14Min=1.27027e-06
+LineDim14Points=1006
+LineDim15Range=9.93906e-07
+LineDim15Min=1.25608e-06
+LineDim15Points=1017
+LineDim16Range=9.93214e-07
+LineDim16Min=1.25109e-06
+LineDim16Points=1017
+LineDim17Range=9.93642e-07
+LineDim17Min=1.24515e-06
+LineDim17Points=1017
+LineDim18Range=9.85439e-07
+LineDim18Min=1.24101e-06
+LineDim18Points=1009
+LineDim19Range=9.96507e-07
+LineDim19Min=1.22784e-06
+LineDim19Points=1020
+LineDim20Range=1.00009e-06
+LineDim20Min=1.22916e-06
+LineDim20Points=1024
+LineDim21Range=1.00035e-06
+LineDim21Min=1.23556e-06
+LineDim21Points=1024
+LineDim22Range=1.00008e-06
+LineDim22Min=1.23783e-06
+LineDim22Points=1024
+LineDim23Range=9.96022e-07
+LineDim23Min=1.24073e-06
+LineDim23Points=1019
+LineDim24Range=1.00006e-06
+LineDim24Min=1.24371e-06
+LineDim24Points=1024
+LineDim25Range=1.00067e-06
+LineDim25Min=1.24418e-06
+LineDim25Points=1024
+LineDim26Range=1.0006e-06
+LineDim26Min=1.25079e-06
+LineDim26Points=1024
+LineDim27Range=9.97246e-07
+LineDim27Min=1.25272e-06
+LineDim27Points=1021
+LineDim28Range=1.00071e-06
+LineDim28Min=1.25547e-06
+LineDim28Points=1024
+LineDim29Range=9.95418e-07
+LineDim29Min=1.2586e-06
+LineDim29Points=1019
+LineDim30Range=1.00077e-06
+LineDim30Min=1.26915e-06
+LineDim30Points=1024
+LineDim31Range=9.90987e-07
+LineDim31Min=1.27269e-06
+LineDim31Points=1014
+LineDim32Range=9.88055e-07
+LineDim32Min=1.26629e-06
+LineDim32Points=1011
+LineDim33Range=9.93013e-07
+LineDim33Min=1.25608e-06
+LineDim33Points=1016
+LineDim34Range=9.93162e-07
+LineDim34Min=1.25042e-06
+LineDim34Points=1017
+LineDim35Range=9.89202e-07
+LineDim35Min=1.24523e-06
+LineDim35Points=1012
+LineDim36Range=9.97232e-07
+LineDim36Min=1.23446e-06
+LineDim36Points=1021
+LineDim37Range=9.91362e-07
+LineDim37Min=1.23286e-06
+LineDim37Points=1015
+LineDim38Range=9.94938e-07
+LineDim38Min=1.22459e-06
+LineDim38Points=1018
+LineDim39Range=9.88237e-07
+LineDim39Min=1.22149e-06
+LineDim39Points=1012
+LineDim40Range=1.00007e-06
+LineDim40Min=1.22154e-06
+LineDim40Points=1024
+LineDim41Range=1.00026e-06
+LineDim41Min=1.22525e-06
+LineDim41Points=1024
+LineDim42Range=1.00064e-06
+LineDim42Min=1.22873e-06
+LineDim42Points=1024
+LineDim43Range=9.98494e-07
+LineDim43Min=1.23193e-06
+LineDim43Points=1022
+LineDim44Range=1.00066e-06
+LineDim44Min=1.23677e-06
+LineDim44Points=1024
+LineDim45Range=1.00023e-06
+LineDim45Min=1.24459e-06
+LineDim45Points=1024
+LineDim46Range=9.98245e-07
+LineDim46Min=1.24634e-06
+LineDim46Points=1022
+LineDim47Range=1.00006e-06
+LineDim47Min=1.24672e-06
+LineDim47Points=1024
+LineDim48Range=1.00089e-06
+LineDim48Min=1.25144e-06
+LineDim48Points=1024
+LineDim49Range=1.00054e-06
+LineDim49Min=1.25588e-06
+LineDim49Points=1024
+LineDim50Range=1.00082e-06
+LineDim50Min=1.26542e-06
+LineDim50Points=1024
+LineDim51Range=9.94045e-07
+LineDim51Min=1.26725e-06
+LineDim51Points=1017
+LineDim52Range=9.9392e-07
+LineDim52Min=1.26386e-06
+LineDim52Points=1017
+LineDim53Range=9.91515e-07
+LineDim53Min=1.25969e-06
+LineDim53Points=1015
+LineDim54Range=9.95043e-07
+LineDim54Min=1.25154e-06
+LineDim54Points=1018
+LineDim55Range=9.88856e-07
+LineDim55Min=1.24805e-06
+LineDim55Points=1012
+LineDim56Range=9.98293e-07
+LineDim56Min=1.23837e-06
+LineDim56Points=1022
+LineDim57Range=9.84786e-07
+LineDim57Min=1.23948e-06
+LineDim57Points=1008
+LineDim58Range=9.95389e-07
+LineDim58Min=1.22544e-06
+LineDim58Points=1019
+LineDim59Range=9.95226e-07
+LineDim59Min=1.22126e-06
+LineDim59Points=1019
+LineDim60Range=9.86917e-07
+LineDim60Min=1.21834e-06
+LineDim60Points=1010
+LineDim61Range=1.00069e-06
+LineDim61Min=1.2277e-06
+LineDim61Points=1024
+LineDim62Range=1.00073e-06
+LineDim62Min=1.23672e-06
+LineDim62Points=1024
+LineDim63Range=1e-06
+LineDim63Min=1.23956e-06
+LineDim63Points=1024
+LineDim64Range=1.00058e-06
+LineDim64Min=1.24167e-06
+LineDim64Points=1024
+LineDim65Range=1.00055e-06
+LineDim65Min=1.25216e-06
+LineDim65Points=1024
+LineDim66Range=1.00041e-06
+LineDim66Min=1.25558e-06
+LineDim66Points=1024
+LineDim67Range=9.97227e-07
+LineDim67Min=1.25593e-06
+LineDim67Points=1021
+LineDim68Range=1.00001e-06
+LineDim68Min=1.26329e-06
+LineDim68Points=1024
+LineDim69Range=1.00084e-06
+LineDim69Min=1.26281e-06
+LineDim69Points=1024
+LineDim70Range=1.00039e-06
+LineDim70Min=1.27016e-06
+LineDim70Points=1024
+LineDim71Range=9.93022e-07
+LineDim71Min=1.27129e-06
+LineDim71Points=1016
+LineDim72Range=9.95154e-07
+LineDim72Min=1.26538e-06
+LineDim72Points=1019
+LineDim73Range=9.93848e-07
+LineDim73Min=1.26352e-06
+LineDim73Points=1017
+LineDim74Range=9.93747e-07
+LineDim74Min=1.25991e-06
+LineDim74Points=1017
+LineDim75Range=9.89936e-07
+LineDim75Min=1.25368e-06
+LineDim75Points=1013
+LineDim76Range=1.00083e-06
+LineDim76Min=1.24971e-06
+LineDim76Points=1024
+LineDim77Range=9.79813e-07
+LineDim77Min=1.25142e-06
+LineDim77Points=1003
+LineDim78Range=9.96138e-07
+LineDim78Min=1.23232e-06
+LineDim78Points=1020
+LineDim79Range=9.99027e-07
+LineDim79Min=1.22708e-06
+LineDim79Points=1023
+LineDim80Range=1.00018e-06
+LineDim80Min=1.24049e-06
+LineDim80Points=1024
+LineDim81Range=1.00005e-06
+LineDim81Min=1.24588e-06
+LineDim81Points=1024
+LineDim82Range=9.96022e-07
+LineDim82Min=1.24974e-06
+LineDim82Points=1019
+LineDim83Range=1.00031e-06
+LineDim83Min=1.25258e-06
+LineDim83Points=1024
+LineDim84Range=1.00056e-06
+LineDim84Min=1.25646e-06
+LineDim84Points=1024
+LineDim85Range=9.98192e-07
+LineDim85Min=1.25829e-06
+LineDim85Points=1022
+LineDim86Range=1.00083e-06
+LineDim86Min=1.25917e-06
+LineDim86Points=1024
+LineDim87Range=1.00021e-06
+LineDim87Min=1.26571e-06
+LineDim87Points=1024
+LineDim88Range=9.97222e-07
+LineDim88Min=1.26794e-06
+LineDim88Points=1021
+LineDim89Range=9.99988e-07
+LineDim89Min=1.27013e-06
+LineDim89Points=1024
+LineDim90Range=1.00025e-06
+LineDim90Min=1.28033e-06
+LineDim90Points=1024
+LineDim91Range=9.91856e-07
+LineDim91Min=1.28283e-06
+LineDim91Points=1015
+LineDim92Range=9.98657e-07
+LineDim92Min=1.27622e-06
+LineDim92Points=1022
+LineDim93Range=9.85554e-07
+LineDim93Min=1.2777e-06
+LineDim93Points=1009
+LineDim94Range=9.96565e-07
+LineDim94Min=1.26523e-06
+LineDim94Points=1020
+LineDim95Range=9.95658e-07
+LineDim95Min=1.26376e-06
+LineDim95Points=1019
+LineDim96Range=9.91232e-07
+LineDim96Min=1.26184e-06
+LineDim96Points=1015
+LineDim97Range=1e-06
+LineDim97Min=1.25497e-06
+LineDim97Points=1024
+LineDim98Range=9.90205e-07
+LineDim98Min=1.25537e-06
+LineDim98Points=1014
+LineDim99Range=9.95403e-07
+LineDim99Min=1.24632e-06
+LineDim99Points=1019
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-5:0]
+Version=3
+Points=1024
+Lines=100
+Frame=IndentationDeflBwd0
+CurLine=99
+Dim0Name=NameX3
+Dim0Unit=Unit3
+Dim0Range=0
+Dim0Min=0
+Dim1Range=1
+Dim1Min=0
+Dim2Name=Deflection
+Dim2Unit=N
+Dim2Range=3.9815e-06
+Dim2Min=-1.99075e-06
+LineDim0Range=0
+LineDim0Min=0
+LineDim0Points=1024
+LineDim1Range=1e-06
+LineDim1Min=1.35325e-06
+LineDim1Points=1024
+LineDim2Range=1e-06
+LineDim2Min=1.3203e-06
+LineDim2Points=1024
+LineDim3Range=1e-06
+LineDim3Min=1.3158e-06
+LineDim3Points=1024
+LineDim4Range=1e-06
+LineDim4Min=1.30511e-06
+LineDim4Points=1024
+LineDim5Range=1e-06
+LineDim5Min=1.29803e-06
+LineDim5Points=1024
+LineDim6Range=1e-06
+LineDim6Min=1.29417e-06
+LineDim6Points=1024
+LineDim7Range=1e-06
+LineDim7Min=1.2956e-06
+LineDim7Points=1024
+LineDim8Range=1e-06
+LineDim8Min=1.28244e-06
+LineDim8Points=1024
+LineDim9Range=1e-06
+LineDim9Min=1.29172e-06
+LineDim9Points=1024
+LineDim10Range=1e-06
+LineDim10Min=1.29203e-06
+LineDim10Points=1024
+LineDim11Range=1e-06
+LineDim11Min=1.28431e-06
+LineDim11Points=1024
+LineDim12Range=1e-06
+LineDim12Min=1.27553e-06
+LineDim12Points=1024
+LineDim13Range=1e-06
+LineDim13Min=1.26868e-06
+LineDim13Points=1024
+LineDim14Range=1e-06
+LineDim14Min=1.25273e-06
+LineDim14Points=1024
+LineDim15Range=1e-06
+LineDim15Min=1.24998e-06
+LineDim15Points=1024
+LineDim16Range=1e-06
+LineDim16Min=1.2443e-06
+LineDim16Points=1024
+LineDim17Range=1e-06
+LineDim17Min=1.23879e-06
+LineDim17Points=1024
+LineDim18Range=1e-06
+LineDim18Min=1.22644e-06
+LineDim18Points=1024
+LineDim19Range=1e-06
+LineDim19Min=1.22434e-06
+LineDim19Points=1024
+LineDim20Range=1e-06
+LineDim20Min=1.22925e-06
+LineDim20Points=1024
+LineDim21Range=1e-06
+LineDim21Min=1.23591e-06
+LineDim21Points=1024
+LineDim22Range=1e-06
+LineDim22Min=1.23791e-06
+LineDim22Points=1024
+LineDim23Range=1e-06
+LineDim23Min=1.23675e-06
+LineDim23Points=1024
+LineDim24Range=1e-06
+LineDim24Min=1.24377e-06
+LineDim24Points=1024
+LineDim25Range=1e-06
+LineDim25Min=1.24485e-06
+LineDim25Points=1024
+LineDim26Range=1e-06
+LineDim26Min=1.25139e-06
+LineDim26Points=1024
+LineDim27Range=1e-06
+LineDim27Min=1.24996e-06
+LineDim27Points=1024
+LineDim28Range=1e-06
+LineDim28Min=1.25617e-06
+LineDim28Points=1024
+LineDim29Range=1e-06
+LineDim29Min=1.25402e-06
+LineDim29Points=1024
+LineDim30Range=1e-06
+LineDim30Min=1.26992e-06
+LineDim30Points=1024
+LineDim31Range=1e-06
+LineDim31Min=1.26368e-06
+LineDim31Points=1024
+LineDim32Range=1e-06
+LineDim32Min=1.25434e-06
+LineDim32Points=1024
+LineDim33Range=1e-06
+LineDim33Min=1.24909e-06
+LineDim33Points=1024
+LineDim34Range=1e-06
+LineDim34Min=1.24358e-06
+LineDim34Points=1024
+LineDim35Range=1e-06
+LineDim35Min=1.23443e-06
+LineDim35Points=1024
+LineDim36Range=1e-06
+LineDim36Min=1.23169e-06
+LineDim36Points=1024
+LineDim37Range=1e-06
+LineDim37Min=1.22423e-06
+LineDim37Points=1024
+LineDim38Range=1e-06
+LineDim38Min=1.21953e-06
+LineDim38Points=1024
+LineDim39Range=1e-06
+LineDim39Min=1.20972e-06
+LineDim39Points=1024
+LineDim40Range=1e-06
+LineDim40Min=1.22161e-06
+LineDim40Points=1024
+LineDim41Range=1e-06
+LineDim41Min=1.22551e-06
+LineDim41Points=1024
+LineDim42Range=1e-06
+LineDim42Min=1.22937e-06
+LineDim42Points=1024
+LineDim43Range=1e-06
+LineDim43Min=1.23043e-06
+LineDim43Points=1024
+LineDim44Range=1e-06
+LineDim44Min=1.23743e-06
+LineDim44Points=1024
+LineDim45Range=1e-06
+LineDim45Min=1.24482e-06
+LineDim45Points=1024
+LineDim46Range=1e-06
+LineDim46Min=1.24459e-06
+LineDim46Points=1024
+LineDim47Range=1e-06
+LineDim47Min=1.24678e-06
+LineDim47Points=1024
+LineDim48Range=1e-06
+LineDim48Min=1.25234e-06
+LineDim48Points=1024
+LineDim49Range=1e-06
+LineDim49Min=1.25643e-06
+LineDim49Points=1024
+LineDim50Range=1e-06
+LineDim50Min=1.26623e-06
+LineDim50Points=1024
+LineDim51Range=1e-06
+LineDim51Min=1.26129e-06
+LineDim51Points=1024
+LineDim52Range=1e-06
+LineDim52Min=1.25777e-06
+LineDim52Points=1024
+LineDim53Range=1e-06
+LineDim53Min=1.25121e-06
+LineDim53Points=1024
+LineDim54Range=1e-06
+LineDim54Min=1.24658e-06
+LineDim54Points=1024
+LineDim55Range=1e-06
+LineDim55Min=1.2369e-06
+LineDim55Points=1024
+LineDim56Range=1e-06
+LineDim56Min=1.23666e-06
+LineDim56Points=1024
+LineDim57Range=1e-06
+LineDim57Min=1.22427e-06
+LineDim57Points=1024
+LineDim58Range=1e-06
+LineDim58Min=1.22083e-06
+LineDim58Points=1024
+LineDim59Range=1e-06
+LineDim59Min=1.21648e-06
+LineDim59Points=1024
+LineDim60Range=1e-06
+LineDim60Min=1.20526e-06
+LineDim60Points=1024
+LineDim61Range=1e-06
+LineDim61Min=1.22839e-06
+LineDim61Points=1024
+LineDim62Range=1e-06
+LineDim62Min=1.23745e-06
+LineDim62Points=1024
+LineDim63Range=1e-06
+LineDim63Min=1.23957e-06
+LineDim63Points=1024
+LineDim64Range=1e-06
+LineDim64Min=1.24225e-06
+LineDim64Points=1024
+LineDim65Range=1e-06
+LineDim65Min=1.25271e-06
+LineDim65Points=1024
+LineDim66Range=1e-06
+LineDim66Min=1.25599e-06
+LineDim66Points=1024
+LineDim67Range=1e-06
+LineDim67Min=1.25315e-06
+LineDim67Points=1024
+LineDim68Range=1e-06
+LineDim68Min=1.2633e-06
+LineDim68Points=1024
+LineDim69Range=1e-06
+LineDim69Min=1.26365e-06
+LineDim69Points=1024
+LineDim70Range=1e-06
+LineDim70Min=1.27055e-06
+LineDim70Points=1024
+LineDim71Range=1e-06
+LineDim71Min=1.26432e-06
+LineDim71Points=1024
+LineDim72Range=1e-06
+LineDim72Min=1.26053e-06
+LineDim72Points=1024
+LineDim73Range=1e-06
+LineDim73Min=1.25737e-06
+LineDim73Points=1024
+LineDim74Range=1e-06
+LineDim74Min=1.25365e-06
+LineDim74Points=1024
+LineDim75Range=1e-06
+LineDim75Min=1.24361e-06
+LineDim75Points=1024
+LineDim76Range=1e-06
+LineDim76Min=1.25054e-06
+LineDim76Points=1024
+LineDim77Range=1e-06
+LineDim77Min=1.23123e-06
+LineDim77Points=1024
+LineDim78Range=1e-06
+LineDim78Min=1.22846e-06
+LineDim78Points=1024
+LineDim79Range=1e-06
+LineDim79Min=1.2261e-06
+LineDim79Points=1024
+LineDim80Range=1e-06
+LineDim80Min=1.24067e-06
+LineDim80Points=1024
+LineDim81Range=1e-06
+LineDim81Min=1.24593e-06
+LineDim81Points=1024
+LineDim82Range=1e-06
+LineDim82Min=1.24576e-06
+LineDim82Points=1024
+LineDim83Range=1e-06
+LineDim83Min=1.25289e-06
+LineDim83Points=1024
+LineDim84Range=1e-06
+LineDim84Min=1.25702e-06
+LineDim84Points=1024
+LineDim85Range=1e-06
+LineDim85Min=1.25648e-06
+LineDim85Points=1024
+LineDim86Range=1e-06
+LineDim86Min=1.26e-06
+LineDim86Points=1024
+LineDim87Range=1e-06
+LineDim87Min=1.26592e-06
+LineDim87Points=1024
+LineDim88Range=1e-06
+LineDim88Min=1.26516e-06
+LineDim88Points=1024
+LineDim89Range=1e-06
+LineDim89Min=1.27012e-06
+LineDim89Points=1024
+LineDim90Range=1e-06
+LineDim90Min=1.28058e-06
+LineDim90Points=1024
+LineDim91Range=1e-06
+LineDim91Min=1.27468e-06
+LineDim91Points=1024
+LineDim92Range=1e-06
+LineDim92Min=1.27487e-06
+LineDim92Points=1024
+LineDim93Range=1e-06
+LineDim93Min=1.26325e-06
+LineDim93Points=1024
+LineDim94Range=1e-06
+LineDim94Min=1.26179e-06
+LineDim94Points=1024
+LineDim95Range=1e-06
+LineDim95Min=1.25941e-06
+LineDim95Points=1024
+LineDim96Range=1e-06
+LineDim96Min=1.25307e-06
+LineDim96Points=1024
+LineDim97Range=1e-06
+LineDim97Min=1.25498e-06
+LineDim97Points=1024
+LineDim98Range=1e-06
+LineDim98Min=1.24557e-06
+LineDim98Points=1024
+LineDim99Range=1e-06
+LineDim99Min=1.24173e-06
+LineDim99Points=1024
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-6:0]
+Version=3
+Points=2
+Lines=100
+Frame=SlopeOut00
+CurLine=99
+Dim0Name=NameX4
+Dim0Unit=Unit4
+Dim0Range=0
+Dim0Min=0
+Dim1Range=1
+Dim1Min=0
+Dim2Name=NameZ4
+Dim2Unit=m
+Dim2Range=2.56187e-06
+Dim2Min=-2.56187e-06
+LineDim0Range=0
+LineDim0Min=0
+LineDim0Points=2
+LineDim1Range=2.56187e-06
+LineDim1Min=-2.56187e-06
+LineDim1Points=2
+LineDim2Range=0
+LineDim2Min=0
+LineDim2Points=2
+LineDim3Range=0
+LineDim3Min=0
+LineDim3Points=2
+LineDim4Range=0
+LineDim4Min=0
+LineDim4Points=2
+LineDim5Range=0
+LineDim5Min=0
+LineDim5Points=2
+LineDim6Range=0
+LineDim6Min=0
+LineDim6Points=2
+LineDim7Range=0
+LineDim7Min=0
+LineDim7Points=2
+LineDim8Range=0
+LineDim8Min=0
+LineDim8Points=2
+LineDim9Range=0
+LineDim9Min=0
+LineDim9Points=2
+LineDim10Range=0
+LineDim10Min=0
+LineDim10Points=2
+LineDim11Range=0
+LineDim11Min=0
+LineDim11Points=2
+LineDim12Range=0
+LineDim12Min=0
+LineDim12Points=2
+LineDim13Range=0
+LineDim13Min=0
+LineDim13Points=2
+LineDim14Range=0
+LineDim14Min=0
+LineDim14Points=2
+LineDim15Range=0
+LineDim15Min=0
+LineDim15Points=2
+LineDim16Range=0
+LineDim16Min=0
+LineDim16Points=2
+LineDim17Range=0
+LineDim17Min=0
+LineDim17Points=2
+LineDim18Range=0
+LineDim18Min=0
+LineDim18Points=2
+LineDim19Range=0
+LineDim19Min=0
+LineDim19Points=2
+LineDim20Range=0
+LineDim20Min=0
+LineDim20Points=2
+LineDim21Range=0
+LineDim21Min=0
+LineDim21Points=2
+LineDim22Range=0
+LineDim22Min=0
+LineDim22Points=2
+LineDim23Range=0
+LineDim23Min=0
+LineDim23Points=2
+LineDim24Range=0
+LineDim24Min=0
+LineDim24Points=2
+LineDim25Range=0
+LineDim25Min=0
+LineDim25Points=2
+LineDim26Range=0
+LineDim26Min=0
+LineDim26Points=2
+LineDim27Range=0
+LineDim27Min=0
+LineDim27Points=2
+LineDim28Range=0
+LineDim28Min=0
+LineDim28Points=2
+LineDim29Range=0
+LineDim29Min=0
+LineDim29Points=2
+LineDim30Range=0
+LineDim30Min=0
+LineDim30Points=2
+LineDim31Range=0
+LineDim31Min=0
+LineDim31Points=2
+LineDim32Range=0
+LineDim32Min=0
+LineDim32Points=2
+LineDim33Range=0
+LineDim33Min=0
+LineDim33Points=2
+LineDim34Range=0
+LineDim34Min=0
+LineDim34Points=2
+LineDim35Range=0
+LineDim35Min=0
+LineDim35Points=2
+LineDim36Range=0
+LineDim36Min=0
+LineDim36Points=2
+LineDim37Range=0
+LineDim37Min=0
+LineDim37Points=2
+LineDim38Range=0
+LineDim38Min=0
+LineDim38Points=2
+LineDim39Range=0
+LineDim39Min=0
+LineDim39Points=2
+LineDim40Range=0
+LineDim40Min=0
+LineDim40Points=2
+LineDim41Range=0
+LineDim41Min=0
+LineDim41Points=2
+LineDim42Range=0
+LineDim42Min=0
+LineDim42Points=2
+LineDim43Range=0
+LineDim43Min=0
+LineDim43Points=2
+LineDim44Range=0
+LineDim44Min=0
+LineDim44Points=2
+LineDim45Range=0
+LineDim45Min=0
+LineDim45Points=2
+LineDim46Range=0
+LineDim46Min=0
+LineDim46Points=2
+LineDim47Range=0
+LineDim47Min=0
+LineDim47Points=2
+LineDim48Range=0
+LineDim48Min=0
+LineDim48Points=2
+LineDim49Range=0
+LineDim49Min=0
+LineDim49Points=2
+LineDim50Range=0
+LineDim50Min=0
+LineDim50Points=2
+LineDim51Range=0
+LineDim51Min=0
+LineDim51Points=2
+LineDim52Range=0
+LineDim52Min=0
+LineDim52Points=2
+LineDim53Range=0
+LineDim53Min=0
+LineDim53Points=2
+LineDim54Range=0
+LineDim54Min=0
+LineDim54Points=2
+LineDim55Range=0
+LineDim55Min=0
+LineDim55Points=2
+LineDim56Range=0
+LineDim56Min=0
+LineDim56Points=2
+LineDim57Range=0
+LineDim57Min=0
+LineDim57Points=2
+LineDim58Range=0
+LineDim58Min=0
+LineDim58Points=2
+LineDim59Range=0
+LineDim59Min=0
+LineDim59Points=2
+LineDim60Range=0
+LineDim60Min=0
+LineDim60Points=2
+LineDim61Range=0
+LineDim61Min=0
+LineDim61Points=2
+LineDim62Range=0
+LineDim62Min=0
+LineDim62Points=2
+LineDim63Range=0
+LineDim63Min=0
+LineDim63Points=2
+LineDim64Range=0
+LineDim64Min=0
+LineDim64Points=2
+LineDim65Range=0
+LineDim65Min=0
+LineDim65Points=2
+LineDim66Range=0
+LineDim66Min=0
+LineDim66Points=2
+LineDim67Range=0
+LineDim67Min=0
+LineDim67Points=2
+LineDim68Range=0
+LineDim68Min=0
+LineDim68Points=2
+LineDim69Range=0
+LineDim69Min=0
+LineDim69Points=2
+LineDim70Range=0
+LineDim70Min=0
+LineDim70Points=2
+LineDim71Range=0
+LineDim71Min=0
+LineDim71Points=2
+LineDim72Range=0
+LineDim72Min=0
+LineDim72Points=2
+LineDim73Range=0
+LineDim73Min=0
+LineDim73Points=2
+LineDim74Range=0
+LineDim74Min=0
+LineDim74Points=2
+LineDim75Range=0
+LineDim75Min=0
+LineDim75Points=2
+LineDim76Range=0
+LineDim76Min=0
+LineDim76Points=2
+LineDim77Range=0
+LineDim77Min=0
+LineDim77Points=2
+LineDim78Range=0
+LineDim78Min=0
+LineDim78Points=2
+LineDim79Range=0
+LineDim79Min=0
+LineDim79Points=2
+LineDim80Range=0
+LineDim80Min=0
+LineDim80Points=2
+LineDim81Range=0
+LineDim81Min=0
+LineDim81Points=2
+LineDim82Range=0
+LineDim82Min=0
+LineDim82Points=2
+LineDim83Range=0
+LineDim83Min=0
+LineDim83Points=2
+LineDim84Range=0
+LineDim84Min=0
+LineDim84Points=2
+LineDim85Range=0
+LineDim85Min=0
+LineDim85Points=2
+LineDim86Range=0
+LineDim86Min=0
+LineDim86Points=2
+LineDim87Range=0
+LineDim87Min=0
+LineDim87Points=2
+LineDim88Range=0
+LineDim88Min=0
+LineDim88Points=2
+LineDim89Range=0
+LineDim89Min=0
+LineDim89Points=2
+LineDim90Range=0
+LineDim90Min=0
+LineDim90Points=2
+LineDim91Range=0
+LineDim91Min=0
+LineDim91Points=2
+LineDim92Range=0
+LineDim92Min=0
+LineDim92Points=2
+LineDim93Range=0
+LineDim93Min=0
+LineDim93Points=2
+LineDim94Range=0
+LineDim94Min=0
+LineDim94Points=2
+LineDim95Range=0
+LineDim95Min=0
+LineDim95Points=2
+LineDim96Range=0
+LineDim96Min=0
+LineDim96Points=2
+LineDim97Range=0
+LineDim97Min=0
+LineDim97Points=2
+LineDim98Range=0
+LineDim98Min=0
+LineDim98Points=2
+LineDim99Range=2.35737e-06
+LineDim99Min=-2.35737e-06
+LineDim99Points=2
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-7:0]
+Version=3
+Points=3
+Lines=100
+Frame=SlopeOut10
+CurLine=99
+Dim0Name=NameX5
+Dim0Unit=Unit5
+Dim0Range=0
+Dim0Min=0
+Dim1Range=1
+Dim1Min=0
+Dim2Name=NameZ5
+Dim2Unit=N/m
+Dim2Range=83.7756
+Dim2Min=0
+LineDim0Range=0
+LineDim0Min=0
+LineDim0Points=3
+LineDim1Range=83.7756
+LineDim1Min=0
+LineDim1Points=3
+LineDim2Range=0
+LineDim2Min=0
+LineDim2Points=3
+LineDim3Range=0
+LineDim3Min=0
+LineDim3Points=3
+LineDim4Range=0
+LineDim4Min=0
+LineDim4Points=3
+LineDim5Range=0
+LineDim5Min=0
+LineDim5Points=3
+LineDim6Range=0
+LineDim6Min=0
+LineDim6Points=3
+LineDim7Range=0
+LineDim7Min=0
+LineDim7Points=3
+LineDim8Range=0
+LineDim8Min=0
+LineDim8Points=3
+LineDim9Range=0
+LineDim9Min=0
+LineDim9Points=3
+LineDim10Range=0
+LineDim10Min=0
+LineDim10Points=3
+LineDim11Range=0
+LineDim11Min=0
+LineDim11Points=3
+LineDim12Range=0
+LineDim12Min=0
+LineDim12Points=3
+LineDim13Range=0
+LineDim13Min=0
+LineDim13Points=3
+LineDim14Range=0
+LineDim14Min=0
+LineDim14Points=3
+LineDim15Range=0
+LineDim15Min=0
+LineDim15Points=3
+LineDim16Range=0
+LineDim16Min=0
+LineDim16Points=3
+LineDim17Range=0
+LineDim17Min=0
+LineDim17Points=3
+LineDim18Range=0
+LineDim18Min=0
+LineDim18Points=3
+LineDim19Range=0
+LineDim19Min=0
+LineDim19Points=3
+LineDim20Range=0
+LineDim20Min=0
+LineDim20Points=3
+LineDim21Range=0
+LineDim21Min=0
+LineDim21Points=3
+LineDim22Range=0
+LineDim22Min=0
+LineDim22Points=3
+LineDim23Range=0
+LineDim23Min=0
+LineDim23Points=3
+LineDim24Range=0
+LineDim24Min=0
+LineDim24Points=3
+LineDim25Range=0
+LineDim25Min=0
+LineDim25Points=3
+LineDim26Range=0
+LineDim26Min=0
+LineDim26Points=3
+LineDim27Range=0
+LineDim27Min=0
+LineDim27Points=3
+LineDim28Range=0
+LineDim28Min=0
+LineDim28Points=3
+LineDim29Range=0
+LineDim29Min=0
+LineDim29Points=3
+LineDim30Range=0
+LineDim30Min=0
+LineDim30Points=3
+LineDim31Range=0
+LineDim31Min=0
+LineDim31Points=3
+LineDim32Range=0
+LineDim32Min=0
+LineDim32Points=3
+LineDim33Range=0
+LineDim33Min=0
+LineDim33Points=3
+LineDim34Range=0
+LineDim34Min=0
+LineDim34Points=3
+LineDim35Range=0
+LineDim35Min=0
+LineDim35Points=3
+LineDim36Range=0
+LineDim36Min=0
+LineDim36Points=3
+LineDim37Range=0
+LineDim37Min=0
+LineDim37Points=3
+LineDim38Range=0
+LineDim38Min=0
+LineDim38Points=3
+LineDim39Range=0
+LineDim39Min=0
+LineDim39Points=3
+LineDim40Range=0
+LineDim40Min=0
+LineDim40Points=3
+LineDim41Range=0
+LineDim41Min=0
+LineDim41Points=3
+LineDim42Range=0
+LineDim42Min=0
+LineDim42Points=3
+LineDim43Range=0
+LineDim43Min=0
+LineDim43Points=3
+LineDim44Range=0
+LineDim44Min=0
+LineDim44Points=3
+LineDim45Range=0
+LineDim45Min=0
+LineDim45Points=3
+LineDim46Range=0
+LineDim46Min=0
+LineDim46Points=3
+LineDim47Range=0
+LineDim47Min=0
+LineDim47Points=3
+LineDim48Range=0
+LineDim48Min=0
+LineDim48Points=3
+LineDim49Range=0
+LineDim49Min=0
+LineDim49Points=3
+LineDim50Range=0
+LineDim50Min=0
+LineDim50Points=3
+LineDim51Range=0
+LineDim51Min=0
+LineDim51Points=3
+LineDim52Range=0
+LineDim52Min=0
+LineDim52Points=3
+LineDim53Range=0
+LineDim53Min=0
+LineDim53Points=3
+LineDim54Range=0
+LineDim54Min=0
+LineDim54Points=3
+LineDim55Range=0
+LineDim55Min=0
+LineDim55Points=3
+LineDim56Range=0
+LineDim56Min=0
+LineDim56Points=3
+LineDim57Range=0
+LineDim57Min=0
+LineDim57Points=3
+LineDim58Range=0
+LineDim58Min=0
+LineDim58Points=3
+LineDim59Range=0
+LineDim59Min=0
+LineDim59Points=3
+LineDim60Range=0
+LineDim60Min=0
+LineDim60Points=3
+LineDim61Range=0
+LineDim61Min=0
+LineDim61Points=3
+LineDim62Range=0
+LineDim62Min=0
+LineDim62Points=3
+LineDim63Range=0
+LineDim63Min=0
+LineDim63Points=3
+LineDim64Range=0
+LineDim64Min=0
+LineDim64Points=3
+LineDim65Range=0
+LineDim65Min=0
+LineDim65Points=3
+LineDim66Range=0
+LineDim66Min=0
+LineDim66Points=3
+LineDim67Range=0
+LineDim67Min=0
+LineDim67Points=3
+LineDim68Range=0
+LineDim68Min=0
+LineDim68Points=3
+LineDim69Range=0
+LineDim69Min=0
+LineDim69Points=3
+LineDim70Range=0
+LineDim70Min=0
+LineDim70Points=3
+LineDim71Range=0
+LineDim71Min=0
+LineDim71Points=3
+LineDim72Range=0
+LineDim72Min=0
+LineDim72Points=3
+LineDim73Range=0
+LineDim73Min=0
+LineDim73Points=3
+LineDim74Range=0
+LineDim74Min=0
+LineDim74Points=3
+LineDim75Range=0
+LineDim75Min=0
+LineDim75Points=3
+LineDim76Range=0
+LineDim76Min=0
+LineDim76Points=3
+LineDim77Range=0
+LineDim77Min=0
+LineDim77Points=3
+LineDim78Range=0
+LineDim78Min=0
+LineDim78Points=3
+LineDim79Range=0
+LineDim79Min=0
+LineDim79Points=3
+LineDim80Range=0
+LineDim80Min=0
+LineDim80Points=3
+LineDim81Range=0
+LineDim81Min=0
+LineDim81Points=3
+LineDim82Range=0
+LineDim82Min=0
+LineDim82Points=3
+LineDim83Range=0
+LineDim83Min=0
+LineDim83Points=3
+LineDim84Range=0
+LineDim84Min=0
+LineDim84Points=3
+LineDim85Range=0
+LineDim85Min=0
+LineDim85Points=3
+LineDim86Range=0
+LineDim86Min=0
+LineDim86Points=3
+LineDim87Range=0
+LineDim87Min=0
+LineDim87Points=3
+LineDim88Range=0
+LineDim88Min=0
+LineDim88Points=3
+LineDim89Range=0
+LineDim89Min=0
+LineDim89Points=3
+LineDim90Range=0
+LineDim90Min=0
+LineDim90Points=3
+LineDim91Range=0
+LineDim91Min=0
+LineDim91Points=3
+LineDim92Range=0
+LineDim92Min=0
+LineDim92Points=3
+LineDim93Range=0
+LineDim93Min=0
+LineDim93Points=3
+LineDim94Range=0
+LineDim94Min=0
+LineDim94Points=3
+LineDim95Range=0
+LineDim95Min=0
+LineDim95Points=3
+LineDim96Range=0
+LineDim96Min=0
+LineDim96Points=3
+LineDim97Range=0
+LineDim97Min=0
+LineDim97Points=3
+LineDim98Range=0
+LineDim98Min=0
+LineDim98Points=3
+LineDim99Range=2.96221e+08
+LineDim99Min=0
+LineDim99Points=3
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-8:0]
+Version=3
+Points=1
+Lines=100
+Frame=SlopeOut20
+CurLine=99
+Dim0Name=NameX6
+Dim0Unit=Unit6
+Dim0Range=0
+Dim0Min=0
+Dim1Range=1
+Dim1Min=0
+Dim2Name=NameZ6
+Dim2Unit=N
+Dim2Range=0.00010759
+Dim2Min=0
+LineDim0Range=0
+LineDim0Min=0
+LineDim0Points=1
+LineDim1Range=0.00010759
+LineDim1Min=0
+LineDim1Points=1
+LineDim2Range=0
+LineDim2Min=0
+LineDim2Points=1
+LineDim3Range=0
+LineDim3Min=0
+LineDim3Points=1
+LineDim4Range=0
+LineDim4Min=0
+LineDim4Points=1
+LineDim5Range=0
+LineDim5Min=0
+LineDim5Points=1
+LineDim6Range=0
+LineDim6Min=0
+LineDim6Points=1
+LineDim7Range=0
+LineDim7Min=0
+LineDim7Points=1
+LineDim8Range=0
+LineDim8Min=0
+LineDim8Points=1
+LineDim9Range=0
+LineDim9Min=0
+LineDim9Points=1
+LineDim10Range=0
+LineDim10Min=0
+LineDim10Points=1
+LineDim11Range=0
+LineDim11Min=0
+LineDim11Points=1
+LineDim12Range=0
+LineDim12Min=0
+LineDim12Points=1
+LineDim13Range=0
+LineDim13Min=0
+LineDim13Points=1
+LineDim14Range=0
+LineDim14Min=0
+LineDim14Points=1
+LineDim15Range=0
+LineDim15Min=0
+LineDim15Points=1
+LineDim16Range=0
+LineDim16Min=0
+LineDim16Points=1
+LineDim17Range=0
+LineDim17Min=0
+LineDim17Points=1
+LineDim18Range=0
+LineDim18Min=0
+LineDim18Points=1
+LineDim19Range=0
+LineDim19Min=0
+LineDim19Points=1
+LineDim20Range=0
+LineDim20Min=0
+LineDim20Points=1
+LineDim21Range=0
+LineDim21Min=0
+LineDim21Points=1
+LineDim22Range=0
+LineDim22Min=0
+LineDim22Points=1
+LineDim23Range=0
+LineDim23Min=0
+LineDim23Points=1
+LineDim24Range=0
+LineDim24Min=0
+LineDim24Points=1
+LineDim25Range=0
+LineDim25Min=0
+LineDim25Points=1
+LineDim26Range=0
+LineDim26Min=0
+LineDim26Points=1
+LineDim27Range=0
+LineDim27Min=0
+LineDim27Points=1
+LineDim28Range=0
+LineDim28Min=0
+LineDim28Points=1
+LineDim29Range=0
+LineDim29Min=0
+LineDim29Points=1
+LineDim30Range=0
+LineDim30Min=0
+LineDim30Points=1
+LineDim31Range=0
+LineDim31Min=0
+LineDim31Points=1
+LineDim32Range=0
+LineDim32Min=0
+LineDim32Points=1
+LineDim33Range=0
+LineDim33Min=0
+LineDim33Points=1
+LineDim34Range=0
+LineDim34Min=0
+LineDim34Points=1
+LineDim35Range=0
+LineDim35Min=0
+LineDim35Points=1
+LineDim36Range=0
+LineDim36Min=0
+LineDim36Points=1
+LineDim37Range=0
+LineDim37Min=0
+LineDim37Points=1
+LineDim38Range=0
+LineDim38Min=0
+LineDim38Points=1
+LineDim39Range=0
+LineDim39Min=0
+LineDim39Points=1
+LineDim40Range=0
+LineDim40Min=0
+LineDim40Points=1
+LineDim41Range=0
+LineDim41Min=0
+LineDim41Points=1
+LineDim42Range=0
+LineDim42Min=0
+LineDim42Points=1
+LineDim43Range=0
+LineDim43Min=0
+LineDim43Points=1
+LineDim44Range=0
+LineDim44Min=0
+LineDim44Points=1
+LineDim45Range=0
+LineDim45Min=0
+LineDim45Points=1
+LineDim46Range=0
+LineDim46Min=0
+LineDim46Points=1
+LineDim47Range=0
+LineDim47Min=0
+LineDim47Points=1
+LineDim48Range=0
+LineDim48Min=0
+LineDim48Points=1
+LineDim49Range=0
+LineDim49Min=0
+LineDim49Points=1
+LineDim50Range=0
+LineDim50Min=0
+LineDim50Points=1
+LineDim51Range=0
+LineDim51Min=0
+LineDim51Points=1
+LineDim52Range=0
+LineDim52Min=0
+LineDim52Points=1
+LineDim53Range=0
+LineDim53Min=0
+LineDim53Points=1
+LineDim54Range=0
+LineDim54Min=0
+LineDim54Points=1
+LineDim55Range=0
+LineDim55Min=0
+LineDim55Points=1
+LineDim56Range=0
+LineDim56Min=0
+LineDim56Points=1
+LineDim57Range=0
+LineDim57Min=0
+LineDim57Points=1
+LineDim58Range=0
+LineDim58Min=0
+LineDim58Points=1
+LineDim59Range=0
+LineDim59Min=0
+LineDim59Points=1
+LineDim60Range=0
+LineDim60Min=0
+LineDim60Points=1
+LineDim61Range=0
+LineDim61Min=0
+LineDim61Points=1
+LineDim62Range=0
+LineDim62Min=0
+LineDim62Points=1
+LineDim63Range=0
+LineDim63Min=0
+LineDim63Points=1
+LineDim64Range=0
+LineDim64Min=0
+LineDim64Points=1
+LineDim65Range=0
+LineDim65Min=0
+LineDim65Points=1
+LineDim66Range=0
+LineDim66Min=0
+LineDim66Points=1
+LineDim67Range=0
+LineDim67Min=0
+LineDim67Points=1
+LineDim68Range=0
+LineDim68Min=0
+LineDim68Points=1
+LineDim69Range=0
+LineDim69Min=0
+LineDim69Points=1
+LineDim70Range=0
+LineDim70Min=0
+LineDim70Points=1
+LineDim71Range=0
+LineDim71Min=0
+LineDim71Points=1
+LineDim72Range=0
+LineDim72Min=0
+LineDim72Points=1
+LineDim73Range=0
+LineDim73Min=0
+LineDim73Points=1
+LineDim74Range=0
+LineDim74Min=0
+LineDim74Points=1
+LineDim75Range=0
+LineDim75Min=0
+LineDim75Points=1
+LineDim76Range=0
+LineDim76Min=0
+LineDim76Points=1
+LineDim77Range=0
+LineDim77Min=0
+LineDim77Points=1
+LineDim78Range=0
+LineDim78Min=0
+LineDim78Points=1
+LineDim79Range=0
+LineDim79Min=0
+LineDim79Points=1
+LineDim80Range=0
+LineDim80Min=0
+LineDim80Points=1
+LineDim81Range=0
+LineDim81Min=0
+LineDim81Points=1
+LineDim82Range=0
+LineDim82Min=0
+LineDim82Points=1
+LineDim83Range=0
+LineDim83Min=0
+LineDim83Points=1
+LineDim84Range=0
+LineDim84Min=0
+LineDim84Points=1
+LineDim85Range=0
+LineDim85Min=0
+LineDim85Points=1
+LineDim86Range=0
+LineDim86Min=0
+LineDim86Points=1
+LineDim87Range=0
+LineDim87Min=0
+LineDim87Points=1
+LineDim88Range=0
+LineDim88Min=0
+LineDim88Points=1
+LineDim89Range=0
+LineDim89Min=0
+LineDim89Points=1
+LineDim90Range=0
+LineDim90Min=0
+LineDim90Points=1
+LineDim91Range=0
+LineDim91Min=0
+LineDim91Points=1
+LineDim92Range=0
+LineDim92Min=0
+LineDim92Points=1
+LineDim93Range=0
+LineDim93Min=0
+LineDim93Points=1
+LineDim94Range=0
+LineDim94Min=0
+LineDim94Points=1
+LineDim95Range=0
+LineDim95Min=0
+LineDim95Points=1
+LineDim96Range=0
+LineDim96Min=0
+LineDim96Points=1
+LineDim97Range=0
+LineDim97Min=0
+LineDim97Points=1
+LineDim98Range=0
+LineDim98Min=0
+LineDim98Points=1
+LineDim99Range=350.519
+LineDim99Min=0
+LineDim99Points=1
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-9:0]
+Version=3
+Points=3
+Lines=100
+Frame=MaxAdhesionOut00
+CurLine=99
+Dim0Name=NameX7
+Dim0Unit=Unit7
+Dim0Range=0
+Dim0Min=0
+Dim1Range=1
+Dim1Min=0
+Dim2Name=NameZ7
+Dim2Unit=N
+Dim2Range=2.48398e-08
+Dim2Min=-2.48398e-08
+LineDim0Range=0
+LineDim0Min=0
+LineDim0Points=3
+LineDim1Range=2.48398e-08
+LineDim1Min=-2.48398e-08
+LineDim1Points=3
+LineDim2Range=0
+LineDim2Min=0
+LineDim2Points=3
+LineDim3Range=0
+LineDim3Min=0
+LineDim3Points=3
+LineDim4Range=0
+LineDim4Min=0
+LineDim4Points=3
+LineDim5Range=0
+LineDim5Min=0
+LineDim5Points=3
+LineDim6Range=0
+LineDim6Min=0
+LineDim6Points=3
+LineDim7Range=0
+LineDim7Min=0
+LineDim7Points=3
+LineDim8Range=0
+LineDim8Min=0
+LineDim8Points=3
+LineDim9Range=0
+LineDim9Min=0
+LineDim9Points=3
+LineDim10Range=0
+LineDim10Min=0
+LineDim10Points=3
+LineDim11Range=0
+LineDim11Min=0
+LineDim11Points=3
+LineDim12Range=0
+LineDim12Min=0
+LineDim12Points=3
+LineDim13Range=0
+LineDim13Min=0
+LineDim13Points=3
+LineDim14Range=0
+LineDim14Min=0
+LineDim14Points=3
+LineDim15Range=0
+LineDim15Min=0
+LineDim15Points=3
+LineDim16Range=0
+LineDim16Min=0
+LineDim16Points=3
+LineDim17Range=0
+LineDim17Min=0
+LineDim17Points=3
+LineDim18Range=0
+LineDim18Min=0
+LineDim18Points=3
+LineDim19Range=0
+LineDim19Min=0
+LineDim19Points=3
+LineDim20Range=0
+LineDim20Min=0
+LineDim20Points=3
+LineDim21Range=0
+LineDim21Min=0
+LineDim21Points=3
+LineDim22Range=0
+LineDim22Min=0
+LineDim22Points=3
+LineDim23Range=0
+LineDim23Min=0
+LineDim23Points=3
+LineDim24Range=0
+LineDim24Min=0
+LineDim24Points=3
+LineDim25Range=0
+LineDim25Min=0
+LineDim25Points=3
+LineDim26Range=0
+LineDim26Min=0
+LineDim26Points=3
+LineDim27Range=0
+LineDim27Min=0
+LineDim27Points=3
+LineDim28Range=0
+LineDim28Min=0
+LineDim28Points=3
+LineDim29Range=0
+LineDim29Min=0
+LineDim29Points=3
+LineDim30Range=0
+LineDim30Min=0
+LineDim30Points=3
+LineDim31Range=0
+LineDim31Min=0
+LineDim31Points=3
+LineDim32Range=0
+LineDim32Min=0
+LineDim32Points=3
+LineDim33Range=0
+LineDim33Min=0
+LineDim33Points=3
+LineDim34Range=0
+LineDim34Min=0
+LineDim34Points=3
+LineDim35Range=0
+LineDim35Min=0
+LineDim35Points=3
+LineDim36Range=0
+LineDim36Min=0
+LineDim36Points=3
+LineDim37Range=0
+LineDim37Min=0
+LineDim37Points=3
+LineDim38Range=0
+LineDim38Min=0
+LineDim38Points=3
+LineDim39Range=0
+LineDim39Min=0
+LineDim39Points=3
+LineDim40Range=0
+LineDim40Min=0
+LineDim40Points=3
+LineDim41Range=0
+LineDim41Min=0
+LineDim41Points=3
+LineDim42Range=0
+LineDim42Min=0
+LineDim42Points=3
+LineDim43Range=0
+LineDim43Min=0
+LineDim43Points=3
+LineDim44Range=0
+LineDim44Min=0
+LineDim44Points=3
+LineDim45Range=0
+LineDim45Min=0
+LineDim45Points=3
+LineDim46Range=0
+LineDim46Min=0
+LineDim46Points=3
+LineDim47Range=0
+LineDim47Min=0
+LineDim47Points=3
+LineDim48Range=0
+LineDim48Min=0
+LineDim48Points=3
+LineDim49Range=0
+LineDim49Min=0
+LineDim49Points=3
+LineDim50Range=0
+LineDim50Min=0
+LineDim50Points=3
+LineDim51Range=0
+LineDim51Min=0
+LineDim51Points=3
+LineDim52Range=0
+LineDim52Min=0
+LineDim52Points=3
+LineDim53Range=0
+LineDim53Min=0
+LineDim53Points=3
+LineDim54Range=0
+LineDim54Min=0
+LineDim54Points=3
+LineDim55Range=0
+LineDim55Min=0
+LineDim55Points=3
+LineDim56Range=0
+LineDim56Min=0
+LineDim56Points=3
+LineDim57Range=0
+LineDim57Min=0
+LineDim57Points=3
+LineDim58Range=0
+LineDim58Min=0
+LineDim58Points=3
+LineDim59Range=0
+LineDim59Min=0
+LineDim59Points=3
+LineDim60Range=0
+LineDim60Min=0
+LineDim60Points=3
+LineDim61Range=0
+LineDim61Min=0
+LineDim61Points=3
+LineDim62Range=0
+LineDim62Min=0
+LineDim62Points=3
+LineDim63Range=0
+LineDim63Min=0
+LineDim63Points=3
+LineDim64Range=0
+LineDim64Min=0
+LineDim64Points=3
+LineDim65Range=0
+LineDim65Min=0
+LineDim65Points=3
+LineDim66Range=0
+LineDim66Min=0
+LineDim66Points=3
+LineDim67Range=0
+LineDim67Min=0
+LineDim67Points=3
+LineDim68Range=0
+LineDim68Min=0
+LineDim68Points=3
+LineDim69Range=0
+LineDim69Min=0
+LineDim69Points=3
+LineDim70Range=0
+LineDim70Min=0
+LineDim70Points=3
+LineDim71Range=0
+LineDim71Min=0
+LineDim71Points=3
+LineDim72Range=0
+LineDim72Min=0
+LineDim72Points=3
+LineDim73Range=0
+LineDim73Min=0
+LineDim73Points=3
+LineDim74Range=0
+LineDim74Min=0
+LineDim74Points=3
+LineDim75Range=0
+LineDim75Min=0
+LineDim75Points=3
+LineDim76Range=0
+LineDim76Min=0
+LineDim76Points=3
+LineDim77Range=0
+LineDim77Min=0
+LineDim77Points=3
+LineDim78Range=0
+LineDim78Min=0
+LineDim78Points=3
+LineDim79Range=0
+LineDim79Min=0
+LineDim79Points=3
+LineDim80Range=0
+LineDim80Min=0
+LineDim80Points=3
+LineDim81Range=0
+LineDim81Min=0
+LineDim81Points=3
+LineDim82Range=0
+LineDim82Min=0
+LineDim82Points=3
+LineDim83Range=0
+LineDim83Min=0
+LineDim83Points=3
+LineDim84Range=0
+LineDim84Min=0
+LineDim84Points=3
+LineDim85Range=0
+LineDim85Min=0
+LineDim85Points=3
+LineDim86Range=0
+LineDim86Min=0
+LineDim86Points=3
+LineDim87Range=0
+LineDim87Min=0
+LineDim87Points=3
+LineDim88Range=0
+LineDim88Min=0
+LineDim88Points=3
+LineDim89Range=0
+LineDim89Min=0
+LineDim89Points=3
+LineDim90Range=0
+LineDim90Min=0
+LineDim90Points=3
+LineDim91Range=0
+LineDim91Min=0
+LineDim91Points=3
+LineDim92Range=0
+LineDim92Min=0
+LineDim92Points=3
+LineDim93Range=0
+LineDim93Min=0
+LineDim93Points=3
+LineDim94Range=0
+LineDim94Min=0
+LineDim94Points=3
+LineDim95Range=0
+LineDim95Min=0
+LineDim95Points=3
+LineDim96Range=0
+LineDim96Min=0
+LineDim96Points=3
+LineDim97Range=0
+LineDim97Min=0
+LineDim97Points=3
+LineDim98Range=0
+LineDim98Min=0
+LineDim98Points=3
+LineDim99Range=0.122436
+LineDim99Min=-0.122436
+LineDim99Points=3
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-10:0]
+Version=3
+Points=1
+Lines=100
+Frame=MaxAdhesionOut10
+CurLine=99
+Dim0Name=NameX8
+Dim0Unit=Unit8
+Dim0Range=0
+Dim0Min=0
+Dim1Range=1
+Dim1Min=0
+Dim2Name=NameZ8
+Dim2Unit=m
+Dim2Range=2.56662e-06
+Dim2Min=-2.56662e-06
+LineDim0Range=0
+LineDim0Min=0
+LineDim0Points=1
+LineDim1Range=2.56662e-06
+LineDim1Min=-2.56662e-06
+LineDim1Points=1
+LineDim2Range=0
+LineDim2Min=0
+LineDim2Points=1
+LineDim3Range=0
+LineDim3Min=0
+LineDim3Points=1
+LineDim4Range=0
+LineDim4Min=0
+LineDim4Points=1
+LineDim5Range=0
+LineDim5Min=0
+LineDim5Points=1
+LineDim6Range=0
+LineDim6Min=0
+LineDim6Points=1
+LineDim7Range=0
+LineDim7Min=0
+LineDim7Points=1
+LineDim8Range=0
+LineDim8Min=0
+LineDim8Points=1
+LineDim9Range=0
+LineDim9Min=0
+LineDim9Points=1
+LineDim10Range=0
+LineDim10Min=0
+LineDim10Points=1
+LineDim11Range=0
+LineDim11Min=0
+LineDim11Points=1
+LineDim12Range=0
+LineDim12Min=0
+LineDim12Points=1
+LineDim13Range=0
+LineDim13Min=0
+LineDim13Points=1
+LineDim14Range=0
+LineDim14Min=0
+LineDim14Points=1
+LineDim15Range=0
+LineDim15Min=0
+LineDim15Points=1
+LineDim16Range=0
+LineDim16Min=0
+LineDim16Points=1
+LineDim17Range=0
+LineDim17Min=0
+LineDim17Points=1
+LineDim18Range=0
+LineDim18Min=0
+LineDim18Points=1
+LineDim19Range=0
+LineDim19Min=0
+LineDim19Points=1
+LineDim20Range=0
+LineDim20Min=0
+LineDim20Points=1
+LineDim21Range=0
+LineDim21Min=0
+LineDim21Points=1
+LineDim22Range=0
+LineDim22Min=0
+LineDim22Points=1
+LineDim23Range=0
+LineDim23Min=0
+LineDim23Points=1
+LineDim24Range=0
+LineDim24Min=0
+LineDim24Points=1
+LineDim25Range=0
+LineDim25Min=0
+LineDim25Points=1
+LineDim26Range=0
+LineDim26Min=0
+LineDim26Points=1
+LineDim27Range=0
+LineDim27Min=0
+LineDim27Points=1
+LineDim28Range=0
+LineDim28Min=0
+LineDim28Points=1
+LineDim29Range=0
+LineDim29Min=0
+LineDim29Points=1
+LineDim30Range=0
+LineDim30Min=0
+LineDim30Points=1
+LineDim31Range=0
+LineDim31Min=0
+LineDim31Points=1
+LineDim32Range=0
+LineDim32Min=0
+LineDim32Points=1
+LineDim33Range=0
+LineDim33Min=0
+LineDim33Points=1
+LineDim34Range=0
+LineDim34Min=0
+LineDim34Points=1
+LineDim35Range=0
+LineDim35Min=0
+LineDim35Points=1
+LineDim36Range=0
+LineDim36Min=0
+LineDim36Points=1
+LineDim37Range=0
+LineDim37Min=0
+LineDim37Points=1
+LineDim38Range=0
+LineDim38Min=0
+LineDim38Points=1
+LineDim39Range=0
+LineDim39Min=0
+LineDim39Points=1
+LineDim40Range=0
+LineDim40Min=0
+LineDim40Points=1
+LineDim41Range=0
+LineDim41Min=0
+LineDim41Points=1
+LineDim42Range=0
+LineDim42Min=0
+LineDim42Points=1
+LineDim43Range=0
+LineDim43Min=0
+LineDim43Points=1
+LineDim44Range=0
+LineDim44Min=0
+LineDim44Points=1
+LineDim45Range=0
+LineDim45Min=0
+LineDim45Points=1
+LineDim46Range=0
+LineDim46Min=0
+LineDim46Points=1
+LineDim47Range=0
+LineDim47Min=0
+LineDim47Points=1
+LineDim48Range=0
+LineDim48Min=0
+LineDim48Points=1
+LineDim49Range=0
+LineDim49Min=0
+LineDim49Points=1
+LineDim50Range=0
+LineDim50Min=0
+LineDim50Points=1
+LineDim51Range=0
+LineDim51Min=0
+LineDim51Points=1
+LineDim52Range=0
+LineDim52Min=0
+LineDim52Points=1
+LineDim53Range=0
+LineDim53Min=0
+LineDim53Points=1
+LineDim54Range=0
+LineDim54Min=0
+LineDim54Points=1
+LineDim55Range=0
+LineDim55Min=0
+LineDim55Points=1
+LineDim56Range=0
+LineDim56Min=0
+LineDim56Points=1
+LineDim57Range=0
+LineDim57Min=0
+LineDim57Points=1
+LineDim58Range=0
+LineDim58Min=0
+LineDim58Points=1
+LineDim59Range=0
+LineDim59Min=0
+LineDim59Points=1
+LineDim60Range=0
+LineDim60Min=0
+LineDim60Points=1
+LineDim61Range=0
+LineDim61Min=0
+LineDim61Points=1
+LineDim62Range=0
+LineDim62Min=0
+LineDim62Points=1
+LineDim63Range=0
+LineDim63Min=0
+LineDim63Points=1
+LineDim64Range=0
+LineDim64Min=0
+LineDim64Points=1
+LineDim65Range=0
+LineDim65Min=0
+LineDim65Points=1
+LineDim66Range=0
+LineDim66Min=0
+LineDim66Points=1
+LineDim67Range=0
+LineDim67Min=0
+LineDim67Points=1
+LineDim68Range=0
+LineDim68Min=0
+LineDim68Points=1
+LineDim69Range=0
+LineDim69Min=0
+LineDim69Points=1
+LineDim70Range=0
+LineDim70Min=0
+LineDim70Points=1
+LineDim71Range=0
+LineDim71Min=0
+LineDim71Points=1
+LineDim72Range=0
+LineDim72Min=0
+LineDim72Points=1
+LineDim73Range=0
+LineDim73Min=0
+LineDim73Points=1
+LineDim74Range=0
+LineDim74Min=0
+LineDim74Points=1
+LineDim75Range=0
+LineDim75Min=0
+LineDim75Points=1
+LineDim76Range=0
+LineDim76Min=0
+LineDim76Points=1
+LineDim77Range=0
+LineDim77Min=0
+LineDim77Points=1
+LineDim78Range=0
+LineDim78Min=0
+LineDim78Points=1
+LineDim79Range=0
+LineDim79Min=0
+LineDim79Points=1
+LineDim80Range=0
+LineDim80Min=0
+LineDim80Points=1
+LineDim81Range=0
+LineDim81Min=0
+LineDim81Points=1
+LineDim82Range=0
+LineDim82Min=0
+LineDim82Points=1
+LineDim83Range=0
+LineDim83Min=0
+LineDim83Points=1
+LineDim84Range=0
+LineDim84Min=0
+LineDim84Points=1
+LineDim85Range=0
+LineDim85Min=0
+LineDim85Points=1
+LineDim86Range=0
+LineDim86Min=0
+LineDim86Points=1
+LineDim87Range=0
+LineDim87Min=0
+LineDim87Points=1
+LineDim88Range=0
+LineDim88Min=0
+LineDim88Points=1
+LineDim89Range=0
+LineDim89Min=0
+LineDim89Points=1
+LineDim90Range=0
+LineDim90Min=0
+LineDim90Points=1
+LineDim91Range=0
+LineDim91Min=0
+LineDim91Points=1
+LineDim92Range=0
+LineDim92Min=0
+LineDim92Points=1
+LineDim93Range=0
+LineDim93Min=0
+LineDim93Points=1
+LineDim94Range=0
+LineDim94Min=0
+LineDim94Points=1
+LineDim95Range=0
+LineDim95Min=0
+LineDim95Points=1
+LineDim96Range=0
+LineDim96Min=0
+LineDim96Points=1
+LineDim97Range=0
+LineDim97Min=0
+LineDim97Points=1
+LineDim98Range=0
+LineDim98Min=0
+LineDim98Points=1
+LineDim99Range=2.36201e-06
+LineDim99Min=-2.36201e-06
+LineDim99Points=1
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-11:0]
+Version=3
+Points=2
+Lines=100
+Frame=SlopeOut01
+CurLine=99
+Dim0Name=NameX9
+Dim0Unit=Unit9
+Dim0Range=0
+Dim0Min=0
+Dim1Range=1
+Dim1Min=0
+Dim2Name=NameZ9
+Dim2Unit=m
+Dim2Range=2.55953e-06
+Dim2Min=-2.55953e-06
+LineDim0Range=0
+LineDim0Min=0
+LineDim0Points=2
+LineDim1Range=2.55953e-06
+LineDim1Min=-2.55953e-06
+LineDim1Points=2
+LineDim2Range=0
+LineDim2Min=0
+LineDim2Points=2
+LineDim3Range=0
+LineDim3Min=0
+LineDim3Points=2
+LineDim4Range=0
+LineDim4Min=0
+LineDim4Points=2
+LineDim5Range=0
+LineDim5Min=0
+LineDim5Points=2
+LineDim6Range=0
+LineDim6Min=0
+LineDim6Points=2
+LineDim7Range=0
+LineDim7Min=0
+LineDim7Points=2
+LineDim8Range=0
+LineDim8Min=0
+LineDim8Points=2
+LineDim9Range=0
+LineDim9Min=0
+LineDim9Points=2
+LineDim10Range=0
+LineDim10Min=0
+LineDim10Points=2
+LineDim11Range=0
+LineDim11Min=0
+LineDim11Points=2
+LineDim12Range=0
+LineDim12Min=0
+LineDim12Points=2
+LineDim13Range=0
+LineDim13Min=0
+LineDim13Points=2
+LineDim14Range=0
+LineDim14Min=0
+LineDim14Points=2
+LineDim15Range=0
+LineDim15Min=0
+LineDim15Points=2
+LineDim16Range=0
+LineDim16Min=0
+LineDim16Points=2
+LineDim17Range=0
+LineDim17Min=0
+LineDim17Points=2
+LineDim18Range=0
+LineDim18Min=0
+LineDim18Points=2
+LineDim19Range=0
+LineDim19Min=0
+LineDim19Points=2
+LineDim20Range=0
+LineDim20Min=0
+LineDim20Points=2
+LineDim21Range=0
+LineDim21Min=0
+LineDim21Points=2
+LineDim22Range=0
+LineDim22Min=0
+LineDim22Points=2
+LineDim23Range=0
+LineDim23Min=0
+LineDim23Points=2
+LineDim24Range=0
+LineDim24Min=0
+LineDim24Points=2
+LineDim25Range=0
+LineDim25Min=0
+LineDim25Points=2
+LineDim26Range=0
+LineDim26Min=0
+LineDim26Points=2
+LineDim27Range=0
+LineDim27Min=0
+LineDim27Points=2
+LineDim28Range=0
+LineDim28Min=0
+LineDim28Points=2
+LineDim29Range=0
+LineDim29Min=0
+LineDim29Points=2
+LineDim30Range=0
+LineDim30Min=0
+LineDim30Points=2
+LineDim31Range=0
+LineDim31Min=0
+LineDim31Points=2
+LineDim32Range=0
+LineDim32Min=0
+LineDim32Points=2
+LineDim33Range=0
+LineDim33Min=0
+LineDim33Points=2
+LineDim34Range=0
+LineDim34Min=0
+LineDim34Points=2
+LineDim35Range=0
+LineDim35Min=0
+LineDim35Points=2
+LineDim36Range=0
+LineDim36Min=0
+LineDim36Points=2
+LineDim37Range=0
+LineDim37Min=0
+LineDim37Points=2
+LineDim38Range=0
+LineDim38Min=0
+LineDim38Points=2
+LineDim39Range=0
+LineDim39Min=0
+LineDim39Points=2
+LineDim40Range=0
+LineDim40Min=0
+LineDim40Points=2
+LineDim41Range=0
+LineDim41Min=0
+LineDim41Points=2
+LineDim42Range=0
+LineDim42Min=0
+LineDim42Points=2
+LineDim43Range=0
+LineDim43Min=0
+LineDim43Points=2
+LineDim44Range=0
+LineDim44Min=0
+LineDim44Points=2
+LineDim45Range=0
+LineDim45Min=0
+LineDim45Points=2
+LineDim46Range=0
+LineDim46Min=0
+LineDim46Points=2
+LineDim47Range=0
+LineDim47Min=0
+LineDim47Points=2
+LineDim48Range=0
+LineDim48Min=0
+LineDim48Points=2
+LineDim49Range=0
+LineDim49Min=0
+LineDim49Points=2
+LineDim50Range=0
+LineDim50Min=0
+LineDim50Points=2
+LineDim51Range=0
+LineDim51Min=0
+LineDim51Points=2
+LineDim52Range=0
+LineDim52Min=0
+LineDim52Points=2
+LineDim53Range=0
+LineDim53Min=0
+LineDim53Points=2
+LineDim54Range=0
+LineDim54Min=0
+LineDim54Points=2
+LineDim55Range=0
+LineDim55Min=0
+LineDim55Points=2
+LineDim56Range=0
+LineDim56Min=0
+LineDim56Points=2
+LineDim57Range=0
+LineDim57Min=0
+LineDim57Points=2
+LineDim58Range=0
+LineDim58Min=0
+LineDim58Points=2
+LineDim59Range=0
+LineDim59Min=0
+LineDim59Points=2
+LineDim60Range=0
+LineDim60Min=0
+LineDim60Points=2
+LineDim61Range=0
+LineDim61Min=0
+LineDim61Points=2
+LineDim62Range=0
+LineDim62Min=0
+LineDim62Points=2
+LineDim63Range=0
+LineDim63Min=0
+LineDim63Points=2
+LineDim64Range=0
+LineDim64Min=0
+LineDim64Points=2
+LineDim65Range=0
+LineDim65Min=0
+LineDim65Points=2
+LineDim66Range=0
+LineDim66Min=0
+LineDim66Points=2
+LineDim67Range=0
+LineDim67Min=0
+LineDim67Points=2
+LineDim68Range=0
+LineDim68Min=0
+LineDim68Points=2
+LineDim69Range=0
+LineDim69Min=0
+LineDim69Points=2
+LineDim70Range=0
+LineDim70Min=0
+LineDim70Points=2
+LineDim71Range=0
+LineDim71Min=0
+LineDim71Points=2
+LineDim72Range=0
+LineDim72Min=0
+LineDim72Points=2
+LineDim73Range=0
+LineDim73Min=0
+LineDim73Points=2
+LineDim74Range=0
+LineDim74Min=0
+LineDim74Points=2
+LineDim75Range=0
+LineDim75Min=0
+LineDim75Points=2
+LineDim76Range=0
+LineDim76Min=0
+LineDim76Points=2
+LineDim77Range=0
+LineDim77Min=0
+LineDim77Points=2
+LineDim78Range=0
+LineDim78Min=0
+LineDim78Points=2
+LineDim79Range=0
+LineDim79Min=0
+LineDim79Points=2
+LineDim80Range=0
+LineDim80Min=0
+LineDim80Points=2
+LineDim81Range=0
+LineDim81Min=0
+LineDim81Points=2
+LineDim82Range=0
+LineDim82Min=0
+LineDim82Points=2
+LineDim83Range=0
+LineDim83Min=0
+LineDim83Points=2
+LineDim84Range=0
+LineDim84Min=0
+LineDim84Points=2
+LineDim85Range=0
+LineDim85Min=0
+LineDim85Points=2
+LineDim86Range=0
+LineDim86Min=0
+LineDim86Points=2
+LineDim87Range=0
+LineDim87Min=0
+LineDim87Points=2
+LineDim88Range=0
+LineDim88Min=0
+LineDim88Points=2
+LineDim89Range=0
+LineDim89Min=0
+LineDim89Points=2
+LineDim90Range=0
+LineDim90Min=0
+LineDim90Points=2
+LineDim91Range=0
+LineDim91Min=0
+LineDim91Points=2
+LineDim92Range=0
+LineDim92Min=0
+LineDim92Points=2
+LineDim93Range=0
+LineDim93Min=0
+LineDim93Points=2
+LineDim94Range=0
+LineDim94Min=0
+LineDim94Points=2
+LineDim95Range=0
+LineDim95Min=0
+LineDim95Points=2
+LineDim96Range=0
+LineDim96Min=0
+LineDim96Points=2
+LineDim97Range=0
+LineDim97Min=0
+LineDim97Points=2
+LineDim98Range=0
+LineDim98Min=0
+LineDim98Points=2
+LineDim99Range=2.35325e-06
+LineDim99Min=-2.35325e-06
+LineDim99Points=2
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-12:0]
+Version=3
+Points=3
+Lines=100
+Frame=SlopeOut11
+CurLine=99
+Dim0Name=NameX10
+Dim0Unit=Unit10
+Dim0Range=0
+Dim0Min=0
+Dim1Range=1
+Dim1Min=0
+Dim2Name=NameZ10
+Dim2Unit=N/m
+Dim2Range=358.677
+Dim2Min=-80.0889
+LineDim0Range=0
+LineDim0Min=0
+LineDim0Points=3
+LineDim1Range=139.294
+LineDim1Min=0
+LineDim1Points=3
+LineDim2Range=0
+LineDim2Min=0
+LineDim2Points=3
+LineDim3Range=0
+LineDim3Min=0
+LineDim3Points=3
+LineDim4Range=0
+LineDim4Min=0
+LineDim4Points=3
+LineDim5Range=0
+LineDim5Min=0
+LineDim5Points=3
+LineDim6Range=0
+LineDim6Min=0
+LineDim6Points=3
+LineDim7Range=0
+LineDim7Min=0
+LineDim7Points=3
+LineDim8Range=0
+LineDim8Min=0
+LineDim8Points=3
+LineDim9Range=0
+LineDim9Min=0
+LineDim9Points=3
+LineDim10Range=0
+LineDim10Min=0
+LineDim10Points=3
+LineDim11Range=0
+LineDim11Min=0
+LineDim11Points=3
+LineDim12Range=0
+LineDim12Min=0
+LineDim12Points=3
+LineDim13Range=0
+LineDim13Min=0
+LineDim13Points=3
+LineDim14Range=0
+LineDim14Min=0
+LineDim14Points=3
+LineDim15Range=0
+LineDim15Min=0
+LineDim15Points=3
+LineDim16Range=0
+LineDim16Min=0
+LineDim16Points=3
+LineDim17Range=0
+LineDim17Min=0
+LineDim17Points=3
+LineDim18Range=0
+LineDim18Min=0
+LineDim18Points=3
+LineDim19Range=0
+LineDim19Min=0
+LineDim19Points=3
+LineDim20Range=0
+LineDim20Min=0
+LineDim20Points=3
+LineDim21Range=0
+LineDim21Min=0
+LineDim21Points=3
+LineDim22Range=0
+LineDim22Min=0
+LineDim22Points=3
+LineDim23Range=0
+LineDim23Min=0
+LineDim23Points=3
+LineDim24Range=0
+LineDim24Min=0
+LineDim24Points=3
+LineDim25Range=0
+LineDim25Min=0
+LineDim25Points=3
+LineDim26Range=0
+LineDim26Min=0
+LineDim26Points=3
+LineDim27Range=0
+LineDim27Min=0
+LineDim27Points=3
+LineDim28Range=0
+LineDim28Min=0
+LineDim28Points=3
+LineDim29Range=0
+LineDim29Min=0
+LineDim29Points=3
+LineDim30Range=0
+LineDim30Min=0
+LineDim30Points=3
+LineDim31Range=0
+LineDim31Min=0
+LineDim31Points=3
+LineDim32Range=0
+LineDim32Min=0
+LineDim32Points=3
+LineDim33Range=0
+LineDim33Min=0
+LineDim33Points=3
+LineDim34Range=0
+LineDim34Min=0
+LineDim34Points=3
+LineDim35Range=0
+LineDim35Min=0
+LineDim35Points=3
+LineDim36Range=0
+LineDim36Min=0
+LineDim36Points=3
+LineDim37Range=0
+LineDim37Min=0
+LineDim37Points=3
+LineDim38Range=0
+LineDim38Min=0
+LineDim38Points=3
+LineDim39Range=0
+LineDim39Min=0
+LineDim39Points=3
+LineDim40Range=0
+LineDim40Min=0
+LineDim40Points=3
+LineDim41Range=0
+LineDim41Min=0
+LineDim41Points=3
+LineDim42Range=0
+LineDim42Min=0
+LineDim42Points=3
+LineDim43Range=0
+LineDim43Min=0
+LineDim43Points=3
+LineDim44Range=0
+LineDim44Min=0
+LineDim44Points=3
+LineDim45Range=0
+LineDim45Min=0
+LineDim45Points=3
+LineDim46Range=0
+LineDim46Min=0
+LineDim46Points=3
+LineDim47Range=0
+LineDim47Min=0
+LineDim47Points=3
+LineDim48Range=0
+LineDim48Min=0
+LineDim48Points=3
+LineDim49Range=0
+LineDim49Min=0
+LineDim49Points=3
+LineDim50Range=0
+LineDim50Min=0
+LineDim50Points=3
+LineDim51Range=0
+LineDim51Min=0
+LineDim51Points=3
+LineDim52Range=0
+LineDim52Min=0
+LineDim52Points=3
+LineDim53Range=0
+LineDim53Min=0
+LineDim53Points=3
+LineDim54Range=0
+LineDim54Min=0
+LineDim54Points=3
+LineDim55Range=0
+LineDim55Min=0
+LineDim55Points=3
+LineDim56Range=0
+LineDim56Min=0
+LineDim56Points=3
+LineDim57Range=0
+LineDim57Min=0
+LineDim57Points=3
+LineDim58Range=0
+LineDim58Min=0
+LineDim58Points=3
+LineDim59Range=0
+LineDim59Min=0
+LineDim59Points=3
+LineDim60Range=0
+LineDim60Min=0
+LineDim60Points=3
+LineDim61Range=0
+LineDim61Min=0
+LineDim61Points=3
+LineDim62Range=0
+LineDim62Min=0
+LineDim62Points=3
+LineDim63Range=0
+LineDim63Min=0
+LineDim63Points=3
+LineDim64Range=0
+LineDim64Min=0
+LineDim64Points=3
+LineDim65Range=0
+LineDim65Min=0
+LineDim65Points=3
+LineDim66Range=0
+LineDim66Min=0
+LineDim66Points=3
+LineDim67Range=0
+LineDim67Min=0
+LineDim67Points=3
+LineDim68Range=0
+LineDim68Min=0
+LineDim68Points=3
+LineDim69Range=0
+LineDim69Min=0
+LineDim69Points=3
+LineDim70Range=0
+LineDim70Min=0
+LineDim70Points=3
+LineDim71Range=0
+LineDim71Min=0
+LineDim71Points=3
+LineDim72Range=0
+LineDim72Min=0
+LineDim72Points=3
+LineDim73Range=0
+LineDim73Min=0
+LineDim73Points=3
+LineDim74Range=0
+LineDim74Min=0
+LineDim74Points=3
+LineDim75Range=0
+LineDim75Min=0
+LineDim75Points=3
+LineDim76Range=0
+LineDim76Min=0
+LineDim76Points=3
+LineDim77Range=0
+LineDim77Min=0
+LineDim77Points=3
+LineDim78Range=0
+LineDim78Min=0
+LineDim78Points=3
+LineDim79Range=0
+LineDim79Min=0
+LineDim79Points=3
+LineDim80Range=254.782
+LineDim80Min=0
+LineDim80Points=3
+LineDim81Range=358.677
+LineDim81Min=-80.0889
+LineDim81Points=3
+LineDim82Range=0
+LineDim82Min=0
+LineDim82Points=3
+LineDim83Range=0
+LineDim83Min=0
+LineDim83Points=3
+LineDim84Range=0
+LineDim84Min=0
+LineDim84Points=3
+LineDim85Range=0
+LineDim85Min=0
+LineDim85Points=3
+LineDim86Range=0
+LineDim86Min=0
+LineDim86Points=3
+LineDim87Range=0
+LineDim87Min=0
+LineDim87Points=3
+LineDim88Range=0
+LineDim88Min=0
+LineDim88Points=3
+LineDim89Range=0
+LineDim89Min=0
+LineDim89Points=3
+LineDim90Range=0
+LineDim90Min=0
+LineDim90Points=3
+LineDim91Range=0
+LineDim91Min=0
+LineDim91Points=3
+LineDim92Range=0
+LineDim92Min=0
+LineDim92Points=3
+LineDim93Range=0
+LineDim93Min=0
+LineDim93Points=3
+LineDim94Range=0
+LineDim94Min=0
+LineDim94Points=3
+LineDim95Range=0
+LineDim95Min=0
+LineDim95Points=3
+LineDim96Range=0
+LineDim96Min=0
+LineDim96Points=3
+LineDim97Range=0
+LineDim97Min=0
+LineDim97Points=3
+LineDim98Range=0
+LineDim98Min=0
+LineDim98Points=3
+LineDim99Range=4.57369e+08
+LineDim99Min=0
+LineDim99Points=3
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-13:0]
+Version=3
+Points=1
+Lines=100
+Frame=SlopeOut21
+CurLine=99
+Dim0Name=NameX11
+Dim0Unit=Unit11
+Dim0Range=0
+Dim0Min=0
+Dim1Range=1
+Dim1Min=0
+Dim2Name=NameZ11
+Dim2Unit=N
+Dim2Range=0.000453639
+Dim2Min=-9.65784e-05
+LineDim0Range=0
+LineDim0Min=0
+LineDim0Points=1
+LineDim1Range=0.00017853
+LineDim1Min=0
+LineDim1Points=1
+LineDim2Range=0
+LineDim2Min=0
+LineDim2Points=1
+LineDim3Range=0
+LineDim3Min=0
+LineDim3Points=1
+LineDim4Range=0
+LineDim4Min=0
+LineDim4Points=1
+LineDim5Range=0
+LineDim5Min=0
+LineDim5Points=1
+LineDim6Range=0
+LineDim6Min=0
+LineDim6Points=1
+LineDim7Range=0
+LineDim7Min=0
+LineDim7Points=1
+LineDim8Range=0
+LineDim8Min=0
+LineDim8Points=1
+LineDim9Range=0
+LineDim9Min=0
+LineDim9Points=1
+LineDim10Range=0
+LineDim10Min=0
+LineDim10Points=1
+LineDim11Range=0
+LineDim11Min=0
+LineDim11Points=1
+LineDim12Range=0
+LineDim12Min=0
+LineDim12Points=1
+LineDim13Range=0
+LineDim13Min=0
+LineDim13Points=1
+LineDim14Range=0
+LineDim14Min=0
+LineDim14Points=1
+LineDim15Range=0
+LineDim15Min=0
+LineDim15Points=1
+LineDim16Range=0
+LineDim16Min=0
+LineDim16Points=1
+LineDim17Range=0
+LineDim17Min=0
+LineDim17Points=1
+LineDim18Range=0
+LineDim18Min=0
+LineDim18Points=1
+LineDim19Range=0
+LineDim19Min=0
+LineDim19Points=1
+LineDim20Range=0
+LineDim20Min=0
+LineDim20Points=1
+LineDim21Range=0
+LineDim21Min=0
+LineDim21Points=1
+LineDim22Range=0
+LineDim22Min=0
+LineDim22Points=1
+LineDim23Range=0
+LineDim23Min=0
+LineDim23Points=1
+LineDim24Range=0
+LineDim24Min=0
+LineDim24Points=1
+LineDim25Range=0
+LineDim25Min=0
+LineDim25Points=1
+LineDim26Range=0
+LineDim26Min=0
+LineDim26Points=1
+LineDim27Range=0
+LineDim27Min=0
+LineDim27Points=1
+LineDim28Range=0
+LineDim28Min=0
+LineDim28Points=1
+LineDim29Range=0
+LineDim29Min=0
+LineDim29Points=1
+LineDim30Range=0
+LineDim30Min=0
+LineDim30Points=1
+LineDim31Range=0
+LineDim31Min=0
+LineDim31Points=1
+LineDim32Range=0
+LineDim32Min=0
+LineDim32Points=1
+LineDim33Range=0
+LineDim33Min=0
+LineDim33Points=1
+LineDim34Range=0
+LineDim34Min=0
+LineDim34Points=1
+LineDim35Range=0
+LineDim35Min=0
+LineDim35Points=1
+LineDim36Range=0
+LineDim36Min=0
+LineDim36Points=1
+LineDim37Range=0
+LineDim37Min=0
+LineDim37Points=1
+LineDim38Range=0
+LineDim38Min=0
+LineDim38Points=1
+LineDim39Range=0
+LineDim39Min=0
+LineDim39Points=1
+LineDim40Range=0
+LineDim40Min=0
+LineDim40Points=1
+LineDim41Range=0
+LineDim41Min=0
+LineDim41Points=1
+LineDim42Range=0
+LineDim42Min=0
+LineDim42Points=1
+LineDim43Range=0
+LineDim43Min=0
+LineDim43Points=1
+LineDim44Range=0
+LineDim44Min=0
+LineDim44Points=1
+LineDim45Range=0
+LineDim45Min=0
+LineDim45Points=1
+LineDim46Range=0
+LineDim46Min=0
+LineDim46Points=1
+LineDim47Range=0
+LineDim47Min=0
+LineDim47Points=1
+LineDim48Range=0
+LineDim48Min=0
+LineDim48Points=1
+LineDim49Range=0
+LineDim49Min=0
+LineDim49Points=1
+LineDim50Range=0
+LineDim50Min=0
+LineDim50Points=1
+LineDim51Range=0
+LineDim51Min=0
+LineDim51Points=1
+LineDim52Range=0
+LineDim52Min=0
+LineDim52Points=1
+LineDim53Range=0
+LineDim53Min=0
+LineDim53Points=1
+LineDim54Range=0
+LineDim54Min=0
+LineDim54Points=1
+LineDim55Range=0
+LineDim55Min=0
+LineDim55Points=1
+LineDim56Range=0
+LineDim56Min=0
+LineDim56Points=1
+LineDim57Range=0
+LineDim57Min=0
+LineDim57Points=1
+LineDim58Range=0
+LineDim58Min=0
+LineDim58Points=1
+LineDim59Range=0
+LineDim59Min=0
+LineDim59Points=1
+LineDim60Range=0
+LineDim60Min=0
+LineDim60Points=1
+LineDim61Range=0
+LineDim61Min=0
+LineDim61Points=1
+LineDim62Range=0
+LineDim62Min=0
+LineDim62Points=1
+LineDim63Range=0
+LineDim63Min=0
+LineDim63Points=1
+LineDim64Range=0
+LineDim64Min=0
+LineDim64Points=1
+LineDim65Range=0
+LineDim65Min=0
+LineDim65Points=1
+LineDim66Range=0
+LineDim66Min=0
+LineDim66Points=1
+LineDim67Range=0
+LineDim67Min=0
+LineDim67Points=1
+LineDim68Range=0
+LineDim68Min=0
+LineDim68Points=1
+LineDim69Range=0
+LineDim69Min=0
+LineDim69Points=1
+LineDim70Range=0
+LineDim70Min=0
+LineDim70Points=1
+LineDim71Range=0
+LineDim71Min=0
+LineDim71Points=1
+LineDim72Range=0
+LineDim72Min=0
+LineDim72Points=1
+LineDim73Range=0
+LineDim73Min=0
+LineDim73Points=1
+LineDim74Range=0
+LineDim74Min=0
+LineDim74Points=1
+LineDim75Range=0
+LineDim75Min=0
+LineDim75Points=1
+LineDim76Range=0
+LineDim76Min=0
+LineDim76Points=1
+LineDim77Range=0
+LineDim77Min=0
+LineDim77Points=1
+LineDim78Range=0
+LineDim78Min=0
+LineDim78Points=1
+LineDim79Range=0
+LineDim79Min=0
+LineDim79Points=1
+LineDim80Range=0.000303857
+LineDim80Min=0
+LineDim80Points=1
+LineDim81Range=0.000453639
+LineDim81Min=-9.65784e-05
+LineDim81Points=1
+LineDim82Range=0
+LineDim82Min=0
+LineDim82Points=1
+LineDim83Range=0
+LineDim83Min=0
+LineDim83Points=1
+LineDim84Range=0
+LineDim84Min=0
+LineDim84Points=1
+LineDim85Range=0
+LineDim85Min=0
+LineDim85Points=1
+LineDim86Range=0
+LineDim86Min=0
+LineDim86Points=1
+LineDim87Range=0
+LineDim87Min=0
+LineDim87Points=1
+LineDim88Range=0
+LineDim88Min=0
+LineDim88Points=1
+LineDim89Range=0
+LineDim89Min=0
+LineDim89Points=1
+LineDim90Range=0
+LineDim90Min=0
+LineDim90Points=1
+LineDim91Range=0
+LineDim91Min=0
+LineDim91Points=1
+LineDim92Range=0
+LineDim92Min=0
+LineDim92Points=1
+LineDim93Range=0
+LineDim93Min=0
+LineDim93Points=1
+LineDim94Range=0
+LineDim94Min=0
+LineDim94Points=1
+LineDim95Range=0
+LineDim95Min=0
+LineDim95Points=1
+LineDim96Range=0
+LineDim96Min=0
+LineDim96Points=1
+LineDim97Range=0
+LineDim97Min=0
+LineDim97Points=1
+LineDim98Range=0
+LineDim98Min=0
+LineDim98Points=1
+LineDim99Range=539.527
+LineDim99Min=0
+LineDim99Points=1
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-14:0]
+Version=3
+Points=3
+Lines=100
+Frame=SnapInOut00
+CurLine=99
+Dim0Name=NameX12
+Dim0Unit=Unit12
+Dim0Range=0
+Dim0Min=0
+Dim1Range=1
+Dim1Min=0
+Dim2Name=NameZ12
+Dim2Unit=N
+Dim2Range=7.95778e-09
+Dim2Min=-7.95778e-09
+LineDim0Range=0
+LineDim0Min=0
+LineDim0Points=3
+LineDim1Range=7.95778e-09
+LineDim1Min=-7.95778e-09
+LineDim1Points=3
+LineDim2Range=0
+LineDim2Min=0
+LineDim2Points=3
+LineDim3Range=0
+LineDim3Min=0
+LineDim3Points=3
+LineDim4Range=0
+LineDim4Min=0
+LineDim4Points=3
+LineDim5Range=0
+LineDim5Min=0
+LineDim5Points=3
+LineDim6Range=0
+LineDim6Min=0
+LineDim6Points=3
+LineDim7Range=0
+LineDim7Min=0
+LineDim7Points=3
+LineDim8Range=0
+LineDim8Min=0
+LineDim8Points=3
+LineDim9Range=0
+LineDim9Min=0
+LineDim9Points=3
+LineDim10Range=0
+LineDim10Min=0
+LineDim10Points=3
+LineDim11Range=0
+LineDim11Min=0
+LineDim11Points=3
+LineDim12Range=0
+LineDim12Min=0
+LineDim12Points=3
+LineDim13Range=0
+LineDim13Min=0
+LineDim13Points=3
+LineDim14Range=0
+LineDim14Min=0
+LineDim14Points=3
+LineDim15Range=0
+LineDim15Min=0
+LineDim15Points=3
+LineDim16Range=0
+LineDim16Min=0
+LineDim16Points=3
+LineDim17Range=0
+LineDim17Min=0
+LineDim17Points=3
+LineDim18Range=0
+LineDim18Min=0
+LineDim18Points=3
+LineDim19Range=0
+LineDim19Min=0
+LineDim19Points=3
+LineDim20Range=0
+LineDim20Min=0
+LineDim20Points=3
+LineDim21Range=0
+LineDim21Min=0
+LineDim21Points=3
+LineDim22Range=0
+LineDim22Min=0
+LineDim22Points=3
+LineDim23Range=0
+LineDim23Min=0
+LineDim23Points=3
+LineDim24Range=0
+LineDim24Min=0
+LineDim24Points=3
+LineDim25Range=0
+LineDim25Min=0
+LineDim25Points=3
+LineDim26Range=0
+LineDim26Min=0
+LineDim26Points=3
+LineDim27Range=0
+LineDim27Min=0
+LineDim27Points=3
+LineDim28Range=0
+LineDim28Min=0
+LineDim28Points=3
+LineDim29Range=0
+LineDim29Min=0
+LineDim29Points=3
+LineDim30Range=0
+LineDim30Min=0
+LineDim30Points=3
+LineDim31Range=0
+LineDim31Min=0
+LineDim31Points=3
+LineDim32Range=0
+LineDim32Min=0
+LineDim32Points=3
+LineDim33Range=0
+LineDim33Min=0
+LineDim33Points=3
+LineDim34Range=0
+LineDim34Min=0
+LineDim34Points=3
+LineDim35Range=0
+LineDim35Min=0
+LineDim35Points=3
+LineDim36Range=0
+LineDim36Min=0
+LineDim36Points=3
+LineDim37Range=0
+LineDim37Min=0
+LineDim37Points=3
+LineDim38Range=0
+LineDim38Min=0
+LineDim38Points=3
+LineDim39Range=0
+LineDim39Min=0
+LineDim39Points=3
+LineDim40Range=0
+LineDim40Min=0
+LineDim40Points=3
+LineDim41Range=0
+LineDim41Min=0
+LineDim41Points=3
+LineDim42Range=0
+LineDim42Min=0
+LineDim42Points=3
+LineDim43Range=0
+LineDim43Min=0
+LineDim43Points=3
+LineDim44Range=0
+LineDim44Min=0
+LineDim44Points=3
+LineDim45Range=0
+LineDim45Min=0
+LineDim45Points=3
+LineDim46Range=0
+LineDim46Min=0
+LineDim46Points=3
+LineDim47Range=0
+LineDim47Min=0
+LineDim47Points=3
+LineDim48Range=0
+LineDim48Min=0
+LineDim48Points=3
+LineDim49Range=0
+LineDim49Min=0
+LineDim49Points=3
+LineDim50Range=0
+LineDim50Min=0
+LineDim50Points=3
+LineDim51Range=0
+LineDim51Min=0
+LineDim51Points=3
+LineDim52Range=0
+LineDim52Min=0
+LineDim52Points=3
+LineDim53Range=0
+LineDim53Min=0
+LineDim53Points=3
+LineDim54Range=0
+LineDim54Min=0
+LineDim54Points=3
+LineDim55Range=0
+LineDim55Min=0
+LineDim55Points=3
+LineDim56Range=0
+LineDim56Min=0
+LineDim56Points=3
+LineDim57Range=0
+LineDim57Min=0
+LineDim57Points=3
+LineDim58Range=0
+LineDim58Min=0
+LineDim58Points=3
+LineDim59Range=0
+LineDim59Min=0
+LineDim59Points=3
+LineDim60Range=0
+LineDim60Min=0
+LineDim60Points=3
+LineDim61Range=0
+LineDim61Min=0
+LineDim61Points=3
+LineDim62Range=0
+LineDim62Min=0
+LineDim62Points=3
+LineDim63Range=0
+LineDim63Min=0
+LineDim63Points=3
+LineDim64Range=0
+LineDim64Min=0
+LineDim64Points=3
+LineDim65Range=0
+LineDim65Min=0
+LineDim65Points=3
+LineDim66Range=0
+LineDim66Min=0
+LineDim66Points=3
+LineDim67Range=0
+LineDim67Min=0
+LineDim67Points=3
+LineDim68Range=0
+LineDim68Min=0
+LineDim68Points=3
+LineDim69Range=0
+LineDim69Min=0
+LineDim69Points=3
+LineDim70Range=0
+LineDim70Min=0
+LineDim70Points=3
+LineDim71Range=0
+LineDim71Min=0
+LineDim71Points=3
+LineDim72Range=0
+LineDim72Min=0
+LineDim72Points=3
+LineDim73Range=0
+LineDim73Min=0
+LineDim73Points=3
+LineDim74Range=0
+LineDim74Min=0
+LineDim74Points=3
+LineDim75Range=0
+LineDim75Min=0
+LineDim75Points=3
+LineDim76Range=0
+LineDim76Min=0
+LineDim76Points=3
+LineDim77Range=0
+LineDim77Min=0
+LineDim77Points=3
+LineDim78Range=0
+LineDim78Min=0
+LineDim78Points=3
+LineDim79Range=0
+LineDim79Min=0
+LineDim79Points=3
+LineDim80Range=0
+LineDim80Min=0
+LineDim80Points=3
+LineDim81Range=0
+LineDim81Min=0
+LineDim81Points=3
+LineDim82Range=0
+LineDim82Min=0
+LineDim82Points=3
+LineDim83Range=0
+LineDim83Min=0
+LineDim83Points=3
+LineDim84Range=0
+LineDim84Min=0
+LineDim84Points=3
+LineDim85Range=0
+LineDim85Min=0
+LineDim85Points=3
+LineDim86Range=0
+LineDim86Min=0
+LineDim86Points=3
+LineDim87Range=0
+LineDim87Min=0
+LineDim87Points=3
+LineDim88Range=0
+LineDim88Min=0
+LineDim88Points=3
+LineDim89Range=0
+LineDim89Min=0
+LineDim89Points=3
+LineDim90Range=0
+LineDim90Min=0
+LineDim90Points=3
+LineDim91Range=0
+LineDim91Min=0
+LineDim91Points=3
+LineDim92Range=0
+LineDim92Min=0
+LineDim92Points=3
+LineDim93Range=0
+LineDim93Min=0
+LineDim93Points=3
+LineDim94Range=0
+LineDim94Min=0
+LineDim94Points=3
+LineDim95Range=0
+LineDim95Min=0
+LineDim95Points=3
+LineDim96Range=0
+LineDim96Min=0
+LineDim96Points=3
+LineDim97Range=0
+LineDim97Min=0
+LineDim97Points=3
+LineDim98Range=0
+LineDim98Min=0
+LineDim98Points=3
+LineDim99Range=0.0300356
+LineDim99Min=-0.0300356
+LineDim99Points=3
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-15:0]
+Version=3
+Points=1
+Lines=100
+Frame=SnapInOut10
+CurLine=99
+Dim0Name=NameX13
+Dim0Unit=Unit13
+Dim0Range=0
+Dim0Min=0
+Dim1Range=1
+Dim1Min=0
+Dim2Name=NameZ13
+Dim2Unit=m
+Dim2Range=2.57661e-06
+Dim2Min=-2.57661e-06
+LineDim0Range=0
+LineDim0Min=0
+LineDim0Points=1
+LineDim1Range=2.57661e-06
+LineDim1Min=-2.57661e-06
+LineDim1Points=1
+LineDim2Range=0
+LineDim2Min=0
+LineDim2Points=1
+LineDim3Range=0
+LineDim3Min=0
+LineDim3Points=1
+LineDim4Range=0
+LineDim4Min=0
+LineDim4Points=1
+LineDim5Range=0
+LineDim5Min=0
+LineDim5Points=1
+LineDim6Range=0
+LineDim6Min=0
+LineDim6Points=1
+LineDim7Range=0
+LineDim7Min=0
+LineDim7Points=1
+LineDim8Range=0
+LineDim8Min=0
+LineDim8Points=1
+LineDim9Range=0
+LineDim9Min=0
+LineDim9Points=1
+LineDim10Range=0
+LineDim10Min=0
+LineDim10Points=1
+LineDim11Range=0
+LineDim11Min=0
+LineDim11Points=1
+LineDim12Range=0
+LineDim12Min=0
+LineDim12Points=1
+LineDim13Range=0
+LineDim13Min=0
+LineDim13Points=1
+LineDim14Range=0
+LineDim14Min=0
+LineDim14Points=1
+LineDim15Range=0
+LineDim15Min=0
+LineDim15Points=1
+LineDim16Range=0
+LineDim16Min=0
+LineDim16Points=1
+LineDim17Range=0
+LineDim17Min=0
+LineDim17Points=1
+LineDim18Range=0
+LineDim18Min=0
+LineDim18Points=1
+LineDim19Range=0
+LineDim19Min=0
+LineDim19Points=1
+LineDim20Range=0
+LineDim20Min=0
+LineDim20Points=1
+LineDim21Range=0
+LineDim21Min=0
+LineDim21Points=1
+LineDim22Range=0
+LineDim22Min=0
+LineDim22Points=1
+LineDim23Range=0
+LineDim23Min=0
+LineDim23Points=1
+LineDim24Range=0
+LineDim24Min=0
+LineDim24Points=1
+LineDim25Range=0
+LineDim25Min=0
+LineDim25Points=1
+LineDim26Range=0
+LineDim26Min=0
+LineDim26Points=1
+LineDim27Range=0
+LineDim27Min=0
+LineDim27Points=1
+LineDim28Range=0
+LineDim28Min=0
+LineDim28Points=1
+LineDim29Range=0
+LineDim29Min=0
+LineDim29Points=1
+LineDim30Range=0
+LineDim30Min=0
+LineDim30Points=1
+LineDim31Range=0
+LineDim31Min=0
+LineDim31Points=1
+LineDim32Range=0
+LineDim32Min=0
+LineDim32Points=1
+LineDim33Range=0
+LineDim33Min=0
+LineDim33Points=1
+LineDim34Range=0
+LineDim34Min=0
+LineDim34Points=1
+LineDim35Range=0
+LineDim35Min=0
+LineDim35Points=1
+LineDim36Range=0
+LineDim36Min=0
+LineDim36Points=1
+LineDim37Range=0
+LineDim37Min=0
+LineDim37Points=1
+LineDim38Range=0
+LineDim38Min=0
+LineDim38Points=1
+LineDim39Range=0
+LineDim39Min=0
+LineDim39Points=1
+LineDim40Range=0
+LineDim40Min=0
+LineDim40Points=1
+LineDim41Range=0
+LineDim41Min=0
+LineDim41Points=1
+LineDim42Range=0
+LineDim42Min=0
+LineDim42Points=1
+LineDim43Range=0
+LineDim43Min=0
+LineDim43Points=1
+LineDim44Range=0
+LineDim44Min=0
+LineDim44Points=1
+LineDim45Range=0
+LineDim45Min=0
+LineDim45Points=1
+LineDim46Range=0
+LineDim46Min=0
+LineDim46Points=1
+LineDim47Range=0
+LineDim47Min=0
+LineDim47Points=1
+LineDim48Range=0
+LineDim48Min=0
+LineDim48Points=1
+LineDim49Range=0
+LineDim49Min=0
+LineDim49Points=1
+LineDim50Range=0
+LineDim50Min=0
+LineDim50Points=1
+LineDim51Range=0
+LineDim51Min=0
+LineDim51Points=1
+LineDim52Range=0
+LineDim52Min=0
+LineDim52Points=1
+LineDim53Range=0
+LineDim53Min=0
+LineDim53Points=1
+LineDim54Range=0
+LineDim54Min=0
+LineDim54Points=1
+LineDim55Range=0
+LineDim55Min=0
+LineDim55Points=1
+LineDim56Range=0
+LineDim56Min=0
+LineDim56Points=1
+LineDim57Range=0
+LineDim57Min=0
+LineDim57Points=1
+LineDim58Range=0
+LineDim58Min=0
+LineDim58Points=1
+LineDim59Range=0
+LineDim59Min=0
+LineDim59Points=1
+LineDim60Range=0
+LineDim60Min=0
+LineDim60Points=1
+LineDim61Range=0
+LineDim61Min=0
+LineDim61Points=1
+LineDim62Range=0
+LineDim62Min=0
+LineDim62Points=1
+LineDim63Range=0
+LineDim63Min=0
+LineDim63Points=1
+LineDim64Range=0
+LineDim64Min=0
+LineDim64Points=1
+LineDim65Range=0
+LineDim65Min=0
+LineDim65Points=1
+LineDim66Range=0
+LineDim66Min=0
+LineDim66Points=1
+LineDim67Range=0
+LineDim67Min=0
+LineDim67Points=1
+LineDim68Range=0
+LineDim68Min=0
+LineDim68Points=1
+LineDim69Range=0
+LineDim69Min=0
+LineDim69Points=1
+LineDim70Range=0
+LineDim70Min=0
+LineDim70Points=1
+LineDim71Range=0
+LineDim71Min=0
+LineDim71Points=1
+LineDim72Range=0
+LineDim72Min=0
+LineDim72Points=1
+LineDim73Range=0
+LineDim73Min=0
+LineDim73Points=1
+LineDim74Range=0
+LineDim74Min=0
+LineDim74Points=1
+LineDim75Range=0
+LineDim75Min=0
+LineDim75Points=1
+LineDim76Range=0
+LineDim76Min=0
+LineDim76Points=1
+LineDim77Range=0
+LineDim77Min=0
+LineDim77Points=1
+LineDim78Range=0
+LineDim78Min=0
+LineDim78Points=1
+LineDim79Range=0
+LineDim79Min=0
+LineDim79Points=1
+LineDim80Range=0
+LineDim80Min=0
+LineDim80Points=1
+LineDim81Range=0
+LineDim81Min=0
+LineDim81Points=1
+LineDim82Range=0
+LineDim82Min=0
+LineDim82Points=1
+LineDim83Range=0
+LineDim83Min=0
+LineDim83Points=1
+LineDim84Range=0
+LineDim84Min=0
+LineDim84Points=1
+LineDim85Range=0
+LineDim85Min=0
+LineDim85Points=1
+LineDim86Range=0
+LineDim86Min=0
+LineDim86Points=1
+LineDim87Range=0
+LineDim87Min=0
+LineDim87Points=1
+LineDim88Range=0
+LineDim88Min=0
+LineDim88Points=1
+LineDim89Range=0
+LineDim89Min=0
+LineDim89Points=1
+LineDim90Range=0
+LineDim90Min=0
+LineDim90Points=1
+LineDim91Range=0
+LineDim91Min=0
+LineDim91Points=1
+LineDim92Range=0
+LineDim92Min=0
+LineDim92Points=1
+LineDim93Range=0
+LineDim93Min=0
+LineDim93Points=1
+LineDim94Range=0
+LineDim94Min=0
+LineDim94Points=1
+LineDim95Range=0
+LineDim95Min=0
+LineDim95Points=1
+LineDim96Range=0
+LineDim96Min=0
+LineDim96Points=1
+LineDim97Range=0
+LineDim97Min=0
+LineDim97Points=1
+LineDim98Range=0
+LineDim98Min=0
+LineDim98Points=1
+LineDim99Range=2.37158e-06
+LineDim99Min=-2.37158e-06
+LineDim99Points=1
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-16:1]
+Version=2
+Points=256
+Lines=256
+Frame=Scan forward
+CurLine=156
+Dim0Name=X*
+Dim0Unit=m
+Dim0Range=5e-06
+Dim0Min=0
+Dim1Name=Y*
+Dim1Unit=m
+Dim1Range=5e-06
+Dim1Min=0
+Dim2Name=Z-Axis
+Dim2Unit=m
+Dim2Range=1.47e-05
+Dim2Min=-7.35e-06
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-16:2]
+Version=2
+Points=256
+Lines=256
+Frame=Scan forward
+CurLine=156
+Dim0Name=X*
+Dim0Unit=m
+Dim0Range=5e-06
+Dim0Min=0
+Dim1Name=Y*
+Dim1Unit=m
+Dim1Range=5e-06
+Dim1Min=0
+Dim2Name=Amplitude
+Dim2Unit=V
+Dim2Range=20
+Dim2Min=-10
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-16:3]
+Version=2
+Points=256
+Lines=256
+Frame=Scan forward
+CurLine=156
+Dim0Name=X*
+Dim0Unit=m
+Dim0Range=5e-06
+Dim0Min=0
+Dim1Name=Y*
+Dim1Unit=m
+Dim1Range=5e-06
+Dim1Min=0
+Dim2Name=Phase
+Dim2Unit=°
+Dim2Range=360
+Dim2Min=-180
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-16:7]
+Version=2
+Points=256
+Lines=256
+Frame=Scan forward
+CurLine=156
+Dim0Name=X*
+Dim0Unit=m
+Dim0Range=5e-06
+Dim0Min=0
+Dim1Name=Y*
+Dim1Unit=m
+Dim1Range=5e-06
+Dim1Min=0
+Dim2Name=Z-Axis Sensor
+Dim2Unit=m
+Dim2Range=2.8e-05
+Dim2Min=-1.4e-05
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-17:1]
+Version=2
+Points=256
+Lines=256
+Frame=Scan backward
+CurLine=156
+Dim0Unit=m
+Dim0Range=5e-06
+Dim0Min=0
+Dim1Unit=m
+Dim1Range=5e-06
+Dim1Min=0
+Dim2Name=Z-Axis
+Dim2Unit=m
+Dim2Range=1.47e-05
+Dim2Min=-7.35e-06
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-17:2]
+Version=2
+Points=256
+Lines=256
+Frame=Scan backward
+CurLine=156
+Dim0Unit=m
+Dim0Range=5e-06
+Dim0Min=0
+Dim1Unit=m
+Dim1Range=5e-06
+Dim1Min=0
+Dim2Name=Amplitude
+Dim2Unit=V
+Dim2Range=20
+Dim2Min=-10
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-17:3]
+Version=2
+Points=256
+Lines=256
+Frame=Scan backward
+CurLine=156
+Dim0Unit=m
+Dim0Range=5e-06
+Dim0Min=0
+Dim1Unit=m
+Dim1Range=5e-06
+Dim1Min=0
+Dim2Name=Phase
+Dim2Unit=°
+Dim2Range=360
+Dim2Min=-180
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[DataSet-17:7]
+Version=2
+Points=256
+Lines=256
+Frame=Scan backward
+CurLine=156
+Dim0Unit=m
+Dim0Range=5e-06
+Dim0Min=0
+Dim1Unit=m
+Dim1Range=5e-06
+Dim1Min=0
+Dim2Name=Z-Axis Sensor
+Dim2Unit=m
+Dim2Range=2.8e-05
+Dim2Min=-1.4e-05
+SaveMode=Binary
+SaveBits=32
+SaveSign=Signed
+SaveOrder=Intel
+
+[SetView]
+ViewWidth=766
+ViewHeight=611
+ViewCount=4
+View0=SetView-View0
+View1=SetView-View1
+View2=SetView-View2
+View3=SetView-View3
+
+[SetView-View0]
+ViewTyp=1
+LineOp=2
+Channel=1
+Frame=16
+ViewSize=256
+ViewMode=0
+GraphMode=1
+AspectRatio=0
+AutoZRange=1
+ZRange=-3449120,5316512
+ParCount=9
+Par0=0,ReverseLine,0
+Par1=0,XAxisMin,0
+Par2=0,XAxisMax,1
+Par3=1,Shading,0
+Par4=1,DeltaGain,5
+Par5=1,Smooth,0
+Par6=1,DriverInstanceNo,0
+Par7=1,RangeMin,0
+Par8=1,RangeMax,95
+
+[SetView-View1]
+ViewTyp=1
+LineOp=0
+Channel=2
+Frame=0
+ViewSize=256
+ViewMode=0
+GraphMode=1
+AspectRatio=0
+AutoZRange=1
+ParCount=9
+Par0=0,ReverseLine,0
+Par1=0,XAxisMin,0
+Par2=0,XAxisMax,1
+Par3=1,Shading,0
+Par4=1,DeltaGain,5
+Par5=1,Smooth,0
+Par6=1,DriverInstanceNo,0
+Par7=1,RangeMin,0
+Par8=1,RangeMax,95
+
+[SetView-View2]
+ViewTyp=5
+LineOp=0
+Channel=0
+Frame=0
+ViewSize=384
+ViewMode=0
+GraphMode=1
+AspectRatio=0
+AutoZRange=1
+ZRange=-6286144,294921184
+ParCount=7
+Par0=0,ReverseLine,1
+Par1=0,XAxisMin,0
+Par2=0,XAxisMax,1
+Par3=5,ReverseLine,1
+Par4=5,XAxisMin,-2.05983e-06
+Par5=5,XAxisMax,-1.10192e-06
+Par6=5,XAxisChannel,7
+
+[SetView-View3]
+ViewTyp=5
+LineOp=0
+Channel=7
+Frame=0
+ViewSize=384
+ViewMode=0
+GraphMode=1
+AspectRatio=0
+AutoZRange=1
+ZRange=-326285356,-170909120
+ParCount=12
+Par0=0,XAxisChannel,7
+Par1=0,ReverseLine,1
+Par2=0,XAxisMin,0
+Par3=0,XAxisMax,1
+Par4=5,ReverseLine,1
+Par5=5,XAxisMin,-2.05983e-06
+Par6=5,XAxisMax,-1.10192e-06
+Par7=5,XAxisChannel,7
+Par8=6,ReverseLine,1
+Par9=6,XAxisMin,-2.07229e-06
+Par10=6,XAxisMax,-1.1994e-06
+Par11=6,XAxisChannel,7
+
+[ProcessorSet]
+ProcessorCount=5
+Processor0=ProcessorSet-{138707ED-FD93-4E28-B2B0-C5DA628CB815}:0
+Processor1=ProcessorSet-{AB0B4062-81AF-464B-A7F7-75B11891ACF8}:0
+Processor2=ProcessorSet-{AB0B4062-81AF-464B-A7F7-75B11891ACF8}:1
+Processor3=ProcessorSet-{1220F4A8-053A-494D-B28C-3635059D1530}:0
+Processor4=ProcessorSet-{1220F4A8-053A-494D-B28C-3635059D1530}:1
+
+[ProcessorSet-{138707ED-FD93-4E28-B2B0-C5DA628CB815}:0]
+Name=Indentation
+GUID={138707ED-FD93-4E28-B2B0-C5DA628CB815}
+DisplayName=Indentation
+InstanceNo=0
+Enabled=B[1]*[]
+InputCount=4
+Input0Group=0
+Input0Channel=0
+Input1Group=1
+Input1Channel=0
+Input2Group=0
+Input2Channel=7
+Input3Group=1
+Input3Channel=7
+OutputCount=4
+Output0Group=4
+Output0Channel=0
+Output1Group=5
+Output1Channel=0
+Output2Group=2
+Output2Channel=0
+Output3Group=3
+Output3Channel=0
+
+[ProcessorSet-{AB0B4062-81AF-464B-A7F7-75B11891ACF8}:0]
+Name=Slope
+GUID={AB0B4062-81AF-464B-A7F7-75B11891ACF8}
+DisplayName=Slope Forward
+InstanceNo=0
+Enabled=B[1]*[]
+RangeMin=D[50]*[%]
+RangeMax=D[90]*[%]
+InputCount=2
+Input0Group=4
+Input0Channel=0
+Input1Group=2
+Input1Channel=0
+OutputCount=3
+Output0Group=6
+Output0Channel=0
+Output1Group=7
+Output1Channel=0
+Output2Group=8
+Output2Channel=0
+
+[ProcessorSet-{AB0B4062-81AF-464B-A7F7-75B11891ACF8}:1]
+Name=Slope
+GUID={AB0B4062-81AF-464B-A7F7-75B11891ACF8}
+DisplayName=Slope Backward
+InstanceNo=1
+Enabled=B[1]*[]
+RangeMin=D[50]*[%]
+RangeMax=D[90]*[%]
+InputCount=2
+Input0Group=5
+Input0Channel=0
+Input1Group=3
+Input1Channel=0
+OutputCount=3
+Output0Group=11
+Output0Channel=0
+Output1Group=12
+Output1Channel=0
+Output2Group=13
+Output2Channel=0
+
+[ProcessorSet-{1220F4A8-053A-494D-B28C-3635059D1530}:0]
+Name=MaxAdhesion
+GUID={1220F4A8-053A-494D-B28C-3635059D1530}
+DisplayName=Max Adhesion
+InstanceNo=0
+Enabled=B[1]*[]
+InputCount=2
+Input0Group=5
+Input0Channel=0
+Input1Group=3
+Input1Channel=0
+OutputCount=2
+Output0Group=9
+Output0Channel=0
+Output1Group=10
+Output1Channel=0
+
+[ProcessorSet-{1220F4A8-053A-494D-B28C-3635059D1530}:1]
+Name=MaxAdhesion
+GUID={1220F4A8-053A-494D-B28C-3635059D1530}
+DisplayName=Snap-In
+InstanceNo=1
+Enabled=B[1]*[]
+InputCount=2
+Input0Group=4
+Input0Channel=0
+Input1Group=2
+Input1Channel=0
+OutputCount=2
+Output0Group=14
+Output0Channel=0
+Output1Group=15
+Output1Channel=0
+
+
+#!PSLt.1,L\"C#Tx
+