Source code for mcal.utils.gjf_maker

""" GjfMaker beta (2025/08/18)"""
import os
import warnings
from pathlib import Path
from typing import List, Optional, Tuple

import pandas as pd


[docs] class GjfMaker: parent_dir = Path(os.path.abspath(__file__)).parent.parent ELEMENT_PROP = pd.read_csv(f'{parent_dir}/constants/element_properties.csv') ELEMENTS_NUM = ELEMENT_PROP[['symbol', 'number']].set_index('symbol').to_dict()['number'] def __init__(self, remove_radical_flag: bool = False) -> None: """Initialize the GjfMaker class. Parameters ---------- remove_radical_flag : bool If True, the radical is removed, by default False. Not recommended to use this flag. """ self.remove_radical_flag = remove_radical_flag self._link_dict = {} self._link_num = 0 self._cpu_num = '%NProcShared=9\n' self._mem_num = '%Mem=40GB\n' self._is_chk_file = False self._is_rwf_file = False self.is_detail = False self._function = '# B3LYP/6-31G(d,p)\n' self._root = [] self._title = '\nDefalut Title\n' self._symbols = [] self._charge_spin = '' self._coordinates = [] self._root_space = ' '
[docs] def add_root(self, root: str) -> None: """Add the root of the gjf file.""" if root[0] == '#': root = root[1:] self._root.append(root + '\n')
[docs] def create_chk_file(self) -> None: """Create the chk file.""" self._is_chk_file = True
[docs] def create_rwf_file(self) -> None: """Create the rwf file.""" self._is_rwf_file = True
[docs] def export_gjf( self, file_name: str, save_dir: str = '.', chk_rwf_name: Optional[str] = None ) -> None: """Export the gjf file. Parameters ---------- file_name : str Name of the gjf file. save_dir : str Directory to save the gjf file. chk_rwf_name : Optional[str] Name of the chk or rwf file. """ if self.remove_radical_flag and self._check_radical(): print('This molecule is radical') return os.makedirs(save_dir, exist_ok=True) self._link_dict[self._link_num] = [self._cpu_num, self._mem_num, self._function, self._root] if self._is_chk_file: if chk_rwf_name: self._chk_file = f'%Chk={chk_rwf_name}.chk\n' else: self._chk_file = f'%Chk={save_dir}/{file_name}.chk\n' else: self._chk_file = '' if self._is_rwf_file: if chk_rwf_name: self._rwf_file = f'%RWF={chk_rwf_name}.rwf\n' else: self._rwf_file = f'%RWF={save_dir}/{file_name}.rwf\n' else: self._rwf_file = '' with open(f'{save_dir}/{file_name}.gjf', 'w') as f: for i in range(self._link_num+1): _cpn_num, _mem_num, _function, _root = self._link_dict[i] if i > 0: f.write(f'\n--Link{i}--\n') f.write(_cpn_num) f.write(_mem_num) f.write(self._chk_file) f.write(self._rwf_file) f.write(_function) for r in _root: f.write(f'#{self._root_space}{r}') if i == 0: f.write(self._title) if not self._charge_spin: self._check_charge_spin() f.write(self._charge_spin) for sym, (x, y, z) in zip(self._symbols, self._coordinates): x = f'{x:.8f}'.rjust(14, ' ') y = f'{y:.8f}'.rjust(14, ' ') z = f'{z:.8f}'.rjust(14, ' ') f.write(f' {sym} {x} {y} {z}\n') f.write('\n')
[docs] def opt(self, tight: bool = True) -> None: """Set the optimization option of the gjf file. Parameters ---------- tight : bool If True, the optimization is tight, by default True. """ opt = 'Opt' if tight: opt = 'Opt=Tight' self.add_root(opt)
[docs] def output_detail(self) -> None: """Output the detail to log file.""" self.is_detail = True _, function = self._function.split() self._function = f'#P {function}\n' self._root_space = ' '
[docs] def reset_variable(self) -> None: """Reset the variables of the gjf file.""" self._root = [] self._symbols = [] self._coordinates = [] self._link_dict = {} self._link_num = 0
[docs] def set_charge_spin(self, charge: int, spin: int) -> None: """Set the charge and spin of the molecule. Parameters ---------- charge : int Charge of the molecule. spin : int Spin of the molecule. """ self._charge_spin = f'\n{charge} {spin}\n'
[docs] def set_coordinates(self, coordinates: List[Tuple[float, float, float]]) -> None: """Set the coordinates of the molecule. Parameters ---------- coordinates : List[Tuple[float, float, float]] Coordinates of the molecule. """ self._coordinates.extend(coordinates)
[docs] def set_function(self, function: str) -> None: """Set the function of the gjf file. Parameters ---------- function : str Function of the gjf file. """ detail = '' if self.is_detail: detail = 'P' self._function = f'#{detail} {function}\n'
[docs] def set_resource(self, cpu_num: int, mem_num: int, mem_unit: str = 'GB') -> None: """Set the number of cpu and memory. Parameters ---------- cpu_num : int Number of cpu. mem_num : int Number of memory. mem_unit : str Unit of memory, by default 'GB'. """ self._cpu_num = f'%NProcShared={cpu_num}\n' self._mem_num = f'%Mem={mem_num}{mem_unit}\n'
[docs] def set_symbols(self, symbols: List[str]) -> None: """Set the symbols of the molecule. Parameters ---------- symbols : List[str] Symbols of the molecule. """ self._symbols.extend(symbols)
[docs] def set_title(self, title: str) -> None: """Set the title of the gjf file. Parameters ---------- title : str Title of the gjf file. """ self._title = f'\n{title}\n'
def _check_charge_spin(self) -> None: """Check the charge and spin of the molecule.""" elements = 0 if not self._symbols: self._charge_spin = '' return for symbol in self._symbols: elements += self.ELEMENTS_NUM[symbol] if elements % 2 == 0: self._charge_spin = '\n0 1\n' else: self._charge_spin = '\n0 2\n' def _check_radical(self) -> bool: """Check if the molecule is radical. Returns ------- bool True if the molecule is radical, False otherwise. Warnings -------- This radical check is incomplete. If the number of unpaired electrons is even, it is not considered a radical. """ # FIXME: This radical check is incomplete. If the number of unpaired electrons is even, it is not considered a radical. warnings.warn('This radical check is incomplete. If the number of unpaired electrons is even, it is not considered a radical.') elements = 0 for symbol in self._symbols: elements += self.ELEMENTS_NUM[symbol] if elements % 2 == 0: return False else: return True