-
Notifications
You must be signed in to change notification settings - Fork 33
Constitutional descriptor added #642
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: mordred
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| """Constitutional descriptors.""" | ||
|
|
||
| import numpy as np | ||
| from rdkit.Chem import Mol | ||
| from rdkit.Chem.rdchem import Atom | ||
|
|
||
| from skfp.fingerprints._new_mordred.utils.atomic_properties import ( | ||
| PROPERTY_FUNCS, | ||
| ) | ||
|
|
||
| """ | ||
| This code has been adapted from the BSD-licensed mordred-community library. | ||
| https://github.com/JacksonBurns/mordred-community | ||
|
|
||
| See skfp/fingerprints/data/mordred-community_bsd_license.txt for the license text. | ||
| """ | ||
|
|
||
| FEATURE_NAMES = [ | ||
| "SZ", | ||
| "Sm", | ||
| "Sv", | ||
| "Sse", | ||
| "Spe", | ||
| "Sare", | ||
| "Sp", | ||
| "Si", | ||
| "MZ", | ||
| "Mm", | ||
| "Mv", | ||
| "Mse", | ||
| "Mpe", | ||
| "Mare", | ||
| "Mp", | ||
| "Mi", | ||
| ] | ||
|
|
||
| _NUM_ELEMENTS = 119 | ||
|
|
||
|
|
||
| def _get_normalized_property_table() -> np.ndarray: | ||
| """Precompute each atomic property divided by its value for carbon.""" | ||
| atoms = tuple(Atom(atomic_num) for atomic_num in range(_NUM_ELEMENTS)) | ||
| property_values = np.asarray( | ||
| [ | ||
| [property_func(atom) for atom in atoms] | ||
| for property_func in PROPERTY_FUNCS.values() | ||
| ], | ||
| dtype=np.float64, | ||
| ) | ||
| carbon_values = property_values[:, [6]] | ||
| return property_values / carbon_values | ||
|
|
||
|
|
||
| _CARBON_NORMALIZED_PROPERTIES = _get_normalized_property_table() | ||
|
|
||
|
|
||
| def calc(mol_hydrogens: Mol) -> tuple[np.ndarray, list[str]]: | ||
| """ | ||
| Compute the Mordred constitutional descriptors. | ||
|
|
||
| For each atomic property, the property values of all atoms, including explicit | ||
| hydrogens, are normalized by the corresponding value for carbon. The `S*` | ||
| descriptors are their sums and the `M*` descriptors are their means. | ||
| """ | ||
| atomic_numbers = np.fromiter( | ||
| (atom.GetAtomicNum() for atom in mol_hydrogens.GetAtoms()), | ||
| dtype=np.intp, | ||
| count=mol_hydrogens.GetNumAtoms(), | ||
| ) | ||
| sums = _CARBON_NORMALIZED_PROPERTIES[:, atomic_numbers].sum(axis=1) | ||
| means = sums / atomic_numbers.size if atomic_numbers.size else np.full(8, np.nan) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can assume there is at least 1 atom |
||
| return np.concatenate((sums, means)).astype(np.float32), FEATURE_NAMES | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import numpy as np | ||
| import pytest | ||
| from mordred import Calculator, Constitutional | ||
| from numpy.testing import assert_allclose | ||
| from rdkit import Chem | ||
|
|
||
| from skfp.fingerprints._new_mordred import calculator | ||
| from skfp.fingerprints._new_mordred.descriptors import constitutional | ||
| from skfp.fingerprints._new_mordred.utils.mol_preprocess import preprocess_mol | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| "smiles", | ||
| ["CCO", "c1ccccc1", "C(F)(Cl)(Br)I", "[Na+]", "[H][H]", ""], | ||
| ) | ||
| def test_constitutional_matches_mordred(smiles): | ||
| mol = Chem.MolFromSmiles(smiles) | ||
| mol_hydrogens = preprocess_mol(mol, explicit_hydrogens=True) | ||
|
|
||
| values, feature_names = constitutional.calc(mol_hydrogens) | ||
| mordred_calc = Calculator(Constitutional) | ||
| expected = np.asarray(list(mordred_calc(mol)), dtype=np.float32) | ||
|
|
||
| assert feature_names == [str(desc) for desc in mordred_calc.descriptors] | ||
| assert_allclose(values, expected, rtol=1e-6, equal_nan=True) | ||
|
|
||
|
|
||
| def test_constitutional_is_connected_to_calculator(): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would be a pretty expensive test. Let's remove this for now, and later consider how to wire together individual tests and calculator |
||
| mol = Chem.MolFromSmiles("CCO") | ||
| result = calculator.compute(mol, use_3D=False) | ||
| names = calculator.get_feature_names(use_3d=False) | ||
| expected, feature_names = constitutional.calc( | ||
| preprocess_mol(mol, explicit_hydrogens=True) | ||
| ) | ||
|
|
||
| indices = [np.flatnonzero(names == name)[0] for name in feature_names] | ||
| assert_allclose(result[indices], expected) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why
[6]instead of just6?