Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions mkdocs/docs/development/dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ The semantic release configuration is defined in `pyproject.toml`:
[tool.semantic_release]
version_toml = ["pyproject.toml:project.version"]
build_command = "uv build"
upload_to_release = true
upload_to_vcs_release = true

[tool.semantic_release.changelog]
mode = "update"
Expand All @@ -280,14 +280,11 @@ Releases are automatically triggered when commits are pushed to the `main` branc
While releases are automated, you can manually trigger a release:

```bash
# Dry run to see what would happen
uv run semantic-release version --no-push
# Print the next version without changing files
uv run semantic-release version --print

# Generate changelog only
uv run semantic-release changelog

# Print current version
uv run semantic-release version --print
```

### Best Practices
Expand Down
24 changes: 14 additions & 10 deletions mkdocs/docs/feature_generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ for k, v in Descriptors.descList:

#### Binary vs. Count Fingerprints

By default, fingerprints are generated as **binary** vectors — each bit is either `0` (substructure absent) or `1` (substructure present). You can switch to **count-based** fingerprints by setting `use_counts=True`, which records ***how many times*** each substructure occurs in the molecule. Count fingerprints can improve model performance when the frequency of substructures is informative.
By default, `FingerprintsGeneric` and `MorganFingerprints` generate **count-based** vectors, which record ***how many times*** each substructure occurs in a molecule. Count fingerprints can improve model performance when substructure frequency is informative. Set `use_counts=False` to generate a binary vector, where each bit is either `0` (substructure absent) or `1` (substructure present).

| Mode | Parameter | Output values | Use case |
|------|-----------|---------------|----------|
| Binary (default) | `use_counts=False` | 0 or 1 | General-purpose fingerprinting |
| Count | `use_counts=True` | 0, 1, 2, … | When substructure frequency matters |
| Count (default) | `use_counts=True` | 0, 1, 2, ... | When substructure frequency matters |
| Binary | `use_counts=False` | 0 or 1 | When only substructure presence matters |

The `use_counts` parameter is available on both `FingerprintsGeneric` and the `MorganFingerprints` convenience class:

Expand All @@ -46,17 +46,19 @@ from mother.feature_generation import FingerprintsGeneric, MorganFingerprints

molecule_objects = [Chem.MolFromSmiles(smi) for smi in ["CCO", "CCN", "c1ccccc1"]]

# Count-based Morgan fingerprints via the convenience class
morgan_counts = MorganFingerprints(radius=2, fpSize=1024, use_counts=True)
# Count-based Morgan fingerprints are the default
morgan_counts = MorganFingerprints(radius=2, fpSize=2048)
features = morgan_counts.fit_transform(molecule_objects)

# Count-based fingerprints via the generic class (works with any supported fp_type)
fp_counts = FingerprintsGeneric(
fp_type="AtomPairFP",
parameters={"fpSize": 2048},
use_counts=True,
)
features = fp_counts.fit_transform(molecule_objects)

# Opt in to binary Morgan fingerprints
morgan_binary = MorganFingerprints(radius=2, fpSize=2048, use_counts=False)
```

!!! note
Expand Down Expand Up @@ -86,10 +88,12 @@ from mother.feature_generation import ChemicalDescriptors, MorganFingerprints

molecule_objects = [Chem.MolFromSmiles(smi) for smi in ["CCO", "CCN", "c1ccccc1"]]

feature_generator = FeatureUnion([
("descriptors", ChemicalDescriptors(descriptor_list=["MolWt", "MolLogP"])),
("morgan_fp", MorganFingerprints(radius=2, fpSize=1024)),
])
feature_generator = FeatureUnion(
[
("descriptors", ChemicalDescriptors(descriptor_list=["MolWt", "MolLogP"])),
("morgan_fp", MorganFingerprints(radius=2, fpSize=2048)),
]
)
features = feature_generator.fit_transform(molecule_objects)
```

Expand Down
2 changes: 1 addition & 1 deletion src/mother/feature_generation/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ class FeatureGenerationConfig(BaseModel):
fingerprints: List[Dict[str, Any]] = Field(default=[], description="List of fingerprint generator settings")
maccs: bool = Field(default=False, description="Flag if maccs fingerprints should be generated")
chemical_descriptors: Optional[ChemicalDescriptorsParams] = Field(default=None)
use_counts: bool = Field(default=False, description="Whether to use count fingerprints")
use_counts: bool = Field(default=True, description="Whether to use count fingerprints")
Comment thread
thomasATbayer marked this conversation as resolved.

