# -*- coding: utf-8 -*-
"""
chemreac.util.table
-------------------
Convenience functions for presenting reaction systems in tables.
"""
import os
import shutil
import subprocess
import tempfile
from chemreac.units import to_unitless, get_derived_unit
tex_templates = {
'document': {
'default': r"""
\documentclass{article}
\pagestyle{empty}
%(usepkg)s
\begin{document}
%(begins)s
%(table)s
%(ends)s
\end{document}
"""
},
'table': {
'default': r"""
\begin{%(table_env)s}
\centering
\label{tab:%(label)s}
\caption[%(short_cap)s]{%(long_cap)s}
\begin{tabular}{%(alignment)s}
\toprule
%(header)s
\midrule
%(body)s
\bottomrule
\end{tabular}
\end{%(table_env)s}""",
'longtable': r"""
\begin{%(table_env)s}{%(alignment)s}
\caption[%(short_cap)s]{%(long_cap)s
\label{tab:%(label)s}}\\
\toprule
%(header)s
\midrule
%(body)s
\bottomrule
\end{%(table_env)s}"""
}
}
def render_tex_to_pdf(contents, texfname, pdffname, output_dir, save):
created_tempdir = False
try:
if output_dir is None:
output_dir = tempfile.mkdtemp()
created_tempdir = True
texpath = os.path.join(output_dir, texfname)
pdfpath = os.path.join(output_dir, pdffname)
cmds = ['pdflatex', '-halt-on-error', '-interaction',
'batchmode', texfname]
with open(texpath, 'wt') as ofh:
ofh.write(contents)
ofh.flush()
with open(pdfpath + '.out', 'wb') as logfile:
p = subprocess.Popen(cmds, cwd=output_dir,
stdout=logfile, stderr=logfile)
retcode = p.wait()
p = subprocess.Popen(cmds, cwd=output_dir,
stdout=logfile, stderr=logfile)
retcode += p.wait()
if retcode:
fmtstr = "{}\n returned with exit status {}"
raise RuntimeError(fmtstr.format(' '.join(cmds), retcode))
else:
return pdfpath
finally:
if save is True or save == 'True':
pass
else:
if save is False or save == 'False':
if created_tempdir:
shutil.rmtree(output_dir)
else:
# interpret path to copy pdf to.
shutil.copy(pdfpath, save)
[docs]def rsys2tablines(rsys, substances, rref0=1, coldelim=' & ',
tex=True, rxnarrow=r'$\rightarrow$', ref_fmt='{}',
units=None, unit_fmt='{}'):
"""
Generates a table representation of a ReactionSystem.
Parameters
----------
rsys: ReactionSystem
substances: sequence of Substance instances
rref0: integer
default start of index counter (default: 1)
coldelim: string
column delimiter (default: ' & ')
tex: bool
use latex formated output (default: True)
rxnarrow: string
default: '\$\\rightarrow\$'
ref_fmt: string or callable
format string of ``ref`` attribute of reactions
units: unit registry
optional (default: None)
"""
param_fmt = '{0:.3g}' # Could be taken from Reaction instance
def _get_name(sn):
return getattr(substances[sn], 'tex_name' if tex else 'name')
lines = []
for ri, rxn in enumerate(rsys.rxns):
if units is not None:
kunit = (get_derived_unit(units, 'concentration')**(1-rxn.order) /
get_derived_unit(units, 'time'))
k = to_unitless(rxn.k, kunit)
else:
kunit = 1
k = rxn.k
lines.append(coldelim.join([
str(rref0+ri),
' + '.join([('' if num == 1 else str(num)) + _get_name(sn) for
sn, num in rxn.reactants.items() if num > 0]),
rxnarrow,
' + '.join([('' if num == 1 else str(num)) + _get_name(sn) for
sn, num in rxn.products.items() if num > 0]),
param_fmt.format(k),
unit_fmt.format(kunit),
ref_fmt(rxn.ref) if callable(ref_fmt) else ref_fmt.format(rxn.ref)
]))
return lines
[docs]def rsys2table(rsys, substances, table_template=None,
table_template_dict=None, **kwargs):
r"""
Renders user provided table_template with table_template_dict which
also has 'body' entry generated from `rsys2tablines`.
Defaults is LaTeX table requiring booktabs package to be used
(add \usepackage{booktabs} to preamble).
Parameters
==========
rsys: ReactionSystem
substances: sequence of Substance instances
table_template: string
table_tempalte_dict: dict used to render table_template (excl. "body")
longtable: bool
use longtable in defaults. (default: False)
**kwargs:
passed onto rsys2tablines
"""
siunitx = kwargs.pop('siunitx', False)
line_term = r' \\'
defaults = {
'table_env': 'longtable' if kwargs.pop(
'longtable', False) else 'table',
'alignment': 'llllSll' if siunitx else 'lllllll',
'header': kwargs.get('coldelim', ' & ').join([
'Id.', 'Reactants', '', 'Products', '{Rate constant}',
'Unit', 'Ref'
]) + line_term,
'short_cap': rsys.name,
'long_cap': rsys.name,
'label': (rsys.name or 'None').lower()
}
if table_template_dict is None:
table_template_dict = defaults
else:
for k, v in defaults:
if k not in table_template_dict:
table_template_dict[k] = v
if 'body' in table_template_dict:
raise KeyError("There is already a 'body' key in table_template_dict")
table_template_dict['body'] = (line_term + '\n').join(rsys2tablines(
rsys, substances, **kwargs)) + line_term
if table_template is None:
if table_template_dict['table_env'] == 'longtable':
table_template = tex_templates['table']['longtable']
else:
table_template = tex_templates['table']['default']
return table_template % table_template_dict
[docs]def rsys2pdf_table(rsys, substances, output_dir=None, doc_template=None,
doc_template_dict=None, save=True, landscape=False,
**kwargs):
"""
Convenience function to render a ReactionSystem as
e.g. a pdf using e.g. pdflatex.
Parameters
==========
rsys: ReactionSystem
substances: sequence of Substance instances
output_dir: path to output directory
(default: system's temporary folder)
doc_template: string
LaTeX boiler plate temlpate including preamble,
document environment etc.
doc_template_dict: dict (string -> string)
dict used to render temlpate (excl. 'table')
longtable: bool
use longtable in defaults. (default: False)
**kwargs:
passed on to `rsys2table`
"""
if doc_template is None:
doc_template = tex_templates['document']['default']
_pkgs = ['booktabs'] + (['lscape'] if landscape else [])
if kwargs.get('longtable', False):
_pkgs += ['longtable']
if kwargs.get('siunitx', False):
_pkgs += ['siunitx']
_envs = ['tiny'] + (['landscape'] if landscape else [])
defaults = {
'usepkg': '\n'.join([r'\usepackage{%s}' % pkg for pkg in _pkgs]),
'begins': '\n'.join([r'\begin{%s}' % env for env in _envs]),
'ends': '\n'.join([r'\end{%s}' % env for env in _envs[::-1]])
}
if doc_template_dict is None:
doc_template_dict = defaults
else:
for k, v in defaults:
if k not in doc_template_dict:
doc_template_dict[k] = v
if 'table' in doc_template_dict:
raise KeyError("There is already a 'table' key in doc_template_dict")
doc_template_dict['table'] = rsys2table(rsys, substances, **kwargs)
contents = doc_template % doc_template_dict
texfname = (rsys.name or 'output') + '.tex'
pdffname = (rsys.name or 'output') + '.pdf'
return render_tex_to_pdf(contents, texfname, pdffname, output_dir, save)
def radyields2pdf_table(rd, output_dir=None, save=True, units=None,
siunitx=False, fmtstr='{0:.3f}', **kwargs):
line_term = r' \\'
col_delim = ' & '
header = (col_delim.join(rd.substance_tex_names or rd.substance_names) +
line_term)
lines = []
for cur_gs in rd.g_values:
if units is not None:
gunit = get_derived_unit(units, 'radiolytic_yield')
cur_gs = to_unitless(cur_gs, gunit)
lines.append(col_delim.join(map(
lambda v: fmtstr.format(v), cur_gs)) + line_term)
table_template_dict = {
'table_env': 'table',
'alignment': ('@{}S' if siunitx else '@{}l')*rd.n,
'header': header,
'short_cap': 'G-values',
'long_cap': 'G-values',
'label': 'none',
'body': '\n'.join(lines)
}
table_template_dict.update(kwargs)
table = tex_templates['table']['default'] % table_template_dict
_envs = ['landscape', 'tiny']
_pkgs = (['siunitx'] if siunitx else []) + ['booktabs', 'lscape']
contents = tex_templates['document']['default'] % {
'usepkg': '\n'.join([r'\usepackage{%s}' % pkg for pkg in _pkgs]),
'begins': '\n'.join([r'\begin{%s}' % env for env in _envs]),
'ends': '\n'.join([r'\end{%s}' % env for env in _envs[::-1]]),
'table': table
}
return render_tex_to_pdf(contents, 'gvalues.tex', 'gvalues.pdf',
output_dir, save)