alf_tools 0.1.0a0__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.
- alf_tools-0.1.0a0/PKG-INFO +166 -0
- alf_tools-0.1.0a0/README.md +122 -0
- alf_tools-0.1.0a0/alf_tools/__init__.py +17 -0
- alf_tools-0.1.0a0/alf_tools/datasets/__init__.py +28 -0
- alf_tools-0.1.0a0/alf_tools/datasets/flip.py +312 -0
- alf_tools-0.1.0a0/alf_tools/datasets/gfp.py +73 -0
- alf_tools-0.1.0a0/alf_tools/datasets/guacamol/__init__.py +17 -0
- alf_tools-0.1.0a0/alf_tools/datasets/guacamol/guacamol_dataset.py +482 -0
- alf_tools-0.1.0a0/alf_tools/datasets/guacamol/guacamol_scoring.py +587 -0
- alf_tools-0.1.0a0/alf_tools/datasets/guacamol/guacamol_utils.py +375 -0
- alf_tools-0.1.0a0/alf_tools/datasets/proteingym.py +323 -0
- alf_tools-0.1.0a0/alf_tools/models/__init__.py +84 -0
- alf_tools-0.1.0a0/alf_tools/models/chemprop.py +475 -0
- alf_tools-0.1.0a0/alf_tools/models/cnn.py +674 -0
- alf_tools-0.1.0a0/alf_tools/models/ensemble.py +350 -0
- alf_tools-0.1.0a0/alf_tools/models/esm2.py +902 -0
- alf_tools-0.1.0a0/alf_tools/models/esm_utils/__init__.py +15 -0
- alf_tools-0.1.0a0/alf_tools/models/esm_utils/config.py +202 -0
- alf_tools-0.1.0a0/alf_tools/models/esm_utils/loss.py +171 -0
- alf_tools-0.1.0a0/alf_tools/models/esm_utils/scoring_function.py +129 -0
- alf_tools-0.1.0a0/alf_tools/models/esmfold.py +316 -0
- alf_tools-0.1.0a0/alf_tools/models/gp.py +955 -0
- alf_tools-0.1.0a0/alf_tools/models/mlp.py +520 -0
- alf_tools-0.1.0a0/alf_tools/models/pyrosetta.py +270 -0
- alf_tools-0.1.0a0/alf_tools/models/utils/__init__.py +33 -0
- alf_tools-0.1.0a0/alf_tools/models/utils/botorch_model_wrapper.py +326 -0
- alf_tools-0.1.0a0/alf_tools/models/utils/botorch_utils.py +340 -0
- alf_tools-0.1.0a0/alf_tools/models/utils/config_utils.py +105 -0
- alf_tools-0.1.0a0/alf_tools/models/utils/data_utils.py +81 -0
- alf_tools-0.1.0a0/alf_tools/models/utils/sequence_utils.py +134 -0
- alf_tools-0.1.0a0/alf_tools/models/utils/torch_utils.py +31 -0
- alf_tools-0.1.0a0/alf_tools/optimizer/acquisition_functions/__init__.py +35 -0
- alf_tools-0.1.0a0/alf_tools/optimizer/acquisition_functions/botorch_acquisition.py +519 -0
- alf_tools-0.1.0a0/alf_tools/optimizer/acquisition_functions/botorch_samplers.py +128 -0
- alf_tools-0.1.0a0/alf_tools/optimizer/acquisition_functions/core_set.py +127 -0
- alf_tools-0.1.0a0/alf_tools/optimizer/acquisition_functions/expected_improvement.py +64 -0
- alf_tools-0.1.0a0/alf_tools/optimizer/acquisition_functions/greedy.py +39 -0
- alf_tools-0.1.0a0/alf_tools/optimizer/acquisition_functions/thompson_sampling.py +89 -0
- alf_tools-0.1.0a0/alf_tools/optimizer/acquisition_functions/ucb.py +59 -0
- alf_tools-0.1.0a0/alf_tools/optimizer/search/__init__.py +20 -0
- alf_tools-0.1.0a0/alf_tools/optimizer/search/botorch_continuous_search.py +71 -0
- alf_tools-0.1.0a0/alf_tools/optimizer/search/single_mutant_search.py +52 -0
- alf_tools-0.1.0a0/alf_tools/utils/__init__.py +27 -0
- alf_tools-0.1.0a0/alf_tools/utils/constants.py +20 -0
- alf_tools-0.1.0a0/alf_tools.egg-info/PKG-INFO +166 -0
- alf_tools-0.1.0a0/alf_tools.egg-info/SOURCES.txt +49 -0
- alf_tools-0.1.0a0/alf_tools.egg-info/dependency_links.txt +1 -0
- alf_tools-0.1.0a0/alf_tools.egg-info/requires.txt +31 -0
- alf_tools-0.1.0a0/alf_tools.egg-info/top_level.txt +1 -0
- alf_tools-0.1.0a0/pyproject.toml +89 -0
- alf_tools-0.1.0a0/setup.cfg +4 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: alf_tools
|
|
3
|
+
Version: 0.1.0a0
|
|
4
|
+
Summary: A Python package with tools for performing active learning experiments.
|
|
5
|
+
Author: InstaDeep Ltd
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://instadeepai.github.io/alf/
|
|
8
|
+
Project-URL: Repository, https://github.com/instadeepai/alf
|
|
9
|
+
Project-URL: Documentation, https://instadeepai.github.io/alf/
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/instadeepai/alf/issues
|
|
11
|
+
Keywords: active learning,bayesian optimization,machine learning,scientific discovery,pytorch
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: alf_core
|
|
21
|
+
Requires-Dist: requests>=2.25.0
|
|
22
|
+
Requires-Dist: huggingface_hub<2.0,>=0.27.0
|
|
23
|
+
Requires-Dist: pyarrow>=22.0.0
|
|
24
|
+
Requires-Dist: jaxtyping>=0.3.5
|
|
25
|
+
Requires-Dist: torch>=2.10.0
|
|
26
|
+
Requires-Dist: gpytorch<2.0,>=1.9.0
|
|
27
|
+
Requires-Dist: scipy>=1.9.0
|
|
28
|
+
Requires-Dist: botorch<1.0,>=0.17.0
|
|
29
|
+
Provides-Extra: transformers
|
|
30
|
+
Requires-Dist: transformers>=4.36.0; extra == "transformers"
|
|
31
|
+
Provides-Extra: chemprop
|
|
32
|
+
Requires-Dist: chemprop<3.0.0,>=2.0.0; extra == "chemprop"
|
|
33
|
+
Provides-Extra: esm2
|
|
34
|
+
Requires-Dist: alf_tools[transformers]; extra == "esm2"
|
|
35
|
+
Provides-Extra: esmfold
|
|
36
|
+
Requires-Dist: alf_tools[transformers]; extra == "esmfold"
|
|
37
|
+
Requires-Dist: accelerate>=0.26.0; extra == "esmfold"
|
|
38
|
+
Provides-Extra: guacamol
|
|
39
|
+
Requires-Dist: rdkit>=2022.9.0; extra == "guacamol"
|
|
40
|
+
Provides-Extra: protein
|
|
41
|
+
Requires-Dist: alf_tools[esm2,esmfold]; extra == "protein"
|
|
42
|
+
Provides-Extra: molecule
|
|
43
|
+
Requires-Dist: alf_tools[chemprop,guacamol]; extra == "molecule"
|
|
44
|
+
|
|
45
|
+
# alf-tools
|
|
46
|
+
|
|
47
|
+
[](https://pypi.org/project/alf-tools/)
|
|
48
|
+
[](https://www.python.org/downloads/)
|
|
49
|
+
[](https://github.com/instadeepai/alf/blob/main/LICENSE)
|
|
50
|
+
[](https://github.com/instadeepai/alf/tree/main/tools)
|
|
51
|
+
[](https://instadeepai.github.io/alf/)
|
|
52
|
+
|
|
53
|
+
**Ready-to-use models, datasets, and acquisition functions for ALF (Active Learning Framework).**
|
|
54
|
+
|
|
55
|
+
`alf-tools` builds on [alf-core](https://github.com/instadeepai/alf/blob/main/core/README.md)
|
|
56
|
+
to give you everything needed to run active-learning experiments out of the box — example
|
|
57
|
+
datasets (GFP, ProteinGym, FLIP, GuacaMol), models (CNN, Gaussian Process, ESM-2, Chemprop),
|
|
58
|
+
acquisition functions, and search strategies. Use it for quick-start and prototyping; reach for
|
|
59
|
+
`alf-core` alone when you want the lightweight framework with no ML-framework dependencies.
|
|
60
|
+
|
|
61
|
+
<div align="center">
|
|
62
|
+
<img src="https://raw.githubusercontent.com/instadeepai/alf/main/docs/imgs/alf_main_figure.png" alt="ALF active-learning loop overview" width="70%">
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
## Installation
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install alf-tools
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
This installs PyTorch and the core dependencies. Some models need additional dependencies —
|
|
72
|
+
see [Optional Extras](#optional-extras) below.
|
|
73
|
+
|
|
74
|
+
### Optional Extras
|
|
75
|
+
|
|
76
|
+
Some models require additional dependencies. Append one or more extras to the package name.
|
|
77
|
+
Per-model extras (`esm2`, `esmfold`, `chemprop`, `guacamol`) install exactly one model's
|
|
78
|
+
dependencies; workflow umbrellas (`protein`, `molecule`) group the extras you are likely to use
|
|
79
|
+
together.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pip install "alf-tools[esm2]" # ESM-2 protein language model (ESM2Model)
|
|
83
|
+
pip install "alf-tools[esmfold]" # ESMFold structure-prediction oracle (ESMFoldModel)
|
|
84
|
+
pip install "alf-tools[chemprop]" # Chemprop small-molecule MPNN (ChempropModel)
|
|
85
|
+
pip install "alf-tools[guacamol]" # GuacaMol RDKit-based dataset/scoring
|
|
86
|
+
pip install "alf-tools[protein]" # umbrella: esm2 + esmfold
|
|
87
|
+
pip install "alf-tools[molecule]" # umbrella: chemprop + guacamol
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Documentation
|
|
91
|
+
|
|
92
|
+
- **Full documentation:** [instadeepai.github.io/alf](https://instadeepai.github.io/alf/)
|
|
93
|
+
- **API reference:** [alf-tools API](https://instadeepai.github.io/alf/api/alf_tools/index.html)
|
|
94
|
+
- **Installation guide:** [instadeepai.github.io/alf/installation.html](https://instadeepai.github.io/alf/installation.html)
|
|
95
|
+
- **Core framework:** [alf-core](https://github.com/instadeepai/alf/blob/main/core/README.md)
|
|
96
|
+
- **Tutorials:** [tutorials/](https://github.com/instadeepai/alf/tree/main/tutorials)
|
|
97
|
+
|
|
98
|
+
## What's included
|
|
99
|
+
|
|
100
|
+
`alf-tools` bundles example datasets (GFP, ProteinGym, FLIP, GuacaMol), surrogate and oracle
|
|
101
|
+
models (CNN, Gaussian Process, ESM-2, ESMFold, Chemprop, PyRosetta, plus an ensemble wrapper),
|
|
102
|
+
acquisition functions (Greedy, UCB, Expected Improvement, Thompson Sampling, CoreSet, and a
|
|
103
|
+
BoTorch wrapper), and search strategies. See the
|
|
104
|
+
[API reference](https://instadeepai.github.io/alf/api/alf_tools/index.html) for the full
|
|
105
|
+
catalogue and configuration options.
|
|
106
|
+
|
|
107
|
+
## Quick example
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from alf_core import (
|
|
111
|
+
BaseDatasetConfig,
|
|
112
|
+
DatasetSearch,
|
|
113
|
+
DesignTask,
|
|
114
|
+
Optimizer,
|
|
115
|
+
Oracle,
|
|
116
|
+
Surrogate,
|
|
117
|
+
TerminalStateLogger,
|
|
118
|
+
)
|
|
119
|
+
from alf_tools.datasets import GFP
|
|
120
|
+
from alf_tools.models import CNNModel
|
|
121
|
+
from alf_tools.optimizer.acquisition_functions import Greedy
|
|
122
|
+
|
|
123
|
+
# Configure and load the dataset
|
|
124
|
+
config = BaseDatasetConfig(
|
|
125
|
+
name="gfp",
|
|
126
|
+
modality="sequence",
|
|
127
|
+
seed=42,
|
|
128
|
+
train_ratio=0.1,
|
|
129
|
+
validation_frac=0.5,
|
|
130
|
+
test_ratio=0.2,
|
|
131
|
+
split_type="random",
|
|
132
|
+
problem_type="regression",
|
|
133
|
+
)
|
|
134
|
+
dataset = GFP(config)
|
|
135
|
+
surrogate = Surrogate(model=CNNModel())
|
|
136
|
+
optimizer = Optimizer(acquisition_fn=Greedy(), search_fn=DatasetSearch())
|
|
137
|
+
oracle = Oracle(scorer=dataset)
|
|
138
|
+
|
|
139
|
+
# Run active learning
|
|
140
|
+
task = DesignTask(num_acq_rounds=5, acq_batch_size=100)
|
|
141
|
+
state = task.setup(dataset=dataset, surrogate=surrogate)
|
|
142
|
+
task.run(
|
|
143
|
+
state=state,
|
|
144
|
+
state_loggers=[TerminalStateLogger()],
|
|
145
|
+
optimizer=optimizer,
|
|
146
|
+
oracle=oracle,
|
|
147
|
+
)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Normalisation
|
|
151
|
+
|
|
152
|
+
Models support input normalisation and output standardisation through their train configs
|
|
153
|
+
(`BaseTrainConfig`). `GPModel` enables both by default (`minmax` inputs, standardised outputs);
|
|
154
|
+
the other models default to neither. See the
|
|
155
|
+
[API reference](https://instadeepai.github.io/alf/api/alf_tools/index.html) for per-model
|
|
156
|
+
defaults and configuration.
|
|
157
|
+
|
|
158
|
+
## Creating custom components
|
|
159
|
+
|
|
160
|
+
All components subclass the `alf_core` base classes (`BaseDataset`, `BaseModel`,
|
|
161
|
+
`AcquisitionFunction`, `BaseSearch`). See the
|
|
162
|
+
[how-to guides](https://instadeepai.github.io/alf/how-to/index.html) for step-by-step instructions.
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
Apache License 2.0 — see [LICENSE](https://github.com/instadeepai/alf/blob/main/LICENSE).
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# alf-tools
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/alf-tools/)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
[](https://github.com/instadeepai/alf/blob/main/LICENSE)
|
|
6
|
+
[](https://github.com/instadeepai/alf/tree/main/tools)
|
|
7
|
+
[](https://instadeepai.github.io/alf/)
|
|
8
|
+
|
|
9
|
+
**Ready-to-use models, datasets, and acquisition functions for ALF (Active Learning Framework).**
|
|
10
|
+
|
|
11
|
+
`alf-tools` builds on [alf-core](https://github.com/instadeepai/alf/blob/main/core/README.md)
|
|
12
|
+
to give you everything needed to run active-learning experiments out of the box — example
|
|
13
|
+
datasets (GFP, ProteinGym, FLIP, GuacaMol), models (CNN, Gaussian Process, ESM-2, Chemprop),
|
|
14
|
+
acquisition functions, and search strategies. Use it for quick-start and prototyping; reach for
|
|
15
|
+
`alf-core` alone when you want the lightweight framework with no ML-framework dependencies.
|
|
16
|
+
|
|
17
|
+
<div align="center">
|
|
18
|
+
<img src="https://raw.githubusercontent.com/instadeepai/alf/main/docs/imgs/alf_main_figure.png" alt="ALF active-learning loop overview" width="70%">
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install alf-tools
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This installs PyTorch and the core dependencies. Some models need additional dependencies —
|
|
28
|
+
see [Optional Extras](#optional-extras) below.
|
|
29
|
+
|
|
30
|
+
### Optional Extras
|
|
31
|
+
|
|
32
|
+
Some models require additional dependencies. Append one or more extras to the package name.
|
|
33
|
+
Per-model extras (`esm2`, `esmfold`, `chemprop`, `guacamol`) install exactly one model's
|
|
34
|
+
dependencies; workflow umbrellas (`protein`, `molecule`) group the extras you are likely to use
|
|
35
|
+
together.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install "alf-tools[esm2]" # ESM-2 protein language model (ESM2Model)
|
|
39
|
+
pip install "alf-tools[esmfold]" # ESMFold structure-prediction oracle (ESMFoldModel)
|
|
40
|
+
pip install "alf-tools[chemprop]" # Chemprop small-molecule MPNN (ChempropModel)
|
|
41
|
+
pip install "alf-tools[guacamol]" # GuacaMol RDKit-based dataset/scoring
|
|
42
|
+
pip install "alf-tools[protein]" # umbrella: esm2 + esmfold
|
|
43
|
+
pip install "alf-tools[molecule]" # umbrella: chemprop + guacamol
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Documentation
|
|
47
|
+
|
|
48
|
+
- **Full documentation:** [instadeepai.github.io/alf](https://instadeepai.github.io/alf/)
|
|
49
|
+
- **API reference:** [alf-tools API](https://instadeepai.github.io/alf/api/alf_tools/index.html)
|
|
50
|
+
- **Installation guide:** [instadeepai.github.io/alf/installation.html](https://instadeepai.github.io/alf/installation.html)
|
|
51
|
+
- **Core framework:** [alf-core](https://github.com/instadeepai/alf/blob/main/core/README.md)
|
|
52
|
+
- **Tutorials:** [tutorials/](https://github.com/instadeepai/alf/tree/main/tutorials)
|
|
53
|
+
|
|
54
|
+
## What's included
|
|
55
|
+
|
|
56
|
+
`alf-tools` bundles example datasets (GFP, ProteinGym, FLIP, GuacaMol), surrogate and oracle
|
|
57
|
+
models (CNN, Gaussian Process, ESM-2, ESMFold, Chemprop, PyRosetta, plus an ensemble wrapper),
|
|
58
|
+
acquisition functions (Greedy, UCB, Expected Improvement, Thompson Sampling, CoreSet, and a
|
|
59
|
+
BoTorch wrapper), and search strategies. See the
|
|
60
|
+
[API reference](https://instadeepai.github.io/alf/api/alf_tools/index.html) for the full
|
|
61
|
+
catalogue and configuration options.
|
|
62
|
+
|
|
63
|
+
## Quick example
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from alf_core import (
|
|
67
|
+
BaseDatasetConfig,
|
|
68
|
+
DatasetSearch,
|
|
69
|
+
DesignTask,
|
|
70
|
+
Optimizer,
|
|
71
|
+
Oracle,
|
|
72
|
+
Surrogate,
|
|
73
|
+
TerminalStateLogger,
|
|
74
|
+
)
|
|
75
|
+
from alf_tools.datasets import GFP
|
|
76
|
+
from alf_tools.models import CNNModel
|
|
77
|
+
from alf_tools.optimizer.acquisition_functions import Greedy
|
|
78
|
+
|
|
79
|
+
# Configure and load the dataset
|
|
80
|
+
config = BaseDatasetConfig(
|
|
81
|
+
name="gfp",
|
|
82
|
+
modality="sequence",
|
|
83
|
+
seed=42,
|
|
84
|
+
train_ratio=0.1,
|
|
85
|
+
validation_frac=0.5,
|
|
86
|
+
test_ratio=0.2,
|
|
87
|
+
split_type="random",
|
|
88
|
+
problem_type="regression",
|
|
89
|
+
)
|
|
90
|
+
dataset = GFP(config)
|
|
91
|
+
surrogate = Surrogate(model=CNNModel())
|
|
92
|
+
optimizer = Optimizer(acquisition_fn=Greedy(), search_fn=DatasetSearch())
|
|
93
|
+
oracle = Oracle(scorer=dataset)
|
|
94
|
+
|
|
95
|
+
# Run active learning
|
|
96
|
+
task = DesignTask(num_acq_rounds=5, acq_batch_size=100)
|
|
97
|
+
state = task.setup(dataset=dataset, surrogate=surrogate)
|
|
98
|
+
task.run(
|
|
99
|
+
state=state,
|
|
100
|
+
state_loggers=[TerminalStateLogger()],
|
|
101
|
+
optimizer=optimizer,
|
|
102
|
+
oracle=oracle,
|
|
103
|
+
)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Normalisation
|
|
107
|
+
|
|
108
|
+
Models support input normalisation and output standardisation through their train configs
|
|
109
|
+
(`BaseTrainConfig`). `GPModel` enables both by default (`minmax` inputs, standardised outputs);
|
|
110
|
+
the other models default to neither. See the
|
|
111
|
+
[API reference](https://instadeepai.github.io/alf/api/alf_tools/index.html) for per-model
|
|
112
|
+
defaults and configuration.
|
|
113
|
+
|
|
114
|
+
## Creating custom components
|
|
115
|
+
|
|
116
|
+
All components subclass the `alf_core` base classes (`BaseDataset`, `BaseModel`,
|
|
117
|
+
`AcquisitionFunction`, `BaseSearch`). See the
|
|
118
|
+
[how-to guides](https://instadeepai.github.io/alf/how-to/index.html) for step-by-step instructions.
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
Apache License 2.0 — see [LICENSE](https://github.com/instadeepai/alf/blob/main/LICENSE).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Copyright 2026 InstaDeep Ltd. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import importlib.metadata
|
|
16
|
+
|
|
17
|
+
__version__ = importlib.metadata.version("alf-tools")
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Copyright 2026 InstaDeep Ltd. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from alf_tools.datasets.flip import FLIP, FLIPConfig
|
|
16
|
+
from alf_tools.datasets.gfp import GFP
|
|
17
|
+
from alf_tools.datasets.proteingym import ProteinGym
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from alf_tools.datasets.guacamol import (
|
|
21
|
+
GuacaMol,
|
|
22
|
+
GuacaMolConfig,
|
|
23
|
+
GuacaMolFileInfo,
|
|
24
|
+
download_guacamol,
|
|
25
|
+
get_task_scorer,
|
|
26
|
+
)
|
|
27
|
+
except ImportError:
|
|
28
|
+
pass
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# Copyright 2026 InstaDeep Ltd. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import copy
|
|
16
|
+
import io
|
|
17
|
+
import logging
|
|
18
|
+
import zipfile
|
|
19
|
+
from math import floor
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Literal, Self
|
|
22
|
+
|
|
23
|
+
import numpy as np
|
|
24
|
+
import pandas as pd
|
|
25
|
+
import requests
|
|
26
|
+
from alf_core import BaseDataset, BaseDatasetConfig, Candidate, LabelledCandidates, ProblemType
|
|
27
|
+
from pydantic import model_validator
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger("alf-tools")
|
|
30
|
+
|
|
31
|
+
DATAPATH = Path(__file__).parent / "data"
|
|
32
|
+
FLIP_GITHUB_BASE = "https://github.com/J-SNACKKB/FLIP/raw/main/splits"
|
|
33
|
+
|
|
34
|
+
FLIP_DATASETS = Literal[
|
|
35
|
+
"aav",
|
|
36
|
+
"gb1",
|
|
37
|
+
"meltome",
|
|
38
|
+
"scl",
|
|
39
|
+
"sav",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
# Active (green) splits per dataset — orange/red splits and FASTA-only datasets excluded
|
|
43
|
+
FLIP_SPLITS: dict[str, list[str]] = {
|
|
44
|
+
"aav": ["des_mut", "mut_des", "one_vs_many", "two_vs_many", "seven_vs_many", "low_vs_high"],
|
|
45
|
+
"gb1": ["one_vs_rest", "two_vs_rest", "three_vs_rest", "low_vs_high"],
|
|
46
|
+
"meltome": ["mixed", "human", "human_cell"],
|
|
47
|
+
"scl": ["mixed_soft", "mixed_hard", "human_soft", "human_hard", "balanced", "mixed_vs_human_2"],
|
|
48
|
+
"sav": ["mixed", "human", "only_savs"],
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# A few FLIP split names don't match their CSV file stem exactly
|
|
52
|
+
_SPLIT_FILE_MAP: dict[tuple[str, str], str] = {
|
|
53
|
+
("meltome", "mixed"): "mixed_split",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
_EXPECTED_COLUMNS = {"sequence", "target", "set", "validation"}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class FLIPConfig(BaseDatasetConfig):
|
|
60
|
+
"""Configuration for FLIP benchmark datasets.
|
|
61
|
+
|
|
62
|
+
`train_ratio` controls what fraction of the FLIP train sequences form the
|
|
63
|
+
initial labelled training set. `validation_frac` is applied to that portion
|
|
64
|
+
to carve out a validation set (same as `BaseDataset`). The remaining FLIP
|
|
65
|
+
train sequences become the candidate pool (capped at `max_candidate_pool`).
|
|
66
|
+
`test_ratio` controls what fraction of the FLIP test sequences form the
|
|
67
|
+
evaluation set. `split_type` defaults to `"random"` and need not be set.
|
|
68
|
+
|
|
69
|
+
Attributes:
|
|
70
|
+
flip_dataset: Name of the FLIP dataset (e.g. `"gb1"`, `"aav"`).
|
|
71
|
+
flip_split: Name of the split within the dataset (e.g. `"one_vs_rest"`).
|
|
72
|
+
Must be an active (green-status) split. See `FLIP_SPLITS` for valid options.
|
|
73
|
+
|
|
74
|
+
Example::
|
|
75
|
+
|
|
76
|
+
config = FLIPConfig(
|
|
77
|
+
name="gb1_experiment",
|
|
78
|
+
modality="sequence",
|
|
79
|
+
seed=42,
|
|
80
|
+
flip_dataset="gb1",
|
|
81
|
+
flip_split="one_vs_rest",
|
|
82
|
+
train_ratio=0.1,
|
|
83
|
+
validation_frac=0.1,
|
|
84
|
+
test_ratio=0.8,
|
|
85
|
+
max_candidate_pool=5000,
|
|
86
|
+
)
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
flip_dataset: FLIP_DATASETS
|
|
90
|
+
flip_split: str
|
|
91
|
+
problem_type: ProblemType = ProblemType.REGRESSION
|
|
92
|
+
|
|
93
|
+
@model_validator(mode="after")
|
|
94
|
+
def validate_config(self) -> Self:
|
|
95
|
+
"""Override base class validator.
|
|
96
|
+
|
|
97
|
+
train_ratio and test_ratio apply to separate FLIP pools (FLIP train and FLIP test),
|
|
98
|
+
so their sum is allowed to exceed 1. Also validates that flip_split is a valid
|
|
99
|
+
active split for the chosen flip_dataset.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
The validated configuration instance.
|
|
103
|
+
|
|
104
|
+
Raises:
|
|
105
|
+
ValueError: If `flip_split` is not a valid active split for `flip_dataset`.
|
|
106
|
+
"""
|
|
107
|
+
if self.flip_split not in FLIP_SPLITS.get(self.flip_dataset, []):
|
|
108
|
+
raise ValueError(
|
|
109
|
+
f"'{self.flip_split}' is not a valid active split for dataset "
|
|
110
|
+
f"'{self.flip_dataset}'. Valid splits: {FLIP_SPLITS[self.flip_dataset]}"
|
|
111
|
+
)
|
|
112
|
+
return self
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class FLIP(BaseDataset):
|
|
116
|
+
"""FLIP benchmark dataset class.
|
|
117
|
+
|
|
118
|
+
FLIP provides pre-defined train/test splits for protein fitness landscapes.
|
|
119
|
+
The CSV columns are:
|
|
120
|
+
- sequence: amino acid string (may contain special characters)
|
|
121
|
+
- target: float fitness score
|
|
122
|
+
- set: "train" or "test"
|
|
123
|
+
- validation: bool, FLIP-recommended early-stopping flag (stored as a
|
|
124
|
+
candidate feature but not used for splitting)
|
|
125
|
+
|
|
126
|
+
The splits map to alf as follows:
|
|
127
|
+
- train_ratio × FLIP train -> alf train
|
|
128
|
+
- validation_frac × (train_ratio × FLIP train) -> alf validation
|
|
129
|
+
- remaining FLIP train (up to max_candidate_pool) -> alf candidate_pool
|
|
130
|
+
- test_ratio × FLIP test -> alf test
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
config: FLIPConfig # narrows the inherited BaseDatasetConfig type
|
|
134
|
+
|
|
135
|
+
def __init__(self, config: FLIPConfig):
|
|
136
|
+
"""Initialise FLIP dataset."""
|
|
137
|
+
super().__init__(config)
|
|
138
|
+
self.setup()
|
|
139
|
+
|
|
140
|
+
def __repr__(self) -> str:
|
|
141
|
+
"""Return a string representation identifying dataset and split."""
|
|
142
|
+
return (
|
|
143
|
+
f"FLIP(name={self.config.name}, modality={self.modality}, "
|
|
144
|
+
f"seed={self.config.seed}, "
|
|
145
|
+
f"flip_dataset={self.config.flip_dataset}, "
|
|
146
|
+
f"flip_split={self.config.flip_split})"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def load_dataset(self) -> LabelledCandidates:
|
|
150
|
+
"""Load FLIP split from local cache or download from GitHub.
|
|
151
|
+
|
|
152
|
+
Downloads splits.zip for the configured dataset if not cached locally,
|
|
153
|
+
then extracts and parses the configured split CSV.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
LabelledCandidates with sequence data, fitness labels, and features
|
|
157
|
+
containing 'set' ("train"/"test") and 'validation' (bool).
|
|
158
|
+
|
|
159
|
+
Raises:
|
|
160
|
+
FileNotFoundError: If the split CSV is not found inside splits.zip.
|
|
161
|
+
requests.HTTPError: If the download from GitHub fails.
|
|
162
|
+
"""
|
|
163
|
+
df = self._load_split_dataframe()
|
|
164
|
+
|
|
165
|
+
candidates = []
|
|
166
|
+
labels = []
|
|
167
|
+
for _, row in df.iterrows():
|
|
168
|
+
candidates.append(
|
|
169
|
+
Candidate(
|
|
170
|
+
data=row["sequence"],
|
|
171
|
+
modality=self.modality,
|
|
172
|
+
features={
|
|
173
|
+
"set": row["set"],
|
|
174
|
+
"validation": bool(row["validation"]),
|
|
175
|
+
},
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
labels.append(row["target"])
|
|
179
|
+
|
|
180
|
+
return LabelledCandidates(
|
|
181
|
+
candidates=candidates,
|
|
182
|
+
labels=np.array(labels),
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def _split_dataset(self) -> dict[str, LabelledCandidates]:
|
|
186
|
+
"""Split the raw dataset into train, validation, candidate_pool, and test.
|
|
187
|
+
|
|
188
|
+
The FLIP train sequences are shuffled, then partitioned using train_ratio
|
|
189
|
+
and validation_frac. The remainder becomes the candidate pool (capped at
|
|
190
|
+
max_candidate_pool). The FLIP test sequences are sampled using test_ratio.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Dictionary with keys "train", "validation", "test", "candidate_pool".
|
|
194
|
+
|
|
195
|
+
Raises:
|
|
196
|
+
RuntimeError: If dataset has not been loaded yet.
|
|
197
|
+
"""
|
|
198
|
+
if self._raw_dataset is None:
|
|
199
|
+
raise RuntimeError(
|
|
200
|
+
"Dataset must be loaded before splitting; "
|
|
201
|
+
"_raw_dataset is None — call dataset.setup() (or load_dataset()) before splitting"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Separate FLIP's pre-defined train and test pools
|
|
205
|
+
flip_train = LabelledCandidates(candidates=[], labels=np.array([]))
|
|
206
|
+
flip_test = LabelledCandidates(candidates=[], labels=np.array([]))
|
|
207
|
+
for candidate, label in self._raw_dataset:
|
|
208
|
+
if candidate.features and candidate.features["set"] == "train":
|
|
209
|
+
flip_train.append([candidate], np.array([label]))
|
|
210
|
+
else:
|
|
211
|
+
flip_test.append([candidate], np.array([label]))
|
|
212
|
+
|
|
213
|
+
logger.debug("FLIP pools — train: %d, test: %d", len(flip_train), len(flip_test))
|
|
214
|
+
|
|
215
|
+
# Shuffle FLIP train for reproducible random splitting
|
|
216
|
+
flip_train = flip_train.shuffle(self.config.seed)
|
|
217
|
+
|
|
218
|
+
# Split FLIP train into train, validation, and candidate_pool
|
|
219
|
+
train_plus_val_size = floor(len(flip_train) * self.split_ratio["train"])
|
|
220
|
+
validation_size = floor(train_plus_val_size * self.split_ratio["validation_frac"])
|
|
221
|
+
train_size = train_plus_val_size - validation_size
|
|
222
|
+
|
|
223
|
+
train = LabelledCandidates(
|
|
224
|
+
candidates=flip_train.candidates[:train_size],
|
|
225
|
+
labels=flip_train.labels[:train_size],
|
|
226
|
+
)
|
|
227
|
+
validation = LabelledCandidates(
|
|
228
|
+
candidates=flip_train.candidates[train_size:train_plus_val_size],
|
|
229
|
+
labels=flip_train.labels[train_size:train_plus_val_size],
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
pool_candidates = flip_train.candidates[train_plus_val_size:]
|
|
233
|
+
pool_labels = flip_train.labels[train_plus_val_size:]
|
|
234
|
+
if self.config.max_candidate_pool is not None:
|
|
235
|
+
pool_candidates = pool_candidates[: self.config.max_candidate_pool]
|
|
236
|
+
pool_labels = pool_labels[: self.config.max_candidate_pool]
|
|
237
|
+
candidate_pool = LabelledCandidates(candidates=pool_candidates, labels=pool_labels)
|
|
238
|
+
|
|
239
|
+
# Sample FLIP test using test_ratio
|
|
240
|
+
test_size = floor(len(flip_test) * self.split_ratio["test"])
|
|
241
|
+
test = LabelledCandidates(
|
|
242
|
+
candidates=flip_test.candidates[:test_size],
|
|
243
|
+
labels=flip_test.labels[:test_size],
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
logger.debug(
|
|
247
|
+
"Split sizes — train: %d, validation: %d, candidate_pool: %d, test: %d",
|
|
248
|
+
len(train),
|
|
249
|
+
len(validation),
|
|
250
|
+
len(candidate_pool),
|
|
251
|
+
len(test),
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
self.init_candidate_pool = copy.deepcopy(candidate_pool)
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
"train": train,
|
|
258
|
+
"validation": validation,
|
|
259
|
+
"test": test,
|
|
260
|
+
"candidate_pool": candidate_pool,
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
def _load_split_dataframe(self) -> pd.DataFrame:
|
|
264
|
+
"""Load the split CSV as a DataFrame, downloading splits.zip if needed.
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
DataFrame with columns: sequence, target, set, validation.
|
|
268
|
+
|
|
269
|
+
Raises:
|
|
270
|
+
FileNotFoundError: If split CSV not found inside the zip archive.
|
|
271
|
+
ValueError: If the loaded CSV is missing expected columns.
|
|
272
|
+
requests.HTTPError: If the GitHub download fails.
|
|
273
|
+
"""
|
|
274
|
+
zip_path = DATAPATH / "FLIP" / self.config.flip_dataset / "splits.zip"
|
|
275
|
+
|
|
276
|
+
if not zip_path.exists():
|
|
277
|
+
zip_path.parent.mkdir(parents=True, exist_ok=True)
|
|
278
|
+
url = f"{FLIP_GITHUB_BASE}/{self.config.flip_dataset}/splits.zip"
|
|
279
|
+
logger.info(f"Downloading FLIP splits for '{self.config.flip_dataset}' from {url}")
|
|
280
|
+
response = requests.get(url, stream=True, timeout=300)
|
|
281
|
+
response.raise_for_status()
|
|
282
|
+
with open(zip_path, "wb") as f:
|
|
283
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
284
|
+
f.write(chunk)
|
|
285
|
+
logger.info("Download complete.")
|
|
286
|
+
else:
|
|
287
|
+
logger.debug("Using cached splits.zip at %s", zip_path)
|
|
288
|
+
|
|
289
|
+
file_stem = _SPLIT_FILE_MAP.get(
|
|
290
|
+
(self.config.flip_dataset, self.config.flip_split),
|
|
291
|
+
self.config.flip_split,
|
|
292
|
+
)
|
|
293
|
+
csv_name = f"{file_stem}.csv"
|
|
294
|
+
with zipfile.ZipFile(zip_path, "r") as zf:
|
|
295
|
+
available = zf.namelist()
|
|
296
|
+
match = next((n for n in available if n.endswith(csv_name)), None)
|
|
297
|
+
if match is None:
|
|
298
|
+
raise FileNotFoundError(
|
|
299
|
+
f"'{csv_name}' not found in splits.zip for dataset "
|
|
300
|
+
f"'{self.config.flip_dataset}'. Available files: {available}"
|
|
301
|
+
)
|
|
302
|
+
with zf.open(match) as csv_file:
|
|
303
|
+
df = pd.read_csv(io.BytesIO(csv_file.read()))
|
|
304
|
+
|
|
305
|
+
missing = _EXPECTED_COLUMNS - set(df.columns)
|
|
306
|
+
if missing:
|
|
307
|
+
raise ValueError(
|
|
308
|
+
f"FLIP CSV '{csv_name}' is missing expected columns: {missing}. "
|
|
309
|
+
f"Found: {set(df.columns)}"
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
return df
|