/
polyutils.py
221 lines (168 loc) · 6.54 KB
/
polyutils.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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
"""Useful utilities for higher level polynomial classes."""
import re
from ..core import Add, Mul, Pow
from ..core.exprtools import decompose_power
from ..utilities import default_sort_key
from .polyerrors import GeneratorsNeededError, PolynomialError
from .polyoptions import build_options
_gens_order = {
'a': 301, 'b': 302, 'c': 303, 'd': 304,
'e': 305, 'f': 306, 'g': 307, 'h': 308,
'i': 309, 'j': 310, 'k': 311, 'l': 312,
'm': 313, 'n': 314, 'o': 315, 'p': 216,
'q': 217, 'r': 218, 's': 219, 't': 220,
'u': 221, 'v': 222, 'w': 223, 'x': 124,
'y': 125, 'z': 126,
}
_max_order = 1000
_re_gen = re.compile(r'^(.+?)(\d*)$')
def _nsort(roots, separated=False):
"""Sort the numerical roots putting the real roots first, then sorting
according to real and imaginary parts. If ``separated`` is True, then
the real and imaginary roots will be returned in two lists, respectively.
This routine tries to avoid issue sympy/sympy#6137 by separating the roots into real
and imaginary parts before evaluation. In addition, the sorting will raise
an error if any computation cannot be done with precision.
"""
if len(roots) == 1:
if not separated:
return list(roots)
r = list(roots)[0]
if r.is_extended_real:
return [[r], []]
if r.is_real is False and r.is_complex:
return [[], [r]]
raise NotImplementedError
all_numbers = all(r.is_number for r in roots)
if not all_numbers:
raise NotImplementedError
# see issue sympy/sympy#6137:
# get the real part of the evaluated real and imaginary parts of each root
key = [[i.evalf(2).as_real_imag()[0] for i in r.as_real_imag()] for r in roots]
# insert a key to indicate if the root has an imaginary part
key = [(1 if i else 0, r, -abs(i), i.is_positive) for r, i in key]
key = sorted(zip(key, roots))
# return the real and imaginary roots separately if desired
if separated:
r = []
i = []
for (im, _, _, _), v in key:
if im:
i.append(v)
else:
r.append(v)
return r, i
_, roots = zip(*key)
return list(roots)
def _sort_gens(gens, **args):
"""Sort generators in a reasonably intelligent way."""
opt = build_options(args)
gens_order, wrt = _gens_order.copy(), opt.wrt
for i, gen in enumerate(opt.sort, start=1):
gens_order[gen] = i
def order_key(gen):
gen = str(gen)
if wrt is not None:
try:
return -len(wrt) + wrt.index(gen), gen, 0
except ValueError:
pass
name, index = _re_gen.match(gen).groups()
index = int(index) if index else 0
return gens_order.get(name, _max_order), name, index
return tuple(sorted(gens, key=order_key))
def _unify_gens(f_gens, g_gens):
"""Unify generators in a reasonably intelligent way."""
f_gens = list(f_gens)
g_gens = list(g_gens)
if f_gens == g_gens:
return tuple(f_gens)
gens, common, k = [], [], 0
for gen in f_gens:
if gen in g_gens:
common.append(gen)
for i, gen in enumerate(g_gens):
if gen in common:
g_gens[i], k = common[k], k + 1
for gen in common:
i = f_gens.index(gen)
gens.extend(f_gens[:i])
f_gens = f_gens[i + 1:]
i = g_gens.index(gen)
gens.extend(g_gens[:i])
g_gens = g_gens[i + 1:]
gens.append(gen)
gens.extend(f_gens)
gens.extend(g_gens)
return tuple(gens)
def _find_gens(exprs, opt):
"""Find generators in a reasonably intelligent way."""
if opt.domain is not None:
def _is_coeff(factor):
return factor in opt.domain
elif opt.extension is True:
def _is_coeff(factor):
return factor.is_number and factor.is_algebraic
elif opt.greedy is not False:
def _is_coeff(factor):
return factor.is_Number and factor.is_finite is not False
else:
def _is_coeff(factor):
return factor.is_number and factor.is_finite is not False
gens = set()
for expr in exprs:
for term in Add.make_args(expr):
for factor in Mul.make_args(term):
if not _is_coeff(factor):
base, exp = decompose_power(factor)
if exp < 0:
base = Pow(base, -1)
if opt.expand and exp > 1:
gens |= set(_find_gens([base], opt))
else:
gens.add(base)
if not gens:
raise GeneratorsNeededError(f'specify generators to give {exprs} a meaning')
return _sort_gens(gens, opt=opt)
def _sort_factors(factors, **args):
"""Sort low-level factors in increasing 'complexity' order."""
def order_if_multiple_key(factor):
f, n = factor
return len(f), n, default_sort_key(f)
def order_no_multiple_key(f):
return len(f), default_sort_key(f)
if args.get('multiple', True):
return sorted(factors, key=order_if_multiple_key)
return sorted(factors, key=order_no_multiple_key)
def _parallel_dict_from_expr_if_gens(exprs, opt):
"""Transform expressions into a multinomial form given generators."""
indices = {g: i for i, g in enumerate(opt.gens)}
zero_monom = [0]*len(opt.gens)
polys = []
for expr in exprs:
poly = {}
for term in Add.make_args(expr):
coeff, monom = [], zero_monom.copy()
for factor in Mul.make_args(term):
base, exp = decompose_power(factor)
if exp < 0:
exp, base = -exp, Pow(base, -1)
try:
monom[indices[base]] += exp
continue
except KeyError as exc:
if factor.free_symbols & set(opt.gens):
raise PolynomialError(f'{factor} contains an element'
' of the generators set') from exc
coeff.append(factor)
monom = tuple(monom)
poly[monom] = Mul(*coeff) + poly.get(monom, 0)
polys.append(poly)
return polys
def _parallel_dict_from_expr(exprs, opt):
"""Transform expressions into a multinomial form."""
if any(not expr.is_commutative for expr in exprs):
raise PolynomialError('non-commutative expressions are not supported')
if not opt.gens:
opt = opt.clone({'gens': _find_gens(exprs, opt)})
return _parallel_dict_from_expr_if_gens(exprs, opt), opt.clone()