Coverage for /usr/lib/python3/dist-packages/sympy/core/sympify.py: 42%

178 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1"""sympify -- convert objects SymPy internal format""" 

2 

3from __future__ import annotations 

4from typing import Any, Callable 

5 

6from inspect import getmro 

7import string 

8from sympy.core.random import choice 

9 

10from .parameters import global_parameters 

11 

12from sympy.utilities.exceptions import sympy_deprecation_warning 

13from sympy.utilities.iterables import iterable 

14 

15 

16class SympifyError(ValueError): 

17 def __init__(self, expr, base_exc=None): 

18 self.expr = expr 

19 self.base_exc = base_exc 

20 

21 def __str__(self): 

22 if self.base_exc is None: 

23 return "SympifyError: %r" % (self.expr,) 

24 

25 return ("Sympify of expression '%s' failed, because of exception being " 

26 "raised:\n%s: %s" % (self.expr, self.base_exc.__class__.__name__, 

27 str(self.base_exc))) 

28 

29 

30converter: dict[type[Any], Callable[[Any], Basic]] = {} 

31 

32#holds the conversions defined in SymPy itself, i.e. non-user defined conversions 

33_sympy_converter: dict[type[Any], Callable[[Any], Basic]] = {} 

34 

35#alias for clearer use in the library 

36_external_converter = converter 

37 

38class CantSympify: 

39 """ 

40 Mix in this trait to a class to disallow sympification of its instances. 

41 

42 Examples 

43 ======== 

44 

45 >>> from sympy import sympify 

46 >>> from sympy.core.sympify import CantSympify 

47 

48 >>> class Something(dict): 

49 ... pass 

50 ... 

51 >>> sympify(Something()) 

52 {} 

53 

54 >>> class Something(dict, CantSympify): 

55 ... pass 

56 ... 

57 >>> sympify(Something()) 

58 Traceback (most recent call last): 

59 ... 

60 SympifyError: SympifyError: {} 

61 

62 """ 

63 

64 __slots__ = () 

65 

66 

67def _is_numpy_instance(a): 

68 """ 

69 Checks if an object is an instance of a type from the numpy module. 

70 """ 

71 # This check avoids unnecessarily importing NumPy. We check the whole 

72 # __mro__ in case any base type is a numpy type. 

73 return any(type_.__module__ == 'numpy' 

74 for type_ in type(a).__mro__) 

75 

76 

77def _convert_numpy_types(a, **sympify_args): 

78 """ 

79 Converts a numpy datatype input to an appropriate SymPy type. 

80 """ 

81 import numpy as np 

82 if not isinstance(a, np.floating): 

83 if np.iscomplex(a): 

84 return _sympy_converter[complex](a.item()) 

85 else: 

86 return sympify(a.item(), **sympify_args) 

87 else: 

88 try: 

89 from .numbers import Float 

90 prec = np.finfo(a).nmant + 1 

91 # E.g. double precision means prec=53 but nmant=52 

92 # Leading bit of mantissa is always 1, so is not stored 

93 a = str(list(np.reshape(np.asarray(a), 

94 (1, np.size(a)))[0]))[1:-1] 

95 return Float(a, precision=prec) 

96 except NotImplementedError: 

97 raise SympifyError('Translation for numpy float : %s ' 

98 'is not implemented' % a) 

99 

100 

101def sympify(a, locals=None, convert_xor=True, strict=False, rational=False, 

102 evaluate=None): 

