Rings with Operators: Category, Factory and Wrapper

Module with all structures for defining rings with operators.

Let \(\sigma: R \rightarrow R\) be an additive homomorphism, i.e., for all elements \(r,s \in R\), the map satisfies \(\sigma(r+s) = \sigma(r) + \sigma(s)\). We define the ring \(R\) with operator \(\sigma\) as the pair \((R, \sigma)\).

Similarly, if we have a set of additive maps \(\sigma_1,\ldots,\sigma_n : R \rightarrow R\). Then we define the ring \(R\) with operators \((\sigma_1,\ldots,\sigma_n)\) as the tuple \((R, (\sigma_1,\ldots,\sigma_n))\).

This module provides the framework to define this type of rings with as many operators as the user wants and we also provide a Wrapper class so we can extend existing ring structures that already exist in SageMath.

The factory RingWithOperator() allows the creation of these rings with operators and will determine automatically in which specified category a ring will belong. For example, we can create the differential ring \((\mathbb{Q}[x], \partial_x)\) or the difference ring \((\mathbb{Q}[x], x \mapsto x + 1)\) with the following code:

sage: from dalgebra import *
sage: dQx = RingWithOperators(QQ[x], lambda p : p.derivative())
sage: sQx = RingWithOperators(QQ[x], lambda p : QQ[x](p)(x=QQ[x].gens()[0] + 1))

Once the rings are created, we can create elements within the ring and apply the corresponding operator:

sage: x = dQx(x)
sage: x.operation()
1
sage: x = sQx(x)
sage: x.operation()
x + 1

We can also create the same ring with both operators together:

sage: dsQx = RingWithOperators(QQ[x], lambda p : p.derivative(), lambda p : QQ[x](p)(x=QQ[x].gens()[0] + 1))
sage: x = dsQx(x)
sage: x.operation(operation=0)
1
sage: x.operation(operation=1)
x + 1

However, these operators have no structure by themselves: SageMath is not able to distinguish the type of the operators if they are defined using lambda expressions or callables. This can be seen by the fact that the factory can not detect the equality on two identical rings:

sage: dQx is RingWithOperators(QQ[x], lambda p : p.derivative())
False

To avoid this behavior, we can set the types by providing an optional list called types whose elements are strings with values:

  • homomorphism: the operator is interpret as a homomorphism/shift/difference operator.

  • derivation: the operator is considered as a derivation.

  • skew: the operator is considered as a skew-derivation.

  • none: the operator will only be considered as an additive Map without further structure.

We can see that, when setting this value, the ring is detected to be equal:

sage: dQx = RingWithOperators(QQ[x], lambda p : p.derivative(), types=["derivation"])
sage: dQx is RingWithOperators(QQ[x], lambda p : p.derivative(), types=["derivation"])
True
sage: # Since we have one variable, the built-in `diff` also work
sage: dQx is RingWithOperators(QQ[x], diff, types=["derivation"])
True
sage: # We can also use elements in the derivation module
sage: dQx is RingWithOperators(QQ[x], QQ[x].derivation_module().gens()[0], types=["derivation"])
True

Also, we can detect this equality when adding operators sequentially instead of at once:

sage: dsQx = RingWithOperators(QQ[x], 
....:     lambda p : p.derivative(), 
....:     lambda p : QQ[x](p)(x=QQ[x].gens()[0] + 1), 
....:     types = ["derivation", "homomorphism"]
....: )
sage: dsQx is RingWithOperators(dQx, lambda p : QQ[x](p)(x=QQ[x].gens()[0] + 1), types=["homomorphism"])
True

For specific types of operators as derivations or homomorphism, there are other functions where the types argument can be skipped taking the corresponding value by default:

sage: dQx is DifferentialRing(QQ[x], lambda p : p.derivative())
True
sage: dsQx is DifferenceRing(DifferentialRing(QQ[x], lambda p : p.derivative()), lambda p : QQ[x](p)(x=QQ[x].gens()[0] + 1))
True

We can also have more complexes structures with different types of operators:

sage: R.<x,y> = QQ[] # x is the usual variable, y is an exponential
sage: dx, dy = R.derivation_module().gens(); d = dx + y*dy
sage: DR = DifferentialRing(R, d)
sage: # We add a special homomorphism where the two generators are squared but QQ is fixed
sage: DSR = DifferenceRing(DR, R.Hom(R)([x^2, y^2]))
sage: DSR.noperators()
2
sage: DSR.operator_types()
('derivation', 'homomorphism')

We can see that these operator do not commute:

sage: x = DSR(x); y = DSR(y)
sage: x.difference().derivative()
2*x
sage: x.derivative().difference()
1
sage: y.difference().derivative()
2*y^2
sage: y.derivative().difference()
y^2

Finally, this module also allows the definition of skew-derivations for any ring. This requires the use of derivation modules with twist (see sage.rings.derivations):

