ctrl-freak 0.1.0__tar.gz → 0.2.0__tar.gz

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.
Files changed (29) hide show
  1. ctrl_freak-0.2.0/PKG-INFO +130 -0
  2. ctrl_freak-0.2.0/README.md +102 -0
  3. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/pyproject.toml +3 -2
  4. ctrl_freak-0.1.0/PKG-INFO +0 -238
  5. ctrl_freak-0.1.0/README.md +0 -210
  6. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/LICENSE +0 -0
  7. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/__init__.py +0 -0
  8. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/algorithms/__init__.py +0 -0
  9. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/algorithms/ga.py +0 -0
  10. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/algorithms/nsga2.py +0 -0
  11. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/operators/__init__.py +0 -0
  12. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/operators/base.py +0 -0
  13. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/operators/selection.py +0 -0
  14. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/operators/standard.py +0 -0
  15. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/population.py +0 -0
  16. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/primitives/__init__.py +0 -0
  17. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/primitives/pareto.py +0 -0
  18. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/protocols.py +0 -0
  19. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/py.typed +0 -0
  20. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/registry.py +0 -0
  21. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/results.py +0 -0
  22. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/selection/__init__.py +0 -0
  23. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/selection/crowded.py +0 -0
  24. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/selection/roulette.py +0 -0
  25. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/selection/tournament.py +0 -0
  26. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/survival/__init__.py +0 -0
  27. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/survival/elitist.py +0 -0
  28. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/survival/nsga2.py +0 -0
  29. {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/survival/truncation.py +0 -0
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.4
2
+ Name: ctrl-freak
3
+ Version: 0.2.0
4
+ Summary: Pure-numpy genetic algorithm framework for single-objective (GA) and multi-objective (NSGA-II) optimization
5
+ Keywords: genetic-algorithm,evolutionary-algorithm,optimization,multi-objective-optimization,nsga-ii,nsga2,pareto,numpy
6
+ Author: Nicolas Lazaro
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Scientific/Engineering
17
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
18
+ Classifier: Operating System :: OS Independent
19
+ Requires-Dist: numpy>=2.0
20
+ Requires-Dist: joblib>=1.3.0
21
+ Requires-Python: >=3.11
22
+ Project-URL: Changelog, https://github.com/hydrosolutions/ctrl-freak/blob/main/CHANGELOG.md
23
+ Project-URL: Documentation, https://hydrosolutions.github.io/ctrl-freak/
24
+ Project-URL: Homepage, https://github.com/hydrosolutions/ctrl-freak
25
+ Project-URL: Issues, https://github.com/hydrosolutions/ctrl-freak/issues
26
+ Project-URL: Repository, https://github.com/hydrosolutions/ctrl-freak
27
+ Description-Content-Type: text/markdown
28
+
29
+ # ctrl-freak
30
+
31
+ [![PyPI](https://img.shields.io/pypi/v/ctrl-freak.svg)](https://pypi.org/project/ctrl-freak/)
32
+ [![Python](https://img.shields.io/pypi/pyversions/ctrl-freak.svg)](https://pypi.org/project/ctrl-freak/)
33
+ [![License](https://img.shields.io/pypi/l/ctrl-freak.svg)](LICENSE)
34
+
35
+ An extensible genetic algorithm framework for single and multi-objective optimization, built on pure numpy.
36
+
37
+ ## Installation
38
+
39
+ ```bash
40
+ uv add ctrl-freak
41
+ ```
42
+
43
+ or:
44
+
45
+ ```bash
46
+ pip install ctrl-freak
47
+ ```
48
+
49
+ ## Quick Start
50
+
51
+ ```python
52
+ import numpy as np
53
+ from ctrl_freak import nsga2, ga
54
+
55
+ # === Multi-Objective: NSGA-II ===
56
+ def init(rng):
57
+ return rng.uniform(0, 1, size=5)
58
+
59
+ def evaluate_multi(x):
60
+ f1 = x[0]
61
+ f2 = 1 - np.sqrt(x[0]) + x[1:]@x[1:]
62
+ return np.array([f1, f2])
63
+
64
+ def crossover(p1, p2):
65
+ return (p1 + p2) / 2
66
+
67
+ def mutate(x):
68
+ return np.clip(x + np.random.normal(0, 0.1, size=x.shape), 0, 1)
69
+
70
+ result = nsga2(
71
+ init=init,
72
+ evaluate=evaluate_multi,
73
+ crossover=crossover,
74
+ mutate=mutate,
75
+ pop_size=100,
76
+ n_generations=50,
77
+ seed=42,
78
+ )
79
+
80
+ # Extract Pareto front
81
+ pareto_front = result.pareto_front
82
+ print(f"Found {len(pareto_front)} Pareto-optimal solutions")
83
+
84
+ # === Single-Objective: Standard GA ===
85
+ def evaluate_single(x):
86
+ return float(np.sum(x ** 2)) # Sphere function
87
+
88
+ result = ga(
89
+ init=init,
90
+ evaluate=evaluate_single,
91
+ crossover=crossover,
92
+ mutate=mutate,
93
+ pop_size=100,
94
+ n_generations=100,
95
+ seed=42,
96
+ )
97
+
98
+ print(f"Best fitness: {result.best[1]:.6f}")
99
+ ```
100
+
101
+ ## Features
102
+
103
+ - Single-objective optimization with `ga()`.
104
+ - Multi-objective optimization with `nsga2()`.
105
+ - Pluggable parent-selection and survival strategies.
106
+ - Pure numpy primitives for Pareto dominance, non-dominated sorting, and crowding distance.
107
+ - Parallel evaluation through `n_workers`.
108
+
109
+ See the [full documentation](https://hydrosolutions.github.io/ctrl-freak/) for API details, user contracts, examples, and extension points.
110
+
111
+ ## Benchmarks
112
+
113
+ ctrl-freak ships a validation benchmark suite that checks `ga()` and `nsga2()`
114
+ against pymoo and DEAP on standard problems with known optima. With the genetic
115
+ algorithm held identical across all three libraries (ported SBX, aligned mutation
116
+ and selection, identical evaluation budget), ctrl-freak's results are statistically
117
+ indistinguishable from both baselines on the single-objective error metrics (all
118
+ six functions) and on multi-objective convergence (ZDT1, ZDT2, ZDT3, and DTLZ2). On
119
+ the two hardest problems (ZDT4, ZDT6) none of the three libraries converges at this
120
+ budget, and ctrl-freak is at least as good as both. The goal is parity, not
121
+ superiority.
122
+
123
+ See the canonical report in [benchmarks/README.md](benchmarks/README.md) and the
124
+ citable [Validation page](https://hydrosolutions.github.io/ctrl-freak/validation/).
125
+
126
+ ## Links
127
+
128
+ - [Documentation](https://hydrosolutions.github.io/ctrl-freak/)
129
+ - [Changelog](CHANGELOG.md)
130
+ - [License](LICENSE)
@@ -0,0 +1,102 @@
1
+ # ctrl-freak
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/ctrl-freak.svg)](https://pypi.org/project/ctrl-freak/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/ctrl-freak.svg)](https://pypi.org/project/ctrl-freak/)
5
+ [![License](https://img.shields.io/pypi/l/ctrl-freak.svg)](LICENSE)
6
+
7
+ An extensible genetic algorithm framework for single and multi-objective optimization, built on pure numpy.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ uv add ctrl-freak
13
+ ```
14
+
15
+ or:
16
+
17
+ ```bash
18
+ pip install ctrl-freak
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ```python
24
+ import numpy as np
25
+ from ctrl_freak import nsga2, ga
26
+
27
+ # === Multi-Objective: NSGA-II ===
28
+ def init(rng):
29
+ return rng.uniform(0, 1, size=5)
30
+
31
+ def evaluate_multi(x):
32
+ f1 = x[0]
33
+ f2 = 1 - np.sqrt(x[0]) + x[1:]@x[1:]
34
+ return np.array([f1, f2])
35
+
36
+ def crossover(p1, p2):
37
+ return (p1 + p2) / 2
38
+
39
+ def mutate(x):
40
+ return np.clip(x + np.random.normal(0, 0.1, size=x.shape), 0, 1)
41
+
42
+ result = nsga2(
43
+ init=init,
44
+ evaluate=evaluate_multi,
45
+ crossover=crossover,
46
+ mutate=mutate,
47
+ pop_size=100,
48
+ n_generations=50,
49
+ seed=42,
50
+ )
51
+
52
+ # Extract Pareto front
53
+ pareto_front = result.pareto_front
54
+ print(f"Found {len(pareto_front)} Pareto-optimal solutions")
55
+
56
+ # === Single-Objective: Standard GA ===
57
+ def evaluate_single(x):
58
+ return float(np.sum(x ** 2)) # Sphere function
59
+
60
+ result = ga(
61
+ init=init,
62
+ evaluate=evaluate_single,
63
+ crossover=crossover,
64
+ mutate=mutate,
65
+ pop_size=100,
66
+ n_generations=100,
67
+ seed=42,
68
+ )
69
+
70
+ print(f"Best fitness: {result.best[1]:.6f}")
71
+ ```
72
+
73
+ ## Features
74
+
75
+ - Single-objective optimization with `ga()`.
76
+ - Multi-objective optimization with `nsga2()`.
77
+ - Pluggable parent-selection and survival strategies.
78
+ - Pure numpy primitives for Pareto dominance, non-dominated sorting, and crowding distance.
79
+ - Parallel evaluation through `n_workers`.
80
+
81
+ See the [full documentation](https://hydrosolutions.github.io/ctrl-freak/) for API details, user contracts, examples, and extension points.
82
+
83
+ ## Benchmarks
84
+
85
+ ctrl-freak ships a validation benchmark suite that checks `ga()` and `nsga2()`
86
+ against pymoo and DEAP on standard problems with known optima. With the genetic
87
+ algorithm held identical across all three libraries (ported SBX, aligned mutation
88
+ and selection, identical evaluation budget), ctrl-freak's results are statistically
89
+ indistinguishable from both baselines on the single-objective error metrics (all
90
+ six functions) and on multi-objective convergence (ZDT1, ZDT2, ZDT3, and DTLZ2). On
91
+ the two hardest problems (ZDT4, ZDT6) none of the three libraries converges at this
92
+ budget, and ctrl-freak is at least as good as both. The goal is parity, not
93
+ superiority.
94
+
95
+ See the canonical report in [benchmarks/README.md](benchmarks/README.md) and the
96
+ citable [Validation page](https://hydrosolutions.github.io/ctrl-freak/validation/).
97
+
98
+ ## Links
99
+
100
+ - [Documentation](https://hydrosolutions.github.io/ctrl-freak/)
101
+ - [Changelog](CHANGELOG.md)
102
+ - [License](LICENSE)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ctrl-freak"
3
- version = "0.1.0"
3
+ version = "0.2.0"
4
4
  description = "Pure-numpy genetic algorithm framework for single-objective (GA) and multi-objective (NSGA-II) optimization"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -49,6 +49,7 @@ build-backend = "uv_build"
49
49
  [dependency-groups]
50
50
  dev = [
51
51
  "deap>=1.4.3",
52
+ "matplotlib>=3.10.8",
52
53
  "pymoo>=0.6.1.6",
53
54
  "pytest>=8.3", # tests
54
55
  "pytest-cov>=6.2.1",
@@ -92,7 +93,7 @@ possibly-unresolved-reference = "warn"
92
93
  # Pytest
93
94
  # -----------------------
94
95
  [tool.pytest.ini_options]
95
- pythonpath = ["src"]
96
+ pythonpath = ["src", "."]
96
97
  testpaths = ["tests"]
97
98
  python_files = ["test_*.py", "*_test.py"]
98
99
  python_classes = ["Test*"]
ctrl_freak-0.1.0/PKG-INFO DELETED
@@ -1,238 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: ctrl-freak
3
- Version: 0.1.0
4
- Summary: Pure-numpy genetic algorithm framework for single-objective (GA) and multi-objective (NSGA-II) optimization
5
- Keywords: genetic-algorithm,evolutionary-algorithm,optimization,multi-objective-optimization,nsga-ii,nsga2,pareto,numpy
6
- Author: Nicolas Lazaro
7
- License-Expression: MIT
8
- License-File: LICENSE
9
- Classifier: Development Status :: 4 - Beta
10
- Classifier: Intended Audience :: Science/Research
11
- Classifier: Intended Audience :: Developers
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.11
14
- Classifier: Programming Language :: Python :: 3.12
15
- Classifier: Programming Language :: Python :: 3.13
16
- Classifier: Topic :: Scientific/Engineering
17
- Classifier: Topic :: Scientific/Engineering :: Mathematics
18
- Classifier: Operating System :: OS Independent
19
- Requires-Dist: numpy>=2.0
20
- Requires-Dist: joblib>=1.3.0
21
- Requires-Python: >=3.11
22
- Project-URL: Changelog, https://github.com/hydrosolutions/ctrl-freak/blob/main/CHANGELOG.md
23
- Project-URL: Documentation, https://hydrosolutions.github.io/ctrl-freak/
24
- Project-URL: Homepage, https://github.com/hydrosolutions/ctrl-freak
25
- Project-URL: Issues, https://github.com/hydrosolutions/ctrl-freak/issues
26
- Project-URL: Repository, https://github.com/hydrosolutions/ctrl-freak
27
- Description-Content-Type: text/markdown
28
-
29
- # ctrl-freak
30
-
31
- An extensible genetic algorithm framework for single and multi-objective optimization, built on pure numpy.
32
-
33
- ## Maintenance Status
34
-
35
- 🟢 **Active Development**
36
-
37
- This repository is part of an ongoing project and actively maintained.
38
-
39
- ---
40
-
41
- ## Installation
42
-
43
- ```bash
44
- uv add ctrl-freak
45
- ```
46
-
47
- ## Quick Start
48
-
49
- ```python
50
- import numpy as np
51
- from ctrl_freak import nsga2, ga
52
-
53
- # === Multi-Objective: NSGA-II ===
54
- def init(rng):
55
- return rng.uniform(0, 1, size=5)
56
-
57
- def evaluate_multi(x):
58
- f1 = x[0]
59
- f2 = 1 - np.sqrt(x[0]) + x[1:]@x[1:]
60
- return np.array([f1, f2])
61
-
62
- def crossover(p1, p2):
63
- return (p1 + p2) / 2
64
-
65
- def mutate(x):
66
- return np.clip(x + np.random.normal(0, 0.1, size=x.shape), 0, 1)
67
-
68
- result = nsga2(
69
- init=init,
70
- evaluate=evaluate_multi,
71
- crossover=crossover,
72
- mutate=mutate,
73
- pop_size=100,
74
- n_generations=50,
75
- seed=42,
76
- )
77
-
78
- # Extract Pareto front
79
- pareto_front = result.pareto_front
80
- print(f"Found {len(pareto_front)} Pareto-optimal solutions")
81
-
82
- # === Single-Objective: Standard GA ===
83
- def evaluate_single(x):
84
- return float(np.sum(x ** 2)) # Sphere function
85
-
86
- result = ga(
87
- init=init,
88
- evaluate=evaluate_single,
89
- crossover=crossover,
90
- mutate=mutate,
91
- pop_size=100,
92
- n_generations=100,
93
- seed=42,
94
- )
95
-
96
- print(f"Best fitness: {result.best[1]:.6f}")
97
- ```
98
-
99
- ## Documentation
100
-
101
- - [API Usage Guide](docs/usage.md) — Installation, examples, working with results
102
- - [User Contracts](docs/contracts.md) — Function signatures and responsibilities
103
-
104
- ---
105
-
106
- ## Design Philosophy
107
-
108
- - **Pure numpy** for performance
109
- - **Functional style** with immutable data structures
110
- - **User thinks about individuals**, framework handles vectorization via `lift()`
111
- - **Fail fast** with eager validation
112
- - **Domain agnostic** — framework handles selection pressure, user handles constraints/bounds
113
- - **Extensible** via pluggable selection and survival strategies
114
-
115
- ## Architecture
116
-
117
- ```
118
- ┌─────────────────────────────────────────────────────────────┐
119
- │ User Domain Layer │
120
- │ init(), evaluate(), crossover(), mutate() │
121
- │ (per-individual, user-defined) │
122
- └─────────────────────────────────────────────────────────────┘
123
- │ lift()
124
-
125
- ┌─────────────────────────────────────────────────────────────┐
126
- │ Algorithm Layer │
127
- │ ┌─────────────┐ ┌─────────────┐ │
128
- │ │ nsga2() │ │ ga() │ │
129
- │ │ multi-obj │ │ single-obj │ │
130
- │ └─────────────┘ └─────────────┘ │
131
- │ │ │ │
132
- │ └────────┬─────────┘ │
133
- │ ▼ │
134
- │ ┌───────────────────────────────────────────────────────┐ │
135
- │ │ Pluggable Strategies │ │
136
- │ │ Selection: crowded, tournament, roulette │ │
137
- │ │ Survival: nsga2, truncation, elitist │ │
138
- │ └───────────────────────────────────────────────────────┘ │
139
- └─────────────────────────────────────────────────────────────┘
140
-
141
-
142
- ┌─────────────────────────────────────────────────────────────┐
143
- │ Primitives (pure functions) │
144
- │ non_dominated_sort(), crowding_distance(), dominates() │
145
- └─────────────────────────────────────────────────────────────┘
146
- ```
147
-
148
- ## API Reference
149
-
150
- ### Algorithms
151
-
152
- ```python
153
- # Multi-objective optimization
154
- nsga2(init, evaluate, crossover, mutate, pop_size, n_generations,
155
- seed=None, callback=None, select='crowded', survive='nsga2',
156
- n_workers=1) -> NSGA2Result
157
-
158
- # Single-objective optimization
159
- ga(init, evaluate, crossover, mutate, pop_size, n_generations,
160
- seed=None, callback=None, select='tournament', survive='elitist',
161
- n_workers=1) -> GAResult
162
- ```
163
-
164
- ### User Function Contracts
165
-
166
- | Function | Signature | Description |
167
- |----------|-----------|-------------|
168
- | `init` | `(rng) -> (n_vars,)` | Initialize one random individual |
169
- | `evaluate` | `(n_vars,) -> (n_obj,)` or `float` | Compute objectives (minimization) |
170
- | `crossover` | `(n_vars,), (n_vars,) -> (n_vars,)` | Combine two parents into one child |
171
- | `mutate` | `(n_vars,) -> (n_vars,)` | Perturb an individual |
172
-
173
- ### Result Types
174
-
175
- **NSGA2Result** — Multi-objective optimization result:
176
-
177
- - `population: Population` — Final population
178
- - `rank: np.ndarray` — Pareto front ranks `(n,)` where 0 = optimal
179
- - `crowding_distance: np.ndarray` — Diversity measure `(n,)`
180
- - `pareto_front: Population` — Property returning rank-0 individuals
181
- - `generations: int` — Generations completed
182
- - `evaluations: int` — Total evaluations
183
-
184
- **GAResult** — Single-objective optimization result:
185
-
186
- - `population: Population` — Final population
187
- - `fitness: np.ndarray` — Fitness values `(n,)`
188
- - `best: tuple[np.ndarray, float]` — Property returning (best_x, best_fitness)
189
- - `generations: int` — Generations completed
190
- - `evaluations: int` — Total evaluations
191
-
192
- ### Data Structures
193
-
194
- **Population** — Immutable collection of solutions:
195
-
196
- - `x: np.ndarray` — Decision variables `(n, n_vars)`
197
- - `objectives: np.ndarray | None` — Objective values `(n, n_obj)`
198
-
199
- ### Selection Strategies
200
-
201
- | Name | Function | Use Case |
202
- |------|----------|----------|
203
- | `'crowded'` | `crowded_tournament()` | NSGA-II (rank + crowding) |
204
- | `'tournament'` | `fitness_tournament()` | GA (fitness-based) |
205
- | `'roulette'` | `roulette_wheel()` | GA (fitness-proportionate) |
206
-
207
- ### Survival Strategies
208
-
209
- | Name | Function | Use Case |
210
- |------|----------|----------|
211
- | `'nsga2'` | `nsga2_survival()` | NSGA-II (fronts + crowding) |
212
- | `'truncation'` | `truncation_survival()` | Keep best k |
213
- | `'elitist'` | `elitist_survival()` | Preserve elite parents |
214
-
215
- ### Primitives
216
-
217
- | Function | Description |
218
- |----------|-------------|
219
- | `dominates(a, b)` | Check if `a` Pareto-dominates `b` |
220
- | `dominates_matrix(objectives)` | Pairwise dominance matrix |
221
- | `non_dominated_sort(objectives)` | Assign Pareto front ranks |
222
- | `crowding_distance(front)` | Compute crowding distances for one front |
223
-
224
- ---
225
-
226
- ## Benchmarks
227
-
228
- Tested against Pymoo and DEAP on ZDT test problems (100 pop, 250 generations, 10 seeds):
229
-
230
- | Problem | ctrl-freak | Pymoo | DEAP |
231
- |---------|------------|-------|------|
232
- | ZDT1 | 0.8653 ± 0.0011 | 0.8241 ± 0.0255 | **0.8698 ± 0.0002** |
233
- | ZDT2 | 0.5320 ± 0.0017 | 0.4764 ± 0.0182 | **0.5363 ± 0.0002** |
234
- | ZDT3 | 1.3224 ± 0.0008 | 1.2836 ± 0.0123 | **1.3275 ± 0.0002** |
235
-
236
- ctrl-freak matches DEAP-level hypervolume on ZDT1-3 with low variance. See [full benchmark results](benchmarks/README.md).
237
-
238
- ---
@@ -1,210 +0,0 @@
1
- # ctrl-freak
2
-
3
- An extensible genetic algorithm framework for single and multi-objective optimization, built on pure numpy.
4
-
5
- ## Maintenance Status
6
-
7
- 🟢 **Active Development**
8
-
9
- This repository is part of an ongoing project and actively maintained.
10
-
11
- ---
12
-
13
- ## Installation
14
-
15
- ```bash
16
- uv add ctrl-freak
17
- ```
18
-
19
- ## Quick Start
20
-
21
- ```python
22
- import numpy as np
23
- from ctrl_freak import nsga2, ga
24
-
25
- # === Multi-Objective: NSGA-II ===
26
- def init(rng):
27
- return rng.uniform(0, 1, size=5)
28
-
29
- def evaluate_multi(x):
30
- f1 = x[0]
31
- f2 = 1 - np.sqrt(x[0]) + x[1:]@x[1:]
32
- return np.array([f1, f2])
33
-
34
- def crossover(p1, p2):
35
- return (p1 + p2) / 2
36
-
37
- def mutate(x):
38
- return np.clip(x + np.random.normal(0, 0.1, size=x.shape), 0, 1)
39
-
40
- result = nsga2(
41
- init=init,
42
- evaluate=evaluate_multi,
43
- crossover=crossover,
44
- mutate=mutate,
45
- pop_size=100,
46
- n_generations=50,
47
- seed=42,
48
- )
49
-
50
- # Extract Pareto front
51
- pareto_front = result.pareto_front
52
- print(f"Found {len(pareto_front)} Pareto-optimal solutions")
53
-
54
- # === Single-Objective: Standard GA ===
55
- def evaluate_single(x):
56
- return float(np.sum(x ** 2)) # Sphere function
57
-
58
- result = ga(
59
- init=init,
60
- evaluate=evaluate_single,
61
- crossover=crossover,
62
- mutate=mutate,
63
- pop_size=100,
64
- n_generations=100,
65
- seed=42,
66
- )
67
-
68
- print(f"Best fitness: {result.best[1]:.6f}")
69
- ```
70
-
71
- ## Documentation
72
-
73
- - [API Usage Guide](docs/usage.md) — Installation, examples, working with results
74
- - [User Contracts](docs/contracts.md) — Function signatures and responsibilities
75
-
76
- ---
77
-
78
- ## Design Philosophy
79
-
80
- - **Pure numpy** for performance
81
- - **Functional style** with immutable data structures
82
- - **User thinks about individuals**, framework handles vectorization via `lift()`
83
- - **Fail fast** with eager validation
84
- - **Domain agnostic** — framework handles selection pressure, user handles constraints/bounds
85
- - **Extensible** via pluggable selection and survival strategies
86
-
87
- ## Architecture
88
-
89
- ```
90
- ┌─────────────────────────────────────────────────────────────┐
91
- │ User Domain Layer │
92
- │ init(), evaluate(), crossover(), mutate() │
93
- │ (per-individual, user-defined) │
94
- └─────────────────────────────────────────────────────────────┘
95
- │ lift()
96
-
97
- ┌─────────────────────────────────────────────────────────────┐
98
- │ Algorithm Layer │
99
- │ ┌─────────────┐ ┌─────────────┐ │
100
- │ │ nsga2() │ │ ga() │ │
101
- │ │ multi-obj │ │ single-obj │ │
102
- │ └─────────────┘ └─────────────┘ │
103
- │ │ │ │
104
- │ └────────┬─────────┘ │
105
- │ ▼ │
106
- │ ┌───────────────────────────────────────────────────────┐ │
107
- │ │ Pluggable Strategies │ │
108
- │ │ Selection: crowded, tournament, roulette │ │
109
- │ │ Survival: nsga2, truncation, elitist │ │
110
- │ └───────────────────────────────────────────────────────┘ │
111
- └─────────────────────────────────────────────────────────────┘
112
-
113
-
114
- ┌─────────────────────────────────────────────────────────────┐
115
- │ Primitives (pure functions) │
116
- │ non_dominated_sort(), crowding_distance(), dominates() │
117
- └─────────────────────────────────────────────────────────────┘
118
- ```
119
-
120
- ## API Reference
121
-
122
- ### Algorithms
123
-
124
- ```python
125
- # Multi-objective optimization
126
- nsga2(init, evaluate, crossover, mutate, pop_size, n_generations,
127
- seed=None, callback=None, select='crowded', survive='nsga2',
128
- n_workers=1) -> NSGA2Result
129
-
130
- # Single-objective optimization
131
- ga(init, evaluate, crossover, mutate, pop_size, n_generations,
132
- seed=None, callback=None, select='tournament', survive='elitist',
133
- n_workers=1) -> GAResult
134
- ```
135
-
136
- ### User Function Contracts
137
-
138
- | Function | Signature | Description |
139
- |----------|-----------|-------------|
140
- | `init` | `(rng) -> (n_vars,)` | Initialize one random individual |
141
- | `evaluate` | `(n_vars,) -> (n_obj,)` or `float` | Compute objectives (minimization) |
142
- | `crossover` | `(n_vars,), (n_vars,) -> (n_vars,)` | Combine two parents into one child |
143
- | `mutate` | `(n_vars,) -> (n_vars,)` | Perturb an individual |
144
-
145
- ### Result Types
146
-
147
- **NSGA2Result** — Multi-objective optimization result:
148
-
149
- - `population: Population` — Final population
150
- - `rank: np.ndarray` — Pareto front ranks `(n,)` where 0 = optimal
151
- - `crowding_distance: np.ndarray` — Diversity measure `(n,)`
152
- - `pareto_front: Population` — Property returning rank-0 individuals
153
- - `generations: int` — Generations completed
154
- - `evaluations: int` — Total evaluations
155
-
156
- **GAResult** — Single-objective optimization result:
157
-
158
- - `population: Population` — Final population
159
- - `fitness: np.ndarray` — Fitness values `(n,)`
160
- - `best: tuple[np.ndarray, float]` — Property returning (best_x, best_fitness)
161
- - `generations: int` — Generations completed
162
- - `evaluations: int` — Total evaluations
163
-
164
- ### Data Structures
165
-
166
- **Population** — Immutable collection of solutions:
167
-
168
- - `x: np.ndarray` — Decision variables `(n, n_vars)`
169
- - `objectives: np.ndarray | None` — Objective values `(n, n_obj)`
170
-
171
- ### Selection Strategies
172
-
173
- | Name | Function | Use Case |
174
- |------|----------|----------|
175
- | `'crowded'` | `crowded_tournament()` | NSGA-II (rank + crowding) |
176
- | `'tournament'` | `fitness_tournament()` | GA (fitness-based) |
177
- | `'roulette'` | `roulette_wheel()` | GA (fitness-proportionate) |
178
-
179
- ### Survival Strategies
180
-
181
- | Name | Function | Use Case |
182
- |------|----------|----------|
183
- | `'nsga2'` | `nsga2_survival()` | NSGA-II (fronts + crowding) |
184
- | `'truncation'` | `truncation_survival()` | Keep best k |
185
- | `'elitist'` | `elitist_survival()` | Preserve elite parents |
186
-
187
- ### Primitives
188
-
189
- | Function | Description |
190
- |----------|-------------|
191
- | `dominates(a, b)` | Check if `a` Pareto-dominates `b` |
192
- | `dominates_matrix(objectives)` | Pairwise dominance matrix |
193
- | `non_dominated_sort(objectives)` | Assign Pareto front ranks |
194
- | `crowding_distance(front)` | Compute crowding distances for one front |
195
-
196
- ---
197
-
198
- ## Benchmarks
199
-
200
- Tested against Pymoo and DEAP on ZDT test problems (100 pop, 250 generations, 10 seeds):
201
-
202
- | Problem | ctrl-freak | Pymoo | DEAP |
203
- |---------|------------|-------|------|
204
- | ZDT1 | 0.8653 ± 0.0011 | 0.8241 ± 0.0255 | **0.8698 ± 0.0002** |
205
- | ZDT2 | 0.5320 ± 0.0017 | 0.4764 ± 0.0182 | **0.5363 ± 0.0002** |
206
- | ZDT3 | 1.3224 ± 0.0008 | 1.2836 ± 0.0123 | **1.3275 ± 0.0002** |
207
-
208
- ctrl-freak matches DEAP-level hypervolume on ZDT1-3 with low variance. See [full benchmark results](benchmarks/README.md).
209
-
210
- ---
File without changes