# ACO Multi-Objective Scoring & Tri-Layer Stigmergy

> Detailed mechanics of the decentralized Ant Colony Optimization engine, the 8-factor score formulation, Boltzmann Softmax action selection, tri-layer pheromone evaporation dynamics, and recursive Bayesian target probability updates.

- Repository: ivaziz/swarm_car_search
- GitHub: https://github.com/ivaziz/swarm_car_search
- Human wiki: https://grok-wiki.com/public/wiki/ivaziz-swarm-car-search-66a3d00cb601
- Complete Markdown: https://grok-wiki.com/public/wiki/ivaziz-swarm-car-search-66a3d00cb601/llms-full.txt

## Source Files

- `swarm/aco/aco_engine.py`
- `swarm/aco/task_scorer.py`
- `swarm/aco/aco_config.py`
- `swarm/pheromone/pheromone_map.py`
- `swarm/pheromone/target_probability_map.py`
- `configs/aco_params.yaml`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [swarm/aco/aco_config.py](swarm/aco/aco_config.py)
- [swarm/aco/aco_engine.py](swarm/aco/aco_engine.py)
- [swarm/aco/task_scorer.py](swarm/aco/task_scorer.py)
- [swarm/pheromone/evaporation_model.py](swarm/pheromone/evaporation_model.py)
- [swarm/pheromone/pheromone_map.py](swarm/pheromone/pheromone_map.py)
- [swarm/pheromone/pheromone_updater.py](swarm/pheromone/pheromone_updater.py)
- [swarm/pheromone/target_probability_map.py](swarm/pheromone/target_probability_map.py)
- [swarm/task_allocation/search_history.py](swarm/task_allocation/search_history.py)
- [swarm/task_allocation/frontier_task.py](swarm/task_allocation/frontier_task.py)
- [swarm/task_allocation/allocation_strategy.py](swarm/task_allocation/allocation_strategy.py)
- [configs/aco_params.yaml](configs/aco_params.yaml)
</details>

# ACO Multi-Objective Scoring & Tri-Layer Stigmergy

The Ant Colony Optimization (ACO) allocation engine in `swarm_car_search` coordinates decentralized frontier selection across autonomous search robots. In GPS-denied or bandwidth-constrained urban search operations, relying on centralized auctioneers or global scheduling causes single points of failure and communication bottlenecks. Instead, this system coordinates agents through **stigmergy**—indirect coordination through environmental modifications—combining multi-layer synthetic pheromone fields, recursive Bayesian belief distributions, and spatio-temporal search memories.

At the core of each agent's decision-making process is an 8-factor multi-objective attractiveness evaluator (`TaskScorer`) coupled with a scale-invariant Boltzmann Softmax action selector (`ACOEngine`). This architecture balances pure geographic exploration, fast vehicle candidate verification, travel distance efficiency, peer congestion dispersion, and vehicle battery health, preventing swarm clustering and redundant searches.

```mermaid
flowchart TD
    subgraph StigmergicBelief["Stigmergic & Spatial Belief State"]
        direction TB
        PM["PheromoneMap<br/>(Tri-Layer Continuous Grid)"]
        TPM["TargetProbabilityMap<br/>(Log-Odds Bayesian Belief)"]
        SSM["SearchSpatialMemory<br/>(Dwell & Sighting Bins)"]
        Peers["SwarmState Peers<br/>(Active Commitments)"]
    end

    subgraph Inputs["Task & Robot Inputs"]
        direction TB
        Tasks["Candidate SearchTasks<br/>(Status: PENDING)"]
        Pose["Robot Pose2D & Battery"]
    end

    subgraph ScoringEngine["Multi-Objective Task Scoring (TaskScorer)"]
        direction TB
        Factors["Compute 8 Normalized Factors:<br/>• Info Gain (+) • Target Prior (+)<br/>• Historical Value (+) • Positive Pheromone (+)<br/>• Distance Cost (-) • Peer Congestion (-)<br/>• Collision Avoidance (-) • Redundancy Penalty (-)"]
        NetUtil["Net Utility Synthesis<br/>max(0.05, 1.0 + ΣPos - ΣNeg)"]
        ClassRatio["Classical Ratio Integration<br/>(τ^α · Urgency^δ) / Cost^ε"]
        BatMod["Battery Modulation<br/>0.4 + 0.6 · BatteryLevel"]
        Factors --> NetUtil
        NetUtil --> ClassRatio
        ClassRatio --> BatMod
    end

    subgraph SelectionEngine["Action Selection (ACOEngine)"]
        direction TB
        Gate{"Selection Gate"}
        DetChoice["Deterministic Top-1 Choice"]
        EpsGreedy["Epsilon-Greedy Random Draw"]
        Softmax["Boltzmann Softmax Distribution<br/>Normalized Logits: (Score/Max - 1) / Temp"]
        Sample["Cumulative Probability Sampling"]
        
        Gate -->|deterministic=True or Temp ≤ 0.01| DetChoice
        Gate -->|rand < epsilon_greedy| EpsGreedy
        Gate -->|Stochastic Exploration| Softmax
        Softmax --> Sample
    end

    StigmergicBelief --> Factors
    Inputs --> Factors
    BatMod --> Gate
    Sample --> Selected["Assigned SearchTask"]
    DetChoice --> Selected
    EpsGreedy --> Selected
```

