diff --git a/CHANGELOG.md b/CHANGELOG.md index e958fec0c1..68b752917f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/haddock/modules/__init__.py b/src/haddock/modules/__init__.py index f385e7cead..4d58043515 100644 --- a/src/haddock/modules/__init__.py +++ b/src/haddock/modules/__init__.py @@ -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 @@ -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) @@ -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: @@ -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) @@ -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. @@ -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)}" diff --git a/tests/test_modules.py b/tests/test_modules.py new file mode 100644 index 0000000000..fa87d20388 --- /dev/null +++ b/tests/test_modules.py @@ -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