Coverage for /usr/lib/python3/dist-packages/sympy/core/symbol.py: 47%

330 statements  

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

1from __future__ import annotations 

2 

3from .assumptions import StdFactKB, _assume_defined 

4from .basic import Basic, Atom 

5from .cache import cacheit 

6from .containers import Tuple 

7from .expr import Expr, AtomicExpr 

8from .function import AppliedUndef, FunctionClass 

9from .kind import NumberKind, UndefinedKind 

10from .logic import fuzzy_bool 

11from .singleton import S 

12from .sorting import ordered 

13from .sympify import sympify 

14from sympy.logic.boolalg import Boolean 

15from sympy.utilities.iterables import sift, is_sequence 

16from sympy.utilities.misc import filldedent 

17 

18import string 

19import re as _re 

20import random 

21from itertools import product 

22from typing import Any 

23 

24 

25class Str(Atom): 

26 """ 

27 Represents string in SymPy. 

28 

29 Explanation 

30 =========== 

31 

32 Previously, ``Symbol`` was used where string is needed in ``args`` of SymPy 

33 objects, e.g. denoting the name of the instance. However, since ``Symbol`` 

34 represents mathematical scalar, this class should be used instead. 

35 

36 """ 

37 __slots__ = ('name',) 

38 

39 def __new__(cls, name, **kwargs): 

40 if not isinstance(name, str): 

41 raise TypeError("name should be a string, not %s" % repr(type(name))) 

42 obj = Expr.__new__(cls, **kwargs) 

43 obj.name = name 

44 return obj 

45 

46 def __getnewargs__(self): 

47 return (self.name,) 

48 

49 def _hashable_content(self): 

50 return (self.name,) 

51 

52 

53def _filter_assumptions(kwargs): 

54 """Split the given dict into assumptions and non-assumptions. 

55 Keys are taken as assumptions if they correspond to an 

56 entry in ``_assume_defined``. 

57 """ 

58 assumptions, nonassumptions = map(dict, sift(kwargs.items(), 

59 lambda i: i[0] in _assume_defined, 

60 binary=True)) 

61 Symbol._sanitize(assumptions) 

62 return assumptions, nonassumptions 

63 

64def _symbol(s, matching_symbol=None, **assumptions): 

65 """Return s if s is a Symbol, else if s is a string, return either 

66 the matching_symbol if the names are the same or else a new symbol 

67 with the same assumptions as the matching symbol (or the 

68 assumptions as provided). 

69 

70 Examples 

71 ======== 

72 

73 >>> from sympy import Symbol 

74 >>> from sympy.core.symbol import _symbol 

75 >>> _symbol('y') 

76 y 

77 >>> _.is_real is None 

78 True 

79 >>> _symbol('y', real=True).is_real 

80 True 

81 

82 >>> x = Symbol('x') 

83 >>> _symbol(x, real=True) 

84 x 

85 >>> _.is_real is None # ignore attribute if s is a Symbol 

86 True 

87 

88 Below, the variable sym has the name 'foo': 

89 

90 >>> sym = Symbol('foo', real=True) 

91 

92 Since 'x' is not the same as sym's name, a new symbol is created: 

93 

94 >>> _symbol('x', sym).name 

95 'x' 

96 

97 It will acquire any assumptions give: 

98 

99 >>> _symbol('x', sym, real=False).is_real 

100 False 

101 

102 Since 'foo' is the same as sym's name, sym is returned 

103 

104 >>> _symbol('foo', sym) 

105 foo 

106 

107 Any assumptions given are ignored: 

108 

109 >>> _symbol('foo', sym, real=False).is_real 

110 True 

111 

112 NB: the symbol here may not be the same as a symbol with the same 

113 name defined elsewhere as a result of different assumptions. 

114 

115 See Also 

116 ======== 

117 

118 sympy.core.symbol.Symbol 

119 

120 """ 

121 if isinstance(s, str): 

122 if matching_symbol and matching_symbol.name == s: 

123 return matching_symbol 

124 return Symbol(s, **assumptions) 

125 elif isinstance(s, Symbol): 

126 return s 

127 else: 

