Merge remote-tracking branch 'origin/main' into devel

This commit is contained in:
2023-06-11 15:39:16 -03:00
56 changed files with 4081 additions and 3173 deletions

30
.github/workflows/python-tests.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
name: Python application
on:
push
permissions:
contents: read
jobs:
run-unitest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: "3.8"
- name: Install dependencies
run: |
python -m pip install --upgrade pip poetry;
[ -f pyproject.toml ] && poetry install;
- name: Test with unittest
run: |
poetry run python -m unittest -v

20
.gitignore vendored Normal file
View File

@@ -0,0 +1,20 @@
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
.vscode/settings.json
*.ljc
*.log
*.log.backup
simfiles/*
.vscode/*
.idea/*
*.pkl
*.xyz
dist/

17
Pipfile
View File

@@ -1,17 +0,0 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
numpy = "*"
pickle5 = "*"
argparse = "*"
pyinstaller = "*"
setproctitle = "*"
pyyaml = "*"
[dev-packages]
[requires]
python_version = "3.10"

24
control.example.yml Normal file
View File

@@ -0,0 +1,24 @@
diceplayer:
opt: no
mem: 24
maxcyc: 5
ncores: 5
nprocs: 4
qmprog: 'g16'
lps: no
ghosts: no
altsteps: 2000
dice:
nmol: [1, 100]
dens: 1.5
nstep: [2000, 3000]
isave: 1000
outname: 'phb'
progname: '~/.local/bin/dice'
ljname: 'phb.ljc'
randominit: 'always'
gaussian:
qmprog: 'g16'
level: 'MP2/aug-cc-pVDZ'

View File

@@ -1,23 +0,0 @@
diceplayer:
maxcyc: 3
opt: no
nprocs: 4
qmprog: 'g16'
lps: no
ghosts: no
altsteps: 20000
dice:
ncores: 3
nmol: [1, 50]
dens: 0.75
nstep: [2000, 3000]
isave: 1000
ljname: 'phb.ljc'
outname: 'phb'
randominit: 'always'
gaussian:
qmprog: 'g16'
level: 'MP2/aug-cc-pVDZ'
keywords: 'freq'

View File

@@ -1,244 +0,0 @@
from diceplayer.DPpack.Utils.PTable import *
from diceplayer.DPpack.Utils.Misc import *
from diceplayer.DPpack.Environment.Molecule import ANG2BOHR, BOHR2ANG, Molecule
from diceplayer.DPpack.Environment.Atom import Atom
from typing import IO, Final, Tuple, List, TextIO
from numpy import linalg
import numpy as np
from copy import deepcopy
import sys, math
import sys
import math
BOHR2ANG: Final[float] = 0.52917721092
ANG2BOHR: Final[float] = 1 / BOHR2ANG
class System:
"""
System class declaration. This class is used throughout the DicePlayer program to represent the system containing the molecules.
Atributes:
molecule (List[Molecule]): List of molecules of the system
nmols (List[int]): List of number of molecules in the system
"""
def __init__(self) -> None:
"""
Initializes a empty system object that will be populated afterwards
"""
self.molecule: List[Molecule] = []
self.nmols: List[int] = []
def add_type(self, nmols: int, m: Molecule) -> None:
"""
Adds a new molecule type to the system
Args:
nmols (int): Number of molecules of the new type in the system
m (Molecule): The instance of the new type of molecule
"""
self.molecule.append(m)
self.nmols.append(nmols)
def center_of_mass_distance(self, a: Molecule, b: Molecule) -> float:
"""
Calculates the distance between the center of mass of two molecules
Args:
a (Molecule): First Molecule Instance
b (Molecule): Second Molecule Instance
Returns:
float: module of the distance between the two center of masses
"""
com1 = self.molecule[a].center_of_mass()
com2 = self.molecule[b].center_of_mass()
dx = com1[0] - com2[0]
dy = com1[1] - com2[1]
dz = com1[2] - com2[2]
distance = math.sqrt(dx**2 + dy**2 + dz**2)
return distance
def rmsd_fit(self, p_index: int, r_index: int) -> Tuple[float, Molecule]:
projecting_mol = self.molecule[p_index]
reference_mol = self.molecule[r_index]
if len(projecting_mol.atom) != len(reference_mol.atom):
sys.exit(
"Error in RMSD fit procedure: molecules have different number of atoms"
)
dim = len(projecting_mol.atom)
new_projecting_mol = deepcopy(projecting_mol)
new_reference_mol = deepcopy(reference_mol)
new_projecting_mol.center_of_mass_to_origin()
new_reference_mol.center_of_mass_to_origin()
x = []
y = []
for atom in new_projecting_mol.atom:
x.extend([atom.rx, atom.ry, atom.rz])
for atom in new_reference_mol.atom:
y.extend([atom.rx, atom.ry, atom.rz])
x = np.array(x).reshape(dim, 3)
y = np.array(y).reshape(dim, 3)
r = np.matmul(y.T, x)
rr = np.matmul(r.T, r)
try:
evals, evecs = linalg.eigh(rr)
except:
sys.exit("Error: diagonalization of RR matrix did not converge")
a1 = evecs[:, 2].T
a2 = evecs[:, 1].T
a3 = np.cross(a1, a2)
A = np.array([a1[0], a1[1], a1[2], a2[0], a2[1], a2[2], a3[0], a3[1], a3[2]])
A = A.reshape(3, 3)
b1 = np.matmul(r, a1.T).T # or np.dot(r, a1)
b1 /= linalg.norm(b1)
b2 = np.matmul(r, a2.T).T # or np.dot(r, a2)
b2 /= linalg.norm(b2)
b3 = np.cross(b1, b2)
B = np.array([b1[0], b1[1], b1[2], b2[0], b2[1], b2[2], b3[0], b3[1], b3[2]])
B = B.reshape(3, 3).T
rot_matrix = np.matmul(B, A)
x = np.matmul(rot_matrix, x.T).T
rmsd = 0
for i in range(dim):
rmsd += (
(x[i, 0] - y[i, 0]) ** 2
+ (x[i, 1] - y[i, 1]) ** 2
+ (x[i, 2] - y[i, 2]) ** 2
)
rmsd = math.sqrt(rmsd / dim)
for i in range(dim):
new_projecting_mol.atom[i].rx = x[i, 0]
new_projecting_mol.atom[i].ry = x[i, 1]
new_projecting_mol.atom[i].rz = x[i, 2]
reference_mol.center_of_mass()
projected_mol = new_projecting_mol.translate(reference_mol.com)
return rmsd, projected_mol
def update_molecule(self, position: np.ndarray, fh: TextIO) -> None:
"""Updates the position of the molecule in the Output file
Args:
position (np.ndarray): numpy position vector
fh (TextIO): Output file
"""
position_in_ang = (position * BOHR2ANG).tolist()
self.add_type(self.nmols[0], deepcopy(self.molecule[0]))
for atom in self.molecule[-1].atom:
atom.rx = position_in_ang.pop(0)
atom.ry = position_in_ang.pop(0)
atom.rz = position_in_ang.pop(0)
rmsd, self.molecule[0] = self.rmsd_fit(-1, 0)
self.molecule.pop(-1)
fh.write("\nProjected new conformation of reference molecule with RMSD fit\n")
fh.write("RMSD = {:>8.5f} Angstrom\n".format(rmsd))
def nearest_image(
self,
index_r: int,
index_m: int,
lx: float,
ly: float,
lz: float,
criterium=None,
) -> Tuple[float, Molecule]:
if criterium in None:
criterium = "com"
if criterium != "com" and criterium != "min":
sys.exit("Error in value passed to function nearest_image")
min_dist = 1e20
for i in range(-1, 2):
for j in range(-1, 2):
for k in range(-1, 2):
tr_vector = [i * lx, j * ly, k * lz]
self.add_molecule(self.molecule[index_m].translate(tr_vector))
if criterium == "com":
dist = self.center_of_mass_distance(index_r, -1)
else:
dist = self.minimum_distance(index_r, -1)
if dist < min_dist:
min_dist = dist
nearestmol = deepcopy(self.molecule[-1])
self.molecule.pop(-1)
return min_dist, nearestmol
def print_geom(self, cycle: int, fh: TextIO) -> None:
"""
Print the geometry of the molecule in the Output file
Args:
cycle (int): Number of the cycle
fh (TextIO): Output file
"""
fh.write("Cycle # {}\n".format(cycle))
fh.write("Number of site: {}\n".format(len(self.molecule[0].atom)))
for atom in self.molecule[0].atom:
symbol = atomsymb[atom.na]
fh.write(
"{:<2s} {:>10.6f} {:>10.6f} {:>10.6f}\n".format(
symbol, atom.rx, atom.ry, atom.rz
)
)
def printChargesAndDipole(self, cycle: int, fh: TextIO) -> None:
"""
Print the charges and dipole of the molecule in the Output file
Args:
cycle (int): Number of the cycle
fh (TextIO): Output file
"""
fh.write("Cycle # {}\n".format(cycle))
fh.write("Number of site: {}\n".format(len(self.molecule[0].atom)))
chargesAndDipole = self.molecule[0].charges_and_dipole()
fh.write(
"{:>10.6f} {:>10.6f} {:>10.6f} {:>10.6f} {:>10.6f}\n".format(
chargesAndDipole[0], chargesAndDipole[1], chargesAndDipole[2], chargesAndDipole[3], chargesAndDipole[4]
)
)

View File

@@ -1,753 +0,0 @@
import os
import shutil
import subprocess
import sys
from multiprocessing import Process, connection
from typing import Final, List, TextIO
import setproctitle
from diceplayer.DPpack.Utils.Misc import *
from diceplayer.DPpack.Utils.PTable import *
from diceplayer.DPpack.Utils.StepDTO import StepDTO
from diceplayer.DPpack.Utils.Validations import NotNull
from numpy import random
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 Dice:
title = "Diceplayer run"
progname = "dice"
nprocs: int = None
randominit = "first"
combrule = "*"
temp = 300.0
press = 1.0
isave = 1000
dens = None
ljname = None
outname = None
nmol: List[int] = None
nstep: List[int] = None
upbuf = 360
def __init__(self, infile: TextIO, outfile: TextIO) -> None:
self.infile = infile
self.outfile = outfile
@NotNull(requiredArgs=["ncores", "nmol", "dens", "nstep", "ljname", "outname"])
def updateKeywords(self, **data):
self.__dict__.update(**data)
def __new_density(self, cycle: int, proc: int) -> float:
sim_dir = "simfiles"
step_dir = "step{:02d}".format(cycle - 1)
proc_dir = "p{:02d}".format(proc)
path = sim_dir + os.sep + step_dir + os.sep + proc_dir
file = path + os.sep + "last.xyz"
if not os.path.isfile(file):
sys.exit(
"Error: cannot find the xyz file {} in main directory".format(file)
)
try:
with open(file) as fh:
xyzfile = fh.readlines()
except:
sys.exit("Error: cannot open file {}".format(file))
box = xyzfile[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 __print_last_config(self, cycle: int, proc: int) -> None:
sim_dir = "simfiles"
step_dir = "step{:02d}".format(cycle)
proc_dir = "p{:02d}".format(proc)
path = sim_dir + os.sep + step_dir + os.sep + proc_dir
file = path + os.sep + "phb.xyz"
if not os.path.isfile(file):
sys.exit("Error: cannot find the xyz file {}".format(file))
try:
with open(file) as fh:
xyzfile = fh.readlines()
except:
sys.exit("Error: cannot open file {}".format(file))
nsites = len(self.step.molecule[0].atom) * self.step.nmol[0]
for i in range(1, len(self.step.nmol)):
nsites += self.step.nmol[i] * len(self.step.molecule[i].atom)
nsites += 2
nsites *= -1
xyzfile = xyzfile[nsites:]
file = path + os.sep + "last.xyz"
fh = open(file, "w")
for line in xyzfile:
fh.write(line)
def __make_dice_inputs(self, cycle: int, proc: int) -> None:
sim_dir = "simfiles"
step_dir = "step{:02d}".format(cycle)
proc_dir = "p{:02d}".format(proc)
path = sim_dir + os.sep + step_dir + os.sep + proc_dir
num = time.time()
num = (num - int(num)) * 1e6
num = int((num - int(num)) * 1e6)
random.seed((os.getpid() * num) % (MAX_SEED + 1))
if self.randominit == "first" and cycle > self.step.initcyc:
last_step_dir = "step{:02d}".format(cycle - 1)
last_path = sim_dir + os.sep + last_step_dir + os.sep + proc_dir
xyzfile = last_path + os.sep + "last.xyz"
self.__make_init_file(path, xyzfile)
if len(self.nstep) == 2:
self.__make_nvt_ter(cycle, path)
self.__make_nvt_eq(path)
elif len(self.nstep) == 3:
if self.randominit == "first" and cycle > self.step.initcyc:
self.dens = self.__new_density(cycle, proc)
else:
self.__make_nvt_ter(cycle, path)
self.__make_npt_ter(cycle, path)
self.__make_npt_eq(path)
else:
sys.exit("Error: bad number of entries for 'nstep'")
self.__make_potential(path)
def __make_nvt_ter(self, cycle: int, path: str) -> None:
file = path + os.sep + "NVT.ter"
try:
fh = open(file, "w")
except:
sys.exit("Error: cannot open file {}".format(file))
fh.write("title = {} - NVT Thermalization\n".format(self.title))
fh.write("ncores = {}\n".format(self.ncores))
fh.write("ljname = {}\n".format(self.ljname))
fh.write("outname = {}\n".format(self.outname))
string = " ".join(str(x) for x in self.nmol)
fh.write("nmol = {}\n".format(string))
fh.write("dens = {}\n".format(self.dens))
fh.write("temp = {}\n".format(self.temp))
if self.randominit == "first" and cycle > self.step.initcyc:
fh.write("init = yesreadxyz\n")
fh.write("nstep = {}\n".format(self.step.altsteps))
else:
fh.write("init = yes\n")
fh.write("nstep = {}\n".format(self.nstep[0]))
fh.write("vstep = 0\n")
fh.write("mstop = 1\n")
fh.write("accum = no\n")
fh.write("iprint = 1\n")
fh.write("isave = 0\n")
fh.write("irdf = 0\n")
seed = int(1e6 * random.random())
fh.write("seed = {}\n".format(seed))
fh.write("upbuf = {}".format(self.upbuf))
fh.close()
def __make_nvt_eq(self, path: str) -> None:
file = path + os.sep + "NVT.eq"
try:
fh = open(file, "w")
except:
sys.exit("Error: cannot open file {}".format(file))
fh.write("title = {} - NVT Production\n".format(self.title))
fh.write("ncores = {}\n".format(self.ncores))
fh.write("ljname = {}\n".format(self.ljname))
fh.write("outname = {}\n".format(self.outname))
string = " ".join(str(x) for x in self.nmol)
fh.write("nmol = {}\n".format(string))
fh.write("dens = {}\n".format(self.dens))
fh.write("temp = {}\n".format(self.temp))
fh.write("init = no\n")
fh.write("nstep = {}\n".format(self.nstep[1]))
fh.write("vstep = 0\n")
fh.write("mstop = 1\n")
fh.write("accum = no\n")
fh.write("iprint = 1\n")
fh.write("isave = {}\n".format(self.isave))
fh.write("irdf = {}\n".format(10 * self.step.nprocs))
seed = int(1e6 * random.random())
fh.write("seed = {}\n".format(seed))
fh.close()
def __make_npt_ter(self, cycle: int, path: str) -> None:
file = path + os.sep + "NPT.ter"
try:
fh = open(file, "w")
except:
sys.exit("Error: cannot open file {}".format(file))
fh.write("title = {} - NPT Thermalization\n".format(self.title))
fh.write("ncores = {}\n".format(self.ncores))
fh.write("ljname = {}\n".format(self.ljname))
fh.write("outname = {}\n".format(self.outname))
string = " ".join(str(x) for x in self.nmol)
fh.write("nmol = {}\n".format(string))
fh.write("press = {}\n".format(self.press))
fh.write("temp = {}\n".format(self.temp))
if self.randominit == "first" and cycle > self.step.initcyc:
fh.write("init = yesreadxyz\n")
fh.write("dens = {:<8.4f}\n".format(self.dens))
fh.write("vstep = {}\n".format(int(self.step.altsteps / 5)))
else:
fh.write("init = no\n")
fh.write("vstep = {}\n".format(int(self.nstep[1] / 5)))
fh.write("nstep = 5\n")
fh.write("mstop = 1\n")
fh.write("accum = no\n")
fh.write("iprint = 1\n")
fh.write("isave = 0\n")
fh.write("irdf = 0\n")
seed = int(1e6 * random.random())
fh.write("seed = {}\n".format(seed))
fh.close()
def __make_npt_eq(self, path: str) -> None:
file = path + os.sep + "NPT.eq"
try:
fh = open(file, "w")
except:
sys.exit("Error: cannot open file {}".format(file))
fh.write("title = {} - NPT Production\n".format(self.title))
fh.write("ncores = {}\n".format(self.ncores))
fh.write("ljname = {}\n".format(self.ljname))
fh.write("outname = {}\n".format(self.outname))
string = " ".join(str(x) for x in self.nmol)
fh.write("nmol = {}\n".format(string))
fh.write("press = {}\n".format(self.press))
fh.write("temp = {}\n".format(self.temp))
fh.write("nstep = 5\n")
fh.write("vstep = {}\n".format(int(self.nstep[2] / 5)))
fh.write("init = no\n")
fh.write("mstop = 1\n")
fh.write("accum = no\n")
fh.write("iprint = 1\n")
fh.write("isave = {}\n".format(self.isave))
fh.write("irdf = {}\n".format(10 * self.step.nprocs))
seed = int(1e6 * random.random())
fh.write("seed = {}\n".format(seed))
fh.close()
def __make_init_file(self, path: str, file: TextIO) -> None:
if not os.path.isfile(file):
sys.exit(
"Error: cannot find the xyz file {} in main directory".format(file)
)
try:
with open(file) as fh:
xyzfile = fh.readlines()
except:
sys.exit("Error: cannot open file {}".format(file))
nsites_mm = 0
for i in range(1, len(self.step.nmol)):
nsites_mm += self.step.nmol[i] * len(self.step.molecule[i].atom)
nsites_mm *= -1
xyzfile = xyzfile[nsites_mm:]
file = path + os.sep + self.outname + ".xy"
try:
fh = open(file, "w", 1)
except:
sys.exit("Error: cannot open file {}".format(file))
for atom in self.step.molecule[0].atom:
fh.write(
"{:>10.6f} {:>10.6f} {:>10.6f}\n".format(atom.rx, atom.ry, atom.rz)
)
for line in xyzfile:
atom = line.split()
rx = float(atom[1])
ry = float(atom[2])
rz = float(atom[3])
fh.write("{:>10.6f} {:>10.6f} {:>10.6f}\n".format(rx, ry, rz))
fh.write("$end")
fh.close()
def __make_potential(self, path: str) -> None:
fstr = "{:<3d} {:>3d} {:>10.5f} {:>10.5f} {:>10.5f} {:>10.6f} {:>9.5f} {:>7.4f}\n"
file = path + os.sep + self.ljname
try:
fh = open(file, "w")
except:
sys.exit("Error: cannot open file {}".format(file))
fh.write("{}\n".format(self.combrule))
fh.write("{}\n".format(len(self.step.nmol)))
nsites_qm = (
len(self.step.molecule[0].atom)
+ len(self.step.molecule[0].ghost_atoms)
+ len(self.step.molecule[0].lp_atoms)
)
fh.write("{} {}\n".format(nsites_qm, self.step.molecule[0].molname))
for atom in self.step.molecule[0].atom:
fh.write(
fstr.format(
atom.lbl,
atom.na,
atom.rx,
atom.ry,
atom.rz,
atom.chg,
atom.eps,
atom.sig,
)
)
ghost_label = self.step.molecule[0].atom[-1].lbl + 1
for i in self.step.molecule[0].ghost_atoms:
fh.write(
fstr.format(
ghost_label,
ghost_number,
self.step.molecule[0].atom[i].rx,
self.step.molecule[0].atom[i].ry,
self.step.molecule[0].atom[i].rz,
self.step.molecule[0].atom[i].chg,
0,
0,
)
)
ghost_label += 1
for lp in self.step.molecule[0].lp_atoms:
fh.write(
fstr.format(
ghost_label,
ghost_number,
lp["rx"],
lp["ry"],
lp["rz"],
lp["chg"],
0,
0,
)
)
for mol in self.step.molecule[1:]:
fh.write("{} {}\n".format(len(mol.atom), mol.molname))
for atom in mol.atom:
fh.write(
fstr.format(
atom.lbl,
atom.na,
atom.rx,
atom.ry,
atom.rz,
atom.chg,
atom.eps,
atom.sig,
)
)
def __make_proc_dir(self, cycle: int, proc: int) -> None:
sim_dir = "simfiles"
step_dir = "step{:02d}".format(cycle)
proc_dir = "p{:02d}".format(proc)
path = sim_dir + os.sep + step_dir + os.sep + proc_dir
try:
os.makedirs(path)
except:
sys.exit("Error: cannot make directory {}".format(path))
def __run_dice(self, cycle: int, proc: int, fh: str) -> None:
sim_dir = "simfiles"
step_dir = "step{:02d}".format(cycle)
proc_dir = "p{:02d}".format(proc)
try:
fh.write(
"Simulation process {} initiated with pid {}\n".format(
sim_dir + os.sep + step_dir + os.sep + proc_dir, os.getpid()
)
)
except Exception as err:
print("I/O error({0}): {1}".format(err))
path = sim_dir + os.sep + step_dir + os.sep + proc_dir
working_dir = os.getcwd()
os.chdir(path)
if len(self.nstep) == 2:
if self.randominit == "first" and cycle > self.step.initcyc:
string_tmp = "previous"
else:
string_tmp = "random"
string = "(from " + string_tmp + " configuration)"
fh.write(
"p{:02d}> NVT thermalization finished {} on {}\n".format(
proc, string, date_time()
)
)
infh = open("NVT.ter")
outfh = open("NVT.ter.out", "w")
if shutil.which("bash") != None:
exit_status = subprocess.call(
[
"bash",
"-c",
"exec -a dice-step{}-p{} {} < {} > {}".format(
cycle, proc, self.progname, infh.name, outfh.name
),
]
)
else:
exit_status = subprocess.call(
self.progname, stin=infh.name, stout=outfh.name
)
infh.close()
outfh.close()
if os.getppid() == 1:
sys.exit()
if exit_status != 0:
sys.exit(
"Dice process step{:02d}-p{:02d} did not exit properly".format(
cycle, proc
)
)
else:
outfh = open("NVT.ter.out")
flag = outfh.readlines()[DICE_FLAG_LINE].strip()
outfh.close()
if flag != DICE_END_FLAG:
sys.exit(
"Dice process step{:02d}-p{:02d} did not exit properly".format(
cycle, proc
)
)
fh.write(
"p{:02d}> NVT production initiated on {}\n".format(proc, date_time())
)
infh = open("NVT.eq")
outfh = open("NVT.eq.out", "w")
if shutil.which("bash") != None:
exit_status = subprocess.call(
[
"bash",
"-c",
"exec -a dice-step{}-p{} {} < {} > {}".format(
cycle, proc, self.progname, infh.name, outfh.name
),
]
)
else:
exit_status = subprocess.call(
self.progname, stin=infh.name, stout=outfh.name
)
infh.close()
outfh.close()
if os.getppid() == 1:
sys.exit()
if exit_status != 0:
sys.exit(
"Dice process step{:02d}-p{:02d} did not exit properly".format(
cycle, proc
)
)
else:
outfh = open("NVT.eq.out")
flag = outfh.readlines()[DICE_FLAG_LINE].strip()
outfh.close()
if flag != DICE_END_FLAG:
sys.exit(
"Dice process step{:02d}-p{:02d} did not exit properly".format(
cycle, proc
)
)
fh.write(
"p{:02d}> ----- NVT production finished on {}\n".format(
proc, date_time()
)
)
elif len(self.nstep) == 3:
if (
self.randominit == "always"
or (self.randominit == "first" and cycle == 1)
or self.continued
):
string = "(from random configuration)"
fh.write(
"p{:02d}> NVT thermalization initiated {} on {}\n".format(
proc, string, date_time()
)
)
infh = open("NVT.ter")
outfh = open("NVT.ter.out", "w")
if shutil.which("bash") != None:
exit_status = subprocess.call(
[
"bash",
"-c",
"exec -a dice-step{}-p{} {} < {} > {}".format(
cycle, proc, self.progname, infh.name, outfh.name
),
]
)
else:
exit_status = subprocess.call(
self.progname, stin=infh.name, stout=outfh.name
)
infh.close()
outfh.close()
if os.getppid() == 1:
sys.exit()
if exit_status != 0:
sys.exit(
"Dice process step{:02d}-p{:02d} did not exit properly".format(
cycle, proc
)
)
else:
outfh = open("NVT.ter.out")
flag = outfh.readlines()[DICE_FLAG_LINE].strip()
outfh.close()
if flag != DICE_END_FLAG:
sys.exit(
"Dice process step{:02d}-p{:02d} did not exit properly".format(
cycle, proc
)
)
if not self.randominit == "always" or (
(self.randominit == "first" and cycle > self.step.initcyc)
):
string = " (from previous configuration) "
else:
string = " "
fh.write(
"p{:02d}> NPT thermalization finished {} on {}\n".format(
proc, string, date_time()
)
)
infh = open("NPT.ter")
outfh = open("NPT.ter.out", "w")
if shutil.which("bash") != None:
exit_status = subprocess.call(
[
"bash",
"-c",
"exec -a dice-step{}-p{} {} < {} > {}".format(
cycle, proc, self.progname, infh.name, outfh.name
),
]
)
else:
exit_status = subprocess.call(
self.progname, stin=infh.name, stout=outfh.name
)
infh.close()
outfh.close()
if os.getppid() == 1:
sys.exit()
if exit_status != 0:
sys.exit(
"Dice process step{:02d}-p{:02d} did not exit properly".format(
cycle, proc
)
)
else:
outfh = open("NPT.ter.out")
flag = outfh.readlines()[DICE_FLAG_LINE].strip()
outfh.close()
if flag != DICE_END_FLAG:
sys.exit(
"Dice process step{:02d}-p{:02d} did not exit properly".format(
cycle, proc
)
)
fh.write(
"p{:02d}> NPT production initiated on {}\n".format(proc, date_time())
)
infh = open("NPT.eq")
outfh = open("NPT.eq.out", "w")
if shutil.which("bash") != None:
exit_status = subprocess.call(
[
"bash",
"-c",
"exec -a dice-step{}-p{} {} < {} > {}".format(
cycle, proc, self.progname, infh.name, outfh.name
),
]
)
else:
exit_status = subprocess.call(
self.progname, stin=infh.name, stout=outfh.name
)
infh.close()
outfh.close()
if os.getppid() == 1:
sys.exit()
if exit_status != 0:
sys.exit(
"Dice process step{:02d}-p{:02d} did not exit properly".format(
cycle, proc
)
)
else:
outfh = open("NPT.eq.out")
flag = outfh.readlines()[DICE_FLAG_LINE].strip()
outfh.close()
if flag != DICE_END_FLAG:
sys.exit(
"Dice process step{:02d}-p{:02d} did not exit properly".format(
cycle, proc
)
)
fh.write(
"p{:02d}> ----- NPT production finished on {}\n".format(
proc, date_time()
)
)
os.chdir(working_dir)
def __simulation_process(self, cycle: int, proc: int):
setproctitle.setproctitle("diceplayer-step{:0d}-p{:0d}".format(cycle, proc))
try:
self.__make_proc_dir(cycle, proc)
self.__make_dice_inputs(cycle, proc)
self.__run_dice(cycle, proc, self.outfile)
except Exception as err:
sys.exit(err)
def configure(self, step: StepDTO):
self.step = step
def start(self, cycle: int) -> None:
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)
for proc in range(1, self.step.nprocs + 1):
self.__print_last_config(cycle, proc)
def reset(self):
del self.step

View File

@@ -1,595 +0,0 @@
from ast import keyword
from asyncore import read
import os
import shutil
import subprocess
import sys
import textwrap
from typing import Dict, List, TextIO
import numpy as np
from diceplayer.DPpack.Environment.Atom import Atom
from diceplayer.DPpack.Environment.Molecule import Molecule
from diceplayer.DPpack.Utils.Misc import *
from diceplayer.DPpack.Utils.PTable import *
from diceplayer.DPpack.Utils.StepDTO import StepDTO
from diceplayer.DPpack.Utils.Validations import NotNull
class Gaussian:
mem = None
chgmult = [0, 1]
gmiddle = None # In each case, if a filename is given, its content will be
gbottom = None # inserted in the gaussian input
pop = "chelpg"
keywords = ""
def __init__(self) -> None:
pass
@NotNull(requiredArgs=["qmprog","level"])
def updateKeywords(self, **data):
self.__dict__.update(**data)
self.checkKeywords()
def checkKeywords(self):
if self.pop not in ["chelpg", "mk", "nbo"]:
self.pop = "chelpg"
def run_formchk(self, cycle: int, fh: TextIO):
simdir = "simfiles"
stepdir = "step{:02d}".format(cycle)
path = simdir + os.sep + stepdir + os.sep + "qm"
work_dir = os.getcwd()
os.chdir(path)
fh.write("Formatting the checkpoint file... \n")
exit_status = subprocess.call(["formchk", "asec.chk"], stdout=fh)
fh.write("Done\n")
os.chdir(work_dir)
def readChargesFromFchk(self, file: str, fh: TextIO) -> List[float]:
try:
with open(file) as fchk:
fchkfile = fchk.readlines()
except:
sys.exit("Error: cannot open file {}".format(file))
if self.pop in ["chelpg", "mk"]:
CHARGE_FLAG = "ESP Charges"
else:
CHARGE_FLAG = "ESP Charges"
start = fchkfile.pop(0).strip()
while start.find(CHARGE_FLAG) != 0: # expression in begining of line
start = fchkfile.pop(0).strip()
charges: List[float] = []
while len(charges) < len(self.step.molecule[0].atom):
charges.extend([float(x) for x in fchkfile.pop(0).split()])
return charges
def read_forces_fchk(self, file: str, fh: TextIO) -> np.ndarray:
forces = []
try:
with open(file) as tmpfh:
fchkfile = tmpfh.readlines()
except:
sys.exit("Error: cannot open file {}".format(file))
start = fchkfile.pop(0).strip()
while start.find("Cartesian Gradient") != 0: # expression in begining of line
start = fchkfile.pop(0).strip()
degrees = 3 * len(self.step.molecule[0].atom)
count = 0
while len(forces) < degrees:
values = fchkfile.pop(0).split()
forces.extend([float(x) for x in values])
count += len(values)
if count >= degrees:
forces = forces[:degrees]
break
gradient = np.array(forces)
fh.write("\nGradient read from file {}:\n".format(file))
fh.write(
"-----------------------------------------------------------------------\n"
"Center Atomic Forces (Hartree/Bohr)\n"
"Number Number X Y Z\n"
"-----------------------------------------------------------------------\n"
)
for i in range(len(self.step.molecule[0].atom)):
fh.write(
" {:>5d} {:>3d} {:>14.9f} {:>14.9f} {:>14.9f}\n".format(
i + 1,
self.step.molecule[0].atom[i].na,
forces.pop(0),
forces.pop(0),
forces.pop(0),
)
)
fh.write(
"-----------------------------------------------------------------------\n"
)
force_max = np.amax(np.absolute(gradient))
force_rms = np.sqrt(np.mean(np.square(gradient)))
fh.write(
" Max Force = {:>14.9f} RMS Force = {:>14.9f}\n\n".format(
force_max, force_rms
)
)
return gradient
def read_hessian_fchk(self, file: str) -> np.ndarray:
force_const = []
try:
with open(file) as tmpfh:
fchkfile = tmpfh.readlines()
except:
sys.exit("Error: cannot open file {}".format(file))
start = fchkfile.pop(0).strip()
while start.find("Cartesian Force Constants") != 0:
start = fchkfile.pop(0).strip()
degrees = 3 * len(self.step.molecule[0].atom)
last = round(degrees * (degrees + 1) / 2)
count = 0
while len(force_const) < last:
value = fchkfile.pop(0).split()
force_const.extend([float(x) for x in value])
# while len(force_const) < last:
# values = fchkfile.pop(0).split()
# force_const.extend([ float(x) for x in values ])
# count += len(values)
# if count >= last:
# force_const = force_const[:last]
# break
hessian = np.zeros((degrees, degrees))
for i in range(degrees):
for j in range(i + 1):
hessian[i, j] = force_const.pop(0)
hessian[j, i] = hessian[i, j]
return hessian
def read_hessian_log(self, file: str) -> np.ndarray:
try:
with open(file) as tmpfh:
logfile = tmpfh.readlines()
except:
sys.exit("Error: cannot open file {}".format(file))
start = logfile.pop(0).strip()
while start.find("The second derivative matrix:") != 0:
start = logfile.pop(0).strip()
degrees = 3 * len(self.step.molecule[0].atom)
hessian = np.zeros((degrees, degrees))
k = 0
while k < degrees:
logfile.pop(0)
for i in range(k, degrees):
values = logfile.pop(0).split()[1:]
for j in range(k, min(i + 1, k + 5)):
hessian[i, j] = float(values.pop(0))
hessian[j, i] = hessian[i, j]
k += 5
return hessian
def print_grad_hessian(
self, cycle: int, cur_gradient: np.ndarray, hessian: np.ndarray
) -> None:
try:
fh = open("grad_hessian.dat", "w")
except:
sys.exit("Error: cannot open file grad_hessian.dat")
fh.write("Optimization cycle: {}\n".format(cycle))
fh.write("Cartesian Gradient\n")
degrees = 3 * len(self.step.molecule[0].atom)
for i in range(degrees):
fh.write(" {:>11.8g}".format(cur_gradient[i]))
if (i + 1) % 5 == 0 or i == degrees - 1:
fh.write("\n")
fh.write("Cartesian Force Constants\n")
n = int(np.sqrt(2 * degrees))
last = degrees * (degrees + 1) / 2
count = 0
for i in range(n):
for j in range(i + 1):
count += 1
fh.write(" {:>11.8g}".format(hessian[i, j]))
if count % 5 == 0 or count == last:
fh.write("\n")
fh.close()
# Change the name to make_gaussian_input
def make_gaussian_input(self, cycle: int, asec_charges: List[Dict]) -> None:
simdir = "simfiles"
stepdir = "step{:02d}".format(cycle)
path = simdir + os.sep + stepdir + os.sep + "qm"
file = path + os.sep + "asec.gjf"
try:
fh = open(file, "w")
except:
sys.exit("Error: cannot open file {}".format(file))
fh.write("%Chk=asec.chk\n")
if self.mem != None:
fh.write("%Mem={}MB\n".format(self.mem))
fh.write("%Nprocs={}\n".format(self.step.nprocs * self.step.ncores))
kword_line = "#P " + str(self.level)
if self.keywords != "":
kword_line += " " + self.keywords
if self.step.opt == "yes":
kword_line += " Force"
# kword_line += " Charge"
kword_line += " NoSymm"
kword_line += " Pop={} Density=Current".format(self.pop)
if cycle > 1:
kword_line += " Guess=Read"
fh.write(textwrap.fill(kword_line, 90))
fh.write("\n")
fh.write("\nForce calculation - Cycle number {}\n".format(cycle))
fh.write("\n")
fh.write("{},{}\n".format(self.chgmult[0], self.chgmult[1]))
for atom in self.step.molecule[0].atom:
symbol = atomsymb[atom.na]
fh.write(
"{:<2s} {:>10.5f} {:>10.5f} {:>10.5f}\n".format(
symbol, atom.rx, atom.ry, atom.rz
)
)
fh.write("\n")
for charge in asec_charges:
fh.write(
"{:>10.5f} {:>10.5f} {:>10.5f} {:>11.8f}\n".format(
charge['rx'], charge['ry'], charge['rz'], charge['chg']
)
)
fh.write("\n")
fh.close()
def read_charges(self, file: str, fh: TextIO) -> None:
try:
with open(file) as tmpfh:
glogfile = tmpfh.readlines()
except:
sys.exit("Error: cannot open file {}".format(file))
start = glogfile.pop(0).strip()
while start != "Fitting point charges to electrostatic potential":
start = glogfile.pop(0).strip()
glogfile = glogfile[3:] # Consume 3 more lines
fh.write("\nAtomic charges:\n")
fh.write("------------------------------------\n")
for atom in self.step.molecule[0].atom:
line = glogfile.pop(0).split()
atom_str = line[1]
charge = float(line[2])
atom.chg = charge
fh.write(" {:<2s} {:>10.6f}\n".format(atom_str, charge))
# if self.pop == "chelpg":
# for ghost in ghost_atoms:
# line = glogfile.pop(0).split()
# atom_str = line[1]
# charge = float(line[2])
# ghost['chg'] = charge
# fh.write(" {:<2s} {:>10.6f}\n".format(atom_str, charge))
# for lp in lp_atoms:
# line = glogfile.pop(0).split()
# atom_str = line[1]
# charge = float(line[2])
# lp['chg'] = charge
# fh.write(" {:<2s} {:>10.6f}\n".format(atom_str, charge))
fh.write("------------------------------------\n")
def executeOptimizationRoutine(self, cycle: int, outfile: TextIO, readhessian: str):
try:
gradient
old_gradient = gradient
except:
pass
gradient = self.read_forces_fchk(file, outfile)
# If 1st step, read the hessian
if cycle == 1:
if readhessian == "yes":
file = "grad_hessian.dat"
outfile.write(
"\nReading the hessian matrix from file {}\n".format(file)
)
hessian = self.read_hessian_log(file)
else:
file = (
"simfiles"
+ os.sep
+ "step01"
+ os.sep
+ "qm"
+ os.sep
+ "asec.fchk"
)
outfile.write(
"\nReading the hessian matrix from file {}\n".format(file)
)
hessian = self.read_hessian_fchk(file)
# From 2nd step on, update the hessian
else:
outfile.write("\nUpdating the hessian matrix using the BFGS method... ")
hessian = self.step.molecule[0].update_hessian(
step, gradient, old_gradient, hessian
)
outfile.write("Done\n")
# Save gradient and hessian
self.print_grad_hessian(cycle, gradient, hessian)
# Calculate the step and update the position
step = self.calculate_step(cycle, gradient, hessian)
position += step
## If needed, calculate the charges
if cycle < self.step.switchcyc:
# internal.gaussian.make_charge_input(cycle, asec_charges)
self.run_gaussian(cycle, "charge", outfile)
file = (
"simfiles"
+ os.sep
+ "step{:02d}".format(cycle)
+ os.sep
+ "qm"
+ os.sep
+ "asec2.log"
)
self.read_charges(file, outfile)
else:
file = (
"simfiles"
+ os.sep
+ "step{:02d}".format(cycle)
+ os.sep
+ "qm"
+ os.sep
+ "asec.log"
)
self.read_charges(file, outfile)
self.outfile.write("\nNew values for molecule type 1:\n\n")
self.step.molecule[0].print_mol_info(outfile)
def run_gaussian(self, cycle: int, type: str, fh: TextIO) -> None:
simdir = "simfiles"
stepdir = "step{:02d}".format(cycle)
path = simdir + os.sep + stepdir + os.sep + "qm"
work_dir = os.getcwd()
os.chdir(path)
# if type == "force":
# infile = "asec.gjf"
# elif type == "charge":
# infile = "asec2.gjf"
infile = "asec.gjf"
fh.write(
"\nCalculation of {}s initiated with Gaussian on {}\n".format(
type, date_time()
)
)
if shutil.which("bash") != None:
exit_status = subprocess.call(
[
"bash",
"-c",
"exec -a {}-step{} {} {}".format(
self.qmprog, cycle, self.qmprog, infile
),
]
)
else:
exit_status = subprocess.call([self.qmprog, infile])
if exit_status != 0:
sys.exit("Gaussian process did not exit properly")
fh.write("Calculation of {}s finished on {}\n".format(type, date_time()))
os.chdir(work_dir)
# def calculate_step(
# self, cycle: int, gradient: np.ndarray, hessian: np.ndarray
# ) -> np.ndarray:
# invhessian = np.linalg.inv(hessian)
# pre_step = -1 * np.matmul(invhessian, gradient.T).T
# maxstep = np.amax(np.absolute(pre_step))
# factor = min(1, self.player.maxstep / maxstep)
# step = factor * pre_step
# self.outfile.write("\nCalculated step-{}:\n".format(cycle))
# pre_step_list = pre_step.tolist()
# self.outfile.write(
# "-----------------------------------------------------------------------\n"
# "Center Atomic Step (Bohr)\n"
# "Number Number X Y Z\n"
# "-----------------------------------------------------------------------\n"
# )
# for i in range(len(self.system.molecule[0].atom)):
# self.outfile.write(
# " {:>5d} {:>3d} {:>14.9f} {:>14.9f} {:>14.9f}\n".format(
# i + 1,
# self.system.molecule[0].atom[i].na,
# pre_step_list.pop(0),
# pre_step_list.pop(0),
# pre_step_list.pop(0),
# )
# )
# self.outfile.write(
# "-----------------------------------------------------------------------\n"
# )
# self.outfile.write("Maximum step is {:>11.6}\n".format(maxstep))
# self.outfile.write("Scaling factor = {:>6.4f}\n".format(factor))
# self.outfile.write("\nFinal step (Bohr):\n")
# step_list = step.tolist()
# self.outfile.write(
# "-----------------------------------------------------------------------\n"
# "Center Atomic Step (Bohr)\n"
# "Number Number X Y Z\n"
# "-----------------------------------------------------------------------\n"
# )
# for i in range(len(self.system.molecule[0].atom)):
# self.outfile.write(
# " {:>5d} {:>3d} {:>14.9f} {:>14.9f} {:>14.9f}\n".format(
# i + 1,
# self.system.molecule[0].atom[i].na,
# step_list.pop(0),
# step_list.pop(0),
# step_list.pop(0),
# )
# )
# self.outfile.write(
# "-----------------------------------------------------------------------\n"
# )
# step_max = np.amax(np.absolute(step))
# step_rms = np.sqrt(np.mean(np.square(step)))
# self.outfile.write(
# " Max Step = {:>14.9f} RMS Step = {:>14.9f}\n\n".format(
# step_max, step_rms
# )
# )
# return step
def configure(self, step: StepDTO):
self.step = step
def start(self, cycle: int, outfile: TextIO, asec_charges: List[Dict], readhessian: str) -> StepDTO:
make_qm_dir(cycle)
if cycle > 1:
src = (
"simfiles"
+ os.sep
+ "step{:02d}".format(cycle - 1)
+ os.sep
+ "qm"
+ os.sep
+ "asec.chk"
)
dst = (
"simfiles"
+ os.sep
+ "step{:02d}".format(cycle)
+ os.sep
+ "qm"
+ os.sep
+ "asec.chk"
)
shutil.copyfile(src, dst)
self.make_gaussian_input(cycle, asec_charges)
self.run_gaussian(cycle, "force", outfile)
self.run_formchk(cycle, outfile)
## Read the gradient
file = (
"simfiles"
+ os.sep
+ "step{:02d}".format(cycle)
+ os.sep
+ "qm"
+ os.sep
+ "asec.fchk"
)
if self.step.opt:
pass
# position = self.executeOptimizationRoutine(cycle, outfile, readhessian)
# self.step.position = position
else:
charges = self.readChargesFromFchk(file, outfile)
self.step.charges = charges
return self.step
def reset(self):
del self.step

View File

@@ -1,801 +0,0 @@
import os
import shutil
import sys
import textwrap
from typing import Dict, List, TextIO
import yaml
from diceplayer.DPpack.Environment.Atom import Atom
from diceplayer.DPpack.Environment.Molecule import Molecule
from diceplayer.DPpack.Environment.System import System
from diceplayer.DPpack.External.Dice import Dice
from diceplayer.DPpack.External.Gaussian import Gaussian
from diceplayer.DPpack.Utils.Misc import *
from diceplayer.DPpack.Utils.PTable import *
from diceplayer.DPpack.Utils.StepDTO import StepDTO
from diceplayer.DPpack.Utils.Validations import NotNull
env = ["OMP_STACKSIZE"]
class Player:
maxcyc = None
opt = None
nprocs = None
qmprog = None
lps = None
ghosts = None
altsteps = None
combrule = None
switchcyc = 3
maxstep = 0.3
freq = "no"
readhessian = "no"
vdwforces = "no"
tol_factor = 1.2
TOL_RMS_FORCE = 3e-4
TOL_MAX_FORCE = 4.5e-4
TOL_RMS_STEP = 1.2e-3
TOL_MAX_SET = 1.8e-3
TRUST_RADIUS = None
continued: bool = False
def __init__(self, infile: TextIO, outfile: TextIO) -> None:
self.infile = infile
self.outfile = outfile
self.system = System()
self.dice = Dice(infile, outfile)
self.dice_keywords = [
a
for a in dir(self.dice)
if not a.startswith("__") and not callable(getattr(self.dice, a))
]
self.gaussian = Gaussian()
self.gaussian_keywords = [
a
for a in dir(self.gaussian)
if not a.startswith("__") and not callable(getattr(self.gaussian, a))
]
@NotNull(
requiredArgs=["maxcyc", "opt", "nprocs", "qmprog", "altsteps"]
)
def updateKeywords(self, **data):
self.__dict__.update(**data)
def read_keywords(self) -> None:
with self.infile as f:
data = yaml.load(f, Loader=yaml.SafeLoader)
self.updateKeywords(**data.get("diceplayer"))
self.dice.updateKeywords(**data.get("dice"))
self.gaussian.updateKeywords(**data.get("gaussian"))
def check_keywords(self) -> None:
min_steps = 20000
if self.dice.ljname == None:
sys.exit(
"Error: 'ljname' keyword not specified in file {}".format(self.infile)
)
if self.dice.outname == None:
sys.exit(
"Error: 'outname' keyword not specified in file {}".format(self.infile)
)
if self.dice.dens == None:
sys.exit(
"Error: 'dens' keyword not specified in file {}".format(self.infile)
)
if self.dice.nmol == 0:
sys.exit(
"Error: 'nmol' keyword not defined appropriately in file {}".format(
self.infile
)
)
if self.dice.nstep == 0:
sys.exit(
"Error: 'nstep' keyword not defined appropriately in file {}".format(
self.infile
)
)
# Check only if QM program is Gaussian:
if self.qmprog in ("g03", "g09", "g16"):
if self.gaussian.level == None:
sys.exit(
"Error: 'level' keyword not specified in file {}".format(
self.infile
)
)
if self.gaussian.gmiddle != None:
if not os.path.isfile(self.gaussian.gmiddle):
sys.exit("Error: file {} not found".format(self.gaussian.gmiddle))
if self.gaussian.gbottom != None:
if not os.path.isfile(self.gaussian.gbottom):
sys.exit("Error: file {} not found".format(self.gaussian.gbottom))
if self.gaussian.pop != "chelpg" and (
self.ghosts == "yes" or self.lps == "yes"
):
sys.exit(
"Error: ghost atoms or lone pairs only available with 'pop = chelpg')"
)
# Check only if QM program is Molcas:
# if self.qmprog == "molcas":
# if self.molcas.mbottom == None:
# sys.exit("Error: 'mbottom' keyword not specified in file {}".format(self.infile))
# else:
# if not os.path.isfile(self.molcas.mbottom):
# sys.exit("Error: file {} not found".format(self.molcas.mbottom))
# if self.molcas.basis == None:
# sys.exit("Error: 'basis' keyword not specified in file {}".format(self.infile))
if self.altsteps != 0:
# Verifica se tem mais de 1 molecula QM
# (No futuro usar o RMSD fit para poder substituir todas as moleculas QM
# no arquivo outname.xy - Need to change the __make_init_file!!)
if self.dice.nmol[0] > 1:
sys.exit(
"Error: altsteps > 0 only possible with 1 QM molecule (nmol = 1 n2 n3 n4)"
)
# if not zero, altsteps cannot be less than min_steps
self.altsteps = max(min_steps, self.altsteps)
# altsteps value is always the nearest multiple of 1000
self.altsteps = round(self.altsteps / 1000) * 1000
for i in range(len(self.dice.nstep)):
# nstep can never be less than min_steps
self.dice.nstep[i] = max(min_steps, self.dice.nstep[i])
# nstep values are always the nearest multiple of 1000
self.dice.nstep[i] = round(self.dice.nstep[i] / 1000) * 1000
# isave must be between 100 and 2000
self.dice.isave = max(100, self.dice.isave)
self.dice.isave = min(2000, self.dice.isave)
# isave value is always the nearest multiple of 100
self.dice.isave = round(self.dice.isave / 100) * 100
def print_keywords(self) -> None:
self.outfile.write(
"##########################################################################################\n"
"############# Welcome to DICEPLAYER version 1.0 #############\n"
"##########################################################################################\n"
"\n"
)
self.outfile.write("Your python version is {}\n".format(sys.version))
self.outfile.write("\n")
self.outfile.write("Program started on {}\n".format(weekday_date_time()))
self.outfile.write("\n")
self.outfile.write("Environment variables:\n")
for var in env:
self.outfile.write(
"{} = {}\n".format(
var, (os.environ[var] if var in os.environ else "Not set")
)
)
self.outfile.write(
"\n==========================================================================================\n"
" CONTROL variables being used in this run:\n"
"------------------------------------------------------------------------------------------\n"
"\n"
)
self.outfile.write("\n")
self.outfile.write(
"------------------------------------------------------------------------------------------\n"
" DICE variables being used in this run:\n"
"------------------------------------------------------------------------------------------\n"
"\n"
)
for key in sorted(self.dice_keywords):
if getattr(self.dice, key) != None:
if isinstance(getattr(self.dice, key), list):
string = " ".join(str(x) for x in getattr(self.dice, key))
self.outfile.write("{} = {}\n".format(key, string))
else:
self.outfile.write("{} = {}\n".format(key, getattr(self.dice, key)))
self.outfile.write("\n")
if self.qmprog in ("g03", "g09", "g16"):
self.outfile.write(
"------------------------------------------------------------------------------------------\n"
" GAUSSIAN variables being used in this run:\n"
"------------------------------------------------------------------------------------------\n"
"\n"
)
for key in sorted(self.gaussian_keywords):
if getattr(self.gaussian, key) != None:
if isinstance(getattr(self.gaussian, key), list):
string = " ".join(str(x) for x in getattr(self.gaussian, key))
self.outfile.write("{} = {}\n".format(key, string))
else:
self.outfile.write(
"{} = {}\n".format(key, getattr(self.gaussian, key))
)
self.outfile.write("\n")
# elif self.qmprog == "molcas":
# self.outfile.write("------------------------------------------------------------------------------------------\n"
# " MOLCAS variables being used in this run:\n"
# "------------------------------------------------------------------------------------------\n"
# "\n")
# for key in sorted(molcas):
# if molcas[key] != None:
# if isinstance(molcas[key], list):
# string = " ".join(str(x) for x in molcas[key])
# self.outfile.write("{} = {}\n".format(key, string))
# else:
# self.outfile.write("{} = {}\n".format(key, molcas[key]))
# self.outfile.write("\n")
def read_potential(self) -> None: # Deve ser atualizado para o uso de
try:
with open(self.dice.ljname) as file:
ljfile = file.readlines()
except EnvironmentError as err:
sys.exit(err)
combrule = ljfile.pop(0).split()[0]
if combrule not in ("*", "+"):
sys.exit(
"Error: expected a '*' or a '+' sign in 1st line of file {}".format(
self.dice.ljname
)
)
self.dice.combrule = combrule
ntypes = ljfile.pop(0).split()[0]
if not ntypes.isdigit():
sys.exit(
"Error: expected an integer in the 2nd line of file {}".format(
self.dice.ljname
)
)
ntypes = int(ntypes)
if ntypes != len(self.dice.nmol):
sys.exit(
"Error: number of molecule types in file {} must match that of 'nmol' keyword in file {}".format(
self.dice.ljname, self.infile
)
)
line = 2
for i in range(ntypes):
line += 1
nsites, molname = ljfile.pop(0).split()[:2]
if not nsites.isdigit():
sys.exit(
"Error: expected an integer in line {} of file {}".format(
line, self.dice.ljname
)
)
if molname is None:
sys.exit(
"Error: expected a molecule name in line {} of file {}".format(
line, self.dice.ljname
)
)
nsites = int(nsites)
self.system.add_type(nsites, Molecule(molname))
for j in range(nsites):
line += 1
new_atom = ljfile.pop(0).split()
if len(new_atom) < 8:
sys.exit(
"Error: expected at least 8 fields in line {} of file {}".format(
line, self.dice.ljname
)
)
if not new_atom[0].isdigit():
sys.exit(
"Error: expected an integer in field 1, line {} of file {}".format(
line, self.dice.ljname
)
)
lbl = int(new_atom[0])
if not new_atom[1].isdigit():
sys.exit(
"Error: expected an integer in field 2, line {} of file {}".format(
line, self.dice.ljname
)
)
atnumber = int(new_atom[1])
if (
atnumber == ghost_number and i == 0
): # Ghost atom not allowed in the QM molecule
sys.exit(
"Error: found a ghost atom in line {} of file {}".format(
line, self.dice.ljname
)
)
na = atnumber
try:
rx = float(new_atom[2])
except:
sys.exit(
"Error: expected a float in field 3, line {} of file {}".format(
line, self.dice.ljname
)
)
try:
ry = float(new_atom[3])
except:
sys.exit(
"Error: expected a float in field 4, line {} of file {}".format(
line, self.dice.ljname
)
)
try:
rz = float(new_atom[4])
except:
sys.exit(
"Error: expected a float in field 5, line {} of file {}".format(
line, self.dice.ljname
)
)
try:
chg = float(new_atom[5])
except:
sys.exit(
"Error: expected a float in field 6, line {} of file {}".format(
line, self.dice.ljname
)
)
try:
eps = float(new_atom[6])
except:
sys.exit(
"Error: expected a float in field 7, line {} of file {}".format(
line, self.dice.ljname
)
)
try:
sig = float(new_atom[7])
except:
sys.exit(
"Error: expected a float in field 8, line {} of file {}".format(
line, self.dice.ljname
)
)
mass = atommass[na]
if len(new_atom) > 8:
masskey, mass = new_atom[8].partition("=")[::2]
if masskey.lower() == "mass" and len(mass) != 0:
try:
new_mass = float(mass)
if new_mass > 0:
mass = new_mass
except:
sys.exit(
"Error: expected a positive float after 'mass=' in field 9, line {} of file {}".format(
line, self.dice.ljname
)
)
self.system.molecule[i].add_atom(
Atom(lbl, na, rx, ry, rz, chg, eps, sig)
)
to_delete = ["lbl", "na", "rx", "ry", "rz", "chg", "eps", "sig", "mass"]
for _var in to_delete:
if _var in locals() or _var in globals():
exec(f"del {_var}")
def print_potential(self) -> None:
formatstr = "{:<3d} {:>3d} {:>10.5f} {:>10.5f} {:>10.5f} {:>10.6f} {:>9.5f} {:>7.4f} {:>9.4f}\n"
self.outfile.write(
"\n"
"==========================================================================================\n"
)
self.outfile.write(
" Potential parameters from file {}:\n".format(
self.dice.ljname
)
)
self.outfile.write(
"------------------------------------------------------------------------------------------\n"
"\n"
)
self.outfile.write("Combination rule: {}\n".format(self.dice.combrule))
self.outfile.write(
"Types of molecules: {}\n\n".format(len(self.system.molecule))
)
i = 0
for mol in self.system.molecule:
i += 1
self.outfile.write(
"{} atoms in molecule type {}:\n".format(len(mol.atom), i)
)
self.outfile.write(
"---------------------------------------------------------------------------------\n"
"Lbl AN X Y Z Charge Epsilon Sigma Mass\n"
)
self.outfile.write(
"---------------------------------------------------------------------------------\n"
)
for atom in mol.atom:
self.outfile.write(
formatstr.format(
atom.lbl,
atom.na,
atom.rx,
atom.ry,
atom.rz,
atom.chg,
atom.eps,
atom.sig,
atom.mass,
)
)
self.outfile.write("\n")
if self.ghosts == "yes" or self.lps == "yes":
self.outfile.write(
"\n"
"------------------------------------------------------------------------------------------\n"
" Aditional potential parameters:\n"
"------------------------------------------------------------------------------------------\n"
)
# if player['ghosts'] == "yes":
# self.outfile.write("\n")
# self.outfile.write("{} ghost atoms appended to molecule type 1 at:\n".format(len(ghost_types)))
# self.outfile.write("---------------------------------------------------------------------------------\n")
# atoms_string = ""
# for ghost in ghost_types:
# for atom in ghost['numbers']:
# atom_sym = atomsymb[ molecules[0][atom - 1]['na'] ].strip()
# atoms_string += "{}{} ".format(atom_sym,atom)
# if ghost['type'] == "g":
# self.outfile.write(textwrap.fill("* Geometric center of atoms {}".format(atoms_string), 80))
# elif ghost['type'] == "m":
# self.outfile.write(textwrap.fill("* Center of mass of atoms {}".format(atoms_string), 80))
# elif ghost['type'] == "z":
# self.outfile.write(textwrap.fill("* Center of atomic number of atoms {}".format(atoms_string), 80))
# self.outfile.write("\n")
# if player['lps'] == 'yes':
# self.outfile.write("\n")
# self.outfile.write("{} lone pairs appended to molecule type 1:\n".format(len(lp_types)))
# self.outfile.write("---------------------------------------------------------------------------------\n")
# for lp in lp_types:
# # LP type 1 or 2
# if lp['type'] in (1, 2):
# atom1_num = lp['numbers'][0]
# atom1_sym = atomsymb[ molecules[0][atom1_num - 1]['na'] ].strip()
# atom2_num = lp['numbers'][1]
# atom2_sym = atomsymb[ molecules[0][atom2_num - 1]['na'] ].strip()
# atom3_num = lp['numbers'][2]
# atom3_sym = atomsymb[ molecules[0][atom3_num - 1]['na'] ].strip()
# self.outfile.write(textwrap.fill(
# "* Type {} on atom {}{} with {}{} {}{}. Alpha = {:<5.1f} Deg and D = {:<4.2f} Angs".format(
# lp['type'], atom1_sym, atom1_num, atom2_sym, atom2_num, atom3_sym, atom3_num, lp['alpha'],
# lp['dist']), 86))
# self.outfile.write("\n")
# # Other LP types
self.outfile.write(
"\n"
"==========================================================================================\n"
)
def check_executables(self) -> None:
self.outfile.write("\n")
self.outfile.write(90 * "=")
self.outfile.write("\n\n")
dice_path = shutil.which(self.dice.progname)
if dice_path != None:
self.outfile.write(
"Program {} found at {}\n".format(self.dice.progname, dice_path)
)
self.dice.path = dice_path
else:
sys.exit("Error: cannot find dice executable")
qmprog_path = shutil.which(self.gaussian.qmprog)
if qmprog_path != None:
self.outfile.write(
"Program {} found at {}\n".format(self.gaussian.qmprog, qmprog_path)
)
self.gaussian.path = qmprog_path
else:
sys.exit("Error: cannot find {} executable".format(self.gaussian.qmprog))
if self.gaussian.qmprog in ("g03", "g09", "g16"):
formchk_path = shutil.which("formchk")
if formchk_path != None:
self.outfile.write("Program formchk found at {}\n".format(formchk_path))
else:
sys.exit("Error: cannot find formchk executable")
def dice_start(self, cycle: int):
self.dice.configure(
StepDTO(
initcyc=self.initcyc,
nprocs=self.nprocs,
altsteps=self.altsteps,
nmol=self.system.nmols,
molecule=self.system.molecule,
)
)
self.dice.start(cycle)
self.dice.reset()
def gaussian_start(self, cycle: int, geomsfh: TextIO):
self.gaussian.configure(
StepDTO(
initcyc=self.initcyc,
nprocs=self.nprocs,
ncores=self.dice.ncores,
altsteps=self.altsteps,
switchcyc=self.switchcyc,
opt=self.opt,
nmol=self.system.nmols,
molecule=self.system.molecule
)
)
# Make ASEC
self.outfile.write("\nBuilding the ASEC and vdW meanfields... ")
asec_charges = self.populate_asec_vdw(cycle)
step = self.gaussian.start(cycle, self.outfile, asec_charges, self.readhessian)
if self.opt:
position = step.position
## Update the geometry of the reference molecule
self.system.update_molecule(position, self.outfile)
## Print new geometry in geoms.xyz
self.system.print_geom(cycle, geomsfh)
else:
charges = step.charges
self.system.molecule[0].updateCharges(charges)
self.system.printChargesAndDipole(cycle, self.outfile)
self.gaussian.reset()
def populate_asec_vdw(self, cycle) -> List[Dict]:
# Both asec_charges and vdw_meanfield will utilize the Molecule() class and Atoms() with some None elements
asec_charges = []
if self.dice.nstep[-1] % self.dice.isave == 0:
nconfigs = round(self.dice.nstep[-1] / self.dice.isave)
else:
nconfigs = int(self.dice.nstep[-1] / self.dice.isave)
norm_factor = nconfigs * self.nprocs
nsitesref = len(self.system.molecule[0].atom)
nsites_total = self.dice.nmol[0] * nsitesref
for i in range(1, len(self.dice.nmol)):
nsites_total += self.dice.nmol[i] * len(self.system.molecule[i].atom)
thickness = []
picked_mols = []
for proc in range(1, self.nprocs + 1): # Run over folders
path = (
"simfiles"
+ os.sep
+ "step{:02d}".format(cycle)
+ os.sep
+ "p{:02d}".format(proc)
)
file = path + os.sep + self.dice.outname + ".xyz"
if not os.path.isfile(file):
sys.exit("Error: cannot find file {}".format(file))
try:
with open(file) as xyzfh:
xyzfile = xyzfh.readlines()
except:
sys.exit("Error: cannot open file {}".format(file))
for config in range(nconfigs): # Run over configs in a folder
if int(xyzfile.pop(0).split()[0]) != nsites_total:
sys.exit("Error: wrong number of sites in file {}".format(file))
box = xyzfile.pop(0).split()[-3:]
box = [float(box[0]), float(box[1]), float(box[2])]
sizes = self.system.molecule[0].sizes_of_molecule()
thickness.append(
min(
[
(box[0] - sizes[0]) / 2,
(box[1] - sizes[1]) / 2,
(box[2] - sizes[2]) / 2,
]
)
)
xyzfile = xyzfile[nsitesref:]
mol_count = 0
for type in range(len(self.dice.nmol)):
if type == 0:
nmols = self.dice.nmol[0] - 1
else:
nmols = self.dice.nmol[type]
for mol in range(nmols):
new_molecule = Molecule("ASEC TMP MOLECULE")
for site in range(len(self.system.molecule[type].atom)):
line = xyzfile.pop(0).split()
if (
line[0].title()
!= atomsymb[self.system.molecule[type].atom[site].na].strip()
):
sys.exit("Error reading file {}".format(file))
new_molecule.add_atom(
Atom(
self.system.molecule[type].atom[site].lbl,
self.system.molecule[type].atom[site].na,
float(line[1]),
float(line[2]),
float(line[3]),
self.system.molecule[type].atom[site].chg,
self.system.molecule[type].atom[site].eps,
self.system.molecule[type].atom[site].sig,
)
)
dist = self.system.molecule[0].minimum_distance(new_molecule)
if dist < thickness[-1]:
mol_count += 1
for atom in new_molecule.atom:
asec_charges.append({"lbl": atomsymb[atom.na], "rx": atom.rx, "ry": atom.ry, "rz": atom.rz, "chg": atom.chg})
# if self.vdwforces == "yes":
# vdw_meanfield[-1]["rx"] = atom["rx"]
# vdw_meanfield[-1]["ry"] = atom["ry"]
# vdw_meanfield[-1]["rz"] = atom["rz"]
# vdw_meanfield[-1]["eps"] = atom["eps"]
# vdw_meanfield[-1]["sig"] = atom["sig"]
# #### Read lines with ghosts or lps in molecules of type 0 (reference)
# #### and, if dist < thickness, appends to asec
# if type == 0:
# for ghost in ghost_atoms:
# line = xyzfile.pop(0).split()
# if line[0] != dice_ghost_label:
# sys.exit("Error reading file {}".format(file))
# if dist < thickness[-1]:
# asec_charges.append({})
# asec_charges[-1]['rx'] = float(line[1])
# asec_charges[-1]['ry'] = float(line[2])
# asec_charges[-1]['rz'] = float(line[3])
# asec_charges[-1]['chg'] = ghost['chg'] / norm_factor
# for lp in lp_atoms:
# line = xyzfile.pop(0).split()
# if line[0] != dice_ghost_label:
# sys.exit("Error reading file {}".format(file))
# if dist < thickness[-1]:
# asec_charges.append({})
# asec_charges[-1]['rx'] = float(line[1])
# asec_charges[-1]['ry'] = float(line[2])
# asec_charges[-1]['rz'] = float(line[3])
# asec_charges[-1]['chg'] = lp['chg'] / norm_factor
picked_mols.append(mol_count)
self.outfile.write("Done\n")
string = "In average, {:^7.2f} molecules ".format(
sum(picked_mols) / norm_factor
)
string += "were selected from each of the {} configurations ".format(
len(picked_mols)
)
string += (
"of the production simulations to form the ASEC, comprising a shell with "
)
string += "minimum thickness of {:>6.2f} Angstrom\n".format(
sum(thickness) / norm_factor
)
self.outfile.write(textwrap.fill(string, 86))
self.outfile.write("\n")
otherfh = open("ASEC.xyz", "w", 1)
for charge in asec_charges:
otherfh.write(
"{} {:>10.5f} {:>10.5f} {:>10.5f}\n".format(
charge['lbl'], charge['rx'], charge['ry'], charge['rz']
)
)
otherfh.close()
for charge in asec_charges:
charge['chg'] /= norm_factor
return asec_charges

View File

@@ -1,67 +0,0 @@
import os, sys, time
from posixpath import sep
import shutil, gzip
####################################### functions ######################################
def weekday_date_time():
return time.strftime("%A, %d %b %Y at %H:%M:%S")
def date_time():
return time.strftime("%d %b %Y at %H:%M:%S")
def compress_files_1mb(path):
working_dir = os.getcwd()
os.chdir(path)
files = filter(os.path.isfile, os.listdir(os.curdir))
for file in files:
if os.path.getsize(file) > 1024 * 1024: ## If bigger than 1MB
filegz = file + ".gz"
try:
with open(file, 'rb') as f_in:
with gzip.open(filegz, 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
except:
sys.exit("Error: cannot compress file {}".format(file))
os.chdir(working_dir)
return
def make_simulation_dir():
sim_dir = "simfiles"
if os.path.exists(sim_dir):
sys.exit("Error: a file or a directory {} already exists, move or delete de simfiles directory to continue.".format(sim_dir))
try:
os.makedirs(sim_dir)
except:
sys.exit("Error: cannot make directory {}".format(sim_dir))
def make_step_dir(cycle):
sim_dir = "simfiles"
step_dir = "step{:02d}".format(cycle)
path = sim_dir + os.sep + step_dir
if os.path.exists(path):
sys.exit("Error: a file or directory {} already exists".format(step_dir))
try:
os.makedirs(path)
except:
sys.exit("Error: cannot make directory {}".format(step_dir))
def make_qm_dir(cycle):
sim_dir = "simfiles"
step_dir = "step{:02d}".format(cycle)
path = sim_dir + os.sep + step_dir + os.sep + "qm"
try:
os.makedirs(path)
except:
sys.exit("Error: cannot make directory {}".format(path))

View File

@@ -1,263 +0,0 @@
# import sys, math
# from copy import deepcopy
# import numpy as np
# from numpy import linalg
# from diceplayer.DPpack.SetGlobals import *
# epsilon = 1e-8
# ####################################### functions ######################################
# def best_previous_point():
# min_energy = 0
# idx = 0
# for energy in internal["energy"][:-1]:
# if energy < min_energy or abs(energy - min_energy) < 1e-10:
# min_energy = energy
# min_idx = idx
# idx += 1
# return min_idx
# def best_point():
# min_energy = 0
# idx = 0
# for energy in internal["energy"]:
# if energy < min_energy or abs(energy - min_energy) < 1e-10:
# min_energy = energy
# min_idx = idx
# idx += 1
# return min_idx
# def line_search(fh):
# X1 = internal["position"][-1] # numpy array
# e1 = internal["energy"][-1]
# G1 = internal["gradient"][-1] # numpy array
# idx = best_previous_point()
# X0 = internal["position"][idx] # numpy array
# e0 = internal["energy"][idx]
# G0 = internal["gradient"][idx] # numpy array
# # First try a quartic fit
# fh.write("Attempting a quartic fit.\n")
# success, y0 = quartic_fit(X0, X1, e0, e1, G0, G1, fh)
# if success and y0 > 0:
# if y0 < 1:
# new_point = X0 + y0 * (X1 - X0)
# new_gradient = interpolate_gradient(G0, G1, y0)
# new_gradient = perpendicular_projection(new_gradient, X1 - X0)
# fh.write("Line search succeded.\n")
# return True, new_point, new_gradient
# else:
# idx = best_point()
# if idx == len(internal["energy"]) - 1:
# new_point = X0 + y0 * (X1 - X0)
# new_gradient = interpolate_gradient(G0, G1, y0)
# new_gradient = perpendicular_projection(new_gradient, X1 - X0)
# fh.write("Line search succeded.\n")
# return True, new_point, new_gradient
# else:
# fh.write("Quartic step is not acceptable. ")
# elif success:
# fh.write("Quartic step is not acceptable. ")
# # If no condition is met, then y0 is unacceptable. Try the cubic fit next
# fh.write("Attempting a cubic fit.\n")
# success, y0 = cubic_fit(X0, X1, e0, e1, G0, G1, fh)
# if success and y0 > 0:
# if y0 < 1:
# new_point = X0 + y0 * (X1 - X0)
# new_gradient = interpolate_gradient(G0, G1, y0)
# new_gradient = perpendicular_projection(new_gradient, X1 - X0)
# fh.write("Line search succeded.\n")
# return True, new_point, new_gradient
# else:
# previous_step = X1 - internal["position"][-2]
# previous_step_size = linalg.norm(previous_step)
# new_point = X0 + y0 * (X1 - X0)
# step = new_point - X1
# step_size = linalg.norm(step)
# if step_size < previous_step_size:
# new_gradient = interpolate_gradient(G0, G1, y0)
# new_gradient = perpendicular_projection(new_gradient, X1 - X0)
# fh.write("Line search succeded.\n")
# return True, new_point, new_gradient
# else:
# fh.write("Cubic step is not acceptable. ")
# elif success:
# fh.write("Cubic step is not acceptable. ")
# # If no condition is met again, then all fits fail.
# fh.write("All fits fail. ")
# # Then, if the latest point is not the best, use y0 = 0.5 (step to the midpoint)
# idx = best_point()
# if idx < len(internal["energy"]) - 1:
# y0 = 0.5
# new_point = X0 + y0 * (X1 - X0)
# new_gradient = interpolate_gradient(G0, G1, y0)
# new_gradient = perpendicular_projection(new_gradient, X1 - X0)
# fh.write("Moving to the midpoint.\n")
# return True, new_point, new_gradient
# # If the latest point is the best point, no linear search is done
# fh.write("No linear search will be used in this step.\n")
# return False, None, None
# ## For cubic and quartic fits, G0 and G1 are the gradient vectors
# def cubic_fit(X0, X1, e0, e1, G0, G1, fh):
# line = X1 - X0
# line /= linalg.norm(line)
# g0 = np.dot(G0, line)
# g1 = np.dot(G1, line)
# De = e1 - e0
# fh.write(
# "De = {:<18.15e} g0 = {:<12.8f} g1 = {:<12.8f}\n".format(De, g0, g1)
# )
# alpha = g1 + g0 - 2 * De
# if abs(alpha) < epsilon:
# fh.write("Cubic fit failed: alpha too small\n")
# return False, None
# beta = 3 * De - 2 * g0 - g1
# discriminant = 4 * (beta**2 - 3 * alpha * g0)
# if discriminant < 0:
# fh.write("Cubic fit failed: no minimum found (negative Delta)\n")
# return False, None
# if abs(discriminant) < epsilon:
# fh.write("Cubic fit failed: no minimum found (null Delta)\n")
# return False, None
# y0 = (-beta + math.sqrt(discriminant / 4)) / (3 * alpha)
# fh.write("Minimum found with y0 = {:<8.4f}\n".format(y0))
# return True, y0
# def quartic_fit(X0, X1, e0, e1, G0, G1, fh):
# line = X1 - X0
# line /= linalg.norm(line)
# g0 = np.dot(G0, line)
# g1 = np.dot(G1, line)
# De = e1 - e0
# Dg = g1 - g0
# fh.write(
# "De = {:<18.15e} g0 = {:<12.8f} g1 = {:<12.8f}\n".format(De, g0, g1)
# )
# if Dg < 0 or De - g0 < 0:
# fh.write("Quartic fit failed: negative alpha\n")
# return False, None
# if abs(Dg) < epsilon or abs(De - g0) < epsilon:
# fh.write("Quartic fit failed: alpha too small\n")
# return False, None
# discriminant = 16 * (Dg**2 - 3 * (g1 + g0 - 2 * De) ** 2)
# if discriminant < 0:
# fh.write("Quartic fit failed: no minimum found (negative Delta)\n")
# return False, None
# alpha1 = (Dg + math.sqrt(discriminant / 16)) / 2
# alpha2 = (Dg - math.sqrt(discriminant / 16)) / 2
# fh.write("alpha1 = {:<7.4e} alpha2 = {:<7.4e}\n".format(alpha1, alpha2))
# alpha = alpha1
# beta = g1 + g0 - 2 * De - 2 * alpha
# gamma = De - g0 - alpha - beta
# y0 = (-1 / (2 * alpha)) * (
# (beta**3 - 4 * alpha * beta * gamma + 8 * g0 * alpha**2) / 4
# ) ** (1 / 3)
# fh.write("Minimum found with y0 = {:<8.4f}\n".format(y0))
# return True, y0
# def rfo_step(gradient, hessian, type):
# dim = len(gradient)
# aug_hessian = []
# for i in range(dim):
# aug_hessian.extend(hessian[i, :].tolist())
# aug_hessian.append(gradient[i])
# aug_hessian.extend(gradient.tolist())
# aug_hessian.append(0)
# aug_hessian = np.array(aug_hessian).reshape(dim + 1, dim + 1)
# evals, evecs = linalg.eigh(aug_hessian)
# if type == "min":
# step = np.array(evecs[:-1, 0])
# elif type == "ts":
# step = np.array(evecs[:-1, 1])
# return step
# def update_trust_radius():
# if internal["trust_radius"] == None:
# internal["trust_radius"] = player["maxstep"]
# elif len(internal["energy"]) > 1:
# X1 = internal["position"][-1]
# X0 = internal["position"][-2]
# Dx = X1 - X0
# displace = linalg.norm(Dx)
# e1 = internal["energy"][-1]
# e0 = internal["energy"][-2]
# De = e1 - e0
# g0 = internal["gradient"][-2]
# h0 = internal["hessian"][-2]
# rho = De / (np.dot(g0, Dx) + 0.5 * np.dot(Dx, np.matmul(h0, Dx.T).T))
# if rho > 0.75 and displace > 0.8 * internal["trust_radius"]:
# internal["trust_radius"] = 2 * internal["trust_radius"]
# elif rho < 0.25:
# internal["trust_radius"] = 0.25 * displace
# return
# def interpolate_gradient(G0, G1, y0):
# DG = G1 - G0
# gradient = G0 + y0 * DG
# return gradient
# def perpendicular_projection(vector, line):
# direction = line / linalg.norm(line)
# projection = np.dot(vector, direction) * direction
# return vector - projection

View File

@@ -1,21 +0,0 @@
from dataclasses import dataclass
from typing import List
from diceplayer.DPpack.Environment.Molecule import Molecule
@dataclass
class StepDTO:
cycle: int = None
initcyc: int = None
nprocs: int = None
ncores: int = None
altsteps: int = None
switchcyc: int = None
opt: str = None
nmol: List[int] = None
molecule: List[Molecule] = None
charges: List[float] = None
position: List[float] = None

View File

@@ -1,15 +0,0 @@
def NotNull(requiredArgs=[]):
def _NotNull(function):
def wrapper(*args, **kwargs):
for arg in requiredArgs:
try:
assert (
kwargs.get(arg) is not None
), "Invalid Config File. Keyword {} is required".format(arg)
except AssertionError as err:
print(err)
return function(*args, **kwargs)
return wrapper
return _NotNull

4
diceplayer/__init__.py Normal file
View File

@@ -0,0 +1,4 @@
from diceplayer.shared.utils.logger import Logger
logger = Logger(__name__)

View File

@@ -1,21 +1,21 @@
from diceplayer.shared.interface.dice_interface import DiceInterface
from diceplayer.player import Player
from diceplayer import logger
from pathlib import Path
import argparse
import os
import logging
import pickle
import shutil
import sys
import setproctitle
from diceplayer.DPpack.Player import Player
from diceplayer.DPpack.Utils.Misc import *
__VERSION = "v0.0.1"
os.nice(+19)
setproctitle.setproctitle("diceplayer-{}".format(__VERSION))
if __name__ == "__main__":
#### Read and store the arguments passed to the program ####
#### and set the usage and help messages ####
def main():
"""
Read and store the arguments passed to the program
and set the usage and help messages
"""
parser = argparse.ArgumentParser(prog="Diceplayer")
parser.add_argument(
@@ -38,173 +38,37 @@ if __name__ == "__main__":
metavar="OUTFILE",
help="output file of diceplayer [default = run.log]"
)
## Study the option of a parameter for continuing the last process via data from control.in and run.log files
args = parser.parse_args()
#### Open OUTFILE for writing and print keywords and initial info
try:
if args.opt_continue and os.path.exists(args.outfile):
save = pickle.load(open("latest-step.pkl", "rb"))
if os.path.isfile(args.outfile + ".backup"):
os.remove(args.outfile + ".backup")
os.rename(args.outfile, args.outfile + ".backup")
outfile = open(args.outfile, "w", 1)
elif os.path.exists(args.outfile):
os.rename(args.outfile, args.outfile + ".backup")
outfile = open(args.outfile, "w", 1)
else:
outfile = open(args.outfile, "w", 1)
except Exception as err:
sys.exit(err)
try:
if os.path.exists(args.infile):
infile = open(args.infile, "r")
except Exception as err:
sys.exit(err)
#### Read and check the keywords in INFILE
player = Player(infile, outfile)
player.read_keywords()
player.check_keywords()
player.print_keywords()
# Open OUTFILE for writing and print keywords and initial info
logger.set_logger(args.outfile, logging.INFO)
if args.opt_continue:
player.initcyc = save[0] + 1
player.system = save[1]
player = Player.from_save()
else:
player.initcyc = 1
player.read_potential()
player = Player.from_file(args.infile)
#### Check whether the executables are in the path
#### and print potential to Log File
player.read_potentials()
player.check_executables()
player.create_simulation_dir()
player.create_geoms_file()
player.print_potential()
player.print_keywords()
#### Bring the molecules to standard orientation and prints info about them
player.print_potentials()
for i in range(len(player.system.molecule)):
player.prepare_system()
player.outfile.write(
"\nMolecule type {} - {}:\n\n".format(
i + 1, player.system.molecule[i].molname
)
)
player.system.molecule[i].print_mol_info(player.outfile)
player.outfile.write(
" Translating and rotating molecule to standard orientation..."
)
player.system.molecule[i].standard_orientation()
player.outfile.write(" Done\n\n New values:\n")
player.system.molecule[i].print_mol_info(player.outfile)
player.start()
player.outfile.write(90 * "=")
player.outfile.write("\n")
logger.info("\n+" + 88 * "-" + "+\n")
if not args.opt_continue:
make_simulation_dir()
else:
simdir = "simfiles"
stepdir = "step{:02d}".format(player.initcyc)
if os.path.exists(simdir + os.sep + stepdir):
shutil.rmtree(simdir + os.sep + stepdir)
player.print_results()
#### Open the geoms.xyz file and prints the initial geometry if starting from zero
logger.info("\n+" + 88 * "-" + "+\n")
if player.initcyc == 1:
try:
path = "geoms.xyz"
geomsfh = open(path, "w", 1)
except EnvironmentError as err:
sys.exit(err)
player.system.print_geom(0, geomsfh)
geomsfh.write(40 * "-" + "\n")
else:
try:
path = "geoms.xyz"
geomsfh = open(path, "a", 1)
except EnvironmentError as err:
sys.exit(err)
logger.info("Diceplayer finished successfully \n")
player.outfile.write("\nStarting the iterative process.\n")
## Initial position (in Bohr)
position = player.system.molecule[0].read_position()
## If restarting, read the last gradient and hessian
# if player.initcyc > 1:
# if player.qmprog in ("g03", "g09", "g16"):
# Gaussian.read_forces("grad_hessian.dat")
# Gaussian.read_hessian_fchk("grad_hessian.dat")
# if player['qmprog'] == "molcas":
# Molcas.read_forces("grad_hessian.dat")
# Molcas.read_hessian("grad_hessian.dat")
###
### Start the iterative process
###
player.outfile.write("\n" + 90 * "-" + "\n")
for cycle in range(player.initcyc, player.initcyc + player.maxcyc):
player.outfile.write("{} Step # {}\n".format(40 * " ", cycle))
player.outfile.write(90 * "-" + "\n\n")
make_step_dir(cycle)
####
#### Start block of parallel simulations
####
player.dice_start(cycle)
###
### End of parallel simulations block
###
## After ASEC is built, compress files bigger than 1MB
for proc in range(1, player.nprocs + 1):
path = "simfiles"+os.sep+"step{:02d}".format(cycle) + os.sep + "p{:02d}".format(proc)
compress_files_1mb(path)
###
### Start QM calculation
###
player.gaussian_start(cycle, geomsfh)
player.system.print_geom(cycle, geomsfh)
geomsfh.write(40 * "-" + "\n")
player.outfile.write("\n+" + 88 * "-" + "+\n")
pickle.dump([cycle, player.system], open("latest-step.pkl", "wb"))
####
#### End of the iterative process
####
## imprimir ultimas mensagens, criar um arquivo de potencial para ser usado em eventual
## continuacao, fechar arquivos (geoms.xyz, run.log, ...)
player.outfile.write("\nDiceplayer finished normally!\n")
player.outfile.close()
####
#### End of the program
####
if __name__ == "__main__":
main()

480
diceplayer/player.py Normal file
View File

@@ -0,0 +1,480 @@
from diceplayer.shared.interface.gaussian_interface import GaussianInterface
from diceplayer.shared.interface.dice_interface import DiceInterface
from diceplayer.shared.utils.dataclass_protocol import Dataclass
from diceplayer.shared.config.gaussian_config import GaussianDTO
from diceplayer.shared.config.player_config import PlayerConfig
from diceplayer.shared.config.dice_config import DiceConfig
from diceplayer.shared.utils.misc import weekday_date_time
from diceplayer.shared.environment.molecule import Molecule
from diceplayer.shared.environment.system import System
from diceplayer.shared.environment.atom import Atom
from diceplayer.shared.utils.ptable import atomsymb
from diceplayer import logger
from dataclasses import fields
from typing import Type, Tuple
from pathlib import Path
import pickle
import yaml
import sys
import os
ENV = ["OMP_STACKSIZE"]
class Player:
def __init__(self, infile: str = None, optimization: bool = False):
if infile is None and optimization is False:
raise ValueError("Must specify either infile or optimization")
elif infile is not None:
self.config = self.set_config(
self.read_keywords(infile)
)
self.system = System()
self.initial_cycle = 1
elif optimization is True:
save = self.load_run_from_pickle()
self.config = save[0]
self.system = save[1]
self.initial_cycle = save[2] + 1
else:
raise ValueError("Must specify either infile or config")
self.dice_interface = DiceInterface()
self.gaussian_interface = GaussianInterface()
def start(self):
logger.info(
"==========================================================================================\n"
"Starting the iterative process.\n"
"==========================================================================================\n"
)
for cycle in range(self.initial_cycle, self.initial_cycle + self.config.maxcyc):
logger.info(
f"------------------------------------------------------------------------------------------\n"
f" Step # {cycle}\n"
f"------------------------------------------------------------------------------------------\n"
)
self.dice_start(cycle)
try:
self.gaussian_start(cycle)
except StopIteration:
break
self.save_run_in_pickle(cycle)
def prepare_system(self):
for i, mol in enumerate(self.system.molecule):
logger.info(
f"Molecule {i + 1} - {mol.molname}"
)
mol.print_mol_info()
logger.info(
"\n Translating and rotating molecule to standard orientation..."
)
mol.standard_orientation()
logger.info("\n Done")
logger.info("\nNew values:\n")
mol.print_mol_info()
logger.info("\n")
def create_simulation_dir(self):
simulation_dir_path = Path(self.config.simulation_dir)
if simulation_dir_path.exists():
raise FileExistsError(
f"Error: a file or a directory {self.config.simulation_dir} already exists,"
f" move or delete the simfiles directory to continue."
)
simulation_dir_path.mkdir()
def create_geoms_file(self):
geoms_file_path = Path(self.config.geoms_file)
if geoms_file_path.exists():
raise FileExistsError(
f"Error: a file or a directory {self.config.geoms_file} already exists,"
f" move or delete the simfiles directory to continue."
)
geoms_file_path.touch()
def print_keywords(self) -> None:
def log_keywords(config: Dataclass, dto: Type[Dataclass]):
for key in sorted(list(map(lambda f: f.name, fields(dto)))):
if getattr(config, key) is not None:
if isinstance(getattr(config, key), list):
string = " ".join(str(x) for x in getattr(config, key))
logger.info(f"{key} = [ {string} ]")
else:
logger.info(f"{key} = {getattr(config, key)}")
logger.info(
"##########################################################################################\n"
"############# Welcome to DICEPLAYER version 1.0 #############\n"
"##########################################################################################\n"
)
logger.info("Your python version is {}\n".format(sys.version))
logger.info("Program started on {}\n".format(weekday_date_time()))
logger.info("Environment variables:")
for var in ENV:
logger.info(
"{} = {}\n".format(
var, (os.environ[var] if var in os.environ else "Not set")
)
)
logger.info(
"------------------------------------------------------------------------------------------\n"
" DICE variables being used in this run:\n"
"------------------------------------------------------------------------------------------\n"
)
log_keywords(self.config.dice, DiceConfig)
logger.info(
"------------------------------------------------------------------------------------------\n"
" GAUSSIAN variables being used in this run:\n"
"------------------------------------------------------------------------------------------\n"
)
log_keywords(self.config.gaussian, GaussianDTO)
logger.info("\n")
def read_potentials(self):
ljname_path = Path(self.config.dice.ljname)
if ljname_path.exists():
with open(self.config.dice.ljname) as file:
ljc_data = file.readlines()
else:
raise RuntimeError(
f"Potential file {self.config.dice.ljname} not found."
)
combrule = ljc_data.pop(0).split()[0]
if combrule not in ("*", "+"):
sys.exit(
"Error: expected a '*' or a '+' sign in 1st line of file {}".format(
self.config.dice.ljname
)
)
self.config.dice.combrule = combrule
ntypes = ljc_data.pop(0).split()[0]
if not ntypes.isdigit():
sys.exit(
"Error: expected an integer in the 2nd line of file {}".format(
self.config.dice.ljname
)
)
ntypes = int(ntypes)
if ntypes != len(self.config.dice.nmol):
sys.exit(
f"Error: number of molecule types in file {self.config.dice.ljname} "
f"must match that of 'nmol' keyword in config file"
)
for i in range(ntypes):
try:
nsites, molname = ljc_data.pop(0).split()[:2]
except ValueError:
raise ValueError(
f"Error: expected nsites and molname for the molecule type {i + 1}"
)
if not nsites.isdigit():
raise ValueError(
f"Error: expected nsites to be an integer for molecule type {i + 1}"
)
nsites = int(nsites)
self.system.add_type(Molecule(molname))
atom_fields = ["lbl", "na", "rx", "ry", "rz", "chg", "eps", "sig"]
for j in range(nsites):
new_atom = dict(zip(
atom_fields,
ljc_data.pop(0).split()
))
self.system.molecule[i].add_atom(
Atom(**self.validate_atom_dict(i, j, new_atom))
)
def print_potentials(self) -> None:
formatstr = "{:<3d} {:>3d} {:>10.5f} {:>10.5f} {:>10.5f} {:>10.6f} {:>9.5f} {:>7.4f} {:>9.4f}"
logger.info(
"==========================================================================================\n"
f" Potential parameters from file {self.config.dice.ljname}:\n"
"------------------------------------------------------------------------------------------"
"\n"
)
logger.info(f"Combination rule: {self.config.dice.combrule}")
logger.info(
f"Types of molecules: {len(self.system.molecule)}\n"
)
i = 0
for mol in self.system.molecule:
i += 1
logger.info(
"{} atoms in molecule type {}:".format(len(mol.atom), i)
)
logger.info(
"---------------------------------------------------------------------------------"
)
logger.info(
"Lbl AN X Y Z Charge Epsilon Sigma Mass"
)
logger.info(
"---------------------------------------------------------------------------------"
)
for atom in mol.atom:
logger.info(
formatstr.format(
atom.lbl,
atom.na,
atom.rx,
atom.ry,
atom.rz,
atom.chg,
atom.eps,
atom.sig,
atom.mass,
)
)
logger.info("\n")
def dice_start(self, cycle: int):
self.dice_interface.configure(
self.config,
self.system,
)
self.dice_interface.start(cycle)
self.dice_interface.reset()
def gaussian_start(self, cycle: int):
self.gaussian_interface.configure(
self.config,
self.system,
)
result = self.gaussian_interface.start(cycle)
self.gaussian_interface.reset()
if self.config.opt:
if 'position' not in result:
raise RuntimeError(
'Optimization failed. No position found in result.'
)
self.system.update_molecule(result['position'])
else:
if 'charges' not in result:
raise RuntimeError(
'Charges optimization failed. No charges found in result.'
)
diff = self.system.molecule[0] \
.update_charges(result['charges'])
self.system.print_charges_and_dipole(cycle)
self.print_geoms(cycle)
if diff < self.config.gaussian.chg_tol:
logger.info(
f'Charges converged after {cycle} cycles.'
)
raise StopIteration()
def print_geoms(self, cycle: int):
with open(self.config.geoms_file, 'a') as file:
file.write(f'Cycle # {cycle}\n')
for atom in self.system.molecule[0].atom:
symbol = atomsymb[atom.na]
file.write(
f'{symbol:<2s} {atom.rx:>10.6f} {atom.ry:>10.6f} {atom.rz:>10.6f}\n'
)
file.write('\n')
@staticmethod
def validate_atom_dict(molecule_type, molecule_site, atom_dict: dict) -> dict:
molecule_type += 1
molecule_site += 1
if len(atom_dict) < 8:
raise ValueError(
f'Invalid number of fields for site {molecule_site} for molecule type {molecule_type}.'
)
try:
atom_dict['lbl'] = int(atom_dict['lbl'])
except Exception:
raise ValueError(
f'Invalid lbl fields for site {molecule_site} for molecule type {molecule_type}.'
)
try:
atom_dict['na'] = int(atom_dict['na'])
except Exception:
raise ValueError(
f'Invalid na fields for site {molecule_site} for molecule type {molecule_type}.'
)
try:
atom_dict['rx'] = float(atom_dict['rx'])
except Exception:
raise ValueError(
f'Invalid rx fields for site {molecule_site} for molecule type {molecule_type}. '
f'Value must be a float.'
)
try:
atom_dict['ry'] = float(atom_dict['ry'])
except Exception:
raise ValueError(
f'Invalid ry fields for site {molecule_site} for molecule type {molecule_type}. '
f'Value must be a float.'
)
try:
atom_dict['rz'] = float(atom_dict['rz'])
except Exception:
raise ValueError(
f'Invalid rz fields for site {molecule_site} for molecule type {molecule_type}. '
f'Value must be a float.'
)
try:
atom_dict['chg'] = float(atom_dict['chg'])
except Exception:
raise ValueError(
f'Invalid chg fields for site {molecule_site} for molecule type {molecule_type}. '
f'Value must be a float.'
)
try:
atom_dict['eps'] = float(atom_dict['eps'])
except Exception:
raise ValueError(
f'Invalid eps fields for site {molecule_site} for molecule type {molecule_type}. '
f'Value must be a float.'
)
try:
atom_dict['sig'] = float(atom_dict['sig'])
except Exception:
raise ValueError(
f'Invalid sig fields for site {molecule_site} for molecule type {molecule_type}. '
f'Value must be a float.'
)
return atom_dict
def print_results(self):
formatstr = "{:<3d} {:>3d} {:>10.5f} {:>10.5f} {:>10.5f} {:>10.6f} {:>9.5f} {:>7.4f} {:>9.4f}"
mol = self.system.molecule[0]
logger.info(
"{} atoms in molecule type {}:".format(len(mol.atom), 1)
)
logger.info(
"---------------------------------------------------------------------------------"
)
logger.info(
"Lbl AN X Y Z Charge Epsilon Sigma Mass"
)
logger.info(
"---------------------------------------------------------------------------------"
)
for atom in mol.atom:
logger.info(
formatstr.format(
atom.lbl,
atom.na,
atom.rx,
atom.ry,
atom.rz,
atom.chg,
atom.eps,
atom.sig,
atom.mass,
)
)
logger.info("\n")
def save_run_in_pickle(self, cycle):
try:
with open('latest-step.pkl', 'wb') as pickle_file:
pickle.dump(
(self.config, self.system, cycle),
pickle_file
)
except Exception:
raise RuntimeError(
f'Could not save pickle file latest-step.pkl.'
)
@staticmethod
def load_run_from_pickle() -> Tuple[PlayerConfig, System, int]:
pickle_path = Path("latest-step.pkl")
try:
with open(pickle_path, 'rb') as pickle_file:
save = pickle.load(pickle_file)
return save[0], save[1], save[2] + 1
except Exception:
raise RuntimeError(
f'Could not load pickle file {pickle_path}.'
)
@staticmethod
def set_config(data: dict) -> PlayerConfig:
return PlayerConfig.from_dict(data)
@staticmethod
def read_keywords(infile) -> dict:
with open(infile, 'r') as yml_file:
config = yaml.load(yml_file, Loader=yaml.SafeLoader)
if "diceplayer" in config:
return config.get("diceplayer")
raise RuntimeError(
f'Could not find diceplayer section in {infile}.'
)
@classmethod
def from_file(cls, infile: str) -> 'Player':
return cls(infile=infile)
@classmethod
def from_save(cls):
return cls(optimization=True)

View File

@@ -0,0 +1,56 @@
from diceplayer.shared.utils.dataclass_protocol import Dataclass
from dataclasses import dataclass
from dacite import from_dict
from typing import List
@dataclass
class DiceConfig(Dataclass):
"""
Data Transfer Object for the Dice configuration.
"""
ljname: str
outname: str
dens: float
nmol: List[int]
nstep: List[int]
upbuf = 360
combrule = "*"
isave: int = 1000
press: float = 1.0
temp: float = 300.0
progname: str = "dice"
randominit: str = 'first'
def __post_init__(self):
if not isinstance(self.ljname, str):
raise ValueError(
"Error: 'ljname' keyword not specified in config file"
)
if not isinstance(self.outname, str):
raise ValueError(
"Error: 'outname' keyword not specified in config file"
)
if not isinstance(self.dens, float):
raise ValueError(
"Error: 'dens' keyword not specified in config file"
)
if not isinstance(self.nmol, list):
raise ValueError(
"Error: 'nmol' keyword not defined appropriately in config file"
)
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"
)
@classmethod
def from_dict(cls, param: dict):
return from_dict(DiceConfig, param)

View File

@@ -0,0 +1,32 @@
from diceplayer.shared.utils.dataclass_protocol import Dataclass
from dataclasses import dataclass
from dacite import from_dict
@dataclass
class GaussianDTO(Dataclass):
"""
Data Transfer Object for the Gaussian configuration.
"""
level: str
qmprog: str
chgmult = [0, 1]
pop: str = 'chelpg'
chg_tol: float = 0.01
keywords: str = None
def __post_init__(self):
if self.qmprog not in ("g03", "g09", "g16"):
raise ValueError(
"Error: invalid qmprog value."
)
if self.level is None:
raise ValueError(
"Error: 'level' keyword not specified in config file."
)
@classmethod
def from_dict(cls, param: dict):
return from_dict(GaussianDTO, param)

View File

@@ -0,0 +1,48 @@
from diceplayer.shared.utils.dataclass_protocol import Dataclass
from diceplayer.shared.config.gaussian_config import GaussianDTO
from diceplayer.shared.config.dice_config import DiceConfig
from dataclasses import dataclass
from dacite import from_dict
@dataclass
class PlayerConfig(Dataclass):
"""
Data Transfer Object for the player configuration.
"""
opt: bool
maxcyc: int
nprocs: int
ncores: int
dice: DiceConfig
gaussian: GaussianDTO
mem: int = None
switchcyc: int = 3
qmprog: str = 'g16'
altsteps: int = 20000
geoms_file = 'geoms.xyz'
simulation_dir = 'simfiles'
def __post_init__(self):
MIN_STEP = 20000
# altsteps value is always the nearest multiple of 1000
self.altsteps = round(max(MIN_STEP, self.altsteps) / 1000) * 1000
@classmethod
def from_dict(cls, param: dict):
if param['dice'] is None:
raise ValueError(
"Error: 'dice' keyword not specified in config file."
)
param['dice'] = DiceConfig.from_dict(param['dice'])
if param['gaussian'] is None:
raise ValueError(
"Error: 'gaussian' keyword not specified in config file."
)
param['gaussian'] = GaussianDTO.from_dict(param['gaussian'])
return from_dict(PlayerConfig, param)

View File

@@ -1,5 +1,4 @@
from diceplayer.DPpack.Utils.PTable import *
from diceplayer.DPpack.Utils.Misc import *
from diceplayer.shared.utils.ptable import atommass
class Atom:
@@ -16,16 +15,17 @@ class Atom:
eps (float): quantum number epsilon of the represented atom.
sig (float): quantum number sigma of the represented atom.
"""
def __init__(
self,
lbl: int,
na: int,
rx: float,
ry: float,
rz: float,
chg: float,
eps: float,
sig: float,
self,
lbl: int,
na: int,
rx: float,
ry: float,
rz: float,
chg: float,
eps: float,
sig: float,
) -> None:
"""
The constructor function __init__ is used to create new instances of the Atom class.

View File

@@ -1,25 +1,20 @@
from diceplayer.DPpack.Utils.PTable import *
from diceplayer.DPpack.Utils.Misc import *
from __future__ import annotations
from diceplayer.DPpack.Environment.Atom import Atom
from diceplayer.shared.utils.ptable import ghost_number
from diceplayer.shared.environment.atom import Atom
from diceplayer.shared.utils.misc import BOHR2ANG
from diceplayer import logger
from typing import IO, Any, Final, Tuple, List, TextIO
from nptyping import Float, NDArray, Shape
from numpy import linalg
from nptyping import NDArray, Shape, Float
from numpy.linalg import linalg
import numpy as np
from typing import List, Any, Tuple, Union
from copy import deepcopy
import sys, math
import sys
import logging
import math
""" Constants of unit conversion """
BOHR2ANG: Final[float] = 0.52917721092
ANG2BOHR: Final[float] = 1 / BOHR2ANG
class Molecule:
"""
Molecule class declaration. This class is used throughout the DicePlayer program to represent molecules.
@@ -52,9 +47,9 @@ class Molecule:
self.ghost_atoms: List[Atom] = []
self.lp_atoms: List[Atom] = []
self.total_mass: int = 0
self.com: NDArray[Any, Any] = None
self.com: Union[None, NDArray[Any, Any]] = None
def add_atom(self, a: Atom) -> None:
"""
@@ -67,13 +62,9 @@ class Molecule:
self.atom.append(a)
self.total_mass += a.mass
if a.na == ghost_number:
self.ghost_atoms.append(self.atom.index(a))
self.center_of_mass()
def center_of_mass(self) -> None:
def center_of_mass(self) -> NDArray[Any, Any]:
"""
Calculates the center of mass of the molecule
"""
@@ -81,24 +72,23 @@ class Molecule:
self.com = np.zeros(3)
for atom in self.atom:
self.com += atom.mass * np.array([atom.rx, atom.ry, atom.rz])
self.com = self.com / self.total_mass
return self.com
def center_of_mass_to_origin(self) -> None:
"""
Updated positions based on the center of mass of the molecule
"""
self.center_of_mass()
for atom in self.atom:
atom.rx -= self.com[0]
atom.ry -= self.com[1]
atom.rz -= self.com[2]
self.center_of_mass()
def charges_and_dipole(self) -> List[float]:
"""
Calculates the charges and dipole of the molecule atoms
@@ -121,7 +111,7 @@ class Molecule:
return [charge, dipole[0], dipole[1], dipole[2], total_dipole]
def distances_between_atoms(self) -> NDArray[Shape["Any,Any"],Float]:
def distances_between_atoms(self) -> NDArray[Shape["Any,Any"], Float]:
"""
Calculates distances between the atoms of the molecule
@@ -131,16 +121,15 @@ class Molecule:
distances = []
dim = len(self.atom)
for atom1 in self.atom:
if atom1.na != ghost_number:
for atom2 in self.atom:
if atom2.na != ghost_number:
dx = atom1.rx - atom2.rx
dy = atom1.ry - atom2.ry
dz = atom1.rz - atom2.rz
distances.append(math.sqrt(dx**2 + dy**2 + dz**2))
for index1, atom1 in enumerate(self.atom):
for index2, atom2 in enumerate(self.atom):
if index1 != index2:
dx = atom1.rx - atom2.rx
dy = atom1.ry - atom2.ry
dz = atom1.rz - atom2.rz
distances.append(math.sqrt(dx ** 2 + dy ** 2 + dz ** 2))
return np.array(distances).reshape(dim, dim)
return np.array(distances).reshape(dim, dim-1)
def inertia_tensor(self) -> NDArray[Shape["3, 3"], Float]:
"""
@@ -154,14 +143,13 @@ class Molecule:
Ixx = Ixy = Ixz = Iyy = Iyz = Izz = 0.0
for atom in self.atom:
dx = atom.rx - self.com[0]
dy = atom.ry - self.com[1]
dz = atom.rz - self.com[2]
Ixx += atom.mass * (dy**2 + dz**2)
Iyy += atom.mass * (dz**2 + dx**2)
Izz += atom.mass * (dx**2 + dy**2)
Ixx += atom.mass * (dy ** 2 + dz ** 2)
Iyy += atom.mass * (dz ** 2 + dx ** 2)
Izz += atom.mass * (dx ** 2 + dy ** 2)
Ixy += atom.mass * dx * dy * -1
Ixz += atom.mass * dx * dz * -1
@@ -169,40 +157,6 @@ class Molecule:
return np.array([[Ixx, Ixy, Ixz], [Ixy, Iyy, Iyz], [Ixz, Iyz, Izz]])
def axes(self) -> NDArray[Shape["3, 3"], Float]:
"""
Calculates the axes of the molecule
Returns:
NDArray[Shape["3, 3"], Float]: Returns the axes of molecule
"""
eixos = np.zeros(3)
if len(self.atom) == 2:
position1 = np.array([self.atom[0].rx, self.atom[0].ry, self.atom[0].rz])
position2 = np.array([self.atom[1].rx, self.atom[1].ry, self.atom[1].rz])
eixos = position2 - position1
eixos /= linalg.norm(eixos)
elif len(self.atom) > 2:
position1 = np.array([self.atom[0].rx, self.atom[0].ry, self.atom[0].rz])
position2 = np.array([self.atom[1].rx, self.atom[1].ry, self.atom[1].rz])
position3 = np.array([self.atom[2].rx, self.atom[2].ry, self.atom[2].rz])
v1 = position2 - position1
v2 = position3 - position1
v3 = np.cross(v1, v2)
v2 = np.cross(v1, v3)
v1 /= linalg.norm(v1)
v2 /= linalg.norm(v2)
v3 /= linalg.norm(v3)
eixos = np.array(
[[v1[0], v1[1], v1[2]], [v2[0], v2[1], v2[2]], [v3[0], v3[1], v3[2]]]
)
return eixos
def principal_axes(self) -> Tuple[np.ndarray, np.ndarray]:
"""
Calculates the principal axes of the molecule
@@ -214,8 +168,8 @@ class Molecule:
try:
evals, evecs = linalg.eigh(self.inertia_tensor())
except:
sys.exit("Error: diagonalization of inertia tensor did not converge")
except ValueError:
raise RuntimeError("Error: diagonalization of inertia tensor did not converge")
return evals, evecs
@@ -234,38 +188,45 @@ class Molecule:
return position
def updateCharges(self, charges: List[float]) -> None:
def update_charges(self, charges: NDArray) -> int:
"""
Updates the charges of the atoms of the molecule and
returns the max difference between the new and old charges
"""
diff = 0
for i, atom in enumerate(self.atom):
diff = max(diff, abs(atom.chg - charges[i]))
atom.chg = charges[i]
def update_hessian(
self,
step: np.ndarray,
cur_gradient: np.ndarray,
old_gradient: np.ndarray,
hessian: np.ndarray,
) -> np.ndarray:
"""
Updates the Hessian of the molecule based on the current hessian, the current gradient and the previous gradient
return diff
Args:
step (np.ndarray): step value of the iteration
cur_gradient (np.ndarray): current gradient
old_gradient (np.ndarray): previous gradient
hessian (np.ndarray): current hessian
Returns:
np.ndarray: updated hessian of the molecule
"""
dif_gradient = cur_gradient - old_gradient
mat1 = 1 / np.dot(dif_gradient, step) * np.matmul(dif_gradient.T, dif_gradient)
mat2 = 1 / np.dot(step, np.matmul(hessian, step.T).T)
mat2 *= np.matmul(np.matmul(hessian, step.T), np.matmul(step, hessian))
return hessian + mat1 - mat2
# @staticmethod
# def update_hessian(
# step: np.ndarray,
# cur_gradient: np.ndarray,
# old_gradient: np.ndarray,
# hessian: np.ndarray,
# ) -> np.ndarray:
# """
# Updates the Hessian of the molecule based on the current hessian, the current gradient and the previous gradient
#
# Args:
# step (np.ndarray): step value of the iteration
# cur_gradient (np.ndarray): current gradient
# old_gradient (np.ndarray): previous gradient
# hessian (np.ndarray): current hessian
#
# Returns:
# np.ndarray: updated hessian of the molecule
# """
#
# dif_gradient = cur_gradient - old_gradient
#
# mat1 = 1 / np.dot(dif_gradient, step) * np.matmul(dif_gradient.T, dif_gradient)
# mat2 = 1 / np.dot(step, np.matmul(hessian, step.T).T)
# mat2 *= np.matmul(np.matmul(hessian, step.T), np.matmul(step, hessian))
#
# return hessian + mat1 - mat2
def sizes_of_molecule(self) -> List[float]:
"""
@@ -280,10 +241,9 @@ class Molecule:
z_list = []
for atom in self.atom:
if atom.na != ghost_number:
x_list.append(atom.rx)
y_list.append(atom.ry)
z_list.append(atom.rz)
x_list.append(atom.rx)
y_list.append(atom.ry)
z_list.append(atom.rz)
x_max = max(x_list)
x_min = min(x_list)
@@ -305,21 +265,18 @@ class Molecule:
evals, evecs = self.principal_axes()
if round(linalg.det(evecs)) == -1:
evecs[0, 2] *= -1
evecs[1, 2] *= -1
evecs[2, 2] *= -1
if round(linalg.det(evecs)) != 1:
sys.exit(
raise RuntimeError(
"Error: could not make a rotation matrix while adopting the standard orientation"
)
rot_matrix = evecs.T
for atom in self.atom:
position = np.array([atom.rx, atom.ry, atom.rz])
new_position = np.matmul(rot_matrix, position.T).T
@@ -332,7 +289,7 @@ class Molecule:
Creates a new Molecule object where its' atoms has been translated by a vector
Args:
vector (np.ndarray): translation vector
vector (np.ndarray): translation vector
Returns:
Molecule: new Molecule object translated by a vector
@@ -341,68 +298,64 @@ class Molecule:
new_molecule = deepcopy(self)
for atom in new_molecule.atom:
atom.rx += vector[0]
atom.ry += vector[1]
atom.rz += vector[2]
return new_molecule
def print_mol_info(self, fh: TextIO) -> None:
def print_mol_info(self) -> None:
"""
Prints the Molecule information into a Output File
Args:
fh (TextIO): Output File
"""
fh.write(
" Center of mass = ( {:>10.4f} , {:>10.4f} , {:>10.4f} )\n".format(
logger.info(
" Center of mass = ( {:>10.4f} , {:>10.4f} , {:>10.4f} )".format(
self.com[0], self.com[1], self.com[2]
)
)
inertia = self.inertia_tensor()
evals, evecs = self.principal_axes()
fh.write(
" Moments of inertia = {:>9E} {:>9E} {:>9E}\n".format(
logger.info(
" Moments of inertia = {:>9E} {:>9E} {:>9E}".format(
evals[0], evals[1], evals[2]
)
)
fh.write(
" Major principal axis = ( {:>10.6f} , {:>10.6f} , {:>10.6f} )\n".format(
logger.info(
" Major principal axis = ( {:>10.6f} , {:>10.6f} , {:>10.6f} )".format(
evecs[0, 0], evecs[1, 0], evecs[2, 0]
)
)
fh.write(
" Inter principal axis = ( {:>10.6f} , {:>10.6f} , {:>10.6f} )\n".format(
logger.info(
" Inter principal axis = ( {:>10.6f} , {:>10.6f} , {:>10.6f} )".format(
evecs[0, 1], evecs[1, 1], evecs[2, 1]
)
)
fh.write(
" Minor principal axis = ( {:>10.6f} , {:>10.6f} , {:>10.6f} )\n".format(
logger.info(
" Minor principal axis = ( {:>10.6f} , {:>10.6f} , {:>10.6f} )".format(
evecs[0, 2], evecs[1, 2], evecs[2, 2]
)
)
sizes = self.sizes_of_molecule()
fh.write(
" Characteristic lengths = ( {:>6.2f} , {:>6.2f} , {:>6.2f} )\n".format(
logger.info(
" Characteristic lengths = ( {:>6.2f} , {:>6.2f} , {:>6.2f} )".format(
sizes[0], sizes[1], sizes[2]
)
)
fh.write(" Total mass = {:>8.2f} au\n".format(self.total_mass))
logger.info(" Total mass = {:>8.2f} au".format(self.total_mass))
chg_dip = self.charges_and_dipole()
fh.write(" Total charge = {:>8.4f} e\n".format(chg_dip[0]))
fh.write(
" Dipole moment = ( {:>9.4f} , {:>9.4f} , {:>9.4f} ) Total = {:>9.4f} Debye\n\n".format(
logger.info(" Total charge = {:>8.4f} e".format(chg_dip[0]))
logger.info(
" Dipole moment = ( {:>9.4f} , {:>9.4f} , {:>9.4f} ) Total = {:>9.4f} Debye".format(
chg_dip[1], chg_dip[2], chg_dip[3], chg_dip[4]
)
)
def minimum_distance(self, molec: "Molecule") -> float:
def minimum_distance(self, molec: 'Molecule') -> float:
"""
Return the minimum distance between two molecules
@@ -421,6 +374,6 @@ class Molecule:
dx = atom1.rx - atom2.rx
dy = atom1.ry - atom2.ry
dz = atom1.rz - atom2.rz
distances.append(math.sqrt(dx**2 + dy**2 + dz**2))
distances.append(math.sqrt(dx ** 2 + dy ** 2 + dz ** 2))
return min(distances)

View File

@@ -0,0 +1,233 @@
from diceplayer.shared.environment.molecule import Molecule
from diceplayer.shared.utils.ptable import atomsymb
from diceplayer.shared.utils.misc import BOHR2ANG
from diceplayer import logger
from typing import List, Tuple, TextIO
from copy import deepcopy
from numpy import linalg
import numpy as np
import math
class System:
"""
System class declaration. This class is used throughout the DicePlayer program to represent the system containing the molecules.
Atributes:
molecule (List[Molecule]): List of molecules of the system
nmols (List[int]): List of number of molecules in the system
"""
def __init__(self) -> None:
"""
Initializes an empty system object that will be populated afterwards
"""
self.nmols: List[int] = []
self.molecule: List[Molecule] = []
def add_type(self, m: Molecule) -> None:
"""
Adds a new molecule type to the system
Args:
m (Molecule): The instance of the new type of molecule
"""
if isinstance(m, Molecule) is False:
raise TypeError("Error: molecule is not a Molecule instance")
self.molecule.append(m)
def update_molecule(self, position: np.ndarray) -> None:
"""Updates the position of the molecule in the Output file
Args:
position (np.ndarray): numpy position vector
"""
position_in_ang = (position * BOHR2ANG).tolist()
self.add_type(deepcopy(self.molecule[0]))
for atom in self.molecule[-1].atom:
atom.rx = position_in_ang.pop(0)
atom.ry = position_in_ang.pop(0)
atom.rz = position_in_ang.pop(0)
rmsd, self.molecule[0] = self.rmsd_fit(-1, 0)
self.molecule.pop(-1)
logger.info("Projected new conformation of reference molecule with RMSD fit")
logger.info(f"RMSD = {rmsd:>8.5f} Angstrom")
def rmsd_fit(self, p_index: int, r_index: int) -> Tuple[float, Molecule]:
projecting_mol = self.molecule[p_index]
reference_mol = self.molecule[r_index]
if len(projecting_mol.atom) != len(reference_mol.atom):
raise RuntimeError(
"Error in RMSD fit procedure: molecules have different number of atoms"
)
dim = len(projecting_mol.atom)
new_projecting_mol = deepcopy(projecting_mol)
new_reference_mol = deepcopy(reference_mol)
new_projecting_mol.center_of_mass_to_origin()
new_reference_mol.center_of_mass_to_origin()
x = []
y = []
for atom in new_projecting_mol.atom:
x.extend([atom.rx, atom.ry, atom.rz])
for atom in new_reference_mol.atom:
y.extend([atom.rx, atom.ry, atom.rz])
x = np.array(x).reshape(dim, 3)
y = np.array(y).reshape(dim, 3)
r = np.matmul(y.T, x)
rr = np.matmul(r.T, r)
try:
evals, evecs = linalg.eigh(rr)
except:
raise RuntimeError("Error: diagonalization of RR matrix did not converge")
a1 = evecs[:, 2].T
a2 = evecs[:, 1].T
a3 = np.cross(a1, a2)
A = np.array([a1[0], a1[1], a1[2], a2[0], a2[1], a2[2], a3[0], a3[1], a3[2]])
A = A.reshape(3, 3)
b1 = np.matmul(r, a1.T).T # or np.dot(r, a1)
b1 /= linalg.norm(b1)
b2 = np.matmul(r, a2.T).T # or np.dot(r, a2)
b2 /= linalg.norm(b2)
b3 = np.cross(b1, b2)
B = np.array([b1[0], b1[1], b1[2], b2[0], b2[1], b2[2], b3[0], b3[1], b3[2]])
B = B.reshape(3, 3).T
rot_matrix = np.matmul(B, A)
x = np.matmul(rot_matrix, x.T).T
rmsd = 0
for i in range(dim):
rmsd += (
(x[i, 0] - y[i, 0]) ** 2
+ (x[i, 1] - y[i, 1]) ** 2
+ (x[i, 2] - y[i, 2]) ** 2
)
rmsd = math.sqrt(rmsd / dim)
for i in range(dim):
new_projecting_mol.atom[i].rx = x[i, 0]
new_projecting_mol.atom[i].ry = x[i, 1]
new_projecting_mol.atom[i].rz = x[i, 2]
reference_mol.center_of_mass()
projected_mol = new_projecting_mol.translate(reference_mol.com)
return rmsd, projected_mol
# def center_of_mass_distance(self, a: int, b: int) -> float:
# """
# Calculates the distance between the center of mass of two molecules
#
# Args:
# a (Molecule): First Molecule Instance
# b (Molecule): Second Molecule Instance
#
# Returns:
# float: module of the distance between the two center of masses
# """
#
# com1 = self.molecule[a].center_of_mass()
# com2 = self.molecule[b].center_of_mass()
# dx = com1[0] - com2[0]
# dy = com1[1] - com2[1]
# dz = com1[2] - com2[2]
# distance = math.sqrt(dx**2 + dy**2 + dz**2)
#
# return distance
# def nearest_image(
# self,
# index_r: int,
# index_m: int,
# lx: float,
# ly: float,
# lz: float,
# criterium=None,
# ) -> Tuple[float, Molecule]:
#
# if criterium in None:
# criterium = "com"
#
# if criterium != "com" and criterium != "min":
# raise RuntimeError("Error in value passed to function nearest_image")
#
# min_dist = 1e20
#
# for i in range(-1, 2):
# for j in range(-1, 2):
# for k in range(-1, 2):
#
# tr_vector = [i * lx, j * ly, k * lz]
# self.add_molecule(self.molecule[index_m].translate(tr_vector))
#
# if criterium == "com":
# dist = self.center_of_mass_distance(index_r, -1)
# else:
# dist = self.minimum_distance(index_r, -1)
#
# if dist < min_dist:
# min_dist = dist
# nearestmol = deepcopy(self.molecule[-1])
#
# self.molecule.pop(-1)
#
# return min_dist, nearestmol
# def print_geom(self, cycle: int, fh: TextIO) -> None:
# """
# Print the geometry of the molecule in the Output file
#
# Args:
# cycle (int): Number of the cycle
# fh (TextIO): Output file
# """
#
# fh.write("Cycle # {}\n".format(cycle))
# fh.write("Number of site: {}\n".format(len(self.molecule[0].atom)))
# for atom in self.molecule[0].atom:
# symbol = atomsymb[atom.na]
# fh.write(
# "{:<2s} {:>10.6f} {:>10.6f} {:>10.6f}\n".format(
# symbol, atom.rx, atom.ry, atom.rz
# )
# )
#
def print_charges_and_dipole(self, cycle: int) -> None:
"""
Print the charges and dipole of the molecule in the Output file
Args:
cycle (int): Number of the cycle
fh (TextIO): Output file
"""
logger.info("Cycle # {}\n".format(cycle))
logger.info("Number of site: {}\n".format(len(self.molecule[0].atom)))
chargesAndDipole = self.molecule[0].charges_and_dipole()
logger.info(
"{:>10.6f} {:>10.6f} {:>10.6f} {:>10.6f} {:>10.6f}\n".format(
chargesAndDipole[0], chargesAndDipole[1], chargesAndDipole[2], chargesAndDipole[3], chargesAndDipole[4]
)
)

View File

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

View File

@@ -0,0 +1,29 @@
from __future__ import annotations
from diceplayer.shared.config.player_config import PlayerConfig
from diceplayer.shared.environment.system import System
from abc import ABC, abstractmethod
class Interface(ABC):
__slots__ = [
'step',
'system'
]
def __init__(self):
self.system: System | None = None
self.step: PlayerConfig | None = None
@abstractmethod
def configure(self, step: PlayerConfig, system: System):
pass
@abstractmethod
def start(self, cycle: int):
pass
@abstractmethod
def reset(self):
pass

View File

@@ -0,0 +1,395 @@
from __future__ import annotations
from diceplayer.shared.config.player_config import PlayerConfig
from diceplayer.shared.environment.system import System
from diceplayer.shared.interface import Interface
from diceplayer import logger
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):
title = "Diceplayer run"
def configure(self, step: PlayerConfig, system: System):
self.step = step
self.system = system
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)
logger.info("\n")
def reset(self):
del self.step
del self.system
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.step.dice.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.step.dice.dens = self._new_density(last_xyz_file)
else:
self._make_nvt_ter(cycle, proc_dir)
if len(self.step.dice.nstep) == 2:
self._make_nvt_eq(cycle, proc_dir)
elif len(self.step.dice.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}"
)
logger.info(
f"Simulation process {str(proc_dir)} initiated with pid {os.getpid()}"
)
os.chdir(proc_dir)
if not (self.step.dice.randominit == 'first' and cycle > 1):
self.run_dice_file(cycle, proc, "NVT.ter")
if len(self.step.dice.nstep) == 2:
self.run_dice_file(cycle, proc, "NVT.eq")
elif len(self.step.dice.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()
SECONDARY_MOLECULE_LENGTH = 0
for i in range(1, len(self.step.dice.nmol)):
SECONDARY_MOLECULE_LENGTH += self.step.dice.nmol[i] * len(self.system.molecule[i].atom)
xyz_lines = xyz_lines[-SECONDARY_MOLECULE_LENGTH:]
input_file = Path(proc_dir, self.step.dice.outname + ".xy")
with open(input_file, 'w') as f:
for atom in self.system.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.system.molecule)):
total_mass += self.system.molecule[i].total_mass * self.step.dice.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.step.dice.ljname}\n")
f.write(f"outname = {self.step.dice.outname}\n")
mol_string = " ".join(str(x) for x in self.step.dice.nmol)
f.write(f"nmol = {mol_string}\n")
f.write(f"dens = {self.step.dice.dens}\n")
f.write(f"temp = {self.step.dice.temp}\n")
if self.step.dice.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.step.dice.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.step.dice.upbuf}")
def _make_nvt_eq(self, cycle, 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.step.dice.ljname}\n")
f.write(f"outname = {self.step.dice.outname}\n")
mol_string = " ".join(str(x) for x in self.step.dice.nmol)
f.write(f"nmol = {mol_string}\n")
f.write(f"dens = {self.step.dice.dens}\n")
f.write(f"temp = {self.step.dice.temp}\n")
if self.step.dice.randominit == "first" and cycle > 1:
f.write("init = yesreadxyz\n")
else:
f.write("init = no\n")
f.write(f"nstep = {self.step.dice.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.step.dice.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.step.dice.ljname}\n")
f.write(f"outname = {self.step.dice.outname}\n")
mol_string = " ".join(str(x) for x in self.step.dice.nmol)
f.write(f"nmol = {mol_string}\n")
f.write(f"press = {self.step.dice.press}\n")
f.write(f"temp = {self.step.dice.temp}\n")
if self.step.dice.randominit == "first" and cycle > 1:
f.write("init = yesreadxyz\n")
f.write(f"dens = {self.step.dice.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.step.dice.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.step.dice.ljname}\n")
f.write(f"outname = {self.step.dice.outname}\n")
mol_string = " ".join(str(x) for x in self.step.dice.nmol)
f.write(f"nmol = {mol_string}\n")
f.write(f"press = {self.step.dice.press}\n")
f.write(f"temp = {self.step.dice.temp}\n")
f.write(f"nstep = 5\n")
f.write(f"vstep = {int(self.step.dice.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.step.dice.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.step.dice.ljname)
with open(file, 'w') as f:
f.write(f"{self.step.dice.combrule}\n")
f.write(f"{len(self.step.dice.nmol)}\n")
nsites_qm = len(self.system.molecule[0].atom)
f.write(f"{nsites_qm} {self.system.molecule[0].molname}\n")
for atom in self.system.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.system.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.step.dice.progname} < {infile.name} > {outfile.name}",
]
)
else:
exit_status = subprocess.call(
self.step.dice.progname, stdin=infile, stdout=outfile
)
if exit_status != 0:
raise RuntimeError(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 RuntimeError(f"Dice process step{cycle:02d}-p{proc:02d} did not exit properly")
logger.info(f"Dice {file_name} - step{cycle:02d}-p{proc:02d} exited properly")

View File

@@ -0,0 +1,380 @@
from __future__ import annotations
from diceplayer.shared.config.player_config import PlayerConfig
from diceplayer.shared.environment.molecule import Molecule
from diceplayer.shared.environment.system import System
from diceplayer.shared.environment.atom import Atom
from diceplayer.shared.utils.misc import date_time
from diceplayer.shared.utils.ptable import atomsymb
from diceplayer.shared.interface import Interface
from diceplayer import logger
from typing import Tuple, List, Dict, Any
from nptyping import NDArray
import numpy as np
from pathlib import Path
import subprocess
import textwrap
import shutil
import os
class GaussianInterface(Interface):
def configure(self, step_dto: PlayerConfig, system: System):
self.system = system
self.step = step_dto
def start(self, cycle: int) -> Dict[str, NDArray]:
self._make_qm_dir(cycle)
if cycle > 1:
self._copy_chk_file_from_previous_step(cycle)
asec_charges = self.populate_asec_vdw(cycle)
self._make_gaussian_input_file(
cycle,
asec_charges
)
self._run_gaussian(cycle)
self._run_formchk(cycle)
return_value = {}
if self.step.opt:
# return_value['position'] = np.array(
# self._run_optimization(cycle)
# )
raise NotImplementedError("Optimization not implemented yet.")
else:
return_value['charges'] = np.array(
self._read_charges_from_fchk(cycle)
)
return return_value
def reset(self):
del self.step
del self.system
def _make_qm_dir(self, cycle: int):
qm_dir_path = Path(
self.step.simulation_dir,
f"step{cycle:02d}",
"qm"
)
if not qm_dir_path.exists():
qm_dir_path.mkdir()
def _copy_chk_file_from_previous_step(self, cycle: int):
current_chk_file_path = Path(
self.step.simulation_dir,
f"step{cycle:02d}",
"qm",
f"asec.chk"
)
if current_chk_file_path.exists():
raise FileExistsError(
f"File {current_chk_file_path} already exists."
)
previous_chk_file_path = Path(
self.step.simulation_dir,
f"step{(cycle - 1):02d}",
"qm",
f"asec.chk"
)
if not previous_chk_file_path.exists():
raise FileNotFoundError(
f"File {previous_chk_file_path} does not exist."
)
shutil.copy(previous_chk_file_path, current_chk_file_path)
def populate_asec_vdw(self, cycle: int) -> list[dict]:
norm_factor = self._calculate_norm_factor()
nsitesref = len(self.system.molecule[0].atom)
nsites_total = self._calculate_total_number_of_sites(nsitesref)
proc_charges = []
for proc in range(1, self.step.nprocs + 1):
proc_charges.append(self._read_charges_from_last_step(cycle, proc))
asec_charges, thickness, picked_mols = \
self._evaluate_proc_charges(nsites_total, proc_charges)
logger.info(f"In average, {(sum(picked_mols) / norm_factor):^7.2f} molecules\n"
f"were selected from each of the {len(picked_mols)} configurations\n"
f"of the production simulations to form the ASEC, comprising a shell with\n"
f"minimum thickness of {(sum(thickness) / norm_factor):>6.2f} Angstrom\n"
)
for charge in asec_charges:
charge['chg'] = charge['chg'] / norm_factor
return asec_charges
def _calculate_norm_factor(self) -> int:
if self.step.dice.nstep[-1] % self.step.dice.isave == 0:
nconfigs = round(self.step.dice.nstep[-1] / self.step.dice.isave)
else:
nconfigs = int(self.step.dice.nstep[-1] / self.step.dice.isave)
return nconfigs * self.step.nprocs
def _calculate_total_number_of_sites(self, nsitesref) -> int:
nsites_total = self.step.dice.nmol[0] * nsitesref
for i in range(1, len(self.step.dice.nmol)):
nsites_total += self.step.dice.nmol[i] * len(self.system.molecule[i].atom)
return nsites_total
def _read_charges_from_last_step(self, cycle: int, proc: int) -> list[str]:
last_xyz_file_path = Path(
self.step.simulation_dir,
f"step{cycle:02d}",
f"p{proc:02d}",
"last.xyz"
)
if not last_xyz_file_path.exists():
raise FileNotFoundError(
f"File {last_xyz_file_path} does not exist."
)
with open(last_xyz_file_path, 'r') as last_xyz_file:
lines = last_xyz_file.readlines()
return lines
def _evaluate_proc_charges(self, total_nsites: int, proc_charges: list[list[str]]) -> Tuple[
List[Dict[str, float | Any]], List[float], List[int]]:
asec_charges = []
thickness = []
picked_mols = []
for charges in proc_charges:
charges_nsites = int(charges.pop(0))
if int(charges_nsites) != total_nsites:
raise ValueError(
f"Number of sites does not match total number of sites."
)
thickness.append(
self._calculate_proc_thickness(charges)
)
nsites_ref_mol = len(self.system.molecule[0].atom)
charges = charges[nsites_ref_mol:]
mol_count = 0
for type in range(len(self.step.dice.nmol)):
if type == 0:
# Reference Molecule must be ignored from type 0
nmols = self.step.dice.nmol[type] - 1
else:
nmols = self.step.dice.nmol[type]
for mol in range(nmols):
new_molecule = Molecule("ASEC TMP MOLECULE")
for site in range(len(self.system.molecule[type].atom)):
line = charges.pop(0).split()
if line[0].title() != atomsymb[self.system.molecule[type].atom[site].na].strip():
raise SyntaxError(
f"Error: Invalid Dice Output. Atom type does not match."
)
new_molecule.add_atom(
Atom(
self.system.molecule[type].atom[site].lbl,
self.system.molecule[type].atom[site].na,
float(line[1]),
float(line[2]),
float(line[3]),
self.system.molecule[type].atom[site].chg,
self.system.molecule[type].atom[site].eps,
self.system.molecule[type].atom[site].sig,
)
)
distance = self.system.molecule[0] \
.minimum_distance(new_molecule)
if distance < thickness[-1]:
for atom in new_molecule.atom:
asec_charges.append(
{"lbl": atomsymb[atom.na], "rx": atom.rx, "ry": atom.ry, "rz": atom.rz, "chg": atom.chg}
)
mol_count += 1
picked_mols.append(mol_count)
return asec_charges, thickness, picked_mols
def _calculate_proc_thickness(self, charges: list[str]) -> float:
box = charges.pop(0).split()[-3:]
box = [float(box[0]), float(box[1]), float(box[2])]
sizes = self.system.molecule[0].sizes_of_molecule()
return min(
[
(box[0] - sizes[0]) / 2,
(box[1] - sizes[1]) / 2,
(box[2] - sizes[2]) / 2,
]
)
def _make_gaussian_input_file(self, cycle: int, asec_charges: list[dict]) -> None:
gaussian_input_file_path = Path(
self.step.simulation_dir,
f"step{cycle:02d}",
"qm",
f"asec.gjf"
)
with open(gaussian_input_file_path, 'w') as gaussian_input_file:
gaussian_input_file.writelines(
self._generate_gaussian_input(cycle, asec_charges)
)
def _generate_gaussian_input(self, cycle: int, asec_charges: list[dict]) -> list[str]:
gaussian_input = ["%Chk=asec.chk\n"]
if self.step.mem is not None:
gaussian_input.append(f"%Mem={self.step.mem}GB\n")
gaussian_input.append(f"%Nprocs={self.step.nprocs * self.step.ncores}\n")
kwords_line = f"#P {self.step.gaussian.level}"
if self.step.gaussian.keywords:
kwords_line += " " + self.step.gaussian.keywords
if self.step.opt == "yes":
kwords_line += " Force"
kwords_line += " NoSymm"
kwords_line += f" Pop={self.step.gaussian.pop} Density=Current"
if cycle > 1:
kwords_line += " Guess=Read"
gaussian_input.append(textwrap.fill(kwords_line, 90))
gaussian_input.append("\n")
gaussian_input.append("\nForce calculation - Cycle number {}\n".format(cycle))
gaussian_input.append("\n")
gaussian_input.append(f"{self.step.gaussian.chgmult[0]},{self.step.gaussian.chgmult[1]}\n")
for atom in self.system.molecule[0].atom:
symbol = atomsymb[atom.na]
gaussian_input.append(
"{:<2s} {:>10.5f} {:>10.5f} {:>10.5f}\n".format(
symbol, atom.rx, atom.ry, atom.rz
)
)
gaussian_input.append("\n")
for charge in asec_charges:
gaussian_input.append(
"{:>10.5f} {:>10.5f} {:>10.5f} {:>11.8f}\n".format(
charge['rx'], charge['ry'], charge['rz'], charge['chg']
)
)
gaussian_input.append("\n")
return gaussian_input
def _run_gaussian(self, cycle: int) -> None:
qm_dir = Path(
self.step.simulation_dir,
f"step{(cycle):02d}",
"qm"
)
working_dir = os.getcwd()
os.chdir(qm_dir)
infile = "asec.gjf"
operation = None
if self.step.opt:
operation = "forces"
else:
operation = "charges"
logger.info(
f"Calculation of {operation} initiated with Gaussian on {date_time()}\n"
)
if shutil.which("bash") is not None:
exit_status = subprocess.call(
[
"bash",
"-c",
"exec -a {}-step{} {} {}".format(
self.step.gaussian.qmprog, cycle, self.step.gaussian.qmprog, infile
),
]
)
else:
exit_status = subprocess.call([self.step.gaussian.qmprog, infile])
if exit_status != 0:
raise SystemError("Gaussian process did not exit properly")
logger.info(f"Calculation of {operation} finished on {date_time()}")
os.chdir(working_dir)
def _run_formchk(self, cycle: int):
qm_dir = Path(
self.step.simulation_dir,
f"step{(cycle):02d}",
"qm"
)
work_dir = os.getcwd()
os.chdir(qm_dir)
logger.info("Formatting the checkpoint file... \n")
exit_status = subprocess.call(["formchk", "asec.chk"], stdout=subprocess.DEVNULL)
if exit_status != 0:
raise SystemError("Formchk process did not exit properly")
logger.info("Done\n")
os.chdir(work_dir)
def _read_charges_from_fchk(self, cycle: int):
fchk_file_path = Path(
"simfiles",
f"step{cycle:02d}",
"qm",
"asec.fchk"
)
with open(fchk_file_path) as fchk:
fchkfile = fchk.readlines()
if self.step.gaussian.pop in ["chelpg", "mk"]:
CHARGE_FLAG = "ESP Charges"
else:
CHARGE_FLAG = "ESP Charges"
start = fchkfile.pop(0).strip()
while start.find(CHARGE_FLAG) != 0: # expression in begining of line
start = fchkfile.pop(0).strip()
charges: List[float] = []
while len(charges) < len(self.system.molecule[0].atom):
charges.extend([float(x) for x in fchkfile.pop(0).split()])
return charges

View File

@@ -0,0 +1,6 @@
from typing import runtime_checkable, Protocol
@runtime_checkable
class Dataclass(Protocol):
__dataclass_fields__: dict

View File

@@ -0,0 +1,73 @@
from pathlib import Path
import logging
def valid_logger(func):
def wrapper(*args, **kwargs):
logger = args[0]
assert logger._was_set, \
"Logger is not set. Please call set_logger() first."
return func(*args, **kwargs)
return wrapper
class Logger:
outfile = None
_logger = None
_was_set = False
def __init__(self, logger_name):
if self._logger is None:
self._logger = logging.getLogger(logger_name)
def set_logger(self, outfile='run.log', level=logging.INFO, stream=None):
outfile_path = None
if outfile is not None and stream is None:
outfile_path = Path(outfile)
if outfile_path.exists():
outfile_path.rename(str(outfile_path) + ".backup")
if level is not None:
self._logger.setLevel(level)
self._create_handlers(outfile_path, stream)
self._was_set = True
@valid_logger
def info(self, message):
self._logger.info(message)
@valid_logger
def debug(self, message):
self._logger.debug(message)
@valid_logger
def warning(self, message):
self._logger.warning(message)
@valid_logger
def error(self, message):
self._logger.error(message)
def _create_handlers(self, outfile_path: Path, stream):
handlers = []
if outfile_path is not None:
handlers.append(logging.FileHandler(outfile_path, mode='a+'))
elif stream is not None:
handlers.append(logging.StreamHandler(stream))
else:
handlers.append(logging.StreamHandler())
for handler in handlers:
handler.setFormatter(logging.Formatter('%(message)s'))
self._logger.addHandler(handler)
def close(self):
for handler in self._logger.handlers:
handler.close()
self._logger.removeHandler(handler)

View File

@@ -0,0 +1,64 @@
import gzip
import os
import shutil
import sys
import time
from typing import Final
####################################### constants ######################################
BOHR2ANG: Final[float] = 0.52917721092
ANG2BOHR: Final[float] = 1 / BOHR2ANG
####################################### functions ######################################
def weekday_date_time():
return time.strftime("%A, %d %b %Y at %H:%M:%S")
def date_time():
return time.strftime("%d %b %Y at %H:%M:%S")
def compress_files_1mb(path):
working_dir = os.getcwd()
os.chdir(path)
files = filter(os.path.isfile, os.listdir(os.curdir))
for file in files:
if os.path.getsize(file) > 1024 * 1024: ## If bigger than 1MB
filegz = file + ".gz"
try:
with open(file, 'rb') as f_in:
with gzip.open(filegz, 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
except:
sys.exit("Error: cannot compress file {}".format(file))
os.chdir(working_dir)
return
def make_step_dir(cycle):
sim_dir = "simfiles"
step_dir = "step{:02d}".format(cycle)
path = sim_dir + os.sep + step_dir
if os.path.exists(path):
sys.exit("Error: a file or directory {} already exists".format(step_dir))
try:
os.makedirs(path)
except:
sys.exit("Error: cannot make directory {}".format(step_dir))
def make_qm_dir(cycle):
sim_dir = "simfiles"
step_dir = "step{:02d}".format(cycle)
path = sim_dir + os.sep + step_dir + os.sep + "qm"
try:
os.makedirs(path)
except:
sys.exit("Error: cannot make directory {}".format(path))

313
poetry.lock generated Normal file
View File

@@ -0,0 +1,313 @@
# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand.
[[package]]
name = "argparse"
version = "1.4.0"
description = "Python command-line parsing library"
category = "main"
optional = false
python-versions = "*"
files = [
{file = "argparse-1.4.0-py2.py3-none-any.whl", hash = "sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314"},
{file = "argparse-1.4.0.tar.gz", hash = "sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4"},
]
[[package]]
name = "coverage"
version = "7.2.7"
description = "Code coverage measurement for Python"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"},
{file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"},
{file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"},
{file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"},
{file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"},
{file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"},
{file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"},
{file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"},
{file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"},
{file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"},
{file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"},
{file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"},
{file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"},
{file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"},
{file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"},
{file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"},
{file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"},
{file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"},
{file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"},
{file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"},
{file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"},
{file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"},
{file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"},
{file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"},
{file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"},
{file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"},
{file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"},
{file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"},
{file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"},
{file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"},
{file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"},
{file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"},
{file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"},
{file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"},
{file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"},
{file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"},
{file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"},
{file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"},
{file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"},
{file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"},
{file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"},
{file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"},
{file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"},
{file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"},
{file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"},
{file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"},
{file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"},
{file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"},
{file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"},
{file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"},
{file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"},
{file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"},
{file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"},
{file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"},
{file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"},
{file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"},
{file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"},
{file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"},
{file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"},
{file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"},
]
[package.extras]
toml = ["tomli"]
[[package]]
name = "dacite"
version = "1.8.1"
description = "Simple creation of data classes from dictionaries."
category = "main"
optional = false
python-versions = ">=3.6"
files = [
{file = "dacite-1.8.1-py3-none-any.whl", hash = "sha256:cc31ad6fdea1f49962ea42db9421772afe01ac5442380d9a99fcf3d188c61afe"},
]
[package.extras]
dev = ["black", "coveralls", "mypy", "pre-commit", "pylint", "pytest (>=5)", "pytest-benchmark", "pytest-cov"]
[[package]]
name = "nptyping"
version = "2.5.0"
description = "Type hints for NumPy."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "nptyping-2.5.0-py3-none-any.whl", hash = "sha256:764e51836faae33a7ae2e928af574cfb701355647accadcc89f2ad793630b7c8"},
{file = "nptyping-2.5.0.tar.gz", hash = "sha256:e3d35b53af967e6fb407c3016ff9abae954d3a0568f7cc13a461084224e8e20a"},
]
[package.dependencies]
numpy = {version = ">=1.20.0,<2.0.0", markers = "python_version >= \"3.8\""}
typing-extensions = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.10\""}
[package.extras]
build = ["invoke (>=1.6.0)", "pip-tools (>=6.5.0)"]
complete = ["pandas", "pandas-stubs-fork"]
dev = ["autoflake", "beartype (<0.10.0)", "beartype (>=0.10.0)", "black", "codecov (>=2.1.0)", "coverage", "feedparser", "invoke (>=1.6.0)", "isort", "mypy", "pandas", "pandas-stubs-fork", "pip-tools (>=6.5.0)", "pylint", "pyright", "setuptools", "typeguard", "wheel"]
pandas = ["pandas", "pandas-stubs-fork"]
qa = ["autoflake", "beartype (<0.10.0)", "beartype (>=0.10.0)", "black", "codecov (>=2.1.0)", "coverage", "feedparser", "isort", "mypy", "pylint", "pyright", "setuptools", "typeguard", "wheel"]
[[package]]
name = "numpy"
version = "1.24.3"
description = "Fundamental package for array computing in Python"
category = "main"
optional = false
python-versions = ">=3.8"
files = [
{file = "numpy-1.24.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c1104d3c036fb81ab923f507536daedc718d0ad5a8707c6061cdfd6d184e570"},
{file = "numpy-1.24.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:202de8f38fc4a45a3eea4b63e2f376e5f2dc64ef0fa692838e31a808520efaf7"},
{file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8535303847b89aa6b0f00aa1dc62867b5a32923e4d1681a35b5eef2d9591a463"},
{file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d926b52ba1367f9acb76b0df6ed21f0b16a1ad87c6720a1121674e5cf63e2b6"},
{file = "numpy-1.24.3-cp310-cp310-win32.whl", hash = "sha256:f21c442fdd2805e91799fbe044a7b999b8571bb0ab0f7850d0cb9641a687092b"},
{file = "numpy-1.24.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f23af8c16022663a652d3b25dcdc272ac3f83c3af4c02eb8b824e6b3ab9d7"},
{file = "numpy-1.24.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9a7721ec204d3a237225db3e194c25268faf92e19338a35f3a224469cb6039a3"},
{file = "numpy-1.24.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d6cc757de514c00b24ae8cf5c876af2a7c3df189028d68c0cb4eaa9cd5afc2bf"},
{file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76e3f4e85fc5d4fd311f6e9b794d0c00e7002ec122be271f2019d63376f1d385"},
{file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1d3c026f57ceaad42f8231305d4653d5f05dc6332a730ae5c0bea3513de0950"},
{file = "numpy-1.24.3-cp311-cp311-win32.whl", hash = "sha256:c91c4afd8abc3908e00a44b2672718905b8611503f7ff87390cc0ac3423fb096"},
{file = "numpy-1.24.3-cp311-cp311-win_amd64.whl", hash = "sha256:5342cf6aad47943286afa6f1609cad9b4266a05e7f2ec408e2cf7aea7ff69d80"},
{file = "numpy-1.24.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7776ea65423ca6a15255ba1872d82d207bd1e09f6d0894ee4a64678dd2204078"},
{file = "numpy-1.24.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ae8d0be48d1b6ed82588934aaaa179875e7dc4f3d84da18d7eae6eb3f06c242c"},
{file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecde0f8adef7dfdec993fd54b0f78183051b6580f606111a6d789cd14c61ea0c"},
{file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4749e053a29364d3452c034827102ee100986903263e89884922ef01a0a6fd2f"},
{file = "numpy-1.24.3-cp38-cp38-win32.whl", hash = "sha256:d933fabd8f6a319e8530d0de4fcc2e6a61917e0b0c271fded460032db42a0fe4"},
{file = "numpy-1.24.3-cp38-cp38-win_amd64.whl", hash = "sha256:56e48aec79ae238f6e4395886b5eaed058abb7231fb3361ddd7bfdf4eed54289"},
{file = "numpy-1.24.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4719d5aefb5189f50887773699eaf94e7d1e02bf36c1a9d353d9f46703758ca4"},
{file = "numpy-1.24.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ec87a7084caa559c36e0a2309e4ecb1baa03b687201d0a847c8b0ed476a7187"},
{file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea8282b9bcfe2b5e7d491d0bf7f3e2da29700cec05b49e64d6246923329f2b02"},
{file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210461d87fb02a84ef243cac5e814aad2b7f4be953b32cb53327bb49fd77fbb4"},
{file = "numpy-1.24.3-cp39-cp39-win32.whl", hash = "sha256:784c6da1a07818491b0ffd63c6bbe5a33deaa0e25a20e1b3ea20cf0e43f8046c"},
{file = "numpy-1.24.3-cp39-cp39-win_amd64.whl", hash = "sha256:d5036197ecae68d7f491fcdb4df90082b0d4960ca6599ba2659957aafced7c17"},
{file = "numpy-1.24.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:352ee00c7f8387b44d19f4cada524586f07379c0d49270f87233983bc5087ca0"},
{file = "numpy-1.24.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7d6acc2e7524c9955e5c903160aa4ea083736fde7e91276b0e5d98e6332812"},
{file = "numpy-1.24.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:35400e6a8d102fd07c71ed7dcadd9eb62ee9a6e84ec159bd48c28235bbb0f8e4"},
{file = "numpy-1.24.3.tar.gz", hash = "sha256:ab344f1bf21f140adab8e47fdbc7c35a477dc01408791f8ba00d018dd0bc5155"},
]
[[package]]
name = "pyyaml"
version = "6.0"
description = "YAML parser and emitter for Python"
category = "main"
optional = false
python-versions = ">=3.6"
files = [
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
{file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
{file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
{file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"},
{file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"},
{file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"},
{file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"},
{file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"},
{file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"},
{file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"},
{file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
{file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
{file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
{file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
{file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
{file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
{file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
{file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
{file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
{file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
{file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
{file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
]
[[package]]
name = "setproctitle"
version = "1.3.2"
description = "A Python module to customize the process title"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "setproctitle-1.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:288943dec88e178bb2fd868adf491197cc0fc8b6810416b1c6775e686bab87fe"},
{file = "setproctitle-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:630f6fe5e24a619ccf970c78e084319ee8be5be253ecc9b5b216b0f474f5ef18"},
{file = "setproctitle-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c877691b90026670e5a70adfbcc735460a9f4c274d35ec5e8a43ce3f8443005"},
{file = "setproctitle-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7a55fe05f15c10e8c705038777656fe45e3bd676d49ad9ac8370b75c66dd7cd7"},
{file = "setproctitle-1.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab45146c71ca6592c9cc8b354a2cc9cc4843c33efcbe1d245d7d37ce9696552d"},
{file = "setproctitle-1.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00c9d5c541a2713ba0e657e0303bf96ddddc412ef4761676adc35df35d7c246"},
{file = "setproctitle-1.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:265ecbe2c6eafe82e104f994ddd7c811520acdd0647b73f65c24f51374cf9494"},
{file = "setproctitle-1.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c2c46200656280a064073447ebd363937562debef329482fd7e570c8d498f806"},
{file = "setproctitle-1.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:fa2f50678f04fda7a75d0fe5dd02bbdd3b13cbe6ed4cf626e4472a7ccf47ae94"},
{file = "setproctitle-1.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7f2719a398e1a2c01c2a63bf30377a34d0b6ef61946ab9cf4d550733af8f1ef1"},
{file = "setproctitle-1.3.2-cp310-cp310-win32.whl", hash = "sha256:e425be62524dc0c593985da794ee73eb8a17abb10fe692ee43bb39e201d7a099"},
{file = "setproctitle-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:e85e50b9c67854f89635a86247412f3ad66b132a4d8534ac017547197c88f27d"},
{file = "setproctitle-1.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2a97d51c17d438cf5be284775a322d57b7ca9505bb7e118c28b1824ecaf8aeaa"},
{file = "setproctitle-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:587c7d6780109fbd8a627758063d08ab0421377c0853780e5c356873cdf0f077"},
{file = "setproctitle-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d17c8bd073cbf8d141993db45145a70b307385b69171d6b54bcf23e5d644de"},
{file = "setproctitle-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e932089c35a396dc31a5a1fc49889dd559548d14cb2237adae260382a090382e"},
{file = "setproctitle-1.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e4f8f12258a8739c565292a551c3db62cca4ed4f6b6126664e2381acb4931bf"},
{file = "setproctitle-1.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:570d255fd99c7f14d8f91363c3ea96bd54f8742275796bca67e1414aeca7d8c3"},
{file = "setproctitle-1.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a8e0881568c5e6beff91ef73c0ec8ac2a9d3ecc9edd6bd83c31ca34f770910c4"},
{file = "setproctitle-1.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4bba3be4c1fabf170595b71f3af46c6d482fbe7d9e0563999b49999a31876f77"},
{file = "setproctitle-1.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:37ece938110cab2bb3957e3910af8152ca15f2b6efdf4f2612e3f6b7e5459b80"},
{file = "setproctitle-1.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db684d6bbb735a80bcbc3737856385b55d53f8a44ce9b46e9a5682c5133a9bf7"},
{file = "setproctitle-1.3.2-cp311-cp311-win32.whl", hash = "sha256:ca58cd260ea02759238d994cfae844fc8b1e206c684beb8f38877dcab8451dfc"},
{file = "setproctitle-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:88486e6cce2a18a033013d17b30a594f1c5cb42520c49c19e6ade40b864bb7ff"},
{file = "setproctitle-1.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:92c626edc66169a1b09e9541b9c0c9f10488447d8a2b1d87c8f0672e771bc927"},
{file = "setproctitle-1.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:710e16fa3bade3b026907e4a5e841124983620046166f355bbb84be364bf2a02"},
{file = "setproctitle-1.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f29b75e86260b0ab59adb12661ef9f113d2f93a59951373eb6d68a852b13e83"},
{file = "setproctitle-1.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c8d9650154afaa86a44ff195b7b10d683c73509d085339d174e394a22cccbb9"},
{file = "setproctitle-1.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0452282258dfcc01697026a8841258dd2057c4438b43914b611bccbcd048f10"},
{file = "setproctitle-1.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e49ae693306d7624015f31cb3e82708916759d592c2e5f72a35c8f4cc8aef258"},
{file = "setproctitle-1.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1ff863a20d1ff6ba2c24e22436a3daa3cd80be1dfb26891aae73f61b54b04aca"},
{file = "setproctitle-1.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:55ce1e9925ce1765865442ede9dca0ba9bde10593fcd570b1f0fa25d3ec6b31c"},
{file = "setproctitle-1.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7fe9df7aeb8c64db6c34fc3b13271a363475d77bc157d3f00275a53910cb1989"},
{file = "setproctitle-1.3.2-cp37-cp37m-win32.whl", hash = "sha256:e5c50e164cd2459bc5137c15288a9ef57160fd5cbf293265ea3c45efe7870865"},
{file = "setproctitle-1.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a499fff50387c1520c085a07578a000123f519e5f3eee61dd68e1d301659651f"},
{file = "setproctitle-1.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5b932c3041aa924163f4aab970c2f0e6b4d9d773f4d50326e0ea1cd69240e5c5"},
{file = "setproctitle-1.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f4bfc89bd33ebb8e4c0e9846a09b1f5a4a86f5cb7a317e75cc42fee1131b4f4f"},
{file = "setproctitle-1.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcd3cf4286a60fdc95451d8d14e0389a6b4f5cebe02c7f2609325eb016535963"},
{file = "setproctitle-1.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fb4f769c02f63fac90989711a3fee83919f47ae9afd4758ced5d86596318c65"},
{file = "setproctitle-1.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5194b4969f82ea842a4f6af2f82cd16ebdc3f1771fb2771796e6add9835c1973"},
{file = "setproctitle-1.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f0cde41857a644b7353a0060b5f94f7ba7cf593ebde5a1094da1be581ac9a31"},
{file = "setproctitle-1.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9124bedd8006b0e04d4e8a71a0945da9b67e7a4ab88fdad7b1440dc5b6122c42"},
{file = "setproctitle-1.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c8a09d570b39517de10ee5b718730e171251ce63bbb890c430c725c8c53d4484"},
{file = "setproctitle-1.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:8ff3c8cb26afaed25e8bca7b9dd0c1e36de71f35a3a0706b5c0d5172587a3827"},
{file = "setproctitle-1.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:589be87172b238f839e19f146b9ea47c71e413e951ef0dc6db4218ddacf3c202"},
{file = "setproctitle-1.3.2-cp38-cp38-win32.whl", hash = "sha256:4749a2b0c9ac52f864d13cee94546606f92b981b50e46226f7f830a56a9dc8e1"},
{file = "setproctitle-1.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:e43f315c68aa61cbdef522a2272c5a5b9b8fd03c301d3167b5e1343ef50c676c"},
{file = "setproctitle-1.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:de3a540cd1817ede31f530d20e6a4935bbc1b145fd8f8cf393903b1e02f1ae76"},
{file = "setproctitle-1.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4058564195b975ddc3f0462375c533cce310ccdd41b80ac9aed641c296c3eff4"},
{file = "setproctitle-1.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c5d5dad7c28bdd1ec4187d818e43796f58a845aa892bb4481587010dc4d362b"},
{file = "setproctitle-1.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ffc61a388a5834a97953d6444a2888c24a05f2e333f9ed49f977a87bb1ad4761"},
{file = "setproctitle-1.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fa1a0fbee72b47dc339c87c890d3c03a72ea65c061ade3204f285582f2da30f"},
{file = "setproctitle-1.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8a988c7220c002c45347430993830666e55bc350179d91fcee0feafe64e1d4"},
{file = "setproctitle-1.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bae283e85fc084b18ffeb92e061ff7ac5af9e183c9d1345c93e178c3e5069cbe"},
{file = "setproctitle-1.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fed18e44711c5af4b681c2b3b18f85e6f0f1b2370a28854c645d636d5305ccd8"},
{file = "setproctitle-1.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:b34baef93bfb20a8ecb930e395ccd2ae3268050d8cf4fe187de5e2bd806fd796"},
{file = "setproctitle-1.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7f0bed90a216ef28b9d227d8d73e28a8c9b88c0f48a082d13ab3fa83c581488f"},
{file = "setproctitle-1.3.2-cp39-cp39-win32.whl", hash = "sha256:4d8938249a7cea45ab7e1e48b77685d0f2bab1ebfa9dde23e94ab97968996a7c"},
{file = "setproctitle-1.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:a47d97a75fd2d10c37410b180f67a5835cb1d8fdea2648fd7f359d4277f180b9"},
{file = "setproctitle-1.3.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:dad42e676c5261eb50fdb16bdf3e2771cf8f99a79ef69ba88729aeb3472d8575"},
{file = "setproctitle-1.3.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c91b9bc8985d00239f7dc08a49927a7ca1ca8a6af2c3890feec3ed9665b6f91e"},
{file = "setproctitle-1.3.2-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8579a43eafd246e285eb3a5b939e7158073d5087aacdd2308f23200eac2458b"},
{file = "setproctitle-1.3.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:2fbd8187948284293f43533c150cd69a0e4192c83c377da837dbcd29f6b83084"},
{file = "setproctitle-1.3.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:faec934cfe5fd6ac1151c02e67156c3f526e82f96b24d550b5d51efa4a5527c6"},
{file = "setproctitle-1.3.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1aafc91cbdacc9e5fe712c52077369168e6b6c346f3a9d51bf600b53eae56bb"},
{file = "setproctitle-1.3.2-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b617f12c9be61e8f4b2857be4a4319754756845dbbbd9c3718f468bbb1e17bcb"},
{file = "setproctitle-1.3.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b2c9cb2705fc84cb8798f1ba74194f4c080aaef19d9dae843591c09b97678e98"},
{file = "setproctitle-1.3.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a149a5f7f2c5a065d4e63cb0d7a4b6d3b66e6e80f12e3f8827c4f63974cbf122"},
{file = "setproctitle-1.3.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e3ac25bfc4a0f29d2409650c7532d5ddfdbf29f16f8a256fc31c47d0dc05172"},
{file = "setproctitle-1.3.2-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65d884e22037b23fa25b2baf1a3316602ed5c5971eb3e9d771a38c3a69ce6e13"},
{file = "setproctitle-1.3.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7aa0aac1711fadffc1d51e9d00a3bea61f68443d6ac0241a224e4d622489d665"},
{file = "setproctitle-1.3.2.tar.gz", hash = "sha256:b9fb97907c830d260fa0658ed58afd48a86b2b88aac521135c352ff7fd3477fd"},
]
[package.extras]
test = ["pytest"]
[[package]]
name = "typing-extensions"
version = "4.6.3"
description = "Backported and Experimental Type Hints for Python 3.7+"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "typing_extensions-4.6.3-py3-none-any.whl", hash = "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26"},
{file = "typing_extensions-4.6.3.tar.gz", hash = "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"},
]
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
content-hash = "5767445dc10a28eeb01ba23bfbf781b68d8c158df80be696d53cb0c962d409ab"

24
pyproject.toml Normal file
View File

@@ -0,0 +1,24 @@
[tool.poetry]
name = "diceplayer"
version = "0.0.1"
description = ""
authors = ["Vitor Hideyoshi <vitor.h.n.batista@gmail.com>", "Herbert Georg <hcgeorg@ufg.br>"]
license = "GPL-2.0-only"
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.8"
numpy = "^1.24.3"
argparse = "^1.4.0"
setproctitle = "^1.3.2"
pyyaml = "^6.0"
dacite = "^1.8.1"
nptyping = "^2.5.0"
[tool.poetry.group.dev.dependencies]
coverage = "^7.2.7"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View File

@@ -1,62 +0,0 @@
import argparse
from distutils.command.clean import clean
import os
import shutil
import sys
import PyInstaller.__main__
name = "diceplayer"
parser = argparse.ArgumentParser(prog="Diceplayer Setup")
parser.add_argument(
"-b", "--build",
dest="build",
default=False,
action="store_true",
help="Builds the Diceplayer Binary",
)
parser.add_argument(
"-c", "--clean",
dest="clean",
default=False,
action="store_true",
help="Cleans the Development Environment"
)
args = parser.parse_args()
def __build():
PyInstaller.__main__.run(
["diceplayer/__main__.py", "--onefile", "-n{}".format(name)]
)
def __clean():
try:
shutil.rmtree("build")
shutil.rmtree("dist")
os.remove("diceplayer.spec")
except:
print("Workspace clean.")
if __name__ == "__main__":
if args.build:
__build()
elif args.clean:
__clean()
else:
parser.print_help(sys.stderr)
sys.exit(1)

0
tests/__init__.py Normal file
View File

0
tests/mocks/__init__.py Normal file
View File

113
tests/mocks/mock_inputs.py Normal file
View File

@@ -0,0 +1,113 @@
from unittest import mock
def get_config_example():
return """
diceplayer:
opt: no
mem: 12
maxcyc: 3
ncores: 4
nprocs: 4
qmprog: 'g16'
lps: no
ghosts: no
altsteps: 20000
dice:
nmol: [1, 50]
dens: 0.75
nstep: [2000, 3000, 4000]
isave: 1000
outname: 'phb'
progname: '~/.local/bin/dice'
ljname: 'phb.ljc'
randominit: 'first'
gaussian:
qmprog: 'g16'
level: 'MP2/aug-cc-pVDZ'
keywords: 'freq'
"""
def get_potentials_exemple():
return """\
*
2
1 TEST
1 1 0.000000 0.000000 0.000000 0.000000 0.0000 0.0000
1 PLACEHOLDER
1 1 0.000000 0.000000 0.000000 0.000000 0.0000 0.0000
"""
def get_potentials_error_combrule():
return """\
.
2
1 TEST
1 1 0.000000 0.000000 0.000000 0.000000 0.0000 0.0000
1 PLACEHOLDER
1 1 0.000000 0.000000 0.000000 0.000000 0.0000 0.0000
"""
def get_potentials_error_ntypes():
return """\
*
a
1 TEST
1 1 0.000000 0.000000 0.000000 0.000000 0.0000 0.0000
1 PLACEHOLDER
1 1 0.000000 0.000000 0.000000 0.000000 0.0000 0.0000
"""
def get_potentials_error_ntypes_config():
return """\
*
3
1 TEST
1 1 0.000000 0.000000 0.000000 0.000000 0.0000 0.0000
1 PLACEHOLDER
1 1 0.000000 0.000000 0.000000 0.000000 0.0000 0.0000
"""
def get_potentials_error_nsites():
return """\
*
2
. TEST
1 1 0.000000 0.000000 0.000000 0.000000 0.0000 0.0000
1 PLACEHOLDER
1 1 0.000000 0.000000 0.000000 0.000000 0.0000 0.0000
"""
def get_potentials_error_molname():
return """\
*
2
1
1 1 0.000000 0.000000 0.000000 0.000000 0.0000 0.0000
1 PLACEHOLDER
1 1 0.000000 0.000000 0.000000 0.000000 0.0000 0.0000
"""
def mock_open(file, *args, **kwargs):
values = {
"control.test.yml": get_config_example(),
"phb.ljc": get_potentials_exemple(),
"phb.error.combrule.ljc": get_potentials_error_combrule(),
"phb.error.ntypes.ljc": get_potentials_error_ntypes(),
"phb.error.ntypes.config.ljc": get_potentials_error_ntypes_config(),
"phb.error.nsites.ljc": get_potentials_error_nsites(),
"phb.error.molname.ljc": get_potentials_error_molname(),
}
if file in values:
return mock.mock_open(read_data=values[file])()
return mock.mock_open(read_data="")()

31
tests/mocks/mock_proc.py Normal file
View File

@@ -0,0 +1,31 @@
from typing import List
import itertools
class MockProc:
pid_counter = itertools.count()
def __init__(self, *args, **kwargs):
self.pid = next(MockProc.pid_counter)
if 'exitcode' in kwargs:
self.exitcode = kwargs['exitcode']
else:
self.exitcode = 0
self.sentinel = self.pid
def __call__(self, *args, **kwargs):
return self
def start(self):
pass
def terminate(self):
pass
class MockConnection:
@staticmethod
def wait(sentinels: List[int]):
return sentinels

0
tests/shared/__init__.py Normal file
View File

View File

View File

@@ -0,0 +1,82 @@
from diceplayer.shared.config.dice_config import DiceConfig
import unittest
class TestDiceDto(unittest.TestCase):
def test_class_instantiation(self):
dice_dto = DiceConfig(
ljname='test',
outname='test',
dens=1.0,
nmol=[1],
nstep=[1, 1],
)
self.assertIsInstance(dice_dto, DiceConfig)
def test_validate_jname(self):
with self.assertRaises(ValueError) as ex:
DiceConfig(
ljname=None,
outname='test',
dens=1.0,
nmol=[1],
nstep=[1, 1],
)
self.assertEqual(ex.exception, "Error: 'ljname' keyword not specified in config file")
def test_validate_outname(self):
with self.assertRaises(ValueError) as ex:
DiceConfig(
ljname='test',
outname=None,
dens=1.0,
nmol=[1],
nstep=[1, 1],
)
self.assertEqual(ex.exception, "Error: 'outname' keyword not specified in config file")
def test_validate_dens(self):
with self.assertRaises(ValueError) as ex:
DiceConfig(
ljname='test',
outname='test',
dens=None,
nmol=[1],
nstep=[1, 1],
)
self.assertEqual(ex.exception, "Error: 'dens' keyword not specified in config file")
def test_validate_nmol(self):
with self.assertRaises(ValueError) as ex:
DiceConfig(
ljname='test',
outname='test',
dens=1.0,
nmol=0,
nstep=[1, 1],
)
self.assertEqual(ex.exception, "Error: 'nmol' keyword not defined appropriately in config file")
def test_validate_nstep(self):
with self.assertRaises(ValueError) as ex:
DiceConfig(
ljname='test',
outname='test',
dens=1.0,
nmol=[1],
nstep=0,
)
self.assertEqual(ex.exception, "Error: 'nstep' keyword not defined appropriately in config file")
def test_from_dict(self):
dice_dto = DiceConfig.from_dict({
'ljname': 'test',
'outname': 'test',
'dens': 1.0,
'nmol': [1],
'nstep': [1, 1],
})
self.assertIsInstance(dice_dto, DiceConfig)

View File

@@ -0,0 +1,45 @@
from diceplayer.shared.config.gaussian_config import GaussianDTO
import unittest
class TestGaussianDTO(unittest.TestCase):
def test_class_instantiation(self):
gaussian_dto = GaussianDTO(
level='test',
qmprog='g16',
keywords='test',
)
self.assertIsInstance(gaussian_dto, GaussianDTO)
def test_is_valid_qmprog(self):
with self.assertRaises(ValueError):
gaussian_dto = GaussianDTO(
level='test',
qmprog='test',
keywords='test',
)
def test_is_valid_level(self):
with self.assertRaises(ValueError):
gaussian_dto = GaussianDTO(
level=None,
qmprog='g16',
keywords='test',
)
def test_from_dict(self):
gaussian_dto = GaussianDTO.from_dict(
{
'level': 'test',
'qmprog': 'g16',
'keywords': 'test',
}
)
self.assertIsInstance(gaussian_dto, GaussianDTO)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,85 @@
from diceplayer.shared.config.gaussian_config import GaussianDTO
from diceplayer.shared.config.player_config import PlayerConfig
from diceplayer.shared.config.dice_config import DiceConfig
import unittest
def get_config_dict():
return {
'opt': True,
'mem': 12,
'maxcyc': 100,
'nprocs': 4,
'ncores': 4,
'dice': {
'ljname': 'test',
'outname': 'test',
'dens': 1.0,
'nmol': [1],
'nstep': [1, 1],
},
'gaussian': {
'level': 'test',
'qmprog': 'g16',
'keywords': 'test',
}
}
class TestPlayerDTO(unittest.TestCase):
def setUp(self) -> None:
self.dice_dto = DiceConfig(
ljname='test',
outname='test',
dens=1.0,
nmol=[1],
nstep=[1, 1],
)
self.gaussian_dto = GaussianDTO(
level='test',
qmprog='g16',
keywords='test',
)
def test_class_instantiation(self):
player_dto = PlayerConfig(
opt=True,
mem=12,
maxcyc=100,
nprocs=4,
ncores=4,
dice=self.dice_dto,
gaussian=self.gaussian_dto
)
self.assertIsInstance(player_dto, PlayerConfig)
self.assertIsInstance(player_dto.dice, DiceConfig)
self.assertIsInstance(player_dto.gaussian, GaussianDTO)
def test_min_altsteps(self):
player_dto = PlayerConfig(
opt=True,
mem=12,
maxcyc=100,
nprocs=4,
ncores=4,
altsteps=100,
dice=self.dice_dto,
gaussian=self.gaussian_dto
)
self.assertEqual(player_dto.altsteps, 20000)
def test_from_dict(self):
player_dto = PlayerConfig.from_dict(
get_config_dict()
)
self.assertIsInstance(player_dto, PlayerConfig)
self.assertIsInstance(player_dto.dice, DiceConfig)
self.assertIsInstance(player_dto.gaussian, GaussianDTO)
if __name__ == '__main__':
unittest.main()

View File

View File

@@ -0,0 +1,19 @@
from diceplayer.shared.environment.atom import Atom
import unittest
class TestAtom(unittest.TestCase):
def test_class_instantiation(self):
atom = Atom(
lbl=1,
na=1,
rx=1.0,
ry=1.0,
rz=1.0,
chg=1.0,
eps=1.0,
sig=1.0,
)
self.assertIsInstance(atom, Atom)

View File

@@ -0,0 +1,212 @@
import numpy as np
from diceplayer.shared.environment.molecule import Molecule
from diceplayer.shared.environment.atom import Atom
import numpy.testing as npt
import unittest
class TestMolecule(unittest.TestCase):
def test_class_instantiation(self):
mol = Molecule('test')
self.assertIsInstance(mol, Molecule)
def test_add_atom(self):
mol = Molecule('test')
mol.add_atom(
Atom(lbl=1, na=1, rx=1.0, ry=1.0, rz=1.0, chg=1.0, eps=1.0, sig=1.0)
)
self.assertEqual(len(mol.atom), 1)
npt.assert_equal(mol.com, [1., 1., 1.])
def test_center_of_mass(self):
mol = Molecule('test')
mol.add_atom(
Atom(lbl=1, na=1, rx=1.0, ry=1.0, rz=1.0, chg=1.0, eps=1.0, sig=1.0)
)
mol.add_atom(
Atom(lbl=1, na=1, rx=0.0, ry=0.0, rz=0.0, chg=1.0, eps=1.0, sig=1.0)
)
npt.assert_equal(mol.com, [.5, .5, .5])
def test_center_of_mass_to_origin(self):
mol = Molecule('test')
mol.add_atom(
Atom(lbl=1, na=1, rx=1.0, ry=1.0, rz=1.0, chg=1.0, eps=1.0, sig=1.0)
)
mol.center_of_mass_to_origin()
npt.assert_equal(mol.com, [0, 0, 0])
def test_charges_and_dipole(self):
mol = Molecule('test')
mol.add_atom(
Atom(lbl=1, na=1, rx=0.0, ry=0.0, rz=0.0, chg=1.0, eps=1.0, sig=1.0)
)
actual_charge_dipole_array = mol.charges_and_dipole()
expected_charge_dipole_array = [1.0, 0.0, 0.0, 0.0, 0.0]
npt.assert_equal(
actual_charge_dipole_array,
expected_charge_dipole_array
)
def test_distances_between_atoms(self):
mol = Molecule('test')
mol.add_atom(
Atom(lbl=1, na=1, rx=0.0, ry=0.0, rz=0.0, chg=1.0, eps=1.0, sig=1.0)
)
mol.add_atom(
Atom(lbl=1, na=1, rx=1.0, ry=1.0, rz=1.0, chg=1.0, eps=1.0, sig=1.0)
)
expected_distance_between_atoms = [[1.73205081], [1.73205081]]
actual_distance_between_atoms = mol.distances_between_atoms()
npt.assert_almost_equal(
expected_distance_between_atoms,
actual_distance_between_atoms
)
def test_inertia_tensor(self):
mol = Molecule('test')
mol.add_atom(
Atom(lbl=1, na=1, rx=0.0, ry=0.0, rz=0.0, chg=1.0, eps=1.0, sig=1.0)
)
mol.add_atom(
Atom(lbl=1, na=1, rx=1.0, ry=1.0, rz=1.0, chg=1.0, eps=1.0, sig=1.0)
)
expected_inertia_tensor = [[1.00790, -0.50395, -0.50395],
[-0.50395, 1.0079, -0.50395],
[-0.50395, -0.50395, 1.0079]]
actual_inertia_tensor = mol.inertia_tensor()
npt.assert_equal(
expected_inertia_tensor,
actual_inertia_tensor
)
def test_principal_axes(self):
mol = Molecule('test')
mol.add_atom(
Atom(lbl=1, na=1, rx=0.0, ry=0.0, rz=0.0, chg=1.0, eps=1.0, sig=1.0)
)
expected_evals, expected_evecs = [0., 0., 0.], [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]
evals, evecs = mol.principal_axes()
npt.assert_equal(expected_evals, evals)
npt.assert_equal(expected_evecs, evecs)
def test_read_position(self):
mol = Molecule('test')
mol.add_atom(
Atom(lbl=1, na=1, rx=0.0, ry=0.0, rz=0.0, chg=1.0, eps=1.0, sig=1.0)
)
expected_position = mol.read_position()
actual_position = mol.read_position()
npt.assert_equal(
expected_position,
actual_position
)
def test_update_charges(self):
mol = Molecule('test')
mol.add_atom(
Atom(lbl=1, na=1, rx=0.0, ry=0.0, rz=0.0, chg=1.0, eps=1.0, sig=1.0)
)
expected_charges = [2.]
mol.update_charges(expected_charges)
actual_charges = list(map(lambda a: a.chg, mol.atom))
npt.assert_equal(
expected_charges,
actual_charges
)
def test_sizes_of_molecule(self):
mol = Molecule('test')
mol.add_atom(
Atom(lbl=1, na=1, rx=0.0, ry=0.0, rz=0.0, chg=1.0, eps=1.0, sig=1.0)
)
sizes = mol.sizes_of_molecule()
expected_sizes = [0.0, 0.0, 0.0]
npt.assert_equal(sizes, expected_sizes)
def test_standard_orientation(self):
mol = Molecule('test')
mol.add_atom(
Atom(lbl=1, na=1, rx=1.0, ry=1.0, rz=1.0, chg=1.0, eps=1.0, sig=1.0)
)
mol.standard_orientation()
expected_position = [0.0, 0.0, 0.0]
self.assertEqual(mol.read_position().tolist(), expected_position)
def test_translate(self):
mol = Molecule('test')
mol.add_atom(
Atom(lbl=1, na=1, rx=1.0, ry=1.0, rz=1.0, chg=1.0, eps=1.0, sig=1.0)
)
new_mol = mol.translate(np.array([-1, -1, -1]))
expected_position = [0.0, 0.0, 0.0]
self.assertEqual(
new_mol.read_position().tolist(),
expected_position
)
def test_minimum_distance(self):
mol1 = Molecule('test1')
mol1.add_atom(
Atom(lbl=1, na=1, rx=0.0, ry=0.0, rz=0.0, chg=1.0, eps=1.0, sig=1.0)
)
mol2 = Molecule('test2')
mol2.add_atom(
Atom(lbl=1, na=1, rx=1.0, ry=0.0, rz=0.0, chg=1.0, eps=1.0, sig=1.0)
)
expected_distance = 1.0
actual_distance = mol1.minimum_distance(mol2)
self.assertEqual(expected_distance, actual_distance)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,27 @@
from diceplayer.shared.environment.atom import Atom
from diceplayer.shared.environment.molecule import Molecule
from diceplayer.shared.environment.system import System
import unittest
class TestSystem(unittest.TestCase):
def test_class_instantiation(self):
system = System()
self.assertIsInstance(system, System)
def test_add_type(self):
system = System()
system.add_type(Molecule('test'))
self.assertIsInstance(system.molecule, list)
with self.assertRaises(TypeError) as ex:
system.add_type('test')
self.assertEqual(ex.exception, 'Error: molecule is not a Molecule instance')
if __name__ == '__main__':
unittest.main()

View File

View File

@@ -0,0 +1,540 @@
from diceplayer.shared.interface.dice_interface import DiceInterface
from diceplayer.shared.config.player_config import PlayerConfig
from diceplayer.shared.environment.molecule import Molecule
from diceplayer.shared.environment.system import System
from diceplayer.shared.environment.atom import Atom
from diceplayer import logger
import yaml
import io
from tests.mocks.mock_inputs import get_config_example
from tests.mocks.mock_proc import MockConnection, MockProc
from unittest import mock
import unittest
class TestDiceInterface(unittest.TestCase):
def setUp(self):
logger.set_logger(stream=io.StringIO())
config = yaml.load(get_config_example(), Loader=yaml.Loader)
self.config = PlayerConfig.from_dict(config['diceplayer'])
def test_class_instantiation(self):
dice = DiceInterface()
self.assertIsInstance(dice, DiceInterface)
def test_configure(self):
dice = DiceInterface()
self.assertIsNone(dice.step)
self.assertIsNone(dice.system)
# Ignoring the types for testing purposes
dice.configure(self.config, System())
self.assertIsNotNone(dice.step)
self.assertIsNotNone(dice.system)
def test_reset(self):
dice = DiceInterface()
dice.configure(self.config, System())
self.assertTrue(hasattr(dice, 'step'))
self.assertTrue(hasattr(dice, 'system'))
dice.reset()
self.assertFalse(hasattr(dice, 'step'))
self.assertFalse(hasattr(dice, 'system'))
@mock.patch('diceplayer.shared.interface.dice_interface.Process', MockProc())
@mock.patch('diceplayer.shared.interface.dice_interface.connection', MockConnection)
def test_start(self):
dice = DiceInterface()
dice.configure(self.config, System())
dice.start(1)
@mock.patch('diceplayer.shared.interface.dice_interface.connection', MockConnection)
@mock.patch('diceplayer.shared.interface.dice_interface.Process', MockProc(exitcode=1))
def test_start_with_process_error(self):
dice = DiceInterface()
dice.configure(self.config, System())
with self.assertRaises(SystemExit):
dice.start(1)
def test_simulation_process_raises_exception(self):
dice = DiceInterface()
with self.assertRaises(SystemExit):
dice._simulation_process(1, 1)
@mock.patch('diceplayer.shared.interface.dice_interface.DiceInterface._make_proc_dir')
@mock.patch('diceplayer.shared.interface.dice_interface.DiceInterface._make_dice_inputs')
@mock.patch('diceplayer.shared.interface.dice_interface.DiceInterface._run_dice')
def test_simulation_process(self, mock_run_dice, mock_make_dice_inputs, mock_make_proc_dir):
dice = DiceInterface()
dice._simulation_process(1, 1)
self.assertTrue(dice._make_proc_dir.called)
self.assertTrue(dice._make_dice_inputs.called)
self.assertTrue(dice._run_dice.called)
@mock.patch('diceplayer.shared.interface.dice_interface.Path.mkdir')
@mock.patch('diceplayer.shared.interface.dice_interface.Path.exists')
def test_make_proc_dir_if_simdir_exists(self, mock_path_exists, mock_path_mkdir):
dice = DiceInterface()
dice.configure(self.config, System())
mock_path_exists.return_value = False
dice._make_proc_dir(1, 1)
self.assertEqual(mock_path_mkdir.call_count, 2)
@mock.patch('diceplayer.shared.interface.dice_interface.Path.mkdir')
@mock.patch('diceplayer.shared.interface.dice_interface.Path.exists')
def test_make_proc_dir_if_simdir_doesnt_exists(self, mock_path_exists, mock_path_mkdir):
dice = DiceInterface()
dice.configure(self.config, System())
mock_path_exists.return_value = False
dice._make_proc_dir(1, 1)
self.assertEqual(mock_path_mkdir.call_count, 2)
def test_make_dice_seed(self):
seed = DiceInterface._make_dice_seed()
self.assertIsInstance(seed, int)
def test_make_dice_inputs_nstep_len_two_with_randoninit_first_cycle_one(self):
dice = DiceInterface()
dice.configure(self.config, System())
dice.step.dice.nstep = [1, 1]
dice._make_potentials = mock.Mock()
dice._make_init_file = mock.Mock()
dice._new_density = mock.Mock()
dice._make_nvt_ter = mock.Mock()
dice._make_nvt_eq = mock.Mock()
dice._make_npt_ter = mock.Mock()
dice._make_npt_eq = mock.Mock()
dice._make_dice_inputs(1, 1)
self.assertTrue(dice._make_potentials.called)
self.assertFalse(dice._make_init_file.called)
self.assertFalse(dice._new_density.called)
self.assertTrue(dice._make_nvt_ter.called)
self.assertTrue(dice._make_nvt_eq.called)
self.assertFalse(dice._make_npt_ter.called)
self.assertFalse(dice._make_npt_eq.called)
@mock.patch('builtins.open', new_callable=mock.mock_open, read_data='test')
@mock.patch('diceplayer.shared.interface.dice_interface.Path.exists', return_value=True)
def test_make_dice_inputs_nstep_len_two_with_randoninit_first_cycle_two(self, mock_path_exists, mock_open):
dice = DiceInterface()
dice.configure(self.config, System())
dice.step.dice.nstep = [1, 1]
dice._make_potentials = mock.Mock()
dice._make_init_file = mock.Mock()
dice._new_density = mock.Mock()
dice._make_nvt_ter = mock.Mock()
dice._make_nvt_eq = mock.Mock()
dice._make_npt_ter = mock.Mock()
dice._make_npt_eq = mock.Mock()
dice._make_dice_inputs(2, 1)
self.assertTrue(dice._make_potentials.called)
self.assertTrue(dice._make_init_file.called)
self.assertTrue(dice._new_density.called)
self.assertFalse(dice._make_nvt_ter.called)
self.assertTrue(dice._make_nvt_eq.called)
self.assertFalse(dice._make_npt_ter.called)
self.assertFalse(dice._make_npt_eq.called)
@mock.patch('diceplayer.shared.interface.dice_interface.Path.exists', return_value=False)
def test_make_dice_inputs_raises_exception_on_last_not_found(self, mock_path_exists):
dice = DiceInterface()
dice.configure(self.config, System())
dice.step.dice.nstep = [1, 1]
dice._make_potentials = mock.Mock()
dice._make_init_file = mock.Mock()
dice._new_density = mock.Mock()
dice._make_nvt_ter = mock.Mock()
dice._make_nvt_eq = mock.Mock()
dice._make_npt_ter = mock.Mock()
dice._make_npt_eq = mock.Mock()
with self.assertRaises(FileNotFoundError):
dice._make_dice_inputs(2, 1)
def test_make_dice_inputs_nstep_len_three_with_randoninit_first_cycle_one(self):
dice = DiceInterface()
dice.configure(self.config, System())
dice._make_potentials = mock.Mock()
dice._make_init_file = mock.Mock()
dice._new_density = mock.Mock()
dice._make_nvt_ter = mock.Mock()
dice._make_nvt_eq = mock.Mock()
dice._make_npt_ter = mock.Mock()
dice._make_npt_eq = mock.Mock()
dice._make_dice_inputs(1, 1)
self.assertTrue(dice._make_potentials.called)
self.assertFalse(dice._make_init_file.called)
self.assertFalse(dice._new_density.called)
self.assertTrue(dice._make_nvt_ter.called)
self.assertFalse(dice._make_nvt_eq.called)
self.assertTrue(dice._make_npt_ter.called)
self.assertTrue(dice._make_npt_eq.called)
@mock.patch('diceplayer.shared.interface.dice_interface.os')
@mock.patch('diceplayer.shared.interface.dice_interface.shutil')
@mock.patch('diceplayer.shared.interface.dice_interface.Path.exists', return_value=True)
def test_run_dice_on_first_cycle_run_successful(self, mock_path_exists, mock_shutils, mock_os):
dice = DiceInterface()
dice.configure(self.config, System())
dice.step.dice.nstep = [1, 1, 1]
dice.run_dice_file = mock.Mock()
dice._run_dice(1, 1)
self.assertTrue(mock_os.getcwd.called)
self.assertTrue(mock_os.chdir.called)
self.assertEqual(dice.run_dice_file.call_count, 3)
self.assertTrue(mock_shutils.copy.called)
dice = DiceInterface()
dice.configure(self.config, System())
dice.step.dice.nstep = [1, 1]
dice.run_dice_file = mock.Mock()
dice._run_dice(1, 1)
self.assertTrue(mock_os.getcwd.called)
self.assertTrue(mock_os.chdir.called)
self.assertEqual(dice.run_dice_file.call_count, 2)
self.assertTrue(mock_shutils.copy.called)
@mock.patch('diceplayer.shared.interface.dice_interface.os')
@mock.patch('diceplayer.shared.interface.dice_interface.shutil')
@mock.patch('diceplayer.shared.interface.dice_interface.Path.exists', return_value=True)
def test_run_dice_on_second_cycle_run_successful(self, mock_path_exists, mock_shutils, mock_os):
dice = DiceInterface()
dice.configure(self.config, System())
dice.run_dice_file = mock.Mock()
dice._run_dice(2, 1)
self.assertTrue(mock_os.getcwd.called)
self.assertTrue(mock_os.chdir.called)
self.assertEqual(dice.run_dice_file.call_count, 2)
self.assertTrue(mock_shutils.copy.called)
dice = DiceInterface()
dice.configure(self.config, System())
dice.run_dice_file = mock.Mock()
dice._run_dice(2, 1)
self.assertTrue(mock_os.getcwd.called)
self.assertTrue(mock_os.chdir.called)
self.assertEqual(dice.run_dice_file.call_count, 1)
self.assertTrue(mock_shutils.copy.called)
@mock.patch('diceplayer.shared.interface.dice_interface.os')
@mock.patch('diceplayer.shared.interface.dice_interface.shutil')
@mock.patch('diceplayer.shared.interface.dice_interface.Path.exists', return_value=False)
def test_run_dice_on_second_cycle_run_successful(self, mock_path_exists, mock_shutils, mock_os):
dice = DiceInterface()
dice.configure(self.config, System())
dice.run_dice_file = mock.Mock()
with self.assertRaises(FileNotFoundError):
dice._run_dice(1, 1)
@mock.patch('builtins.open', new_callable=mock.mock_open)
def test_make_init_file(self, mock_open):
example_atom = Atom(
lbl=1,
na=1,
rx=1.0,
ry=1.0,
rz=1.0,
chg=1.0,
eps=1.0,
sig=1.0,
)
main_molecule = Molecule('main_molecule')
main_molecule.add_atom(example_atom)
secondary_molecule = Molecule('secondary_molecule')
secondary_molecule.add_atom(example_atom)
system = System()
system.add_type(main_molecule)
system.add_type(secondary_molecule)
dice = DiceInterface()
dice.configure(self.config, system)
dice.step.dice.nmol = [1, 1]
last_xyz_file = io.StringIO()
last_xyz_file.writelines([
' TEST\n',
' Configuration number : TEST = TEST TEST TEST\n',
' H 1.00000 1.00000 1.00000\n',
' H 1.00000 1.00000 1.00000\n',
])
last_xyz_file.seek(0)
dice._make_init_file('test', last_xyz_file)
mock_handler = mock_open()
calls = mock_handler.write.call_args_list
lines = list(map(lambda x: x[0][0], calls))
expected_lines = [
' 1.000000 1.000000 1.000000\n',
' 1.000000 1.000000 1.000000\n',
'$end'
]
self.assertEqual(lines, expected_lines)
@mock.patch('builtins.open', new_callable=mock.mock_open)
def test_new_density(self, mock_open):
example_atom = Atom(
lbl=1,
na=1,
rx=1.0,
ry=1.0,
rz=1.0,
chg=1.0,
eps=1.0,
sig=1.0,
)
main_molecule = Molecule('main_molecule')
main_molecule.add_atom(example_atom)
secondary_molecule = Molecule('secondary_molecule')
secondary_molecule.add_atom(example_atom)
system = System()
system.add_type(main_molecule)
system.add_type(secondary_molecule)
dice = DiceInterface()
dice.configure(self.config, system)
last_xyz_file = io.StringIO()
last_xyz_file.writelines([
' TEST\n',
' Configuration number : TEST = 1 1 1\n',
' H 1.00000 1.00000 1.00000\n',
' H 1.00000 1.00000 1.00000\n',
])
last_xyz_file.seek(0)
density = dice._new_density(last_xyz_file)
self.assertEqual(density, 85.35451545000001)
@mock.patch('builtins.open', new_callable=mock.mock_open)
@mock.patch('diceplayer.shared.interface.dice_interface.random')
def test_make_nvt_ter(self, mock_random, mock_open):
mock_random.random.return_value = 1
dice = DiceInterface()
dice.configure(self.config, System())
dice._make_nvt_ter(1, 'test')
mock_handler = mock_open()
calls = mock_handler.write.call_args_list
lines = list(map(lambda x: x[0][0], calls))
expected_lines = ['title = Diceplayer run - NVT Thermalization\n', 'ncores = 4\n', 'ljname = phb.ljc\n', 'outname = phb\n', 'nmol = 1 50\n', 'dens = 0.75\n', 'temp = 300.0\n', 'init = yes\n', 'nstep = 2000\n', 'vstep = 0\n', 'mstop = 1\n', 'accum = no\n', 'iprint = 1\n', 'isave = 0\n', 'irdf = 0\n', 'seed = 1000000\n', 'upbuf = 360']
self.assertEqual(lines, expected_lines)
@mock.patch('builtins.open', new_callable=mock.mock_open)
@mock.patch('diceplayer.shared.interface.dice_interface.random')
def test_make_nvt_eq(self, mock_random, mock_open):
mock_random.random.return_value = 1
dice = DiceInterface()
dice.configure(self.config, System())
dice._make_nvt_eq(1, 'test')
mock_handler = mock_open()
calls = mock_handler.write.call_args_list
lines = list(map(lambda x: x[0][0], calls))
expected_lines = ['title = Diceplayer run - NVT Production\n', 'ncores = 4\n', 'ljname = phb.ljc\n', 'outname = phb\n', 'nmol = 1 50\n', 'dens = 0.75\n', 'temp = 300.0\n', 'init = no\n', 'nstep = 3000\n', 'vstep = 0\n', 'mstop = 1\n', 'accum = no\n', 'iprint = 1\n', 'isave = 1000\n', 'irdf = 40\n', 'seed = 1000000\n']
self.assertEqual(lines, expected_lines)
@mock.patch('builtins.open', new_callable=mock.mock_open)
@mock.patch('diceplayer.shared.interface.dice_interface.random')
def test_make_npt_ter(self, mock_random, mock_open):
mock_random.random.return_value = 1
dice = DiceInterface()
dice.configure(self.config, System())
dice._make_npt_ter(1, 'test')
mock_handler = mock_open()
calls = mock_handler.write.call_args_list
lines = list(map(lambda x: x[0][0], calls))
expected_lines = ['title = Diceplayer run - NPT Thermalization\n', 'ncores = 4\n', 'ljname = phb.ljc\n', 'outname = phb\n', 'nmol = 1 50\n', 'press = 1.0\n', 'temp = 300.0\n', 'init = no\n', 'vstep = 600\n', 'nstep = 5\n', 'mstop = 1\n', 'accum = no\n', 'iprint = 1\n', 'isave = 0\n', 'irdf = 0\n', 'seed = 1000000\n']
self.assertEqual(lines, expected_lines)
@mock.patch('builtins.open', new_callable=mock.mock_open)
@mock.patch('diceplayer.shared.interface.dice_interface.random')
def test_make_npt_eq(self, mock_random, mock_open):
mock_random.random.return_value = 1
dice = DiceInterface()
dice.configure(self.config, System())
dice._make_npt_eq('test')
mock_handler = mock_open()
calls = mock_handler.write.call_args_list
lines = list(map(lambda x: x[0][0], calls))
expected_lines = ['title = Diceplayer run - NPT Production\n', 'ncores = 4\n', 'ljname = phb.ljc\n', 'outname = phb\n', 'nmol = 1 50\n', 'press = 1.0\n', 'temp = 300.0\n', 'nstep = 5\n', 'vstep = 800\n', 'init = no\n', 'mstop = 1\n', 'accum = no\n', 'iprint = 1\n', 'isave = 1000\n', 'irdf = 40\n', 'seed = 1000000\n']
self.assertEqual(lines, expected_lines)
@mock.patch('builtins.open', new_callable=mock.mock_open)
def test_make_potentials(self, mock_open):
example_atom = Atom(
lbl=1,
na=1,
rx=1.0,
ry=1.0,
rz=1.0,
chg=1.0,
eps=1.0,
sig=1.0,
)
main_molecule = Molecule('main_molecule')
main_molecule.add_atom(example_atom)
secondary_molecule = Molecule('secondary_molecule')
secondary_molecule.add_atom(example_atom)
system = System()
system.add_type(main_molecule)
system.add_type(secondary_molecule)
dice = DiceInterface()
dice.configure(self.config, system)
dice._make_potentials('test')
mock_handler = mock_open()
calls = mock_handler.write.call_args_list
lines = list(map(lambda x: x[0][0], calls))
expected_lines = ['*\n', '2\n', '1 main_molecule\n', '1 1 1.00000 1.00000 1.00000 1.000000 1.00000 1.0000\n', '1 secondary_molecule\n', '1 1 1.00000 1.00000 1.00000 1.000000 1.00000 1.0000\n']
self.assertEqual(lines, expected_lines)
@mock.patch('diceplayer.shared.interface.dice_interface.subprocess')
@mock.patch('builtins.open', new_callable=mock.mock_open, read_data='End of simulation\nBLABLA')
def test_run_dice_file(self, mock_open, mock_subprocess):
mock_subprocess.call.return_value = 0
dice = DiceInterface()
dice.configure(self.config, System())
dice.run_dice_file(1, 1, 'test')
self.assertTrue(mock_subprocess.call.called)
self.assertTrue(mock_open.called)
@mock.patch('diceplayer.shared.interface.dice_interface.subprocess')
@mock.patch('builtins.open', new_callable=mock.mock_open, read_data='Error\nBLABLA')
def test_run_dice_file_raises_runtime_error_on_dice_file(self, mock_open, mock_subprocess):
mock_subprocess.call.return_value = 0
dice = DiceInterface()
dice.configure(self.config, System())
with self.assertRaises(RuntimeError):
dice.run_dice_file(1, 1, 'test')
@mock.patch('diceplayer.shared.interface.dice_interface.subprocess')
@mock.patch('builtins.open', new_callable=mock.mock_open, read_data='End of simulation\nBLABLA')
def test_run_dice_file_raises_runtime_error_of_dice_exit_code(self, mock_open, mock_subprocess):
mock_subprocess.call.return_value = 1
dice = DiceInterface()
dice.configure(self.config, System())
with self.assertRaises(RuntimeError):
dice.run_dice_file(1, 1, 'test')
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,113 @@
from diceplayer.shared.interface.gaussian_interface import GaussianInterface
from diceplayer.shared.config.player_config import PlayerConfig
from diceplayer.shared.environment.system import System
from diceplayer import logger
from tests.mocks.mock_inputs import get_config_example
import yaml
import io
from unittest import mock
import unittest
class TestGaussianInterface(unittest.TestCase):
def setUp(self) -> None:
logger.set_logger(stream=io.StringIO())
config = yaml.load(get_config_example(), Loader=yaml.Loader)
self.config = PlayerConfig.from_dict(config['diceplayer'])
def test_class_instantiation(self):
gaussian_interface = GaussianInterface()
self.assertIsInstance(gaussian_interface, GaussianInterface)
def test_configure(self):
gaussian_interface = GaussianInterface()
self.assertIsNone(gaussian_interface.step)
self.assertIsNone(gaussian_interface.system)
gaussian_interface.configure(self.config, System())
self.assertIsNotNone(gaussian_interface.step)
self.assertIsNotNone(gaussian_interface.system)
def test_reset(self):
gaussian_interface = GaussianInterface()
gaussian_interface.configure(self.config, System())
self.assertIsNotNone(gaussian_interface.step)
self.assertIsNotNone(gaussian_interface.system)
gaussian_interface.reset()
self.assertFalse(hasattr(gaussian_interface, 'step'))
self.assertFalse(hasattr(gaussian_interface, 'system'))
@mock.patch('diceplayer.shared.interface.gaussian_interface.Path.mkdir')
@mock.patch('diceplayer.shared.interface.gaussian_interface.Path.exists')
def test_make_qm_dir(self, mock_exists, mock_mkdir):
mock_exists.return_value = False
gaussian_interface = GaussianInterface()
gaussian_interface.configure(self.config, System())
gaussian_interface._make_qm_dir(1)
mock_exists.assert_called_once()
mock_mkdir.assert_called_once()
@mock.patch('diceplayer.shared.interface.gaussian_interface.shutil.copy')
@mock.patch('diceplayer.shared.interface.gaussian_interface.Path.exists')
def test_copy_chk_file_from_previous_step(self, mock_exists, mock_copy):
gaussian_interface = GaussianInterface()
gaussian_interface.configure(self.config, System())
mock_exists.side_effect = [False, True]
gaussian_interface._copy_chk_file_from_previous_step(2)
self.assertTrue(mock_exists.called)
self.assertTrue(mock_copy.called)
@mock.patch('diceplayer.shared.interface.gaussian_interface.shutil.copy')
@mock.patch('diceplayer.shared.interface.gaussian_interface.Path.exists')
def test_copy_chk_file_from_previous_step_no_previous_step(self, mock_exists, mock_copy):
gaussian_interface = GaussianInterface()
gaussian_interface.configure(self.config, System())
mock_exists.side_effect = [False, False]
with self.assertRaises(FileNotFoundError):
gaussian_interface._copy_chk_file_from_previous_step(2)
@mock.patch('diceplayer.shared.interface.gaussian_interface.shutil.copy')
@mock.patch('diceplayer.shared.interface.gaussian_interface.Path.exists')
def test_copy_chk_file_from_previous_step_current_exists(self, mock_exists, mock_copy):
gaussian_interface = GaussianInterface()
gaussian_interface.configure(self.config, System())
mock_exists.side_effect = [True, True]
with self.assertRaises(FileExistsError):
gaussian_interface._copy_chk_file_from_previous_step(2)
# def test_start(self):
# gaussian_interface = GaussianInterface()
# gaussian_interface.configure(self.config, System())
#
# gaussian_interface._make_qm_dir = mock.Mock()
# gaussian_interface._copy_chk_file_from_previous_step = mock.Mock()
#
# gaussian_interface.start(2)
#
# gaussian_interface._make_qm_dir.assert_called_once_with(2)
# gaussian_interface._copy_chk_file_from_previous_step.assert_called_once_with(2)
if __name__ == '__main__':
unittest.main()

View File

View File

@@ -0,0 +1,133 @@
from diceplayer.shared.utils.logger import Logger, valid_logger
import logging
import io
from unittest import mock
import unittest
class TestValidateLogger(unittest.TestCase):
def test_validate_logger(self):
class MockLogger:
_was_set = True
@valid_logger
def test_func(self):
pass
MockLogger().test_func()
def test_validate_logger_exception(self):
class MockLogger:
_was_set = False
@valid_logger
def test_func(self):
pass
with self.assertRaises(AssertionError):
MockLogger().test_func()
class TestLogger(unittest.TestCase):
def test_class_instantiation(self):
logger = Logger('test')
self.assertIsInstance(logger, Logger)
@mock.patch('builtins.open', mock.mock_open())
def test_set_logger_to_file(self):
logger = Logger('test')
logger.set_logger(stream=io.StringIO())
self.assertIsNotNone(logger._logger)
self.assertEqual(logger._logger.name, 'test')
def test_set_logger_to_stream(self):
logger = Logger('test')
logger.set_logger(stream=io.StringIO())
self.assertIsNotNone(logger._logger)
self.assertEqual(logger._logger.name, 'test')
@mock.patch('builtins.open', mock.mock_open())
@mock.patch('diceplayer.shared.utils.logger.Path.exists')
@mock.patch('diceplayer.shared.utils.logger.Path.rename')
def test_set_logger_if_file_exists(self, mock_rename, mock_exists):
logger = Logger('test')
mock_exists.return_value = True
logger.set_logger()
self.assertTrue(mock_rename.called)
self.assertIsNotNone(logger._logger)
self.assertEqual(logger._logger.name, 'test')
@mock.patch('builtins.open', mock.mock_open())
@mock.patch('diceplayer.shared.utils.logger.Path.exists')
@mock.patch('diceplayer.shared.utils.logger.Path.rename')
def test_set_logger_if_file_not_exists(self, mock_rename, mock_exists):
logger = Logger('test')
mock_exists.return_value = False
logger.set_logger()
self.assertFalse(mock_rename.called)
self.assertIsNotNone(logger._logger)
self.assertEqual(logger._logger.name, 'test')
@mock.patch('builtins.open', mock.mock_open())
def test_close(self):
logger = Logger('test')
logger.set_logger()
logger.close()
self.assertEqual(len(logger._logger.handlers), 0)
@mock.patch('builtins.open', mock.mock_open())
def test_info(self):
logger = Logger('test')
logger.set_logger(stream=io.StringIO())
with self.assertLogs(level='INFO') as cm:
logger.info('test')
self.assertEqual(cm.output, ['INFO:test:test'])
@mock.patch('builtins.open', mock.mock_open())
def test_debug(self):
logger = Logger('test')
logger.set_logger(stream=io.StringIO(), level=logging.DEBUG)
with self.assertLogs(level='DEBUG') as cm:
logger.debug('test')
self.assertEqual(cm.output, ['DEBUG:test:test'])
@mock.patch('builtins.open', mock.mock_open())
def test_warning(self):
logger = Logger('test')
logger.set_logger(stream=io.StringIO())
with self.assertLogs(level='WARNING') as cm:
logger.warning('test')
self.assertEqual(cm.output, ['WARNING:test:test'])
@mock.patch('builtins.open', mock.mock_open())
def test_error(self):
logger = Logger('test')
logger.set_logger(stream=io.StringIO())
with self.assertLogs(level='ERROR') as cm:
logger.error('test')
self.assertEqual(cm.output, ['ERROR:test:test'])
if __name__ == '__main__':
unittest.main()

340
tests/test_player.py Normal file
View File

@@ -0,0 +1,340 @@
from diceplayer.player import Player
from diceplayer import logger
import io
from tests.mocks.mock_inputs import mock_open
from unittest import mock
import unittest
class TestPlayer(unittest.TestCase):
def setUp(self):
logger.set_logger(stream=io.StringIO())
@mock.patch("builtins.open", mock_open)
def test_class_instantiation(self):
# This file does not exist and it will be mocked
player = Player("control.test.yml")
self.assertIsInstance(player, Player)
@mock.patch("builtins.open", mock_open)
def test_start(self):
player = Player("control.test.yml")
player.gaussian_start = mock.MagicMock()
player.dice_start = mock.MagicMock()
player.start()
self.assertEqual(player.dice_start.call_count, 3)
self.assertEqual(player.gaussian_start.call_count, 3)
@mock.patch("builtins.open", mock_open)
@mock.patch("diceplayer.player.Path")
def test_create_simulation_dir_if_already_exists(self, mock_path):
player = Player("control.test.yml")
mock_path.return_value.exists.return_value = True
with self.assertRaises(FileExistsError):
player.create_simulation_dir()
self.assertTrue(mock_path.called)
@mock.patch("builtins.open", mock_open)
@mock.patch("diceplayer.player.Path")
def test_create_simulation_dir_if_not_exists(self, mock_path):
player = Player("control.test.yml")
mock_path.return_value.exists.return_value = False
player.create_simulation_dir()
self.assertTrue(mock_path.called)
@mock.patch("diceplayer.player.sys")
@mock.patch("diceplayer.player.weekday_date_time")
@mock.patch("builtins.open", mock_open)
def test_print_keywords(self, mock_date_func, mock_sys):
player = Player("control.test.yml")
mock_sys.version = 'TEST'
mock_date_func.return_value = '00 Test 0000 at 00:00:00'
with self.assertLogs() as cm:
player.print_keywords()
expected_output = [
'INFO:diceplayer:##########################################################################################\n############# Welcome to DICEPLAYER version 1.0 #############\n##########################################################################################\n',
'INFO:diceplayer:Your python version is TEST\n',
'INFO:diceplayer:Program started on 00 Test 0000 at 00:00:00\n',
'INFO:diceplayer:Environment variables:',
'INFO:diceplayer:OMP_STACKSIZE = Not set\n',
'INFO:diceplayer:------------------------------------------------------------------------------------------\n DICE variables being used in this run:\n------------------------------------------------------------------------------------------\n',
'INFO:diceplayer:dens = 0.75',
'INFO:diceplayer:isave = 1000',
'INFO:diceplayer:ljname = phb.ljc',
'INFO:diceplayer:nmol = [ 1 50 ]',
'INFO:diceplayer:nstep = [ 2000 3000 4000 ]',
'INFO:diceplayer:outname = phb',
'INFO:diceplayer:press = 1.0',
'INFO:diceplayer:progname = ~/.local/bin/dice',
'INFO:diceplayer:randominit = first',
'INFO:diceplayer:temp = 300.0',
'INFO:diceplayer:------------------------------------------------------------------------------------------\n GAUSSIAN variables being used in this run:\n------------------------------------------------------------------------------------------\n',
'INFO:diceplayer:chg_tol = 0.01',
'INFO:diceplayer:keywords = freq',
'INFO:diceplayer:level = MP2/aug-cc-pVDZ',
'INFO:diceplayer:pop = chelpg',
'INFO:diceplayer:qmprog = g16',
'INFO:diceplayer:\n'
]
self.assertEqual(cm.output, expected_output)
def test_validate_atom_dict(self):
with self.assertRaises(ValueError) as context:
Player.validate_atom_dict(
molecule_type=0,
molecule_site=0,
atom_dict={
"lbl": 0, "na": 1, "rx": 1.0, "ry": 1.0, "rz": 1.0, "chg": 1.0, "eps": 1.0
}
)
self.assertEqual(
str(context.exception),
"Invalid number of fields for site 1 for molecule type 1."
)
with self.assertRaises(ValueError) as context:
Player.validate_atom_dict(
molecule_type=0,
molecule_site=0,
atom_dict={
"lbl": '', "na": 1, "rx": 1.0, "ry": 1.0, "rz": 1.0, "chg": 1.0, "eps": 1.0, "sig": 1.0
}
)
self.assertEqual(
str(context.exception),
"Invalid lbl fields for site 1 for molecule type 1."
)
with self.assertRaises(ValueError) as context:
Player.validate_atom_dict(
molecule_type=0,
molecule_site=0,
atom_dict={
"lbl": 1.0, "na": '', "rx": 1.0, "ry": 1.0, "rz": 1.0, "chg": 1.0, "eps": 1.0, "sig": 1.0
}
)
self.assertEqual(
str(context.exception),
"Invalid na fields for site 1 for molecule type 1."
)
with self.assertRaises(ValueError) as context:
Player.validate_atom_dict(
molecule_type=0,
molecule_site=0,
atom_dict={
"lbl": 1.0, "na": 1, "rx": '', "ry": 1.0, "rz": 1.0, "chg": 1.0, "eps": 1.0, "sig": 1.0
}
)
self.assertEqual(
str(context.exception),
"Invalid rx fields for site 1 for molecule type 1. Value must be a float."
)
with self.assertRaises(ValueError) as context:
Player.validate_atom_dict(
molecule_type=0,
molecule_site=0,
atom_dict={
"lbl": 1.0, "na": 1, "rx": 1.0, "ry": '', "rz": 1.0, "chg": 1.0, "eps": 1.0, "sig": 1.0
}
)
self.assertEqual(
str(context.exception),
"Invalid ry fields for site 1 for molecule type 1. Value must be a float."
)
with self.assertRaises(ValueError) as context:
Player.validate_atom_dict(
molecule_type=0,
molecule_site=0,
atom_dict={
"lbl": 1.0, "na": 1, "rx": 1.0, "ry": 1.0, "rz": '', "chg": 1.0, "eps": 1.0, "sig": 1.0
}
)
self.assertEqual(
str(context.exception),
"Invalid rz fields for site 1 for molecule type 1. Value must be a float."
)
with self.assertRaises(ValueError) as context:
Player.validate_atom_dict(
molecule_type=0,
molecule_site=0,
atom_dict={
"lbl": 1.0, "na": 1, "rx": 1.0, "ry": 1.0, "rz": 1.0, "chg": '', "eps": 1.0, "sig": 1.0
}
)
self.assertEqual(
str(context.exception),
"Invalid chg fields for site 1 for molecule type 1. Value must be a float."
)
with self.assertRaises(ValueError) as context:
Player.validate_atom_dict(
molecule_type=0,
molecule_site=0,
atom_dict={
"lbl": 1.0, "na": 1, "rx": 1.0, "ry": 1.0, "rz": 1.0, "chg": 1.0, "eps": '', "sig": 1.0
}
)
self.assertEqual(
str(context.exception),
"Invalid eps fields for site 1 for molecule type 1. Value must be a float."
)
with self.assertRaises(ValueError) as context:
Player.validate_atom_dict(
molecule_type=0,
molecule_site=0,
atom_dict={
"lbl": 1.0, "na": 1, "rx": 1.0, "ry": 1.0, "rz": 1.0, "chg": 1.0, "eps": 1.0, "sig": ''
}
)
self.assertEqual(
str(context.exception),
"Invalid sig fields for site 1 for molecule type 1. Value must be a float."
)
@mock.patch("builtins.open", mock_open)
@mock.patch("diceplayer.player.Path.exists", return_value=True)
def test_read_potentials(self, mock_path_exists):
player = Player("control.test.yml")
player.read_potentials()
self.assertEqual(player.system.molecule[0].molname, "TEST")
self.assertEqual(len(player.system.molecule[0].atom), 1)
self.assertEqual(player.system.molecule[1].molname, "PLACEHOLDER")
self.assertEqual(len(player.system.molecule[1].atom), 1)
@mock.patch("builtins.open", mock_open)
@mock.patch("diceplayer.player.Path.exists")
def test_read_potentials_error(self, mock_path_exists):
player = Player("control.test.yml")
# Testing file not found error
mock_path_exists.return_value = False
with self.assertRaises(RuntimeError) as context:
player.read_potentials()
self.assertEqual(
str(context.exception),
"Potential file phb.ljc not found."
)
# Enabling file found for next tests
mock_path_exists.return_value = True
# Testing combrule error
with self.assertRaises(SystemExit) as context:
player.config.dice.ljname = "phb.error.combrule.ljc"
player.read_potentials()
self.assertEqual(
str(context.exception),
"Error: expected a '*' or a '+' sign in 1st line of file phb.error.combrule.ljc"
)
# Testing ntypes error
with self.assertRaises(SystemExit) as context:
player.config.dice.ljname = "phb.error.ntypes.ljc"
player.read_potentials()
self.assertEqual(
str(context.exception),
"Error: expected an integer in the 2nd line of file phb.error.ntypes.ljc"
)
# Testing ntypes error on config
with self.assertRaises(SystemExit) as context:
player.config.dice.ljname = "phb.error.ntypes.config.ljc"
player.read_potentials()
self.assertEqual(
str(context.exception),
"Error: number of molecule types in file phb.error.ntypes.config.ljc "
"must match that of 'nmol' keyword in config file"
)
# Testing nsite error
with self.assertRaises(ValueError) as context:
player.config.dice.ljname = "phb.error.nsites.ljc"
player.read_potentials()
self.assertEqual(
str(context.exception),
"Error: expected nsites to be an integer for molecule type 1"
)
# Testing molname error
with self.assertRaises(ValueError) as context:
player.config.dice.ljname = "phb.error.molname.ljc"
player.read_potentials()
self.assertEqual(
str(context.exception),
"Error: expected nsites and molname for the molecule type 1"
)
@mock.patch("builtins.open", mock_open)
@mock.patch("diceplayer.player.Path.exists", return_value=True)
def test_print_potentials(self, mock_path_exists):
player = Player("control.test.yml")
player.read_potentials()
with self.assertLogs(level='INFO') as context:
player.print_potentials()
expected_output = [
'INFO:diceplayer:==========================================================================================\n Potential parameters from file phb.ljc:\n------------------------------------------------------------------------------------------\n',
'INFO:diceplayer:Combination rule: *',
'INFO:diceplayer:Types of molecules: 2\n',
'INFO:diceplayer:1 atoms in molecule type 1:',
'INFO:diceplayer:---------------------------------------------------------------------------------',
'INFO:diceplayer:Lbl AN X Y Z Charge Epsilon Sigma Mass',
'INFO:diceplayer:---------------------------------------------------------------------------------',
'INFO:diceplayer:1 1 0.00000 0.00000 0.00000 0.000000 0.00000 0.0000 1.0079',
'INFO:diceplayer:\n',
'INFO:diceplayer:1 atoms in molecule type 2:',
'INFO:diceplayer:---------------------------------------------------------------------------------',
'INFO:diceplayer:Lbl AN X Y Z Charge Epsilon Sigma Mass',
'INFO:diceplayer:---------------------------------------------------------------------------------',
'INFO:diceplayer:1 1 0.00000 0.00000 0.00000 0.000000 0.00000 0.0000 1.0079',
'INFO:diceplayer:\n'
]
self.assertEqual(
context.output,
expected_output
)
@mock.patch("builtins.open", mock_open)
def test_dice_start(self):
player = Player("control.test.yml")
player.dice_interface = mock.MagicMock()
player.dice_interface.start = mock.MagicMock()
player.dice_start(1)
player.dice_interface.start.assert_called_once()
if __name__ == '__main__':
unittest.main()