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

  1. Declare the conservative state and any parameters. Pass roles= so the System can 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)
    
  2. 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)))
    
  3. Declare the flux and the eigenvalues, one Expr per component and direction. Use pops.ir.ops.sqrt for the sound speed. The named math functions in the algebra are pops.ir.ops.sqrt and pops.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])
    
  4. Add the source and the elliptic right-hand side if the model needs them. Read the potential gradient through the fixed aux channels grad_x and grad_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))
    
  5. Set the primitive layout, supply the inverse Prim -> U by 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()
    
  6. Assemble a pops.Case with the model as its block, declare the typed elliptic field, then compile and bind. pops.compile with Production() is the native zero-copy path; it requires that _pops and the .so share the same headers, compiler and C++ standard. HLLC and Roe require a primitive named p.

    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_poisson runtime methods pops.bind calls internally stay for the native/AMR runtime and the tests; they are not the documented front door.

Next steps