128 raise ValueError('symbol must be string for symbol name or Symbol') 

129 

130def uniquely_named_symbol(xname, exprs=(), compare=str, modify=None, **assumptions): 

131 """ 

132 Return a symbol whose name is derivated from *xname* but is unique 

133 from any other symbols in *exprs*. 

134 

135 *xname* and symbol names in *exprs* are passed to *compare* to be 

136 converted to comparable forms. If ``compare(xname)`` is not unique, 

137 it is recursively passed to *modify* until unique name is acquired. 

138 

139 Parameters 

140 ========== 

141 

142 xname : str or Symbol 

143 Base name for the new symbol. 

144 

145 exprs : Expr or iterable of Expr 

146 Expressions whose symbols are compared to *xname*. 

147 

148 compare : function 

149 Unary function which transforms *xname* and symbol names from 

150 *exprs* to comparable form. 

151 

152 modify : function 

153 Unary function which modifies the string. Default is appending 

154 the number, or increasing the number if exists. 

155 

156 Examples 

157 ======== 

158 

159 By default, a number is appended to *xname* to generate unique name. 

160 If the number already exists, it is recursively increased. 

161 

162 >>> from sympy.core.symbol import uniquely_named_symbol, Symbol 

163 >>> uniquely_named_symbol('x', Symbol('x')) 

164 x0 

165 >>> uniquely_named_symbol('x', (Symbol('x'), Symbol('x0'))) 

166 x1 

167 >>> uniquely_named_symbol('x0', (Symbol('x1'), Symbol('x0'))) 

168 x2 

169 

170 Name generation can be controlled by passing *modify* parameter. 

171 

172 >>> from sympy.abc import x 

173 >>> uniquely_named_symbol('x', x, modify=lambda s: 2*s) 

174 xx 

175 

176 """ 

177 def numbered_string_incr(s, start=0): 

178 if not s: 

179 return str(start) 

180 i = len(s) - 1 

181 while i != -1: 

182 if not s[i].isdigit(): 

183 break 

184 i -= 1 

185 n = str(int(s[i + 1:] or start - 1) + 1) 

186 return s[:i + 1] + n 

187 

188 default = None 

189 if is_sequence(xname): 

190 xname, default = xname 

191 x = compare(xname) 

192 if not exprs: 

193 return _symbol(x, default, **assumptions) 

194 if not is_sequence(exprs): 

195 exprs = [exprs] 

196 names = set().union( 

197 [i.name for e in exprs for i in e.atoms(Symbol)] + 

198 [i.func.name for e in exprs for i in e.atoms(AppliedUndef)]) 

199 if modify is None: 

200 modify = numbered_string_incr 

201 while any(x == compare(s) for s in names): 

202 x = modify(x) 

203 return _symbol(x, default, **assumptions) 

204_uniquely_named_symbol = uniquely_named_symbol 

205 

206class Symbol(AtomicExpr, Boolean): 

207 """ 

208 Assumptions: 

209 commutative = True 

210 

211 You can override the default assumptions in the constructor. 

212 

213 Examples 

214 ======== 

215 

216 >>> from sympy import symbols 

217 >>> A,B = symbols('A,B', commutative = False) 

218 >>> bool(A*B != B*A) 

219 True 

220 >>> bool(A*B*2 == 2*A*B) == True # multiplication by scalars is commutative 

221 True 

222 

223 """ 

224 

225 is_comparable = False 

226 

227 __slots__ = ('name', '_assumptions_orig', '_assumptions0') 

228 

229 name: str 

230 

231 is_Symbol = True 

232 is_symbol = True 

233 

234 @property 

235 def kind(self): 

236 if self.is_commutative: 

237 return NumberKind 

238 return UndefinedKind 

239 

240 @property 

241 def _diff_wrt(self): 

242 """Allow derivatives wrt Symbols. 

243 

244 Examples 

245 ======== 

246 

247 >>> from sympy import Symbol 

248 >>> x = Symbol('x') 

249 >>> x._diff_wrt 

250 True 

251 """ 

252 return True 

253 

254 @staticmethod 

255 def _sanitize(assumptions, obj=None): 

256 """Remove None, convert values to bool, check commutativity *in place*. 

257 """ 

