Metadata-Version: 2.4
Name: amcpso
Version: 1.0.0
Summary: Adaptive Mirror-Consensus Particle Swarm Optimization (AMC-PSO): a gradient-assisted swarm optimizer with entropy-regulated annealing and free feasibility on constrained domains.
Author: razibmustafiz
License: MIT
Project-URL: Homepage, https://github.com/razibmustafiz/AMC-PSO
Project-URL: Repository, https://github.com/razibmustafiz/AMC-PSO
Project-URL: Issues, https://github.com/razibmustafiz/AMC-PSO/issues
Keywords: optimization,particle swarm optimization,PSO,mirror descent,natural gradient,simplex optimization,constrained optimization,metaheuristics,swarm intelligence
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Operating System :: OS Independent
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.20
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: scipy>=1.7; extra == "dev"
Requires-Dist: build>=1.0; extra == "dev"
Requires-Dist: twine>=4.0; extra == "dev"
Dynamic: license-file

# amcpso — Adaptive Mirror-Consensus Particle Swarm Optimization

`amcpso` is a gradient-assisted particle swarm optimizer that evolves particles
in the **dual space** of a strongly convex *mirror potential*. This buys two
things classical PSO doesn't have:

- **Feasibility by construction.** With the entropic mirror, every iterate is
  automatically a valid point on the probability simplex (`x_k >= 0`,
  `sum(x) == 1`) — no clip-and-renormalize projection distorting the search.
- **Entropy-regulated annealing.** A swarm-dispersion signal `r_t in [0, 1]`
  closes a feedback loop that cools exploration (noise, consensus pull)
  automatically as the swarm converges, instead of using a fixed schedule.

It combines five forces per iteration — inertia, natural-gradient descent,
mirrored cognitive/social attraction, graph-Laplacian consensus over a
time-varying neighborhood topology, and annealed Gaussian noise.

## Installation

```bash
pip install amcpso
```

Requires Python >= 3.8 and NumPy >= 1.20.

## When to use it (and when not to)

**Use `amcpso` when your problem is constrained, geometry-sensitive, and/or
multimodal:**

- **Simplex-constrained problems** — recovering or optimizing over a
  probability distribution, mixture weights, portfolio allocations, or any
  compositional/proportion data (`h_type='entropic'`). This is where the
  mirror machinery pays off most: feasibility is structural, not statistical,
  and the algorithm is significantly better than projection-based PSO in the
  reference benchmarks.
- **Rugged, multimodal landscapes** in ordinary `R^d` (`h_type='euclidean'`),
  such as the Rastrigin function. The entropy-regulated schedule and local
  (rather than global-best) topology help delay premature convergence to a
  poor local minimum, and it is competitive-to-better than classical
  constriction PSO in the reference benchmarks.
- You have (or can cheaply compute, e.g. via autodiff or finite differences)
  a **gradient** of the objective — `amcpso` is gradient-assisted, not a
  purely black-box method.

**Don't expect a win when:**

- Your objective is **smooth and unimodal** (e.g. a plain quadratic bowl) —
  classical PSO's noiseless contraction is typically competitive or better
  here, consistent with the No-Free-Lunch theorem.
- You have **no usable gradient** and computing one is expensive or
  impossible — consider a derivative-free method instead.

## Quick start

```python
import numpy as np
from amcpso import amc_pso

def sphere(x):
    return float(np.sum(x**2))

def sphere_grad(x):
    return 2 * x

x_best, f_best = amc_pso(sphere, sphere_grad, d=5, seed=0)
print(x_best, f_best)
```

## The function: `amc_pso(...)`

```python
from amcpso import amc_pso

x_best, f_best = amc_pso(
    f, grad_f, d,
    N=50, T=500, h_type='euclidean', init_range=(-5, 5),
    beta=0.7, eta0=0.05, c10=1.5, c20=1.5, lambda0=0.3,
    T0=0.5, alpha=5.0, gamma=1.0, bandwidth=2.0, epsilon=1e-10,
    kappa=1e-3, p=0.5, G_max=10.0, v_max=None,
    natural='diag', naive=False, entropy_feedback=True,
    return_history=False, seed=None,
)
```

### Required arguments

| Argument  | Type       | Description |
|-----------|------------|-------------|
| `f`       | callable   | Objective function `f(x) -> float`, `x` is shape `(d,)`. The function being minimized. |
| `grad_f`  | callable   | Gradient `grad_f(x) -> array of shape (d,)`. Required — `amcpso` is gradient-assisted. |
| `d`       | int        | Problem dimension. |

### Key optional arguments

