Skip to content
Merged
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Changelog

- 2026-02-20: Add fallback routine to use `Scheduler` if the GRID is not available
- 2025-12-15: Added missing NGA glycan parameters - Issue #1462
- 2025-11-25: Simplify the use of multiple ambig archives
- 2025-11-19: Corrected CNS verbosity settings - Issue #1446
- 2025-11-19: Added zinc-finger docking example, related to Issue #1445
- 2025-11-19: Corrected flexref module for Issue #1445
- 2025-11-19: Corrected flexref module for Issue #1445
- 2025-11-17: Added possibility to use alascan with ligands - Issue #1411
- 2025-10-22: Allow the definition of chain combinations to be used for scoring - Issue #1414
- 2025-09-11: Added `grid` mode
Expand Down
30 changes: 19 additions & 11 deletions src/haddock/modules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from haddock.gear.parameters import config_mandatory_general_parameters
from haddock.gear.yaml2cfg import read_from_yaml_config, find_incompatible_parameters
from haddock.libs.libhpc import HPCScheduler
from haddock.libs.libgrid import GRIDScheduler
from haddock.libs.libgrid import GRIDScheduler, ping_dirac
from haddock.libs.libio import folder_exists, working_directory
from haddock.libs.libmpi import MPIScheduler
from haddock.libs.libontology import ModuleIO, PDBFile
Expand Down Expand Up @@ -65,9 +65,7 @@
# modules will use these parameters. It is the responsibility of the module to
# extract the parameters it needs.
# the config file is in modules/defaults.cfg
non_mandatory_general_parameters_defaults = read_from_yaml_config(
modules_defaults_path
) # noqa : E501
non_mandatory_general_parameters_defaults = read_from_yaml_config(modules_defaults_path) # noqa : E501

incompatible_defaults_params = find_incompatible_parameters(modules_defaults_path)

Expand Down Expand Up @@ -187,9 +185,7 @@ def update_params(
>>> m.update_params(...)
"""
if update_from_cfg_file and params:
_msg = (
"You can not provide both `update_from_cfg_file` " "and key arguments."
)
_msg = "You can not provide both `update_from_cfg_file` and key arguments."
raise TypeError(_msg)

if update_from_cfg_file:
Expand Down Expand Up @@ -305,7 +301,7 @@ def export_io_models(self, faulty_tolerance: float = 0.0) -> None:
if detected_errors := find_all_cns_errors(self.path):
_msg += linesep
for error in detected_errors.values():
_msg += f'{str(error["error"])}{linesep}'
_msg += f"{str(error['error'])}{linesep}"
# Show final error message
self.finish_with_error(_msg)

Expand Down Expand Up @@ -399,7 +395,7 @@ def _fill_emptypaths(self) -> None:
def get_engine(
mode: str,
params: dict[Any, Any],
) -> partial[Union[HPCScheduler, Scheduler, MPIScheduler]]:
) -> partial[Union[HPCScheduler, Scheduler, MPIScheduler, GRIDScheduler]]:
"""
Create an engine to run the jobs.

Expand Down Expand Up @@ -433,10 +429,22 @@ def get_engine(
return partial(MPIScheduler, ncores=params["ncores"]) # type: ignore

elif mode == "grid":
return partial(GRIDScheduler, params=params) # type: ignore
# `grid` mode should only be used IF the grid is reachable,
# if not it should fallback to `local`
grid_available = ping_dirac()

if grid_available:
return partial(GRIDScheduler, params=params) # type: ignore
else:
log.warning("GRID is not available, activating fallback using `mode=local`")
return partial( # type: ignore
Scheduler,
ncores=params["ncores"],
max_cpus=params["max_cpus"],
)

else:
available_engines = ("batch", "local", "mpi")
available_engines = ("batch", "local", "mpi", "grid")
raise ValueError(
f"Scheduler `mode` {mode!r} not recognized. "
f"Available options are {', '.join(available_engines)}"
Expand Down
49 changes: 49 additions & 0 deletions tests/test_modules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from functools import partial
from unittest.mock import patch

from haddock.libs.libgrid import GRIDScheduler
from haddock.libs.libhpc import HPCScheduler
from haddock.libs.libmpi import MPIScheduler
from haddock.libs.libparallel import Scheduler
from haddock.modules import get_engine


def test_get_engine_grid_unavailable():
with patch("haddock.modules.ping_dirac", return_value=False):
engine = get_engine(mode="grid", params={"ncores": 1, "max_cpus": 4})
assert isinstance(engine, partial)
assert engine.func is Scheduler


def test_get_engine_grid_available():
with patch("haddock.modules.ping_dirac", return_value=True):
engine = get_engine(mode="grid", params={"ncores": 1, "max_cpus": 4})
assert isinstance(engine, partial)
assert engine.func is GRIDScheduler


def test_get_engine_local():
engine = get_engine(mode="local", params={"ncores": 2, "max_cpus": 8})
assert isinstance(engine, partial)
assert engine.func is Scheduler
assert engine.keywords["ncores"] == 2
assert engine.keywords["max_cpus"] == 8


def test_get_engine_batch():
engine = get_engine(
mode="batch",
params={"queue": "short", "queue_limit": 100, "concat": 5},
)
assert isinstance(engine, partial)
assert engine.func is HPCScheduler
assert engine.keywords["target_queue"] == "short"
assert engine.keywords["queue_limit"] == 100
assert engine.keywords["concat"] == 5


def test_get_engine_mpi():
engine = get_engine(mode="mpi", params={"ncores": 4})
assert isinstance(engine, partial)
assert engine.func is MPIScheduler
assert engine.keywords["ncores"] == 4