{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In lecture 13 of MCS 320, we look at expression trees, symbolic and numeric evaluation."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 1. Expression Trees"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We will make a random polynomial with 3 terms, of degree 13, with rational coefficients, in the variables x, y, and z. To see always the same polynomial, we fix the seed of the random number generator."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
""
],
"text/latex": [
"$$\\newcommand{\\Bold}[1]{\\mathbf{#1}}\\frac{2}{9} x^{5} y^{6} z^{2} - \\frac{1}{2} x^{3} y^{6} z - \\frac{35}{2} x^{5} z^{4}$$"
],
"text/plain": [
"2/9*x^5*y^6*z^2 - 1/2*x^3*y^6*z - 35/2*x^5*z^4"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"P. = QQ[]\n",
"set_random_seed(2022)\n",
"p = P.random_element(terms=3, degree=13)\n",
"show(p)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To get the operator/operands view to work on ``p``, we must convert to a symbolic expression. While the shortest way is to cast ``p`` into the Symbolic Ring ``SR``, the alternative is to apply *symbolic evaluation*. We redeclare x, y, and z as variables and evaluate ``p`` symbolically in those new variables to obtain the expression ``q``."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2/9*x^5*y^6*z^2 - 1/2*x^3*y^6*z - 35/2*x^5*z^4 has type \n"
]
}
],
"source": [
"x, y, z = var('x, y, z')\n",
"q = p(x=x, y=y, z=z)\n",
"print(q, 'has type', type(q))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In the symbolic evaluation ``p(x=x, y=y, z=z)`` at the left of each ``=`` are the symbols used to define ``p``. At the right of each ``=`` are the newly declared symbols. Observe that ``q`` is now a symbolic expression."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
""
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"q.operator()"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[2/9*x^5*y^6*z^2, -1/2*x^3*y^6*z, -35/2*x^5*z^4]"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"q.operands()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The operator of ``q`` is an addition which takes as operands a list."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
""
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"q0 = q.operands()[0]\n",
"q0.operator()"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[x^5, y^6, z^2, 2/9]"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"q0.operands()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The first term of ``q`` has the multiplication operator with a list of four operands. "
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
""
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"q00 = q0.operands()[0]\n",
"q00.operator()"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[x, 5]"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"q00.operands()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The power of the first operand in the the first term of ``q`` is a binary operator, with two operands."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To draw the expression tree, we use a ``LabelledOrderedTree``. The labels for the children are the operands in the expression."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"On the WSL (Window Subsystem for Linux) running Ubuntu 22.04.2 LTS, the following statement is needed for the `ascii_art` to display properly."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"sage.typeset.ascii_art.AsciiArt._terminal_width = lambda x: 80"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
" ______________+________________\n",
" / / / \n",
"2/9*x^5*y^6*z^2 -1/2*x^3*y^6*z -35/2*x^5*z^4"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"oplabels = [str(op) for op in q.operands()]\n",
"operands = [LabelledOrderedTree([], label=op) for op in oplabels]\n",
"exptree = LabelledOrderedTree(operands, label='+')\n",
"ascii_art(exptree)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We now do the same to the leaves."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[[x^5, y^6, z^2, 2/9], [x^3, y^6, z, -1/2], [x^5, z^4, -35/2]]"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"oplabels = [op.operands() for op in q.operands()]\n",
"oplabels"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"in the definition of ``oplabels`` we applied the ``operands()`` to every operand of ``q``."
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
" _____*______\n",
" / / / / \n",
"x^5 y^6 z^2 2/9"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"term0leaves = [LabelledOrderedTree([], label=op) for op in oplabels[0]]\n",
"term0tree = LabelledOrderedTree(term0leaves, label='*')\n",
"ascii_art(term0tree)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We do the same operation for the other two terms."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
" ____*_____\n",
" / / / / \n",
"x^3 y^6 z -1/2"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"term1leaves = [LabelledOrderedTree([], label=op) for op in oplabels[1]]\n",
"term1tree = LabelledOrderedTree(term1leaves, label='*')\n",
"ascii_art(term1tree)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
" ___*____\n",
" / / / \n",
"x^5 z^4 -35/2"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"term2leaves = [LabelledOrderedTree([], label=op) for op in oplabels[2]]\n",
"term2tree = LabelledOrderedTree(term2leaves, label='*')\n",
"ascii_art(term2tree)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We still have to replace the powers of the variables by trees."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
" ^_\n",
" / /\n",
"x 5"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"leafx = LabelledOrderedTree([], label='x')\n",
"leaf5 = LabelledOrderedTree([], label='5')\n",
"nodex5 = LabelledOrderedTree([leafx, leaf5], label='^')\n",
"ascii_art(nodex5)"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
" ^_\n",
" / /\n",
"z 4"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"leafz = LabelledOrderedTree([], label='z')\n",
"leaf4 = LabelledOrderedTree([], label='4')\n",
"nodez4 = LabelledOrderedTree([leafz, leaf4], label='^')\n",
"ascii_art(nodez4)"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
" ___*____\n",
" / / / \n",
" ^_ ^_ -35/2\n",
" / / / /\n",
"x 5 z 4 "
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"newterm2tree = LabelledOrderedTree([nodex5, nodez4, term2leaves[2]], label='*')\n",
"ascii_art(newterm2tree)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can defined the other powers similarly."
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
" ^_\n",
" / /\n",
"z 2"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"leafy = LabelledOrderedTree([], label='y')\n",
"leaf2 = LabelledOrderedTree([], label='2')\n",
"leaf3 = LabelledOrderedTree([], label='3')\n",
"leaf4 = LabelledOrderedTree([], label='4')\n",
"leaf6 = LabelledOrderedTree([], label='6')\n",
"nodex3 = LabelledOrderedTree([leafx, leaf3], label='^')\n",
"ascii_art(nodex3)\n",
"nodey6 = LabelledOrderedTree([leafy, leaf6], label='^')\n",
"ascii_art(nodey6)\n",
"nodez2 = LabelledOrderedTree([leafz, leaf2], label='^')\n",
"ascii_art(nodez2)"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
" _____*______\n",
" / / / / \n",
"x^5 y^6 z^2 2/9"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"ascii_art(term0tree)"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
" _____*_______\n",
" / / / / \n",
" ^_ ^_ ^_ 2/9\n",
" / / / / / /\n",
"x 5 y 6 z 2 "
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"newterm0tree = LabelledOrderedTree([nodex5, nodey6, nodez2, term0leaves[3]], label='*')\n",
"ascii_art(newterm0tree)"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
" ____*_____\n",
" / / / / \n",
"x^3 y^6 z -1/2"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"ascii_art(term1tree)"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
" ____*_____\n",
" / / / / \n",
" ^_ ^_ z -1/2\n",
" / / / / \n",
"x 3 y 6 "
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"newterm1tree = LabelledOrderedTree([nodex3, nodey6, leafz, term1leaves[3]], label='*')\n",
"ascii_art(newterm1tree)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we can assemble the entire tree."
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
" _________________+__________________\n",
" / / / \n",
" _____*_______ ____*_____ ___*____\n",
" / / / / / / / / / / / \n",
" ^_ ^_ ^_ 2/9 ^_ ^_ z -1/2 ^_ ^_ -35/2\n",
" / / / / / / / / / / / / / /\n",
"x 5 y 6 z 2 x 3 y 6 x 5 z 4 "
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nodes = [newterm0tree, newterm1tree, newterm2tree]\n",
"exptree = LabelledOrderedTree(nodes, label='+')\n",
"ascii_art(exptree)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 2. Fast Callable Objects"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We encountered fast callable objects before, with the binary expression trees. In this section we demonstrate why they are *fast* compared to general expressions."
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"2/9*x^5*y^6*z^2 - 1/2*x^3*y^6*z - 35/2*x^5*z^4"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"q"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"-1385.50000000000"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"q(x=1.0, y=2.0, z=3.0)"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [],
"source": [
"f = fast_callable(q, vars=['x','y','z'])"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"-1385.50000000000"
]
},
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f(1.0, 2.0, 3.0)"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"625 loops, best of 3: 48.7 μs per loop"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"timeit('q(x=1.0, y=2.0, z=3.0)')"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"625 loops, best of 3: 5.86 μs per loop"
]
},
"execution_count": 29,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"timeit('f(1.0, 2.0, 3.0)')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Compare the difference in times. Even for such a small expression as ``q``, the fast callable is much faster. Observe however, that fast callables are defined for numerical, not for symbolic evaluation."
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[('load_arg', 0),\n",
" ('ipow', 5),\n",
" ('load_arg', 1),\n",
" ('ipow', 6),\n",
" 'mul',\n",
" ('load_arg', 2),\n",
" ('ipow', 2),\n",
" 'mul',\n",
" ('load_const', 2/9),\n",
" 'mul',\n",
" ('load_arg', 0),\n",
" ('ipow', 3),\n",
" ('load_arg', 1),\n",
" ('ipow', 6),\n",
" 'mul',\n",
" ('load_arg', 2),\n",
" 'mul',\n",
" ('load_const', -1/2),\n",
" 'mul',\n",
" 'add',\n",
" ('load_arg', 0),\n",
" ('ipow', 5),\n",
" ('load_arg', 2),\n",
" ('ipow', 4),\n",
" 'mul',\n",
" ('load_const', -35/2),\n",
" 'mul',\n",
" 'add',\n",
" 'return']"
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f.op_list()"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"add(add(add(0, mul(2/9, mul(mul(ipow(v_0, 5), ipow(v_1, 6)), ipow(v_2, 2)))), mul(-1/2, mul(mul(ipow(v_0, 3), ipow(v_1, 6)), ipow(v_2, 1)))), mul(-35/2, mul(ipow(v_0, 5), ipow(v_2, 4))))"
]
},
"execution_count": 31,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from sage.ext.fast_callable import ExpressionTreeBuilder\n",
"etb = ExpressionTreeBuilder(vars=['x','y','z'])\n",
"x = etb.var('x')\n",
"y = etb.var('y')\n",
"z = etb.var('z')\n",
"p(x=x, y=y, z=z)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Observe the binary structure."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "SageMath 10.3",
"language": "sage",
"name": "sagemath"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.6"
}
},
"nbformat": 4,
"nbformat_minor": 4
}