0
# Constraints and Boundaries
1
2
Comprehensive support for box constraints, boundary conditions, and general nonlinear constraints in CMA-ES optimization. This includes boundary handlers, constraint handlers, and constrained optimization interfaces.
3
4
## Box Constraints and Boundary Handlers
5
6
Box constraints define simple lower and upper bounds for each variable. CMA-ES provides several boundary handling strategies.
7
8
### Boundary Handler Classes
9
10
```python { .api }
11
# All boundary handler classes from cma.boundary_handler
12
from cma.boundary_handler import BoundTransform, BoundPenalty, BoundNone, BoundDomainTransform
13
14
class BoundTransform:
15
"""
16
Transform solutions to stay within box constraints using bijective transformation.
17
18
This is the recommended boundary handler for most applications. It uses
19
smooth, invertible transformations that preserve the CMA-ES distribution
20
properties while ensuring feasibility.
21
"""
22
23
def __init__(self, bounds):
24
"""
25
Initialize boundary transformation handler.
26
27
Parameters:
28
-----------
29
bounds : list or None
30
Box constraints as [lower_bounds, upper_bounds] where each can be:
31
- None: no bounds
32
- scalar: same bound for all variables
33
- array-like: individual bounds per variable
34
- Can contain None entries for unbounded variables
35
36
Examples:
37
---------
38
>>> import cma
39
>>>
40
>>> # 2D box constraints [-5,5]^2
41
>>> bounds = [[-5, -5], [5, 5]]
42
>>> handler = cma.BoundTransform(bounds)
43
>>>
44
>>> # Mixed bounds: x[0] in [0,10], x[1] unbounded, x[2] in [-1,1]
45
>>> bounds = [[0, None, -1], [10, None, 1]]
46
>>> handler = cma.BoundTransform(bounds)
47
>>>
48
>>> # Single bound for all variables
49
>>> bounds = [[-2], [2]] # All variables in [-2, 2]
50
>>> handler = cma.BoundTransform(bounds)
51
"""
52
pass
53
54
def transform(self, x):
55
"""
56
Transform solution from internal to external (feasible) coordinates.
57
58
Parameters:
59
-----------
60
x : array-like
61
Solution in internal coordinates (can be unbounded).
62
63
Returns:
64
--------
65
numpy.ndarray
66
Solution transformed to satisfy box constraints.
67
68
Examples:
69
---------
70
>>> bounds = [[-1, -1], [1, 1]]
71
>>> handler = cma.BoundTransform(bounds)
72
>>>
73
>>> # Large internal values are mapped to boundaries
74
>>> x_internal = [100, -100]
75
>>> x_feasible = handler.transform(x_internal)
76
>>> print(x_feasible) # Close to [1, -1]
77
>>>
78
>>> # Values near zero map to middle of domain
79
>>> x_internal = [0, 0]
80
>>> x_feasible = handler.transform(x_internal)
81
>>> print(x_feasible) # Close to [0, 0]
82
"""
83
pass
84
85
class BoundPenalty:
86
"""
87
Handle bounds using penalty method.
88
89
Penalizes solutions that violate constraints by adding penalty
90
terms to the objective function value.
91
"""
92
93
def __init__(self, bounds, **kwargs):
94
"""
95
Initialize penalty-based boundary handler.
96
97
Parameters:
98
-----------
99
bounds : list
100
Box constraints as [lower_bounds, upper_bounds].
101
**kwargs : dict
102
Additional options for penalty computation.
103
"""
104
pass
105
106
def repair(self, x, copy_if_changed=True):
107
"""
108
Repair solutions to satisfy bounds and compute penalties.
109
110
Parameters:
111
-----------
112
x : array-like
113
Solutions to repair.
114
copy_if_changed : bool
115
Whether to copy array if modifications needed.
116
117
Returns:
118
--------
119
tuple[array, float]
120
Repaired solutions and penalty value.
121
"""
122
pass
123
124
class BoundNone:
125
"""
126
No boundary handling - allows unbounded optimization.
127
128
Dummy boundary handler for unconstrained problems.
129
"""
130
131
def __init__(self, bounds=None):
132
"""Initialize no-bounds handler."""
133
pass
134
135
class BoundDomainTransform:
136
"""
137
Domain transformation for more complex boundary handling.
138
139
Advanced boundary handler with customizable transformation methods.
140
"""
141
142
def __init__(self, bounds, **kwargs):
143
"""
144
Initialize domain transformation handler.
145
146
Parameters:
147
-----------
148
bounds : list
149
Boundary constraints.
150
**kwargs : dict
151
Transformation options.
152
"""
153
pass
154
155
def inverse_transform(self, x):
156
"""
157
Transform from external (feasible) to internal coordinates.
158
159
Parameters:
160
-----------
161
x : array-like
162
Feasible solution satisfying box constraints.
163
164
Returns:
165
--------
166
numpy.ndarray
167
Solution in internal coordinates.
168
"""
169
pass
170
171
class BoundPenalty:
172
"""
173
Penalty-based boundary handler using quadratic penalties.
174
175
Adds penalty terms to the objective function for constraint violations.
176
Less smooth than BoundTransform but simpler conceptually.
177
"""
178
179
def __init__(self, bounds):
180
"""
181
Initialize penalty-based boundary handler.
182
183
Parameters:
184
-----------
185
bounds : list
186
Box constraints as [lower_bounds, upper_bounds].
187
188
Examples:
189
---------
190
>>> bounds = [[-5, -5], [5, 5]]
191
>>> handler = cma.BoundPenalty(bounds)
192
"""
193
pass
194
195
def __call__(self, f, x):
196
"""
197
Evaluate objective with penalty for bound violations.
198
199
Parameters:
200
-----------
201
f : float
202
Original objective function value.
203
204
x : array-like
205
Solution to check for bound violations.
206
207
Returns:
208
--------
209
float
210
Penalized objective value.
211
"""
212
pass
213
214
def repair(self, x):
215
"""
216
Repair solution to satisfy box constraints by projection.
217
218
Parameters:
219
-----------
220
x : array-like
221
Potentially infeasible solution.
222
223
Returns:
224
--------
225
numpy.ndarray
226
Solution projected onto feasible region.
227
"""
228
pass
229
230
class BoundNone:
231
"""
232
No-op boundary handler for unconstrained problems.
233
234
Provides same interface as other handlers but performs no transformations.
235
"""
236
237
def __init__(self, bounds=None):
238
"""Initialize no-op boundary handler."""
239
pass
240
241
def transform(self, x):
242
"""Return x unchanged."""
243
return x
244
245
def inverse_transform(self, x):
246
"""Return x unchanged."""
247
return x
248
249
class BoundDomainTransform:
250
"""
251
Function wrapper that automatically transforms domain using BoundTransform.
252
253
Wraps an objective function to handle box constraints transparently.
254
The function can be called with any input and constraints are handled
255
automatically via coordinate transformation.
256
"""
257
258
def __init__(self, function, boundaries):
259
"""
260
Create domain-transforming function wrapper.
261
262
Parameters:
263
-----------
264
function : callable
265
Original objective function to be wrapped.
266
267
boundaries : list
268
Box constraints as [lower_bounds, upper_bounds].
269
270
Examples:
271
---------
272
>>> import cma
273
>>>
274
>>> # Original unbounded function
275
>>> def sphere(x):
276
... return sum(xi**2 for xi in x)
277
>>>
278
>>> # Create bounded version
279
>>> bounds = [[-2, -2], [2, 2]]
280
>>> bounded_sphere = cma.BoundDomainTransform(sphere, bounds)
281
>>>
282
>>> # Can now call with any input - bounds handled automatically
283
>>> result = bounded_sphere([100, -100]) # Maps to feasible region
284
>>>
285
>>> # Use in optimization
286
>>> x, es = cma.fmin2(bounded_sphere, [0, 0], 1.0)
287
"""
288
pass
289
290
def __call__(self, x, *args, **kwargs):
291
"""
292
Evaluate wrapped function with automatic bound handling.
293
294
Parameters:
295
-----------
296
x : array-like
297
Input solution (will be transformed to feasible region).
298
299
*args, **kwargs
300
Additional arguments passed to wrapped function.
301
302
Returns:
303
--------
304
float
305
Function value evaluated at transformed (feasible) point.
306
"""
307
pass
308
```
309
310
### Box-Constrained Optimization Usage
311
312
```python { .api }
313
import cma
314
import numpy as np
315
316
# Pattern 1: Simple box constraints using options
317
def simple_box_constraints():
318
"""Basic box constraint optimization."""
319
320
def objective(x):
321
# Rosenbrock function
322
return sum(100*(x[1:] - x[:-1]**2)**2 + (1 - x[:-1])**2)
323
324
# Define box constraints
325
bounds = [[-2, -2, -2], [2, 2, 2]] # Each variable in [-2, 2]
326
327
# Optimize with bounds in options
328
x_best, es = cma.fmin2(
329
objective,
330
[0, 0, 0],
331
0.5,
332
options={'bounds': bounds}
333
)
334
335
print(f"Bounded optimization result: {x_best}")
336
print(f"All variables in bounds: {all(-2 <= xi <= 2 for xi in x_best)}")
337
338
return x_best, es
339
340
# Pattern 2: Mixed bounds (some variables unbounded)
341
def mixed_bounds_optimization():
342
"""Optimization with mixed bounded/unbounded variables."""
343
344
def objective(x):
345
return sum((x - np.array([1, 2, 3, 4]))**2)
346
347
# Mixed bounds: x[0] >= 0, x[1] unbounded, x[2] in [-1,1], x[3] <= 5
348
lower = [0, None, -1, None]
349
upper = [None, None, 1, 5]
350
bounds = [lower, upper]
351
352
x_best, es = cma.fmin2(
353
objective,
354
[0.5, 2, 0, 4],
355
0.3,
356
options={'bounds': bounds}
357
)
358
359
print(f"Mixed bounds result: {x_best}")
360
return x_best, es
361
362
# Pattern 3: Using BoundDomainTransform wrapper
363
def domain_transform_wrapper():
364
"""Using domain transformation wrapper."""
365
366
# Original function (designed for unbounded domain)
367
def unconstrained_function(x):
368
return sum(xi**2 for xi in x)
369
370
# Create bounded version
371
bounds = [[-5, -5], [5, 5]]
372
bounded_function = cma.BoundDomainTransform(unconstrained_function, bounds)
373
374
# Optimize bounded version (no bounds option needed)
375
x_best, es = cma.fmin2(bounded_function, [1, 1], 0.5)
376
377
print(f"Domain transform result: {x_best}")
378
return x_best, es
379
380
# Pattern 4: Manual boundary handler usage
381
def manual_boundary_handling():
382
"""Manual use of boundary handlers."""
383
384
def objective(x):
385
return sum(x**2)
386
387
bounds = [[-3, -3, -3], [3, 3, 3]]
388
389
# Create and configure boundary handler
390
bound_handler = cma.BoundTransform(bounds)
391
392
# Manual ask-and-tell with transformation
393
es = cma.CMAEvolutionStrategy([0, 0, 0], 0.5)
394
395
while not es.stop():
396
# Get solutions in internal coordinates
397
solutions = es.ask()
398
399
# Transform to feasible coordinates and evaluate
400
feasible_solutions = [bound_handler.transform(x) for x in solutions]
401
fitness_values = [objective(x) for x in feasible_solutions]
402
403
# Update with internal coordinates
404
es.tell(solutions, fitness_values)
405
406
if es.countiter % 50 == 0:
407
es.disp()
408
409
# Transform final result to feasible coordinates
410
final_solution = bound_handler.transform(es.result.xbest)
411
print(f"Manual handling result: {final_solution}")
412
413
return final_solution, es
414
415
# Pattern 5: Comparing boundary handlers
416
def compare_boundary_handlers():
417
"""Compare different boundary handling approaches."""
418
419
def objective(x):
420
# Function with optimum at boundary
421
return sum((x - 2)**2) # Optimum at [2, 2]
422
423
bounds = [[-1, -1], [1, 1]] # Optimum outside feasible region
424
425
results = {}
426
427
# BoundTransform (default)
428
x1, es1 = cma.fmin2(
429
objective, [0, 0], 0.3,
430
options={'bounds': bounds, 'BoundaryHandler': 'BoundTransform'}
431
)
432
results['BoundTransform'] = (x1, es1.result.fbest)
433
434
# BoundPenalty
435
x2, es2 = cma.fmin2(
436
objective, [0, 0], 0.3,
437
options={'bounds': bounds, 'BoundaryHandler': 'BoundPenalty'}
438
)
439
results['BoundPenalty'] = (x2, es2.result.fbest)
440
441
# Compare results
442
for method, (x, f) in results.items():
443
print(f"{method}: x = {x}, f = {f:.6f}")
444
445
return results
446
447
# Run examples
448
simple_box_constraints()
449
mixed_bounds_optimization()
450
domain_transform_wrapper()
451
manual_boundary_handling()
452
compare_boundary_handlers()
453
```
454
455
## General Nonlinear Constraints
456
457
Support for general inequality and equality constraints through augmented Lagrangian methods.
458
459
### Constrained Optimization Functions
460
461
```python { .api }
462
def fmin_con2(
463
objective_function,
464
x0,
465
sigma0,
466
constraints=lambda x: [],
467
find_feasible_first=False,
468
find_feasible_final=False,
469
kwargs_confit=None,
470
**kwargs_fmin
471
):
472
"""
473
Optimize objective function with general inequality constraints.
474
475
This is the main interface for constrained optimization with CMA-ES
476
using the augmented Lagrangian method.
477
478
Parameters:
479
-----------
480
objective_function : callable
481
Function to minimize, called as objective_function(x).
482
483
x0 : array-like
484
Initial solution estimate.
485
486
sigma0 : float
487
Initial step size.
488
489
constraints : callable, optional
490
Constraint function returning list of constraint values.
491
Feasibility means all values <= 0. For equality constraint h(x) = 0,
492
use two inequalities: [h(x) - eps, -h(x) - eps] with eps >= 0.
493
494
find_feasible_first : bool, optional
495
Whether to find feasible solution before optimization (default False).
496
497
find_feasible_final : bool, optional
498
Whether to find feasible solution after optimization (default False).
499
500
kwargs_confit : dict, optional
501
Keyword arguments for ConstrainedFitnessAL instance.
502
503
**kwargs_fmin : dict
504
Additional arguments passed to fmin2.
505
506
Returns:
507
--------
508
tuple[numpy.ndarray, CMAEvolutionStrategy]
509
(xbest, es) where xbest is best feasible solution found and es
510
contains optimization details and constraint handler as
511
es.objective_function attribute.
512
513
Examples:
514
---------
515
>>> import cma
516
>>> import numpy as np
517
>>>
518
>>> # Minimize sphere subject to constraint x[0]^2 + x[1]^2 <= 1
519
>>> def objective(x):
520
... return sum(x**2)
521
>>>
522
>>> def constraints(x):
523
... return [x[0]**2 + x[1]**2 - 1] # <= 0 for feasibility
524
>>>
525
>>> x_best, es = cma.fmin_con2(
526
... objective,
527
... [0.5, 0.5],
528
... 0.3,
529
... constraints=constraints,
530
... options={'maxfevals': 2000, 'verb_disp': 100}
531
... )
532
>>>
533
>>> print(f"Best solution: {x_best}")
534
>>> print(f"Constraint value: {constraints(x_best)[0]:.6f}") # Should be <= 0
535
>>>
536
>>> # Multiple constraints
537
>>> def multiple_constraints(x):
538
... return [
539
... x[0]**2 + x[1]**2 - 1, # Circle constraint
540
... -x[0] - 0.5, # x[0] >= -0.5
541
... -x[1] - 0.5 # x[1] >= -0.5
542
... ]
543
>>>
544
>>> x_best, es = cma.fmin_con2(
545
... objective,
546
... [0, 0],
547
... 0.3,
548
... constraints=multiple_constraints
549
... )
550
>>>
551
>>> # Equality constraint h(x) = 0 as two inequalities
552
>>> def equality_as_inequalities(x):
553
... h_val = x[0] + x[1] - 1 # Want x[0] + x[1] = 1
554
... eps = 1e-6
555
... return [h_val - eps, -h_val - eps] # h-eps <= 0 and -h-eps <= 0
556
>>>
557
>>> x_best, es = cma.fmin_con2(
558
... objective,
559
... [0.5, 0.5],
560
... 0.2,
561
... constraints=equality_as_inequalities
562
... )
563
"""
564
pass
565
566
def fmin_con(
567
objective_function,
568
x0,
569
sigma0,
570
g=lambda x: [],
571
h=lambda x: [],
572
**kwargs
573
):
574
"""
575
DEPRECATED: Use fmin_con2 instead.
576
577
Legacy interface for constrained optimization. Provides separate
578
inequality (g) and equality (h) constraint functions.
579
580
Parameters:
581
-----------
582
objective_function : callable
583
Function to minimize.
584
585
x0 : array-like
586
Initial solution.
587
588
sigma0 : float
589
Initial step size.
590
591
g : callable, optional
592
Inequality constraints, g(x) <= 0 for feasibility.
593
594
h : callable, optional
595
Equality constraints, h(x) = 0 for feasibility.
596
597
**kwargs : dict
598
Additional arguments for fmin2.
599
600
Returns:
601
--------
602
tuple[numpy.ndarray, CMAEvolutionStrategy]
603
(xbest, es) with constraint information in es.best_feasible.
604
"""
605
pass
606
```
607
608
### Constraint Handler Classes
609
610
```python { .api }
611
class ConstrainedFitnessAL:
612
"""
613
Augmented Lagrangian fitness function for constrained optimization.
614
615
Transforms constrained optimization problem into sequence of unconstrained
616
problems using augmented Lagrangian method. Can be used directly for
617
fine-grained control over constraint handling.
618
"""
619
620
def __init__(
621
self,
622
objective_function,
623
constraints_function,
624
find_feasible_first=False,
625
logging=True
626
):
627
"""
628
Initialize augmented Lagrangian constraint handler.
629
630
Parameters:
631
-----------
632
objective_function : callable
633
Original objective function to minimize.
634
635
constraints_function : callable
636
Function returning list of constraint values (g(x) <= 0).
637
638
find_feasible_first : bool, optional
639
Whether to find feasible point before optimization.
640
641
logging : bool, optional
642
Whether to enable constraint violation logging.
643
644
Examples:
645
---------
646
>>> import cma
647
>>>
648
>>> def objective(x):
649
... return sum(x**2)
650
>>>
651
>>> def constraints(x):
652
... return [x[0] + x[1] - 1] # x[0] + x[1] <= 1
653
>>>
654
>>> # Create augmented Lagrangian fitness
655
>>> al_fitness = cma.ConstrainedFitnessAL(objective, constraints)
656
>>>
657
>>> # Use with CMA-ES
658
>>> es = cma.CMAEvolutionStrategy([0, 0], 0.3)
659
>>>
660
>>> while not es.stop():
661
... solutions = es.ask()
662
... fitness_values = [al_fitness(x) for x in solutions]
663
... es.tell(solutions, fitness_values)
664
... al_fitness.update(es) # Update Lagrange multipliers
665
>>>
666
>>> # Best feasible solution
667
>>> x_best = al_fitness.best_feasible.x
668
>>> print(f"Best feasible: {x_best}")
669
"""
670
pass
671
672
def __call__(self, x):
673
"""
674
Evaluate augmented Lagrangian function.
675
676
Parameters:
677
-----------
678
x : array-like
679
Solution to evaluate.
680
681
Returns:
682
--------
683
float
684
Augmented Lagrangian value (objective + constraint penalties).
685
"""
686
pass
687
688
def update(self, es):
689
"""
690
Update Lagrange multipliers and penalty parameters.
691
692
Should be called after each CMA-ES iteration (tell operation).
693
694
Parameters:
695
-----------
696
es : CMAEvolutionStrategy
697
Evolution strategy instance to get population information.
698
"""
699
pass
700
701
@property
702
def best_feasible(self):
703
"""
704
Best feasible solution found so far.
705
706
Returns:
707
--------
708
object
709
Object with attributes x (solution), f (objective value),
710
g (constraint values), and info dictionary.
711
"""
712
pass
713
714
class AugmentedLagrangian:
715
"""
716
Lower-level augmented Lagrangian implementation.
717
718
Provides direct access to augmented Lagrangian computations for
719
advanced users who need fine control over the constraint handling process.
720
"""
721
722
def __init__(self, dimension):
723
"""
724
Initialize augmented Lagrangian handler.
725
726
Parameters:
727
-----------
728
dimension : int
729
Problem dimension.
730
"""
731
pass
732
733
def __call__(self, x, f_x, g_x):
734
"""
735
Compute augmented Lagrangian value.
736
737
Parameters:
738
-----------
739
x : array-like
740
Solution vector.
741
742
f_x : float
743
Objective function value at x.
744
745
g_x : array-like
746
Constraint values at x.
747
748
Returns:
749
--------
750
float
751
Augmented Lagrangian value.
752
"""
753
pass
754
755
def update(self, X, F, G):
756
"""
757
Update Lagrange multipliers and penalty parameters.
758
759
Parameters:
760
-----------
761
X : list[array]
762
Population of solutions.
763
764
F : list[float]
765
Objective function values.
766
767
G : list[list]
768
Constraint values for each solution.
769
"""
770
pass
771
```
772
773
### Constrained Optimization Usage Patterns
774
775
```python { .api }
776
import cma
777
import numpy as np
778
779
# Pattern 1: Simple inequality constraint
780
def simple_inequality_constraint():
781
"""Optimization with single inequality constraint."""
782
783
def objective(x):
784
# Minimize distance to point [2, 2]
785
return sum((x - np.array([2, 2]))**2)
786
787
def constraints(x):
788
# Must stay within unit circle: x[0]^2 + x[1]^2 <= 1
789
return [x[0]**2 + x[1]**2 - 1]
790
791
x_best, es = cma.fmin_con2(
792
objective,
793
[0.5, 0.5], # Start inside feasible region
794
0.2,
795
constraints=constraints,
796
options={'maxfevals': 3000, 'verb_disp': 100}
797
)
798
799
print(f"Simple constraint result: {x_best}")
800
print(f"Constraint violation: {max(0, constraints(x_best)[0]):.6f}")
801
802
return x_best, es
803
804
# Pattern 2: Multiple inequality constraints
805
def multiple_inequality_constraints():
806
"""Optimization with multiple inequality constraints."""
807
808
def objective(x):
809
# Quadratic function with minimum at [1, 1, 1]
810
return sum((x - 1)**2)
811
812
def constraints(x):
813
return [
814
x[0] + x[1] + x[2] - 2, # x[0] + x[1] + x[2] <= 2
815
-x[0], # x[0] >= 0
816
-x[1], # x[1] >= 0
817
-x[2], # x[2] >= 0
818
x[0]**2 + x[1]**2 - 1 # x[0]^2 + x[1]^2 <= 1
819
]
820
821
x_best, es = cma.fmin_con2(
822
objective,
823
[0.3, 0.3, 0.3],
824
0.2,
825
constraints=constraints,
826
options={'maxfevals': 5000}
827
)
828
829
print(f"Multiple constraints result: {x_best}")
830
constraint_vals = constraints(x_best)
831
print(f"All constraints satisfied: {all(g <= 1e-6 for g in constraint_vals)}")
832
833
return x_best, es
834
835
# Pattern 3: Equality constraints via inequalities
836
def equality_constraint_example():
837
"""Handle equality constraints as pairs of inequalities."""
838
839
def objective(x):
840
return x[0]**2 + x[1]**2
841
842
def constraints_with_equality(x):
843
# Equality: x[0] + x[1] = 1 (as two inequalities)
844
h1 = x[0] + x[1] - 1
845
tolerance = 1e-6
846
847
return [
848
h1 - tolerance, # x[0] + x[1] <= 1 + tol
849
-h1 - tolerance, # x[0] + x[1] >= 1 - tol
850
-x[0] + 0.1, # x[0] >= -0.1 (inequality)
851
-x[1] + 0.1 # x[1] >= -0.1 (inequality)
852
]
853
854
x_best, es = cma.fmin_con2(
855
objective,
856
[0.5, 0.5],
857
0.2,
858
constraints=constraints_with_equality,
859
options={'maxfevals': 2000}
860
)
861
862
print(f"Equality constraint result: {x_best}")
863
print(f"Equality satisfied: {abs(x_best[0] + x_best[1] - 1):.6f}")
864
865
return x_best, es
866
867
# Pattern 4: Direct use of ConstrainedFitnessAL
868
def direct_constraint_handler_use():
869
"""Direct use of ConstrainedFitnessAL for advanced control."""
870
871
def objective(x):
872
return sum(x**4) # Quartic function
873
874
def constraints(x):
875
return [
876
x[0]**2 + x[1]**2 - 4, # Stay in circle of radius 2
877
x[0] * x[1] - 0.5 # Product constraint
878
]
879
880
# Create constraint handler directly
881
constraint_handler = cma.ConstrainedFitnessAL(
882
objective,
883
constraints,
884
find_feasible_first=True # Find feasible start
885
)
886
887
# Manual optimization loop
888
es = cma.CMAEvolutionStrategy([1, 1], 0.5)
889
890
iteration = 0
891
while not es.stop() and iteration < 200:
892
solutions = es.ask()
893
894
# Evaluate augmented Lagrangian fitness
895
fitness_values = [constraint_handler(x) for x in solutions]
896
897
es.tell(solutions, fitness_values)
898
899
# Update constraint handler
900
constraint_handler.update(es)
901
902
if iteration % 50 == 0:
903
best_feasible = constraint_handler.best_feasible
904
if best_feasible.x is not None:
905
print(f"Iter {iteration}: best feasible f = {best_feasible.f:.6f}")
906
else:
907
print(f"Iter {iteration}: no feasible solution yet")
908
909
iteration += 1
910
911
# Extract best feasible solution
912
best_feasible = constraint_handler.best_feasible
913
if best_feasible.x is not None:
914
print(f"Final feasible solution: {best_feasible.x}")
915
print(f"Final objective value: {best_feasible.f}")
916
print(f"Final constraint values: {best_feasible.g}")
917
else:
918
print("No feasible solution found")
919
920
return best_feasible, es
921
922
# Pattern 5: Feasibility search
923
def feasibility_search_example():
924
"""Find feasible solutions for difficult constraint sets."""
925
926
def objective(x):
927
# Minimize sum of squares (not the main goal)
928
return sum(x**2)
929
930
def difficult_constraints(x):
931
return [
932
x[0]**2 + x[1]**2 - 0.25, # Inside small circle
933
-(x[0] - 1)**2 - (x[1] - 1)**2 + 0.5, # Outside another circle
934
x[0] + x[1] - 1.2, # Above line
935
-x[0] - x[1] + 0.8 # Below line
936
]
937
938
# First find any feasible solution
939
x_feasible, es = cma.fmin_con2(
940
lambda x: 0, # Dummy objective - just find feasible point
941
[0, 0],
942
0.5,
943
constraints=difficult_constraints,
944
find_feasible_first=True,
945
options={'maxfevals': 5000, 'verb_disp': 200}
946
)
947
948
constraint_vals = difficult_constraints(x_feasible)
949
is_feasible = all(g <= 1e-6 for g in constraint_vals)
950
951
print(f"Found feasible solution: {is_feasible}")
952
if is_feasible:
953
print(f"Feasible point: {x_feasible}")
954
955
# Now optimize from feasible starting point
956
x_optimal, es2 = cma.fmin_con2(
957
objective, # Real objective
958
x_feasible, # Start from feasible point
959
0.1, # Smaller step size
960
constraints=difficult_constraints,
961
options={'maxfevals': 3000}
962
)
963
964
print(f"Optimized feasible solution: {x_optimal}")
965
print(f"Final objective: {objective(x_optimal):.6f}")
966
967
return x_feasible, es
968
969
# Run constraint examples
970
simple_inequality_constraint()
971
multiple_inequality_constraints()
972
equality_constraint_example()
973
direct_constraint_handler_use()
974
feasibility_search_example()
975
```
976
977
## Integration with Box Constraints
978
979
Combining general constraints with box constraints for comprehensive constraint handling.
980
981
```python { .api }
982
import cma
983
import numpy as np
984
985
def combined_constraints_example():
986
"""Example combining box constraints with general constraints."""
987
988
def objective(x):
989
# Rosenbrock function
990
return sum(100*(x[1:] - x[:-1]**2)**2 + (1 - x[:-1])**2)
991
992
def general_constraints(x):
993
# Nonlinear constraint
994
return [x[0]**2 + x[1]**2 - 2] # Stay in circle
995
996
# Box constraints: each variable in [-3, 3]
997
box_bounds = [[-3, -3], [3, 3]]
998
999
x_best, es = cma.fmin_con2(
1000
objective,
1001
[0.5, 0.5],
1002
0.3,
1003
constraints=general_constraints,
1004
options={
1005
'bounds': box_bounds, # Box constraints via options
1006
'maxfevals': 5000,
1007
'verb_disp': 100
1008
}
1009
)
1010
1011
print(f"Combined constraints result: {x_best}")
1012
print(f"In box bounds: {all(-3 <= xi <= 3 for xi in x_best)}")
1013
print(f"Satisfies circle: {general_constraints(x_best)[0] <= 0}")
1014
1015
return x_best, es
1016
1017
def integer_constrained_optimization():
1018
"""Example with integer variables and constraints."""
1019
1020
def objective(x):
1021
# Integer variables x[0], x[1] should be close to [3, 5]
1022
return (x[0] - 3)**2 + (x[1] - 5)**2 + sum(x[2:]**2)
1023
1024
def constraints(x):
1025
# Sum of integer variables must be <= 7
1026
return [x[0] + x[1] - 7]
1027
1028
x_best, es = cma.fmin_con2(
1029
objective,
1030
[2, 4, 0, 0],
1031
0.5,
1032
constraints=constraints,
1033
options={
1034
'integer_variables': [0, 1], # x[0], x[1] are integers
1035
'bounds': [[0, 0, -5, -5], [10, 10, 5, 5]], # Box bounds
1036
'maxfevals': 3000
1037
}
1038
)
1039
1040
print(f"Integer constrained result: {x_best}")
1041
print(f"Integer values: {[int(round(x_best[i])) for i in [0, 1]]}")
1042
print(f"Sum constraint: {x_best[0] + x_best[1]} <= 7")
1043
1044
return x_best, es
1045
1046
combined_constraints_example()
1047
integer_constrained_optimization()
1048
```
1049
1050
## Advanced Constraint Handling
1051
1052
Techniques for difficult constraint scenarios and performance optimization.
1053
1054
```python { .api }
1055
import cma
1056
import numpy as np
1057
1058
def constraint_violation_monitoring():
1059
"""Monitor and analyze constraint violations during optimization."""
1060
1061
class ConstraintMonitor:
1062
def __init__(self):
1063
self.violations = []
1064
self.iterations = []
1065
1066
def __call__(self, es):
1067
# Custom callback to monitor constraint violations
1068
if hasattr(es, 'objective_function'):
1069
cf = es.objective_function
1070
if hasattr(cf, 'best_feasible') and cf.best_feasible.x is not None:
1071
max_violation = max(0, max(cf.best_feasible.g))
1072
else:
1073
max_violation = float('inf')
1074
1075
self.violations.append(max_violation)
1076
self.iterations.append(es.countiter)
1077
1078
def objective(x):
1079
return sum((x - np.array([1, 2]))**2)
1080
1081
def constraints(x):
1082
return [
1083
x[0]**2 + x[1]**2 - 4, # Circle constraint
1084
-x[0] - x[1] + 2.5 # Linear constraint
1085
]
1086
1087
monitor = ConstraintMonitor()
1088
1089
x_best, es = cma.fmin_con2(
1090
objective,
1091
[0, 0],
1092
0.5,
1093
constraints=constraints,
1094
callback=monitor, # Add monitoring callback
1095
options={'maxfevals': 2000, 'verb_disp': 100}
1096
)
1097
1098
print(f"Monitored optimization result: {x_best}")
1099
print(f"Final max violation: {monitor.violations[-1]:.6f}")
1100
print(f"Violation decreased monotonically: {all(monitor.violations[i] >= monitor.violations[i+1] for i in range(len(monitor.violations)-1))}")
1101
1102
return x_best, monitor
1103
1104
def adaptive_constraint_parameters():
1105
"""Example with adaptive constraint handling parameters."""
1106
1107
def objective(x):
1108
return sum(x**2)
1109
1110
def constraints(x):
1111
return [x[0] + x[1] - 1, x[0]**2 + x[1]**2 - 2]
1112
1113
# Custom constraint handler with adaptive parameters
1114
constraint_handler = cma.ConstrainedFitnessAL(
1115
objective,
1116
constraints,
1117
logging=True
1118
)
1119
1120
# Manual optimization with parameter adaptation
1121
es = cma.CMAEvolutionStrategy([0.5, 0.5], 0.3)
1122
1123
while not es.stop():
1124
solutions = es.ask()
1125
fitness_values = [constraint_handler(x) for x in solutions]
1126
es.tell(solutions, fitness_values)
1127
constraint_handler.update(es)
1128
1129
# Adaptive parameter adjustment
1130
if es.countiter % 50 == 0:
1131
# Access internal augmented Lagrangian
1132
al = constraint_handler.al
1133
1134
# Example: adapt penalty parameters based on progress
1135
if hasattr(al, 'gamma') and es.countiter > 100:
1136
# Increase penalty if not making progress
1137
best = constraint_handler.best_feasible
1138
if best.x is not None and max(best.g) > 1e-3:
1139
al.gamma *= 1.1 # Increase penalty parameter
1140
1141
best_solution = constraint_handler.best_feasible
1142
print(f"Adaptive parameters result: {best_solution.x}")
1143
1144
return best_solution, es
1145
1146
def constraint_approximation_example():
1147
"""Handle expensive constraints using approximation."""
1148
1149
def objective(x):
1150
return sum((x - 1)**2)
1151
1152
# Simulate expensive constraint evaluation
1153
constraint_call_count = [0]
1154
1155
def expensive_constraints(x):
1156
constraint_call_count[0] += 1
1157
# Simulate computational cost
1158
import time
1159
time.sleep(0.001) # Simulate 1ms computation
1160
1161
return [
1162
x[0]**2 + x[1]**2 - 1, # Circle constraint
1163
x[0] * x[1] + 0.5 # Product constraint
1164
]
1165
1166
# Cache constraint evaluations
1167
constraint_cache = {}
1168
1169
def cached_constraints(x):
1170
x_key = tuple(np.round(x, 6)) # Round for cache key
1171
if x_key in constraint_cache:
1172
return constraint_cache[x_key]
1173
1174
result = expensive_constraints(x)
1175
constraint_cache[x_key] = result
1176
return result
1177
1178
x_best, es = cma.fmin_con2(
1179
objective,
1180
[0.5, 0.5],
1181
0.2,
1182
constraints=cached_constraints,
1183
options={'maxfevals': 1000, 'verb_disp': 50}
1184
)
1185
1186
print(f"Cached constraints result: {x_best}")
1187
print(f"Constraint evaluations: {constraint_call_count[0]}")
1188
print(f"Cache entries: {len(constraint_cache)}")
1189
1190
return x_best, es
1191
1192
# Run advanced constraint examples
1193
constraint_violation_monitoring()
1194
adaptive_constraint_parameters()
1195
constraint_approximation_example()
1196
```