Source code for qnet.printing.base

"""Provides the base class for Printers"""

from sympy.core.basic import Basic as SympyBasic
from sympy.printing.printer import Printer as SympyPrinter

from ..algebra.core.scalar_algebra import Scalar
from ..utils.indices import StrLabel
from .sympy import SympyStrPrinter
from ._render_head_repr import render_head_repr

__all__ = []
__private__ = ['QnetBasePrinter']


[docs]class QnetBasePrinter(SympyPrinter): """Base class for all QNET expression printers Args: cache (dict or None): A dict that maps expressions to strings. It may be given during istantiation to use pre-defined strings for specific expressions. The cache will be updated as the printer is used. settings (dict or None): A dict of settings. Class Attributes: sympy_printer_cls (type): The class that will be instantiated to print Sympy expressions _default_settings (dict): The default value of all settings. Note only settings for which there are defaults defined here are accepted when instantiating the printer printmethod (None or str): Name of a method that expressions may define to print themeselves. Attributes: cache (dict): Dictionary where the results of any call to :meth:`doprint` is stored. When :meth:`doprint` is called for an expression that is already in `cache`, the result from the cache is returned. _sympy_printer (sympy.printing.printer.Printer): The printer instance that will be used to print any Sympy expression. _allow_caching (bool): A flag that may be set to completely disable caching _print_level (int): The recursion depth of :meth:`doprint` (>= 1 inside any of the ``_print*`` methods) Raises: TypeError: If any key in `settings` is not defined in the `_default_settings` of the printer, respectively the `sympy_printer_cls`. """ sympy_printer_cls = SympyStrPrinter _default_settings = {} # note that subclasses should always define their own _default_settings _allow_caching = True printmethod = None def __init__(self, cache=None, settings=None): # This would be more elegant if we declared this as **settings, but we # want to closely mirror the SymPy implementation. The frontend-facing # routines, e.g. init_printing or ascii do flatten out the settings self.cache = {} if cache is not None: self.cache = cache sympy_settings = {} qnet_settings = {} if settings is not None: for key in settings: key_ok = False if key in self.sympy_printer_cls._default_settings: key_ok = True sympy_settings[key] = settings[key] if key in self._default_settings: key_ok = True qnet_settings[key] = settings[key] if not key_ok: raise TypeError( "%s is not a valid setting for either %s or %s" % ( key, self.__class__.__name__, self.sympy_printer_cls.__name__)) self._sympy_printer = self.sympy_printer_cls(settings=sympy_settings) super().__init__(settings=qnet_settings) def _render_str(self, string): if isinstance(string, StrLabel): string = string._render(string.expr) return str(string)
[docs] def emptyPrinter(self, expr): """Fallback method for expressions that neither know how to print themeselves, nor for which the printer has a suitable ``_print*`` method""" return render_head_repr(expr)
@staticmethod def _isinstance(expr, classname): """Check whether `expr` is an instance of the class with name `classname` This is like the builtin `isinstance`, but it take the `classname` a string, instead of the class directly. Useful for when we don't want to import the class for which we want to check (also, remember that printer choose rendering method based on the class name, so this is totally ok) """ for cls in type(expr).__mro__: if cls.__name__ == classname: return True return False def _get_from_cache(self, expr): """Get the result of :meth:`doprint` from the internal cache""" # The reason method this is separated out from `doprint` is that # printers that use identation, e.g. IndentedSReprPrinter, need to # override how caching is handled, applying variable indentation even # for cached results try: is_cached = expr in self.cache except TypeError: # expr is unhashable is_cached = False if is_cached: return True, self.cache[expr] else: return False, None def _write_to_cache(self, expr, res): """Store the result of :meth:`doprint` in the internal cache""" # Well be overwritten by printers that use indentation, see # _get_from_cache try: self.cache[expr] = res except TypeError: # expr is unhashable pass def _print_SCALAR_TYPES(self, expr, *args, **kwargs): """Render scalars""" adjoint = kwargs.get('adjoint', False) if adjoint: expr = expr.conjugate() if isinstance(expr, SympyBasic): self._sympy_printer._print_level = self._print_level + 1 res = self._sympy_printer.doprint(expr) else: # numeric type try: if int(expr) == expr: # In Python, objects that evaluate equal (e.g. 2.0 == 2) # have the same hash. We want to normalize this, so that we # get consistent results when printing with a cache expr = int(expr) except TypeError: pass if adjoint: kwargs = { key: val for (key, val) in kwargs.items() if key != 'adjoint'} res = self._print(expr, *args, **kwargs) return res
[docs] def doprint(self, expr, *args, **kwargs): """Returns printer's representation for expr (as a string) The representation is obtained by the following methods: 1. from the :attr:`cache` 2. If `expr` is a Sympy object, delegate to the :meth:`~sympy.printing.printer.Printer.doprint` method of :attr:`_sympy_printer` 3. Let the `expr` print itself if has the :attr:`printmethod` 4. Take the best fitting ``_print_*`` method of the printer 5. As fallback, delegate to :meth:`emptyPrinter` Any extra `args` or `kwargs` are passed to the internal `_print` method. """ allow_caching = self._allow_caching is_cached = False if len(args) > 0 or len(kwargs) > 0: # we don't want to cache "custom" rendering, such as the adjoint of # the actual expression (kwargs['adjoint'] is True). Otherwise, we # might return a cached values for args/kwargs that are different # from the the expression was originally cached. allow_caching = False if allow_caching: is_cached, res = self._get_from_cache(expr) if not is_cached: if isinstance(expr, Scalar._val_types): res = self._print_SCALAR_TYPES(expr, *args, **kwargs) elif isinstance(expr, str): return self._render_str(expr) else: # the _print method, inherited from SympyPrinter implements the # internal dispatcher for (3-5) res = self._str(self._print(expr, *args, **kwargs)) if allow_caching: self._write_to_cache(expr, res) return res