RingElem

© 2005-2010 John Abbott
GNU Free Documentation License, Version 1.2



CoCoALib Documentation Index

Examples

User documentation

The file ring.H introduces several classes used for representing rings and their elements. A normal user of the CoCoA library will use principally the classes ring and RingElem: an object of type ring represents a mathematical ring with unity, and objects of type RingElem represent values from some ring. To make the documentation more manageable it has been split into two: this file describes operations on a RingElem, whereas a separate file describes the operations directly applicable to rings. Documentation about the creation and use of homomorphisms between rings can be found in RingHom.

A variable of type RingElem comprises two parts: the ring to which the value belongs, and the value itself. An important point to note is that the ring cannot be changed, but the value part can (if C++ const rules permit). This means that you must specify the ring to which the RingElem belongs whenever you create one.

Here are a some examples of valid ways of creating a RingElem (see below for a full list of constructors).

VALID CODE Comment
RingElem a(R); value is 0
RingElem a(R,3); value is 3
RingElem a(R, BigRat(3,7)); value is 3/7 (or error)
RingElem a = one(RingZZ()); value is 1 in RingZZ()
RingElem a = b; a copy of b: same value & same ring

Here are a some examples of invalid ways of creating a RingElem; note in particular that integer and rational values are not automatically mapped into the obvious rings, instead the ring to map into must be given explicitly.

INVALID CODE Comment
RingElem a = 2; owning ring is not specified
RingElem a(2); owning ring is not specified
RingElem a = BigRat(3,7); owning ring is not specified

Operations on RingElems

RingElems are designed to be easy and safe to use; the various checks do incur a certain run-time overhead, so a faster alternative is offered (see below in the section Fast and Ugly Code). Arithmetic operations between RingElems will fail if they do not belong to the same ring (the exception has code ERR::MixedRings).

You can get the zero and one of a ring directly using the following:

zero(R) the zero element of R
one(R) the one element of R

Constructors of RingElems

Let R be a ring, n an integer q a BigRat (rational)

RingElem r(R); an element of R, initially zero
RingElem r(R, n); an element of R, initially the image of n
RingElem r(R, q); an element of R, initially the image of q (or error)
RingElem r(R, s); an element of R, initially the value of symbol s
RingElem r(r2); a copy of r2, element of the same ring
RingElem r = r2; (alternative syntax, discouraged)

Construction from a rational may fail if the denominator is a zero divisor in the ring; if it does fail then an exception is thrown (with code ERR::DivByZero).

Note: there is no constructor RingElem(R, r2), if you want to map a RingElem from one ring to another then you must apply a homomorphism (see RingHom).

Queries

CoCoALib offers functions for querying various properties of RingElems, and about relationships between RingElems.

Let r1 and r2 be a (possibly const) RingElems, and let N be a variable of type BigInt, and q a variable of type BigRat

owner(r1) the ring to which r1 is associated
IsZero(r1) true iff r1 is zero
IsOne(r1) true iff r1 is one
IsMinusOne(r1) true iff -r1 is one
IsInteger(N, r1) true iff r1 is the image of an integer (if true, a preimage is placed in N)
IsRational(q, r1) true iff r1 is the image of a rational (if true, a preimage is placed in q)
IsDouble(d, r1) true iff r1 is the image of a rational whose approx is put into d (false if overflow)
IsUnit(r1) true iff r1 has a multiplicative inverse
IsDivisible(r1, r2) true iff r1 is divisible by r2
raw(r1) (see below, section "Fast and Ugly Code")
R->myIsPrintAtom(r1) true iff r1 does not need brackets when a num or denom of a fraction
R->myIsPrintedWithMinus(r1) true iff the printed form of r1 begins with a minus sign

Note that IsDivisible tests divisibility in the ring containing the values: so 1 is not divisible by 2 in RingZZ, but their images in RingQQ are divisible.

Arithmetic

Arithmetic operations between RingElems will fail if they do not belong to the same ring (the exception has code ERR::MixedRings). You may perform arithmetic between a RingElem and a machine integer, a BigInt value or a BigRat value -- the integer/rational is automatically mapped into the same ring as the RingElem.

Let r be a non-const RingElem, and r1, r2 be potentially const RingElems. Assume they are all associated to the same ring. Then the operations available are: (meanings are obvious)

Ordering

