Ask Your Question

Revision history [back]

Given a polynomial ring R, one wants to be able to define elements in its fraction field Q.

Here, this is achieved by creating a class fastfrac where an element in the fraction field is stored as an object f in this class, with a numerator stored as f.top and a denominator stored as f.bot (the numerator and denominator are the "top" and "bottom" of the fraction).

The __init__ method of this class fastfrac is built such that:

  • calling fastfrac(a, b), with a and b in R, will return a fraction representing a/b, so it will have top equal to a and bot equal to b;

  • this should also work if a is already in Q; in that case, imagining that a = c/d with c, d in R, the fraction f equal to a/b should be equal to (c/d)/b, that is, c/(d*b);

    the implementation is split into two subcases:

    • if a is in the class fastfrac, this means the top of a/b should be a.top and its bot should be a.bot * b;

    • otherwise (maybe a belongs to some other implementation of the fraction field), we access the top and bottom of a by numerator(a) and denominator(b) and then make sure to transform them into elements of R by doing R(numerator(a)) and R(denominator(a)); so f has top equal to R(numerator(a)) and bottom equal to R(denominator(a)) * b;

  • finally, one also wants to be able to call fastfrac(a) instead of fastfrac(a, 1); so if fastfrac is called with just one argument (playing the role of a), an extra argument equal to 1 (playing the role of b) is inserted.

The if/elif/else clause in the __init__ method of the class fastfrac corresponds to the first list item and the two subitems of the second list item from the explanation.

The first part (the "if") corresponds to a in ZZ or R, where ZZ is for the case of fractions of type 1/b. The "elif" is for a already in the class fastfrac; the "else" is for a already in the fraction field but not in the class fastfrac.

Compared to this explanation, a and b are called top and bot in the code, so we have top.top and top.bot instead of a.top and a.bot, and we have bot instead of b.

The presence of the optional argument bot with a default value of 1 takes care of the last item in the wishlist.


Let us illustrate using polynomials in one variable, for simplicity.

Define

sage: R.<x> = PolynomialRing(QQ)

and

sage: class fastfrac:
....:
....:   def __init__(self, top, bot=1):
....:
....:     if parent(top) == ZZ or parent(top) == R:
....:       self.top = R(top)
....:       self.bot = R(bot)
....:     elif top.__class__ == fastfrac:
....:       self.top = top.top
....:       self.bot = top.bot * bot
....:     else:
....:       self.top = R(numerator(top))
....:       self.bot = R(denominator(top)) * bot
....:

Then with

sage: a = x^2 + 1
sage: b = x^3 - 2

one can define their quotient as

sage: f = fastfrac(a, b)

and recover the "top" and "bottom" of the fraction:

sage: f.top
x^2 + 1
sage: f.bot
x^3 - 2

By contrast we might have tried to produce the quotient by dividing a by b:

sage: g = a/b

and then we can't get the "top" and "bottom" in the same way:

sage: g.top
Traceback (most recent call last)
...
AttributeError: 'FractionFieldElement_1poly_field' object has no attribute 'top'

This is because the way it was defined, g belongs to Sage's implementation of the fraction field of R, and not to the class fastfrac:

sage: g.parent()
Fraction Field of Univariate Polynomial Ring in x over Rational Field

But nothing to worry about, we can convert g to the class fastfrac easily:

sage: h = fastfrac(g)

and then we get, as expected:

sage: h.top
x^2 + 1
sage: h.bot
x^3 - 2