# -*- coding: utf-8 -*-
"""
Convenience functions for presenting reaction systems in tables.
"""
from __future__ import (absolute_import, division, print_function)
import os
import shutil
import subprocess
import tempfile
from ..kinetics.rates import RadiolyticBase
from ..units import to_unitless, get_derived_unit
tex_templates = {
'document': {
'default': r"""
\documentclass[a4paper,9pt]{article}
\pagestyle{empty}
\usepackage[paper=a4paper,margin=1cm]{geometry}
%(usepkg)s
\hypersetup{
bookmarksnumbered=true,
breaklinks=false,
raiselinks=true,
pdfborder={0 0 0},
colorlinks=true,
plainpages=false,
pdfstartview={FitH},
pdfcreator={LaTeX with hyperref package},
citecolor=teal,
linkcolor=red,
urlcolor=blue,
}
\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}"""
}
}
[docs]def render_tex_to_pdf(contents, texfname, pdffname, output_dir, save):
""" Generates a pdf from a tex file by calling pdflatex
Parameters
----------
contents : str
texfname : path
pdffname : path
output_dir : path
save : path or bool or str(bool)
"""
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.
if not os.path.samefile(pdfpath, save):
shutil.copy(pdfpath, save)
[docs]def rsys2tablines(rsys, rref0=1, coldelim=' & ',
tex=True, ref_fmt=None,
unit_registry=None, unit_fmt='{}', k_fmt='%.4g'):
"""
Generates a table representation of a ReactionSystem.
Parameters
----------
rsys : ReactionSystem
rref0 : integer
default start of index counter (default: 1)
coldelim : string
column delimiter (default: ' & ')
tex : bool
use latex formated output (default: True)
ref_fmt : string or callable
format string of ``ref`` attribute of reactions
unit_registry : unit registry
optional (default: None)
"""
if ref_fmt is None:
def _doi(s):
return r'\texttt{\href{http://dx.doi.org/'+s+'}{doi:'+s+'}}'
def ref_fmt(s):
if s is None:
return 'None'
if tex:
if isinstance(s, dict):
return _doi(s['doi'])
if s.startswith('doi:'):
return _doi(s[4:])
return s
def _wrap(s):
if tex:
return '\\ensuremath{' + s + '}'
else:
return s
lines = []
for ri, rxn in enumerate(rsys.rxns):
rxn_ref = rxn.ref
if isinstance(rxn.param, RadiolyticBase):
if unit_registry is not None:
kunit = get_derived_unit(unit_registry, 'radiolytic_yield')
k = k_fmt % to_unitless(rxn.param.args[0], kunit)
k_unit_str = (kunit.dimensionality.latex.strip('$') if tex
else kunit.dimensionality)
else:
if unit_registry is not None:
kunit = (get_derived_unit(unit_registry,
'concentration')**(1-rxn.order()) /
get_derived_unit(unit_registry, 'time'))
try:
k = k_fmt % to_unitless(rxn.param, kunit)
k_unit_str = (kunit.dimensionality.latex.strip('$') if tex
else kunit.dimensionality)
except:
k, k_unit_str = rxn.param.equation_as_string(k_fmt, tex)
else:
k_unit_str = '-'
k = k_fmt % rxn.param
r_str, ir_str, arrow_str, p_str, ip_str = rxn._get_str_parts(
'latex_name' if tex else 'name',
'latex_arrow' if tex else 'str_arrow',
rsys.substances)
lines.append(coldelim.join([
str(rref0+ri),
_wrap(r_str + ir_str),
_wrap(arrow_str),
_wrap(p_str + ip_str),
_wrap(k),
unit_fmt.format(_wrap(k_unit_str)),
ref_fmt(rxn_ref) if callable(ref_fmt) else ref_fmt.format(rxn_ref)
]))
return lines
[docs]def rsys2table(rsys, table_template=None, table_template_dict=None,
param_name='Rate constant', **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
table_template : string
table_tempalte_dict : dict used to render table_template (excl. "body")
param_name : str
Column header for parameter column
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', '{%s}' % param_name,
'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, k_fmt=r'\num{%.4g}' if siunitx else '%.4g', **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, 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
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', 'amsmath', ('pdftex,colorlinks,unicode=True',
'hyperref')] + (
['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]' if isinstance(pkg, tuple) else '') + '{%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, **kwargs)
contents = doc_template % doc_template_dict
if isinstance(save, str) and save.endswith('.pdf'):
texfname = save.rstrip('.pdf') + '.tex'
pdffname = save
else:
texfname = 'output.tex'
pdffname = 'output.pdf'
return render_tex_to_pdf(contents, texfname, pdffname, output_dir, save)