258 

259 # be strict about commutativity: cannot be None 

260 is_commutative = fuzzy_bool(assumptions.get('commutative', True)) 

261 if is_commutative is None: 

262 whose = '%s ' % obj.__name__ if obj else '' 

263 raise ValueError( 

264 '%scommutativity must be True or False.' % whose) 

265 

266 # sanitize other assumptions so 1 -> True and 0 -> False 

267 for key in list(assumptions.keys()): 

268 v = assumptions[key] 

269 if v is None: 

270 assumptions.pop(key) 

271 continue 

272 assumptions[key] = bool(v) 

273 

274 def _merge(self, assumptions): 

275 base = self.assumptions0 

276 for k in set(assumptions) & set(base): 

277 if assumptions[k] != base[k]: 

278 raise ValueError(filldedent(''' 

279 non-matching assumptions for %s: existing value 

280 is %s and new value is %s''' % ( 

281 k, base[k], assumptions[k]))) 

282 base.update(assumptions) 

283 return base 

284 

285 def __new__(cls, name, **assumptions): 

286 """Symbols are identified by name and assumptions:: 

287 

288 >>> from sympy import Symbol 

289 >>> Symbol("x") == Symbol("x") 

290 True 

291 >>> Symbol("x", real=True) == Symbol("x", real=False) 

292 False 

293 

294 """ 

295 cls._sanitize(assumptions, cls) 

296 return Symbol.__xnew_cached_(cls, name, **assumptions) 

297 

298 @staticmethod 

299 def __xnew__(cls, name, **assumptions): # never cached (e.g. dummy) 

300 if not isinstance(name, str): 

301 raise TypeError("name should be a string, not %s" % repr(type(name))) 

302 

303 # This is retained purely so that srepr can include commutative=True if 

304 # that was explicitly specified but not if it was not. Ideally srepr 

305 # should not distinguish these cases because the symbols otherwise 

306 # compare equal and are considered equivalent. 

307 # 

308 # See https://github.com/sympy/sympy/issues/8873 

309 # 

310 assumptions_orig = assumptions.copy() 

311 

312 # The only assumption that is assumed by default is comutative=True: 

313 assumptions.setdefault('commutative', True) 

314 

315 assumptions_kb = StdFactKB(assumptions) 

316 assumptions0 = dict(assumptions_kb) 

317 

318 obj = Expr.__new__(cls) 

319 obj.name = name 

320 

321 obj._assumptions = assumptions_kb 

322 obj._assumptions_orig = assumptions_orig 

323 obj._assumptions0 = assumptions0 

324 

325 # The three assumptions dicts are all a little different: 

326 # 

327 # >>> from sympy import Symbol 

328 # >>> x = Symbol('x', finite=True) 

329 # >>> x.is_positive # query an assumption 

330 # >>> x._assumptions 

331 # {'finite': True, 'infinite': False, 'commutative': True, 'positive': None} 

332 # >>> x._assumptions0 

333 # {'finite': True, 'infinite': False, 'commutative': True} 

334 # >>> x._assumptions_orig 

335 # {'finite': True} 

336 # 

337 # Two symbols with the same name are equal if their _assumptions0 are 

338 # the same. Arguably it should be _assumptions_orig that is being 

339 # compared because that is more transparent to the user (it is 

340 # what was passed to the constructor modulo changes made by _sanitize). 

341 

342 return obj 

343 

344 @staticmethod 

345 @cacheit 

346 def __xnew_cached_(cls, name, **assumptions): # symbols are always cached 

347 return Symbol.__xnew__(cls, name, **assumptions) 

348 

349 def __getnewargs_ex__(self): 

350 return ((self.name,), self._assumptions_orig) 

351 

352 # NOTE: __setstate__ is not needed for pickles created by __getnewargs_ex__ 

353 # but was used before Symbol was changed to use __getnewargs_ex__ in v1.9. 

354 # Pickles created in previous SymPy versions will still need __setstate__ 

355 # so that they can be unpickled in SymPy > v1.9. 

356 

357 def __setstate__(self, state): 

358 for name, value in state.items(): 

359 setattr(self, name, value) 

