/
utils.py
137 lines (117 loc) · 4.21 KB
/
utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
"""General utility functions for solvers."""
import warnings
from ..core import (expand_mul, expand_multinomial, nan, oo,
preorder_traversal, zoo)
from ..core.sympify import sympify
from ..simplify.simplify import posify, simplify
__all__ = 'checksol',
def checksol(f, sol, **flags):
r"""Checks whether sol is a solution of equations f.
Examples
========
>>> checksol(x**4 - 1, {x: 1})
True
>>> checksol(x**4 - 1, {x: 0})
False
>>> checksol(x**2 + y**2 - 5**2, {x: 3, y: 4})
True
Returns
=======
bool or None
Return True, if solution satisfy all equations
in ``f``. Return False, if a solution doesn't
satisfy any equation. Else (i.e. one or more checks
are inconclusive), return None.
Parameters
==========
f : Expr or iterable of Expr's
Equations to substitute solutions in.
sol : dict of Expr's
Mapping of symbols to values.
\*\*flags : dict
A dictionary of following parameters:
minimal : bool, optional
Do a very fast, minimal testing. Default is False.
warn : bool, optional
Show a warning if it could not conclude. Default is False.
simplify : bool, optional
Simplify solution before substituting into function and
simplify the function before trying specific simplifications.
Default is True.
force : bool, optional
Make positive all symbols without assumptions regarding
sign. Default is False.
"""
minimal = flags.get('minimal', False)
if not isinstance(sol, dict):
raise ValueError(f'Expecting dictionary but got {sol}')
if sol and not f.has(*list(sol)):
# if f(y) == 0, x=3 does not set f(y) to zero...nor does it not
if f.is_Number:
return f.is_zero
else:
return
illegal = {nan, zoo, oo, -oo}
if any(sympify(v).atoms() & illegal for k, v in sol.items()):
return False
was = f
attempt = -1
while 1:
attempt += 1
if attempt == 0:
val = f.subs(sol)
if val.atoms() & illegal:
return False
elif attempt == 1:
assert val.free_symbols
if not val.is_constant(*list(sol), simplify=not minimal):
return False
# there are free symbols -- simple expansion might work
_, val = val.as_content_primitive()
val = expand_mul(expand_multinomial(val))
elif attempt == 2:
if minimal:
return
if flags.get('simplify', True):
for k in sol:
sol[k] = simplify(sol[k])
# start over without the failed expanded form, possibly
# with a simplified solution
val = simplify(f.subs(sol))
if flags.get('force', True):
val, _ = posify(val)
# expansion may work now, so try again and check
exval = expand_mul(expand_multinomial(val))
if exval.is_number or not exval.free_symbols:
# we can decide now
val = exval
else:
# if there are no radicals and no functions then this can't be
# zero anymore -- can it?
pot = preorder_traversal(expand_mul(val))
seen = set()
saw_pow_func = False
for p in pot:
if p in seen:
continue
seen.add(p)
if p.is_Pow and not p.exp.is_Integer:
saw_pow_func = True
elif p.is_Function:
saw_pow_func = True
if saw_pow_func:
break
if saw_pow_func is False:
return False
break
if val == was:
continue
elif val.is_Rational:
return val == 0
elif val.is_nonzero:
return False
if not val.free_symbols:
return bool(abs(val.evalf(18, strict=False).evalf(12, chop=True)) < 1e-9)
was = val
if flags.get('warn', False):
warnings.warn(f'\n\tWarning: could not verify solution {sol}.')