{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#!pip install pyparsing" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# fourFn" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pyparsing import (\n", " Literal,\n", " Word,\n", " Group,\n", " Forward,\n", " alphas,\n", " alphanums,\n", " Regex,\n", " ParseException,\n", " CaselessKeyword,\n", " Suppress,\n", " delimitedList,\n", ")\n", "import math\n", "import operator" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "exprStack = []" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def push_first(toks):\n", " exprStack.append(toks[0])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def push_unary_minus(toks):\n", " for t in toks:\n", " if t == \"-\":\n", " exprStack.append(\"unary -\")\n", " else:\n", " break" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "bnf = None" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def BNF():\n", " \"\"\"\n", " expop :: '^'\n", " multop :: '*' | '/'\n", " addop :: '+' | '-'\n", " integer :: ['+' | '-'] '0'..'9'+\n", " atom :: PI | E | real | fn '(' expr ')' | '(' expr ')'\n", " factor :: atom [ expop factor ]*\n", " term :: factor [ multop factor ]*\n", " expr :: term [ addop term ]*\n", " \"\"\"\n", " global bnf\n", " if not bnf:\n", " # use CaselessKeyword for e and pi, to avoid accidentally matching\n", " # functions that start with 'e' or 'pi' (such as 'exp'); Keyword\n", " # and CaselessKeyword only match whole words\n", " e = CaselessKeyword(\"E\")\n", " pi = CaselessKeyword(\"PI\")\n", " # fnumber = Combine(Word(\"+-\"+nums, nums) +\n", " # Optional(\".\" + Optional(Word(nums))) +\n", " # Optional(e + Word(\"+-\"+nums, nums)))\n", " # or use provided pyparsing_common.number, but convert back to str:\n", " # fnumber = ppc.number().addParseAction(lambda t: str(t[0]))\n", " fnumber = Regex(r\"[+-]?\\d+(?:\\.\\d*)?(?:[eE][+-]?\\d+)?\")\n", " ident = Word(alphas, alphanums + \"_$\")\n", "\n", " plus, minus, mult, div = map(Literal, \"+-*/\")\n", " lpar, rpar = map(Suppress, \"()\")\n", " addop = plus | minus\n", " multop = mult | div\n", " expop = Literal(\"^\")\n", "\n", " expr = Forward()\n", " expr_list = delimitedList(Group(expr))\n", " # add parse action that replaces the function identifier with a (name, number of args) tuple\n", " def insert_fn_argcount_tuple(t):\n", " fn = t.pop(0)\n", " num_args = len(t[0])\n", " t.insert(0, (fn, num_args))\n", "\n", " fn_call = (ident + lpar - Group(expr_list) + rpar).setParseAction(\n", " insert_fn_argcount_tuple\n", " )\n", " atom = (\n", " addop[...]\n", " + (\n", " (fn_call | pi | e | fnumber | ident).setParseAction(push_first)\n", " | Group(lpar + expr + rpar)\n", " )\n", " ).setParseAction(push_unary_minus)\n", "\n", " # by defining exponentiation as \"atom [ ^ factor ]...\" instead of \"atom [ ^ atom ]...\", we get right-to-left\n", " # exponents, instead of left-to-right that is, 2^3^2 = 2^(3^2), not (2^3)^2.\n", " factor = Forward()\n", " factor <<= atom + (expop + factor).setParseAction(push_first)[...]\n", " term = factor + (multop + factor).setParseAction(push_first)[...]\n", " expr <<= term + (addop + term).setParseAction(push_first)[...]\n", " bnf = expr\n", " return bnf" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# map operator symbols to corresponding arithmetic operations\n", "epsilon = 1e-12\n", "opn = {\n", " \"+\": operator.add,\n", " \"-\": operator.sub,\n", " \"*\": operator.mul,\n", " \"/\": operator.truediv,\n", " \"^\": operator.pow,\n", "}\n", "\n", "fn = {\n", " \"sin\": math.sin,\n", " \"cos\": math.cos,\n", " \"tan\": math.tan,\n", " \"exp\": math.exp,\n", " \"abs\": abs,\n", " \"trunc\": int,\n", " \"round\": round,\n", " \"sgn\": lambda a: -1 if a < -epsilon else 1 if a > epsilon else 0,\n", " # functionsl with multiple arguments\n", " \"multiply\": lambda a, b: a * b,\n", " \"hypot\": math.hypot,\n", " # functions with a variable number of arguments\n", " \"all\": lambda *a: all(a),\n", " \"Circle\": lambda x,y,r: f\"center=({x},{y}),radius={r}\"\n", "}" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def evaluate_stack(s):\n", " op, num_args = s.pop(), 0\n", " if isinstance(op, tuple):\n", " op, num_args = op\n", " if op == \"unary -\":\n", " return -evaluate_stack(s)\n", " if op in \"+-*/^\":\n", " # note: operands are pushed onto the stack in reverse order\n", " op2 = evaluate_stack(s)\n", " op1 = evaluate_stack(s)\n", " return opn[op](op1, op2)\n", " elif op == \"PI\":\n", " return math.pi # 3.1415926535\n", " elif op == \"E\":\n", " return math.e # 2.718281828\n", " elif op in fn:\n", " # note: args are pushed onto the stack in reverse order\n", " args = reversed([evaluate_stack(s) for _ in range(num_args)])\n", " return fn[op](*args)\n", " elif op[0].isalpha():\n", " raise Exception(\"invalid identifier '%s'\" % op)\n", " else:\n", " # try to evaluate as int first, then as float if int fails\n", " try:\n", " return int(op)\n", " except ValueError:\n", " return float(op)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def test(s, expected):\n", " exprStack[:] = []\n", " try:\n", " results = BNF().parseString(s, parseAll=True)\n", " val = evaluate_stack(exprStack[:])\n", " except ParseException as pe:\n", " print(s, \"failed parse:\", str(pe))\n", " except Exception as e:\n", " print(s, \"failed eval:\", str(e), exprStack)\n", " else:\n", " if val == expected:\n", " print(s, \"=\", val, results, \"=>\", exprStack)\n", " else:\n", " print(s + \"!!!\", val, \"!=\", expected, results, \"=>\", exprStack)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test(\"9\", 9)\n", "test(\"-9\", -9)\n", "test(\"--9\", 9)\n", "test(\"-E\", -math.e)\n", "test(\"9 + 3 + 6\", 9 + 3 + 6)\n", "test(\"9 + 3 / 11\", 9 + 3.0 / 11)\n", "test(\"(9 + 3)\", (9 + 3))\n", "test(\"(9+3) / 11\", (9 + 3.0) / 11)\n", "test(\"9 - 12 - 6\", 9 - 12 - 6)\n", "test(\"9 - (12 - 6)\", 9 - (12 - 6))\n", "test(\"2*3.14159\", 2 * 3.14159)\n", "test(\"3.1415926535*3.1415926535 / 10\", 3.1415926535 * 3.1415926535 / 10)\n", "test(\"PI * PI / 10\", math.pi * math.pi / 10)\n", "test(\"PI*PI/10\", math.pi * math.pi / 10)\n", "test(\"PI^2\", math.pi ** 2)\n", "test(\"round(PI^2)\", round(math.pi ** 2))\n", "test(\"6.02E23 * 8.048\", 6.02e23 * 8.048)\n", "test(\"e / 3\", math.e / 3)\n", "test(\"sin(PI/2)\", math.sin(math.pi / 2))\n", "test(\"10+sin(PI/4)^2\", 10 + math.sin(math.pi / 4) ** 2)\n", "test(\"trunc(E)\", int(math.e))\n", "test(\"trunc(-E)\", int(-math.e))\n", "test(\"round(E)\", round(math.e))\n", "test(\"round(-E)\", round(-math.e))\n", "test(\"E^PI\", math.e ** math.pi)\n", "test(\"exp(0)\", 1)\n", "test(\"exp(1)\", math.e)\n", "test(\"2^3^2\", 2 ** 3 ** 2)\n", "test(\"(2^3)^2\", (2 ** 3) ** 2)\n", "test(\"2^3+2\", 2 ** 3 + 2)\n", "test(\"2^3+5\", 2 ** 3 + 5)\n", "test(\"2^9\", 2 ** 9)\n", "test(\"sgn(-2)\", -1)\n", "test(\"sgn(0)\", 0)\n", "test(\"sgn(0.1)\", 1)\n", "test(\"foo(0.1)\", None)\n", "test(\"round(E, 3)\", round(math.e, 3))\n", "test(\"round(PI^2, 3)\", round(math.pi ** 2, 3))\n", "test(\"sgn(cos(PI/4))\", 1)\n", "test(\"sgn(cos(PI/2))\", 0)\n", "test(\"sgn(cos(PI*3/4))\", -1)\n", "test(\"+(sgn(cos(PI/4)))\", 1)\n", "test(\"-(sgn(cos(PI/4)))\", -1)\n", "test(\"hypot(3, 4)\", 5)\n", "test(\"multiply(3, 7)\", 21)\n", "test(\"all(1,1,1)\", True)\n", "test(\"all(1,1,1,1,1,0)\", False)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test(\"Circle(0,0,5)\", \"center=(0,0),radius=5\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test(\"Circle(0,1,3)\", \"center=(0,0),radius=5\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "A=3\n", "N=4" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test(\"N*A/5\",12/5)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "body=\"\"\"\\\n", "rectangle: \n", " formula: Rectangle(contact, rl, rL)\n", " style:\n", " linecolor: blue\n", " filled_curves: blue\n", " transform: [\"rotate(-theta, contact)\",\n", " \"translate(-rl/2*tangent_vec)\"]\n", "N: \n", " formula: Force(contact - rl*normal_vec, contact, r'$N$', text_pos='start')\n", " style:\n", " linecolor: black\n", "wheel: \n", " formula: \"Composition({'outer': rectangle})\" \n", " style:\n", " shadow: 1\n", "mc:\n", " formula: Text(r'$c$', c)\n", "body: \n", " formula: \"Composition({'wheel': wheel, 'N': N, 'mc': mc})\"\n", " style:\n", " linecolor: black\n", "\"\"\"" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "from ruamel.yaml import YAML" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "yaml = YAML()" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "gwd = yaml.load(body)" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "8" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gwd['N'].lc.line" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "BNF().parseString(\"Rectangle(contact, rl, rL)\", parseAll=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "BNF().parseString(\"Force(contact - rl*normal_vec, contact, r'$N$', text_pos='start')\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def sketchInterpreter(sketch, container):\n", " yaml = YAML()\n", " gwd = yaml.load(sketch)\n", " for _k in list(gwd.keys()):\n", " if _k == \"stop\":\n", " break\n", " _c = gwd[_k]\n", " _t = str(type(_c))\n", " if _k == \"libraries\":\n", " for l in _c:\n", " exec(l,container)\n", " #print(_k, _c, _t)\n", " if _t == \"\" or \\\n", " _t == \"\" or _t == \"\":\n", " _formula = f\"{_k} = {_c}\".replace(\"\",\"\\\\\") \n", " #print(_formula)\n", " exec(_formula,container)\n", " elif _t == \"\":\n", " #print(_c)\n", " _keys = list(_c.keys())\n", " #print(_keys)\n", " if 'formula' in _keys:\n", " _formula = f\"{_k} = {_c['formula']}\".replace(\"\",\"\\\\\")\n", " #print(_formula)\n", " exec(_formula,container)\n", " if 'style' in _keys:\n", " for _style in _c[\"style\"]:\n", " # x_const.set_linestyle('dotted')\n", " _param = _c[\"style\"][_style]\n", " __t = str(type(_param))\n", " #print(__t)\n", " if __t == \"\":\n", " _style = f\"{_k}.set_{_style}({_param})\"\n", " else:\n", " _style = f\"{_k}.set_{_style}('{_param}')\"\n", " #print(_style)\n", " exec(_style,container)\n", " if 'transform' in _keys:\n", " #print(_c['transform'])\n", " if str(type(_c['transform'])) == \"\":\n", " _t = f\"{_k}.{_c['transform']}\"\n", " #print(_t)\n", " exec(_t,container)\n", " else:\n", " for _transform in _c[\"transform\"]:\n", " # x_const.rotate(-theta, contact)\n", " _t = f\"{_k}.{_transform}\"\n", " #print(_t)\n", " exec(_t,container)\n", " if \"action\" in _keys:\n", " _action = _c[\"action\"]\n", " #print(_action)\n", " exec(_action,container)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "parser" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import parser" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "exp = parser.expr(\"Force(contact - rl*normal_vec, contact, r'$N$', text_pos='start')\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "exp.isexpr()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "parser" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pip install showast" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!pip install wheel" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import showast" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "%3\n", "\n", "\n", "0\n", "Expr\n", "\n", "\n", "1\n", "Call\n", "\n", "\n", "0--1\n", "\n", "\n", "\n", "2\n", "Name\n", "\n", "\n", "1--2\n", "\n", "\n", "\n", "5\n", "BinOp\n", "\n", "\n", "1--5\n", "\n", "\n", "\n", "18\n", "Name\n", "\n", "\n", "1--18\n", "\n", "\n", "\n", "21\n", "Constant\n", "\n", "\n", "1--21\n", "\n", "\n", "\n", "23\n", "keyword\n", "\n", "\n", "1--23\n", "\n", "\n", "\n", "3\n", ""Force"\n", "\n", "\n", "2--3\n", "\n", "\n", "\n", "4\n", "Load\n", "\n", "\n", "2--4\n", "\n", "\n", "\n", "6\n", "Name\n", "\n", "\n", "5--6\n", "\n", "\n", "\n", "9\n", "Sub\n", "\n", "\n", "5--9\n", "\n", "\n", "\n", "10\n", "BinOp\n", "\n", "\n", "5--10\n", "\n", "\n", "\n", "7\n", ""contact"\n", "\n", "\n", "6--7\n", "\n", "\n", "\n", "8\n", "Load\n", "\n", "\n", "6--8\n", "\n", "\n", "\n", "11\n", "Name\n", "\n", "\n", "10--11\n", "\n", "\n", "\n", "14\n", "Mult\n", "\n", "\n", "10--14\n", "\n", "\n", "\n", "15\n", "Name\n", "\n", "\n", "10--15\n", "\n", "\n", "\n", "12\n", ""rl"\n", "\n", "\n", "11--12\n", "\n", "\n", "\n", "13\n", "Load\n", "\n", "\n", "11--13\n", "\n", "\n", "\n", "16\n", ""normal_vec"\n", "\n", "\n", "15--16\n", "\n", "\n", "\n", "17\n", "Load\n", "\n", "\n", "15--17\n", "\n", "\n", "\n", "19\n", ""contact"\n", "\n", "\n", "18--19\n", "\n", "\n", "\n", "20\n", "Load\n", "\n", "\n", "18--20\n", "\n", "\n", "\n", "22\n", ""$N$"\n", "\n", "\n", "21--22\n", "\n", "\n", "\n", "24\n", ""text_pos"\n", "\n", "\n", "23--24\n", "\n", "\n", "\n", "25\n", "Constant\n", "\n", "\n", "23--25\n", "\n", "\n", "\n", "26\n", ""start"\n", "\n", "\n", "25--26\n", "\n", "\n", "\n", "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%showast\n", "Force(contact - rl*normal_vec, contact, r'$N$', text_pos='start')" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "import ast\n", "\n", "root = ast.parse(\"Force(contact - rl*normal_vec, contact, r'$N$', text_pos='start')\")\n", "names = sorted({node.id for node in ast.walk(root) if isinstance(node, ast.Name)})\n", "constants = sorted({node.value for node in ast.walk(root) if isinstance(node, ast.Constant)})" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['Force', 'contact', 'normal_vec', 'rl']" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "names" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['$N$', 'start']" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "constants" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "head = \"\"\"\\\n", "libraries: [\"from math import tan, radians, sin, cos\",\"from pysketcher import *\"]\n", "fontsize: 18\n", "g: 9.81 # constant gravity\n", "theta: 30.0 # inclined plane angle\n", "L: 10.0 # sketch sizing parameter\n", "a: 1.0 #\n", "xmin: 0.0 # sketech min Abscissa\n", "ymin: -3.0 # sketech min Ordinate \n", "rl: 2.0 # rectangle width\n", "rL: 1.0 # rectangle length\n", "setframe: # sketch setup\n", " action: \"drawing_tool.set_coordinate_system(xmin=xmin-L/5, xmax=xmin+1.5*L,ymin=ymin, ymax=ymin+1.5*L,instruction_file='tmp_mpl_friction.py')\"\n", "setblackline: # default frame values and actions\n", " action: \"drawing_tool.set_linecolor('black')\"\n", "B: point(a+L,0) # wall right end\n", "A: point(a,tan(radians(theta))*L) # wall left end\n", "normal_vec: point(sin(radians(theta)),cos(radians(theta))) # Vector normal to wall\n", "tangent_vec: point(cos(radians(theta)),-sin(radians(theta))) # Vector tangent to wall\n", "help_line: Line(A,B) # wall line\n", "x: a + 3*L/10.\n", "y: help_line(x=x) \n", "contact: point(x, y) \n", "c: contact + rL/2*normal_vec\n", "\"\"\"" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "myfig={}" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [], "source": [ "from pysketcher import *" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [], "source": [ "sketchParse(head,myfig)" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "'g' in myfig" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "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.8.5" } }, "nbformat": 4, "nbformat_minor": 4 }