Coverage for /usr/lib/python3/dist-packages/sympy/sets/handlers/intersection.py: 20%

332 statements  

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

1from sympy.core.function import Lambda, expand_complex 

2from sympy.core.mul import Mul 

3from sympy.core.numbers import ilcm 

4from sympy.core.relational import Eq 

5from sympy.core.singleton import S 

6from sympy.core.symbol import (Dummy, symbols) 

7from sympy.core.sorting import ordered 

8from sympy.functions.elementary.complexes import sign 

9from sympy.functions.elementary.integers import floor, ceiling 

10from sympy.sets.fancysets import ComplexRegion 

11from sympy.sets.sets import (FiniteSet, Intersection, Interval, Set, Union) 

12from sympy.multipledispatch import Dispatcher 

13from sympy.sets.conditionset import ConditionSet 

14from sympy.sets.fancysets import (Integers, Naturals, Reals, Range, 

15 ImageSet, Rationals) 

16from sympy.sets.sets import EmptySet, UniversalSet, imageset, ProductSet 

17from sympy.simplify.radsimp import numer 

18 

19 

20intersection_sets = Dispatcher('intersection_sets') 

21 

22 

23@intersection_sets.register(ConditionSet, ConditionSet) 

24def _(a, b): 

25 return None 

26 

27@intersection_sets.register(ConditionSet, Set) 

28def _(a, b): 

29 return ConditionSet(a.sym, a.condition, Intersection(a.base_set, b)) 

30 

31@intersection_sets.register(Naturals, Integers) 

32def _(a, b): 

33 return a 

34 

35@intersection_sets.register(Naturals, Naturals) 

36def _(a, b): 

37 return a if a is S.Naturals else b 

38 

39@intersection_sets.register(Interval, Naturals) 

40def _(a, b): 

41 return intersection_sets(b, a) 

42 

43@intersection_sets.register(ComplexRegion, Set) 

44def _(self, other): 

45 if other.is_ComplexRegion: 

46 # self in rectangular form 

47 if (not self.polar) and (not other.polar): 

48 return ComplexRegion(Intersection(self.sets, other.sets)) 

49 

50 # self in polar form 

51 elif self.polar and other.polar: 

52 r1, theta1 = self.a_interval, self.b_interval 

53 r2, theta2 = other.a_interval, other.b_interval 

54 new_r_interval = Intersection(r1, r2) 

55 new_theta_interval = Intersection(theta1, theta2) 

56 

57 # 0 and 2*Pi means the same 

58 if ((2*S.Pi in theta1 and S.Zero in theta2) or 

59 (2*S.Pi in theta2 and S.Zero in theta1)): 

60 new_theta_interval = Union(new_theta_interval, 

61 FiniteSet(0)) 

62 return ComplexRegion(new_r_interval*new_theta_interval, 

63 polar=True) 

64 

65 

66 if other.is_subset(S.Reals): 

67 new_interval = [] 

68 x = symbols("x", cls=Dummy, real=True) 

69 

70 # self in rectangular form 

71 if not self.polar: 

72 for element in self.psets: 

73 if S.Zero in element.args[1]: 

74 new_interval.append(element.args[0]) 

75 new_interval = Union(*new_interval) 

76 return Intersection(new_interval, other) 

77 

78 # self in polar form 

79 elif self.polar: 

80 for element in self.psets: 

81 if S.Zero in element.args[1]: 

82 new_interval.append(element.args[0]) 

83 if S.Pi in element.args[1]: 

84 new_interval.append(ImageSet(Lambda(x, -x), element.args[0])) 

85 if S.Zero in element.args[0]: 

86 new_interval.append(FiniteSet(0)) 

87 new_interval = Union(*new_interval) 

88 return Intersection(new_interval, other) 

89 

90@intersection_sets.register(Integers, Reals) 

91def _(a, b): 

92 return a 

93 

94@intersection_sets.register(Range, Interval) 

95def _(a, b): 

96 # Check that there are no symbolic arguments 

97 if not all(i.is_number for i in a.args + b.args[:2]): 

98 return 

99 

100 # In case of null Range, return an EmptySet. 

101 if a.size == 0: 

102 return S.EmptySet 

103 

104 # trim down to self's size, and represent 

105 # as a Range with step 1. 

106 start = ceiling(max(b.inf, a.inf)) 

107 if start not in b: 

108 start += 1 

109 end = floor(min(b.sup, a.sup)) 

110 if end not in b: 

111 end -= 1 

112 return intersection_sets(a, Range(start, end + 1)) 

113 

114@intersection_sets.register(Range, Naturals) 

115def _(a, b): 

116 return intersection_sets(a, Interval(b.inf, S.Infinity)) 

