Skip to content

Commit

Permalink
Merge pull request #11 from GaoheHu/dev
Browse files Browse the repository at this point in the history
Fix collection for properties
  • Loading branch information
GaoheHu authored Sep 18, 2024
2 parents 33a903c + 65c1f1d commit ddf4db8
Show file tree
Hide file tree
Showing 5 changed files with 396 additions and 46 deletions.
34 changes: 26 additions & 8 deletions src/chemPackage/ams/ams.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ def __init__(self, name):

# These keys may or may not be good
# Create tuples of general keywords
self.mixedkeys = ('ATOMS', 'EFIELD', 'FRAGMENTS', 'GEOMETRY',
'INTEGRATION')
# TODO: Enginekeys to be collected separately, to be implemented
# self.enginekeys = ('GEOMETRY')
self.mixedkeys = ('ATOMS', 'EFIELD', 'FRAGMENTS', 'INTEGRATION')
self.blockkeys = ('ANALYTICALFREQ', 'AORESPONSE', 'BASIS',
'CONSTRAINTS', 'DIMQM', 'DIMPAR', 'EXCITATIONS',
'EXCITEDGO', 'EXTERNALS', 'FDE', 'GEOVAR',
Expand Down Expand Up @@ -78,15 +79,19 @@ def _collect(self, abort=False):
f, indices = read_file(self)

# Collect input block
# if 'INPUT START' in indices:
# from .input_block import collect_input
# collect_input(self, f, indices)
if 'INPUT START' in indices:
from .input_block import collect_input
collect_input(self, f, indices)

# Determine calculation type
# self.__det_calc_type()
self.__det_calc_type()

if self.filetype != 'out': return

if 'DIM' in self.calctype:
from .dim import collect_dim
collect_dim(self, f, indices)

if 'INITIAL GEOMETRY' in indices:
from .coordinates import collect_geometry
collect_geometry(self, f, indices)
Expand Down Expand Up @@ -126,6 +131,19 @@ def _collect(self, abort=False):
from .polarizability import collect_ctensor
collect_ctensor(self, f, indices)

# Collect hyperpolarizability
if 'HYPERPOLARIZABILITY' in indices:
if 'RESPONSE' in self.key:
pass
# from .polarizability import collect_hyperpolarizability
# collect_hyperpolarizability(self, f, indices)
else:
from .polarizability import collect_aoresponse_hyperpolarizability
collect_aoresponse_hyperpolarizability(self, f, indices)

# from .polarizability import collect_quadrupole_beta
# collect_quadrupole_beta(self, f, indices)

# Collecct magnetizability
if 'MAGNETIZABILITY' in indices:
from .polarizability import collect_magnetizability
Expand Down Expand Up @@ -201,8 +219,8 @@ def __det_calc_type(self):
self.calctype.add('FD')
else:
self.calctype.add('STATIC')
if 'FREQUENCIES' in self.subkey:
self.calctype.add('FREQUENCIES')
# if 'FREQUENCIES' in self.subkey:
# self.calctype.add('FREQUENCIES')
if 'GEOMETRY' in self.key:
it = 'ITERATIONS'
x = [x for x in self.key['GEOMETRY'][1] if it in x.upper()]
Expand Down
229 changes: 229 additions & 0 deletions src/chemPackage/ams/dim.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
from __future__ import print_function, division
from copy import deepcopy
import numpy as np

# Replace fast copy and transpose
def fcat(arr):
return arr.T.copy()

def collect_dim(self, f, indices):
'''Collect all DIM properties.'''

########################
# COLLECT DIM CORDINATES
########################

# The DIM coordinates are laid out similarly to the QM coordinates,
# so we are able to use the same regular expression to collect
# them.
if 'DIM COORDINATES' in indices:
s = indices['DIM COORDINATES']
print(s)
e = next(i for i, x in enumerate(f[s:], s) if not x.strip())
# Elements 2, 3 and 4 are the X, Y, and Z coordinates
self.dim_coordinates = np.array([x.split()[2:5] for x in f[s:e]],
dtype=float)
self.dim_atoms = np.array([x.split()[1] for x in f[s:e]])
self.dim_elements = set(self.dim_atoms)
self.ndim_elements = len(self.dim_elements)
self.ndim = len(self.dim_atoms)
from ..constants import BOHR2ANGSTROM
self.dim_coordinates = BOHR2ANGSTROM(self.dim_coordinates)
else:
self._raise_or_pass('Error locating DIM coordinates')

#########################################
# COLLECT DIM INDUCED CHARGES AND DIPOLES
#########################################

# The DIM induced charges and dipoles are listed in a table with
# the dipole vector first and then the charge.
if 'DIM DIPOLES AND CHARGES' in indices:
start = indices['DIM DIPOLES AND CHARGES']
elif 'DIM DIPOLES' in indices:
start = indices['DIM DIPOLES']
elif 'OLD DIM D&C' in indices:
start = indices['OLD DIM D&C']
else:
start = None
# Read in the dipoles and charges if they are in the output file
if start:
self.dim_dipoles = {}
if 'CPIM' in self.subkey: self.dim_charges = {}

# Locate all locations of the induced charges and dipoles
for st in start:

# Start and stop limits
s = next(i for i, x in enumerate(f[st:], st) if 'ATOM' in x) + 3
e = next(i for i, x in enumerate(f[st:], st) if '========' in x)

# Complex boolean
cmplx = ('FD' in self.calctype and len(f[st+6].split()) == 6)
#cmplx = ('FD' in self.calctype and len(f[st+7].split()) == 5)
# Complex.
if cmplx:
# Elements 2, 3, and 4 on every other line are the real dipoles
r = np.array([x.split()[2:5] for x in f[s:e-1:2]], dtype=float)
# Elements 0, 1, and 2 on every other line are the imag dipoles
i = np.array([x.split()[0:3] for x in f[s+1:e:2]], dtype=float)
dpls = r + i*1j
# Charge not printed for PIM and DRF.
if 'CPIM' in self.subkey:
# Element 5 on every other line is the real charge.
r = np.array([x.split()[5] for x in f[s:e-1:2]], dtype=float)
# Element 3 on every other line is the imag charge.
i = np.array([x.split()[3] for x in f[s+1:e:2]], dtype=float)
chrgs = r + i*1j

# Real
else:
#print(array([x.split()[2:5] for x in f[s:e]], dtype=float))
dpls = np.array([x.split()[2:5] for x in f[s:e]], dtype=float)
if 'CPIM' in self.subkey:
chrgs = np.array([x.split()[5] for x in f[s:e]], dtype=float)

# Assign to proper location based on description after header
if '---------------' in f[st+1]:
self.dim_dipoles['ground image'] = dpls
if 'CPIM' in self.subkey:
self.dim_charges['ground image'] = chrgs
elif not cmplx:
head = 'static scattered'
# Store the different direction sequentially for each frequency
dir = { 'X': 0, 'Y': 1, 'Z': 2 }[f[st+1].split()[0]]
try:
self.dim_dipoles[head][dir] = dpls
if 'CPIM' in self.subkey: self.dim_charges[head][dir] = chrgs
except KeyError:
self.dim_dipoles[head] = [None, None, None]
self.dim_dipoles[head][dir] = dpls
if 'CPIM' in self.subkey:
self.dim_charges[head] = [None, None, None]
self.dim_charges[head][dir] = chrgs
else:
head = 'FD scattered' if 'Local' in f[st+1] else 'ES image'
# Store the different direction sequentially for each frequency

dir = { 'X': 0, 'Y': 1, 'Z': 2 }[f[st+1].split()[0]]
try:
self.dim_dipoles[head][dir].append(dpls)
if 'CPIM' in self.subkey:
self.dim_charges[head][dir].append(chrgs)
except KeyError:
self.dim_dipoles[head] = [[], [], []]
self.dim_dipoles[head][dir].append(dpls)
if 'CPIM' in self.subkey:
self.dim_charges[head] = [[], [], []]
self.dim_charges[head][dir].append(chrgs)

# Reorder so that it goes head:freq:dir:atom(:xyz) instead
# of head:dir:freq:atom(:xyz)
for head in self.dim_dipoles:
if head == 'ground image':
continue
elif head == 'static scattered':
self.dim_dipoles[head] = np.array(self.dim_dipoles[head])
if 'CPIM' in self.subkey:
self.dim_charges[head] = np.array(self.dim_charges[head])
else:
try:
self.dim_dipoles[head] = np.array(self.dim_dipoles[head]).swapaxes(0,1)
except ValueError:
# Temporarily disabled ValueError raise;
# if calculated only one direction in AOResponse, other dir have empty list,
# which leads to axes swap ValueErrors. We can ignore this case. --Pengchong, Jan 2018
continue;
if 'CPIM' in self.subkey:
self.dim_charges[head] = np.array(
self.dim_charges[head]).swapaxes(0,1)

####################################
# COLLECT DIM/QM TOTAL DIPOLE MOMENT
####################################

# If this is a DIMQM run, then we want to collect the DIM total
# dipole moment. It is after the results header just after the line
# discussing the total dipole moment.
if 'DIM DIPOLE MOMENT' in indices:
s = indices['DIM DIPOLE MOMENT']
sl = ' Total induced dipole moment in DIM system :'
ix = next(i for i, x in enumerate(f[s:], s) if x == sl)
self.dim_dipole_tot = np.array(f[ix+3].split()[1:4], dtype=float)
else:
self._raise_or_pass('Error locating DIM total dipole moment')

###################################
# COLLECT DIM/QM INTERACTION ENERGY
###################################

# If this is a DIMQM run, then we want to collect the DIM/QM
# interaction energy. It on the line with 'Total' after the header
# given below
if 'DIM/QM ENERGY' in indices:
s = indices['DIM/QM ENERGY']
ix = next(i for i, x in enumerate(f[s:], s) if ' Total' in x)
self.dimqm_energy = float(f[ix].split()[2])
else:
self._raise_or_pass('Error locating DIM/QM energy')

###########################
# COLLECT DIM SYSTEM ENERGY
###########################

# If this is a DIMQM run, then we want to collect the DIM system
# energy. If it on the line with 'Total' after the header given below
if 'DIM ENERGY' in indices:
s = indices['DIM ENERGY']
ix = next(i for i, x in enumerate(f[s:], s) if ' Total' in x)
self.dim_energy = float(f[ix].split()[2])

####################################
# COLLECT DIM POLARIZABILITY TENSORS
####################################

# The polarizability tensor for the DIM system is collected.
# It can be real or complex, depending on the calculation.
if 'POLARIZABILITY' in self.calctype:
if 'FD' in self.calctype:
if 'DIM FD REAL POLARIZABILITY' in indices:
arr = indices['DIM FD REAL POLARIZABILITY']
else:
self._raise_or_pass('Error locating DIM real polarizability')
arr = []
if 'DIM FD IMAG POLARIZABILITY' in indices:
# Make Major axis the frequencies
arr = fcat(np.array([arr,indices['DIM FD IMAG POLARIZABILITY']]))
else:
self._raise_or_pass('Error locating DIM imag polarizability')
arr = []
else:
if 'DIM STATIC POLARIZABILITY' in indices:
arr = indices['DIM STATIC POLARIZABILITY']
else:
self._raise_or_pass('Error locating DIM static polarizability')
arr = []

for ar in arr:
if 'FD' in self.calctype:
# Collect complex isolated DIM tensor
s = ar[0] + 3
e = ar[0] + 6
r = np.array([[x.split()[1:4] for x in f[s:e]]], dtype=float)
s = ar[1] + 3
e = ar[1] + 6
i = np.array([[x.split()[1:4] for x in f[s:e]]], dtype=float)
try:
self.dim_pol = np.append(self.dim_pol, r+i*1j, axis=0)
except ValueError:
self.dim_pol = r+i*1j
else:
# Collect real isolated DIM tensor
s = ar + 3
e = ar + 6
r = np.array([[x.split()[1:4] for x in f[s:e]]], dtype=float)
try:
self.dim_pol = np.append(self.dim_pol, r, axis=0)
except ValueError:
self.dim_pol = r

78 changes: 40 additions & 38 deletions src/chemPackage/ams/input_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,41 +73,42 @@ def collect_input(self, f, indices):

# Next, collect the mixed types. Can be line only or block.
# Lines are kept in element one, block in element two.
for keyword in self.mixedkeys:
ix = next((i for i, x in enumerate(search[si:], si) if keyword in x), -1)
if ix == -1:
pass
else:
# Skip EFIELD if this word is in the DIMQM block
if keyword == 'EFIELD' and 'DIMQM' in self.key:
if index['DIMQM'][0] < ix < index['DIMQM'][1]: continue
# Make a two element list
self.key[keyword] = ['', tuple()]
# Add the line part, if any
self.key[keyword][0] = ' '.join(f[ix].lstrip().split()[1:])
# Now add the block part if it exists. Not always true
# For EFIELD or GEOMETRY
bl = '&' in self.key[keyword][0] or not self.key[keyword][0]
if ((keyword in ('GEOMETRY', 'EFIELD') and bl) or
(keyword in ('FRAGMENTS', 'ATOMS'))):
s = ix + 1
# Locate END for this block
e = next(i for i, x in enumerate(search[s:], s) if x == 'END')
# Make the parameters a tuple in this key
self.key[keyword][1] = tuple(x for x in f[s:e])
# Locate possible subkeys
__determine_subkeys(self, keyword, s, e, search)
# ATOMS specific handling
if keyword == 'ATOMS':
from .coordinates import atom_block
atom_block(self, s, e, search, f, indices)
# Record where found
index[keyword] = (ix, e)
else:
# Record where found
index[keyword] = (ix, None)
# Make the entry a tuple
self.key[keyword] = tuple(self.key[keyword])
# FIXME: Some of these keys have been moved to engine settings, commented out for now.
# for keyword in self.mixedkeys:
# ix = next((i for i, x in enumerate(search[si:], si) if keyword in x), -1)
# if ix == -1:
# pass
# else:
# # Skip EFIELD if this word is in the DIMQM block
# if keyword == 'EFIELD' and 'DIMQM' in self.key:
# if index['DIMQM'][0] < ix < index['DIMQM'][1]: continue
# # Make a two element list
# self.key[keyword] = ['', tuple()]
# # Add the line part, if any
# self.key[keyword][0] = ' '.join(f[ix].lstrip().split()[1:])
# # Now add the block part if it exists. Not always true
# # For EFIELD or GEOMETRY
# bl = '&' in self.key[keyword][0] or not self.key[keyword][0]
# if ((keyword in ('GEOMETRY', 'EFIELD') and bl) or
# (keyword in ('FRAGMENTS', 'ATOMS'))):
# s = ix + 1
# # Locate END for this block
# e = next(i for i, x in enumerate(search[s:], s) if x == 'END')
# # Make the parameters a tuple in this key
# self.key[keyword][1] = tuple(x for x in f[s:e])
# # Locate possible subkeys
# __determine_subkeys(self, keyword, s, e, search)
# # ATOMS specific handling
# if keyword == 'ATOMS':
# from .coordinates import atom_block
# atom_block(self, s, e, search, f, indices)
# # Record where found
# index[keyword] = (ix, e)
# else:
# # Record where found
# index[keyword] = (ix, None)
# # Make the entry a tuple
# self.key[keyword] = tuple(self.key[keyword])

# Last, keep a record of the order of the inputs
self._input_order = tuple(sorted(index, key=index.get))
Expand Down Expand Up @@ -166,7 +167,7 @@ def __determine_subkeys(self, k, s, e, search):
self.subkey.add('FREQUENCIES')
elif next((x for x in search[s:e] if 'FREQUENCIES' in x), False):
self.subkey.add('FREQUENCIES')
elif k == 'ANALYTICALFREQ': self.subkey.add('FREQUENCIES')
# elif k == 'ANALYTICALFREQ': self.subkey.add('FREQUENCIES')
elif k == 'AORESPONSE':
if next((x for x in search[s:e] if 'LIFETIME' in x), False):
self.subkey.add('LIFETIME')
Expand All @@ -184,8 +185,9 @@ def __determine_subkeys(self, k, s, e, search):
# self.subkey.add('BETA')
if next((x for x in search[s:e] if 'GAMMA' in x), False):
self.subkey.add('GAMMA')
if next((x for x in search[s:e] if 'FREQRANGE' in x), False):
self.subkey.add('FREQRANGE')
# TODO: I didn't find any places that use this subkey, and it looks wrong
# if next((x for x in search[s:e] if 'FREQRANGE' in x), False):
# self.subkey.add('FREQRANGE')
elif k == 'RESPONSE':
if next((x for x in search[s:e] if 'RAMAN' in x), False):
self.subkey.add('RAMAN')
Expand Down
Loading

0 comments on commit ddf4db8

Please sign in to comment.