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.
- ctrl_freak-0.2.0/PKG-INFO +130 -0
- ctrl_freak-0.2.0/README.md +102 -0
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/pyproject.toml +3 -2
- ctrl_freak-0.1.0/PKG-INFO +0 -238
- ctrl_freak-0.1.0/README.md +0 -210
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/LICENSE +0 -0
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/__init__.py +0 -0
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/algorithms/__init__.py +0 -0
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/algorithms/ga.py +0 -0
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/algorithms/nsga2.py +0 -0
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/operators/__init__.py +0 -0
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/operators/base.py +0 -0
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/operators/selection.py +0 -0
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/operators/standard.py +0 -0
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/population.py +0 -0
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/primitives/__init__.py +0 -0
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/primitives/pareto.py +0 -0
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/protocols.py +0 -0
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/py.typed +0 -0
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/registry.py +0 -0
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/results.py +0 -0
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/selection/__init__.py +0 -0
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/selection/crowded.py +0 -0
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/selection/roulette.py +0 -0
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/selection/tournament.py +0 -0
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/survival/__init__.py +0 -0
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/survival/elitist.py +0 -0
- {ctrl_freak-0.1.0 → ctrl_freak-0.2.0}/src/ctrl_freak/survival/nsga2.py +0 -0
- {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
|
+
[](https://pypi.org/project/ctrl-freak/)
|
|
32
|
+
[](https://pypi.org/project/ctrl-freak/)
|
|
33
|
+
[](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
|
+
[](https://pypi.org/project/ctrl-freak/)
|
|
4
|
+
[](https://pypi.org/project/ctrl-freak/)
|
|
5
|
+
[](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.
|
|
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
|
-
---
|
ctrl_freak-0.1.0/README.md
DELETED
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|