103 """ 

104 Converts an arbitrary expression to a type that can be used inside SymPy. 

105 

106 Explanation 

107 =========== 

108 

109 It will convert Python ints into instances of :class:`~.Integer`, floats 

110 into instances of :class:`~.Float`, etc. It is also able to coerce 

111 symbolic expressions which inherit from :class:`~.Basic`. This can be 

112 useful in cooperation with SAGE. 

113 

114 .. warning:: 

115 Note that this function uses ``eval``, and thus shouldn't be used on 

116 unsanitized input. 

117 

118 If the argument is already a type that SymPy understands, it will do 

119 nothing but return that value. This can be used at the beginning of a 

120 function to ensure you are working with the correct type. 

121 

122 Examples 

123 ======== 

124 

125 >>> from sympy import sympify 

126 

127 >>> sympify(2).is_integer 

128 True 

129 >>> sympify(2).is_real 

130 True 

131 

132 >>> sympify(2.0).is_real 

133 True 

134 >>> sympify("2.0").is_real 

135 True 

136 >>> sympify("2e-45").is_real 

137 True 

138 

139 If the expression could not be converted, a SympifyError is raised. 

140 

141 >>> sympify("x***2") 

142 Traceback (most recent call last): 

143 ... 

144 SympifyError: SympifyError: "could not parse 'x***2'" 

145 

146 Locals 

147 ------ 

148 

149 The sympification happens with access to everything that is loaded 

150 by ``from sympy import *``; anything used in a string that is not 

151 defined by that import will be converted to a symbol. In the following, 

152 the ``bitcount`` function is treated as a symbol and the ``O`` is 

153 interpreted as the :class:`~.Order` object (used with series) and it raises 

154 an error when used improperly: 

155 

156 >>> s = 'bitcount(42)' 

157 >>> sympify(s) 

158 bitcount(42) 

159 >>> sympify("O(x)") 

160 O(x) 

161 >>> sympify("O + 1") 

162 Traceback (most recent call last): 

163 ... 

164 TypeError: unbound method... 

165 

166 In order to have ``bitcount`` be recognized it can be imported into a 

167 namespace dictionary and passed as locals: 

168 

169 >>> ns = {} 

170 >>> exec('from sympy.core.evalf import bitcount', ns) 

171 >>> sympify(s, locals=ns) 

172 6 

173 

174 In order to have the ``O`` interpreted as a Symbol, identify it as such 

175 in the namespace dictionary. This can be done in a variety of ways; all 

176 three of the following are possibilities: 

177 

178 >>> from sympy import Symbol 

179 >>> ns["O"] = Symbol("O") # method 1 

180 >>> exec('from sympy.abc import O', ns) # method 2 

181 >>> ns.update(dict(O=Symbol("O"))) # method 3 

182 >>> sympify("O + 1", locals=ns) 

183 O + 1 

184 

185 If you want *all* single-letter and Greek-letter variables to be symbols 

186 then you can use the clashing-symbols dictionaries that have been defined 

187 there as private variables: ``_clash1`` (single-letter variables), 

188 ``_clash2`` (the multi-letter Greek names) or ``_clash`` (both single and 

189 multi-letter names that are defined in ``abc``). 

190 

191 >>> from sympy.abc import _clash1 

192 >>> set(_clash1) # if this fails, see issue #23903 

193 {'E', 'I', 'N', 'O', 'Q', 'S'} 

194 >>> sympify('I & Q', _clash1) 

195 I & Q 

196 

197 Strict 

198 ------ 

199 

200 If the option ``strict`` is set to ``True``, only the types for which an 

201 explicit conversion has been defined are converted. In the other 

202 cases, a SympifyError is raised. 

203 

204 >>> print(sympify(None)) 

205 None 

206 >>> sympify(None, strict=True) 

207 Traceback (most recent call last): 

208 ... 

209 SympifyError: SympifyError: None 

210 

211 .. deprecated:: 1.6 

212 

213 ``sympify(obj)`` automatically falls back to ``str(obj)`` when all 

214 other conversion methods fail, but this is deprecated. ``strict=True`` 

215 will disable this deprecated behavior. See 

216 :ref:`deprecated-sympify-string-fallback`. 

217 

218 Evaluation 

219 ---------- 

220 

221 If the option ``evaluate`` is set to ``False``, then arithmetic and 

222 operators will be converted into their SymPy equivalents and the 

223 ``evaluate=False`` option will be added. Nested ``Add`` or ``Mul`` will 

224 be denested first. This is done via an AST transformation that replaces 

225 operators with their SymPy equivalents, so if an operand redefines any 

226 of those operations, the redefined operators will not be used. If 

227 argument a is not a string, the mathematical expression is evaluated 

228 before being passed to sympify, so adding ``evaluate=False`` will still 

229 return the evaluated result of expression. 

230 

231 >>> sympify('2**2 / 3 + 5') 

232 19/3 

233 >>> sympify('2**2 / 3 + 5', evaluate=False) 

234 2**2/3 + 5 

235 >>> sympify('4/2+7', evaluate=True) 

236 9 

237 >>> sympify('4/2+7', evaluate=False) 

238 4/2 + 7 

239 >>> sympify(4/2+7, evaluate=False) 

240 9.00000000000000 

241 

242 Extending 

243 --------- 

244 

245 To extend ``sympify`` to convert custom objects (not derived from ``Basic``), 

246 just define a ``_sympy_`` method to your class. You can do that even to 

247 classes that you do not own by subclassing or adding the method at runtime. 

248 

249 >>> from sympy import Matrix 

250 >>> class MyList1(object): 

251 ... def __iter__(self): 

252 ... yield 1 

253 ... yield 2 

254 ... return 

255 ... def __getitem__(self, i): return list(self)[i] 

256 ... def _sympy_(self): return Matrix(self) 

257 >>> sympify(MyList1()) 

258 Matrix([ 

259 [1], 

260 [2]]) 

261 

262 If you do not have control over the class definition you could also use the 

263 ``converter`` global dictionary. The key is the class and the value is a 

264 function that takes a single argument and returns the desired SymPy 

265 object, e.g. ``converter[MyList] = lambda x: Matrix(x)``. 

266 

267 >>> class MyList2(object): # XXX Do not do this if you control the class! 

268 ... def __iter__(self): # Use _sympy_! 

269 ... yield 1 

270 ... yield 2 

271 ... return 

272 ... def __getitem__(self, i): return list(self)[i] 

273 >>> from sympy.core.sympify import converter 

274 >>> converter[MyList2] = lambda x: Matrix(x) 

275 >>> sympify(MyList2()) 

276 Matrix([ 

277 [1], 

278 [2]]) 

279 

280 Notes 

281 ===== 

282 

283 The keywords ``rational`` and ``convert_xor`` are only used 

284 when the input is a string. 

285 

286 convert_xor 

287 ----------- 

288 

289 >>> sympify('x^y',convert_xor=True) 

290 x**y 

291 >>> sympify('x^y',convert_xor=False) 

292 x ^ y 

293 

294 rational 

295 -------- 

296 

297 >>> sympify('0.1',rational=False) 

298 0.1 

299 >>> sympify('0.1',rational=True) 

300 1/10 

301 

302 Sometimes autosimplification during sympification results in expressions 

303 that are very different in structure than what was entered. Until such 

304 autosimplification is no longer done, the ``kernS`` function might be of 

305 some use. In the example below you can see how an expression reduces to 

306 $-1$ by autosimplification, but does not do so when ``kernS`` is used. 

307 

308 >>> from sympy.core.sympify import kernS 

309 >>> from sympy.abc import x 

310 >>> -2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x))) - 1 

311 -1 

312 >>> s = '-2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x))) - 1' 

313 >>> sympify(s) 

314 -1 

315 >>> kernS(s) 

316 -2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x))) - 1 

317 

318 Parameters 

319 ========== 

320 

321 a : 

322 - any object defined in SymPy 

323 - standard numeric Python types: ``int``, ``long``, ``float``, ``Decimal`` 

324 - strings (like ``"0.09"``, ``"2e-19"`` or ``'sin(x)'``) 

325 - booleans, including ``None`` (will leave ``None`` unchanged) 

326 - dicts, lists, sets or tuples containing any of the above 

327 

328 convert_xor : bool, optional 

329 If true, treats ``^`` as exponentiation. 

330 If False, treats ``^`` as XOR itself. 

331 Used only when input is a string. 

332 

333 locals : any object defined in SymPy, optional 

334 In order to have strings be recognized it can be imported 

335 into a namespace dictionary and passed as locals. 

336 

337 strict : bool, optional 

338 If the option strict is set to ``True``, only the types for which 

339 an explicit conversion has been defined are converted. In the 

340 other cases, a SympifyError is raised. 

341 

342 rational : bool, optional 

343 If ``True``, converts floats into :class:`~.Rational`. 

344 If ``False``, it lets floats remain as it is. 

345 Used only when input is a string. 

346 

347 evaluate : bool, optional 

348 If False, then arithmetic and operators will be converted into 

349 their SymPy equivalents. If True the expression will be evaluated 

350 and the result will be returned. 

351 

352 """ 