360 

361 def _hashable_content(self): 

362 # Note: user-specified assumptions not hashed, just derived ones 

363 return (self.name,) + tuple(sorted(self.assumptions0.items())) 

364 

365 def _eval_subs(self, old, new): 

366 if old.is_Pow: 

367 from sympy.core.power import Pow 

368 return Pow(self, S.One, evaluate=False)._eval_subs(old, new) 

369 

370 def _eval_refine(self, assumptions): 

371 return self 

372 

373 @property 

374 def assumptions0(self): 

375 return self._assumptions0.copy() 

376 

377 @cacheit 

378 def sort_key(self, order=None): 

379 return self.class_key(), (1, (self.name,)), S.One.sort_key(), S.One 

380 

381 def as_dummy(self): 

382 # only put commutativity in explicitly if it is False 

383 return Dummy(self.name) if self.is_commutative is not False \ 

384 else Dummy(self.name, commutative=self.is_commutative) 

385 

386 def as_real_imag(self, deep=True, **hints): 

387 if hints.get('ignore') == self: 

388 return None 

389 else: 

390 from sympy.functions.elementary.complexes import im, re 

391 return (re(self), im(self)) 

392 

393 def is_constant(self, *wrt, **flags): 

394 if not wrt: 

395 return False 

396 return self not in wrt 

397 

398 @property 

399 def free_symbols(self): 

400 return {self} 

401 

402 binary_symbols = free_symbols # in this case, not always 

403 

404 def as_set(self): 

405 return S.UniversalSet 

406 

407 

408class Dummy(Symbol): 

409 """Dummy symbols are each unique, even if they have the same name: 

410 

411 Examples 

412 ======== 

413 

414 >>> from sympy import Dummy 

415 >>> Dummy("x") == Dummy("x") 

416 False 

417 

418 If a name is not supplied then a string value of an internal count will be 

419 used. This is useful when a temporary variable is needed and the name 

420 of the variable used in the expression is not important. 

421 

422 >>> Dummy() #doctest: +SKIP 

423 _Dummy_10 

424 

425 """ 

426 

427 # In the rare event that a Dummy object needs to be recreated, both the 

428 # `name` and `dummy_index` should be passed. This is used by `srepr` for 

429 # example: 

430 # >>> d1 = Dummy() 

431 # >>> d2 = eval(srepr(d1)) 

432 # >>> d2 == d1 

433 # True 

434 # 

435 # If a new session is started between `srepr` and `eval`, there is a very 

436 # small chance that `d2` will be equal to a previously-created Dummy. 

437 

438 _count = 0 

439 _prng = random.Random() 

440 _base_dummy_index = _prng.randint(10**6, 9*10**6) 

441 

442 __slots__ = ('dummy_index',) 

443 

444 is_Dummy = True 

445 

446 def __new__(cls, name=None, dummy_index=None, **assumptions): 

447 if dummy_index is not None: 

448 assert name is not None, "If you specify a dummy_index, you must also provide a name" 

449 

450 if name is None: 

451 name = "Dummy_" + str(Dummy._count) 

452 

453 if dummy_index is None: 

454 dummy_index = Dummy._base_dummy_index + Dummy._count 

455 Dummy._count += 1 

456 

457 cls._sanitize(assumptions, cls) 

458 obj = Symbol.__xnew__(cls, name, **assumptions) 

459 

460 obj.dummy_index = dummy_index 

461 

462 return obj 

463 

464 def __getnewargs_ex__(self): 

465 return ((self.name, self.dummy_index), self._assumptions_orig) 

466 

467 @cacheit 

468 def sort_key(self, order=None): 

469 return self.class_key(), ( 

470 2, (self.name, self.dummy_index)), S.One.sort_key(), S.One 

471 

472 def _hashable_content(self): 

473 return Symbol._hashable_content(self) + (self.dummy_index,) 

474 

475 

476class Wild(Symbol): 

