Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

is-23: polynomials viewed as operators #28

Merged
merged 8 commits into from
Feb 27, 2023
Merged
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.0.4
0.0.4+is23
99 changes: 96 additions & 3 deletions dalgebra/ring_w_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@

from __future__ import annotations

from typing import Callable, Collection
from ore_algebra.ore_algebra import OreAlgebra, OreAlgebra_generic
from sage.all import ZZ, latex, Parent
from sage.categories.all import Morphism, Category, Rings, CommutativeRings, CommutativeAdditiveGroups
from sage.categories.morphism import IdentityMorphism, SetMorphism # pylint: disable=no-name-in-module
Expand All @@ -151,6 +151,7 @@
from sage.structure.element import parent, Element #pylint: disable=no-name-in-module
from sage.structure.factory import UniqueFactory #pylint: disable=no-name-in-module
from sage.symbolic.ring import SR #pylint: disable=no-name-in-module
from typing import Callable, Collection

_Rings = Rings.__classcall__(Rings)
_CommutativeRings = CommutativeRings.__classcall__(CommutativeRings)
Expand Down Expand Up @@ -414,14 +415,16 @@ def skew(self, element: Element, skew: int = None) -> Element:
### OTHER METHODS
##########################################################
@abstract_method
def operator_ring(self) -> Ring:
def linear_operator_ring(self) -> Ring:
r'''
Method to get the operator ring of ``self``.

When we consider a ring with operators, we can always consider a new (usually non-commutative)
ring where we extend ``self`` polynomially with all the operators and its elements represent
new operators created from the operators defined over ``self``.

This ring is the ring of linear operators over the ground ring.

This method return this new structure.
'''
raise NotImplementedError("Method 'operator_ring' need to be implemented")
Expand Down Expand Up @@ -937,25 +940,115 @@ def __init__(self,
self.__wrapped = base

# registering conversion to simpler structures
current = self.base()
current = self.__wrapped
morph = RingWithOperators_Wrapper_SimpleMorphism(self, current)
current.register_conversion(morph)
while(not(current.base() == current)):
current = current.base()
morph = RingWithOperators_Wrapper_SimpleMorphism(self, current)
current.register_conversion(morph)

# registering coercion into its ring of linear operators
try:
operator_ring = self.linear_operator_ring()
morph = RingWithOperators_Wrapper_SimpleMorphism(self, operator_ring)
operator_ring.register_conversion(morph)
except:
pass

#########################################################################################################
### CREATING THE NEW OPERATORS FOR THE CORRECT STRUCTURE
self.__operators : tuple[WrappedMap] = tuple([WrappedMap(self, operator) for operator in operators])

#########################################################################################################
### CREATING CACHED VARIABLES
self.__linear_operator_ring : OreAlgebra_generic = None

@property
def wrapped(self) -> CommutativeRing: return self.__wrapped

def operators(self) -> tuple[WrappedMap]: return self.__operators

def operator_types(self) -> tuple[str]: return self.__types

def linear_operator_ring(self) -> OreAlgebra_generic:
r'''
Overridden method from :func:`~RingsWithOperators.ParentMethods.linear_operator_ring`.

This method builds the ring of linear operators using :mod:`ore_algebra`. It raises an error
if this can not be done for any reason. The generators of the new ring are named
depending on the type:

* "D" for derivations,
* "S" for homomorphisms,
* "K" for skew derivations.

If more than one is present, we use a subindex enumerating them.

EXAMPLES::

sage: from dalgebra import *
sage: R = DifferentialRing(QQ[x], diff)
sage: R.linear_operator_ring()
Univariate Ore algebra in D over Univariate Polynomial Ring in x over Rational Field

This also works when having several operators::

sage: B.<x,y> = QQ[]; dx,dy = B.derivation_module().gens()
sage: s = B.Hom(B)([x+1,y-1])
sage: R = DifferenceRing(DifferentialRing(B, dx, dy), s); R
Ring [[Multivariate Polynomial Ring in x, y over Rational Field], (d/dx, d/dy, Hom({x: x + 1, y: y - 1}))]
sage: R.linear_operator_ring()
Multivariate Ore algebra in D_0, D_1, S over Multivariate Polynomial Ring in x, y over Rational Field

We can check that `D_0` represents the first derivation (i.e., derivation w.r.t. `x`), `D_1` represents the second derivative and
`S` represents the special shift we are considering::

sage: D_0,D_1,S = R.linear_operator_ring().gens()
sage: D_0*x, D_0*y
(x*D_0 + 1, y*D_0)
sage: D_1*x, D_1*y
(x*D_1, y*D_1 + 1)
sage: S*x, S*y
((x + 1)*S, (y - 1)*S)

This can only be used when the operators in the ring commute::

sage: ns = B.Hom(B)([x^2, y^2])
sage: T = DifferenceRing(DifferentialRing(B, dx, y*dy), ns); T
Ring [[Multivariate Polynomial Ring in x, y over Rational Field], (d/dx, y*d/dy, Hom({x: x^2, y: y^2}))]
sage: T.all_operators_commute()
False
sage: T.linear_operator_ring()
Traceback (most recent call last):
...
TypeError: Ore Algebra can only be created with commuting operators.

But this can be done when the operators are not in the same ring::

sage: U = DifferenceRing(B, ns); U.linear_operator_ring()
Univariate Ore algebra in S over Multivariate Polynomial Ring in x, y over Rational Field
'''
if self.__linear_operator_ring == None:
## We need the operators to commute
if not self.all_operators_commute():
raise TypeError("Ore Algebra can only be created with commuting operators.")

base_ring = self.wrapped

operators = []
def zero(_): return 0
for operator, ttype in zip(self.operators(), self.operator_types()):
if ttype == "homomorphism":
operators.append((f"S{f'_{self.differences().index(operator)}' if self.ndifferences() > 1 else ''}", operator.function, zero))
elif ttype == "derivation":
operators.append((f"D{f'_{self.derivations().index(operator)}' if self.nderivations() > 1 else ''}", base_ring.Hom(base_ring).one(), operator.function))
elif ttype == "skew":
operators.append((f"K{f'_{self.skews().index(operator)}' if self.nskews() > 1 else ''}", operator.function.twist, operator.function))

self.__linear_operator_ring = OreAlgebra(self.wrapped, *operators)
return self.__linear_operator_ring

## Coercion methods
def _has_coerce_map_from(self, S) -> bool:
r'''
Expand Down
17 changes: 14 additions & 3 deletions dalgebra/rwo_polynomial/rwo_polynomial_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ def contains(self, element: RWOPolynomial) -> bool:
def __contains__(self, other) -> bool:
return self.contains(other)

def index(self, element: RWOPolynomial, as_tuple : bool = True) -> int:
def index(self, element: RWOPolynomial, as_tuple : bool = None) -> int | tuple[int]:
r'''
Method to know the index to generate ``element`` using ``self``.

Expand All @@ -233,16 +233,19 @@ def index(self, element: RWOPolynomial, as_tuple : bool = True) -> int:
Assumed the string form of ``X_Y`` from :func:`contains`, this method returns
the numerical value of ``Y`` or an error if not possible.
'''
as_tuple = self._parent.noperators() > 1 if as_tuple is None else as_tuple # defaulting value for as_tuple
if(self.contains(element)):
str_element = str(element).split("_")
if self._name == "_".join(str_element[:-1]): # pure index given
index = self.index_map(int(str_element[-1])) if self._parent.noperators() > 1 else int(str_element[-1])
index = tuple(self.index_map(int(str_element[-1]))) if self._parent.noperators() > 1 else int(str_element[-1])
else:
index = tuple(int(el) for el in str_element[-self._parent.noperators():])

# here ''index'' contains a number if noperators == 1 or a tuple
# here ''index'' contains a number if noperators == 1 or a tuple
if self._parent.noperators() > 1 and not as_tuple: # if we want the number instead of the tuple
index = self.index_map.inverse(index)
elif self._parent.noperators() == 1 and as_tuple:
index = (index,)
return index

def next(self, element: RWOPolynomial, operation : int) -> RWOPolynomial:
Expand Down Expand Up @@ -632,6 +635,14 @@ def is_linear(self, variables : RWOPolynomialGen | Collection[RWOPolynomialGen]=
) <= 1
for t in self.monomials())

def as_linear_operator(self):
r'''
Method to convert this operator to a linear operator.

See method :func:`~.rwo_polynomial_ring.RWOPolynomialRing_dense.as_linear_operator`.
'''
return self.parent().as_linear_operator(self)

# Magic methods
def __call__(self, *args, **kwargs) -> RWOPolynomial:
r'''
Expand Down
Loading