Path: blob/master/src/sage/structure/coerce_actions.pyx
8814 views
"""1Coerce actions2"""3#*****************************************************************************4# Copyright (C) 2007 Robert Bradshaw <[email protected]>5#6# Distributed under the terms of the GNU General Public License (GPL)7#8# http://www.gnu.org/licenses/9#*****************************************************************************1011import operator1213include "sage/ext/stdsage.pxi"14include "sage/ext/interrupt.pxi"15from cpython.int cimport *16from cpython.number cimport *17include "coerce.pxi"1819from coerce_exceptions import CoercionException2021cdef extern from *:22ctypedef struct RefPyObject "PyObject":23int ob_refcnt2425cdef _record_exception():26from element import get_coercion_model27get_coercion_model()._record_exception()2829cdef inline an_element(R):30if isinstance(R, Parent):31return R.an_element()32else:33for x in ([(1, 2)], "abc", 10.5, 10):34try:35return R(x)36except Exception:37pass3839cdef class LAction(Action):40"""Action calls _l_action of the actor."""41def __init__(self, G, S):42Action.__init__(self, G, S, True, operator.mul)43cpdef _call_(self, g, a):44return g._l_action(a) # a * g454647cdef class RAction(Action):48"""Action calls _r_action of the actor."""49def __init__(self, G, S):50Action.__init__(self, G, S, False, operator.mul)51cpdef _call_(self, a, g):52return g._r_action(a) # g * a535455# In the code below, I take the convention that g is acting on a.5657cdef class GenericAction(Action):5859cdef _codomain6061def __init__(self, Parent G, S, is_left, bint check=True):62"""63TESTS::6465sage: sage.structure.coerce_actions.ActedUponAction(MatrixSpace(ZZ, 2), Cusps, True)66Left action by Full MatrixSpace of 2 by 2 dense matrices over Integer Ring on Set P^1(QQ) of all cusps6768sage: sage.structure.coerce_actions.GenericAction(QQ, Zmod(6), True)69Traceback (most recent call last):70...71NotImplementedError: Action not implemented.7273This will break if we tried to use it::7475sage: sage.structure.coerce_actions.GenericAction(QQ, Zmod(6), True, check=False)76Left action by Rational Field on Ring of integers modulo 67778"""79Action.__init__(self, G, S, is_left, operator.mul)80if check:81res = self.act(G.an_element(), S.an_element())82if res is None:83raise CoercionException84_codomain = parent_c(res)8586def codomain(self):87"""88Returns the "codomain" of this action, i.e. the Parent in which the89result elements live. Typically, this should be the same as the90acted upon set.9192EXAMPLES::9394sage: A = sage.structure.coerce_actions.ActedUponAction(MatrixSpace(ZZ, 2), Cusps, True)95sage: A.codomain()96Set P^1(QQ) of all cusps97sage: A = sage.structure.coerce_actions.ActOnAction(SymmetricGroup(3), QQ['x,y,z'], False)98sage: A.codomain()99Multivariate Polynomial Ring in x, y, z over Rational Field100"""101if self._codomain is None:102self._codomain = parent_c(self.act(an_element(self.G),103an_element(self.underlying_set())))104return self._codomain105106107cdef class ActOnAction(GenericAction):108"""109Class for actions defined via the _act_on_ method.110"""111cpdef _call_(self, a, b):112"""113TESTS::114115sage: G = SymmetricGroup(3)116sage: R.<x,y,z> = QQ[]117sage: A = sage.structure.coerce_actions.ActOnAction(G, R, False)118sage: A._call_(x^2 + y - z, G((1,2)))119y^2 + x - z120sage: A._call_(x+2*y+3*z, G((1,3,2)))1212*x + 3*y + z122123sage: type(A)124<type 'sage.structure.coerce_actions.ActOnAction'>125"""126if self._is_left:127return (<Element>a)._act_on_(b, True)128else:129return (<Element>b)._act_on_(a, False)130131cdef class ActedUponAction(GenericAction):132"""133Class for actions defined via the _acted_upon_ method.134"""135cpdef _call_(self, a, b):136"""137TESTS::138139sage: A = sage.structure.coerce_actions.ActedUponAction(MatrixSpace(ZZ, 2), Cusps, True)140sage: A.act(matrix(ZZ, 2, [1,0,2,-1]), Cusp(1,2))141Infinity142sage: A._call_(matrix(ZZ, 2, [1,0,2,-1]), Cusp(1,2))143Infinity144145sage: type(A)146<type 'sage.structure.coerce_actions.ActedUponAction'>147"""148if self._is_left:149return (<Element>b)._acted_upon_(a, False)150else:151return (<Element>a)._acted_upon_(b, True)152153def detect_element_action(Parent X, Y, bint X_on_left, X_el=None, Y_el=None):154r"""155Returns an action of X on Y or Y on X as defined by elements X, if any.156157EXAMPLES::158159sage: from sage.structure.coerce_actions import detect_element_action160sage: detect_element_action(ZZ['x'], ZZ, False)161Left scalar multiplication by Integer Ring on Univariate Polynomial Ring in x over Integer Ring162sage: detect_element_action(ZZ['x'], QQ, True)163Right scalar multiplication by Rational Field on Univariate Polynomial Ring in x over Integer Ring164sage: detect_element_action(Cusps, MatrixSpace(ZZ, 2), False)165Left action by Full MatrixSpace of 2 by 2 dense matrices over Integer Ring on Set P^1(QQ) of all cusps166sage: detect_element_action(Cusps, MatrixSpace(ZZ, 2), True),167(None,)168sage: detect_element_action(ZZ, QQ, True),169(None,)170171TESTS:172173This test checks that the issue in :trac:`7718` has been fixed::174175sage: class MyParent(Parent):176....: def an_element(self):177....: pass178....:179sage: A = MyParent()180sage: detect_element_action(A, ZZ, True)181Traceback (most recent call last):182...183RuntimeError: an_element() for <class '__main__.MyParent'> returned None184"""185cdef Element x186if X_el is None or (parent_c(X_el) is not X):187x = an_element(X)188else:189x = X_el190if x is None:191raise RuntimeError("an_element() for %s returned None" % X)192if Y_el is None or (parent_c(Y_el) is not Y):193y = an_element(Y)194else:195y = Y_el196if y is None:197if isinstance(Y, Parent):198raise RuntimeError("an_element() for %s returned None" % Y)199else:200return # don't know how to make elements of this type...201if isinstance(x, ModuleElement) and isinstance(y, RingElement):202# Elements defining _lmul_ and _rmul_203try:204return (RightModuleAction if X_on_left else LeftModuleAction)(Y, X, y, x)205except CoercionException, msg:206_record_exception()207try:208# Elements defining _act_on_209if x._act_on_(y, X_on_left) is not None:210return ActOnAction(X, Y, X_on_left, False)211except CoercionException:212_record_exception()213if isinstance(Y, Parent):214try:215# Elements defining _acted_upon_216if x._acted_upon_(y, X_on_left) is not None:217return ActedUponAction(Y, X, not X_on_left, False)218except CoercionException:219_record_exception()220221222cdef class ModuleAction(Action):223"""224Module action.225226.. SEEALSO::227228This is an abstract class, one must actually instantiate a229:class:`LeftModuleAction` or a :class:`RightModuleAction`.230231INPUT:232233- ``G`` -- the actor, an instance of :class:`~sage.structure.parent.Parent`.234- ``S`` -- the object that is acted upon.235- ``g`` -- optional, an element of ``G``.236- ``a`` -- optional, an element of ``S``.237- ``check`` -- if True (default), then there will be no consistency tests238performed on sample elements.239240NOTE:241242By default, the sample elements of ``S`` and ``G`` are obtained from243:meth:`~sage.structure.parent.Parent.an_element`, which relies on the244implementation of an ``_an_element_()`` method. This is not always245awailable. But usually, the action is only needed when one already246*has* two elements. Hence, by :trac:`14249`, the coercion model will247pass these two elements the the :class:`ModuleAction` constructor.248249The actual action is implemented by the ``_rmul_`` or ``_lmul_``250function on its elements. We must, however, be very particular about251what we feed into these functions, because they operate under the252assumption that the inputs lie exactly in the base ring and may253segfault otherwise. Thus we handle all possible base extensions254manually here.255256"""257def __init__(self, G, S, g=None, a=None, check=True):258"""259This creates an action of an element of a module by an element of its260base ring. The simplest example to keep in mind is R acting on the261polynomial ring R[x].262263EXAMPLES::264265sage: from sage.structure.coerce_actions import LeftModuleAction266sage: LeftModuleAction(ZZ, ZZ['x'])267Left scalar multiplication by Integer Ring on Univariate Polynomial Ring in x over Integer Ring268sage: LeftModuleAction(ZZ, QQ['x'])269Left scalar multiplication by Integer Ring on Univariate Polynomial Ring in x over Rational Field270sage: LeftModuleAction(QQ, ZZ['x'])271Left scalar multiplication by Rational Field on Univariate Polynomial Ring in x over Integer Ring272sage: LeftModuleAction(QQ, ZZ['x']['y'])273Left scalar multiplication by Rational Field on Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Integer Ring274275The following tests against a problem that was relevant during work on276trac ticket #9944::277278sage: R.<x> = PolynomialRing(ZZ)279sage: S.<x> = PolynomialRing(ZZ, sparse=True)280sage: 1/R.02811/x282sage: 1/S.02831/x284285"""286Action.__init__(self, G, S, not PY_TYPE_CHECK(self, RightModuleAction), operator.mul)287if not isinstance(G, Parent):288# only let Parents act289raise TypeError, "Actor must be a parent."290base = S.base()291if base is S or base is None:292# The right thing to do is a normal multiplication293raise CoercionException, "Best viewed as standard multiplication"294# Objects are implemented with the assumption that295# _rmul_ is given an element of the base ring296if G is not base:297# first we try the easy case of coercing G to the base ring of S298self.connecting = base.coerce_map_from(G)299if self.connecting is None:300# otherwise, we try and find a base extension301from sage.categories.pushout import pushout302# this may raise a type error, which we propagate303self.extended_base = pushout(G, S)304# make sure the pushout actually gave correct a base extension of S305if self.extended_base.base() != pushout(G, base):306raise CoercionException, "Actor must be coercible into base."307else:308self.connecting = self.extended_base.base().coerce_map_from(G)309if self.connecting is None:310# this may happen if G is, say, int rather than a parent311# TODO: let python types be valid actions312raise CoercionException, "Missing connecting morphism"313314# Don't waste time if our connecting morphisms happen to be the identity.315if self.connecting is not None and self.connecting.codomain() is G:316self.connecting = None317318if self.extended_base is not None and self.extended_base is S:319self.extended_base = None320321# At this point, we can assert it is safe to call _Xmul_c322the_ring = G if self.connecting is None else self.connecting.codomain()323the_set = S if self.extended_base is None else self.extended_base324assert the_ring is the_set.base(), "BUG in coercion model\n Apparently there are two versions of\n %s\n in the cache."%the_ring325326if not check:327return328if g is None:329g = G.an_element()330if parent_c(g) is not G:331raise CoercionException("The parent of %s is not %s but %s"%(g,G,parent_c(g)))332if a is None:333a = S.an_element()334if parent_c(a) is not S:335raise CoercionException("The parent of %s is not %s but %s"%(a,S,parent_c(a)))336if not isinstance(g, RingElement) or not isinstance(a, ModuleElement):337raise CoercionException("Not a ring element acting on a module element.")338res = self.act(g, a)339if parent_c(res) is not the_set:340# In particular we will raise an error if res is None341raise CoercionException("Result is None or has wrong parent.")342343344def _repr_name_(self):345"""346The default name of this action type, which is has a sane default.347348EXAMPLES::349350sage: from sage.structure.coerce_actions import LeftModuleAction, RightModuleAction351sage: A = LeftModuleAction(ZZ, ZZ['x']); A352Left scalar multiplication by Integer Ring on Univariate Polynomial Ring in x over Integer Ring353sage: A._repr_name_()354'scalar multiplication'355sage: RightModuleAction(GF(5), GF(5)[['t']])356Right scalar multiplication by Finite Field of size 5 on Power Series Ring in t over Finite Field of size 5357"""358return "scalar multiplication"359360def codomain(self):361"""362The codomain of self, which may or may not be equal to the domain.363364EXAMPLES::365366sage: from sage.structure.coerce_actions import LeftModuleAction367sage: A = LeftModuleAction(QQ, ZZ['x,y,z'])368sage: A.codomain()369Multivariate Polynomial Ring in x, y, z over Rational Field370"""371if self.extended_base is not None:372return self.extended_base373return self.underlying_set()374375def domain(self):376"""377The domain of self, which is the module that is being acted on.378379EXAMPLES::380381sage: from sage.structure.coerce_actions import LeftModuleAction382sage: A = LeftModuleAction(QQ, ZZ['x,y,z'])383sage: A.domain()384Multivariate Polynomial Ring in x, y, z over Integer Ring385"""386return self.underlying_set()387388389390cdef class LeftModuleAction(ModuleAction):391392cpdef _call_(self, g, a):393"""394A left module action is an action that takes the ring element as the395first argument (the left side) and the module element as the second396argument (the right side).397398EXAMPLES::399400sage: from sage.structure.coerce_actions import LeftModuleAction401sage: R.<x> = QQ['x']402sage: A = LeftModuleAction(ZZ, R)403sage: A(5, x+1)4045*x + 5405sage: R.<x> = ZZ['x']406sage: A = LeftModuleAction(QQ, R)407sage: A(1/2, x+1)4081/2*x + 1/2409sage: A._call_(1/2, x+1) # safe only when arguments have exactly the correct parent4101/2*x + 1/2411"""412if self.connecting is not None:413g = self.connecting._call_(g)414if self.extended_base is not None:415a = self.extended_base(a)416return (<ModuleElement>a)._rmul_(<RingElement>g) # a * g417418419cdef class RightModuleAction(ModuleAction):420421def __cinit__(self):422self.is_inplace = 0423424cpdef _call_(self, a, g):425"""426A right module action is an action that takes the module element as the427first argument (the left side) and the ring element as the second428argument (the right side).429430EXAMPLES::431432sage: from sage.structure.coerce_actions import RightModuleAction433sage: R.<x> = QQ['x']434sage: A = RightModuleAction(ZZ, R)435sage: A(x+5, 2)4362*x + 10437sage: A._call_(x+5, 2) # safe only when arguments have exactly the correct parent4382*x + 10439"""440cdef PyObject* tmp441if self.connecting is not None:442g = self.connecting._call_(g)443if self.extended_base is not None:444a = self.extended_base(a)445# TODO: figure out where/why the polynomial constructor is caching 'a'446if (<RefPyObject *>a).ob_refcnt == 2:447b = self.extended_base(0)448if (<RefPyObject *>a).ob_refcnt == 1:449# This is a truly new object, mutate it450return (<ModuleElement>a)._ilmul_(<RingElement>g) # a * g451else:452return (<ModuleElement>a)._lmul_(<RingElement>g) # a * g453else:454# If we have few enough references to this object, then we know455# it is safe to do a (mutating) inplace operation.456if (<RefPyObject *>a).ob_refcnt < inplace_threshold + self.is_inplace:457return (<ModuleElement>a)._ilmul_(<RingElement>g) # a * g458else:459return (<ModuleElement>a)._lmul_(<RingElement>g) # a * g460461462463cdef class IntegerMulAction(Action):464465def __init__(self, ZZ, M, is_left=True, m=None):466r"""467This class implements the action `n \cdot a = a + a + \cdots + a` via468repeated doubling.469470Both addition and negation must be defined on the set `M`.471472INPUT:473474- An integer ring, ``ZZ``475- A ``ZZ`` module ``M``476- Optional: An element ``m`` of ``M``477478EXAMPLES::479480sage: from sage.structure.coerce_actions import IntegerMulAction481sage: R.<x> = QQ['x']482sage: act = IntegerMulAction(ZZ, R)483sage: act(5, x)4845*x485sage: act(0, x)4860487sage: act(-3, x-1)488-3*x + 3489"""490if PY_TYPE_CHECK(ZZ, type):491from sage.structure.parent import Set_PythonType492ZZ = Set_PythonType(ZZ)493if m is None:494m = M.an_element()495test = m + (-m) # make sure addition and negation is allowed496Action.__init__(self, ZZ, M, is_left, operator.mul)497498cpdef _call_(self, nn, a):499"""500EXAMPLES::501502sage: from sage.structure.coerce_actions import IntegerMulAction503sage: act = IntegerMulAction(ZZ, GF(101))504sage: act(3, 9)50527506sage: act(3^689, 9)50742508sage: 3^689 * mod(9, 101)50942510511Use round off error to verify this is doing actual repeated addition512instead of just multiplying::513514sage: act = IntegerMulAction(ZZ, RR)515sage: act(49, 1/49) == 49*RR(1/49)516False517"""518if not self._is_left:519a, nn = nn, a520if not PyInt_CheckExact(nn):521nn = PyNumber_Int(nn)522if not PyInt_CheckExact(nn):523return fast_mul(a, nn)524return fast_mul_long(a, PyInt_AS_LONG(nn))525526def __invert__(self):527"""528EXAMPLES::529530sage: from sage.structure.coerce_actions import IntegerMulAction531sage: act = IntegerMulAction(ZZ, CDF)532sage: ~act533Traceback (most recent call last):534...535TypeError: No generic module division by Z.536"""537raise TypeError, "No generic module division by Z."538539def _repr_name_(self):540"""541EXAMPLES::542543sage: from sage.structure.coerce_actions import IntegerMulAction544sage: IntegerMulAction(ZZ, GF(5))545Left Integer Multiplication by Integer Ring on Finite Field of size 5546"""547return "Integer Multiplication"548549550551cdef inline fast_mul(a, n):552# We cannot use sig_on() here because this may call arbitrary Python553# code raising exceptions. -- Jeroen Demeyer554if n < 0:555n = -n556a = -a557pow2a = a558while n & 1 == 0:559pow2a += pow2a560n = n >> 1561sum = pow2a562n = n >> 1563while n != 0:564pow2a += pow2a565if n & 1:566sum += pow2a567n = n >> 1568return sum569570cdef inline fast_mul_long(a, long n):571# We cannot use sig_on() here because this may call arbitrary Python572# code raising exceptions. -- Jeroen Demeyer573if n < 0:574n = -n575a = -a576if n < 4:577if n == 0: return parent_c(a)(0)578elif n == 1: return a579elif n == 2: return a+a580elif n == 3: return a+a+a581pow2a = a582while n & 1 == 0:583pow2a += pow2a584n = n >> 1585sum = pow2a586n = n >> 1587while n != 0:588pow2a += pow2a589if n & 1:590sum += pow2a591n = n >> 1592return sum593594595