Skip to content

Commit 7471bd5

Browse files
authored
Merge pull request #116 from tjduigna/master
Molcas symmetrized orbital generation
2 parents a0290bd + 39eb5b0 commit 7471bd5

13 files changed

Lines changed: 356 additions & 112 deletions

File tree

exatomic/algorithms/basis.py

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -347,12 +347,8 @@ def _radial(self, x, y, z, alphas, cs, rs=None, pre=None):
347347
def _angular(self, shl, x, y, z, *ang):
348348
"""Generates the symbolic angular portion of a basis function.
349349
Substitutes symbolic (_i) -> (_i - iA) for i in [x, y, z]."""
350-
if len(ang) == 3:
351-
l, m, n = ang
352-
sym = _x ** l * _y ** m * _z ** n
353-
else:
354-
L, ml = ang
355-
sym = self._sh[L][ml]
350+
if len(ang) == 3: sym = _x ** ang[0] * _y ** ang[1] * _z ** ang[2]
351+
else: sym = self._sh[ang[0]][ang[1]]
356352
return sym.subs({_x: _x - x, _y: _y - y, _z: _z - z})
357353

358354

@@ -379,13 +375,11 @@ def _evaluate_gau_bso_sym(self, xs, ys, zs, irrep=None):
379375
p['L'] = [self._shells[i].L for i in p['shldx']]
380376
shls = p.groupby(['center', 'L'])
381377
norms = [shl.norm_contract() for shl in self._shells]
382-
ocens = [(col, col.replace('ocen', 'sign'))
383-
for col in bso.columns if col.startswith('ocen')]
384-
ocens = [c for t in ocens for c in t]
385-
for cen, L, ml, irrep, args in zip(bso['center'],
386-
bso['L'], bso['ml'],
387-
bso['irrep'],
388-
*(bso[col] for col in ocens)):
378+
ocens = [c for col in bso.columns if col.startswith('ocen')
379+
for c in (col, col.replace('ocen', 'sign'))]
380+
for i, (cen, L, ml, irrep) in enumerate(zip(bso['center'],
381+
bso['L'], bso['ml'],
382+
bso['irrep'])):
389383
ax, ay, az = self._xyzs[cen]
390384
shldx = shls.get_group((cen, L)).shldx.values[0]
391385
ishl = self._shells[shldx]
@@ -394,7 +388,9 @@ def _evaluate_gau_bso_sym(self, xs, ys, zs, irrep=None):
394388
r = self._radial(ax, ay, az, ishl.alphas,
395389
norm[:,cache[irrep][cen][L][ml]])
396390
oterm = None
397-
for ocen, sign in zip(args[::2], args[1::2]):
391+
for oc, si in zip(ocens[::2], ocens[1::2]):
392+
ocen = bso[oc].iloc[i]
393+
sign = bso[si].iloc[i]
398394
if ocen >= 0:
399395
ox, oy, oz = self._xyzs[ocen]
400396
ao = self._angular(ishl, ox, oy, oz, L, ml)
@@ -416,7 +412,7 @@ def _evaluate_gau_bso_sym(self, xs, ys, zs, irrep=None):
416412
def _evaluate_gau_bso(self, xs, ys, zs, irrep=None):
417413
"""Evaluates a Gaussian basis set according to the order specified
418414
by the :class:`~exatomic.core.basis.BasisSetOrder` and returns a
419-
numpy array."""
415+
numpy array of numerical basis function values."""
420416
cnt = 0
421417
if xs is not None: flds = np.empty((len(self), len(xs)))
422418
else: flds = Series([None for _ in range(len(self))])
@@ -523,7 +519,7 @@ def __repr__(self):
523519
def __init__(self, uni, frame=0, cartp=True):
524520
# Attach relevant uni attributes
525521
self._meta = uni.meta
526-
self._bso = uni.basis_set_order
522+
self._bso = uni.current_basis_set_order
527523
ptrs, xyzs, shells = uni.enumerate_shells()
528524
self._ptrs = ptrs
529525
self._xyzs = xyzs
@@ -547,4 +543,35 @@ def __init__(self, uni, frame=0, cartp=True):
547543
self._expnt = _r ** 2
548544
if not self._meta['gaussian']:
549545
self._expnt = _r
550-
self._pre = uni.basis_set_order['prefac']
546+
self._pre = uni.current_basis_set_order['prefac']
547+
548+
549+
def compute_uncontracted_basis_set_order(uni):
550+
bso = uni.basis_set_order
551+
prims = uni.basis_set.primitives_by_shell()
552+
df = []
553+
pcen, pL, pml = -1, -1, -1
554+
if uni.meta['program'] == 'molcas':
555+
for cen, L, ml in zip(bso['center'], bso['L'], bso['ml']):
556+
if not all(p == c for p, c in zip((pcen, pL, pml), (cen, L, ml))):
557+
seht = uni.atom.loc[cen]['set']
558+
nprim = prims.loc[(seht, L)]
559+
for i in range(nprim):
560+
df.append((cen, L, ml, i, 0))
561+
pcen = cen
562+
pL = L
563+
pml = ml
564+
elif uni.meta['program'] == 'nwchem':
565+
if not uni.meta['spherical']:
566+
raise NotImplementedError('Sorry.')
567+
from exatomic.nwchem.basis import spherical_ordering_function as func
568+
prims = prims.groupby('set')
569+
for i, seht in enumerate(uni.atom['set']):
570+
shls = prims.get_group(seht)
571+
for (seht, L), nprim in shls.items():
572+
for j in range(nprim):
573+
for ml in func(L):
574+
df.append([i, L, ml, j, 0])
575+
cols = ('center', 'L', 'ml', 'shell', 'frame')
576+
uni.meta['uncontracted'] = True
577+
return pd.DataFrame(df, columns=cols)

exatomic/algorithms/numerical.py

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
Everything in this module is implemented in numba.
88
"""
99
import numpy as np
10+
import pandas as pd
1011
from numba import (jit, jitclass, deferred_type,
1112
optional, int64, float64, boolean)
1213
from exatomic.base import nbche
@@ -159,6 +160,78 @@ def momatrix_as_square(movec):
159160
return square
160161

