Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ee9a8ea
ruff fmt + check
mikemhenry Jan 13, 2026
74cdcb3
setup ruff to use pyupgrade
mikemhenry Jan 13, 2026
2c1ee89
turn on pyupgrade
mikemhenry Jan 13, 2026
1208870
pyupgrade --py311-plus
mikemhenry Jan 13, 2026
f0744eb
Format pyproject.toml
mikemhenry Jan 13, 2026
8b45cf1
more ruff fixes
mikemhenry Jan 13, 2026
dfae282
imoport fixes
mikemhenry Jan 13, 2026
34e35f6
more linting rules
mikemhenry Jan 13, 2026
8e497ce
furby the codebase
mikemhenry Jan 13, 2026
1eff717
fix import in notebooks
mikemhenry Jan 14, 2026
b8dfbc9
skip unused import on bit of code it can't see is actually used
mikemhenry Jan 14, 2026
0acd60b
remove commented out code
mikemhenry Jan 14, 2026
2993bb9
add YTT
mikemhenry Jan 14, 2026
8733e49
fix syntax error in pyproject.toml
mikemhenry Jan 14, 2026
d807ef9
fix formatting and explain what is going on with the linters
mikemhenry Jan 14, 2026
8d58626
add missing return types
mikemhenry Jan 14, 2026
dadbc8d
add missing return types
mikemhenry Jan 14, 2026
580bb5b
format pyproject.toml and add security linter
mikemhenry Jan 14, 2026
93fa908
added noqa, still need to fix https://github.com/OpenFreeEnergy/karto…
mikemhenry Jan 14, 2026
b69778a
make explcit where our src is
mikemhenry Jan 15, 2026
6a60c0f
force imports to be on one line (need to fix group)
mikemhenry Jan 16, 2026
461e5ca
this is closer now to what we want
mikemhenry Jan 17, 2026
31d0dd4
ruff fmt
mikemhenry Jan 17, 2026
f39c2ef
format imports
mikemhenry Jan 17, 2026
25bae5f
testing isort
mikemhenry Jan 17, 2026
ddd5e19
atom_mapping_scorer is now mapping_metrics
mikemhenry Feb 6, 2026
9e9f7e1
pin openfe_benchmarks to hash since the package has changed a TON, se…
mikemhenry Feb 6, 2026
8374b04
bump, is passing locally
mikemhenry Feb 9, 2026
13f0c7d
need to catch ImportError as well
mikemhenry Feb 9, 2026
6b4927e
each import on a new line tokeep diffs small and clean
mikemhenry Feb 9, 2026
4270415
reformat imports
mikemhenry Feb 10, 2026
86104ec
install openfe_benchmarks directly in env, installing from notebook s…
mikemhenry Feb 10, 2026
d056379
need to install openfe for testing
mikemhenry Feb 10, 2026
1c94027
bump, is passing locally
mikemhenry Feb 10, 2026
5831bdf
make sure we install a newer version of openfe than 0.1
mikemhenry Feb 10, 2026
c0150ed
remove old cruft
mikemhenry Feb 10, 2026
ae2e68a
pyproject-fmt
mikemhenry Feb 10, 2026
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Optionally those set of coordinates can be aligned onto each other, checkout the
of Kartograf that offer a shape alignment implementation and a MCS-skeleton alignment.
The `atom_mapper` can be used to generate the 3D geometry focused atom mapping, the algorithm is described in the related publication of Kartograf (see reference).
Additionally, rule based filter functions can be provided to demap atoms, that do not fulfill the desired criteria, see `filters`.
Several mapping scoring metrics are provided, that evaluate geometric properties of your mapping, from `atom_mapping_scorer`, which might be useful for checking quality of your mappings.
Several mapping scoring metrics are provided, that evaluate geometric properties of your mapping, from `mapping_metrics`, which might be useful for checking quality of your mappings.
Finally, there is a visualization function `display_mappings_3d` that can be used to check out the mappings with a jupyter notebook widget.