353 # XXX: If a is a Basic subclass rather than instance (e.g. sin rather than 

354 # sin(x)) then a.__sympy__ will be the property. Only on the instance will 

355 # a.__sympy__ give the *value* of the property (True). Since sympify(sin) 

356 # was used for a long time we allow it to pass. However if strict=True as 

357 # is the case in internal calls to _sympify then we only allow 

358 # is_sympy=True. 

359 # 

360 # https://github.com/sympy/sympy/issues/20124 

361 is_sympy = getattr(a, '__sympy__', None) 

362 if is_sympy is True: 

363 return a 

364 elif is_sympy is not None: 

365 if not strict: 

366 return a 

367 else: 

368 raise SympifyError(a) 

369 

370 if isinstance(a, CantSympify): 

371 raise SympifyError(a) 

372 

373 cls = getattr(a, "__class__", None) 

374 

375 #Check if there exists a converter for any of the types in the mro 

376 for superclass in getmro(cls): 

377 #First check for user defined converters 

378 conv = _external_converter.get(superclass) 

379 if conv is None: 

380 #if none exists, check for SymPy defined converters 

381 conv = _sympy_converter.get(superclass) 

382 if conv is not None: 

383 return conv(a) 

384 

385 if cls is type(None): 

386 if strict: 

387 raise SympifyError(a) 