---

## 8-Factor Multi-Objective Scoring Formulation

The candidate task attractiveness evaluator is implemented in `TaskScorer.explain_score()` and `TaskScorer.compute_score()`. Each candidate frontier task $i$ is scored relative to an evaluating robot pose $\mathbf{x}_{\text{robot}}$ by synthesizing eight distinct positive rewards and negative cost penalties, modulated by classical ACO pheromone-heuristic ratios and robot battery status.

### Mathematical Formulation

The final attractiveness score is computed as:

$$\text{FinalScore}(i, \mathbf{x}_{\text{robot}}) = \text{NetUtility}_i \times \text{ClassicalRatio}_i \times \text{BatteryFactor}$$

Where:

1. **Net Utility Synthesis**:
   $$\text{NetUtility}_i = \max\left(0.05, \; 1.0 + \sum_{p \in \text{Pos}} w_p \cdot f_p(i) - \sum_{n \in \text{Neg}} w_n \cdot f_n(i)\right)$$

2. **Classical ACO Pheromone-Distance Ratio**:
   $$\text{ClassicalRatio}_i = \frac{(\tau_i)^{\alpha \cdot 0.5} \times (\text{Urgency}_i)^\delta}{(\text{Cost}_i)^{\epsilon \cdot 0.5}}$$
   where $\tau_i = \max(0.01, \text{pheromone}_{\text{success}})$, $\text{Cost}_i = \max(d_{\min}, \|\mathbf{x}_{\text{task}} - \mathbf{x}_{\text{robot}}\|)$, and exponents $\alpha, \delta, \epsilon$ are defined in `ACOWeights`.

3. **Battery Factor**:
   $$\text{BatteryFactor} = \max(0.2, \; \min(1.0, \; 0.4 + 0.6 \cdot \text{BatteryLevel}))$$

```python
# File: swarm/aco/task_scorer.py:121-132
net_utility = max(0.05, 1.0 + positive_components - negative_components)

tau_val = max(0.01, tau)
cost_val = max(self.config.min_distance_offset, euclidean_dist)
classical_ratio = ((tau_val ** (w.alpha * 0.5)) * (urgency ** w.delta)) / (cost_val ** (w.epsilon * 0.5))

battery_factor = max(0.2, min(1.0, 0.4 + 0.6 * battery_level))

final_score = net_utility * classical_ratio * battery_factor
```
Sources: [swarm/aco/task_scorer.py:106-133](), [swarm/aco/aco_config.py:10-32]()

---

### The 8 Scoring Factors Breakdown

The scoring pipeline decomposes task features into eight normalized factors in $[0.0, 1.0]$:

| # | Factor | Symbol | Weight Param | Type | Source & Normalization Formula |
|---|---|---|---|---|---|
| **1** | **Information Gain** | $f_{\text{ig}}$ | `w_information_gain` (1.0) | Reward | $\min\left(1.0, \max\left(0.05, \frac{\text{task.size}}{\text{max\_frontier\_size}}\right)\right)$ where $\text{max\_frontier\_size} = 30.0$ |
| **2** | **Target Prior** | $f_{\text{tp}}$ | `w_target_prior` (1.4) | Reward | $\text{TargetProbabilityMap.get\_box\_probability}(\text{bbox}, \text{mode}=\text{"max"})$ or task prior $P \in [0.01, 1.0]$ |
| **3** | **Historical Value** | $f_{\text{he}}$ | `w_historical_value` (0.8) | Reward | `SearchSpatialMemory.get_historical_value(x, y)`: returns $1.0$ for fresh cells, decayed by $e^{-0.40 \cdot \text{visits}}$ |
| **4** | **Exploration / Pheromone** | $f_{\text{fs}}$ | `w_exploration_value` (0.9) | Reward | $\min\left(1.0, \max\left(0.05, \frac{\tau_{\text{SUCCESS}}}{20.0}\right)\right)$ aggregated over task bounding box |
| **5** | **Distance Cost** | $f_{\text{dist}}$ | `w_distance_cost` (0.7) | Penalty | $\min\left(1.0, \max\left(0.01, \frac{\|\mathbf{x}_{\text{task}} - \mathbf{x}_{\text{robot}}\|}{\text{max\_norm\_dist}}\right)\right)$ where $\text{max\_dist} = 70.0\text{m}$ |
| **6** | **Congestion Cost** | $f_{\text{cong}}$ | `w_congestion_cost` (1.0) | Penalty | $\min\left(1.0, \frac{\text{congestion\_count}}{3.0}\right)$ based on peer robot task commitments |
| **7** | **Collision / Avoidance Risk** | $f_{\text{col}}$ | `w_collision_risk` (0.6) | Penalty | $\min\left(1.0, \max\left(0.0, \frac{k_{\text{AVOIDANCE}}}{15.0}\right)\right)$ aggregated from `AVOIDANCE` layer |
| **8** | **Redundancy Penalty** | $f_{\text{red}}$ | `w_redundant_penalty` (0.8) | Penalty | `SearchSpatialMemory.get_redundancy_penalty(x, y)`: $1.0 - e^{-0.35 \cdot (\text{visits}-1)}$ for zero-candidate sweeps |

### Dynamic Urgency Multiplier

Task priority is modulated dynamically by task classification (`TaskType`):
- `TaskType.TARGET_CONFIRMATION`: Multiplier **3.5** (immediate verification of high-probability vehicle detections)
- `TaskType.VEHICLE_INVESTIGATION`: Multiplier **2.2** (inspection of visual car candidate detections)
- `TaskType.EXPLORATION`: Multiplier **1.0** (baseline frontier mapping)

$$\text{Urgency} = \max(0.1, \; \text{task.priority} \times \text{UrgencyMultiplier})$$

Sources: [swarm/aco/task_scorer.py:52-105](), [swarm/aco/aco_config.py:22-32](), [swarm/task_allocation/frontier_task.py:21-26]()

---

## Boltzmann Softmax Action Selection

Action selection is performed in `ACOEngine.select_task()`. Rather than purely greedily selecting the task with the highest attractiveness score (which causes the entire swarm to converge on the same cluster), the engine employs a stochastic Boltzmann Softmax distribution with an $\epsilon$-greedy exploratory branch.

```
Candidate PENDING Tasks
         │
         ▼
 ┌───────────────┐      Yes
 │ deterministic?│─────────────► Return Highest Scored Task
 └───────┬───────┘
         │ No
         ▼
 ┌───────────────┐      Yes
 │ rand < ε_opt? │─────────────► Uniform Random Task Choice
 └───────┬───────┘
         │ No
         ▼
 ┌───────────────┐      Yes
 │ Temp ≤ 0.01?  │─────────────► Return Highest Scored Task
 └───────┬───────┘
         │ No
         ▼
Compute Scale-Invariant Logits:
logit_i = ( (score_i / max_score) - 1.0 ) / Temperature
         │
         ▼
Probabilities:
P_i = exp(logit_i) / Σ exp(logit_j)
         │
         ▼
Sample Task via Cumulative Distribution
```

### Scale-Invariant Softmax Formulation

Standard Softmax $\frac{e^{S_i / T}}{\sum e^{S_j / T}}$ is vulnerable to numerical overflow when scores are large, and underflow when scores are small. `ACOEngine` implements a normalized relative logit transformation:

$$\text{logit}_i = \frac{\frac{S_i}{S_{\max}} - 1.0}{T}$$

Because $\frac{S_i}{S_{\max}} \in (0.0, 1.0]$, every logit is strictly bounded in:

$$\text{logit}_i \in \left[-\frac{1}{T}, \; 0.0\right]$$

The selection probabilities are calculated as:

$$P(i) = \frac{\exp(\text{logit}_i)}{\sum_{j=1}^{N} \exp(\text{logit}_j)}$$

```python
# File: swarm/aco/aco_engine.py:157-165
# Normalized relative logits: (score / max_score - 1.0) / temp
# Guaranteed to be in range [-1/temp, 0], preventing numerical underflow/overflow
weights = [math.exp(((s / max_score) - 1.0) / temp) for s in scores]
total_weight = sum(weights)

if total_weight <= 0.0:
    return scored_tasks[0][0]

probabilities = [w / total_weight for w in weights]
```

### Selection Modes and Edge Cases

1. **Filtering**: Non-pending tasks (`ASSIGNED`, `IN_PROGRESS`, `COMPLETED`, `CANCELLED`) are filtered out prior to scoring.
2. **Deterministic Bypass**: If `config.stochastic.deterministic == True` or $T \le 0.01$, the highest-scoring candidate is chosen directly.
3. **$\epsilon$-Greedy Perturbation**: With probability $\epsilon$ (`epsilon_greedy = 0.05`), a random available pending task is chosen to maintain wide-area frontier dispersion.
4. **Near-Zero Scores**: If $S_{\max} \le 10^{-9}$, Softmax collapses to returning the top-ranked task.

Sources: [swarm/aco/aco_engine.py:103-176](), [swarm/aco/aco_config.py:34-40]()

---

## Tri-Layer Stigmergy & Pheromone Evaporation Dynamics

Synthetic pheromones are maintained on a continuous 2D spatial scalar grid by `PheromoneMap` and updated by `PheromoneUpdater`. Rather than maintaining a single scalar trail, the environment maintains three distinct stigmergic layers running concurrently.

```mermaid
classDiagram
    class PheromoneLayerType {
        <<enumeration>>
        EXPLORATION
        SUCCESS
        AVOIDANCE
    }

    class EvaporationConfig {
        +dict evaporation_rates
        +float min_pheromone = 0.01
        +float max_pheromone = 50.0
    }

    class PheromoneMap {
        +float width
        +float height
        +float resolution
        +dict layers
        +world_to_grid(x, y) Tuple
        +grid_to_world(gx, gy) Tuple
        +get_value(layer, x, y) float
        +set_value_grid(layer, gx, gy, val)
        +get_box_aggregate(layer, min_x, min_y, max_x, max_y, mode) float
    }

    class EvaporationModel {
        +EvaporationConfig config
        +get_decay_factor(layer, dt) float
        +clamp(value) float
    }

    class PheromoneUpdater {
        +PheromoneMap map
        +EvaporationModel evaporation_model
        +deposit(layer, x, y, amount, radius)
        +deposit_exploration(x, y, amount, radius)
        +deposit_success(x, y, amount, radius)
        +deposit_avoidance(x, y, amount, radius)
        +step_evaporation(dt)
        +sync_from_peer_update(layer, x, y, amount, radius)
    }

    PheromoneMap --> PheromoneLayerType
    PheromoneMap --> EvaporationConfig
    EvaporationModel --> EvaporationConfig
    PheromoneUpdater --> PheromoneMap
    PheromoneUpdater --> EvaporationModel
```

### Layer Semantics & Decay Rates

| Pheromone Layer | Enum Identifier | Evaporation Rate $\rho_k$ | Half-Life ($dt=1$) | Functional Purpose |
|---|---|---|---|---|
| **Exploration** | `PheromoneLayerType.EXPLORATION` | **0.01** (1% per step) | $\approx 69$ steps | Long-term memory of visited/mapped territory; prevents redundant long-term re-exploration. |
| **Success** | `PheromoneLayerType.SUCCESS` | **0.05** (5% per step) | $\approx 14$ steps | Recruitment beacon deposited when vehicle candidates or targets are detected; attracts peer agents for verification. |
| **Avoidance** | `PheromoneLayerType.AVOIDANCE` | **0.30** (30% per step) | $\approx 2$ steps | Short-term spatial reservation deposited around active robots to enforce spatial separation and prevent swarm clustering. |