If the ring is an ordered domain then these functions may also be used. Comparison operations between RingElems will fail if they do not belong to the same ring (the exception has code ERR::MixedRings). You may perform comparisons between a RingElem and a machine integer, a BigInt value or a BigRat value -- the integer/rational is automatically mapped into the same ring as the RingElem.

Let r1 and r2 belong to an ordered ring. Trying to use any of these functions on elements belonging to a ring which is not ordered will produce an exception with code ERR::NotOrdDomain.

Operations on RingElems of a PolyRing

In addition to the standard ring operations, elements of a PolyRing may used in other functions.

Let P denote a polynomial ring. Let f denote a non-const element of P. Let f1, f2 denote const elements of P.

NOTE: to compute the weighted degree of a polynomial use the function wdeg defined for RingElem of a SparsePolyRing (see below).

Operations on RingElems of a SparsePolyRing

In addition to the standard ring and PolyRing operations, elements of a SparsePolyRing may used in other functions. Let P denote a SparsePolyRing. Let f denote a non-const element of P. Let f1, f2 denote const elements of P. Let expv be a vector<long> of size equal to the number of indeterminates.

NB For running through the summands (monomial/terms) of a polynomial use SparsePolyIters (see SparsePolyRing).

We have still doubts on the usefullness of these two functions:

Use the following two functions with great care: they throw an error if the PPOrdering is not respected: (the coefficient c may be 0)

The corresponding member functions myPushFront/myPushBack will not check the validity of these assumpions: they should have a CoCoA_ASSERT to check in DEBUG mode.

Notes on operations

Operations combining elements of different rings will cause a run-time error.

In all functions involving two RingElems either r1 or r2 may be replaced by a machine integer, or by a big integer (an element of the class BigInt). The integer value is automatically mapped into the ring owning the RingElem in the same expression.

The exponent n in the power function may be zero or negative, but a run-time error will be signalled if one attempts to compute a negative power of a non-invertible element or if one attempts to raise zero to the power zero. NB You cannot use ^ to compute powers -- see Bugs section.

An attempt to perform an inexact division or to compute a GCD not in a GCD domain will produce a run-time error.

The printing of ring elements is always in decimal regardless of the ostream settings (this is supposed to be a feature rather than a bug).

At this point, if you are new to CoCoALib, you should probably look at some of the example programs in the examples/ directory.

Writing functions with RingElems as arguments

One would normally expect to use the type const RingElem& for read-only arguments which are RingElems, and RingElem& for read-write arguments. Unfortunately, doing so would lead to problems with the CoCoA library. INSTEAD you should use the types:

ConstRefRingElem x for read-only arguments: morally const RingElem& x
RefRingElem x for read-write arguments: morally RingElem& x

If you are curious to know why this non-standard quirk has to be used, read on.

Internally, ring element values are really smart pointers to the true value. Now the const keyword in C++ when applied to a pointer makes the pointer const while the pointed-to value remains alterable -- this is not the behaviour we want for const RingElem&. To get the desired behaviour we have to use another type: the type we have called ConstRefRingElem.

You might wonder why Ref appears in the names RefRingElem and ConstRefRingElem. It indicates that you are working with a reference to a value which is owned by another object (e.g. a variable of type RingElem, or maybe a matrix).

ADVANCED USE OF RingElem

The rest of this section is for more advanced use of rings and RingElems (e.g. by CoCoA library contributors). If you are new to CoCoA, you need not read beyond here.

Fast and Ugly Code

WE DO NOT RECOMMEND that you use what is described in this section. If you are curious to know a bit more how rings are implemented, you might find this section informative.

RingElems are designed to be easy and pleasant to use, but this convenience has a price: a run-time performance penalty (and a memory space penalty too). Both penalities may be avoided by using raw values but at a considerable loss of programming convenience and safety. You should consider using raw values only if you are desperate for speed; even so, performance gains may be only marginal except perhaps for operations on elements of a simple ring (e.g. a small finite field).

A RingElem object contains within itself an indication of the owning ring, and a raw value which is a pointer to where the real representation of the ring element value lies. These raw values may be accessed via the raw function. They may be combined arithmetically by calling member functions of the owning ring. For instance, if x,y,z are all RingElem objects all BELONGING TO EXACTLY THE SAME RING then we can achieve

      x = y+z;

slightly faster by calling

      owner(x)->my.Add(raw(x), raw(y), raw(z));