| Argument           | Default        | Description |
|--------------------|----------------|-------------|
| `N`                 | `50`           | Swarm size. |
| `T`                 | `500`          | Number of iterations (optimization budget). |
| `h_type`            | `'euclidean'`  | `'euclidean'` for unconstrained `R^d`; `'entropic'` for the probability simplex (feasibility for free). |
| `init_range`        | `(-5, 5)`      | Per-coordinate init range, used only for `h_type='euclidean'`. |
| `beta`              | `0.7`          | Inertia weight, in `[0, 1)`. |
| `eta0`, `p`         | `0.05`, `0.5`  | Step-size schedule `eta_t = eta0 / (1 + t) ** p`. |
| `c10`, `c20`        | `1.5`, `1.5`   | Initial cognitive / social gains (linearly annealed to 0). |
| `lambda0`           | `0.3`          | Base consensus gain; effective gain scales with the dispersion ratio `r_t`. |
| `T0`, `alpha`, `gamma` | `0.5`, `5.0`, `1.0` | Exploration temperature schedule. |
| `bandwidth`, `kappa`| `2.0`, `1e-3`  | Gaussian-kernel neighborhood topology bandwidth and cutoff. |
| `G_max`             | `10.0`         | Gradient-norm clip (rescales, doesn't truncate). |
| `v_max`             | `None`         | Optional dual-velocity norm clip for extra robustness. |
| `natural`           | `'diag'`       | Natural-gradient preconditioner for the entropic mirror: `'diag'` or `'fisher'`. |
| `return_history`    | `False`        | If `True`, also return a dict of per-iteration traces (`best`, `r`, `T`, `lam`, `eta`). |
| `seed`              | `None`         | RNG seed for reproducibility. |
| `naive`, `entropy_feedback` | `False`, `True` | Ablation flags — leave at defaults for actual optimization; see the docstring for details. |

Full parameter-by-parameter documentation is available in the function's
docstring: `help(amc_pso)`.

### Returns

- `x_best` (`numpy.ndarray`, shape `(d,)`) — best position found.
- `f_best` (`float`) — objective value at `x_best`.
- `history` (`dict`, only if `return_history=True`) — per-iteration traces.

## Example: multimodal optimization (Rastrigin, `R^d`)

This is the best-use-case scenario for the Euclidean mirror: a rugged,
multimodal landscape.

```python
import numpy as np
from amcpso import amc_pso
from amcpso.benchmarks import rastrigin, rastrigin_grad

d = 10
x_best, f_best = amc_pso(
    rastrigin, rastrigin_grad, d=d,
    h_type='euclidean', init_range=(-5.12, 5.12),
    N=50, T=500, seed=7,
)
print(f"best objective: {f_best:.4f}")
```

## Example: constrained optimization on the probability simplex

This is the **best use case** for `amcpso`: recovering a probability vector
`x*` on the simplex from noisy linear measurements `b = A @ x*`, where every
iterate must satisfy `x >= 0` and `sum(x) == 1`.

```python
import numpy as np
from amcpso import amc_pso

rng = np.random.default_rng(12345)
d = 20
A = rng.standard_normal((40, d))
x_star = rng.dirichlet(np.ones(d))   # ground truth on the simplex
b = A @ x_star

def loss(x):
    return float(np.sum((A @ x - b) ** 2))

def loss_grad(x):
    return 2.0 * A.T @ (A @ x - b)

x_best, f_best = amc_pso(loss, loss_grad, d=d, h_type='entropic', seed=0)

print("sum(x_best) =", x_best.sum())      # 1.0, always -- feasible by construction
print("min(x_best) =", x_best.min())      # >= 0, always
print("loss        =", f_best)
```

Unlike classical PSO with clip-and-renormalize projection, `amc_pso` with
`h_type='entropic'` never produces an infeasible iterate at any point during
the search — the softmax inverse mirror map guarantees it structurally.

## Diagnosing convergence with `return_history`

```python
x_best, f_best, hist = amc_pso(
    rastrigin, rastrigin_grad, d=10,
    h_type='euclidean', init_range=(-5.12, 5.12),
    seed=7, return_history=True,
)

# hist['best']: best-so-far objective per iteration
# hist['r']:    dispersion ratio r_t in [0, 1]
# hist['T']:    temperature T_t
# hist['lam']:  consensus gain lambda_t
# hist['eta']:  step size eta_t
```

## Algorithm summary

Each particle keeps a primal position `x`, a dual variable `y = grad_h(x)`,
and a dual velocity `v`. Every iteration:

1. Rebuilds a time-varying neighborhood topology from a thresholded Gaussian
   kernel on pairwise distances.
2. Measures swarm dispersion `S_t` (half log-determinant of the swarm
   covariance) and derives a dispersion ratio `r_t = min(1, exp((S_t - S_0) / d))`,
   which drives the consensus gain and temperature schedules.
3. Updates the dual velocity as the sum of inertia, natural-gradient descent,
   cognitive attraction, social attraction, Laplacian consensus, and annealed
   noise.
4. Maps back to the primal space with the inverse mirror map
   (`x = grad_h_star(y)`), which is the identity for the Euclidean mirror and
   a softmax for the entropic mirror.

See the function docstring (`help(amc_pso)`) and the accompanying manuscript
for the full derivation.

## License

MIT — see [LICENSE](LICENSE).
