Source code for epsman.sym.convertSyms

"""
Functions for converting symmetry lists for various contexts.

08/02/22    v1  Implements ePS & Gamess symmetries.

TODO: add manual overrides for user-defined sym mapping.
        This will need to set a mapping dictionary as per dimMapPD.attrs['mappingDict'] output by convertSymsGamessePS().
        And propagte to main orb table as set by setOrbInfoPD(), `orbPD['ePS'] = orbPD['Gamess'].apply(lambda x: orbPD.attrs['PGmap'].attrs['mappingDict'][x]) `

"""
import pandas as pd
import numpy as np

from epsman._util import parseLineDigits
from epsman.sym.defineSyms import getePSsymmetries


# TODO: use passed PG dict or orbPD data?
[docs]def setePSPGlabel(PG): """ Get and convert point group labels from Gamess inputs (via orbPD data) to ePS format. Parameters ---------- PG : dictionary A dictionary defining the point group, as currently set in :py:func:`epsman.elecStructure._orbInfo.setOrbInfoPD` for Gamess input files. Minimally passing PG['Label'] = '<Gamess symmetry label>' will also work. For N-fold groups, PG['NAXIS'] = N also needs to be defined. Returns ------- PG : dictionary, now includes PG['ePSLabel'] for converted label. """ # Set N PG['ePSLabel'] = PG['Label']['Name'] PG['gamessLabel'] = PG['Label']['Name'] # Set short-form Gamess label for display too # PG['ePSLabel'][-1] = PG['ePSLabel'][-1].swapcase() if 'N' in PG['Label']['Name']: PG['gamessLabel'] = PG['gamessLabel'] + ', ' + str(PG['Label']['NAXIS']) if int(PG['Label']['NAXIS']) < 8: PG['ePSLabel'] = PG['ePSLabel'].replace('N', str(PG['Label']['NAXIS'])) else: PG['ePSLabel'] = PG['ePSLabel'].replace('N', 'A') PG['ePSLabel'] = PG['ePSLabel'][:-1] + PG['ePSLabel'][-1].lower() # Set last char to lower case return PG
[docs]def convertSymsGamessePS(gamessPGDict, symDict = None, verbose = True): """ Convert from Gamess defined labels to ePS symmetry labels. Parameters ---------- gamessPGDict : dict Dictionary containing PG info from a Gamess file. Currently set at file IO, via :py:func:`epsman.elecStructure._orbInfo.setOrbInfoPD`. In that case accessible as `orbPD.attrs['PG']` symDict : dict, default = None Dictionary contining ePS symmetry definitions. If not passed will be created via :py:func:`epsman.sym.defineSyms.getePSsymmetries`. (Which uses https://epolyscat.droppages.com/SymmetryLabels) Returns ------- dimMapPD : Pandas dataframe Dataframe with all dim mappings. TODO: - Return basic dict form too, needs to be updated with missing vals. - Push converted labels back to orbPD? """ # Lookup ePS symmtery list if symDict is None: symDict = getePSsymmetries() # Basic error checks & warnings try: ePSPG = gamessPGDict['ePSLabel'] # May want to also try running convertSyms.setePSPGlabel again here? Although already set in setOrbInfoPD. except KeyError as e: print("***Couldn't find `gamessPGDict['ePSLabel']`, can't convert symmetries to ePS labels.") return 0 try: ePSdims = symDict[ePSPG]['ePSlabels'] except KeyError as e: print(f"***Couldn't find PG {ePSPG} in ePS symDict, can't convert symmetries to ePS labels.") return 0 # Dim checks # Borrowed from ePSproc.util.misc.checkDims(), may just want to use that directly? gamessDims = gamessPGDict['Members'] sharedDims = list({*ePSdims}&{*gamessDims}) # Intersection, no remapping required for these. # Remap missing dims Gamess > ePS labels remapDims = list({*gamessDims} - {*sharedDims}) # This is pretty much manual at this point... # Should also be able to generate with libmsym or pull from character tables...? dimMap = {} Edims = ['P','D','F','G'] # Mappings for EN cases (only for linear cases, e.g. DAh ?) if 'A' in ePSPG: for item in remapDims: if item.startswith('A1'): dimMap[item] = item.replace('A1','S') elif item.startswith('E'): N = parseLineDigits(item)[0] dimMap[item] = item.replace('E' + N, str(Edims[int(N)-1])) # elif item.startswith('E2'): # dimMap[item] = item.replace('E2','D') # Add existing mappings dimMap.update({k:k for k in sharedDims}) # Check for any remaining unmapped dims ePSextras = list({*ePSdims} - {*dimMap.values()}) gamessExtras = list({*gamessDims} - {*dimMap.keys()}) # Push to PD tables for display... and final list/checks # 07/02/22 - mostly in place, needs tidying up and consolidate set logic as separate function? # pd.DataFrame(pd.Series(dimMap), dtype="category").sort_values(by = 0) # Sort by data dimMapPD = pd.DataFrame(pd.Series(dimMap), dtype="category", columns=['ePS']).sort_index() #index.sort_values() #.sort_values(by = 0) # Sort by data dimMapPD = dimMapPD.reset_index().rename(columns = {'index':'Gamess'}) # dimMapPD = dimMapPD.reset_index().reindex(list(range(1,len(dimMap.keys())+1))) # NEED to remember how to reset this - this just chops current axis! NOW set below. # Update with any missing dims from either input list # dimMapPD.append([[' ', item] for item in list({*ePSdims} - {*dimMap.values()})], ignore_index = True) if ePSextras: dimMapPD = dimMapPD.append(pd.DataFrame([[' ', item] for item in ePSextras], columns=['Gamess','ePS']), ignore_index = True) if gamessExtras: dimMapPD = dimMapPD.append(pd.DataFrame([[item, ' '] for item in gamessExtras], columns=['Gamess','ePS']), ignore_index = True) if 'Dimensions' in gamessPGDict.keys(): # Update with Gamess dim sizes for reference (== used dims) # May be easier to add above, but this form allows for additional columns later too # pdDims = pd.DataFrame(pd.Series(gamessPGDict['Dimensions'], name = 'GDims', dtype=int)) try: # 12/02/24 - add explicit dtype cast, otherwise dtype issues in PD v1.5.3, # BUT this fails in older versions - may be issue with original datatype (as object) to fix? # This is currently sort-of working with try/except, tested in PD v1.5.3 (NP v1.23.5) and v1.2.3 (NP v1.19.2) pdDims = pd.DataFrame(pd.Series(gamessPGDict['Dimensions'], name = 'GDims').astype('Int64')) except TypeError as E: # if E is "object cannot be converted to an IntegerDtype": pdDims = pd.DataFrame(pd.Series(gamessPGDict['Dimensions'], name = 'GDims', dtype=int)) pdDims.index.name = 'Gamess' dimMapPD = dimMapPD.merge(pdDims, on = 'Gamess', how='outer') # TODO: # Check Dims consistent (all initial dims remapped?) and add additional data? dimSum = np.array(list(gamessPGDict['Dimensions'].values()), dtype=int).sum() if dimSum > int(dimMapPD['GDims'].sum()): print(f"***Warning: inconsistent dim sum, some Gamess input dims missing.") if len(gamessPGDict['Members']) != len(dimMapPD): print(f"***Warning: inconsistent dim mapping, some Gamess or ePS dims unmapped. Check PD table for details.") # Just warning, or list dims here...? # Should just print values as set earlier? # TODO: should also check ePSextras and gamessExtras lists directly? else: print(f"***Warning: 'Dimensions' key not found in input dictionary, skipping dim size checks. Please check PD table for consistency.") dimMapPD.index = dimMapPD.index+1 # Fix index, OK # Keep inputs dimMapPD.name = 'Sym table' dimMapPD.attrs['notes'] = f"Gamess ({gamessPGDict['gamessLabel']}) > ePS ({ePSPG}) dim mapping." dimMapPD.attrs['gamessPGDict'] = gamessPGDict dimMapPD.attrs['ePSdims'] = ePSdims dimMapPD.attrs['PG'] = ePSPG dimMapPD.attrs['mappingDict'] = dimMap # Set caption for display - this seems to remove attrs? Also breaks DataFrame in some cases. # TODO: more style settings, see https://pandas.pydata.org/pandas-docs/stable/user_guide/style.html#Table-Styles # attrs = dimMapPD.attrs.copy() # dimMapPD = dimMapPD.style.set_caption(dimMapPD.attrs['notes']) # dimMapPD.attrs = attrs return dimMapPD