Finishes DiceInterface Refactoring and Continues Tests Implementation

This commit is contained in:
2023-04-24 12:15:51 -03:00
parent b869ee74fb
commit 9202331852
19 changed files with 629 additions and 190 deletions

View File

@@ -1,4 +1,4 @@
from diceplayer.shared.external.dice import Dice
from diceplayer.shared.interface.dice_interface import DiceInterface
from diceplayer.player import Player
from pathlib import Path
@@ -7,7 +7,6 @@ import logging
import pickle
import sys
__VERSION = "v0.0.1"
@@ -51,7 +50,7 @@ def main():
output_path = Path(args.outfile)
if output_path.exists():
output_path.rename(str(output_path)+".backup")
output_path.rename(str(output_path) + ".backup")
except Exception as err:
sys.exit(err)
@@ -66,5 +65,6 @@ def main():
player.start()
if __name__ == "__main__":
main()
main()

View File

@@ -1,3 +1,4 @@
from diceplayer.shared.config.step_dto import StepDTO
from diceplayer.shared.environment.atom import Atom
from diceplayer.shared.utils.dataclass_protocol import Dataclass
from diceplayer.shared.config.gaussian_dto import GaussianDTO
@@ -5,9 +6,9 @@ from diceplayer.shared.environment.molecule import Molecule
from diceplayer.shared.environment.system import System
from diceplayer.shared.utils.misc import weekday_date_time
from diceplayer.shared.config.player_dto import PlayerDTO
from diceplayer.shared.external.gaussian import Gaussian
from diceplayer.shared.interface.gaussian_interface import GaussianInterface
from diceplayer.shared.config.dice_dto import DiceDTO
from diceplayer.shared.external.dice import Dice
from diceplayer.shared.interface.dice_interface import DiceInterface
from dataclasses import fields
from pathlib import Path
@@ -39,8 +40,8 @@ class Player:
config_data.get("diceplayer")
)
self.gaussian = Gaussian(config_data.get("gaussian"))
self.dice = Dice(config_data.get("dice"))
self.gaussian = GaussianInterface(config_data.get("gaussian"))
self.dice = DiceInterface(config_data.get("dice"))
def start(self):
self.print_keywords()
@@ -50,6 +51,9 @@ class Player:
self.read_potentials()
# self.print_potentials()
self.dice_start(1)
self.dice_start(2)
def create_simulation_dir(self):
simulation_dir_path = Path(self.config.simulation_dir)
if simulation_dir_path.exists():
@@ -140,7 +144,7 @@ class Player:
self.dice.config.ljname
)
)
self.dice.combrule = combrule
self.dice.config.combrule = combrule
ntypes = ljdata.pop(0).split()[0]
if not ntypes.isdigit():
@@ -184,8 +188,21 @@ class Player:
Atom(**self.validate_atom_dict(i, j, new_atom))
)
def dice_start(self):
self.dice.start()
def dice_start(self, cycle: int):
self.dice.configure(
StepDTO(
ncores=self.config.ncores,
nprocs=self.config.nprocs,
simulation_dir=self.config.simulation_dir,
altsteps=self.config.altsteps,
molecule=self.system.molecule,
nmol=self.system.nmols,
)
)
self.dice.start(cycle)
self.dice.reset()
def gaussian_start(self):
self.gaussian.start()

View File

@@ -7,10 +7,11 @@ from typing import List
@dataclass
class DiceDTO(Dataclass):
"""
Data Transfer Object for the Dice configuration.
"""
ljname: str
outname: str
ncores: int
dens: float
nmol: List[int]
nstep: List[int]
@@ -20,6 +21,7 @@ class DiceDTO(Dataclass):
isave: int = 1000
press: float = 1.0
temp: float = 300.0
progname: str = "dice"
randominit: str = 'first'
def __post_init__(self):
@@ -44,7 +46,7 @@ class DiceDTO(Dataclass):
"Error: 'nmol' keyword not defined appropriately in config file"
)
if not isinstance(self.nstep, list):
if not isinstance(self.nstep, list) or len(self.nstep) not in (2, 3):
raise ValueError(
"Error: 'nstep' keyword not defined appropriately in config file"
)

