Physics authoring facade¶
pops.physics is the high-level way to write equations with physical names. It
is a facade over pops.model; it does not compile or run.
Boundary
Python authors the model and program. C++/Kokkos/MPI executes the generated or native route.
Write physics¶
from pops.physics import Model
from pops.math import sqrt, div, ddt
m = Model("isothermal_euler")
U = m.state("U", components=["rho", "mx", "my"], roles={"rho": "density"})
rho, mx, my = U
u = m.primitive("u", mx / rho)
v = m.primitive("v", my / rho)
cs2 = m.param("cs2", 1.0)
p = m.scalar("p", cs2 * rho)
c = m.scalar("c", sqrt(cs2))
F = m.flux(
"F",
on=U,
x=[mx, mx * u + p, mx * v],
y=[my, my * u, my * v + p],
waves={"x": [u - c, u, u + c], "y": [v - c, v, v + c]},
)
explicit_rate = m.rate("explicit_rate", ddt(U) == -div(F))
module = m.lower()
Use the model¶
case = (
pops.Case(layout=layout)
.block("plasma", physics=module, spatial=spatial)
.time(program)
)
compiled = pops.compile(case, backend=Production())
sim = pops.bind(compiled, state={"plasma": U0})
sim.run(t_final=1.0, cfl=0.5)
Time syntax¶
Use pops.time.Program for custom schedules:
from pops.time import Program
T = Program("forward_euler").bind_operators(module)
U = T.state("U", block="plasma")
fields = T.solve_fields(state=U.n)
rate = explicit_rate(U.n, fields)
T.define(U.next, U.n + T.dt * rate)
T.commit("plasma", U.next)
Use pops.lib.time for ready-made schemes.
Operator-first layer¶
pops.model.Module, OperatorHandle, Signature, Rate, and
pops.time.Program remain first-class. They are the explicit layer for library
authors, tests, and inspection. The physics facade lowers to those objects.