From def7fb01fee24aee5f1bca5b8ad3e25d46cd9b6b Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Sat, 28 Oct 2023 13:42:47 -0400 Subject: [PATCH 01/37] Update inputs.py Add "IWAVPR" to the tuple of int_keys. --- pymatgen/io/vasp/inputs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pymatgen/io/vasp/inputs.py b/pymatgen/io/vasp/inputs.py index 56bbd14a3a3..58c5e48d7ac 100644 --- a/pymatgen/io/vasp/inputs.py +++ b/pymatgen/io/vasp/inputs.py @@ -845,6 +845,7 @@ def proc_val(key: str, val: Any): "ISPIND", "LDAUTYPE", "IVDW", + "IWAVPR", ) def smart_int_or_float(numstr): From 75372bfdeb6664011d704c890532ea93b87dfaa6 Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Sat, 4 Nov 2023 18:03:55 -0400 Subject: [PATCH 02/37] Update chargemol_caller.py Add the mpi function when running the chargemol program. --- pymatgen/command_line/chargemol_caller.py | 26 +++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index dff7e89f3f6..17f487589e5 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -80,7 +80,9 @@ def __init__( self, path=None, atomic_densities_path=None, - run_chargemol=True, + run_chargemol: bool =True, + mpi: bool = False, + ncores:int = None, ): """ Initializes the Chargemol Analysis. @@ -97,6 +99,9 @@ def __init__( run_chargemol (bool): Whether to run the Chargemol analysis. If False, the existing Chargemol output files will be read from path. Default: True. + mpi (bool): Whether to run the Chargemol in a parallel way. + ncores: Use how many cores to run the Chargemol! Default is "os.environ.get('SLURM_JOB_CPUS_PER_NODE') or os.environ.get('SLURM_CPUS_ON_NODE')", + or "multiprocessing.cpu_count()". Take your own risk! This default value might not suit you! You'd better set your own number!!! """ if not path: path = os.getcwd() @@ -137,7 +142,7 @@ def __init__( self.aeccar2 = Chgcar.from_file(self._aeccar2path) if self._aeccar2path else None if run_chargemol: - self._execute_chargemol() + self._execute_chargemol(mpi=mpi,ncores=ncores) else: self._from_data_dir(chargemol_output_path=path) @@ -169,7 +174,7 @@ def _get_filepath(path, filename, suffix=""): fpath = paths[0] return fpath - def _execute_chargemol(self, **jobcontrol_kwargs): + def _execute_chargemol(self, mpi=False, ncores=None, **jobcontrol_kwargs): """ Internal function to run Chargemol. @@ -178,9 +183,22 @@ def _execute_chargemol(self, **jobcontrol_kwargs): required by Chargemol. If None, Pymatgen assumes that this is defined in a "DDEC6_ATOMIC_DENSITIES_DIR" environment variable. Default: None. + mpi(bool): Whether run the Chargemol in a parallel way. Default is False. + ncores (int): The number of cores you want to use. Default is os.getenv('SLURM_CPUS_ON_NODE') or os.getenv('SLURM_NTASKS') or multiprocessing.cpu_count(). jobcontrol_kwargs: Keyword arguments for _write_jobscript_for_chargemol. """ - + + if mpi: + if ncores: + CHARGEMOLEXE = f"mpirun -n {ncores} {CHARGEMOLEXE}" + else: + ncores = os.getenv('SLURM_CPUS_ON_NODE') or os.getenv('SLURM_NTASKS') + if ncores: + ncores = multiprocessing.cpu_count() + CHARGEMOLEXE = f"mpirun -n {ncores} {CHARGEMOLEXE}" + else: + pass + with ScratchDir("."): with zopen(self._chgcarpath, "rt") as f_in: with open("CHGCAR", "wt") as f_out: From 5efa8cdba952d0bb634ae8a798b03e10b8f3e47f Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Sat, 4 Nov 2023 18:09:41 -0400 Subject: [PATCH 03/37] Update chargemol_caller.py import multiprocessing --- pymatgen/command_line/chargemol_caller.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index 17f487589e5..b719ba8f6be 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -53,6 +53,7 @@ import os import shutil import subprocess +import multiprocessing import warnings from shutil import which From d67aa94e2c4539cfcb30bc3277191318fe2098bd Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Sat, 4 Nov 2023 19:23:27 -0400 Subject: [PATCH 04/37] Update chargemol_caller.py Comment the warning while check the path. Revise the command of mpirun. --- pymatgen/command_line/chargemol_caller.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index b719ba8f6be..da3a1948d8b 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -77,6 +77,10 @@ class ChargemolAnalysis: bond orders, and related properties. """ + CHARGEMOLEXE = ( + which("Chargemol_09_26_2017_linux_parallel") or which("Chargemol_09_26_2017_linux_serial") or which("chargemol") +) + def __init__( self, path=None, @@ -170,8 +174,8 @@ def _get_filepath(path, filename, suffix=""): # and this would give 'static' over 'relax2' over 'relax' # however, better to use 'suffix' kwarg to avoid this! paths.sort(reverse=True) - warning_msg = f"Multiple files detected, using {os.path.basename(paths[0])}" if len(paths) > 1 else None - warnings.warn(warning_msg) + # warning_msg = f"Multiple files detected, using {os.path.basename(paths[0])}" if len(paths) > 1 else None + # warnings.warn(warning_msg) fpath = paths[0] return fpath @@ -191,12 +195,12 @@ def _execute_chargemol(self, mpi=False, ncores=None, **jobcontrol_kwargs): if mpi: if ncores: - CHARGEMOLEXE = f"mpirun -n {ncores} {CHARGEMOLEXE}" + CHARGEMOLEXE = ["mpirun", "-n", str(ncores), ChargemolAnalysis.CHARGEMOLEXE] else: ncores = os.getenv('SLURM_CPUS_ON_NODE') or os.getenv('SLURM_NTASKS') if ncores: ncores = multiprocessing.cpu_count() - CHARGEMOLEXE = f"mpirun -n {ncores} {CHARGEMOLEXE}" + CHARGEMOLEXE = ["mpirun", "-n", str(ncores), ChargemolAnalysis.CHARGEMOLEXE] else: pass From b7e2343954029baebd30f2a362542ab5f4357fd2 Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Sat, 4 Nov 2023 19:31:07 -0400 Subject: [PATCH 05/37] Update chargemol_caller.py Change the command to run chargemol --- pymatgen/command_line/chargemol_caller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index da3a1948d8b..49b62595341 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -202,7 +202,7 @@ def _execute_chargemol(self, mpi=False, ncores=None, **jobcontrol_kwargs): ncores = multiprocessing.cpu_count() CHARGEMOLEXE = ["mpirun", "-n", str(ncores), ChargemolAnalysis.CHARGEMOLEXE] else: - pass + CHARGEMOLEXE = ChargemolAnalysis.CHARGEMOLEXE with ScratchDir("."): with zopen(self._chgcarpath, "rt") as f_in: From 6498f618b863fe1d638f728e2344055b16730558 Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Sun, 5 Nov 2023 02:08:14 -0500 Subject: [PATCH 06/37] Update chargemol_caller.py Change the previous copy to link for CHGCAR, POTCAR, AECCAR0, AECCAR2. Add the save parameter, to control whether save the output files of chargemol. --- pymatgen/command_line/chargemol_caller.py | 59 +++++++++++++++-------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index 49b62595341..2a2986c8807 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -56,6 +56,7 @@ import multiprocessing import warnings from shutil import which +from pathlib import Path import numpy as np from monty.io import zopen @@ -87,7 +88,8 @@ def __init__( atomic_densities_path=None, run_chargemol: bool =True, mpi: bool = False, - ncores:int = None, + ncores: int = None, + save: bool = False, ): """ Initializes the Chargemol Analysis. @@ -107,6 +109,7 @@ def __init__( mpi (bool): Whether to run the Chargemol in a parallel way. ncores: Use how many cores to run the Chargemol! Default is "os.environ.get('SLURM_JOB_CPUS_PER_NODE') or os.environ.get('SLURM_CPUS_ON_NODE')", or "multiprocessing.cpu_count()". Take your own risk! This default value might not suit you! You'd better set your own number!!! + save: save (bool): Whether to save the Chargemol output files. Default is False. """ if not path: path = os.getcwd() @@ -123,7 +126,8 @@ def __init__( if atomic_densities_path == "": atomic_densities_path = os.getcwd() self._atomic_densities_path = atomic_densities_path - + self.save = save + self._chgcarpath = self._get_filepath(path, "CHGCAR") self._potcarpath = self._get_filepath(path, "POTCAR") self._aeccar0path = self._get_filepath(path, "AECCAR0") @@ -204,22 +208,14 @@ def _execute_chargemol(self, mpi=False, ncores=None, **jobcontrol_kwargs): else: CHARGEMOLEXE = ChargemolAnalysis.CHARGEMOLEXE - with ScratchDir("."): - with zopen(self._chgcarpath, "rt") as f_in: - with open("CHGCAR", "wt") as f_out: - shutil.copyfileobj(f_in, f_out) - with zopen(self._potcarpath, "rt") as f_in: - with open("POTCAR", "wt") as f_out: - shutil.copyfileobj(f_in, f_out) - with zopen(self._aeccar0path, "rt") as f_in: - with open("AECCAR0", "wt") as f_out: - shutil.copyfileobj(f_in, f_out) - with zopen(self._aeccar2path, "rt") as f_in: - with open("AECCAR2", "wt") as f_out: - shutil.copyfileobj(f_in, f_out) - + if self.save: + save_path = Path(Path.cwd(),"charge") + save_path.mkdir(parents=True, exist_ok=True) + source = [Path(self._chgcarpath),Path(self._potcarpath),Path(self._aeccar0path),Path(self._aeccar2path)] + links = [Path(save_path,"CHGCAR"),Path(save_path,"POTCAR"),Path(save_path,"AECCAR0"),Path(save_path,"AECCAR2")] + [links[i].symlink_to(source[i]) for i in range(len(links))] # write job_script file: - self._write_jobscript_for_chargemol(**jobcontrol_kwargs) + self._write_jobscript_for_chargemol(write_path=str(save_path)+"/job_control.txt",**jobcontrol_kwargs) # Run Chargemol with subprocess.Popen( @@ -227,14 +223,38 @@ def _execute_chargemol(self, mpi=False, ncores=None, **jobcontrol_kwargs): stdout=subprocess.PIPE, stdin=subprocess.PIPE, close_fds=True, + cwd=save_path, ) as rs: rs.communicate() if rs.returncode != 0: raise RuntimeError( f"Chargemol exited with return code {int(rs.returncode)}. Please check your Chargemol installation." ) + self._from_data_dir(chargemol_output_path=save_path) - self._from_data_dir() + else: + with ScratchDir("."): + cwd = Path.cwd() + source = [Path(self._chgcarpath),Path(self._potcarpath),Path(self._aeccar0path),Path(self._aeccar2path)] + links = [Path(cwd,"CHGCAR"),Path(cwd,"POTCAR"),Path(cwd,"AECCAR0"),Path(cwd,"AECCAR2")] + [links[i].symlink_to(source[i]) for i in range(len(links))] + # write job_script file: + self._write_jobscript_for_chargemol(**jobcontrol_kwargs) + + # Run Chargemol + with subprocess.Popen( + CHARGEMOLEXE, + stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + close_fds=True, + ) as rs: + rs.communicate() + if rs.returncode != 0: + raise RuntimeError( + f"Chargemol exited with return code {int(rs.returncode)}. Please check your Chargemol installation." + ) + + self._from_data_dir() def _from_data_dir(self, chargemol_output_path=None): """ @@ -388,6 +408,7 @@ def _write_jobscript_for_chargemol( periodicity=[True, True, True], method="ddec6", compute_bond_orders=True, + write_path: str = "job_control.txt", ): """ Writes job_script.txt for Chargemol execution @@ -451,7 +472,7 @@ def _write_jobscript_for_chargemol( bo = ".true." if compute_bond_orders else ".false." lines += f"\n\n{bo}\n\n" - with open("job_control.txt", "wt") as fh: + with open(write_path, "wt") as fh: fh.write(lines) @staticmethod From ad4809747c842d07e84851b31bf95bf55904ae49 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 21:47:26 +0000 Subject: [PATCH 07/37] pre-commit auto-fixes --- pymatgen/command_line/chargemol_caller.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index 59329e08bd9..51bf1796b6a 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -83,7 +83,7 @@ class ChargemolAnalysis: CHARGEMOLEXE = ( which("Chargemol_09_26_2017_linux_parallel") or which("Chargemol_09_26_2017_linux_serial") or which("chargemol") ) - + def __init__( self, path=None, @@ -115,7 +115,7 @@ def __init__( Default: True. mpi (bool): Whether to run the Chargemol in a parallel way. ncores: Use how many cores to run the Chargemol! Default is "os.environ.get('SLURM_JOB_CPUS_PER_NODE') or os.environ.get('SLURM_CPUS_ON_NODE')", - or "multiprocessing.cpu_count()". Take your own risk! This default value might not suit you! You'd better set your own number!!! + or "multiprocessing.cpu_count()". Take your own risk! This default value might not suit you! You'd better set your own number!!! save: save (bool): Whether to save the Chargemol output files. Default is False. the existing Chargemol output files will be read from path. Default: True. """ @@ -131,7 +131,7 @@ def __init__( self._atomic_densities_path = atomic_densities_path self.save = save - + self._chgcarpath = self._get_filepath(path, "CHGCAR") self._potcarpath = self._get_filepath(path, "POTCAR") self._aeccar0path = self._get_filepath(path, "AECCAR0") @@ -213,10 +213,10 @@ def _execute_chargemol(self, **job_control_kwargs): Default: None. mpi(bool): Whether run the Chargemol in a parallel way. Default is False. - ncores (int): The number of cores you want to use. Default is os.getenv('SLURM_CPUS_ON_NODE') or os.getenv('SLURM_NTASKS') or multiprocessing.cpu_count(). + ncores (int): The number of cores you want to use. Default is os.getenv('SLURM_CPUS_ON_NODE') or os.getenv('SLURM_NTASKS') or multiprocessing.cpu_count(). jobcontrol_kwargs: Keyword arguments for _write_jobscript_for_chargemol. """ - + if mpi: if ncores: CHARGEMOLEXE = ["mpirun", "-n", str(ncores), ChargemolAnalysis.CHARGEMOLEXE] @@ -227,7 +227,7 @@ def _execute_chargemol(self, **job_control_kwargs): CHARGEMOLEXE = ["mpirun", "-n", str(ncores), ChargemolAnalysis.CHARGEMOLEXE] else: CHARGEMOLEXE = ChargemolAnalysis.CHARGEMOLEXE - + if self.save: save_path = Path(Path.cwd(),"charge") save_path.mkdir(parents=True, exist_ok=True) @@ -279,7 +279,7 @@ def _execute_chargemol(self, **job_control_kwargs): [links[i].symlink_to(source[i]) for i in range(len(links))] # write job_script file: self._write_jobscript_for_chargemol(**jobcontrol_kwargs) - + # Run Chargemol with subprocess.Popen( CHARGEMOLEXE, @@ -292,7 +292,7 @@ def _execute_chargemol(self, **job_control_kwargs): raise RuntimeError( f"Chargemol exited with return code {int(rs.returncode)}. Please check your Chargemol installation." ) - + self._from_data_dir() def _from_data_dir(self, chargemol_output_path=None): From e1756fd92d6e864939ffa872e520ac5f7729d533 Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Mon, 6 Nov 2023 22:20:47 +0000 Subject: [PATCH 08/37] Revise some small parts in chargemol_caller.py --- pymatgen/command_line/chargemol_caller.py | 47 +---------------------- 1 file changed, 1 insertion(+), 46 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index 51bf1796b6a..b4b4b95e17e 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -96,12 +96,6 @@ def __init__( """ Initializes the Chargemol Analysis. - path: str | Path | None = None, - atomic_densities_path: str | Path | None = None, - run_chargemol: bool = True, - ) -> None: - """Initializes the Chargemol Analysis. - Args: path (str): Path to the CHGCAR, POTCAR, AECCAR0, and AECCAR files. @@ -129,20 +123,13 @@ def __init__( if atomic_densities_path == "": atomic_densities_path = os.getcwd() self._atomic_densities_path = atomic_densities_path - self.save = save - + self._chgcarpath = self._get_filepath(path, "CHGCAR") self._potcarpath = self._get_filepath(path, "POTCAR") self._aeccar0path = self._get_filepath(path, "AECCAR0") self._aeccar2path = self._get_filepath(path, "AECCAR2") - if run_chargemol and not (self._chgcarpath and self._potcarpath and self._aeccar0path and self._aeccar2path): - - self._chgcar_path = self._get_filepath(path, "CHGCAR") - self._potcar_path = self._get_filepath(path, "POTCAR") - self._aeccar0_path = self._get_filepath(path, "AECCAR0") - self._aeccar2_path = self._get_filepath(path, "AECCAR2") if run_chargemol and not ( self._chgcar_path and self._potcar_path and self._aeccar0_path and self._aeccar2_path ): @@ -197,14 +184,6 @@ def _execute_chargemol(self, mpi=False, ncores=None, **jobcontrol_kwargs): """ Internal function to run Chargemol. - if len(paths) > 1: - warnings.warn(f"Multiple files detected, using {os.path.basename(paths[0])}") - fpath = paths[0] - return fpath - - def _execute_chargemol(self, **job_control_kwargs): - """Internal function to run Chargemol. - Args: atomic_densities_path (str): Path to the atomic densities directory @@ -247,30 +226,6 @@ def _execute_chargemol(self, **job_control_kwargs): ) as rs: rs.communicate() - job_control_kwargs: Keyword arguments for _write_jobscript_for_chargemol. - """ - with ScratchDir("."): - try: - os.symlink(self._chgcar_path, "./CHGCAR") - os.symlink(self._potcar_path, "./POTCAR") - os.symlink(self._aeccar0_path, "./AECCAR0") - os.symlink(self._aeccar2_path, "./AECCAR2") - except OSError as exc: - print(f"Error creating symbolic link: {exc}") - - # write job_script file: - self._write_jobscript_for_chargemol(**job_control_kwargs) - - # Run Chargemol - with subprocess.Popen(CHARGEMOL_EXE, stdout=subprocess.PIPE, stdin=subprocess.PIPE, close_fds=True) as rs: - _stdout, stderr = rs.communicate() - if rs.returncode != 0: - raise RuntimeError( - f"{CHARGEMOL_EXE} exit code: {rs.returncode}, error message: {stderr!s}. " - "Please check your Chargemol installation." - ) - self._from_data_dir(chargemol_output_path=save_path) - else: with ScratchDir("."): cwd = Path.cwd() From e9c69bc0cb9c0974d5b7f900c0f65c43286c6756 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 22:22:30 +0000 Subject: [PATCH 09/37] pre-commit auto-fixes --- pymatgen/command_line/chargemol_caller.py | 41 +++++++++++++---------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index b4b4b95e17e..c275d3f6832 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -42,17 +42,15 @@ from __future__ import annotations +import multiprocessing import os import subprocess -import multiprocessing import warnings from glob import glob -from shutil import which - from pathlib import Path +from shutil import which from typing import TYPE_CHECKING - import numpy as np from monty.tempfile import ScratchDir @@ -81,14 +79,14 @@ class ChargemolAnalysis: """ CHARGEMOLEXE = ( - which("Chargemol_09_26_2017_linux_parallel") or which("Chargemol_09_26_2017_linux_serial") or which("chargemol") -) + which("Chargemol_09_26_2017_linux_parallel") or which("Chargemol_09_26_2017_linux_serial") or which("chargemol") + ) def __init__( self, path=None, atomic_densities_path=None, - run_chargemol: bool =True, + run_chargemol: bool = True, mpi: bool = False, ncores: int = None, save: bool = False, @@ -124,7 +122,7 @@ def __init__( atomic_densities_path = os.getcwd() self._atomic_densities_path = atomic_densities_path self.save = save - + self._chgcarpath = self._get_filepath(path, "CHGCAR") self._potcarpath = self._get_filepath(path, "POTCAR") self._aeccar0path = self._get_filepath(path, "AECCAR0") @@ -149,7 +147,7 @@ def __init__( self.aeccar2 = Chgcar.from_file(self._aeccar2_path) if self._aeccar2_path else None if run_chargemol: - self._execute_chargemol(mpi=mpi,ncores=ncores) + self._execute_chargemol(mpi=mpi, ncores=ncores) else: self._from_data_dir(chargemol_output_path=path) @@ -195,12 +193,11 @@ def _execute_chargemol(self, mpi=False, ncores=None, **jobcontrol_kwargs): ncores (int): The number of cores you want to use. Default is os.getenv('SLURM_CPUS_ON_NODE') or os.getenv('SLURM_NTASKS') or multiprocessing.cpu_count(). jobcontrol_kwargs: Keyword arguments for _write_jobscript_for_chargemol. """ - if mpi: if ncores: CHARGEMOLEXE = ["mpirun", "-n", str(ncores), ChargemolAnalysis.CHARGEMOLEXE] else: - ncores = os.getenv('SLURM_CPUS_ON_NODE') or os.getenv('SLURM_NTASKS') + ncores = os.getenv("SLURM_CPUS_ON_NODE") or os.getenv("SLURM_NTASKS") if ncores: ncores = multiprocessing.cpu_count() CHARGEMOLEXE = ["mpirun", "-n", str(ncores), ChargemolAnalysis.CHARGEMOLEXE] @@ -208,13 +205,18 @@ def _execute_chargemol(self, mpi=False, ncores=None, **jobcontrol_kwargs): CHARGEMOLEXE = ChargemolAnalysis.CHARGEMOLEXE if self.save: - save_path = Path(Path.cwd(),"charge") + save_path = Path(Path.cwd(), "charge") save_path.mkdir(parents=True, exist_ok=True) - source = [Path(self._chgcarpath),Path(self._potcarpath),Path(self._aeccar0path),Path(self._aeccar2path)] - links = [Path(save_path,"CHGCAR"),Path(save_path,"POTCAR"),Path(save_path,"AECCAR0"),Path(save_path,"AECCAR2")] + source = [Path(self._chgcarpath), Path(self._potcarpath), Path(self._aeccar0path), Path(self._aeccar2path)] + links = [ + Path(save_path, "CHGCAR"), + Path(save_path, "POTCAR"), + Path(save_path, "AECCAR0"), + Path(save_path, "AECCAR2"), + ] [links[i].symlink_to(source[i]) for i in range(len(links))] # write job_script file: - self._write_jobscript_for_chargemol(write_path=str(save_path)+"/job_control.txt",**jobcontrol_kwargs) + self._write_jobscript_for_chargemol(write_path=str(save_path) + "/job_control.txt", **jobcontrol_kwargs) # Run Chargemol with subprocess.Popen( @@ -229,8 +231,13 @@ def _execute_chargemol(self, mpi=False, ncores=None, **jobcontrol_kwargs): else: with ScratchDir("."): cwd = Path.cwd() - source = [Path(self._chgcarpath),Path(self._potcarpath),Path(self._aeccar0path),Path(self._aeccar2path)] - links = [Path(cwd,"CHGCAR"),Path(cwd,"POTCAR"),Path(cwd,"AECCAR0"),Path(cwd,"AECCAR2")] + source = [ + Path(self._chgcarpath), + Path(self._potcarpath), + Path(self._aeccar0path), + Path(self._aeccar2path), + ] + links = [Path(cwd, "CHGCAR"), Path(cwd, "POTCAR"), Path(cwd, "AECCAR0"), Path(cwd, "AECCAR2")] [links[i].symlink_to(source[i]) for i in range(len(links))] # write job_script file: self._write_jobscript_for_chargemol(**jobcontrol_kwargs) From bdd8f89f83e050d3e275a9ebaceb1d97002ed09b Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Mon, 6 Nov 2023 22:58:27 +0000 Subject: [PATCH 10/37] Change the import method of Path, shorten the line --- pymatgen/command_line/chargemol_caller.py | 30 +++++++++++++++-------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index c275d3f6832..3463c9cadbf 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -49,8 +49,11 @@ from glob import glob from pathlib import Path from shutil import which + +from pathlib import Path from typing import TYPE_CHECKING + import numpy as np from monty.tempfile import ScratchDir @@ -86,9 +89,9 @@ def __init__( self, path=None, atomic_densities_path=None, - run_chargemol: bool = True, + run_chargemol: bool =True, mpi: bool = False, - ncores: int = None, + ncores: Optional[int] = None, save: bool = False, ): """ @@ -106,10 +109,13 @@ def __init__( the existing Chargemol output files will be read from path. Default: True. mpi (bool): Whether to run the Chargemol in a parallel way. - ncores: Use how many cores to run the Chargemol! Default is "os.environ.get('SLURM_JOB_CPUS_PER_NODE') or os.environ.get('SLURM_CPUS_ON_NODE')", - or "multiprocessing.cpu_count()". Take your own risk! This default value might not suit you! You'd better set your own number!!! + ncores: Use how many cores to run the Chargemol! + Default is "os.environ.get('SLURM_JOB_CPUS_PER_NODE'), + or os.environ.get('SLURM_CPUS_ON_NODE')", or "multiprocessing.cpu_count()". + Take your own risk! This default value might not suit you! + You'd better set your own number!!! save: save (bool): Whether to save the Chargemol output files. Default is False. - the existing Chargemol output files will be read from path. Default: True. + the existing Chargemol output files will be read from path. Default: True. """ path = path or os.getcwd() if run_chargemol and not CHARGEMOL_EXE: @@ -190,7 +196,9 @@ def _execute_chargemol(self, mpi=False, ncores=None, **jobcontrol_kwargs): Default: None. mpi(bool): Whether run the Chargemol in a parallel way. Default is False. - ncores (int): The number of cores you want to use. Default is os.getenv('SLURM_CPUS_ON_NODE') or os.getenv('SLURM_NTASKS') or multiprocessing.cpu_count(). + ncores (int): The number of cores you want to use. + Default is os.getenv('SLURM_CPUS_ON_NODE') or os.getenv('SLURM_NTASKS') + or multiprocessing.cpu_count(). jobcontrol_kwargs: Keyword arguments for _write_jobscript_for_chargemol. """ if mpi: @@ -258,12 +266,13 @@ def _execute_chargemol(self, mpi=False, ncores=None, **jobcontrol_kwargs): self._from_data_dir() def _from_data_dir(self, chargemol_output_path=None): - """Internal command to parse Chargemol files from a directory. + """ + Internal command to parse Chargemol files from a directory. Args: - chargemol_output_path (str): Path to the folder containing the - Chargemol output files. - Default: None (current working directory). + chargemol_output_path (str): Path to the folder containing + the Chargemol output files. + Default: None (current working directory). """ if chargemol_output_path is None: chargemol_output_path = "." @@ -411,6 +420,7 @@ def _write_jobscript_for_chargemol( method (str): Method to use for the analysis. Options include "ddec6" and "ddec3". Default: "ddec6" compute_bond_orders (bool): Whether to compute bond orders. Default: True. + write_path (str): The path of output files of chargemol if you want to save them. """ self.net_charge = net_charge self.periodicity = periodicity From 3fabe7815f3b39de55b3e6a6e14a751c7022c6a1 Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Mon, 6 Nov 2023 22:45:37 +0000 Subject: [PATCH 11/37] Change the import method of Path, shorten the line --- pymatgen/command_line/chargemol_caller.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index 3463c9cadbf..270da4161a5 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -47,11 +47,9 @@ import subprocess import warnings from glob import glob -from pathlib import Path from shutil import which -from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional import numpy as np From 3f0cce06b39cf34b6e4388d9566530728e19fe50 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 23:00:19 +0000 Subject: [PATCH 12/37] pre-commit auto-fixes --- pymatgen/command_line/chargemol_caller.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index 270da4161a5..a931c909269 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -48,10 +48,8 @@ import warnings from glob import glob from shutil import which - from typing import TYPE_CHECKING, Optional - import numpy as np from monty.tempfile import ScratchDir @@ -87,7 +85,7 @@ def __init__( self, path=None, atomic_densities_path=None, - run_chargemol: bool =True, + run_chargemol: bool = True, mpi: bool = False, ncores: Optional[int] = None, save: bool = False, @@ -107,10 +105,10 @@ def __init__( the existing Chargemol output files will be read from path. Default: True. mpi (bool): Whether to run the Chargemol in a parallel way. - ncores: Use how many cores to run the Chargemol! - Default is "os.environ.get('SLURM_JOB_CPUS_PER_NODE'), - or os.environ.get('SLURM_CPUS_ON_NODE')", or "multiprocessing.cpu_count()". - Take your own risk! This default value might not suit you! + ncores: Use how many cores to run the Chargemol! + Default is "os.environ.get('SLURM_JOB_CPUS_PER_NODE'), + or os.environ.get('SLURM_CPUS_ON_NODE')", or "multiprocessing.cpu_count()". + Take your own risk! This default value might not suit you! You'd better set your own number!!! save: save (bool): Whether to save the Chargemol output files. Default is False. the existing Chargemol output files will be read from path. Default: True. @@ -194,8 +192,8 @@ def _execute_chargemol(self, mpi=False, ncores=None, **jobcontrol_kwargs): Default: None. mpi(bool): Whether run the Chargemol in a parallel way. Default is False. - ncores (int): The number of cores you want to use. - Default is os.getenv('SLURM_CPUS_ON_NODE') or os.getenv('SLURM_NTASKS') + ncores (int): The number of cores you want to use. + Default is os.getenv('SLURM_CPUS_ON_NODE') or os.getenv('SLURM_NTASKS') or multiprocessing.cpu_count(). jobcontrol_kwargs: Keyword arguments for _write_jobscript_for_chargemol. """ @@ -268,7 +266,7 @@ def _from_data_dir(self, chargemol_output_path=None): Internal command to parse Chargemol files from a directory. Args: - chargemol_output_path (str): Path to the folder containing + chargemol_output_path (str): Path to the folder containing the Chargemol output files. Default: None (current working directory). """ @@ -418,7 +416,7 @@ def _write_jobscript_for_chargemol( method (str): Method to use for the analysis. Options include "ddec6" and "ddec3". Default: "ddec6" compute_bond_orders (bool): Whether to compute bond orders. Default: True. - write_path (str): The path of output files of chargemol if you want to save them. + write_path (str): The path of output files of chargemol if you want to save them. """ self.net_charge = net_charge self.periodicity = periodicity From e482d87dee07c39a6f9e5b071fdce815b27826e7 Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Mon, 6 Nov 2023 23:18:33 +0000 Subject: [PATCH 13/37] Delete trailing whitespace, shorten the code line --- pymatgen/command_line/chargemol_caller.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index a931c909269..f0bfdd64622 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -48,7 +48,9 @@ import warnings from glob import glob from shutil import which -from typing import TYPE_CHECKING, Optional + +from typing import TYPE_CHECKING + import numpy as np from monty.tempfile import ScratchDir @@ -57,8 +59,8 @@ from pymatgen.io.vasp.inputs import Potcar from pymatgen.io.vasp.outputs import Chgcar -if TYPE_CHECKING: - from pathlib import Path + + __author__ = "Martin Siron, Andrew S. Rosen" __version__ = "0.1" @@ -83,11 +85,11 @@ class ChargemolAnalysis: def __init__( self, - path=None, + path: str | None =None, atomic_densities_path=None, run_chargemol: bool = True, mpi: bool = False, - ncores: Optional[int] = None, + ncores: int | None = None, save: bool = False, ): """ @@ -180,7 +182,7 @@ def _get_filepath(path, filename, suffix=""): fpath = paths[0] return fpath - def _execute_chargemol(self, mpi=False, ncores=None, **jobcontrol_kwargs): + def _execute_chargemol(self, mpi=False, ncores: int | None =None, **jobcontrol_kwargs): """ Internal function to run Chargemol. @@ -256,12 +258,13 @@ def _execute_chargemol(self, mpi=False, ncores=None, **jobcontrol_kwargs): rs.communicate() if rs.returncode != 0: raise RuntimeError( - f"Chargemol exited with return code {int(rs.returncode)}. Please check your Chargemol installation." + f"Chargemol exited with return code {int(rs.returncode)}. + Please check your Chargemol installation." ) self._from_data_dir() - def _from_data_dir(self, chargemol_output_path=None): + def _from_data_dir(self, chargemol_output_path: str | None =None): """ Internal command to parse Chargemol files from a directory. @@ -336,7 +339,7 @@ def get_charge_transfer(self, atom_index, charge_type="ddec"): charge_transfer = -self.cm5_charges[atom_index] return charge_transfer - def get_charge(self, atom_index, nelect=None, charge_type="ddec"): + def get_charge(self, atom_index, nelect: int | None =None, charge_type="ddec"): """Convenience method to get the charge on a particular atom using the same sign convention as the BaderAnalysis. Note that this is *not* the partial atomic charge. This value is nelect (e.g. ZVAL from the POTCAR) + the From f0374197aa481da05c37c4f199bd6565fe79e9f3 Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Mon, 6 Nov 2023 23:21:38 +0000 Subject: [PATCH 14/37] All Path pack --- pymatgen/command_line/chargemol_caller.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index f0bfdd64622..0d28b78ad1d 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -50,6 +50,7 @@ from shutil import which from typing import TYPE_CHECKING +from pathlib import Path import numpy as np From 845ac0b43c30389ecd42159c5fef4c5590e09fdd Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Mon, 6 Nov 2023 23:38:14 +0000 Subject: [PATCH 15/37] Revise the f-string --- pymatgen/command_line/chargemol_caller.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index 0d28b78ad1d..d5463704206 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -259,8 +259,8 @@ def _execute_chargemol(self, mpi=False, ncores: int | None =None, **jobcontrol_k rs.communicate() if rs.returncode != 0: raise RuntimeError( - f"Chargemol exited with return code {int(rs.returncode)}. - Please check your Chargemol installation." + f"Chargemol exited with return code {int(rs.returncode)}. " + "Please check your Chargemol installation." ) self._from_data_dir() From 3509c5b78908cfc5448b847ce00c7b9b1e3ffb29 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 23:40:05 +0000 Subject: [PATCH 16/37] pre-commit auto-fixes --- pymatgen/command_line/chargemol_caller.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index d5463704206..5697f5a3925 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -47,11 +47,8 @@ import subprocess import warnings from glob import glob -from shutil import which - -from typing import TYPE_CHECKING from pathlib import Path - +from shutil import which import numpy as np from monty.tempfile import ScratchDir @@ -60,9 +57,6 @@ from pymatgen.io.vasp.inputs import Potcar from pymatgen.io.vasp.outputs import Chgcar - - - __author__ = "Martin Siron, Andrew S. Rosen" __version__ = "0.1" __maintainer__ = "Shyue Ping Ong" @@ -86,7 +80,7 @@ class ChargemolAnalysis: def __init__( self, - path: str | None =None, + path: str | None = None, atomic_densities_path=None, run_chargemol: bool = True, mpi: bool = False, @@ -183,7 +177,7 @@ def _get_filepath(path, filename, suffix=""): fpath = paths[0] return fpath - def _execute_chargemol(self, mpi=False, ncores: int | None =None, **jobcontrol_kwargs): + def _execute_chargemol(self, mpi=False, ncores: int | None = None, **jobcontrol_kwargs): """ Internal function to run Chargemol. @@ -265,7 +259,7 @@ def _execute_chargemol(self, mpi=False, ncores: int | None =None, **jobcontrol_k self._from_data_dir() - def _from_data_dir(self, chargemol_output_path: str | None =None): + def _from_data_dir(self, chargemol_output_path: str | None = None): """ Internal command to parse Chargemol files from a directory. @@ -340,7 +334,7 @@ def get_charge_transfer(self, atom_index, charge_type="ddec"): charge_transfer = -self.cm5_charges[atom_index] return charge_transfer - def get_charge(self, atom_index, nelect: int | None =None, charge_type="ddec"): + def get_charge(self, atom_index, nelect: int | None = None, charge_type="ddec"): """Convenience method to get the charge on a particular atom using the same sign convention as the BaderAnalysis. Note that this is *not* the partial atomic charge. This value is nelect (e.g. ZVAL from the POTCAR) + the From 4b1bff21478c6b563206c92e60d06ba6dae698fd Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Tue, 7 Nov 2023 00:26:25 +0000 Subject: [PATCH 17/37] Revise _chgcarpath to _chgcar_path, add self._from_data_dir() to mode with outputs saved --- pymatgen/command_line/chargemol_caller.py | 32 +++++++++++++---------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index 5697f5a3925..808cd404a07 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -122,10 +122,10 @@ def __init__( self._atomic_densities_path = atomic_densities_path self.save = save - self._chgcarpath = self._get_filepath(path, "CHGCAR") - self._potcarpath = self._get_filepath(path, "POTCAR") - self._aeccar0path = self._get_filepath(path, "AECCAR0") - self._aeccar2path = self._get_filepath(path, "AECCAR2") + self._chgcar_path = self._get_filepath(path, "CHGCAR") + self._potcar_path = self._get_filepath(path, "POTCAR") + self._aeccar0_path = self._get_filepath(path, "AECCAR0") + self._aeccar2_path = self._get_filepath(path, "AECCAR2") if run_chargemol and not ( self._chgcar_path and self._potcar_path and self._aeccar0_path and self._aeccar2_path @@ -177,7 +177,7 @@ def _get_filepath(path, filename, suffix=""): fpath = paths[0] return fpath - def _execute_chargemol(self, mpi=False, ncores: int | None = None, **jobcontrol_kwargs): + def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_kwargs): """ Internal function to run Chargemol. @@ -189,7 +189,7 @@ def _execute_chargemol(self, mpi=False, ncores: int | None = None, **jobcontrol_ Default: None. mpi(bool): Whether run the Chargemol in a parallel way. Default is False. - ncores (int): The number of cores you want to use. + ncores (str): The number of cores you want to use. Default is os.getenv('SLURM_CPUS_ON_NODE') or os.getenv('SLURM_NTASKS') or multiprocessing.cpu_count(). jobcontrol_kwargs: Keyword arguments for _write_jobscript_for_chargemol. @@ -199,7 +199,7 @@ def _execute_chargemol(self, mpi=False, ncores: int | None = None, **jobcontrol_ CHARGEMOLEXE = ["mpirun", "-n", str(ncores), ChargemolAnalysis.CHARGEMOLEXE] else: ncores = os.getenv("SLURM_CPUS_ON_NODE") or os.getenv("SLURM_NTASKS") - if ncores: + if not ncores: ncores = multiprocessing.cpu_count() CHARGEMOLEXE = ["mpirun", "-n", str(ncores), ChargemolAnalysis.CHARGEMOLEXE] else: @@ -208,14 +208,15 @@ def _execute_chargemol(self, mpi=False, ncores: int | None = None, **jobcontrol_ if self.save: save_path = Path(Path.cwd(), "charge") save_path.mkdir(parents=True, exist_ok=True) - source = [Path(self._chgcarpath), Path(self._potcarpath), Path(self._aeccar0path), Path(self._aeccar2path)] + source = [Path(self._chgcar_path), Path(self._potcar_path), Path(self._aeccar0_path), Path(self._aeccar2_path)] links = [ Path(save_path, "CHGCAR"), Path(save_path, "POTCAR"), Path(save_path, "AECCAR0"), Path(save_path, "AECCAR2"), ] - [links[i].symlink_to(source[i]) for i in range(len(links))] + for link, src in zip(links, source): + link.symlink_to(src) # write job_script file: self._write_jobscript_for_chargemol(write_path=str(save_path) + "/job_control.txt", **jobcontrol_kwargs) @@ -228,18 +229,21 @@ def _execute_chargemol(self, mpi=False, ncores: int | None = None, **jobcontrol_ cwd=save_path, ) as rs: rs.communicate() + + self._from_data_dir(chargemol_output_path = str(save_path)) else: with ScratchDir("."): cwd = Path.cwd() source = [ - Path(self._chgcarpath), - Path(self._potcarpath), - Path(self._aeccar0path), - Path(self._aeccar2path), + Path(self._chgcar_path), + Path(self._potcar_path), + Path(self._aeccar0_path), + Path(self._aeccar2_path), ] links = [Path(cwd, "CHGCAR"), Path(cwd, "POTCAR"), Path(cwd, "AECCAR0"), Path(cwd, "AECCAR2")] - [links[i].symlink_to(source[i]) for i in range(len(links))] + for link, src in zip(links, source): + link.symlink_to(src) # write job_script file: self._write_jobscript_for_chargemol(**jobcontrol_kwargs) From 92872b0a85c4deb1f5625cc7ea42b54a7ed8b60f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Nov 2023 00:30:26 +0000 Subject: [PATCH 18/37] pre-commit auto-fixes --- pymatgen/command_line/chargemol_caller.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index 808cd404a07..1fc88e063b5 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -177,7 +177,7 @@ def _get_filepath(path, filename, suffix=""): fpath = paths[0] return fpath - def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_kwargs): + def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_kwargs): """ Internal function to run Chargemol. @@ -208,7 +208,12 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol if self.save: save_path = Path(Path.cwd(), "charge") save_path.mkdir(parents=True, exist_ok=True) - source = [Path(self._chgcar_path), Path(self._potcar_path), Path(self._aeccar0_path), Path(self._aeccar2_path)] + source = [ + Path(self._chgcar_path), + Path(self._potcar_path), + Path(self._aeccar0_path), + Path(self._aeccar2_path), + ] links = [ Path(save_path, "CHGCAR"), Path(save_path, "POTCAR"), @@ -229,8 +234,8 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol cwd=save_path, ) as rs: rs.communicate() - - self._from_data_dir(chargemol_output_path = str(save_path)) + + self._from_data_dir(chargemol_output_path=str(save_path)) else: with ScratchDir("."): From 1c243d3a51119942710b456b96b1d1f216b11ddd Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Tue, 7 Nov 2023 00:38:13 +0000 Subject: [PATCH 19/37] Shorten the line, remove the whitespace --- pymatgen/command_line/chargemol_caller.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index 1fc88e063b5..22996e4e5ee 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -209,11 +209,17 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ save_path = Path(Path.cwd(), "charge") save_path.mkdir(parents=True, exist_ok=True) source = [ + Path(self._chgcar_path), + Path(self._potcar_path), + Path(self._aeccar0_path), + Path(self._aeccar2_path), + , ] + links = [ Path(save_path, "CHGCAR"), Path(save_path, "POTCAR"), @@ -223,7 +229,8 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ for link, src in zip(links, source): link.symlink_to(src) # write job_script file: - self._write_jobscript_for_chargemol(write_path=str(save_path) + "/job_control.txt", **jobcontrol_kwargs) + write_path = str(save_path) + "/job_control.txt" + self._write_jobscript_for_chargemol(write_path=write_path, **jobcontrol_kwargs) # Run Chargemol with subprocess.Popen( @@ -234,8 +241,8 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ cwd=save_path, ) as rs: rs.communicate() - - self._from_data_dir(chargemol_output_path=str(save_path)) + + self._from_data_dir(chargemol_output_path = str(save_path)) else: with ScratchDir("."): From 88cba347ae34e575f0bd24a8cc7bfd7cf41b8000 Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Tue, 7 Nov 2023 00:34:39 +0000 Subject: [PATCH 20/37] Shorten the line, remove the whitespace --- pymatgen/command_line/chargemol_caller.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index 22996e4e5ee..5ded6096d11 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -177,7 +177,7 @@ def _get_filepath(path, filename, suffix=""): fpath = paths[0] return fpath - def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_kwargs): + def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_kwargs): """ Internal function to run Chargemol. @@ -209,15 +209,10 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ save_path = Path(Path.cwd(), "charge") save_path.mkdir(parents=True, exist_ok=True) source = [ - - Path(self._chgcar_path), - - Path(self._potcar_path), - - Path(self._aeccar0_path), - + Path(self._chgcar_path), + Path(self._potcar_path), + Path(self._aeccar0_path), Path(self._aeccar2_path), - , ] links = [ @@ -241,9 +236,8 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ cwd=save_path, ) as rs: rs.communicate() - self._from_data_dir(chargemol_output_path = str(save_path)) - + else: with ScratchDir("."): cwd = Path.cwd() From 4ca657ff4d903dc791d58a77dc8349a8836007c3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Nov 2023 00:41:01 +0000 Subject: [PATCH 21/37] pre-commit auto-fixes --- pymatgen/command_line/chargemol_caller.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index 5ded6096d11..5311a88bc25 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -177,7 +177,7 @@ def _get_filepath(path, filename, suffix=""): fpath = paths[0] return fpath - def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_kwargs): + def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_kwargs): """ Internal function to run Chargemol. @@ -209,9 +209,9 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol save_path = Path(Path.cwd(), "charge") save_path.mkdir(parents=True, exist_ok=True) source = [ - Path(self._chgcar_path), - Path(self._potcar_path), - Path(self._aeccar0_path), + Path(self._chgcar_path), + Path(self._potcar_path), + Path(self._aeccar0_path), Path(self._aeccar2_path), ] @@ -236,8 +236,8 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol cwd=save_path, ) as rs: rs.communicate() - self._from_data_dir(chargemol_output_path = str(save_path)) - + self._from_data_dir(chargemol_output_path=str(save_path)) + else: with ScratchDir("."): cwd = Path.cwd() From f773a3ecc4e8ff2bf427e13c3b1dcc77de28eec8 Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Tue, 7 Nov 2023 00:43:48 +0000 Subject: [PATCH 22/37] delete trailing whitespace and black line contains whitespace --- pymatgen/command_line/chargemol_caller.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index 5311a88bc25..e26c74859a9 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -236,8 +236,8 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ cwd=save_path, ) as rs: rs.communicate() - self._from_data_dir(chargemol_output_path=str(save_path)) - + self._from_data_dir(chargemol_output_path = str(save_path)) + else: with ScratchDir("."): cwd = Path.cwd() From cb10c905bfa211a62fed8a064787a71044970a01 Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Tue, 7 Nov 2023 00:41:21 +0000 Subject: [PATCH 23/37] delete trailing whitespace and black line contains whitespace --- pymatgen/command_line/chargemol_caller.py | 653 +++++++++++++++++++++- 1 file changed, 652 insertions(+), 1 deletion(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index e26c74859a9..a15b697aa35 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -182,6 +182,658 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ Internal function to run Chargemol. + Args: + atomic_densities_path (str): Path to the atomic densities directory + required by Chargemol. If None, Pymatgen assumes that this is + defined in a "DDEC6_ATOMIC_DENSITIES_DIR" environment variable. + Default: None. + + mpi(bool): Whether run the Chargemol in a parallel way. Default is False. + ncores (str): The number of cores you want to use. + Default is os.getenv('SLURM_CPUS_ON_NODE') or os.getenv('SLURM_NTASKS') + or multiprocessing.cpu_count(). + jobcontrol_kwargs: Keyword arguments for _write_jobscript_for_chargemol. + """ + if mpi: + if ncores: + CHARGEMOLEXE = ["mpirun", "-n", str(ncores), ChargemolAnalysis.CHARGEMOLEXE] + else: + ncores = os.getenv("SLURM_CPUS_ON_NODE") or os.getenv("SLURM_NTASKS") + if not ncores: + ncores = multiprocessing.cpu_count() + CHARGEMOLEXE = ["mpirun", "-n", str(ncores), ChargemolAnalysis.CHARGEMOLEXE] + else: + CHARGEMOLEXE = ChargemolAnalysis.CHARGEMOLEXE + + if self.save: + save_path = Path(Path.cwd(), "charge") + save_path.mkdir(parents=True, exist_ok=True) + source = [ + Path(self._chgcar_path), + Path(self._potcar_path), + Path(self._aeccar0_path), + Path(self._aeccar2_path), + ] + + links = [ + Path(save_path, "CHGCAR"), + Path(save_path, "POTCAR"), + Path(save_path, "AECCAR0"), + Path(save_path, "AECCAR2"), + ] + for link, src in zip(links, source): + link.symlink_to(src) + # write job_script file: + write_path = str(save_path) + "/job_control.txt" + self._write_jobscript_for_chargemol(write_path=write_path, **jobcontrol_kwargs) + + # Run Chargemol + with subprocess.Popen( + CHARGEMOLEXE, + stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + close_fds=True, + cwd=save_path, + ) as rs: + rs.communicate() + self._from_data_dir(chargemol_output_path=str(save_path)) + + else: + with ScratchDir("."): + cwd = Path.cwd() + source = [ + Path(self._chgcar_path), + Path(self._potcar_path), + Path(self._aeccar0_path), + Path(self._aeccar2_path), + ] + links = [Path(cwd, "CHGCAR"), Path(cwd, "POTCAR"), Path(cwd, "AECCAR0"), Path(cwd, "AECCAR2")] + for link, src in zip(links, source): + link.symlink_to(src) + # write job_script file: + self._write_jobscript_for_chargemol(**jobcontrol_kwargs) + + # Run Chargemol + with subprocess.Popen( + CHARGEMOLEXE, + stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + close_fds=True, + ) as rs: + rs.communicate() + if rs.returncode != 0: + raise RuntimeError( + f"Chargemol exited with return code {int(rs.returncode)}. " + "Please check your Chargemol installation." + ) + + self._from_data_dir() + + def _from_data_dir(self, chargemol_output_path: str | None = None): + """ + Internal command to parse Chargemol files from a directory. + + Args: + chargemol_output_path (str): Path to the folder containing + the Chargemol output files. + Default: None (current working directory). + """ + if chargemol_output_path is None: + chargemol_output_path = "." + + charge_path = f"{chargemol_output_path}/DDEC6_even_tempered_net_atomic_charges.xyz" + self.ddec_charges = self._get_data_from_xyz(charge_path) + self.dipoles = self._get_dipole_info(charge_path) + + bond_order_path = f"{chargemol_output_path}/DDEC6_even_tempered_bond_orders.xyz" + if os.path.exists(bond_order_path): + self.bond_order_sums = self._get_data_from_xyz(bond_order_path) + self.bond_order_dict = self._get_bond_order_info(bond_order_path) + else: + self.bond_order_sums = self.bond_order_dict = None + + spin_moment_path = f"{chargemol_output_path}/DDEC6_even_tempered_atomic_spin_moments.xyz" + if os.path.exists(spin_moment_path): + self.ddec_spin_moments = self._get_data_from_xyz(spin_moment_path) + else: + self.ddec_spin_moments = None + + rsquared_path = f"{chargemol_output_path}/DDEC_atomic_Rsquared_moments.xyz" + if os.path.exists(rsquared_path): + self.ddec_rsquared_moments = self._get_data_from_xyz(rsquared_path) + else: + self.ddec_rsquared_moments = None + + rcubed_path = f"{chargemol_output_path}/DDEC_atomic_Rcubed_moments.xyz" + if os.path.exists(rcubed_path): + self.ddec_rcubed_moments = self._get_data_from_xyz(rcubed_path) + else: + self.ddec_rcubed_moments = None + + rfourth_path = f"{chargemol_output_path}/DDEC_atomic_Rfourth_moments.xyz" + if os.path.exists(rfourth_path): + self.ddec_rfourth_moments = self._get_data_from_xyz(rfourth_path) + else: + self.ddec_rfourth_moments = None + + ddec_analysis_path = f"{chargemol_output_path}/VASP_DDEC_analysis.output" + if os.path.exists(ddec_analysis_path): + self.cm5_charges = self._get_cm5_data_from_output(ddec_analysis_path) + else: + self.cm5_charges = None + + def get_charge_transfer(self, atom_index, charge_type="ddec"): + """Returns the charge transferred for a particular atom. A positive value means + that the site has gained electron density (i.e. exhibits anionic character) + whereas a negative value means the site has lost electron density (i.e. exhibits + cationic character). This is the same thing as the negative of the partial atomic + charge. + + Args: + atom_index (int): Index of atom to get charge transfer for. + charge_type (str): Type of charge to use ("ddec" or "cm5"). + + Returns: + float: charge transferred at atom_index + """ + if charge_type.lower() not in ["ddec", "cm5"]: + raise ValueError(f"Invalid {charge_type=}") + if charge_type.lower() == "ddec": + charge_transfer = -self.ddec_charges[atom_index] + elif charge_type.lower() == "cm5": + charge_transfer = -self.cm5_charges[atom_index] + return charge_transfer + + def get_charge(self, atom_index, nelect: int | None = None, charge_type="ddec"): + """Convenience method to get the charge on a particular atom using the same + sign convention as the BaderAnalysis. Note that this is *not* the partial + atomic charge. This value is nelect (e.g. ZVAL from the POTCAR) + the + charge transferred. If you want the partial atomic charge, use + get_partial_charge(). + + Args: + atom_index (int): Index of atom to get charge for. + nelect (int): number of electrons associated with an isolated atom at this index. + For most DFT codes this corresponds to the number of valence electrons + associated with the pseudopotential. If None, this value will be automatically + obtained from the POTCAR (if present). + Default: None. + charge_type (str): Type of charge to use ("ddec" or "cm5"). + + Returns: + float: charge on atom_index + """ + if nelect: + charge = nelect + self.get_charge_transfer(atom_index, charge_type=charge_type) + elif self.potcar and self.natoms: + charge = None + potcar_indices = [] + for i, v in enumerate(self.natoms): + potcar_indices += [i] * v + nelect = self.potcar[potcar_indices[atom_index]].nelectrons + charge = nelect + self.get_charge_transfer(atom_index, charge_type=charge_type) + else: + charge = None + return charge + + def get_partial_charge(self, atom_index, charge_type="ddec"): + """Convenience method to get the partial atomic charge on a particular atom. + This is the value printed in the Chargemol analysis. + + Args: + atom_index (int): Index of atom to get charge for. + charge_type (str): Type of charge to use ("ddec" or "cm5"). + """ + if charge_type.lower() not in ["ddec", "cm5"]: + raise ValueError(f"Invalid charge_type: {charge_type}") + if charge_type.lower() == "ddec": + partial_charge = self.ddec_charges[atom_index] + elif charge_type.lower() == "cm5": + partial_charge = self.cm5_charges[atom_index] + return partial_charge + + def get_bond_order(self, index_from, index_to): + """Convenience method to get the bond order between two atoms. + + Args: + index_from (int): Index of atom to get bond order from. + index_to (int): Index of atom to get bond order to. + + Returns: + float: bond order between atoms + """ + bonded_set = self.bond_order_dict[index_from]["bonded_to"] + bond_orders = [v["bond_order"] for v in bonded_set if v["index"] == index_to] + return 0.0 if bond_orders == [] else np.sum(bond_orders) + + def _write_jobscript_for_chargemol( + self, + net_charge=0.0, + periodicity=(True, True, True), + method="ddec6", + compute_bond_orders=True, + write_path: str = "job_control.txt", + ): + """Writes job_script.txt for Chargemol execution. + + Args: + net_charge (float): Net charge of the system. + Defaults to 0.0. + periodicity (tuple[bool]): Periodicity of the system. + Default: (True, True, True). + method (str): Method to use for the analysis. Options include "ddec6" + and "ddec3". Default: "ddec6" + compute_bond_orders (bool): Whether to compute bond orders. Default: True. + write_path (str): The path of output files of chargemol if you want to save them. + """ + self.net_charge = net_charge + self.periodicity = periodicity + self.method = method + + lines = "" + + # Net Charge + if net_charge: + lines += f"\n{net_charge}\n\n" + + # Periodicity + if periodicity: + per_a = ".true." if periodicity[0] else ".false." + per_b = ".true." if periodicity[1] else ".false." + per_c = ".true." if periodicity[2] else ".false." + lines += ( + f"\n{per_a}\n{per_b}\n{per_c}\n" + "\n" + ) + + # atomic_densities dir + atomic_densities_path = self._atomic_densities_path or os.getenv("DDEC6_ATOMIC_DENSITIES_DIR") + if atomic_densities_path is None: + raise OSError( + "The DDEC6_ATOMIC_DENSITIES_DIR environment variable must be set or the atomic_densities_path must" + " be specified" + ) + if not os.path.exists(atomic_densities_path): + raise FileNotFoundError(f"{atomic_densities_path=} does not exist") + + # This is to fix a Chargemol filepath nuance + if os.name == "nt": # Windows + if atomic_densities_path[-1] != "\\": + atomic_densities_path += "\\" + elif atomic_densities_path[-1] != "/": + atomic_densities_path += "/" + + lines += ( + f"\n\n{atomic_densities_path}\n\n" + ) + + # Charge type + lines += f"\n\n{method.upper()}\n\n" + + if compute_bond_orders: + bo = ".true." if compute_bond_orders else ".false." + lines += f"\n\n{bo}\n\n" + + with open(write_path, "w") as fh: + fh.write(lines) + + @staticmethod + def _get_dipole_info(filepath): + """Internal command to process dipoles. + + Args: + filepath (str): The path to the DDEC6_even_tempered_net_atomic_charges.xyz file + """ + idx = 0 + start = False + dipoles = [] + with open(filepath) as r: + for line in r: + if "The following XYZ" in line: + start = True + idx += 1 + continue + if start and line.strip() == "": + break + if idx >= 2: + dipoles.append([float(d) for d in line.strip().split()[7:10]]) + if start: + idx += 1 + + return dipoles + + @staticmethod + def _get_bond_order_info(filename): + """Internal command to process pairwise bond order information. + + Args: + filename (str): The path to the DDEC6_even_tempered_bond_orders.xyz file + """ + # Get where relevant info for each atom starts + bond_order_info = {} + + with open(filename) as r: + for line in r: + split = line.strip().split() + if "Printing BOs" in line: + start_idx = int(split[5]) - 1 + start_el = Element(split[7]) + bond_order_info[start_idx] = {"element": start_el, "bonded_to": []} + elif "Bonded to the" in line: + direction = tuple(int(i.split(")")[0].split(",")[0]) for i in split[4:7]) + end_idx = int(split[12]) - 1 + end_el = Element(split[14]) + bo = float(split[20]) + spin_bo = float(split[-1]) + bonded_to = { + "index": end_idx, + "element": end_el, + "bond_order": bo, + "direction": direction, + "spin_polarization": spin_bo, + } + bond_order_info[start_idx]["bonded_to"].append(bonded_to) + elif "The sum of bond orders for this atom" in line: + bond_order_info[start_idx]["bond_order_sum"] = float(split[-1]) + + return bond_order_info + + def get_property_decorated_structure(self): + """Takes CHGCAR's structure object and updates it with properties + from the Chargemol analysis. + + Returns: + Pymatgen structure with site properties added + """ + struct = self.structure.copy() + struct.add_site_property("partial_charge_ddec6", self.ddec_charges) + if self.dipoles: + struct.add_site_property("dipole_ddec6", self.dipoles) + if self.bond_order_sums: + struct.add_site_property("bond_order_sum_ddec6", self.bond_order_sums) + if self.ddec_spin_moments: + struct.add_site_property("spin_moment_ddec6", self.ddec_spin_moments) + if self.cm5_charges: + struct.add_site_property("partial_charge_cm5", self.cm5_charges) + return struct + + @property + def summary(self): + """Returns a dictionary summary of the Chargemol analysis + { + "ddec": { + "partial_charges": list[float], + "spin_moments": list[float], + "dipoles": list[float], + "rsquared_moments": list[float], + "rcubed_moments": list[float], + "rfourth_moments": list[float], + "bond_order_dict": dict + }, + "cm5": { + "partial_charges": list[float], + } + }. + """ + summary = {} + ddec_summary = {"partial_charges": self.ddec_charges} + if self.bond_order_sums: + ddec_summary["bond_order_sums"] = self.bond_order_sums + if self.ddec_spin_moments: + ddec_summary["spin_moments"] = self.ddec_spin_moments + if self.dipoles: + ddec_summary["dipoles"] = self.dipoles + if self.ddec_rsquared_moments: + ddec_summary["rsquared_moments"] = self.ddec_rsquared_moments + if self.ddec_rcubed_moments: + ddec_summary["rcubed_moments"] = self.ddec_rcubed_moments + if self.ddec_rfourth_moments: + ddec_summary["rfourth_moments"] = self.ddec_rfourth_moments + if self.bond_order_dict: + ddec_summary["bond_order_dict"] = self.bond_order_dict + + cm5_summary = {"partial_charges": self.cm5_charges} if self.cm5_charges else None + + summary["ddec"] = ddec_summary + summary["cm5"] = cm5_summary + + return summary + + @staticmethod + def _get_data_from_xyz(xyz_path): + """Internal command to process Chargemol XYZ files. + + Args: + xyz_path (str): Path to XYZ file + + Returns: + list[float]: site-specific properties + """ + props = [] + if os.path.exists(xyz_path): + with open(xyz_path) as r: + for i, line in enumerate(r): + if i <= 1: + continue + if line.strip() == "": + break + props.append(float(line.split()[-1])) + else: + raise FileNotFoundError(f"{xyz_path} not found") + + return props + + @staticmethod + def _get_cm5_data_from_output(ddec_analysis_path): + """Internal command to process Chargemol CM5 data. + + Args: + ddec_analysis_path (str): Path VASP_DDEC_analysis.output file + + Returns: + list[float]: CM5 charges + """ + props = [] + if os.path.exists(ddec_analysis_path): + start = False + with open(ddec_analysis_path) as r: + for line in r: + if "computed CM5" in line: + start = True + continue + if "Hirshfeld and CM5" in line: + break + if start: + vals = line.split() + props.extend([float(c) for c in [val.strip() for val in vals]]) + else: + raise FileNotFoundError(f"{ddec_analysis_path} not found") + return props +"""This module implements an interface to Thomas Manz's Chargemol code +https://sourceforge.net/projects/ddec for calculating DDEC3, DDEC6, and CM5 population analyses. + +This module depends on a compiled chargemol executable being available in the path. +If you use this module, please cite the following based on which modules you use: + +Chargemol: +(1) T. A. Manz and N. Gabaldon Limas, Chargemol program for performing DDEC analysis, +Version 3.5, 2017, ddec.sourceforge.net. + +DDEC6 Charges: +(1) T. A. Manz and N. Gabaldon Limas, “Introducing DDEC6 atomic population analysis: +part 1. Charge partitioning theory and methodology,” RSC Adv., 6 (2016) 47771-47801. +(2) N. Gabaldon Limas and T. A. Manz, “Introducing DDEC6 atomic population analysis: +part 2. Computed results for a wide range of periodic and nonperiodic materials,” +(3) N. Gabaldon Limas and T. A. Manz, “Introducing DDEC6 atomic population analysis: +part 4. Efficient parallel computation of net atomic charges, atomic spin moments, +bond orders, and more,” RSC Adv., 8 (2018) 2678-2707. + +CM5 Charges: +(1) A.V. Marenich, S.V. Jerome, C.J. Cramer, D.G. Truhlar, "Charge Model 5: An Extension +of Hirshfeld Population Analysis for the Accurate Description of Molecular Interactions +in Gaseous and Condensed Phases", J. Chem. Theory. Comput., 8 (2012) 527-541. + +Spin Moments: +(1) T. A. Manz and D. S. Sholl, “Methods for Computing Accurate Atomic Spin Moments for +Collinear and Noncollinear Magnetism in Periodic and Nonperiodic Materials,” +J. Chem. Theory Comput. 7 (2011) 4146-4164. + +Bond Orders: +(1) “Introducing DDEC6 atomic population analysis: part 3. Comprehensive method to compute +bond orders,” RSC Adv., 7 (2017) 45552-45581. + +DDEC3 Charges: +(1) T. A. Manz and D. S. Sholl, “Improved Atoms-in-Molecule Charge Partitioning Functional +for Simultaneously Reproducing the Electrostatic Potential and Chemical States in Periodic +and Non-Periodic Materials,” J. Chem. Theory Comput. 8 (2012) 2844-2867. +(2) T. A. Manz and D. S. Sholl, “Chemically Meaningful Atomic Charges that Reproduce the +Electrostatic Potential in Periodic and Nonperiodic Materials,” J. Chem. Theory Comput. 6 +(2010) 2455-2468. +""" + +from __future__ import annotations + +import multiprocessing +import os +import subprocess +import warnings +from glob import glob +from pathlib import Path +from shutil import which + +import numpy as np +from monty.tempfile import ScratchDir + +from pymatgen.core import Element +from pymatgen.io.vasp.inputs import Potcar +from pymatgen.io.vasp.outputs import Chgcar + +__author__ = "Martin Siron, Andrew S. Rosen" +__version__ = "0.1" +__maintainer__ = "Shyue Ping Ong" +__email__ = "shyuep@gmail.com" +__date__ = "01/18/21" + +CHARGEMOL_EXE = ( + which("Chargemol_09_26_2017_linux_parallel") or which("Chargemol_09_26_2017_linux_serial") or which("chargemol") +) + + +class ChargemolAnalysis: + """Chargemol analysis for DDEC3, DDEC6, and/or CM5 population analyses, + including the calculation of partial atomic charges, atomic spin moments, + bond orders, and related properties. + """ + + CHARGEMOLEXE = ( + which("Chargemol_09_26_2017_linux_parallel") or which("Chargemol_09_26_2017_linux_serial") or which("chargemol") + ) + + def __init__( + self, + path: str | None = None, + atomic_densities_path=None, + run_chargemol: bool = True, + mpi: bool = False, + ncores: int | None = None, + save: bool = False, + ): + """ + Initializes the Chargemol Analysis. + + + Args: + path (str): Path to the CHGCAR, POTCAR, AECCAR0, and AECCAR files. + The files can be gzipped or not. Default: None (current working directory). + atomic_densities_path (str | None): Path to the atomic densities directory + required by Chargemol. If None, Pymatgen assumes that this is + defined in a "DDEC6_ATOMIC_DENSITIES_DIR" environment variable. + Only used if run_chargemol is True. Default: None. + run_chargemol (bool): Whether to run the Chargemol analysis. If False, + the existing Chargemol output files will be read from path. + Default: True. + mpi (bool): Whether to run the Chargemol in a parallel way. + ncores: Use how many cores to run the Chargemol! + Default is "os.environ.get('SLURM_JOB_CPUS_PER_NODE'), + or os.environ.get('SLURM_CPUS_ON_NODE')", or "multiprocessing.cpu_count()". + Take your own risk! This default value might not suit you! + You'd better set your own number!!! + save: save (bool): Whether to save the Chargemol output files. Default is False. + the existing Chargemol output files will be read from path. Default: True. + """ + path = path or os.getcwd() + if run_chargemol and not CHARGEMOL_EXE: + raise OSError( + "ChargemolAnalysis requires the Chargemol executable to be in PATH." + " Please download the library at https://sourceforge.net/projects/ddec/files" + "and follow the instructions." + ) + if atomic_densities_path == "": + atomic_densities_path = os.getcwd() + self._atomic_densities_path = atomic_densities_path + self.save = save + + self._chgcar_path = self._get_filepath(path, "CHGCAR") + self._potcar_path = self._get_filepath(path, "POTCAR") + self._aeccar0_path = self._get_filepath(path, "AECCAR0") + self._aeccar2_path = self._get_filepath(path, "AECCAR2") + + if run_chargemol and not ( + self._chgcar_path and self._potcar_path and self._aeccar0_path and self._aeccar2_path + ): + raise FileNotFoundError("CHGCAR, AECCAR0, AECCAR2, and POTCAR are all needed for Chargemol.") + if self._chgcar_path: + self.chgcar = Chgcar.from_file(self._chgcar_path) + self.structure = self.chgcar.structure + self.natoms = self.chgcar.poscar.natoms + else: + self.chgcar = self.structure = self.natoms = None + warnings.warn("No CHGCAR found. Some properties may be unavailable.", UserWarning) + if self._potcar_path: + self.potcar = Potcar.from_file(self._potcar_path) + else: + warnings.warn("No POTCAR found. Some properties may be unavailable.", UserWarning) + self.aeccar0 = Chgcar.from_file(self._aeccar0_path) if self._aeccar0_path else None + self.aeccar2 = Chgcar.from_file(self._aeccar2_path) if self._aeccar2_path else None + + if run_chargemol: + self._execute_chargemol(mpi=mpi, ncores=ncores) + else: + self._from_data_dir(chargemol_output_path=path) + + @staticmethod + def _get_filepath(path, filename, suffix=""): + """Returns the full path to the filename in the path. Works even if the file has + a .gz extension. + + Args: + path (str): Path to the file. + filename (str): Filename. + suffix (str): Optional suffix at the end of the filename. + + Returns: + str: Absolute path to the file. + """ + name_pattern = f"{filename}{suffix}*" if filename != "POTCAR" else f"{filename}*" + paths = glob(os.path.join(path, name_pattern)) + fpath = None + if len(paths) >= 1: + # using reverse=True because, if multiple files are present, + # they likely have suffixes 'static', 'relax', 'relax2', etc. + # and this would give 'static' over 'relax2' over 'relax' + # however, better to use 'suffix' kwarg to avoid this! + paths.sort(reverse=True) + # warning_msg = f"Multiple files detected, using {os.path.basename(paths[0])}" if len(paths) > 1 else None + # warnings.warn(warning_msg) + fpath = paths[0] + return fpath + + def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_kwargs): + """ + Internal function to run Chargemol. + + Args: atomic_densities_path (str): Path to the atomic densities directory required by Chargemol. If None, Pymatgen assumes that this is @@ -237,7 +889,6 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ ) as rs: rs.communicate() self._from_data_dir(chargemol_output_path = str(save_path)) - else: with ScratchDir("."): cwd = Path.cwd() From 841c728823e1c821048f830f93a58452bf5eb5d4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Nov 2023 00:45:17 +0000 Subject: [PATCH 24/37] pre-commit auto-fixes --- pymatgen/command_line/chargemol_caller.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index a15b697aa35..629d7195d99 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -650,6 +650,8 @@ def _get_cm5_data_from_output(ddec_analysis_path): else: raise FileNotFoundError(f"{ddec_analysis_path} not found") return props + + """This module implements an interface to Thomas Manz's Chargemol code https://sourceforge.net/projects/ddec for calculating DDEC3, DDEC6, and CM5 population analyses. @@ -829,7 +831,7 @@ def _get_filepath(path, filename, suffix=""): fpath = paths[0] return fpath - def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_kwargs): + def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_kwargs): """ Internal function to run Chargemol. @@ -888,7 +890,7 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol cwd=save_path, ) as rs: rs.communicate() - self._from_data_dir(chargemol_output_path = str(save_path)) + self._from_data_dir(chargemol_output_path=str(save_path)) else: with ScratchDir("."): cwd = Path.cwd() From db4eeb2938fd607d8bfca031cf3c8c52e9cc2548 Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Tue, 7 Nov 2023 00:49:04 +0000 Subject: [PATCH 25/37] Delete the wrong lines --- pymatgen/command_line/chargemol_caller.py | 653 ---------------------- 1 file changed, 653 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index 629d7195d99..5311a88bc25 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -650,656 +650,3 @@ def _get_cm5_data_from_output(ddec_analysis_path): else: raise FileNotFoundError(f"{ddec_analysis_path} not found") return props - - -"""This module implements an interface to Thomas Manz's Chargemol code -https://sourceforge.net/projects/ddec for calculating DDEC3, DDEC6, and CM5 population analyses. - -This module depends on a compiled chargemol executable being available in the path. -If you use this module, please cite the following based on which modules you use: - -Chargemol: -(1) T. A. Manz and N. Gabaldon Limas, Chargemol program for performing DDEC analysis, -Version 3.5, 2017, ddec.sourceforge.net. - -DDEC6 Charges: -(1) T. A. Manz and N. Gabaldon Limas, “Introducing DDEC6 atomic population analysis: -part 1. Charge partitioning theory and methodology,” RSC Adv., 6 (2016) 47771-47801. -(2) N. Gabaldon Limas and T. A. Manz, “Introducing DDEC6 atomic population analysis: -part 2. Computed results for a wide range of periodic and nonperiodic materials,” -(3) N. Gabaldon Limas and T. A. Manz, “Introducing DDEC6 atomic population analysis: -part 4. Efficient parallel computation of net atomic charges, atomic spin moments, -bond orders, and more,” RSC Adv., 8 (2018) 2678-2707. - -CM5 Charges: -(1) A.V. Marenich, S.V. Jerome, C.J. Cramer, D.G. Truhlar, "Charge Model 5: An Extension -of Hirshfeld Population Analysis for the Accurate Description of Molecular Interactions -in Gaseous and Condensed Phases", J. Chem. Theory. Comput., 8 (2012) 527-541. - -Spin Moments: -(1) T. A. Manz and D. S. Sholl, “Methods for Computing Accurate Atomic Spin Moments for -Collinear and Noncollinear Magnetism in Periodic and Nonperiodic Materials,” -J. Chem. Theory Comput. 7 (2011) 4146-4164. - -Bond Orders: -(1) “Introducing DDEC6 atomic population analysis: part 3. Comprehensive method to compute -bond orders,” RSC Adv., 7 (2017) 45552-45581. - -DDEC3 Charges: -(1) T. A. Manz and D. S. Sholl, “Improved Atoms-in-Molecule Charge Partitioning Functional -for Simultaneously Reproducing the Electrostatic Potential and Chemical States in Periodic -and Non-Periodic Materials,” J. Chem. Theory Comput. 8 (2012) 2844-2867. -(2) T. A. Manz and D. S. Sholl, “Chemically Meaningful Atomic Charges that Reproduce the -Electrostatic Potential in Periodic and Nonperiodic Materials,” J. Chem. Theory Comput. 6 -(2010) 2455-2468. -""" - -from __future__ import annotations - -import multiprocessing -import os -import subprocess -import warnings -from glob import glob -from pathlib import Path -from shutil import which - -import numpy as np -from monty.tempfile import ScratchDir - -from pymatgen.core import Element -from pymatgen.io.vasp.inputs import Potcar -from pymatgen.io.vasp.outputs import Chgcar - -__author__ = "Martin Siron, Andrew S. Rosen" -__version__ = "0.1" -__maintainer__ = "Shyue Ping Ong" -__email__ = "shyuep@gmail.com" -__date__ = "01/18/21" - -CHARGEMOL_EXE = ( - which("Chargemol_09_26_2017_linux_parallel") or which("Chargemol_09_26_2017_linux_serial") or which("chargemol") -) - - -class ChargemolAnalysis: - """Chargemol analysis for DDEC3, DDEC6, and/or CM5 population analyses, - including the calculation of partial atomic charges, atomic spin moments, - bond orders, and related properties. - """ - - CHARGEMOLEXE = ( - which("Chargemol_09_26_2017_linux_parallel") or which("Chargemol_09_26_2017_linux_serial") or which("chargemol") - ) - - def __init__( - self, - path: str | None = None, - atomic_densities_path=None, - run_chargemol: bool = True, - mpi: bool = False, - ncores: int | None = None, - save: bool = False, - ): - """ - Initializes the Chargemol Analysis. - - - Args: - path (str): Path to the CHGCAR, POTCAR, AECCAR0, and AECCAR files. - The files can be gzipped or not. Default: None (current working directory). - atomic_densities_path (str | None): Path to the atomic densities directory - required by Chargemol. If None, Pymatgen assumes that this is - defined in a "DDEC6_ATOMIC_DENSITIES_DIR" environment variable. - Only used if run_chargemol is True. Default: None. - run_chargemol (bool): Whether to run the Chargemol analysis. If False, - the existing Chargemol output files will be read from path. - Default: True. - mpi (bool): Whether to run the Chargemol in a parallel way. - ncores: Use how many cores to run the Chargemol! - Default is "os.environ.get('SLURM_JOB_CPUS_PER_NODE'), - or os.environ.get('SLURM_CPUS_ON_NODE')", or "multiprocessing.cpu_count()". - Take your own risk! This default value might not suit you! - You'd better set your own number!!! - save: save (bool): Whether to save the Chargemol output files. Default is False. - the existing Chargemol output files will be read from path. Default: True. - """ - path = path or os.getcwd() - if run_chargemol and not CHARGEMOL_EXE: - raise OSError( - "ChargemolAnalysis requires the Chargemol executable to be in PATH." - " Please download the library at https://sourceforge.net/projects/ddec/files" - "and follow the instructions." - ) - if atomic_densities_path == "": - atomic_densities_path = os.getcwd() - self._atomic_densities_path = atomic_densities_path - self.save = save - - self._chgcar_path = self._get_filepath(path, "CHGCAR") - self._potcar_path = self._get_filepath(path, "POTCAR") - self._aeccar0_path = self._get_filepath(path, "AECCAR0") - self._aeccar2_path = self._get_filepath(path, "AECCAR2") - - if run_chargemol and not ( - self._chgcar_path and self._potcar_path and self._aeccar0_path and self._aeccar2_path - ): - raise FileNotFoundError("CHGCAR, AECCAR0, AECCAR2, and POTCAR are all needed for Chargemol.") - if self._chgcar_path: - self.chgcar = Chgcar.from_file(self._chgcar_path) - self.structure = self.chgcar.structure - self.natoms = self.chgcar.poscar.natoms - else: - self.chgcar = self.structure = self.natoms = None - warnings.warn("No CHGCAR found. Some properties may be unavailable.", UserWarning) - if self._potcar_path: - self.potcar = Potcar.from_file(self._potcar_path) - else: - warnings.warn("No POTCAR found. Some properties may be unavailable.", UserWarning) - self.aeccar0 = Chgcar.from_file(self._aeccar0_path) if self._aeccar0_path else None - self.aeccar2 = Chgcar.from_file(self._aeccar2_path) if self._aeccar2_path else None - - if run_chargemol: - self._execute_chargemol(mpi=mpi, ncores=ncores) - else: - self._from_data_dir(chargemol_output_path=path) - - @staticmethod - def _get_filepath(path, filename, suffix=""): - """Returns the full path to the filename in the path. Works even if the file has - a .gz extension. - - Args: - path (str): Path to the file. - filename (str): Filename. - suffix (str): Optional suffix at the end of the filename. - - Returns: - str: Absolute path to the file. - """ - name_pattern = f"{filename}{suffix}*" if filename != "POTCAR" else f"{filename}*" - paths = glob(os.path.join(path, name_pattern)) - fpath = None - if len(paths) >= 1: - # using reverse=True because, if multiple files are present, - # they likely have suffixes 'static', 'relax', 'relax2', etc. - # and this would give 'static' over 'relax2' over 'relax' - # however, better to use 'suffix' kwarg to avoid this! - paths.sort(reverse=True) - # warning_msg = f"Multiple files detected, using {os.path.basename(paths[0])}" if len(paths) > 1 else None - # warnings.warn(warning_msg) - fpath = paths[0] - return fpath - - def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_kwargs): - """ - Internal function to run Chargemol. - - - Args: - atomic_densities_path (str): Path to the atomic densities directory - required by Chargemol. If None, Pymatgen assumes that this is - defined in a "DDEC6_ATOMIC_DENSITIES_DIR" environment variable. - Default: None. - - mpi(bool): Whether run the Chargemol in a parallel way. Default is False. - ncores (str): The number of cores you want to use. - Default is os.getenv('SLURM_CPUS_ON_NODE') or os.getenv('SLURM_NTASKS') - or multiprocessing.cpu_count(). - jobcontrol_kwargs: Keyword arguments for _write_jobscript_for_chargemol. - """ - if mpi: - if ncores: - CHARGEMOLEXE = ["mpirun", "-n", str(ncores), ChargemolAnalysis.CHARGEMOLEXE] - else: - ncores = os.getenv("SLURM_CPUS_ON_NODE") or os.getenv("SLURM_NTASKS") - if not ncores: - ncores = multiprocessing.cpu_count() - CHARGEMOLEXE = ["mpirun", "-n", str(ncores), ChargemolAnalysis.CHARGEMOLEXE] - else: - CHARGEMOLEXE = ChargemolAnalysis.CHARGEMOLEXE - - if self.save: - save_path = Path(Path.cwd(), "charge") - save_path.mkdir(parents=True, exist_ok=True) - source = [ - Path(self._chgcar_path), - Path(self._potcar_path), - Path(self._aeccar0_path), - Path(self._aeccar2_path), - ] - - links = [ - Path(save_path, "CHGCAR"), - Path(save_path, "POTCAR"), - Path(save_path, "AECCAR0"), - Path(save_path, "AECCAR2"), - ] - for link, src in zip(links, source): - link.symlink_to(src) - # write job_script file: - write_path = str(save_path) + "/job_control.txt" - self._write_jobscript_for_chargemol(write_path=write_path, **jobcontrol_kwargs) - - # Run Chargemol - with subprocess.Popen( - CHARGEMOLEXE, - stdout=subprocess.PIPE, - stdin=subprocess.PIPE, - close_fds=True, - cwd=save_path, - ) as rs: - rs.communicate() - self._from_data_dir(chargemol_output_path=str(save_path)) - else: - with ScratchDir("."): - cwd = Path.cwd() - source = [ - Path(self._chgcar_path), - Path(self._potcar_path), - Path(self._aeccar0_path), - Path(self._aeccar2_path), - ] - links = [Path(cwd, "CHGCAR"), Path(cwd, "POTCAR"), Path(cwd, "AECCAR0"), Path(cwd, "AECCAR2")] - for link, src in zip(links, source): - link.symlink_to(src) - # write job_script file: - self._write_jobscript_for_chargemol(**jobcontrol_kwargs) - - # Run Chargemol - with subprocess.Popen( - CHARGEMOLEXE, - stdout=subprocess.PIPE, - stdin=subprocess.PIPE, - close_fds=True, - ) as rs: - rs.communicate() - if rs.returncode != 0: - raise RuntimeError( - f"Chargemol exited with return code {int(rs.returncode)}. " - "Please check your Chargemol installation." - ) - - self._from_data_dir() - - def _from_data_dir(self, chargemol_output_path: str | None = None): - """ - Internal command to parse Chargemol files from a directory. - - Args: - chargemol_output_path (str): Path to the folder containing - the Chargemol output files. - Default: None (current working directory). - """ - if chargemol_output_path is None: - chargemol_output_path = "." - - charge_path = f"{chargemol_output_path}/DDEC6_even_tempered_net_atomic_charges.xyz" - self.ddec_charges = self._get_data_from_xyz(charge_path) - self.dipoles = self._get_dipole_info(charge_path) - - bond_order_path = f"{chargemol_output_path}/DDEC6_even_tempered_bond_orders.xyz" - if os.path.exists(bond_order_path): - self.bond_order_sums = self._get_data_from_xyz(bond_order_path) - self.bond_order_dict = self._get_bond_order_info(bond_order_path) - else: - self.bond_order_sums = self.bond_order_dict = None - - spin_moment_path = f"{chargemol_output_path}/DDEC6_even_tempered_atomic_spin_moments.xyz" - if os.path.exists(spin_moment_path): - self.ddec_spin_moments = self._get_data_from_xyz(spin_moment_path) - else: - self.ddec_spin_moments = None - - rsquared_path = f"{chargemol_output_path}/DDEC_atomic_Rsquared_moments.xyz" - if os.path.exists(rsquared_path): - self.ddec_rsquared_moments = self._get_data_from_xyz(rsquared_path) - else: - self.ddec_rsquared_moments = None - - rcubed_path = f"{chargemol_output_path}/DDEC_atomic_Rcubed_moments.xyz" - if os.path.exists(rcubed_path): - self.ddec_rcubed_moments = self._get_data_from_xyz(rcubed_path) - else: - self.ddec_rcubed_moments = None - - rfourth_path = f"{chargemol_output_path}/DDEC_atomic_Rfourth_moments.xyz" - if os.path.exists(rfourth_path): - self.ddec_rfourth_moments = self._get_data_from_xyz(rfourth_path) - else: - self.ddec_rfourth_moments = None - - ddec_analysis_path = f"{chargemol_output_path}/VASP_DDEC_analysis.output" - if os.path.exists(ddec_analysis_path): - self.cm5_charges = self._get_cm5_data_from_output(ddec_analysis_path) - else: - self.cm5_charges = None - - def get_charge_transfer(self, atom_index, charge_type="ddec"): - """Returns the charge transferred for a particular atom. A positive value means - that the site has gained electron density (i.e. exhibits anionic character) - whereas a negative value means the site has lost electron density (i.e. exhibits - cationic character). This is the same thing as the negative of the partial atomic - charge. - - Args: - atom_index (int): Index of atom to get charge transfer for. - charge_type (str): Type of charge to use ("ddec" or "cm5"). - - Returns: - float: charge transferred at atom_index - """ - if charge_type.lower() not in ["ddec", "cm5"]: - raise ValueError(f"Invalid {charge_type=}") - if charge_type.lower() == "ddec": - charge_transfer = -self.ddec_charges[atom_index] - elif charge_type.lower() == "cm5": - charge_transfer = -self.cm5_charges[atom_index] - return charge_transfer - - def get_charge(self, atom_index, nelect: int | None = None, charge_type="ddec"): - """Convenience method to get the charge on a particular atom using the same - sign convention as the BaderAnalysis. Note that this is *not* the partial - atomic charge. This value is nelect (e.g. ZVAL from the POTCAR) + the - charge transferred. If you want the partial atomic charge, use - get_partial_charge(). - - Args: - atom_index (int): Index of atom to get charge for. - nelect (int): number of electrons associated with an isolated atom at this index. - For most DFT codes this corresponds to the number of valence electrons - associated with the pseudopotential. If None, this value will be automatically - obtained from the POTCAR (if present). - Default: None. - charge_type (str): Type of charge to use ("ddec" or "cm5"). - - Returns: - float: charge on atom_index - """ - if nelect: - charge = nelect + self.get_charge_transfer(atom_index, charge_type=charge_type) - elif self.potcar and self.natoms: - charge = None - potcar_indices = [] - for i, v in enumerate(self.natoms): - potcar_indices += [i] * v - nelect = self.potcar[potcar_indices[atom_index]].nelectrons - charge = nelect + self.get_charge_transfer(atom_index, charge_type=charge_type) - else: - charge = None - return charge - - def get_partial_charge(self, atom_index, charge_type="ddec"): - """Convenience method to get the partial atomic charge on a particular atom. - This is the value printed in the Chargemol analysis. - - Args: - atom_index (int): Index of atom to get charge for. - charge_type (str): Type of charge to use ("ddec" or "cm5"). - """ - if charge_type.lower() not in ["ddec", "cm5"]: - raise ValueError(f"Invalid charge_type: {charge_type}") - if charge_type.lower() == "ddec": - partial_charge = self.ddec_charges[atom_index] - elif charge_type.lower() == "cm5": - partial_charge = self.cm5_charges[atom_index] - return partial_charge - - def get_bond_order(self, index_from, index_to): - """Convenience method to get the bond order between two atoms. - - Args: - index_from (int): Index of atom to get bond order from. - index_to (int): Index of atom to get bond order to. - - Returns: - float: bond order between atoms - """ - bonded_set = self.bond_order_dict[index_from]["bonded_to"] - bond_orders = [v["bond_order"] for v in bonded_set if v["index"] == index_to] - return 0.0 if bond_orders == [] else np.sum(bond_orders) - - def _write_jobscript_for_chargemol( - self, - net_charge=0.0, - periodicity=(True, True, True), - method="ddec6", - compute_bond_orders=True, - write_path: str = "job_control.txt", - ): - """Writes job_script.txt for Chargemol execution. - - Args: - net_charge (float): Net charge of the system. - Defaults to 0.0. - periodicity (tuple[bool]): Periodicity of the system. - Default: (True, True, True). - method (str): Method to use for the analysis. Options include "ddec6" - and "ddec3". Default: "ddec6" - compute_bond_orders (bool): Whether to compute bond orders. Default: True. - write_path (str): The path of output files of chargemol if you want to save them. - """ - self.net_charge = net_charge - self.periodicity = periodicity - self.method = method - - lines = "" - - # Net Charge - if net_charge: - lines += f"\n{net_charge}\n\n" - - # Periodicity - if periodicity: - per_a = ".true." if periodicity[0] else ".false." - per_b = ".true." if periodicity[1] else ".false." - per_c = ".true." if periodicity[2] else ".false." - lines += ( - f"\n{per_a}\n{per_b}\n{per_c}\n" - "\n" - ) - - # atomic_densities dir - atomic_densities_path = self._atomic_densities_path or os.getenv("DDEC6_ATOMIC_DENSITIES_DIR") - if atomic_densities_path is None: - raise OSError( - "The DDEC6_ATOMIC_DENSITIES_DIR environment variable must be set or the atomic_densities_path must" - " be specified" - ) - if not os.path.exists(atomic_densities_path): - raise FileNotFoundError(f"{atomic_densities_path=} does not exist") - - # This is to fix a Chargemol filepath nuance - if os.name == "nt": # Windows - if atomic_densities_path[-1] != "\\": - atomic_densities_path += "\\" - elif atomic_densities_path[-1] != "/": - atomic_densities_path += "/" - - lines += ( - f"\n\n{atomic_densities_path}\n\n" - ) - - # Charge type - lines += f"\n\n{method.upper()}\n\n" - - if compute_bond_orders: - bo = ".true." if compute_bond_orders else ".false." - lines += f"\n\n{bo}\n\n" - - with open(write_path, "w") as fh: - fh.write(lines) - - @staticmethod - def _get_dipole_info(filepath): - """Internal command to process dipoles. - - Args: - filepath (str): The path to the DDEC6_even_tempered_net_atomic_charges.xyz file - """ - idx = 0 - start = False - dipoles = [] - with open(filepath) as r: - for line in r: - if "The following XYZ" in line: - start = True - idx += 1 - continue - if start and line.strip() == "": - break - if idx >= 2: - dipoles.append([float(d) for d in line.strip().split()[7:10]]) - if start: - idx += 1 - - return dipoles - - @staticmethod - def _get_bond_order_info(filename): - """Internal command to process pairwise bond order information. - - Args: - filename (str): The path to the DDEC6_even_tempered_bond_orders.xyz file - """ - # Get where relevant info for each atom starts - bond_order_info = {} - - with open(filename) as r: - for line in r: - split = line.strip().split() - if "Printing BOs" in line: - start_idx = int(split[5]) - 1 - start_el = Element(split[7]) - bond_order_info[start_idx] = {"element": start_el, "bonded_to": []} - elif "Bonded to the" in line: - direction = tuple(int(i.split(")")[0].split(",")[0]) for i in split[4:7]) - end_idx = int(split[12]) - 1 - end_el = Element(split[14]) - bo = float(split[20]) - spin_bo = float(split[-1]) - bonded_to = { - "index": end_idx, - "element": end_el, - "bond_order": bo, - "direction": direction, - "spin_polarization": spin_bo, - } - bond_order_info[start_idx]["bonded_to"].append(bonded_to) - elif "The sum of bond orders for this atom" in line: - bond_order_info[start_idx]["bond_order_sum"] = float(split[-1]) - - return bond_order_info - - def get_property_decorated_structure(self): - """Takes CHGCAR's structure object and updates it with properties - from the Chargemol analysis. - - Returns: - Pymatgen structure with site properties added - """ - struct = self.structure.copy() - struct.add_site_property("partial_charge_ddec6", self.ddec_charges) - if self.dipoles: - struct.add_site_property("dipole_ddec6", self.dipoles) - if self.bond_order_sums: - struct.add_site_property("bond_order_sum_ddec6", self.bond_order_sums) - if self.ddec_spin_moments: - struct.add_site_property("spin_moment_ddec6", self.ddec_spin_moments) - if self.cm5_charges: - struct.add_site_property("partial_charge_cm5", self.cm5_charges) - return struct - - @property - def summary(self): - """Returns a dictionary summary of the Chargemol analysis - { - "ddec": { - "partial_charges": list[float], - "spin_moments": list[float], - "dipoles": list[float], - "rsquared_moments": list[float], - "rcubed_moments": list[float], - "rfourth_moments": list[float], - "bond_order_dict": dict - }, - "cm5": { - "partial_charges": list[float], - } - }. - """ - summary = {} - ddec_summary = {"partial_charges": self.ddec_charges} - if self.bond_order_sums: - ddec_summary["bond_order_sums"] = self.bond_order_sums - if self.ddec_spin_moments: - ddec_summary["spin_moments"] = self.ddec_spin_moments - if self.dipoles: - ddec_summary["dipoles"] = self.dipoles - if self.ddec_rsquared_moments: - ddec_summary["rsquared_moments"] = self.ddec_rsquared_moments - if self.ddec_rcubed_moments: - ddec_summary["rcubed_moments"] = self.ddec_rcubed_moments - if self.ddec_rfourth_moments: - ddec_summary["rfourth_moments"] = self.ddec_rfourth_moments - if self.bond_order_dict: - ddec_summary["bond_order_dict"] = self.bond_order_dict - - cm5_summary = {"partial_charges": self.cm5_charges} if self.cm5_charges else None - - summary["ddec"] = ddec_summary - summary["cm5"] = cm5_summary - - return summary - - @staticmethod - def _get_data_from_xyz(xyz_path): - """Internal command to process Chargemol XYZ files. - - Args: - xyz_path (str): Path to XYZ file - - Returns: - list[float]: site-specific properties - """ - props = [] - if os.path.exists(xyz_path): - with open(xyz_path) as r: - for i, line in enumerate(r): - if i <= 1: - continue - if line.strip() == "": - break - props.append(float(line.split()[-1])) - else: - raise FileNotFoundError(f"{xyz_path} not found") - - return props - - @staticmethod - def _get_cm5_data_from_output(ddec_analysis_path): - """Internal command to process Chargemol CM5 data. - - Args: - ddec_analysis_path (str): Path VASP_DDEC_analysis.output file - - Returns: - list[float]: CM5 charges - """ - props = [] - if os.path.exists(ddec_analysis_path): - start = False - with open(ddec_analysis_path) as r: - for line in r: - if "computed CM5" in line: - start = True - continue - if "Hirshfeld and CM5" in line: - break - if start: - vals = line.split() - props.extend([float(c) for c in [val.strip() for val in vals]]) - else: - raise FileNotFoundError(f"{ddec_analysis_path} not found") - return props From 27905f1a68e3e521979f5894491339383b4a98d3 Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Tue, 7 Nov 2023 01:21:04 +0000 Subject: [PATCH 26/37] Make sure chargemol exe is not None --- pymatgen/command_line/chargemol_caller.py | 75 +++++++++++++---------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index 5311a88bc25..445fbaf32b6 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -84,7 +84,7 @@ def __init__( atomic_densities_path=None, run_chargemol: bool = True, mpi: bool = False, - ncores: int | None = None, + ncores: str | None = None, save: bool = False, ): """ @@ -102,11 +102,11 @@ def __init__( the existing Chargemol output files will be read from path. Default: True. mpi (bool): Whether to run the Chargemol in a parallel way. - ncores: Use how many cores to run the Chargemol! - Default is "os.environ.get('SLURM_JOB_CPUS_PER_NODE'), - or os.environ.get('SLURM_CPUS_ON_NODE')", or "multiprocessing.cpu_count()". - Take your own risk! This default value might not suit you! - You'd better set your own number!!! + ncores (str): Use how many cores to run the Chargemol! + Default is "os.environ.get('SLURM_JOB_CPUS_PER_NODE'), + or os.environ.get('SLURM_CPUS_ON_NODE')", or "multiprocessing.cpu_count()". + Take your own risk! This default value might not suit you! + You'd better set your own number!!! save: save (bool): Whether to save the Chargemol output files. Default is False. the existing Chargemol output files will be read from path. Default: True. """ @@ -200,7 +200,7 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ else: ncores = os.getenv("SLURM_CPUS_ON_NODE") or os.getenv("SLURM_NTASKS") if not ncores: - ncores = multiprocessing.cpu_count() + ncores = str(multiprocessing.cpu_count()) CHARGEMOLEXE = ["mpirun", "-n", str(ncores), ChargemolAnalysis.CHARGEMOLEXE] else: CHARGEMOLEXE = ChargemolAnalysis.CHARGEMOLEXE @@ -228,16 +228,23 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ self._write_jobscript_for_chargemol(write_path=write_path, **jobcontrol_kwargs) # Run Chargemol - with subprocess.Popen( - CHARGEMOLEXE, - stdout=subprocess.PIPE, - stdin=subprocess.PIPE, - close_fds=True, - cwd=save_path, - ) as rs: - rs.communicate() - self._from_data_dir(chargemol_output_path=str(save_path)) - + if CHARGEMOLEXE: + if isinstance(CHARGEMOLEXE, list): + CHARGEMOLEXE = [x for x in CHARGEMOLEXE if x is not None] + if not CHARGEMOLEXE: + raise RuntimeError("Make sure compiled chargemol executable being available in the path") + + with subprocess.Popen( + CHARGEMOLEXE, + stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + close_fds=True, + cwd=save_path, + ) as rs: + rs.communicate() + self._from_data_dir(chargemol_output_path=str(save_path)) + else: + raise RuntimeError("Make sure compiled chargemol executable being available in the path") else: with ScratchDir("."): cwd = Path.cwd() @@ -254,20 +261,26 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ self._write_jobscript_for_chargemol(**jobcontrol_kwargs) # Run Chargemol - with subprocess.Popen( - CHARGEMOLEXE, - stdout=subprocess.PIPE, - stdin=subprocess.PIPE, - close_fds=True, - ) as rs: - rs.communicate() - if rs.returncode != 0: - raise RuntimeError( - f"Chargemol exited with return code {int(rs.returncode)}. " - "Please check your Chargemol installation." - ) - - self._from_data_dir() + if CHARGEMOLEXE: + if isinstance(CHARGEMOLEXE, list): + CHARGEMOLEXE = [x for x in CHARGEMOLEXE if x is not None] + if not CHARGEMOLEXE: + raise RuntimeError("Make sure compiled chargemol executable being available in the path") + with subprocess.Popen( + CHARGEMOLEXE, + stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + close_fds=True, + ) as rs: + rs.communicate() + if rs.returncode != 0: + raise RuntimeError( + f"Chargemol exited with return code {int(rs.returncode)}. " + "Please check your Chargemol installation." + ) + self._from_data_dir() + else: + raise RuntimeError("Make sure compiled chargemol executable being available in the path") def _from_data_dir(self, chargemol_output_path: str | None = None): """ From 15114163c53eb91511a5637663a471a5354c93bd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Nov 2023 01:22:33 +0000 Subject: [PATCH 27/37] pre-commit auto-fixes --- pymatgen/command_line/chargemol_caller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index 445fbaf32b6..a901e306ee7 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -233,7 +233,7 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ CHARGEMOLEXE = [x for x in CHARGEMOLEXE if x is not None] if not CHARGEMOLEXE: raise RuntimeError("Make sure compiled chargemol executable being available in the path") - + with subprocess.Popen( CHARGEMOLEXE, stdout=subprocess.PIPE, From c633a532334e5c2028b66c391719adf3efac8101 Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Tue, 7 Nov 2023 01:23:24 +0000 Subject: [PATCH 28/37] Remove the black whitespace --- pymatgen/command_line/chargemol_caller.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index a901e306ee7..c39fe248c92 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -233,7 +233,6 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ CHARGEMOLEXE = [x for x in CHARGEMOLEXE if x is not None] if not CHARGEMOLEXE: raise RuntimeError("Make sure compiled chargemol executable being available in the path") - with subprocess.Popen( CHARGEMOLEXE, stdout=subprocess.PIPE, From 9fbcf087ca16578c63e8fe88422369b469871b14 Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Tue, 7 Nov 2023 03:35:28 +0000 Subject: [PATCH 29/37] Use cast from typing to assert to the type checker that CHARGEMOLEXE is indeed a list of strings after the None filtering. --- pymatgen/command_line/chargemol_caller.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index c39fe248c92..989cd983f7a 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -49,6 +49,7 @@ from glob import glob from pathlib import Path from shutil import which +from typing import List, Optional, Union, cast import numpy as np from monty.tempfile import ScratchDir @@ -74,7 +75,7 @@ class ChargemolAnalysis: bond orders, and related properties. """ - CHARGEMOLEXE = ( + CHARGEMOLEXE: Optional[Union[str, List[Optional[str]]]] = ( which("Chargemol_09_26_2017_linux_parallel") or which("Chargemol_09_26_2017_linux_serial") or which("chargemol") ) @@ -231,10 +232,12 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ if CHARGEMOLEXE: if isinstance(CHARGEMOLEXE, list): CHARGEMOLEXE = [x for x in CHARGEMOLEXE if x is not None] + CHARGEMOLEXE = cast(List[str], CHARGEMOLEXE) if not CHARGEMOLEXE: raise RuntimeError("Make sure compiled chargemol executable being available in the path") + popen_args = cast(Union[str, List[str]], CHARGEMOLEXE) with subprocess.Popen( - CHARGEMOLEXE, + popen_args, stdout=subprocess.PIPE, stdin=subprocess.PIPE, close_fds=True, @@ -263,8 +266,10 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ if CHARGEMOLEXE: if isinstance(CHARGEMOLEXE, list): CHARGEMOLEXE = [x for x in CHARGEMOLEXE if x is not None] + CHARGEMOLEXE = cast(List[str], CHARGEMOLEXE) if not CHARGEMOLEXE: raise RuntimeError("Make sure compiled chargemol executable being available in the path") + popen_args = cast(Union[str, List[str]], CHARGEMOLEXE) with subprocess.Popen( CHARGEMOLEXE, stdout=subprocess.PIPE, From c2f40e5e1c61f6f89f666d6ceb341445799da9a1 Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Tue, 7 Nov 2023 03:37:45 +0000 Subject: [PATCH 30/37] change to popen_args --- pymatgen/command_line/chargemol_caller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index 989cd983f7a..c95a3b76810 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -271,7 +271,7 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ raise RuntimeError("Make sure compiled chargemol executable being available in the path") popen_args = cast(Union[str, List[str]], CHARGEMOLEXE) with subprocess.Popen( - CHARGEMOLEXE, + popen_args, stdout=subprocess.PIPE, stdin=subprocess.PIPE, close_fds=True, From f6289d572daa8f947748e52e3a3a132f06c6a371 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Nov 2023 03:39:23 +0000 Subject: [PATCH 31/37] pre-commit auto-fixes --- pymatgen/command_line/chargemol_caller.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index c95a3b76810..3b6f2bf126c 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -49,7 +49,7 @@ from glob import glob from pathlib import Path from shutil import which -from typing import List, Optional, Union, cast +from typing import Optional, Union, cast import numpy as np from monty.tempfile import ScratchDir @@ -75,7 +75,7 @@ class ChargemolAnalysis: bond orders, and related properties. """ - CHARGEMOLEXE: Optional[Union[str, List[Optional[str]]]] = ( + CHARGEMOLEXE: Optional[Union[str, list[Optional[str]]]] = ( which("Chargemol_09_26_2017_linux_parallel") or which("Chargemol_09_26_2017_linux_serial") or which("chargemol") ) @@ -232,10 +232,10 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ if CHARGEMOLEXE: if isinstance(CHARGEMOLEXE, list): CHARGEMOLEXE = [x for x in CHARGEMOLEXE if x is not None] - CHARGEMOLEXE = cast(List[str], CHARGEMOLEXE) + CHARGEMOLEXE = cast(list[str], CHARGEMOLEXE) if not CHARGEMOLEXE: raise RuntimeError("Make sure compiled chargemol executable being available in the path") - popen_args = cast(Union[str, List[str]], CHARGEMOLEXE) + popen_args = cast(Union[str, list[str]], CHARGEMOLEXE) with subprocess.Popen( popen_args, stdout=subprocess.PIPE, @@ -266,10 +266,10 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ if CHARGEMOLEXE: if isinstance(CHARGEMOLEXE, list): CHARGEMOLEXE = [x for x in CHARGEMOLEXE if x is not None] - CHARGEMOLEXE = cast(List[str], CHARGEMOLEXE) + CHARGEMOLEXE = cast(list[str], CHARGEMOLEXE) if not CHARGEMOLEXE: raise RuntimeError("Make sure compiled chargemol executable being available in the path") - popen_args = cast(Union[str, List[str]], CHARGEMOLEXE) + popen_args = cast(Union[str, list[str]], CHARGEMOLEXE) with subprocess.Popen( popen_args, stdout=subprocess.PIPE, From 486e0d1b1f7b46e0a8efee9646537d14f98d93aa Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Tue, 7 Nov 2023 03:42:54 +0000 Subject: [PATCH 32/37] 1.Replace typing.List with the built-in list. 2.Replace typing.Optional with the | None. 3.Replace typing.Union with the | operator for union types. --- pymatgen/command_line/chargemol_caller.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index 3b6f2bf126c..b24f7cdf805 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -49,7 +49,7 @@ from glob import glob from pathlib import Path from shutil import which -from typing import Optional, Union, cast +from typing import cast import numpy as np from monty.tempfile import ScratchDir @@ -64,7 +64,7 @@ __email__ = "shyuep@gmail.com" __date__ = "01/18/21" -CHARGEMOL_EXE = ( +CHARGEMOL_EXE: "str | list[str | None] | None" = ( which("Chargemol_09_26_2017_linux_parallel") or which("Chargemol_09_26_2017_linux_serial") or which("chargemol") ) @@ -75,7 +75,7 @@ class ChargemolAnalysis: bond orders, and related properties. """ - CHARGEMOLEXE: Optional[Union[str, list[Optional[str]]]] = ( + CHARGEMOLEXE: "str | list[str | None] | None" = ( which("Chargemol_09_26_2017_linux_parallel") or which("Chargemol_09_26_2017_linux_serial") or which("chargemol") ) @@ -232,10 +232,10 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ if CHARGEMOLEXE: if isinstance(CHARGEMOLEXE, list): CHARGEMOLEXE = [x for x in CHARGEMOLEXE if x is not None] - CHARGEMOLEXE = cast(list[str], CHARGEMOLEXE) + CHARGEMOLEXE = cast("list[str]", CHARGEMOLEXE) if not CHARGEMOLEXE: raise RuntimeError("Make sure compiled chargemol executable being available in the path") - popen_args = cast(Union[str, list[str]], CHARGEMOLEXE) + popen_args = cast("str | list[str]", CHARGEMOLEXE) with subprocess.Popen( popen_args, stdout=subprocess.PIPE, @@ -266,10 +266,10 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ if CHARGEMOLEXE: if isinstance(CHARGEMOLEXE, list): CHARGEMOLEXE = [x for x in CHARGEMOLEXE if x is not None] - CHARGEMOLEXE = cast(list[str], CHARGEMOLEXE) + CHARGEMOLEXE = cast("list[str]", CHARGEMOLEXE) if not CHARGEMOLEXE: raise RuntimeError("Make sure compiled chargemol executable being available in the path") - popen_args = cast(Union[str, list[str]], CHARGEMOLEXE) + popen_args = cast("str | list[str]", CHARGEMOLEXE) with subprocess.Popen( popen_args, stdout=subprocess.PIPE, From 404d0484c8fb151476f54624f9b605113fd8747a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Nov 2023 03:44:58 +0000 Subject: [PATCH 33/37] pre-commit auto-fixes --- pymatgen/command_line/chargemol_caller.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index b24f7cdf805..8f09e9f6b95 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -64,7 +64,7 @@ __email__ = "shyuep@gmail.com" __date__ = "01/18/21" -CHARGEMOL_EXE: "str | list[str | None] | None" = ( +CHARGEMOL_EXE: str | list[str | None] | None = ( which("Chargemol_09_26_2017_linux_parallel") or which("Chargemol_09_26_2017_linux_serial") or which("chargemol") ) @@ -75,7 +75,7 @@ class ChargemolAnalysis: bond orders, and related properties. """ - CHARGEMOLEXE: "str | list[str | None] | None" = ( + CHARGEMOLEXE: str | list[str | None] | None = ( which("Chargemol_09_26_2017_linux_parallel") or which("Chargemol_09_26_2017_linux_serial") or which("chargemol") ) From cebe56b6b6a0f29f7a561d09f6bd2c56379f95d2 Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Tue, 7 Nov 2023 03:47:50 +0000 Subject: [PATCH 34/37] Remove quotes from type annotation --- pymatgen/command_line/chargemol_caller.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index 8f09e9f6b95..d9bab2864f8 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -232,10 +232,10 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ if CHARGEMOLEXE: if isinstance(CHARGEMOLEXE, list): CHARGEMOLEXE = [x for x in CHARGEMOLEXE if x is not None] - CHARGEMOLEXE = cast("list[str]", CHARGEMOLEXE) + CHARGEMOLEXE = cast(list[str], CHARGEMOLEXE) if not CHARGEMOLEXE: raise RuntimeError("Make sure compiled chargemol executable being available in the path") - popen_args = cast("str | list[str]", CHARGEMOLEXE) + popen_args = cast(str | list[str], CHARGEMOLEXE) with subprocess.Popen( popen_args, stdout=subprocess.PIPE, @@ -266,10 +266,10 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ if CHARGEMOLEXE: if isinstance(CHARGEMOLEXE, list): CHARGEMOLEXE = [x for x in CHARGEMOLEXE if x is not None] - CHARGEMOLEXE = cast("list[str]", CHARGEMOLEXE) + CHARGEMOLEXE = cast(list[str], CHARGEMOLEXE) if not CHARGEMOLEXE: raise RuntimeError("Make sure compiled chargemol executable being available in the path") - popen_args = cast("str | list[str]", CHARGEMOLEXE) + popen_args = cast(str | list[str], CHARGEMOLEXE) with subprocess.Popen( popen_args, stdout=subprocess.PIPE, From 7bb8cbc22ca045ec70d93209530a32c1711b94b0 Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Tue, 7 Nov 2023 03:55:16 +0000 Subject: [PATCH 35/37] remove cast --- pymatgen/command_line/chargemol_caller.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index d9bab2864f8..67a891d78fa 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -49,7 +49,6 @@ from glob import glob from pathlib import Path from shutil import which -from typing import cast import numpy as np from monty.tempfile import ScratchDir @@ -64,7 +63,7 @@ __email__ = "shyuep@gmail.com" __date__ = "01/18/21" -CHARGEMOL_EXE: str | list[str | None] | None = ( +CHARGEMOL_EXE: str | list[str] = ( which("Chargemol_09_26_2017_linux_parallel") or which("Chargemol_09_26_2017_linux_serial") or which("chargemol") ) @@ -75,7 +74,7 @@ class ChargemolAnalysis: bond orders, and related properties. """ - CHARGEMOLEXE: str | list[str | None] | None = ( + CHARGEMOLEXE: str | list[str] = ( which("Chargemol_09_26_2017_linux_parallel") or which("Chargemol_09_26_2017_linux_serial") or which("chargemol") ) @@ -232,10 +231,10 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ if CHARGEMOLEXE: if isinstance(CHARGEMOLEXE, list): CHARGEMOLEXE = [x for x in CHARGEMOLEXE if x is not None] - CHARGEMOLEXE = cast(list[str], CHARGEMOLEXE) + # CHARGEMOLEXE = cast(list[str], CHARGEMOLEXE) if not CHARGEMOLEXE: raise RuntimeError("Make sure compiled chargemol executable being available in the path") - popen_args = cast(str | list[str], CHARGEMOLEXE) + popen_args = CHARGEMOLEXE with subprocess.Popen( popen_args, stdout=subprocess.PIPE, @@ -266,10 +265,10 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ if CHARGEMOLEXE: if isinstance(CHARGEMOLEXE, list): CHARGEMOLEXE = [x for x in CHARGEMOLEXE if x is not None] - CHARGEMOLEXE = cast(list[str], CHARGEMOLEXE) + # CHARGEMOLEXE = cast(list[str], CHARGEMOLEXE) if not CHARGEMOLEXE: raise RuntimeError("Make sure compiled chargemol executable being available in the path") - popen_args = cast(str | list[str], CHARGEMOLEXE) + popen_args = CHARGEMOLEXE with subprocess.Popen( popen_args, stdout=subprocess.PIPE, From 65451754d5c32011d0baee77cf9f3bf61820a5f4 Mon Sep 17 00:00:00 2001 From: Six_ligand <49940294+RedStar-Iron@users.noreply.github.com> Date: Tue, 7 Nov 2023 05:58:15 +0000 Subject: [PATCH 36/37] Ignore the type of "CHARGEMOLEXE" --- pymatgen/command_line/chargemol_caller.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index 67a891d78fa..df798f4c89c 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -63,7 +63,7 @@ __email__ = "shyuep@gmail.com" __date__ = "01/18/21" -CHARGEMOL_EXE: str | list[str] = ( +CHARGEMOL_EXE = ( which("Chargemol_09_26_2017_linux_parallel") or which("Chargemol_09_26_2017_linux_serial") or which("chargemol") ) @@ -74,7 +74,7 @@ class ChargemolAnalysis: bond orders, and related properties. """ - CHARGEMOLEXE: str | list[str] = ( + CHARGEMOLEXE = ( which("Chargemol_09_26_2017_linux_parallel") or which("Chargemol_09_26_2017_linux_serial") or which("chargemol") ) @@ -196,14 +196,14 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ """ if mpi: if ncores: - CHARGEMOLEXE = ["mpirun", "-n", str(ncores), ChargemolAnalysis.CHARGEMOLEXE] + CHARGEMOLEXE = ["mpirun", "-n", str(ncores), ChargemolAnalysis.CHARGEMOLEXE] # type: ignore else: ncores = os.getenv("SLURM_CPUS_ON_NODE") or os.getenv("SLURM_NTASKS") if not ncores: ncores = str(multiprocessing.cpu_count()) - CHARGEMOLEXE = ["mpirun", "-n", str(ncores), ChargemolAnalysis.CHARGEMOLEXE] + CHARGEMOLEXE = ["mpirun", "-n", str(ncores), ChargemolAnalysis.CHARGEMOLEXE] # type: ignore else: - CHARGEMOLEXE = ChargemolAnalysis.CHARGEMOLEXE + CHARGEMOLEXE = ChargemolAnalysis.CHARGEMOLEXE # type: ignore if self.save: save_path = Path(Path.cwd(), "charge") @@ -231,12 +231,12 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ if CHARGEMOLEXE: if isinstance(CHARGEMOLEXE, list): CHARGEMOLEXE = [x for x in CHARGEMOLEXE if x is not None] - # CHARGEMOLEXE = cast(list[str], CHARGEMOLEXE) + popen_args: list[str] = CHARGEMOLEXE # type: ignore if not CHARGEMOLEXE: raise RuntimeError("Make sure compiled chargemol executable being available in the path") - popen_args = CHARGEMOLEXE + popen_args = CHARGEMOLEXE # type: ignore with subprocess.Popen( - popen_args, + popen_args, # type: ignore stdout=subprocess.PIPE, stdin=subprocess.PIPE, close_fds=True, @@ -268,9 +268,9 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ # CHARGEMOLEXE = cast(list[str], CHARGEMOLEXE) if not CHARGEMOLEXE: raise RuntimeError("Make sure compiled chargemol executable being available in the path") - popen_args = CHARGEMOLEXE + popen_args = CHARGEMOLEXE # type: ignore with subprocess.Popen( - popen_args, + popen_args, # type: ignore stdout=subprocess.PIPE, stdin=subprocess.PIPE, close_fds=True, From e2ebd579f4082fc8d617644ba6ac703b6978b887 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Nov 2023 05:59:21 +0000 Subject: [PATCH 37/37] pre-commit auto-fixes --- pymatgen/command_line/chargemol_caller.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index df798f4c89c..6d7c585d374 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -196,14 +196,14 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ """ if mpi: if ncores: - CHARGEMOLEXE = ["mpirun", "-n", str(ncores), ChargemolAnalysis.CHARGEMOLEXE] # type: ignore + CHARGEMOLEXE = ["mpirun", "-n", str(ncores), ChargemolAnalysis.CHARGEMOLEXE] # type: ignore else: ncores = os.getenv("SLURM_CPUS_ON_NODE") or os.getenv("SLURM_NTASKS") if not ncores: ncores = str(multiprocessing.cpu_count()) - CHARGEMOLEXE = ["mpirun", "-n", str(ncores), ChargemolAnalysis.CHARGEMOLEXE] # type: ignore + CHARGEMOLEXE = ["mpirun", "-n", str(ncores), ChargemolAnalysis.CHARGEMOLEXE] # type: ignore else: - CHARGEMOLEXE = ChargemolAnalysis.CHARGEMOLEXE # type: ignore + CHARGEMOLEXE = ChargemolAnalysis.CHARGEMOLEXE # type: ignore if self.save: save_path = Path(Path.cwd(), "charge") @@ -231,12 +231,12 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ if CHARGEMOLEXE: if isinstance(CHARGEMOLEXE, list): CHARGEMOLEXE = [x for x in CHARGEMOLEXE if x is not None] - popen_args: list[str] = CHARGEMOLEXE # type: ignore + popen_args: list[str] = CHARGEMOLEXE # type: ignore if not CHARGEMOLEXE: raise RuntimeError("Make sure compiled chargemol executable being available in the path") popen_args = CHARGEMOLEXE # type: ignore with subprocess.Popen( - popen_args, # type: ignore + popen_args, # type: ignore stdout=subprocess.PIPE, stdin=subprocess.PIPE, close_fds=True, @@ -268,9 +268,9 @@ def _execute_chargemol(self, mpi=False, ncores: str | None = None, **jobcontrol_ # CHARGEMOLEXE = cast(list[str], CHARGEMOLEXE) if not CHARGEMOLEXE: raise RuntimeError("Make sure compiled chargemol executable being available in the path") - popen_args = CHARGEMOLEXE # type: ignore + popen_args = CHARGEMOLEXE # type: ignore with subprocess.Popen( - popen_args, # type: ignore + popen_args, # type: ignore stdout=subprocess.PIPE, stdin=subprocess.PIPE, close_fds=True,