Data¶
ps.Data holds measurements on a grid.
import numpy as np
import pysnape as ps
x = np.linspace(-1.0, 1.0, 41)
t = np.linspace(0.0, 2.0, 21)
X, T = np.meshgrid(x, t, indexing="ij")
data = ps.Data(fields={"u": np.sin(np.pi * (X - T))}, coords={"x": x, "t": t})
print(data)
| Argument | What it holds |
|---|---|
fields |
each measured field: an array with one axis per coordinate, in coordinate order (above, u[i, j] is at x[i], t[j]) |
coords |
the grid: one strictly increasing 1-D array per coordinate; the order of the keys is the order of the axes |
inputs |
known signals on the same grid, such as an applied force, used by name in equations |
observe |
what was measured when it is not the field itself: {"x1": "tt"} means the array under x1 holds accelerations |
mask |
a boolean array the shape of the grid: which points were measured; None means all |
Use np.meshgrid(..., indexing="ij") to build fields from formulas, so the axes come out in
coordinate order.
Accelerations and other derivatives¶
Accelerometers measure x_tt, not x. Say so with observe, and fit the displacement
equation unchanged:
from pysnape.data.generators import ode
acc = ode.mdof_chain(n_dof=3, t_end=30.0, n=751, observe="acceleration")
print(acc.observe) # {'x1': 'tt', 'x2': 'tt', 'x3': 'tt'}
result = ps.fit(ps.models.mdof_chain(3), acc, multistart=False, weights=(1, 10, 100, 1000))
print(result.theta) # k = 12, 8, 4 and c = 0.6, 0.4, 0.2
The constant and linear parts of a displacement are invisible in its acceleration; the equation pins them.
Missing points¶
Set mask to the points you measured. The equation is still enforced everywhere, so the
fitted field fills the gaps. subsample makes such a mask at random, for experiments:
sparse = data.subsample(0.5, seed=1) # half the x samples times half the t samples
print(sparse.n_observed, "of", data.n_points)
Noise¶
add_noise adds white Gaussian noise. The level is a percentage, the root-mean-square of the
noise over that of the signal, or a signal-to-noise ratio in decibels:
noisy = data.add_noise(percent=5, seed=0) # the same as snr_db=26.02
One seed names one noise realisation of the whole dataset, which is what
ensemble loops over.
Files¶
import tempfile
from pathlib import Path
path = Path(tempfile.mkdtemp()) / "run.npz"
data.save(path) # everything in one .npz
again = ps.Data.load(path)
Arrays from MATLAB or a CSV file with other names: map them.
# not run: needs your file
data = ps.Data.from_file("experiment.mat", fields={"u": "usol"}, coords={"x": "x", "t": "t"})
.mat files up to version 7.3 are read (7.3 needs pip install "pysnape[mat73]").
Published datasets¶
ps.datasets fetches the PDE-FIND datasets of Rudy et al. (Burgers, Korteweg–de Vries,
Kuramoto–Sivashinsky). Each file is downloaded once from the authors' repository
into ~/.cache/pysnape (or $PYSNAPE_DATA), checked against its sha256, and never
redistributed with pysnape. The datasets belong to their authors: use them according to
their terms, and cite them:
# not run: downloads on first use
print(ps.datasets.available()) # ['burgers', 'kdv', 'ks']
data = ps.datasets.load("burgers")
print(ps.datasets.info("burgers")["citation"]) # please cite the data's source
Generated data¶
pysnape.data.generators integrates the built-in systems accurately, for experiments with a
known truth: ode.duffing, ode.vanderpol, ode.mdof_chain and pde2d.wave. See
Ready-made models.