morphml 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of morphml might be problematic. Click here for more details.

Files changed (158) hide show
  1. morphml/__init__.py +14 -0
  2. morphml/api/__init__.py +26 -0
  3. morphml/api/app.py +326 -0
  4. morphml/api/auth.py +193 -0
  5. morphml/api/client.py +338 -0
  6. morphml/api/models.py +132 -0
  7. morphml/api/rate_limit.py +192 -0
  8. morphml/benchmarking/__init__.py +36 -0
  9. morphml/benchmarking/comparison.py +430 -0
  10. morphml/benchmarks/__init__.py +56 -0
  11. morphml/benchmarks/comparator.py +409 -0
  12. morphml/benchmarks/datasets.py +280 -0
  13. morphml/benchmarks/metrics.py +199 -0
  14. morphml/benchmarks/openml_suite.py +201 -0
  15. morphml/benchmarks/problems.py +289 -0
  16. morphml/benchmarks/suite.py +318 -0
  17. morphml/cli/__init__.py +5 -0
  18. morphml/cli/commands/experiment.py +329 -0
  19. morphml/cli/main.py +457 -0
  20. morphml/cli/quickstart.py +312 -0
  21. morphml/config.py +278 -0
  22. morphml/constraints/__init__.py +19 -0
  23. morphml/constraints/handler.py +205 -0
  24. morphml/constraints/predicates.py +285 -0
  25. morphml/core/__init__.py +3 -0
  26. morphml/core/crossover.py +449 -0
  27. morphml/core/dsl/README.md +359 -0
  28. morphml/core/dsl/__init__.py +72 -0
  29. morphml/core/dsl/ast_nodes.py +364 -0
  30. morphml/core/dsl/compiler.py +318 -0
  31. morphml/core/dsl/layers.py +368 -0
  32. morphml/core/dsl/lexer.py +336 -0
  33. morphml/core/dsl/parser.py +455 -0
  34. morphml/core/dsl/search_space.py +386 -0
  35. morphml/core/dsl/syntax.py +199 -0
  36. morphml/core/dsl/type_system.py +361 -0
  37. morphml/core/dsl/validator.py +386 -0
  38. morphml/core/graph/__init__.py +40 -0
  39. morphml/core/graph/edge.py +124 -0
  40. morphml/core/graph/graph.py +507 -0
  41. morphml/core/graph/mutations.py +409 -0
  42. morphml/core/graph/node.py +196 -0
  43. morphml/core/graph/serialization.py +361 -0
  44. morphml/core/graph/visualization.py +431 -0
  45. morphml/core/objectives/__init__.py +20 -0
  46. morphml/core/search/__init__.py +33 -0
  47. morphml/core/search/individual.py +252 -0
  48. morphml/core/search/parameters.py +453 -0
  49. morphml/core/search/population.py +375 -0
  50. morphml/core/search/search_engine.py +340 -0
  51. morphml/distributed/__init__.py +76 -0
  52. morphml/distributed/fault_tolerance.py +497 -0
  53. morphml/distributed/health_monitor.py +348 -0
  54. morphml/distributed/master.py +709 -0
  55. morphml/distributed/proto/README.md +224 -0
  56. morphml/distributed/proto/__init__.py +74 -0
  57. morphml/distributed/proto/worker.proto +170 -0
  58. morphml/distributed/proto/worker_pb2.py +79 -0
  59. morphml/distributed/proto/worker_pb2_grpc.py +423 -0
  60. morphml/distributed/resource_manager.py +416 -0
  61. morphml/distributed/scheduler.py +567 -0
  62. morphml/distributed/storage/__init__.py +33 -0
  63. morphml/distributed/storage/artifacts.py +381 -0
  64. morphml/distributed/storage/cache.py +366 -0
  65. morphml/distributed/storage/checkpointing.py +329 -0
  66. morphml/distributed/storage/database.py +459 -0
  67. morphml/distributed/worker.py +549 -0
  68. morphml/evaluation/__init__.py +5 -0
  69. morphml/evaluation/heuristic.py +237 -0
  70. morphml/exceptions.py +55 -0
  71. morphml/execution/__init__.py +5 -0
  72. morphml/execution/local_executor.py +350 -0
  73. morphml/integrations/__init__.py +28 -0
  74. morphml/integrations/jax_adapter.py +206 -0
  75. morphml/integrations/pytorch_adapter.py +530 -0
  76. morphml/integrations/sklearn_adapter.py +206 -0
  77. morphml/integrations/tensorflow_adapter.py +230 -0
  78. morphml/logging_config.py +93 -0
  79. morphml/meta_learning/__init__.py +66 -0
  80. morphml/meta_learning/architecture_similarity.py +277 -0
  81. morphml/meta_learning/experiment_database.py +240 -0
  82. morphml/meta_learning/knowledge_base/__init__.py +19 -0
  83. morphml/meta_learning/knowledge_base/embedder.py +179 -0
  84. morphml/meta_learning/knowledge_base/knowledge_base.py +313 -0
  85. morphml/meta_learning/knowledge_base/meta_features.py +265 -0
  86. morphml/meta_learning/knowledge_base/vector_store.py +271 -0
  87. morphml/meta_learning/predictors/__init__.py +27 -0
  88. morphml/meta_learning/predictors/ensemble.py +221 -0
  89. morphml/meta_learning/predictors/gnn_predictor.py +552 -0
  90. morphml/meta_learning/predictors/learning_curve.py +231 -0
  91. morphml/meta_learning/predictors/proxy_metrics.py +261 -0
  92. morphml/meta_learning/strategy_evolution/__init__.py +27 -0
  93. morphml/meta_learning/strategy_evolution/adaptive_optimizer.py +226 -0
  94. morphml/meta_learning/strategy_evolution/bandit.py +276 -0
  95. morphml/meta_learning/strategy_evolution/portfolio.py +230 -0
  96. morphml/meta_learning/transfer.py +581 -0
  97. morphml/meta_learning/warm_start.py +286 -0
  98. morphml/optimizers/__init__.py +74 -0
  99. morphml/optimizers/adaptive_operators.py +399 -0
  100. morphml/optimizers/bayesian/__init__.py +52 -0
  101. morphml/optimizers/bayesian/acquisition.py +387 -0
  102. morphml/optimizers/bayesian/base.py +319 -0
  103. morphml/optimizers/bayesian/gaussian_process.py +635 -0
  104. morphml/optimizers/bayesian/smac.py +534 -0
  105. morphml/optimizers/bayesian/tpe.py +411 -0
  106. morphml/optimizers/differential_evolution.py +220 -0
  107. morphml/optimizers/evolutionary/__init__.py +61 -0
  108. morphml/optimizers/evolutionary/cma_es.py +416 -0
  109. morphml/optimizers/evolutionary/differential_evolution.py +556 -0
  110. morphml/optimizers/evolutionary/encoding.py +426 -0
  111. morphml/optimizers/evolutionary/particle_swarm.py +449 -0
  112. morphml/optimizers/genetic_algorithm.py +486 -0
  113. morphml/optimizers/gradient_based/__init__.py +22 -0
  114. morphml/optimizers/gradient_based/darts.py +550 -0
  115. morphml/optimizers/gradient_based/enas.py +585 -0
  116. morphml/optimizers/gradient_based/operations.py +474 -0
  117. morphml/optimizers/gradient_based/utils.py +601 -0
  118. morphml/optimizers/hill_climbing.py +169 -0
  119. morphml/optimizers/multi_objective/__init__.py +56 -0
  120. morphml/optimizers/multi_objective/indicators.py +504 -0
  121. morphml/optimizers/multi_objective/nsga2.py +647 -0
  122. morphml/optimizers/multi_objective/visualization.py +427 -0
  123. morphml/optimizers/nsga2.py +308 -0
  124. morphml/optimizers/random_search.py +172 -0
  125. morphml/optimizers/simulated_annealing.py +181 -0
  126. morphml/plugins/__init__.py +35 -0
  127. morphml/plugins/custom_evaluator_example.py +81 -0
  128. morphml/plugins/custom_optimizer_example.py +63 -0
  129. morphml/plugins/plugin_system.py +454 -0
  130. morphml/reports/__init__.py +30 -0
  131. morphml/reports/generator.py +362 -0
  132. morphml/tracking/__init__.py +7 -0
  133. morphml/tracking/experiment.py +309 -0
  134. morphml/tracking/logger.py +301 -0
  135. morphml/tracking/reporter.py +357 -0
  136. morphml/utils/__init__.py +6 -0
  137. morphml/utils/checkpoint.py +189 -0
  138. morphml/utils/comparison.py +390 -0
  139. morphml/utils/export.py +407 -0
  140. morphml/utils/progress.py +392 -0
  141. morphml/utils/validation.py +392 -0
  142. morphml/version.py +7 -0
  143. morphml/visualization/__init__.py +50 -0
  144. morphml/visualization/analytics.py +423 -0
  145. morphml/visualization/architecture_diagrams.py +353 -0
  146. morphml/visualization/architecture_plot.py +223 -0
  147. morphml/visualization/convergence_plot.py +174 -0
  148. morphml/visualization/crossover_viz.py +386 -0
  149. morphml/visualization/graph_viz.py +338 -0
  150. morphml/visualization/pareto_plot.py +149 -0
  151. morphml/visualization/plotly_dashboards.py +422 -0
  152. morphml/visualization/population.py +309 -0
  153. morphml/visualization/progress.py +260 -0
  154. morphml-1.0.0.dist-info/METADATA +434 -0
  155. morphml-1.0.0.dist-info/RECORD +158 -0
  156. morphml-1.0.0.dist-info/WHEEL +4 -0
  157. morphml-1.0.0.dist-info/entry_points.txt +3 -0
  158. morphml-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,449 @@
