Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sympy
GitHub Repository: sympy/sympy
Path: blob/master/sympy/solvers/inequalities.py
2046 views
1
"""Tools for solving inequalities and systems of inequalities. """
2
import itertools
3
4
from sympy.calculus.util import (continuous_domain, periodicity,
5
function_range)
6
from sympy.core import sympify
7
from sympy.core.exprtools import factor_terms
8
from sympy.core.relational import Relational, Lt, Ge, Eq
9
from sympy.core.symbol import Symbol, Dummy
10
from sympy.sets.sets import Interval, FiniteSet, Union, Intersection
11
from sympy.core.singleton import S
12
from sympy.core.function import expand_mul
13
from sympy.functions.elementary.complexes import Abs
14
from sympy.logic import And
15
from sympy.polys import Poly, PolynomialError, parallel_poly_from_expr
16
from sympy.polys.polyutils import _nsort
17
from sympy.solvers.solveset import solvify, solveset
18
from sympy.utilities.iterables import sift, iterable
19
from sympy.utilities.misc import filldedent
20
21
22
def solve_poly_inequality(poly, rel):
23
"""Solve a polynomial inequality with rational coefficients.
24
25
Examples
26
========
27
28
>>> from sympy import solve_poly_inequality, Poly
29
>>> from sympy.abc import x
30
31
>>> solve_poly_inequality(Poly(x, x, domain='ZZ'), '==')
32
[{0}]
33
34
>>> solve_poly_inequality(Poly(x**2 - 1, x, domain='ZZ'), '!=')
35
[Interval.open(-oo, -1), Interval.open(-1, 1), Interval.open(1, oo)]
36
37
>>> solve_poly_inequality(Poly(x**2 - 1, x, domain='ZZ'), '==')
38
[{-1}, {1}]
39
40
See Also
41
========
42
solve_poly_inequalities
43
"""
44
if not isinstance(poly, Poly):
45
raise ValueError(
46
'For efficiency reasons, `poly` should be a Poly instance')
47
if poly.as_expr().is_number:
48
t = Relational(poly.as_expr(), 0, rel)
49
if t is S.true:
50
return [S.Reals]
51
elif t is S.false:
52
return [S.EmptySet]
53
else:
54
raise NotImplementedError(
55
"could not determine truth value of %s" % t)
56
57
reals, intervals = poly.real_roots(multiple=False), []
58
59
if rel == '==':
60
for root, _ in reals:
61
interval = Interval(root, root)
62
intervals.append(interval)
63
elif rel == '!=':
64
left = S.NegativeInfinity
65
66
for right, _ in reals + [(S.Infinity, 1)]:
67
interval = Interval(left, right, True, True)
68
intervals.append(interval)
69
left = right
70
else:
71
if poly.LC() > 0:
72
sign = +1
73
else:
74
sign = -1
75
76
eq_sign, equal = None, False
77
78
if rel == '>':
79
eq_sign = +1
80
elif rel == '<':
81
eq_sign = -1
82
elif rel == '>=':
83
eq_sign, equal = +1, True
84
elif rel == '<=':
85
eq_sign, equal = -1, True
86
else:
87
raise ValueError("'%s' is not a valid relation" % rel)
88
89
right, right_open = S.Infinity, True
90
91
for left, multiplicity in reversed(reals):
92
if multiplicity % 2:
93
if sign == eq_sign:
94
intervals.insert(
95
0, Interval(left, right, not equal, right_open))
96
97
sign, right, right_open = -sign, left, not equal
98
else:
99
if sign == eq_sign and not equal:
100
intervals.insert(
101
0, Interval(left, right, True, right_open))
102
right, right_open = left, True
103
elif sign != eq_sign and equal:
104
intervals.insert(0, Interval(left, left))
105
106
if sign == eq_sign:
107
intervals.insert(
108
0, Interval(S.NegativeInfinity, right, True, right_open))
109
110
return intervals
111
112
113
def solve_poly_inequalities(polys):
114
"""Solve polynomial inequalities with rational coefficients.
115
116
Examples
117
========
118
119
>>> from sympy import Poly
120
>>> from sympy.solvers.inequalities import solve_poly_inequalities
121
>>> from sympy.abc import x
122
>>> solve_poly_inequalities(((
123
... Poly(x**2 - 3), ">"), (
124
... Poly(-x**2 + 1), ">")))
125
Union(Interval.open(-oo, -sqrt(3)), Interval.open(-1, 1), Interval.open(sqrt(3), oo))
126
"""
127
return Union(*[s for p in polys for s in solve_poly_inequality(*p)])
128
129
130
def solve_rational_inequalities(eqs):
131
"""Solve a system of rational inequalities with rational coefficients.
132
133
Examples
134
========
135
136
>>> from sympy.abc import x
137
>>> from sympy import solve_rational_inequalities, Poly
138
139
>>> solve_rational_inequalities([[
140
... ((Poly(-x + 1), Poly(1, x)), '>='),
141
... ((Poly(-x + 1), Poly(1, x)), '<=')]])
142
{1}
143
144
>>> solve_rational_inequalities([[
145
... ((Poly(x), Poly(1, x)), '!='),
146
... ((Poly(-x + 1), Poly(1, x)), '>=')]])
147
Union(Interval.open(-oo, 0), Interval.Lopen(0, 1))
148
149
See Also
150
========
151
solve_poly_inequality
152
"""
153
result = S.EmptySet
154
155
for _eqs in eqs:
156
if not _eqs:
157
continue
158
159
global_intervals = [Interval(S.NegativeInfinity, S.Infinity)]
160
161
for (numer, denom), rel in _eqs:
162
numer_intervals = solve_poly_inequality(numer*denom, rel)
163
denom_intervals = solve_poly_inequality(denom, '==')
164
165
intervals = []
166
167
for numer_interval, global_interval in itertools.product(
168
numer_intervals, global_intervals):
169
interval = numer_interval.intersect(global_interval)
170
171
if interval is not S.EmptySet:
172
intervals.append(interval)
173
174
global_intervals = intervals
175
176
intervals = []
177
178
for global_interval in global_intervals:
179
for denom_interval in denom_intervals:
180
global_interval -= denom_interval
181
182
if global_interval is not S.EmptySet:
183
intervals.append(global_interval)
184
185
global_intervals = intervals
186
187
if not global_intervals:
188
break
189
190
for interval in global_intervals:
191
result = result.union(interval)
192
193
return result
194
195
196
def reduce_rational_inequalities(exprs, gen, relational=True):
197
"""Reduce a system of rational inequalities with rational coefficients.
198
199
Examples
200
========
201
202
>>> from sympy import Symbol
203
>>> from sympy.solvers.inequalities import reduce_rational_inequalities
204
205
>>> x = Symbol('x', real=True)
206
207
>>> reduce_rational_inequalities([[x**2 <= 0]], x)
208
Eq(x, 0)
209
210
>>> reduce_rational_inequalities([[x + 2 > 0]], x)
211
-2 < x
212
>>> reduce_rational_inequalities([[(x + 2, ">")]], x)
213
-2 < x
214
>>> reduce_rational_inequalities([[x + 2]], x)
215
Eq(x, -2)
216
217
This function find the non-infinite solution set so if the unknown symbol
218
is declared as extended real rather than real then the result may include
219
finiteness conditions:
220
221
>>> y = Symbol('y', extended_real=True)
222
>>> reduce_rational_inequalities([[y + 2 > 0]], y)
223
(-2 < y) & (y < oo)
224
"""
225
exact = True
226
eqs = []
227
solution = S.EmptySet # add pieces for each group
228
for _exprs in exprs:
229
if not _exprs:
230
continue
231
_eqs = []
232
_sol = S.Reals
233
for expr in _exprs:
234
if isinstance(expr, tuple):
235
expr, rel = expr
236
else:
237
if expr.is_Relational:
238
expr, rel = expr.lhs - expr.rhs, expr.rel_op
239
else:
240
rel = '=='
241
242
if expr is S.true:
243
numer, denom, rel = S.Zero, S.One, '=='
244
elif expr is S.false:
245
numer, denom, rel = S.One, S.One, '=='
246
else:
247
numer, denom = expr.together().as_numer_denom()
248
249
try:
250
(numer, denom), opt = parallel_poly_from_expr(
251
(numer, denom), gen)
252
except PolynomialError:
253
raise PolynomialError(filldedent('''
254
only polynomials and rational functions are
255
supported in this context.
256
'''))
257
258
if not opt.domain.is_Exact:
259
numer, denom, exact = numer.to_exact(), denom.to_exact(), False
260
261
domain = opt.domain.get_exact()
262
263
if not (domain.is_ZZ or domain.is_QQ):
264
expr = numer/denom
265
expr = Relational(expr, 0, rel)
266
_sol &= solve_univariate_inequality(expr, gen, relational=False)
267
else:
268
_eqs.append(((numer, denom), rel))
269
270
if _eqs:
271
_sol &= solve_rational_inequalities([_eqs])
272
exclude = solve_rational_inequalities([[((d, d.one), '==')
273
for i in eqs for ((n, d), _) in i if d.has(gen)]])
274
_sol -= exclude
275
276
solution |= _sol
277
278
if not exact and solution:
279
solution = solution.evalf()
280
281
if relational:
282
solution = solution.as_relational(gen)
283
284
return solution
285
286
287
def reduce_abs_inequality(expr, rel, gen):
288
"""Reduce an inequality with nested absolute values.
289
290
Examples
291
========
292
293
>>> from sympy import reduce_abs_inequality, Abs, Symbol
294
>>> x = Symbol('x', real=True)
295
296
>>> reduce_abs_inequality(Abs(x - 5) - 3, '<', x)
297
(2 < x) & (x < 8)
298
299
>>> reduce_abs_inequality(Abs(x + 2)*3 - 13, '<', x)
300
(-19/3 < x) & (x < 7/3)
301
302
See Also
303
========
304
305
reduce_abs_inequalities
306
"""
307
if gen.is_extended_real is False:
308
raise TypeError(filldedent('''
309
Cannot solve inequalities with absolute values containing
310
non-real variables.
311
'''))
312
313
def _bottom_up_scan(expr):
314
exprs = []
315
316
if expr.is_Add or expr.is_Mul:
317
op = expr.func
318
319
for arg in expr.args:
320
_exprs = _bottom_up_scan(arg)
321
322
if not exprs:
323
exprs = _exprs
324
else:
325
exprs = [(op(expr, _expr), conds + _conds) for (expr, conds), (_expr, _conds) in
326
itertools.product(exprs, _exprs)]
327
elif expr.is_Pow:
328
n = expr.exp
329
if not n.is_Integer:
330
raise ValueError("Only Integer Powers are allowed on Abs.")
331
332
exprs.extend((expr**n, conds) for expr, conds in _bottom_up_scan(expr.base))
333
elif isinstance(expr, Abs):
334
_exprs = _bottom_up_scan(expr.args[0])
335
336
for expr, conds in _exprs:
337
exprs.append(( expr, conds + [Ge(expr, 0)]))
338
exprs.append((-expr, conds + [Lt(expr, 0)]))
339
else:
340
exprs = [(expr, [])]
341
342
return exprs
343
344
mapping = {'<': '>', '<=': '>='}
345
inequalities = []
346
347
for expr, conds in _bottom_up_scan(expr):
348
if rel not in mapping.keys():
349
expr = Relational( expr, 0, rel)
350
else:
351
expr = Relational(-expr, 0, mapping[rel])
352
353
inequalities.append([expr] + conds)
354
355
return reduce_rational_inequalities(inequalities, gen)
356
357
358
def reduce_abs_inequalities(exprs, gen):
359
"""Reduce a system of inequalities with nested absolute values.
360
361
Examples
362
========
363
364
>>> from sympy import reduce_abs_inequalities, Abs, Symbol
365
>>> x = Symbol('x', extended_real=True)
366
367
>>> reduce_abs_inequalities([(Abs(3*x - 5) - 7, '<'),
368
... (Abs(x + 25) - 13, '>')], x)
369
(-2/3 < x) & (x < 4) & (((-oo < x) & (x < -38)) | ((-12 < x) & (x < oo)))
370
371
>>> reduce_abs_inequalities([(Abs(x - 4) + Abs(3*x - 5) - 7, '<')], x)
372
(1/2 < x) & (x < 4)
373
374
See Also
375
========
376
377
reduce_abs_inequality
378
"""
379
return And(*[ reduce_abs_inequality(expr, rel, gen)
380
for expr, rel in exprs ])
381
382
383
def solve_univariate_inequality(expr, gen, relational=True, domain=S.Reals, continuous=False):
384
"""Solves a real univariate inequality.
385
386
Parameters
387
==========
388
389
expr : Relational
390
The target inequality
391
gen : Symbol
392
The variable for which the inequality is solved
393
relational : bool
394
A Relational type output is expected or not
395
domain : Set
396
The domain over which the equation is solved
397
continuous: bool
398
True if expr is known to be continuous over the given domain
399
(and so continuous_domain() does not need to be called on it)
400
401
Raises
402
======
403
404
NotImplementedError
405
The solution of the inequality cannot be determined due to limitation
406
in :func:`sympy.solvers.solveset.solvify`.
407
408
Notes
409
=====
410
411
Currently, we cannot solve all the inequalities due to limitations in
412
:func:`sympy.solvers.solveset.solvify`. Also, the solution returned for trigonometric inequalities
413
are restricted in its periodic interval.
414
415
See Also
416
========
417
418
sympy.solvers.solveset.solvify: solver returning solveset solutions with solve's output API
419
420
Examples
421
========
422
423
>>> from sympy import solve_univariate_inequality, Symbol, sin, Interval, S
424
>>> x = Symbol('x')
425
426
>>> solve_univariate_inequality(x**2 >= 4, x)
427
((2 <= x) & (x < oo)) | ((-oo < x) & (x <= -2))
428
429
>>> solve_univariate_inequality(x**2 >= 4, x, relational=False)
430
Union(Interval(-oo, -2), Interval(2, oo))
431
432
>>> domain = Interval(0, S.Infinity)
433
>>> solve_univariate_inequality(x**2 >= 4, x, False, domain)
434
Interval(2, oo)
435
436
>>> solve_univariate_inequality(sin(x) > 0, x, relational=False)
437
Interval.open(0, pi)
438
439
"""
440
from sympy.solvers.solvers import denoms
441
442
if domain.is_subset(S.Reals) is False:
443
raise NotImplementedError(filldedent('''
444
Inequalities in the complex domain are
445
not supported. Try the real domain by
446
setting domain=S.Reals'''))
447
elif domain is not S.Reals:
448
rv = solve_univariate_inequality(
449
expr, gen, relational=False, continuous=continuous).intersection(domain)
450
if relational:
451
rv = rv.as_relational(gen)
452
return rv
453
else:
454
pass # continue with attempt to solve in Real domain
455
456
# This keeps the function independent of the assumptions about `gen`.
457
# `solveset` makes sure this function is called only when the domain is
458
# real.
459
_gen = gen
460
_domain = domain
461
if gen.is_extended_real is False:
462
rv = S.EmptySet
463
return rv if not relational else rv.as_relational(_gen)
464
elif gen.is_extended_real is None:
465
gen = Dummy('gen', extended_real=True)
466
try:
467
expr = expr.xreplace({_gen: gen})
468
except TypeError:
469
raise TypeError(filldedent('''
470
When gen is real, the relational has a complex part
471
which leads to an invalid comparison like I < 0.
472
'''))
473
474
rv = None
475
476
if expr is S.true:
477
rv = domain
478
479
elif expr is S.false:
480
rv = S.EmptySet
481
482
else:
483
e = expr.lhs - expr.rhs
484
period = periodicity(e, gen)
485
if period == S.Zero:
486
e = expand_mul(e)
487
const = expr.func(e, 0)
488
if const is S.true:
489
rv = domain
490
elif const is S.false:
491
rv = S.EmptySet
492
elif period is not None:
493
frange = function_range(e, gen, domain)
494
495
rel = expr.rel_op
496
if rel in ('<', '<='):
497
if expr.func(frange.sup, 0):
498
rv = domain
499
elif not expr.func(frange.inf, 0):
500
rv = S.EmptySet
501
502
elif rel in ('>', '>='):
503
if expr.func(frange.inf, 0):
504
rv = domain
505
elif not expr.func(frange.sup, 0):
506
rv = S.EmptySet
507
508
inf, sup = domain.inf, domain.sup
509
if sup - inf is S.Infinity:
510
domain = Interval(0, period, False, True).intersect(_domain)
511
_domain = domain
512
513
if rv is None:
514
n, d = e.as_numer_denom()
515
try:
516
if gen not in n.free_symbols and len(e.free_symbols) > 1:
517
raise ValueError
518
# this might raise ValueError on its own
519
# or it might give None...
520
solns = solvify(e, gen, domain)
521
if solns is None:
522
# in which case we raise ValueError
523
raise ValueError
524
except (ValueError, NotImplementedError):
525
# replace gen with generic x since it's
526
# univariate anyway
527
raise NotImplementedError(filldedent('''
528
The inequality, %s, cannot be solved using
529
solve_univariate_inequality.
530
''' % expr.subs(gen, Symbol('x'))))
531
532
expanded_e = expand_mul(e)
533
def valid(x):
534
# this is used to see if gen=x satisfies the
535
# relational by substituting it into the
536
# expanded form and testing against 0, e.g.
537
# if expr = x*(x + 1) < 2 then e = x*(x + 1) - 2
538
# and expanded_e = x**2 + x - 2; the test is
539
# whether a given value of x satisfies
540
# x**2 + x - 2 < 0
541
#
542
# expanded_e, expr and gen used from enclosing scope
543
v = expanded_e.subs(gen, expand_mul(x))
544
try:
545
r = expr.func(v, 0)
546
except TypeError:
547
r = S.false
548
if r in (S.true, S.false):
549
return r
550
if v.is_extended_real is False:
551
return S.false
552
else:
553
v = v.n(2)
554
if v.is_comparable:
555
return expr.func(v, 0)
556
# not comparable or couldn't be evaluated
557
raise NotImplementedError(
558
'relationship did not evaluate: %s' % r)
559
560
singularities = []
561
for d in denoms(expr, gen):
562
singularities.extend(solvify(d, gen, domain))
563
if not continuous:
564
domain = continuous_domain(expanded_e, gen, domain)
565
566
include_x = '=' in expr.rel_op and expr.rel_op != '!='
567
568
try:
569
discontinuities = set(domain.boundary -
570
FiniteSet(domain.inf, domain.sup))
571
# remove points that are not between inf and sup of domain
572
critical_points = FiniteSet(*(solns + singularities + list(
573
discontinuities))).intersection(
574
Interval(domain.inf, domain.sup,
575
domain.inf not in domain, domain.sup not in domain))
576
if all(r.is_number for r in critical_points):
577
reals = _nsort(critical_points, separated=True)[0]
578
else:
579
sifted = sift(critical_points, lambda x: x.is_extended_real)
580
if sifted[None]:
581
# there were some roots that weren't known
582
# to be real
583
raise NotImplementedError
584
try:
585
reals = sifted[True]
586
if len(reals) > 1:
587
reals = sorted(reals)
588
except TypeError:
589
raise NotImplementedError
590
except NotImplementedError:
591
raise NotImplementedError('sorting of these roots is not supported')
592
593
# If expr contains imaginary coefficients, only take real
594
# values of x for which the imaginary part is 0
595
make_real = S.Reals
596
if (coeffI := expanded_e.coeff(S.ImaginaryUnit)) != S.Zero:
597
check = True
598
im_sol = FiniteSet()
599
try:
600
a = solveset(coeffI, gen, domain)
601
if not isinstance(a, Interval):
602
for z in a:
603
if z not in singularities and valid(z) and z.is_extended_real:
604
im_sol += FiniteSet(z)
605
else:
606
start, end = a.inf, a.sup
607
for z in _nsort(critical_points + FiniteSet(end)):
608
valid_start = valid(start)
609
if start != end:
610
valid_z = valid(z)
611
pt = _pt(start, z)
612
if pt not in singularities and pt.is_extended_real and valid(pt):
613
if valid_start and valid_z:
614
im_sol += Interval(start, z)
615
elif valid_start:
616
im_sol += Interval.Ropen(start, z)
617
elif valid_z:
618
im_sol += Interval.Lopen(start, z)
619
else:
620
im_sol += Interval.open(start, z)
621
start = z
622
for s in singularities:
623
im_sol -= FiniteSet(s)
624
except (TypeError):
625
im_sol = S.Reals
626
check = False
627
628
if im_sol is S.EmptySet:
629
raise ValueError(filldedent('''
630
%s contains imaginary parts which cannot be
631
made 0 for any value of %s satisfying the
632
inequality, leading to relations like I < 0.
633
''' % (expr.subs(gen, _gen), _gen)))
634
635
make_real = make_real.intersect(im_sol)
636
637
sol_sets = [S.EmptySet]
638
639
start = domain.inf
640
if start in domain and valid(start) and start.is_finite:
641
sol_sets.append(FiniteSet(start))
642
643
for x in reals:
644
end = x
645
646
if valid(_pt(start, end)):
647
sol_sets.append(Interval(start, end, True, True))
648
649
if x in singularities:
650
singularities.remove(x)
651
else:
652
if x in discontinuities:
653
discontinuities.remove(x)
654
_valid = valid(x)
655
else: # it's a solution
656
_valid = include_x
657
if _valid:
658
sol_sets.append(FiniteSet(x))
659
660
start = end
661
662
end = domain.sup
663
if end in domain and valid(end) and end.is_finite:
664
sol_sets.append(FiniteSet(end))
665
666
if valid(_pt(start, end)):
667
sol_sets.append(Interval.open(start, end))
668
669
if coeffI != S.Zero and check:
670
rv = (make_real).intersect(_domain)
671
else:
672
rv = Intersection(
673
(Union(*sol_sets)), make_real, _domain).subs(gen, _gen)
674
675
return rv if not relational else rv.as_relational(_gen)
676
677
678
def _pt(start, end):
679
"""Return a point between start and end"""
680
if not start.is_infinite and not end.is_infinite:
681
pt = (start + end)/2
682
elif start.is_infinite and end.is_infinite:
683
pt = S.Zero
684
else:
685
if (start.is_infinite and start.is_extended_positive is None or
686
end.is_infinite and end.is_extended_positive is None):
687
raise ValueError('cannot proceed with unsigned infinite values')
688
if (end.is_infinite and end.is_extended_negative or
689
start.is_infinite and start.is_extended_positive):
690
start, end = end, start
691
# if possible, use a multiple of self which has
692
# better behavior when checking assumptions than
693
# an expression obtained by adding or subtracting 1
694
if end.is_infinite:
695
if start.is_extended_positive:
696
pt = start*2
697
elif start.is_extended_negative:
698
pt = start*S.Half
699
else:
700
pt = start + 1
701
elif start.is_infinite:
702
if end.is_extended_positive:
703
pt = end*S.Half
704
elif end.is_extended_negative:
705
pt = end*2
706
else:
707
pt = end - 1
708
return pt
709
710
711
def _solve_inequality(ie, s, linear=False):
712
"""Return the inequality with s isolated on the left, if possible.
713
If the relationship is non-linear, a solution involving And or Or
714
may be returned. False or True are returned if the relationship
715
is never True or always True, respectively.
716
717
If `linear` is True (default is False) an `s`-dependent expression
718
will be isolated on the left, if possible
719
but it will not be solved for `s` unless the expression is linear
720
in `s`. Furthermore, only "safe" operations which do not change the
721
sense of the relationship are applied: no division by an unsigned
722
value is attempted unless the relationship involves Eq or Ne and
723
no division by a value not known to be nonzero is ever attempted.
724
725
Examples
726
========
727
728
>>> from sympy import Eq, Symbol
729
>>> from sympy.solvers.inequalities import _solve_inequality as f
730
>>> from sympy.abc import x, y
731
732
For linear expressions, the symbol can be isolated:
733
734
>>> f(x - 2 < 0, x)
735
x < 2
736
>>> f(-x - 6 < x, x)
737
x > -3
738
739
Sometimes nonlinear relationships will be False
740
741
>>> f(x**2 + 4 < 0, x)
742
False
743
744
Or they may involve more than one region of values:
745
746
>>> f(x**2 - 4 < 0, x)
747
(-2 < x) & (x < 2)
748
749
To restrict the solution to a relational, set linear=True
750
and only the x-dependent portion will be isolated on the left:
751
752
>>> f(x**2 - 4 < 0, x, linear=True)
753
x**2 < 4
754
755
Division of only nonzero quantities is allowed, so x cannot
756
be isolated by dividing by y:
757
758
>>> y.is_nonzero is None # it is unknown whether it is 0 or not
759
True
760
>>> f(x*y < 1, x)
761
x*y < 1
762
763
And while an equality (or inequality) still holds after dividing by a
764
non-zero quantity
765
766
>>> nz = Symbol('nz', nonzero=True)
767
>>> f(Eq(x*nz, 1), x)
768
Eq(x, 1/nz)
769
770
the sign must be known for other inequalities involving > or <:
771
772
>>> f(x*nz <= 1, x)
773
nz*x <= 1
774
>>> p = Symbol('p', positive=True)
775
>>> f(x*p <= 1, x)
776
x <= 1/p
777
778
When there are denominators in the original expression that
779
are removed by expansion, conditions for them will be returned
780
as part of the result:
781
782
>>> f(x < x*(2/x - 1), x)
783
(x < 1) & Ne(x, 0)
784
"""
785
from sympy.solvers.solvers import denoms
786
if s not in ie.free_symbols:
787
return ie
788
if ie.rhs == s:
789
ie = ie.reversed
790
if ie.lhs == s and s not in ie.rhs.free_symbols:
791
return ie
792
793
def classify(ie, s, i):
794
# return True or False if ie evaluates when substituting s with
795
# i else None (if unevaluated) or NaN (when there is an error
796
# in evaluating)
797
try:
798
v = ie.subs(s, i)
799
if v is S.NaN:
800
return v
801
elif v not in (True, False):
802
return
803
return v
804
except TypeError:
805
return S.NaN
806
807
rv = None
808
oo = S.Infinity
809
expr = ie.lhs - ie.rhs
810
try:
811
p = Poly(expr, s)
812
if p.degree() == 0:
813
rv = ie.func(p.as_expr(), 0)
814
elif not linear and p.degree() > 1:
815
# handle in except clause
816
raise NotImplementedError
817
except (PolynomialError, NotImplementedError):
818
if not linear:
819
try:
820
rv = reduce_rational_inequalities([[ie]], s)
821
except PolynomialError:
822
rv = solve_univariate_inequality(ie, s)
823
# remove restrictions wrt +/-oo that may have been
824
# applied when using sets to simplify the relationship
825
okoo = classify(ie, s, oo)
826
if okoo is S.true and classify(rv, s, oo) is S.false:
827
rv = rv.subs(s < oo, True)
828
oknoo = classify(ie, s, -oo)
829
if (oknoo is S.true and
830
classify(rv, s, -oo) is S.false):
831
rv = rv.subs(-oo < s, True)
832
rv = rv.subs(s > -oo, True)
833
if rv is S.true:
834
rv = (s <= oo) if okoo is S.true else (s < oo)
835
if oknoo is not S.true:
836
rv = And(-oo < s, rv)
837
else:
838
p = Poly(expr)
839
840
conds = []
841
if rv is None:
842
e = p.as_expr() # this is in expanded form
843
# Do a safe inversion of e, moving non-s terms
844
# to the rhs and dividing by a nonzero factor if
845
# the relational is Eq/Ne; for other relationals
846
# the sign must also be positive or negative
847
rhs = 0
848
b, ax = e.as_independent(s, as_Add=True)
849
e -= b
850
rhs -= b
851
ef = factor_terms(e)
852
a, e = ef.as_independent(s, as_Add=False)
853
if (a.is_zero != False or # don't divide by potential 0
854
a.is_negative ==
855
a.is_positive is None and # if sign is not known then
856
ie.rel_op not in ('!=', '==')): # reject if not Eq/Ne
857
e = ef
858
a = S.One
859
rhs /= a
860
if a.is_positive:
861
rv = ie.func(e, rhs)
862
else:
863
rv = ie.reversed.func(e, rhs)
864
865
# return conditions under which the value is
866
# valid, too.
867
beginning_denoms = denoms(ie.lhs) | denoms(ie.rhs)
868
current_denoms = denoms(rv)
869
for d in beginning_denoms - current_denoms:
870
c = _solve_inequality(Eq(d, 0), s, linear=linear)
871
if isinstance(c, Eq) and c.lhs == s:
872
if classify(rv, s, c.rhs) is S.true:
873
# rv is permitting this value but it shouldn't
874
conds.append(~c)
875
for i in (-oo, oo):
876
if (classify(rv, s, i) is S.true and
877
classify(ie, s, i) is not S.true):
878
conds.append(s < i if i is oo else i < s)
879
880
conds.append(rv)
881
return And(*conds)
882
883
884
def _reduce_inequalities(inequalities, symbols):
885
# helper for reduce_inequalities
886
887
poly_part, abs_part = {}, {}
888
other = []
889
890
for inequality in inequalities:
891
892
expr, rel = inequality.lhs, inequality.rel_op # rhs is 0
893
894
# check for gens using atoms which is more strict than free_symbols to
895
# guard against EX domain which won't be handled by
896
# reduce_rational_inequalities
897
gens = expr.atoms(Symbol)
898
899
if len(gens) == 1:
900
gen = gens.pop()
901
else:
902
common = expr.free_symbols & symbols
903
if len(common) == 1:
904
gen = common.pop()
905
other.append(_solve_inequality(Relational(expr, 0, rel), gen))
906
continue
907
else:
908
raise NotImplementedError(filldedent('''
909
inequality has more than one symbol of interest.
910
'''))
911
912
if expr.is_polynomial(gen):
913
poly_part.setdefault(gen, []).append((expr, rel))
914
else:
915
components = expr.find(lambda u:
916
u.has(gen) and (
917
u.is_Function or u.is_Pow and not u.exp.is_Integer))
918
if components and all(isinstance(i, Abs) for i in components):
919
abs_part.setdefault(gen, []).append((expr, rel))
920
else:
921
other.append(_solve_inequality(Relational(expr, 0, rel), gen))
922
923
poly_reduced = [reduce_rational_inequalities([exprs], gen) for gen, exprs in poly_part.items()]
924
abs_reduced = [reduce_abs_inequalities(exprs, gen) for gen, exprs in abs_part.items()]
925
926
return And(*(poly_reduced + abs_reduced + other))
927
928
929
def reduce_inequalities(inequalities, symbols=[]):
930
"""Reduce a system of inequalities with rational coefficients.
931
932
Examples
933
========
934
935
>>> from sympy.abc import x, y
936
>>> from sympy import reduce_inequalities
937
938
>>> reduce_inequalities(0 <= x + 3, [])
939
(-3 <= x) & (x < oo)
940
941
>>> reduce_inequalities(0 <= x + y*2 - 1, [x])
942
(x < oo) & (x >= 1 - 2*y)
943
"""
944
if not iterable(inequalities):
945
inequalities = [inequalities]
946
inequalities = [sympify(i) for i in inequalities]
947
948
gens = set().union(*[i.free_symbols for i in inequalities])
949
950
if not iterable(symbols):
951
symbols = [symbols]
952
symbols = (set(symbols) or gens) & gens
953
if any(i.is_extended_real is False for i in symbols):
954
raise TypeError(filldedent('''
955
inequalities cannot contain symbols that are not real.
956
'''))
957
958
# make vanilla symbol real
959
recast = {i: Dummy(i.name, extended_real=True)
960
for i in gens if i.is_extended_real is None}
961
inequalities = [i.xreplace(recast) for i in inequalities]
962
symbols = {i.xreplace(recast) for i in symbols}
963
964
# prefilter
965
keep = []
966
for i in inequalities:
967
if isinstance(i, Relational):
968
i = i.func(i.lhs.as_expr() - i.rhs.as_expr(), 0)
969
elif i not in (True, False):
970
i = Eq(i, 0)
971
if i == True:
972
continue
973
elif i == False:
974
return S.false
975
if i.lhs.is_number:
976
raise NotImplementedError(
977
"could not determine truth value of %s" % i)
978
keep.append(i)
979
inequalities = keep
980
del keep
981
982
# solve system
983
rv = _reduce_inequalities(inequalities, symbols)
984
985
# restore original symbols and return
986
return rv.xreplace({v: k for k, v in recast.items()})
987
988