|
AntHocNet 2.0.0
Paper-faithful ant-colony ad hoc routing: the shared core and its adapters
|
AntHistoryTracker: (src, seqNum) duplicate detection. More...
Namespaces | |
| namespace | core |
AntHistoryTracker: (src, seqNum) duplicate detection.
The visited-node stack an ant carries.
Simulator-agnostic fundamental types for the AntHocNet algorithm core.
Single-source shortest paths over an explicit graph (issue #296 item 1, #216).
RouteDecision: the verb the algorithm hands back to an adapter.
Ports the simulator adapters implement.
PheromoneTable: per-node routing state.
PheromoneEngine: the reinforcement / evaporation math, lifted out of the NS-2 AntNest class so both simulators share identical routing dynamics.
Name -> ILinkMetric registry (issue #143, epic #142).
ILinkMetric: the strategy that turns a backward ant's path observation into a pheromone value (Eq.2).
Tunable AntHocNet parameters.
AntRouterLogic: the shared, simulator-agnostic AntHocNet state machine.
Canonical, simulator-agnostic wire format for an AntMessage.
AntMessage: the simulator-agnostic, value-type representation of an ant.
Replaces the unbounded std::set<AntHistory> that lived in AntNest. The set grew for the entire run; here it is capped (FIFO eviction) so memory stays bounded on long simulations.
Both adapters convert their on-the-wire packet header to/from this struct (see anthocnet/core/ant_message_codec.h for the canonical wire format), so the algorithm never touches simulator memory directly.
Both adapters reuse this so an NS-2 and an NS-3 node would interpret the same bytes identically (and so the round-trip is unit-testable without a simulator). Encoding is little-endian, length-prefixed for the variable arrays. The NS-2 PacketHeaderClass and the NS-3 ns3::Header both delegate their (de)serialization to these functions.
This is the former AntNest + the decision parts of the AntHocNet agent, with all NS-2 packet/scheduler coupling removed. It owns a node's routing state (pheromone table, dedup history, sequence counter) and turns received ants and local data demands into RouteDecisions for the adapter to execute.
It is deliberately free of I/O: time and randomness come through ports, and outputs are returned as values. That makes the routing behaviour identical across NS-2 and NS-3 and testable on its own.
These were compile-time constants in AntHocNetUtils (ALFA/BETA/GAMA/...). Promoting them to a value type lets each adapter expose them as TCL binds (NS-2) or TypeId attributes (NS-3) without touching the algorithm code. The defaults reproduce the original constants.
Extracting it from advanceBackAnt makes the pure core the natural home for both the canonical metric and pluggable research metrics (fuzzy / energy / QoS), selected by config, without touching the state machine (item 16, ADR-0003).
The metric must be pure: it reads only the observation, never the simulator, clock, or RNG. Adapter-supplied signals (energy, SNR, queue occupancy) enter through LinkObservation, keeping the core free of simulator dependencies.
The ILinkMetric seam exists but is unreachable: both adapters pass nullptr, so ClassicMetric is the only formula that can ever run. Selecting a metric needs a name -> instance mapping, and that mapping belongs in the core rather than in each adapter — otherwise NS-2 and NS-3 grow two switch statements that can disagree about what a name means.
Lookup hands back a NON-OWNING pointer/reference to a function-local static instance, one per registered name, alive for the whole process. The caller must not delete it and must not assume any per-node state.
This is safe because a metric is a stateless strategy object: pheromone() is const and reads only the LinkObservation it is handed, so a single instance is shared by every node in a simulation without interference. It is also the shape AntRouterLogic already wants — its constructor takes a bare const ILinkMetric* and stores it without owning it, so a registry pointer drops straight in and can never dangle. Handing out unique_ptr/shared_ptr instead would push an ownership refactor through AntRouterLogic and both adapters, buying nothing for an object that holds no state.
The default path does not go through here at all: with no explicit selection, AntRouterLogic still falls back to its own ClassicMetric and behaviour is byte-identical to before this file existed. An unknown name is a loud error — never a quiet fall back to "classic" — because a mistyped metric would otherwise produce plausible-looking results from the wrong protocol.
Operates on a PheromoneTable by reference; carries no simulator state.
Holds the regular and virtual pheromone maps keyed by (neighbor, destination), the neighbour set, and the per-table destination sets. This is a near-verbatim port of the original NS-2 class with two changes:
The core depends only on these interfaces, never on a concrete simulator. NS-2 implements them over Scheduler/Random/Node; NS-3 over Simulator::Now/UniformRandomVariable/Ipv4 interfaces.
AntRouterLogic is pure: it never sends a packet or schedules a timer itself. It returns RouteDecisions and the adapter carries them out against its simulator (unicast to nextHop, link-layer broadcast, enqueue pending, deliver to the local transport, or drop).
This is NOT part of the AntHocNet protocol and no protocol code calls it. It is the computation behind the oracle control arm — the global-knowledge upper bound the benchmark suites lack: Dijkstra over the ground-truth topology, replayed as an ns-3 routing protocol (ns3/oracle/). It lives in core/ for one reason: it is simulator-agnostic logic, and AGENTS.md rule 7 wants logic covered by a core unit test rather than only by a simulator run.
computeFrom(source) runs Dijkstra and stores, for every node, the distance from the source and the first hop on a shortest path — the neighbour the source must hand the packet to. First-hop-from-source is exactly what a routing table needs, and propagating it during relaxation (firstHop[v] = (u == source) ? v : firstHop[u]) avoids walking a predecessor chain per destination.
Equal-cost shortest paths are ubiquitous in the topologies this serves (a +Grid ISL torus is almost nothing but ties, and a dense wifi field has many). An arbitrary tie-break would make the oracle's routes depend on container iteration order, i.e. on the ns-3 build — the reproducibility failure #352 was about, in a different guise. So the tie-break is part of the contract: among equal-cost shortest paths the one with the numerically smallest first hop wins, which makes the whole next-hop table a pure function of the edge set.
Weights are non-negative doubles; the oracle uses 1.0 per edge (hop count), which is what makes "the oracle's hop count is a lower bound on every protocol's" an assertion rather than an expectation.
Nothing in core/ may include an NS-2 or NS-3 header. Addresses are modelled with a plain integer so both simulators (whose own address types are integral) can pass values straight through their adapters.
In the original NS-2 code this was a header-resident AntTimeEntry** malloc'd array of heap pointers, which leaked, double-freed on broadcast (the same header memory was reused by NS-2's packet pool), and serialized as sizeof(pointer) bytes regardless of the real path length. Modelling it as a value-type vector of PODs removes that entire class of bug.