Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Coop coordination #280

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

# 0.10.4 (Unrelease)
## New
* Template to create B3LYP computations (#269)
* Allow to compute both alphas/betas derivative couplings simultaneusly (#275)
* Add nanoCAT dependency (#280)

## Changed
* Do not remove the CP2K log files by default
Expand Down
5 changes: 4 additions & 1 deletion nanoqm/workflows/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,10 @@ def merge(d1: Dict[str, Any], d2: Dict[str, Any]) -> Dict[str, Any]:
#: Input for a Crystal Orbital Overlap Population calculation
dict_coop = {
# List of the two elements to calculate the COOP for
"coop_elements": list}
"coop_elements": list,
# Coordination number to be considered for each of the two elements
Optional("elements_coordination", default=None): Or(None, list)
}


dict_merged_single_points = merge(dict_general_options, dict_single_points)
Expand Down
32 changes: 27 additions & 5 deletions nanoqm/workflows/workflow_coop.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
"""Crystal Orbital Overlap Population calculation.
The COOP is calculated between two selected elements.
For each element, a specific coordination number can optionally be selected
by using the "elements_coordination" key in the yaml input.

Index
-----
Expand All @@ -10,12 +13,16 @@
__all__ = ['workflow_crystal_orbital_overlap_population']

import logging
from typing import List, Tuple
from typing import List, Tuple, Union

import numpy as np

from scm.plams import Molecule

from qmflows.parsers.xyzParser import readXYZ

from nanoCAT.recipes import coordination_number
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nanoCAT must be included in the library requirements at setup.py


from ..common import (DictConfig, MolXYZ, h2ev,
number_spherical_functions_per_atom, retrieve_hdf5_data)
from ..integrals.multipole_matrices import compute_matrix_multipole
Expand Down Expand Up @@ -44,11 +51,16 @@ def workflow_crystal_orbital_overlap_population(config: DictConfig):
# Converting the xyz-file to a mol-file
mol = readXYZ(config.path_traj_xyz)

if config.elements_coordination is not None:
geometry = Molecule(config.path_traj_xyz)
else:
geometry = None

# Computing the indices of the atomic orbitals of the two selected
# elements, and the overlap matrix that contains only elements related to
# the two elements
el_1_orbital_ind, el_2_orbital_ind, overlap_reduced = compute_overlap_and_atomic_orbitals(
mol, config)
mol, config, geometry)

# Compute the crystal orbital overlap population between the two selected
# elements
Expand Down Expand Up @@ -83,7 +95,8 @@ def get_eigenvalues_coefficients(config: DictConfig) -> Tuple[np.ndarray, np.nda


def compute_overlap_and_atomic_orbitals(
mol: MolXYZ, config: DictConfig) -> Tuple[List[int], List[int], List[int]]:
mol: MolXYZ, config: DictConfig,
geometry: Union[Molecule, None]) -> Tuple[List[int], List[int], List[int]]:
"""Compute the indices of the atomic orbitals of the two selected elements.

Computes the overlap matrix, containing only the elements related to those two elements.
Expand All @@ -102,8 +115,17 @@ def compute_overlap_and_atomic_orbitals(
element_1 = config["coop_elements"][0]
element_2 = config["coop_elements"][1]

element_1_index = [i for i, s in enumerate(mol) if element_1.lower() in s]
element_2_index = [i for i, s in enumerate(mol) if element_2.lower() in s]
if config["elements_coordination"] is None:
element_1_index = [i for i, s in enumerate(mol) if element_1.lower() in s]
element_2_index = [i for i, s in enumerate(mol) if element_2.lower() in s]

# For the two selected elements, only the indices corresponding to a given coordination number
else:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fyi, you can also make nano-CAT an optional dependency and then raise any potential ImportErrors whenever a code branch is reached that does require nano-CAT:

from typing import Optional
try:
    from nanoCAT import coordination_number
    NANOCAT_EX: Optional[ImportError] = None
except ImportError as ex:
    NANOCAT_EX = ex


def compute_overlap_and_atomic_orbitals(...):
    if config["elements_coordination"] is None:
        ...
    elif NANOCAT_EX is not None:
        raise NANOCAT_EX
    else:
        coord = coordination_number(geometry)
        ...

coord = coordination_number(geometry)
cn_1 = config["elements_coordination"][0]
cn_2 = config["elements_coordination"][1]
element_1_index = [i - 1 for i in coord[element_1][cn_1]]
element_2_index = [i - 1 for i in coord[element_2][cn_2]]

# Making a list of the indices of the atomic orbitals for each of the two
# elements
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ def build_extensions(self):
'h5py', 'mendeleev', 'more-itertools', 'noodles==0.3.3', 'numpy', 'pybind11>=2.2.4',
'scipy', 'schema', 'pyyaml>=5.1',
'plams@git+https://github.com/SCM-NV/PLAMS@master',
'qmflows@git+https://github.com/SCM-NV/qmflows@master'
'qmflows@git+https://github.com/SCM-NV/qmflows@master',
'nano-CAT@git+https://github.com/nlesc-nano/nano-CAT@master'
],
cmdclass={'build_ext': BuildExt},
ext_modules=[ext_pybind],
Expand Down
13 changes: 8 additions & 5 deletions test/test_distribute.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Test the distribution script."""
from qmflows.type_hints import PathLike
from pathlib import Path
from subprocess import (PIPE, Popen)
import fnmatch
import shutil
import os
import re
import shutil
from pathlib import Path
from subprocess import PIPE, Popen

from qmflows.type_hints import PathLike


def test_distribute(tmp_path: PathLike) -> None:
Expand All @@ -20,7 +22,8 @@ def call_distribute(tmp_path: PathLike, cmd: str) -> None:
try:
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=True)
_, err = p.communicate()
if err:
error = re.search("error", err.decode(), re.IGNORECASE)
if error is not None:
raise RuntimeError(err.decode())
check_scripts()
finally:
Expand Down
26 changes: 26 additions & 0 deletions test/test_files/input_test_coop_coordination.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
workflow:
coop_calculation
project_name: Cd33Se33

active_space: [50, 50]
path_hdf5: "test/test_files/Cd33Se33.hdf5"
path_traj_xyz: "test/test_files/Cd33Se33.xyz"
scratch_path: "/tmp/COOP"

coop_elements: ["Cd", "Se"]
elements_coordination: [4, 3]

cp2k_general_settings:
basis: "DZVP-MOLOPT-SR-GTH"
potential: "GTH-PBE"
cell_parameters: 20.0
periodic: none

cp2k_settings_main:
specific:
template: pbe_main

cp2k_settings_guess:
specific:
template:
pbe_guess
4 changes: 3 additions & 1 deletion test/test_run_workflow.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Test the CLI to run a workflow."""

import os
import re
import shutil
from pathlib import Path
from subprocess import PIPE, Popen
Expand Down Expand Up @@ -37,7 +38,8 @@ def test_run_workflow(tmp_path):
try:
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=True)
out, err = p.communicate()
if err:
error = re.search("error", err.decode(), re.IGNORECASE)
if error is not None:
print("output: ", out)
print("err: ", err)
raise RuntimeError(err.decode())
Expand Down
18 changes: 18 additions & 0 deletions test/test_workflow_coop.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,21 @@ def test_workflow_coop(tmp_path: PathLike) -> None:
print("scratch_path: ", tmp_path)
print("Unexpected error:", sys.exc_info()[0])
raise


def test_workflow_coop_coordination(tmp_path: PathLike) -> None:
"""Test the Crystal Orbital Overlap Population workflow."""
file_path = PATH_TEST / 'input_test_coop_coordination.yml'
config = process_input(file_path, 'coop_calculation')

# create scratch path
shutil.copy(config.path_hdf5, tmp_path)
config.path_hdf5 = join(tmp_path, "Cd33Se33.hdf5")
config.workdir = tmp_path
try:
workflow_crystal_orbital_overlap_population(config)
os.remove("COOP.txt")
except BaseException:
print("scratch_path: ", tmp_path)
print("Unexpected error:", sys.exc_info()[0])
raise