388 else: 

389 return a 

390 

391 if evaluate is None: 

392 evaluate = global_parameters.evaluate 

393 

394 # Support for basic numpy datatypes 

395 if _is_numpy_instance(a): 

396 import numpy as np 

397 if np.isscalar(a): 

398 return _convert_numpy_types(a, locals=locals, 

399 convert_xor=convert_xor, strict=strict, rational=rational, 

400 evaluate=evaluate) 

401 

402 _sympy_ = getattr(a, "_sympy_", None) 

403 if _sympy_ is not None: 

404 try: 

405 return a._sympy_() 

406 # XXX: Catches AttributeError: 'SymPyConverter' object has no 

407 # attribute 'tuple' 

408 # This is probably a bug somewhere but for now we catch it here. 

409 except AttributeError: 

410 pass 

411 

412 if not strict: 

413 # Put numpy array conversion _before_ float/int, see 

414 # <https://github.com/sympy/sympy/issues/13924>. 

415 flat = getattr(a, "flat", None) 

416 if flat is not None: 

417 shape = getattr(a, "shape", None) 

418 if shape is not None: 

419 from sympy.tensor.array import Array 

420 return Array(a.flat, a.shape) # works with e.g. NumPy arrays 

421 

422 if not isinstance(a, str): 

423 if _is_numpy_instance(a): 

424 import numpy as np 

425 assert not isinstance(a, np.number) 

426 if isinstance(a, np.ndarray): 

427 # Scalar arrays (those with zero dimensions) have sympify 

428 # called on the scalar element. 

429 if a.ndim == 0: 

430 try: 

431 return sympify(a.item(), 

432 locals=locals, 

433 convert_xor=convert_xor, 

434 strict=strict, 

435 rational=rational, 

436 evaluate=evaluate) 

437 except SympifyError: 

438 pass 

439 else: 

440 # float and int can coerce size-one numpy arrays to their lone 

441 # element. See issue https://github.com/numpy/numpy/issues/10404. 

442 for coerce in (float, int): 

443 try: 

444 return sympify(coerce(a)) 

445 except (TypeError, ValueError, AttributeError, SympifyError): 

446 continue 

447 

448 if strict: 

449 raise SympifyError(a) 

450 

451 if iterable(a): 

452 try: 

453 return type(a)([sympify(x, locals=locals, convert_xor=convert_xor, 

454 rational=rational, evaluate=evaluate) for x in a]) 

455 except TypeError: 

456 # Not all iterables are rebuildable with their type. 

457 pass 

458 

459 if not isinstance(a, str): 

460 try: 

461 a = str(a) 

462 except Exception as exc: 

463 raise SympifyError(a, exc) 

464 sympy_deprecation_warning( 

465 f""" 

466The string fallback in sympify() is deprecated. 

467 

468To explicitly convert the string form of an object, use 

469sympify(str(obj)). To add define sympify behavior on custom 

470objects, use sympy.core.sympify.converter or define obj._sympy_ 

471(see the sympify() docstring). 

472 

473sympify() performed the string fallback resulting in the following string: 

474 

475{a!r} 

476 """, 

477 deprecated_since_version='1.6', 

478 active_deprecations_target="deprecated-sympify-string-fallback", 

479 ) 

480 

481 from sympy.parsing.sympy_parser import (parse_expr, TokenError, 

482 standard_transformations) 

483 from sympy.parsing.sympy_parser import convert_xor as t_convert_xor 

484 from sympy.parsing.sympy_parser import rationalize as t_rationalize 

485 

486 transformations = standard_transformations 

487 

488 if rational: 

489 transformations += (t_rationalize,) 

490 if convert_xor: 

491 transformations += (t_convert_xor,) 

492 

493 try: 

494 a = a.replace('\n', '') 

495 expr = parse_expr(a, local_dict=locals, transformations=transformations, evaluate=evaluate) 

496 except (TokenError, SyntaxError) as exc: 