It should now be clear that the syntax involved is cumbersome and somewhat obscure. For the future maintainability of the code the simpler x = y+z; has many advantages. Furthermore, should x,y,z somehow happen not all to lie in the same ring then x = y+z; will act in a reasonable way, whereas the supposedly faster call will likely lead to many hours of debugging grief. The member functions for arithmetic (e.g. myAdd) DO NOT PERFORM sanity checks on their arguments: e.g. attempting to divide by zero could well crash the program.

If you use a debugging version of the CoCoA Library then some member functions do use assertions to check their arguments. This is useful during development, but must not be relied upon since the checks are absent from the non-debugging version of the CoCoA Library. See the file config.txt for more information.

This fast, ugly, unsafe way of programming is made available for those who desperately need the speed. If you're not desperate, don't use it!

Fast, Ugly and Unsafe operations on raw values

Read the section Fast and Ugly Code before using any of these!

Let r be a non-const raw value, and r1, r2 potentially const raw values. Assume they are all owned by the ring R. Then the functions available are:

Maintainer documentation

(NB consider consulting also QuotientRing, FractionField and PolyRing)

The design underlying rings and their elements is more complex than I would have liked, but it is not as complex as the source code may make it appear. The guiding principles are that the implementation should be flexible and easy/pleasant to use while offering a good degree of safety; extreme speed of execution was not a goal (as it is usually contrary to good flexibility) though an interface offering slightly better run-time efficiency remains.

Regarding flexibility: in CoCoALib we want to handle polynomials whose coefficients reside in (almost) any commutative ring. Furthermore, the actual rings to be used will be decided at run-time, and cannot restricted to a given finite set. We have chosen to use C++ inheritance to achieve the implementation: the abstract class RingBase defines the interface that every concrete ring class must offer.

Regarding ease of use: since C++ allows the common arithmetic operators to be overloaded, it is essential that these work as expected for elements of arbitrary rings -- with the caveat that / means exact division, being the only reasonable interpretation. Due to problems of ambiguity arithmetic between elements of different rings is forbidden: e.g. let f in Q[x,y] and g in Z[y,x], where should f+g reside?

The classes in the file ring.H are closely interrelated, and there is no obvious starting point for describing them -- you may find that you need to read the following more than once to comprehend it. Here is a list of the classes:

ring value represents a ring; it is a smart pointer
RingBase abstract class defining what a ring is
RingElem value represents an element of a ring
RefRingElem reference to a RingElem
ConstRefRingElem const-reference to a RingElem
RingElemConstRawPtr raw pointer to a const ring value
RingElemRawPtr raw pointer to a ring value

For the first two see ring.

The classes RingElem, RefRingElem and ConstRefRingElem are related by inheritance: they are very similar but differ in important ways. The three classes are used for representing values in rings (e.g 1 as an element Z, or 1 as an element of Q[x], etc). In each case an object of that C++ type comprises two components: one is the identity of ring to which the element belongs, and the other is the value in that ring (the value is stored in a format that only the owning ring can comprehend). All operations on ring elements are effected by member functions of the ring to which the value belongs.

The difference between ConstRefRingElem and RefRingElem is quite simple: you cannot change the value of a ConstRefRingElem, for instance you cannot assign to it, while you are free to change the value of a RefRingElem.

The difference between a RefRingElem and a RingElem is also quite simple, but possibly harder to grasp. A variable of type RingElem is the owner of the value that it represents: that value will be destroyed when the variable passes out of scope. In contrast, a variable of type RefRingElem is not the owner; it merely refers to value belonging to some other structure (e.g. a RingElem, or a matrix, or a polynomial). So you can create a RingElem from nothing, whereas you must already have a ring element to be able to create a RefRingElem which refers to it.

Why bother to distinguish between RingElem and RefRingElem? The main reason was to allow matrices and iterators of polynomials to be implemented cleanly and efficiently. Clearly a matrix should be the owner of the values appearing as its entries, but we also want a way of reading the matrix entries without having to copy them. Furthermore, the matrix can use a compact representation: the ring to which its elements belong is stored just once, and not once for each element.

The reason that ConstRefRingElem and RefRingElem are distinct classes is that neither const RefRingElem& nor const RingElem& achieves what one might reasonably expect. Since a RingElem is effectively a pointer to the value represented, applying a C++ const keyword merely makes the pointer const while leaving the pointed-to value modifiable. Consider the following procedure

  void CannotChange(const RefRingElem& x)
  {
    RefRingElem writable(x); // writable reference to value of x
    writable = writable+1;
  }

