Add a DSL model¶
Express a model’s physics as symbolic formulas with pops.physics.facade.Model, compile it into a .so,
and attach it to a System. Use this when you want to declare the flux, eigenvalues, source and
elliptic right-hand side directly, instead of composing native bricks. This page assumes you can
already build the Python module and run a case. For the syntax of every declarator, see the
DSL reference; for the concepts, see
fluxes, sources and eigenvalues.
Before you start¶
Set the environment variables the DSL needs to find the pops headers and cache the generated
.so. Replace REPO with the path to your adc_cpp checkout.
export POPS_INCLUDE=REPO/include
export POPS_CACHE_DIR=REPO/.pops_cache
POPS_INCLUDE points at the headers the .so compiles against; POPS_CACHE_DIR caches the
generated .so between runs (default ~/.cache/pops/dsl).
Steps¶
Declare the conservative state and any parameters. Pass
roles=so theSystemcan resolve couplings by role.import pops m = pops.physics.facade.Model("euler_poisson") rho, rhou, rhov, E = m.conservative_vars( "rho", "rho_u", "rho_v", "E", roles=["Density", "MomentumX", "MomentumY", "Energy"]) g = m.param("gamma", 1.4)
Define the primitives in dependency order with
primitive.u = m.primitive("u", rhou / rho) v = m.primitive("v", rhov / rho) p = m.primitive("p", (g - 1.0) * (E - 0.5 * rho * (u*u + v*v)))
Declare the flux and the eigenvalues, one
Exprper component and direction. Usepops.ir.ops.sqrtfor the sound speed. The named math functions in the algebra arepops.ir.ops.sqrtandpops.ir.ops.abs_(absolute value).H = (E + p) / rho c = pops.ir.ops.sqrt(g * p / rho) m.flux(x=[rhou, rhou*u + p, rhou*v, rho*H*u], y=[rhov, rhov*u, rhov*v + p, rho*H*v]) m.eigenvalues(x=[u-c, u, u+c], y=[v-c, v, v+c])
Add the source and the elliptic right-hand side if the model needs them. Read the potential gradient through the fixed
auxchannelsgrad_xandgrad_y.gx, gy = m.aux("grad_x"), m.aux("grad_y") m.source([0.0, -rho*gx, -rho*gy, -(rhou*gx + rhov*gy)]) m.elliptic_rhs(-1.0 * (rho - 1.0))
Set the primitive layout, supply the inverse
Prim -> Uby hand, then validate. The DSL does not invert the primitives for you.prho, pu, pv, pp = m.primitive_vars(rho=rho, u=u, v=v, p=p) m.conservative_from([prho, prho*pu, prho*pv, pp/(g-1.0) + 0.5*prho*(pu*pu + pv*pv)]) m.check()
Assemble a
pops.Casewith the model as its block, declare the typed elliptic field, then compile and bind.pops.compilewithProduction()is the native zero-copy path; it requires that_popsand the.soshare the same headers, compiler and C++ standard. HLLC and Roe require a primitive namedp.import pops import pops.time as T from pops.numerics.riemann import HLLC from pops.numerics.reconstruction.limiters import Minmod from pops.numerics.variables import Primitive from pops.mesh.cartesian import CartesianMesh from pops.mesh.layouts import Uniform from pops.fields import PoissonProblem from pops.fields.bcs import Periodic from pops.fields.rhs import ChargeDensity from pops.solvers.elliptic import GeometricMG from pops.codegen import Production from pops.math import laplacian poisson = PoissonProblem(name="phi", unknown="phi", equation=(-laplacian("phi") == ChargeDensity.from_blocks("gas")), bcs=(Periodic(),), solver=GeometricMG()) case = (pops.Case(layout=Uniform(CartesianMesh(n=32, L=1.0, periodic=True))) .block("gas", physics=m, spatial=pops.FiniteVolume(limiter=Minmod(), riemann=HLLC(), variables=Primitive())) .field(poisson) .time(T.Program("euler"))) compiled = pops.compile(case, backend=Production()) sim = pops.bind(compiled, state={"gas": U0}) # U0: initial conservative state sim.run(0.1, cfl=0.4)
The low-level
System.add_equation/set_poissonruntime methodspops.bindcalls internally stay for the native/AMR runtime and the tests; they are not the documented front door.
Next steps¶
Pick the backend for your run (
prototype,aot,production) in the DSL reference.Mix native bricks with DSL bricks in one model with the brick reference.
Run the model on a refined hierarchy in the simulation guide.