Skip to content

Writing equations

fit reads an equation the way you would write it. This page lists what it may contain.

Names

Every name in an equation is one of five things, decided in this order:

Name Example Comes from
a field, or its derivative u, u_x, u_xt, x1_tt the fields of your Data
a known input f, p Data.inputs: a signal on the grid, such as a force
a coordinate t, x Data.coords
a known constant m, A known={"m": 2.0}
an unknown coefficient anything else unknowns={"k": 1.0}, with a starting value

A derivative repeats the coordinate letter once per order: u_x is du/dx, u_xx is d²u/dx², u_xt is the mixed derivative, w_xxxxt is d⁵w/dx⁴dt. Coordinates are therefore single letters.

An unknown that is not declared is an error, so a typo cannot silently become a new coefficient:

import pysnape as ps

try:
    ps.build_model("x_tt + k*x = 0", fields=["x"], coords=["t"], unknowns={})
except ps.ParseError as err:
    print(err)  # 'k' appears in the equations but is not declared ...

What a term may be

Each term is a product of at most one unknown coefficient, a number or known constant, and any of:

  • fields and their derivatives, multiplied freely: u*u_x, x**3, x**2*x_t, abs(u)**2*u;
  • known inputs: = f, p*x;
  • expressions of the coordinates: x*u_x, cos(2*t), A*sin(w0*t).

Powers must be positive whole numbers (x**3; ^ also works). Two rules keep the problem solvable:

  • Linear in the unknowns. (k1 + k2)*x is fine and is expanded into two terms; k1*k2*x is refused, because each term may carry at most one unknown.
  • Polynomial in the fields. sqrt(x) and exp(x) are refused. Functions are fine on coordinates (exp(-t)*x), but not of the unknowns (exp(-k*t)*x is refused).

The right-hand side is moved to the left, so x_tt + c*x_t + k*x = f and x_tt + c*x_t + k*x - f = 0 are the same equation.

Several equations

Pass a list. A coefficient that appears in several equations is one coefficient, estimated from all of them; this is how a chain of masses shares its springs between neighbours:

import numpy as np

t = np.linspace(0.0, 6.0, 301)
data = ps.Data(
    fields={"x1": np.exp(-0.3 * t) * np.cos(2 * t), "x2": np.exp(-0.3 * t) * np.sin(2 * t)},
    coords={"t": t},
)
result = ps.fit(
    ["x1_t + a*x1 + b*x2 = 0", "x2_t - b*x1 + a*x2 = 0"],
    data,
    unknowns=["a", "b"],  # names only: each starts at 1
    multistart=False,
)
print(result.theta)  # a = 0.3, b = 2.0

Constraints

Keep a coefficient non-negative or non-positive with constraints, per coefficient or for all of them:

result = ps.fit(
    "x1_t + a*x1 + b*x2 = 0", data, unknowns=["a", "b"], constraints=">=0", multistart=False
)

Ready-made models

The systems of the SNAPE papers, and a few more, are built in: ps.models.burgers(), ps.models.mdof_chain(5) and so on. Pass one to fit in place of the equation; see Ready-made models.

Not yet supported

  • Complex-valued fields: the equation parses, but the solver does not fit them yet.
  • Coefficients that vary in space or time.