Checkout our article on Kartograf in the Journal of Chemical Theory and Computation: [*Kartograf: A Geometrically Accurate Atom Mapper for Hybrid-Topology Relative Free Energy Calculations* - Benjamin Ries*, Irfan Alibay, David W. H. Swenson, Hannah M. Baumann, Michael M. Henry, James R. B. Eastwood, and Richard J. Gowers](https://doi.org/10.1021/acs.jctc.3c01206).
Expand All @@ -41,7 +41,7 @@ Try our interactive demo: [![Open In Colab](https://colab.research.google.com/as
```python3
from rdkit import Chem
from kartograf.atom_aligner import align_mol_shape
from kartograf.atom_mapping_scorer import MappingRMSDScorer
from kartograf.mapping_metrics import MappingRMSDScorer
from kartograf import KartografAtomMapper, SmallMoleculeComponent

# Preprocessing from Smiles - Here you can add your Input!
Expand Down
2 changes: 1 addition & 1 deletion docs/api/kartograf.scorers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
kartograf.atom\_mapping\_scorer
=================================

.. automodule:: kartograf.atom_mapping_scorer
.. automodule:: kartograf.mapping_metrics
:members:
:undoc-members:
:show-inheritance:
30 changes: 15 additions & 15 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@
#
import os
import sys
sys.path.insert(0, os.path.abspath('../src'))

sys.path.insert(0, os.path.abspath("../src"))


# -- Project information -----------------------------------------------------

project = 'kartograf'
copyright = '2022, The OpenFE Development Team'
author = 'The OpenFE Development Team'
project = "kartograf"
copyright = "2022, The OpenFE Development Team"
author = "The OpenFE Development Team"


# -- General configuration ---------------------------------------------------
Expand All @@ -28,20 +29,20 @@
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.napoleon',
"sphinx.ext.autodoc",
"sphinx.ext.napoleon",
]

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
templates_path = ["_templates"]

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]


#autodoc_mock_imports = ['lomap', 'networkx', 'openff', 'openff.toolkit', 'rdkit', 'pytest',
# autodoc_mock_imports = ['lomap', 'networkx', 'openff', 'openff.toolkit', 'rdkit', 'pytest',
# 'typing_extensions',
# 'click', 'plugcli']

Expand All @@ -50,10 +51,10 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.

html_static_path = ['_static']
html_theme = 'ofe_sphinx_theme'
autoclass_content = 'both'
html_favicon = '_static/img/logo/Kartograf_logo_boxed_dark.ico'
html_static_path = ["_static"]
html_theme = "ofe_sphinx_theme"
autoclass_content = "both"
html_favicon = "_static/img/logo/Kartograf_logo_boxed_dark.ico"
html_logo = "_static/img/logo/Kartograf_logo_light_transp.png"
html_theme_options = {
"logo": {
Expand All @@ -69,12 +70,11 @@
],
"accent_color": "FeelingSick",
}
#html_logo = "_static/Squaredcircle.svg"


# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_css_files = [
'css/custom.css',
"css/custom.css",
]
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ mapping, the algorithm is described in the related publication of Kartograf (see
Additionally, rule-based filter functions can be provided to demap atoms,
that do not fulfill the desired criteria, see ``filters``.
Several mapping scoring metrics are provided, that evaluate geometric
properties of your mapping, from ``atom_mapping_scorer``, which might be
properties of your mapping, from ``mapping_metrics``, which might be
useful for checking the quality of your mappings.
Finally, there is a visualization function ``display_mappings_3d`` that can be
used to check out the mappings with a Jupyter Notebook widget.
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorial/mapping_tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ going to use the :class:`.MappingRMSDScorer`, which gives insight into how far t
need to travel from one state to the other, if they are mapped onto each
other::

from kartograf.atom_mapping_scorer import MappingRMSDScorer
from kartograf.mapping_metrics import MappingRMSDScorer

# Score Mapping
rmsd_scorer = MappingRMSDScorer()
Expand Down
3 changes: 2 additions & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ dependencies:
# vis
- ipywidgets
- py3Dmol
# deps for testinging
- openfe >=1.8.1
# docs
- pip:
- git+https://github.com/OpenFreeEnergy/ofe-sphinx-theme@a45f3edd5bc3e973c1a01b577c71efa1b62a65d6

94 changes: 53 additions & 41 deletions examples/kartograf_example.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,16 @@
"# NBVAL_SKIP\n",
"# Only run this cell if on google colab\n",
"import os\n",
"\n",
"if \"COLAB_RELEASE_TAG\" in os.environ:\n",
" !pip install -U https://github.com/conda-incubator/condacolab/archive/cuda-version-12.tar.gz\n",
" import condacolab\n",
"\n",
" condacolab.install_mambaforge()\n",
" !wget -q https://raw.githubusercontent.com/OpenFreeEnergy/kartograf/main/examples/environment.yaml\n",
" !mamba env update -q --name=base --file=environment.yaml\n",
" from google.colab import output\n",
"\n",
" output.enable_custom_widget_manager()"
]
},
Expand All @@ -51,23 +54,24 @@
},
"outputs": [],
"source": [
"import sys\n",
"\n",
"from rdkit import Chem\n",
"\n",
"try:\n",
" from openfe_benchmarks import benzenes\n",
"except ModuleNotFoundError:\n",
" !{sys.executable} -m pip install --no-deps git+https://github.com/OpenFreeEnergy/openfe-benchmarks.git\n",
"except (ModuleNotFoundError, ImportError):\n",
" import sys # noqa: F401\n",
"\n",
" !{sys.executable} -m pip install --no-deps git+https://github.com/OpenFreeEnergy/openfe-benchmarks.git@d5a027e4e3cb53e47d4b230e8ddffda274b70aff\n",
" from openfe_benchmarks import benzenes\n",
" \n",
"\n",
"\n",
"components = benzenes.get_system().ligand_components\n",
"\n",
"# Exclude cycle breakers! as not feasible for Hybrid topology approaches.\n",
"not_lig = [\"lig_4\", \"lig_7\", \"lig_2\", \"lig_3\"]\n",
"components = [c for c in components if(c.name not in not_lig)]\n",
"components = [c for c in components if (c.name not in not_lig)]\n",
"\n",
"Chem.Draw.MolsToGridImage([c.to_rdkit() for c in components])\n"
"Chem.Draw.MolsToGridImage([c.to_rdkit() for c in components])"
]
},
{
Expand Down Expand Up @@ -104,11 +108,11 @@
"\n",
"atomMapper = KartografAtomMapper()\n",
"\n",
"#Generate Mappings\n",
"# Generate Mappings\n",
"mappings = []\n",
"cA=components[-5] # central ligand from Ries et al. 2022\n",
"cA = components[-5] # central ligand from Ries et al. 2022\n",
"for cB in components:\n",
" if(cA != cB):\n",
" if cA != cB:\n",
" mapping = next(atomMapper.suggest_mappings(cA, cB))\n",
" mappings.append(mapping)"
]
Expand All @@ -129,6 +133,7 @@
"outputs": [],
"source": [
"from kartograf.utils.mapping_visualization_widget import display_mappings_3d\n",
"\n",
"display_mappings_3d(mappings)"
]
},
Expand Down Expand Up @@ -162,28 +167,29 @@
},
"outputs": [],
"source": [
"from kartograf.atom_mapping_scorer import (\n",
" MappingVolumeRatioScorer, \n",
"from kartograf.mapping_metrics import (\n",
" MappingRMSDScorer,\n",
" MappingShapeOverlapScorer, \n",
" MappingShapeMismatchScorer\n",
" )\n",
" MappingShapeMismatchScorer,\n",
" MappingShapeOverlapScorer,\n",
" MappingVolumeRatioScorer,\n",
")\n",
"\n",
"scorer_dict ={\n",
"scorer_dict = {\n",
" \"volume_score\": MappingVolumeRatioScorer(),\n",
" \"rmsd_score\": MappingRMSDScorer(),\n",
" \"overlap_score\": MappingShapeOverlapScorer(),\n",
" \"mismatch_score\": MappingShapeMismatchScorer()\n",
" \"mismatch_score\": MappingShapeMismatchScorer(),\n",
"}\n",
"\n",
"def apply_scorers(mapping):\n",
"\n",
"def apply_scorers(mapping) -> None:\n",
" for score_name, scorer in scorer_dict.items():\n",
" setattr(mapping, score_name, scorer(mapping))\n",
"\n",
"#score mappings:\n",
"\n",
"# score mappings:\n",
"for mapping in mappings:\n",
" apply_scorers(mapping)\n",
"\n"
" apply_scorers(mapping)"
]
},
{
Expand All @@ -205,7 +211,7 @@
"\n",
"score_names = sorted(scorer_dict)\n",
"plt.boxplot([[getattr(m, score_name) for m in mappings] for score_name in score_names])\n",
"plt.xticks(range(1, len(score_names)+1), score_names, rotation=45)"
"plt.xticks(range(1, len(score_names) + 1), score_names, rotation=45)"
]
},
{
Expand All @@ -223,45 +229,51 @@
},
"outputs": [],
"source": [
"from scipy.stats import spearmanr\n",
"from matplotlib import pyplot as plt\n",
"from scipy.stats import spearmanr\n",
"\n",
"score_names = sorted(scorer_dict)\n",
"fig, axes = plt.subplots(nrows=len(score_names), ncols=len(score_names), figsize=[16,9])\n",
"fig, axes = plt.subplots(nrows=len(score_names), ncols=len(score_names), figsize=[16, 9])\n",
"\n",
"i=0\n",
"i = 0\n",
"for score_nameA in score_names:\n",
" j=0\n",
" axes[i,j].set_ylabel(score_nameA)\n",
" j = 0\n",
" axes[i, j].set_ylabel(score_nameA)\n",
"\n",
" for score_nameB in score_names:\n",
" ax = axes[i,j]\n",
" if(i == len(score_names)-1):\n",
" ax = axes[i, j]\n",
" if i == len(score_names) - 1:\n",
" ax.set_xlabel(score_nameB)\n",
" else:\n",
" ax.set_xticklabels([])\n",
" if(j>0):\n",
" if j > 0:\n",
" ax.set_yticklabels([])\n",
"\n",
"\n",
" x = [getattr(m, score_nameA) for m in mappings]\n",
" y = [getattr(m, score_nameB) for m in mappings]\n",
" r, _ = spearmanr(x,y)\n",
" ax.scatter(x,y)\n",
" ax.text(0.1,0.85, \"$r_{spearman}:~$\"+str(round(r,2)))\n",
" ax.set_xlim([0,1])\n",
" ax.set_ylim([0,1])\n",
" r, _ = spearmanr(x, y)\n",
" ax.scatter(x, y)\n",
" ax.text(0.1, 0.85, \"$r_{spearman}:~$\" + str(round(r, 2)))\n",
" ax.set_xlim([0, 1])\n",
" ax.set_ylim([0, 1])\n",
"\n",
" j+=1\n",
" i+=1\n",
" j += 1\n",
" i += 1\n",
"fig.tight_layout()\n",
"fig.subplots_adjust(wspace=0, hspace=0)\n"
"fig.subplots_adjust(wspace=0, hspace=0)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "asapdiscovery",
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
Expand All @@ -275,7 +287,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.6"
"version": "3.13.11"
}
},
"nbformat": 4,
Expand Down
50 changes: 35 additions & 15 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,26 +41,46 @@ kartograf = [ "tests/data/*.pdb", "tests/data/*.sdf" ]
fallback_version = "0.0.0"

[tool.ruff]
target-version = "py311"
line-length = 120

# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
src = [ "src" ]
# I've added all the linters that ruff supports as of 0.14.11
# If it doesn't exist in the package (for example async) it is not enabled
# I wanted to include it so that if there is a PR that adds async, we can
# easily turn on the linter
# For linters that exist and we want to use, but don't want to fix now, I've added them and then
# excluded the rule(s) that we don't want to fix now, but would be good future TODO
# TODO add more linters!
lint.select = [
# "F", # Pyflakes
"I", # isort
"W", # pycodestyle warnings
# "E", # pycodestyle errors
# "C901" # mccabe complexity TODO: add this back in
# "UP", # TODO: add this in
# "AIR", # airflow https://pypi.org/project/apache-airflow/
# "FAST", # FastAPI https://pypi.org/project/fastapi/
"ANN", # type annotations https://pypi.org/project/flake8-annotations/
"E", # pycodestyle errors
"ERA", # find commented-out code https://pypi.org/project/eradicate/
"F", # Pyflakes
"FURB", # A tool for refurbishing and modernizing Python codebases https://github.com/dosisod/refurb
"I", # isort
# "C901", # mccabe complexity TODO: add this back in
"S", # security related lint https://pypi.org/project/flake8-bandit/
"UP", # pyupgrade
"W", # pycodestyle warnings
"YTT", # detect issues using sys to check python version https://pypi.org/project/flake8-2020/
]
Comment on lines +57 to 68
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

excited about many of these - I'll add some to konnektor and gufe!


lint.ignore = [
"E402", # module-level import not at top (conflicts w/ isort)
"E722", # bare excepts (TODO: we should fix these in a follow-up PR)
"E731", # lambda expressions (TODO: we should fix these)
"F401", # unused imports (TODO: we should fix these)
"UP03", # pyupgrade linting (TODO: we should fix these)
"ANN001", # TODO Missing type annotation for function argument {name}
"ANN002", # TODO Missing type annotation for *{name}
"ANN003", # TODO Missing type annotation for **{name}
"ANN201", # TODO Missing return type annotation for public function {name}
"ANN202", # TODO Missing return type annotation for private function {name}
"ANN206", # TODO Missing return type annotation for classmethod {name}
"E501", # line too long, if the formatter can't fix it then it can be okay
]

# ignore "unused import" error for all __init__.py files
lint.per-file-ignores."__init__.py" = [ "E401" ]

lint.isort.known-first-party = [ "gufe" ]
# ignore securty issues in tests
lint.per-file-ignores."src/kartograf/tests/*" = [ "S" ]
lint.isort.combine-as-imports = true
lint.isort.force-wrap-aliases = true
lint.isort.known-first-party = [ "kartograf" ]
Loading