{ "cell_id": 7900896013554414286, "cells": [ { "cell_id": 3008518946232599177, "cell_origin": "client", "cell_type": "latex", "cells": [ { "cell_id": 798500217262993049, "cell_origin": "client", "cell_type": "latex_view", "source": "\\package{cdb.utils.develop}{Helper functions to aid development, debugging and testing}\n\nThis package contains some standardised functionality to aid in development of library code and Python algorithms" } ], "hidden": true, "source": "\\package{cdb.utils.develop}{Helper functions to aid development, debugging and testing}\n\nThis package contains some standardised functionality to aid in development of library code and Python algorithms" }, { "cell_id": 13348113113731925581, "cell_origin": "client", "cell_type": "input", "source": "import inspect" }, { "cell_id": 13041134632829120942, "cell_origin": "client", "cell_type": "latex", "cells": [ { "cell_id": 17870137200133729598, "cell_origin": "client", "cell_type": "latex_view", "source": "\\algorithm{class Algorithm(ex: Ex)}{Base class for user defined tree-traversal algorithms}" } ], "hidden": true, "source": "\\algorithm{class Algorithm(ex: Ex)}{Base class for user defined tree-traversal algorithms}" }, { "cell_id": 1319220678577630750, "cell_origin": "client", "cell_type": "input", "source": "from cdb.utils._algorithm import Algorithm" }, { "cell_id": 13334862458884777602, "cell_origin": "client", "cell_type": "input", "source": "from cdb.utils._algorithm import apply_algo_base as _apply_algo_base\n\ndef _cast_algo(apply, can_apply = lambda node: True):\n\tif len(inspect.getfullargspec(apply).args) != 1:\n\t\traise TypeError(\"Failed to convert function to algorithm: function must only take one 'node' parameter\")\n\tif len(inspect.getfullargspec(can_apply).args) != 1:\n\t\traise TypeError(\"Failed to convert function to algorithm: predicate must only take one 'node' parameter\")\n\treturn type(apply.__name__, (Algorithm, ), { \n\t\t\"can_apply\": lambda self, node: can_apply(node), \n\t\t\"apply\": lambda self, node: apply(node),\n\t\t\"__doc__\": apply.__doc__\n\t})\n\ndef _create_algo(cls, pre_order):\n\tif Algorithm not in cls.__bases__:\n\t\traise TypeError(\"Algorithm must derive from Algorithm type\")\n\tdef wrapper(ex, *args, deep=True, repeat=False, depth=0, **kwargs):\n\t\tx = cls(ex, *args, **kwargs)\n\t\treturn _apply_algo_base(x, ex, deep, repeat, depth, pre_order)\n\n\t# Generate correct meta-information\n\twrapper.__name__ = cls.__name__\n\twrapper.__doc__ = cls.__doc__\n\t# Get argument list of Algorithm constructor\n\ttry:\n\t\tparameters = list(inspect.signature(cls\t.__init__).parameters.values())[1:]\n\texcept:\n\t\tparameters = [ inspect.Parameter(\"ex\", inspect.Parameter.POSITIONAL_OR_KEYWORD) ]\n\t# Parameters of apply_algo_base\n\talgo_parameters = [\n\t\tinspect.Parameter(\"deep\", inspect.Parameter.KEYWORD_ONLY, default=True),\n\t\tinspect.Parameter(\"repeat\", inspect.Parameter.KEYWORD_ONLY, default=False),\n\t\tinspect.Parameter(\"depth\", inspect.Parameter.KEYWORD_ONLY, default=0) ]\n\t# If parameters has a **kwargs at the end, algo_parameters must be inserted before this\n\tif parameters[-1].kind == inspect.Parameter.VAR_KEYWORD:\n\t\twrapper.__signature__ = inspect.Signature(parameters[:-1] + algo_parameters + parameters[-1:])\n\telse:\n\t\twrapper.__signature__ = inspect.Signature(parameters + algo_parameters)\n\treturn wrapper" }, { "cell_id": 2421359616022599147, "cell_origin": "client", "cell_type": "latex", "cells": [ { "cell_id": 16353153281984723823, "cell_origin": "client", "cell_type": "latex_view", "source": "\\algorithm{algo(pre_order: bool = False, pred: function = (node) -> True) -> function}{Decorator for creating tree-traversal algorithms}\n\nThis decorator takes a function or class derived from \\verb|cdb.utils.develop.Algorithm| and creates a tree-traversal algorithm which\niterates through an expression applying the function or \\verb|apply| method of a class to each node in turn. It optionally takes the\narguments \\verb|pre_order| which determines the order of traversal of the tree (\\verb|False| reverts to post-order iteration) and\n\\verb|pred| which, if decorating a function, will be called at each node in the tree and the function only applied if the predicate\nreturns true. If an \\verb|Algorithm| class is instead decorated, the behaviour of \\verb|pred| is implemented by the \\verb|can_apply|\nmethod which must be overloaded" } ], "hidden": true, "source": "\\algorithm{algo(pre_order: bool = False, pred: function = (node) -> True) -> function}{Decorator for creating tree-traversal algorithms}\n\nThis decorator takes a function or class derived from \\verb|cdb.utils.develop.Algorithm| and creates a tree-traversal algorithm which\niterates through an expression applying the function or \\verb|apply| method of a class to each node in turn. It optionally takes the\narguments \\verb|pre_order| which determines the order of traversal of the tree (\\verb|False| reverts to post-order iteration) and\n\\verb|pred| which, if decorating a function, will be called at each node in the tree and the function only applied if the predicate\nreturns true. If an \\verb|Algorithm| class is instead decorated, the behaviour of \\verb|pred| is implemented by the \\verb|can_apply|\nmethod which must be overloaded" }, { "cell_id": 8704625228430657301, "cell_origin": "client", "cell_type": "input", "source": "def algo(*args, **kwargs):\n\t# Detect if we are called without arguments and therefore\n\t# get a single class or function instance. In this instance\n\t# default to post order and a predicate always returning true\n\tif len(args) == 1 and len(kwargs) == 0:\n\t\tif inspect.isfunction(args[0]):\n\t\t\tcls = _cast_algo(args[0])\n\t\telif inspect.isclass(args[0]):\n\t\t\tcls = args[0]\n\t\telse:\n\t\t\traise ValueError(\"algo does not accept positional arguments\")\n\t\treturn _create_algo(cls, pre_order=False)\n\n\t# Otherwise we expect an empty *args and collect the keyword arguments\n\tif len(args) != 0:\n\t\traise ValueError(\"algo does not accept positional arguments\")\n\tpre_order = kwargs.get('pre_order', False)\n\tpred = kwargs.get('pred', lambda node: True)\n\n\t# Then we return a decorator with no arguments which will get applied\n\tdef unwrapped(arg):\n\t\tif inspect.isfunction(arg):\n\t\t\tcls = _cast_algo(arg, pred)\n\t\telse:\n\t\t\tif 'pred' in kwargs:\n\t\t\t\traise ValueError(\"pred supplied to algo decorator, but this does not apply to class being decorated\")\n\t\t\tcls = arg\n\t\treturn _create_algo(cls, pre_order=pre_order)\n\treturn unwrapped" }, { "cell_id": 2405738556624578142, "cell_origin": "client", "cell_type": "input", "cells": [ { "cell_id": 15761994739137138802, "cell_origin": "server", "cell_type": "latex_view", "cells": [ { "cell_id": 17415156100168742565, "cell_origin": "server", "cell_type": "input_form", "source": "A^{\\mu} + B^{\\mu}" } ], "source": "\\begin{dmath*}{}A^{\\mu}+B^{\\mu}\\end{dmath*}" }, { "cell_id": 15144194947899395553, "cell_origin": "server", "cell_type": "output", "source": "\\begin{verbatim}Help on function switch_indices in module __main__:\n\nswitch_indices(ex, *, deep=True, repeat=False, depth=0)\n Switch positions on all indices\n\n\\end{verbatim}" } ], "ignore_on_import": true, "source": "@algo\ndef switch_indices(node):\n\t\"Switch positions on all indices\"\n\tif node.parent_rel == parent_rel_t.sub:\n\t\tnode.parent_rel = parent_rel_t.super\n\t\treturn result_t.changed\n\tif node.parent_rel == parent_rel_t.super:\n\t\tnode.parent_rel = parent_rel_t.sub\n\t\treturn result_t.changed\n\treturn result_t.unchanged\n\n# also takes optional 'deep', 'repeat' and 'depth' arguments\nswitch_indices($A_{\\mu} + B_{\\mu}$);\nhelp(switch_indices)" }, { "cell_id": 9340011096170931285, "cell_origin": "client", "cell_type": "input", "cells": [ { "cell_id": 5850074756591759657, "cell_origin": "server", "cell_type": "latex_view", "cells": [ { "cell_id": 9620504524171758578, "cell_origin": "server", "cell_type": "input_form", "source": "2y" } ], "source": "\\begin{dmath*}{}2y\\end{dmath*}" }, { "cell_id": 17850582158135921593, "cell_origin": "server", "cell_type": "output", "source": "\\begin{verbatim}Help on function x_to_y in module __main__:\n\nx_to_y(ex, *, deep=True, repeat=False, depth=0)\n\n\\end{verbatim}" } ], "ignore_on_import": true, "source": "@algo(pre_order=True, pred=lambda node: node.name == 'x')\ndef x_to_y(node):\n\tnode.name = 'y'\n\treturn result_t.changed\n\nx_to_y($x + y$);\nhelp(x_to_y)" }, { "cell_id": 15634397355979861807, "cell_origin": "client", "cell_type": "input", "cells": [ { "cell_id": 14537821031780487327, "cell_origin": "server", "cell_type": "latex_view", "cells": [ { "cell_id": 3417601803473011010, "cell_origin": "server", "cell_type": "input_form", "source": "2t_{\\mu}" } ], "source": "\\begin{dmath*}{}2t_{\\mu}\\end{dmath*}" }, { "cell_id": 17754394564063656935, "cell_origin": "server", "cell_type": "output", "source": "\\begin{verbatim}Help on function a_to_b in module __main__:\n\na_to_b(ex, a, b, *args, deep=True, repeat=False, depth=0, **kwargs)\n\n\\end{verbatim}" } ], "ignore_on_import": true, "source": "@algo\nclass a_to_b(Algorithm):\n\tdef __init__(self, ex, a, b, *args, **kwargs):\n\t\tAlgorithm.__init__(self, ex)\n\t\tself.a, self.b = a, b\n\t\n\tdef can_apply(self, node):\n\t\treturn node.name == self.a.head()\n\n\tdef apply(self, node):\n\t\tnode.name = self.b.head()\n\t\treturn result_t.changed\n\na_to_b($s_{\\mu} + t_{\\mu}$, $s_{\\mu}$, $t_{\\mu}$);\nhelp(a_to_b)" }, { "cell_id": 10135200674124616797, "cell_origin": "client", "cell_type": "input", "ignore_on_import": true, "source": "try:\n\t@algo\n\tdef function_with_too_many_arguments(a,b,c):\n\t\tpass\n\traise AssertionError(\"TypeError not raised\")\nexcept TypeError:\n\tpass\n\ntry:\n\t@algo\n\tclass does_not_inherit_from_Algorithm:\n\t\tpass\n\traise AssertionError(\"TypeError not raised\")\nexcept TypeError:\n\tpass\n\ntry:\n\t@algo(pred=lambda n: n.name == 'x')\n\tclass does_not_need_pred(Algorithm):\n\t\tdef can_apply(self,node): return n.name =='x'\n\t\tdef apply(self,node): return result_t.unchanged\n\traise AssertionError(\"ValueError not raised\")\nexcept ValueError:\n\tpass" }, { "cell_id": 15845148038835594810, "cell_origin": "client", "cell_type": "latex", "cells": [ { "cell_id": 8739236297038682978, "cell_origin": "client", "cell_type": "latex_view", "source": "\\algorithm{time_algo(algo: function, ex: Ex, *args: , iterations: int = 100) -> float}{Simple function to time the execution of an algorithm with given inputs.}\n\nThe arguments in *args are passed directly, but ex is copied before each\ninvocation and so remains unmodified." } ], "hidden": true, "source": "\\algorithm{time_algo(algo: function, ex: Ex, *args: , iterations: int = 100) -> float}{Simple function to time the execution of an algorithm with given inputs.}\n\nThe arguments in *args are passed directly, but ex is copied before each\ninvocation and so remains unmodified." }, { "cell_id": 5937432672654218849, "cell_origin": "client", "cell_type": "input", "source": "from datetime import timedelta\n\ndef time_algo(algo, ex, *args, iterations=100):\n\ts = Stopwatch()\n\tfor i in range(iterations):\n\t\ttmp := @(ex);\n\t\ts.start()\n\t\talgo(tmp, *args)\n\t\ts.stop()\n\treturn timedelta(seconds=s.seconds()/iterations, microseconds=s.useconds()/iterations)" }, { "cell_id": 17198486848786764579, "cell_origin": "client", "cell_type": "input", "cells": [ { "cell_id": 6295148871247203489, "cell_origin": "server", "cell_type": "verbatim", "source": "\\begin{verbatim}0:00:00.000875\\end{verbatim}" } ], "ignore_on_import": true, "source": "time_algo(substitute, $a + b$, $a -> b$);" }, { "cell_id": 12791856048123301106, "cell_origin": "client", "cell_type": "input", "cells": [ { "cell_id": 13532413926824958590, "cell_origin": "server", "cell_type": "verbatim", "source": "\\begin{verbatim}0:00:00.000493\\end{verbatim}" } ], "ignore_on_import": true, "source": "def times_2(ex):\n\treturn ex * $2$\n\ntime_algo(times_2, $a$);" }, { "cell_id": 15332315745938100730, "cell_origin": "client", "cell_type": "latex", "cells": [ { "cell_id": 8935506242999741244, "cell_origin": "client", "cell_type": "latex_view", "source": "\\algorithm{CadabraTestError}{Exception derived from AssertionError raised by testing functions when an assertion fails}" } ], "hidden": true, "source": "\\algorithm{CadabraTestError}{Exception derived from AssertionError raised by testing functions when an assertion fails}" }, { "cell_id": 11180569447504398263, "cell_origin": "client", "cell_type": "input", "source": "class CadabraTestError(AssertionError):\n\tpass" }, { "cell_id": 3479765664248597757, "cell_origin": "client", "cell_type": "latex", "cells": [ { "cell_id": 11588087231457837709, "cell_origin": "client", "cell_type": "latex_view", "source": "\\algorithm{test_algo(expected: Ex, throw_on_fail: bool)}{Decorator to aid defining unit tests for algorithms.}\n\nThis\tadds the boilerplate code and adds an assert for the test." } ], "hidden": true, "source": "\\algorithm{test_algo(expected: Ex, throw_on_fail: bool)}{Decorator to aid defining unit tests for algorithms.}\n\nThis\tadds the boilerplate code and adds an assert for the test." }, { "cell_id": 15123798302514262900, "cell_origin": "client", "cell_type": "input", "source": "def test_algo(expected, throw_on_fail=False):\n\tdef decorator(func):\n\t\tdef wrapper(*args, **kwargs):\n\t\t\tres = func(*args, **kwargs)\n\t\t\tif res == expected:\n\t\t\t\tprint(func.__name__ + \" passed\")\n\t\t\telse:\n\t\t\t\tprint(func.__name__ + \" FAILED\")\n\t\t\t\tprint(\" Expected: \" + str(expected))\n\t\t\t\tprint(\" Produced: \" + str(res))\n\t\t\t\tif throw_on_fail: raise CadabraTestError\n\t\t\treturn res\n\t\treturn wrapper\n\treturn decorator" }, { "cell_id": 2554539507442301090, "cell_origin": "client", "cell_type": "input", "cells": [ { "cell_id": 4855635716783607432, "cell_origin": "server", "cell_type": "output", "source": "\\begin{verbatim}sort_sum_test01 passed\nsort_sum_test02 FAILED\n Expected: a + b\n Produced: a + c\nsort_sum_test03 FAILED\n Expected: a + b\n Produced: a + c\nRaised CadabraTestError\n\\end{verbatim}" } ], "ignore_on_import": true, "source": "@test_algo($a + b + c$)\ndef sort_sum_test01():\n\tex := b + a + c.\n\treturn sort_sum(ex)\nsort_sum_test01()\n\n@test_algo($a + b$)\ndef sort_sum_test02():\n\tex := c + a.\n\treturn sort_sum(ex)\nsort_sum_test02()\n\n@test_algo($a + b$, throw_on_fail=True)\ndef sort_sum_test03():\n\tex := c + a.\n\treturn sort_sum(ex)\n\ntry:\n\tsort_sum_test03()\n\traise AssertionError(\"CadabraTestError not raised!\")\nexcept CadabraTestError:\n\tprint(\"Raised CadabraTestError\")" }, { "cell_id": 17571440201905887785, "cell_origin": "client", "cell_type": "latex", "cells": [ { "cell_id": 12853042454041156620, "cell_origin": "client", "cell_type": "latex_view", "source": "\\algorithm{inherit_kernel() -> Kernel}{Find a kernel in the stack.}\n\nMove up stack frames until one which defines the \\verb|__cdbkernel__| variable is located and return \nit. If no Kernel object is found then None is returned" } ], "hidden": true, "source": "\\algorithm{inherit_kernel() -> Kernel}{Find a kernel in the stack.}\n\nMove up stack frames until one which defines the \\verb|__cdbkernel__| variable is located and return \nit. If no Kernel object is found then None is returned" }, { "cell_id": 460414117077303111, "cell_origin": "client", "cell_type": "input", "source": "def inherit_kernel():\n\tframe = inspect.stack()[1][0]\n\twhile \"__cdbkernel__\" not in frame.f_locals:\n\t\tframe = frame.f_back\n\t\tif frame is None:\n\t\t\treturn None\n\treturn frame.f_locals[\"__cdbkernel__\"]" }, { "cell_id": 13850153861758424314, "cell_origin": "client", "cell_type": "input", "ignore_on_import": true, "source": "" } ], "description": "Cadabra JSON notebook format", "version": 1.0 }