The solver finds profitable cycles (negative cycles in log-space) using the Shortest Path Faster Algorithm (SPFA) — an optimized variant of Bellman-Ford.
- SPFA relaxes edges only for nodes that can potentially improve a shortest path, skipping redundant computations.
- Much faster than standard Bellman–Ford on sparse or semi-sparse graphs.
- Works on additive weights, so multiplicative rates (e.g., exchange rates) must be transformed as:
[ w = -\ln(r) ]
This converts the multiplicative product of rates into a sum of edge weights.
A profitable cycle (∏ rᵢ > 1) becomes a negative cycle in log-space (∑ -ln(rᵢ) < 0).
The graph is stored in Compressed Sparse Row (CSR) format:
| Field | Description |
|---|---|
node_pointers |
Index ranges for each node’s outgoing edges |
edge_targets |
Destination nodes of each edge |
edge_weights |
Transformed weights -ln(rate) |
edge_source_by_index |
Reverse mapping for O(1) lookup of an edge’s source node |
Advantages:
- Fast iteration over outgoing edges.
- Better cache locality.
- O(1) edge-to-source lookup for cycle reconstruction.
- Supports dynamic batch updates with amortized rebuild cost.
The
edge_source_by_indexenables quick tracing during cycle reconstruction. New edges are buffered and rebuilt only when the batch exceeds a threshold (rebuild_limit).
SPFA iterates over the CSR graph, relaxing edges and tracking predecessor edges (pred_edge_idx) for each node.
Algorithmic Steps:
-
Initialization:
All nodes start withdistance = 0to detect cycles even in disconnected components. -
Relaxation:
Propagate relaxations through a queue. -
Cycle Detection:
If a node’s relaxation count ≥hop_cap(typically |V|), SPFA detects a negative cycle and reconstructs it.
Output:
path: Sequence of(u, v, rate)edges in the cyclerates: Original rates of edges in the cyclelog_rate_sum: Sum of transformed weights along the cycle
Reconstruction is done in two stages:
-
Walk Backward:
Walk back |V| steps through predecessors to ensure entry into the cycle. -
Loop Reconstruction:
Trace edges until returning to the same node, forming a complete cycle.
Guarantees the recovered path represents the actual negative cycle, not just a downstream node.
arb-solver supports a robust async pipeline for continuous monitoring:
| Component | Role | Mechanism |
|---|---|---|
Producer (SimStreamer, CsvStreamer) |
Ingests or generates edge batches | Uses bounded MPSC channels for backpressure |
| Writer | Applies updates to the CSR graph | Implements a two-phase lock on Arc<RwLock<GraphCSR>> |
| Searcher | Detects arbitrage | Acquires a snapshot of the graph (O(1) clone) and runs SPFA asynchronously |
Two-Phase Locking Strategy:
- Phase 1 (Extract): Briefly lock to extract pending updates.
- Unlocked Work: Sorting and deduplication occur without holding the lock, minimizing reader blocking.
- Phase 2 (Commit): Briefly lock to commit rebuilt CSR arrays.
This ensures continuous reading with minimal blocking while handling dynamic updates efficiently.
- Optimized SPFA for sparse and semi-sparse graphs
- CSR layout for fast traversal and low memory overhead
- Negative cycle reconstruction with O(1) edge-to-source lookup
- Dynamic updates with batching, amortizing rebuild costs
- Fully async-compatible pipeline with backpressure