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)*xis fine and is expanded into two terms;k1*k2*xis refused, because each term may carry at most one unknown. - Polynomial in the fields.
sqrt(x)andexp(x)are refused. Functions are fine on coordinates (exp(-t)*x), but not of the unknowns (exp(-k*t)*xis 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.