From 6528f398dd446d3e16f1139d3951b60bcaffcb63 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 1 Apr 2026 05:25:49 +0000 Subject: [PATCH 1/2] chore: pre-commit autoupdate --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b9246b5..3b49c31 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ ci: repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: 'v4.5.0' + rev: 'v6.0.0' hooks: - id: end-of-file-fixer - id: trailing-whitespace @@ -18,7 +18,7 @@ repos: - id: requirements-txt-fixer - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.4.10 + rev: v0.15.8 hooks: # Run the linter. - id: ruff From 78feffe6ff070efd841eaf2bd665d96a15d50bc5 Mon Sep 17 00:00:00 2001 From: Domagoj Fijan Date: Wed, 8 Apr 2026 19:54:03 -0400 Subject: [PATCH 2/2] appease ruff --- .pre-commit-config.yaml | 2 +- ConservedWaterSearch/hydrogen_orientation.py | 79 ++++++++++---------- ConservedWaterSearch/utils.py | 74 +++++++----------- ConservedWaterSearch/water_clustering.py | 16 ++-- tests/conftest.py | 10 +-- tests/test_hydrogen_orientation.py | 13 ++-- 6 files changed, 90 insertions(+), 104 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3b49c31..80cf569 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: rev: v0.15.8 hooks: # Run the linter. - - id: ruff + - id: ruff-check types_or: [ python, pyi, jupyter ] args: [ --fix ] # Run the formatter. diff --git a/ConservedWaterSearch/hydrogen_orientation.py b/ConservedWaterSearch/hydrogen_orientation.py index 0fad4e4..bbb8e40 100644 --- a/ConservedWaterSearch/hydrogen_orientation.py +++ b/ConservedWaterSearch/hydrogen_orientation.py @@ -2,6 +2,11 @@ from typing import TYPE_CHECKING +try: + import matplotlib.pyplot as plt +except ImportError: + plt = None + if TYPE_CHECKING: try: from matplotlib.axes import Axes @@ -360,7 +365,7 @@ def find_fully_conserved_orientations( sk = ( f"Conserved - kmeans analysis:\n" f"angle - mean: {ang:.2f}(calced)<{kmeans_ang_cutoff:.2f};" - f" inertia per H: {kmeans.inertia_/len(orientations):.2f}" + f" inertia per H: {kmeans.inertia_ / len(orientations):.2f}" f"(calced)<{kmeans_inertia_cutoff:.2f}\n" ) if debugH == 2 or (debugH == 1 and len(fully_conserved) > 0): @@ -372,11 +377,11 @@ def find_fully_conserved_orientations( f" buffer:{pct_size_buffer:.2f}\n" f"cluster sizes: {counts}; clust labels:{values};biggest: {biggest}" f"; secondbiggest: {secondbiggest}\n" - f"clusters size: {neioc*pct_size_buffer:.2f}<" + f"clusters size: {neioc * pct_size_buffer:.2f}<" f"{len(orientations[labels == biggest])}," f"{len(orientations[labels == secondbiggest])}" - f"(calced)<{neioc*(2-pct_size_buffer):.2f}\n" - f"angle mean: {np.abs(np.mean(angs21)-np.mean(angs12)):.2f}(calced)" + f"(calced)<{neioc * (2 - pct_size_buffer):.2f}\n" + f"angle mean: {np.abs(np.mean(angs21) - np.mean(angs12)):.2f}(calced)" f"<{angdiff_cutoff:.2f}); 12 std : {np.std(angs12):.2f};21 std: " f"{np.std(angs21):.2f} (both <{angstd_cutoff:.2f})\n" ) @@ -391,7 +396,7 @@ def find_fully_conserved_orientations( ss, ( f"Reachability of optics plot\n" - f"minsamples={int(neioc*pct_size_buffer)}; xi={xi}" + f"minsamples={int(neioc * pct_size_buffer)}; xi={xi}" ), len(fully_conserved) > 0, debugH, @@ -480,8 +485,8 @@ def find_half_conserved_orientations( N_elems = len(orientations[labels == bestcand]) # Debug if verbose > 0: - sk += f"Cluster {bestcand} has {neioc*pct_size_buffer:.2f}<" - sk += f"{N_elems}<{neioc*(2-pct_size_buffer):.2f} elements\n" + sk += f"Cluster {bestcand} has {neioc * pct_size_buffer:.2f}<" + sk += f"{N_elems}<{neioc * (2 - pct_size_buffer):.2f} elements\n" # check if number of elements in hydrogen orientation cluster is # between 0.80 and 1.20 elements of Oxygen cluster if ( @@ -499,7 +504,7 @@ def find_half_conserved_orientations( # Debug if verbose > 0 or debugH > 0: sk += "result angles: mean= " - sk += f"{np.abs(np.mean(angs1all)-104.5):.2f}(Calced)<" + sk += f"{np.abs(np.mean(angs1all) - 104.5):.2f}(Calced)<" sk += f"{angdiff_cutoff:.2f}; std dev={np.std(angs1all):.2f}" sk += f"(Calced)<{angstd_cutoff:.2f}\n" # check if angle and its std deviation is satisfactory @@ -522,10 +527,10 @@ def find_half_conserved_orientations( ss = ( f"Half Conserved - OPTICS analysis;xi={xi}" f"best: {bestcand}; N_hyd_cls (w/o -1): " - f"{len(np.unique(labels[labels!=-1]))};\n N elem in biggest:" - f"{neioc*(2-pct_size_buffer):.2f}>" - f"{len(orientations[labels==bestcand])}>" - f"{neioc*pct_size_buffer:.2f}\n" + f"{len(np.unique(labels[labels != -1]))};\n N elem in biggest:" + f"{neioc * (2 - pct_size_buffer):.2f}>" + f"{len(orientations[labels == bestcand])}>" + f"{neioc * pct_size_buffer:.2f}\n" ) # Printing if verbose == 2 or (verbose == 1 and len(half_conserved) > 0): @@ -538,7 +543,7 @@ def find_half_conserved_orientations( ss + sk, ( f"Reachability of optics plot\n minsamples=" - f"{int(neioc*min_samp_data_size_pct)}; xi={xi}" + f"{int(neioc * min_samp_data_size_pct)}; xi={xi}" ), len(half_conserved) > 0, debugH, @@ -645,8 +650,8 @@ def find_weakly_conserved_orientations( N_elems = len(orientations[labels == ii]) # Debug if verbose > 0 or debugH > 0: - sk += f"Cluster {ii} has {neioc*lower_bound_pct_buffer:.2f}<" - sk += f"{N_elems}<{neioc*(2-pct_size_buffer):.2f} elements\n" + sk += f"Cluster {ii} has {neioc * lower_bound_pct_buffer:.2f}<" + sk += f"{N_elems}<{neioc * (2 - pct_size_buffer):.2f} elements\n" # check if size of hydorgen orientation cluster is between 1.20 # and lower_bound_pct_buffer times number of elements in oxygen cluster if ( @@ -675,11 +680,11 @@ def find_weakly_conserved_orientations( maxcomp = np.max([N_elems, N_elems_jj]) calcedcomp = (1 - pct_size_buffer) * maxcomp sk += f"cluster combo:{ii} & {jj}size:{N_elems}," - sk += f"{neioc*lower_bound_pct_buffer:.2f}<{N_elems_jj}<" - sk += f"{neioc*(2-pct_size_buffer):.2f}\n" - sk += f"size comparison {np.abs(N_elems -N_elems_jj)}" + sk += f"{neioc * lower_bound_pct_buffer:.2f}<{N_elems_jj}<" + sk += f"{neioc * (2 - pct_size_buffer):.2f}\n" + sk += f"size comparison {np.abs(N_elems - N_elems_jj)}" sk += f"(calced) < {calcedcomp} \n" - sk += f"ang diff={np.abs(np.mean(angs1j)-104.5):.2f}" + sk += f"ang diff={np.abs(np.mean(angs1j) - 104.5):.2f}" sk += f"(calced)<{angdiff_cutoff:.2f},std dev:" sk += f"{angstd_cutoff:.2f}>{np.std(angs1j):.2f}(calced)\n" # check if size of new cluster and check if size of clusters @@ -796,20 +801,20 @@ def find_weakly_conserved_orientations( sk += "size check " sk += f"{np.abs(N_elems - (N_elems_jj + N_elems_kk))}" sk += f"(calced)< {sizeingprint}\n" - sk += f"ang diff={np.abs(np.mean(angs1jk)-104.5):.2f}" + sk += f"ang diff={np.abs(np.mean(angs1jk) - 104.5):.2f}" sk += f"(calced)<{angdiff_cutoff:.2f},std dev:" sk += f"{angstd_cutoff:.2f}>{np.std(angs1jk):.2f}" sk += "(calced)\n" - sk += f"ij {ii},{jj}, {np.abs(np.mean(angs1j)-104.5)}," + sk += f"ij {ii},{jj}, {np.abs(np.mean(angs1j) - 104.5)}," sk += f"<,angdiff_cutoff,and std {np.std(angs1j)}<" sk += f"{angstd_cutoff} \n" - sk += f"ik {ii},{kk}, {np.abs(np.mean(angs1k)-104.5)}," + sk += f"ik {ii},{kk}, {np.abs(np.mean(angs1k) - 104.5)}," sk += f"<,angdiff_cutoff,and std {np.std(angs1k)}<" sk += f"{angstd_cutoff} \n" - sk += f"kj {kk},{jj}, {np.abs(np.mean(angskj)-104.5)}," + sk += f"kj {kk},{jj}, {np.abs(np.mean(angskj) - 104.5)}," sk += f"<,angdiff_cutoff,and std {np.std(angskj)}<" sk += f"{angstd_cutoff} \n" - sk += f"jk {jj},{kk}, {np.abs(np.mean(angsjk)-104.5)}," + sk += f"jk {jj},{kk}, {np.abs(np.mean(angsjk) - 104.5)}," sk += f"<,angdiff_cutoff,and std {np.std(angsjk)}<" sk += f"{angstd_cutoff} \n" # check if size of clusters is about equal ii==jj+kk @@ -981,8 +986,8 @@ def find_weakly_conserved_orientations( f"weakly Conserved - OPTICS analysis;xi={xi}\n" f"Number of hydrogen clusters : {len(np.unique(labels))};\n" f"number of elements : {counts}; range needed for best cluster:" - f"(depends on numbpct) {neioc*(2-pct_size_buffer):.2f}," - f"{neioc*lower_bound_pct_buffer:.2f}\n" + f"(depends on numbpct) {neioc * (2 - pct_size_buffer):.2f}," + f"{neioc * lower_bound_pct_buffer:.2f}\n" ) # Debug Printing if verbose == 2 or (verbose == 1 and len(weakly_conserved) > 0): @@ -995,7 +1000,7 @@ def find_weakly_conserved_orientations( ss, ( f"Reachability of optics plot\n" - f"minsamples={int(neioc*min_samp_data_size_pct)}; xi={xi}" + f"minsamples={int(neioc * min_samp_data_size_pct)}; xi={xi}" ), len(weakly_conserved) > 0, debugH, @@ -1026,11 +1031,9 @@ def __plot3Dorients(subplot, labels: int, orientations: np.ndarray, tip: str) -> For debuging only. """ - try: - import matplotlib.pyplot as plt - except ModuleNotFoundError as err: + if plt is None: msg = "install matplotlib" - raise Exception(msg) from err + raise Exception(msg) fig = plt.figure() if isinstance(labels, int): @@ -1043,7 +1046,7 @@ def __plot3Dorients(subplot, labels: int, orientations: np.ndarray, tip: str) -> jaba[:, 0], jaba[:, 1], jaba[:, 2], - label=f"{j} ({len(labels[labels==j])})", + label=f"{j} ({len(labels[labels == j])})", ) if j > -1: ax.quiver( @@ -1075,15 +1078,13 @@ def __plotreachability( For debuging purposes only. """ - try: - import matplotlib.pyplot as plt - except ModuleNotFoundError as err: + if plt is None: msg = "install matplotlib" - raise Exception(msg) from err + raise Exception(msg) if fig is None: fig: Figure = plt.figure() - if type(cc) != OPTICS: + if not isinstance(cc, OPTICS): return fig lblls = cc.labels_[cc.ordering_] labels = cc.labels_ @@ -1100,7 +1101,7 @@ def __plotreachability( ax2.plot( space[lblls == clst], reachability[lblls == clst], - label=f"{clst} ({len(space[lblls==clst])}), avg reach={label}", + label=f"{clst} ({len(space[lblls == clst])}), avg reach={label}", color="blue", ) else: @@ -1108,7 +1109,7 @@ def __plotreachability( ax2.plot( space[lblls == clst], reachability[lblls == clst], - label=f"{clst} ({len(space[lblls==clst])}), avg reach={label}", + label=f"{clst} ({len(space[lblls == clst])}), avg reach={label}", ) ax2.legend() return fig diff --git a/ConservedWaterSearch/utils.py b/ConservedWaterSearch/utils.py index 869d9bd..a9e797a 100644 --- a/ConservedWaterSearch/utils.py +++ b/ConservedWaterSearch/utils.py @@ -3,33 +3,43 @@ import os import platform +import warnings from typing import TYPE_CHECKING import numpy as np +try: + import matplotlib.pyplot as plt +except ImportError: + plt = None + +try: + import pymol + from pymol import cmd +except ImportError: + pymol = None + cmd = None + +try: + import nglview as ngl +except ImportError: + ngl = None + if TYPE_CHECKING: try: from nglview import NGLWidget except ImportError: NGLWidget = None - try: - import pymol - from pymol import cmd - except ImportError: - pymol = None - cmd = None def _check_mpl_installation(): """Check if matplotlib is installed.""" - try: - import matplotlib.pyplot as plt - except ModuleNotFoundError as err: + if plt is None: msg = ( "install matplotlib using conda install -c conda-forge" "matplotlib or pip install matplotlib" ) - raise Exception(msg) from err + raise Exception(msg) return plt @@ -154,8 +164,6 @@ def get_orientations_from_positions( def _make_protein_surface_with_ligand(): - from pymol import cmd - protein = cmd.get_unused_name("only_protein_") cmd.select(protein, "polymer", state=1) povrsina = cmd.get_unused_name("protein_surface_") @@ -171,8 +179,6 @@ def _make_protein_surface_with_ligand(): def _add_polar_contacts(waters: str, aminokis_u_am: str | None = None): - from pymol import cmd - if aminokis_u_am is not None: sele = aminokis_u_am + " or " + waters + " or organic" else: @@ -187,8 +193,6 @@ def _add_polar_contacts(waters: str, aminokis_u_am: str | None = None): def _fix_pymol_camera(active_site_center: str | None = None, waters: str | None = None): - from pymol import cmd - # reset camera cmd.reset() if active_site_center is not None: @@ -198,8 +202,6 @@ def _fix_pymol_camera(active_site_center: str | None = None, waters: str | None def _determine_active_site_ids(active_site_ids: list[int]): - from pymol import cmd - selection = "" for i in active_site_ids: selection += str(i) + "+" @@ -220,17 +222,13 @@ def _determine_active_site_ids(active_site_ids: list[int]): def _add_density_map(density_map: str): - from pymol import cmd - cmd.load(density_map) - cmd.volume("water_density", density_map.split(".")[0]) + cmd.volume("water_density", density_map.split(".", maxsplit=1)[0]) def _add_crystal_waters( crystal_waters, protein, ligand_resname, dist, active_site_ids, active_site_center ): - from pymol import cmd - cmd.fetch(crystal_waters) cmd.hide("everything", crystal_waters) cmd.align( @@ -264,8 +262,6 @@ def _add_crystal_waters( def _add_hydrogen_and_bond(wname, Hpos, Hname, resn, resi): - from pymol import cmd - cmd.pseudoatom( wname, pos=[Hpos[0], Hpos[1], Hpos[2]], @@ -279,8 +275,6 @@ def _add_hydrogen_and_bond(wname, Hpos, Hname, resn, resi): def _make_water_objects(water_type, waterO, waterH1, waterH2, output_file): - from pymol import cmd - cntr = { name: ( len(cmd.get_names("objects", False, f"model {name}*")) @@ -340,7 +334,7 @@ def _make_water_objects(water_type, waterO, waterH1, waterH2, output_file): _add_hydrogen_and_bond( wname, waterH2[ind + add_ind + 1], - f"H{add_ind+3}", + f"H{add_ind + 3}", resn, highest_resi + 1, ) @@ -367,7 +361,7 @@ def _make_water_objects(water_type, waterO, waterH1, waterH2, output_file): _add_hydrogen_and_bond( wname, waterH1[ind + add_ind + 1], - f"H{add_ind+ghind+3}", + f"H{add_ind + ghind + 3}", resn, highest_resi + 1, ) @@ -387,7 +381,7 @@ def _make_water_objects(water_type, waterO, waterH1, waterH2, output_file): _add_hydrogen_and_bond( wname, waterH2[ind + add_ind + 1], - f"H{add_ind+hind+ghind+3}", + f"H{add_ind + hind + ghind + 3}", resn, highest_resi + 1, ) @@ -491,8 +485,6 @@ def visualise_pymol( lunch_pymol = False _initialize_pymol(reinitialize, lunch_pymol) if platform.system() == "Darwin" and output_file is None: - import warnings - warnings.warn( ( "mac OS detected interactive pymol session cannot be lunched." @@ -503,8 +495,6 @@ def visualise_pymol( stacklevel=2, ) output_file = "pymol_water_visualization.pse" - from pymol import cmd - cmd.hide("everything") active_site_center = None aminokis_u_am = None @@ -599,7 +589,6 @@ def visualise_pymol_from_pdb( if platform.system() == "Darwin": lunch_pymol = False _initialize_pymol(reinitialize, lunch_pymol) - from pymol import cmd cmd.load(pdbfile) cmd.hide("everything") @@ -640,7 +629,7 @@ def visualise_pymol_from_pdb( if crystal_waters is not None: _add_crystal_waters( crystal_waters, - pdbfile.split(".")[0], + pdbfile.split(".", maxsplit=1)[0], ligand_resname, dist, active_site_ids, @@ -669,12 +658,9 @@ def _initialize_pymol(reinitialize: bool, finish: bool): in interactive mode. If `False` pymol will be imported without lunching. Defaults to True. """ - try: - import pymol - from pymol import cmd - except ModuleNotFoundError as err: + if pymol is None or cmd is None: msg = "pymol not installed. Either install pymol or use nglview" - raise Exception(msg) from err + raise Exception(msg) if finish: pymol.finish_launching(["pymol", "-q"]) if reinitialize: @@ -734,11 +720,9 @@ def visualise_nglview( # initialise widget view """ - try: - import nglview as ngl - except ModuleNotFoundError as err: + if ngl is None: msg = "nglview not installed. Either install pymol or nglview" - raise Exception(msg) from err + raise Exception(msg) if aligned_protein is not None: view: NGLWidget = ngl.show_file(aligned_protein, default_representation=False) diff --git a/ConservedWaterSearch/water_clustering.py b/ConservedWaterSearch/water_clustering.py index 9c9f10d..ec5c336 100644 --- a/ConservedWaterSearch/water_clustering.py +++ b/ConservedWaterSearch/water_clustering.py @@ -835,15 +835,15 @@ def _scan_clustering_params( if self.debugO > 0: dbgt: str = "" if self.verbose > 0: - (aa, bb) = np.unique(clusters, return_counts=True) + (_aa, bb) = np.unique(clusters, return_counts=True) dbgt = ( f"Oxygen clustering {type(clust)} " f"minsamp={i}, xi={j}, " - f"{len(np.unique(clusters[clusters!=-1]))} " + f"{len(np.unique(clusters[clusters != -1]))} " f"clusters \n" f"Required N(elem) range:" - f"{self.nsnaps*self.numbpct_oxygen:.2f} to " - f"{(2-self.numbpct_oxygen)*self.nsnaps}; " + f"{self.nsnaps * self.numbpct_oxygen:.2f} to " + f"{(2 - self.numbpct_oxygen) * self.nsnaps}; " f"(tar cls size={self.nsnaps} and numbpct=" f"{self.numbpct_oxygen:.2f})\n" f"N(elements) for each cluster: {bb}\n" @@ -932,7 +932,7 @@ def _analyze_oxygen_clustering( # Nsnap*0.85 0: - print(f"O clust {k}, size {len(clusters[clusters==k])}\n") + print(f"O clust {k}, size {len(clusters[clusters == k])}\n") O_center = np.mean(Odata[mask], axis=0) if "onlyO" not in self.water_types_to_find: # Construct array of hydrogen orientations @@ -1214,7 +1214,7 @@ def _oxygen_clustering_plot( jaba[:, 0], jaba[:, 1], jaba[:, 2], - label=f"{k} ({len(cc.labels_[cc.labels_==k])})", + label=f"{k} ({len(cc.labels_[cc.labels_ == k])})", s=s, ) ax.set_xlabel("X") @@ -1234,14 +1234,14 @@ def _oxygen_clustering_plot( ax.plot( space[lblls == clst], cc.reachability_[lblls == clst], - label=f"{clst} ({len(space[lblls==clst])})", + label=f"{clst} ({len(space[lblls == clst])})", color="blue", ) else: ax.plot( space[lblls == clst], cc.reachability_[lblls == clst], - label=f"{clst} ({len(space[lblls==clst])})", + label=f"{clst} ({len(space[lblls == clst])})", ) ax.legend() if debugO == 2: diff --git a/tests/conftest.py b/tests/conftest.py index a774c44..b4cf5ee 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -99,12 +99,12 @@ def generate_water_network( return np.asarray(Opos), np.asarray(Hpos), base_oxygens -@pytest.fixture() +@pytest.fixture def orientations_normalized(): return np.asarray([[1, 0, 0], [-0.25038 * 2, 0.96814764 * 2, 0]]) -@pytest.fixture() +@pytest.fixture def orientations_not_normalized(): return np.asarray([[1, 0, 0], [-0.25038, 0.96814764, 0]]) @@ -123,7 +123,7 @@ def water_data(request): return generate_orientation_sample(kind, nsnaps=24, rng=rng), expected -@pytest.fixture() +@pytest.fixture def _pymol_skip(): pytest.importorskip("pymol") @@ -151,7 +151,7 @@ def water_clustering_setup(request): return wc, request.param["onlyO"] -@pytest.fixture() +@pytest.fixture def water_clustering_setup_for_deletion(): with tempfile.NamedTemporaryFile( mode="w+", delete=True @@ -163,7 +163,7 @@ def water_clustering_setup_for_deletion(): yield wc, dat.name, Odata, H1, H2 -@pytest.fixture() +@pytest.fixture def water_clustering_data(): rng = np.random.default_rng(2024) Opos, Hpos, centers = generate_water_network(nsnaps=12, n_waters=2, rng=rng) diff --git a/tests/test_hydrogen_orientation.py b/tests/test_hydrogen_orientation.py index 7dbf748..990ea8f 100644 --- a/tests/test_hydrogen_orientation.py +++ b/tests/test_hydrogen_orientation.py @@ -1,6 +1,11 @@ """Unit and regression test for the ConservedWaterSearch package.""" # Import package, test suite, and other packages as needed +try: + import matplotlib.pyplot as plt +except ImportError: + plt = None + import numpy as np import pytest @@ -33,9 +38,7 @@ def test_water_types_from_files(data_file): res = ConservedWaterSearch.hydrogen_orientation.hydrogen_orientation_analysis( orientations, debugH=make_ho_plots ) - if make_ho_plots > 0: - import matplotlib.pyplot as plt - + if make_ho_plots > 0 and plt is not None: plt.show() if "not_conserved" in data_label: assert len(res) == 0 @@ -49,9 +52,7 @@ def test_water_types(water_data): res = ConservedWaterSearch.hydrogen_orientation.hydrogen_orientation_analysis( orientations, debugH=make_ho_plots ) - if make_ho_plots > 0: - import matplotlib.pyplot as plt - + if make_ho_plots > 0 and plt is not None: plt.show() if expected is None: assert len(res) == 0