161162

163+
############################################
164+
# Reordering matrix elements can be useful #
165+
############################################
166+
167+
@jit(nopython=True, nogil=True, cache=nbche)
168+
def _index_map(old, new):
169+
"""
170+
Basis functions are uniquely defined by 4 indices;
171+
atomic center, L value, ml value and so-called "shell" index.
172+
shell is defined here as corresponding to a column index in an instance
173+
of a :class:`~exatomic.algorithms.numerical.Shell`. This function
174+
simply finds the mapping between the `old` basis set ordering scheme
175+
and the new one.
176+
177+
Args:
178+
old (np.ndarray): order [center, L, ml, shell]
179+
new (np.ndarray): order [center, L, ml, shell]
180+
181+
Returns:
182+
mappr (np.ndarray): old -> new indices
183+
"""
184+
mappr = np.empty(len(new), dtype=np.int64)
185+
for ni in range(len(new)):
186+
for oi in range(len(old)):
187+
if (new[ni] == old[oi]).all():
188+
mappr[ni] = oi
189+
break
190+
return mappr
191+
192+
@jit(nopython=True, nogil=True, cache=nbche)
193+
def _reorder_matrix(old, new, values):
194+
"""
195+
Reorders matrix elements according to an old and new basis set order.
196+
197+
Args:
198+
old (np.ndarray): order [center, L, ml, shell]
199+
new (np.ndarray): order [center, L, ml, shell]
200+
201+
Returns:
202+
nvals (np.ndarray): reordered matrix
203+
"""
204+
nvals = np.empty(values.shape)
205+
mappr = _index_map(old, new)
206+
for ni, oi in enumerate(mappr):
207+
for nj, oj in enumerate(mappr):
208+
nvals[ni, nj] = values[oi, oj]
209+
return nvals
210+
211+
212+
def reorder_matrix(uni_to_reorder, ordered_uni, attr='momatrix', mocoefs='coef'):
213+
"""
214+
Reorders matrix elements in a uni_to_reorder by the basis set order
215+
defined in ordered_uni.
216+
217+
Args:
218+
uni_to_reorder (:class:`~exatomic.core.universe.Universe`): uni to reorder
219+
ordered_uni (:class:`~exatomic.core.universe.Universe`): ordered uni
220+
attr (str): specify if non-standard matrices (default "momatrix")
221+
mocoefs (str): column name in gettattr(uni_to_reorder, attr)
222+
223+
Returns:
224+
reordered (pd.DataFrame): reordered matrix with labeled columns and indices
225+
"""
226+
cols = ['center', 'L', 'ml', 'shell']
227+
old = uni_to_reorder.current_basis_set_order[cols].values.astype(np.int64)
228+
new = ordered_uni.current_basis_set_order[cols].values.astype(np.int64)
229+
val = getattr(uni_to_reorder, attr).square(column=mocoefs).values
230+
cmat = _reorder_matrix(old, new, val)
231+
idxs = pd.Index(range(val.shape[0]), name='chi')
232+
cols = pd.Index(range(val.shape[0]), name='orbital')
233+
return pd.DataFrame(cmat, columns=cols, index=idxs)
234+
162235
#######################
163236
# Basis set expansion #
164237
#######################
@@ -169,12 +242,6 @@ def _enum_cartesian(L):
169242
# for z in range(L + 1):
170243
# for y in range(L + 1 - z):
171244
# yield (L - y - z, y, z)
172-
# Naive combinations with replacements
173-
# for i in range(L, -1, -1):
174-
# for j in range(L, -1, -1):
175-
# for k in range(L, -1, -1):
176-
# if i + j + k == L:
177-
# yield (i, j, k)
178245
# Double loop CWR
179246
for x in range(L, -1, -1):
180247
for z in range(L + 1 - x):

