| """Population diversity entropy. |
| |
| Per Research Proposal §4 controller, the population's diversity is |
| summarised as the **sum of per-dimension Shannon entropies** of the |
| histograms over each search-space coordinate. Higher entropy means the |
| swarm is spread out (favouring exploitation if the landscape allows); |
| lower entropy signals collapse onto a single basin (a cue to switch into |
| exploration). |
| """ |
|
|
| from __future__ import annotations |
|
|
| import numpy as np |
| from numpy.typing import NDArray |
|
|
| _LOG2 = np.log(2.0) |
|
|
|
|
| def population_entropy( |
| population: NDArray[np.float64], |
| *, |
| bins: int = 10, |
| bounds: tuple[float, float] = (0.0, 1.0), |
| ) -> float: |
| """Sum of per-dimension Shannon entropies (in bits). |
| |
| Parameters |
| ---------- |
| population : NDArray[np.float64] |
| Array of shape ``(n, d)`` with rows being individuals. |
| bins : int |
| Histogram bin count per dimension. Default ``10`` matches the |
| controller config. |
| bounds : tuple[float, float] |
| Histogram range applied to every dimension. Default ``(0, 1)`` |
| matches the unit-cube convention used throughout the project. |
| |
| Returns |
| ------- |
| float |
| Non-negative scalar in ``[0, d * log2(bins)]``. Returns ``0.0`` |
| for a degenerate population (all individuals at the same point). |
| """ |
| p = np.asarray(population, dtype=np.float64) |
| if p.ndim != 2: |
| raise ValueError(f"population must be 2D (n, d); got shape {p.shape}") |
| n, d = p.shape |
| if n == 0 or d == 0: |
| return 0.0 |
| if bins < 2: |
| raise ValueError(f"bins must be >= 2, got {bins}") |
|
|
| total = 0.0 |
| for j in range(d): |
| counts, _ = np.histogram(p[:, j], bins=bins, range=bounds) |
| probs = counts.astype(np.float64) / float(n) |
| nonzero = probs[probs > 0] |
| if nonzero.size > 0: |
| total += float(-np.sum(nonzero * np.log(nonzero)) / _LOG2) |
| return total |
|
|