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
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 —
amcpsois 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
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(...)
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 atx_best.history(dict, only ifreturn_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.
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.
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
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:
- Rebuilds a time-varying neighborhood topology from a thresholded Gaussian kernel on pairwise distances.
- Measures swarm dispersion
S_t(half log-determinant of the swarm covariance) and derives a dispersion ratior_t = min(1, exp((S_t - S_0) / d)), which drives the consensus gain and temperature schedules. - Updates the dual velocity as the sum of inertia, natural-gradient descent, cognitive attraction, social attraction, Laplacian consensus, and annealed noise.
- 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.
Metadata
Release files for amcpso 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| amcpso-1.0.0.tar.gz | 15.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| amcpso-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 27.6 kB
Release files / amcpso-1.0.0.tar.gz
| Download URL | amcpso-1.0.0.tar.gz |
|---|---|
| Size | 15.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
956514d502219ccb6ca91091b65d53c12f9f673a14822f98370209566bf5dead
|
|
BLAKE2b-256 checksum How to use checksums |
bd285973b1d4b10fb6ebb972abb7e4d7f8429ba455d1a593374afe0db41fbe60
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 8, 2026.
Transparency logRelease files / amcpso-1.0.0-py3-none-any.whl
| Download URL | amcpso-1.0.0-py3-none-any.whl |
|---|---|
| Size | 12.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
21d267fa6bb65dd77da9cbee66fc4781488b9eb4f1ceae3a5c8a0a9b66f72eed
|
|
BLAKE2b-256 checksum How to use checksums |
5a4e96bbc52f10d307e5733e1a42d5a0e987e1cdf2d99881b94516880616b4bc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 8, 2026.
Transparency log