This repository is a self-contained Rust reimplementation of the simulation stack used in the master's dissertation described below. It fuses the parts of The ONE simulator that the experiments touch with the GrAnt and rGrAnt routing protocols, and reproduces the dissertation's reported results byte-for-byte against report files dumped from the original Java code.
Everything needed to re-run the experiments — maps, scenario configuration files, and the Java golden reports used as ground truth — is committed here. No Java, no The ONE checkout, and no network access are required.
Uma Nova Versão do Protocolo GrAnt para Redes Tolerantes a Atrasos (A New Version of the GrAnt Protocol for Delay Tolerant Networks)
Luís Guilherme Bergamini Mendes
Advisor: Profª. Drª. Ana Cristina Barreiras Kochem Vendramin
Universidade Tecnológica Federal do Paraná (UTFPR) — PPGCA
Concentration area: Redes e Sistemas Distribuídos — Curitiba, 2018
Delay/Disruption Tolerant Networks (DTNs) have no guarantee of an end-to-end path. Nodes meet sporadically, and messages travel by store-carry-forward: a node holds a message until it encounters a peer that looks like a better custodian. The whole problem is deciding, at each contact, whether handing the message over improves its chance of delivery.
The dissertation's thesis is that a single forwarding policy cannot suit every connectivity regime. A node that meets one peer per hour and a node that meets eighty need opposite strategies:
- Sparsely connected nodes must exploit the few contacts they get, so forwarding should be permissive — otherwise a strict rule rejects the only peer available and the message dies in the buffer.
- Densely connected nodes must avoid flooding the same popular peers and links, so forwarding should be restrictive — otherwise a handful of nodes absorb all the traffic, buffers overflow and messages are dropped.
The proposal, rGrAnt (Rule-based Greedy Ant), gives GrAnt the ability to measure its own connectivity online and switch forwarding policy accordingly.
GrAnt (Greedy Ant) is an existing DTN routing protocol that applies the Ant Colony Optimization (ACO) metaheuristic. Like ants laying trails, nodes accumulate pheromone on paths that have proven to deliver messages, while a heuristic function provides a greedy estimate of how promising a candidate next hop looks right now.
Two quantities drive every decision:
| Quantity | Meaning |
|---|---|
Heuristic Heu |
Immediate desirability of a neighbour, from social metrics: betweenness centrality, social proximity to the destination, free buffer percentage, and path quality. |
Pheromone Fer |
Learned, reinforced by backward ants that retrace the route after a successful delivery, and decayed by evaporation. |
The combined score FerHeu ranks candidate next hops. GrAnt also tracks U_Best — the best
score any node has achieved for that message so far — and only forwards to a neighbour that beats
it. U_Best is what makes GrAnt greedy: it keeps replication low, but it is also a hard
limiter, because a node holding the message may never meet anyone better than the current best.
The dissertation's variations all attack the same question: when should that limiter be relaxed, and when should it be tightened?
Each Var is a modified GrAnt forwarding rule. They fall into two families.
Built by removing U_Best, replacing the AND in the forwarding test with OR, relaxing strict
> comparisons to ≥, and adding a fallback that lets a message be forwarded at least once.
| Var | What it changes relative to GrAnt |
|---|---|
| Var1 | Removes U_Best; AND→OR; fallback (FerHeu_J ≥ FerHeu_I) and message never forwarded. |
| Var2 | Forwards immediately if pheromone exists; otherwise keeps U_Best logic. |
| Var2a | GrAnt's test, but accepts ties against U_Best (≥ instead of >). |
| Var2b | Var2 plus tie-acceptance on both U_Best and the fallback. |
| Var2c | AND→OR; fallback compares the heuristic rather than FerHeu. |
| Var2d | AND→OR, ≥ against U_Best, heuristic fallback. |
| Var3 | Removes U_Best; AND→OR; fallback (Heu_J ≥ Heu_I) and message never forwarded. |
| Var4 | Removes U_Best; OR with ≥; fallback is simply message never forwarded. |
| Var4a | Var4, but a repeat forward additionally requires FerHeu_J ≥ FerHeu_I. |
| Var4b | Removes U_Best; OR with ≥; no extra fallback. |
| Var4c | Var4b using FerHeu instead of Heu. |
| Var4d | Var4b with strict >. |
| Var4e | Var4d using FerHeu instead of Heu. |
These drop betweenness centrality, and use only 1/nHops for path quality (GrAnt also factors
in popularity). The trailing letter selects the heuristic:
- a → free-buffer percentage only
- b → social proximity only
- c → free-buffer percentage and social proximity
| Var | What it changes relative to GrAnt |
|---|---|
| Var5a/5b/5c | An initial test (pheromone exists) AND (Heu_J > Heu_I) seeds U_Best; thereafter FerHeu_J > U_Best; strict > throughout. |
| Var6a/6b/6c | Stricter than Var5: the seeding test is removed, so U_Best gates from the start. |
| Var7a/7b/7c | Strictest: pheromone is removed entirely, decisions use the heuristic alone (Heu_J > U_Best). Motivated by pheromone concentrating traffic onto a few overused links when connectivity is high. |
rGrAnt is not a new algorithm — it is GrAnt plus a rule layer that selects, per node and per moment, which of the Vars to behave as.
Each selected Var is renamed to a behaviour. The dissertation states it directly:
"O comportamento chamado Esparso1 representa a utilização do Var3, o comportamento Esparso2 representa a utilização do Var1, o comportamento Conectado1 representa a utilização do Var5b, o comportamento Conectado2 representa a utilização do Var6c e, por fim, o comportamento Conectado3 representa a utilização do Var7b."
| Behaviour | Var | Family |
|---|---|---|
| Esparso1 | Var3 | low connectivity, permissive |
| Esparso2 | Var1 | low connectivity, permissive |
| (none) | GrAnt | baseline, middle ground |
| Conectado1 | Var5b | high connectivity, restrictive |
| Conectado2 | Var6c | high connectivity, more restrictive |
| Conectado3 | Var7b | high connectivity, most restrictive |
So rGrAnt = { Var3, Var1, GrAnt, Var5b, Var6c, Var7b } arbitrated at runtime. The other Vars were evaluated and discarded during tuning.
| Input | Definition |
|---|---|
| NC — Número de Contatos | Number of contacts a node had in a sliding 5000-second window, averaged with the previous window to smooth abrupt swings. |
| TB — Tamanho do Buffer | The node's buffer size in MB. |
Linguistic terms, with the intervals fixed by the tuning experiments:
- TB = {
baixo= [0, 4] MB,alto= [5, ∞) MB } - NC = {
muito baixo= [1, 3],baixo= [4, 5],médio= [6, 16],moderadamente alto= [17, 55],alto= [56, 65],muito alto= [66, ∞) }
IF TB is alto AND NC is muito baixo -> Esparso1 (Var3)
IF TB is alto AND NC is baixo -> Esparso2 (Var1)
IF TB is alto AND NC is médio -> GrAnt
IF TB is baixo AND NC is (muito baixo | baixo | médio) -> GrAnt
IF NC is moderadamente alto -> Conectado1 (Var5b)
IF NC is alto -> Conectado2 (Var6c)
IF NC is muito alto -> Conectado3 (Var7b)
Note the asymmetry: buffer size only gates the sparse behaviours. A node with a small buffer (≤ 4 MB) falls back to plain GrAnt at low connectivity, because permissive forwarding into an already-scarce buffer just causes drops. Once connectivity is high, the restrictive behaviours apply regardless of buffer size.
The selection was driven by a single metric — message delivery ratio (taxa de entrega) — using the WD scenario to tune and the PoI scenario to validate. A Var was adopted for an NC band only if it beat GrAnt in both. All figures are means of 30 runs at a 95% confidence interval.
All 13 low-connectivity Vars were run on WD with the Var applied for NC below a threshold and GrAnt elsewhere. Delivery ratio, GrAnt baseline 0.5699:
| Band | Best Var | Best value | Runner-up |
|---|---|---|---|
| NC = 1 | Var1 / Var3 (tie) | 0.5971 | Var4d 0.5946 |
| NC ≤ 2 | Var2d | 0.6064 | Var3 0.6039, Var1 0.6001 |
| NC ≤ 3 | Var3 | 0.6089 | Var1 0.6004 |
| NC ≤ 4 | Var1 | 0.6101 | Var2d 0.6045 |
Var3 owned the very sparse band and Var1 took over at NC = 4 — which is exactly where the
muito baixo / baixo boundary was drawn.
PoI validation confirmed the choice, and also revealed the buffer dependency:
| Test | GrAnt | Var |
|---|---|---|
| Var3, PoI 8 MB, NC ≤ 3 | 0.8052 | 0.8176 ✔ |
| Var1, PoI 8 MB, NC = 4 | 0.8052 | 0.8181 ✔ |
| Var3, PoI 4 MB, NC ≤ 3 | 0.6259 | 0.6161 ✘ |
| Var1, PoI 4 MB, NC = 4 | 0.6259 | 0.6071 ✘ |
The sparse behaviours lose to GrAnt at a 4 MB buffer but win at 8 MB. This is why TB entered
the rule set and why TB baixo = [0, 4] MB falls back to GrAnt.
Extending Var1 upward settled the ceiling at NC ≤ 5:
| Band | WD (GrAnt 0.5699) | PoI (GrAnt 0.8052) |
|---|---|---|
| NC ≤ 5 | 0.6082 | 0.8211 |
| NC ≤ 6 | 0.6078 | 0.8163 |
The opposite procedure: start at a very high NC floor and lower it until the gain disappears.
Step 1 — NC ≥ 70, all nine high-connectivity Vars (GrAnt 0.5699):
| Var5a | Var5b | Var5c | Var6a | Var6b | Var6c | Var7a | Var7b | Var7c |
|---|---|---|---|---|---|---|---|---|
| 0.5621 | 0.5671 | 0.5694 | 0.5621 | 0.5671 | 0.5694 | 0.5702 | 0.5715 | 0.5702 |
Step 2 — lower Var7b's floor until it stops beating GrAnt:
| NC ≥ 70 | NC ≥ 68 | NC ≥ 66 | NC ≥ 65 |
|---|---|---|---|
| 0.5715 | 0.5763 | 0.5721 | 0.5679 ✘ |
Var7b holds down to NC ≥ 66 → muito alto = [66, ∞).
Step 3 — re-run all Vars at NC ≥ 65. Var6c now wins (0.5760), and lowering its floor
keeps it ahead down to the mid-50s, fixing alto = [56, 65].
Step 4 — drop to NC ≥ 15. Var5b wins decisively (0.5880 vs GrAnt 0.5699); several other
Vars collapse here (Var5a 0.4038, Var7a 0.4060) — evidence that the strictest rules are actively
harmful at moderate connectivity.
Step 5 — PoI validation of Var5b exposed the lower bound:
| NC ≥ 17 | NC ≥ 15 | NC ≥ 14 | NC ≥ 10 | NC ≥ 6 | |
|---|---|---|---|---|---|
| PoI (GrAnt 0.8052) | 0.8062 ✔ | 0.8047 ✘ | 0.7995 ✘ | 0.7945 ✘ | 0.7813 ✘ |
Var5b only beats GrAnt on PoI from NC ≥ 17 upward → moderadamente alto = [17, 55], and the
gap [6, 16] stays with plain GrAnt.
Means of 30 runs, 95 % confidence interval. Tx. Entrega = delivery ratio;
Rel. Redundância = redundancy (replication) ratio. Decimal separators normalised to ..
| Buffer | GrAnt delivery | rGrAnt delivery | GrAnt redund. | rGrAnt redund. |
|---|---|---|---|---|
| 4 MB | 45.7537 ± 0.4058 % | 47.1343 ± 0.3825 % | 14.4765 | 16.4345 |
| 6 MB | 53.7443 ± 0.4357 % | 58.3313 ± 0.4715 % | 13.9483 | 23.1636 |
| 8 MB | 57.8180 ± 0.4532 % | 63.2810 ± 0.4784 % | 13.7716 | 22.6404 |
| 10 MB | 59.9593 ± 0.4309 % | 66.0020 ± 0.4759 % | 13.6233 | 22.3564 |
| 12 MB | 61.1870 ± 0.4552 % | 67.6203 ± 0.5142 % | 13.4517 | 21.9697 |
| 14 MB | 61.7153 ± 0.5033 % | 68.6107 ± 0.4954 % | 13.3705 | 21.9697 |
| 16 MB | 61.9923 ± 0.4932 % | 69.0790 ± 0.5072 % | 13.2846 | 21.8503 |
rGrAnt wins at every buffer size, by up to +7.1 percentage points, at the cost of higher redundancy. The 4 MB row shows the smallest gain and the smallest redundancy penalty — the rule set deliberately falls back to GrAnt for small buffers.
| Nodes | GrAnt delivery | rGrAnt delivery |
|---|---|---|
| 75 | 11.0457 ± 0.2470 % | 10.9743 ± 0.2653 % |
| 142 | 16.8773 ± 0.5115 % | 18.0230 ± 0.5654 % |
| 339 | 59.9573 ± 0.4757 % | 66.0020 ± 0.4759 % |
| 594 | 69.4153 ± 0.3356 % | 74.4663 ± 0.3105 % |
| 1104 | 71.0547 ± 0.3847 % | 76.7023 ± 0.3371 % |
| Buffer | GrAnt delivery | rGrAnt delivery |
|---|---|---|
| 4 MB | 61.8962 ± 0.2170 % | 61.5147 ± 0.2266 % |
| 6 MB | 74.4397 ± 0.2217 % | 74.9370 ± 0.2462 % |
| 8 MB | 80.3120 ± 0.1940 % | 81.8483 ± 0.1774 % |
| 10 MB | 82.5990 ± 0.1680 % | 84.5750 ± 0.1654 % |
| 12 MB | 83.3267 ± 0.1674 % | 85.5360 ± 0.1746 % |
| 14 MB | 83.4637 ± 0.1738 % | 85.8443 ± 0.1680 % |
| 16 MB | 83.4690 ± 0.1867 % | 85.8963 ± 0.1592 % |
"Com tamanhos de buffer pequenos, percebe-se que o rGrAnt não possui desempenho superior ao GrAnt … Porém, com o aumento do tamanho do buffer, os ganhos passam a ser significativos."
| Nodes | GrAnt delivery | rGrAnt delivery |
|---|---|---|
| 12 | 5.6483 ± 0.1996 % | 5.8077 ± 0.1999 % |
| 36 | 29.1790 ± 0.3286 % | 28.1333 ± 0.3549 % |
| 120 | 80.3120 ± 0.1940 % | 81.8483 ± 0.1774 % |
| 240 | 90.6420 ± 0.1289 % | 90.2147 ± 0.1110 % |
| 360 | 94.0350 ± 0.0953 % | 92.7960 ± 0.0836 % |
PoI is the scenario where rGrAnt is not uniformly better: at high node counts the network is already dense enough that plain GrAnt suffices.
| Protocol | Delivery ratio | Messages delivered | Redundancy ratio |
|---|---|---|---|
| GrAnt | 22.7390 ± 0.1509 % | 3490.0333 ± 23.2107 | 18.6741 ± 0.1448 |
| rGrAnt | 23.4043 ± 0.1746 % | 3592.1000 ± 26.8584 | 8.2283 ± 0.0608 |
The strongest result in the dissertation: rGrAnt delivers more messages while more than halving redundancy (−55.9 %). Both gains are statistically significant.
"O desempenho do rGrAnt foi comparado com o GrAnt em três cenários diferentes de simulação. Resultados mostram que, nos três cenários, o rGrAnt obtém uma maior taxa de entrega de mensagens do que o GrAnt."
A PoI variant with node stop times scaled ×100 ("tempo de parada") is also reported; rGrAnt gains there for buffers above 4 MB, but the differences are not statistically significant.
The dissertation's conclusion suggests that rGrAnt's crisp, rule-based forwarding logic "could be adapted to use a fuzzy-based system in order to better explore the connectivity intervals of the nodes," and that other ACO parameters "could be adapted online." FuzzyAnt is that line of work: four fuzzy variants that keep rGrAnt's entire ant-colony substrate byte-for-byte and replace only the forwarding-decision layer, so every measured difference is the fuzzy contribution alone.
| Variant | Idea | Curitiba character |
|---|---|---|
| FuzzyAntVar1 | A Mamdani controller scores each candidate into a forwarding suitability s, turning rGrAnt's crisp nC bands into a smooth ramp. |
efficiency corner |
| FuzzyAntVar2 | A per-node restrictiveness meta-controller R = f(nC, buffer, sociality) continuously tunes how choosy a node is. |
delivery corner |
| FuzzyAntVar3 | Unifies Var1 + Var2 and adds the online adaptation the dissertation asked for (a pivot that tracks each node's own history) plus a latency-protecting urgency term. | balanced, Pareto-dominant |
| FuzzyAntVar4 | Adds online gain self-tuning and urgency on the ACO substrate — a rigorous negative result (neither helps on Curitiba; Var4 ≈ Var3). | confirms Var3 |
On the Curitiba public-transport scenario (rGrAnt's best, §6.5), across the same 30 seeds, all four variants raise delivery over rGrAnt:
| Router | Delivery ratio | Δ vs rGrAnt | Redundancy | Median latency (s) |
|---|---|---|---|---|
| rGrAnt | 0.2340 | — | 8.228 | 2804 |
| FuzzyAntVar1 | 0.2428 | +3.8 % | 6.928 | 2271 |
| FuzzyAntVar2 | 0.2513 | +7.4 % | 8.532 | 3022 |
| FuzzyAntVar3 | 0.2455 | +4.9 % | 6.433 | 2268 |
| FuzzyAntVar4 | 0.2458 | +5.0 % | 6.464 | 2270 |
The crowned final FuzzyAnt is FuzzyAntVar2 (router id FuzzyAnt2). It wins the primary DTN
metric — the highest delivery ratio of any router on Curitiba (+7.4 % vs rGrAnt, on every one of
the 30 seeds) — at overhead and delay within a few percent of rGrAnt's. It is also the only
variant that generalises: on the sparse WD/PoI scenarios the restrictive Var1/Var3/Var4 collapse
(−64 % delivery on WD), while FuzzyAntVar2 holds near-parity delivery (−9 % WD, −6 % PoI) at
half the relays and lower latency. No fuzzy variant beats rGrAnt on delivery off Curitiba —
rGrAnt remains the generalist — but FuzzyAntVar2 is the robust fuzzy alternative.
If the objective is instead efficiency-first on Curitiba — a clean Pareto win, better on every axis — then FuzzyAntVar3 / FuzzyAntVar4 dominate rGrAnt (more delivery and −22 % overhead, −23 % delay, −19 % latency) at slightly lower delivery than Var2.
Full design, calibration, per-seed significance, and the cross-scenario tables are in
docs/fuzzyant.md. The variants keep their code/router identifiersFuzzyAnt/FuzzyAnt2/FuzzyAnt3/FuzzyAnt4(= Var1–Var4) in the source tree and parameter files.
The original experiments ran on The ONE simulator (Java) with GrAnt/rGrAnt routing. This
repository reimplements that stack in Rust with one hard requirement: bit-exact reproduction.
Not statistically similar — identical, down to every digit of every line of the
MessageStatsReport.
Meeting that bar meant porting the exact behaviour of the JDK, not idiomatic Rust equivalents:
| Concern | Approach |
|---|---|
java.util.Random |
Reimplemented LCG, including nextGaussian's cached second value. |
HashMap iteration order |
Ported the JDK's bucket layout, resize behaviour and red-black-tree treeification, because message-buffer iteration order changes drop decisions. |
Collections.sort |
Ported the legacy merge sort, tie-breaking included. |
| Floating point | Simulation clock kept as a float accumulator (clock += updateInterval) exactly as The ONE does, rather than an integer tick counter. |
Hashtable |
Ported separately where The ONE relies on it. |
Every entry compares the Rust port's MessageStatsReport byte-for-byte (after newline
normalisation) against a golden produced by the original Java stack.
| Scope | Result |
|---|---|
| Headline results — WD, PoI, Curitiba × GrAnt, rGrAnt × 30 seeds | 180 / 180 byte-identical |
| Full multi-seed sweep — 2,775 runs over 195 configurations (every router variant; buffer, host-count and wait-time sweeps; all 30 seeds) | 2,774 / 2,775 byte-identical |
Unit tests (cargo test --release) |
50 passing |
The sweep was executed both locally and on a Linux Azure VM fleet; the Rust port produces identical output on Windows and Linux.
The single exception. Vars_POI12M_WaitTime_120hosts_30seeds, seed 7345, differs by 0.0001
in exactly one derived statistic — hopcount_avg: FR (1,2312 against the golden's 1,2313).
Every physical counter (created, delivered, relayed, dropped, aborted, removed, ant relays) is
identical, and the port yields the same value on Windows and Linux. The report-aggregation code and
the String.format("%.4f") HALF_UP rounding are both verified byte-identical to Java (see
src/sim/report.rs), and summing the integer hop counts as f64 is exact and order-independent.
The FR hop-count list is built from an overwrite map keyed by message id (last write wins), so
the difference is a rare same-tick event-ordering snapshot for a single message. It changes no
physical count and no aggregate the dissertation reports.
The empty-golden finding. 40 of the archived goldens were 0-byte. GrAnt sets
java.util.Arrays.useLegacyMergeSort=true in every router's initVariaveis() and then sorts with an
intentionally non-transitive comparator (OrdenaTupla/OrdenaMensagens). Java 6–8 honour the flag —
legacy merge sort, no contract check — while Java 9+ ignore it and always use TimSort, which
aborts with "Comparison method violates its general contract!". The goldens had therefore never
regenerated on a modern JDK. Re-running the original ONE simulator under Java 8 (jdk1.8.0_202)
reproduces them faithfully; all 40 were regenerated and validated byte-identical to the Rust port,
which ports the legacy merge sort exactly (src/java/sort.rs). Regeneration is scripted in
tools/regen_empty_goldens.py.
- Rust (stable, 2021 edition or newer) —
cargo buildis the only build step. - Windows PowerShell for the parity harness scripts.
- ~12 GB RAM for the Curitiba scenario at high parallelism.
cargo build --release
cargo test --release # 50 tests# One seed per configuration - 6 runs, ~35 min
.\tools\run_headline.ps1
# Full dissertation depth - 30 seeds x 6 configurations = 180 runs, ~8 h
.\tools\run_headline.ps1 -SeedMode all -Parallel 10Each run is compared against goldens/<config>/ and the script prints
ALL BYTE-IDENTICAL on success.
tools\run_parity2.ps1 runs an arbitrary set of configurations. Each -Configs entry is
label|paramDir|goldenDir, with paths relative to the repository root.
# WD buffer sweep, GrAnt vs rGrAnt at 4 MB (Section 6.1)
.\tools\run_parity2.ps1 -SeedMode all -Parallel 10 -Ticks 800000 -Configs @(
"WD4M_GrAnt|params\GrAnt_WD4M_Seeds30|goldens\GrAnt_WD4M_Seeds30",
"WD4M_rGrAnt|params\Vars_WD4M_339hosts_30seeds|goldens\Vars_WD4M_339hosts_30seeds"
)-Ticks is the scenario's endTime in simulated seconds and must match the scenario:
| Scenario | -Ticks |
|---|---|
| WD | 800000 |
| PoI (incl. wait-time variant) | 800000 |
| Curitiba | 10800 |
Parameter directories follow a stable naming convention:
| Table | Parameter directories |
|---|---|
| WD buffer sweep (6.1) | GrAnt_WD{4,6,8,10,12,14,16}M_Seeds30, Vars_WD{...}M_339hosts_30seeds |
| WD node sweep (6.2) | {GrAnt,Vars}_WD10M_{75,142,339,594,1104}hosts_30seeds |
| PoI buffer sweep (6.3) | {GrAnt,Vars}_POI{4,6,8,10,12,14,16}M_120hosts_30seeds |
| PoI node sweep (6.4) | {GrAnt,Vars}_POI8M_{12,36,120,240,360}hosts_30seeds |
| PoI wait-time variant | same names with _WaitTime_ |
| Curitiba (6.5) | {GrAnt,Vars}_Curitiba_30seeds |
| Tuning tables (Section 5) | GrAntPlus*, GrAntAllVars* — one directory per Var × NC threshold |
Vars_* are the rGrAnt configurations; GrAnt_* are the baseline.
.\target\release\grant_report.exe `
params\Vars_Curitiba_30seeds\Vars_Seed2045.txt ` # parameter file
data\HelsinkiMedium\roads.wkt ` # fallback map (ignored when the param file sets its own)
. ` # base directory for relative data/ paths
10800 ` # endTime in simulated seconds
out.txt # report destinationCompare against goldens\Vars_Curitiba_30seeds\Curitiba_Vars_Seed2045_MessageStatsReport.txt.
Every multi-seed directory contains one parameter file and one golden report per seed, selected by
MovementModel.rngSeed:
2, 2045, 1210, 7361, 4264, 6080, 6806, 972, 8187, 659,
5088, 6772, 2606, 7345, 3921, 6088, 7204, 2061, 4494, 99,
7485, 6681, 5057, 399, 5232, 3396, 1936, 3994, 6771, 482
Nine binaries under src/bin/ dump intermediate simulation state, and exist so that any future
parity regression can be bisected to its first divergent stage rather than debugged from the final
report. Each has a matching Java probe in validation/:
| Binary | Dumps |
|---|---|
rng_dump |
java.util.Random draw sequences |
wkt_dump |
Parsed WKT geometry |
map_dump |
The assembled road-network graph |
positions_dump |
Initial host placement |
positions_at_time_dump |
Host positions at an arbitrary time |
events_dump |
The message-generation event schedule |
connectivity_dump |
The contact trace |
javamap_dump |
java.util.HashMap iteration order |
grant_report |
The final MessageStatsReport |
The full dissertation depth is 2,775 runs over 195 configurations (every router variant ×
every scenario × 30 seeds). tools\parity.py is the portable twin of run_parity2.ps1: it runs
each (parameter file, golden) pair, compares the report byte-for-byte, and prints one
pass/fail line per run, so verification is inline — no need to fetch reports to know the
result. It adds --shard I/N to split the cost-sorted work list across machines.
# On any Linux box with the repo built (cargo build --release):
python3 tools/parity.py --seed-mode all --list # 2775 runs, total cost 640.9
python3 tools/parity.py --seed-mode all --parallel $(nproc) --skip-headlinetools\azure\fleet.ps1 drives a fleet of Azure Linux VMs that run the sweep in parallel. Nodes may
differ in size: each node's vCPU count is read back from Azure and turned into that many shares of
--shard, so a 16-core node receives four times the work of a 4-core node and they finish together.
.\tools\azure\fleet.ps1 -Action create -Size Standard_F16als_v7 -Count 1 # or -Spot
.\tools\azure\fleet.ps1 -Action setup # upload the source tree, build on each node
.\tools\azure\fleet.ps1 -Action run # start each node's cost-weighted shard
.\tools\azure\fleet.ps1 -Action wait # block until every shard finishes, then summarise
.\tools\azure\fleet.ps1 -Action fetch # optional: archive the reports locally
.\tools\azure\fleet.ps1 -Action delete # tear the fleet down — do this when doneThe workload is cost-bound, not count-bound: most runs are trivially cheap while a minority
(Curitiba, PoI 360/480-host) dominate, so runtime tracks total cost (≈ 640.9 units) rather than the
run count. On the Fasv7 family (AMD EPYC Turin, one thread per core) it calibrates to ≈ 9.6
min/cost-unit, so 20 cores complete the sweep in ≈ 5 h. Azure Batch was tried first and abandoned:
its pool allocation failed repeatedly with AllocationFailed, while plain VMs of the same size
allocate immediately.
Regenerating the empty goldens (Java 8). 40 goldens ship as 0-byte files because they only reproduce under Java 6–8: GrAnt relies on
java.util.Arrays.useLegacyMergeSort=true, which Java 9+ ignore in favour of TimSort — and TimSort aborts on GrAnt's non-transitive comparator (see §8).python tools\regen_empty_goldens.py --tier {cheap,medium,heavy}re-runs those cases with the original ONE simulator under Java 8 (jdk1.8.0_202) and drops the reports back intogoldens/.
visualizer/ is a small, browser-based companion tool that illustrates the three headline
scenarios interactively. It is a self-contained TypeScript re-implementation of the routing —
not the byte-identical simulator — whose only job is to make the protocols' behaviour visible:
nodes and buses move on the real maps, contacts open and close, and messages hop toward their
destinations while live metrics update.
Three presets are included, each in its lighter (non-compute-intensive) variant so it runs smoothly in a browser tab, and each runnable with any of the four routers — rGrAnt, GrAnt, Epidemic and PRoPHET:
| Preset | Mobility | Nodes |
|---|---|---|
| WD — Working Day | Helsinki road graph, 7 districts × 10 workers + 8 buses | 78 |
| PoI — Points of Interest | 4 communities × 30 walkers, weighted destinations | 120 |
| Curitiba — Bus routes | 16-line subset of the 267-route network, 2 buses/line | 32 |
Run it:
cd visualizer
npm install
npm run dev # then open http://localhost:5173Pick a scenario and a router, press Start, and use the sidebar to tweak seed, TX range, buffer,
message rate and TTL. The map shows nodes/buses moving, active contacts, and nodes carrying a
message (yellow); the right panel tracks delivery ratio, overhead, latency and hops live. See
visualizer/README.md for the architecture and the rGrAnt summary.
The Curitiba preset builds its graph directly from the bus-route WKT files (
visualizer/public/curitiba/), deliberately avoiding the 73 MBCombined.wktused by the heavy study runs. Because the tool is an independent re-implementation with its own RNG, it is not byte-identical to the Rust port or the Java originals — the parity results in this README come from the Rust port insrc/, while the visualizer is for intuition and demonstration.
.
├── Cargo.toml
├── src/
│ ├── lib.rs
│ ├── java/ bit-exact java.util primitives (rng, sort, hash_map, hashtable)
│ ├── sim/ The ONE core: settings, coord, message, connection,
│ │ events, report, world
│ ├── movement/ map, wkt, path, working_day, map_based, host, build,
│ │ initial_positions
│ ├── routing/ grant.rs — GrAnt, the Vars, and rGrAnt's rule layer
│ ├── scenario/ run.rs (main loop), connectivity.rs
│ └── bin/ 9 dump tools (sources; Cargo compiles them to target/release)
├── data/ maps — HelsinkiMedium (WD/PoI) and Curitiba_full (267 bus routes)
├── ee/ external event trace driving the Curitiba scenario
├── params/ 230 scenario configuration directories
├── goldens/ 246 directories of Java MessageStatsReport ground truth
├── tools/ run_headline.ps1, run_parity2.ps1 and helpers
├── validation/ Java probe programs used to generate the ground truth
├── visualizer/ browser-based TS visual tool for WD / PoI / Curitiba
└── docs/ fuzzyant.md (the FuzzyAnt study) + images referenced by this README
Module names mirror The ONE's Java packages, so the port maps onto the original source
one-to-one. src/bin/ is Cargo's convention for sources that compile into binary targets —
the executables themselves land in target/release/.
- The ONE simulator — Aalto University / Nokia Research Center, GPLv3.
- GrAnt — Vendramin et al.
- rGrAnt, the Vars, and the experiment configurations — Mendes (2018), the dissertation above.
- Golden reports in
goldens/were produced by the original instrumented Java implementation and are committed verbatim as ground truth.
Research and educational use.