exatomic/algorithms/orbital.py

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
set of operations that are provided by this module and wrapped into a clean API.
99
"""
1010
import numpy as np
11+
from numba import TypingError
1112
from datetime import datetime
1213
from exatomic.base import sym2z
1314
from .orbital_util import (
1415
numerical_grid_from_field_params, _determine_fps,
1516
_determine_vector, _compute_orb_ang_mom, _compute_current_density,
16-
_compute_orbitals, _compute_density, _check_column, _make_field,
17-
_compute_orbitals_nojit)
17+
_compute_density, _check_column, _make_field,
18+
_compute_orbitals_numba, _compute_orbitals_numpy)
1819

1920

2021
def _setup_orbital(uni, verbose, vector, fps, icoefs, jcoefs=None, irrep=None):
@@ -38,17 +39,20 @@ def _setup_orbital(uni, verbose, vector, fps, icoefs, jcoefs=None, irrep=None):
3839
return t1, vector, fps, x, y, z, bvs, icoefs, jcoefs
3940
return t1, vector, fps, x, y, z, bvs, icoefs
4041

42+
def _compute_orbital(verbose, npts, bvs, vector, cmat):
43+
try: ovs = _compute_orbitals_numba(npts, bvs, vector, cmat)
44+
except (ValueError, IndexError, AssertionError, TypingError) as e:
45+
if verbose: print('numba eval failed, falling back to numpy')
46+
ovs = _compute_orbitals_numpy(npts, bvs, vector, cmat)
47+
return ovs
4148

42-
def _teardown_orbital(uni, verbose, field, t1, inplace, replace, dens=False):
49+
def _teardown_orbital(uni, verbose, field, t1, inplace, name='orbitals'):
4350
"""Boilerplate for finishing the functions in this module."""
4451
if verbose:
4552
t2 = datetime.now()
46-
kind = 'density ' if dens else 'orbitals'
47-
p2 = 'Timing: compute {} - {:>8.2f}s.'
48-
print(p2.format(kind, (t2-t1).total_seconds()))
53+
p2 = 'Timing: compute {:<8} - {:>8.2f}s.'
54+
print(p2.format(name, (t2-t1).total_seconds()))
4955
if not inplace: return field
50-
if replace and hasattr(uni, '_field'):
51-
del uni.__dict__['_field']
5256
uni.add_field(field)
5357

5458

@@ -76,14 +80,12 @@ def add_molecular_orbitals(uni, field_params=None, mocoefs=None,
7680
Warning:
7781
If replace is True, removes any fields previously attached to the universe
7882
"""
83+
if replace and hasattr(uni, '_field'): del uni.__dict__['_field']
7984
t1, vector, fps, x, y, z, bvs, mocoefs = \
8085
_setup_orbital(uni, verbose, vector, field_params, mocoefs, irrep=irrep)
81-
try: ovs = _compute_orbitals(len(x), bvs, vector, mocoefs)
82-
except (ValueError, IndexError, AssertionError) as e:
83-
if verbose: print('Falling back to numpy orbital evaluation.')
84-
ovs = _compute_orbitals_nojit(len(x), bvs, vector, mocoefs)
86+
ovs = _compute_orbital(verbose, len(x), bvs, vector, mocoefs)
8587
field = _make_field(ovs, fps)
86-
return _teardown_orbital(uni, verbose, field, t1, inplace, replace)
88+
return _teardown_orbital(uni, verbose, field, t1, inplace)
8789

8890

