Skip to content

Commit

Permalink
Standardize Libcint implementation to include IOData conventions (#174)
Browse files Browse the repository at this point in the history
* Add function to get permutation with LibCint order

* Apply ordering permutation to libcint arrays

* Minor changes libcint conventions

* Add tests and files for iodata/libcint

* Change default convention for p spherical orbitals

* Add install libcint to workflow

* Exclude notebooks and test/*fchk from pre-commit

* Fix errors workflows and pre-commit

* Fix errors

- Install lisp interpreter
- Exclude only folders

* Skip install sbcl for windows

* Fix excluding folders pre-commit

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update syntax

* Debug workflows pytest

* Try again if statment in workflows

* Exclude libcint tests if build folder is not found

* Exclude libcint tests if operating in windows os

* Move imports from libcint into tests

* Add some changes included in PR #151

* Change to test with IOData to cover wrappers

* Delete version check IOData

* Add pyscf to tests to cover wrappers

* Remove pyscf and lower coverage requirement

---------

Co-authored-by: Michelle Richer <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Apr 23, 2024
1 parent 32a40f4 commit bb86ed6
Show file tree
Hide file tree
Showing 11 changed files with 44,096 additions and 29 deletions.
35 changes: 17 additions & 18 deletions .github/workflows/pytest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@ on:
pull_request:

jobs:
tests:
pytest:
name: "Python ${{ matrix.py }} on OS ${{ matrix.os }}"
runs-on: ${{ matrix.os }}
env:
COVERAGE_SINGLE: 78
COVERAGE_TOTAL: 78

strategy:
matrix:
os: ["ubuntu-latest", "windows-latest"]
py: ["3.7", "3.9", "3.10", "3.11"]
py: ["3.7", "3.8", "3.9", "3.10", "3.11"]

steps:
- uses: "actions/checkout@v3"
Expand All @@ -34,20 +37,16 @@ jobs:
- name: Install development version
run: |
# Need editable mode in order to include the test files
pip install -e .
pip install --user git+https://github.com/theochem/iodata.git@master
pip install -e .[dev,iodata]
- name: Run pytest
uses: pavelzw/pytest-action@v2
with:
verbose: true
emoji: true
job-summary: true
click-to-expand: true
custom-arguments: "--junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=gbasis"
report-title: 'Test Report'

- name: Pytest coverage comment
uses: MishaKav/pytest-coverage-comment@main
with:
pytest-coverage-path: ./pytest-coverage.txt
junitxml-path: ./pytest.xml
- name: Install Libcint library
if: ${{ matrix.os != 'windows-latest' }}
run: |
echo "${{ matrix.os }}"
sudo apt-get install -y sbcl
USE_LIBCINT=1 ./tools/install_libcint.sh
- name: Run Pytest
run: |
pytest --cov=./gbasis/ --cov-fail-under=78 --cov-report term .
2 changes: 2 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
files: "gbasis/"
exclude: "notebooks/|tests/"
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
Expand Down
11 changes: 7 additions & 4 deletions gbasis/contractions.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,10 +495,13 @@ def angmom_components_sph(self):
['s2', 's1', 'c0', 'c1', 'c2'].
"""
return tuple(
["s{}".format(m) for m in range(self.angmom, 0, -1)]
+ ["c{}".format(m) for m in range(self.angmom + 1)]
)
if self.angmom == 1:
return tuple(["c1", "s1", "c0"])
else:
return tuple(
["s{}".format(m) for m in range(self.angmom, 0, -1)]
+ ["c{}".format(m) for m in range(self.angmom + 1)]
)

@property
def norm_prim_cart(self):
Expand Down
23 changes: 22 additions & 1 deletion gbasis/integrals/libcint.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,17 @@ def __init__(self, basis, atnums, atcoords, coord_type="spherical"):
offs = np.asarray(offs, dtype=c_int)
atm_offs = np.cumsum(atm_offs)

# Get permutation vector for ordering convention
permutations = []
for shell in basis:
if hasattr(shell, "permutation_libcint"):
permutation = shell.permutation_libcint()
else:
permutation = list(range(num_angmom(shell)))
for _ in range(shell.num_seg_cont):
perm_off = len(permutations)
permutations.extend(p + perm_off for p in permutation)

# Allocate and fill C input arrays
ienv = 20
atm = np.zeros((natm, 6), dtype=c_int)
Expand Down Expand Up @@ -536,9 +547,10 @@ def __init__(self, basis, atnums, atcoords, coord_type="spherical"):
self.atcoords = atcoords.copy()
self._atm_offs = atm_offs

# Save basis function offsets
# Save basis function offsets and ordering permutation
self._offs = offs
self._max_off = max(offs)
self._permutations = permutations

# Set inverse sqrt of overlap integral (temporarily, for __init__)
self._ovlp_minhalf = np.ones(nbfn)
Expand Down Expand Up @@ -740,6 +752,9 @@ def int1e(notation="physicist", origin=None, inv_origin=None):
if constant is not None:
out *= constant

# Apply permutation
out = out[self._permutations, :][:, self._permutations]

# Return normalized integrals
if self.coord_type == "cartesian":
return np.einsum(norm_einsum, self._ovlp_minhalf, self._ovlp_minhalf, out)
Expand Down Expand Up @@ -941,6 +956,12 @@ def int2e(notation="physicist", origin=None, inv_origin=None):
if physicist:
out = out.transpose(0, 2, 1, 3)

# Apply permutation
out = out[self._permutations]
out = out[:, self._permutations]
out = out[:, :, self._permutations]
out = out[:, :, :, self._permutations]

# Return normalized integrals
if self.coord_type == "cartesian":
return np.einsum(
Expand Down
115 changes: 115 additions & 0 deletions gbasis/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,88 @@
import numpy as np


CONVENTIONS_LIBCINT = {
(5, "p"): ["s5", "s4", "s3", "s2", "s1", "c0", "c1", "c2", "c3", "c4", "c5"],
(4, "p"): ["s4", "s3", "s2", "s1", "c0", "c1", "c2", "c3", "c4"],
(3, "p"): ["s3", "s2", "s1", "c0", "c1", "c2", "c3"],
(2, "p"): ["s2", "s1", "c0", "c1", "c2"],
(0, "c"): ["1"],
(1, "c"): ["x", "y", "z"],
(2, "c"): ["xx", "xy", "xz", "yy", "yz", "zz"],
(3, "c"): ["xxx", "xxy", "xxz", "xyy", "xyz", "xzz", "yyy", "yyz", "yzz", "zzz"],
(4, "c"): [
"xxxx",
"xxxy",
"xxxz",
"xxyy",
"xxyz",
"xxzz",
"xyyy",
"xyyz",
"xyzz",
"xzzz",
"yyyy",
"yyyz",
"yyzz",
"yzzz",
"zzzz",
],
(5, "c"): [
"xxxxx",
"xxxxy",
"xxxxz",
"xxxyy",
"xxxyz",
"xxxzz",
"xxyyy",
"xxyyz",
"xxyzz",
"xxzzz",
"xyyyy",
"xyyyz",
"xyyzz",
"xyzzz",
"xzzzz",
"yyyyy",
"yyyyz",
"yyyzz",
"yyzzz",
"yzzzz",
"zzzzz",
],
(6, "c"): [
"xxxxxx",
"xxxxxy",
"xxxxxz",
"xxxxyy",
"xxxxyz",
"xxxxzz",
"xxxyyy",
"xxxyyz",
"xxxyzz",
"xxxzzz",
"xxyyyy",
"xxyyyz",
"xxyyzz",
"xxyzzz",
"xxzzzz",
"xyyyyy",
"xyyyyz",
"xyyyzz",
"xyyzzz",
"xyzzzz",
"xzzzzz",
"yyyyyy",
"yyyyyz",
"yyyyzz",
"yyyzzz",
"yyzzzz",
"yzzzzz",
"zzzzzz",
],
}


def from_iodata(mol, tol=1e-20, overlap=False):
"""Return basis set stored within the `IOData` instance in `iodata`.
Expand Down Expand Up @@ -113,6 +195,39 @@ def angmom_components_sph(self):

return tuple(sph_conventions[self.angmom])

def permutation_libcint(self):
"""Returns the permutation of the stored convention to the one used in libcint for
the given angular momentum
Conventions supported
cartesian: string of X, Y, and Z characters,such that :math:`a_x` `X` characters,
:math:`a_y` `Y` characters, and :math:`a_z` `Z`, characters appear. F
For example, `(2, 1, 1)` corresponds to `XXYZ`
Spherical: Strings of the form `c{m}` and `s{m}`.
Rerturns:
--------
permutation: An integer array that permutes basis function from IOData stored convention
to LibCint.
"""

angmom = self.angmom
if self.coord_type == "cartesian":
coord_type = "c"
iodata_shell_convention = cart_conventions[angmom]
elif self.coord_type == "spherical":
coord_type = "p"
iodata_shell_convention = sph_conventions[angmom]

# Get libcint convention
libcint_convention = CONVENTIONS_LIBCINT[(angmom, coord_type)]
# Generate permutation
permutation = [libcint_convention.index(conv1) for conv1 in iodata_shell_convention]

return permutation

if molbasis.primitive_normalization != "L2": # pragma: no cover
raise ValueError(
"Only L2 normalization scheme is supported in `gbasis`. Given `IOData` instance uses "
Expand Down
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ doc = [
]
"dev" = ["tox"]
"test" = ["tox", "pytest", "pytest-cov"]
"iodata" = ["iodata>0.1.7"]
"pyscf" = ["pyscf>=1.6.1"]

[tool.black]
Expand Down
Loading

0 comments on commit bb86ed6

Please sign in to comment.