2 # Author: Pearu Peterson
3 # Created: February 2008
5 """ Provides PolynomialRing class.
8 __docformat__
= "restructuredtext"
9 __all__
= "PolynomialRing"
11 from ..core
import classes
, IntegerList
, Expr
12 from ..utils
import SYMBOL
, NUMBER
, ADD
, MUL
, POW
, SPARSE_POLY
, SUB
, DIV
13 from ..basealgebra
.algebra
import Algebra
14 from ..ring
import CommutativeRing
15 from ..basealgebra
.verbatim
import Verbatim
16 from ..arithmetic
.numbers
import div
17 from ..arithmetic
.number_theory
import multinomial_coefficients
19 def cmp_symbols(x
, y
):
20 return cmp(str(x
), str(y
))
22 class PolynomialRingFactory(type):
23 """ Factory of polynomial rings with symbols and coefficient ring.
25 def __new__(typ
, name
, bases
, attrdict
):
26 if not attrdict
.has_key('ring'):
27 attrdict
['ring'] = classes
.Calculus
28 if not attrdict
.has_key('variables'):
29 attrdict
['variables'] = ()
31 cls
= type.__new
__(typ
, name
, bases
, attrdict
)
32 cls
.zero
= cls
.Number(0)
33 cls
.one
= cls
.Number(1)
36 def __eq__(self
, other
):
37 if isinstance(other
, PolynomialRingFactory
):
38 return self
.ring
==other
.ring
and self
.variables
==other
.variables
41 def __ne__(self
, other
):
42 return not self
==other
44 def __getitem__(self
, ring_info
, cache
={}):
45 """ Return a new polynomial ring class
49 PolynomialRing[<seq of variables>, <coefficient ring>]
50 PolynomialRing[<n>, <coefficient ring>] is
51 PolynomialRing[['X%s'%i for i in range(<n>)], <coefficient ring>]
52 PolynomialRing[<variable info>] is
53 PolynomialRing[<variable info>, Calculus]
55 if isinstance(ring_info
, (int, long)):
57 assert nvars
>=0,`nvars`
58 variables
= ['X%s'%i for i
in range(nvars
)]
59 ring
= classes
.Calculus
60 elif isinstance(ring_info
, tuple) and isinstance(ring_info
[-1], type):
62 var_info
, ring
= ring_info
63 if isinstance(var_info
,(int, long)):
65 assert nvars
>=0,`nvars`
66 variables
= ['X%s'%i for i
in range(nvars
)]
67 elif isinstance(var_info
,(tuple, list, set)):
69 elif isinstance(var_info
, str):
72 raise TypeError(`ring_info`
)
74 variables
= ring_info
[:-1]
76 elif isinstance(ring_info
, (tuple, list, set)):
78 ring
= classes
.Calculus
79 elif isinstance(ring_info
, str):
80 variables
= ring_info
,
81 ring
= classes
.Calculus
83 raise TypeError(`ring_info`
)
84 variables
= tuple(sorted(variables
, cmp=cmp_symbols
))
85 nvars
= len(variables
)
87 r
= None #cache.get((variables, ring))
89 name
= '%s[%s, %s]' % (self
.__name
__, tuple(variables
), ring
.__name
__)
90 r
= PolynomialRingFactory(name
,
92 dict(nvars
=nvars
, ring
= ring
,
93 variables
= variables
))
94 #cache[variables, ring] = r
97 def is_subring(self
, other
):
98 """ Check if self contains other as a subring, i.e. whether
99 the other instances can be converted to self instances.
101 if not isinstance(other
, PolynomialRingFactory
):
103 if self
.ring
!= other
.ring
:
105 return set(other
.variables
).issubset(self
.variables
)
107 AdditiveTuple
= IntegerList
109 class PolynomialRing(CommutativeRing
):
110 """ Base class to polynomial rings that holds polynomial information
111 using pairs ``(<exponents>: <coefficient>)`` stored in Python dictionary.
113 Suitable for representing sparse multivariate polynomials.
116 __slots__
= ['_degree', '_ldegree']
121 __metaclass__
= PolynomialRingFactory
123 __str__
= CommutativeRing
.__str
__
124 __repr__
= CommutativeRing
.__repr
__
127 def convert(cls
, data
, typeerror
=True):
128 if isinstance(data
, list):
129 if cls
.nvars
==1 and data
:
130 if type(data
[0]) not in [tuple,list,set]:
131 data
= [(i
,c
) for (i
,c
) in enumerate(data
) if c
]
133 return cls(SPARSE_POLY
, data
)
134 elif isinstance(data
, dict):
135 return cls(SPARSE_POLY
, data
)
136 r
= super(CommutativeRing
, cls
).convert(data
, typeerror
=True)
140 def get_operand_algebra(cls
, head
, index
=0):
141 if head
in [ADD
, SUB
, MUL
]:
148 return cls
.handle_get_operand_algebra_failure(head
, index
)
150 def __eq__(self
, other
):
151 if type(other
)==type(self
):
152 return self
.pair
== other
.pair
153 return CommutativeRing
.__eq
__(self
, other
)
156 def Symbol(cls
, obj
):
157 """ Return symbol element of a polynomial ring. The result
158 may be an instance of a super polynomial ring.
162 r = PolynomialRing['x']
163 r.Symbol('x') -> r({1:1})
164 r.Symbol('y') -> PolynomialRing['x','y']({(0,1):1})
167 i
= list(cls
.variables
).index(obj
)
171 cls
= PolynomialRing
[cls
.variables
+(obj
,), cls
.ring
]
172 i
= list(cls
.variables
).index(obj
)
175 return cls(SPARSE_POLY
, {AdditiveTuple(l
):1})
178 def convert_symbol(cls
, obj
):
180 i
= list(cls
.variables
).index(obj
)
184 cls
= PolynomialRing
[cls
.variables
+(obj
,), cls
.ring
]
185 i
= list(cls
.variables
).index(obj
)
188 return cls(SPARSE_POLY
, {AdditiveTuple(l
):1})
191 def Number(cls
, obj
):
192 """ Return number element of a polynomial ring.
196 r = PolynomialRing['x']
197 r.Number(2) -> r({0:2})
199 data
= {AdditiveTuple((0,)*cls
.nvars
): obj
} if obj
else {}
200 return cls(SPARSE_POLY
, data
)
204 r
= cls(SPARSE_POLY
, {})
208 iadd_POLY_POLY(r
, t
, cls
)
209 elif cls
.is_subring(tcls
):
210 t
= t
.as_algebra(cls
)
211 assert cls
==t
.__class
__,`cls
,t`
212 iadd_POLY_POLY(r
, t
, cls
)
213 elif tcls
.is_subring(cls
):
215 r
= r
.as_algebra(cls
)
216 iadd_POLY_POLY(r
, t
, cls
)
217 elif cls
.ring
==tcls
.ring
:
218 cls
= PolynomialRing
[cls
.variables
+tcls
.variables
, cls
.ring
]
219 r
= r
.as_algebra(cls
)
220 t
= t
.as_algebra(cls
)
221 iadd_POLY_POLY(r
, t
, cls
)
223 raise NotImplementedError(`r
, t`
)
234 r
= mul_POLY_POLY(r
, t
, cls
)
235 elif cls
.is_subring(tcls
):
236 t
= t
.as_algebra(cls
)
237 assert cls
==t
.__class
__,`cls
,t`
238 r
= mul_POLY_POLY(r
, t
, cls
)
239 elif tcls
.is_subring(cls
):
241 r
= r
.as_algebra(cls
)
242 r
= mul_POLY_POLY(r
, t
, cls
)
243 elif cls
.ring
==tcls
.ring
:
244 cls
= PolynomialRing
[cls
.variables
+tcls
.variables
, cls
.ring
]
245 r
= r
.as_algebra(cls
)
246 t
= t
.as_algebra(cls
)
247 r
= mul_POLY_POLY(r
, t
, cls
)
249 raise NotImplementedError(`r
, t`
)
255 def Pow(cls
, base
, exp
):
256 r
= cls
.__pow
__(base
, exp
)
257 if r
is NotImplemented:
258 raise NotImplementedError(`base
,exp`
)
262 def convert_coefficient(cls
, obj
, typeerror
=True):
263 if not isinstance(obj
, cls
.ring
):
264 r
= cls
.ring
.convert_coefficient(obj
, typeerror
=False)
267 r
= cls
.ring
.convert(obj
, typeerror
=False)
271 raise TypeError('%s.convert_coefficient: failed to convert %s instance'\
272 ' to coefficient algebra, expected int|long object'\
273 % (cls
.__name
__, obj
.__class
__.__name
__))
275 return NotImplemented
277 def as_verbatim(self
):
279 symbols
= [Verbatim
.convert(v
) for v
in self
.variables
]
281 for exps
in sorted(data
.keys(), reverse
=True):
286 factors
= [Verbatim
.convert(coeff
)]
287 if not isinstance(exps
, tuple):
289 for s
,e
in zip(symbols
, exps
):
294 factors
.append(s
**e
) #XXX: ????
296 terms
.append(Verbatim
.convert(coeff
))
297 elif len(factors
)==1:
298 terms
.append(factors
[0])
300 terms
.append(Verbatim(MUL
,tuple(factors
)))
302 return Verbatim
.convert(0)
305 return Verbatim(ADD
, tuple(terms
))
307 def as_algebra(self
, target_cls
):
309 if cls
is target_cls
:
311 if issubclass(target_cls
, PolynomialRing
):
312 target_ring
= target_cls
.ring
313 if cls
.variables
== target_cls
.variables
:
314 data
= dict([(e
,target_ring
.convert(c
)) for e
,c
in self
.data
.iteritems()])
315 return target_cls(data
)
316 if cls
.ring
== target_ring
:
317 new_cls
= PolynomialRing
[set(cls
.variables
+ target_cls
.variables
), target_ring
]
318 new_variables
= list(new_cls
.variables
)
319 indices
= [new_variables
.index(v
) for v
in cls
.variables
]
322 for e
,c
in self
.data
.iteritems():
324 if not isinstance(e
, tuple):
326 for i
,j
in enumerate(indices
):
328 data
[AdditiveTuple(new_e
)] = c
330 return NotImplemented
333 head
, data
= self
.pair
334 return type(self
)(head
, dict([(e
,-c
) for e
, c
in data
.iteritems()]))
336 def __add__(self
, other
):
338 other_cls
= other
.__class
__
339 if cls
== other_cls
and self
.head
is other
.head
:
340 return add_POLY_POLY(self
, other
, cls
)
341 other
= self
.convert(other
)
342 if other
is NotImplemented:
344 other_cls
= other
.__class
__
345 if cls
== other_cls
and self
.head
is other
.head
:
346 return add_POLY_POLY(self
, other
, cls
)
347 elif self
.nvars
< other
.nvars
:
349 return self
.head
.add(cls
, self
, other
)
353 def __mul__(self
, other
):
355 other_cls
= other
.__class
__
356 if cls
== other_cls
and self
.head
is other
.head
:
357 return mul_POLY_POLY(self
, other
, cls
)
358 other2
= self
.convert_coefficient(other
, typeerror
=False)
359 if other2
is not NotImplemented:
360 return mul_POLY_COEFF(self
, other2
, cls
)
361 other
= self
.convert(other
, typeerror
=False)
362 if other
is NotImplemented:
364 other_cls
= other
.__class
__
365 if cls
== other_cls
and self
.head
is other
.head
:
366 return mul_POLY_POLY(self
, other
, cls
)
367 elif self
.nvars
< other
.nvars
:
369 return self
.head
.commutative_mul(cls
, self
, other
)
373 def __divmod__(self
, other
):
375 other_cls
= other
.__class
__
378 return divmod_POLY1_POLY1_SPARSE(self
, other
, cls
)
379 return NotImplemented
381 def __div__(self
, other
):
383 r
= self
.convert_coefficient(other
, typeerror
=False)
384 if r
is not NotImplemented:
385 return mul_POLY_COEFF(self
, div(1, r
), cls
)
386 return divmod(self
, other
)[0]
388 def __mod__(self
, other
):
389 return divmod(self
, other
)[1]
391 def __pow__(self
, other
):
392 if isinstance(other
, type(self
)):
393 if other
.head
is NUMBER
:
395 if other
.head
is SPARSE_POLY
:
398 exps
, coeff
= data
.items()[0]
399 if exps
.data
==[0]*len(exps
):
401 if isinstance(other
,(int, long)):
402 return pow_POLY_INT(self
, other
, self
.__class
__)
403 print `self`
, `other
.pair`
404 return NotImplemented
406 def __call__(self
, *args
):
407 # XXX: Use Horner scheme.
410 for exps
, coeff
in self
.data
.iteritems():
411 r
= coeff
* args
[0]**exps
+ r
413 for exps
, coeff
in self
.data
.iteritems():
414 r
= reduce(lambda x
,y
: x
*y
, [a
**e
for a
,e
in zip(args
,exps
)], coeff
) + r
427 self
._degree
= d
= max(data
.keys())
429 self
._degree
= d
= map(max,zip(*data
.keys()))
438 self
._ldegree
= d
= 0
440 self
._ldegree
= d
= 0
442 self
._ldegree
= d
= min(data
.keys())
444 self
._ldegree
= d
= map(min,zip(*data
.keys()))
453 return data
[self
.degree
]
455 raise NotImplementedError(`self
,nvars`
)
457 def diff(self
, index
=0): # TODO: don't use index as the order of variables is automatically sorted
460 head
, data
= self
.pair
463 assert index
==0,`index`
464 for exps
, coeff
in data
.iteritems():
466 d
[exps
-1] = coeff
* exps
468 assert 0<=index
<self
.nvars
, `index`
469 for exps
, coeff
in data
.iteritems():
472 d
[exps
.add(index
,-1)] = coeff
* e
473 return type(self
)(head
, d
)
475 def variable_diff(self
, variable
, order
=1):
477 index
= list(self
.variables
).index(variable
)
480 if index
is not None:
481 return self
.head
.diff_index(type(self
), self
.data
, self
, index
, order
=order
)
482 raise NotImplementedError(`self
.variables
, variable
, index`
)
484 def variable_integrate(self
, variable
, *bounds
):
485 """ Return integral over variable.
487 poly.variable_integrate(x) -> indefinite integral
488 poly.variable_integrate(x, a, b) -> definite integral
491 index
= list(self
.variables
).index(variable
)
494 if index
is not None:
495 indef_integral
= self
.head
.integrate_indefinite_index(type(self
), self
.data
, self
, index
)
498 return indef_integral
.variable_subs(variable
, high
) - indef_integral
.variable_subs(variable
, low
)
499 return indef_integral
500 raise NotImplementedError(`self
.variables
, variable
, index`
)
502 def variable_subs(self
, variable
, newexpr
):
503 """ Substitute variable with newexpr that must be instance of the same ring.
506 newexpr
= cls(newexpr
)
508 index
= list(self
.variables
).index(variable
)
511 if index
is not None:
512 head
, data
= self
.pair
513 result
= cls
.Number(0)
514 variables
= cls
.variables
515 for exps
, coeff
in data
.iteritems():
517 for i
,exp
in enumerate(exps
):
522 term
*= cls
.Symbol(variables
[i
])**exp
523 result
+= term
* cls
.Number(coeff
)
525 raise NotImplementedError(`self
.variables
, variable
, index`
)
527 def divmod_POLY1_POLY1_SPARSE(lhs
, rhs
, cls
):
531 raise ZeroDivisionError, "polynomial division"
532 other_iter
= rhs
.data
.iteritems
535 dseq
= [0] + dr
.keys()
537 dseq_append
= dseq
.append
538 dseq_remove
= dseq
.remove
542 return cls(SPARSE_POLY
, dq
), cls(SPARSE_POLY
, dr
)
548 for e0
,c0
in other_iter():
563 def divmod_POLY1_POLY1_DENSE(lhs
, rhs
, cls
):
565 raise ZeroDivisionError, "polynomial division"
570 for e
,c
in lhs
.data
.iteritems():
572 for e
,c
in rhs
.data
.iteritems():
576 for k
in range(n
-nv
, -1, -1):
577 q
[k
] = div(r
[nv
+k
], v
[nv
])
578 for j
in range(nv
+k
-1, k
-1, -1):
580 for j
in range(nv
, n
+1, 1):
582 q
, r
= cls(q
), cls(r
)
585 def pow_POLY_INT(base
, exp
, cls
):
591 return NotImplemented
594 exps
, coeff
= data
.items()[0]
595 return cls(SPARSE_POLY
, {exps
* exp
: coeff
** exp
})
600 for k
,c_kn
in multinomial_coefficients(m
, exp
).iteritems():
601 new_exps
= AdditiveTuple((0,)*nvars
)
603 for i
,e
in enumerate(k
):
605 exps
, coeff
= items
[i
]
607 new_coeff
*= coeff
** e
610 d
[new_exps
] = new_coeff
617 return cls(SPARSE_POLY
, d
)
620 def add_POLY_POLY(lhs
, rhs
, cls
):
622 for exps
, coeff
in rhs
.data
.iteritems():
632 return cls(SPARSE_POLY
, d
)
634 def iadd_POLY_POLY(lhs
, rhs
, cls
):
636 for exps
, coeff
in rhs
.data
.iteritems():
648 def mul_POLY_POLY(lhs
, rhs
, cls
):
650 for exps1
, coeff1
in lhs
.data
.iteritems():
651 for exps2
, coeff2
in rhs
.data
.iteritems():
653 coeff
= coeff1
* coeff2
663 return cls(SPARSE_POLY
, d
)
665 def mul_POLY_COEFF(lhs
, rhs
, cls
):
667 for exps
, coeff
in lhs
.data
.iteritems():
670 d
[exps
] = coeff
* rhs
677 return cls(SPARSE_POLY
, d
)