The above procedure will add one to the value of its argument even though it would seem that it should not be alter the value.

The inheritance structure between ConstRefRingElem, RefRingElem and RingElem implements the similarities and differences between these classes while also allowing ConstRefRingElem and RefRingElem to be used as types of parameters to functions and procedures. Given that matrix entry accessors return a ConstRefRingElem it is important not to use RingElem& or const RingElem& as the parameter type because compilation would fail if a matrix entry were passed as parameter.

As already hinted above, the internal data layouts for objects of types RingElem, RefRingElem and ConstRefRingElem are identical -- this is guaranteed by the C++ inheritance mechanism. The subfield indicating the ring to which the value belongs is simply a const ring, which is just a reference counting smart pointer. The subfield indicating the value is a raw pointer of type ``void* const``; however, when the raw pointer value is to be handled outside a ring element object then it is wrapped up as a RingElemRawPtr or RingElemConstRawPtr -- these are simply wrapped copies of the void*. Make a careful note of the exact type of the data member myValuePtr: the pointer is constant while the pointed to value is not constant. The constness of the pointer is ABSOLUTELY CRUCIAL to the correct behaviour of RefRingElem. The fact that the pointed-to value is not const may seem contradictory (for an object of type ConstRefRingElem), but it allows slightly easier implementation of the non-constant derived classes RefRingElem and RingElem; the friend raw function puts in the necessary constness when it is called.

The classes RingElemRawPtr and RingElemConstRawPtr are used for two reasons. One is that if a naked void* were used outside the ring element objects then C++ would find the call ``RingElem(R, 0)`` ambiguous because the constant 0 can be interpreted either as an integer constant or as a null pointer: there are two constructors which match the call equally well. The other reason is that it discourages accidentally creating a ring element object from any old pointer; it makes the programmer think -- plus I feel uneasy when there are naked void* pointers around. Note that the type of the data member myPtr is simply void* as opposed to ``void const*`` which one might reasonably expect. I implemented it this way as it is simpler to add in the missing constness in the member function RingElemConstRawPtr::myRawPtr than it would be to cast it away in the myRawPtr function of RingElemRawPtr.

In ConstRefRingElem why did I chose to make the data member myValuePtr of type void* const rather than ``RingElemRawPtr const``?

Further comments about implementation aspects of the above classes.

The class RingBase declares a number of pure virtual functions for computing with ring elements. Since these functions are pure they must all be fully defined in any instantiable ring class (e.g. RingZZImpl or RingFpImpl). These member functions follow certain conventions:

RETURN VALUES:
most arithmetic functions return no value, instead the result is placed in one of the arguments (normally the first argument is the one in which the result is placed), but functions which return particularly simple values (e.g. booleans or machine integers) do indeed return the values by the usual function return mechanism.

ARG TYPES:
ring element values are passed as raw pointers (i.e. a wrapped void* pointing to the actual value). A read-only arg is of type RingElemConstRawPtr, while a writable arg is of type RingElemRawPtr. When there are writable args they normally appear first. For brevity there are typedefs ConstRawPtr and RawPtr in the scope of RingBase or any derived class.

ARG CHECKS:
sanity checks on the arguments are NOT CONDUCTED (e.g. the division function assumes the divisor is non-zero). These member functions are supposed to be fast rather than safe.

In a few cases there are non-pure virtual member functions in RingBase. They exist either because there is a simple universal definition or merely to avoid having to define inappropriate member functions (e.g. gcd functions when the ring cannot be a gcd domain). Here is a list of them:

myIsUnit(x) by default checks that 1 is divisible by x
myGcd(lhs, x, y) gives an error: either NotGcdDom or NYI
myLcm(lhs, x, y) gives an error: either NotGcdDom or NYI
myGcdQuot(lhs, xquot, yquot, x, y) gives an error: either NotGcdDom or NYI
myExgcd(lhs, xcofac, ycofac, x, y) gives an error: either NotGcdDom or NYI
myIsPrintAtom(x) defaults to false
myIsPrintedWithMinus(x) gives a SERIOUS error
myIsMinusOne(x) defaults to myIsOne(-x); calculates -x
myIsZeroAddMul(lhs, y, z) computes lhs += y*z in the obvious way, and calls myIsZero
myCmp(x, y) gives NotOrdDom error
mySign(x) simply calls myCmp(x, 0), then returns -1,0,1 accordingly