### Discrete Evaporation Model

The evaporation update follows discrete exponential decay bounded within $[\tau_{\min}, \tau_{\max}]$:

$$\tau_k(t + \Delta t) = \text{clamp}\left(\tau_k(t) \cdot (1 - \rho_k)^{\Delta t}, \; \tau_{\min}, \; \tau_{\max}\right)$$

where $\tau_{\min} = 0.01$ and $\tau_{\max} = 50.0$. In `PheromoneUpdater.step_evaporation()`, this decay factor is computed once per layer and applied to all grid cells.

### Radial Gaussian Kernel Deposition

When a robot deposits pheromone with a non-zero radius $r \ge \text{resolution}$, `PheromoneUpdater.deposit()` spreads the deposit across neighbor cells using a 2D Gaussian kernel:

$$\Delta \tau(x, y) = \text{amount} \times \exp\left(-\frac{d^2}{2\sigma^2}\right) \quad \text{for } d \le r$$

where $\sigma = \frac{r}{2.0}$ and $d = \sqrt{(x - x_0)^2 + (y - y_0)^2}$.

```python
# File: swarm/pheromone/pheromone_updater.py:50-68
cell_radius = int(math.ceil(radius / self.map.resolution))
sigma = radius / 2.0
two_sigma_sq = 2.0 * sigma * sigma

for dy in range(-cell_radius, cell_radius + 1):
    gy = cy + dy
    for dx in range(-cell_radius, cell_radius + 1):
        gx = cx + dx
        dist = math.hypot(dx * self.map.resolution, dy * self.map.resolution)
        if dist <= radius:
            weight = math.exp(-(dist * dist) / two_sigma_sq)
            curr = self.map.get_value_grid(layer, gx, gy)
            self.map.set_value_grid(layer, gx, gy, curr + amount * weight)
```

Sources: [swarm/pheromone/evaporation_model.py:10-56](), [swarm/pheromone/pheromone_map.py:10-114](), [swarm/pheromone/pheromone_updater.py:25-101]()

---

## Recursive Bayesian Target Probability Updates

The `TargetProbabilityMap` maintains a spatial belief grid of target vehicle presence $P(\text{target} \mid \text{location}) \in [0.01, 0.99]$. To avoid catastrophic floating-point underflow or normalization overhead during sequential updates, the map stores beliefs in **log-odds** representation:

$$L(x, y) = \ln \left(\frac{P(x, y)}{1 - P(x, y)}\right)$$

Posterior probabilities are recovered via the logistic sigmoid function clamped to $[-10.0, 10.0]$:

$$P(x, y) = \frac{1}{1 + \exp\left(-\text{clip}(L(x, y), -10.0, 10.0)\right)}$$

```mermaid
sequenceDiagram
    autonumber
    participant Robot as Search Robot (Perception)
    participant TPM as TargetProbabilityMap (Log-Odds)
    participant Scorer as TaskScorer (ACO)

    Note over Robot,TPM: Phase 1: Environment Prior Initialization
    Robot->>TPM: initialize_from_environment(env)
    TPM-->>TPM: Set zone priors P ∈ [0.05, 0.95] into log_odds grid

    Note over Robot,TPM: Phase 2: Candidate Vehicle Sighted
    Robot->>TPM: deposit_positive_evidence(x, y, confidence=0.85, radius=4.0)
    TPM-->>TPM: ΔL = ln(conf / (1 - conf)) ∈ [0.2, 3.5]
    TPM-->>TPM: Add Gaussian kernel: L = min(8.0, L + ΔL · weight)

    Note over Robot,TPM: Phase 3: Empty Area Swept or Decoy Rejected
    Robot->>TPM: deposit_negative_evidence(x, y, decay_factor=0.15, radius=2.5)
    TPM-->>TPM: Subtract Gaussian kernel: L = max(-6.0, L - decay · weight)

    Note over Scorer,TPM: Phase 4: ACO Task Scoring Query
    Scorer->>TPM: get_box_probability(min_x, min_y, max_x, max_y, mode="max")
    TPM-->>Scorer: Returns posterior target prior p_target ∈ [0.01, 0.99]
```