477 """ 

478 A Wild symbol matches anything, or anything 

479 without whatever is explicitly excluded. 

480 

481 Parameters 

482 ========== 

483 

484 name : str 

485 Name of the Wild instance. 

486 

487 exclude : iterable, optional 

488 Instances in ``exclude`` will not be matched. 

489 

490 properties : iterable of functions, optional 

491 Functions, each taking an expressions as input 

492 and returns a ``bool``. All functions in ``properties`` 

493 need to return ``True`` in order for the Wild instance 

494 to match the expression. 

495 

496 Examples 

497 ======== 

498 

499 >>> from sympy import Wild, WildFunction, cos, pi 

500 >>> from sympy.abc import x, y, z 

501 >>> a = Wild('a') 

502 >>> x.match(a) 

503 {a_: x} 

504 >>> pi.match(a) 

505 {a_: pi} 

506 >>> (3*x**2).match(a*x) 

507 {a_: 3*x} 

508 >>> cos(x).match(a) 

509 {a_: cos(x)} 

510 >>> b = Wild('b', exclude=[x]) 

511 >>> (3*x**2).match(b*x) 

512 >>> b.match(a) 

513 {a_: b_} 

514 >>> A = WildFunction('A') 

515 >>> A.match(a) 

516 {a_: A_} 

517 

518 Tips 

519 ==== 

520 

521 When using Wild, be sure to use the exclude 

522 keyword to make the pattern more precise. 

523 Without the exclude pattern, you may get matches 

524 that are technically correct, but not what you 

525 wanted. For example, using the above without 

526 exclude: 

527 

528 >>> from sympy import symbols 

529 >>> a, b = symbols('a b', cls=Wild) 

530 >>> (2 + 3*y).match(a*x + b*y) 

531 {a_: 2/x, b_: 3} 

532 

533 This is technically correct, because 

534 (2/x)*x + 3*y == 2 + 3*y, but you probably 

535 wanted it to not match at all. The issue is that 

536 you really did not want a and b to include x and y, 

537 and the exclude parameter lets you specify exactly 

538 this. With the exclude parameter, the pattern will 

539 not match. 

540 

541 >>> a = Wild('a', exclude=[x, y]) 

542 >>> b = Wild('b', exclude=[x, y]) 

543 >>> (2 + 3*y).match(a*x + b*y) 

544 

545 Exclude also helps remove ambiguity from matches. 

546 

547 >>> E = 2*x**3*y*z 

548 >>> a, b = symbols('a b', cls=Wild) 

549 >>> E.match(a*b) 

550 {a_: 2*y*z, b_: x**3} 

551 >>> a = Wild('a', exclude=[x, y]) 

552 >>> E.match(a*b) 

553 {a_: z, b_: 2*x**3*y} 

554 >>> a = Wild('a', exclude=[x, y, z]) 

555 >>> E.match(a*b) 

556 {a_: 2, b_: x**3*y*z} 

557 

558 Wild also accepts a ``properties`` parameter: 

559 

560 >>> a = Wild('a', properties=[lambda k: k.is_Integer]) 

561 >>> E.match(a*b) 

562 {a_: 2, b_: x**3*y*z} 

563 

564 """ 

565 is_Wild = True 

566 

567 __slots__ = ('exclude', 'properties') 

568 

569 def __new__(cls, name, exclude=(), properties=(), **assumptions): 

570 exclude = tuple([sympify(x) for x in exclude]) 

571 properties = tuple(properties) 

572 cls._sanitize(assumptions, cls) 

573 return Wild.__xnew__(cls, name, exclude, properties, **assumptions) 

574 

575 def __getnewargs__(self): 

576 return (self.name, self.exclude, self.properties) 

577 

578 @staticmethod 

579 @cacheit 

580 def __xnew__(cls, name, exclude, properties, **assumptions): 

581 obj = Symbol.__xnew__(cls, name, **assumptions) 

582 obj.exclude = exclude 

583 obj.properties = properties 

584 return obj 

585 

586 def _hashable_content(self): 

587 return super()._hashable_content() + (self.exclude, self.properties) 

588 

589 # TODO add check against another Wild 

590 def matches(self, expr, repl_dict=None, old=False): 

591 if any(expr.has(x) for x in self.exclude): 

592 return None 

593 if not all(f(expr) for f in self.properties): 

594 return None 

595 if repl_dict is None: 