View File

@@ -6,6 +6,9 @@ from dacite import from_dict
@dataclass
class GaussianDTO(Dataclass):
"""
Data Transfer Object for the Gaussian configuration.
"""
level: str
qmprog: str
keywords: str

View File

@@ -6,9 +6,13 @@ from dacite import from_dict
@dataclass
class PlayerDTO(Dataclass):
"""
Data Transfer Object for the player configuration.
"""
opt: bool
maxcyc: int
nprocs: int
ncores: int
qmprog: str = 'g16'
altsteps: int = 20000

View File

@@ -1,4 +1,5 @@
from diceplayer.shared.environment.molecule import Molecule
from diceplayer.shared.config.player_dto import PlayerDTO
from dataclasses import dataclass
from typing import List
@@ -6,13 +7,16 @@ from typing import List
@dataclass
class StepDTO:
nprocs: int = None
ncores: int = None
altsteps: int = None
switchcyc: int = None
opt: str = None
"""
Data Transfer Object for the step configuration.
"""
ncores: int
nprocs: int
simulation_dir: str
altsteps: int
nmol: List[int] = None
molecule: List[Molecule] = None
charges: List[float] = None
position: List[float] = None
position: List[float] = None

View File

@@ -1 +0,0 @@
from .__external import External

View File

@@ -1,53 +0,0 @@
from diceplayer.shared.config.dice_dto import DiceDTO
from diceplayer.shared.external import External
from multiprocessing import Process, connection
from setproctitle import setproctitle
import sys
class Dice(External):
__slots__ = ['config', 'step']
def __init__(self, data: dict):
self.config: DiceDTO = self.set_config(data)
@staticmethod
def set_config(data: dict) -> DiceDTO:
return DiceDTO.from_dict(data)
def configure(self, step: any):
self.step = step
def start(self, cycle: int):
procs = [
Process(target=self._simulation_process, args=(cycle, proc))
for proc in range(1, self.config.ncores+1)
]
for proc in procs:
proc.start()
connection.wait(p.sentinel for p in procs)
def reset(self):
del self.step
def _simulation_process(self, cycle: int, proc: int):
setproctitle(f"diceplayer-step{cycle:0d}-p{proc:0d}")
try:
self._make_proc_dir(cycle, proc)
self._make_dice_inputs(cycle, proc)
self._run_dice(cycle, proc)
except Exception as err:
sys.exit(err)
def _make_proc_dir(self, cycle, proc):
raise NotImplementedError
def _make_dice_inputs(self, cycle, proc):
raise NotImplementedError
def _run_dice(self, cycle, proc):
raise NotImplementedError

View File

@@ -0,0 +1 @@
from .__interface import Interface

View File

@@ -3,7 +3,7 @@ from diceplayer.shared.utils.dataclass_protocol import Dataclass
from abc import ABC, abstractmethod
class External(ABC):
class Interface(ABC):
__slots__ = [
'config'
]

View File

