On this page
SIR in NuML: your first NuML program
Manual contents | Model logic | Dashboard configuration | Platforms | Where NuML runs
This tutorial writes the classic SIR epidemic entirely in NuML inside a DynSS app. No JavaScript class is edited; the whole model — equations, sliders, graph, and clock — is one block of text. If you have read the SD manual's SDSir tutorial, this is the same model expressed in the language instead of the API.
1. Open a DynSS app
Open dynss2 (or any DynSS-family app) and click
Model Definition to reveal the NuML code editor. The app provides a bank
of dashboard components your program can mobilize by name: sliders Slider1
through Slider35, switches Switch1 through Switch4, four time-graph
slots (TimeGraph, TimeGraph1, TimeGraph2, TimeGraph3), four
phase-graph slots, tables, and more — see the
component inventory.
Components you do not reference stay hidden.
2. The complete program
Type (or paste) the following. Every NuML program begins with the string
'numl' on its own line.
'numl'
// model
deriv(S) @= -beta * S * I / N
deriv(I) @= beta * S * I / N - sigma * I
deriv(R) @= sigma * I
initial(S) @= N - I0
initial(I) @= I0
initial(R) @= 0
update(S) @= TimeGraph
update(I, R) @= TimeGraph1
N @= Slider1
beta @= Slider2
I0 @= Slider3
sigma @= Slider4
// configuration
TimeGraph #= [legend("S"), title("SIR Model -- Susceptible"), ylabel("Population")]
TimeGraph1 #= [legend("I", "R"), title("SIR Model -- Infected and Recovered"), ylabel("Population")]
Slider1 #= [legend("N"), min(1000), max(1000000), value(100000), step(1000)]
Slider2 #= [legend("Beta"), min(0), max(10), value(4), step(0.1)]
Slider3 #= [legend("I0"), min(0), max(10), value(1), step(0.1)]
Slider4 #= [legend("Sigma"), min(0), max(10), value(2), step(0.1)]
integration #= [maxTime(15), dt(0.5), method("RK4"), autorun]
3. Reading the model logic
The @= statements define the simulation:
deriv(S) @= -beta * S * I / NdeclaresSa continuous model variable and gives its derivative — the right-hand side of its differential equation. Under the hood eachderivbecomes a stock with a flow in the SD runtime (how NuML compiles).initial(S) @= N - I0givesSits starting value. Everyderiv(ornext) variable must have a matchinginitial, and thederiv/nextstatement must come first. Initial expressions may read sliders, so Reset restarts from the current dashboard settings.N @= Slider1binds the termNto an input component. Terms are recomputed from current values whenever used — drag the slider mid-run and the equations feel it immediately.update(I, R) @= TimeGraph1routes committed values to a display once per time step. Group every series for one graph in a singleupdate; the order matches the graph'slegend.
Write all multiplication explicitly (beta * S, never beta S or 2S), and
prefix JavaScript math with Math. (Math.exp(-r)); NuML's own
primops such as sin,
normal, and binomial need no prefix.
4. Reading the configuration
The #= statements shape the dashboard and the clock:
Slider2 #= [legend("Beta"), min(0), max(10), value(4), step(0.1)]labels and ranges the slider the model bound tobeta. Sliders used by the model should always get a legend, range, initial value, and step.TimeGraph #= [legend("S"), title(...), ylabel(...)]formats a graph. The number oflegendarguments must equal the number of variables in the correspondingupdate.integration #= [maxTime(15), dt(0.5), method("RK4"), autorun]sets the stop time, the step, fourth-order Runge-Kutta integration (right forderivmodels; usemethod("Euler")withdt(1)fornextmodels), and automatic re-run when an input changes. Addbatchto run to completion without animating. These properties can also be set individually:dt #= 0.5.
5. Run it
Press Save/Run (the editor's apply action) and then Run. With the
defaults (N = 100,000, beta = 4, I0 = 1, sigma = 2, so R0 = 2):
Sfalls along a sigmoid;Irises to a single peak and decays;Rgrows toward the final epidemic size.- The curves on the two graphs always sum to
N— if not, a term is missing from one of the threederivstatements. - With
autorunconfigured, dragging any slider restarts the run with the new value; the epidemic dies out immediately oncebeta <= sigma. - The run stops by itself at
maxTime = 15.
If the editor reports Numl Error(s), the message names the offending
line; the most common causes are implied multiplication, a missing
initial, and a phantom variable — see
rules and pitfalls.
6. Experiments
- Stop on a condition. Add
quitWhen @= (I < 0.5)to end each run when the epidemic is effectively over instead of waiting formaxTime. - Discrete variant. Replace the three
derivstatements withnextequations (for examplenext(S) @= S - beta * S * I / N) and switch tointegration #= [method("Euler"), dt(1), maxTime(100)]. - Make it stochastic. Replace the flows with binomial draws per step — the Ex 20: Stochastic SIR model preset in dynsshs shows the full pattern, and the platforms chapter shows how to histogram the outcomes over hundreds of runs.
- Sensitivity study. Append a
SensPlatformblock to sweepBetaand watch the family ofIcurves. - Save and share. Store the model as a preset; presets serialize the program and dashboard state to JSON (sharing models).
7. Where to go next
- Model logic with
@=— the full statement inventory and the compile semantics. - Dashboard configuration with
#=— every directive for every component type. - Sensitivity and stochastic platforms — analytical runs configured from NuML.
- Where NuML runs — the editor, presets, the AI portal, and embedding NuML in a project definition.