(sec-optimization-laboratory)=
# Optimization Lab

:::{div} feynman-prose
In Optimization Lab, each walker is one proposed answer to an optimization problem.
Its complete state contains every coordinate the objective needs. The view lets
you inspect where those answers move, which ones survive, and whether their
objective values improve. Changing the camera does not change the problem.

The application lives at `/optimization/`, alongside the control laboratory at
`/lab/`. It shares the C++ swarm machinery and browser tooling, but its walkers
explore benchmark functions directly. Start with the short experiment below,
then use the two views to separate spatial motion from objective improvement.
:::

(sec-optimization-start)=
## Build and run the first experiment

:::{div} feynman-prose
From the repository root, build the native engine, build its WebAssembly version,
and start the local server. The browser build uses the existing Emscripten SDK
setup and pinned JavaScript packages. The first setup may download dependencies;
console emulator builds and ROMs are not required.
:::

```bash
make optimization-native
make optimization-web
make optimization-lab
```

:::{div} feynman-prose
Open [http://127.0.0.1:8081/optimization/](http://127.0.0.1:8081/optimization/).
Use HTTP rather than opening `index.html` as a local file. The initial session is
paused: **Euclidean Gas**, **256 walkers**, **3D Rastrigin**, **seed 7**, and the
**spatial view**. **Objective** defaults to **Minimize**, and **Perturbation** to
**Gaussian (mean 0)** with **Standard deviation = 1**. One simulation worker owns
the engine. The evaluation budget defaults to **0**, meaning unlimited.

1. Wait for **C++ / WebAssembly**, then press **Step**. Inspect the objective values
   and the change in the cloud before advancing again.
2. Press **Run**, let the swarm advance, and press **Pause**. The convergence chart
   follows best and mean objective values. **Convergence axis** defaults to
   **Evaluations** and also offers **Iteration**; an inverse-hyperbolic-sine
   (`asinh`) scale compresses large value ranges vertically.
3. Select a walker to inspect its complete coordinates and available diagnostics.
4. Change the dimension or algorithm in the configuration panel, then press
   **Apply and reset** once. This starts a new session with the edited settings.
5. Use **Reset** to restart the active configuration and seed. Save a recording
   first if you want to keep the current history.

Settings are a draft until applied. Camera, projection, coloring, and other view
changes affect the display immediately and preserve the run. An iteration is an
engine operation, not a screen refresh: a slower renderer does not increase the
integration time step. For reproducibility, retain the engine version as well as
the configuration and seed. Python and C++ do not promise identical random streams
from the same seed.
:::

(sec-optimization-algorithms)=
## Choose how the swarm explores

For Graph, **Freeze shared prefix after N nodes** defaults to `0` (disabled).
When enabled, a sufficiently long path shared by alive continuations is archived
up to its first branching point. The branching point becomes the active root;
the archived path is excluded from the active population cap, retained in the
recording, and drawn green in the spatial view.

:::{div} feynman-prose
Choose **FMC**, **Wave**, **Wave Jump**, **Graph**, **Euclidean Gas**,
**GAS (2017)**, **Active CMA-ES**, or **BIPOP-active CMA-ES** under **Algorithm**.
All eight support **Objective → Minimize** and **Maximize**. The
selection score is the negative of the function value for minimization and the
function value itself for maximization. Surfaces, colors, and the inspector keep
the raw objective convention: maximizing does not flip the landscape upside down.
**Best** decreases during minimization and increases during maximization.

Internal fitness can also depend on diversity, so a walker's fitness and its
objective value answer different questions. Read the objective when comparing
solutions; use fitness and companion overlays to understand selection. **Fitness
(pre-step)** and **Pre-step fitness** label the selection-stage value retained in
a snapshot. It is not recomputed at the displayed post-step coordinates.

**Wave** and **Graph** use the shared C++ Fractal implementations through
`FractalGas` and `FractalTree` adapters for a continuous benchmark environment.
A move earns the change
in selection score: objective decrease when minimizing, increase when maximizing.
Cumulative reward is anchored to a common baseline, so different path lengths do
not give a walker credit merely for taking more steps. Cloning carries the
corresponding state and reward history together. Graph also retains ancestry;
its population can grow beyond its starting set of walkers.

**FMC** and **Wave Jump** use the shared Fractal planner through `ArcadePlanner`
over a shared Wave search.
They keep one committed position and explore possible continuations from it.
**Step** advances one search iteration or executes one chosen action; it does not
complete an entire planning cycle. During search, the cloud can move while the
committed position stays fixed. The next search starts from the position reached
by the preceding execution.

Both planners default to **Search horizon = 32**. FMC chooses the most represented
first action in the surviving search ancestry and executes it before searching
again. Wave Jump defaults to **Execute shared ancestry prefix**: it executes the
common initial path when surviving walkers share one. Without a shared prefix,
it can extend the search to **Maximum horizon (0 = automatic)**; automatic means
twice the search horizon, capped at 4096. At that limit it falls back to one action
from the best branch. Disabling the shared-prefix option executes the selected
best branch's complete path. Each action along a chosen path still requires its
own execution step.

**Euclidean Gas** maintains positions and velocities. It selects companions,
computes fitness, applies cloning at the configured interval, and advances motion
with the core BAOAB integrator. Cloning includes position jitter and velocity
restitution; the kinetic step has independent noise in each coordinate and an
optional potential force. The force follows the selected objective direction.
Time step, friction, inverse temperature, cloning settings, and companion
strategies belong to the algorithm configuration. Adaptive fitness forces,
anisotropic diffusion, and viscous and curl extensions remain outside this app.

**Perturbation** selects the noise distribution. **Gaussian (mean 0)** and
**Uniform (mean 0)** both interpret **Standard deviation** as the per-coordinate
standard deviation, rather than the uniform distribution's half-width. For Wave,
Graph, FMC, and Wave Jump, this is a position displacement in coordinate units per
proposal step. Changing the domain bounds therefore does not silently rescale a
new run's perturbations. The old domain-fraction `proposal` setting is translated
only when loading legacy configurations without an explicit `perturbation_std`.

For Euclidean Gas, the perturbation is the random velocity input to BAOAB's
O stage, scaled by the existing thermal-noise coefficient. Gaussian noise with
standard deviation 1 retains the configured temperature convention. Increasing
the standard deviation increases the kick variance. Uniform noise with the same
standard deviation matches that variance but does not reproduce the Gaussian
Ornstein–Uhlenbeck transition law. The perturbation setting does not replace the
separate **Clone position jitter** control.

For Wave, Graph, the planners, and Euclidean Gas, bounded-domain mode marks
out-of-bounds walkers as dead. GAS uses the retry rule described below. Periodic boundaries
instead wrap coordinates across the box when enabled. Wrapping changes the
boundary rule; it does not make an arbitrary benchmark smooth across that seam.
Wave, Graph, and Euclidean Gas stop when every walker becomes invalid. FMC and
Wave Jump can use the shared planner's all-dead search fallback while their
committed position remains valid; an invalid committed position ends the run.
Reduce the perturbation standard deviation or kinetic step size, review the
bounds, and apply a reset to recover.
:::

(sec-optimization-cmaes)=
### Active CMA-ES and BIPOP-active CMA-ES

:::{div} feynman-prose
**Active CMA-ES** (`cmaes_active`) and **BIPOP-active CMA-ES** (`cmaes_bipop`)
add two optimization references to the Algorithm selector. Both use the vendored
libcmaes implementation and support minimization and maximization. Imagine fitting
an ellipse around promising moves in a narrow valley. CMA-ES moves the ellipse's
center, changes its overall size, and learns which coordinate changes belong
together. Active covariance adaptation also uses poorly ranked samples to reduce
exploration in unhelpful directions, with safeguards that preserve a valid
covariance.

This ellipse belongs to the optimizer's search distribution. Each generation
draws a new candidate cloud from it. The adaptive local perturbation described
below instead gives each surviving Wave or GAS walker its own centered proposal.
Selecting CMA-ES therefore selects a complete optimizer; it does not replace a
swarm's perturbation strategy. The independent **Perturbation** and **Walkers**
controls do not configure its population or sampling.

Active CMA-ES follows one run until its stopping criteria are met. BIPOP-active
CMA-ES restarts, alternating searches with larger populations and searches with
smaller populations and varied initial scales. The best objective found so far
survives these restarts. A restart changes the visible candidate cloud and can
change its size; it does not reset the total evaluation counter.
:::

:::{div} feynman-prose
**Initial standard deviation** defaults to 20% of the domain width, in coordinate
units. It sets the initial distribution's scale; CMA-ES subsequently adapts its
step size. **Initial population** defaults to zero, which selects libcmaes's
dimension-dependent population size. BIPOP's **Large-population runs** defaults
to nine. These controls belong to CMA-ES and are independent of the Gaussian
perturbation's standard deviation. The initial mean follows the benchmark's
seeded initialization.

CMA uses ordinary bounded domains and the upstream boundary transformation.
Periodic wrapping is unavailable. Candidates and objective queries use double
precision, while existing swarm algorithms retain their float-coordinate path.
Near an optimum, that distinction can affect the smallest attainable objective
gap; include precision when reporting a comparison. Rendering may convert
coordinates to floats without changing the evaluated or recorded candidates.

One **Step** evaluates and incorporates a complete generation. Initialization
already evaluates the first generation, displayed at iteration zero, and counts
all its queries. A positive evaluation budget must accommodate that generation.
Before each later generation, the engine checks the remaining budget, including
after a restart. It pauses if the generation cannot fit; it does not truncate the
generation or reduce the population to spend the last few evaluations. Read the
generation, current population size, restart count, step size, and termination
reason alongside the objective chart to understand why a run progresses or stops.
:::

:::{div} feynman-prose
Reset reconstructs the optimizer and its random streams from the configuration.
The same seed, including zero, is reproducible within the same build. libcmaes
retains its upstream sampler, so native and WebAssembly builds need not generate
identical candidate trajectories. Objective noise uses a separate random stream.

Recordings retain the library revision, precision mode, initial mean, resolved
population, and initial scale with the configuration, plus the displayed frames
and CMA status. Their Float64 coordinates preserve the evaluated candidates,
including changes in population size across restarts. Loading a recording replays
those frames; it does not restore covariance, evolution paths, or a resumable
optimizer. Older frame recordings retain their existing interpretation.

Compare objective quality at equal evaluation budgets, and report runtime
separately. A good best objective says that the optimizer found a good answer.
It does not say that the candidate cloud approximates a reward density. Mode
occupancy is a separate observation, especially when a restart replaces the
whole CMA population.
:::

(sec-optimization-local-covariance)=
### Adaptive local Gaussian perturbations

:::{div} feynman-prose
Select **Adaptive local Gaussian** (`local_covariance`) with **Wave**, **FMC**,
**Wave Jump**, or **GAS (2017)** to learn the shape of position perturbations from
previous moves.
Imagine a narrow valley: coordinated changes in two coordinates may carry a
walker along it, while independent changes send it into a wall. A covariance
matrix describes that coordination. Each walker still starts its move at its
own post-cloning position; the population's dynamic cloning continues as before.

The strategy remains Gaussian. Its learned covariance is a CMA-inspired proposal
geometry, or curvature proxy, rather than a measured Hessian. Successful moves
contain information about slope, location, and noise as well as curvature.
Learning from those moves does not separate these effects. Local neighborhoods
help avoid interpreting the separation between distinct reward peaks as a useful
perturbation direction.
:::

:::{prf:definition} Adaptive local proposal in Optimization Lab
:label: def-optimization-local-covariance

Let $d$ be the coordinate dimension and $\sigma$ the **Standard deviation** in
coordinate units. At an action's post-cloning origin $x$, select the nearest
spatial anchor and its dimensionless covariance $C$, normalized so that
$\operatorname{tr}(C)=d$. Retain its factor for every draw in that action:

$$
C_{\mathrm{used}}=0.95C+0.05I,
\qquad LL^\top=C_{\mathrm{used}},
\qquad \delta_k=\sigma Lz_k,
\qquad z_k\sim\mathcal N(0,I).
$$

Thus each proposal draw has expected squared displacement $d\sigma^2$ before GAS
retry scaling, acceptance filtering, or boundary handling, although individual
coordinate variances can differ. With $\sigma=0$,
displacements are zero and learning is skipped.

The archive holds the latest 512 valid search transitions: origin, total
unwrapped displacement $\delta$, number of draws $n$, retry scale $s$, and
objective improvement in the selected optimization direction. GAS records only
accepted random jumps and retains its retry halving; $s$ is the scale of the
accepted retry and is $1$ for the other algorithms. Initialization, invalid
outcomes, zero-length actions, committed planner execution replays, rejected
GAS retries, local-search moves, and tabu moves are excluded.
At each update, choose up to 16 distinct anchors deterministically by farthest-point
selection from archived origins, starting with the newest origin. Distances use
the minimum-image convention in periodic domains.

At each anchor, take the nearest 32 transitions and rank positive objective
improvements per draw. With normalized positive logarithmic rank weights $w_i$,
form

$$
y_i=\frac{\delta_i}{\sigma s_i\sqrt{n_i}},\qquad
S=\sum_i w_i y_i y_i^\top,\qquad
C_{\mathrm{new}}=(1-\eta)C_{\mathrm{old}}
 +\eta\frac{dS}{\operatorname{tr}(S)}.
$$

The covariance learning rate $\eta$ defaults to $0.1$. Initialize geometry with
the identity and retain the previous local covariance when fewer than four
improving transitions are available. Normalize the resulting covariance to
trace $d$ and cache the regularized Cholesky factor for sampling.
:::

:::{div} feynman-prose
**Standard deviation** controls average squared step length; **Covariance learning
rate** (`covariance_learning_rate`) controls how quickly the shape changes.
The five-percent isotropic component keeps every direction available. The archive,
anchor, and neighborhood limits are internal defaults, so increasing the run
length does not keep growing the estimator's memory. Displacements are accumulated
before periodic wrapping: crossing a seam should not look like a jump across the
whole box to the estimator.

Wave and GAS update geometry after each complete population step. All walkers
in that step therefore see one model snapshot. GAS measures each accepted random
jump from its post-cloning origin, using its unwrapped displacement and dividing
out the retry scale when learning. Optimization Lab selects `local_covariance`
when GAS is chosen. The objective-scaled `gas_adaptive` variant remains available.
FMC and Wave Jump update before a new
planning cycle and freeze geometry throughout search and execution. Replaying a
chosen action uses the same factor and action seed as its search trial, including
the same stochastic objective draw. Executing that trial does not count as a new
training observation.

Learning reuses objective evaluations already made by the optimizer and introduces
no extra objective queries. **Reset** clears the learned archive and geometry.
Recordings retain the strategy settings and displayed frames through the ordinary
recording path; loading frames does not restore a resumable covariance model.
:::

:::{warning}
:class: feynman-added

Changing perturbation geometry changes the mutation part of the population
process. Faster progress toward a good objective value does not establish a
better approximation to the intended reward density. Compare mode coverage and
relative population mass as well as best objective values, using matching
evaluation budgets and several seeds.
:::

(sec-optimization-gas-2017)=
### GAS (2017): adaptive jumps and optional local refinement

:::{div} feynman-prose
**GAS (2017)** (`gas`) implements General Algorithmic Search from
[Hernández, Duran, and Amigó, Section 2](https://arxiv.org/pdf/1705.08691).
It maintains positions, applies flow-based cloning, and proposes random jumps.
Euclidean Gas has a separate velocity state and BAOAB integration; selecting GAS
does not enable another kinetic mode of that integrator.

The default Optimization Lab configuration uses `local_covariance`, which learns
local Gaussian proposal directions from accepted GAS moves. The alternative
`gas_adaptive` perturbation gives better walkers smaller isotropic jumps.
For a coordinate whose domain width is 10, a best walker has jump standard
deviation 0.0001, while a worst walker has standard deviation 1. These are widths
of random distributions, not fixed jump lengths. A good position can therefore
be explored finely while other walkers search farther away. Gaussian and uniform
perturbations are also explicit GAS variants; their
scale is the ordinary **Standard deviation** control, rather than the adaptive
domain fraction. The `gas_adaptive` strategy appears only for GAS.
:::

:::{prf:definition} GAS conventions in Optimization Lab
:label: def-optimization-gas-conventions

Let $g_i=f(x_i)$ for minimization and $g_i=-f(x_i)$ for maximization. Among finite
objective values, define the dimensionless normalized objective

$$
\phi_i=\frac{g_i-\min_j g_j}{\max_j g_j-\min_j g_j},
$$

with $\phi_i=0$ when all finite values coincide. For a random distinct distance
companion $j$ and a sampled tabu entry $t_r$, the swarm flow is

$$
F_i=(1+\phi_i)^2\lVert x_i-x_j\rVert^2\delta_{i,r}^2,
\qquad
\delta_{i,r}^2=
\begin{cases}
\lVert x_i-t_r\rVert^2,&x_i\ne t_r,\\
1,&x_i=t_r.
\end{cases}
$$

When tabu memory is disabled, omit $\delta_{i,r}^2$. Distances are ordinary
Euclidean distances, including in periodic mode. For a random distinct cloning
companion $k$, use $P_{i,k}=\max(0,\min(1,(F_i-F_k)/F_i))$ when $F_i>0$ and
$P_{i,k}=0$ otherwise. Apply accepted clones simultaneously from a population
snapshot, then recompute $\phi$.

For coordinate width $L^{(n)}$, the adaptive Gaussian displacement has standard
deviation $L^{(n)}10^{-5+4\phi_i}$ in coordinate units. This implementation
interprets the paper's jump parameter as a standard deviation. In bounded mode,
redraw from the original position, halving the scale after rejection; after 64
unsuccessful attempts, retain that position. Periodic mode wraps proposals.

The local-search centroid is

$$
x_{\mathrm{cm}}=\frac{\sum_i\phi_i x_i}{\sum_i\phi_i},
$$

with the arithmetic mean used when the denominator vanishes. Dividing by the
weight sum is an explicit clarification of the paper's unnormalized centroid
expression. These weights are larger for worse objectives.
:::

:::{div} feynman-prose
**Tabu memory** (`gas_tabu`) and **L-BFGS-B local search** (`gas_local_search`)
both default to enabled and can be changed independently. With both enabled,
initialization refines the best sampled walker and fills an N-entry memory with
that result. Each iteration refines the centroid and best walker, inserts each
result into a random memory slot, and runs memory flow and cloning after each
insertion. Memory flow uses the same objective normalization and pair-distance
factor, without another tabu-distance factor.

Turning off local search leaves tabu memory initialized from the best sampled
walker; each iteration inserts the current best walker. Turning off tabu memory
leaves local refinement active, with refined candidates contributing to **Best**
without allocating memory. Turning off both leaves swarm cloning and random
proposals. Tabu memory influences selection through its distance factor; it does
not impose a forbidden region on the benchmark.

Each bounded local search has an objective-query cap, `gas_local_evaluations`,
default **200**, and a maximum of **100 iterations**. It uses the benchmark's
gradient path, including finite differences where needed. A cap, a nonfinite
result, or solver failure ends that search while retaining its best finite
candidate. Every objective query counts, including finite-difference probes and
rejected line-search trials. A refined candidate may improve **Best** without
appearing as a displayed walker.

Stochastic benchmarks disable local search and show the reason in the controls.
The active configuration and recording retain this effective setting. The
evaluation budget must cover the conservative initialization bound; later steps
reserve their full bound before starting. GAS can consequently stop with unused
budget even when a local search would have converged before reaching its cap.
Use actual **Evaluations**, and retain both switches and the local-search cap,
when comparing recorded results.
:::

(sec-optimization-benchmarks)=
## Choose a benchmark and its dimension

:::{div} feynman-prose
The catalog contains **37 benchmarks**: the 13 functions below and all 24 functions
in COCO's noiseless, single-objective BBOB suite. Each entry supplies dimension
restrictions, default bounds, parameters, gradient availability, and any known
minimum or reference value. The table below describes minimization experiments.
A catalog reference minimum remains a minimum when you choose **Maximize**; it
is not a maximization target.
Changing the number of displayed axes never reduces the dimension passed to the
objective. A ten-dimensional walker still has ten coordinates even when you see
only three of them.
:::

:::{div} feynman-added
| Benchmark | Dimension | What to inspect |
|---|---|---|
| Sphere | At least 1 | Radial concentration toward the origin. |
| Quadratic Well | At least 1 | How curvature changes motion and selection. |
| Mexican Hat | At least 1 | A degenerate set of minima when the tilt is zero; a tilt changes the preference. |
| Rastrigin | At least 1 | Competing basins in a regularly oscillating landscape. |
| EggHolder | Exactly 2 | Irregular basins and nonsmooth locations. |
| Styblinski–Tang | At least 1 | Competing wells with unequal depths. |
| Rosenbrock | At least 2 | Progress along a curved, narrow valley with adjacent-coordinate coupling. |
| Easom | Exactly 2 | Discovery of a narrow well in a broad, nearly flat domain. |
| Holder Table | Exactly 2 | Separated wells and nonsmooth locations. |
| Lennard–Jones | 3 times the atom count | Atomic configurations and pair interactions. |
| Constant | At least 1 | Exploration without spatial objective preference. |
| Stochastic Gaussian | At least 1 | Selection driven by sampled noise without a spatial optimum. |
| Mixture of Gaussians | At least 1 | Basins of a negative log mixture density. |
:::

:::{div} feynman-prose
For a Gaussian mixture, the realized component centers, deviations, and weights
are part of the saved configuration. Component centers are reference points, not
guaranteed minima of the combined objective. Keep those realized parameters when
repeating an experiment.

Stochastic Gaussian draws an objective sample during optimization. Its displayed
surface is the expected value, zero; recorded walker heights retain their sampled
values. Rotating the camera or resampling a slice consumes no simulation random
numbers. Potential forces are disabled for this benchmark because its noise is
not a differentiable spatial landscape.

Smooth functions in this original group use analytic gradients. EggHolder and
Holder Table use a central finite-difference fallback at nonsmooth locations;
this gives a numerical force, not a proof that a derivative exists there.
Singular or nonfinite evaluations,
such as coincident Lennard–Jones atoms, remain invalid. They do not become best
candidates, and the surface omits triangles with invalid samples.
:::

:::{div} feynman-prose
Choose an entry beginning **BBOB f** to use the official COCO problem. The IDs are
`bbob_1` through `bbob_24`; accepted dimensions are **2, 3, 5, 10, 20, and 40**.
The **COCO instance** selects one of **1–1000**, default **1**, independently of
the swarm's random seed. The default domain is **[-5, 5]** in each coordinate.

An instance includes the shifts, rotations, and other transformations specified
for its function. Its optimum value can differ from zero and from another
instance's optimum. Retain the function, dimension, instance, and resolved
reference minimum together. In BBOB minimization runs, **Gap to minimum** displays
the best observed value minus that instance's reference minimum. It is hidden
when maximizing. The suite's function groups are listed in the
[official BBOB overview](https://coco-platform.org/testsuites/bbob/overview.html).
:::

:::{div} feynman-added
| BBOB IDs | Functions in numerical order |
|---|---|
| `bbob_1`–`bbob_5` | Sphere; separable Ellipsoid; separable Rastrigin; Bueche–Rastrigin; Linear slope. |
| `bbob_6`–`bbob_9` | Attractive sector; Step ellipsoid; original Rosenbrock; rotated Rosenbrock. |
| `bbob_10`–`bbob_14` | Rotated Ellipsoid; Discus; Bent cigar; Sharp ridge; Different powers. |
| `bbob_15`–`bbob_19` | Rotated Rastrigin; Weierstrass; Schaffer F7, condition 10; Schaffer F7, condition 1000; Griewank–Rosenbrock. |
| `bbob_20`–`bbob_24` | Schwefel; Gallagher 101 peaks; Gallagher 21 peaks; Katsuura; Lunacek bi-Rastrigin. |
:::

:::{div} feynman-prose
Use these functions to isolate different difficulties. On the
[rotated Ellipsoid](https://coco-platform.org/testsuites/bbob/functions/f10.html),
the directions of steep and shallow change no longer align with the coordinate
axes. A displacement that moves far enough along the valley can overshoot across
it. Compare this with the separable Ellipsoid before attributing the problem only
to a small step size.

[Gallagher's peaks](https://coco-platform.org/testsuites/bbob/functions/f21.html)
have independently arranged locations and heights; finding one attractive basin
does not reveal a dependable route to the best one.
[Katsuura](https://coco-platform.org/testsuites/bbob/functions/f23.html) is rugged
and repetitive across fine scales, so a coarse surface can conceal structure the
optimizer still evaluates. [Lunacek bi-Rastrigin](https://coco-platform.org/testsuites/bbob/functions/f24.html)
combines two broad funnels with many smaller local optima. Watch whether the
swarm concentrates in one funnel before it has adequately explored the other.

For BBOB, **Potential force** starts disabled. Enabling it explicitly uses central
finite differences with coordinate step `1e-5 * max(1, abs(x[k]))`; one gradient
costs **2 × dimension** objective calls. These calls count against the evaluation
budget. A finite-difference force on a rugged or nonsmooth function is a numerical
choice, not an analytic gradient supplied by COCO. Surface sampling remains
separate from optimization and does not consume that budget.
:::

(sec-optimization-views)=
## Read the spatial and landscape views

:::{div} feynman-prose
Choose **View → Spatial** to place walkers using the **Horizontal**, **Depth**,
and **Vertical** coordinates. **Color** offers **Objective**, **Fitness
(pre-step)**, and **Uniform**. A two-dimensional benchmark occupies a plane.
Drag to orbit, right-drag to pan, scroll to zoom, and use **Reset camera** to
recover the initial view. Gold identifies the current best walker; white marks
your selection. For FMC and Wave Jump, a larger marker identifies the committed
position. It is teal unless the best or selection highlight overrides that color.
The other markers are search candidates. The snapshot appends the committed
position after the search rows, so a planner configured with 256 walkers displays
257 rows. **Role** in the inspector distinguishes **Search walker** from
**Committed position**.

**Mean** summarizes the valid rows currently displayed, including a planner's
committed row. **Best** retains the best valid objective query observed anywhere
in the run, including finite-difference probes and search candidates later
discarded. It can therefore be better than every visible walker. Gold marks the
best current row, rather than the historical query behind **Best**. A candidate
found during planning is not necessarily a position the planner has executed.
Inspect the committed row's **Objective** to track the executed route; its
pre-step fitness is not a search-selection diagnostic.

Open **Display and slices** for **Point size**, **Opacity**, and **Links**.
The link choices are **Distance companions**, **Clone companions**, and
**Ancestry**; relationships appear when the algorithm supplies them. **Trails
(up to 96 walkers)** shows recent recorded motion, or the selected walker's trail
when one is selected. Trails break at cloning events and when planners begin a
new search from their committed position. **Show objective slice** adds a sampled
plane to the spatial view without changing the run.

Choose **View → Landscape** to use the **Horizontal** and **Depth** coordinates
for the domain and an `asinh`-scaled objective for height. This monotone transform
compresses large objective differences; the displayed height is not the raw
objective value. The surface and walkers share the same transform. **Height
scale** adjusts the vertical magnification, and **Surface resolution** changes
the sample grid. The surface and contours are sampled from the same C++ evaluator
used by the optimizer. Read raw objective values in the inspector and metrics.

For a higher-dimensional function, you must also choose fixed values for the
coordinates absent from the surface. That surface is one slice. Each walker is
drawn at the transformed height of its full-dimensional objective, so it need not
lie on the displayed surface. For example, two walkers can overlap
in the selected coordinates and have different heights because their other
coordinates differ. Move the slice coordinates to investigate that difference;
do not interpret the gap as a rendering error.

Click a walker, or enter its **Walker index**, to inspect its complete coordinates,
objective, pre-step fitness, status, leaf flag, cloned flag, and parent index.
Velocity components appear for Euclidean Gas. For a valid Lennard–Jones walker,
**Atom configuration** turns its coordinate triples into atom positions. One
swarm point represents an entire molecule: the atoms in the inspector are
components of that candidate, rather than additional optimization walkers.
:::

(sec-optimization-recordings)=
## Save, inspect, and replay a run

:::{div} feynman-prose
**Record history** is off by default and takes effect on reset. With recording
off, only the current frame is retained; replay, trails, convergence history, and
exports are unavailable. Enable **Record history** before **Apply and reset**
to retain the run history. Imported recordings remain available for replay and export.

Use **Save recording** to export a versioned `.fgopt` file. It contains the active
configuration, realized benchmark parameters, engine version, metrics, and
recorded snapshot arrays. These snapshots preserve the full coordinate vectors,
so a replay can use a different projection without rerunning the optimization.

Dragging the timeline pauses computation and displays a recorded frame. **Play
replay** advances through recorded frames at the selected **Replay speed**; the
button becomes **Stop replay** while playback runs. **Latest** returns to the
newest frame and permits continuation of the still-live session. Scrubbing does
not restore an earlier random-generator or algorithm state.

**Load recording** validates the file format and array shapes before replacing
the displayed session. Loaded recordings provide exact visual replay of their
frames. The current engine identifies itself as `fgopt-3`; earlier `fgopt-1` and
`fgopt-2` files retain exact replay of their saved frames. Exporting an imported
recording preserves its original engine identifier and stored metrics, including
the earlier engine's evaluation-count and best-value semantics.

The **Reset** button becomes **Rerun settings**: use it to start a fresh run with
the saved settings, including realized mixture parameters. This rerun uses the
current engine, so an older engine's trajectory need not be regenerated.
**Run** and **Step** remain disabled for the imported history itself. A `.fgopt`
file is a frame recording, not a resumable engine checkpoint; a fresh rerun starts
at its initial state. Python `.pt` recordings are not imported by this application.

The recording budget is **64 MiB**. The Lab pauses before the next frame would
exceed it and retains the existing recording for export. Larger populations and
dimensions fill that budget sooner. Save the current history, then reset to begin
a new recording. If an imported file is rejected, keep the current session and
inspect the reported validation error rather than treating a partial file as a
usable run.
:::

(sec-optimization-budgets)=
## Compare results at an evaluation budget

:::{div} feynman-prose
A screen frame is not a unit of search effort. Neither is an iteration: one
iteration can ask the objective many times, especially when a finite-difference
force is enabled. An evaluation budget gives each run a limit on those queries.
IOHanalyzer's **Fixed-Budget Results** analyzes solution quality against function
evaluations; it is an analysis view of the continuous problems already in the
catalog. See the [IOHanalyzer GUI guide](https://iohprofiler.github.io/IOHanalyzer/GUI/).

Set **Evaluation budget (0 = unlimited)** (`max_evaluations`) before **Apply and
reset**. A positive budget must cover initialization. The counter includes every
optimization objective query, including initialization,
finite-difference force probes, and evaluations of proposals that are later
discarded or invalid. Invalid values cannot improve **Best**. Drawing surfaces,
changing slice coordinates, and reading the reference optimum do not increment
the optimization counter.

The engine checks the next complete step before starting it. If its evaluation
bound exceeds the remaining budget, the run pauses with its last frame and
history unchanged.
It does not shrink the population, shorten a planner search step, or partially
execute an integration step to fill the remainder. Graph uses a conservative
bound because its population can change, so it can stop with unused budget even
when the next step would actually have been cheaper. Report the final actual
**Evaluations** count alongside the requested cap.

For a first comparison, choose one BBOB function, dimension, and COCO instance;
keep those fixed while changing algorithm and swarm seed across separate runs.
Set the same evaluation cap, then choose **Convergence axis → Evaluations**.
The **Iteration** view remains useful for understanding the algorithm's phases.
Keep minimization selected for the ordinary BBOB task; maximization explores a
different objective direction even though it uses the same function evaluator.

Use **Export CSV** to save checkpoint results for IOHanalyzer. The export contains
`evaluations`, `best`, `function`, `algorithm`, `dimension`, and `run`, followed by
`instance`, `seed`, `objective`, `budget`, `engine`, `perturbation`, and
`perturbation_std`. Enable IOHanalyzer's **use custom csv format**, then map
the first six columns to evaluation counter, function values, function ID,
algorithm ID, problem dimension, and run ID respectively. Set its minimization/maximization
option to match the run. Keep the evaluation column mapped: these checkpoints
are not sequential single-query observations. See the
[custom CSV data format](https://iohprofiler.github.io/IOHanalyzer/data/).

GAS exports additionally include `gas_tabu`, `gas_local_search`, and
`gas_local_evaluations`. With `gas_adaptive`, `perturbation_std` is empty because
the scale depends on each walker's objective. Other algorithms retain their
existing CSV columns.

Each CSV row describes a saved snapshot checkpoint; duplicate evaluation counts
and checkpoints without a finite best value are omitted. The actual evaluation
count and best value at that checkpoint are recorded; the export does not invent the
query at which an improvement occurred between snapshots. For fixed-budget
analysis, choose budgets supported by the recorded checkpoints and inspect how
the analyzer handles sparse data or runs that stop early. Do not interpret a
plotted interpolation as an additional measured result. For shifted BBOB
instances, compare raw values within the same instance or derive an objective
gap using that run's saved `reference_minimum` before aggregating instances.
When combining different parameter variants with the same algorithm and seed,
assign distinct algorithm or run IDs in the combined data before import so the
analyzer does not merge their checkpoints into one run.

CSV exports use IOHanalyzer's custom format. The app also retains `.fgopt` visual
recordings; it does not emit an official COCO observer/postprocessing archive.
Discrete pseudo-Boolean optimization (PBO) suites are not included in this
continuous-domain integration.
:::

:::{warning}
:class: feynman-added

Using the official COCO evaluator does not by itself reproduce an unmodified
official BBOB experimental protocol. The swarm algorithms store walker coordinates as
float32, while CMA-ES evaluates double-precision coordinates; maximization,
periodic wrapping, custom domain bounds, and optional
finite-difference potential forces also change the experimental procedure or
numerical assumptions. Identify these settings when comparing or reporting runs.
The custom CSV export is not an official COCO observer archive.
:::

(sec-optimization-engine)=
## Extend the engine and check changes

:::{div} feynman-prose
The C++ optimization engine owns benchmark evaluation and swarm state. The browser
worker calls its handle-based C API to create a session, step it, request a
snapshot, sample objectives, and destroy it. The main thread receives transferred
snapshot buffers and draws them with Three.js. It does not implement a second
version of the objective or advance the algorithm during rendering.

`fractal-gas-web/src/fractal/` contains the shared Wave, Graph, FMC, Wave Jump,
and Euclidean Gas implementations. Its `fg_fractal_core` library supplies common
numerical and history operations; the algorithm templates specialize state storage
and actions without depending on benchmarks or application configuration. Wave
owns the population lifecycle, including cloning, rewards, elites, and lineage.
The shared planner owns action selection and search-horizon decisions; each host
retains its execution clock and presentation.

Optimization reaches Wave, Graph, FMC, and Wave Jump through the `fg_swarm_core`
snapshot adapters, using integer action seeds for position perturbations. Lab
instantiates the same Wave implementation with packed physics states and continuous
actions, so sharing the algorithm does not require serializing its state batches.
Euclidean Gas instead uses an objective-domain adapter for evaluation, gradients,
bounds, and evaluation accounting, with a separate proposal interface for noise.
The config IDs remain `wave`, `graph`, `fmc`, `wave_jump`, and `euclidean`;
consolidation does not change the algorithm menu.

GAS (2017) and the CMA-ES comparison algorithms retain their separate
implementations. GAS (2017) uses `gas` and registers its adaptive position proposal as
`gas_adaptive`. Its bounded local solver and dependencies are shared by native
and WebAssembly builds. GAS settings travel through the existing configuration
and recording paths; its population uses the existing snapshot layout.

BBOB evaluation compiles the pinned official **COCO 2.8.2** C source at revision
`e5d068f69e36f346c86cc2934413369abe36fc22` into both native and WebAssembly builds.
The app neither rewrites these benchmark formulas nor adds a replacement
optimizer for them. The active configuration records `coco_version`,
`coco_problem_id`, and `reference_minimum`. The COCO adapter owns separate
simulation and display problem objects. Both evaluate the same instance; only
the display object is used to read the reference optimum. Visualization cannot
alter the simulation object's counters or reveal an optimum to its search.

Start in `fractal-gas-web/src/optimization/`: the benchmark implementation supplies
the catalog, values, gradients, initialization, and boundary behavior; the engine
interface supplies settings, populations, and algorithm factories. A new benchmark
needs a catalog descriptor and evaluator, dimension and parameter validation,
gradient behavior, and reference tests. Use the same evaluator for visualization
sampling, and keep stochastic display sampling independent of the simulation
random stream.

A new optimizer implements the algorithm interface and calls
`register_algorithm(id, name, velocity, factory, parameters)`. The optional
`parameters` array describes its controls: each entry has `id`, `label`, `type`,
and `default`, with `min` and `max` for numeric limits or `options` for an enum.
Supported types are `number`, `integer`, `boolean`, and `enum`; enum options are
`[[id, label], ...]`. The frontend renders these descriptors automatically. The
factory reads custom settings from `Settings::json` and validates the values
needed by its algorithm.

For a new proposal distribution, implement
`Perturbation::sample(position, delta, dimensions, rng)` and register its factory
with `register_perturbation(id, name, factory, parameters)`. Write one displacement
vector into `delta` using the supplied random generator. The factory receives the
benchmark and configuration, allowing parameters or state-dependent proposals.
Its parameter descriptors use the same schema as algorithm controls and populate
the **Perturbation** UI automatically. The benchmark environment adds the sampled
vector to position; Euclidean Gas passes it through the BAOAB thermal coefficient
as velocity noise. Account for that difference when designing a distribution.

Return the shared population and metric fields, and expose optional velocity,
companion, or ancestry data only when they have defined meaning. The renderer and
recording format can then consume the result without requiring an
algorithm-specific copy of the application. Keep stored objective values raw;
use `Settings::score` and `Settings::better` when the algorithm needs the selected
optimization direction.

For an objective-dependent proposal, override `sample_with_context` and read the
optional `PerturbationContext` supplied by the algorithm. GAS passes the walker's
normalized objective through this interface. The default method forwards to
`sample`, preserving existing distribution implementations; GAS adaptive
sampling without context reports an error. Optional `algorithms` compatibility
metadata restricts a perturbation to supported algorithm IDs. The UI filters
its choices using that metadata, and backend validation enforces the same rule.

Route objective queries through the benchmark's optimization evaluation path so
evaluation counts and best-observed values include all attempted candidates.
Implement `next_evaluations_upper_bound()` for a new algorithm; the session uses
it to admit whole steps within `max_evaluations`. Account for initialization and
any finite-difference queries without changing the underlying optimizer's step
semantics. Keep display sampling on the separate evaluation path.

The C API is declared in `c_api.h`. Returned strings and snapshot pointers are
borrowed; copy data needed beyond the next mutation of that session. The snapshot
contains a header followed by full-dimensional rows and diagnostics. Treat its
version and documented layout as an interface contract when changing producers,
the worker decoder, or recording validation. Planner snapshots append their
committed-position row after the search walkers; consumers must preserve that
role when interpreting counts, diagnostics, and replay.

The benchmark environment uses 24-bit action IDs as perturbation seeds, exactly
representable in the shared planner's float action recorder. Given the same
engine, configuration, starting state, action ID, and proposal-step count, the
environment repeats the same perturbations and stochastic objective sample.
The proposal geometry stays frozen throughout each planning cycle, including
execution, and updates before the next search. Thus executing a recorded search
edge does not draw a different future. Planner
rewards are changes from their search root's score; the adapter adds that root
baseline back when exposing an absolute candidate score. This preserves the
objective ranking across searches without changing the planner's reward logic.

Optimization uses MT19937-64 with explicit portable bit mappings for uniform real
and integer draws, shared by the native and WebAssembly builds. This makes their
random draws comparable without depending on a platform's standard-library
distribution implementation for the swarm algorithms. CMA-ES uses libcmaes's
upstream sampling distributions and promises deterministic resets within each
build, rather than identical random trajectories across builds.
Retain the engine version when rerunning settings;
recorded-frame replay uses the saved arrays directly and does not require
regenerating those draws. Python uses its own random streams, so matching seeds
alone does not establish Python/C++ operator parity.

The shared Wave implementation standardizes elite bookkeeping, carrying each
elite's state, rewards, actions, terminal flags, and lineage together. Its
cumulative-reward ordering can change trajectories from earlier engine versions
even with the same seed; the fitness and cloning formulas are retained.

Two details matter when comparing Euclidean Gas with its Python reference. The
reference `random_pairing` operator pairs live rows and leaves dead rows pointing
to themselves. The C++ operator preserves that rule; the shared Euclidean engine then
replaces a dead cloning donor with a live donor so cloning can revive the row.
This revival belongs to the population update, not the pairing operator.
Also, when **Clone every N iterations** skips a cloning update, the engine still
consumes the companion, decision, and proposed-clone random draws before
discarding that proposal, following the reference's draw order. Removing those
draws would change subsequent motion even on iterations without applied cloning.

For BAOAB operator comparisons, the Python `integrator="baoab"` reference now
applies one half-duration force kick in each B stage. The C++ core follows that
force-kick convention. The separate Boris path is unchanged by this correction.

Run the focused checks after changing the engine or browser contracts:
:::

```bash
make optimization-test
```

:::{div} feynman-prose
Relevant checks include benchmark values and gradients, both objective
directions, perturbation moments and replay, cloning and kinetic operators,
cumulative-score ranking, committed planner motion, deterministic resets,
recording round trips, and native/WebAssembly agreement. Browser verification
should exercise all eight algorithms, both views, dimension changes,
Lennard–Jones inspection, loading all supported recording engine versions, and reset
while a worker request is outstanding. The **Step** and **Draw** timings report
simulation and rendering duration separately; either can explain a slow-looking
experiment.

For BBOB, check all 24 functions, supported dimensions, multiple instances,
reference values, native/WebAssembly agreement, and independence of display
sampling. Budget checks should cover initialization rejection, finite-difference
costs, the last admitted step, Graph's conservative bound, and best values from
discarded candidates. Verify CSV column mapping and sparse checkpoints alongside
`.fgopt` compatibility. For GAS, also check all four tabu/local-search switch
combinations, flat objectives, coincident walkers, simultaneous cloning, adaptive
jump scales, bounded retries, and periodic wrapping. Verify local convergence,
per-search caps, finite-difference accounting, solver failure recovery, and
stochastic-benchmark disabling. Include effective GAS settings in configuration
reload and recording round-trip checks.

The browser CI runs Chromium and Firefox on `ubuntu-latest`. For Ubuntu 24.04,
reproduce its Xvfb and software Mesa setup, using `LIBGL_ALWAYS_SOFTWARE=1` and
`OPTIMIZATION_FIREFOX_HEADLESS=0`; this supplies the WebGL context needed by the
renderer. Startup checks report browser and WebGL errors immediately instead of
waiting for an unexplained readiness timeout. Reproduce that display setup when
investigating a Linux CI startup failure.

Geometry and fluid benchmarks, additional optimizers, automated experiment suites,
adaptive or viscous physics, and resumable checkpoints are outside this release.
For the related continuous-control engine and its separate world-state contracts,
see {doc}`control_lab_architecture`.
:::
