Coverage for /usr/lib/python3/dist-packages/sympy/sets/conditionset.py: 22%
125 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.singleton import S
2from sympy.core.basic import Basic
3from sympy.core.containers import Tuple
4from sympy.core.function import Lambda, BadSignatureError
5from sympy.core.logic import fuzzy_bool
6from sympy.core.relational import Eq
7from sympy.core.symbol import Dummy
8from sympy.core.sympify import _sympify
9from sympy.logic.boolalg import And, as_Boolean
10from sympy.utilities.iterables import sift, flatten, has_dups
11from sympy.utilities.exceptions import sympy_deprecation_warning
12from .contains import Contains
13from .sets import Set, Union, FiniteSet, SetKind
16adummy = Dummy('conditionset')
19class ConditionSet(Set):
20 r"""
21 Set of elements which satisfies a given condition.
23 .. math:: \{x \mid \textrm{condition}(x) = \texttt{True}, x \in S\}
25 Examples
26 ========
28 >>> from sympy import Symbol, S, ConditionSet, pi, Eq, sin, Interval
29 >>> from sympy.abc import x, y, z
31 >>> sin_sols = ConditionSet(x, Eq(sin(x), 0), Interval(0, 2*pi))
32 >>> 2*pi in sin_sols
33 True
34 >>> pi/2 in sin_sols
35 False
36 >>> 3*pi in sin_sols
37 False
38 >>> 5 in ConditionSet(x, x**2 > 4, S.Reals)
39 True
41 If the value is not in the base set, the result is false:
43 >>> 5 in ConditionSet(x, x**2 > 4, Interval(2, 4))
44 False
46 Notes
47 =====
49 Symbols with assumptions should be avoided or else the
50 condition may evaluate without consideration of the set:
52 >>> n = Symbol('n', negative=True)
53 >>> cond = (n > 0); cond
54 False
55 >>> ConditionSet(n, cond, S.Integers)
56 EmptySet
58 Only free symbols can be changed by using `subs`:
60 >>> c = ConditionSet(x, x < 1, {x, z})
61 >>> c.subs(x, y)
62 ConditionSet(x, x < 1, {y, z})
64 To check if ``pi`` is in ``c`` use:
66 >>> pi in c
67 False
69 If no base set is specified, the universal set is implied:
71 >>> ConditionSet(x, x < 1).base_set
72 UniversalSet
74 Only symbols or symbol-like expressions can be used:
76 >>> ConditionSet(x + 1, x + 1 < 1, S.Integers)
77 Traceback (most recent call last):
78 ...
79 ValueError: non-symbol dummy not recognized in condition
81 When the base set is a ConditionSet, the symbols will be
82 unified if possible with preference for the outermost symbols:
84 >>> ConditionSet(x, x < y, ConditionSet(z, z + y < 2, S.Integers))
85 ConditionSet(x, (x < y) & (x + y < 2), Integers)
87 """
88 def __new__(cls, sym, condition, base_set=S.UniversalSet):
89 sym = _sympify(sym)
90 flat = flatten([sym])
91 if has_dups(flat):
92 raise BadSignatureError("Duplicate symbols detected")
93 base_set = _sympify(base_set)
94 if not isinstance(base_set, Set):
95 raise TypeError(
96 'base set should be a Set object, not %s' % base_set)
97 condition = _sympify(condition)
99 if isinstance(condition, FiniteSet):
100 condition_orig = condition
101 temp = (Eq(lhs, 0) for lhs in condition)
102 condition = And(*temp)
103 sympy_deprecation_warning(
104 f"""
105Using a set for the condition in ConditionSet is deprecated. Use a boolean
106instead.
108In this case, replace
110 {condition_orig}
112with
114 {condition}
115""",
116 deprecated_since_version='1.5',
117 active_deprecations_target="deprecated-conditionset-set",
118 )
120 condition = as_Boolean(condition)
122 if condition is S.true:
123 return base_set
125 if condition is S.false:
126 return S.EmptySet
128 if base_set is S.EmptySet:
129 return S.EmptySet
131 # no simple answers, so now check syms
132 for i in flat:
133 if not getattr(i, '_diff_wrt', False):
134 raise ValueError('`%s` is not symbol-like' % i)
136 if base_set.contains(sym) is S.false:
137 raise TypeError('sym `%s` is not in base_set `%s`' % (sym, base_set))
139 know = None
140 if isinstance(base_set, FiniteSet):
141 sifted = sift(
142 base_set, lambda _: fuzzy_bool(condition.subs(sym, _)))
143 if sifted[None]:
144 know = FiniteSet(*sifted[True])
145 base_set = FiniteSet(*sifted[None])
146 else:
147 return FiniteSet(*sifted[True])
149 if isinstance(base_set, cls):
150 s, c, b = base_set.args
151 def sig(s):
152 return cls(s, Eq(adummy, 0)).as_dummy().sym
153 sa, sb = map(sig, (sym, s))
154 if sa != sb:
155 raise BadSignatureError('sym does not match sym of base set')
156 reps = dict(zip(flatten([sym]), flatten([s])))
157 if s == sym:
158 condition = And(condition, c)
159 base_set = b
160 elif not c.free_symbols & sym.free_symbols:
161 reps = {v: k for k, v in reps.items()}
162 condition = And(condition, c.xreplace(reps))
163 base_set = b
164 elif not condition.free_symbols & s.free_symbols:
165 sym = sym.xreplace(reps)
166 condition = And(condition.xreplace(reps), c)
167 base_set = b
169 # flatten ConditionSet(Contains(ConditionSet())) expressions
170 if isinstance(condition, Contains) and (sym == condition.args[0]):
171 if isinstance(condition.args[1], Set):
172 return condition.args[1].intersect(base_set)
174 rv = Basic.__new__(cls, sym, condition, base_set)
175 return rv if know is None else Union(know, rv)
177 sym = property(lambda self: self.args[0])
178 condition = property(lambda self: self.args[1])
179 base_set = property(lambda self: self.args[2])
181 @property
182 def free_symbols(self):
183 cond_syms = self.condition.free_symbols - self.sym.free_symbols
184 return cond_syms | self.base_set.free_symbols
186 @property
187 def bound_symbols(self):
188 return flatten([self.sym])
190 def _contains(self, other):
191 def ok_sig(a, b):
192 tuples = [isinstance(i, Tuple) for i in (a, b)]
193 c = tuples.count(True)
194 if c == 1:
195 return False
196 if c == 0:
197 return True
198 return len(a) == len(b) and all(
199 ok_sig(i, j) for i, j in zip(a, b))
200 if not ok_sig(self.sym, other):
201 return S.false
203 # try doing base_cond first and return
204 # False immediately if it is False
205 base_cond = Contains(other, self.base_set)
206 if base_cond is S.false:
207 return S.false
209 # Substitute other into condition. This could raise e.g. for
210 # ConditionSet(x, 1/x >= 0, Reals).contains(0)
211 lamda = Lambda((self.sym,), self.condition)
212 try:
213 lambda_cond = lamda(other)
214 except TypeError:
215 return Contains(other, self, evaluate=False)
216 else:
217 return And(base_cond, lambda_cond)
219 def as_relational(self, other):
220 f = Lambda(self.sym, self.condition)
221 if isinstance(self.sym, Tuple):
222 f = f(*other)
223 else:
224 f = f(other)
225 return And(f, self.base_set.contains(other))
227 def _eval_subs(self, old, new):
228 sym, cond, base = self.args
229 dsym = sym.subs(old, adummy)
230 insym = dsym.has(adummy)
231 # prioritize changing a symbol in the base
232 newbase = base.subs(old, new)
233 if newbase != base:
234 if not insym:
235 cond = cond.subs(old, new)
236 return self.func(sym, cond, newbase)
237 if insym:
238 pass # no change of bound symbols via subs
239 elif getattr(new, '_diff_wrt', False):
240 cond = cond.subs(old, new)
241 else:
242 pass # let error about the symbol raise from __new__
243 return self.func(sym, cond, base)
245 def _kind(self):
246 return SetKind(self.sym.kind)