@field_validator("fingerprints", mode="before")
@classmethod
Expand Down
4 changes: 2 additions & 2 deletions src/mother/feature_generation/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class FingerprintsGeneric(BaseEstimator, TransformerMixin, _TransformOnlyValidMo
the type and configuration of the fingerprint is determined by the parameters
"""

def __init__(self, fp_type: str, parameters: dict, use_counts: bool = False) -> None:
def __init__(self, fp_type: str, parameters: dict, use_counts: bool = True) -> None:
self.fp_type: str = fp_type
self.parameters: dict = parameters
self.use_counts: bool = use_counts
Comment thread
thomasATbayer marked this conversation as resolved.
Expand Down Expand Up @@ -119,7 +119,7 @@ def handle_bad_conformer(self, compound, error) -> Chem.rdchem.Mol:

class MorganFingerprints(FingerprintsGeneric):
def __init__(
self, radius: int = 2, fpSize=1024, include_chirality: bool = False, use_counts: bool = False, **kwargs
self, radius: int = 2, fpSize: int = 2048, include_chirality: bool = False, use_counts: bool = True, **kwargs
) -> None:
Comment on lines 121 to 123
super().__init__(
Comment on lines 121 to 124
"MorganFP",
Expand Down
25 changes: 12 additions & 13 deletions test/unit/test_use_counts.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,13 @@ def test_count_and_binary_differ(self, mols_with_repeated_substructures):
"Count and binary fingerprints should produce different outputs"
)

def test_use_counts_default_is_false(self):
"""RED if default changed: use_counts should default to False."""
def test_use_counts_default_is_true(self):
"""use_counts should default to True."""
fg = FingerprintsGeneric(
fp_type="MorganFP",
parameters={"radius": 2, "fpSize": 2048},
)
assert fg.use_counts is False
assert fg.use_counts is True

def test_output_shape_consistent(self, mols_with_repeated_substructures):
"""Both modes should produce the same output shape."""
Expand Down Expand Up @@ -123,18 +123,17 @@ def test_output_shape_consistent(self, mols_with_repeated_substructures):
class TestUseCountsMorganFingerprints:
"""Tests for use_counts on the MorganFingerprints convenience class."""

def test_morgan_binary_default(self, mols_with_repeated_substructures):
"""MorganFingerprints with default use_counts=False should produce binary output."""
fg = MorganFingerprints(radius=2, fpSize=1024)
def test_morgan_count_default(self, mols_with_repeated_substructures):
"""MorganFingerprints with default use_counts=True should produce count output."""
fg = MorganFingerprints(radius=2, fpSize=2048)
fg.fit()
result = fg.transform(mols_with_repeated_substructures)

unique_values = np.unique(result[~np.isnan(result)])
assert set(unique_values).issubset({0, 1})
assert np.nanmax(result) > 1

def test_morgan_count_mode(self, mols_with_repeated_substructures):
"""RED if use_counts not wired through MorganFingerprints: should produce counts > 1."""
fg = MorganFingerprints(radius=2, fpSize=1024, use_counts=True)
fg = MorganFingerprints(radius=2, fpSize=2048, use_counts=True)
fg.fit()
result = fg.transform(mols_with_repeated_substructures)

Expand All @@ -143,7 +142,7 @@ def test_morgan_count_mode(self, mols_with_repeated_substructures):

def test_morgan_use_counts_preserved_after_set_params(self, mols_with_repeated_substructures):
"""use_counts should persist after set_params on other parameters."""
fg = MorganFingerprints(radius=2, fpSize=1024, use_counts=True)
fg = MorganFingerprints(radius=2, fpSize=2048, use_counts=True)
fg.set_params(radius=3)
fg.fit()
result = fg.transform(mols_with_repeated_substructures)
Expand All @@ -153,7 +152,7 @@ def test_morgan_use_counts_preserved_after_set_params(self, mols_with_repeated_s

def test_morgan_use_counts_toggled_via_set_params(self, mols_with_repeated_substructures):
"""RED if use_counts isn't a proper sklearn param: set_params should toggle it."""
fg = MorganFingerprints(radius=2, fpSize=1024, use_counts=False)
fg = MorganFingerprints(radius=2, fpSize=2048, use_counts=False)
fg.set_params(use_counts=True)
fg.fit()
result = fg.transform(mols_with_repeated_substructures)
Expand All @@ -165,15 +164,15 @@ def test_morgan_clone_preserves_use_counts(self):
"""sklearn.clone should preserve use_counts parameter."""
from sklearn.base import clone

fg = MorganFingerprints(radius=2, fpSize=1024, use_counts=True)
fg = MorganFingerprints(radius=2, fpSize=2048, use_counts=True)
fg_cloned = clone(fg)

assert fg_cloned.use_counts is True
assert fg_cloned.get_params()["use_counts"] is True

def test_morgan_get_params_includes_use_counts(self):
"""use_counts should be visible in get_params()."""
fg = MorganFingerprints(radius=2, fpSize=1024, use_counts=True)
fg = MorganFingerprints(radius=2, fpSize=2048, use_counts=True)
params = fg.get_params()
assert "use_counts" in params
assert params["use_counts"] is True
Loading
Loading