sage: R.<x,y> = QQ[]
sage: s = R.Hom(R)([x-y, x+y])
sage: td = R.derivation_module(twist=s)(x-y)
sage: tR = RingWithOperators(R, s, td, types=["homomorphism", "skew"])
sage: x,y = tR.gens()
sage: (x*y).skew() == x.skew()*y + x.shift()*y.skew()
True
sage: (x*y).skew() == x.skew()*y.shift() + x*y.skew()
True

AUTHORS:

  • Antonio Jimenez-Pastor (GitHub)

class dalgebra.ring_w_operator.RingsWithOperators(s=None)

Bases: sage.categories.category.Category

Category for representing rings with operators.

Let \(\sigma: R \rightarrow R\) be an additive homomorphism, i.e., for all elements \(r,s \in R\), the map satisfies \(\sigma(r+s) = \sigma(r) + \sigma(s)\). We define the ring \(R\) with operator \(\sigma\) as the pair \((R, \sigma)\).

Similarly, if we have a set of additive maps \(\sigma_1,\ldots,\sigma_n : R \rightarrow R\). Then we define the ring \(R\) with operators \((\sigma_1,\ldots,\sigma_n)\) as the tuple \((R, (\sigma_1,\ldots,\sigma_n))\).

This category defines the basic methods for these rings and their elements

class ElementMethods

Bases: object

derivative(derivation=None, times=1)

Apply a derivation to self a given amount of times.

This method applies repeatedly a derivation defined in the parent of self. See derivative() for further information.

difference(difference=None, times=1)

Apply a difference to self a given amount of times.

This method applies repeatedly a difference defined in the parent of self. See difference() for further information.

is_constant(operation=0)

Method to check whether an element is a constant with respect to one operator.

INPUT:

  • operation: index defining the operation we want to check.

OUTPUT:

A boolean value with True is the element is a constant (see constant_ring() for further information on what is a constant depending on the type of operator).

REMARK: this method do not require the implementation on constant_ring() on its parent structure.

EXAMPLES:

sage: from dalgebra import *
sage: R = DifferentialRing(QQ[x], diff)
sage: p = R(3)
sage: p.is_constant()
True
sage: p = R(x^3 - 3*x + 1)
sage: p.is_constant()
False

Some interesting constants may arise unexpectedly when adding other derivations:

sage: R.<x,y> = QQ[]
sage: dx, dy = R.derivation_module().gens(); d = y*dx - x*dy
sage: dR = DifferentialRing(R, d)
sage: x,y = dR.gens()
sage: x.is_constant()
False
sage: y.is_constant()
False
sage: (x^2 + y^2).is_constant()
True
operation(operation=None, times=1)

Apply an operation to self a given amount of times.

This method applies repeatedly an operation defined in the parent of self. See operation() for further information.

shift(shift=None, times=1)

Alias for difference().

skew(skew=None, times=1)

Apply a skew-derivation to self a given amount of times.

This method applies repeatedly a difference defined in the parent of self. See skew() for further information.

class MorphismMethods

Bases: object

class ParentMethods

Bases: object

all_operators_commute(points=10, *args, **kwds)

Method to check whether all operators of the ring commute.

This method is not deterministic (meaning that it may return True even when the two operators do not fully commute) but it tries to check in a fix number of random elements if the two operators actually commute.

It also try to see if the operators commute in the generators of the ring.

See operators_commute() for further information

INPUT:

  • points: number of random points to be selected.

  • args: arguments to be passed to the random_element method.

  • kwds: arguments to be passed to the random_element method.

OUTPUT:

True if all the tests indicates the operators commute, False otherwise.

EXAMPLES:

sage: from dalgebra import *
sage: R.<x> = QQ[]; d = diff; s = R.Hom(R)(x+1)
sage: dsR = DifferenceRing(DifferentialRing(R, d), s)
sage: dsR.all_operators_commute()
True
sage: R.<x,y> = QQ[]
sage: dx,dy = R.derivation_module().gens(); d = dx + y*dy
sage: s = R.Hom(R)([x + 1, y^2])
sage: dsR = DifferenceRing(DifferentialRing(R, d), s)
sage: dsR.all_operators_commute()
False
constant_ring()

Method to obtain the constant ring of a given operation.

The meaning of a ring of constants depends on the type of operator that we are considering:

  • “homomorphism”: the elements that are fixed by the operator.

  • “derivation”: the elements that goes to zero with the operator.

  • “skew”: the elements that goes to zero with the operator.

  • “none”: it makes no sense to talk about constant for these operators.

derivations()

Method to filter the derivations out of a ring with operators.

Derivations are a particular type of operators. With this method we provide a similar interface as with the generic operators but just with derivation.

Similarly, this class offers access to homomorphisms and skew derivations.

When no derivation is declared for a ring, an empty tuple is returned.

derivative(element, derivation=None)

Method to apply a derivation over an element.

This method applies a derivation over a given element in the same way an operator is applied by the method operation().

difference(element, difference=None)

Method to apply a difference over an element.

This method applies a difference over a given element in the same way an operator is applied by the method operation().

differences()

Method to filter the differences out of a ring with operators.

Differences are a particular type of operators. With this method we provide a similar interface as with the generic operators but just with difference.

