Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatic writing of coordinates to si files #67

Merged
merged 8 commits into from
Dec 10, 2023
2 changes: 1 addition & 1 deletion TCutility/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ def ensure_2d(x, transposed=False):
return x


from TCutility import analysis, results, constants, log, molecule # noqa: F401, E402
from TCutility import analysis, results, constants, log, molecule, formula # noqa: F401, E402
43 changes: 43 additions & 0 deletions TCutility/formula.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
def molecule(molstring: str, mode: str = 'html') -> str:
'''
Parse and return a string containing a molecular formula that will show up properly in LaTeX or HTML.

Args:
molstring: the string that contains the molecular formula to be parsed. It can be either single molecule or a reaction. Molecules should be separated by '+' or '->'.
mode: the formatter to convert the string to. Should be 'html' or 'latex'.

Returns:
A string that is formatted to be rendered nicely in either HTML or LaTeX.
'''
# to take care of plus-signs used to denote reactions we have to first split
# the molstring into its parts.
for part in molstring.split():
# if part is only a plus-sign we skip this part. This is only true when the plus-sign
# is used to denote a reaction
if part in ['+', '->']:
continue

# parse the part
partret = part
# numbers should be subscript
for num in '0123456789':
if mode == 'latex':
partret = partret.replace(num, f'_{num}')
if mode == 'html':
partret = partret.replace(num, f'<sub>{num}</sub>')

# signs should be superscript
for sign in '+-':
# negative charges should be denoted by em dash and not a normal dash
if mode == 'latex':
partret = partret.replace(sign, f'^{sign.replace("-", "—")}')
if mode == 'html':
partret = partret.replace(sign, f'<sup>{sign.replace("-", "—")}</sup>')
# replace the part in the original string
molstring = molstring.replace(part, partret)

return molstring


if __name__ == '__main__':
print(molecule('F- + CH3Cl', 'html'))
77 changes: 77 additions & 0 deletions TCutility/report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from TCutility import results
import docx
from htmldocx import HtmlToDocx


class SI:
def __init__(self, path: str):
'''
Class for creating supporting information (SI) files in Microsoft Word.

Args:
path: the location of the Word file. Does not have to have a file-extension.
append_mode: whether to append to or overwrite the file.
'''

self.path = path.removesuffix('.docx') + '.docx'
self.doc = docx.Document()

# set the font to Calibri
self.doc.styles['Normal'].font.name = 'Calibri'

def __enter__(self):
return self

def __exit__(self, *args, **kwargs):
self.doc.save(self.path)

def add_xyz(self, obj: str or dict, title: str):
'''
Add the coordinates and information about a calculation to the SI.
It will add the electronic bond energy, Gibb's free energy, enthalpy and imaginary mode, as well as the coordinates of the molecule.

Args:
obj: a string specifying a calculation directory or a `TCutility.results.Result` object from a calculation.
title: title to be written before the coordinates and information.
'''
if isinstance(obj, str):
obj = results.read(obj)

# title is always bold
s = f'<b>{title}</b><br>'

parser = HtmlToDocx()

# add electronic energy. E should be bold and italics. Unit will be kcal mol^-1
E = str(round(obj.properties.energy.bond, 1)).replace('-', '—')
s += f'<b><i>E</i></b> = {E} kcal mol<sup>—1</sup><br>'

# add Gibbs and enthalpy if we have them
if obj.properties.energy.gibbs:
G = str(round(obj.properties.energy.gibbs, 1)).replace('-', '—')
s += f'<b><i>G</i></b> = {G} kcal mol<sup>—1</sup><br>'
if obj.properties.energy.enthalpy:
H = str(round(obj.properties.energy.enthalpy, 1)).replace('-', '—')
s += f'<b><i>H</i></b> = {H} kcal mol<sup>—1</sup><br>'

# add imaginary frequency if we have one
if obj.properties.vibrations:
if obj.properties.vibrations.number_of_imag_modes == 1:
freq = abs(round(obj.properties.vibrations.frequencies[0]))
s += f'<b><i>ν<sub>imag</sub></i></b> = {freq}<i>i</i> cm<sup>—1</sup>'

# remove trailing line breaks
s = s.removesuffix('<br>')

# coords should be written in mono-type font with 8 decimals and 4 spaces between each coordinate
s += '<pre>'
for atom in obj.molecule.output:
s += f'{atom.symbol:2} {atom.coords[0]: .8f} {atom.coords[1]: .8f} {atom.coords[2]: .8f}<br>'
s += '</pre>'
parser.add_html_to_document(s, self.doc)

def add_heading(self, text: str, level: int = 1):
'''
Add a heading to the file. This method has the same arguments and functionality as docx.Document.add_heading.
'''
self.doc.add_heading(text, level)
10 changes: 10 additions & 0 deletions examples/write_SI.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from TCutility import report, formula
import os

j = os.path.join

pwd = os.path.split(__file__)[0]
with report.SI('test.docx') as si:
si.add_heading('Test molecules:')
si.add_xyz(j(pwd, '..', 'test', 'fixtures', 'level_of_theory', 'M06_2X'), 'Cyclo-octatriene')
si.add_xyz(j(pwd, '..', 'test', 'fixtures', 'ethanol'), formula.molecule('C2H5OH'))
27 changes: 27 additions & 0 deletions test/test_formula.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from TCutility import formula


def test_single_molecule():
assert formula.molecule('F-', 'html') == 'F<sup>—</sup>'


def test_single_molecule2():
assert formula.molecule('CH3Cl', 'html') == 'CH<sub>3</sub>Cl'


def test_single_molecule3():
assert formula.molecule('C6H12O6', 'html') == 'C<sub>6</sub>H<sub>1</sub><sub>2</sub>O<sub>6</sub>'


def test_reaction():
assert formula.molecule('F- + CH3Cl', 'html') == 'F<sup>—</sup> + CH<sub>3</sub>Cl'


def test_reaction2():
assert formula.molecule('F- + CH3Cl -> CH3F + Cl-', 'html') == 'F<sup>—</sup> + CH<sub>3</sub>Cl -> CH<sub>3</sub>F + Cl<sup>—</sup>'


if __name__ == '__main__':
import pytest

pytest.main()