596 repl_dict = {} 

597 else: 

598 repl_dict = repl_dict.copy() 

599 repl_dict[self] = expr 

600 return repl_dict 

601 

602 

603_range = _re.compile('([0-9]*:[0-9]+|[a-zA-Z]?:[a-zA-Z])') 

604 

605 

606def symbols(names, *, cls=Symbol, **args) -> Any: 

607 r""" 

608 Transform strings into instances of :class:`Symbol` class. 

609 

610 :func:`symbols` function returns a sequence of symbols with names taken 

611 from ``names`` argument, which can be a comma or whitespace delimited 

612 string, or a sequence of strings:: 

613 

614 >>> from sympy import symbols, Function 

615 

616 >>> x, y, z = symbols('x,y,z') 

617 >>> a, b, c = symbols('a b c') 

618 

619 The type of output is dependent on the properties of input arguments:: 

620 

621 >>> symbols('x') 

622 x 

623 >>> symbols('x,') 

624 (x,) 

625 >>> symbols('x,y') 

626 (x, y) 

627 >>> symbols(('a', 'b', 'c')) 

628 (a, b, c) 

629 >>> symbols(['a', 'b', 'c']) 

630 [a, b, c] 

631 >>> symbols({'a', 'b', 'c'}) 

632 {a, b, c} 

633 

634 If an iterable container is needed for a single symbol, set the ``seq`` 

635 argument to ``True`` or terminate the symbol name with a comma:: 

636 

637 >>> symbols('x', seq=True) 

638 (x,) 

639 

640 To reduce typing, range syntax is supported to create indexed symbols. 

641 Ranges are indicated by a colon and the type of range is determined by 

642 the character to the right of the colon. If the character is a digit 

643 then all contiguous digits to the left are taken as the nonnegative 

644 starting value (or 0 if there is no digit left of the colon) and all 

645 contiguous digits to the right are taken as 1 greater than the ending 

646 value:: 

647 

648 >>> symbols('x:10') 

649 (x0, x1, x2, x3, x4, x5, x6, x7, x8, x9) 

650 

651 >>> symbols('x5:10') 

652 (x5, x6, x7, x8, x9) 

653 >>> symbols('x5(:2)') 

654 (x50, x51) 

655 

656 >>> symbols('x5:10,y:5') 

657 (x5, x6, x7, x8, x9, y0, y1, y2, y3, y4) 

658 

659 >>> symbols(('x5:10', 'y:5')) 

660 ((x5, x6, x7, x8, x9), (y0, y1, y2, y3, y4)) 

661 

662 If the character to the right of the colon is a letter, then the single 

663 letter to the left (or 'a' if there is none) is taken as the start 

664 and all characters in the lexicographic range *through* the letter to 

665 the right are used as the range:: 

666 

667 >>> symbols('x:z') 

668 (x, y, z) 

669 >>> symbols('x:c') # null range 

670 () 

671 >>> symbols('x(:c)') 

672 (xa, xb, xc) 

673 

674 >>> symbols(':c') 

675 (a, b, c) 

676 

677 >>> symbols('a:d, x:z') 

678 (a, b, c, d, x, y, z) 

679 

680 >>> symbols(('a:d', 'x:z')) 

681 ((a, b, c, d), (x, y, z)) 

682 

683 Multiple ranges are supported; contiguous numerical ranges should be 

684 separated by parentheses to disambiguate the ending number of one 

685 range from the starting number of the next:: 

686 

687 >>> symbols('x:2(1:3)') 

688 (x01, x02, x11, x12) 

689 >>> symbols(':3:2') # parsing is from left to right 

690 (00, 01, 10, 11, 20, 21) 

691 

692 Only one pair of parentheses surrounding ranges are removed, so to 

693 include parentheses around ranges, double them. And to include spaces, 

694 commas, or colons, escape them with a backslash:: 

695 

696 >>> symbols('x((a:b))') 

697 (x(a), x(b)) 

698 >>> symbols(r'x(:1\,:2)') # or r'x((:1)\,(:2))' 

699 (x(0,0), x(0,1)) 

700 

701 All newly created symbols have assumptions set according to ``args``:: 

702 

703 >>> a = symbols('a', integer=True) 

704 >>> a.is_integer 

705 True 

706 

707 >>> x, y, z = symbols('x,y,z', real=True) 

708 >>> x.is_real and y.is_real and z.is_real 

709 True 

710 

711 Despite its name, :func:`symbols` can create symbol-like objects like 

712 instances of Function or Wild classes. To achieve this, set ``cls`` 

713 keyword argument to the desired type:: 

714 

715 >>> symbols('f,g,h', cls=Function) 

716 (f, g, h) 

717 

718 >>> type(_[0]) 

719 <class 'sympy.core.function.UndefinedFunction'> 

720 

721 """ 

