Metadata-Version: 2.4
Name: stresspy
Version: 0.0.4
Summary: Core numerical routines for the Geometric Stress Criterion
Author: Dan James
License-Expression: PolyForm-Noncommercial-1.0.0
Project-URL: Homepage, https://pypi.org/project/stresspy/
Project-URL: Documentation, https://pypi.org/project/stresspy/
Keywords: geometric stress criterion,model diagnostics,tangent space,structural inadequacy,scientific computing
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.24
Provides-Extra: test
Requires-Dist: pytest>=8; extra == "test"
Dynamic: license-file

# StressPy

**Core numerical routines for the Geometric Stress Criterion (GSC).**

`stresspy` decomposes a model--data discrepancy into a component aligned with
the model's local parameter-accessible tangent space and a component normal to
that space. It also reports numerical rank, singular values, the normal
fraction, conditioning and a minimum-norm local repair vector.

StressPy is an early reference implementation. It performs the deterministic
geometric calculation; it does not yet fit models, construct ODE Jacobians or
perform bootstrap calibration.

## Installation

```bash
pip install stresspy
```

## Quick start

```python
import numpy as np
from stresspy import analyze

# Use the convention residual = observed - predicted.
residual = np.array([0.1, -0.1, -0.1])
jacobian = np.array([
    [1.0, 0.0],
    [1.0, 1.0],
    [0.0, 1.0],
])
sigma = np.array([0.1, 0.1, 0.1])

result = analyze(
    residual=residual,
    jacobian=jacobian,
    sigma=sigma,
)

print("Tangent stress:", result.tangent_stress)
print("Normal stress:", result.normal_stress)
print("Normal fraction (%):", result.normal_fraction_pct)
print("Numerical rank:", result.rank)
print("Repair vector:", result.repair_vector)
```

`analyze` is the recommended entry point when the residual and Jacobian have
already been calculated. Users who have separate observed and predicted arrays
can either form `residual = observed - predicted` or call the lower-level
`decompose` function directly.

## Weighting

An unweighted Euclidean analysis requires no additional argument:

```python
result = analyze(residual, jacobian)
```

Independent observational standard deviations can be supplied with `sigma`:

```python
result = analyze(residual, jacobian, sigma=sigma)
```

An optional absolute or quantile-based lower floor can prevent extremely small
standard deviations from dominating the observation metric:

```python
absolute_floor = analyze(
    residual,
    jacobian,
    sigma=sigma,
    sigma_floor=0.05,
)

quantile_floor = analyze(
    residual,
    jacobian,
    sigma=sigma,
    sigma_floor_quantile=0.10,
)
```

Positive diagonal precision weights may be supplied directly. They define the
metric `sum(weights * residual**2)` and are equivalent to
`sigma = 1 / sqrt(weights)`:

```python
weights = 1.0 / sigma**2
result = analyze(residual, jacobian, weights=weights)
```

For correlated observations, supply a positive-definite covariance matrix:

```python
result = analyze(residual, jacobian, covariance=covariance)
```

Supply only one of `sigma`, `weights`, `covariance` or `whitener`. Weighting is
part of the geometry: different defensible metrics can produce different
tangent--normal decompositions and should be reported explicitly.

The discrepancy convention is

\[
r = y - f(\hat{\theta}).
\]

With observation-space whitening matrix \(L\), StressPy forms
\(r_W=Lr\) and \(J_W=LJ\). If \(U_r\) contains the retained left singular
vectors of \(J_W\), then

\[
r_{\parallel,W}=U_rU_r^\top r_W,
\qquad
r_{\perp,W}=r_W-r_{\parallel,W}.
\]

The squared norms give total, tangent and normal stress. The minimum-norm local
repair is calculated in the parameter coordinates represented by the supplied
Jacobian. Consequently, repair magnitude is coordinate-dependent, and local
tangent accessibility does not guarantee a practical finite nonlinear repair.

## Principal functions

- `analyze`: recommended high-level analysis from a residual and Jacobian,
  including common weighting and uncertainty-floor options.
- `decompose`: single tangent--normal decomposition with optional uncertainty
  or covariance weighting.
- `decompose_blocks`: joint interrogation of multiple independent observation
  blocks sharing the same parameter coordinates.
- `floor_sigma`: explicit uncertainty-floor preprocessing.
- `jacobian_to_log_coordinates`: conversion of selected Jacobian columns to
  log-parameter coordinates.

Both `analyze` and `decompose` return an immutable `GSCResult` containing the
component vectors, stress measures, normal fraction, numerical-rank diagnostics
and local repair.

## Interpretation

Normal stress measures discrepancy outside the retained local Jacobian column
space in the selected observation metric. It is a local geometric diagnostic,
not by itself a calibrated hypothesis test. Conclusions can depend on the
chosen weighting, parameter point and singular-value threshold.

## Licence and commercial use

StressPy is available under the
[PolyForm Noncommercial License 1.0.0](https://polyformproject.org/licenses/noncommercial/1.0.0).
It may be used, studied, modified and redistributed for permitted
non-commercial purposes under those terms. Commercial use requires separate
written permission from the copyright holder.

## Citation

If StressPy contributes to academic work, please cite the software and the
associated GSC publication when available. Citation metadata is provided in
`CITATION.cff`.

Suggested software citation:

> James, D. (2026). *StressPy: Core numerical routines for the Geometric Stress
> Criterion* (Version 0.0.4) [Computer software].
> https://pypi.org/project/stresspy/
