{ "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 }