Initial Polarization Process

This commit is contained in:
2023-02-23 04:07:40 -03:00
parent 0f7484756b
commit b9b6c373b1
6 changed files with 73 additions and 23 deletions

View File

@@ -1,5 +1,5 @@
crystal_pol: crystal_pol:
mem: 28 mem: 24
n_procs: 20 n_procs: 20
level: "b3lyp/aug-cc-pVDZ" level: "b3lyp/aug-cc-pVDZ"
pop: "chelpg" pop: "chelpg"

View File

@@ -3,10 +3,11 @@ from crystalpol.shared.system.crystal import Crystal
from crystalpol.shared.config import Config from crystalpol.shared.config import Config
from pathlib import Path, PosixPath from pathlib import Path, PosixPath
from typing import TextIO, Union from typing import TextIO, Union, List
import subprocess import subprocess
import textwrap import textwrap
import shutil import shutil
import sys
import os import os
@@ -23,9 +24,7 @@ class Gaussian:
if self.config.pop not in ["chelpg", "mk", "nbo"]: if self.config.pop not in ["chelpg", "mk", "nbo"]:
self.config.pop = "chelpg" self.config.pop = "chelpg"
def run(self, cycle: int, crystal: Crystal) -> None: def run(self, cycle: int, crystal: Crystal) -> List[float]:
self.create_simulation_dir()
file = Path( file = Path(
"simfiles", "simfiles",
@@ -51,11 +50,14 @@ class Gaussian:
if exit_status != 0: if exit_status != 0:
raise RuntimeError("Gaussian process did not exit properly") raise RuntimeError("Gaussian process did not exit properly")
return self.read_charges_from_gaussian_output() return self.read_charges_from_gaussian_output(
cycle,
crystal.get_number_of_charges()
)
def create_step_dir(self, cycle): def create_step_dir(self, cycle):
step_dir = Path( step_dir = Path(
"simfiles", self.config.simulation_dir,
f"crystal-{str(cycle).zfill(2)}" f"crystal-{str(cycle).zfill(2)}"
) )
if not os.path.exists(step_dir): if not os.path.exists(step_dir):
@@ -79,6 +81,9 @@ class Gaussian:
with open(file, 'w+') as fh: with open(file, 'w+') as fh:
chk_path = file.with_suffix('.chk')
fh.write(f"%Chk={chk_path}\n")
fh.write(f"%Mem={self.config.mem}Gb\n") fh.write(f"%Mem={self.config.mem}Gb\n")
fh.write(f"%Nprocs={self.config.n_procs}\n") fh.write(f"%Nprocs={self.config.n_procs}\n")
@@ -123,13 +128,32 @@ class Gaussian:
for atom in molecule: for atom in molecule:
symbol = atom_symbol[atom.na] symbol = atom_symbol[atom.na]
fh.write( fh.write(
f"{symbol:<2s} "
f"{float(atom.rx):>10.5f} " f"{float(atom.rx):>10.5f} "
f"{float(atom.ry):>10.5f} " f"{float(atom.ry):>10.5f} "
f"{float(atom.rz):>10.5f}\n" f"{float(atom.rz):>10.5f} "
f"{float(atom.chg):>10.5f}\n"
) )
fh.write("\n") fh.write("\n")
def read_charges_from_gaussian_output(self) -> None: def read_charges_from_gaussian_output(self, cycle, number_of_charges: int) -> List[float]:
pass step_dir = Path(
self.config.simulation_dir,
f"crystal-{str(cycle).zfill(2)}"
)
filename = f"crystal-{str(cycle).zfill(2)}.log"
file = Path(step_dir, filename)
try:
with open(file) as data:
lines = data.readlines()
except FileNotFoundError:
sys.exit("Error: cannot open file {}".format(file))
start = lines.pop(0).strip()
while start != "Fitting point charges to electrostatic potential":
start = lines.pop(0).strip()
lines = lines[3:] # Consume 3 more lines
return list(map(lambda x: float(x.split()[2]), lines[:number_of_charges]))

View File

@@ -8,6 +8,8 @@ from crystalpol.gaussian import Gaussian
from typing import List from typing import List
import sys
class Polarization: class Polarization:
__slots__ = ('geom_file', 'outfile', 'config', 'gaussian', 'crystal') __slots__ = ('geom_file', 'outfile', 'config', 'gaussian', 'crystal')
@@ -23,9 +25,18 @@ class Polarization:
def run(self): def run(self):
self.gaussian.create_simulation_dir()
self.read_crystal() self.read_crystal()
self.gaussian.run(1, self.crystal) cycle = 1
charge_diff = sys.float_info.max
while charge_diff > self.config.charge_tolerance:
charge_diff = self.update_crystal_charges(
self.gaussian.run(cycle, self.crystal),
)
cycle += 1
def read_crystal(self) -> None: def read_crystal(self) -> None:
with open(self.geom_file, 'r') as geom_file: with open(self.geom_file, 'r') as geom_file:
@@ -38,11 +49,24 @@ class Polarization:
for molecule in molecules: for molecule in molecules:
self.crystal.add_cell([molecule]) self.crystal.add_cell([molecule])
def update_crystal_charges(self, charges: List[float]) -> float:
charge_diff = []
for cell in self.crystal:
for molecule in cell:
for index, atom in enumerate(molecule):
if atom.chg:
charge_diff.append(abs(atom.chg - charges[index]))
atom.chg = charges[index]
return abs(max(charge_diff, key=abs)) if charge_diff else sys.float_info.max
def _get_molecules_from_lines(self, lines: List[str]) -> List[Molecule]: def _get_molecules_from_lines(self, lines: List[str]) -> List[Molecule]:
if (len(lines) % self.config.n_atoms) == 0: if (len(lines) % self.config.n_atoms) == 0:
molecules: List[Molecule] = [] molecules: List[Molecule] = []
for index, molecule in enumerate(split(lines, self.config.n_atoms)): for index, molecule in enumerate(self.split(lines, self.config.n_atoms)):
mol = Molecule(f"Molecule-{index}") mol = Molecule(f"Molecule-{index}")
for atom_line in molecule: for atom_line in molecule:
symbol, rx, ry, rz = tuple(atom_line.split()) symbol, rx, ry, rz = tuple(atom_line.split())
@@ -68,7 +92,7 @@ class Polarization:
structure.append(atom.symbol) structure.append(atom.symbol)
return structure return structure
@staticmethod
def split(array: List, partitions: int): def split(array: List, partitions: int):
for i in range(0, len(array), partitions): for i in range(0, len(array), partitions):
yield array[i: i + partitions] yield array[i: i + partitions]

View File

@@ -10,6 +10,7 @@ class Config:
n_procs: int = 1 n_procs: int = 1
pop: str = "chelpg" pop: str = "chelpg"
charge_tolerance = 0.02
comment: str = "crystalpol" comment: str = "crystalpol"
simulation_dir = "simfiles" simulation_dir = "simfiles"
mult: list = \ mult: list = \

View File

@@ -34,6 +34,9 @@ class Crystal:
else: else:
self.cells.append(cell) self.cells.append(cell)
def get_number_of_charges(self):
return len(self.cells[0][0])
def _is_valid_cell(self, cell: List[Molecule]) -> bool: def _is_valid_cell(self, cell: List[Molecule]) -> bool:
if len(cell) == len(self.structure): if len(cell) == len(self.structure):
for i, molecule in enumerate(cell): for i, molecule in enumerate(cell):

View File

@@ -28,12 +28,7 @@ class Molecule:
__slots__ = ( __slots__ = (
'mol_name', 'mol_name',
'atoms', 'atoms',
'position', 'position'
'energy',
'gradient',
'hessian',
'total_mass',
'com'
) )
def __init__(self, mol_name: str) -> None: def __init__(self, mol_name: str) -> None:
@@ -51,6 +46,9 @@ class Molecule:
for atom in self.atoms: for atom in self.atoms:
yield atom yield atom
def __len__(self):
return len(self.atoms)
def add_atom(self, a: Atom) -> None: def add_atom(self, a: Atom) -> None:
""" """
Adds Atom instance to the molecule. Adds Atom instance to the molecule.