Similarly, this class offers access to derivations and skew derivations.

When no difference is declared for a ring, an empty tuple is returned.

has_derivations()

Method to know if there are derivations defined over the ring.

has_differences()

Method to know if there are differences defined over the ring.

has_skews()

Method to know if there are skew-derivations defined over the ring.

is_difference()

Method to check whether a ring is difference, i.e, all operators are homomorphisms.

is_differential()

Method to check whether a ring is differential, i.e, all operators are derivations.

is_skew()

Method to check whether a ring is skewed, i.e, all operators are skew-derivations.

nderivations()

Method to get the number of derivations defined over a ring

ndifferences()

Method to get the number of differences defined over a ring

noperators()

Method to get the number of operators defined over a ring

nskews()

Method to get the number of skew-derivations defined over a ring

operation(element, operator=None)

Method to apply an operator over an element.

This method takes an element of self and applies one of the operators defined over self over such element. This operator is given by its index, hence raising a IndexError if the index is not in the valid range.

INPUT:

  • element: an element over the operator of this ring will be applied.

  • operator (\(0\) by default) the index of the operator that will be applied.

OUTPUT:

If the index is incorrect, an IndexError is raised. Otherwise this method returns \(f(x)\) where \(x\) is the element and \(f\) is the operator defined by operator.

EXAMPLES:

sage: from dalgebra import *
sage: dQx = RingWithOperators(QQ[x], lambda p : p.derivative())
sage: sQx = RingWithOperators(QQ[x], lambda p : p(x=QQ[x].gens()[0] + 1))
sage: sdQx = RingWithOperators(QQ[x], lambda p : p(x=QQ[x].gens()[0] + 1), lambda p : p.derivative())
sage: p = QQ[x](x^3 - 3*x^2 + 3*x - 1)
sage: dQx.operation(p)
3*x^2 - 6*x + 3
sage: sQx.operation(p)
x^3
sage: sdQx.operation(p)
Traceback (most recent call last):
...
IndexError: An index for the operation must be provided when having several operations
sage: sdQx.operation(p, 0)
x^3
sage: sdQx.operation(p, 1)
3*x^2 - 6*x + 3
sage: sdQx.operation(p, 2)
Traceback (most recent call last):
...
IndexError: ... index out of range
operator_ring()

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 method return this new structure.

operator_types()

Method to get the types of the operators.

The only condition for \(\sigma: R \rightarrow R\) to be a valid operator is that it is an additive homomorphism. However, the behavior of \(\sigma\) with respect to the multiplication of \(R\) categorize \(\sigma\) into several possibilities:

  • “none”: no condition is known over this method. This will disallow some extension operations.

  • “homomorphism”: the map \(\sigma\) is an homomorphism, i.e., for all \(r, s \in R\) it satisfies \(\sigma(rs) = \sigma(r)\sigma(s)\).

  • “derivative”: the map \(\sigma\) satisfies Leibniz rule, i.e., for all \(r, s \in R\) it satisfies \(\sigma(rs) = \sigma(r)s + r\sigma(s)\).

  • “skew”: the map \(\sigma\) satisfies the skew-Leibniz rule, i.e., there is an homomorphism \(\delta\) such for all \(r, s \in R\) it satisfies \(\sigma(rs) = \sigma(r)s + \delta(r)\sigma(s)\).

This method returns a tuple (sorted as the output of operators()) with the types of each of the operators.

operators()

Method to get the collection of operators that are defined over the ring.

These operators are maps from self to self that compute the application of each operator over the elements of self.

operators_commute(op1, op2, points=10, *args, **kwds)

Method to check whether two operators of the ring commute.

This method is not deterministic (meaning that it may return True even when the two operators do not fully commute) but it tries to check in a fix number of random elements if the two operators actually commute.

It also try to see if the operators commute in the generators of the ring.

INPUT:

  • op1: index of the first operator to check.

  • op2: index of the second operator to check.

  • points: number of random points to be selected.

  • args: arguments to be passed to the random_element method.

  • kwds: arguments to be passed to the random_element method.

OUTPUT:

True if all the tests indicates the operators commute, False otherwise.

shift(element, shift=None)

Alias for difference().

skew(element, skew=None)

Method to apply a skew-derivation over an element.

This method applies a skew-derivation over a given element in the same way an operator is applied by the method operation().

skews()

Method to filter the skew-derivations out of a ring with operators.

Differences are a particular type of operators. With this method we provide a similar interface as with the generic operators but just with difference.

Similarly, this class offers access to homomorphisms and derivations.

When no skew-derivation is declared for a ring, an empty tuple is returned.

super_categories()
dalgebra.ring_w_operator.DifferentialRing(base, *operators)

Method that calls the RingWithOperatorFactory with types always as “derivation”.

See documentation on RingWithOperatorFactory for further information.

dalgebra.ring_w_operator.DifferenceRing(base, *operators)

Method that calls the RingWithOperatorFactory with types always as “homomorphism”.

See documentation on RingWithOperatorFactory for further information.