Simulation — `simulate`
Declare params, states, laws, a scheme and a stop condition. The build writes the C# class that runs it — there is no loop in the source.
A simulate declaration in a .hph file says what a physical system is. The build generates a class
whose Run() returns the trajectory and whose Stream() yields it sample by sample. Every quantity is
dimension-checked, every unit is converted to SI base measures, and the only loop is the Physis driver.
open Physis.Constants
open Physis.Schemes
simulate Projectile {
param v₀ : Velocity = 30 m/s
param α : Angle = 45 deg
param k : Frequency = 0.1 Hz
state x : Length[2] = [0 m, 0 m]
state v : Velocity[2] = v₀·[cos(α), sin(α)]
law x' = v
law v' = [0 m/s², −g₀] − k·v
integrate rk45 tolerance 1e-9
record every 10 ms
observe height ≔ x[1]
observe speed ≔ norm(v)
stop when x[1] < 0 m ∧ t > 0 s
stop when t ≥ 60 s
run Steep with α = 70 deg
}
var projectile = new Projectile();
var run = projectile.Run(); // or Run(projectile.Defaults with { V0 = 50 })
run.Reason // Stop { Kind = When, Clause = "x[1] < 0 m ∧ t > 0 s", Line = 14 }
run.Time; run.Height; run.Speed // double[] — one column per observe, SI base measures
run.Final.State.X // double[2]
run.ToFrame(); run.ToCsv();
await foreach (var s in projectile.Stream()) { /* s.T, s.Height, s.State */ }
projectile.Steep(); // a named run
The clauses
| clause | meaning |
|---|---|
param p : Q = value |
a run input with a constant default; a member of Params |
state y : Q = value |
a state and its initial value (may read params); Length[2] is two components |
law y' = e |
a first-order law; law y'' = e is second order and needs state y' : … = … for the initial velocity |
integrate scheme [step Δ] [tolerance ε] [limit n] |
the scheme; default rk45 tolerance 1e-8 |
record every Δ |
samples on a time grid; without it, every accepted step is a sample |
observe o ≔ e |
a recorded column (one number: index with x[i], or take norm(x), dot(a, b)) |
stop when c |
a condition that ends the run, located exactly; comparisons joined by ∧ and ∨ |
run Name with p = value, … |
a named scenario, generated as a method |
In every expression, t is the time, x[i] a component, and y' the derivative of a state: the velocity of
a second-order state, or the right side of a first-order law.
Motion from a Lagrangian or a Hamiltonian
simulate CartPendulum {
param M : Mass = 2 kg
param m : Mass = 1 kg
param ℓ : Length = 1 m
state s : Length = 0 m
state s' : Velocity = 0 m/s
state φ : Angle = 0.5 rad
state φ' : AngularVelocity = 0 rad/s
lagrangian ≔ 0.5·(M + m)·s'² + m·ℓ·s'·φ'·cos(φ) + 0.5·m·ℓ²·φ'² + m·g₀·ℓ·cos(φ)
integrate rk4 step 1 ms
observe energy ≔ hamiltonian
conserve energy within 1e-6
stop when t ≥ 5 s
}
lagrangian ≔ Lmoves every stateqthat has a velocity stateq'. The build takesp = ∂L/∂q̇, the mass matrixM = ∂p/∂q̇and the forceb = ∂L/∂q − (∂p/∂q)·q̇ − ∂p/∂twith the laws ofPrelude/Calculus.hb, and the run solvesM·a = b(directly whenMis diagonal, by elimination otherwise).Lis an Energy.hamiltonian ≔ Hmoves every pair declaredstate p : Momentum conjugate q = …:q' = ∂H/∂p,p' = −∂H/∂q.hamiltonianread in an expression is the energy:H, orΣ q̇·∂L/∂q̇ − L.conserve o within εends the run asViolatedwhen the observed columnodrifts pastε·max(|o₀|, 1).
Effort and flow: circuits, motors and loudspeakers
A circuit is its energies. An inductor is the kinetic part of a Lagrangian, a capacitor the potential part, a resistor Rayleigh’s dissipation, and a source or a coupling an effort on a coordinate — the same four words for a mass, a spring, a damper and a force.
simulate DcMotor {
param V : Voltage = 12 V
param R : Resistance = 1 Ω
param L : Inductance = 0.5 mH
param J : MomentOfInertia = 0.01 kg·m²
param b : RotationalDrag = 0.001 N·m·s
param k : TorqueConstant = 0.05 N·m/A
state q : Charge = 0 C
state q' : Current = 0 A
state θ : Angle = 0 rad
state θ' : AngularVelocity = 0 rad/s
lagrangian ≔ 0.5·L·q'² + 0.5·J·θ'²
dissipation ≔ 0.5·R·q'² + 0.5·b·θ'²
effort q ≔ V − k·θ'
effort θ ≔ k·q'
integrate rk45 tolerance 1e-9
observe speed ≔ θ'
stop when t ≥ 60 s
}
dissipation ≔ Dis a Power, a function of the velocities; the force onqgains−∂D/∂q̇. It needs alagrangian.effort q ≔ Qis the generalised force on the coordinateq, an Energy per[q]: a Voltage on a Charge, a Force on a Length, a Torque on an Angle. It adds to the Euler–Lagrange force, or top'under ahamiltonian.- The winding’s
k·θ'and the shaft’sk·q'are one constant read both ways — the reciprocity that makes a motor a generator. The run settles atω = kV/(k² + Rb), whichPhysis.Electromechanics.motorSpeedalso states.
Rigid bodies
simulate FreeBody {
param I : MomentOfInertia[3] = [1 kg·m², 1 kg·m², 2 kg·m²]
state ω : AngularVelocity[3] = [1 rad/s, 0 rad/s, 2 rad/s]
law ω' = cross(I·ω, ω) / I
integrate rk45 tolerance 1e-11
observe energy ≔ 0.5·dot(I·ω, ω)
conserve energy within 1e-8
stop when t ≥ 10 s
}
cross(a, b)takes two quantities of size 3. Euler’s equations in the body frame are one law,I·ωbeing the component product.- An attitude is a
Dimensionless[4]quaternion state whose law is½·q ⊗ (0, ω)written out component by component; a heavy top is alagrangianin Euler angles (samplesPhysics/RigidBody.hph).
Fields on grids
simulate HeatedRod {
param α : Diffusivity = 0.01 m²/s
param s : HeatingRate = 1 K/s
grid (x) : Length[51] from 0 m to 1 m
field u : Temperature = 0 K
boundary u dirichlet 0 K
law u' = α·laplacian(u) + s
integrate backwardEuler step 10 s
observe middle ≔ u[25]
stop when maxAbs(u') < 1e-6 K/s
}
grid (x) : Q[N] from a to borgrid (x, y) : Q[N, M] from a to b; inside a field’s valuexandyare the node’s coordinates.field u : Q = e— one component per node.boundary u dirichlet v,neumann(the default) orperiodic.dx(u),dy(u),laplacian(u)are central-difference stencils;sum,mean,maxAbsreduce a field. Two quantities of one size multiply component by component; the inner product isdot(a, b).dxBackward(u),dxForward(u),dyBackward(u),dyForward(u)are one-sided differences. Advection at a positive speed is upwind withdxBackward— monotone and conservative across a jump — where the centraldxis second order but ripples. The side is yours to choose; no speed’s sign is read.- A steady state is reached by relaxing to rest:
stop when maxAbs(u') < …— which is how a Poisson problem is solved.
Events, noise and ensembles
simulate BouncingBall {
param e : Dimensionless = 0.8
state y : Length = 10 m
state y' : Velocity = 0 m/s
law y'' = −g₀
on y < 0 m ∧ y' < 0 m/s do y' ≔ −e·y'
integrate rk45 tolerance 1e-12
stop when t ≥ 6 s
}
simulate OrnsteinUhlenbeck {
param γ : Frequency = 1 Hz
param σ : Velocity = 1 m/s
state v : Velocity = 0 m/s
noise w
law v' = −γ·v + σ·√γ·w
integrate eulerMaruyama step 10 ms
stop when t ≥ 10 s
}
on c do s ≔ e, …fires whencturns true, located on the step’s interpolant; every reset reads the state from before the event, and the run continues.noise wis white noise of dimensionTime^-1/2. A law must be linear in it; the build splits it into drift and diffusion, and astochasticscheme reads them.integrate … seed nandRun(seed: n)fix the path.Ensemble(runs, parameters, seed)runs many in parallel;Ensemble(sweep)one per parameter set.
Schemes
Physis.Schemes states each integrator as mathematics over the rate f(t, y) (or the acceleration a(t, x)):
| scheme | kind |
|---|---|
euler |
first order, fixed step |
midpoint |
second order, fixed step |
rk4 |
fourth order, fixed step |
rk45 |
Dormand–Prince 5(4), adaptive |
verlet, leapfrog |
second order, symplectic — every law must be x'' = a(t, x) |
backwardEuler |
implicit, first order, A-stable — solved by Newton |
crankNicolson |
implicit, second order, A-stable — solved by Newton |
eulerMaruyama |
stochastic, for laws with noise |
A fixed explicit scheme declares its stability radius (scheme rk4 stable 2.78 { … }); before a run takes its first
step, the spectral radius of the rate’s Jacobian times the step is checked against it, and a step past it ends the run
as Unstable.
A scheme of your own is a scheme declaration: bind next (and error ≔ … for adaptive <order>), or
nextX and nextV for a second scheme.
How a run proceeds
- The initial state is the first sample; a clause that already holds ends the run.
- A fixed scheme steps by
step; an adaptive one retries until its error is withintoleranceand scales the next step. - A non-finite state ends the run as
Diverged;limitaccepted steps end it asLimit. Neither throws. - After each step the first clause that holds is located by bisection over the cubic Hermite interpolant of the step, so a projectile stops at the ground and not one step below it.
Run(step:, tolerance:)overrides the declared settings.
Diagnostics
| code | meaning |
|---|---|
| PH0101 | a state with no law or two, a law naming no state, a second-order law with no velocity state |
| PH0102 | a second-order scheme with a first-order law, or an acceleration that reads a velocity |
| PH0103 | a fixed-step scheme with no step |
| PH0104 | no stop when |
| PH0105 | a scheme that reads what it may not or binds the wrong outputs |
| PH0106 | a symbolic extent (Length[n]) |
| PH0107 | a scheme not in scope |
| PH0108 | a run overriding no param |
| PH0109 | shapes that do not fit |
| PH0110 | a setting of the wrong dimension, or a param default that is not a constant |
| PH0111 | a simulation named like another generated class |
| PH0112 | a derivative the calculus laws do not take |
| PH0113 | a grid operator on something that is not a field, or a field with no grid |
| PH0114 | a law not linear in its noise, or noise under a scheme that is not stochastic |
| PH0115 | a lagrangian/hamiltonian with nothing to move |
| PH0116 | a conserve naming no observed column |
| PH0117 | a dissipation or effort with no lagrangian (or a dissipation beside a hamiltonian), an effort on no coordinate, two efforts on one, or an effort of the wrong size |