Skip to content
Open
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
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ Once you have `micromamba` installed and have already cloned this repo, you can

### Linux
```bash
micromamba create -n qligfep_new python=3.11
micromamba create -n qligfep_new python=3.14
micromamba activate qligfep_new
micromamba install gfortran=11.3.0 openff-toolkit=0.17.1 "openff-utilities>=0.1.12" openff-forcefields=2026.01.0 openmm=8.1.1 openff-nagl=0.5.4 openff-nagl-models=2025.9.0 lomap2 kartograf=1.0.1 michellab::fkcombu konnektor -c conda-forge --yes
micromamba install gfortran=11.3.0 openff-toolkit=0.18.1 "openff-utilities>=0.1.12" openff-forcefields=2026.01.0 openmm=8.4.0 openff-nagl=0.5.5 openff-nagl-models=2025.9.0 lomap2 kartograf=1.0.1 michellab::fkcombu konnektor -c conda-forge --yes
```

Now that you have the environment ready and activated, [clone the repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository), enter the `Q` directory with `cd Q/`, and install qligfep:
Expand All @@ -49,7 +49,7 @@ The `qprep` Fortran binary will be automatically compiled during installation.
<summary>To install everything in one line...</summary>

```bash
micromamba create -n qligfep_new python=3.11 gfortran=11.3.0 openff-toolkit=0.17.1 "openff-utilities>=0.1.12" openff-forcefields=2026.01.0 openmm=8.1.1 openff-nagl=0.5.4 openff-nagl-models=2025.9.0 lomap2 kartograf=1.0.1 michellab::fkcombu konnektor -c conda-forge --yes && micromamba activate qligfep_new && python -m pip install -e .
micromamba create -n qligfep_new python=3.14 gfortran=11.3.0 openff-toolkit=0.18.1 "openff-utilities>=0.1.12" openff-forcefields=2026.01.0 openmm=8.4.0 openff-nagl=0.5.5 openff-nagl-models=2025.9.0 lomap2 kartograf=1.0.1 michellab::fkcombu konnektor -c conda-forge --yes && micromamba activate qligfep_new && python -m pip install -e .
```
</details>

Expand All @@ -58,7 +58,7 @@ micromamba create -n qligfep_new python=3.11 gfortran=11.3.0 openff-toolkit=0.17
Similar to Linux, [clone the repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository), enter the `Q` directory with `cd Q/`, create the environment and install:

``` bash
micromamba create -n qligfep_new python=3.11 gfortran=11.3.0 openff-toolkit=0.17.1 "openff-utilities>=0.1.12" openff-forcefields=2026.01.0 openmm=8.1.1 openff-nagl=0.5.4 openff-nagl-models=2025.9.0 lomap2 kartograf=1.0.1 davidararipe::kcombu_bss konnektor -c conda-forge --yes
micromamba create -n qligfep_new python=3.14 gfortran=11.3.0 openff-toolkit=0.18.1 "openff-utilities>=0.1.12" openff-forcefields=2026.01.0 openmm=8.4.0 openff-nagl=0.5.5 openff-nagl-models=2025.9.0 lomap2 kartograf=1.0.1 davidararipe::kcombu_bss konnektor -c conda-forge --yes
micromamba activate qligfep_new
python -m pip install joblib scipy tqdm
python -m pip install -e .
Expand All @@ -70,7 +70,7 @@ The `qprep` Fortran binary will be automatically compiled during installation.
<summary>To install everything in one line...</summary>

```bash
micromamba create -n qligfep_new python=3.11 gfortran=11.3.0 openff-toolkit=0.17.1 "openff-utilities>=0.1.12" openff-forcefields=2026.01.0 openmm=8.1.1 openff-nagl=0.5.4 openff-nagl-models=2025.9.0 lomap2 kartograf=1.0.1 davidararipe::kcombu_bss konnektor -c conda-forge --yes && micromamba activate qligfep_new && python -m pip install joblib scipy tqdm && python -m pip install -e .
micromamba create -n qligfep_new python=3.14 gfortran=11.3.0 openff-toolkit=0.18.1 "openff-utilities>=0.1.12" openff-forcefields=2026.01.0 openmm=8.4.0 openff-nagl=0.5.5 openff-nagl-models=2025.9.0 lomap2 kartograf=1.0.1 davidararipe::kcombu_bss konnektor -c conda-forge --yes && micromamba activate qligfep_new && python -m pip install joblib scipy tqdm && python -m pip install -e .
```
</details>

Expand Down
9 changes: 5 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ name = "QligFEP"
dynamic = ["version"]
description = "Python CLI designed to facilitate a robust and fast setup of free energy perturbation (FEP)."
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.10"
requires-python = ">=3.14"
license = { file = "LICENSE" }
# keywords = [""]
authors = [{ name = "Willem Jespers", email = "w.jespers@rug.nl" },
Expand All @@ -24,7 +24,7 @@ classifiers = [
"Intended Audience :: Science/Research",
"Topic :: Scientific/Engineering :: Chemistry",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10" # todo: might have other versions
"Programming Language :: Python :: 3.14" # todo: might have other versions
]
dependencies = [
"joblib",
Expand Down Expand Up @@ -69,15 +69,16 @@ repository = "https://github.com/qusers/qligfep"

[tool.black]
line-length = 110
target-version = ['py310']
target-version = ['py314']

[tool.isort]
profile = "black"
line_length = 110

[tool.ruff]
line-length = 110
indent-width = 4
target-version = 'py39'
target-version = 'py314'

[tool.ruff.lint]
select = [
Expand Down
2 changes: 1 addition & 1 deletion src/QligFEP/CLI/cog_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def _cog_sdf(self):
for i, center in enumerate(centers):
logger.debug(f"Ligand {i+1} center: {center}")

overall_center = [sum(x) / len(centers) for x in zip(*centers)]
overall_center = [sum(x) / len(centers) for x in zip(*centers, strict=False)]
return f"[{round(overall_center[0], 3):.3f} {round(overall_center[1], 3):.3f} {round(overall_center[2], 3):.3f}]"

def _calculate_center(self, coordinates):
Expand Down
15 changes: 7 additions & 8 deletions src/QligFEP/CLI/konnektor_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import argparse
import json
from pathlib import Path
from typing import Optional

import numpy as np
from kartograf import KartografAtomMapper, SmallMoleculeComponent
Expand Down Expand Up @@ -34,18 +33,18 @@ class KonnektorWrap:
def __init__(
self,
inp: str,
out: Optional[str] = None,
out: str | None = None,
network: str = "mst",
scorer: str = "combined",
restraint_method: str = "heavyatom_p",
processes: int = 1,
log_level: str = "info",
central_ligand: Optional[str] = None,
central_ligand: str | None = None,
n_redundancy: int = 2,
connectivity: int = 3,
separate_charges: bool = False,
charge_changes_score: float = 0.0,
exp_key: Optional[str] = None,
exp_key: str | None = None,
self_solve: bool = False,
):
self.inp = inp
Expand All @@ -66,7 +65,7 @@ def __init__(
self.out = self._parse_output(out)
self._sdf_dir = self._prepare_input()

def _parse_output(self, output: Optional[str]) -> str:
def _parse_output(self, output: str | None) -> str:
inpath = Path(self.inp)
if output is None:
if inpath.is_dir():
Expand Down Expand Up @@ -310,7 +309,7 @@ def run(self) -> dict:

return result

def _resolve_sdf_path(self) -> Optional[Path]:
def _resolve_sdf_path(self) -> Path | None:
"""Find the SDF file used as input."""
inp = Path(self.inp)
if inp.is_file() and inp.suffix == ".sdf":
Expand Down Expand Up @@ -354,7 +353,7 @@ def _mcs_rmsd(mol_a, mol_b, timeout=5):
conf_a = ha.GetConformer()
conf_b = hb.GetConformer()
sq_dists = []
for ia, ib in zip(match_a, match_b):
for ia, ib in zip(match_a, match_b, strict=False):
pa = conf_a.GetAtomPosition(ia)
pb = conf_b.GetAtomPosition(ib)
sq_dists.append((pa.x - pb.x) ** 2 + (pa.y - pb.y) ** 2 + (pa.z - pb.z) ** 2)
Expand Down Expand Up @@ -423,7 +422,7 @@ def _realign_to_neighbors(outlier_mol, neighbor_mols):
continue
conf_n = hn.GetConformer()
o_to_core = {oa: i for i, oa in enumerate(match_o)}
for oa, na in zip(match_o_nb, match_n_nb):
for oa, na in zip(match_o_nb, match_n_nb, strict=False):
if oa in o_to_core:
p = conf_n.GetAtomPosition(na)
core_positions.setdefault(o_to_core[oa], []).append(
Expand Down
5 changes: 2 additions & 3 deletions src/QligFEP/CLI/lomap_wrap_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import re
from multiprocessing import cpu_count
from pathlib import Path
from typing import Optional

import lomap
import numpy as np
Expand All @@ -22,10 +21,10 @@ class LomapWrap:
def __init__(
self,
inp: str,
out: Optional[str] = None,
out: str | None = None,
time=30,
verbose="info",
exp_key: Optional[str] = None,
exp_key: str | None = None,
**kwargs,
):
self.nodes = {}
Expand Down
3 changes: 1 addition & 2 deletions src/QligFEP/CLI/qligfep_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import datetime
import json
from pathlib import Path
from typing import Optional

from QligFEP import __version__

Expand All @@ -15,7 +14,7 @@
from .parser_base import parse_arguments


def main(args: Optional[argparse.Namespace] = None, **kwargs) -> None:
def main(args: argparse.Namespace | None = None, **kwargs) -> None:
"""Main function for qligfep_cli.py. Takes arguments from argparse and passes them
to QligFEP class. If no arguments are given, the function will use the keyword arguments
that are passed to it.
Expand Down
5 changes: 2 additions & 3 deletions src/QligFEP/CLI/setupFEP.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import shutil
import subprocess
from pathlib import Path
from typing import Optional

from ..IO import parse_qprep_total_charge
from ..logger import logger, setup_logger
Expand Down Expand Up @@ -76,7 +75,7 @@ def submit_command(command: str) -> None:
raise


def main(args: Optional[argparse.Namespace] = None, **kwargs) -> None:
def main(args: argparse.Namespace | None = None, **kwargs) -> None:
# setup the logger with the desired log level
setup_logger(level=args.log)

Expand Down Expand Up @@ -113,7 +112,7 @@ def main(args: Optional[argparse.Namespace] = None, **kwargs) -> None:

lig_pairs = ligpairs_from_json(args.json_map)
protein_dir = cwd / "2.protein"
for system, sys_dir in zip(systems, sys_directories):
for system, sys_dir in zip(systems, sys_directories, strict=False):
for lig1, lig2, same_charge in lig_pairs:

# For cross-charge water edges, look up the protein leg's total charge
Expand Down
6 changes: 3 additions & 3 deletions src/QligFEP/IO.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import stat
import subprocess
from pathlib import Path
from typing import NamedTuple, Optional
from typing import NamedTuple

import numpy as np
import pandas as pd
Expand Down Expand Up @@ -34,8 +34,8 @@ class SlurmRunInfo(NamedTuple):
"""Per-replicate run metadata parsed from one ``slurm*.out`` footer."""

runtime: str
seed: Optional[str]
replicate: Optional[str]
seed: str | None
replicate: str | None
status: str


Expand Down
66 changes: 0 additions & 66 deletions src/QligFEP/README.md

This file was deleted.

2 changes: 1 addition & 1 deletion src/QligFEP/analysis_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ def result_to_latex(res, latexify_each=False): # TODO: move this out of this me
(1.04, hori_height - spacing),
(1.04, hori_height - spacing * 2),
)
for txt_position, body in zip(txt_positions, text_body):
for txt_position, body in zip(txt_positions, text_body, strict=False):
plt.text(
*txt_position,
body,
Expand Down
3 changes: 1 addition & 2 deletions src/QligFEP/analyze_FEP.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import json
import os
from pathlib import Path
from typing import Optional

import numpy as np
import pandas as pd
Expand All @@ -31,7 +30,7 @@ def __init__(
system: str,
target_name: str,
mapping_json: str,
n_lambdas: Optional[int] = None,
n_lambdas: int | None = None,
allow_missing_edges: bool = False,
) -> None:
"""Initialize the FEP reader class. This class will store the FEP information inside
Expand Down
9 changes: 4 additions & 5 deletions src/QligFEP/analyze_neq.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import os
from collections import defaultdict
from pathlib import Path
from typing import Optional

import numpy as np
import pandas as pd
Expand Down Expand Up @@ -75,7 +74,7 @@ def dF_to_kcal(dF: float, work_units: str, temperature: float) -> float:
WORK_TAIL_BYTES = 256 * 1024


def read_final_work(log_path: str) -> Optional[float]:
def read_final_work(log_path: str) -> float | None:
"""Read the final accumulated switching work from a qdyn NEQ-mode log.

Each switch prints ``At step N, work accumulated was <W> ...`` every output interval;
Expand All @@ -101,7 +100,7 @@ def read_final_work(log_path: str) -> Optional[float]:
if "work accumulated" in line:
try:
return float(line.split()[6])
except (IndexError, ValueError):
except IndexError, ValueError:
logger.warning(f"Could not parse the work value in {log_path}")
return None
return None
Expand Down Expand Up @@ -264,7 +263,7 @@ def _dF_by_rep_from_works(by_rep: dict, beta: float, work_units: str, temperatur
continue
try:
result[rep] = dF_to_kcal(bar_delta_f(forward, reverse, beta), work_units, temperature)
except (ValueError, RuntimeError):
except ValueError, RuntimeError:
result[rep] = None
return result

Expand Down Expand Up @@ -573,7 +572,7 @@ def populate_mapping_json(df: pd.DataFrame, mapping_json: str, output_file: str)
logger.info(f"Injected NEQ ddG into {matched} edge(s); wrote {output_file}")


def _nan_to_none(value) -> Optional[float]:
def _nan_to_none(value) -> float | None:
"""Return ``None`` for a missing/NaN value (so it serializes to JSON null), else a float."""
return None if pd.isna(value) else float(value)

Expand Down
Loading