๐งฎ Canonical ordering, and opting outยถ
Bertini hash-conses the function tree: identical subexpressions become a single shared node.
For that sharing to fire on expressions you build separately, the library also canonically
orders the operands of every sum and product โ so x + y and y + x are not just equal,
they are the same node. This tutorial shows what that ordering does, how to choose it, and how
to turn it off when you want to keep the exact operand order you wrote.
On by defaultยถ
Commutative reorderings collapse, coefficients sort to the front of a monomial, and the constant term of a sum stays last โ so printed expressions read the conventional way.
x, y = Variable('x'), Variable('y')
assert str(x + y) == str(y + x) == 'x+y' # commutative: one canonical form (and one node)
assert str(x * y) == str(y * x) == 'x*y'
assert str(3 * x**2) == '3*x^2' # the coefficient leads its monomial
assert str(x**2 + 2*x - 1) == 'x^2+2*x-1' # degree-descending; the constant term is last
You can ask whether canonicalization is on, and toggle it, with bertini.canonicalize().
assert bertini.canonicalize() is True # on by default
Choosing the monomial orderยถ
The order is a pluggable monomial order โ Lex, RevLex, or GrevLex (the default,
graded so higher total degree leads). It is a session-global setting, because it determines which
expressions share a node.
bertini.monomial_order(MonomialOrder.Lex)
lex = str(x**2 + y**3) # lexicographic: x before y
bertini.monomial_order(MonomialOrder.GrevLex)
grev = str(x**2 + y**3) # graded: the higher-degree y^3 leads
assert lex == 'x^2+y^3'
assert grev == 'y^3+x^2'
bertini.monomial_order(MonomialOrder.GrevLex) # restore the default
Opting outยถ
Canonicalization is the right default โ it maximizes sharing and keeps the compiled straight-line program small. But sometimes you have arranged the operands of an expression deliberately, and you want the library to leave that arrangement alone. Turn canonicalization off while you build that expression, and the authored operand order is preserved:
bertini.canonicalize(False) # opt out
try:
authored = y + x
assert str(authored) == 'y+x' # exactly as written, not reordered to x+y
finally:
bertini.canonicalize(True) # back to the default for everything else
Why would you do this? Floating-point addition and multiplication are not associative: the order in which operands are combined changes the rounding. If you have written an expression so that a catastrophically-cancelling pair is combined first (or so that a product avoids an intermediate overflow), canonical reordering could undo that arrangement. Opting out hands you back exact control of the operand order for the numerically-sensitive part of a model, while everything you do not opt out of stays canonicalized โ and keeps sharing.
Note
The toggle is session-global, so the idiom is โopt out, build the sensitive expression, opt back inโ (as above). Everything built while it is off keeps its authored order and is a distinct node from the canonical form; everything built while it is on is canonicalized and shared.
Complete exampleยถ
The whole tutorial as one runnable script โ assemble nothing, just run it:
"""Canonical ordering, and opting out -- Bertini 2 tutorial.
Assembled from the ``.. testcode::`` blocks of the "Canonical ordering, and
opting out" tutorial into one runnable program.
Run: python canonical_ordering.py
"""
import bertini
from bertini import Variable, MonomialOrder
def on_by_default():
"""Commutative reorderings collapse and printed expressions read the conventional way."""
x, y = Variable('x'), Variable('y')
assert str(x + y) == str(y + x) == 'x+y' # commutative: one canonical form (and one node)
assert str(x * y) == str(y * x) == 'x*y'
assert str(3 * x**2) == '3*x^2' # the coefficient leads its monomial
assert str(x**2 + 2*x - 1) == 'x^2+2*x-1' # degree-descending; the constant term is last
assert bertini.canonicalize() is True # on by default
return x, y
def choosing_the_monomial_order(x, y):
"""The pluggable, session-global monomial order determines which expressions share a node."""
bertini.monomial_order(MonomialOrder.Lex)
lex = str(x**2 + y**3) # lexicographic: x before y
bertini.monomial_order(MonomialOrder.GrevLex)
grev = str(x**2 + y**3) # graded: the higher-degree y^3 leads
assert lex == 'x^2+y^3'
assert grev == 'y^3+x^2'
bertini.monomial_order(MonomialOrder.GrevLex) # restore the default
def opting_out(x, y):
"""Turn canonicalization off to preserve a deliberately authored operand order."""
bertini.canonicalize(False) # opt out
try:
authored = y + x
assert str(authored) == 'y+x' # exactly as written, not reordered to x+y
finally:
bertini.canonicalize(True) # back to the default for everything else
def main():
x, y = on_by_default()
choosing_the_monomial_order(x, y)
opting_out(x, y)
if __name__ == '__main__':
main()