Skip to content

Commit e0f5e0b

Browse files
authored
Merge pull request #20 from aakankschit/feature/custom-evaluator
Add CustomEvaluator implementation
2 parents b7ceed1 + df8869b commit e0f5e0b

6 files changed

Lines changed: 828 additions & 5 deletions

File tree

src/TACTICS/thompson_sampling/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
ROCSEvaluatorConfig,
3131
FredEvaluatorConfig,
3232
MLClassifierEvaluatorConfig,
33+
CustomEvaluatorConfig,
3334
)
3435

3536
if TYPE_CHECKING:
@@ -59,6 +60,7 @@
5960
ROCSEvaluatorConfig,
6061
FredEvaluatorConfig,
6162
MLClassifierEvaluatorConfig,
63+
CustomEvaluatorConfig,
6264
]
6365

6466

src/TACTICS/thompson_sampling/core/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
FredEvaluator,
1414
FPEvaluator,
1515
MWEvaluator,
16-
MLClassifierEvaluator
16+
MLClassifierEvaluator,
17+
CustomEvaluator,
1718
)
1819

1920
__all__ = [
@@ -26,4 +27,5 @@
2627
'FPEvaluator',
2728
'MWEvaluator',
2829
'MLClassifierEvaluator',
30+
'CustomEvaluator',
2931
]

src/TACTICS/thompson_sampling/core/evaluator_config.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Pydantic configuration models for evaluators."""
22

33
from pydantic import BaseModel, Field
4-
from typing import Literal, Optional
4+
from typing import Literal, Optional, Callable, Any
55

66

77
class LookupEvaluatorConfig(BaseModel):
@@ -88,3 +88,17 @@ class MLClassifierEvaluatorConfig(BaseModel):
8888

8989
evaluator_type: Literal["ml_classifier"] = "ml_classifier"
9090
model_filename: str = Field(..., description="Path to trained model file (joblib pickle)")
91+
92+
class CustomEvaluatorConfig(BaseModel):
93+
"""
94+
Configuration for CustomEvaluator.
95+
96+
Uses a user-provided callable scoring function that accepts an RDKit Mol
97+
and returns a float.
98+
"""
99+
100+
evaluator_type: Literal["custom"] = "custom"
101+
scoring_function: Callable[[Any], float] = Field(
102+
...,
103+
description="Callable that accepts an RDKit Mol and returns a float score"
104+
)

src/TACTICS/thompson_sampling/core/evaluators.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,42 @@ def evaluate(self, mol):
234234
score = float(oechem.OEGetSDData(docked_mol, sd_tag))
235235
return score
236236

237+
class CustomEvaluator(Evaluator):
238+
"""An evaluator class that uses a user-provided custom scoring function
239+
"""
240+
241+
def __init__(self, scoring_function):
242+
"""
243+
:param scoring_function: callable that accepts an RDKit Mol and returns a float
244+
"""
245+
self.scoring_function = scoring_function
246+
self.num_evaluations = 0
247+
self.score_cache = {}
248+
249+
@property
250+
def counter(self):
251+
return self.num_evaluations
252+
253+
def evaluate(self, mol):
254+
"""Evaluate a molecule using the user-defined scoring function
255+
:param mol: Input RDKit molecule
256+
:return: Float score from the custom function, np.nan on failure
257+
"""
258+
self.num_evaluations += 1
259+
260+
# Look up to see if we already processed this molecule
261+
smi = Chem.MolToSmiles(mol)
262+
cached_score = self.score_cache.get(smi)
263+
if cached_score is not None:
264+
return cached_score
265+
266+
try:
267+
score = float(self.scoring_function(mol))
268+
except Exception:
269+
score = np.nan
270+
271+
self.score_cache[smi] = score
272+
return score
237273

238274
def generate_confs(mol, max_confs):
239275
"""Generate conformers with Omega

src/TACTICS/thompson_sampling/factories.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@
4141
MWEvaluatorConfig,
4242
ROCSEvaluatorConfig,
4343
FredEvaluatorConfig,
44-
MLClassifierEvaluatorConfig
44+
MLClassifierEvaluatorConfig,
45+
CustomEvaluatorConfig
4546
)
4647
from .core.evaluators import (
4748
LookupEvaluator,
@@ -51,7 +52,8 @@
5152
ROCSEvaluator,
5253
FredEvaluator,
5354
MLClassifierEvaluator,
54-
Evaluator
55+
Evaluator,
56+
CustomEvaluator
5557
)
5658

5759

@@ -79,7 +81,8 @@
7981
MWEvaluatorConfig,
8082
ROCSEvaluatorConfig,
8183
FredEvaluatorConfig,
82-
MLClassifierEvaluatorConfig
84+
MLClassifierEvaluatorConfig,
85+
CustomEvaluatorConfig
8386
]
8487

8588

@@ -265,5 +268,9 @@ def create_evaluator(config: EvaluatorConfig) -> Evaluator:
265268
input_dict = {"model_filename": config.model_filename}
266269
return MLClassifierEvaluator(input_dict)
267270

271+
elif isinstance(config, CustomEvaluatorConfig):
272+
scoring_function = config.scoring_function
273+
return CustomEvaluator(scoring_function)
274+
268275
else:
269276
raise ValueError(f"Unknown evaluator config type: {type(config)}")

0 commit comments

Comments
 (0)