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

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 

6 

7__all__ = ['dotprint'] 

8 

9default_styles = ( 

10 (Basic, {'color': 'blue', 'shape': 'ellipse'}), 

11 (Expr, {'color': 'black'}) 

12) 

13 

14slotClasses = (Symbol, Integer, Rational, Float) 

15def purestr(x, with_args=False): 

16 """A string that follows ```obj = type(obj)(*obj.args)``` exactly. 

17 

18 Parameters 

19 ========== 

20 

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. 

25 

26 If ``False``, there will not be a second argument for the 

27 return. 

28 

29 Default is ``False`` 

30 

31 Examples 

32 ======== 

33 

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 

38 

39 Applying ``purestr`` for basic symbolic object: 

40 >>> code = purestr(Symbol('x')) 

41 >>> code 

42 "Symbol('x')" 

43 >>> eval(code) == Symbol('x') 

44 True 

45 

46 For basic numeric object: 

47 >>> purestr(Float(2)) 

48 "Float('2.0', precision=53)" 

49 

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 

56 

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 

76 

77 

78def styleof(expr, styles=default_styles): 

79 """ Merge style dictionaries in order 

80 

81 Examples 

82 ======== 

83 

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'})] 

88 

89 >>> styleof(Basic(S(1)), styles) 

90 {'color': 'blue', 'shape': 'ellipse'} 

91 

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 

101 

102 

103def attrprint(d, delimiter=', '): 

104 """ Print a dictionary of attributes 

105 

106 Examples 

107 ======== 

108 

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())) 

114 

115 

116def dotnode(expr, styles=default_styles, labelfunc=str, pos=(), repeat=True): 

117 """ String defining a node 

118 

119 Examples 

120 ======== 

121 

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) 

128 

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)) 

138 

139 

140def dotedges(expr, atom=lambda x: not isinstance(x, Basic), pos=(), repeat=True): 

141 """ List of strings for all expr->expr.arg pairs 

142 

143 See the docstring of dotprint for explanations of the options. 

144 

145 Examples 

146 ======== 

147 

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] 

164 

165template = \ 

166"""digraph{ 

167 

168# Graph style 

169%(graphstyle)s 

170 

171######### 

172# Nodes # 

173######### 

174 

175%(nodes)s 

176 

177######### 

178# Edges # 

179######### 

180 

181%(edges)s 

182}""" 

183 

184_graphstyle = {'rankdir': 'TD', 'ordering': 'out'} 

185 

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 

190 

191 Parameters 

192 ========== 

193 

194 styles : list of lists composed of (Class, mapping), optional 

195 Styles for different classes. 

196 

197 The default is 

198 

199 .. code-block:: python 

200 

201 ( 

202 (Basic, {'color': 'blue', 'shape': 'ellipse'}), 

203 (Expr, {'color': 'black'}) 

204 ) 

205 

206 atom : function, optional 

207 Function used to determine if an arg is an atom. 

208 

209 A good choice is ``lambda x: not x.args``. 

210 

211 The default is ``lambda x: not isinstance(x, Basic)``. 

212 

213 maxdepth : integer, optional 

214 The maximum depth. 

215 

216 The default is ``None``, meaning no limit. 

217 

218 repeat : boolean, optional 

219 Whether to use different nodes for common subexpressions. 

220 

221 The default is ``True``. 

222 

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. 

226 

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. 

232 

233 labelfunc : function, optional 

234 A function to create a label for a given leaf node. 

235 

236 The default is ``str``. 

237 

238 Another good option is ``srepr``. 

239 

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)``. 

243 

244 **kwargs : optional 

245 Additional keyword arguments are included as styles for the graph. 

246 

247 Examples 

248 ======== 

249 

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 } 

274 

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) 

281 

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) 

291 

292 return template%{'graphstyle': attrprint(graphstyle, delimiter='\n'), 

293 'nodes': '\n'.join(nodes), 

294 'edges': '\n'.join(edges)}