Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,15 @@ jobs:
# The point is not to publish, it is that a docstring which no longer
# parses -- a bad cross-reference, a broken code block -- fails here
# rather than silently degrading. Output is not committed (#30).
#
# The module list is derived, not written here: `pdoc pyimr` alone documents
# only the package page, because `__init__` sets `__all__`, and the explicit
# list this replaced had drifted to seven modules out of nineteen.
- name: Build API documentation
if: matrix.python-version == '3.12'
run: |
python -m pip install "pdoc>=15"
python -m pdoc -o site pyimr pyimr.sensitivity pyimr.inference pyimr.data pyimr.design pyimr.pymc_op pyimr.assimilation pyimr.optimize
python tools/api_docs.py -o site

- name: Build distributions
if: matrix.python-version == '3.12'
Expand Down
24 changes: 20 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ constitutive selector, no shared bag of parameters.
Materials compose: pick an elastic law and a viscous law and combine them, or
reach for a closed-form memory model when it applies. Neo-Hookean, Mooney-Rivlin,
Yeoh, Fung, Gent, Arruda-Boyce and Ogden on the elastic side; Carreau-Yasuda,
Cross, Powell-Eyring, Herschel-Bulkley and Bingham on the viscous; Zener,
Oldroyd-B, Giesekus and linear PTT for memory. See
Cross, Powell-Eyring, Herschel-Bulkley and Bingham on the viscous; the Zener
family (linear, quadratic, cubic, two-mode and Carreau-thinning), Oldroyd-B and
linear Maxwell in closed form; Giesekus and linear PTT as distributed memory.
`RelaxingMaterial` puts a Maxwell arm on any elastic law. See
**[docs/materials.md](docs/materials.md)**.

## Beyond a forward solve
Expand All @@ -76,6 +78,19 @@ redundancy and Occam penalties.
tolerance meeting an accuracy target on *your* problem, and raises rather than
guessing when the target is out of reach.

**Experiment design.** `pyimr.design` scores a design that has not been run;
`pyimr.measure` optimises over a batch of them and returns a certificate of
global optimality with the answer; `pyimr.gain` scores any question, a material
parameter or a model label, in nats, so one batch can serve several;
`pyimr.discriminate` screens which rivals are still worth an experiment;
`pyimr.pareto` traces the front when the criteria disagree.

**Knowing why a solve failed.** `pyimr.diagnose` separates a step budget, a
material-domain violation and an ill-conditioned trajectory, which raise the
same error. `pyimr.store` caches solves and failures on disk so a re-run of an
unchanged study costs nothing. `pyimr.assimilation` runs 4D-Var and ensemble
state estimation on the exact tangents.

See **[docs/usage.md](docs/usage.md)**.

## Validation
Expand All @@ -102,15 +117,16 @@ visible.

| | |
|---|---|
| [Usage](docs/usage.md) | solving, sensitivities, inference, model selection, resolution |
| [Usage](docs/usage.md) | solving, sensitivities, inference, model selection, experiment design, diagnostics |
| [Materials](docs/materials.md) | every constitutive law, and what each one requires |
| [Accuracy](docs/accuracy.md) | what error each tolerance and discretization actually buys |
| [Discretization](docs/discretization.md) | stress quadrature and the two thermal backends |
| [Validation](docs/validation.md) | what the suite pins, and per-case deviations from IMRv2 |
| [Upstream](docs/upstream.md) | defects found in IMRv2, and what PyIMR does instead |
| [Boundaries](docs/boundaries.md) | where PyIMR stops, and where it diverges deliberately |

API reference: `pip install 'PyIMR[docs]'`, then `python -m pdoc pyimr`.
API reference: `pip install 'PyIMR[docs]'`, then `python tools/api_docs.py`
(every public module; `python -m pdoc pyimr` alone stops at the package page).
Comment on lines +128 to +129

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make the documented API-doc command available after installation

For a user following the preceding pip install 'PyIMR[docs]' instruction outside a source checkout, this command fails because tools/api_docs.py is not installed: setuptools discovery includes only pyimr*, and there is no console-script entry point. The new API-doc workflow is therefore usable by CI and repository contributors but not by the installed-package users addressed here; expose the helper from the package/an entry point or document a command that only uses installed files.

Useful? React with 👍 / 👎.


## Citation

Expand Down
3 changes: 2 additions & 1 deletion docs/accuracy.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ augmented state/tangent system is integrated. On the coupled fd case at

