The Printing System¶
Overview¶
As a computer algebra framework, QNET puts great emphasis on the appropriate display of expressions, both in the context of a Jupyter notebook (QNETs main “graphical interface”) and in the terminal. It also provides the possibility for you to completely customize the display.
The printing system is modeled closely after the printing system of SymPy (and directly builds on it). Unlike SymPy, however, the display of an expression will always directly reflect the algebraic structure (summands will not be reordered, for example).
In the context of a Jupyter notebook, expressions will be shown via LaTeX. In an interactive (I)Python terminal, a unicode rendering will be used if the terminal has unicode support, with a fallback to ascii. We can force this manually by:
>>> init_printing(repr_format='unicode')
>>> Create(hs='q_1') * CoherentStateKet(symbols('eta')**2/2, hs='q_1')
â^(q₁)† |α=η²/2⟩^(q₁)
These textual renderings can be obtained manually through the ascii()
and
unicode()
functions.
Unlike SymPy, the unicode rendering will not span multiple lines. Also, QNET will not rationalize the denominators of scalar fractions by default, to match the standard notation in quantum mechanics:
>>> (BasisKet(0, hs=1) + BasisKet(1, hs=1)) / sqrt(2)
1/√2 (|0⟩⁽¹⁾ + |1⟩⁽¹⁾)
Compare this to the default in SymPy:
>>> (symbols('a') + symbols('b')) / sqrt(2)
√2⋅(a + b)
──────────
2
With the default settings, the LaTeX renderer that produces the output in the
Jupyter notebook uses only tex macros that MathJax understands. You can obtain
the LaTeX code through the latex()
function. When generating code for a
paper or report, it is better to customize the output for better readability
with a more semantic use of macros, e.g. as:
>>> print(latex((BasisKet(0, hs=1) + BasisKet(1, hs=1)) / sqrt(2), tex_use_braket=True))
\frac{1}{\sqrt{2}} \left(\Ket{0}^{(1)} + \Ket{1}^{(1)}\right)
In addition to the “mathematical” display of expressions, QNET also has functions to show the exact internal (tree) structure of an expression, either for debugging or for designing algebraic transformations.
The srepr()
function returns the most direct representation of the
expression: it is a string (possibly with indentation for the tree structure)
that if evaluated results in the exact same expression.
An alternative, specifically for interactive use, is the
print_tree()
function. To generate a graphic
representation of the tree structure, the dotprint()
function produces a graph in the DOT language.
Basic Customization¶
At the beginning of an interactive session or notebook, the
init_printing()
routine should be called. This routine associates specific
printing functions, e.g. unicode()
, with the __str__
and __repr__
representation of an expression. This is what is returned by str(expr)
, and
by repr(expr)
or as the output in an interactive (I)Python session.
The initialization also specifies the default settings for each
printing function. For example, you could suppress the display of Hilbert space
labels:
>>> init_printing(show_hs_label=False, repr_format='unicode')
>>> (BasisKet(0, hs=1) + BasisKet(1, hs=1)) / sqrt(2)
1/√2 (|0⟩ + |1⟩)
Or, in a debugging session, you could switch the default representation to use
the indented srepr()
:
>>> init_printing(repr_format='indsrepr')
>>> (BasisKet(0, hs=1) + BasisKet(1, hs=1)) / sqrt(2)
ScalarTimesKet(
Mul(Rational(1, 2), Pow(Integer(2), Rational(1, 2))),
KetPlus(
BasisKet(
0,
hs=LocalSpace(
'1')),
BasisKet(
1,
hs=LocalSpace(
'1'))))
The settings can also be changed temporarily via the configure_printing()
context manager.
Note that init_printing()
should only be called once; or else it should
be given the reset
parameter:
>>> init_printing(repr_format='unicode', reset=True)
Printer classes¶
The printing functions ascii()
, unicode()
, and latex()
each
delegate to an internal printer object that subclasses
qnet.printing.base.QnetBasePrinter
. After initialization,
the printer class is referenced at e.g. ascii.printer
.
For the ultimate control in customizing the printing system, you can implement your
own subclasses of QnetBasePrinter
, which is in
turn a subclass of sympy.printing.printer.Printer
. Thus, the
overview of SymPy’s printing system applies.
The QNET printers conceptually extend SymPy printers in the following ways:
- QNET printers have support for caching. One reason for this is efficiency.
More importantly, it allows to pass a pre-initialized cache to
force certain expressions to be represented by fixed strings, which can make
expressions considerably more readable, and aids in generating code from
expressions, see the example for
srepr()
. - Every printer contains a sub-printer in the _sympy_printer attribute,
instantiated from the sympy_printer_cls class attribute. Actual SymPy
objects (e.g., scalar coefficients) are delegated to this sub-printer, while
the main printer handles all
Expression
instances. Not that the default sub-printers use classes fromqnet.printing.sympy
that implement some custom printing more in line with the conventions of quantum physics.
When init_printing()
is called with direct settings as in the previous
section, these will be used as
global settings, and will affect any printers (including SymPy sub-printers)
that are instantiated afterwards.
The settings that are given to any printing function will be used for that specific call of the printing function only. If you define custom classes with different or additional settings and set them up for use with the printing function (see below), the accepted arguments to the printing functions change accordingly.
Customization through an INI file¶
While init_printing()
can simply be called with explicit settings to configure the
printing system globally (see above), for a more advanced set up an INI-file
can be used. In this case, the path to the file must be the only argument:
init_printing(inifile=<path to file>)
This allows to associate custom printer classes with the printing functions, and also define the settings settings for those particular printers (as opposed to just global settings).
The INI file may have sections ‘global’, ‘ascii’, ‘unicode’, and ‘latex’.
Parameters in the ‘global’ section are equivalent to those could be passed to
init_printing()
as direct settings. That is, they set up the printing
function to be used for __str__
and __repr__
, and set the global
options for all printer classes.
The ‘ascii’, ‘unicode’, and ‘latex’ sections configure the respective printing
functions. To link them to custom Printer classes, you may specify printer
and sympy_printer
as the full path to the Printer class that should be used
for the main printer and the sub-printer for SymPy expressions. All other
settings in the sections override the settings from ‘global’ for that
particular printer.
Consider the following annotated example for an INI file:
[global]
# The settings in the 'global' section are for all Printer classes (both
# SymPy and QNET). They are equivalent to passing them to init_printing
# directly
# the printing function to use for str(expr)
str_format = ascii
# the printing function to use for expr(expr)
repr_format = unicode
# direct global settings
show_hs_label = False
sig_as_ketbra = False
# note that boolean values must be specified as "True", or "False"
# The three sections below associate the printing functions with particular
# Printer classes, and override the global settings for those particular
# printers
[ascii]
printer = qnet.printing.asciiprinter.QnetAsciiPrinter
# we use the SymPy StrPrinter here, instead of the default
# qnet.printing.sympy.SympyStrPrinter that is customized to not
# rationalize denominators
sympy_printer = sympy.printing.str.StrPrinter
# we override the the settings from the 'global' section
show_hs_label = True
sig_as_ketbra = True
[unicode]
printer = qnet.printing.unicodeprinter.QnetUnicodePrinter
sympy_printer = qnet.printing.sympy.SympyUnicodePrinter
show_hs_label = subscript
unicode_op_hats = False
[latex]
printer = qnet.printing.latexprinter.QnetLatexPrinter
sympy_printer = qnet.printing.sympy.SympyLatexPrinter
# string values can be written un-escaped
tex_op_macro = \Op{{{name}}}
tex_use_braket = True
# You can also include options for the sympy_printer
inv_trig_style = full