#!/usr/bin/env python
"""CPython AKMC / server dispatcher.
Entry points:
python -m eon.server
python -m eon
eon-server (console script → eon.server:main)
"""
from __future__ import annotations
import glob
import os
import shutil
from io import StringIO
from typing import Callable, Optional
from eon.config import ConfigClass
[docs]
def select_job_runner(job: str) -> Optional[Callable[[ConfigClass], None]]:
"""Return the registered server runner for *job*, or None for fallback.
Job modules are imported lazily so ``import eon.server`` stays light and
does not require the full AKMC stack until a job is actually selected.
"""
key = job.strip().lower()
if key == "akmc":
from eon import akmc
return akmc.main
if key in ("parallel_replica", "unbiased_parallel_replica"):
from eon import parallelreplica
return parallelreplica.main
if key == "basin_hopping":
from eon import basinhopping
return basinhopping.main
if key == "escape_rate":
from eon import escaperate
return escaperate.main
return None
def _warn_pos_con_in_potfiles(config: ConfigClass) -> None:
fnames = [os.path.basename(f) for f in glob.glob(os.path.join(config.path_pot, "*"))]
if "pos.con" in fnames:
print(
"WARNING: pos.con found in potfiles path. Are you sure you want this? "
"It will overwrite the pos.con in the calculation directory when your "
"jobs are being run."
)
def _fallback_single_job(config: ConfigClass) -> None:
"""Submit the current working directory as one client job via communicator."""
from eon import communicator
from eon import fileio as io
config.path_scratch = config.path_root
comm = communicator.get_communicator(config)
invariants = dict(io.load_potfiles(config.path_pot))
job: dict = {}
files = [f for f in os.listdir(".") if os.path.isfile(f)]
for f in files:
with open(f) as fh:
if len(f.split(".")) > 1:
f_passed = f.split(".")[0] + "." + f.split(".")[1]
job[f_passed] = StringIO(fh.read())
job["id"] = "output"
if os.path.isdir("output_old"):
shutil.rmtree("output_old")
if os.path.isdir("output"):
shutil.move("output", "output_old")
comm.submit_jobs([job], invariants)
[docs]
def server(config: ConfigClass | None = None) -> None:
"""Initialize config and dispatch to the configured main job.
*config* defaults to the process-edge module singleton for CLI
compatibility; callers should prefer constructing ``ConfigClass()`` and
passing it explicitly.
"""
if config is None:
config = ConfigClass()
config.init()
_warn_pos_con_in_potfiles(config)
job = config.main_job.lower()
runner = select_job_runner(job)
if runner is not None:
runner(config)
else:
_fallback_single_job(config)
[docs]
def main() -> None:
"""Console-script entry point for ``eon-server``."""
server()
if __name__ == "__main__":
main()