fcmaes: Gradient-Free Optimization Library
repository·master·Indexed 41 days ago
https://github.com/dietmarwo/fast-cma-esA high-performance Python 3 library for gradient-free optimization featuring a native Rust backend. It provides a variety of scalar minimizers including active CMA-ES, Differential Evolution (DE), CR-FM-NES, PGPE, Dual Annealing, and BiteOpt, as well as multi-objective optimization via MODE and Quality-Diversity tools like MAP-Elites. Supports both direct minimization wrappers and a stateful ask/tell interface for custom evaluation loops.
What's inside fcmaes
- fcmaes is a Python 3 library designed for gradient-free optimization. It provides high-performance optimization capabilities, leveraging a native Rust implementation via PyO3 to ensure speed while maintaining a Pythonic interface.
Overview of fcmaes optimization algorithms
masterThe library provides several gradient-free optimization algorithms tailored for different problem scales and types:
- BiteOpt (C++ only): A robust single-objective optimizer. If you are unsure which algorithm to use, try this first. Use weighted penalties for constraints.
- MO-DE (Python/C++): Multi-objective optimization merging Differential Evolution and NSGA concepts. Supports constraints and mixed-integer problems.
- CMA-ES (Python/C++): Efficient for a low number of decision variables (< 500).
- CR-FM-NES (Python/C++): Fast Moving Natural Evolution Strategy. Recommended for high-dimensional problems (> 100 variables).
- PGPE (C++): Parameter Exploring Policy Gradients. Best for very high-dimensional problems (> 1000 variables) and machine learning tasks.
- Differential Evolution (Python/C++): Supports parallel function evaluation and mixed-integer problems.
- Quality Diversity (QD) Methods: Includes
CVT-map-elitesand theDiversifiermeta-algorithm for exploring diverse niches in the search space.
Best practices for implementing high-performance objective functions
masterWhen implementing objective functions in C++ to be called from Python (e.g., via
ctypes) for optimization, follow these patterns to avoid performance bottlenecks:- Avoid CLI overhead: Do not use a command-line interface to call the objective function from Python. This creates massive overhead, especially in parallel settings. Instead, use a shared library with a
ctypes-based API. - Ensure Reentrancy: Implement the objective function as reentrant so multiple invocations can run safely in parallel.
- Avoid global variables: All shared state should be member variables of a singleton class instance.
- Instance per call: Each reentrant API call should create its own singleton instance to avoid sharing state between parallel calls.
- Handle Discrete Variables correctly: Do not hide discrete decision variables from the optimizer. Use integer decision variables to generate discrete values so optimizers like
fcmaes-MODEorDEcan adapt their algorithms accordingly. - Separate Initialization: Move expensive initialization code out of the objective function loop so it runs only once.
- Avoid CLI overhead: Do not use a command-line interface to call the objective function from Python. This creates massive overhead, especially in parallel settings. Instead, use a shared library with a
Parallelization Best Practices for Quantum Simulation
masterTo optimize performance when using
fcmaesfor quantum circuit optimization, follow these parallelization guidelines:- Maxcut Parallelization: Scales best when using
qasm_simulatorandaer_simulator. - Fitness Evaluation: While the optimization loop can be parallelized, the actual fitness evaluation and quantum circuit simulation should generally be kept single-threaded to avoid performance degradation (the "room heater" effect).
- Verification: Before running multi-threaded quantum circuit simulations, verify that it actually improves performance on your specific CPU architecture.
- Maxcut Parallelization: Scales best when using
Configure MO-DE for multi-objective optimization
masterWhile the TopTrumps tutorial uses a weighted-sum approach with single-objective optimizers,
fcmaesnow includes a dedicated multi-objective algorithm: MO-DE.MO-DE supports constraints and is the preferred method for multi-objective problems. When using it, you can tune the update method using the
nsga_updateparameter. For the TopTrumps problem, settingnsga_update=False(which uses the DE update method) is recommended for better performance.Additionally, since TopTrumps is a mixed-integer problem, you can use the
intsparameter to specify discrete parameters, provided you adapt the bounds to integer values.Define a multi-objective problem for fcmaes
masterTo use
fcmaesfor multi-objective optimization, define a problem class that specifies the dimensionality, bounds, and fitness functions.For multi-objective tasks, the fitness function should return a list or array of values representing the different objectives to be minimized (e.g.,
[infection_rate, total_cost]).class fcmaes_problem(): def __init__(self): self.dim = 2 self.bounds = Bounds([0]*self.dim, [budget]*self.dim) def simulate(self, budged_doses, spending): # ... simulation logic ... return calc_total_infected(results, system) def fitness2(self, x): budged_doses = x[0] spending = x[1] infected = self.simulate(budged_doses, spending) # Returns two objectives: infection rate and total budget spent return [infected, budged_doses + spending] problem = fcmaes_problem()Compare CR-FM-NES and PGPE algorithms for high-dimensional tasks
masterWhen choosing an algorithm for high-dimensional optimization in EvoJax, consider the following properties:
Algorithm Population Basis Symmetric Sampling ADAM Mean Update Covariance Type ARS Random Normal No No N/A PGPE / FPGPEC Random Normal Yes No N/A CRFMNES / FCRFMC Random Normal No No Diagonal Covariance CMA-ES Random Normal Yes No Full Covariance Performance Heuristics
- Low Optimization Budget: PGPE (and its C++ wrapper
FPGPEC) tends to lead in the early stages of optimization. - High Optimization Budget: CR-FM-NES (and its C++ wrapper
FCRFMC) shows a higher improvement rate and often takes the lead as the budget increases. - Strategy Tip: For complex tasks, consider a phased approach: use PGPE in the first phase, then initialize CR-FM-NES with the resulting population to leverage its superior late-stage convergence.
- Low Optimization Budget: PGPE (and its C++ wrapper
Handle Constraints in Multi-Objective Optimization
masterWhen using
modecpp.retry, constraints are treated as a special kind of objective. To implement this:- Define the number of constraints in your fitness class using
self.ncon. - In your objective function, return the constraint violation (e.g., a count of failed operations) as the last element of the returned array.
- Filter the results after optimization to ensure only valid solutions (where the constraint value is 0) are kept.
For single-objective optimization, constraints are typically handled by assigning them a very high weight in a weighted sum of objectives.
def retry_modecpp(fit, retry_num = 32, popsize = 48, max_eval = 500000, nsga_update = True, logger = logger(), workers=mp.cpu_count()): xf, yf = modecpp.retry(fit.fun, fit.nobj, fit.ncon, fit.bounds, retry_num, popsize, max_evaluations = 960000, nsga_update = nsga_update, logger = logger, workers=workers) xs = []; ys = [] for i in range(len(yf)): if yf[i][-1] == 0: # filter valid solutions (constraint == 0) xs.append(xf[i]) ys.append(yf[i][:fit.nobj]) return np.array(xs), np.array(ys)- Define the number of constraints in your fitness class using
Optimize spherical designs for laser fusion reactor applications
masterIn applications like laser fusion reactor design, points (lasers) must be arranged symmetrically, and weights (intensities) must be non-negative and ideally consist of only a few distinct values to simplify hardware requirements.
To achieve this, you can reduce the search space by:
- Generating Orbits: Instead of optimizing all $N$ points, optimize a smaller set of 'reference points' and use symmetry operations (permutation and mirroring) to generate the full set of 12 symmetric points per reference point.
- Reducing Decision Variables: This significantly shrinks the dimensionality of the optimization problem, speeding up convergence.
- Weight Constraints: The optimization naturally tends toward solutions with a small number of distinct weights (e.g., only 2 or 3 different intensity levels), which is highly practical for physical implementation.
def generate_orbit(A, B, C): # apply 12 symmetries (permutations and sign changes) cart = [] for perm in [(A, B, C), (B, C, A), (C, A, B)]: for signs in [(1, 1, 1), (1, -1, -1), (-1, 1, -1), (-1, -1, 1)]: x, y, z = [sign * coord for sign, coord in zip(signs, perm)] cart.append((x, y, z)) return cartesian_to_spherical(cart) # The fitness function then uses these generated orbits def fit(x): points = generate_orbits(N, x[:2*n]) weights = x[2*n:] return t_design_error(points, l_max, weights)Scaling QD optimizations with parallel processes
masterTo scale QD optimizations effectively, consider sharing a single QD archive across multiple parallel processes (such as multiple MAP-Elites and improvement-emitter processes). This approach often scales better than attempting to parallelize the simulation itself.Implementation details for Spherical T-Design computation
masterWhen implementing or verifying Spherical T-Designs (specifically for computing Legendre functions and Spherical Harmonics), the following mathematical steps are required:
- Legendre Functions: Compute $\cos(\theta)$ for the Legendre functions.
- Normalization: Compute the normalization factor.
- Associated Legendre Functions: Compute the associated Legendre function for order $m$ and degree $l$.
- For $m = 0$, no extra trigonometric factor is needed.
- For $m > 0$, compute both the cosine and sine components.
- Spherical Harmonics (SH) Calculation:
- Step 1: Compute the SH values at all points (unweighted).
- Step 2: Multiply each $Y_{l,m}$ by the corresponding weight and sum them up (element-wise multiply by $w_i$ and sum).
- Coordinate Assumptions: Assume points are provided as
pts[:,0]=thetaandpts[:,1]=phi. - Weighting: For a given $l$, create a multiplier $1 / (2l + 1)$ for $l=0, ext{까지 } l_{max}$.
Encoding Constraints and Variables
masterFollow these conventions for consistent optimization:
General Rules:
- Minimization: All objectives are minimized. Negate quantities that must be maximized.
- Constraint Format: For MODE/moretry, use
[objective_0, ..., objective_m, constraint_0, ..., constraint_k]. A constraint is feasible ifconstraint <= 0. - Constraint Conversion: Convert
h(x) = 0toabs(h(x)) - tolerance <= 0. - Penalty Method: For scalar optimizers, use decoding/repair for hard structure and a scaled penalty for violations:
objective + rho * sum(max(0, g_i(x))**2). Normalize terms before tuningrho. - Rejection: Return a large finite rejection value for invalid simulations.
Variable Encoding:
- Integers: Encode as real coordinates; round, clamp, and repair in the objective. Optionally set DE or MODE's integer mask.
- Categories: Encode as an index into a fixed list. Do not interpret distances between unrelated categories physically.
- Complex Structures: Decode random keys or repair permutations/schedules/subsets deterministically.