497 raise SympifyError('could not parse %r' % a, exc) 

498 

499 return expr 

500 

501 

502def _sympify(a): 

503 """ 

504 Short version of :func:`~.sympify` for internal usage for ``__add__`` and 

505 ``__eq__`` methods where it is ok to allow some things (like Python 

506 integers and floats) in the expression. This excludes things (like strings) 

507 that are unwise to allow into such an expression. 

508 

509 >>> from sympy import Integer 

510 >>> Integer(1) == 1 

511 True 

512 

513 >>> Integer(1) == '1' 

514 False 

515 

516 >>> from sympy.abc import x 

517 >>> x + 1 

518 x + 1 

519 

520 >>> x + '1' 

521 Traceback (most recent call last): 

522 ... 

523 TypeError: unsupported operand type(s) for +: 'Symbol' and 'str' 

524 

525 see: sympify 

526 

527 """ 

528 return sympify(a, strict=True) 

529 

530 

531def kernS(s): 

532 """Use a hack to try keep autosimplification from distributing a 

533 a number into an Add; this modification does not 

534 prevent the 2-arg Mul from becoming an Add, however. 

535 

536 Examples 

537 ======== 

538 

539 >>> from sympy.core.sympify import kernS 

540 >>> from sympy.abc import x, y 

541 

542 The 2-arg Mul distributes a number (or minus sign) across the terms 

543 of an expression, but kernS will prevent that: 

544 

545 >>> 2*(x + y), -(x + 1) 

546 (2*x + 2*y, -x - 1) 

547 >>> kernS('2*(x + y)') 

548 2*(x + y) 

549 >>> kernS('-(x + 1)') 

550 -(x + 1) 

551 

552 If use of the hack fails, the un-hacked string will be passed to sympify... 

553 and you get what you get. 

554 

555 XXX This hack should not be necessary once issue 4596 has been resolved. 

556 """ 

557 hit = False 

558 quoted = '"' in s or "'" in s 

559 if '(' in s and not quoted: 

560 if s.count('(') != s.count(")"): 

561 raise SympifyError('unmatched left parenthesis') 

562 

563 # strip all space from s 

564 s = ''.join(s.split()) 

565 olds = s 

566 # now use space to represent a symbol that 

567 # will 

568 # step 1. turn potential 2-arg Muls into 3-arg versions 

569 # 1a. *( -> * *( 

570 s = s.replace('*(', '* *(') 

571 # 1b. close up exponentials 

572 s = s.replace('** *', '**') 

573 # 2. handle the implied multiplication of a negated 

574 # parenthesized expression in two steps 

575 # 2a: -(...) --> -( *(...) 

576 target = '-( *(' 

577 s = s.replace('-(', target) 

578 # 2b: double the matching closing parenthesis 

579 # -( *(...) --> -( *(...)) 

580 i = nest = 0 

581 assert target.endswith('(') # assumption below 

582 while True: 

583 j = s.find(target, i) 

584 if j == -1: 

585 break 

586 j += len(target) - 1 

587 for j in range(j, len(s)): 

588 if s[j] == "(": 

589 nest += 1 

590 elif s[j] == ")": 

591 nest -= 1 

592 if nest == 0: 

593 break 

594 s = s[:j] + ")" + s[j:] 

595 i = j + 2 # the first char after 2nd ) 

596 if ' ' in s: 

597 # get a unique kern 

598 kern = '_' 

599 while kern in s: 

600 kern += choice(string.ascii_letters + string.digits) 

601 s = s.replace(' ', kern) 

602 hit = kern in s 

603 else: 

604 hit = False 

605 

606 for i in range(2): 

607 try: 

608 expr = sympify(s) 

609 break 

610 except TypeError: # the kern might cause unknown errors... 

611 if hit: 

612 s = olds # maybe it didn't like the kern; use un-kerned s 

613 hit = False 

614 continue 

615 expr = sympify(s) # let original error raise 

616 

617 if not hit: 

618 return expr 

619 

620 from .symbol import Symbol 

621 rep = {Symbol(kern): 1} 

622 def _clear(expr): 

623 if isinstance(expr, (list, tuple, set)): 

624 return type(expr)([_clear(e) for e in expr]) 

625 if hasattr(expr, 'subs'): 

626 return expr.subs(rep, hack2=True) 

627 return expr 

628 expr = _clear(expr) 

629 # hope that kern is not there anymore 

630 return expr 

631 

632 

633# Avoid circular import 

634from .basic import Basic