### Positive Bayesian Evidence Update

When a perception pipeline detects a potential target car (e.g., color match, license plate candidate, or CNN detection), `deposit_positive_evidence()` converts confidence into log-odds innovation $\Delta L$:

$$\Delta L = \ln \left(\frac{\max(0.01, \text{confidence})}{1.0 - \min(0.99, \text{confidence})}\right)$$

This update is clamped to $\Delta L \in [0.2, 3.5]$ and deposited with a 2D Gaussian kernel over radius $r=4.0\text{m}$ ($\sigma = \max(1.0, \frac{r_{\text{cells}}}{2})$):

$$L(r, c) \leftarrow \min\left(8.0, \; L(r, c) + \Delta L \cdot \exp\left(-\frac{d^2}{2\sigma^2}\right)\right)$$

### Negative Bayesian Evidence Update

When a robot surveys an empty region or confirms that a detected vehicle is an uninteresting civilian car or decoy, `deposit_negative_evidence()` decrements log-odds over radius $r=2.5\text{m}$ with decay factor $0.15$:

$$L(r, c) \leftarrow \max\left(-6.0, \; L(r, c) - 0.15 \cdot \exp\left(-\frac{d^2}{2\sigma^2}\right)\right)$$

Sources: [swarm/pheromone/target_probability_map.py:13-145]()

---

## Spatial Search Memory & Redundancy Tracking

Complementing the continuous pheromone fields, `SearchSpatialMemory` provides a discrete 2D spatial bin tracker ($\text{bin\_size} = 2.0\text{m}$) tracking visitation counts, visiting robot identifiers, candidate sightings, and confirmed decoy rejections.

```python
# File: swarm/task_allocation/search_history.py:69-88
def get_redundancy_penalty(self, x: float, y: float) -> float:
    b = self._to_bin(x, y)
    visits = self.visit_counts.get(b, 0)
    candidates = self.candidate_counts.get(b, 0)

    if visits <= 1:
        return 0.0

    # If candidates were sighted here, do not penalize revisiting for confirmation
    if candidates > 0:
        return 0.05

    # Penalty rises smoothly with repeat unpromising visits
    penalty = 1.0 - math.exp(-0.35 * (visits - 1))
    return round(float(min(0.95, penalty)), 3)
```

1. **Redundancy Penalty ($f_{\text{red}}$)**:
   - Zero penalty ($0.0$) if visits $\le 1$.
   - If candidate sightings exist in the cell, penalty is capped at $0.05$ so robots are not deterred from re-visiting to confirm targets.
   - For unpromising sweeps without sightings, penalty smoothly climbs towards $0.95$ via $1 - e^{-0.35(\text{visits} - 1)}$.
2. **Historical Exploration Value ($f_{\text{he}}$)**:
   - Pristine, unvisited cells return $1.0$.
   - Confirmed decoy locations with no candidate sightings return $0.15$.
   - Cells with candidate sightings are boosted to $\min(1.0, 0.80 + 0.10 \times \text{candidates})$.
   - Empty visits decay exponentially via $\max(0.10, e^{-0.40 \times \text{visits}})$.

Sources: [swarm/task_allocation/search_history.py:46-113]()

---

## Configuration & Parameter Reference

Configuration values can be set programmatically via dataclasses in `swarm/aco/aco_config.py` or loaded from `configs/aco_params.yaml`.

```yaml
# File: configs/aco_params.yaml
objective_function_weights:
  alpha: 1.2       # Weight of positive pheromone trail (exploitation)
  beta: 1.5        # Weight of information gain / unexplored ratio (exploration)
  gamma: 1.8       # Weight of target vehicle detection likelihood
  delta: 1.0       # Weight of search priority / mission urgency
  epsilon: 1.3     # Penalty exponent for distance / travel cost
  zeta: 2.0        # Penalty exponent for congestion (other robots targeting area)
  theta: 1.5       # Penalty exponent for negative/recent visit pheromone

pheromone_dynamics:
  evaporation_rates:
    exploration: 0.01  # Slow decay for permanent environmental exploration memory
    success: 0.05      # Moderate decay for vehicle sighting evidence
    avoidance: 0.30    # Rapid decay for temporary robot reservation / congestion

  deposit_values:
    exploration_visit: 1.0
    vehicle_detected: 5.0
    confirmed_target_match: 20.0
    active_reservation: 3.0

  min_pheromone: 0.01
  max_pheromone: 50.0

stochastic_selection:
  temperature: 1.0     # Softmax temperature parameter (higher = more exploratory)
  epsilon_greedy: 0.05 # Probability of picking random valid frontier for exploration
```

