graphfla 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.
- graphfla-0.1.0/LICENSE +21 -0
- graphfla-0.1.0/MANIFEST.in +3 -0
- graphfla-0.1.0/PKG-INFO +47 -0
- graphfla-0.1.0/README.md +166 -0
- graphfla-0.1.0/graphfla/__init__.py +87 -0
- graphfla-0.1.0/graphfla/_neighbors.py +143 -0
- graphfla-0.1.0/graphfla/_processor.py +657 -0
- graphfla-0.1.0/graphfla/algorithms/__init__.py +11 -0
- graphfla-0.1.0/graphfla/algorithms/adaptive_walk.py +137 -0
- graphfla-0.1.0/graphfla/algorithms/random_walk.py +76 -0
- graphfla-0.1.0/graphfla/analysis/__init__.py +99 -0
- graphfla-0.1.0/graphfla/analysis/correlation.py +274 -0
- graphfla-0.1.0/graphfla/analysis/epistasis.py +1019 -0
- graphfla-0.1.0/graphfla/analysis/fitness.py +212 -0
- graphfla-0.1.0/graphfla/analysis/navigability.py +532 -0
- graphfla-0.1.0/graphfla/analysis/robustness.py +267 -0
- graphfla-0.1.0/graphfla/analysis/ruggedness.py +246 -0
- graphfla-0.1.0/graphfla/distances.py +113 -0
- graphfla-0.1.0/graphfla/filters.py +306 -0
- graphfla-0.1.0/graphfla/landscape.py +2219 -0
- graphfla-0.1.0/graphfla/lon.py +510 -0
- graphfla-0.1.0/graphfla/problems/__init__.py +22 -0
- graphfla-0.1.0/graphfla/problems/base_problem.py +110 -0
- graphfla-0.1.0/graphfla/problems/biological.py +398 -0
- graphfla-0.1.0/graphfla/problems/combinatorial.py +346 -0
- graphfla-0.1.0/graphfla/sampling.py +186 -0
- graphfla-0.1.0/graphfla/utils.py +149 -0
- graphfla-0.1.0/graphfla.egg-info/PKG-INFO +47 -0
- graphfla-0.1.0/graphfla.egg-info/SOURCES.txt +34 -0
- graphfla-0.1.0/graphfla.egg-info/dependency_links.txt +1 -0
- graphfla-0.1.0/graphfla.egg-info/requires.txt +7 -0
- graphfla-0.1.0/graphfla.egg-info/top_level.txt +1 -0
- graphfla-0.1.0/pyproject.toml +3 -0
- graphfla-0.1.0/setup.cfg +4 -0
- graphfla-0.1.0/setup.py +46 -0
- graphfla-0.1.0/tests/test.py +467 -0
graphfla-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 COLA Laboratory
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
graphfla-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: graphfla
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python package for Graph-based Fitness Landscape Analysis.
|
|
5
|
+
Home-page: https://github.com/COLA-Laboratory/GraphFLA/tree/main
|
|
6
|
+
Author: Mingyu Huang
|
|
7
|
+
Author-email: m.huang.gla@outlook.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
18
|
+
Classifier: Development Status :: 3 - Alpha
|
|
19
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/plain
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: joblib>=1.0.0
|
|
23
|
+
Requires-Dist: numpy>=1.19
|
|
24
|
+
Requires-Dist: pandas>=1.1
|
|
25
|
+
Requires-Dist: python-igraph>=0.9
|
|
26
|
+
Requires-Dist: scikit-learn>=0.24
|
|
27
|
+
Requires-Dist: scipy>=1.6.0
|
|
28
|
+
Requires-Dist: tqdm>=4.40
|
|
29
|
+
Dynamic: author
|
|
30
|
+
Dynamic: author-email
|
|
31
|
+
Dynamic: classifier
|
|
32
|
+
Dynamic: description
|
|
33
|
+
Dynamic: description-content-type
|
|
34
|
+
Dynamic: home-page
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
Dynamic: requires-dist
|
|
37
|
+
Dynamic: requires-python
|
|
38
|
+
Dynamic: summary
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
graphfla: A Python package for Graph-based Fitness Landscape Analysis.
|
|
42
|
+
========================================================
|
|
43
|
+
graphfla provides tools for generating, constructing, analyzing and
|
|
44
|
+
manipulating fitness landscapes commonly encountered in evolutionary biology
|
|
45
|
+
and black-box optimization. It includes a variety of features chacterizing
|
|
46
|
+
different aspects of fitness landscape topography, such as ruggedness,
|
|
47
|
+
navigability, neutrality, and epistasis.
|
graphfla-0.1.0/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# GraphFLA
|
|
2
|
+
|
|
3
|
+
[](./LICENSE)
|
|
4
|
+
<!-- [](https://pypi.python.org/pypi/graphfla/)
|
|
5
|
+
[](https://github.com/yourusername/graphfla/issues)
|
|
6
|
+
[](https://github.com/yourusername/graphfla/stargazers) -->
|
|
7
|
+
|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
**GraphFLA** (Graph-based Fitness Landscape Analysis) is a Python framework for constructing, analyzing, manipulating and visualizing **fitness landscapes** as graphs. It provides a broad collection of features rooted in evolutoinary biology to decipher the topography of compelx fitness landscapes of diverse modalities.
|
|
11
|
+
|
|
12
|
+
## Key Features
|
|
13
|
+
- **Versatility:** applicable to arbitrary discrete, combinatorial sequence-fitness data, ranging from biomolecules like DNA, RNA, and protein, to functional units like genes, to complex ecological communities.
|
|
14
|
+
- **Comprehensiveness:** offers a holistic collection of 20+ features for characterizing 4 fundamental topographical aspects of fitness landscape, including ruggedness, navigability, epistassi and neutrality.
|
|
15
|
+
- **Interoperability:** works with the same data format (i.e., `X` and `f`) as in training machine learning (ML) models, thus interoperable with established ML ecosystems in different disciplines.
|
|
16
|
+
- **Scalability:** heavily optimized to be capable of handling landscapes with even millions of variants.
|
|
17
|
+
- **Extensibility:** new landscape features can be easily added via an unified API.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
Our documentation website is currently under development, but `GraphFLA` is quite easy to get started with!
|
|
23
|
+
|
|
24
|
+
### 1. Prepare your data
|
|
25
|
+
|
|
26
|
+
`GraphFLA` is designed to interoperate with established ML frameworks and benchmarks by using the same data format as in ML model training: an `X` and an `f`.
|
|
27
|
+
|
|
28
|
+
Specifically, `X` can either be a list of sequences of strings representing genotypes, or a `pd.DataFrame` or an `numpy.ndarray`, wherein each column represents a loci; `f` can either be a list, `pd.Series` or `numpy.ndarray`.
|
|
29
|
+
|
|
30
|
+
To make landscape construction faster, we recommended removing redundant loci in `X` (i.e., those that are never mutated across the whole library) .
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import pandas as pd
|
|
34
|
+
|
|
35
|
+
data = pd.read_csv("path_to_data.csv")
|
|
36
|
+
|
|
37
|
+
X = data["sequences"]
|
|
38
|
+
f = data["fitness"]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. Create the landscape object
|
|
42
|
+
|
|
43
|
+
Creating a landscape object in `GraphFLA` is much like training an ML model: we first initialize a `Landscape` class, and then build it with our data.
|
|
44
|
+
|
|
45
|
+
Here, assume we are working with DNA sequences. `GraphFLA` provides registered methods for performance optimization for this type, which can be triggered by specifying `type="dna"`. Alternatively, you can directly use the `DNALandscape` class to get the same effect, which is natively built for DNA data.
|
|
46
|
+
|
|
47
|
+
The `maximize` parameter specifies the direction of optimization, i.e., whether `f` is to be optimized or minimized.
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from graphfla.landscape import Landscape
|
|
51
|
+
|
|
52
|
+
# initialize the landscape
|
|
53
|
+
# this is equivalent to:
|
|
54
|
+
# from graphfla.landscape import DNALandscape
|
|
55
|
+
# landscape = DNALandscape(maximize=True)
|
|
56
|
+
landscape = Landscape(type="dna", maximize=True)
|
|
57
|
+
|
|
58
|
+
# build the landscape with our data
|
|
59
|
+
landscape.build_from_data(X, f, verbose=True)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 3. Landscape analysis
|
|
63
|
+
|
|
64
|
+
Once the landscape is constructed, we can then analyze its features using the available functions (see later).
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from graphfla.analysis import (
|
|
68
|
+
lo_ratio,
|
|
69
|
+
classify_epistasis,
|
|
70
|
+
r_s_ratio,
|
|
71
|
+
neutrality,
|
|
72
|
+
global_optima_accessibility,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
local_optima_ratio = lo_ratio(landscape)
|
|
76
|
+
epistasis = classify_epistasis(landscape)
|
|
77
|
+
r_s_score = r_s_ratio(landscape)
|
|
78
|
+
neutrality_index = neutrality(landscape)
|
|
79
|
+
go_access = global_optima_accessibility(landscape)
|
|
80
|
+
```
|
|
81
|
+
### 4. Playing with arbitrary combinatorial data
|
|
82
|
+
The `type` parameter of the `Landscape` class currently supports `"dna"`, `rna`, `"protein"`, and `"boolean"`. However, this does not mean that `GraphFLA` can only work with these types of data; instead, these registered values are only for convenience and performance optimization purpose.
|
|
83
|
+
|
|
84
|
+
In fact, `GraphFLA` can handle arbitrary combinatorial search space as long as the values of each variable is discrete. To work with such data, we can initialize a general landscape, and then pass in a dictionary to specify the data type of each variable (options: `{"ordinal", "cateogrical", "boolean"}`).
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
import pandas as pd
|
|
88
|
+
from graphfla.landscape import Landscape
|
|
89
|
+
|
|
90
|
+
complex_data = pd.read_csv("path_to_complex_data.csv")
|
|
91
|
+
|
|
92
|
+
f = complex_data["fitness"]
|
|
93
|
+
# data serving as "X"
|
|
94
|
+
complex_search_space = complex_data.drop(columns=["fitness"])
|
|
95
|
+
|
|
96
|
+
# initialize a general fitness landscape without specifying `type`
|
|
97
|
+
landscape = Landscape(maximize=True)
|
|
98
|
+
|
|
99
|
+
# create a data type dictionary
|
|
100
|
+
data_types = {
|
|
101
|
+
"x1": "ordinal",
|
|
102
|
+
"x2": "categorical",
|
|
103
|
+
"x3": "boolean",
|
|
104
|
+
"x4": "categorical"
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# build the landscape with our data and specified data types
|
|
108
|
+
landscape.build_from_data(X, f, data_types=data_types, verbose=True)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Landscape Analysis Features
|
|
112
|
+
|
|
113
|
+
`GraphFLA` currently supports the following features for landscape analysis.
|
|
114
|
+
|
|
115
|
+
| **Class** | **Function** | **Feature** | **Range** | **Higher value indicates** |
|
|
116
|
+
|--------------------------|----------------------------------|----------------------------------------|---------------|----------------------------------------|
|
|
117
|
+
| **Ruggedness** | `lo_ratio` | Fraction of local optima | [0,1] | ↑ more peaks |
|
|
118
|
+
| | `r_s_ratio` | Roughness-slope ratio | [0, ∞) | ↑ ruggedness |
|
|
119
|
+
| | `autocorrelation` | Autocorrelation | [-1, 1] | ↓ ruggedness |
|
|
120
|
+
| | `gamma_statistic` | Gamma statistic | [-1, 1] | ↑ ruggedness |
|
|
121
|
+
| | `gamma_statistic` | Gamma star statistic | [-1, 1] | ↑ ruggedness |
|
|
122
|
+
| | `neighbor_fit_corr` | Neighbor-fitness correlation | [-1, 1] | ↓ ruggedness |
|
|
123
|
+
| **Epistasis** | `classify_epistasis` | Magnitude epistasis | [0, 1) | ↓ evolutionary constraints |
|
|
124
|
+
| | `classify_epistasis` | Sign epistasis | [0, 1] | ↑ evolutionary constraints |
|
|
125
|
+
| | `classify_epistasis` | Reciprocal sign epistasis | [0, 1] | ↑ evolutionary constraints |
|
|
126
|
+
| | `classify_epistasis` | Positive epistasis | [0, 1] | ↑ synergistic effects |
|
|
127
|
+
| | `classify_epistasis` | Negative epistasis | [0, 1] | ↑ antagonistic effects |
|
|
128
|
+
| | `global_idiosyncratic_index` | Global idiosyncratic index | [0, 1] | ↑ specific interactions |
|
|
129
|
+
| | `diminishing_returns_index` | Diminishing return epistasis | [0, 1] | ↑ flat peaks |
|
|
130
|
+
| | `increasing_costs_index` | Increasing cost epistasis | [0, 1] | ↑ steep descents |
|
|
131
|
+
| | `higher_order_epistasis` | Higher-order epistasis | [0, 1] | ↓ higher-order interactions |
|
|
132
|
+
| **Navigability** | `fitness_distance_corr` | Fitness-distance correlation | [-1, 1] | ↑ navigation |
|
|
133
|
+
| | `go_accessibility` | Global optima accessibility | [0, 1] | ↑ access to global peaks |
|
|
134
|
+
| | `basin_fit_corr` | Basin-fitness corr. (accessible) | [-1, 1] | ↑ access to fitter peaks |
|
|
135
|
+
| | `basin_fit_corr` | Basin-fitness corr. (greedy) | [-1, 1] | ↑ access to fitter peaks |
|
|
136
|
+
| | `calculate_evol_enhance` | Evol-enhancing mutation | [0, 1] | ↑ evolvability |
|
|
137
|
+
| **Neutrality** | `neutrality` | Neutrality | [0, 1] | ↑ neutrality |
|
|
138
|
+
| **Fitness Distribution** | `fitness_distribution` | Skewness | (-∞, ∞) | ↑ asymmetry of fitness values |
|
|
139
|
+
| | `fitness_distribution` | Kurtosis | (-∞, ∞) | ↑ outlier/extreme value prevalence |
|
|
140
|
+
| | `fitness_distribution` | Coefficient of variation (CV) | [0, ∞) | ↑ relative fitness variability |
|
|
141
|
+
| | `fitness_distribution` | Quartile coefficient | [0, 1] | ↑ interquartile dispersion |
|
|
142
|
+
| | `fitness_distribution` | Median/Mean ratio | [0, ∞) | ↑ deviation from symmetry |
|
|
143
|
+
| | `fitness_distribution` | Relative range | [0, ∞) | ↑ spread of fitness values |
|
|
144
|
+
| | `fitness_distribution` | Cauchy location parameter | (-∞, ∞) | ↑ central tendency estimate |
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
## Landscape Classes
|
|
148
|
+
|
|
149
|
+
`GraphFLA` currently offers the following classes for landscape construction.
|
|
150
|
+
|
|
151
|
+
|**Classes**|**Supported search space**|**Description**|
|
|
152
|
+
|--|--|--|
|
|
153
|
+
|`Landscape`|All discrete, combinatorial spaces, where each variable can be either categorical, boolean, or ordinal|The base landscape class, most generalizable|
|
|
154
|
+
|`SequenceLandscape`|Categorical data where each variable takes values from the same alphabet.|Class optimized for general sequence data|
|
|
155
|
+
|`BooleanLandscape`|Boolean space|Class optimized for boolean data|
|
|
156
|
+
|`DNALandscape`|DNA sequence space|Class optimized for DNA data|
|
|
157
|
+
|`RNALandscape`|RNA sequence space|Class optimized for RNA data|
|
|
158
|
+
|`ProteinLandscape`|Protein sequence space|Class optimized for protein data|
|
|
159
|
+
|
|
160
|
+
## License
|
|
161
|
+
|
|
162
|
+
This project is licensed under the terms of the [MIT License](./LICENSE).
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
**Happy analyzing!** If you have any questions or suggestions, feel free to open an issue or start a discussion.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# graphfla/__init__.py
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
graphfla: A Python package for Graph-based Fitness Landscape Analysis.
|
|
5
|
+
========================================================
|
|
6
|
+
|
|
7
|
+
graphfla provides tools for generating, analyzing, simulating evolution on,
|
|
8
|
+
and visualizing fitness landscapes, commonly encountered in evolutionary
|
|
9
|
+
computation, biology, optimization, and machine learning model training dynamics.
|
|
10
|
+
|
|
11
|
+
It aims to offer a modular and user-friendly interface for researchers and
|
|
12
|
+
practitioners working with sequence spaces, combinatorial spaces, and
|
|
13
|
+
their associated fitness functions.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
# Authors: [Mingyu Huang, COLALab@UoE]
|
|
17
|
+
|
|
18
|
+
import importlib
|
|
19
|
+
import logging
|
|
20
|
+
import os
|
|
21
|
+
import random
|
|
22
|
+
|
|
23
|
+
__version__ = "0.1.dev0"
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
_exported_config_functions = []
|
|
28
|
+
|
|
29
|
+
_exported_core_objects = ["Landscape"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# List of submodules and top-level utility modules to be accessible
|
|
33
|
+
# via lazy loading (e.g., graphfla.analysis, graphfla.utils)
|
|
34
|
+
_submodules = [
|
|
35
|
+
"analysis",
|
|
36
|
+
"algorithms",
|
|
37
|
+
"distances",
|
|
38
|
+
"landscape",
|
|
39
|
+
"lon",
|
|
40
|
+
"plotting",
|
|
41
|
+
"problems",
|
|
42
|
+
"sampling",
|
|
43
|
+
"filters" "utils",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
__all__ = _submodules + _exported_config_functions + _exported_core_objects
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def __dir__():
|
|
50
|
+
"""Provides controlled module listing for autocompletion."""
|
|
51
|
+
return __all__
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def __getattr__(name):
|
|
55
|
+
"""
|
|
56
|
+
Lazily imports submodules and top-level modules upon first access.
|
|
57
|
+
|
|
58
|
+
Example:
|
|
59
|
+
>>> import graphfla
|
|
60
|
+
>>> graphfla.analysis.fdc # analysis submodule is imported here
|
|
61
|
+
"""
|
|
62
|
+
if name in _submodules:
|
|
63
|
+
return importlib.import_module(f".{name}", __name__)
|
|
64
|
+
elif name in _exported_core_objects or name in _exported_config_functions:
|
|
65
|
+
try:
|
|
66
|
+
return globals()[name]
|
|
67
|
+
except KeyError:
|
|
68
|
+
raise AttributeError(f"Module '{__name__}' has no attribute '{name}'")
|
|
69
|
+
else:
|
|
70
|
+
try:
|
|
71
|
+
return globals()[name]
|
|
72
|
+
except KeyError:
|
|
73
|
+
raise AttributeError(f"Module '{__name__}' has no attribute '{name}'")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def setup_module(module):
|
|
77
|
+
"""Fixture for the tests to assure globally controllable seeding of RNGs."""
|
|
78
|
+
import numpy as np
|
|
79
|
+
|
|
80
|
+
_random_seed = os.environ.get("GRAPHFLA_SEED", None)
|
|
81
|
+
if _random_seed is None:
|
|
82
|
+
_random_seed = np.random.uniform() * np.iinfo(np.int32).max
|
|
83
|
+
_random_seed = int(_random_seed)
|
|
84
|
+
|
|
85
|
+
logger.info("I: Seeding RNGs with %r", _random_seed)
|
|
86
|
+
np.random.seed(_random_seed)
|
|
87
|
+
random.seed(_random_seed)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
from typing import Protocol, Tuple, Dict, List, Any, runtime_checkable
|
|
2
|
+
import warnings
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@runtime_checkable
|
|
6
|
+
class NeighborGenerator(Protocol):
|
|
7
|
+
"""Protocol defining the interface for neighbor generation."""
|
|
8
|
+
|
|
9
|
+
def generate(
|
|
10
|
+
self, config: Tuple, config_dict: Dict, n_edit: int = 1
|
|
11
|
+
) -> List[Tuple]:
|
|
12
|
+
"""
|
|
13
|
+
Generate neighbors for a given configuration.
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
config : tuple
|
|
18
|
+
The configuration for which to find neighbors
|
|
19
|
+
config_dict : dict
|
|
20
|
+
Dictionary describing the encoding
|
|
21
|
+
n_edit : int
|
|
22
|
+
Edit distance for neighborhood definition
|
|
23
|
+
|
|
24
|
+
Returns
|
|
25
|
+
-------
|
|
26
|
+
list[tuple]
|
|
27
|
+
List of neighboring configurations
|
|
28
|
+
"""
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BooleanNeighborGenerator:
|
|
33
|
+
"""Generator for boolean neighbors (bit flips)."""
|
|
34
|
+
|
|
35
|
+
def generate(
|
|
36
|
+
self, config: Tuple, config_dict: Dict, n_edit: int = 1
|
|
37
|
+
) -> List[Tuple]:
|
|
38
|
+
"""Generate neighbors by flipping bits."""
|
|
39
|
+
if n_edit != 1:
|
|
40
|
+
warnings.warn(
|
|
41
|
+
f"BooleanNeighborGenerator only supports n_edit=1 for single bit flips. "
|
|
42
|
+
f"Received n_edit={n_edit}. Returning no neighbors.",
|
|
43
|
+
UserWarning,
|
|
44
|
+
)
|
|
45
|
+
return []
|
|
46
|
+
|
|
47
|
+
neighbors = []
|
|
48
|
+
current_config_list = list(config)
|
|
49
|
+
num_bits = len(current_config_list)
|
|
50
|
+
|
|
51
|
+
for i in range(num_bits):
|
|
52
|
+
neighbor_list = current_config_list.copy()
|
|
53
|
+
neighbor_list[i] = 1 - neighbor_list[i] # Flip bit
|
|
54
|
+
neighbors.append(tuple(neighbor_list))
|
|
55
|
+
|
|
56
|
+
return neighbors
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class SequenceNeighborGenerator:
|
|
60
|
+
"""Generator for sequence neighbors (substitutions)."""
|
|
61
|
+
|
|
62
|
+
def __init__(self, alphabet_size: int):
|
|
63
|
+
"""
|
|
64
|
+
Initialize with the size of the alphabet.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
alphabet_size : int
|
|
69
|
+
Number of possible values at each position
|
|
70
|
+
"""
|
|
71
|
+
self.alphabet_size = alphabet_size
|
|
72
|
+
|
|
73
|
+
def generate(
|
|
74
|
+
self, config: Tuple, config_dict: Dict, n_edit: int = 1
|
|
75
|
+
) -> List[Tuple]:
|
|
76
|
+
"""Generate neighbors by substituting at each position."""
|
|
77
|
+
if n_edit != 1:
|
|
78
|
+
warnings.warn(
|
|
79
|
+
f"SequenceNeighborGenerator only supports n_edit=1 for single position substitutions. "
|
|
80
|
+
f"Received n_edit={n_edit}. Returning no neighbors.",
|
|
81
|
+
UserWarning,
|
|
82
|
+
)
|
|
83
|
+
return []
|
|
84
|
+
|
|
85
|
+
neighbors = []
|
|
86
|
+
current_config_list = list(config)
|
|
87
|
+
num_positions = len(current_config_list)
|
|
88
|
+
|
|
89
|
+
for i in range(num_positions):
|
|
90
|
+
original_val = current_config_list[i]
|
|
91
|
+
# Try each possible substitution at this position
|
|
92
|
+
for new_val in range(self.alphabet_size):
|
|
93
|
+
if new_val != original_val:
|
|
94
|
+
neighbor_list = current_config_list.copy()
|
|
95
|
+
neighbor_list[i] = new_val
|
|
96
|
+
neighbors.append(tuple(neighbor_list))
|
|
97
|
+
|
|
98
|
+
return neighbors
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class DefaultNeighborGenerator:
|
|
102
|
+
"""Default generator for mixed data types."""
|
|
103
|
+
|
|
104
|
+
def generate(
|
|
105
|
+
self, config: Tuple, config_dict: Dict, n_edit: int = 1
|
|
106
|
+
) -> List[Tuple]:
|
|
107
|
+
"""Generate neighbors based on data types in config_dict."""
|
|
108
|
+
if n_edit != 1:
|
|
109
|
+
warnings.warn(
|
|
110
|
+
f"DefaultNeighborGenerator only fully supports n_edit=1. "
|
|
111
|
+
f"Received n_edit={n_edit}.",
|
|
112
|
+
UserWarning,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
neighbors = []
|
|
116
|
+
num_vars = len(config)
|
|
117
|
+
|
|
118
|
+
for i in range(num_vars):
|
|
119
|
+
info = config_dict[i]
|
|
120
|
+
current_val = config[i]
|
|
121
|
+
dtype = info["type"]
|
|
122
|
+
|
|
123
|
+
if dtype == "boolean":
|
|
124
|
+
# Flip the bit (0 to 1, 1 to 0)
|
|
125
|
+
new_vals = [1 - current_val]
|
|
126
|
+
elif dtype in ["categorical", "ordinal"]:
|
|
127
|
+
# Iterate through all possible values
|
|
128
|
+
max_val = info["max"]
|
|
129
|
+
new_vals = [v for v in range(max_val + 1) if v != current_val]
|
|
130
|
+
else:
|
|
131
|
+
warnings.warn(
|
|
132
|
+
f"Unsupported dtype '{dtype}' in generate_neighbors, skipping var {i}",
|
|
133
|
+
RuntimeWarning,
|
|
134
|
+
)
|
|
135
|
+
continue
|
|
136
|
+
|
|
137
|
+
# Create neighbor tuples
|
|
138
|
+
for new_val in new_vals:
|
|
139
|
+
neighbor_list = list(config)
|
|
140
|
+
neighbor_list[i] = new_val
|
|
141
|
+
neighbors.append(tuple(neighbor_list))
|
|
142
|
+
|
|
143
|
+
return neighbors
|