117 

118@intersection_sets.register(Range, Range) 

119def _(a, b): 

120 # Check that there are no symbolic range arguments 

121 if not all(all(v.is_number for v in r.args) for r in [a, b]): 

122 return None 

123 

124 # non-overlap quick exits 

125 if not b: 

126 return S.EmptySet 

127 if not a: 

128 return S.EmptySet 

129 if b.sup < a.inf: 

130 return S.EmptySet 

131 if b.inf > a.sup: 

132 return S.EmptySet 

133 

134 # work with finite end at the start 

135 r1 = a 

136 if r1.start.is_infinite: 

137 r1 = r1.reversed 

138 r2 = b 

139 if r2.start.is_infinite: 

140 r2 = r2.reversed 

141 

142 # If both ends are infinite then it means that one Range is just the set 

143 # of all integers (the step must be 1). 

144 if r1.start.is_infinite: 

145 return b 

146 if r2.start.is_infinite: 

147 return a 

148 

149 from sympy.solvers.diophantine.diophantine import diop_linear 

150 

151 # this equation represents the values of the Range; 

152 # it's a linear equation 

153 eq = lambda r, i: r.start + i*r.step 

154 

155 # we want to know when the two equations might 

156 # have integer solutions so we use the diophantine 

157 # solver 

158 va, vb = diop_linear(eq(r1, Dummy('a')) - eq(r2, Dummy('b'))) 

159 

160 # check for no solution 

161 no_solution = va is None and vb is None 

162 if no_solution: 

163 return S.EmptySet 

164 

165 # there is a solution 

166 # ------------------- 

167 

168 # find the coincident point, c 

169 a0 = va.as_coeff_Add()[0] 

170 c = eq(r1, a0) 

171 

172 # find the first point, if possible, in each range 

173 # since c may not be that point 

174 def _first_finite_point(r1, c): 

175 if c == r1.start: 

176 return c 

177 # st is the signed step we need to take to 

178 # get from c to r1.start 

179 st = sign(r1.start - c)*step 

180 # use Range to calculate the first point: 

181 # we want to get as close as possible to 

182 # r1.start; the Range will not be null since 

183 # it will at least contain c 

184 s1 = Range(c, r1.start + st, st)[-1] 

185 if s1 == r1.start: 

186 pass 

187 else: 

188 # if we didn't hit r1.start then, if the 

189 # sign of st didn't match the sign of r1.step 

190 # we are off by one and s1 is not in r1 

191 if sign(r1.step) != sign(st): 

192 s1 -= st 

193 if s1 not in r1: 

194 return 

195 return s1 

196 

197 # calculate the step size of the new Range 

198 step = abs(ilcm(r1.step, r2.step)) 

199 s1 = _first_finite_point(r1, c) 

200 if s1 is None: 

201 return S.EmptySet 

202 s2 = _first_finite_point(r2, c) 

203 if s2 is None: 

204 return S.EmptySet 

205 

206 # replace the corresponding start or stop in 

207 # the original Ranges with these points; the 

208 # result must have at least one point since 

209 # we know that s1 and s2 are in the Ranges 

210 def _updated_range(r, first): 

211 st = sign(r.step)*step 

212 if r.start.is_finite: 

213 rv = Range(first, r.stop, st) 

214 else: 

215 rv = Range(r.start, first + st, st) 

216 return rv 

217 r1 = _updated_range(a, s1) 

218 r2 = _updated_range(b, s2) 

219 

220 # work with them both in the increasing direction 

221 if sign(r1.step) < 0: 

222 r1 = r1.reversed 

223 if sign(r2.step) < 0: 

224 r2 = r2.reversed 

225 

226 # return clipped Range with positive step; it 

227 # can't be empty at this point 

228 start = max(r1.start, r2.start) 

229 stop = min(r1.stop, r2.stop) 

230 return Range(start, stop, step) 

231 

232 

233@intersection_sets.register(Range, Integers) 

234def _(a, b): 

235 return a 

236 

237 

238@intersection_sets.register(Range, Rationals) 

239def _(a, b): 

240 return a 

241 

242 

243@intersection_sets.register(ImageSet, Set) 

244def _(self, other): 

245 from sympy.solvers.diophantine import diophantine 

246 

247 # Only handle the straight-forward univariate case 

248 if (len(self.lamda.variables) > 1 

249 or self.lamda.signature != self.lamda.variables): 

250 return None 

251 base_set = self.base_sets[0] 

252 

253 # Intersection between ImageSets with Integers as base set 

254 # For {f(n) : n in Integers} & {g(m) : m in Integers} we solve the 

255 # diophantine equations f(n)=g(m). 

