-
Notifications
You must be signed in to change notification settings - Fork 28
Parity and Divergence
NetLogo Web aims to produce the same results as NetLogo desktop. Our goal is behavioral parity. If you see a behavior in the desktop version of NetLogo, you should be able to see the same behavior in NetLogo Web. As much as we can we strive to produce the exact same outputs for the same commands with a given model, random seed, and starting state.
This page covers how that is enforced, the three places it deliberately stops, and how to tell an expected difference from a real bug.
Most models run virtually identically in the browser for as long as you care to run them. Some primitives can cause divergence faster than others, and there are two-main groups of these.
The primitives every, timer, and reset-timer read the system clock. The mouse primitives depend on where a person's cursor is. Behavior gated behind them depends on how fast the machine is running, so it is not reproducible on desktop either.
Arithmetic can differ in the last bit. Desktop and browser round sin/cos differently: about 1 part in 10^16, on roughly a quarter of inputs. The forward command uses trigonometry internally, so essentially every model that moves turtles picks this up.
The behavior changes for a given run of a model with a particular random seed; the behavior isn't buggy or invalid, it's just a different result than you'd get on desktop with than same random seed. The specific run is not exactly reproduceable between the platforms.
The differences in behavior will show up more quickly when a model has both of:
-
A continuous distance comparison —
in-radius,distance < ...,link-length > ...— rather than a patch-level or whole-number one. These have no margin: two agents almost exactly at the cutoff can land on opposite sides of it. -
A feedback loop where positions are computed from positions —
layout-spring,layout-tutte, flocking and alignment rules, anything shaped like "move based on where my neighbors are." These magnify a tiny difference every tick instead of leaving it small.
Together those can grow a last-bit difference into a visibly different world within a few dozen ticks; once agents make different choices they draw from the random number generator differently, and the runs stop being comparable at all. A model with many starting turtles calling layout-spring on neighbors found in-radius can diverge by tick 1.
If your model has neither ingredient above and still differs between desktop and web, then it might be worth reporting; it could be a genuine bug in the NetLogo Web engine.
-
A shared parser (
parser-js), so tokenization, name resolution, and error messages come from one implementation. -
A shared RNG.
engine/src/main/scala/MersenneTwisterFast.scalais Scala compiled through Scala.js, not a hand-written JS port. Mersenne Twister leans on 32-bit integer bit-shifting and JS numbers are not JVMInts.TestMersenneTwistercovers seeding, cloning, and thenextInt/nextDouble/nextGaussianstreams. -
Docking tests, the main mechanism: run NetLogo code on headless desktop and on the compiled engine, export both worlds to JSON, compare after every command. See Tortoise Tests; model-level runs live in
TestModels.scala. - Language tests, mostly imported from desktop, so upstream behavior changes fail here.
Desktop computes with Java's StrictMath, which is specified to give identical results everywhere. The browser has no equivalent: the engine uses the host's Math (shim/strictmath.coffee), and Math.sin and friends are only required to be "implementation-approximated." Measured over 3600 whole-degree inputs, V8 differs from StrictMath by 1 ulp on roughly 24% of sin and cos results.
The docking tests do not see this. netlogo-web/src/main/scala/jsengine/GraalJS.scala deliberately overwrites the engine's math shim before running anything:
put("StrictMath", Strict)
evalRaw("var strictmath = tortoise_require('shim/strictmath')")
evalRaw("Object.getOwnPropertyNames(StrictMath).forEach( (prop) => strictmath[ prop ] = StrictMath[ prop ] )")Without it nearly every docking test that moves a turtle would fail for reasons unrelated to the change under test, and the suite would be worthless as a regression check. The trade is that docking compares Tortoise-on-StrictMath against desktop, so a model can dock perfectly clean and still diverge in a browser.
Note that trig use is invisible in model source: fd calls sin/cos internally to compute dx/dy (engine/core/turtle.coffee), so a model is exposed whether or not it ever writes sin.
We could reimplement StrictMath in JavaScript, but that would mean porting the JVM algorithms and maintaining them forever, on very performance-sensitive code that's often called thousands of times per tick. We don't think the effort and risks are worth the benefits for a problem affecting a small minority of models.
If you have a model that you think is demonstrating a true divergence, dock it first: add it to Model.scala to run in TestModels.scala (see Tortoise Tests).
- If it fails there, it is an ordinary parity bug, and the failing command tells you where.
-
If the docking test passes, check for the common cases:
a. If it docks clean, look for wall-clock primitives. Just seeing
everyin the model doesn't mean much, it has to be firing off real changes in the model. b. Otherwise apply the two ingredients above. Continuous geometry thresholds plus a position feedback loop means you are almost certainly looking at floating-point amplification, not a defect. - Check the rarer cases below.
If you are testing through Galapagos, check tortoiseVersion in its build.sbt. It pins a published Tortoise build, so a local fix is not in the browser until you publish and bump it (see CONTRIBUTING).
There are two other possible sources of divergence. These are much more rare, but are good to keep in mind if you're troubleshooting a divergent model without any of the common causes that docks cleanly.
- NetLogo
headless(what we test against in Tortoise) versusnetlogo(what the NetLogo desktop GUI app runs). This would indicate a bug in the NetLogo project, essentially a divergence between those two versions of the engine. Once that's reconciled, Tortoise can be updated to match, if necessary. - Galapagos (the user interface) itself causing some state change. This would almost certainly be a bug, something like a widget pulling from the main RNG instead of a clone.