| `rtol` / `atol` | relative error |
|---|---|
| `1e-9` / `1e-11` (default) | 8.43e-05 |
| `1e-9` / `1e-11` (the suite's setting for this check) | 8.43e-05 |
| `1e-12` / `1e-14` | 1.53e-06 |

The package default is `1e-8` / `1e-10`, one order looser than the first row.
So three orders of tolerance buys a factor of about 55, at a large cost in
runtime -- the coupled tangent solve is already the slowest operation in the
package. Tightening further stops helping, because the centered-difference
Expand Down
43 changes: 40 additions & 3 deletions docs/materials.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ and the equivalent composable material agree to solver tolerance.

## Closed-form memory

The finite-dimensional hot paths are `Zener`, `QuadraticZener`, `OldroydB` and
`LinearMaxwell`:
The finite-dimensional hot paths are `Zener`, `QuadraticZener`, `CubicZener`,
`QuadraticKelvinVoigt`, `OldroydB` and `LinearMaxwell`:

```python
material = Zener(
Expand All @@ -97,6 +97,43 @@ material = Zener(
)
```

`QuadraticKelvinVoigt` is `NeoHookeanKelvinVoigt` with a stiffening term
quadratic in `I1 - 3`, which is Yeoh truncated after `c2`; `QuadraticZener`
adds the Maxwell arm to it, and `Zener` is `QuadraticZener` at zero stiffening.
`CubicZener` carries the next term of the same expansion, with `cubic = 0`
reducing to `QuadraticZener` exactly. On measured collapses `I1 - 3` reaches
24 to 119, where the cubic term is not a small correction. `LinearMaxwell` is
Zener without the parallel spring: no modulus, no retardation, an elastic
target of zero.

### Relaxation for any elastic law

Each Zener above carries its own hand-derived stress integral, so relaxation
was available to the neo-Hookean family and to nothing else. Gent, Fung,
Arruda-Boyce, Mooney-Rivlin, Yeoh and Ogden existed only as
`InstantaneousMaterial`, elastic with no memory, which is the wrong comparison
to make against a relaxing model. `RelaxingMaterial` is the Zener construction
freed from one potential: any `ElasticModel` as the equilibrium target, with a
Maxwell arm on top.

```python
from pyimr import RelaxingMaterial, Yeoh

material = RelaxingMaterial(
elastic=Yeoh(c1_pa=1250.0, c2_pa=100.0, c3_pa=10.0),
viscosity_pa_s=0.1,
relaxation_time_s=40e-6,
retardation_time_s=8e-6,
)
```

The equilibrium target is taken by the same quadrature `InstantaneousMaterial`
uses rather than by a closed form derived per law, so one class covers every
elastic model in the package and any added later. That costs the quadrature at
each step (`quadrature_points`, default 32) and buys a controlled comparison.
With `elastic=NeoHookean(...)` it agrees with `Zener`; the closed form is the
one to use where it applies.

### Beyond one relaxation time

A single relaxation time is a strong assumption for a crosslinked biopolymer, and the
Expand Down Expand Up @@ -143,7 +180,7 @@ material = CarreauZener(

Both are comparison candidates, in `EXTENDED_MODELS` rather than `STANDARD_MODELS`: at six
free parameters the grid quadrature in `pyimr.selection` costs `count**6`, so they are
scored by `candidate_log_evidence` instead. See [selection](../README.md#model-selection).
scored by `candidate_log_evidence` instead. See [model selection](usage.md#model-selection).

## Distributed nonlinear memory

Expand Down
23 changes: 10 additions & 13 deletions docs/upstream.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Reference implementation

Defects found in IMRv2 at `dea31cd`, all reproduced with MATLAB R2025a via
`tools/gen_imrv2_cases.m` and `tools/probe_viscosity.m`. The full list is below;
`tools/gen_imrv2_cases.m` and `tools/probe_viscosity.m`. The eight are listed below;
the original scoping notes are in git history, in a `PLAN.md` retired in #218.

- **Giesekus and linear PTT cannot be run.** `f_call_params.m` dispatches
Expand Down Expand Up @@ -34,7 +34,8 @@ the original scoping notes are in git history, in a `PLAN.md` retired in #218.
as `A -> 0`; `f_radial_eq.m` takes `(-b + sqrt(d))/(2a)`, which is the
`-1/nog` branch -- a 32.5% density deficit at ambient pressure, a 48-60%
enthalpy error, and a negative `c^2`. That negative `c^2` is why `radial = 6`
returns complex radii: it is the only branch that evaluates the sound speed
(Gilmore/Mie-Gruneisen) returns complex radii, reaching `max|imag(R/R0)| =
4.069` without raising: it is the only branch that evaluates the sound speed
from the EoS. The branch also omits the stress term from `Pb`, which
`radial = 3` and `4` both include.

Expand All @@ -48,22 +49,16 @@ the original scoping notes are in git history, in a `PLAN.md` retired in #218.
- **`f_init_stress.m` uses an undefined `z1`** in the `De == 0 || De == Inf`
branch. Unreachable for the memory models that call it, so latent rather
than active.
- **`calc_omega_N` treats the gas pressure at `Rmax` as the equilibrium
value.** This one is in IMR-vanilla rather than IMRv2. It inflates the
linearised stiffness by `alpha**(-3*kappa)` and overpredicts the natural
frequency by 42x on the reference case, which is why PyIMR's
`data.natural_frequency` is a reimplementation rather than a port.

These are the reason several PyIMR models are validated by reduction limit
rather than against a pinned upstream trajectory: for those models, no working
upstream implementation exists to pin against.

[Back to the README](../README.md)

- **`calc_omega_N` (IMR-vanilla) treats the gas pressure at `Rmax` as the
equilibrium value.** That inflates the linearised stiffness by
`alpha**(-3*kappa)` and overpredicts the natural frequency by 42x on the
reference case, which is why PyIMR's `data.natural_frequency` is a
reimplementation rather than a port.
- **`radial = 6` (Gilmore/Mie-Gruneisen) returns complex radii.** Upstream
reaches `max|imag(R/R0)| = 4.069` without raising, from a wrong root of the
Mie-Gruneisen density quadratic.

## Which branches replicate upstream, and which correct it

Moved here from the package docstring, where four of its claims had gone stale
Expand Down Expand Up @@ -114,3 +109,5 @@ is internally inconsistent, and the reduction limit to `LinearMaxwell` converges
only with the `LAM` factor restored. Three Zener reference trajectories were
regenerated from PyIMR as a result, and pin regressions rather than
cross-checking upstream (#174, IMRv2#18).

[Back to the README](../README.md)
Loading
Loading