mixle-pde 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. mixle_pde-0.7.0/LICENSE +21 -0
  2. mixle_pde-0.7.0/PKG-INFO +347 -0
  3. mixle_pde-0.7.0/README.md +321 -0
  4. mixle_pde-0.7.0/mixle_pde/__init__.py +567 -0
  5. mixle_pde-0.7.0/mixle_pde/_operator.py +119 -0
  6. mixle_pde-0.7.0/mixle_pde/attenuation.py +456 -0
  7. mixle_pde-0.7.0/mixle_pde/basin.py +175 -0
  8. mixle_pde-0.7.0/mixle_pde/beam.py +160 -0
  9. mixle_pde-0.7.0/mixle_pde/boundaries.py +274 -0
  10. mixle_pde-0.7.0/mixle_pde/capabilities.py +2449 -0
  11. mixle_pde-0.7.0/mixle_pde/dynamics.py +683 -0
  12. mixle_pde-0.7.0/mixle_pde/earth_scenarios.py +221 -0
  13. mixle_pde-0.7.0/mixle_pde/elastic.py +336 -0
  14. mixle_pde-0.7.0/mixle_pde/elastic_aniso.py +450 -0
  15. mixle_pde-0.7.0/mixle_pde/electrostatics.py +202 -0
  16. mixle_pde-0.7.0/mixle_pde/em_diffusion.py +206 -0
  17. mixle_pde-0.7.0/mixle_pde/em_diffusion_3d.py +500 -0
  18. mixle_pde-0.7.0/mixle_pde/env_data.py +349 -0
  19. mixle_pde-0.7.0/mixle_pde/fem.py +329 -0
  20. mixle_pde-0.7.0/mixle_pde/field_assimilation.py +768 -0
  21. mixle_pde-0.7.0/mixle_pde/field_gauss_newton.py +183 -0
  22. mixle_pde-0.7.0/mixle_pde/field_inversion.py +308 -0
  23. mixle_pde-0.7.0/mixle_pde/field_mcmc.py +192 -0
  24. mixle_pde-0.7.0/mixle_pde/field_priors.py +225 -0
  25. mixle_pde-0.7.0/mixle_pde/flow.py +106 -0
  26. mixle_pde-0.7.0/mixle_pde/flow3d.py +183 -0
  27. mixle_pde-0.7.0/mixle_pde/gas_dynamics.py +465 -0
  28. mixle_pde-0.7.0/mixle_pde/geo_observations.py +622 -0
  29. mixle_pde-0.7.0/mixle_pde/geophysics.py +744 -0
  30. mixle_pde-0.7.0/mixle_pde/guided_wave.py +369 -0
  31. mixle_pde-0.7.0/mixle_pde/heat.py +168 -0
  32. mixle_pde-0.7.0/mixle_pde/helmholtz_pml.py +220 -0
  33. mixle_pde-0.7.0/mixle_pde/induced_polarization.py +179 -0
  34. mixle_pde-0.7.0/mixle_pde/inverse.py +238 -0
  35. mixle_pde-0.7.0/mixle_pde/latent.py +585 -0
  36. mixle_pde-0.7.0/mixle_pde/maxwell.py +191 -0
  37. mixle_pde-0.7.0/mixle_pde/mesh.py +787 -0
  38. mixle_pde-0.7.0/mixle_pde/migration.py +252 -0
  39. mixle_pde-0.7.0/mixle_pde/misfit.py +225 -0
  40. mixle_pde-0.7.0/mixle_pde/multiphysics.py +190 -0
  41. mixle_pde-0.7.0/mixle_pde/nonlinear.py +191 -0
  42. mixle_pde-0.7.0/mixle_pde/normal_modes.py +399 -0
  43. mixle_pde-0.7.0/mixle_pde/observations.py +906 -0
  44. mixle_pde-0.7.0/mixle_pde/ops.py +180 -0
  45. mixle_pde-0.7.0/mixle_pde/parabolic_equation.py +292 -0
  46. mixle_pde-0.7.0/mixle_pde/pde.py +351 -0
  47. mixle_pde-0.7.0/mixle_pde/pde_solve.py +231 -0
  48. mixle_pde-0.7.0/mixle_pde/plate.py +172 -0
  49. mixle_pde-0.7.0/mixle_pde/pnp.py +155 -0
  50. mixle_pde-0.7.0/mixle_pde/poroelastic.py +281 -0
  51. mixle_pde-0.7.0/mixle_pde/posterior_calibration.py +238 -0
  52. mixle_pde-0.7.0/mixle_pde/posterior_query.py +372 -0
  53. mixle_pde-0.7.0/mixle_pde/potential_fields.py +239 -0
  54. mixle_pde-0.7.0/mixle_pde/propagation_inverse.py +190 -0
  55. mixle_pde-0.7.0/mixle_pde/ray_scattering.py +299 -0
  56. mixle_pde-0.7.0/mixle_pde/reasoning.py +262 -0
  57. mixle_pde-0.7.0/mixle_pde/refractivity.py +142 -0
  58. mixle_pde-0.7.0/mixle_pde/rock_physics.py +137 -0
  59. mixle_pde-0.7.0/mixle_pde/sample_update.py +265 -0
  60. mixle_pde-0.7.0/mixle_pde/schrodinger.py +80 -0
  61. mixle_pde-0.7.0/mixle_pde/shape.py +65 -0
  62. mixle_pde-0.7.0/mixle_pde/smoluchowski.py +247 -0
  63. mixle_pde-0.7.0/mixle_pde/sound_speed.py +161 -0
  64. mixle_pde-0.7.0/mixle_pde/spectral_flow.py +215 -0
  65. mixle_pde-0.7.0/mixle_pde/two_phase.py +408 -0
  66. mixle_pde-0.7.0/mixle_pde/viscoelastic.py +235 -0
  67. mixle_pde-0.7.0/mixle_pde/wave.py +78 -0
  68. mixle_pde-0.7.0/mixle_pde/wave3d.py +90 -0
  69. mixle_pde-0.7.0/mixle_pde/wave_pml.py +92 -0
  70. mixle_pde-0.7.0/mixle_pde/wavenumber_integration.py +296 -0
  71. mixle_pde-0.7.0/mixle_pde.egg-info/PKG-INFO +347 -0
  72. mixle_pde-0.7.0/mixle_pde.egg-info/SOURCES.txt +75 -0
  73. mixle_pde-0.7.0/mixle_pde.egg-info/dependency_links.txt +1 -0
  74. mixle_pde-0.7.0/mixle_pde.egg-info/requires.txt +15 -0
  75. mixle_pde-0.7.0/mixle_pde.egg-info/top_level.txt +1 -0
  76. mixle_pde-0.7.0/pyproject.toml +50 -0
  77. mixle_pde-0.7.0/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Grant Boquet
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,347 @@
1
+ Metadata-Version: 2.4
2
+ Name: mixle-pde
3
+ Version: 0.7.0
4
+ Summary: PDE/ODE-constrained Bayesian inverse problems for mixle (a mixle.ppl plugin).
5
+ Author-email: Grant Boquet <grant.boquet@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/gmboquet/mixle-pde
8
+ Project-URL: Repository, https://github.com/gmboquet/mixle-pde
9
+ Keywords: pde,inverse problems,bayesian,mixle
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: mixle>=0.7.0
14
+ Requires-Dist: numpy
15
+ Requires-Dist: scipy
16
+ Requires-Dist: torch>=2.0
17
+ Provides-Extra: test
18
+ Requires-Dist: pytest>=8; extra == "test"
19
+ Requires-Dist: pytest-xdist; extra == "test"
20
+ Provides-Extra: lint
21
+ Requires-Dist: ruff==0.15.17; extra == "lint"
22
+ Provides-Extra: docs
23
+ Requires-Dist: sphinx>=7; extra == "docs"
24
+ Requires-Dist: furo; extra == "docs"
25
+ Dynamic: license-file
26
+
27
+ # mixle-pde
28
+
29
+ ![license](https://img.shields.io/badge/license-MIT-green)
30
+ ![python](https://img.shields.io/badge/python-3.10%2B-blue)
31
+
32
+ PDE and ODE-constrained Bayesian inverse problems for [mixle](https://github.com/gmboquet/mixle).
33
+
34
+ mixle-pde is a `mixle.ppl` plugin. Importing it wires a stack of differentiable forward solvers and
35
+ PDE-constrained state-space models into mixle's probabilistic-programming surface through mixle's
36
+ extension hooks. It never patches mixle: the plugin depends on mixle, not the reverse.
37
+
38
+ The organizing idea is that many quantities are observable only through a dynamical system they drive.
39
+ A rate constant shows up in a decay curve, a contaminant source in downstream concentrations, a
40
+ subsurface velocity in a seismic record, a diffusivity in a steady temperature field. mixle-pde gives
41
+ you the forward physics as differentiable solvers and the inverse machinery to recover a posterior over
42
+ the hidden drivers from noisy, partial, indirect observations.
43
+
44
+ ## Install
45
+
46
+ mixle-pde depends directly on `mixle`, `numpy`, `scipy`, and `torch` (the `ops` namespace every solver
47
+ runs through is a torch backend), so a plain install brings everything the solvers need.
48
+
49
+ ```bash
50
+ pip install -e . # from a checkout
51
+ pip install -e ".[test]" # with the test extras
52
+ pip install -e ".[docs]" # with the docs extras
53
+ ```
54
+
55
+ ## Documentation
56
+
57
+ The Sphinx manual starts at [`docs/index.rst`](docs/index.rst). It includes installation notes, the
58
+ package map, solver/API navigation, validation guidance, generated API reference pages, and the
59
+ release-facing 3D/4D field modeling guide.
60
+
61
+ ```bash
62
+ make -C docs html SPHINXOPTS="-W --keep-going"
63
+ ```
64
+
65
+ Release notes and the current changelog are in
66
+ [`docs/release-notes.rst`](docs/release-notes.rst) and
67
+ [CHANGELOG.md](CHANGELOG.md).
68
+
69
+ ## Quickstart
70
+
71
+ ### Recover a hidden driver from a dynamical system
72
+
73
+ `Differential` is an observation whose forward model is the solution of an ODE or PDE. The latent
74
+ drivers are `free` handles (or a shared field a `GP` carries); a single callback supplies the physics
75
+ through a backend-agnostic `ops` namespace, so it never imports a tensor library.
76
+
77
+ ```python
78
+ import numpy as np
79
+ from mixle.ppl import free, joint
80
+ from mixle_pde import Differential
81
+
82
+ t = np.linspace(0.0, 5.0, 40)
83
+ y_obs = np.exp(-0.7 * t) + 0.02 * np.random.default_rng(0).standard_normal(t.size)
84
+
85
+ k = free(1, name="k", support="positive")
86
+ obs = Differential(y_obs, drivers=[k], y0=1.0, t_grid=t, scale=0.05,
87
+ rhs=lambda u, t, p, ops: -p.k * u) # dy/dt = -k y
88
+ post = joint([obs]).fit(how="laplace")
89
+ k_mean, k_sd = post.posterior("k") # ~0.7 with a calibrated sd
90
+ ```
91
+
92
+ Swap the `rhs`/`forward` callback for a spatial PDE and hand it a `GP` field to recover a whole source
93
+ map instead of a scalar. Fit with `how="laplace"` (or `"map"` for a point estimate, `"gauss_newton"`,
94
+ `"vi"`); `posterior` returns a mean and sd once a curvature-bearing fit like Laplace has run.
95
+
96
+ ### Run a forward solver directly
97
+
98
+ Every solver steps through the same `ops` namespace, so the forward run is differentiable end to end.
99
+
100
+ ```python
101
+ import numpy as np
102
+ from mixle_pde import WaveEquation3D
103
+ from mixle_pde.ops import make_ops
104
+
105
+ ops = make_ops()
106
+ n = 48
107
+ solver = WaveEquation3D(n, dt=0.4 / (n - 1), absorb_width=6) # CFL-safe step, sponge layer
108
+
109
+ c2 = ops.tensor(np.ones(n**3)) # squared wave speed per cell
110
+ u0 = np.zeros((n, n, n)); u0[n // 2, n // 2, n // 2] = 1.0 # a point disturbance
111
+ state = solver.pack(u0.ravel(), np.zeros(n**3))
112
+ for _ in range(100):
113
+ state = solver.step(state, c2, ops)
114
+ field = solver.displacement(state).reshape(n, n, n)
115
+ ```
116
+
117
+ ### Fit a PDE-constrained state space
118
+
119
+ `PDE(operator)` is a latent-field model whose linear state transition is fixed by the physics. Fit it on
120
+ a `(T, m)` array of noisy field snapshots: a Kalman/RTS smoother recovers the latent field while EM
121
+ estimates the process and observation noise.
122
+
123
+ ```python
124
+ from mixle_pde import PDE, DiffusionOperator
125
+
126
+ model = PDE(DiffusionOperator(0.1, n)).fit(field_snapshots, dt=0.1)
127
+ ```
128
+
129
+ ## Solver catalog
130
+
131
+ Release-facing documentation for the 3D/4D field posterior stack lives in
132
+ [`docs/field-modeling.rst`](docs/field-modeling.rst). It covers
133
+ latent fields, observations, priors, inversion, assimilation, geoscience
134
+ likelihoods, posterior queries, posterior calibration diagnostics, meshes, and readiness checks.
135
+
136
+ ### Forward solvers
137
+
138
+ All are differentiable through the `ops` backend and each ships with a test that checks it against an
139
+ exact analytical solution (a normal-mode frequency, a decaying eigenmode, a Poiseuille profile).
140
+
141
+ | Solver | Equation | Method |
142
+ | --- | --- | --- |
143
+ | `WaveEquation2D`, `WaveEquation3D` | acoustic wave | leapfrog, sponge absorbing layer |
144
+ | `NavierStokes2D`, `NavierStokes3D` | incompressible Navier-Stokes | streamfunction-vorticity (2D), Chorin projection (3D) |
145
+ | `TwoPhaseFlow2D` | immiscible two-fluid flow (core-annular / lubricated pipelining) | diffuse-interface phase field, variable-property Chorin projection |
146
+ | `Maxwell3D` | source-free Maxwell curl equations | FDTD on a Yee staggered grid |
147
+ | `ElasticWave3D` | isotropic elastodynamics (P and S waves) | velocity-stress staggered grid (Virieux) |
148
+ | `AnisotropicElasticWave3D` | anisotropic (VTI/TTI) elastodynamics | Thomsen / Bond-rotated stiffness, velocity-stress staggered grid |
149
+ | `ViscoacousticWave1D` | constant-Q attenuating wave | GSLS memory variables (tau-method) |
150
+ | `BiotPoroelastic1D` | Biot poroelasticity (fast + slow P) | velocity-stress-pressure staggered grid |
151
+ | `TransientHeat` | transient heterogeneous heat conduction | divergence-form + checkpointed time stepping |
152
+ | `SAFEPlate`, `safe_dispersion` | guided-wave (Lamb / SH) dispersion | semi-analytical finite elements |
153
+ | `EulerBernoulliBeam` | slender-beam bending and vibration | 1D fourth-order (biharmonic) |
154
+ | `KirchhoffPlate` | thin-plate bending, static and dynamic | 2D biharmonic |
155
+
156
+ More equations live in their own modules: `gas_dynamics` (1D compressible Euler with an exact Riemann
157
+ solver), `schrodinger` (time-dependent Schrodinger, split-step Fourier), `spectral_flow` (pseudo-spectral
158
+ 2D/3D Navier-Stokes with an optional Smagorinsky LES closure), `wave_pml` (2D acoustic wave with a
159
+ perfectly-matched-layer boundary), and `fem` (P1 triangular finite elements for Poisson).
160
+
161
+ The reusable mesh layer is `SimplexMesh`: `box_simplex_mesh` creates deterministic simplex meshes in any
162
+ dimension, `delaunay_mesh` wraps SciPy Delaunay for scattered point clouds, and `space_time_mesh` extrudes a
163
+ 3D tetrahedral mesh through time into a 4D simplex mesh. This is the geometry foundation for moving-domain
164
+ and transient finite-element work; adaptive remeshing, ALE/FSI coupling, and curved/high-order elements are
165
+ still future solver work.
166
+
167
+ The 3D/4D Earth posterior surface is modular, built from these pieces:
168
+
169
+ - **Fields.** `Field3D` / `PosteriorField3D` represent one gridded physical property with units,
170
+ optional bounds, posterior mean/MAP, covariance, credible intervals, and sampling.
171
+ - **Observations and forward operators.** `Observation`, `ForwardOperator`, and
172
+ `ForwardOperatorRegistry` give measurements a common geometry/noise/provenance contract; the
173
+ built-in registry operators cover gravity, magnetics, and direct borehole/sensor samples.
174
+ `dc_resistivity_forward_operator` wraps the DC/ERT forward as a nonlinear log-conductivity
175
+ observation with local finite-difference sensitivities. `layered_mt_forward_operator`,
176
+ `aem_layered_forward_operator`, `mt_2d_te_forward_operator`, `mt_3d_forward_operator`, and
177
+ `csem_3d_forward_operator` do the same for 1D layered MT/AEM, 2D TE-mode, 3D curl-curl
178
+ magnetotelluric, and 3D controlled-source EM soundings, all mapping log-conductivity to
179
+ real-valued geophysical observations.
180
+ - **Linear-Gaussian and Gauss-Newton inversion.** `FieldGaussianPrior` supplies graph-Matern
181
+ smoothness (dense or sparse CSR precision matrices); `linear_gaussian_invert` performs exact
182
+ linear-Gaussian inversion; `gauss_newton_invert` adds bounded MAP/Laplace inversion for
183
+ properties such as porosity, susceptibility, or concentration. `sparse_linear_gaussian_invert`
184
+ stores the posterior in sparse precision-factor form, using sparse covariance solves for
185
+ marginals and linear derived quantities without ever retaining a dense covariance matrix.
186
+ - **4D time-lapse assimilation.** `PosteriorField4D`, `assimilate_4d`, and
187
+ `assimilate_4d_ensemble` add a time axis through exact Kalman/RTS smoothing (linear
188
+ observations) or ensemble Kalman filtering (nonlinear observations), exposing each time slice
189
+ as an ordinary `PosteriorField3D`.
190
+ - **Depth-aware and cross-property priors.** `depth_weights`, `depth_weighted_marginal_precision`,
191
+ `depth_weighted_marginal_precision_sparse`, `CrossPropertyPrior`, and
192
+ `joint_linear_gaussian_invert` let observations of one property regularize another.
193
+ - **Geochemistry and geochronology likelihoods.** `GeochemAssay`, `MultiElementAssay`,
194
+ `assay_log_likelihood`, `multi_element_assay_log_likelihood`, and `additive_log_ratio` add
195
+ detection-limit/compositional and multi-element covariance/batch-effect likelihoods.
196
+ `BiostratConstraint` / `biostrat_log_likelihood` add fossil range-zone constraints;
197
+ `GeochronologyAge`, `StratigraphicCorrelation`, and `FaciesIntervalConstraint` add isotopic age
198
+ measurements, relative-age/horizon constraints, and facies/environment interval evidence. These
199
+ likelihoods can be converted into field observations where a project defines the property mapping.
200
+ - **Posterior extraction and calibration.** `posterior_query` extracts point/section/region
201
+ marginals, linear derived quantities such as total anomalous mass, low-rank or diagonal Gaussian
202
+ summaries, and ensemble samples. `posterior_calibration` measures synthetic-truth coverage,
203
+ held-out observation fit, uncertainty inflation away from data, and insufficient-observation flags.
204
+
205
+ Not yet implemented: full reaction-path geochemistry, paleoecological/basin-process simulators, full
206
+ truncated multivariate censoring for multi-element assays, production-scale adjoint sensitivities,
207
+ iterative sparse posterior solvers, and full airborne loop/flight-line AEM geometry. The ensemble 4D
208
+ path is a stochastic Gaussian-summary reference, not a production particle/MCMC smoother.
209
+
210
+ ### Inverse and inference layer
211
+
212
+ | Surface | What it does |
213
+ | --- | --- |
214
+ | `Differential` | an observation whose forward model is an ODE/PDE solution; recover latent drivers (rate constants, source fields, initial states, coefficients) with `joint([...]).fit(how=...)` |
215
+ | `PDE(operator).fit` | PDE-constrained latent-field state space (Kalman/RTS smoother + EM) over `DiffusionOperator`, `AdvectionOperator`, `AdvectionDiffusionOperator`, or any operator you `register_dynamics_operator` |
216
+ | `pde_solve` | adjoint-capable sparse PDE solves (differentiable Poisson / divergence-form) for large-scale inverse problems |
217
+ | `nonlinear_solve` | differentiable nonlinear steady solves `F(u;θ)=0` (Newton forward, implicit-function-theorem adjoint); the base for nonlinear elliptic inverse problems |
218
+ | `rtm_image`, `born_modeling`, `lsrtm_step` | reverse-time migration and least-squares / Born imaging over the wave steppers |
219
+ | `misfit` (`envelope` / `xcorr` / `wasserstein`) | cycle-skip-robust FWI misfit functionals for any wave forward |
220
+ | `helmholtz_pml_operator` | frequency-domain Helmholtz with a PML boundary and a complex modulus (viscoacoustic attenuation) |
221
+ | `shape_optimize`, `level_set_material` | level-set shape optimization and inverse shape inference |
222
+ | `CoupledPDESystem`, `solve_poisson` | nD steady diffusion/Poisson and node-coupled multiphysics |
223
+
224
+ ### Geophysics
225
+
226
+ Near-surface forward operators plus an Occam-style regularized inversion engine that inverts any
227
+ differentiable forward without per-problem prior tuning: `gravity_point_sensitivity`,
228
+ `magnetic_dipole_sensitivity`, `dc_resistivity` (ERT), `straight_ray_operator` (traveltime tomography),
229
+ `depth_weighting`, `roughness_operator`, `regularized_gauss_newton`, and `cross_gradient` /
230
+ `joint_inversion` for structural coupling of several property models.
231
+
232
+ Electromagnetics in the diffusive (induction) regime, distinct from the wave-regime `Maxwell3D`:
233
+ `layered_mt_impedance` (1D magnetotelluric / airborne EM), `mt_2d_te` (2D magnetotelluric), and `mt_3d` /
234
+ `csem_3d` (a 3D edge-element / Yee curl-curl solver for CSEM, magnetotellurics, borehole induction, and
235
+ eddy-current NDE), plus `cole_cole_conductivity` / `sip_forward` for spectral induced polarization
236
+ (disseminated-sulphide detection). Potential fields extend to `gravity_gradient_tensor` (full-tensor
237
+ gradiometry) and `magnetic_vector_sensitivity` / `magnetic_gradient_tensor`.
238
+
239
+ ### Petroleum systems
240
+
241
+ `geotherm` (steady conductive geotherm for a layered column) and `easy_ro` / `easy_ro_profile` (the
242
+ EASY%Ro vitrinite-reflectance maturation model of Sweeney & Burnham 1990), differentiable forwards for
243
+ heat-flow and thermal-history inversion. `gassmann_ksat` / `fluid_substitute` give closed-form
244
+ differentiable Gassmann fluid substitution, turning elastic-FWI velocities into reservoir variables
245
+ (porosity, saturation).
246
+
247
+ ### Biomolecular electrostatics and reaction-diffusion
248
+
249
+ `linearized_pbe` and `nonlinear_pbe` solve the Poisson-Boltzmann equation (linear Debye-Huckel and the
250
+ full sinh form) for biomolecular electrostatics, with `reaction_field_energy` for MM-PBSA-style binding
251
+ free energy. `pnp_equilibrium` is the equilibrium Poisson-Nernst-Planck ion-channel model, and
252
+ `smoluchowski_rate_radial` / `smoluchowski_rate_box` give diffusion-limited association on-rates. All
253
+ build on the differentiable `nonlinear_solve` keystone.
254
+
255
+ ### Sonar and radar propagation
256
+
257
+ Long-range sonar and radar are the same problem: propagation through a range-varying medium. The shared
258
+ keystone is `ParabolicEquation2D`, a range-marched split-step Fourier one-way propagator that serves both
259
+ by swapping only the environmental potential and the boundary. Underwater it marches on the acoustic index
260
+ from `mackenzie` / `unesco` sound speed c(T,S,depth); in the troposphere it marches on the modified
261
+ refractivity from `refractivity` / `modified_refractivity` (ITU-R P.453) so radar ducting falls out. It is
262
+ differentiable, so it drops into `Differential`.
263
+
264
+ Around it: `boundaries` (seabed Rayleigh reflection with the critical grazing angle, rough-surface coherent
265
+ loss, and the radar Fresnel surface impedance); `attenuation` (Thorp / Francois-Garrison for seawater,
266
+ ITU-R P.676 gaseous and P.838 rain for the atmosphere) feeding the solvers' complex-modulus `Q` slot;
267
+ `NormalModes1D`, a KRAKEN-style differentiable depth-mode solver verified against the Pekeris waveguide and
268
+ cross-checked against the PE; and `WavenumberIntegration1D`, the OASES-style full-wave reference. For small
269
+ scenes and targets, `po_rcs` / `knife_edge_diffraction` / `two_ray_pattern` / `multipath_power` give
270
+ asymptotic radar cross sections and urban multipath.
271
+
272
+ The inverse problems come for free on the `Differential` stack: `refractivity_from_clutter` recovers an
273
+ atmospheric duct from radar clutter, and `ocean_sound_speed_inversion` recovers a sound-speed anomaly from a
274
+ received acoustic field. `env_data` assembles real profiles and bathymetry/terrain into the range-depth
275
+ coefficient fields (differentiable interpolation + seabed masking), with import-guarded loaders for GEBCO,
276
+ World Ocean Atlas / Argo, DEM, and ERA5 data behind an optional extra.
277
+
278
+ ### Cross-modal reasoning
279
+
280
+ `JointPotentialField`, `SpatialFieldStore`, and `MechanisticFieldReasoner` fuse geophysical modalities
281
+ into a spatial belief with uncertainty, feeding mixle's `reason` surface.
282
+
283
+ ### Modeling readiness
284
+
285
+ Applications should not infer solver availability from imports or README claims. The package exposes a
286
+ small capability catalog and deterministic readiness checks:
287
+
288
+ ```python
289
+ from mixle_pde import readiness_report, assert_required_modeling
290
+
291
+ print(readiness_report())
292
+ assert_required_modeling()
293
+ ```
294
+
295
+ The required modeling gate currently checks:
296
+
297
+ - 3D tetrahedral meshes, direct 4D simplex meshes, and 3D-to-4D space-time extrusion.
298
+ - Censored geochemical assay likelihoods, compositional transforms, biostratigraphic range-zone likelihoods,
299
+ geochronology age likelihoods, stratigraphic correlation constraints, and facies/environment intervals with
300
+ provenance and units.
301
+ - The common observation/forward-operator contract for gravity, magnetics, and borehole/sensor samples.
302
+ - Exact 3D linear-Gaussian field inversion and bounded-field Gauss-Newton MAP/Laplace inversion.
303
+ - Nonlinear DC/ERT log-conductivity posterior observations through the Gauss-Newton path.
304
+ - 4D random-walk Kalman assimilation plus RTS smoothing, and ensemble nonlinear 4D assimilation, with
305
+ posterior time-slice extraction.
306
+ - Depth weighting, graph-Matern smoothness, and cross-property Gaussian coupling.
307
+ - Posterior extraction for points, regions/volumes, sections, linear derived quantities, low-rank/diagonal
308
+ summaries, and ensemble samples.
309
+ - Posterior calibration diagnostics for truth coverage, held-out fit, uncertainty inflation, and insufficient
310
+ observations.
311
+ - PDE-constrained state-space smoothing and forecasting.
312
+ - Transient diffusion / heat-equation decay against a discrete analytical rate.
313
+ - Potential-field geophysics sign and linearity.
314
+ - Mechanistic field reconstruction from sparse sensors.
315
+ - 2D acoustic wave propagation stability.
316
+
317
+ These checks are intentionally cheap smoke-and-physics scenarios. They do not replace the full analytic
318
+ test suite; they give applications and CI a quick answer to "is the required modeling surface actually
319
+ present and runnable in this environment?"
320
+
321
+ ## How it works
322
+
323
+ Every solver and inverse callback talks to a single `ops` namespace (`mixle_pde/ops.py`): a float64
324
+ torch backend that provides N-dimensional finite differences (`ops.grad`, `ops.laplacian`), a
325
+ differentiable sparse Poisson solve with adjoint gradients (`ops.sparse_solve`), and the small array
326
+ algebra the steppers need. Because the physics is written against `ops` and never imports torch
327
+ directly, a forward solver is differentiable end to end, which is exactly what lets it drop into the
328
+ `Differential` inverse stack and be fit by gradient-based MAP, Laplace, Gauss-Newton, or VI.
329
+
330
+ ## Tests
331
+
332
+ ```bash
333
+ pytest # the full suite (-n auto via pyproject)
334
+ pytest tests/wave3d_test.py -q # one file
335
+ pytest tests/capabilities_test.py -q
336
+ ```
337
+
338
+ ## Maintainers & contributors
339
+
340
+ Maintained by **Grant Boquet** ([@gmboquet](https://github.com/gmboquet) ·
341
+ grant.boquet@gmail.com).
342
+
343
+ Contributions, issues, and discussion are welcome — open a PR or an issue.
344
+
345
+ ## License
346
+
347
+ MIT — see [LICENSE](https://github.com/gmboquet/mixle-pde/blob/main/LICENSE).