8991
def add_density(uni, field_params=None, mocoefs=None, orbocc=None,
@@ -106,13 +108,9 @@ def add_density(uni, field_params=None, mocoefs=None, orbocc=None,
106108
orbocc = _check_column(uni, 'orbital', orbocc)
107109
vector = uni.orbital[~np.isclose(uni.orbital[orbocc], 0)].index.values
108110
orbocc = uni.orbital.loc[vector][orbocc].values
109-
try: ovs = _compute_orbitals(len(x), bvs, vector, mocoefs)
110-
except (ValueError, IndexError, AssertionError) as e:
111-
#if verbose:
112-
print('Falling back to numpy orbital evaluation.')
113-
ovs = _compute_orbitals_nojit(len(x), bvs, vector, mocoefs)
111+
ovs = _compute_orbital(verbose, len(x), bvs, vector, mocoefs)
114112
field = _make_field(_compute_density(ovs, orbocc), fps.loc[0])
115-
return _teardown_orbital(uni, verbose, field, t1, inplace, False)
113+
return _teardown_orbital(uni, verbose, field, t1, inplace, name='density')
116114

117115

118116
def add_orb_ang_mom(uni, field_params=None, rcoefs=None, icoefs=None,
@@ -150,8 +148,6 @@ def add_orb_ang_mom(uni, field_params=None, rcoefs=None, icoefs=None,
150148
if verbose:
151149
p1 = 'Timing: grid evaluation - {:>8.2f}s.'
152150
print(p1.format((t2-t1).total_seconds()))
153-
print(rcoefs.shape, rcoefs.dtype)
154-
print(icoefs.shape, icoefs.dtype)
155151
curx, cury, curz = _compute_current_density(
156152
bvs, grx, gry, grz, rcoefs, icoefs, occvec, verbose=verbose)
157153
t3 = datetime.now()
@@ -160,4 +156,4 @@ def add_orb_ang_mom(uni, field_params=None, rcoefs=None, icoefs=None,
160156
print(p2.format((t3-t2).total_seconds()))
161157
field = _make_field(_compute_orb_ang_mom(
162158
x, y, z, curx, cury, curz, maxes), fps)
163-
return _teardown_orbital(uni, False, field, t1, inplace, False)
159+
return _teardown_orbital(uni, verbose, field, t1, inplace, name='angmom')

exatomic/algorithms/orbital_util.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -220,10 +220,7 @@ def _determine_vector(uni, vector, irrep=None):
220220
'could not be found in uni.orbital.')
221221
else:
222222
ihomo = iorb[iorb['occupation'] < 1.98]
223-
print(ihomo)
224223
ihomo = ihomo.vector.values[0]
225-
print(max(0, ihomo-5))
226-
print(min(ihomo + 7, len(iorb.index)))
227224
return np.array(range(max(0, ihomo-5),
228225
min(ihomo + 7, len(iorb.index))))
229226
# If specified, carry on
@@ -294,15 +291,14 @@ def _check_column(uni, df, key):
294291

295292

296293
@jit(nopython=True, nogil=True, parallel=nbpll)
297-
def _compute_orbitals(npts, bvs, vecs, cmat):
294+
def _compute_orbitals_numba(npts, bvs, vecs, cmat):
298295
"""Compute orbitals from numerical basis functions."""
299296
ovs = np.empty((len(vecs), npts), dtype=np.float64)
300297
for i, vec in enumerate(vecs):
301298
ovs[i] = np.dot(cmat[:, vec], bvs)
302299
return ovs
303300

304-
305-
def _compute_orbitals_nojit(npts, bvs, vecs, cmat):
301+
def _compute_orbitals_numpy(npts, bvs, vecs, cmat):
306302
"""Compute orbitals from numerical basis functions."""
307303
ovs = np.empty((len(vecs), npts), dtype=np.float64)
308304
for i, vec in enumerate(vecs):

exatomic/core/editor.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
found file formats directly into :class:`~exatomic.container.Universe` objects.
99
"""
1010
import six
11+
import pandas as pd
1112
from exa import Editor as _Editor
1213
from exa import TypedMeta
1314
from .universe import Universe
@@ -56,11 +57,14 @@ def to_universe(self, **kws):
5657
meta.update(self.meta)
5758
else:
5859
meta = self.meta
59-
kwargs = {'name': name, 'description': description,
60-
'meta': meta}
60+
kwargs = {'name': name, 'meta': meta,
61+
'description': description}
6162
attrs = [attr.replace('parse_', '')
6263
for attr in vars(self.__class__).keys()
6364
if attr.startswith('parse_')]
65+
extras = {key: val for key, val in vars(self).items()
66+
if isinstance(val, pd.DataFrame)
67+
and key[1:] not in attrs}
6468
for attr in attrs:
6569
result = None
6670
try:
@@ -72,4 +76,5 @@ def to_universe(self, **kws):
7276
if result is not None:
7377
kwargs[attr] = result
7478
kwargs.update(kws)
79+
kwargs.update(extras)
7580
return Universe(**kwargs)

0 commit comments

Comments
 (0)