Skip to content

Commit 4ab7bcd

Browse files
committed
[ENH] Add minimal AptaMCTS pipeline (MCTS-based aptamer recommendation)
1 parent 13fd5cd commit 4ab7bcd

8 files changed

Lines changed: 1258 additions & 1 deletion

File tree

pyaptamer/aptamcts/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""AptaMCTS pipeline for aptamer recommendation."""
2+
3+
from pyaptamer.aptamcts._pipeline import AptaMCTSPipeline
4+
5+
__all__ = ["AptaMCTSPipeline"]

pyaptamer/aptamcts/_pipeline.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
__all__ = ["AptaMCTSPipeline"]
2+
3+
import numpy as np
4+
5+
from pyaptamer.experiments import AptamerEvalAptaMCTS
6+
from pyaptamer.mcts import MCTS
7+
from pyaptamer.utils._aptamcts_utils import pairs_to_features
8+
9+
10+
class AptaMCTSPipeline:
11+
"""AptaMCTS pipeline for aptamer recommendation.
12+
13+
This pipeline wraps a pre-trained model exposing ``predict_proba`` and combines
14+
it with Monte Carlo Tree Search (MCTS) for candidate recommendation.
15+
16+
Parameters
17+
----------
18+
model : object
19+
Pre-trained model exposing a ``predict_proba`` method.
20+
depth : int, optional, default=20
21+
Search depth passed to MCTS.
22+
n_iterations : int, optional, default=1000
23+
Number of iterations passed to MCTS.
24+
"""
25+
26+
def __init__(self, model, depth=20, n_iterations=1000):
27+
self.model = model
28+
self.depth = depth
29+
self.n_iterations = n_iterations
30+
31+
def _init_aptamer_experiment(self, target: str) -> AptamerEvalAptaMCTS:
32+
"""Initialize the aptamer recommendation experiment."""
33+
return AptamerEvalAptaMCTS(target=target, pipeline=self)
34+
35+
def predict(self, aptamer: str, target: str) -> np.float64:
36+
"""Predict interaction score for an aptamer-target pair.
37+
38+
Parameters
39+
----------
40+
aptamer : str
41+
Aptamer candidate sequence.
42+
target : str
43+
Target sequence.
44+
45+
Returns
46+
-------
47+
np.float64
48+
Positive-class interaction score.
49+
"""
50+
if not hasattr(self.model, "predict_proba"):
51+
raise AttributeError("`model` must implement `predict_proba`.")
52+
53+
features = pairs_to_features([(aptamer, target)])
54+
score = self.model.predict_proba(features)
55+
56+
if score.ndim != 2 or score.shape[1] < 2:
57+
raise ValueError(
58+
"`predict_proba` must return an array with shape "
59+
"(n_samples, n_classes>=2)."
60+
)
61+
62+
return np.float64(score[:, 1].item())
63+
64+
def recommend(self, target: str, n_candidates=10):
65+
"""Recommend aptamer candidates for a target using MCTS.
66+
67+
Parameters
68+
----------
69+
target : str
70+
Target sequence.
71+
n_candidates : int, optional, default=10
72+
Number of unique candidates to return.
73+
74+
Returns
75+
-------
76+
set[tuple[str, str, float]]
77+
Set of ``(candidate, sequence, score)`` tuples.
78+
"""
79+
experiment = self._init_aptamer_experiment(target=target)
80+
mcts = MCTS(
81+
experiment=experiment,
82+
depth=self.depth,
83+
n_iterations=self.n_iterations,
84+
)
85+
86+
candidates = {}
87+
while len(candidates) < n_candidates:
88+
result = mcts.run(verbose=False)
89+
candidate = result["candidate"]
90+
sequence = result["sequence"]
91+
score = result["score"]
92+
93+
if candidate not in candidates:
94+
if hasattr(score, "item"):
95+
score = score.item()
96+
candidates[candidate] = (candidate, sequence, float(score))
97+
98+
return set(candidates.values())

0 commit comments

Comments
 (0)