1
+ """Particle Swarm Optimization (PSO) for neural architecture search.
2
+
3
+ PSO is a swarm intelligence algorithm inspired by social behavior of bird flocking
4
+ and fish schooling. Particles move through search space guided by their personal
5
+ best positions and the global best position.
6
+
7
+ Key Features:
8
+ - Swarm intelligence
9
+ - Velocity-based movement
10
+ - Cognitive and social components
11
+ - No gradient information needed
12
+ - Works in continuous spaces
13
+
14
+ Reference:
15
+ Kennedy, J., and Eberhart, R. "Particle Swarm Optimization."
16
+ IEEE International Conference on Neural Networks, 1995.
17
+
18
+ Author: Eshan Roy <eshanized@proton.me>
19
+ Organization: TONMOY INFRASTRUCTURE & VISION
20
+ """
21
+
22
+ from dataclasses import dataclass
23
+ from typing import Any, Callable, Dict, List, Optional
24
+
25
+ import numpy as np
26
+
27
+ from morphml.core.dsl import SearchSpace
28
+ from morphml.core.search import Individual
29
+ from morphml.logging_config import get_logger
30
+ from morphml.optimizers.evolutionary.encoding import ArchitectureEncoder
31
+
32
+ logger = get_logger(__name__)
33
+
34
+
35
+ @dataclass
36
+ class Particle:
37
+ """
38
+ Particle in PSO swarm.
39
+
40
+ Each particle has:
41
+ - Current position in search space
42
+ - Current velocity
43
+ - Personal best position found so far
44
+ - Personal best fitness
45
+
46
+ Attributes:
47
+ position: Current position vector
48
+ velocity: Current velocity vector
49
+ best_position: Personal best position
50
+ best_fitness: Personal best fitness value
51
+ fitness: Current fitness
52
+
53
+ Example:
54
+ >>> particle = Particle(
55
+ ... position=np.random.rand(50),
56
+ ... velocity=np.random.rand(50) * 0.1,
57
+ ... best_position=np.random.rand(50),
58
+ ... best_fitness=-np.inf
59
+ ... )
60
+ """
61
+
62
+ position: np.ndarray
63
+ velocity: np.ndarray
64
+ best_position: np.ndarray
65
+ best_fitness: float = -np.inf
66
+ fitness: float = 0.0
67
+
68
+
69
+ class ParticleSwarmOptimizer:
70
+ """
71
+ Particle Swarm Optimization for architecture search.
72
+
73
+ PSO uses a swarm of particles that move through continuous search space.
74
+ Each particle:
75
+ 1. Remembers its personal best position (cognitive component)
76
+ 2. Is attracted to the global best position (social component)
77
+ 3. Has inertia that maintains its previous direction
78
+
79
+ Update Equations:
80
+ v_i(t+1) = w*v_i(t) + c1*r1*(p_i - x_i(t)) + c2*r2*(g - x_i(t))
81
+ x_i(t+1) = x_i(t) + v_i(t+1)
82
+
83
+ where:
84
+ - v_i: velocity of particle i
85
+ - x_i: position of particle i
86
+ - p_i: personal best of particle i
87
+ - g: global best position
88
+ - w: inertia weight (controls exploration vs exploitation)
89
+ - c1: cognitive coefficient (attraction to personal best)
90
+ - c2: social coefficient (attraction to global best)
91
+ - r1, r2: random numbers in [0,1]
92
+
93
+ Configuration:
94
+ num_particles: Swarm size (default: 30)
95
+ max_iterations: Maximum iterations (default: 100)
96
+ w: Inertia weight (default: 0.7)
97
+ w_min: Minimum inertia (default: 0.4)
98
+ w_max: Maximum inertia (default: 0.9)
99
+ c1: Cognitive coefficient (default: 1.5)
100
+ c2: Social coefficient (default: 1.5)
101
+ max_velocity: Velocity clamping (default: 0.5)
102
+
103
+ Example:
104
+ >>> from morphml.optimizers.evolutionary import ParticleSwarmOptimizer
105
+ >>> optimizer = ParticleSwarmOptimizer(
106
+ ... search_space=space,
107
+ ... config={
108
+ ... 'num_particles': 30,
109
+ ... 'max_iterations': 100,
110
+ ... 'w': 0.7,
111
+ ... 'c1': 1.5,
112
+ ... 'c2': 1.5
113
+ ... }
114
+ ... )
115
+ >>> best = optimizer.optimize(evaluator)
116
+ """
117
+
118
+ def __init__(self, search_space: SearchSpace, config: Optional[Dict[str, Any]] = None):
119
+ """
120
+ Initialize PSO optimizer.
121
+
122
+ Args:
123
+ search_space: SearchSpace for architecture sampling
124
+ config: Configuration dictionary
125
+ """
126
+ self.search_space = search_space
127
+ self.config = config or {}
128
+
129
+ # PSO parameters
130
+ self.num_particles = self.config.get("num_particles", 30)
131
+ self.max_iterations = self.config.get("max_iterations", 100)
132
+
133
+ # Inertia weight (can be adaptive)
134
+ self.w = self.config.get("w", 0.7)
135
+ self.w_min = self.config.get("w_min", 0.4)
136
+ self.w_max = self.config.get("w_max", 0.9)
137
+ self.adaptive_inertia = self.config.get("adaptive_inertia", True)
138
+
139
+ # Cognitive and social coefficients
140
+ self.c1 = self.config.get("c1", 1.5) # Personal best attraction
141
+ self.c2 = self.config.get("c2", 1.5) # Global best attraction
142
+
143
+ # Velocity clamping
144
+ self.max_velocity = self.config.get("max_velocity", 0.5)
145
+
146
+ # Architecture encoding
147
+ self.max_nodes = self.config.get("max_nodes", 20)
148
+ self.encoder = ArchitectureEncoder(search_space, self.max_nodes)
149
+ self.dim = self.encoder.get_dimension()
150
+
151
+ # Swarm state
152
+ self.particles: List[Particle] = []
153
+ self.global_best_position: Optional[np.ndarray] = None
154
+ self.global_best_fitness: float = -np.inf
155
+ self.global_best_individual: Optional[Individual] = None
156
+
157
+ # History
158
+ self.iteration = 0
159
+ self.history: List[Dict[str, Any]] = []
160
+
161
+ logger.info(
162
+ f"Initialized PSO: {self.num_particles} particles, "
163
+ f"dimension={self.dim}, max_iterations={self.max_iterations}"
164
+ )
165
+
166
+ def initialize_swarm(self) -> None:
167
+ """Initialize swarm with random particles."""
168
+ self.particles = []
169
+
170
+ for _i in range(self.num_particles):
171
+ # Random position in [0, 1]^dim
172
+ position = np.random.rand(self.dim)
173
+
174
+ # Random velocity (small initial values)
175
+ velocity = (np.random.rand(self.dim) - 0.5) * 0.1
176
+
177
+ # Create particle
178
+ particle = Particle(
179
+ position=position,
180
+ velocity=velocity,
181
+ best_position=position.copy(),
182
+ best_fitness=-np.inf,
183
+ )
184
+
185
+ self.particles.append(particle)
186
+
187
+ logger.debug(f"Initialized swarm of {len(self.particles)} particles")
188
+
189
+ def evaluate_particle(self, particle: Particle, evaluator: Callable) -> None:
190
+ """
191
+ Evaluate fitness of a particle.
192
+
193
+ Args:
194
+ particle: Particle to evaluate
195
+ evaluator: Fitness evaluation function
196
+ """
197
+ # Decode position to architecture
198
+ graph = self.encoder.decode(particle.position)
199
+
200
+ # Evaluate
201
+ fitness = evaluator(graph)
202
+ particle.fitness = fitness
203
+
204
+ # Update personal best
205
+ if fitness > particle.best_fitness:
206
+ particle.best_position = particle.position.copy()
207
+ particle.best_fitness = fitness
208
+
209
+ # Update global best
210
+ if fitness > self.global_best_fitness:
211
+ self.global_best_fitness = fitness
212
+ self.global_best_position = particle.position.copy()
213
+
214
+ # Store as Individual
215
+ individual = Individual(graph)
216
+ individual.fitness = fitness
217
+ self.global_best_individual = individual
218
+
219
+ def update_velocity(self, particle: Particle, iteration: int) -> np.ndarray:
220
+ """
221
+ Update particle velocity using PSO equations.
222
+
223
+ Args:
224
+ particle: Particle to update
225
+ iteration: Current iteration number
226
+
227
+ Returns:
228
+ New velocity vector
229
+ """
230
+ # Adaptive inertia weight (linearly decreasing)
231
+ if self.adaptive_inertia:
232
+ w = self.w_max - (self.w_max - self.w_min) * iteration / self.max_iterations
233
+ else:
234
+ w = self.w
235
+
236
+ # Random coefficients for stochasticity
237
+ r1 = np.random.rand(self.dim)
238
+ r2 = np.random.rand(self.dim)
239
+
240
+ # Cognitive component (personal best attraction)
241
+ cognitive = self.c1 * r1 * (particle.best_position - particle.position)
242
+
243
+ # Social component (global best attraction)
244
+ social = self.c2 * r2 * (self.global_best_position - particle.position)
245
+
246
+ # New velocity
247
+ new_velocity = w * particle.velocity + cognitive + social
248
+
249
+ # Velocity clamping to prevent explosion
250
+ new_velocity = np.clip(new_velocity, -self.max_velocity, self.max_velocity)
251
+
252
+ return new_velocity
253
+
254
+ def update_position(self, particle: Particle) -> np.ndarray:
255
+ """
256
+ Update particle position.
257
+
258
+ Args:
259
+ particle: Particle to update
260
+
261
+ Returns:
262
+ New position vector
263
+ """
264
+ new_position = particle.position + particle.velocity
265
+
266
+ # Clamp to bounds [0, 1]
267
+ new_position = np.clip(new_position, 0.0, 1.0)
268
+
269
+ return new_position
270
+
271
+ def optimize(self, evaluator: Callable) -> Individual:
272
+ """
273
+ Run PSO optimization.
274
+
275
+ Args:
276
+ evaluator: Function that evaluates ModelGraph -> fitness
277
+
278
+ Returns:
279
+ Best Individual found
280
+
281
+ Example:
282
+ >>> def my_evaluator(graph):
283
+ ... return train_and_evaluate(graph)
284
+ >>> best = optimizer.optimize(my_evaluator)
285
+ >>> print(f"Best fitness: {best.fitness:.4f}")
286
+ """
287
+ logger.info(f"Starting PSO optimization for {self.max_iterations} iterations")
288
+
289
+ # Initialize swarm
290
+ self.initialize_swarm()
291
+
292
+ # Evaluate initial swarm
293
+ for particle in self.particles:
294
+ self.evaluate_particle(particle, evaluator)
295
+
296
+ # Main PSO loop
297
+ for iteration in range(self.max_iterations):
298
+ self.iteration = iteration
299
+
300
+ # Update each particle
301
+ for particle in self.particles:
302
+ # Update velocity
303
+ particle.velocity = self.update_velocity(particle, iteration)
304
+
305
+ # Update position
306
+ particle.position = self.update_position(particle)
307
+
308
+ # Evaluate new position
309
+ self.evaluate_particle(particle, evaluator)
310
+
311
+ # Record history
312
+ avg_fitness = np.mean([p.fitness for p in self.particles])
313
+ best_fitness = max(p.fitness for p in self.particles)
314
+
315
+ self.history.append(
316
+ {
317
+ "iteration": iteration,
318
+ "global_best_fitness": self.global_best_fitness,
319
+ "avg_fitness": avg_fitness,
320
+ "best_fitness": best_fitness,
321
+ }
322
+ )
323
+
324
+ # Logging
325
+ if iteration % 10 == 0 or iteration == self.max_iterations - 1:
326
+ logger.info(
327
+ f"Iteration {iteration}/{self.max_iterations}: "
328
+ f"global_best={self.global_best_fitness:.4f}, "
329
+ f"avg={avg_fitness:.4f}"
330
+ )
331
+
332
+ logger.info(f"PSO complete. Best fitness: {self.global_best_fitness:.4f}")
333
+
334
+ return self.global_best_individual
335
+
336
+ def get_history(self) -> List[Dict[str, Any]]:
337
+ """Get optimization history."""
338
+ return self.history
339
+
340
+ def plot_convergence(self, save_path: Optional[str] = None) -> None:
341
+ """
342
+ Plot PSO convergence.
343
+
344
+ Args:
345
+ save_path: Optional path to save plot
346
+ """
347
+ try:
348
+ import matplotlib.pyplot as plt
349
+ except ImportError:
350
+ logger.warning("matplotlib not available, cannot plot")
351
+ return
352
+
353
+ if not self.history:
354
+ logger.warning("No history to plot")
355
+ return
356
+
357
+ iterations = [h["iteration"] for h in self.history]
358
+ global_best = [h["global_best_fitness"] for h in self.history]
359
+ avg_fitness = [h["avg_fitness"] for h in self.history]
360
+
361
+ plt.figure(figsize=(10, 6))
362
+ plt.plot(iterations, global_best, "b-", linewidth=2, label="Global Best")
363
+ plt.plot(iterations, avg_fitness, "r--", linewidth=2, label="Average Fitness")
364
+
365
+ plt.xlabel("Iteration", fontsize=12)
366
+ plt.ylabel("Fitness", fontsize=12)
367
+ plt.title("PSO Convergence", fontsize=14, fontweight="bold")
368
+ plt.legend()
369
+ plt.grid(True, alpha=0.3)
370
+ plt.tight_layout()
371
+
372
+ if save_path:
373
+ plt.savefig(save_path, dpi=300, bbox_inches="tight")
374
+ logger.info(f"Convergence plot saved to {save_path}")
375
+ else:
376
+ plt.show()
377
+
378
+ plt.close()
379
+
380
+ def __repr__(self) -> str:
381
+ """String representation."""
382
+ return (
383
+ f"ParticleSwarmOptimizer("
384
+ f"particles={self.num_particles}, "
385
+ f"w={self.w:.2f}, "
386
+ f"c1={self.c1:.2f}, "
387
+ f"c2={self.c2:.2f})"
388
+ )
389
+
390
+
391
+ # Convenience function
392
+ def optimize_with_pso(
393
+ search_space: SearchSpace,
394
+ evaluator: Callable,
395
+ num_particles: int = 30,
396
+ max_iterations: int = 100,
397
+ w: float = 0.7,
398
+ c1: float = 1.5,
399
+ c2: float = 1.5,
400
+ verbose: bool = True,
401
+ ) -> Individual:
402
+ """
403
+ Quick PSO optimization with sensible defaults.
404
+
405
+ Args:
406
+ search_space: SearchSpace to optimize over
407
+ evaluator: Fitness evaluation function
408
+ num_particles: Swarm size
409
+ max_iterations: Maximum iterations
410
+ w: Inertia weight
411
+ c1: Cognitive coefficient
412
+ c2: Social coefficient
413
+ verbose: Print progress
414
+
415
+ Returns:
416
+ Best Individual found
417
+
418
+ Example:
419
+ >>> from morphml.optimizers.evolutionary import optimize_with_pso
420
+ >>> best = optimize_with_pso(
421
+ ... search_space=space,
422
+ ... evaluator=my_evaluator,
423
+ ... num_particles=30,
424
+ ... max_iterations=100
425
+ ... )
426
+ """
427
+ optimizer = ParticleSwarmOptimizer(
428
+ search_space=search_space,
429
+ config={
430
+ "num_particles": num_particles,
431
+ "max_iterations": max_iterations,
432
+ "w": w,
433
+ "c1": c1,
434
+ "c2": c2,
435
+ },
436
+ )
437
+
438
+ best = optimizer.optimize(evaluator)
439
+
440
+ if verbose:
441
+ print(f"\n{'='*60}")
442
+ print("PSO Optimization Complete")
443
+ print(f"{'='*60}")
444
+ print(f"Best Fitness: {best.fitness:.4f}")
445
+ print(f"Iterations: {max_iterations}")
446
+ print(f"Swarm Size: {num_particles}")
447
+ print(f"{'='*60}\n")
448
+
449
+ return best