256 # If the solutions for n are {h(t) : t in Integers} then we return 

257 # {f(h(t)) : t in integers}. 

258 # If the solutions for n are {n_1, n_2, ..., n_k} then we return 

259 # {f(n_i) : 1 <= i <= k}. 

260 if base_set is S.Integers: 

261 gm = None 

262 if isinstance(other, ImageSet) and other.base_sets == (S.Integers,): 

263 gm = other.lamda.expr 

264 var = other.lamda.variables[0] 

265 # Symbol of second ImageSet lambda must be distinct from first 

266 m = Dummy('m') 

267 gm = gm.subs(var, m) 

268 elif other is S.Integers: 

269 m = gm = Dummy('m') 

270 if gm is not None: 

271 fn = self.lamda.expr 

272 n = self.lamda.variables[0] 

273 try: 

274 solns = list(diophantine(fn - gm, syms=(n, m), permute=True)) 

275 except (TypeError, NotImplementedError): 

276 # TypeError if equation not polynomial with rational coeff. 

277 # NotImplementedError if correct format but no solver. 

278 return 

279 # 3 cases are possible for solns: 

280 # - empty set, 

281 # - one or more parametric (infinite) solutions, 

282 # - a finite number of (non-parametric) solution couples. 

283 # Among those, there is one type of solution set that is 

284 # not helpful here: multiple parametric solutions. 

285 if len(solns) == 0: 

286 return S.EmptySet 

287 elif any(s.free_symbols for tupl in solns for s in tupl): 

288 if len(solns) == 1: 

289 soln, solm = solns[0] 

290 (t,) = soln.free_symbols 

291 expr = fn.subs(n, soln.subs(t, n)).expand() 

292 return imageset(Lambda(n, expr), S.Integers) 

293 else: 

294 return 

295 else: 

296 return FiniteSet(*(fn.subs(n, s[0]) for s in solns)) 

297 

298 if other == S.Reals: 

299 from sympy.solvers.solvers import denoms, solve_linear 

300 

301 def _solution_union(exprs, sym): 

302 # return a union of linear solutions to i in expr; 

303 # if i cannot be solved, use a ConditionSet for solution 

304 sols = [] 

305 for i in exprs: 

306 x, xis = solve_linear(i, 0, [sym]) 

307 if x == sym: 

308 sols.append(FiniteSet(xis)) 

309 else: 

310 sols.append(ConditionSet(sym, Eq(i, 0))) 

311 return Union(*sols) 

312 

313 f = self.lamda.expr 

314 n = self.lamda.variables[0] 

315 

316 n_ = Dummy(n.name, real=True) 

317 f_ = f.subs(n, n_) 

318 

319 re, im = f_.as_real_imag() 

320 im = expand_complex(im) 

321 

322 re = re.subs(n_, n) 

323 im = im.subs(n_, n) 

324 ifree = im.free_symbols 

325 lam = Lambda(n, re) 

326 if im.is_zero: 

327 # allow re-evaluation 

328 # of self in this case to make 

329 # the result canonical 

330 pass 

331 elif im.is_zero is False: 

332 return S.EmptySet 

333 elif ifree != {n}: 

334 return None 

335 else: 

336 # univarite imaginary part in same variable; 

337 # use numer instead of as_numer_denom to keep 

338 # this as fast as possible while still handling 

339 # simple cases 

340 base_set &= _solution_union( 

341 Mul.make_args(numer(im)), n) 

342 # exclude values that make denominators 0 

343 base_set -= _solution_union(denoms(f), n) 

344 return imageset(lam, base_set) 

345 

346 elif isinstance(other, Interval): 

347 from sympy.solvers.solveset import (invert_real, invert_complex, 

348 solveset) 

349 

350 f = self.lamda.expr 

351 n = self.lamda.variables[0] 

352 new_inf, new_sup = None, None 

353 new_lopen, new_ropen = other.left_open, other.right_open 

354 

355 if f.is_real: 

356 inverter = invert_real 

357 else: 

358 inverter = invert_complex 

359 

360 g1, h1 = inverter(f, other.inf, n) 

361 g2, h2 = inverter(f, other.sup, n) 

362 

363 if all(isinstance(i, FiniteSet) for i in (h1, h2)): 

364 if g1 == n: 

365 if len(h1) == 1: 

366 new_inf = h1.args[0] 

367 if g2 == n: 

368 if len(h2) == 1: 

369 new_sup = h2.args[0] 

370 # TODO: Design a technique to handle multiple-inverse 

371 # functions 

372 

373 # Any of the new boundary values cannot be determined 

374 if any(i is None for i in (new_sup, new_inf)): 

