
.. _tutorial_printing_drawing:

================================
Printing/Drawing PyTensor graphs
================================


PyTensor provides the functions :func:`pytensor.printing.pprint` and
:func:`pytensor.printing.debugprint` to print a graph to the terminal before or
after compilation. :func:`pprint` is more compact and math-like,
:func:`debugprint` is more verbose. PyTensor also provides :func:`pydotprint`
that creates an image of the function. You can read about them in
:ref:`libdoc_printing`.

.. note::

    When printing PyTensor functions, they can sometimes be hard to
    read.  To help with this, you can disable some PyTensor rewrites
    by using the PyTensor flag:
    ``optimizer_excluding=fusion:inplace``. Do not use this during
    real job execution, as this will make the graph slower and use more
    memory.

Consider again the logistic regression example:

>>> import numpy as np
>>> import pytensor
>>> import pytensor.tensor as pt
>>> rng = np.random.default_rng(2382)
>>> # Training data
>>> N = 400
>>> feats = 784
>>> D = (rng.standard_normal(N, feats).astype(pytensor.config.floatX), rng.integers(size=N,low=0, high=2).astype(pytensor.config.floatX))
>>> training_steps = 10000
>>> # Declare PyTensor symbolic variables
>>> x = pt.matrix("x")
>>> y = pt.vector("y")
>>> w = pytensor.shared(rng.standard_normal(feats).astype(pytensor.config.floatX), name="w")
>>> b = pytensor.shared(np.asarray(0., dtype=pytensor.config.floatX), name="b")
>>> x.tag.test_value = D[0]
>>> y.tag.test_value = D[1]
>>> # Construct PyTensor expression graph
>>> p_1 = 1 / (1 + pt.exp(-pt.dot(x, w)-b)) # Probability of having a one
>>> prediction = p_1 > 0.5 # The prediction that is done: 0 or 1
>>> # Compute gradients
>>> xent = -y*pt.log(p_1) - (1-y)*pt.log(1-p_1) # Cross-entropy
>>> cost = xent.mean() + 0.01*(w**2).sum() # The cost to optimize
>>> gw,gb = pt.grad(cost, [w,b])
>>> # Training and prediction function
>>> train = pytensor.function(inputs=[x,y], outputs=[prediction, xent], updates=[[w, w-0.01*gw], [b, b-0.01*gb]], name = "train")
>>> predict = pytensor.function(inputs=[x], outputs=prediction, name = "predict")


Pretty Printing
===============

>>> pytensor.printing.pprint(prediction) # doctest: +NORMALIZE_WHITESPACE
'gt((TensorConstant{1} / (TensorConstant{1} + exp(((-(x \\dot w)) - b)))),
TensorConstant{0.5})'


Debug Print
===========

The pre-compilation graph:

>>> pytensor.printing.debugprint(prediction) # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS
Elemwise{gt,no_inplace} [id A] ''
 |Elemwise{true_div,no_inplace} [id B] ''
 | |InplaceDimShuffle{x} [id C] ''
 | | |TensorConstant{1} [id D]
 | |Elemwise{add,no_inplace} [id E] ''
 |   |InplaceDimShuffle{x} [id F] ''
 |   | |TensorConstant{1} [id D]
 |   |Elemwise{exp,no_inplace} [id G] ''
 |     |Elemwise{sub,no_inplace} [id H] ''
 |       |Elemwise{neg,no_inplace} [id I] ''
 |       | |dot [id J] ''
 |       |   |x [id K]
 |       |   |w [id L]
 |       |InplaceDimShuffle{x} [id M] ''
 |         |b [id N]
 |InplaceDimShuffle{x} [id O] ''
   |TensorConstant{0.5} [id P]

The post-compilation graph:

>>> pytensor.printing.debugprint(predict)  # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS
Elemwise{Composite{GT(scalar_sigmoid((-((-i0) - i1))), i2)}} [id A] ''   4
 |...Gemv{inplace} [id B] ''   3
 | |AllocEmpty{dtype='float64'} [id C] ''   2
 | | |Shape_i{0} [id D] ''   1
 | |   |x [id E]
 | |TensorConstant{1.0} [id F]
 | |x [id E]
 | |w [id G]
 | |TensorConstant{0.0} [id H]
 |InplaceDimShuffle{x} [id I] ''   0
 | |b [id J]
 |TensorConstant{(1,) of 0.5} [id K]


Picture Printing of Graphs
==========================

The pre-compilation graph:

>>> pytensor.printing.pydotprint(prediction, outfile="pics/logreg_pydotprint_prediction.png", var_with_name_simple=True)  # doctest: +SKIP
The output file is available at pics/logreg_pydotprint_prediction.png

.. image:: ./pics/logreg_pydotprint_prediction.png
   :width: 800 px

The post-compilation graph:

>>> pytensor.printing.pydotprint(predict, outfile="pics/logreg_pydotprint_predict.png", var_with_name_simple=True)  # doctest: +SKIP
The output file is available at pics/logreg_pydotprint_predict.png

.. image:: ./pics/logreg_pydotprint_predict.png
   :width: 800 px

The optimized training graph:

>>> pytensor.printing.pydotprint(train, outfile="pics/logreg_pydotprint_train.png", var_with_name_simple=True)  # doctest: +SKIP
The output file is available at pics/logreg_pydotprint_train.png

.. image:: ./pics/logreg_pydotprint_train.png
   :width: 1500 px


Interactive Graph Visualization
===============================

The new :mod:`d3viz` module complements :func:`pytensor.printing.pydotprint` to
visualize complex graph structures. Instead of creating a static image, it
generates an HTML file, which allows to dynamically inspect graph structures in
a web browser. Features include zooming, drag-and-drop, editing node labels, or
coloring nodes by their compute time.

=> :mod:`d3viz` <=

.. image:: ./pics/d3viz.png
   :width: 350 px