### Parameter Reference Table

| Category | Parameter | Default Value | Description |
|---|---|---|---|
| **Classical Exponents** | `alpha` | `1.2` | Exponent for positive pheromone $\tau$ in classical ratio |
| | `beta` | `1.5` | Exponent for frontier size / information gain |
| | `gamma` | `1.8` | Exponent for vehicle detection prior probability |
| | `delta` | `1.0` | Exponent for mission priority / urgency |
| | `epsilon` | `1.3` | Exponent for Euclidean distance travel cost penalty |
| | `zeta` | `2.0` | Exponent for peer congestion penalty |
| | `theta` | `1.5` | Exponent for avoidance / reservation pheromone |
| **Normalized Weights** | `w_information_gain` | `1.0` | Reward weight for normalized frontier cluster size |
| | `w_target_prior` | `1.4` | Reward weight for Bayesian target probability $P(\text{target})$ |
| | `w_historical_value` | `0.8` | Reward weight for spatial memory exploration history |
| | `w_exploration_value`| `0.9` | Reward weight for `SUCCESS` layer pheromone aggregation |
| | `w_distance_cost` | `0.7` | Penalty weight for Euclidean travel distance |
| | `w_congestion_cost` | `1.0` | Penalty weight for peer robot target commitment overlap |
| | `w_collision_risk` | `0.6` | Penalty weight for `AVOIDANCE` layer pheromone |
| | `w_redundant_penalty`| `0.8` | Penalty weight for repeatedly swept empty zones |
| | `w_battery_health` | `0.5` | Base weight for battery state modulation |
| **Stochastic Selection**| `temperature` | `1.0` | Boltzmann Softmax exploration temperature ($T \to 0$ exploits, $T \to \infty$ uniform) |
| | `epsilon_greedy` | `0.05` | Probability of choosing a random pending task |
| | `deterministic` | `False` | When `True`, bypasses Softmax and selects top score strictly |
| **Pheromone Map** | `min_pheromone` | `0.01` | Lower bound floor for all pheromone layers |
| | `max_pheromone` | `50.0` | Upper saturation ceiling for pheromones |
| | `resolution` | `0.5` m | Spatial cell resolution of continuous 2D pheromone map |
| **Normalization** | `max_normalization_distance` | `70.0` m | Normalization distance denominator |
| | `max_frontier_size` | `30.0` | Normalization frontier cluster size denominator |

Sources: [configs/aco_params.yaml:1-36](), [swarm/aco/aco_config.py:10-80]()

---

## Architectural Integration & Data Flow

Within the multi-robot coordination architecture, the ACO components integrate through `ACOAllocationStrategy` and `TaskManager`. During every task allocation cycle, the robot:
1. Receives candidate frontiers extracted by SLAM and deduplicated by `TaskManager`.
2. Queries active peer commitments from `SwarmState.peers` to compute peer congestion counts.
3. Samples the continuous `PheromoneMap` bounding boxes for `SUCCESS` and `AVOIDANCE` trails.
4. Samples the `TargetProbabilityMap` bounding box for posterior vehicle belief.
5. Ingests dwell counts and decoy rejection records from `SearchSpatialMemory`.
6. Executes `TaskScorer.compute_score()` across all candidates and passes scores to `ACOEngine.select_task()` for Boltzmann Softmax sampling.
7. Deposits `AVOIDANCE` pheromone upon commitment and broadcasts stigmergic updates to peer robots via `sync_from_peer_update()`.

This closed-loop stigmergic feedback cycle allows the multi-robot swarm to rapidly locate target vehicles in large, cluttered environments without centralized communication bottlenecks.