375 return 

376 

377 

378 range_set = S.EmptySet 

379 

380 if all(i.is_real for i in (new_sup, new_inf)): 

381 # this assumes continuity of underlying function 

382 # however fixes the case when it is decreasing 

383 if new_inf > new_sup: 

384 new_inf, new_sup = new_sup, new_inf 

385 new_interval = Interval(new_inf, new_sup, new_lopen, new_ropen) 

386 range_set = base_set.intersect(new_interval) 

387 else: 

388 if other.is_subset(S.Reals): 

389 solutions = solveset(f, n, S.Reals) 

390 if not isinstance(range_set, (ImageSet, ConditionSet)): 

391 range_set = solutions.intersect(other) 

392 else: 

393 return 

394 

395 if range_set is S.EmptySet: 

396 return S.EmptySet 

397 elif isinstance(range_set, Range) and range_set.size is not S.Infinity: 

398 range_set = FiniteSet(*list(range_set)) 

399 

400 if range_set is not None: 

401 return imageset(Lambda(n, f), range_set) 

402 return 

403 else: 

404 return 

405 

406 

407@intersection_sets.register(ProductSet, ProductSet) 

408def _(a, b): 

409 if len(b.args) != len(a.args): 

410 return S.EmptySet 

411 return ProductSet(*(i.intersect(j) for i, j in zip(a.sets, b.sets))) 

412 

413 

414@intersection_sets.register(Interval, Interval) 

415def _(a, b): 

416 # handle (-oo, oo) 

417 infty = S.NegativeInfinity, S.Infinity 

418 if a == Interval(*infty): 

419 l, r = a.left, a.right 

420 if l.is_real or l in infty or r.is_real or r in infty: 

421 return b 

422 

423 # We can't intersect [0,3] with [x,6] -- we don't know if x>0 or x<0 

424 if not a._is_comparable(b): 

425 return None 

426 

427 empty = False 

428 

429 if a.start <= b.end and b.start <= a.end: 

430 # Get topology right. 

431 if a.start < b.start: 

432 start = b.start 

433 left_open = b.left_open 

434 elif a.start > b.start: 

435 start = a.start 

436 left_open = a.left_open 

437 else: 

438 #this is to ensure that if Eq(a.start,b.start) but 

439 #type(a.start) != type(b.start) the order of a and b 

440 #does not matter for the result 

441 start = list(ordered([a,b]))[0].start 

442 left_open = a.left_open or b.left_open 

443 

444 if a.end < b.end: 

445 end = a.end 

446 right_open = a.right_open 

447 elif a.end > b.end: 

448 end = b.end 

449 right_open = b.right_open 

450 else: 

451 end = list(ordered([a,b]))[0].end 

452 right_open = a.right_open or b.right_open 

453 

454 if end - start == 0 and (left_open or right_open): 

455 empty = True 

456 else: 

457 empty = True 

458 

459 if empty: 

460 return S.EmptySet 

461 

462 return Interval(start, end, left_open, right_open) 

463 

464@intersection_sets.register(EmptySet, Set) 

465def _(a, b): 

466 return S.EmptySet 

467 

468@intersection_sets.register(UniversalSet, Set) 

469def _(a, b): 

470 return b 

471 

472@intersection_sets.register(FiniteSet, FiniteSet) 

473def _(a, b): 

474 return FiniteSet(*(a._elements & b._elements)) 

475 

476@intersection_sets.register(FiniteSet, Set) 

477def _(a, b): 

478 try: 

479 return FiniteSet(*[el for el in a if el in b]) 

480 except TypeError: 

481 return None # could not evaluate `el in b` due to symbolic ranges. 

482 

483@intersection_sets.register(Set, Set) 

484def _(a, b): 

485 return None 

486 

487@intersection_sets.register(Integers, Rationals) 

488def _(a, b): 

489 return a 

490 

491@intersection_sets.register(Naturals, Rationals) 

492def _(a, b): 

493 return a 

494 

495@intersection_sets.register(Rationals, Reals) 

496def _(a, b): 

497 return a 

498 

499def _intlike_interval(a, b): 

500 try: 

501 if b._inf is S.NegativeInfinity and b._sup is S.Infinity: 

502 return a 

503 s = Range(max(a.inf, ceiling(b.left)), floor(b.right) + 1) 

504 return intersection_sets(s, b) # take out endpoints if open interval 

505 except ValueError: 

506 return None 

507 

508@intersection_sets.register(Integers, Interval) 

509def _(a, b): 

510 return _intlike_interval(a, b) 

511 

512@intersection_sets.register(Naturals, Interval) 

513def _(a, b): 

514 return _intlike_interval(a, b)