Coverage for /usr/lib/python3/dist-packages/sympy/printing/dot.py: 26%
61 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1from sympy.core.basic import Basic
2from sympy.core.expr import Expr
3from sympy.core.symbol import Symbol
4from sympy.core.numbers import Integer, Rational, Float
5from sympy.printing.repr import srepr
7__all__ = ['dotprint']
9default_styles = (
10 (Basic, {'color': 'blue', 'shape': 'ellipse'}),
11 (Expr, {'color': 'black'})
12)
14slotClasses = (Symbol, Integer, Rational, Float)
15def purestr(x, with_args=False):
16 """A string that follows ```obj = type(obj)(*obj.args)``` exactly.
18 Parameters
19 ==========
21 with_args : boolean, optional
22 If ``True``, there will be a second argument for the return
23 value, which is a tuple containing ``purestr`` applied to each
24 of the subnodes.
26 If ``False``, there will not be a second argument for the
27 return.
29 Default is ``False``
31 Examples
32 ========
34 >>> from sympy import Float, Symbol, MatrixSymbol
35 >>> from sympy import Integer # noqa: F401
36 >>> from sympy.core.symbol import Str # noqa: F401
37 >>> from sympy.printing.dot import purestr
39 Applying ``purestr`` for basic symbolic object:
40 >>> code = purestr(Symbol('x'))
41 >>> code
42 "Symbol('x')"
43 >>> eval(code) == Symbol('x')
44 True
46 For basic numeric object:
47 >>> purestr(Float(2))
48 "Float('2.0', precision=53)"
50 For matrix symbol:
51 >>> code = purestr(MatrixSymbol('x', 2, 2))
52 >>> code
53 "MatrixSymbol(Str('x'), Integer(2), Integer(2))"
54 >>> eval(code) == MatrixSymbol('x', 2, 2)
55 True
57 With ``with_args=True``:
58 >>> purestr(Float(2), with_args=True)
59 ("Float('2.0', precision=53)", ())
60 >>> purestr(MatrixSymbol('x', 2, 2), with_args=True)
61 ("MatrixSymbol(Str('x'), Integer(2), Integer(2))",
62 ("Str('x')", 'Integer(2)', 'Integer(2)'))
63 """
64 sargs = ()
65 if not isinstance(x, Basic):
66 rv = str(x)
67 elif not x.args:
68 rv = srepr(x)
69 else:
70 args = x.args
71 sargs = tuple(map(purestr, args))
72 rv = "%s(%s)"%(type(x).__name__, ', '.join(sargs))
73 if with_args:
74 rv = rv, sargs
75 return rv
78def styleof(expr, styles=default_styles):
79 """ Merge style dictionaries in order
81 Examples
82 ========
84 >>> from sympy import Symbol, Basic, Expr, S
85 >>> from sympy.printing.dot import styleof
86 >>> styles = [(Basic, {'color': 'blue', 'shape': 'ellipse'}),
87 ... (Expr, {'color': 'black'})]
89 >>> styleof(Basic(S(1)), styles)
90 {'color': 'blue', 'shape': 'ellipse'}
92 >>> x = Symbol('x')
93 >>> styleof(x + 1, styles) # this is an Expr
94 {'color': 'black', 'shape': 'ellipse'}
95 """
96 style = {}
97 for typ, sty in styles:
98 if isinstance(expr, typ):
99 style.update(sty)
100 return style
103def attrprint(d, delimiter=', '):
104 """ Print a dictionary of attributes
106 Examples
107 ========
109 >>> from sympy.printing.dot import attrprint
110 >>> print(attrprint({'color': 'blue', 'shape': 'ellipse'}))
111 "color"="blue", "shape"="ellipse"
112 """
113 return delimiter.join('"%s"="%s"'%item for item in sorted(d.items()))
116def dotnode(expr, styles=default_styles, labelfunc=str, pos=(), repeat=True):
117 """ String defining a node
119 Examples
120 ========
122 >>> from sympy.printing.dot import dotnode
123 >>> from sympy.abc import x
124 >>> print(dotnode(x))
125 "Symbol('x')_()" ["color"="black", "label"="x", "shape"="ellipse"];
126 """
127 style = styleof(expr, styles)
129 if isinstance(expr, Basic) and not expr.is_Atom:
130 label = str(expr.__class__.__name__)
131 else:
132 label = labelfunc(expr)
133 style['label'] = label
134 expr_str = purestr(expr)
135 if repeat:
136 expr_str += '_%s' % str(pos)
137 return '"%s" [%s];' % (expr_str, attrprint(style))
140def dotedges(expr, atom=lambda x: not isinstance(x, Basic), pos=(), repeat=True):
141 """ List of strings for all expr->expr.arg pairs
143 See the docstring of dotprint for explanations of the options.
145 Examples
146 ========
148 >>> from sympy.printing.dot import dotedges
149 >>> from sympy.abc import x
150 >>> for e in dotedges(x+2):
151 ... print(e)
152 "Add(Integer(2), Symbol('x'))_()" -> "Integer(2)_(0,)";
153 "Add(Integer(2), Symbol('x'))_()" -> "Symbol('x')_(1,)";
154 """
155 if atom(expr):
156 return []
157 else:
158 expr_str, arg_strs = purestr(expr, with_args=True)
159 if repeat:
160 expr_str += '_%s' % str(pos)
161 arg_strs = ['%s_%s' % (a, str(pos + (i,)))
162 for i, a in enumerate(arg_strs)]
163 return ['"%s" -> "%s";' % (expr_str, a) for a in arg_strs]
165template = \
166"""digraph{
168# Graph style
169%(graphstyle)s
171#########
172# Nodes #
173#########
175%(nodes)s
177#########
178# Edges #
179#########
181%(edges)s
182}"""
184_graphstyle = {'rankdir': 'TD', 'ordering': 'out'}
186def dotprint(expr,
187 styles=default_styles, atom=lambda x: not isinstance(x, Basic),
188 maxdepth=None, repeat=True, labelfunc=str, **kwargs):
189 """DOT description of a SymPy expression tree
191 Parameters
192 ==========
194 styles : list of lists composed of (Class, mapping), optional
195 Styles for different classes.
197 The default is
199 .. code-block:: python
201 (
202 (Basic, {'color': 'blue', 'shape': 'ellipse'}),
203 (Expr, {'color': 'black'})
204 )
206 atom : function, optional
207 Function used to determine if an arg is an atom.
209 A good choice is ``lambda x: not x.args``.
211 The default is ``lambda x: not isinstance(x, Basic)``.
213 maxdepth : integer, optional
214 The maximum depth.
216 The default is ``None``, meaning no limit.
218 repeat : boolean, optional
219 Whether to use different nodes for common subexpressions.
221 The default is ``True``.
223 For example, for ``x + x*y`` with ``repeat=True``, it will have
224 two nodes for ``x``; with ``repeat=False``, it will have one
225 node.
227 .. warning::
228 Even if a node appears twice in the same object like ``x`` in
229 ``Pow(x, x)``, it will still only appear once.
230 Hence, with ``repeat=False``, the number of arrows out of an
231 object might not equal the number of args it has.
233 labelfunc : function, optional
234 A function to create a label for a given leaf node.
236 The default is ``str``.
238 Another good option is ``srepr``.
240 For example with ``str``, the leaf nodes of ``x + 1`` are labeled,
241 ``x`` and ``1``. With ``srepr``, they are labeled ``Symbol('x')``
242 and ``Integer(1)``.
244 **kwargs : optional
245 Additional keyword arguments are included as styles for the graph.
247 Examples
248 ========
250 >>> from sympy import dotprint
251 >>> from sympy.abc import x
252 >>> print(dotprint(x+2)) # doctest: +NORMALIZE_WHITESPACE
253 digraph{
254 <BLANKLINE>
255 # Graph style
256 "ordering"="out"
257 "rankdir"="TD"
258 <BLANKLINE>
259 #########
260 # Nodes #
261 #########
262 <BLANKLINE>
263 "Add(Integer(2), Symbol('x'))_()" ["color"="black", "label"="Add", "shape"="ellipse"];
264 "Integer(2)_(0,)" ["color"="black", "label"="2", "shape"="ellipse"];
265 "Symbol('x')_(1,)" ["color"="black", "label"="x", "shape"="ellipse"];
266 <BLANKLINE>
267 #########
268 # Edges #
269 #########
270 <BLANKLINE>
271 "Add(Integer(2), Symbol('x'))_()" -> "Integer(2)_(0,)";
272 "Add(Integer(2), Symbol('x'))_()" -> "Symbol('x')_(1,)";
273 }
275 """
276 # repeat works by adding a signature tuple to the end of each node for its
277 # position in the graph. For example, for expr = Add(x, Pow(x, 2)), the x in the
278 # Pow will have the tuple (1, 0), meaning it is expr.args[1].args[0].
279 graphstyle = _graphstyle.copy()
280 graphstyle.update(kwargs)
282 nodes = []
283 edges = []
284 def traverse(e, depth, pos=()):
285 nodes.append(dotnode(e, styles, labelfunc=labelfunc, pos=pos, repeat=repeat))
286 if maxdepth and depth >= maxdepth:
287 return
288 edges.extend(dotedges(e, atom=atom, pos=pos, repeat=repeat))
289 [traverse(arg, depth+1, pos + (i,)) for i, arg in enumerate(e.args) if not atom(arg)]
290 traverse(expr, 0)
292 return template%{'graphstyle': attrprint(graphstyle, delimiter='\n'),
293 'nodes': '\n'.join(nodes),
294 'edges': '\n'.join(edges)}