722 result = [] 

723 

724 if isinstance(names, str): 

725 marker = 0 

726 splitters = r'\,', r'\:', r'\ ' 

727 literals: list[tuple[str, str]] = [] 

728 for splitter in splitters: 

729 if splitter in names: 

730 while chr(marker) in names: 

731 marker += 1 

732 lit_char = chr(marker) 

733 marker += 1 

734 names = names.replace(splitter, lit_char) 

735 literals.append((lit_char, splitter[1:])) 

736 def literal(s): 

737 if literals: 

738 for c, l in literals: 

739 s = s.replace(c, l) 

740 return s 

741 

742 names = names.strip() 

743 as_seq = names.endswith(',') 

744 if as_seq: 

745 names = names[:-1].rstrip() 

746 if not names: 

747 raise ValueError('no symbols given') 

748 

749 # split on commas 

750 names = [n.strip() for n in names.split(',')] 

751 if not all(n for n in names): 

752 raise ValueError('missing symbol between commas') 

753 # split on spaces 

754 for i in range(len(names) - 1, -1, -1): 

755 names[i: i + 1] = names[i].split() 

756 

757 seq = args.pop('seq', as_seq) 

758 

759 for name in names: 

760 if not name: 

761 raise ValueError('missing symbol') 

762 

763 if ':' not in name: 

764 symbol = cls(literal(name), **args) 

765 result.append(symbol) 

766 continue 

767 

768 split: list[str] = _range.split(name) 

769 split_list: list[list[str]] = [] 

770 # remove 1 layer of bounding parentheses around ranges 

771 for i in range(len(split) - 1): 

772 if i and ':' in split[i] and split[i] != ':' and \ 

773 split[i - 1].endswith('(') and \ 

774 split[i + 1].startswith(')'): 

775 split[i - 1] = split[i - 1][:-1] 

776 split[i + 1] = split[i + 1][1:] 

777 for s in split: 

778 if ':' in s: 

779 if s.endswith(':'): 

780 raise ValueError('missing end range') 

781 a, b = s.split(':') 

782 if b[-1] in string.digits: 

783 a_i = 0 if not a else int(a) 

784 b_i = int(b) 

785 split_list.append([str(c) for c in range(a_i, b_i)]) 

786 else: 

787 a = a or 'a' 

788 split_list.append([string.ascii_letters[c] for c in range( 

789 string.ascii_letters.index(a), 

790 string.ascii_letters.index(b) + 1)]) # inclusive 

791 if not split_list[-1]: 

792 break 

793 else: 

794 split_list.append([s]) 

795 else: 

796 seq = True 

797 if len(split_list) == 1: 

798 names = split_list[0] 

799 else: 

800 names = [''.join(s) for s in product(*split_list)] 

801 if literals: 

802 result.extend([cls(literal(s), **args) for s in names]) 

803 else: 

804 result.extend([cls(s, **args) for s in names]) 

805 

806 if not seq and len(result) <= 1: 

807 if not result: 

808 return () 

809 return result[0] 

810 

811 return tuple(result) 

812 else: 

813 for name in names: 

814 result.append(symbols(name, cls=cls, **args)) 

815 

816 return type(names)(result) 

817 

818 

819def var(names, **args): 