There are three non-virtual member functions for calculating powers: one uses the sequential method, the other two implement the repeated squaring method (one is an entry point, the other an implementation detail). These are non-virtual since they do not need to be redefined; they are universal for all rings.

For the moment I shall assume that the intended meaning of the pure virtual functions is obvious (given the comments in the source code).

Recall that arithmetic operations on objects of type RingElem, RefRingElem and ConstRefRingElem are converted into member function calls of the corresponding owning ring. Here is the source code for addition of ring elements -- it typifies the implementation of operations on ring elements.

    RingElem operator+(ConstRefRingElem x, ConstRefRingElem y)
    {
      const ring& Rx = owner(x);
      const ring& Ry = owner(y);
      if (Rx != Ry)
        error(CoCoAError(ERR::MixedRings, "RingElem + RingElem"));
  
      RingElem ans(Rx);
      Rx->myAdd(raw(ans), raw(x), raw(y));
      return ans;
    }

The arguments are of type ConstRefRingElem since they are read-only, and the return type is RingElem since it is new self-owning value (it does not refer to a value belinging to some other structure]. Inside the function we check that the rings of the arguments are compatible, and report an error if they are not. Otherwise a temporary local variable is created for the answer, and the actual computation is effected via a member function call to the ring in which the values lie. Note the use of the raw function for accessing the raw pointer of a ring element. In summary, an operation on ring elements intended for public use should fully check its arguments for compatibility and correctness (e.g. to avoid division by zero); if all checks pass, the result is computed by passing raw pointers to the appropriate member functions of the ring involved -- this member function assumes that the values handed to it are compatible and valid; if not, undefined behaviour will result (i.e. a crash if you are lucky).

Most of the member functions of a ring are for manipulating raw values from that same ring, a few permit one to query properties of the ring. The type of a raw value is RingBase::RawValue, which helpfully abbreviates to RawValue inside the namespace of RingBase. Wherever possible the concrete implementations should be exception safe, i.e. they should offer either the strong exception guarantee or the no-throw guarantee (according to the definitions in Exceptional C++ by Sutter).

Bugs, Shortcomings and other ideas

I have chosen not to use operator^ for computing powers because of a significant risk of misunderstanding between programmer and compiler. The syntax/grammar of C++ cannot be changed, and operator^ binds less tightly than (binary) operator*, so any expression of the form a*b^c will be parsed as (a*b)^c; this is almost certainly not what the programmer intended. To avoid such problems of misunderstanding I have preferred not to define operator^; it seems too dangerous.

How to swap RefRingElems? Must be careful when trying to swap a RefRingElem with a RingElem to avoid possible orphans (memory leaks) or doubly owned values.

Note about comparison operators (<,<=,>,>=, and !=). The C++ STL does have templates which will define all the relational operators efficiently assuming the existence of operator< and operator==. These are defined in the namespace std::rel_ops in the standard header file <utility>. I have chosen NOT to use these because they can define only homogeneous comparisons; so the comparisons between ConstRefRingElem and int or BigInt would still have to be written out manually, and I prefer the symmetry of writing them all out. See p.69ff of Josuttis for details.

The function myAssignZero was NECESSARY because myAssign(x, 0) was ambiguous (ambiguated by the assignment from an mpz_t). It is no longer necessary, but I prefer to keep it (for the time being).

The requirement to use the type ConstRefRingElem for function arguments (which should normally be const RingElem& is not ideal, but it seems hard to find a better way. It is not nice to expect users to use a funny type for their function arguments. How else could I implement (noncopying) access to coefficients in a polynomial via an iterator, or access to matrix elements?

Would we want ++ and -- operators for RingElems???

Should (some of) the query functions return bool3 values? What about properties which are hard to determine?

How to generate random elements from a ring?

Note the slightly unusual return types for operator+= etc; it seemed daft to have a reference-to-reference. In contrast, I left the return type for RingElem::operator= as RingElem&; it felt very odd thinking of some other type RefRingElem as the return type.

The dtor for ConstRefRingElem is deliberately not virtual; idem for RefRingElem. This could potentially cause trouble if you convert a pointer to RingElem into a pointer to RefRingElem; but if you do that, you'd better be doubly sure about what you're doing anyway.

Anna thinks that NearestInteger could handle specially elements of RingZZ rather than doing the full wasteful computation. Not sure if the extra code and complication would really make a difference in practice.

gcd and lcm: there is no guarantee on sign/monic because may be costly to compute and generally useless.