|
7 | 7 | "cell_type": "latex", |
8 | 8 | "cells": [ |
9 | 9 | { |
10 | | - "cell_id": 12015957794285357231, |
| 10 | + "cell_id": 798500217262993049, |
11 | 11 | "cell_origin": "client", |
12 | 12 | "cell_type": "latex_view", |
13 | 13 | "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" |
|
22 | 22 | "cell_type": "input", |
23 | 23 | "source": "import inspect" |
24 | 24 | }, |
| 25 | + { |
| 26 | + "cell_id": 13041134632829120942, |
| 27 | + "cell_origin": "client", |
| 28 | + "cell_type": "latex", |
| 29 | + "cells": [ |
| 30 | + { |
| 31 | + "cell_id": 17870137200133729598, |
| 32 | + "cell_origin": "client", |
| 33 | + "cell_type": "latex_view", |
| 34 | + "source": "\\algorithm{class Algorithm(ex: Ex)}{Base class for user defined tree-traversal algorithms}" |
| 35 | + } |
| 36 | + ], |
| 37 | + "hidden": true, |
| 38 | + "source": "\\algorithm{class Algorithm(ex: Ex)}{Base class for user defined tree-traversal algorithms}" |
| 39 | + }, |
| 40 | + { |
| 41 | + "cell_id": 1319220678577630750, |
| 42 | + "cell_origin": "client", |
| 43 | + "cell_type": "input", |
| 44 | + "source": "from cdb.utils._algorithm import Algorithm" |
| 45 | + }, |
| 46 | + { |
| 47 | + "cell_id": 13334862458884777602, |
| 48 | + "cell_origin": "client", |
| 49 | + "cell_type": "input", |
| 50 | + "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})\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\treturn wrapper" |
| 51 | + }, |
| 52 | + { |
| 53 | + "cell_id": 2421359616022599147, |
| 54 | + "cell_origin": "client", |
| 55 | + "cell_type": "latex", |
| 56 | + "cells": [ |
| 57 | + { |
| 58 | + "cell_id": 16353153281984723823, |
| 59 | + "cell_origin": "client", |
| 60 | + "cell_type": "latex_view", |
| 61 | + "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" |
| 62 | + } |
| 63 | + ], |
| 64 | + "hidden": true, |
| 65 | + "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" |
| 66 | + }, |
| 67 | + { |
| 68 | + "cell_id": 8704625228430657301, |
| 69 | + "cell_origin": "client", |
| 70 | + "cell_type": "input", |
| 71 | + "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" |
| 72 | + }, |
| 73 | + { |
| 74 | + "cell_id": 2405738556624578142, |
| 75 | + "cell_origin": "client", |
| 76 | + "cell_type": "input", |
| 77 | + "cells": [ |
| 78 | + { |
| 79 | + "cell_id": 4043273741898506206, |
| 80 | + "cell_origin": "server", |
| 81 | + "cell_type": "latex_view", |
| 82 | + "cells": [ |
| 83 | + { |
| 84 | + "cell_id": 11767503884852531506, |
| 85 | + "cell_origin": "server", |
| 86 | + "cell_type": "input_form", |
| 87 | + "source": "A^{\\mu} + B^{\\mu}" |
| 88 | + } |
| 89 | + ], |
| 90 | + "source": "\\begin{dmath*}{}A^{\\mu}+B^{\\mu}\\end{dmath*}" |
| 91 | + } |
| 92 | + ], |
| 93 | + "ignore_on_import": true, |
| 94 | + "source": "@algo\ndef switch_indices(node):\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}$);" |
| 95 | + }, |
| 96 | + { |
| 97 | + "cell_id": 9340011096170931285, |
| 98 | + "cell_origin": "client", |
| 99 | + "cell_type": "input", |
| 100 | + "cells": [ |
| 101 | + { |
| 102 | + "cell_id": 14835945528453774882, |
| 103 | + "cell_origin": "server", |
| 104 | + "cell_type": "latex_view", |
| 105 | + "cells": [ |
| 106 | + { |
| 107 | + "cell_id": 15750930497414250141, |
| 108 | + "cell_origin": "server", |
| 109 | + "cell_type": "input_form", |
| 110 | + "source": "2y" |
| 111 | + } |
| 112 | + ], |
| 113 | + "source": "\\begin{dmath*}{}2y\\end{dmath*}" |
| 114 | + } |
| 115 | + ], |
| 116 | + "ignore_on_import": true, |
| 117 | + "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$);" |
| 118 | + }, |
| 119 | + { |
| 120 | + "cell_id": 15634397355979861807, |
| 121 | + "cell_origin": "client", |
| 122 | + "cell_type": "input", |
| 123 | + "cells": [ |
| 124 | + { |
| 125 | + "cell_id": 960346398456001684, |
| 126 | + "cell_origin": "server", |
| 127 | + "cell_type": "latex_view", |
| 128 | + "cells": [ |
| 129 | + { |
| 130 | + "cell_id": 1313190532695810937, |
| 131 | + "cell_origin": "server", |
| 132 | + "cell_type": "input_form", |
| 133 | + "source": "2t_{\\mu}" |
| 134 | + } |
| 135 | + ], |
| 136 | + "source": "\\begin{dmath*}{}2t_{\\mu}\\end{dmath*}" |
| 137 | + } |
| 138 | + ], |
| 139 | + "ignore_on_import": true, |
| 140 | + "source": "@algo\nclass a_to_b(Algorithm):\n\tdef __init__(self, ex, a, b):\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}$);" |
| 141 | + }, |
| 142 | + { |
| 143 | + "cell_id": 10135200674124616797, |
| 144 | + "cell_origin": "client", |
| 145 | + "cell_type": "input", |
| 146 | + "ignore_on_import": true, |
| 147 | + "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" |
| 148 | + }, |
25 | 149 | { |
26 | 150 | "cell_id": 15845148038835594810, |
27 | 151 | "cell_origin": "client", |
28 | 152 | "cell_type": "latex", |
29 | 153 | "cells": [ |
30 | 154 | { |
31 | | - "cell_id": 11938570826720444108, |
| 155 | + "cell_id": 8739236297038682978, |
32 | 156 | "cell_origin": "client", |
33 | 157 | "cell_type": "latex_view", |
34 | | - "source": "\\algorithm{time_algo(algo: function, ex: Ex, *args: <mixed>, iterations: int = 100) -> float}{Simple function to time the \nexecution 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." |
| 158 | + "source": "\\algorithm{time_algo(algo: function, ex: Ex, *args: <mixed>, 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." |
35 | 159 | } |
36 | 160 | ], |
| 161 | + "hidden": true, |
37 | 162 | "source": "\\algorithm{time_algo(algo: function, ex: Ex, *args: <mixed>, 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." |
38 | 163 | }, |
39 | 164 | { |
40 | 165 | "cell_id": 5937432672654218849, |
41 | 166 | "cell_origin": "client", |
42 | 167 | "cell_type": "input", |
43 | | - "source": "def 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 (s.seconds() + s.useconds() / 1000000) / iterations" |
| 168 | + "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)" |
| 169 | + }, |
| 170 | + { |
| 171 | + "cell_id": 17198486848786764579, |
| 172 | + "cell_origin": "client", |
| 173 | + "cell_type": "input", |
| 174 | + "cells": [ |
| 175 | + { |
| 176 | + "cell_id": 16504503887878721184, |
| 177 | + "cell_origin": "server", |
| 178 | + "cell_type": "verbatim", |
| 179 | + "source": "\\begin{verbatim}0:00:00.008331\\end{verbatim}" |
| 180 | + } |
| 181 | + ], |
| 182 | + "ignore_on_import": true, |
| 183 | + "source": "time_algo(substitute, $a + b$, $a -> b$);" |
| 184 | + }, |
| 185 | + { |
| 186 | + "cell_id": 12791856048123301106, |
| 187 | + "cell_origin": "client", |
| 188 | + "cell_type": "input", |
| 189 | + "cells": [ |
| 190 | + { |
| 191 | + "cell_id": 17622425082194156367, |
| 192 | + "cell_origin": "server", |
| 193 | + "cell_type": "verbatim", |
| 194 | + "source": "\\begin{verbatim}0:00:00.003194\\end{verbatim}" |
| 195 | + } |
| 196 | + ], |
| 197 | + "ignore_on_import": true, |
| 198 | + "source": "def times_2(ex):\n\treturn ex * $2$\n\ntime_algo(times_2, $a$);" |
44 | 199 | }, |
45 | 200 | { |
46 | 201 | "cell_id": 15332315745938100730, |
47 | 202 | "cell_origin": "client", |
48 | 203 | "cell_type": "latex", |
49 | 204 | "cells": [ |
50 | 205 | { |
51 | | - "cell_id": 11894560304174452086, |
| 206 | + "cell_id": 8935506242999741244, |
52 | 207 | "cell_origin": "client", |
53 | 208 | "cell_type": "latex_view", |
54 | 209 | "source": "\\algorithm{CadabraTestError}{Exception derived from AssertionError raised by testing functions when an assertion fails}" |
|
69 | 224 | "cell_type": "latex", |
70 | 225 | "cells": [ |
71 | 226 | { |
72 | | - "cell_id": 14998088897783344386, |
| 227 | + "cell_id": 11588087231457837709, |
73 | 228 | "cell_origin": "client", |
74 | 229 | "cell_type": "latex_view", |
75 | | - "source": "\\algorithm{test_algo(expected: Ex, verbose: bool)}{Decorator to aid defining unit tests for algorithms.}\n\nThis\tadds the boilerplate code and adds an assert for the test." |
| 230 | + "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." |
76 | 231 | } |
77 | 232 | ], |
78 | 233 | "hidden": true, |
79 | | - "source": "\\algorithm{test_algo(expected: Ex, verbose: bool)}{Decorator to aid defining unit tests for algorithms.}\n\nThis\tadds the boilerplate code and adds an assert for the test." |
| 234 | + "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." |
80 | 235 | }, |
81 | 236 | { |
82 | 237 | "cell_id": 15123798302514262900, |
83 | 238 | "cell_origin": "client", |
84 | 239 | "cell_type": "input", |
85 | | - "source": "def test_algo(expected, verbose=False):\n\t\"\"\"\n\tExample usage:\n\t\t@test_algo($a + b + c$)\n\t\tdef sort_sum_test():\n\t\t\tex := b + a + c.\n\t\t\treturn sort_sum(ex)\n\t\"\"\"\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\tif verbose: print(tree(expected))\n\t\t\t\tprint(\"Produced: \" + str(res))\n\t\t\t\tif verbose: print(tree(res))\n\t\t\t\traise CadabraTestError\n\t\t\treturn res\n\t\treturn wrapper\n\treturn decorator" |
| 240 | + "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" |
| 241 | + }, |
| 242 | + { |
| 243 | + "cell_id": 2554539507442301090, |
| 244 | + "cell_origin": "client", |
| 245 | + "cell_type": "input", |
| 246 | + "cells": [ |
| 247 | + { |
| 248 | + "cell_id": 17109594158398788775, |
| 249 | + "cell_origin": "server", |
| 250 | + "cell_type": "output", |
| 251 | + "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\n\\end{verbatim}" |
| 252 | + } |
| 253 | + ], |
| 254 | + "ignore_on_import": true, |
| 255 | + "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\tpass" |
86 | 256 | }, |
87 | 257 | { |
88 | 258 | "cell_id": 17571440201905887785, |
89 | 259 | "cell_origin": "client", |
90 | 260 | "cell_type": "latex", |
91 | 261 | "cells": [ |
92 | 262 | { |
93 | | - "cell_id": 309410713532683075, |
| 263 | + "cell_id": 12853042454041156620, |
94 | 264 | "cell_origin": "client", |
95 | 265 | "cell_type": "latex_view", |
96 | | - "source": "\\algorithm{inherit_kernel() -> Kernel}{Find a kernel in the stack.}\n\nMove up stack frames until one which defines the \\_\\_cdbkernel\\_\\_ variable is located and return \nit. If no Kernel object is foundthen None is returned" |
| 266 | + "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" |
97 | 267 | } |
98 | 268 | ], |
99 | 269 | "hidden": true, |
100 | | - "source": "\\algorithm{inherit_kernel() -> Kernel}{Find a kernel in the stack.}\n\nMove up stack frames until one which defines the \\_\\_cdbkernel\\_\\_ variable is located and return \nit. If no Kernel object is foundthen None is returned" |
| 270 | + "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" |
101 | 271 | }, |
102 | 272 | { |
103 | 273 | "cell_id": 460414117077303111, |
|
109 | 279 | "cell_id": 13850153861758424314, |
110 | 280 | "cell_origin": "client", |
111 | 281 | "cell_type": "input", |
| 282 | + "ignore_on_import": true, |
112 | 283 | "source": "" |
113 | 284 | } |
114 | 285 | ], |
|
0 commit comments