Skip to content

Ready-made models

ps.models builds the systems of the SNAPE papers and a few more, with sensible starting values. Pass one to fit in place of an equation string; unknowns= still overrides the starting values.

Builder Equation Coordinates True values of the matching data
burgers() u_t + θ1 u u_x + θ2 u_xx = 0 x, t (1, −0.1)
kdv() u_t + θ1 u u_x + θ2 u_xxx = 0 x, t (6, 1)
ks() u_t + θ1 u u_x + θ2 u_xx + θ3 u_xxxx = 0 x, t (1, 1, 1)
duffing() x_tt + θ1 x_t + θ2 x + θ3 x³ = p t (0.5, −1, 1)
vanderpol() x_tt + θ1 x_t + θ2 x² x_t + θ3 x = 0 t (−8, 8, 1)
wave2d() u_tt − θ1 u_xx − θ2 u_yy = 0 x, y, t (1, 1)
mdof_chain(n) a chain of n masses fixed at one end t k = 12 … 4, c = 0.6 … 0.2

The coefficients are named theta1, theta2, …, except in mdof_chain, whose springs and dampers are k1 … kn and c1 … cn. ps.models.TRUTH holds the true values above.

Data for each

Model Data
burgers, kdv, ks ps.datasets.load(...): the PDE-FIND data (Data)
duffing ode.duffing(): the forced oscillator of the paper, with the force as input p
vanderpol ode.vanderpol(): μ = 8 by default, with sharp fronts
wave2d pde2d.wave(): the paper's 2-D wave, exact to about 1e-11
mdof_chain ode.mdof_chain(): displacements or accelerations

The generators live in pysnape.data.generators and integrate to tight tolerances, so that any error in a fit comes from the noise and the method, not from the data.

The MDOF chain

ps.models.mdof_chain(n, masses=1.0) writes one force balance per mass, for a chain fixed at its first spring and free at its last mass:

import pysnape as ps

model = ps.models.mdof_chain(3)
for eq in model.equations:
    print(eq.text)
# m1*x1_tt + c1*x1_t + k1*x1 - c2*(x2_t - x1_t) - k2*(x2 - x1) = f1
# m2*x2_tt + c2*(x2_t - x1_t) + k2*(x2 - x1) - c3*(x3_t - x2_t) - k3*(x3 - x2) = f2
# m3*x3_tt + c3*(x3_t - x2_t) + k3*(x3 - x2) = f3

The masses are known; f1 … fn are the applied forces, zero where a mass is not driven. Each spring and damper appears in two equations and is estimated from both, and all are kept non-negative. ode.mdof_chain(n_dof=5) makes matching data: unit masses, a broadband shaker on the free end, displacements or (with observe="acceleration") accelerations.

Measured on five masses, ten unknowns, with the default settings: from 5 % noisy accelerations, every spring within 0.2 % and every damper within 3.6 %.

The 2-D wave

pde2d.wave() reproduces the paper's setup: u_tt = c (u_xx + u_yy) on the square [−1, 1]², fixed at x = ±1, free at y = ±1, with its initial displacement and velocity, on a 50 × 50 × 100 grid over t from 0 to 10. It is solved by expanding in the natural modes of those edges, so it is exact to about 1e-11.