def _read_optimized_geometry(self): """ Parses optimized XYZ coordinates. If not present, parses optimized Z-matrix. """ header_pattern = r"\*+\s+OPTIMIZATION\s+CONVERGED\s+\*+\s+\*+\s+Coordinates \(Angstroms\)\s+ATOM\s+X\s+Y\s+Z" table_pattern = r"\s+\d+\s+\w+\s+([\d\-\.]+)\s+([\d\-\.]+)\s+([\d\-\.]+)" footer_pattern = r"\s+Z-matrix Print:" parsed_optimized_geometry = read_table_pattern( self.text, header_pattern, table_pattern, footer_pattern) if parsed_optimized_geometry == [] or None: self.data["optimized_geometry"] = [] header_pattern = r"^\s+\*+\s+OPTIMIZATION CONVERGED\s+\*+\s+\*+\s+Z-matrix\s+Print:\s+\$molecule\s+[\d\-]+\s+[\d\-]+\n" table_pattern = r"\s*(\w+)(?:\s+(\d+)\s+([\d\-\.]+)(?:\s+(\d+)\s+([\d\-\.]+)(?:\s+(\d+)\s+([\d\-\.]+))*)*)*(?:\s+0)*" footer_pattern = r"^\$end\n" self.data["optimized_zmat"] = read_table_pattern( self.text, header_pattern, table_pattern, footer_pattern) else: self.data["optimized_geometry"] = process_parsed_coords( parsed_optimized_geometry[0]) if self.data.get('charge') != None: self.data["molecule_from_optimized_geometry"] = Molecule( species=self.data.get('species'), coords=self.data.get('optimized_geometry'), charge=self.data.get('charge'), spin_multiplicity=self.data.get('multiplicity'))
def _parse_node_opt_trajectory(self): self.data["opt_trajectory_energies"] = dict() self.data["opt_trajectory_gradrms"] = dict() header_pattern = r"\s*converged\n\s*opt-summary [0-9]+" body_pattern = r"\s*Node: ([0-9]+) Opt step: [0-9]+ E: ([\.\-0-9]+) predE: [\.\-0-9]+ ratio: [\.\-0-9]+ gradrms: ([\.0-9]+) ss: [\-\.0-9]+ DMAX: ([\.0-9]+)" footer_pattern = r"" temp_opt_trajectories = read_table_pattern( self.text, header_pattern=header_pattern, row_pattern=body_pattern, footer_pattern=footer_pattern) for table in temp_opt_trajectories: energies = list() grads = list() node_num = int(table[0][0]) if node_num not in self.data["opt_trajectory_energies"]: self.data["opt_trajectory_energies"][node_num] = list() if node_num not in self.data["opt_trajectory_gradrms"]: self.data["opt_trajectory_gradrms"][node_num] = list() for row in table: energies.append(float(row[1])) grads.append(float(row[2])) self.data["opt_trajectory_energies"][node_num].append(energies) self.data["opt_trajectory_gradrms"][node_num].append(grads)
def read_molecule(string): charge = None spin_mult = None patterns = { "read": r"^\s*\$molecule\n\s*(read)", "charge": r"^\s*\$molecule\n\s*(\d+)\s+\d", "spin_mult": r"^\s*\$molecule\n\s*\d+\s*(\d)" } matches = read_pattern(string, patterns) if "read" in matches.keys(): return "read" if "charge" in matches.keys(): charge = float(matches["charge"][0][0]) if "spin_mult" in matches.keys(): spin_mult = int(matches["spin_mult"][0][0]) header = r"^\s*\$molecule\n\s*\d\s*\d" row = r"\s*((?i)[a-z]+)\s+([\d\-\.]+)\s+([\d\-\.]+)\s+([\d\-\.]+)" footer = r"^\$end" mol_table = read_table_pattern(string, header_pattern=header, row_pattern=row, footer_pattern=footer) species = [val[0] for val in mol_table[0]] coords = [[float(val[1]), float(val[2]), float(val[3])] for val in mol_table[0]] mol = Molecule(species=species, coords=coords, charge=charge, spin_multiplicity=spin_mult) return mol
def _read_mulliken(self): """ Parses Mulliken charges. Also parses spins given an unrestricted SCF. """ if self.data.get('unrestricted', []): header_pattern = r"\-+\s+Ground-State Mulliken Net Atomic Charges\s+Atom\s+Charge \(a\.u\.\)\s+Spin\s\(a\.u\.\)\s+\-+" table_pattern = r"\s+\d+\s\w+\s+([\d\-\.]+)\s+([\d\-\.]+)" footer_pattern = r"\s\s\-+\s+Sum of atomic charges" else: header_pattern = r"\-+\s+Ground-State Mulliken Net Atomic Charges\s+Atom\s+Charge \(a\.u\.\)\s+\-+" table_pattern = r"\s+\d+\s\w+\s+([\d\-\.]+)" footer_pattern = r"\s\s\-+\s+Sum of atomic charges" temp_mulliken = read_table_pattern(self.text, header_pattern, table_pattern, footer_pattern) real_mulliken = [] for one_mulliken in temp_mulliken: if self.data.get('unrestricted', []): temp = np.zeros(shape=(len(one_mulliken), 2)) for ii, entry in enumerate(one_mulliken): temp[ii, 0] = float(entry[0]) temp[ii, 1] = float(entry[1]) else: temp = np.zeros(len(one_mulliken)) for ii, entry in enumerate(one_mulliken): temp[ii] = float(entry[0]) real_mulliken += [temp] self.data["Mulliken"] = real_mulliken
def _read_SCF(self): """ Parses both old and new SCFs. """ if self.data.get('using_GEN_SCFMAN', []): if "SCF_failed_to_converge" in self.data.get("errors"): footer_pattern = r"^\s*gen_scfman_exception: SCF failed to converge" else: footer_pattern = r"^\s*\-+\n" header_pattern = r"^\s*\-+\s+Cycle\s+Energy\s+(?:(?:DIIS)*\s+[Ee]rror)*(?:RMS Gradient)*\s+\-+(?:\s*\-+\s+OpenMP\s+Integral\s+computing\s+Module\s+(?:Release:\s+version\s+[\d\-\.]+\,\s+\w+\s+[\d\-\.]+\, Q-Chem Inc\. Pittsburgh\s+)*\-+)*\n" table_pattern = r"(?:\s*Nonlocal correlation = [\d\-\.]+e[\d\-]+)*(?:\s*Inaccurate integrated density:\n\s+Number of electrons\s+=\s+[\d\-\.]+\n\s+Numerical integral\s+=\s+[\d\-\.]+\n\s+Relative error\s+=\s+[\d\-\.]+\s+\%\n)*\s*\d+\s+([\d\-\.]+)\s+([\d\-\.]+)e([\d\-\.\+]+)(?:\s+Convergence criterion met)*(?:\s+Preconditoned Steepest Descent)*(?:\s+Roothaan Step)*(?:\s+(?:Normal\s+)*BFGS [Ss]tep)*(?:\s+LineSearch Step)*(?:\s+Line search: overstep)*(?:\s+Descent step)*" else: if "SCF_failed_to_converge" in self.data.get("errors"): footer_pattern = r"^\s*\d+\s*[\d\-\.]+\s+[\d\-\.]+E[\d\-\.]+\s+Convergence\s+failure\n" else: footer_pattern = r"^\s*\-+\n" header_pattern = r"^\s*\-+\s+Cycle\s+Energy\s+DIIS Error\s+\-+\n" table_pattern = r"(?:\s*Inaccurate integrated density:\n\s+Number of electrons\s+=\s+[\d\-\.]+\n\s+Numerical integral\s+=\s+[\d\-\.]+\n\s+Relative error\s+=\s+[\d\-\.]+\s+\%\n)*\s*\d+\s*([\d\-\.]+)\s+([\d\-\.]+)E([\d\-\.\+]+)(?:\s*\n\s*cpu\s+[\d\-\.]+\swall\s+[\d\-\.]+)*(?:\nin dftxc\.C, eleTot sum is:[\d\-\.]+, tauTot is\:[\d\-\.]+)*(?:\s+Convergence criterion met)*(?:\s+Done RCA\. Switching to DIIS)*(?:\n\s*Warning: not using a symmetric Q)*(?:\nRecomputing EXC\s*[\d\-\.]+\s*[\d\-\.]+\s*[\d\-\.]+(?:\s*\nRecomputing EXC\s*[\d\-\.]+\s*[\d\-\.]+\s*[\d\-\.]+)*)*" temp_scf = read_table_pattern(self.text, header_pattern, table_pattern, footer_pattern) real_scf = [] for one_scf in temp_scf: temp = np.zeros(shape=(len(one_scf), 2)) for ii, entry in enumerate(one_scf): temp[ii, 0] = float(entry[0]) temp[ii, 1] = float(entry[1]) * 10**float(entry[2]) real_scf += [temp] self.data["SCF"] = real_scf
def _read_species_and_inital_geometry(self): """ Parses species and initial geometry. """ header_pattern = r"Standard Nuclear Orientation \(Angstroms\)\s+I\s+Atom\s+X\s+Y\s+Z\s+-+" table_pattern = r"\s*\d+\s+([a-zA-Z]+)\s*([\d\-\.]+)\s*([\d\-\.]+)\s*([\d\-\.]+)\s*" footer_pattern = r"\s*-+" temp_geom = read_table_pattern(self.text, header_pattern, table_pattern, footer_pattern) if temp_geom == None or len(temp_geom) == 0: self.data["species"] = None self.data["initial_geometry"] = None self.data["initial_molecule"] = None else: temp_geom = temp_geom[0] species = [] geometry = np.zeros(shape=(len(temp_geom), 3), dtype=float) for ii, entry in enumerate(temp_geom): species += [entry[0]] for jj in range(3): geometry[ii, jj] = float(entry[jj + 1]) self.data["species"] = species self.data["initial_geometry"] = geometry self.data["initial_molecule"] = Molecule( species=species, coords=geometry, charge=self.data.get('charge'), spin_multiplicity=self.data.get('multiplicity'))
def read_opt(string): patterns = { "CONSTRAINT": r"^\s*CONSTRAINT", "FIXED": r"^\s*FIXED", "DUMMY": r"^\s*DUMMY", "CONNECT": r"^\s*CONNECT" } opt_matches = read_pattern(string, patterns) opt_sections = [key for key in opt_matches.keys()] opt = {} if "CONSTRAINT" in opt_sections: c_header = r"^\s*CONSTRAINT\n" c_row = r"(\w.*)\n" c_footer = r"^\s*ENDCONSTRAINT\n" c_table = read_table_pattern(string, header_pattern=c_header, row_pattern=c_row, footer_pattern=c_footer) opt["CONSTRAINT"] = [val[0] for val in c_table[0]] if "FIXED" in opt_sections: f_header = r"^\s*FIXED\n" f_row = r"(\w.*)\n" f_footer = r"^\s*ENDFIXED\n" f_table = read_table_pattern(string, header_pattern=f_header, row_pattern=f_row, footer_pattern=f_footer) opt["FIXED"] = [val[0] for val in f_table[0]] if "DUMMY" in opt_sections: d_header = r"^\s*DUMMY\n" d_row = r"(\w.*)\n" d_footer = r"^\s*ENDDUMMY\n" d_table = read_table_pattern(string, header_pattern=d_header, row_pattern=d_row, footer_pattern=d_footer) opt["DUMMY"] = [val[0] for val in d_table[0]] if "CONNECT" in opt_sections: cc_header = r"^\s*CONNECT\n" cc_row = r"(\w.*)\n" cc_footer = r"^\s*ENDCONNECT\n" cc_table = read_table_pattern(string, header_pattern=cc_header, row_pattern=cc_row, footer_pattern=cc_footer) opt["CONNECT"] = [val[0] for val in cc_table[0]] return opt
def _parse_input_values(self): header_pattern = r"#=+#\n#\|.+\[92m\s+Parsed GSM Keys : Values.+\[0m\s+\|#\n#=+#" table_pattern = r"(?P<key>[A-Za-z_]+)\s+(?P<value>[A-Za-z0-9\[\]_\.\-]+)\s*\n" footer_pattern = r"\-+" temp_inputs = read_table_pattern(self.text, header_pattern, table_pattern, footer_pattern) if temp_inputs is None or len(temp_inputs) == 0: self.data["inputs"] = dict() else: self.data["inputs"] = dict() temp_inputs = temp_inputs[0] for row in temp_inputs: key = row["key"] value = row["value"] if value == "True": # Deal with bools self.data["inputs"][key] = True elif value == "False": self.data["inputs"][key] = False elif value.startswith("[") and value.endswith( "]"): # Deal with lists val = value[1:-1].split(", ") try: # ints val = [int(v) for v in val] self.data["inputs"][key] = val except ValueError: self.data["inputs"][key] = val else: # int is_int = True is_float = True val = value try: val = int(value) except ValueError: is_int = False if is_int: self.data["inputs"][key] = val continue else: try: val = float(value) except ValueError: is_float = False if is_float: self.data["inputs"][key] = val continue else: self.data["inputs"][key] = value if "charge" not in self.data["inputs"]: self.data["inputs"]["charge"] = 0
def read_rem(string): header = r"^\s*\$rem" row = r"\s*([a-zA-Z\_]+)\s*=?\s*(\S+)" footer = r"^\s*\$end" rem_table = read_table_pattern(string, header_pattern=header, row_pattern=row, footer_pattern=footer) rem = {key: val for key, val in rem_table[0]} return rem
def _read_frequency_data(self): """ Parses frequencies, enthalpy, entropy, and mode vectors. """ temp_dict = read_pattern( self.text, { "frequencies": r"\s*Frequency:\s+([\d\-\.]+)(?:\s+([\d\-\.]+)(?:\s+([\d\-\.]+))*)*", "enthalpy": r"\s*Total Enthalpy:\s+([\d\-\.]+)\s+kcal/mol", "entropy": r"\s*Total Entropy:\s+([\d\-\.]+)\s+cal/mol\.K" }) if temp_dict.get('enthalpy') == None: self.data['enthalpy'] = [] else: self.data['enthalpy'] = float(temp_dict.get('enthalpy')[0][0]) if temp_dict.get('entropy') == None: self.data['entropy'] = None else: self.data['entropy'] = float(temp_dict.get('entropy')[0][0]) if temp_dict.get('frequencies') == None: self.data['frequencies'] = None else: temp_freqs = [ value for entry in temp_dict.get('frequencies') for value in entry ] freqs = np.zeros(len(temp_freqs) - temp_freqs.count('None')) for ii, entry in enumerate(temp_freqs): if entry != 'None': freqs[ii] = float(entry) self.data['frequencies'] = freqs header_pattern = r"\s*Raman Active:\s+[YESNO]+\s+(?:[YESNO]+\s+)*X\s+Y\s+Z\s+(?:X\s+Y\s+Z\s+)*" table_pattern = r"\s*[a-zA-Z][a-zA-Z\s]\s*([\d\-\.]+)\s*([\d\-\.]+)\s*([\d\-\.]+)\s*(?:([\d\-\.]+)\s*([\d\-\.]+)\s*([\d\-\.]+)\s*(?:([\d\-\.]+)\s*([\d\-\.]+)\s*([\d\-\.]+))*)*" footer_pattern = r"TransDip\s+[\d\-\.]+\s*[\d\-\.]+\s*[\d\-\.]+\s*(?:[\d\-\.]+\s*[\d\-\.]+\s*[\d\-\.]+\s*)*" temp_freq_mode_vecs = read_table_pattern( self.text, header_pattern, table_pattern, footer_pattern) freq_mode_vecs = np.zeros( shape=(len(freqs), len(temp_freq_mode_vecs[0]), 3)) for ii, triple_FMV in enumerate(temp_freq_mode_vecs): for jj, line in enumerate(triple_FMV): for kk, entry in enumerate(line): if entry != 'None': freq_mode_vecs[int(ii * 3 + math.floor(kk / 3)), jj, kk % 3] = float(entry) self.data["frequency_mode_vectors"] = freq_mode_vecs
def read_smx(string): header = r"^\s*\$smx" row = r"\s*([a-zA-Z\_]+)\s+(\S+)" footer = r"^\s*\$end" smx_table = read_table_pattern(string, header_pattern=header, row_pattern=row, footer_pattern=footer) if smx_table == []: print( "No valid smx inputs found. Note that there should be no '=' chracters in smx input lines." ) return {} else: smx = {key: val for key, val in smx_table[0]} return smx
def read_pcm(string): header = r"^\s*\$pcm" row = r"\s*([a-zA-Z\_]+)\s+(\S+)" footer = r"^\s*\$end" pcm_table = read_table_pattern(string, header_pattern=header, row_pattern=row, footer_pattern=footer) if pcm_table == []: print( "No valid PCM inputs found. Note that there should be no '=' chracters in PCM input lines." ) return {} else: pcm = {key: val for key, val in pcm_table[0]} return pcm
def _read_last_geometry(self): """ Parses the last geometry from an optimization trajectory for use in a new input file. """ header_pattern = r"\s+Optimization\sCycle:\s+" + \ str(len(self.data.get("energy_trajectory"))) + \ "\s+Coordinates \(Angstroms\)\s+ATOM\s+X\s+Y\s+Z" table_pattern = r"\s+\d+\s+\w+\s+([\d\-\.]+)\s+([\d\-\.]+)\s+([\d\-\.]+)" footer_pattern = r"\s+Point Group\:\s+[\d\w]+\s+Number of degrees of freedom\:\s+\d+" parsed_last_geometry = read_table_pattern( self.text, header_pattern, table_pattern, footer_pattern) if parsed_last_geometry == [] or None: self.data["last_geometry"] = [] else: self.data["last_geometry"] = process_parsed_coords( parsed_last_geometry[0]) if self.data.get('charge') != None: self.data["molecule_from_last_geometry"] = Molecule( species=self.data.get('species'), coords=self.data.get('last_geometry'), charge=self.data.get('charge'), spin_multiplicity=self.data.get('multiplicity'))
def _parse_calculation_summary(self): header_struct_energies = ( r"\s*=+\n\s+Structure Energies \(Hartree\)\s+\n\s*=+\n\s+Structure\s+Total\s+" + r"Separated\s+Gibbs \(298.15K\)\s*\n\s*=+") table_struct_energies = r"\s*([A-Za-z0-9_]+)\s+([\-\.0-9]+)\s+([\-\.0-9]+)\s+([\-\.0-9]+)" footer_struct_energies = r"\s*=+" temp_struct_energies = read_table_pattern(self.text, header_struct_energies, table_struct_energies, footer_struct_energies) if temp_struct_energies is None or len(temp_struct_energies) == 0: self.data["output"]["struct_energies"] = None else: self.data["output"]["struct_energies"] = dict() for row in temp_struct_energies[0]: struct_name = row[0] total_energy = float(row[1]) separated_energy = float(row[2]) gibbs = float(row[3]) self.data["output"]["struct_energies"][struct_name] = { "total": total_energy, "separated": separated_energy, "gibbs": gibbs } header_complex_energy = ( r"\s+Complex Energy \(kcal/mol\)\s+\n\s*\-+\n\s*Rxn\s+Er\s+Ep\s+Ets\s+Rxn E\s+Act E" + r"\s+frequency\s+Status of IRC\?\s+Path number\s+Approx TS") table_complex_energy = ( r"\s*([0-9]+)\s+([\-\.0-9]+)\s+([\-\.0-9]+)\s+([\-\.0-9]+)\s+([\-\.0-9]+)\s+" + r"([\-\.0-9]+)\s+([\-\.0-9]+)\s+(Failed|Passed)\s+([0-9]+)\s+(True|False)\s*" ) footer_complex_energy = r"\s*\-+" temp_complex_energies = read_table_pattern(self.text, header_complex_energy, table_complex_energy, footer_complex_energy) if temp_complex_energies is None or len(temp_complex_energies) == 0: self.data["output"]["complex_energies"] = None else: self.data["output"]["complex_energies"] = list() for row in temp_complex_energies[0]: if row[7] == "Failed": irc = False else: irc = True if row[9] == "False": approx_ts = False else: approx_ts = True self.data["output"]["complex_energies"].append({ "reactant": float(row[1]), "product": float(row[2]), "transition_state": float(row[3]), "rxn": float(row[4]), "activation": float(row[5]), "freq_mode": float(row[6]), "irc_succeeded": irc, "path_number": int(row[8]), "approx_ts": approx_ts }) header_sep_energy = ( r"\s+Inf\. Separated Energy \(kcal/mol\)\s+\n\s*\-+\n\s*Rxn\s+Er\s+Ep\s+Ets\s+Rxn E\s+" + r"Act E\s+frequency\s+Status of IRC\?\s+Path number\s+Approx TS") table_sep_energy = ( r"\s*([0-9]+)\s+([\-\.0-9]+)\s+([\-\.0-9]+)\s+([\-\.0-9]+)\s+([\-\.0-9]+)\s+" + r"([\-\.0-9]+)\s+([\-\.0-9]+)\s+(Failed|Passed)\s+([0-9]+)\s+(True|False)\s*" ) footer_sep_energy = r"\s*\-+" temp_sep_energies = read_table_pattern(self.text, header_sep_energy, table_sep_energy, footer_sep_energy) if temp_sep_energies is None or len(temp_sep_energies) == 0: self.data["output"]["separated_energies"] = None else: self.data["output"]["separated_energies"] = list() for row in temp_sep_energies[0]: if row[7] == "Failed": irc = False else: irc = True if row[9] == "False": approx_ts = False else: approx_ts = True self.data["output"]["separated_energies"].append({ "reactant": float(row[1]), "product": float(row[2]), "transition_state": float(row[3]), "rxn": float(row[4]), "activation": float(row[5]), "freq_mode": float(row[6]), "irc_succeeded": irc, "path_number": int(row[8]), "approx_ts": approx_ts }) header_gibbs_energy = ( r"\s+Gibbs Free Energy at 298\.15K and concentration of 1 mol/liter \(kcal/mol\)\s+\n" + r"\s*\-+\n\s*Rxn\s+Gr\s+Gp\s+Gts\s+Rxn G\s+Act G" + r"\s+frequency\s+Status of IRC\?\s+Path number\s+Approx TS") table_gibbs_energy = ( r"\s*([0-9]+)\s+([\-\.0-9]+)\s+([\-\.0-9]+)\s+([\-\.0-9]+)\s+([\-\.0-9]+)\s+" + r"([\-\.0-9]+)\s+([\-\.0-9]+)\s+(Failed|Passed)\s+([0-9]+)\s+(True|False)\s*" ) footer_gibbs_energy = r"\s*\-+" temp_gibbs_energies = read_table_pattern(self.text, header_gibbs_energy, table_gibbs_energy, footer_gibbs_energy) if temp_gibbs_energies is None or len(temp_gibbs_energies) == 0: self.data["output"]["gibbs_energies"] = None else: self.data["output"]["gibbs_energies"] = list() for row in temp_gibbs_energies[0]: if row[7] == "Failed": irc = False else: irc = True if row[9] == "False": approx_ts = False else: approx_ts = True self.data["output"]["gibbs_energies"].append({ "reactant": float(row[1]), "product": float(row[2]), "transition_state": float(row[3]), "rxn": float(row[4]), "activation": float(row[5]), "freq_mode": float(row[6]), "irc_succeeded": irc, "path_number": int(row[8]), "approx_ts": approx_ts }) header_temp_dep = ( r"\(Celsius\)\s+\(kcal/mol\)\s+\(kcal/mol\)\s+\(s\^\-1.*\)\s+\(s\^\-1.*\)\s+\(unitless\)" + r"\s+at t1/2 \(%\)\s+half life\s+\n\-+") table_temp_dep = ( r"\s*([0-9\.\-]+)\s+([0-9\+\-eE\.]+)\s+([0-9\+\-eE\.]+)\s+([0-9\+\-eE\.]+)\s+" + r"([0-9\+\-eE\.]+)\s+([0-9\+\-eE\.]+)\s+([0-9\+\-eE\.]+)\s+([A-Za-z0-9\.\-\+ ]+)" ) footer_temp_dep = r"" temp_temp_dep = read_table_pattern(self.text, header_temp_dep, table_temp_dep, footer_temp_dep) if temp_temp_dep is None or len(temp_temp_dep) == 0: self.data["output"]["temperature_dependence"] = None else: self.data["output"]["temperature_dependence"] = dict() for row in temp_temp_dep[0]: temp = float(row[0]) self.data["output"]["temperature_dependence"][temp] = { "gibbs_activation": float(row[1]), "gibbs_reaction": float(row[2]), "k_forwards": float(row[3]), "k_reverse": float(row[4]), "log10_keq": float(row[5]), "rct_remaining_t_half": float(row[6]), "half_life": row[7] } header_diagnostic = r"\s+AutoTS diagnostic report\s+\n\-+\n\s+property\s+value\s+diagnostic passes\n\-+" table_diagnostic = r"\s*((?:[A-za-z]+ ?)+)\s*([A-Za-z0-9\-,/ ]+)\s+(Yes|No)" footer_diagnostic = r"" temp_diagnostic = read_table_pattern(self.text, header_diagnostic, table_diagnostic, footer_diagnostic) if temp_diagnostic is None or len(temp_diagnostic) == 0: self.data["diagnostic"] = None else: self.data["diagnostic"] = dict() for row in temp_diagnostic[0]: if "Level of theory" in row[0]: continue if row[2].strip() == "Yes": passed = True else: passed = False self.data["diagnostic"][row[0].strip()] = { "value": row[1].strip(), "passed": passed }