From d4190729a8babc47e4f219af02477160327c0566 Mon Sep 17 00:00:00 2001 From: Kevin Boyd Date: Fri, 18 Sep 2026 08:35:10 -0400 Subject: [PATCH 01/10] Enable Ruff lint checks in CI --- .github/workflows/lint.yml | 6 +++--- admin/run_ruff_format.sh | 15 ++++++++++----- benchmarks/cross_similarity_bench.py | 1 - benchmarks/tfd_prepare_mols.py | 1 - nvmolkit/batchedForcefield.py | 2 -- nvmolkit/mmffOptimization.py | 2 -- nvmolkit/tests/test_mmff_optimization.py | 4 ++++ nvmolkit/types.py | 4 ++++ nvmolkit/uffOptimization.py | 1 - pyproject.toml | 23 +++-------------------- 10 files changed, 24 insertions(+), 35 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6dfcde41..431935bf 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -143,8 +143,8 @@ jobs: set -xeuo pipefail bash admin/run_include_check.sh -d - ruff-format: - name: ruff-format + ruff: + name: ruff runs-on: ubuntu-24.04 timeout-minutes: 30 @@ -164,7 +164,7 @@ jobs: python -m pip install --upgrade pip python -m pip install ruff==0.15.8 - - name: Run ruff format check + - name: Run Ruff checks shell: bash run: | set -xeuo pipefail diff --git a/admin/run_ruff_format.sh b/admin/run_ruff_format.sh index 0255845e..d113619e 100644 --- a/admin/run_ruff_format.sh +++ b/admin/run_ruff_format.sh @@ -15,17 +15,19 @@ # limitations under the License. -# Runs ruff format over the codebase. +# Runs Ruff lint fixes and formatting over the codebase. # By default will modify files in-place. Use -d to do a dry-run. set -ex EXPECTED_RUFF_VERSION="0.15.8" -RUFF_ARGS=() +RUFF_CHECK_ARGS=(--fix) +RUFF_FORMAT_ARGS=() while getopts ":d" opt; do case ${opt} in d ) - RUFF_ARGS+=(--check) + RUFF_CHECK_ARGS=() + RUFF_FORMAT_ARGS+=(--check) ;; \? ) echo "Usage: run_ruff_format.sh [-d]" @@ -39,12 +41,15 @@ ROOT_DIR=$(git rev-parse --show-toplevel) if command -v ruff >/dev/null 2>&1; then ACTUAL_RUFF_VERSION=$(ruff --version | awk '{print $2}') if [ "$ACTUAL_RUFF_VERSION" != "$EXPECTED_RUFF_VERSION" ]; then - echo "Warning: expected ruff version $EXPECTED_RUFF_VERSION, found $ACTUAL_RUFF_VERSION. Formatting may not match CI checker." >&2 + echo "Warning: expected ruff version $EXPECTED_RUFF_VERSION, found $ACTUAL_RUFF_VERSION. Results may not match CI." >&2 fi else echo "Error: ruff is not installed; expected version $EXPECTED_RUFF_VERSION." >&2 exit 1 fi +echo "Running ruff check:" +ruff check "${RUFF_CHECK_ARGS[@]}" "$ROOT_DIR" + echo "Running ruff format:" -ruff format "${RUFF_ARGS[@]}" "$ROOT_DIR" +ruff format "${RUFF_FORMAT_ARGS[@]}" "$ROOT_DIR" diff --git a/benchmarks/cross_similarity_bench.py b/benchmarks/cross_similarity_bench.py index 1e4a2537..c0b744ed 100644 --- a/benchmarks/cross_similarity_bench.py +++ b/benchmarks/cross_similarity_bench.py @@ -24,7 +24,6 @@ from nvmolkit.fingerprints import MorganFingerprintGenerator from nvmolkit.similarity import crossCosineSimilarity, crossTanimotoSimilarity - SIZES = [2000, 4000, 6000, 8000, 10000, 12000, 14000, 16000, 20000, 24000, 28000, 32000] CPU_SINGLE_VALUE_ABOVE = 6000 diff --git a/benchmarks/tfd_prepare_mols.py b/benchmarks/tfd_prepare_mols.py index fc3bbfdd..3ac1996c 100644 --- a/benchmarks/tfd_prepare_mols.py +++ b/benchmarks/tfd_prepare_mols.py @@ -37,7 +37,6 @@ import multiprocessing import os import pickle -import sys import time from functools import partial diff --git a/nvmolkit/batchedForcefield.py b/nvmolkit/batchedForcefield.py index bc7eaa94..a071d632 100644 --- a/nvmolkit/batchedForcefield.py +++ b/nvmolkit/batchedForcefield.py @@ -87,8 +87,6 @@ from nvmolkit._mmff_bridge import default_rdkit_mmff_properties, make_internal_mmff_properties from nvmolkit.types import CoordinateOutput, Device3DResult, FireOptions, HardwareOptions -from nvmolkit import _batchedForcefield # type: ignore - if TYPE_CHECKING: from rdkit.Chem import Mol from rdkit.ForceField.rdForceField import MMFFMolProperties as RDKitMMFFMolProperties diff --git a/nvmolkit/mmffOptimization.py b/nvmolkit/mmffOptimization.py index 01de9591..c31b6bee 100644 --- a/nvmolkit/mmffOptimization.py +++ b/nvmolkit/mmffOptimization.py @@ -33,8 +33,6 @@ from rdkit.ForceField.rdForceField import MMFFMolProperties from nvmolkit import _mmffOptimization -from nvmolkit._mmff_bridge import default_rdkit_mmff_properties, make_internal_mmff_properties -from nvmolkit.types import CoordinateOutput, Device3DResult, HardwareOptions @overload diff --git a/nvmolkit/tests/test_mmff_optimization.py b/nvmolkit/tests/test_mmff_optimization.py index cfbdf758..1136c41c 100644 --- a/nvmolkit/tests/test_mmff_optimization.py +++ b/nvmolkit/tests/test_mmff_optimization.py @@ -23,9 +23,13 @@ from rdkit.ForceField import rdForceField as _rdForceField # noqa: F401 from rdkit.Geometry import Point3D +# Preserve this initialization order so converter registration remains +# explicit in the integration test module. +# isort: off from nvmolkit.embedMolecules import EmbedMolecules import nvmolkit.mmffOptimization as nvmolkit_mmff from nvmolkit.types import CoordinateOutput, Device3DResult, FireOptions, HardwareOptions +# isort: on @pytest.fixture diff --git a/nvmolkit/types.py b/nvmolkit/types.py index 4e87ce50..9e961685 100644 --- a/nvmolkit/types.py +++ b/nvmolkit/types.py @@ -21,9 +21,13 @@ import numpy as np import torch +# Import order is significant: these native modules register converters used by +# the modules imported after them. +# isort: off from nvmolkit import _arrayHelpers # noqa: F401 from nvmolkit import _embedMolecules # type: ignore from nvmolkit import _types +# isort: on class FireOptions: diff --git a/nvmolkit/uffOptimization.py b/nvmolkit/uffOptimization.py index 522efeb8..960cce08 100644 --- a/nvmolkit/uffOptimization.py +++ b/nvmolkit/uffOptimization.py @@ -27,7 +27,6 @@ from rdkit.Chem import Mol from nvmolkit import _uffOptimization -from nvmolkit.types import CoordinateOutput, Device3DResult, HardwareOptions @overload diff --git a/pyproject.toml b/pyproject.toml index 67e0f5b3..0a499742 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -144,30 +144,13 @@ line-length = 119 include = ["nvmolkit/**/*.py", "benchmarks/**/*.py", "setup.py"] [tool.ruff.lint] -ignore = [ - "C901", # Checks for functions with a high McCabe complexity. - "D100", # Checks for undocumented public module definitions. - "E501", # Checks for lines that exceed the specified maximum character length. - "E741", # Checks for the use of the characters 'l', 'O', or 'I' as variable names. - "RUF005", # Checks for uses of the + operator to concatenate collections. -] select = [ - "C", # Pylint conventions - "D", # Documentation formatting - "E", # style stuff, whitespaces - "F", # important pyflakes lints - "I", # import sorting - "RUF", # Some Ruff-specific lints, unused noqas, etc. - "W", # Pylint warnings + "F", # Pyflakes correctness checks + "I", # Import sorting ] [tool.ruff.lint.per-file-ignores] -"__init__.py" = ["D104"] -"nvmolkit/tests/test_*.py" = ["D103"] -"benchmarks/*.py" = ["F841", "D103"] - -[tool.ruff.lint.isort] -lines-after-imports = 1 +"benchmarks/*.py" = ["F841"] [tool.ruff.lint.pydocstyle] convention = "google" From 56d6ff645e2e98dfe5514365711139d7482ddd5e Mon Sep 17 00:00:00 2001 From: Kevin Boyd Date: Fri, 18 Sep 2026 08:50:04 -0400 Subject: [PATCH 02/10] Fix Ruff docstring layout findings --- nvmolkit/_mmff_bridge.py | 2 - nvmolkit/batchedForcefield.py | 6 +-- nvmolkit/tests/test_batched_forcefield.py | 52 +++++++++++++++-------- 3 files changed, 36 insertions(+), 24 deletions(-) diff --git a/nvmolkit/_mmff_bridge.py b/nvmolkit/_mmff_bridge.py index 8b2eaca5..53473ebf 100644 --- a/nvmolkit/_mmff_bridge.py +++ b/nvmolkit/_mmff_bridge.py @@ -39,7 +39,6 @@ def default_rdkit_mmff_properties(mol: "Mol"): """Create default RDKit MMFF properties for ``mol``.""" - properties = rdForceFieldHelpers.MMFFGetMoleculeProperties(mol) if properties is None: raise ValueError("RDKit could not create MMFF properties for molecule") @@ -58,7 +57,6 @@ def make_internal_mmff_properties( (variant, dielectric, per-term flags); the corresponding getters are not wrapped. We read the settings through the C++ binding layer instead. """ - return _batchedForcefield.buildMMFFPropertiesFromRDKit( properties, float(non_bonded_threshold), diff --git a/nvmolkit/batchedForcefield.py b/nvmolkit/batchedForcefield.py index a071d632..e8953ed6 100644 --- a/nvmolkit/batchedForcefield.py +++ b/nvmolkit/batchedForcefield.py @@ -454,8 +454,7 @@ def _minimize( class MMFFBatchedForcefield(_BatchedForcefieldBase): - """Evaluate MMFF energies and gradients, or run BFGS minimization, for a - batch of molecules with all their conformers. + """Evaluate MMFF energies and gradients or run BFGS minimization for molecule batches. Properties and constraints are per-molecule and are shared across all conformers of that molecule. Results are nested as @@ -630,8 +629,7 @@ def minimize( class UFFBatchedForcefield(_BatchedForcefieldBase): - """Evaluate UFF energies and gradients, or run BFGS minimization, for a - batch of molecules with all their conformers. + """Evaluate UFF energies and gradients or run BFGS minimization for molecule batches. Constraints are per-molecule and are shared across all conformers of that molecule. Results are nested as ``list[list[...]]`` — outer diff --git a/nvmolkit/tests/test_batched_forcefield.py b/nvmolkit/tests/test_batched_forcefield.py index fdb5fdda..377e0ac7 100644 --- a/nvmolkit/tests/test_batched_forcefield.py +++ b/nvmolkit/tests/test_batched_forcefield.py @@ -165,9 +165,10 @@ def assert_energy_and_gradient_close(got_energy, want_energy, got_grad, want_gra def _assert_batched_compute_matches_rdkit_mmff(mol_specs): - """Build a single MMFFBatchedForcefield from ``mol_specs`` and verify that - per-mol ``compute_energy``/``compute_gradients`` match RDKit's single-mol FF - for each mol, with its configured properties and (optionally) constraint. + """Verify batched MMFF energies and gradients against RDKit. + + Build a single MMFFBatchedForcefield from ``mol_specs`` and compare each + molecule using its configured properties and optional constraint. When any mol has a constraint configured, additionally verify that the constraint has an observable effect on that mol's energy AND gradient — as @@ -266,8 +267,11 @@ def _assert_batched_compute_matches_rdkit_mmff(mol_specs): def test_mmff_batched_forcefield_properties_match_rdkit(): - """Batch of mols with varied per-mol property configurations (default, MMFF variant, - dielectric model, term toggles, fragmented+interfrag).""" + """Test varied per-molecule property configurations. + + Cover defaults, MMFF variants, dielectric models, term toggles, and + fragmented molecules with interfragment interactions. + """ _assert_batched_compute_matches_rdkit_mmff( [ {"factory": load_reference_mol}, @@ -296,9 +300,11 @@ def test_mmff_batched_forcefield_properties_match_rdkit(): def test_mmff_batched_forcefield_reads_externally_configured_properties(): - """Configure RDKit MMFF properties via raw ``rdForceFieldHelpers.MMFFGetMoleculeProperties`` - plus direct ``SetMMFF*Term``/``SetMMFFDielectricConstant`` calls — no nvmolkit helpers - in the path — then hand the object to ``MMFFBatchedForcefield``. + """Test externally configured RDKit MMFF properties. + + Configure properties via raw ``rdForceFieldHelpers.MMFFGetMoleculeProperties`` + plus direct ``SetMMFF*Term``/``SetMMFFDielectricConstant`` calls, with no + nvmolkit helpers in the path, then pass the object to ``MMFFBatchedForcefield``. Needed because of our workaround for RDKit bug https://github.com/rdkit/rdkit/issues/9253 """ @@ -327,8 +333,11 @@ def test_mmff_batched_forcefield_reads_externally_configured_properties(): def test_mmff_batched_forcefield_constraints_match_rdkit(): - """Batch of mols with all 5 MMFF constraint types applied (one per mol), some also - carrying non-default property settings to exercise the properties+constraints path.""" + """Test all five MMFF constraint types against RDKit. + + Apply one constraint type per molecule, with some molecules also carrying + non-default property settings to exercise the properties-plus-constraints path. + """ _assert_batched_compute_matches_rdkit_mmff( [ { @@ -515,8 +524,11 @@ def _build_constrained_mmff_batch(specs=_MMFF_BATCH_CONSTRAINT_SPECS, hardwareOp def _assert_batched_minimize_matches_rdkit(specs, mols, opt_energies, converged, make_ref_ff): - """Compare nvMolKit minimize() result to RDKit minimize per (mol, conformer), with each - mol carrying a different constraint from `specs`.""" + """Compare nvMolKit and RDKit minimization results. + + Compare each molecule and conformer while each molecule carries a different + constraint from ``specs``. + """ for mol_idx, (mol, spec) in enumerate(zip(mols, specs)): assert len(opt_energies[mol_idx]) == mol.GetNumConformers() assert all(converged[mol_idx]), f"Mol {mol_idx} failed to converge" @@ -531,9 +543,11 @@ def _assert_batched_minimize_matches_rdkit(specs, mols, opt_energies, converged, def test_mmff_batched_minimize_with_constraints_batch_matches_rdkit(): - """Batch minimize with different constraint types on different-size mols - and different conformer counts, comparing each (mol, conformer) energy - to RDKit's minimize with the same constraint.""" + """Test constrained batch minimization against RDKit. + + Use different constraint types, molecule sizes, and conformer counts, then + compare each molecule and conformer energy to RDKit with the same constraint. + """ mols, _, ff = _build_constrained_mmff_batch() opt_energies, converged = ff.minimize(maxIters=500) @@ -544,9 +558,11 @@ def make_ref(mol, conf_id): def test_mmff_batched_minimize_respects_maxiters_and_forcetol(): - """maxIters and forceTol must be plumbed through: a single-iteration minimize - should not converge and should leave energies closer to the starting point - than a generous-iteration minimize.""" + """Test that maxIters and forceTol are passed through. + + A single-iteration minimization should not converge and should leave energies + closer to the starting point than a generous-iteration minimization. + """ perturbed_mols = [ perturb_conformers(make_embedded_mol("CCCO", num_confs=2)), perturb_conformers(make_embedded_mol("c1ccccc1CCO", num_confs=2)), From 39d911bd1658c0de511e39752c4a8dcdaf8e3c31 Mon Sep 17 00:00:00 2001 From: Kevin Boyd Date: Fri, 18 Sep 2026 08:50:33 -0400 Subject: [PATCH 03/10] Document Ruff-reported Python APIs --- nvmolkit/batchedForcefield.py | 2 ++ nvmolkit/substructure.py | 3 +++ nvmolkit/tests/test_mmff_optimization.py | 6 +++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/nvmolkit/batchedForcefield.py b/nvmolkit/batchedForcefield.py index e8953ed6..b0dbb33d 100644 --- a/nvmolkit/batchedForcefield.py +++ b/nvmolkit/batchedForcefield.py @@ -512,6 +512,7 @@ def __init__( ) def __getitem__(self, idx: int) -> MMFFBatchElement: + """Return the force-field element at ``idx``.""" return super().__getitem__(idx) def _normalize_properties( @@ -675,6 +676,7 @@ def __init__( self._vdw_thresholds = _normalize_scalar_or_list(vdwThreshold, len(molecules), "vdwThreshold") def __getitem__(self, idx: int) -> UFFBatchElement: + """Return the force-field element at ``idx``.""" return super().__getitem__(idx) def _build_native(self): diff --git a/nvmolkit/substructure.py b/nvmolkit/substructure.py index 4c59f8e7..31b767bb 100644 --- a/nvmolkit/substructure.py +++ b/nvmolkit/substructure.py @@ -79,6 +79,7 @@ def __init__( gpuIds: list[int] | None = None, algorithm: str = "dfs", ) -> None: + """Initialize a substructure-search configuration.""" native = _NativeSubstructSearchConfig() native.batchSize = int(batchSize) native.workerThreads = int(workerThreads) @@ -198,9 +199,11 @@ class SubstructMatchResults: shape: tuple[int, int] def __len__(self) -> int: + """Return the number of target molecules.""" return self.shape[0] def __getitem__(self, target_idx: int) -> _SubstructTargetView: + """Return the match view for one target molecule.""" return _SubstructTargetView(self, target_idx) def get_pair(self, target_idx: int, query_idx: int) -> list[np.ndarray]: diff --git a/nvmolkit/tests/test_mmff_optimization.py b/nvmolkit/tests/test_mmff_optimization.py index 1136c41c..00aa5973 100644 --- a/nvmolkit/tests/test_mmff_optimization.py +++ b/nvmolkit/tests/test_mmff_optimization.py @@ -137,7 +137,11 @@ def calculate_rdkit_mmff_energies( """Calculate MMFF energies using RDKit for all conformers of all molecules. Args: - molecules: List of RDKit molecules with conformers + molecules: List of RDKit molecules with conformers. + maxIters: Maximum minimization iterations per conformer. + property_settings: Optional MMFF property overrides. + nonBondedThreshold: Non-bonded interaction cutoff. + ignoreInterfragInteractions: Whether to omit interactions between fragments. Returns: list: List of lists containing energies for each molecule's conformers From fe1b850503716ffdd6f8e6f18cdb6911c967f61d Mon Sep 17 00:00:00 2001 From: Kevin Boyd Date: Fri, 18 Sep 2026 08:52:12 -0400 Subject: [PATCH 04/10] Remove redundant integer casts --- nvmolkit/autotune/_calibration.py | 4 ++-- nvmolkit/autotune/_core.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nvmolkit/autotune/_calibration.py b/nvmolkit/autotune/_calibration.py index e4a1b5af..5ccaf878 100644 --- a/nvmolkit/autotune/_calibration.py +++ b/nvmolkit/autotune/_calibration.py @@ -37,7 +37,7 @@ def auto_subsample( """ if workload_size <= 0: raise ValueError("workload_size must be positive") - target = min(max_size, max(min_size, int(round(fraction * workload_size)))) + target = min(max_size, max(min_size, round(fraction * workload_size))) target = min(target, workload_size) rng = random.Random(seed) indices = list(range(workload_size)) @@ -86,6 +86,6 @@ def shrink(indices: Sequence[int], factor: float = 0.5, *, min_size: int = 1) -> """ if factor <= 0.0 or factor >= 1.0: raise ValueError("factor must be in (0, 1)") - new_size = max(min_size, int(round(len(indices) * factor))) + new_size = max(min_size, round(len(indices) * factor)) new_size = min(new_size, len(indices)) return list(indices[:new_size]) diff --git a/nvmolkit/autotune/_core.py b/nvmolkit/autotune/_core.py index fa8a9671..2d24eb35 100644 --- a/nvmolkit/autotune/_core.py +++ b/nvmolkit/autotune/_core.py @@ -348,7 +348,7 @@ def collect_int_from_space(spec: Any) -> int: high_int = int(high) if low_int <= 0 or high_int <= 0: raise ValueError(f"Log-uniform range {spec!r} requires strictly positive bounds.") - midpoint = int(round(math.sqrt(low_int * high_int))) + midpoint = round(math.sqrt(low_int * high_int)) return max(low_int, min(high_int, midpoint)) if isinstance(spec, tuple) and len(spec) == 3 and all(isinstance(v, int) for v in spec): low, high, step = (int(v) for v in spec) From e9ed6aa30d5e8064240f2abb4e8cedc870b4fea7 Mon Sep 17 00:00:00 2001 From: Kevin Boyd Date: Fri, 18 Sep 2026 08:52:41 -0400 Subject: [PATCH 05/10] Remove stale Ruff suppressions --- nvmolkit/autotune/_core.py | 2 +- nvmolkit/autotune/_ff_common.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nvmolkit/autotune/_core.py b/nvmolkit/autotune/_core.py index 2d24eb35..d03aab4c 100644 --- a/nvmolkit/autotune/_core.py +++ b/nvmolkit/autotune/_core.py @@ -53,7 +53,7 @@ def _require_optuna(): """Import optuna or raise an :class:`ImportError` with install instructions.""" if not is_optuna_available(): raise ImportError(OPTUNA_INSTALL_HINT) - import optuna # noqa: PLC0415 + import optuna return optuna diff --git a/nvmolkit/autotune/_ff_common.py b/nvmolkit/autotune/_ff_common.py index c37845fb..4cfd9db9 100644 --- a/nvmolkit/autotune/_ff_common.py +++ b/nvmolkit/autotune/_ff_common.py @@ -100,7 +100,7 @@ def resolve_num_gpus(fixed_gpu_ids: list[int]) -> int: if fixed_gpu_ids: return max(1, len(fixed_gpu_ids)) try: - import torch # noqa: PLC0415 + import torch return max(1, int(torch.cuda.device_count())) except Exception: From 5ce2813818011940ffcd509a8968667044be1f49 Mon Sep 17 00:00:00 2001 From: Kevin Boyd Date: Fri, 18 Sep 2026 09:16:42 -0400 Subject: [PATCH 06/10] Remove unnecessary Ruff isort guards --- nvmolkit/tests/test_mmff_optimization.py | 6 +----- nvmolkit/types.py | 10 +++++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/nvmolkit/tests/test_mmff_optimization.py b/nvmolkit/tests/test_mmff_optimization.py index 00aa5973..9853afc8 100644 --- a/nvmolkit/tests/test_mmff_optimization.py +++ b/nvmolkit/tests/test_mmff_optimization.py @@ -23,13 +23,9 @@ from rdkit.ForceField import rdForceField as _rdForceField # noqa: F401 from rdkit.Geometry import Point3D -# Preserve this initialization order so converter registration remains -# explicit in the integration test module. -# isort: off -from nvmolkit.embedMolecules import EmbedMolecules import nvmolkit.mmffOptimization as nvmolkit_mmff +from nvmolkit.embedMolecules import EmbedMolecules from nvmolkit.types import CoordinateOutput, Device3DResult, FireOptions, HardwareOptions -# isort: on @pytest.fixture diff --git a/nvmolkit/types.py b/nvmolkit/types.py index 9e961685..a1e3f892 100644 --- a/nvmolkit/types.py +++ b/nvmolkit/types.py @@ -23,11 +23,11 @@ # Import order is significant: these native modules register converters used by # the modules imported after them. -# isort: off -from nvmolkit import _arrayHelpers # noqa: F401 -from nvmolkit import _embedMolecules # type: ignore -from nvmolkit import _types -# isort: on +from nvmolkit import ( + _arrayHelpers, # noqa: F401 + _embedMolecules, # type: ignore + _types, +) class FireOptions: From f4a72cedc03194dae82c426f085202d9b16f1473 Mon Sep 17 00:00:00 2001 From: Kevin Boyd Date: Fri, 18 Sep 2026 09:20:40 -0400 Subject: [PATCH 07/10] Resolve remaining Ruff findings --- benchmarks/tfd_profile.py | 2 +- nvmolkit/substructure.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/benchmarks/tfd_profile.py b/benchmarks/tfd_profile.py index 962a9719..e5c14dc2 100644 --- a/benchmarks/tfd_profile.py +++ b/benchmarks/tfd_profile.py @@ -174,7 +174,7 @@ def main(): # === Warmup === if args.warmup > 0: with nvtx.annotate("Warmup", color="red"): - first_mols = list(configs.values())[0] + first_mols = next(iter(configs.values())) warmup_mols = first_mols[: min(5, len(first_mols))] print(f"\nWarmup ({args.warmup} iteration(s)) with {len(warmup_mols)} molecules...") for _ in range(args.warmup): diff --git a/nvmolkit/substructure.py b/nvmolkit/substructure.py index 31b767bb..2dea4473 100644 --- a/nvmolkit/substructure.py +++ b/nvmolkit/substructure.py @@ -41,10 +41,10 @@ from nvmolkit._substructure import hasSubstructMatch as _hasSubstructMatch __all__ = [ - "SubstructSearchConfig", "SubstructMatchResults", - "getSubstructMatches", + "SubstructSearchConfig", "countSubstructMatches", + "getSubstructMatches", "hasSubstructMatch", ] From 58bfd5f67c441313ea11702772368e19df4bc03c Mon Sep 17 00:00:00 2001 From: Kevin Boyd Date: Fri, 18 Sep 2026 09:20:56 -0400 Subject: [PATCH 08/10] Restore full configured Ruff policy --- pyproject.toml | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0a499742..3d88d81c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -144,13 +144,27 @@ line-length = 119 include = ["nvmolkit/**/*.py", "benchmarks/**/*.py", "setup.py"] [tool.ruff.lint] +ignore = [ + "C901", # Checks for functions with a high McCabe complexity. + "D100", # Checks for undocumented public module definitions. + "E501", # Checks for lines that exceed the specified maximum character length. + "E741", # Checks for the use of the characters 'l', 'O', or 'I' as variable names. + "RUF005", # Checks for uses of the + operator to concatenate collections. +] select = [ - "F", # Pyflakes correctness checks - "I", # Import sorting + "C", # Pylint conventions + "D", # Documentation formatting + "E", # Style and whitespace checks + "F", # Pyflakes correctness checks + "I", # Import sorting + "RUF", # Ruff-specific checks + "W", # Pylint warnings ] [tool.ruff.lint.per-file-ignores] -"benchmarks/*.py" = ["F841"] +"__init__.py" = ["D104"] +"nvmolkit/tests/test_*.py" = ["D103"] +"benchmarks/*.py" = ["F841", "D103"] [tool.ruff.lint.pydocstyle] convention = "google" From 068dabba59832882a2181d586d158be82daae80e Mon Sep 17 00:00:00 2001 From: Kevin Boyd Date: Fri, 18 Sep 2026 09:25:16 -0400 Subject: [PATCH 09/10] Enable clean Ruff correctness categories --- pyproject.toml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3d88d81c..b2abc3b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -152,13 +152,18 @@ ignore = [ "RUF005", # Checks for uses of the + operator to concatenate collections. ] select = [ - "C", # Pylint conventions + "A", # Builtin shadowing + "C", # C-prefixed convention checks "D", # Documentation formatting + "DTZ", # Datetime timezone checks "E", # Style and whitespace checks "F", # Pyflakes correctness checks "I", # Import sorting + "LOG", # Logging checks + "PIE", # Miscellaneous correctness checks + "PLE", # Pylint errors "RUF", # Ruff-specific checks - "W", # Pylint warnings + "W", # Pycodestyle warnings ] [tool.ruff.lint.per-file-ignores] From 3add7ba0d7b42232354746d287e71af8138bd43c Mon Sep 17 00:00:00 2001 From: Kevin Boyd Date: Fri, 18 Sep 2026 18:24:12 -0400 Subject: [PATCH 10/10] Clarify native converter import ordering --- nvmolkit/types.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/nvmolkit/types.py b/nvmolkit/types.py index a1e3f892..3376da5a 100644 --- a/nvmolkit/types.py +++ b/nvmolkit/types.py @@ -21,13 +21,14 @@ import numpy as np import torch -# Import order is significant: these native modules register converters used by -# the modules imported after them. -from nvmolkit import ( - _arrayHelpers, # noqa: F401 - _embedMolecules, # type: ignore - _types, -) +# These imports populate Boost.Python's global converter registry. Keep this +# bootstrap sequence developer-controlled so future binding dependencies are +# not silently reordered by the formatter. +# isort: off +from nvmolkit import _arrayHelpers # noqa: F401 +from nvmolkit import _embedMolecules # type: ignore +from nvmolkit import _types +# isort: on class FireOptions: