def as_jmol(self, index, output='extent'): pse = PeriodicTable() coords = self.data.atomcoords[index] with open(output + '.xyz', 'w') as fout: fout.write('%d\n\n' % len(coords)) for atomno, coord in zip(self.data.atomnos, coords): fout.write('%s %f %f %f\n' % (tuple(pse.element[atomno]) + tuple(coord))) with open(output + '.spt', 'w') as fout: fout.write('load "%s.xyz"\n' % output) fout.write('draw POLYGON ') fout.write('8 ') origin = self.lower_limit diagonal = self.upper_limit - self.lower_limit vertices = [] for i in ((0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0), (0, 0, 1), (1, 0, 1), (1, 1, 1), (0, 1, 1)): vertex = origin + np.asarray(i) * diagonal vertices.append(vertex) fout.write('{%f %f %f} ' % tuple(vertex)) fout.write( '6 [0 1 2 3] [2 3 0 3] [0 4 5 3] [1 5 6 3] [2 6 7 3] [3 7 4 3] ' ) fout.write('mesh nofill\n') with open(output + '_vertices.xyz', 'w') as fout: fout.write('%d\n\n' % len(vertices)) for v in vertices: fout.write('C %f %f %f\n' % tuple(v))
def make_chemical_formula(d): periodic_obj = PeriodicTable() try: atom_dict = {} atomsymbols = [] for x in d["attributes"]["atomnos"]: atomsymbols.append(periodic_obj.element[x]) if x in atom_dict: atom_dict[x] += 1 else: atom_dict[x] = 1 atom_arr = [] for x in atom_dict: atom_arr.append({"atomno": x, "count": atom_dict[x]}) atom_arr.sort(key=lambda x: x["atomno"]) formula_dict = {} formula_str = "" for x in atom_arr: elem = periodic_obj.element[x["atomno"]] formula_dict[elem] = x["count"] formula_str = formula_str + elem + " " + str(x["count"]) + " " d["formula"] = formula_dict d["formula_string"] = formula_str[:-1] d["attributes"]["atomsymbols"] = atomsymbols massnos = [round(x) for x in d["attributes"]["atommasses"]] d["attributes"]["massnos"] = massnos except: pass
def __init__(self, ccdata, jobfilename=None, terse=False, *args, **kwargs): """Initialize the Writer object. This should be called by a subclass in its own __init__ method. Inputs: ccdata - An instance of ccData, parsed from a logfile. jobfilename - The filename of the parsed logfile. terse - Whether to print the terse version of the output file - currently limited to cjson/json formats """ self.ccdata = ccdata self.jobfilename = jobfilename self.terse = terse self.pt = PeriodicTable() # Open Babel isn't necessarily present. if has_openbabel: # Generate the Open Babel/Pybel representation of the molecule. # Used for calculating SMILES/InChI, formula, MW, etc. self.obmol, self.pbmol = self._make_openbabel_from_ccdata() self.bond_connectivities = self._make_bond_connectivity_from_openbabel(self.obmol) self._check_required_attributes()
def xyzfile(xyzfile, ccxyz=False): """Parse xyzfile to ccData or ccData_xyz object""" if not type(xyzfile) == str: print(xzyfile, "is not a xyzfilename") raise attributes = {} ptable = PeriodicTable() with open(xyzfile, 'r') as handle: lines = handle.readlines() charge, mult = _chargemult(lines[1]) geometry = [x.split() for x in lines[2:]] coordinates = [x[1:] for x in geometry] atomnos = [ptable.number[x[0]] for x in geometry] attributes['atomcoords'] = [np.array(coordinates)] attributes['atomnos'] = np.array(atomnos) attributes['natom'] = len(atomnos) elements = [pt.Element[x] for x in atomnos] attributes['atommasses'] = [pt.Mass[x] for x in elements] if ccxyz: # Custom ccData_xyz attributes elements = [x[0] for x in geometry] attributes['elements'] = elements attributes['comment'] = lines[1] attributes['filename'] = os.path.split(xyzfile.rstrip())[1] ccObject = ccData_xyz(attributes=attributes) else: ccObject = ccData(attributes=attributes) return ccObject
def extract_xyz_geometries(xyz_file): """Extract xyz geometries from files in QM9""" coordinates = list() atoms = list() # Open the file, then retrieve the first two lines for the properties included. # With natoms, read all useful lines to get the coordinates, splitting them between # atom nuclei on one list and xyz coordinates on the other. See qm9_readme for details n_atoms = int(xyz_file.readline()) xyz_file.readline() # Property line. Do not use. for line in xyz_file.readlines()[0:n_atoms]: line = line.split(b"\t") line = [elem.decode("utf-8") for elem in line] atoms.append(str(line[0])) coords = line[1:4] for j, word in enumerate(coords): if "*^" in word: # Some values are written as powers of 10: e.g. 1.999*^-6. values = re.split(r"\*\^", word) coords[j] = float(values[0]) * pow(10, int(values[1])) else: coords[j] = float(word) coordinates.append(coords) # Cleanup atoms list thanks to cclib PeriodicTable, convert them to atomic number periodic_table = PeriodicTable() elements_list = [periodic_table.number[atom] for atom in atoms] # Build the Molecule object and return it molecule = Molecule(coordinates, elements_list) return molecule
def makepyscf(data, charge=0, mult=1): """Create a Pyscf Molecule.""" _check_pyscf(_found_pyscf) mol = gto.Mole( atom=[ ["{}".format(data.atomnos[i]), data.atomcoords[-1][i]] for i in range(data.natom) ], unit="Angstrom", charge=charge, multiplicity=mult, ) inputattr = data.__dict__ pt = PeriodicTable() if "gbasis" in inputattr: basis = {} # object for internal PySCF format uatoms, uatoms_idx = np.unique( data.atomnos, return_index=True ) # find unique atoms for idx, i in enumerate(uatoms_idx): curr_atom_basis = data.gbasis[i] for jdx, j in enumerate(curr_atom_basis): curr_l = j[0] curr_e_prim = j[1] new_list = [l_sym2num["{}".format(curr_l)]] new_list += curr_e_prim if not "{}".format(pt.element[uatoms[idx]]) in basis: basis["{}".format(pt.element[uatoms[idx]])] = [new_list] else: basis["{}".format(pt.element[uatoms[idx]])].append(new_list) mol.basis = basis return mol
def build_footer(self): """ Builds the bottom part used for the Gaussian calculation. List of strings. """ footer = [] # Basis set is the same for all elements. No ECP either. # Remove duplicates, and convert to element name periodic_table = PeriodicTable() elements = [periodic_table.element[el] for el in list(set(self.molecule.elements_list))] elements = " ".join(elements) basisset = self.gaussian_args["basisset"] footer.append(elements + " 0") footer.append(basisset) footer.append("****") footer.append("") # footer.append("$NBO") # # NBO_FILES should be updated to something more useful # footer.append("FILE=NBO_FILES") # footer.append("PLOT") # footer.append("$END") logging.debug("Footer: \n %s", "\n".join(footer)) return footer
def testcorrect(self): """Is coreelectrons equal to what it should be?""" pt = PeriodicTable() ans = [] for x in self.data.atomnos: ans.append(self.coredict[pt.element[x]]) ans = numpy.array(ans, "i") self.assertArrayEquals(self.data.coreelectrons, ans)
def print_gzmat(self): """Print Gaussian Z-Matrix Format e.g. 0 3 C O 1 r2 C 1 r3 2 a3 Si 3 r4 1 a4 2 d4 ... Variables: r2= 1.1963 r3= 1.3054 a3= 179.97 r4= 1.8426 a4= 120.10 d4= 96.84 ... """ pt = PeriodicTable() print('#', self.filename, "\n") print(self.comment) print(self.comment, end='') for i in range(len(self.atomnos)): idx = str(i + 1) + " " if i >= 3: print(pt.element[self.atomnos[i]], "", self.connectivity[i] + 1, " r" + idx, self.angleconnectivity[i] + 1, " a" + idx, self.dihedralconnectivity[i] + 1, " d" + idx.rstrip()) elif i == 2: print(pt.element[self.atomnos[i]], "", self.connectivity[i] + 1, " r" + idx, self.angleconnectivity[i] + 1, " a" + idx.rstrip()) elif i == 1: print(pt.element[self.atomnos[i]], "", self.connectivity[i] + 1, " r" + idx.rstrip()) elif i == 0: print(pt.element[self.atomnos[i]]) print("Variables:") for i in range(1, len(self.atomnos)): idx = str(i + 1) + "=" if i >= 3: print("%s" % "r" + idx, "%6.4f" % self.distances[i]) print("%s" % "a" + idx, "%6.2f" % self.angles[i]) print("%s" % "d" + idx, "%6.2f" % self.dihedrals[i]) elif i == 2: print("%s" % "r" + idx, "%6.4f" % self.distances[i]) print("%s" % "a" + idx, "%6.2f" % self.angles[i]) elif i == 1: print("%s" % "r" + idx, "%6.4f" % self.distances[i])
def xyz_geometry(self): """Returns geometry in XYZ format""" periodic_table = PeriodicTable() xyz_geometry = [ " ".join([periodic_table.element[self.elements_list[i]].ljust(5)] + ["{:.6f}".format(s).rjust(25) for s in atom]) for i, atom in enumerate(self.coordinates) ] return xyz_geometry
def list_elements(input_file): """ Return a list of all unique elements used in the computation. The list will look like: ['C', 'H', 'N', 'P'] """ file = ccread(input_file) atoms = dict.fromkeys(file.atomnos.tolist()) periodic_table = PeriodicTable() atom_list = [periodic_table.element[i] for i in atoms] return atom_list
def atom_types(input_file): """ Return a list of all atom types in the right order. The list will look like: ['C', 'H', 'H', 'H', 'C', 'N', 'P'] """ file = ccread(input_file) atoms = file.atomnos.tolist() periodic_table = PeriodicTable() atom_list = [periodic_table.element[i] for i in atoms] return atom_list
def _makeatomnames(self): """Create unique names for the atoms""" pt = PeriodicTable() d = {} names = [] for atomno in self.atomnos: if atomno in d: d[atomno] += 1 else: d[atomno] = 1 names.append(pt.element[atomno] + str(d[atomno])) self.atomnames = names
def XYZ_data(d): periodic_obj = PeriodicTable() xyz_data = "" try: xyz_data += str(d["natom"]) + "\n\n" for atom_row in list(zip(d["atomnos"], d["atomcoords"][0])): elem = periodic_obj.element[atom_row[0]] coords_text = " ".join(list(map(str, atom_row[1]))) xyz_data += elem + " " + coords_text + "\n" except: xyz_data = "" return xyz_data
def makebiopython(atomcoords, atomnos): """Create a list of BioPython Atoms. This creates a list of BioPython Atoms suitable for use by Bio.PDB.Superimposer, for example. """ pt = PeriodicTable() bioatoms = [] for coords, atomno in zip(atomcoords, atomnos): symbol = pt.element[atomno] bioatoms.append( Atom(symbol, coords, 0, 0, 0, symbol, 0, symbol.upper())) return bioatoms
def makebiopython(atomcoords, atomnos): """Create a list of BioPython Atoms. This creates a list of BioPython Atoms suitable for use by Bio.PDB.Superimposer, for example. """ if not _found_biopython: raise ImportError("You must install `biopython` to use this function") pt = PeriodicTable() bioatoms = [] for coords, atomno in zip(atomcoords, atomnos): symbol = pt.element[atomno] bioatoms.append(Atom(symbol, coords, 0, 0, 0, symbol, 0, symbol.upper())) return bioatoms
def multixyzfile(multixyzfile): """Parse multixyzfile to list of ccData objects""" assert type(multixyzfile) == str attributeslist = [] ptable = PeriodicTable() # Check that the file is not empty, if it is not, parse away! if os.stat(multixyzfile).st_size == 0: raise EOFError(multixyzfile + " is empty") else: with open(multixyzfile, 'r') as handle: attributeslist = [] lines = handle.readlines() filelength = len(lines) idx = 0 while True: attributes = {} atomcoords = [] atomnos = [] # Get number of atoms and charge/mult from comment line numatoms = int(lines[idx]) charge, mult = _chargemult(lines[idx + 1]) for line in lines[idx + 2:numatoms + idx + 2]: atomgeometry = [x for x in line.split()] atomnos.append(ptable.number[atomgeometry[0]]) atomcoords.append([float(x) for x in atomgeometry[1:]]) idx = numatoms + idx + 2 attributes['charge'] = charge attributes['mult'] = mult attributes['atomcoords'] = [np.array(atomcoords)] attributes['atomnos'] = np.array(atomnos) attributeslist.append(attributes) # Break at EOF if idx >= filelength: break print('Number of conformers parsed:', len(attributeslist)) ccdatas = [ccData(attributes=attrs) for attrs in attributeslist] return ccdatas
def main(): pse = PeriodicTable() formula = {} log = ccopen(sys.argv[1]) data = log.parse() for atom in data.atomnos: formula[atom] = formula.setdefault(atom, 0) + 1 string = [] exclude = set() if 6 in formula: string.append('C%d' % formula[6]) exclude.add(6) if 1 in formula: string.append('H%d' % formula[1]) exclude.add(1) elements = set(formula.keys()) - exclude for element in sorted([pse.element[atom] for atom in elements]): string.append(element + str(formula[pse.number[element]])) print(' '.join(string))
def make_formula_string(elems, elem_counts=[]): periodic_obj = PeriodicTable() formula_arr = [] if type(elems) is dict: for x in elems: formula_arr.append({ "atomno": periodic_obj.number[x], "symbol": x, "count": elems[x] }) else: for (x, y) in zip(elems, elem_counts): formula_arr.append({ "atomno": periodic_obj.number[x], "symbol": x, "count": y }) formula_arr.sort(key=lambda x: x["atomno"]) formula_arr = [x["symbol"] + " " + str(x["count"]) for x in formula_arr] formula_string = " ".join(formula_arr) return formula_string
def makebiopython(atomcoords, atomnos): """Create a list of BioPython Atoms. This creates a list of BioPython Atoms suitable for use by Bio.PDB.Superimposer, for example. >>> import numpy >>> from Bio.PDB.Superimposer import Superimposer >>> atomnos = numpy.array([1,8,1],"i") >>> a = numpy.array([[-1,1,0],[0,0,0],[1,1,0]],"f") >>> b = numpy.array([[1.1,2,0],[1,1,0],[2,1,0]],"f") >>> si = Superimposer() >>> si.set_atoms(makebiopython(a,atomnos),makebiopython(b,atomnos)) >>> print si.rms 0.29337859596 """ pt = PeriodicTable() bioatoms = [] for coords, atomno in zip(atomcoords, atomnos): bioatoms.append(Atom(pt.element[atomno], coords, 0, 0, 0, 0, 0)) return bioatoms
def stoichiometry(self): """Return the stoichemistry of the object according to the Hill system""" cclib_pt = PeriodicTable() elements = [cclib_pt.element[ano] for ano in self.data.atomnos] counts = {el: elements.count(el) for el in set(elements)} formula = "" elcount = lambda el, c: "%s%i" % (el, c) if c > 1 else el if 'C' in elements: formula += elcount('C', counts['C']) counts.pop('C') if 'H' in elements: formula += elcount('H', counts['H']) counts.pop('H') for el, c in sorted(counts.items()): formula += elcount(el, c) if getattr(self.data, 'charge', 0): magnitude = abs(self.data.charge) sign = "+" if self.data.charge > 0 else "-" formula += "(%s%i)" % (sign, magnitude) return formula
def __init__(self, attributes={}): """Adding some new attributes for xyzfiles""" self.newcoords = None self.distancematrix = None # Internal Coordinate Connectivity self.connectivity = None self.angleconnectivity = None self.dihedralconnectivity = None # Internal Coordinates self.distances = None self.angles = None self.dihedrals = None self._attrtypes['comment'] = str self._attrlist.append('comment') self._attrtypes['filename'] = str self._attrlist.append('filename') self._attrtypes['elements'] = list self._attrlist.append('elements') #self._attrtypes['distancematrix'] = np.ndarray #self._attrlist.append('distancematrix') #self._attrtypes['connectivity'] = list #self._attrlist.append('connectivity') super(ccData_xyz, self).__init__(attributes=attributes) # Initialize new data types if attributes were parsed as an original ccdata_xyz if not hasattr(self, 'elements'): pt = PeriodicTable() self.comment = '\n' self.filename = '' self.elements = [] for atomno in self.atomnos: self.elements.append(pt.element[atomno])
def makepyscf(data, charge=0, mult=1): """Create a Pyscf Molecule.""" _check_pyscf(_found_pyscf) inputattrs = data.__dict__ required_attrs = {"atomcoords", "atomnos"} missing = [x for x in required_attrs if not hasattr(data, x)] if missing: missing = " ".join(missing) raise MissingAttributeError( "Could not create pyscf molecule due to missing attribute: {}". format(missing)) mol = gto.Mole( atom=[["{}".format(data.atomnos[i]), data.atomcoords[-1][i]] for i in range(data.natom)], unit="Angstrom", charge=charge, multiplicity=mult) inputattr = data.__dict__ pt = PeriodicTable() if "gbasis" in inputattr: basis = {} # object for internal PySCF format uatoms, uatoms_idx = np.unique(data.atomnos, return_index=True) # find unique atoms for idx, i in enumerate(uatoms_idx): curr_atom_basis = data.gbasis[i] for jdx, j in enumerate(curr_atom_basis): curr_l = j[0] curr_e_prim = j[1] new_list = [l_sym2num["{}".format(curr_l)]] new_list += curr_e_prim if not "{}".format(pt.element[uatoms[idx]]) in basis: basis["{}".format(pt.element[uatoms[idx]])] = [new_list] else: basis["{}".format( pt.element[uatoms[idx]])].append(new_list) mol.basis = basis mol.cart = True return mol
def __init__(self, ccdata, jobfilename=None, *args, **kwargs): """Initialize the Writer object. This should be called by a subclass in its own __init__ method. Inputs: ccdata - An instance of ccData, parsed from a logfile. jobfilename - The filename of the parsed logfile. """ self.ccdata = ccdata self.jobfilename = jobfilename self.pt = PeriodicTable() self.elements = [self.pt.element[Z] for Z in self.ccdata.atomnos] # Open Babel isn't necessarily present. if has_openbabel: # Generate the Open Babel/Pybel representation of the molecule. # Used for calculating SMILES/InChI, formula, MW, etc. self.obmol, self.pbmol = self._make_openbabel_from_ccdata() self.bond_connectivities = self._make_bond_connectivity_from_openbabel(self.obmol)
def get_chromophore_info(calcdir, basis="None"): """ calcdir: directory the output file resides in """ outfile = glob.glob("{}/*.out".format(calcdir))[0] results = QChem(outfile).parse() n_states = len(results.etenergies) if False in results.etconv: print("Not converged!") # Reads in the TXT files with the transition density matrix ptr_files = sorted(glob.glob("{}/*.txt".format(calcdir)))[:n_states] ptr = [] periodicT = PeriodicTable() for ptr_f in ptr_files: ptr_mat = np.loadtxt(ptr_f) assert ptr_mat.shape[0] == ptr_mat.shape[1] assert ptr_mat.ndim == 2 ptr.append(ptr_mat) chrom = Chromophore(n_states, basis, results.atomcoords[0], [periodicT.element[x] for x in results.atomnos], results.charge, convertor(results.etenergies, "eV", "hartree"), results.etoscs, ptr, results.ettransdipmoms) return chrom
def __init__(self, source, *args, **kwargs): super().__init__(source, *args, **kwargs) self.pt = PeriodicTable()
#!/usr/bin/env python # -*- coding: utf-8 -*- # Stdlib from __future__ import division, print_function import os from cclib.parser.utils import convertor, PeriodicTable PERIODIC_TABLE = PeriodicTable() def new_filename(path): i = 0 name, ext = os.path.splitext(path) while os.path.isfile(path): i += 1 path = '{}_{}{}'.format(name, i, ext) return path
""" s = '{:3s} {:15.10f} {:15.10f} {:15.10f}' molecule_section = ['{} {}'.format(charge, multiplicity)] for element, coords, in zip(elements, geometry): molecule_section.append(s.format(element, *coords)) return molecule_section if __name__ == '__main__': args = getargs() pt = PeriodicTable() for outputfilename in args.outputfilename: job = cclib.io.ccopen(outputfilename) assert isinstance(job, cclib.parser.qchemparser.QChem) try: data = job.parse() # this is to deal with the Q-Chem parser not handling # incomplete SCF cycles properly except StopIteration: print('no output made: StopIteration in {}'.format(outputfilename)) continue # Determine the name of the file we're writing. assert outputfilename.endswith('.out')
def parse(self, **kwargs): """ It uses cclib to get the output dictionary. Herein, we report all parsed data by ccli in output_dict which can be parsed further at workchain level. If it would be an optimization run, the relaxed structure also will be stored under relaxed_structure key. """ opt_run = False try: out_folder = self.retrieved except NotExistent: return self.exit_codes.ERROR_NO_RETRIEVED_FOLDER fname_out = self.node.process_class._OUTPUT_FILE #pylint: disable=protected-access fname_relaxed = self.node.process_class._RELAX_COORDS_FILE #pylint: disable=protected-access # fname_traj = self.node.process_class._TRAJECTORY_FILE #pylint: disable=protected-access # fname_hessian = self.node.process_class._HESSIAN_FILE #pylint: disable=protected-access if fname_out not in out_folder._repository.list_object_names(): #pylint: disable=protected-access raise OutputParsingError('Orca output file not retrieved') parsed_obj = io.ccread( os.path.join(out_folder._repository._get_base_folder().abspath, fname_out)) #pylint: disable=protected-access parsed_dict = parsed_obj.getattributes() def _remove_nan(parsed_dictionary: dict) -> dict: """cclib parsed object may contain nan values in ndarray. It will results in an exception in aiida-core which comes from json serialization and thereofore dictionary cannot be stored. This removes nan values to remedy this issue. See: https://github.com/aiidateam/aiida-core/issues/2412 https://github.com/aiidateam/aiida-core/issues/3450 Args: parsed_dictionary (dict): Parsed dictionary from `cclib` Returns: dict: Parsed dictionary without `NaN` """ for key, value in parsed_dictionary.items(): if isinstance(value, np.ndarray): non_nan_value = np.nan_to_num(value, nan=123456789, posinf=2e308, neginf=-2e308) parsed_dictionary.update({key: non_nan_value}) return parsed_dictionary output_dict = _remove_nan(parsed_dict) keywords = output_dict['metadata']['keywords'] #opt_pattern = re.compile('(GDIIS-)?[CZ?OPT]', re.IGNORECASE) #if any(re.match(opt_pattern, keyword) for keyword in keywords): #opt_run = True opt_run = False for keyword in keywords: if 'opt' in keyword.lower(): opt_run = True if opt_run: relaxed_structure = StructureData( pymatgen_molecule=Molecule.from_file( os.path.join( out_folder._repository._get_base_folder().abspath, fname_relaxed)) #pylint: disable=protected-access ) # relaxation_trajectory = SinglefileData( # file=os.path.join(out_folder._repository._get_base_folder().abspath, fname_traj) #pylint: disable=protected-access # ) self.out('relaxed_structure', relaxed_structure) # self.out('relaxation_trajectory', relaxation_trajectory) pt = PeriodicTable() #pylint: disable=invalid-name output_dict['elements'] = [ pt.element[Z] for Z in output_dict['atomnos'].tolist() ] self.out('output_parameters', Dict(dict=output_dict)) return ExitCode(0)
def json2latex2pdf(json, mode="clean"): # on construit le nom du rapport #rapFile = "_TEX_report" name = json["comp_details"]["general"]["job_type"][0] rapFile = (name + "_report") ##################################################################### # # ## production du rapport en .tex # # # ##################################################################### ### create document and import needed packages doc = (Document("article")) # packages doc.packages.append(Package('datetime')) doc.packages.append(NoEscape(r'\usepackage[margin=2cm]{geometry}')) doc.packages.append(NoEscape(r'\usepackage{extramarks}')) doc.packages.append(NoEscape(r'\usepackage{fancyhdr}')) doc.packages.append(NoEscape(r'\usepackage{svg}')) doc.packages.append(NoEscape(r'\usepackage[utf8]{inputenc}')) doc.packages.append(NoEscape(r'\usepackage{titlesec}')) # doc.packages.append(NoEscape(r'\pagestyle{fancy}')) doc.packages.append(NoEscape(r'\fancyhf{}')) # define header / footer texts doc.packages.append(NoEscape(r'\lhead{MOLECULAR CALCULATION REPORT}')) doc.packages.append(NoEscape(r'\rhead{Generated by QuChemReport}')) doc.packages.append(NoEscape(r'\lfoot{\today ~ \currenttime}')) doc.packages.append(NoEscape(r'\rfoot{Page \thepage}')) doc.packages.append(NoEscape(r'\cfoot{}')) # redefine rules for header / footer doc.packages.append(NoEscape(r'\renewcommand\headrulewidth{0.4pt}')) doc.packages.append(NoEscape(r'\renewcommand\footrulewidth{0.4pt}')) # redefine section doc.packages.append(NoEscape(r'\definecolor{ufrblue}{RGB}{0,161,140}')) doc.packages.append(NoEscape(r'\definecolor{bordeau}{RGB}{125,31,31}')) doc.packages.append( NoEscape( r'\titleformat{name=\section}[block]{\sc\large}{}{0pt}{\colorsection}' )) doc.packages.append( NoEscape(r'\titlespacing*{\section}{0pt}{\baselineskip}{5pt}')) doc.packages.append( NoEscape(r'\titlespacing{\subsection}{4pt}{\baselineskip}{5pt}')) doc.packages.append( NoEscape( r'\newcommand{\colorsection}[1]{' + r'\colorbox{ufrblue}{\parbox{\dimexpr\textwidth-2\fboxsep}{\textcolor{white}{' + r'\textbf{{\thesection.\ #1}}}}}}')) ### section 1 : authorship TODO with the DB ! ######################################### # with doc.create(Section('AUTHORSHIP')): # with doc.create(LongTabu("X[1,l] X[2,l]")) as tab: # tab.add_row(['Original file', json["authorship"]["log_file"]]) # tab.add_row(['Primary author', json["authorship"]["primary_author"]]) # add_row_filter(tab, ['ORCID', json["authorship"]["primary_author_orcid"]]) # add_row_filter(tab, ['Affiliation', json["authorship"]["primary_author_affiliation"]]) # add_row_filter(tab, ['Publication DOI', json["authorship"]["publication_DOI"]]) ### section 2 : molecule with doc.create(Section('MOLECULE')): taillePng = "6cm" nomPng = "img-TOPOLOGY.png" if (not os.path.isfile("img-TOPOLOGY.png")): print("No PNG named " + nomPng + " found. STOP.\n") sys.exit() # figure with Chemical structure diagram doc.append(NoEscape(r'\begin{figure}[h]')) doc.append(NoEscape(r'\begin{center}')) doc.append( NoEscape(r'\includegraphics[width=' + taillePng + ']{' + nomPng + '}')) doc.append(NoEscape(r'\end{center}')) doc.append(NoEscape(r'\vspace{-5mm}')) doc.append( NoEscape( r'\caption*{Chemical structure diagram with atomic numbering.}' )) doc.append(NoEscape(r'\end{figure}')) with doc.create(LongTabu("X[1,l] X[2,l]")) as mol_table: inchi = (json["molecule"]["inchi"])[0].rstrip().split("=")[-1] mol_table.add_row(['InChI', inchi]) mol_table.add_row(['SMILES', json["molecule"]["smi"]]) mol_table.add_row([ 'Monoisotopic mass', "%.5f Da" % json["molecule"]["monoisotopic_mass"] ]) mol_table.add_row(['Formula', json["molecule"]["formula"]]) mol_table.add_row(['Charge', json["molecule"]["charge"]]) mol_table.add_row( ['Spin multiplicity', json["molecule"]["multiplicity"]]) ### section 2 : computational details ######################################### software = json["comp_details"]["general"]["package"] with doc.create(Section('COMPUTATIONAL DETAILS')): #with doc.create(Subsection('General parameters')) : with doc.create(LongTabu("X[1,l] X[1,l]")) as param_table: try: param_table.add_row([ 'Software', json["comp_details"]["general"]["package"] + ' (' + json["comp_details"]["general"]["package_version"] + ')' ]) except KeyError: pass param_table.add_row([ 'Computational method', json["comp_details"]["general"]["last_theory"] ]) add_row_filter( param_table, ['Functional', json["comp_details"]["general"]["functional"]]) try: add_row_filter(param_table, [ 'Basis set name', json["comp_details"]["general"]["basis_set_name"] ]) except KeyError: pass param_table.add_row([ 'Number of basis set functions', json["comp_details"]["general"]["basis_set_size"] ]) param_table.add_row([ 'Closed shell calculation', json["comp_details"]["general"]["is_closed_shell"] ]) add_row_filter(param_table, [ 'Integration grid', json["comp_details"]["general"]["integration_grid"] ]) add_row_filter( param_table, ['Solvent', json["comp_details"]["general"]["solvent"]]) # TODO Test if solvent = gas in this case no Solvent reaction filed method # add_row_filter(param_table, ['Solvent reaction filed method', # json["comp_details"]["general"]["solvent_reaction_field"]]) scfTargets = json["comp_details"]["general"]["scf_targets"][-1] if software == "Gaussian": # Gaussian or GAUSSIAN (upper/lower? param_table.add_row([ "Requested SCF convergence on RMS density matrix", scfTargets[0] ]) param_table.add_row([ "Requested SCF convergence on MAX density matrix", scfTargets[1] ]) param_table.add_row( ["Requested SCF convergence on energy", scfTargets[2]]) if software == "GAMESS": param_table.add_row( ["Requested SCF convergence on density", scfTargets[0]]) #with doc.create(Subsection('Thermochemistry and normal modes calculation parameters')) : ## TODO tester si freq try: add_row_filter(param_table, [ 'Temperature', "%.2f K" % json["comp_details"]["freq"]["temperature"] ]) except: pass T_len = False try: len(json["comp_details"]["freq"]["temperature"]) except KeyError: json["comp_details"]["freq"]["temperature"] = [] except TypeError: T_len = True if T_len is True: try: add_row_filter(param_table, [ 'Anharmonic effects', json["comp_details"]["freq"]["anharmonicity"] ]) except KeyError: pass if (json["comp_details"]["freq"]["temperature"]) != []: try: add_row_filter(param_table, [ 'Anharmonic effects', json["comp_details"]["freq"]["anharmonicity"] ]) except KeyError: pass #with doc.create(Subsection('Excited states calculation parameters')) : try: add_row_filter(param_table, [ 'Number of excited states', json["comp_details"]["excited_states"]["nb_et_states"] ]) except KeyError: pass ### section 3 : results ######################################### with doc.create(Section('RESULTS')): with doc.create(Subsection('Wavefunction')): with doc.create(LongTabu("X[l] X[l]")) as wavef_table: wavef_table.add_row([ 'Total molecular energy', "%.5f Hartrees" % json["results"]["wavefunction"]["total_molecular_energy"] ]) homo_indexes = [ i + 1 for i in json["results"]["wavefunction"]["homo_indexes"] ] wavef_table.add_row([ 'H**O number', ("%s" % homo_indexes)[1:-1] ]) # indice begins at 0, remove brackets MO_energies = json["results"]["wavefunction"]["MO_energies"][-1] homo_nb = json["results"]["wavefunction"]["homo_indexes"][-1] doc.append(NoEscape(r'\begin{center}')) with doc.create(Table()) as motab: doc.append(NoEscape(r'\centering')) with doc.create(Tabular('p{1cm}ccccp{1cm}')) as mo_table: row_cells = MultiColumn( 6, align='c', data= 'Calculated energies for the frontier molecular orbitals (in eV)' ) mo_table.add_row([row_cells]) mo_table.add_row( ['', 'H**O-1', 'H**O', 'LUMO', 'LUMO+1', '']) mo_table.add_hline() mo_table.add_row([ '', "%.2f" % MO_energies[homo_nb - 1], "%.2f" % MO_energies[homo_nb], "%.2f" % MO_energies[homo_nb + 1], "%.2f" % MO_energies[homo_nb + 2], '' ]) doc.append(NoEscape(r'\end{center}')) # figure with MO #### TODO : test autosize try: if len(json["results"]["excited_states"]['MO_png_list']) > 0: figure_MO_table( doc, ["LU1", "LUMO", "H**O", "HO1"], json["results"]["excited_states"]['MO_png_list'], caption= ("Representation of the frontier molecular orbitals with a cutoff " "value of 0.05. From up to bottom: LUMO+1, LUMO, H**O, H**O-1." )) except: pass # figure skel for Atom numbering scheme try: figure_two_col(doc, "skel", json["results"]["geometry"]['skel_png_list'], caption="Atom numbering scheme.", pos="h") except: pass # Mulliken partial charges table try: mulliken = json["results"]["wavefunction"][ "Mulliken_partial_charges"] except KeyError: mulliken = [] if len(mulliken) != 0: mulliken = np.array(mulliken) mean_m = np.mean(mulliken) dev_m = np.std(mulliken) thres_max = mean_m + dev_m thres_min = mean_m - dev_m doc.append(NoEscape(r'\begin{center}')) ind = np.argsort(mulliken) with doc.create(Tabular('p{0.5cm}rrrp{0.5cm}')) as tableau: row_cells = [ MultiColumn( 5, align='c', data='Most intense Mulliken atomic charges') ] tableau.add_row(row_cells) row_cells = [ MultiColumn(5, align='c', data='mean = %.3f e, std = %.3f' % (mean_m, dev_m)) ] tableau.add_row(row_cells) tableau.add_row( ["", "Atom", "number", "Mulliken charges", ""]) tableau.add_hline() for ielt in ind: if (mulliken[ielt] > thres_max) or (mulliken[ielt] < thres_min): tableau.add_row([ "", PeriodicTable().element[json['molecule'] ["atoms_Z"][ielt]], (1 + ielt), "%.3f" % mulliken[ielt], "" ]) tableau.add_hline() doc.append(NoEscape(r'\end{center}')) # figure MEP try: if len(json["results"]["wavefunction"]['MEP_png_list']) > 0: figure_two_col( doc, "MEP", json["results"]["wavefunction"]['MEP_png_list'], caption= ("Representation of the molecular electrostatic potential " "at the distance of the van der Waals radii of the atoms. " "The red/blue regions depict the most negative (-0.1) / " "positive (+0.1) regions."), pos="h") except: pass with doc.create(Subsection('Geometry')): ######## PB atomic = json["results"]["geometry"][ "elements_3D_coords_converged"] oxtrf, oytrf, oxtrd, oytrd = ('N/A', 'N/A', 'N/A', 'N/A') ### TODO # if singlePoint==1 : # doc.append('This calculation is a single point ') try: is_opt = json["results"]["geometry"]["OPT_DONE"] == True except KeyError: is_opt = [] if is_opt: doc.append( NoEscape( r"This calculation is the result of a geometry optimization process." )) else: doc.append( NoEscape( r"\textcolor{bordeau}{Warning : this calculation does not include a geometry " "optimization process. This geometry may not be a stationary " "point for those computational parameters. In this case, results must be " "used with caution.}")) if is_opt: ### TODO tests gaussian doc.append(NoEscape(r'\begin{center}')) geomTargets = json["comp_details"]["geometry"][ "geometric_targets"] geomValues = json["results"]["geometry"]["geometric_values"][ -1] with doc.create(Tabular('p{0cm}lrrp{0cm}')) as param_table: row_cells = [ MultiColumn( 5, align='c', data='Geometry optimization convergence criteria') ] param_table.add_row(row_cells) param_table.add_row(["", "", "Value", "Threshold", ""]) param_table.add_hline() if software == "Gaussian": param_table.add_row([ "", "Maximum Force", "%.6f" % geomValues[0], "%.6f" % geomTargets[0], "" ]) param_table.add_row([ "", "RMS Force", "%.6f" % geomValues[1], "%.6f" % geomTargets[1], "" ]) param_table.add_row([ "", "Maximum Displacement", "%.6f" % geomValues[2], "%.6f" % geomTargets[2], "" ]) param_table.add_row([ "", "RMS Displacement", "%.6f" % geomValues[3], "%.6f" % geomTargets[3], "" ]) if software == "GAMESS": # in Hartrees per Bohr param_table.add_row([ "", "Maximum Force", "%.5f" % geomValues[0], "%.5f" % geomTargets[0], "" ]) param_table.add_row([ "", "RMS Force", "%.5f" % geomValues[1], "%.5f" % geomTargets[1], "" ]) param_table.add_hline() doc.append(NoEscape(r'\end{center}')) with doc.create(LongTabu("X[l] X[l]")) as geom_table: geom_table.add_row([ 'Nuclear repulsion energy', "%.5f Hartrees" % json["results"]["geometry"] ["nuclear_repulsion_energy_from_xyz"] ]) doc.append(NoEscape(r'\begin{center}')) with doc.create(Tabular('p{0.5cm}rrrrp{0.5cm}')) as tableau: row_cells = [ MultiColumn( 6, align='c', data='Cartesian atomic coordinates in Angstroms') ] tableau.add_row(row_cells) tableau.add_row([ "", "Atom", MultiColumn(1, align='c', data='X'), MultiColumn(1, align='c', data='Y'), MultiColumn(1, align='c', data='Z'), "" ]) tableau.add_hline() atoms = np.array(json["results"]["geometry"] ["elements_3D_coords_converged"]).reshape( (-1, 3)) for i, a in enumerate(atoms): tableau.add_row([ "", PeriodicTable().element[json['molecule']["atoms_Z"] [i]], "%.4f" % a[0], "%.4f" % a[1], "%.4f" % a[2], "" ]) tableau.add_hline() doc.append(NoEscape(r'\end{center}')) ### Freq #### TODO : tests with doc.create(Subsection('Thermochemistry and normal modes')): rtemper = json["comp_details"]["freq"]["temperature"] # ND-arrays try: vibrational_int = np.array( json["results"]["freq"]["vibrational_int"]) except KeyError: vibrational_int = [] try: vibrational_freq = np.array( json["results"]["freq"]["vibrational_freq"]) except KeyError: vibrational_freq = [] try: vibrational_sym = np.array( json["results"]["freq"]["vibrational_sym"]) except KeyError: vibrational_sym = [] # filtering & orderering if len(vibrational_int) == 0: vibrational_int = [] else: vib_filter = vibrational_int > 50. vib_order = np.argsort(vibrational_freq[vib_filter])[::-1] vibrational_int = vibrational_int[vib_filter][vib_order] vibrational_freq = vibrational_freq[vib_filter][vib_order] vibrational_sym = vibrational_sym[vib_filter][vib_order] if (len(vibrational_int) != 0) and (rtemper != "N/A"): with doc.create(LongTabu("X[l] X[l]")) as thermo_table: thermo_table.add_row([ 'Sum of electronic and zero-point energy in Hartrees', json["results"]["freq"]["zero_point_energy"] ]) thermo_table.add_row([ "Sum of electronic and thermal at " "%f K energies in atomic units" % rtemper, json["results"]["freq"]["electronic_thermal_energy"] ]) thermo_table.add_row([ "Entropy at %f K in atomic units" % rtemper, json["results"]["freq"]["entropy"] ]) thermo_table.add_row([ "Enthalpy at %f K in atomic units" % rtemper, json["results"]["freq"]["enthalpy"] ]) thermo_table.add_row([ "Gibbs free energy at %f K in atomic units" % rtemper, json["results"]["freq"]["free_energy"] ]) doc.append( "Table of the most intense molecular vibrations (> 20 km/mol) (%d)\n" % len(vibrational_int)) with doc.create(Tabular('rrrc')) as tableau: tableau.add_row( ["", "Frequencies", "Intensity", "Symmetry"]) for i in range(len(vibrational_freq)): tableau.add_row([ "", "%.4f" % vibrational_freq[i], "%.4f" % vibrational_int[i], vibrational_sym[i] ]) else: doc.append( NoEscape( r"\textcolor{bordeau}{Warning : force constants and the " "resulting vibrational frequencies were not computed.}" )) ### Excited states try: et_energies = json["results"]["excited_states"]["et_energies"] except KeyError: et_energies = [] rnbExci = len(et_energies) if rnbExci != 0 and et_energies != 'N/A': with doc.create(Subsection('Excited states')): doc.append(NoEscape(r'\begin{center}')) with doc.create(Tabular('p{0cm}rrrrrrp{0cm}')) as tableau: row_cells = [ MultiColumn( 8, align='c', data="Calculated mono-electronic excitations") ] tableau.add_row(row_cells) tableau.add_row([ "", "Number", "Energy", "Symmetry", "Oscillator strength", "Rotatory strength", "Transitions", "" ]) tableau.add_hline() for i in range(rnbExci): etr_i = (json["results"]["excited_states"]["et_rot"][i] if json["results"]["excited_states"]["et_rot"] != 'N/A' else 'N/A') tableau.add_row([ "", (1 + i), "%.2f" % et_energies[i], json["results"]["excited_states"]["et_sym"][i], "%.6f" % json["results"]["excited_states"]["et_oscs"][i], etr_i, len(json["results"]["excited_states"] ["et_transitions"][i]), "" ]) tableau.add_hline() doc.append(NoEscape(r'\end{center}')) ##################################################################### # # ## 3. compilation LaTeX # # # ##################################################################### # on compile # on previent si le fichier PDF est present ou pas # par defaut dans pylatex tout ce qui concerne latex est efface ### compile and output # doc.generate_pdf(rapFile, clean_tex=True) # comportement en routine, avec False en mode dev/debug # OK sans SVG doc.generate_pdf(rapFile, clean_tex=False) # pas de chance !! doc.generate_pdf(rapFile, clean_tex=False,compiler="pdflatex --shell-escape") texFile = rapFile + ".tex" if (os.path.isfile(texFile)): os.remove(texFile) pdfFile = rapFile + ".pdf" if (os.path.isfile(pdfFile)): os.remove(pdfFile) doc.generate_tex(rapFile) cmd = "pdflatex --shell-escape " + rapFile # si cleanMode = "--clean" on redirige vers /dev/null if mode == "clean": cmd += " > /dev/null" # compilation LaTeX os.system(cmd) # un peu de nettoyage si cleanMode = "--clean" if mode == "clean": if os.path.isfile(rapFile + '.aux'): os.remove(rapFile + '.aux') if os.path.isfile(rapFile + '.log'): os.remove(rapFile + '.log') if os.path.isfile(rapFile + '.tex'): os.remove(rapFile + '.tex') # message de fin if (os.path.isfile(pdfFile)): print('Report file is ' + rapFile + ".pdf") else: print('Probably a LateX error.')