pyecsago 0.1.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.
- pyecsago-0.1.0/PKG-INFO +182 -0
- pyecsago-0.1.0/README.md +160 -0
- pyecsago-0.1.0/pyproject.toml +63 -0
- pyecsago-0.1.0/src/pyecsago/__init__.py +31 -0
- pyecsago-0.1.0/src/pyecsago/core/__init__.py +16 -0
- pyecsago-0.1.0/src/pyecsago/core/algorithm.py +46 -0
- pyecsago-0.1.0/src/pyecsago/core/context.py +80 -0
- pyecsago-0.1.0/src/pyecsago/core/exceptions.py +21 -0
- pyecsago-0.1.0/src/pyecsago/core/individual.py +33 -0
- pyecsago-0.1.0/src/pyecsago/core/population.py +17 -0
- pyecsago-0.1.0/src/pyecsago/implementations/__init__.py +0 -0
- pyecsago-0.1.0/src/pyecsago/implementations/ecsago/__init__.py +11 -0
- pyecsago-0.1.0/src/pyecsago/implementations/ecsago/algorithm.py +233 -0
- pyecsago-0.1.0/src/pyecsago/implementations/ecsago/context.py +184 -0
- pyecsago-0.1.0/src/pyecsago/implementations/ecsago/individual.py +129 -0
- pyecsago-0.1.0/src/pyecsago/implementations/ecsago/population.py +161 -0
- pyecsago-0.1.0/src/pyecsago/strategies/__init__.py +0 -0
- pyecsago-0.1.0/src/pyecsago/strategies/context/__init__.py +0 -0
- pyecsago-0.1.0/src/pyecsago/strategies/context/factory.py +33 -0
- pyecsago-0.1.0/src/pyecsago/strategies/evolution/__init__.py +0 -0
- pyecsago-0.1.0/src/pyecsago/strategies/evolution/base.py +20 -0
- pyecsago-0.1.0/src/pyecsago/strategies/evolution/ecsago.py +132 -0
- pyecsago-0.1.0/src/pyecsago/strategies/evolution/factory.py +78 -0
- pyecsago-0.1.0/src/pyecsago/strategies/extraction/__init__.py +0 -0
- pyecsago-0.1.0/src/pyecsago/strategies/extraction/base.py +28 -0
- pyecsago-0.1.0/src/pyecsago/strategies/extraction/ecsago.py +173 -0
- pyecsago-0.1.0/src/pyecsago/strategies/extraction/factory.py +76 -0
- pyecsago-0.1.0/src/pyecsago/strategies/fitness/__init__.py +0 -0
- pyecsago-0.1.0/src/pyecsago/strategies/fitness/base.py +15 -0
- pyecsago-0.1.0/src/pyecsago/strategies/fitness/ecsago.py +289 -0
- pyecsago-0.1.0/src/pyecsago/strategies/niching/__init__.py +0 -0
- pyecsago-0.1.0/src/pyecsago/strategies/niching/base.py +34 -0
- pyecsago-0.1.0/src/pyecsago/strategies/niching/deterministic_crowding.py +67 -0
- pyecsago-0.1.0/src/pyecsago/strategies/operators/__init__.py +0 -0
- pyecsago-0.1.0/src/pyecsago/strategies/operators/base.py +49 -0
- pyecsago-0.1.0/src/pyecsago/strategies/operators/crossover.py +45 -0
- pyecsago-0.1.0/src/pyecsago/strategies/operators/genetic_operators.py +58 -0
- pyecsago-0.1.0/src/pyecsago/strategies/operators/haea.py +119 -0
- pyecsago-0.1.0/src/pyecsago/strategies/operators/mutation.py +68 -0
- pyecsago-0.1.0/src/pyecsago/strategies/refinement/__init__.py +0 -0
- pyecsago-0.1.0/src/pyecsago/strategies/refinement/base.py +62 -0
- pyecsago-0.1.0/src/pyecsago/strategies/refinement/cuda_mde.py +100 -0
- pyecsago-0.1.0/src/pyecsago/strategies/refinement/factory.py +34 -0
- pyecsago-0.1.0/src/pyecsago/strategies/refinement/mde.py +146 -0
- pyecsago-0.1.0/src/pyecsago/utils/__init__.py +0 -0
- pyecsago-0.1.0/src/pyecsago/utils/compat.py +45 -0
- pyecsago-0.1.0/src/pyecsago/utils/cuda/__init__.py +0 -0
- pyecsago-0.1.0/src/pyecsago/utils/cuda/compiler.py +35 -0
- pyecsago-0.1.0/src/pyecsago/utils/cuda/config.py +94 -0
- pyecsago-0.1.0/src/pyecsago/utils/cuda/context.py +84 -0
- pyecsago-0.1.0/src/pyecsago/utils/cuda/kernels.py +254 -0
- pyecsago-0.1.0/src/pyecsago/utils/data.py +36 -0
- pyecsago-0.1.0/src/pyecsago/utils/data_types.py +187 -0
- pyecsago-0.1.0/src/pyecsago/utils/dataviz.py +55 -0
- pyecsago-0.1.0/src/pyecsago/utils/funcs.py +85 -0
- pyecsago-0.1.0/src/pyecsago/utils/metrics.py +6 -0
- pyecsago-0.1.0/src/pyecsago/utils/validators.py +48 -0
pyecsago-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyecsago
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python implementation of the evolutionary clustering ECSAGO - Evolutionary Clustering with Self Adaptive Genetic Operators
|
|
5
|
+
Author: Joan Sebastian Tamayo Rivera
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
11
|
+
Requires-Dist: numpy>=2.0,<3
|
|
12
|
+
Requires-Dist: matplotlib>=3.9,<4
|
|
13
|
+
Requires-Dist: scikit-learn>=1.5,<2
|
|
14
|
+
Requires-Dist: pandas>=2.2,<3
|
|
15
|
+
Requires-Dist: seaborn>=0.13,<1
|
|
16
|
+
Requires-Dist: scipy>=1.14,<2
|
|
17
|
+
Requires-Dist: tqdm>=4.67,<5
|
|
18
|
+
Requires-Dist: cupy-cuda12x>=13.3.0,<14 ; extra == 'cuda'
|
|
19
|
+
Requires-Python: >=3.12, <4
|
|
20
|
+
Provides-Extra: cuda
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# pyecsago
|
|
24
|
+
|
|
25
|
+
Python implementation of **ECSAGO** (Evolutionary Clustering with Self-Adaptive Genetic Operators), a robust evolutionary clustering algorithm that automatically discovers the number of clusters in data while being resistant to noise.
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
- **Automatic cluster detection** — no need to specify *k* in advance
|
|
32
|
+
- **Noise-robust** density-based fitness function (RBF kernel weights)
|
|
33
|
+
- **Self-adaptive operators** via HAEA (Hybrid Adaptive Evolutionary Algorithm)
|
|
34
|
+
- **Deterministic Crowding** for niche maintenance
|
|
35
|
+
- **MDE refinement** (Maximal Density Estimator) for prototype center/spread convergence
|
|
36
|
+
- **GPU acceleration** — optional CUDA support via CuPy
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
CPU only:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install pyecsago
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
With CUDA support:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install pyecsago[cuda]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### From source
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
git clone https://github.com/pwnaoj/pyecsago
|
|
56
|
+
cd pyecsago
|
|
57
|
+
pip install .
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Quick start
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
import numpy as np
|
|
64
|
+
from pyecsago import ECSAGO
|
|
65
|
+
|
|
66
|
+
# Sample data: 3 Gaussian clusters
|
|
67
|
+
data = np.vstack([
|
|
68
|
+
np.random.randn(100, 2) + [0, 0],
|
|
69
|
+
np.random.randn(100, 2) + [5, 5],
|
|
70
|
+
np.random.randn(100, 2) + [10, 0],
|
|
71
|
+
])
|
|
72
|
+
|
|
73
|
+
config = {
|
|
74
|
+
"population_size": 100,
|
|
75
|
+
"weight_threshold": 0.3,
|
|
76
|
+
"max_generations": 30,
|
|
77
|
+
"iterations": 10,
|
|
78
|
+
"extraction_type": {2: 0.25}, # PROPORTION_MAX with 25% threshold
|
|
79
|
+
"k": 13.8,
|
|
80
|
+
"use_cuda": False,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
ecsago = ECSAGO(config)
|
|
84
|
+
results = ecsago.run(data)
|
|
85
|
+
|
|
86
|
+
prototypes = results["refined_prototypes"]
|
|
87
|
+
labels = results["cluster_assignments"]
|
|
88
|
+
|
|
89
|
+
print(f"Clusters found: {len(prototypes)}")
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Step-by-step usage
|
|
93
|
+
|
|
94
|
+
For finer control you can invoke each stage independently:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
ecsago = ECSAGO(config)
|
|
98
|
+
|
|
99
|
+
# 1. Load data
|
|
100
|
+
ecsago.context.set_data(data)
|
|
101
|
+
|
|
102
|
+
# 2. Evolve the population
|
|
103
|
+
ecsago.evolve()
|
|
104
|
+
|
|
105
|
+
# 3. Extract prototypes
|
|
106
|
+
prototypes = ecsago.extract_prototypes(
|
|
107
|
+
extraction_type={2: 0.25},
|
|
108
|
+
k=13.8,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# 4. Refine with MDE
|
|
112
|
+
refined = ecsago.refine_prototypes(
|
|
113
|
+
prototypes=prototypes,
|
|
114
|
+
iterations=10,
|
|
115
|
+
k=13.8,
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Configuration
|
|
120
|
+
|
|
121
|
+
All parameters are passed as a dictionary to `ECSAGO(config)`.
|
|
122
|
+
|
|
123
|
+
| Parameter | Type | Description |
|
|
124
|
+
|---|---|---|
|
|
125
|
+
| `population_size` | `int` | Number of individuals in the evolutionary population |
|
|
126
|
+
| `weight_threshold` | `float` | Threshold for weight binarization (0–1) |
|
|
127
|
+
| `max_generations` | `int` | Maximum number of evolutionary generations |
|
|
128
|
+
| `iterations` | `int` | Number of MDE refinement iterations |
|
|
129
|
+
| `extraction_type` | `dict` | Extraction method — key is the type (0–4), value is the threshold |
|
|
130
|
+
| `k` | `float` | Chi-squared factor for minimum inter-prototype distance |
|
|
131
|
+
| `use_cuda` | `bool` | Enable CUDA/GPU acceleration via CuPy |
|
|
132
|
+
|
|
133
|
+
### Extraction types
|
|
134
|
+
|
|
135
|
+
| Key | Method | Threshold meaning |
|
|
136
|
+
|---|---|---|
|
|
137
|
+
| 0 | `ABSOLUTE_VALUE` | Absolute fitness threshold (auto-calculated) |
|
|
138
|
+
| 1 | `PROPORTION_AVG` | Proportion of average fitness |
|
|
139
|
+
| 2 | `PROPORTION_MAX` | Proportion of maximum fitness |
|
|
140
|
+
| 3 | `PROPORTION_MEDIAN` | Proportion of median fitness |
|
|
141
|
+
| 4 | `MINIMUM_DENSITY` | Based on minimum density |
|
|
142
|
+
|
|
143
|
+
## Output
|
|
144
|
+
|
|
145
|
+
`ecsago.run(data)` returns a dictionary:
|
|
146
|
+
|
|
147
|
+
| Key | Description |
|
|
148
|
+
|---|---|
|
|
149
|
+
| `final_population` | Full evolved population |
|
|
150
|
+
| `prototypes` | Extracted prototypes (before refinement) |
|
|
151
|
+
| `refined_prototypes` | Refined prototypes (after MDE) |
|
|
152
|
+
| `cluster_assignments` | Cluster label for each data point |
|
|
153
|
+
|
|
154
|
+
Each prototype is an `ECSAGOIndividual` with attributes `genome` (center), `sigma2` (spread), and `fitness`.
|
|
155
|
+
|
|
156
|
+
## Architecture
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
pyecsago/
|
|
160
|
+
├── core/ # Abstract base classes and exceptions
|
|
161
|
+
├── implementations/
|
|
162
|
+
│ └── ecsago/ # ECSAGO algorithm, context, individual, population
|
|
163
|
+
├── strategies/
|
|
164
|
+
│ ├── evolution/ # Evolution strategy (HAEA + Deterministic Crowding)
|
|
165
|
+
│ ├── extraction/ # Prototype extraction (fitness, niche, composite)
|
|
166
|
+
│ ├── fitness/ # Fitness calculation (CPU and CUDA)
|
|
167
|
+
│ ├── niching/ # Deterministic Crowding
|
|
168
|
+
│ ├── operators/ # Genetic operators (mutation, crossover, HAEA)
|
|
169
|
+
│ └── refinement/ # MDE refinement (CPU and CUDA)
|
|
170
|
+
└── utils/ # Data type strategies, compatibility, utilities
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
**pyecsago** was created by Joan Sebastian Tamayo Rivera. It is licensed under the terms of the MIT license.
|
|
176
|
+
|
|
177
|
+
## References
|
|
178
|
+
|
|
179
|
+
- León, E. *"Scalable and Adaptive Evolutionary Clustering for Noisy and Dynamic Data"*
|
|
180
|
+
- León, E., Nasraoui, O., & Gómez, J. *"ECSAGO: Evolutionary Clustering with Self-Adaptive Genetic Operators"*
|
|
181
|
+
- Gómez, J. *"Self Adaptation of Operator Rates for Multimodal Optimization"*
|
|
182
|
+
- Tamayo, J. *"GPU/CUDA-Based Parallelization of the ECSAGO Evolutionary Algorithm"*
|
pyecsago-0.1.0/README.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# pyecsago
|
|
2
|
+
|
|
3
|
+
Python implementation of **ECSAGO** (Evolutionary Clustering with Self-Adaptive Genetic Operators), a robust evolutionary clustering algorithm that automatically discovers the number of clusters in data while being resistant to noise.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Automatic cluster detection** — no need to specify *k* in advance
|
|
10
|
+
- **Noise-robust** density-based fitness function (RBF kernel weights)
|
|
11
|
+
- **Self-adaptive operators** via HAEA (Hybrid Adaptive Evolutionary Algorithm)
|
|
12
|
+
- **Deterministic Crowding** for niche maintenance
|
|
13
|
+
- **MDE refinement** (Maximal Density Estimator) for prototype center/spread convergence
|
|
14
|
+
- **GPU acceleration** — optional CUDA support via CuPy
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
CPU only:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install pyecsago
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
With CUDA support:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install pyecsago[cuda]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### From source
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
git clone https://github.com/pwnaoj/pyecsago
|
|
34
|
+
cd pyecsago
|
|
35
|
+
pip install .
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick start
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import numpy as np
|
|
42
|
+
from pyecsago import ECSAGO
|
|
43
|
+
|
|
44
|
+
# Sample data: 3 Gaussian clusters
|
|
45
|
+
data = np.vstack([
|
|
46
|
+
np.random.randn(100, 2) + [0, 0],
|
|
47
|
+
np.random.randn(100, 2) + [5, 5],
|
|
48
|
+
np.random.randn(100, 2) + [10, 0],
|
|
49
|
+
])
|
|
50
|
+
|
|
51
|
+
config = {
|
|
52
|
+
"population_size": 100,
|
|
53
|
+
"weight_threshold": 0.3,
|
|
54
|
+
"max_generations": 30,
|
|
55
|
+
"iterations": 10,
|
|
56
|
+
"extraction_type": {2: 0.25}, # PROPORTION_MAX with 25% threshold
|
|
57
|
+
"k": 13.8,
|
|
58
|
+
"use_cuda": False,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
ecsago = ECSAGO(config)
|
|
62
|
+
results = ecsago.run(data)
|
|
63
|
+
|
|
64
|
+
prototypes = results["refined_prototypes"]
|
|
65
|
+
labels = results["cluster_assignments"]
|
|
66
|
+
|
|
67
|
+
print(f"Clusters found: {len(prototypes)}")
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Step-by-step usage
|
|
71
|
+
|
|
72
|
+
For finer control you can invoke each stage independently:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
ecsago = ECSAGO(config)
|
|
76
|
+
|
|
77
|
+
# 1. Load data
|
|
78
|
+
ecsago.context.set_data(data)
|
|
79
|
+
|
|
80
|
+
# 2. Evolve the population
|
|
81
|
+
ecsago.evolve()
|
|
82
|
+
|
|
83
|
+
# 3. Extract prototypes
|
|
84
|
+
prototypes = ecsago.extract_prototypes(
|
|
85
|
+
extraction_type={2: 0.25},
|
|
86
|
+
k=13.8,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# 4. Refine with MDE
|
|
90
|
+
refined = ecsago.refine_prototypes(
|
|
91
|
+
prototypes=prototypes,
|
|
92
|
+
iterations=10,
|
|
93
|
+
k=13.8,
|
|
94
|
+
)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Configuration
|
|
98
|
+
|
|
99
|
+
All parameters are passed as a dictionary to `ECSAGO(config)`.
|
|
100
|
+
|
|
101
|
+
| Parameter | Type | Description |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `population_size` | `int` | Number of individuals in the evolutionary population |
|
|
104
|
+
| `weight_threshold` | `float` | Threshold for weight binarization (0–1) |
|
|
105
|
+
| `max_generations` | `int` | Maximum number of evolutionary generations |
|
|
106
|
+
| `iterations` | `int` | Number of MDE refinement iterations |
|
|
107
|
+
| `extraction_type` | `dict` | Extraction method — key is the type (0–4), value is the threshold |
|
|
108
|
+
| `k` | `float` | Chi-squared factor for minimum inter-prototype distance |
|
|
109
|
+
| `use_cuda` | `bool` | Enable CUDA/GPU acceleration via CuPy |
|
|
110
|
+
|
|
111
|
+
### Extraction types
|
|
112
|
+
|
|
113
|
+
| Key | Method | Threshold meaning |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| 0 | `ABSOLUTE_VALUE` | Absolute fitness threshold (auto-calculated) |
|
|
116
|
+
| 1 | `PROPORTION_AVG` | Proportion of average fitness |
|
|
117
|
+
| 2 | `PROPORTION_MAX` | Proportion of maximum fitness |
|
|
118
|
+
| 3 | `PROPORTION_MEDIAN` | Proportion of median fitness |
|
|
119
|
+
| 4 | `MINIMUM_DENSITY` | Based on minimum density |
|
|
120
|
+
|
|
121
|
+
## Output
|
|
122
|
+
|
|
123
|
+
`ecsago.run(data)` returns a dictionary:
|
|
124
|
+
|
|
125
|
+
| Key | Description |
|
|
126
|
+
|---|---|
|
|
127
|
+
| `final_population` | Full evolved population |
|
|
128
|
+
| `prototypes` | Extracted prototypes (before refinement) |
|
|
129
|
+
| `refined_prototypes` | Refined prototypes (after MDE) |
|
|
130
|
+
| `cluster_assignments` | Cluster label for each data point |
|
|
131
|
+
|
|
132
|
+
Each prototype is an `ECSAGOIndividual` with attributes `genome` (center), `sigma2` (spread), and `fitness`.
|
|
133
|
+
|
|
134
|
+
## Architecture
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
pyecsago/
|
|
138
|
+
├── core/ # Abstract base classes and exceptions
|
|
139
|
+
├── implementations/
|
|
140
|
+
│ └── ecsago/ # ECSAGO algorithm, context, individual, population
|
|
141
|
+
├── strategies/
|
|
142
|
+
│ ├── evolution/ # Evolution strategy (HAEA + Deterministic Crowding)
|
|
143
|
+
│ ├── extraction/ # Prototype extraction (fitness, niche, composite)
|
|
144
|
+
│ ├── fitness/ # Fitness calculation (CPU and CUDA)
|
|
145
|
+
│ ├── niching/ # Deterministic Crowding
|
|
146
|
+
│ ├── operators/ # Genetic operators (mutation, crossover, HAEA)
|
|
147
|
+
│ └── refinement/ # MDE refinement (CPU and CUDA)
|
|
148
|
+
└── utils/ # Data type strategies, compatibility, utilities
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
**pyecsago** was created by Joan Sebastian Tamayo Rivera. It is licensed under the terms of the MIT license.
|
|
154
|
+
|
|
155
|
+
## References
|
|
156
|
+
|
|
157
|
+
- León, E. *"Scalable and Adaptive Evolutionary Clustering for Noisy and Dynamic Data"*
|
|
158
|
+
- León, E., Nasraoui, O., & Gómez, J. *"ECSAGO: Evolutionary Clustering with Self-Adaptive Genetic Operators"*
|
|
159
|
+
- Gómez, J. *"Self Adaptation of Operator Rates for Multimodal Optimization"*
|
|
160
|
+
- Tamayo, J. *"GPU/CUDA-Based Parallelization of the ECSAGO Evolutionary Algorithm"*
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pyecsago"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Python implementation of the evolutionary clustering ECSAGO - Evolutionary Clustering with Self Adaptive Genetic Operators"
|
|
5
|
+
authors = [{ name = "Joan Sebastian Tamayo Rivera" }]
|
|
6
|
+
requires-python = ">=3.12,<4"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
license = "MIT"
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Programming Language :: Python :: 3",
|
|
11
|
+
"Programming Language :: Python :: 3.12",
|
|
12
|
+
"Programming Language :: Python :: 3.13",
|
|
13
|
+
"Programming Language :: Python :: 3.14",
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"numpy>=2.0,<3",
|
|
17
|
+
"matplotlib>=3.9,<4",
|
|
18
|
+
"scikit-learn>=1.5,<2",
|
|
19
|
+
"pandas>=2.2,<3",
|
|
20
|
+
"seaborn>=0.13,<1",
|
|
21
|
+
"scipy>=1.14,<2",
|
|
22
|
+
"tqdm>=4.67,<5",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
cuda = ["cupy-cuda12x>=13.3.0,<14"]
|
|
27
|
+
|
|
28
|
+
[dependency-groups]
|
|
29
|
+
dev = [
|
|
30
|
+
"pytest>=8.0,<9",
|
|
31
|
+
"pytest-cov>=6.0,<7",
|
|
32
|
+
]
|
|
33
|
+
docs = [
|
|
34
|
+
"mkdocs-material>=9.6,<10",
|
|
35
|
+
"mkdocstrings[python]>=0.29,<1",
|
|
36
|
+
"mkdocs-jupyter>=0.25,<1",
|
|
37
|
+
"mkdocs-include-markdown-plugin>=7.0,<8",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[tool.uv]
|
|
41
|
+
default-groups = "all"
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
testpaths = ["tests"]
|
|
45
|
+
|
|
46
|
+
[tool.coverage.run]
|
|
47
|
+
source = ["pyecsago"]
|
|
48
|
+
omit = [
|
|
49
|
+
"src/pyecsago/utils/cuda/*",
|
|
50
|
+
"src/pyecsago/strategies/refinement/cuda_mde.py",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[tool.coverage.report]
|
|
54
|
+
exclude_also = [
|
|
55
|
+
"if TYPE_CHECKING:",
|
|
56
|
+
"@abstractmethod",
|
|
57
|
+
"pass$",
|
|
58
|
+
"raise NotImplementedError",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[build-system]
|
|
62
|
+
requires = ["uv_build>=0.9.21,<0.10.0"]
|
|
63
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# read version from installed package
|
|
2
|
+
from importlib.metadata import version
|
|
3
|
+
__version__ = version("pyecsago")
|
|
4
|
+
|
|
5
|
+
# import features
|
|
6
|
+
from .implementations.ecsago.algorithm import ECSAGO
|
|
7
|
+
from .core.algorithm import EvolutionaryAlgorithm
|
|
8
|
+
from .strategies.evolution.factory import ECSAGOStrategyFactory
|
|
9
|
+
from .strategies.evolution.ecsago import ECSAGOStrategy
|
|
10
|
+
from .implementations.ecsago.population import ECSAGOPopulation
|
|
11
|
+
from .implementations.ecsago.individual import ECSAGOIndividual
|
|
12
|
+
from .strategies.fitness.ecsago import ECSAGOFitnessCalculator, CUDAECSAGOFitnessCalculator
|
|
13
|
+
from .strategies.operators.crossover import LinearCrossoverPerDimension
|
|
14
|
+
from .strategies.operators.mutation import AdaptiveGaussianMutation, GaussianMutation
|
|
15
|
+
from .strategies.niching.deterministic_crowding import DeterministicCrowding
|
|
16
|
+
from .strategies.operators.haea import HAEA
|
|
17
|
+
__all__ = [
|
|
18
|
+
'ECSAGO',
|
|
19
|
+
'EvolutionaryAlgorithm',
|
|
20
|
+
'ECSAGOStrategyFactory',
|
|
21
|
+
'ECSAGOStrategy',
|
|
22
|
+
'ECSAGOPopulation',
|
|
23
|
+
'ECSAGOIndividual',
|
|
24
|
+
'ECSAGOFitnessCalculator',
|
|
25
|
+
'CUDAECSAGOFitnessCalculator',
|
|
26
|
+
'LinearCrossoverPerDimension',
|
|
27
|
+
'AdaptiveGaussianMutation',
|
|
28
|
+
'GaussianMutation',
|
|
29
|
+
'DeterministicCrowding',
|
|
30
|
+
'HAEA'
|
|
31
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .algorithm import EvolutionaryAlgorithm
|
|
2
|
+
from .context import AlgorithmContext
|
|
3
|
+
from .individual import BaseIndividual
|
|
4
|
+
from .population import BasePopulation
|
|
5
|
+
from .exceptions import PyECSAGOError, ValidationError, ConfigurationError, EvolutionError
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
'EvolutionaryAlgorithm',
|
|
9
|
+
'AlgorithmContext',
|
|
10
|
+
'BaseIndividual',
|
|
11
|
+
'BasePopulation',
|
|
12
|
+
'PyECSAGOError',
|
|
13
|
+
'ValidationError',
|
|
14
|
+
'ConfigurationError',
|
|
15
|
+
'EvolutionError',
|
|
16
|
+
]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Base class for evolutionary algorithms."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class EvolutionaryAlgorithm(ABC):
|
|
7
|
+
"""Abstract base class defining the evolutionary algorithm interface.
|
|
8
|
+
|
|
9
|
+
Subclasses must implement evolve, extract_prototypes, and refine_prototypes.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, config: dict):
|
|
13
|
+
"""Initializes the algorithm with configuration.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
config: Algorithm configuration dictionary.
|
|
17
|
+
"""
|
|
18
|
+
self.config: dict = config
|
|
19
|
+
self.use_cuda: bool = config.get('use_cuda', False)
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def evolve(self, max_generations: int = 100):
|
|
23
|
+
"""Runs the main evolutionary loop.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
max_generations: Maximum number of generations.
|
|
27
|
+
"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def extract_prototypes(self):
|
|
32
|
+
"""Extracts final prototypes from the evolved population."""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def refine_prototypes(self, prototypes, iterations: int = 10):
|
|
37
|
+
"""Refines extracted prototypes.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
prototypes: Prototypes to refine.
|
|
41
|
+
iterations: Number of refinement iterations.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
List of refined prototypes.
|
|
45
|
+
"""
|
|
46
|
+
pass
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Abstract context interface for evolutionary algorithms."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from typing import Any, TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from pyecsago.utils.data_types import DataTypeStrategy
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AlgorithmContext(ABC):
|
|
14
|
+
"""Abstract context managing data, type strategy, and CUDA resources."""
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def dtype_strategy(self) -> DataTypeStrategy:
|
|
19
|
+
"""Returns the data type strategy (NumPy/CuPy)."""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def use_cuda(self) -> bool:
|
|
25
|
+
"""Returns whether CUDA is enabled."""
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def cuda_context(self) -> Any | None:
|
|
31
|
+
"""Returns the CUDA context, or None if not available."""
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def _initialize_sigma_limits(self) -> None:
|
|
36
|
+
"""Initializes sigma2 bounds based on current data."""
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def get_sigma2_max(self) -> Any:
|
|
41
|
+
"""Returns the maximum allowed sigma2 value."""
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
def get_sigma2_min(self) -> Any:
|
|
46
|
+
"""Returns the minimum allowed sigma2 value."""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def get_sigma2_initial(self) -> Any:
|
|
51
|
+
"""Returns the initial sigma2 value for new individuals."""
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def set_data(self, data: Any) -> None:
|
|
56
|
+
"""Sets the dataset in the context.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
data: Dataset to set.
|
|
60
|
+
"""
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
@abstractmethod
|
|
64
|
+
def get_data(self) -> Any:
|
|
65
|
+
"""Returns the current dataset."""
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
@abstractmethod
|
|
69
|
+
def execute(self, func: Callable, *args: Any, **kwargs: Any) -> Any:
|
|
70
|
+
"""Executes a function in the appropriate context (CPU/GPU).
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
func: Function to execute.
|
|
74
|
+
*args: Positional arguments.
|
|
75
|
+
**kwargs: Keyword arguments.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Function result.
|
|
79
|
+
"""
|
|
80
|
+
pass
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Custom exception hierarchy for PyECSAGO."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class PyECSAGOError(Exception):
|
|
5
|
+
"""Base exception for the PyECSAGO project."""
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ValidationError(PyECSAGOError, ValueError):
|
|
10
|
+
"""Raised when data or parameter validation fails."""
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ConfigurationError(PyECSAGOError, ValueError):
|
|
15
|
+
"""Raised when algorithm configuration is invalid."""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class EvolutionError(PyECSAGOError, RuntimeError):
|
|
20
|
+
"""Raised when an error occurs during the evolutionary process."""
|
|
21
|
+
pass
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Base class for population individuals."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from typing import Any, TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
import cupy as cp
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BaseIndividual(ABC):
|
|
15
|
+
"""Abstract base class for a population individual."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, genome: np.ndarray | cp.ndarray, sigma2: np.ndarray | cp.ndarray, **kwargs: Any) -> None:
|
|
18
|
+
self.genome = genome
|
|
19
|
+
self.sigma2 = sigma2
|
|
20
|
+
self.fitness = 0.0
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def calculate_fitness(self, data: np.ndarray, **kwargs: Any) -> None:
|
|
24
|
+
"""Calculates the individual's fitness."""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
def clone(self) -> BaseIndividual:
|
|
28
|
+
"""Creates a copy of the individual.
|
|
29
|
+
|
|
30
|
+
Raises:
|
|
31
|
+
NotImplementedError: Subclasses must override this method.
|
|
32
|
+
"""
|
|
33
|
+
raise NotImplementedError("Subclasses must override clone()")
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Base class for populations."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BasePopulation(ABC):
|
|
7
|
+
"""Abstract base class for a population of individuals."""
|
|
8
|
+
|
|
9
|
+
@abstractmethod
|
|
10
|
+
def evaluate_population(self):
|
|
11
|
+
"""Evaluates fitness for all individuals in the population."""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def evolve(self):
|
|
16
|
+
"""Evolves the population to the next generation."""
|
|
17
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .algorithm import ECSAGO
|
|
2
|
+
from .context import StandardAlgorithmContext
|
|
3
|
+
from .individual import ECSAGOIndividual
|
|
4
|
+
from .population import ECSAGOPopulation
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
'ECSAGO',
|
|
8
|
+
'StandardAlgorithmContext',
|
|
9
|
+
'ECSAGOIndividual',
|
|
10
|
+
'ECSAGOPopulation',
|
|
11
|
+
]
|