"""
A simple tool to calculate the HADDOCK-score of a complex.
You can pass to the command-line any parameter accepted by the `emscoring`
module. For this, use the ``-p`` option writing the name of the parameters
followed by the desired value. Write booleans with capital letter.
Use the ``haddock3-cfg`` command-line to obtain the list of parameters for
the ``emscoring`` module.
Usage::
haddock3-score complex.pdb
haddock3-score complex.pdb -p nemsteps 50
haddock3-score complex.pdb -p nemsteps 50 w_air 1
haddock3-score complex.pdb -p nemsteps 50 w_air 1 electflag True
"""
import argparse
import sys
import tempfile
from haddock.core.typing import (
Any,
ArgumentParser,
Callable,
FilePath,
Namespace,
)
from haddock.libs.libcli import _ParamsToDict
ap = argparse.ArgumentParser(
prog="haddock3-score",
description=__doc__,
)
ap.add_argument("pdb_file", help="Input PDB file")
ap.add_argument(
"--run_dir",
default="haddock-score-client",
type=str,
required=False,
help="Run directory name.",
)
ap.add_argument(
"--full",
action="store_true",
help="Print all energy components",
)
ap.add_argument(
"--outputpdb",
action="store_true",
help="Save the output PDB file (minimized structure)",
)
ap.add_argument(
"--outputpsf",
action="store_true",
help="Save the output PSF file (topology)",
)
ap.add_argument(
"-k" "--keep-all",
dest="keep_all",
action="store_true",
help="Keep the whole run folder.",
)
ap.add_argument(
"-p",
"--other-params",
dest="other_params",
help=(
"Any other parameter of the `emscoring` module. "
"For example: -p nemsteps 1000. You can give any number of "
"parameters."
),
action=_ParamsToDict,
default={},
nargs="*",
)
def _ap() -> ArgumentParser:
return ap
[docs]
def load_args(ap: ArgumentParser) -> Namespace:
"""Load argument parser args."""
return ap.parse_args()
[docs]
def cli(ap: ArgumentParser, main: Callable[..., None]) -> None:
"""Command-line interface entry point."""
cmd = vars(load_args(ap))
kwargs = cmd.pop("other_params")
main(**cmd, **kwargs)
[docs]
def maincli() -> None:
"""Execute main client."""
cli(ap, main)
[docs]
def main(
pdb_file: FilePath,
run_dir: FilePath,
full: bool = False,
outputpdb: bool = False,
outputpsf: bool = False,
keep_all: bool = False,
**kwargs: Any,
) -> None:
"""
Calculate the score of a complex using the ``emscoring`` module.
Parameters
----------
pdb_file : str or pathlib.Path
The path to the PDB containing the complex.
full : bool
Print all energy components.
outputpdb : bool
Save the PDB file resulting from the scoring calculation.
outputpsf : bool
Save the PSF file resulting from the scoring calculation; this
is the CNS topology created before running the calculation.
keep_all : bool
Keep the whole temporary run folder. ``haddock3-score`` creates
a temporary run folder where the calculations are performed. If
``keep_all`` is True, this folder is **not** deleted after when
the calculation finishes.
kwargs : any
Any additional arguments that will be passed to the ``emscoring``
module.
"""
import os
import logging
import shutil
from contextlib import suppress
from pathlib import Path
from haddock import log
from haddock.gear.haddockmodel import HaddockModel
from haddock.gear.yaml2cfg import read_from_yaml_config
from haddock.gear.zerofill import zero_fill
from haddock.libs.libio import working_directory
from haddock.libs.libworkflow import WorkflowManager
from haddock.modules.scoring.emscoring import DEFAULT_CONFIG
log.setLevel(logging.ERROR)
input_pdb = Path(pdb_file).resolve()
if not input_pdb.exists():
sys.exit(f"* ERROR * Input PDB file {str(input_pdb)!r} does not exist")
# config all parameters are correctly spelled.
default_emscoring = read_from_yaml_config(DEFAULT_CONFIG)
ems_dict = default_emscoring.copy()
n_warnings = 0
for param, value in kwargs.items():
if param not in default_emscoring:
sys.exit(
f"* ERROR * Parameter {param!r} is not a "
f"valid `emscoring` parameter.{os.linesep}"
f"Valid emscoring parameters are: {', '.join(sorted(default_emscoring))}"
)
if value != default_emscoring[param]:
print(
f"* ATTENTION * Value ({value}) of parameter {param} different from default ({default_emscoring[param]})"
) # noqa:E501
# get the type of default value
default_type = type(default_emscoring[param])
# convert the value to the same type
if default_type == bool:
if value.lower() not in ["true", "false"]:
sys.exit(f"* ERROR * Boolean parameter {param} should be True or False")
value = value.lower() == "true"
else:
value = default_type(value)
ems_dict[param] = value
n_warnings += 1
if n_warnings != 0:
print(
"* ATTENTION * Non-default parameter values were used. "
"They should be properly reported if the output "
"data are used for publication."
)
print(f"used emscoring parameters: {ems_dict}")
# create run directory
run_dir = Path(run_dir)
with suppress(FileNotFoundError):
shutil.rmtree(run_dir)
run_dir.mkdir()
zero_fill.set_zerofill_number(2)
# create temporary file
with tempfile.NamedTemporaryFile(prefix=input_pdb.stem, suffix=".pdb") as tmp:
# create a copy of the input pdb
input_pdb_copy = Path(tmp.name)
shutil.copy(input_pdb, input_pdb_copy)
params = {
"topoaa": {"molecules": [input_pdb_copy]},
"emscoring": ems_dict,
}
print("> starting calculations...")
# run workflow
with working_directory(run_dir):
workflow = WorkflowManager(
workflow_params=params,
start=0,
run_dir=run_dir,
)
workflow.run()
minimized_mol = Path(run_dir, "1_emscoring", "emscoring_1.pdb")
haddock_score_component_dic = HaddockModel(minimized_mol).energies
vdw = haddock_score_component_dic["vdw"]
elec = haddock_score_component_dic["elec"]
desolv = haddock_score_component_dic["desolv"]
air = haddock_score_component_dic["air"]
bsa = haddock_score_component_dic["bsa"]
# emscoring is equivalent to itw
haddock_score_itw = (
ems_dict["w_vdw"] * vdw
+ ems_dict["w_elec"] * elec
+ ems_dict["w_desolv"] * desolv
+ ems_dict["w_air"] * air
+ ems_dict["w_bsa"] * bsa
)
print(
"> HADDOCK-score ="
f" ({ems_dict['w_vdw']} * vdw)"
f" + ({ems_dict['w_elec']} * elec)"
f" + ({ems_dict['w_desolv']} * desolv)"
f" + ({ems_dict['w_air']} * air)"
f" + ({ems_dict['w_bsa']} * bsa)"
)
print(f"> HADDOCK-score (emscoring) = {haddock_score_itw:.4f}")
if full:
print(f"> vdw={vdw},elec={elec},desolv={desolv},air={air},bsa={bsa}")
if outputpdb:
outputpdb_name = Path(f"{input_pdb.stem}_hs.pdb")
print(f"> writing {outputpdb_name}")
shutil.copy(
Path(run_dir, "1_emscoring", "emscoring_1.pdb"),
outputpdb_name,
)
if outputpsf:
outputpsf_name = Path(f"{input_pdb.stem}_hs.psf")
print(f"> writing {outputpsf_name}")
shutil.copy(
Path(run_dir, "0_topoaa", f"{input_pdb_copy.stem}_haddock.psf"),
outputpsf_name,
)
if not keep_all:
shutil.rmtree(run_dir)
else:
print(
'The folder where the calculations were performed was kept.'
f' See folder: {run_dir}'
)
if __name__ == "__main__":
sys.exit(maincli()) # type: ignore