Chart 1 of 4 · ccm-algorithms
Drop and Freeze (legacy baseline)
Three phases per round, over the agents standing on each node. Unsettled agents fan out one per port to look at a neighbour, come back carrying one bit — does it have a settled agent — and then one of them settles where nobody has while the rest move on together through a port a probe reported empty. When every port has been tried the group backtracks through the settled agent's parent port, so the walk is a DFS whose tree is held by the settled agents.
It is meant for agents that all start on one node: from there, with no more agents than nodes, it always finishes. The code also accepts agents on several start nodes (as the Python reference did), but then a group can get stuck for good — see the notes.
canonicalize_ports is a compatibility decision rather than an algorithmic one: the Python reference replaced any supplied local port labels with canonical sorted-neighbour ones, so this port does too. It means Drop-and-Freeze ignores the port assignment the experiment asked for.
Notes
- Several start nodes can stall the run. "Empty" is read during the probe, and a start node's agent only settles later in the same round. So a group can move into another group's start node just as it becomes owned. Nobody settles there for this group, so it has no parent port to go back by: once that node's ports are used up, the group stays for good, and the run ends at the round limit with empty nodes left. Example: tree 0–1, 0–2, 1–3 with agents starting on 0, 0, 0 and 2 — two agents settle, two are stuck on node 2. The Python reference behaves the same way.
- No empty port does not always mean backtrack: the group stays and probes again next round whenever ports remain after the cursor that this round's probers (counting an agent that just settled) did not cover.
- "Empty" is read during the probe. In the same round a group standing on that neighbour may settle one of its agents there, so a group can move into a node that has just become owned.
validate_stateruns before the first round and after each of the three phases; the chart shows it once. A bad input, a failed traversal or a broken invariant returns an error instead of a result.- Round limit: the rounds run as
1..=round_limit. A run that settles in exactly the last round still ends Completed.
Chart 2 of 4 · ccm-algorithms
Help by Scouts (legacy baseline)
Also a DFS, but settled agents help. A settled agent whose node can spare it vacates and travels with the group as a scout, so more agents share the probing of a node's ports. When nobody is left unsettled, the scouts walk the tree depth first and each drops back onto its own node when the group reaches it, so the run ends with one settled agent on each occupied node.
Like the Python reference it is rooted: placements on several start nodes are not supported (a test records the reference's failure on them).
Notes
- The order inside a step matters:
can_vacatereads the node type thatupdate_node_typehas just set from the probe reports. - An owner that vacates joins this step's move and the next step's probe — not the probe already done.
- Only when
can_vacatesays the owner stays can it have released the parent node's owner — the root's included — into the group. - A scout is not always out until the retrace: if the group comes back to its node and it may no longer leave, it settles back in there.
- Every step spends logical rounds through
tick(), and so does each move of the retrace; the first one past the limit ends the run as RoundLimitReached. Backtracking at the root is an error (MissingParent).
Chart 3 of 4 · ccm-algorithms
P1Tree
DFS_P1Tree from Pattanayak, Kshemkalyani, Kumar, Molla and Sharma, Optimal Dispersion Under Asynchrony. A DFS that builds a port-one tree: in a finished tree every vertex has an incident tree edge carrying port 1 at one of its ends. Among the ports probed so far, edges are preferred tp1 > t11 ~ t1q > tpq; a node that would be left without a port-1 tree edge is parked as partiallyVisited until its port-1 neighbour comes to claim it.
Two details carry the O(k) bound, and losing either makes the cost scale with the graph instead of with the agents:
neighbourhood_searchstops at the first batch that finds somewhere to go. Remark 1 bounds the search by the agent count, not the degree: at mostknodes are ever occupied, so when the degree allows, an empty neighbour turns up within the firstk-1probed ports at the root andk-2elsewhere.traversestops at dispersion. Section 5: "the process continues until no unsettled agents remain". The tree at that moment need not yet be a P1Tree — a parked node may still hold atpqparent edge — and that is fine, because dispersion is the goal.
Notes
- Because the search stops at the first batch that finds something, the edge preference only applies among the ports probed so far: a port-1 edge on a port not yet probed can lose to an earlier batch's choice.
- A node reached by a
tpqedge is parked only when its next choice would be anothertpqedge or it has no candidate at all; if a port-1 edge turns up among the ports probed, it advances instead. - After a move,
apply_can_vacateandapply_parent_vacateboth look at the node just left: it is the new node's parent. - A leaf (no ports but the parent's) or a search with nobody to probe skips the probe steps: the search returns nothing and the node goes straight to the "none, all probed" branch.
- "Every agent settled?" is the stop rule of
simulate()(Stop::AtDispersion).simulate_with_stopwithStop::AtFullTreeruns on instead, and its only Completed exit is the root being popped (it can still stop at the round limit).
Chart 4 of 4 · ccm-algorithms
RootedDisp
RootedDisp from Sudo, Shibata, Nakamura, Kim and Masuzawa, Near-linear Time Dispersion of Mobile Agents, Section 3 and Algorithm 1: the rooted HEO-DFS. The agent with the largest ID leads a depth-first search, and the smallest-ID explorer settles at each node it reaches. Every box that moves agents is exactly one synchronous step, committed through apply_move_intents; settling takes none. rooted-disp.md has the time model, the decisions taken where Algorithm 1 leaves a choice, and two worked traces.
What makes the search fast is probe_pass. Every agent at the node except its settler takes one port; a settler found at home comes back as a helper and probes in the next pass too, so the probers double until one finds an unsettled neighbour: O(log τ) passes per probe and O(k log τ) steps in all. probe resets next and checked on every call, so no cursor survives from an earlier visit.
Notes
- Rooted only: every agent must start on one node. Any other placement is refused with
NonRootedStartbefore the first step. - Two choices Algorithm 1 leaves open (rooted-disp.md, decisions 1 and 2): the helpers walk home in a step of their own, taken only when there are helpers; and probers take ports in ascending ID, the smallest unsettled port winning.
- A retreat at the root would mean the root's component has fewer nodes than agents; the run returns
RootExhaustedinstead of looping. - Round limit:
commitchecks it before every step, so a run can stop between the passes of a probe and returns its partial state as RoundLimitReached.