820 """ 

821 Create symbols and inject them into the global namespace. 

822 

823 Explanation 

824 =========== 

825 

826 This calls :func:`symbols` with the same arguments and puts the results 

827 into the *global* namespace. It's recommended not to use :func:`var` in 

828 library code, where :func:`symbols` has to be used:: 

829 

830 Examples 

831 ======== 

832 

833 >>> from sympy import var 

834 

835 >>> var('x') 

836 x 

837 >>> x # noqa: F821 

838 x 

839 

840 >>> var('a,ab,abc') 

841 (a, ab, abc) 

842 >>> abc # noqa: F821 

843 abc 

844 

845 >>> var('x,y', real=True) 

846 (x, y) 

847 >>> x.is_real and y.is_real # noqa: F821 

848 True 

849 

850 See :func:`symbols` documentation for more details on what kinds of 

851 arguments can be passed to :func:`var`. 

852 

853 """ 

854 def traverse(symbols, frame): 

855 """Recursively inject symbols to the global namespace. """ 

856 for symbol in symbols: 

857 if isinstance(symbol, Basic): 

858 frame.f_globals[symbol.name] = symbol 

859 elif isinstance(symbol, FunctionClass): 

860 frame.f_globals[symbol.__name__] = symbol 

861 else: 

862 traverse(symbol, frame) 

863 

864 from inspect import currentframe 

865 frame = currentframe().f_back 

866 

867 try: 

868 syms = symbols(names, **args) 

869 

870 if syms is not None: 

871 if isinstance(syms, Basic): 

872 frame.f_globals[syms.name] = syms 

873 elif isinstance(syms, FunctionClass): 

874 frame.f_globals[syms.__name__] = syms 

875 else: 

876 traverse(syms, frame) 

877 finally: 

878 del frame # break cyclic dependencies as stated in inspect docs 

879 

880 return syms 

881 

882def disambiguate(*iter): 

883 """ 

884 Return a Tuple containing the passed expressions with symbols 

885 that appear the same when printed replaced with numerically 

886 subscripted symbols, and all Dummy symbols replaced with Symbols. 

887 

888 Parameters 

889 ========== 

890 

891 iter: list of symbols or expressions. 

892 

893 Examples 

894 ======== 

895 

896 >>> from sympy.core.symbol import disambiguate 

897 >>> from sympy import Dummy, Symbol, Tuple 

898 >>> from sympy.abc import y 

899 

900 >>> tup = Symbol('_x'), Dummy('x'), Dummy('x') 

901 >>> disambiguate(*tup) 

902 (x_2, x, x_1) 

903 

904 >>> eqs = Tuple(Symbol('x')/y, Dummy('x')/y) 

905 >>> disambiguate(*eqs) 

906 (x_1/y, x/y) 

907 

908 >>> ix = Symbol('x', integer=True) 

909 >>> vx = Symbol('x') 

910 >>> disambiguate(vx + ix) 

911 (x + x_1,) 

912 

913 To make your own mapping of symbols to use, pass only the free symbols 

914 of the expressions and create a dictionary: 

915 

916 >>> free = eqs.free_symbols 

917 >>> mapping = dict(zip(free, disambiguate(*free))) 

918 >>> eqs.xreplace(mapping) 

919 (x_1/y, x/y) 

920 

921 """ 

922 new_iter = Tuple(*iter) 

923 key = lambda x:tuple(sorted(x.assumptions0.items())) 

924 syms = ordered(new_iter.free_symbols, keys=key) 

925 mapping = {} 

926 for s in syms: 

927 mapping.setdefault(str(s).lstrip('_'), []).append(s) 

928 reps = {} 

929 for k in mapping: 

930 # the first or only symbol doesn't get subscripted but make 

931 # sure that it's a Symbol, not a Dummy 

932 mapk0 = Symbol("%s" % (k), **mapping[k][0].assumptions0) 

933 if mapping[k][0] != mapk0: 

934 reps[mapping[k][0]] = mapk0 

935 # the others get subscripts (and are made into Symbols) 

936 skip = 0 

937 for i in range(1, len(mapping[k])): 

938 while True: 

939 name = "%s_%i" % (k, i + skip) 

940 if name not in mapping: 

941 break 

942 skip += 1 

943 ki = mapping[k][i] 

944 reps[ki] = Symbol(name, **ki.assumptions0) 

945 return new_iter.xreplace(reps)