Source code for schrodinger.application.mopac.results71

"""
A module to parse and store the results of a MOPAC7.1 calculation.
It interfaces directly with the Fortran code to populate the output variables
from memory.
"""
# Contributors: Mike Beachy, Mark A. Watson

import re
from itertools import zip_longest

import numpy

import schrodinger.infra.mopac as _mop
from schrodinger.application.jaguar import file_logger

MOPAC71 = 'MOPAC71'


[docs]class MopacResults71(): """ A class to parse and store the results of a MOPAC7.1 calculation. """ MULLIK = "MULLIK" ESP = "ESP" SUPER = "SUPER" UHF = "UHF" PLOTESP = "PLOTESP" PLOTALIE = "PLOTALIE" PLOTALEA = "PLOTALEA" scf_OK = int(_mop.molkst_c.scfstatus_ok) scf_FAIL = int(_mop.molkst_c.scfstatus_fail) exit_status_OK = int(_mop.molkst_c.moperrno_ok) exit_status_FATAL = int(_mop.molkst_c.moperrno_fatal) exit_status_BAD_INPUT = int(_mop.molkst_c.moperrno_bad_input) # Molecular properties energy_prop = "r_NDDO_NDDO_Heat_of_Formation" method_prop = "s_NDDO_SemiEmpirical_Method" homo_prop = "r_NDDO_HOMO_Energy" lumo_prop = "r_NDDO_LUMO_Energy" ahomo_prop = "r_NDDO_Alpha_HOMO_Energy" alumo_prop = "r_NDDO_Alpha_LUMO_Energy" bhomo_prop = "r_NDDO_Beta_HOMO_Energy" blumo_prop = "r_NDDO_Beta_LUMO_Energy" hardness_prop = "r_NDDO_Molecular_Hardness" eneg_prop = "r_NDDO_Molecular_Electronegativity" se_tot_prop = "r_NDDO_Total_Electrophilic_Superdelocalizability" sn_tot_prop = "r_NDDO_Total_Nucleophilic_Superdelocalizability" sr_tot_prop = "r_NDDO_Total_Radical_Superdelocalizability" asp_tot_prop = "r_NDDO_Total_Atom_Self_Polarizability" dipole_prop = "r_NDDO_Dipole" dipole_x_prop = "r_NDDO_Dipole_X" dipole_y_prop = "r_NDDO_Dipole_Y" dipole_z_prop = "r_NDDO_Dipole_Z" dipole_esp_prop = "r_NDDO_Dipole_ESP" dipole_esp_x_prop = "r_NDDO_Dipole_ESP_X" dipole_esp_y_prop = "r_NDDO_Dipole_ESP_Y" dipole_esp_z_prop = "r_NDDO_Dipole_ESP_Z" min_esp_prop = "r_NDDO_Min_ESP_On_Mol_Surface" max_esp_prop = "r_NDDO_Max_ESP_On_Mol_Surface" mean_esp_prop = "r_NDDO_Mean_ESP_On_Mol_Surface" mean_pos_esp_prop = "r_NDDO_Mean_Pos_ESP_On_Mol_Surface" mean_neg_esp_prop = "r_NDDO_Mean_Neg_ESP_On_Mol_Surface" sig_pos_esp_prop = "r_NDDO_Pos_ESP_Variance_On_Mol_Surface" sig_neg_esp_prop = "r_NDDO_Neg_ESP_Variance_On_Mol_Surface" sig_tot_esp_prop = "r_NDDO_Tot_ESP_Variance_On_Mol_Surface" balance_esp_prop = "r_NDDO_ESP_Balance_On_Mol_Surface" local_pol_esp_prop = "r_NDDO_Avg_Abs_Dev_from_Mean_ESP_On_Mol_Surface" min_alie_prop = "r_NDDO_Min_ALIE_On_Mol_Surface" max_alie_prop = "r_NDDO_Max_ALIE_On_Mol_Surface" mean_alie_prop = "r_NDDO_Mean_ALIE_On_Mol_Surface" mean_pos_alie_prop = "r_NDDO_Mean_Pos_ALIE_On_Mol_Surface" mean_neg_alie_prop = "r_NDDO_Mean_Neg_ALIE_On_Mol_Surface" sig_pos_alie_prop = "r_NDDO_Pos_ALIE_Variance_On_Mol_Surface" sig_neg_alie_prop = "r_NDDO_Neg_ALIE_Variance_On_Mol_Surface" sig_tot_alie_prop = "r_NDDO_Tot_ALIE_Variance_On_Mol_Surface" balance_alie_prop = "r_NDDO_ALIE_Balance_On_Mol_Surface" local_pol_alie_prop = "r_NDDO_Avg_Abs_Dev_from_Mean_ALIE_On_Mol_Surface" min_alea_prop = "r_NDDO_Min_ALEA_On_Mol_Surface" max_alea_prop = "r_NDDO_Max_ALEA_On_Mol_Surface" mean_alea_prop = "r_NDDO_Mean_ALEA_On_Mol_Surface" mean_pos_alea_prop = "r_NDDO_Mean_Pos_ALEA_On_Mol_Surface" mean_neg_alea_prop = "r_NDDO_Mean_Neg_ALEA_On_Mol_Surface" sig_pos_alea_prop = "r_NDDO_Pos_ALEA_Variance_On_Mol_Surface" sig_neg_alea_prop = "r_NDDO_Neg_ALEA_Variance_On_Mol_Surface" sig_tot_alea_prop = "r_NDDO_Tot_ALEA_Variance_On_Mol_Surface" balance_alea_prop = "r_NDDO_ALEA_Balance_On_Mol_Surface" local_pol_alea_prop = "r_NDDO_Avg_Abs_Dev_from_Mean_ALEA_On_Mol_Surface" # Atomic properties mulliken_charge_prop = "r_NDDO_Mulliken_Charge" atomic_charge_prop = "r_NDDO_NDDO_Charge" esp_charge_prop = "r_NDDO_ESP_Charge" fe_prop = "r_NDDO_Electrophilic_Frontier_Electron_Density" fn_prop = "r_NDDO_Nucleophilic_Frontier_Electron_Density" se_prop = "r_NDDO_Electrophilic_Superdelocalizability" sn_prop = "r_NDDO_Nucleophilic_Superdelocalizability" sr_prop = "r_NDDO_Radical_Superdelocalizability" asp_prop = "r_NDDO_Atom_Self_Polarizability" maxat_esp_prop = "r_NDDO_Max_surface_ESP" minat_esp_prop = "r_NDDO_Min_surface_ESP" maxat_alie_prop = "r_NDDO_Max_surface_ALIE" minat_alie_prop = "r_NDDO_Min_surface_ALIE" maxat_alea_prop = "r_NDDO_Max_surface_ALEA" minat_alea_prop = "r_NDDO_Min_surface_ALEA"
[docs] def __init__(self, mopfile): # # General attributes # self.mopfile = mopfile self.error_text = self.get_error_text() self.scf_status = int(_mop.molkst_c.iscf) self.geopt_status = int(_mop.molkst_c.iflepo) self.molchg = int(_mop.molkst_c.molchg) self._keywords = str(_mop.molkst_c.prevkeywrd, 'utf-8').rstrip() self._return_code = None self._structure = None self._method = None self._output_file = None
@property def structure(self): return self._structure @property def method(self): return self._method @property def output_file(self): return self._output_file @property def statusOk(self): """ Check the summary status of the job, looking at SCF status and return code of the main routine. Return True if all is ok, False if not. """ if (self.scf_status != self.scf_OK or self._return_code != self.exit_status_OK): return False else: return True
[docs] def set_method(self, value): self._method = value
[docs] def set_output_file(self, value): self._output_file = value
[docs] def set_return_code(self, value): self._return_code = value
[docs] def set_count(self, value): # Determine the calculation count based on the current value of # numcal and the last stored value of numcal. self._calc_count = _mop.molkst_c.numcal - value - 1
[docs] def populate_from_memory(self): """ Populate data derived from the f2py wrapped code. """ # Local aliases numat = _mop.molkst_c.numat molkst_c = _mop.molkst_c perm_arrays = _mop.permanent_arrays charges_c = _mop.charges_c esp_c = _mop.esp_c properties = _mop.properties_c if self.scf_status == self.scf_OK: self.escf = float(molkst_c.escf) self.ehomo = float(properties.ehomo) self.elumo = float(properties.elumo) if self.check_key(self.UHF): self.ehomo_b = float(properties.ehomo_b) self.elumo_b = float(properties.elumo_b) else: self.ehomo_b = None self.elumo_b = None self.dipole = float(molkst_c.utotal) self.dipole_x = float(molkst_c.ux) self.dipole_y = float(molkst_c.uy) self.dipole_z = float(molkst_c.uz) else: self.escf = None self.ehomo = None self.elumo = None self.dipole = None self.dipole_x = None self.dipole_y = None self.dipole_z = None self.geopt_status = int(molkst_c.iflepo) # TODO: add checks on geopt_status. The iflepo meanings are recorded # in the flepo array defined in src_subroutines/writmo.F90. self.geometry = numpy.array(perm_arrays.coord[:, :numat]).transpose() # We do charge-fitting if either ESP is set or PLOTESP is set, but # we don't calculate the dipole from the charges unless the # molecular charge is 0. if (self.check_key(self.ESP) or self.check_key(self.PLOTESP)) and self.molchg == 0: self.dipole_esp = float(molkst_c.dipole_esp) self.dipole_esp_x = float(molkst_c.dipole_esp_x) self.dipole_esp_y = float(molkst_c.dipole_esp_y) self.dipole_esp_z = float(molkst_c.dipole_esp_z) else: self.dipole_esp = None self.dipole_esp_x = None self.dipole_esp_y = None self.dipole_esp_z = None if self.check_key(self.SUPER): self.eneg = float(properties.eneg) self.hardness = float(properties.hardness) self.se_tot = float(properties.se_tot) self.sn_tot = float(properties.sn_tot) self.sr_tot = float(properties.sr_tot) self.asp_tot = float(properties.asp_tot) else: self.eneg = None self.hardness = None self.se_tot = None self.sn_tot = None self.sr_tot = None self.asp_tot = None if self.check_key(self.PLOTESP): self.min_esp = float(properties.min_esp) self.max_esp = float(properties.max_esp) self.mean_esp = float(properties.mean_esp) self.mean_pos_esp = float(properties.mean_pos_esp) self.mean_neg_esp = float(properties.mean_neg_esp) self.sig_pos_esp = float(properties.sig_pos_esp) self.sig_neg_esp = float(properties.sig_neg_esp) self.sig_tot_esp = float(properties.sig_tot_esp) self.balance_esp = float(properties.balance_esp) self.local_pol_esp = float(properties.local_pol_esp) else: self.min_esp = None self.max_esp = None self.mean_esp = None self.mean_pos_esp = None self.mean_neg_esp = None self.sig_pos_esp = None self.sig_neg_esp = None self.sig_tot_esp = None self.balance_esp = None self.local_pol_esp = None if self.check_key(self.PLOTALIE): self.min_alie = float(properties.min_alie) self.max_alie = float(properties.max_alie) self.mean_alie = float(properties.mean_alie) self.mean_pos_alie = float(properties.mean_pos_alie) self.mean_neg_alie = float(properties.mean_neg_alie) self.sig_pos_alie = float(properties.sig_pos_alie) self.sig_neg_alie = float(properties.sig_neg_alie) self.sig_tot_alie = float(properties.sig_tot_alie) self.balance_alie = float(properties.balance_alie) self.local_pol_alie = float(properties.local_pol_alie) else: self.min_alie = None self.max_alie = None self.mean_alie = None self.mean_pos_alie = None self.mean_neg_alie = None self.sig_pos_alie = None self.sig_neg_alie = None self.sig_tot_alie = None self.balance_alie = None self.local_pol_alie = None if self.check_key(self.PLOTALEA): self.min_alea = float(properties.min_alea) self.max_alea = float(properties.max_alea) self.mean_alea = float(properties.mean_alea) self.mean_pos_alea = float(properties.mean_pos_alea) self.mean_neg_alea = float(properties.mean_neg_alea) self.sig_pos_alea = float(properties.sig_pos_alea) self.sig_neg_alea = float(properties.sig_neg_alea) self.sig_tot_alea = float(properties.sig_tot_alea) self.balance_alea = float(properties.balance_alea) self.local_pol_alea = float(properties.local_pol_alea) else: self.min_alea = None self.max_alea = None self.mean_alea = None self.mean_pos_alea = None self.mean_neg_alea = None self.sig_pos_alea = None self.sig_neg_alea = None self.sig_tot_alea = None self.balance_alea = None self.local_pol_alea = None ## Atomic properties self.atomic_charge = numpy.array(charges_c.q2) if self.check_key(self.MULLIK): self.mulliken_charge = numpy.array(charges_c.mulliken) else: self.mulliken_charge = None ## We do charge-fitting if either ESP is set or PLOTESP is set if self.check_key(self.ESP) or \ self.check_key(self.PLOTESP): self.esp_charge = numpy.array(esp_c.qesp[:numat]) else: self.esp_charge = None if self.check_key(self.PLOTESP): self.maxat_esp = numpy.array(properties.maxat_esp[:numat]) self.minat_esp = numpy.array(properties.minat_esp[:numat]) else: self.maxat_esp = None self.minat_esp = None if self.check_key(self.PLOTALIE): self.maxat_alie = numpy.array(properties.maxat_alie[:numat]) self.minat_alie = numpy.array(properties.minat_alie[:numat]) else: self.maxat_alie = None self.minat_alie = None if self.check_key(self.PLOTALEA): self.maxat_alea = numpy.array(properties.maxat_alea[:numat]) self.minat_alea = numpy.array(properties.minat_alea[:numat]) else: self.maxat_alea = None self.minat_alea = None if self.check_key(self.SUPER): self.fe = numpy.array(properties.fe) self.fn = numpy.array(properties.fn) self.se = numpy.array(properties.se) self.sn = numpy.array(properties.sn) self.sr = numpy.array(properties.sr) self.asp = numpy.array(properties.asp) else: self.fe = None self.fn = None self.se = None self.sn = None self.sr = None self.asp = None
[docs] def check_key(self, keyword): """ If the given keyword was set in the job specification, return True, otherwise return False. """ if self._keywords.find(" " + keyword.upper()) != -1: return True else: return False
[docs] def set_final_structure(self, structure): """ Set the final structure and update the coordinates to match the final geometry. """ self._structure = structure self._structure.setXYZ(self.geometry) # Used to create .smap files self._structure.property[file_logger.JNAME] = self.mopfile # Molecular properties if self.escf is not None: self._structure.property[self.energy_prop] = self.escf if self._method: self._structure.property[self.method_prop] = self._method if self.dipole is not None: self._structure.property[self.dipole_prop] = self.dipole self._structure.property[self.dipole_x_prop] = self.dipole_x self._structure.property[self.dipole_y_prop] = self.dipole_y self._structure.property[self.dipole_z_prop] = self.dipole_z if self.dipole_esp is not None: self._structure.property[self.dipole_esp_prop] = self.dipole_esp self._structure.property[self.dipole_esp_x_prop] = self.dipole_esp_x self._structure.property[self.dipole_esp_y_prop] = self.dipole_esp_y self._structure.property[self.dipole_esp_z_prop] = self.dipole_esp_z if self.check_key(self.UHF): if self.ehomo is not None: self._structure.property[self.ahomo_prop] = self.ehomo if self.elumo is not None: self._structure.property[self.alumo_prop] = self.elumo if self.ehomo_b is not None: self._structure.property[self.bhomo_prop] = self.ehomo_b if self.elumo_b is not None: self._structure.property[self.blumo_prop] = self.elumo_b else: if self.ehomo is not None: self._structure.property[self.homo_prop] = self.ehomo if self.elumo is not None: self._structure.property[self.lumo_prop] = self.elumo if self.eneg is not None: self._structure.property[self.eneg_prop] = self.eneg if self.hardness is not None: self._structure.property[self.hardness_prop] = self.hardness if self.se_tot is not None: self._structure.property[self.se_tot_prop] = self.se_tot if self.sn_tot is not None: self._structure.property[self.sn_tot_prop] = self.sn_tot if self.sr_tot is not None: self._structure.property[self.sr_tot_prop] = self.sr_tot if self.asp_tot is not None: self._structure.property[self.asp_tot_prop] = self.asp_tot if self.min_esp is not None: self._structure.property[self.min_esp_prop] = self.min_esp if self.max_esp is not None: self._structure.property[self.max_esp_prop] = self.max_esp if self.mean_esp is not None: self._structure.property[self.mean_esp_prop] = self.mean_esp if self.mean_pos_esp is not None: self._structure.property[self.mean_pos_esp_prop] = self.mean_pos_esp if self.mean_neg_esp is not None: self._structure.property[self.mean_neg_esp_prop] = self.mean_neg_esp if self.sig_pos_esp is not None: self._structure.property[self.sig_pos_esp_prop] = self.sig_pos_esp if self.sig_neg_esp is not None: self._structure.property[self.sig_neg_esp_prop] = self.sig_neg_esp if self.sig_tot_esp is not None: self._structure.property[self.sig_tot_esp_prop] = self.sig_tot_esp if self.balance_esp is not None: self._structure.property[self.balance_esp_prop] = self.balance_esp if self.local_pol_esp is not None: self._structure.property[ self.local_pol_esp_prop] = self.local_pol_esp if self.min_alie is not None: self._structure.property[self.min_alie_prop] = self.min_alie if self.max_alie is not None: self._structure.property[self.max_alie_prop] = self.max_alie if self.mean_alie is not None: self._structure.property[self.mean_alie_prop] = self.mean_alie if self.mean_pos_alie is not None: self._structure.property[ self.mean_pos_alie_prop] = self.mean_pos_alie if self.mean_neg_alie is not None: self._structure.property[ self.mean_neg_alie_prop] = self.mean_neg_alie if self.sig_pos_alie is not None: self._structure.property[self.sig_pos_alie_prop] = self.sig_pos_alie if self.sig_neg_alie is not None: self._structure.property[self.sig_neg_alie_prop] = self.sig_neg_alie if self.sig_tot_alie is not None: self._structure.property[self.sig_tot_alie_prop] = self.sig_tot_alie if self.balance_alie is not None: self._structure.property[self.balance_alie_prop] = self.balance_alie if self.local_pol_alie is not None: self._structure.property[ self.local_pol_alie_prop] = self.local_pol_alie if self.min_alea is not None: self._structure.property[self.min_alea_prop] = self.min_alea if self.max_alea is not None: self._structure.property[self.max_alea_prop] = self.max_alea if self.mean_alea is not None: self._structure.property[self.mean_alea_prop] = self.mean_alea if self.mean_pos_alea is not None: self._structure.property[ self.mean_pos_alea_prop] = self.mean_pos_alea if self.mean_neg_alea is not None: self._structure.property[ self.mean_neg_alea_prop] = self.mean_neg_alea if self.sig_pos_alea is not None: self._structure.property[self.sig_pos_alea_prop] = self.sig_pos_alea if self.sig_neg_alea is not None: self._structure.property[self.sig_neg_alea_prop] = self.sig_neg_alea if self.sig_tot_alea is not None: self._structure.property[self.sig_tot_alea_prop] = self.sig_tot_alea if self.balance_alea is not None: self._structure.property[self.balance_alea_prop] = self.balance_alea if self.local_pol_alea is not None: self._structure.property[ self.local_pol_alea_prop] = self.local_pol_alea # Atomic properties for at, c in zip(self._structure.atom, self.atomic_charge): at.property[self.atomic_charge_prop] = c # Also set charge1 and charge2. at.partial_charge = c at.solvation_charge = c if self.mulliken_charge is not None: for at, c in zip(self._structure.atom, self.mulliken_charge): at.property[self.mulliken_charge_prop] = c if self.esp_charge is not None: for at, c in zip(self._structure.atom, self.esp_charge): at.property[self.esp_charge_prop] = c if self.maxat_esp is not None: for at, c in zip(self._structure.atom, self.maxat_esp): at.property[self.maxat_esp_prop] = c if self.minat_esp is not None: for at, c in zip(self._structure.atom, self.minat_esp): at.property[self.minat_esp_prop] = c if self.maxat_alie is not None: for at, c in zip(self._structure.atom, self.maxat_alie): at.property[self.maxat_alie_prop] = c if self.minat_alie is not None: for at, c in zip(self._structure.atom, self.minat_alie): at.property[self.minat_alie_prop] = c if self.maxat_alea is not None: for at, c in zip(self._structure.atom, self.maxat_alea): at.property[self.maxat_alea_prop] = c if self.minat_alea is not None: for at, c in zip(self._structure.atom, self.minat_alea): at.property[self.minat_alea_prop] = c # The following 6 properties are all initialized to 0.0 in the # fortran code, but they are not actually defined for hydrogen # atoms, so don't set them here for hydrogens (or dummies). if self.fe is not None: for at, c in zip(self._structure.atom, self.fe): if at.atomic_number > 1: at.property[self.fe_prop] = c if self.fn is not None: for at, c in zip(self._structure.atom, self.fn): if at.atomic_number > 1: at.property[self.fn_prop] = c if self.se is not None: for at, c in zip(self._structure.atom, self.se): if at.atomic_number > 1: at.property[self.se_prop] = c if self.sn is not None: for at, c in zip(self._structure.atom, self.sn): if at.atomic_number > 1: at.property[self.sn_prop] = c if self.sr is not None: for at, c in zip(self._structure.atom, self.sr): if at.atomic_number > 1: at.property[self.sr_prop] = c if self.asp is not None: for at, c in zip(self._structure.atom, self.asp): if at.atomic_number > 1: at.property[self.asp_prop] = c
[docs] def get_error_text(self): """ Get the error text set during the main MOPAC calculation. If there is no error text, returns the empty string. """ return str(_mop.molkst_c.errtxt, 'utf-8').rstrip()
[docs] def write_vis_files(self): """ This function is not needed in this API """ return True
[docs] def populate_from_outfile(self, structures): """ Parse a MOPAC .out file for a limited set of properties and populate an associated Structure instance with the parsed values. Currently only "semi-empirical method" (e.g. AM1, PM3) and "Final Heat of Formation" are supported. We consider a subjob to have failed for a given structure if any of these properties fail to be populated from the output file. i.e. we expect any type of MOPAC job will generate these properties. This method should be used when running a multi-structure MOPAC input file when the f2py interface cannot be used to get results. :type structures: list :param structures: list of Structure objects :return: list of bools denoting job status of each subjob """ # Regex for regular decimal float DNUM_REGEX = r'(\s*\+?\-?\d+\.\d+)' PROPERTIES = { 'energy_prop': (re.compile(r'FINAL HEAT OF FORMATION\s+=' + DNUM_REGEX), float ), 'method_prop': (re.compile(r' (\S*)\s*CALCULATION RESULTS'), str) } results = [] # Parse .mop output file for selected properties and store them with open(self._output_file, 'r') as fh: for line in fh: if line.strip() == 'Schrodinger semiempirical NDDO module': # Create a new result container for this subjob result = dict((k, None) for k in PROPERTIES.keys()) elif line.strip() == '== DONE ==': # Save the result and reset results.append(result) result = dict((k, None) for k in PROPERTIES.keys()) # Search line for any relevant data to store for prop, (pattern, cast) in PROPERTIES.items(): match = pattern.search(line) if match: result[prop] = cast(match.group(1)) # Populate structure properties with the parsed data. # If lists have different lengths, we assume results has been truncated # due to e.g. job crash, so we only update CT's as far as we can. all_ok = [] null = dict((k, None) for k in PROPERTIES.keys()) i = 0 for ct, result in zip_longest(structures, results, fillvalue=null): i += 1 ok = True for k, v in result.items(): if v is not None: ct.property[getattr(self, k)] = v else: # Consider job to have failed if any of the expected # properties fail to be populated print(f'Failed to parse {k} for job {ct.title}') ok = False all_ok.append(ok) if len(all_ok) != len(structures): msg = 'Error text-parsing MOPAC7.1 output file.\n' msg += 'len(ok) = %d\n' % len(ok) msg += 'len(structures) = %d' % len(structures) raise RuntimeError(msg) return all_ok