@@ -0,0 +1,388 @@
from __future__ import annotations
from diceplayer.shared.config.dice_dto import DiceDTO
from diceplayer.shared.config.step_dto import StepDTO
from diceplayer.shared.interface import Interface
from multiprocessing import Process, connection
from setproctitle import setproctitle
from typing import Final, TextIO
from pathlib import Path
import subprocess
import shutil
import random
import time
import sys
import os
DICE_END_FLAG: Final[str] = "End of simulation"
DICE_FLAG_LINE: Final[int] = -2
UMAANG3_TO_GCM3: Final[float] = 1.6605
MAX_SEED: Final[int] = 4294967295
class DiceInterface(Interface):
__slots__ = ['config', 'step']
title = "Diceplayer run"
def __init__(self, data: dict):
self.config: DiceDTO = self.set_config(data)
self.step: StepDTO | None = None
@staticmethod
def set_config(data: dict) -> DiceDTO:
return DiceDTO.from_dict(data)
def configure(self, step: any):
self.step = step
def start(self, cycle: int):
procs = []
sentinels = []
for proc in range(1, self.step.nprocs + 1):
p = Process(target=self._simulation_process, args=(cycle, proc))
p.start()
procs.append(p)
sentinels.append(p.sentinel)
while procs:
finished = connection.wait(sentinels)
for proc_sentinel in finished:
i = sentinels.index(proc_sentinel)
status = procs[i].exitcode
procs.pop(i)
sentinels.pop(i)
if status != 0:
for p in procs:
p.terminate()
sys.exit(status)
def reset(self):
del self.step
def _simulation_process(self, cycle: int, proc: int):
setproctitle(f"diceplayer-step{cycle:0d}-p{proc:0d}")
try:
self._make_proc_dir(cycle, proc)
self._make_dice_inputs(cycle, proc)
self._run_dice(cycle, proc)
except Exception as err:
sys.exit(err)
def _make_proc_dir(self, cycle, proc):
simulation_dir = Path(self.step.simulation_dir)
if not simulation_dir.exists():
simulation_dir.mkdir(parents=True)
proc_dir = Path(
simulation_dir,
f"step{cycle:02d}",
f"p{proc:02d}"
)
proc_dir.mkdir(parents=True, exist_ok=True)
def _make_dice_inputs(self, cycle, proc):
proc_dir = Path(
self.step.simulation_dir,
f"step{cycle:02d}",
f"p{proc:02d}"
)
self._make_potentials(proc_dir)
random.seed(self._make_dice_seed())
# This is logic is used to make the initial configuration file
# for the next cycle using the last.xyz file from the previous cycle.
if self.config.randominit == 'first' and cycle > 1:
last_xyz = Path(
self.step.simulation_dir,
f"step{(cycle - 1):02d}",
f"p{proc:02d}",
"last.xyz"
)
if not last_xyz.exists():
raise FileNotFoundError(f"File {last_xyz} not found.")
with open(last_xyz, 'r') as last_xyz_file:
self._make_init_file(proc_dir, last_xyz_file)
last_xyz_file.seek(0)
self.config.dens = self._new_density(last_xyz_file)
else:
self._make_nvt_ter(cycle, proc_dir)
if len(self.config.nstep) == 2:
self._make_nvt_eq(proc_dir)
elif len(self.config.nstep) == 3:
self._make_npt_ter(cycle, proc_dir)
self._make_npt_eq(proc_dir)
def _run_dice(self, cycle: int, proc: int):
working_dir = os.getcwd()
proc_dir = Path(
self.step.simulation_dir,
f"step{cycle:02d}",
f"p{proc:02d}"
)
os.chdir(proc_dir)
if not (self.config.randominit == 'first' and cycle > 1):
self.run_dice_file(cycle, proc, "NVT.ter")
if len(self.config.nstep) == 2:
self.run_dice_file(cycle, proc, "NVT.eq")
elif len(self.config.nstep) == 3:
self.run_dice_file(cycle, proc, "NPT.ter")
self.run_dice_file(cycle, proc, "NPT.eq")
os.chdir(working_dir)
xyz_file = Path(proc_dir, "phb.xyz")
last_xyz_file = Path(proc_dir, "last.xyz")
if xyz_file.exists():
shutil.copy(xyz_file, last_xyz_file)
else:
raise FileNotFoundError(f"File {xyz_file} not found.")
@staticmethod
def _make_dice_seed() -> int:
num = time.time()
num = (num - int(num)) * 1e6
num = int((num - int(num)) * 1e6)
return (os.getpid() * num) % (MAX_SEED + 1)
def _make_init_file(self, proc_dir: Path, last_xyz_file: TextIO):
xyz_lines = last_xyz_file.readlines()
nsites_mm = 0
for i in range(len(self.step.nmol)):
nsites_mm += self.step.nmol[i] * len(self.step.molecule[i].atom)
xyz_lines = xyz_lines[-nsites_mm:]
input_file = Path(proc_dir, self.config.outname + ".xy")
with open(input_file, 'w') as f:
for atom in self.step.molecule[0].atom:
f.write(
f"{atom.rx:>10.6f} {atom.ry:>10.6f} {atom.rz:>10.6f}\n"
)
for line in xyz_lines:
atom = line.split()
rx = float(atom[1])
ry = float(atom[2])
rz = float(atom[3])
f.write(f"{rx:>10.6f} {ry:>10.6f} {rz:>10.6f}\n")
f.write("$end")
def _new_density(self, last_xyz_file: TextIO):
last_xyz_lines = last_xyz_file.readlines()
box = last_xyz_lines[1].split()
volume = float(box[-3]) * float(box[-2]) * float(box[-1])
total_mass = 0
for i in range(len(self.step.molecule)):
total_mass += self.step.molecule[i].total_mass * self.step.nmol[i]
density = (total_mass / volume) * UMAANG3_TO_GCM3
return density
def _make_nvt_ter(self, cycle, proc_dir):
file = Path(proc_dir, "NVT.ter")
with open(file, 'w') as f:
f.write(f"title = {self.title} - NVT Thermalization\n")
f.write(f"ncores = {self.step.ncores}\n")
f.write(f"ljname = {self.config.ljname}\n")
f.write(f"outname = {self.config.outname}\n")
mol_string = " ".join(str(x) for x in self.config.nmol)
f.write(f"nmol = {mol_string}\n")
f.write(f"dens = {self.config.dens}\n")
f.write(f"temp = {self.config.temp}\n")
if self.config.randominit == "first" and cycle > 1:
f.write(f"init = yesreadxyz\n")
f.write(f"nstep = {self.step.altsteps}\n")
else:
f.write(f"init = yes\n")
f.write(f"nstep = {self.config.nstep[0]}\n")
f.write("vstep = 0\n")
f.write("mstop = 1\n")
f.write("accum = no\n")
f.write("iprint = 1\n")
f.write("isave = 0\n")
f.write("irdf = 0\n")
seed = int(1e6 * random.random())
f.write(f"seed = {seed}\n")
f.write(f"upbuf = {self.config.upbuf}")
def _make_nvt_eq(self, proc_dir):
file = Path(proc_dir, "NVT.eq")
with open(file, 'w') as f:
f.write(f"title = {self.title} - NVT Production\n")
f.write(f"ncores = {self.step.ncores}\n")
f.write(f"ljname = {self.config.ljname}\n")
f.write(f"outname = {self.config.outname}\n")
mol_string = " ".join(str(x) for x in self.config.nmol)
f.write(f"nmol = {mol_string}\n")
f.write(f"dens = {self.config.dens}\n")
f.write(f"temp = {self.config.temp}\n")
f.write("init = no\n")
f.write(f"nstep = {self.config.nstep[1]}\n")
f.write("vstep = 0\n")
f.write("mstop = 1\n")
f.write("accum = no\n")
f.write("iprint = 1\n")
f.write(f"isave = {self.config.isave}\n")
f.write(f"irdf = {10 * self.step.nprocs}\n")
seed = int(1e6 * random.random())
f.write("seed = {}\n".format(seed))
def _make_npt_ter(self, cycle, proc_dir):
file = Path(proc_dir, "NPT.ter")
with open(file, 'w') as f:
f.write(f"title = {self.title} - NPT Thermalization\n")
f.write(f"ncores = {self.step.ncores}\n")
f.write(f"ljname = {self.config.ljname}\n")
f.write(f"outname = {self.config.outname}\n")
mol_string = " ".join(str(x) for x in self.config.nmol)
f.write(f"nmol = {mol_string}\n")
f.write(f"press = {self.config.press}\n")
f.write(f"temp = {self.config.temp}\n")
if self.config.randominit == "first" and cycle > 1:
f.write("init = yesreadxyz\n")
f.write(f"dens = {self.config.dens:<8.4f}\n")
f.write(f"vstep = {int(self.step.altsteps / 5)}\n")
else:
f.write("init = no\n")
f.write(f"vstep = {int(self.config.nstep[1] / 5)}\n")
f.write("nstep = 5\n")
f.write("mstop = 1\n")
f.write("accum = no\n")
f.write("iprint = 1\n")
f.write("isave = 0\n")
f.write("irdf = 0\n")
seed = int(1e6 * random.random())
f.write(f"seed = {seed}\n")
def _make_npt_eq(self, proc_dir):
file = Path(proc_dir, "NPT.eq")
with open(file, 'w') as f:
f.write(f"title = {self.title} - NPT Production\n")
f.write(f"ncores = {self.step.ncores}\n")
f.write(f"ljname = {self.config.ljname}\n")
f.write(f"outname = {self.config.outname}\n")
mol_string = " ".join(str(x) for x in self.config.nmol)
f.write(f"nmol = {mol_string}\n")
f.write(f"press = {self.config.press}\n")
f.write(f"temp = {self.config.temp}\n")
f.write(f"nstep = 5\n")
f.write(f"vstep = {int(self.config.nstep[2] / 5)}\n")
f.write("init = no\n")
f.write("mstop = 1\n")
f.write("accum = no\n")
f.write("iprint = 1\n")
f.write(f"isave = {self.config.isave}\n")
f.write(f"irdf = {10 * self.step.nprocs}\n")
seed = int(1e6 * random.random())
f.write(f"seed = {seed}\n")
def _make_potentials(self, proc_dir):
fstr = "{:<3d} {:>3d} {:>10.5f} {:>10.5f} {:>10.5f} {:>10.6f} {:>9.5f} {:>7.4f}\n"
file = Path(proc_dir, self.config.ljname)
with open(file, 'w') as f:
f.write(f"{self.config.combrule}\n")
f.write(f"{len(self.step.nmol)}\n")
nsites_qm = len(self.step.molecule[0].atom)
f.write(f"{nsites_qm} {self.step.molecule[0].molname}\n")
for atom in self.step.molecule[0].atom:
f.write(
fstr.format(
atom.lbl,
atom.na,
atom.rx,
atom.ry,
atom.rz,
atom.chg,
atom.eps,
atom.sig,
)
)
for mol in self.step.molecule[1:]:
f.write(f"{len(mol.atom)} {mol.molname}\n")
for atom in mol.atom:
f.write(
fstr.format(
atom.lbl,
atom.na,
atom.rx,
atom.ry,
atom.rz,
atom.chg,
atom.eps,
atom.sig,
)
)
def run_dice_file(self, cycle: int, proc: int, file_name: str):
with open(Path(file_name), 'r') as infile, open(Path(file_name + ".out"), 'w') as outfile:
if shutil.which("bash") is not None:
exit_status = subprocess.call(
[
"bash",
"-c",
f"exec -a dice-step{cycle}-p{proc} {self.config.progname} < {infile.name} > {outfile.name}",
]
)
else:
exit_status = subprocess.call(
self.config.progname, stdin=infile, stdout=outfile
)
if exit_status != 0:
raise Exception(f"Dice process step{cycle:02d}-p{proc:02d} did not exit properly")
with open(Path(file_name + ".out"), 'r') as outfile:
flag = outfile.readlines()[DICE_FLAG_LINE].strip()
if flag != DICE_END_FLAG:
raise Exception(f"Dice process step{cycle:02d}-p{proc:02d} did not exit properly")

View File

@@ -1,8 +1,8 @@
from diceplayer.shared.config.gaussian_dto import GaussianDTO
from diceplayer.shared.external import External
from diceplayer.shared.interface import Interface
class Gaussian(External):
class GaussianInterface(Interface):
def __init__(self, data: dict):
self.config: GaussianDTO = self.set_config(data)