Source code for algebraixlib.util.latexprinter

"""Conversion utilities that present a `MathObject` as LaTeX markup.

The main entry point is the function `math_object_to_latex`; it delegates to the appropriate
conversion function according to the argument type.
"""

# Copyright Algebraix Data Corporation 2015 - 2017
#
# This file is part of algebraixlib <http://github.com/AlgebraixData/algebraixlib>.
#
# algebraixlib is free software: you can redistribute it and/or modify it under the terms of version
# 3 of the GNU Lesser General Public License as published by the Free Software Foundation.
#
# algebraixlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along with algebraixlib.
# If not, see <http://www.gnu.org/licenses/>.
# --------------------------------------------------------------------------------------------------
import itertools as _itertools

import algebraixlib.mathobjects as _mo
import algebraixlib.undef as _undef
import algebraixlib.util.miscellaneous as _misc


# --------------------------------------------------------------------------------------------------

[docs]class Config: """A static class with module configuration values.""" #: If ``True``, add colors to the output. If ``False``, print black only. colorize_output = True #: Number of characters to print for 'short' length of :class:`~.Atom` values. The remainder #: of the elements is represented by an ellipsis ('...'). See `math_object_to_latex` and #: `iprint_latex`. short_atom_len = 10 #: Number of elements to print for 'short' length of :class:`~.Set`\s and #: :class:`~.Multiset`\s. The remainder of the elements is represented by an ellipsis, #: followed by the number of not shown elements in parentheses (for example '... (15)'). See #: `math_object_to_latex` and `iprint_latex`. short_set_len = 4
[docs]def math_object_to_latex(mobj, short: bool=False, _depth: int=0): """Return a `string` that represents a `MathObject` on `Undef()` in LaTeX markup. This function sorts the input ``mobj`` if it is a :class:`~.Set` or a :class:`~.Multiset` to make the output consistent, so be careful with big (multi)sets. (Such large (multi)sets where this is a problem may not be suitable to display in LaTeX anyway.) :param mobj: The instance that you want to translate into LaTeX. It must be a `MathObject` or `Undef()`. :param short: (Optional) When set to ``True``, a short version of the content is generated. Longer parts are abbreviated with ellipses ('...'). Defaults to ``False``. See also `Config.short_atom_len` and `Config.short_set_len`. :param _depth: (Optional) Internal use only. Indicate levels of nested (multi)sets. Is incremented for every nesting level. Default is 0. """ if isinstance(mobj, _mo.MathObject): if mobj.is_set: return set_to_latex(mobj, short, _depth) elif mobj.is_multiset: return mset_to_latex(mobj, short, _depth) elif mobj.is_couplet: return couplet_to_latex(mobj, short) elif mobj.is_atom: return atom_to_latex(mobj, short) else: return '<application error>' elif mobj is _undef.Undef(): return r"\mathit{undef}" else: return str(mobj)
# noinspection PyPackageRequirements
[docs]def iprint_latex(variable_name: str, variable_value=None, short: bool=False): """Display variables in IPython notebooks using LaTeX markup. Uses `math_object_to_latex`. This function sorts the input ``mobj`` if it is a :class:`~.Set` or a :class:`~.Multiset` to make the output consistent, so be careful with big (multi)sets. (Such large (multi)sets where this is a problem may not be suitable to display in LaTeX anyway.) :param variable_name: The name of the variable to display. :param variable_value: (Optional) The value of the variable. If it is missing, the variable value is fetched from the caller's frame; a variable with the name ``variable_name`` is assumed to exist in this case. :param short: (Optional) When set to ``True``, a short version of the content is generated. Longer parts are abbreviated with ellipses ('...'). Defaults to ``False``. See also `Config.short_atom_len` and `Config.short_set_len`. .. note:: This function imports from ``IPython`` and expects IPython to be installed. This is generally given when running in an IPython notebook. """ from IPython.display import Math, display if variable_value is None: variable_value = _misc.get_variable(variable_name, frames_up=1) variable_latex = math_object_to_latex(variable_value, short=short) variable_name_latex = variable_name.replace('_', r'\_') latex = '{name} = {value}'.format(name=variable_name_latex, value=variable_latex) display(Math(latex))
# --------------------------------------------------------------------------------------------------
[docs]def atom_to_latex(atom: _mo.Atom, short: bool=False): """Return a `string` that represents the value of an Atom in LaTeX markup. :param atom: The :class:`~.Atom` to be represented in LaTeX markup. :param short: (Optional) When set to ``True``, a short version of the content is generated. Longer parts are abbreviated with ellipses ('...'). Defaults to ``False``. See also `Config.short_atom_len`. """ assert atom.is_atom atom_str = str(atom) if short and len(atom_str) > Config.short_atom_len + 3: atom_str = atom_str[:Config.short_atom_len] + '...' # Wrap in mbox tag to enforce that it remains on one line. # Note: The str function on an Atom returns the value wrapped in single quotes. result_value = '{mbox}{{{atom}}}'.format(mbox=_Tokens.mbox, atom=atom_str) return result_value
[docs]def couplet_to_latex(couplet: _mo.Couplet, short: bool=False): r"""Return a `string` that represents a Couplet in LaTeX markup. :param couplet: The :class:`~.Couplet` to be represented in LaTeX markup. :param short: (Optional) When set to ``True``, a short version of the content is generated. Longer parts are abbreviated with ellipses ('...'). Defaults to ``False``. See also `Config.short_atom_len` and `Config.short_set_len`. (Even though this doesn't affect a ``Couplet`` directly, it may affect the :term:`left` and :term:`right component`\s of it.) """ assert couplet.is_couplet left_color = _Colors().red if Config.colorize_output else None right_color = _Colors().blue if Config.colorize_output else None def make_component(comp, color=None): latex_str = math_object_to_latex(comp, short=short) if color: latex_str = ''.join((_Tokens.color, color, '{', latex_str, '}')) if comp.is_atom: return latex_str return '{}{}{}'.format(_Tokens.left_paren_dyn, latex_str, _Tokens.right_paren_dyn) result_value = '{left}{couplet_sym}{{{right}}}'.format( right=make_component(couplet.right, right_color), couplet_sym=_Tokens.mapsto, left=make_component(couplet.left, left_color)) return result_value
[docs]def set_to_latex(set_: _mo.Set, short: bool=False, _depth: int=0): """Return a `string` that represents a Set in LaTeX markup. This function sorts the input ``set_`` to make the output consistent, so be careful with big sets. (Such large sets where this is a problem may not be suitable to display in LaTeX anyway.) :param set_: The :class:`~.Set` to be represented in LaTeX markup. :param short: (Optional) When set to ``True``, a short version of the content is generated. Longer parts are abbreviated with ellipses ('...'). Defaults to ``False``. See also `Config.short_set_len`. :param _depth: (Optional) Internal use only. Indicate levels of nested (multi)sets. Is incremented for every nesting level. Default is 0. """ assert set_.is_set result = _Tokens.left_brace_dyn if _set_hasa_set(set_): # Use nesting delimiters for elements. Print on new lines. result += _Tokens.array_start # Add sorting to obtain predictable output. elem_itr = iter(sorted(set_.data)) if short and set_.cardinality > Config.short_set_len + 1: result += (_Tokens.comma + _nested_set_helper(_depth)).join( [math_object_to_latex(element, short=short, _depth=_depth + 1) for element in _itertools.islice(elem_itr, Config.short_set_len)]) result += _Tokens.comma + _nested_set_helper(_depth) + _Tokens.ellipsis result += '({0})'.format(set_.cardinality - Config.short_set_len) else: result += (_Tokens.comma + _nested_set_helper(_depth)).join( [math_object_to_latex(element, short=short, _depth=_depth + 1) for element in elem_itr]) result += _Tokens.array_end else: # Use comma delimiters for elements. Print on single line. # Add sorting to obtain predictable output. elem_itr = iter(sorted(set_.data)) if short and set_.cardinality > Config.short_set_len + 1: is_couplet_list = [elem.is_couplet for elem in _itertools.islice(set_, Config.short_set_len)] else: is_couplet_list = [elem.is_couplet for elem in set_] parenthesize_couplets = any(is_couplet_list) and not all(is_couplet_list) def optionally_parenthesize_couplet(element): def do_paren(e): return parenthesize_couplets and e.is_couplet left = _Tokens.left_paren if do_paren(element) else '' right = _Tokens.right_paren if do_paren(element) else '' elem_latex = math_object_to_latex(element, short=short, _depth=_depth + 1) return "{}{}{}".format(left, elem_latex, right) if short and set_.cardinality > Config.short_set_len + 1: result += (_Tokens.comma + _Tokens.space).join( optionally_parenthesize_couplet(element) for element in _itertools.islice(elem_itr, Config.short_set_len)) result += _Tokens.comma + _Tokens.space + _Tokens.ellipsis result += '({0})'.format(set_.cardinality - Config.short_set_len) else: result += (_Tokens.comma + _Tokens.space).join( optionally_parenthesize_couplet(element) for element in elem_itr) result += _Tokens.right_brace_dyn return result
[docs]def mset_to_latex(mset: _mo.Multiset, short: bool=False, _depth: int=0): """Return a `string` that represents a Multiset in LaTeX markup. This function sorts the input ``mset`` to make the output consistent, so be careful with big sets. (Such large multisets where this is a problem may not be suitable to display in LaTeX anyway.) :param mset: The :class:`~.Multiset` to be represented in LaTeX markup. :param short: (Optional) When set to ``True``, a short version of the content is generated. Longer parts are abbreviated with ellipses ('...'). Defaults to ``False``. See also `Config.short_set_len`. :param _depth: (Optional) Internal use only. Indicate levels of nested (multi)sets. Is incremented for every nesting level. Default is 0. """ assert mset.is_multiset def latex_mset(value, multiple): return '{value}{separator}{multiple}'.format( value=math_object_to_latex(value, short=short, _depth=_depth + 1), separator=_Tokens.colon + _Tokens.space, multiple=multiple) if _set_hasa_set(mset): # Use nesting delimiters for elements. Print on new lines. start = _Tokens.array_start separator = _Tokens.comma + _nested_set_helper(_depth) end = _Tokens.array_end else: # Use comma delimiters for elements. Print on single line. start = '' separator = _Tokens.comma + _Tokens.space end = '' result = _Tokens.left_bracket_dyn + start if short and mset.cardinality > Config.short_set_len + 1: result += separator.join( [latex_mset(elem_key, multiple) for elem_key, multiple in _itertools.islice( sorted(mset.data.items()), Config.short_set_len)]) result += separator + _Tokens.ellipsis else: result += separator.join([latex_mset(elem_key, multiple) for elem_key, multiple in sorted(mset.data.items())]) result += end + _Tokens.right_bracket_dyn return result
# -------------------------------------------------------------------------------------------------- # noinspection PyUnusedLocal def _nested_set_helper(depth): """Insert markup for nested indentation. (Currently only insert newlines.) Meant to insert markup and indendation for printing nested sets and multisets. This functionality is currently deactivated. :param depth: The nesting level. :return: LaTeX markup representing a new line and the associated indentation. """ return ''.join([_Tokens.newline, "\n"]) # newline added for markup readability # Print additional indentation (presently undesired) # return ''.join([_Tokens.newline, _Tokens.quad, _Tokens.quad * depth]) def _set_hasa_set(a_set): """Determe whether a (multi)set has elements that are also (multi)sets.""" for element in a_set: if element.is_set or element.is_multiset: return True return False class _Tokens: """A static class that contains all the LaTeX tokens that we need.""" newline = r"\\" space = r"\ " medium_space = r"\>" big_space = r"\;" comma = r"," colon = r":" right_brace = r"\}" right_bracket = r"]" right_paren = r")" left_brace = r"\{" left_bracket = r"[" left_paren = r"(" left_brace_dyn = r"\left\{" right_brace_dyn = r"\right\}" left_bracket_dyn = r"\left[" right_bracket_dyn = r"\right]" left_paren_dyn = r"\left(" right_paren_dyn = r"\right)" array_start = r"\begin{array}{l}" array_end = r"\end{array}" expon = r"^" quad = r"\quad" dquad = r"\qquad" mbox = r"\mbox" color = r"\color" ellipsis = r'\text{...}' mapsto = r"{\mapsto}" class _Colors: r"""A static class that contains the strings that can be used as arguments to LaTeX \color.""" gray = "{gray}" silver = "{silver}" black = "{black}" white = "{white}" red = "{red}" yellow = "{yellow}" maroon = "{maroon}" lime = "{lime}" olive = "{olive}" green = "{green}" teal = "{teal}" aqua = "{aqua}" blue = "{blue}" navy = "{navy}" purple = "{purple}" fuchsia = "{fuchsia}"