From dc2949b72f0d4beca980daf06b66e8af71449fe1 Mon Sep 17 00:00:00 2001 From: "Lee, Kin Long Kelvin" Date: Thu, 15 Aug 2024 10:44:10 -0700 Subject: [PATCH 001/116] notebook: functional notebook for refactorization Signed-off-by: Lee, Kin Long Kelvin --- ...ct evaluation of spherical harmonics.ipynb | 1333 +++++++++++++++++ 1 file changed, 1333 insertions(+) create mode 100644 notebooks/Direct evaluation of spherical harmonics.ipynb diff --git a/notebooks/Direct evaluation of spherical harmonics.ipynb b/notebooks/Direct evaluation of spherical harmonics.ipynb new file mode 100644 index 0000000..a02b754 --- /dev/null +++ b/notebooks/Direct evaluation of spherical harmonics.ipynb @@ -0,0 +1,1333 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "a575e30b-f424-46fb-a5ba-1099c5c112db", + "metadata": {}, + "outputs": [], + "source": [ + "import math\n", + "from functools import cache, partial\n", + "from itertools import combinations, chain\n", + "\n", + "import sympy\n", + "from sympy import symbols, sqrt, diff, Symbol, latex, cos, sin, acos, simplify, pi\n", + "from sympy.functions.special.spherical_harmonics import Ynm, Znm\n", + "from sympy.functions.elementary.complexes import sign\n", + "from sympy.simplify.radsimp import collect_const, collect_sqrt\n", + "from e3nn.o3._spherical_harmonics import _spherical_harmonics\n", + "import torch\n", + "\n", + "x, y, z = symbols(\"x y z\", real=True)\n", + "\n", + "# express in terms of spherical coordinates\n", + "r = symbols(\"r\", nonnegative=True)\n", + "phi, theta = symbols(\"phi theta\")\n", + "\n", + "# conversion mapping\n", + "sph_to_cart = {\n", + " \"theta\": acos(z / sqrt(x**2. + y**2. + z**2)),\n", + " \"phi\": sign(y) * acos(x / sqrt(x**2 + y**2.)),\n", + " \"r\": sqrt(x**2. + y**2 + z**2)\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "9b9baf24-7d79-404f-8d12-b68b5dd0ded2", + "metadata": {}, + "outputs": [], + "source": [ + "def derive_sph_harm(n: int, real_only: bool = True):\n", + " # define symbols and conversions\n", + " x, y, z = symbols(\"x y z\", real=real_only)\n", + " theta, phi = symbols(\"theta phi\")\n", + " sph_to_cart = {\n", + " \"theta\": acos(z / sqrt(x**2. + y**2. + z**2)),\n", + " \"phi\": sign(y) * acos(x / sqrt(x**2 + y**2.)),\n", + " }\n", + " num_projections = 2 * n + 1\n", + " terms = {}\n", + " for m in range(-n, n + 1):\n", + " sph_harm = Ynm(n, m, theta, phi).subs(sph_to_cart).expand(func=True).cancel().simplify()\n", + " terms[f\"{n},{m}\"] = sph_harm\n", + " return terms\n", + "\n", + "\n", + "def numerical_evaluation(expr, _x: float = 1., _y: float = 1., _z: float = 1.):\n", + " return expr.evalf().subs({\"x\": _x, \"y\": _y, \"z\": _z})" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "1e2d450d-a978-404b-92b4-f8600e5f0aae", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle \\frac{\\sqrt{5} \\left(- x^{2.0} - y^{2.0} + 2 z^{2}\\right)}{4 \\sqrt{\\pi} \\left(x^{2.0} + y^{2.0} + z^{2}\\right)}$" + ], + "text/plain": [ + "sqrt(5)*(-x**2.0 - y**2.0 + 2*z**2)/(4*sqrt(pi)*(x**2.0 + y**2.0 + z**2))" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "derive_sph_harm(2, real_only=True)[\"2,0\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "f7893df2-4c98-4cbc-b5af-8b05d065dd50", + "metadata": {}, + "outputs": [], + "source": [ + "test_tensor = torch.tensor([[1., 1., 1.]])" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "eec63b4e-db2c-48c3-aa7c-6dd64f3a33a1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([[1.0000, 1.7321, 1.7321, 1.7321, 3.8730, 3.8730, 0.0000, 3.8730, 0.0000]])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "_spherical_harmonics(2, test_tensor[:,0], test_tensor[:,1], test_tensor[:,2])" + ] + }, + { + "cell_type": "markdown", + "id": "4feef347-fa61-4ea3-afce-2c004c12be51", + "metadata": {}, + "source": [ + "## From `e3nn` equations\n", + "\n", + "The cell below is incredibly long, as they were directly copied from `e3nn`." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "bbc12b0d-3974-4cdf-b706-6e31eaa9c016", + "metadata": {}, + "outputs": [], + "source": [ + "x, y, z = symbols(\"x y z\")\n", + "\n", + "y_2_0 = math.sqrt(15) * x * z\n", + "y_2_1 = math.sqrt(15) * x * y\n", + "y2 = y**2.\n", + "x2z2 = x**2. + z**2.\n", + "y_2_2 = math.sqrt(5) * (y2 - (1 / 2) * x2z2)\n", + "y_2_3 = math.sqrt(15) * y * z\n", + "y_2_4 = (1 / 2) * math.sqrt(15) * (z**2. - x**2.)\n", + "\n", + "y_3_0 = (1 / 6) * math.sqrt(42) * (y_2_0 * z + y_2_4 * x)\n", + "y_3_1 = math.sqrt(7) * y_2_0 * y\n", + "y_3_2 = (1 / 8) * math.sqrt(168) * (4.0 * y2 - x2z2) * x\n", + "y_3_3 = (1 / 2) * math.sqrt(7) * y * (2.0 * y2 - 3.0 * x2z2)\n", + "y_3_4 = (1 / 8) * math.sqrt(168) * z * (4.0 * y2 - x2z2)\n", + "y_3_5 = math.sqrt(7) * y_2_4 * y\n", + "y_3_6 = (1 / 6) * math.sqrt(42) * (y_2_4 * z - y_2_0 * x)\n", + "\n", + "y_4_0 = (3 / 4) * math.sqrt(2) * (y_3_0 * z + y_3_6 * x)\n", + "y_4_1 = (3 / 4) * y_3_0 * y + (3 / 8) * math.sqrt(6) * y_3_1 * z + (3 / 8) * math.sqrt(6) * y_3_5 * x\n", + "y_4_2 = (\n", + " -3 / 56 * math.sqrt(14) * y_3_0 * z\n", + " + (3 / 14) * math.sqrt(21) * y_3_1 * y\n", + " + (3 / 56) * math.sqrt(210) * y_3_2 * z\n", + " + (3 / 56) * math.sqrt(210) * y_3_4 * x\n", + " + (3 / 56) * math.sqrt(14) * y_3_6 * x\n", + ")\n", + "y_4_3 = (\n", + " -3 / 56 * math.sqrt(42) * y_3_1 * z\n", + " + (3 / 28) * math.sqrt(105) * y_3_2 * y\n", + " + (3 / 28) * math.sqrt(70) * y_3_3 * x\n", + " + (3 / 56) * math.sqrt(42) * y_3_5 * x\n", + ")\n", + "y_4_4 = -3 / 28 * math.sqrt(42) * y_3_2 * x + (3 / 7) * math.sqrt(7) * y_3_3 * y - 3 / 28 * math.sqrt(42) * y_3_4 * z\n", + "y_4_5 = (\n", + " -3 / 56 * math.sqrt(42) * y_3_1 * x\n", + " + (3 / 28) * math.sqrt(70) * y_3_3 * z\n", + " + (3 / 28) * math.sqrt(105) * y_3_4 * y\n", + " - 3 / 56 * math.sqrt(42) * y_3_5 * z\n", + ")\n", + "y_4_6 = (\n", + " -3 / 56 * math.sqrt(14) * y_3_0 * x\n", + " - 3 / 56 * math.sqrt(210) * y_3_2 * x\n", + " + (3 / 56) * math.sqrt(210) * y_3_4 * z\n", + " + (3 / 14) * math.sqrt(21) * y_3_5 * y\n", + " - 3 / 56 * math.sqrt(14) * y_3_6 * z\n", + ")\n", + "y_4_7 = -3 / 8 * math.sqrt(6) * y_3_1 * x + (3 / 8) * math.sqrt(6) * y_3_5 * z + (3 / 4) * y_3_6 * y\n", + "y_4_8 = (3 / 4) * math.sqrt(2) * (-y_3_0 * x + y_3_6 * z)\n", + "\n", + "y_5_0 = (1 / 10) * math.sqrt(110) * (y_4_0 * z + y_4_8 * x)\n", + "y_5_1 = (1 / 5) * math.sqrt(11) * y_4_0 * y + (1 / 5) * math.sqrt(22) * y_4_1 * z + (1 / 5) * math.sqrt(22) * y_4_7 * x\n", + "y_5_2 = (\n", + " -1 / 30 * math.sqrt(22) * y_4_0 * z\n", + " + (4 / 15) * math.sqrt(11) * y_4_1 * y\n", + " + (1 / 15) * math.sqrt(154) * y_4_2 * z\n", + " + (1 / 15) * math.sqrt(154) * y_4_6 * x\n", + " + (1 / 30) * math.sqrt(22) * y_4_8 * x\n", + ")\n", + "y_5_3 = (\n", + " -1 / 30 * math.sqrt(66) * y_4_1 * z\n", + " + (1 / 15) * math.sqrt(231) * y_4_2 * y\n", + " + (1 / 30) * math.sqrt(462) * y_4_3 * z\n", + " + (1 / 30) * math.sqrt(462) * y_4_5 * x\n", + " + (1 / 30) * math.sqrt(66) * y_4_7 * x\n", + ")\n", + "y_5_4 = (\n", + " -1 / 15 * math.sqrt(33) * y_4_2 * z\n", + " + (2 / 15) * math.sqrt(66) * y_4_3 * y\n", + " + (1 / 15) * math.sqrt(165) * y_4_4 * x\n", + " + (1 / 15) * math.sqrt(33) * y_4_6 * x\n", + ")\n", + "y_5_5 = (\n", + " -1 / 15 * math.sqrt(110) * y_4_3 * x + (1 / 3) * math.sqrt(11) * y_4_4 * y - 1 / 15 * math.sqrt(110) * y_4_5 * z\n", + ")\n", + "y_5_6 = (\n", + " -1 / 15 * math.sqrt(33) * y_4_2 * x\n", + " + (1 / 15) * math.sqrt(165) * y_4_4 * z\n", + " + (2 / 15) * math.sqrt(66) * y_4_5 * y\n", + " - 1 / 15 * math.sqrt(33) * y_4_6 * z\n", + ")\n", + "y_5_7 = (\n", + " -1 / 30 * math.sqrt(66) * y_4_1 * x\n", + " - 1 / 30 * math.sqrt(462) * y_4_3 * x\n", + " + (1 / 30) * math.sqrt(462) * y_4_5 * z\n", + " + (1 / 15) * math.sqrt(231) * y_4_6 * y\n", + " - 1 / 30 * math.sqrt(66) * y_4_7 * z\n", + ")\n", + "y_5_8 = (\n", + " -1 / 30 * math.sqrt(22) * y_4_0 * x\n", + " - 1 / 15 * math.sqrt(154) * y_4_2 * x\n", + " + (1 / 15) * math.sqrt(154) * y_4_6 * z\n", + " + (4 / 15) * math.sqrt(11) * y_4_7 * y\n", + " - 1 / 30 * math.sqrt(22) * y_4_8 * z\n", + ")\n", + "y_5_9 = -1 / 5 * math.sqrt(22) * y_4_1 * x + (1 / 5) * math.sqrt(22) * y_4_7 * z + (1 / 5) * math.sqrt(11) * y_4_8 * y\n", + "y_5_10 = (1 / 10) * math.sqrt(110) * (-y_4_0 * x + y_4_8 * z)\n", + "\n", + "y_6_0 = (1 / 6) * math.sqrt(39) * (y_5_0 * z + y_5_10 * x)\n", + "y_6_1 = (\n", + " (1 / 6) * math.sqrt(13) * y_5_0 * y + (1 / 12) * math.sqrt(130) * y_5_1 * z + (1 / 12) * math.sqrt(130) * y_5_9 * x\n", + ")\n", + "y_6_2 = (\n", + " -1 / 132 * math.sqrt(286) * y_5_0 * z\n", + " + (1 / 33) * math.sqrt(715) * y_5_1 * y\n", + " + (1 / 132) * math.sqrt(286) * y_5_10 * x\n", + " + (1 / 44) * math.sqrt(1430) * y_5_2 * z\n", + " + (1 / 44) * math.sqrt(1430) * y_5_8 * x\n", + ")\n", + "y_6_3 = (\n", + " -1 / 132 * math.sqrt(858) * y_5_1 * z\n", + " + (1 / 22) * math.sqrt(429) * y_5_2 * y\n", + " + (1 / 22) * math.sqrt(286) * y_5_3 * z\n", + " + (1 / 22) * math.sqrt(286) * y_5_7 * x\n", + " + (1 / 132) * math.sqrt(858) * y_5_9 * x\n", + ")\n", + "y_6_4 = (\n", + " -1 / 66 * math.sqrt(429) * y_5_2 * z\n", + " + (2 / 33) * math.sqrt(286) * y_5_3 * y\n", + " + (1 / 66) * math.sqrt(2002) * y_5_4 * z\n", + " + (1 / 66) * math.sqrt(2002) * y_5_6 * x\n", + " + (1 / 66) * math.sqrt(429) * y_5_8 * x\n", + ")\n", + "y_6_5 = (\n", + " -1 / 66 * math.sqrt(715) * y_5_3 * z\n", + " + (1 / 66) * math.sqrt(5005) * y_5_4 * y\n", + " + (1 / 66) * math.sqrt(3003) * y_5_5 * x\n", + " + (1 / 66) * math.sqrt(715) * y_5_7 * x\n", + ")\n", + "y_6_6 = (\n", + " -1 / 66 * math.sqrt(2145) * y_5_4 * x + (1 / 11) * math.sqrt(143) * y_5_5 * y - 1 / 66 * math.sqrt(2145) * y_5_6 * z\n", + ")\n", + "y_6_7 = (\n", + " -1 / 66 * math.sqrt(715) * y_5_3 * x\n", + " + (1 / 66) * math.sqrt(3003) * y_5_5 * z\n", + " + (1 / 66) * math.sqrt(5005) * y_5_6 * y\n", + " - 1 / 66 * math.sqrt(715) * y_5_7 * z\n", + ")\n", + "y_6_8 = (\n", + " -1 / 66 * math.sqrt(429) * y_5_2 * x\n", + " - 1 / 66 * math.sqrt(2002) * y_5_4 * x\n", + " + (1 / 66) * math.sqrt(2002) * y_5_6 * z\n", + " + (2 / 33) * math.sqrt(286) * y_5_7 * y\n", + " - 1 / 66 * math.sqrt(429) * y_5_8 * z\n", + ")\n", + "y_6_9 = (\n", + " -1 / 132 * math.sqrt(858) * y_5_1 * x\n", + " - 1 / 22 * math.sqrt(286) * y_5_3 * x\n", + " + (1 / 22) * math.sqrt(286) * y_5_7 * z\n", + " + (1 / 22) * math.sqrt(429) * y_5_8 * y\n", + " - 1 / 132 * math.sqrt(858) * y_5_9 * z\n", + ")\n", + "y_6_10 = (\n", + " -1 / 132 * math.sqrt(286) * y_5_0 * x\n", + " - 1 / 132 * math.sqrt(286) * y_5_10 * z\n", + " - 1 / 44 * math.sqrt(1430) * y_5_2 * x\n", + " + (1 / 44) * math.sqrt(1430) * y_5_8 * z\n", + " + (1 / 33) * math.sqrt(715) * y_5_9 * y\n", + ")\n", + "y_6_11 = (\n", + " -1 / 12 * math.sqrt(130) * y_5_1 * x + (1 / 6) * math.sqrt(13) * y_5_10 * y + (1 / 12) * math.sqrt(130) * y_5_9 * z\n", + ")\n", + "y_6_12 = (1 / 6) * math.sqrt(39) * (-y_5_0 * x + y_5_10 * z)\n", + "\n", + "y_7_0 = (1 / 14) * math.sqrt(210) * (y_6_0 * z + y_6_12 * x)\n", + "y_7_1 = (1 / 7) * math.sqrt(15) * y_6_0 * y + (3 / 7) * math.sqrt(5) * y_6_1 * z + (3 / 7) * math.sqrt(5) * y_6_11 * x\n", + "y_7_2 = (\n", + " -1 / 182 * math.sqrt(390) * y_6_0 * z\n", + " + (6 / 91) * math.sqrt(130) * y_6_1 * y\n", + " + (3 / 91) * math.sqrt(715) * y_6_10 * x\n", + " + (1 / 182) * math.sqrt(390) * y_6_12 * x\n", + " + (3 / 91) * math.sqrt(715) * y_6_2 * z\n", + ")\n", + "y_7_3 = (\n", + " -3 / 182 * math.sqrt(130) * y_6_1 * z\n", + " + (3 / 182) * math.sqrt(130) * y_6_11 * x\n", + " + (3 / 91) * math.sqrt(715) * y_6_2 * y\n", + " + (5 / 182) * math.sqrt(858) * y_6_3 * z\n", + " + (5 / 182) * math.sqrt(858) * y_6_9 * x\n", + ")\n", + "y_7_4 = (\n", + " (3 / 91) * math.sqrt(65) * y_6_10 * x\n", + " - 3 / 91 * math.sqrt(65) * y_6_2 * z\n", + " + (10 / 91) * math.sqrt(78) * y_6_3 * y\n", + " + (15 / 182) * math.sqrt(78) * y_6_4 * z\n", + " + (15 / 182) * math.sqrt(78) * y_6_8 * x\n", + ")\n", + "y_7_5 = (\n", + " -5 / 91 * math.sqrt(39) * y_6_3 * z\n", + " + (15 / 91) * math.sqrt(39) * y_6_4 * y\n", + " + (3 / 91) * math.sqrt(390) * y_6_5 * z\n", + " + (3 / 91) * math.sqrt(390) * y_6_7 * x\n", + " + (5 / 91) * math.sqrt(39) * y_6_9 * x\n", + ")\n", + "y_7_6 = (\n", + " -15 / 182 * math.sqrt(26) * y_6_4 * z\n", + " + (12 / 91) * math.sqrt(65) * y_6_5 * y\n", + " + (2 / 91) * math.sqrt(1365) * y_6_6 * x\n", + " + (15 / 182) * math.sqrt(26) * y_6_8 * x\n", + ")\n", + "y_7_7 = (\n", + " -3 / 91 * math.sqrt(455) * y_6_5 * x + (1 / 13) * math.sqrt(195) * y_6_6 * y - 3 / 91 * math.sqrt(455) * y_6_7 * z\n", + ")\n", + "y_7_8 = (\n", + " -15 / 182 * math.sqrt(26) * y_6_4 * x\n", + " + (2 / 91) * math.sqrt(1365) * y_6_6 * z\n", + " + (12 / 91) * math.sqrt(65) * y_6_7 * y\n", + " - 15 / 182 * math.sqrt(26) * y_6_8 * z\n", + ")\n", + "y_7_9 = (\n", + " -5 / 91 * math.sqrt(39) * y_6_3 * x\n", + " - 3 / 91 * math.sqrt(390) * y_6_5 * x\n", + " + (3 / 91) * math.sqrt(390) * y_6_7 * z\n", + " + (15 / 91) * math.sqrt(39) * y_6_8 * y\n", + " - 5 / 91 * math.sqrt(39) * y_6_9 * z\n", + ")\n", + "y_7_10 = (\n", + " -3 / 91 * math.sqrt(65) * y_6_10 * z\n", + " - 3 / 91 * math.sqrt(65) * y_6_2 * x\n", + " - 15 / 182 * math.sqrt(78) * y_6_4 * x\n", + " + (15 / 182) * math.sqrt(78) * y_6_8 * z\n", + " + (10 / 91) * math.sqrt(78) * y_6_9 * y\n", + ")\n", + "y_7_11 = (\n", + " -3 / 182 * math.sqrt(130) * y_6_1 * x\n", + " + (3 / 91) * math.sqrt(715) * y_6_10 * y\n", + " - 3 / 182 * math.sqrt(130) * y_6_11 * z\n", + " - 5 / 182 * math.sqrt(858) * y_6_3 * x\n", + " + (5 / 182) * math.sqrt(858) * y_6_9 * z\n", + ")\n", + "y_7_12 = (\n", + " -1 / 182 * math.sqrt(390) * y_6_0 * x\n", + " + (3 / 91) * math.sqrt(715) * y_6_10 * z\n", + " + (6 / 91) * math.sqrt(130) * y_6_11 * y\n", + " - 1 / 182 * math.sqrt(390) * y_6_12 * z\n", + " - 3 / 91 * math.sqrt(715) * y_6_2 * x\n", + ")\n", + "y_7_13 = -3 / 7 * math.sqrt(5) * y_6_1 * x + (3 / 7) * math.sqrt(5) * y_6_11 * z + (1 / 7) * math.sqrt(15) * y_6_12 * y\n", + "y_7_14 = (1 / 14) * math.sqrt(210) * (-y_6_0 * x + y_6_12 * z)\n", + "\n", + "y_8_0 = (1 / 4) * math.sqrt(17) * (y_7_0 * z + y_7_14 * x)\n", + "y_8_1 = (\n", + " (1 / 8) * math.sqrt(17) * y_7_0 * y + (1 / 16) * math.sqrt(238) * y_7_1 * z + (1 / 16) * math.sqrt(238) * y_7_13 * x\n", + ")\n", + "y_8_2 = (\n", + " -1 / 240 * math.sqrt(510) * y_7_0 * z\n", + " + (1 / 60) * math.sqrt(1785) * y_7_1 * y\n", + " + (1 / 240) * math.sqrt(46410) * y_7_12 * x\n", + " + (1 / 240) * math.sqrt(510) * y_7_14 * x\n", + " + (1 / 240) * math.sqrt(46410) * y_7_2 * z\n", + ")\n", + "y_8_3 = (\n", + " (1 / 80)\n", + " * math.sqrt(2)\n", + " * (\n", + " -math.sqrt(85) * y_7_1 * z\n", + " + math.sqrt(2210) * y_7_11 * x\n", + " + math.sqrt(85) * y_7_13 * x\n", + " + math.sqrt(2210) * y_7_2 * y\n", + " + math.sqrt(2210) * y_7_3 * z\n", + " )\n", + ")\n", + "y_8_4 = (\n", + " (1 / 40) * math.sqrt(935) * y_7_10 * x\n", + " + (1 / 40) * math.sqrt(85) * y_7_12 * x\n", + " - 1 / 40 * math.sqrt(85) * y_7_2 * z\n", + " + (1 / 10) * math.sqrt(85) * y_7_3 * y\n", + " + (1 / 40) * math.sqrt(935) * y_7_4 * z\n", + ")\n", + "y_8_5 = (\n", + " (1 / 48)\n", + " * math.sqrt(2)\n", + " * (\n", + " math.sqrt(102) * y_7_11 * x\n", + " - math.sqrt(102) * y_7_3 * z\n", + " + math.sqrt(1122) * y_7_4 * y\n", + " + math.sqrt(561) * y_7_5 * z\n", + " + math.sqrt(561) * y_7_9 * x\n", + " )\n", + ")\n", + "y_8_6 = (\n", + " (1 / 16) * math.sqrt(34) * y_7_10 * x\n", + " - 1 / 16 * math.sqrt(34) * y_7_4 * z\n", + " + (1 / 4) * math.sqrt(17) * y_7_5 * y\n", + " + (1 / 16) * math.sqrt(102) * y_7_6 * z\n", + " + (1 / 16) * math.sqrt(102) * y_7_8 * x\n", + ")\n", + "y_8_7 = (\n", + " -1 / 80 * math.sqrt(1190) * y_7_5 * z\n", + " + (1 / 40) * math.sqrt(1785) * y_7_6 * y\n", + " + (1 / 20) * math.sqrt(255) * y_7_7 * x\n", + " + (1 / 80) * math.sqrt(1190) * y_7_9 * x\n", + ")\n", + "y_8_8 = (\n", + " -1 / 60 * math.sqrt(1785) * y_7_6 * x + (1 / 15) * math.sqrt(255) * y_7_7 * y - 1 / 60 * math.sqrt(1785) * y_7_8 * z\n", + ")\n", + "y_8_9 = (\n", + " -1 / 80 * math.sqrt(1190) * y_7_5 * x\n", + " + (1 / 20) * math.sqrt(255) * y_7_7 * z\n", + " + (1 / 40) * math.sqrt(1785) * y_7_8 * y\n", + " - 1 / 80 * math.sqrt(1190) * y_7_9 * z\n", + ")\n", + "y_8_10 = (\n", + " -1 / 16 * math.sqrt(34) * y_7_10 * z\n", + " - 1 / 16 * math.sqrt(34) * y_7_4 * x\n", + " - 1 / 16 * math.sqrt(102) * y_7_6 * x\n", + " + (1 / 16) * math.sqrt(102) * y_7_8 * z\n", + " + (1 / 4) * math.sqrt(17) * y_7_9 * y\n", + ")\n", + "y_8_11 = (\n", + " (1 / 48)\n", + " * math.sqrt(2)\n", + " * (\n", + " math.sqrt(1122) * y_7_10 * y\n", + " - math.sqrt(102) * y_7_11 * z\n", + " - math.sqrt(102) * y_7_3 * x\n", + " - math.sqrt(561) * y_7_5 * x\n", + " + math.sqrt(561) * y_7_9 * z\n", + " )\n", + ")\n", + "y_8_12 = (\n", + " (1 / 40) * math.sqrt(935) * y_7_10 * z\n", + " + (1 / 10) * math.sqrt(85) * y_7_11 * y\n", + " - 1 / 40 * math.sqrt(85) * y_7_12 * z\n", + " - 1 / 40 * math.sqrt(85) * y_7_2 * x\n", + " - 1 / 40 * math.sqrt(935) * y_7_4 * x\n", + ")\n", + "y_8_13 = (\n", + " (1 / 80)\n", + " * math.sqrt(2)\n", + " * (\n", + " -math.sqrt(85) * y_7_1 * x\n", + " + math.sqrt(2210) * y_7_11 * z\n", + " + math.sqrt(2210) * y_7_12 * y\n", + " - math.sqrt(85) * y_7_13 * z\n", + " - math.sqrt(2210) * y_7_3 * x\n", + " )\n", + ")\n", + "y_8_14 = (\n", + " -1 / 240 * math.sqrt(510) * y_7_0 * x\n", + " + (1 / 240) * math.sqrt(46410) * y_7_12 * z\n", + " + (1 / 60) * math.sqrt(1785) * y_7_13 * y\n", + " - 1 / 240 * math.sqrt(510) * y_7_14 * z\n", + " - 1 / 240 * math.sqrt(46410) * y_7_2 * x\n", + ")\n", + "y_8_15 = (\n", + " -1 / 16 * math.sqrt(238) * y_7_1 * x + (1 / 16) * math.sqrt(238) * y_7_13 * z + (1 / 8) * math.sqrt(17) * y_7_14 * y\n", + ")\n", + "y_8_16 = (1 / 4) * math.sqrt(17) * (-y_7_0 * x + y_7_14 * z)\n", + "\n", + "y_9_0 = (1 / 6) * math.sqrt(38) * (y_8_0 * z + y_8_16 * x)\n", + "y_9_1 = (1 / 9) * math.sqrt(19) * (y_8_0 * y + 2 * y_8_1 * z + 2 * y_8_15 * x)\n", + "y_9_2 = (\n", + " -1 / 306 * math.sqrt(646) * y_8_0 * z\n", + " + (4 / 153) * math.sqrt(646) * y_8_1 * y\n", + " + (2 / 153) * math.sqrt(4845) * y_8_14 * x\n", + " + (1 / 306) * math.sqrt(646) * y_8_16 * x\n", + " + (2 / 153) * math.sqrt(4845) * y_8_2 * z\n", + ")\n", + "y_9_3 = (\n", + " -1 / 306 * math.sqrt(1938) * y_8_1 * z\n", + " + (1 / 306) * math.sqrt(67830) * y_8_13 * x\n", + " + (1 / 306) * math.sqrt(1938) * y_8_15 * x\n", + " + (1 / 51) * math.sqrt(1615) * y_8_2 * y\n", + " + (1 / 306) * math.sqrt(67830) * y_8_3 * z\n", + ")\n", + "y_9_4 = (\n", + " (1 / 306) * math.sqrt(58786) * y_8_12 * x\n", + " + (1 / 153) * math.sqrt(969) * y_8_14 * x\n", + " - 1 / 153 * math.sqrt(969) * y_8_2 * z\n", + " + (2 / 153) * math.sqrt(4522) * y_8_3 * y\n", + " + (1 / 306) * math.sqrt(58786) * y_8_4 * z\n", + ")\n", + "y_9_5 = (\n", + " (1 / 153) * math.sqrt(12597) * y_8_11 * x\n", + " + (1 / 153) * math.sqrt(1615) * y_8_13 * x\n", + " - 1 / 153 * math.sqrt(1615) * y_8_3 * z\n", + " + (1 / 153) * math.sqrt(20995) * y_8_4 * y\n", + " + (1 / 153) * math.sqrt(12597) * y_8_5 * z\n", + ")\n", + "y_9_6 = (\n", + " (1 / 153) * math.sqrt(10659) * y_8_10 * x\n", + " + (1 / 306) * math.sqrt(9690) * y_8_12 * x\n", + " - 1 / 306 * math.sqrt(9690) * y_8_4 * z\n", + " + (2 / 51) * math.sqrt(646) * y_8_5 * y\n", + " + (1 / 153) * math.sqrt(10659) * y_8_6 * z\n", + ")\n", + "y_9_7 = (\n", + " (1 / 306) * math.sqrt(13566) * y_8_11 * x\n", + " - 1 / 306 * math.sqrt(13566) * y_8_5 * z\n", + " + (1 / 153) * math.sqrt(24871) * y_8_6 * y\n", + " + (1 / 306) * math.sqrt(35530) * y_8_7 * z\n", + " + (1 / 306) * math.sqrt(35530) * y_8_9 * x\n", + ")\n", + "y_9_8 = (\n", + " (1 / 153) * math.sqrt(4522) * y_8_10 * x\n", + " - 1 / 153 * math.sqrt(4522) * y_8_6 * z\n", + " + (4 / 153) * math.sqrt(1615) * y_8_7 * y\n", + " + (1 / 51) * math.sqrt(1615) * y_8_8 * x\n", + ")\n", + "y_9_9 = (1 / 51) * math.sqrt(323) * (-2 * y_8_7 * x + 3 * y_8_8 * y - 2 * y_8_9 * z)\n", + "y_9_10 = (\n", + " -1 / 153 * math.sqrt(4522) * y_8_10 * z\n", + " - 1 / 153 * math.sqrt(4522) * y_8_6 * x\n", + " + (1 / 51) * math.sqrt(1615) * y_8_8 * z\n", + " + (4 / 153) * math.sqrt(1615) * y_8_9 * y\n", + ")\n", + "y_9_11 = (\n", + " (1 / 153) * math.sqrt(24871) * y_8_10 * y\n", + " - 1 / 306 * math.sqrt(13566) * y_8_11 * z\n", + " - 1 / 306 * math.sqrt(13566) * y_8_5 * x\n", + " - 1 / 306 * math.sqrt(35530) * y_8_7 * x\n", + " + (1 / 306) * math.sqrt(35530) * y_8_9 * z\n", + ")\n", + "y_9_12 = (\n", + " (1 / 153) * math.sqrt(10659) * y_8_10 * z\n", + " + (2 / 51) * math.sqrt(646) * y_8_11 * y\n", + " - 1 / 306 * math.sqrt(9690) * y_8_12 * z\n", + " - 1 / 306 * math.sqrt(9690) * y_8_4 * x\n", + " - 1 / 153 * math.sqrt(10659) * y_8_6 * x\n", + ")\n", + "y_9_13 = (\n", + " (1 / 153) * math.sqrt(12597) * y_8_11 * z\n", + " + (1 / 153) * math.sqrt(20995) * y_8_12 * y\n", + " - 1 / 153 * math.sqrt(1615) * y_8_13 * z\n", + " - 1 / 153 * math.sqrt(1615) * y_8_3 * x\n", + " - 1 / 153 * math.sqrt(12597) * y_8_5 * x\n", + ")\n", + "y_9_14 = (\n", + " (1 / 306) * math.sqrt(58786) * y_8_12 * z\n", + " + (2 / 153) * math.sqrt(4522) * y_8_13 * y\n", + " - 1 / 153 * math.sqrt(969) * y_8_14 * z\n", + " - 1 / 153 * math.sqrt(969) * y_8_2 * x\n", + " - 1 / 306 * math.sqrt(58786) * y_8_4 * x\n", + ")\n", + "y_9_15 = (\n", + " -1 / 306 * math.sqrt(1938) * y_8_1 * x\n", + " + (1 / 306) * math.sqrt(67830) * y_8_13 * z\n", + " + (1 / 51) * math.sqrt(1615) * y_8_14 * y\n", + " - 1 / 306 * math.sqrt(1938) * y_8_15 * z\n", + " - 1 / 306 * math.sqrt(67830) * y_8_3 * x\n", + ")\n", + "y_9_16 = (\n", + " -1 / 306 * math.sqrt(646) * y_8_0 * x\n", + " + (2 / 153) * math.sqrt(4845) * y_8_14 * z\n", + " + (4 / 153) * math.sqrt(646) * y_8_15 * y\n", + " - 1 / 306 * math.sqrt(646) * y_8_16 * z\n", + " - 2 / 153 * math.sqrt(4845) * y_8_2 * x\n", + ")\n", + "y_9_17 = (1 / 9) * math.sqrt(19) * (-2 * y_8_1 * x + 2 * y_8_15 * z + y_8_16 * y)\n", + "y_9_18 = (1 / 6) * math.sqrt(38) * (-y_8_0 * x + y_8_16 * z)\n", + "\n", + "y_10_0 = (1 / 10) * math.sqrt(105) * (y_9_0 * z + y_9_18 * x)\n", + "y_10_1 = (\n", + " (1 / 10) * math.sqrt(21) * y_9_0 * y + (3 / 20) * math.sqrt(42) * y_9_1 * z + (3 / 20) * math.sqrt(42) * y_9_17 * x\n", + ")\n", + "y_10_2 = (\n", + " -1 / 380 * math.sqrt(798) * y_9_0 * z\n", + " + (3 / 95) * math.sqrt(399) * y_9_1 * y\n", + " + (3 / 380) * math.sqrt(13566) * y_9_16 * x\n", + " + (1 / 380) * math.sqrt(798) * y_9_18 * x\n", + " + (3 / 380) * math.sqrt(13566) * y_9_2 * z\n", + ")\n", + "y_10_3 = (\n", + " -3 / 380 * math.sqrt(266) * y_9_1 * z\n", + " + (1 / 95) * math.sqrt(6783) * y_9_15 * x\n", + " + (3 / 380) * math.sqrt(266) * y_9_17 * x\n", + " + (3 / 190) * math.sqrt(2261) * y_9_2 * y\n", + " + (1 / 95) * math.sqrt(6783) * y_9_3 * z\n", + ")\n", + "y_10_4 = (\n", + " (3 / 95) * math.sqrt(665) * y_9_14 * x\n", + " + (3 / 190) * math.sqrt(133) * y_9_16 * x\n", + " - 3 / 190 * math.sqrt(133) * y_9_2 * z\n", + " + (4 / 95) * math.sqrt(399) * y_9_3 * y\n", + " + (3 / 95) * math.sqrt(665) * y_9_4 * z\n", + ")\n", + "y_10_5 = (\n", + " (21 / 380) * math.sqrt(190) * y_9_13 * x\n", + " + (1 / 190) * math.sqrt(1995) * y_9_15 * x\n", + " - 1 / 190 * math.sqrt(1995) * y_9_3 * z\n", + " + (3 / 38) * math.sqrt(133) * y_9_4 * y\n", + " + (21 / 380) * math.sqrt(190) * y_9_5 * z\n", + ")\n", + "y_10_6 = (\n", + " (7 / 380) * math.sqrt(1482) * y_9_12 * x\n", + " + (3 / 380) * math.sqrt(1330) * y_9_14 * x\n", + " - 3 / 380 * math.sqrt(1330) * y_9_4 * z\n", + " + (21 / 95) * math.sqrt(19) * y_9_5 * y\n", + " + (7 / 380) * math.sqrt(1482) * y_9_6 * z\n", + ")\n", + "y_10_7 = (\n", + " (3 / 190) * math.sqrt(1729) * y_9_11 * x\n", + " + (21 / 380) * math.sqrt(38) * y_9_13 * x\n", + " - 21 / 380 * math.sqrt(38) * y_9_5 * z\n", + " + (7 / 190) * math.sqrt(741) * y_9_6 * y\n", + " + (3 / 190) * math.sqrt(1729) * y_9_7 * z\n", + ")\n", + "y_10_8 = (\n", + " (3 / 190) * math.sqrt(1463) * y_9_10 * x\n", + " + (7 / 190) * math.sqrt(114) * y_9_12 * x\n", + " - 7 / 190 * math.sqrt(114) * y_9_6 * z\n", + " + (6 / 95) * math.sqrt(266) * y_9_7 * y\n", + " + (3 / 190) * math.sqrt(1463) * y_9_8 * z\n", + ")\n", + "y_10_9 = (\n", + " (3 / 190) * math.sqrt(798) * y_9_11 * x\n", + " - 3 / 190 * math.sqrt(798) * y_9_7 * z\n", + " + (3 / 190) * math.sqrt(4389) * y_9_8 * y\n", + " + (1 / 190) * math.sqrt(21945) * y_9_9 * x\n", + ")\n", + "y_10_10 = (\n", + " -3 / 190 * math.sqrt(1995) * y_9_10 * z\n", + " - 3 / 190 * math.sqrt(1995) * y_9_8 * x\n", + " + (1 / 19) * math.sqrt(399) * y_9_9 * y\n", + ")\n", + "y_10_11 = (\n", + " (3 / 190) * math.sqrt(4389) * y_9_10 * y\n", + " - 3 / 190 * math.sqrt(798) * y_9_11 * z\n", + " - 3 / 190 * math.sqrt(798) * y_9_7 * x\n", + " + (1 / 190) * math.sqrt(21945) * y_9_9 * z\n", + ")\n", + "y_10_12 = (\n", + " (3 / 190) * math.sqrt(1463) * y_9_10 * z\n", + " + (6 / 95) * math.sqrt(266) * y_9_11 * y\n", + " - 7 / 190 * math.sqrt(114) * y_9_12 * z\n", + " - 7 / 190 * math.sqrt(114) * y_9_6 * x\n", + " - 3 / 190 * math.sqrt(1463) * y_9_8 * x\n", + ")\n", + "y_10_13 = (\n", + " (3 / 190) * math.sqrt(1729) * y_9_11 * z\n", + " + (7 / 190) * math.sqrt(741) * y_9_12 * y\n", + " - 21 / 380 * math.sqrt(38) * y_9_13 * z\n", + " - 21 / 380 * math.sqrt(38) * y_9_5 * x\n", + " - 3 / 190 * math.sqrt(1729) * y_9_7 * x\n", + ")\n", + "y_10_14 = (\n", + " (7 / 380) * math.sqrt(1482) * y_9_12 * z\n", + " + (21 / 95) * math.sqrt(19) * y_9_13 * y\n", + " - 3 / 380 * math.sqrt(1330) * y_9_14 * z\n", + " - 3 / 380 * math.sqrt(1330) * y_9_4 * x\n", + " - 7 / 380 * math.sqrt(1482) * y_9_6 * x\n", + ")\n", + "y_10_15 = (\n", + " (21 / 380) * math.sqrt(190) * y_9_13 * z\n", + " + (3 / 38) * math.sqrt(133) * y_9_14 * y\n", + " - 1 / 190 * math.sqrt(1995) * y_9_15 * z\n", + " - 1 / 190 * math.sqrt(1995) * y_9_3 * x\n", + " - 21 / 380 * math.sqrt(190) * y_9_5 * x\n", + ")\n", + "y_10_16 = (\n", + " (3 / 95) * math.sqrt(665) * y_9_14 * z\n", + " + (4 / 95) * math.sqrt(399) * y_9_15 * y\n", + " - 3 / 190 * math.sqrt(133) * y_9_16 * z\n", + " - 3 / 190 * math.sqrt(133) * y_9_2 * x\n", + " - 3 / 95 * math.sqrt(665) * y_9_4 * x\n", + ")\n", + "y_10_17 = (\n", + " -3 / 380 * math.sqrt(266) * y_9_1 * x\n", + " + (1 / 95) * math.sqrt(6783) * y_9_15 * z\n", + " + (3 / 190) * math.sqrt(2261) * y_9_16 * y\n", + " - 3 / 380 * math.sqrt(266) * y_9_17 * z\n", + " - 1 / 95 * math.sqrt(6783) * y_9_3 * x\n", + ")\n", + "y_10_18 = (\n", + " -1 / 380 * math.sqrt(798) * y_9_0 * x\n", + " + (3 / 380) * math.sqrt(13566) * y_9_16 * z\n", + " + (3 / 95) * math.sqrt(399) * y_9_17 * y\n", + " - 1 / 380 * math.sqrt(798) * y_9_18 * z\n", + " - 3 / 380 * math.sqrt(13566) * y_9_2 * x\n", + ")\n", + "y_10_19 = (\n", + " -3 / 20 * math.sqrt(42) * y_9_1 * x + (3 / 20) * math.sqrt(42) * y_9_17 * z + (1 / 10) * math.sqrt(21) * y_9_18 * y\n", + ")\n", + "y_10_20 = (1 / 10) * math.sqrt(105) * (-y_9_0 * x + y_9_18 * z)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "fae57ecb-2ef5-43f5-a19b-fedb24d9f491", + "metadata": {}, + "outputs": [], + "source": [ + "nsimplify = partial(sympy.nsimplify, tolerance=1e-12, rational=True)\n", + "\n", + "def replace_floating_integers(expr):\n", + " \"\"\"Dumb, but straight forward way to replace floats that should be integers.\"\"\"\n", + " replace_dict = {sympy.Float(value): sympy.Integer(value) for value in range(1, 15)}\n", + " return expr.subs(replace_dict)\n", + "\n", + "\n", + "def optimization_chain(expr):\n", + " opt_chain = [collect_sqrt, collect_const, nsimplify]\n", + " for func in opt_chain:\n", + " expr = func(expr)\n", + " return expr.n()\n", + "\n", + "\n", + "def make_eval_expr(expr, **kwargs):\n", + " \"\"\"\n", + " For a given expression, prepare it for implementation by\n", + " running it through a gambit of manipulations/optimizations,\n", + " particularly in terms of collecting like variables and replacing\n", + " floating point symbols with integer ones if they are numerically\n", + " equivalent (i.e. 2.0 -> 2).\n", + "\n", + " The main computationally intensive part is a loop over various\n", + " possible combinations of xyz collections: some may lead to\n", + " fewer arithmetic operations, and the loop works to find the\n", + " combination with the fewest.\n", + " \"\"\"\n", + " new_expr = expr.expand(func=True).simplify(**kwargs)\n", + " # first thing to do is to replace floats that are actually\n", + " # integers\n", + " new_expr = replace_floating_integers(new_expr)\n", + " # collect terms differently, minimizing number of operations\n", + " combos = chain(\n", + " combinations([x, y, z], 1),\n", + " combinations([x, y, z], 2),\n", + " combinations([x, y, z], 3)\n", + " )\n", + " best_solution = new_expr\n", + " best_num_ops = sympy.count_ops(best_solution)\n", + " for combo in combos:\n", + " # runs a sequence of chained collections to try and\n", + " # minimize the number of operations\n", + " temp = optimization_chain(\n", + " sympy.collect(new_expr, combo)\n", + " )\n", + " # count the number of computational operations we perform\n", + " counts = sympy.count_ops(temp)\n", + " # if we end up with fewer ops, go for it\n", + " if counts < best_num_ops:\n", + " best_solution = temp\n", + " best_num_ops = counts\n", + " return best_solution\n", + "\n", + "\n", + "def take_derivative(expr, symbols: list[Symbol], optimize: bool = True):\n", + " \"\"\"\n", + " Function to take the derivative of a symbolic equation with respect\n", + " to a list of symbols.\n", + "\n", + " We loop through each symbol, and if it is used in the equation,\n", + " we take the first derivative with respect to that function.\n", + " \"\"\"\n", + " return_dict = {}\n", + " for symbol in symbols:\n", + " if symbol in expr.free_symbols:\n", + " deriv = diff(expr, symbol)\n", + " if simplify:\n", + " deriv = optimization_chain(deriv)\n", + " return_dict[str(symbol)] = deriv\n", + " if len(return_dict) == 0:\n", + " raise RuntimeError(\"None of the requested symbols were used in the expression!\")\n", + " return return_dict\n", + "\n", + "\n", + "@cache\n", + "def driver(*expressions):\n", + " \"\"\"\n", + " Creates the expected data structure, generates the optimized forward\n", + " pass code, computes derivatives, and aggregates them to be in\n", + " terms of xyz for ease of implementation.\n", + " \"\"\"\n", + " outputs = {\"fwd\": [], \"bwd\": {\"x\": 0, \"y\": 0, \"z\": 0}}\n", + " for index, expr in enumerate(expressions):\n", + " fwd = make_eval_expr(expr, ratio=1.5)\n", + " outputs[\"fwd\"].append(fwd)\n", + " bwd = take_derivative(fwd, [x, y, z], optimize=True)\n", + " grad_term = symbols(f\"g_{index}\")\n", + " # collect up all the terms for the backward pass w.r.t.\n", + " # axes, with the corresponding gradient term in the forward\n", + " for key in [\"x\", \"y\", \"z\"]:\n", + " if key in bwd:\n", + " outputs[\"bwd\"][key] += bwd[key] * grad_term\n", + " # now do a final optimization on the derivatives\n", + " for key in [\"x\", \"y\", \"z\"]:\n", + " outputs[\"bwd\"][key] = optimization_chain(outputs[\"bwd\"][key])\n", + " return outputs\n", + "\n", + "\n", + "def wrapper(n: int):\n", + " \"\"\"\n", + " High level function that will take the symbolic e3nn expressions and\n", + " run them through an optimization chain to obtain any of the spherical\n", + " harmonics directly as functions of xyz.\n", + "\n", + " Also computes the derivative, running that also through the optimization chain.\n", + " \"\"\"\n", + " num_terms = 2 * n + 1\n", + " return driver(*[eval(f\"y_{n}_{m}\") for m in range(num_terms)])" + ] + }, + { + "cell_type": "markdown", + "id": "53ce9de1-8bdb-4922-ba77-707af4a6b6d8", + "metadata": {}, + "source": [ + "### Generating the expressions _en masse_\n", + "\n", + "The `driver` function that's called by `wrapper` is cached, which means the first time will take a hot minute but subsequent calls with the same `n` value should be free." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "bc287731-f493-4e58-b10e-a45daebbaf17", + "metadata": {}, + "outputs": [], + "source": [ + "second_order_expressions = wrapper(2)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "6cd00e51-8b97-4ddc-a95d-f9037ae4535d", + "metadata": {}, + "outputs": [], + "source": [ + "third_order_expressions = wrapper(3)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "fe8c8edb-6672-49c0-ac93-46c97b31e9e5", + "metadata": {}, + "outputs": [], + "source": [ + "fourth_order_expressions = wrapper(4)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "1cf41f26-2562-4625-a9e4-8b0d86be6de4", + "metadata": {}, + "outputs": [], + "source": [ + "fifth_order_expressions = wrapper(5)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "5e996499-c56b-44bd-aaee-d796347764bd", + "metadata": {}, + "outputs": [], + "source": [ + "sixth_order_expressions = wrapper(6)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "9b6d7ca3-a427-4ff9-a657-a202623ba1bc", + "metadata": {}, + "outputs": [], + "source": [ + "seventh_order_expressions = wrapper(7)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "429fd2f5-8e13-46f5-a4eb-9cae1419568a", + "metadata": {}, + "outputs": [], + "source": [ + "eighth_order_expressions = wrapper(8)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "d37250a7-ece0-4c3e-88bf-0c987ab9ce5e", + "metadata": {}, + "outputs": [], + "source": [ + "ninth_order_expressions = wrapper(9)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "f5b04e7e-fec5-4871-987a-2951526f6380", + "metadata": {}, + "outputs": [], + "source": [ + "tenth_order_expressions = wrapper(10)" + ] + }, + { + "cell_type": "markdown", + "id": "f323ee83-1a65-4f45-a524-9bee84cd9443", + "metadata": {}, + "source": [ + "## Numerical checks with `e3nn`" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "2a32d49e-7346-401e-99a0-606e89bd6eba", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([[1.0000, 1.7321, 1.7321, 1.7321, 3.8730, 3.8730, 0.0000, 3.8730, 0.0000]])" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# second order\n", + "_spherical_harmonics(2, test_tensor[:,0], test_tensor[:,1], test_tensor[:,2])" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "edc9c855-c4b7-482f-ad9d-494d03c4e7ef", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle 0$" + ], + "text/plain": [ + "0" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# compare each of the forward terms with above\n", + "second_order_expressions[\"fwd\"][-1].subs({\"x\": 1., \"y\": 1., \"z\": 1.})" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "9fabc07f-2de0-4c5b-9d6d-2c00e88c71f7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([[ 1.0000, 1.7321, 1.7321, 1.7321, 3.8730, 3.8730, 0.0000, 3.8730,\n", + " 0.0000, 4.1833, 10.2470, 3.2404, -5.2915, 3.2404, 0.0000, -4.1833]])" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# third order\n", + "_spherical_harmonics(3, test_tensor[:,0], test_tensor[:,1], test_tensor[:,2])" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "005edb58-51d0-4acb-9038-74d5f54ea375", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle -5.29150262212918$" + ], + "text/plain": [ + "-5.29150262212918" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "third_order_expressions[\"fwd\"][3].subs({\"x\": 1., \"y\": 1., \"z\": 1.})" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "f907e089-8ab7-4b8a-b766-dc3a69a3d7ea", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([[ 1.0000e+00, 1.7321e+00, 1.7321e+00, 1.7321e+00, 3.8730e+00,\n", + " 3.8730e+00, 0.0000e+00, 3.8730e+00, 0.0000e+00, 4.1833e+00,\n", + " 1.0247e+01, 3.2404e+00, -5.2915e+00, 3.2404e+00, 0.0000e+00,\n", + " -4.1833e+00, 0.0000e+00, 1.2550e+01, 1.3416e+01, -4.7434e+00,\n", + " -1.0500e+01, -4.7434e+00, -5.9605e-08, -1.2550e+01, -8.8741e+00]])" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# fourth order\n", + "_spherical_harmonics(4, test_tensor[:,0], test_tensor[:,1], test_tensor[:,2])" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "29a82d20-e9f9-40ce-a70c-963569439cf3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle -8.87411967464942$" + ], + "text/plain": [ + "-8.87411967464942" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fourth_order_expressions[\"fwd\"][-1].subs({\"x\": 1., \"y\": 1., \"z\": 1.})" + ] + }, + { + "cell_type": "markdown", + "id": "a08d5640-8b8d-48f1-86cf-21eeae568001", + "metadata": {}, + "source": [ + "## Operations count comparison" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "bc255e1f-b274-4010-8699-0fbe7049d7db", + "metadata": {}, + "outputs": [], + "source": [ + "def count_operations_per_n(n: int):\n", + " projections = range(2 * n + 1)\n", + " e3nn_impl = [sympy.count_ops(eval(f\"y_{n}_{m}\")) for m in projections]\n", + " direct = wrapper(n)[\"fwd\"]\n", + " direct_counts = [sympy.count_ops(expr) for expr in direct]\n", + " return e3nn_impl, direct_counts" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "68340cc8-c716-414f-8bf2-48de8976ea17", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from matplotlib import pyplot as plt\n", + "from matplotlib.lines import Line2D" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "a98325e2-8738-4f52-a02f-b581bf0ec944", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = plt.subplots()\n", + "\n", + "for n in range(2, 11):\n", + " num_projections = 2 * n + 1\n", + " x_points = np.ones(num_projections) * n\n", + " e3nn_impl, direct_counts = count_operations_per_n(n)\n", + " ax.scatter(x_points, e3nn_impl, label=\"e3nn\", facecolor=\"#d33c25\", edgecolor=\"k\", s=30., lw=0.5)\n", + " ax.scatter(x_points, direct_counts, label=\"EquiTriton\", edgecolor=\"k\", facecolor=\"#4a7cb6\", s=30., lw=0.5,)\n", + "ax.set(yscale=\"log\", xlabel=\"$L_{max}$\", ylabel=\"# of arithmetic operations\")\n", + "\n", + "leg = [\n", + " Line2D([0], [0], marker=\"o\", color=\"w\", markerfacecolor=\"#d33c25\", markersize=15., label=\"e3nn\"),\n", + " Line2D([0], [0], marker=\"o\", color=\"w\", markerfacecolor=\"#4a7cb6\", markersize=15., label=\"EquiTriton\")\n", + "]\n", + "ax.legend(handles=leg, ncols=2, frameon=False)\n", + "fig.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "7c4bb376-60fb-4a91-b941-21f43eb76161", + "metadata": {}, + "outputs": [], + "source": [ + "fig.savefig(\"equitriton_algorithmic_scaling.png\", dpi=150)" + ] + }, + { + "cell_type": "markdown", + "id": "c5b89738-0f3c-4dbe-84f1-b4c1851a7924", + "metadata": {}, + "source": [ + "## Dumping expressions to JSON\n", + "\n", + "Helps with implementation and just to have a nice record of them outside the notebook." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "257b5427-64b5-4eeb-a151-fa32099e646f", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import os\n", + "import string" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "dbaee485-0654-461a-8436-b9a2c673907c", + "metadata": {}, + "outputs": [], + "source": [ + "def write_expressions_to_json(expr_set, n: int):\n", + " os.makedirs(\"direct_sph_harm\", exist_ok=True)\n", + " write_dict = {}\n", + " write_dict[\"fwd\"] = [str(expr) for expr in expr_set[\"fwd\"]]\n", + " write_dict[\"bwd\"] = {axis: str(expr) for axis, expr in expr_set[\"bwd\"].items()}\n", + " with open(f\"direct_sph_harm/l_{n}.json\", \"w+\") as write_file:\n", + " json.dump(write_dict, write_file, indent=2)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "aabda44e-e1a1-4ef7-bb4d-2a37884ba38d", + "metadata": {}, + "outputs": [], + "source": [ + "for e, n in zip(\n", + " [second_order_expressions, third_order_expressions, fourth_order_expressions, fifth_order_expressions, sixth_order_expressions, seventh_order_expressions, eighth_order_expressions, ninth_order_expressions, tenth_order_expressions], range(2, 11)\n", + "):\n", + " write_expressions_to_json(e, n)" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "c72e6e01-92c6-4d08-8c70-eeb2708bc8d9", + "metadata": {}, + "outputs": [], + "source": [ + "def collect_symbols(expr, agg_set):\n", + " if len(expr.args) != 0 and not isinstance(expr, sympy.Pow):\n", + " for arg in expr.args:\n", + " collect_symbols(arg, agg_set)\n", + " else:\n", + " agg_set.add(expr)" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "3d837ed4-bb65-4843-9f20-7f19978b0586", + "metadata": {}, + "outputs": [], + "source": [ + "def generate_fwd_implementation(exprs):\n", + " variable_set = set()\n", + " for expr in exprs:\n", + " collect_symbols(expr, variable_set)\n", + " mapping = {}\n", + " const_counter = 0\n", + " char_counter = 0\n", + " for sym in variable_set:\n", + " if isinstance(sym, (sympy.Float, sympy.Integer)):\n", + " varname = f\"CONST{const_counter:03}\"\n", + " const_counter += 1\n", + " else:\n", + " varname = string.ascii_uppercase[char_counter]\n", + " char_counter += 1\n", + " mapping[sym] = varname\n", + " mapping = dict(sorted(mapping.items(), key=lambda x: x[1]))\n", + " fmt_string = \"# -------------------- variable and constant definitions\\n\"\n", + " for sym, char in mapping.items():\n", + " fmt_string += f\"{char} = {sym}\\n\"\n", + " # now generate the actual kernels\n", + " fmt_string += \"# -------------------- kernel implementations\\n\"\n", + " term_counter = 0\n", + " for index, kernel in enumerate(exprs):\n", + " fmt_string += f\"Y{index:02} = {kernel.subs(mapping)}\\n\"\n", + " print(fmt_string)" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "6685cbaf-0098-41e2-ace7-9d8d92ded1de", + "metadata": {}, + "outputs": [ + { + "ename": "SympifyError", + "evalue": "SympifyError: ", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mSympifyError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[50], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mgenerate_fwd_implementation\u001b[49m\u001b[43m(\u001b[49m\u001b[43msixth_order_expressions\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mfwd\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m5\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m]\u001b[49m\u001b[43m)\u001b[49m\n", + "Cell \u001b[0;32mIn[38], line 24\u001b[0m, in \u001b[0;36mgenerate_fwd_implementation\u001b[0;34m(exprs)\u001b[0m\n\u001b[1;32m 22\u001b[0m term_counter \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m0\u001b[39m\n\u001b[1;32m 23\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m index, kernel \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(exprs):\n\u001b[0;32m---> 24\u001b[0m fmt_string \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mY\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mindex\u001b[38;5;132;01m:\u001b[39;00m\u001b[38;5;124m02\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m = \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[43mkernel\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msubs\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmapping\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 25\u001b[0m \u001b[38;5;28mprint\u001b[39m(fmt_string)\n", + "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/basic.py:1123\u001b[0m, in \u001b[0;36mBasic.subs\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1121\u001b[0m rv \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\n\u001b[1;32m 1122\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m old, new \u001b[38;5;129;01min\u001b[39;00m sequence:\n\u001b[0;32m-> 1123\u001b[0m rv \u001b[38;5;241m=\u001b[39m \u001b[43mrv\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_subs\u001b[49m\u001b[43m(\u001b[49m\u001b[43mold\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnew\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1124\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(rv, Basic):\n\u001b[1;32m 1125\u001b[0m \u001b[38;5;28;01mbreak\u001b[39;00m\n", + "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/cache.py:72\u001b[0m, in \u001b[0;36m__cacheit..func_wrapper..wrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 69\u001b[0m \u001b[38;5;129m@wraps\u001b[39m(func)\n\u001b[1;32m 70\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mwrapper\u001b[39m(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 71\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m---> 72\u001b[0m retval \u001b[38;5;241m=\u001b[39m \u001b[43mcfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 73\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 74\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m e\u001b[38;5;241m.\u001b[39margs \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m e\u001b[38;5;241m.\u001b[39margs[\u001b[38;5;241m0\u001b[39m]\u001b[38;5;241m.\u001b[39mstartswith(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124munhashable type:\u001b[39m\u001b[38;5;124m'\u001b[39m):\n", + "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/basic.py:1237\u001b[0m, in \u001b[0;36mBasic._subs\u001b[0;34m(self, old, new, **hints)\u001b[0m\n\u001b[1;32m 1235\u001b[0m rv \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_eval_subs(old, new)\n\u001b[1;32m 1236\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m rv \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m-> 1237\u001b[0m rv \u001b[38;5;241m=\u001b[39m \u001b[43mfallback\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mold\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnew\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1238\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m rv\n", + "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/basic.py:1209\u001b[0m, in \u001b[0;36mBasic._subs..fallback\u001b[0;34m(self, old, new)\u001b[0m\n\u001b[1;32m 1207\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mhasattr\u001b[39m(arg, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124m_eval_subs\u001b[39m\u001b[38;5;124m'\u001b[39m):\n\u001b[1;32m 1208\u001b[0m \u001b[38;5;28;01mcontinue\u001b[39;00m\n\u001b[0;32m-> 1209\u001b[0m arg \u001b[38;5;241m=\u001b[39m \u001b[43marg\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_subs\u001b[49m\u001b[43m(\u001b[49m\u001b[43mold\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnew\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mhints\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1210\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m _aresame(arg, args[i]):\n\u001b[1;32m 1211\u001b[0m hit \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n", + "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/cache.py:72\u001b[0m, in \u001b[0;36m__cacheit..func_wrapper..wrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 69\u001b[0m \u001b[38;5;129m@wraps\u001b[39m(func)\n\u001b[1;32m 70\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mwrapper\u001b[39m(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 71\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m---> 72\u001b[0m retval \u001b[38;5;241m=\u001b[39m \u001b[43mcfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 73\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 74\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m e\u001b[38;5;241m.\u001b[39margs \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m e\u001b[38;5;241m.\u001b[39margs[\u001b[38;5;241m0\u001b[39m]\u001b[38;5;241m.\u001b[39mstartswith(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124munhashable type:\u001b[39m\u001b[38;5;124m'\u001b[39m):\n", + "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/basic.py:1237\u001b[0m, in \u001b[0;36mBasic._subs\u001b[0;34m(self, old, new, **hints)\u001b[0m\n\u001b[1;32m 1235\u001b[0m rv \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_eval_subs(old, new)\n\u001b[1;32m 1236\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m rv \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m-> 1237\u001b[0m rv \u001b[38;5;241m=\u001b[39m \u001b[43mfallback\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mold\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnew\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1238\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m rv\n", + "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/basic.py:1209\u001b[0m, in \u001b[0;36mBasic._subs..fallback\u001b[0;34m(self, old, new)\u001b[0m\n\u001b[1;32m 1207\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mhasattr\u001b[39m(arg, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124m_eval_subs\u001b[39m\u001b[38;5;124m'\u001b[39m):\n\u001b[1;32m 1208\u001b[0m \u001b[38;5;28;01mcontinue\u001b[39;00m\n\u001b[0;32m-> 1209\u001b[0m arg \u001b[38;5;241m=\u001b[39m \u001b[43marg\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_subs\u001b[49m\u001b[43m(\u001b[49m\u001b[43mold\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnew\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mhints\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1210\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m _aresame(arg, args[i]):\n\u001b[1;32m 1211\u001b[0m hit \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n", + "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/cache.py:72\u001b[0m, in \u001b[0;36m__cacheit..func_wrapper..wrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 69\u001b[0m \u001b[38;5;129m@wraps\u001b[39m(func)\n\u001b[1;32m 70\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mwrapper\u001b[39m(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 71\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m---> 72\u001b[0m retval \u001b[38;5;241m=\u001b[39m \u001b[43mcfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 73\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 74\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m e\u001b[38;5;241m.\u001b[39margs \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m e\u001b[38;5;241m.\u001b[39margs[\u001b[38;5;241m0\u001b[39m]\u001b[38;5;241m.\u001b[39mstartswith(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124munhashable type:\u001b[39m\u001b[38;5;124m'\u001b[39m):\n", + "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/basic.py:1237\u001b[0m, in \u001b[0;36mBasic._subs\u001b[0;34m(self, old, new, **hints)\u001b[0m\n\u001b[1;32m 1235\u001b[0m rv \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_eval_subs(old, new)\n\u001b[1;32m 1236\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m rv \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m-> 1237\u001b[0m rv \u001b[38;5;241m=\u001b[39m \u001b[43mfallback\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mold\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnew\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1238\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m rv\n", + "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/basic.py:1209\u001b[0m, in \u001b[0;36mBasic._subs..fallback\u001b[0;34m(self, old, new)\u001b[0m\n\u001b[1;32m 1207\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mhasattr\u001b[39m(arg, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124m_eval_subs\u001b[39m\u001b[38;5;124m'\u001b[39m):\n\u001b[1;32m 1208\u001b[0m \u001b[38;5;28;01mcontinue\u001b[39;00m\n\u001b[0;32m-> 1209\u001b[0m arg \u001b[38;5;241m=\u001b[39m \u001b[43marg\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_subs\u001b[49m\u001b[43m(\u001b[49m\u001b[43mold\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnew\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mhints\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1210\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m _aresame(arg, args[i]):\n\u001b[1;32m 1211\u001b[0m hit \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n", + "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/cache.py:72\u001b[0m, in \u001b[0;36m__cacheit..func_wrapper..wrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 69\u001b[0m \u001b[38;5;129m@wraps\u001b[39m(func)\n\u001b[1;32m 70\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mwrapper\u001b[39m(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 71\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m---> 72\u001b[0m retval \u001b[38;5;241m=\u001b[39m \u001b[43mcfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 73\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 74\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m e\u001b[38;5;241m.\u001b[39margs \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m e\u001b[38;5;241m.\u001b[39margs[\u001b[38;5;241m0\u001b[39m]\u001b[38;5;241m.\u001b[39mstartswith(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124munhashable type:\u001b[39m\u001b[38;5;124m'\u001b[39m):\n", + "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/basic.py:1237\u001b[0m, in \u001b[0;36mBasic._subs\u001b[0;34m(self, old, new, **hints)\u001b[0m\n\u001b[1;32m 1235\u001b[0m rv \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_eval_subs(old, new)\n\u001b[1;32m 1236\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m rv \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m-> 1237\u001b[0m rv \u001b[38;5;241m=\u001b[39m \u001b[43mfallback\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mold\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnew\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1238\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m rv\n", + "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/basic.py:1214\u001b[0m, in \u001b[0;36mBasic._subs..fallback\u001b[0;34m(self, old, new)\u001b[0m\n\u001b[1;32m 1212\u001b[0m args[i] \u001b[38;5;241m=\u001b[39m arg\n\u001b[1;32m 1213\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m hit:\n\u001b[0;32m-> 1214\u001b[0m rv \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1215\u001b[0m hack2 \u001b[38;5;241m=\u001b[39m hints\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mhack2\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[38;5;28;01mFalse\u001b[39;00m)\n\u001b[1;32m 1216\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m hack2 \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mis_Mul \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m rv\u001b[38;5;241m.\u001b[39mis_Mul: \u001b[38;5;66;03m# 2-arg hack\u001b[39;00m\n", + "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/cache.py:72\u001b[0m, in \u001b[0;36m__cacheit..func_wrapper..wrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 69\u001b[0m \u001b[38;5;129m@wraps\u001b[39m(func)\n\u001b[1;32m 70\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mwrapper\u001b[39m(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 71\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m---> 72\u001b[0m retval \u001b[38;5;241m=\u001b[39m \u001b[43mcfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 73\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 74\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m e\u001b[38;5;241m.\u001b[39margs \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m e\u001b[38;5;241m.\u001b[39margs[\u001b[38;5;241m0\u001b[39m]\u001b[38;5;241m.\u001b[39mstartswith(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124munhashable type:\u001b[39m\u001b[38;5;124m'\u001b[39m):\n", + "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/operations.py:57\u001b[0m, in \u001b[0;36mAssocOp.__new__\u001b[0;34m(cls, evaluate, _sympify, *args)\u001b[0m\n\u001b[1;32m 52\u001b[0m \u001b[38;5;129m@cacheit\u001b[39m\n\u001b[1;32m 53\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__new__\u001b[39m(\u001b[38;5;28mcls\u001b[39m, \u001b[38;5;241m*\u001b[39margs, evaluate\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m, _sympify\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m):\n\u001b[1;32m 54\u001b[0m \u001b[38;5;66;03m# Allow faster processing by passing ``_sympify=False``, if all arguments\u001b[39;00m\n\u001b[1;32m 55\u001b[0m \u001b[38;5;66;03m# are already sympified.\u001b[39;00m\n\u001b[1;32m 56\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m _sympify:\n\u001b[0;32m---> 57\u001b[0m args \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mlist\u001b[39m(\u001b[38;5;28mmap\u001b[39m(_sympify_, args))\n\u001b[1;32m 59\u001b[0m \u001b[38;5;66;03m# Disallow non-Expr args in Add/Mul\u001b[39;00m\n\u001b[1;32m 60\u001b[0m typ \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mcls\u001b[39m\u001b[38;5;241m.\u001b[39m_args_type\n", + "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/sympify.py:514\u001b[0m, in \u001b[0;36m_sympify\u001b[0;34m(a)\u001b[0m\n\u001b[1;32m 488\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_sympify\u001b[39m(a):\n\u001b[1;32m 489\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 490\u001b[0m \u001b[38;5;124;03m Short version of :func:`~.sympify` for internal usage for ``__add__`` and\u001b[39;00m\n\u001b[1;32m 491\u001b[0m \u001b[38;5;124;03m ``__eq__`` methods where it is ok to allow some things (like Python\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 512\u001b[0m \n\u001b[1;32m 513\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[0;32m--> 514\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43msympify\u001b[49m\u001b[43m(\u001b[49m\u001b[43ma\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstrict\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/sympify.py:454\u001b[0m, in \u001b[0;36msympify\u001b[0;34m(a, locals, convert_xor, strict, rational, evaluate)\u001b[0m\n\u001b[1;32m 451\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m sympify(\u001b[38;5;28mint\u001b[39m(a))\n\u001b[1;32m 453\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m strict:\n\u001b[0;32m--> 454\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m SympifyError(a)\n\u001b[1;32m 456\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m iterable(a):\n\u001b[1;32m 457\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n", + "\u001b[0;31mSympifyError\u001b[0m: SympifyError: " + ] + } + ], + "source": [ + "generate_fwd_implementation(sixth_order_expressions[\"fwd\"][5:])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22dee377-3e18-44bb-ad7c-621cdb27ded2", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From b5cfb6191ab1b47b7fc2a1ec5c268694cb50d294 Mon Sep 17 00:00:00 2001 From: "Lee, Kin Long Kelvin" Date: Mon, 19 Aug 2024 09:34:19 -0700 Subject: [PATCH 002/116] feat: added utility function to calculate number of parallel blocks --- src/equitriton/utils.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/equitriton/utils.py b/src/equitriton/utils.py index 7760350..0f8e3bb 100644 --- a/src/equitriton/utils.py +++ b/src/equitriton/utils.py @@ -1,9 +1,11 @@ from __future__ import annotations +import math + import torch import triton -__all__ = ["pad_tensor_to_power"] +__all__ = ["pad_tensor_to_power", "calculate_lastdim_num_blocks"] def pad_tensor_to_power( @@ -43,3 +45,30 @@ def pad_tensor_to_power( mask = torch.ones(pad_size, device=joint_tensor.device, dtype=torch.bool) mask[num_nodes:] = False return (joint_tensor, mask) + + +def calculate_lastdim_num_blocks(input_tensor: torch.Tensor, block_size: int) -> int: + """ + Calculate the number of blocks for a tensor, assuming we + stride along the last dimension, and a given block size. + + This function is used to work out the amount of parallel + work that needs to be done, given as the total number of + elements divided by the last dimension stride, and a specified + block size that will then divvy up the work. + + Parameters + ---------- + input_tensor : torch.Tensor + Torch N-d tensor to operate over. + + Returns + ------- + int + Number of blocks of work, given a block size. + """ + # get the stride of the last dimension + stride = input_tensor.stride()[-2] + numel = input_tensor.numel() + total_blocks = math.ceil(numel / stride) + return math.ceil(total_blocks / block_size) From 37d1652e9cbe1b06d56a89f8ee8e4e0d1d39356a Mon Sep 17 00:00:00 2001 From: "Lee, Kin Long Kelvin" Date: Mon, 19 Aug 2024 09:53:26 -0700 Subject: [PATCH 003/116] feat: added unravel index function --- src/equitriton/utils.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/equitriton/utils.py b/src/equitriton/utils.py index 0f8e3bb..fab4456 100644 --- a/src/equitriton/utils.py +++ b/src/equitriton/utils.py @@ -72,3 +72,36 @@ def calculate_lastdim_num_blocks(input_tensor: torch.Tensor, block_size: int) -> numel = input_tensor.numel() total_blocks = math.ceil(numel / stride) return math.ceil(total_blocks / block_size) + + +def unravel_index(tensor: torch.Tensor, index: int) -> tuple[int, ...]: + """ + For a given N-d tensor and a 1D index, work out the corresponding + index tuple for the N-d tensor. + + This is equivalent to the `torch.unravel_index` function, but + makes it a bit more friendlier in terms of Python types. + + Parameters + ---------- + tensor : torch.Tensor + Torch N-D tensor to index. + index : int + 1D index value to map onto an N-tuple, where N + is the dimensionality of the tensor. Must be + greater or equal to zero, and smaller than the + total number of elements. + + Returns + ------- + tuple[int, ...] + An N-tuple of integers corresponding to the + N-d index of the provided index. + """ + # make sure that the index is within bounds + assert 0 <= index < tensor.numel() + indices = [] + for size in reversed(tensor.shape): + indices.append(index % size) + index //= size + return tuple(reversed(indices)) From 1827344005c6178bc02113df56dd2684fce04adc Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Wed, 21 Aug 2024 13:53:41 -0700 Subject: [PATCH 004/116] refactor: modifying num block calculation with matching arithmetic --- src/equitriton/utils.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/equitriton/utils.py b/src/equitriton/utils.py index fab4456..033f934 100644 --- a/src/equitriton/utils.py +++ b/src/equitriton/utils.py @@ -52,6 +52,14 @@ def calculate_lastdim_num_blocks(input_tensor: torch.Tensor, block_size: int) -> Calculate the number of blocks for a tensor, assuming we stride along the last dimension, and a given block size. + The corresponding pointer arithmetic looks like this: + + ```python + block_id = tl.program_id(0) + striding = tl.arange(0, block_size) * stride + offset = (striding + (block_size * stride * block_id)) + ``` + This function is used to work out the amount of parallel work that needs to be done, given as the total number of elements divided by the last dimension stride, and a specified @@ -68,10 +76,10 @@ def calculate_lastdim_num_blocks(input_tensor: torch.Tensor, block_size: int) -> Number of blocks of work, given a block size. """ # get the stride of the last dimension - stride = input_tensor.stride()[-2] + stride = input_tensor.stride(-2) numel = input_tensor.numel() total_blocks = math.ceil(numel / stride) - return math.ceil(total_blocks / block_size) + return total_blocks def unravel_index(tensor: torch.Tensor, index: int) -> tuple[int, ...]: From 5e56f15d9aa9dd9461f685af4662f71a897352ea Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Wed, 21 Aug 2024 15:00:54 -0700 Subject: [PATCH 005/116] feat: added direct second order implementation --- src/equitriton/sph_harm/direct/__init__.py | 0 .../direct/tests/test_direct_sph_harm.py | 58 ++++++++ src/equitriton/sph_harm/direct/y_2.py | 140 ++++++++++++++++++ 3 files changed, 198 insertions(+) create mode 100644 src/equitriton/sph_harm/direct/__init__.py create mode 100644 src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py create mode 100644 src/equitriton/sph_harm/direct/y_2.py diff --git a/src/equitriton/sph_harm/direct/__init__.py b/src/equitriton/sph_harm/direct/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py new file mode 100644 index 0000000..c8accde --- /dev/null +++ b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py @@ -0,0 +1,58 @@ +import pytest +import torch + +from equitriton import __HAS_XPU__, __HAS_CUDA__ +from equitriton.sph_harm.direct import y_2 + + +@pytest.mark.parametrize( + "device", + [ + pytest.param( + "xpu", + marks=pytest.mark.skipif(not __HAS_XPU__, reason="No XPUs available."), + ), + pytest.param( + "cuda", + marks=pytest.mark.skipif( + not __HAS_CUDA__, reason="No CUDA GPUs available." + ), + ), + ], +) +@pytest.mark.parametrize("tensor_shape", [(512, 3), (128, 16, 3), (256, 8, 8, 3)]) +def test_forward_equivalence(device, tensor_shape): + coords = torch.rand(tensor_shape, device=device) + triton_out = y_2.SecondOrderSphericalHarmonic.apply(coords) + torch_out = y_2.torch_second_order_fwd(coords) + assert torch.allclose(triton_out, torch_out, atol=1e-6, rtol=1e-4) + + +@pytest.mark.parametrize( + "device", + [ + pytest.param( + "xpu", + marks=pytest.mark.skipif(not __HAS_XPU__, reason="No XPUs available."), + ), + pytest.param( + "cuda", + marks=pytest.mark.skipif( + not __HAS_CUDA__, reason="No CUDA GPUs available." + ), + ), + ], +) +@pytest.mark.parametrize("tensor_shape", [(512, 3), (128, 16, 3), (256, 8, 8, 3)]) +def test_backward_equivalence(device, tensor_shape): + coords = torch.rand(tensor_shape, device=device, requires_grad=True) + # run with autograd first + torch_out = y_2.torch_second_order_fwd(coords) + torch_out.backward(gradient=torch.ones_like(torch_out)) + torch_grad = coords.grad.clone().detach() + coords.grad.zero_() + # now run the triton result + triton_out = y_2.SecondOrderSphericalHarmonic.apply(coords) + triton_out.backward(gradient=torch.ones_like(triton_out)) + triton_grad = coords.grad.clone().detach() + assert torch.allclose(triton_grad, torch_grad, atol=1e-6, rtol=1e-4) \ No newline at end of file diff --git a/src/equitriton/sph_harm/direct/y_2.py b/src/equitriton/sph_harm/direct/y_2.py new file mode 100644 index 0000000..fd9adeb --- /dev/null +++ b/src/equitriton/sph_harm/direct/y_2.py @@ -0,0 +1,140 @@ +import triton +import torch +from triton import language as tl + +from equitriton.utils import calculate_lastdim_num_blocks + + +class SecondOrderSphericalHarmonic(torch.autograd.Function): + @staticmethod + def forward(ctx, coords: torch.Tensor, mask: torch.Tensor | None = None, block_size: int = 64): + output_tensor = torch.empty( + (*coords.shape[:-1], 5), dtype=coords.dtype, device=coords.device + ) + coord_numel = coords.numel() + output_numel = output_tensor.numel() + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + # apply the kernel + second_order_fwd[num_blocks,](coords, output_tensor, block_size, coord_numel, output_numel) + ctx.save_for_backward(coords) + return output_tensor + + @staticmethod + def backward(ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64) -> torch.Tensor: + (coords,) = ctx.saved_tensors + coord_grad_output = torch.zeros_like(coords) + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + # call backward kernel + second_order_bwd[num_blocks,](coords, coord_grad_output, sph_grad_tensor, block_size, coords.numel(), sph_grad_tensor.numel()) + return coord_grad_output + + +def torch_second_order_fwd(coords: torch.Tensor) -> torch.Tensor: + """ + PyTorch implementation of the kernel. This is designed + purely for unit testing to ensure that the Triton implementation + is behaving as intended. + + Parameters + ---------- + coords : torch.Tensor + N-d tensor, where the last dimension corresponds to + xyz values. + + Returns + ------- + torch.Tensor + N-d tensor, where the last dimension corresponds to + each projection of the second order spherical harmonic. + """ + x = coords[...,0].contiguous().unsqueeze(-1) + y = coords[...,1].contiguous().unsqueeze(-1) + z = coords[...,2].contiguous().unsqueeze(-1) + CONST_00 = 3.87298334620742 + CONST_01 = 2.23606797749979 + CONST_02 = -1.11803398874989 + CONST_03 = 1.93649167310371 + Y20 = CONST_00 * x * z + Y21 = CONST_00 * x * y + Y23 = CONST_00 * y * z # looks jarring but just helping the compiler ;) + Y22 = CONST_02 * x * x + CONST_01 * y * y + CONST_02 * z * z + Y24 = -CONST_03 * x * x + CONST_03 * z * z + return torch.cat([Y20, Y21, Y22, Y23, Y24], dim=-1) + + +@triton.jit +def second_order_fwd( + coord_ptr: tl.tensor, + output_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, +): + # these are hardcoded because they are predetermined; + coord_stride = 3 + # work out the row offsets + block_id = tl.program_id(0) + coord_striding = tl.arange(0, block_size) * coord_stride + # as the name suggests, this is effectively every node/atom + coord_row_offset = coord_striding + (block_size * coord_stride * block_id) + x = tl.load(coord_ptr + coord_row_offset, mask=coord_row_offset < coord_numel) + y = tl.load(coord_ptr + coord_row_offset + 1, mask = coord_row_offset + 1 < coord_numel) + z = tl.load(coord_ptr + coord_row_offset + 2, mask = coord_row_offset + 2 < coord_numel) + CONST_00 = 3.87298334620742 + CONST_01 = 2.23606797749979 + CONST_02 = -1.11803398874989 + CONST_03 = 1.93649167310371 + Y20 = CONST_00 * x * z + Y21 = CONST_00 * x * y + Y23 = CONST_00 * y * z # looks jarring but just helping the compiler ;) + Y22 = CONST_02 * x * x + CONST_01 * y * y + CONST_02 * z * z + Y24 = -CONST_03 * x * x + CONST_03 * z * z + output_stride = 5 # [2l + 1] + output_striding = tl.arange(0, block_size) * output_stride + output_row_offset = output_striding + (block_size * output_stride * block_id) + tl.store(output_ptr + output_row_offset, Y20, mask=output_row_offset < output_numel) + tl.store(output_ptr + output_row_offset + 1, Y21, mask=output_row_offset + 1 < output_numel) + tl.store(output_ptr + output_row_offset + 2, Y22, mask=output_row_offset + 2 < output_numel) + tl.store(output_ptr + output_row_offset + 3, Y23, mask=output_row_offset + 3 < output_numel) + tl.store(output_ptr + output_row_offset + 4, Y24, mask=output_row_offset + 4 < output_numel) + + +@triton.jit +def second_order_bwd( + coord_ptr: tl.tensor, + coord_grad_ptr: tl.tensor, + sph_grad_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, +): + # work out the row offsets + block_id = tl.program_id(0) + # these are hardcoded because they are predetermined; + coord_stride = 3 + coord_striding = tl.arange(0, block_size) * coord_stride + # as the name suggests, this is effectively every node/atom + coord_row_offset = coord_striding + (block_size * coord_stride * block_id) + x = tl.load(coord_ptr + coord_row_offset, mask=coord_row_offset < coord_numel) + y = tl.load(coord_ptr + coord_row_offset + 1, mask = coord_row_offset + 1 < coord_numel) + z = tl.load(coord_ptr + coord_row_offset + 2, mask = coord_row_offset + 2 < coord_numel) + output_stride = 5 # [2l + 1] + output_striding = tl.arange(0, block_size) * output_stride + output_row_offset = output_striding + (block_size * output_stride * block_id) + CONST_00 = 3.87298334620742 + CONST_01 = 2.23606797749979 + CONST_02 = 4.47213595499958 + # load in gradients w.r.t. spherical harmonic projections + g_Y20 = tl.load(sph_grad_ptr + output_row_offset, mask=output_row_offset < output_numel) + g_Y21 = tl.load(sph_grad_ptr + output_row_offset + 1, mask=output_row_offset + 1 < output_numel) + g_Y22 = tl.load(sph_grad_ptr + output_row_offset + 2, mask=output_row_offset + 2 < output_numel) + g_Y23 = tl.load(sph_grad_ptr + output_row_offset + 3, mask=output_row_offset + 3 < output_numel) + g_Y24 = tl.load(sph_grad_ptr + output_row_offset + 4, mask=output_row_offset + 4 < output_numel) + g_x = CONST_00 * g_Y20 * z + CONST_00 * g_Y21 * y - CONST_01 * g_Y22 * x - CONST_00 * g_Y24 * x + g_y = CONST_00 * g_Y21 * x + CONST_02 * g_Y22 * y + CONST_00 * g_Y23 * z + g_z = CONST_00 * g_Y20 * x - CONST_01 * g_Y22 * z + CONST_00 * g_Y23 * y + CONST_00 * g_Y24 * z + # write out gradients + tl.store(coord_grad_ptr + coord_row_offset, g_x, mask=coord_row_offset < coord_numel) + tl.store(coord_grad_ptr + coord_row_offset + 1, g_y, mask=coord_row_offset + 1 < coord_numel) + tl.store(coord_grad_ptr + coord_row_offset + 2, g_z, mask=coord_row_offset + 2< coord_numel) + \ No newline at end of file From fa442b9ff55f7c4939324fbe7202c48f22bf2423 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Wed, 21 Aug 2024 15:26:21 -0700 Subject: [PATCH 006/116] feat: adding automatically formatted kernels --- ...ct evaluation of spherical harmonics.ipynb | 182 +++++-- notebooks/bwd_implementations/bwd_10.py | 452 ++++++++++++++++++ notebooks/bwd_implementations/bwd_2.py | 17 + notebooks/bwd_implementations/bwd_3.py | 34 ++ notebooks/bwd_implementations/bwd_4.py | 52 ++ notebooks/bwd_implementations/bwd_5.py | 93 ++++ notebooks/bwd_implementations/bwd_6.py | 121 +++++ notebooks/bwd_implementations/bwd_7.py | 195 ++++++++ notebooks/bwd_implementations/bwd_8.py | 270 +++++++++++ notebooks/bwd_implementations/bwd_9.py | 352 ++++++++++++++ notebooks/fwd_implementations/fwd_10.py | 238 +++++++++ notebooks/fwd_implementations/fwd_2.py | 18 + notebooks/fwd_implementations/fwd_3.py | 29 ++ notebooks/fwd_implementations/fwd_4.py | 43 ++ notebooks/fwd_implementations/fwd_5.py | 63 +++ notebooks/fwd_implementations/fwd_6.py | 81 ++++ notebooks/fwd_implementations/fwd_7.py | 119 +++++ notebooks/fwd_implementations/fwd_8.py | 147 ++++++ notebooks/fwd_implementations/fwd_9.py | 194 ++++++++ 19 files changed, 2659 insertions(+), 41 deletions(-) create mode 100644 notebooks/bwd_implementations/bwd_10.py create mode 100644 notebooks/bwd_implementations/bwd_2.py create mode 100644 notebooks/bwd_implementations/bwd_3.py create mode 100644 notebooks/bwd_implementations/bwd_4.py create mode 100644 notebooks/bwd_implementations/bwd_5.py create mode 100644 notebooks/bwd_implementations/bwd_6.py create mode 100644 notebooks/bwd_implementations/bwd_7.py create mode 100644 notebooks/bwd_implementations/bwd_8.py create mode 100644 notebooks/bwd_implementations/bwd_9.py create mode 100644 notebooks/fwd_implementations/fwd_10.py create mode 100644 notebooks/fwd_implementations/fwd_2.py create mode 100644 notebooks/fwd_implementations/fwd_3.py create mode 100644 notebooks/fwd_implementations/fwd_4.py create mode 100644 notebooks/fwd_implementations/fwd_5.py create mode 100644 notebooks/fwd_implementations/fwd_6.py create mode 100644 notebooks/fwd_implementations/fwd_7.py create mode 100644 notebooks/fwd_implementations/fwd_8.py create mode 100644 notebooks/fwd_implementations/fwd_9.py diff --git a/notebooks/Direct evaluation of spherical harmonics.ipynb b/notebooks/Direct evaluation of spherical harmonics.ipynb index a02b754..630083c 100644 --- a/notebooks/Direct evaluation of spherical harmonics.ipynb +++ b/notebooks/Direct evaluation of spherical harmonics.ipynb @@ -10,6 +10,7 @@ "import math\n", "from functools import cache, partial\n", "from itertools import combinations, chain\n", + "from pathlib import Path\n", "\n", "import sympy\n", "from sympy import symbols, sqrt, diff, Symbol, latex, cos, sin, acos, simplify, pi\n", @@ -30,7 +31,13 @@ " \"theta\": acos(z / sqrt(x**2. + y**2. + z**2)),\n", " \"phi\": sign(y) * acos(x / sqrt(x**2 + y**2.)),\n", " \"r\": sqrt(x**2. + y**2 + z**2)\n", - "}" + "}\n", + "\n", + "# this is used to round floats: ~1e-7 corresponds to single precision\n", + "# decimal limits, and ~1e-15 is double precision based on the number\n", + "# of bits in the fraction. More precision = potentially more terms\n", + "# in the expression!\n", + "PRECISION_TOL = 1e-8" ] }, { @@ -716,7 +723,14 @@ "metadata": {}, "outputs": [], "source": [ - "nsimplify = partial(sympy.nsimplify, tolerance=1e-12, rational=True)\n", + "\"\"\"\n", + "See the first cell for details about `PRECISION_TOL`: this impacts the\n", + "number of terms that will appear in the final implementation. More terms\n", + "make it more precise, at the cost of being more difficult to maintain\n", + "and from a performance perspective, may eventually exceed the number of\n", + "registers on a given platform!\n", + "\"\"\"\n", + "nsimplify = partial(sympy.nsimplify, tolerance=PRECISION_TOL, rational=True)\n", "\n", "def replace_floating_integers(expr):\n", " \"\"\"Dumb, but straight forward way to replace floats that should be integers.\"\"\"\n", @@ -725,6 +739,7 @@ "\n", "\n", "def optimization_chain(expr):\n", + " \"\"\"Sequentially run a sequence of optimization steps on an expression\"\"\"\n", " opt_chain = [collect_sqrt, collect_const, nsimplify]\n", " for func in opt_chain:\n", " expr = func(expr)\n", @@ -1219,6 +1234,10 @@ "outputs": [], "source": [ "def collect_symbols(expr, agg_set):\n", + " \"\"\"\n", + " This function iterates across a set of expressions, and collects up unique\n", + " symbols into a set.\n", + " \"\"\"\n", " if len(expr.args) != 0 and not isinstance(expr, sympy.Pow):\n", " for arg in expr.args:\n", " collect_symbols(arg, agg_set)\n", @@ -1226,14 +1245,28 @@ " agg_set.add(expr)" ] }, + { + "cell_type": "markdown", + "id": "946b001a-dcc1-428a-848c-08664954afa6", + "metadata": {}, + "source": [ + "## Generating forward code" + ] + }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 31, "id": "3d837ed4-bb65-4843-9f20-7f19978b0586", "metadata": {}, "outputs": [], "source": [ - "def generate_fwd_implementation(exprs):\n", + "def generate_fwd_implementation(exprs, output_file: Path | None):\n", + " \"\"\"\n", + " This function takes a set of expressions for a particular forward\n", + " pass, determines the set of variables and constants used by each\n", + " kernel, and prints out a format that can be used as a 'machine-generated'\n", + " implementation for the collection of kernels.\n", + " \"\"\"\n", " variable_set = set()\n", " for expr in exprs:\n", " collect_symbols(expr, variable_set)\n", @@ -1245,9 +1278,13 @@ " varname = f\"CONST{const_counter:03}\"\n", " const_counter += 1\n", " else:\n", - " varname = string.ascii_uppercase[char_counter]\n", + " # note that orignially I used the alphabet for this table;\n", + " # turns out sympy doesn't like uppercase O as a variable\n", + " # to substitute, so we're using this counter now as well\n", + " varname = f\"VAR{char_counter:02}\"\n", " char_counter += 1\n", " mapping[sym] = varname\n", + " # sort the dict to make the output cleaner\n", " mapping = dict(sorted(mapping.items(), key=lambda x: x[1]))\n", " fmt_string = \"# -------------------- variable and constant definitions\\n\"\n", " for sym, char in mapping.items():\n", @@ -1256,56 +1293,119 @@ " fmt_string += \"# -------------------- kernel implementations\\n\"\n", " term_counter = 0\n", " for index, kernel in enumerate(exprs):\n", - " fmt_string += f\"Y{index:02} = {kernel.subs(mapping)}\\n\"\n", - " print(fmt_string)" + " kernel_str = str(kernel.subs(mapping))\n", + " fmt_string += f\"Y{index:02} = {kernel_str}\\n\"\n", + " if output_file:\n", + " with open(output_file, \"w+\") as write_file:\n", + " write_file.write(fmt_string)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "32600151-71ec-4738-b691-17862c4d3a94", + "metadata": {}, + "outputs": [], + "source": [ + "os.makedirs(\"fwd_implementations\", exist_ok=True)" ] }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 33, "id": "6685cbaf-0098-41e2-ace7-9d8d92ded1de", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "for order, expressions in zip(\n", + " range(2, 11),\n", + " [second_order_expressions, third_order_expressions, fourth_order_expressions, fifth_order_expressions, sixth_order_expressions, seventh_order_expressions, eighth_order_expressions, ninth_order_expressions, tenth_order_expressions]\n", + "):\n", + " output_path = Path(f\"fwd_implementations/fwd_{order}.py\")\n", + " generate_fwd_implementation(expressions[\"fwd\"], output_path)" + ] + }, + { + "cell_type": "markdown", + "id": "ffb012be-79d0-4a48-9810-4903b8ebed5e", "metadata": {}, - "outputs": [ - { - "ename": "SympifyError", - "evalue": "SympifyError: ", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mSympifyError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[50], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mgenerate_fwd_implementation\u001b[49m\u001b[43m(\u001b[49m\u001b[43msixth_order_expressions\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mfwd\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m5\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m]\u001b[49m\u001b[43m)\u001b[49m\n", - "Cell \u001b[0;32mIn[38], line 24\u001b[0m, in \u001b[0;36mgenerate_fwd_implementation\u001b[0;34m(exprs)\u001b[0m\n\u001b[1;32m 22\u001b[0m term_counter \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m0\u001b[39m\n\u001b[1;32m 23\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m index, kernel \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(exprs):\n\u001b[0;32m---> 24\u001b[0m fmt_string \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mY\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mindex\u001b[38;5;132;01m:\u001b[39;00m\u001b[38;5;124m02\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m = \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[43mkernel\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msubs\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmapping\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 25\u001b[0m \u001b[38;5;28mprint\u001b[39m(fmt_string)\n", - "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/basic.py:1123\u001b[0m, in \u001b[0;36mBasic.subs\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1121\u001b[0m rv \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\n\u001b[1;32m 1122\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m old, new \u001b[38;5;129;01min\u001b[39;00m sequence:\n\u001b[0;32m-> 1123\u001b[0m rv \u001b[38;5;241m=\u001b[39m \u001b[43mrv\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_subs\u001b[49m\u001b[43m(\u001b[49m\u001b[43mold\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnew\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1124\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(rv, Basic):\n\u001b[1;32m 1125\u001b[0m \u001b[38;5;28;01mbreak\u001b[39;00m\n", - "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/cache.py:72\u001b[0m, in \u001b[0;36m__cacheit..func_wrapper..wrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 69\u001b[0m \u001b[38;5;129m@wraps\u001b[39m(func)\n\u001b[1;32m 70\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mwrapper\u001b[39m(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 71\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m---> 72\u001b[0m retval \u001b[38;5;241m=\u001b[39m \u001b[43mcfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 73\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 74\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m e\u001b[38;5;241m.\u001b[39margs \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m e\u001b[38;5;241m.\u001b[39margs[\u001b[38;5;241m0\u001b[39m]\u001b[38;5;241m.\u001b[39mstartswith(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124munhashable type:\u001b[39m\u001b[38;5;124m'\u001b[39m):\n", - "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/basic.py:1237\u001b[0m, in \u001b[0;36mBasic._subs\u001b[0;34m(self, old, new, **hints)\u001b[0m\n\u001b[1;32m 1235\u001b[0m rv \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_eval_subs(old, new)\n\u001b[1;32m 1236\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m rv \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m-> 1237\u001b[0m rv \u001b[38;5;241m=\u001b[39m \u001b[43mfallback\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mold\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnew\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1238\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m rv\n", - "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/basic.py:1209\u001b[0m, in \u001b[0;36mBasic._subs..fallback\u001b[0;34m(self, old, new)\u001b[0m\n\u001b[1;32m 1207\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mhasattr\u001b[39m(arg, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124m_eval_subs\u001b[39m\u001b[38;5;124m'\u001b[39m):\n\u001b[1;32m 1208\u001b[0m \u001b[38;5;28;01mcontinue\u001b[39;00m\n\u001b[0;32m-> 1209\u001b[0m arg \u001b[38;5;241m=\u001b[39m \u001b[43marg\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_subs\u001b[49m\u001b[43m(\u001b[49m\u001b[43mold\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnew\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mhints\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1210\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m _aresame(arg, args[i]):\n\u001b[1;32m 1211\u001b[0m hit \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n", - "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/cache.py:72\u001b[0m, in \u001b[0;36m__cacheit..func_wrapper..wrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 69\u001b[0m \u001b[38;5;129m@wraps\u001b[39m(func)\n\u001b[1;32m 70\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mwrapper\u001b[39m(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 71\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m---> 72\u001b[0m retval \u001b[38;5;241m=\u001b[39m \u001b[43mcfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 73\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 74\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m e\u001b[38;5;241m.\u001b[39margs \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m e\u001b[38;5;241m.\u001b[39margs[\u001b[38;5;241m0\u001b[39m]\u001b[38;5;241m.\u001b[39mstartswith(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124munhashable type:\u001b[39m\u001b[38;5;124m'\u001b[39m):\n", - "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/basic.py:1237\u001b[0m, in \u001b[0;36mBasic._subs\u001b[0;34m(self, old, new, **hints)\u001b[0m\n\u001b[1;32m 1235\u001b[0m rv \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_eval_subs(old, new)\n\u001b[1;32m 1236\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m rv \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m-> 1237\u001b[0m rv \u001b[38;5;241m=\u001b[39m \u001b[43mfallback\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mold\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnew\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1238\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m rv\n", - "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/basic.py:1209\u001b[0m, in \u001b[0;36mBasic._subs..fallback\u001b[0;34m(self, old, new)\u001b[0m\n\u001b[1;32m 1207\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mhasattr\u001b[39m(arg, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124m_eval_subs\u001b[39m\u001b[38;5;124m'\u001b[39m):\n\u001b[1;32m 1208\u001b[0m \u001b[38;5;28;01mcontinue\u001b[39;00m\n\u001b[0;32m-> 1209\u001b[0m arg \u001b[38;5;241m=\u001b[39m \u001b[43marg\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_subs\u001b[49m\u001b[43m(\u001b[49m\u001b[43mold\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnew\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mhints\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1210\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m _aresame(arg, args[i]):\n\u001b[1;32m 1211\u001b[0m hit \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n", - "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/cache.py:72\u001b[0m, in \u001b[0;36m__cacheit..func_wrapper..wrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 69\u001b[0m \u001b[38;5;129m@wraps\u001b[39m(func)\n\u001b[1;32m 70\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mwrapper\u001b[39m(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 71\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m---> 72\u001b[0m retval \u001b[38;5;241m=\u001b[39m \u001b[43mcfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 73\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 74\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m e\u001b[38;5;241m.\u001b[39margs \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m e\u001b[38;5;241m.\u001b[39margs[\u001b[38;5;241m0\u001b[39m]\u001b[38;5;241m.\u001b[39mstartswith(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124munhashable type:\u001b[39m\u001b[38;5;124m'\u001b[39m):\n", - "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/basic.py:1237\u001b[0m, in \u001b[0;36mBasic._subs\u001b[0;34m(self, old, new, **hints)\u001b[0m\n\u001b[1;32m 1235\u001b[0m rv \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_eval_subs(old, new)\n\u001b[1;32m 1236\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m rv \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m-> 1237\u001b[0m rv \u001b[38;5;241m=\u001b[39m \u001b[43mfallback\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mold\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnew\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1238\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m rv\n", - "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/basic.py:1209\u001b[0m, in \u001b[0;36mBasic._subs..fallback\u001b[0;34m(self, old, new)\u001b[0m\n\u001b[1;32m 1207\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mhasattr\u001b[39m(arg, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124m_eval_subs\u001b[39m\u001b[38;5;124m'\u001b[39m):\n\u001b[1;32m 1208\u001b[0m \u001b[38;5;28;01mcontinue\u001b[39;00m\n\u001b[0;32m-> 1209\u001b[0m arg \u001b[38;5;241m=\u001b[39m \u001b[43marg\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_subs\u001b[49m\u001b[43m(\u001b[49m\u001b[43mold\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnew\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mhints\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1210\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m _aresame(arg, args[i]):\n\u001b[1;32m 1211\u001b[0m hit \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n", - "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/cache.py:72\u001b[0m, in \u001b[0;36m__cacheit..func_wrapper..wrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 69\u001b[0m \u001b[38;5;129m@wraps\u001b[39m(func)\n\u001b[1;32m 70\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mwrapper\u001b[39m(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 71\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m---> 72\u001b[0m retval \u001b[38;5;241m=\u001b[39m \u001b[43mcfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 73\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 74\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m e\u001b[38;5;241m.\u001b[39margs \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m e\u001b[38;5;241m.\u001b[39margs[\u001b[38;5;241m0\u001b[39m]\u001b[38;5;241m.\u001b[39mstartswith(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124munhashable type:\u001b[39m\u001b[38;5;124m'\u001b[39m):\n", - "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/basic.py:1237\u001b[0m, in \u001b[0;36mBasic._subs\u001b[0;34m(self, old, new, **hints)\u001b[0m\n\u001b[1;32m 1235\u001b[0m rv \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_eval_subs(old, new)\n\u001b[1;32m 1236\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m rv \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m-> 1237\u001b[0m rv \u001b[38;5;241m=\u001b[39m \u001b[43mfallback\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mold\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnew\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1238\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m rv\n", - "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/basic.py:1214\u001b[0m, in \u001b[0;36mBasic._subs..fallback\u001b[0;34m(self, old, new)\u001b[0m\n\u001b[1;32m 1212\u001b[0m args[i] \u001b[38;5;241m=\u001b[39m arg\n\u001b[1;32m 1213\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m hit:\n\u001b[0;32m-> 1214\u001b[0m rv \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1215\u001b[0m hack2 \u001b[38;5;241m=\u001b[39m hints\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mhack2\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[38;5;28;01mFalse\u001b[39;00m)\n\u001b[1;32m 1216\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m hack2 \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mis_Mul \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m rv\u001b[38;5;241m.\u001b[39mis_Mul: \u001b[38;5;66;03m# 2-arg hack\u001b[39;00m\n", - "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/cache.py:72\u001b[0m, in \u001b[0;36m__cacheit..func_wrapper..wrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 69\u001b[0m \u001b[38;5;129m@wraps\u001b[39m(func)\n\u001b[1;32m 70\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mwrapper\u001b[39m(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 71\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m---> 72\u001b[0m retval \u001b[38;5;241m=\u001b[39m \u001b[43mcfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 73\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 74\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m e\u001b[38;5;241m.\u001b[39margs \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m e\u001b[38;5;241m.\u001b[39margs[\u001b[38;5;241m0\u001b[39m]\u001b[38;5;241m.\u001b[39mstartswith(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124munhashable type:\u001b[39m\u001b[38;5;124m'\u001b[39m):\n", - "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/operations.py:57\u001b[0m, in \u001b[0;36mAssocOp.__new__\u001b[0;34m(cls, evaluate, _sympify, *args)\u001b[0m\n\u001b[1;32m 52\u001b[0m \u001b[38;5;129m@cacheit\u001b[39m\n\u001b[1;32m 53\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__new__\u001b[39m(\u001b[38;5;28mcls\u001b[39m, \u001b[38;5;241m*\u001b[39margs, evaluate\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m, _sympify\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m):\n\u001b[1;32m 54\u001b[0m \u001b[38;5;66;03m# Allow faster processing by passing ``_sympify=False``, if all arguments\u001b[39;00m\n\u001b[1;32m 55\u001b[0m \u001b[38;5;66;03m# are already sympified.\u001b[39;00m\n\u001b[1;32m 56\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m _sympify:\n\u001b[0;32m---> 57\u001b[0m args \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mlist\u001b[39m(\u001b[38;5;28mmap\u001b[39m(_sympify_, args))\n\u001b[1;32m 59\u001b[0m \u001b[38;5;66;03m# Disallow non-Expr args in Add/Mul\u001b[39;00m\n\u001b[1;32m 60\u001b[0m typ \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mcls\u001b[39m\u001b[38;5;241m.\u001b[39m_args_type\n", - "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/sympify.py:514\u001b[0m, in \u001b[0;36m_sympify\u001b[0;34m(a)\u001b[0m\n\u001b[1;32m 488\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_sympify\u001b[39m(a):\n\u001b[1;32m 489\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 490\u001b[0m \u001b[38;5;124;03m Short version of :func:`~.sympify` for internal usage for ``__add__`` and\u001b[39;00m\n\u001b[1;32m 491\u001b[0m \u001b[38;5;124;03m ``__eq__`` methods where it is ok to allow some things (like Python\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 512\u001b[0m \n\u001b[1;32m 513\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[0;32m--> 514\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43msympify\u001b[49m\u001b[43m(\u001b[49m\u001b[43ma\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstrict\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m/large/miniforge3/envs/equitriton/lib/python3.11/site-packages/sympy/core/sympify.py:454\u001b[0m, in \u001b[0;36msympify\u001b[0;34m(a, locals, convert_xor, strict, rational, evaluate)\u001b[0m\n\u001b[1;32m 451\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m sympify(\u001b[38;5;28mint\u001b[39m(a))\n\u001b[1;32m 453\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m strict:\n\u001b[0;32m--> 454\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m SympifyError(a)\n\u001b[1;32m 456\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m iterable(a):\n\u001b[1;32m 457\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n", - "\u001b[0;31mSympifyError\u001b[0m: SympifyError: " - ] - } - ], "source": [ - "generate_fwd_implementation(sixth_order_expressions[\"fwd\"][5:])" + "## Generating backward code" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 42, "id": "22dee377-3e18-44bb-ad7c-621cdb27ded2", "metadata": {}, "outputs": [], + "source": [ + "def generate_bwd_implementation(exprs: dict, output_file: Path | None):\n", + " \"\"\"\n", + " This function takes a set of expressions for a particular backward\n", + " pass, determines the set of variables and constants used by each\n", + " kernel, and prints out a format that can be used as a 'machine-generated'\n", + " implementation for the collection of kernels.\n", + " \"\"\"\n", + " variable_set = set()\n", + " # we expect a dictionary, where the key corresponds to the cart axis\n", + " for expr in exprs.values():\n", + " collect_symbols(expr, variable_set)\n", + " mapping = {}\n", + " const_counter = 0\n", + " char_counter = 0\n", + " for sym in variable_set:\n", + " if isinstance(sym, (sympy.Float, sympy.Integer)):\n", + " varname = f\"CONST{const_counter:03}\"\n", + " const_counter += 1\n", + " else:\n", + " # note that orignially I used the alphabet for this table;\n", + " # turns out sympy doesn't like uppercase O as a variable\n", + " # to substitute, so we're using this counter now as well\n", + " varname = f\"VAR{char_counter:02}\"\n", + " char_counter += 1\n", + " mapping[sym] = varname\n", + " # sort the dict to make the output cleaner\n", + " mapping = dict(sorted(mapping.items(), key=lambda x: x[1]))\n", + " fmt_string = \"# -------------------- variable and constant definitions\\n\"\n", + " for sym, char in mapping.items():\n", + " fmt_string += f\"{char} = {sym}\\n\"\n", + " # now generate the actual kernels\n", + " fmt_string += \"# -------------------- kernel implementations\\n\"\n", + " term_counter = 0\n", + " for axis, grad_kernel in exprs.items():\n", + " kernel_str = str(grad_kernel.subs(mapping))\n", + " fmt_string += f\"g_{axis} = {kernel_str}\\n\"\n", + " if output_file:\n", + " with open(output_file, \"w+\") as write_file:\n", + " write_file.write(fmt_string)" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "53e9e681-72df-4f1d-aa38-21620816d372", + "metadata": {}, + "outputs": [], + "source": [ + "os.makedirs(\"bwd_implementations\", exist_ok=True)\n", + "\n", + "for order, expressions in zip(\n", + " range(2, 11),\n", + " [second_order_expressions, third_order_expressions, fourth_order_expressions, fifth_order_expressions, sixth_order_expressions, seventh_order_expressions, eighth_order_expressions, ninth_order_expressions, tenth_order_expressions]\n", + "):\n", + " output_path = Path(f\"bwd_implementations/bwd_{order}.py\")\n", + " generate_bwd_implementation(expressions[\"bwd\"], output_path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13130ed1-0c5c-422c-a1a9-f1fa07ee4b20", + "metadata": {}, + "outputs": [], "source": [] } ], diff --git a/notebooks/bwd_implementations/bwd_10.py b/notebooks/bwd_implementations/bwd_10.py new file mode 100644 index 0000000..d5bef89 --- /dev/null +++ b/notebooks/bwd_implementations/bwd_10.py @@ -0,0 +1,452 @@ +# -------------------- variable and constant definitions +CONST000 = 2.00000000000000 +CONST001 = 3.21913870529156 +CONST002 = 4.00000000000000 +CONST003 = 4.82870805793735 +CONST004 = 6.00000000000000 +CONST005 = 4.97432985632550 +CONST006 = 8.00000000000000 +CONST007 = 4.97432985632550 +CONST008 = 10.5521471197994 +CONST009 = 3.00000000000000 +CONST010 = 5.00000000000000 +CONST011 = 7.00000000000000 +CONST012 = 13.2648796168680 +CONST013 = 12.8765548211663 +CONST014 = 12.1657520803952 +CONST015 = 16.7271353825295 +CONST016 = -2030.35546709287 +CONST017 = 19.3148322317494 +CONST018 = -6131.53904851919 +CONST019 = 15.7302121789667 +CONST020 = 22.8629854262320 +CONST021 = 23.2135393295190 +CONST022 = 24.6216766128653 +CONST023 = 16.4144510752435 +CONST024 = 17.5869118663323 +CONST025 = 27.2034486491732 +CONST026 = 28.9722483476241 +CONST027 = 33.9852909359329 +CONST028 = 33.9852909359329 +CONST029 = 35.5238206489124 +CONST030 = 6180.74631415980 +CONST031 = 38.6296644634988 +CONST032 = 39.7946388506040 +CONST033 = -2007.25624590353 +CONST034 = -2007.25624590353 +CONST035 = 38.6296644634988 +CONST036 = 45.8257569495584 +CONST037 = 45.7259708524640 +CONST038 = 49.2433532257305 +CONST039 = 56.3871618715269 +CONST040 = 56.2781179722634 +CONST041 = -1989.33395633909 +CONST042 = -1989.33395633909 +CONST043 = 59.6919582759060 +CONST044 = 66.9085415301178 +CONST045 = 69.6406179885570 +CONST046 = -8121.42186837148 +CONST047 = 77.2593289269976 +CONST048 = 78.6510608948335 +CONST049 = -1969.73412902922 +CONST050 = 77.3468749368712 +CONST051 = -1969.73412902922 +CONST052 = -9.65741611587469 +CONST053 = 90.1358837481638 +CONST054 = 92.8541573180760 +CONST055 = 2141.07332896377 +CONST056 = 94.9693240781945 +CONST057 = 96.5741611587469 +CONST058 = 98.4867064514610 +CONST059 = 98.4867064514610 +CONST060 = 100.362812295177 +CONST061 = 101.517773354644 +CONST062 = 106.571461946737 +CONST063 = 106.571461946737 +CONST064 = 109.491768723557 +CONST065 = 109.491768723557 +CONST066 = 112.774323743054 +CONST067 = 112.774323743054 +CONST068 = 112.556235944527 +CONST069 = 2165.26701586663 +CONST070 = 130.522851455970 +CONST071 = 131.315608601948 +CONST072 = 133.817083060236 +CONST073 = 139.281235977114 +CONST074 = 139.281235977114 +CONST075 = 141.571909610700 +CONST076 = 142.095282595650 +CONST077 = 147.730059677192 +CONST078 = 150.544218442765 +CONST079 = 150.074981259369 +CONST080 = 154.518657853995 +CONST081 = 2202.22970505534 +CONST082 = -3939.46825805844 +CONST083 = -5968.00186901728 +CONST084 = 176.592751833137 +CONST085 = 176.178376404427 +CONST086 = 2228.49977563382 +CONST087 = 185.708314636152 +CONST088 = 196.973412902922 +CONST089 = 196.973412902922 +CONST090 = 203.035546709287 +CONST091 = 225.548647486108 +CONST092 = 225.548647486108 +CONST093 = 4330.53403173327 +CONST094 = 2285.08968653055 +CONST095 = 244.831037842559 +CONST096 = -1804.38917988886 +CONST097 = -1804.38917988886 +CONST098 = 244.831037842559 +CONST099 = 2317.77986780993 +CONST100 = 278.562471954228 +CONST101 = 284.190565191299 +CONST102 = 284.190565191299 +CONST103 = -1761.78376404427 +CONST104 = 290.050781013267 +CONST105 = -9946.66978169547 +CONST106 = 9.94865971265100 +CONST107 = 305.867618423396 +CONST108 = 305.867618423396 +CONST109 = 309.037315707990 +CONST110 = -7878.93651611688 +CONST111 = 2363.68095483506 +CONST112 = 14.5025390506634 +CONST113 = 338.322971229162 +CONST114 = 360.877835977772 +CONST115 = 4456.99955126765 +CONST116 = -1671.37483172537 +CONST117 = 386.296644634988 +CONST118 = 2436.42656051144 +CONST119 = 393.946825805844 +CONST120 = 393.946825805844 +CONST121 = 393.946825805844 +CONST122 = -1648.19901710928 +CONST123 = 401.451249180707 +CONST124 = 406.071093418574 +CONST125 = 412.049754277320 +CONST126 = 2472.29852566392 +CONST127 = -1624.28437367430 +CONST128 = 426.285847786949 +CONST129 = 426.285847786948 +CONST130 = 2486.66744542387 +CONST131 = 450.224943778107 +CONST132 = 451.097294972216 +CONST133 = 451.097294972216 +CONST134 = 451.097294972215 +CONST135 = 6606.68911516602 +CONST136 = 6606.68911516602 +CONST137 = -1575.78730322338 +CONST138 = -1575.78730322338 +CONST139 = -3608.77835977772 +CONST140 = 492.433532257305 +CONST141 = -1545.18657853995 +CONST142 = -1545.18657853995 +CONST143 = 525.262434407792 +CONST144 = 535.268332240943 +CONST145 = 4635.55973561985 +CONST146 = 541.428124558099 +CONST147 = -3545.52143225260 +CONST148 = 557.124943908456 +CONST149 = -3523.56752808854 +CONST150 = -5571.24943908456 +CONST151 = 580.101562026534 +CONST152 = 10828.5624911620 +CONST153 = 15.7883647328499 +CONST154 = 590.920238708766 +CONST155 = 2642.67564606641 +CONST156 = 2642.67564606641 +CONST157 = 2676.34166120471 +CONST158 = 629.208487158668 +CONST159 = 4727.36190967013 +CONST160 = 4727.36190967013 +CONST161 = -1392.81235977114 +CONST162 = -1390.66792068596 +CONST163 = 2707.14062279049 +CONST164 = 663.111318779698 +CONST165 = -3427.63452979582 +CONST166 = -1378.81389032045 +CONST167 = 676.645942458323 +CONST168 = 706.371007332549 +CONST169 = -1338.17083060236 +CONST170 = -1338.17083060236 +CONST171 = 721.755671955545 +CONST172 = 734.076568351780 +CONST173 = 2785.62471954228 +CONST174 = 742.833258544608 +CONST175 = 772.593289269975 +CONST176 = 787.893651611688 +CONST177 = 787.893651611688 +CONST178 = 787.893651611688 +CONST179 = 6.63243980843400 +CONST180 = 812.142186837148 +CONST181 = 812.142186837148 +CONST182 = -1218.21328025572 +CONST183 = -1202.92611992591 +CONST184 = -1202.92611992591 +CONST185 = -3248.56874734859 +CONST186 = -3248.56874734859 +CONST187 = -5285.35129213281 +CONST188 = -1181.84047741753 +CONST189 = 875.934149788456 +CONST190 = 880.891882022136 +CONST191 = 880.891882022136 +CONST192 = 2936.30627340712 +CONST193 = 900.449887556215 +CONST194 = 2954.60119354383 +CONST195 = -1114.24988781691 +CONST196 = -16.5810995210850 +CONST197 = -1101.11485252767 +CONST198 = -1081.63060497797 +CONST199 = 979.324151370235 +CONST200 = 984.867064514610 +CONST201 = 984.867064514610 +CONST202 = 1015.17773354644 +CONST203 = -1027.70719569249 +CONST204 = -1021.92317475320 +CONST205 = -3065.76952425960 +CONST206 = -1015.17773354644 +CONST207 = 3090.37315707990 +CONST208 = -994.666978169547 +CONST209 = -984.867064514610 +CONST210 = -984.867064514610 +CONST211 = -979.324151370235 +CONST212 = 1070.53666448189 +CONST213 = -979.324151370235 +CONST214 = 3151.57460644675 +CONST215 = 16.0956935264578 +CONST216 = 1114.24988781691 +CONST217 = -927.111947123971 +CONST218 = -927.111947123970 +CONST219 = -5.63871618715269 +CONST220 = -2954.60119354383 +CONST221 = -902.194589944431 +CONST222 = -900.449887556215 +CONST223 = -880.891882022136 +CONST224 = -880.891882022136 +CONST225 = -875.934149788456 +CONST226 = 1181.84047741753 +CONST227 = -4944.59705132784 +CONST228 = 3248.56874734859 +CONST229 = 3248.56874734859 +CONST230 = -835.687415862684 +CONST231 = 1218.21328025572 +CONST232 = -824.099508554641 +CONST233 = -824.863625092051 +CONST234 = -824.863625092051 +CONST235 = -812.142186837148 +CONST236 = 5352.68332240943 +CONST237 = -787.893651611688 +CONST238 = -787.893651611688 +CONST239 = -772.593289269976 +CONST240 = -742.833258544608 +CONST241 = -2785.62471954228 +CONST242 = -734.076568351780 +CONST243 = 1321.33782303320 +CONST244 = 1321.33782303320 +CONST245 = -706.371007332549 +CONST246 = -696.406179885570 +CONST247 = 1353.29188491665 +CONST248 = -675.337415667161 +CONST249 = -675.337415667161 +CONST250 = 1378.81389032045 +CONST251 = -669.085415301178 +CONST252 = 3427.63452979582 +CONST253 = -669.085415301178 +CONST254 = -669.085415301178 +CONST255 = 3427.63452979582 +CONST256 = -663.111318779698 +CONST257 = -2707.14062279049 +CONST258 = 1392.81235977114 +CONST259 = 1392.81235977114 +CONST260 = 1412.74201466510 +CONST261 = -4727.36190967013 +CONST262 = -2676.34166120471 +CONST263 = -618.074631415980 +CONST264 = -611.735236846792 +CONST265 = -611.735236846792 +CONST266 = 1443.51134391109 +CONST267 = -590.920238708766 +CONST268 = -10828.5624911620 +CONST269 = -580.101562026534 +CONST270 = -2626.31217203896 +CONST271 = 3523.56752808854 +CONST272 = 5571.24943908456 +CONST273 = 5571.24943908456 +CONST274 = -12.8765548211663 +CONST275 = -557.124943908456 +CONST276 = -557.124943908456 +CONST277 = 3545.52143225260 +CONST278 = -541.428124558099 +CONST279 = -6685.49932690147 +CONST280 = 7664.42381064899 +CONST281 = -525.262434407792 +CONST282 = 1532.88476212980 +CONST283 = 1545.18657853995 +CONST284 = -497.333489084773 +CONST285 = -497.333489084773 +CONST286 = -492.433532257305 +CONST287 = 1575.78730322338 +CONST288 = 1575.78730322338 +CONST289 = -463.555973561985 +CONST290 = -450.224943778107 +CONST291 = -450.224943778107 +CONST292 = -450.224943778108 +CONST293 = -437.967074894228 +CONST294 = -2472.29852566392 +CONST295 = 1624.28437367430 +CONST296 = -2472.29852566392 +CONST297 = -406.071093418574 +CONST298 = -393.946825805844 +CONST299 = -393.946825805844 +CONST300 = -2436.42656051144 +CONST301 = -386.296644634988 +CONST302 = -386.296644634988 +CONST303 = -4456.99955126765 +CONST304 = -337.668707833581 +CONST305 = -337.668707833581 +CONST306 = -331.555659389849 +CONST307 = -331.555659389849 +CONST308 = -2363.68095483506 +CONST309 = 7878.93651611688 +CONST310 = -309.037315707990 +CONST311 = -4404.45941011068 +CONST312 = -309.037315707990 +CONST313 = -305.867618423396 +CONST314 = -305.867618423396 +CONST315 = -305.867618423396 +CONST316 = -300.731529981477 +CONST317 = 9946.66978169547 +CONST318 = 9946.66978169547 +CONST319 = -290.050781013267 +CONST320 = -284.190565191299 +CONST321 = -278.562471954228 +CONST322 = -278.562471954228 +CONST323 = -2317.77986780993 +CONST324 = -10505.2486881558 +CONST325 = -251.683394863467 +CONST326 = -251.683394863467 +CONST327 = -246.216766128653 +CONST328 = -244.831037842559 +CONST329 = -2285.08968653055 +CONST330 = -2285.08968653055 +CONST331 = 3862.96644634988 +CONST332 = -223.028471767059 +CONST333 = -220.222970505534 +CONST334 = -206.215906273013 +CONST335 = -203.035546709287 +CONST336 = -196.973412902922 +CONST337 = -196.973412902922 +CONST338 = -182.903883409856 +CONST339 = -2228.49977563382 +CONST340 = 5968.00186901728 +CONST341 = 3939.46825805844 +CONST342 = 3939.46825805844 +CONST343 = -154.518657853995 +CONST344 = -154.518657853995 +CONST345 = -150.074981259369 +CONST346 = -147.730059677191 +CONST347 = -146.815313670356 +CONST348 = -142.095282595650 +CONST349 = -131.315608601948 +CONST350 = -131.315608601948 +CONST351 = -130.522851455970 +CONST352 = -125.841697431734 +CONST353 = -125.841697431734 +CONST354 = -112.556235944527 +CONST355 = -103.107953136506 +CONST356 = -101.517773354644 +CONST357 = 1949.93730367960 +CONST358 = -98.4867064514610 +CONST359 = -98.4867064514610 +CONST360 = -2141.07332896377 +CONST361 = -2141.07332896377 +CONST362 = -92.8541573180760 +CONST363 = -88.2963759165686 +CONST364 = 1969.73412902922 +CONST365 = 1969.73412902922 +CONST366 = -77.3468749368713 +CONST367 = 8121.42186837148 +CONST368 = 8121.42186837148 +CONST369 = -67.6645942458323 +CONST370 = 1989.33395633909 +CONST371 = 1989.33395633909 +CONST372 = -59.6919582759060 +CONST373 = -49.2433532257305 +CONST374 = -49.2433532257305 +CONST375 = -45.1097294972216 +CONST376 = -45.1097294972216 +CONST377 = -42.2085884791976 +CONST378 = -27.2034486491732 +CONST379 = -24.6216766128653 +CONST380 = -22.8629854262320 +CONST381 = -19.7354559160624 +CONST382 = 2030.35546709287 +CONST383 = -17.5869118663323 +CONST384 = -16.4144510752435 +CONST385 = -16.0956935264578 +CONST386 = -14.5025390506634 +CONST387 = -16.5810995210850 +CONST388 = -15.7883647328499 +CONST389 = -14.0695294930659 +CONST390 = -13.2648796168680 +CONST391 = -11.2774323743054 +CONST392 = -11.2774323743054 +CONST393 = 6131.53904851919 +CONST394 = -6.63243980843400 +CONST395 = -5.63871618715269 +CONST396 = -4.82870805793735 +CONST397 = -3.21913870529156 +CONST398 = -11.2774323743054 +VAR00 = g_14 +VAR01 = z +VAR02 = g_12 +VAR03 = g_16 +VAR04 = g_4 +VAR05 = z**9 +VAR06 = z**4 +VAR07 = x**9 +VAR08 = z**8 +VAR09 = y +VAR10 = x**4 +VAR11 = y**8 +VAR12 = z**3 +VAR13 = x +VAR14 = x**8 +VAR15 = y**3 +VAR16 = x**3 +VAR17 = y**7 +VAR18 = y**2 +VAR19 = g_9 +VAR20 = g_1 +VAR21 = g_11 +VAR22 = g_19 +VAR23 = g_15 +VAR24 = g_8 +VAR25 = g_18 +VAR26 = z**7 +VAR27 = g_5 +VAR28 = z**2 +VAR29 = x**7 +VAR30 = g_6 +VAR31 = z**6 +VAR32 = x**2 +VAR33 = g_7 +VAR34 = y**6 +VAR35 = x**6 +VAR36 = z**5 +VAR37 = g_0 +VAR38 = g_3 +VAR39 = y**5 +VAR40 = g_2 +VAR41 = g_17 +VAR42 = x**5 +VAR43 = y**9 +VAR44 = g_13 +VAR45 = y**4 +VAR46 = g_20 +VAR47 = g_10 +# -------------------- kernel implementations +g_x = VAR00*(CONST000*VAR13*(CONST005*VAR28**4 - CONST161*VAR18**2*VAR28**2 + CONST195*VAR18**3*VAR28 + CONST321*VAR18*VAR28**3) + CONST002*VAR16*(CONST021*VAR28**3 + CONST087*VAR18**3 + CONST246*VAR18*VAR28**2 + CONST259*VAR18**2*VAR28) + CONST004*VAR42*(CONST021*VAR28**2 + CONST321*VAR18**2 + CONST321*VAR18*VAR28) + CONST006*VAR29*(CONST007*VAR28 + CONST045*VAR18) + CONST387*VAR16**3) + VAR02*(CONST000*VAR13*(CONST003*VAR28**4 - CONST302*VAR18**2*VAR28**2 + CONST343*VAR18*VAR28**3 + CONST363*VAR18**4) + CONST002*VAR16*(CONST125*VAR18**3 + CONST301*VAR18**2*VAR28 - CONST397*VAR28**3) + CONST004*VAR42*(CONST302*VAR18**2 - CONST344*VAR18*VAR28 + CONST397*VAR28**2) + CONST006*VAR29*(CONST047*VAR18 + CONST396*VAR28) + CONST385*VAR16**3) + VAR03*(CONST000*VAR13*(CONST049*VAR18**2*VAR28**2 + CONST177*VAR18*VAR28**3 + CONST380*VAR28**4) + CONST002*VAR16*(-CONST049*VAR18**2*VAR28 + CONST379*VAR28**3) + CONST004*VAR42*(CONST022*VAR28**2 + CONST237*VAR18*VAR28 + CONST349*VAR18**2) + CONST006*VAR29*(CONST020*VAR28 + CONST040*VAR18) + CONST383*VAR16**3) + VAR04*(CONST008*VAR12**3 + CONST009*VAR32*(CONST177*VAR18*VAR36 + CONST270*VAR12*VAR18**2 + CONST389*VAR26) + CONST010*VAR32**2*(CONST177*VAR01*VAR18**2 + CONST178*VAR12*VAR18 + CONST373*VAR36) + CONST011*VAR32**3*(CONST304*VAR01*VAR18 + CONST389*VAR12) + CONST056*VAR01*VAR32**4 + CONST177*VAR18**2*VAR36 + CONST305*VAR18*VAR26) + VAR09*VAR20*(CONST064*VAR28**4 + CONST065*VAR32**4 + CONST205*VAR28**3*VAR32 + CONST205*VAR28*VAR32**3 + CONST280*VAR28**2*VAR32**2) + VAR09*VAR22*(CONST018*VAR12*VAR42 - CONST018*VAR16*VAR36 - CONST225*VAR01*VAR29 + CONST225*VAR13*VAR26) + VAR19*(CONST009*VAR32*(CONST044*VAR09*VAR28**3 + CONST212*VAR28*VAR39 + CONST253*VAR15*VAR28**2 + CONST313*VAR17) + CONST010*VAR32**2*(CONST060*VAR09*VAR28**2 + CONST144*VAR39 + CONST254*VAR15*VAR28) + CONST011*VAR32**3*(CONST044*VAR09*VAR28 + CONST332*VAR15) + CONST015*VAR09*VAR28**4 + CONST027*VAR15**3 + CONST078*VAR09*VAR32**4 + CONST144*VAR28**2*VAR39 + CONST313*VAR17*VAR28 + CONST332*VAR15*VAR28**3) + VAR21*(CONST072*VAR09*VAR13*VAR26 + VAR01*(CONST072*VAR09*VAR29 + CONST169*VAR15*VAR42 + CONST264*VAR13*VAR17 - CONST361*VAR16*VAR39) + VAR12*(CONST123*VAR09*VAR42 + CONST262*VAR15*VAR16 - CONST361*VAR13*VAR39) + VAR36*(CONST123*VAR09*VAR16 + CONST170*VAR13*VAR15)) + VAR23*(VAR09*(CONST158*VAR01*VAR29 + CONST223*VAR16*VAR36 + CONST325*VAR13*VAR26) + VAR15*(CONST155*VAR13*VAR36 + CONST192*VAR12*VAR16 + CONST311*VAR01*VAR42) + VAR39*(-CONST149*VAR01*VAR16 + CONST149*VAR12*VAR13)) + VAR24*(CONST009*VAR32*(-CONST142*VAR12*VAR18**2 + CONST232*VAR01*VAR18**3 - CONST274*VAR26 + CONST289*VAR18*VAR36) + CONST010*VAR32**2*(CONST017*VAR36 + CONST175*VAR01*VAR18**2 + CONST289*VAR12*VAR18) + CONST011*VAR32**3*(-CONST274*VAR12 + CONST344*VAR01*VAR18) + CONST026*VAR01*VAR32**4 + CONST084*VAR01*VAR18**4 + CONST175*VAR18**2*VAR36 + CONST232*VAR12*VAR18**3 + CONST344*VAR18*VAR26 - CONST397*VAR12**3) + VAR25*(CONST062*VAR13*VAR28**4 + CONST128*VAR28*VAR29 + CONST284*VAR28**2*VAR42 + CONST306*VAR16*VAR28**3 + CONST381*VAR16**3 + VAR18*(CONST041*VAR13*VAR28**3 + CONST083*VAR28*VAR42 + CONST317*VAR16*VAR28**2 - CONST320*VAR29)) + VAR27*(VAR09*(CONST048*VAR28**4 + CONST075*VAR32**4 + CONST197*VAR28**2*VAR32**2 + CONST223*VAR28*VAR32**3) + VAR15*(CONST081*VAR28**2*VAR32 + CONST135*VAR28*VAR32**2 + CONST203*VAR32**3 + CONST242*VAR28**3) + VAR39*(CONST187*VAR28*VAR32 - CONST223*VAR32**2 - CONST224*VAR28**2)) + VAR30*(CONST009*VAR32*(CONST100*VAR18*VAR36 + CONST240*VAR01*VAR18**3 + CONST390*VAR26) + CONST010*VAR32**2*(-CONST195*VAR01*VAR18**2 + CONST321*VAR12*VAR18) + CONST011*VAR32**3*(CONST012*VAR12 + CONST322*VAR01*VAR18) + CONST043*VAR01*VAR32**4 + CONST100*VAR18*VAR26 + CONST195*VAR18**2*VAR36 - CONST240*VAR12*VAR18**3 + CONST394*VAR12**3) + VAR33*(VAR09*(CONST077*VAR32**4 + CONST286*VAR28**2*VAR32**2 + CONST298*VAR28**3*VAR32 + CONST374*VAR28**4) + VAR15*(CONST166*VAR32**3 + CONST194*VAR28**2*VAR32 + CONST200*VAR28*VAR32**2 - CONST267*VAR28**3) + VAR17*(CONST290*VAR32 - CONST291*VAR28) + VAR39*(-CONST051*VAR32**2 + CONST188*VAR28**2 + CONST308*VAR28*VAR32)) + VAR37*(CONST095*VAR01*VAR32**4 + CONST211*VAR26*VAR32 + CONST252*VAR32**2*VAR36 + CONST329*VAR12*VAR32**3 - CONST378*VAR12**3) + VAR38*(VAR09*(CONST016*VAR28*VAR32**3 - CONST206*VAR28**2*VAR32**2 + CONST231*VAR28**3*VAR32 - CONST351*VAR32**4 + CONST356*VAR28**4) + VAR15*(CONST046*VAR28**2*VAR32 + CONST146*VAR28**3 + CONST278*VAR32**3 + CONST367*VAR28*VAR32**2)) + VAR40*(CONST076*VAR01*VAR32**4 + CONST102*VAR26*VAR32 + CONST256*VAR12*VAR32**3 + CONST388*VAR12**3 + VAR18*(CONST042*VAR01*VAR32**3 + CONST083*VAR32*VAR36 - CONST105*VAR12*VAR32**2 - CONST320*VAR26)) + VAR41*(VAR09*(CONST180*VAR01*VAR29 + CONST235*VAR16*VAR36 - CONST269*VAR13*VAR26 + CONST300*VAR12*VAR42) + VAR15*(CONST185*VAR13*VAR36 + CONST186*VAR01*VAR42 - CONST268*VAR12*VAR16)) + VAR44*(CONST222*VAR01*VAR13*VAR17 + VAR09*(-CONST238*VAR12*VAR42 - CONST298*VAR16*VAR36 - CONST299*VAR01*VAR29) + VAR15*(CONST082*VAR12*VAR16 + CONST147*VAR01*VAR42 + CONST298*VAR13*VAR36) + VAR39*(-CONST261*VAR01*VAR16 + CONST287*VAR12*VAR13)) + VAR46*(CONST165*VAR28**2*VAR42 - CONST213*VAR28*VAR29 + CONST328*VAR13*VAR28**4 - CONST330*VAR16*VAR28**3 + CONST378*VAR16**3) + VAR47*(CONST000*VAR13*(CONST091*VAR18*VAR28**3 + CONST171*VAR18**3*VAR28 + CONST221*VAR18**2*VAR28**2 + CONST355*VAR18**4 + CONST395*VAR28**4) + CONST002*VAR16*(CONST113*VAR18*VAR28**2 + CONST114*VAR18**3 + CONST221*VAR18**2*VAR28 + CONST392*VAR28**3) + CONST004*VAR42*(CONST092*VAR18*VAR28 + CONST316*VAR18**2 + CONST392*VAR28**2) + CONST006*VAR29*(CONST039*VAR18 + CONST219*VAR28) + CONST391*VAR16**3) +g_y = CONST000*VAR09*VAR25*(CONST029*VAR28**4 + CONST029*VAR32**4 + CONST130*VAR28**2*VAR32**2 + CONST208*VAR28**3*VAR32 + CONST208*VAR28*VAR32**3) + CONST000*VAR09*VAR40*(-CONST041*VAR12*VAR42 + CONST041*VAR16*VAR36 + CONST320*VAR01*VAR29 - CONST320*VAR13*VAR26) + VAR00*(CONST073*VAR09*VAR32**4 + CONST074*VAR09*VAR28**4 + CONST195*VAR15*VAR28**3 - CONST195*VAR28**2*VAR39 + VAR32**3*(CONST195*VAR15 + CONST275*VAR09*VAR28) + VAR32**2*(CONST161*VAR09*VAR28**2 - CONST195*VAR39 + CONST273*VAR15*VAR28) + VAR32*(-CONST150*VAR15*VAR28**2 + CONST275*VAR09*VAR28**3 + CONST279*VAR28*VAR39)) + VAR02*(-CONST142*VAR15*VAR28**3 - CONST245*VAR17*VAR28 + CONST294*VAR28**2*VAR39 + CONST343*VAR09*VAR28**4 - CONST344*VAR09*VAR32**4 + VAR32**3*(CONST142*VAR15 - CONST312*VAR09*VAR28) + VAR32**2*(CONST141*VAR15*VAR28 - CONST296*VAR39) + VAR32*(-CONST142*VAR15*VAR28**2 + CONST245*VAR17 + CONST310*VAR09*VAR28**3)) + VAR03*(-CONST110*VAR15*VAR28*VAR32**2 - CONST281*VAR15*VAR28**3 + CONST354*VAR09*VAR28**4 - CONST354*VAR09*VAR32**4 + VAR32**3*(CONST137*VAR09*VAR28 + CONST281*VAR15) + VAR32*(CONST110*VAR15*VAR28**2 + CONST288*VAR09*VAR28**3)) + VAR04*(CONST249*VAR01*VAR09*VAR29 + VAR13*(CONST214*VAR15*VAR36 + CONST248*VAR09*VAR26) + VAR16*(CONST288*VAR09*VAR36 + CONST324*VAR12*VAR15) + VAR42*(CONST214*VAR01*VAR15 + CONST287*VAR09*VAR12)) + VAR19*(CONST015*VAR16**3 + VAR13*(CONST015*VAR28**4 + CONST157*VAR18**2*VAR28**2 + CONST251*VAR18*VAR28**3 - CONST315*VAR18**4 + CONST361*VAR18**3*VAR28) + VAR16*(CONST033*VAR18*VAR28**2 + CONST044*VAR28**3 + CONST236*VAR18**2*VAR28 + CONST361*VAR18**3) + VAR29*(CONST044*VAR28 + CONST251*VAR18) + VAR42*(CONST034*VAR18*VAR28 + CONST060*VAR28**2 + CONST157*VAR18**2)) + VAR20*(CONST014*VAR16**3 + CONST064*VAR13*VAR28**4 + CONST204*VAR16*VAR28**3 + CONST282*VAR28**2*VAR42 + CONST293*VAR28*VAR29) + VAR21*(CONST015*VAR12**3 + VAR01*(CONST015*VAR32**4 + CONST157*VAR18**2*VAR32**2 + CONST251*VAR18*VAR32**3 - CONST313*VAR18**4 + CONST360*VAR18**3*VAR32) + VAR12*(CONST033*VAR18*VAR32**2 + CONST044*VAR32**3 + CONST236*VAR18**2*VAR32 + CONST361*VAR18**3) + VAR26*(CONST044*VAR32 + CONST251*VAR18) + VAR36*(CONST034*VAR18*VAR32 + CONST060*VAR32**2 + CONST157*VAR18**2)) + VAR22*(CONST014*VAR12**3 + CONST064*VAR01*VAR32**4 + CONST204*VAR12*VAR32**3 + CONST282*VAR32**2*VAR36 + CONST293*VAR26*VAR32) + VAR23*(CONST009*VAR18*(CONST242*VAR01*VAR32**3 - CONST242*VAR12*VAR32**2 + CONST243*VAR32*VAR36 + CONST347*VAR26) + CONST010*VAR18**2*(CONST085*VAR36 + CONST103*VAR12*VAR32 - CONST224*VAR01*VAR32**2) + CONST019*VAR12**3 + CONST048*VAR01*VAR32**4 + CONST333*VAR32**2*VAR36 + CONST352*VAR26*VAR32) + VAR24*(CONST312*VAR01*VAR09*VAR29 + VAR13*(CONST207*VAR15*VAR36 + CONST227*VAR12*VAR39 + CONST260*VAR01*VAR17 + CONST312*VAR09*VAR26) + VAR16*(CONST030*VAR12*VAR15 + CONST217*VAR09*VAR36 + CONST227*VAR01*VAR39) + VAR42*(CONST207*VAR01*VAR15 + CONST217*VAR09*VAR12)) + VAR27*(CONST009*VAR18*(CONST242*VAR13*VAR28**3 - CONST242*VAR16*VAR28**2 + CONST244*VAR28*VAR42 + CONST347*VAR29) + CONST010*VAR18**2*(CONST085*VAR42 + CONST103*VAR16*VAR28 - CONST224*VAR13*VAR28**2) + CONST019*VAR16**3 + CONST048*VAR13*VAR28**4 + CONST333*VAR28**2*VAR42 + CONST353*VAR28*VAR29) + VAR30*(CONST276*VAR01*VAR09*VAR29 + VAR13*(CONST148*VAR09*VAR26 - CONST303*VAR12*VAR39 + CONST303*VAR15*VAR36) + VAR16*(CONST148*VAR09*VAR36 + CONST303*VAR01*VAR39) + VAR42*(CONST275*VAR09*VAR12 - CONST303*VAR01*VAR15)) + VAR33*(CONST009*VAR18*(CONST089*VAR28*VAR42 - CONST210*VAR16*VAR28**2 - CONST267*VAR13*VAR28**3 + CONST337*VAR29) + CONST010*VAR18**2*(CONST188*VAR13*VAR28**2 + CONST238*VAR16*VAR28 - CONST299*VAR42) + CONST011*VAR18**3*(-CONST291*VAR13*VAR28 + CONST345*VAR16) + CONST023*VAR16**3 + CONST350*VAR16*VAR28**3 + CONST358*VAR28**2*VAR42 + CONST374*VAR13*VAR28**4) + VAR38*(CONST009*VAR18*(CONST146*VAR13*VAR28**3 + CONST257*VAR16*VAR28**2 + CONST295*VAR28*VAR42 + CONST366*VAR29) + CONST124*VAR16*VAR28**3 + CONST319*VAR28*VAR29 - CONST335*VAR28**2*VAR42 + CONST356*VAR13*VAR28**4 - CONST386*VAR16**3) + VAR41*(CONST009*VAR18*(CONST050*VAR26 + CONST127*VAR32*VAR36 - CONST257*VAR12*VAR32**2 + CONST278*VAR01*VAR32**3) + CONST061*VAR01*VAR32**4 + CONST297*VAR12*VAR32**3 - CONST319*VAR26*VAR32 + CONST335*VAR32**2*VAR36 + CONST386*VAR12**3) + VAR44*(CONST009*VAR18*(CONST209*VAR12*VAR32**2 + CONST267*VAR01*VAR32**3 + CONST336*VAR32*VAR36 - CONST337*VAR26) + CONST010*VAR18**2*(CONST178*VAR12*VAR32 - CONST188*VAR01*VAR32**2 + CONST299*VAR36) + CONST011*VAR18**3*(CONST079*VAR12 + CONST291*VAR01*VAR32) - CONST350*VAR12*VAR32**3 - CONST358*VAR32**2*VAR36 - CONST374*VAR01*VAR32**4 + CONST384*VAR12**3) + VAR47*(CONST036*VAR15**3 + CONST066*VAR09*VAR28**4 + CONST067*VAR09*VAR32**4 + CONST069*VAR28**2*VAR39 + CONST184*VAR15*VAR28**3 + CONST234*VAR17*VAR28 + VAR32**3*(CONST133*VAR09*VAR28 + CONST183*VAR15) + VAR32**2*(CONST069*VAR39 + CONST139*VAR15*VAR28 + CONST167*VAR09*VAR28**2) + VAR32*(CONST093*VAR28*VAR39 + CONST132*VAR09*VAR28**3 + CONST139*VAR15*VAR28**2 + CONST233*VAR17)) +g_z = VAR00*(CONST106*VAR01*VAR32**4 + CONST116*VAR18**2*VAR36 + CONST148*VAR18*VAR26 + CONST196*VAR12**3 - CONST240*VAR12*VAR18**3 + VAR32**3*(CONST275*VAR01*VAR18 - CONST362*VAR12) + VAR32**2*(CONST074*VAR36 + CONST173*VAR01*VAR18**2 + CONST241*VAR12*VAR18) + VAR32*(CONST032*VAR26 + CONST116*VAR18*VAR36 - CONST150*VAR12*VAR18**2 + CONST339*VAR01*VAR18**3)) + VAR02*(CONST052*VAR01*VAR32**4 + CONST084*VAR01*VAR18**4 + CONST099*VAR18**2*VAR36 + CONST122*VAR12*VAR18**3 + CONST263*VAR18*VAR26 - CONST385*VAR12**3 + VAR32**3*(CONST274*VAR12 - CONST312*VAR01*VAR18) + VAR32**2*(CONST017*VAR36 + CONST239*VAR01*VAR18**2) + VAR32*(CONST031*VAR26 - CONST142*VAR12*VAR18**2 + CONST218*VAR18*VAR36)) + VAR03*(CONST024*VAR12**3 + CONST037*VAR01*VAR32**4 + CONST177*VAR18**2*VAR36 + CONST292*VAR18*VAR26 + VAR32**3*(CONST059*VAR12 + CONST137*VAR01*VAR18) + VAR32**2*(CONST341*VAR01*VAR18**2 + CONST346*VAR36) + VAR32*(CONST110*VAR12*VAR18**2 + CONST160*VAR18*VAR36 + CONST338*VAR26)) + VAR04*(CONST008*VAR16**3 + VAR13*(CONST056*VAR28**4 + CONST308*VAR18*VAR28**3 + CONST341*VAR18**2*VAR28**2) + VAR16*(CONST110*VAR18**2*VAR28 + CONST341*VAR18*VAR28**2 + CONST359*VAR28**3) + VAR29*(CONST304*VAR18 + CONST377*VAR28) + VAR42*(CONST177*VAR18**2 - CONST308*VAR18*VAR28 + CONST327*VAR28**2)) + VAR09*VAR20*(-CONST018*VAR12*VAR42 + CONST018*VAR16*VAR36 + CONST225*VAR01*VAR29 - CONST225*VAR13*VAR26) + VAR09*VAR22*(CONST064*VAR32**4 + CONST065*VAR28**4 + CONST205*VAR28**3*VAR32 + CONST205*VAR28*VAR32**3 + CONST280*VAR28**2*VAR32**2) + VAR19*(CONST072*VAR01*VAR09*VAR29 + VAR13*(CONST072*VAR09*VAR26 + CONST169*VAR15*VAR36 + CONST265*VAR01*VAR17 - CONST361*VAR12*VAR39) + VAR16*(CONST123*VAR09*VAR36 + CONST262*VAR12*VAR15 - CONST361*VAR01*VAR39) + VAR42*(CONST123*VAR09*VAR12 + CONST170*VAR01*VAR15)) + VAR21*(CONST009*VAR28*(CONST044*VAR09*VAR32**3 + CONST212*VAR32*VAR39 + CONST253*VAR15*VAR32**2 + CONST314*VAR17) + CONST010*VAR28**2*(CONST060*VAR09*VAR32**2 + CONST144*VAR39 + CONST254*VAR15*VAR32) + CONST011*VAR28**3*(CONST044*VAR09*VAR32 + CONST332*VAR15) + CONST015*VAR09*VAR32**4 + CONST028*VAR15**3 + CONST078*VAR09*VAR28**4 + CONST144*VAR32**2*VAR39 + CONST315*VAR17*VAR32 + CONST332*VAR15*VAR32**3) + VAR23*(VAR09*(CONST048*VAR32**4 + CONST075*VAR28**4 + CONST197*VAR28**2*VAR32**2 + CONST224*VAR28**3*VAR32) + VAR15*(CONST081*VAR28*VAR32**2 + CONST136*VAR28**2*VAR32 + CONST203*VAR28**3 + CONST242*VAR32**3) + VAR39*(CONST187*VAR28*VAR32 - CONST223*VAR28**2 - CONST224*VAR32**2)) + VAR24*(-CONST397*VAR16**3 + VAR13*(CONST026*VAR28**4 + CONST084*VAR18**4 + CONST198*VAR18*VAR28**3 + CONST296*VAR18**3*VAR28 + CONST331*VAR18**2*VAR28**2) + VAR16*(CONST053*VAR28**3 + CONST145*VAR18**2*VAR28 + CONST232*VAR18**3 + CONST323*VAR18*VAR28**2) + VAR29*(CONST035*VAR28 + CONST344*VAR18) + VAR42*(CONST057*VAR28**2 + CONST162*VAR18*VAR28 + CONST175*VAR18**2)) + VAR25*(CONST063*VAR01*VAR32**4 + CONST129*VAR26*VAR32 + CONST285*VAR32**2*VAR36 + CONST307*VAR12*VAR32**3 + CONST381*VAR12**3 + VAR18*(CONST041*VAR01*VAR32**3 + CONST083*VAR32*VAR36 + CONST317*VAR12*VAR32**2 - CONST320*VAR26)) + VAR27*(VAR09*(CONST158*VAR13*VAR26 + CONST223*VAR12*VAR42 + CONST326*VAR01*VAR29) + VAR15*(CONST156*VAR01*VAR42 + CONST192*VAR12*VAR16 + CONST311*VAR13*VAR36) + VAR39*(CONST149*VAR01*VAR16 - CONST149*VAR12*VAR13)) + VAR30*(CONST179*VAR16**3 + VAR13*(CONST150*VAR18**2*VAR28**2 - CONST339*VAR18**3*VAR28 + CONST357*VAR18*VAR28**3 + CONST372*VAR28**4) + VAR16*(CONST240*VAR18**3 + CONST259*VAR18*VAR28**2 + CONST362*VAR28**3) + VAR29*(CONST032*VAR28 + CONST322*VAR18) + VAR42*(-CONST195*VAR18**2 + CONST230*VAR18*VAR28)) + VAR33*(-CONST222*VAR01*VAR13*VAR17 + VAR09*(CONST238*VAR16*VAR36 + CONST298*VAR12*VAR42 + CONST299*VAR13*VAR26) + VAR15*(CONST121*VAR01*VAR42 - CONST147*VAR13*VAR36 + CONST342*VAR12*VAR16) + VAR39*(CONST138*VAR01*VAR16 + CONST261*VAR12*VAR13)) + VAR37*(CONST095*VAR13*VAR28**4 + CONST211*VAR28*VAR29 + CONST252*VAR28**2*VAR42 + CONST329*VAR16*VAR28**3 - CONST378*VAR16**3) + VAR38*(VAR09*(CONST118*VAR16*VAR36 - CONST235*VAR12*VAR42 + CONST235*VAR13*VAR26 + CONST269*VAR01*VAR29) + VAR15*(-CONST185*VAR13*VAR36 + CONST229*VAR01*VAR42 + CONST268*VAR12*VAR16)) + VAR40*(CONST153*VAR16**3 + CONST164*VAR16*VAR28**3 + CONST320*VAR28*VAR29 + CONST348*VAR13*VAR28**4 + VAR18*(-CONST042*VAR13*VAR28**3 - CONST083*VAR28*VAR42 + CONST105*VAR16*VAR28**2 + CONST320*VAR29)) + VAR41*(VAR09*(-CONST016*VAR28**3*VAR32 + CONST061*VAR32**4 + CONST182*VAR28*VAR32**3 + CONST206*VAR28**2*VAR32**2 + CONST351*VAR28**4) + VAR15*(CONST046*VAR28**2*VAR32 - CONST046*VAR28*VAR32**2 + CONST146*VAR28**3 + CONST278*VAR32**3)) + VAR44*(VAR09*(-CONST286*VAR28**2*VAR32**2 - CONST298*VAR28*VAR32**3 + CONST346*VAR28**4 - CONST374*VAR32**4) + VAR15*(-CONST166*VAR28**3 + CONST210*VAR28**2*VAR32 + CONST220*VAR28*VAR32**2 + CONST267*VAR32**3) + VAR17*(-CONST291*VAR28 + CONST291*VAR32) + VAR39*(CONST051*VAR28**2 - CONST188*VAR32**2 - CONST308*VAR28*VAR32)) + VAR46*(-CONST165*VAR32**2*VAR36 + CONST213*VAR26*VAR32 - CONST328*VAR01*VAR32**4 + CONST330*VAR12*VAR32**3 - CONST378*VAR12**3) + VAR47*(CONST097*VAR18**2*VAR36 + CONST134*VAR18*VAR26 + CONST266*VAR12*VAR18**3 + CONST334*VAR01*VAR18**4 + CONST391*VAR12**3 + CONST398*VAR01*VAR32**4 + VAR32**3*(CONST133*VAR01*VAR18 + CONST376*VAR12) + VAR32**2*(CONST096*VAR01*VAR18**2 + CONST247*VAR12*VAR18 + CONST369*VAR36) + VAR32*(CONST139*VAR12*VAR18**2 + CONST247*VAR18*VAR36 + CONST266*VAR01*VAR18**3 + CONST375*VAR26)) diff --git a/notebooks/bwd_implementations/bwd_2.py b/notebooks/bwd_implementations/bwd_2.py new file mode 100644 index 0000000..8e33b88 --- /dev/null +++ b/notebooks/bwd_implementations/bwd_2.py @@ -0,0 +1,17 @@ +# -------------------- variable and constant definitions +CONST000 = 3.87298334620742 +CONST001 = 4.47213595499958 +CONST002 = -2.23606797749979 +CONST003 = -3.87298334620742 +VAR00 = g_1 +VAR01 = g_3 +VAR02 = g_4 +VAR03 = g_0 +VAR04 = z +VAR05 = x +VAR06 = g_2 +VAR07 = y +# -------------------- kernel implementations +g_x = CONST002*VAR05*VAR06 - CONST003*VAR00*VAR07 + CONST003*VAR02*VAR05 - CONST003*VAR03*VAR04 +g_y = CONST001*VAR06*VAR07 - CONST003*VAR00*VAR05 - CONST003*VAR01*VAR04 +g_z = CONST002*VAR04*VAR06 - CONST003*VAR01*VAR07 - CONST003*VAR02*VAR04 - CONST003*VAR03*VAR05 diff --git a/notebooks/bwd_implementations/bwd_3.py b/notebooks/bwd_implementations/bwd_3.py new file mode 100644 index 0000000..1f26a25 --- /dev/null +++ b/notebooks/bwd_implementations/bwd_3.py @@ -0,0 +1,34 @@ +# -------------------- variable and constant definitions +CONST000 = 5.12347538297980 +CONST001 = 6.27495019900557 +CONST002 = 6.48074069840786 +CONST003 = 7.93725393319377 +CONST004 = 10.2469507659596 +CONST005 = 12.9614813968157 +CONST006 = 12.5499003980111 +CONST007 = -3.96862696659689 +CONST008 = -12.5499003980111 +CONST009 = -10.2469507659596 +CONST010 = -7.93725393319377 +CONST011 = -6.27495019900557 +CONST012 = -5.12347538297980 +CONST013 = -4.86055552380590 +CONST014 = -3.24037034920393 +CONST015 = -1.62018517460197 +VAR00 = g_5 +VAR01 = g_6 +VAR02 = y**2 +VAR03 = g_4 +VAR04 = g_1 +VAR05 = z +VAR06 = x +VAR07 = g_2 +VAR08 = x**2 +VAR09 = z**2 +VAR10 = g_0 +VAR11 = g_3 +VAR12 = y +# -------------------- kernel implementations +g_x = CONST008*VAR01*VAR05*VAR06 + CONST009*VAR00*VAR06*VAR12 - CONST009*VAR04*VAR05*VAR12 + CONST010*VAR06*VAR11*VAR12 + CONST014*VAR03*VAR05*VAR06 + VAR07*(CONST002*VAR02 + CONST013*VAR08 + CONST015*VAR09) + VAR10*(CONST011*VAR08 - CONST011*VAR09) +g_y = CONST005*VAR03*VAR05*VAR12 + CONST005*VAR06*VAR07*VAR12 - CONST009*VAR04*VAR05*VAR06 + VAR00*(CONST012*VAR08 - CONST012*VAR09) + VAR11*(CONST007*VAR08 + CONST007*VAR09 - CONST010*VAR02) +g_z = -CONST008*VAR05*VAR06*VAR10 - CONST009*VAR00*VAR05*VAR12 - CONST009*VAR04*VAR06*VAR12 + CONST010*VAR05*VAR11*VAR12 + CONST014*VAR05*VAR06*VAR07 + VAR01*(CONST011*VAR08 - CONST011*VAR09) + VAR03*(CONST002*VAR02 + CONST013*VAR09 + CONST015*VAR08) diff --git a/notebooks/bwd_implementations/bwd_4.py b/notebooks/bwd_implementations/bwd_4.py new file mode 100644 index 0000000..ded7f4f --- /dev/null +++ b/notebooks/bwd_implementations/bwd_4.py @@ -0,0 +1,52 @@ +# -------------------- variable and constant definitions +CONST000 = 2.00000000000000 +CONST001 = 2.25000000000000 +CONST002 = 4.50000000000000 +CONST003 = 6.70820393249937 +CONST004 = 8.87411967464942 +CONST005 = 9.48683298050514 +CONST006 = 6.27495019900557 +CONST007 = 10.0623058987491 +CONST008 = 12.0000000000000 +CONST009 = 18.8248505970167 +CONST010 = 20.1246117974981 +CONST011 = 26.6223590239483 +CONST012 = 28.4604989415154 +CONST013 = 37.6497011940334 +CONST014 = 40.2492235949962 +CONST015 = -9.00000000000000 +CONST016 = -8.87411967464942 +CONST017 = -37.6497011940334 +CONST018 = -6.70820393249937 +CONST019 = -26.6223590239483 +CONST020 = -21.3453742061366 +CONST021 = -20.1246117974981 +CONST022 = -18.8248505970167 +CONST023 = -18.0000000000000 +CONST024 = -14.2302494707577 +CONST025 = -10.0623058987491 +CONST026 = -7.11512473537885 +CONST027 = -6.27495019900557 +CONST028 = -3.35410196624968 +VAR00 = g_7 +VAR01 = y**3 +VAR02 = g_8 +VAR03 = g_5 +VAR04 = g_6 +VAR05 = y**2 +VAR06 = g_4 +VAR07 = x**3 +VAR08 = z**3 +VAR09 = g_1 +VAR10 = z +VAR11 = x +VAR12 = g_2 +VAR13 = x**2 +VAR14 = z**2 +VAR15 = g_0 +VAR16 = g_3 +VAR17 = y +# -------------------- kernel implementations +g_x = CONST017*VAR00*VAR10*VAR11*VAR17 + CONST024*VAR03*VAR10*VAR11*VAR17 + VAR02*(-CONST016*VAR07 + CONST019*VAR11*VAR14) + VAR04*(-CONST018*VAR07 + CONST021*VAR05*VAR11) + VAR06*(CONST000*VAR11*(CONST001*VAR14 + CONST015*VAR05) + CONST002*VAR07) + VAR09*VAR17*(CONST022*VAR13 - CONST022*VAR14) + VAR12*(-CONST021*VAR05*VAR10 + CONST025*VAR10*VAR13 + CONST028*VAR08) + VAR15*(-CONST016*VAR08 + CONST019*VAR10*VAR13) + VAR16*(CONST005*VAR01 + CONST020*VAR13*VAR17 + CONST026*VAR14*VAR17) +g_y = CONST000*VAR04*VAR17*(CONST025*VAR13 - CONST025*VAR14) + CONST014*VAR10*VAR11*VAR12*VAR17 + VAR00*(CONST022*VAR10*VAR13 - CONST027*VAR08) + VAR03*(CONST026*VAR08 + VAR10*(CONST012*VAR05 + CONST026*VAR13)) + VAR06*(CONST008*VAR01 + CONST023*VAR13*VAR17 + CONST023*VAR14*VAR17) + VAR09*(-CONST022*VAR11*VAR14 + CONST027*VAR07) + VAR16*(CONST026*VAR07 + VAR11*(CONST012*VAR05 + CONST026*VAR14)) +g_z = -CONST017*VAR09*VAR10*VAR11*VAR17 + CONST024*VAR10*VAR11*VAR16*VAR17 + VAR00*VAR17*(CONST022*VAR13 - CONST022*VAR14) + VAR02*(-CONST016*VAR08 + CONST019*VAR10*VAR13) + VAR03*(CONST005*VAR01 + CONST020*VAR14*VAR17 + CONST026*VAR13*VAR17) + VAR04*(CONST018*VAR08 - CONST021*VAR05*VAR10) + VAR06*(CONST002*VAR08 + CONST002*VAR10*VAR13 + CONST023*VAR05*VAR10) + VAR12*(CONST028*VAR07 + VAR11*(-CONST021*VAR05 + CONST025*VAR14)) + VAR15*(CONST016*VAR07 - CONST019*VAR11*VAR14) diff --git a/notebooks/bwd_implementations/bwd_5.py b/notebooks/bwd_implementations/bwd_5.py new file mode 100644 index 0000000..c9d9ae2 --- /dev/null +++ b/notebooks/bwd_implementations/bwd_5.py @@ -0,0 +1,93 @@ +# -------------------- variable and constant definitions +CONST000 = 1.60565407233314 +CONST001 = 1.60565407233314 +CONST002 = 3.00000000000000 +CONST003 = 3.21130814466628 +CONST004 = 5.20291384706685 +CONST005 = 6.42261628933256 +CONST006 = 6.42261628933256 +CONST007 = 8.67152307844476 +CONST008 = 8.02827036166571 +CONST009 = 6.93721846275580 +CONST010 = 11.6340690431164 +CONST011 = 12.8452325786651 +CONST012 = 6.21867148191637 +CONST013 = 12.4373429638327 +CONST014 = 7.35803132638072 +CONST015 = 16.5831239517770 +CONST016 = 16.9926454679664 +CONST017 = 12.8452325786651 +CONST018 = 13.8744369255116 +CONST019 = 20.8116553882674 +CONST020 = 24.8746859276655 +CONST021 = 24.8746859276655 +CONST022 = 27.7488738510232 +CONST023 = 29.4321253055229 +CONST024 = 29.4321253055229 +CONST025 = 33.9852909359329 +CONST026 = 33.9852909359329 +CONST027 = 41.6233107765348 +CONST028 = 46.5362761724657 +CONST029 = 51.3809303146605 +CONST030 = 51.3809303146605 +CONST031 = 83.2466215530696 +CONST032 = 88.2963759165686 +CONST033 = 6.21867148191637 +CONST034 = 101.955872807799 +CONST035 = 8.49632273398321 +CONST036 = -8.67152307844475 +CONST037 = 3.46860923137790 +CONST038 = -88.2963759165686 +CONST039 = -83.2466215530696 +CONST040 = -69.8044142586986 +CONST041 = -50.9779364038993 +CONST042 = -50.9779364038993 +CONST043 = -46.5362761724657 +CONST044 = -44.1481879582843 +CONST045 = -41.6233107765348 +CONST046 = -38.5356977359954 +CONST047 = -38.5356977359954 +CONST048 = -33.1662479035540 +CONST049 = -33.9852909359329 +CONST050 = 6.42261628933257 +CONST051 = -33.9852909359329 +CONST052 = -29.4321253055229 +CONST053 = -27.7488738510232 +CONST054 = -20.8116553882674 +CONST055 = -19.2678488679977 +CONST056 = -19.2678488679977 +CONST057 = -16.9926454679664 +CONST058 = -16.9926454679664 +CONST059 = -16.5831239517770 +CONST060 = -13.8744369255116 +CONST061 = -8.49632273398321 +CONST062 = -6.93721846275580 +CONST063 = -5.20291384706685 +CONST064 = -3.46860923137790 +VAR00 = z**4 +VAR01 = g_6 +VAR02 = x**2 +VAR03 = g_7 +VAR04 = y +VAR05 = x**4 +VAR06 = g_8 +VAR07 = z**3 +VAR08 = z +VAR09 = x +VAR10 = g_0 +VAR11 = g_3 +VAR12 = y**3 +VAR13 = x**3 +VAR14 = g_2 +VAR15 = g_5 +VAR16 = y**2 +VAR17 = g_9 +VAR18 = g_4 +VAR19 = g_1 +VAR20 = y**4 +VAR21 = g_10 +VAR22 = z**2 +# -------------------- kernel implementations +g_x = VAR01*(CONST006*VAR07*VAR09 + VAR08*(CONST005*VAR13 + CONST046*VAR09*VAR16)) + VAR03*(CONST049*VAR09*VAR12 - CONST051*VAR04*VAR13) + VAR04*VAR17*(CONST024*VAR13 + CONST038*VAR09*VAR22) + VAR04*VAR19*(CONST038*VAR02*VAR08 - CONST052*VAR07) + VAR06*(CONST009*VAR07*VAR09 + VAR08*(CONST039*VAR09*VAR16 - CONST054*VAR13)) + VAR10*(CONST010*VAR02**2 + CONST010*VAR22**2 + CONST040*VAR02*VAR22) + VAR11*(CONST041*VAR02*VAR04*VAR08 - CONST049*VAR08*VAR12 + CONST057*VAR04*VAR07) + VAR14*(CONST002*VAR02*(CONST060*VAR16 + CONST064*VAR22) + CONST007*VAR02**2 - CONST045*VAR16*VAR22 + CONST063*VAR22**2) + VAR15*(CONST048*VAR09*VAR12 + VAR04*(CONST020*VAR09*VAR22 + CONST020*VAR13)) + VAR18*(CONST000*VAR22**2 + CONST002*VAR02*(CONST003*VAR22 + CONST055*VAR16) + CONST008*VAR02**2 + CONST011*VAR16**2 + CONST056*VAR16*VAR22) + VAR21*(CONST028*VAR08*VAR13 + CONST043*VAR07*VAR09) +g_y = VAR01*(CONST046*VAR04*VAR07 + VAR08*(CONST030*VAR12 + CONST046*VAR02*VAR04)) + VAR03*(CONST002*VAR16*(CONST057*VAR02 - CONST057*VAR22) - CONST061*VAR02**2 + CONST061*VAR22**2) + VAR06*(CONST022*VAR04*VAR07 + CONST039*VAR02*VAR04*VAR08) + VAR11*(CONST058*VAR08*VAR13 + VAR09*(CONST034*VAR08*VAR16 + CONST057*VAR07)) + VAR14*(-CONST039*VAR04*VAR09*VAR22 + CONST053*VAR04*VAR13) + VAR15*(CONST002*VAR16*(CONST059*VAR02 + CONST059*VAR22) + CONST012*VAR02**2 + CONST013*VAR02*VAR22 + CONST033*VAR22**2 - CONST059*VAR16**2) + VAR17*(CONST014*VAR02**2 + CONST014*VAR22**2 + CONST044*VAR02*VAR22) + VAR18*(CONST047*VAR04*VAR13 + VAR09*(CONST029*VAR12 + CONST046*VAR04*VAR22)) + VAR19*(-CONST052*VAR07*VAR09 + CONST052*VAR08*VAR13) +g_z = VAR01*(CONST001*VAR02**2 + CONST002*VAR22*(CONST003*VAR02 + CONST056*VAR16) + CONST008*VAR22**2 + CONST017*VAR16**2 + CONST056*VAR02*VAR16) + VAR03*(-CONST049*VAR08*VAR12 + CONST051*VAR04*VAR07) + VAR04*VAR17*(CONST024*VAR07 + CONST038*VAR02*VAR08) + VAR04*VAR19*(-CONST038*VAR09*VAR22 + CONST052*VAR13) + VAR06*(CONST002*VAR22*(CONST018*VAR16 + CONST037*VAR02) + CONST036*VAR22**2 + CONST045*VAR02*VAR16 - CONST063*VAR02**2) + VAR10*(CONST028*VAR07*VAR09 + CONST043*VAR08*VAR13) + VAR11*(CONST058*VAR04*VAR13 + VAR09*(CONST042*VAR04*VAR22 - CONST049*VAR12)) + VAR14*(CONST062*VAR08*VAR13 + VAR09*(-CONST039*VAR08*VAR16 + CONST054*VAR07)) + VAR15*(CONST048*VAR08*VAR12 + VAR04*(CONST020*VAR02*VAR08 + CONST021*VAR07)) + VAR18*(CONST006*VAR08*VAR13 + VAR09*(CONST046*VAR08*VAR16 + CONST050*VAR07)) + VAR21*(CONST010*VAR02**2 + CONST010*VAR22**2 + CONST040*VAR02*VAR22) diff --git a/notebooks/bwd_implementations/bwd_6.py b/notebooks/bwd_implementations/bwd_6.py new file mode 100644 index 0000000..418658a --- /dev/null +++ b/notebooks/bwd_implementations/bwd_6.py @@ -0,0 +1,121 @@ +# -------------------- variable and constant definitions +CONST000 = 1.63279380970164 +CONST001 = 2.00000000000000 +CONST002 = 3.26558761940328 +CONST003 = 4.00000000000000 +CONST004 = 3.00000000000000 +CONST005 = 6.53117523880657 +CONST006 = 7.15454401062709 +CONST007 = 8.94318001328386 +CONST008 = 8.38944649544891 +CONST009 = 10.3266947761614 +CONST010 = 9.79676285820985 +CONST011 = 3.26558761940328 +CONST012 = 9.79676285820985 +CONST013 = 14.5309475774982 +CONST014 = 16.3279380970164 +CONST015 = 17.8863600265677 +CONST016 = 16.5227116418583 +CONST017 = 19.5935257164197 +CONST018 = 20.6533895523229 +CONST019 = 20.2812259244849 +CONST020 = 21.6333076527839 +CONST021 = 17.8863600265677 +CONST022 = 16.5227116418583 +CONST023 = 26.1247009552263 +CONST024 = 29.3902885746295 +CONST025 = 35.7727200531355 +CONST026 = 35.7727200531355 +CONST027 = 39.1870514328394 +CONST028 = 40.5624518489699 +CONST029 = 41.3067791046458 +CONST030 = 41.9472324772445 +CONST031 = 48.9838142910493 +CONST032 = 51.6334738808072 +CONST033 = 52.2494019104525 +CONST034 = 58.7805771492591 +CONST035 = 71.5454401062709 +CONST036 = 72.6547378874909 +CONST037 = 71.5454401062709 +CONST038 = 78.3741028656788 +CONST039 = 81.1249036979398 +CONST040 = 82.6135582092915 +CONST041 = 82.6135582092915 +CONST042 = -3.26558761940328 +CONST043 = 104.498803820905 +CONST044 = 117.561154298518 +CONST045 = 145.309475774982 +CONST046 = 156.748205731358 +CONST047 = 167.788929908978 +CONST048 = 208.997607641810 +CONST049 = 214.636320318813 +CONST050 = -251.683394863467 +CONST051 = -214.636320318813 +CONST052 = -214.636320318813 +CONST053 = -167.788929908978 +CONST054 = -156.748205731358 +CONST055 = -145.309475774982 +CONST056 = -123.920337313937 +CONST057 = -117.561154298518 +CONST058 = -108.166538263920 +CONST059 = -107.318160159406 +CONST060 = -104.498803820905 +CONST061 = -104.498803820905 +CONST062 = -83.8944649544891 +CONST063 = -82.6135582092915 +CONST064 = -78.3741028656788 +CONST065 = -72.6547378874909 +CONST066 = -71.5454401062709 +CONST067 = -58.7805771492591 +CONST068 = -54.0832691319598 +CONST069 = -52.2494019104525 +CONST070 = -52.2494019104525 +CONST071 = -48.9838142910492 +CONST072 = -41.3067791046458 +CONST073 = -39.1870514328394 +CONST074 = -35.7727200531355 +CONST075 = -29.3902885746295 +CONST076 = -27.0416345659799 +CONST077 = -26.1247009552263 +CONST078 = -26.1247009552263 +CONST079 = -19.5935257164197 +CONST080 = -14.5309475774982 +CONST081 = -13.5208172829900 +CONST082 = -10.7318160159406 +CONST083 = -9.79676285820985 +CONST084 = -7.15454401062709 +CONST085 = -6.76040864149498 +CONST086 = -3.38020432074749 +CONST087 = -1.63279380970164 +VAR00 = z**4 +VAR01 = g_6 +VAR02 = x**2 +VAR03 = g_7 +VAR04 = y +VAR05 = x**4 +VAR06 = g_8 +VAR07 = z**3 +VAR08 = z +VAR09 = x +VAR10 = z**5 +VAR11 = g_0 +VAR12 = g_3 +VAR13 = y**3 +VAR14 = y**5 +VAR15 = g_12 +VAR16 = x**3 +VAR17 = g_2 +VAR18 = x**5 +VAR19 = g_5 +VAR20 = y**2 +VAR21 = g_9 +VAR22 = g_4 +VAR23 = g_1 +VAR24 = g_11 +VAR25 = y**4 +VAR26 = g_10 +VAR27 = z**2 +# -------------------- kernel implementations +g_x = VAR01*(CONST001*VAR09*(CONST028*VAR20*VAR27 + CONST076*VAR20**2 + CONST086*VAR27**2) + CONST003*VAR16*(CONST019*VAR20 + CONST086*VAR27) + CONST085*VAR18) + VAR03*(-CONST072*VAR04*VAR07*VAR09 + VAR08*(CONST063*VAR09*VAR13 - CONST072*VAR04*VAR16)) + VAR04*VAR23*(CONST030*VAR02**2 + CONST030*VAR27**2 + CONST050*VAR02*VAR27) + VAR04*VAR24*(CONST053*VAR07*VAR09 - CONST053*VAR08*VAR16) + VAR06*(CONST001*VAR09*(CONST077*VAR20**2 - CONST087*VAR27**2) + CONST003*VAR16*(-CONST077*VAR20 + CONST087*VAR27) + CONST083*VAR18) + VAR11*(CONST055*VAR02*VAR07 - CONST065*VAR02**2*VAR08 - CONST080*VAR10) + VAR12*(VAR04*(CONST031*VAR02**2 + CONST067*VAR02*VAR27 + CONST075*VAR27**2) + VAR13*(CONST064*VAR02 - CONST064*VAR27)) + VAR15*(-CONST055*VAR16*VAR27 + CONST065*VAR09*VAR27**2 + CONST080*VAR18) + VAR17*(-CONST074*VAR02**2*VAR08 + CONST084*VAR10 + VAR20*(CONST051*VAR02*VAR08 - CONST066*VAR07)) + VAR19*(CONST004*VAR02*(CONST018*VAR04*VAR27 + CONST072*VAR13) + CONST009*VAR04*VAR27**2 + CONST016*VAR14 + CONST032*VAR02**2*VAR04 + CONST072*VAR13*VAR27) + VAR21*(CONST054*VAR08*VAR09*VAR13 + VAR04*(CONST044*VAR08*VAR16 - CONST073*VAR07*VAR09)) + VAR22*(CONST004*VAR02*(CONST005*VAR07 + CONST069*VAR08*VAR20) + CONST014*VAR02**2*VAR08 - CONST042*VAR10 + CONST070*VAR07*VAR20 - CONST070*VAR08*VAR20**2) + VAR26*(CONST001*VAR09*(CONST007*VAR27**2 + CONST059*VAR20*VAR27) + CONST003*VAR16*(CONST007*VAR27 + CONST015*VAR20) + CONST082*VAR18) +g_y = CONST001*VAR04*VAR17*(-CONST066*VAR07*VAR09 + CONST066*VAR08*VAR16) + VAR01*(CONST020*VAR14 + CONST028*VAR02**2*VAR04 + CONST028*VAR04*VAR27**2 + CONST058*VAR13*VAR27 + VAR02*(CONST039*VAR04*VAR27 + CONST058*VAR13)) + VAR03*(CONST009*VAR10 + VAR07*(CONST018*VAR02 + CONST056*VAR20) + VAR08*(CONST009*VAR02**2 + CONST041*VAR20**2 + CONST056*VAR02*VAR20)) + VAR06*(CONST060*VAR02*VAR13 - CONST060*VAR13*VAR27 + CONST069*VAR04*VAR27**2 - CONST070*VAR02**2*VAR04) + VAR12*(CONST004*VAR20*(-CONST064*VAR09*VAR27 + CONST078*VAR16) + CONST010*VAR18 + CONST075*VAR09*VAR27**2 + CONST079*VAR16*VAR27) + VAR19*(CONST009*VAR18 + VAR09*(CONST009*VAR27**2 + CONST056*VAR20*VAR27 - CONST063*VAR20**2) + VAR16*(CONST018*VAR27 + CONST056*VAR20)) + VAR21*(CONST004*VAR20*(CONST064*VAR02*VAR08 - CONST077*VAR07) + CONST024*VAR02**2*VAR08 - CONST079*VAR02*VAR07 + CONST083*VAR10) + VAR22*(CONST061*VAR04*VAR08*VAR16 + VAR09*(CONST048*VAR08*VAR13 + CONST060*VAR04*VAR07)) + VAR23*(CONST008*VAR18 + CONST030*VAR09*VAR27**2 + CONST062*VAR16*VAR27) + VAR24*(CONST008*VAR10 + CONST030*VAR02**2*VAR08 + CONST062*VAR02*VAR07) + VAR26*(CONST026*VAR02**2*VAR04 + CONST052*VAR02*VAR04*VAR27 - CONST074*VAR04*VAR27**2) +g_z = VAR01*(CONST039*VAR07*VAR20 + CONST068*VAR08*VAR20**2 + CONST085*VAR02**2*VAR08 + CONST085*VAR10 + VAR02*(CONST039*VAR08*VAR20 + CONST081*VAR07)) + VAR03*(CONST004*VAR27*(CONST018*VAR02*VAR04 + CONST072*VAR13) + CONST009*VAR02**2*VAR04 + CONST022*VAR14 + CONST032*VAR04*VAR27**2 + CONST072*VAR02*VAR13) + VAR04*VAR23*(-CONST053*VAR07*VAR09 + CONST053*VAR08*VAR16) + VAR04*VAR24*(CONST030*VAR02**2 + CONST030*VAR27**2 + CONST050*VAR02*VAR27) + VAR06*(CONST005*VAR02*VAR07 + CONST042*VAR02**2*VAR08 + CONST061*VAR07*VAR20 - CONST070*VAR08*VAR20**2 - CONST083*VAR10) + VAR11*(CONST055*VAR16*VAR27 - CONST065*VAR09*VAR27**2 - CONST080*VAR18) + VAR12*(-CONST054*VAR08*VAR09*VAR13 + VAR04*(CONST057*VAR07*VAR09 + CONST073*VAR08*VAR16)) + VAR15*(CONST055*VAR02*VAR07 - CONST065*VAR02**2*VAR08 - CONST080*VAR10) + VAR17*(CONST074*VAR09*VAR27**2 - CONST084*VAR18 + VAR20*(-CONST051*VAR09*VAR27 + CONST066*VAR16)) + VAR19*(-CONST072*VAR04*VAR08*VAR16 + VAR09*(CONST063*VAR08*VAR13 - CONST072*VAR04*VAR07)) + VAR21*(VAR04*(CONST024*VAR02**2 - CONST067*VAR02*VAR27 + CONST071*VAR27**2) + VAR13*(CONST064*VAR02 - CONST064*VAR27)) + VAR22*(CONST011*VAR18 + VAR09*(CONST014*VAR27**2 + CONST054*VAR20*VAR27 - CONST070*VAR20**2) + VAR16*(CONST069*VAR20 - CONST079*VAR27)) + VAR26*(CONST021*VAR02**2*VAR08 + CONST037*VAR07*VAR20 + CONST082*VAR10 + VAR02*(CONST052*VAR08*VAR20 - CONST074*VAR07)) diff --git a/notebooks/bwd_implementations/bwd_7.py b/notebooks/bwd_implementations/bwd_7.py new file mode 100644 index 0000000..f03f5d1 --- /dev/null +++ b/notebooks/bwd_implementations/bwd_7.py @@ -0,0 +1,195 @@ +# -------------------- variable and constant definitions +CONST000 = 1.66389743899677 +CONST001 = 3.00000000000000 +CONST002 = 4.99169231699030 +CONST003 = 5.00000000000000 +CONST004 = 3.32779487799353 +CONST005 = 8.31948719498384 +CONST006 = 9.19753915797590 +CONST007 = 9.37968632871057 +CONST008 = 11.7655316231354 +CONST009 = 11.7655316231354 +CONST010 = 11.6472820729774 +CONST011 = 9.19753915797590 +CONST012 = 16.5555704843566 +CONST013 = 17.5477863187212 +CONST014 = 20.4939015319192 +CONST015 = 22.0740939791422 +CONST016 = 23.5310632462709 +CONST017 = 23.5310632462709 +CONST018 = 532.447180478965 +CONST019 = 20.4939015319192 +CONST020 = 27.1108834234519 +CONST021 = 29.9501539019418 +CONST022 = 33.1111409687132 +CONST023 = 33.2779487799353 +CONST024 = 36.7901566319036 +CONST025 = 36.7901566319036 +CONST026 = 38.4260653723485 +CONST027 = 38.4260653723485 +CONST028 = 37.6497011940334 +CONST029 = 38.4260653723485 +CONST030 = 44.1481879582843 +CONST031 = 44.1481879582843 +CONST032 = -4.99169231699030 +CONST033 = 47.0621264925418 +CONST034 = 44.3705983732471 +CONST035 = 47.0621264925417 +CONST036 = 562.781179722634 +CONST037 = 50.8329064189723 +CONST038 = 55.1852349478554 +CONST039 = 56.2781179722634 +CONST040 = 56.2781179722634 +CONST041 = 62.7495019900557 +CONST042 = 66.5558975598707 +CONST043 = 70.5931897388126 +CONST044 = -441.481879582843 +CONST045 = -441.481879582843 +CONST046 = 75.2994023880668 +CONST047 = 76.8521307446970 +CONST048 = 76.8521307446970 +CONST049 = 76.8521307446970 +CONST050 = -8.47215106982872 +CONST051 = 99.8338463398060 +CONST052 = 101.665812837945 +CONST053 = 105.286717912327 +CONST054 = 110.370469895711 +CONST055 = 110.370469895711 +CONST056 = -399.335385359224 +CONST057 = 117.655316231354 +CONST058 = 122.963409191515 +CONST059 = 122.963409191515 +CONST060 = 133.111795119741 +CONST061 = -376.497011940334 +CONST062 = -376.497011940334 +CONST063 = 140.695294930659 +CONST064 = 141.186379477625 +CONST065 = 147.160626527614 +CONST066 = 147.160626527614 +CONST067 = 153.704261489394 +CONST068 = 153.704261489394 +CONST069 = -350.955726374425 +CONST070 = 177.482393492989 +CONST071 = 199.667692679612 +CONST072 = 203.331625675889 +CONST073 = 203.331625675889 +CONST074 = -307.408522978788 +CONST075 = -9.60651634308713 +CONST076 = -9.37968632871057 +CONST077 = 220.740939791422 +CONST078 = 220.740939791422 +CONST079 = -281.390589861317 +CONST080 = -1.66389743899677 +CONST081 = -266.223590239483 +CONST082 = -263.216794780819 +CONST083 = 250.998007960223 +CONST084 = -263.216794780818 +CONST085 = -250.998007960223 +CONST086 = 263.216794780818 +CONST087 = 263.216794780819 +CONST088 = 266.223590239483 +CONST089 = 281.390589861317 +CONST090 = 281.390589861317 +CONST091 = -220.740939791422 +CONST092 = -220.740939791422 +CONST093 = -199.667692679612 +CONST094 = -1.60108605718119 +CONST095 = -187.593726574211 +CONST096 = -177.482393492989 +CONST097 = -9.60651634308712 +CONST098 = -9.19753915797590 +CONST099 = 350.955726374425 +CONST100 = -153.704261489394 +CONST101 = -147.160626527614 +CONST102 = -140.695294930659 +CONST103 = 376.497011940334 +CONST104 = -133.111795119741 +CONST105 = -133.111795119741 +CONST106 = -125.499003980111 +CONST107 = -125.499003980111 +CONST108 = 399.335385359224 +CONST109 = -105.286717912327 +CONST110 = -101.665812837945 +CONST111 = -99.8338463398060 +CONST112 = -101.665812837945 +CONST113 = -4.80325817154356 +CONST114 = -81.3326502703558 +CONST115 = -81.3326502703557 +CONST116 = -76.8521307446970 +CONST117 = -75.2994023880668 +CONST118 = 441.481879582843 +CONST119 = -70.5931897388126 +CONST120 = 441.481879582843 +CONST121 = -66.2222819374265 +CONST122 = -66.5558975598707 +CONST123 = -66.5558975598707 +CONST124 = -62.7495019900557 +CONST125 = -56.2781179722634 +CONST126 = -55.1852349478554 +CONST127 = -55.1852349478554 +CONST128 = -50.8329064189723 +CONST129 = -50.8329064189723 +CONST130 = -47.0621264925418 +CONST131 = -562.781179722634 +CONST132 = -50.8329064189724 +CONST133 = -44.1481879582843 +CONST134 = -44.3705983732471 +CONST135 = -40.6663251351779 +CONST136 = -40.6663251351779 +CONST137 = -8.31948719498384 +CONST138 = -37.6497011940334 +CONST139 = -33.2779487799353 +CONST140 = -29.9501539019418 +CONST141 = -25.4164532094862 +CONST142 = -25.4164532094862 +CONST143 = -23.5310632462709 +CONST144 = -532.447180478965 +CONST145 = -19.2130326861743 +CONST146 = -17.5477863187212 +CONST147 = -12.8765548211663 +CONST148 = -11.6472820729774 +CONST149 = -11.2076024002683 +CONST150 = -9.19753915797590 +CONST151 = -11.0370469895711 +CONST152 = -11.7655316231354 +CONST153 = -12.8765548211663 +CONST154 = -4.80325817154356 +CONST155 = -3.32779487799353 +CONST156 = -1.60108605718119 +VAR00 = z**4 +VAR01 = g_6 +VAR02 = g_14 +VAR03 = z**6 +VAR04 = x**2 +VAR05 = g_7 +VAR06 = y +VAR07 = y**6 +VAR08 = x**4 +VAR09 = g_8 +VAR10 = z**3 +VAR11 = x**6 +VAR12 = z +VAR13 = x +VAR14 = z**5 +VAR15 = g_0 +VAR16 = g_3 +VAR17 = y**3 +VAR18 = y**5 +VAR19 = g_12 +VAR20 = x**3 +VAR21 = g_2 +VAR22 = x**5 +VAR23 = g_5 +VAR24 = g_13 +VAR25 = y**2 +VAR26 = g_9 +VAR27 = g_4 +VAR28 = g_1 +VAR29 = g_11 +VAR30 = y**4 +VAR31 = g_10 +VAR32 = z**2 +# -------------------- kernel implementations +g_x = VAR01*(CONST001*VAR04*(CONST116*VAR25**2 - CONST116*VAR25*VAR32 + CONST154*VAR32**2) + CONST003*VAR04**2*(CONST026*VAR25 + CONST113*VAR32) + CONST014*VAR25**3 + CONST027*VAR25*VAR32**2 + CONST116*VAR25**2*VAR32 + CONST149*VAR04**3 + CONST156*VAR32**3) + VAR02*(-CONST069*VAR10*VAR20 + CONST109*VAR12*VAR22 + CONST109*VAR13*VAR14) + VAR05*(CONST114*VAR13*VAR18 + VAR06*(CONST110*VAR20*VAR32 + CONST128*VAR22 + CONST129*VAR13*VAR32**2) + VAR17*(CONST072*VAR20 + CONST073*VAR13*VAR32)) + VAR06*VAR24*(CONST079*VAR13*VAR32**2 + CONST125*VAR22 - CONST131*VAR20*VAR32) + VAR06*VAR28*(CONST039*VAR14 + CONST089*VAR04**2*VAR12 + CONST131*VAR04*VAR10) + VAR09*(CONST075*VAR13*VAR14 + VAR10*(-CONST100*VAR13*VAR25 + CONST145*VAR20) + VAR12*(CONST067*VAR20*VAR25 + CONST097*VAR22 + CONST100*VAR13*VAR25**2)) + VAR15*(CONST082*VAR04*VAR32**2 - CONST084*VAR04**2*VAR32 + CONST146*VAR04**3 - CONST146*VAR32**3) + VAR16*(VAR06*(-CONST091*VAR04**2*VAR12 + CONST133*VAR14) + VAR17*(CONST044*VAR04*VAR12 + CONST066*VAR10)) + VAR19*(CONST022*VAR13*VAR14 + VAR10*(CONST024*VAR20 + CONST045*VAR13*VAR25) + VAR12*(-CONST044*VAR20*VAR25 + CONST126*VAR22)) + VAR21*(CONST001*VAR04*(CONST091*VAR25*VAR32 - CONST150*VAR32**2) + CONST003*VAR04**2*(CONST012*VAR32 + CONST015*VAR25) + CONST055*VAR25*VAR32**2 + CONST147*VAR04**3 + CONST150*VAR32**3) + VAR23*(CONST001*VAR04*(CONST106*VAR12*VAR17 - CONST130*VAR06*VAR10) + CONST057*VAR04**2*VAR06*VAR12 + CONST107*VAR10*VAR17 - CONST117*VAR12*VAR18 - CONST143*VAR06*VAR14) + VAR26*(-CONST085*VAR17*VAR20 + CONST117*VAR13*VAR18 + VAR06*(CONST017*VAR13*VAR32**2 + CONST119*VAR22 + CONST130*VAR20*VAR32)) + VAR27*(CONST001*VAR04*(CONST122*VAR25*VAR32 + CONST134*VAR25**2 - CONST137*VAR32**2) + CONST003*VAR04**2*(CONST000*VAR32 - CONST139*VAR25) - CONST032*VAR32**3 - CONST105*VAR25**2*VAR32 + CONST111*VAR25*VAR32**2 + CONST148*VAR04**3) + VAR29*(VAR06*(CONST054*VAR13*VAR32**2 - CONST091*VAR20*VAR32 + CONST121*VAR22) + VAR17*(CONST044*VAR13*VAR32 - CONST101*VAR20)) + VAR31*(CONST155*VAR13*VAR14 + VAR10*(-CONST105*VAR13*VAR25 + CONST139*VAR20) + VAR12*(-CONST056*VAR20*VAR25 + CONST081*VAR13*VAR25**2 + CONST140*VAR22)) +g_y = VAR01*(CONST048*VAR06*VAR22 + VAR13*(CONST058*VAR18 + CONST074*VAR17*VAR32 - CONST116*VAR06*VAR32**2) + VAR20*(CONST074*VAR17 - CONST100*VAR06*VAR32)) + VAR05*(CONST001*VAR25*(-CONST112*VAR04*VAR32 - CONST128*VAR04**2 - CONST128*VAR32**2) + CONST003*VAR25**2*(CONST135*VAR04 + CONST136*VAR32) + CONST020*VAR25**3 + CONST050*VAR04**3 + CONST050*VAR32**3 + CONST141*VAR04**2*VAR32 + CONST142*VAR04*VAR32**2) + VAR09*(CONST048*VAR06*VAR14 + VAR10*(CONST074*VAR17 - CONST100*VAR04*VAR06) + VAR12*(CONST049*VAR04**2*VAR06 + CONST059*VAR18 + CONST074*VAR04*VAR17)) + VAR16*(CONST001*VAR25*(CONST066*VAR10*VAR13 + CONST101*VAR12*VAR20) - CONST133*VAR12*VAR22 + CONST133*VAR13*VAR14) + VAR19*(CONST030*VAR06*VAR14 + CONST045*VAR04*VAR06*VAR10 - CONST092*VAR04**2*VAR06*VAR12) + VAR21*(CONST030*VAR06*VAR22 + CONST045*VAR06*VAR20*VAR32 - CONST092*VAR06*VAR13*VAR32**2) + VAR23*(-CONST143*VAR12*VAR22 + VAR13*(CONST061*VAR10*VAR25 - CONST062*VAR12*VAR25**2 - CONST143*VAR14) + VAR20*(CONST062*VAR12*VAR25 - CONST130*VAR10)) + VAR24*(CONST076*VAR04**3 - CONST076*VAR32**3 - CONST102*VAR04**2*VAR32 + CONST102*VAR04*VAR32**2) + VAR26*(CONST001*VAR25*(-CONST124*VAR04**2 + CONST124*VAR32**2) + CONST003*VAR25**2*(CONST138*VAR04 - CONST138*VAR32) + CONST009*VAR04*VAR32**2 + CONST152*VAR04**3 + CONST152*VAR04**2*VAR32 - CONST152*VAR32**3) + VAR27*(-CONST123*VAR06*VAR22 + VAR13*(CONST093*VAR06*VAR32**2 - CONST144*VAR17*VAR32) + VAR20*(CONST096*VAR17 + CONST104*VAR06*VAR32)) + VAR28*(CONST039*VAR13*VAR14 + CONST095*VAR10*VAR20 - CONST125*VAR12*VAR22) + VAR29*(CONST001*VAR25*(CONST025*VAR04**2 + CONST025*VAR32**2 + CONST092*VAR04*VAR32) - CONST126*VAR04**2*VAR32 - CONST126*VAR04*VAR32**2 + CONST151*VAR04**3 + CONST151*VAR32**3) + VAR31*(CONST123*VAR06*VAR14 + VAR10*(-CONST096*VAR17 - CONST105*VAR04*VAR06) + VAR12*(-CONST093*VAR04**2*VAR06 + CONST144*VAR04*VAR17)) +g_z = VAR01*(CONST097*VAR12*VAR22 + VAR13*(CONST075*VAR14 - CONST100*VAR10*VAR25 + CONST100*VAR12*VAR25**2) + VAR20*(-CONST100*VAR12*VAR25 + CONST145*VAR10)) + VAR02*(-CONST082*VAR04**2*VAR32 + CONST084*VAR04*VAR32**2 + CONST146*VAR04**3 - CONST146*VAR32**3) + VAR05*(CONST115*VAR12*VAR18 + VAR06*(CONST112*VAR04*VAR10 + CONST128*VAR14 + CONST132*VAR04**2*VAR12) + VAR17*(CONST072*VAR10 + CONST073*VAR04*VAR12)) + VAR06*VAR24*(-CONST079*VAR04**2*VAR12 - CONST125*VAR14 + CONST131*VAR04*VAR10) + VAR06*VAR28*(-CONST079*VAR13*VAR32**2 - CONST125*VAR22 + CONST131*VAR20*VAR32) + VAR09*(CONST001*VAR32*(-CONST116*VAR04*VAR25 + CONST116*VAR25**2 + CONST154*VAR04**2) + CONST003*VAR32**2*(CONST026*VAR25 + CONST154*VAR04) + CONST019*VAR25**3 + CONST029*VAR04**2*VAR25 + CONST094*VAR04**3 + CONST116*VAR04*VAR25**2 + CONST149*VAR32**3) + VAR15*(CONST069*VAR10*VAR20 - CONST109*VAR12*VAR22 - CONST109*VAR13*VAR14) + VAR16*(VAR06*(CONST091*VAR13*VAR32**2 - CONST133*VAR22) + VAR17*(-CONST045*VAR13*VAR32 + CONST101*VAR20)) + VAR19*(CONST001*VAR32*(CONST091*VAR04*VAR25 - CONST098*VAR04**2) + CONST003*VAR32**2*(CONST012*VAR04 + CONST015*VAR25) + CONST055*VAR04**2*VAR25 + CONST098*VAR04**3 + CONST153*VAR32**3) + VAR21*(CONST022*VAR12*VAR22 + VAR13*(-CONST044*VAR10*VAR25 + CONST127*VAR14) + VAR20*(CONST025*VAR10 + CONST045*VAR12*VAR25)) + VAR23*(-CONST143*VAR06*VAR22 + VAR13*(CONST057*VAR06*VAR32**2 + CONST061*VAR17*VAR32 - CONST117*VAR18) + VAR20*(CONST064*VAR06*VAR32 + CONST106*VAR17)) + VAR26*(CONST085*VAR10*VAR17 - CONST117*VAR12*VAR18 + VAR06*(CONST035*VAR04*VAR10 - CONST119*VAR14 + CONST143*VAR04**2*VAR12)) + VAR27*(CONST004*VAR12*VAR22 + VAR13*(CONST056*VAR10*VAR25 - CONST081*VAR12*VAR25**2 - CONST140*VAR14) + VAR20*(CONST104*VAR12*VAR25 - CONST139*VAR10)) + VAR29*(VAR06*(CONST054*VAR04**2*VAR12 - CONST091*VAR04*VAR10 + CONST121*VAR14) + VAR17*(CONST044*VAR04*VAR12 - CONST101*VAR10)) + VAR31*(CONST001*VAR32*(-CONST123*VAR04*VAR25 - CONST134*VAR25**2 + CONST137*VAR04**2) + CONST003*VAR32**2*(CONST080*VAR04 + CONST139*VAR25) + CONST032*VAR04**3 + CONST105*VAR04*VAR25**2 - CONST111*VAR04**2*VAR25 - CONST148*VAR32**3) diff --git a/notebooks/bwd_implementations/bwd_8.py b/notebooks/bwd_implementations/bwd_8.py new file mode 100644 index 0000000..2428c99 --- /dev/null +++ b/notebooks/bwd_implementations/bwd_8.py @@ -0,0 +1,270 @@ +# -------------------- variable and constant definitions +CONST000 = 2.00000000000000 +CONST001 = 3.00000000000000 +CONST002 = 4.50964677801932 +CONST003 = 517.445649319810 +CONST004 = 5.00000000000000 +CONST005 = 6.78376969317208 +CONST006 = 4.00000000000000 +CONST007 = 9.01929355603863 +CONST008 = 6.76447016702898 +CONST009 = 6.00000000000000 +CONST010 = 12.9361412329953 +CONST011 = 13.5675393863442 +CONST012 = 13.1367135230810 +CONST013 = 15.0965641786467 +CONST014 = 10.3359109268366 +CONST015 = 13.1367135230810 +CONST016 = 525.468540923241 +CONST017 = 19.4042118494929 +CONST018 = 20.6718218536732 +CONST019 = 24.7386337537060 +CONST020 = -489.184589393411 +CONST021 = 1050.93708184648 +CONST022 = 26.4189873126318 +CONST023 = 26.2734270461621 +CONST024 = 27.0578806681159 +CONST025 = 24.7386337537060 +CONST026 = 32.9848450049413 +CONST027 = 33.9188484658604 +CONST028 = 550.332663067587 +CONST029 = 39.4101405692431 +CONST030 = -978.369178786822 +CONST031 = 47.4863878522046 +CONST032 = 48.5105296237322 +CONST033 = 1585.13923875791 +CONST034 = 48.9184589393411 +CONST035 = 51.7445649319810 +CONST036 = 52.8379746252636 +CONST037 = 1085.27064731784 +CONST038 = 61.1480736741764 +CONST039 = 61.1480736741764 +CONST040 = 65.6835676154051 +CONST041 = 1085.40315090753 +CONST042 = 67.8376969317208 +CONST043 = 1085.27064731784 +CONST044 = -1467.55376818023 +CONST045 = 70.0624721230988 +CONST046 = 582.126355484786 +CONST047 = 72.3513764878561 +CONST048 = -437.890450769368 +CONST049 = -434.108258927137 +CONST050 = -434.108258927137 +CONST051 = 79.2569619378954 +CONST052 = -432.926090689854 +CONST053 = 87.5780901538735 +CONST054 = -1447.02752975712 +CONST055 = 91.9569946615672 +CONST056 = -420.374832738593 +CONST057 = 6.46807061649763 +CONST058 = 97.0210592474644 +CONST059 = 97.0210592474644 +CONST060 = 103.489129863962 +CONST061 = -407.026181590325 +CONST062 = 103.489129863962 +CONST063 = 108.231522672464 +CONST064 = 108.231522672464 +CONST065 = 110.066532613517 +CONST066 = 110.066532613517 +CONST067 = 620.934779183772 +CONST068 = -396.284809689477 +CONST069 = 129.361412329953 +CONST070 = 132.094936563159 +CONST071 = 434.108258927137 +CONST072 = 649.389136034782 +CONST073 = 649.389136034781 +CONST074 = 434.108258927137 +CONST075 = 144.702752975712 +CONST076 = -366.888442045058 +CONST077 = -366.888442045058 +CONST078 = -361.756882439281 +CONST079 = 158.513923875791 +CONST080 = -6.78376969317208 +CONST081 = 162.810472636130 +CONST082 = -350.312360615494 +CONST083 = -346.340872551883 +CONST084 = -346.340872551883 +CONST085 = 173.170436275942 +CONST086 = 173.170436275942 +CONST087 = 175.156180307747 +CONST088 = 183.444221022529 +CONST089 = 183.444221022529 +CONST090 = -325.620945272260 +CONST091 = -13.5289403340579 +CONST092 = -13.5675393863442 +CONST093 = 194.042118494929 +CONST094 = 194.042118494929 +CONST095 = 197.050702846215 +CONST096 = -11.3224231339851 +CONST097 = 203.513090795162 +CONST098 = -814.052363180650 +CONST099 = 723.513764878561 +CONST100 = 210.187416369296 +CONST101 = 210.187416369296 +CONST102 = -814.052363180650 +CONST103 = 216.463045344927 +CONST104 = 217.054129463568 +CONST105 = 220.133065227035 +CONST106 = -291.063177742393 +CONST107 = 220.133065227035 +CONST108 = 216.463045344927 +CONST109 = -792.569619378954 +CONST110 = 236.460843415458 +CONST111 = -271.350787726883 +CONST112 = 244.592294696705 +CONST113 = 244.592294696706 +CONST114 = 244.592294696706 +CONST115 = -776.168473979715 +CONST116 = -262.734270461621 +CONST117 = -259.755654413913 +CONST118 = -258.722824659905 +CONST119 = 262.734270461621 +CONST120 = 262.734270461621 +CONST121 = -244.215708954195 +CONST122 = 271.350787726883 +CONST123 = 271.350787726883 +CONST124 = -236.460843415458 +CONST125 = 792.569619378954 +CONST126 = 291.063177742393 +CONST127 = -217.054129463568 +CONST128 = -216.463045344927 +CONST129 = -216.463045344927 +CONST130 = -216.463045344927 +CONST131 = -723.513764878561 +CONST132 = 814.052363180650 +CONST133 = -210.187416369296 +CONST134 = -210.187416369296 +CONST135 = 814.052363180650 +CONST136 = -197.050702846215 +CONST137 = 317.027847751582 +CONST138 = -194.042118494929 +CONST139 = -13.1367135230810 +CONST140 = 324.694568017391 +CONST141 = 325.620945272260 +CONST142 = 324.694568017391 +CONST143 = -175.156180307747 +CONST144 = 350.312360615494 +CONST145 = -162.810472636130 +CONST146 = -162.347284008695 +CONST147 = 865.852181379709 +CONST148 = -158.513923875791 +CONST149 = 361.756882439281 +CONST150 = -144.702752975712 +CONST151 = -649.389136034782 +CONST152 = -129.877827206956 +CONST153 = -129.361412329953 +CONST154 = 388.084236989858 +CONST155 = 396.284809689477 +CONST156 = -115.446957517294 +CONST157 = -108.231522672464 +CONST158 = -108.231522672464 +CONST159 = 407.026181590325 +CONST160 = -103.489129863962 +CONST161 = -97.0210592474644 +CONST162 = -94.7025823384056 +CONST163 = 420.374832738593 +CONST164 = -91.9569946615672 +CONST165 = 1447.02752975712 +CONST166 = -87.5780901538735 +CONST167 = -85.6073031438469 +CONST168 = -85.6073031438469 +CONST169 = -81.1736420043477 +CONST170 = 432.926090689854 +CONST171 = -79.2569619378954 +CONST172 = -81.1736420043477 +CONST173 = 432.926090689854 +CONST174 = 437.890450769368 +CONST175 = 434.108258927137 +CONST176 = -79.2569619378954 +CONST177 = -72.3513764878561 +CONST178 = -72.1543484483091 +CONST179 = -70.0624721230988 +CONST180 = -72.1543484483091 +CONST181 = -67.8376969317208 +CONST182 = -65.6835676154052 +CONST183 = -61.1480736741764 +CONST184 = -1085.27064731784 +CONST185 = -61.1480736741764 +CONST186 = -1085.40315090753 +CONST187 = -57.7234787586472 +CONST188 = -12.9361412329953 +CONST189 = -1085.27064731784 +CONST190 = -52.8379746252636 +CONST191 = -51.7445649319810 +CONST192 = -1585.13923875791 +CONST193 = -47.4863878522046 +CONST194 = 978.369178786822 +CONST195 = -48.5105296237322 +CONST196 = 978.369178786822 +CONST197 = -517.445649319810 +CONST198 = -40.7026181590325 +CONST199 = -40.5868210021738 +CONST200 = -39.4101405692431 +CONST201 = -40.7026181590325 +CONST202 = -36.0771742241545 +CONST203 = -1056.75949250527 +CONST204 = -29.1063177742393 +CONST205 = 485.105296237322 +CONST206 = -26.2734270461621 +CONST207 = -26.4189873126318 +CONST208 = -1050.93708184648 +CONST209 = -22.6382471577417 +CONST210 = -20.6718218536732 +CONST211 = -19.4042118494929 +CONST212 = -20.3513090795162 +CONST213 = -528.379746252636 +CONST214 = -15.0965641786467 +CONST215 = -13.5675393863442 +CONST216 = -525.468540923241 +CONST217 = -11.3224231339851 +CONST218 = -13.5289403340579 +CONST219 = -9.70210592474644 +CONST220 = -10.3359109268366 +CONST221 = -13.1367135230810 +CONST222 = -6.46807061649763 +CONST223 = -12.2296147348353 +CONST224 = -12.2296147348353 +CONST225 = -3.23403530824881 +CONST226 = -1034.89129863962 +VAR00 = z**4 +VAR01 = x**7 +VAR02 = g_6 +VAR03 = g_14 +VAR04 = z**6 +VAR05 = x**2 +VAR06 = g_7 +VAR07 = y +VAR08 = y**6 +VAR09 = g_15 +VAR10 = x**4 +VAR11 = g_8 +VAR12 = z**3 +VAR13 = x**6 +VAR14 = z +VAR15 = x +VAR16 = z**5 +VAR17 = g_0 +VAR18 = g_3 +VAR19 = y**3 +VAR20 = y**5 +VAR21 = g_12 +VAR22 = x**3 +VAR23 = y**7 +VAR24 = g_2 +VAR25 = x**5 +VAR26 = g_16 +VAR27 = z**7 +VAR28 = g_5 +VAR29 = g_13 +VAR30 = y**2 +VAR31 = g_9 +VAR32 = g_4 +VAR33 = g_1 +VAR34 = g_11 +VAR35 = y**4 +VAR36 = g_10 +VAR37 = z**2 +# -------------------- kernel implementations +g_x = VAR02*(CONST001*VAR05*(CONST093*VAR12*VAR30 + CONST118*VAR14*VAR30**2 + CONST219*VAR16) + CONST004*VAR05**2*(-CONST161*VAR14*VAR30 + CONST219*VAR12) + CONST118*VAR12*VAR30**2 - CONST160*VAR14*VAR30**3 - CONST161*VAR16*VAR30 + CONST209*VAR05**3*VAR14 + CONST225*VAR27) + VAR03*(CONST013*VAR01 + CONST148*VAR25*VAR37 - CONST190*VAR15*VAR37**3 + VAR30*(CONST109*VAR15*VAR37**2 + CONST148*VAR25 - CONST192*VAR22*VAR37)) + VAR06*(CONST001*VAR05*(-CONST128*VAR19*VAR37 + CONST152*VAR20 + CONST199*VAR07*VAR37**2) + CONST004*VAR05**2*(CONST063*VAR19 + CONST199*VAR07*VAR37) + CONST019*VAR23 + CONST152*VAR20*VAR37 - CONST157*VAR19*VAR37**2 + CONST162*VAR05**3*VAR07 + CONST218*VAR07*VAR37**3) + VAR07*VAR09*(CONST050*VAR14*VAR25 + CONST050*VAR15*VAR16 - CONST054*VAR12*VAR22) + VAR07*VAR33*(CONST177*VAR05**3 - CONST177*VAR37**3 + CONST184*VAR05*VAR37**2 - CONST189*VAR05**2*VAR37) + VAR11*(CONST000*VAR15*(CONST002*VAR37**3 - CONST128*VAR30**2*VAR37 + CONST157*VAR30*VAR37**2 + CONST187*VAR30**3) + CONST006*VAR22*(CONST008*VAR37**2 - CONST157*VAR30**2 + CONST158*VAR30*VAR37) + CONST007*VAR01 + CONST009*VAR25*(CONST002*VAR37 + CONST202*VAR30)) + VAR17*(CONST049*VAR05*VAR16 - CONST131*VAR05**2*VAR12 + CONST150*VAR05**3*VAR14 - CONST210*VAR27) + VAR18*(VAR07*(CONST028*VAR05**2*VAR37 + CONST088*VAR05*VAR37**2 + CONST167*VAR05**3 + CONST183*VAR37**3) + VAR19*(CONST044*VAR05*VAR37 + CONST113*VAR05**2 + CONST114*VAR37**2)) + VAR21*(CONST011*VAR01 + CONST181*VAR22*VAR37**2 + CONST198*VAR25*VAR37 + CONST215*VAR15*VAR37**3 + VAR30**2*(CONST098*VAR15*VAR37 + CONST122*VAR22) + VAR30*(-CONST102*VAR22*VAR37 + CONST121*VAR25 + CONST159*VAR15*VAR37**2)) + VAR24*(CONST001*VAR05*(-CONST207*VAR16 + CONST213*VAR12*VAR30) + CONST004*VAR05**2*(-CONST148*VAR14*VAR30 - CONST207*VAR12) - CONST148*VAR16*VAR30 + CONST171*VAR05**3*VAR14 + CONST217*VAR27) + VAR26*(CONST050*VAR25*VAR37 - CONST131*VAR22*VAR37**2 + CONST150*VAR15*VAR37**3 - CONST210*VAR01) + VAR28*(VAR07*(CONST040*VAR05**2*VAR37 + CONST095*VAR05*VAR37**2 + CONST164*VAR05**3 - CONST200*VAR37**3) + VAR19*(-CONST048*VAR05**2 + CONST116*VAR37**2 + CONST216*VAR05*VAR37) + VAR20*(CONST133*VAR05 - CONST134*VAR37)) + VAR29*(VAR07*(CONST076*VAR14*VAR25 + CONST105*VAR15*VAR16 + CONST112*VAR12*VAR22) + VAR19*(CONST030*VAR12*VAR15 - CONST030*VAR14*VAR22)) + VAR31*(CONST172*VAR07*VAR15*VAR16 + VAR12*(CONST146*VAR07*VAR22 + CONST170*VAR15*VAR19) + VAR14*(CONST117*VAR15*VAR20 + CONST169*VAR07*VAR25 + CONST170*VAR19*VAR22)) + VAR32*(CONST001*VAR05*(CONST005*VAR16 + CONST111*VAR14*VAR30**2) + CONST004*VAR05**2*(CONST080*VAR12 - CONST145*VAR14*VAR30) + CONST005*VAR27 - CONST111*VAR12*VAR30**2 + CONST145*VAR16*VAR30 + CONST193*VAR05**3*VAR14) + VAR34*(CONST056*VAR14*VAR15*VAR20 + VAR07*(CONST116*VAR12*VAR22 + CONST124*VAR14*VAR25 + CONST206*VAR15*VAR16) + VAR19*(-CONST082*VAR12*VAR15 - CONST208*VAR14*VAR22)) + VAR36*(CONST017*VAR25*VAR37 + CONST160*VAR15*VAR30**3 - CONST188*VAR01 - CONST197*VAR22*VAR30**2 + CONST222*VAR15*VAR37**3 + VAR30*(CONST058*VAR15*VAR37**2 + CONST106*VAR25 + CONST138*VAR22*VAR37)) +g_y = CONST000*VAR03*VAR07*(-CONST068*VAR05**2*VAR37 + CONST068*VAR05*VAR37**2 + CONST207*VAR05**3 - CONST207*VAR37**3) + VAR02*(-CONST138*VAR07*VAR14*VAR25 + VAR15*(CONST067*VAR14*VAR20 - CONST138*VAR07*VAR16 + CONST226*VAR12*VAR19) + VAR22*(CONST154*VAR07*VAR12 + CONST226*VAR14*VAR19)) + VAR06*(CONST218*VAR01 + VAR15*(CONST085*VAR30**3 + CONST140*VAR30*VAR37**2 + CONST151*VAR30**2*VAR37 + CONST218*VAR37**3) + VAR22*(CONST151*VAR30**2 - CONST151*VAR30*VAR37 + CONST199*VAR37**2) + VAR25*(CONST142*VAR30 + CONST199*VAR37)) + VAR09*(-CONST078*VAR05**2*VAR12 + CONST127*VAR05*VAR16 + CONST177*VAR05**3*VAR14 - CONST220*VAR27) + VAR11*(CONST026*VAR23 - CONST052*VAR19*VAR37**2 + CONST084*VAR20*VAR37 + CONST178*VAR05**3*VAR07 + CONST180*VAR07*VAR37**3 + VAR05**2*(-CONST052*VAR19 + CONST129*VAR07*VAR37) + VAR05*(CONST083*VAR20 + CONST128*VAR07*VAR37**2 + CONST147*VAR19*VAR37)) + VAR18*(CONST001*VAR30*(CONST020*VAR22*VAR37 + CONST034*VAR25 + CONST114*VAR15*VAR37**2) + CONST066*VAR25*VAR37 + CONST183*VAR15*VAR37**3 - CONST185*VAR22*VAR37**2 + CONST224*VAR01) + VAR21*(CONST000*VAR07*(CONST097*VAR05**2*VAR37 + CONST097*VAR05*VAR37**2 + CONST198*VAR05**3 + CONST198*VAR37**3) + CONST006*VAR19*(CONST061*VAR05*VAR37 - CONST181*VAR05**2 - CONST181*VAR37**2)) + VAR24*(CONST137*VAR07*VAR14*VAR25 + CONST137*VAR07*VAR15*VAR16 + CONST203*VAR07*VAR12*VAR22) + VAR28*(CONST001*VAR30*(CONST116*VAR15*VAR37**2 + CONST143*VAR22*VAR37 - CONST166*VAR25) + CONST004*VAR30**2*(-CONST134*VAR15*VAR37 + CONST179*VAR22) + CONST015*VAR25*VAR37 + CONST040*VAR22*VAR37**2 + CONST139*VAR01 - CONST200*VAR15*VAR37**3) + VAR29*(CONST001*VAR30*(CONST020*VAR05*VAR12 + CONST034*VAR16 + CONST113*VAR05**2*VAR14) + CONST065*VAR05*VAR16 - CONST183*VAR05**2*VAR12 + CONST185*VAR05**3*VAR14 + CONST223*VAR27) + VAR31*(CONST218*VAR27 + VAR12*(CONST073*VAR05*VAR30 + CONST151*VAR30**2 + CONST199*VAR05**2) + VAR14*(CONST086*VAR30**3 + CONST091*VAR05**3 + CONST142*VAR05**2*VAR30 + CONST151*VAR05*VAR30**2) + VAR16*(CONST142*VAR30 + CONST199*VAR05)) + VAR32*(-CONST090*VAR07*VAR14*VAR25 + CONST186*VAR14*VAR19*VAR22 + VAR15*(CONST090*VAR07*VAR16 - CONST186*VAR12*VAR19)) + VAR33*(CONST078*VAR22*VAR37**2 + CONST104*VAR25*VAR37 - CONST177*VAR15*VAR37**3 + CONST220*VAR01) + VAR34*(CONST001*VAR30*(-CONST116*VAR05**2*VAR14 - CONST143*VAR05*VAR12 + CONST166*VAR16) + CONST004*VAR30**2*(CONST134*VAR05*VAR14 - CONST179*VAR12) + CONST012*VAR27 + CONST182*VAR05**2*VAR12 + CONST200*VAR05**3*VAR14 + CONST221*VAR05*VAR16) + VAR36*(CONST000*VAR07*(CONST032*VAR05*VAR37**2 + CONST032*VAR37**3 + CONST195*VAR05**3 + CONST195*VAR05**2*VAR37) + CONST006*VAR19*(-CONST153*VAR05**2 + CONST153*VAR37**2) + CONST009*VAR20*(CONST035*VAR37 + CONST191*VAR05)) +g_z = VAR02*(CONST225*VAR01 + VAR15*(CONST115*VAR30**2*VAR37 - CONST160*VAR30**3 + CONST205*VAR30*VAR37**2 + CONST209*VAR37**3) + VAR22*(CONST046*VAR30*VAR37 + CONST118*VAR30**2 + CONST195*VAR37**2) + VAR25*(-CONST161*VAR30 + CONST204*VAR37)) + VAR03*(-CONST148*VAR05*VAR16 + CONST190*VAR05**3*VAR14 + CONST214*VAR27 + VAR30*(-CONST109*VAR05**2*VAR14 - CONST148*VAR16 + CONST192*VAR05*VAR12)) + VAR06*(CONST172*VAR07*VAR14*VAR25 + VAR15*(-CONST052*VAR12*VAR19 + CONST117*VAR14*VAR20 + CONST172*VAR07*VAR16) + VAR22*(-CONST052*VAR14*VAR19 + CONST146*VAR07*VAR12)) + VAR07*VAR09*(CONST177*VAR05**3 - CONST177*VAR37**3 - CONST184*VAR05**2*VAR37 + CONST189*VAR05*VAR37**2) + VAR07*VAR33*(-CONST050*VAR15*VAR16 + CONST054*VAR12*VAR22 + CONST071*VAR14*VAR25) + VAR11*(CONST007*VAR05**3*VAR14 + CONST007*VAR27 - CONST052*VAR12*VAR30**2 + CONST130*VAR16*VAR30 + CONST156*VAR14*VAR30**3 + VAR05**2*(CONST024*VAR12 + CONST129*VAR14*VAR30) + VAR05*(CONST024*VAR16 + CONST052*VAR12*VAR30 - CONST052*VAR14*VAR30**2)) + VAR17*(-CONST049*VAR25*VAR37 + CONST131*VAR22*VAR37**2 - CONST150*VAR15*VAR37**3 + CONST210*VAR01) + VAR18*(VAR07*(CONST077*VAR15*VAR16 + CONST107*VAR14*VAR25 + CONST114*VAR12*VAR22) + VAR19*(CONST030*VAR14*VAR22 + CONST196*VAR12*VAR15)) + VAR21*(CONST011*VAR27 + CONST092*VAR05**3*VAR14 + CONST181*VAR05**2*VAR12 + CONST201*VAR05*VAR16 + VAR30**2*(CONST098*VAR05*VAR14 + CONST122*VAR12) + VAR30*(-CONST102*VAR05*VAR12 + CONST121*VAR16 + CONST159*VAR05**2*VAR14)) + VAR24*(CONST096*VAR01 + VAR15*(-CONST109*VAR30*VAR37**2 + CONST176*VAR37**3) + VAR22*(CONST070*VAR37**2 + CONST192*VAR30*VAR37) + VAR25*(-CONST148*VAR30 - CONST176*VAR37)) + VAR26*(CONST050*VAR05*VAR16 - CONST131*VAR05**2*VAR12 + CONST150*VAR05**3*VAR14 - CONST210*VAR27) + VAR28*(-CONST056*VAR14*VAR15*VAR20 + VAR07*(CONST023*VAR14*VAR25 + CONST120*VAR12*VAR22 - CONST124*VAR15*VAR16) + VAR19*(CONST082*VAR14*VAR22 + CONST208*VAR12*VAR15)) + VAR29*(VAR07*(CONST028*VAR05*VAR37**2 + CONST089*VAR05**2*VAR37 + CONST168*VAR37**3 + CONST185*VAR05**3) + VAR19*(CONST044*VAR05*VAR37 + CONST113*VAR05**2 + CONST113*VAR37**2)) + VAR31*(CONST001*VAR37*(CONST108*VAR05*VAR19 + CONST152*VAR20 + CONST199*VAR05**2*VAR07) + CONST004*VAR37**2*(CONST063*VAR19 + CONST199*VAR05*VAR07) + CONST025*VAR23 + CONST063*VAR05**2*VAR19 + CONST091*VAR05**3*VAR07 + CONST152*VAR05*VAR20 + CONST162*VAR07*VAR37**3) + VAR32*(CONST080*VAR01 + VAR15*(CONST102*VAR30*VAR37**2 + CONST135*VAR30**2*VAR37 - CONST193*VAR37**3) + VAR22*(CONST027*VAR37**2 + CONST111*VAR30**2) + VAR25*(-CONST145*VAR30 + CONST212*VAR37)) + VAR34*(VAR07*(CONST055*VAR37**3 + CONST136*VAR05**2*VAR37 + CONST182*VAR05*VAR37**2 + CONST200*VAR05**3) + VAR19*(CONST048*VAR37**2 - CONST116*VAR05**2 - CONST216*VAR05*VAR37) + VAR20*(-CONST133*VAR37 + CONST134*VAR05)) + VAR36*(CONST057*VAR05**3*VAR14 + CONST062*VAR14*VAR30**3 + CONST188*VAR27 + CONST197*VAR12*VAR30**2 + CONST211*VAR05*VAR16 + VAR30*(CONST093*VAR05*VAR12 - CONST106*VAR16 + CONST161*VAR05**2*VAR14)) diff --git a/notebooks/bwd_implementations/bwd_9.py b/notebooks/bwd_implementations/bwd_9.py new file mode 100644 index 0000000..1d3061e --- /dev/null +++ b/notebooks/bwd_implementations/bwd_9.py @@ -0,0 +1,352 @@ +# -------------------- variable and constant definitions +CONST000 = 1.59908344719522 +CONST001 = 2.00000000000000 +CONST002 = 3.00000000000000 +CONST003 = 4.00000000000000 +CONST004 = 5.00000000000000 +CONST005 = 6.39633378878088 +CONST006 = 7.00000000000000 +CONST007 = 8.63855507530412 +CONST008 = 9.59450068317133 +CONST009 = 6.39633378878088 +CONST010 = 9.82028453158308 +CONST011 = 12.7926675775618 +CONST012 = 12.7926675775618 +CONST013 = 14.7304267973746 +CONST014 = 15.5493991355474 +CONST015 = 14.3917510247570 +CONST016 = 17.3847567381802 +CONST017 = 15.0007324039945 +CONST018 = 14.4550674370400 +CONST019 = 14.4550674370400 +CONST020 = 13.3827919767794 +CONST021 = 13.0937127087774 +CONST022 = 23.8930627690618 +CONST023 = 23.8930627690618 +CONST024 = 27.0429549260581 +CONST025 = 29.2403830344269 +CONST026 = 30.0014648079890 +CONST027 = 30.9062342012093 +CONST028 = 29.2403830344269 +CONST029 = 38.3780027326853 +CONST030 = 39.2811381263323 +CONST031 = 39.2811381263323 +CONST032 = 39.2300904918661 +CONST033 = 42.9079114754785 +CONST034 = 10.7269778688696 +CONST035 = 54.0859098521163 +CONST036 = 57.8202697481601 +CONST037 = 58.9217071894985 +CONST038 = 57.8202697481601 +CONST039 = 60.0029296159779 +CONST040 = 62.4530292249704 +CONST041 = 64.3618672132178 +CONST042 = 68.5747767039748 +CONST043 = 69.1084406024329 +CONST044 = 77.2655855030233 +CONST045 = 78.5622762526647 +CONST046 = 85.8158229509570 +CONST047 = 85.8158229509570 +CONST048 = 90.1063824390370 +CONST049 = 96.7518168434061 +CONST050 = 104.749701670220 +CONST051 = 107.062335814235 +CONST052 = 108.171819704233 +CONST053 = 108.171819704233 +CONST054 = -1935.03633686812 +CONST055 = 115.640539496320 +CONST056 = 115.640539496320 +CONST057 = 117.843414378997 +CONST058 = 115.640539496320 +CONST059 = 117.843414378997 +CONST060 = 120.005859231956 +CONST061 = 2176.91587897664 +CONST062 = 2176.91587897664 +CONST063 = 137.149553407950 +CONST064 = 150.007324039945 +CONST065 = 150.007324039945 +CONST066 = -1892.23403121978 +CONST067 = -1885.49463006395 +CONST068 = 173.460809244480 +CONST069 = -1873.59087674911 +CONST070 = 176.765121568496 +CONST071 = 10.7269778688696 +CONST072 = 180.008788847934 +CONST073 = 187.359087674911 +CONST074 = 191.144502152495 +CONST075 = 13.5214774630291 +CONST076 = 196.405690631662 +CONST077 = 205.957975082297 +CONST078 = 216.343639408465 +CONST079 = 216.343639408465 +CONST080 = 4326.87278816930 +CONST081 = 233.923064275415 +CONST082 = 233.923064275415 +CONST083 = 240.011718463912 +CONST084 = 241.879542108515 +CONST085 = 241.879542108515 +CONST086 = 255.853351551235 +CONST087 = 255.853351551235 +CONST088 = 257.447468852871 +CONST089 = 257.447468852871 +CONST090 = 2312.81078992641 +CONST091 = 270.429549260581 +CONST092 = 289.101348740801 +CONST093 = 294.608535947493 +CONST094 = 300.014648079890 +CONST095 = 300.014648079890 +CONST096 = 2356.86828757994 +CONST097 = 314.249105010659 +CONST098 = 324.515459112698 +CONST099 = -3747.18175349822 +CONST100 = 6.39633378878088 +CONST101 = 353.530243136991 +CONST102 = 374.718175349822 +CONST103 = 374.718175349822 +CONST104 = 392.811381263323 +CONST105 = 404.741888237121 +CONST106 = 411.915950164594 +CONST107 = 412.451950326490 +CONST108 = 432.687278816930 +CONST109 = 435.383175795328 +CONST110 = 435.383175795327 +CONST111 = 462.562157985281 +CONST112 = 462.562157985281 +CONST113 = -1571.24552505329 +CONST114 = 483.759084217031 +CONST115 = 511.706703102471 +CONST116 = 562.077263024733 +CONST117 = 578.202697481601 +CONST118 = 589.217071894985 +CONST119 = -1451.27725265109 +CONST120 = 4.91014226579154 +CONST121 = -1451.27725265109 +CONST122 = 600.029296159779 +CONST123 = 600.029296159779 +CONST124 = -1440.07031078347 +CONST125 = 628.498210021317 +CONST126 = 628.498210021318 +CONST127 = 630.744677073259 +CONST128 = 649.030918225395 +CONST129 = -1387.68647395584 +CONST130 = -1387.68647395584 +CONST131 = -1373.05316721531 +CONST132 = -1338.01151506746 +CONST133 = 725.638626325546 +CONST134 = -1298.06183645079 +CONST135 = 785.622762526647 +CONST136 = 785.622762526647 +CONST137 = 788.430846341574 +CONST138 = -1249.06058449941 +CONST139 = -1228.09608744593 +CONST140 = -1228.09608744593 +CONST141 = 823.831900329187 +CONST142 = -3245.15459112698 +CONST143 = -1178.43414378997 +CONST144 = 870.766351590655 +CONST145 = 870.766351590655 +CONST146 = 900.043944239669 +CONST147 = -1124.15452604947 +CONST148 = 936.795438374555 +CONST149 = -3153.72338536630 +CONST150 = 960.046873855647 +CONST151 = 960.046873855647 +CONST152 = 967.518168434061 +CONST153 = -1081.71819704233 +CONST154 = 967.518168434061 +CONST155 = -1060.59072941097 +CONST156 = 1023.41340620494 +CONST157 = 1023.41340620494 +CONST158 = 1060.59072941097 +CONST159 = -967.518168434061 +CONST160 = 1081.71819704233 +CONST161 = -960.046873855647 +CONST162 = 3153.72338536630 +CONST163 = -936.795438374555 +CONST164 = 1124.15452604947 +CONST165 = -900.043944239669 +CONST166 = 1156.40539496320 +CONST167 = 1178.43414378997 +CONST168 = -2902.55450530218 +CONST169 = 3245.15459112698 +CONST170 = 11.2632978048796 +CONST171 = -785.622762526647 +CONST172 = -785.622762526647 +CONST173 = -767.560054653706 +CONST174 = 1298.06183645079 +CONST175 = 1338.01151506746 +CONST176 = -693.843236977922 +CONST177 = -693.843236977921 +CONST178 = -686.526583607656 +CONST179 = -669.005757533731 +CONST180 = -669.005757533731 +CONST181 = 1387.68647395584 +CONST182 = -649.030918225395 +CONST183 = -630.744677073259 +CONST184 = -628.498210021318 +CONST185 = -628.498210021317 +CONST186 = -600.029296159779 +CONST187 = -589.217071894985 +CONST188 = -578.202697481601 +CONST189 = 15.5493991355474 +CONST190 = -562.077263024733 +CONST191 = 1500.07324039945 +CONST192 = -480.023436927823 +CONST193 = -480.023436927823 +CONST194 = 1571.24552505329 +CONST195 = -462.562157985281 +CONST196 = -450.021972119834 +CONST197 = -412.451950326490 +CONST198 = -409.365362481977 +CONST199 = -409.365362481976 +CONST200 = -404.741888237121 +CONST201 = -392.811381263323 +CONST202 = -383.780027326853 +CONST203 = -383.780027326853 +CONST204 = 1672.51439383433 +CONST205 = -374.718175349822 +CONST206 = -353.530243136991 +CONST207 = -2400.11718463912 +CONST208 = 3747.18175349822 +CONST209 = -346.921618488961 +CONST210 = -346.921618488961 +CONST211 = -343.263291803828 +CONST212 = -338.631358951921 +CONST213 = -338.631358951921 +CONST214 = -324.515459112698 +CONST215 = -315.372338536630 +CONST216 = -314.249105010659 +CONST217 = -2356.86828757994 +CONST218 = -300.014648079890 +CONST219 = -294.608535947493 +CONST220 = -289.101348740801 +CONST221 = -270.013183271901 +CONST222 = -2312.81078992641 +CONST223 = 1800.08788847934 +CONST224 = -241.879542108515 +CONST225 = -240.011718463912 +CONST226 = -241.879542108515 +CONST227 = -4326.87278816930 +CONST228 = -216.343639408465 +CONST229 = -210.010253655923 +CONST230 = -204.682681240988 +CONST231 = -204.682681240988 +CONST232 = -204.682681240988 +CONST233 = -196.405690631662 +CONST234 = -191.144502152495 +CONST235 = -191.890013663426 +CONST236 = -191.890013663427 +CONST237 = -187.359087674911 +CONST238 = -180.008788847934 +CONST239 = -176.765121568496 +CONST240 = 1873.59087674911 +CONST241 = 1873.59087674911 +CONST242 = -173.460809244480 +CONST243 = 1885.49463006395 +CONST244 = -162.257729556349 +CONST245 = -156.920361967464 +CONST246 = -156.920361967464 +CONST247 = 1892.23403121978 +CONST248 = -150.007324039945 +CONST249 = -144.550674370400 +CONST250 = -137.149553407950 +CONST251 = -135.214774630291 +CONST252 = -127.926675775618 +CONST253 = -127.926675775618 +CONST254 = -120.939771054258 +CONST255 = -120.005859231956 +CONST256 = -120.939771054258 +CONST257 = -117.843414378997 +CONST258 = -117.843414378997 +CONST259 = -115.640539496320 +CONST260 = -115.640539496320 +CONST261 = 1935.03633686812 +CONST262 = -2163.43639408465 +CONST263 = -114.421097267943 +CONST264 = -108.171819704233 +CONST265 = -107.062335814235 +CONST266 = -108.171819704233 +CONST267 = -104.749701670220 +CONST268 = -96.7518168434061 +CONST269 = -96.7518168434061 +CONST270 = -90.0043944239669 +CONST271 = -90.1063824390370 +CONST272 = -80.2967518606762 +CONST273 = -78.4601809837321 +CONST274 = -78.4601809837321 +CONST275 = -77.2655855030233 +CONST276 = -78.5622762526647 +CONST277 = -68.5747767039748 +CONST278 = -63.9633378878088 +CONST279 = -63.9633378878088 +CONST280 = -62.4530292249704 +CONST281 = -60.0029296159779 +CONST282 = -61.8124684024186 +CONST283 = -58.9217071894985 +CONST284 = -57.8202697481601 +CONST285 = -57.8202697481601 +CONST286 = -48.3759084217030 +CONST287 = -48.3759084217031 +CONST288 = -39.2811381263323 +CONST289 = -38.6327927515116 +CONST290 = -39.2811381263323 +CONST291 = -30.9062342012093 +CONST292 = -30.0014648079890 +CONST293 = -30.0014648079890 +CONST294 = -27.6433762409732 +CONST295 = -17.3847567381802 +CONST296 = -15.0007324039945 +CONST297 = -14.7304267973746 +CONST298 = -13.5214774630291 +CONST299 = -13.0937127087774 +CONST300 = -13.3827919767794 +CONST301 = -9.82028453158308 +CONST302 = -4.91014226579154 +CONST303 = 2046.82681240988 +VAR00 = g_14 +VAR01 = z +VAR02 = g_12 +VAR03 = g_16 +VAR04 = g_4 +VAR05 = z**4 +VAR06 = z**8 +VAR07 = y +VAR08 = x**4 +VAR09 = y**8 +VAR10 = z**3 +VAR11 = x +VAR12 = x**8 +VAR13 = y**3 +VAR14 = x**3 +VAR15 = y**7 +VAR16 = y**2 +VAR17 = g_9 +VAR18 = g_1 +VAR19 = g_11 +VAR20 = g_15 +VAR21 = g_8 +VAR22 = g_18 +VAR23 = z**7 +VAR24 = g_5 +VAR25 = z**2 +VAR26 = x**7 +VAR27 = g_6 +VAR28 = z**6 +VAR29 = x**2 +VAR30 = g_7 +VAR31 = y**6 +VAR32 = x**6 +VAR33 = z**5 +VAR34 = g_0 +VAR35 = g_3 +VAR36 = y**5 +VAR37 = g_2 +VAR38 = g_17 +VAR39 = x**5 +VAR40 = g_13 +VAR41 = y**4 +VAR42 = g_10 +# -------------------- kernel implementations +g_x = VAR00*(CONST043*VAR01*VAR26 + CONST268*VAR14*VAR33 + CONST294*VAR11*VAR23 + VAR16**2*(CONST054*VAR10*VAR11 + CONST261*VAR01*VAR14) + VAR16*(CONST119*VAR01*VAR39 + CONST144*VAR11*VAR33 + CONST152*VAR10*VAR14)) + VAR02*(VAR01*(CONST155*VAR16*VAR39 + CONST184*VAR11*VAR16**3 - CONST217*VAR14*VAR16**2 - CONST288*VAR26) + VAR10*(CONST045*VAR39 + CONST143*VAR14*VAR16 - CONST172*VAR11*VAR16**2) + VAR33*(CONST257*VAR11*VAR16 - CONST290*VAR14)) + VAR03*(CONST214*VAR10*VAR39 - CONST264*VAR01*VAR26 + CONST264*VAR14*VAR33 - CONST275*VAR11*VAR23 + VAR16*(CONST080*VAR10*VAR14 + CONST134*VAR01*VAR39 + CONST134*VAR11*VAR33)) + VAR04*(CONST007*VAR25**4 + CONST014*VAR29**4 + CONST254*VAR25**2*VAR29**2 + CONST269*VAR25*VAR29**3 + VAR16**2*(CONST114*VAR25**2 + CONST114*VAR29**2 + CONST168*VAR25*VAR29) + VAR16*(CONST061*VAR25*VAR29**2 + CONST133*VAR25**2*VAR29 + CONST212*VAR29**3 + CONST224*VAR25**3)) + VAR07*VAR18*(CONST066*VAR29*VAR33 - CONST149*VAR10*VAR29**2 + CONST183*VAR01*VAR29**3 - CONST271*VAR23) + VAR07*VAR38*(CONST066*VAR25*VAR39 - CONST149*VAR14*VAR25**2 + CONST183*VAR11*VAR25**3 - CONST271*VAR26) + VAR17*(CONST245*VAR11*VAR15 + VAR07*(CONST046*VAR26 + CONST047*VAR11*VAR25**3 + CONST088*VAR25*VAR39 + CONST089*VAR14*VAR25**2) + VAR13*(CONST131*VAR14*VAR25 + CONST178*VAR11*VAR25**2 + CONST178*VAR39) + VAR36*(CONST141*VAR11*VAR25 + CONST141*VAR14)) + VAR19*(CONST150*VAR14*VAR36 + CONST250*VAR11*VAR15 + VAR07*(CONST060*VAR26 + CONST072*VAR25*VAR39 + CONST281*VAR11*VAR25**3) + VAR13*(CONST094*VAR11*VAR25**2 + CONST165*VAR39 + CONST186*VAR14*VAR25)) + VAR20*(VAR07*(CONST051*VAR26 + CONST147*VAR25*VAR39 - CONST205*VAR11*VAR25**3) + VAR13*(CONST069*VAR11*VAR25**2 - CONST099*VAR14*VAR25 + CONST205*VAR39)) + VAR21*(CONST000*VAR25**4 + CONST002*VAR29*(CONST005*VAR25**3 + CONST115*VAR16**2*VAR25 + CONST230*VAR16**3 + CONST235*VAR16*VAR25**2) + CONST004*VAR29**2*(CONST008*VAR25**2 + CONST086*VAR16**2 + CONST235*VAR16*VAR25) + CONST006*VAR29**3*(CONST009*VAR25 + CONST279*VAR16) + CONST015*VAR29**4 + CONST025*VAR16**4 + CONST086*VAR16**2*VAR25**2 + CONST231*VAR16**3*VAR25 + CONST279*VAR16*VAR25**3) + VAR22*(CONST132*VAR10*VAR39 + CONST175*VAR14*VAR33 - CONST234*VAR01*VAR26 + CONST234*VAR11*VAR23) + VAR24*(VAR07*(CONST068*VAR29*VAR33 + CONST200*VAR01*VAR29**3 + CONST220*VAR10*VAR29**2 - CONST284*VAR23) + VAR13*(CONST195*VAR33 - CONST222*VAR01*VAR29**2) + VAR36*(CONST130*VAR01*VAR29 - CONST195*VAR10)) + VAR27*(CONST002*VAR29*(CONST201*VAR16**2*VAR25 - CONST219*VAR16*VAR25**2 + CONST267*VAR16**3 + CONST299*VAR25**3) + CONST004*VAR29**2*(CONST037*VAR16*VAR25 - CONST233*VAR16**2 + CONST301*VAR25**2) + CONST187*VAR16**2*VAR25**2 + CONST197*VAR16*VAR29**3 - CONST216*VAR16**3*VAR25 - CONST239*VAR16*VAR25**3 - CONST297*VAR29**4 + CONST302*VAR25**4) + VAR30*(CONST002*VAR29*(-CONST186*VAR10*VAR13 + CONST192*VAR01*VAR36 + CONST270*VAR07*VAR33) + CONST004*VAR29**2*(-CONST218*VAR01*VAR13 + CONST270*VAR07*VAR10) + CONST193*VAR10*VAR36 - CONST218*VAR13*VAR33 + CONST229*VAR01*VAR07*VAR29**3 - CONST250*VAR01*VAR15 + CONST292*VAR07*VAR23) + VAR34*(CONST022*VAR29**4 + CONST023*VAR25**4 + CONST179*VAR25*VAR29**3 + CONST180*VAR25**3*VAR29 + CONST204*VAR25**2*VAR29**2) + VAR35*(VAR07*(CONST116*VAR29*VAR33 - CONST163*VAR10*VAR29**2 + CONST190*VAR01*VAR29**3 + CONST272*VAR23) + VAR13*(-CONST069*VAR01*VAR29**2 + CONST099*VAR10*VAR29 + CONST103*VAR33)) + VAR37*(CONST002*VAR29*(CONST035*VAR25**3 + CONST153*VAR16*VAR25**2) + CONST004*VAR29**2*(CONST024*VAR25**2 - CONST182*VAR16*VAR25) + CONST006*VAR29**3*(CONST289*VAR25 + CONST291*VAR16) - CONST228*VAR16*VAR25**3 - CONST295*VAR29**4 + CONST298*VAR25**4) + VAR40*(VAR07*(CONST188*VAR14*VAR25**2 + CONST209*VAR25*VAR39 + CONST259*VAR11*VAR25**3 - CONST259*VAR26) + VAR13*(CONST166*VAR11*VAR25**2 + CONST176*VAR39 - CONST222*VAR14*VAR25) + VAR36*(CONST129*VAR11*VAR25 - CONST195*VAR14)) + VAR42*(CONST012*VAR11*VAR23 + VAR01*(CONST011*VAR26 + CONST157*VAR14*VAR16**2 + CONST198*VAR11*VAR16**3 + CONST202*VAR16*VAR39) + VAR10*(CONST029*VAR39 + CONST157*VAR11*VAR16**2 + CONST173*VAR14*VAR16) + VAR33*(CONST029*VAR14 + CONST203*VAR11*VAR16)) +g_y = CONST001*VAR03*VAR07*(CONST160*VAR10*VAR29**2 + CONST182*VAR29*VAR33 + CONST228*VAR01*VAR29**3 - CONST291*VAR23) + VAR00*(CONST001*VAR07*(CONST084*VAR10*VAR29**2 + CONST109*VAR29*VAR33 + CONST226*VAR01*VAR29**3 + CONST286*VAR23) + CONST003*VAR13*(CONST114*VAR01*VAR29**2 + CONST159*VAR10*VAR29 - CONST269*VAR33)) + VAR02*(CONST057*VAR07*VAR23 + VAR01*(CONST067*VAR29*VAR36 + CONST206*VAR07*VAR29**3 - CONST217*VAR13*VAR29**2) + VAR10*(-CONST113*VAR13*VAR29 - CONST185*VAR36 + CONST187*VAR07*VAR29**2) + VAR33*(CONST171*VAR13 + CONST257*VAR07*VAR29)) + VAR04*(CONST001*VAR07*(CONST110*VAR25*VAR39 + CONST224*VAR11*VAR25**3 - CONST224*VAR14*VAR25**2 + CONST287*VAR26) + CONST003*VAR13*(CONST114*VAR11*VAR25**2 + CONST159*VAR14*VAR25 - CONST269*VAR39)) + VAR17*(CONST002*VAR16*(CONST211*VAR25**2*VAR29 + CONST211*VAR25*VAR29**2 + CONST263*VAR25**3 + CONST263*VAR29**3) + CONST004*VAR16**2*(CONST077*VAR25**2 + CONST077*VAR29**2 + CONST106*VAR25*VAR29) + CONST006*VAR16**3*(CONST273*VAR25 + CONST274*VAR29) + CONST032*VAR16**4 + CONST033*VAR25**3*VAR29 + CONST033*VAR25*VAR29**3 + CONST034*VAR25**4 + CONST041*VAR25**2*VAR29**2 + CONST071*VAR29**4) + VAR18*(-CONST183*VAR10*VAR39 + CONST183*VAR14*VAR33 + CONST271*VAR01*VAR26 - CONST271*VAR11*VAR23) + VAR19*(CONST002*VAR16*(CONST065*VAR25**2*VAR29 - CONST248*VAR25**3 + CONST248*VAR25*VAR29**2 + CONST248*VAR29**3) + CONST004*VAR16**2*(CONST083*VAR29**2 + CONST225*VAR25**2) + CONST006*VAR16**3*(-CONST277*VAR25 + CONST277*VAR29) + CONST017*VAR29**4 + CONST026*VAR25*VAR29**3 + CONST293*VAR25**3*VAR29 + CONST296*VAR25**4) + VAR20*(CONST002*VAR16*(CONST040*VAR25**3 + CONST163*VAR25**2*VAR29 - CONST163*VAR25*VAR29**2 + CONST280*VAR29**3) + CONST020*VAR29**4 - CONST237*VAR25**3*VAR29 + CONST237*VAR25*VAR29**3 + CONST300*VAR25**4) + VAR21*(CONST253*VAR07*VAR26 + VAR11*(CONST082*VAR15 + CONST140*VAR25*VAR36 + CONST156*VAR13*VAR25**2 + CONST253*VAR07*VAR25**3) + VAR14*(CONST139*VAR36 + CONST202*VAR07*VAR25**2 + CONST303*VAR13*VAR25) + VAR39*(CONST156*VAR13 + CONST202*VAR07*VAR25)) + VAR24*(CONST002*VAR16*(CONST112*VAR01*VAR39 + CONST195*VAR11*VAR33) + CONST004*VAR16**2*(CONST195*VAR01*VAR14 - CONST195*VAR10*VAR11) + CONST038*VAR14*VAR33 + CONST284*VAR10*VAR39 - CONST284*VAR11*VAR23 + CONST285*VAR01*VAR26) + VAR27*(CONST258*VAR07*VAR26 + VAR11*(-CONST067*VAR25*VAR36 - CONST206*VAR07*VAR25**3 + CONST217*VAR13*VAR25**2) + VAR14*(CONST113*VAR13*VAR25 + CONST185*VAR36 - CONST187*VAR07*VAR25**2) + VAR39*(CONST059*VAR07*VAR25 - CONST171*VAR13)) + VAR30*(CONST292*VAR01*VAR26 + VAR11*(CONST151*VAR01*VAR16**3 - CONST165*VAR16*VAR33 + CONST207*VAR10*VAR16**2 + CONST292*VAR23) + VAR14*(CONST207*VAR01*VAR16**2 + CONST223*VAR10*VAR16 + CONST270*VAR33) + VAR39*(-CONST165*VAR01*VAR16 + CONST270*VAR10)) + VAR35*(CONST002*VAR16*(CONST103*VAR11*VAR33 + CONST138*VAR10*VAR14 - CONST205*VAR01*VAR39) - CONST237*VAR10*VAR39 - CONST237*VAR14*VAR33 + CONST272*VAR01*VAR26 + CONST272*VAR11*VAR23) + VAR37*(CONST108*VAR07*VAR11*VAR25**3 - CONST134*VAR07*VAR25*VAR39 + CONST262*VAR07*VAR14*VAR25**2 + CONST282*VAR07*VAR26) + VAR38*(CONST137*VAR25**2*VAR29**2 + CONST170*VAR25**4 + CONST170*VAR29**4 + CONST215*VAR25**3*VAR29 + CONST215*VAR25*VAR29**3) + VAR40*(CONST002*VAR16*(CONST117*VAR25**2*VAR29 + CONST117*VAR25*VAR29**2 + CONST259*VAR29**3 + CONST260*VAR25**3) + CONST004*VAR16**2*(CONST058*VAR25**2 + CONST058*VAR29**2 + CONST176*VAR25*VAR29) + CONST018*VAR25**4 + CONST019*VAR29**4 + CONST249*VAR25**2*VAR29**2 + CONST284*VAR25*VAR29**3 + CONST285*VAR25**3*VAR29) + VAR42*(CONST252*VAR07*VAR23 + VAR01*(CONST081*VAR15 + CONST139*VAR29*VAR36 + CONST157*VAR13*VAR29**2 + CONST252*VAR07*VAR29**3) + VAR10*(CONST140*VAR36 + CONST202*VAR07*VAR29**2 + CONST303*VAR13*VAR29) + VAR33*(CONST157*VAR13 + CONST203*VAR07*VAR29)) +g_z = VAR00*(CONST007*VAR29**4 + CONST189*VAR25**4 + CONST256*VAR25**2*VAR29**2 + CONST269*VAR25**3*VAR29 + VAR16**2*(CONST114*VAR25**2 + CONST114*VAR29**2 + CONST168*VAR25*VAR29) + VAR16*(CONST062*VAR25**2*VAR29 + CONST133*VAR25*VAR29**2 + CONST213*VAR25**3 + CONST226*VAR29**3)) + VAR02*(CONST002*VAR25*(CONST021*VAR29**3 - CONST201*VAR16**2*VAR29 + CONST219*VAR16*VAR29**2 - CONST267*VAR16**3) + CONST004*VAR25**2*(CONST233*VAR16**2 + CONST283*VAR16*VAR29 - CONST301*VAR29**2) + CONST107*VAR16*VAR25**3 - CONST187*VAR16**2*VAR29**2 + CONST216*VAR16**3*VAR29 + CONST239*VAR16*VAR29**3 + CONST297*VAR25**4 - CONST302*VAR29**4) + VAR03*(CONST075*VAR29**4 + CONST091*VAR25**3*VAR29 + CONST244*VAR25*VAR29**3 + CONST251*VAR25**2*VAR29**2 + CONST295*VAR25**4 + VAR16*(CONST079*VAR25**3 + CONST142*VAR25**2*VAR29 - CONST142*VAR25*VAR29**2 + CONST228*VAR29**3)) + VAR04*(CONST043*VAR11*VAR23 + CONST269*VAR10*VAR39 + CONST294*VAR01*VAR26 + VAR16**2*(CONST054*VAR01*VAR14 + CONST261*VAR10*VAR11) + VAR16*(CONST121*VAR11*VAR33 + CONST145*VAR01*VAR39 + CONST154*VAR10*VAR14)) + VAR07*VAR18*(-CONST066*VAR25*VAR39 + CONST149*VAR14*VAR25**2 - CONST183*VAR11*VAR25**3 + CONST271*VAR26) + VAR07*VAR38*(CONST066*VAR29*VAR33 - CONST149*VAR10*VAR29**2 + CONST183*VAR01*VAR29**3 - CONST271*VAR23) + VAR17*(CONST246*VAR01*VAR15 + VAR07*(CONST047*VAR01*VAR29**3 + CONST047*VAR23 + CONST088*VAR29*VAR33 + CONST089*VAR10*VAR29**2) + VAR13*(CONST131*VAR10*VAR29 + CONST178*VAR01*VAR29**2 + CONST178*VAR33) + VAR36*(CONST141*VAR01*VAR29 + CONST141*VAR10)) + VAR19*(CONST161*VAR10*VAR36 - CONST250*VAR01*VAR15 + VAR07*(CONST039*VAR01*VAR29**3 + CONST238*VAR29*VAR33 + CONST255*VAR23) + VAR13*(CONST123*VAR10*VAR29 - CONST165*VAR33 + CONST218*VAR01*VAR29**2)) + VAR20*(VAR07*(-CONST147*VAR29*VAR33 + CONST205*VAR01*VAR29**3 + CONST265*VAR23) + VAR13*(-CONST069*VAR01*VAR29**2 + CONST099*VAR10*VAR29 + CONST103*VAR33)) + VAR21*(CONST011*VAR01*VAR26 + VAR11*(CONST011*VAR23 + CONST156*VAR10*VAR16**2 + CONST199*VAR01*VAR16**3 + CONST202*VAR16*VAR33) + VAR14*(CONST029*VAR33 + CONST157*VAR01*VAR16**2 + CONST173*VAR10*VAR16) + VAR39*(CONST029*VAR10 + CONST202*VAR01*VAR16)) + VAR22*(CONST022*VAR25**4 + CONST023*VAR29**4 + CONST179*VAR25**3*VAR29 + CONST180*VAR25*VAR29**3 + CONST204*VAR25**2*VAR29**2) + VAR24*(VAR07*(CONST092*VAR14*VAR25**2 + CONST105*VAR11*VAR25**3 + CONST242*VAR25*VAR39 + CONST285*VAR26) + VAR13*(CONST112*VAR39 + CONST222*VAR11*VAR25**2) + VAR36*(-CONST130*VAR11*VAR25 + CONST195*VAR14)) + VAR27*(VAR11*(-CONST155*VAR16*VAR33 - CONST184*VAR01*VAR16**3 + CONST217*VAR10*VAR16**2 + CONST288*VAR23) + VAR14*(-CONST143*VAR10*VAR16 + CONST172*VAR01*VAR16**2 + CONST276*VAR33) + VAR39*(CONST059*VAR01*VAR16 + CONST290*VAR10)) + VAR30*(CONST292*VAR07*VAR26 + VAR11*(CONST124*VAR25*VAR36 + CONST191*VAR13*VAR25**2 + CONST229*VAR07*VAR25**3 - CONST250*VAR15) + VAR14*(CONST192*VAR36 + CONST196*VAR07*VAR25**2 + CONST223*VAR13*VAR25) + VAR39*(-CONST218*VAR13 + CONST221*VAR07*VAR25)) + VAR34*(CONST132*VAR14*VAR33 + CONST175*VAR10*VAR39 + CONST234*VAR01*VAR26 - CONST234*VAR11*VAR23) + VAR35*(VAR07*(CONST116*VAR25*VAR39 - CONST163*VAR14*VAR25**2 + CONST190*VAR11*VAR25**3 + CONST272*VAR26) + VAR13*(CONST099*VAR14*VAR25 - CONST205*VAR39 + CONST241*VAR11*VAR25**2)) + VAR37*(CONST275*VAR01*VAR26 + VAR11*(-CONST134*VAR16*VAR33 + CONST266*VAR23) + VAR14*(-CONST214*VAR33 + CONST227*VAR10*VAR16) + VAR39*(CONST053*VAR10 - CONST134*VAR01*VAR16)) + VAR40*(VAR07*(CONST188*VAR10*VAR29**2 + CONST210*VAR29*VAR33 + CONST260*VAR01*VAR29**3 - CONST260*VAR23) + VAR13*(CONST166*VAR01*VAR29**2 + CONST177*VAR33 - CONST222*VAR10*VAR29) + VAR36*(CONST129*VAR01*VAR29 - CONST195*VAR10)) + VAR42*(CONST000*VAR29**4 + CONST002*VAR25*(CONST100*VAR29**3 + CONST115*VAR16**2*VAR29 + CONST231*VAR16**3 + CONST235*VAR16*VAR29**2) + CONST004*VAR25**2*(CONST008*VAR29**2 + CONST087*VAR16**2 + CONST236*VAR16*VAR29) + CONST006*VAR25**3*(CONST005*VAR29 + CONST278*VAR16) + CONST015*VAR25**4 + CONST028*VAR16**4 + CONST087*VAR16**2*VAR29**2 + CONST232*VAR16**3*VAR29 + CONST278*VAR16*VAR29**3) diff --git a/notebooks/fwd_implementations/fwd_10.py b/notebooks/fwd_implementations/fwd_10.py new file mode 100644 index 0000000..4918ac9 --- /dev/null +++ b/notebooks/fwd_implementations/fwd_10.py @@ -0,0 +1,238 @@ +# -------------------- variable and constant definitions +CONST000 = 1.60956935264578 +CONST001 = 1.75869118663323 +CONST002 = -1021.92317475320 +CONST003 = 3.21913870529156 +CONST004 = 4.58257569495584 +CONST005 = 6.63243980843400 +CONST006 = 4.82870805793735 +CONST007 = 4.97432985632550 +CONST008 = 1545.18657853995 +CONST009 = 10.5521471197994 +CONST010 = 4.97432985632550 +CONST011 = 12.1657520803952 +CONST012 = 13.2648796168680 +CONST013 = 14.5025390506634 +CONST014 = 15.7883647328499 +CONST015 = 15.7302121789667 +CONST016 = 16.4144510752435 +CONST017 = 12.8765548211663 +CONST018 = 19.3148322317494 +CONST019 = 16.7271353825295 +CONST020 = 22.8629854262320 +CONST021 = 535.268332240943 +CONST022 = 23.2135393295190 +CONST023 = 24.6216766128653 +CONST024 = 27.2034486491732 +CONST025 = 541.428124558099 +CONST026 = -994.666978169547 +CONST027 = 33.9852909359329 +CONST028 = 33.9852909359329 +CONST029 = 35.5238206489124 +CONST030 = -984.867064514610 +CONST031 = -4.82870805793735 +CONST032 = 1070.53666448189 +CONST033 = -463.555973561985 +CONST034 = 49.2433532257305 +CONST035 = 53.2857309733686 +CONST036 = 53.2857309733686 +CONST037 = 56.3871618715269 +CONST038 = 56.3871618715269 +CONST039 = 56.2781179722634 +CONST040 = -1989.33395633909 +CONST041 = 571.272421632637 +CONST042 = -450.224943778107 +CONST043 = 66.9085415301178 +CONST044 = 69.6406179885570 +CONST045 = 69.6406179885570 +CONST046 = -437.967074894228 +CONST047 = 77.2593289269976 +CONST048 = 78.6510608948335 +CONST049 = 590.920238708766 +CONST050 = -1969.73412902922 +CONST051 = 77.3468749368712 +CONST052 = 1624.28437367430 +CONST053 = 88.2963759165686 +CONST054 = 1114.24988781691 +CONST055 = 94.7301883970997 +CONST056 = 98.4867064514610 +CONST057 = 100.362812295177 +CONST058 = -412.049754277320 +CONST059 = 101.517773354644 +CONST060 = -5.63871618715269 +CONST061 = -406.071093418574 +CONST062 = 109.491768723557 +CONST063 = -393.946825805844 +CONST064 = -902.194589944431 +CONST065 = 122.415518921279 +CONST066 = -386.296644634988 +CONST067 = -386.296644634988 +CONST068 = 131.315608601948 +CONST069 = 131.315608601948 +CONST070 = 2707.14062279049 +CONST071 = 150.074981259369 +CONST072 = 154.518657853995 +CONST073 = 1181.84047741753 +CONST074 = 685.526905959165 +CONST075 = -337.668707833581 +CONST076 = -337.668707833581 +CONST077 = 176.178376404427 +CONST078 = 176.592751833137 +CONST079 = 185.708314636152 +CONST080 = -326.441383790078 +CONST081 = -1.60956935264578 +CONST082 = -1.97354559160624 +CONST083 = 196.973412902922 +CONST084 = 196.973412902922 +CONST085 = -824.099508554641 +CONST086 = 203.035546709287 +CONST087 = -1.97354559160624 +CONST088 = -305.867618423396 +CONST089 = -305.867618423396 +CONST090 = 721.755671955545 +CONST091 = -305.867618423396 +CONST092 = -300.731529981477 +CONST093 = -300.731529981477 +CONST094 = -1.75869118663323 +CONST095 = -290.050781013267 +CONST096 = 734.076568351780 +CONST097 = 225.548647486108 +CONST098 = 225.548647486108 +CONST099 = -284.190565191299 +CONST100 = 742.833258544608 +CONST101 = -278.562471954228 +CONST102 = -278.562471954228 +CONST103 = -787.893651611688 +CONST104 = -787.893651611688 +CONST105 = 772.593289269975 +CONST106 = 787.893651611688 +CONST107 = 787.893651611688 +CONST108 = 278.562471954228 +CONST109 = -742.833258544608 +CONST110 = -1.65810995210850 +CONST111 = 284.190565191299 +CONST112 = -1761.78376404427 +CONST113 = -223.028471767059 +CONST114 = -734.076568351780 +CONST115 = 290.050781013267 +CONST116 = -220.222970505534 +CONST117 = 1321.33782303320 +CONST118 = 1321.33782303320 +CONST119 = -203.035546709287 +CONST120 = -1.65810995210850 +CONST121 = -196.973412902922 +CONST122 = -196.973412902922 +CONST123 = -696.406179885570 +CONST124 = 2.72034486491732 +CONST125 = 338.322971229162 +CONST126 = -1181.84047741753 +CONST127 = -669.085415301178 +CONST128 = -154.518657853995 +CONST129 = -669.085415301178 +CONST130 = 360.877835977772 +CONST131 = -154.518657853995 +CONST132 = -150.074981259369 +CONST133 = -2707.14062279049 +CONST134 = -146.815313670356 +CONST135 = 880.891882022136 +CONST136 = 1392.81235977114 +CONST137 = 1392.81235977114 +CONST138 = -131.315608601948 +CONST139 = -131.315608601948 +CONST140 = 386.296644634988 +CONST141 = -125.841697431734 +CONST142 = -125.841697431734 +CONST143 = -122.415518921279 +CONST144 = 393.946825805844 +CONST145 = 406.071093418574 +CONST146 = -103.107953136506 +CONST147 = -103.107953136506 +CONST148 = -101.517773354644 +CONST149 = 412.049754277320 +CONST150 = -98.4867064514610 +CONST151 = -94.7301883970997 +CONST152 = -1114.24988781691 +CONST153 = -88.2963759165686 +CONST154 = -1624.28437367430 +CONST155 = -82.8889148474622 +CONST156 = -82.8889148474622 +CONST157 = 1969.73412902922 +CONST158 = -590.920238708766 +CONST159 = -77.3468749368713 +CONST160 = -77.2593289269975 +CONST161 = 2486.66744542387 +CONST162 = -2626.31217203896 +CONST163 = 450.224943778107 +CONST164 = 1989.33395633909 +CONST165 = -571.272421632637 +CONST166 = -56.2781179722634 +CONST167 = -49.2433532257305 +CONST168 = -49.2433532257305 +CONST169 = 984.867064514610 +CONST170 = -541.428124558099 +CONST171 = -24.6216766128653 +CONST172 = -22.8629854262320 +CONST173 = -16.4144510752435 +CONST174 = -15.7883647328499 +CONST175 = -14.0695294930659 +CONST176 = -13.2648796168680 +CONST177 = -14.5025390506634 +CONST178 = -11.2774323743054 +CONST179 = -6.63243980843400 +CONST180 = -5.63871618715269 +CONST181 = 1532.88476212980 +CONST182 = -3.21913870529156 +CONST183 = -2.72034486491732 +CONST184 = -1.12774323743054 +VAR00 = z**4 +VAR01 = x**7 +VAR02 = z**6 +VAR03 = x**9 +VAR04 = x**2 +VAR05 = z**8 +VAR06 = y +VAR07 = y**6 +VAR08 = x**4 +VAR09 = z**10 +VAR10 = y**8 +VAR11 = z**3 +VAR12 = x**6 +VAR13 = z +VAR14 = y**10 +VAR15 = x +VAR16 = z**5 +VAR17 = x**8 +VAR18 = y**3 +VAR19 = x**10 +VAR20 = y**5 +VAR21 = x**3 +VAR22 = y**7 +VAR23 = x**5 +VAR24 = y**9 +VAR25 = z**7 +VAR26 = y**2 +VAR27 = z**9 +VAR28 = y**4 +VAR29 = z**2 +# -------------------- kernel implementations +Y00 = CONST024*VAR11**3*VAR15 + CONST024*VAR13*VAR21**3 + CONST074*VAR16*VAR23 + CONST080*VAR01*VAR11 + CONST080*VAR21*VAR25 +Y01 = VAR06*(CONST002*VAR21*VAR29**3 + CONST011*VAR21**3 + CONST046*VAR01*VAR29 + CONST062*VAR15*VAR29**4 + CONST181*VAR23*VAR29**2) +Y02 = CONST014*VAR13*VAR21**3 + CONST055*VAR21*VAR25 + CONST151*VAR01*VAR11 + CONST174*VAR11**3*VAR15 + VAR26*(-CONST040*VAR11*VAR23 + CONST040*VAR16*VAR21 + CONST099*VAR01*VAR13 - CONST099*VAR15*VAR25) +Y03 = VAR06*(CONST095*VAR01*VAR29 - CONST119*VAR23*VAR29**2 + CONST145*VAR21*VAR29**3 + CONST148*VAR15*VAR29**4 - CONST177*VAR21**3) + VAR18*(CONST025*VAR15*VAR29**3 + CONST052*VAR23*VAR29 + CONST133*VAR21*VAR29**2 + CONST159*VAR01) +Y04 = CONST009*VAR13*VAR21**3 + VAR01*(CONST076*VAR13*VAR26 + CONST175*VAR11) + VAR15*(CONST009*VAR11**3 + CONST075*VAR25*VAR26 + CONST106*VAR16*VAR26**2) + VAR21*(CONST106*VAR16*VAR26 + CONST162*VAR11*VAR26**2 + CONST175*VAR25) + VAR23*(CONST106*VAR13*VAR26**2 + CONST107*VAR11*VAR26 + CONST167*VAR16) +Y05 = VAR06*(CONST015*VAR21**3 + CONST048*VAR15*VAR29**4 + CONST116*VAR23*VAR29**2 + CONST141*VAR01*VAR29) + VAR18*(CONST114*VAR15*VAR29**3 - CONST114*VAR21*VAR29**2 + CONST117*VAR23*VAR29 + CONST134*VAR01) + VAR20*(CONST077*VAR23 + CONST112*VAR21*VAR29 + CONST135*VAR15*VAR29**2) +Y06 = CONST005*VAR13*VAR21**3 + VAR01*(CONST012*VAR11 + CONST102*VAR13*VAR26) + VAR15*(CONST108*VAR25*VAR26 - CONST109*VAR11*VAR26**3 + CONST152*VAR16*VAR26**2 + CONST179*VAR11**3) + VAR21*(CONST108*VAR16*VAR26 + CONST109*VAR13*VAR26**3 + CONST176*VAR25) + VAR23*(CONST101*VAR11*VAR26 - CONST152*VAR13*VAR26**2) +Y07 = VAR06*(CONST016*VAR21**3 + CONST138*VAR21*VAR29**3 + CONST150*VAR23*VAR29**2 + CONST168*VAR15*VAR29**4) + VAR18*(CONST083*VAR23*VAR29 + CONST121*VAR01 - CONST158*VAR15*VAR29**3 + CONST169*VAR21*VAR29**2) + VAR20*(-CONST063*VAR23 + CONST103*VAR21*VAR29 + CONST126*VAR15*VAR29**2) + VAR22*(-CONST042*VAR15*VAR29 + CONST132*VAR21) +Y08 = -CONST182*VAR13*VAR21**3 + VAR01*(CONST017*VAR11 + CONST128*VAR13*VAR26) + VAR15*(CONST078*VAR13*VAR26**4 + CONST085*VAR11*VAR26**3 + CONST105*VAR16*VAR26**2 + CONST128*VAR25*VAR26 - CONST182*VAR11**3) + VAR21*(CONST008*VAR11*VAR26**2 + CONST017*VAR25 + CONST033*VAR16*VAR26 + CONST085*VAR13*VAR26**3) + VAR23*(CONST018*VAR16 + CONST033*VAR11*VAR26 + CONST105*VAR13*VAR26**2) +Y09 = CONST019*VAR06*VAR21**3 + VAR01*(CONST043*VAR06*VAR29 + CONST113*VAR18) + VAR15*(CONST019*VAR06*VAR29**4 + CONST021*VAR20*VAR29**2 + CONST027*VAR18**3 + CONST088*VAR22*VAR29 + CONST113*VAR18*VAR29**3) + VAR21*(CONST032*VAR20*VAR29 + CONST043*VAR06*VAR29**3 + CONST088*VAR22 + CONST127*VAR18*VAR29**2) + VAR23*(CONST021*VAR20 + CONST057*VAR06*VAR29**2 + CONST129*VAR18*VAR29) +Y10 = CONST004*VAR26**5 + CONST038*VAR26*VAR29**4 + CONST093*VAR26**2*VAR29**3 + CONST130*VAR26**3*VAR29**2 + CONST147*VAR26**4*VAR29 + CONST184*VAR04**5 + CONST184*VAR29**5 + VAR04**4*(CONST037*VAR26 + CONST060*VAR29) + VAR04**3*(CONST092*VAR26**2 + CONST098*VAR26*VAR29 + CONST178*VAR29**2) + VAR04**2*(CONST064*VAR26**2*VAR29 + CONST125*VAR26*VAR29**2 + CONST130*VAR26**3 + CONST178*VAR29**3) + VAR04*(CONST064*VAR26**2*VAR29**2 + CONST090*VAR26**3*VAR29 + CONST097*VAR26*VAR29**3 + CONST146*VAR26**4 + CONST180*VAR29**4) +Y11 = CONST019*VAR06*VAR11**3 + VAR11*(CONST032*VAR04*VAR20 + CONST043*VAR04**3*VAR06 + CONST091*VAR22 + CONST127*VAR04**2*VAR18) + VAR13*(CONST019*VAR04**4*VAR06 + CONST021*VAR04**2*VAR20 + CONST028*VAR18**3 + CONST089*VAR04*VAR22 + CONST113*VAR04**3*VAR18) + VAR16*(CONST021*VAR20 + CONST057*VAR04**2*VAR06 + CONST129*VAR04*VAR18) + VAR25*(CONST043*VAR04*VAR06 + CONST113*VAR18) +Y12 = CONST058*VAR26**3*VAR29**2 - CONST067*VAR26**2*VAR29**3 + CONST081*VAR04**5 - CONST081*VAR29**5 - CONST153*VAR26**4*VAR29 + CONST160*VAR26*VAR29**4 + VAR04**4*(CONST031*VAR29 + CONST047*VAR26) + VAR04**3*(CONST067*VAR26**2 - CONST128*VAR26*VAR29 + CONST182*VAR29**2) + VAR04**2*(CONST066*VAR26**2*VAR29 + CONST149*VAR26**3 - CONST182*VAR29**3) + VAR04*(CONST006*VAR29**4 - CONST067*VAR26**2*VAR29**2 + CONST131*VAR26*VAR29**3 + CONST153*VAR26**4) +Y13 = VAR06*(-CONST138*VAR04**3*VAR11 - CONST150*VAR04**2*VAR16 - CONST168*VAR04**4*VAR13 + CONST173*VAR11**3) + VAR18*(CONST030*VAR04**2*VAR11 - CONST121*VAR25 + CONST122*VAR04*VAR16 + CONST158*VAR04**3*VAR13) + VAR20*(CONST063*VAR16 + CONST107*VAR04*VAR11 - CONST126*VAR04**2*VAR13) + VAR22*(CONST042*VAR04*VAR13 + CONST071*VAR11) +Y14 = CONST045*VAR26*VAR29**4 + CONST079*VAR26**3*VAR29**2 + CONST101*VAR26**2*VAR29**3 + CONST110*VAR04**5 + CONST120*VAR29**5 + VAR04**4*(CONST010*VAR29 + CONST044*VAR26) + VAR04**3*(CONST022*VAR29**2 + CONST101*VAR26**2 + CONST101*VAR26*VAR29) + VAR04**2*(CONST022*VAR29**3 + CONST079*VAR26**3 + CONST123*VAR26*VAR29**2 + CONST137*VAR26**2*VAR29) + VAR04*(CONST007*VAR29**4 + CONST101*VAR26*VAR29**3 + CONST136*VAR26**2*VAR29**2 + CONST152*VAR26**3*VAR29) +Y15 = VAR06*(CONST015*VAR11**3 + CONST048*VAR04**4*VAR13 + CONST116*VAR04**2*VAR16 + CONST142*VAR04*VAR25) + VAR18*(CONST114*VAR04**3*VAR13 - CONST114*VAR04**2*VAR11 + CONST118*VAR04*VAR16 + CONST134*VAR25) + VAR20*(CONST077*VAR16 + CONST112*VAR04*VAR11 + CONST135*VAR04**2*VAR13) +Y16 = CONST001*VAR29**5 + CONST094*VAR04**5 - CONST139*VAR26**2*VAR29**3 + CONST166*VAR26*VAR29**4 + VAR04**4*(CONST020*VAR29 - CONST166*VAR26) + VAR04**3*(CONST023*VAR29**2 + CONST104*VAR26*VAR29 + CONST139*VAR26**2) + VAR04**2*(-CONST050*VAR26**2*VAR29 + CONST171*VAR29**3) + VAR04*(CONST050*VAR26**2*VAR29**2 + CONST106*VAR26*VAR29**3 + CONST172*VAR29**4) +Y17 = VAR06*(CONST059*VAR04**4*VAR13 + CONST061*VAR04**3*VAR11 - CONST095*VAR04*VAR25 + CONST119*VAR04**2*VAR16 + CONST177*VAR11**3) + VAR18*(CONST051*VAR25 - CONST133*VAR04**2*VAR11 + CONST154*VAR04*VAR16 + CONST170*VAR04**3*VAR13) +Y18 = CONST035*VAR04**4*VAR29 + CONST036*VAR04*VAR29**4 + CONST082*VAR04**5 + CONST087*VAR29**5 + CONST155*VAR04**3*VAR29**2 + CONST156*VAR04**2*VAR29**3 + VAR26*(CONST026*VAR04**3*VAR29 + CONST026*VAR04*VAR29**3 + CONST029*VAR04**4 + CONST029*VAR29**4 + CONST161*VAR04**2*VAR29**2) +Y19 = VAR06*(CONST002*VAR04**3*VAR11 + CONST011*VAR11**3 + CONST046*VAR04*VAR25 + CONST062*VAR04**4*VAR13 + CONST181*VAR04**2*VAR16) +Y20 = -CONST143*VAR04**4*VAR29 + CONST143*VAR04*VAR29**4 + CONST165*VAR04**3*VAR29**2 - CONST165*VAR04**2*VAR29**3 + CONST183*VAR04**5 - CONST183*VAR29**5 diff --git a/notebooks/fwd_implementations/fwd_2.py b/notebooks/fwd_implementations/fwd_2.py new file mode 100644 index 0000000..527a70d --- /dev/null +++ b/notebooks/fwd_implementations/fwd_2.py @@ -0,0 +1,18 @@ +# -------------------- variable and constant definitions +CONST000 = 1.93649167310371 +CONST001 = 2.23606797749979 +CONST002 = 3.87298334620742 +CONST003 = -1.93649167310371 +CONST004 = -1.11803398874989 +VAR00 = y**2 +VAR01 = x**2 +VAR02 = z**2 +VAR03 = z +VAR04 = x +VAR05 = y +# -------------------- kernel implementations +Y00 = CONST002*VAR03*VAR04 +Y01 = CONST002*VAR04*VAR05 +Y02 = CONST001*VAR00 + CONST004*VAR01 + CONST004*VAR02 +Y03 = CONST002*VAR03*VAR05 +Y04 = CONST003*VAR01 - CONST003*VAR02 diff --git a/notebooks/fwd_implementations/fwd_3.py b/notebooks/fwd_implementations/fwd_3.py new file mode 100644 index 0000000..a54aa11 --- /dev/null +++ b/notebooks/fwd_implementations/fwd_3.py @@ -0,0 +1,29 @@ +# -------------------- variable and constant definitions +CONST000 = 2.64575131106459 +CONST001 = 2.09165006633519 +CONST002 = 5.12347538297980 +CONST003 = 6.27495019900557 +CONST004 = 6.48074069840786 +CONST005 = 10.2469507659596 +CONST006 = -2.09165006633519 +CONST007 = -1 +CONST008 = -6.27495019900557 +CONST009 = -3.96862696659689 +CONST010 = -1.62018517460197 +VAR00 = y**3 +VAR01 = y**2 +VAR02 = x**3 +VAR03 = z**3 +VAR04 = z +VAR05 = x +VAR06 = x**2 +VAR07 = z**2 +VAR08 = y +# -------------------- kernel implementations +Y00 = CONST006*VAR02 - CONST008*VAR05*VAR07 +Y01 = CONST005*VAR04*VAR05*VAR08 +Y02 = CONST010*VAR02 + VAR05*(CONST004*VAR01 + CONST010*VAR07) +Y03 = CONST000*VAR00 + CONST009*VAR06*VAR08 + CONST009*VAR07*VAR08 +Y04 = CONST010*VAR03 + VAR04*(CONST004*VAR01 + CONST010*VAR06) +Y05 = CONST002*VAR08*(CONST007*VAR06 + VAR07) +Y06 = -CONST006*VAR03 + CONST008*VAR04*VAR06 diff --git a/notebooks/fwd_implementations/fwd_4.py b/notebooks/fwd_implementations/fwd_4.py new file mode 100644 index 0000000..79328c5 --- /dev/null +++ b/notebooks/fwd_implementations/fwd_4.py @@ -0,0 +1,43 @@ +# -------------------- variable and constant definitions +CONST000 = 1.12500000000000 +CONST001 = 2.25000000000000 +CONST002 = 3.00000000000000 +CONST003 = 1.67705098312484 +CONST004 = 2.21852991866236 +CONST005 = 6.27495019900557 +CONST006 = 8.87411967464942 +CONST007 = 9.48683298050514 +CONST008 = 10.0623058987491 +CONST009 = 18.8248505970167 +CONST010 = 20.1246117974981 +CONST011 = -18.8248505970167 +CONST012 = -13.3111795119741 +CONST013 = -10.0623058987491 +CONST014 = -9.00000000000000 +CONST015 = -8.87411967464942 +CONST016 = -7.11512473537885 +CONST017 = -6.27495019900557 +CONST018 = -3.35410196624968 +CONST019 = -1.67705098312484 +VAR00 = y +VAR01 = x**4 +VAR02 = z**4 +VAR03 = y**2 +VAR04 = x**3 +VAR05 = z**3 +VAR06 = y**4 +VAR07 = z +VAR08 = x +VAR09 = x**2 +VAR10 = z**2 +VAR11 = y**3 +# -------------------- kernel implementations +Y00 = CONST015*VAR04*VAR07 - CONST015*VAR05*VAR08 +Y01 = VAR00*(-CONST011*VAR08*VAR10 + CONST017*VAR04) +Y02 = CONST018*VAR04*VAR07 + VAR08*(CONST010*VAR03*VAR07 + CONST018*VAR05) +Y03 = CONST016*VAR00*VAR04 + VAR08*(CONST007*VAR11 + CONST016*VAR00*VAR10) +Y04 = CONST000*VAR09**2 + CONST000*VAR10**2 + CONST002*VAR03**2 + CONST014*VAR03*VAR10 + VAR09*(CONST001*VAR10 + CONST014*VAR03) +Y05 = CONST016*VAR00*VAR05 + VAR07*(CONST007*VAR11 + CONST016*VAR00*VAR09) +Y06 = -CONST019*VAR09**2 + CONST019*VAR10**2 + VAR03*(CONST013*VAR09 - CONST013*VAR10) +Y07 = VAR00*(CONST011*VAR07*VAR09 - CONST017*VAR05) +Y08 = CONST004*VAR09**2 + CONST004*VAR10**2 + CONST012*VAR09*VAR10 diff --git a/notebooks/fwd_implementations/fwd_5.py b/notebooks/fwd_implementations/fwd_5.py new file mode 100644 index 0000000..8a71186 --- /dev/null +++ b/notebooks/fwd_implementations/fwd_5.py @@ -0,0 +1,63 @@ +# -------------------- variable and constant definitions +CONST000 = 1.73430461568895 +CONST001 = 2.32681380862329 +CONST002 = 1.60565407233314 +CONST003 = 3.21130814466628 +CONST004 = 3.31662479035540 +CONST005 = 6.21867148191637 +CONST006 = 6.21867148191637 +CONST007 = 1.60565407233314 +CONST008 = 8.49632273398321 +CONST009 = 5.20291384706685 +CONST010 = 11.6340690431164 +CONST011 = 12.8452325786651 +CONST012 = 12.4373429638327 +CONST013 = 12.8452325786651 +CONST014 = 16.9926454679664 +CONST015 = 13.8744369255116 +CONST016 = 29.4321253055229 +CONST017 = 33.9852909359329 +CONST018 = 7.35803132638072 +CONST019 = 41.6233107765348 +CONST020 = -44.1481879582843 +CONST021 = -41.6233107765348 +CONST022 = -29.4321253055229 +CONST023 = -23.2681380862329 +CONST024 = -19.2678488679977 +CONST025 = -19.2678488679977 +CONST026 = -16.9926454679664 +CONST027 = -16.9926454679664 +CONST028 = -16.5831239517770 +CONST029 = -13.8744369255116 +CONST030 = 3.46860923137790 +CONST031 = -8.49632273398321 +CONST032 = -5.20291384706685 +CONST033 = -3.46860923137790 +CONST034 = -1.73430461568895 +VAR00 = y**3 +VAR01 = x**4 +VAR02 = z**4 +VAR03 = y**2 +VAR04 = y**5 +VAR05 = x**3 +VAR06 = z**3 +VAR07 = y**4 +VAR08 = z +VAR09 = x +VAR10 = x**5 +VAR11 = z**5 +VAR12 = x**2 +VAR13 = z**2 +VAR14 = y +# -------------------- kernel implementations +Y00 = CONST001*VAR10 + CONST010*VAR09*VAR13**2 + CONST023*VAR05*VAR13 +Y01 = VAR14*(CONST022*VAR05*VAR08 - CONST022*VAR06*VAR09) +Y02 = CONST000*VAR10 + VAR05*(CONST029*VAR03 + CONST033*VAR13) + VAR09*(-CONST021*VAR03*VAR13 + CONST032*VAR13**2) +Y03 = CONST027*VAR05*VAR08*VAR14 + VAR09*(CONST017*VAR00*VAR08 + CONST026*VAR06*VAR14) +Y04 = CONST002*VAR10 + VAR05*(CONST003*VAR13 + CONST025*VAR03) + VAR09*(CONST002*VAR13**2 + CONST011*VAR03**2 + CONST024*VAR03*VAR13) +Y05 = CONST004*VAR04 + VAR00*(CONST028*VAR12 + CONST028*VAR13) + VAR14*(CONST005*VAR12**2 + CONST006*VAR13**2 + CONST012*VAR12*VAR13) +Y06 = CONST002*VAR11 + VAR06*(CONST003*VAR12 + CONST024*VAR03) + VAR08*(CONST007*VAR12**2 + CONST013*VAR03**2 + CONST024*VAR03*VAR12) +Y07 = VAR00*(CONST026*VAR12 - CONST026*VAR13) + VAR14*(-CONST031*VAR12**2 + CONST031*VAR13**2) +Y08 = CONST034*VAR11 + VAR06*(CONST015*VAR03 + CONST030*VAR12) + VAR08*(CONST021*VAR03*VAR12 - CONST032*VAR12**2) +Y09 = VAR14*(CONST018*VAR12**2 + CONST018*VAR13**2 + CONST020*VAR12*VAR13) +Y10 = CONST001*VAR11 + CONST010*VAR08*VAR12**2 + CONST023*VAR06*VAR12 diff --git a/notebooks/fwd_implementations/fwd_6.py b/notebooks/fwd_implementations/fwd_6.py new file mode 100644 index 0000000..0e09e4c --- /dev/null +++ b/notebooks/fwd_implementations/fwd_6.py @@ -0,0 +1,81 @@ +# -------------------- variable and constant definitions +CONST000 = 1.63279380970164 +CONST001 = 3.26558761940328 +CONST002 = 3.26558761940328 +CONST003 = 3.60555127546399 +CONST004 = 6.53117523880657 +CONST005 = 7.15454401062709 +CONST006 = 8.38944649544891 +CONST007 = 9.79676285820985 +CONST008 = 10.3266947761614 +CONST009 = -1.78863600265677 +CONST010 = 8.94318001328386 +CONST011 = 2.42182459624970 +CONST012 = 14.5309475774982 +CONST013 = 16.5227116418583 +CONST014 = 16.5227116418583 +CONST015 = 17.8863600265677 +CONST016 = 20.6533895523229 +CONST017 = 20.2812259244849 +CONST018 = 19.5935257164197 +CONST019 = -107.318160159406 +CONST020 = 17.8863600265677 +CONST021 = 26.1247009552263 +CONST022 = 29.3902885746295 +CONST023 = 36.3273689437454 +CONST024 = 40.5624518489699 +CONST025 = 41.9472324772445 +CONST026 = -1.63279380970164 +CONST027 = -83.8944649544891 +CONST028 = -78.3741028656788 +CONST029 = 52.2494019104525 +CONST030 = -71.5454401062709 +CONST031 = 71.5454401062709 +CONST032 = -52.2494019104525 +CONST033 = -52.2494019104525 +CONST034 = 78.3741028656788 +CONST035 = -48.4364919249939 +CONST036 = -41.3067791046458 +CONST037 = -36.3273689437454 +CONST038 = -29.3902885746295 +CONST039 = -26.1247009552263 +CONST040 = -27.0416345659799 +CONST041 = -26.1247009552263 +CONST042 = -19.5935257164197 +CONST043 = -2.42182459624970 +CONST044 = -9.79676285820985 +CONST045 = -7.15454401062709 +CONST046 = -3.38020432074749 +CONST047 = -1.12673477358250 +VAR00 = y**6 +VAR01 = y**3 +VAR02 = x**4 +VAR03 = z**4 +VAR04 = y**2 +VAR05 = y**5 +VAR06 = x**3 +VAR07 = z**3 +VAR08 = x**6 +VAR09 = z**6 +VAR10 = y**4 +VAR11 = z +VAR12 = x +VAR13 = x**5 +VAR14 = z**5 +VAR15 = x**2 +VAR16 = z**2 +VAR17 = y +# -------------------- kernel implementations +Y00 = CONST012*VAR11*VAR13 + CONST012*VAR12*VAR14 + CONST035*VAR06*VAR07 +Y01 = VAR17*(CONST006*VAR13 + CONST025*VAR12*VAR16**2 + CONST027*VAR06*VAR16) +Y02 = -CONST045*VAR11*VAR13 + CONST045*VAR12*VAR14 + VAR04*(CONST030*VAR06*VAR11 - CONST030*VAR07*VAR12) +Y03 = VAR01*(-CONST028*VAR12*VAR16 + CONST039*VAR06) + VAR17*(CONST007*VAR13 + CONST038*VAR12*VAR16**2 + CONST042*VAR06*VAR16) +Y04 = CONST002*VAR11*VAR13 + VAR06*(CONST004*VAR07 + CONST033*VAR04*VAR11) + VAR12*(CONST001*VAR14 - CONST032*VAR04**2*VAR11 + CONST032*VAR04*VAR07) +Y05 = CONST008*VAR13*VAR17 + VAR06*(CONST016*VAR16*VAR17 + CONST036*VAR01) + VAR12*(CONST008*VAR16**2*VAR17 + CONST013*VAR05 + CONST036*VAR01*VAR16) +Y06 = CONST003*VAR04**3 + CONST017*VAR04*VAR16**2 + CONST040*VAR04**2*VAR16 + CONST047*VAR15**3 + CONST047*VAR16**3 + VAR15**2*(CONST017*VAR04 + CONST046*VAR16) + VAR15*(CONST024*VAR04*VAR16 + CONST040*VAR04**2 + CONST046*VAR16**2) +Y07 = CONST008*VAR14*VAR17 + VAR07*(CONST016*VAR15*VAR17 + CONST036*VAR01) + VAR11*(CONST008*VAR15**2*VAR17 + CONST014*VAR05 + CONST036*VAR01*VAR15) +Y08 = CONST026*VAR15**3 - CONST026*VAR16**3 + CONST039*VAR04*VAR16**2 - CONST041*VAR04**2*VAR16 + VAR15**2*(CONST026*VAR16 - CONST041*VAR04) + VAR15*(-CONST026*VAR16**2 + CONST041*VAR04**2) +Y09 = VAR01*(CONST028*VAR11*VAR15 - CONST041*VAR07) + VAR17*(CONST022*VAR11*VAR15**2 - CONST042*VAR07*VAR15 + CONST044*VAR14) +Y10 = CONST009*VAR15**3 + CONST009*VAR16**3 + CONST020*VAR04*VAR16**2 + VAR15**2*(CONST010*VAR16 + CONST015*VAR04) + VAR15*(CONST010*VAR16**2 + CONST019*VAR04*VAR16) +Y11 = VAR17*(CONST006*VAR14 + CONST025*VAR11*VAR15**2 + CONST027*VAR07*VAR15) +Y12 = -CONST037*VAR15**2*VAR16 + CONST037*VAR15*VAR16**2 + CONST043*VAR15**3 - CONST043*VAR16**3 diff --git a/notebooks/fwd_implementations/fwd_7.py b/notebooks/fwd_implementations/fwd_7.py new file mode 100644 index 0000000..a0b2766 --- /dev/null +++ b/notebooks/fwd_implementations/fwd_7.py @@ -0,0 +1,119 @@ +# -------------------- variable and constant definitions +CONST000 = 1.66389743899677 +CONST001 = 2.50682661696018 +CONST002 = 3.87298334620742 +CONST003 = 4.99169231699030 +CONST004 = 8.31948719498384 +CONST005 = 9.19753915797590 +CONST006 = 9.19753915797590 +CONST007 = 11.7655316231354 +CONST008 = 11.7655316231354 +CONST009 = 9.37968632871057 +CONST010 = 16.5555704843566 +CONST011 = 17.5477863187212 +CONST012 = 20.4939015319192 +CONST013 = 22.0740939791422 +CONST014 = 23.5310632462709 +CONST015 = 20.4939015319192 +CONST016 = 33.2779487799353 +CONST017 = 36.7901566319036 +CONST018 = 37.6497011940334 +CONST019 = 38.4260653723485 +CONST020 = 38.4260653723485 +CONST021 = 38.4260653723485 +CONST022 = 44.1481879582843 +CONST023 = -4.99169231699030 +CONST024 = 44.3705983732471 +CONST025 = 47.0621264925418 +CONST026 = 50.8329064189723 +CONST027 = 52.6433589561637 +CONST028 = 55.1852349478554 +CONST029 = 56.2781179722634 +CONST030 = 56.2781179722634 +CONST031 = 62.7495019900557 +CONST032 = 66.5558975598707 +CONST033 = 75.2994023880668 +CONST034 = 76.8521307446970 +CONST035 = 87.7389315936062 +CONST036 = 99.8338463398060 +CONST037 = 101.665812837945 +CONST038 = 110.370469895711 +CONST039 = 133.111795119741 +CONST040 = 140.695294930659 +CONST041 = 147.160626527614 +CONST042 = -1.66389743899677 +CONST043 = -9.37968632871057 +CONST044 = -1.66389743899677 +CONST045 = -220.740939791422 +CONST046 = -220.740939791422 +CONST047 = -1.60108605718119 +CONST048 = -187.593726574211 +CONST049 = -9.19753915797590 +CONST050 = -1.83950783159518 +CONST051 = -1.83950783159518 +CONST052 = -4.80325817154356 +CONST053 = -147.160626527614 +CONST054 = -140.695294930659 +CONST055 = -133.111795119741 +CONST056 = -125.499003980111 +CONST057 = -125.499003980111 +CONST058 = -99.8338463398060 +CONST059 = -87.7389315936062 +CONST060 = -76.8521307446970 +CONST061 = -66.5558975598707 +CONST062 = -62.7495019900557 +CONST063 = -52.6433589561637 +CONST064 = -44.1481879582843 +CONST065 = -44.3705983732471 +CONST066 = -40.6663251351779 +CONST067 = -40.6663251351779 +CONST068 = -8.31948719498384 +CONST069 = -37.6497011940334 +CONST070 = -33.2779487799353 +CONST071 = -25.4164532094862 +CONST072 = -25.4164532094862 +CONST073 = -17.5477863187212 +CONST074 = -11.7655316231354 +CONST075 = -11.0370469895711 +CONST076 = -9.19753915797590 +CONST077 = -8.47215106982872 +CONST078 = -4.80325817154356 +CONST079 = -2.50682661696018 +CONST080 = -1.60108605718119 +VAR00 = z**4 +VAR01 = x**7 +VAR02 = z**6 +VAR03 = x**2 +VAR04 = y +VAR05 = y**6 +VAR06 = x**4 +VAR07 = z**3 +VAR08 = x**6 +VAR09 = z +VAR10 = x +VAR11 = z**5 +VAR12 = y**3 +VAR13 = y**5 +VAR14 = x**3 +VAR15 = y**7 +VAR16 = x**5 +VAR17 = z**7 +VAR18 = y**2 +VAR19 = y**4 +VAR20 = z**2 +# -------------------- kernel implementations +Y00 = CONST059*VAR14*VAR20**2 - CONST063*VAR16*VAR20 - CONST073*VAR10*VAR20**3 + CONST079*VAR01 +Y01 = VAR04*(CONST029*VAR09*VAR16 + CONST030*VAR10*VAR11 + CONST048*VAR07*VAR14) +Y02 = CONST050*VAR01 + VAR10*(CONST038*VAR18*VAR20**2 + CONST076*VAR20**3) + VAR14*(CONST045*VAR18*VAR20 - CONST076*VAR20**2) + VAR16*(CONST010*VAR20 + CONST013*VAR18) +Y03 = VAR04*(-CONST064*VAR09*VAR16 + CONST064*VAR10*VAR11) + VAR12*(CONST041*VAR07*VAR10 + CONST053*VAR09*VAR14) +Y04 = CONST042*VAR01 + VAR10*(-CONST023*VAR20**3 - CONST055*VAR18**2*VAR20 + CONST058*VAR18*VAR20**2) + VAR14*(CONST061*VAR18*VAR20 + CONST065*VAR18**2 - CONST068*VAR20**2) + VAR16*(-CONST042*VAR20 - CONST070*VAR18) +Y05 = CONST014*VAR04*VAR09*VAR16 + VAR10*(CONST014*VAR04*VAR11 + CONST033*VAR09*VAR13 + CONST056*VAR07*VAR12) + VAR14*(CONST025*VAR04*VAR07 + CONST057*VAR09*VAR12) +Y06 = CONST047*VAR01 + VAR10*(CONST012*VAR18**3 + CONST019*VAR18*VAR20**2 + CONST060*VAR18**2*VAR20 + CONST080*VAR20**3) + VAR14*(CONST052*VAR20**2 + CONST060*VAR18**2 - CONST060*VAR18*VAR20) + VAR16*(CONST020*VAR18 + CONST078*VAR20) +Y07 = CONST002*VAR15 + VAR04*(CONST071*VAR03**2*VAR20 + CONST072*VAR03*VAR20**2 + CONST077*VAR03**3 + CONST077*VAR20**3) + VAR12*(CONST026*VAR03**2 + CONST026*VAR20**2 + CONST037*VAR03*VAR20) + VAR13*(CONST066*VAR03 + CONST067*VAR20) +Y08 = CONST047*VAR17 + VAR07*(CONST052*VAR03**2 - CONST060*VAR03*VAR18 + CONST060*VAR18**2) + VAR09*(CONST015*VAR18**3 + CONST021*VAR03**2*VAR18 + CONST047*VAR03**3 + CONST060*VAR03*VAR18**2) + VAR11*(CONST020*VAR18 + CONST052*VAR03) +Y09 = VAR04*(CONST008*VAR03*VAR20**2 + CONST074*VAR03**3 + CONST074*VAR03**2*VAR20 - CONST074*VAR20**3) + VAR12*(-CONST062*VAR03**2 + CONST062*VAR20**2) + VAR13*(CONST069*VAR03 - CONST069*VAR20) +Y10 = -CONST042*VAR17 + VAR07*(CONST032*VAR03*VAR18 - CONST065*VAR18**2 + CONST068*VAR03**2) + VAR09*(CONST023*VAR03**3 + CONST055*VAR03*VAR18**2 - CONST058*VAR03**2*VAR18) + VAR11*(CONST044*VAR03 + CONST070*VAR18) +Y11 = VAR04*(CONST028*VAR03**2*VAR20 + CONST028*VAR03*VAR20**2 + CONST075*VAR03**3 + CONST075*VAR20**3) + VAR12*(CONST017*VAR03**2 + CONST017*VAR20**2 + CONST046*VAR03*VAR20) +Y12 = CONST051*VAR17 + VAR07*(CONST045*VAR03*VAR18 - CONST049*VAR03**2) + VAR09*(CONST038*VAR03**2*VAR18 + CONST049*VAR03**3) + VAR11*(CONST010*VAR03 + CONST013*VAR18) +Y13 = VAR04*(CONST043*VAR03**3 - CONST043*VAR20**3 - CONST054*VAR03**2*VAR20 + CONST054*VAR03*VAR20**2) +Y14 = -CONST059*VAR03**2*VAR07 + CONST063*VAR03*VAR11 + CONST073*VAR03**3*VAR09 - CONST079*VAR17 diff --git a/notebooks/fwd_implementations/fwd_8.py b/notebooks/fwd_implementations/fwd_8.py new file mode 100644 index 0000000..de6adbc --- /dev/null +++ b/notebooks/fwd_implementations/fwd_8.py @@ -0,0 +1,147 @@ +# -------------------- variable and constant definitions +CONST000 = 1.12741169450483 +CONST001 = 1.61701765412441 +CONST002 = 3.23403530824881 +CONST003 = 4.12310562561766 +CONST004 = 4.50964677801932 +CONST005 = 6.78376969317208 +CONST006 = 6.76447016702898 +CONST007 = 1.69594242329302 +CONST008 = 1.88707052233084 +CONST009 = 10.3359109268366 +CONST010 = 2.58397773170915 +CONST011 = 13.1367135230810 +CONST012 = 13.1367135230810 +CONST013 = 20.6718218536732 +CONST014 = -489.184589393411 +CONST015 = 24.7386337537060 +CONST016 = 26.4189873126318 +CONST017 = 24.7386337537060 +CONST018 = 39.4101405692431 +CONST019 = 48.9184589393411 +CONST020 = 48.5105296237322 +CONST021 = 51.7445649319810 +CONST022 = 61.1480736741764 +CONST023 = 61.1480736741764 +CONST024 = 65.6835676154051 +CONST025 = 67.8376969317208 +CONST026 = 70.0624721230988 +CONST027 = 72.3513764878561 +CONST028 = 87.5780901538735 +CONST029 = 97.0210592474644 +CONST030 = -6.78376969317208 +CONST031 = 103.489129863962 +CONST032 = -407.026181590325 +CONST033 = 108.231522672464 +CONST034 = 108.231522672464 +CONST035 = 110.066532613517 +CONST036 = 110.066532613517 +CONST037 = -396.284809689477 +CONST038 = 129.361412329953 +CONST039 = 144.702752975712 +CONST040 = -361.756882439281 +CONST041 = -1.88707052233084 +CONST042 = 158.513923875791 +CONST043 = 162.810472636130 +CONST044 = 175.156180307747 +CONST045 = 180.878441219640 +CONST046 = 194.042118494929 +CONST047 = -12.2296147348353 +CONST048 = 203.513090795162 +CONST049 = 210.187416369296 +CONST050 = 217.054129463568 +CONST051 = 216.463045344927 +CONST052 = 216.463045344927 +CONST053 = -6.78376969317208 +CONST054 = -271.350787726883 +CONST055 = 244.592294696706 +CONST056 = 244.592294696706 +CONST057 = -262.734270461621 +CONST058 = -258.722824659905 +CONST059 = 262.734270461621 +CONST060 = 271.350787726883 +CONST061 = -217.054129463568 +CONST062 = -210.187416369296 +CONST063 = -175.156180307747 +CONST064 = -162.810472636130 +CONST065 = 361.756882439281 +CONST066 = -144.702752975712 +CONST067 = -129.877827206956 +CONST068 = -129.361412329953 +CONST069 = 396.284809689477 +CONST070 = -108.231522672464 +CONST071 = -108.231522672464 +CONST072 = -87.5780901538735 +CONST073 = -3.23403530824881 +CONST074 = -72.3513764878561 +CONST075 = -70.0624721230988 +CONST076 = -65.6835676154052 +CONST077 = -61.1480736741764 +CONST078 = -61.1480736741764 +CONST079 = -57.7234787586472 +CONST080 = -57.7234787586472 +CONST081 = -51.7445649319810 +CONST082 = -48.5105296237322 +CONST083 = -40.5868210021738 +CONST084 = -39.4101405692431 +CONST085 = -40.7026181590325 +CONST086 = -36.0771742241545 +CONST087 = -36.0771742241545 +CONST088 = -26.4189873126318 +CONST089 = -20.6718218536732 +CONST090 = -528.379746252636 +CONST091 = -16.9594242329302 +CONST092 = -13.1367135230810 +CONST093 = -12.2296147348353 +CONST094 = -11.3224231339851 +CONST095 = -10.3359109268366 +CONST096 = -9.70210592474644 +CONST097 = -11.3224231339851 +CONST098 = -13.5289403340579 +CONST099 = -6.78376969317208 +CONST100 = -13.5289403340579 +CONST101 = -13.1367135230810 +CONST102 = -3.23403530824881 +CONST103 = -1.61701765412441 +VAR00 = z**4 +VAR01 = x**7 +VAR02 = z**6 +VAR03 = x**2 +VAR04 = z**8 +VAR05 = y +VAR06 = y**6 +VAR07 = x**4 +VAR08 = y**8 +VAR09 = z**3 +VAR10 = x**6 +VAR11 = z +VAR12 = x +VAR13 = z**5 +VAR14 = x**8 +VAR15 = y**3 +VAR16 = y**5 +VAR17 = x**3 +VAR18 = y**7 +VAR19 = x**5 +VAR20 = z**7 +VAR21 = y**2 +VAR22 = y**4 +VAR23 = z**2 +# -------------------- kernel implementations +Y00 = -CONST066*VAR09*VAR19 + CONST066*VAR13*VAR17 + CONST089*VAR01*VAR11 - CONST089*VAR12*VAR20 +Y01 = VAR05*(CONST040*VAR17*VAR23**2 + CONST050*VAR19*VAR23 - CONST074*VAR12*VAR23**3 + CONST095*VAR01) +Y02 = CONST097*VAR01*VAR11 + VAR12*(CONST042*VAR13*VAR21 + CONST094*VAR20) + VAR17*(-CONST088*VAR13 + CONST090*VAR09*VAR21) + VAR19*(CONST042*VAR11*VAR21 - CONST088*VAR09) +Y03 = VAR05*(CONST035*VAR19*VAR23 + CONST077*VAR12*VAR23**3 - CONST078*VAR17*VAR23**2 + CONST093*VAR01) + VAR15*(CONST014*VAR17*VAR23 + CONST019*VAR19 + CONST055*VAR12*VAR23**2) +Y04 = CONST099*VAR01*VAR11 + VAR12*(-CONST053*VAR20 - CONST054*VAR09*VAR21**2 + CONST064*VAR13*VAR21) + VAR17*(-CONST053*VAR13 + CONST054*VAR11*VAR21**2) + VAR19*(-CONST064*VAR11*VAR21 + CONST099*VAR09) +Y05 = VAR05*(CONST011*VAR19*VAR23 + CONST024*VAR17*VAR23**2 - CONST084*VAR12*VAR23**3 + CONST092*VAR01) + VAR15*(CONST057*VAR12*VAR23**2 + CONST063*VAR17*VAR23 - CONST072*VAR19) + VAR16*(-CONST062*VAR12*VAR23 + CONST075*VAR17) +Y06 = CONST102*VAR01*VAR11 + VAR12*(CONST029*VAR13*VAR21 + CONST031*VAR11*VAR21**3 + CONST058*VAR09*VAR21**2 + CONST102*VAR20) + VAR17*(CONST046*VAR09*VAR21 + CONST058*VAR11*VAR21**2 + CONST096*VAR13) + VAR19*(CONST029*VAR11*VAR21 + CONST096*VAR09) +Y07 = CONST098*VAR01*VAR05 + VAR12*(CONST015*VAR18 + CONST067*VAR16*VAR23 - CONST070*VAR15*VAR23**2 + CONST098*VAR05*VAR23**3) + VAR17*(CONST051*VAR15*VAR23 + CONST067*VAR16 + CONST083*VAR05*VAR23**2) + VAR19*(CONST033*VAR15 + CONST083*VAR05*VAR23) +Y08 = CONST000*VAR03**4 + CONST000*VAR23**4 + CONST003*VAR21**4 - CONST070*VAR21**2*VAR23**2 + CONST080*VAR21**3*VAR23 + CONST087*VAR21*VAR23**3 + VAR03**3*(CONST004*VAR23 + CONST086*VAR21) + VAR03**2*(CONST006*VAR23**2 - CONST070*VAR21**2 + CONST071*VAR21*VAR23) + VAR03*(CONST004*VAR23**3 + CONST051*VAR21**2*VAR23 + CONST070*VAR21*VAR23**2 + CONST079*VAR21**3) +Y09 = CONST098*VAR05*VAR20 + VAR09*(CONST052*VAR03*VAR15 + CONST067*VAR16 + CONST083*VAR03**2*VAR05) + VAR11*(CONST017*VAR18 + CONST033*VAR03**2*VAR15 + CONST067*VAR03*VAR16 + CONST100*VAR03**3*VAR05) + VAR13*(CONST033*VAR15 + CONST083*VAR03*VAR05) +Y10 = CONST073*VAR03*VAR23**3 - CONST102*VAR03**3*VAR23 - CONST103*VAR03**4 + CONST103*VAR23**4 + VAR21**3*(CONST021*VAR23 + CONST081*VAR03) + VAR21**2*(-CONST068*VAR03**2 + CONST068*VAR23**2) + VAR21*(CONST020*VAR03*VAR23**2 + CONST020*VAR23**3 + CONST082*VAR03**3 + CONST082*VAR03**2*VAR23) +Y11 = VAR05*(CONST012*VAR20 + CONST076*VAR03**2*VAR09 + CONST084*VAR03**3*VAR11 + CONST101*VAR03*VAR13) + VAR15*(-CONST057*VAR03**2*VAR11 - CONST063*VAR03*VAR09 + CONST072*VAR13) + VAR16*(CONST062*VAR03*VAR11 - CONST075*VAR09) +Y12 = CONST007*VAR03**4 + CONST007*VAR23**4 + CONST030*VAR03**3*VAR23 + CONST053*VAR03*VAR23**3 + CONST091*VAR03**2*VAR23**2 + VAR21**2*(CONST025*VAR03**2 + CONST025*VAR23**2 + CONST032*VAR03*VAR23) + VAR21*(CONST048*VAR03**2*VAR23 + CONST048*VAR03*VAR23**2 + CONST085*VAR03**3 + CONST085*VAR23**3) +Y13 = VAR05*(CONST036*VAR03*VAR13 + CONST047*VAR20 - CONST077*VAR03**2*VAR09 + CONST078*VAR03**3*VAR11) + VAR15*(CONST014*VAR03*VAR09 + CONST019*VAR13 + CONST056*VAR03**2*VAR11) +Y14 = CONST008*VAR03**4 + CONST041*VAR23**4 + CONST088*VAR03**3*VAR23 - CONST088*VAR03*VAR23**3 + VAR21*(-CONST037*VAR03**2*VAR23 + CONST037*VAR03*VAR23**2 + CONST088*VAR03**3 - CONST088*VAR23**3) +Y15 = VAR05*(-CONST040*VAR03**2*VAR09 + CONST061*VAR03*VAR13 + CONST074*VAR03**3*VAR11 - CONST095*VAR20) +Y16 = CONST010*VAR03**4 + CONST010*VAR23**4 + CONST045*VAR03**2*VAR23**2 + CONST074*VAR03**3*VAR23 + CONST074*VAR03*VAR23**3 diff --git a/notebooks/fwd_implementations/fwd_9.py b/notebooks/fwd_implementations/fwd_9.py new file mode 100644 index 0000000..0d0fc4c --- /dev/null +++ b/notebooks/fwd_implementations/fwd_9.py @@ -0,0 +1,194 @@ +# -------------------- variable and constant definitions +CONST000 = 1.93163963757558 +CONST001 = 2.65478475211798 +CONST002 = 1.72771101506082 +CONST003 = 1.63671408859718 +CONST004 = 1.59908344719522 +CONST005 = 6.39633378878088 +CONST006 = 6.39633378878088 +CONST007 = 8.63855507530412 +CONST008 = 9.59450068317133 +CONST009 = 4.35889894354067 +CONST010 = 10.7269778688696 +CONST011 = 10.7269778688696 +CONST012 = 6.39633378878088 +CONST013 = 13.0937127087774 +CONST014 = 15.0007324039945 +CONST015 = 9.82028453158308 +CONST016 = 14.4550674370400 +CONST017 = 14.4550674370400 +CONST018 = 13.3827919767794 +CONST019 = 13.5214774630291 +CONST020 = 23.8930627690618 +CONST021 = 27.0429549260581 +CONST022 = 29.2403830344269 +CONST023 = 29.2403830344269 +CONST024 = 30.0014648079890 +CONST025 = -480.023436927823 +CONST026 = -480.023436927823 +CONST027 = 30.9062342012093 +CONST028 = 38.6327927515116 +CONST029 = 42.9079114754785 +CONST030 = -462.562157985281 +CONST031 = 54.0859098521163 +CONST032 = -967.518168434061 +CONST033 = 57.8202697481601 +CONST034 = 57.8202697481601 +CONST035 = 58.9217071894985 +CONST036 = 58.9217071894985 +CONST037 = 62.4530292249704 +CONST038 = 64.3618672132178 +CONST039 = 1081.71819704233 +CONST040 = 578.202697481601 +CONST041 = 68.5747767039748 +CONST042 = 589.217071894985 +CONST043 = 4.91014226579154 +CONST044 = 600.029296159779 +CONST045 = -936.795438374555 +CONST046 = 90.1063824390370 +CONST047 = 96.7518168434061 +CONST048 = 104.749701670220 +CONST049 = 115.640539496320 +CONST050 = 630.744677073259 +CONST051 = -392.811381263323 +CONST052 = 649.030918225395 +CONST053 = 137.149553407950 +CONST054 = 150.007324039945 +CONST055 = 150.007324039945 +CONST056 = -343.263291803828 +CONST057 = 176.765121568496 +CONST058 = 11.2632978048796 +CONST059 = 187.359087674911 +CONST060 = 196.405690631662 +CONST061 = -315.372338536630 +CONST062 = -314.249105010659 +CONST063 = 205.957975082297 +CONST064 = 216.343639408465 +CONST065 = -294.608535947493 +CONST066 = 240.011718463912 +CONST067 = 241.879542108515 +CONST068 = 241.879542108515 +CONST069 = 255.853351551235 +CONST070 = 255.853351551235 +CONST071 = -241.879542108515 +CONST072 = -240.011718463912 +CONST073 = -241.879542108515 +CONST074 = 788.430846341574 +CONST075 = 1.72771101506082 +CONST076 = -1.93163963757558 +CONST077 = -1249.06058449941 +CONST078 = -223.001919177910 +CONST079 = 294.608535947493 +CONST080 = -216.343639408465 +CONST081 = 300.014648079890 +CONST082 = -204.682681240988 +CONST083 = -204.682681240988 +CONST084 = -204.682681240988 +CONST085 = 314.249105010659 +CONST086 = -196.405690631662 +CONST087 = -191.890013663426 +CONST088 = -191.890013663427 +CONST089 = -187.359087674911 +CONST090 = -693.843236977922 +CONST091 = 334.502878766866 +CONST092 = -176.765121568496 +CONST093 = -150.007324039945 +CONST094 = -144.550674370400 +CONST095 = 374.718175349822 +CONST096 = 374.718175349822 +CONST097 = -649.030918225395 +CONST098 = 392.811381263323 +CONST099 = -630.744677073259 +CONST100 = -115.640539496320 +CONST101 = -114.421097267943 +CONST102 = -115.640539496320 +CONST103 = -104.749701670220 +CONST104 = 411.915950164594 +CONST105 = -95.5722510762473 +CONST106 = -90.1063824390370 +CONST107 = -90.0043944239669 +CONST108 = 936.795438374555 +CONST109 = -80.2967518606762 +CONST110 = -78.4601809837321 +CONST111 = 435.383175795327 +CONST112 = -589.217071894985 +CONST113 = -78.4601809837321 +CONST114 = 435.383175795328 +CONST115 = -68.5747767039748 +CONST116 = -63.9633378878088 +CONST117 = -63.9633378878088 +CONST118 = -62.4530292249704 +CONST119 = -58.9217071894985 +CONST120 = -1081.71819704233 +CONST121 = -57.8202697481601 +CONST122 = -57.8202697481601 +CONST123 = -58.9217071894985 +CONST124 = -54.0859098521163 +CONST125 = 462.562157985281 +CONST126 = 462.562157985281 +CONST127 = -48.3759084217031 +CONST128 = -48.3759084217030 +CONST129 = -38.6327927515116 +CONST130 = -30.9062342012093 +CONST131 = 483.759084217031 +CONST132 = -30.0014648079890 +CONST133 = -30.0014648079890 +CONST134 = -27.0429549260581 +CONST135 = -24.1879542108515 +CONST136 = -24.1879542108515 +CONST137 = -1.63671408859718 +CONST138 = -15.0007324039945 +CONST139 = -13.5214774630291 +CONST140 = -13.8216881204866 +CONST141 = -13.0937127087774 +CONST142 = -13.3827919767794 +CONST143 = -9.82028453158308 +CONST144 = -4.91014226579154 +CONST145 = 511.706703102471 +VAR00 = z**4 +VAR01 = x**7 +VAR02 = z**6 +VAR03 = x**9 +VAR04 = x**2 +VAR05 = z**8 +VAR06 = y +VAR07 = y**6 +VAR08 = x**4 +VAR09 = y**8 +VAR10 = z**3 +VAR11 = x**6 +VAR12 = z +VAR13 = x +VAR14 = z**5 +VAR15 = x**8 +VAR16 = y**3 +VAR17 = y**5 +VAR18 = x**3 +VAR19 = y**7 +VAR20 = x**5 +VAR21 = y**9 +VAR22 = z**7 +VAR23 = y**2 +VAR24 = z**9 +VAR25 = y**4 +VAR26 = z**2 +# -------------------- kernel implementations +Y00 = CONST001*VAR18**3 + CONST020*VAR13*VAR26**4 + CONST078*VAR18*VAR26**3 + CONST091*VAR20*VAR26**2 + CONST105*VAR01*VAR26 +Y01 = VAR06*(-CONST099*VAR10*VAR20 + CONST099*VAR14*VAR18 + CONST106*VAR01*VAR12 - CONST106*VAR13*VAR22) +Y02 = CONST000*VAR18**3 + VAR01*(CONST129*VAR26 + CONST130*VAR23) + VAR13*(-CONST080*VAR23*VAR26**3 + CONST139*VAR26**4) + VAR18*(CONST120*VAR23*VAR26**2 - CONST124*VAR26**3) + VAR20*(CONST021*VAR26**2 - CONST097*VAR23*VAR26) +Y03 = VAR06*(-CONST089*VAR10*VAR20 - CONST089*VAR14*VAR18 + CONST109*VAR01*VAR12 + CONST109*VAR13*VAR22) + VAR16*(CONST077*VAR10*VAR18 + CONST095*VAR12*VAR20 + CONST096*VAR13*VAR14) +Y04 = CONST002*VAR18**3 + CONST007*VAR13*VAR26**4 + CONST135*VAR20*VAR26**2 + CONST140*VAR01*VAR26 + VAR23**2*(CONST032*VAR18*VAR26 + CONST047*VAR20 + CONST131*VAR13*VAR26**2) + VAR23*(CONST071*VAR13*VAR26**3 - CONST071*VAR18*VAR26**2 + CONST111*VAR20*VAR26 + CONST127*VAR01) +Y05 = VAR06*(CONST034*VAR14*VAR18 + CONST121*VAR10*VAR20 - CONST121*VAR13*VAR22 + CONST122*VAR01*VAR12) + VAR16*(CONST030*VAR13*VAR14 + CONST125*VAR12*VAR20) + VAR17*(-CONST030*VAR10*VAR13 + CONST030*VAR12*VAR18) +Y06 = CONST119*VAR01*VAR23 - CONST137*VAR18**3 + VAR13*(-CONST062*VAR23**3*VAR26 - CONST092*VAR23*VAR26**3 + CONST112*VAR23**2*VAR26**2 + CONST144*VAR26**4) + VAR18*(CONST051*VAR23**2*VAR26 - CONST065*VAR23*VAR26**2 + CONST103*VAR23**3 + CONST141*VAR26**3) + VAR20*(CONST035*VAR23*VAR26 - CONST086*VAR23**2 + CONST143*VAR26**2) +Y07 = CONST132*VAR01*VAR06*VAR12 + VAR13*(CONST025*VAR10*VAR17 + CONST053*VAR12*VAR19 + CONST081*VAR14*VAR16 + CONST132*VAR06*VAR22) + VAR18*(CONST026*VAR12*VAR17 + CONST044*VAR10*VAR16 + CONST107*VAR06*VAR14) + VAR20*(CONST081*VAR12*VAR16 + CONST107*VAR06*VAR10) +Y08 = CONST004*VAR18**3 + VAR01*(CONST006*VAR26 + CONST116*VAR23) + VAR13*(CONST004*VAR26**4 + CONST022*VAR23**4 + CONST069*VAR23**2*VAR26**2 + CONST082*VAR23**3*VAR26 + CONST116*VAR23*VAR26**3) + VAR18*(CONST005*VAR26**3 + CONST083*VAR23**3 + CONST087*VAR23*VAR26**2 + CONST145*VAR23**2*VAR26) + VAR20*(CONST008*VAR26**2 + CONST069*VAR23**2 + CONST087*VAR23*VAR26) +Y09 = CONST009*VAR16**3 + VAR06*(CONST010*VAR26**4 + CONST011*VAR04**4 + CONST029*VAR04**3*VAR26 + CONST029*VAR04*VAR26**3 + CONST038*VAR04**2*VAR26**2) + VAR16*(CONST056*VAR04**2*VAR26 + CONST056*VAR04*VAR26**2 + CONST101*VAR04**3 + CONST101*VAR26**3) + VAR17*(CONST063*VAR04**2 + CONST063*VAR26**2 + CONST104*VAR04*VAR26) + VAR19*(CONST110*VAR26 + CONST113*VAR04) +Y10 = CONST004*VAR10**3 + VAR10*(CONST012*VAR04**3 + CONST082*VAR23**3 + CONST087*VAR04**2*VAR23 + CONST145*VAR04*VAR23**2) + VAR12*(CONST004*VAR04**4 + CONST023*VAR23**4 + CONST070*VAR04**2*VAR23**2 + CONST084*VAR04*VAR23**3 + CONST117*VAR04**3*VAR23) + VAR14*(CONST008*VAR04**2 + CONST070*VAR23**2 + CONST088*VAR04*VAR23) + VAR22*(CONST005*VAR04 + CONST117*VAR23) +Y11 = VAR06*(CONST014*VAR04**4 + CONST024*VAR04**3*VAR26 + CONST133*VAR04*VAR26**3 + CONST138*VAR26**4) + VAR16*(CONST055*VAR04*VAR26**2 + CONST093*VAR04**3 + CONST093*VAR04**2*VAR26 - CONST093*VAR26**3) + VAR17*(CONST066*VAR04**2 + CONST072*VAR26**2) + VAR19*(CONST115*VAR04 - CONST115*VAR26) +Y12 = CONST036*VAR22*VAR23 + CONST137*VAR10**3 + VAR10*(CONST013*VAR04**3 - CONST051*VAR04*VAR23**2 + CONST065*VAR04**2*VAR23 - CONST103*VAR23**3) + VAR12*(CONST062*VAR04*VAR23**3 + CONST092*VAR04**3*VAR23 - CONST112*VAR04**2*VAR23**2 - CONST144*VAR04**4) + VAR14*(CONST086*VAR23**2 + CONST123*VAR04*VAR23 - CONST143*VAR04**2) +Y13 = VAR06*(CONST016*VAR26**4 + CONST017*VAR04**4 + CONST094*VAR04**2*VAR26**2 + CONST121*VAR04**3*VAR26 + CONST122*VAR04*VAR26**3) + VAR16*(CONST040*VAR04**2*VAR26 + CONST040*VAR04*VAR26**2 + CONST100*VAR26**3 + CONST102*VAR04**3) + VAR17*(CONST049*VAR04**2 + CONST049*VAR26**2 + CONST090*VAR04*VAR26) +Y14 = CONST007*VAR04**4*VAR12 + CONST075*VAR10**3 + CONST136*VAR04**2*VAR14 + CONST140*VAR04*VAR22 + VAR23**2*(CONST032*VAR04*VAR10 + CONST047*VAR14 + CONST131*VAR04**2*VAR12) + VAR23*(CONST068*VAR04**2*VAR10 + CONST073*VAR04**3*VAR12 + CONST114*VAR04*VAR14 + CONST128*VAR22) +Y15 = VAR06*(CONST018*VAR04**4 + CONST089*VAR04**3*VAR26 - CONST089*VAR04*VAR26**3 + CONST142*VAR26**4) + VAR16*(CONST037*VAR26**3 - CONST045*VAR04**2*VAR26 + CONST045*VAR04*VAR26**2 + CONST118*VAR04**3) +Y16 = CONST019*VAR04**4*VAR12 + CONST076*VAR10**3 + CONST124*VAR04**3*VAR10 - CONST129*VAR04*VAR22 + CONST134*VAR04**2*VAR14 + VAR23*(CONST039*VAR04**2*VAR10 + CONST080*VAR04**3*VAR12 + CONST097*VAR04*VAR14 - CONST130*VAR22) +Y17 = VAR06*(CONST058*VAR04**4 + CONST058*VAR26**4 + CONST061*VAR04**3*VAR26 + CONST061*VAR04*VAR26**3 + CONST074*VAR04**2*VAR26**2) +Y18 = CONST001*VAR10**3 + CONST020*VAR04**4*VAR12 + CONST078*VAR04**3*VAR10 + CONST091*VAR04**2*VAR14 + CONST105*VAR04*VAR22 From b0b506fab30e7f405124987f484a4c28302a8c6f Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Thu, 22 Aug 2024 07:44:25 -0700 Subject: [PATCH 007/116] style: ran ruff on modules Signed-off-by: Kin Long Kelvin Lee --- .../direct/tests/test_direct_sph_harm.py | 2 +- src/equitriton/sph_harm/direct/y_2.py | 133 +++++++++++++----- 2 files changed, 102 insertions(+), 33 deletions(-) diff --git a/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py index c8accde..3509bb7 100644 --- a/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py +++ b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py @@ -55,4 +55,4 @@ def test_backward_equivalence(device, tensor_shape): triton_out = y_2.SecondOrderSphericalHarmonic.apply(coords) triton_out.backward(gradient=torch.ones_like(triton_out)) triton_grad = coords.grad.clone().detach() - assert torch.allclose(triton_grad, torch_grad, atol=1e-6, rtol=1e-4) \ No newline at end of file + assert torch.allclose(triton_grad, torch_grad, atol=1e-6, rtol=1e-4) diff --git a/src/equitriton/sph_harm/direct/y_2.py b/src/equitriton/sph_harm/direct/y_2.py index fd9adeb..6504fa4 100644 --- a/src/equitriton/sph_harm/direct/y_2.py +++ b/src/equitriton/sph_harm/direct/y_2.py @@ -7,7 +7,12 @@ class SecondOrderSphericalHarmonic(torch.autograd.Function): @staticmethod - def forward(ctx, coords: torch.Tensor, mask: torch.Tensor | None = None, block_size: int = 64): + def forward( + ctx, + coords: torch.Tensor, + mask: torch.Tensor | None = None, + block_size: int = 64, + ): output_tensor = torch.empty( (*coords.shape[:-1], 5), dtype=coords.dtype, device=coords.device ) @@ -15,17 +20,28 @@ def forward(ctx, coords: torch.Tensor, mask: torch.Tensor | None = None, block_s output_numel = output_tensor.numel() num_blocks = calculate_lastdim_num_blocks(coords, block_size) # apply the kernel - second_order_fwd[num_blocks,](coords, output_tensor, block_size, coord_numel, output_numel) + second_order_fwd[num_blocks,]( + coords, output_tensor, block_size, coord_numel, output_numel + ) ctx.save_for_backward(coords) return output_tensor - + @staticmethod - def backward(ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64) -> torch.Tensor: + def backward( + ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 + ) -> torch.Tensor: (coords,) = ctx.saved_tensors coord_grad_output = torch.zeros_like(coords) num_blocks = calculate_lastdim_num_blocks(coords, block_size) # call backward kernel - second_order_bwd[num_blocks,](coords, coord_grad_output, sph_grad_tensor, block_size, coords.numel(), sph_grad_tensor.numel()) + second_order_bwd[num_blocks,]( + coords, + coord_grad_output, + sph_grad_tensor, + block_size, + coords.numel(), + sph_grad_tensor.numel(), + ) return coord_grad_output @@ -47,20 +63,20 @@ def torch_second_order_fwd(coords: torch.Tensor) -> torch.Tensor: N-d tensor, where the last dimension corresponds to each projection of the second order spherical harmonic. """ - x = coords[...,0].contiguous().unsqueeze(-1) - y = coords[...,1].contiguous().unsqueeze(-1) - z = coords[...,2].contiguous().unsqueeze(-1) + x = coords[..., 0].contiguous().unsqueeze(-1) + y = coords[..., 1].contiguous().unsqueeze(-1) + z = coords[..., 2].contiguous().unsqueeze(-1) CONST_00 = 3.87298334620742 CONST_01 = 2.23606797749979 CONST_02 = -1.11803398874989 CONST_03 = 1.93649167310371 Y20 = CONST_00 * x * z Y21 = CONST_00 * x * y - Y23 = CONST_00 * y * z # looks jarring but just helping the compiler ;) + Y23 = CONST_00 * y * z # looks jarring but just helping the compiler ;) Y22 = CONST_02 * x * x + CONST_01 * y * y + CONST_02 * z * z Y24 = -CONST_03 * x * x + CONST_03 * z * z return torch.cat([Y20, Y21, Y22, Y23, Y24], dim=-1) - + @triton.jit def second_order_fwd( @@ -78,25 +94,45 @@ def second_order_fwd( # as the name suggests, this is effectively every node/atom coord_row_offset = coord_striding + (block_size * coord_stride * block_id) x = tl.load(coord_ptr + coord_row_offset, mask=coord_row_offset < coord_numel) - y = tl.load(coord_ptr + coord_row_offset + 1, mask = coord_row_offset + 1 < coord_numel) - z = tl.load(coord_ptr + coord_row_offset + 2, mask = coord_row_offset + 2 < coord_numel) + y = tl.load( + coord_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + z = tl.load( + coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) CONST_00 = 3.87298334620742 CONST_01 = 2.23606797749979 CONST_02 = -1.11803398874989 CONST_03 = 1.93649167310371 Y20 = CONST_00 * x * z Y21 = CONST_00 * x * y - Y23 = CONST_00 * y * z # looks jarring but just helping the compiler ;) + Y23 = CONST_00 * y * z # looks jarring but just helping the compiler ;) Y22 = CONST_02 * x * x + CONST_01 * y * y + CONST_02 * z * z Y24 = -CONST_03 * x * x + CONST_03 * z * z - output_stride = 5 # [2l + 1] + output_stride = 5 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride output_row_offset = output_striding + (block_size * output_stride * block_id) tl.store(output_ptr + output_row_offset, Y20, mask=output_row_offset < output_numel) - tl.store(output_ptr + output_row_offset + 1, Y21, mask=output_row_offset + 1 < output_numel) - tl.store(output_ptr + output_row_offset + 2, Y22, mask=output_row_offset + 2 < output_numel) - tl.store(output_ptr + output_row_offset + 3, Y23, mask=output_row_offset + 3 < output_numel) - tl.store(output_ptr + output_row_offset + 4, Y24, mask=output_row_offset + 4 < output_numel) + tl.store( + output_ptr + output_row_offset + 1, + Y21, + mask=output_row_offset + 1 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 2, + Y22, + mask=output_row_offset + 2 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 3, + Y23, + mask=output_row_offset + 3 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 4, + Y24, + mask=output_row_offset + 4 < output_numel, + ) @triton.jit @@ -116,25 +152,58 @@ def second_order_bwd( # as the name suggests, this is effectively every node/atom coord_row_offset = coord_striding + (block_size * coord_stride * block_id) x = tl.load(coord_ptr + coord_row_offset, mask=coord_row_offset < coord_numel) - y = tl.load(coord_ptr + coord_row_offset + 1, mask = coord_row_offset + 1 < coord_numel) - z = tl.load(coord_ptr + coord_row_offset + 2, mask = coord_row_offset + 2 < coord_numel) - output_stride = 5 # [2l + 1] + y = tl.load( + coord_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + z = tl.load( + coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + output_stride = 5 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride output_row_offset = output_striding + (block_size * output_stride * block_id) CONST_00 = 3.87298334620742 CONST_01 = 2.23606797749979 CONST_02 = 4.47213595499958 # load in gradients w.r.t. spherical harmonic projections - g_Y20 = tl.load(sph_grad_ptr + output_row_offset, mask=output_row_offset < output_numel) - g_Y21 = tl.load(sph_grad_ptr + output_row_offset + 1, mask=output_row_offset + 1 < output_numel) - g_Y22 = tl.load(sph_grad_ptr + output_row_offset + 2, mask=output_row_offset + 2 < output_numel) - g_Y23 = tl.load(sph_grad_ptr + output_row_offset + 3, mask=output_row_offset + 3 < output_numel) - g_Y24 = tl.load(sph_grad_ptr + output_row_offset + 4, mask=output_row_offset + 4 < output_numel) - g_x = CONST_00 * g_Y20 * z + CONST_00 * g_Y21 * y - CONST_01 * g_Y22 * x - CONST_00 * g_Y24 * x + g_Y20 = tl.load( + sph_grad_ptr + output_row_offset, mask=output_row_offset < output_numel + ) + g_Y21 = tl.load( + sph_grad_ptr + output_row_offset + 1, mask=output_row_offset + 1 < output_numel + ) + g_Y22 = tl.load( + sph_grad_ptr + output_row_offset + 2, mask=output_row_offset + 2 < output_numel + ) + g_Y23 = tl.load( + sph_grad_ptr + output_row_offset + 3, mask=output_row_offset + 3 < output_numel + ) + g_Y24 = tl.load( + sph_grad_ptr + output_row_offset + 4, mask=output_row_offset + 4 < output_numel + ) + g_x = ( + CONST_00 * g_Y20 * z + + CONST_00 * g_Y21 * y + - CONST_01 * g_Y22 * x + - CONST_00 * g_Y24 * x + ) g_y = CONST_00 * g_Y21 * x + CONST_02 * g_Y22 * y + CONST_00 * g_Y23 * z - g_z = CONST_00 * g_Y20 * x - CONST_01 * g_Y22 * z + CONST_00 * g_Y23 * y + CONST_00 * g_Y24 * z + g_z = ( + CONST_00 * g_Y20 * x + - CONST_01 * g_Y22 * z + + CONST_00 * g_Y23 * y + + CONST_00 * g_Y24 * z + ) # write out gradients - tl.store(coord_grad_ptr + coord_row_offset, g_x, mask=coord_row_offset < coord_numel) - tl.store(coord_grad_ptr + coord_row_offset + 1, g_y, mask=coord_row_offset + 1 < coord_numel) - tl.store(coord_grad_ptr + coord_row_offset + 2, g_z, mask=coord_row_offset + 2< coord_numel) - \ No newline at end of file + tl.store( + coord_grad_ptr + coord_row_offset, g_x, mask=coord_row_offset < coord_numel + ) + tl.store( + coord_grad_ptr + coord_row_offset + 1, + g_y, + mask=coord_row_offset + 1 < coord_numel, + ) + tl.store( + coord_grad_ptr + coord_row_offset + 2, + g_z, + mask=coord_row_offset + 2 < coord_numel, + ) From 2b3e5b90e01067d57101b3c554eea83db7a36148 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Thu, 22 Aug 2024 09:42:19 -0700 Subject: [PATCH 008/116] notebook: updated implementations to substitute powers --- ...ct evaluation of spherical harmonics.ipynb | 139 +++- notebooks/bwd_implementations/bwd_10.py | 665 +++++++++--------- notebooks/bwd_implementations/bwd_2.py | 41 +- notebooks/bwd_implementations/bwd_3.py | 44 +- notebooks/bwd_implementations/bwd_4.py | 81 ++- notebooks/bwd_implementations/bwd_5.py | 106 +-- notebooks/bwd_implementations/bwd_6.py | 171 +++-- notebooks/bwd_implementations/bwd_7.py | 84 +-- notebooks/bwd_implementations/bwd_8.py | 273 ++++--- notebooks/bwd_implementations/bwd_9.py | 206 +++--- notebooks/direct_sph_harm/l_10.json | 30 + notebooks/direct_sph_harm/l_2.json | 14 + notebooks/direct_sph_harm/l_3.json | 16 + notebooks/direct_sph_harm/l_4.json | 18 + notebooks/direct_sph_harm/l_5.json | 20 + notebooks/direct_sph_harm/l_6.json | 22 + notebooks/direct_sph_harm/l_7.json | 24 + notebooks/direct_sph_harm/l_8.json | 26 + notebooks/direct_sph_harm/l_9.json | 28 + notebooks/fwd_implementations/fwd_10.py | 225 +++--- notebooks/fwd_implementations/fwd_2.py | 43 +- notebooks/fwd_implementations/fwd_3.py | 50 +- notebooks/fwd_implementations/fwd_4.py | 61 +- notebooks/fwd_implementations/fwd_5.py | 80 ++- notebooks/fwd_implementations/fwd_6.py | 93 +-- notebooks/fwd_implementations/fwd_7.py | 82 ++- notebooks/fwd_implementations/fwd_8.py | 89 +-- notebooks/fwd_implementations/fwd_9.py | 96 +-- 28 files changed, 1579 insertions(+), 1248 deletions(-) create mode 100644 notebooks/direct_sph_harm/l_10.json create mode 100644 notebooks/direct_sph_harm/l_2.json create mode 100644 notebooks/direct_sph_harm/l_3.json create mode 100644 notebooks/direct_sph_harm/l_4.json create mode 100644 notebooks/direct_sph_harm/l_5.json create mode 100644 notebooks/direct_sph_harm/l_6.json create mode 100644 notebooks/direct_sph_harm/l_7.json create mode 100644 notebooks/direct_sph_harm/l_8.json create mode 100644 notebooks/direct_sph_harm/l_9.json diff --git a/notebooks/Direct evaluation of spherical harmonics.ipynb b/notebooks/Direct evaluation of spherical harmonics.ipynb index 630083c..68e7a3a 100644 --- a/notebooks/Direct evaluation of spherical harmonics.ipynb +++ b/notebooks/Direct evaluation of spherical harmonics.ipynb @@ -1228,7 +1228,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 89, "id": "c72e6e01-92c6-4d08-8c70-eeb2708bc8d9", "metadata": {}, "outputs": [], @@ -1242,7 +1242,20 @@ " for arg in expr.args:\n", " collect_symbols(arg, agg_set)\n", " else:\n", - " agg_set.add(expr)" + " agg_set.add(expr)\n", + "\n", + "\n", + "def ordered_power_replacement(expr):\n", + " x, y, z = symbols(\"x y z\")\n", + " mapping = {}\n", + " char_counter = 0\n", + " for axis in [x, y, z]:\n", + " for exponent in range(10, 1, -1):\n", + " varname = f\"VAR{char_counter:02}\"\n", + " expr = expr.subs({axis**exponent: varname})\n", + " mapping[axis**exponent] = varname\n", + " char_counter += 1\n", + " return expr, mapping" ] }, { @@ -1255,12 +1268,12 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 93, "id": "3d837ed4-bb65-4843-9f20-7f19978b0586", "metadata": {}, "outputs": [], "source": [ - "def generate_fwd_implementation(exprs, output_file: Path | None):\n", + "def generate_fwd_implementation(exprs, output_file: Path | None = None):\n", " \"\"\"\n", " This function takes a set of expressions for a particular forward\n", " pass, determines the set of variables and constants used by each\n", @@ -1272,29 +1285,29 @@ " collect_symbols(expr, variable_set)\n", " mapping = {}\n", " const_counter = 0\n", - " char_counter = 0\n", " for sym in variable_set:\n", " if isinstance(sym, (sympy.Float, sympy.Integer)):\n", " varname = f\"CONST{const_counter:03}\"\n", " const_counter += 1\n", - " else:\n", - " # note that orignially I used the alphabet for this table;\n", - " # turns out sympy doesn't like uppercase O as a variable\n", - " # to substitute, so we're using this counter now as well\n", - " varname = f\"VAR{char_counter:02}\"\n", - " char_counter += 1\n", - " mapping[sym] = varname\n", + " mapping[sym] = varname\n", " # sort the dict to make the output cleaner\n", + " variable_sec = \"\"\n", + " kernel_sec = \"\"\n", " mapping = dict(sorted(mapping.items(), key=lambda x: x[1]))\n", - " fmt_string = \"# -------------------- variable and constant definitions\\n\"\n", - " for sym, char in mapping.items():\n", - " fmt_string += f\"{char} = {sym}\\n\"\n", " # now generate the actual kernels\n", - " fmt_string += \"# -------------------- kernel implementations\\n\"\n", " term_counter = 0\n", " for index, kernel in enumerate(exprs):\n", + " # this ensures that higher order powers are replaced\n", + " kernel, new_mapping = ordered_power_replacement(kernel)\n", + " mapping.update(new_mapping)\n", " kernel_str = str(kernel.subs(mapping))\n", - " fmt_string += f\"Y{index:02} = {kernel_str}\\n\"\n", + " kernel_sec += f\"Y{index:02} = {kernel_str}\\n\"\n", + " for sym, char in mapping.items():\n", + " variable_sec += f\"{char} = {sym}\\n\"\n", + " fmt_string = \"# -------------------- variable and constant definitions\\n\"\n", + " fmt_string += variable_sec\n", + " fmt_string += \"# -------------------- kernel implementations\\n\"\n", + " fmt_string += kernel_sec\n", " if output_file:\n", " with open(output_file, \"w+\") as write_file:\n", " write_file.write(fmt_string)" @@ -1302,7 +1315,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 94, "id": "32600151-71ec-4738-b691-17862c4d3a94", "metadata": {}, "outputs": [], @@ -1312,7 +1325,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 95, "id": "6685cbaf-0098-41e2-ace7-9d8d92ded1de", "metadata": { "scrolled": true @@ -1337,12 +1350,12 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 96, "id": "22dee377-3e18-44bb-ad7c-621cdb27ded2", "metadata": {}, "outputs": [], "source": [ - "def generate_bwd_implementation(exprs: dict, output_file: Path | None):\n", + "def generate_bwd_implementation(exprs: dict, output_file: Path | None = None):\n", " \"\"\"\n", " This function takes a set of expressions for a particular backward\n", " pass, determines the set of variables and constants used by each\n", @@ -1350,34 +1363,33 @@ " implementation for the collection of kernels.\n", " \"\"\"\n", " variable_set = set()\n", + " mapping = {}\n", " # we expect a dictionary, where the key corresponds to the cart axis\n", " for expr in exprs.values():\n", " collect_symbols(expr, variable_set)\n", - " mapping = {}\n", " const_counter = 0\n", - " char_counter = 0\n", " for sym in variable_set:\n", " if isinstance(sym, (sympy.Float, sympy.Integer)):\n", " varname = f\"CONST{const_counter:03}\"\n", " const_counter += 1\n", - " else:\n", - " # note that orignially I used the alphabet for this table;\n", - " # turns out sympy doesn't like uppercase O as a variable\n", - " # to substitute, so we're using this counter now as well\n", - " varname = f\"VAR{char_counter:02}\"\n", - " char_counter += 1\n", - " mapping[sym] = varname\n", + " mapping[sym] = varname\n", + " variable_sec = \"\"\n", + " kernel_sec = \"\"\n", + " for axis, grad_kernel in exprs.items():\n", + " # this ensures we replace exponents\n", + " grad_kernel, new_mapping = ordered_power_replacement(grad_kernel)\n", + " mapping.update(new_mapping)\n", + " # now replace constants\n", + " kernel_str = str(grad_kernel.subs(mapping))\n", + " kernel_sec += f\"g_{axis} = {kernel_str}\\n\"\n", " # sort the dict to make the output cleaner\n", " mapping = dict(sorted(mapping.items(), key=lambda x: x[1]))\n", - " fmt_string = \"# -------------------- variable and constant definitions\\n\"\n", " for sym, char in mapping.items():\n", - " fmt_string += f\"{char} = {sym}\\n\"\n", - " # now generate the actual kernels\n", + " variable_sec += f\"{char} = {sym}\\n\"\n", + " fmt_string = \"# -------------------- variable and constant definitions\\n\"\n", + " fmt_string += variable_sec\n", " fmt_string += \"# -------------------- kernel implementations\\n\"\n", - " term_counter = 0\n", - " for axis, grad_kernel in exprs.items():\n", - " kernel_str = str(grad_kernel.subs(mapping))\n", - " fmt_string += f\"g_{axis} = {kernel_str}\\n\"\n", + " fmt_string += kernel_sec\n", " if output_file:\n", " with open(output_file, \"w+\") as write_file:\n", " write_file.write(fmt_string)" @@ -1385,7 +1397,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 97, "id": "53e9e681-72df-4f1d-aa38-21620816d372", "metadata": {}, "outputs": [], @@ -1402,9 +1414,58 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 86, "id": "13130ed1-0c5c-422c-a1a9-f1fa07ee4b20", "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "x \n", + " g_0*(11.6340690431164*VAR06 - 69.8044142586986*VAR08*VAR26 + 11.6340690431164*VAR24) + g_1*y*(-88.2963759165686*VAR08*z + 29.4321253055229*VAR25) + g_10*(46.5362761724657*VAR07*z - 46.5362761724657*VAR25*x) + g_2*(8.67152307844476*VAR06 + 3.0*VAR08*(-13.8744369255116*VAR17 - 3.4686092313779*VAR26) + 41.6233107765348*VAR17*VAR26 - 5.20291384706685*VAR24) + g_3*(-50.9779364038993*VAR08*y*z + 33.9852909359329*VAR16*z - 16.9926454679664*VAR25*y) + g_4*(8.02827036166571*VAR06 + 3.0*VAR08*(-19.2678488679977*VAR17 + 3.21130814466628*VAR26) + 12.8452325786651*VAR15 - 19.2678488679977*VAR17*VAR26 + 1.60565407233314*VAR24) + g_5*(-33.166247903554*VAR16*x + y*(24.8746859276655*VAR07 + 24.8746859276655*VAR26*x)) + g_6*(6.42261628933256*VAR25*x + z*(6.42261628933256*VAR07 - 38.5356977359954*VAR17*x)) + g_7*(33.9852909359329*VAR07*y - 33.9852909359329*VAR16*x) + g_8*(6.9372184627558*VAR25*x + z*(20.8116553882674*VAR07 - 83.2466215530696*VAR17*x)) + g_9*y*(29.4321253055229*VAR07 - 88.2963759165686*VAR26*x) \n", + " g_0*(CONST009*VAR06 + CONST009*VAR24 + CONST040*VAR08*VAR26) + g_1*y*(CONST038*VAR08*z - CONST052*VAR25) + g_10*(CONST029*VAR07*z + CONST043*VAR25*x) + g_2*(CONST001*VAR08*(CONST059*VAR17 + CONST064*VAR26) + CONST006*VAR06 - CONST045*VAR17*VAR26 + CONST063*VAR24) + g_3*(CONST041*VAR08*y*z - CONST049*VAR16*z + CONST057*VAR25*y) + g_4*(CONST000*VAR24 + CONST001*VAR08*(CONST002*VAR26 + CONST055*VAR17) + CONST007*VAR06 + CONST010*VAR15 + CONST056*VAR17*VAR26) + g_5*(CONST048*VAR16*x + y*(CONST019*VAR07 + CONST019*VAR26*x)) + g_6*(CONST005*VAR25*x + z*(CONST004*VAR07 + CONST046*VAR17*x)) + g_7*(CONST049*VAR16*x - CONST051*VAR07*y) + g_8*(CONST008*VAR25*x + z*(CONST039*VAR17*x - CONST054*VAR07)) + g_9*y*(CONST024*VAR07 + CONST038*VAR26*x)\n", + "y \n", + " g_1*(-29.4321253055229*VAR07*z + 29.4321253055229*VAR25*x) + g_2*(-27.7488738510232*VAR07*y + 83.2466215530696*VAR26*x*y) + g_3*(-16.9926454679664*VAR07*z + x*(101.955872807799*VAR17*z - 16.9926454679664*VAR25)) + g_4*(-38.5356977359954*VAR07*y + x*(51.3809303146605*VAR16 - 38.5356977359954*VAR26*y)) + g_5*(6.21867148191637*VAR06 + 12.4373429638327*VAR08*VAR26 + 16.583123951777*VAR15 + 3.0*VAR17*(-16.583123951777*VAR08 - 16.583123951777*VAR26) + 6.21867148191637*VAR24) + g_6*(-38.5356977359954*VAR25*y + z*(-38.5356977359954*VAR08*y + 51.3809303146605*VAR16)) + g_7*(8.49632273398321*VAR06 + 3.0*VAR17*(-16.9926454679664*VAR08 + 16.9926454679664*VAR26) - 8.49632273398321*VAR24) + g_8*(-83.2466215530696*VAR08*y*z + 27.7488738510232*VAR25*y) + g_9*(7.35803132638072*VAR06 - 44.1481879582843*VAR08*VAR26 + 7.35803132638072*VAR24) \n", + " g_1*(CONST052*VAR07*z - CONST052*VAR25*x) + g_2*(-CONST039*VAR26*x*y + CONST053*VAR07*y) + g_3*(CONST058*VAR07*z + x*(CONST034*VAR17*z + CONST057*VAR25)) + g_4*(CONST047*VAR07*y + x*(CONST030*VAR16 + CONST046*VAR26*y)) + g_5*(CONST001*VAR17*(CONST060*VAR08 + CONST060*VAR26) + CONST011*VAR06 + CONST012*VAR24 + CONST014*VAR08*VAR26 - CONST060*VAR15) + g_6*(CONST046*VAR25*y + z*(CONST031*VAR16 + CONST046*VAR08*y)) + g_7*(CONST001*VAR17*(CONST057*VAR08 - CONST057*VAR26) - CONST061*VAR06 + CONST061*VAR24) + g_8*(CONST021*VAR25*y + CONST039*VAR08*y*z) + g_9*(CONST027*VAR06 + CONST027*VAR24 + CONST044*VAR08*VAR26)\n", + "z \n", + " g_0*(-46.5362761724657*VAR07*z + 46.5362761724657*VAR25*x) + g_1*y*(-29.4321253055229*VAR07 + 88.2963759165686*VAR26*x) + g_10*(11.6340690431164*VAR06 - 69.8044142586986*VAR08*VAR26 + 11.6340690431164*VAR24) + g_2*(-6.9372184627558*VAR07*z + x*(83.2466215530696*VAR17*z - 20.8116553882674*VAR25)) + g_3*(-16.9926454679664*VAR07*y + x*(33.9852909359329*VAR16 - 50.9779364038993*VAR26*y)) + g_4*(6.42261628933256*VAR07*z + x*(-38.5356977359954*VAR17*z + 6.42261628933257*VAR25)) + g_5*(-33.166247903554*VAR16*z + y*(24.8746859276655*VAR08*z + 24.8746859276655*VAR25)) + g_6*(1.60565407233314*VAR06 - 19.2678488679977*VAR08*VAR17 + 12.8452325786651*VAR15 + 8.02827036166571*VAR24 + 3.0*VAR26*(3.21130814466628*VAR08 - 19.2678488679977*VAR17)) + g_7*(33.9852909359329*VAR16*z - 33.9852909359329*VAR25*y) + g_8*(5.20291384706685*VAR06 - 41.6233107765348*VAR08*VAR17 - 8.67152307844475*VAR24 + 3.0*VAR26*(3.4686092313779*VAR08 + 13.8744369255116*VAR17)) + g_9*y*(-88.2963759165686*VAR08*z + 29.4321253055229*VAR25) \n", + " g_0*(CONST029*VAR25*x + CONST043*VAR07*z) + g_1*y*(-CONST038*VAR26*x + CONST052*VAR07) + g_10*(CONST009*VAR06 + CONST009*VAR24 + CONST040*VAR08*VAR26) + g_2*(CONST062*VAR07*z + x*(-CONST039*VAR17*z + CONST054*VAR25)) + g_3*(CONST058*VAR07*y + x*(CONST042*VAR26*y - CONST049*VAR16)) + g_4*(CONST005*VAR07*z + x*(CONST046*VAR17*z + CONST050*VAR25)) + g_5*(CONST048*VAR16*z + y*(CONST019*VAR08*z + CONST020*VAR25)) + g_6*(CONST001*VAR26*(CONST002*VAR08 + CONST056*VAR17) + CONST003*VAR06 + CONST007*VAR24 + CONST017*VAR15 + CONST056*VAR08*VAR17) + g_7*(-CONST049*VAR16*z + CONST051*VAR25*y) + g_8*(CONST001*VAR26*(CONST018*VAR17 + CONST037*VAR08) + CONST036*VAR24 + CONST045*VAR08*VAR17 - CONST063*VAR06) + g_9*y*(CONST024*VAR25 + CONST038*VAR08*z)\n", + "{1.60565407233314: 'CONST000', 3.00000000000000: 'CONST001', 3.21130814466628: 'CONST002', 1.60565407233314: 'CONST003', 6.42261628933256: 'CONST004', 6.42261628933256: 'CONST005', 8.67152307844476: 'CONST006', 8.02827036166571: 'CONST007', 6.93721846275580: 'CONST008', 11.6340690431164: 'CONST009', 12.8452325786651: 'CONST010', 6.21867148191637: 'CONST011', 6.21867148191637: 'CONST012', 16.5831239517770: 'CONST013', 12.4373429638327: 'CONST014', 16.9926454679664: 'CONST015', 20.8116553882674: 'CONST016', 12.8452325786651: 'CONST017', 13.8744369255116: 'CONST018', 24.8746859276655: 'CONST019', 24.8746859276655: 'CONST020', 27.7488738510232: 'CONST021', 5.20291384706685: 'CONST022', 29.4321253055229: 'CONST023', 29.4321253055229: 'CONST024', 33.9852909359329: 'CONST025', 33.9852909359329: 'CONST026', 7.35803132638072: 'CONST027', 41.6233107765348: 'CONST028', 46.5362761724657: 'CONST029', 51.3809303146605: 'CONST030', 51.3809303146605: 'CONST031', 83.2466215530696: 'CONST032', 88.2963759165686: 'CONST033', 101.955872807799: 'CONST034', 8.49632273398321: 'CONST035', -8.67152307844475: 'CONST036', 3.46860923137790: 'CONST037', -88.2963759165686: 'CONST038', -83.2466215530696: 'CONST039', -69.8044142586986: 'CONST040', -50.9779364038993: 'CONST041', -50.9779364038993: 'CONST042', -46.5362761724657: 'CONST043', -44.1481879582843: 'CONST044', -41.6233107765348: 'CONST045', -38.5356977359954: 'CONST046', -38.5356977359954: 'CONST047', -33.1662479035540: 'CONST048', -33.9852909359329: 'CONST049', 6.42261628933257: 'CONST050', -33.9852909359329: 'CONST051', -29.4321253055229: 'CONST052', -27.7488738510232: 'CONST053', -20.8116553882674: 'CONST054', -19.2678488679977: 'CONST055', -19.2678488679977: 'CONST056', -16.9926454679664: 'CONST057', -16.9926454679664: 'CONST058', -13.8744369255116: 'CONST059', -16.5831239517770: 'CONST060', -8.49632273398321: 'CONST061', -6.93721846275580: 'CONST062', -5.20291384706685: 'CONST063', -3.46860923137790: 'CONST064', x**10: 'VAR00', x**9: 'VAR01', x**8: 'VAR02', x**7: 'VAR03', x**6: 'VAR04', x**5: 'VAR05', x**4: 'VAR06', x**3: 'VAR07', x**2: 'VAR08', y**10: 'VAR09', y**9: 'VAR10', y**8: 'VAR11', y**7: 'VAR12', y**6: 'VAR13', y**5: 'VAR14', y**4: 'VAR15', y**3: 'VAR16', y**2: 'VAR17', z**10: 'VAR18', z**9: 'VAR19', z**8: 'VAR20', z**7: 'VAR21', z**6: 'VAR22', z**5: 'VAR23', z**4: 'VAR24', z**3: 'VAR25', z**2: 'VAR26'}\n" + ] + } + ], + "source": [ + "generate_bwd_implementation(fifth_order_expressions[\"bwd\"], None)" + ] + }, + { + "cell_type": "code", + "execution_count": 99, + "id": "78cd38de-bf35-40ce-8410-57a94e63141a", + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'Pow' object has no attribute 'evaluate'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[99], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43m(\u001b[49m\u001b[43mz\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m6\u001b[39;49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mevaluate\u001b[49m(full\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m)\n", + "\u001b[0;31mAttributeError\u001b[0m: 'Pow' object has no attribute 'evaluate'" + ] + } + ], + "source": [ + "(z**6).evaluate(f)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "379cf400-8e4e-48f2-8e3a-0c02dbbd3897", + "metadata": {}, "outputs": [], "source": [] } diff --git a/notebooks/bwd_implementations/bwd_10.py b/notebooks/bwd_implementations/bwd_10.py index d5bef89..338aeff 100644 --- a/notebooks/bwd_implementations/bwd_10.py +++ b/notebooks/bwd_implementations/bwd_10.py @@ -18,328 +18,328 @@ CONST016 = -2030.35546709287 CONST017 = 19.3148322317494 CONST018 = -6131.53904851919 -CONST019 = 15.7302121789667 -CONST020 = 22.8629854262320 -CONST021 = 23.2135393295190 -CONST022 = 24.6216766128653 -CONST023 = 16.4144510752435 -CONST024 = 17.5869118663323 -CONST025 = 27.2034486491732 -CONST026 = 28.9722483476241 -CONST027 = 33.9852909359329 -CONST028 = 33.9852909359329 -CONST029 = 35.5238206489124 -CONST030 = 6180.74631415980 +CONST019 = 22.8629854262320 +CONST020 = 23.2135393295190 +CONST021 = 24.6216766128653 +CONST022 = 17.5869118663323 +CONST023 = 27.2034486491732 +CONST024 = 28.9722483476241 +CONST025 = 33.9852909359329 +CONST026 = 33.9852909359329 +CONST027 = 35.5238206489124 +CONST028 = 6180.74631415980 +CONST029 = 38.6296644634988 +CONST030 = 39.7946388506040 CONST031 = 38.6296644634988 -CONST032 = 39.7946388506040 +CONST032 = -2007.25624590353 CONST033 = -2007.25624590353 -CONST034 = -2007.25624590353 -CONST035 = 38.6296644634988 -CONST036 = 45.8257569495584 -CONST037 = 45.7259708524640 -CONST038 = 49.2433532257305 -CONST039 = 56.3871618715269 -CONST040 = 56.2781179722634 -CONST041 = -1989.33395633909 -CONST042 = -1989.33395633909 -CONST043 = 59.6919582759060 -CONST044 = 66.9085415301178 -CONST045 = 69.6406179885570 -CONST046 = -8121.42186837148 -CONST047 = 77.2593289269976 -CONST048 = 78.6510608948335 +CONST034 = 45.8257569495584 +CONST035 = 45.7259708524640 +CONST036 = 49.2433532257305 +CONST037 = 56.3871618715269 +CONST038 = 56.2781179722634 +CONST039 = -1989.33395633909 +CONST040 = -1989.33395633909 +CONST041 = 59.6919582759060 +CONST042 = 66.9085415301178 +CONST043 = 69.6406179885570 +CONST044 = -8121.42186837148 +CONST045 = 77.2593289269976 +CONST046 = 78.6510608948335 +CONST047 = -1969.73412902922 +CONST048 = 77.3468749368712 CONST049 = -1969.73412902922 -CONST050 = 77.3468749368712 -CONST051 = -1969.73412902922 -CONST052 = -9.65741611587469 -CONST053 = 90.1358837481638 +CONST050 = -9.65741611587469 +CONST051 = 90.1358837481638 +CONST052 = 2141.07332896377 +CONST053 = 94.9693240781945 CONST054 = 92.8541573180760 -CONST055 = 2141.07332896377 -CONST056 = 94.9693240781945 -CONST057 = 96.5741611587469 -CONST058 = 98.4867064514610 -CONST059 = 98.4867064514610 -CONST060 = 100.362812295177 -CONST061 = 101.517773354644 -CONST062 = 106.571461946737 -CONST063 = 106.571461946737 -CONST064 = 109.491768723557 -CONST065 = 109.491768723557 -CONST066 = 112.774323743054 -CONST067 = 112.774323743054 -CONST068 = 112.556235944527 -CONST069 = 2165.26701586663 -CONST070 = 130.522851455970 -CONST071 = 131.315608601948 -CONST072 = 133.817083060236 -CONST073 = 139.281235977114 -CONST074 = 139.281235977114 -CONST075 = 141.571909610700 -CONST076 = 142.095282595650 -CONST077 = 147.730059677192 -CONST078 = 150.544218442765 -CONST079 = 150.074981259369 -CONST080 = 154.518657853995 -CONST081 = 2202.22970505534 -CONST082 = -3939.46825805844 -CONST083 = -5968.00186901728 -CONST084 = 176.592751833137 -CONST085 = 176.178376404427 -CONST086 = 2228.49977563382 -CONST087 = 185.708314636152 -CONST088 = 196.973412902922 -CONST089 = 196.973412902922 -CONST090 = 203.035546709287 -CONST091 = 225.548647486108 -CONST092 = 225.548647486108 -CONST093 = 4330.53403173327 -CONST094 = 2285.08968653055 -CONST095 = 244.831037842559 -CONST096 = -1804.38917988886 -CONST097 = -1804.38917988886 -CONST098 = 244.831037842559 -CONST099 = 2317.77986780993 -CONST100 = 278.562471954228 -CONST101 = 284.190565191299 -CONST102 = 284.190565191299 -CONST103 = -1761.78376404427 -CONST104 = 290.050781013267 -CONST105 = -9946.66978169547 -CONST106 = 9.94865971265100 -CONST107 = 305.867618423396 -CONST108 = 305.867618423396 -CONST109 = 309.037315707990 -CONST110 = -7878.93651611688 -CONST111 = 2363.68095483506 -CONST112 = 14.5025390506634 -CONST113 = 338.322971229162 -CONST114 = 360.877835977772 -CONST115 = 4456.99955126765 -CONST116 = -1671.37483172537 -CONST117 = 386.296644634988 -CONST118 = 2436.42656051144 +CONST055 = 96.5741611587469 +CONST056 = 98.4867064514610 +CONST057 = 98.4867064514610 +CONST058 = 100.362812295177 +CONST059 = 101.517773354644 +CONST060 = 106.571461946737 +CONST061 = 106.571461946737 +CONST062 = 109.491768723557 +CONST063 = 109.491768723557 +CONST064 = 112.774323743054 +CONST065 = 112.774323743054 +CONST066 = 112.556235944527 +CONST067 = 2165.26701586663 +CONST068 = 130.522851455970 +CONST069 = 131.315608601948 +CONST070 = 133.817083060236 +CONST071 = 139.281235977114 +CONST072 = 139.281235977114 +CONST073 = 141.571909610700 +CONST074 = 142.095282595650 +CONST075 = 147.730059677192 +CONST076 = 150.544218442765 +CONST077 = 150.074981259369 +CONST078 = 154.518657853995 +CONST079 = 2202.22970505534 +CONST080 = -3939.46825805844 +CONST081 = -5968.00186901728 +CONST082 = 176.592751833137 +CONST083 = 176.178376404427 +CONST084 = 2228.49977563382 +CONST085 = 185.708314636152 +CONST086 = 196.973412902922 +CONST087 = 196.973412902922 +CONST088 = 203.035546709287 +CONST089 = 225.548647486108 +CONST090 = 225.548647486108 +CONST091 = 4330.53403173327 +CONST092 = 2285.08968653055 +CONST093 = 244.831037842559 +CONST094 = -1804.38917988886 +CONST095 = -1804.38917988886 +CONST096 = 244.831037842559 +CONST097 = 2317.77986780993 +CONST098 = 278.562471954228 +CONST099 = 284.190565191299 +CONST100 = 284.190565191299 +CONST101 = -1761.78376404427 +CONST102 = 290.050781013267 +CONST103 = -9946.66978169547 +CONST104 = 9.94865971265100 +CONST105 = 305.867618423396 +CONST106 = 305.867618423396 +CONST107 = 309.037315707990 +CONST108 = -7878.93651611688 +CONST109 = 2363.68095483506 +CONST110 = 14.5025390506634 +CONST111 = 338.322971229162 +CONST112 = 360.877835977772 +CONST113 = 4456.99955126765 +CONST114 = -1671.37483172537 +CONST115 = 386.296644634988 +CONST116 = 2436.42656051144 +CONST117 = 393.946825805844 +CONST118 = 393.946825805844 CONST119 = 393.946825805844 -CONST120 = 393.946825805844 -CONST121 = 393.946825805844 -CONST122 = -1648.19901710928 -CONST123 = 401.451249180707 -CONST124 = 406.071093418574 -CONST125 = 412.049754277320 -CONST126 = 2472.29852566392 -CONST127 = -1624.28437367430 -CONST128 = 426.285847786949 -CONST129 = 426.285847786948 -CONST130 = 2486.66744542387 -CONST131 = 450.224943778107 -CONST132 = 451.097294972216 -CONST133 = 451.097294972216 -CONST134 = 451.097294972215 -CONST135 = 6606.68911516602 -CONST136 = 6606.68911516602 -CONST137 = -1575.78730322338 -CONST138 = -1575.78730322338 -CONST139 = -3608.77835977772 -CONST140 = 492.433532257305 -CONST141 = -1545.18657853995 -CONST142 = -1545.18657853995 -CONST143 = 525.262434407792 -CONST144 = 535.268332240943 -CONST145 = 4635.55973561985 -CONST146 = 541.428124558099 -CONST147 = -3545.52143225260 -CONST148 = 557.124943908456 -CONST149 = -3523.56752808854 -CONST150 = -5571.24943908456 -CONST151 = 580.101562026534 -CONST152 = 10828.5624911620 -CONST153 = 15.7883647328499 -CONST154 = 590.920238708766 -CONST155 = 2642.67564606641 -CONST156 = 2642.67564606641 -CONST157 = 2676.34166120471 -CONST158 = 629.208487158668 -CONST159 = 4727.36190967013 -CONST160 = 4727.36190967013 -CONST161 = -1392.81235977114 -CONST162 = -1390.66792068596 -CONST163 = 2707.14062279049 -CONST164 = 663.111318779698 -CONST165 = -3427.63452979582 -CONST166 = -1378.81389032045 -CONST167 = 676.645942458323 -CONST168 = 706.371007332549 -CONST169 = -1338.17083060236 -CONST170 = -1338.17083060236 -CONST171 = 721.755671955545 -CONST172 = 734.076568351780 -CONST173 = 2785.62471954228 -CONST174 = 742.833258544608 -CONST175 = 772.593289269975 +CONST120 = -1648.19901710928 +CONST121 = 401.451249180707 +CONST122 = 406.071093418574 +CONST123 = 412.049754277320 +CONST124 = 2472.29852566392 +CONST125 = -1624.28437367430 +CONST126 = 426.285847786949 +CONST127 = 426.285847786948 +CONST128 = 2486.66744542387 +CONST129 = 450.224943778107 +CONST130 = 451.097294972216 +CONST131 = 451.097294972216 +CONST132 = 451.097294972215 +CONST133 = 6606.68911516602 +CONST134 = 6606.68911516602 +CONST135 = -1575.78730322338 +CONST136 = -1575.78730322338 +CONST137 = -3608.77835977772 +CONST138 = 492.433532257305 +CONST139 = -1545.18657853995 +CONST140 = -1545.18657853995 +CONST141 = 525.262434407792 +CONST142 = 535.268332240943 +CONST143 = 4635.55973561985 +CONST144 = 541.428124558099 +CONST145 = -3545.52143225260 +CONST146 = 557.124943908456 +CONST147 = -3523.56752808854 +CONST148 = -5571.24943908456 +CONST149 = 580.101562026534 +CONST150 = 10828.5624911620 +CONST151 = 15.7883647328499 +CONST152 = 590.920238708766 +CONST153 = 2642.67564606641 +CONST154 = 2642.67564606641 +CONST155 = 2676.34166120471 +CONST156 = 629.208487158668 +CONST157 = 4727.36190967013 +CONST158 = 4727.36190967013 +CONST159 = -1392.81235977114 +CONST160 = -1390.66792068596 +CONST161 = 2707.14062279049 +CONST162 = 663.111318779698 +CONST163 = -3427.63452979582 +CONST164 = -1378.81389032045 +CONST165 = 676.645942458323 +CONST166 = 706.371007332549 +CONST167 = -1338.17083060236 +CONST168 = -1338.17083060236 +CONST169 = 721.755671955545 +CONST170 = 734.076568351780 +CONST171 = 2785.62471954228 +CONST172 = 742.833258544608 +CONST173 = 772.593289269975 +CONST174 = 787.893651611688 +CONST175 = 787.893651611688 CONST176 = 787.893651611688 -CONST177 = 787.893651611688 -CONST178 = 787.893651611688 -CONST179 = 6.63243980843400 -CONST180 = 812.142186837148 -CONST181 = 812.142186837148 -CONST182 = -1218.21328025572 -CONST183 = -1202.92611992591 -CONST184 = -1202.92611992591 -CONST185 = -3248.56874734859 -CONST186 = -3248.56874734859 -CONST187 = -5285.35129213281 -CONST188 = -1181.84047741753 -CONST189 = 875.934149788456 -CONST190 = 880.891882022136 -CONST191 = 880.891882022136 -CONST192 = 2936.30627340712 -CONST193 = 900.449887556215 -CONST194 = 2954.60119354383 -CONST195 = -1114.24988781691 -CONST196 = -16.5810995210850 -CONST197 = -1101.11485252767 -CONST198 = -1081.63060497797 -CONST199 = 979.324151370235 +CONST177 = 6.63243980843400 +CONST178 = 812.142186837148 +CONST179 = 812.142186837148 +CONST180 = -1218.21328025572 +CONST181 = -1202.92611992591 +CONST182 = -1202.92611992591 +CONST183 = -3248.56874734859 +CONST184 = -3248.56874734859 +CONST185 = -5285.35129213281 +CONST186 = -1181.84047741753 +CONST187 = 875.934149788456 +CONST188 = 880.891882022136 +CONST189 = 880.891882022136 +CONST190 = 2936.30627340712 +CONST191 = 900.449887556215 +CONST192 = 2954.60119354383 +CONST193 = -1114.24988781691 +CONST194 = -16.5810995210850 +CONST195 = -1101.11485252767 +CONST196 = -1081.63060497797 +CONST197 = 15.7302121789667 +CONST198 = 979.324151370235 +CONST199 = 984.867064514610 CONST200 = 984.867064514610 -CONST201 = 984.867064514610 -CONST202 = 1015.17773354644 -CONST203 = -1027.70719569249 -CONST204 = -1021.92317475320 -CONST205 = -3065.76952425960 -CONST206 = -1015.17773354644 -CONST207 = 3090.37315707990 -CONST208 = -994.666978169547 +CONST201 = 1015.17773354644 +CONST202 = -1027.70719569249 +CONST203 = -1021.92317475320 +CONST204 = -3065.76952425960 +CONST205 = -1015.17773354644 +CONST206 = 3090.37315707990 +CONST207 = -994.666978169547 +CONST208 = -984.867064514610 CONST209 = -984.867064514610 -CONST210 = -984.867064514610 -CONST211 = -979.324151370235 -CONST212 = 1070.53666448189 -CONST213 = -979.324151370235 -CONST214 = 3151.57460644675 -CONST215 = 16.0956935264578 -CONST216 = 1114.24988781691 -CONST217 = -927.111947123971 -CONST218 = -927.111947123970 -CONST219 = -5.63871618715269 -CONST220 = -2954.60119354383 -CONST221 = -902.194589944431 -CONST222 = -900.449887556215 +CONST210 = -979.324151370235 +CONST211 = 1070.53666448189 +CONST212 = -979.324151370235 +CONST213 = 3151.57460644675 +CONST214 = 16.0956935264578 +CONST215 = 1114.24988781691 +CONST216 = -927.111947123971 +CONST217 = -927.111947123970 +CONST218 = -5.63871618715269 +CONST219 = -2954.60119354383 +CONST220 = -902.194589944431 +CONST221 = -900.449887556215 +CONST222 = -880.891882022136 CONST223 = -880.891882022136 -CONST224 = -880.891882022136 -CONST225 = -875.934149788456 -CONST226 = 1181.84047741753 -CONST227 = -4944.59705132784 +CONST224 = -875.934149788456 +CONST225 = 1181.84047741753 +CONST226 = -4944.59705132784 +CONST227 = 3248.56874734859 CONST228 = 3248.56874734859 -CONST229 = 3248.56874734859 -CONST230 = -835.687415862684 -CONST231 = 1218.21328025572 -CONST232 = -824.099508554641 +CONST229 = -835.687415862684 +CONST230 = 1218.21328025572 +CONST231 = -824.099508554641 +CONST232 = -824.863625092051 CONST233 = -824.863625092051 -CONST234 = -824.863625092051 -CONST235 = -812.142186837148 -CONST236 = 5352.68332240943 +CONST234 = -812.142186837148 +CONST235 = 5352.68332240943 +CONST236 = -787.893651611688 CONST237 = -787.893651611688 -CONST238 = -787.893651611688 -CONST239 = -772.593289269976 -CONST240 = -742.833258544608 -CONST241 = -2785.62471954228 -CONST242 = -734.076568351780 +CONST238 = -772.593289269976 +CONST239 = -742.833258544608 +CONST240 = -2785.62471954228 +CONST241 = -734.076568351780 +CONST242 = 1321.33782303320 CONST243 = 1321.33782303320 -CONST244 = 1321.33782303320 -CONST245 = -706.371007332549 -CONST246 = -696.406179885570 -CONST247 = 1353.29188491665 +CONST244 = -706.371007332549 +CONST245 = -696.406179885570 +CONST246 = 1353.29188491665 +CONST247 = -675.337415667161 CONST248 = -675.337415667161 -CONST249 = -675.337415667161 -CONST250 = 1378.81389032045 +CONST249 = 1378.81389032045 +CONST250 = 3427.63452979582 CONST251 = -669.085415301178 -CONST252 = 3427.63452979582 +CONST252 = -669.085415301178 CONST253 = -669.085415301178 -CONST254 = -669.085415301178 -CONST255 = 3427.63452979582 -CONST256 = -663.111318779698 -CONST257 = -2707.14062279049 +CONST254 = 3427.63452979582 +CONST255 = -663.111318779698 +CONST256 = -2707.14062279049 +CONST257 = 1392.81235977114 CONST258 = 1392.81235977114 -CONST259 = 1392.81235977114 -CONST260 = 1412.74201466510 -CONST261 = -4727.36190967013 -CONST262 = -2676.34166120471 -CONST263 = -618.074631415980 +CONST259 = 1412.74201466510 +CONST260 = -4727.36190967013 +CONST261 = -2676.34166120471 +CONST262 = -618.074631415980 +CONST263 = -611.735236846792 CONST264 = -611.735236846792 -CONST265 = -611.735236846792 -CONST266 = 1443.51134391109 -CONST267 = -590.920238708766 -CONST268 = -10828.5624911620 -CONST269 = -580.101562026534 -CONST270 = -2626.31217203896 -CONST271 = 3523.56752808854 +CONST265 = 1443.51134391109 +CONST266 = -590.920238708766 +CONST267 = -10828.5624911620 +CONST268 = -580.101562026534 +CONST269 = -2626.31217203896 +CONST270 = 3523.56752808854 +CONST271 = 5571.24943908456 CONST272 = 5571.24943908456 -CONST273 = 5571.24943908456 -CONST274 = -12.8765548211663 +CONST273 = -12.8765548211663 +CONST274 = -557.124943908456 CONST275 = -557.124943908456 -CONST276 = -557.124943908456 -CONST277 = 3545.52143225260 -CONST278 = -541.428124558099 -CONST279 = -6685.49932690147 -CONST280 = 7664.42381064899 -CONST281 = -525.262434407792 -CONST282 = 1532.88476212980 -CONST283 = 1545.18657853995 +CONST276 = 3545.52143225260 +CONST277 = -541.428124558099 +CONST278 = -6685.49932690147 +CONST279 = 7664.42381064899 +CONST280 = -525.262434407792 +CONST281 = 1532.88476212980 +CONST282 = 1545.18657853995 +CONST283 = -497.333489084773 CONST284 = -497.333489084773 -CONST285 = -497.333489084773 -CONST286 = -492.433532257305 +CONST285 = -492.433532257305 +CONST286 = 1575.78730322338 CONST287 = 1575.78730322338 -CONST288 = 1575.78730322338 -CONST289 = -463.555973561985 +CONST288 = -463.555973561985 +CONST289 = -450.224943778107 CONST290 = -450.224943778107 -CONST291 = -450.224943778107 -CONST292 = -450.224943778108 -CONST293 = -437.967074894228 -CONST294 = -2472.29852566392 -CONST295 = 1624.28437367430 -CONST296 = -2472.29852566392 -CONST297 = -406.071093418574 +CONST291 = -450.224943778108 +CONST292 = -437.967074894228 +CONST293 = -2472.29852566392 +CONST294 = 1624.28437367430 +CONST295 = -2472.29852566392 +CONST296 = -406.071093418574 +CONST297 = -393.946825805844 CONST298 = -393.946825805844 -CONST299 = -393.946825805844 -CONST300 = -2436.42656051144 +CONST299 = -2436.42656051144 +CONST300 = -386.296644634988 CONST301 = -386.296644634988 -CONST302 = -386.296644634988 -CONST303 = -4456.99955126765 +CONST302 = -4456.99955126765 +CONST303 = -337.668707833581 CONST304 = -337.668707833581 -CONST305 = -337.668707833581 +CONST305 = -331.555659389849 CONST306 = -331.555659389849 -CONST307 = -331.555659389849 -CONST308 = -2363.68095483506 -CONST309 = 7878.93651611688 -CONST310 = -309.037315707990 -CONST311 = -4404.45941011068 -CONST312 = -309.037315707990 +CONST307 = -2363.68095483506 +CONST308 = 7878.93651611688 +CONST309 = -309.037315707990 +CONST310 = -4404.45941011068 +CONST311 = -309.037315707990 +CONST312 = -305.867618423396 CONST313 = -305.867618423396 CONST314 = -305.867618423396 -CONST315 = -305.867618423396 -CONST316 = -300.731529981477 +CONST315 = -300.731529981477 +CONST316 = 9946.66978169547 CONST317 = 9946.66978169547 -CONST318 = 9946.66978169547 -CONST319 = -290.050781013267 -CONST320 = -284.190565191299 +CONST318 = -290.050781013267 +CONST319 = -284.190565191299 +CONST320 = -278.562471954228 CONST321 = -278.562471954228 -CONST322 = -278.562471954228 -CONST323 = -2317.77986780993 -CONST324 = -10505.2486881558 +CONST322 = -2317.77986780993 +CONST323 = -10505.2486881558 +CONST324 = -251.683394863467 CONST325 = -251.683394863467 -CONST326 = -251.683394863467 -CONST327 = -246.216766128653 -CONST328 = -244.831037842559 +CONST326 = -246.216766128653 +CONST327 = -244.831037842559 +CONST328 = -2285.08968653055 CONST329 = -2285.08968653055 -CONST330 = -2285.08968653055 -CONST331 = 3862.96644634988 -CONST332 = -223.028471767059 -CONST333 = -220.222970505534 -CONST334 = -206.215906273013 -CONST335 = -203.035546709287 +CONST330 = 3862.96644634988 +CONST331 = -223.028471767059 +CONST332 = -220.222970505534 +CONST333 = -206.215906273013 +CONST334 = -203.035546709287 +CONST335 = -196.973412902922 CONST336 = -196.973412902922 -CONST337 = -196.973412902922 -CONST338 = -182.903883409856 -CONST339 = -2228.49977563382 -CONST340 = 5968.00186901728 +CONST337 = -182.903883409856 +CONST338 = -2228.49977563382 +CONST339 = 5968.00186901728 +CONST340 = 16.4144510752435 CONST341 = 3939.46825805844 CONST342 = 3939.46825805844 CONST343 = -154.518657853995 @@ -386,67 +386,46 @@ CONST384 = -16.4144510752435 CONST385 = -16.0956935264578 CONST386 = -14.5025390506634 -CONST387 = -16.5810995210850 -CONST388 = -15.7883647328499 -CONST389 = -14.0695294930659 -CONST390 = -13.2648796168680 +CONST387 = 6131.53904851919 +CONST388 = -16.5810995210850 +CONST389 = -15.7883647328499 +CONST390 = -14.0695294930659 CONST391 = -11.2774323743054 CONST392 = -11.2774323743054 -CONST393 = 6131.53904851919 +CONST393 = -13.2648796168680 CONST394 = -6.63243980843400 CONST395 = -5.63871618715269 CONST396 = -4.82870805793735 CONST397 = -3.21913870529156 CONST398 = -11.2774323743054 -VAR00 = g_14 -VAR01 = z -VAR02 = g_12 -VAR03 = g_16 -VAR04 = g_4 -VAR05 = z**9 -VAR06 = z**4 -VAR07 = x**9 -VAR08 = z**8 -VAR09 = y -VAR10 = x**4 +VAR00 = x**10 +VAR01 = x**9 +VAR02 = x**8 +VAR03 = x**7 +VAR04 = x**6 +VAR05 = x**5 +VAR06 = x**4 +VAR07 = x**3 +VAR08 = x**2 +VAR09 = y**10 +VAR10 = y**9 VAR11 = y**8 -VAR12 = z**3 -VAR13 = x -VAR14 = x**8 -VAR15 = y**3 -VAR16 = x**3 -VAR17 = y**7 -VAR18 = y**2 -VAR19 = g_9 -VAR20 = g_1 -VAR21 = g_11 -VAR22 = g_19 -VAR23 = g_15 -VAR24 = g_8 -VAR25 = g_18 -VAR26 = z**7 -VAR27 = g_5 -VAR28 = z**2 -VAR29 = x**7 -VAR30 = g_6 -VAR31 = z**6 -VAR32 = x**2 -VAR33 = g_7 -VAR34 = y**6 -VAR35 = x**6 -VAR36 = z**5 -VAR37 = g_0 -VAR38 = g_3 -VAR39 = y**5 -VAR40 = g_2 -VAR41 = g_17 -VAR42 = x**5 -VAR43 = y**9 -VAR44 = g_13 -VAR45 = y**4 -VAR46 = g_20 -VAR47 = g_10 +VAR12 = y**7 +VAR13 = y**6 +VAR14 = y**5 +VAR15 = y**4 +VAR16 = y**3 +VAR17 = y**2 +VAR18 = z**10 +VAR19 = z**9 +VAR20 = z**8 +VAR21 = z**7 +VAR22 = z**6 +VAR23 = z**5 +VAR24 = z**4 +VAR25 = z**3 +VAR26 = z**2 # -------------------- kernel implementations -g_x = VAR00*(CONST000*VAR13*(CONST005*VAR28**4 - CONST161*VAR18**2*VAR28**2 + CONST195*VAR18**3*VAR28 + CONST321*VAR18*VAR28**3) + CONST002*VAR16*(CONST021*VAR28**3 + CONST087*VAR18**3 + CONST246*VAR18*VAR28**2 + CONST259*VAR18**2*VAR28) + CONST004*VAR42*(CONST021*VAR28**2 + CONST321*VAR18**2 + CONST321*VAR18*VAR28) + CONST006*VAR29*(CONST007*VAR28 + CONST045*VAR18) + CONST387*VAR16**3) + VAR02*(CONST000*VAR13*(CONST003*VAR28**4 - CONST302*VAR18**2*VAR28**2 + CONST343*VAR18*VAR28**3 + CONST363*VAR18**4) + CONST002*VAR16*(CONST125*VAR18**3 + CONST301*VAR18**2*VAR28 - CONST397*VAR28**3) + CONST004*VAR42*(CONST302*VAR18**2 - CONST344*VAR18*VAR28 + CONST397*VAR28**2) + CONST006*VAR29*(CONST047*VAR18 + CONST396*VAR28) + CONST385*VAR16**3) + VAR03*(CONST000*VAR13*(CONST049*VAR18**2*VAR28**2 + CONST177*VAR18*VAR28**3 + CONST380*VAR28**4) + CONST002*VAR16*(-CONST049*VAR18**2*VAR28 + CONST379*VAR28**3) + CONST004*VAR42*(CONST022*VAR28**2 + CONST237*VAR18*VAR28 + CONST349*VAR18**2) + CONST006*VAR29*(CONST020*VAR28 + CONST040*VAR18) + CONST383*VAR16**3) + VAR04*(CONST008*VAR12**3 + CONST009*VAR32*(CONST177*VAR18*VAR36 + CONST270*VAR12*VAR18**2 + CONST389*VAR26) + CONST010*VAR32**2*(CONST177*VAR01*VAR18**2 + CONST178*VAR12*VAR18 + CONST373*VAR36) + CONST011*VAR32**3*(CONST304*VAR01*VAR18 + CONST389*VAR12) + CONST056*VAR01*VAR32**4 + CONST177*VAR18**2*VAR36 + CONST305*VAR18*VAR26) + VAR09*VAR20*(CONST064*VAR28**4 + CONST065*VAR32**4 + CONST205*VAR28**3*VAR32 + CONST205*VAR28*VAR32**3 + CONST280*VAR28**2*VAR32**2) + VAR09*VAR22*(CONST018*VAR12*VAR42 - CONST018*VAR16*VAR36 - CONST225*VAR01*VAR29 + CONST225*VAR13*VAR26) + VAR19*(CONST009*VAR32*(CONST044*VAR09*VAR28**3 + CONST212*VAR28*VAR39 + CONST253*VAR15*VAR28**2 + CONST313*VAR17) + CONST010*VAR32**2*(CONST060*VAR09*VAR28**2 + CONST144*VAR39 + CONST254*VAR15*VAR28) + CONST011*VAR32**3*(CONST044*VAR09*VAR28 + CONST332*VAR15) + CONST015*VAR09*VAR28**4 + CONST027*VAR15**3 + CONST078*VAR09*VAR32**4 + CONST144*VAR28**2*VAR39 + CONST313*VAR17*VAR28 + CONST332*VAR15*VAR28**3) + VAR21*(CONST072*VAR09*VAR13*VAR26 + VAR01*(CONST072*VAR09*VAR29 + CONST169*VAR15*VAR42 + CONST264*VAR13*VAR17 - CONST361*VAR16*VAR39) + VAR12*(CONST123*VAR09*VAR42 + CONST262*VAR15*VAR16 - CONST361*VAR13*VAR39) + VAR36*(CONST123*VAR09*VAR16 + CONST170*VAR13*VAR15)) + VAR23*(VAR09*(CONST158*VAR01*VAR29 + CONST223*VAR16*VAR36 + CONST325*VAR13*VAR26) + VAR15*(CONST155*VAR13*VAR36 + CONST192*VAR12*VAR16 + CONST311*VAR01*VAR42) + VAR39*(-CONST149*VAR01*VAR16 + CONST149*VAR12*VAR13)) + VAR24*(CONST009*VAR32*(-CONST142*VAR12*VAR18**2 + CONST232*VAR01*VAR18**3 - CONST274*VAR26 + CONST289*VAR18*VAR36) + CONST010*VAR32**2*(CONST017*VAR36 + CONST175*VAR01*VAR18**2 + CONST289*VAR12*VAR18) + CONST011*VAR32**3*(-CONST274*VAR12 + CONST344*VAR01*VAR18) + CONST026*VAR01*VAR32**4 + CONST084*VAR01*VAR18**4 + CONST175*VAR18**2*VAR36 + CONST232*VAR12*VAR18**3 + CONST344*VAR18*VAR26 - CONST397*VAR12**3) + VAR25*(CONST062*VAR13*VAR28**4 + CONST128*VAR28*VAR29 + CONST284*VAR28**2*VAR42 + CONST306*VAR16*VAR28**3 + CONST381*VAR16**3 + VAR18*(CONST041*VAR13*VAR28**3 + CONST083*VAR28*VAR42 + CONST317*VAR16*VAR28**2 - CONST320*VAR29)) + VAR27*(VAR09*(CONST048*VAR28**4 + CONST075*VAR32**4 + CONST197*VAR28**2*VAR32**2 + CONST223*VAR28*VAR32**3) + VAR15*(CONST081*VAR28**2*VAR32 + CONST135*VAR28*VAR32**2 + CONST203*VAR32**3 + CONST242*VAR28**3) + VAR39*(CONST187*VAR28*VAR32 - CONST223*VAR32**2 - CONST224*VAR28**2)) + VAR30*(CONST009*VAR32*(CONST100*VAR18*VAR36 + CONST240*VAR01*VAR18**3 + CONST390*VAR26) + CONST010*VAR32**2*(-CONST195*VAR01*VAR18**2 + CONST321*VAR12*VAR18) + CONST011*VAR32**3*(CONST012*VAR12 + CONST322*VAR01*VAR18) + CONST043*VAR01*VAR32**4 + CONST100*VAR18*VAR26 + CONST195*VAR18**2*VAR36 - CONST240*VAR12*VAR18**3 + CONST394*VAR12**3) + VAR33*(VAR09*(CONST077*VAR32**4 + CONST286*VAR28**2*VAR32**2 + CONST298*VAR28**3*VAR32 + CONST374*VAR28**4) + VAR15*(CONST166*VAR32**3 + CONST194*VAR28**2*VAR32 + CONST200*VAR28*VAR32**2 - CONST267*VAR28**3) + VAR17*(CONST290*VAR32 - CONST291*VAR28) + VAR39*(-CONST051*VAR32**2 + CONST188*VAR28**2 + CONST308*VAR28*VAR32)) + VAR37*(CONST095*VAR01*VAR32**4 + CONST211*VAR26*VAR32 + CONST252*VAR32**2*VAR36 + CONST329*VAR12*VAR32**3 - CONST378*VAR12**3) + VAR38*(VAR09*(CONST016*VAR28*VAR32**3 - CONST206*VAR28**2*VAR32**2 + CONST231*VAR28**3*VAR32 - CONST351*VAR32**4 + CONST356*VAR28**4) + VAR15*(CONST046*VAR28**2*VAR32 + CONST146*VAR28**3 + CONST278*VAR32**3 + CONST367*VAR28*VAR32**2)) + VAR40*(CONST076*VAR01*VAR32**4 + CONST102*VAR26*VAR32 + CONST256*VAR12*VAR32**3 + CONST388*VAR12**3 + VAR18*(CONST042*VAR01*VAR32**3 + CONST083*VAR32*VAR36 - CONST105*VAR12*VAR32**2 - CONST320*VAR26)) + VAR41*(VAR09*(CONST180*VAR01*VAR29 + CONST235*VAR16*VAR36 - CONST269*VAR13*VAR26 + CONST300*VAR12*VAR42) + VAR15*(CONST185*VAR13*VAR36 + CONST186*VAR01*VAR42 - CONST268*VAR12*VAR16)) + VAR44*(CONST222*VAR01*VAR13*VAR17 + VAR09*(-CONST238*VAR12*VAR42 - CONST298*VAR16*VAR36 - CONST299*VAR01*VAR29) + VAR15*(CONST082*VAR12*VAR16 + CONST147*VAR01*VAR42 + CONST298*VAR13*VAR36) + VAR39*(-CONST261*VAR01*VAR16 + CONST287*VAR12*VAR13)) + VAR46*(CONST165*VAR28**2*VAR42 - CONST213*VAR28*VAR29 + CONST328*VAR13*VAR28**4 - CONST330*VAR16*VAR28**3 + CONST378*VAR16**3) + VAR47*(CONST000*VAR13*(CONST091*VAR18*VAR28**3 + CONST171*VAR18**3*VAR28 + CONST221*VAR18**2*VAR28**2 + CONST355*VAR18**4 + CONST395*VAR28**4) + CONST002*VAR16*(CONST113*VAR18*VAR28**2 + CONST114*VAR18**3 + CONST221*VAR18**2*VAR28 + CONST392*VAR28**3) + CONST004*VAR42*(CONST092*VAR18*VAR28 + CONST316*VAR18**2 + CONST392*VAR28**2) + CONST006*VAR29*(CONST039*VAR18 + CONST219*VAR28) + CONST391*VAR16**3) -g_y = CONST000*VAR09*VAR25*(CONST029*VAR28**4 + CONST029*VAR32**4 + CONST130*VAR28**2*VAR32**2 + CONST208*VAR28**3*VAR32 + CONST208*VAR28*VAR32**3) + CONST000*VAR09*VAR40*(-CONST041*VAR12*VAR42 + CONST041*VAR16*VAR36 + CONST320*VAR01*VAR29 - CONST320*VAR13*VAR26) + VAR00*(CONST073*VAR09*VAR32**4 + CONST074*VAR09*VAR28**4 + CONST195*VAR15*VAR28**3 - CONST195*VAR28**2*VAR39 + VAR32**3*(CONST195*VAR15 + CONST275*VAR09*VAR28) + VAR32**2*(CONST161*VAR09*VAR28**2 - CONST195*VAR39 + CONST273*VAR15*VAR28) + VAR32*(-CONST150*VAR15*VAR28**2 + CONST275*VAR09*VAR28**3 + CONST279*VAR28*VAR39)) + VAR02*(-CONST142*VAR15*VAR28**3 - CONST245*VAR17*VAR28 + CONST294*VAR28**2*VAR39 + CONST343*VAR09*VAR28**4 - CONST344*VAR09*VAR32**4 + VAR32**3*(CONST142*VAR15 - CONST312*VAR09*VAR28) + VAR32**2*(CONST141*VAR15*VAR28 - CONST296*VAR39) + VAR32*(-CONST142*VAR15*VAR28**2 + CONST245*VAR17 + CONST310*VAR09*VAR28**3)) + VAR03*(-CONST110*VAR15*VAR28*VAR32**2 - CONST281*VAR15*VAR28**3 + CONST354*VAR09*VAR28**4 - CONST354*VAR09*VAR32**4 + VAR32**3*(CONST137*VAR09*VAR28 + CONST281*VAR15) + VAR32*(CONST110*VAR15*VAR28**2 + CONST288*VAR09*VAR28**3)) + VAR04*(CONST249*VAR01*VAR09*VAR29 + VAR13*(CONST214*VAR15*VAR36 + CONST248*VAR09*VAR26) + VAR16*(CONST288*VAR09*VAR36 + CONST324*VAR12*VAR15) + VAR42*(CONST214*VAR01*VAR15 + CONST287*VAR09*VAR12)) + VAR19*(CONST015*VAR16**3 + VAR13*(CONST015*VAR28**4 + CONST157*VAR18**2*VAR28**2 + CONST251*VAR18*VAR28**3 - CONST315*VAR18**4 + CONST361*VAR18**3*VAR28) + VAR16*(CONST033*VAR18*VAR28**2 + CONST044*VAR28**3 + CONST236*VAR18**2*VAR28 + CONST361*VAR18**3) + VAR29*(CONST044*VAR28 + CONST251*VAR18) + VAR42*(CONST034*VAR18*VAR28 + CONST060*VAR28**2 + CONST157*VAR18**2)) + VAR20*(CONST014*VAR16**3 + CONST064*VAR13*VAR28**4 + CONST204*VAR16*VAR28**3 + CONST282*VAR28**2*VAR42 + CONST293*VAR28*VAR29) + VAR21*(CONST015*VAR12**3 + VAR01*(CONST015*VAR32**4 + CONST157*VAR18**2*VAR32**2 + CONST251*VAR18*VAR32**3 - CONST313*VAR18**4 + CONST360*VAR18**3*VAR32) + VAR12*(CONST033*VAR18*VAR32**2 + CONST044*VAR32**3 + CONST236*VAR18**2*VAR32 + CONST361*VAR18**3) + VAR26*(CONST044*VAR32 + CONST251*VAR18) + VAR36*(CONST034*VAR18*VAR32 + CONST060*VAR32**2 + CONST157*VAR18**2)) + VAR22*(CONST014*VAR12**3 + CONST064*VAR01*VAR32**4 + CONST204*VAR12*VAR32**3 + CONST282*VAR32**2*VAR36 + CONST293*VAR26*VAR32) + VAR23*(CONST009*VAR18*(CONST242*VAR01*VAR32**3 - CONST242*VAR12*VAR32**2 + CONST243*VAR32*VAR36 + CONST347*VAR26) + CONST010*VAR18**2*(CONST085*VAR36 + CONST103*VAR12*VAR32 - CONST224*VAR01*VAR32**2) + CONST019*VAR12**3 + CONST048*VAR01*VAR32**4 + CONST333*VAR32**2*VAR36 + CONST352*VAR26*VAR32) + VAR24*(CONST312*VAR01*VAR09*VAR29 + VAR13*(CONST207*VAR15*VAR36 + CONST227*VAR12*VAR39 + CONST260*VAR01*VAR17 + CONST312*VAR09*VAR26) + VAR16*(CONST030*VAR12*VAR15 + CONST217*VAR09*VAR36 + CONST227*VAR01*VAR39) + VAR42*(CONST207*VAR01*VAR15 + CONST217*VAR09*VAR12)) + VAR27*(CONST009*VAR18*(CONST242*VAR13*VAR28**3 - CONST242*VAR16*VAR28**2 + CONST244*VAR28*VAR42 + CONST347*VAR29) + CONST010*VAR18**2*(CONST085*VAR42 + CONST103*VAR16*VAR28 - CONST224*VAR13*VAR28**2) + CONST019*VAR16**3 + CONST048*VAR13*VAR28**4 + CONST333*VAR28**2*VAR42 + CONST353*VAR28*VAR29) + VAR30*(CONST276*VAR01*VAR09*VAR29 + VAR13*(CONST148*VAR09*VAR26 - CONST303*VAR12*VAR39 + CONST303*VAR15*VAR36) + VAR16*(CONST148*VAR09*VAR36 + CONST303*VAR01*VAR39) + VAR42*(CONST275*VAR09*VAR12 - CONST303*VAR01*VAR15)) + VAR33*(CONST009*VAR18*(CONST089*VAR28*VAR42 - CONST210*VAR16*VAR28**2 - CONST267*VAR13*VAR28**3 + CONST337*VAR29) + CONST010*VAR18**2*(CONST188*VAR13*VAR28**2 + CONST238*VAR16*VAR28 - CONST299*VAR42) + CONST011*VAR18**3*(-CONST291*VAR13*VAR28 + CONST345*VAR16) + CONST023*VAR16**3 + CONST350*VAR16*VAR28**3 + CONST358*VAR28**2*VAR42 + CONST374*VAR13*VAR28**4) + VAR38*(CONST009*VAR18*(CONST146*VAR13*VAR28**3 + CONST257*VAR16*VAR28**2 + CONST295*VAR28*VAR42 + CONST366*VAR29) + CONST124*VAR16*VAR28**3 + CONST319*VAR28*VAR29 - CONST335*VAR28**2*VAR42 + CONST356*VAR13*VAR28**4 - CONST386*VAR16**3) + VAR41*(CONST009*VAR18*(CONST050*VAR26 + CONST127*VAR32*VAR36 - CONST257*VAR12*VAR32**2 + CONST278*VAR01*VAR32**3) + CONST061*VAR01*VAR32**4 + CONST297*VAR12*VAR32**3 - CONST319*VAR26*VAR32 + CONST335*VAR32**2*VAR36 + CONST386*VAR12**3) + VAR44*(CONST009*VAR18*(CONST209*VAR12*VAR32**2 + CONST267*VAR01*VAR32**3 + CONST336*VAR32*VAR36 - CONST337*VAR26) + CONST010*VAR18**2*(CONST178*VAR12*VAR32 - CONST188*VAR01*VAR32**2 + CONST299*VAR36) + CONST011*VAR18**3*(CONST079*VAR12 + CONST291*VAR01*VAR32) - CONST350*VAR12*VAR32**3 - CONST358*VAR32**2*VAR36 - CONST374*VAR01*VAR32**4 + CONST384*VAR12**3) + VAR47*(CONST036*VAR15**3 + CONST066*VAR09*VAR28**4 + CONST067*VAR09*VAR32**4 + CONST069*VAR28**2*VAR39 + CONST184*VAR15*VAR28**3 + CONST234*VAR17*VAR28 + VAR32**3*(CONST133*VAR09*VAR28 + CONST183*VAR15) + VAR32**2*(CONST069*VAR39 + CONST139*VAR15*VAR28 + CONST167*VAR09*VAR28**2) + VAR32*(CONST093*VAR28*VAR39 + CONST132*VAR09*VAR28**3 + CONST139*VAR15*VAR28**2 + CONST233*VAR17)) -g_z = VAR00*(CONST106*VAR01*VAR32**4 + CONST116*VAR18**2*VAR36 + CONST148*VAR18*VAR26 + CONST196*VAR12**3 - CONST240*VAR12*VAR18**3 + VAR32**3*(CONST275*VAR01*VAR18 - CONST362*VAR12) + VAR32**2*(CONST074*VAR36 + CONST173*VAR01*VAR18**2 + CONST241*VAR12*VAR18) + VAR32*(CONST032*VAR26 + CONST116*VAR18*VAR36 - CONST150*VAR12*VAR18**2 + CONST339*VAR01*VAR18**3)) + VAR02*(CONST052*VAR01*VAR32**4 + CONST084*VAR01*VAR18**4 + CONST099*VAR18**2*VAR36 + CONST122*VAR12*VAR18**3 + CONST263*VAR18*VAR26 - CONST385*VAR12**3 + VAR32**3*(CONST274*VAR12 - CONST312*VAR01*VAR18) + VAR32**2*(CONST017*VAR36 + CONST239*VAR01*VAR18**2) + VAR32*(CONST031*VAR26 - CONST142*VAR12*VAR18**2 + CONST218*VAR18*VAR36)) + VAR03*(CONST024*VAR12**3 + CONST037*VAR01*VAR32**4 + CONST177*VAR18**2*VAR36 + CONST292*VAR18*VAR26 + VAR32**3*(CONST059*VAR12 + CONST137*VAR01*VAR18) + VAR32**2*(CONST341*VAR01*VAR18**2 + CONST346*VAR36) + VAR32*(CONST110*VAR12*VAR18**2 + CONST160*VAR18*VAR36 + CONST338*VAR26)) + VAR04*(CONST008*VAR16**3 + VAR13*(CONST056*VAR28**4 + CONST308*VAR18*VAR28**3 + CONST341*VAR18**2*VAR28**2) + VAR16*(CONST110*VAR18**2*VAR28 + CONST341*VAR18*VAR28**2 + CONST359*VAR28**3) + VAR29*(CONST304*VAR18 + CONST377*VAR28) + VAR42*(CONST177*VAR18**2 - CONST308*VAR18*VAR28 + CONST327*VAR28**2)) + VAR09*VAR20*(-CONST018*VAR12*VAR42 + CONST018*VAR16*VAR36 + CONST225*VAR01*VAR29 - CONST225*VAR13*VAR26) + VAR09*VAR22*(CONST064*VAR32**4 + CONST065*VAR28**4 + CONST205*VAR28**3*VAR32 + CONST205*VAR28*VAR32**3 + CONST280*VAR28**2*VAR32**2) + VAR19*(CONST072*VAR01*VAR09*VAR29 + VAR13*(CONST072*VAR09*VAR26 + CONST169*VAR15*VAR36 + CONST265*VAR01*VAR17 - CONST361*VAR12*VAR39) + VAR16*(CONST123*VAR09*VAR36 + CONST262*VAR12*VAR15 - CONST361*VAR01*VAR39) + VAR42*(CONST123*VAR09*VAR12 + CONST170*VAR01*VAR15)) + VAR21*(CONST009*VAR28*(CONST044*VAR09*VAR32**3 + CONST212*VAR32*VAR39 + CONST253*VAR15*VAR32**2 + CONST314*VAR17) + CONST010*VAR28**2*(CONST060*VAR09*VAR32**2 + CONST144*VAR39 + CONST254*VAR15*VAR32) + CONST011*VAR28**3*(CONST044*VAR09*VAR32 + CONST332*VAR15) + CONST015*VAR09*VAR32**4 + CONST028*VAR15**3 + CONST078*VAR09*VAR28**4 + CONST144*VAR32**2*VAR39 + CONST315*VAR17*VAR32 + CONST332*VAR15*VAR32**3) + VAR23*(VAR09*(CONST048*VAR32**4 + CONST075*VAR28**4 + CONST197*VAR28**2*VAR32**2 + CONST224*VAR28**3*VAR32) + VAR15*(CONST081*VAR28*VAR32**2 + CONST136*VAR28**2*VAR32 + CONST203*VAR28**3 + CONST242*VAR32**3) + VAR39*(CONST187*VAR28*VAR32 - CONST223*VAR28**2 - CONST224*VAR32**2)) + VAR24*(-CONST397*VAR16**3 + VAR13*(CONST026*VAR28**4 + CONST084*VAR18**4 + CONST198*VAR18*VAR28**3 + CONST296*VAR18**3*VAR28 + CONST331*VAR18**2*VAR28**2) + VAR16*(CONST053*VAR28**3 + CONST145*VAR18**2*VAR28 + CONST232*VAR18**3 + CONST323*VAR18*VAR28**2) + VAR29*(CONST035*VAR28 + CONST344*VAR18) + VAR42*(CONST057*VAR28**2 + CONST162*VAR18*VAR28 + CONST175*VAR18**2)) + VAR25*(CONST063*VAR01*VAR32**4 + CONST129*VAR26*VAR32 + CONST285*VAR32**2*VAR36 + CONST307*VAR12*VAR32**3 + CONST381*VAR12**3 + VAR18*(CONST041*VAR01*VAR32**3 + CONST083*VAR32*VAR36 + CONST317*VAR12*VAR32**2 - CONST320*VAR26)) + VAR27*(VAR09*(CONST158*VAR13*VAR26 + CONST223*VAR12*VAR42 + CONST326*VAR01*VAR29) + VAR15*(CONST156*VAR01*VAR42 + CONST192*VAR12*VAR16 + CONST311*VAR13*VAR36) + VAR39*(CONST149*VAR01*VAR16 - CONST149*VAR12*VAR13)) + VAR30*(CONST179*VAR16**3 + VAR13*(CONST150*VAR18**2*VAR28**2 - CONST339*VAR18**3*VAR28 + CONST357*VAR18*VAR28**3 + CONST372*VAR28**4) + VAR16*(CONST240*VAR18**3 + CONST259*VAR18*VAR28**2 + CONST362*VAR28**3) + VAR29*(CONST032*VAR28 + CONST322*VAR18) + VAR42*(-CONST195*VAR18**2 + CONST230*VAR18*VAR28)) + VAR33*(-CONST222*VAR01*VAR13*VAR17 + VAR09*(CONST238*VAR16*VAR36 + CONST298*VAR12*VAR42 + CONST299*VAR13*VAR26) + VAR15*(CONST121*VAR01*VAR42 - CONST147*VAR13*VAR36 + CONST342*VAR12*VAR16) + VAR39*(CONST138*VAR01*VAR16 + CONST261*VAR12*VAR13)) + VAR37*(CONST095*VAR13*VAR28**4 + CONST211*VAR28*VAR29 + CONST252*VAR28**2*VAR42 + CONST329*VAR16*VAR28**3 - CONST378*VAR16**3) + VAR38*(VAR09*(CONST118*VAR16*VAR36 - CONST235*VAR12*VAR42 + CONST235*VAR13*VAR26 + CONST269*VAR01*VAR29) + VAR15*(-CONST185*VAR13*VAR36 + CONST229*VAR01*VAR42 + CONST268*VAR12*VAR16)) + VAR40*(CONST153*VAR16**3 + CONST164*VAR16*VAR28**3 + CONST320*VAR28*VAR29 + CONST348*VAR13*VAR28**4 + VAR18*(-CONST042*VAR13*VAR28**3 - CONST083*VAR28*VAR42 + CONST105*VAR16*VAR28**2 + CONST320*VAR29)) + VAR41*(VAR09*(-CONST016*VAR28**3*VAR32 + CONST061*VAR32**4 + CONST182*VAR28*VAR32**3 + CONST206*VAR28**2*VAR32**2 + CONST351*VAR28**4) + VAR15*(CONST046*VAR28**2*VAR32 - CONST046*VAR28*VAR32**2 + CONST146*VAR28**3 + CONST278*VAR32**3)) + VAR44*(VAR09*(-CONST286*VAR28**2*VAR32**2 - CONST298*VAR28*VAR32**3 + CONST346*VAR28**4 - CONST374*VAR32**4) + VAR15*(-CONST166*VAR28**3 + CONST210*VAR28**2*VAR32 + CONST220*VAR28*VAR32**2 + CONST267*VAR32**3) + VAR17*(-CONST291*VAR28 + CONST291*VAR32) + VAR39*(CONST051*VAR28**2 - CONST188*VAR32**2 - CONST308*VAR28*VAR32)) + VAR46*(-CONST165*VAR32**2*VAR36 + CONST213*VAR26*VAR32 - CONST328*VAR01*VAR32**4 + CONST330*VAR12*VAR32**3 - CONST378*VAR12**3) + VAR47*(CONST097*VAR18**2*VAR36 + CONST134*VAR18*VAR26 + CONST266*VAR12*VAR18**3 + CONST334*VAR01*VAR18**4 + CONST391*VAR12**3 + CONST398*VAR01*VAR32**4 + VAR32**3*(CONST133*VAR01*VAR18 + CONST376*VAR12) + VAR32**2*(CONST096*VAR01*VAR18**2 + CONST247*VAR12*VAR18 + CONST369*VAR36) + VAR32*(CONST139*VAR12*VAR18**2 + CONST247*VAR18*VAR36 + CONST266*VAR01*VAR18**3 + CONST375*VAR26)) +g_x = g_0*(CONST093*VAR02*z + CONST210*VAR08*VAR21 + CONST250*VAR06*VAR23 + CONST328*VAR04*VAR25 - CONST378*VAR19) + g_1*y*(CONST062*VAR20 + CONST063*VAR02 + CONST204*VAR04*VAR26 + CONST204*VAR08*VAR22 + CONST279*VAR06*VAR24) + g_10*(CONST000*x*(CONST089*VAR17*VAR22 + CONST169*VAR13*VAR26 + CONST220*VAR15*VAR24 + CONST355*VAR11 + CONST395*VAR20) + CONST002*VAR07*(CONST111*VAR17*VAR24 + CONST112*VAR13 + CONST220*VAR15*VAR26 + CONST392*VAR22) + CONST004*VAR05*(CONST090*VAR17*VAR26 + CONST315*VAR15 + CONST392*VAR24) + CONST006*VAR03*(CONST037*VAR17 + CONST218*VAR26) + CONST391*VAR01) + g_11*(CONST070*VAR21*x*y + VAR23*(CONST121*VAR07*y + CONST168*VAR16*x) + VAR25*(CONST121*VAR05*y + CONST261*VAR07*VAR16 - CONST361*VAR14*x) + z*(CONST070*VAR03*y + CONST167*VAR05*VAR16 + CONST263*VAR12*x - CONST361*VAR07*VAR14)) + g_12*(CONST000*x*(CONST003*VAR20 - CONST301*VAR15*VAR24 + CONST343*VAR17*VAR22 + CONST363*VAR11) + CONST002*VAR07*(CONST123*VAR13 + CONST300*VAR15*VAR26 - CONST397*VAR22) + CONST004*VAR05*(CONST301*VAR15 - CONST344*VAR17*VAR26 + CONST397*VAR24) + CONST006*VAR03*(CONST045*VAR17 + CONST396*VAR26) + CONST385*VAR01) + g_13*(CONST221*VAR12*x*z + VAR14*(-CONST260*VAR07*z + CONST286*VAR25*x) + VAR16*(CONST080*VAR07*VAR25 + CONST145*VAR05*z + CONST297*VAR23*x) + y*(-CONST237*VAR05*VAR25 - CONST297*VAR07*VAR23 - CONST298*VAR03*z)) + g_14*(CONST000*x*(CONST005*VAR20 - CONST159*VAR15*VAR24 + CONST193*VAR13*VAR26 + CONST320*VAR17*VAR22) + CONST002*VAR07*(CONST020*VAR22 + CONST085*VAR13 + CONST245*VAR17*VAR24 + CONST258*VAR15*VAR26) + CONST004*VAR05*(CONST020*VAR24 + CONST320*VAR15 + CONST320*VAR17*VAR26) + CONST006*VAR03*(CONST007*VAR26 + CONST043*VAR17) + CONST388*VAR01) + g_15*(VAR14*(-CONST147*VAR07*z + CONST147*VAR25*x) + VAR16*(CONST153*VAR23*x + CONST190*VAR07*VAR25 + CONST310*VAR05*z) + y*(CONST156*VAR03*z + CONST222*VAR07*VAR23 + CONST324*VAR21*x)) + g_16*(CONST000*x*(CONST047*VAR15*VAR24 + CONST175*VAR17*VAR22 + CONST380*VAR20) + CONST002*VAR07*(-CONST047*VAR15*VAR26 + CONST379*VAR22) + CONST004*VAR05*(CONST021*VAR24 + CONST236*VAR17*VAR26 + CONST349*VAR15) + CONST006*VAR03*(CONST019*VAR26 + CONST038*VAR17) + CONST383*VAR01) + g_17*(VAR16*(CONST183*VAR23*x + CONST184*VAR05*z - CONST267*VAR07*VAR25) + y*(CONST178*VAR03*z + CONST234*VAR07*VAR23 - CONST268*VAR21*x + CONST299*VAR05*VAR25)) + g_18*(CONST060*VAR20*x + CONST126*VAR03*VAR26 + CONST283*VAR05*VAR24 + CONST305*VAR07*VAR22 + CONST381*VAR01 + VAR17*(CONST039*VAR22*x + CONST081*VAR05*VAR26 + CONST316*VAR07*VAR24 - CONST319*VAR03)) + g_19*y*(CONST018*VAR05*VAR25 - CONST018*VAR07*VAR23 - CONST224*VAR03*z + CONST224*VAR21*x) + g_2*(CONST074*VAR02*z + CONST100*VAR08*VAR21 + CONST255*VAR04*VAR25 + CONST389*VAR19 + VAR17*(CONST040*VAR04*z + CONST081*VAR08*VAR23 - CONST103*VAR06*VAR25 - CONST319*VAR21)) + g_20*(CONST163*VAR05*VAR24 - CONST212*VAR03*VAR26 + CONST327*VAR20*x - CONST329*VAR07*VAR22 + CONST378*VAR01) + g_3*(VAR16*(CONST044*VAR08*VAR24 + CONST144*VAR22 + CONST277*VAR04 + CONST367*VAR06*VAR26) + y*(CONST016*VAR04*VAR26 - CONST205*VAR06*VAR24 + CONST230*VAR08*VAR22 - CONST351*VAR02 + CONST356*VAR20)) + g_4*(CONST008*VAR19 + CONST009*VAR08*(CONST175*VAR17*VAR23 + CONST269*VAR15*VAR25 + CONST390*VAR21) + CONST010*VAR06*(CONST175*VAR15*z + CONST176*VAR17*VAR25 + CONST373*VAR23) + CONST011*VAR04*(CONST303*VAR17*z + CONST390*VAR25) + CONST053*VAR02*z + CONST175*VAR15*VAR23 + CONST304*VAR17*VAR21) + g_5*(VAR14*(CONST185*VAR08*VAR26 - CONST222*VAR06 - CONST223*VAR24) + VAR16*(CONST079*VAR08*VAR24 + CONST133*VAR06*VAR26 + CONST202*VAR04 + CONST241*VAR22) + y*(CONST046*VAR20 + CONST073*VAR02 + CONST195*VAR06*VAR24 + CONST222*VAR04*VAR26)) + g_6*(CONST009*VAR08*(CONST098*VAR17*VAR23 + CONST239*VAR13*z + CONST393*VAR21) + CONST010*VAR06*(-CONST193*VAR15*z + CONST320*VAR17*VAR25) + CONST011*VAR04*(CONST012*VAR25 + CONST321*VAR17*z) + CONST041*VAR02*z + CONST098*VAR17*VAR21 + CONST193*VAR15*VAR23 - CONST239*VAR13*VAR25 + CONST394*VAR19) + g_7*(VAR12*(CONST289*VAR08 - CONST290*VAR26) + VAR14*(-CONST049*VAR06 + CONST186*VAR24 + CONST307*VAR08*VAR26) + VAR16*(CONST164*VAR04 + CONST192*VAR08*VAR24 + CONST199*VAR06*VAR26 - CONST266*VAR22) + y*(CONST075*VAR02 + CONST285*VAR06*VAR24 + CONST297*VAR08*VAR22 + CONST374*VAR20)) + g_8*(CONST009*VAR08*(-CONST140*VAR15*VAR25 + CONST231*VAR13*z - CONST273*VAR21 + CONST288*VAR17*VAR23) + CONST010*VAR06*(CONST017*VAR23 + CONST173*VAR15*z + CONST288*VAR17*VAR25) + CONST011*VAR04*(-CONST273*VAR25 + CONST344*VAR17*z) + CONST024*VAR02*z + CONST082*VAR11*z + CONST173*VAR15*VAR23 + CONST231*VAR13*VAR25 + CONST344*VAR17*VAR21 - CONST397*VAR19) + g_9*(CONST009*VAR08*(CONST042*VAR22*y + CONST211*VAR14*VAR26 + CONST251*VAR16*VAR24 + CONST312*VAR12) + CONST010*VAR06*(CONST058*VAR24*y + CONST142*VAR14 + CONST252*VAR16*VAR26) + CONST011*VAR04*(CONST042*VAR26*y + CONST331*VAR16) + CONST015*VAR20*y + CONST025*VAR10 + CONST076*VAR02*y + CONST142*VAR14*VAR24 + CONST312*VAR12*VAR26 + CONST331*VAR16*VAR22) +g_y = CONST000*g_18*y*(CONST027*VAR02 + CONST027*VAR20 + CONST128*VAR06*VAR24 + CONST207*VAR04*VAR26 + CONST207*VAR08*VAR22) + CONST000*g_2*y*(-CONST039*VAR05*VAR25 + CONST039*VAR07*VAR23 + CONST319*VAR03*z - CONST319*VAR21*x) + g_1*(CONST014*VAR01 + CONST062*VAR20*x + CONST203*VAR07*VAR22 + CONST281*VAR05*VAR24 + CONST292*VAR03*VAR26) + g_10*(CONST034*VAR10 + CONST064*VAR20*y + CONST065*VAR02*y + CONST067*VAR14*VAR24 + CONST182*VAR16*VAR22 + CONST233*VAR12*VAR26 + VAR04*(CONST131*VAR26*y + CONST181*VAR16) + VAR06*(CONST067*VAR14 + CONST137*VAR16*VAR26 + CONST165*VAR24*y) + VAR08*(CONST091*VAR14*VAR26 + CONST130*VAR22*y + CONST137*VAR16*VAR24 + CONST232*VAR12)) + g_11*(CONST015*VAR19 + VAR21*(CONST042*VAR08 + CONST253*VAR17) + VAR23*(CONST033*VAR08*VAR17 + CONST058*VAR06 + CONST155*VAR15) + VAR25*(CONST032*VAR06*VAR17 + CONST042*VAR04 + CONST235*VAR08*VAR15 + CONST361*VAR13) + z*(CONST015*VAR02 + CONST155*VAR06*VAR15 + CONST253*VAR04*VAR17 - CONST312*VAR11 + CONST360*VAR08*VAR13)) + g_12*(-CONST140*VAR16*VAR22 - CONST244*VAR12*VAR26 + CONST293*VAR14*VAR24 + CONST343*VAR20*y - CONST344*VAR02*y + VAR04*(CONST140*VAR16 - CONST311*VAR26*y) + VAR06*(CONST139*VAR16*VAR26 - CONST295*VAR14) + VAR08*(-CONST140*VAR16*VAR24 + CONST244*VAR12 + CONST309*VAR22*y)) + g_13*(CONST009*VAR17*(CONST208*VAR06*VAR25 + CONST266*VAR04*z + CONST335*VAR08*VAR23 - CONST336*VAR21) + CONST010*VAR15*(CONST176*VAR08*VAR25 - CONST186*VAR06*z + CONST298*VAR23) + CONST011*VAR13*(CONST077*VAR25 + CONST290*VAR08*z) - CONST350*VAR04*VAR25 - CONST358*VAR06*VAR23 - CONST374*VAR02*z + CONST384*VAR19) + g_14*(CONST071*VAR02*y + CONST072*VAR20*y - CONST193*VAR14*VAR24 + CONST193*VAR16*VAR22 + VAR04*(CONST193*VAR16 + CONST274*VAR26*y) + VAR06*(CONST159*VAR24*y - CONST193*VAR14 + CONST272*VAR16*VAR26) + VAR08*(-CONST148*VAR16*VAR24 + CONST274*VAR22*y + CONST278*VAR14*VAR26)) + g_15*(CONST009*VAR17*(CONST241*VAR04*z - CONST241*VAR06*VAR25 + CONST242*VAR08*VAR23 + CONST347*VAR21) + CONST010*VAR15*(CONST083*VAR23 + CONST101*VAR08*VAR25 - CONST223*VAR06*z) + CONST046*VAR02*z + CONST197*VAR19 + CONST332*VAR06*VAR23 + CONST352*VAR08*VAR21) + g_16*(-CONST108*VAR06*VAR16*VAR26 - CONST280*VAR16*VAR22 - CONST354*VAR02*y + CONST354*VAR20*y + VAR04*(CONST135*VAR26*y + CONST280*VAR16) + VAR08*(CONST108*VAR16*VAR24 + CONST287*VAR22*y)) + g_17*(CONST009*VAR17*(CONST048*VAR21 + CONST125*VAR08*VAR23 - CONST256*VAR06*VAR25 + CONST277*VAR04*z) + CONST059*VAR02*z + CONST296*VAR04*VAR25 - CONST318*VAR08*VAR21 + CONST334*VAR06*VAR23 + CONST386*VAR19) + g_19*(CONST014*VAR19 + CONST062*VAR02*z + CONST203*VAR04*VAR25 + CONST281*VAR06*VAR23 + CONST292*VAR08*VAR21) + g_3*(CONST009*VAR17*(CONST144*VAR22*x + CONST256*VAR07*VAR24 + CONST294*VAR05*VAR26 + CONST366*VAR03) + CONST122*VAR07*VAR22 + CONST318*VAR03*VAR26 - CONST334*VAR05*VAR24 + CONST356*VAR20*x - CONST386*VAR01) + g_4*(CONST248*VAR03*y*z + VAR05*(CONST213*VAR16*z + CONST286*VAR25*y) + VAR07*(CONST287*VAR23*y + CONST323*VAR16*VAR25) + x*(CONST213*VAR16*VAR23 + CONST247*VAR21*y)) + g_5*(CONST009*VAR17*(-CONST241*VAR07*VAR24 + CONST241*VAR22*x + CONST243*VAR05*VAR26 + CONST347*VAR03) + CONST010*VAR15*(CONST083*VAR05 + CONST101*VAR07*VAR26 - CONST223*VAR24*x) + CONST046*VAR20*x + CONST197*VAR01 + CONST332*VAR05*VAR24 + CONST353*VAR03*VAR26) + g_6*(CONST275*VAR03*y*z + VAR05*(CONST274*VAR25*y - CONST302*VAR16*z) + VAR07*(CONST146*VAR23*y + CONST302*VAR14*z) + x*(CONST146*VAR21*y - CONST302*VAR14*VAR25 + CONST302*VAR16*VAR23)) + g_7*(CONST009*VAR17*(CONST087*VAR05*VAR26 - CONST209*VAR07*VAR24 - CONST266*VAR22*x + CONST336*VAR03) + CONST010*VAR15*(CONST186*VAR24*x + CONST237*VAR07*VAR26 - CONST298*VAR05) + CONST011*VAR13*(-CONST290*VAR26*x + CONST345*VAR07) + CONST340*VAR01 + CONST350*VAR07*VAR22 + CONST358*VAR05*VAR24 + CONST374*VAR20*x) + g_8*(CONST311*VAR03*y*z + VAR05*(CONST206*VAR16*z + CONST216*VAR25*y) + VAR07*(CONST028*VAR16*VAR25 + CONST216*VAR23*y + CONST226*VAR14*z) + x*(CONST206*VAR16*VAR23 + CONST226*VAR14*VAR25 + CONST259*VAR12*z + CONST311*VAR21*y)) + g_9*(CONST015*VAR01 + VAR03*(CONST042*VAR26 + CONST253*VAR17) + VAR05*(CONST033*VAR17*VAR26 + CONST058*VAR24 + CONST155*VAR15) + VAR07*(CONST032*VAR17*VAR24 + CONST042*VAR22 + CONST235*VAR15*VAR26 + CONST361*VAR13) + x*(CONST015*VAR20 + CONST155*VAR15*VAR24 + CONST253*VAR17*VAR22 - CONST314*VAR11 + CONST361*VAR13*VAR26)) +g_z = g_0*(CONST093*VAR20*x + CONST210*VAR03*VAR26 + CONST250*VAR05*VAR24 + CONST328*VAR07*VAR22 - CONST378*VAR01) + g_1*y*(-CONST018*VAR05*VAR25 + CONST018*VAR07*VAR23 + CONST224*VAR03*z - CONST224*VAR21*x) + g_10*(CONST095*VAR15*VAR23 + CONST132*VAR17*VAR21 + CONST265*VAR13*VAR25 + CONST333*VAR11*z + CONST391*VAR19 + CONST398*VAR02*z + VAR04*(CONST131*VAR17*z + CONST376*VAR25) + VAR06*(CONST094*VAR15*z + CONST246*VAR17*VAR25 + CONST369*VAR23) + VAR08*(CONST137*VAR15*VAR25 + CONST246*VAR17*VAR23 + CONST265*VAR13*z + CONST375*VAR21)) + g_11*(CONST009*VAR26*(CONST042*VAR04*y + CONST211*VAR08*VAR14 + CONST251*VAR06*VAR16 + CONST313*VAR12) + CONST010*VAR24*(CONST058*VAR06*y + CONST142*VAR14 + CONST252*VAR08*VAR16) + CONST011*VAR22*(CONST042*VAR08*y + CONST331*VAR16) + CONST015*VAR02*y + CONST026*VAR10 + CONST076*VAR20*y + CONST142*VAR06*VAR14 + CONST314*VAR08*VAR12 + CONST331*VAR04*VAR16) + g_12*(CONST050*VAR02*z + CONST082*VAR11*z + CONST097*VAR15*VAR23 + CONST120*VAR13*VAR25 + CONST262*VAR17*VAR21 - CONST385*VAR19 + VAR04*(CONST273*VAR25 - CONST311*VAR17*z) + VAR06*(CONST017*VAR23 + CONST238*VAR15*z) + VAR08*(CONST029*VAR21 - CONST140*VAR15*VAR25 + CONST217*VAR17*VAR23)) + g_13*(VAR12*(CONST290*VAR08 - CONST290*VAR26) + VAR14*(CONST049*VAR24 - CONST186*VAR06 - CONST307*VAR08*VAR26) + VAR16*(-CONST164*VAR22 + CONST209*VAR08*VAR24 + CONST219*VAR06*VAR26 + CONST266*VAR04) + y*(-CONST285*VAR06*VAR24 - CONST297*VAR04*VAR26 + CONST346*VAR20 - CONST374*VAR02)) + g_14*(CONST104*VAR02*z + CONST114*VAR15*VAR23 + CONST146*VAR17*VAR21 + CONST194*VAR19 - CONST239*VAR13*VAR25 + VAR04*(CONST274*VAR17*z - CONST362*VAR25) + VAR06*(CONST072*VAR23 + CONST171*VAR15*z + CONST240*VAR17*VAR25) + VAR08*(CONST030*VAR21 + CONST114*VAR17*VAR23 - CONST148*VAR15*VAR25 + CONST338*VAR13*z)) + g_15*(VAR14*(CONST185*VAR08*VAR26 - CONST222*VAR24 - CONST223*VAR06) + VAR16*(CONST079*VAR06*VAR26 + CONST134*VAR08*VAR24 + CONST202*VAR22 + CONST241*VAR04) + y*(CONST046*VAR02 + CONST073*VAR20 + CONST195*VAR06*VAR24 + CONST223*VAR08*VAR22)) + g_16*(CONST022*VAR19 + CONST035*VAR02*z + CONST175*VAR15*VAR23 + CONST291*VAR17*VAR21 + VAR04*(CONST057*VAR25 + CONST135*VAR17*z) + VAR06*(CONST341*VAR15*z + CONST346*VAR23) + VAR08*(CONST108*VAR15*VAR25 + CONST158*VAR17*VAR23 + CONST337*VAR21)) + g_17*(VAR16*(-CONST044*VAR06*VAR26 + CONST044*VAR08*VAR24 + CONST144*VAR22 + CONST277*VAR04) + y*(-CONST016*VAR08*VAR22 + CONST059*VAR02 + CONST180*VAR04*VAR26 + CONST205*VAR06*VAR24 + CONST351*VAR20)) + g_18*(CONST061*VAR02*z + CONST127*VAR08*VAR21 + CONST284*VAR06*VAR23 + CONST306*VAR04*VAR25 + CONST381*VAR19 + VAR17*(CONST039*VAR04*z + CONST081*VAR08*VAR23 + CONST316*VAR06*VAR25 - CONST319*VAR21)) + g_19*y*(CONST062*VAR02 + CONST063*VAR20 + CONST204*VAR04*VAR26 + CONST204*VAR08*VAR22 + CONST279*VAR06*VAR24) + g_2*(CONST151*VAR01 + CONST162*VAR07*VAR22 + CONST319*VAR03*VAR26 + CONST348*VAR20*x + VAR17*(-CONST040*VAR22*x - CONST081*VAR05*VAR26 + CONST103*VAR07*VAR24 + CONST319*VAR03)) + g_20*(-CONST163*VAR06*VAR23 + CONST212*VAR08*VAR21 - CONST327*VAR02*z + CONST329*VAR04*VAR25 - CONST378*VAR19) + g_3*(VAR16*(-CONST183*VAR23*x + CONST228*VAR05*z + CONST267*VAR07*VAR25) + y*(CONST116*VAR07*VAR23 - CONST234*VAR05*VAR25 + CONST234*VAR21*x + CONST268*VAR03*z)) + g_4*(CONST008*VAR01 + VAR03*(CONST303*VAR17 + CONST377*VAR26) + VAR05*(CONST175*VAR15 - CONST307*VAR17*VAR26 + CONST326*VAR24) + VAR07*(CONST108*VAR15*VAR26 + CONST341*VAR17*VAR24 + CONST359*VAR22) + x*(CONST053*VAR20 + CONST307*VAR17*VAR22 + CONST341*VAR15*VAR24)) + g_5*(VAR14*(CONST147*VAR07*z - CONST147*VAR25*x) + VAR16*(CONST154*VAR05*z + CONST190*VAR07*VAR25 + CONST310*VAR23*x) + y*(CONST156*VAR21*x + CONST222*VAR05*VAR25 + CONST325*VAR03*z)) + g_6*(CONST177*VAR01 + VAR03*(CONST030*VAR26 + CONST321*VAR17) + VAR05*(-CONST193*VAR15 + CONST229*VAR17*VAR26) + VAR07*(CONST239*VAR13 + CONST258*VAR17*VAR24 + CONST362*VAR22) + x*(CONST148*VAR15*VAR24 - CONST338*VAR13*VAR26 + CONST357*VAR17*VAR22 + CONST372*VAR20)) + g_7*(-CONST221*VAR12*x*z + VAR14*(CONST136*VAR07*z + CONST260*VAR25*x) + VAR16*(CONST119*VAR05*z - CONST145*VAR23*x + CONST342*VAR07*VAR25) + y*(CONST237*VAR07*VAR23 + CONST297*VAR05*VAR25 + CONST298*VAR21*x)) + g_8*(-CONST397*VAR01 + VAR03*(CONST031*VAR26 + CONST344*VAR17) + VAR05*(CONST055*VAR24 + CONST160*VAR17*VAR26 + CONST173*VAR15) + VAR07*(CONST051*VAR22 + CONST143*VAR15*VAR26 + CONST231*VAR13 + CONST322*VAR17*VAR24) + x*(CONST024*VAR20 + CONST082*VAR11 + CONST196*VAR17*VAR22 + CONST295*VAR13*VAR26 + CONST330*VAR15*VAR24)) + g_9*(CONST070*VAR03*y*z + VAR05*(CONST121*VAR25*y + CONST168*VAR16*z) + VAR07*(CONST121*VAR23*y + CONST261*VAR16*VAR25 - CONST361*VAR14*z) + x*(CONST070*VAR21*y + CONST167*VAR16*VAR23 + CONST264*VAR12*z - CONST361*VAR14*VAR25)) diff --git a/notebooks/bwd_implementations/bwd_2.py b/notebooks/bwd_implementations/bwd_2.py index 8e33b88..686e576 100644 --- a/notebooks/bwd_implementations/bwd_2.py +++ b/notebooks/bwd_implementations/bwd_2.py @@ -3,15 +3,34 @@ CONST001 = 4.47213595499958 CONST002 = -2.23606797749979 CONST003 = -3.87298334620742 -VAR00 = g_1 -VAR01 = g_3 -VAR02 = g_4 -VAR03 = g_0 -VAR04 = z -VAR05 = x -VAR06 = g_2 -VAR07 = y +VAR00 = x**10 +VAR01 = x**9 +VAR02 = x**8 +VAR03 = x**7 +VAR04 = x**6 +VAR05 = x**5 +VAR06 = x**4 +VAR07 = x**3 +VAR08 = x**2 +VAR09 = y**10 +VAR10 = y**9 +VAR11 = y**8 +VAR12 = y**7 +VAR13 = y**6 +VAR14 = y**5 +VAR15 = y**4 +VAR16 = y**3 +VAR17 = y**2 +VAR18 = z**10 +VAR19 = z**9 +VAR20 = z**8 +VAR21 = z**7 +VAR22 = z**6 +VAR23 = z**5 +VAR24 = z**4 +VAR25 = z**3 +VAR26 = z**2 # -------------------- kernel implementations -g_x = CONST002*VAR05*VAR06 - CONST003*VAR00*VAR07 + CONST003*VAR02*VAR05 - CONST003*VAR03*VAR04 -g_y = CONST001*VAR06*VAR07 - CONST003*VAR00*VAR05 - CONST003*VAR01*VAR04 -g_z = CONST002*VAR04*VAR06 - CONST003*VAR01*VAR07 - CONST003*VAR02*VAR04 - CONST003*VAR03*VAR05 +g_x = CONST002*g_2*x - CONST003*g_0*z - CONST003*g_1*y + CONST003*g_4*x +g_y = CONST001*g_2*y - CONST003*g_1*x - CONST003*g_3*z +g_z = CONST002*g_2*z - CONST003*g_0*x - CONST003*g_3*y - CONST003*g_4*z diff --git a/notebooks/bwd_implementations/bwd_3.py b/notebooks/bwd_implementations/bwd_3.py index 1f26a25..837fbd2 100644 --- a/notebooks/bwd_implementations/bwd_3.py +++ b/notebooks/bwd_implementations/bwd_3.py @@ -15,20 +15,34 @@ CONST013 = -4.86055552380590 CONST014 = -3.24037034920393 CONST015 = -1.62018517460197 -VAR00 = g_5 -VAR01 = g_6 -VAR02 = y**2 -VAR03 = g_4 -VAR04 = g_1 -VAR05 = z -VAR06 = x -VAR07 = g_2 +VAR00 = x**10 +VAR01 = x**9 +VAR02 = x**8 +VAR03 = x**7 +VAR04 = x**6 +VAR05 = x**5 +VAR06 = x**4 +VAR07 = x**3 VAR08 = x**2 -VAR09 = z**2 -VAR10 = g_0 -VAR11 = g_3 -VAR12 = y +VAR09 = y**10 +VAR10 = y**9 +VAR11 = y**8 +VAR12 = y**7 +VAR13 = y**6 +VAR14 = y**5 +VAR15 = y**4 +VAR16 = y**3 +VAR17 = y**2 +VAR18 = z**10 +VAR19 = z**9 +VAR20 = z**8 +VAR21 = z**7 +VAR22 = z**6 +VAR23 = z**5 +VAR24 = z**4 +VAR25 = z**3 +VAR26 = z**2 # -------------------- kernel implementations -g_x = CONST008*VAR01*VAR05*VAR06 + CONST009*VAR00*VAR06*VAR12 - CONST009*VAR04*VAR05*VAR12 + CONST010*VAR06*VAR11*VAR12 + CONST014*VAR03*VAR05*VAR06 + VAR07*(CONST002*VAR02 + CONST013*VAR08 + CONST015*VAR09) + VAR10*(CONST011*VAR08 - CONST011*VAR09) -g_y = CONST005*VAR03*VAR05*VAR12 + CONST005*VAR06*VAR07*VAR12 - CONST009*VAR04*VAR05*VAR06 + VAR00*(CONST012*VAR08 - CONST012*VAR09) + VAR11*(CONST007*VAR08 + CONST007*VAR09 - CONST010*VAR02) -g_z = -CONST008*VAR05*VAR06*VAR10 - CONST009*VAR00*VAR05*VAR12 - CONST009*VAR04*VAR06*VAR12 + CONST010*VAR05*VAR11*VAR12 + CONST014*VAR05*VAR06*VAR07 + VAR01*(CONST011*VAR08 - CONST011*VAR09) + VAR03*(CONST002*VAR02 + CONST013*VAR09 + CONST015*VAR08) +g_x = CONST008*g_6*x*z - CONST009*g_1*y*z + CONST009*g_5*x*y + CONST010*g_3*x*y + CONST014*g_4*x*z + g_0*(CONST011*VAR08 - CONST011*VAR26) + g_2*(CONST002*VAR17 + CONST013*VAR08 + CONST015*VAR26) +g_y = CONST005*g_2*x*y + CONST005*g_4*y*z - CONST009*g_1*x*z + g_3*(CONST007*VAR08 + CONST007*VAR26 - CONST010*VAR17) + g_5*(CONST012*VAR08 - CONST012*VAR26) +g_z = -CONST008*g_0*x*z - CONST009*g_1*x*y - CONST009*g_5*y*z + CONST010*g_3*y*z + CONST014*g_2*x*z + g_4*(CONST002*VAR17 + CONST013*VAR26 + CONST015*VAR08) + g_6*(CONST011*VAR08 - CONST011*VAR26) diff --git a/notebooks/bwd_implementations/bwd_4.py b/notebooks/bwd_implementations/bwd_4.py index ded7f4f..8886695 100644 --- a/notebooks/bwd_implementations/bwd_4.py +++ b/notebooks/bwd_implementations/bwd_4.py @@ -1,11 +1,11 @@ # -------------------- variable and constant definitions CONST000 = 2.00000000000000 -CONST001 = 2.25000000000000 -CONST002 = 4.50000000000000 +CONST001 = 4.50000000000000 +CONST002 = 2.25000000000000 CONST003 = 6.70820393249937 -CONST004 = 8.87411967464942 -CONST005 = 9.48683298050514 -CONST006 = 6.27495019900557 +CONST004 = 6.27495019900557 +CONST005 = 8.87411967464942 +CONST006 = 9.48683298050514 CONST007 = 10.0623058987491 CONST008 = 12.0000000000000 CONST009 = 18.8248505970167 @@ -14,39 +14,48 @@ CONST012 = 28.4604989415154 CONST013 = 37.6497011940334 CONST014 = 40.2492235949962 -CONST015 = -9.00000000000000 -CONST016 = -8.87411967464942 -CONST017 = -37.6497011940334 -CONST018 = -6.70820393249937 -CONST019 = -26.6223590239483 -CONST020 = -21.3453742061366 -CONST021 = -20.1246117974981 -CONST022 = -18.8248505970167 -CONST023 = -18.0000000000000 -CONST024 = -14.2302494707577 -CONST025 = -10.0623058987491 +CONST015 = -37.6497011940334 +CONST016 = -6.70820393249937 +CONST017 = -26.6223590239483 +CONST018 = -21.3453742061366 +CONST019 = -20.1246117974981 +CONST020 = -18.8248505970167 +CONST021 = -18.0000000000000 +CONST022 = -14.2302494707577 +CONST023 = -10.0623058987491 +CONST024 = -9.00000000000000 +CONST025 = -8.87411967464942 CONST026 = -7.11512473537885 CONST027 = -6.27495019900557 CONST028 = -3.35410196624968 -VAR00 = g_7 -VAR01 = y**3 -VAR02 = g_8 -VAR03 = g_5 -VAR04 = g_6 -VAR05 = y**2 -VAR06 = g_4 +VAR00 = x**10 +VAR01 = x**9 +VAR02 = x**8 +VAR03 = x**7 +VAR04 = x**6 +VAR05 = x**5 +VAR06 = x**4 VAR07 = x**3 -VAR08 = z**3 -VAR09 = g_1 -VAR10 = z -VAR11 = x -VAR12 = g_2 -VAR13 = x**2 -VAR14 = z**2 -VAR15 = g_0 -VAR16 = g_3 -VAR17 = y +VAR08 = x**2 +VAR09 = y**10 +VAR10 = y**9 +VAR11 = y**8 +VAR12 = y**7 +VAR13 = y**6 +VAR14 = y**5 +VAR15 = y**4 +VAR16 = y**3 +VAR17 = y**2 +VAR18 = z**10 +VAR19 = z**9 +VAR20 = z**8 +VAR21 = z**7 +VAR22 = z**6 +VAR23 = z**5 +VAR24 = z**4 +VAR25 = z**3 +VAR26 = z**2 # -------------------- kernel implementations -g_x = CONST017*VAR00*VAR10*VAR11*VAR17 + CONST024*VAR03*VAR10*VAR11*VAR17 + VAR02*(-CONST016*VAR07 + CONST019*VAR11*VAR14) + VAR04*(-CONST018*VAR07 + CONST021*VAR05*VAR11) + VAR06*(CONST000*VAR11*(CONST001*VAR14 + CONST015*VAR05) + CONST002*VAR07) + VAR09*VAR17*(CONST022*VAR13 - CONST022*VAR14) + VAR12*(-CONST021*VAR05*VAR10 + CONST025*VAR10*VAR13 + CONST028*VAR08) + VAR15*(-CONST016*VAR08 + CONST019*VAR10*VAR13) + VAR16*(CONST005*VAR01 + CONST020*VAR13*VAR17 + CONST026*VAR14*VAR17) -g_y = CONST000*VAR04*VAR17*(CONST025*VAR13 - CONST025*VAR14) + CONST014*VAR10*VAR11*VAR12*VAR17 + VAR00*(CONST022*VAR10*VAR13 - CONST027*VAR08) + VAR03*(CONST026*VAR08 + VAR10*(CONST012*VAR05 + CONST026*VAR13)) + VAR06*(CONST008*VAR01 + CONST023*VAR13*VAR17 + CONST023*VAR14*VAR17) + VAR09*(-CONST022*VAR11*VAR14 + CONST027*VAR07) + VAR16*(CONST026*VAR07 + VAR11*(CONST012*VAR05 + CONST026*VAR14)) -g_z = -CONST017*VAR09*VAR10*VAR11*VAR17 + CONST024*VAR10*VAR11*VAR16*VAR17 + VAR00*VAR17*(CONST022*VAR13 - CONST022*VAR14) + VAR02*(-CONST016*VAR08 + CONST019*VAR10*VAR13) + VAR03*(CONST005*VAR01 + CONST020*VAR14*VAR17 + CONST026*VAR13*VAR17) + VAR04*(CONST018*VAR08 - CONST021*VAR05*VAR10) + VAR06*(CONST002*VAR08 + CONST002*VAR10*VAR13 + CONST023*VAR05*VAR10) + VAR12*(CONST028*VAR07 + VAR11*(-CONST021*VAR05 + CONST025*VAR14)) + VAR15*(CONST016*VAR07 - CONST019*VAR11*VAR14) +g_x = CONST015*g_7*x*y*z + CONST022*g_5*x*y*z + g_0*(CONST017*VAR08*z - CONST025*VAR25) + g_1*y*(CONST020*VAR08 - CONST020*VAR26) + g_2*(-CONST019*VAR17*z + CONST023*VAR08*z + CONST028*VAR25) + g_3*(CONST006*VAR16 + CONST018*VAR08*y + CONST026*VAR26*y) + g_4*(CONST000*x*(CONST002*VAR26 + CONST024*VAR17) + CONST001*VAR07) + g_6*(-CONST016*VAR07 + CONST019*VAR17*x) + g_8*(CONST017*VAR26*x - CONST025*VAR07) +g_y = CONST000*g_6*y*(CONST023*VAR08 - CONST023*VAR26) + CONST014*g_2*x*y*z + g_1*(-CONST020*VAR26*x + CONST027*VAR07) + g_3*(CONST026*VAR07 + x*(CONST012*VAR17 + CONST026*VAR26)) + g_4*(CONST008*VAR16 + CONST021*VAR08*y + CONST021*VAR26*y) + g_5*(CONST026*VAR25 + z*(CONST012*VAR17 + CONST026*VAR08)) + g_7*(CONST020*VAR08*z - CONST027*VAR25) +g_z = -CONST015*g_1*x*y*z + CONST022*g_3*x*y*z + g_0*(-CONST017*VAR26*x + CONST025*VAR07) + g_2*(CONST028*VAR07 + x*(-CONST019*VAR17 + CONST023*VAR26)) + g_4*(CONST001*VAR08*z + CONST001*VAR25 + CONST021*VAR17*z) + g_5*(CONST006*VAR16 + CONST018*VAR26*y + CONST026*VAR08*y) + g_6*(CONST016*VAR25 - CONST019*VAR17*z) + g_7*y*(CONST020*VAR08 - CONST020*VAR26) + g_8*(CONST017*VAR08*z - CONST025*VAR25) diff --git a/notebooks/bwd_implementations/bwd_5.py b/notebooks/bwd_implementations/bwd_5.py index c9d9ae2..fb26cc2 100644 --- a/notebooks/bwd_implementations/bwd_5.py +++ b/notebooks/bwd_implementations/bwd_5.py @@ -1,38 +1,38 @@ # -------------------- variable and constant definitions CONST000 = 1.60565407233314 -CONST001 = 1.60565407233314 -CONST002 = 3.00000000000000 -CONST003 = 3.21130814466628 -CONST004 = 5.20291384706685 +CONST001 = 3.00000000000000 +CONST002 = 3.21130814466628 +CONST003 = 1.60565407233314 +CONST004 = 6.42261628933256 CONST005 = 6.42261628933256 -CONST006 = 6.42261628933256 -CONST007 = 8.67152307844476 -CONST008 = 8.02827036166571 -CONST009 = 6.93721846275580 -CONST010 = 11.6340690431164 -CONST011 = 12.8452325786651 +CONST006 = 8.67152307844476 +CONST007 = 8.02827036166571 +CONST008 = 6.93721846275580 +CONST009 = 11.6340690431164 +CONST010 = 12.8452325786651 +CONST011 = 6.21867148191637 CONST012 = 6.21867148191637 -CONST013 = 12.4373429638327 -CONST014 = 7.35803132638072 -CONST015 = 16.5831239517770 -CONST016 = 16.9926454679664 +CONST013 = 16.5831239517770 +CONST014 = 12.4373429638327 +CONST015 = 16.9926454679664 +CONST016 = 20.8116553882674 CONST017 = 12.8452325786651 CONST018 = 13.8744369255116 -CONST019 = 20.8116553882674 +CONST019 = 24.8746859276655 CONST020 = 24.8746859276655 -CONST021 = 24.8746859276655 -CONST022 = 27.7488738510232 +CONST021 = 27.7488738510232 +CONST022 = 5.20291384706685 CONST023 = 29.4321253055229 CONST024 = 29.4321253055229 CONST025 = 33.9852909359329 CONST026 = 33.9852909359329 -CONST027 = 41.6233107765348 -CONST028 = 46.5362761724657 -CONST029 = 51.3809303146605 +CONST027 = 7.35803132638072 +CONST028 = 41.6233107765348 +CONST029 = 46.5362761724657 CONST030 = 51.3809303146605 -CONST031 = 83.2466215530696 -CONST032 = 88.2963759165686 -CONST033 = 6.21867148191637 +CONST031 = 51.3809303146605 +CONST032 = 83.2466215530696 +CONST033 = 88.2963759165686 CONST034 = 101.955872807799 CONST035 = 8.49632273398321 CONST036 = -8.67152307844475 @@ -58,36 +58,40 @@ CONST056 = -19.2678488679977 CONST057 = -16.9926454679664 CONST058 = -16.9926454679664 -CONST059 = -16.5831239517770 -CONST060 = -13.8744369255116 +CONST059 = -13.8744369255116 +CONST060 = -16.5831239517770 CONST061 = -8.49632273398321 CONST062 = -6.93721846275580 CONST063 = -5.20291384706685 CONST064 = -3.46860923137790 -VAR00 = z**4 -VAR01 = g_6 -VAR02 = x**2 -VAR03 = g_7 -VAR04 = y -VAR05 = x**4 -VAR06 = g_8 -VAR07 = z**3 -VAR08 = z -VAR09 = x -VAR10 = g_0 -VAR11 = g_3 -VAR12 = y**3 -VAR13 = x**3 -VAR14 = g_2 -VAR15 = g_5 -VAR16 = y**2 -VAR17 = g_9 -VAR18 = g_4 -VAR19 = g_1 -VAR20 = y**4 -VAR21 = g_10 -VAR22 = z**2 +VAR00 = x**10 +VAR01 = x**9 +VAR02 = x**8 +VAR03 = x**7 +VAR04 = x**6 +VAR05 = x**5 +VAR06 = x**4 +VAR07 = x**3 +VAR08 = x**2 +VAR09 = y**10 +VAR10 = y**9 +VAR11 = y**8 +VAR12 = y**7 +VAR13 = y**6 +VAR14 = y**5 +VAR15 = y**4 +VAR16 = y**3 +VAR17 = y**2 +VAR18 = z**10 +VAR19 = z**9 +VAR20 = z**8 +VAR21 = z**7 +VAR22 = z**6 +VAR23 = z**5 +VAR24 = z**4 +VAR25 = z**3 +VAR26 = z**2 # -------------------- kernel implementations -g_x = VAR01*(CONST006*VAR07*VAR09 + VAR08*(CONST005*VAR13 + CONST046*VAR09*VAR16)) + VAR03*(CONST049*VAR09*VAR12 - CONST051*VAR04*VAR13) + VAR04*VAR17*(CONST024*VAR13 + CONST038*VAR09*VAR22) + VAR04*VAR19*(CONST038*VAR02*VAR08 - CONST052*VAR07) + VAR06*(CONST009*VAR07*VAR09 + VAR08*(CONST039*VAR09*VAR16 - CONST054*VAR13)) + VAR10*(CONST010*VAR02**2 + CONST010*VAR22**2 + CONST040*VAR02*VAR22) + VAR11*(CONST041*VAR02*VAR04*VAR08 - CONST049*VAR08*VAR12 + CONST057*VAR04*VAR07) + VAR14*(CONST002*VAR02*(CONST060*VAR16 + CONST064*VAR22) + CONST007*VAR02**2 - CONST045*VAR16*VAR22 + CONST063*VAR22**2) + VAR15*(CONST048*VAR09*VAR12 + VAR04*(CONST020*VAR09*VAR22 + CONST020*VAR13)) + VAR18*(CONST000*VAR22**2 + CONST002*VAR02*(CONST003*VAR22 + CONST055*VAR16) + CONST008*VAR02**2 + CONST011*VAR16**2 + CONST056*VAR16*VAR22) + VAR21*(CONST028*VAR08*VAR13 + CONST043*VAR07*VAR09) -g_y = VAR01*(CONST046*VAR04*VAR07 + VAR08*(CONST030*VAR12 + CONST046*VAR02*VAR04)) + VAR03*(CONST002*VAR16*(CONST057*VAR02 - CONST057*VAR22) - CONST061*VAR02**2 + CONST061*VAR22**2) + VAR06*(CONST022*VAR04*VAR07 + CONST039*VAR02*VAR04*VAR08) + VAR11*(CONST058*VAR08*VAR13 + VAR09*(CONST034*VAR08*VAR16 + CONST057*VAR07)) + VAR14*(-CONST039*VAR04*VAR09*VAR22 + CONST053*VAR04*VAR13) + VAR15*(CONST002*VAR16*(CONST059*VAR02 + CONST059*VAR22) + CONST012*VAR02**2 + CONST013*VAR02*VAR22 + CONST033*VAR22**2 - CONST059*VAR16**2) + VAR17*(CONST014*VAR02**2 + CONST014*VAR22**2 + CONST044*VAR02*VAR22) + VAR18*(CONST047*VAR04*VAR13 + VAR09*(CONST029*VAR12 + CONST046*VAR04*VAR22)) + VAR19*(-CONST052*VAR07*VAR09 + CONST052*VAR08*VAR13) -g_z = VAR01*(CONST001*VAR02**2 + CONST002*VAR22*(CONST003*VAR02 + CONST056*VAR16) + CONST008*VAR22**2 + CONST017*VAR16**2 + CONST056*VAR02*VAR16) + VAR03*(-CONST049*VAR08*VAR12 + CONST051*VAR04*VAR07) + VAR04*VAR17*(CONST024*VAR07 + CONST038*VAR02*VAR08) + VAR04*VAR19*(-CONST038*VAR09*VAR22 + CONST052*VAR13) + VAR06*(CONST002*VAR22*(CONST018*VAR16 + CONST037*VAR02) + CONST036*VAR22**2 + CONST045*VAR02*VAR16 - CONST063*VAR02**2) + VAR10*(CONST028*VAR07*VAR09 + CONST043*VAR08*VAR13) + VAR11*(CONST058*VAR04*VAR13 + VAR09*(CONST042*VAR04*VAR22 - CONST049*VAR12)) + VAR14*(CONST062*VAR08*VAR13 + VAR09*(-CONST039*VAR08*VAR16 + CONST054*VAR07)) + VAR15*(CONST048*VAR08*VAR12 + VAR04*(CONST020*VAR02*VAR08 + CONST021*VAR07)) + VAR18*(CONST006*VAR08*VAR13 + VAR09*(CONST046*VAR08*VAR16 + CONST050*VAR07)) + VAR21*(CONST010*VAR02**2 + CONST010*VAR22**2 + CONST040*VAR02*VAR22) +g_x = g_0*(CONST009*VAR06 + CONST009*VAR24 + CONST040*VAR08*VAR26) + g_1*y*(CONST038*VAR08*z - CONST052*VAR25) + g_10*(CONST029*VAR07*z + CONST043*VAR25*x) + g_2*(CONST001*VAR08*(CONST059*VAR17 + CONST064*VAR26) + CONST006*VAR06 - CONST045*VAR17*VAR26 + CONST063*VAR24) + g_3*(CONST041*VAR08*y*z - CONST049*VAR16*z + CONST057*VAR25*y) + g_4*(CONST000*VAR24 + CONST001*VAR08*(CONST002*VAR26 + CONST055*VAR17) + CONST007*VAR06 + CONST010*VAR15 + CONST056*VAR17*VAR26) + g_5*(CONST048*VAR16*x + y*(CONST019*VAR07 + CONST019*VAR26*x)) + g_6*(CONST005*VAR25*x + z*(CONST004*VAR07 + CONST046*VAR17*x)) + g_7*(CONST049*VAR16*x - CONST051*VAR07*y) + g_8*(CONST008*VAR25*x + z*(CONST039*VAR17*x - CONST054*VAR07)) + g_9*y*(CONST024*VAR07 + CONST038*VAR26*x) +g_y = g_1*(CONST052*VAR07*z - CONST052*VAR25*x) + g_2*(-CONST039*VAR26*x*y + CONST053*VAR07*y) + g_3*(CONST058*VAR07*z + x*(CONST034*VAR17*z + CONST057*VAR25)) + g_4*(CONST047*VAR07*y + x*(CONST030*VAR16 + CONST046*VAR26*y)) + g_5*(CONST001*VAR17*(CONST060*VAR08 + CONST060*VAR26) + CONST011*VAR06 + CONST012*VAR24 + CONST014*VAR08*VAR26 - CONST060*VAR15) + g_6*(CONST046*VAR25*y + z*(CONST031*VAR16 + CONST046*VAR08*y)) + g_7*(CONST001*VAR17*(CONST057*VAR08 - CONST057*VAR26) - CONST061*VAR06 + CONST061*VAR24) + g_8*(CONST021*VAR25*y + CONST039*VAR08*y*z) + g_9*(CONST027*VAR06 + CONST027*VAR24 + CONST044*VAR08*VAR26) +g_z = g_0*(CONST029*VAR25*x + CONST043*VAR07*z) + g_1*y*(-CONST038*VAR26*x + CONST052*VAR07) + g_10*(CONST009*VAR06 + CONST009*VAR24 + CONST040*VAR08*VAR26) + g_2*(CONST062*VAR07*z + x*(-CONST039*VAR17*z + CONST054*VAR25)) + g_3*(CONST058*VAR07*y + x*(CONST042*VAR26*y - CONST049*VAR16)) + g_4*(CONST005*VAR07*z + x*(CONST046*VAR17*z + CONST050*VAR25)) + g_5*(CONST048*VAR16*z + y*(CONST019*VAR08*z + CONST020*VAR25)) + g_6*(CONST001*VAR26*(CONST002*VAR08 + CONST056*VAR17) + CONST003*VAR06 + CONST007*VAR24 + CONST017*VAR15 + CONST056*VAR08*VAR17) + g_7*(-CONST049*VAR16*z + CONST051*VAR25*y) + g_8*(CONST001*VAR26*(CONST018*VAR17 + CONST037*VAR08) + CONST036*VAR24 + CONST045*VAR08*VAR17 - CONST063*VAR06) + g_9*y*(CONST024*VAR25 + CONST038*VAR08*z) diff --git a/notebooks/bwd_implementations/bwd_6.py b/notebooks/bwd_implementations/bwd_6.py index 418658a..b8610bf 100644 --- a/notebooks/bwd_implementations/bwd_6.py +++ b/notebooks/bwd_implementations/bwd_6.py @@ -1,62 +1,62 @@ # -------------------- variable and constant definitions -CONST000 = 1.63279380970164 -CONST001 = 2.00000000000000 -CONST002 = 3.26558761940328 -CONST003 = 4.00000000000000 -CONST004 = 3.00000000000000 -CONST005 = 6.53117523880657 -CONST006 = 7.15454401062709 -CONST007 = 8.94318001328386 -CONST008 = 8.38944649544891 -CONST009 = 10.3266947761614 -CONST010 = 9.79676285820985 -CONST011 = 3.26558761940328 +CONST000 = 2.00000000000000 +CONST001 = 3.26558761940328 +CONST002 = 4.00000000000000 +CONST003 = 3.00000000000000 +CONST004 = 6.53117523880657 +CONST005 = 1.63279380970164 +CONST006 = 8.94318001328386 +CONST007 = 8.38944649544891 +CONST008 = 10.3266947761614 +CONST009 = 9.79676285820985 +CONST010 = 7.15454401062709 +CONST011 = 14.5309475774982 CONST012 = 9.79676285820985 -CONST013 = 14.5309475774982 -CONST014 = 16.3279380970164 -CONST015 = 17.8863600265677 -CONST016 = 16.5227116418583 -CONST017 = 19.5935257164197 -CONST018 = 20.6533895523229 -CONST019 = 20.2812259244849 -CONST020 = 21.6333076527839 -CONST021 = 17.8863600265677 -CONST022 = 16.5227116418583 -CONST023 = 26.1247009552263 -CONST024 = 29.3902885746295 -CONST025 = 35.7727200531355 -CONST026 = 35.7727200531355 -CONST027 = 39.1870514328394 -CONST028 = 40.5624518489699 -CONST029 = 41.3067791046458 -CONST030 = 41.9472324772445 -CONST031 = 48.9838142910493 -CONST032 = 51.6334738808072 -CONST033 = 52.2494019104525 -CONST034 = 58.7805771492591 +CONST013 = 16.3279380970164 +CONST014 = 17.8863600265677 +CONST015 = 16.5227116418583 +CONST016 = 20.6533895523229 +CONST017 = 20.2812259244849 +CONST018 = 21.6333076527839 +CONST019 = 19.5935257164197 +CONST020 = 17.8863600265677 +CONST021 = 26.1247009552263 +CONST022 = 29.3902885746295 +CONST023 = 35.7727200531355 +CONST024 = 35.7727200531355 +CONST025 = 39.1870514328394 +CONST026 = 40.5624518489699 +CONST027 = 41.3067791046458 +CONST028 = 41.9472324772445 +CONST029 = 48.9838142910493 +CONST030 = 51.6334738808072 +CONST031 = 52.2494019104525 +CONST032 = 58.7805771492591 +CONST033 = 71.5454401062709 +CONST034 = 72.6547378874909 CONST035 = 71.5454401062709 -CONST036 = 72.6547378874909 -CONST037 = 71.5454401062709 -CONST038 = 78.3741028656788 -CONST039 = 81.1249036979398 -CONST040 = 82.6135582092915 -CONST041 = 82.6135582092915 -CONST042 = -3.26558761940328 -CONST043 = 104.498803820905 -CONST044 = 117.561154298518 -CONST045 = 145.309475774982 -CONST046 = 156.748205731358 -CONST047 = 167.788929908978 -CONST048 = 208.997607641810 -CONST049 = 214.636320318813 -CONST050 = -251.683394863467 -CONST051 = -214.636320318813 -CONST052 = -214.636320318813 -CONST053 = -167.788929908978 -CONST054 = -156.748205731358 -CONST055 = -145.309475774982 -CONST056 = -123.920337313937 -CONST057 = -117.561154298518 +CONST036 = 78.3741028656788 +CONST037 = 81.1249036979398 +CONST038 = 82.6135582092915 +CONST039 = 82.6135582092915 +CONST040 = -3.26558761940328 +CONST041 = 104.498803820905 +CONST042 = 117.561154298518 +CONST043 = 145.309475774982 +CONST044 = 156.748205731358 +CONST045 = 167.788929908978 +CONST046 = 208.997607641810 +CONST047 = 214.636320318813 +CONST048 = -251.683394863467 +CONST049 = -214.636320318813 +CONST050 = -214.636320318813 +CONST051 = 16.5227116418583 +CONST052 = -167.788929908978 +CONST053 = -156.748205731358 +CONST054 = -145.309475774982 +CONST055 = -123.920337313937 +CONST056 = -117.561154298518 +CONST057 = 3.26558761940328 CONST058 = -108.166538263920 CONST059 = -107.318160159406 CONST060 = -104.498803820905 @@ -87,35 +87,34 @@ CONST085 = -6.76040864149498 CONST086 = -3.38020432074749 CONST087 = -1.63279380970164 -VAR00 = z**4 -VAR01 = g_6 -VAR02 = x**2 -VAR03 = g_7 -VAR04 = y -VAR05 = x**4 -VAR06 = g_8 -VAR07 = z**3 -VAR08 = z -VAR09 = x -VAR10 = z**5 -VAR11 = g_0 -VAR12 = g_3 -VAR13 = y**3 +VAR00 = x**10 +VAR01 = x**9 +VAR02 = x**8 +VAR03 = x**7 +VAR04 = x**6 +VAR05 = x**5 +VAR06 = x**4 +VAR07 = x**3 +VAR08 = x**2 +VAR09 = y**10 +VAR10 = y**9 +VAR11 = y**8 +VAR12 = y**7 +VAR13 = y**6 VAR14 = y**5 -VAR15 = g_12 -VAR16 = x**3 -VAR17 = g_2 -VAR18 = x**5 -VAR19 = g_5 -VAR20 = y**2 -VAR21 = g_9 -VAR22 = g_4 -VAR23 = g_1 -VAR24 = g_11 -VAR25 = y**4 -VAR26 = g_10 -VAR27 = z**2 +VAR15 = y**4 +VAR16 = y**3 +VAR17 = y**2 +VAR18 = z**10 +VAR19 = z**9 +VAR20 = z**8 +VAR21 = z**7 +VAR22 = z**6 +VAR23 = z**5 +VAR24 = z**4 +VAR25 = z**3 +VAR26 = z**2 # -------------------- kernel implementations -g_x = VAR01*(CONST001*VAR09*(CONST028*VAR20*VAR27 + CONST076*VAR20**2 + CONST086*VAR27**2) + CONST003*VAR16*(CONST019*VAR20 + CONST086*VAR27) + CONST085*VAR18) + VAR03*(-CONST072*VAR04*VAR07*VAR09 + VAR08*(CONST063*VAR09*VAR13 - CONST072*VAR04*VAR16)) + VAR04*VAR23*(CONST030*VAR02**2 + CONST030*VAR27**2 + CONST050*VAR02*VAR27) + VAR04*VAR24*(CONST053*VAR07*VAR09 - CONST053*VAR08*VAR16) + VAR06*(CONST001*VAR09*(CONST077*VAR20**2 - CONST087*VAR27**2) + CONST003*VAR16*(-CONST077*VAR20 + CONST087*VAR27) + CONST083*VAR18) + VAR11*(CONST055*VAR02*VAR07 - CONST065*VAR02**2*VAR08 - CONST080*VAR10) + VAR12*(VAR04*(CONST031*VAR02**2 + CONST067*VAR02*VAR27 + CONST075*VAR27**2) + VAR13*(CONST064*VAR02 - CONST064*VAR27)) + VAR15*(-CONST055*VAR16*VAR27 + CONST065*VAR09*VAR27**2 + CONST080*VAR18) + VAR17*(-CONST074*VAR02**2*VAR08 + CONST084*VAR10 + VAR20*(CONST051*VAR02*VAR08 - CONST066*VAR07)) + VAR19*(CONST004*VAR02*(CONST018*VAR04*VAR27 + CONST072*VAR13) + CONST009*VAR04*VAR27**2 + CONST016*VAR14 + CONST032*VAR02**2*VAR04 + CONST072*VAR13*VAR27) + VAR21*(CONST054*VAR08*VAR09*VAR13 + VAR04*(CONST044*VAR08*VAR16 - CONST073*VAR07*VAR09)) + VAR22*(CONST004*VAR02*(CONST005*VAR07 + CONST069*VAR08*VAR20) + CONST014*VAR02**2*VAR08 - CONST042*VAR10 + CONST070*VAR07*VAR20 - CONST070*VAR08*VAR20**2) + VAR26*(CONST001*VAR09*(CONST007*VAR27**2 + CONST059*VAR20*VAR27) + CONST003*VAR16*(CONST007*VAR27 + CONST015*VAR20) + CONST082*VAR18) -g_y = CONST001*VAR04*VAR17*(-CONST066*VAR07*VAR09 + CONST066*VAR08*VAR16) + VAR01*(CONST020*VAR14 + CONST028*VAR02**2*VAR04 + CONST028*VAR04*VAR27**2 + CONST058*VAR13*VAR27 + VAR02*(CONST039*VAR04*VAR27 + CONST058*VAR13)) + VAR03*(CONST009*VAR10 + VAR07*(CONST018*VAR02 + CONST056*VAR20) + VAR08*(CONST009*VAR02**2 + CONST041*VAR20**2 + CONST056*VAR02*VAR20)) + VAR06*(CONST060*VAR02*VAR13 - CONST060*VAR13*VAR27 + CONST069*VAR04*VAR27**2 - CONST070*VAR02**2*VAR04) + VAR12*(CONST004*VAR20*(-CONST064*VAR09*VAR27 + CONST078*VAR16) + CONST010*VAR18 + CONST075*VAR09*VAR27**2 + CONST079*VAR16*VAR27) + VAR19*(CONST009*VAR18 + VAR09*(CONST009*VAR27**2 + CONST056*VAR20*VAR27 - CONST063*VAR20**2) + VAR16*(CONST018*VAR27 + CONST056*VAR20)) + VAR21*(CONST004*VAR20*(CONST064*VAR02*VAR08 - CONST077*VAR07) + CONST024*VAR02**2*VAR08 - CONST079*VAR02*VAR07 + CONST083*VAR10) + VAR22*(CONST061*VAR04*VAR08*VAR16 + VAR09*(CONST048*VAR08*VAR13 + CONST060*VAR04*VAR07)) + VAR23*(CONST008*VAR18 + CONST030*VAR09*VAR27**2 + CONST062*VAR16*VAR27) + VAR24*(CONST008*VAR10 + CONST030*VAR02**2*VAR08 + CONST062*VAR02*VAR07) + VAR26*(CONST026*VAR02**2*VAR04 + CONST052*VAR02*VAR04*VAR27 - CONST074*VAR04*VAR27**2) -g_z = VAR01*(CONST039*VAR07*VAR20 + CONST068*VAR08*VAR20**2 + CONST085*VAR02**2*VAR08 + CONST085*VAR10 + VAR02*(CONST039*VAR08*VAR20 + CONST081*VAR07)) + VAR03*(CONST004*VAR27*(CONST018*VAR02*VAR04 + CONST072*VAR13) + CONST009*VAR02**2*VAR04 + CONST022*VAR14 + CONST032*VAR04*VAR27**2 + CONST072*VAR02*VAR13) + VAR04*VAR23*(-CONST053*VAR07*VAR09 + CONST053*VAR08*VAR16) + VAR04*VAR24*(CONST030*VAR02**2 + CONST030*VAR27**2 + CONST050*VAR02*VAR27) + VAR06*(CONST005*VAR02*VAR07 + CONST042*VAR02**2*VAR08 + CONST061*VAR07*VAR20 - CONST070*VAR08*VAR20**2 - CONST083*VAR10) + VAR11*(CONST055*VAR16*VAR27 - CONST065*VAR09*VAR27**2 - CONST080*VAR18) + VAR12*(-CONST054*VAR08*VAR09*VAR13 + VAR04*(CONST057*VAR07*VAR09 + CONST073*VAR08*VAR16)) + VAR15*(CONST055*VAR02*VAR07 - CONST065*VAR02**2*VAR08 - CONST080*VAR10) + VAR17*(CONST074*VAR09*VAR27**2 - CONST084*VAR18 + VAR20*(-CONST051*VAR09*VAR27 + CONST066*VAR16)) + VAR19*(-CONST072*VAR04*VAR08*VAR16 + VAR09*(CONST063*VAR08*VAR13 - CONST072*VAR04*VAR07)) + VAR21*(VAR04*(CONST024*VAR02**2 - CONST067*VAR02*VAR27 + CONST071*VAR27**2) + VAR13*(CONST064*VAR02 - CONST064*VAR27)) + VAR22*(CONST011*VAR18 + VAR09*(CONST014*VAR27**2 + CONST054*VAR20*VAR27 - CONST070*VAR20**2) + VAR16*(CONST069*VAR20 - CONST079*VAR27)) + VAR26*(CONST021*VAR02**2*VAR08 + CONST037*VAR07*VAR20 + CONST082*VAR10 + VAR02*(CONST052*VAR08*VAR20 - CONST074*VAR07)) +g_x = g_0*(CONST054*VAR08*VAR25 - CONST065*VAR06*z - CONST080*VAR23) + g_1*y*(CONST028*VAR06 + CONST028*VAR24 + CONST048*VAR08*VAR26) + g_10*(CONST000*x*(CONST006*VAR24 + CONST059*VAR17*VAR26) + CONST002*VAR07*(CONST006*VAR26 + CONST014*VAR17) + CONST082*VAR05) + g_11*y*(-CONST052*VAR07*z + CONST052*VAR25*x) + g_12*(-CONST054*VAR07*VAR26 + CONST065*VAR24*x + CONST080*VAR05) + g_2*(-CONST074*VAR06*z + CONST084*VAR23 + VAR17*(CONST049*VAR08*z - CONST066*VAR25)) + g_3*(VAR16*(CONST064*VAR08 - CONST064*VAR26) + y*(CONST029*VAR06 + CONST067*VAR08*VAR26 + CONST075*VAR24)) + g_4*(CONST003*VAR08*(CONST004*VAR25 + CONST069*VAR17*z) + CONST013*VAR06*z - CONST040*VAR23 - CONST070*VAR15*z + CONST070*VAR17*VAR25) + g_5*(CONST003*VAR08*(CONST016*VAR26*y + CONST072*VAR16) + CONST008*VAR24*y + CONST015*VAR14 + CONST030*VAR06*y + CONST072*VAR16*VAR26) + g_6*(CONST000*x*(CONST026*VAR17*VAR26 + CONST076*VAR15 + CONST086*VAR24) + CONST002*VAR07*(CONST017*VAR17 + CONST086*VAR26) + CONST085*VAR05) + g_7*(-CONST072*VAR25*x*y + z*(CONST063*VAR16*x - CONST072*VAR07*y)) + g_8*(CONST000*x*(CONST077*VAR15 - CONST087*VAR24) + CONST002*VAR07*(-CONST077*VAR17 + CONST087*VAR26) + CONST083*VAR05) + g_9*(CONST053*VAR16*x*z + y*(CONST042*VAR07*z - CONST073*VAR25*x)) +g_y = CONST000*g_2*y*(CONST066*VAR07*z - CONST066*VAR25*x) + g_1*(CONST007*VAR05 + CONST028*VAR24*x + CONST062*VAR07*VAR26) + g_10*(CONST024*VAR06*y + CONST050*VAR08*VAR26*y - CONST074*VAR24*y) + g_11*(CONST007*VAR23 + CONST028*VAR06*z + CONST062*VAR08*VAR25) + g_3*(CONST003*VAR17*(-CONST064*VAR26*x + CONST078*VAR07) + CONST009*VAR05 + CONST075*VAR24*x + CONST079*VAR07*VAR26) + g_4*(CONST061*VAR07*y*z + x*(CONST046*VAR16*z + CONST060*VAR25*y)) + g_5*(CONST008*VAR05 + VAR07*(CONST016*VAR26 + CONST055*VAR17) + x*(CONST008*VAR24 + CONST055*VAR17*VAR26 - CONST063*VAR15)) + g_6*(CONST018*VAR14 + CONST026*VAR06*y + CONST026*VAR24*y + CONST058*VAR16*VAR26 + VAR08*(CONST037*VAR26*y + CONST058*VAR16)) + g_7*(CONST008*VAR23 + VAR25*(CONST016*VAR08 + CONST055*VAR17) + z*(CONST008*VAR06 + CONST039*VAR15 + CONST055*VAR08*VAR17)) + g_8*(CONST060*VAR08*VAR16 - CONST060*VAR16*VAR26 + CONST069*VAR24*y - CONST070*VAR06*y) + g_9*(CONST003*VAR17*(CONST064*VAR08*z - CONST077*VAR25) + CONST022*VAR06*z - CONST079*VAR08*VAR25 + CONST083*VAR23) +g_z = g_0*(CONST054*VAR07*VAR26 - CONST065*VAR24*x - CONST080*VAR05) + g_1*y*(CONST052*VAR07*z - CONST052*VAR25*x) + g_10*(CONST020*VAR06*z + CONST035*VAR17*VAR25 + CONST082*VAR23 + VAR08*(CONST050*VAR17*z - CONST074*VAR25)) + g_11*y*(CONST028*VAR06 + CONST028*VAR24 + CONST048*VAR08*VAR26) + g_12*(CONST054*VAR08*VAR25 - CONST065*VAR06*z - CONST080*VAR23) + g_2*(CONST074*VAR24*x - CONST084*VAR05 + VAR17*(-CONST049*VAR26*x + CONST066*VAR07)) + g_3*(-CONST053*VAR16*x*z + y*(CONST056*VAR25*x + CONST073*VAR07*z)) + g_4*(CONST057*VAR05 + VAR07*(CONST069*VAR17 - CONST079*VAR26) + x*(CONST013*VAR24 + CONST053*VAR17*VAR26 - CONST070*VAR15)) + g_5*(-CONST072*VAR07*y*z + x*(CONST063*VAR16*z - CONST072*VAR25*y)) + g_6*(CONST037*VAR17*VAR25 + CONST068*VAR15*z + CONST085*VAR06*z + CONST085*VAR23 + VAR08*(CONST037*VAR17*z + CONST081*VAR25)) + g_7*(CONST003*VAR26*(CONST016*VAR08*y + CONST072*VAR16) + CONST008*VAR06*y + CONST030*VAR24*y + CONST051*VAR14 + CONST072*VAR08*VAR16) + g_8*(CONST004*VAR08*VAR25 + CONST040*VAR06*z + CONST061*VAR17*VAR25 - CONST070*VAR15*z - CONST083*VAR23) + g_9*(VAR16*(CONST064*VAR08 - CONST064*VAR26) + y*(CONST022*VAR06 - CONST067*VAR08*VAR26 + CONST071*VAR24)) diff --git a/notebooks/bwd_implementations/bwd_7.py b/notebooks/bwd_implementations/bwd_7.py index f03f5d1..8b42d50 100644 --- a/notebooks/bwd_implementations/bwd_7.py +++ b/notebooks/bwd_implementations/bwd_7.py @@ -14,10 +14,10 @@ CONST012 = 16.5555704843566 CONST013 = 17.5477863187212 CONST014 = 20.4939015319192 -CONST015 = 22.0740939791422 -CONST016 = 23.5310632462709 +CONST015 = 532.447180478965 +CONST016 = 22.0740939791422 CONST017 = 23.5310632462709 -CONST018 = 532.447180478965 +CONST018 = 23.5310632462709 CONST019 = 20.4939015319192 CONST020 = 27.1108834234519 CONST021 = 29.9501539019418 @@ -33,10 +33,10 @@ CONST031 = 44.1481879582843 CONST032 = -4.99169231699030 CONST033 = 47.0621264925418 -CONST034 = 44.3705983732471 -CONST035 = 47.0621264925417 -CONST036 = 562.781179722634 -CONST037 = 50.8329064189723 +CONST034 = 562.781179722634 +CONST035 = 50.8329064189723 +CONST036 = 44.3705983732471 +CONST037 = 47.0621264925417 CONST038 = 55.1852349478554 CONST039 = 56.2781179722634 CONST040 = 56.2781179722634 @@ -129,8 +129,8 @@ CONST127 = -55.1852349478554 CONST128 = -50.8329064189723 CONST129 = -50.8329064189723 -CONST130 = -47.0621264925418 -CONST131 = -562.781179722634 +CONST130 = -562.781179722634 +CONST131 = -47.0621264925418 CONST132 = -50.8329064189724 CONST133 = -44.1481879582843 CONST134 = -44.3705983732471 @@ -156,40 +156,34 @@ CONST154 = -4.80325817154356 CONST155 = -3.32779487799353 CONST156 = -1.60108605718119 -VAR00 = z**4 -VAR01 = g_6 -VAR02 = g_14 -VAR03 = z**6 -VAR04 = x**2 -VAR05 = g_7 -VAR06 = y -VAR07 = y**6 -VAR08 = x**4 -VAR09 = g_8 -VAR10 = z**3 -VAR11 = x**6 -VAR12 = z -VAR13 = x -VAR14 = z**5 -VAR15 = g_0 -VAR16 = g_3 -VAR17 = y**3 -VAR18 = y**5 -VAR19 = g_12 -VAR20 = x**3 -VAR21 = g_2 -VAR22 = x**5 -VAR23 = g_5 -VAR24 = g_13 -VAR25 = y**2 -VAR26 = g_9 -VAR27 = g_4 -VAR28 = g_1 -VAR29 = g_11 -VAR30 = y**4 -VAR31 = g_10 -VAR32 = z**2 +VAR00 = x**10 +VAR01 = x**9 +VAR02 = x**8 +VAR03 = x**7 +VAR04 = x**6 +VAR05 = x**5 +VAR06 = x**4 +VAR07 = x**3 +VAR08 = x**2 +VAR09 = y**10 +VAR10 = y**9 +VAR11 = y**8 +VAR12 = y**7 +VAR13 = y**6 +VAR14 = y**5 +VAR15 = y**4 +VAR16 = y**3 +VAR17 = y**2 +VAR18 = z**10 +VAR19 = z**9 +VAR20 = z**8 +VAR21 = z**7 +VAR22 = z**6 +VAR23 = z**5 +VAR24 = z**4 +VAR25 = z**3 +VAR26 = z**2 # -------------------- kernel implementations -g_x = VAR01*(CONST001*VAR04*(CONST116*VAR25**2 - CONST116*VAR25*VAR32 + CONST154*VAR32**2) + CONST003*VAR04**2*(CONST026*VAR25 + CONST113*VAR32) + CONST014*VAR25**3 + CONST027*VAR25*VAR32**2 + CONST116*VAR25**2*VAR32 + CONST149*VAR04**3 + CONST156*VAR32**3) + VAR02*(-CONST069*VAR10*VAR20 + CONST109*VAR12*VAR22 + CONST109*VAR13*VAR14) + VAR05*(CONST114*VAR13*VAR18 + VAR06*(CONST110*VAR20*VAR32 + CONST128*VAR22 + CONST129*VAR13*VAR32**2) + VAR17*(CONST072*VAR20 + CONST073*VAR13*VAR32)) + VAR06*VAR24*(CONST079*VAR13*VAR32**2 + CONST125*VAR22 - CONST131*VAR20*VAR32) + VAR06*VAR28*(CONST039*VAR14 + CONST089*VAR04**2*VAR12 + CONST131*VAR04*VAR10) + VAR09*(CONST075*VAR13*VAR14 + VAR10*(-CONST100*VAR13*VAR25 + CONST145*VAR20) + VAR12*(CONST067*VAR20*VAR25 + CONST097*VAR22 + CONST100*VAR13*VAR25**2)) + VAR15*(CONST082*VAR04*VAR32**2 - CONST084*VAR04**2*VAR32 + CONST146*VAR04**3 - CONST146*VAR32**3) + VAR16*(VAR06*(-CONST091*VAR04**2*VAR12 + CONST133*VAR14) + VAR17*(CONST044*VAR04*VAR12 + CONST066*VAR10)) + VAR19*(CONST022*VAR13*VAR14 + VAR10*(CONST024*VAR20 + CONST045*VAR13*VAR25) + VAR12*(-CONST044*VAR20*VAR25 + CONST126*VAR22)) + VAR21*(CONST001*VAR04*(CONST091*VAR25*VAR32 - CONST150*VAR32**2) + CONST003*VAR04**2*(CONST012*VAR32 + CONST015*VAR25) + CONST055*VAR25*VAR32**2 + CONST147*VAR04**3 + CONST150*VAR32**3) + VAR23*(CONST001*VAR04*(CONST106*VAR12*VAR17 - CONST130*VAR06*VAR10) + CONST057*VAR04**2*VAR06*VAR12 + CONST107*VAR10*VAR17 - CONST117*VAR12*VAR18 - CONST143*VAR06*VAR14) + VAR26*(-CONST085*VAR17*VAR20 + CONST117*VAR13*VAR18 + VAR06*(CONST017*VAR13*VAR32**2 + CONST119*VAR22 + CONST130*VAR20*VAR32)) + VAR27*(CONST001*VAR04*(CONST122*VAR25*VAR32 + CONST134*VAR25**2 - CONST137*VAR32**2) + CONST003*VAR04**2*(CONST000*VAR32 - CONST139*VAR25) - CONST032*VAR32**3 - CONST105*VAR25**2*VAR32 + CONST111*VAR25*VAR32**2 + CONST148*VAR04**3) + VAR29*(VAR06*(CONST054*VAR13*VAR32**2 - CONST091*VAR20*VAR32 + CONST121*VAR22) + VAR17*(CONST044*VAR13*VAR32 - CONST101*VAR20)) + VAR31*(CONST155*VAR13*VAR14 + VAR10*(-CONST105*VAR13*VAR25 + CONST139*VAR20) + VAR12*(-CONST056*VAR20*VAR25 + CONST081*VAR13*VAR25**2 + CONST140*VAR22)) -g_y = VAR01*(CONST048*VAR06*VAR22 + VAR13*(CONST058*VAR18 + CONST074*VAR17*VAR32 - CONST116*VAR06*VAR32**2) + VAR20*(CONST074*VAR17 - CONST100*VAR06*VAR32)) + VAR05*(CONST001*VAR25*(-CONST112*VAR04*VAR32 - CONST128*VAR04**2 - CONST128*VAR32**2) + CONST003*VAR25**2*(CONST135*VAR04 + CONST136*VAR32) + CONST020*VAR25**3 + CONST050*VAR04**3 + CONST050*VAR32**3 + CONST141*VAR04**2*VAR32 + CONST142*VAR04*VAR32**2) + VAR09*(CONST048*VAR06*VAR14 + VAR10*(CONST074*VAR17 - CONST100*VAR04*VAR06) + VAR12*(CONST049*VAR04**2*VAR06 + CONST059*VAR18 + CONST074*VAR04*VAR17)) + VAR16*(CONST001*VAR25*(CONST066*VAR10*VAR13 + CONST101*VAR12*VAR20) - CONST133*VAR12*VAR22 + CONST133*VAR13*VAR14) + VAR19*(CONST030*VAR06*VAR14 + CONST045*VAR04*VAR06*VAR10 - CONST092*VAR04**2*VAR06*VAR12) + VAR21*(CONST030*VAR06*VAR22 + CONST045*VAR06*VAR20*VAR32 - CONST092*VAR06*VAR13*VAR32**2) + VAR23*(-CONST143*VAR12*VAR22 + VAR13*(CONST061*VAR10*VAR25 - CONST062*VAR12*VAR25**2 - CONST143*VAR14) + VAR20*(CONST062*VAR12*VAR25 - CONST130*VAR10)) + VAR24*(CONST076*VAR04**3 - CONST076*VAR32**3 - CONST102*VAR04**2*VAR32 + CONST102*VAR04*VAR32**2) + VAR26*(CONST001*VAR25*(-CONST124*VAR04**2 + CONST124*VAR32**2) + CONST003*VAR25**2*(CONST138*VAR04 - CONST138*VAR32) + CONST009*VAR04*VAR32**2 + CONST152*VAR04**3 + CONST152*VAR04**2*VAR32 - CONST152*VAR32**3) + VAR27*(-CONST123*VAR06*VAR22 + VAR13*(CONST093*VAR06*VAR32**2 - CONST144*VAR17*VAR32) + VAR20*(CONST096*VAR17 + CONST104*VAR06*VAR32)) + VAR28*(CONST039*VAR13*VAR14 + CONST095*VAR10*VAR20 - CONST125*VAR12*VAR22) + VAR29*(CONST001*VAR25*(CONST025*VAR04**2 + CONST025*VAR32**2 + CONST092*VAR04*VAR32) - CONST126*VAR04**2*VAR32 - CONST126*VAR04*VAR32**2 + CONST151*VAR04**3 + CONST151*VAR32**3) + VAR31*(CONST123*VAR06*VAR14 + VAR10*(-CONST096*VAR17 - CONST105*VAR04*VAR06) + VAR12*(-CONST093*VAR04**2*VAR06 + CONST144*VAR04*VAR17)) -g_z = VAR01*(CONST097*VAR12*VAR22 + VAR13*(CONST075*VAR14 - CONST100*VAR10*VAR25 + CONST100*VAR12*VAR25**2) + VAR20*(-CONST100*VAR12*VAR25 + CONST145*VAR10)) + VAR02*(-CONST082*VAR04**2*VAR32 + CONST084*VAR04*VAR32**2 + CONST146*VAR04**3 - CONST146*VAR32**3) + VAR05*(CONST115*VAR12*VAR18 + VAR06*(CONST112*VAR04*VAR10 + CONST128*VAR14 + CONST132*VAR04**2*VAR12) + VAR17*(CONST072*VAR10 + CONST073*VAR04*VAR12)) + VAR06*VAR24*(-CONST079*VAR04**2*VAR12 - CONST125*VAR14 + CONST131*VAR04*VAR10) + VAR06*VAR28*(-CONST079*VAR13*VAR32**2 - CONST125*VAR22 + CONST131*VAR20*VAR32) + VAR09*(CONST001*VAR32*(-CONST116*VAR04*VAR25 + CONST116*VAR25**2 + CONST154*VAR04**2) + CONST003*VAR32**2*(CONST026*VAR25 + CONST154*VAR04) + CONST019*VAR25**3 + CONST029*VAR04**2*VAR25 + CONST094*VAR04**3 + CONST116*VAR04*VAR25**2 + CONST149*VAR32**3) + VAR15*(CONST069*VAR10*VAR20 - CONST109*VAR12*VAR22 - CONST109*VAR13*VAR14) + VAR16*(VAR06*(CONST091*VAR13*VAR32**2 - CONST133*VAR22) + VAR17*(-CONST045*VAR13*VAR32 + CONST101*VAR20)) + VAR19*(CONST001*VAR32*(CONST091*VAR04*VAR25 - CONST098*VAR04**2) + CONST003*VAR32**2*(CONST012*VAR04 + CONST015*VAR25) + CONST055*VAR04**2*VAR25 + CONST098*VAR04**3 + CONST153*VAR32**3) + VAR21*(CONST022*VAR12*VAR22 + VAR13*(-CONST044*VAR10*VAR25 + CONST127*VAR14) + VAR20*(CONST025*VAR10 + CONST045*VAR12*VAR25)) + VAR23*(-CONST143*VAR06*VAR22 + VAR13*(CONST057*VAR06*VAR32**2 + CONST061*VAR17*VAR32 - CONST117*VAR18) + VAR20*(CONST064*VAR06*VAR32 + CONST106*VAR17)) + VAR26*(CONST085*VAR10*VAR17 - CONST117*VAR12*VAR18 + VAR06*(CONST035*VAR04*VAR10 - CONST119*VAR14 + CONST143*VAR04**2*VAR12)) + VAR27*(CONST004*VAR12*VAR22 + VAR13*(CONST056*VAR10*VAR25 - CONST081*VAR12*VAR25**2 - CONST140*VAR14) + VAR20*(CONST104*VAR12*VAR25 - CONST139*VAR10)) + VAR29*(VAR06*(CONST054*VAR04**2*VAR12 - CONST091*VAR04*VAR10 + CONST121*VAR14) + VAR17*(CONST044*VAR04*VAR12 - CONST101*VAR10)) + VAR31*(CONST001*VAR32*(-CONST123*VAR04*VAR25 - CONST134*VAR25**2 + CONST137*VAR04**2) + CONST003*VAR32**2*(CONST080*VAR04 + CONST139*VAR25) + CONST032*VAR04**3 + CONST105*VAR04*VAR25**2 - CONST111*VAR04**2*VAR25 - CONST148*VAR32**3) +g_x = g_0*(CONST082*VAR08*VAR24 - CONST084*VAR06*VAR26 + CONST146*VAR04 - CONST146*VAR22) + g_1*y*(CONST039*VAR23 + CONST089*VAR06*z + CONST130*VAR08*VAR25) + g_10*(CONST155*VAR23*x + VAR25*(-CONST105*VAR17*x + CONST139*VAR07) + z*(-CONST056*VAR07*VAR17 + CONST081*VAR15*x + CONST140*VAR05)) + g_11*(VAR16*(CONST044*VAR26*x - CONST101*VAR07) + y*(CONST054*VAR24*x - CONST091*VAR07*VAR26 + CONST121*VAR05)) + g_12*(CONST022*VAR23*x + VAR25*(CONST024*VAR07 + CONST045*VAR17*x) + z*(-CONST044*VAR07*VAR17 + CONST126*VAR05)) + g_13*y*(CONST079*VAR24*x + CONST125*VAR05 - CONST130*VAR07*VAR26) + g_14*(-CONST069*VAR07*VAR25 + CONST109*VAR05*z + CONST109*VAR23*x) + g_2*(CONST001*VAR08*(CONST091*VAR17*VAR26 - CONST150*VAR24) + CONST003*VAR06*(CONST012*VAR26 + CONST016*VAR17) + CONST055*VAR17*VAR24 + CONST147*VAR04 + CONST150*VAR22) + g_3*(VAR16*(CONST044*VAR08*z + CONST066*VAR25) + y*(-CONST091*VAR06*z + CONST133*VAR23)) + g_4*(CONST001*VAR08*(CONST122*VAR17*VAR26 + CONST134*VAR15 - CONST137*VAR24) + CONST003*VAR06*(CONST000*VAR26 - CONST139*VAR17) - CONST032*VAR22 - CONST105*VAR15*VAR26 + CONST111*VAR17*VAR24 + CONST148*VAR04) + g_5*(CONST001*VAR08*(CONST106*VAR16*z - CONST131*VAR25*y) + CONST057*VAR06*y*z + CONST107*VAR16*VAR25 - CONST117*VAR14*z - CONST143*VAR23*y) + g_6*(CONST001*VAR08*(CONST116*VAR15 - CONST116*VAR17*VAR26 + CONST154*VAR24) + CONST003*VAR06*(CONST026*VAR17 + CONST113*VAR26) + CONST014*VAR13 + CONST027*VAR17*VAR24 + CONST116*VAR15*VAR26 + CONST149*VAR04 + CONST156*VAR22) + g_7*(CONST114*VAR14*x + VAR16*(CONST072*VAR07 + CONST073*VAR26*x) + y*(CONST110*VAR07*VAR26 + CONST128*VAR05 + CONST129*VAR24*x)) + g_8*(CONST075*VAR23*x + VAR25*(-CONST100*VAR17*x + CONST145*VAR07) + z*(CONST067*VAR07*VAR17 + CONST097*VAR05 + CONST100*VAR15*x)) + g_9*(-CONST085*VAR07*VAR16 + CONST117*VAR14*x + y*(CONST018*VAR24*x + CONST119*VAR05 + CONST131*VAR07*VAR26)) +g_y = g_1*(CONST039*VAR23*x + CONST095*VAR07*VAR25 - CONST125*VAR05*z) + g_10*(CONST123*VAR23*y + VAR25*(-CONST096*VAR16 - CONST105*VAR08*y) + z*(-CONST093*VAR06*y + CONST144*VAR08*VAR16)) + g_11*(CONST001*VAR17*(CONST025*VAR06 + CONST025*VAR24 + CONST092*VAR08*VAR26) - CONST126*VAR06*VAR26 - CONST126*VAR08*VAR24 + CONST151*VAR04 + CONST151*VAR22) + g_12*(CONST030*VAR23*y + CONST045*VAR08*VAR25*y - CONST092*VAR06*y*z) + g_13*(CONST076*VAR04 - CONST076*VAR22 - CONST102*VAR06*VAR26 + CONST102*VAR08*VAR24) + g_2*(CONST030*VAR05*y + CONST045*VAR07*VAR26*y - CONST092*VAR24*x*y) + g_3*(CONST001*VAR17*(CONST066*VAR25*x + CONST101*VAR07*z) - CONST133*VAR05*z + CONST133*VAR23*x) + g_4*(-CONST123*VAR05*y + VAR07*(CONST096*VAR16 + CONST104*VAR26*y) + x*(CONST093*VAR24*y - CONST144*VAR16*VAR26)) + g_5*(-CONST143*VAR05*z + VAR07*(CONST062*VAR17*z - CONST131*VAR25) + x*(CONST061*VAR17*VAR25 - CONST062*VAR15*z - CONST143*VAR23)) + g_6*(CONST048*VAR05*y + VAR07*(CONST074*VAR16 - CONST100*VAR26*y) + x*(CONST058*VAR14 + CONST074*VAR16*VAR26 - CONST116*VAR24*y)) + g_7*(CONST001*VAR17*(-CONST112*VAR08*VAR26 - CONST128*VAR06 - CONST128*VAR24) + CONST003*VAR15*(CONST135*VAR08 + CONST136*VAR26) + CONST020*VAR13 + CONST050*VAR04 + CONST050*VAR22 + CONST141*VAR06*VAR26 + CONST142*VAR08*VAR24) + g_8*(CONST048*VAR23*y + VAR25*(CONST074*VAR16 - CONST100*VAR08*y) + z*(CONST049*VAR06*y + CONST059*VAR14 + CONST074*VAR08*VAR16)) + g_9*(CONST001*VAR17*(-CONST124*VAR06 + CONST124*VAR24) + CONST003*VAR15*(CONST138*VAR08 - CONST138*VAR26) + CONST009*VAR08*VAR24 + CONST152*VAR04 + CONST152*VAR06*VAR26 - CONST152*VAR22) +g_z = g_0*(CONST069*VAR07*VAR25 - CONST109*VAR05*z - CONST109*VAR23*x) + g_1*y*(-CONST079*VAR24*x - CONST125*VAR05 + CONST130*VAR07*VAR26) + g_10*(CONST001*VAR26*(-CONST123*VAR08*VAR17 - CONST134*VAR15 + CONST137*VAR06) + CONST003*VAR24*(CONST080*VAR08 + CONST139*VAR17) + CONST032*VAR04 + CONST105*VAR08*VAR15 - CONST111*VAR06*VAR17 - CONST148*VAR22) + g_11*(VAR16*(CONST044*VAR08*z - CONST101*VAR25) + y*(CONST054*VAR06*z - CONST091*VAR08*VAR25 + CONST121*VAR23)) + g_12*(CONST001*VAR26*(CONST091*VAR08*VAR17 - CONST098*VAR06) + CONST003*VAR24*(CONST012*VAR08 + CONST016*VAR17) + CONST055*VAR06*VAR17 + CONST098*VAR04 + CONST153*VAR22) + g_13*y*(-CONST079*VAR06*z - CONST125*VAR23 + CONST130*VAR08*VAR25) + g_14*(-CONST082*VAR06*VAR26 + CONST084*VAR08*VAR24 + CONST146*VAR04 - CONST146*VAR22) + g_2*(CONST022*VAR05*z + VAR07*(CONST025*VAR25 + CONST045*VAR17*z) + x*(-CONST044*VAR17*VAR25 + CONST127*VAR23)) + g_3*(VAR16*(-CONST045*VAR26*x + CONST101*VAR07) + y*(CONST091*VAR24*x - CONST133*VAR05)) + g_4*(CONST004*VAR05*z + VAR07*(CONST104*VAR17*z - CONST139*VAR25) + x*(CONST056*VAR17*VAR25 - CONST081*VAR15*z - CONST140*VAR23)) + g_5*(-CONST143*VAR05*y + VAR07*(CONST064*VAR26*y + CONST106*VAR16) + x*(CONST057*VAR24*y + CONST061*VAR16*VAR26 - CONST117*VAR14)) + g_6*(CONST097*VAR05*z + VAR07*(-CONST100*VAR17*z + CONST145*VAR25) + x*(CONST075*VAR23 + CONST100*VAR15*z - CONST100*VAR17*VAR25)) + g_7*(CONST115*VAR14*z + VAR16*(CONST072*VAR25 + CONST073*VAR08*z) + y*(CONST112*VAR08*VAR25 + CONST128*VAR23 + CONST132*VAR06*z)) + g_8*(CONST001*VAR26*(-CONST116*VAR08*VAR17 + CONST116*VAR15 + CONST154*VAR06) + CONST003*VAR24*(CONST026*VAR17 + CONST154*VAR08) + CONST019*VAR13 + CONST029*VAR06*VAR17 + CONST094*VAR04 + CONST116*VAR08*VAR15 + CONST149*VAR22) + g_9*(CONST085*VAR16*VAR25 - CONST117*VAR14*z + y*(CONST037*VAR08*VAR25 - CONST119*VAR23 + CONST143*VAR06*z)) diff --git a/notebooks/bwd_implementations/bwd_8.py b/notebooks/bwd_implementations/bwd_8.py index 2428c99..53794e3 100644 --- a/notebooks/bwd_implementations/bwd_8.py +++ b/notebooks/bwd_implementations/bwd_8.py @@ -11,15 +11,15 @@ CONST009 = 6.00000000000000 CONST010 = 12.9361412329953 CONST011 = 13.5675393863442 -CONST012 = 13.1367135230810 -CONST013 = 15.0965641786467 +CONST012 = 15.0965641786467 +CONST013 = 13.1367135230810 CONST014 = 10.3359109268366 CONST015 = 13.1367135230810 -CONST016 = 525.468540923241 +CONST016 = 20.6718218536732 CONST017 = 19.4042118494929 -CONST018 = 20.6718218536732 -CONST019 = 24.7386337537060 -CONST020 = -489.184589393411 +CONST018 = 525.468540923241 +CONST019 = -489.184589393411 +CONST020 = 24.7386337537060 CONST021 = 1050.93708184648 CONST022 = 26.4189873126318 CONST023 = 26.2734270461621 @@ -30,23 +30,23 @@ CONST028 = 550.332663067587 CONST029 = 39.4101405692431 CONST030 = -978.369178786822 -CONST031 = 47.4863878522046 -CONST032 = 48.5105296237322 -CONST033 = 1585.13923875791 -CONST034 = 48.9184589393411 -CONST035 = 51.7445649319810 -CONST036 = 52.8379746252636 +CONST031 = 48.5105296237322 +CONST032 = 1585.13923875791 +CONST033 = 51.7445649319810 +CONST034 = 52.8379746252636 +CONST035 = 48.9184589393411 +CONST036 = 47.4863878522046 CONST037 = 1085.27064731784 CONST038 = 61.1480736741764 CONST039 = 61.1480736741764 -CONST040 = 65.6835676154051 -CONST041 = 1085.40315090753 +CONST040 = 1085.40315090753 +CONST041 = 65.6835676154051 CONST042 = 67.8376969317208 -CONST043 = 1085.27064731784 -CONST044 = -1467.55376818023 -CONST045 = 70.0624721230988 -CONST046 = 582.126355484786 -CONST047 = 72.3513764878561 +CONST043 = -1467.55376818023 +CONST044 = 70.0624721230988 +CONST045 = -12.2296147348353 +CONST046 = 72.3513764878561 +CONST047 = 582.126355484786 CONST048 = -437.890450769368 CONST049 = -434.108258927137 CONST050 = -434.108258927137 @@ -60,8 +60,8 @@ CONST058 = 97.0210592474644 CONST059 = 97.0210592474644 CONST060 = 103.489129863962 -CONST061 = -407.026181590325 -CONST062 = 103.489129863962 +CONST061 = 103.489129863962 +CONST062 = -407.026181590325 CONST063 = 108.231522672464 CONST064 = 108.231522672464 CONST065 = 110.066532613517 @@ -104,10 +104,10 @@ CONST102 = -814.052363180650 CONST103 = 216.463045344927 CONST104 = 217.054129463568 -CONST105 = 220.133065227035 -CONST106 = -291.063177742393 -CONST107 = 220.133065227035 -CONST108 = 216.463045344927 +CONST105 = 216.463045344927 +CONST106 = 220.133065227035 +CONST107 = -291.063177742393 +CONST108 = 220.133065227035 CONST109 = -792.569619378954 CONST110 = 236.460843415458 CONST111 = -271.350787726883 @@ -143,128 +143,117 @@ CONST141 = 325.620945272260 CONST142 = 324.694568017391 CONST143 = -175.156180307747 -CONST144 = 350.312360615494 -CONST145 = -162.810472636130 -CONST146 = -162.347284008695 -CONST147 = 865.852181379709 -CONST148 = -158.513923875791 -CONST149 = 361.756882439281 -CONST150 = -144.702752975712 -CONST151 = -649.389136034782 -CONST152 = -129.877827206956 -CONST153 = -129.361412329953 -CONST154 = 388.084236989858 -CONST155 = 396.284809689477 -CONST156 = -115.446957517294 -CONST157 = -108.231522672464 +CONST144 = 1085.27064731784 +CONST145 = 350.312360615494 +CONST146 = -162.810472636130 +CONST147 = -162.347284008695 +CONST148 = 865.852181379709 +CONST149 = -158.513923875791 +CONST150 = 361.756882439281 +CONST151 = -144.702752975712 +CONST152 = -649.389136034782 +CONST153 = -129.877827206956 +CONST154 = -129.361412329953 +CONST155 = 388.084236989858 +CONST156 = 396.284809689477 +CONST157 = -115.446957517294 CONST158 = -108.231522672464 -CONST159 = 407.026181590325 -CONST160 = -103.489129863962 -CONST161 = -97.0210592474644 -CONST162 = -94.7025823384056 -CONST163 = 420.374832738593 -CONST164 = -91.9569946615672 -CONST165 = 1447.02752975712 -CONST166 = -87.5780901538735 -CONST167 = -85.6073031438469 +CONST159 = -108.231522672464 +CONST160 = 407.026181590325 +CONST161 = -103.489129863962 +CONST162 = -97.0210592474644 +CONST163 = -94.7025823384056 +CONST164 = 420.374832738593 +CONST165 = -91.9569946615672 +CONST166 = 1447.02752975712 +CONST167 = -87.5780901538735 CONST168 = -85.6073031438469 -CONST169 = -81.1736420043477 -CONST170 = 432.926090689854 -CONST171 = -79.2569619378954 -CONST172 = -81.1736420043477 -CONST173 = 432.926090689854 -CONST174 = 437.890450769368 -CONST175 = 434.108258927137 -CONST176 = -79.2569619378954 -CONST177 = -72.3513764878561 -CONST178 = -72.1543484483091 -CONST179 = -70.0624721230988 -CONST180 = -72.1543484483091 -CONST181 = -67.8376969317208 -CONST182 = -65.6835676154052 -CONST183 = -61.1480736741764 -CONST184 = -1085.27064731784 -CONST185 = -61.1480736741764 -CONST186 = -1085.40315090753 -CONST187 = -57.7234787586472 -CONST188 = -12.9361412329953 -CONST189 = -1085.27064731784 -CONST190 = -52.8379746252636 -CONST191 = -51.7445649319810 -CONST192 = -1585.13923875791 -CONST193 = -47.4863878522046 -CONST194 = 978.369178786822 -CONST195 = -48.5105296237322 +CONST169 = -85.6073031438469 +CONST170 = -81.1736420043477 +CONST171 = 432.926090689854 +CONST172 = -79.2569619378954 +CONST173 = -81.1736420043477 +CONST174 = 432.926090689854 +CONST175 = 437.890450769368 +CONST176 = 434.108258927137 +CONST177 = -79.2569619378954 +CONST178 = -72.3513764878561 +CONST179 = -72.1543484483091 +CONST180 = -70.0624721230988 +CONST181 = -72.1543484483091 +CONST182 = -67.8376969317208 +CONST183 = -65.6835676154052 +CONST184 = -61.1480736741764 +CONST185 = -1085.27064731784 +CONST186 = -61.1480736741764 +CONST187 = -1085.40315090753 +CONST188 = -57.7234787586472 +CONST189 = -12.9361412329953 +CONST190 = -1085.27064731784 +CONST191 = -52.8379746252636 +CONST192 = -51.7445649319810 +CONST193 = -1585.13923875791 +CONST194 = -48.5105296237322 +CONST195 = -47.4863878522046 CONST196 = 978.369178786822 -CONST197 = -517.445649319810 -CONST198 = -40.7026181590325 -CONST199 = -40.5868210021738 -CONST200 = -39.4101405692431 -CONST201 = -40.7026181590325 -CONST202 = -36.0771742241545 -CONST203 = -1056.75949250527 -CONST204 = -29.1063177742393 -CONST205 = 485.105296237322 -CONST206 = -26.2734270461621 -CONST207 = -26.4189873126318 -CONST208 = -1050.93708184648 -CONST209 = -22.6382471577417 -CONST210 = -20.6718218536732 -CONST211 = -19.4042118494929 -CONST212 = -20.3513090795162 -CONST213 = -528.379746252636 -CONST214 = -15.0965641786467 -CONST215 = -13.5675393863442 -CONST216 = -525.468540923241 -CONST217 = -11.3224231339851 -CONST218 = -13.5289403340579 -CONST219 = -9.70210592474644 -CONST220 = -10.3359109268366 -CONST221 = -13.1367135230810 +CONST197 = 978.369178786822 +CONST198 = -517.445649319810 +CONST199 = -40.7026181590325 +CONST200 = -40.5868210021738 +CONST201 = -39.4101405692431 +CONST202 = -40.7026181590325 +CONST203 = -36.0771742241545 +CONST204 = -1056.75949250527 +CONST205 = -29.1063177742393 +CONST206 = 485.105296237322 +CONST207 = -26.2734270461621 +CONST208 = -26.4189873126318 +CONST209 = -1050.93708184648 +CONST210 = -22.6382471577417 +CONST211 = -20.6718218536732 +CONST212 = -19.4042118494929 +CONST213 = -20.3513090795162 +CONST214 = -528.379746252636 +CONST215 = -15.0965641786467 +CONST216 = -13.5675393863442 +CONST217 = -525.468540923241 +CONST218 = -11.3224231339851 +CONST219 = -13.5289403340579 +CONST220 = -9.70210592474644 +CONST221 = -10.3359109268366 CONST222 = -6.46807061649763 -CONST223 = -12.2296147348353 +CONST223 = -13.1367135230810 CONST224 = -12.2296147348353 CONST225 = -3.23403530824881 CONST226 = -1034.89129863962 -VAR00 = z**4 -VAR01 = x**7 -VAR02 = g_6 -VAR03 = g_14 -VAR04 = z**6 -VAR05 = x**2 -VAR06 = g_7 -VAR07 = y -VAR08 = y**6 -VAR09 = g_15 -VAR10 = x**4 -VAR11 = g_8 -VAR12 = z**3 -VAR13 = x**6 -VAR14 = z -VAR15 = x -VAR16 = z**5 -VAR17 = g_0 -VAR18 = g_3 -VAR19 = y**3 -VAR20 = y**5 -VAR21 = g_12 -VAR22 = x**3 -VAR23 = y**7 -VAR24 = g_2 -VAR25 = x**5 -VAR26 = g_16 -VAR27 = z**7 -VAR28 = g_5 -VAR29 = g_13 -VAR30 = y**2 -VAR31 = g_9 -VAR32 = g_4 -VAR33 = g_1 -VAR34 = g_11 -VAR35 = y**4 -VAR36 = g_10 -VAR37 = z**2 +VAR00 = x**10 +VAR01 = x**9 +VAR02 = x**8 +VAR03 = x**7 +VAR04 = x**6 +VAR05 = x**5 +VAR06 = x**4 +VAR07 = x**3 +VAR08 = x**2 +VAR09 = y**10 +VAR10 = y**9 +VAR11 = y**8 +VAR12 = y**7 +VAR13 = y**6 +VAR14 = y**5 +VAR15 = y**4 +VAR16 = y**3 +VAR17 = y**2 +VAR18 = z**10 +VAR19 = z**9 +VAR20 = z**8 +VAR21 = z**7 +VAR22 = z**6 +VAR23 = z**5 +VAR24 = z**4 +VAR25 = z**3 +VAR26 = z**2 # -------------------- kernel implementations -g_x = VAR02*(CONST001*VAR05*(CONST093*VAR12*VAR30 + CONST118*VAR14*VAR30**2 + CONST219*VAR16) + CONST004*VAR05**2*(-CONST161*VAR14*VAR30 + CONST219*VAR12) + CONST118*VAR12*VAR30**2 - CONST160*VAR14*VAR30**3 - CONST161*VAR16*VAR30 + CONST209*VAR05**3*VAR14 + CONST225*VAR27) + VAR03*(CONST013*VAR01 + CONST148*VAR25*VAR37 - CONST190*VAR15*VAR37**3 + VAR30*(CONST109*VAR15*VAR37**2 + CONST148*VAR25 - CONST192*VAR22*VAR37)) + VAR06*(CONST001*VAR05*(-CONST128*VAR19*VAR37 + CONST152*VAR20 + CONST199*VAR07*VAR37**2) + CONST004*VAR05**2*(CONST063*VAR19 + CONST199*VAR07*VAR37) + CONST019*VAR23 + CONST152*VAR20*VAR37 - CONST157*VAR19*VAR37**2 + CONST162*VAR05**3*VAR07 + CONST218*VAR07*VAR37**3) + VAR07*VAR09*(CONST050*VAR14*VAR25 + CONST050*VAR15*VAR16 - CONST054*VAR12*VAR22) + VAR07*VAR33*(CONST177*VAR05**3 - CONST177*VAR37**3 + CONST184*VAR05*VAR37**2 - CONST189*VAR05**2*VAR37) + VAR11*(CONST000*VAR15*(CONST002*VAR37**3 - CONST128*VAR30**2*VAR37 + CONST157*VAR30*VAR37**2 + CONST187*VAR30**3) + CONST006*VAR22*(CONST008*VAR37**2 - CONST157*VAR30**2 + CONST158*VAR30*VAR37) + CONST007*VAR01 + CONST009*VAR25*(CONST002*VAR37 + CONST202*VAR30)) + VAR17*(CONST049*VAR05*VAR16 - CONST131*VAR05**2*VAR12 + CONST150*VAR05**3*VAR14 - CONST210*VAR27) + VAR18*(VAR07*(CONST028*VAR05**2*VAR37 + CONST088*VAR05*VAR37**2 + CONST167*VAR05**3 + CONST183*VAR37**3) + VAR19*(CONST044*VAR05*VAR37 + CONST113*VAR05**2 + CONST114*VAR37**2)) + VAR21*(CONST011*VAR01 + CONST181*VAR22*VAR37**2 + CONST198*VAR25*VAR37 + CONST215*VAR15*VAR37**3 + VAR30**2*(CONST098*VAR15*VAR37 + CONST122*VAR22) + VAR30*(-CONST102*VAR22*VAR37 + CONST121*VAR25 + CONST159*VAR15*VAR37**2)) + VAR24*(CONST001*VAR05*(-CONST207*VAR16 + CONST213*VAR12*VAR30) + CONST004*VAR05**2*(-CONST148*VAR14*VAR30 - CONST207*VAR12) - CONST148*VAR16*VAR30 + CONST171*VAR05**3*VAR14 + CONST217*VAR27) + VAR26*(CONST050*VAR25*VAR37 - CONST131*VAR22*VAR37**2 + CONST150*VAR15*VAR37**3 - CONST210*VAR01) + VAR28*(VAR07*(CONST040*VAR05**2*VAR37 + CONST095*VAR05*VAR37**2 + CONST164*VAR05**3 - CONST200*VAR37**3) + VAR19*(-CONST048*VAR05**2 + CONST116*VAR37**2 + CONST216*VAR05*VAR37) + VAR20*(CONST133*VAR05 - CONST134*VAR37)) + VAR29*(VAR07*(CONST076*VAR14*VAR25 + CONST105*VAR15*VAR16 + CONST112*VAR12*VAR22) + VAR19*(CONST030*VAR12*VAR15 - CONST030*VAR14*VAR22)) + VAR31*(CONST172*VAR07*VAR15*VAR16 + VAR12*(CONST146*VAR07*VAR22 + CONST170*VAR15*VAR19) + VAR14*(CONST117*VAR15*VAR20 + CONST169*VAR07*VAR25 + CONST170*VAR19*VAR22)) + VAR32*(CONST001*VAR05*(CONST005*VAR16 + CONST111*VAR14*VAR30**2) + CONST004*VAR05**2*(CONST080*VAR12 - CONST145*VAR14*VAR30) + CONST005*VAR27 - CONST111*VAR12*VAR30**2 + CONST145*VAR16*VAR30 + CONST193*VAR05**3*VAR14) + VAR34*(CONST056*VAR14*VAR15*VAR20 + VAR07*(CONST116*VAR12*VAR22 + CONST124*VAR14*VAR25 + CONST206*VAR15*VAR16) + VAR19*(-CONST082*VAR12*VAR15 - CONST208*VAR14*VAR22)) + VAR36*(CONST017*VAR25*VAR37 + CONST160*VAR15*VAR30**3 - CONST188*VAR01 - CONST197*VAR22*VAR30**2 + CONST222*VAR15*VAR37**3 + VAR30*(CONST058*VAR15*VAR37**2 + CONST106*VAR25 + CONST138*VAR22*VAR37)) -g_y = CONST000*VAR03*VAR07*(-CONST068*VAR05**2*VAR37 + CONST068*VAR05*VAR37**2 + CONST207*VAR05**3 - CONST207*VAR37**3) + VAR02*(-CONST138*VAR07*VAR14*VAR25 + VAR15*(CONST067*VAR14*VAR20 - CONST138*VAR07*VAR16 + CONST226*VAR12*VAR19) + VAR22*(CONST154*VAR07*VAR12 + CONST226*VAR14*VAR19)) + VAR06*(CONST218*VAR01 + VAR15*(CONST085*VAR30**3 + CONST140*VAR30*VAR37**2 + CONST151*VAR30**2*VAR37 + CONST218*VAR37**3) + VAR22*(CONST151*VAR30**2 - CONST151*VAR30*VAR37 + CONST199*VAR37**2) + VAR25*(CONST142*VAR30 + CONST199*VAR37)) + VAR09*(-CONST078*VAR05**2*VAR12 + CONST127*VAR05*VAR16 + CONST177*VAR05**3*VAR14 - CONST220*VAR27) + VAR11*(CONST026*VAR23 - CONST052*VAR19*VAR37**2 + CONST084*VAR20*VAR37 + CONST178*VAR05**3*VAR07 + CONST180*VAR07*VAR37**3 + VAR05**2*(-CONST052*VAR19 + CONST129*VAR07*VAR37) + VAR05*(CONST083*VAR20 + CONST128*VAR07*VAR37**2 + CONST147*VAR19*VAR37)) + VAR18*(CONST001*VAR30*(CONST020*VAR22*VAR37 + CONST034*VAR25 + CONST114*VAR15*VAR37**2) + CONST066*VAR25*VAR37 + CONST183*VAR15*VAR37**3 - CONST185*VAR22*VAR37**2 + CONST224*VAR01) + VAR21*(CONST000*VAR07*(CONST097*VAR05**2*VAR37 + CONST097*VAR05*VAR37**2 + CONST198*VAR05**3 + CONST198*VAR37**3) + CONST006*VAR19*(CONST061*VAR05*VAR37 - CONST181*VAR05**2 - CONST181*VAR37**2)) + VAR24*(CONST137*VAR07*VAR14*VAR25 + CONST137*VAR07*VAR15*VAR16 + CONST203*VAR07*VAR12*VAR22) + VAR28*(CONST001*VAR30*(CONST116*VAR15*VAR37**2 + CONST143*VAR22*VAR37 - CONST166*VAR25) + CONST004*VAR30**2*(-CONST134*VAR15*VAR37 + CONST179*VAR22) + CONST015*VAR25*VAR37 + CONST040*VAR22*VAR37**2 + CONST139*VAR01 - CONST200*VAR15*VAR37**3) + VAR29*(CONST001*VAR30*(CONST020*VAR05*VAR12 + CONST034*VAR16 + CONST113*VAR05**2*VAR14) + CONST065*VAR05*VAR16 - CONST183*VAR05**2*VAR12 + CONST185*VAR05**3*VAR14 + CONST223*VAR27) + VAR31*(CONST218*VAR27 + VAR12*(CONST073*VAR05*VAR30 + CONST151*VAR30**2 + CONST199*VAR05**2) + VAR14*(CONST086*VAR30**3 + CONST091*VAR05**3 + CONST142*VAR05**2*VAR30 + CONST151*VAR05*VAR30**2) + VAR16*(CONST142*VAR30 + CONST199*VAR05)) + VAR32*(-CONST090*VAR07*VAR14*VAR25 + CONST186*VAR14*VAR19*VAR22 + VAR15*(CONST090*VAR07*VAR16 - CONST186*VAR12*VAR19)) + VAR33*(CONST078*VAR22*VAR37**2 + CONST104*VAR25*VAR37 - CONST177*VAR15*VAR37**3 + CONST220*VAR01) + VAR34*(CONST001*VAR30*(-CONST116*VAR05**2*VAR14 - CONST143*VAR05*VAR12 + CONST166*VAR16) + CONST004*VAR30**2*(CONST134*VAR05*VAR14 - CONST179*VAR12) + CONST012*VAR27 + CONST182*VAR05**2*VAR12 + CONST200*VAR05**3*VAR14 + CONST221*VAR05*VAR16) + VAR36*(CONST000*VAR07*(CONST032*VAR05*VAR37**2 + CONST032*VAR37**3 + CONST195*VAR05**3 + CONST195*VAR05**2*VAR37) + CONST006*VAR19*(-CONST153*VAR05**2 + CONST153*VAR37**2) + CONST009*VAR20*(CONST035*VAR37 + CONST191*VAR05)) -g_z = VAR02*(CONST225*VAR01 + VAR15*(CONST115*VAR30**2*VAR37 - CONST160*VAR30**3 + CONST205*VAR30*VAR37**2 + CONST209*VAR37**3) + VAR22*(CONST046*VAR30*VAR37 + CONST118*VAR30**2 + CONST195*VAR37**2) + VAR25*(-CONST161*VAR30 + CONST204*VAR37)) + VAR03*(-CONST148*VAR05*VAR16 + CONST190*VAR05**3*VAR14 + CONST214*VAR27 + VAR30*(-CONST109*VAR05**2*VAR14 - CONST148*VAR16 + CONST192*VAR05*VAR12)) + VAR06*(CONST172*VAR07*VAR14*VAR25 + VAR15*(-CONST052*VAR12*VAR19 + CONST117*VAR14*VAR20 + CONST172*VAR07*VAR16) + VAR22*(-CONST052*VAR14*VAR19 + CONST146*VAR07*VAR12)) + VAR07*VAR09*(CONST177*VAR05**3 - CONST177*VAR37**3 - CONST184*VAR05**2*VAR37 + CONST189*VAR05*VAR37**2) + VAR07*VAR33*(-CONST050*VAR15*VAR16 + CONST054*VAR12*VAR22 + CONST071*VAR14*VAR25) + VAR11*(CONST007*VAR05**3*VAR14 + CONST007*VAR27 - CONST052*VAR12*VAR30**2 + CONST130*VAR16*VAR30 + CONST156*VAR14*VAR30**3 + VAR05**2*(CONST024*VAR12 + CONST129*VAR14*VAR30) + VAR05*(CONST024*VAR16 + CONST052*VAR12*VAR30 - CONST052*VAR14*VAR30**2)) + VAR17*(-CONST049*VAR25*VAR37 + CONST131*VAR22*VAR37**2 - CONST150*VAR15*VAR37**3 + CONST210*VAR01) + VAR18*(VAR07*(CONST077*VAR15*VAR16 + CONST107*VAR14*VAR25 + CONST114*VAR12*VAR22) + VAR19*(CONST030*VAR14*VAR22 + CONST196*VAR12*VAR15)) + VAR21*(CONST011*VAR27 + CONST092*VAR05**3*VAR14 + CONST181*VAR05**2*VAR12 + CONST201*VAR05*VAR16 + VAR30**2*(CONST098*VAR05*VAR14 + CONST122*VAR12) + VAR30*(-CONST102*VAR05*VAR12 + CONST121*VAR16 + CONST159*VAR05**2*VAR14)) + VAR24*(CONST096*VAR01 + VAR15*(-CONST109*VAR30*VAR37**2 + CONST176*VAR37**3) + VAR22*(CONST070*VAR37**2 + CONST192*VAR30*VAR37) + VAR25*(-CONST148*VAR30 - CONST176*VAR37)) + VAR26*(CONST050*VAR05*VAR16 - CONST131*VAR05**2*VAR12 + CONST150*VAR05**3*VAR14 - CONST210*VAR27) + VAR28*(-CONST056*VAR14*VAR15*VAR20 + VAR07*(CONST023*VAR14*VAR25 + CONST120*VAR12*VAR22 - CONST124*VAR15*VAR16) + VAR19*(CONST082*VAR14*VAR22 + CONST208*VAR12*VAR15)) + VAR29*(VAR07*(CONST028*VAR05*VAR37**2 + CONST089*VAR05**2*VAR37 + CONST168*VAR37**3 + CONST185*VAR05**3) + VAR19*(CONST044*VAR05*VAR37 + CONST113*VAR05**2 + CONST113*VAR37**2)) + VAR31*(CONST001*VAR37*(CONST108*VAR05*VAR19 + CONST152*VAR20 + CONST199*VAR05**2*VAR07) + CONST004*VAR37**2*(CONST063*VAR19 + CONST199*VAR05*VAR07) + CONST025*VAR23 + CONST063*VAR05**2*VAR19 + CONST091*VAR05**3*VAR07 + CONST152*VAR05*VAR20 + CONST162*VAR07*VAR37**3) + VAR32*(CONST080*VAR01 + VAR15*(CONST102*VAR30*VAR37**2 + CONST135*VAR30**2*VAR37 - CONST193*VAR37**3) + VAR22*(CONST027*VAR37**2 + CONST111*VAR30**2) + VAR25*(-CONST145*VAR30 + CONST212*VAR37)) + VAR34*(VAR07*(CONST055*VAR37**3 + CONST136*VAR05**2*VAR37 + CONST182*VAR05*VAR37**2 + CONST200*VAR05**3) + VAR19*(CONST048*VAR37**2 - CONST116*VAR05**2 - CONST216*VAR05*VAR37) + VAR20*(-CONST133*VAR37 + CONST134*VAR05)) + VAR36*(CONST057*VAR05**3*VAR14 + CONST062*VAR14*VAR30**3 + CONST188*VAR27 + CONST197*VAR12*VAR30**2 + CONST211*VAR05*VAR16 + VAR30*(CONST093*VAR05*VAR12 - CONST106*VAR16 + CONST161*VAR05**2*VAR14)) +g_x = g_0*(CONST049*VAR08*VAR23 - CONST131*VAR06*VAR25 + CONST151*VAR04*z - CONST211*VAR21) + g_1*y*(CONST178*VAR04 - CONST178*VAR22 + CONST185*VAR08*VAR24 - CONST190*VAR06*VAR26) + g_10*(CONST017*VAR05*VAR26 + CONST161*VAR13*x - CONST189*VAR03 - CONST198*VAR07*VAR15 + CONST222*VAR22*x + VAR17*(CONST058*VAR24*x + CONST107*VAR05 + CONST138*VAR07*VAR26)) + g_11*(CONST056*VAR14*x*z + VAR16*(-CONST082*VAR25*x - CONST209*VAR07*z) + y*(CONST116*VAR07*VAR25 + CONST124*VAR05*z + CONST207*VAR23*x)) + g_12*(CONST011*VAR03 + CONST182*VAR07*VAR24 + CONST199*VAR05*VAR26 + CONST216*VAR22*x + VAR15*(CONST098*VAR26*x + CONST122*VAR07) + VAR17*(-CONST102*VAR07*VAR26 + CONST121*VAR05 + CONST160*VAR24*x)) + g_13*(VAR16*(-CONST030*VAR07*z + CONST030*VAR25*x) + y*(CONST076*VAR05*z + CONST106*VAR23*x + CONST112*VAR07*VAR25)) + g_14*(CONST012*VAR03 + CONST149*VAR05*VAR26 - CONST191*VAR22*x + VAR17*(CONST109*VAR24*x + CONST149*VAR05 - CONST193*VAR07*VAR26)) + g_15*y*(CONST050*VAR05*z + CONST050*VAR23*x - CONST054*VAR07*VAR25) + g_16*(CONST050*VAR05*VAR26 - CONST131*VAR07*VAR24 + CONST151*VAR22*x - CONST211*VAR03) + g_2*(CONST001*VAR08*(-CONST208*VAR23 + CONST214*VAR17*VAR25) + CONST004*VAR06*(-CONST149*VAR17*z - CONST208*VAR25) - CONST149*VAR17*VAR23 + CONST172*VAR04*z + CONST218*VAR21) + g_3*(VAR16*(CONST043*VAR08*VAR26 + CONST113*VAR06 + CONST114*VAR24) + y*(CONST028*VAR06*VAR26 + CONST088*VAR08*VAR24 + CONST168*VAR04 + CONST184*VAR22)) + g_4*(CONST001*VAR08*(CONST005*VAR23 + CONST111*VAR15*z) + CONST004*VAR06*(CONST080*VAR25 - CONST146*VAR17*z) + CONST005*VAR21 - CONST111*VAR15*VAR25 + CONST146*VAR17*VAR23 + CONST195*VAR04*z) + g_5*(VAR14*(CONST133*VAR08 - CONST134*VAR26) + VAR16*(-CONST048*VAR06 + CONST116*VAR24 + CONST217*VAR08*VAR26) + y*(CONST041*VAR06*VAR26 + CONST095*VAR08*VAR24 + CONST165*VAR04 - CONST201*VAR22)) + g_6*(CONST001*VAR08*(CONST093*VAR17*VAR25 + CONST118*VAR15*z + CONST220*VAR23) + CONST004*VAR06*(-CONST162*VAR17*z + CONST220*VAR25) + CONST118*VAR15*VAR25 - CONST161*VAR13*z - CONST162*VAR17*VAR23 + CONST210*VAR04*z + CONST225*VAR21) + g_7*(CONST001*VAR08*(-CONST128*VAR16*VAR26 + CONST153*VAR14 + CONST200*VAR24*y) + CONST004*VAR06*(CONST063*VAR16 + CONST200*VAR26*y) + CONST020*VAR12 + CONST153*VAR14*VAR26 - CONST158*VAR16*VAR24 + CONST163*VAR04*y + CONST219*VAR22*y) + g_8*(CONST000*x*(CONST002*VAR22 - CONST128*VAR15*VAR26 + CONST158*VAR17*VAR24 + CONST188*VAR13) + CONST006*VAR07*(CONST008*VAR24 - CONST158*VAR15 + CONST159*VAR17*VAR26) + CONST007*VAR03 + CONST009*VAR05*(CONST002*VAR26 + CONST203*VAR17)) + g_9*(CONST173*VAR23*x*y + VAR25*(CONST147*VAR07*y + CONST171*VAR16*x) + z*(CONST117*VAR14*x + CONST170*VAR05*y + CONST171*VAR07*VAR16)) +g_y = CONST000*g_14*y*(-CONST068*VAR06*VAR26 + CONST068*VAR08*VAR24 + CONST208*VAR04 - CONST208*VAR22) + g_1*(CONST078*VAR07*VAR24 + CONST104*VAR05*VAR26 - CONST178*VAR22*x + CONST221*VAR03) + g_10*(CONST000*y*(CONST031*VAR08*VAR24 + CONST031*VAR22 + CONST194*VAR04 + CONST194*VAR06*VAR26) + CONST006*VAR16*(-CONST154*VAR06 + CONST154*VAR24) + CONST009*VAR14*(CONST033*VAR26 + CONST192*VAR08)) + g_11*(CONST001*VAR17*(-CONST116*VAR06*z - CONST143*VAR08*VAR25 + CONST167*VAR23) + CONST004*VAR15*(CONST134*VAR08*z - CONST180*VAR25) + CONST013*VAR21 + CONST183*VAR06*VAR25 + CONST201*VAR04*z + CONST223*VAR08*VAR23) + g_12*(CONST000*y*(CONST097*VAR06*VAR26 + CONST097*VAR08*VAR24 + CONST199*VAR04 + CONST199*VAR22) + CONST006*VAR16*(CONST062*VAR08*VAR26 - CONST182*VAR06 - CONST182*VAR24)) + g_13*(CONST001*VAR17*(CONST019*VAR08*VAR25 + CONST035*VAR23 + CONST113*VAR06*z) + CONST065*VAR08*VAR23 - CONST184*VAR06*VAR25 + CONST186*VAR04*z + CONST224*VAR21) + g_15*(-CONST078*VAR06*VAR25 + CONST127*VAR08*VAR23 + CONST178*VAR04*z - CONST221*VAR21) + g_2*(CONST137*VAR05*y*z + CONST137*VAR23*x*y + CONST204*VAR07*VAR25*y) + g_3*(CONST001*VAR17*(CONST019*VAR07*VAR26 + CONST035*VAR05 + CONST114*VAR24*x) + CONST045*VAR03 + CONST066*VAR05*VAR26 + CONST184*VAR22*x - CONST186*VAR07*VAR24) + g_4*(-CONST090*VAR05*y*z + CONST187*VAR07*VAR16*z + x*(CONST090*VAR23*y - CONST187*VAR16*VAR25)) + g_5*(CONST001*VAR17*(CONST116*VAR24*x + CONST143*VAR07*VAR26 - CONST167*VAR05) + CONST004*VAR15*(-CONST134*VAR26*x + CONST180*VAR07) + CONST015*VAR05*VAR26 + CONST041*VAR07*VAR24 + CONST139*VAR03 - CONST201*VAR22*x) + g_6*(-CONST138*VAR05*y*z + VAR07*(CONST155*VAR25*y + CONST226*VAR16*z) + x*(CONST067*VAR14*z - CONST138*VAR23*y + CONST226*VAR16*VAR25)) + g_7*(CONST219*VAR03 + VAR05*(CONST142*VAR17 + CONST200*VAR26) + VAR07*(CONST152*VAR15 - CONST152*VAR17*VAR26 + CONST200*VAR24) + x*(CONST085*VAR13 + CONST140*VAR17*VAR24 + CONST152*VAR15*VAR26 + CONST219*VAR22)) + g_8*(CONST026*VAR12 - CONST052*VAR16*VAR24 + CONST084*VAR14*VAR26 + CONST179*VAR04*y + CONST181*VAR22*y + VAR06*(-CONST052*VAR16 + CONST129*VAR26*y) + VAR08*(CONST083*VAR14 + CONST128*VAR24*y + CONST148*VAR16*VAR26)) + g_9*(CONST219*VAR21 + VAR23*(CONST142*VAR17 + CONST200*VAR08) + VAR25*(CONST073*VAR08*VAR17 + CONST152*VAR15 + CONST200*VAR06) + z*(CONST086*VAR13 + CONST091*VAR04 + CONST142*VAR06*VAR17 + CONST152*VAR08*VAR15)) +g_z = g_0*(-CONST049*VAR05*VAR26 + CONST131*VAR07*VAR24 - CONST151*VAR22*x + CONST211*VAR03) + g_1*y*(-CONST050*VAR23*x + CONST054*VAR07*VAR25 + CONST071*VAR05*z) + g_10*(CONST057*VAR04*z + CONST061*VAR13*z + CONST189*VAR21 + CONST198*VAR15*VAR25 + CONST212*VAR08*VAR23 + VAR17*(CONST093*VAR08*VAR25 - CONST107*VAR23 + CONST162*VAR06*z)) + g_11*(VAR14*(-CONST133*VAR26 + CONST134*VAR08) + VAR16*(CONST048*VAR24 - CONST116*VAR06 - CONST217*VAR08*VAR26) + y*(CONST055*VAR22 + CONST136*VAR06*VAR26 + CONST183*VAR08*VAR24 + CONST201*VAR04)) + g_12*(CONST011*VAR21 + CONST092*VAR04*z + CONST182*VAR06*VAR25 + CONST202*VAR08*VAR23 + VAR15*(CONST098*VAR08*z + CONST122*VAR25) + VAR17*(-CONST102*VAR08*VAR25 + CONST121*VAR23 + CONST160*VAR06*z)) + g_13*(VAR16*(CONST043*VAR08*VAR26 + CONST113*VAR06 + CONST113*VAR24) + y*(CONST028*VAR08*VAR24 + CONST089*VAR06*VAR26 + CONST169*VAR22 + CONST186*VAR04)) + g_14*(-CONST149*VAR08*VAR23 + CONST191*VAR04*z + CONST215*VAR21 + VAR17*(-CONST109*VAR06*z - CONST149*VAR23 + CONST193*VAR08*VAR25)) + g_15*y*(CONST178*VAR04 - CONST178*VAR22 - CONST185*VAR06*VAR26 + CONST190*VAR08*VAR24) + g_16*(CONST050*VAR08*VAR23 - CONST131*VAR06*VAR25 + CONST151*VAR04*z - CONST211*VAR21) + g_2*(CONST096*VAR03 + VAR05*(-CONST149*VAR17 - CONST177*VAR26) + VAR07*(CONST070*VAR24 + CONST193*VAR17*VAR26) + x*(-CONST109*VAR17*VAR24 + CONST177*VAR22)) + g_3*(VAR16*(CONST030*VAR07*z + CONST197*VAR25*x) + y*(CONST077*VAR23*x + CONST108*VAR05*z + CONST114*VAR07*VAR25)) + g_4*(CONST080*VAR03 + VAR05*(-CONST146*VAR17 + CONST213*VAR26) + VAR07*(CONST027*VAR24 + CONST111*VAR15) + x*(CONST102*VAR17*VAR24 + CONST135*VAR15*VAR26 - CONST195*VAR22)) + g_5*(-CONST056*VAR14*x*z + VAR16*(CONST082*VAR07*z + CONST209*VAR25*x) + y*(CONST023*VAR05*z + CONST120*VAR07*VAR25 - CONST124*VAR23*x)) + g_6*(CONST225*VAR03 + VAR05*(-CONST162*VAR17 + CONST205*VAR26) + VAR07*(CONST047*VAR17*VAR26 + CONST118*VAR15 + CONST194*VAR24) + x*(CONST115*VAR15*VAR26 - CONST161*VAR13 + CONST206*VAR17*VAR24 + CONST210*VAR22)) + g_7*(CONST173*VAR05*y*z + VAR07*(-CONST052*VAR16*z + CONST147*VAR25*y) + x*(-CONST052*VAR16*VAR25 + CONST117*VAR14*z + CONST173*VAR23*y)) + g_8*(CONST007*VAR04*z + CONST007*VAR21 - CONST052*VAR15*VAR25 + CONST130*VAR17*VAR23 + CONST157*VAR13*z + VAR06*(CONST024*VAR25 + CONST129*VAR17*z) + VAR08*(CONST024*VAR23 - CONST052*VAR15*z + CONST052*VAR17*VAR25)) + g_9*(CONST001*VAR26*(CONST105*VAR08*VAR16 + CONST153*VAR14 + CONST200*VAR06*y) + CONST004*VAR24*(CONST063*VAR16 + CONST200*VAR08*y) + CONST025*VAR12 + CONST063*VAR06*VAR16 + CONST091*VAR04*y + CONST153*VAR08*VAR14 + CONST163*VAR22*y) diff --git a/notebooks/bwd_implementations/bwd_9.py b/notebooks/bwd_implementations/bwd_9.py index 1d3061e..5f7fb30 100644 --- a/notebooks/bwd_implementations/bwd_9.py +++ b/notebooks/bwd_implementations/bwd_9.py @@ -20,83 +20,83 @@ CONST018 = 14.4550674370400 CONST019 = 14.4550674370400 CONST020 = 13.3827919767794 -CONST021 = 13.0937127087774 +CONST021 = 23.8930627690618 CONST022 = 23.8930627690618 -CONST023 = 23.8930627690618 -CONST024 = 27.0429549260581 -CONST025 = 29.2403830344269 -CONST026 = 30.0014648079890 -CONST027 = 30.9062342012093 -CONST028 = 29.2403830344269 -CONST029 = 38.3780027326853 +CONST023 = 27.0429549260581 +CONST024 = 29.2403830344269 +CONST025 = 30.0014648079890 +CONST026 = 30.9062342012093 +CONST027 = 29.2403830344269 +CONST028 = 38.3780027326853 +CONST029 = 39.2811381263323 CONST030 = 39.2811381263323 -CONST031 = 39.2811381263323 -CONST032 = 39.2300904918661 -CONST033 = 42.9079114754785 -CONST034 = 10.7269778688696 -CONST035 = 54.0859098521163 -CONST036 = 57.8202697481601 -CONST037 = 58.9217071894985 -CONST038 = 57.8202697481601 -CONST039 = 60.0029296159779 -CONST040 = 62.4530292249704 -CONST041 = 64.3618672132178 -CONST042 = 68.5747767039748 -CONST043 = 69.1084406024329 -CONST044 = 77.2655855030233 -CONST045 = 78.5622762526647 +CONST031 = 39.2300904918661 +CONST032 = 42.9079114754785 +CONST033 = 10.7269778688696 +CONST034 = 54.0859098521163 +CONST035 = 57.8202697481601 +CONST036 = 58.9217071894985 +CONST037 = 57.8202697481601 +CONST038 = 60.0029296159779 +CONST039 = 62.4530292249704 +CONST040 = 64.3618672132178 +CONST041 = 68.5747767039748 +CONST042 = 69.1084406024329 +CONST043 = 77.2655855030233 +CONST044 = 78.5622762526647 +CONST045 = 85.8158229509570 CONST046 = 85.8158229509570 -CONST047 = 85.8158229509570 -CONST048 = 90.1063824390370 -CONST049 = 96.7518168434061 -CONST050 = 104.749701670220 -CONST051 = 107.062335814235 +CONST047 = 90.1063824390370 +CONST048 = 96.7518168434061 +CONST049 = 104.749701670220 +CONST050 = 107.062335814235 +CONST051 = 108.171819704233 CONST052 = 108.171819704233 -CONST053 = 108.171819704233 -CONST054 = -1935.03633686812 +CONST053 = -1935.03633686812 +CONST054 = 115.640539496320 CONST055 = 115.640539496320 -CONST056 = 115.640539496320 +CONST056 = 117.843414378997 CONST057 = 117.843414378997 CONST058 = 115.640539496320 -CONST059 = 117.843414378997 -CONST060 = 120.005859231956 +CONST059 = 120.005859231956 +CONST060 = 2176.91587897664 CONST061 = 2176.91587897664 -CONST062 = 2176.91587897664 -CONST063 = 137.149553407950 +CONST062 = 137.149553407950 +CONST063 = 150.007324039945 CONST064 = 150.007324039945 -CONST065 = 150.007324039945 -CONST066 = -1892.23403121978 -CONST067 = -1885.49463006395 -CONST068 = 173.460809244480 -CONST069 = -1873.59087674911 -CONST070 = 176.765121568496 -CONST071 = 10.7269778688696 -CONST072 = 180.008788847934 -CONST073 = 187.359087674911 -CONST074 = 191.144502152495 -CONST075 = 13.5214774630291 -CONST076 = 196.405690631662 -CONST077 = 205.957975082297 +CONST065 = -1892.23403121978 +CONST066 = -1885.49463006395 +CONST067 = 173.460809244480 +CONST068 = -1873.59087674911 +CONST069 = 176.765121568496 +CONST070 = 10.7269778688696 +CONST071 = 180.008788847934 +CONST072 = 187.359087674911 +CONST073 = 191.144502152495 +CONST074 = 13.5214774630291 +CONST075 = 196.405690631662 +CONST076 = 205.957975082297 +CONST077 = 216.343639408465 CONST078 = 216.343639408465 -CONST079 = 216.343639408465 -CONST080 = 4326.87278816930 +CONST079 = 4326.87278816930 +CONST080 = 233.923064275415 CONST081 = 233.923064275415 -CONST082 = 233.923064275415 -CONST083 = 240.011718463912 +CONST082 = 240.011718463912 +CONST083 = 241.879542108515 CONST084 = 241.879542108515 -CONST085 = 241.879542108515 +CONST085 = 255.853351551235 CONST086 = 255.853351551235 -CONST087 = 255.853351551235 +CONST087 = 257.447468852871 CONST088 = 257.447468852871 -CONST089 = 257.447468852871 -CONST090 = 2312.81078992641 -CONST091 = 270.429549260581 -CONST092 = 289.101348740801 -CONST093 = 294.608535947493 +CONST089 = 2312.81078992641 +CONST090 = 270.429549260581 +CONST091 = 289.101348740801 +CONST092 = 294.608535947493 +CONST093 = 300.014648079890 CONST094 = 300.014648079890 -CONST095 = 300.014648079890 -CONST096 = 2356.86828757994 -CONST097 = 314.249105010659 +CONST095 = 2356.86828757994 +CONST096 = 314.249105010659 +CONST097 = 13.0937127087774 CONST098 = 324.515459112698 CONST099 = -3747.18175349822 CONST100 = 6.39633378878088 @@ -278,10 +278,10 @@ CONST276 = -78.5622762526647 CONST277 = -68.5747767039748 CONST278 = -63.9633378878088 -CONST279 = -63.9633378878088 -CONST280 = -62.4530292249704 +CONST279 = -62.4530292249704 +CONST280 = -61.8124684024186 CONST281 = -60.0029296159779 -CONST282 = -61.8124684024186 +CONST282 = -63.9633378878088 CONST283 = -58.9217071894985 CONST284 = -57.8202697481601 CONST285 = -57.8202697481601 @@ -303,50 +303,34 @@ CONST301 = -9.82028453158308 CONST302 = -4.91014226579154 CONST303 = 2046.82681240988 -VAR00 = g_14 -VAR01 = z -VAR02 = g_12 -VAR03 = g_16 -VAR04 = g_4 -VAR05 = z**4 -VAR06 = z**8 -VAR07 = y -VAR08 = x**4 -VAR09 = y**8 -VAR10 = z**3 -VAR11 = x -VAR12 = x**8 -VAR13 = y**3 -VAR14 = x**3 -VAR15 = y**7 -VAR16 = y**2 -VAR17 = g_9 -VAR18 = g_1 -VAR19 = g_11 -VAR20 = g_15 -VAR21 = g_8 -VAR22 = g_18 -VAR23 = z**7 -VAR24 = g_5 -VAR25 = z**2 -VAR26 = x**7 -VAR27 = g_6 -VAR28 = z**6 -VAR29 = x**2 -VAR30 = g_7 -VAR31 = y**6 -VAR32 = x**6 -VAR33 = z**5 -VAR34 = g_0 -VAR35 = g_3 -VAR36 = y**5 -VAR37 = g_2 -VAR38 = g_17 -VAR39 = x**5 -VAR40 = g_13 -VAR41 = y**4 -VAR42 = g_10 +VAR00 = x**10 +VAR01 = x**9 +VAR02 = x**8 +VAR03 = x**7 +VAR04 = x**6 +VAR05 = x**5 +VAR06 = x**4 +VAR07 = x**3 +VAR08 = x**2 +VAR09 = y**10 +VAR10 = y**9 +VAR11 = y**8 +VAR12 = y**7 +VAR13 = y**6 +VAR14 = y**5 +VAR15 = y**4 +VAR16 = y**3 +VAR17 = y**2 +VAR18 = z**10 +VAR19 = z**9 +VAR20 = z**8 +VAR21 = z**7 +VAR22 = z**6 +VAR23 = z**5 +VAR24 = z**4 +VAR25 = z**3 +VAR26 = z**2 # -------------------- kernel implementations -g_x = VAR00*(CONST043*VAR01*VAR26 + CONST268*VAR14*VAR33 + CONST294*VAR11*VAR23 + VAR16**2*(CONST054*VAR10*VAR11 + CONST261*VAR01*VAR14) + VAR16*(CONST119*VAR01*VAR39 + CONST144*VAR11*VAR33 + CONST152*VAR10*VAR14)) + VAR02*(VAR01*(CONST155*VAR16*VAR39 + CONST184*VAR11*VAR16**3 - CONST217*VAR14*VAR16**2 - CONST288*VAR26) + VAR10*(CONST045*VAR39 + CONST143*VAR14*VAR16 - CONST172*VAR11*VAR16**2) + VAR33*(CONST257*VAR11*VAR16 - CONST290*VAR14)) + VAR03*(CONST214*VAR10*VAR39 - CONST264*VAR01*VAR26 + CONST264*VAR14*VAR33 - CONST275*VAR11*VAR23 + VAR16*(CONST080*VAR10*VAR14 + CONST134*VAR01*VAR39 + CONST134*VAR11*VAR33)) + VAR04*(CONST007*VAR25**4 + CONST014*VAR29**4 + CONST254*VAR25**2*VAR29**2 + CONST269*VAR25*VAR29**3 + VAR16**2*(CONST114*VAR25**2 + CONST114*VAR29**2 + CONST168*VAR25*VAR29) + VAR16*(CONST061*VAR25*VAR29**2 + CONST133*VAR25**2*VAR29 + CONST212*VAR29**3 + CONST224*VAR25**3)) + VAR07*VAR18*(CONST066*VAR29*VAR33 - CONST149*VAR10*VAR29**2 + CONST183*VAR01*VAR29**3 - CONST271*VAR23) + VAR07*VAR38*(CONST066*VAR25*VAR39 - CONST149*VAR14*VAR25**2 + CONST183*VAR11*VAR25**3 - CONST271*VAR26) + VAR17*(CONST245*VAR11*VAR15 + VAR07*(CONST046*VAR26 + CONST047*VAR11*VAR25**3 + CONST088*VAR25*VAR39 + CONST089*VAR14*VAR25**2) + VAR13*(CONST131*VAR14*VAR25 + CONST178*VAR11*VAR25**2 + CONST178*VAR39) + VAR36*(CONST141*VAR11*VAR25 + CONST141*VAR14)) + VAR19*(CONST150*VAR14*VAR36 + CONST250*VAR11*VAR15 + VAR07*(CONST060*VAR26 + CONST072*VAR25*VAR39 + CONST281*VAR11*VAR25**3) + VAR13*(CONST094*VAR11*VAR25**2 + CONST165*VAR39 + CONST186*VAR14*VAR25)) + VAR20*(VAR07*(CONST051*VAR26 + CONST147*VAR25*VAR39 - CONST205*VAR11*VAR25**3) + VAR13*(CONST069*VAR11*VAR25**2 - CONST099*VAR14*VAR25 + CONST205*VAR39)) + VAR21*(CONST000*VAR25**4 + CONST002*VAR29*(CONST005*VAR25**3 + CONST115*VAR16**2*VAR25 + CONST230*VAR16**3 + CONST235*VAR16*VAR25**2) + CONST004*VAR29**2*(CONST008*VAR25**2 + CONST086*VAR16**2 + CONST235*VAR16*VAR25) + CONST006*VAR29**3*(CONST009*VAR25 + CONST279*VAR16) + CONST015*VAR29**4 + CONST025*VAR16**4 + CONST086*VAR16**2*VAR25**2 + CONST231*VAR16**3*VAR25 + CONST279*VAR16*VAR25**3) + VAR22*(CONST132*VAR10*VAR39 + CONST175*VAR14*VAR33 - CONST234*VAR01*VAR26 + CONST234*VAR11*VAR23) + VAR24*(VAR07*(CONST068*VAR29*VAR33 + CONST200*VAR01*VAR29**3 + CONST220*VAR10*VAR29**2 - CONST284*VAR23) + VAR13*(CONST195*VAR33 - CONST222*VAR01*VAR29**2) + VAR36*(CONST130*VAR01*VAR29 - CONST195*VAR10)) + VAR27*(CONST002*VAR29*(CONST201*VAR16**2*VAR25 - CONST219*VAR16*VAR25**2 + CONST267*VAR16**3 + CONST299*VAR25**3) + CONST004*VAR29**2*(CONST037*VAR16*VAR25 - CONST233*VAR16**2 + CONST301*VAR25**2) + CONST187*VAR16**2*VAR25**2 + CONST197*VAR16*VAR29**3 - CONST216*VAR16**3*VAR25 - CONST239*VAR16*VAR25**3 - CONST297*VAR29**4 + CONST302*VAR25**4) + VAR30*(CONST002*VAR29*(-CONST186*VAR10*VAR13 + CONST192*VAR01*VAR36 + CONST270*VAR07*VAR33) + CONST004*VAR29**2*(-CONST218*VAR01*VAR13 + CONST270*VAR07*VAR10) + CONST193*VAR10*VAR36 - CONST218*VAR13*VAR33 + CONST229*VAR01*VAR07*VAR29**3 - CONST250*VAR01*VAR15 + CONST292*VAR07*VAR23) + VAR34*(CONST022*VAR29**4 + CONST023*VAR25**4 + CONST179*VAR25*VAR29**3 + CONST180*VAR25**3*VAR29 + CONST204*VAR25**2*VAR29**2) + VAR35*(VAR07*(CONST116*VAR29*VAR33 - CONST163*VAR10*VAR29**2 + CONST190*VAR01*VAR29**3 + CONST272*VAR23) + VAR13*(-CONST069*VAR01*VAR29**2 + CONST099*VAR10*VAR29 + CONST103*VAR33)) + VAR37*(CONST002*VAR29*(CONST035*VAR25**3 + CONST153*VAR16*VAR25**2) + CONST004*VAR29**2*(CONST024*VAR25**2 - CONST182*VAR16*VAR25) + CONST006*VAR29**3*(CONST289*VAR25 + CONST291*VAR16) - CONST228*VAR16*VAR25**3 - CONST295*VAR29**4 + CONST298*VAR25**4) + VAR40*(VAR07*(CONST188*VAR14*VAR25**2 + CONST209*VAR25*VAR39 + CONST259*VAR11*VAR25**3 - CONST259*VAR26) + VAR13*(CONST166*VAR11*VAR25**2 + CONST176*VAR39 - CONST222*VAR14*VAR25) + VAR36*(CONST129*VAR11*VAR25 - CONST195*VAR14)) + VAR42*(CONST012*VAR11*VAR23 + VAR01*(CONST011*VAR26 + CONST157*VAR14*VAR16**2 + CONST198*VAR11*VAR16**3 + CONST202*VAR16*VAR39) + VAR10*(CONST029*VAR39 + CONST157*VAR11*VAR16**2 + CONST173*VAR14*VAR16) + VAR33*(CONST029*VAR14 + CONST203*VAR11*VAR16)) -g_y = CONST001*VAR03*VAR07*(CONST160*VAR10*VAR29**2 + CONST182*VAR29*VAR33 + CONST228*VAR01*VAR29**3 - CONST291*VAR23) + VAR00*(CONST001*VAR07*(CONST084*VAR10*VAR29**2 + CONST109*VAR29*VAR33 + CONST226*VAR01*VAR29**3 + CONST286*VAR23) + CONST003*VAR13*(CONST114*VAR01*VAR29**2 + CONST159*VAR10*VAR29 - CONST269*VAR33)) + VAR02*(CONST057*VAR07*VAR23 + VAR01*(CONST067*VAR29*VAR36 + CONST206*VAR07*VAR29**3 - CONST217*VAR13*VAR29**2) + VAR10*(-CONST113*VAR13*VAR29 - CONST185*VAR36 + CONST187*VAR07*VAR29**2) + VAR33*(CONST171*VAR13 + CONST257*VAR07*VAR29)) + VAR04*(CONST001*VAR07*(CONST110*VAR25*VAR39 + CONST224*VAR11*VAR25**3 - CONST224*VAR14*VAR25**2 + CONST287*VAR26) + CONST003*VAR13*(CONST114*VAR11*VAR25**2 + CONST159*VAR14*VAR25 - CONST269*VAR39)) + VAR17*(CONST002*VAR16*(CONST211*VAR25**2*VAR29 + CONST211*VAR25*VAR29**2 + CONST263*VAR25**3 + CONST263*VAR29**3) + CONST004*VAR16**2*(CONST077*VAR25**2 + CONST077*VAR29**2 + CONST106*VAR25*VAR29) + CONST006*VAR16**3*(CONST273*VAR25 + CONST274*VAR29) + CONST032*VAR16**4 + CONST033*VAR25**3*VAR29 + CONST033*VAR25*VAR29**3 + CONST034*VAR25**4 + CONST041*VAR25**2*VAR29**2 + CONST071*VAR29**4) + VAR18*(-CONST183*VAR10*VAR39 + CONST183*VAR14*VAR33 + CONST271*VAR01*VAR26 - CONST271*VAR11*VAR23) + VAR19*(CONST002*VAR16*(CONST065*VAR25**2*VAR29 - CONST248*VAR25**3 + CONST248*VAR25*VAR29**2 + CONST248*VAR29**3) + CONST004*VAR16**2*(CONST083*VAR29**2 + CONST225*VAR25**2) + CONST006*VAR16**3*(-CONST277*VAR25 + CONST277*VAR29) + CONST017*VAR29**4 + CONST026*VAR25*VAR29**3 + CONST293*VAR25**3*VAR29 + CONST296*VAR25**4) + VAR20*(CONST002*VAR16*(CONST040*VAR25**3 + CONST163*VAR25**2*VAR29 - CONST163*VAR25*VAR29**2 + CONST280*VAR29**3) + CONST020*VAR29**4 - CONST237*VAR25**3*VAR29 + CONST237*VAR25*VAR29**3 + CONST300*VAR25**4) + VAR21*(CONST253*VAR07*VAR26 + VAR11*(CONST082*VAR15 + CONST140*VAR25*VAR36 + CONST156*VAR13*VAR25**2 + CONST253*VAR07*VAR25**3) + VAR14*(CONST139*VAR36 + CONST202*VAR07*VAR25**2 + CONST303*VAR13*VAR25) + VAR39*(CONST156*VAR13 + CONST202*VAR07*VAR25)) + VAR24*(CONST002*VAR16*(CONST112*VAR01*VAR39 + CONST195*VAR11*VAR33) + CONST004*VAR16**2*(CONST195*VAR01*VAR14 - CONST195*VAR10*VAR11) + CONST038*VAR14*VAR33 + CONST284*VAR10*VAR39 - CONST284*VAR11*VAR23 + CONST285*VAR01*VAR26) + VAR27*(CONST258*VAR07*VAR26 + VAR11*(-CONST067*VAR25*VAR36 - CONST206*VAR07*VAR25**3 + CONST217*VAR13*VAR25**2) + VAR14*(CONST113*VAR13*VAR25 + CONST185*VAR36 - CONST187*VAR07*VAR25**2) + VAR39*(CONST059*VAR07*VAR25 - CONST171*VAR13)) + VAR30*(CONST292*VAR01*VAR26 + VAR11*(CONST151*VAR01*VAR16**3 - CONST165*VAR16*VAR33 + CONST207*VAR10*VAR16**2 + CONST292*VAR23) + VAR14*(CONST207*VAR01*VAR16**2 + CONST223*VAR10*VAR16 + CONST270*VAR33) + VAR39*(-CONST165*VAR01*VAR16 + CONST270*VAR10)) + VAR35*(CONST002*VAR16*(CONST103*VAR11*VAR33 + CONST138*VAR10*VAR14 - CONST205*VAR01*VAR39) - CONST237*VAR10*VAR39 - CONST237*VAR14*VAR33 + CONST272*VAR01*VAR26 + CONST272*VAR11*VAR23) + VAR37*(CONST108*VAR07*VAR11*VAR25**3 - CONST134*VAR07*VAR25*VAR39 + CONST262*VAR07*VAR14*VAR25**2 + CONST282*VAR07*VAR26) + VAR38*(CONST137*VAR25**2*VAR29**2 + CONST170*VAR25**4 + CONST170*VAR29**4 + CONST215*VAR25**3*VAR29 + CONST215*VAR25*VAR29**3) + VAR40*(CONST002*VAR16*(CONST117*VAR25**2*VAR29 + CONST117*VAR25*VAR29**2 + CONST259*VAR29**3 + CONST260*VAR25**3) + CONST004*VAR16**2*(CONST058*VAR25**2 + CONST058*VAR29**2 + CONST176*VAR25*VAR29) + CONST018*VAR25**4 + CONST019*VAR29**4 + CONST249*VAR25**2*VAR29**2 + CONST284*VAR25*VAR29**3 + CONST285*VAR25**3*VAR29) + VAR42*(CONST252*VAR07*VAR23 + VAR01*(CONST081*VAR15 + CONST139*VAR29*VAR36 + CONST157*VAR13*VAR29**2 + CONST252*VAR07*VAR29**3) + VAR10*(CONST140*VAR36 + CONST202*VAR07*VAR29**2 + CONST303*VAR13*VAR29) + VAR33*(CONST157*VAR13 + CONST203*VAR07*VAR29)) -g_z = VAR00*(CONST007*VAR29**4 + CONST189*VAR25**4 + CONST256*VAR25**2*VAR29**2 + CONST269*VAR25**3*VAR29 + VAR16**2*(CONST114*VAR25**2 + CONST114*VAR29**2 + CONST168*VAR25*VAR29) + VAR16*(CONST062*VAR25**2*VAR29 + CONST133*VAR25*VAR29**2 + CONST213*VAR25**3 + CONST226*VAR29**3)) + VAR02*(CONST002*VAR25*(CONST021*VAR29**3 - CONST201*VAR16**2*VAR29 + CONST219*VAR16*VAR29**2 - CONST267*VAR16**3) + CONST004*VAR25**2*(CONST233*VAR16**2 + CONST283*VAR16*VAR29 - CONST301*VAR29**2) + CONST107*VAR16*VAR25**3 - CONST187*VAR16**2*VAR29**2 + CONST216*VAR16**3*VAR29 + CONST239*VAR16*VAR29**3 + CONST297*VAR25**4 - CONST302*VAR29**4) + VAR03*(CONST075*VAR29**4 + CONST091*VAR25**3*VAR29 + CONST244*VAR25*VAR29**3 + CONST251*VAR25**2*VAR29**2 + CONST295*VAR25**4 + VAR16*(CONST079*VAR25**3 + CONST142*VAR25**2*VAR29 - CONST142*VAR25*VAR29**2 + CONST228*VAR29**3)) + VAR04*(CONST043*VAR11*VAR23 + CONST269*VAR10*VAR39 + CONST294*VAR01*VAR26 + VAR16**2*(CONST054*VAR01*VAR14 + CONST261*VAR10*VAR11) + VAR16*(CONST121*VAR11*VAR33 + CONST145*VAR01*VAR39 + CONST154*VAR10*VAR14)) + VAR07*VAR18*(-CONST066*VAR25*VAR39 + CONST149*VAR14*VAR25**2 - CONST183*VAR11*VAR25**3 + CONST271*VAR26) + VAR07*VAR38*(CONST066*VAR29*VAR33 - CONST149*VAR10*VAR29**2 + CONST183*VAR01*VAR29**3 - CONST271*VAR23) + VAR17*(CONST246*VAR01*VAR15 + VAR07*(CONST047*VAR01*VAR29**3 + CONST047*VAR23 + CONST088*VAR29*VAR33 + CONST089*VAR10*VAR29**2) + VAR13*(CONST131*VAR10*VAR29 + CONST178*VAR01*VAR29**2 + CONST178*VAR33) + VAR36*(CONST141*VAR01*VAR29 + CONST141*VAR10)) + VAR19*(CONST161*VAR10*VAR36 - CONST250*VAR01*VAR15 + VAR07*(CONST039*VAR01*VAR29**3 + CONST238*VAR29*VAR33 + CONST255*VAR23) + VAR13*(CONST123*VAR10*VAR29 - CONST165*VAR33 + CONST218*VAR01*VAR29**2)) + VAR20*(VAR07*(-CONST147*VAR29*VAR33 + CONST205*VAR01*VAR29**3 + CONST265*VAR23) + VAR13*(-CONST069*VAR01*VAR29**2 + CONST099*VAR10*VAR29 + CONST103*VAR33)) + VAR21*(CONST011*VAR01*VAR26 + VAR11*(CONST011*VAR23 + CONST156*VAR10*VAR16**2 + CONST199*VAR01*VAR16**3 + CONST202*VAR16*VAR33) + VAR14*(CONST029*VAR33 + CONST157*VAR01*VAR16**2 + CONST173*VAR10*VAR16) + VAR39*(CONST029*VAR10 + CONST202*VAR01*VAR16)) + VAR22*(CONST022*VAR25**4 + CONST023*VAR29**4 + CONST179*VAR25**3*VAR29 + CONST180*VAR25*VAR29**3 + CONST204*VAR25**2*VAR29**2) + VAR24*(VAR07*(CONST092*VAR14*VAR25**2 + CONST105*VAR11*VAR25**3 + CONST242*VAR25*VAR39 + CONST285*VAR26) + VAR13*(CONST112*VAR39 + CONST222*VAR11*VAR25**2) + VAR36*(-CONST130*VAR11*VAR25 + CONST195*VAR14)) + VAR27*(VAR11*(-CONST155*VAR16*VAR33 - CONST184*VAR01*VAR16**3 + CONST217*VAR10*VAR16**2 + CONST288*VAR23) + VAR14*(-CONST143*VAR10*VAR16 + CONST172*VAR01*VAR16**2 + CONST276*VAR33) + VAR39*(CONST059*VAR01*VAR16 + CONST290*VAR10)) + VAR30*(CONST292*VAR07*VAR26 + VAR11*(CONST124*VAR25*VAR36 + CONST191*VAR13*VAR25**2 + CONST229*VAR07*VAR25**3 - CONST250*VAR15) + VAR14*(CONST192*VAR36 + CONST196*VAR07*VAR25**2 + CONST223*VAR13*VAR25) + VAR39*(-CONST218*VAR13 + CONST221*VAR07*VAR25)) + VAR34*(CONST132*VAR14*VAR33 + CONST175*VAR10*VAR39 + CONST234*VAR01*VAR26 - CONST234*VAR11*VAR23) + VAR35*(VAR07*(CONST116*VAR25*VAR39 - CONST163*VAR14*VAR25**2 + CONST190*VAR11*VAR25**3 + CONST272*VAR26) + VAR13*(CONST099*VAR14*VAR25 - CONST205*VAR39 + CONST241*VAR11*VAR25**2)) + VAR37*(CONST275*VAR01*VAR26 + VAR11*(-CONST134*VAR16*VAR33 + CONST266*VAR23) + VAR14*(-CONST214*VAR33 + CONST227*VAR10*VAR16) + VAR39*(CONST053*VAR10 - CONST134*VAR01*VAR16)) + VAR40*(VAR07*(CONST188*VAR10*VAR29**2 + CONST210*VAR29*VAR33 + CONST260*VAR01*VAR29**3 - CONST260*VAR23) + VAR13*(CONST166*VAR01*VAR29**2 + CONST177*VAR33 - CONST222*VAR10*VAR29) + VAR36*(CONST129*VAR01*VAR29 - CONST195*VAR10)) + VAR42*(CONST000*VAR29**4 + CONST002*VAR25*(CONST100*VAR29**3 + CONST115*VAR16**2*VAR29 + CONST231*VAR16**3 + CONST235*VAR16*VAR29**2) + CONST004*VAR25**2*(CONST008*VAR29**2 + CONST087*VAR16**2 + CONST236*VAR16*VAR29) + CONST006*VAR25**3*(CONST005*VAR29 + CONST278*VAR16) + CONST015*VAR25**4 + CONST028*VAR16**4 + CONST087*VAR16**2*VAR29**2 + CONST232*VAR16**3*VAR29 + CONST278*VAR16*VAR29**3) +g_x = g_0*(CONST021*VAR20 + CONST022*VAR02 + CONST179*VAR04*VAR26 + CONST180*VAR08*VAR22 + CONST204*VAR06*VAR24) + g_1*y*(CONST065*VAR08*VAR23 - CONST149*VAR06*VAR25 + CONST183*VAR04*z - CONST271*VAR21) + g_10*(CONST012*VAR21*x + VAR23*(CONST028*VAR07 + CONST203*VAR17*x) + VAR25*(CONST028*VAR05 + CONST157*VAR15*x + CONST173*VAR07*VAR17) + z*(CONST011*VAR03 + CONST157*VAR07*VAR15 + CONST198*VAR13*x + CONST202*VAR05*VAR17)) + g_11*(CONST150*VAR07*VAR14 + CONST250*VAR12*x + VAR16*(CONST093*VAR24*x + CONST165*VAR05 + CONST186*VAR07*VAR26) + y*(CONST059*VAR03 + CONST071*VAR05*VAR26 + CONST281*VAR22*x)) + g_12*(VAR23*(CONST257*VAR17*x - CONST290*VAR07) + VAR25*(CONST044*VAR05 + CONST143*VAR07*VAR17 - CONST172*VAR15*x) + z*(CONST155*VAR05*VAR17 + CONST184*VAR13*x - CONST217*VAR07*VAR15 - CONST288*VAR03)) + g_13*(VAR14*(CONST129*VAR26*x - CONST195*VAR07) + VAR16*(CONST166*VAR24*x + CONST176*VAR05 - CONST222*VAR07*VAR26) + y*(CONST188*VAR07*VAR24 + CONST209*VAR05*VAR26 - CONST259*VAR03 + CONST259*VAR22*x)) + g_14*(CONST042*VAR03*z + CONST268*VAR07*VAR23 + CONST294*VAR21*x + VAR15*(CONST053*VAR25*x + CONST261*VAR07*z) + VAR17*(CONST119*VAR05*z + CONST144*VAR23*x + CONST152*VAR07*VAR25)) + g_15*(VAR16*(CONST068*VAR24*x - CONST099*VAR07*VAR26 + CONST205*VAR05) + y*(CONST050*VAR03 + CONST147*VAR05*VAR26 - CONST205*VAR22*x)) + g_16*(CONST214*VAR05*VAR25 - CONST264*VAR03*z + CONST264*VAR07*VAR23 - CONST275*VAR21*x + VAR17*(CONST079*VAR07*VAR25 + CONST134*VAR05*z + CONST134*VAR23*x)) + g_17*y*(CONST065*VAR05*VAR26 - CONST149*VAR07*VAR24 + CONST183*VAR22*x - CONST271*VAR03) + g_18*(CONST132*VAR05*VAR25 + CONST175*VAR07*VAR23 - CONST234*VAR03*z + CONST234*VAR21*x) + g_2*(CONST002*VAR08*(CONST034*VAR22 + CONST153*VAR17*VAR24) + CONST004*VAR06*(CONST023*VAR24 - CONST182*VAR17*VAR26) + CONST006*VAR04*(CONST289*VAR26 + CONST291*VAR17) - CONST228*VAR17*VAR22 - CONST295*VAR02 + CONST298*VAR20) + g_3*(VAR16*(-CONST068*VAR06*z + CONST099*VAR08*VAR25 + CONST103*VAR23) + y*(CONST116*VAR08*VAR23 - CONST163*VAR06*VAR25 + CONST190*VAR04*z + CONST272*VAR21)) + g_4*(CONST007*VAR20 + CONST014*VAR02 + CONST254*VAR06*VAR24 + CONST269*VAR04*VAR26 + VAR15*(CONST114*VAR06 + CONST114*VAR24 + CONST168*VAR08*VAR26) + VAR17*(CONST060*VAR06*VAR26 + CONST133*VAR08*VAR24 + CONST212*VAR04 + CONST224*VAR22)) + g_5*(VAR14*(CONST130*VAR08*z - CONST195*VAR25) + VAR16*(CONST195*VAR23 - CONST222*VAR06*z) + y*(CONST067*VAR08*VAR23 + CONST200*VAR04*z + CONST220*VAR06*VAR25 - CONST284*VAR21)) + g_6*(CONST002*VAR08*(CONST201*VAR15*VAR26 - CONST219*VAR17*VAR24 + CONST267*VAR13 + CONST299*VAR22) + CONST004*VAR06*(CONST036*VAR17*VAR26 - CONST233*VAR15 + CONST301*VAR24) + CONST187*VAR15*VAR24 + CONST197*VAR04*VAR17 - CONST216*VAR13*VAR26 - CONST239*VAR17*VAR22 - CONST297*VAR02 + CONST302*VAR20) + g_7*(CONST002*VAR08*(-CONST186*VAR16*VAR25 + CONST192*VAR14*z + CONST270*VAR23*y) + CONST004*VAR06*(-CONST218*VAR16*z + CONST270*VAR25*y) + CONST193*VAR14*VAR25 - CONST218*VAR16*VAR23 + CONST229*VAR04*y*z - CONST250*VAR12*z + CONST292*VAR21*y) + g_8*(CONST000*VAR20 + CONST002*VAR08*(CONST005*VAR22 + CONST115*VAR15*VAR26 + CONST230*VAR13 + CONST235*VAR17*VAR24) + CONST004*VAR06*(CONST008*VAR24 + CONST085*VAR15 + CONST235*VAR17*VAR26) + CONST006*VAR04*(CONST009*VAR26 + CONST278*VAR17) + CONST015*VAR02 + CONST024*VAR11 + CONST085*VAR15*VAR24 + CONST231*VAR13*VAR26 + CONST278*VAR17*VAR22) + g_9*(CONST245*VAR12*x + VAR14*(CONST141*VAR07 + CONST141*VAR26*x) + VAR16*(CONST131*VAR07*VAR26 + CONST178*VAR05 + CONST178*VAR24*x) + y*(CONST045*VAR03 + CONST046*VAR22*x + CONST087*VAR05*VAR26 + CONST088*VAR07*VAR24)) +g_y = CONST001*g_16*y*(CONST160*VAR06*VAR25 + CONST182*VAR08*VAR23 + CONST228*VAR04*z - CONST291*VAR21) + g_1*(-CONST183*VAR05*VAR25 + CONST183*VAR07*VAR23 + CONST271*VAR03*z - CONST271*VAR21*x) + g_10*(CONST252*VAR21*y + VAR23*(CONST157*VAR16 + CONST203*VAR08*y) + VAR25*(CONST140*VAR14 + CONST202*VAR06*y + CONST303*VAR08*VAR16) + z*(CONST080*VAR12 + CONST139*VAR08*VAR14 + CONST157*VAR06*VAR16 + CONST252*VAR04*y)) + g_11*(CONST002*VAR17*(CONST064*VAR08*VAR24 + CONST248*VAR04 + CONST248*VAR06*VAR26 - CONST248*VAR22) + CONST004*VAR15*(CONST082*VAR06 + CONST225*VAR24) + CONST006*VAR13*(CONST277*VAR08 - CONST277*VAR26) + CONST017*VAR02 + CONST025*VAR04*VAR26 + CONST293*VAR08*VAR22 + CONST296*VAR20) + g_12*(CONST056*VAR21*y + VAR23*(CONST171*VAR16 + CONST257*VAR08*y) + VAR25*(-CONST113*VAR08*VAR16 - CONST185*VAR14 + CONST187*VAR06*y) + z*(CONST066*VAR08*VAR14 + CONST206*VAR04*y - CONST217*VAR06*VAR16)) + g_13*(CONST002*VAR17*(CONST117*VAR06*VAR26 + CONST117*VAR08*VAR24 + CONST259*VAR04 + CONST260*VAR22) + CONST004*VAR15*(CONST055*VAR06 + CONST055*VAR24 + CONST176*VAR08*VAR26) + CONST018*VAR20 + CONST019*VAR02 + CONST249*VAR06*VAR24 + CONST284*VAR04*VAR26 + CONST285*VAR08*VAR22) + g_14*(CONST001*y*(CONST083*VAR06*VAR25 + CONST109*VAR08*VAR23 + CONST226*VAR04*z + CONST286*VAR21) + CONST003*VAR16*(CONST114*VAR06*z + CONST159*VAR08*VAR25 - CONST269*VAR23)) + g_15*(CONST002*VAR17*(CONST039*VAR22 - CONST163*VAR06*VAR26 + CONST163*VAR08*VAR24 + CONST279*VAR04) + CONST020*VAR02 + CONST237*VAR04*VAR26 - CONST237*VAR08*VAR22 + CONST300*VAR20) + g_17*(CONST137*VAR06*VAR24 + CONST170*VAR02 + CONST170*VAR20 + CONST215*VAR04*VAR26 + CONST215*VAR08*VAR22) + g_2*(CONST108*VAR22*x*y - CONST134*VAR05*VAR26*y + CONST262*VAR07*VAR24*y + CONST280*VAR03*y) + g_3*(CONST002*VAR17*(CONST103*VAR23*x + CONST138*VAR07*VAR25 - CONST205*VAR05*z) - CONST237*VAR05*VAR25 - CONST237*VAR07*VAR23 + CONST272*VAR03*z + CONST272*VAR21*x) + g_4*(CONST001*y*(CONST110*VAR05*VAR26 - CONST224*VAR07*VAR24 + CONST224*VAR22*x + CONST287*VAR03) + CONST003*VAR16*(CONST114*VAR24*x + CONST159*VAR07*VAR26 - CONST269*VAR05)) + g_5*(CONST002*VAR17*(CONST112*VAR05*z + CONST195*VAR23*x) + CONST004*VAR15*(CONST195*VAR07*z - CONST195*VAR25*x) + CONST037*VAR07*VAR23 + CONST284*VAR05*VAR25 - CONST284*VAR21*x + CONST285*VAR03*z) + g_6*(CONST258*VAR03*y + VAR05*(CONST057*VAR26*y - CONST171*VAR16) + VAR07*(CONST113*VAR16*VAR26 + CONST185*VAR14 - CONST187*VAR24*y) + x*(-CONST066*VAR14*VAR26 - CONST206*VAR22*y + CONST217*VAR16*VAR24)) + g_7*(CONST292*VAR03*z + VAR05*(-CONST165*VAR17*z + CONST270*VAR25) + VAR07*(CONST207*VAR15*z + CONST223*VAR17*VAR25 + CONST270*VAR23) + x*(CONST151*VAR13*z - CONST165*VAR17*VAR23 + CONST207*VAR15*VAR25 + CONST292*VAR21)) + g_8*(CONST253*VAR03*y + VAR05*(CONST156*VAR16 + CONST202*VAR26*y) + VAR07*(CONST139*VAR14 + CONST202*VAR24*y + CONST303*VAR16*VAR26) + x*(CONST081*VAR12 + CONST140*VAR14*VAR26 + CONST156*VAR16*VAR24 + CONST253*VAR22*y)) + g_9*(CONST002*VAR17*(CONST211*VAR06*VAR26 + CONST211*VAR08*VAR24 + CONST263*VAR04 + CONST263*VAR22) + CONST004*VAR15*(CONST076*VAR06 + CONST076*VAR24 + CONST106*VAR08*VAR26) + CONST006*VAR13*(CONST273*VAR26 + CONST274*VAR08) + CONST031*VAR11 + CONST032*VAR04*VAR26 + CONST032*VAR08*VAR22 + CONST033*VAR20 + CONST040*VAR06*VAR24 + CONST070*VAR02) +g_z = g_0*(CONST132*VAR07*VAR23 + CONST175*VAR05*VAR25 + CONST234*VAR03*z - CONST234*VAR21*x) + g_1*y*(-CONST065*VAR05*VAR26 + CONST149*VAR07*VAR24 - CONST183*VAR22*x + CONST271*VAR03) + g_10*(CONST000*VAR02 + CONST002*VAR26*(CONST100*VAR04 + CONST115*VAR08*VAR15 + CONST231*VAR13 + CONST235*VAR06*VAR17) + CONST004*VAR24*(CONST008*VAR06 + CONST086*VAR15 + CONST236*VAR08*VAR17) + CONST006*VAR22*(CONST005*VAR08 + CONST282*VAR17) + CONST015*VAR20 + CONST027*VAR11 + CONST086*VAR06*VAR15 + CONST232*VAR08*VAR13 + CONST282*VAR04*VAR17) + g_11*(CONST161*VAR14*VAR25 - CONST250*VAR12*z + VAR16*(CONST123*VAR08*VAR25 - CONST165*VAR23 + CONST218*VAR06*z) + y*(CONST038*VAR04*z + CONST238*VAR08*VAR23 + CONST255*VAR21)) + g_12*(CONST002*VAR26*(CONST097*VAR04 - CONST201*VAR08*VAR15 + CONST219*VAR06*VAR17 - CONST267*VAR13) + CONST004*VAR24*(CONST233*VAR15 + CONST283*VAR08*VAR17 - CONST301*VAR06) + CONST107*VAR17*VAR22 - CONST187*VAR06*VAR15 + CONST216*VAR08*VAR13 + CONST239*VAR04*VAR17 + CONST297*VAR20 - CONST302*VAR02) + g_13*(VAR14*(CONST129*VAR08*z - CONST195*VAR25) + VAR16*(CONST166*VAR06*z + CONST177*VAR23 - CONST222*VAR08*VAR25) + y*(CONST188*VAR06*VAR25 + CONST210*VAR08*VAR23 + CONST260*VAR04*z - CONST260*VAR21)) + g_14*(CONST007*VAR02 + CONST189*VAR20 + CONST256*VAR06*VAR24 + CONST269*VAR08*VAR22 + VAR15*(CONST114*VAR06 + CONST114*VAR24 + CONST168*VAR08*VAR26) + VAR17*(CONST061*VAR08*VAR24 + CONST133*VAR06*VAR26 + CONST213*VAR22 + CONST226*VAR04)) + g_15*(VAR16*(-CONST068*VAR06*z + CONST099*VAR08*VAR25 + CONST103*VAR23) + y*(-CONST147*VAR08*VAR23 + CONST205*VAR04*z + CONST265*VAR21)) + g_16*(CONST074*VAR02 + CONST090*VAR08*VAR22 + CONST244*VAR04*VAR26 + CONST251*VAR06*VAR24 + CONST295*VAR20 + VAR17*(CONST078*VAR22 - CONST142*VAR06*VAR26 + CONST142*VAR08*VAR24 + CONST228*VAR04)) + g_17*y*(CONST065*VAR08*VAR23 - CONST149*VAR06*VAR25 + CONST183*VAR04*z - CONST271*VAR21) + g_18*(CONST021*VAR02 + CONST022*VAR20 + CONST179*VAR08*VAR22 + CONST180*VAR04*VAR26 + CONST204*VAR06*VAR24) + g_2*(CONST275*VAR03*z + VAR05*(CONST052*VAR25 - CONST134*VAR17*z) + VAR07*(-CONST214*VAR23 + CONST227*VAR17*VAR25) + x*(-CONST134*VAR17*VAR23 + CONST266*VAR21)) + g_3*(VAR16*(CONST099*VAR07*VAR26 - CONST205*VAR05 + CONST241*VAR24*x) + y*(CONST116*VAR05*VAR26 - CONST163*VAR07*VAR24 + CONST190*VAR22*x + CONST272*VAR03)) + g_4*(CONST042*VAR21*x + CONST269*VAR05*VAR25 + CONST294*VAR03*z + VAR15*(CONST053*VAR07*z + CONST261*VAR25*x) + VAR17*(CONST121*VAR23*x + CONST145*VAR05*z + CONST154*VAR07*VAR25)) + g_5*(VAR14*(-CONST130*VAR26*x + CONST195*VAR07) + VAR16*(CONST112*VAR05 + CONST222*VAR24*x) + y*(CONST091*VAR07*VAR24 + CONST105*VAR22*x + CONST242*VAR05*VAR26 + CONST285*VAR03)) + g_6*(VAR05*(CONST057*VAR17*z + CONST290*VAR25) + VAR07*(-CONST143*VAR17*VAR25 + CONST172*VAR15*z + CONST276*VAR23) + x*(-CONST155*VAR17*VAR23 - CONST184*VAR13*z + CONST217*VAR15*VAR25 + CONST288*VAR21)) + g_7*(CONST292*VAR03*y + VAR05*(-CONST218*VAR16 + CONST221*VAR26*y) + VAR07*(CONST192*VAR14 + CONST196*VAR24*y + CONST223*VAR16*VAR26) + x*(CONST124*VAR14*VAR26 + CONST191*VAR16*VAR24 + CONST229*VAR22*y - CONST250*VAR12)) + g_8*(CONST011*VAR03*z + VAR05*(CONST028*VAR25 + CONST202*VAR17*z) + VAR07*(CONST028*VAR23 + CONST157*VAR15*z + CONST173*VAR17*VAR25) + x*(CONST011*VAR21 + CONST156*VAR15*VAR25 + CONST199*VAR13*z + CONST202*VAR17*VAR23)) + g_9*(CONST246*VAR12*z + VAR14*(CONST141*VAR08*z + CONST141*VAR25) + VAR16*(CONST131*VAR08*VAR25 + CONST178*VAR06*z + CONST178*VAR23) + y*(CONST046*VAR04*z + CONST046*VAR21 + CONST087*VAR08*VAR23 + CONST088*VAR06*VAR25)) diff --git a/notebooks/direct_sph_harm/l_10.json b/notebooks/direct_sph_harm/l_10.json new file mode 100644 index 0000000..fd9a7dc --- /dev/null +++ b/notebooks/direct_sph_harm/l_10.json @@ -0,0 +1,30 @@ +{ + "fwd": [ + "27.2034486491732*x**9*z - 326.441383790078*x**7*z**3 + 685.526905959165*x**5*z**5 - 326.441383790078*x**3*z**7 + 27.2034486491732*x*z**9", + "y*(12.1657520803952*x**9 - 437.967074894228*x**7*z**2 + 1532.8847621298*x**5*z**4 - 1021.9231747532*x**3*z**6 + 109.491768723557*x*z**8)", + "15.7883647328499*x**9*z - 94.7301883970997*x**7*z**3 + 94.7301883970997*x**3*z**7 - 15.7883647328499*x*z**9 + y**2*(-284.190565191299*x**7*z + 1989.33395633909*x**5*z**3 - 1989.33395633909*x**3*z**5 + 284.190565191299*x*z**7)", + "y**3*(-77.3468749368713*x**7 + 1624.2843736743*x**5*z**2 - 2707.14062279049*x**3*z**4 + 541.428124558099*x*z**6) + y*(14.5025390506634*x**9 - 290.050781013267*x**7*z**2 + 203.035546709287*x**5*z**4 + 406.071093418574*x**3*z**6 - 101.517773354644*x*z**8)", + "10.5521471197994*x**9*z + x**7*(-337.668707833581*y**2*z - 14.0695294930659*z**3) + x**5*(787.893651611688*y**4*z + 787.893651611688*y**2*z**3 - 49.2433532257305*z**5) + x**3*(-2626.31217203896*y**4*z**3 + 787.893651611688*y**2*z**5 - 14.0695294930659*z**7) + x*(787.893651611688*y**4*z**5 - 337.668707833581*y**2*z**7 + 10.5521471197994*z**9)", + "y**5*(176.178376404427*x**5 - 1761.78376404427*x**3*z**2 + 880.891882022136*x*z**4) + y**3*(-146.815313670356*x**7 + 1321.3378230332*x**5*z**2 + 734.07656835178*x**3*z**4 - 734.07656835178*x*z**6) + y*(15.7302121789667*x**9 - 125.841697431734*x**7*z**2 - 220.222970505534*x**5*z**4 + 78.6510608948335*x*z**8)", + "6.632439808434*x**9*z + x**7*(-278.562471954228*y**2*z + 13.264879616868*z**3) + x**5*(1114.24988781691*y**4*z - 278.562471954228*y**2*z**3) + x**3*(-742.833258544608*y**6*z + 278.562471954228*y**2*z**5 - 13.264879616868*z**7) + x*(742.833258544608*y**6*z**3 - 1114.24988781691*y**4*z**5 + 278.562471954228*y**2*z**7 - 6.632439808434*z**9)", + "y**7*(-150.074981259369*x**3 + 450.224943778107*x*z**2) + y**5*(393.946825805844*x**5 - 787.893651611688*x**3*z**2 - 1181.84047741753*x*z**4) + y**3*(-196.973412902922*x**7 + 196.973412902922*x**5*z**2 + 984.86706451461*x**3*z**4 + 590.920238708766*x*z**6) + y*(16.4144510752435*x**9 - 98.486706451461*x**5*z**4 - 131.315608601948*x**3*z**6 - 49.2433532257305*x*z**8)", + "3.21913870529156*x**9*z + x**7*(-154.518657853995*y**2*z + 12.8765548211663*z**3) + x**5*(772.593289269975*y**4*z - 463.555973561985*y**2*z**3 + 19.3148322317494*z**5) + x**3*(-824.099508554641*y**6*z + 1545.18657853995*y**4*z**3 - 463.555973561985*y**2*z**5 + 12.8765548211663*z**7) + x*(176.592751833137*y**8*z - 824.099508554641*y**6*z**3 + 772.593289269975*y**4*z**5 - 154.518657853995*y**2*z**7 + 3.21913870529156*z**9)", + "16.7271353825295*x**9*y + x**7*(-223.028471767059*y**3 + 66.9085415301178*y*z**2) + x**5*(535.268332240943*y**5 - 669.085415301178*y**3*z**2 + 100.362812295177*y*z**4) + x**3*(-305.867618423396*y**7 + 1070.53666448189*y**5*z**2 - 669.085415301178*y**3*z**4 + 66.9085415301178*y*z**6) + x*(33.9852909359329*y**9 - 305.867618423396*y**7*z**2 + 535.268332240943*y**5*z**4 - 223.028471767059*y**3*z**6 + 16.7271353825295*y*z**8)", + "-1.12774323743054*x**10 + x**8*(56.3871618715269*y**2 - 5.63871618715269*z**2) + x**6*(-300.731529981477*y**4 + 225.548647486108*y**2*z**2 - 11.2774323743054*z**4) + x**4*(360.877835977772*y**6 - 902.194589944431*y**4*z**2 + 338.322971229162*y**2*z**4 - 11.2774323743054*z**6) + x**2*(-103.107953136506*y**8 + 721.755671955545*y**6*z**2 - 902.194589944431*y**4*z**4 + 225.548647486108*y**2*z**6 - 5.63871618715269*z**8) + 4.58257569495584*y**10 - 103.107953136506*y**8*z**2 + 360.877835977772*y**6*z**4 - 300.731529981477*y**4*z**6 + 56.3871618715269*y**2*z**8 - 1.12774323743054*z**10", + "16.7271353825295*y*z**9 + z**7*(66.9085415301178*x**2*y - 223.028471767059*y**3) + z**5*(100.362812295177*x**4*y - 669.085415301178*x**2*y**3 + 535.268332240943*y**5) + z**3*(66.9085415301178*x**6*y - 669.085415301178*x**4*y**3 + 1070.53666448189*x**2*y**5 - 305.867618423396*y**7) + z*(16.7271353825295*x**8*y - 223.028471767059*x**6*y**3 + 535.268332240943*x**4*y**5 - 305.867618423396*x**2*y**7 + 33.9852909359329*y**9)", + "-1.60956935264578*x**10 + x**8*(77.2593289269976*y**2 - 4.82870805793735*z**2) + x**6*(-386.296644634988*y**4 + 154.518657853995*y**2*z**2 - 3.21913870529156*z**4) + x**4*(412.04975427732*y**6 - 386.296644634988*y**4*z**2 + 3.21913870529156*z**6) + x**2*(-88.2963759165686*y**8 + 386.296644634988*y**4*z**4 - 154.518657853995*y**2*z**6 + 4.82870805793735*z**8) + 88.2963759165686*y**8*z**2 - 412.04975427732*y**6*z**4 + 386.296644634988*y**4*z**6 - 77.2593289269975*y**2*z**8 + 1.60956935264578*z**10", + "y**7*(-450.224943778107*x**2*z + 150.074981259369*z**3) + y**5*(1181.84047741753*x**4*z + 787.893651611688*x**2*z**3 - 393.946825805844*z**5) + y**3*(-590.920238708766*x**6*z - 984.86706451461*x**4*z**3 - 196.973412902922*x**2*z**5 + 196.973412902922*z**7) + y*(49.2433532257305*x**8*z + 131.315608601948*x**6*z**3 + 98.486706451461*x**4*z**5 - 16.4144510752435*z**9)", + "-1.6581099521085*x**10 + x**8*(69.640617988557*y**2 + 4.9743298563255*z**2) + x**6*(-278.562471954228*y**4 - 278.562471954228*y**2*z**2 + 23.213539329519*z**4) + x**4*(185.708314636152*y**6 + 1392.81235977114*y**4*z**2 - 696.40617988557*y**2*z**4 + 23.213539329519*z**6) + x**2*(-1114.24988781691*y**6*z**2 + 1392.81235977114*y**4*z**4 - 278.562471954228*y**2*z**6 + 4.9743298563255*z**8) + 185.708314636152*y**6*z**4 - 278.562471954228*y**4*z**6 + 69.640617988557*y**2*z**8 - 1.6581099521085*z**10", + "y**5*(880.891882022136*x**4*z - 1761.78376404427*x**2*z**3 + 176.178376404427*z**5) + y**3*(-734.07656835178*x**6*z + 734.07656835178*x**4*z**3 + 1321.3378230332*x**2*z**5 - 146.815313670356*z**7) + y*(78.6510608948335*x**8*z - 220.222970505534*x**4*z**5 - 125.841697431734*x**2*z**7 + 15.7302121789667*z**9)", + "-1.75869118663323*x**10 + x**8*(56.2781179722634*y**2 + 22.862985426232*z**2) + x**6*(-131.315608601948*y**4 - 787.893651611688*y**2*z**2 + 24.6216766128653*z**4) + x**4*(1969.73412902922*y**4*z**2 - 24.6216766128653*z**6) + x**2*(-1969.73412902922*y**4*z**4 + 787.893651611688*y**2*z**6 - 22.862985426232*z**8) + 131.315608601948*y**4*z**6 - 56.2781179722634*y**2*z**8 + 1.75869118663323*z**10", + "y**3*(-541.428124558099*x**6*z + 2707.14062279049*x**4*z**3 - 1624.2843736743*x**2*z**5 + 77.3468749368712*z**7) + y*(101.517773354644*x**8*z - 406.071093418574*x**6*z**3 - 203.035546709287*x**4*z**5 + 290.050781013267*x**2*z**7 - 14.5025390506634*z**9)", + "-1.97354559160624*x**10 + 53.2857309733686*x**8*z**2 - 82.8889148474622*x**6*z**4 - 82.8889148474622*x**4*z**6 + 53.2857309733686*x**2*z**8 + y**2*(35.5238206489124*x**8 - 994.666978169547*x**6*z**2 + 2486.66744542387*x**4*z**4 - 994.666978169547*x**2*z**6 + 35.5238206489124*z**8) - 1.97354559160624*z**10", + "y*(109.491768723557*x**8*z - 1021.9231747532*x**6*z**3 + 1532.8847621298*x**4*z**5 - 437.967074894228*x**2*z**7 + 12.1657520803952*z**9)", + "-2.72034486491732*x**10 + 122.415518921279*x**8*z**2 - 571.272421632637*x**6*z**4 + 571.272421632637*x**4*z**6 - 122.415518921279*x**2*z**8 + 2.72034486491732*z**10" + ], + "bwd": { + "x": "g_0*(244.831037842559*x**8*z - 2285.08968653055*x**6*z**3 + 3427.63452979582*x**4*z**5 - 979.324151370235*x**2*z**7 + 27.2034486491732*z**9) + g_1*y*(109.491768723557*x**8 - 3065.7695242596*x**6*z**2 + 7664.42381064899*x**4*z**4 - 3065.7695242596*x**2*z**6 + 109.491768723557*z**8) + g_10*(-11.2774323743054*x**9 + 8.0*x**7*(56.3871618715269*y**2 - 5.63871618715269*z**2) + 6.0*x**5*(-300.731529981477*y**4 + 225.548647486108*y**2*z**2 - 11.2774323743054*z**4) + 4.0*x**3*(360.877835977772*y**6 - 902.194589944431*y**4*z**2 + 338.322971229162*y**2*z**4 - 11.2774323743054*z**6) + 2.0*x*(-103.107953136506*y**8 + 721.755671955545*y**6*z**2 - 902.194589944431*y**4*z**4 + 225.548647486108*y**2*z**6 - 5.63871618715269*z**8)) + g_11*(133.817083060236*x*y*z**7 + z**5*(401.451249180707*x**3*y - 1338.17083060236*x*y**3) + z**3*(401.451249180707*x**5*y - 2676.34166120471*x**3*y**3 + 2141.07332896377*x*y**5) + z*(133.817083060236*x**7*y - 1338.17083060236*x**5*y**3 + 2141.07332896377*x**3*y**5 - 611.735236846792*x*y**7)) + g_12*(-16.0956935264578*x**9 + 8.0*x**7*(77.2593289269976*y**2 - 4.82870805793735*z**2) + 6.0*x**5*(-386.296644634988*y**4 + 154.518657853995*y**2*z**2 - 3.21913870529156*z**4) + 4.0*x**3*(412.04975427732*y**6 - 386.296644634988*y**4*z**2 + 3.21913870529156*z**6) + 2.0*x*(-88.2963759165686*y**8 + 386.296644634988*y**4*z**4 - 154.518657853995*y**2*z**6 + 4.82870805793735*z**8)) + g_13*(-900.449887556215*x*y**7*z + y**5*(4727.36190967013*x**3*z + 1575.78730322338*x*z**3) + y**3*(-3545.5214322526*x**5*z - 3939.46825805844*x**3*z**3 - 393.946825805844*x*z**5) + y*(393.946825805844*x**7*z + 787.893651611688*x**5*z**3 + 393.946825805844*x**3*z**5)) + g_14*(-16.581099521085*x**9 + 8.0*x**7*(69.640617988557*y**2 + 4.9743298563255*z**2) + 6.0*x**5*(-278.562471954228*y**4 - 278.562471954228*y**2*z**2 + 23.213539329519*z**4) + 4.0*x**3*(185.708314636152*y**6 + 1392.81235977114*y**4*z**2 - 696.40617988557*y**2*z**4 + 23.213539329519*z**6) + 2.0*x*(-1114.24988781691*y**6*z**2 + 1392.81235977114*y**4*z**4 - 278.562471954228*y**2*z**6 + 4.9743298563255*z**8)) + g_15*(y**5*(3523.56752808854*x**3*z - 3523.56752808854*x*z**3) + y**3*(-4404.45941011068*x**5*z + 2936.30627340712*x**3*z**3 + 2642.67564606641*x*z**5) + y*(629.208487158668*x**7*z - 880.891882022136*x**3*z**5 - 251.683394863467*x*z**7)) + g_16*(-17.5869118663323*x**9 + 8.0*x**7*(56.2781179722634*y**2 + 22.862985426232*z**2) + 6.0*x**5*(-131.315608601948*y**4 - 787.893651611688*y**2*z**2 + 24.6216766128653*z**4) + 4.0*x**3*(1969.73412902922*y**4*z**2 - 24.6216766128653*z**6) + 2.0*x*(-1969.73412902922*y**4*z**4 + 787.893651611688*y**2*z**6 - 22.862985426232*z**8)) + g_17*(y**3*(-3248.56874734859*x**5*z + 10828.562491162*x**3*z**3 - 3248.56874734859*x*z**5) + y*(812.142186837148*x**7*z - 2436.42656051144*x**5*z**3 - 812.142186837148*x**3*z**5 + 580.101562026534*x*z**7)) + g_18*(-19.7354559160624*x**9 + 426.285847786949*x**7*z**2 - 497.333489084773*x**5*z**4 - 331.555659389849*x**3*z**6 + 106.571461946737*x*z**8 + y**2*(284.190565191299*x**7 - 5968.00186901728*x**5*z**2 + 9946.66978169547*x**3*z**4 - 1989.33395633909*x*z**6)) + g_19*y*(875.934149788456*x**7*z - 6131.53904851919*x**5*z**3 + 6131.53904851919*x**3*z**5 - 875.934149788456*x*z**7) + g_2*(142.09528259565*x**8*z - 663.111318779698*x**6*z**3 + 284.190565191299*x**2*z**7 + y**2*(-1989.33395633909*x**6*z + 9946.66978169547*x**4*z**3 - 5968.00186901728*x**2*z**5 + 284.190565191299*z**7) - 15.7883647328499*z**9) + g_20*(-27.2034486491732*x**9 + 979.324151370235*x**7*z**2 - 3427.63452979582*x**5*z**4 + 2285.08968653055*x**3*z**6 - 244.831037842559*x*z**8) + g_3*(y**3*(-541.428124558099*x**6 + 8121.42186837148*x**4*z**2 - 8121.42186837148*x**2*z**4 + 541.428124558099*z**6) + y*(130.52285145597*x**8 - 2030.35546709287*x**6*z**2 + 1015.17773354644*x**4*z**4 + 1218.21328025572*x**2*z**6 - 101.517773354644*z**8)) + g_4*(94.9693240781945*x**8*z + 7.0*x**6*(-337.668707833581*y**2*z - 14.0695294930659*z**3) + 5.0*x**4*(787.893651611688*y**4*z + 787.893651611688*y**2*z**3 - 49.2433532257305*z**5) + 3.0*x**2*(-2626.31217203896*y**4*z**3 + 787.893651611688*y**2*z**5 - 14.0695294930659*z**7) + 787.893651611688*y**4*z**5 - 337.668707833581*y**2*z**7 + 10.5521471197994*z**9) + g_5*(y**5*(880.891882022136*x**4 - 5285.35129213281*x**2*z**2 + 880.891882022136*z**4) + y**3*(-1027.70719569249*x**6 + 6606.68911516602*x**4*z**2 + 2202.22970505534*x**2*z**4 - 734.07656835178*z**6) + y*(141.5719096107*x**8 - 880.891882022136*x**6*z**2 - 1101.11485252767*x**4*z**4 + 78.6510608948335*z**8)) + g_6*(59.691958275906*x**8*z + 7.0*x**6*(-278.562471954228*y**2*z + 13.264879616868*z**3) + 5.0*x**4*(1114.24988781691*y**4*z - 278.562471954228*y**2*z**3) + 3.0*x**2*(-742.833258544608*y**6*z + 278.562471954228*y**2*z**5 - 13.264879616868*z**7) + 742.833258544608*y**6*z**3 - 1114.24988781691*y**4*z**5 + 278.562471954228*y**2*z**7 - 6.632439808434*z**9) + g_7*(y**7*(-450.224943778107*x**2 + 450.224943778107*z**2) + y**5*(1969.73412902922*x**4 - 2363.68095483506*x**2*z**2 - 1181.84047741753*z**4) + y**3*(-1378.81389032045*x**6 + 984.86706451461*x**4*z**2 + 2954.60119354383*x**2*z**4 + 590.920238708766*z**6) + y*(147.730059677192*x**8 - 492.433532257305*x**4*z**4 - 393.946825805844*x**2*z**6 - 49.2433532257305*z**8)) + g_8*(28.9722483476241*x**8*z + 7.0*x**6*(-154.518657853995*y**2*z + 12.8765548211663*z**3) + 5.0*x**4*(772.593289269975*y**4*z - 463.555973561985*y**2*z**3 + 19.3148322317494*z**5) + 3.0*x**2*(-824.099508554641*y**6*z + 1545.18657853995*y**4*z**3 - 463.555973561985*y**2*z**5 + 12.8765548211663*z**7) + 176.592751833137*y**8*z - 824.099508554641*y**6*z**3 + 772.593289269975*y**4*z**5 - 154.518657853995*y**2*z**7 + 3.21913870529156*z**9) + g_9*(150.544218442765*x**8*y + 7.0*x**6*(-223.028471767059*y**3 + 66.9085415301178*y*z**2) + 5.0*x**4*(535.268332240943*y**5 - 669.085415301178*y**3*z**2 + 100.362812295177*y*z**4) + 3.0*x**2*(-305.867618423396*y**7 + 1070.53666448189*y**5*z**2 - 669.085415301178*y**3*z**4 + 66.9085415301178*y*z**6) + 33.9852909359329*y**9 - 305.867618423396*y**7*z**2 + 535.268332240943*y**5*z**4 - 223.028471767059*y**3*z**6 + 16.7271353825295*y*z**8)", + "y": "g_1*(12.1657520803952*x**9 - 437.967074894228*x**7*z**2 + 1532.8847621298*x**5*z**4 - 1021.9231747532*x**3*z**6 + 109.491768723557*x*z**8) + g_10*(112.774323743054*x**8*y + x**6*(-1202.92611992591*y**3 + 451.097294972216*y*z**2) + x**4*(2165.26701586663*y**5 - 3608.77835977772*y**3*z**2 + 676.645942458323*y*z**4) + x**2*(-824.863625092051*y**7 + 4330.53403173327*y**5*z**2 - 3608.77835977772*y**3*z**4 + 451.097294972216*y*z**6) + 45.8257569495584*y**9 - 824.863625092051*y**7*z**2 + 2165.26701586663*y**5*z**4 - 1202.92611992591*y**3*z**6 + 112.774323743054*y*z**8) + g_11*(16.7271353825295*z**9 + z**7*(66.9085415301178*x**2 - 669.085415301178*y**2) + z**5*(100.362812295177*x**4 - 2007.25624590353*x**2*y**2 + 2676.34166120471*y**4) + z**3*(66.9085415301178*x**6 - 2007.25624590353*x**4*y**2 + 5352.68332240943*x**2*y**4 - 2141.07332896377*y**6) + z*(16.7271353825295*x**8 - 669.085415301178*x**6*y**2 + 2676.34166120471*x**4*y**4 - 2141.07332896377*x**2*y**6 + 305.867618423396*y**8)) + g_12*(154.518657853995*x**8*y + x**6*(-1545.18657853995*y**3 + 309.03731570799*y*z**2) + x**4*(2472.29852566392*y**5 - 1545.18657853995*y**3*z**2) + x**2*(-706.371007332549*y**7 + 1545.18657853995*y**3*z**4 - 309.03731570799*y*z**6) + 706.371007332549*y**7*z**2 - 2472.29852566392*y**5*z**4 + 1545.18657853995*y**3*z**6 - 154.518657853995*y*z**8) + g_13*(49.2433532257305*x**8*z + 131.315608601948*x**6*z**3 + 98.486706451461*x**4*z**5 + 7.0*y**6*(-450.224943778107*x**2*z + 150.074981259369*z**3) + 5.0*y**4*(1181.84047741753*x**4*z + 787.893651611688*x**2*z**3 - 393.946825805844*z**5) + 3.0*y**2*(-590.920238708766*x**6*z - 984.86706451461*x**4*z**3 - 196.973412902922*x**2*z**5 + 196.973412902922*z**7) - 16.4144510752435*z**9) + g_14*(139.281235977114*x**8*y + x**6*(-1114.24988781691*y**3 - 557.124943908456*y*z**2) + x**4*(1114.24988781691*y**5 + 5571.24943908456*y**3*z**2 - 1392.81235977114*y*z**4) + x**2*(-6685.49932690147*y**5*z**2 + 5571.24943908456*y**3*z**4 - 557.124943908456*y*z**6) + 1114.24988781691*y**5*z**4 - 1114.24988781691*y**3*z**6 + 139.281235977114*y*z**8) + g_15*(78.6510608948335*x**8*z - 220.222970505534*x**4*z**5 - 125.841697431734*x**2*z**7 + 5.0*y**4*(880.891882022136*x**4*z - 1761.78376404427*x**2*z**3 + 176.178376404427*z**5) + 3.0*y**2*(-734.07656835178*x**6*z + 734.07656835178*x**4*z**3 + 1321.3378230332*x**2*z**5 - 146.815313670356*z**7) + 15.7302121789667*z**9) + g_16*(112.556235944527*x**8*y + x**6*(-525.262434407792*y**3 - 1575.78730322338*y*z**2) + 7878.93651611688*x**4*y**3*z**2 + x**2*(-7878.93651611688*y**3*z**4 + 1575.78730322338*y*z**6) + 525.262434407792*y**3*z**6 - 112.556235944527*y*z**8) + g_17*(101.517773354644*x**8*z - 406.071093418574*x**6*z**3 - 203.035546709287*x**4*z**5 + 290.050781013267*x**2*z**7 + 3.0*y**2*(-541.428124558099*x**6*z + 2707.14062279049*x**4*z**3 - 1624.2843736743*x**2*z**5 + 77.3468749368712*z**7) - 14.5025390506634*z**9) + 2.0*g_18*y*(35.5238206489124*x**8 - 994.666978169547*x**6*z**2 + 2486.66744542387*x**4*z**4 - 994.666978169547*x**2*z**6 + 35.5238206489124*z**8) + g_19*(109.491768723557*x**8*z - 1021.9231747532*x**6*z**3 + 1532.8847621298*x**4*z**5 - 437.967074894228*x**2*z**7 + 12.1657520803952*z**9) + 2.0*g_2*y*(-284.190565191299*x**7*z + 1989.33395633909*x**5*z**3 - 1989.33395633909*x**3*z**5 + 284.190565191299*x*z**7) + g_3*(14.5025390506634*x**9 - 290.050781013267*x**7*z**2 + 203.035546709287*x**5*z**4 + 406.071093418574*x**3*z**6 - 101.517773354644*x*z**8 + 3.0*y**2*(-77.3468749368713*x**7 + 1624.2843736743*x**5*z**2 - 2707.14062279049*x**3*z**4 + 541.428124558099*x*z**6)) + g_4*(-675.337415667161*x**7*y*z + x**5*(3151.57460644675*y**3*z + 1575.78730322338*y*z**3) + x**3*(-10505.2486881558*y**3*z**3 + 1575.78730322338*y*z**5) + x*(3151.57460644675*y**3*z**5 - 675.337415667161*y*z**7)) + g_5*(15.7302121789667*x**9 - 125.841697431734*x**7*z**2 - 220.222970505534*x**5*z**4 + 78.6510608948335*x*z**8 + 5.0*y**4*(176.178376404427*x**5 - 1761.78376404427*x**3*z**2 + 880.891882022136*x*z**4) + 3.0*y**2*(-146.815313670356*x**7 + 1321.3378230332*x**5*z**2 + 734.07656835178*x**3*z**4 - 734.07656835178*x*z**6)) + g_6*(-557.124943908456*x**7*y*z + x**5*(4456.99955126765*y**3*z - 557.124943908456*y*z**3) + x**3*(-4456.99955126765*y**5*z + 557.124943908456*y*z**5) + x*(4456.99955126765*y**5*z**3 - 4456.99955126765*y**3*z**5 + 557.124943908456*y*z**7)) + g_7*(16.4144510752435*x**9 - 98.486706451461*x**5*z**4 - 131.315608601948*x**3*z**6 - 49.2433532257305*x*z**8 + 7.0*y**6*(-150.074981259369*x**3 + 450.224943778107*x*z**2) + 5.0*y**4*(393.946825805844*x**5 - 787.893651611688*x**3*z**2 - 1181.84047741753*x*z**4) + 3.0*y**2*(-196.973412902922*x**7 + 196.973412902922*x**5*z**2 + 984.86706451461*x**3*z**4 + 590.920238708766*x*z**6)) + g_8*(-309.03731570799*x**7*y*z + x**5*(3090.3731570799*y**3*z - 927.111947123971*y*z**3) + x**3*(-4944.59705132784*y**5*z + 6180.7463141598*y**3*z**3 - 927.111947123971*y*z**5) + x*(1412.7420146651*y**7*z - 4944.59705132784*y**5*z**3 + 3090.3731570799*y**3*z**5 - 309.03731570799*y*z**7)) + g_9*(16.7271353825295*x**9 + x**7*(-669.085415301178*y**2 + 66.9085415301178*z**2) + x**5*(2676.34166120471*y**4 - 2007.25624590353*y**2*z**2 + 100.362812295177*z**4) + x**3*(-2141.07332896377*y**6 + 5352.68332240943*y**4*z**2 - 2007.25624590353*y**2*z**4 + 66.9085415301178*z**6) + x*(305.867618423396*y**8 - 2141.07332896377*y**6*z**2 + 2676.34166120471*y**4*z**4 - 669.085415301178*y**2*z**6 + 16.7271353825295*z**8))", + "z": "g_0*(27.2034486491732*x**9 - 979.324151370235*x**7*z**2 + 3427.63452979582*x**5*z**4 - 2285.08968653055*x**3*z**6 + 244.831037842559*x*z**8) + g_1*y*(-875.934149788456*x**7*z + 6131.53904851919*x**5*z**3 - 6131.53904851919*x**3*z**5 + 875.934149788456*x*z**7) + g_10*(-11.2774323743054*x**8*z + x**6*(451.097294972216*y**2*z - 45.1097294972216*z**3) + x**4*(-1804.38917988886*y**4*z + 1353.29188491665*y**2*z**3 - 67.6645942458323*z**5) + x**2*(1443.51134391109*y**6*z - 3608.77835977772*y**4*z**3 + 1353.29188491665*y**2*z**5 - 45.1097294972216*z**7) - 206.215906273013*y**8*z + 1443.51134391109*y**6*z**3 - 1804.38917988886*y**4*z**5 + 451.097294972215*y**2*z**7 - 11.2774323743054*z**9) + g_11*(16.7271353825295*x**8*y - 223.028471767059*x**6*y**3 + 535.268332240943*x**4*y**5 - 305.867618423396*x**2*y**7 + 33.9852909359329*y**9 + 150.544218442765*y*z**8 + 7.0*z**6*(66.9085415301178*x**2*y - 223.028471767059*y**3) + 5.0*z**4*(100.362812295177*x**4*y - 669.085415301178*x**2*y**3 + 535.268332240943*y**5) + 3.0*z**2*(66.9085415301178*x**6*y - 669.085415301178*x**4*y**3 + 1070.53666448189*x**2*y**5 - 305.867618423396*y**7)) + g_12*(-9.65741611587469*x**8*z + x**6*(309.03731570799*y**2*z - 12.8765548211663*z**3) + x**4*(-772.593289269976*y**4*z + 19.3148322317494*z**5) + x**2*(1545.18657853995*y**4*z**3 - 927.11194712397*y**2*z**5 + 38.6296644634988*z**7) + 176.592751833137*y**8*z - 1648.19901710928*y**6*z**3 + 2317.77986780993*y**4*z**5 - 618.07463141598*y**2*z**7 + 16.0956935264578*z**9) + g_13*(y**7*(-450.224943778107*x**2 + 450.224943778107*z**2) + y**5*(1181.84047741753*x**4 + 2363.68095483506*x**2*z**2 - 1969.73412902922*z**4) + y**3*(-590.920238708766*x**6 - 2954.60119354383*x**4*z**2 - 984.86706451461*x**2*z**4 + 1378.81389032045*z**6) + y*(49.2433532257305*x**8 + 393.946825805844*x**6*z**2 + 492.433532257305*x**4*z**4 - 147.730059677191*z**8)) + g_14*(9.948659712651*x**8*z + x**6*(-557.124943908456*y**2*z + 92.854157318076*z**3) + x**4*(2785.62471954228*y**4*z - 2785.62471954228*y**2*z**3 + 139.281235977114*z**5) + x**2*(-2228.49977563382*y**6*z + 5571.24943908456*y**4*z**3 - 1671.37483172537*y**2*z**5 + 39.794638850604*z**7) + 742.833258544608*y**6*z**3 - 1671.37483172537*y**4*z**5 + 557.124943908456*y**2*z**7 - 16.581099521085*z**9) + g_15*(y**5*(880.891882022136*x**4 - 5285.35129213281*x**2*z**2 + 880.891882022136*z**4) + y**3*(-734.07656835178*x**6 + 2202.22970505534*x**4*z**2 + 6606.68911516602*x**2*z**4 - 1027.70719569249*z**6) + y*(78.6510608948335*x**8 - 1101.11485252767*x**4*z**4 - 880.891882022136*x**2*z**6 + 141.5719096107*z**8)) + g_16*(45.725970852464*x**8*z + x**6*(-1575.78730322338*y**2*z + 98.486706451461*z**3) + x**4*(3939.46825805844*y**4*z - 147.730059677191*z**5) + x**2*(-7878.93651611688*y**4*z**3 + 4727.36190967013*y**2*z**5 - 182.903883409856*z**7) + 787.893651611688*y**4*z**5 - 450.224943778108*y**2*z**7 + 17.5869118663323*z**9) + g_17*(y**3*(-541.428124558099*x**6 + 8121.42186837148*x**4*z**2 - 8121.42186837148*x**2*z**4 + 541.428124558099*z**6) + y*(101.517773354644*x**8 - 1218.21328025572*x**6*z**2 - 1015.17773354644*x**4*z**4 + 2030.35546709287*x**2*z**6 - 130.52285145597*z**8)) + g_18*(106.571461946737*x**8*z - 331.555659389849*x**6*z**3 - 497.333489084773*x**4*z**5 + 426.285847786948*x**2*z**7 + y**2*(-1989.33395633909*x**6*z + 9946.66978169547*x**4*z**3 - 5968.00186901728*x**2*z**5 + 284.190565191299*z**7) - 19.7354559160624*z**9) + g_19*y*(109.491768723557*x**8 - 3065.7695242596*x**6*z**2 + 7664.42381064899*x**4*z**4 - 3065.7695242596*x**2*z**6 + 109.491768723557*z**8) + g_2*(15.7883647328499*x**9 - 284.190565191299*x**7*z**2 + 663.111318779698*x**3*z**6 - 142.09528259565*x*z**8 + y**2*(-284.190565191299*x**7 + 5968.00186901728*x**5*z**2 - 9946.66978169547*x**3*z**4 + 1989.33395633909*x*z**6)) + g_20*(244.831037842559*x**8*z - 2285.08968653055*x**6*z**3 + 3427.63452979582*x**4*z**5 - 979.324151370235*x**2*z**7 + 27.2034486491732*z**9) + g_3*(y**3*(3248.56874734859*x**5*z - 10828.562491162*x**3*z**3 + 3248.56874734859*x*z**5) + y*(-580.101562026534*x**7*z + 812.142186837148*x**5*z**3 + 2436.42656051144*x**3*z**5 - 812.142186837148*x*z**7)) + g_4*(10.5521471197994*x**9 + x**7*(-337.668707833581*y**2 - 42.2085884791976*z**2) + x**5*(787.893651611688*y**4 + 2363.68095483506*y**2*z**2 - 246.216766128653*z**4) + x**3*(-7878.93651611688*y**4*z**2 + 3939.46825805844*y**2*z**4 - 98.486706451461*z**6) + x*(3939.46825805844*y**4*z**4 - 2363.68095483506*y**2*z**6 + 94.9693240781945*z**8)) + g_5*(y**5*(-3523.56752808854*x**3*z + 3523.56752808854*x*z**3) + y**3*(2642.67564606641*x**5*z + 2936.30627340712*x**3*z**3 - 4404.45941011068*x*z**5) + y*(-251.683394863467*x**7*z - 880.891882022136*x**5*z**3 + 629.208487158668*x*z**7)) + g_6*(6.632439808434*x**9 + x**7*(-278.562471954228*y**2 + 39.794638850604*z**2) + x**5*(1114.24988781691*y**4 - 835.687415862684*y**2*z**2) + x**3*(-742.833258544608*y**6 + 1392.81235977114*y**2*z**4 - 92.854157318076*z**6) + x*(2228.49977563382*y**6*z**2 - 5571.24943908456*y**4*z**4 + 1949.9373036796*y**2*z**6 - 59.691958275906*z**8)) + g_7*(900.449887556215*x*y**7*z + y**5*(-1575.78730322338*x**3*z - 4727.36190967013*x*z**3) + y**3*(393.946825805844*x**5*z + 3939.46825805844*x**3*z**3 + 3545.5214322526*x*z**5) + y*(-393.946825805844*x**5*z**3 - 787.893651611688*x**3*z**5 - 393.946825805844*x*z**7)) + g_8*(3.21913870529156*x**9 + x**7*(-154.518657853995*y**2 + 38.6296644634988*z**2) + x**5*(772.593289269975*y**4 - 1390.66792068596*y**2*z**2 + 96.5741611587469*z**4) + x**3*(-824.099508554641*y**6 + 4635.55973561985*y**4*z**2 - 2317.77986780993*y**2*z**4 + 90.1358837481638*z**6) + x*(176.592751833137*y**8 - 2472.29852566392*y**6*z**2 + 3862.96644634988*y**4*z**4 - 1081.63060497797*y**2*z**6 + 28.9722483476241*z**8)) + g_9*(133.817083060236*x**7*y*z + x**5*(-1338.17083060236*y**3*z + 401.451249180707*y*z**3) + x**3*(2141.07332896377*y**5*z - 2676.34166120471*y**3*z**3 + 401.451249180707*y*z**5) + x*(-611.735236846792*y**7*z + 2141.07332896377*y**5*z**3 - 1338.17083060236*y**3*z**5 + 133.817083060236*y*z**7))" + } +} \ No newline at end of file diff --git a/notebooks/direct_sph_harm/l_2.json b/notebooks/direct_sph_harm/l_2.json new file mode 100644 index 0000000..a0e3082 --- /dev/null +++ b/notebooks/direct_sph_harm/l_2.json @@ -0,0 +1,14 @@ +{ + "fwd": [ + "3.87298334620742*x*z", + "3.87298334620742*x*y", + "-1.11803398874989*x**2 + 2.23606797749979*y**2 - 1.11803398874989*z**2", + "3.87298334620742*y*z", + "-1.93649167310371*x**2 + 1.93649167310371*z**2" + ], + "bwd": { + "x": "3.87298334620742*g_0*z + 3.87298334620742*g_1*y - 2.23606797749979*g_2*x - 3.87298334620742*g_4*x", + "y": "3.87298334620742*g_1*x + 4.47213595499958*g_2*y + 3.87298334620742*g_3*z", + "z": "3.87298334620742*g_0*x - 2.23606797749979*g_2*z + 3.87298334620742*g_3*y + 3.87298334620742*g_4*z" + } +} \ No newline at end of file diff --git a/notebooks/direct_sph_harm/l_3.json b/notebooks/direct_sph_harm/l_3.json new file mode 100644 index 0000000..271a64f --- /dev/null +++ b/notebooks/direct_sph_harm/l_3.json @@ -0,0 +1,16 @@ +{ + "fwd": [ + "-2.09165006633519*x**3 + 6.27495019900557*x*z**2", + "10.2469507659596*x*y*z", + "-1.62018517460197*x**3 + x*(6.48074069840786*y**2 - 1.62018517460197*z**2)", + "-3.96862696659689*x**2*y + 2.64575131106459*y**3 - 3.96862696659689*y*z**2", + "-1.62018517460197*z**3 + z*(-1.62018517460197*x**2 + 6.48074069840786*y**2)", + "5.1234753829798*y*(-x**2 + z**2)", + "-6.27495019900557*x**2*z + 2.09165006633519*z**3" + ], + "bwd": { + "x": "g_0*(-6.27495019900557*x**2 + 6.27495019900557*z**2) + 10.2469507659596*g_1*y*z + g_2*(-4.8605555238059*x**2 + 6.48074069840786*y**2 - 1.62018517460197*z**2) - 7.93725393319377*g_3*x*y - 3.24037034920393*g_4*x*z - 10.2469507659596*g_5*x*y - 12.5499003980111*g_6*x*z", + "y": "10.2469507659596*g_1*x*z + 12.9614813968157*g_2*x*y + g_3*(-3.96862696659689*x**2 + 7.93725393319377*y**2 - 3.96862696659689*z**2) + 12.9614813968157*g_4*y*z + g_5*(-5.1234753829798*x**2 + 5.1234753829798*z**2)", + "z": "12.5499003980111*g_0*x*z + 10.2469507659596*g_1*x*y - 3.24037034920393*g_2*x*z - 7.93725393319377*g_3*y*z + g_4*(-1.62018517460197*x**2 + 6.48074069840786*y**2 - 4.8605555238059*z**2) + 10.2469507659596*g_5*y*z + g_6*(-6.27495019900557*x**2 + 6.27495019900557*z**2)" + } +} \ No newline at end of file diff --git a/notebooks/direct_sph_harm/l_4.json b/notebooks/direct_sph_harm/l_4.json new file mode 100644 index 0000000..9e41f26 --- /dev/null +++ b/notebooks/direct_sph_harm/l_4.json @@ -0,0 +1,18 @@ +{ + "fwd": [ + "-8.87411967464942*x**3*z + 8.87411967464942*x*z**3", + "y*(-6.27495019900557*x**3 + 18.8248505970167*x*z**2)", + "-3.35410196624968*x**3*z + x*(20.1246117974981*y**2*z - 3.35410196624968*z**3)", + "-7.11512473537885*x**3*y + x*(9.48683298050514*y**3 - 7.11512473537885*y*z**2)", + "1.125*x**4 + x**2*(-9.0*y**2 + 2.25*z**2) + 3.0*y**4 - 9.0*y**2*z**2 + 1.125*z**4", + "-7.11512473537885*y*z**3 + z*(-7.11512473537885*x**2*y + 9.48683298050514*y**3)", + "1.67705098312484*x**4 + y**2*(-10.0623058987491*x**2 + 10.0623058987491*z**2) - 1.67705098312484*z**4", + "y*(-18.8248505970167*x**2*z + 6.27495019900557*z**3)", + "2.21852991866236*x**4 - 13.3111795119741*x**2*z**2 + 2.21852991866236*z**4" + ], + "bwd": { + "x": "g_0*(-26.6223590239483*x**2*z + 8.87411967464942*z**3) + g_1*y*(-18.8248505970167*x**2 + 18.8248505970167*z**2) + g_2*(-10.0623058987491*x**2*z + 20.1246117974981*y**2*z - 3.35410196624968*z**3) + g_3*(-21.3453742061366*x**2*y + 9.48683298050514*y**3 - 7.11512473537885*y*z**2) + g_4*(4.5*x**3 + 2.0*x*(-9.0*y**2 + 2.25*z**2)) - 14.2302494707577*g_5*x*y*z + g_6*(6.70820393249937*x**3 - 20.1246117974981*x*y**2) - 37.6497011940334*g_7*x*y*z + g_8*(8.87411967464942*x**3 - 26.6223590239483*x*z**2)", + "y": "g_1*(-6.27495019900557*x**3 + 18.8248505970167*x*z**2) + 40.2492235949962*g_2*x*y*z + g_3*(-7.11512473537885*x**3 + x*(28.4604989415154*y**2 - 7.11512473537885*z**2)) + g_4*(-18.0*x**2*y + 12.0*y**3 - 18.0*y*z**2) + g_5*(-7.11512473537885*z**3 + z*(-7.11512473537885*x**2 + 28.4604989415154*y**2)) + 2.0*g_6*y*(-10.0623058987491*x**2 + 10.0623058987491*z**2) + g_7*(-18.8248505970167*x**2*z + 6.27495019900557*z**3)", + "z": "g_0*(-8.87411967464942*x**3 + 26.6223590239483*x*z**2) + 37.6497011940334*g_1*x*y*z + g_2*(-3.35410196624968*x**3 + x*(20.1246117974981*y**2 - 10.0623058987491*z**2)) - 14.2302494707577*g_3*x*y*z + g_4*(4.5*x**2*z - 18.0*y**2*z + 4.5*z**3) + g_5*(-7.11512473537885*x**2*y + 9.48683298050514*y**3 - 21.3453742061366*y*z**2) + g_6*(20.1246117974981*y**2*z - 6.70820393249937*z**3) + g_7*y*(-18.8248505970167*x**2 + 18.8248505970167*z**2) + g_8*(-26.6223590239483*x**2*z + 8.87411967464942*z**3)" + } +} \ No newline at end of file diff --git a/notebooks/direct_sph_harm/l_5.json b/notebooks/direct_sph_harm/l_5.json new file mode 100644 index 0000000..dd04bb3 --- /dev/null +++ b/notebooks/direct_sph_harm/l_5.json @@ -0,0 +1,20 @@ +{ + "fwd": [ + "2.32681380862329*x**5 - 23.2681380862329*x**3*z**2 + 11.6340690431164*x*z**4", + "y*(-29.4321253055229*x**3*z + 29.4321253055229*x*z**3)", + "1.73430461568895*x**5 + x**3*(-13.8744369255116*y**2 - 3.4686092313779*z**2) + x*(41.6233107765348*y**2*z**2 - 5.20291384706685*z**4)", + "-16.9926454679664*x**3*y*z + x*(33.9852909359329*y**3*z - 16.9926454679664*y*z**3)", + "1.60565407233314*x**5 + x**3*(-19.2678488679977*y**2 + 3.21130814466628*z**2) + x*(12.8452325786651*y**4 - 19.2678488679977*y**2*z**2 + 1.60565407233314*z**4)", + "3.3166247903554*y**5 + y**3*(-16.583123951777*x**2 - 16.583123951777*z**2) + y*(6.21867148191637*x**4 + 12.4373429638327*x**2*z**2 + 6.21867148191637*z**4)", + "1.60565407233314*z**5 + z**3*(3.21130814466628*x**2 - 19.2678488679977*y**2) + z*(1.60565407233314*x**4 - 19.2678488679977*x**2*y**2 + 12.8452325786651*y**4)", + "y**3*(-16.9926454679664*x**2 + 16.9926454679664*z**2) + y*(8.49632273398321*x**4 - 8.49632273398321*z**4)", + "-1.73430461568895*z**5 + z**3*(3.4686092313779*x**2 + 13.8744369255116*y**2) + z*(5.20291384706685*x**4 - 41.6233107765348*x**2*y**2)", + "y*(7.35803132638072*x**4 - 44.1481879582843*x**2*z**2 + 7.35803132638072*z**4)", + "11.6340690431164*x**4*z - 23.2681380862329*x**2*z**3 + 2.32681380862329*z**5" + ], + "bwd": { + "x": "g_0*(11.6340690431164*x**4 - 69.8044142586986*x**2*z**2 + 11.6340690431164*z**4) + g_1*y*(-88.2963759165686*x**2*z + 29.4321253055229*z**3) + g_10*(46.5362761724657*x**3*z - 46.5362761724657*x*z**3) + g_2*(8.67152307844476*x**4 + 3.0*x**2*(-13.8744369255116*y**2 - 3.4686092313779*z**2) + 41.6233107765348*y**2*z**2 - 5.20291384706685*z**4) + g_3*(-50.9779364038993*x**2*y*z + 33.9852909359329*y**3*z - 16.9926454679664*y*z**3) + g_4*(8.02827036166571*x**4 + 3.0*x**2*(-19.2678488679977*y**2 + 3.21130814466628*z**2) + 12.8452325786651*y**4 - 19.2678488679977*y**2*z**2 + 1.60565407233314*z**4) + g_5*(-33.166247903554*x*y**3 + y*(24.8746859276655*x**3 + 24.8746859276655*x*z**2)) + g_6*(6.42261628933256*x*z**3 + z*(6.42261628933256*x**3 - 38.5356977359954*x*y**2)) + g_7*(33.9852909359329*x**3*y - 33.9852909359329*x*y**3) + g_8*(6.9372184627558*x*z**3 + z*(20.8116553882674*x**3 - 83.2466215530696*x*y**2)) + g_9*y*(29.4321253055229*x**3 - 88.2963759165686*x*z**2)", + "y": "g_1*(-29.4321253055229*x**3*z + 29.4321253055229*x*z**3) + g_2*(-27.7488738510232*x**3*y + 83.2466215530696*x*y*z**2) + g_3*(-16.9926454679664*x**3*z + x*(101.955872807799*y**2*z - 16.9926454679664*z**3)) + g_4*(-38.5356977359954*x**3*y + x*(51.3809303146605*y**3 - 38.5356977359954*y*z**2)) + g_5*(6.21867148191637*x**4 + 12.4373429638327*x**2*z**2 + 16.583123951777*y**4 + 3.0*y**2*(-16.583123951777*x**2 - 16.583123951777*z**2) + 6.21867148191637*z**4) + g_6*(-38.5356977359954*y*z**3 + z*(-38.5356977359954*x**2*y + 51.3809303146605*y**3)) + g_7*(8.49632273398321*x**4 + 3.0*y**2*(-16.9926454679664*x**2 + 16.9926454679664*z**2) - 8.49632273398321*z**4) + g_8*(-83.2466215530696*x**2*y*z + 27.7488738510232*y*z**3) + g_9*(7.35803132638072*x**4 - 44.1481879582843*x**2*z**2 + 7.35803132638072*z**4)", + "z": "g_0*(-46.5362761724657*x**3*z + 46.5362761724657*x*z**3) + g_1*y*(-29.4321253055229*x**3 + 88.2963759165686*x*z**2) + g_10*(11.6340690431164*x**4 - 69.8044142586986*x**2*z**2 + 11.6340690431164*z**4) + g_2*(-6.9372184627558*x**3*z + x*(83.2466215530696*y**2*z - 20.8116553882674*z**3)) + g_3*(-16.9926454679664*x**3*y + x*(33.9852909359329*y**3 - 50.9779364038993*y*z**2)) + g_4*(6.42261628933256*x**3*z + x*(-38.5356977359954*y**2*z + 6.42261628933257*z**3)) + g_5*(-33.166247903554*y**3*z + y*(24.8746859276655*x**2*z + 24.8746859276655*z**3)) + g_6*(1.60565407233314*x**4 - 19.2678488679977*x**2*y**2 + 12.8452325786651*y**4 + 8.02827036166571*z**4 + 3.0*z**2*(3.21130814466628*x**2 - 19.2678488679977*y**2)) + g_7*(33.9852909359329*y**3*z - 33.9852909359329*y*z**3) + g_8*(5.20291384706685*x**4 - 41.6233107765348*x**2*y**2 - 8.67152307844475*z**4 + 3.0*z**2*(3.4686092313779*x**2 + 13.8744369255116*y**2)) + g_9*y*(-88.2963759165686*x**2*z + 29.4321253055229*z**3)" + } +} \ No newline at end of file diff --git a/notebooks/direct_sph_harm/l_6.json b/notebooks/direct_sph_harm/l_6.json new file mode 100644 index 0000000..6144889 --- /dev/null +++ b/notebooks/direct_sph_harm/l_6.json @@ -0,0 +1,22 @@ +{ + "fwd": [ + "14.5309475774982*x**5*z - 48.4364919249939*x**3*z**3 + 14.5309475774982*x*z**5", + "y*(8.38944649544891*x**5 - 83.8944649544891*x**3*z**2 + 41.9472324772445*x*z**4)", + "7.15454401062709*x**5*z - 7.15454401062709*x*z**5 + y**2*(-71.5454401062709*x**3*z + 71.5454401062709*x*z**3)", + "y**3*(-26.1247009552263*x**3 + 78.3741028656788*x*z**2) + y*(9.79676285820985*x**5 - 19.5935257164197*x**3*z**2 - 29.3902885746295*x*z**4)", + "3.26558761940328*x**5*z + x**3*(-52.2494019104525*y**2*z + 6.53117523880657*z**3) + x*(52.2494019104525*y**4*z - 52.2494019104525*y**2*z**3 + 3.26558761940328*z**5)", + "10.3266947761614*x**5*y + x**3*(-41.3067791046458*y**3 + 20.6533895523229*y*z**2) + x*(16.5227116418583*y**5 - 41.3067791046458*y**3*z**2 + 10.3266947761614*y*z**4)", + "-1.1267347735825*x**6 + x**4*(20.2812259244849*y**2 - 3.38020432074749*z**2) + x**2*(-27.0416345659799*y**4 + 40.5624518489699*y**2*z**2 - 3.38020432074749*z**4) + 3.60555127546399*y**6 - 27.0416345659799*y**4*z**2 + 20.2812259244849*y**2*z**4 - 1.1267347735825*z**6", + "10.3266947761614*y*z**5 + z**3*(20.6533895523229*x**2*y - 41.3067791046458*y**3) + z*(10.3266947761614*x**4*y - 41.3067791046458*x**2*y**3 + 16.5227116418583*y**5)", + "-1.63279380970164*x**6 + x**4*(26.1247009552263*y**2 - 1.63279380970164*z**2) + x**2*(-26.1247009552263*y**4 + 1.63279380970164*z**4) + 26.1247009552263*y**4*z**2 - 26.1247009552263*y**2*z**4 + 1.63279380970164*z**6", + "y**3*(-78.3741028656788*x**2*z + 26.1247009552263*z**3) + y*(29.3902885746295*x**4*z + 19.5935257164197*x**2*z**3 - 9.79676285820985*z**5)", + "-1.78863600265677*x**6 + x**4*(17.8863600265677*y**2 + 8.94318001328386*z**2) + x**2*(-107.318160159406*y**2*z**2 + 8.94318001328386*z**4) + 17.8863600265677*y**2*z**4 - 1.78863600265677*z**6", + "y*(41.9472324772445*x**4*z - 83.8944649544891*x**2*z**3 + 8.38944649544891*z**5)", + "-2.4218245962497*x**6 + 36.3273689437454*x**4*z**2 - 36.3273689437454*x**2*z**4 + 2.4218245962497*z**6" + ], + "bwd": { + "x": "g_0*(72.6547378874909*x**4*z - 145.309475774982*x**2*z**3 + 14.5309475774982*z**5) + g_1*y*(41.9472324772445*x**4 - 251.683394863467*x**2*z**2 + 41.9472324772445*z**4) + g_10*(-10.7318160159406*x**5 + 4.0*x**3*(17.8863600265677*y**2 + 8.94318001328386*z**2) + 2.0*x*(-107.318160159406*y**2*z**2 + 8.94318001328386*z**4)) + g_11*y*(167.788929908978*x**3*z - 167.788929908978*x*z**3) + g_12*(-14.5309475774982*x**5 + 145.309475774982*x**3*z**2 - 72.6547378874909*x*z**4) + g_2*(35.7727200531355*x**4*z + y**2*(-214.636320318813*x**2*z + 71.5454401062709*z**3) - 7.15454401062709*z**5) + g_3*(y**3*(-78.3741028656788*x**2 + 78.3741028656788*z**2) + y*(48.9838142910493*x**4 - 58.7805771492591*x**2*z**2 - 29.3902885746295*z**4)) + g_4*(16.3279380970164*x**4*z + 3.0*x**2*(-52.2494019104525*y**2*z + 6.53117523880657*z**3) + 52.2494019104525*y**4*z - 52.2494019104525*y**2*z**3 + 3.26558761940328*z**5) + g_5*(51.6334738808072*x**4*y + 3.0*x**2*(-41.3067791046458*y**3 + 20.6533895523229*y*z**2) + 16.5227116418583*y**5 - 41.3067791046458*y**3*z**2 + 10.3266947761614*y*z**4) + g_6*(-6.76040864149498*x**5 + 4.0*x**3*(20.2812259244849*y**2 - 3.38020432074749*z**2) + 2.0*x*(-27.0416345659799*y**4 + 40.5624518489699*y**2*z**2 - 3.38020432074749*z**4)) + g_7*(41.3067791046458*x*y*z**3 + z*(41.3067791046458*x**3*y - 82.6135582092915*x*y**3)) + g_8*(-9.79676285820985*x**5 + 4.0*x**3*(26.1247009552263*y**2 - 1.63279380970164*z**2) + 2.0*x*(-26.1247009552263*y**4 + 1.63279380970164*z**4)) + g_9*(-156.748205731358*x*y**3*z + y*(117.561154298518*x**3*z + 39.1870514328394*x*z**3))", + "y": "g_1*(8.38944649544891*x**5 - 83.8944649544891*x**3*z**2 + 41.9472324772445*x*z**4) + g_10*(35.7727200531355*x**4*y - 214.636320318813*x**2*y*z**2 + 35.7727200531355*y*z**4) + g_11*(41.9472324772445*x**4*z - 83.8944649544891*x**2*z**3 + 8.38944649544891*z**5) + 2.0*g_2*y*(-71.5454401062709*x**3*z + 71.5454401062709*x*z**3) + g_3*(9.79676285820985*x**5 - 19.5935257164197*x**3*z**2 - 29.3902885746295*x*z**4 + 3.0*y**2*(-26.1247009552263*x**3 + 78.3741028656788*x*z**2)) + g_4*(-104.498803820905*x**3*y*z + x*(208.99760764181*y**3*z - 104.498803820905*y*z**3)) + g_5*(10.3266947761614*x**5 + x**3*(-123.920337313937*y**2 + 20.6533895523229*z**2) + x*(82.6135582092915*y**4 - 123.920337313937*y**2*z**2 + 10.3266947761614*z**4)) + g_6*(40.5624518489699*x**4*y + x**2*(-108.16653826392*y**3 + 81.1249036979398*y*z**2) + 21.6333076527839*y**5 - 108.16653826392*y**3*z**2 + 40.5624518489699*y*z**4) + g_7*(10.3266947761614*z**5 + z**3*(20.6533895523229*x**2 - 123.920337313937*y**2) + z*(10.3266947761614*x**4 - 123.920337313937*x**2*y**2 + 82.6135582092915*y**4)) + g_8*(52.2494019104525*x**4*y - 104.498803820905*x**2*y**3 + 104.498803820905*y**3*z**2 - 52.2494019104525*y*z**4) + g_9*(29.3902885746295*x**4*z + 19.5935257164197*x**2*z**3 + 3.0*y**2*(-78.3741028656788*x**2*z + 26.1247009552263*z**3) - 9.79676285820985*z**5)", + "z": "g_0*(14.5309475774982*x**5 - 145.309475774982*x**3*z**2 + 72.6547378874909*x*z**4) + g_1*y*(-167.788929908978*x**3*z + 167.788929908978*x*z**3) + g_10*(17.8863600265677*x**4*z + x**2*(-214.636320318813*y**2*z + 35.7727200531355*z**3) + 71.5454401062709*y**2*z**3 - 10.7318160159406*z**5) + g_11*y*(41.9472324772445*x**4 - 251.683394863467*x**2*z**2 + 41.9472324772445*z**4) + g_12*(72.6547378874909*x**4*z - 145.309475774982*x**2*z**3 + 14.5309475774982*z**5) + g_2*(7.15454401062709*x**5 - 35.7727200531355*x*z**4 + y**2*(-71.5454401062709*x**3 + 214.636320318813*x*z**2)) + g_3*(156.748205731358*x*y**3*z + y*(-39.1870514328394*x**3*z - 117.561154298518*x*z**3)) + g_4*(3.26558761940328*x**5 + x**3*(-52.2494019104525*y**2 + 19.5935257164197*z**2) + x*(52.2494019104525*y**4 - 156.748205731358*y**2*z**2 + 16.3279380970164*z**4)) + g_5*(41.3067791046458*x**3*y*z + x*(-82.6135582092915*y**3*z + 41.3067791046458*y*z**3)) + g_6*(-6.76040864149498*x**4*z + x**2*(81.1249036979398*y**2*z - 13.52081728299*z**3) - 54.0832691319598*y**4*z + 81.1249036979398*y**2*z**3 - 6.76040864149498*z**5) + g_7*(10.3266947761614*x**4*y - 41.3067791046458*x**2*y**3 + 16.5227116418583*y**5 + 51.6334738808072*y*z**4 + 3.0*z**2*(20.6533895523229*x**2*y - 41.3067791046458*y**3)) + g_8*(-3.26558761940328*x**4*z + 6.53117523880657*x**2*z**3 + 52.2494019104525*y**4*z - 104.498803820905*y**2*z**3 + 9.79676285820985*z**5) + g_9*(y**3*(-78.3741028656788*x**2 + 78.3741028656788*z**2) + y*(29.3902885746295*x**4 + 58.7805771492591*x**2*z**2 - 48.9838142910492*z**4))" + } +} \ No newline at end of file diff --git a/notebooks/direct_sph_harm/l_7.json b/notebooks/direct_sph_harm/l_7.json new file mode 100644 index 0000000..8169a0b --- /dev/null +++ b/notebooks/direct_sph_harm/l_7.json @@ -0,0 +1,24 @@ +{ + "fwd": [ + "-2.50682661696018*x**7 + 52.6433589561637*x**5*z**2 - 87.7389315936062*x**3*z**4 + 17.5477863187212*x*z**6", + "y*(56.2781179722634*x**5*z - 187.593726574211*x**3*z**3 + 56.2781179722634*x*z**5)", + "-1.83950783159518*x**7 + x**5*(22.0740939791422*y**2 + 16.5555704843566*z**2) + x**3*(-220.740939791422*y**2*z**2 + 9.1975391579759*z**4) + x*(110.370469895711*y**2*z**4 - 9.1975391579759*z**6)", + "y**3*(-147.160626527614*x**3*z + 147.160626527614*x*z**3) + y*(44.1481879582843*x**5*z - 44.1481879582843*x*z**5)", + "-1.66389743899677*x**7 + x**5*(33.2779487799353*y**2 + 1.66389743899677*z**2) + x**3*(-44.3705983732471*y**4 - 66.5558975598707*y**2*z**2 + 8.31948719498384*z**4) + x*(133.111795119741*y**4*z**2 - 99.833846339806*y**2*z**4 + 4.9916923169903*z**6)", + "23.5310632462709*x**5*y*z + x**3*(-125.499003980111*y**3*z + 47.0621264925418*y*z**3) + x*(75.2994023880668*y**5*z - 125.499003980111*y**3*z**3 + 23.5310632462709*y*z**5)", + "-1.60108605718119*x**7 + x**5*(38.4260653723485*y**2 - 4.80325817154356*z**2) + x**3*(-76.852130744697*y**4 + 76.852130744697*y**2*z**2 - 4.80325817154356*z**4) + x*(20.4939015319192*y**6 - 76.852130744697*y**4*z**2 + 38.4260653723485*y**2*z**4 - 1.60108605718119*z**6)", + "3.87298334620742*y**7 + y**5*(-40.6663251351779*x**2 - 40.6663251351779*z**2) + y**3*(50.8329064189723*x**4 + 101.665812837945*x**2*z**2 + 50.8329064189723*z**4) + y*(-8.47215106982872*x**6 - 25.4164532094862*x**4*z**2 - 25.4164532094862*x**2*z**4 - 8.47215106982872*z**6)", + "-1.60108605718119*z**7 + z**5*(-4.80325817154356*x**2 + 38.4260653723485*y**2) + z**3*(-4.80325817154356*x**4 + 76.852130744697*x**2*y**2 - 76.852130744697*y**4) + z*(-1.60108605718119*x**6 + 38.4260653723485*x**4*y**2 - 76.852130744697*x**2*y**4 + 20.4939015319192*y**6)", + "y**5*(-37.6497011940334*x**2 + 37.6497011940334*z**2) + y**3*(62.7495019900557*x**4 - 62.7495019900557*z**4) + y*(-11.7655316231354*x**6 - 11.7655316231354*x**4*z**2 + 11.7655316231354*x**2*z**4 + 11.7655316231354*z**6)", + "1.66389743899677*z**7 + z**5*(-1.66389743899677*x**2 - 33.2779487799353*y**2) + z**3*(-8.31948719498384*x**4 + 66.5558975598707*x**2*y**2 + 44.3705983732471*y**4) + z*(-4.9916923169903*x**6 + 99.833846339806*x**4*y**2 - 133.111795119741*x**2*y**4)", + "y**3*(36.7901566319036*x**4 - 220.740939791422*x**2*z**2 + 36.7901566319036*z**4) + y*(-11.0370469895711*x**6 + 55.1852349478554*x**4*z**2 + 55.1852349478554*x**2*z**4 - 11.0370469895711*z**6)", + "-1.83950783159518*z**7 + z**5*(16.5555704843566*x**2 + 22.0740939791422*y**2) + z**3*(9.1975391579759*x**4 - 220.740939791422*x**2*y**2) + z*(-9.1975391579759*x**6 + 110.370469895711*x**4*y**2)", + "y*(-9.37968632871057*x**6 + 140.695294930659*x**4*z**2 - 140.695294930659*x**2*z**4 + 9.37968632871057*z**6)", + "-17.5477863187212*x**6*z + 87.7389315936062*x**4*z**3 - 52.6433589561637*x**2*z**5 + 2.50682661696018*z**7" + ], + "bwd": { + "x": "g_0*(-17.5477863187212*x**6 + 263.216794780818*x**4*z**2 - 263.216794780819*x**2*z**4 + 17.5477863187212*z**6) + g_1*y*(281.390589861317*x**4*z - 562.781179722634*x**2*z**3 + 56.2781179722634*z**5) + g_10*(-3.32779487799353*x*z**5 + z**3*(-33.2779487799353*x**3 + 133.111795119741*x*y**2) + z*(-29.9501539019418*x**5 + 399.335385359224*x**3*y**2 - 266.223590239483*x*y**4)) + g_11*(y**3*(147.160626527614*x**3 - 441.481879582843*x*z**2) + y*(-66.2222819374265*x**5 + 220.740939791422*x**3*z**2 + 110.370469895711*x*z**4)) + g_12*(33.1111409687132*x*z**5 + z**3*(36.7901566319036*x**3 - 441.481879582843*x*y**2) + z*(-55.1852349478554*x**5 + 441.481879582843*x**3*y**2)) + g_13*y*(-56.2781179722634*x**5 + 562.781179722634*x**3*z**2 - 281.390589861317*x*z**4) + g_14*(-105.286717912327*x**5*z + 350.955726374425*x**3*z**3 - 105.286717912327*x*z**5) + g_2*(-12.8765548211663*x**6 + 5.0*x**4*(22.0740939791422*y**2 + 16.5555704843566*z**2) + 3.0*x**2*(-220.740939791422*y**2*z**2 + 9.1975391579759*z**4) + 110.370469895711*y**2*z**4 - 9.1975391579759*z**6) + g_3*(y**3*(-441.481879582843*x**2*z + 147.160626527614*z**3) + y*(220.740939791422*x**4*z - 44.1481879582843*z**5)) + g_4*(-11.6472820729774*x**6 + 5.0*x**4*(33.2779487799353*y**2 + 1.66389743899677*z**2) + 3.0*x**2*(-44.3705983732471*y**4 - 66.5558975598707*y**2*z**2 + 8.31948719498384*z**4) + 133.111795119741*y**4*z**2 - 99.833846339806*y**2*z**4 + 4.9916923169903*z**6) + g_5*(117.655316231354*x**4*y*z + 3.0*x**2*(-125.499003980111*y**3*z + 47.0621264925418*y*z**3) + 75.2994023880668*y**5*z - 125.499003980111*y**3*z**3 + 23.5310632462709*y*z**5) + g_6*(-11.2076024002683*x**6 + 5.0*x**4*(38.4260653723485*y**2 - 4.80325817154356*z**2) + 3.0*x**2*(-76.852130744697*y**4 + 76.852130744697*y**2*z**2 - 4.80325817154356*z**4) + 20.4939015319192*y**6 - 76.852130744697*y**4*z**2 + 38.4260653723485*y**2*z**4 - 1.60108605718119*z**6) + g_7*(-81.3326502703558*x*y**5 + y**3*(203.331625675889*x**3 + 203.331625675889*x*z**2) + y*(-50.8329064189723*x**5 - 101.665812837945*x**3*z**2 - 50.8329064189723*x*z**4)) + g_8*(-9.60651634308713*x*z**5 + z**3*(-19.2130326861743*x**3 + 153.704261489394*x*y**2) + z*(-9.60651634308712*x**5 + 153.704261489394*x**3*y**2 - 153.704261489394*x*y**4)) + g_9*(250.998007960223*x**3*y**3 - 75.2994023880668*x*y**5 + y*(-70.5931897388126*x**5 - 47.0621264925418*x**3*z**2 + 23.5310632462709*x*z**4))", + "y": "g_1*(56.2781179722634*x**5*z - 187.593726574211*x**3*z**3 + 56.2781179722634*x*z**5) + g_10*(-66.5558975598707*y*z**5 + z**3*(133.111795119741*x**2*y + 177.482393492989*y**3) + z*(199.667692679612*x**4*y - 532.447180478965*x**2*y**3)) + g_11*(-11.0370469895711*x**6 + 55.1852349478554*x**4*z**2 + 55.1852349478554*x**2*z**4 + 3.0*y**2*(36.7901566319036*x**4 - 220.740939791422*x**2*z**2 + 36.7901566319036*z**4) - 11.0370469895711*z**6) + g_12*(220.740939791422*x**4*y*z - 441.481879582843*x**2*y*z**3 + 44.1481879582843*y*z**5) + g_13*(-9.37968632871057*x**6 + 140.695294930659*x**4*z**2 - 140.695294930659*x**2*z**4 + 9.37968632871057*z**6) + g_2*(44.1481879582843*x**5*y - 441.481879582843*x**3*y*z**2 + 220.740939791422*x*y*z**4) + g_3*(44.1481879582843*x**5*z - 44.1481879582843*x*z**5 + 3.0*y**2*(-147.160626527614*x**3*z + 147.160626527614*x*z**3)) + g_4*(66.5558975598707*x**5*y + x**3*(-177.482393492989*y**3 - 133.111795119741*y*z**2) + x*(532.447180478965*y**3*z**2 - 199.667692679612*y*z**4)) + g_5*(23.5310632462709*x**5*z + x**3*(-376.497011940334*y**2*z + 47.0621264925418*z**3) + x*(376.497011940334*y**4*z - 376.497011940334*y**2*z**3 + 23.5310632462709*z**5)) + g_6*(76.852130744697*x**5*y + x**3*(-307.408522978788*y**3 + 153.704261489394*y*z**2) + x*(122.963409191515*y**5 - 307.408522978788*y**3*z**2 + 76.852130744697*y*z**4)) + g_7*(-8.47215106982872*x**6 - 25.4164532094862*x**4*z**2 - 25.4164532094862*x**2*z**4 + 27.1108834234519*y**6 + 5.0*y**4*(-40.6663251351779*x**2 - 40.6663251351779*z**2) + 3.0*y**2*(50.8329064189723*x**4 + 101.665812837945*x**2*z**2 + 50.8329064189723*z**4) - 8.47215106982872*z**6) + g_8*(76.852130744697*y*z**5 + z**3*(153.704261489394*x**2*y - 307.408522978788*y**3) + z*(76.852130744697*x**4*y - 307.408522978788*x**2*y**3 + 122.963409191515*y**5)) + g_9*(-11.7655316231354*x**6 - 11.7655316231354*x**4*z**2 + 11.7655316231354*x**2*z**4 + 5.0*y**4*(-37.6497011940334*x**2 + 37.6497011940334*z**2) + 3.0*y**2*(62.7495019900557*x**4 - 62.7495019900557*z**4) + 11.7655316231354*z**6)", + "z": "g_0*(105.286717912327*x**5*z - 350.955726374425*x**3*z**3 + 105.286717912327*x*z**5) + g_1*y*(56.2781179722634*x**5 - 562.781179722634*x**3*z**2 + 281.390589861317*x*z**4) + g_10*(-4.9916923169903*x**6 + 99.833846339806*x**4*y**2 - 133.111795119741*x**2*y**4 + 11.6472820729774*z**6 + 5.0*z**4*(-1.66389743899677*x**2 - 33.2779487799353*y**2) + 3.0*z**2*(-8.31948719498384*x**4 + 66.5558975598707*x**2*y**2 + 44.3705983732471*y**4)) + g_11*(y**3*(-441.481879582843*x**2*z + 147.160626527614*z**3) + y*(110.370469895711*x**4*z + 220.740939791422*x**2*z**3 - 66.2222819374265*z**5)) + g_12*(-9.1975391579759*x**6 + 110.370469895711*x**4*y**2 - 12.8765548211663*z**6 + 5.0*z**4*(16.5555704843566*x**2 + 22.0740939791422*y**2) + 3.0*z**2*(9.1975391579759*x**4 - 220.740939791422*x**2*y**2)) + g_13*y*(281.390589861317*x**4*z - 562.781179722634*x**2*z**3 + 56.2781179722634*z**5) + g_14*(-17.5477863187212*x**6 + 263.216794780819*x**4*z**2 - 263.216794780818*x**2*z**4 + 17.5477863187212*z**6) + g_2*(33.1111409687132*x**5*z + x**3*(-441.481879582843*y**2*z + 36.7901566319036*z**3) + x*(441.481879582843*y**2*z**3 - 55.1852349478554*z**5)) + g_3*(y**3*(-147.160626527614*x**3 + 441.481879582843*x*z**2) + y*(44.1481879582843*x**5 - 220.740939791422*x*z**4)) + g_4*(3.32779487799353*x**5*z + x**3*(-133.111795119741*y**2*z + 33.2779487799353*z**3) + x*(266.223590239483*y**4*z - 399.335385359224*y**2*z**3 + 29.9501539019418*z**5)) + g_5*(23.5310632462709*x**5*y + x**3*(-125.499003980111*y**3 + 141.186379477625*y*z**2) + x*(75.2994023880668*y**5 - 376.497011940334*y**3*z**2 + 117.655316231354*y*z**4)) + g_6*(-9.60651634308712*x**5*z + x**3*(153.704261489394*y**2*z - 19.2130326861743*z**3) + x*(-153.704261489394*y**4*z + 153.704261489394*y**2*z**3 - 9.60651634308713*z**5)) + g_7*(-81.3326502703557*y**5*z + y**3*(203.331625675889*x**2*z + 203.331625675889*z**3) + y*(-50.8329064189724*x**4*z - 101.665812837945*x**2*z**3 - 50.8329064189723*z**5)) + g_8*(-1.60108605718119*x**6 + 38.4260653723485*x**4*y**2 - 76.852130744697*x**2*y**4 + 20.4939015319192*y**6 - 11.2076024002683*z**6 + 5.0*z**4*(-4.80325817154356*x**2 + 38.4260653723485*y**2) + 3.0*z**2*(-4.80325817154356*x**4 + 76.852130744697*x**2*y**2 - 76.852130744697*y**4)) + g_9*(75.2994023880668*y**5*z - 250.998007960223*y**3*z**3 + y*(-23.5310632462709*x**4*z + 47.0621264925417*x**2*z**3 + 70.5931897388126*z**5))" + } +} \ No newline at end of file diff --git a/notebooks/direct_sph_harm/l_8.json b/notebooks/direct_sph_harm/l_8.json new file mode 100644 index 0000000..43e17a8 --- /dev/null +++ b/notebooks/direct_sph_harm/l_8.json @@ -0,0 +1,26 @@ +{ + "fwd": [ + "-20.6718218536732*x**7*z + 144.702752975712*x**5*z**3 - 144.702752975712*x**3*z**5 + 20.6718218536732*x*z**7", + "y*(-10.3359109268366*x**7 + 217.054129463568*x**5*z**2 - 361.756882439281*x**3*z**4 + 72.3513764878561*x*z**6)", + "-11.3224231339851*x**7*z + x**5*(158.513923875791*y**2*z + 26.4189873126318*z**3) + x**3*(-528.379746252636*y**2*z**3 + 26.4189873126318*z**5) + x*(158.513923875791*y**2*z**5 - 11.3224231339851*z**7)", + "y**3*(48.9184589393411*x**5 - 489.184589393411*x**3*z**2 + 244.592294696706*x*z**4) + y*(-12.2296147348353*x**7 + 110.066532613517*x**5*z**2 + 61.1480736741764*x**3*z**4 - 61.1480736741764*x*z**6)", + "-6.78376969317208*x**7*z + x**5*(162.81047263613*y**2*z - 6.78376969317208*z**3) + x**3*(-271.350787726883*y**4*z + 6.78376969317208*z**5) + x*(271.350787726883*y**4*z**3 - 162.81047263613*y**2*z**5 + 6.78376969317208*z**7)", + "y**5*(-70.0624721230988*x**3 + 210.187416369296*x*z**2) + y**3*(87.5780901538735*x**5 - 175.156180307747*x**3*z**2 - 262.734270461621*x*z**4) + y*(-13.136713523081*x**7 + 13.136713523081*x**5*z**2 + 65.6835676154051*x**3*z**4 + 39.4101405692431*x*z**6)", + "-3.23403530824881*x**7*z + x**5*(97.0210592474644*y**2*z - 9.70210592474644*z**3) + x**3*(-258.722824659905*y**4*z + 194.042118494929*y**2*z**3 - 9.70210592474644*z**5) + x*(103.489129863962*y**6*z - 258.722824659905*y**4*z**3 + 97.0210592474644*y**2*z**5 - 3.23403530824881*z**7)", + "-13.5289403340579*x**7*y + x**5*(108.231522672464*y**3 - 40.5868210021738*y*z**2) + x**3*(-129.877827206956*y**5 + 216.463045344927*y**3*z**2 - 40.5868210021738*y*z**4) + x*(24.738633753706*y**7 - 129.877827206956*y**5*z**2 + 108.231522672464*y**3*z**4 - 13.5289403340579*y*z**6)", + "1.12741169450483*x**8 + x**6*(-36.0771742241545*y**2 + 4.50964677801932*z**2) + x**4*(108.231522672464*y**4 - 108.231522672464*y**2*z**2 + 6.76447016702898*z**4) + x**2*(-57.7234787586472*y**6 + 216.463045344927*y**4*z**2 - 108.231522672464*y**2*z**4 + 4.50964677801932*z**6) + 4.12310562561766*y**8 - 57.7234787586472*y**6*z**2 + 108.231522672464*y**4*z**4 - 36.0771742241545*y**2*z**6 + 1.12741169450483*z**8", + "-13.5289403340579*y*z**7 + z**5*(-40.5868210021738*x**2*y + 108.231522672464*y**3) + z**3*(-40.5868210021738*x**4*y + 216.463045344927*x**2*y**3 - 129.877827206956*y**5) + z*(-13.5289403340579*x**6*y + 108.231522672464*x**4*y**3 - 129.877827206956*x**2*y**5 + 24.738633753706*y**7)", + "1.61701765412441*x**8 + 3.23403530824881*x**6*z**2 - 3.23403530824881*x**2*z**6 + y**6*(-51.744564931981*x**2 + 51.744564931981*z**2) + y**4*(129.361412329953*x**4 - 129.361412329953*z**4) + y**2*(-48.5105296237322*x**6 - 48.5105296237322*x**4*z**2 + 48.5105296237322*x**2*z**4 + 48.5105296237322*z**6) - 1.61701765412441*z**8", + "y**5*(-210.187416369296*x**2*z + 70.0624721230988*z**3) + y**3*(262.734270461621*x**4*z + 175.156180307747*x**2*z**3 - 87.5780901538735*z**5) + y*(-39.4101405692431*x**6*z - 65.6835676154052*x**4*z**3 - 13.136713523081*x**2*z**5 + 13.136713523081*z**7)", + "1.69594242329302*x**8 - 6.78376969317208*x**6*z**2 - 16.9594242329302*x**4*z**4 - 6.78376969317208*x**2*z**6 + y**4*(67.8376969317208*x**4 - 407.026181590325*x**2*z**2 + 67.8376969317208*z**4) + y**2*(-40.7026181590325*x**6 + 203.513090795162*x**4*z**2 + 203.513090795162*x**2*z**4 - 40.7026181590325*z**6) + 1.69594242329302*z**8", + "y**3*(244.592294696706*x**4*z - 489.184589393411*x**2*z**3 + 48.9184589393411*z**5) + y*(-61.1480736741764*x**6*z + 61.1480736741764*x**4*z**3 + 110.066532613517*x**2*z**5 - 12.2296147348353*z**7)", + "1.88707052233084*x**8 - 26.4189873126318*x**6*z**2 + 26.4189873126318*x**2*z**6 + y**2*(-26.4189873126318*x**6 + 396.284809689477*x**4*z**2 - 396.284809689477*x**2*z**4 + 26.4189873126318*z**6) - 1.88707052233084*z**8", + "y*(-72.3513764878561*x**6*z + 361.756882439281*x**4*z**3 - 217.054129463568*x**2*z**5 + 10.3359109268366*z**7)", + "2.58397773170915*x**8 - 72.3513764878561*x**6*z**2 + 180.87844121964*x**4*z**4 - 72.3513764878561*x**2*z**6 + 2.58397773170915*z**8" + ], + "bwd": { + "x": "g_0*(-144.702752975712*x**6*z + 723.513764878561*x**4*z**3 - 434.108258927137*x**2*z**5 + 20.6718218536732*z**7) + g_1*y*(-72.3513764878561*x**6 + 1085.27064731784*x**4*z**2 - 1085.27064731784*x**2*z**4 + 72.3513764878561*z**6) + g_10*(12.9361412329953*x**7 + 19.4042118494929*x**5*z**2 + 517.44564931981*x**3*y**4 - 103.489129863962*x*y**6 - 6.46807061649763*x*z**6 + y**2*(-291.063177742393*x**5 - 194.042118494929*x**3*z**2 + 97.0210592474644*x*z**4)) + g_11*(-420.374832738593*x*y**5*z + y**3*(1050.93708184648*x**3*z + 350.312360615494*x*z**3) + y*(-236.460843415458*x**5*z - 262.734270461621*x**3*z**3 - 26.2734270461621*x*z**5)) + g_12*(13.5675393863442*x**7 - 40.7026181590325*x**5*z**2 - 67.8376969317208*x**3*z**4 - 13.5675393863442*x*z**6 + y**4*(271.350787726883*x**3 - 814.05236318065*x*z**2) + y**2*(-244.215708954195*x**5 + 814.05236318065*x**3*z**2 + 407.026181590325*x*z**4)) + g_13*(y**3*(978.369178786822*x**3*z - 978.369178786822*x*z**3) + y*(-366.888442045058*x**5*z + 244.592294696705*x**3*z**3 + 220.133065227035*x*z**5)) + g_14*(15.0965641786467*x**7 - 158.513923875791*x**5*z**2 + 52.8379746252636*x*z**6 + y**2*(-158.513923875791*x**5 + 1585.13923875791*x**3*z**2 - 792.569619378954*x*z**4)) + g_15*y*(-434.108258927137*x**5*z + 1447.02752975712*x**3*z**3 - 434.108258927137*x*z**5) + g_16*(20.6718218536732*x**7 - 434.108258927137*x**5*z**2 + 723.513764878561*x**3*z**4 - 144.702752975712*x*z**6) + g_2*(-79.2569619378954*x**6*z + 5.0*x**4*(158.513923875791*y**2*z + 26.4189873126318*z**3) + 3.0*x**2*(-528.379746252636*y**2*z**3 + 26.4189873126318*z**5) + 158.513923875791*y**2*z**5 - 11.3224231339851*z**7) + g_3*(y**3*(244.592294696706*x**4 - 1467.55376818023*x**2*z**2 + 244.592294696706*z**4) + y*(-85.6073031438469*x**6 + 550.332663067587*x**4*z**2 + 183.444221022529*x**2*z**4 - 61.1480736741764*z**6)) + g_4*(-47.4863878522046*x**6*z + 5.0*x**4*(162.81047263613*y**2*z - 6.78376969317208*z**3) + 3.0*x**2*(-271.350787726883*y**4*z + 6.78376969317208*z**5) + 271.350787726883*y**4*z**3 - 162.81047263613*y**2*z**5 + 6.78376969317208*z**7) + g_5*(y**5*(-210.187416369296*x**2 + 210.187416369296*z**2) + y**3*(437.890450769368*x**4 - 525.468540923241*x**2*z**2 - 262.734270461621*z**4) + y*(-91.9569946615672*x**6 + 65.6835676154051*x**4*z**2 + 197.050702846215*x**2*z**4 + 39.4101405692431*z**6)) + g_6*(-22.6382471577417*x**6*z + 5.0*x**4*(97.0210592474644*y**2*z - 9.70210592474644*z**3) + 3.0*x**2*(-258.722824659905*y**4*z + 194.042118494929*y**2*z**3 - 9.70210592474644*z**5) + 103.489129863962*y**6*z - 258.722824659905*y**4*z**3 + 97.0210592474644*y**2*z**5 - 3.23403530824881*z**7) + g_7*(-94.7025823384056*x**6*y + 5.0*x**4*(108.231522672464*y**3 - 40.5868210021738*y*z**2) + 3.0*x**2*(-129.877827206956*y**5 + 216.463045344927*y**3*z**2 - 40.5868210021738*y*z**4) + 24.738633753706*y**7 - 129.877827206956*y**5*z**2 + 108.231522672464*y**3*z**4 - 13.5289403340579*y*z**6) + g_8*(9.01929355603863*x**7 + 6.0*x**5*(-36.0771742241545*y**2 + 4.50964677801932*z**2) + 4.0*x**3*(108.231522672464*y**4 - 108.231522672464*y**2*z**2 + 6.76447016702898*z**4) + 2.0*x*(-57.7234787586472*y**6 + 216.463045344927*y**4*z**2 - 108.231522672464*y**2*z**4 + 4.50964677801932*z**6)) + g_9*(-81.1736420043477*x*y*z**5 + z**3*(-162.347284008695*x**3*y + 432.926090689854*x*y**3) + z*(-81.1736420043477*x**5*y + 432.926090689854*x**3*y**3 - 259.755654413913*x*y**5))", + "y": "g_1*(-10.3359109268366*x**7 + 217.054129463568*x**5*z**2 - 361.756882439281*x**3*z**4 + 72.3513764878561*x*z**6) + g_10*(6.0*y**5*(-51.744564931981*x**2 + 51.744564931981*z**2) + 4.0*y**3*(129.361412329953*x**4 - 129.361412329953*z**4) + 2.0*y*(-48.5105296237322*x**6 - 48.5105296237322*x**4*z**2 + 48.5105296237322*x**2*z**4 + 48.5105296237322*z**6)) + g_11*(-39.4101405692431*x**6*z - 65.6835676154052*x**4*z**3 - 13.136713523081*x**2*z**5 + 5.0*y**4*(-210.187416369296*x**2*z + 70.0624721230988*z**3) + 3.0*y**2*(262.734270461621*x**4*z + 175.156180307747*x**2*z**3 - 87.5780901538735*z**5) + 13.136713523081*z**7) + g_12*(4.0*y**3*(67.8376969317208*x**4 - 407.026181590325*x**2*z**2 + 67.8376969317208*z**4) + 2.0*y*(-40.7026181590325*x**6 + 203.513090795162*x**4*z**2 + 203.513090795162*x**2*z**4 - 40.7026181590325*z**6)) + g_13*(-61.1480736741764*x**6*z + 61.1480736741764*x**4*z**3 + 110.066532613517*x**2*z**5 + 3.0*y**2*(244.592294696706*x**4*z - 489.184589393411*x**2*z**3 + 48.9184589393411*z**5) - 12.2296147348353*z**7) + 2.0*g_14*y*(-26.4189873126318*x**6 + 396.284809689477*x**4*z**2 - 396.284809689477*x**2*z**4 + 26.4189873126318*z**6) + g_15*(-72.3513764878561*x**6*z + 361.756882439281*x**4*z**3 - 217.054129463568*x**2*z**5 + 10.3359109268366*z**7) + g_2*(317.027847751582*x**5*y*z - 1056.75949250527*x**3*y*z**3 + 317.027847751582*x*y*z**5) + g_3*(-12.2296147348353*x**7 + 110.066532613517*x**5*z**2 + 61.1480736741764*x**3*z**4 - 61.1480736741764*x*z**6 + 3.0*y**2*(48.9184589393411*x**5 - 489.184589393411*x**3*z**2 + 244.592294696706*x*z**4)) + g_4*(325.62094527226*x**5*y*z - 1085.40315090753*x**3*y**3*z + x*(1085.40315090753*y**3*z**3 - 325.62094527226*y*z**5)) + g_5*(-13.136713523081*x**7 + 13.136713523081*x**5*z**2 + 65.6835676154051*x**3*z**4 + 39.4101405692431*x*z**6 + 5.0*y**4*(-70.0624721230988*x**3 + 210.187416369296*x*z**2) + 3.0*y**2*(87.5780901538735*x**5 - 175.156180307747*x**3*z**2 - 262.734270461621*x*z**4)) + g_6*(194.042118494929*x**5*y*z + x**3*(-1034.89129863962*y**3*z + 388.084236989858*y*z**3) + x*(620.934779183772*y**5*z - 1034.89129863962*y**3*z**3 + 194.042118494929*y*z**5)) + g_7*(-13.5289403340579*x**7 + x**5*(324.694568017391*y**2 - 40.5868210021738*z**2) + x**3*(-649.389136034782*y**4 + 649.389136034782*y**2*z**2 - 40.5868210021738*z**4) + x*(173.170436275942*y**6 - 649.389136034782*y**4*z**2 + 324.694568017391*y**2*z**4 - 13.5289403340579*z**6)) + g_8*(-72.1543484483091*x**6*y + x**4*(432.926090689854*y**3 - 216.463045344927*y*z**2) + x**2*(-346.340872551883*y**5 + 865.852181379709*y**3*z**2 - 216.463045344927*y*z**4) + 32.9848450049413*y**7 - 346.340872551883*y**5*z**2 + 432.926090689854*y**3*z**4 - 72.1543484483091*y*z**6) + g_9*(-13.5289403340579*z**7 + z**5*(-40.5868210021738*x**2 + 324.694568017391*y**2) + z**3*(-40.5868210021738*x**4 + 649.389136034781*x**2*y**2 - 649.389136034782*y**4) + z*(-13.5289403340579*x**6 + 324.694568017391*x**4*y**2 - 649.389136034782*x**2*y**4 + 173.170436275942*y**6))", + "z": "g_0*(-20.6718218536732*x**7 + 434.108258927137*x**5*z**2 - 723.513764878561*x**3*z**4 + 144.702752975712*x*z**6) + g_1*y*(434.108258927137*x**5*z - 1447.02752975712*x**3*z**3 + 434.108258927137*x*z**5) + g_10*(6.46807061649763*x**6*z - 19.4042118494929*x**2*z**5 + 103.489129863962*y**6*z - 517.44564931981*y**4*z**3 + y**2*(-97.0210592474644*x**4*z + 194.042118494929*x**2*z**3 + 291.063177742393*z**5) - 12.9361412329953*z**7) + g_11*(y**5*(-210.187416369296*x**2 + 210.187416369296*z**2) + y**3*(262.734270461621*x**4 + 525.468540923241*x**2*z**2 - 437.890450769368*z**4) + y*(-39.4101405692431*x**6 - 197.050702846215*x**4*z**2 - 65.6835676154052*x**2*z**4 + 91.9569946615672*z**6)) + g_12*(-13.5675393863442*x**6*z - 67.8376969317208*x**4*z**3 - 40.7026181590325*x**2*z**5 + y**4*(-814.05236318065*x**2*z + 271.350787726883*z**3) + y**2*(407.026181590325*x**4*z + 814.05236318065*x**2*z**3 - 244.215708954195*z**5) + 13.5675393863442*z**7) + g_13*(y**3*(244.592294696706*x**4 - 1467.55376818023*x**2*z**2 + 244.592294696706*z**4) + y*(-61.1480736741764*x**6 + 183.444221022529*x**4*z**2 + 550.332663067587*x**2*z**4 - 85.6073031438469*z**6)) + g_14*(-52.8379746252636*x**6*z + 158.513923875791*x**2*z**5 + y**2*(792.569619378954*x**4*z - 1585.13923875791*x**2*z**3 + 158.513923875791*z**5) - 15.0965641786467*z**7) + g_15*y*(-72.3513764878561*x**6 + 1085.27064731784*x**4*z**2 - 1085.27064731784*x**2*z**4 + 72.3513764878561*z**6) + g_16*(-144.702752975712*x**6*z + 723.513764878561*x**4*z**3 - 434.108258927137*x**2*z**5 + 20.6718218536732*z**7) + g_2*(-11.3224231339851*x**7 + x**5*(158.513923875791*y**2 + 79.2569619378954*z**2) + x**3*(-1585.13923875791*y**2*z**2 + 132.094936563159*z**4) + x*(792.569619378954*y**2*z**4 - 79.2569619378954*z**6)) + g_3*(y**3*(-978.369178786822*x**3*z + 978.369178786822*x*z**3) + y*(220.133065227035*x**5*z + 244.592294696706*x**3*z**3 - 366.888442045058*x*z**5)) + g_4*(-6.78376969317208*x**7 + x**5*(162.81047263613*y**2 - 20.3513090795162*z**2) + x**3*(-271.350787726883*y**4 + 33.9188484658604*z**4) + x*(814.05236318065*y**4*z**2 - 814.05236318065*y**2*z**4 + 47.4863878522046*z**6)) + g_5*(420.374832738593*x*y**5*z + y**3*(-350.312360615494*x**3*z - 1050.93708184648*x*z**3) + y*(26.2734270461621*x**5*z + 262.734270461621*x**3*z**3 + 236.460843415458*x*z**5)) + g_6*(-3.23403530824881*x**7 + x**5*(97.0210592474644*y**2 - 29.1063177742393*z**2) + x**3*(-258.722824659905*y**4 + 582.126355484786*y**2*z**2 - 48.5105296237322*z**4) + x*(103.489129863962*y**6 - 776.168473979715*y**4*z**2 + 485.105296237322*y**2*z**4 - 22.6382471577417*z**6)) + g_7*(-81.1736420043477*x**5*y*z + x**3*(432.926090689854*y**3*z - 162.347284008695*y*z**3) + x*(-259.755654413913*y**5*z + 432.926090689854*y**3*z**3 - 81.1736420043477*y*z**5)) + g_8*(9.01929355603863*x**6*z + x**4*(-216.463045344927*y**2*z + 27.0578806681159*z**3) + x**2*(432.926090689854*y**4*z - 432.926090689854*y**2*z**3 + 27.0578806681159*z**5) - 115.446957517294*y**6*z + 432.926090689854*y**4*z**3 - 216.463045344927*y**2*z**5 + 9.01929355603863*z**7) + g_9*(-13.5289403340579*x**6*y + 108.231522672464*x**4*y**3 - 129.877827206956*x**2*y**5 + 24.738633753706*y**7 - 94.7025823384056*y*z**6 + 5.0*z**4*(-40.5868210021738*x**2*y + 108.231522672464*y**3) + 3.0*z**2*(-40.5868210021738*x**4*y + 216.463045344927*x**2*y**3 - 129.877827206956*y**5))" + } +} \ No newline at end of file diff --git a/notebooks/direct_sph_harm/l_9.json b/notebooks/direct_sph_harm/l_9.json new file mode 100644 index 0000000..a3ca649 --- /dev/null +++ b/notebooks/direct_sph_harm/l_9.json @@ -0,0 +1,28 @@ +{ + "fwd": [ + "2.65478475211798*x**9 - 95.5722510762473*x**7*z**2 + 334.502878766866*x**5*z**4 - 223.00191917791*x**3*z**6 + 23.8930627690618*x*z**8", + "y*(-90.106382439037*x**7*z + 630.744677073259*x**5*z**3 - 630.744677073259*x**3*z**5 + 90.106382439037*x*z**7)", + "1.93163963757558*x**9 + x**7*(-30.9062342012093*y**2 - 38.6327927515116*z**2) + x**5*(649.030918225395*y**2*z**2 + 27.0429549260581*z**4) + x**3*(-1081.71819704233*y**2*z**4 + 54.0859098521163*z**6) + x*(216.343639408465*y**2*z**6 - 13.5214774630291*z**8)", + "y**3*(374.718175349822*x**5*z - 1249.06058449941*x**3*z**3 + 374.718175349822*x*z**5) + y*(-80.2967518606762*x**7*z + 187.359087674911*x**5*z**3 + 187.359087674911*x**3*z**5 - 80.2967518606762*x*z**7)", + "1.72771101506082*x**9 - 13.8216881204866*x**7*z**2 - 24.1879542108515*x**5*z**4 + 8.63855507530412*x*z**8 + y**4*(96.7518168434061*x**5 - 967.518168434061*x**3*z**2 + 483.759084217031*x*z**4) + y**2*(-48.3759084217031*x**7 + 435.383175795327*x**5*z**2 + 241.879542108515*x**3*z**4 - 241.879542108515*x*z**6)", + "y**5*(-462.562157985281*x**3*z + 462.562157985281*x*z**3) + y**3*(462.562157985281*x**5*z - 462.562157985281*x*z**5) + y*(-57.8202697481601*x**7*z - 57.8202697481601*x**5*z**3 + 57.8202697481601*x**3*z**5 + 57.8202697481601*x*z**7)", + "1.63671408859718*x**9 - 58.9217071894985*x**7*y**2 + x**5*(196.405690631662*y**4 + 58.9217071894985*y**2*z**2 - 9.82028453158308*z**4) + x**3*(-104.74970167022*y**6 - 392.811381263323*y**4*z**2 + 294.608535947493*y**2*z**4 - 13.0937127087774*z**6) + x*(314.249105010659*y**6*z**2 - 589.217071894985*y**4*z**4 + 176.765121568496*y**2*z**6 - 4.91014226579154*z**8)", + "-30.001464807989*x**7*y*z + x**5*(300.01464807989*y**3*z - 90.0043944239669*y*z**3) + x**3*(-480.023436927823*y**5*z + 600.029296159779*y**3*z**3 - 90.0043944239669*y*z**5) + x*(137.14955340795*y**7*z - 480.023436927823*y**5*z**3 + 300.01464807989*y**3*z**5 - 30.001464807989*y*z**7)", + "1.59908344719522*x**9 + x**7*(-63.9633378878088*y**2 + 6.39633378878088*z**2) + x**5*(255.853351551235*y**4 - 191.890013663426*y**2*z**2 + 9.59450068317133*z**4) + x**3*(-204.682681240988*y**6 + 511.706703102471*y**4*z**2 - 191.890013663426*y**2*z**4 + 6.39633378878088*z**6) + x*(29.2403830344269*y**8 - 204.682681240988*y**6*z**2 + 255.853351551235*y**4*z**4 - 63.9633378878088*y**2*z**6 + 1.59908344719522*z**8)", + "4.35889894354067*y**9 + y**7*(-78.4601809837321*x**2 - 78.4601809837321*z**2) + y**5*(205.957975082297*x**4 + 411.915950164594*x**2*z**2 + 205.957975082297*z**4) + y**3*(-114.421097267943*x**6 - 343.263291803828*x**4*z**2 - 343.263291803828*x**2*z**4 - 114.421097267943*z**6) + y*(10.7269778688696*x**8 + 42.9079114754785*x**6*z**2 + 64.3618672132178*x**4*z**4 + 42.9079114754785*x**2*z**6 + 10.7269778688696*z**8)", + "1.59908344719522*z**9 + z**7*(6.39633378878088*x**2 - 63.9633378878088*y**2) + z**5*(9.59450068317133*x**4 - 191.890013663427*x**2*y**2 + 255.853351551235*y**4) + z**3*(6.39633378878088*x**6 - 191.890013663426*x**4*y**2 + 511.706703102471*x**2*y**4 - 204.682681240988*y**6) + z*(1.59908344719522*x**8 - 63.9633378878088*x**6*y**2 + 255.853351551235*x**4*y**4 - 204.682681240988*x**2*y**6 + 29.2403830344269*y**8)", + "y**7*(-68.5747767039748*x**2 + 68.5747767039748*z**2) + y**5*(240.011718463912*x**4 - 240.011718463912*z**4) + y**3*(-150.007324039945*x**6 - 150.007324039945*x**4*z**2 + 150.007324039945*x**2*z**4 + 150.007324039945*z**6) + y*(15.0007324039945*x**8 + 30.001464807989*x**6*z**2 - 30.001464807989*x**2*z**6 - 15.0007324039945*z**8)", + "58.9217071894985*y**2*z**7 - 1.63671408859718*z**9 + z**5*(9.82028453158308*x**4 - 58.9217071894985*x**2*y**2 - 196.405690631662*y**4) + z**3*(13.0937127087774*x**6 - 294.608535947493*x**4*y**2 + 392.811381263323*x**2*y**4 + 104.74970167022*y**6) + z*(4.91014226579154*x**8 - 176.765121568496*x**6*y**2 + 589.217071894985*x**4*y**4 - 314.249105010659*x**2*y**6)", + "y**5*(115.64053949632*x**4 - 693.843236977922*x**2*z**2 + 115.64053949632*z**4) + y**3*(-115.64053949632*x**6 + 578.202697481601*x**4*z**2 + 578.202697481601*x**2*z**4 - 115.64053949632*z**6) + y*(14.45506743704*x**8 - 57.8202697481601*x**6*z**2 - 144.5506743704*x**4*z**4 - 57.8202697481601*x**2*z**6 + 14.45506743704*z**8)", + "8.63855507530412*x**8*z - 24.1879542108515*x**4*z**5 - 13.8216881204866*x**2*z**7 + y**4*(483.759084217031*x**4*z - 967.518168434061*x**2*z**3 + 96.7518168434061*z**5) + y**2*(-241.879542108515*x**6*z + 241.879542108515*x**4*z**3 + 435.383175795328*x**2*z**5 - 48.375908421703*z**7) + 1.72771101506082*z**9", + "y**3*(-62.4530292249704*x**6 + 936.795438374555*x**4*z**2 - 936.795438374555*x**2*z**4 + 62.4530292249704*z**6) + y*(13.3827919767794*x**8 - 187.359087674911*x**6*z**2 + 187.359087674911*x**2*z**6 - 13.3827919767794*z**8)", + "13.5214774630291*x**8*z - 54.0859098521163*x**6*z**3 - 27.0429549260581*x**4*z**5 + 38.6327927515116*x**2*z**7 + y**2*(-216.343639408465*x**6*z + 1081.71819704233*x**4*z**3 - 649.030918225395*x**2*z**5 + 30.9062342012093*z**7) - 1.93163963757558*z**9", + "y*(11.2632978048796*x**8 - 315.37233853663*x**6*z**2 + 788.430846341574*x**4*z**4 - 315.37233853663*x**2*z**6 + 11.2632978048796*z**8)", + "23.8930627690618*x**8*z - 223.00191917791*x**6*z**3 + 334.502878766866*x**4*z**5 - 95.5722510762473*x**2*z**7 + 2.65478475211798*z**9" + ], + "bwd": { + "x": "g_0*(23.8930627690618*x**8 - 669.005757533731*x**6*z**2 + 1672.51439383433*x**4*z**4 - 669.005757533731*x**2*z**6 + 23.8930627690618*z**8) + g_1*y*(-630.744677073259*x**6*z + 3153.7233853663*x**4*z**3 - 1892.23403121978*x**2*z**5 + 90.106382439037*z**7) + g_10*(12.7926675775618*x*z**7 + z**5*(38.3780027326853*x**3 - 383.780027326853*x*y**2) + z**3*(38.3780027326853*x**5 - 767.560054653706*x**3*y**2 + 1023.41340620494*x*y**4) + z*(12.7926675775618*x**7 - 383.780027326853*x**5*y**2 + 1023.41340620494*x**3*y**4 - 409.365362481977*x*y**6)) + g_11*(960.046873855647*x**3*y**5 - 137.14955340795*x*y**7 + y**3*(-900.043944239669*x**5 - 600.029296159779*x**3*z**2 + 300.01464807989*x*z**4) + y*(120.005859231956*x**7 + 180.008788847934*x**5*z**2 - 60.0029296159779*x*z**6)) + g_12*(z**5*(39.2811381263323*x**3 - 117.843414378997*x*y**2) + z**3*(78.5622762526647*x**5 - 1178.43414378997*x**3*y**2 + 785.622762526647*x*y**4) + z*(39.2811381263323*x**7 - 1060.59072941097*x**5*y**2 + 2356.86828757994*x**3*y**4 - 628.498210021318*x*y**6)) + g_13*(y**5*(462.562157985281*x**3 - 1387.68647395584*x*z**2) + y**3*(-693.843236977922*x**5 + 2312.81078992641*x**3*z**2 + 1156.4053949632*x*z**4) + y*(115.64053949632*x**7 - 346.921618488961*x**5*z**2 - 578.202697481601*x**3*z**4 - 115.64053949632*x*z**6)) + g_14*(69.1084406024329*x**7*z - 96.7518168434061*x**3*z**5 - 27.6433762409732*x*z**7 + y**4*(1935.03633686812*x**3*z - 1935.03633686812*x*z**3) + y**2*(-1451.27725265109*x**5*z + 967.518168434061*x**3*z**3 + 870.766351590655*x*z**5)) + g_15*(y**3*(-374.718175349822*x**5 + 3747.18175349822*x**3*z**2 - 1873.59087674911*x*z**4) + y*(107.062335814235*x**7 - 1124.15452604947*x**5*z**2 + 374.718175349822*x*z**6)) + g_16*(108.171819704233*x**7*z - 324.515459112698*x**5*z**3 - 108.171819704233*x**3*z**5 + 77.2655855030233*x*z**7 + y**2*(-1298.06183645079*x**5*z + 4326.8727881693*x**3*z**3 - 1298.06183645079*x*z**5)) + g_17*y*(90.106382439037*x**7 - 1892.23403121978*x**5*z**2 + 3153.7233853663*x**3*z**4 - 630.744677073259*x*z**6) + g_18*(191.144502152495*x**7*z - 1338.01151506746*x**5*z**3 + 1338.01151506746*x**3*z**5 - 191.144502152495*x*z**7) + g_2*(17.3847567381802*x**8 + 7.0*x**6*(-30.9062342012093*y**2 - 38.6327927515116*z**2) + 5.0*x**4*(649.030918225395*y**2*z**2 + 27.0429549260581*z**4) + 3.0*x**2*(-1081.71819704233*y**2*z**4 + 54.0859098521163*z**6) + 216.343639408465*y**2*z**6 - 13.5214774630291*z**8) + g_3*(y**3*(1873.59087674911*x**4*z - 3747.18175349822*x**2*z**3 + 374.718175349822*z**5) + y*(-562.077263024733*x**6*z + 936.795438374555*x**4*z**3 + 562.077263024733*x**2*z**5 - 80.2967518606762*z**7)) + g_4*(15.5493991355474*x**8 - 96.7518168434061*x**6*z**2 - 120.939771054258*x**4*z**4 + y**4*(483.759084217031*x**4 - 2902.55450530218*x**2*z**2 + 483.759084217031*z**4) + y**2*(-338.631358951921*x**6 + 2176.91587897664*x**4*z**2 + 725.638626325546*x**2*z**4 - 241.879542108515*z**6) + 8.63855507530412*z**8) + g_5*(y**5*(-1387.68647395584*x**2*z + 462.562157985281*z**3) + y**3*(2312.81078992641*x**4*z - 462.562157985281*z**5) + y*(-404.741888237121*x**6*z - 289.101348740801*x**4*z**3 + 173.46080924448*x**2*z**5 + 57.8202697481601*z**7)) + g_6*(14.7304267973746*x**8 - 412.45195032649*x**6*y**2 + 5.0*x**4*(196.405690631662*y**4 + 58.9217071894985*y**2*z**2 - 9.82028453158308*z**4) + 3.0*x**2*(-104.74970167022*y**6 - 392.811381263323*y**4*z**2 + 294.608535947493*y**2*z**4 - 13.0937127087774*z**6) + 314.249105010659*y**6*z**2 - 589.217071894985*y**4*z**4 + 176.765121568496*y**2*z**6 - 4.91014226579154*z**8) + g_7*(-210.010253655923*x**6*y*z + 5.0*x**4*(300.01464807989*y**3*z - 90.0043944239669*y*z**3) + 3.0*x**2*(-480.023436927823*y**5*z + 600.029296159779*y**3*z**3 - 90.0043944239669*y*z**5) + 137.14955340795*y**7*z - 480.023436927823*y**5*z**3 + 300.01464807989*y**3*z**5 - 30.001464807989*y*z**7) + g_8*(14.391751024757*x**8 + 7.0*x**6*(-63.9633378878088*y**2 + 6.39633378878088*z**2) + 5.0*x**4*(255.853351551235*y**4 - 191.890013663426*y**2*z**2 + 9.59450068317133*z**4) + 3.0*x**2*(-204.682681240988*y**6 + 511.706703102471*y**4*z**2 - 191.890013663426*y**2*z**4 + 6.39633378878088*z**6) + 29.2403830344269*y**8 - 204.682681240988*y**6*z**2 + 255.853351551235*y**4*z**4 - 63.9633378878088*y**2*z**6 + 1.59908344719522*z**8) + g_9*(-156.920361967464*x*y**7 + y**5*(823.831900329187*x**3 + 823.831900329187*x*z**2) + y**3*(-686.526583607656*x**5 - 1373.05316721531*x**3*z**2 - 686.526583607656*x*z**4) + y*(85.815822950957*x**7 + 257.447468852871*x**5*z**2 + 257.447468852871*x**3*z**4 + 85.815822950957*x*z**6))", + "y": "g_1*(-90.106382439037*x**7*z + 630.744677073259*x**5*z**3 - 630.744677073259*x**3*z**5 + 90.106382439037*x*z**7) + g_10*(-127.926675775618*y*z**7 + z**5*(-383.780027326853*x**2*y + 1023.41340620494*y**3) + z**3*(-383.780027326853*x**4*y + 2046.82681240988*x**2*y**3 - 1228.09608744593*y**5) + z*(-127.926675775618*x**6*y + 1023.41340620494*x**4*y**3 - 1228.09608744593*x**2*y**5 + 233.923064275415*y**7)) + g_11*(15.0007324039945*x**8 + 30.001464807989*x**6*z**2 - 30.001464807989*x**2*z**6 + 7.0*y**6*(-68.5747767039748*x**2 + 68.5747767039748*z**2) + 5.0*y**4*(240.011718463912*x**4 - 240.011718463912*z**4) + 3.0*y**2*(-150.007324039945*x**6 - 150.007324039945*x**4*z**2 + 150.007324039945*x**2*z**4 + 150.007324039945*z**6) - 15.0007324039945*z**8) + g_12*(117.843414378997*y*z**7 + z**5*(-117.843414378997*x**2*y - 785.622762526647*y**3) + z**3*(-589.217071894985*x**4*y + 1571.24552505329*x**2*y**3 + 628.498210021317*y**5) + z*(-353.530243136991*x**6*y + 2356.86828757994*x**4*y**3 - 1885.49463006395*x**2*y**5)) + g_13*(14.45506743704*x**8 - 57.8202697481601*x**6*z**2 - 144.5506743704*x**4*z**4 - 57.8202697481601*x**2*z**6 + 5.0*y**4*(115.64053949632*x**4 - 693.843236977922*x**2*z**2 + 115.64053949632*z**4) + 3.0*y**2*(-115.64053949632*x**6 + 578.202697481601*x**4*z**2 + 578.202697481601*x**2*z**4 - 115.64053949632*z**6) + 14.45506743704*z**8) + g_14*(4.0*y**3*(483.759084217031*x**4*z - 967.518168434061*x**2*z**3 + 96.7518168434061*z**5) + 2.0*y*(-241.879542108515*x**6*z + 241.879542108515*x**4*z**3 + 435.383175795328*x**2*z**5 - 48.375908421703*z**7)) + g_15*(13.3827919767794*x**8 - 187.359087674911*x**6*z**2 + 187.359087674911*x**2*z**6 + 3.0*y**2*(-62.4530292249704*x**6 + 936.795438374555*x**4*z**2 - 936.795438374555*x**2*z**4 + 62.4530292249704*z**6) - 13.3827919767794*z**8) + 2.0*g_16*y*(-216.343639408465*x**6*z + 1081.71819704233*x**4*z**3 - 649.030918225395*x**2*z**5 + 30.9062342012093*z**7) + g_17*(11.2632978048796*x**8 - 315.37233853663*x**6*z**2 + 788.430846341574*x**4*z**4 - 315.37233853663*x**2*z**6 + 11.2632978048796*z**8) + g_2*(-61.8124684024186*x**7*y + 1298.06183645079*x**5*y*z**2 - 2163.43639408465*x**3*y*z**4 + 432.68727881693*x*y*z**6) + g_3*(-80.2967518606762*x**7*z + 187.359087674911*x**5*z**3 + 187.359087674911*x**3*z**5 - 80.2967518606762*x*z**7 + 3.0*y**2*(374.718175349822*x**5*z - 1249.06058449941*x**3*z**3 + 374.718175349822*x*z**5)) + g_4*(4.0*y**3*(96.7518168434061*x**5 - 967.518168434061*x**3*z**2 + 483.759084217031*x*z**4) + 2.0*y*(-48.3759084217031*x**7 + 435.383175795327*x**5*z**2 + 241.879542108515*x**3*z**4 - 241.879542108515*x*z**6)) + g_5*(-57.8202697481601*x**7*z - 57.8202697481601*x**5*z**3 + 57.8202697481601*x**3*z**5 + 57.8202697481601*x*z**7 + 5.0*y**4*(-462.562157985281*x**3*z + 462.562157985281*x*z**3) + 3.0*y**2*(462.562157985281*x**5*z - 462.562157985281*x*z**5)) + g_6*(-117.843414378997*x**7*y + x**5*(785.622762526647*y**3 + 117.843414378997*y*z**2) + x**3*(-628.498210021317*y**5 - 1571.24552505329*y**3*z**2 + 589.217071894985*y*z**4) + x*(1885.49463006395*y**5*z**2 - 2356.86828757994*y**3*z**4 + 353.530243136991*y*z**6)) + g_7*(-30.001464807989*x**7*z + x**5*(900.043944239669*y**2*z - 90.0043944239669*z**3) + x**3*(-2400.11718463912*y**4*z + 1800.08788847934*y**2*z**3 - 90.0043944239669*z**5) + x*(960.046873855647*y**6*z - 2400.11718463912*y**4*z**3 + 900.043944239669*y**2*z**5 - 30.001464807989*z**7)) + g_8*(-127.926675775618*x**7*y + x**5*(1023.41340620494*y**3 - 383.780027326853*y*z**2) + x**3*(-1228.09608744593*y**5 + 2046.82681240988*y**3*z**2 - 383.780027326853*y*z**4) + x*(233.923064275415*y**7 - 1228.09608744593*y**5*z**2 + 1023.41340620494*y**3*z**4 - 127.926675775618*y*z**6)) + g_9*(10.7269778688696*x**8 + 42.9079114754785*x**6*z**2 + 64.3618672132178*x**4*z**4 + 42.9079114754785*x**2*z**6 + 39.2300904918661*y**8 + 7.0*y**6*(-78.4601809837321*x**2 - 78.4601809837321*z**2) + 5.0*y**4*(205.957975082297*x**4 + 411.915950164594*x**2*z**2 + 205.957975082297*z**4) + 3.0*y**2*(-114.421097267943*x**6 - 343.263291803828*x**4*z**2 - 343.263291803828*x**2*z**4 - 114.421097267943*z**6) + 10.7269778688696*z**8)", + "z": "g_0*(-191.144502152495*x**7*z + 1338.01151506746*x**5*z**3 - 1338.01151506746*x**3*z**5 + 191.144502152495*x*z**7) + g_1*y*(-90.106382439037*x**7 + 1892.23403121978*x**5*z**2 - 3153.7233853663*x**3*z**4 + 630.744677073259*x*z**6) + g_10*(1.59908344719522*x**8 - 63.9633378878088*x**6*y**2 + 255.853351551235*x**4*y**4 - 204.682681240988*x**2*y**6 + 29.2403830344269*y**8 + 14.391751024757*z**8 + 7.0*z**6*(6.39633378878088*x**2 - 63.9633378878088*y**2) + 5.0*z**4*(9.59450068317133*x**4 - 191.890013663427*x**2*y**2 + 255.853351551235*y**4) + 3.0*z**2*(6.39633378878088*x**6 - 191.890013663426*x**4*y**2 + 511.706703102471*x**2*y**4 - 204.682681240988*y**6)) + g_11*(137.14955340795*y**7*z - 960.046873855647*y**5*z**3 + y**3*(-300.01464807989*x**4*z + 600.029296159779*x**2*z**3 + 900.043944239669*z**5) + y*(60.0029296159779*x**6*z - 180.008788847934*x**2*z**5 - 120.005859231956*z**7)) + g_12*(4.91014226579154*x**8 - 176.765121568496*x**6*y**2 + 589.217071894985*x**4*y**4 - 314.249105010659*x**2*y**6 + 412.45195032649*y**2*z**6 - 14.7304267973746*z**8 + 5.0*z**4*(9.82028453158308*x**4 - 58.9217071894985*x**2*y**2 - 196.405690631662*y**4) + 3.0*z**2*(13.0937127087774*x**6 - 294.608535947493*x**4*y**2 + 392.811381263323*x**2*y**4 + 104.74970167022*y**6)) + g_13*(y**5*(-1387.68647395584*x**2*z + 462.562157985281*z**3) + y**3*(1156.4053949632*x**4*z + 2312.81078992641*x**2*z**3 - 693.843236977921*z**5) + y*(-115.64053949632*x**6*z - 578.202697481601*x**4*z**3 - 346.921618488961*x**2*z**5 + 115.64053949632*z**7)) + g_14*(8.63855507530412*x**8 - 120.939771054258*x**4*z**4 - 96.7518168434061*x**2*z**6 + y**4*(483.759084217031*x**4 - 2902.55450530218*x**2*z**2 + 483.759084217031*z**4) + y**2*(-241.879542108515*x**6 + 725.638626325546*x**4*z**2 + 2176.91587897664*x**2*z**4 - 338.631358951921*z**6) + 15.5493991355474*z**8) + g_15*(y**3*(1873.59087674911*x**4*z - 3747.18175349822*x**2*z**3 + 374.718175349822*z**5) + y*(-374.718175349822*x**6*z + 1124.15452604947*x**2*z**5 - 107.062335814235*z**7)) + g_16*(13.5214774630291*x**8 - 162.257729556349*x**6*z**2 - 135.214774630291*x**4*z**4 + 270.429549260581*x**2*z**6 + y**2*(-216.343639408465*x**6 + 3245.15459112698*x**4*z**2 - 3245.15459112698*x**2*z**4 + 216.343639408465*z**6) - 17.3847567381802*z**8) + g_17*y*(-630.744677073259*x**6*z + 3153.7233853663*x**4*z**3 - 1892.23403121978*x**2*z**5 + 90.106382439037*z**7) + g_18*(23.8930627690618*x**8 - 669.005757533731*x**6*z**2 + 1672.51439383433*x**4*z**4 - 669.005757533731*x**2*z**6 + 23.8930627690618*z**8) + g_2*(-77.2655855030233*x**7*z + x**5*(1298.06183645079*y**2*z + 108.171819704233*z**3) + x**3*(-4326.8727881693*y**2*z**3 + 324.515459112698*z**5) + x*(1298.06183645079*y**2*z**5 - 108.171819704233*z**7)) + g_3*(y**3*(374.718175349822*x**5 - 3747.18175349822*x**3*z**2 + 1873.59087674911*x*z**4) + y*(-80.2967518606762*x**7 + 562.077263024733*x**5*z**2 + 936.795438374555*x**3*z**4 - 562.077263024733*x*z**6)) + g_4*(-27.6433762409732*x**7*z - 96.7518168434061*x**5*z**3 + 69.1084406024329*x*z**7 + y**4*(-1935.03633686812*x**3*z + 1935.03633686812*x*z**3) + y**2*(870.766351590655*x**5*z + 967.518168434061*x**3*z**3 - 1451.27725265109*x*z**5)) + g_5*(y**5*(-462.562157985281*x**3 + 1387.68647395584*x*z**2) + y**3*(462.562157985281*x**5 - 2312.81078992641*x*z**4) + y*(-57.8202697481601*x**7 - 173.46080924448*x**5*z**2 + 289.101348740801*x**3*z**4 + 404.741888237121*x*z**6)) + g_6*(x**5*(117.843414378997*y**2*z - 39.2811381263323*z**3) + x**3*(-785.622762526647*y**4*z + 1178.43414378997*y**2*z**3 - 78.5622762526647*z**5) + x*(628.498210021318*y**6*z - 2356.86828757994*y**4*z**3 + 1060.59072941097*y**2*z**5 - 39.2811381263323*z**7)) + g_7*(-30.001464807989*x**7*y + x**5*(300.01464807989*y**3 - 270.013183271901*y*z**2) + x**3*(-480.023436927823*y**5 + 1800.08788847934*y**3*z**2 - 450.021972119834*y*z**4) + x*(137.14955340795*y**7 - 1440.07031078347*y**5*z**2 + 1500.07324039945*y**3*z**4 - 210.010253655923*y*z**6)) + g_8*(12.7926675775618*x**7*z + x**5*(-383.780027326853*y**2*z + 38.3780027326853*z**3) + x**3*(1023.41340620494*y**4*z - 767.560054653706*y**2*z**3 + 38.3780027326853*z**5) + x*(-409.365362481976*y**6*z + 1023.41340620494*y**4*z**3 - 383.780027326853*y**2*z**5 + 12.7926675775618*z**7)) + g_9*(-156.920361967464*y**7*z + y**5*(823.831900329187*x**2*z + 823.831900329187*z**3) + y**3*(-686.526583607656*x**4*z - 1373.05316721531*x**2*z**3 - 686.526583607656*z**5) + y*(85.815822950957*x**6*z + 257.447468852871*x**4*z**3 + 257.447468852871*x**2*z**5 + 85.815822950957*z**7))" + } +} \ No newline at end of file diff --git a/notebooks/fwd_implementations/fwd_10.py b/notebooks/fwd_implementations/fwd_10.py index 4918ac9..fc2cb74 100644 --- a/notebooks/fwd_implementations/fwd_10.py +++ b/notebooks/fwd_implementations/fwd_10.py @@ -9,67 +9,67 @@ CONST007 = 4.97432985632550 CONST008 = 1545.18657853995 CONST009 = 10.5521471197994 -CONST010 = 4.97432985632550 -CONST011 = 12.1657520803952 -CONST012 = 13.2648796168680 -CONST013 = 14.5025390506634 -CONST014 = 15.7883647328499 -CONST015 = 15.7302121789667 -CONST016 = 16.4144510752435 -CONST017 = 12.8765548211663 -CONST018 = 19.3148322317494 -CONST019 = 16.7271353825295 -CONST020 = 22.8629854262320 -CONST021 = 535.268332240943 -CONST022 = 23.2135393295190 -CONST023 = 24.6216766128653 -CONST024 = 27.2034486491732 -CONST025 = 541.428124558099 -CONST026 = -994.666978169547 +CONST010 = 12.1657520803952 +CONST011 = 13.2648796168680 +CONST012 = 14.5025390506634 +CONST013 = 15.7883647328499 +CONST014 = 15.7302121789667 +CONST015 = 16.4144510752435 +CONST016 = 12.8765548211663 +CONST017 = 19.3148322317494 +CONST018 = 16.7271353825295 +CONST019 = 22.8629854262320 +CONST020 = 535.268332240943 +CONST021 = 23.2135393295190 +CONST022 = 24.6216766128653 +CONST023 = 27.2034486491732 +CONST024 = 541.428124558099 +CONST025 = -994.666978169547 +CONST026 = 33.9852909359329 CONST027 = 33.9852909359329 -CONST028 = 33.9852909359329 -CONST029 = 35.5238206489124 -CONST030 = -984.867064514610 -CONST031 = -4.82870805793735 -CONST032 = 1070.53666448189 -CONST033 = -463.555973561985 -CONST034 = 49.2433532257305 +CONST028 = 35.5238206489124 +CONST029 = -984.867064514610 +CONST030 = -4.82870805793735 +CONST031 = 1070.53666448189 +CONST032 = -463.555973561985 +CONST033 = 49.2433532257305 +CONST034 = 53.2857309733686 CONST035 = 53.2857309733686 -CONST036 = 53.2857309733686 +CONST036 = 56.3871618715269 CONST037 = 56.3871618715269 -CONST038 = 56.3871618715269 -CONST039 = 56.2781179722634 -CONST040 = -1989.33395633909 -CONST041 = 571.272421632637 -CONST042 = -450.224943778107 -CONST043 = 66.9085415301178 +CONST038 = 56.2781179722634 +CONST039 = -1989.33395633909 +CONST040 = 571.272421632637 +CONST041 = -450.224943778107 +CONST042 = 66.9085415301178 +CONST043 = 69.6406179885570 CONST044 = 69.6406179885570 -CONST045 = 69.6406179885570 -CONST046 = -437.967074894228 -CONST047 = 77.2593289269976 -CONST048 = 78.6510608948335 -CONST049 = 590.920238708766 -CONST050 = -1969.73412902922 -CONST051 = 77.3468749368712 -CONST052 = 1624.28437367430 -CONST053 = 88.2963759165686 -CONST054 = 1114.24988781691 -CONST055 = 94.7301883970997 -CONST056 = 98.4867064514610 -CONST057 = 100.362812295177 -CONST058 = -412.049754277320 -CONST059 = 101.517773354644 -CONST060 = -5.63871618715269 -CONST061 = -406.071093418574 -CONST062 = 109.491768723557 -CONST063 = -393.946825805844 -CONST064 = -902.194589944431 -CONST065 = 122.415518921279 +CONST045 = -437.967074894228 +CONST046 = 77.2593289269976 +CONST047 = 78.6510608948335 +CONST048 = 590.920238708766 +CONST049 = -1969.73412902922 +CONST050 = 77.3468749368712 +CONST051 = 1624.28437367430 +CONST052 = 88.2963759165686 +CONST053 = 1114.24988781691 +CONST054 = 94.7301883970997 +CONST055 = 98.4867064514610 +CONST056 = 100.362812295177 +CONST057 = -412.049754277320 +CONST058 = 101.517773354644 +CONST059 = -5.63871618715269 +CONST060 = -406.071093418574 +CONST061 = 109.491768723557 +CONST062 = -393.946825805844 +CONST063 = -902.194589944431 +CONST064 = 122.415518921279 +CONST065 = -386.296644634988 CONST066 = -386.296644634988 -CONST067 = -386.296644634988 +CONST067 = 131.315608601948 CONST068 = 131.315608601948 -CONST069 = 131.315608601948 -CONST070 = 2707.14062279049 +CONST069 = 2707.14062279049 +CONST070 = 4.97432985632550 CONST071 = 150.074981259369 CONST072 = 154.518657853995 CONST073 = 1181.84047741753 @@ -127,10 +127,10 @@ CONST125 = 338.322971229162 CONST126 = -1181.84047741753 CONST127 = -669.085415301178 -CONST128 = -154.518657853995 -CONST129 = -669.085415301178 -CONST130 = 360.877835977772 -CONST131 = -154.518657853995 +CONST128 = -669.085415301178 +CONST129 = -154.518657853995 +CONST130 = -154.518657853995 +CONST131 = 360.877835977772 CONST132 = -150.074981259369 CONST133 = -2707.14062279049 CONST134 = -146.815313670356 @@ -148,8 +148,8 @@ CONST146 = -103.107953136506 CONST147 = -103.107953136506 CONST148 = -101.517773354644 -CONST149 = 412.049754277320 -CONST150 = -98.4867064514610 +CONST149 = -98.4867064514610 +CONST150 = 412.049754277320 CONST151 = -94.7301883970997 CONST152 = -1114.24988781691 CONST153 = -88.2963759165686 @@ -176,63 +176,60 @@ CONST174 = -15.7883647328499 CONST175 = -14.0695294930659 CONST176 = -13.2648796168680 -CONST177 = -14.5025390506634 -CONST178 = -11.2774323743054 +CONST177 = -11.2774323743054 +CONST178 = -14.5025390506634 CONST179 = -6.63243980843400 CONST180 = -5.63871618715269 CONST181 = 1532.88476212980 CONST182 = -3.21913870529156 CONST183 = -2.72034486491732 CONST184 = -1.12774323743054 -VAR00 = z**4 -VAR01 = x**7 -VAR02 = z**6 -VAR03 = x**9 -VAR04 = x**2 -VAR05 = z**8 -VAR06 = y -VAR07 = y**6 -VAR08 = x**4 -VAR09 = z**10 -VAR10 = y**8 -VAR11 = z**3 -VAR12 = x**6 -VAR13 = z -VAR14 = y**10 -VAR15 = x -VAR16 = z**5 -VAR17 = x**8 -VAR18 = y**3 -VAR19 = x**10 -VAR20 = y**5 -VAR21 = x**3 -VAR22 = y**7 -VAR23 = x**5 -VAR24 = y**9 -VAR25 = z**7 -VAR26 = y**2 -VAR27 = z**9 -VAR28 = y**4 -VAR29 = z**2 +VAR00 = x**10 +VAR01 = x**9 +VAR02 = x**8 +VAR03 = x**7 +VAR04 = x**6 +VAR05 = x**5 +VAR06 = x**4 +VAR07 = x**3 +VAR08 = x**2 +VAR09 = y**10 +VAR10 = y**9 +VAR11 = y**8 +VAR12 = y**7 +VAR13 = y**6 +VAR14 = y**5 +VAR15 = y**4 +VAR16 = y**3 +VAR17 = y**2 +VAR18 = z**10 +VAR19 = z**9 +VAR20 = z**8 +VAR21 = z**7 +VAR22 = z**6 +VAR23 = z**5 +VAR24 = z**4 +VAR25 = z**3 +VAR26 = z**2 # -------------------- kernel implementations -Y00 = CONST024*VAR11**3*VAR15 + CONST024*VAR13*VAR21**3 + CONST074*VAR16*VAR23 + CONST080*VAR01*VAR11 + CONST080*VAR21*VAR25 -Y01 = VAR06*(CONST002*VAR21*VAR29**3 + CONST011*VAR21**3 + CONST046*VAR01*VAR29 + CONST062*VAR15*VAR29**4 + CONST181*VAR23*VAR29**2) -Y02 = CONST014*VAR13*VAR21**3 + CONST055*VAR21*VAR25 + CONST151*VAR01*VAR11 + CONST174*VAR11**3*VAR15 + VAR26*(-CONST040*VAR11*VAR23 + CONST040*VAR16*VAR21 + CONST099*VAR01*VAR13 - CONST099*VAR15*VAR25) -Y03 = VAR06*(CONST095*VAR01*VAR29 - CONST119*VAR23*VAR29**2 + CONST145*VAR21*VAR29**3 + CONST148*VAR15*VAR29**4 - CONST177*VAR21**3) + VAR18*(CONST025*VAR15*VAR29**3 + CONST052*VAR23*VAR29 + CONST133*VAR21*VAR29**2 + CONST159*VAR01) -Y04 = CONST009*VAR13*VAR21**3 + VAR01*(CONST076*VAR13*VAR26 + CONST175*VAR11) + VAR15*(CONST009*VAR11**3 + CONST075*VAR25*VAR26 + CONST106*VAR16*VAR26**2) + VAR21*(CONST106*VAR16*VAR26 + CONST162*VAR11*VAR26**2 + CONST175*VAR25) + VAR23*(CONST106*VAR13*VAR26**2 + CONST107*VAR11*VAR26 + CONST167*VAR16) -Y05 = VAR06*(CONST015*VAR21**3 + CONST048*VAR15*VAR29**4 + CONST116*VAR23*VAR29**2 + CONST141*VAR01*VAR29) + VAR18*(CONST114*VAR15*VAR29**3 - CONST114*VAR21*VAR29**2 + CONST117*VAR23*VAR29 + CONST134*VAR01) + VAR20*(CONST077*VAR23 + CONST112*VAR21*VAR29 + CONST135*VAR15*VAR29**2) -Y06 = CONST005*VAR13*VAR21**3 + VAR01*(CONST012*VAR11 + CONST102*VAR13*VAR26) + VAR15*(CONST108*VAR25*VAR26 - CONST109*VAR11*VAR26**3 + CONST152*VAR16*VAR26**2 + CONST179*VAR11**3) + VAR21*(CONST108*VAR16*VAR26 + CONST109*VAR13*VAR26**3 + CONST176*VAR25) + VAR23*(CONST101*VAR11*VAR26 - CONST152*VAR13*VAR26**2) -Y07 = VAR06*(CONST016*VAR21**3 + CONST138*VAR21*VAR29**3 + CONST150*VAR23*VAR29**2 + CONST168*VAR15*VAR29**4) + VAR18*(CONST083*VAR23*VAR29 + CONST121*VAR01 - CONST158*VAR15*VAR29**3 + CONST169*VAR21*VAR29**2) + VAR20*(-CONST063*VAR23 + CONST103*VAR21*VAR29 + CONST126*VAR15*VAR29**2) + VAR22*(-CONST042*VAR15*VAR29 + CONST132*VAR21) -Y08 = -CONST182*VAR13*VAR21**3 + VAR01*(CONST017*VAR11 + CONST128*VAR13*VAR26) + VAR15*(CONST078*VAR13*VAR26**4 + CONST085*VAR11*VAR26**3 + CONST105*VAR16*VAR26**2 + CONST128*VAR25*VAR26 - CONST182*VAR11**3) + VAR21*(CONST008*VAR11*VAR26**2 + CONST017*VAR25 + CONST033*VAR16*VAR26 + CONST085*VAR13*VAR26**3) + VAR23*(CONST018*VAR16 + CONST033*VAR11*VAR26 + CONST105*VAR13*VAR26**2) -Y09 = CONST019*VAR06*VAR21**3 + VAR01*(CONST043*VAR06*VAR29 + CONST113*VAR18) + VAR15*(CONST019*VAR06*VAR29**4 + CONST021*VAR20*VAR29**2 + CONST027*VAR18**3 + CONST088*VAR22*VAR29 + CONST113*VAR18*VAR29**3) + VAR21*(CONST032*VAR20*VAR29 + CONST043*VAR06*VAR29**3 + CONST088*VAR22 + CONST127*VAR18*VAR29**2) + VAR23*(CONST021*VAR20 + CONST057*VAR06*VAR29**2 + CONST129*VAR18*VAR29) -Y10 = CONST004*VAR26**5 + CONST038*VAR26*VAR29**4 + CONST093*VAR26**2*VAR29**3 + CONST130*VAR26**3*VAR29**2 + CONST147*VAR26**4*VAR29 + CONST184*VAR04**5 + CONST184*VAR29**5 + VAR04**4*(CONST037*VAR26 + CONST060*VAR29) + VAR04**3*(CONST092*VAR26**2 + CONST098*VAR26*VAR29 + CONST178*VAR29**2) + VAR04**2*(CONST064*VAR26**2*VAR29 + CONST125*VAR26*VAR29**2 + CONST130*VAR26**3 + CONST178*VAR29**3) + VAR04*(CONST064*VAR26**2*VAR29**2 + CONST090*VAR26**3*VAR29 + CONST097*VAR26*VAR29**3 + CONST146*VAR26**4 + CONST180*VAR29**4) -Y11 = CONST019*VAR06*VAR11**3 + VAR11*(CONST032*VAR04*VAR20 + CONST043*VAR04**3*VAR06 + CONST091*VAR22 + CONST127*VAR04**2*VAR18) + VAR13*(CONST019*VAR04**4*VAR06 + CONST021*VAR04**2*VAR20 + CONST028*VAR18**3 + CONST089*VAR04*VAR22 + CONST113*VAR04**3*VAR18) + VAR16*(CONST021*VAR20 + CONST057*VAR04**2*VAR06 + CONST129*VAR04*VAR18) + VAR25*(CONST043*VAR04*VAR06 + CONST113*VAR18) -Y12 = CONST058*VAR26**3*VAR29**2 - CONST067*VAR26**2*VAR29**3 + CONST081*VAR04**5 - CONST081*VAR29**5 - CONST153*VAR26**4*VAR29 + CONST160*VAR26*VAR29**4 + VAR04**4*(CONST031*VAR29 + CONST047*VAR26) + VAR04**3*(CONST067*VAR26**2 - CONST128*VAR26*VAR29 + CONST182*VAR29**2) + VAR04**2*(CONST066*VAR26**2*VAR29 + CONST149*VAR26**3 - CONST182*VAR29**3) + VAR04*(CONST006*VAR29**4 - CONST067*VAR26**2*VAR29**2 + CONST131*VAR26*VAR29**3 + CONST153*VAR26**4) -Y13 = VAR06*(-CONST138*VAR04**3*VAR11 - CONST150*VAR04**2*VAR16 - CONST168*VAR04**4*VAR13 + CONST173*VAR11**3) + VAR18*(CONST030*VAR04**2*VAR11 - CONST121*VAR25 + CONST122*VAR04*VAR16 + CONST158*VAR04**3*VAR13) + VAR20*(CONST063*VAR16 + CONST107*VAR04*VAR11 - CONST126*VAR04**2*VAR13) + VAR22*(CONST042*VAR04*VAR13 + CONST071*VAR11) -Y14 = CONST045*VAR26*VAR29**4 + CONST079*VAR26**3*VAR29**2 + CONST101*VAR26**2*VAR29**3 + CONST110*VAR04**5 + CONST120*VAR29**5 + VAR04**4*(CONST010*VAR29 + CONST044*VAR26) + VAR04**3*(CONST022*VAR29**2 + CONST101*VAR26**2 + CONST101*VAR26*VAR29) + VAR04**2*(CONST022*VAR29**3 + CONST079*VAR26**3 + CONST123*VAR26*VAR29**2 + CONST137*VAR26**2*VAR29) + VAR04*(CONST007*VAR29**4 + CONST101*VAR26*VAR29**3 + CONST136*VAR26**2*VAR29**2 + CONST152*VAR26**3*VAR29) -Y15 = VAR06*(CONST015*VAR11**3 + CONST048*VAR04**4*VAR13 + CONST116*VAR04**2*VAR16 + CONST142*VAR04*VAR25) + VAR18*(CONST114*VAR04**3*VAR13 - CONST114*VAR04**2*VAR11 + CONST118*VAR04*VAR16 + CONST134*VAR25) + VAR20*(CONST077*VAR16 + CONST112*VAR04*VAR11 + CONST135*VAR04**2*VAR13) -Y16 = CONST001*VAR29**5 + CONST094*VAR04**5 - CONST139*VAR26**2*VAR29**3 + CONST166*VAR26*VAR29**4 + VAR04**4*(CONST020*VAR29 - CONST166*VAR26) + VAR04**3*(CONST023*VAR29**2 + CONST104*VAR26*VAR29 + CONST139*VAR26**2) + VAR04**2*(-CONST050*VAR26**2*VAR29 + CONST171*VAR29**3) + VAR04*(CONST050*VAR26**2*VAR29**2 + CONST106*VAR26*VAR29**3 + CONST172*VAR29**4) -Y17 = VAR06*(CONST059*VAR04**4*VAR13 + CONST061*VAR04**3*VAR11 - CONST095*VAR04*VAR25 + CONST119*VAR04**2*VAR16 + CONST177*VAR11**3) + VAR18*(CONST051*VAR25 - CONST133*VAR04**2*VAR11 + CONST154*VAR04*VAR16 + CONST170*VAR04**3*VAR13) -Y18 = CONST035*VAR04**4*VAR29 + CONST036*VAR04*VAR29**4 + CONST082*VAR04**5 + CONST087*VAR29**5 + CONST155*VAR04**3*VAR29**2 + CONST156*VAR04**2*VAR29**3 + VAR26*(CONST026*VAR04**3*VAR29 + CONST026*VAR04*VAR29**3 + CONST029*VAR04**4 + CONST029*VAR29**4 + CONST161*VAR04**2*VAR29**2) -Y19 = VAR06*(CONST002*VAR04**3*VAR11 + CONST011*VAR11**3 + CONST046*VAR04*VAR25 + CONST062*VAR04**4*VAR13 + CONST181*VAR04**2*VAR16) -Y20 = -CONST143*VAR04**4*VAR29 + CONST143*VAR04*VAR29**4 + CONST165*VAR04**3*VAR29**2 - CONST165*VAR04**2*VAR29**3 + CONST183*VAR04**5 - CONST183*VAR29**5 +Y00 = CONST023*VAR01*z + CONST023*VAR19*x + CONST074*VAR05*VAR23 + CONST080*VAR03*VAR25 + CONST080*VAR07*VAR21 +Y01 = y*(CONST002*VAR07*VAR22 + CONST010*VAR01 + CONST045*VAR03*VAR26 + CONST061*VAR20*x + CONST181*VAR05*VAR24) +Y02 = CONST013*VAR01*z + CONST054*VAR07*VAR21 + CONST151*VAR03*VAR25 + CONST174*VAR19*x + VAR17*(-CONST039*VAR05*VAR25 + CONST039*VAR07*VAR23 + CONST099*VAR03*z - CONST099*VAR21*x) +Y03 = VAR16*(CONST024*VAR22*x + CONST051*VAR05*VAR26 + CONST133*VAR07*VAR24 + CONST159*VAR03) + y*(CONST095*VAR03*VAR26 - CONST119*VAR05*VAR24 + CONST145*VAR07*VAR22 + CONST148*VAR20*x - CONST178*VAR01) +Y04 = CONST009*VAR01*z + VAR03*(CONST076*VAR17*z + CONST175*VAR25) + VAR05*(CONST106*VAR15*z + CONST107*VAR17*VAR25 + CONST167*VAR23) + VAR07*(CONST106*VAR17*VAR23 + CONST162*VAR15*VAR25 + CONST175*VAR21) + x*(CONST009*VAR19 + CONST075*VAR17*VAR21 + CONST106*VAR15*VAR23) +Y05 = VAR14*(CONST077*VAR05 + CONST112*VAR07*VAR26 + CONST135*VAR24*x) + VAR16*(-CONST114*VAR07*VAR24 + CONST114*VAR22*x + CONST117*VAR05*VAR26 + CONST134*VAR03) + y*(CONST014*VAR01 + CONST047*VAR20*x + CONST116*VAR05*VAR24 + CONST141*VAR03*VAR26) +Y06 = CONST005*VAR01*z + VAR03*(CONST011*VAR25 + CONST102*VAR17*z) + VAR05*(CONST101*VAR17*VAR25 - CONST152*VAR15*z) + VAR07*(CONST108*VAR17*VAR23 + CONST109*VAR13*z + CONST176*VAR21) + x*(CONST108*VAR17*VAR21 - CONST109*VAR13*VAR25 + CONST152*VAR15*VAR23 + CONST179*VAR19) +Y07 = VAR12*(-CONST041*VAR26*x + CONST132*VAR07) + VAR14*(-CONST062*VAR05 + CONST103*VAR07*VAR26 + CONST126*VAR24*x) + VAR16*(CONST083*VAR05*VAR26 + CONST121*VAR03 - CONST158*VAR22*x + CONST169*VAR07*VAR24) + y*(CONST015*VAR01 + CONST138*VAR07*VAR22 + CONST149*VAR05*VAR24 + CONST168*VAR20*x) +Y08 = -CONST182*VAR01*z + VAR03*(CONST016*VAR25 + CONST129*VAR17*z) + VAR05*(CONST017*VAR23 + CONST032*VAR17*VAR25 + CONST105*VAR15*z) + VAR07*(CONST008*VAR15*VAR25 + CONST016*VAR21 + CONST032*VAR17*VAR23 + CONST085*VAR13*z) + x*(CONST078*VAR11*z + CONST085*VAR13*VAR25 + CONST105*VAR15*VAR23 + CONST129*VAR17*VAR21 - CONST182*VAR19) +Y09 = CONST018*VAR01*y + VAR03*(CONST042*VAR26*y + CONST113*VAR16) + VAR05*(CONST020*VAR14 + CONST056*VAR24*y + CONST128*VAR16*VAR26) + VAR07*(CONST031*VAR14*VAR26 + CONST042*VAR22*y + CONST088*VAR12 + CONST127*VAR16*VAR24) + x*(CONST018*VAR20*y + CONST020*VAR14*VAR24 + CONST026*VAR10 + CONST088*VAR12*VAR26 + CONST113*VAR16*VAR22) +Y10 = CONST004*VAR09 + CONST037*VAR17*VAR20 + CONST093*VAR15*VAR22 + CONST131*VAR13*VAR24 + CONST147*VAR11*VAR26 + CONST184*VAR00 + CONST184*VAR18 + VAR02*(CONST036*VAR17 + CONST059*VAR26) + VAR04*(CONST092*VAR15 + CONST098*VAR17*VAR26 + CONST177*VAR24) + VAR06*(CONST063*VAR15*VAR26 + CONST125*VAR17*VAR24 + CONST131*VAR13 + CONST177*VAR22) + VAR08*(CONST063*VAR15*VAR24 + CONST090*VAR13*VAR26 + CONST097*VAR17*VAR22 + CONST146*VAR11 + CONST180*VAR20) +Y11 = CONST018*VAR19*y + VAR21*(CONST042*VAR08*y + CONST113*VAR16) + VAR23*(CONST020*VAR14 + CONST056*VAR06*y + CONST128*VAR08*VAR16) + VAR25*(CONST031*VAR08*VAR14 + CONST042*VAR04*y + CONST091*VAR12 + CONST127*VAR06*VAR16) + z*(CONST018*VAR02*y + CONST020*VAR06*VAR14 + CONST027*VAR10 + CONST089*VAR08*VAR12 + CONST113*VAR04*VAR16) +Y12 = CONST057*VAR13*VAR24 - CONST066*VAR15*VAR22 + CONST081*VAR00 - CONST081*VAR18 - CONST153*VAR11*VAR26 + CONST160*VAR17*VAR20 + VAR02*(CONST030*VAR26 + CONST046*VAR17) + VAR04*(CONST066*VAR15 - CONST129*VAR17*VAR26 + CONST182*VAR24) + VAR06*(CONST065*VAR15*VAR26 + CONST150*VAR13 - CONST182*VAR22) + VAR08*(CONST006*VAR20 - CONST066*VAR15*VAR24 + CONST130*VAR17*VAR22 + CONST153*VAR11) +Y13 = VAR12*(CONST041*VAR08*z + CONST071*VAR25) + VAR14*(CONST062*VAR23 + CONST107*VAR08*VAR25 - CONST126*VAR06*z) + VAR16*(CONST029*VAR06*VAR25 - CONST121*VAR21 + CONST122*VAR08*VAR23 + CONST158*VAR04*z) + y*(-CONST138*VAR04*VAR25 - CONST149*VAR06*VAR23 - CONST168*VAR02*z + CONST173*VAR19) +Y14 = CONST044*VAR17*VAR20 + CONST079*VAR13*VAR24 + CONST101*VAR15*VAR22 + CONST110*VAR00 + CONST120*VAR18 + VAR02*(CONST043*VAR17 + CONST070*VAR26) + VAR04*(CONST021*VAR24 + CONST101*VAR15 + CONST101*VAR17*VAR26) + VAR06*(CONST021*VAR22 + CONST079*VAR13 + CONST123*VAR17*VAR24 + CONST137*VAR15*VAR26) + VAR08*(CONST007*VAR20 + CONST101*VAR17*VAR22 + CONST136*VAR15*VAR24 + CONST152*VAR13*VAR26) +Y15 = VAR14*(CONST077*VAR23 + CONST112*VAR08*VAR25 + CONST135*VAR06*z) + VAR16*(CONST114*VAR04*z - CONST114*VAR06*VAR25 + CONST118*VAR08*VAR23 + CONST134*VAR21) + y*(CONST014*VAR19 + CONST047*VAR02*z + CONST116*VAR06*VAR23 + CONST142*VAR08*VAR21) +Y16 = CONST001*VAR18 + CONST094*VAR00 - CONST139*VAR15*VAR22 + CONST166*VAR17*VAR20 + VAR02*(CONST019*VAR26 - CONST166*VAR17) + VAR04*(CONST022*VAR24 + CONST104*VAR17*VAR26 + CONST139*VAR15) + VAR06*(-CONST049*VAR15*VAR26 + CONST171*VAR22) + VAR08*(CONST049*VAR15*VAR24 + CONST106*VAR17*VAR22 + CONST172*VAR20) +Y17 = VAR16*(CONST050*VAR21 - CONST133*VAR06*VAR25 + CONST154*VAR08*VAR23 + CONST170*VAR04*z) + y*(CONST058*VAR02*z + CONST060*VAR04*VAR25 - CONST095*VAR08*VAR21 + CONST119*VAR06*VAR23 + CONST178*VAR19) +Y18 = CONST034*VAR02*VAR26 + CONST035*VAR08*VAR20 + CONST082*VAR00 + CONST087*VAR18 + CONST155*VAR04*VAR24 + CONST156*VAR06*VAR22 + VAR17*(CONST025*VAR04*VAR26 + CONST025*VAR08*VAR22 + CONST028*VAR02 + CONST028*VAR20 + CONST161*VAR06*VAR24) +Y19 = y*(CONST002*VAR04*VAR25 + CONST010*VAR19 + CONST045*VAR08*VAR21 + CONST061*VAR02*z + CONST181*VAR06*VAR23) +Y20 = -CONST143*VAR02*VAR26 + CONST143*VAR08*VAR20 + CONST165*VAR04*VAR24 - CONST165*VAR06*VAR22 + CONST183*VAR00 - CONST183*VAR18 diff --git a/notebooks/fwd_implementations/fwd_2.py b/notebooks/fwd_implementations/fwd_2.py index 527a70d..faa1e85 100644 --- a/notebooks/fwd_implementations/fwd_2.py +++ b/notebooks/fwd_implementations/fwd_2.py @@ -4,15 +4,36 @@ CONST002 = 3.87298334620742 CONST003 = -1.93649167310371 CONST004 = -1.11803398874989 -VAR00 = y**2 -VAR01 = x**2 -VAR02 = z**2 -VAR03 = z -VAR04 = x -VAR05 = y +VAR00 = x**10 +VAR01 = x**9 +VAR02 = x**8 +VAR03 = x**7 +VAR04 = x**6 +VAR05 = x**5 +VAR06 = x**4 +VAR07 = x**3 +VAR08 = x**2 +VAR09 = y**10 +VAR10 = y**9 +VAR11 = y**8 +VAR12 = y**7 +VAR13 = y**6 +VAR14 = y**5 +VAR15 = y**4 +VAR16 = y**3 +VAR17 = y**2 +VAR18 = z**10 +VAR19 = z**9 +VAR20 = z**8 +VAR21 = z**7 +VAR22 = z**6 +VAR23 = z**5 +VAR24 = z**4 +VAR25 = z**3 +VAR26 = z**2 # -------------------- kernel implementations -Y00 = CONST002*VAR03*VAR04 -Y01 = CONST002*VAR04*VAR05 -Y02 = CONST001*VAR00 + CONST004*VAR01 + CONST004*VAR02 -Y03 = CONST002*VAR03*VAR05 -Y04 = CONST003*VAR01 - CONST003*VAR02 +Y00 = CONST002*x*z +Y01 = CONST002*x*y +Y02 = CONST001*VAR17 + CONST004*VAR08 + CONST004*VAR26 +Y03 = CONST002*y*z +Y04 = CONST003*VAR08 - CONST003*VAR26 diff --git a/notebooks/fwd_implementations/fwd_3.py b/notebooks/fwd_implementations/fwd_3.py index a54aa11..0630c5c 100644 --- a/notebooks/fwd_implementations/fwd_3.py +++ b/notebooks/fwd_implementations/fwd_3.py @@ -10,20 +10,38 @@ CONST008 = -6.27495019900557 CONST009 = -3.96862696659689 CONST010 = -1.62018517460197 -VAR00 = y**3 -VAR01 = y**2 -VAR02 = x**3 -VAR03 = z**3 -VAR04 = z -VAR05 = x -VAR06 = x**2 -VAR07 = z**2 -VAR08 = y +VAR00 = x**10 +VAR01 = x**9 +VAR02 = x**8 +VAR03 = x**7 +VAR04 = x**6 +VAR05 = x**5 +VAR06 = x**4 +VAR07 = x**3 +VAR08 = x**2 +VAR09 = y**10 +VAR10 = y**9 +VAR11 = y**8 +VAR12 = y**7 +VAR13 = y**6 +VAR14 = y**5 +VAR15 = y**4 +VAR16 = y**3 +VAR17 = y**2 +VAR18 = z**10 +VAR19 = z**9 +VAR20 = z**8 +VAR21 = z**7 +VAR22 = z**6 +VAR23 = z**5 +VAR24 = z**4 +VAR25 = z**3 +VAR26 = z**2 # -------------------- kernel implementations -Y00 = CONST006*VAR02 - CONST008*VAR05*VAR07 -Y01 = CONST005*VAR04*VAR05*VAR08 -Y02 = CONST010*VAR02 + VAR05*(CONST004*VAR01 + CONST010*VAR07) -Y03 = CONST000*VAR00 + CONST009*VAR06*VAR08 + CONST009*VAR07*VAR08 -Y04 = CONST010*VAR03 + VAR04*(CONST004*VAR01 + CONST010*VAR06) -Y05 = CONST002*VAR08*(CONST007*VAR06 + VAR07) -Y06 = -CONST006*VAR03 + CONST008*VAR04*VAR06 +Y00 = CONST006*VAR07 - CONST008*VAR26*x +Y01 = CONST005*x*y*z +Y02 = CONST010*VAR07 + x*(CONST004*VAR17 + CONST010*VAR26) +Y03 = CONST000*VAR16 + CONST009*VAR08*y + CONST009*VAR26*y +Y04 = CONST010*VAR25 + z*(CONST004*VAR17 + CONST010*VAR08) +Y05 = CONST002*y*(CONST007*VAR08 + VAR26) +Y06 = -CONST006*VAR25 + CONST008*VAR08*z diff --git a/notebooks/fwd_implementations/fwd_4.py b/notebooks/fwd_implementations/fwd_4.py index 79328c5..9a3d2d8 100644 --- a/notebooks/fwd_implementations/fwd_4.py +++ b/notebooks/fwd_implementations/fwd_4.py @@ -3,8 +3,8 @@ CONST001 = 2.25000000000000 CONST002 = 3.00000000000000 CONST003 = 1.67705098312484 -CONST004 = 2.21852991866236 -CONST005 = 6.27495019900557 +CONST004 = 6.27495019900557 +CONST005 = 2.21852991866236 CONST006 = 8.87411967464942 CONST007 = 9.48683298050514 CONST008 = 10.0623058987491 @@ -19,25 +19,40 @@ CONST017 = -6.27495019900557 CONST018 = -3.35410196624968 CONST019 = -1.67705098312484 -VAR00 = y -VAR01 = x**4 -VAR02 = z**4 -VAR03 = y**2 -VAR04 = x**3 -VAR05 = z**3 -VAR06 = y**4 -VAR07 = z -VAR08 = x -VAR09 = x**2 -VAR10 = z**2 -VAR11 = y**3 +VAR00 = x**10 +VAR01 = x**9 +VAR02 = x**8 +VAR03 = x**7 +VAR04 = x**6 +VAR05 = x**5 +VAR06 = x**4 +VAR07 = x**3 +VAR08 = x**2 +VAR09 = y**10 +VAR10 = y**9 +VAR11 = y**8 +VAR12 = y**7 +VAR13 = y**6 +VAR14 = y**5 +VAR15 = y**4 +VAR16 = y**3 +VAR17 = y**2 +VAR18 = z**10 +VAR19 = z**9 +VAR20 = z**8 +VAR21 = z**7 +VAR22 = z**6 +VAR23 = z**5 +VAR24 = z**4 +VAR25 = z**3 +VAR26 = z**2 # -------------------- kernel implementations -Y00 = CONST015*VAR04*VAR07 - CONST015*VAR05*VAR08 -Y01 = VAR00*(-CONST011*VAR08*VAR10 + CONST017*VAR04) -Y02 = CONST018*VAR04*VAR07 + VAR08*(CONST010*VAR03*VAR07 + CONST018*VAR05) -Y03 = CONST016*VAR00*VAR04 + VAR08*(CONST007*VAR11 + CONST016*VAR00*VAR10) -Y04 = CONST000*VAR09**2 + CONST000*VAR10**2 + CONST002*VAR03**2 + CONST014*VAR03*VAR10 + VAR09*(CONST001*VAR10 + CONST014*VAR03) -Y05 = CONST016*VAR00*VAR05 + VAR07*(CONST007*VAR11 + CONST016*VAR00*VAR09) -Y06 = -CONST019*VAR09**2 + CONST019*VAR10**2 + VAR03*(CONST013*VAR09 - CONST013*VAR10) -Y07 = VAR00*(CONST011*VAR07*VAR09 - CONST017*VAR05) -Y08 = CONST004*VAR09**2 + CONST004*VAR10**2 + CONST012*VAR09*VAR10 +Y00 = CONST015*VAR07*z - CONST015*VAR25*x +Y01 = y*(-CONST011*VAR26*x + CONST017*VAR07) +Y02 = CONST018*VAR07*z + x*(CONST010*VAR17*z + CONST018*VAR25) +Y03 = CONST016*VAR07*y + x*(CONST007*VAR16 + CONST016*VAR26*y) +Y04 = CONST000*VAR06 + CONST000*VAR24 + CONST002*VAR15 + CONST014*VAR17*VAR26 + VAR08*(CONST001*VAR26 + CONST014*VAR17) +Y05 = CONST016*VAR25*y + z*(CONST007*VAR16 + CONST016*VAR08*y) +Y06 = -CONST019*VAR06 + CONST019*VAR24 + VAR17*(CONST013*VAR08 - CONST013*VAR26) +Y07 = y*(CONST011*VAR08*z - CONST017*VAR25) +Y08 = CONST005*VAR06 + CONST005*VAR24 + CONST012*VAR08*VAR26 diff --git a/notebooks/fwd_implementations/fwd_5.py b/notebooks/fwd_implementations/fwd_5.py index 8a71186..df1e2c9 100644 --- a/notebooks/fwd_implementations/fwd_5.py +++ b/notebooks/fwd_implementations/fwd_5.py @@ -8,13 +8,13 @@ CONST006 = 6.21867148191637 CONST007 = 1.60565407233314 CONST008 = 8.49632273398321 -CONST009 = 5.20291384706685 -CONST010 = 11.6340690431164 -CONST011 = 12.8452325786651 -CONST012 = 12.4373429638327 -CONST013 = 12.8452325786651 +CONST009 = 11.6340690431164 +CONST010 = 12.8452325786651 +CONST011 = 12.4373429638327 +CONST012 = 12.8452325786651 +CONST013 = 13.8744369255116 CONST014 = 16.9926454679664 -CONST015 = 13.8744369255116 +CONST015 = 5.20291384706685 CONST016 = 29.4321253055229 CONST017 = 33.9852909359329 CONST018 = 7.35803132638072 @@ -27,37 +27,49 @@ CONST025 = -19.2678488679977 CONST026 = -16.9926454679664 CONST027 = -16.9926454679664 -CONST028 = -16.5831239517770 -CONST029 = -13.8744369255116 +CONST028 = -13.8744369255116 +CONST029 = -16.5831239517770 CONST030 = 3.46860923137790 CONST031 = -8.49632273398321 CONST032 = -5.20291384706685 CONST033 = -3.46860923137790 CONST034 = -1.73430461568895 -VAR00 = y**3 -VAR01 = x**4 -VAR02 = z**4 -VAR03 = y**2 -VAR04 = y**5 -VAR05 = x**3 -VAR06 = z**3 -VAR07 = y**4 -VAR08 = z -VAR09 = x -VAR10 = x**5 -VAR11 = z**5 -VAR12 = x**2 -VAR13 = z**2 -VAR14 = y +VAR00 = x**10 +VAR01 = x**9 +VAR02 = x**8 +VAR03 = x**7 +VAR04 = x**6 +VAR05 = x**5 +VAR06 = x**4 +VAR07 = x**3 +VAR08 = x**2 +VAR09 = y**10 +VAR10 = y**9 +VAR11 = y**8 +VAR12 = y**7 +VAR13 = y**6 +VAR14 = y**5 +VAR15 = y**4 +VAR16 = y**3 +VAR17 = y**2 +VAR18 = z**10 +VAR19 = z**9 +VAR20 = z**8 +VAR21 = z**7 +VAR22 = z**6 +VAR23 = z**5 +VAR24 = z**4 +VAR25 = z**3 +VAR26 = z**2 # -------------------- kernel implementations -Y00 = CONST001*VAR10 + CONST010*VAR09*VAR13**2 + CONST023*VAR05*VAR13 -Y01 = VAR14*(CONST022*VAR05*VAR08 - CONST022*VAR06*VAR09) -Y02 = CONST000*VAR10 + VAR05*(CONST029*VAR03 + CONST033*VAR13) + VAR09*(-CONST021*VAR03*VAR13 + CONST032*VAR13**2) -Y03 = CONST027*VAR05*VAR08*VAR14 + VAR09*(CONST017*VAR00*VAR08 + CONST026*VAR06*VAR14) -Y04 = CONST002*VAR10 + VAR05*(CONST003*VAR13 + CONST025*VAR03) + VAR09*(CONST002*VAR13**2 + CONST011*VAR03**2 + CONST024*VAR03*VAR13) -Y05 = CONST004*VAR04 + VAR00*(CONST028*VAR12 + CONST028*VAR13) + VAR14*(CONST005*VAR12**2 + CONST006*VAR13**2 + CONST012*VAR12*VAR13) -Y06 = CONST002*VAR11 + VAR06*(CONST003*VAR12 + CONST024*VAR03) + VAR08*(CONST007*VAR12**2 + CONST013*VAR03**2 + CONST024*VAR03*VAR12) -Y07 = VAR00*(CONST026*VAR12 - CONST026*VAR13) + VAR14*(-CONST031*VAR12**2 + CONST031*VAR13**2) -Y08 = CONST034*VAR11 + VAR06*(CONST015*VAR03 + CONST030*VAR12) + VAR08*(CONST021*VAR03*VAR12 - CONST032*VAR12**2) -Y09 = VAR14*(CONST018*VAR12**2 + CONST018*VAR13**2 + CONST020*VAR12*VAR13) -Y10 = CONST001*VAR11 + CONST010*VAR08*VAR12**2 + CONST023*VAR06*VAR12 +Y00 = CONST001*VAR05 + CONST009*VAR24*x + CONST023*VAR07*VAR26 +Y01 = y*(CONST022*VAR07*z - CONST022*VAR25*x) +Y02 = CONST000*VAR05 + VAR07*(CONST028*VAR17 + CONST033*VAR26) + x*(-CONST021*VAR17*VAR26 + CONST032*VAR24) +Y03 = CONST027*VAR07*y*z + x*(CONST017*VAR16*z + CONST026*VAR25*y) +Y04 = CONST002*VAR05 + VAR07*(CONST003*VAR26 + CONST025*VAR17) + x*(CONST002*VAR24 + CONST010*VAR15 + CONST024*VAR17*VAR26) +Y05 = CONST004*VAR14 + VAR16*(CONST029*VAR08 + CONST029*VAR26) + y*(CONST005*VAR06 + CONST006*VAR24 + CONST011*VAR08*VAR26) +Y06 = CONST002*VAR23 + VAR25*(CONST003*VAR08 + CONST024*VAR17) + z*(CONST007*VAR06 + CONST012*VAR15 + CONST024*VAR08*VAR17) +Y07 = VAR16*(CONST026*VAR08 - CONST026*VAR26) + y*(-CONST031*VAR06 + CONST031*VAR24) +Y08 = CONST034*VAR23 + VAR25*(CONST013*VAR17 + CONST030*VAR08) + z*(CONST021*VAR08*VAR17 - CONST032*VAR06) +Y09 = y*(CONST018*VAR06 + CONST018*VAR24 + CONST020*VAR08*VAR26) +Y10 = CONST001*VAR23 + CONST009*VAR06*z + CONST023*VAR08*VAR25 diff --git a/notebooks/fwd_implementations/fwd_6.py b/notebooks/fwd_implementations/fwd_6.py index 0e09e4c..4aa8b02 100644 --- a/notebooks/fwd_implementations/fwd_6.py +++ b/notebooks/fwd_implementations/fwd_6.py @@ -1,23 +1,23 @@ # -------------------- variable and constant definitions CONST000 = 1.63279380970164 -CONST001 = 3.26558761940328 +CONST001 = 2.42182459624970 CONST002 = 3.26558761940328 -CONST003 = 3.60555127546399 +CONST003 = 3.26558761940328 CONST004 = 6.53117523880657 CONST005 = 7.15454401062709 CONST006 = 8.38944649544891 CONST007 = 9.79676285820985 CONST008 = 10.3266947761614 -CONST009 = -1.78863600265677 -CONST010 = 8.94318001328386 -CONST011 = 2.42182459624970 -CONST012 = 14.5309475774982 +CONST009 = 3.60555127546399 +CONST010 = -1.78863600265677 +CONST011 = 14.5309475774982 +CONST012 = 8.94318001328386 CONST013 = 16.5227116418583 CONST014 = 16.5227116418583 CONST015 = 17.8863600265677 -CONST016 = 20.6533895523229 -CONST017 = 20.2812259244849 -CONST018 = 19.5935257164197 +CONST016 = 19.5935257164197 +CONST017 = 20.6533895523229 +CONST018 = 20.2812259244849 CONST019 = -107.318160159406 CONST020 = 17.8863600265677 CONST021 = 26.1247009552263 @@ -38,8 +38,8 @@ CONST036 = -41.3067791046458 CONST037 = -36.3273689437454 CONST038 = -29.3902885746295 -CONST039 = -26.1247009552263 -CONST040 = -27.0416345659799 +CONST039 = -27.0416345659799 +CONST040 = -26.1247009552263 CONST041 = -26.1247009552263 CONST042 = -19.5935257164197 CONST043 = -2.42182459624970 @@ -47,35 +47,44 @@ CONST045 = -7.15454401062709 CONST046 = -3.38020432074749 CONST047 = -1.12673477358250 -VAR00 = y**6 -VAR01 = y**3 -VAR02 = x**4 -VAR03 = z**4 -VAR04 = y**2 -VAR05 = y**5 -VAR06 = x**3 -VAR07 = z**3 -VAR08 = x**6 -VAR09 = z**6 -VAR10 = y**4 -VAR11 = z -VAR12 = x -VAR13 = x**5 -VAR14 = z**5 -VAR15 = x**2 -VAR16 = z**2 -VAR17 = y +VAR00 = x**10 +VAR01 = x**9 +VAR02 = x**8 +VAR03 = x**7 +VAR04 = x**6 +VAR05 = x**5 +VAR06 = x**4 +VAR07 = x**3 +VAR08 = x**2 +VAR09 = y**10 +VAR10 = y**9 +VAR11 = y**8 +VAR12 = y**7 +VAR13 = y**6 +VAR14 = y**5 +VAR15 = y**4 +VAR16 = y**3 +VAR17 = y**2 +VAR18 = z**10 +VAR19 = z**9 +VAR20 = z**8 +VAR21 = z**7 +VAR22 = z**6 +VAR23 = z**5 +VAR24 = z**4 +VAR25 = z**3 +VAR26 = z**2 # -------------------- kernel implementations -Y00 = CONST012*VAR11*VAR13 + CONST012*VAR12*VAR14 + CONST035*VAR06*VAR07 -Y01 = VAR17*(CONST006*VAR13 + CONST025*VAR12*VAR16**2 + CONST027*VAR06*VAR16) -Y02 = -CONST045*VAR11*VAR13 + CONST045*VAR12*VAR14 + VAR04*(CONST030*VAR06*VAR11 - CONST030*VAR07*VAR12) -Y03 = VAR01*(-CONST028*VAR12*VAR16 + CONST039*VAR06) + VAR17*(CONST007*VAR13 + CONST038*VAR12*VAR16**2 + CONST042*VAR06*VAR16) -Y04 = CONST002*VAR11*VAR13 + VAR06*(CONST004*VAR07 + CONST033*VAR04*VAR11) + VAR12*(CONST001*VAR14 - CONST032*VAR04**2*VAR11 + CONST032*VAR04*VAR07) -Y05 = CONST008*VAR13*VAR17 + VAR06*(CONST016*VAR16*VAR17 + CONST036*VAR01) + VAR12*(CONST008*VAR16**2*VAR17 + CONST013*VAR05 + CONST036*VAR01*VAR16) -Y06 = CONST003*VAR04**3 + CONST017*VAR04*VAR16**2 + CONST040*VAR04**2*VAR16 + CONST047*VAR15**3 + CONST047*VAR16**3 + VAR15**2*(CONST017*VAR04 + CONST046*VAR16) + VAR15*(CONST024*VAR04*VAR16 + CONST040*VAR04**2 + CONST046*VAR16**2) -Y07 = CONST008*VAR14*VAR17 + VAR07*(CONST016*VAR15*VAR17 + CONST036*VAR01) + VAR11*(CONST008*VAR15**2*VAR17 + CONST014*VAR05 + CONST036*VAR01*VAR15) -Y08 = CONST026*VAR15**3 - CONST026*VAR16**3 + CONST039*VAR04*VAR16**2 - CONST041*VAR04**2*VAR16 + VAR15**2*(CONST026*VAR16 - CONST041*VAR04) + VAR15*(-CONST026*VAR16**2 + CONST041*VAR04**2) -Y09 = VAR01*(CONST028*VAR11*VAR15 - CONST041*VAR07) + VAR17*(CONST022*VAR11*VAR15**2 - CONST042*VAR07*VAR15 + CONST044*VAR14) -Y10 = CONST009*VAR15**3 + CONST009*VAR16**3 + CONST020*VAR04*VAR16**2 + VAR15**2*(CONST010*VAR16 + CONST015*VAR04) + VAR15*(CONST010*VAR16**2 + CONST019*VAR04*VAR16) -Y11 = VAR17*(CONST006*VAR14 + CONST025*VAR11*VAR15**2 + CONST027*VAR07*VAR15) -Y12 = -CONST037*VAR15**2*VAR16 + CONST037*VAR15*VAR16**2 + CONST043*VAR15**3 - CONST043*VAR16**3 +Y00 = CONST011*VAR05*z + CONST011*VAR23*x + CONST035*VAR07*VAR25 +Y01 = y*(CONST006*VAR05 + CONST025*VAR24*x + CONST027*VAR07*VAR26) +Y02 = -CONST045*VAR05*z + CONST045*VAR23*x + VAR17*(CONST030*VAR07*z - CONST030*VAR25*x) +Y03 = VAR16*(-CONST028*VAR26*x + CONST040*VAR07) + y*(CONST007*VAR05 + CONST038*VAR24*x + CONST042*VAR07*VAR26) +Y04 = CONST003*VAR05*z + VAR07*(CONST004*VAR25 + CONST033*VAR17*z) + x*(CONST002*VAR23 - CONST032*VAR15*z + CONST032*VAR17*VAR25) +Y05 = CONST008*VAR05*y + VAR07*(CONST017*VAR26*y + CONST036*VAR16) + x*(CONST008*VAR24*y + CONST013*VAR14 + CONST036*VAR16*VAR26) +Y06 = CONST009*VAR13 + CONST018*VAR17*VAR24 + CONST039*VAR15*VAR26 + CONST047*VAR04 + CONST047*VAR22 + VAR06*(CONST018*VAR17 + CONST046*VAR26) + VAR08*(CONST024*VAR17*VAR26 + CONST039*VAR15 + CONST046*VAR24) +Y07 = CONST008*VAR23*y + VAR25*(CONST017*VAR08*y + CONST036*VAR16) + z*(CONST008*VAR06*y + CONST014*VAR14 + CONST036*VAR08*VAR16) +Y08 = CONST026*VAR04 - CONST026*VAR22 + CONST040*VAR17*VAR24 - CONST041*VAR15*VAR26 + VAR06*(CONST026*VAR26 - CONST041*VAR17) + VAR08*(-CONST026*VAR24 + CONST041*VAR15) +Y09 = VAR16*(CONST028*VAR08*z - CONST041*VAR25) + y*(CONST022*VAR06*z - CONST042*VAR08*VAR25 + CONST044*VAR23) +Y10 = CONST010*VAR04 + CONST010*VAR22 + CONST020*VAR17*VAR24 + VAR06*(CONST012*VAR26 + CONST015*VAR17) + VAR08*(CONST012*VAR24 + CONST019*VAR17*VAR26) +Y11 = y*(CONST006*VAR23 + CONST025*VAR06*z + CONST027*VAR08*VAR25) +Y12 = -CONST037*VAR06*VAR26 + CONST037*VAR08*VAR24 + CONST043*VAR04 - CONST043*VAR22 diff --git a/notebooks/fwd_implementations/fwd_7.py b/notebooks/fwd_implementations/fwd_7.py index a0b2766..257071b 100644 --- a/notebooks/fwd_implementations/fwd_7.py +++ b/notebooks/fwd_implementations/fwd_7.py @@ -12,9 +12,9 @@ CONST010 = 16.5555704843566 CONST011 = 17.5477863187212 CONST012 = 20.4939015319192 -CONST013 = 22.0740939791422 -CONST014 = 23.5310632462709 -CONST015 = 20.4939015319192 +CONST013 = 20.4939015319192 +CONST014 = 22.0740939791422 +CONST015 = 23.5310632462709 CONST016 = 33.2779487799353 CONST017 = 36.7901566319036 CONST018 = 37.6497011940334 @@ -80,40 +80,46 @@ CONST078 = -4.80325817154356 CONST079 = -2.50682661696018 CONST080 = -1.60108605718119 -VAR00 = z**4 -VAR01 = x**7 -VAR02 = z**6 -VAR03 = x**2 -VAR04 = y -VAR05 = y**6 +VAR00 = x**10 +VAR01 = x**9 +VAR02 = x**8 +VAR03 = x**7 +VAR04 = x**6 +VAR05 = x**5 VAR06 = x**4 -VAR07 = z**3 -VAR08 = x**6 -VAR09 = z -VAR10 = x -VAR11 = z**5 -VAR12 = y**3 -VAR13 = y**5 -VAR14 = x**3 -VAR15 = y**7 -VAR16 = x**5 -VAR17 = z**7 -VAR18 = y**2 -VAR19 = y**4 -VAR20 = z**2 +VAR07 = x**3 +VAR08 = x**2 +VAR09 = y**10 +VAR10 = y**9 +VAR11 = y**8 +VAR12 = y**7 +VAR13 = y**6 +VAR14 = y**5 +VAR15 = y**4 +VAR16 = y**3 +VAR17 = y**2 +VAR18 = z**10 +VAR19 = z**9 +VAR20 = z**8 +VAR21 = z**7 +VAR22 = z**6 +VAR23 = z**5 +VAR24 = z**4 +VAR25 = z**3 +VAR26 = z**2 # -------------------- kernel implementations -Y00 = CONST059*VAR14*VAR20**2 - CONST063*VAR16*VAR20 - CONST073*VAR10*VAR20**3 + CONST079*VAR01 -Y01 = VAR04*(CONST029*VAR09*VAR16 + CONST030*VAR10*VAR11 + CONST048*VAR07*VAR14) -Y02 = CONST050*VAR01 + VAR10*(CONST038*VAR18*VAR20**2 + CONST076*VAR20**3) + VAR14*(CONST045*VAR18*VAR20 - CONST076*VAR20**2) + VAR16*(CONST010*VAR20 + CONST013*VAR18) -Y03 = VAR04*(-CONST064*VAR09*VAR16 + CONST064*VAR10*VAR11) + VAR12*(CONST041*VAR07*VAR10 + CONST053*VAR09*VAR14) -Y04 = CONST042*VAR01 + VAR10*(-CONST023*VAR20**3 - CONST055*VAR18**2*VAR20 + CONST058*VAR18*VAR20**2) + VAR14*(CONST061*VAR18*VAR20 + CONST065*VAR18**2 - CONST068*VAR20**2) + VAR16*(-CONST042*VAR20 - CONST070*VAR18) -Y05 = CONST014*VAR04*VAR09*VAR16 + VAR10*(CONST014*VAR04*VAR11 + CONST033*VAR09*VAR13 + CONST056*VAR07*VAR12) + VAR14*(CONST025*VAR04*VAR07 + CONST057*VAR09*VAR12) -Y06 = CONST047*VAR01 + VAR10*(CONST012*VAR18**3 + CONST019*VAR18*VAR20**2 + CONST060*VAR18**2*VAR20 + CONST080*VAR20**3) + VAR14*(CONST052*VAR20**2 + CONST060*VAR18**2 - CONST060*VAR18*VAR20) + VAR16*(CONST020*VAR18 + CONST078*VAR20) -Y07 = CONST002*VAR15 + VAR04*(CONST071*VAR03**2*VAR20 + CONST072*VAR03*VAR20**2 + CONST077*VAR03**3 + CONST077*VAR20**3) + VAR12*(CONST026*VAR03**2 + CONST026*VAR20**2 + CONST037*VAR03*VAR20) + VAR13*(CONST066*VAR03 + CONST067*VAR20) -Y08 = CONST047*VAR17 + VAR07*(CONST052*VAR03**2 - CONST060*VAR03*VAR18 + CONST060*VAR18**2) + VAR09*(CONST015*VAR18**3 + CONST021*VAR03**2*VAR18 + CONST047*VAR03**3 + CONST060*VAR03*VAR18**2) + VAR11*(CONST020*VAR18 + CONST052*VAR03) -Y09 = VAR04*(CONST008*VAR03*VAR20**2 + CONST074*VAR03**3 + CONST074*VAR03**2*VAR20 - CONST074*VAR20**3) + VAR12*(-CONST062*VAR03**2 + CONST062*VAR20**2) + VAR13*(CONST069*VAR03 - CONST069*VAR20) -Y10 = -CONST042*VAR17 + VAR07*(CONST032*VAR03*VAR18 - CONST065*VAR18**2 + CONST068*VAR03**2) + VAR09*(CONST023*VAR03**3 + CONST055*VAR03*VAR18**2 - CONST058*VAR03**2*VAR18) + VAR11*(CONST044*VAR03 + CONST070*VAR18) -Y11 = VAR04*(CONST028*VAR03**2*VAR20 + CONST028*VAR03*VAR20**2 + CONST075*VAR03**3 + CONST075*VAR20**3) + VAR12*(CONST017*VAR03**2 + CONST017*VAR20**2 + CONST046*VAR03*VAR20) -Y12 = CONST051*VAR17 + VAR07*(CONST045*VAR03*VAR18 - CONST049*VAR03**2) + VAR09*(CONST038*VAR03**2*VAR18 + CONST049*VAR03**3) + VAR11*(CONST010*VAR03 + CONST013*VAR18) -Y13 = VAR04*(CONST043*VAR03**3 - CONST043*VAR20**3 - CONST054*VAR03**2*VAR20 + CONST054*VAR03*VAR20**2) -Y14 = -CONST059*VAR03**2*VAR07 + CONST063*VAR03*VAR11 + CONST073*VAR03**3*VAR09 - CONST079*VAR17 +Y00 = CONST059*VAR07*VAR24 - CONST063*VAR05*VAR26 - CONST073*VAR22*x + CONST079*VAR03 +Y01 = y*(CONST029*VAR23*x + CONST030*VAR05*z + CONST048*VAR07*VAR25) +Y02 = CONST050*VAR03 + VAR05*(CONST010*VAR26 + CONST014*VAR17) + VAR07*(CONST045*VAR17*VAR26 - CONST076*VAR24) + x*(CONST038*VAR17*VAR24 + CONST076*VAR22) +Y03 = VAR16*(CONST041*VAR25*x + CONST053*VAR07*z) + y*(-CONST064*VAR05*z + CONST064*VAR23*x) +Y04 = CONST042*VAR03 + VAR05*(-CONST042*VAR26 - CONST070*VAR17) + VAR07*(CONST061*VAR17*VAR26 + CONST065*VAR15 - CONST068*VAR24) + x*(-CONST023*VAR22 - CONST055*VAR15*VAR26 + CONST058*VAR17*VAR24) +Y05 = CONST015*VAR05*y*z + VAR07*(CONST025*VAR25*y + CONST057*VAR16*z) + x*(CONST015*VAR23*y + CONST033*VAR14*z + CONST056*VAR16*VAR25) +Y06 = CONST047*VAR03 + VAR05*(CONST020*VAR17 + CONST078*VAR26) + VAR07*(CONST052*VAR24 + CONST060*VAR15 - CONST060*VAR17*VAR26) + x*(CONST012*VAR13 + CONST019*VAR17*VAR24 + CONST060*VAR15*VAR26 + CONST080*VAR22) +Y07 = CONST002*VAR12 + VAR14*(CONST066*VAR08 + CONST067*VAR26) + VAR16*(CONST026*VAR06 + CONST026*VAR24 + CONST037*VAR08*VAR26) + y*(CONST071*VAR06*VAR26 + CONST072*VAR08*VAR24 + CONST077*VAR04 + CONST077*VAR22) +Y08 = CONST047*VAR21 + VAR23*(CONST020*VAR17 + CONST052*VAR08) + VAR25*(CONST052*VAR06 - CONST060*VAR08*VAR17 + CONST060*VAR15) + z*(CONST013*VAR13 + CONST021*VAR06*VAR17 + CONST047*VAR04 + CONST060*VAR08*VAR15) +Y09 = VAR14*(CONST069*VAR08 - CONST069*VAR26) + VAR16*(-CONST062*VAR06 + CONST062*VAR24) + y*(CONST008*VAR08*VAR24 + CONST074*VAR04 + CONST074*VAR06*VAR26 - CONST074*VAR22) +Y10 = -CONST042*VAR21 + VAR23*(CONST044*VAR08 + CONST070*VAR17) + VAR25*(CONST032*VAR08*VAR17 - CONST065*VAR15 + CONST068*VAR06) + z*(CONST023*VAR04 + CONST055*VAR08*VAR15 - CONST058*VAR06*VAR17) +Y11 = VAR16*(CONST017*VAR06 + CONST017*VAR24 + CONST046*VAR08*VAR26) + y*(CONST028*VAR06*VAR26 + CONST028*VAR08*VAR24 + CONST075*VAR04 + CONST075*VAR22) +Y12 = CONST051*VAR21 + VAR23*(CONST010*VAR08 + CONST014*VAR17) + VAR25*(CONST045*VAR08*VAR17 - CONST049*VAR06) + z*(CONST038*VAR06*VAR17 + CONST049*VAR04) +Y13 = y*(CONST043*VAR04 - CONST043*VAR22 - CONST054*VAR06*VAR26 + CONST054*VAR08*VAR24) +Y14 = -CONST059*VAR06*VAR25 + CONST063*VAR08*VAR23 + CONST073*VAR04*z - CONST079*VAR21 diff --git a/notebooks/fwd_implementations/fwd_8.py b/notebooks/fwd_implementations/fwd_8.py index de6adbc..ff11a70 100644 --- a/notebooks/fwd_implementations/fwd_8.py +++ b/notebooks/fwd_implementations/fwd_8.py @@ -49,8 +49,8 @@ CONST047 = -12.2296147348353 CONST048 = 203.513090795162 CONST049 = 210.187416369296 -CONST050 = 217.054129463568 -CONST051 = 216.463045344927 +CONST050 = 216.463045344927 +CONST051 = 217.054129463568 CONST052 = 216.463045344927 CONST053 = -6.78376969317208 CONST054 = -271.350787726883 @@ -103,45 +103,48 @@ CONST101 = -13.1367135230810 CONST102 = -3.23403530824881 CONST103 = -1.61701765412441 -VAR00 = z**4 -VAR01 = x**7 -VAR02 = z**6 -VAR03 = x**2 -VAR04 = z**8 -VAR05 = y -VAR06 = y**6 -VAR07 = x**4 -VAR08 = y**8 -VAR09 = z**3 -VAR10 = x**6 -VAR11 = z -VAR12 = x -VAR13 = z**5 -VAR14 = x**8 -VAR15 = y**3 -VAR16 = y**5 -VAR17 = x**3 -VAR18 = y**7 -VAR19 = x**5 -VAR20 = z**7 -VAR21 = y**2 -VAR22 = y**4 -VAR23 = z**2 +VAR00 = x**10 +VAR01 = x**9 +VAR02 = x**8 +VAR03 = x**7 +VAR04 = x**6 +VAR05 = x**5 +VAR06 = x**4 +VAR07 = x**3 +VAR08 = x**2 +VAR09 = y**10 +VAR10 = y**9 +VAR11 = y**8 +VAR12 = y**7 +VAR13 = y**6 +VAR14 = y**5 +VAR15 = y**4 +VAR16 = y**3 +VAR17 = y**2 +VAR18 = z**10 +VAR19 = z**9 +VAR20 = z**8 +VAR21 = z**7 +VAR22 = z**6 +VAR23 = z**5 +VAR24 = z**4 +VAR25 = z**3 +VAR26 = z**2 # -------------------- kernel implementations -Y00 = -CONST066*VAR09*VAR19 + CONST066*VAR13*VAR17 + CONST089*VAR01*VAR11 - CONST089*VAR12*VAR20 -Y01 = VAR05*(CONST040*VAR17*VAR23**2 + CONST050*VAR19*VAR23 - CONST074*VAR12*VAR23**3 + CONST095*VAR01) -Y02 = CONST097*VAR01*VAR11 + VAR12*(CONST042*VAR13*VAR21 + CONST094*VAR20) + VAR17*(-CONST088*VAR13 + CONST090*VAR09*VAR21) + VAR19*(CONST042*VAR11*VAR21 - CONST088*VAR09) -Y03 = VAR05*(CONST035*VAR19*VAR23 + CONST077*VAR12*VAR23**3 - CONST078*VAR17*VAR23**2 + CONST093*VAR01) + VAR15*(CONST014*VAR17*VAR23 + CONST019*VAR19 + CONST055*VAR12*VAR23**2) -Y04 = CONST099*VAR01*VAR11 + VAR12*(-CONST053*VAR20 - CONST054*VAR09*VAR21**2 + CONST064*VAR13*VAR21) + VAR17*(-CONST053*VAR13 + CONST054*VAR11*VAR21**2) + VAR19*(-CONST064*VAR11*VAR21 + CONST099*VAR09) -Y05 = VAR05*(CONST011*VAR19*VAR23 + CONST024*VAR17*VAR23**2 - CONST084*VAR12*VAR23**3 + CONST092*VAR01) + VAR15*(CONST057*VAR12*VAR23**2 + CONST063*VAR17*VAR23 - CONST072*VAR19) + VAR16*(-CONST062*VAR12*VAR23 + CONST075*VAR17) -Y06 = CONST102*VAR01*VAR11 + VAR12*(CONST029*VAR13*VAR21 + CONST031*VAR11*VAR21**3 + CONST058*VAR09*VAR21**2 + CONST102*VAR20) + VAR17*(CONST046*VAR09*VAR21 + CONST058*VAR11*VAR21**2 + CONST096*VAR13) + VAR19*(CONST029*VAR11*VAR21 + CONST096*VAR09) -Y07 = CONST098*VAR01*VAR05 + VAR12*(CONST015*VAR18 + CONST067*VAR16*VAR23 - CONST070*VAR15*VAR23**2 + CONST098*VAR05*VAR23**3) + VAR17*(CONST051*VAR15*VAR23 + CONST067*VAR16 + CONST083*VAR05*VAR23**2) + VAR19*(CONST033*VAR15 + CONST083*VAR05*VAR23) -Y08 = CONST000*VAR03**4 + CONST000*VAR23**4 + CONST003*VAR21**4 - CONST070*VAR21**2*VAR23**2 + CONST080*VAR21**3*VAR23 + CONST087*VAR21*VAR23**3 + VAR03**3*(CONST004*VAR23 + CONST086*VAR21) + VAR03**2*(CONST006*VAR23**2 - CONST070*VAR21**2 + CONST071*VAR21*VAR23) + VAR03*(CONST004*VAR23**3 + CONST051*VAR21**2*VAR23 + CONST070*VAR21*VAR23**2 + CONST079*VAR21**3) -Y09 = CONST098*VAR05*VAR20 + VAR09*(CONST052*VAR03*VAR15 + CONST067*VAR16 + CONST083*VAR03**2*VAR05) + VAR11*(CONST017*VAR18 + CONST033*VAR03**2*VAR15 + CONST067*VAR03*VAR16 + CONST100*VAR03**3*VAR05) + VAR13*(CONST033*VAR15 + CONST083*VAR03*VAR05) -Y10 = CONST073*VAR03*VAR23**3 - CONST102*VAR03**3*VAR23 - CONST103*VAR03**4 + CONST103*VAR23**4 + VAR21**3*(CONST021*VAR23 + CONST081*VAR03) + VAR21**2*(-CONST068*VAR03**2 + CONST068*VAR23**2) + VAR21*(CONST020*VAR03*VAR23**2 + CONST020*VAR23**3 + CONST082*VAR03**3 + CONST082*VAR03**2*VAR23) -Y11 = VAR05*(CONST012*VAR20 + CONST076*VAR03**2*VAR09 + CONST084*VAR03**3*VAR11 + CONST101*VAR03*VAR13) + VAR15*(-CONST057*VAR03**2*VAR11 - CONST063*VAR03*VAR09 + CONST072*VAR13) + VAR16*(CONST062*VAR03*VAR11 - CONST075*VAR09) -Y12 = CONST007*VAR03**4 + CONST007*VAR23**4 + CONST030*VAR03**3*VAR23 + CONST053*VAR03*VAR23**3 + CONST091*VAR03**2*VAR23**2 + VAR21**2*(CONST025*VAR03**2 + CONST025*VAR23**2 + CONST032*VAR03*VAR23) + VAR21*(CONST048*VAR03**2*VAR23 + CONST048*VAR03*VAR23**2 + CONST085*VAR03**3 + CONST085*VAR23**3) -Y13 = VAR05*(CONST036*VAR03*VAR13 + CONST047*VAR20 - CONST077*VAR03**2*VAR09 + CONST078*VAR03**3*VAR11) + VAR15*(CONST014*VAR03*VAR09 + CONST019*VAR13 + CONST056*VAR03**2*VAR11) -Y14 = CONST008*VAR03**4 + CONST041*VAR23**4 + CONST088*VAR03**3*VAR23 - CONST088*VAR03*VAR23**3 + VAR21*(-CONST037*VAR03**2*VAR23 + CONST037*VAR03*VAR23**2 + CONST088*VAR03**3 - CONST088*VAR23**3) -Y15 = VAR05*(-CONST040*VAR03**2*VAR09 + CONST061*VAR03*VAR13 + CONST074*VAR03**3*VAR11 - CONST095*VAR20) -Y16 = CONST010*VAR03**4 + CONST010*VAR23**4 + CONST045*VAR03**2*VAR23**2 + CONST074*VAR03**3*VAR23 + CONST074*VAR03*VAR23**3 +Y00 = -CONST066*VAR05*VAR25 + CONST066*VAR07*VAR23 + CONST089*VAR03*z - CONST089*VAR21*x +Y01 = y*(CONST040*VAR07*VAR24 + CONST051*VAR05*VAR26 - CONST074*VAR22*x + CONST095*VAR03) +Y02 = CONST097*VAR03*z + VAR05*(CONST042*VAR17*z - CONST088*VAR25) + VAR07*(-CONST088*VAR23 + CONST090*VAR17*VAR25) + x*(CONST042*VAR17*VAR23 + CONST094*VAR21) +Y03 = VAR16*(CONST014*VAR07*VAR26 + CONST019*VAR05 + CONST055*VAR24*x) + y*(CONST035*VAR05*VAR26 + CONST077*VAR22*x - CONST078*VAR07*VAR24 + CONST093*VAR03) +Y04 = CONST099*VAR03*z + VAR05*(-CONST064*VAR17*z + CONST099*VAR25) + VAR07*(-CONST053*VAR23 + CONST054*VAR15*z) + x*(-CONST053*VAR21 - CONST054*VAR15*VAR25 + CONST064*VAR17*VAR23) +Y05 = VAR14*(-CONST062*VAR26*x + CONST075*VAR07) + VAR16*(CONST057*VAR24*x + CONST063*VAR07*VAR26 - CONST072*VAR05) + y*(CONST011*VAR05*VAR26 + CONST024*VAR07*VAR24 - CONST084*VAR22*x + CONST092*VAR03) +Y06 = CONST102*VAR03*z + VAR05*(CONST029*VAR17*z + CONST096*VAR25) + VAR07*(CONST046*VAR17*VAR25 + CONST058*VAR15*z + CONST096*VAR23) + x*(CONST029*VAR17*VAR23 + CONST031*VAR13*z + CONST058*VAR15*VAR25 + CONST102*VAR21) +Y07 = CONST098*VAR03*y + VAR05*(CONST033*VAR16 + CONST083*VAR26*y) + VAR07*(CONST050*VAR16*VAR26 + CONST067*VAR14 + CONST083*VAR24*y) + x*(CONST015*VAR12 + CONST067*VAR14*VAR26 - CONST070*VAR16*VAR24 + CONST098*VAR22*y) +Y08 = CONST000*VAR02 + CONST000*VAR20 + CONST003*VAR11 - CONST070*VAR15*VAR24 + CONST080*VAR13*VAR26 + CONST087*VAR17*VAR22 + VAR04*(CONST004*VAR26 + CONST086*VAR17) + VAR06*(CONST006*VAR24 - CONST070*VAR15 + CONST071*VAR17*VAR26) + VAR08*(CONST004*VAR22 + CONST050*VAR15*VAR26 + CONST070*VAR17*VAR24 + CONST079*VAR13) +Y09 = CONST098*VAR21*y + VAR23*(CONST033*VAR16 + CONST083*VAR08*y) + VAR25*(CONST052*VAR08*VAR16 + CONST067*VAR14 + CONST083*VAR06*y) + z*(CONST017*VAR12 + CONST033*VAR06*VAR16 + CONST067*VAR08*VAR14 + CONST100*VAR04*y) +Y10 = CONST073*VAR08*VAR22 - CONST102*VAR04*VAR26 - CONST103*VAR02 + CONST103*VAR20 + VAR13*(CONST021*VAR26 + CONST081*VAR08) + VAR15*(-CONST068*VAR06 + CONST068*VAR24) + VAR17*(CONST020*VAR08*VAR24 + CONST020*VAR22 + CONST082*VAR04 + CONST082*VAR06*VAR26) +Y11 = VAR14*(CONST062*VAR08*z - CONST075*VAR25) + VAR16*(-CONST057*VAR06*z - CONST063*VAR08*VAR25 + CONST072*VAR23) + y*(CONST012*VAR21 + CONST076*VAR06*VAR25 + CONST084*VAR04*z + CONST101*VAR08*VAR23) +Y12 = CONST007*VAR02 + CONST007*VAR20 + CONST030*VAR04*VAR26 + CONST053*VAR08*VAR22 + CONST091*VAR06*VAR24 + VAR15*(CONST025*VAR06 + CONST025*VAR24 + CONST032*VAR08*VAR26) + VAR17*(CONST048*VAR06*VAR26 + CONST048*VAR08*VAR24 + CONST085*VAR04 + CONST085*VAR22) +Y13 = VAR16*(CONST014*VAR08*VAR25 + CONST019*VAR23 + CONST056*VAR06*z) + y*(CONST036*VAR08*VAR23 + CONST047*VAR21 - CONST077*VAR06*VAR25 + CONST078*VAR04*z) +Y14 = CONST008*VAR02 + CONST041*VAR20 + CONST088*VAR04*VAR26 - CONST088*VAR08*VAR22 + VAR17*(-CONST037*VAR06*VAR26 + CONST037*VAR08*VAR24 + CONST088*VAR04 - CONST088*VAR22) +Y15 = y*(-CONST040*VAR06*VAR25 + CONST061*VAR08*VAR23 + CONST074*VAR04*z - CONST095*VAR21) +Y16 = CONST010*VAR02 + CONST010*VAR20 + CONST045*VAR06*VAR24 + CONST074*VAR04*VAR26 + CONST074*VAR08*VAR22 diff --git a/notebooks/fwd_implementations/fwd_9.py b/notebooks/fwd_implementations/fwd_9.py index 0d0fc4c..3d2bc05 100644 --- a/notebooks/fwd_implementations/fwd_9.py +++ b/notebooks/fwd_implementations/fwd_9.py @@ -12,8 +12,8 @@ CONST010 = 10.7269778688696 CONST011 = 10.7269778688696 CONST012 = 6.39633378878088 -CONST013 = 13.0937127087774 -CONST014 = 15.0007324039945 +CONST013 = 15.0007324039945 +CONST014 = 13.0937127087774 CONST015 = 9.82028453158308 CONST016 = 14.4550674370400 CONST017 = 14.4550674370400 @@ -37,8 +37,8 @@ CONST035 = 58.9217071894985 CONST036 = 58.9217071894985 CONST037 = 62.4530292249704 -CONST038 = 64.3618672132178 -CONST039 = 1081.71819704233 +CONST038 = 1081.71819704233 +CONST039 = 64.3618672132178 CONST040 = 578.202697481601 CONST041 = 68.5747767039748 CONST042 = 589.217071894985 @@ -145,50 +145,50 @@ CONST143 = -9.82028453158308 CONST144 = -4.91014226579154 CONST145 = 511.706703102471 -VAR00 = z**4 -VAR01 = x**7 -VAR02 = z**6 -VAR03 = x**9 -VAR04 = x**2 -VAR05 = z**8 -VAR06 = y -VAR07 = y**6 -VAR08 = x**4 -VAR09 = y**8 -VAR10 = z**3 -VAR11 = x**6 -VAR12 = z -VAR13 = x -VAR14 = z**5 -VAR15 = x**8 +VAR00 = x**10 +VAR01 = x**9 +VAR02 = x**8 +VAR03 = x**7 +VAR04 = x**6 +VAR05 = x**5 +VAR06 = x**4 +VAR07 = x**3 +VAR08 = x**2 +VAR09 = y**10 +VAR10 = y**9 +VAR11 = y**8 +VAR12 = y**7 +VAR13 = y**6 +VAR14 = y**5 +VAR15 = y**4 VAR16 = y**3 -VAR17 = y**5 -VAR18 = x**3 -VAR19 = y**7 -VAR20 = x**5 -VAR21 = y**9 -VAR22 = z**7 -VAR23 = y**2 -VAR24 = z**9 -VAR25 = y**4 +VAR17 = y**2 +VAR18 = z**10 +VAR19 = z**9 +VAR20 = z**8 +VAR21 = z**7 +VAR22 = z**6 +VAR23 = z**5 +VAR24 = z**4 +VAR25 = z**3 VAR26 = z**2 # -------------------- kernel implementations -Y00 = CONST001*VAR18**3 + CONST020*VAR13*VAR26**4 + CONST078*VAR18*VAR26**3 + CONST091*VAR20*VAR26**2 + CONST105*VAR01*VAR26 -Y01 = VAR06*(-CONST099*VAR10*VAR20 + CONST099*VAR14*VAR18 + CONST106*VAR01*VAR12 - CONST106*VAR13*VAR22) -Y02 = CONST000*VAR18**3 + VAR01*(CONST129*VAR26 + CONST130*VAR23) + VAR13*(-CONST080*VAR23*VAR26**3 + CONST139*VAR26**4) + VAR18*(CONST120*VAR23*VAR26**2 - CONST124*VAR26**3) + VAR20*(CONST021*VAR26**2 - CONST097*VAR23*VAR26) -Y03 = VAR06*(-CONST089*VAR10*VAR20 - CONST089*VAR14*VAR18 + CONST109*VAR01*VAR12 + CONST109*VAR13*VAR22) + VAR16*(CONST077*VAR10*VAR18 + CONST095*VAR12*VAR20 + CONST096*VAR13*VAR14) -Y04 = CONST002*VAR18**3 + CONST007*VAR13*VAR26**4 + CONST135*VAR20*VAR26**2 + CONST140*VAR01*VAR26 + VAR23**2*(CONST032*VAR18*VAR26 + CONST047*VAR20 + CONST131*VAR13*VAR26**2) + VAR23*(CONST071*VAR13*VAR26**3 - CONST071*VAR18*VAR26**2 + CONST111*VAR20*VAR26 + CONST127*VAR01) -Y05 = VAR06*(CONST034*VAR14*VAR18 + CONST121*VAR10*VAR20 - CONST121*VAR13*VAR22 + CONST122*VAR01*VAR12) + VAR16*(CONST030*VAR13*VAR14 + CONST125*VAR12*VAR20) + VAR17*(-CONST030*VAR10*VAR13 + CONST030*VAR12*VAR18) -Y06 = CONST119*VAR01*VAR23 - CONST137*VAR18**3 + VAR13*(-CONST062*VAR23**3*VAR26 - CONST092*VAR23*VAR26**3 + CONST112*VAR23**2*VAR26**2 + CONST144*VAR26**4) + VAR18*(CONST051*VAR23**2*VAR26 - CONST065*VAR23*VAR26**2 + CONST103*VAR23**3 + CONST141*VAR26**3) + VAR20*(CONST035*VAR23*VAR26 - CONST086*VAR23**2 + CONST143*VAR26**2) -Y07 = CONST132*VAR01*VAR06*VAR12 + VAR13*(CONST025*VAR10*VAR17 + CONST053*VAR12*VAR19 + CONST081*VAR14*VAR16 + CONST132*VAR06*VAR22) + VAR18*(CONST026*VAR12*VAR17 + CONST044*VAR10*VAR16 + CONST107*VAR06*VAR14) + VAR20*(CONST081*VAR12*VAR16 + CONST107*VAR06*VAR10) -Y08 = CONST004*VAR18**3 + VAR01*(CONST006*VAR26 + CONST116*VAR23) + VAR13*(CONST004*VAR26**4 + CONST022*VAR23**4 + CONST069*VAR23**2*VAR26**2 + CONST082*VAR23**3*VAR26 + CONST116*VAR23*VAR26**3) + VAR18*(CONST005*VAR26**3 + CONST083*VAR23**3 + CONST087*VAR23*VAR26**2 + CONST145*VAR23**2*VAR26) + VAR20*(CONST008*VAR26**2 + CONST069*VAR23**2 + CONST087*VAR23*VAR26) -Y09 = CONST009*VAR16**3 + VAR06*(CONST010*VAR26**4 + CONST011*VAR04**4 + CONST029*VAR04**3*VAR26 + CONST029*VAR04*VAR26**3 + CONST038*VAR04**2*VAR26**2) + VAR16*(CONST056*VAR04**2*VAR26 + CONST056*VAR04*VAR26**2 + CONST101*VAR04**3 + CONST101*VAR26**3) + VAR17*(CONST063*VAR04**2 + CONST063*VAR26**2 + CONST104*VAR04*VAR26) + VAR19*(CONST110*VAR26 + CONST113*VAR04) -Y10 = CONST004*VAR10**3 + VAR10*(CONST012*VAR04**3 + CONST082*VAR23**3 + CONST087*VAR04**2*VAR23 + CONST145*VAR04*VAR23**2) + VAR12*(CONST004*VAR04**4 + CONST023*VAR23**4 + CONST070*VAR04**2*VAR23**2 + CONST084*VAR04*VAR23**3 + CONST117*VAR04**3*VAR23) + VAR14*(CONST008*VAR04**2 + CONST070*VAR23**2 + CONST088*VAR04*VAR23) + VAR22*(CONST005*VAR04 + CONST117*VAR23) -Y11 = VAR06*(CONST014*VAR04**4 + CONST024*VAR04**3*VAR26 + CONST133*VAR04*VAR26**3 + CONST138*VAR26**4) + VAR16*(CONST055*VAR04*VAR26**2 + CONST093*VAR04**3 + CONST093*VAR04**2*VAR26 - CONST093*VAR26**3) + VAR17*(CONST066*VAR04**2 + CONST072*VAR26**2) + VAR19*(CONST115*VAR04 - CONST115*VAR26) -Y12 = CONST036*VAR22*VAR23 + CONST137*VAR10**3 + VAR10*(CONST013*VAR04**3 - CONST051*VAR04*VAR23**2 + CONST065*VAR04**2*VAR23 - CONST103*VAR23**3) + VAR12*(CONST062*VAR04*VAR23**3 + CONST092*VAR04**3*VAR23 - CONST112*VAR04**2*VAR23**2 - CONST144*VAR04**4) + VAR14*(CONST086*VAR23**2 + CONST123*VAR04*VAR23 - CONST143*VAR04**2) -Y13 = VAR06*(CONST016*VAR26**4 + CONST017*VAR04**4 + CONST094*VAR04**2*VAR26**2 + CONST121*VAR04**3*VAR26 + CONST122*VAR04*VAR26**3) + VAR16*(CONST040*VAR04**2*VAR26 + CONST040*VAR04*VAR26**2 + CONST100*VAR26**3 + CONST102*VAR04**3) + VAR17*(CONST049*VAR04**2 + CONST049*VAR26**2 + CONST090*VAR04*VAR26) -Y14 = CONST007*VAR04**4*VAR12 + CONST075*VAR10**3 + CONST136*VAR04**2*VAR14 + CONST140*VAR04*VAR22 + VAR23**2*(CONST032*VAR04*VAR10 + CONST047*VAR14 + CONST131*VAR04**2*VAR12) + VAR23*(CONST068*VAR04**2*VAR10 + CONST073*VAR04**3*VAR12 + CONST114*VAR04*VAR14 + CONST128*VAR22) -Y15 = VAR06*(CONST018*VAR04**4 + CONST089*VAR04**3*VAR26 - CONST089*VAR04*VAR26**3 + CONST142*VAR26**4) + VAR16*(CONST037*VAR26**3 - CONST045*VAR04**2*VAR26 + CONST045*VAR04*VAR26**2 + CONST118*VAR04**3) -Y16 = CONST019*VAR04**4*VAR12 + CONST076*VAR10**3 + CONST124*VAR04**3*VAR10 - CONST129*VAR04*VAR22 + CONST134*VAR04**2*VAR14 + VAR23*(CONST039*VAR04**2*VAR10 + CONST080*VAR04**3*VAR12 + CONST097*VAR04*VAR14 - CONST130*VAR22) -Y17 = VAR06*(CONST058*VAR04**4 + CONST058*VAR26**4 + CONST061*VAR04**3*VAR26 + CONST061*VAR04*VAR26**3 + CONST074*VAR04**2*VAR26**2) -Y18 = CONST001*VAR10**3 + CONST020*VAR04**4*VAR12 + CONST078*VAR04**3*VAR10 + CONST091*VAR04**2*VAR14 + CONST105*VAR04*VAR22 +Y00 = CONST001*VAR01 + CONST020*VAR20*x + CONST078*VAR07*VAR22 + CONST091*VAR05*VAR24 + CONST105*VAR03*VAR26 +Y01 = y*(-CONST099*VAR05*VAR25 + CONST099*VAR07*VAR23 + CONST106*VAR03*z - CONST106*VAR21*x) +Y02 = CONST000*VAR01 + VAR03*(CONST129*VAR26 + CONST130*VAR17) + VAR05*(CONST021*VAR24 - CONST097*VAR17*VAR26) + VAR07*(CONST120*VAR17*VAR24 - CONST124*VAR22) + x*(-CONST080*VAR17*VAR22 + CONST139*VAR20) +Y03 = VAR16*(CONST077*VAR07*VAR25 + CONST095*VAR05*z + CONST096*VAR23*x) + y*(-CONST089*VAR05*VAR25 - CONST089*VAR07*VAR23 + CONST109*VAR03*z + CONST109*VAR21*x) +Y04 = CONST002*VAR01 + CONST007*VAR20*x + CONST135*VAR05*VAR24 + CONST140*VAR03*VAR26 + VAR15*(CONST032*VAR07*VAR26 + CONST047*VAR05 + CONST131*VAR24*x) + VAR17*(-CONST071*VAR07*VAR24 + CONST071*VAR22*x + CONST111*VAR05*VAR26 + CONST127*VAR03) +Y05 = VAR14*(CONST030*VAR07*z - CONST030*VAR25*x) + VAR16*(CONST030*VAR23*x + CONST125*VAR05*z) + y*(CONST034*VAR07*VAR23 + CONST121*VAR05*VAR25 - CONST121*VAR21*x + CONST122*VAR03*z) +Y06 = CONST119*VAR03*VAR17 - CONST137*VAR01 + VAR05*(CONST035*VAR17*VAR26 - CONST086*VAR15 + CONST143*VAR24) + VAR07*(CONST051*VAR15*VAR26 - CONST065*VAR17*VAR24 + CONST103*VAR13 + CONST141*VAR22) + x*(-CONST062*VAR13*VAR26 - CONST092*VAR17*VAR22 + CONST112*VAR15*VAR24 + CONST144*VAR20) +Y07 = CONST132*VAR03*y*z + VAR05*(CONST081*VAR16*z + CONST107*VAR25*y) + VAR07*(CONST026*VAR14*z + CONST044*VAR16*VAR25 + CONST107*VAR23*y) + x*(CONST025*VAR14*VAR25 + CONST053*VAR12*z + CONST081*VAR16*VAR23 + CONST132*VAR21*y) +Y08 = CONST004*VAR01 + VAR03*(CONST006*VAR26 + CONST116*VAR17) + VAR05*(CONST008*VAR24 + CONST069*VAR15 + CONST087*VAR17*VAR26) + VAR07*(CONST005*VAR22 + CONST083*VAR13 + CONST087*VAR17*VAR24 + CONST145*VAR15*VAR26) + x*(CONST004*VAR20 + CONST022*VAR11 + CONST069*VAR15*VAR24 + CONST082*VAR13*VAR26 + CONST116*VAR17*VAR22) +Y09 = CONST009*VAR10 + VAR12*(CONST110*VAR26 + CONST113*VAR08) + VAR14*(CONST063*VAR06 + CONST063*VAR24 + CONST104*VAR08*VAR26) + VAR16*(CONST056*VAR06*VAR26 + CONST056*VAR08*VAR24 + CONST101*VAR04 + CONST101*VAR22) + y*(CONST010*VAR20 + CONST011*VAR02 + CONST029*VAR04*VAR26 + CONST029*VAR08*VAR22 + CONST039*VAR06*VAR24) +Y10 = CONST004*VAR19 + VAR21*(CONST005*VAR08 + CONST117*VAR17) + VAR23*(CONST008*VAR06 + CONST070*VAR15 + CONST088*VAR08*VAR17) + VAR25*(CONST012*VAR04 + CONST082*VAR13 + CONST087*VAR06*VAR17 + CONST145*VAR08*VAR15) + z*(CONST004*VAR02 + CONST023*VAR11 + CONST070*VAR06*VAR15 + CONST084*VAR08*VAR13 + CONST117*VAR04*VAR17) +Y11 = VAR12*(CONST115*VAR08 - CONST115*VAR26) + VAR14*(CONST066*VAR06 + CONST072*VAR24) + VAR16*(CONST055*VAR08*VAR24 + CONST093*VAR04 + CONST093*VAR06*VAR26 - CONST093*VAR22) + y*(CONST013*VAR02 + CONST024*VAR04*VAR26 + CONST133*VAR08*VAR22 + CONST138*VAR20) +Y12 = CONST036*VAR17*VAR21 + CONST137*VAR19 + VAR23*(CONST086*VAR15 + CONST123*VAR08*VAR17 - CONST143*VAR06) + VAR25*(CONST014*VAR04 - CONST051*VAR08*VAR15 + CONST065*VAR06*VAR17 - CONST103*VAR13) + z*(CONST062*VAR08*VAR13 + CONST092*VAR04*VAR17 - CONST112*VAR06*VAR15 - CONST144*VAR02) +Y13 = VAR14*(CONST049*VAR06 + CONST049*VAR24 + CONST090*VAR08*VAR26) + VAR16*(CONST040*VAR06*VAR26 + CONST040*VAR08*VAR24 + CONST100*VAR22 + CONST102*VAR04) + y*(CONST016*VAR20 + CONST017*VAR02 + CONST094*VAR06*VAR24 + CONST121*VAR04*VAR26 + CONST122*VAR08*VAR22) +Y14 = CONST007*VAR02*z + CONST075*VAR19 + CONST136*VAR06*VAR23 + CONST140*VAR08*VAR21 + VAR15*(CONST032*VAR08*VAR25 + CONST047*VAR23 + CONST131*VAR06*z) + VAR17*(CONST068*VAR06*VAR25 + CONST073*VAR04*z + CONST114*VAR08*VAR23 + CONST128*VAR21) +Y15 = VAR16*(CONST037*VAR22 - CONST045*VAR06*VAR26 + CONST045*VAR08*VAR24 + CONST118*VAR04) + y*(CONST018*VAR02 + CONST089*VAR04*VAR26 - CONST089*VAR08*VAR22 + CONST142*VAR20) +Y16 = CONST019*VAR02*z + CONST076*VAR19 + CONST124*VAR04*VAR25 - CONST129*VAR08*VAR21 + CONST134*VAR06*VAR23 + VAR17*(CONST038*VAR06*VAR25 + CONST080*VAR04*z + CONST097*VAR08*VAR23 - CONST130*VAR21) +Y17 = y*(CONST058*VAR02 + CONST058*VAR20 + CONST061*VAR04*VAR26 + CONST061*VAR08*VAR22 + CONST074*VAR06*VAR24) +Y18 = CONST001*VAR19 + CONST020*VAR02*z + CONST078*VAR04*VAR25 + CONST091*VAR06*VAR23 + CONST105*VAR08*VAR21 From 998b3ccd6f6849d203a6e03e02895c7142b6269a Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Thu, 22 Aug 2024 10:36:21 -0700 Subject: [PATCH 009/116] refactor: constraining export to wrapper class for second order --- src/equitriton/sph_harm/direct/y_2.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/equitriton/sph_harm/direct/y_2.py b/src/equitriton/sph_harm/direct/y_2.py index 6504fa4..85cc8f9 100644 --- a/src/equitriton/sph_harm/direct/y_2.py +++ b/src/equitriton/sph_harm/direct/y_2.py @@ -4,6 +4,8 @@ from equitriton.utils import calculate_lastdim_num_blocks +__all__ = ["SecondOrderSphericalHarmonic"] + class SecondOrderSphericalHarmonic(torch.autograd.Function): @staticmethod From 72d7630de63713459303924e4b6c22a50b53d6cc Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Thu, 22 Aug 2024 10:40:16 -0700 Subject: [PATCH 010/116] feat: implemented fifth order --- src/equitriton/sph_harm/direct/y_5.py | 523 ++++++++++++++++++++++++++ 1 file changed, 523 insertions(+) create mode 100644 src/equitriton/sph_harm/direct/y_5.py diff --git a/src/equitriton/sph_harm/direct/y_5.py b/src/equitriton/sph_harm/direct/y_5.py new file mode 100644 index 0000000..d5f2460 --- /dev/null +++ b/src/equitriton/sph_harm/direct/y_5.py @@ -0,0 +1,523 @@ +import triton +import torch +from triton import language as tl + +from equitriton.utils import calculate_lastdim_num_blocks + +__all__ = ["FifthOrderSphericalHarmonic"] + + +class FifthOrderSphericalHarmonic(torch.autograd.Function): + @staticmethod + def forward( + ctx, + coords: torch.Tensor, + mask: torch.Tensor | None = None, + block_size: int = 64, + ): + output_tensor = torch.empty( + (*coords.shape[:-1], 11), dtype=coords.dtype, device=coords.device + ) + coord_numel = coords.numel() + output_numel = output_tensor.numel() + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + # apply the kernel + fifth_order_fwd[num_blocks,]( + coords, output_tensor, block_size, coord_numel, output_numel + ) + ctx.save_for_backward(coords) + return output_tensor + + @staticmethod + def backward( + ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 + ) -> torch.Tensor: + (coords,) = ctx.saved_tensors + coord_grad_output = torch.zeros_like(coords) + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + # call backward kernel + fifth_order_bwd[num_blocks,]( + coords, + coord_grad_output, + sph_grad_tensor, + block_size, + coords.numel(), + sph_grad_tensor.numel(), + ) + return coord_grad_output + + +def torch_fifth_order_fwd(coords: torch.Tensor) -> torch.Tensor: + """ + PyTorch implementation of the kernel. This is designed + purely for unit testing to ensure that the Triton implementation + is behaving as intended. + + Parameters + ---------- + coords : torch.Tensor + N-d tensor, where the last dimension corresponds to + xyz values. + + Returns + ------- + torch.Tensor + N-d tensor, where the last dimension corresponds to + each projection of the second order spherical harmonic. + """ + x = coords[..., 0].contiguous().unsqueeze(-1) + y = coords[..., 1].contiguous().unsqueeze(-1) + z = coords[..., 2].contiguous().unsqueeze(-1) + # -------------------- variable and constant definitions + CONST000 = 1.73430461568895 + CONST001 = 2.32681380862329 + CONST002 = 1.60565407233314 + CONST003 = 3.21130814466628 + CONST004 = 3.31662479035540 + CONST005 = 6.21867148191637 + CONST006 = 6.21867148191637 + CONST007 = 1.60565407233314 + CONST009 = 11.6340690431164 + CONST010 = 12.8452325786651 + CONST011 = 12.4373429638327 + CONST012 = 12.8452325786651 + CONST013 = 13.8744369255116 + CONST017 = 33.9852909359329 + CONST018 = 7.35803132638072 + CONST020 = -44.1481879582843 + CONST021 = -41.6233107765348 + CONST022 = -29.4321253055229 + CONST023 = -23.2681380862329 + CONST024 = -19.2678488679977 + CONST025 = -19.2678488679977 + CONST026 = -16.9926454679664 + CONST027 = -16.9926454679664 + CONST028 = -13.8744369255116 + CONST029 = -16.5831239517770 + CONST030 = 3.46860923137790 + CONST031 = -8.49632273398321 + CONST032 = -5.20291384706685 + CONST033 = -3.46860923137790 + CONST034 = -1.73430461568895 + VAR05 = x**5 + VAR06 = x**4 + VAR07 = x**3 + VAR08 = x**2 + VAR14 = y**5 + VAR15 = y**4 + VAR16 = y**3 + VAR17 = y**2 + VAR23 = z**5 + VAR24 = z**4 + VAR25 = z**3 + VAR26 = z**2 + # -------------------- kernel implementations + Y00 = CONST001 * VAR05 + CONST009 * VAR24 * x + CONST023 * VAR07 * VAR26 + Y01 = y * (CONST022 * VAR07 * z - CONST022 * VAR25 * x) + Y02 = ( + CONST000 * VAR05 + + VAR07 * (CONST028 * VAR17 + CONST033 * VAR26) + + x * (-CONST021 * VAR17 * VAR26 + CONST032 * VAR24) + ) + Y03 = CONST027 * VAR07 * y * z + x * (CONST017 * VAR16 * z + CONST026 * VAR25 * y) + Y04 = ( + CONST002 * VAR05 + + VAR07 * (CONST003 * VAR26 + CONST025 * VAR17) + + x * (CONST002 * VAR24 + CONST010 * VAR15 + CONST024 * VAR17 * VAR26) + ) + Y05 = ( + CONST004 * VAR14 + + VAR16 * (CONST029 * VAR08 + CONST029 * VAR26) + + y * (CONST005 * VAR06 + CONST006 * VAR24 + CONST011 * VAR08 * VAR26) + ) + Y06 = ( + CONST002 * VAR23 + + VAR25 * (CONST003 * VAR08 + CONST024 * VAR17) + + z * (CONST007 * VAR06 + CONST012 * VAR15 + CONST024 * VAR08 * VAR17) + ) + Y07 = VAR16 * (CONST026 * VAR08 - CONST026 * VAR26) + y * ( + -CONST031 * VAR06 + CONST031 * VAR24 + ) + Y08 = ( + CONST034 * VAR23 + + VAR25 * (CONST013 * VAR17 + CONST030 * VAR08) + + z * (CONST021 * VAR08 * VAR17 - CONST032 * VAR06) + ) + Y09 = y * (CONST018 * VAR06 + CONST018 * VAR24 + CONST020 * VAR08 * VAR26) + Y10 = CONST001 * VAR23 + CONST009 * VAR06 * z + CONST023 * VAR08 * VAR25 + # not the prettiest way to concatenate, but better than + # messing with the linter + tensors = [Y00, Y01, Y02, Y03, Y04, Y05, Y06, Y07, Y08, Y09, Y10] + return torch.cat(tensors, dim=-1) + + +@triton.jit +def fifth_order_fwd( + coord_ptr: tl.tensor, + output_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, +): + # these are hardcoded because they are predetermined; + coord_stride = 3 + # work out the row offsets + block_id = tl.program_id(0) + coord_striding = tl.arange(0, block_size) * coord_stride + # as the name suggests, this is effectively every node/atom + coord_row_offset = coord_striding + (block_size * coord_stride * block_id) + x = tl.load(coord_ptr + coord_row_offset, mask=coord_row_offset < coord_numel) + y = tl.load( + coord_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + z = tl.load( + coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + # -------------------- variable and constant definitions + CONST000 = 1.73430461568895 + CONST001 = 2.32681380862329 + CONST002 = 1.60565407233314 + CONST003 = 3.21130814466628 + CONST004 = 3.31662479035540 + CONST005 = 6.21867148191637 + CONST006 = 6.21867148191637 + CONST007 = 1.60565407233314 + CONST009 = 11.6340690431164 + CONST010 = 12.8452325786651 + CONST011 = 12.4373429638327 + CONST012 = 12.8452325786651 + CONST013 = 13.8744369255116 + CONST017 = 33.9852909359329 + CONST018 = 7.35803132638072 + CONST020 = -44.1481879582843 + CONST021 = -41.6233107765348 + CONST022 = -29.4321253055229 + CONST023 = -23.2681380862329 + CONST024 = -19.2678488679977 + CONST025 = -19.2678488679977 + CONST026 = -16.9926454679664 + CONST027 = -16.9926454679664 + CONST028 = -13.8744369255116 + CONST029 = -16.5831239517770 + CONST030 = 3.46860923137790 + CONST031 = -8.49632273398321 + CONST032 = -5.20291384706685 + CONST033 = -3.46860923137790 + CONST034 = -1.73430461568895 + VAR05 = x * x * x * x * x + VAR06 = x * x * x * x + VAR07 = x * x * x + VAR08 = x * x + VAR14 = y * y * y * y * y + VAR15 = y * y * y * y + VAR16 = y * y * y + VAR17 = y * y + VAR23 = z * z * z * z * z + VAR24 = z * z * z * z + VAR25 = z * z * z + VAR26 = z * z + # -------------------- kernel implementations + Y00 = CONST001 * VAR05 + CONST009 * VAR24 * x + CONST023 * VAR07 * VAR26 + Y01 = y * (CONST022 * VAR07 * z - CONST022 * VAR25 * x) + Y02 = ( + CONST000 * VAR05 + + VAR07 * (CONST028 * VAR17 + CONST033 * VAR26) + + x * (-CONST021 * VAR17 * VAR26 + CONST032 * VAR24) + ) + Y03 = CONST027 * VAR07 * y * z + x * (CONST017 * VAR16 * z + CONST026 * VAR25 * y) + Y04 = ( + CONST002 * VAR05 + + VAR07 * (CONST003 * VAR26 + CONST025 * VAR17) + + x * (CONST002 * VAR24 + CONST010 * VAR15 + CONST024 * VAR17 * VAR26) + ) + Y05 = ( + CONST004 * VAR14 + + VAR16 * (CONST029 * VAR08 + CONST029 * VAR26) + + y * (CONST005 * VAR06 + CONST006 * VAR24 + CONST011 * VAR08 * VAR26) + ) + Y06 = ( + CONST002 * VAR23 + + VAR25 * (CONST003 * VAR08 + CONST024 * VAR17) + + z * (CONST007 * VAR06 + CONST012 * VAR15 + CONST024 * VAR08 * VAR17) + ) + Y07 = VAR16 * (CONST026 * VAR08 - CONST026 * VAR26) + y * ( + -CONST031 * VAR06 + CONST031 * VAR24 + ) + Y08 = ( + CONST034 * VAR23 + + VAR25 * (CONST013 * VAR17 + CONST030 * VAR08) + + z * (CONST021 * VAR08 * VAR17 - CONST032 * VAR06) + ) + Y09 = y * (CONST018 * VAR06 + CONST018 * VAR24 + CONST020 * VAR08 * VAR26) + Y10 = CONST001 * VAR23 + CONST009 * VAR06 * z + CONST023 * VAR08 * VAR25 + output_stride = 11 # [2l + 1] + output_striding = tl.arange(0, block_size) * output_stride + output_row_offset = output_striding + (block_size * output_stride * block_id) + tl.store(output_ptr + output_row_offset, Y00, mask=output_row_offset < output_numel) + tl.store( + output_ptr + output_row_offset + 1, + Y01, + mask=output_row_offset + 1 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 2, + Y02, + mask=output_row_offset + 2 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 3, + Y03, + mask=output_row_offset + 3 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 4, + Y04, + mask=output_row_offset + 4 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 5, + Y05, + mask=output_row_offset + 5 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 6, + Y06, + mask=output_row_offset + 6 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 7, + Y07, + mask=output_row_offset + 7 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 8, + Y08, + mask=output_row_offset + 8 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 9, + Y09, + mask=output_row_offset + 9 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 10, + Y10, + mask=output_row_offset + 10 < output_numel, + ) + + +@triton.jit +def fifth_order_bwd( + coord_ptr: tl.tensor, + coord_grad_ptr: tl.tensor, + sph_grad_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, +): + # work out the row offsets + block_id = tl.program_id(0) + # these are hardcoded because they are predetermined; + coord_stride = 3 + coord_striding = tl.arange(0, block_size) * coord_stride + # as the name suggests, this is effectively every node/atom + coord_row_offset = coord_striding + (block_size * coord_stride * block_id) + x = tl.load(coord_ptr + coord_row_offset, mask=coord_row_offset < coord_numel) + y = tl.load( + coord_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + z = tl.load( + coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + output_stride = 11 # [2l + 1] + output_striding = tl.arange(0, block_size) * output_stride + output_row_offset = output_striding + (block_size * output_stride * block_id) + # load in gradients w.r.t. spherical harmonic projections + g_0 = tl.load( + sph_grad_ptr + output_row_offset, mask=output_row_offset < output_numel + ) + g_1 = tl.load( + sph_grad_ptr + output_row_offset + 1, mask=output_row_offset + 1 < output_numel + ) + g_2 = tl.load( + sph_grad_ptr + output_row_offset + 2, mask=output_row_offset + 2 < output_numel + ) + g_3 = tl.load( + sph_grad_ptr + output_row_offset + 3, mask=output_row_offset + 3 < output_numel + ) + g_4 = tl.load( + sph_grad_ptr + output_row_offset + 4, mask=output_row_offset + 4 < output_numel + ) + g_5 = tl.load( + sph_grad_ptr + output_row_offset + 5, mask=output_row_offset + 5 < output_numel + ) + g_6 = tl.load( + sph_grad_ptr + output_row_offset + 6, mask=output_row_offset + 6 < output_numel + ) + g_7 = tl.load( + sph_grad_ptr + output_row_offset + 7, mask=output_row_offset + 7 < output_numel + ) + g_8 = tl.load( + sph_grad_ptr + output_row_offset + 8, mask=output_row_offset + 8 < output_numel + ) + g_9 = tl.load( + sph_grad_ptr + output_row_offset + 9, mask=output_row_offset + 9 < output_numel + ) + g_10 = tl.load( + sph_grad_ptr + output_row_offset + 10, + mask=output_row_offset + 10 < output_numel, + ) + # -------------------- variable and constant definitions + CONST000 = 1.60565407233314 + CONST001 = 3.00000000000000 + CONST002 = 3.21130814466628 + CONST003 = 1.60565407233314 + CONST004 = 6.42261628933256 + CONST005 = 6.42261628933256 + CONST006 = 8.67152307844476 + CONST007 = 8.02827036166571 + CONST008 = 6.93721846275580 + CONST009 = 11.6340690431164 + CONST010 = 12.8452325786651 + CONST011 = 6.21867148191637 + CONST012 = 6.21867148191637 + CONST014 = 12.4373429638327 + CONST017 = 12.8452325786651 + CONST018 = 13.8744369255116 + CONST019 = 24.8746859276655 + CONST020 = 24.8746859276655 + CONST021 = 27.7488738510232 + CONST024 = 29.4321253055229 + CONST027 = 7.35803132638072 + CONST029 = 46.5362761724657 + CONST030 = 51.3809303146605 + CONST031 = 51.3809303146605 + CONST034 = 101.955872807799 + CONST036 = -8.67152307844475 + CONST037 = 3.46860923137790 + CONST038 = -88.2963759165686 + CONST039 = -83.2466215530696 + CONST040 = -69.8044142586986 + CONST041 = -50.9779364038993 + CONST042 = -50.9779364038993 + CONST043 = -46.5362761724657 + CONST044 = -44.1481879582843 + CONST045 = -41.6233107765348 + CONST046 = -38.5356977359954 + CONST047 = -38.5356977359954 + CONST048 = -33.1662479035540 + CONST049 = -33.9852909359329 + CONST050 = 6.42261628933257 + CONST051 = -33.9852909359329 + CONST052 = -29.4321253055229 + CONST053 = -27.7488738510232 + CONST054 = -20.8116553882674 + CONST055 = -19.2678488679977 + CONST056 = -19.2678488679977 + CONST057 = -16.9926454679664 + CONST058 = -16.9926454679664 + CONST059 = -13.8744369255116 + CONST060 = -16.5831239517770 + CONST061 = -8.49632273398321 + CONST062 = -6.93721846275580 + CONST063 = -5.20291384706685 + CONST064 = -3.46860923137790 + VAR06 = x * x * x * x + VAR07 = x * x * x + VAR08 = x * x + VAR15 = y * y * y * y + VAR16 = y * y * y + VAR17 = y * y + VAR24 = z * z * z * z + VAR25 = z * z * z + VAR26 = z * z + # -------------------- kernel implementations + g_x = ( + g_0 * (CONST009 * VAR06 + CONST009 * VAR24 + CONST040 * VAR08 * VAR26) + + g_1 * y * (CONST038 * VAR08 * z - CONST052 * VAR25) + + g_10 * (CONST029 * VAR07 * z + CONST043 * VAR25 * x) + + g_2 + * ( + CONST001 * VAR08 * (CONST059 * VAR17 + CONST064 * VAR26) + + CONST006 * VAR06 + - CONST045 * VAR17 * VAR26 + + CONST063 * VAR24 + ) + + g_3 * (CONST041 * VAR08 * y * z - CONST049 * VAR16 * z + CONST057 * VAR25 * y) + + g_4 + * ( + CONST000 * VAR24 + + CONST001 * VAR08 * (CONST002 * VAR26 + CONST055 * VAR17) + + CONST007 * VAR06 + + CONST010 * VAR15 + + CONST056 * VAR17 * VAR26 + ) + + g_5 * (CONST048 * VAR16 * x + y * (CONST019 * VAR07 + CONST019 * VAR26 * x)) + + g_6 * (CONST005 * VAR25 * x + z * (CONST004 * VAR07 + CONST046 * VAR17 * x)) + + g_7 * (CONST049 * VAR16 * x - CONST051 * VAR07 * y) + + g_8 * (CONST008 * VAR25 * x + z * (CONST039 * VAR17 * x - CONST054 * VAR07)) + + g_9 * y * (CONST024 * VAR07 + CONST038 * VAR26 * x) + ) + g_y = ( + g_1 * (CONST052 * VAR07 * z - CONST052 * VAR25 * x) + + g_2 * (-CONST039 * VAR26 * x * y + CONST053 * VAR07 * y) + + g_3 * (CONST058 * VAR07 * z + x * (CONST034 * VAR17 * z + CONST057 * VAR25)) + + g_4 * (CONST047 * VAR07 * y + x * (CONST030 * VAR16 + CONST046 * VAR26 * y)) + + g_5 + * ( + CONST001 * VAR17 * (CONST060 * VAR08 + CONST060 * VAR26) + + CONST011 * VAR06 + + CONST012 * VAR24 + + CONST014 * VAR08 * VAR26 + - CONST060 * VAR15 + ) + + g_6 * (CONST046 * VAR25 * y + z * (CONST031 * VAR16 + CONST046 * VAR08 * y)) + + g_7 + * ( + CONST001 * VAR17 * (CONST057 * VAR08 - CONST057 * VAR26) + - CONST061 * VAR06 + + CONST061 * VAR24 + ) + + g_8 * (CONST021 * VAR25 * y + CONST039 * VAR08 * y * z) + + g_9 * (CONST027 * VAR06 + CONST027 * VAR24 + CONST044 * VAR08 * VAR26) + ) + g_z = ( + g_0 * (CONST029 * VAR25 * x + CONST043 * VAR07 * z) + + g_1 * y * (-CONST038 * VAR26 * x + CONST052 * VAR07) + + g_10 * (CONST009 * VAR06 + CONST009 * VAR24 + CONST040 * VAR08 * VAR26) + + g_2 * (CONST062 * VAR07 * z + x * (-CONST039 * VAR17 * z + CONST054 * VAR25)) + + g_3 * (CONST058 * VAR07 * y + x * (CONST042 * VAR26 * y - CONST049 * VAR16)) + + g_4 * (CONST005 * VAR07 * z + x * (CONST046 * VAR17 * z + CONST050 * VAR25)) + + g_5 * (CONST048 * VAR16 * z + y * (CONST019 * VAR08 * z + CONST020 * VAR25)) + + g_6 + * ( + CONST001 * VAR26 * (CONST002 * VAR08 + CONST056 * VAR17) + + CONST003 * VAR06 + + CONST007 * VAR24 + + CONST017 * VAR15 + + CONST056 * VAR08 * VAR17 + ) + + g_7 * (-CONST049 * VAR16 * z + CONST051 * VAR25 * y) + + g_8 + * ( + CONST001 * VAR26 * (CONST018 * VAR17 + CONST037 * VAR08) + + CONST036 * VAR24 + + CONST045 * VAR08 * VAR17 + - CONST063 * VAR06 + ) + + g_9 * y * (CONST024 * VAR25 + CONST038 * VAR08 * z) + ) + # write out gradients + tl.store( + coord_grad_ptr + coord_row_offset, g_x, mask=coord_row_offset < coord_numel + ) + tl.store( + coord_grad_ptr + coord_row_offset + 1, + g_y, + mask=coord_row_offset + 1 < coord_numel, + ) + tl.store( + coord_grad_ptr + coord_row_offset + 2, + g_z, + mask=coord_row_offset + 2 < coord_numel, + ) From b7e62a42ffe1c0df0c61751f8297528c12b7b034 Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Thu, 22 Aug 2024 10:43:57 -0700 Subject: [PATCH 011/116] feat: exposing direct wrapper functions --- src/equitriton/sph_harm/direct/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/equitriton/sph_harm/direct/__init__.py b/src/equitriton/sph_harm/direct/__init__.py index e69de29..06df358 100644 --- a/src/equitriton/sph_harm/direct/__init__.py +++ b/src/equitriton/sph_harm/direct/__init__.py @@ -0,0 +1,4 @@ +from equitriton.sph_harm.direct.y_2 import SecondOrderSphericalHarmonic +from equitriton.sph_harm.direct.y_5 import FifthOrderSphericalHarmonic + +__all__ = ["SecondOrderSphericalHarmonic", "FifthOrderSphericalHarmonic"] From a6d128c035002a8d9a875780ded30ab9cd196dda Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Thu, 22 Aug 2024 11:01:36 -0700 Subject: [PATCH 012/116] refactor & feat: added standardized utility functions for running spherical harmonics --- src/equitriton/sph_harm/direct/utils.py | 107 ++++++++++++++++++++++++ src/equitriton/sph_harm/direct/y_2.py | 6 +- src/equitriton/sph_harm/direct/y_5.py | 2 +- 3 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 src/equitriton/sph_harm/direct/utils.py diff --git a/src/equitriton/sph_harm/direct/utils.py b/src/equitriton/sph_harm/direct/utils.py new file mode 100644 index 0000000..d886009 --- /dev/null +++ b/src/equitriton/sph_harm/direct/utils.py @@ -0,0 +1,107 @@ +from __future__ import annotations + +from importlib import import_module + +import torch + +__all__ = ["torch_spherical_harmonic", "triton_spherical_harmonic"] + + +def torch_spherical_harmonic(l: int, coords: torch.Tensor) -> torch.Tensor: + """ + Utility function that will call the PyTorch implementation + of a spherical harmonic order. + + This is not intended for production use, but mainly for + sanity checking and convenience. + + Parameters + ---------- + l : int + Order of spherical harmonic requested. + coords : torch.Tensor + N-d tensor, where the last dimension should correspond + to xyz vectors. + + Returns + ------- + torch.Tensor + N-d tensor of the same dimensionality as the input coordinates, + but the size of the last dimension equal to [2 * l + 1]. + + Raises + ------ + ModuleNotFoundError + If order of spherical harmonic requested is not found, it is + likely not yet implemented. + RuntimeError + If the PyTorch implementation of the spherical harmonic is + not found within the module. + RuntimeError + If the shape of the last dimension of the ``coords`` tensor + is not equal to three. + """ + try: + target_module = import_module(f"sph_harm.direct.y_{l}", "equitriton") + except ModuleNotFoundError as e: + raise ModuleNotFoundError( + f"Spherical harmonic order l={l} requested, but not found!" + ) from e + torch_func = getattr(target_module, "_torch_fwd", None) + if not torch_func: + raise RuntimeError(f"PyTorch implementation of l={l} not found.") + if coords.size(-1) != 3: + raise RuntimeError("Expects last dimension of coordinate tensor to be 3!") + return torch_func(coords) + + +def triton_spherical_harmonic( + l: int, coords: torch.Tensor, mask: torch.Tensor | None = None +) -> torch.Tensor: + """ + Utility function that will call the Triton implementation + of a spherical harmonic order. + + This is not intended for production use, but mainly for + sanity checking and convenience. + + Parameters + ---------- + l : int + Order of spherical harmonic requested. + coords : torch.Tensor + N-d tensor, where the last dimension should correspond + to xyz vectors. + + Returns + ------- + torch.Tensor + N-d tensor of the same dimensionality as the input coordinates, + but the size of the last dimension equal to [2 * l + 1]. + + Raises + ------ + ModuleNotFoundError + If order of spherical harmonic requested is not found, it is + likely not yet implemented. + RuntimeError + If the Triton implementation of the spherical harmonic is + not found within the module. + RuntimeError + If the shape of the last dimension of the ``coords`` tensor + is not equal to three. + """ + try: + target_module = import_module(f"sph_harm.direct.y_{l}", "equitriton") + except ModuleNotFoundError as e: + raise ModuleNotFoundError( + f"Spherical harmonic order l={l} requested, but not found!" + ) from e + defined_classes: list = getattr(target_module, "__all__") + # there should only be one entry in __all__, which is the autograd wrapper + sph_harm_func = getattr(target_module, defined_classes.pop(), None) + if not sph_harm_func: + raise RuntimeError(f"Triton implementation of l={l} not found.") + if coords.size(-1) != 3: + raise RuntimeError("Expects last dimension of coordinate tensor to be 3!") + return sph_harm_func.apply(coords, mask) diff --git a/src/equitriton/sph_harm/direct/y_2.py b/src/equitriton/sph_harm/direct/y_2.py index 85cc8f9..9fe7f37 100644 --- a/src/equitriton/sph_harm/direct/y_2.py +++ b/src/equitriton/sph_harm/direct/y_2.py @@ -47,12 +47,16 @@ def backward( return coord_grad_output -def torch_second_order_fwd(coords: torch.Tensor) -> torch.Tensor: +def _torch_fwd(coords: torch.Tensor) -> torch.Tensor: """ PyTorch implementation of the kernel. This is designed purely for unit testing to ensure that the Triton implementation is behaving as intended. + This function is generically named to make it easier for + it to be called programmatically: it is _not_ intended + to be used manually. + Parameters ---------- coords : torch.Tensor diff --git a/src/equitriton/sph_harm/direct/y_5.py b/src/equitriton/sph_harm/direct/y_5.py index d5f2460..d6e3f68 100644 --- a/src/equitriton/sph_harm/direct/y_5.py +++ b/src/equitriton/sph_harm/direct/y_5.py @@ -47,7 +47,7 @@ def backward( return coord_grad_output -def torch_fifth_order_fwd(coords: torch.Tensor) -> torch.Tensor: +def _torch_fwd(coords: torch.Tensor) -> torch.Tensor: """ PyTorch implementation of the kernel. This is designed purely for unit testing to ensure that the Triton implementation From 5a4f388e3d43520620da7ae0aca376ba894f1248 Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Thu, 22 Aug 2024 11:19:54 -0700 Subject: [PATCH 013/116] test: refactored unit test to be generalized --- .../direct/tests/test_direct_sph_harm.py | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py index 3509bb7..eb03c98 100644 --- a/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py +++ b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py @@ -2,9 +2,15 @@ import torch from equitriton import __HAS_XPU__, __HAS_CUDA__ -from equitriton.sph_harm.direct import y_2 +from equitriton.sph_harm.direct.utils import ( + torch_spherical_harmonic, + triton_spherical_harmonic, +) + +torch.manual_seed(316165) +@pytest.mark.parametrize("order", [2, 5]) @pytest.mark.parametrize( "device", [ @@ -21,13 +27,17 @@ ], ) @pytest.mark.parametrize("tensor_shape", [(512, 3), (128, 16, 3), (256, 8, 8, 3)]) -def test_forward_equivalence(device, tensor_shape): - coords = torch.rand(tensor_shape, device=device) - triton_out = y_2.SecondOrderSphericalHarmonic.apply(coords) - torch_out = y_2.torch_second_order_fwd(coords) +@pytest.mark.parametrize( + "dtype", [torch.float16, torch.bfloat16, torch.float32, torch.float64] +) +def test_forward_equivalence(order, device, tensor_shape, dtype): + coords = torch.rand(tensor_shape, device=device, dtype=dtype) + triton_out = triton_spherical_harmonic(order, coords) + torch_out = torch_spherical_harmonic(order, coords) assert torch.allclose(triton_out, torch_out, atol=1e-6, rtol=1e-4) +@pytest.mark.parametrize("order", [2, 5]) @pytest.mark.parametrize( "device", [ @@ -44,15 +54,18 @@ def test_forward_equivalence(device, tensor_shape): ], ) @pytest.mark.parametrize("tensor_shape", [(512, 3), (128, 16, 3), (256, 8, 8, 3)]) -def test_backward_equivalence(device, tensor_shape): - coords = torch.rand(tensor_shape, device=device, requires_grad=True) +@pytest.mark.parametrize( + "dtype", [torch.float16, torch.bfloat16, torch.float32, torch.float64] +) +def test_backward_equivalence(order, device, tensor_shape, dtype): + coords = torch.rand(tensor_shape, device=device, dtype=dtype, requires_grad=True) # run with autograd first - torch_out = y_2.torch_second_order_fwd(coords) + torch_out = torch_spherical_harmonic(order, coords) torch_out.backward(gradient=torch.ones_like(torch_out)) torch_grad = coords.grad.clone().detach() coords.grad.zero_() # now run the triton result - triton_out = y_2.SecondOrderSphericalHarmonic.apply(coords) + triton_out = triton_spherical_harmonic(order, coords) triton_out.backward(gradient=torch.ones_like(triton_out)) triton_grad = coords.grad.clone().detach() - assert torch.allclose(triton_grad, torch_grad, atol=1e-6, rtol=1e-4) + assert torch.allclose(triton_grad, torch_grad, atol=1e-5, rtol=1e-3) From 581526d6b534a57de83f6a33670d0f1b6e6aa84d Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Thu, 22 Aug 2024 11:20:15 -0700 Subject: [PATCH 014/116] fix: correcting programmatic library importing mechanism --- src/equitriton/sph_harm/direct/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/equitriton/sph_harm/direct/utils.py b/src/equitriton/sph_harm/direct/utils.py index d886009..1fe0816 100644 --- a/src/equitriton/sph_harm/direct/utils.py +++ b/src/equitriton/sph_harm/direct/utils.py @@ -42,7 +42,7 @@ def torch_spherical_harmonic(l: int, coords: torch.Tensor) -> torch.Tensor: is not equal to three. """ try: - target_module = import_module(f"sph_harm.direct.y_{l}", "equitriton") + target_module = import_module(f"equitriton.sph_harm.direct.y_{l}") except ModuleNotFoundError as e: raise ModuleNotFoundError( f"Spherical harmonic order l={l} requested, but not found!" @@ -92,16 +92,16 @@ def triton_spherical_harmonic( is not equal to three. """ try: - target_module = import_module(f"sph_harm.direct.y_{l}", "equitriton") + target_module = import_module(f"equitriton.sph_harm.direct.y_{l}") except ModuleNotFoundError as e: raise ModuleNotFoundError( f"Spherical harmonic order l={l} requested, but not found!" ) from e defined_classes: list = getattr(target_module, "__all__") # there should only be one entry in __all__, which is the autograd wrapper - sph_harm_func = getattr(target_module, defined_classes.pop(), None) + sph_harm_func = getattr(target_module, defined_classes[0], None) if not sph_harm_func: raise RuntimeError(f"Triton implementation of l={l} not found.") if coords.size(-1) != 3: raise RuntimeError("Expects last dimension of coordinate tensor to be 3!") - return sph_harm_func.apply(coords, mask) + return sph_harm_func.apply(coords) From ffa0ce5061f9235fa7f3e9c914cbd5e9df7ab448 Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Thu, 22 Aug 2024 11:38:27 -0700 Subject: [PATCH 015/116] feat: implemented tenth order terms --- src/equitriton/sph_harm/direct/y_10.py | 2577 ++++++++++++++++++++++++ 1 file changed, 2577 insertions(+) create mode 100644 src/equitriton/sph_harm/direct/y_10.py diff --git a/src/equitriton/sph_harm/direct/y_10.py b/src/equitriton/sph_harm/direct/y_10.py new file mode 100644 index 0000000..ce535b3 --- /dev/null +++ b/src/equitriton/sph_harm/direct/y_10.py @@ -0,0 +1,2577 @@ +import triton +import torch +from triton import language as tl + +from equitriton.utils import calculate_lastdim_num_blocks + +__all__ = ["TenthOrderSphericalHarmonic"] + + +class TenthOrderSphericalHarmonic(torch.autograd.Function): + @staticmethod + def forward( + ctx, + coords: torch.Tensor, + mask: torch.Tensor | None = None, + block_size: int = 64, + ): + output_tensor = torch.empty( + (*coords.shape[:-1], 11), dtype=coords.dtype, device=coords.device + ) + coord_numel = coords.numel() + output_numel = output_tensor.numel() + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + # apply the kernel + tenth_order_fwd[num_blocks,]( + coords, output_tensor, block_size, coord_numel, output_numel + ) + ctx.save_for_backward(coords) + return output_tensor + + @staticmethod + def backward( + ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 + ) -> torch.Tensor: + (coords,) = ctx.saved_tensors + coord_grad_output = torch.zeros_like(coords) + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + # call backward kernel + tenth_order_bwd[num_blocks,]( + coords, + coord_grad_output, + sph_grad_tensor, + block_size, + coords.numel(), + sph_grad_tensor.numel(), + ) + return coord_grad_output + + +def _torch_fwd(coords: torch.Tensor) -> torch.Tensor: + """ + PyTorch implementation of the kernel. This is designed + purely for unit testing to ensure that the Triton implementation + is behaving as intended. + + Parameters + ---------- + coords : torch.Tensor + N-d tensor, where the last dimension corresponds to + xyz values. + + Returns + ------- + torch.Tensor + N-d tensor, where the last dimension corresponds to + each projection of the second order spherical harmonic. + """ + x = coords[..., 0].contiguous().unsqueeze(-1) + y = coords[..., 1].contiguous().unsqueeze(-1) + z = coords[..., 2].contiguous().unsqueeze(-1) + # -------------------- variable and constant definitions + CONST001 = 1.75869118663323 + CONST002 = -1021.92317475320 + CONST004 = 4.58257569495584 + CONST005 = 6.63243980843400 + CONST006 = 4.82870805793735 + CONST007 = 4.97432985632550 + CONST008 = 1545.18657853995 + CONST009 = 10.5521471197994 + CONST010 = 12.1657520803952 + CONST011 = 13.2648796168680 + CONST013 = 15.7883647328499 + CONST014 = 15.7302121789667 + CONST015 = 16.4144510752435 + CONST016 = 12.8765548211663 + CONST017 = 19.3148322317494 + CONST018 = 16.7271353825295 + CONST019 = 22.8629854262320 + CONST020 = 535.268332240943 + CONST021 = 23.2135393295190 + CONST022 = 24.6216766128653 + CONST023 = 27.2034486491732 + CONST024 = 541.428124558099 + CONST025 = -994.666978169547 + CONST026 = 33.9852909359329 + CONST027 = 33.9852909359329 + CONST028 = 35.5238206489124 + CONST029 = -984.867064514610 + CONST030 = -4.82870805793735 + CONST031 = 1070.53666448189 + CONST032 = -463.555973561985 + CONST034 = 53.2857309733686 + CONST035 = 53.2857309733686 + CONST036 = 56.3871618715269 + CONST037 = 56.3871618715269 + CONST039 = -1989.33395633909 + CONST041 = -450.224943778107 + CONST042 = 66.9085415301178 + CONST043 = 69.6406179885570 + CONST044 = 69.6406179885570 + CONST045 = -437.967074894228 + CONST046 = 77.2593289269976 + CONST047 = 78.6510608948335 + CONST049 = -1969.73412902922 + CONST050 = 77.3468749368712 + CONST051 = 1624.28437367430 + CONST054 = 94.7301883970997 + CONST056 = 100.362812295177 + CONST057 = -412.049754277320 + CONST058 = 101.517773354644 + CONST059 = -5.63871618715269 + CONST060 = -406.071093418574 + CONST061 = 109.491768723557 + CONST062 = -393.946825805844 + CONST063 = -902.194589944431 + CONST065 = -386.296644634988 + CONST066 = -386.296644634988 + CONST070 = 4.97432985632550 + CONST071 = 150.074981259369 + CONST074 = 685.526905959165 + CONST075 = -337.668707833581 + CONST076 = -337.668707833581 + CONST077 = 176.178376404427 + CONST078 = 176.592751833137 + CONST079 = 185.708314636152 + CONST080 = -326.441383790078 + CONST081 = -1.60956935264578 + CONST082 = -1.97354559160624 + CONST083 = 196.973412902922 + CONST085 = -824.099508554641 + CONST087 = -1.97354559160624 + CONST088 = -305.867618423396 + CONST089 = -305.867618423396 + CONST090 = 721.755671955545 + CONST091 = -305.867618423396 + CONST092 = -300.731529981477 + CONST093 = -300.731529981477 + CONST094 = -1.75869118663323 + CONST095 = -290.050781013267 + CONST097 = 225.548647486108 + CONST098 = 225.548647486108 + CONST099 = -284.190565191299 + CONST101 = -278.562471954228 + CONST102 = -278.562471954228 + CONST103 = -787.893651611688 + CONST104 = -787.893651611688 + CONST105 = 772.593289269975 + CONST106 = 787.893651611688 + CONST107 = 787.893651611688 + CONST108 = 278.562471954228 + CONST109 = -742.833258544608 + CONST110 = -1.65810995210850 + CONST112 = -1761.78376404427 + CONST113 = -223.028471767059 + CONST114 = -734.076568351780 + CONST116 = -220.222970505534 + CONST117 = 1321.33782303320 + CONST118 = 1321.33782303320 + CONST119 = -203.035546709287 + CONST120 = -1.65810995210850 + CONST121 = -196.973412902922 + CONST122 = -196.973412902922 + CONST123 = -696.406179885570 + CONST125 = 338.322971229162 + CONST126 = -1181.84047741753 + CONST127 = -669.085415301178 + CONST128 = -669.085415301178 + CONST129 = -154.518657853995 + CONST130 = -154.518657853995 + CONST131 = 360.877835977772 + CONST132 = -150.074981259369 + CONST133 = -2707.14062279049 + CONST134 = -146.815313670356 + CONST135 = 880.891882022136 + CONST136 = 1392.81235977114 + CONST137 = 1392.81235977114 + CONST138 = -131.315608601948 + CONST139 = -131.315608601948 + CONST141 = -125.841697431734 + CONST142 = -125.841697431734 + CONST143 = -122.415518921279 + CONST145 = 406.071093418574 + CONST146 = -103.107953136506 + CONST147 = -103.107953136506 + CONST148 = -101.517773354644 + CONST149 = -98.4867064514610 + CONST150 = 412.049754277320 + CONST151 = -94.7301883970997 + CONST152 = -1114.24988781691 + CONST153 = -88.2963759165686 + CONST154 = -1624.28437367430 + CONST155 = -82.8889148474622 + CONST156 = -82.8889148474622 + CONST158 = -590.920238708766 + CONST159 = -77.3468749368713 + CONST160 = -77.2593289269975 + CONST161 = 2486.66744542387 + CONST162 = -2626.31217203896 + CONST165 = -571.272421632637 + CONST166 = -56.2781179722634 + CONST167 = -49.2433532257305 + CONST168 = -49.2433532257305 + CONST169 = 984.867064514610 + CONST170 = -541.428124558099 + CONST171 = -24.6216766128653 + CONST172 = -22.8629854262320 + CONST173 = -16.4144510752435 + CONST174 = -15.7883647328499 + CONST175 = -14.0695294930659 + CONST176 = -13.2648796168680 + CONST177 = -11.2774323743054 + CONST178 = -14.5025390506634 + CONST179 = -6.63243980843400 + CONST180 = -5.63871618715269 + CONST181 = 1532.88476212980 + CONST182 = -3.21913870529156 + CONST183 = -2.72034486491732 + CONST184 = -1.12774323743054 + # ordering is really messy because I've refactored + # the higher powers in terms of the lower ones + VAR05 = x * x * x * x * x + VAR06 = x * x * x * x + VAR07 = x * x * x + VAR08 = x * x + VAR00 = VAR05 * VAR05 + VAR01 = VAR05 * VAR06 + VAR02 = VAR06 * VAR06 + VAR03 = VAR06 * VAR07 + VAR04 = VAR07 * VAR07 + VAR14 = y * y * y * y * y + VAR15 = y * y * y * y + VAR16 = y * y * y + VAR17 = y * y + VAR09 = VAR14 * VAR14 + VAR10 = VAR14 * VAR15 + VAR11 = VAR15 * VAR15 + VAR12 = VAR15 * VAR16 + VAR13 = VAR16 * VAR16 + VAR23 = z * z * z * z * z + VAR24 = z * z * z * z + VAR25 = z * z * z + VAR26 = z * z + VAR18 = VAR23 * VAR23 + VAR19 = VAR23 * VAR24 + VAR20 = VAR24 * VAR24 + VAR21 = VAR24 * VAR25 + VAR22 = VAR25 * VAR25 + # -------------------- kernel implementations + Y00 = ( + CONST023 * VAR01 * z + + CONST023 * VAR19 * x + + CONST074 * VAR05 * VAR23 + + CONST080 * VAR03 * VAR25 + + CONST080 * VAR07 * VAR21 + ) + Y01 = y * ( + CONST002 * VAR07 * VAR22 + + CONST010 * VAR01 + + CONST045 * VAR03 * VAR26 + + CONST061 * VAR20 * x + + CONST181 * VAR05 * VAR24 + ) + Y02 = ( + CONST013 * VAR01 * z + + CONST054 * VAR07 * VAR21 + + CONST151 * VAR03 * VAR25 + + CONST174 * VAR19 * x + + VAR17 + * ( + -CONST039 * VAR05 * VAR25 + + CONST039 * VAR07 * VAR23 + + CONST099 * VAR03 * z + - CONST099 * VAR21 * x + ) + ) + Y03 = VAR16 * ( + CONST024 * VAR22 * x + + CONST051 * VAR05 * VAR26 + + CONST133 * VAR07 * VAR24 + + CONST159 * VAR03 + ) + y * ( + CONST095 * VAR03 * VAR26 + - CONST119 * VAR05 * VAR24 + + CONST145 * VAR07 * VAR22 + + CONST148 * VAR20 * x + - CONST178 * VAR01 + ) + Y04 = ( + CONST009 * VAR01 * z + + VAR03 * (CONST076 * VAR17 * z + CONST175 * VAR25) + + VAR05 * (CONST106 * VAR15 * z + CONST107 * VAR17 * VAR25 + CONST167 * VAR23) + + VAR07 + * (CONST106 * VAR17 * VAR23 + CONST162 * VAR15 * VAR25 + CONST175 * VAR21) + + x * (CONST009 * VAR19 + CONST075 * VAR17 * VAR21 + CONST106 * VAR15 * VAR23) + ) + Y05 = ( + VAR14 * (CONST077 * VAR05 + CONST112 * VAR07 * VAR26 + CONST135 * VAR24 * x) + + VAR16 + * ( + -CONST114 * VAR07 * VAR24 + + CONST114 * VAR22 * x + + CONST117 * VAR05 * VAR26 + + CONST134 * VAR03 + ) + + y + * ( + CONST014 * VAR01 + + CONST047 * VAR20 * x + + CONST116 * VAR05 * VAR24 + + CONST141 * VAR03 * VAR26 + ) + ) + Y06 = ( + CONST005 * VAR01 * z + + VAR03 * (CONST011 * VAR25 + CONST102 * VAR17 * z) + + VAR05 * (CONST101 * VAR17 * VAR25 - CONST152 * VAR15 * z) + + VAR07 * (CONST108 * VAR17 * VAR23 + CONST109 * VAR13 * z + CONST176 * VAR21) + + x + * ( + CONST108 * VAR17 * VAR21 + - CONST109 * VAR13 * VAR25 + + CONST152 * VAR15 * VAR23 + + CONST179 * VAR19 + ) + ) + Y07 = ( + VAR12 * (-CONST041 * VAR26 * x + CONST132 * VAR07) + + VAR14 * (-CONST062 * VAR05 + CONST103 * VAR07 * VAR26 + CONST126 * VAR24 * x) + + VAR16 + * ( + CONST083 * VAR05 * VAR26 + + CONST121 * VAR03 + - CONST158 * VAR22 * x + + CONST169 * VAR07 * VAR24 + ) + + y + * ( + CONST015 * VAR01 + + CONST138 * VAR07 * VAR22 + + CONST149 * VAR05 * VAR24 + + CONST168 * VAR20 * x + ) + ) + Y08 = ( + -CONST182 * VAR01 * z + + VAR03 * (CONST016 * VAR25 + CONST129 * VAR17 * z) + + VAR05 * (CONST017 * VAR23 + CONST032 * VAR17 * VAR25 + CONST105 * VAR15 * z) + + VAR07 + * ( + CONST008 * VAR15 * VAR25 + + CONST016 * VAR21 + + CONST032 * VAR17 * VAR23 + + CONST085 * VAR13 * z + ) + + x + * ( + CONST078 * VAR11 * z + + CONST085 * VAR13 * VAR25 + + CONST105 * VAR15 * VAR23 + + CONST129 * VAR17 * VAR21 + - CONST182 * VAR19 + ) + ) + Y09 = ( + CONST018 * VAR01 * y + + VAR03 * (CONST042 * VAR26 * y + CONST113 * VAR16) + + VAR05 * (CONST020 * VAR14 + CONST056 * VAR24 * y + CONST128 * VAR16 * VAR26) + + VAR07 + * ( + CONST031 * VAR14 * VAR26 + + CONST042 * VAR22 * y + + CONST088 * VAR12 + + CONST127 * VAR16 * VAR24 + ) + + x + * ( + CONST018 * VAR20 * y + + CONST020 * VAR14 * VAR24 + + CONST026 * VAR10 + + CONST088 * VAR12 * VAR26 + + CONST113 * VAR16 * VAR22 + ) + ) + Y10 = ( + CONST004 * VAR09 + + CONST037 * VAR17 * VAR20 + + CONST093 * VAR15 * VAR22 + + CONST131 * VAR13 * VAR24 + + CONST147 * VAR11 * VAR26 + + CONST184 * VAR00 + + CONST184 * VAR18 + + VAR02 * (CONST036 * VAR17 + CONST059 * VAR26) + + VAR04 * (CONST092 * VAR15 + CONST098 * VAR17 * VAR26 + CONST177 * VAR24) + + VAR06 + * ( + CONST063 * VAR15 * VAR26 + + CONST125 * VAR17 * VAR24 + + CONST131 * VAR13 + + CONST177 * VAR22 + ) + + VAR08 + * ( + CONST063 * VAR15 * VAR24 + + CONST090 * VAR13 * VAR26 + + CONST097 * VAR17 * VAR22 + + CONST146 * VAR11 + + CONST180 * VAR20 + ) + ) + Y11 = ( + CONST018 * VAR19 * y + + VAR21 * (CONST042 * VAR08 * y + CONST113 * VAR16) + + VAR23 * (CONST020 * VAR14 + CONST056 * VAR06 * y + CONST128 * VAR08 * VAR16) + + VAR25 + * ( + CONST031 * VAR08 * VAR14 + + CONST042 * VAR04 * y + + CONST091 * VAR12 + + CONST127 * VAR06 * VAR16 + ) + + z + * ( + CONST018 * VAR02 * y + + CONST020 * VAR06 * VAR14 + + CONST027 * VAR10 + + CONST089 * VAR08 * VAR12 + + CONST113 * VAR04 * VAR16 + ) + ) + Y12 = ( + CONST057 * VAR13 * VAR24 + - CONST066 * VAR15 * VAR22 + + CONST081 * VAR00 + - CONST081 * VAR18 + - CONST153 * VAR11 * VAR26 + + CONST160 * VAR17 * VAR20 + + VAR02 * (CONST030 * VAR26 + CONST046 * VAR17) + + VAR04 * (CONST066 * VAR15 - CONST129 * VAR17 * VAR26 + CONST182 * VAR24) + + VAR06 * (CONST065 * VAR15 * VAR26 + CONST150 * VAR13 - CONST182 * VAR22) + + VAR08 + * ( + CONST006 * VAR20 + - CONST066 * VAR15 * VAR24 + + CONST130 * VAR17 * VAR22 + + CONST153 * VAR11 + ) + ) + Y13 = ( + VAR12 * (CONST041 * VAR08 * z + CONST071 * VAR25) + + VAR14 * (CONST062 * VAR23 + CONST107 * VAR08 * VAR25 - CONST126 * VAR06 * z) + + VAR16 + * ( + CONST029 * VAR06 * VAR25 + - CONST121 * VAR21 + + CONST122 * VAR08 * VAR23 + + CONST158 * VAR04 * z + ) + + y + * ( + -CONST138 * VAR04 * VAR25 + - CONST149 * VAR06 * VAR23 + - CONST168 * VAR02 * z + + CONST173 * VAR19 + ) + ) + Y14 = ( + CONST044 * VAR17 * VAR20 + + CONST079 * VAR13 * VAR24 + + CONST101 * VAR15 * VAR22 + + CONST110 * VAR00 + + CONST120 * VAR18 + + VAR02 * (CONST043 * VAR17 + CONST070 * VAR26) + + VAR04 * (CONST021 * VAR24 + CONST101 * VAR15 + CONST101 * VAR17 * VAR26) + + VAR06 + * ( + CONST021 * VAR22 + + CONST079 * VAR13 + + CONST123 * VAR17 * VAR24 + + CONST137 * VAR15 * VAR26 + ) + + VAR08 + * ( + CONST007 * VAR20 + + CONST101 * VAR17 * VAR22 + + CONST136 * VAR15 * VAR24 + + CONST152 * VAR13 * VAR26 + ) + ) + Y15 = ( + VAR14 * (CONST077 * VAR23 + CONST112 * VAR08 * VAR25 + CONST135 * VAR06 * z) + + VAR16 + * ( + CONST114 * VAR04 * z + - CONST114 * VAR06 * VAR25 + + CONST118 * VAR08 * VAR23 + + CONST134 * VAR21 + ) + + y + * ( + CONST014 * VAR19 + + CONST047 * VAR02 * z + + CONST116 * VAR06 * VAR23 + + CONST142 * VAR08 * VAR21 + ) + ) + Y16 = ( + CONST001 * VAR18 + + CONST094 * VAR00 + - CONST139 * VAR15 * VAR22 + + CONST166 * VAR17 * VAR20 + + VAR02 * (CONST019 * VAR26 - CONST166 * VAR17) + + VAR04 * (CONST022 * VAR24 + CONST104 * VAR17 * VAR26 + CONST139 * VAR15) + + VAR06 * (-CONST049 * VAR15 * VAR26 + CONST171 * VAR22) + + VAR08 + * (CONST049 * VAR15 * VAR24 + CONST106 * VAR17 * VAR22 + CONST172 * VAR20) + ) + Y17 = VAR16 * ( + CONST050 * VAR21 + - CONST133 * VAR06 * VAR25 + + CONST154 * VAR08 * VAR23 + + CONST170 * VAR04 * z + ) + y * ( + CONST058 * VAR02 * z + + CONST060 * VAR04 * VAR25 + - CONST095 * VAR08 * VAR21 + + CONST119 * VAR06 * VAR23 + + CONST178 * VAR19 + ) + Y18 = ( + CONST034 * VAR02 * VAR26 + + CONST035 * VAR08 * VAR20 + + CONST082 * VAR00 + + CONST087 * VAR18 + + CONST155 * VAR04 * VAR24 + + CONST156 * VAR06 * VAR22 + + VAR17 + * ( + CONST025 * VAR04 * VAR26 + + CONST025 * VAR08 * VAR22 + + CONST028 * VAR02 + + CONST028 * VAR20 + + CONST161 * VAR06 * VAR24 + ) + ) + Y19 = y * ( + CONST002 * VAR04 * VAR25 + + CONST010 * VAR19 + + CONST045 * VAR08 * VAR21 + + CONST061 * VAR02 * z + + CONST181 * VAR06 * VAR23 + ) + Y20 = ( + -CONST143 * VAR02 * VAR26 + + CONST143 * VAR08 * VAR20 + + CONST165 * VAR04 * VAR24 + - CONST165 * VAR06 * VAR22 + + CONST183 * VAR00 + - CONST183 * VAR18 + ) + # not the prettiest way to concatenate, but better than + # messing with the linter + tensors = [ + Y00, + Y01, + Y02, + Y03, + Y04, + Y05, + Y06, + Y07, + Y08, + Y09, + Y10, + Y11, + Y12, + Y13, + Y14, + Y15, + Y16, + Y17, + Y18, + Y19, + Y20, + ] + return torch.cat(tensors, dim=-1) + + +@triton.jit +def tenth_order_fwd( + coord_ptr: tl.tensor, + output_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, +): + # these are hardcoded because they are predetermined; + coord_stride = 3 + # work out the row offsets + block_id = tl.program_id(0) + coord_striding = tl.arange(0, block_size) * coord_stride + # as the name suggests, this is effectively every node/atom + coord_row_offset = coord_striding + (block_size * coord_stride * block_id) + x = tl.load(coord_ptr + coord_row_offset, mask=coord_row_offset < coord_numel) + y = tl.load( + coord_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + z = tl.load( + coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + # -------------------- variable and constant definitions + CONST001 = 1.75869118663323 + CONST002 = -1021.92317475320 + CONST004 = 4.58257569495584 + CONST005 = 6.63243980843400 + CONST006 = 4.82870805793735 + CONST007 = 4.97432985632550 + CONST008 = 1545.18657853995 + CONST009 = 10.5521471197994 + CONST010 = 12.1657520803952 + CONST011 = 13.2648796168680 + CONST013 = 15.7883647328499 + CONST014 = 15.7302121789667 + CONST015 = 16.4144510752435 + CONST016 = 12.8765548211663 + CONST017 = 19.3148322317494 + CONST018 = 16.7271353825295 + CONST019 = 22.8629854262320 + CONST020 = 535.268332240943 + CONST021 = 23.2135393295190 + CONST022 = 24.6216766128653 + CONST023 = 27.2034486491732 + CONST024 = 541.428124558099 + CONST025 = -994.666978169547 + CONST026 = 33.9852909359329 + CONST027 = 33.9852909359329 + CONST028 = 35.5238206489124 + CONST029 = -984.867064514610 + CONST030 = -4.82870805793735 + CONST031 = 1070.53666448189 + CONST032 = -463.555973561985 + CONST034 = 53.2857309733686 + CONST035 = 53.2857309733686 + CONST036 = 56.3871618715269 + CONST037 = 56.3871618715269 + CONST039 = -1989.33395633909 + CONST041 = -450.224943778107 + CONST042 = 66.9085415301178 + CONST043 = 69.6406179885570 + CONST044 = 69.6406179885570 + CONST045 = -437.967074894228 + CONST046 = 77.2593289269976 + CONST047 = 78.6510608948335 + CONST049 = -1969.73412902922 + CONST050 = 77.3468749368712 + CONST051 = 1624.28437367430 + CONST054 = 94.7301883970997 + CONST056 = 100.362812295177 + CONST057 = -412.049754277320 + CONST058 = 101.517773354644 + CONST059 = -5.63871618715269 + CONST060 = -406.071093418574 + CONST061 = 109.491768723557 + CONST062 = -393.946825805844 + CONST063 = -902.194589944431 + CONST065 = -386.296644634988 + CONST066 = -386.296644634988 + CONST070 = 4.97432985632550 + CONST071 = 150.074981259369 + CONST074 = 685.526905959165 + CONST075 = -337.668707833581 + CONST076 = -337.668707833581 + CONST077 = 176.178376404427 + CONST078 = 176.592751833137 + CONST079 = 185.708314636152 + CONST080 = -326.441383790078 + CONST081 = -1.60956935264578 + CONST082 = -1.97354559160624 + CONST083 = 196.973412902922 + CONST085 = -824.099508554641 + CONST087 = -1.97354559160624 + CONST088 = -305.867618423396 + CONST089 = -305.867618423396 + CONST090 = 721.755671955545 + CONST091 = -305.867618423396 + CONST092 = -300.731529981477 + CONST093 = -300.731529981477 + CONST094 = -1.75869118663323 + CONST095 = -290.050781013267 + CONST097 = 225.548647486108 + CONST098 = 225.548647486108 + CONST099 = -284.190565191299 + CONST101 = -278.562471954228 + CONST102 = -278.562471954228 + CONST103 = -787.893651611688 + CONST104 = -787.893651611688 + CONST105 = 772.593289269975 + CONST106 = 787.893651611688 + CONST107 = 787.893651611688 + CONST108 = 278.562471954228 + CONST109 = -742.833258544608 + CONST110 = -1.65810995210850 + CONST112 = -1761.78376404427 + CONST113 = -223.028471767059 + CONST114 = -734.076568351780 + CONST116 = -220.222970505534 + CONST117 = 1321.33782303320 + CONST118 = 1321.33782303320 + CONST119 = -203.035546709287 + CONST120 = -1.65810995210850 + CONST121 = -196.973412902922 + CONST122 = -196.973412902922 + CONST123 = -696.406179885570 + CONST125 = 338.322971229162 + CONST126 = -1181.84047741753 + CONST127 = -669.085415301178 + CONST128 = -669.085415301178 + CONST129 = -154.518657853995 + CONST130 = -154.518657853995 + CONST131 = 360.877835977772 + CONST132 = -150.074981259369 + CONST133 = -2707.14062279049 + CONST134 = -146.815313670356 + CONST135 = 880.891882022136 + CONST136 = 1392.81235977114 + CONST137 = 1392.81235977114 + CONST138 = -131.315608601948 + CONST139 = -131.315608601948 + CONST141 = -125.841697431734 + CONST142 = -125.841697431734 + CONST143 = -122.415518921279 + CONST145 = 406.071093418574 + CONST146 = -103.107953136506 + CONST147 = -103.107953136506 + CONST148 = -101.517773354644 + CONST149 = -98.4867064514610 + CONST150 = 412.049754277320 + CONST151 = -94.7301883970997 + CONST152 = -1114.24988781691 + CONST153 = -88.2963759165686 + CONST154 = -1624.28437367430 + CONST155 = -82.8889148474622 + CONST156 = -82.8889148474622 + CONST158 = -590.920238708766 + CONST159 = -77.3468749368713 + CONST160 = -77.2593289269975 + CONST161 = 2486.66744542387 + CONST162 = -2626.31217203896 + CONST165 = -571.272421632637 + CONST166 = -56.2781179722634 + CONST167 = -49.2433532257305 + CONST168 = -49.2433532257305 + CONST169 = 984.867064514610 + CONST170 = -541.428124558099 + CONST171 = -24.6216766128653 + CONST172 = -22.8629854262320 + CONST173 = -16.4144510752435 + CONST174 = -15.7883647328499 + CONST175 = -14.0695294930659 + CONST176 = -13.2648796168680 + CONST177 = -11.2774323743054 + CONST178 = -14.5025390506634 + CONST179 = -6.63243980843400 + CONST180 = -5.63871618715269 + CONST181 = 1532.88476212980 + CONST182 = -3.21913870529156 + CONST183 = -2.72034486491732 + CONST184 = -1.12774323743054 + # ordering is really messy because I've refactored + # the higher powers in terms of the lower ones + VAR05 = x * x * x * x * x + VAR06 = x * x * x * x + VAR07 = x * x * x + VAR08 = x * x + VAR00 = VAR05 * VAR05 + VAR01 = VAR05 * VAR06 + VAR02 = VAR06 * VAR06 + VAR03 = VAR06 * VAR07 + VAR04 = VAR07 * VAR07 + VAR14 = y * y * y * y * y + VAR15 = y * y * y * y + VAR16 = y * y * y + VAR17 = y * y + VAR09 = VAR14 * VAR14 + VAR10 = VAR14 * VAR15 + VAR11 = VAR15 * VAR15 + VAR12 = VAR15 * VAR16 + VAR13 = VAR16 * VAR16 + VAR23 = z * z * z * z * z + VAR24 = z * z * z * z + VAR25 = z * z * z + VAR26 = z * z + VAR18 = VAR23 * VAR23 + VAR19 = VAR23 * VAR24 + VAR20 = VAR24 * VAR24 + VAR21 = VAR24 * VAR25 + VAR22 = VAR25 * VAR25 + # -------------------- kernel implementations + Y00 = ( + CONST023 * VAR01 * z + + CONST023 * VAR19 * x + + CONST074 * VAR05 * VAR23 + + CONST080 * VAR03 * VAR25 + + CONST080 * VAR07 * VAR21 + ) + Y01 = y * ( + CONST002 * VAR07 * VAR22 + + CONST010 * VAR01 + + CONST045 * VAR03 * VAR26 + + CONST061 * VAR20 * x + + CONST181 * VAR05 * VAR24 + ) + Y02 = ( + CONST013 * VAR01 * z + + CONST054 * VAR07 * VAR21 + + CONST151 * VAR03 * VAR25 + + CONST174 * VAR19 * x + + VAR17 + * ( + -CONST039 * VAR05 * VAR25 + + CONST039 * VAR07 * VAR23 + + CONST099 * VAR03 * z + - CONST099 * VAR21 * x + ) + ) + Y03 = VAR16 * ( + CONST024 * VAR22 * x + + CONST051 * VAR05 * VAR26 + + CONST133 * VAR07 * VAR24 + + CONST159 * VAR03 + ) + y * ( + CONST095 * VAR03 * VAR26 + - CONST119 * VAR05 * VAR24 + + CONST145 * VAR07 * VAR22 + + CONST148 * VAR20 * x + - CONST178 * VAR01 + ) + Y04 = ( + CONST009 * VAR01 * z + + VAR03 * (CONST076 * VAR17 * z + CONST175 * VAR25) + + VAR05 * (CONST106 * VAR15 * z + CONST107 * VAR17 * VAR25 + CONST167 * VAR23) + + VAR07 + * (CONST106 * VAR17 * VAR23 + CONST162 * VAR15 * VAR25 + CONST175 * VAR21) + + x * (CONST009 * VAR19 + CONST075 * VAR17 * VAR21 + CONST106 * VAR15 * VAR23) + ) + Y05 = ( + VAR14 * (CONST077 * VAR05 + CONST112 * VAR07 * VAR26 + CONST135 * VAR24 * x) + + VAR16 + * ( + -CONST114 * VAR07 * VAR24 + + CONST114 * VAR22 * x + + CONST117 * VAR05 * VAR26 + + CONST134 * VAR03 + ) + + y + * ( + CONST014 * VAR01 + + CONST047 * VAR20 * x + + CONST116 * VAR05 * VAR24 + + CONST141 * VAR03 * VAR26 + ) + ) + Y06 = ( + CONST005 * VAR01 * z + + VAR03 * (CONST011 * VAR25 + CONST102 * VAR17 * z) + + VAR05 * (CONST101 * VAR17 * VAR25 - CONST152 * VAR15 * z) + + VAR07 * (CONST108 * VAR17 * VAR23 + CONST109 * VAR13 * z + CONST176 * VAR21) + + x + * ( + CONST108 * VAR17 * VAR21 + - CONST109 * VAR13 * VAR25 + + CONST152 * VAR15 * VAR23 + + CONST179 * VAR19 + ) + ) + Y07 = ( + VAR12 * (-CONST041 * VAR26 * x + CONST132 * VAR07) + + VAR14 * (-CONST062 * VAR05 + CONST103 * VAR07 * VAR26 + CONST126 * VAR24 * x) + + VAR16 + * ( + CONST083 * VAR05 * VAR26 + + CONST121 * VAR03 + - CONST158 * VAR22 * x + + CONST169 * VAR07 * VAR24 + ) + + y + * ( + CONST015 * VAR01 + + CONST138 * VAR07 * VAR22 + + CONST149 * VAR05 * VAR24 + + CONST168 * VAR20 * x + ) + ) + Y08 = ( + -CONST182 * VAR01 * z + + VAR03 * (CONST016 * VAR25 + CONST129 * VAR17 * z) + + VAR05 * (CONST017 * VAR23 + CONST032 * VAR17 * VAR25 + CONST105 * VAR15 * z) + + VAR07 + * ( + CONST008 * VAR15 * VAR25 + + CONST016 * VAR21 + + CONST032 * VAR17 * VAR23 + + CONST085 * VAR13 * z + ) + + x + * ( + CONST078 * VAR11 * z + + CONST085 * VAR13 * VAR25 + + CONST105 * VAR15 * VAR23 + + CONST129 * VAR17 * VAR21 + - CONST182 * VAR19 + ) + ) + Y09 = ( + CONST018 * VAR01 * y + + VAR03 * (CONST042 * VAR26 * y + CONST113 * VAR16) + + VAR05 * (CONST020 * VAR14 + CONST056 * VAR24 * y + CONST128 * VAR16 * VAR26) + + VAR07 + * ( + CONST031 * VAR14 * VAR26 + + CONST042 * VAR22 * y + + CONST088 * VAR12 + + CONST127 * VAR16 * VAR24 + ) + + x + * ( + CONST018 * VAR20 * y + + CONST020 * VAR14 * VAR24 + + CONST026 * VAR10 + + CONST088 * VAR12 * VAR26 + + CONST113 * VAR16 * VAR22 + ) + ) + Y10 = ( + CONST004 * VAR09 + + CONST037 * VAR17 * VAR20 + + CONST093 * VAR15 * VAR22 + + CONST131 * VAR13 * VAR24 + + CONST147 * VAR11 * VAR26 + + CONST184 * VAR00 + + CONST184 * VAR18 + + VAR02 * (CONST036 * VAR17 + CONST059 * VAR26) + + VAR04 * (CONST092 * VAR15 + CONST098 * VAR17 * VAR26 + CONST177 * VAR24) + + VAR06 + * ( + CONST063 * VAR15 * VAR26 + + CONST125 * VAR17 * VAR24 + + CONST131 * VAR13 + + CONST177 * VAR22 + ) + + VAR08 + * ( + CONST063 * VAR15 * VAR24 + + CONST090 * VAR13 * VAR26 + + CONST097 * VAR17 * VAR22 + + CONST146 * VAR11 + + CONST180 * VAR20 + ) + ) + Y11 = ( + CONST018 * VAR19 * y + + VAR21 * (CONST042 * VAR08 * y + CONST113 * VAR16) + + VAR23 * (CONST020 * VAR14 + CONST056 * VAR06 * y + CONST128 * VAR08 * VAR16) + + VAR25 + * ( + CONST031 * VAR08 * VAR14 + + CONST042 * VAR04 * y + + CONST091 * VAR12 + + CONST127 * VAR06 * VAR16 + ) + + z + * ( + CONST018 * VAR02 * y + + CONST020 * VAR06 * VAR14 + + CONST027 * VAR10 + + CONST089 * VAR08 * VAR12 + + CONST113 * VAR04 * VAR16 + ) + ) + Y12 = ( + CONST057 * VAR13 * VAR24 + - CONST066 * VAR15 * VAR22 + + CONST081 * VAR00 + - CONST081 * VAR18 + - CONST153 * VAR11 * VAR26 + + CONST160 * VAR17 * VAR20 + + VAR02 * (CONST030 * VAR26 + CONST046 * VAR17) + + VAR04 * (CONST066 * VAR15 - CONST129 * VAR17 * VAR26 + CONST182 * VAR24) + + VAR06 * (CONST065 * VAR15 * VAR26 + CONST150 * VAR13 - CONST182 * VAR22) + + VAR08 + * ( + CONST006 * VAR20 + - CONST066 * VAR15 * VAR24 + + CONST130 * VAR17 * VAR22 + + CONST153 * VAR11 + ) + ) + Y13 = ( + VAR12 * (CONST041 * VAR08 * z + CONST071 * VAR25) + + VAR14 * (CONST062 * VAR23 + CONST107 * VAR08 * VAR25 - CONST126 * VAR06 * z) + + VAR16 + * ( + CONST029 * VAR06 * VAR25 + - CONST121 * VAR21 + + CONST122 * VAR08 * VAR23 + + CONST158 * VAR04 * z + ) + + y + * ( + -CONST138 * VAR04 * VAR25 + - CONST149 * VAR06 * VAR23 + - CONST168 * VAR02 * z + + CONST173 * VAR19 + ) + ) + Y14 = ( + CONST044 * VAR17 * VAR20 + + CONST079 * VAR13 * VAR24 + + CONST101 * VAR15 * VAR22 + + CONST110 * VAR00 + + CONST120 * VAR18 + + VAR02 * (CONST043 * VAR17 + CONST070 * VAR26) + + VAR04 * (CONST021 * VAR24 + CONST101 * VAR15 + CONST101 * VAR17 * VAR26) + + VAR06 + * ( + CONST021 * VAR22 + + CONST079 * VAR13 + + CONST123 * VAR17 * VAR24 + + CONST137 * VAR15 * VAR26 + ) + + VAR08 + * ( + CONST007 * VAR20 + + CONST101 * VAR17 * VAR22 + + CONST136 * VAR15 * VAR24 + + CONST152 * VAR13 * VAR26 + ) + ) + Y15 = ( + VAR14 * (CONST077 * VAR23 + CONST112 * VAR08 * VAR25 + CONST135 * VAR06 * z) + + VAR16 + * ( + CONST114 * VAR04 * z + - CONST114 * VAR06 * VAR25 + + CONST118 * VAR08 * VAR23 + + CONST134 * VAR21 + ) + + y + * ( + CONST014 * VAR19 + + CONST047 * VAR02 * z + + CONST116 * VAR06 * VAR23 + + CONST142 * VAR08 * VAR21 + ) + ) + Y16 = ( + CONST001 * VAR18 + + CONST094 * VAR00 + - CONST139 * VAR15 * VAR22 + + CONST166 * VAR17 * VAR20 + + VAR02 * (CONST019 * VAR26 - CONST166 * VAR17) + + VAR04 * (CONST022 * VAR24 + CONST104 * VAR17 * VAR26 + CONST139 * VAR15) + + VAR06 * (-CONST049 * VAR15 * VAR26 + CONST171 * VAR22) + + VAR08 + * (CONST049 * VAR15 * VAR24 + CONST106 * VAR17 * VAR22 + CONST172 * VAR20) + ) + Y17 = VAR16 * ( + CONST050 * VAR21 + - CONST133 * VAR06 * VAR25 + + CONST154 * VAR08 * VAR23 + + CONST170 * VAR04 * z + ) + y * ( + CONST058 * VAR02 * z + + CONST060 * VAR04 * VAR25 + - CONST095 * VAR08 * VAR21 + + CONST119 * VAR06 * VAR23 + + CONST178 * VAR19 + ) + Y18 = ( + CONST034 * VAR02 * VAR26 + + CONST035 * VAR08 * VAR20 + + CONST082 * VAR00 + + CONST087 * VAR18 + + CONST155 * VAR04 * VAR24 + + CONST156 * VAR06 * VAR22 + + VAR17 + * ( + CONST025 * VAR04 * VAR26 + + CONST025 * VAR08 * VAR22 + + CONST028 * VAR02 + + CONST028 * VAR20 + + CONST161 * VAR06 * VAR24 + ) + ) + Y19 = y * ( + CONST002 * VAR04 * VAR25 + + CONST010 * VAR19 + + CONST045 * VAR08 * VAR21 + + CONST061 * VAR02 * z + + CONST181 * VAR06 * VAR23 + ) + Y20 = ( + -CONST143 * VAR02 * VAR26 + + CONST143 * VAR08 * VAR20 + + CONST165 * VAR04 * VAR24 + - CONST165 * VAR06 * VAR22 + + CONST183 * VAR00 + - CONST183 * VAR18 + ) + output_stride = 21 # [2l + 1] + output_striding = tl.arange(0, block_size) * output_stride + output_row_offset = output_striding + (block_size * output_stride * block_id) + tl.store(output_ptr + output_row_offset, Y00, mask=output_row_offset < output_numel) + tl.store( + output_ptr + output_row_offset + 1, + Y01, + mask=output_row_offset + 1 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 2, + Y02, + mask=output_row_offset + 2 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 3, + Y03, + mask=output_row_offset + 3 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 4, + Y04, + mask=output_row_offset + 4 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 5, + Y05, + mask=output_row_offset + 5 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 6, + Y06, + mask=output_row_offset + 6 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 7, + Y07, + mask=output_row_offset + 7 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 8, + Y08, + mask=output_row_offset + 8 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 9, + Y09, + mask=output_row_offset + 9 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 10, + Y10, + mask=output_row_offset + 10 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 11, + Y11, + mask=output_row_offset + 11 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 12, + Y12, + mask=output_row_offset + 12 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 13, + Y13, + mask=output_row_offset + 13 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 14, + Y14, + mask=output_row_offset + 14 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 15, + Y15, + mask=output_row_offset + 15 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 16, + Y16, + mask=output_row_offset + 16 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 17, + Y17, + mask=output_row_offset + 17 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 18, + Y18, + mask=output_row_offset + 18 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 19, + Y19, + mask=output_row_offset + 19 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 20, + Y20, + mask=output_row_offset + 20 < output_numel, + ) + + +@triton.jit +def tenth_order_bwd( + coord_ptr: tl.tensor, + coord_grad_ptr: tl.tensor, + sph_grad_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, +): + # work out the row offsets + block_id = tl.program_id(0) + # these are hardcoded because they are predetermined; + coord_stride = 3 + coord_striding = tl.arange(0, block_size) * coord_stride + # as the name suggests, this is effectively every node/atom + coord_row_offset = coord_striding + (block_size * coord_stride * block_id) + x = tl.load(coord_ptr + coord_row_offset, mask=coord_row_offset < coord_numel) + y = tl.load( + coord_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + z = tl.load( + coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + output_stride = 21 # [2l + 1] + output_striding = tl.arange(0, block_size) * output_stride + output_row_offset = output_striding + (block_size * output_stride * block_id) + # load in gradients w.r.t. spherical harmonic projections + g_0 = tl.load( + sph_grad_ptr + output_row_offset, mask=output_row_offset < output_numel + ) + g_1 = tl.load( + sph_grad_ptr + output_row_offset + 1, mask=output_row_offset + 1 < output_numel + ) + g_2 = tl.load( + sph_grad_ptr + output_row_offset + 2, mask=output_row_offset + 2 < output_numel + ) + g_3 = tl.load( + sph_grad_ptr + output_row_offset + 3, mask=output_row_offset + 3 < output_numel + ) + g_4 = tl.load( + sph_grad_ptr + output_row_offset + 4, mask=output_row_offset + 4 < output_numel + ) + g_5 = tl.load( + sph_grad_ptr + output_row_offset + 5, mask=output_row_offset + 5 < output_numel + ) + g_6 = tl.load( + sph_grad_ptr + output_row_offset + 6, mask=output_row_offset + 6 < output_numel + ) + g_7 = tl.load( + sph_grad_ptr + output_row_offset + 7, mask=output_row_offset + 7 < output_numel + ) + g_8 = tl.load( + sph_grad_ptr + output_row_offset + 8, mask=output_row_offset + 8 < output_numel + ) + g_9 = tl.load( + sph_grad_ptr + output_row_offset + 9, mask=output_row_offset + 9 < output_numel + ) + g_10 = tl.load( + sph_grad_ptr + output_row_offset + 10, + mask=output_row_offset + 10 < output_numel, + ) + g_11 = tl.load( + sph_grad_ptr + output_row_offset + 11, + mask=output_row_offset + 11 < output_numel, + ) + g_12 = tl.load( + sph_grad_ptr + output_row_offset + 12, + mask=output_row_offset + 12 < output_numel, + ) + g_13 = tl.load( + sph_grad_ptr + output_row_offset + 13, + mask=output_row_offset + 13 < output_numel, + ) + g_14 = tl.load( + sph_grad_ptr + output_row_offset + 14, + mask=output_row_offset + 14 < output_numel, + ) + g_15 = tl.load( + sph_grad_ptr + output_row_offset + 15, + mask=output_row_offset + 15 < output_numel, + ) + g_16 = tl.load( + sph_grad_ptr + output_row_offset + 16, + mask=output_row_offset + 16 < output_numel, + ) + g_17 = tl.load( + sph_grad_ptr + output_row_offset + 17, + mask=output_row_offset + 17 < output_numel, + ) + g_18 = tl.load( + sph_grad_ptr + output_row_offset + 18, + mask=output_row_offset + 18 < output_numel, + ) + g_19 = tl.load( + sph_grad_ptr + output_row_offset + 19, + mask=output_row_offset + 19 < output_numel, + ) + g_20 = tl.load( + sph_grad_ptr + output_row_offset + 20, + mask=output_row_offset + 20 < output_numel, + ) + # -------------------- variable and constant definitions + CONST000 = 2.00000000000000 + CONST002 = 4.00000000000000 + CONST003 = 4.82870805793735 + CONST004 = 6.00000000000000 + CONST005 = 4.97432985632550 + CONST006 = 8.00000000000000 + CONST007 = 4.97432985632550 + CONST008 = 10.5521471197994 + CONST009 = 3.00000000000000 + CONST010 = 5.00000000000000 + CONST011 = 7.00000000000000 + CONST012 = 13.2648796168680 + CONST014 = 12.1657520803952 + CONST015 = 16.7271353825295 + CONST016 = -2030.35546709287 + CONST017 = 19.3148322317494 + CONST018 = -6131.53904851919 + CONST019 = 22.8629854262320 + CONST020 = 23.2135393295190 + CONST021 = 24.6216766128653 + CONST022 = 17.5869118663323 + CONST024 = 28.9722483476241 + CONST025 = 33.9852909359329 + CONST026 = 33.9852909359329 + CONST027 = 35.5238206489124 + CONST028 = 6180.74631415980 + CONST029 = 38.6296644634988 + CONST030 = 39.7946388506040 + CONST031 = 38.6296644634988 + CONST032 = -2007.25624590353 + CONST033 = -2007.25624590353 + CONST034 = 45.8257569495584 + CONST035 = 45.7259708524640 + CONST037 = 56.3871618715269 + CONST038 = 56.2781179722634 + CONST039 = -1989.33395633909 + CONST040 = -1989.33395633909 + CONST041 = 59.6919582759060 + CONST042 = 66.9085415301178 + CONST043 = 69.6406179885570 + CONST044 = -8121.42186837148 + CONST045 = 77.2593289269976 + CONST046 = 78.6510608948335 + CONST047 = -1969.73412902922 + CONST048 = 77.3468749368712 + CONST049 = -1969.73412902922 + CONST050 = -9.65741611587469 + CONST051 = 90.1358837481638 + CONST053 = 94.9693240781945 + CONST055 = 96.5741611587469 + CONST057 = 98.4867064514610 + CONST058 = 100.362812295177 + CONST059 = 101.517773354644 + CONST060 = 106.571461946737 + CONST061 = 106.571461946737 + CONST062 = 109.491768723557 + CONST063 = 109.491768723557 + CONST064 = 112.774323743054 + CONST065 = 112.774323743054 + CONST067 = 2165.26701586663 + CONST070 = 133.817083060236 + CONST071 = 139.281235977114 + CONST072 = 139.281235977114 + CONST073 = 141.571909610700 + CONST074 = 142.095282595650 + CONST075 = 147.730059677192 + CONST076 = 150.544218442765 + CONST077 = 150.074981259369 + CONST079 = 2202.22970505534 + CONST080 = -3939.46825805844 + CONST081 = -5968.00186901728 + CONST082 = 176.592751833137 + CONST083 = 176.178376404427 + CONST085 = 185.708314636152 + CONST087 = 196.973412902922 + CONST089 = 225.548647486108 + CONST090 = 225.548647486108 + CONST091 = 4330.53403173327 + CONST093 = 244.831037842559 + CONST094 = -1804.38917988886 + CONST095 = -1804.38917988886 + CONST097 = 2317.77986780993 + CONST098 = 278.562471954228 + CONST100 = 284.190565191299 + CONST101 = -1761.78376404427 + CONST103 = -9946.66978169547 + CONST104 = 9.94865971265100 + CONST108 = -7878.93651611688 + CONST111 = 338.322971229162 + CONST112 = 360.877835977772 + CONST114 = -1671.37483172537 + CONST116 = 2436.42656051144 + CONST119 = 393.946825805844 + CONST120 = -1648.19901710928 + CONST121 = 401.451249180707 + CONST122 = 406.071093418574 + CONST123 = 412.049754277320 + CONST125 = -1624.28437367430 + CONST126 = 426.285847786949 + CONST127 = 426.285847786948 + CONST128 = 2486.66744542387 + CONST130 = 451.097294972216 + CONST131 = 451.097294972216 + CONST132 = 451.097294972215 + CONST133 = 6606.68911516602 + CONST134 = 6606.68911516602 + CONST135 = -1575.78730322338 + CONST136 = -1575.78730322338 + CONST137 = -3608.77835977772 + CONST139 = -1545.18657853995 + CONST140 = -1545.18657853995 + CONST142 = 535.268332240943 + CONST143 = 4635.55973561985 + CONST144 = 541.428124558099 + CONST145 = -3545.52143225260 + CONST146 = 557.124943908456 + CONST147 = -3523.56752808854 + CONST148 = -5571.24943908456 + CONST151 = 15.7883647328499 + CONST153 = 2642.67564606641 + CONST154 = 2642.67564606641 + CONST155 = 2676.34166120471 + CONST156 = 629.208487158668 + CONST158 = 4727.36190967013 + CONST159 = -1392.81235977114 + CONST160 = -1390.66792068596 + CONST162 = 663.111318779698 + CONST163 = -3427.63452979582 + CONST164 = -1378.81389032045 + CONST165 = 676.645942458323 + CONST167 = -1338.17083060236 + CONST168 = -1338.17083060236 + CONST169 = 721.755671955545 + CONST171 = 2785.62471954228 + CONST173 = 772.593289269975 + CONST175 = 787.893651611688 + CONST176 = 787.893651611688 + CONST177 = 6.63243980843400 + CONST178 = 812.142186837148 + CONST180 = -1218.21328025572 + CONST181 = -1202.92611992591 + CONST182 = -1202.92611992591 + CONST183 = -3248.56874734859 + CONST184 = -3248.56874734859 + CONST185 = -5285.35129213281 + CONST186 = -1181.84047741753 + CONST190 = 2936.30627340712 + CONST192 = 2954.60119354383 + CONST193 = -1114.24988781691 + CONST194 = -16.5810995210850 + CONST195 = -1101.11485252767 + CONST196 = -1081.63060497797 + CONST197 = 15.7302121789667 + CONST199 = 984.867064514610 + CONST202 = -1027.70719569249 + CONST203 = -1021.92317475320 + CONST204 = -3065.76952425960 + CONST205 = -1015.17773354644 + CONST206 = 3090.37315707990 + CONST207 = -994.666978169547 + CONST208 = -984.867064514610 + CONST209 = -984.867064514610 + CONST210 = -979.324151370235 + CONST211 = 1070.53666448189 + CONST212 = -979.324151370235 + CONST213 = 3151.57460644675 + CONST216 = -927.111947123971 + CONST217 = -927.111947123970 + CONST218 = -5.63871618715269 + CONST219 = -2954.60119354383 + CONST220 = -902.194589944431 + CONST221 = -900.449887556215 + CONST222 = -880.891882022136 + CONST223 = -880.891882022136 + CONST224 = -875.934149788456 + CONST226 = -4944.59705132784 + CONST228 = 3248.56874734859 + CONST229 = -835.687415862684 + CONST230 = 1218.21328025572 + CONST231 = -824.099508554641 + CONST232 = -824.863625092051 + CONST233 = -824.863625092051 + CONST234 = -812.142186837148 + CONST235 = 5352.68332240943 + CONST236 = -787.893651611688 + CONST237 = -787.893651611688 + CONST238 = -772.593289269976 + CONST239 = -742.833258544608 + CONST240 = -2785.62471954228 + CONST241 = -734.076568351780 + CONST242 = 1321.33782303320 + CONST243 = 1321.33782303320 + CONST244 = -706.371007332549 + CONST245 = -696.406179885570 + CONST246 = 1353.29188491665 + CONST247 = -675.337415667161 + CONST248 = -675.337415667161 + CONST250 = 3427.63452979582 + CONST251 = -669.085415301178 + CONST252 = -669.085415301178 + CONST253 = -669.085415301178 + CONST255 = -663.111318779698 + CONST256 = -2707.14062279049 + CONST258 = 1392.81235977114 + CONST259 = 1412.74201466510 + CONST260 = -4727.36190967013 + CONST261 = -2676.34166120471 + CONST262 = -618.074631415980 + CONST263 = -611.735236846792 + CONST264 = -611.735236846792 + CONST265 = 1443.51134391109 + CONST266 = -590.920238708766 + CONST267 = -10828.5624911620 + CONST268 = -580.101562026534 + CONST269 = -2626.31217203896 + CONST272 = 5571.24943908456 + CONST273 = -12.8765548211663 + CONST274 = -557.124943908456 + CONST275 = -557.124943908456 + CONST277 = -541.428124558099 + CONST278 = -6685.49932690147 + CONST279 = 7664.42381064899 + CONST280 = -525.262434407792 + CONST281 = 1532.88476212980 + CONST283 = -497.333489084773 + CONST284 = -497.333489084773 + CONST285 = -492.433532257305 + CONST286 = 1575.78730322338 + CONST287 = 1575.78730322338 + CONST288 = -463.555973561985 + CONST289 = -450.224943778107 + CONST290 = -450.224943778107 + CONST291 = -450.224943778108 + CONST292 = -437.967074894228 + CONST293 = -2472.29852566392 + CONST294 = 1624.28437367430 + CONST295 = -2472.29852566392 + CONST296 = -406.071093418574 + CONST297 = -393.946825805844 + CONST298 = -393.946825805844 + CONST299 = -2436.42656051144 + CONST300 = -386.296644634988 + CONST301 = -386.296644634988 + CONST302 = -4456.99955126765 + CONST303 = -337.668707833581 + CONST304 = -337.668707833581 + CONST305 = -331.555659389849 + CONST306 = -331.555659389849 + CONST307 = -2363.68095483506 + CONST309 = -309.037315707990 + CONST310 = -4404.45941011068 + CONST311 = -309.037315707990 + CONST312 = -305.867618423396 + CONST313 = -305.867618423396 + CONST314 = -305.867618423396 + CONST315 = -300.731529981477 + CONST316 = 9946.66978169547 + CONST318 = -290.050781013267 + CONST319 = -284.190565191299 + CONST320 = -278.562471954228 + CONST321 = -278.562471954228 + CONST322 = -2317.77986780993 + CONST323 = -10505.2486881558 + CONST324 = -251.683394863467 + CONST325 = -251.683394863467 + CONST326 = -246.216766128653 + CONST327 = -244.831037842559 + CONST328 = -2285.08968653055 + CONST329 = -2285.08968653055 + CONST330 = 3862.96644634988 + CONST331 = -223.028471767059 + CONST332 = -220.222970505534 + CONST333 = -206.215906273013 + CONST334 = -203.035546709287 + CONST335 = -196.973412902922 + CONST336 = -196.973412902922 + CONST337 = -182.903883409856 + CONST338 = -2228.49977563382 + CONST340 = 16.4144510752435 + CONST341 = 3939.46825805844 + CONST342 = 3939.46825805844 + CONST343 = -154.518657853995 + CONST344 = -154.518657853995 + CONST345 = -150.074981259369 + CONST346 = -147.730059677191 + CONST347 = -146.815313670356 + CONST348 = -142.095282595650 + CONST349 = -131.315608601948 + CONST350 = -131.315608601948 + CONST351 = -130.522851455970 + CONST352 = -125.841697431734 + CONST353 = -125.841697431734 + CONST354 = -112.556235944527 + CONST355 = -103.107953136506 + CONST356 = -101.517773354644 + CONST357 = 1949.93730367960 + CONST358 = -98.4867064514610 + CONST359 = -98.4867064514610 + CONST360 = -2141.07332896377 + CONST361 = -2141.07332896377 + CONST362 = -92.8541573180760 + CONST363 = -88.2963759165686 + CONST366 = -77.3468749368713 + CONST367 = 8121.42186837148 + CONST369 = -67.6645942458323 + CONST372 = -59.6919582759060 + CONST373 = -49.2433532257305 + CONST374 = -49.2433532257305 + CONST375 = -45.1097294972216 + CONST376 = -45.1097294972216 + CONST377 = -42.2085884791976 + CONST378 = -27.2034486491732 + CONST379 = -24.6216766128653 + CONST380 = -22.8629854262320 + CONST381 = -19.7354559160624 + CONST383 = -17.5869118663323 + CONST384 = -16.4144510752435 + CONST385 = -16.0956935264578 + CONST386 = -14.5025390506634 + CONST388 = -16.5810995210850 + CONST389 = -15.7883647328499 + CONST390 = -14.0695294930659 + CONST391 = -11.2774323743054 + CONST392 = -11.2774323743054 + CONST393 = -13.2648796168680 + CONST394 = -6.63243980843400 + CONST395 = -5.63871618715269 + CONST396 = -4.82870805793735 + CONST397 = -3.21913870529156 + CONST398 = -11.2774323743054 + VAR05 = x * x * x * x * x + VAR06 = x * x * x * x + VAR07 = x * x * x + VAR08 = x * x + VAR01 = VAR05 * VAR06 + VAR02 = VAR06 * VAR06 + VAR03 = VAR06 * VAR07 + VAR04 = VAR07 * VAR07 + VAR14 = y * y * y * y * y + VAR15 = y * y * y * y + VAR16 = y * y * y + VAR17 = y * y + VAR10 = VAR14 * VAR15 + VAR11 = VAR15 * VAR15 + VAR12 = VAR15 * VAR16 + VAR13 = VAR16 * VAR16 + VAR23 = z * z * z * z * z + VAR24 = z * z * z * z + VAR25 = z * z * z + VAR26 = z * z + VAR19 = VAR23 * VAR24 + VAR20 = VAR24 * VAR24 + VAR21 = VAR24 * VAR25 + VAR22 = VAR25 * VAR25 + # -------------------- kernel implementations + g_x = ( + g_0 + * ( + CONST093 * VAR02 * z + + CONST210 * VAR08 * VAR21 + + CONST250 * VAR06 * VAR23 + + CONST328 * VAR04 * VAR25 + - CONST378 * VAR19 + ) + + g_1 + * y + * ( + CONST062 * VAR20 + + CONST063 * VAR02 + + CONST204 * VAR04 * VAR26 + + CONST204 * VAR08 * VAR22 + + CONST279 * VAR06 * VAR24 + ) + + g_10 + * ( + CONST000 + * x + * ( + CONST089 * VAR17 * VAR22 + + CONST169 * VAR13 * VAR26 + + CONST220 * VAR15 * VAR24 + + CONST355 * VAR11 + + CONST395 * VAR20 + ) + + CONST002 + * VAR07 + * ( + CONST111 * VAR17 * VAR24 + + CONST112 * VAR13 + + CONST220 * VAR15 * VAR26 + + CONST392 * VAR22 + ) + + CONST004 + * VAR05 + * (CONST090 * VAR17 * VAR26 + CONST315 * VAR15 + CONST392 * VAR24) + + CONST006 * VAR03 * (CONST037 * VAR17 + CONST218 * VAR26) + + CONST391 * VAR01 + ) + + g_11 + * ( + CONST070 * VAR21 * x * y + + VAR23 * (CONST121 * VAR07 * y + CONST168 * VAR16 * x) + + VAR25 + * (CONST121 * VAR05 * y + CONST261 * VAR07 * VAR16 - CONST361 * VAR14 * x) + + z + * ( + CONST070 * VAR03 * y + + CONST167 * VAR05 * VAR16 + + CONST263 * VAR12 * x + - CONST361 * VAR07 * VAR14 + ) + ) + + g_12 + * ( + CONST000 + * x + * ( + CONST003 * VAR20 + - CONST301 * VAR15 * VAR24 + + CONST343 * VAR17 * VAR22 + + CONST363 * VAR11 + ) + + CONST002 + * VAR07 + * (CONST123 * VAR13 + CONST300 * VAR15 * VAR26 - CONST397 * VAR22) + + CONST004 + * VAR05 + * (CONST301 * VAR15 - CONST344 * VAR17 * VAR26 + CONST397 * VAR24) + + CONST006 * VAR03 * (CONST045 * VAR17 + CONST396 * VAR26) + + CONST385 * VAR01 + ) + + g_13 + * ( + CONST221 * VAR12 * x * z + + VAR14 * (-CONST260 * VAR07 * z + CONST286 * VAR25 * x) + + VAR16 + * (CONST080 * VAR07 * VAR25 + CONST145 * VAR05 * z + CONST297 * VAR23 * x) + + y + * ( + -CONST237 * VAR05 * VAR25 + - CONST297 * VAR07 * VAR23 + - CONST298 * VAR03 * z + ) + ) + + g_14 + * ( + CONST000 + * x + * ( + CONST005 * VAR20 + - CONST159 * VAR15 * VAR24 + + CONST193 * VAR13 * VAR26 + + CONST320 * VAR17 * VAR22 + ) + + CONST002 + * VAR07 + * ( + CONST020 * VAR22 + + CONST085 * VAR13 + + CONST245 * VAR17 * VAR24 + + CONST258 * VAR15 * VAR26 + ) + + CONST004 + * VAR05 + * (CONST020 * VAR24 + CONST320 * VAR15 + CONST320 * VAR17 * VAR26) + + CONST006 * VAR03 * (CONST007 * VAR26 + CONST043 * VAR17) + + CONST388 * VAR01 + ) + + g_15 + * ( + VAR14 * (-CONST147 * VAR07 * z + CONST147 * VAR25 * x) + + VAR16 + * (CONST153 * VAR23 * x + CONST190 * VAR07 * VAR25 + CONST310 * VAR05 * z) + + y + * (CONST156 * VAR03 * z + CONST222 * VAR07 * VAR23 + CONST324 * VAR21 * x) + ) + + g_16 + * ( + CONST000 + * x + * (CONST047 * VAR15 * VAR24 + CONST175 * VAR17 * VAR22 + CONST380 * VAR20) + + CONST002 * VAR07 * (-CONST047 * VAR15 * VAR26 + CONST379 * VAR22) + + CONST004 + * VAR05 + * (CONST021 * VAR24 + CONST236 * VAR17 * VAR26 + CONST349 * VAR15) + + CONST006 * VAR03 * (CONST019 * VAR26 + CONST038 * VAR17) + + CONST383 * VAR01 + ) + + g_17 + * ( + VAR16 + * (CONST183 * VAR23 * x + CONST184 * VAR05 * z - CONST267 * VAR07 * VAR25) + + y + * ( + CONST178 * VAR03 * z + + CONST234 * VAR07 * VAR23 + - CONST268 * VAR21 * x + + CONST299 * VAR05 * VAR25 + ) + ) + + g_18 + * ( + CONST060 * VAR20 * x + + CONST126 * VAR03 * VAR26 + + CONST283 * VAR05 * VAR24 + + CONST305 * VAR07 * VAR22 + + CONST381 * VAR01 + + VAR17 + * ( + CONST039 * VAR22 * x + + CONST081 * VAR05 * VAR26 + + CONST316 * VAR07 * VAR24 + - CONST319 * VAR03 + ) + ) + + g_19 + * y + * ( + CONST018 * VAR05 * VAR25 + - CONST018 * VAR07 * VAR23 + - CONST224 * VAR03 * z + + CONST224 * VAR21 * x + ) + + g_2 + * ( + CONST074 * VAR02 * z + + CONST100 * VAR08 * VAR21 + + CONST255 * VAR04 * VAR25 + + CONST389 * VAR19 + + VAR17 + * ( + CONST040 * VAR04 * z + + CONST081 * VAR08 * VAR23 + - CONST103 * VAR06 * VAR25 + - CONST319 * VAR21 + ) + ) + + g_20 + * ( + CONST163 * VAR05 * VAR24 + - CONST212 * VAR03 * VAR26 + + CONST327 * VAR20 * x + - CONST329 * VAR07 * VAR22 + + CONST378 * VAR01 + ) + + g_3 + * ( + VAR16 + * ( + CONST044 * VAR08 * VAR24 + + CONST144 * VAR22 + + CONST277 * VAR04 + + CONST367 * VAR06 * VAR26 + ) + + y + * ( + CONST016 * VAR04 * VAR26 + - CONST205 * VAR06 * VAR24 + + CONST230 * VAR08 * VAR22 + - CONST351 * VAR02 + + CONST356 * VAR20 + ) + ) + + g_4 + * ( + CONST008 * VAR19 + + CONST009 + * VAR08 + * (CONST175 * VAR17 * VAR23 + CONST269 * VAR15 * VAR25 + CONST390 * VAR21) + + CONST010 + * VAR06 + * (CONST175 * VAR15 * z + CONST176 * VAR17 * VAR25 + CONST373 * VAR23) + + CONST011 * VAR04 * (CONST303 * VAR17 * z + CONST390 * VAR25) + + CONST053 * VAR02 * z + + CONST175 * VAR15 * VAR23 + + CONST304 * VAR17 * VAR21 + ) + + g_5 + * ( + VAR14 * (CONST185 * VAR08 * VAR26 - CONST222 * VAR06 - CONST223 * VAR24) + + VAR16 + * ( + CONST079 * VAR08 * VAR24 + + CONST133 * VAR06 * VAR26 + + CONST202 * VAR04 + + CONST241 * VAR22 + ) + + y + * ( + CONST046 * VAR20 + + CONST073 * VAR02 + + CONST195 * VAR06 * VAR24 + + CONST222 * VAR04 * VAR26 + ) + ) + + g_6 + * ( + CONST009 + * VAR08 + * (CONST098 * VAR17 * VAR23 + CONST239 * VAR13 * z + CONST393 * VAR21) + + CONST010 * VAR06 * (-CONST193 * VAR15 * z + CONST320 * VAR17 * VAR25) + + CONST011 * VAR04 * (CONST012 * VAR25 + CONST321 * VAR17 * z) + + CONST041 * VAR02 * z + + CONST098 * VAR17 * VAR21 + + CONST193 * VAR15 * VAR23 + - CONST239 * VAR13 * VAR25 + + CONST394 * VAR19 + ) + + g_7 + * ( + VAR12 * (CONST289 * VAR08 - CONST290 * VAR26) + + VAR14 * (-CONST049 * VAR06 + CONST186 * VAR24 + CONST307 * VAR08 * VAR26) + + VAR16 + * ( + CONST164 * VAR04 + + CONST192 * VAR08 * VAR24 + + CONST199 * VAR06 * VAR26 + - CONST266 * VAR22 + ) + + y + * ( + CONST075 * VAR02 + + CONST285 * VAR06 * VAR24 + + CONST297 * VAR08 * VAR22 + + CONST374 * VAR20 + ) + ) + + g_8 + * ( + CONST009 + * VAR08 + * ( + -CONST140 * VAR15 * VAR25 + + CONST231 * VAR13 * z + - CONST273 * VAR21 + + CONST288 * VAR17 * VAR23 + ) + + CONST010 + * VAR06 + * (CONST017 * VAR23 + CONST173 * VAR15 * z + CONST288 * VAR17 * VAR25) + + CONST011 * VAR04 * (-CONST273 * VAR25 + CONST344 * VAR17 * z) + + CONST024 * VAR02 * z + + CONST082 * VAR11 * z + + CONST173 * VAR15 * VAR23 + + CONST231 * VAR13 * VAR25 + + CONST344 * VAR17 * VAR21 + - CONST397 * VAR19 + ) + + g_9 + * ( + CONST009 + * VAR08 + * ( + CONST042 * VAR22 * y + + CONST211 * VAR14 * VAR26 + + CONST251 * VAR16 * VAR24 + + CONST312 * VAR12 + ) + + CONST010 + * VAR06 + * (CONST058 * VAR24 * y + CONST142 * VAR14 + CONST252 * VAR16 * VAR26) + + CONST011 * VAR04 * (CONST042 * VAR26 * y + CONST331 * VAR16) + + CONST015 * VAR20 * y + + CONST025 * VAR10 + + CONST076 * VAR02 * y + + CONST142 * VAR14 * VAR24 + + CONST312 * VAR12 * VAR26 + + CONST331 * VAR16 * VAR22 + ) + ) + g_y = ( + CONST000 + * g_18 + * y + * ( + CONST027 * VAR02 + + CONST027 * VAR20 + + CONST128 * VAR06 * VAR24 + + CONST207 * VAR04 * VAR26 + + CONST207 * VAR08 * VAR22 + ) + + CONST000 + * g_2 + * y + * ( + -CONST039 * VAR05 * VAR25 + + CONST039 * VAR07 * VAR23 + + CONST319 * VAR03 * z + - CONST319 * VAR21 * x + ) + + g_1 + * ( + CONST014 * VAR01 + + CONST062 * VAR20 * x + + CONST203 * VAR07 * VAR22 + + CONST281 * VAR05 * VAR24 + + CONST292 * VAR03 * VAR26 + ) + + g_10 + * ( + CONST034 * VAR10 + + CONST064 * VAR20 * y + + CONST065 * VAR02 * y + + CONST067 * VAR14 * VAR24 + + CONST182 * VAR16 * VAR22 + + CONST233 * VAR12 * VAR26 + + VAR04 * (CONST131 * VAR26 * y + CONST181 * VAR16) + + VAR06 + * (CONST067 * VAR14 + CONST137 * VAR16 * VAR26 + CONST165 * VAR24 * y) + + VAR08 + * ( + CONST091 * VAR14 * VAR26 + + CONST130 * VAR22 * y + + CONST137 * VAR16 * VAR24 + + CONST232 * VAR12 + ) + ) + + g_11 + * ( + CONST015 * VAR19 + + VAR21 * (CONST042 * VAR08 + CONST253 * VAR17) + + VAR23 * (CONST033 * VAR08 * VAR17 + CONST058 * VAR06 + CONST155 * VAR15) + + VAR25 + * ( + CONST032 * VAR06 * VAR17 + + CONST042 * VAR04 + + CONST235 * VAR08 * VAR15 + + CONST361 * VAR13 + ) + + z + * ( + CONST015 * VAR02 + + CONST155 * VAR06 * VAR15 + + CONST253 * VAR04 * VAR17 + - CONST312 * VAR11 + + CONST360 * VAR08 * VAR13 + ) + ) + + g_12 + * ( + -CONST140 * VAR16 * VAR22 + - CONST244 * VAR12 * VAR26 + + CONST293 * VAR14 * VAR24 + + CONST343 * VAR20 * y + - CONST344 * VAR02 * y + + VAR04 * (CONST140 * VAR16 - CONST311 * VAR26 * y) + + VAR06 * (CONST139 * VAR16 * VAR26 - CONST295 * VAR14) + + VAR08 + * (-CONST140 * VAR16 * VAR24 + CONST244 * VAR12 + CONST309 * VAR22 * y) + ) + + g_13 + * ( + CONST009 + * VAR17 + * ( + CONST208 * VAR06 * VAR25 + + CONST266 * VAR04 * z + + CONST335 * VAR08 * VAR23 + - CONST336 * VAR21 + ) + + CONST010 + * VAR15 + * (CONST176 * VAR08 * VAR25 - CONST186 * VAR06 * z + CONST298 * VAR23) + + CONST011 * VAR13 * (CONST077 * VAR25 + CONST290 * VAR08 * z) + - CONST350 * VAR04 * VAR25 + - CONST358 * VAR06 * VAR23 + - CONST374 * VAR02 * z + + CONST384 * VAR19 + ) + + g_14 + * ( + CONST071 * VAR02 * y + + CONST072 * VAR20 * y + - CONST193 * VAR14 * VAR24 + + CONST193 * VAR16 * VAR22 + + VAR04 * (CONST193 * VAR16 + CONST274 * VAR26 * y) + + VAR06 + * (CONST159 * VAR24 * y - CONST193 * VAR14 + CONST272 * VAR16 * VAR26) + + VAR08 + * ( + -CONST148 * VAR16 * VAR24 + + CONST274 * VAR22 * y + + CONST278 * VAR14 * VAR26 + ) + ) + + g_15 + * ( + CONST009 + * VAR17 + * ( + CONST241 * VAR04 * z + - CONST241 * VAR06 * VAR25 + + CONST242 * VAR08 * VAR23 + + CONST347 * VAR21 + ) + + CONST010 + * VAR15 + * (CONST083 * VAR23 + CONST101 * VAR08 * VAR25 - CONST223 * VAR06 * z) + + CONST046 * VAR02 * z + + CONST197 * VAR19 + + CONST332 * VAR06 * VAR23 + + CONST352 * VAR08 * VAR21 + ) + + g_16 + * ( + -CONST108 * VAR06 * VAR16 * VAR26 + - CONST280 * VAR16 * VAR22 + - CONST354 * VAR02 * y + + CONST354 * VAR20 * y + + VAR04 * (CONST135 * VAR26 * y + CONST280 * VAR16) + + VAR08 * (CONST108 * VAR16 * VAR24 + CONST287 * VAR22 * y) + ) + + g_17 + * ( + CONST009 + * VAR17 + * ( + CONST048 * VAR21 + + CONST125 * VAR08 * VAR23 + - CONST256 * VAR06 * VAR25 + + CONST277 * VAR04 * z + ) + + CONST059 * VAR02 * z + + CONST296 * VAR04 * VAR25 + - CONST318 * VAR08 * VAR21 + + CONST334 * VAR06 * VAR23 + + CONST386 * VAR19 + ) + + g_19 + * ( + CONST014 * VAR19 + + CONST062 * VAR02 * z + + CONST203 * VAR04 * VAR25 + + CONST281 * VAR06 * VAR23 + + CONST292 * VAR08 * VAR21 + ) + + g_3 + * ( + CONST009 + * VAR17 + * ( + CONST144 * VAR22 * x + + CONST256 * VAR07 * VAR24 + + CONST294 * VAR05 * VAR26 + + CONST366 * VAR03 + ) + + CONST122 * VAR07 * VAR22 + + CONST318 * VAR03 * VAR26 + - CONST334 * VAR05 * VAR24 + + CONST356 * VAR20 * x + - CONST386 * VAR01 + ) + + g_4 + * ( + CONST248 * VAR03 * y * z + + VAR05 * (CONST213 * VAR16 * z + CONST286 * VAR25 * y) + + VAR07 * (CONST287 * VAR23 * y + CONST323 * VAR16 * VAR25) + + x * (CONST213 * VAR16 * VAR23 + CONST247 * VAR21 * y) + ) + + g_5 + * ( + CONST009 + * VAR17 + * ( + -CONST241 * VAR07 * VAR24 + + CONST241 * VAR22 * x + + CONST243 * VAR05 * VAR26 + + CONST347 * VAR03 + ) + + CONST010 + * VAR15 + * (CONST083 * VAR05 + CONST101 * VAR07 * VAR26 - CONST223 * VAR24 * x) + + CONST046 * VAR20 * x + + CONST197 * VAR01 + + CONST332 * VAR05 * VAR24 + + CONST353 * VAR03 * VAR26 + ) + + g_6 + * ( + CONST275 * VAR03 * y * z + + VAR05 * (CONST274 * VAR25 * y - CONST302 * VAR16 * z) + + VAR07 * (CONST146 * VAR23 * y + CONST302 * VAR14 * z) + + x + * ( + CONST146 * VAR21 * y + - CONST302 * VAR14 * VAR25 + + CONST302 * VAR16 * VAR23 + ) + ) + + g_7 + * ( + CONST009 + * VAR17 + * ( + CONST087 * VAR05 * VAR26 + - CONST209 * VAR07 * VAR24 + - CONST266 * VAR22 * x + + CONST336 * VAR03 + ) + + CONST010 + * VAR15 + * (CONST186 * VAR24 * x + CONST237 * VAR07 * VAR26 - CONST298 * VAR05) + + CONST011 * VAR13 * (-CONST290 * VAR26 * x + CONST345 * VAR07) + + CONST340 * VAR01 + + CONST350 * VAR07 * VAR22 + + CONST358 * VAR05 * VAR24 + + CONST374 * VAR20 * x + ) + + g_8 + * ( + CONST311 * VAR03 * y * z + + VAR05 * (CONST206 * VAR16 * z + CONST216 * VAR25 * y) + + VAR07 + * (CONST028 * VAR16 * VAR25 + CONST216 * VAR23 * y + CONST226 * VAR14 * z) + + x + * ( + CONST206 * VAR16 * VAR23 + + CONST226 * VAR14 * VAR25 + + CONST259 * VAR12 * z + + CONST311 * VAR21 * y + ) + ) + + g_9 + * ( + CONST015 * VAR01 + + VAR03 * (CONST042 * VAR26 + CONST253 * VAR17) + + VAR05 * (CONST033 * VAR17 * VAR26 + CONST058 * VAR24 + CONST155 * VAR15) + + VAR07 + * ( + CONST032 * VAR17 * VAR24 + + CONST042 * VAR22 + + CONST235 * VAR15 * VAR26 + + CONST361 * VAR13 + ) + + x + * ( + CONST015 * VAR20 + + CONST155 * VAR15 * VAR24 + + CONST253 * VAR17 * VAR22 + - CONST314 * VAR11 + + CONST361 * VAR13 * VAR26 + ) + ) + ) + g_z = ( + g_0 + * ( + CONST093 * VAR20 * x + + CONST210 * VAR03 * VAR26 + + CONST250 * VAR05 * VAR24 + + CONST328 * VAR07 * VAR22 + - CONST378 * VAR01 + ) + + g_1 + * y + * ( + -CONST018 * VAR05 * VAR25 + + CONST018 * VAR07 * VAR23 + + CONST224 * VAR03 * z + - CONST224 * VAR21 * x + ) + + g_10 + * ( + CONST095 * VAR15 * VAR23 + + CONST132 * VAR17 * VAR21 + + CONST265 * VAR13 * VAR25 + + CONST333 * VAR11 * z + + CONST391 * VAR19 + + CONST398 * VAR02 * z + + VAR04 * (CONST131 * VAR17 * z + CONST376 * VAR25) + + VAR06 + * (CONST094 * VAR15 * z + CONST246 * VAR17 * VAR25 + CONST369 * VAR23) + + VAR08 + * ( + CONST137 * VAR15 * VAR25 + + CONST246 * VAR17 * VAR23 + + CONST265 * VAR13 * z + + CONST375 * VAR21 + ) + ) + + g_11 + * ( + CONST009 + * VAR26 + * ( + CONST042 * VAR04 * y + + CONST211 * VAR08 * VAR14 + + CONST251 * VAR06 * VAR16 + + CONST313 * VAR12 + ) + + CONST010 + * VAR24 + * (CONST058 * VAR06 * y + CONST142 * VAR14 + CONST252 * VAR08 * VAR16) + + CONST011 * VAR22 * (CONST042 * VAR08 * y + CONST331 * VAR16) + + CONST015 * VAR02 * y + + CONST026 * VAR10 + + CONST076 * VAR20 * y + + CONST142 * VAR06 * VAR14 + + CONST314 * VAR08 * VAR12 + + CONST331 * VAR04 * VAR16 + ) + + g_12 + * ( + CONST050 * VAR02 * z + + CONST082 * VAR11 * z + + CONST097 * VAR15 * VAR23 + + CONST120 * VAR13 * VAR25 + + CONST262 * VAR17 * VAR21 + - CONST385 * VAR19 + + VAR04 * (CONST273 * VAR25 - CONST311 * VAR17 * z) + + VAR06 * (CONST017 * VAR23 + CONST238 * VAR15 * z) + + VAR08 + * (CONST029 * VAR21 - CONST140 * VAR15 * VAR25 + CONST217 * VAR17 * VAR23) + ) + + g_13 + * ( + VAR12 * (CONST290 * VAR08 - CONST290 * VAR26) + + VAR14 * (CONST049 * VAR24 - CONST186 * VAR06 - CONST307 * VAR08 * VAR26) + + VAR16 + * ( + -CONST164 * VAR22 + + CONST209 * VAR08 * VAR24 + + CONST219 * VAR06 * VAR26 + + CONST266 * VAR04 + ) + + y + * ( + -CONST285 * VAR06 * VAR24 + - CONST297 * VAR04 * VAR26 + + CONST346 * VAR20 + - CONST374 * VAR02 + ) + ) + + g_14 + * ( + CONST104 * VAR02 * z + + CONST114 * VAR15 * VAR23 + + CONST146 * VAR17 * VAR21 + + CONST194 * VAR19 + - CONST239 * VAR13 * VAR25 + + VAR04 * (CONST274 * VAR17 * z - CONST362 * VAR25) + + VAR06 + * (CONST072 * VAR23 + CONST171 * VAR15 * z + CONST240 * VAR17 * VAR25) + + VAR08 + * ( + CONST030 * VAR21 + + CONST114 * VAR17 * VAR23 + - CONST148 * VAR15 * VAR25 + + CONST338 * VAR13 * z + ) + ) + + g_15 + * ( + VAR14 * (CONST185 * VAR08 * VAR26 - CONST222 * VAR24 - CONST223 * VAR06) + + VAR16 + * ( + CONST079 * VAR06 * VAR26 + + CONST134 * VAR08 * VAR24 + + CONST202 * VAR22 + + CONST241 * VAR04 + ) + + y + * ( + CONST046 * VAR02 + + CONST073 * VAR20 + + CONST195 * VAR06 * VAR24 + + CONST223 * VAR08 * VAR22 + ) + ) + + g_16 + * ( + CONST022 * VAR19 + + CONST035 * VAR02 * z + + CONST175 * VAR15 * VAR23 + + CONST291 * VAR17 * VAR21 + + VAR04 * (CONST057 * VAR25 + CONST135 * VAR17 * z) + + VAR06 * (CONST341 * VAR15 * z + CONST346 * VAR23) + + VAR08 + * (CONST108 * VAR15 * VAR25 + CONST158 * VAR17 * VAR23 + CONST337 * VAR21) + ) + + g_17 + * ( + VAR16 + * ( + -CONST044 * VAR06 * VAR26 + + CONST044 * VAR08 * VAR24 + + CONST144 * VAR22 + + CONST277 * VAR04 + ) + + y + * ( + -CONST016 * VAR08 * VAR22 + + CONST059 * VAR02 + + CONST180 * VAR04 * VAR26 + + CONST205 * VAR06 * VAR24 + + CONST351 * VAR20 + ) + ) + + g_18 + * ( + CONST061 * VAR02 * z + + CONST127 * VAR08 * VAR21 + + CONST284 * VAR06 * VAR23 + + CONST306 * VAR04 * VAR25 + + CONST381 * VAR19 + + VAR17 + * ( + CONST039 * VAR04 * z + + CONST081 * VAR08 * VAR23 + + CONST316 * VAR06 * VAR25 + - CONST319 * VAR21 + ) + ) + + g_19 + * y + * ( + CONST062 * VAR02 + + CONST063 * VAR20 + + CONST204 * VAR04 * VAR26 + + CONST204 * VAR08 * VAR22 + + CONST279 * VAR06 * VAR24 + ) + + g_2 + * ( + CONST151 * VAR01 + + CONST162 * VAR07 * VAR22 + + CONST319 * VAR03 * VAR26 + + CONST348 * VAR20 * x + + VAR17 + * ( + -CONST040 * VAR22 * x + - CONST081 * VAR05 * VAR26 + + CONST103 * VAR07 * VAR24 + + CONST319 * VAR03 + ) + ) + + g_20 + * ( + -CONST163 * VAR06 * VAR23 + + CONST212 * VAR08 * VAR21 + - CONST327 * VAR02 * z + + CONST329 * VAR04 * VAR25 + - CONST378 * VAR19 + ) + + g_3 + * ( + VAR16 + * (-CONST183 * VAR23 * x + CONST228 * VAR05 * z + CONST267 * VAR07 * VAR25) + + y + * ( + CONST116 * VAR07 * VAR23 + - CONST234 * VAR05 * VAR25 + + CONST234 * VAR21 * x + + CONST268 * VAR03 * z + ) + ) + + g_4 + * ( + CONST008 * VAR01 + + VAR03 * (CONST303 * VAR17 + CONST377 * VAR26) + + VAR05 * (CONST175 * VAR15 - CONST307 * VAR17 * VAR26 + CONST326 * VAR24) + + VAR07 + * (CONST108 * VAR15 * VAR26 + CONST341 * VAR17 * VAR24 + CONST359 * VAR22) + + x + * (CONST053 * VAR20 + CONST307 * VAR17 * VAR22 + CONST341 * VAR15 * VAR24) + ) + + g_5 + * ( + VAR14 * (CONST147 * VAR07 * z - CONST147 * VAR25 * x) + + VAR16 + * (CONST154 * VAR05 * z + CONST190 * VAR07 * VAR25 + CONST310 * VAR23 * x) + + y + * (CONST156 * VAR21 * x + CONST222 * VAR05 * VAR25 + CONST325 * VAR03 * z) + ) + + g_6 + * ( + CONST177 * VAR01 + + VAR03 * (CONST030 * VAR26 + CONST321 * VAR17) + + VAR05 * (-CONST193 * VAR15 + CONST229 * VAR17 * VAR26) + + VAR07 * (CONST239 * VAR13 + CONST258 * VAR17 * VAR24 + CONST362 * VAR22) + + x + * ( + CONST148 * VAR15 * VAR24 + - CONST338 * VAR13 * VAR26 + + CONST357 * VAR17 * VAR22 + + CONST372 * VAR20 + ) + ) + + g_7 + * ( + -CONST221 * VAR12 * x * z + + VAR14 * (CONST136 * VAR07 * z + CONST260 * VAR25 * x) + + VAR16 + * (CONST119 * VAR05 * z - CONST145 * VAR23 * x + CONST342 * VAR07 * VAR25) + + y + * ( + CONST237 * VAR07 * VAR23 + + CONST297 * VAR05 * VAR25 + + CONST298 * VAR21 * x + ) + ) + + g_8 + * ( + -CONST397 * VAR01 + + VAR03 * (CONST031 * VAR26 + CONST344 * VAR17) + + VAR05 * (CONST055 * VAR24 + CONST160 * VAR17 * VAR26 + CONST173 * VAR15) + + VAR07 + * ( + CONST051 * VAR22 + + CONST143 * VAR15 * VAR26 + + CONST231 * VAR13 + + CONST322 * VAR17 * VAR24 + ) + + x + * ( + CONST024 * VAR20 + + CONST082 * VAR11 + + CONST196 * VAR17 * VAR22 + + CONST295 * VAR13 * VAR26 + + CONST330 * VAR15 * VAR24 + ) + ) + + g_9 + * ( + CONST070 * VAR03 * y * z + + VAR05 * (CONST121 * VAR25 * y + CONST168 * VAR16 * z) + + VAR07 + * (CONST121 * VAR23 * y + CONST261 * VAR16 * VAR25 - CONST361 * VAR14 * z) + + x + * ( + CONST070 * VAR21 * y + + CONST167 * VAR16 * VAR23 + + CONST264 * VAR12 * z + - CONST361 * VAR14 * VAR25 + ) + ) + ) + # write out gradients + tl.store( + coord_grad_ptr + coord_row_offset, g_x, mask=coord_row_offset < coord_numel + ) + tl.store( + coord_grad_ptr + coord_row_offset + 1, + g_y, + mask=coord_row_offset + 1 < coord_numel, + ) + tl.store( + coord_grad_ptr + coord_row_offset + 2, + g_z, + mask=coord_row_offset + 2 < coord_numel, + ) From 17b0234fa5cccb3621b27737c2df88ed1356a72a Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Thu, 22 Aug 2024 11:39:28 -0700 Subject: [PATCH 016/116] feat: exposing tenth order to direct namespace --- src/equitriton/sph_harm/direct/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/equitriton/sph_harm/direct/__init__.py b/src/equitriton/sph_harm/direct/__init__.py index 06df358..d53bd0e 100644 --- a/src/equitriton/sph_harm/direct/__init__.py +++ b/src/equitriton/sph_harm/direct/__init__.py @@ -1,4 +1,9 @@ from equitriton.sph_harm.direct.y_2 import SecondOrderSphericalHarmonic from equitriton.sph_harm.direct.y_5 import FifthOrderSphericalHarmonic +from equitriton.sph_harm.direct.y_10 import TenthOrderSphericalHarmonic -__all__ = ["SecondOrderSphericalHarmonic", "FifthOrderSphericalHarmonic"] +__all__ = [ + "SecondOrderSphericalHarmonic", + "FifthOrderSphericalHarmonic", + "TenthOrderSphericalHarmonic", +] From 15ca83b26d762240cc00b8f59d1d600bc16f8aa8 Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Thu, 22 Aug 2024 11:51:27 -0700 Subject: [PATCH 017/116] feat & test: functioning tenth order done --- .../direct/tests/test_direct_sph_harm.py | 32 ++++++++++++++++--- src/equitriton/sph_harm/direct/y_10.py | 2 +- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py index eb03c98..70a0a67 100644 --- a/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py +++ b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py @@ -10,7 +10,7 @@ torch.manual_seed(316165) -@pytest.mark.parametrize("order", [2, 5]) +@pytest.mark.parametrize("order", [2, 5, 10]) @pytest.mark.parametrize( "device", [ @@ -28,16 +28,27 @@ ) @pytest.mark.parametrize("tensor_shape", [(512, 3), (128, 16, 3), (256, 8, 8, 3)]) @pytest.mark.parametrize( - "dtype", [torch.float16, torch.bfloat16, torch.float32, torch.float64] + "dtype", + [ + pytest.param(torch.float16, marks=pytest.mark.xfail(reason="low precision")), + pytest.param(torch.bfloat16, marks=pytest.mark.xfail(reason="low precision")), + torch.float32, + torch.float64, + ], ) def test_forward_equivalence(order, device, tensor_shape, dtype): + """ + Tests the numerical equivalence of the PyTorch versus + the Triton implementations. This is mostly to ensure that + writing outputs back out is being done correctly. + """ coords = torch.rand(tensor_shape, device=device, dtype=dtype) triton_out = triton_spherical_harmonic(order, coords) torch_out = torch_spherical_harmonic(order, coords) - assert torch.allclose(triton_out, torch_out, atol=1e-6, rtol=1e-4) + assert torch.allclose(triton_out, torch_out, atol=1e-5, rtol=1e-3) -@pytest.mark.parametrize("order", [2, 5]) +@pytest.mark.parametrize("order", [2, 5, 10]) @pytest.mark.parametrize( "device", [ @@ -55,9 +66,20 @@ def test_forward_equivalence(order, device, tensor_shape, dtype): ) @pytest.mark.parametrize("tensor_shape", [(512, 3), (128, 16, 3), (256, 8, 8, 3)]) @pytest.mark.parametrize( - "dtype", [torch.float16, torch.bfloat16, torch.float32, torch.float64] + "dtype", + [ + pytest.param(torch.float16, marks=pytest.mark.xfail(reason="low precision")), + pytest.param(torch.bfloat16, marks=pytest.mark.xfail(reason="low precision")), + torch.float32, + torch.float64, + ], ) def test_backward_equivalence(order, device, tensor_shape, dtype): + """ + Tests the numerical equivalence of the PyTorch versus + the Triton implementation of the backward pass. This is mostly to ensure that + writing outputs back out is being done correctly. + """ coords = torch.rand(tensor_shape, device=device, dtype=dtype, requires_grad=True) # run with autograd first torch_out = torch_spherical_harmonic(order, coords) diff --git a/src/equitriton/sph_harm/direct/y_10.py b/src/equitriton/sph_harm/direct/y_10.py index ce535b3..1c41062 100644 --- a/src/equitriton/sph_harm/direct/y_10.py +++ b/src/equitriton/sph_harm/direct/y_10.py @@ -16,7 +16,7 @@ def forward( block_size: int = 64, ): output_tensor = torch.empty( - (*coords.shape[:-1], 11), dtype=coords.dtype, device=coords.device + (*coords.shape[:-1], 21), dtype=coords.dtype, device=coords.device ) coord_numel = coords.numel() output_numel = output_tensor.numel() From ddb01380de1cb19c12cd5b2e6d4a9be41aa49390 Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Thu, 22 Aug 2024 11:54:13 -0700 Subject: [PATCH 018/116] chore: exposing utility function to direct namespace --- src/equitriton/sph_harm/direct/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/equitriton/sph_harm/direct/__init__.py b/src/equitriton/sph_harm/direct/__init__.py index d53bd0e..2839bef 100644 --- a/src/equitriton/sph_harm/direct/__init__.py +++ b/src/equitriton/sph_harm/direct/__init__.py @@ -1,9 +1,11 @@ from equitriton.sph_harm.direct.y_2 import SecondOrderSphericalHarmonic from equitriton.sph_harm.direct.y_5 import FifthOrderSphericalHarmonic from equitriton.sph_harm.direct.y_10 import TenthOrderSphericalHarmonic +from equitriton.sph_harm.direct.utils import triton_spherical_harmonic __all__ = [ "SecondOrderSphericalHarmonic", "FifthOrderSphericalHarmonic", "TenthOrderSphericalHarmonic", + "triton_spherical_harmonic", ] From b3a157afec8831a1a72b785fc77278853f3d02e6 Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Thu, 22 Aug 2024 12:23:26 -0700 Subject: [PATCH 019/116] feat: implemented fused second order kernel --- src/equitriton/sph_harm/direct/__init__.py | 2 + src/equitriton/sph_harm/direct/special.py | 267 +++++++++++++++++++++ 2 files changed, 269 insertions(+) create mode 100644 src/equitriton/sph_harm/direct/special.py diff --git a/src/equitriton/sph_harm/direct/__init__.py b/src/equitriton/sph_harm/direct/__init__.py index 2839bef..111e106 100644 --- a/src/equitriton/sph_harm/direct/__init__.py +++ b/src/equitriton/sph_harm/direct/__init__.py @@ -1,11 +1,13 @@ from equitriton.sph_harm.direct.y_2 import SecondOrderSphericalHarmonic from equitriton.sph_harm.direct.y_5 import FifthOrderSphericalHarmonic from equitriton.sph_harm.direct.y_10 import TenthOrderSphericalHarmonic +from equitriton.sph_harm.direct.special import FusedSecondOrderSphericalHarmonic from equitriton.sph_harm.direct.utils import triton_spherical_harmonic __all__ = [ "SecondOrderSphericalHarmonic", "FifthOrderSphericalHarmonic", "TenthOrderSphericalHarmonic", + "FusedSecondOrderSphericalHarmonic", "triton_spherical_harmonic", ] diff --git a/src/equitriton/sph_harm/direct/special.py b/src/equitriton/sph_harm/direct/special.py new file mode 100644 index 0000000..f6fe596 --- /dev/null +++ b/src/equitriton/sph_harm/direct/special.py @@ -0,0 +1,267 @@ +import triton +import torch +from triton import language as tl + +from equitriton.utils import calculate_lastdim_num_blocks + +__all__ = ["FusedSecondOrderSphericalHarmonic"] + + +class FusedSecondOrderSphericalHarmonic(torch.autograd.Function): + @staticmethod + def forward( + ctx, + coords: torch.Tensor, + mask: torch.Tensor | None = None, + block_size: int = 64, + ): + output_tensor = torch.empty( + (*coords.shape[:-1], 9), dtype=coords.dtype, device=coords.device + ) + coord_numel = coords.numel() + output_numel = output_tensor.numel() + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + # apply the kernel + joint_second_order_fwd[num_blocks,]( + coords, output_tensor, block_size, coord_numel, output_numel + ) + ctx.save_for_backward(coords) + return output_tensor + + @staticmethod + def backward( + ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 + ) -> torch.Tensor: + (coords,) = ctx.saved_tensors + coord_grad_output = torch.zeros_like(coords) + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + # call backward kernel + joint_second_order_bwd[num_blocks,]( + coords, + coord_grad_output, + sph_grad_tensor, + block_size, + coords.numel(), + sph_grad_tensor.numel(), + ) + return coord_grad_output + + +def _torch_fwd(coords: torch.Tensor) -> torch.Tensor: + """ + PyTorch implementation of the kernel. This is designed + purely for unit testing to ensure that the Triton implementation + is behaving as intended. + + This function is generically named to make it easier for + it to be called programmatically: it is _not_ intended + to be used manually. + + Parameters + ---------- + coords : torch.Tensor + N-d tensor, where the last dimension corresponds to + xyz values. + + Returns + ------- + torch.Tensor + N-d tensor, where the last dimension corresponds to + each projection of the second order spherical harmonic. + """ + x = coords[..., 0].contiguous().unsqueeze(-1) + y = coords[..., 1].contiguous().unsqueeze(-1) + z = coords[..., 2].contiguous().unsqueeze(-1) + CONST_00 = 3.87298334620742 + CONST_01 = 2.23606797749979 + CONST_02 = -1.11803398874989 + CONST_03 = 1.93649167310371 + CONST_04 = 3**0.5 + Y00 = torch.ones_like(x) + Y10 = x * CONST_04 + Y11 = y * CONST_04 + Y12 = z * CONST_04 + Y20 = CONST_00 * x * z + Y21 = CONST_00 * x * y + Y23 = CONST_00 * y * z # looks jarring but just helping the compiler ;) + Y22 = CONST_02 * x * x + CONST_01 * y * y + CONST_02 * z * z + Y24 = -CONST_03 * x * x + CONST_03 * z * z + return torch.cat([Y00, Y10, Y11, Y12, Y20, Y21, Y22, Y23, Y24], dim=-1) + + +@triton.jit +def joint_second_order_fwd( + coord_ptr: tl.tensor, + output_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, +): + """ + This Triton implementation includes l=0, 1, 2 within the + same kernel, as it would be a common operation. + """ + # these are hardcoded because they are predetermined; + coord_stride = 3 + # work out the row offsets + block_id = tl.program_id(0) + coord_striding = tl.arange(0, block_size) * coord_stride + # as the name suggests, this is effectively every node/atom + coord_row_offset = coord_striding + (block_size * coord_stride * block_id) + x = tl.load(coord_ptr + coord_row_offset, mask=coord_row_offset < coord_numel) + y = tl.load( + coord_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + z = tl.load( + coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + CONST_00 = 3.87298334620742 + CONST_01 = 2.23606797749979 + CONST_02 = -1.11803398874989 + CONST_03 = 1.93649167310371 + CONST_04 = tl.sqrt(3.0) + Y10 = CONST_04 * x + Y11 = CONST_04 * y + Y12 = CONST_04 * z + Y20 = CONST_00 * x * z + Y21 = CONST_00 * x * y + Y23 = CONST_00 * y * z # looks jarring but just helping the compiler ;) + Y22 = CONST_02 * x * x + CONST_01 * y * y + CONST_02 * z * z + Y24 = -CONST_03 * x * x + CONST_03 * z * z + output_stride = 9 # sum of [2l + 1] over l=0, 1, 2 + output_striding = tl.arange(0, block_size) * output_stride + output_row_offset = output_striding + (block_size * output_stride * block_id) + # first column are all zeros, per zeroth order + tl.store(output_ptr + output_row_offset, 1.0, mask=output_row_offset < output_numel) + tl.store( + output_ptr + output_row_offset + 1, + Y10, + mask=output_row_offset + 1 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 2, + Y11, + mask=output_row_offset + 2 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 3, + Y12, + mask=output_row_offset + 3 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 4, + Y20, + mask=output_row_offset + 4 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 5, + Y21, + mask=output_row_offset + 5 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 6, + Y22, + mask=output_row_offset + 6 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 7, + Y23, + mask=output_row_offset + 6 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 8, + Y24, + mask=output_row_offset + 7 < output_numel, + ) + + +@triton.jit +def joint_second_order_bwd( + coord_ptr: tl.tensor, + coord_grad_ptr: tl.tensor, + sph_grad_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, +): + # work out the row offsets + block_id = tl.program_id(0) + # these are hardcoded because they are predetermined; + coord_stride = 3 + coord_striding = tl.arange(0, block_size) * coord_stride + # as the name suggests, this is effectively every node/atom + coord_row_offset = coord_striding + (block_size * coord_stride * block_id) + x = tl.load(coord_ptr + coord_row_offset, mask=coord_row_offset < coord_numel) + y = tl.load( + coord_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + z = tl.load( + coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + output_stride = 9 # [2l + 1] + output_striding = tl.arange(0, block_size) * output_stride + output_row_offset = output_striding + (block_size * output_stride * block_id) + CONST_00 = 3.87298334620742 + CONST_01 = 2.23606797749979 + CONST_02 = 4.47213595499958 + CONST_03 = tl.sqrt(3.0) + # load in gradients w.r.t. spherical harmonic projections. + # gradient of l = 0 goes to zero + g_Y10 = tl.load( + sph_grad_ptr + output_row_offset + 1, mask=output_row_offset + 1 < output_numel + ) + g_Y11 = tl.load( + sph_grad_ptr + output_row_offset + 2, mask=output_row_offset + 2 < output_numel + ) + g_Y12 = tl.load( + sph_grad_ptr + output_row_offset + 3, mask=output_row_offset + 3 < output_numel + ) + g_Y20 = tl.load( + sph_grad_ptr + output_row_offset + 4, mask=output_row_offset + 4 < output_numel + ) + g_Y21 = tl.load( + sph_grad_ptr + output_row_offset + 5, mask=output_row_offset + 5 < output_numel + ) + g_Y22 = tl.load( + sph_grad_ptr + output_row_offset + 6, mask=output_row_offset + 6 < output_numel + ) + g_Y23 = tl.load( + sph_grad_ptr + output_row_offset + 7, mask=output_row_offset + 7 < output_numel + ) + g_Y24 = tl.load( + sph_grad_ptr + output_row_offset + 8, mask=output_row_offset + 8 < output_numel + ) + g_x = ( + CONST_00 * g_Y20 * z + + CONST_00 * g_Y21 * y + - CONST_01 * g_Y22 * x + - CONST_00 * g_Y24 * x + + CONST_03 * g_Y10 + ) + g_y = ( + CONST_00 * g_Y21 * x + + CONST_02 * g_Y22 * y + + CONST_00 * g_Y23 * z + + CONST_03 * g_Y11 + ) + g_z = ( + CONST_00 * g_Y20 * x + - CONST_01 * g_Y22 * z + + CONST_00 * g_Y23 * y + + CONST_00 * g_Y24 * z + + CONST_03 * g_Y12 + ) + # write out gradients + tl.store( + coord_grad_ptr + coord_row_offset, g_x, mask=coord_row_offset < coord_numel + ) + tl.store( + coord_grad_ptr + coord_row_offset + 1, + g_y, + mask=coord_row_offset + 1 < coord_numel, + ) + tl.store( + coord_grad_ptr + coord_row_offset + 2, + g_z, + mask=coord_row_offset + 2 < coord_numel, + ) From bbf531344a78249d7e950472d1572fee892b14fd Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Thu, 22 Aug 2024 13:52:14 -0700 Subject: [PATCH 020/116] docs: added short write up for direct --- src/equitriton/sph_harm/direct/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/equitriton/sph_harm/direct/README.md diff --git a/src/equitriton/sph_harm/direct/README.md b/src/equitriton/sph_harm/direct/README.md new file mode 100644 index 0000000..e5e1a27 --- /dev/null +++ b/src/equitriton/sph_harm/direct/README.md @@ -0,0 +1,15 @@ +# Direct spherical harmonics + +This module implements spherical harmonics of up to $l=10$ _directly_ in terms +of $x,y,z$. Each submodule implements a particular order, comprising four objects: +a PyTorch `autograd.Function` wrapper, forward and backward Triton kernels, +and a PyTorch implementation of the forward kernel. The PyTorch implementation +is not necessarily intended for performance, rather for double checking that +the Triton versions are behaving as intended. + +Currently, the kernels are heavily computer assisted, and may not be optimal +particularly on the register front: there are a lot of redudant constants, +and we are relying heavily on the LLVM compiler to realize this and group +them at run time. Similarly, the variable names are also not very human-friendly; +this is unlikely to change; they might have a high maintenance burden, +but we're unlikely to touch them very much. From 269807dd2bf0c5548e9616bd144f467843132893 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sat, 24 Aug 2024 09:51:34 -0700 Subject: [PATCH 021/116] feat: implemented third order terms --- src/equitriton/sph_harm/direct/y_3.py | 265 ++++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 src/equitriton/sph_harm/direct/y_3.py diff --git a/src/equitriton/sph_harm/direct/y_3.py b/src/equitriton/sph_harm/direct/y_3.py new file mode 100644 index 0000000..ce82d42 --- /dev/null +++ b/src/equitriton/sph_harm/direct/y_3.py @@ -0,0 +1,265 @@ +import triton +import torch +from triton import language as tl + +from equitriton.utils import calculate_lastdim_num_blocks + +__all__ = ["ThirdOrderSphericalHarmonic"] + + +class ThirdOrderSphericalHarmonic(torch.autograd.Function): + @staticmethod + def forward( + ctx, + coords: torch.Tensor, + mask: torch.Tensor | None = None, + block_size: int = 64, + ): + output_tensor = torch.empty( + (*coords.shape[:-1], 5), dtype=coords.dtype, device=coords.device + ) + coord_numel = coords.numel() + output_numel = output_tensor.numel() + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + # apply the kernel + third_order_fwd[num_blocks,]( + coords, output_tensor, block_size, coord_numel, output_numel + ) + ctx.save_for_backward(coords) + return output_tensor + + @staticmethod + def backward( + ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 + ) -> torch.Tensor: + (coords,) = ctx.saved_tensors + coord_grad_output = torch.zeros_like(coords) + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + # call backward kernel + third_order_bwd[num_blocks,]( + coords, + coord_grad_output, + sph_grad_tensor, + block_size, + coords.numel(), + sph_grad_tensor.numel(), + ) + return coord_grad_output + + +def _torch_fwd(coords: torch.Tensor) -> torch.Tensor: + """ + PyTorch implementation of the kernel. This is designed + purely for unit testing to ensure that the Triton implementation + is behaving as intended. + + This function is generically named to make it easier for + it to be called programmatically: it is _not_ intended + to be used manually. + + Parameters + ---------- + coords : torch.Tensor + N-d tensor, where the last dimension corresponds to + xyz values. + + Returns + ------- + torch.Tensor + N-d tensor, where the last dimension corresponds to + each projection of the second order spherical harmonic. + """ + x = coords[..., 0].contiguous().unsqueeze(-1) + y = coords[..., 1].contiguous().unsqueeze(-1) + z = coords[..., 2].contiguous().unsqueeze(-1) + # -------------------- variable and constant definitions + CONST000 = 2.64575131106459 + CONST002 = 5.12347538297980 + CONST004 = 6.48074069840786 + CONST005 = 10.2469507659596 + CONST006 = -2.09165006633519 + CONST007 = -1 + CONST008 = -6.27495019900557 + CONST009 = -3.96862696659689 + CONST010 = -1.62018517460197 + VAR07 = x * x * x + VAR08 = x * x + VAR16 = y * y * y + VAR17 = y * y + VAR25 = z * z * z + VAR26 = z * z + # -------------------- kernel implementations + Y00 = CONST006*VAR07 - CONST008*VAR26*x + Y01 = CONST005*x*y*z + Y02 = CONST010*VAR07 + x*(CONST004*VAR17 + CONST010*VAR26) + Y03 = CONST000*VAR16 + CONST009*VAR08*y + CONST009*VAR26*y + Y04 = CONST010*VAR25 + z*(CONST004*VAR17 + CONST010*VAR08) + Y05 = CONST002*y*(CONST007*VAR08 + VAR26) + Y06 = -CONST006*VAR25 + CONST008*VAR08*z + tensors = [Y00, Y01, Y02, Y03, Y04, Y05, Y06] + return torch.cat(tensors, dim=-1) + + +@triton.jit +def third_order_fwd( + coord_ptr: tl.tensor, + output_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, +): + # these are hardcoded because they are predetermined; + coord_stride = 3 + # work out the row offsets + block_id = tl.program_id(0) + coord_striding = tl.arange(0, block_size) * coord_stride + # as the name suggests, this is effectively every node/atom + coord_row_offset = coord_striding + (block_size * coord_stride * block_id) + x = tl.load(coord_ptr + coord_row_offset, mask=coord_row_offset < coord_numel) + y = tl.load( + coord_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + z = tl.load( + coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + # -------------------- variable and constant definitions + CONST000 = 2.64575131106459 + CONST002 = 5.12347538297980 + CONST004 = 6.48074069840786 + CONST005 = 10.2469507659596 + CONST006 = -2.09165006633519 + CONST007 = -1 + CONST008 = -6.27495019900557 + CONST009 = -3.96862696659689 + CONST010 = -1.62018517460197 + VAR07 = x * x * x + VAR08 = x * x + VAR16 = y * y * y + VAR17 = y * y + VAR25 = z * z * z + VAR26 = z * z + # -------------------- kernel implementations + Y00 = CONST006*VAR07 - CONST008*VAR26*x + Y01 = CONST005*x*y*z + Y02 = CONST010*VAR07 + x*(CONST004*VAR17 + CONST010*VAR26) + Y03 = CONST000*VAR16 + CONST009*VAR08*y + CONST009*VAR26*y + Y04 = CONST010*VAR25 + z*(CONST004*VAR17 + CONST010*VAR08) + Y05 = CONST002*y*(CONST007*VAR08 + VAR26) + Y06 = -CONST006*VAR25 + CONST008*VAR08*z + output_stride = 7 # [2l + 1] + output_striding = tl.arange(0, block_size) * output_stride + output_row_offset = output_striding + (block_size * output_stride * block_id) + tl.store(output_ptr + output_row_offset, Y00, mask=output_row_offset < output_numel) + tl.store( + output_ptr + output_row_offset + 1, + Y01, + mask=output_row_offset + 1 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 2, + Y02, + mask=output_row_offset + 2 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 3, + Y03, + mask=output_row_offset + 3 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 4, + Y04, + mask=output_row_offset + 4 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 5, + Y05, + mask=output_row_offset + 5 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 6, + Y06, + mask=output_row_offset + 6 < output_numel, + ) + + +@triton.jit +def third_order_bwd( + coord_ptr: tl.tensor, + coord_grad_ptr: tl.tensor, + sph_grad_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, +): + # work out the row offsets + block_id = tl.program_id(0) + # these are hardcoded because they are predetermined; + coord_stride = 3 + coord_striding = tl.arange(0, block_size) * coord_stride + # as the name suggests, this is effectively every node/atom + coord_row_offset = coord_striding + (block_size * coord_stride * block_id) + x = tl.load(coord_ptr + coord_row_offset, mask=coord_row_offset < coord_numel) + y = tl.load( + coord_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + z = tl.load( + coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + output_stride = 7 # [2l + 1] + output_striding = tl.arange(0, block_size) * output_stride + output_row_offset = output_striding + (block_size * output_stride * block_id) + # load in gradients w.r.t. spherical harmonic projections + g_0 = tl.load( + sph_grad_ptr + output_row_offset, mask=output_row_offset < output_numel + ) + g_1 = tl.load( + sph_grad_ptr + output_row_offset + 1, mask=output_row_offset + 1 < output_numel + ) + g_2 = tl.load( + sph_grad_ptr + output_row_offset + 2, mask=output_row_offset + 2 < output_numel + ) + g_3 = tl.load( + sph_grad_ptr + output_row_offset + 3, mask=output_row_offset + 3 < output_numel + ) + g_4 = tl.load( + sph_grad_ptr + output_row_offset + 4, mask=output_row_offset + 4 < output_numel + ) + g_5 = tl.load( + sph_grad_ptr + output_row_offset + 5, mask=output_row_offset + 5 < output_numel + ) + g_6 = tl.load( + sph_grad_ptr + output_row_offset + 6, mask=output_row_offset + 6 < output_numel + ) + # -------------------- variable and constant definitions + CONST002 = 6.48074069840786 + CONST005 = 12.9614813968157 + CONST007 = -3.96862696659689 + CONST008 = -12.5499003980111 + CONST009 = -10.2469507659596 + CONST010 = -7.93725393319377 + CONST011 = -6.27495019900557 + CONST012 = -5.12347538297980 + CONST013 = -4.86055552380590 + CONST014 = -3.24037034920393 + CONST015 = -1.62018517460197 + VAR08 = x * x + VAR17 = y * y + VAR26 = z * z + # -------------------- kernel implementations + g_x = CONST008*g_6*x*z - CONST009*g_1*y*z + CONST009*g_5*x*y + CONST010*g_3*x*y + CONST014*g_4*x*z + g_0*(CONST011*VAR08 - CONST011*VAR26) + g_2*(CONST002*VAR17 + CONST013*VAR08 + CONST015*VAR26) + g_y = CONST005*g_2*x*y + CONST005*g_4*y*z - CONST009*g_1*x*z + g_3*(CONST007*VAR08 + CONST007*VAR26 - CONST010*VAR17) + g_5*(CONST012*VAR08 - CONST012*VAR26) + g_z = -CONST008*g_0*x*z - CONST009*g_1*x*y - CONST009*g_5*y*z + CONST010*g_3*y*z + CONST014*g_2*x*z + g_4*(CONST002*VAR17 + CONST013*VAR26 + CONST015*VAR08) + g_6*(CONST011*VAR08 - CONST011*VAR26) + # write out gradients + tl.store( + coord_grad_ptr + coord_row_offset, g_x, mask=coord_row_offset < coord_numel + ) + tl.store( + coord_grad_ptr + coord_row_offset + 1, + g_y, + mask=coord_row_offset + 1 < coord_numel, + ) + tl.store( + coord_grad_ptr + coord_row_offset + 2, + g_z, + mask=coord_row_offset + 2 < coord_numel, + ) From 768b44dc876a86fa0e373e501bbd5933f47318aa Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sat, 24 Aug 2024 09:53:20 -0700 Subject: [PATCH 022/116] fix: correcting output allocation shape for third order --- src/equitriton/sph_harm/direct/y_3.py | 58 ++++++++++++++++++--------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_3.py b/src/equitriton/sph_harm/direct/y_3.py index ce82d42..141f1f2 100644 --- a/src/equitriton/sph_harm/direct/y_3.py +++ b/src/equitriton/sph_harm/direct/y_3.py @@ -16,7 +16,7 @@ def forward( block_size: int = 64, ): output_tensor = torch.empty( - (*coords.shape[:-1], 5), dtype=coords.dtype, device=coords.device + (*coords.shape[:-1], 7), dtype=coords.dtype, device=coords.device ) coord_numel = coords.numel() output_numel = output_tensor.numel() @@ -89,13 +89,13 @@ def _torch_fwd(coords: torch.Tensor) -> torch.Tensor: VAR25 = z * z * z VAR26 = z * z # -------------------- kernel implementations - Y00 = CONST006*VAR07 - CONST008*VAR26*x - Y01 = CONST005*x*y*z - Y02 = CONST010*VAR07 + x*(CONST004*VAR17 + CONST010*VAR26) - Y03 = CONST000*VAR16 + CONST009*VAR08*y + CONST009*VAR26*y - Y04 = CONST010*VAR25 + z*(CONST004*VAR17 + CONST010*VAR08) - Y05 = CONST002*y*(CONST007*VAR08 + VAR26) - Y06 = -CONST006*VAR25 + CONST008*VAR08*z + Y00 = CONST006 * VAR07 - CONST008 * VAR26 * x + Y01 = CONST005 * x * y * z + Y02 = CONST010 * VAR07 + x * (CONST004 * VAR17 + CONST010 * VAR26) + Y03 = CONST000 * VAR16 + CONST009 * VAR08 * y + CONST009 * VAR26 * y + Y04 = CONST010 * VAR25 + z * (CONST004 * VAR17 + CONST010 * VAR08) + Y05 = CONST002 * y * (CONST007 * VAR08 + VAR26) + Y06 = -CONST006 * VAR25 + CONST008 * VAR08 * z tensors = [Y00, Y01, Y02, Y03, Y04, Y05, Y06] return torch.cat(tensors, dim=-1) @@ -139,13 +139,13 @@ def third_order_fwd( VAR25 = z * z * z VAR26 = z * z # -------------------- kernel implementations - Y00 = CONST006*VAR07 - CONST008*VAR26*x - Y01 = CONST005*x*y*z - Y02 = CONST010*VAR07 + x*(CONST004*VAR17 + CONST010*VAR26) - Y03 = CONST000*VAR16 + CONST009*VAR08*y + CONST009*VAR26*y - Y04 = CONST010*VAR25 + z*(CONST004*VAR17 + CONST010*VAR08) - Y05 = CONST002*y*(CONST007*VAR08 + VAR26) - Y06 = -CONST006*VAR25 + CONST008*VAR08*z + Y00 = CONST006 * VAR07 - CONST008 * VAR26 * x + Y01 = CONST005 * x * y * z + Y02 = CONST010 * VAR07 + x * (CONST004 * VAR17 + CONST010 * VAR26) + Y03 = CONST000 * VAR16 + CONST009 * VAR08 * y + CONST009 * VAR26 * y + Y04 = CONST010 * VAR25 + z * (CONST004 * VAR17 + CONST010 * VAR08) + Y05 = CONST002 * y * (CONST007 * VAR08 + VAR26) + Y06 = -CONST006 * VAR25 + CONST008 * VAR08 * z output_stride = 7 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride output_row_offset = output_striding + (block_size * output_stride * block_id) @@ -246,9 +246,31 @@ def third_order_bwd( VAR17 = y * y VAR26 = z * z # -------------------- kernel implementations - g_x = CONST008*g_6*x*z - CONST009*g_1*y*z + CONST009*g_5*x*y + CONST010*g_3*x*y + CONST014*g_4*x*z + g_0*(CONST011*VAR08 - CONST011*VAR26) + g_2*(CONST002*VAR17 + CONST013*VAR08 + CONST015*VAR26) - g_y = CONST005*g_2*x*y + CONST005*g_4*y*z - CONST009*g_1*x*z + g_3*(CONST007*VAR08 + CONST007*VAR26 - CONST010*VAR17) + g_5*(CONST012*VAR08 - CONST012*VAR26) - g_z = -CONST008*g_0*x*z - CONST009*g_1*x*y - CONST009*g_5*y*z + CONST010*g_3*y*z + CONST014*g_2*x*z + g_4*(CONST002*VAR17 + CONST013*VAR26 + CONST015*VAR08) + g_6*(CONST011*VAR08 - CONST011*VAR26) + g_x = ( + CONST008 * g_6 * x * z + - CONST009 * g_1 * y * z + + CONST009 * g_5 * x * y + + CONST010 * g_3 * x * y + + CONST014 * g_4 * x * z + + g_0 * (CONST011 * VAR08 - CONST011 * VAR26) + + g_2 * (CONST002 * VAR17 + CONST013 * VAR08 + CONST015 * VAR26) + ) + g_y = ( + CONST005 * g_2 * x * y + + CONST005 * g_4 * y * z + - CONST009 * g_1 * x * z + + g_3 * (CONST007 * VAR08 + CONST007 * VAR26 - CONST010 * VAR17) + + g_5 * (CONST012 * VAR08 - CONST012 * VAR26) + ) + g_z = ( + -CONST008 * g_0 * x * z + - CONST009 * g_1 * x * y + - CONST009 * g_5 * y * z + + CONST010 * g_3 * y * z + + CONST014 * g_2 * x * z + + g_4 * (CONST002 * VAR17 + CONST013 * VAR26 + CONST015 * VAR08) + + g_6 * (CONST011 * VAR08 - CONST011 * VAR26) + ) # write out gradients tl.store( coord_grad_ptr + coord_row_offset, g_x, mask=coord_row_offset < coord_numel From 410ba7b8f57f54d8f11c977598c57a51fe9ac29f Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sat, 24 Aug 2024 09:53:48 -0700 Subject: [PATCH 023/116] tests: updating unit test for third order --- src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py index 70a0a67..51d365a 100644 --- a/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py +++ b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py @@ -10,7 +10,7 @@ torch.manual_seed(316165) -@pytest.mark.parametrize("order", [2, 5, 10]) +@pytest.mark.parametrize("order", [2, 3, 5, 10]) @pytest.mark.parametrize( "device", [ @@ -48,7 +48,7 @@ def test_forward_equivalence(order, device, tensor_shape, dtype): assert torch.allclose(triton_out, torch_out, atol=1e-5, rtol=1e-3) -@pytest.mark.parametrize("order", [2, 5, 10]) +@pytest.mark.parametrize("order", [2, 3, 5, 10]) @pytest.mark.parametrize( "device", [ From b6ff222f2a1142423a6fe31091c2e9d7018ba895 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sat, 24 Aug 2024 10:00:38 -0700 Subject: [PATCH 024/116] feat: implemented fourth order terms --- src/equitriton/sph_harm/direct/y_4.py | 365 ++++++++++++++++++++++++++ 1 file changed, 365 insertions(+) create mode 100644 src/equitriton/sph_harm/direct/y_4.py diff --git a/src/equitriton/sph_harm/direct/y_4.py b/src/equitriton/sph_harm/direct/y_4.py new file mode 100644 index 0000000..485adfa --- /dev/null +++ b/src/equitriton/sph_harm/direct/y_4.py @@ -0,0 +1,365 @@ +import triton +import torch +from triton import language as tl + +from equitriton.utils import calculate_lastdim_num_blocks + +__all__ = ["FourthOrderSphericalHarmonic"] + + +class FourthOrderSphericalHarmonic(torch.autograd.Function): + @staticmethod + def forward( + ctx, + coords: torch.Tensor, + mask: torch.Tensor | None = None, + block_size: int = 64, + ): + output_tensor = torch.empty( + (*coords.shape[:-1], 9), dtype=coords.dtype, device=coords.device + ) + coord_numel = coords.numel() + output_numel = output_tensor.numel() + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + # apply the kernel + fourth_order_fwd[num_blocks,]( + coords, output_tensor, block_size, coord_numel, output_numel + ) + ctx.save_for_backward(coords) + return output_tensor + + @staticmethod + def backward( + ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 + ) -> torch.Tensor: + (coords,) = ctx.saved_tensors + coord_grad_output = torch.zeros_like(coords) + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + # call backward kernel + fourth_order_bwd[num_blocks,]( + coords, + coord_grad_output, + sph_grad_tensor, + block_size, + coords.numel(), + sph_grad_tensor.numel(), + ) + return coord_grad_output + + +def _torch_fwd(coords: torch.Tensor) -> torch.Tensor: + """ + PyTorch implementation of the kernel. This is designed + purely for unit testing to ensure that the Triton implementation + is behaving as intended. + + This function is generically named to make it easier for + it to be called programmatically: it is _not_ intended + to be used manually. + + Parameters + ---------- + coords : torch.Tensor + N-d tensor, where the last dimension corresponds to + xyz values. + + Returns + ------- + torch.Tensor + N-d tensor, where the last dimension corresponds to + each projection of the second order spherical harmonic. + """ + x = coords[..., 0].contiguous().unsqueeze(-1) + y = coords[..., 1].contiguous().unsqueeze(-1) + z = coords[..., 2].contiguous().unsqueeze(-1) + # -------------------- variable and constant definitions + CONST000 = 1.12500000000000 + CONST001 = 2.25000000000000 + CONST002 = 3.00000000000000 + CONST005 = 2.21852991866236 + CONST007 = 9.48683298050514 + CONST010 = 20.1246117974981 + CONST011 = -18.8248505970167 + CONST012 = -13.3111795119741 + CONST013 = -10.0623058987491 + CONST014 = -9.00000000000000 + CONST015 = -8.87411967464942 + CONST016 = -7.11512473537885 + CONST017 = -6.27495019900557 + CONST018 = -3.35410196624968 + CONST019 = -1.67705098312484 + VAR06 = x * x * x * x + VAR07 = x * x * x + VAR08 = x * x + VAR15 = y * y * y * y + VAR16 = y * y * y + VAR17 = y * y + VAR24 = z * z * z * z + VAR25 = z * z * z + VAR26 = z * z + # -------------------- kernel implementations + Y00 = CONST015 * VAR07 * z - CONST015 * VAR25 * x + Y01 = y * (-CONST011 * VAR26 * x + CONST017 * VAR07) + Y02 = CONST018 * VAR07 * z + x * (CONST010 * VAR17 * z + CONST018 * VAR25) + Y03 = CONST016 * VAR07 * y + x * (CONST007 * VAR16 + CONST016 * VAR26 * y) + Y04 = ( + CONST000 * VAR06 + + CONST000 * VAR24 + + CONST002 * VAR15 + + CONST014 * VAR17 * VAR26 + + VAR08 * (CONST001 * VAR26 + CONST014 * VAR17) + ) + Y05 = CONST016 * VAR25 * y + z * (CONST007 * VAR16 + CONST016 * VAR08 * y) + Y06 = ( + -CONST019 * VAR06 + + CONST019 * VAR24 + + VAR17 * (CONST013 * VAR08 - CONST013 * VAR26) + ) + Y07 = y * (CONST011 * VAR08 * z - CONST017 * VAR25) + Y08 = CONST005 * VAR06 + CONST005 * VAR24 + CONST012 * VAR08 * VAR26 + tensors = [Y00, Y01, Y02, Y03, Y04, Y05, Y06, Y07, Y08] + return torch.cat(tensors, dim=-1) + + +@triton.jit +def fourth_order_fwd( + coord_ptr: tl.tensor, + output_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, +): + # these are hardcoded because they are predetermined; + coord_stride = 3 + # work out the row offsets + block_id = tl.program_id(0) + coord_striding = tl.arange(0, block_size) * coord_stride + # as the name suggests, this is effectively every node/atom + coord_row_offset = coord_striding + (block_size * coord_stride * block_id) + x = tl.load(coord_ptr + coord_row_offset, mask=coord_row_offset < coord_numel) + y = tl.load( + coord_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + z = tl.load( + coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + # -------------------- variable and constant definitions + CONST000 = 1.12500000000000 + CONST001 = 2.25000000000000 + CONST002 = 3.00000000000000 + CONST005 = 2.21852991866236 + CONST007 = 9.48683298050514 + CONST010 = 20.1246117974981 + CONST011 = -18.8248505970167 + CONST012 = -13.3111795119741 + CONST013 = -10.0623058987491 + CONST014 = -9.00000000000000 + CONST015 = -8.87411967464942 + CONST016 = -7.11512473537885 + CONST017 = -6.27495019900557 + CONST018 = -3.35410196624968 + CONST019 = -1.67705098312484 + VAR06 = x * x * x * x + VAR07 = x * x * x + VAR08 = x * x + VAR15 = y * y * y * y + VAR16 = y * y * y + VAR17 = y * y + VAR24 = z * z * z * z + VAR25 = z * z * z + VAR26 = z * z + # -------------------- kernel implementations + Y00 = CONST015 * VAR07 * z - CONST015 * VAR25 * x + Y01 = y * (-CONST011 * VAR26 * x + CONST017 * VAR07) + Y02 = CONST018 * VAR07 * z + x * (CONST010 * VAR17 * z + CONST018 * VAR25) + Y03 = CONST016 * VAR07 * y + x * (CONST007 * VAR16 + CONST016 * VAR26 * y) + Y04 = ( + CONST000 * VAR06 + + CONST000 * VAR24 + + CONST002 * VAR15 + + CONST014 * VAR17 * VAR26 + + VAR08 * (CONST001 * VAR26 + CONST014 * VAR17) + ) + Y05 = CONST016 * VAR25 * y + z * (CONST007 * VAR16 + CONST016 * VAR08 * y) + Y06 = ( + -CONST019 * VAR06 + + CONST019 * VAR24 + + VAR17 * (CONST013 * VAR08 - CONST013 * VAR26) + ) + Y07 = y * (CONST011 * VAR08 * z - CONST017 * VAR25) + Y08 = CONST005 * VAR06 + CONST005 * VAR24 + CONST012 * VAR08 * VAR26 + output_stride = 9 # [2l + 1] + output_striding = tl.arange(0, block_size) * output_stride + output_row_offset = output_striding + (block_size * output_stride * block_id) + tl.store(output_ptr + output_row_offset, Y00, mask=output_row_offset < output_numel) + tl.store( + output_ptr + output_row_offset + 1, + Y01, + mask=output_row_offset + 1 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 2, + Y02, + mask=output_row_offset + 2 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 3, + Y03, + mask=output_row_offset + 3 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 4, + Y04, + mask=output_row_offset + 4 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 5, + Y05, + mask=output_row_offset + 5 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 6, + Y06, + mask=output_row_offset + 6 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 7, + Y07, + mask=output_row_offset + 7 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 8, + Y08, + mask=output_row_offset + 8 < output_numel, + ) + + +@triton.jit +def fourth_order_bwd( + coord_ptr: tl.tensor, + coord_grad_ptr: tl.tensor, + sph_grad_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, +): + # work out the row offsets + block_id = tl.program_id(0) + # these are hardcoded because they are predetermined; + coord_stride = 3 + coord_striding = tl.arange(0, block_size) * coord_stride + # as the name suggests, this is effectively every node/atom + coord_row_offset = coord_striding + (block_size * coord_stride * block_id) + x = tl.load(coord_ptr + coord_row_offset, mask=coord_row_offset < coord_numel) + y = tl.load( + coord_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + z = tl.load( + coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + output_stride = 7 # [2l + 1] + output_striding = tl.arange(0, block_size) * output_stride + output_row_offset = output_striding + (block_size * output_stride * block_id) + # load in gradients w.r.t. spherical harmonic projections + g_0 = tl.load( + sph_grad_ptr + output_row_offset, mask=output_row_offset < output_numel + ) + g_1 = tl.load( + sph_grad_ptr + output_row_offset + 1, mask=output_row_offset + 1 < output_numel + ) + g_2 = tl.load( + sph_grad_ptr + output_row_offset + 2, mask=output_row_offset + 2 < output_numel + ) + g_3 = tl.load( + sph_grad_ptr + output_row_offset + 3, mask=output_row_offset + 3 < output_numel + ) + g_4 = tl.load( + sph_grad_ptr + output_row_offset + 4, mask=output_row_offset + 4 < output_numel + ) + g_5 = tl.load( + sph_grad_ptr + output_row_offset + 5, mask=output_row_offset + 5 < output_numel + ) + g_6 = tl.load( + sph_grad_ptr + output_row_offset + 6, mask=output_row_offset + 6 < output_numel + ) + g_7 = tl.load( + sph_grad_ptr + output_row_offset + 7, mask=output_row_offset + 7 < output_numel + ) + g_8 = tl.load( + sph_grad_ptr + output_row_offset + 8, mask=output_row_offset + 8 < output_numel + ) + # -------------------- variable and constant definitions + CONST000 = 2.00000000000000 + CONST001 = 4.50000000000000 + CONST002 = 2.25000000000000 + CONST006 = 9.48683298050514 + CONST008 = 12.0000000000000 + CONST012 = 28.4604989415154 + CONST014 = 40.2492235949962 + CONST015 = -37.6497011940334 + CONST016 = -6.70820393249937 + CONST017 = -26.6223590239483 + CONST018 = -21.3453742061366 + CONST019 = -20.1246117974981 + CONST020 = -18.8248505970167 + CONST021 = -18.0000000000000 + CONST022 = -14.2302494707577 + CONST023 = -10.0623058987491 + CONST024 = -9.00000000000000 + CONST025 = -8.87411967464942 + CONST026 = -7.11512473537885 + CONST027 = -6.27495019900557 + CONST028 = -3.35410196624968 + VAR07 = x * x * x + VAR08 = x * x + VAR16 = y * y * y + VAR17 = y * y + VAR25 = z * z * z + VAR26 = z * z + # -------------------- kernel implementations + g_x = ( + CONST015 * g_7 * x * y * z + + CONST022 * g_5 * x * y * z + + g_0 * (CONST017 * VAR08 * z - CONST025 * VAR25) + + g_1 * y * (CONST020 * VAR08 - CONST020 * VAR26) + + g_2 * (-CONST019 * VAR17 * z + CONST023 * VAR08 * z + CONST028 * VAR25) + + g_3 * (CONST006 * VAR16 + CONST018 * VAR08 * y + CONST026 * VAR26 * y) + + g_4 + * (CONST000 * x * (CONST002 * VAR26 + CONST024 * VAR17) + CONST001 * VAR07) + + g_6 * (-CONST016 * VAR07 + CONST019 * VAR17 * x) + + g_8 * (CONST017 * VAR26 * x - CONST025 * VAR07) + ) + g_y = ( + CONST000 * g_6 * y * (CONST023 * VAR08 - CONST023 * VAR26) + + CONST014 * g_2 * x * y * z + + g_1 * (-CONST020 * VAR26 * x + CONST027 * VAR07) + + g_3 * (CONST026 * VAR07 + x * (CONST012 * VAR17 + CONST026 * VAR26)) + + g_4 * (CONST008 * VAR16 + CONST021 * VAR08 * y + CONST021 * VAR26 * y) + + g_5 * (CONST026 * VAR25 + z * (CONST012 * VAR17 + CONST026 * VAR08)) + + g_7 * (CONST020 * VAR08 * z - CONST027 * VAR25) + ) + g_z = ( + -CONST015 * g_1 * x * y * z + + CONST022 * g_3 * x * y * z + + g_0 * (-CONST017 * VAR26 * x + CONST025 * VAR07) + + g_2 * (CONST028 * VAR07 + x * (-CONST019 * VAR17 + CONST023 * VAR26)) + + g_4 * (CONST001 * VAR08 * z + CONST001 * VAR25 + CONST021 * VAR17 * z) + + g_5 * (CONST006 * VAR16 + CONST018 * VAR26 * y + CONST026 * VAR08 * y) + + g_6 * (CONST016 * VAR25 - CONST019 * VAR17 * z) + + g_7 * y * (CONST020 * VAR08 - CONST020 * VAR26) + + g_8 * (CONST017 * VAR08 * z - CONST025 * VAR25) + ) + # write out gradients + tl.store( + coord_grad_ptr + coord_row_offset, g_x, mask=coord_row_offset < coord_numel + ) + tl.store( + coord_grad_ptr + coord_row_offset + 1, + g_y, + mask=coord_row_offset + 1 < coord_numel, + ) + tl.store( + coord_grad_ptr + coord_row_offset + 2, + g_z, + mask=coord_row_offset + 2 < coord_numel, + ) From 1d4f9989d4c72ce5bb3d3e6ffec4c6f8633beff9 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sat, 24 Aug 2024 10:01:32 -0700 Subject: [PATCH 025/116] test: added parametrized test for fourth order --- src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py index 51d365a..fe245c8 100644 --- a/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py +++ b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py @@ -10,7 +10,7 @@ torch.manual_seed(316165) -@pytest.mark.parametrize("order", [2, 3, 5, 10]) +@pytest.mark.parametrize("order", [2, 3, 4, 5, 10]) @pytest.mark.parametrize( "device", [ @@ -48,7 +48,7 @@ def test_forward_equivalence(order, device, tensor_shape, dtype): assert torch.allclose(triton_out, torch_out, atol=1e-5, rtol=1e-3) -@pytest.mark.parametrize("order", [2, 3, 5, 10]) +@pytest.mark.parametrize("order", [2, 3, 4, 5, 10]) @pytest.mark.parametrize( "device", [ From e7fdfacbc0e0d5aa839977fe23f6831a15cba468 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sat, 24 Aug 2024 10:12:44 -0700 Subject: [PATCH 026/116] feat: added sixth order terms --- src/equitriton/sph_harm/direct/y_6.py | 732 ++++++++++++++++++++++++++ 1 file changed, 732 insertions(+) create mode 100644 src/equitriton/sph_harm/direct/y_6.py diff --git a/src/equitriton/sph_harm/direct/y_6.py b/src/equitriton/sph_harm/direct/y_6.py new file mode 100644 index 0000000..8f6b258 --- /dev/null +++ b/src/equitriton/sph_harm/direct/y_6.py @@ -0,0 +1,732 @@ +import triton +import torch +from triton import language as tl + +from equitriton.utils import calculate_lastdim_num_blocks + +__all__ = ["SixthOrderSphericalHarmonic"] + + +class SixthOrderSphericalHarmonic(torch.autograd.Function): + @staticmethod + def forward( + ctx, + coords: torch.Tensor, + mask: torch.Tensor | None = None, + block_size: int = 64, + ): + output_tensor = torch.empty( + (*coords.shape[:-1], 13), dtype=coords.dtype, device=coords.device + ) + coord_numel = coords.numel() + output_numel = output_tensor.numel() + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + # apply the kernel + sixth_order_fwd[num_blocks,]( + coords, output_tensor, block_size, coord_numel, output_numel + ) + ctx.save_for_backward(coords) + return output_tensor + + @staticmethod + def backward( + ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 + ) -> torch.Tensor: + (coords,) = ctx.saved_tensors + coord_grad_output = torch.zeros_like(coords) + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + # call backward kernel + sixth_order_bwd[num_blocks,]( + coords, + coord_grad_output, + sph_grad_tensor, + block_size, + coords.numel(), + sph_grad_tensor.numel(), + ) + return coord_grad_output + + +def _torch_fwd(coords: torch.Tensor) -> torch.Tensor: + """ + PyTorch implementation of the kernel. This is designed + purely for unit testing to ensure that the Triton implementation + is behaving as intended. + + Parameters + ---------- + coords : torch.Tensor + N-d tensor, where the last dimension corresponds to + xyz values. + + Returns + ------- + torch.Tensor + N-d tensor, where the last dimension corresponds to + each projection of the second order spherical harmonic. + """ + x = coords[..., 0].contiguous().unsqueeze(-1) + y = coords[..., 1].contiguous().unsqueeze(-1) + z = coords[..., 2].contiguous().unsqueeze(-1) + # -------------------- variable and constant definitions + CONST002 = 3.26558761940328 + CONST003 = 3.26558761940328 + CONST004 = 6.53117523880657 + CONST006 = 8.38944649544891 + CONST007 = 9.79676285820985 + CONST008 = 10.3266947761614 + CONST009 = 3.60555127546399 + CONST010 = -1.78863600265677 + CONST011 = 14.5309475774982 + CONST012 = 8.94318001328386 + CONST013 = 16.5227116418583 + CONST014 = 16.5227116418583 + CONST015 = 17.8863600265677 + CONST017 = 20.6533895523229 + CONST018 = 20.2812259244849 + CONST019 = -107.318160159406 + CONST020 = 17.8863600265677 + CONST022 = 29.3902885746295 + CONST024 = 40.5624518489699 + CONST025 = 41.9472324772445 + CONST026 = -1.63279380970164 + CONST027 = -83.8944649544891 + CONST028 = -78.3741028656788 + CONST030 = -71.5454401062709 + CONST032 = -52.2494019104525 + CONST033 = -52.2494019104525 + CONST035 = -48.4364919249939 + CONST036 = -41.3067791046458 + CONST037 = -36.3273689437454 + CONST038 = -29.3902885746295 + CONST039 = -27.0416345659799 + CONST040 = -26.1247009552263 + CONST041 = -26.1247009552263 + CONST042 = -19.5935257164197 + CONST043 = -2.42182459624970 + CONST044 = -9.79676285820985 + CONST045 = -7.15454401062709 + CONST046 = -3.38020432074749 + CONST047 = -1.12673477358250 + VAR07 = x * x * x + VAR08 = x * x + VAR04 = VAR07 * VAR07 + VAR05 = VAR07 * VAR08 + VAR06 = VAR08 * VAR08 + VAR16 = y * y * y + VAR17 = y * y + VAR13 = VAR16 * VAR16 + VAR14 = VAR16 * VAR17 + VAR15 = VAR17 * VAR17 + VAR25 = z * z * z + VAR26 = z * z + VAR22 = VAR25 * VAR25 + VAR23 = VAR25 * VAR26 + VAR24 = VAR26 * VAR26 + # -------------------- kernel implementations + Y00 = CONST011 * VAR05 * z + CONST011 * VAR23 * x + CONST035 * VAR07 * VAR25 + Y01 = y * (CONST006 * VAR05 + CONST025 * VAR24 * x + CONST027 * VAR07 * VAR26) + Y02 = ( + -CONST045 * VAR05 * z + + CONST045 * VAR23 * x + + VAR17 * (CONST030 * VAR07 * z - CONST030 * VAR25 * x) + ) + Y03 = VAR16 * (-CONST028 * VAR26 * x + CONST040 * VAR07) + y * ( + CONST007 * VAR05 + CONST038 * VAR24 * x + CONST042 * VAR07 * VAR26 + ) + Y04 = ( + CONST003 * VAR05 * z + + VAR07 * (CONST004 * VAR25 + CONST033 * VAR17 * z) + + x * (CONST002 * VAR23 - CONST032 * VAR15 * z + CONST032 * VAR17 * VAR25) + ) + Y05 = ( + CONST008 * VAR05 * y + + VAR07 * (CONST017 * VAR26 * y + CONST036 * VAR16) + + x * (CONST008 * VAR24 * y + CONST013 * VAR14 + CONST036 * VAR16 * VAR26) + ) + Y06 = ( + CONST009 * VAR13 + + CONST018 * VAR17 * VAR24 + + CONST039 * VAR15 * VAR26 + + CONST047 * VAR04 + + CONST047 * VAR22 + + VAR06 * (CONST018 * VAR17 + CONST046 * VAR26) + + VAR08 * (CONST024 * VAR17 * VAR26 + CONST039 * VAR15 + CONST046 * VAR24) + ) + Y07 = ( + CONST008 * VAR23 * y + + VAR25 * (CONST017 * VAR08 * y + CONST036 * VAR16) + + z * (CONST008 * VAR06 * y + CONST014 * VAR14 + CONST036 * VAR08 * VAR16) + ) + Y08 = ( + CONST026 * VAR04 + - CONST026 * VAR22 + + CONST040 * VAR17 * VAR24 + - CONST041 * VAR15 * VAR26 + + VAR06 * (CONST026 * VAR26 - CONST041 * VAR17) + + VAR08 * (-CONST026 * VAR24 + CONST041 * VAR15) + ) + Y09 = VAR16 * (CONST028 * VAR08 * z - CONST041 * VAR25) + y * ( + CONST022 * VAR06 * z - CONST042 * VAR08 * VAR25 + CONST044 * VAR23 + ) + Y10 = ( + CONST010 * VAR04 + + CONST010 * VAR22 + + CONST020 * VAR17 * VAR24 + + VAR06 * (CONST012 * VAR26 + CONST015 * VAR17) + + VAR08 * (CONST012 * VAR24 + CONST019 * VAR17 * VAR26) + ) + Y11 = y * (CONST006 * VAR23 + CONST025 * VAR06 * z + CONST027 * VAR08 * VAR25) + Y12 = ( + -CONST037 * VAR06 * VAR26 + + CONST037 * VAR08 * VAR24 + + CONST043 * VAR04 + - CONST043 * VAR22 + ) + # not the prettiest way to concatenate, but better than + # messing with the linter + tensors = [Y00, Y01, Y02, Y03, Y04, Y05, Y06, Y07, Y08, Y09, Y10, Y11, Y12] + return torch.cat(tensors, dim=-1) + + +@triton.jit +def sixth_order_fwd( + coord_ptr: tl.tensor, + output_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, +): + # these are hardcoded because they are predetermined; + coord_stride = 3 + # work out the row offsets + block_id = tl.program_id(0) + coord_striding = tl.arange(0, block_size) * coord_stride + # as the name suggests, this is effectively every node/atom + coord_row_offset = coord_striding + (block_size * coord_stride * block_id) + x = tl.load(coord_ptr + coord_row_offset, mask=coord_row_offset < coord_numel) + y = tl.load( + coord_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + z = tl.load( + coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + # -------------------- variable and constant definitions + CONST002 = 3.26558761940328 + CONST003 = 3.26558761940328 + CONST004 = 6.53117523880657 + CONST006 = 8.38944649544891 + CONST007 = 9.79676285820985 + CONST008 = 10.3266947761614 + CONST009 = 3.60555127546399 + CONST010 = -1.78863600265677 + CONST011 = 14.5309475774982 + CONST012 = 8.94318001328386 + CONST013 = 16.5227116418583 + CONST014 = 16.5227116418583 + CONST015 = 17.8863600265677 + CONST017 = 20.6533895523229 + CONST018 = 20.2812259244849 + CONST019 = -107.318160159406 + CONST020 = 17.8863600265677 + CONST022 = 29.3902885746295 + CONST024 = 40.5624518489699 + CONST025 = 41.9472324772445 + CONST026 = -1.63279380970164 + CONST027 = -83.8944649544891 + CONST028 = -78.3741028656788 + CONST030 = -71.5454401062709 + CONST032 = -52.2494019104525 + CONST033 = -52.2494019104525 + CONST035 = -48.4364919249939 + CONST036 = -41.3067791046458 + CONST037 = -36.3273689437454 + CONST038 = -29.3902885746295 + CONST039 = -27.0416345659799 + CONST040 = -26.1247009552263 + CONST041 = -26.1247009552263 + CONST042 = -19.5935257164197 + CONST043 = -2.42182459624970 + CONST044 = -9.79676285820985 + CONST045 = -7.15454401062709 + CONST046 = -3.38020432074749 + CONST047 = -1.12673477358250 + VAR07 = x * x * x + VAR08 = x * x + VAR04 = VAR07 * VAR07 + VAR05 = VAR07 * VAR08 + VAR06 = VAR08 * VAR08 + VAR16 = y * y * y + VAR17 = y * y + VAR13 = VAR16 * VAR16 + VAR14 = VAR16 * VAR17 + VAR15 = VAR17 * VAR17 + VAR25 = z * z * z + VAR26 = z * z + VAR22 = VAR25 * VAR25 + VAR23 = VAR25 * VAR26 + VAR24 = VAR26 * VAR26 + # -------------------- kernel implementations + Y00 = CONST011 * VAR05 * z + CONST011 * VAR23 * x + CONST035 * VAR07 * VAR25 + Y01 = y * (CONST006 * VAR05 + CONST025 * VAR24 * x + CONST027 * VAR07 * VAR26) + Y02 = ( + -CONST045 * VAR05 * z + + CONST045 * VAR23 * x + + VAR17 * (CONST030 * VAR07 * z - CONST030 * VAR25 * x) + ) + Y03 = VAR16 * (-CONST028 * VAR26 * x + CONST040 * VAR07) + y * ( + CONST007 * VAR05 + CONST038 * VAR24 * x + CONST042 * VAR07 * VAR26 + ) + Y04 = ( + CONST003 * VAR05 * z + + VAR07 * (CONST004 * VAR25 + CONST033 * VAR17 * z) + + x * (CONST002 * VAR23 - CONST032 * VAR15 * z + CONST032 * VAR17 * VAR25) + ) + Y05 = ( + CONST008 * VAR05 * y + + VAR07 * (CONST017 * VAR26 * y + CONST036 * VAR16) + + x * (CONST008 * VAR24 * y + CONST013 * VAR14 + CONST036 * VAR16 * VAR26) + ) + Y06 = ( + CONST009 * VAR13 + + CONST018 * VAR17 * VAR24 + + CONST039 * VAR15 * VAR26 + + CONST047 * VAR04 + + CONST047 * VAR22 + + VAR06 * (CONST018 * VAR17 + CONST046 * VAR26) + + VAR08 * (CONST024 * VAR17 * VAR26 + CONST039 * VAR15 + CONST046 * VAR24) + ) + Y07 = ( + CONST008 * VAR23 * y + + VAR25 * (CONST017 * VAR08 * y + CONST036 * VAR16) + + z * (CONST008 * VAR06 * y + CONST014 * VAR14 + CONST036 * VAR08 * VAR16) + ) + Y08 = ( + CONST026 * VAR04 + - CONST026 * VAR22 + + CONST040 * VAR17 * VAR24 + - CONST041 * VAR15 * VAR26 + + VAR06 * (CONST026 * VAR26 - CONST041 * VAR17) + + VAR08 * (-CONST026 * VAR24 + CONST041 * VAR15) + ) + Y09 = VAR16 * (CONST028 * VAR08 * z - CONST041 * VAR25) + y * ( + CONST022 * VAR06 * z - CONST042 * VAR08 * VAR25 + CONST044 * VAR23 + ) + Y10 = ( + CONST010 * VAR04 + + CONST010 * VAR22 + + CONST020 * VAR17 * VAR24 + + VAR06 * (CONST012 * VAR26 + CONST015 * VAR17) + + VAR08 * (CONST012 * VAR24 + CONST019 * VAR17 * VAR26) + ) + Y11 = y * (CONST006 * VAR23 + CONST025 * VAR06 * z + CONST027 * VAR08 * VAR25) + Y12 = ( + -CONST037 * VAR06 * VAR26 + + CONST037 * VAR08 * VAR24 + + CONST043 * VAR04 + - CONST043 * VAR22 + ) + output_stride = 13 # [2l + 1] + output_striding = tl.arange(0, block_size) * output_stride + output_row_offset = output_striding + (block_size * output_stride * block_id) + tl.store(output_ptr + output_row_offset, Y00, mask=output_row_offset < output_numel) + tl.store( + output_ptr + output_row_offset + 1, + Y01, + mask=output_row_offset + 1 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 2, + Y02, + mask=output_row_offset + 2 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 3, + Y03, + mask=output_row_offset + 3 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 4, + Y04, + mask=output_row_offset + 4 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 5, + Y05, + mask=output_row_offset + 5 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 6, + Y06, + mask=output_row_offset + 6 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 7, + Y07, + mask=output_row_offset + 7 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 8, + Y08, + mask=output_row_offset + 8 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 9, + Y09, + mask=output_row_offset + 9 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 10, + Y10, + mask=output_row_offset + 10 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 11, + Y11, + mask=output_row_offset + 11 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 12, + Y12, + mask=output_row_offset + 12 < output_numel, + ) + + +@triton.jit +def sixth_order_bwd( + coord_ptr: tl.tensor, + coord_grad_ptr: tl.tensor, + sph_grad_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, +): + # work out the row offsets + block_id = tl.program_id(0) + # these are hardcoded because they are predetermined; + coord_stride = 3 + coord_striding = tl.arange(0, block_size) * coord_stride + # as the name suggests, this is effectively every node/atom + coord_row_offset = coord_striding + (block_size * coord_stride * block_id) + x = tl.load(coord_ptr + coord_row_offset, mask=coord_row_offset < coord_numel) + y = tl.load( + coord_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + z = tl.load( + coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + output_stride = 13 # [2l + 1] + output_striding = tl.arange(0, block_size) * output_stride + output_row_offset = output_striding + (block_size * output_stride * block_id) + # load in gradients w.r.t. spherical harmonic projections + g_0 = tl.load( + sph_grad_ptr + output_row_offset, mask=output_row_offset < output_numel + ) + g_1 = tl.load( + sph_grad_ptr + output_row_offset + 1, mask=output_row_offset + 1 < output_numel + ) + g_2 = tl.load( + sph_grad_ptr + output_row_offset + 2, mask=output_row_offset + 2 < output_numel + ) + g_3 = tl.load( + sph_grad_ptr + output_row_offset + 3, mask=output_row_offset + 3 < output_numel + ) + g_4 = tl.load( + sph_grad_ptr + output_row_offset + 4, mask=output_row_offset + 4 < output_numel + ) + g_5 = tl.load( + sph_grad_ptr + output_row_offset + 5, mask=output_row_offset + 5 < output_numel + ) + g_6 = tl.load( + sph_grad_ptr + output_row_offset + 6, mask=output_row_offset + 6 < output_numel + ) + g_7 = tl.load( + sph_grad_ptr + output_row_offset + 7, mask=output_row_offset + 7 < output_numel + ) + g_8 = tl.load( + sph_grad_ptr + output_row_offset + 8, mask=output_row_offset + 8 < output_numel + ) + g_9 = tl.load( + sph_grad_ptr + output_row_offset + 9, mask=output_row_offset + 9 < output_numel + ) + g_10 = tl.load( + sph_grad_ptr + output_row_offset + 10, + mask=output_row_offset + 10 < output_numel, + ) + g_11 = tl.load( + sph_grad_ptr + output_row_offset + 11, + mask=output_row_offset + 11 < output_numel, + ) + g_12 = tl.load( + sph_grad_ptr + output_row_offset + 12, + mask=output_row_offset + 12 < output_numel, + ) + # -------------------- variable and constant definitions + CONST000 = 2.00000000000000 + CONST002 = 4.00000000000000 + CONST003 = 3.00000000000000 + CONST004 = 6.53117523880657 + CONST006 = 8.94318001328386 + CONST007 = 8.38944649544891 + CONST008 = 10.3266947761614 + CONST009 = 9.79676285820985 + CONST013 = 16.3279380970164 + CONST014 = 17.8863600265677 + CONST015 = 16.5227116418583 + CONST016 = 20.6533895523229 + CONST017 = 20.2812259244849 + CONST018 = 21.6333076527839 + CONST020 = 17.8863600265677 + CONST022 = 29.3902885746295 + CONST024 = 35.7727200531355 + CONST026 = 40.5624518489699 + CONST028 = 41.9472324772445 + CONST029 = 48.9838142910493 + CONST030 = 51.6334738808072 + CONST035 = 71.5454401062709 + CONST037 = 81.1249036979398 + CONST039 = 82.6135582092915 + CONST040 = -3.26558761940328 + CONST042 = 117.561154298518 + CONST046 = 208.997607641810 + CONST048 = -251.683394863467 + CONST049 = -214.636320318813 + CONST050 = -214.636320318813 + CONST051 = 16.5227116418583 + CONST052 = -167.788929908978 + CONST053 = -156.748205731358 + CONST054 = -145.309475774982 + CONST055 = -123.920337313937 + CONST056 = -117.561154298518 + CONST057 = 3.26558761940328 + CONST058 = -108.166538263920 + CONST059 = -107.318160159406 + CONST060 = -104.498803820905 + CONST061 = -104.498803820905 + CONST062 = -83.8944649544891 + CONST063 = -82.6135582092915 + CONST064 = -78.3741028656788 + CONST065 = -72.6547378874909 + CONST066 = -71.5454401062709 + CONST067 = -58.7805771492591 + CONST068 = -54.0832691319598 + CONST069 = -52.2494019104525 + CONST070 = -52.2494019104525 + CONST071 = -48.9838142910492 + CONST072 = -41.3067791046458 + CONST073 = -39.1870514328394 + CONST074 = -35.7727200531355 + CONST075 = -29.3902885746295 + CONST076 = -27.0416345659799 + CONST077 = -26.1247009552263 + CONST078 = -26.1247009552263 + CONST079 = -19.5935257164197 + CONST080 = -14.5309475774982 + CONST081 = -13.5208172829900 + CONST082 = -10.7318160159406 + CONST083 = -9.79676285820985 + CONST084 = -7.15454401062709 + CONST085 = -6.76040864149498 + CONST086 = -3.38020432074749 + CONST087 = -1.63279380970164 + VAR07 = x * x * x + VAR08 = x * x + VAR05 = VAR07 * VAR08 + VAR06 = VAR08 * VAR08 + VAR16 = y * y * y + VAR17 = y * y + VAR14 = VAR16 * VAR17 + VAR15 = VAR17 * VAR17 + VAR25 = z * z * z + VAR26 = z * z + VAR23 = VAR25 * VAR26 + VAR24 = VAR26 * VAR26 + # -------------------- kernel implementations + g_x = ( + g_0 * (CONST054 * VAR08 * VAR25 - CONST065 * VAR06 * z - CONST080 * VAR23) + + g_1 * y * (CONST028 * VAR06 + CONST028 * VAR24 + CONST048 * VAR08 * VAR26) + + g_10 + * ( + CONST000 * x * (CONST006 * VAR24 + CONST059 * VAR17 * VAR26) + + CONST002 * VAR07 * (CONST006 * VAR26 + CONST014 * VAR17) + + CONST082 * VAR05 + ) + + g_11 * y * (-CONST052 * VAR07 * z + CONST052 * VAR25 * x) + + g_12 * (-CONST054 * VAR07 * VAR26 + CONST065 * VAR24 * x + CONST080 * VAR05) + + g_2 + * ( + -CONST074 * VAR06 * z + + CONST084 * VAR23 + + VAR17 * (CONST049 * VAR08 * z - CONST066 * VAR25) + ) + + g_3 + * ( + VAR16 * (CONST064 * VAR08 - CONST064 * VAR26) + + y * (CONST029 * VAR06 + CONST067 * VAR08 * VAR26 + CONST075 * VAR24) + ) + + g_4 + * ( + CONST003 * VAR08 * (CONST004 * VAR25 + CONST069 * VAR17 * z) + + CONST013 * VAR06 * z + - CONST040 * VAR23 + - CONST070 * VAR15 * z + + CONST070 * VAR17 * VAR25 + ) + + g_5 + * ( + CONST003 * VAR08 * (CONST016 * VAR26 * y + CONST072 * VAR16) + + CONST008 * VAR24 * y + + CONST015 * VAR14 + + CONST030 * VAR06 * y + + CONST072 * VAR16 * VAR26 + ) + + g_6 + * ( + CONST000 + * x + * (CONST026 * VAR17 * VAR26 + CONST076 * VAR15 + CONST086 * VAR24) + + CONST002 * VAR07 * (CONST017 * VAR17 + CONST086 * VAR26) + + CONST085 * VAR05 + ) + + g_7 + * ( + -CONST072 * VAR25 * x * y + + z * (CONST063 * VAR16 * x - CONST072 * VAR07 * y) + ) + + g_8 + * ( + CONST000 * x * (CONST077 * VAR15 - CONST087 * VAR24) + + CONST002 * VAR07 * (-CONST077 * VAR17 + CONST087 * VAR26) + + CONST083 * VAR05 + ) + + g_9 + * (CONST053 * VAR16 * x * z + y * (CONST042 * VAR07 * z - CONST073 * VAR25 * x)) + ) + g_y = ( + CONST000 * g_2 * y * (CONST066 * VAR07 * z - CONST066 * VAR25 * x) + + g_1 * (CONST007 * VAR05 + CONST028 * VAR24 * x + CONST062 * VAR07 * VAR26) + + g_10 + * (CONST024 * VAR06 * y + CONST050 * VAR08 * VAR26 * y - CONST074 * VAR24 * y) + + g_11 * (CONST007 * VAR23 + CONST028 * VAR06 * z + CONST062 * VAR08 * VAR25) + + g_3 + * ( + CONST003 * VAR17 * (-CONST064 * VAR26 * x + CONST078 * VAR07) + + CONST009 * VAR05 + + CONST075 * VAR24 * x + + CONST079 * VAR07 * VAR26 + ) + + g_4 + * (CONST061 * VAR07 * y * z + x * (CONST046 * VAR16 * z + CONST060 * VAR25 * y)) + + g_5 + * ( + CONST008 * VAR05 + + VAR07 * (CONST016 * VAR26 + CONST055 * VAR17) + + x * (CONST008 * VAR24 + CONST055 * VAR17 * VAR26 - CONST063 * VAR15) + ) + + g_6 + * ( + CONST018 * VAR14 + + CONST026 * VAR06 * y + + CONST026 * VAR24 * y + + CONST058 * VAR16 * VAR26 + + VAR08 * (CONST037 * VAR26 * y + CONST058 * VAR16) + ) + + g_7 + * ( + CONST008 * VAR23 + + VAR25 * (CONST016 * VAR08 + CONST055 * VAR17) + + z * (CONST008 * VAR06 + CONST039 * VAR15 + CONST055 * VAR08 * VAR17) + ) + + g_8 + * ( + CONST060 * VAR08 * VAR16 + - CONST060 * VAR16 * VAR26 + + CONST069 * VAR24 * y + - CONST070 * VAR06 * y + ) + + g_9 + * ( + CONST003 * VAR17 * (CONST064 * VAR08 * z - CONST077 * VAR25) + + CONST022 * VAR06 * z + - CONST079 * VAR08 * VAR25 + + CONST083 * VAR23 + ) + ) + g_z = ( + g_0 * (CONST054 * VAR07 * VAR26 - CONST065 * VAR24 * x - CONST080 * VAR05) + + g_1 * y * (CONST052 * VAR07 * z - CONST052 * VAR25 * x) + + g_10 + * ( + CONST020 * VAR06 * z + + CONST035 * VAR17 * VAR25 + + CONST082 * VAR23 + + VAR08 * (CONST050 * VAR17 * z - CONST074 * VAR25) + ) + + g_11 * y * (CONST028 * VAR06 + CONST028 * VAR24 + CONST048 * VAR08 * VAR26) + + g_12 * (CONST054 * VAR08 * VAR25 - CONST065 * VAR06 * z - CONST080 * VAR23) + + g_2 + * ( + CONST074 * VAR24 * x + - CONST084 * VAR05 + + VAR17 * (-CONST049 * VAR26 * x + CONST066 * VAR07) + ) + + g_3 + * ( + -CONST053 * VAR16 * x * z + + y * (CONST056 * VAR25 * x + CONST073 * VAR07 * z) + ) + + g_4 + * ( + CONST057 * VAR05 + + VAR07 * (CONST069 * VAR17 - CONST079 * VAR26) + + x * (CONST013 * VAR24 + CONST053 * VAR17 * VAR26 - CONST070 * VAR15) + ) + + g_5 + * ( + -CONST072 * VAR07 * y * z + + x * (CONST063 * VAR16 * z - CONST072 * VAR25 * y) + ) + + g_6 + * ( + CONST037 * VAR17 * VAR25 + + CONST068 * VAR15 * z + + CONST085 * VAR06 * z + + CONST085 * VAR23 + + VAR08 * (CONST037 * VAR17 * z + CONST081 * VAR25) + ) + + g_7 + * ( + CONST003 * VAR26 * (CONST016 * VAR08 * y + CONST072 * VAR16) + + CONST008 * VAR06 * y + + CONST030 * VAR24 * y + + CONST051 * VAR14 + + CONST072 * VAR08 * VAR16 + ) + + g_8 + * ( + CONST004 * VAR08 * VAR25 + + CONST040 * VAR06 * z + + CONST061 * VAR17 * VAR25 + - CONST070 * VAR15 * z + - CONST083 * VAR23 + ) + + g_9 + * ( + VAR16 * (CONST064 * VAR08 - CONST064 * VAR26) + + y * (CONST022 * VAR06 - CONST067 * VAR08 * VAR26 + CONST071 * VAR24) + ) + ) + # write out gradients + tl.store( + coord_grad_ptr + coord_row_offset, g_x, mask=coord_row_offset < coord_numel + ) + tl.store( + coord_grad_ptr + coord_row_offset + 1, + g_y, + mask=coord_row_offset + 1 < coord_numel, + ) + tl.store( + coord_grad_ptr + coord_row_offset + 2, + g_z, + mask=coord_row_offset + 2 < coord_numel, + ) From e1dd9b97544333f8877b3a4584747c7bb5bc72a0 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sat, 24 Aug 2024 10:13:34 -0700 Subject: [PATCH 027/116] test: added parameters for sixth order test --- src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py index fe245c8..d30a953 100644 --- a/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py +++ b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py @@ -10,7 +10,7 @@ torch.manual_seed(316165) -@pytest.mark.parametrize("order", [2, 3, 4, 5, 10]) +@pytest.mark.parametrize("order", [2, 3, 4, 5, 6, 10]) @pytest.mark.parametrize( "device", [ @@ -48,7 +48,7 @@ def test_forward_equivalence(order, device, tensor_shape, dtype): assert torch.allclose(triton_out, torch_out, atol=1e-5, rtol=1e-3) -@pytest.mark.parametrize("order", [2, 3, 4, 5, 10]) +@pytest.mark.parametrize("order", [2, 3, 4, 5, 6, 10]) @pytest.mark.parametrize( "device", [ From 7b7c1b77360b4d8873977f20590361fe06748dae Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sat, 24 Aug 2024 10:32:15 -0700 Subject: [PATCH 028/116] feat: added seventh order terms --- src/equitriton/sph_harm/direct/y_7.py | 1059 +++++++++++++++++++++++++ 1 file changed, 1059 insertions(+) create mode 100644 src/equitriton/sph_harm/direct/y_7.py diff --git a/src/equitriton/sph_harm/direct/y_7.py b/src/equitriton/sph_harm/direct/y_7.py new file mode 100644 index 0000000..583994b --- /dev/null +++ b/src/equitriton/sph_harm/direct/y_7.py @@ -0,0 +1,1059 @@ +import triton +import torch +from triton import language as tl + +from equitriton.utils import calculate_lastdim_num_blocks + +__all__ = ["SeventhOrderSphericalHarmonic"] + + +class SeventhOrderSphericalHarmonic(torch.autograd.Function): + @staticmethod + def forward( + ctx, + coords: torch.Tensor, + mask: torch.Tensor | None = None, + block_size: int = 64, + ): + output_tensor = torch.empty( + (*coords.shape[:-1], 15), dtype=coords.dtype, device=coords.device + ) + coord_numel = coords.numel() + output_numel = output_tensor.numel() + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + # apply the kernel + seventh_order_fwd[num_blocks,]( + coords, output_tensor, block_size, coord_numel, output_numel + ) + ctx.save_for_backward(coords) + return output_tensor + + @staticmethod + def backward( + ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 + ) -> torch.Tensor: + (coords,) = ctx.saved_tensors + coord_grad_output = torch.zeros_like(coords) + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + # call backward kernel + seventh_order_bwd[num_blocks,]( + coords, + coord_grad_output, + sph_grad_tensor, + block_size, + coords.numel(), + sph_grad_tensor.numel(), + ) + return coord_grad_output + + +def _torch_fwd(coords: torch.Tensor) -> torch.Tensor: + """ + PyTorch implementation of the kernel. This is designed + purely for unit testing to ensure that the Triton implementation + is behaving as intended. + + Parameters + ---------- + coords : torch.Tensor + N-d tensor, where the last dimension corresponds to + xyz values. + + Returns + ------- + torch.Tensor + N-d tensor, where the last dimension corresponds to + each projection of the second order spherical harmonic. + """ + x = coords[..., 0].contiguous().unsqueeze(-1) + y = coords[..., 1].contiguous().unsqueeze(-1) + z = coords[..., 2].contiguous().unsqueeze(-1) + # -------------------- variable and constant definitions + CONST002 = 3.87298334620742 + CONST008 = 11.7655316231354 + CONST010 = 16.5555704843566 + CONST012 = 20.4939015319192 + CONST013 = 20.4939015319192 + CONST014 = 22.0740939791422 + CONST015 = 23.5310632462709 + CONST017 = 36.7901566319036 + CONST019 = 38.4260653723485 + CONST020 = 38.4260653723485 + CONST021 = 38.4260653723485 + CONST023 = -4.99169231699030 + CONST025 = 47.0621264925418 + CONST026 = 50.8329064189723 + CONST028 = 55.1852349478554 + CONST029 = 56.2781179722634 + CONST030 = 56.2781179722634 + CONST032 = 66.5558975598707 + CONST033 = 75.2994023880668 + CONST037 = 101.665812837945 + CONST038 = 110.370469895711 + CONST041 = 147.160626527614 + CONST042 = -1.66389743899677 + CONST043 = -9.37968632871057 + CONST044 = -1.66389743899677 + CONST045 = -220.740939791422 + CONST046 = -220.740939791422 + CONST047 = -1.60108605718119 + CONST048 = -187.593726574211 + CONST049 = -9.19753915797590 + CONST050 = -1.83950783159518 + CONST051 = -1.83950783159518 + CONST052 = -4.80325817154356 + CONST053 = -147.160626527614 + CONST054 = -140.695294930659 + CONST055 = -133.111795119741 + CONST056 = -125.499003980111 + CONST057 = -125.499003980111 + CONST058 = -99.8338463398060 + CONST059 = -87.7389315936062 + CONST060 = -76.8521307446970 + CONST061 = -66.5558975598707 + CONST062 = -62.7495019900557 + CONST063 = -52.6433589561637 + CONST064 = -44.1481879582843 + CONST065 = -44.3705983732471 + CONST066 = -40.6663251351779 + CONST067 = -40.6663251351779 + CONST068 = -8.31948719498384 + CONST069 = -37.6497011940334 + CONST070 = -33.2779487799353 + CONST071 = -25.4164532094862 + CONST072 = -25.4164532094862 + CONST073 = -17.5477863187212 + CONST074 = -11.7655316231354 + CONST075 = -11.0370469895711 + CONST076 = -9.19753915797590 + CONST077 = -8.47215106982872 + CONST078 = -4.80325817154356 + CONST079 = -2.50682661696018 + CONST080 = -1.60108605718119 + VAR06 = x * x * x * x + VAR07 = x * x * x + VAR08 = x * x + VAR03 = VAR06 * VAR07 + VAR04 = VAR07 * VAR07 + VAR05 = VAR07 * VAR08 + VAR15 = y * y * y * y + VAR16 = y * y * y + VAR17 = y * y + VAR12 = VAR15 * VAR16 + VAR13 = VAR16 * VAR16 + VAR14 = VAR16 * VAR17 + VAR24 = z * z * z * z + VAR25 = z * z * z + VAR26 = z * z + VAR21 = VAR24 * VAR25 + VAR22 = VAR25 * VAR25 + VAR23 = VAR25 * VAR26 + # -------------------- kernel implementations + Y00 = ( + CONST059 * VAR07 * VAR24 + - CONST063 * VAR05 * VAR26 + - CONST073 * VAR22 * x + + CONST079 * VAR03 + ) + Y01 = y * (CONST029 * VAR23 * x + CONST030 * VAR05 * z + CONST048 * VAR07 * VAR25) + Y02 = ( + CONST050 * VAR03 + + VAR05 * (CONST010 * VAR26 + CONST014 * VAR17) + + VAR07 * (CONST045 * VAR17 * VAR26 - CONST076 * VAR24) + + x * (CONST038 * VAR17 * VAR24 + CONST076 * VAR22) + ) + Y03 = VAR16 * (CONST041 * VAR25 * x + CONST053 * VAR07 * z) + y * ( + -CONST064 * VAR05 * z + CONST064 * VAR23 * x + ) + Y04 = ( + CONST042 * VAR03 + + VAR05 * (-CONST042 * VAR26 - CONST070 * VAR17) + + VAR07 * (CONST061 * VAR17 * VAR26 + CONST065 * VAR15 - CONST068 * VAR24) + + x * (-CONST023 * VAR22 - CONST055 * VAR15 * VAR26 + CONST058 * VAR17 * VAR24) + ) + Y05 = ( + CONST015 * VAR05 * y * z + + VAR07 * (CONST025 * VAR25 * y + CONST057 * VAR16 * z) + + x * (CONST015 * VAR23 * y + CONST033 * VAR14 * z + CONST056 * VAR16 * VAR25) + ) + Y06 = ( + CONST047 * VAR03 + + VAR05 * (CONST020 * VAR17 + CONST078 * VAR26) + + VAR07 * (CONST052 * VAR24 + CONST060 * VAR15 - CONST060 * VAR17 * VAR26) + + x + * ( + CONST012 * VAR13 + + CONST019 * VAR17 * VAR24 + + CONST060 * VAR15 * VAR26 + + CONST080 * VAR22 + ) + ) + Y07 = ( + CONST002 * VAR12 + + VAR14 * (CONST066 * VAR08 + CONST067 * VAR26) + + VAR16 * (CONST026 * VAR06 + CONST026 * VAR24 + CONST037 * VAR08 * VAR26) + + y + * ( + CONST071 * VAR06 * VAR26 + + CONST072 * VAR08 * VAR24 + + CONST077 * VAR04 + + CONST077 * VAR22 + ) + ) + Y08 = ( + CONST047 * VAR21 + + VAR23 * (CONST020 * VAR17 + CONST052 * VAR08) + + VAR25 * (CONST052 * VAR06 - CONST060 * VAR08 * VAR17 + CONST060 * VAR15) + + z + * ( + CONST013 * VAR13 + + CONST021 * VAR06 * VAR17 + + CONST047 * VAR04 + + CONST060 * VAR08 * VAR15 + ) + ) + Y09 = ( + VAR14 * (CONST069 * VAR08 - CONST069 * VAR26) + + VAR16 * (-CONST062 * VAR06 + CONST062 * VAR24) + + y + * ( + CONST008 * VAR08 * VAR24 + + CONST074 * VAR04 + + CONST074 * VAR06 * VAR26 + - CONST074 * VAR22 + ) + ) + Y10 = ( + -CONST042 * VAR21 + + VAR23 * (CONST044 * VAR08 + CONST070 * VAR17) + + VAR25 * (CONST032 * VAR08 * VAR17 - CONST065 * VAR15 + CONST068 * VAR06) + + z * (CONST023 * VAR04 + CONST055 * VAR08 * VAR15 - CONST058 * VAR06 * VAR17) + ) + Y11 = VAR16 * ( + CONST017 * VAR06 + CONST017 * VAR24 + CONST046 * VAR08 * VAR26 + ) + y * ( + CONST028 * VAR06 * VAR26 + + CONST028 * VAR08 * VAR24 + + CONST075 * VAR04 + + CONST075 * VAR22 + ) + Y12 = ( + CONST051 * VAR21 + + VAR23 * (CONST010 * VAR08 + CONST014 * VAR17) + + VAR25 * (CONST045 * VAR08 * VAR17 - CONST049 * VAR06) + + z * (CONST038 * VAR06 * VAR17 + CONST049 * VAR04) + ) + Y13 = y * ( + CONST043 * VAR04 + - CONST043 * VAR22 + - CONST054 * VAR06 * VAR26 + + CONST054 * VAR08 * VAR24 + ) + Y14 = ( + -CONST059 * VAR06 * VAR25 + + CONST063 * VAR08 * VAR23 + + CONST073 * VAR04 * z + - CONST079 * VAR21 + ) + # not the prettiest way to concatenate, but better than + # messing with the linter + tensors = [ + Y00, + Y01, + Y02, + Y03, + Y04, + Y05, + Y06, + Y07, + Y08, + Y09, + Y10, + Y11, + Y12, + Y13, + Y14, + ] + return torch.cat(tensors, dim=-1) + + +@triton.jit +def seventh_order_fwd( + coord_ptr: tl.tensor, + output_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, +): + # these are hardcoded because they are predetermined; + coord_stride = 3 + # work out the row offsets + block_id = tl.program_id(0) + coord_striding = tl.arange(0, block_size) * coord_stride + # as the name suggests, this is effectively every node/atom + coord_row_offset = coord_striding + (block_size * coord_stride * block_id) + x = tl.load(coord_ptr + coord_row_offset, mask=coord_row_offset < coord_numel) + y = tl.load( + coord_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + z = tl.load( + coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + # -------------------- variable and constant definitions + CONST002 = 3.87298334620742 + CONST008 = 11.7655316231354 + CONST010 = 16.5555704843566 + CONST012 = 20.4939015319192 + CONST013 = 20.4939015319192 + CONST014 = 22.0740939791422 + CONST015 = 23.5310632462709 + CONST017 = 36.7901566319036 + CONST019 = 38.4260653723485 + CONST020 = 38.4260653723485 + CONST021 = 38.4260653723485 + CONST023 = -4.99169231699030 + CONST025 = 47.0621264925418 + CONST026 = 50.8329064189723 + CONST028 = 55.1852349478554 + CONST029 = 56.2781179722634 + CONST030 = 56.2781179722634 + CONST032 = 66.5558975598707 + CONST033 = 75.2994023880668 + CONST037 = 101.665812837945 + CONST038 = 110.370469895711 + CONST041 = 147.160626527614 + CONST042 = -1.66389743899677 + CONST043 = -9.37968632871057 + CONST044 = -1.66389743899677 + CONST045 = -220.740939791422 + CONST046 = -220.740939791422 + CONST047 = -1.60108605718119 + CONST048 = -187.593726574211 + CONST049 = -9.19753915797590 + CONST050 = -1.83950783159518 + CONST051 = -1.83950783159518 + CONST052 = -4.80325817154356 + CONST053 = -147.160626527614 + CONST054 = -140.695294930659 + CONST055 = -133.111795119741 + CONST056 = -125.499003980111 + CONST057 = -125.499003980111 + CONST058 = -99.8338463398060 + CONST059 = -87.7389315936062 + CONST060 = -76.8521307446970 + CONST061 = -66.5558975598707 + CONST062 = -62.7495019900557 + CONST063 = -52.6433589561637 + CONST064 = -44.1481879582843 + CONST065 = -44.3705983732471 + CONST066 = -40.6663251351779 + CONST067 = -40.6663251351779 + CONST068 = -8.31948719498384 + CONST069 = -37.6497011940334 + CONST070 = -33.2779487799353 + CONST071 = -25.4164532094862 + CONST072 = -25.4164532094862 + CONST073 = -17.5477863187212 + CONST074 = -11.7655316231354 + CONST075 = -11.0370469895711 + CONST076 = -9.19753915797590 + CONST077 = -8.47215106982872 + CONST078 = -4.80325817154356 + CONST079 = -2.50682661696018 + CONST080 = -1.60108605718119 + VAR06 = x * x * x * x + VAR07 = x * x * x + VAR08 = x * x + VAR03 = VAR06 * VAR07 + VAR04 = VAR07 * VAR07 + VAR05 = VAR07 * VAR08 + VAR15 = y * y * y * y + VAR16 = y * y * y + VAR17 = y * y + VAR12 = VAR15 * VAR16 + VAR13 = VAR16 * VAR16 + VAR14 = VAR16 * VAR17 + VAR24 = z * z * z * z + VAR25 = z * z * z + VAR26 = z * z + VAR21 = VAR24 * VAR25 + VAR22 = VAR25 * VAR25 + VAR23 = VAR25 * VAR26 + # -------------------- kernel implementations + Y00 = ( + CONST059 * VAR07 * VAR24 + - CONST063 * VAR05 * VAR26 + - CONST073 * VAR22 * x + + CONST079 * VAR03 + ) + Y01 = y * (CONST029 * VAR23 * x + CONST030 * VAR05 * z + CONST048 * VAR07 * VAR25) + Y02 = ( + CONST050 * VAR03 + + VAR05 * (CONST010 * VAR26 + CONST014 * VAR17) + + VAR07 * (CONST045 * VAR17 * VAR26 - CONST076 * VAR24) + + x * (CONST038 * VAR17 * VAR24 + CONST076 * VAR22) + ) + Y03 = VAR16 * (CONST041 * VAR25 * x + CONST053 * VAR07 * z) + y * ( + -CONST064 * VAR05 * z + CONST064 * VAR23 * x + ) + Y04 = ( + CONST042 * VAR03 + + VAR05 * (-CONST042 * VAR26 - CONST070 * VAR17) + + VAR07 * (CONST061 * VAR17 * VAR26 + CONST065 * VAR15 - CONST068 * VAR24) + + x * (-CONST023 * VAR22 - CONST055 * VAR15 * VAR26 + CONST058 * VAR17 * VAR24) + ) + Y05 = ( + CONST015 * VAR05 * y * z + + VAR07 * (CONST025 * VAR25 * y + CONST057 * VAR16 * z) + + x * (CONST015 * VAR23 * y + CONST033 * VAR14 * z + CONST056 * VAR16 * VAR25) + ) + Y06 = ( + CONST047 * VAR03 + + VAR05 * (CONST020 * VAR17 + CONST078 * VAR26) + + VAR07 * (CONST052 * VAR24 + CONST060 * VAR15 - CONST060 * VAR17 * VAR26) + + x + * ( + CONST012 * VAR13 + + CONST019 * VAR17 * VAR24 + + CONST060 * VAR15 * VAR26 + + CONST080 * VAR22 + ) + ) + Y07 = ( + CONST002 * VAR12 + + VAR14 * (CONST066 * VAR08 + CONST067 * VAR26) + + VAR16 * (CONST026 * VAR06 + CONST026 * VAR24 + CONST037 * VAR08 * VAR26) + + y + * ( + CONST071 * VAR06 * VAR26 + + CONST072 * VAR08 * VAR24 + + CONST077 * VAR04 + + CONST077 * VAR22 + ) + ) + Y08 = ( + CONST047 * VAR21 + + VAR23 * (CONST020 * VAR17 + CONST052 * VAR08) + + VAR25 * (CONST052 * VAR06 - CONST060 * VAR08 * VAR17 + CONST060 * VAR15) + + z + * ( + CONST013 * VAR13 + + CONST021 * VAR06 * VAR17 + + CONST047 * VAR04 + + CONST060 * VAR08 * VAR15 + ) + ) + Y09 = ( + VAR14 * (CONST069 * VAR08 - CONST069 * VAR26) + + VAR16 * (-CONST062 * VAR06 + CONST062 * VAR24) + + y + * ( + CONST008 * VAR08 * VAR24 + + CONST074 * VAR04 + + CONST074 * VAR06 * VAR26 + - CONST074 * VAR22 + ) + ) + Y10 = ( + -CONST042 * VAR21 + + VAR23 * (CONST044 * VAR08 + CONST070 * VAR17) + + VAR25 * (CONST032 * VAR08 * VAR17 - CONST065 * VAR15 + CONST068 * VAR06) + + z * (CONST023 * VAR04 + CONST055 * VAR08 * VAR15 - CONST058 * VAR06 * VAR17) + ) + Y11 = VAR16 * ( + CONST017 * VAR06 + CONST017 * VAR24 + CONST046 * VAR08 * VAR26 + ) + y * ( + CONST028 * VAR06 * VAR26 + + CONST028 * VAR08 * VAR24 + + CONST075 * VAR04 + + CONST075 * VAR22 + ) + Y12 = ( + CONST051 * VAR21 + + VAR23 * (CONST010 * VAR08 + CONST014 * VAR17) + + VAR25 * (CONST045 * VAR08 * VAR17 - CONST049 * VAR06) + + z * (CONST038 * VAR06 * VAR17 + CONST049 * VAR04) + ) + Y13 = y * ( + CONST043 * VAR04 + - CONST043 * VAR22 + - CONST054 * VAR06 * VAR26 + + CONST054 * VAR08 * VAR24 + ) + Y14 = ( + -CONST059 * VAR06 * VAR25 + + CONST063 * VAR08 * VAR23 + + CONST073 * VAR04 * z + - CONST079 * VAR21 + ) + output_stride = 15 # [2l + 1] + output_striding = tl.arange(0, block_size) * output_stride + output_row_offset = output_striding + (block_size * output_stride * block_id) + tl.store(output_ptr + output_row_offset, Y00, mask=output_row_offset < output_numel) + tl.store( + output_ptr + output_row_offset + 1, + Y01, + mask=output_row_offset + 1 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 2, + Y02, + mask=output_row_offset + 2 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 3, + Y03, + mask=output_row_offset + 3 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 4, + Y04, + mask=output_row_offset + 4 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 5, + Y05, + mask=output_row_offset + 5 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 6, + Y06, + mask=output_row_offset + 6 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 7, + Y07, + mask=output_row_offset + 7 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 8, + Y08, + mask=output_row_offset + 8 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 9, + Y09, + mask=output_row_offset + 9 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 10, + Y10, + mask=output_row_offset + 10 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 11, + Y11, + mask=output_row_offset + 11 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 12, + Y12, + mask=output_row_offset + 12 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 13, + Y13, + mask=output_row_offset + 13 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 14, + Y14, + mask=output_row_offset + 14 < output_numel, + ) + + +@triton.jit +def seventh_order_bwd( + coord_ptr: tl.tensor, + coord_grad_ptr: tl.tensor, + sph_grad_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, +): + # work out the row offsets + block_id = tl.program_id(0) + # these are hardcoded because they are predetermined; + coord_stride = 3 + coord_striding = tl.arange(0, block_size) * coord_stride + # as the name suggests, this is effectively every node/atom + coord_row_offset = coord_striding + (block_size * coord_stride * block_id) + x = tl.load(coord_ptr + coord_row_offset, mask=coord_row_offset < coord_numel) + y = tl.load( + coord_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + z = tl.load( + coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + output_stride = 15 # [2l + 1] + output_striding = tl.arange(0, block_size) * output_stride + output_row_offset = output_striding + (block_size * output_stride * block_id) + # load in gradients w.r.t. spherical harmonic projections + g_0 = tl.load( + sph_grad_ptr + output_row_offset, mask=output_row_offset < output_numel + ) + g_1 = tl.load( + sph_grad_ptr + output_row_offset + 1, mask=output_row_offset + 1 < output_numel + ) + g_2 = tl.load( + sph_grad_ptr + output_row_offset + 2, mask=output_row_offset + 2 < output_numel + ) + g_3 = tl.load( + sph_grad_ptr + output_row_offset + 3, mask=output_row_offset + 3 < output_numel + ) + g_4 = tl.load( + sph_grad_ptr + output_row_offset + 4, mask=output_row_offset + 4 < output_numel + ) + g_5 = tl.load( + sph_grad_ptr + output_row_offset + 5, mask=output_row_offset + 5 < output_numel + ) + g_6 = tl.load( + sph_grad_ptr + output_row_offset + 6, mask=output_row_offset + 6 < output_numel + ) + g_7 = tl.load( + sph_grad_ptr + output_row_offset + 7, mask=output_row_offset + 7 < output_numel + ) + g_8 = tl.load( + sph_grad_ptr + output_row_offset + 8, mask=output_row_offset + 8 < output_numel + ) + g_9 = tl.load( + sph_grad_ptr + output_row_offset + 9, mask=output_row_offset + 9 < output_numel + ) + g_10 = tl.load( + sph_grad_ptr + output_row_offset + 10, + mask=output_row_offset + 10 < output_numel, + ) + g_11 = tl.load( + sph_grad_ptr + output_row_offset + 11, + mask=output_row_offset + 11 < output_numel, + ) + g_12 = tl.load( + sph_grad_ptr + output_row_offset + 12, + mask=output_row_offset + 12 < output_numel, + ) + g_13 = tl.load( + sph_grad_ptr + output_row_offset + 13, + mask=output_row_offset + 13 < output_numel, + ) + g_14 = tl.load( + sph_grad_ptr + output_row_offset + 14, + mask=output_row_offset + 14 < output_numel, + ) + # -------------------- variable and constant definitions + CONST000 = 1.66389743899677 + CONST001 = 3.00000000000000 + CONST003 = 5.00000000000000 + CONST004 = 3.32779487799353 + CONST009 = 11.7655316231354 + CONST012 = 16.5555704843566 + CONST014 = 20.4939015319192 + CONST016 = 22.0740939791422 + CONST018 = 23.5310632462709 + CONST019 = 20.4939015319192 + CONST020 = 27.1108834234519 + CONST022 = 33.1111409687132 + CONST024 = 36.7901566319036 + CONST025 = 36.7901566319036 + CONST026 = 38.4260653723485 + CONST027 = 38.4260653723485 + CONST029 = 38.4260653723485 + CONST030 = 44.1481879582843 + CONST032 = -4.99169231699030 + CONST037 = 47.0621264925417 + CONST039 = 56.2781179722634 + CONST044 = -441.481879582843 + CONST045 = -441.481879582843 + CONST048 = 76.8521307446970 + CONST049 = 76.8521307446970 + CONST050 = -8.47215106982872 + CONST054 = 110.370469895711 + CONST055 = 110.370469895711 + CONST056 = -399.335385359224 + CONST057 = 117.655316231354 + CONST058 = 122.963409191515 + CONST059 = 122.963409191515 + CONST061 = -376.497011940334 + CONST062 = -376.497011940334 + CONST064 = 141.186379477625 + CONST066 = 147.160626527614 + CONST067 = 153.704261489394 + CONST069 = -350.955726374425 + CONST072 = 203.331625675889 + CONST073 = 203.331625675889 + CONST074 = -307.408522978788 + CONST075 = -9.60651634308713 + CONST076 = -9.37968632871057 + CONST079 = -281.390589861317 + CONST080 = -1.66389743899677 + CONST081 = -266.223590239483 + CONST082 = -263.216794780819 + CONST084 = -263.216794780818 + CONST085 = -250.998007960223 + CONST089 = 281.390589861317 + CONST091 = -220.740939791422 + CONST092 = -220.740939791422 + CONST093 = -199.667692679612 + CONST094 = -1.60108605718119 + CONST095 = -187.593726574211 + CONST096 = -177.482393492989 + CONST097 = -9.60651634308712 + CONST098 = -9.19753915797590 + CONST100 = -153.704261489394 + CONST101 = -147.160626527614 + CONST102 = -140.695294930659 + CONST104 = -133.111795119741 + CONST105 = -133.111795119741 + CONST106 = -125.499003980111 + CONST107 = -125.499003980111 + CONST109 = -105.286717912327 + CONST110 = -101.665812837945 + CONST111 = -99.8338463398060 + CONST112 = -101.665812837945 + CONST113 = -4.80325817154356 + CONST114 = -81.3326502703558 + CONST115 = -81.3326502703557 + CONST116 = -76.8521307446970 + CONST117 = -75.2994023880668 + CONST119 = -70.5931897388126 + CONST121 = -66.2222819374265 + CONST122 = -66.5558975598707 + CONST123 = -66.5558975598707 + CONST124 = -62.7495019900557 + CONST125 = -56.2781179722634 + CONST126 = -55.1852349478554 + CONST127 = -55.1852349478554 + CONST128 = -50.8329064189723 + CONST129 = -50.8329064189723 + CONST130 = -562.781179722634 + CONST131 = -47.0621264925418 + CONST132 = -50.8329064189724 + CONST133 = -44.1481879582843 + CONST134 = -44.3705983732471 + CONST135 = -40.6663251351779 + CONST136 = -40.6663251351779 + CONST137 = -8.31948719498384 + CONST138 = -37.6497011940334 + CONST139 = -33.2779487799353 + CONST140 = -29.9501539019418 + CONST141 = -25.4164532094862 + CONST142 = -25.4164532094862 + CONST143 = -23.5310632462709 + CONST144 = -532.447180478965 + CONST145 = -19.2130326861743 + CONST146 = -17.5477863187212 + CONST147 = -12.8765548211663 + CONST148 = -11.6472820729774 + CONST149 = -11.2076024002683 + CONST150 = -9.19753915797590 + CONST151 = -11.0370469895711 + CONST152 = -11.7655316231354 + CONST153 = -12.8765548211663 + CONST154 = -4.80325817154356 + CONST155 = -3.32779487799353 + CONST156 = -1.60108605718119 + VAR06 = x * x * x * x + VAR07 = x * x * x + VAR08 = x * x + VAR04 = VAR07 * VAR07 + VAR05 = VAR07 * VAR08 + VAR16 = y * y * y + VAR17 = y * y + VAR13 = VAR16 * VAR16 + VAR14 = VAR16 * VAR17 + VAR15 = VAR17 * VAR17 + VAR25 = z * z * z + VAR26 = z * z + VAR22 = VAR25 * VAR25 + VAR23 = VAR25 * VAR26 + VAR24 = VAR26 * VAR26 + # -------------------- kernel implementations + g_x = ( + g_0 + * ( + CONST082 * VAR08 * VAR24 + - CONST084 * VAR06 * VAR26 + + CONST146 * VAR04 + - CONST146 * VAR22 + ) + + g_1 * y * (CONST039 * VAR23 + CONST089 * VAR06 * z + CONST130 * VAR08 * VAR25) + + g_10 + * ( + CONST155 * VAR23 * x + + VAR25 * (-CONST105 * VAR17 * x + CONST139 * VAR07) + + z * (-CONST056 * VAR07 * VAR17 + CONST081 * VAR15 * x + CONST140 * VAR05) + ) + + g_11 + * ( + VAR16 * (CONST044 * VAR26 * x - CONST101 * VAR07) + + y * (CONST054 * VAR24 * x - CONST091 * VAR07 * VAR26 + CONST121 * VAR05) + ) + + g_12 + * ( + CONST022 * VAR23 * x + + VAR25 * (CONST024 * VAR07 + CONST045 * VAR17 * x) + + z * (-CONST044 * VAR07 * VAR17 + CONST126 * VAR05) + ) + + g_13 + * y + * (CONST079 * VAR24 * x + CONST125 * VAR05 - CONST130 * VAR07 * VAR26) + + g_14 + * (-CONST069 * VAR07 * VAR25 + CONST109 * VAR05 * z + CONST109 * VAR23 * x) + + g_2 + * ( + CONST001 * VAR08 * (CONST091 * VAR17 * VAR26 - CONST150 * VAR24) + + CONST003 * VAR06 * (CONST012 * VAR26 + CONST016 * VAR17) + + CONST055 * VAR17 * VAR24 + + CONST147 * VAR04 + + CONST150 * VAR22 + ) + + g_3 + * ( + VAR16 * (CONST044 * VAR08 * z + CONST066 * VAR25) + + y * (-CONST091 * VAR06 * z + CONST133 * VAR23) + ) + + g_4 + * ( + CONST001 + * VAR08 + * (CONST122 * VAR17 * VAR26 + CONST134 * VAR15 - CONST137 * VAR24) + + CONST003 * VAR06 * (CONST000 * VAR26 - CONST139 * VAR17) + - CONST032 * VAR22 + - CONST105 * VAR15 * VAR26 + + CONST111 * VAR17 * VAR24 + + CONST148 * VAR04 + ) + + g_5 + * ( + CONST001 * VAR08 * (CONST106 * VAR16 * z - CONST131 * VAR25 * y) + + CONST057 * VAR06 * y * z + + CONST107 * VAR16 * VAR25 + - CONST117 * VAR14 * z + - CONST143 * VAR23 * y + ) + + g_6 + * ( + CONST001 + * VAR08 + * (CONST116 * VAR15 - CONST116 * VAR17 * VAR26 + CONST154 * VAR24) + + CONST003 * VAR06 * (CONST026 * VAR17 + CONST113 * VAR26) + + CONST014 * VAR13 + + CONST027 * VAR17 * VAR24 + + CONST116 * VAR15 * VAR26 + + CONST149 * VAR04 + + CONST156 * VAR22 + ) + + g_7 + * ( + CONST114 * VAR14 * x + + VAR16 * (CONST072 * VAR07 + CONST073 * VAR26 * x) + + y * (CONST110 * VAR07 * VAR26 + CONST128 * VAR05 + CONST129 * VAR24 * x) + ) + + g_8 + * ( + CONST075 * VAR23 * x + + VAR25 * (-CONST100 * VAR17 * x + CONST145 * VAR07) + + z * (CONST067 * VAR07 * VAR17 + CONST097 * VAR05 + CONST100 * VAR15 * x) + ) + + g_9 + * ( + -CONST085 * VAR07 * VAR16 + + CONST117 * VAR14 * x + + y * (CONST018 * VAR24 * x + CONST119 * VAR05 + CONST131 * VAR07 * VAR26) + ) + ) + g_y = ( + g_1 * (CONST039 * VAR23 * x + CONST095 * VAR07 * VAR25 - CONST125 * VAR05 * z) + + g_10 + * ( + CONST123 * VAR23 * y + + VAR25 * (-CONST096 * VAR16 - CONST105 * VAR08 * y) + + z * (-CONST093 * VAR06 * y + CONST144 * VAR08 * VAR16) + ) + + g_11 + * ( + CONST001 + * VAR17 + * (CONST025 * VAR06 + CONST025 * VAR24 + CONST092 * VAR08 * VAR26) + - CONST126 * VAR06 * VAR26 + - CONST126 * VAR08 * VAR24 + + CONST151 * VAR04 + + CONST151 * VAR22 + ) + + g_12 + * ( + CONST030 * VAR23 * y + + CONST045 * VAR08 * VAR25 * y + - CONST092 * VAR06 * y * z + ) + + g_13 + * ( + CONST076 * VAR04 + - CONST076 * VAR22 + - CONST102 * VAR06 * VAR26 + + CONST102 * VAR08 * VAR24 + ) + + g_2 + * ( + CONST030 * VAR05 * y + + CONST045 * VAR07 * VAR26 * y + - CONST092 * VAR24 * x * y + ) + + g_3 + * ( + CONST001 * VAR17 * (CONST066 * VAR25 * x + CONST101 * VAR07 * z) + - CONST133 * VAR05 * z + + CONST133 * VAR23 * x + ) + + g_4 + * ( + -CONST123 * VAR05 * y + + VAR07 * (CONST096 * VAR16 + CONST104 * VAR26 * y) + + x * (CONST093 * VAR24 * y - CONST144 * VAR16 * VAR26) + ) + + g_5 + * ( + -CONST143 * VAR05 * z + + VAR07 * (CONST062 * VAR17 * z - CONST131 * VAR25) + + x * (CONST061 * VAR17 * VAR25 - CONST062 * VAR15 * z - CONST143 * VAR23) + ) + + g_6 + * ( + CONST048 * VAR05 * y + + VAR07 * (CONST074 * VAR16 - CONST100 * VAR26 * y) + + x * (CONST058 * VAR14 + CONST074 * VAR16 * VAR26 - CONST116 * VAR24 * y) + ) + + g_7 + * ( + CONST001 + * VAR17 + * (-CONST112 * VAR08 * VAR26 - CONST128 * VAR06 - CONST128 * VAR24) + + CONST003 * VAR15 * (CONST135 * VAR08 + CONST136 * VAR26) + + CONST020 * VAR13 + + CONST050 * VAR04 + + CONST050 * VAR22 + + CONST141 * VAR06 * VAR26 + + CONST142 * VAR08 * VAR24 + ) + + g_8 + * ( + CONST048 * VAR23 * y + + VAR25 * (CONST074 * VAR16 - CONST100 * VAR08 * y) + + z * (CONST049 * VAR06 * y + CONST059 * VAR14 + CONST074 * VAR08 * VAR16) + ) + + g_9 + * ( + CONST001 * VAR17 * (-CONST124 * VAR06 + CONST124 * VAR24) + + CONST003 * VAR15 * (CONST138 * VAR08 - CONST138 * VAR26) + + CONST009 * VAR08 * VAR24 + + CONST152 * VAR04 + + CONST152 * VAR06 * VAR26 + - CONST152 * VAR22 + ) + ) + g_z = ( + g_0 * (CONST069 * VAR07 * VAR25 - CONST109 * VAR05 * z - CONST109 * VAR23 * x) + + g_1 + * y + * (-CONST079 * VAR24 * x - CONST125 * VAR05 + CONST130 * VAR07 * VAR26) + + g_10 + * ( + CONST001 + * VAR26 + * (-CONST123 * VAR08 * VAR17 - CONST134 * VAR15 + CONST137 * VAR06) + + CONST003 * VAR24 * (CONST080 * VAR08 + CONST139 * VAR17) + + CONST032 * VAR04 + + CONST105 * VAR08 * VAR15 + - CONST111 * VAR06 * VAR17 + - CONST148 * VAR22 + ) + + g_11 + * ( + VAR16 * (CONST044 * VAR08 * z - CONST101 * VAR25) + + y * (CONST054 * VAR06 * z - CONST091 * VAR08 * VAR25 + CONST121 * VAR23) + ) + + g_12 + * ( + CONST001 * VAR26 * (CONST091 * VAR08 * VAR17 - CONST098 * VAR06) + + CONST003 * VAR24 * (CONST012 * VAR08 + CONST016 * VAR17) + + CONST055 * VAR06 * VAR17 + + CONST098 * VAR04 + + CONST153 * VAR22 + ) + + g_13 + * y + * (-CONST079 * VAR06 * z - CONST125 * VAR23 + CONST130 * VAR08 * VAR25) + + g_14 + * ( + -CONST082 * VAR06 * VAR26 + + CONST084 * VAR08 * VAR24 + + CONST146 * VAR04 + - CONST146 * VAR22 + ) + + g_2 + * ( + CONST022 * VAR05 * z + + VAR07 * (CONST025 * VAR25 + CONST045 * VAR17 * z) + + x * (-CONST044 * VAR17 * VAR25 + CONST127 * VAR23) + ) + + g_3 + * ( + VAR16 * (-CONST045 * VAR26 * x + CONST101 * VAR07) + + y * (CONST091 * VAR24 * x - CONST133 * VAR05) + ) + + g_4 + * ( + CONST004 * VAR05 * z + + VAR07 * (CONST104 * VAR17 * z - CONST139 * VAR25) + + x * (CONST056 * VAR17 * VAR25 - CONST081 * VAR15 * z - CONST140 * VAR23) + ) + + g_5 + * ( + -CONST143 * VAR05 * y + + VAR07 * (CONST064 * VAR26 * y + CONST106 * VAR16) + + x * (CONST057 * VAR24 * y + CONST061 * VAR16 * VAR26 - CONST117 * VAR14) + ) + + g_6 + * ( + CONST097 * VAR05 * z + + VAR07 * (-CONST100 * VAR17 * z + CONST145 * VAR25) + + x * (CONST075 * VAR23 + CONST100 * VAR15 * z - CONST100 * VAR17 * VAR25) + ) + + g_7 + * ( + CONST115 * VAR14 * z + + VAR16 * (CONST072 * VAR25 + CONST073 * VAR08 * z) + + y * (CONST112 * VAR08 * VAR25 + CONST128 * VAR23 + CONST132 * VAR06 * z) + ) + + g_8 + * ( + CONST001 + * VAR26 + * (-CONST116 * VAR08 * VAR17 + CONST116 * VAR15 + CONST154 * VAR06) + + CONST003 * VAR24 * (CONST026 * VAR17 + CONST154 * VAR08) + + CONST019 * VAR13 + + CONST029 * VAR06 * VAR17 + + CONST094 * VAR04 + + CONST116 * VAR08 * VAR15 + + CONST149 * VAR22 + ) + + g_9 + * ( + CONST085 * VAR16 * VAR25 + - CONST117 * VAR14 * z + + y * (CONST037 * VAR08 * VAR25 - CONST119 * VAR23 + CONST143 * VAR06 * z) + ) + ) + # write out gradients + tl.store( + coord_grad_ptr + coord_row_offset, g_x, mask=coord_row_offset < coord_numel + ) + tl.store( + coord_grad_ptr + coord_row_offset + 1, + g_y, + mask=coord_row_offset + 1 < coord_numel, + ) + tl.store( + coord_grad_ptr + coord_row_offset + 2, + g_z, + mask=coord_row_offset + 2 < coord_numel, + ) From 6c32ab46e15b8d96f86eeab4e15abb3e382744b5 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sat, 24 Aug 2024 10:37:48 -0700 Subject: [PATCH 029/116] test: parameterized seventh order --- src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py index d30a953..4a5b21a 100644 --- a/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py +++ b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py @@ -10,7 +10,7 @@ torch.manual_seed(316165) -@pytest.mark.parametrize("order", [2, 3, 4, 5, 6, 10]) +@pytest.mark.parametrize("order", [2, 3, 4, 5, 6, 7, 10]) @pytest.mark.parametrize( "device", [ @@ -48,7 +48,7 @@ def test_forward_equivalence(order, device, tensor_shape, dtype): assert torch.allclose(triton_out, torch_out, atol=1e-5, rtol=1e-3) -@pytest.mark.parametrize("order", [2, 3, 4, 5, 6, 10]) +@pytest.mark.parametrize("order", [2, 3, 4, 5, 6, 7, 10]) @pytest.mark.parametrize( "device", [ From ef59bd3606baf7d264f730899f927bce6e052fc8 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sat, 24 Aug 2024 10:51:01 -0700 Subject: [PATCH 030/116] feat: added eighth order terms --- src/equitriton/sph_harm/direct/y_8.py | 1500 +++++++++++++++++++++++++ 1 file changed, 1500 insertions(+) create mode 100644 src/equitriton/sph_harm/direct/y_8.py diff --git a/src/equitriton/sph_harm/direct/y_8.py b/src/equitriton/sph_harm/direct/y_8.py new file mode 100644 index 0000000..1883574 --- /dev/null +++ b/src/equitriton/sph_harm/direct/y_8.py @@ -0,0 +1,1500 @@ +import triton +import torch +from triton import language as tl + +from equitriton.utils import calculate_lastdim_num_blocks + +__all__ = ["EighthOrderSphericalHarmonic"] + + +class EighthOrderSphericalHarmonic(torch.autograd.Function): + @staticmethod + def forward( + ctx, + coords: torch.Tensor, + mask: torch.Tensor | None = None, + block_size: int = 64, + ): + output_tensor = torch.empty( + (*coords.shape[:-1], 17), dtype=coords.dtype, device=coords.device + ) + coord_numel = coords.numel() + output_numel = output_tensor.numel() + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + # apply the kernel + eighth_order_fwd[num_blocks,]( + coords, output_tensor, block_size, coord_numel, output_numel + ) + ctx.save_for_backward(coords) + return output_tensor + + @staticmethod + def backward( + ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 + ) -> torch.Tensor: + (coords,) = ctx.saved_tensors + coord_grad_output = torch.zeros_like(coords) + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + # call backward kernel + eighth_order_bwd[num_blocks,]( + coords, + coord_grad_output, + sph_grad_tensor, + block_size, + coords.numel(), + sph_grad_tensor.numel(), + ) + return coord_grad_output + + +def _torch_fwd(coords: torch.Tensor) -> torch.Tensor: + """ + PyTorch implementation of the kernel. This is designed + purely for unit testing to ensure that the Triton implementation + is behaving as intended. + + Parameters + ---------- + coords : torch.Tensor + N-d tensor, where the last dimension corresponds to + xyz values. + + Returns + ------- + torch.Tensor + N-d tensor, where the last dimension corresponds to + each projection of the second order spherical harmonic. + """ + x = coords[..., 0].contiguous().unsqueeze(-1) + y = coords[..., 1].contiguous().unsqueeze(-1) + z = coords[..., 2].contiguous().unsqueeze(-1) + CONST000 = 1.12741169450483 + CONST003 = 4.12310562561766 + CONST004 = 4.50964677801932 + CONST006 = 6.76447016702898 + CONST007 = 1.69594242329302 + CONST008 = 1.88707052233084 + CONST010 = 2.58397773170915 + CONST011 = 13.1367135230810 + CONST012 = 13.1367135230810 + CONST014 = -489.184589393411 + CONST015 = 24.7386337537060 + CONST017 = 24.7386337537060 + CONST019 = 48.9184589393411 + CONST020 = 48.5105296237322 + CONST021 = 51.7445649319810 + CONST024 = 65.6835676154051 + CONST025 = 67.8376969317208 + CONST029 = 97.0210592474644 + CONST030 = -6.78376969317208 + CONST031 = 103.489129863962 + CONST032 = -407.026181590325 + CONST033 = 108.231522672464 + CONST035 = 110.066532613517 + CONST036 = 110.066532613517 + CONST037 = -396.284809689477 + CONST040 = -361.756882439281 + CONST041 = -1.88707052233084 + CONST042 = 158.513923875791 + CONST045 = 180.878441219640 + CONST046 = 194.042118494929 + CONST047 = -12.2296147348353 + CONST048 = 203.513090795162 + CONST050 = 216.463045344927 + CONST051 = 217.054129463568 + CONST052 = 216.463045344927 + CONST053 = -6.78376969317208 + CONST054 = -271.350787726883 + CONST055 = 244.592294696706 + CONST056 = 244.592294696706 + CONST057 = -262.734270461621 + CONST058 = -258.722824659905 + CONST061 = -217.054129463568 + CONST062 = -210.187416369296 + CONST063 = -175.156180307747 + CONST064 = -162.810472636130 + CONST066 = -144.702752975712 + CONST067 = -129.877827206956 + CONST068 = -129.361412329953 + CONST070 = -108.231522672464 + CONST071 = -108.231522672464 + CONST072 = -87.5780901538735 + CONST073 = -3.23403530824881 + CONST074 = -72.3513764878561 + CONST075 = -70.0624721230988 + CONST076 = -65.6835676154052 + CONST077 = -61.1480736741764 + CONST078 = -61.1480736741764 + CONST079 = -57.7234787586472 + CONST080 = -57.7234787586472 + CONST081 = -51.7445649319810 + CONST082 = -48.5105296237322 + CONST083 = -40.5868210021738 + CONST084 = -39.4101405692431 + CONST085 = -40.7026181590325 + CONST086 = -36.0771742241545 + CONST087 = -36.0771742241545 + CONST088 = -26.4189873126318 + CONST089 = -20.6718218536732 + CONST090 = -528.379746252636 + CONST091 = -16.9594242329302 + CONST092 = -13.1367135230810 + CONST093 = -12.2296147348353 + CONST094 = -11.3224231339851 + CONST095 = -10.3359109268366 + CONST096 = -9.70210592474644 + CONST097 = -11.3224231339851 + CONST098 = -13.5289403340579 + CONST099 = -6.78376969317208 + CONST100 = -13.5289403340579 + CONST101 = -13.1367135230810 + CONST102 = -3.23403530824881 + CONST103 = -1.61701765412441 + VAR06 = x * x * x * x + VAR07 = x * x * x + VAR08 = x * x + VAR02 = VAR06 * VAR06 + VAR03 = VAR06 * VAR07 + VAR04 = VAR07 * VAR07 + VAR05 = VAR07 * VAR08 + VAR15 = y * y * y * y + VAR16 = y * y * y + VAR17 = y * y + VAR11 = VAR15 * VAR16 + VAR12 = VAR15 * VAR16 + VAR13 = VAR16 * VAR16 + VAR14 = VAR16 * VAR17 + VAR24 = z * z * z * z + VAR25 = z * z * z + VAR26 = z * z + VAR20 = VAR24 * VAR24 + VAR21 = VAR24 * VAR25 + VAR22 = VAR25 * VAR25 + VAR23 = VAR25 * VAR26 + # -------------------- kernel implementations + Y00 = ( + -CONST066 * VAR05 * VAR25 + + CONST066 * VAR07 * VAR23 + + CONST089 * VAR03 * z + - CONST089 * VAR21 * x + ) + Y01 = y * ( + CONST040 * VAR07 * VAR24 + + CONST051 * VAR05 * VAR26 + - CONST074 * VAR22 * x + + CONST095 * VAR03 + ) + Y02 = ( + CONST097 * VAR03 * z + + VAR05 * (CONST042 * VAR17 * z - CONST088 * VAR25) + + VAR07 * (-CONST088 * VAR23 + CONST090 * VAR17 * VAR25) + + x * (CONST042 * VAR17 * VAR23 + CONST094 * VAR21) + ) + Y03 = VAR16 * ( + CONST014 * VAR07 * VAR26 + CONST019 * VAR05 + CONST055 * VAR24 * x + ) + y * ( + CONST035 * VAR05 * VAR26 + + CONST077 * VAR22 * x + - CONST078 * VAR07 * VAR24 + + CONST093 * VAR03 + ) + Y04 = ( + CONST099 * VAR03 * z + + VAR05 * (-CONST064 * VAR17 * z + CONST099 * VAR25) + + VAR07 * (-CONST053 * VAR23 + CONST054 * VAR15 * z) + + x * (-CONST053 * VAR21 - CONST054 * VAR15 * VAR25 + CONST064 * VAR17 * VAR23) + ) + Y05 = ( + VAR14 * (-CONST062 * VAR26 * x + CONST075 * VAR07) + + VAR16 * (CONST057 * VAR24 * x + CONST063 * VAR07 * VAR26 - CONST072 * VAR05) + + y + * ( + CONST011 * VAR05 * VAR26 + + CONST024 * VAR07 * VAR24 + - CONST084 * VAR22 * x + + CONST092 * VAR03 + ) + ) + Y06 = ( + CONST102 * VAR03 * z + + VAR05 * (CONST029 * VAR17 * z + CONST096 * VAR25) + + VAR07 * (CONST046 * VAR17 * VAR25 + CONST058 * VAR15 * z + CONST096 * VAR23) + + x + * ( + CONST029 * VAR17 * VAR23 + + CONST031 * VAR13 * z + + CONST058 * VAR15 * VAR25 + + CONST102 * VAR21 + ) + ) + Y07 = ( + CONST098 * VAR03 * y + + VAR05 * (CONST033 * VAR16 + CONST083 * VAR26 * y) + + VAR07 * (CONST050 * VAR16 * VAR26 + CONST067 * VAR14 + CONST083 * VAR24 * y) + + x + * ( + CONST015 * VAR12 + + CONST067 * VAR14 * VAR26 + - CONST070 * VAR16 * VAR24 + + CONST098 * VAR22 * y + ) + ) + Y08 = ( + CONST000 * VAR02 + + CONST000 * VAR20 + + CONST003 * VAR11 + - CONST070 * VAR15 * VAR24 + + CONST080 * VAR13 * VAR26 + + CONST087 * VAR17 * VAR22 + + VAR04 * (CONST004 * VAR26 + CONST086 * VAR17) + + VAR06 * (CONST006 * VAR24 - CONST070 * VAR15 + CONST071 * VAR17 * VAR26) + + VAR08 + * ( + CONST004 * VAR22 + + CONST050 * VAR15 * VAR26 + + CONST070 * VAR17 * VAR24 + + CONST079 * VAR13 + ) + ) + Y09 = ( + CONST098 * VAR21 * y + + VAR23 * (CONST033 * VAR16 + CONST083 * VAR08 * y) + + VAR25 * (CONST052 * VAR08 * VAR16 + CONST067 * VAR14 + CONST083 * VAR06 * y) + + z + * ( + CONST017 * VAR12 + + CONST033 * VAR06 * VAR16 + + CONST067 * VAR08 * VAR14 + + CONST100 * VAR04 * y + ) + ) + Y10 = ( + CONST073 * VAR08 * VAR22 + - CONST102 * VAR04 * VAR26 + - CONST103 * VAR02 + + CONST103 * VAR20 + + VAR13 * (CONST021 * VAR26 + CONST081 * VAR08) + + VAR15 * (-CONST068 * VAR06 + CONST068 * VAR24) + + VAR17 + * ( + CONST020 * VAR08 * VAR24 + + CONST020 * VAR22 + + CONST082 * VAR04 + + CONST082 * VAR06 * VAR26 + ) + ) + Y11 = ( + VAR14 * (CONST062 * VAR08 * z - CONST075 * VAR25) + + VAR16 * (-CONST057 * VAR06 * z - CONST063 * VAR08 * VAR25 + CONST072 * VAR23) + + y + * ( + CONST012 * VAR21 + + CONST076 * VAR06 * VAR25 + + CONST084 * VAR04 * z + + CONST101 * VAR08 * VAR23 + ) + ) + Y12 = ( + CONST007 * VAR02 + + CONST007 * VAR20 + + CONST030 * VAR04 * VAR26 + + CONST053 * VAR08 * VAR22 + + CONST091 * VAR06 * VAR24 + + VAR15 * (CONST025 * VAR06 + CONST025 * VAR24 + CONST032 * VAR08 * VAR26) + + VAR17 + * ( + CONST048 * VAR06 * VAR26 + + CONST048 * VAR08 * VAR24 + + CONST085 * VAR04 + + CONST085 * VAR22 + ) + ) + Y13 = VAR16 * ( + CONST014 * VAR08 * VAR25 + CONST019 * VAR23 + CONST056 * VAR06 * z + ) + y * ( + CONST036 * VAR08 * VAR23 + + CONST047 * VAR21 + - CONST077 * VAR06 * VAR25 + + CONST078 * VAR04 * z + ) + Y14 = ( + CONST008 * VAR02 + + CONST041 * VAR20 + + CONST088 * VAR04 * VAR26 + - CONST088 * VAR08 * VAR22 + + VAR17 + * ( + -CONST037 * VAR06 * VAR26 + + CONST037 * VAR08 * VAR24 + + CONST088 * VAR04 + - CONST088 * VAR22 + ) + ) + Y15 = y * ( + -CONST040 * VAR06 * VAR25 + + CONST061 * VAR08 * VAR23 + + CONST074 * VAR04 * z + - CONST095 * VAR21 + ) + Y16 = ( + CONST010 * VAR02 + + CONST010 * VAR20 + + CONST045 * VAR06 * VAR24 + + CONST074 * VAR04 * VAR26 + + CONST074 * VAR08 * VAR22 + ) + # not the prettiest way to concatenate, but better than + # messing with the linter + tensors = [ + Y00, + Y01, + Y02, + Y03, + Y04, + Y05, + Y06, + Y07, + Y08, + Y09, + Y10, + Y11, + Y12, + Y13, + Y14, + Y15, + Y16, + ] + return torch.cat(tensors, dim=-1) + + +@triton.jit +def eighth_order_fwd( + coord_ptr: tl.tensor, + output_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, +): + # these are hardcoded because they are predetermined; + coord_stride = 3 + # work out the row offsets + block_id = tl.program_id(0) + coord_striding = tl.arange(0, block_size) * coord_stride + # as the name suggests, this is effectively every node/atom + coord_row_offset = coord_striding + (block_size * coord_stride * block_id) + x = tl.load(coord_ptr + coord_row_offset, mask=coord_row_offset < coord_numel) + y = tl.load( + coord_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + z = tl.load( + coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + CONST000 = 1.12741169450483 + CONST003 = 4.12310562561766 + CONST004 = 4.50964677801932 + CONST006 = 6.76447016702898 + CONST007 = 1.69594242329302 + CONST008 = 1.88707052233084 + CONST010 = 2.58397773170915 + CONST011 = 13.1367135230810 + CONST012 = 13.1367135230810 + CONST014 = -489.184589393411 + CONST015 = 24.7386337537060 + CONST017 = 24.7386337537060 + CONST019 = 48.9184589393411 + CONST020 = 48.5105296237322 + CONST021 = 51.7445649319810 + CONST024 = 65.6835676154051 + CONST025 = 67.8376969317208 + CONST029 = 97.0210592474644 + CONST030 = -6.78376969317208 + CONST031 = 103.489129863962 + CONST032 = -407.026181590325 + CONST033 = 108.231522672464 + CONST035 = 110.066532613517 + CONST036 = 110.066532613517 + CONST037 = -396.284809689477 + CONST040 = -361.756882439281 + CONST041 = -1.88707052233084 + CONST042 = 158.513923875791 + CONST045 = 180.878441219640 + CONST046 = 194.042118494929 + CONST047 = -12.2296147348353 + CONST048 = 203.513090795162 + CONST050 = 216.463045344927 + CONST051 = 217.054129463568 + CONST052 = 216.463045344927 + CONST053 = -6.78376969317208 + CONST054 = -271.350787726883 + CONST055 = 244.592294696706 + CONST056 = 244.592294696706 + CONST057 = -262.734270461621 + CONST058 = -258.722824659905 + CONST061 = -217.054129463568 + CONST062 = -210.187416369296 + CONST063 = -175.156180307747 + CONST064 = -162.810472636130 + CONST066 = -144.702752975712 + CONST067 = -129.877827206956 + CONST068 = -129.361412329953 + CONST070 = -108.231522672464 + CONST071 = -108.231522672464 + CONST072 = -87.5780901538735 + CONST073 = -3.23403530824881 + CONST074 = -72.3513764878561 + CONST075 = -70.0624721230988 + CONST076 = -65.6835676154052 + CONST077 = -61.1480736741764 + CONST078 = -61.1480736741764 + CONST079 = -57.7234787586472 + CONST080 = -57.7234787586472 + CONST081 = -51.7445649319810 + CONST082 = -48.5105296237322 + CONST083 = -40.5868210021738 + CONST084 = -39.4101405692431 + CONST085 = -40.7026181590325 + CONST086 = -36.0771742241545 + CONST087 = -36.0771742241545 + CONST088 = -26.4189873126318 + CONST089 = -20.6718218536732 + CONST090 = -528.379746252636 + CONST091 = -16.9594242329302 + CONST092 = -13.1367135230810 + CONST093 = -12.2296147348353 + CONST094 = -11.3224231339851 + CONST095 = -10.3359109268366 + CONST096 = -9.70210592474644 + CONST097 = -11.3224231339851 + CONST098 = -13.5289403340579 + CONST099 = -6.78376969317208 + CONST100 = -13.5289403340579 + CONST101 = -13.1367135230810 + CONST102 = -3.23403530824881 + CONST103 = -1.61701765412441 + VAR06 = x * x * x * x + VAR07 = x * x * x + VAR08 = x * x + VAR02 = VAR06 * VAR06 + VAR03 = VAR06 * VAR07 + VAR04 = VAR07 * VAR07 + VAR05 = VAR07 * VAR08 + VAR15 = y * y * y * y + VAR16 = y * y * y + VAR17 = y * y + VAR11 = VAR15 * VAR16 + VAR12 = VAR15 * VAR16 + VAR13 = VAR16 * VAR16 + VAR14 = VAR16 * VAR17 + VAR24 = z * z * z * z + VAR25 = z * z * z + VAR26 = z * z + VAR20 = VAR24 * VAR24 + VAR21 = VAR24 * VAR25 + VAR22 = VAR25 * VAR25 + VAR23 = VAR25 * VAR26 + # -------------------- kernel implementations + Y00 = ( + -CONST066 * VAR05 * VAR25 + + CONST066 * VAR07 * VAR23 + + CONST089 * VAR03 * z + - CONST089 * VAR21 * x + ) + Y01 = y * ( + CONST040 * VAR07 * VAR24 + + CONST051 * VAR05 * VAR26 + - CONST074 * VAR22 * x + + CONST095 * VAR03 + ) + Y02 = ( + CONST097 * VAR03 * z + + VAR05 * (CONST042 * VAR17 * z - CONST088 * VAR25) + + VAR07 * (-CONST088 * VAR23 + CONST090 * VAR17 * VAR25) + + x * (CONST042 * VAR17 * VAR23 + CONST094 * VAR21) + ) + Y03 = VAR16 * ( + CONST014 * VAR07 * VAR26 + CONST019 * VAR05 + CONST055 * VAR24 * x + ) + y * ( + CONST035 * VAR05 * VAR26 + + CONST077 * VAR22 * x + - CONST078 * VAR07 * VAR24 + + CONST093 * VAR03 + ) + Y04 = ( + CONST099 * VAR03 * z + + VAR05 * (-CONST064 * VAR17 * z + CONST099 * VAR25) + + VAR07 * (-CONST053 * VAR23 + CONST054 * VAR15 * z) + + x * (-CONST053 * VAR21 - CONST054 * VAR15 * VAR25 + CONST064 * VAR17 * VAR23) + ) + Y05 = ( + VAR14 * (-CONST062 * VAR26 * x + CONST075 * VAR07) + + VAR16 * (CONST057 * VAR24 * x + CONST063 * VAR07 * VAR26 - CONST072 * VAR05) + + y + * ( + CONST011 * VAR05 * VAR26 + + CONST024 * VAR07 * VAR24 + - CONST084 * VAR22 * x + + CONST092 * VAR03 + ) + ) + Y06 = ( + CONST102 * VAR03 * z + + VAR05 * (CONST029 * VAR17 * z + CONST096 * VAR25) + + VAR07 * (CONST046 * VAR17 * VAR25 + CONST058 * VAR15 * z + CONST096 * VAR23) + + x + * ( + CONST029 * VAR17 * VAR23 + + CONST031 * VAR13 * z + + CONST058 * VAR15 * VAR25 + + CONST102 * VAR21 + ) + ) + Y07 = ( + CONST098 * VAR03 * y + + VAR05 * (CONST033 * VAR16 + CONST083 * VAR26 * y) + + VAR07 * (CONST050 * VAR16 * VAR26 + CONST067 * VAR14 + CONST083 * VAR24 * y) + + x + * ( + CONST015 * VAR12 + + CONST067 * VAR14 * VAR26 + - CONST070 * VAR16 * VAR24 + + CONST098 * VAR22 * y + ) + ) + Y08 = ( + CONST000 * VAR02 + + CONST000 * VAR20 + + CONST003 * VAR11 + - CONST070 * VAR15 * VAR24 + + CONST080 * VAR13 * VAR26 + + CONST087 * VAR17 * VAR22 + + VAR04 * (CONST004 * VAR26 + CONST086 * VAR17) + + VAR06 * (CONST006 * VAR24 - CONST070 * VAR15 + CONST071 * VAR17 * VAR26) + + VAR08 + * ( + CONST004 * VAR22 + + CONST050 * VAR15 * VAR26 + + CONST070 * VAR17 * VAR24 + + CONST079 * VAR13 + ) + ) + Y09 = ( + CONST098 * VAR21 * y + + VAR23 * (CONST033 * VAR16 + CONST083 * VAR08 * y) + + VAR25 * (CONST052 * VAR08 * VAR16 + CONST067 * VAR14 + CONST083 * VAR06 * y) + + z + * ( + CONST017 * VAR12 + + CONST033 * VAR06 * VAR16 + + CONST067 * VAR08 * VAR14 + + CONST100 * VAR04 * y + ) + ) + Y10 = ( + CONST073 * VAR08 * VAR22 + - CONST102 * VAR04 * VAR26 + - CONST103 * VAR02 + + CONST103 * VAR20 + + VAR13 * (CONST021 * VAR26 + CONST081 * VAR08) + + VAR15 * (-CONST068 * VAR06 + CONST068 * VAR24) + + VAR17 + * ( + CONST020 * VAR08 * VAR24 + + CONST020 * VAR22 + + CONST082 * VAR04 + + CONST082 * VAR06 * VAR26 + ) + ) + Y11 = ( + VAR14 * (CONST062 * VAR08 * z - CONST075 * VAR25) + + VAR16 * (-CONST057 * VAR06 * z - CONST063 * VAR08 * VAR25 + CONST072 * VAR23) + + y + * ( + CONST012 * VAR21 + + CONST076 * VAR06 * VAR25 + + CONST084 * VAR04 * z + + CONST101 * VAR08 * VAR23 + ) + ) + Y12 = ( + CONST007 * VAR02 + + CONST007 * VAR20 + + CONST030 * VAR04 * VAR26 + + CONST053 * VAR08 * VAR22 + + CONST091 * VAR06 * VAR24 + + VAR15 * (CONST025 * VAR06 + CONST025 * VAR24 + CONST032 * VAR08 * VAR26) + + VAR17 + * ( + CONST048 * VAR06 * VAR26 + + CONST048 * VAR08 * VAR24 + + CONST085 * VAR04 + + CONST085 * VAR22 + ) + ) + Y13 = VAR16 * ( + CONST014 * VAR08 * VAR25 + CONST019 * VAR23 + CONST056 * VAR06 * z + ) + y * ( + CONST036 * VAR08 * VAR23 + + CONST047 * VAR21 + - CONST077 * VAR06 * VAR25 + + CONST078 * VAR04 * z + ) + Y14 = ( + CONST008 * VAR02 + + CONST041 * VAR20 + + CONST088 * VAR04 * VAR26 + - CONST088 * VAR08 * VAR22 + + VAR17 + * ( + -CONST037 * VAR06 * VAR26 + + CONST037 * VAR08 * VAR24 + + CONST088 * VAR04 + - CONST088 * VAR22 + ) + ) + Y15 = y * ( + -CONST040 * VAR06 * VAR25 + + CONST061 * VAR08 * VAR23 + + CONST074 * VAR04 * z + - CONST095 * VAR21 + ) + Y16 = ( + CONST010 * VAR02 + + CONST010 * VAR20 + + CONST045 * VAR06 * VAR24 + + CONST074 * VAR04 * VAR26 + + CONST074 * VAR08 * VAR22 + ) + output_stride = 17 # [2l + 1] + output_striding = tl.arange(0, block_size) * output_stride + output_row_offset = output_striding + (block_size * output_stride * block_id) + tl.store(output_ptr + output_row_offset, Y00, mask=output_row_offset < output_numel) + tl.store( + output_ptr + output_row_offset + 1, + Y01, + mask=output_row_offset + 1 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 2, + Y02, + mask=output_row_offset + 2 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 3, + Y03, + mask=output_row_offset + 3 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 4, + Y04, + mask=output_row_offset + 4 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 5, + Y05, + mask=output_row_offset + 5 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 6, + Y06, + mask=output_row_offset + 6 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 7, + Y07, + mask=output_row_offset + 7 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 8, + Y08, + mask=output_row_offset + 8 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 9, + Y09, + mask=output_row_offset + 9 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 10, + Y10, + mask=output_row_offset + 10 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 11, + Y11, + mask=output_row_offset + 11 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 12, + Y12, + mask=output_row_offset + 12 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 13, + Y13, + mask=output_row_offset + 13 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 14, + Y14, + mask=output_row_offset + 14 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 15, + Y15, + mask=output_row_offset + 15 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 16, + Y16, + mask=output_row_offset + 16 < output_numel, + ) + + +@triton.jit +def eighth_order_bwd( + coord_ptr: tl.tensor, + coord_grad_ptr: tl.tensor, + sph_grad_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, +): + # work out the row offsets + block_id = tl.program_id(0) + # these are hardcoded because they are predetermined; + coord_stride = 3 + coord_striding = tl.arange(0, block_size) * coord_stride + # as the name suggests, this is effectively every node/atom + coord_row_offset = coord_striding + (block_size * coord_stride * block_id) + x = tl.load(coord_ptr + coord_row_offset, mask=coord_row_offset < coord_numel) + y = tl.load( + coord_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + z = tl.load( + coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + output_stride = 15 # [2l + 1] + output_striding = tl.arange(0, block_size) * output_stride + output_row_offset = output_striding + (block_size * output_stride * block_id) + # load in gradients w.r.t. spherical harmonic projections + g_0 = tl.load( + sph_grad_ptr + output_row_offset, mask=output_row_offset < output_numel + ) + g_1 = tl.load( + sph_grad_ptr + output_row_offset + 1, mask=output_row_offset + 1 < output_numel + ) + g_2 = tl.load( + sph_grad_ptr + output_row_offset + 2, mask=output_row_offset + 2 < output_numel + ) + g_3 = tl.load( + sph_grad_ptr + output_row_offset + 3, mask=output_row_offset + 3 < output_numel + ) + g_4 = tl.load( + sph_grad_ptr + output_row_offset + 4, mask=output_row_offset + 4 < output_numel + ) + g_5 = tl.load( + sph_grad_ptr + output_row_offset + 5, mask=output_row_offset + 5 < output_numel + ) + g_6 = tl.load( + sph_grad_ptr + output_row_offset + 6, mask=output_row_offset + 6 < output_numel + ) + g_7 = tl.load( + sph_grad_ptr + output_row_offset + 7, mask=output_row_offset + 7 < output_numel + ) + g_8 = tl.load( + sph_grad_ptr + output_row_offset + 8, mask=output_row_offset + 8 < output_numel + ) + g_9 = tl.load( + sph_grad_ptr + output_row_offset + 9, mask=output_row_offset + 9 < output_numel + ) + g_10 = tl.load( + sph_grad_ptr + output_row_offset + 10, + mask=output_row_offset + 10 < output_numel, + ) + g_11 = tl.load( + sph_grad_ptr + output_row_offset + 11, + mask=output_row_offset + 11 < output_numel, + ) + g_12 = tl.load( + sph_grad_ptr + output_row_offset + 12, + mask=output_row_offset + 12 < output_numel, + ) + g_13 = tl.load( + sph_grad_ptr + output_row_offset + 13, + mask=output_row_offset + 13 < output_numel, + ) + g_14 = tl.load( + sph_grad_ptr + output_row_offset + 14, + mask=output_row_offset + 14 < output_numel, + ) + g_15 = tl.load( + sph_grad_ptr + output_row_offset + 15, + mask=output_row_offset + 15 < output_numel, + ) + g_16 = tl.load( + sph_grad_ptr + output_row_offset + 16, + mask=output_row_offset + 16 < output_numel, + ) + # -------------------- variable and constant definitions + CONST000 = 2.00000000000000 + CONST001 = 3.00000000000000 + CONST002 = 4.50964677801932 + CONST004 = 5.00000000000000 + CONST005 = 6.78376969317208 + CONST006 = 4.00000000000000 + CONST007 = 9.01929355603863 + CONST008 = 6.76447016702898 + CONST009 = 6.00000000000000 + CONST011 = 13.5675393863442 + CONST012 = 15.0965641786467 + CONST013 = 13.1367135230810 + CONST015 = 13.1367135230810 + CONST017 = 19.4042118494929 + CONST019 = -489.184589393411 + CONST020 = 24.7386337537060 + CONST023 = 26.2734270461621 + CONST024 = 27.0578806681159 + CONST025 = 24.7386337537060 + CONST026 = 32.9848450049413 + CONST027 = 33.9188484658604 + CONST028 = 550.332663067587 + CONST030 = -978.369178786822 + CONST031 = 48.5105296237322 + CONST033 = 51.7445649319810 + CONST035 = 48.9184589393411 + CONST041 = 65.6835676154051 + CONST043 = -1467.55376818023 + CONST045 = -12.2296147348353 + CONST047 = 582.126355484786 + CONST048 = -437.890450769368 + CONST049 = -434.108258927137 + CONST050 = -434.108258927137 + CONST052 = -432.926090689854 + CONST054 = -1447.02752975712 + CONST055 = 91.9569946615672 + CONST056 = -420.374832738593 + CONST057 = 6.46807061649763 + CONST058 = 97.0210592474644 + CONST061 = 103.489129863962 + CONST062 = -407.026181590325 + CONST063 = 108.231522672464 + CONST065 = 110.066532613517 + CONST066 = 110.066532613517 + CONST067 = 620.934779183772 + CONST068 = -396.284809689477 + CONST070 = 132.094936563159 + CONST071 = 434.108258927137 + CONST073 = 649.389136034781 + CONST076 = -366.888442045058 + CONST077 = -366.888442045058 + CONST078 = -361.756882439281 + CONST080 = -6.78376969317208 + CONST082 = -350.312360615494 + CONST083 = -346.340872551883 + CONST084 = -346.340872551883 + CONST085 = 173.170436275942 + CONST086 = 173.170436275942 + CONST088 = 183.444221022529 + CONST089 = 183.444221022529 + CONST090 = -325.620945272260 + CONST091 = -13.5289403340579 + CONST092 = -13.5675393863442 + CONST093 = 194.042118494929 + CONST095 = 197.050702846215 + CONST096 = -11.3224231339851 + CONST097 = 203.513090795162 + CONST098 = -814.052363180650 + CONST102 = -814.052363180650 + CONST104 = 217.054129463568 + CONST105 = 216.463045344927 + CONST106 = 220.133065227035 + CONST107 = -291.063177742393 + CONST108 = 220.133065227035 + CONST109 = -792.569619378954 + CONST111 = -271.350787726883 + CONST112 = 244.592294696705 + CONST113 = 244.592294696706 + CONST114 = 244.592294696706 + CONST115 = -776.168473979715 + CONST116 = -262.734270461621 + CONST117 = -259.755654413913 + CONST118 = -258.722824659905 + CONST120 = 262.734270461621 + CONST121 = -244.215708954195 + CONST122 = 271.350787726883 + CONST124 = -236.460843415458 + CONST127 = -217.054129463568 + CONST128 = -216.463045344927 + CONST129 = -216.463045344927 + CONST130 = -216.463045344927 + CONST131 = -723.513764878561 + CONST133 = -210.187416369296 + CONST134 = -210.187416369296 + CONST135 = 814.052363180650 + CONST136 = -197.050702846215 + CONST137 = 317.027847751582 + CONST138 = -194.042118494929 + CONST139 = -13.1367135230810 + CONST140 = 324.694568017391 + CONST142 = 324.694568017391 + CONST143 = -175.156180307747 + CONST146 = -162.810472636130 + CONST147 = -162.347284008695 + CONST148 = 865.852181379709 + CONST149 = -158.513923875791 + CONST151 = -144.702752975712 + CONST152 = -649.389136034782 + CONST153 = -129.877827206956 + CONST154 = -129.361412329953 + CONST155 = 388.084236989858 + CONST157 = -115.446957517294 + CONST158 = -108.231522672464 + CONST159 = -108.231522672464 + CONST160 = 407.026181590325 + CONST161 = -103.489129863962 + CONST162 = -97.0210592474644 + CONST163 = -94.7025823384056 + CONST165 = -91.9569946615672 + CONST167 = -87.5780901538735 + CONST168 = -85.6073031438469 + CONST169 = -85.6073031438469 + CONST170 = -81.1736420043477 + CONST171 = 432.926090689854 + CONST172 = -79.2569619378954 + CONST173 = -81.1736420043477 + CONST177 = -79.2569619378954 + CONST178 = -72.3513764878561 + CONST179 = -72.1543484483091 + CONST180 = -70.0624721230988 + CONST181 = -72.1543484483091 + CONST182 = -67.8376969317208 + CONST183 = -65.6835676154052 + CONST184 = -61.1480736741764 + CONST185 = -1085.27064731784 + CONST186 = -61.1480736741764 + CONST187 = -1085.40315090753 + CONST188 = -57.7234787586472 + CONST189 = -12.9361412329953 + CONST190 = -1085.27064731784 + CONST191 = -52.8379746252636 + CONST192 = -51.7445649319810 + CONST193 = -1585.13923875791 + CONST194 = -48.5105296237322 + CONST195 = -47.4863878522046 + CONST197 = 978.369178786822 + CONST198 = -517.445649319810 + CONST199 = -40.7026181590325 + CONST200 = -40.5868210021738 + CONST201 = -39.4101405692431 + CONST202 = -40.7026181590325 + CONST203 = -36.0771742241545 + CONST204 = -1056.75949250527 + CONST205 = -29.1063177742393 + CONST206 = 485.105296237322 + CONST207 = -26.2734270461621 + CONST208 = -26.4189873126318 + CONST209 = -1050.93708184648 + CONST210 = -22.6382471577417 + CONST211 = -20.6718218536732 + CONST212 = -19.4042118494929 + CONST213 = -20.3513090795162 + CONST214 = -528.379746252636 + CONST215 = -15.0965641786467 + CONST216 = -13.5675393863442 + CONST217 = -525.468540923241 + CONST218 = -11.3224231339851 + CONST219 = -13.5289403340579 + CONST220 = -9.70210592474644 + CONST221 = -10.3359109268366 + CONST222 = -6.46807061649763 + CONST223 = -13.1367135230810 + CONST224 = -12.2296147348353 + CONST225 = -3.23403530824881 + CONST226 = -1034.89129863962 + VAR06 = x * x * x * x + VAR07 = x * x * x + VAR08 = x * x + VAR03 = VAR06 * VAR07 + VAR04 = VAR07 * VAR07 + VAR05 = VAR07 * VAR08 + VAR15 = y * y * y * y + VAR16 = y * y * y + VAR17 = y * y + VAR12 = VAR15 * VAR16 + VAR13 = VAR16 * VAR16 + VAR14 = VAR16 * VAR17 + VAR24 = z * z * z * z + VAR25 = z * z * z + VAR26 = z * z + VAR21 = VAR24 * VAR25 + VAR22 = VAR25 * VAR25 + VAR23 = VAR25 * VAR26 + # -------------------- kernel implementations + g_x = ( + g_0 + * ( + CONST049 * VAR08 * VAR23 + - CONST131 * VAR06 * VAR25 + + CONST151 * VAR04 * z + - CONST211 * VAR21 + ) + + g_1 + * y + * ( + CONST178 * VAR04 + - CONST178 * VAR22 + + CONST185 * VAR08 * VAR24 + - CONST190 * VAR06 * VAR26 + ) + + g_10 + * ( + CONST017 * VAR05 * VAR26 + + CONST161 * VAR13 * x + - CONST189 * VAR03 + - CONST198 * VAR07 * VAR15 + + CONST222 * VAR22 * x + + VAR17 + * (CONST058 * VAR24 * x + CONST107 * VAR05 + CONST138 * VAR07 * VAR26) + ) + + g_11 + * ( + CONST056 * VAR14 * x * z + + VAR16 * (-CONST082 * VAR25 * x - CONST209 * VAR07 * z) + + y + * (CONST116 * VAR07 * VAR25 + CONST124 * VAR05 * z + CONST207 * VAR23 * x) + ) + + g_12 + * ( + CONST011 * VAR03 + + CONST182 * VAR07 * VAR24 + + CONST199 * VAR05 * VAR26 + + CONST216 * VAR22 * x + + VAR15 * (CONST098 * VAR26 * x + CONST122 * VAR07) + + VAR17 + * (-CONST102 * VAR07 * VAR26 + CONST121 * VAR05 + CONST160 * VAR24 * x) + ) + + g_13 + * ( + VAR16 * (-CONST030 * VAR07 * z + CONST030 * VAR25 * x) + + y + * (CONST076 * VAR05 * z + CONST106 * VAR23 * x + CONST112 * VAR07 * VAR25) + ) + + g_14 + * ( + CONST012 * VAR03 + + CONST149 * VAR05 * VAR26 + - CONST191 * VAR22 * x + + VAR17 + * (CONST109 * VAR24 * x + CONST149 * VAR05 - CONST193 * VAR07 * VAR26) + ) + + g_15 + * y + * (CONST050 * VAR05 * z + CONST050 * VAR23 * x - CONST054 * VAR07 * VAR25) + + g_16 + * ( + CONST050 * VAR05 * VAR26 + - CONST131 * VAR07 * VAR24 + + CONST151 * VAR22 * x + - CONST211 * VAR03 + ) + + g_2 + * ( + CONST001 * VAR08 * (-CONST208 * VAR23 + CONST214 * VAR17 * VAR25) + + CONST004 * VAR06 * (-CONST149 * VAR17 * z - CONST208 * VAR25) + - CONST149 * VAR17 * VAR23 + + CONST172 * VAR04 * z + + CONST218 * VAR21 + ) + + g_3 + * ( + VAR16 * (CONST043 * VAR08 * VAR26 + CONST113 * VAR06 + CONST114 * VAR24) + + y + * ( + CONST028 * VAR06 * VAR26 + + CONST088 * VAR08 * VAR24 + + CONST168 * VAR04 + + CONST184 * VAR22 + ) + ) + + g_4 + * ( + CONST001 * VAR08 * (CONST005 * VAR23 + CONST111 * VAR15 * z) + + CONST004 * VAR06 * (CONST080 * VAR25 - CONST146 * VAR17 * z) + + CONST005 * VAR21 + - CONST111 * VAR15 * VAR25 + + CONST146 * VAR17 * VAR23 + + CONST195 * VAR04 * z + ) + + g_5 + * ( + VAR14 * (CONST133 * VAR08 - CONST134 * VAR26) + + VAR16 * (-CONST048 * VAR06 + CONST116 * VAR24 + CONST217 * VAR08 * VAR26) + + y + * ( + CONST041 * VAR06 * VAR26 + + CONST095 * VAR08 * VAR24 + + CONST165 * VAR04 + - CONST201 * VAR22 + ) + ) + + g_6 + * ( + CONST001 + * VAR08 + * (CONST093 * VAR17 * VAR25 + CONST118 * VAR15 * z + CONST220 * VAR23) + + CONST004 * VAR06 * (-CONST162 * VAR17 * z + CONST220 * VAR25) + + CONST118 * VAR15 * VAR25 + - CONST161 * VAR13 * z + - CONST162 * VAR17 * VAR23 + + CONST210 * VAR04 * z + + CONST225 * VAR21 + ) + + g_7 + * ( + CONST001 + * VAR08 + * (-CONST128 * VAR16 * VAR26 + CONST153 * VAR14 + CONST200 * VAR24 * y) + + CONST004 * VAR06 * (CONST063 * VAR16 + CONST200 * VAR26 * y) + + CONST020 * VAR12 + + CONST153 * VAR14 * VAR26 + - CONST158 * VAR16 * VAR24 + + CONST163 * VAR04 * y + + CONST219 * VAR22 * y + ) + + g_8 + * ( + CONST000 + * x + * ( + CONST002 * VAR22 + - CONST128 * VAR15 * VAR26 + + CONST158 * VAR17 * VAR24 + + CONST188 * VAR13 + ) + + CONST006 + * VAR07 + * (CONST008 * VAR24 - CONST158 * VAR15 + CONST159 * VAR17 * VAR26) + + CONST007 * VAR03 + + CONST009 * VAR05 * (CONST002 * VAR26 + CONST203 * VAR17) + ) + + g_9 + * ( + CONST173 * VAR23 * x * y + + VAR25 * (CONST147 * VAR07 * y + CONST171 * VAR16 * x) + + z + * (CONST117 * VAR14 * x + CONST170 * VAR05 * y + CONST171 * VAR07 * VAR16) + ) + ) + g_y = ( + CONST000 + * g_14 + * y + * ( + -CONST068 * VAR06 * VAR26 + + CONST068 * VAR08 * VAR24 + + CONST208 * VAR04 + - CONST208 * VAR22 + ) + + g_1 + * ( + CONST078 * VAR07 * VAR24 + + CONST104 * VAR05 * VAR26 + - CONST178 * VAR22 * x + + CONST221 * VAR03 + ) + + g_10 + * ( + CONST000 + * y + * ( + CONST031 * VAR08 * VAR24 + + CONST031 * VAR22 + + CONST194 * VAR04 + + CONST194 * VAR06 * VAR26 + ) + + CONST006 * VAR16 * (-CONST154 * VAR06 + CONST154 * VAR24) + + CONST009 * VAR14 * (CONST033 * VAR26 + CONST192 * VAR08) + ) + + g_11 + * ( + CONST001 + * VAR17 + * (-CONST116 * VAR06 * z - CONST143 * VAR08 * VAR25 + CONST167 * VAR23) + + CONST004 * VAR15 * (CONST134 * VAR08 * z - CONST180 * VAR25) + + CONST013 * VAR21 + + CONST183 * VAR06 * VAR25 + + CONST201 * VAR04 * z + + CONST223 * VAR08 * VAR23 + ) + + g_12 + * ( + CONST000 + * y + * ( + CONST097 * VAR06 * VAR26 + + CONST097 * VAR08 * VAR24 + + CONST199 * VAR04 + + CONST199 * VAR22 + ) + + CONST006 + * VAR16 + * (CONST062 * VAR08 * VAR26 - CONST182 * VAR06 - CONST182 * VAR24) + ) + + g_13 + * ( + CONST001 + * VAR17 + * (CONST019 * VAR08 * VAR25 + CONST035 * VAR23 + CONST113 * VAR06 * z) + + CONST065 * VAR08 * VAR23 + - CONST184 * VAR06 * VAR25 + + CONST186 * VAR04 * z + + CONST224 * VAR21 + ) + + g_15 + * ( + -CONST078 * VAR06 * VAR25 + + CONST127 * VAR08 * VAR23 + + CONST178 * VAR04 * z + - CONST221 * VAR21 + ) + + g_2 + * ( + CONST137 * VAR05 * y * z + + CONST137 * VAR23 * x * y + + CONST204 * VAR07 * VAR25 * y + ) + + g_3 + * ( + CONST001 + * VAR17 + * (CONST019 * VAR07 * VAR26 + CONST035 * VAR05 + CONST114 * VAR24 * x) + + CONST045 * VAR03 + + CONST066 * VAR05 * VAR26 + + CONST184 * VAR22 * x + - CONST186 * VAR07 * VAR24 + ) + + g_4 + * ( + -CONST090 * VAR05 * y * z + + CONST187 * VAR07 * VAR16 * z + + x * (CONST090 * VAR23 * y - CONST187 * VAR16 * VAR25) + ) + + g_5 + * ( + CONST001 + * VAR17 + * (CONST116 * VAR24 * x + CONST143 * VAR07 * VAR26 - CONST167 * VAR05) + + CONST004 * VAR15 * (-CONST134 * VAR26 * x + CONST180 * VAR07) + + CONST015 * VAR05 * VAR26 + + CONST041 * VAR07 * VAR24 + + CONST139 * VAR03 + - CONST201 * VAR22 * x + ) + + g_6 + * ( + -CONST138 * VAR05 * y * z + + VAR07 * (CONST155 * VAR25 * y + CONST226 * VAR16 * z) + + x + * (CONST067 * VAR14 * z - CONST138 * VAR23 * y + CONST226 * VAR16 * VAR25) + ) + + g_7 + * ( + CONST219 * VAR03 + + VAR05 * (CONST142 * VAR17 + CONST200 * VAR26) + + VAR07 * (CONST152 * VAR15 - CONST152 * VAR17 * VAR26 + CONST200 * VAR24) + + x + * ( + CONST085 * VAR13 + + CONST140 * VAR17 * VAR24 + + CONST152 * VAR15 * VAR26 + + CONST219 * VAR22 + ) + ) + + g_8 + * ( + CONST026 * VAR12 + - CONST052 * VAR16 * VAR24 + + CONST084 * VAR14 * VAR26 + + CONST179 * VAR04 * y + + CONST181 * VAR22 * y + + VAR06 * (-CONST052 * VAR16 + CONST129 * VAR26 * y) + + VAR08 + * (CONST083 * VAR14 + CONST128 * VAR24 * y + CONST148 * VAR16 * VAR26) + ) + + g_9 + * ( + CONST219 * VAR21 + + VAR23 * (CONST142 * VAR17 + CONST200 * VAR08) + + VAR25 * (CONST073 * VAR08 * VAR17 + CONST152 * VAR15 + CONST200 * VAR06) + + z + * ( + CONST086 * VAR13 + + CONST091 * VAR04 + + CONST142 * VAR06 * VAR17 + + CONST152 * VAR08 * VAR15 + ) + ) + ) + g_z = ( + g_0 + * ( + -CONST049 * VAR05 * VAR26 + + CONST131 * VAR07 * VAR24 + - CONST151 * VAR22 * x + + CONST211 * VAR03 + ) + + g_1 + * y + * (-CONST050 * VAR23 * x + CONST054 * VAR07 * VAR25 + CONST071 * VAR05 * z) + + g_10 + * ( + CONST057 * VAR04 * z + + CONST061 * VAR13 * z + + CONST189 * VAR21 + + CONST198 * VAR15 * VAR25 + + CONST212 * VAR08 * VAR23 + + VAR17 + * (CONST093 * VAR08 * VAR25 - CONST107 * VAR23 + CONST162 * VAR06 * z) + ) + + g_11 + * ( + VAR14 * (-CONST133 * VAR26 + CONST134 * VAR08) + + VAR16 * (CONST048 * VAR24 - CONST116 * VAR06 - CONST217 * VAR08 * VAR26) + + y + * ( + CONST055 * VAR22 + + CONST136 * VAR06 * VAR26 + + CONST183 * VAR08 * VAR24 + + CONST201 * VAR04 + ) + ) + + g_12 + * ( + CONST011 * VAR21 + + CONST092 * VAR04 * z + + CONST182 * VAR06 * VAR25 + + CONST202 * VAR08 * VAR23 + + VAR15 * (CONST098 * VAR08 * z + CONST122 * VAR25) + + VAR17 + * (-CONST102 * VAR08 * VAR25 + CONST121 * VAR23 + CONST160 * VAR06 * z) + ) + + g_13 + * ( + VAR16 * (CONST043 * VAR08 * VAR26 + CONST113 * VAR06 + CONST113 * VAR24) + + y + * ( + CONST028 * VAR08 * VAR24 + + CONST089 * VAR06 * VAR26 + + CONST169 * VAR22 + + CONST186 * VAR04 + ) + ) + + g_14 + * ( + -CONST149 * VAR08 * VAR23 + + CONST191 * VAR04 * z + + CONST215 * VAR21 + + VAR17 + * (-CONST109 * VAR06 * z - CONST149 * VAR23 + CONST193 * VAR08 * VAR25) + ) + + g_15 + * y + * ( + CONST178 * VAR04 + - CONST178 * VAR22 + - CONST185 * VAR06 * VAR26 + + CONST190 * VAR08 * VAR24 + ) + + g_16 + * ( + CONST050 * VAR08 * VAR23 + - CONST131 * VAR06 * VAR25 + + CONST151 * VAR04 * z + - CONST211 * VAR21 + ) + + g_2 + * ( + CONST096 * VAR03 + + VAR05 * (-CONST149 * VAR17 - CONST177 * VAR26) + + VAR07 * (CONST070 * VAR24 + CONST193 * VAR17 * VAR26) + + x * (-CONST109 * VAR17 * VAR24 + CONST177 * VAR22) + ) + + g_3 + * ( + VAR16 * (CONST030 * VAR07 * z + CONST197 * VAR25 * x) + + y + * (CONST077 * VAR23 * x + CONST108 * VAR05 * z + CONST114 * VAR07 * VAR25) + ) + + g_4 + * ( + CONST080 * VAR03 + + VAR05 * (-CONST146 * VAR17 + CONST213 * VAR26) + + VAR07 * (CONST027 * VAR24 + CONST111 * VAR15) + + x + * (CONST102 * VAR17 * VAR24 + CONST135 * VAR15 * VAR26 - CONST195 * VAR22) + ) + + g_5 + * ( + -CONST056 * VAR14 * x * z + + VAR16 * (CONST082 * VAR07 * z + CONST209 * VAR25 * x) + + y + * (CONST023 * VAR05 * z + CONST120 * VAR07 * VAR25 - CONST124 * VAR23 * x) + ) + + g_6 + * ( + CONST225 * VAR03 + + VAR05 * (-CONST162 * VAR17 + CONST205 * VAR26) + + VAR07 * (CONST047 * VAR17 * VAR26 + CONST118 * VAR15 + CONST194 * VAR24) + + x + * ( + CONST115 * VAR15 * VAR26 + - CONST161 * VAR13 + + CONST206 * VAR17 * VAR24 + + CONST210 * VAR22 + ) + ) + + g_7 + * ( + CONST173 * VAR05 * y * z + + VAR07 * (-CONST052 * VAR16 * z + CONST147 * VAR25 * y) + + x + * (-CONST052 * VAR16 * VAR25 + CONST117 * VAR14 * z + CONST173 * VAR23 * y) + ) + + g_8 + * ( + CONST007 * VAR04 * z + + CONST007 * VAR21 + - CONST052 * VAR15 * VAR25 + + CONST130 * VAR17 * VAR23 + + CONST157 * VAR13 * z + + VAR06 * (CONST024 * VAR25 + CONST129 * VAR17 * z) + + VAR08 + * (CONST024 * VAR23 - CONST052 * VAR15 * z + CONST052 * VAR17 * VAR25) + ) + + g_9 + * ( + CONST001 + * VAR26 + * (CONST105 * VAR08 * VAR16 + CONST153 * VAR14 + CONST200 * VAR06 * y) + + CONST004 * VAR24 * (CONST063 * VAR16 + CONST200 * VAR08 * y) + + CONST025 * VAR12 + + CONST063 * VAR06 * VAR16 + + CONST091 * VAR04 * y + + CONST153 * VAR08 * VAR14 + + CONST163 * VAR22 * y + ) + ) + # write out gradients + tl.store( + coord_grad_ptr + coord_row_offset, g_x, mask=coord_row_offset < coord_numel + ) + tl.store( + coord_grad_ptr + coord_row_offset + 1, + g_y, + mask=coord_row_offset + 1 < coord_numel, + ) + tl.store( + coord_grad_ptr + coord_row_offset + 2, + g_z, + mask=coord_row_offset + 2 < coord_numel, + ) From 07c06adae6e6d3994abcbe5a53bb2ff5ee3759e0 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sat, 24 Aug 2024 10:54:34 -0700 Subject: [PATCH 031/116] tests: parameterized eighth order --- src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py index 4a5b21a..d55a670 100644 --- a/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py +++ b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py @@ -10,7 +10,7 @@ torch.manual_seed(316165) -@pytest.mark.parametrize("order", [2, 3, 4, 5, 6, 7, 10]) +@pytest.mark.parametrize("order", [2, 3, 4, 5, 6, 7, 8, 10]) @pytest.mark.parametrize( "device", [ @@ -48,7 +48,7 @@ def test_forward_equivalence(order, device, tensor_shape, dtype): assert torch.allclose(triton_out, torch_out, atol=1e-5, rtol=1e-3) -@pytest.mark.parametrize("order", [2, 3, 4, 5, 6, 7, 10]) +@pytest.mark.parametrize("order", [2, 3, 4, 5, 6, 7, 8, 10]) @pytest.mark.parametrize( "device", [ From ce09f8b44e46b07ed30041c171e4e96b9a6d3292 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sat, 24 Aug 2024 11:19:05 -0700 Subject: [PATCH 032/116] feat: added ninth order terms --- src/equitriton/sph_harm/direct/y_9.py | 2058 +++++++++++++++++++++++++ 1 file changed, 2058 insertions(+) create mode 100644 src/equitriton/sph_harm/direct/y_9.py diff --git a/src/equitriton/sph_harm/direct/y_9.py b/src/equitriton/sph_harm/direct/y_9.py new file mode 100644 index 0000000..8e57ca7 --- /dev/null +++ b/src/equitriton/sph_harm/direct/y_9.py @@ -0,0 +1,2058 @@ +import triton +import torch +from triton import language as tl + +from equitriton.utils import calculate_lastdim_num_blocks + +__all__ = ["NinthOrderSphericalHarmonic"] + + +class NinthOrderSphericalHarmonic(torch.autograd.Function): + @staticmethod + def forward( + ctx, + coords: torch.Tensor, + mask: torch.Tensor | None = None, + block_size: int = 64, + ): + output_tensor = torch.empty( + (*coords.shape[:-1], 19), dtype=coords.dtype, device=coords.device + ) + coord_numel = coords.numel() + output_numel = output_tensor.numel() + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + # apply the kernel + ninth_order_fwd[num_blocks,]( + coords, output_tensor, block_size, coord_numel, output_numel + ) + ctx.save_for_backward(coords) + return output_tensor + + @staticmethod + def backward( + ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 + ) -> torch.Tensor: + (coords,) = ctx.saved_tensors + coord_grad_output = torch.zeros_like(coords) + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + # call backward kernel + ninth_order_bwd[num_blocks,]( + coords, + coord_grad_output, + sph_grad_tensor, + block_size, + coords.numel(), + sph_grad_tensor.numel(), + ) + return coord_grad_output + + +def _torch_fwd(coords: torch.Tensor) -> torch.Tensor: + """ + PyTorch implementation of the kernel. This is designed + purely for unit testing to ensure that the Triton implementation + is behaving as intended. + + Parameters + ---------- + coords : torch.Tensor + N-d tensor, where the last dimension corresponds to + xyz values. + + Returns + ------- + torch.Tensor + N-d tensor, where the last dimension corresponds to + each projection of the second order spherical harmonic. + """ + x = coords[..., 0].contiguous().unsqueeze(-1) + y = coords[..., 1].contiguous().unsqueeze(-1) + z = coords[..., 2].contiguous().unsqueeze(-1) + # -------------------- variable and constant definitions + CONST000 = 1.93163963757558 + CONST001 = 2.65478475211798 + CONST002 = 1.72771101506082 + CONST004 = 1.59908344719522 + CONST005 = 6.39633378878088 + CONST006 = 6.39633378878088 + CONST007 = 8.63855507530412 + CONST008 = 9.59450068317133 + CONST009 = 4.35889894354067 + CONST010 = 10.7269778688696 + CONST011 = 10.7269778688696 + CONST012 = 6.39633378878088 + CONST013 = 15.0007324039945 + CONST014 = 13.0937127087774 + CONST016 = 14.4550674370400 + CONST017 = 14.4550674370400 + CONST018 = 13.3827919767794 + CONST019 = 13.5214774630291 + CONST020 = 23.8930627690618 + CONST021 = 27.0429549260581 + CONST022 = 29.2403830344269 + CONST023 = 29.2403830344269 + CONST024 = 30.0014648079890 + CONST025 = -480.023436927823 + CONST026 = -480.023436927823 + CONST029 = 42.9079114754785 + CONST030 = -462.562157985281 + CONST032 = -967.518168434061 + CONST034 = 57.8202697481601 + CONST035 = 58.9217071894985 + CONST036 = 58.9217071894985 + CONST037 = 62.4530292249704 + CONST038 = 1081.71819704233 + CONST039 = 64.3618672132178 + CONST040 = 578.202697481601 + CONST044 = 600.029296159779 + CONST045 = -936.795438374555 + CONST047 = 96.7518168434061 + CONST049 = 115.640539496320 + CONST051 = -392.811381263323 + CONST053 = 137.149553407950 + CONST055 = 150.007324039945 + CONST056 = -343.263291803828 + CONST058 = 11.2632978048796 + CONST061 = -315.372338536630 + CONST062 = -314.249105010659 + CONST063 = 205.957975082297 + CONST065 = -294.608535947493 + CONST066 = 240.011718463912 + CONST068 = 241.879542108515 + CONST069 = 255.853351551235 + CONST070 = 255.853351551235 + CONST071 = -241.879542108515 + CONST072 = -240.011718463912 + CONST073 = -241.879542108515 + CONST074 = 788.430846341574 + CONST075 = 1.72771101506082 + CONST076 = -1.93163963757558 + CONST077 = -1249.06058449941 + CONST078 = -223.001919177910 + CONST080 = -216.343639408465 + CONST081 = 300.014648079890 + CONST082 = -204.682681240988 + CONST083 = -204.682681240988 + CONST084 = -204.682681240988 + CONST086 = -196.405690631662 + CONST087 = -191.890013663426 + CONST088 = -191.890013663427 + CONST089 = -187.359087674911 + CONST090 = -693.843236977922 + CONST091 = 334.502878766866 + CONST092 = -176.765121568496 + CONST093 = -150.007324039945 + CONST094 = -144.550674370400 + CONST095 = 374.718175349822 + CONST096 = 374.718175349822 + CONST097 = -649.030918225395 + CONST099 = -630.744677073259 + CONST100 = -115.640539496320 + CONST101 = -114.421097267943 + CONST102 = -115.640539496320 + CONST103 = -104.749701670220 + CONST104 = 411.915950164594 + CONST105 = -95.5722510762473 + CONST106 = -90.1063824390370 + CONST107 = -90.0043944239669 + CONST109 = -80.2967518606762 + CONST110 = -78.4601809837321 + CONST111 = 435.383175795327 + CONST112 = -589.217071894985 + CONST113 = -78.4601809837321 + CONST114 = 435.383175795328 + CONST115 = -68.5747767039748 + CONST116 = -63.9633378878088 + CONST117 = -63.9633378878088 + CONST118 = -62.4530292249704 + CONST119 = -58.9217071894985 + CONST120 = -1081.71819704233 + CONST121 = -57.8202697481601 + CONST122 = -57.8202697481601 + CONST123 = -58.9217071894985 + CONST124 = -54.0859098521163 + CONST125 = 462.562157985281 + CONST127 = -48.3759084217031 + CONST128 = -48.3759084217030 + CONST129 = -38.6327927515116 + CONST130 = -30.9062342012093 + CONST131 = 483.759084217031 + CONST132 = -30.0014648079890 + CONST133 = -30.0014648079890 + CONST134 = -27.0429549260581 + CONST135 = -24.1879542108515 + CONST136 = -24.1879542108515 + CONST137 = -1.63671408859718 + CONST138 = -15.0007324039945 + CONST139 = -13.5214774630291 + CONST140 = -13.8216881204866 + CONST141 = -13.0937127087774 + CONST142 = -13.3827919767794 + CONST143 = -9.82028453158308 + CONST144 = -4.91014226579154 + CONST145 = 511.706703102471 + VAR06 = x * x * x * x + VAR07 = x * x * x + VAR08 = x * x + VAR01 = VAR07 * VAR07 * VAR07 + VAR02 = VAR06 * VAR06 + VAR03 = VAR06 * VAR07 + VAR04 = VAR07 * VAR07 + VAR05 = VAR07 * VAR08 + VAR15 = y * y * y * y + VAR16 = y * y * y + VAR17 = y * y + VAR10 = VAR16 * VAR16 * VAR16 + VAR11 = VAR15 * VAR15 + VAR12 = VAR15 * VAR16 + VAR13 = VAR16 * VAR16 + VAR14 = VAR16 * VAR17 + VAR24 = z * z * z * z + VAR25 = z * z * z + VAR26 = z * z + VAR19 = VAR25 * VAR25 * VAR25 + VAR20 = VAR24 * VAR24 + VAR21 = VAR24 * VAR25 + VAR22 = VAR25 * VAR25 + VAR23 = VAR25 * VAR26 + # -------------------- kernel implementations + Y00 = ( + CONST001 * VAR01 + + CONST020 * VAR20 * x + + CONST078 * VAR07 * VAR22 + + CONST091 * VAR05 * VAR24 + + CONST105 * VAR03 * VAR26 + ) + Y01 = y * ( + -CONST099 * VAR05 * VAR25 + + CONST099 * VAR07 * VAR23 + + CONST106 * VAR03 * z + - CONST106 * VAR21 * x + ) + Y02 = ( + CONST000 * VAR01 + + VAR03 * (CONST129 * VAR26 + CONST130 * VAR17) + + VAR05 * (CONST021 * VAR24 - CONST097 * VAR17 * VAR26) + + VAR07 * (CONST120 * VAR17 * VAR24 - CONST124 * VAR22) + + x * (-CONST080 * VAR17 * VAR22 + CONST139 * VAR20) + ) + Y03 = VAR16 * ( + CONST077 * VAR07 * VAR25 + CONST095 * VAR05 * z + CONST096 * VAR23 * x + ) + y * ( + -CONST089 * VAR05 * VAR25 + - CONST089 * VAR07 * VAR23 + + CONST109 * VAR03 * z + + CONST109 * VAR21 * x + ) + Y04 = ( + CONST002 * VAR01 + + CONST007 * VAR20 * x + + CONST135 * VAR05 * VAR24 + + CONST140 * VAR03 * VAR26 + + VAR15 * (CONST032 * VAR07 * VAR26 + CONST047 * VAR05 + CONST131 * VAR24 * x) + + VAR17 + * ( + -CONST071 * VAR07 * VAR24 + + CONST071 * VAR22 * x + + CONST111 * VAR05 * VAR26 + + CONST127 * VAR03 + ) + ) + Y05 = ( + VAR14 * (CONST030 * VAR07 * z - CONST030 * VAR25 * x) + + VAR16 * (CONST030 * VAR23 * x + CONST125 * VAR05 * z) + + y + * ( + CONST034 * VAR07 * VAR23 + + CONST121 * VAR05 * VAR25 + - CONST121 * VAR21 * x + + CONST122 * VAR03 * z + ) + ) + Y06 = ( + CONST119 * VAR03 * VAR17 + - CONST137 * VAR01 + + VAR05 * (CONST035 * VAR17 * VAR26 - CONST086 * VAR15 + CONST143 * VAR24) + + VAR07 + * ( + CONST051 * VAR15 * VAR26 + - CONST065 * VAR17 * VAR24 + + CONST103 * VAR13 + + CONST141 * VAR22 + ) + + x + * ( + -CONST062 * VAR13 * VAR26 + - CONST092 * VAR17 * VAR22 + + CONST112 * VAR15 * VAR24 + + CONST144 * VAR20 + ) + ) + Y07 = ( + CONST132 * VAR03 * y * z + + VAR05 * (CONST081 * VAR16 * z + CONST107 * VAR25 * y) + + VAR07 + * (CONST026 * VAR14 * z + CONST044 * VAR16 * VAR25 + CONST107 * VAR23 * y) + + x + * ( + CONST025 * VAR14 * VAR25 + + CONST053 * VAR12 * z + + CONST081 * VAR16 * VAR23 + + CONST132 * VAR21 * y + ) + ) + Y08 = ( + CONST004 * VAR01 + + VAR03 * (CONST006 * VAR26 + CONST116 * VAR17) + + VAR05 * (CONST008 * VAR24 + CONST069 * VAR15 + CONST087 * VAR17 * VAR26) + + VAR07 + * ( + CONST005 * VAR22 + + CONST083 * VAR13 + + CONST087 * VAR17 * VAR24 + + CONST145 * VAR15 * VAR26 + ) + + x + * ( + CONST004 * VAR20 + + CONST022 * VAR11 + + CONST069 * VAR15 * VAR24 + + CONST082 * VAR13 * VAR26 + + CONST116 * VAR17 * VAR22 + ) + ) + Y09 = ( + CONST009 * VAR10 + + VAR12 * (CONST110 * VAR26 + CONST113 * VAR08) + + VAR14 * (CONST063 * VAR06 + CONST063 * VAR24 + CONST104 * VAR08 * VAR26) + + VAR16 + * ( + CONST056 * VAR06 * VAR26 + + CONST056 * VAR08 * VAR24 + + CONST101 * VAR04 + + CONST101 * VAR22 + ) + + y + * ( + CONST010 * VAR20 + + CONST011 * VAR02 + + CONST029 * VAR04 * VAR26 + + CONST029 * VAR08 * VAR22 + + CONST039 * VAR06 * VAR24 + ) + ) + Y10 = ( + CONST004 * VAR19 + + VAR21 * (CONST005 * VAR08 + CONST117 * VAR17) + + VAR23 * (CONST008 * VAR06 + CONST070 * VAR15 + CONST088 * VAR08 * VAR17) + + VAR25 + * ( + CONST012 * VAR04 + + CONST082 * VAR13 + + CONST087 * VAR06 * VAR17 + + CONST145 * VAR08 * VAR15 + ) + + z + * ( + CONST004 * VAR02 + + CONST023 * VAR11 + + CONST070 * VAR06 * VAR15 + + CONST084 * VAR08 * VAR13 + + CONST117 * VAR04 * VAR17 + ) + ) + Y11 = ( + VAR12 * (CONST115 * VAR08 - CONST115 * VAR26) + + VAR14 * (CONST066 * VAR06 + CONST072 * VAR24) + + VAR16 + * ( + CONST055 * VAR08 * VAR24 + + CONST093 * VAR04 + + CONST093 * VAR06 * VAR26 + - CONST093 * VAR22 + ) + + y + * ( + CONST013 * VAR02 + + CONST024 * VAR04 * VAR26 + + CONST133 * VAR08 * VAR22 + + CONST138 * VAR20 + ) + ) + Y12 = ( + CONST036 * VAR17 * VAR21 + + CONST137 * VAR19 + + VAR23 * (CONST086 * VAR15 + CONST123 * VAR08 * VAR17 - CONST143 * VAR06) + + VAR25 + * ( + CONST014 * VAR04 + - CONST051 * VAR08 * VAR15 + + CONST065 * VAR06 * VAR17 + - CONST103 * VAR13 + ) + + z + * ( + CONST062 * VAR08 * VAR13 + + CONST092 * VAR04 * VAR17 + - CONST112 * VAR06 * VAR15 + - CONST144 * VAR02 + ) + ) + Y13 = ( + VAR14 * (CONST049 * VAR06 + CONST049 * VAR24 + CONST090 * VAR08 * VAR26) + + VAR16 + * ( + CONST040 * VAR06 * VAR26 + + CONST040 * VAR08 * VAR24 + + CONST100 * VAR22 + + CONST102 * VAR04 + ) + + y + * ( + CONST016 * VAR20 + + CONST017 * VAR02 + + CONST094 * VAR06 * VAR24 + + CONST121 * VAR04 * VAR26 + + CONST122 * VAR08 * VAR22 + ) + ) + Y14 = ( + CONST007 * VAR02 * z + + CONST075 * VAR19 + + CONST136 * VAR06 * VAR23 + + CONST140 * VAR08 * VAR21 + + VAR15 * (CONST032 * VAR08 * VAR25 + CONST047 * VAR23 + CONST131 * VAR06 * z) + + VAR17 + * ( + CONST068 * VAR06 * VAR25 + + CONST073 * VAR04 * z + + CONST114 * VAR08 * VAR23 + + CONST128 * VAR21 + ) + ) + Y15 = VAR16 * ( + CONST037 * VAR22 + - CONST045 * VAR06 * VAR26 + + CONST045 * VAR08 * VAR24 + + CONST118 * VAR04 + ) + y * ( + CONST018 * VAR02 + + CONST089 * VAR04 * VAR26 + - CONST089 * VAR08 * VAR22 + + CONST142 * VAR20 + ) + Y16 = ( + CONST019 * VAR02 * z + + CONST076 * VAR19 + + CONST124 * VAR04 * VAR25 + - CONST129 * VAR08 * VAR21 + + CONST134 * VAR06 * VAR23 + + VAR17 + * ( + CONST038 * VAR06 * VAR25 + + CONST080 * VAR04 * z + + CONST097 * VAR08 * VAR23 + - CONST130 * VAR21 + ) + ) + Y17 = y * ( + CONST058 * VAR02 + + CONST058 * VAR20 + + CONST061 * VAR04 * VAR26 + + CONST061 * VAR08 * VAR22 + + CONST074 * VAR06 * VAR24 + ) + Y18 = ( + CONST001 * VAR19 + + CONST020 * VAR02 * z + + CONST078 * VAR04 * VAR25 + + CONST091 * VAR06 * VAR23 + + CONST105 * VAR08 * VAR21 + ) + # not the prettiest way to concatenate, but better than + # messing with the linter + tensors = [ + Y00, + Y01, + Y02, + Y03, + Y04, + Y05, + Y06, + Y07, + Y08, + Y09, + Y10, + Y11, + Y12, + Y13, + Y14, + Y15, + Y16, + Y17, + Y18, + ] + return torch.cat(tensors, dim=-1) + + +@triton.jit +def ninth_order_fwd( + coord_ptr: tl.tensor, + output_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, +): + # these are hardcoded because they are predetermined; + coord_stride = 3 + # work out the row offsets + block_id = tl.program_id(0) + coord_striding = tl.arange(0, block_size) * coord_stride + # as the name suggests, this is effectively every node/atom + coord_row_offset = coord_striding + (block_size * coord_stride * block_id) + x = tl.load(coord_ptr + coord_row_offset, mask=coord_row_offset < coord_numel) + y = tl.load( + coord_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + z = tl.load( + coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + # -------------------- variable and constant definitions + CONST000 = 1.93163963757558 + CONST001 = 2.65478475211798 + CONST002 = 1.72771101506082 + CONST004 = 1.59908344719522 + CONST005 = 6.39633378878088 + CONST006 = 6.39633378878088 + CONST007 = 8.63855507530412 + CONST008 = 9.59450068317133 + CONST009 = 4.35889894354067 + CONST010 = 10.7269778688696 + CONST011 = 10.7269778688696 + CONST012 = 6.39633378878088 + CONST013 = 15.0007324039945 + CONST014 = 13.0937127087774 + CONST016 = 14.4550674370400 + CONST017 = 14.4550674370400 + CONST018 = 13.3827919767794 + CONST019 = 13.5214774630291 + CONST020 = 23.8930627690618 + CONST021 = 27.0429549260581 + CONST022 = 29.2403830344269 + CONST023 = 29.2403830344269 + CONST024 = 30.0014648079890 + CONST025 = -480.023436927823 + CONST026 = -480.023436927823 + CONST029 = 42.9079114754785 + CONST030 = -462.562157985281 + CONST032 = -967.518168434061 + CONST034 = 57.8202697481601 + CONST035 = 58.9217071894985 + CONST036 = 58.9217071894985 + CONST037 = 62.4530292249704 + CONST038 = 1081.71819704233 + CONST039 = 64.3618672132178 + CONST040 = 578.202697481601 + CONST044 = 600.029296159779 + CONST045 = -936.795438374555 + CONST047 = 96.7518168434061 + CONST049 = 115.640539496320 + CONST051 = -392.811381263323 + CONST053 = 137.149553407950 + CONST055 = 150.007324039945 + CONST056 = -343.263291803828 + CONST058 = 11.2632978048796 + CONST061 = -315.372338536630 + CONST062 = -314.249105010659 + CONST063 = 205.957975082297 + CONST065 = -294.608535947493 + CONST066 = 240.011718463912 + CONST068 = 241.879542108515 + CONST069 = 255.853351551235 + CONST070 = 255.853351551235 + CONST071 = -241.879542108515 + CONST072 = -240.011718463912 + CONST073 = -241.879542108515 + CONST074 = 788.430846341574 + CONST075 = 1.72771101506082 + CONST076 = -1.93163963757558 + CONST077 = -1249.06058449941 + CONST078 = -223.001919177910 + CONST080 = -216.343639408465 + CONST081 = 300.014648079890 + CONST082 = -204.682681240988 + CONST083 = -204.682681240988 + CONST084 = -204.682681240988 + CONST086 = -196.405690631662 + CONST087 = -191.890013663426 + CONST088 = -191.890013663427 + CONST089 = -187.359087674911 + CONST090 = -693.843236977922 + CONST091 = 334.502878766866 + CONST092 = -176.765121568496 + CONST093 = -150.007324039945 + CONST094 = -144.550674370400 + CONST095 = 374.718175349822 + CONST096 = 374.718175349822 + CONST097 = -649.030918225395 + CONST099 = -630.744677073259 + CONST100 = -115.640539496320 + CONST101 = -114.421097267943 + CONST102 = -115.640539496320 + CONST103 = -104.749701670220 + CONST104 = 411.915950164594 + CONST105 = -95.5722510762473 + CONST106 = -90.1063824390370 + CONST107 = -90.0043944239669 + CONST109 = -80.2967518606762 + CONST110 = -78.4601809837321 + CONST111 = 435.383175795327 + CONST112 = -589.217071894985 + CONST113 = -78.4601809837321 + CONST114 = 435.383175795328 + CONST115 = -68.5747767039748 + CONST116 = -63.9633378878088 + CONST117 = -63.9633378878088 + CONST118 = -62.4530292249704 + CONST119 = -58.9217071894985 + CONST120 = -1081.71819704233 + CONST121 = -57.8202697481601 + CONST122 = -57.8202697481601 + CONST123 = -58.9217071894985 + CONST124 = -54.0859098521163 + CONST125 = 462.562157985281 + CONST127 = -48.3759084217031 + CONST128 = -48.3759084217030 + CONST129 = -38.6327927515116 + CONST130 = -30.9062342012093 + CONST131 = 483.759084217031 + CONST132 = -30.0014648079890 + CONST133 = -30.0014648079890 + CONST134 = -27.0429549260581 + CONST135 = -24.1879542108515 + CONST136 = -24.1879542108515 + CONST137 = -1.63671408859718 + CONST138 = -15.0007324039945 + CONST139 = -13.5214774630291 + CONST140 = -13.8216881204866 + CONST141 = -13.0937127087774 + CONST142 = -13.3827919767794 + CONST143 = -9.82028453158308 + CONST144 = -4.91014226579154 + CONST145 = 511.706703102471 + VAR06 = x * x * x * x + VAR07 = x * x * x + VAR08 = x * x + VAR01 = VAR07 * VAR07 * VAR07 + VAR02 = VAR06 * VAR06 + VAR03 = VAR06 * VAR07 + VAR04 = VAR07 * VAR07 + VAR05 = VAR07 * VAR08 + VAR15 = y * y * y * y + VAR16 = y * y * y + VAR17 = y * y + VAR10 = VAR16 * VAR16 * VAR16 + VAR11 = VAR15 * VAR15 + VAR12 = VAR15 * VAR16 + VAR13 = VAR16 * VAR16 + VAR14 = VAR16 * VAR17 + VAR24 = z * z * z * z + VAR25 = z * z * z + VAR26 = z * z + VAR19 = VAR25 * VAR25 * VAR25 + VAR20 = VAR24 * VAR24 + VAR21 = VAR24 * VAR25 + VAR22 = VAR25 * VAR25 + VAR23 = VAR25 * VAR26 + # -------------------- kernel implementations + Y00 = ( + CONST001 * VAR01 + + CONST020 * VAR20 * x + + CONST078 * VAR07 * VAR22 + + CONST091 * VAR05 * VAR24 + + CONST105 * VAR03 * VAR26 + ) + Y01 = y * ( + -CONST099 * VAR05 * VAR25 + + CONST099 * VAR07 * VAR23 + + CONST106 * VAR03 * z + - CONST106 * VAR21 * x + ) + Y02 = ( + CONST000 * VAR01 + + VAR03 * (CONST129 * VAR26 + CONST130 * VAR17) + + VAR05 * (CONST021 * VAR24 - CONST097 * VAR17 * VAR26) + + VAR07 * (CONST120 * VAR17 * VAR24 - CONST124 * VAR22) + + x * (-CONST080 * VAR17 * VAR22 + CONST139 * VAR20) + ) + Y03 = VAR16 * ( + CONST077 * VAR07 * VAR25 + CONST095 * VAR05 * z + CONST096 * VAR23 * x + ) + y * ( + -CONST089 * VAR05 * VAR25 + - CONST089 * VAR07 * VAR23 + + CONST109 * VAR03 * z + + CONST109 * VAR21 * x + ) + Y04 = ( + CONST002 * VAR01 + + CONST007 * VAR20 * x + + CONST135 * VAR05 * VAR24 + + CONST140 * VAR03 * VAR26 + + VAR15 * (CONST032 * VAR07 * VAR26 + CONST047 * VAR05 + CONST131 * VAR24 * x) + + VAR17 + * ( + -CONST071 * VAR07 * VAR24 + + CONST071 * VAR22 * x + + CONST111 * VAR05 * VAR26 + + CONST127 * VAR03 + ) + ) + Y05 = ( + VAR14 * (CONST030 * VAR07 * z - CONST030 * VAR25 * x) + + VAR16 * (CONST030 * VAR23 * x + CONST125 * VAR05 * z) + + y + * ( + CONST034 * VAR07 * VAR23 + + CONST121 * VAR05 * VAR25 + - CONST121 * VAR21 * x + + CONST122 * VAR03 * z + ) + ) + Y06 = ( + CONST119 * VAR03 * VAR17 + - CONST137 * VAR01 + + VAR05 * (CONST035 * VAR17 * VAR26 - CONST086 * VAR15 + CONST143 * VAR24) + + VAR07 + * ( + CONST051 * VAR15 * VAR26 + - CONST065 * VAR17 * VAR24 + + CONST103 * VAR13 + + CONST141 * VAR22 + ) + + x + * ( + -CONST062 * VAR13 * VAR26 + - CONST092 * VAR17 * VAR22 + + CONST112 * VAR15 * VAR24 + + CONST144 * VAR20 + ) + ) + Y07 = ( + CONST132 * VAR03 * y * z + + VAR05 * (CONST081 * VAR16 * z + CONST107 * VAR25 * y) + + VAR07 + * (CONST026 * VAR14 * z + CONST044 * VAR16 * VAR25 + CONST107 * VAR23 * y) + + x + * ( + CONST025 * VAR14 * VAR25 + + CONST053 * VAR12 * z + + CONST081 * VAR16 * VAR23 + + CONST132 * VAR21 * y + ) + ) + Y08 = ( + CONST004 * VAR01 + + VAR03 * (CONST006 * VAR26 + CONST116 * VAR17) + + VAR05 * (CONST008 * VAR24 + CONST069 * VAR15 + CONST087 * VAR17 * VAR26) + + VAR07 + * ( + CONST005 * VAR22 + + CONST083 * VAR13 + + CONST087 * VAR17 * VAR24 + + CONST145 * VAR15 * VAR26 + ) + + x + * ( + CONST004 * VAR20 + + CONST022 * VAR11 + + CONST069 * VAR15 * VAR24 + + CONST082 * VAR13 * VAR26 + + CONST116 * VAR17 * VAR22 + ) + ) + Y09 = ( + CONST009 * VAR10 + + VAR12 * (CONST110 * VAR26 + CONST113 * VAR08) + + VAR14 * (CONST063 * VAR06 + CONST063 * VAR24 + CONST104 * VAR08 * VAR26) + + VAR16 + * ( + CONST056 * VAR06 * VAR26 + + CONST056 * VAR08 * VAR24 + + CONST101 * VAR04 + + CONST101 * VAR22 + ) + + y + * ( + CONST010 * VAR20 + + CONST011 * VAR02 + + CONST029 * VAR04 * VAR26 + + CONST029 * VAR08 * VAR22 + + CONST039 * VAR06 * VAR24 + ) + ) + Y10 = ( + CONST004 * VAR19 + + VAR21 * (CONST005 * VAR08 + CONST117 * VAR17) + + VAR23 * (CONST008 * VAR06 + CONST070 * VAR15 + CONST088 * VAR08 * VAR17) + + VAR25 + * ( + CONST012 * VAR04 + + CONST082 * VAR13 + + CONST087 * VAR06 * VAR17 + + CONST145 * VAR08 * VAR15 + ) + + z + * ( + CONST004 * VAR02 + + CONST023 * VAR11 + + CONST070 * VAR06 * VAR15 + + CONST084 * VAR08 * VAR13 + + CONST117 * VAR04 * VAR17 + ) + ) + Y11 = ( + VAR12 * (CONST115 * VAR08 - CONST115 * VAR26) + + VAR14 * (CONST066 * VAR06 + CONST072 * VAR24) + + VAR16 + * ( + CONST055 * VAR08 * VAR24 + + CONST093 * VAR04 + + CONST093 * VAR06 * VAR26 + - CONST093 * VAR22 + ) + + y + * ( + CONST013 * VAR02 + + CONST024 * VAR04 * VAR26 + + CONST133 * VAR08 * VAR22 + + CONST138 * VAR20 + ) + ) + Y12 = ( + CONST036 * VAR17 * VAR21 + + CONST137 * VAR19 + + VAR23 * (CONST086 * VAR15 + CONST123 * VAR08 * VAR17 - CONST143 * VAR06) + + VAR25 + * ( + CONST014 * VAR04 + - CONST051 * VAR08 * VAR15 + + CONST065 * VAR06 * VAR17 + - CONST103 * VAR13 + ) + + z + * ( + CONST062 * VAR08 * VAR13 + + CONST092 * VAR04 * VAR17 + - CONST112 * VAR06 * VAR15 + - CONST144 * VAR02 + ) + ) + Y13 = ( + VAR14 * (CONST049 * VAR06 + CONST049 * VAR24 + CONST090 * VAR08 * VAR26) + + VAR16 + * ( + CONST040 * VAR06 * VAR26 + + CONST040 * VAR08 * VAR24 + + CONST100 * VAR22 + + CONST102 * VAR04 + ) + + y + * ( + CONST016 * VAR20 + + CONST017 * VAR02 + + CONST094 * VAR06 * VAR24 + + CONST121 * VAR04 * VAR26 + + CONST122 * VAR08 * VAR22 + ) + ) + Y14 = ( + CONST007 * VAR02 * z + + CONST075 * VAR19 + + CONST136 * VAR06 * VAR23 + + CONST140 * VAR08 * VAR21 + + VAR15 * (CONST032 * VAR08 * VAR25 + CONST047 * VAR23 + CONST131 * VAR06 * z) + + VAR17 + * ( + CONST068 * VAR06 * VAR25 + + CONST073 * VAR04 * z + + CONST114 * VAR08 * VAR23 + + CONST128 * VAR21 + ) + ) + Y15 = VAR16 * ( + CONST037 * VAR22 + - CONST045 * VAR06 * VAR26 + + CONST045 * VAR08 * VAR24 + + CONST118 * VAR04 + ) + y * ( + CONST018 * VAR02 + + CONST089 * VAR04 * VAR26 + - CONST089 * VAR08 * VAR22 + + CONST142 * VAR20 + ) + Y16 = ( + CONST019 * VAR02 * z + + CONST076 * VAR19 + + CONST124 * VAR04 * VAR25 + - CONST129 * VAR08 * VAR21 + + CONST134 * VAR06 * VAR23 + + VAR17 + * ( + CONST038 * VAR06 * VAR25 + + CONST080 * VAR04 * z + + CONST097 * VAR08 * VAR23 + - CONST130 * VAR21 + ) + ) + Y17 = y * ( + CONST058 * VAR02 + + CONST058 * VAR20 + + CONST061 * VAR04 * VAR26 + + CONST061 * VAR08 * VAR22 + + CONST074 * VAR06 * VAR24 + ) + Y18 = ( + CONST001 * VAR19 + + CONST020 * VAR02 * z + + CONST078 * VAR04 * VAR25 + + CONST091 * VAR06 * VAR23 + + CONST105 * VAR08 * VAR21 + ) + output_stride = 19 # [2l + 1] + output_striding = tl.arange(0, block_size) * output_stride + output_row_offset = output_striding + (block_size * output_stride * block_id) + tl.store(output_ptr + output_row_offset, Y00, mask=output_row_offset < output_numel) + tl.store( + output_ptr + output_row_offset + 1, + Y01, + mask=output_row_offset + 1 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 2, + Y02, + mask=output_row_offset + 2 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 3, + Y03, + mask=output_row_offset + 3 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 4, + Y04, + mask=output_row_offset + 4 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 5, + Y05, + mask=output_row_offset + 5 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 6, + Y06, + mask=output_row_offset + 6 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 7, + Y07, + mask=output_row_offset + 7 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 8, + Y08, + mask=output_row_offset + 8 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 9, + Y09, + mask=output_row_offset + 9 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 10, + Y10, + mask=output_row_offset + 10 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 11, + Y11, + mask=output_row_offset + 11 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 12, + Y12, + mask=output_row_offset + 12 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 13, + Y13, + mask=output_row_offset + 13 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 14, + Y14, + mask=output_row_offset + 14 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 15, + Y15, + mask=output_row_offset + 15 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 16, + Y16, + mask=output_row_offset + 16 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 17, + Y17, + mask=output_row_offset + 17 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 18, + Y18, + mask=output_row_offset + 18 < output_numel, + ) + + +@triton.jit +def ninth_order_bwd( + coord_ptr: tl.tensor, + coord_grad_ptr: tl.tensor, + sph_grad_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, +): + # work out the row offsets + block_id = tl.program_id(0) + # these are hardcoded because they are predetermined; + coord_stride = 3 + coord_striding = tl.arange(0, block_size) * coord_stride + # as the name suggests, this is effectively every node/atom + coord_row_offset = coord_striding + (block_size * coord_stride * block_id) + x = tl.load(coord_ptr + coord_row_offset, mask=coord_row_offset < coord_numel) + y = tl.load( + coord_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + z = tl.load( + coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + output_stride = 19 # [2l + 1] + output_striding = tl.arange(0, block_size) * output_stride + output_row_offset = output_striding + (block_size * output_stride * block_id) + # load in gradients w.r.t. spherical harmonic projections + g_0 = tl.load( + sph_grad_ptr + output_row_offset, mask=output_row_offset < output_numel + ) + g_1 = tl.load( + sph_grad_ptr + output_row_offset + 1, mask=output_row_offset + 1 < output_numel + ) + g_2 = tl.load( + sph_grad_ptr + output_row_offset + 2, mask=output_row_offset + 2 < output_numel + ) + g_3 = tl.load( + sph_grad_ptr + output_row_offset + 3, mask=output_row_offset + 3 < output_numel + ) + g_4 = tl.load( + sph_grad_ptr + output_row_offset + 4, mask=output_row_offset + 4 < output_numel + ) + g_5 = tl.load( + sph_grad_ptr + output_row_offset + 5, mask=output_row_offset + 5 < output_numel + ) + g_6 = tl.load( + sph_grad_ptr + output_row_offset + 6, mask=output_row_offset + 6 < output_numel + ) + g_7 = tl.load( + sph_grad_ptr + output_row_offset + 7, mask=output_row_offset + 7 < output_numel + ) + g_8 = tl.load( + sph_grad_ptr + output_row_offset + 8, mask=output_row_offset + 8 < output_numel + ) + g_9 = tl.load( + sph_grad_ptr + output_row_offset + 9, mask=output_row_offset + 9 < output_numel + ) + g_10 = tl.load( + sph_grad_ptr + output_row_offset + 10, + mask=output_row_offset + 10 < output_numel, + ) + g_11 = tl.load( + sph_grad_ptr + output_row_offset + 11, + mask=output_row_offset + 11 < output_numel, + ) + g_12 = tl.load( + sph_grad_ptr + output_row_offset + 12, + mask=output_row_offset + 12 < output_numel, + ) + g_13 = tl.load( + sph_grad_ptr + output_row_offset + 13, + mask=output_row_offset + 13 < output_numel, + ) + g_14 = tl.load( + sph_grad_ptr + output_row_offset + 14, + mask=output_row_offset + 14 < output_numel, + ) + g_15 = tl.load( + sph_grad_ptr + output_row_offset + 15, + mask=output_row_offset + 15 < output_numel, + ) + g_16 = tl.load( + sph_grad_ptr + output_row_offset + 16, + mask=output_row_offset + 16 < output_numel, + ) + g_17 = tl.load( + sph_grad_ptr + output_row_offset + 17, + mask=output_row_offset + 17 < output_numel, + ) + g_18 = tl.load( + sph_grad_ptr + output_row_offset + 18, + mask=output_row_offset + 18 < output_numel, + ) + # -------------------- variable and constant definitions + CONST000 = 1.59908344719522 + CONST001 = 2.00000000000000 + CONST002 = 3.00000000000000 + CONST003 = 4.00000000000000 + CONST004 = 5.00000000000000 + CONST005 = 6.39633378878088 + CONST006 = 7.00000000000000 + CONST007 = 8.63855507530412 + CONST008 = 9.59450068317133 + CONST009 = 6.39633378878088 + CONST011 = 12.7926675775618 + CONST012 = 12.7926675775618 + CONST014 = 15.5493991355474 + CONST015 = 14.3917510247570 + CONST017 = 15.0007324039945 + CONST018 = 14.4550674370400 + CONST019 = 14.4550674370400 + CONST020 = 13.3827919767794 + CONST021 = 23.8930627690618 + CONST022 = 23.8930627690618 + CONST023 = 27.0429549260581 + CONST024 = 29.2403830344269 + CONST025 = 30.0014648079890 + CONST027 = 29.2403830344269 + CONST028 = 38.3780027326853 + CONST031 = 39.2300904918661 + CONST032 = 42.9079114754785 + CONST033 = 10.7269778688696 + CONST034 = 54.0859098521163 + CONST036 = 58.9217071894985 + CONST037 = 57.8202697481601 + CONST038 = 60.0029296159779 + CONST039 = 62.4530292249704 + CONST040 = 64.3618672132178 + CONST042 = 69.1084406024329 + CONST044 = 78.5622762526647 + CONST045 = 85.8158229509570 + CONST046 = 85.8158229509570 + CONST050 = 107.062335814235 + CONST052 = 108.171819704233 + CONST053 = -1935.03633686812 + CONST055 = 115.640539496320 + CONST056 = 117.843414378997 + CONST057 = 117.843414378997 + CONST059 = 120.005859231956 + CONST060 = 2176.91587897664 + CONST061 = 2176.91587897664 + CONST064 = 150.007324039945 + CONST065 = -1892.23403121978 + CONST066 = -1885.49463006395 + CONST067 = 173.460809244480 + CONST068 = -1873.59087674911 + CONST070 = 10.7269778688696 + CONST071 = 180.008788847934 + CONST074 = 13.5214774630291 + CONST076 = 205.957975082297 + CONST078 = 216.343639408465 + CONST079 = 4326.87278816930 + CONST080 = 233.923064275415 + CONST081 = 233.923064275415 + CONST082 = 240.011718463912 + CONST083 = 241.879542108515 + CONST085 = 255.853351551235 + CONST086 = 255.853351551235 + CONST087 = 257.447468852871 + CONST088 = 257.447468852871 + CONST090 = 270.429549260581 + CONST091 = 289.101348740801 + CONST093 = 300.014648079890 + CONST097 = 13.0937127087774 + CONST099 = -3747.18175349822 + CONST100 = 6.39633378878088 + CONST103 = 374.718175349822 + CONST105 = 404.741888237121 + CONST106 = 411.915950164594 + CONST107 = 412.451950326490 + CONST108 = 432.687278816930 + CONST109 = 435.383175795328 + CONST110 = 435.383175795327 + CONST112 = 462.562157985281 + CONST113 = -1571.24552505329 + CONST114 = 483.759084217031 + CONST115 = 511.706703102471 + CONST116 = 562.077263024733 + CONST117 = 578.202697481601 + CONST119 = -1451.27725265109 + CONST121 = -1451.27725265109 + CONST123 = 600.029296159779 + CONST124 = -1440.07031078347 + CONST129 = -1387.68647395584 + CONST130 = -1387.68647395584 + CONST131 = -1373.05316721531 + CONST132 = -1338.01151506746 + CONST133 = 725.638626325546 + CONST134 = -1298.06183645079 + CONST137 = 788.430846341574 + CONST138 = -1249.06058449941 + CONST139 = -1228.09608744593 + CONST140 = -1228.09608744593 + CONST141 = 823.831900329187 + CONST142 = -3245.15459112698 + CONST143 = -1178.43414378997 + CONST144 = 870.766351590655 + CONST145 = 870.766351590655 + CONST147 = -1124.15452604947 + CONST149 = -3153.72338536630 + CONST150 = 960.046873855647 + CONST151 = 960.046873855647 + CONST152 = 967.518168434061 + CONST153 = -1081.71819704233 + CONST154 = 967.518168434061 + CONST155 = -1060.59072941097 + CONST156 = 1023.41340620494 + CONST157 = 1023.41340620494 + CONST159 = -967.518168434061 + CONST160 = 1081.71819704233 + CONST161 = -960.046873855647 + CONST163 = -936.795438374555 + CONST165 = -900.043944239669 + CONST166 = 1156.40539496320 + CONST168 = -2902.55450530218 + CONST170 = 11.2632978048796 + CONST171 = -785.622762526647 + CONST172 = -785.622762526647 + CONST173 = -767.560054653706 + CONST175 = 1338.01151506746 + CONST176 = -693.843236977922 + CONST177 = -693.843236977921 + CONST178 = -686.526583607656 + CONST179 = -669.005757533731 + CONST180 = -669.005757533731 + CONST182 = -649.030918225395 + CONST183 = -630.744677073259 + CONST184 = -628.498210021318 + CONST185 = -628.498210021317 + CONST186 = -600.029296159779 + CONST187 = -589.217071894985 + CONST188 = -578.202697481601 + CONST189 = 15.5493991355474 + CONST190 = -562.077263024733 + CONST191 = 1500.07324039945 + CONST192 = -480.023436927823 + CONST193 = -480.023436927823 + CONST195 = -462.562157985281 + CONST196 = -450.021972119834 + CONST197 = -412.451950326490 + CONST198 = -409.365362481977 + CONST199 = -409.365362481976 + CONST200 = -404.741888237121 + CONST201 = -392.811381263323 + CONST202 = -383.780027326853 + CONST203 = -383.780027326853 + CONST204 = 1672.51439383433 + CONST205 = -374.718175349822 + CONST206 = -353.530243136991 + CONST207 = -2400.11718463912 + CONST209 = -346.921618488961 + CONST210 = -346.921618488961 + CONST211 = -343.263291803828 + CONST212 = -338.631358951921 + CONST213 = -338.631358951921 + CONST214 = -324.515459112698 + CONST215 = -315.372338536630 + CONST216 = -314.249105010659 + CONST217 = -2356.86828757994 + CONST218 = -300.014648079890 + CONST219 = -294.608535947493 + CONST220 = -289.101348740801 + CONST221 = -270.013183271901 + CONST222 = -2312.81078992641 + CONST223 = 1800.08788847934 + CONST224 = -241.879542108515 + CONST225 = -240.011718463912 + CONST226 = -241.879542108515 + CONST227 = -4326.87278816930 + CONST228 = -216.343639408465 + CONST229 = -210.010253655923 + CONST230 = -204.682681240988 + CONST231 = -204.682681240988 + CONST232 = -204.682681240988 + CONST233 = -196.405690631662 + CONST234 = -191.144502152495 + CONST235 = -191.890013663426 + CONST236 = -191.890013663427 + CONST237 = -187.359087674911 + CONST238 = -180.008788847934 + CONST239 = -176.765121568496 + CONST241 = 1873.59087674911 + CONST242 = -173.460809244480 + CONST244 = -162.257729556349 + CONST245 = -156.920361967464 + CONST246 = -156.920361967464 + CONST248 = -150.007324039945 + CONST249 = -144.550674370400 + CONST250 = -137.149553407950 + CONST251 = -135.214774630291 + CONST252 = -127.926675775618 + CONST253 = -127.926675775618 + CONST254 = -120.939771054258 + CONST255 = -120.005859231956 + CONST256 = -120.939771054258 + CONST257 = -117.843414378997 + CONST258 = -117.843414378997 + CONST259 = -115.640539496320 + CONST260 = -115.640539496320 + CONST261 = 1935.03633686812 + CONST262 = -2163.43639408465 + CONST263 = -114.421097267943 + CONST264 = -108.171819704233 + CONST265 = -107.062335814235 + CONST266 = -108.171819704233 + CONST267 = -104.749701670220 + CONST268 = -96.7518168434061 + CONST269 = -96.7518168434061 + CONST270 = -90.0043944239669 + CONST271 = -90.1063824390370 + CONST272 = -80.2967518606762 + CONST273 = -78.4601809837321 + CONST274 = -78.4601809837321 + CONST275 = -77.2655855030233 + CONST276 = -78.5622762526647 + CONST277 = -68.5747767039748 + CONST278 = -63.9633378878088 + CONST279 = -62.4530292249704 + CONST280 = -61.8124684024186 + CONST281 = -60.0029296159779 + CONST282 = -63.9633378878088 + CONST283 = -58.9217071894985 + CONST284 = -57.8202697481601 + CONST285 = -57.8202697481601 + CONST286 = -48.3759084217030 + CONST287 = -48.3759084217031 + CONST288 = -39.2811381263323 + CONST289 = -38.6327927515116 + CONST290 = -39.2811381263323 + CONST291 = -30.9062342012093 + CONST292 = -30.0014648079890 + CONST293 = -30.0014648079890 + CONST294 = -27.6433762409732 + CONST295 = -17.3847567381802 + CONST296 = -15.0007324039945 + CONST297 = -14.7304267973746 + CONST298 = -13.5214774630291 + CONST299 = -13.0937127087774 + CONST300 = -13.3827919767794 + CONST301 = -9.82028453158308 + CONST302 = -4.91014226579154 + CONST303 = 2046.82681240988 + VAR06 = x * x * x * x + VAR07 = x * x * x + VAR08 = x * x + VAR02 = VAR06 * VAR06 + VAR03 = VAR06 * VAR07 + VAR04 = VAR07 * VAR07 + VAR05 = VAR07 * VAR08 + VAR15 = y * y * y * y + VAR16 = y * y * y + VAR17 = y * y + VAR11 = VAR15 * VAR15 + VAR12 = VAR15 * VAR16 + VAR13 = VAR16 * VAR16 + VAR14 = VAR16 * VAR17 + VAR24 = z * z * z * z + VAR25 = z * z * z + VAR26 = z * z + VAR20 = VAR24 * VAR24 + VAR21 = VAR24 * VAR25 + VAR22 = VAR25 * VAR25 + VAR23 = VAR25 * VAR26 + # -------------------- kernel implementations + g_x = ( + g_0 + * ( + CONST021 * VAR20 + + CONST022 * VAR02 + + CONST179 * VAR04 * VAR26 + + CONST180 * VAR08 * VAR22 + + CONST204 * VAR06 * VAR24 + ) + + g_1 + * y + * ( + CONST065 * VAR08 * VAR23 + - CONST149 * VAR06 * VAR25 + + CONST183 * VAR04 * z + - CONST271 * VAR21 + ) + + g_10 + * ( + CONST012 * VAR21 * x + + VAR23 * (CONST028 * VAR07 + CONST203 * VAR17 * x) + + VAR25 + * (CONST028 * VAR05 + CONST157 * VAR15 * x + CONST173 * VAR07 * VAR17) + + z + * ( + CONST011 * VAR03 + + CONST157 * VAR07 * VAR15 + + CONST198 * VAR13 * x + + CONST202 * VAR05 * VAR17 + ) + ) + + g_11 + * ( + CONST150 * VAR07 * VAR14 + + CONST250 * VAR12 * x + + VAR16 + * (CONST093 * VAR24 * x + CONST165 * VAR05 + CONST186 * VAR07 * VAR26) + + y * (CONST059 * VAR03 + CONST071 * VAR05 * VAR26 + CONST281 * VAR22 * x) + ) + + g_12 + * ( + VAR23 * (CONST257 * VAR17 * x - CONST290 * VAR07) + + VAR25 + * (CONST044 * VAR05 + CONST143 * VAR07 * VAR17 - CONST172 * VAR15 * x) + + z + * ( + CONST155 * VAR05 * VAR17 + + CONST184 * VAR13 * x + - CONST217 * VAR07 * VAR15 + - CONST288 * VAR03 + ) + ) + + g_13 + * ( + VAR14 * (CONST129 * VAR26 * x - CONST195 * VAR07) + + VAR16 + * (CONST166 * VAR24 * x + CONST176 * VAR05 - CONST222 * VAR07 * VAR26) + + y + * ( + CONST188 * VAR07 * VAR24 + + CONST209 * VAR05 * VAR26 + - CONST259 * VAR03 + + CONST259 * VAR22 * x + ) + ) + + g_14 + * ( + CONST042 * VAR03 * z + + CONST268 * VAR07 * VAR23 + + CONST294 * VAR21 * x + + VAR15 * (CONST053 * VAR25 * x + CONST261 * VAR07 * z) + + VAR17 + * (CONST119 * VAR05 * z + CONST144 * VAR23 * x + CONST152 * VAR07 * VAR25) + ) + + g_15 + * ( + VAR16 * (CONST068 * VAR24 * x - CONST099 * VAR07 * VAR26 + CONST205 * VAR05) + + y * (CONST050 * VAR03 + CONST147 * VAR05 * VAR26 - CONST205 * VAR22 * x) + ) + + g_16 + * ( + CONST214 * VAR05 * VAR25 + - CONST264 * VAR03 * z + + CONST264 * VAR07 * VAR23 + - CONST275 * VAR21 * x + + VAR17 + * (CONST079 * VAR07 * VAR25 + CONST134 * VAR05 * z + CONST134 * VAR23 * x) + ) + + g_17 + * y + * ( + CONST065 * VAR05 * VAR26 + - CONST149 * VAR07 * VAR24 + + CONST183 * VAR22 * x + - CONST271 * VAR03 + ) + + g_18 + * ( + CONST132 * VAR05 * VAR25 + + CONST175 * VAR07 * VAR23 + - CONST234 * VAR03 * z + + CONST234 * VAR21 * x + ) + + g_2 + * ( + CONST002 * VAR08 * (CONST034 * VAR22 + CONST153 * VAR17 * VAR24) + + CONST004 * VAR06 * (CONST023 * VAR24 - CONST182 * VAR17 * VAR26) + + CONST006 * VAR04 * (CONST289 * VAR26 + CONST291 * VAR17) + - CONST228 * VAR17 * VAR22 + - CONST295 * VAR02 + + CONST298 * VAR20 + ) + + g_3 + * ( + VAR16 + * (-CONST068 * VAR06 * z + CONST099 * VAR08 * VAR25 + CONST103 * VAR23) + + y + * ( + CONST116 * VAR08 * VAR23 + - CONST163 * VAR06 * VAR25 + + CONST190 * VAR04 * z + + CONST272 * VAR21 + ) + ) + + g_4 + * ( + CONST007 * VAR20 + + CONST014 * VAR02 + + CONST254 * VAR06 * VAR24 + + CONST269 * VAR04 * VAR26 + + VAR15 * (CONST114 * VAR06 + CONST114 * VAR24 + CONST168 * VAR08 * VAR26) + + VAR17 + * ( + CONST060 * VAR06 * VAR26 + + CONST133 * VAR08 * VAR24 + + CONST212 * VAR04 + + CONST224 * VAR22 + ) + ) + + g_5 + * ( + VAR14 * (CONST130 * VAR08 * z - CONST195 * VAR25) + + VAR16 * (CONST195 * VAR23 - CONST222 * VAR06 * z) + + y + * ( + CONST067 * VAR08 * VAR23 + + CONST200 * VAR04 * z + + CONST220 * VAR06 * VAR25 + - CONST284 * VAR21 + ) + ) + + g_6 + * ( + CONST002 + * VAR08 + * ( + CONST201 * VAR15 * VAR26 + - CONST219 * VAR17 * VAR24 + + CONST267 * VAR13 + + CONST299 * VAR22 + ) + + CONST004 + * VAR06 + * (CONST036 * VAR17 * VAR26 - CONST233 * VAR15 + CONST301 * VAR24) + + CONST187 * VAR15 * VAR24 + + CONST197 * VAR04 * VAR17 + - CONST216 * VAR13 * VAR26 + - CONST239 * VAR17 * VAR22 + - CONST297 * VAR02 + + CONST302 * VAR20 + ) + + g_7 + * ( + CONST002 + * VAR08 + * (-CONST186 * VAR16 * VAR25 + CONST192 * VAR14 * z + CONST270 * VAR23 * y) + + CONST004 * VAR06 * (-CONST218 * VAR16 * z + CONST270 * VAR25 * y) + + CONST193 * VAR14 * VAR25 + - CONST218 * VAR16 * VAR23 + + CONST229 * VAR04 * y * z + - CONST250 * VAR12 * z + + CONST292 * VAR21 * y + ) + + g_8 + * ( + CONST000 * VAR20 + + CONST002 + * VAR08 + * ( + CONST005 * VAR22 + + CONST115 * VAR15 * VAR26 + + CONST230 * VAR13 + + CONST235 * VAR17 * VAR24 + ) + + CONST004 + * VAR06 + * (CONST008 * VAR24 + CONST085 * VAR15 + CONST235 * VAR17 * VAR26) + + CONST006 * VAR04 * (CONST009 * VAR26 + CONST278 * VAR17) + + CONST015 * VAR02 + + CONST024 * VAR11 + + CONST085 * VAR15 * VAR24 + + CONST231 * VAR13 * VAR26 + + CONST278 * VAR17 * VAR22 + ) + + g_9 + * ( + CONST245 * VAR12 * x + + VAR14 * (CONST141 * VAR07 + CONST141 * VAR26 * x) + + VAR16 + * (CONST131 * VAR07 * VAR26 + CONST178 * VAR05 + CONST178 * VAR24 * x) + + y + * ( + CONST045 * VAR03 + + CONST046 * VAR22 * x + + CONST087 * VAR05 * VAR26 + + CONST088 * VAR07 * VAR24 + ) + ) + ) + g_y = ( + CONST001 + * g_16 + * y + * ( + CONST160 * VAR06 * VAR25 + + CONST182 * VAR08 * VAR23 + + CONST228 * VAR04 * z + - CONST291 * VAR21 + ) + + g_1 + * ( + -CONST183 * VAR05 * VAR25 + + CONST183 * VAR07 * VAR23 + + CONST271 * VAR03 * z + - CONST271 * VAR21 * x + ) + + g_10 + * ( + CONST252 * VAR21 * y + + VAR23 * (CONST157 * VAR16 + CONST203 * VAR08 * y) + + VAR25 + * (CONST140 * VAR14 + CONST202 * VAR06 * y + CONST303 * VAR08 * VAR16) + + z + * ( + CONST080 * VAR12 + + CONST139 * VAR08 * VAR14 + + CONST157 * VAR06 * VAR16 + + CONST252 * VAR04 * y + ) + ) + + g_11 + * ( + CONST002 + * VAR17 + * ( + CONST064 * VAR08 * VAR24 + + CONST248 * VAR04 + + CONST248 * VAR06 * VAR26 + - CONST248 * VAR22 + ) + + CONST004 * VAR15 * (CONST082 * VAR06 + CONST225 * VAR24) + + CONST006 * VAR13 * (CONST277 * VAR08 - CONST277 * VAR26) + + CONST017 * VAR02 + + CONST025 * VAR04 * VAR26 + + CONST293 * VAR08 * VAR22 + + CONST296 * VAR20 + ) + + g_12 + * ( + CONST056 * VAR21 * y + + VAR23 * (CONST171 * VAR16 + CONST257 * VAR08 * y) + + VAR25 + * (-CONST113 * VAR08 * VAR16 - CONST185 * VAR14 + CONST187 * VAR06 * y) + + z + * ( + CONST066 * VAR08 * VAR14 + + CONST206 * VAR04 * y + - CONST217 * VAR06 * VAR16 + ) + ) + + g_13 + * ( + CONST002 + * VAR17 + * ( + CONST117 * VAR06 * VAR26 + + CONST117 * VAR08 * VAR24 + + CONST259 * VAR04 + + CONST260 * VAR22 + ) + + CONST004 + * VAR15 + * (CONST055 * VAR06 + CONST055 * VAR24 + CONST176 * VAR08 * VAR26) + + CONST018 * VAR20 + + CONST019 * VAR02 + + CONST249 * VAR06 * VAR24 + + CONST284 * VAR04 * VAR26 + + CONST285 * VAR08 * VAR22 + ) + + g_14 + * ( + CONST001 + * y + * ( + CONST083 * VAR06 * VAR25 + + CONST109 * VAR08 * VAR23 + + CONST226 * VAR04 * z + + CONST286 * VAR21 + ) + + CONST003 + * VAR16 + * (CONST114 * VAR06 * z + CONST159 * VAR08 * VAR25 - CONST269 * VAR23) + ) + + g_15 + * ( + CONST002 + * VAR17 + * ( + CONST039 * VAR22 + - CONST163 * VAR06 * VAR26 + + CONST163 * VAR08 * VAR24 + + CONST279 * VAR04 + ) + + CONST020 * VAR02 + + CONST237 * VAR04 * VAR26 + - CONST237 * VAR08 * VAR22 + + CONST300 * VAR20 + ) + + g_17 + * ( + CONST137 * VAR06 * VAR24 + + CONST170 * VAR02 + + CONST170 * VAR20 + + CONST215 * VAR04 * VAR26 + + CONST215 * VAR08 * VAR22 + ) + + g_2 + * ( + CONST108 * VAR22 * x * y + - CONST134 * VAR05 * VAR26 * y + + CONST262 * VAR07 * VAR24 * y + + CONST280 * VAR03 * y + ) + + g_3 + * ( + CONST002 + * VAR17 + * (CONST103 * VAR23 * x + CONST138 * VAR07 * VAR25 - CONST205 * VAR05 * z) + - CONST237 * VAR05 * VAR25 + - CONST237 * VAR07 * VAR23 + + CONST272 * VAR03 * z + + CONST272 * VAR21 * x + ) + + g_4 + * ( + CONST001 + * y + * ( + CONST110 * VAR05 * VAR26 + - CONST224 * VAR07 * VAR24 + + CONST224 * VAR22 * x + + CONST287 * VAR03 + ) + + CONST003 + * VAR16 + * (CONST114 * VAR24 * x + CONST159 * VAR07 * VAR26 - CONST269 * VAR05) + ) + + g_5 + * ( + CONST002 * VAR17 * (CONST112 * VAR05 * z + CONST195 * VAR23 * x) + + CONST004 * VAR15 * (CONST195 * VAR07 * z - CONST195 * VAR25 * x) + + CONST037 * VAR07 * VAR23 + + CONST284 * VAR05 * VAR25 + - CONST284 * VAR21 * x + + CONST285 * VAR03 * z + ) + + g_6 + * ( + CONST258 * VAR03 * y + + VAR05 * (CONST057 * VAR26 * y - CONST171 * VAR16) + + VAR07 + * (CONST113 * VAR16 * VAR26 + CONST185 * VAR14 - CONST187 * VAR24 * y) + + x + * ( + -CONST066 * VAR14 * VAR26 + - CONST206 * VAR22 * y + + CONST217 * VAR16 * VAR24 + ) + ) + + g_7 + * ( + CONST292 * VAR03 * z + + VAR05 * (-CONST165 * VAR17 * z + CONST270 * VAR25) + + VAR07 + * (CONST207 * VAR15 * z + CONST223 * VAR17 * VAR25 + CONST270 * VAR23) + + x + * ( + CONST151 * VAR13 * z + - CONST165 * VAR17 * VAR23 + + CONST207 * VAR15 * VAR25 + + CONST292 * VAR21 + ) + ) + + g_8 + * ( + CONST253 * VAR03 * y + + VAR05 * (CONST156 * VAR16 + CONST202 * VAR26 * y) + + VAR07 + * (CONST139 * VAR14 + CONST202 * VAR24 * y + CONST303 * VAR16 * VAR26) + + x + * ( + CONST081 * VAR12 + + CONST140 * VAR14 * VAR26 + + CONST156 * VAR16 * VAR24 + + CONST253 * VAR22 * y + ) + ) + + g_9 + * ( + CONST002 + * VAR17 + * ( + CONST211 * VAR06 * VAR26 + + CONST211 * VAR08 * VAR24 + + CONST263 * VAR04 + + CONST263 * VAR22 + ) + + CONST004 + * VAR15 + * (CONST076 * VAR06 + CONST076 * VAR24 + CONST106 * VAR08 * VAR26) + + CONST006 * VAR13 * (CONST273 * VAR26 + CONST274 * VAR08) + + CONST031 * VAR11 + + CONST032 * VAR04 * VAR26 + + CONST032 * VAR08 * VAR22 + + CONST033 * VAR20 + + CONST040 * VAR06 * VAR24 + + CONST070 * VAR02 + ) + ) + g_z = ( + g_0 + * ( + CONST132 * VAR07 * VAR23 + + CONST175 * VAR05 * VAR25 + + CONST234 * VAR03 * z + - CONST234 * VAR21 * x + ) + + g_1 + * y + * ( + -CONST065 * VAR05 * VAR26 + + CONST149 * VAR07 * VAR24 + - CONST183 * VAR22 * x + + CONST271 * VAR03 + ) + + g_10 + * ( + CONST000 * VAR02 + + CONST002 + * VAR26 + * ( + CONST100 * VAR04 + + CONST115 * VAR08 * VAR15 + + CONST231 * VAR13 + + CONST235 * VAR06 * VAR17 + ) + + CONST004 + * VAR24 + * (CONST008 * VAR06 + CONST086 * VAR15 + CONST236 * VAR08 * VAR17) + + CONST006 * VAR22 * (CONST005 * VAR08 + CONST282 * VAR17) + + CONST015 * VAR20 + + CONST027 * VAR11 + + CONST086 * VAR06 * VAR15 + + CONST232 * VAR08 * VAR13 + + CONST282 * VAR04 * VAR17 + ) + + g_11 + * ( + CONST161 * VAR14 * VAR25 + - CONST250 * VAR12 * z + + VAR16 + * (CONST123 * VAR08 * VAR25 - CONST165 * VAR23 + CONST218 * VAR06 * z) + + y * (CONST038 * VAR04 * z + CONST238 * VAR08 * VAR23 + CONST255 * VAR21) + ) + + g_12 + * ( + CONST002 + * VAR26 + * ( + CONST097 * VAR04 + - CONST201 * VAR08 * VAR15 + + CONST219 * VAR06 * VAR17 + - CONST267 * VAR13 + ) + + CONST004 + * VAR24 + * (CONST233 * VAR15 + CONST283 * VAR08 * VAR17 - CONST301 * VAR06) + + CONST107 * VAR17 * VAR22 + - CONST187 * VAR06 * VAR15 + + CONST216 * VAR08 * VAR13 + + CONST239 * VAR04 * VAR17 + + CONST297 * VAR20 + - CONST302 * VAR02 + ) + + g_13 + * ( + VAR14 * (CONST129 * VAR08 * z - CONST195 * VAR25) + + VAR16 + * (CONST166 * VAR06 * z + CONST177 * VAR23 - CONST222 * VAR08 * VAR25) + + y + * ( + CONST188 * VAR06 * VAR25 + + CONST210 * VAR08 * VAR23 + + CONST260 * VAR04 * z + - CONST260 * VAR21 + ) + ) + + g_14 + * ( + CONST007 * VAR02 + + CONST189 * VAR20 + + CONST256 * VAR06 * VAR24 + + CONST269 * VAR08 * VAR22 + + VAR15 * (CONST114 * VAR06 + CONST114 * VAR24 + CONST168 * VAR08 * VAR26) + + VAR17 + * ( + CONST061 * VAR08 * VAR24 + + CONST133 * VAR06 * VAR26 + + CONST213 * VAR22 + + CONST226 * VAR04 + ) + ) + + g_15 + * ( + VAR16 + * (-CONST068 * VAR06 * z + CONST099 * VAR08 * VAR25 + CONST103 * VAR23) + + y * (-CONST147 * VAR08 * VAR23 + CONST205 * VAR04 * z + CONST265 * VAR21) + ) + + g_16 + * ( + CONST074 * VAR02 + + CONST090 * VAR08 * VAR22 + + CONST244 * VAR04 * VAR26 + + CONST251 * VAR06 * VAR24 + + CONST295 * VAR20 + + VAR17 + * ( + CONST078 * VAR22 + - CONST142 * VAR06 * VAR26 + + CONST142 * VAR08 * VAR24 + + CONST228 * VAR04 + ) + ) + + g_17 + * y + * ( + CONST065 * VAR08 * VAR23 + - CONST149 * VAR06 * VAR25 + + CONST183 * VAR04 * z + - CONST271 * VAR21 + ) + + g_18 + * ( + CONST021 * VAR02 + + CONST022 * VAR20 + + CONST179 * VAR08 * VAR22 + + CONST180 * VAR04 * VAR26 + + CONST204 * VAR06 * VAR24 + ) + + g_2 + * ( + CONST275 * VAR03 * z + + VAR05 * (CONST052 * VAR25 - CONST134 * VAR17 * z) + + VAR07 * (-CONST214 * VAR23 + CONST227 * VAR17 * VAR25) + + x * (-CONST134 * VAR17 * VAR23 + CONST266 * VAR21) + ) + + g_3 + * ( + VAR16 * (CONST099 * VAR07 * VAR26 - CONST205 * VAR05 + CONST241 * VAR24 * x) + + y + * ( + CONST116 * VAR05 * VAR26 + - CONST163 * VAR07 * VAR24 + + CONST190 * VAR22 * x + + CONST272 * VAR03 + ) + ) + + g_4 + * ( + CONST042 * VAR21 * x + + CONST269 * VAR05 * VAR25 + + CONST294 * VAR03 * z + + VAR15 * (CONST053 * VAR07 * z + CONST261 * VAR25 * x) + + VAR17 + * (CONST121 * VAR23 * x + CONST145 * VAR05 * z + CONST154 * VAR07 * VAR25) + ) + + g_5 + * ( + VAR14 * (-CONST130 * VAR26 * x + CONST195 * VAR07) + + VAR16 * (CONST112 * VAR05 + CONST222 * VAR24 * x) + + y + * ( + CONST091 * VAR07 * VAR24 + + CONST105 * VAR22 * x + + CONST242 * VAR05 * VAR26 + + CONST285 * VAR03 + ) + ) + + g_6 + * ( + VAR05 * (CONST057 * VAR17 * z + CONST290 * VAR25) + + VAR07 + * (-CONST143 * VAR17 * VAR25 + CONST172 * VAR15 * z + CONST276 * VAR23) + + x + * ( + -CONST155 * VAR17 * VAR23 + - CONST184 * VAR13 * z + + CONST217 * VAR15 * VAR25 + + CONST288 * VAR21 + ) + ) + + g_7 + * ( + CONST292 * VAR03 * y + + VAR05 * (-CONST218 * VAR16 + CONST221 * VAR26 * y) + + VAR07 + * (CONST192 * VAR14 + CONST196 * VAR24 * y + CONST223 * VAR16 * VAR26) + + x + * ( + CONST124 * VAR14 * VAR26 + + CONST191 * VAR16 * VAR24 + + CONST229 * VAR22 * y + - CONST250 * VAR12 + ) + ) + + g_8 + * ( + CONST011 * VAR03 * z + + VAR05 * (CONST028 * VAR25 + CONST202 * VAR17 * z) + + VAR07 + * (CONST028 * VAR23 + CONST157 * VAR15 * z + CONST173 * VAR17 * VAR25) + + x + * ( + CONST011 * VAR21 + + CONST156 * VAR15 * VAR25 + + CONST199 * VAR13 * z + + CONST202 * VAR17 * VAR23 + ) + ) + + g_9 + * ( + CONST246 * VAR12 * z + + VAR14 * (CONST141 * VAR08 * z + CONST141 * VAR25) + + VAR16 + * (CONST131 * VAR08 * VAR25 + CONST178 * VAR06 * z + CONST178 * VAR23) + + y + * ( + CONST046 * VAR04 * z + + CONST046 * VAR21 + + CONST087 * VAR08 * VAR23 + + CONST088 * VAR06 * VAR25 + ) + ) + ) + # write out gradients + tl.store( + coord_grad_ptr + coord_row_offset, g_x, mask=coord_row_offset < coord_numel + ) + tl.store( + coord_grad_ptr + coord_row_offset + 1, + g_y, + mask=coord_row_offset + 1 < coord_numel, + ) + tl.store( + coord_grad_ptr + coord_row_offset + 2, + g_z, + mask=coord_row_offset + 2 < coord_numel, + ) From 812e9c3577b8b7126b05ea60e4916f89e9046502 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sat, 24 Aug 2024 11:19:43 -0700 Subject: [PATCH 033/116] fix: correcting eighth order backward stride --- src/equitriton/sph_harm/direct/y_8.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equitriton/sph_harm/direct/y_8.py b/src/equitriton/sph_harm/direct/y_8.py index 1883574..d2d32a0 100644 --- a/src/equitriton/sph_harm/direct/y_8.py +++ b/src/equitriton/sph_harm/direct/y_8.py @@ -773,7 +773,7 @@ def eighth_order_bwd( z = tl.load( coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel ) - output_stride = 15 # [2l + 1] + output_stride = 17 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride output_row_offset = output_striding + (block_size * output_stride * block_id) # load in gradients w.r.t. spherical harmonic projections From 0988acfdd3eb08b544147169306e16d3cd35355a Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sat, 24 Aug 2024 11:24:45 -0700 Subject: [PATCH 034/116] test: parameterized ninth order tests --- src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py index d55a670..d0fa7db 100644 --- a/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py +++ b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py @@ -10,7 +10,7 @@ torch.manual_seed(316165) -@pytest.mark.parametrize("order", [2, 3, 4, 5, 6, 7, 8, 10]) +@pytest.mark.parametrize("order", [2, 3, 4, 5, 6, 7, 8, 9, 10]) @pytest.mark.parametrize( "device", [ @@ -48,7 +48,7 @@ def test_forward_equivalence(order, device, tensor_shape, dtype): assert torch.allclose(triton_out, torch_out, atol=1e-5, rtol=1e-3) -@pytest.mark.parametrize("order", [2, 3, 4, 5, 6, 7, 8, 10]) +@pytest.mark.parametrize("order", [2, 3, 4, 5, 6, 7, 8, 9, 10]) @pytest.mark.parametrize( "device", [ From 4ed1f9885f63bc49242b22e8c7dcb897ef815008 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Mon, 26 Aug 2024 09:57:58 -0700 Subject: [PATCH 035/116] feat: added utility function for calculating irreps shapes --- src/equitriton/utils.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/equitriton/utils.py b/src/equitriton/utils.py index 033f934..527b37a 100644 --- a/src/equitriton/utils.py +++ b/src/equitriton/utils.py @@ -4,6 +4,7 @@ import torch import triton +from e3nn import o3 __all__ = ["pad_tensor_to_power", "calculate_lastdim_num_blocks"] @@ -113,3 +114,36 @@ def unravel_index(tensor: torch.Tensor, index: int) -> tuple[int, ...]: indices.append(index % size) index //= size return tuple(reversed(indices)) + + +def spherical_harmonics_irreps(l_values: list[int], num_feat: int = 1) -> o3.Irreps: + """ + Generate the set of irreducible representations given a list of + arbitrary l values; i.e. they need not be contiguous. + + While ``l_values`` does not need to be contiguous, this function + will sort in ascending order of ``l``, such that the returned + representations are in order. This makes it a lot more straightforward + for building off of. + + Parameters + ---------- + l_values : list[int] + List of l values to generate representations for. + num_feat : int + Number of features for the associated representations. + Defaults to 1, which can be used for specifying a spherical + harmonic basis, but values greater than one can be used to + specify weights. + + Returns + ------- + o3.Irreps + Irreducible representations for the set of spherical harmonics. + """ + assert num_feat > 1, "Number of features must be positive!" + joint = [] + for l in sorted(l_values): + parity = "e" if (-1) ** l > 0 else "o" + joint.append(f"{num_feat}x{l}{parity}") + return o3.Irreps("+".join(joint)) From 6f97a72efe0ed52de3e07fde226d62f283e58e84 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Mon, 26 Aug 2024 10:15:48 -0700 Subject: [PATCH 036/116] fix: making assert check actually positive instead --- src/equitriton/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equitriton/utils.py b/src/equitriton/utils.py index 527b37a..b43fe7d 100644 --- a/src/equitriton/utils.py +++ b/src/equitriton/utils.py @@ -141,7 +141,7 @@ def spherical_harmonics_irreps(l_values: list[int], num_feat: int = 1) -> o3.Irr o3.Irreps Irreducible representations for the set of spherical harmonics. """ - assert num_feat > 1, "Number of features must be positive!" + assert num_feat > 0, "Number of features must be positive!" joint = [] for l in sorted(l_values): parity = "e" if (-1) ** l > 0 else "o" From aae4a7a226fa8ac4a6b80582690dd1ba0b9dab6f Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Mon, 26 Aug 2024 14:09:30 -0700 Subject: [PATCH 037/116] feat: added first order terms --- .../direct/tests/test_direct_sph_harm.py | 4 +- src/equitriton/sph_harm/direct/y_1.py | 168 ++++++++++++++++++ 2 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 src/equitriton/sph_harm/direct/y_1.py diff --git a/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py index d0fa7db..a7350fc 100644 --- a/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py +++ b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py @@ -10,7 +10,7 @@ torch.manual_seed(316165) -@pytest.mark.parametrize("order", [2, 3, 4, 5, 6, 7, 8, 9, 10]) +@pytest.mark.parametrize("order", [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) @pytest.mark.parametrize( "device", [ @@ -48,7 +48,7 @@ def test_forward_equivalence(order, device, tensor_shape, dtype): assert torch.allclose(triton_out, torch_out, atol=1e-5, rtol=1e-3) -@pytest.mark.parametrize("order", [2, 3, 4, 5, 6, 7, 8, 9, 10]) +@pytest.mark.parametrize("order", [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) @pytest.mark.parametrize( "device", [ diff --git a/src/equitriton/sph_harm/direct/y_1.py b/src/equitriton/sph_harm/direct/y_1.py new file mode 100644 index 0000000..4bb9050 --- /dev/null +++ b/src/equitriton/sph_harm/direct/y_1.py @@ -0,0 +1,168 @@ +import triton +import torch +from triton import language as tl + +from equitriton.utils import calculate_lastdim_num_blocks + +__all__ = ["FirstOrderSphericalHarmonic"] + + +class FirstOrderSphericalHarmonic(torch.autograd.Function): + @staticmethod + def forward( + ctx, + coords: torch.Tensor, + mask: torch.Tensor | None = None, + block_size: int = 64, + ): + output_tensor = torch.empty( + (*coords.shape[:-1], 3), dtype=coords.dtype, device=coords.device + ) + coord_numel = coords.numel() + output_numel = output_tensor.numel() + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + # apply the kernel + first_order_fwd[num_blocks,]( + coords, output_tensor, block_size, coord_numel, output_numel + ) + ctx.save_for_backward(coords) + return output_tensor + + @staticmethod + def backward( + ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 + ) -> torch.Tensor: + (coords,) = ctx.saved_tensors + coord_grad_output = torch.zeros_like(coords) + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + # call backward kernel + first_order_bwd[num_blocks,]( + coord_grad_output, + sph_grad_tensor, + block_size, + coords.numel(), + sph_grad_tensor.numel(), + ) + return coord_grad_output + + +def _torch_fwd(coords: torch.Tensor) -> torch.Tensor: + """ + PyTorch implementation of the kernel. This is designed + purely for unit testing to ensure that the Triton implementation + is behaving as intended. + + This function is generically named to make it easier for + it to be called programmatically: it is _not_ intended + to be used manually. + + Parameters + ---------- + coords : torch.Tensor + N-d tensor, where the last dimension corresponds to + xyz values. + + Returns + ------- + torch.Tensor + N-d tensor, where the last dimension corresponds to + each projection of the second order spherical harmonic. + """ + x = coords[..., 0].contiguous().unsqueeze(-1) + y = coords[..., 1].contiguous().unsqueeze(-1) + z = coords[..., 2].contiguous().unsqueeze(-1) + CONST_00 = 3**0.5 + Y10 = x * CONST_00 + Y11 = y * CONST_00 + Y12 = z * CONST_00 + return torch.cat([Y10, Y11, Y12], dim=-1) + + +@triton.jit +def first_order_fwd( + coord_ptr: tl.tensor, + output_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, +): + # these are hardcoded because they are predetermined; + coord_stride = 3 + # work out the row offsets + block_id = tl.program_id(0) + coord_striding = tl.arange(0, block_size) * coord_stride + # as the name suggests, this is effectively every node/atom + coord_row_offset = coord_striding + (block_size * coord_stride * block_id) + x = tl.load(coord_ptr + coord_row_offset, mask=coord_row_offset < coord_numel) + y = tl.load( + coord_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + z = tl.load( + coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + CONST_00 = tl.sqrt(3.0) + Y10 = CONST_00 * x + Y11 = CONST_00 * y + Y12 = CONST_00 * z + output_stride = 3 # [2l + 1] + output_striding = tl.arange(0, block_size) * output_stride + output_row_offset = output_striding + (block_size * output_stride * block_id) + tl.store(output_ptr + output_row_offset, Y10, mask=output_row_offset < output_numel) + tl.store( + output_ptr + output_row_offset + 1, + Y11, + mask=output_row_offset + 1 < output_numel, + ) + tl.store( + output_ptr + output_row_offset + 2, + Y12, + mask=output_row_offset + 2 < output_numel, + ) + + +@triton.jit +def first_order_bwd( + coord_grad_ptr: tl.tensor, + sph_grad_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, +): + # work out the row offsets + block_id = tl.program_id(0) + # these are hardcoded because they are predetermined; + coord_stride = 3 + coord_striding = tl.arange(0, block_size) * coord_stride + # as the name suggests, this is effectively every node/atom + coord_row_offset = coord_striding + (block_size * coord_stride * block_id) + output_stride = 3 # [2l + 1] + output_striding = tl.arange(0, block_size) * output_stride + output_row_offset = output_striding + (block_size * output_stride * block_id) + # load in gradients w.r.t. spherical harmonic projections + g_Y10 = tl.load( + sph_grad_ptr + output_row_offset, mask=output_row_offset < output_numel + ) + g_Y11 = tl.load( + sph_grad_ptr + output_row_offset + 1, mask=output_row_offset + 1 < output_numel + ) + g_Y12 = tl.load( + sph_grad_ptr + output_row_offset + 2, mask=output_row_offset + 2 < output_numel + ) + CONST_00 = tl.sqrt(3.0) + g_x = CONST_00 * g_Y10 + g_y = CONST_00 * g_Y11 + g_z = CONST_00 * g_Y12 + # write out gradients + tl.store( + coord_grad_ptr + coord_row_offset, g_x, mask=coord_row_offset < coord_numel + ) + tl.store( + coord_grad_ptr + coord_row_offset + 1, + g_y, + mask=coord_row_offset + 1 < coord_numel, + ) + tl.store( + coord_grad_ptr + coord_row_offset + 2, + g_z, + mask=coord_row_offset + 2 < coord_numel, + ) From ea428abd1d4d513cda5c732248a9b9be570cb3c9 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Mon, 26 Aug 2024 15:12:48 -0700 Subject: [PATCH 038/116] feat: added hacky zeroth order and udpate tests --- .../direct/tests/test_direct_sph_harm.py | 20 ++++-- src/equitriton/sph_harm/direct/y_0.py | 69 +++++++++++++++++++ 2 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 src/equitriton/sph_harm/direct/y_0.py diff --git a/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py index a7350fc..5b39b0a 100644 --- a/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py +++ b/src/equitriton/sph_harm/direct/tests/test_direct_sph_harm.py @@ -30,8 +30,14 @@ @pytest.mark.parametrize( "dtype", [ - pytest.param(torch.float16, marks=pytest.mark.xfail(reason="low precision")), - pytest.param(torch.bfloat16, marks=pytest.mark.xfail(reason="low precision")), + pytest.param( + torch.float16, + marks=pytest.mark.xfail(raises=AssertionError, reason="low precision"), + ), + pytest.param( + torch.bfloat16, + marks=pytest.mark.xfail(raises=AssertionError, reason="low precision"), + ), torch.float32, torch.float64, ], @@ -68,8 +74,14 @@ def test_forward_equivalence(order, device, tensor_shape, dtype): @pytest.mark.parametrize( "dtype", [ - pytest.param(torch.float16, marks=pytest.mark.xfail(reason="low precision")), - pytest.param(torch.bfloat16, marks=pytest.mark.xfail(reason="low precision")), + pytest.param( + torch.float16, + marks=pytest.mark.xfail(raises=AssertionError, reason="low precision"), + ), + pytest.param( + torch.bfloat16, + marks=pytest.mark.xfail(raises=AssertionError, reason="low precision"), + ), torch.float32, torch.float64, ], diff --git a/src/equitriton/sph_harm/direct/y_0.py b/src/equitriton/sph_harm/direct/y_0.py new file mode 100644 index 0000000..04ba291 --- /dev/null +++ b/src/equitriton/sph_harm/direct/y_0.py @@ -0,0 +1,69 @@ +import triton +import torch +from triton import language as tl + +__all__ = ["ZerothOrderSphericalHarmonic"] + + +class ZerothOrderSphericalHarmonic(torch.autograd.Function): + @staticmethod + def forward( + ctx, + coords: torch.Tensor, + mask: torch.Tensor | None = None, + block_size: int = 64, + ): + output_tensor = torch.ones( + (*coords.shape[:-1], 1), dtype=coords.dtype, device=coords.device + ) + ctx.save_for_backward(coords) + return output_tensor + + @staticmethod + def backward( + ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 + ) -> torch.Tensor: + (coords,) = ctx.saved_tensors + return torch.zeros_like(coords) + + +def _torch_fwd(coords: torch.Tensor) -> torch.Tensor: + """ + PyTorch implementation of the kernel. This is designed + purely for unit testing to ensure that the Triton implementation + is behaving as intended. + + This function is generically named to make it easier for + it to be called programmatically: it is _not_ intended + to be used manually. + + Parameters + ---------- + coords : torch.Tensor + N-d tensor, where the last dimension corresponds to + xyz values. + + Returns + ------- + torch.Tensor + N-d tensor, where the last dimension corresponds to + each projection of the second order spherical harmonic. + """ + x = coords[..., 0].contiguous().unsqueeze(-1) + output = torch.ones_like(x) + return output + + +@triton.jit +def zeroth_order_fwd( + output_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, +): + # work out the row offsets + block_id = tl.program_id(0) + output_stride = 1 # [2l + 1] + output_striding = tl.arange(0, block_size) * output_stride + output_row_offset = output_striding + (block_size * output_stride * block_id) + tl.store(output_ptr + output_row_offset, 1.0, mask=output_row_offset < output_numel) From ef174d91821e2f69ef074752bf16a073fb9167de Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Mon, 26 Aug 2024 19:57:10 -0700 Subject: [PATCH 039/116] deps: added training dependencies --- pyproject.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index e2e5acf..e2325be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,12 @@ dev = [ "pytest-pretty", "jupyter" ] +train = [ + "torch-geometric", + "torch-scatter", + "torch-sparse", + "pytorch-lightning==2.2.4" +] [tool.setuptools.dynamic] readme = {file = ["README.md"]} From 7243fe89cd933c8e222eed433ca246d33d99ae74 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Mon, 26 Aug 2024 19:57:49 -0700 Subject: [PATCH 040/116] git: ignoring aux files --- notebooks/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 notebooks/.gitignore diff --git a/notebooks/.gitignore b/notebooks/.gitignore new file mode 100644 index 0000000..b75f584 --- /dev/null +++ b/notebooks/.gitignore @@ -0,0 +1,2 @@ +qm9_data/ +lightning_logs/ From a5d9293ae73bceee49535fa92600767c20c5670e Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Mon, 26 Aug 2024 20:42:29 -0700 Subject: [PATCH 041/116] notebook: added self-contained notebook for testing baseline architecture --- notebooks/Baseline model development.ipynb | 1240 ++++++++++++++++++++ 1 file changed, 1240 insertions(+) create mode 100644 notebooks/Baseline model development.ipynb diff --git a/notebooks/Baseline model development.ipynb b/notebooks/Baseline model development.ipynb new file mode 100644 index 0000000..f14a811 --- /dev/null +++ b/notebooks/Baseline model development.ipynb @@ -0,0 +1,1240 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "fd832b52-7df1-4d42-a2ea-a7139df2196b", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Literal, Callable, Any\n", + "from math import ceil\n", + "\n", + "import torch\n", + "from torch import nn\n", + "from torch.utils.data import random_split\n", + "from torch.optim import AdamW\n", + "import e3nn\n", + "from e3nn import o3\n", + "from torch_scatter import scatter\n", + "from torch_geometric.data import Data as PyGGraph\n", + "from torch_geometric.datasets import QM9\n", + "from torch_geometric.loader import DataLoader\n", + "import pytorch_lightning as pl\n", + "\n", + "from equitriton.sph_harm.direct import triton_spherical_harmonic\n", + "from equitriton.utils import spherical_harmonics_irreps" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a0307714-1036-4110-8b53-0c50062ee913", + "metadata": {}, + "outputs": [], + "source": [ + "seed = torch.manual_seed(215162)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "79f96b13-929a-4b2e-a4d9-991194fd98ba", + "metadata": {}, + "outputs": [], + "source": [ + "class AtomEmbedding(nn.Module):\n", + " def __init__(self, num_atoms: int, atom_dim: int):\n", + " super().__init__()\n", + " self.embedding = nn.Embedding(num_atoms, atom_dim, padding_idx=0)\n", + "\n", + " def forward(self, atomic_numbers: torch.LongTensor) -> torch.Tensor:\n", + " return self.embedding(atomic_numbers)\n", + "\n", + "\n", + "class EdgeEmbedding(nn.Module):\n", + " def __init__(self, num_basis: int, radius_cutoff: float = 6.0, **kwargs):\n", + " \"\"\"\n", + " This module embeds edges in a graph with an EdgeEmbedding object.\n", + "\n", + " Parameters\n", + " ----------\n", + " num_basis : int, optional\n", + " The number of basis functions. Defaults to 1.\n", + " radius_cutoff : float, optional\n", + " The maximum radius up to which basis functions are defined. Defaults to 6.0.\n", + "\n", + " Optional kwargs\n", + " ---------------\n", + " basis : str, optional\n", + " The type of basis function to use. Defaults to 'bessel'.\n", + " start : float, optional\n", + " The starting point in the distance grid used in the radial basis.\n", + " cutoff : bool, optional\n", + " Whether or not to apply a cutoff to the basis functions.\n", + "\n", + " Returns\n", + " -------\n", + " torch.Tensor\n", + " A tensor representing the embedding of edges with shape (num_edges, num_basis).\n", + "\n", + " Examples\n", + " --------\n", + " >>> # Define an instance of EdgeEmbedding with 4 basis functions and a radius cutoff of 10.\n", + " >>> embedder = EdgeEmbedding(num_basis=4, radius_cutoff=10.0)\n", + " \"\"\"\n", + " super().__init__()\n", + " kwargs.setdefault(\"basis\", \"bessel\")\n", + " kwargs.setdefault(\"start\", 0.0)\n", + " kwargs.setdefault(\"cutoff\", True)\n", + " self.num_basis = num_basis\n", + " self.radius_cutoff = radius_cutoff\n", + " self.basis_kwargs = kwargs\n", + "\n", + " def forward(self, distances: torch.Tensor) -> torch.Tensor:\n", + " basis_funcs = e3nn.math.soft_one_hot_linspace(\n", + " distances,\n", + " number=self.num_basis,\n", + " end=self.radius_cutoff,\n", + " **self.basis_kwargs,\n", + " )\n", + " return basis_funcs * self.num_basis**0.5\n", + "\n", + "\n", + "class SphericalHarmonicEmbedding(nn.Module):\n", + " def __init__(\n", + " self,\n", + " l_values: list[int],\n", + " normalize: bool = True,\n", + " normalization: Literal[\"norm\", \"integral\", \"component\"] = \"integral\",\n", + " ):\n", + " \"\"\"\n", + " Projects cartesian positions onto spherical harmonic functions.\n", + " \"\"\"\n", + " super().__init__()\n", + " self.l_values = list(sorted(l_values))\n", + " self.irreps = spherical_harmonics_irreps(self.l_values, num_feat=1)\n", + " self.normalize = normalize\n", + " self.normalization = normalization\n", + "\n", + " def forward(self, coords: torch.Tensor) -> torch.Tensor:\n", + " outputs = [triton_spherical_harmonic(l, coords) for l in self.l_values]\n", + " return torch.cat(outputs, dim=-1)\n", + "\n", + "\n", + "class InteractionBlock(nn.Module):\n", + " def __init__(\n", + " self,\n", + " atomic_dim: int | o3.Irreps,\n", + " l_values: int,\n", + " edge_dim: int,\n", + " hidden_dim: int,\n", + " radius_cutoff: float,\n", + " degree_norm: float,\n", + " edge_kwargs: dict[str, Any] = {},\n", + " sph_harm_kwargs: dict[str, Any] = {},\n", + " activation: Callable = nn.functional.silu,\n", + " ):\n", + " \"\"\"\n", + " A module that combines radial basis with spherical harmonics to\n", + " describe molecular interactions.\n", + "\n", + " Parameters\n", + " ----------\n", + " atomic_dim : int | o3.Irreps\n", + " Dimension of the atomic features. If int, it is treated as a\n", + " single irreducible representation.\n", + " l_values : int\n", + " Values of the spherical harmonic order.\n", + " edge_dim : int\n", + " Dimension of the edge features.\n", + " hidden_dim : int\n", + " Hidden dimension for the fully connected network.\n", + " radius_cutoff : float\n", + " Cutoff radius for the radial basis.\n", + " degree_norm : float\n", + " Normalization factor for the degree of the graph.\n", + " edge_kwargs : dict[str, Any], optional\n", + " Keyword arguments for the EdgeEmbedding module. Defaults to {}.\n", + " sph_harm_kwargs : dict[str, Any], optional\n", + " Keyword arguments for the SphericalHarmonicEmbedding module.\n", + " Defaults to {}.\n", + " activation : Callable, optional\n", + " Activation function for the fully connected network. Defaults to\n", + " nn.functional.silu.\n", + "\n", + " Notes\n", + " -----\n", + " The `degree_norm` attribute is set as a property and effectively\n", + " represents the average number of neighbors in other models.\n", + "\n", + " Examples\n", + " --------\n", + " >>> block = InteractionBlock(atomic_dim=8, l_values=[0, 1],\n", + " edge_dim=16, hidden_dim=32)\n", + " >>> block.sph_irreps\n", + " ['1x0e', '2x0e']\n", + " \"\"\"\n", + "\n", + " super().__init__()\n", + " # this is effectively the average number of neighbors in other models\n", + " self.degree_norm = degree_norm\n", + " # treat atom features as invariant\n", + " if isinstance(atomic_dim, int):\n", + " atomic_irreps = f\"{atomic_dim}x0e\"\n", + " else:\n", + " atomic_irreps = atomic_dim\n", + " self.atomic_irreps = atomic_irreps\n", + " self.l_values = list(sorted(l_values))\n", + " # these two attributes are similar but different: the former is used for describing\n", + " # the basis itself, and the latter is for actually specifying the weights\n", + " self.sph_irreps = spherical_harmonics_irreps(self.l_values, num_feat=1)\n", + " self.output_irreps = spherical_harmonics_irreps(\n", + " self.l_values, num_feat=hidden_dim\n", + " )\n", + " # tensor product is the final bit the combines the radial basis with the spherical\n", + " # harmonics\n", + " self.tensor_product = o3.FullyConnectedTensorProduct(\n", + " self.atomic_irreps,\n", + " self.sph_irreps,\n", + " self.output_irreps,\n", + " shared_weights=False,\n", + " )\n", + " self.edge_basis = EdgeEmbedding(edge_dim, radius_cutoff, **edge_kwargs)\n", + " self.spherical_harmonics = SphericalHarmonicEmbedding(\n", + " l_values, **sph_harm_kwargs\n", + " )\n", + " self.fc = e3nn.nn.FullyConnectedNet(\n", + " [edge_dim, hidden_dim, self.tensor_product.weight_numel], activation\n", + " )\n", + "\n", + " @property\n", + " def num_projections(self) -> int:\n", + " \"\"\"Returns the expected number of projections.\"\"\"\n", + " return sum([2 * l + 1 for l in self.l_values])\n", + "\n", + " @property\n", + " def output_dim(self) -> int:\n", + " \"\"\"Returns the dimensionality of the output.\"\"\"\n", + " return self.output_irreps.dim\n", + "\n", + " def forward(\n", + " self,\n", + " atomic_features: torch.Tensor,\n", + " coords: torch.Tensor,\n", + " edge_index: torch.LongTensor,\n", + " ) -> torch.Tensor:\n", + " \"\"\"\n", + " High-level description:\n", + "\n", + " 1. Project cartesian coordinates onto spherical harmonic basis\n", + " 2. Project interatomic distances onto radial (bessel) basis\n", + " 3. Transform radial basis functions with learnable weights\n", + " 4. Compute tensor product between scalar atom features and spherical harmonic basis\n", + " 5. Update node features\n", + " \"\"\"\n", + " edge_dist = coords[edge_index[0]] - coords[edge_index[1]]\n", + " sph_harm = self.spherical_harmonics(edge_dist)\n", + " # calculate atomic distances, embed, and transform them\n", + " edge_basis = self.edge_basis(edge_dist.norm(dim=-1))\n", + " edge_z = self.fc(edge_basis)\n", + " # compute tensor product\n", + " messages = self.tensor_product(atomic_features[edge_index[0]], sph_harm, edge_z)\n", + " # update node features\n", + " hidden_feats = (\n", + " scatter(messages, edge_index[1], dim=0, dim_size=atomic_features.size(0))\n", + " / self.degree_norm\n", + " )\n", + " return hidden_feats\n", + "\n", + "\n", + "class ScalarReadoutLayer(nn.Module):\n", + " def __init__(self, hidden_irreps: o3.Irreps, output_dim: int):\n", + " super().__init__()\n", + " self.hidden_irreps = hidden_irreps\n", + " self.output_irreps = o3.Irreps(f\"{output_dim}x0e\")\n", + " self.output_layer = o3.Linear(\n", + " irreps_in=hidden_irreps, irreps_out=self.output_irreps\n", + " )\n", + "\n", + " def forward(self, node_feats: torch.Tensor) -> torch.Tensor:\n", + " return self.output_layer(node_feats)\n", + "\n", + "\n", + "class EquiTritonModel(nn.Module):\n", + " def __init__(\n", + " self,\n", + " initial_atom_dim: int,\n", + " num_layers: int,\n", + " output_dim: int,\n", + " l_values: int,\n", + " edge_dim: int,\n", + " hidden_dim: int,\n", + " radius_cutoff: float,\n", + " degree_norm: float,\n", + " edge_kwargs: dict[str, Any] = {},\n", + " sph_harm_kwargs: dict[str, Any] = {},\n", + " activation: Callable = nn.functional.silu,\n", + " num_atoms: int = 100,\n", + " skip_connections: bool = True,\n", + " ):\n", + " \"\"\"\n", + " A neural network model designed for processing molecular graphs.\n", + "\n", + " This class implements a hierarchical architecture with multiple interaction blocks,\n", + " allowing for efficient and scalable processing of large molecular datasets.\n", + "\n", + " Parameters:\n", + " initial_atom_dim (int): The dimensionality of the atomic embeddings.\n", + " num_layers (int): The number of convolutional layers to use.\n", + " output_dim (int): The dimensionality of the final scalar features.\n", + " l_values (int): A list of spherical harmonics order to consider.\n", + " edge_dim (int): The dimensionality of the edge features.\n", + " hidden_dim (int): The dimensionality of the hidden state in each interaction block.\n", + " radius_cutoff (float): The cutoff distance for radial basis functions.\n", + " degree_norm (float): The normalization constant for edge features. Typically square root of the average degree.\n", + " edge_kwargs (dict[str, Any], optional): Keyword arguments to pass to the InteractionBlock. Defaults to {}.\n", + " sph_harm_kwargs (dict[str, Any], optional): Keyword arguments to pass to the InteractionBlock. Defaults to {}.\n", + " activation (Callable, optional): The activation function to use in each interaction block. Defaults to nn.functional.silu.\n", + " num_atoms (int, optional): The number of atoms in the embedding table (i.e. unique elements). Defaults to 100.\n", + " skip_connections (bool, optional): Whether to enable residual connections between layers. Defaults to True.\n", + "\n", + " Returns:\n", + " tuple[torch.Tensor, torch.Tensor]: A tuple containing the graph-level scalar features and the node features.\n", + "\n", + " Examples:\n", + " >>> model = EquiTritonModel(...)\n", + " >>> graph = PyGGraph(...).to(device=\"cuda\")\n", + " >>> graph_z, z = model(graph)\n", + "\n", + " Note: This class uses PyTorch Geometric's Graph data structure and assumes that the input graph has already been processed using a suitable preprocessing step.\n", + " \"\"\"\n", + " super().__init__()\n", + " self.atomic_embedding = AtomEmbedding(num_atoms, initial_atom_dim)\n", + " self.initial_layer = InteractionBlock(\n", + " initial_atom_dim,\n", + " l_values,\n", + " edge_dim,\n", + " hidden_dim,\n", + " radius_cutoff,\n", + " degree_norm,\n", + " edge_kwargs,\n", + " sph_harm_kwargs,\n", + " activation,\n", + " )\n", + " self.conv_layers = nn.ModuleDict()\n", + " for layer_index in range(num_layers + 1):\n", + " self.conv_layers[f\"conv_{layer_index}\"] = InteractionBlock(\n", + " self.initial_layer.output_dim,\n", + " l_values,\n", + " edge_dim,\n", + " hidden_dim,\n", + " radius_cutoff,\n", + " degree_norm,\n", + " edge_kwargs,\n", + " sph_harm_kwargs,\n", + " activation,\n", + " )\n", + " self.scalar_readout = ScalarReadoutLayer(\n", + " self.initial_layer.output_irreps, output_dim\n", + " )\n", + " self.skip_connections = skip_connections\n", + " self.output_dim = output_dim\n", + "\n", + " def forward(self, graph: PyGGraph) -> tuple[torch.Tensor, torch.Tensor]:\n", + " # determine if the graph is batched or not\n", + " is_batched = hasattr(graph, \"ptr\")\n", + " # get atom embeddings\n", + " atom_z = self.atomic_embedding(graph.z) # [nodes, initial_atom_dim]\n", + " # first message passing step\n", + " z = self.initial_layer(atom_z, graph.pos, graph.edge_index)\n", + " outputs = {}\n", + " for layer_name, layer in self.conv_layers.items():\n", + " new_z = layer(z, graph.pos, graph.edge_index)\n", + " # add residual connections\n", + " if self.skip_connections and new_z.shape == z.shape:\n", + " new_z += z\n", + " z = new_z\n", + " outputs[layer_name] = z\n", + " # map final output as scalars\n", + " z = self.scalar_readout(z)\n", + " # latest node features are in z; we generate graph-level scalar features\n", + " # by doing a scatter add\n", + " if is_batched:\n", + " graph_z = scatter(z, graph.batch, dim=0, dim_size=graph.batch_size)\n", + " else:\n", + " # for a single graph, just sum up the node features\n", + " graph_z = z.sum(dim=0, keepdims=True)\n", + " return graph_z, z" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "4e568b72-3990-4652-8a32-db375c65a81b", + "metadata": {}, + "outputs": [], + "source": [ + "def make_fake_graph(\n", + " num_nodes: int,\n", + " num_edges: int,\n", + " coord_scale: float = 1.0,\n", + " max_atomic_number: int = 100,\n", + " device=\"cuda\",\n", + "):\n", + " coords = torch.rand(num_nodes, 3, device=device) * coord_scale\n", + " edge_index = torch.randint(0, high=num_nodes, size=(2, num_edges), device=device)\n", + " atomic_numbers = torch.randint(\n", + " 0, max_atomic_number, size=(num_nodes,), device=device\n", + " )\n", + " return coords, edge_index, atomic_numbers" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "8671f0e8-0da8-4250-9d02-fb30ecb977fe", + "metadata": {}, + "outputs": [], + "source": [ + "edge_embedder = EdgeEmbedding(num_basis=10)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "f4971d48-3574-44d0-b2fb-27306bc3aeac", + "metadata": {}, + "outputs": [], + "source": [ + "coords, edge_index, atomic_numbers = make_fake_graph(\n", + " 16,\n", + " 12,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "55f14f94-51ca-4e5c-ad63-ca830404eaf9", + "metadata": {}, + "outputs": [], + "source": [ + "# coords = torch.ones_like(coords, requires_grad=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "67f6e836-f4e0-48cf-981d-4419dda0159b", + "metadata": {}, + "outputs": [], + "source": [ + "atom_embedder = AtomEmbedding(100, 64).to(\"cuda\")\n", + "atom_z = atom_embedder(atomic_numbers)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "46778ea6-92f2-4d7a-9bd8-aa58013cd1c6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "torch.Size([2, 12])" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "edge_index.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "0c82e80f-c4bd-43fe-8846-dab203757992", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "layer = InteractionBlock(\n", + " 64, [2, 3, 4, 5], 10, 32, radius_cutoff=6.0, degree_norm=17**0.5\n", + ").to(\"cuda\")" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "a0913f1e-b000-4a3b-b723-84a92d16425d", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "next_layer = InteractionBlock(\n", + " layer.output_irreps, [2, 3, 4, 5], 10, 32, radius_cutoff=6.0, degree_norm=17**0.5\n", + ").to(\"cuda\")" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "e893b896-f7b9-4b62-95a1-fe7fb45cf2ed", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "InteractionBlock(\n", + " (tensor_product): FullyConnectedTensorProduct(64x0e x 1x2e+1x3o+1x4e+1x5o -> 32x2e+32x3o+32x4e+32x5o | 8192 paths | 8192 weights)\n", + " (edge_basis): EdgeEmbedding()\n", + " (spherical_harmonics): SphericalHarmonicEmbedding()\n", + " (fc): FullyConnectedNet[10, 32, 8192]\n", + ")" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "layer" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "f2785a28-4d2b-4cf0-972d-60df70595580", + "metadata": {}, + "outputs": [], + "source": [ + "o = layer(atom_z, coords, edge_index)" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "4b80d729-b5e4-4cb5-802d-c27f2af32a70", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([[ 0.1235, 0.0030, 0.0861, ..., 0.3042, 0.1641, -0.0114],\n", + " [ 0.0000, 0.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000],\n", + " [ 0.0167, -0.0017, 0.0098, ..., -0.0773, -0.0233, 0.0344],\n", + " ...,\n", + " [ 0.0000, 0.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000],\n", + " [-0.0578, 0.0226, -0.0362, ..., -0.0003, 0.0016, 0.0056],\n", + " [ 0.0000, 0.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000]],\n", + " device='cuda:0', grad_fn=)" + ] + }, + "execution_count": 67, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "o" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "dae3c6e7-8d8f-49bd-9239-8b4b5c6af499", + "metadata": {}, + "outputs": [], + "source": [ + "p = next_layer(o, coords, edge_index)" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "a322cad7-60c8-4710-9257-2e3f0d4405be", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "torch.Size([16, 1184])" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "o.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "599ae7e0-5a54-429d-af9b-749de7ce2434", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([[ 0.3229, -0.0011, 0.2210, ..., 0.3184, 0.1664, -0.0134],\n", + " [ 0.0000, 0.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000],\n", + " [-0.7950, 0.0826, -0.4642, ..., -0.1652, -0.0498, 0.0736],\n", + " ...,\n", + " [ 0.0000, 0.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000],\n", + " [ 0.1217, -0.0898, -0.0128, ..., -0.0215, 0.0755, -0.0243],\n", + " [ 0.0000, 0.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000]],\n", + " device='cuda:0', grad_fn=)" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "o + p" + ] + }, + { + "cell_type": "markdown", + "id": "090ad5de-1172-4bdf-83c9-92eda962a398", + "metadata": {}, + "source": [ + "## Dataset definition" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "8e448746-66f8-484f-8971-1c89c8f75135", + "metadata": {}, + "outputs": [], + "source": [ + "class LightningQM9(pl.LightningDataModule):\n", + " def __init__(\n", + " self,\n", + " root_path: str = \"./qm9_data\",\n", + " batch_size: int = 16,\n", + " train_frac: float = 0.8,\n", + " val_frac: float = 0.1,\n", + " num_workers: int = 0,\n", + " ):\n", + " \"\"\"\n", + " Custom data module for QM9 dataset.\n", + "\n", + " Parameters\n", + " ----------\n", + " root_path : str, optional (default: \"./qm9_data\")\n", + " Path to the QM9 dataset.\n", + " batch_size : int, optional (default: 16)\n", + " Number of samples in each mini-batch.\n", + " train_frac : float, optional (default: 0.8)\n", + " Fraction of data used for training.\n", + " val_frac : float, optional (default: 0.1)\n", + " Fraction of data used for validation.\n", + " num_workers : int, optional (default: 0)\n", + " Number of worker processes to use for loading data.\n", + "\n", + " Examples\n", + " --------\n", + " >>> dm = LightningQM9(root_path=\"/path/to/qm9_data\", batch_size=32)\n", + "\n", + " Attributes\n", + " ----------\n", + " dataset : QM9\n", + " Loaded QM9 dataset.\n", + " hparams : dict\n", + " Hyperparameters of the data module.\n", + "\n", + " Methods\n", + " -------\n", + " setup(stage: str)\n", + " Setup data splits for training, validation and testing.\n", + " train_dataloader()\n", + " Returns a DataLoader instance for training data.\n", + " val_dataloader()\n", + " Returns a DataLoader instance for validation data.\n", + " test_dataloader()\n", + " Returns a DataLoader instance for testing data.\n", + " \"\"\"\n", + " super().__init__()\n", + " self.dataset = QM9(root_path)\n", + " self.save_hyperparameters()\n", + "\n", + " def setup(self, stage: str):\n", + " hparams = self.hparams\n", + " num_samples = len(self.dataset)\n", + " num_train = int(num_samples * hparams[\"train_frac\"])\n", + " num_val = int(num_samples * hparams[\"val_frac\"])\n", + " num_test = ceil(\n", + " num_samples * (1 - (hparams[\"train_frac\"] + hparams[\"val_frac\"]))\n", + " )\n", + " # generate random splits\n", + " train_split, val_split, test_split = random_split(\n", + " self.dataset, lengths=[num_train, num_val, num_test]\n", + " )\n", + " self.splits = {\"train\": train_split, \"val\": val_split, \"test\": test_split}\n", + "\n", + " def train_dataloader(self):\n", + " return DataLoader(\n", + " self.splits[\"train\"],\n", + " batch_size=self.hparams[\"batch_size\"],\n", + " shuffle=True,\n", + " num_workers=self.hparams[\"num_workers\"],\n", + " )\n", + "\n", + " def val_dataloader(self):\n", + " return DataLoader(\n", + " self.splits[\"val\"],\n", + " batch_size=self.hparams[\"batch_size\"],\n", + " shuffle=False,\n", + " num_workers=self.hparams[\"num_workers\"],\n", + " )\n", + "\n", + " def test_dataloader(self):\n", + " return DataLoader(\n", + " self.splits[\"test\"],\n", + " batch_size=self.hparams[\"batch_size\"],\n", + " shuffle=False,\n", + " num_workers=self.hparams[\"num_workers\"],\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "3c15f669-04ca-418b-8f22-81993eafbce0", + "metadata": {}, + "source": [ + "## Loss and Lightning module\n", + "\n", + "Model trains optionally with a loss target that Nequip and MACE uses, which is the atom-weighted MSE. For now we're only using a single target, but can expand to use the full QM9 set of targets too." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "6affd332-fe3a-49dd-b0c2-e20fcb974a04", + "metadata": {}, + "outputs": [], + "source": [ + "class AtomWeightedMSE(nn.Module):\n", + " \"\"\"\n", + " Calculates the mean-squared-error between predicted and targets,\n", + " weighted by the number of atoms within each graph.\n", + "\n", + " From matsciml\n", + " \"\"\"\n", + "\n", + " def forward(\n", + " self,\n", + " input: torch.Tensor,\n", + " target: torch.Tensor,\n", + " atoms_per_graph: torch.Tensor,\n", + " ) -> torch.Tensor:\n", + " if atoms_per_graph.size(0) != target.size(0):\n", + " raise RuntimeError(\n", + " \"Dimensions for atom-weighted loss do not match:\"\n", + " f\" expected atoms_per_graph to have {target.size(0)} elements; got {atoms_per_graph.size(0)}.\"\n", + " \"This loss is intended to be applied to scalar targets only.\"\n", + " )\n", + " # check to make sure we are broad casting correctly\n", + " if (input.ndim != target.ndim) and target.size(-1) == 1:\n", + " input.unsqueeze_(-1)\n", + " # for N-d targets, we might want to keep unsqueezing\n", + " while atoms_per_graph.ndim < target.ndim:\n", + " atoms_per_graph.unsqueeze_(-1)\n", + " # ensures that atoms_per_graph is type cast correctly\n", + " squared_error = ((input - target) / atoms_per_graph.to(input.dtype)) ** 2.0\n", + " return squared_error.mean()\n", + "\n", + "\n", + "class EquiTritonLitModule(pl.LightningModule):\n", + " def __init__(\n", + " self,\n", + " model_class: type,\n", + " model_kwargs,\n", + " e_mean: float,\n", + " e_std: float,\n", + " lr: float = 1e-3,\n", + " weight_decay: float = 0.0,\n", + " atom_weighted_loss: bool = True,\n", + " ):\n", + " \"\"\"\n", + " Initializes the EquiTritonLitModule clas.\n", + "\n", + " Parameters\n", + " ----------\n", + " model_class : type\n", + " Th class of the model to be used.\n", + " model_kwargs : dict\n", + " Keyword argument for the model initialization.\n", + " e_mean : float\n", + " The mean of the energy values.\n", + " e_std : float\n", + " The standard deviation of the energy values.\n", + " lr : float, optional\n", + " The learning rate (default is 1e-3) for AdamW.\n", + " weight_decay : float, optional\n", + " Weight decay value (default is 0.0).\n", + " atom_weighted_loss : bool, optional\n", + " Whether to use atom-weighted loss or not (default is True).\n", + " \"\"\"\n", + " super().__init__()\n", + " self.model = model_class(**model_kwargs)\n", + " if atom_weighted_loss:\n", + " self.loss = AtomWeightedMSE()\n", + " else:\n", + " self.loss = nn.MSELoss()\n", + " self.output_head = nn.Linear(self.model.output_dim, 1)\n", + " self.save_hyperparameters()\n", + "\n", + " def configure_optimizers(self):\n", + " return AdamW(\n", + " self.parameters(),\n", + " lr=self.hparams[\"lr\"],\n", + " weight_decay=self.hparams[\"weight_decay\"],\n", + " )\n", + "\n", + " def step(self, graph: PyGGraph, stage: Literal[\"train\", \"test\", \"val\"]):\n", + " \"\"\"\n", + " Performs a single step of the training, validation or testing\n", + " process.\n", + "\n", + " Parameters\n", + " ----------\n", + " graph : PyGGraph\n", + " The input graph.\n", + " stage : Literal[\"train\", \"test\", \"val\"]\n", + " The current stage (training, testing or validation).\n", + "\n", + " Returns\n", + " -------\n", + " loss : float\n", + " The calculated loss value.\n", + " \"\"\"\n", + " g_z, z = self.model(graph)\n", + " pred_energy = self.output_head(g_z)\n", + " target_energy = graph.y[:, 12].unsqueeze(-1)\n", + " norm_energy = (target_energy - self.hparams[\"e_mean\"]) / self.hparams[\"e_std\"]\n", + " if self.hparams[\"atom_weighted_loss\"]:\n", + " loss = self.loss(pred_energy, norm_energy, torch.diff(graph.ptr))\n", + " else:\n", + " loss = self.loss(pred_energy, norm_energy)\n", + " batch_size = getattr(graph, \"batch_size\", 1)\n", + " self.log(\n", + " f\"{stage}_loss\", loss, prog_bar=True, on_step=True, batch_size=batch_size\n", + " )\n", + " return loss\n", + "\n", + " def training_step(self, batch):\n", + " loss = self.step(batch, \"train\")\n", + " return loss\n", + "\n", + " def validation_step(self, batch):\n", + " loss = self.step(batch, \"val\")\n", + " return loss\n", + "\n", + " def test_step(self, batch):\n", + " loss = self.step(batch, \"test\")\n", + " return loss" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "2d7ce968-f33c-46dd-88e3-8ad47480a9e7", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch_geometric/data/dataset.py:238: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n", + " if osp.exists(f) and torch.load(f) != _repr(self.pre_transform):\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch_geometric/data/dataset.py:246: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n", + " if osp.exists(f) and torch.load(f) != _repr(self.pre_filter):\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch_geometric/io/fs.py:215: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n", + " return torch.load(f, map_location)\n" + ] + } + ], + "source": [ + "dm = LightningQM9(\"./qm9_data/\", batch_size=64)\n", + "dm.setup(\"fit\")\n", + "\n", + "train_loader = dm.train_dataloader()" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "9c89a710-1e68-485c-9325-0b06c7b52b75", + "metadata": {}, + "outputs": [], + "source": [ + "values = torch.cat([sample.y[:, 12] for sample in dm.dataset])\n", + "e_mean = values.mean()\n", + "e_std = values.std()" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "ef3c9902-3673-4c88-a98e-1a4553a5990f", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + " warnings.warn(\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + " warnings.warn(\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + " warnings.warn(\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + " warnings.warn(\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + " warnings.warn(\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "lit_mod = EquiTritonLitModule(\n", + " EquiTritonModel,\n", + " model_kwargs={\n", + " \"initial_atom_dim\": 64,\n", + " \"num_layers\": 3,\n", + " \"output_dim\": 48,\n", + " \"l_values\": [0, 1, 2, 4, 6, 8],\n", + " \"edge_dim\": 10,\n", + " \"hidden_dim\": 16,\n", + " \"radius_cutoff\": 6.0,\n", + " \"degree_norm\": 37.5**0.5,\n", + " },\n", + " e_mean=e_mean,\n", + " e_std=e_std,\n", + " atom_weighted_loss=False,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "1104fe2c-ada6-448b-9e3d-cab1bc491c95", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "GPU available: True (cuda), used: True\n", + "TPU available: False, using: 0 TPU cores\n", + "IPU available: False, using: 0 IPUs\n", + "HPU available: False, using: 0 HPUs\n" + ] + } + ], + "source": [ + "trainer = pl.Trainer(max_epochs=30, accelerator=\"gpu\")" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "e30a0671-efa3-403b-9d8b-cc2cb63bff20", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]\n", + "\n", + " | Name | Type | Params\n", + "------------------------------------------------\n", + "0 | model | EquiTritonModel | 4.8 M \n", + "1 | loss | MSELoss | 0 \n", + "2 | output_head | Linear | 49 \n", + "------------------------------------------------\n", + "4.8 M Trainable params\n", + "0 Non-trainable params\n", + "4.8 M Total params\n", + "19.300 Total estimated model params size (MB)\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Sanity Checking: | …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/pytorch_lightning/trainer/connectors/data_connector.py:441: The 'val_dataloader' does not have many workers which may be a bottleneck. Consider increasing the value of the `num_workers` argument` to `num_workers=27` in the `DataLoader` to improve performance.\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/pytorch_lightning/trainer/connectors/data_connector.py:441: The 'train_dataloader' does not have many workers which may be a bottleneck. Consider increasing the value of the `num_workers` argument` to `num_workers=27` in the `DataLoader` to improve performance.\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "d990cd3f7cca46fe8dac82edee407d90", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Training: | …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Validation: | …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Validation: | …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Validation: | …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Validation: | …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Validation: | …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Validation: | …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Validation: | …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Validation: | …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Validation: | …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Validation: | …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Validation: | …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Validation: | …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/pytorch_lightning/trainer/call.py:54: Detected KeyboardInterrupt, attempting graceful shutdown...\n" + ] + } + ], + "source": [ + "trainer.fit(lit_mod, datamodule=dm)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0663a128-606c-4944-8877-73deafc30305", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 293228524ea4fa72ffee127469bef41946070a05 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Mon, 26 Aug 2024 20:43:05 -0700 Subject: [PATCH 042/116] notebook: updating latest direct evaluation notebook --- ...ct evaluation of spherical harmonics.ipynb | 234 +++++++++++++----- 1 file changed, 166 insertions(+), 68 deletions(-) diff --git a/notebooks/Direct evaluation of spherical harmonics.ipynb b/notebooks/Direct evaluation of spherical harmonics.ipynb index 68e7a3a..35408cc 100644 --- a/notebooks/Direct evaluation of spherical harmonics.ipynb +++ b/notebooks/Direct evaluation of spherical harmonics.ipynb @@ -28,9 +28,9 @@ "\n", "# conversion mapping\n", "sph_to_cart = {\n", - " \"theta\": acos(z / sqrt(x**2. + y**2. + z**2)),\n", - " \"phi\": sign(y) * acos(x / sqrt(x**2 + y**2.)),\n", - " \"r\": sqrt(x**2. + y**2 + z**2)\n", + " \"theta\": acos(z / sqrt(x**2.0 + y**2.0 + z**2)),\n", + " \"phi\": sign(y) * acos(x / sqrt(x**2 + y**2.0)),\n", + " \"r\": sqrt(x**2.0 + y**2 + z**2),\n", "}\n", "\n", "# this is used to round floats: ~1e-7 corresponds to single precision\n", @@ -52,18 +52,24 @@ " x, y, z = symbols(\"x y z\", real=real_only)\n", " theta, phi = symbols(\"theta phi\")\n", " sph_to_cart = {\n", - " \"theta\": acos(z / sqrt(x**2. + y**2. + z**2)),\n", - " \"phi\": sign(y) * acos(x / sqrt(x**2 + y**2.)),\n", + " \"theta\": acos(z / sqrt(x**2.0 + y**2.0 + z**2)),\n", + " \"phi\": sign(y) * acos(x / sqrt(x**2 + y**2.0)),\n", " }\n", " num_projections = 2 * n + 1\n", " terms = {}\n", " for m in range(-n, n + 1):\n", - " sph_harm = Ynm(n, m, theta, phi).subs(sph_to_cart).expand(func=True).cancel().simplify()\n", + " sph_harm = (\n", + " Ynm(n, m, theta, phi)\n", + " .subs(sph_to_cart)\n", + " .expand(func=True)\n", + " .cancel()\n", + " .simplify()\n", + " )\n", " terms[f\"{n},{m}\"] = sph_harm\n", " return terms\n", "\n", "\n", - "def numerical_evaluation(expr, _x: float = 1., _y: float = 1., _z: float = 1.):\n", + "def numerical_evaluation(expr, _x: float = 1.0, _y: float = 1.0, _z: float = 1.0):\n", " return expr.evalf().subs({\"x\": _x, \"y\": _y, \"z\": _z})" ] }, @@ -98,7 +104,7 @@ "metadata": {}, "outputs": [], "source": [ - "test_tensor = torch.tensor([[1., 1., 1.]])" + "test_tensor = torch.tensor([[1.0, 1.0, 1.0]])" ] }, { @@ -119,7 +125,7 @@ } ], "source": [ - "_spherical_harmonics(2, test_tensor[:,0], test_tensor[:,1], test_tensor[:,2])" + "_spherical_harmonics(2, test_tensor[:, 0], test_tensor[:, 1], test_tensor[:, 2])" ] }, { @@ -141,13 +147,17 @@ "source": [ "x, y, z = symbols(\"x y z\")\n", "\n", + "y_1_0 = math.sqrt(3) * x\n", + "y_2_0 = math.sqrt(3) * y\n", + "y_3_0 = math.sqrt(3) * z\n", + "\n", "y_2_0 = math.sqrt(15) * x * z\n", "y_2_1 = math.sqrt(15) * x * y\n", - "y2 = y**2.\n", - "x2z2 = x**2. + z**2.\n", + "y2 = y**2.0\n", + "x2z2 = x**2.0 + z**2.0\n", "y_2_2 = math.sqrt(5) * (y2 - (1 / 2) * x2z2)\n", "y_2_3 = math.sqrt(15) * y * z\n", - "y_2_4 = (1 / 2) * math.sqrt(15) * (z**2. - x**2.)\n", + "y_2_4 = (1 / 2) * math.sqrt(15) * (z**2.0 - x**2.0)\n", "\n", "y_3_0 = (1 / 6) * math.sqrt(42) * (y_2_0 * z + y_2_4 * x)\n", "y_3_1 = math.sqrt(7) * y_2_0 * y\n", @@ -158,7 +168,11 @@ "y_3_6 = (1 / 6) * math.sqrt(42) * (y_2_4 * z - y_2_0 * x)\n", "\n", "y_4_0 = (3 / 4) * math.sqrt(2) * (y_3_0 * z + y_3_6 * x)\n", - "y_4_1 = (3 / 4) * y_3_0 * y + (3 / 8) * math.sqrt(6) * y_3_1 * z + (3 / 8) * math.sqrt(6) * y_3_5 * x\n", + "y_4_1 = (\n", + " (3 / 4) * y_3_0 * y\n", + " + (3 / 8) * math.sqrt(6) * y_3_1 * z\n", + " + (3 / 8) * math.sqrt(6) * y_3_5 * x\n", + ")\n", "y_4_2 = (\n", " -3 / 56 * math.sqrt(14) * y_3_0 * z\n", " + (3 / 14) * math.sqrt(21) * y_3_1 * y\n", @@ -172,7 +186,11 @@ " + (3 / 28) * math.sqrt(70) * y_3_3 * x\n", " + (3 / 56) * math.sqrt(42) * y_3_5 * x\n", ")\n", - "y_4_4 = -3 / 28 * math.sqrt(42) * y_3_2 * x + (3 / 7) * math.sqrt(7) * y_3_3 * y - 3 / 28 * math.sqrt(42) * y_3_4 * z\n", + "y_4_4 = (\n", + " -3 / 28 * math.sqrt(42) * y_3_2 * x\n", + " + (3 / 7) * math.sqrt(7) * y_3_3 * y\n", + " - 3 / 28 * math.sqrt(42) * y_3_4 * z\n", + ")\n", "y_4_5 = (\n", " -3 / 56 * math.sqrt(42) * y_3_1 * x\n", " + (3 / 28) * math.sqrt(70) * y_3_3 * z\n", @@ -186,11 +204,19 @@ " + (3 / 14) * math.sqrt(21) * y_3_5 * y\n", " - 3 / 56 * math.sqrt(14) * y_3_6 * z\n", ")\n", - "y_4_7 = -3 / 8 * math.sqrt(6) * y_3_1 * x + (3 / 8) * math.sqrt(6) * y_3_5 * z + (3 / 4) * y_3_6 * y\n", + "y_4_7 = (\n", + " -3 / 8 * math.sqrt(6) * y_3_1 * x\n", + " + (3 / 8) * math.sqrt(6) * y_3_5 * z\n", + " + (3 / 4) * y_3_6 * y\n", + ")\n", "y_4_8 = (3 / 4) * math.sqrt(2) * (-y_3_0 * x + y_3_6 * z)\n", "\n", "y_5_0 = (1 / 10) * math.sqrt(110) * (y_4_0 * z + y_4_8 * x)\n", - "y_5_1 = (1 / 5) * math.sqrt(11) * y_4_0 * y + (1 / 5) * math.sqrt(22) * y_4_1 * z + (1 / 5) * math.sqrt(22) * y_4_7 * x\n", + "y_5_1 = (\n", + " (1 / 5) * math.sqrt(11) * y_4_0 * y\n", + " + (1 / 5) * math.sqrt(22) * y_4_1 * z\n", + " + (1 / 5) * math.sqrt(22) * y_4_7 * x\n", + ")\n", "y_5_2 = (\n", " -1 / 30 * math.sqrt(22) * y_4_0 * z\n", " + (4 / 15) * math.sqrt(11) * y_4_1 * y\n", @@ -212,7 +238,9 @@ " + (1 / 15) * math.sqrt(33) * y_4_6 * x\n", ")\n", "y_5_5 = (\n", - " -1 / 15 * math.sqrt(110) * y_4_3 * x + (1 / 3) * math.sqrt(11) * y_4_4 * y - 1 / 15 * math.sqrt(110) * y_4_5 * z\n", + " -1 / 15 * math.sqrt(110) * y_4_3 * x\n", + " + (1 / 3) * math.sqrt(11) * y_4_4 * y\n", + " - 1 / 15 * math.sqrt(110) * y_4_5 * z\n", ")\n", "y_5_6 = (\n", " -1 / 15 * math.sqrt(33) * y_4_2 * x\n", @@ -234,12 +262,18 @@ " + (4 / 15) * math.sqrt(11) * y_4_7 * y\n", " - 1 / 30 * math.sqrt(22) * y_4_8 * z\n", ")\n", - "y_5_9 = -1 / 5 * math.sqrt(22) * y_4_1 * x + (1 / 5) * math.sqrt(22) * y_4_7 * z + (1 / 5) * math.sqrt(11) * y_4_8 * y\n", + "y_5_9 = (\n", + " -1 / 5 * math.sqrt(22) * y_4_1 * x\n", + " + (1 / 5) * math.sqrt(22) * y_4_7 * z\n", + " + (1 / 5) * math.sqrt(11) * y_4_8 * y\n", + ")\n", "y_5_10 = (1 / 10) * math.sqrt(110) * (-y_4_0 * x + y_4_8 * z)\n", "\n", "y_6_0 = (1 / 6) * math.sqrt(39) * (y_5_0 * z + y_5_10 * x)\n", "y_6_1 = (\n", - " (1 / 6) * math.sqrt(13) * y_5_0 * y + (1 / 12) * math.sqrt(130) * y_5_1 * z + (1 / 12) * math.sqrt(130) * y_5_9 * x\n", + " (1 / 6) * math.sqrt(13) * y_5_0 * y\n", + " + (1 / 12) * math.sqrt(130) * y_5_1 * z\n", + " + (1 / 12) * math.sqrt(130) * y_5_9 * x\n", ")\n", "y_6_2 = (\n", " -1 / 132 * math.sqrt(286) * y_5_0 * z\n", @@ -269,7 +303,9 @@ " + (1 / 66) * math.sqrt(715) * y_5_7 * x\n", ")\n", "y_6_6 = (\n", - " -1 / 66 * math.sqrt(2145) * y_5_4 * x + (1 / 11) * math.sqrt(143) * y_5_5 * y - 1 / 66 * math.sqrt(2145) * y_5_6 * z\n", + " -1 / 66 * math.sqrt(2145) * y_5_4 * x\n", + " + (1 / 11) * math.sqrt(143) * y_5_5 * y\n", + " - 1 / 66 * math.sqrt(2145) * y_5_6 * z\n", ")\n", "y_6_7 = (\n", " -1 / 66 * math.sqrt(715) * y_5_3 * x\n", @@ -299,12 +335,18 @@ " + (1 / 33) * math.sqrt(715) * y_5_9 * y\n", ")\n", "y_6_11 = (\n", - " -1 / 12 * math.sqrt(130) * y_5_1 * x + (1 / 6) * math.sqrt(13) * y_5_10 * y + (1 / 12) * math.sqrt(130) * y_5_9 * z\n", + " -1 / 12 * math.sqrt(130) * y_5_1 * x\n", + " + (1 / 6) * math.sqrt(13) * y_5_10 * y\n", + " + (1 / 12) * math.sqrt(130) * y_5_9 * z\n", ")\n", "y_6_12 = (1 / 6) * math.sqrt(39) * (-y_5_0 * x + y_5_10 * z)\n", "\n", "y_7_0 = (1 / 14) * math.sqrt(210) * (y_6_0 * z + y_6_12 * x)\n", - "y_7_1 = (1 / 7) * math.sqrt(15) * y_6_0 * y + (3 / 7) * math.sqrt(5) * y_6_1 * z + (3 / 7) * math.sqrt(5) * y_6_11 * x\n", + "y_7_1 = (\n", + " (1 / 7) * math.sqrt(15) * y_6_0 * y\n", + " + (3 / 7) * math.sqrt(5) * y_6_1 * z\n", + " + (3 / 7) * math.sqrt(5) * y_6_11 * x\n", + ")\n", "y_7_2 = (\n", " -1 / 182 * math.sqrt(390) * y_6_0 * z\n", " + (6 / 91) * math.sqrt(130) * y_6_1 * y\n", @@ -340,7 +382,9 @@ " + (15 / 182) * math.sqrt(26) * y_6_8 * x\n", ")\n", "y_7_7 = (\n", - " -3 / 91 * math.sqrt(455) * y_6_5 * x + (1 / 13) * math.sqrt(195) * y_6_6 * y - 3 / 91 * math.sqrt(455) * y_6_7 * z\n", + " -3 / 91 * math.sqrt(455) * y_6_5 * x\n", + " + (1 / 13) * math.sqrt(195) * y_6_6 * y\n", + " - 3 / 91 * math.sqrt(455) * y_6_7 * z\n", ")\n", "y_7_8 = (\n", " -15 / 182 * math.sqrt(26) * y_6_4 * x\n", @@ -376,12 +420,18 @@ " - 1 / 182 * math.sqrt(390) * y_6_12 * z\n", " - 3 / 91 * math.sqrt(715) * y_6_2 * x\n", ")\n", - "y_7_13 = -3 / 7 * math.sqrt(5) * y_6_1 * x + (3 / 7) * math.sqrt(5) * y_6_11 * z + (1 / 7) * math.sqrt(15) * y_6_12 * y\n", + "y_7_13 = (\n", + " -3 / 7 * math.sqrt(5) * y_6_1 * x\n", + " + (3 / 7) * math.sqrt(5) * y_6_11 * z\n", + " + (1 / 7) * math.sqrt(15) * y_6_12 * y\n", + ")\n", "y_7_14 = (1 / 14) * math.sqrt(210) * (-y_6_0 * x + y_6_12 * z)\n", "\n", "y_8_0 = (1 / 4) * math.sqrt(17) * (y_7_0 * z + y_7_14 * x)\n", "y_8_1 = (\n", - " (1 / 8) * math.sqrt(17) * y_7_0 * y + (1 / 16) * math.sqrt(238) * y_7_1 * z + (1 / 16) * math.sqrt(238) * y_7_13 * x\n", + " (1 / 8) * math.sqrt(17) * y_7_0 * y\n", + " + (1 / 16) * math.sqrt(238) * y_7_1 * z\n", + " + (1 / 16) * math.sqrt(238) * y_7_13 * x\n", ")\n", "y_8_2 = (\n", " -1 / 240 * math.sqrt(510) * y_7_0 * z\n", @@ -433,7 +483,9 @@ " + (1 / 80) * math.sqrt(1190) * y_7_9 * x\n", ")\n", "y_8_8 = (\n", - " -1 / 60 * math.sqrt(1785) * y_7_6 * x + (1 / 15) * math.sqrt(255) * y_7_7 * y - 1 / 60 * math.sqrt(1785) * y_7_8 * z\n", + " -1 / 60 * math.sqrt(1785) * y_7_6 * x\n", + " + (1 / 15) * math.sqrt(255) * y_7_7 * y\n", + " - 1 / 60 * math.sqrt(1785) * y_7_8 * z\n", ")\n", "y_8_9 = (\n", " -1 / 80 * math.sqrt(1190) * y_7_5 * x\n", @@ -485,7 +537,9 @@ " - 1 / 240 * math.sqrt(46410) * y_7_2 * x\n", ")\n", "y_8_15 = (\n", - " -1 / 16 * math.sqrt(238) * y_7_1 * x + (1 / 16) * math.sqrt(238) * y_7_13 * z + (1 / 8) * math.sqrt(17) * y_7_14 * y\n", + " -1 / 16 * math.sqrt(238) * y_7_1 * x\n", + " + (1 / 16) * math.sqrt(238) * y_7_13 * z\n", + " + (1 / 8) * math.sqrt(17) * y_7_14 * y\n", ")\n", "y_8_16 = (1 / 4) * math.sqrt(17) * (-y_7_0 * x + y_7_14 * z)\n", "\n", @@ -593,7 +647,9 @@ "\n", "y_10_0 = (1 / 10) * math.sqrt(105) * (y_9_0 * z + y_9_18 * x)\n", "y_10_1 = (\n", - " (1 / 10) * math.sqrt(21) * y_9_0 * y + (3 / 20) * math.sqrt(42) * y_9_1 * z + (3 / 20) * math.sqrt(42) * y_9_17 * x\n", + " (1 / 10) * math.sqrt(21) * y_9_0 * y\n", + " + (3 / 20) * math.sqrt(42) * y_9_1 * z\n", + " + (3 / 20) * math.sqrt(42) * y_9_17 * x\n", ")\n", "y_10_2 = (\n", " -1 / 380 * math.sqrt(798) * y_9_0 * z\n", @@ -711,7 +767,9 @@ " - 3 / 380 * math.sqrt(13566) * y_9_2 * x\n", ")\n", "y_10_19 = (\n", - " -3 / 20 * math.sqrt(42) * y_9_1 * x + (3 / 20) * math.sqrt(42) * y_9_17 * z + (1 / 10) * math.sqrt(21) * y_9_18 * y\n", + " -3 / 20 * math.sqrt(42) * y_9_1 * x\n", + " + (3 / 20) * math.sqrt(42) * y_9_17 * z\n", + " + (1 / 10) * math.sqrt(21) * y_9_18 * y\n", ")\n", "y_10_20 = (1 / 10) * math.sqrt(105) * (-y_9_0 * x + y_9_18 * z)" ] @@ -732,6 +790,7 @@ "\"\"\"\n", "nsimplify = partial(sympy.nsimplify, tolerance=PRECISION_TOL, rational=True)\n", "\n", + "\n", "def replace_floating_integers(expr):\n", " \"\"\"Dumb, but straight forward way to replace floats that should be integers.\"\"\"\n", " replace_dict = {sympy.Float(value): sympy.Integer(value) for value in range(1, 15)}\n", @@ -767,16 +826,14 @@ " combos = chain(\n", " combinations([x, y, z], 1),\n", " combinations([x, y, z], 2),\n", - " combinations([x, y, z], 3)\n", + " combinations([x, y, z], 3),\n", " )\n", " best_solution = new_expr\n", " best_num_ops = sympy.count_ops(best_solution)\n", " for combo in combos:\n", " # runs a sequence of chained collections to try and\n", " # minimize the number of operations\n", - " temp = optimization_chain(\n", - " sympy.collect(new_expr, combo)\n", - " )\n", + " temp = optimization_chain(sympy.collect(new_expr, combo))\n", " # count the number of computational operations we perform\n", " counts = sympy.count_ops(temp)\n", " # if we end up with fewer ops, go for it\n", @@ -969,7 +1026,7 @@ ], "source": [ "# second order\n", - "_spherical_harmonics(2, test_tensor[:,0], test_tensor[:,1], test_tensor[:,2])" + "_spherical_harmonics(2, test_tensor[:, 0], test_tensor[:, 1], test_tensor[:, 2])" ] }, { @@ -994,7 +1051,7 @@ ], "source": [ "# compare each of the forward terms with above\n", - "second_order_expressions[\"fwd\"][-1].subs({\"x\": 1., \"y\": 1., \"z\": 1.})" + "second_order_expressions[\"fwd\"][-1].subs({\"x\": 1.0, \"y\": 1.0, \"z\": 1.0})" ] }, { @@ -1017,7 +1074,7 @@ ], "source": [ "# third order\n", - "_spherical_harmonics(3, test_tensor[:,0], test_tensor[:,1], test_tensor[:,2])" + "_spherical_harmonics(3, test_tensor[:, 0], test_tensor[:, 1], test_tensor[:, 2])" ] }, { @@ -1041,7 +1098,7 @@ } ], "source": [ - "third_order_expressions[\"fwd\"][3].subs({\"x\": 1., \"y\": 1., \"z\": 1.})" + "third_order_expressions[\"fwd\"][3].subs({\"x\": 1.0, \"y\": 1.0, \"z\": 1.0})" ] }, { @@ -1067,7 +1124,7 @@ ], "source": [ "# fourth order\n", - "_spherical_harmonics(4, test_tensor[:,0], test_tensor[:,1], test_tensor[:,2])" + "_spherical_harmonics(4, test_tensor[:, 0], test_tensor[:, 1], test_tensor[:, 2])" ] }, { @@ -1091,7 +1148,7 @@ } ], "source": [ - "fourth_order_expressions[\"fwd\"][-1].subs({\"x\": 1., \"y\": 1., \"z\": 1.})" + "fourth_order_expressions[\"fwd\"][-1].subs({\"x\": 1.0, \"y\": 1.0, \"z\": 1.0})" ] }, { @@ -1153,13 +1210,45 @@ " num_projections = 2 * n + 1\n", " x_points = np.ones(num_projections) * n\n", " e3nn_impl, direct_counts = count_operations_per_n(n)\n", - " ax.scatter(x_points, e3nn_impl, label=\"e3nn\", facecolor=\"#d33c25\", edgecolor=\"k\", s=30., lw=0.5)\n", - " ax.scatter(x_points, direct_counts, label=\"EquiTriton\", edgecolor=\"k\", facecolor=\"#4a7cb6\", s=30., lw=0.5,)\n", + " ax.scatter(\n", + " x_points,\n", + " e3nn_impl,\n", + " label=\"e3nn\",\n", + " facecolor=\"#d33c25\",\n", + " edgecolor=\"k\",\n", + " s=30.0,\n", + " lw=0.5,\n", + " )\n", + " ax.scatter(\n", + " x_points,\n", + " direct_counts,\n", + " label=\"EquiTriton\",\n", + " edgecolor=\"k\",\n", + " facecolor=\"#4a7cb6\",\n", + " s=30.0,\n", + " lw=0.5,\n", + " )\n", "ax.set(yscale=\"log\", xlabel=\"$L_{max}$\", ylabel=\"# of arithmetic operations\")\n", "\n", "leg = [\n", - " Line2D([0], [0], marker=\"o\", color=\"w\", markerfacecolor=\"#d33c25\", markersize=15., label=\"e3nn\"),\n", - " Line2D([0], [0], marker=\"o\", color=\"w\", markerfacecolor=\"#4a7cb6\", markersize=15., label=\"EquiTriton\")\n", + " Line2D(\n", + " [0],\n", + " [0],\n", + " marker=\"o\",\n", + " color=\"w\",\n", + " markerfacecolor=\"#d33c25\",\n", + " markersize=15.0,\n", + " label=\"e3nn\",\n", + " ),\n", + " Line2D(\n", + " [0],\n", + " [0],\n", + " marker=\"o\",\n", + " color=\"w\",\n", + " markerfacecolor=\"#4a7cb6\",\n", + " markersize=15.0,\n", + " label=\"EquiTriton\",\n", + " ),\n", "]\n", "ax.legend(handles=leg, ncols=2, frameon=False)\n", "fig.tight_layout()" @@ -1221,7 +1310,18 @@ "outputs": [], "source": [ "for e, n in zip(\n", - " [second_order_expressions, third_order_expressions, fourth_order_expressions, fifth_order_expressions, sixth_order_expressions, seventh_order_expressions, eighth_order_expressions, ninth_order_expressions, tenth_order_expressions], range(2, 11)\n", + " [\n", + " second_order_expressions,\n", + " third_order_expressions,\n", + " fourth_order_expressions,\n", + " fifth_order_expressions,\n", + " sixth_order_expressions,\n", + " seventh_order_expressions,\n", + " eighth_order_expressions,\n", + " ninth_order_expressions,\n", + " tenth_order_expressions,\n", + " ],\n", + " range(2, 11),\n", "):\n", " write_expressions_to_json(e, n)" ] @@ -1334,7 +1434,17 @@ "source": [ "for order, expressions in zip(\n", " range(2, 11),\n", - " [second_order_expressions, third_order_expressions, fourth_order_expressions, fifth_order_expressions, sixth_order_expressions, seventh_order_expressions, eighth_order_expressions, ninth_order_expressions, tenth_order_expressions]\n", + " [\n", + " second_order_expressions,\n", + " third_order_expressions,\n", + " fourth_order_expressions,\n", + " fifth_order_expressions,\n", + " sixth_order_expressions,\n", + " seventh_order_expressions,\n", + " eighth_order_expressions,\n", + " ninth_order_expressions,\n", + " tenth_order_expressions,\n", + " ],\n", "):\n", " output_path = Path(f\"fwd_implementations/fwd_{order}.py\")\n", " generate_fwd_implementation(expressions[\"fwd\"], output_path)" @@ -1406,7 +1516,17 @@ "\n", "for order, expressions in zip(\n", " range(2, 11),\n", - " [second_order_expressions, third_order_expressions, fourth_order_expressions, fifth_order_expressions, sixth_order_expressions, seventh_order_expressions, eighth_order_expressions, ninth_order_expressions, tenth_order_expressions]\n", + " [\n", + " second_order_expressions,\n", + " third_order_expressions,\n", + " fourth_order_expressions,\n", + " fifth_order_expressions,\n", + " sixth_order_expressions,\n", + " seventh_order_expressions,\n", + " eighth_order_expressions,\n", + " ninth_order_expressions,\n", + " tenth_order_expressions,\n", + " ],\n", "):\n", " output_path = Path(f\"bwd_implementations/bwd_{order}.py\")\n", " generate_bwd_implementation(expressions[\"bwd\"], output_path)" @@ -1439,28 +1559,6 @@ "generate_bwd_implementation(fifth_order_expressions[\"bwd\"], None)" ] }, - { - "cell_type": "code", - "execution_count": 99, - "id": "78cd38de-bf35-40ce-8410-57a94e63141a", - "metadata": {}, - "outputs": [ - { - "ename": "AttributeError", - "evalue": "'Pow' object has no attribute 'evaluate'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[99], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43m(\u001b[49m\u001b[43mz\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m6\u001b[39;49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mevaluate\u001b[49m(full\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m)\n", - "\u001b[0;31mAttributeError\u001b[0m: 'Pow' object has no attribute 'evaluate'" - ] - } - ], - "source": [ - "(z**6).evaluate(f)" - ] - }, { "cell_type": "code", "execution_count": null, From 4fda13ebbea570dac5a138b0abdb5138c8b182ce Mon Sep 17 00:00:00 2001 From: Michael Galkin Date: Tue, 27 Aug 2024 13:59:35 -0700 Subject: [PATCH 043/116] baseline notebook: equivariance test? --- notebooks/Baseline model development.ipynb | 459 ++++++--------------- 1 file changed, 129 insertions(+), 330 deletions(-) diff --git a/notebooks/Baseline model development.ipynb b/notebooks/Baseline model development.ipynb index f14a811..ae654ea 100644 --- a/notebooks/Baseline model development.ipynb +++ b/notebooks/Baseline model development.ipynb @@ -38,7 +38,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 3, "id": "79f96b13-929a-4b2e-a4d9-991194fd98ba", "metadata": {}, "outputs": [], @@ -370,7 +370,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 4, "id": "4e568b72-3990-4652-8a32-db375c65a81b", "metadata": {}, "outputs": [], @@ -392,7 +392,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 5, "id": "8671f0e8-0da8-4250-9d02-fb30ecb977fe", "metadata": {}, "outputs": [], @@ -402,7 +402,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 6, "id": "f4971d48-3574-44d0-b2fb-27306bc3aeac", "metadata": {}, "outputs": [], @@ -415,7 +415,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 7, "id": "55f14f94-51ca-4e5c-ad63-ca830404eaf9", "metadata": {}, "outputs": [], @@ -425,7 +425,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 8, "id": "67f6e836-f4e0-48cf-981d-4419dda0159b", "metadata": {}, "outputs": [], @@ -436,7 +436,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 9, "id": "46778ea6-92f2-4d7a-9bd8-aa58013cd1c6", "metadata": {}, "outputs": [ @@ -446,7 +446,7 @@ "torch.Size([2, 12])" ] }, - "execution_count": 22, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -457,7 +457,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 10, "id": "0c82e80f-c4bd-43fe-8846-dab203757992", "metadata": {}, "outputs": [ @@ -465,7 +465,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + "/home/mgalkin/miniconda3/envs/equitriton/lib/python3.10/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", " warnings.warn(\n" ] } @@ -478,7 +478,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 11, "id": "a0913f1e-b000-4a3b-b723-84a92d16425d", "metadata": {}, "outputs": [ @@ -486,7 +486,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + "/home/mgalkin/miniconda3/envs/equitriton/lib/python3.10/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", " warnings.warn(\n" ] } @@ -499,7 +499,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 12, "id": "e893b896-f7b9-4b62-95a1-fe7fb45cf2ed", "metadata": {}, "outputs": [ @@ -514,7 +514,7 @@ ")" ] }, - "execution_count": 52, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -525,7 +525,7 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 13, "id": "f2785a28-4d2b-4cf0-972d-60df70595580", "metadata": {}, "outputs": [], @@ -535,24 +535,24 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 14, "id": "4b80d729-b5e4-4cb5-802d-c27f2af32a70", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "tensor([[ 0.1235, 0.0030, 0.0861, ..., 0.3042, 0.1641, -0.0114],\n", + "tensor([[ 0.0634, -0.7884, 0.2284, ..., 0.1949, 0.4684, -0.0612],\n", " [ 0.0000, 0.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000],\n", - " [ 0.0167, -0.0017, 0.0098, ..., -0.0773, -0.0233, 0.0344],\n", + " [-0.0337, 0.6920, -0.4316, ..., -0.0851, -0.1326, 0.0159],\n", " ...,\n", + " [-1.5390, -0.3273, -0.8549, ..., -0.2264, 0.3779, 0.7378],\n", " [ 0.0000, 0.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000],\n", - " [-0.0578, 0.0226, -0.0362, ..., -0.0003, 0.0016, 0.0056],\n", - " [ 0.0000, 0.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000]],\n", + " [ nan, nan, nan, ..., nan, nan, nan]],\n", " device='cuda:0', grad_fn=)" ] }, - "execution_count": 67, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -563,7 +563,97 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 35, + "id": "8a38dc82", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/mgalkin/miniconda3/envs/equitriton/lib/python3.10/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + " warnings.warn(\n", + "/home/mgalkin/miniconda3/envs/equitriton/lib/python3.10/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + " warnings.warn(\n", + "/home/mgalkin/miniconda3/envs/equitriton/lib/python3.10/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + " warnings.warn(\n", + "/home/mgalkin/miniconda3/envs/equitriton/lib/python3.10/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + " warnings.warn(\n", + "/home/mgalkin/miniconda3/envs/equitriton/lib/python3.10/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + " warnings.warn(\n", + "/home/mgalkin/miniconda3/envs/equitriton/lib/python3.10/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "text/plain": [ + "((tensor([[0., 0., 0., 0.]], device='cuda:0'),\n", + " tensor([[0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.]], device='cuda:0')),\n", + " (tensor([[0., 0., 0., 0.]], device='cuda:0'),\n", + " tensor([[0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.]], device='cuda:0')))" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# generate a random rotation matrix (bs, 3, 3)\n", + "from scipy.spatial.transform import Rotation\n", + "random_rot = torch.tensor(Rotation.random(coords.size(0)).as_matrix(), dtype=torch.float32).to(coords.device)\n", + "\n", + "# random translation (bs, 3)\n", + "random_t = torch.rand_like(coords)\n", + "\n", + "# rotate coords: (bs, 3, 3) x (bs, 3, 1) -> (bs, 3)\n", + "rotated_coords = (random_rot @ coords.unsqueeze(-1)).squeeze(-1)\n", + "\n", + "#o_transl = layer(atom_z, rotated_coords + random_t, edge_index)\n", + "og_graph = PyGGraph(pos=coords, z=atomic_numbers, edge_index=edge_index).to('cuda')\n", + "rot_graph = PyGGraph(pos=rotated_coords + random_t, z=atomic_numbers, edge_index=edge_index).to('cuda')\n", + "\n", + "model = EquiTritonModel(32, 4, 4, [2,3,4,5], 32, 32, 1.0, 17**0.5).to('cuda')\n", + "o1 = model(og_graph)\n", + "o2 = model(rot_graph)\n", + "\n", + "o1, o2" + ] + }, + { + "cell_type": "code", + "execution_count": null, "id": "dae3c6e7-8d8f-49bd-9239-8b4b5c6af499", "metadata": {}, "outputs": [], @@ -573,17 +663,17 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 15, "id": "a322cad7-60c8-4710-9257-2e3f0d4405be", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "torch.Size([16, 1184])" + "torch.Size([16, 1024])" ] }, - "execution_count": 38, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -594,28 +684,10 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": null, "id": "599ae7e0-5a54-429d-af9b-749de7ce2434", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "tensor([[ 0.3229, -0.0011, 0.2210, ..., 0.3184, 0.1664, -0.0134],\n", - " [ 0.0000, 0.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000],\n", - " [-0.7950, 0.0826, -0.4642, ..., -0.1652, -0.0498, 0.0736],\n", - " ...,\n", - " [ 0.0000, 0.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000],\n", - " [ 0.1217, -0.0898, -0.0128, ..., -0.0215, 0.0755, -0.0243],\n", - " [ 0.0000, 0.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000]],\n", - " device='cuda:0', grad_fn=)" - ] - }, - "execution_count": 39, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "o + p" ] @@ -630,7 +702,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "id": "8e448746-66f8-484f-8971-1c89c8f75135", "metadata": {}, "outputs": [], @@ -737,7 +809,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": null, "id": "6affd332-fe3a-49dd-b0c2-e20fcb974a04", "metadata": {}, "outputs": [], @@ -866,23 +938,10 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": null, "id": "2d7ce968-f33c-46dd-88e3-8ad47480a9e7", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch_geometric/data/dataset.py:238: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n", - " if osp.exists(f) and torch.load(f) != _repr(self.pre_transform):\n", - "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch_geometric/data/dataset.py:246: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n", - " if osp.exists(f) and torch.load(f) != _repr(self.pre_filter):\n", - "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch_geometric/io/fs.py:215: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n", - " return torch.load(f, map_location)\n" - ] - } - ], + "outputs": [], "source": [ "dm = LightningQM9(\"./qm9_data/\", batch_size=64)\n", "dm.setup(\"fit\")\n", @@ -892,7 +951,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": null, "id": "9c89a710-1e68-485c-9325-0b06c7b52b75", "metadata": {}, "outputs": [], @@ -904,29 +963,10 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": null, "id": "ef3c9902-3673-4c88-a98e-1a4553a5990f", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", - " warnings.warn(\n", - "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", - " warnings.warn(\n", - "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", - " warnings.warn(\n", - "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", - " warnings.warn(\n", - "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", - " warnings.warn(\n", - "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", - " warnings.warn(\n" - ] - } - ], + "outputs": [], "source": [ "lit_mod = EquiTritonLitModule(\n", " EquiTritonModel,\n", @@ -948,261 +988,20 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": null, "id": "1104fe2c-ada6-448b-9e3d-cab1bc491c95", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "GPU available: True (cuda), used: True\n", - "TPU available: False, using: 0 TPU cores\n", - "IPU available: False, using: 0 IPUs\n", - "HPU available: False, using: 0 HPUs\n" - ] - } - ], + "outputs": [], "source": [ "trainer = pl.Trainer(max_epochs=30, accelerator=\"gpu\")" ] }, { "cell_type": "code", - "execution_count": 42, + "execution_count": null, "id": "e30a0671-efa3-403b-9d8b-cc2cb63bff20", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]\n", - "\n", - " | Name | Type | Params\n", - "------------------------------------------------\n", - "0 | model | EquiTritonModel | 4.8 M \n", - "1 | loss | MSELoss | 0 \n", - "2 | output_head | Linear | 49 \n", - "------------------------------------------------\n", - "4.8 M Trainable params\n", - "0 Non-trainable params\n", - "4.8 M Total params\n", - "19.300 Total estimated model params size (MB)\n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Sanity Checking: | …" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/pytorch_lightning/trainer/connectors/data_connector.py:441: The 'val_dataloader' does not have many workers which may be a bottleneck. Consider increasing the value of the `num_workers` argument` to `num_workers=27` in the `DataLoader` to improve performance.\n", - "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/pytorch_lightning/trainer/connectors/data_connector.py:441: The 'train_dataloader' does not have many workers which may be a bottleneck. Consider increasing the value of the `num_workers` argument` to `num_workers=27` in the `DataLoader` to improve performance.\n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "d990cd3f7cca46fe8dac82edee407d90", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Training: | …" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Validation: | …" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Validation: | …" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Validation: | …" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Validation: | …" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Validation: | …" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Validation: | …" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Validation: | …" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Validation: | …" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Validation: | …" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Validation: | …" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Validation: | …" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Validation: | …" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/pytorch_lightning/trainer/call.py:54: Detected KeyboardInterrupt, attempting graceful shutdown...\n" - ] - } - ], + "outputs": [], "source": [ "trainer.fit(lit_mod, datamodule=dm)" ] @@ -1232,7 +1031,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.10.14" } }, "nbformat": 4, From 2e7b3d241a1a32de26ac221f82fb607580f6e8f5 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Wed, 28 Aug 2024 10:43:25 -0700 Subject: [PATCH 044/116] notebook: added e3nn equivariance checks --- notebooks/Baseline model development.ipynb | 425 ++++++++++++++++----- 1 file changed, 336 insertions(+), 89 deletions(-) diff --git a/notebooks/Baseline model development.ipynb b/notebooks/Baseline model development.ipynb index ae654ea..9823216 100644 --- a/notebooks/Baseline model development.ipynb +++ b/notebooks/Baseline model development.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 169, "id": "fd832b52-7df1-4d42-a2ea-a7139df2196b", "metadata": {}, "outputs": [], @@ -20,7 +20,9 @@ "from torch_geometric.data import Data as PyGGraph\n", "from torch_geometric.datasets import QM9\n", "from torch_geometric.loader import DataLoader\n", + "from torch_cluster import radius_graph\n", "import pytorch_lightning as pl\n", + "from matplotlib import pyplot as plt\n", "\n", "from equitriton.sph_harm.direct import triton_spherical_harmonic\n", "from equitriton.utils import spherical_harmonics_irreps" @@ -28,7 +30,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 5, "id": "a0307714-1036-4110-8b53-0c50062ee913", "metadata": {}, "outputs": [], @@ -38,7 +40,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 186, "id": "79f96b13-929a-4b2e-a4d9-991194fd98ba", "metadata": {}, "outputs": [], @@ -107,6 +109,7 @@ " l_values: list[int],\n", " normalize: bool = True,\n", " normalization: Literal[\"norm\", \"integral\", \"component\"] = \"integral\",\n", + " use_e3nn: bool = False,\n", " ):\n", " \"\"\"\n", " Projects cartesian positions onto spherical harmonic functions.\n", @@ -116,10 +119,16 @@ " self.irreps = spherical_harmonics_irreps(self.l_values, num_feat=1)\n", " self.normalize = normalize\n", " self.normalization = normalization\n", + " self.use_e3nn = use_e3nn\n", "\n", " def forward(self, coords: torch.Tensor) -> torch.Tensor:\n", - " outputs = [triton_spherical_harmonic(l, coords) for l in self.l_values]\n", - " return torch.cat(outputs, dim=-1)\n", + " if not self.use_e3nn:\n", + " outputs = [triton_spherical_harmonic(l, coords) for l in self.l_values]\n", + " return torch.cat(outputs, dim=-1)\n", + " else:\n", + " return o3.spherical_harmonics(\n", + " self.irreps, coords, self.normalize, self.normalization\n", + " )\n", "\n", "\n", "class InteractionBlock(nn.Module):\n", @@ -175,6 +184,7 @@ " >>> block.sph_irreps\n", " ['1x0e', '2x0e']\n", " \"\"\"\n", + " sph_harm_kwargs.setdefault(\"use_e3nn\", False)\n", "\n", " super().__init__()\n", " # this is effectively the average number of neighbors in other models\n", @@ -309,6 +319,8 @@ "\n", " Note: This class uses PyTorch Geometric's Graph data structure and assumes that the input graph has already been processed using a suitable preprocessing step.\n", " \"\"\"\n", + " sph_harm_kwargs.setdefault(\"use_e3nn\", False)\n", + "\n", " super().__init__()\n", " self.atomic_embedding = AtomEmbedding(num_atoms, initial_atom_dim)\n", " self.initial_layer = InteractionBlock(\n", @@ -341,6 +353,23 @@ " self.skip_connections = skip_connections\n", " self.output_dim = output_dim\n", "\n", + " def visualize(self, **kwargs):\n", + " num_plots = len(self.conv_layers) + 1\n", + " fig, axarray = plt.subplots(num_plots, 1, figsize=(3, 12))\n", + " # make indexing easier\n", + " axarray = axarray.flatten()\n", + "\n", + " self.initial_layer.tensor_product.visualize(ax=axarray[0], **kwargs)\n", + " axarray[0].set_title(\"Input layer\", loc=\"right\")\n", + " index = 1\n", + " for layer_name, layer in self.conv_layers.items():\n", + " ax = axarray[index]\n", + " layer.tensor_product.visualize(ax=ax, **kwargs)\n", + " ax.set_title(layer_name, loc=\"right\")\n", + " index += 1\n", + " fig.tight_layout()\n", + " return fig, axarray\n", + "\n", " def forward(self, graph: PyGGraph) -> tuple[torch.Tensor, torch.Tensor]:\n", " # determine if the graph is batched or not\n", " is_batched = hasattr(graph, \"ptr\")\n", @@ -370,7 +399,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 144, "id": "4e568b72-3990-4652-8a32-db375c65a81b", "metadata": {}, "outputs": [], @@ -378,12 +407,16 @@ "def make_fake_graph(\n", " num_nodes: int,\n", " num_edges: int,\n", + " max_radius: float = 1.5,\n", " coord_scale: float = 1.0,\n", " max_atomic_number: int = 100,\n", " device=\"cuda\",\n", "):\n", " coords = torch.rand(num_nodes, 3, device=device) * coord_scale\n", - " edge_index = torch.randint(0, high=num_nodes, size=(2, num_edges), device=device)\n", + " edge_src, edge_dst = radius_graph(\n", + " coords, max_radius, max_num_neighbors=num_nodes - 1\n", + " )\n", + " edge_index = torch.vstack([edge_src, edge_dst]).to(device)\n", " atomic_numbers = torch.randint(\n", " 0, max_atomic_number, size=(num_nodes,), device=device\n", " )\n", @@ -392,7 +425,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 145, "id": "8671f0e8-0da8-4250-9d02-fb30ecb977fe", "metadata": {}, "outputs": [], @@ -402,7 +435,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 146, "id": "f4971d48-3574-44d0-b2fb-27306bc3aeac", "metadata": {}, "outputs": [], @@ -415,7 +448,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 147, "id": "55f14f94-51ca-4e5c-ad63-ca830404eaf9", "metadata": {}, "outputs": [], @@ -425,7 +458,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 148, "id": "67f6e836-f4e0-48cf-981d-4419dda0159b", "metadata": {}, "outputs": [], @@ -436,17 +469,17 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 149, "id": "46778ea6-92f2-4d7a-9bd8-aa58013cd1c6", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "torch.Size([2, 12])" + "torch.Size([2, 240])" ] }, - "execution_count": 9, + "execution_count": 149, "metadata": {}, "output_type": "execute_result" } @@ -457,7 +490,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 151, "id": "0c82e80f-c4bd-43fe-8846-dab203757992", "metadata": {}, "outputs": [ @@ -465,20 +498,30 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/mgalkin/miniconda3/envs/equitriton/lib/python3.10/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", " warnings.warn(\n" ] } ], "source": [ "layer = InteractionBlock(\n", - " 64, [2, 3, 4, 5], 10, 32, radius_cutoff=6.0, degree_norm=17**0.5\n", + " 64,\n", + " [\n", + " 0,\n", + " 1,\n", + " 2,\n", + " ],\n", + " 10,\n", + " 32,\n", + " radius_cutoff=6.0,\n", + " degree_norm=17**0.5,\n", + " sph_harm_kwargs={\"use_e3nn\": True},\n", ").to(\"cuda\")" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 132, "id": "a0913f1e-b000-4a3b-b723-84a92d16425d", "metadata": {}, "outputs": [ @@ -486,20 +529,29 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/mgalkin/miniconda3/envs/equitriton/lib/python3.10/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", " warnings.warn(\n" ] } ], "source": [ "next_layer = InteractionBlock(\n", - " layer.output_irreps, [2, 3, 4, 5], 10, 32, radius_cutoff=6.0, degree_norm=17**0.5\n", + " layer.output_irreps,\n", + " [\n", + " 0,\n", + " 1,\n", + " 2,\n", + " ],\n", + " 10,\n", + " 32,\n", + " radius_cutoff=6.0,\n", + " degree_norm=17**0.5,\n", ").to(\"cuda\")" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 152, "id": "e893b896-f7b9-4b62-95a1-fe7fb45cf2ed", "metadata": {}, "outputs": [ @@ -507,14 +559,14 @@ "data": { "text/plain": [ "InteractionBlock(\n", - " (tensor_product): FullyConnectedTensorProduct(64x0e x 1x2e+1x3o+1x4e+1x5o -> 32x2e+32x3o+32x4e+32x5o | 8192 paths | 8192 weights)\n", + " (tensor_product): FullyConnectedTensorProduct(64x0e x 1x0e+1x1o+1x2e -> 32x0e+32x1o+32x2e | 6144 paths | 6144 weights)\n", " (edge_basis): EdgeEmbedding()\n", " (spherical_harmonics): SphericalHarmonicEmbedding()\n", - " (fc): FullyConnectedNet[10, 32, 8192]\n", + " (fc): FullyConnectedNet[10, 32, 6144]\n", ")" ] }, - "execution_count": 12, + "execution_count": 152, "metadata": {}, "output_type": "execute_result" } @@ -525,7 +577,38 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 153, + "id": "34c3578d-0017-4350-99d2-373ad2e542cb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(
, )" + ] + }, + "execution_count": 153, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "layer.tensor_product.visualize()" + ] + }, + { + "cell_type": "code", + "execution_count": 154, "id": "f2785a28-4d2b-4cf0-972d-60df70595580", "metadata": {}, "outputs": [], @@ -535,24 +618,24 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 155, "id": "4b80d729-b5e4-4cb5-802d-c27f2af32a70", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "tensor([[ 0.0634, -0.7884, 0.2284, ..., 0.1949, 0.4684, -0.0612],\n", - " [ 0.0000, 0.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000],\n", - " [-0.0337, 0.6920, -0.4316, ..., -0.0851, -0.1326, 0.0159],\n", + "tensor([[ 0.0590, -1.6986, -0.3744, ..., -2.5936, -2.0722, 1.8019],\n", + " [-1.0968, -2.7137, -2.4289, ..., 1.1375, 0.9818, 0.9651],\n", + " [-0.4404, -2.5330, -1.6619, ..., 0.1385, 2.5301, 0.2258],\n", " ...,\n", - " [-1.5390, -0.3273, -0.8549, ..., -0.2264, 0.3779, 0.7378],\n", - " [ 0.0000, 0.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000],\n", - " [ nan, nan, nan, ..., nan, nan, nan]],\n", + " [-0.1863, -0.3904, 0.2787, ..., 0.0165, -0.0322, 0.7170],\n", + " [ 0.5232, -1.4793, -0.0937, ..., -1.7656, 0.8989, 0.3833],\n", + " [-0.1590, -1.6844, 0.0832, ..., -1.7279, 0.9028, -0.9102]],\n", " device='cuda:0', grad_fn=)" ] }, - "execution_count": 14, + "execution_count": 155, "metadata": {}, "output_type": "execute_result" } @@ -561,9 +644,117 @@ "o" ] }, + { + "cell_type": "markdown", + "id": "35dd937d-2581-4d09-954b-ac3ace509c33", + "metadata": {}, + "source": [ + "## Equivariance check\n", + "\n", + "Uses `e3nn` tooling to generate the random rotation matrix, and output as a function of rotation permutation: rotating the coordinates before passing into the layer, and rotating the transformed embeddings with the same rotation matrix." + ] + }, + { + "cell_type": "code", + "execution_count": 192, + "id": "c4ff86c7-e8e5-4560-83b9-fc1885fd8d13", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "# [0, 1, 2] are necessary at the minimum, but all orders should work\n", + "layer = InteractionBlock(\n", + " 64,\n", + " [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],\n", + " 10,\n", + " 32,\n", + " radius_cutoff=6.0,\n", + " degree_norm=17**0.5,\n", + " sph_harm_kwargs={\"use_e3nn\": True},\n", + ").to(\"cuda\")" + ] + }, + { + "cell_type": "code", + "execution_count": 193, + "id": "0a3a0a02-8037-4784-9771-3abf8dad7209", + "metadata": {}, + "outputs": [], + "source": [ + "rot_matrix = o3.rand_matrix()\n", + "\n", + "# dims_in doesn't actually do anything in the case where the features are scalar\n", + "dims_in = o3.Irreps(layer.atomic_irreps).D_from_matrix(rot_matrix).to(\"cuda\")\n", + "# but dims_out actually does something, since the output features/embeddings need\n", + "# to be rotated based on the same rotation matrix\n", + "dims_out = layer.output_irreps.D_from_matrix(rot_matrix).to(\"cuda\")\n", + "\n", + "# rotate coordinates before passing into the layer\n", + "rot_before = layer(atom_z @ dims_in.T, coords @ rot_matrix.T.to(\"cuda\"), edge_index)\n", + "# rotate layer output by the same rotation matrix\n", + "rot_after = layer(atom_z, coords, edge_index) @ dims_out.T\n", + "\n", + "assert torch.allclose(rot_before, rot_after, rtol=1e-7, atol=1e-4)" + ] + }, + { + "cell_type": "markdown", + "id": "bc060469-723a-4ece-9716-1b1fbc936806", + "metadata": {}, + "source": [ + "### Rotation + translation\n", + "\n", + "If all atoms are shifted by a vector, the Bessel embedding should also be the same as it works solely on interatom distances.\n", + "\n", + "The output shouldn't need to be shifted." + ] + }, + { + "cell_type": "code", + "execution_count": 189, + "id": "fea72969-5f5a-410e-9a28-75346701498b", + "metadata": {}, + "outputs": [], + "source": [ + "rot_matrix = o3.rand_matrix()\n", + "# shift all coordinates by the same amount in space\n", + "trans_matrix = torch.randn(size=(1, 3), device=\"cuda\")\n", + "\n", + "# dims_in doesn't actually do anything in the case where the features are scalar\n", + "dims_in = o3.Irreps(layer.atomic_irreps).D_from_matrix(rot_matrix).to(\"cuda\")\n", + "dims_out = layer.output_irreps.D_from_matrix(rot_matrix).to(\"cuda\")\n", + "\n", + "# rotate and translate coordinates before passing into the layer\n", + "rot_before = layer(\n", + " atom_z @ dims_in.T, coords @ rot_matrix.T.to(\"cuda\") + trans_matrix, edge_index\n", + ")\n", + "# rotate layer output by the same rotation matrix\n", + "rot_after = layer(atom_z, coords, edge_index) @ dims_out.T\n", + "\n", + "assert torch.allclose(rot_before, rot_after, rtol=1e-7, atol=1e-4)" + ] + }, + { + "cell_type": "markdown", + "id": "061c7307-5b1c-4b31-b4b7-6bde7158c49e", + "metadata": {}, + "source": [ + "### Mike equivariance check\n", + "\n", + "Need to figure out what is different between these two..." + ] + }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 183, "id": "8a38dc82", "metadata": {}, "outputs": [ @@ -571,60 +762,44 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/mgalkin/miniconda3/envs/equitriton/lib/python3.10/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + " warnings.warn(\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", " warnings.warn(\n", - "/home/mgalkin/miniconda3/envs/equitriton/lib/python3.10/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", " warnings.warn(\n", - "/home/mgalkin/miniconda3/envs/equitriton/lib/python3.10/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", " warnings.warn(\n", - "/home/mgalkin/miniconda3/envs/equitriton/lib/python3.10/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", " warnings.warn(\n", - "/home/mgalkin/miniconda3/envs/equitriton/lib/python3.10/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", " warnings.warn(\n", - "/home/mgalkin/miniconda3/envs/equitriton/lib/python3.10/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", " warnings.warn(\n" ] }, { "data": { "text/plain": [ - "((tensor([[0., 0., 0., 0.]], device='cuda:0'),\n", - " tensor([[0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.]], device='cuda:0')),\n", - " (tensor([[0., 0., 0., 0.]], device='cuda:0'),\n", - " tensor([[0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.],\n", - " [0., 0., 0., 0.]], device='cuda:0')))" + "(tensor([[ 0.0590, -1.6986, -0.3744, ..., -2.5936, -2.0722, 1.8019],\n", + " [-1.0968, -2.7137, -2.4289, ..., 1.1375, 0.9818, 0.9651],\n", + " [-0.4404, -2.5330, -1.6619, ..., 0.1385, 2.5301, 0.2258],\n", + " ...,\n", + " [-0.1863, -0.3904, 0.2787, ..., 0.0165, -0.0322, 0.7170],\n", + " [ 0.5232, -1.4793, -0.0937, ..., -1.7656, 0.8989, 0.3833],\n", + " [-0.1590, -1.6844, 0.0832, ..., -1.7279, 0.9028, -0.9102]],\n", + " device='cuda:0', grad_fn=),\n", + " tensor([[ 0.0590, -1.6986, -0.3744, ..., 2.6008, 0.3975, -1.8447],\n", + " [-1.0968, -2.7137, -2.4289, ..., -0.2491, -1.0484, 1.2295],\n", + " [-0.4404, -2.5330, -1.6619, ..., -2.0959, -2.8912, -0.1732],\n", + " ...,\n", + " [-0.1863, -0.3904, 0.2787, ..., 0.5650, -0.1882, 0.3297],\n", + " [ 0.5232, -1.4793, -0.0937, ..., 1.1183, -1.3011, -0.5838],\n", + " [-0.1590, -1.6844, 0.0832, ..., 0.1020, -0.7125, -0.9762]],\n", + " device='cuda:0', grad_fn=))" ] }, - "execution_count": 35, + "execution_count": 183, "metadata": {}, "output_type": "execute_result" } @@ -632,25 +807,84 @@ "source": [ "# generate a random rotation matrix (bs, 3, 3)\n", "from scipy.spatial.transform import Rotation\n", - "random_rot = torch.tensor(Rotation.random(coords.size(0)).as_matrix(), dtype=torch.float32).to(coords.device)\n", + "\n", + "random_rot = torch.tensor(\n", + " Rotation.random(coords.size(0)).as_matrix(), dtype=torch.float32\n", + ").to(coords.device)\n", "\n", "# random translation (bs, 3)\n", - "random_t = torch.rand_like(coords)\n", + "random_t = torch.rand_like(\n", + " coords\n", + ") # I think it should be a group action, not individual atoms\n", "\n", "# rotate coords: (bs, 3, 3) x (bs, 3, 1) -> (bs, 3)\n", "rotated_coords = (random_rot @ coords.unsqueeze(-1)).squeeze(-1)\n", "\n", - "#o_transl = layer(atom_z, rotated_coords + random_t, edge_index)\n", - "og_graph = PyGGraph(pos=coords, z=atomic_numbers, edge_index=edge_index).to('cuda')\n", - "rot_graph = PyGGraph(pos=rotated_coords + random_t, z=atomic_numbers, edge_index=edge_index).to('cuda')\n", + "# o_transl = layer(atom_z, rotated_coords + random_t, edge_index)\n", + "og_graph = PyGGraph(pos=coords, z=atomic_numbers, edge_index=edge_index).to(\"cuda\")\n", + "rot_graph = PyGGraph(\n", + " pos=coords @ rot_matrix.T.to(\"cuda\"), z=atomic_numbers, edge_index=edge_index\n", + ").to(\"cuda\")\n", "\n", - "model = EquiTritonModel(32, 4, 4, [2,3,4,5], 32, 32, 1.0, 17**0.5).to('cuda')\n", - "o1 = model(og_graph)\n", - "o2 = model(rot_graph)\n", + "model = EquiTritonModel(32, 4, 4, [0, 1, 2], 32, 32, 1.0, 17**0.5).to(\"cuda\")\n", + "# o1 = model(og_graph)\n", + "# o2 = model(rot_graph)\n", + "atom_z = atom_embedder(atomic_numbers)\n", + "o1 = layer(atom_z, og_graph.pos, edge_index=og_graph.edge_index)\n", + "o2 = layer(atom_z, rot_graph.pos, edge_index=rot_graph.edge_index)\n", "\n", "o1, o2" ] }, + { + "cell_type": "code", + "execution_count": 184, + "id": "4482c983-55e6-4419-bf3a-04705010adae", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(
,\n", + " array([,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ], dtype=object))" + ] + }, + "execution_count": 184, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "model.visualize()" + ] + }, + { + "cell_type": "code", + "execution_count": 165, + "id": "07f5254f-825e-47a8-8bf5-82c8ec08faa1", + "metadata": {}, + "outputs": [], + "source": [ + "tests = []\n", + "for axis in range(o1.size(-1)):\n", + " tests.append(torch.allclose(o1[:, axis], o2[:, axis], rtol=1e-7, atol=1e-4))" + ] + }, { "cell_type": "code", "execution_count": null, @@ -702,7 +936,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 30, "id": "8e448746-66f8-484f-8971-1c89c8f75135", "metadata": {}, "outputs": [], @@ -809,7 +1043,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 31, "id": "6affd332-fe3a-49dd-b0c2-e20fcb974a04", "metadata": {}, "outputs": [], @@ -938,10 +1172,23 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 32, "id": "2d7ce968-f33c-46dd-88e3-8ad47480a9e7", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch_geometric/data/dataset.py:238: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n", + " if osp.exists(f) and torch.load(f) != _repr(self.pre_transform):\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch_geometric/data/dataset.py:246: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n", + " if osp.exists(f) and torch.load(f) != _repr(self.pre_filter):\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch_geometric/io/fs.py:215: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n", + " return torch.load(f, map_location)\n" + ] + } + ], "source": [ "dm = LightningQM9(\"./qm9_data/\", batch_size=64)\n", "dm.setup(\"fit\")\n", @@ -951,7 +1198,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 33, "id": "9c89a710-1e68-485c-9325-0b06c7b52b75", "metadata": {}, "outputs": [], @@ -1031,7 +1278,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.11.9" } }, "nbformat": 4, From 975d7826d6c4f81b2c0b9c7a8630eefe8e134a5e Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Wed, 28 Aug 2024 12:22:16 -0700 Subject: [PATCH 045/116] feat: transcribed block implementations from notebook --- src/equitriton/model/blocks.py | 427 +++++++++++++++++++++++++++++++++ 1 file changed, 427 insertions(+) create mode 100644 src/equitriton/model/blocks.py diff --git a/src/equitriton/model/blocks.py b/src/equitriton/model/blocks.py new file mode 100644 index 0000000..323150a --- /dev/null +++ b/src/equitriton/model/blocks.py @@ -0,0 +1,427 @@ +from __future__ import annotations + +from typing import Literal, Any, Callable + +import torch +from torch import nn +import e3nn +from e3nn import o3 +from torch_scatter import scatter +from matplotlib import pyplot as plt +from torch_geometric.data import Data as PyGGraph + +from equitriton.utils import spherical_harmonics_irreps +from equitriton.sph_harm.direct import triton_spherical_harmonic + + +class AtomEmbedding(nn.Module): + def __init__(self, num_atoms: int, atom_dim: int): + super().__init__() + self.embedding = nn.Embedding(num_atoms, atom_dim, padding_idx=0) + + def forward(self, atomic_numbers: torch.LongTensor) -> torch.Tensor: + return self.embedding(atomic_numbers) + + +class EdgeEmbedding(nn.Module): + def __init__(self, num_basis: int, radius_cutoff: float = 6.0, **kwargs): + """ + This module embeds edges in a graph with an EdgeEmbedding object. + + Parameters + ---------- + num_basis : int, optional + The number of basis functions. Defaults to 1. + radius_cutoff : float, optional + The maximum radius up to which basis functions are defined. Defaults to 6.0. + + Optional kwargs + --------------- + basis : str, optional + The type of basis function to use. Defaults to 'bessel'. + start : float, optional + The starting point in the distance grid used in the radial basis. + cutoff : bool, optional + Whether or not to apply a cutoff to the basis functions. + + Returns + ------- + torch.Tensor + A tensor representing the embedding of edges with shape (num_edges, num_basis). + + Examples + -------- + >>> # Define an instance of EdgeEmbedding with 4 basis functions and a radius cutoff of 10. + >>> embedder = EdgeEmbedding(num_basis=4, radius_cutoff=10.0) + """ + super().__init__() + kwargs.setdefault("basis", "bessel") + kwargs.setdefault("start", 0.0) + kwargs.setdefault("cutoff", True) + self.num_basis = num_basis + self.radius_cutoff = radius_cutoff + self.basis_kwargs = kwargs + + def forward(self, distances: torch.Tensor) -> torch.Tensor: + basis_funcs = e3nn.math.soft_one_hot_linspace( + distances, + number=self.num_basis, + end=self.radius_cutoff, + **self.basis_kwargs, + ) + return basis_funcs * self.num_basis**0.5 + + +class SphericalHarmonicEmbedding(nn.Module): + def __init__( + self, + l_values: list[int], + normalize: bool = True, + normalization: Literal["norm", "integral", "component"] = "integral", + use_e3nn: bool = False, + ): + """ + Projects cartesian coordinates onto a specified spherical harmonic basis. + + Arguments mainly implement an equivalent interface to ``e3nn``, + up to just directly using the ``e3nn`` spherical harmonics + implementation. + + Parameters + ---------- + l_values : list[int] + List of l values of spherical harmonics to use as a basis. + normalize : bool, optional + Whether to normalize coordinates before passing into the + embedding step. + normalization : Literal["norm", "integral", "component"], optional + Normalization scheme to use for the embeddings. By default + uses ``integral``, which is the only method implemented for + the Triton kernels. + use_e3nn : bool, optional + Whether to directly use ``e3nn`` spherical harmonics, + by default False. + """ + super().__init__() + self.l_values = list(sorted(l_values)) + self.irreps = spherical_harmonics_irreps(self.l_values, num_feat=1) + self.normalize = normalize + self.normalization = normalization + self.use_e3nn = use_e3nn + + def forward(self, coords: torch.Tensor) -> torch.Tensor: + if not self.use_e3nn: + if self.normalize: + coords = torch.nn.functional.normalize(coords, dim=-1) + # TODO concatenation is slow; work directly on pre-allocated tensors + outputs = [triton_spherical_harmonic(l, coords) for l in self.l_values] + outputs = torch.cat(outputs, dim=-1) + if self.normalization == "integral": + outputs /= (4.0 * torch.pi) ** 0.5 + return outputs + else: + return o3.spherical_harmonics( + self.irreps, coords, self.normalize, self.normalization + ) + + +class InteractionBlock(nn.Module): + def __init__( + self, + atomic_dim: int | o3.Irreps, + l_values: list[int], + edge_dim: int, + hidden_dim: int, + radius_cutoff: float, + degree_norm: float, + edge_kwargs: dict[str, Any] = {}, + sph_harm_kwargs: dict[str, Any] = {}, + activation: Callable = nn.functional.silu, + ): + """ + A module that combines radial basis with spherical harmonics to + describe molecular interactions. + + Parameters + ---------- + atomic_dim : int | o3.Irreps + Dimension of the atomic features. If int, it is treated as a + single irreducible representation. + l_values : list[int] + Values of the spherical harmonic order. If the Triton harmonics + are being used, this does not need to be contiguous. + edge_dim : int + Dimension of the edge features. + hidden_dim : int + Hidden dimension for the fully connected network. + radius_cutoff : float + Cutoff radius for the radial basis. + degree_norm : float + Normalization factor for the degree of the graph. + edge_kwargs : dict[str, Any], optional + Keyword arguments for the EdgeEmbedding module. Defaults to {}. + sph_harm_kwargs : dict[str, Any], optional + Keyword arguments for the SphericalHarmonicEmbedding module. + Defaults to {}. + activation : Callable, optional + Activation function for the fully connected network. Defaults to + nn.functional.silu. + + Notes + ----- + The `degree_norm` attribute is set as a property and effectively + represents the average number of neighbors in other models. + + Examples + -------- + >>> block = InteractionBlock(atomic_dim=8, l_values=[0, 1], + edge_dim=16, hidden_dim=32) + >>> block.sph_irreps + ['1x0e', '2x0e'] + """ + sph_harm_kwargs.setdefault("use_e3nn", False) + + super().__init__() + # this is effectively the average number of neighbors in other models + self.degree_norm = degree_norm + # treat atom features as invariant + if isinstance(atomic_dim, int): + atomic_irreps = f"{atomic_dim}x0e" + else: + atomic_irreps = atomic_dim + self.atomic_irreps = atomic_irreps + self.l_values = list(sorted(l_values)) + # these two attributes are similar but different: the former is used for describing + # the basis itself, and the latter is for actually specifying the weights + self.sph_irreps = spherical_harmonics_irreps(self.l_values, num_feat=1) + self.output_irreps = spherical_harmonics_irreps( + self.l_values, num_feat=hidden_dim + ) + # tensor product is the final bit the combines the radial basis with the spherical + # harmonics + self.tensor_product = o3.FullyConnectedTensorProduct( + self.atomic_irreps, + self.sph_irreps, + self.output_irreps, + shared_weights=False, + ) + self.edge_basis = EdgeEmbedding(edge_dim, radius_cutoff, **edge_kwargs) + self.spherical_harmonics = SphericalHarmonicEmbedding( + l_values, **sph_harm_kwargs + ) + self.fc = e3nn.nn.FullyConnectedNet( + [edge_dim, hidden_dim, self.tensor_product.weight_numel], activation + ) + + @property + def num_projections(self) -> int: + """Returns the expected number of projections.""" + return sum([2 * l + 1 for l in self.l_values]) + + @property + def output_dim(self) -> int: + """Returns the dimensionality of the output.""" + return self.output_irreps.dim + + def forward( + self, + atomic_features: torch.Tensor, + coords: torch.Tensor, + edge_index: torch.LongTensor, + ) -> torch.Tensor: + """ + High-level description: + + 1. Project cartesian coordinates onto spherical harmonic basis + 2. Project interatomic distances onto radial (bessel) basis + 3. Transform radial basis functions with learnable weights + 4. Compute tensor product between scalar atom features and spherical harmonic basis + 5. Update node features + """ + edge_dist = coords[edge_index[0]] - coords[edge_index[1]] + sph_harm = self.spherical_harmonics(edge_dist) + # calculate atomic distances, embed, and transform them + edge_basis = self.edge_basis(edge_dist.norm(dim=-1)) + edge_z = self.fc(edge_basis) + # compute tensor product + messages = self.tensor_product(atomic_features[edge_index[0]], sph_harm, edge_z) + # update node features + hidden_feats = ( + scatter(messages, edge_index[1], dim=0, dim_size=atomic_features.size(0)) + / self.degree_norm + ) + return hidden_feats + + +class ScalarReadoutLayer(nn.Module): + def __init__(self, hidden_irreps: o3.Irreps, output_dim: int): + super().__init__() + self.hidden_irreps = hidden_irreps + self.output_irreps = o3.Irreps(f"{output_dim}x0e") + self.output_layer = o3.Linear( + irreps_in=hidden_irreps, irreps_out=self.output_irreps + ) + + def forward(self, node_feats: torch.Tensor) -> torch.Tensor: + return self.output_layer(node_feats) + + +class EquiTritonModel(nn.Module): + def __init__( + self, + initial_atom_dim: int, + num_layers: int, + output_dim: int, + l_values: list[int], + edge_dim: int, + hidden_dim: int, + radius_cutoff: float, + degree_norm: float, + edge_kwargs: dict[str, Any] = {}, + sph_harm_kwargs: dict[str, Any] = {}, + activation: Callable = nn.functional.silu, + num_atoms: int = 100, + skip_connections: bool = True, + ): + """ + A neural network model designed for processing molecular graphs. + + This class implements a hierarchical architecture with multiple interaction blocks, + allowing for efficient and scalable processing of large molecular datasets. + + Parameters + ============= + initial_atom_dim : int + The dimensionality of the atomic embeddings. + num_layers : int + The number of convolutional layers to use. + output_dim : int + The dimensionality of the final scalar features. + l_values : list[int] + A list of spherical harmonics order to consider. If using the Triton kernels, + does not need to be contiguous. + edge_dim : int + The dimensionality of the edge features. + hidden_dim : int + The dimensionality of the hidden state in each interaction block. + radius_cutoff : float + The cutoff distance for radial basis functions. + degree_norm : float + The normalization constant for edge features. Typically square root of the average degree. + edge_kwargs : dict[str, Any], optional + Keyword arguments to pass to the InteractionBlock. + sph_harm_kwargs : dict[str, Any], optional + Keyword arguments to pass to the InteractionBlock. By default, + the ``use_e3nn`` kwarg is set to False, which uses the Triton kernels instead. + activation : Callable, optional + The activation function to use in each interaction block. Defaults to nn.functional.silu. + num_atoms : int, optional + The number of atoms in the embedding table (i.e. unique elements). Defaults to 100. + skip_connections : bool, optional + Whether to enable residual connections between layers. Defaults to True. + + Examples + ============ + >>> model = EquiTritonModel(...) + >>> graph = PyGGraph(...).to(device="cuda") + >>> graph_z, z = model(graph) + """ + sph_harm_kwargs.setdefault("use_e3nn", False) + + super().__init__() + self.atomic_embedding = AtomEmbedding(num_atoms, initial_atom_dim) + self.initial_layer = InteractionBlock( + initial_atom_dim, + l_values, + edge_dim, + hidden_dim, + radius_cutoff, + degree_norm, + edge_kwargs, + sph_harm_kwargs, + activation, + ) + self.conv_layers = nn.ModuleDict() + for layer_index in range(num_layers + 1): + self.conv_layers[f"conv_{layer_index}"] = InteractionBlock( + self.initial_layer.output_irreps, # subsequent layers use irreps instead + l_values, + edge_dim, + hidden_dim, + radius_cutoff, + degree_norm, + edge_kwargs, + sph_harm_kwargs, + activation, + ) + self.scalar_readout = ScalarReadoutLayer( + self.initial_layer.output_irreps, output_dim + ) + self.skip_connections = skip_connections + self.output_dim = output_dim + + def visualize(self, **kwargs): + """ + Visualize the sequence of tensor products within the model. + + Essentially, all this does is wrap around the ``tensor_product.visualize()`` + functionality, but also tacks on titles for each axis. + """ + num_plots = len(self.conv_layers) + 1 + fig, axarray = plt.subplots(num_plots, 1, figsize=(3, 12)) + # make indexing easier + axarray = axarray.flatten() + + self.initial_layer.tensor_product.visualize(ax=axarray[0], **kwargs) + axarray[0].set_title("Input layer", loc="right") + index = 1 + for layer_name, layer in self.conv_layers.items(): + ax = axarray[index] + layer.tensor_product.visualize(ax=ax, **kwargs) + ax.set_title(layer_name, loc="right") + index += 1 + fig.tight_layout() + return fig, axarray + + def forward(self, graph: PyGGraph) -> tuple[torch.Tensor, torch.Tensor]: + """ + Forward pass for a generic equivariant convolution model. + + Parameters + ---------- + graph : PyGGraph + PyG graph structure, which may be batched or a single graph. + + Returns + ------- + tuple[torch.Tensor, torch.Tensor] + 2-tuple of outputs; first element are graph level + outputs (from summing over nodes), second element + is the node-level outputs. + """ + # determine if the graph is batched or not + is_batched = hasattr(graph, "ptr") + for key in ["pos", "edge_index", "z"]: + assert hasattr(graph, key) + # get atom embeddings + atom_z = self.atomic_embedding(graph.z) # [nodes, initial_atom_dim] + # first message passing step + z = self.initial_layer(atom_z, graph.pos, graph.edge_index) + outputs = {} + for layer_name, layer in self.conv_layers.items(): + new_z = layer(z, graph.pos, graph.edge_index) + # add residual connections + if self.skip_connections and new_z.shape == z.shape: + new_z += z + z = new_z + outputs[layer_name] = z + # map final output as scalars + z = self.scalar_readout(z) + # latest node features are in z; we generate graph-level scalar features + # by doing a scatter add + if is_batched: + graph_z = scatter(z, graph.batch, dim=0, dim_size=graph.batch_size) + else: + # for a single graph, just sum up the node features + graph_z = z.sum(dim=0, keepdims=True) + return graph_z, z From f54f278d6b02093810bd0b6e5eb14f445375a545 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Wed, 28 Aug 2024 12:23:39 -0700 Subject: [PATCH 046/116] feat: defining __all__ for blocks --- src/equitriton/model/blocks.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/equitriton/model/blocks.py b/src/equitriton/model/blocks.py index 323150a..d84a86e 100644 --- a/src/equitriton/model/blocks.py +++ b/src/equitriton/model/blocks.py @@ -14,6 +14,16 @@ from equitriton.sph_harm.direct import triton_spherical_harmonic +__all__ = [ + "AtomEmbedding", + "EdgeEmbedding", + "SphericalHarmonicEmbedding", + "InteractionBlock", + "EquiTritonModel", + "ScalarReadoutLayer", +] + + class AtomEmbedding(nn.Module): def __init__(self, num_atoms: int, atom_dim: int): super().__init__() From 6709e09e7144a05f47793b40496faa1188386485 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Wed, 28 Aug 2024 12:24:44 -0700 Subject: [PATCH 047/116] feat: defining model namespace --- src/equitriton/model/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/equitriton/model/__init__.py diff --git a/src/equitriton/model/__init__.py b/src/equitriton/model/__init__.py new file mode 100644 index 0000000..e849bfe --- /dev/null +++ b/src/equitriton/model/__init__.py @@ -0,0 +1,6 @@ +from __future__ import annotations + +from equitriton.model.blocks import EquiTritonModel + + +__all__ = ["EquiTritonModel"] From fcb314fbcc0e004d9d31e23c27e1903d124f64c7 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Wed, 28 Aug 2024 12:25:34 -0700 Subject: [PATCH 048/116] chore: adding explicit dependencies --- pyproject.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e2325be..514cfc5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,8 @@ dependencies = [ "triton", "torch", "e3nn", - "tqdm" + "tqdm", + "matplotlib" ] description = "Triton-lang implementations of kernels for equivariant neural networks." dynamic = ["version", "readme"] @@ -37,6 +38,7 @@ train = [ "torch-geometric", "torch-scatter", "torch-sparse", + "torch-cluster", "pytorch-lightning==2.2.4" ] From c4f054e48af29a90bba100df2fd4bcc394bd42d7 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Wed, 28 Aug 2024 12:26:03 -0700 Subject: [PATCH 049/116] notebook: updating notebook with fix to subsequent layers needing irreps not scalar features --- notebooks/Baseline model development.ipynb | 217 +++++++++++++++------ 1 file changed, 157 insertions(+), 60 deletions(-) diff --git a/notebooks/Baseline model development.ipynb b/notebooks/Baseline model development.ipynb index 9823216..3ee803b 100644 --- a/notebooks/Baseline model development.ipynb +++ b/notebooks/Baseline model development.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 169, + "execution_count": 27, "id": "fd832b52-7df1-4d42-a2ea-a7139df2196b", "metadata": {}, "outputs": [], @@ -30,7 +30,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 28, "id": "a0307714-1036-4110-8b53-0c50062ee913", "metadata": {}, "outputs": [], @@ -40,7 +40,7 @@ }, { "cell_type": "code", - "execution_count": 186, + "execution_count": 108, "id": "79f96b13-929a-4b2e-a4d9-991194fd98ba", "metadata": {}, "outputs": [], @@ -123,8 +123,13 @@ "\n", " def forward(self, coords: torch.Tensor) -> torch.Tensor:\n", " if not self.use_e3nn:\n", + " if self.normalize:\n", + " coords = torch.nn.functional.normalize(coords, dim=-1)\n", " outputs = [triton_spherical_harmonic(l, coords) for l in self.l_values]\n", - " return torch.cat(outputs, dim=-1)\n", + " outputs = torch.cat(outputs, dim=-1)\n", + " if self.normalization == \"integral\":\n", + " outputs /= (4.0 * torch.pi) ** 0.5\n", + " return outputs\n", " else:\n", " return o3.spherical_harmonics(\n", " self.irreps, coords, self.normalize, self.normalization\n", @@ -337,7 +342,7 @@ " self.conv_layers = nn.ModuleDict()\n", " for layer_index in range(num_layers + 1):\n", " self.conv_layers[f\"conv_{layer_index}\"] = InteractionBlock(\n", - " self.initial_layer.output_dim,\n", + " self.initial_layer.output_irreps,\n", " l_values,\n", " edge_dim,\n", " hidden_dim,\n", @@ -399,7 +404,7 @@ }, { "cell_type": "code", - "execution_count": 144, + "execution_count": 102, "id": "4e568b72-3990-4652-8a32-db375c65a81b", "metadata": {}, "outputs": [], @@ -425,7 +430,7 @@ }, { "cell_type": "code", - "execution_count": 145, + "execution_count": 33, "id": "8671f0e8-0da8-4250-9d02-fb30ecb977fe", "metadata": {}, "outputs": [], @@ -435,7 +440,7 @@ }, { "cell_type": "code", - "execution_count": 146, + "execution_count": 34, "id": "f4971d48-3574-44d0-b2fb-27306bc3aeac", "metadata": {}, "outputs": [], @@ -448,7 +453,7 @@ }, { "cell_type": "code", - "execution_count": 147, + "execution_count": 35, "id": "55f14f94-51ca-4e5c-ad63-ca830404eaf9", "metadata": {}, "outputs": [], @@ -458,7 +463,7 @@ }, { "cell_type": "code", - "execution_count": 148, + "execution_count": 36, "id": "67f6e836-f4e0-48cf-981d-4419dda0159b", "metadata": {}, "outputs": [], @@ -469,7 +474,7 @@ }, { "cell_type": "code", - "execution_count": 149, + "execution_count": 37, "id": "46778ea6-92f2-4d7a-9bd8-aa58013cd1c6", "metadata": {}, "outputs": [ @@ -479,7 +484,7 @@ "torch.Size([2, 240])" ] }, - "execution_count": 149, + "execution_count": 37, "metadata": {}, "output_type": "execute_result" } @@ -490,7 +495,7 @@ }, { "cell_type": "code", - "execution_count": 151, + "execution_count": 38, "id": "0c82e80f-c4bd-43fe-8846-dab203757992", "metadata": {}, "outputs": [ @@ -521,7 +526,7 @@ }, { "cell_type": "code", - "execution_count": 132, + "execution_count": 39, "id": "a0913f1e-b000-4a3b-b723-84a92d16425d", "metadata": {}, "outputs": [ @@ -551,7 +556,7 @@ }, { "cell_type": "code", - "execution_count": 152, + "execution_count": 40, "id": "e893b896-f7b9-4b62-95a1-fe7fb45cf2ed", "metadata": {}, "outputs": [ @@ -566,7 +571,7 @@ ")" ] }, - "execution_count": 152, + "execution_count": 40, "metadata": {}, "output_type": "execute_result" } @@ -577,7 +582,7 @@ }, { "cell_type": "code", - "execution_count": 153, + "execution_count": 41, "id": "34c3578d-0017-4350-99d2-373ad2e542cb", "metadata": {}, "outputs": [ @@ -587,7 +592,7 @@ "(
, )" ] }, - "execution_count": 153, + "execution_count": 41, "metadata": {}, "output_type": "execute_result" }, @@ -608,7 +613,7 @@ }, { "cell_type": "code", - "execution_count": 154, + "execution_count": 42, "id": "f2785a28-4d2b-4cf0-972d-60df70595580", "metadata": {}, "outputs": [], @@ -618,24 +623,24 @@ }, { "cell_type": "code", - "execution_count": 155, + "execution_count": 43, "id": "4b80d729-b5e4-4cb5-802d-c27f2af32a70", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "tensor([[ 0.0590, -1.6986, -0.3744, ..., -2.5936, -2.0722, 1.8019],\n", - " [-1.0968, -2.7137, -2.4289, ..., 1.1375, 0.9818, 0.9651],\n", - " [-0.4404, -2.5330, -1.6619, ..., 0.1385, 2.5301, 0.2258],\n", + "tensor([[-0.3004, -0.0939, -0.3610, ..., 0.5229, -0.7874, -1.0619],\n", + " [-0.3039, -0.4817, -0.9658, ..., 0.2480, -0.3691, -0.5077],\n", + " [-0.6431, -0.0786, -0.3443, ..., 0.2488, -0.2363, -0.1527],\n", " ...,\n", - " [-0.1863, -0.3904, 0.2787, ..., 0.0165, -0.0322, 0.7170],\n", - " [ 0.5232, -1.4793, -0.0937, ..., -1.7656, 0.8989, 0.3833],\n", - " [-0.1590, -1.6844, 0.0832, ..., -1.7279, 0.9028, -0.9102]],\n", + " [-0.2051, 0.1712, 0.3823, ..., 0.6626, -0.1357, -0.1973],\n", + " [ 0.0641, 0.1162, -1.2586, ..., 0.2460, 0.7797, 0.2671],\n", + " [ 0.5735, -0.6425, -0.1460, ..., -0.0062, 0.0575, 0.3319]],\n", " device='cuda:0', grad_fn=)" ] }, - "execution_count": 155, + "execution_count": 43, "metadata": {}, "output_type": "execute_result" } @@ -656,7 +661,7 @@ }, { "cell_type": "code", - "execution_count": 192, + "execution_count": 44, "id": "c4ff86c7-e8e5-4560-83b9-fc1885fd8d13", "metadata": {}, "outputs": [ @@ -673,18 +678,18 @@ "# [0, 1, 2] are necessary at the minimum, but all orders should work\n", "layer = InteractionBlock(\n", " 64,\n", - " [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],\n", + " [0, 1, 2, 3, 4, 5, 7],\n", " 10,\n", " 32,\n", " radius_cutoff=6.0,\n", " degree_norm=17**0.5,\n", - " sph_harm_kwargs={\"use_e3nn\": True},\n", + " sph_harm_kwargs={\"use_e3nn\": True}, # this can be toggled for comparison\n", ").to(\"cuda\")" ] }, { "cell_type": "code", - "execution_count": 193, + "execution_count": 45, "id": "0a3a0a02-8037-4784-9771-3abf8dad7209", "metadata": {}, "outputs": [], @@ -719,7 +724,7 @@ }, { "cell_type": "code", - "execution_count": 189, + "execution_count": 46, "id": "fea72969-5f5a-410e-9a28-75346701498b", "metadata": {}, "outputs": [], @@ -754,7 +759,7 @@ }, { "cell_type": "code", - "execution_count": 183, + "execution_count": 47, "id": "8a38dc82", "metadata": {}, "outputs": [ @@ -781,25 +786,25 @@ { "data": { "text/plain": [ - "(tensor([[ 0.0590, -1.6986, -0.3744, ..., -2.5936, -2.0722, 1.8019],\n", - " [-1.0968, -2.7137, -2.4289, ..., 1.1375, 0.9818, 0.9651],\n", - " [-0.4404, -2.5330, -1.6619, ..., 0.1385, 2.5301, 0.2258],\n", + "(tensor([[ 1.2719, -0.0476, 0.3774, ..., -0.5458, 0.2401, -0.3716],\n", + " [ 1.2811, -0.6789, 2.2231, ..., 1.5065, 0.1164, 2.3330],\n", + " [ 0.8893, -0.5574, 1.1240, ..., -0.3095, 0.4741, 0.1339],\n", " ...,\n", - " [-0.1863, -0.3904, 0.2787, ..., 0.0165, -0.0322, 0.7170],\n", - " [ 0.5232, -1.4793, -0.0937, ..., -1.7656, 0.8989, 0.3833],\n", - " [-0.1590, -1.6844, 0.0832, ..., -1.7279, 0.9028, -0.9102]],\n", + " [ 1.4313, -0.4890, 1.0785, ..., -0.0667, 0.4573, -0.6801],\n", + " [ 0.5933, -0.7581, 1.4494, ..., -0.4565, 0.2007, -0.0230],\n", + " [ 1.0046, 0.8931, -0.0857, ..., -0.1521, 0.1383, -0.6058]],\n", " device='cuda:0', grad_fn=),\n", - " tensor([[ 0.0590, -1.6986, -0.3744, ..., 2.6008, 0.3975, -1.8447],\n", - " [-1.0968, -2.7137, -2.4289, ..., -0.2491, -1.0484, 1.2295],\n", - " [-0.4404, -2.5330, -1.6619, ..., -2.0959, -2.8912, -0.1732],\n", + " tensor([[ 1.2719, -0.0476, 0.3774, ..., -0.4330, -0.5297, 0.1103],\n", + " [ 1.2811, -0.6789, 2.2231, ..., -0.3310, 0.6223, 0.1604],\n", + " [ 0.8893, -0.5574, 1.1240, ..., 1.1496, 0.9203, -0.6469],\n", " ...,\n", - " [-0.1863, -0.3904, 0.2787, ..., 0.5650, -0.1882, 0.3297],\n", - " [ 0.5232, -1.4793, -0.0937, ..., 1.1183, -1.3011, -0.5838],\n", - " [-0.1590, -1.6844, 0.0832, ..., 0.1020, -0.7125, -0.9762]],\n", + " [ 1.4313, -0.4890, 1.0785, ..., -0.3615, -0.6725, -0.3254],\n", + " [ 0.5933, -0.7581, 1.4494, ..., -0.3065, 0.0335, 0.0163],\n", + " [ 1.0046, 0.8931, -0.0857, ..., 0.0519, -0.4086, 0.0626]],\n", " device='cuda:0', grad_fn=))" ] }, - "execution_count": 183, + "execution_count": 47, "metadata": {}, "output_type": "execute_result" } @@ -838,7 +843,7 @@ }, { "cell_type": "code", - "execution_count": 184, + "execution_count": 48, "id": "4482c983-55e6-4419-bf3a-04705010adae", "metadata": {}, "outputs": [ @@ -854,13 +859,13 @@ " ], dtype=object))" ] }, - "execution_count": 184, + "execution_count": 48, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAR8AAASnCAYAAADc9nzyAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAADmn0lEQVR4nOzdd1RU19oG8GfoIF2UKoI0USwBBVGiRpSSxJZYYkxsufEK1iS2aIztKrHEG6NpJjeWFGs0aiIoIkVsoIAiSEdQEBWld2be7w8+JyKggAwHmPe3Fmtlztl7nz1EH/cp+2wREREYY6yVKQjdAcaYfOLwYYwJgsOHMSYIDh/GmCA4fBhjguDwYYwJgsOHMSYIDh/GmCA4fBhjguDwYaydCQkJgUgkQkhIiNBdeSkcPkzu7NmzByKRCFevXhW6KwCA0tJSrFmzpt2HSVNx+DAmsNLSUqxdu5bDhzHGXkZJSUmjynH4MAZgxowZ0NTURFZWFsaNGwdNTU106dIFixcvhlgslpa7ffs2RCIRtm7div/+97/o3r071NXVMWzYMNy8ebNWm8OHD8fw4cPrPZaFhYW0vS5dugAA1q5dC5FIBJFIhDVr1jSp/+fPn8fEiRNhbm4OVVVVdOvWDR999BHKysqkZXbv3g2RSITo6Og69Tdu3AhFRUVkZWVJt125cgVeXl7Q0dGBhoYGhg0bhgsXLtSqt2bNGohEIsTHx+Pdd9+Fnp4e3NzcGtVnDh/G/p9YLIanpyc6d+6MrVu3YtiwYfjyyy+xa9euOmX37duHr7/+GnPnzsWnn36KmzdvYsSIEbh//36TjtmlSxd89913AIDx48fjl19+wS+//IK33nqrSe0cPnwYpaWl8PHxwY4dO+Dp6YkdO3Zg2rRp0jITJkyAuro6fvvttzr1f/vtNwwfPhympqYAgHPnzmHo0KEoLCzE6tWrsXHjRuTn52PEiBGIiIioU3/ixIkoLS3Fxo0b8eGHHzau08SYnNm9ezcBoMjISOm26dOnEwBat25drbKvvPIKOTk5ST+np6cTAFJXV6e7d+9Kt1+5coUA0EcffSTdNmzYMBo2bFid40+fPp26d+8u/fzw4UMCQKtXr25U/4ODgwkABQcHS7eVlpbWKefn50cikYgyMjKk26ZMmUImJiYkFoul26KioggA7d69m4iIJBIJ2djYkKenJ0kkklrHsLS0pFGjRkm3rV69mgDQlClTGtX3p/HIh7GnzJkzp9bnV199FWlpaXXKjRs3TjpKAABnZ2e4uLjg1KlTMu9jfdTV1aX/XVJSgtzcXAwePBhEVOs0a9q0acjOzkZwcLB022+//QZ1dXW8/fbbAICYmBgkJyfj3XffxaNHj5Cbm4vc3FyUlJTA3d0dYWFhkEgktY7/7O+tMZSaXIOxDkpNTU16/eUJPT095OXl1SlrY2NTZ5utrS0OHToks/49T2ZmJj7//HOcOHGiTn8LCgqk/z1q1CgYGxvjt99+g7u7OyQSCfbv34+xY8dCS0sLAJCcnAwAmD59eoPHKygogJ6envSzpaVlk/vM4cPY/1NUVGzR9kQiEaietxQ/fQG7JYjFYowaNQqPHz/GsmXL0LNnT3Tq1AlZWVmYMWNGrVGKoqIi3n33Xfz444/49ttvceHCBWRnZ+O9996TlnlSfsuWLejfv3+9x9TU1Kz1+emRV2Nx+DDWDE9GB09LSkqS3sUCakZN9Z2yZWRk1PosEoleqi+xsbFISkrC3r17a11gDgwMrLf8tGnT8OWXX+LkyZPw9/dHly5d4OnpKd1vZWUFANDW1sbIkSNfqm/Pw9d8GGuGP//8s9Zt6YiICFy5cgXe3t7SbVZWVkhISMDDhw+l265fv17ndrWGhgYAID8/v1l9eTJie3qURUTYvn17veX79u2Lvn374qeffsIff/yBd955B0pK/4xDnJycYGVlha1bt6K4uLhO/ae/z8vgkQ9jzWBtbQ03Nzf4+PigoqICX331FTp37oylS5dKy8yaNQvbtm2Dp6cnPvjgAzx48ADff/89evfujcLCQmk5dXV19OrVCwcPHoStrS309fXh4OAABweHRvWlZ8+esLKywuLFi5GVlQVtbW388ccf9V6remLatGlYvHgxANQ65QIABQUF/PTTT/D29kbv3r0xc+ZMmJqaIisrC8HBwdDW1sbJkyeb8uuqF498GGuGadOmYf78+di5cyc2bNiA3r1749y5czA2NpaWsbe3x759+1BQUICPP/4YJ06cwC+//AJHR8c67f30008wNTXFRx99hClTpuDIkSON7ouysjJOnjyJ/v37w8/PD2vXroWNjQ327dvXYJ2pU6dCUVERtra2cHZ2rrN/+PDhuHTpEgYMGICdO3di/vz52LNnD4yMjPDRRx81um/PI6L6rogxxup1+/ZtWFpaYsuWLdKRQ3uUm5sLY2NjfP7551i1apUgfeCRD2NyaM+ePRCLxXj//fcF6wNf82FMjpw7dw7x8fHYsGEDxo0bV+vuXGvj8GFMjqxbtw4XL17EkCFDsGPHDkH7wtd8GGOC4Gs+jDFBcPgwxgTB4cMYEwSHD2NMEBw+jDFBcPgwxgTB4cMYEwSHTwdRVVUldBdahUQiQXV1tdDdYC2Aw6eDWLhwId59912huyFTVVVVGDJkCHbu3Cl0V1gL4PDpAG7cuIEffvgBAwcOFLorMqWsrIz+/ftjzZo1LfZCKyYcnl7RzhER3N3dkZ2djRs3bkBFRUXoLsnUw4cPYWtri8mTJ+P7778XujvsJfDIp537888/ERwcjG3btnX44AFqFtlbvXo1fvzxR1y/fr1Jdb/55htYWFhATU0NLi4u9S5+x1oPj3zasfLycvTq1Qs9e/YUbL0oIVRVVaFv374wMjLCuXPnGvUC9oMHD2LatGn4/vvv4eLigq+++gqHDx9GYmIiunbt2gq9ZnU0eZlB1mZs3LiRlJSU6NatW0J3pdX5+/sTADpy5Eijyjs7O9PcuXOln8ViMZmYmJCfn590W0ZGBk2ZMoV0dXVJT0+P3n33XXr8+HGL953V4NOudio7OxsbNmzA/Pnz0bNnT6G70+q8vLzwxhtvYPHixSgvL39u2crKSly7dq3WMjAKCgoYOXIkLl26BABISUmBk5MTrK2tcfnyZQQGBiIlJQVLliyR6feQZxw+7dSKFSugrq6Ozz//XOiuCGbbtm24e/cutm3b9txyubm5EIvFMDQ0rLXd0NAQOTk5AABfX1/4+vpi3bp1sLOzg5OTE5YuXYpz587JrP/yjsOnHYqIiMDevXvxn//8B7q6ukJ3RzC2trZYuHAhNm7cWGsNrabKyMhAYGAgtmzZAk1NTenPe++9V2s9K9ay+DfbzkgkEixYsAB9+/bFv/71L6G7I7hVq1Zh3759+PTTTxtcKsbAwACKioq4f/9+re3379+HkZERrl+/Dn19fVy5cqVO3eYsA8wah0c+7czvv/+OK1euYPv27S2+tnh7pKOjgw0bNuCXX37B5cuX6y2joqICJycnBAUFSbdJJBIEBQXB1dUVysrKKCoqgomJCaytrWv9mJqattZXkTt8q70dKS4uhp2dHQYPHozDhw8L3Z02QywWw8nJCcoqyrhy+QoUFOr+m3rw4EFMnz4dP/zwA5ydnfHVV1/h0KFDSEhIgLKyMmxtbTF8+HCsWrUKnTp1QkpKCgICAvDVV1+1/heSF0LfbmON99lnn5Gqqiqlp6cL3ZU2QywR09Wsq/Tvnf8mALRv374Gy+7YsYPMzc1JRUWFnJ2d6fLly9J9V65coeHDh5O2tjZpaWmRo6Mjbd++vTW+gtzikU87cfv2bfTs2ROLFy/Gf/7zH6G70ybczr+NgJQA5BTX3LE6ueEk7sXdQ1JSEjQ1NQXuHXsRDp92YuLEibh48SISExPl/i9Wfnk+zqSeQfzDeACAmpIaXrN4DV2quqB3r974+OOPsWHDBoF7yV6k3V5w/u6779C3b19oa2tDW1sbrq6u8Pf3l+738/PDwIEDoaWlha5du2LcuHFITEyUSV9kPWcoJCQER44cwaZNm+Q6eCrFlTiXfg47I3Yi/mE8RBBhoMlALHBZABczF/Sw7IElS5bgyy+/RFpaWqPbDQsLw+jRo2FiYgKRSIQ///xTdl+CSbXb8DEzM8MXX3yBa9eu4erVqxgxYgTGjh2LuLg4AEBoaCjmzp0rfVq1qqoKHh4eKCkpabDNCxcu1PtSrvj4+Dq3aZ84ePAgPv74Y6xevRpRUVHo168fPD098eDBgxb5nmKxGIsWLcKgQYM6/Pt6GkJEuHH/BnZG7ERYRhiqJdWw1LXEnAFz8IbtG9BQ1pCWXbZsGQwMDJr0ZHJJSQn69euHb775RhbdZw0R9IpTC9PT06Offvqp3n0PHjwgABQaGlrvfrFYTP369aMJEyZQdXW1dHtCQgIZGhrSpk2b6q3XmDlDRM2fN/TDDz8QALpy5coLy3ZEdwvu0o/XfqTVwatpdfBq+urSVxT/IJ4kEkmDdX777TcCQOfOnWvy8QDQsWPH6myPjY0lb29v0tLSIkNDQ/r444+poqKiye2zf3SI8Kmurqb9+/eTiooKxcXF1VsmOTmZAFBsbGyD7WRlZZGVlRW9++67JBaLKSUlhUxMTOjf//53veUrKipIUVGxzh/WadOm0ZgxY2od28DAgFatWkUJCQl09epVcnZ2pg8++OC53ysvL48MDAxo2rRpzy3XERWWF9KxW8ekobMhbAOF3Q6jKnHVC+tKJBIaPHgw9e3bl6qqXlz+afWFT1RUFGlpadHKlSspOTmZgoODydjYmNatW9ektllt7Tp8bty4QZ06dSJFRUXS0dGhv//+u95yYrGY3njjDRoyZMgL28zIyCBzc3OaPHkymZub07Rp0xr8VzYrK4sA0MWLF2ttX7JkCTk7O0s/jxo1ij7//PNaZY4cOUKWlpbP7ctHH31EnTp1oqysrBf2u6OoEldR2O0w2hC2QRo8x24do8Lywia1ExkZSQDou+++a1K9+sLHycmJfH19a21bsWJFrf/HrOna9fQKOzs7xMTEoKCgAEeOHMH06dMRGhqKXr161So3d+5c3Lx5E+Hh4S9s09zcHL/88guGDRuGHj164H//+1+j3hfTkCfzhsLDw/Hll19Kt4vFYnTr1q3BegkJCdixYwfWrVsHExOTZh+/vSAiJOQm4EzqGeSV5wEAzLTN4G3tDVPtpj9lPGDAAMyYMQOfffYZJk+eDD09vWb1KyEhAdeuXcOvv/5aa7uKigoqKiqa1Sar0a7DR0VFBdbW1gAAJycnREZGYvv27fjhhx+kZebNm4e//voLYWFhMDMze2Gb9+/fx+zZszF69GhERkbio48+wo4dO+ot+6I5QwCaPW/o448/Rrdu3fDRRx+9sM/t3f3i+whICUB6fjoAQEtFC6OsRqFP1z4vFfwbN27EkSNHsHbt2mY/qRwXFyd9Avpp8fHx6NOnT7P7xtp5+DxLIpFI/zUiIsyfPx/Hjh1DSEgILC0tX1g/NzcX7u7usLe3x+HDh5GUlIThw4dDVVUVW7durVP+6TlD48aNk/YhKCgI8+bNA4Ba84Y0NDTqtFGfU6dOwd/fH0ePHoWamlojv337U1pViuD0YFzNvgoCQUlBCYO7DYabuRtUFF/+lbDGxsb47LPP8Nlnn+Hf//437O3tm9yGlpYWxGIxqqqqoKqqCgBIT0/HsWPHcOLEiZfuo1wT+ryvuZYvX06hoaGUnp5ON27coOXLl5NIJKIzZ84QEZGPjw/p6OhQSEgI3bt3T/pTWlpab3tisZgGDBhAr7/+eq27GDExMaSvr0/btm2rt96BAwdIVVWV9uzZQ/Hx8TR79mzS1dWlnJwcIiJ69OgRde7cmd5++22KiYmh5ORk8vf3p4ULF9bbXkVFBdnZ2dFrr7323Ds67Vm1uJou37lMfuf9pNd1Dt48SI9LW/6tgeXl5WRlZUWenp4N/j6LioooOjqaoqOjCQBt27aNoqOjKSMjg/Lz80lfX58WLVpEqampFBQURPb29vT++++3eF/lTbsNn1mzZlH37t1JRUWFunTpQu7u7tLgIaq5cFjfz+7duxts88yZM1RWVlZne1RUFN25c6fBes+bM0TUtHlD27ZtI5FIRNO/m06lFfUHZXuW/CiZdl7ZKQ2d7yK/o/S8dJkdr7yqnNZ8t4YA0F9//VVvmeDg4Hr/rEyfPp2IiMLCwsjR0ZHU1NSoR48e5OfnV+txDNY8PL2iDXn48CEsrSwh6itC5wmdoaumi/nO8zGj34x2//qMR6WPcDr1NJIeJQEANJQ14G7pjleMX4GCqOWfdSUixOTE4GzaWRRXFuO3Jb8BBUB8XLxcrPLRHnD4tCFz5szBwYMHMf+X+TiccRhlVWUAgB66PbB+xHoMMR8icA+brry6HGEZYbhy9wrEJIaCSAEupi4YZjEMakqyuZ6VWZAJ/2R/3Cu+BwDorN4ZllWWGDtsLLZs2YKPP/5YJsdlTcPh00bExMTA0dERX331FRYsWICHxQ+x4twKBKUHQUISiEQiuHVzwxfuX8BUp+2/4EpCEsTkxCAoLQglVTVTWmz0beBp7QkDDQOZHLOgvACBaYG4+eAmAEBVURXDLYbD2dQZigqKmDdvHn755RckJyc3erkcPz8/HD16FAkJCVBXV8fgwYOxadMm2NnZyeQ7yBMOnzaAiPDaa6/hwYMHuH79OpSVlaX7rmVfw8pzK5GQmwCgZgb35N6TsXLoyha5IyQLGfkZCEgJkI48DDQM4GnlCZvONjI5XpW4ChfuXMCFzAuoklRBBBEcjR0xwnIEOql0kpZ79OgRbGxsMGHCBOzatatRbXt5eeGdd97BwIEDUV1djRUrVuDmzZuIj49Hp06dXtwAaxCHTxtw5MgRTJw4EQEBAfD09Ky3zO+xv+PLS1/iUekjADV/oZe4LsHkPpNbs6vP9ezIQ01JDcO6D5OOPFoaESHuYRwCUwNRUFEAAOiu0x1e1l4w1jKut87OnTuxYMECXLt2Da+88kqTj/nw4UN07doVoaGhGDp0KAAgMzMTy5cvh7+/P0QiEby9vbFz585mP9goLzh8BFZWVgZ7e3v06dMHJ0+efG7ZSnEl/hP6HxyMO4gKcc3zTPYG9vBz90N/4/6t0NuG+3Uh8wIu3LmAakk1RBDBycQJr1m8Vmvk0ZKyi7IRkBKAzIJMAICOqg48rDzQq0uv5z6YWF1djf79+0NfXx+hoaFNfogxJSUFNjY2iI2NhYODA1JSUuDq6gofHx9MnToVxcXF8PX1RZ8+ffDTTz+91Hfs6Dh8BLZhwwasXbsWN2/erPMUbUOyCrKwPGg5wu+Eg4igIFLAyB4jseG1Deii2UXGPf4HEeHmg5sITAtEYUUhAMBC1wJe1l4w0jSSyTGLK4txLv0cou9Fg0BQVlCGm7kbBncbDGVF5Rc3ACAwMBAeHh44dOgQJk6c2OhjSyQSjBkzBvn5+dKpOh4eHnB1dcXatWul5f744w8sWbKkSe8UkkccPgLKysqCra0tfH19sWXLlibXD7sdhtUhq6XTEjSUNTC933Qsdl0s81vzz448dNV04WHlAXsD+5eaEtGQakk1rty9grCMMOmor0/XPhjZYyR01HSa3N7YsWMRExMjvZDcGD4+PvD390d4eDjMzMyQkZEBCwsLqKur13pp/ZN5e0lJSU3ulzzh8BHQ+++/j9OnTyM5ORk6Ok3/CwTU/EH/OeZn7IzYKb3uYaxpjJWvrsSbdm+2ZHcB1Iw8gtKCEJMTIx15vNr9VbiauTZ65NEURISkR0k4nXoaj8seAwBMtEzgbe2NbjoNT8x9kZSUFPTq1QurVq3CqlWrXlh+3rx5OH78OMLCwqRTdU6cOIGZM2c2OG+Pl915Pg4fgVy6dAmDBw/Gjz/+2CKL/xVXFmNN8BocTzyOKknN2xj7G/WHn7sf7Ls0fU7Ts+obefQ17IuRPUZCW1X7pduvz8OShwhICUBqXioAQFNFEyN7jEQ/w34tMrpatmwZduzYgcTExAbfMEDPzBG0sfnnjp2/vz/Gjh2L/Pz8Rs/bY//g8BGARCLBoEGDUF1djcjIyBY9RUp9nIplZ5fhWvY16WTNN2zewLrh66Cj3vTRFREh8VEizqSekY48TLVM4WXt9VIjj+cpqypDyO0QRGZHQkISKIoU4drNFa+avwpVJdUWO05hYSFsbW3h7u6O3377rd4yvr6++P3333H8+PFaz/bo6OigrKyM1/t6CRw+Ati3bx+mT5+OsLAwvPrqqzI5xumU01gXug5ZRTVrmGupaOHfA/4NHyefRofdg5IHCEgJQFpezYVTTRVNjOoxCn0N+8rkuo6EJLiWfQ3n0s+hrLrm6e6eBj3hYeUBfXX9Fj8eAOz8YSfmz5mPCxcuYPDgwXX2N/Q9d+/ejRkzZiAiIgLLli1DVFQUiAg2NjaYPn06FixYIJP+diQcPq2sqKgIdnZ2GDp0KA4cOCDTY4nFYnwd8TX+F/0/FFcWAwDMdcyxZvgajLAc0WC9sqoyBN+uedXFk5HHk1ddtOTI42lpeWkISAnAg5KaF+937dQVXtZe6KHXQybHqxRX4nzGeVzIvIBdc3bBUNMQkRGR9a52ymSDw6eVrVixAv/973+RmJgIc3PzVjlmXlkeVgWvgn+yP8QkhggiOJs644uRX8BS75/3HElIgqvZVxGcHiwdedgb2MPDygN66rJ5YC6vLA9nUs/gVu4tAIC6kjpes3wNA0wGyGzC6Y37N3A27SyKKosAANW3q7Fh5gbpaIa1Dg6fVpSWlgZ7e3ssX7681nMhrSX2fixWnFuB2PuxAAAVRRWMtx+PNUPX4F7JvTojD29r71rh1JIqqitwPvM8Lt25JJ1wOtBkIIZbDIe6cuNufTfV3cK78E/2l56K6qnpwdPaE3ad7TB16lQEBwcjKSkJWlpaMjk+q43DpxW9/fbbiIiIQEJCgqDzgv6I/wObLmySBk0n5U5wNHaElb4VNJQ18JrFa3AycZLZyOP6/evSV10AgJWeFTytPdG1U+MmezZVUUURzqadxfX71wHUhO7Q7kMxyGwQlBRqXuZ5584d2NnZYeHChfDz85NJP1htHD6t5Ny5c3B3d8fvv/+OKVOmCN0dVIorsfnCZuy9vhdV4ppb8w5dHLDBfQP6GfWTyTHvFNxBQEqAdOShr64PTytP2Ha2ldmDiZfuXML5zPOoFFcCqHn8wN3SHVqqdUc369atw4YNGxAfHw8rK6sW7w+rjcOnFVRXV8PR0RHa2to4f/68TP6iNdeZlDPYfGEz7hTegbGWMRRECnC3dMdc57nQVdNtkWMUVhTibNpZ3Lh/A0DNqy6Gdh8KFzMX6cijJRERbuXewpnUM8gvzwcAdNPuBi9rr+euhFFaWoqePXvCyckJx44da/F+sdo61Avk26off/wRsbGxiIyMbFPBAwAGnQzg1t0N2iraSMtPk87VunDnAqY4TMGUPlOaHRBV4ipcunsJ5zPOS1910d+oP9x7uENTRTZrzucU5yAgJQC3828DALRVtTGqxyg4dHV44e9eQ0MDW7ZswTvvvIOzZ89i5MiRMukjq8EjHxnLy8uDjY0NxowZg59//lno7tQRdS8KJxJPwK6zHab0mYKgtCD8cO0H6fUgEy0T+A70hZu5W6PbbGjk4W3jDRMt2axBVlJZguDbwbUerhzSbQiGmA9p0nuPiAhDhw5FXl4eYmJioKTE/z7LCoePjC1atAj/+9//kJycLF3Lqy15NnwAoLK6Er/c+AWH4w+jvLocAPCK0StYNGgRuut2f257OcU58E/2R0ZBBoCmjTyaQywRIzI7EiG3Q6R97d2lN0ZZjWr2aWNUVBQGDBiAnTt3wtfXtwV7y57G4SND8fHx6Nu3LzZu3IilS5cK3Z161Rc+T+SW5mLnlZ0IzQiVjia8rb3x7wH/rnPaVFJZgnPp5xB1L0pa9smrLmT1xsWUxykISAlAbmkuAMBI0wje1t4vDMjG+PDDD3H06FEkJydDX182T1fLOw4fGSEieHl5ITU1FXFxcdIF59qa54XPEzfv38T2K9uR/DgZQM1oZlq/aXir51sgECKyIhCaESodeTh0dcCoHqOa9aqLxnh2JYxOyp0wwnJEi66Ecf/+fdja2mL69On4+uuvW6RNVhuHj4z89ddfGD16NP7880+MHTtW6O40qDHhA9RMhvVP8cf/ov8nnWCqraoNG30b6W1rY01jeFl7tcjIoz7l1eUIvR2KK1lXICEJFEQKGGQ2CEO7D5XJShhbt27F8uXLcf36dfTu3bvF25d3fDVNRnJycjBu3DiMGTNG6K60CAUFBbxh+wbce7jjf1H/w9FbR1FYUYhr966hu053/MvxXxhiPkQmDyZKSILoe9E4l35OuhKGbWdbeFh5yGwlDABYsGAB/vzzT2RmZnL4yACPfGSIiNrcrfVnNXbk86yE3ASsCVmDnOIcuJm7QUNZA6+avwrXbq4t+uxORn4G/FP8kVOcA6Dmxfle1l6w1rdusWM8T3v4f9he8chHhjryH1pjTWP0NOgJcx1zWOha4G7hXQSlByHqXhQ8rDzQ06DnS33//PJ8BKYGIu5hHICalTCGWwzHQJOBMlkJoyEd+f+h0Dh82EvRVtXGB698gNgHsQhMDUReeR4Oxh2Epa4lvKy9YKhp2KT2hFgJgwmDw4e9NJFIhL6GfdHToCfCM8Nx8c5FpOen4/ur32OAyQC8ZvkaNJSf/5pRIkLsg1icTTtbayUMb2vvJgcYax84fFiLUVFUwQjLEXA0dpSeMkVmRyL2QSxes6h5R099p0xZhVkISAnAncI7AGpWwvC08nzpUzfWtnH4sBanq6aLib0nYmD+QASkBNQ89Zzij6vZV+Fp7Sm9WFxUUYSg9JqVMICa8JLFRWvWNvH/YSYzFroWmO00G9H3ohGUHoSHpQ/x641f0UOvB3RUdRD3ME76qot+hv0wssfIel91wTomDh8mUwoiBTiZOKF3197SBwSfvJAeAMy0zeBl7QUzbTMBe8mEwG/LZq1CTUkNntae8B34z0RNR2NHfPDKBxw8corDh7UqAw0D6VPJslqCh7UPHD6MMUFw+DDGBMHhwxgTBIcPY0wQHD6MMUFw+DDGBMHhwxgTBIcPY0wQHD6MMUFw+DDGBMHhwxgTBIcPY0wQHD6MMUFw+DDGBMHhwxgTBIcPY0wQHD6MMUFw+DDGBMHhwxgTBIcPY0wQHD6MMUFw+DDGBMHhwxgTBIcPY0wQHD6MMUFw+DDGBMHhwxgTBIcPY0wQHD6MMUFw+DDGBMHhwxgTBIcPY0wQHD6MMUFw+DDGBMHhwxgTBIcPY0wQHD6MMUFw+DDGBCEiIhK6E6xl/Prrr7h8+XKT6jwseYj0/HToqunCtrNto+tVVlci5n4MFEQKGGAyoEnHvHH/Bsqry9HToCe0VbWbVHf06NHw9PRsUh3WNikJ3QHWcpKSkhAeHt6kOgSChCQoRzkeiB40qa6YxACA8LSmHVNCEhAI19OvQwRRk+r279+/SeVZ28UjH8aYIPiaD2NMEBw+jDFBcPgwxgTB4cMYEwSHD2NMEBw+cqigoACzZ8+GtbU17O3tce/evUbVq66uxoYNG+Dq6gpHR0dMnz4dgYGBMjse69g4fOTQ3LlzERsbi82bNyMjIwNlZWUAgI8++gg7d+5ssN7y5cvx7bffwt3dHePGjUNFRQXefPNNzJw5E897YqO5x2MdHDG5o6+vT1FRUUREpKmpSampqURE5O/vTwMGDGiwnrGxMYWGhtbalpaWRr169aLNmze3+PFYx8YjHzlERNDS0qqz3cbGBsnJyQ3WKykpgZmZWa1tlpaW2LFjB3bt2tXix2MdG4ePHPL29sZvv/1WZ3tJSQlEooanO7i5uWHv3r11tltaWiI7O7vFj8c6Np7bJYf8/PwwYEDNZFAigkgkQnl5OdavXw9HR8cG623atAlDhgxBXl4e5s+fDxsbG1RVVWHHjh3o1atXix+PdXDCnvUxoSQnJ5OHhweJRCIyMDAgVVVV6tKlC0VGRj63XlRUFA0YMIBEIhGpqqqSkpISGRgYUHh4uEyOxzounlgq5zIzM3H9+nUoKyvDxcUFenp6jaqXmJiIuLg4aGlpwcXFBdrajXs1RnOPxzoeDh/GmCD4grOcyc3NxebNmzF+/Hi4urrC1dUV48ePx5YtW/Dw4cNmtXnnzh3MmjWr3n1lZWUIDw9HfHx8nX3l5eXYt29fs47J2j8e+ciRyMhIeHp6QkNDAyNHjoShoSEA4P79+wgKCkJpaSlOnz4tvTjcWNevX4ejoyPEYnGt7UlJSfDw8EBmZiZEIhHc3Nxw4MABGBsbS49rYmJSpx6TDxw+cmTQoEHo168fvv/++zq3uIkIc+bMwY0bN3Dp0qVa+06cOPHcdtPS0vDJJ5/UCZHx48ejqqoKe/bsQX5+PhYtWoT4+HiEhITA3Nycw0fOcfjIEXV1dURHR6Nnz5717k9ISMArr7winf7whIKCAkQi0XOnUIhEojohYmhoiLNnz6JPnz4AagLO19cXp06dQnBwMDp16sThI8f4mo8cMTIyQkRERIP7IyIipKdiTzM2NsbRo0chkUjq/YmKiqq3vbKyMigp/fMomUgkwnfffYfRo0dj2LBhSEpKevkvxdotfshQjixevBizZ8/GtWvX4O7uXueaz48//oitW7fWqefk5IRr165h7Nix9bbb0KioZ8+euHr1Kuzt7WttfzKZdMyYMS/7lVh7JsTDRUw4Bw4cIBcXF1JSUiKRSEQikYiUlJTIxcWFDh48WG+dsLAw8vf3b7DN4uJiCgkJqbN948aN5O3t3WA9Hx8fEolETf8SrEPgaz5yqqqqCrm5uQAAAwMDKCsrC9wjJm84fBhjguALzowxQXD4MMYEweHDGBMEh4+cefDgQb230wFg+/btDb4UrLXrMTkg7M021tri4+PJyMiIfH19a21fvHgxGRgYUExMTJuoxzo+Dh85lJCQQKampjRz5kwSi8U0f/58MjQ0pOvXr7epeqxj41vtcio1NRXu7u5QVlZGaWkpzp49W+dJ5LZQj3VcfM1HTllZWcHV1RWpqakYOHAg7Ozs2mQ91nFx+MghIsJ7772Hy5cvIzQ0FImJiZg0aRKqq6vbVD3WwQl60sdaXVVVFU2cOJGsra0pMzOTiIhycnLIwcGBRo8eTRUVFW2iHuv4eOQjZyIiIpCcnIzz58+jW7duAGreuxMcHIycnBycP3++TdRjHR9fcJZD9P9rZzV2u1D1WMfG4cMYEwSfdjHGBMHhwxgTBIcPY0wQHD6MMUFw+MiZ5q4g2tr1mBwQ6gEj1voSExOpe/fuJBKJSEFBgYYOHUrZ2dnS/Tk5OaSgoCB4PSYfeOQjR5YtWwYHBwc8ePAAiYmJ0NLSwpAhQ5CZmdmm6jE5IXT6sdbTtWtXunHjhvSzRCKhOXPmkLm5OaWmpjY4Emntekw+8MhHjjR3BdHWrsfkA69YKkeau4Joa9dj8oFHPnJk/Pjx2L9/f737du7ciSlTptS77HFr12Pyged2McYEwSMfOXPr1i3s3r0bCQkJAICEhAT4+Phg1qxZOHfuXJupx+SAoJe7Wavy9/cnFRUV0tfXJzU1NfL396cuXbrQyJEjacSIEaSoqEhBQUGC12PygcNHjri6utLKlSuJiGj//v2kp6dHK1askO5fvnw5jRo1SvB6TD5w+MgRbW1tSk5OJiIisVhMSkpKFBUVJd0fGxtLhoaGgtdj8oGv+ciZJ28OVFBQgJqaGnR0dKT7tLS0UFBQ0CbqsY6Pw0eOWFhYIDk5Wfr50qVLMDc3l37OzMyEsbGx4PWYfOCHDOWIj48PxGKx9LODg0Ot/f7+/hgxYoTg9Zh84Od8GGOC4NMuxpggOHwYY4Lg8GGMCYLDhzEmCA4fxpggOHwYY4Lg8GGMCYLDhzEmCA4fxpggOHwYY4Lg8GGMCYLDhzEmCA4fxpggOHwYY4Lg8GGMCYLDhzHWJBUVFVi2bBlMTEygrq4OFxcXBAYGNrkdDh/GWJPMmDED27Ztw9SpU7F9+3YoKiri9ddfR3h4eJPa4TcZMsYaLSIiAi4uLtiyZQsWL14MACgvL4eDgwO6du2KixcvNrotHvkwJoCsrCx88MEHMDExgaqqKiwtLeHj44PKykoAQFpaGiZOnAh9fX1oaGhg0KBB+Pvvv2u1ERISApFIhEOHDmHDhg0wMzODmpoa3N3dkZKSIi03b948aGpqorS0tE4/pkyZAiMjo1rv2n6eI0eOQFFREbNnz5ZuU1NTwwcffIBLly7hzp07jf4d8AvkGWtl2dnZcHZ2Rn5+PmbPno2ePXsiKysLR44cQWlpKfLy8jB48GCUlpZiwYIF6Ny5M/bu3YsxY8bgyJEjGD9+fK32vvjiCygoKGDx4sUoKCjA5s2bMXXqVFy5cgUAMHnyZHzzzTf4+++/MXHiRGm90tJSnDx5EjNmzICiomKj+h4dHQ1bW1toa2vX2u7s7AwAiImJQbdu3Rr3ixB22TDG5M+0adNIQUGBIiMj6+yTSCS0aNEiAkDnz5+Xbi8qKiJLS0uysLAgsVhMRETBwcEEgOzt7amiokJadvv27QSAYmNjpW2amprS22+/XetYhw4dIgAUFhbW6L737t2bRowYUWd7XFwcAaDvv/++0W3xaRdjrUgikeDPP//E6NGjMWDAgDr7RSIRTp06BWdnZ7i5uUm3a2pqYvbs2bh9+zbi4+Nr1Zk5cyZUVFSkn1999VUANaduT9qcOHEiTp06heLiYmm5gwcPwtTUtNZxXqSsrAyqqqp1tqupqUn3NxaHD2Ot6OHDhygsLKyzhtnTMjIyYGdnV2e7vb29dP/Tnl6IEQD09PQAAHl5edJtkydPRllZGU6cOAEAKC4uxqlTpzBx4kTpqrKNoa6ujoqKijrby8vLpfsbi8OHsXauoes19NSN7EGDBsHCwgKHDh0CAJw8eRJlZWWYPHlyk45lbGyMe/fu1dn+ZJuJiUmj2+LwYawVdenSBdra2rh582aDZbp3747ExMQ62xMSEqT7m2PSpEkICAhAYWEhDh48CAsLCwwaNKhJbfTv3x9JSUkoLCystf3Jxe3+/fs3ui0OH8ZakYKCAsaNG4eTJ0/i6tWrdfYTEV5//XVERETg0qVL0u0lJSXYtWsXLCws0KtXr2Yde/LkyaioqMDevXsREBCASZMmNbmNCRMmQCwWY9euXdJtFRUV2L17N1xcXBp/pwt8q52xVrdx40acOXMGw4YNw+zZs2Fvb4979+7h8OHDCA8Px/Lly7F//354e3tjwYIF0NfXx969e5Geno4//vgDCgrNGzM4OjrC2toaK1euREVFRZNPuQDAxcUFEydOxKeffooHDx7A2toae/fuxe3bt/G///2vaY01+r4YY6zFZGRk0LRp06hLly6kqqpKPXr0oLlz50pvmaemptKECRNIV1eX1NTUyNnZmf76669abTy51X748OFa29PT0wkA7d69u85xV65cSQDI2tq62X0vKyujxYsXk5GREamqqtLAgQMpICCgye3w9ArGmCD4mg9jTBB8zYcxhuLi4loPINanS5cujZ6G0RgcPowxbN26FWvXrn1umfT0dFhYWLTYMfmaD2MMaWlp0ukYDXFzc5NOo2gJHD6MMUHwBWfGmCA4fBhjguDwYYwJgsOHMSYIDh/GmCA4fBhjguDw6SDOnj2L4OBgobshU0SEQ4cO4caNG0J3hbUAfs6nAygvL4e9vT169epVZ3mVjqSqqgr9+vVD165dERwc3KTXf7K2h0c+HcC2bdtw9+5dbNu2TeiuyJSysjL++9//IjQ0FH/88YfQ3WEvicOnncvKysLGjRuxYMGCel863tF4enrizTffxOLFi5u0UgIAfPPNN7CwsICamhpcXFwQEREho16yxuDwaec+/fRTaGhoYNWqVUJ3pdV8+eWXyM7OxpdfftnoOgcPHsTHH3+M1atXIyoqCv369YOnpycePHggw56y52r268yY4C5fvkwA6IcffhC6K61u8eLFpKGhQXfv3m1UeWdnZ5o7d670s1gsJhMTE/Lz85Nuy8jIoClTppCuri7p6enRu+++S48fP27xvrMaPPJppyQSCRYuXIh+/frhgw8+ELo7re6zzz5Dp06dsHz58heWraysxLVr1zBy5EjpNgUFBYwcOVL6kvaUlBQ4OTnB2toaly9fRmBgIFJSUrBkyRKZfQd5x+HTTv3222+4cuUKtm/f3qIveGovdHR0sHHjRvz666+1VnmoT25uLsRiMQwNDWttNzQ0RE5ODgDA19cXvr6+WLduHezs7ODk5ISlS5fi3LlzMvsO8o5vtbdDxcXFsLW1hZubm3QROHkkFosxcOBAKCkp4fLlyw2u6pCdnQ1TU1NcvHgRrq6u0u1Lly5FaGgoDh06BAsLC6irq9dqQywWo1u3bkhKSpL5d5FH/CbDdsjPzw95eXnYvHmz0F0RlKKiIrZv346hQ4fil19+wfTp0+stZ2BgAEVFRdy/f7/W9vv378PIyAjXr1+Hvr6+dOG7pzVl+V/WNDzyaWfS09Nhb2+PJUuWYP369UJ3p0145513EBYWhsTERGhpadVbxsXFBc7OztixYweAmmtm5ubmmDdvHvr164exY8ciPz8fGhoardl1+Sbs9W7WVG+//TaZmppScXGx0F1pMzIyMkhNTY0+/fTTBsscOHCAVFVVac+ePRQfH0+zZ88mXV1dysnJoUePHlHnzp3p7bffppiYGEpOTiZ/f39auHBh630JOcTh046cO3eOANCvv/4qdFfalPKqcnp//vukrKJMqampDZbbsWMHmZubk4qKCjk7O9Ply5el+65cuULDhw8nbW1t0tLSIkdHR9q+fXtrdF9u8WlXO1FdXQ0nJyd06tQJFy5c4HlNqJloev3+dQSlBeFRwSN8N+M7DB88HMeOHRO6a6wR+IJzO/HTTz/hxo0biIiI4OABcLfwLvyT/ZFVlAUAMNI3wmfrP8Pify/GuXPnMGLECIF7yF6kTT7n4+fnh4EDB0JLSwtdu3bFuHHjkJiYKN0vFouxatUqWFpaQl1dHVZWVli/fj2eHsQ1pkxLkfWcoby8PHz22WeYPn06Bg4c2KJttzeFFYU4eusofor6CVlFWVBRVMGoHqPgO9AXH3/4MYYMGYJFixahurq60W2GhYVh9OjRMDExgUgkwp9//im7L8D+IehJXwM8PT1p9+7ddPPmTYqJiaHXX3+dzM3NpRdZN2zYQJ07d6a//vqL0tPT6fDhw6SpqVnrHL0xZZ4VHh5OlZWVdbbHxcVRTk5OvXUOHDhAKioq9PPPP1NcXBx9+OGHpKurS/fv33/J38I/Fi1aRJqampSdnd1ibbY3ldWVFHo7lP4T+h9aHbya1gSvoT9v/UlFFUW1yl29epVEIhF9++23jW771KlTtHLlSjp69CgBoGPHjrVw71l92mT4POvBgwcEgEJDQ4mI6I033qBZs2bVKvPWW2/R1KlTpZ8bU+ZpYrGY+vXrRxMmTKDq6mrp9oSEBDI0NKRNmzbVW68xc4aImj9vKD4+npSUlOq0Jy8kEgnFPYij/176L60OXk2rg1fTT9d+oqzCrAbrzJw5kzp37kyPHj1q8vEaCp/Y2Fjy9vYmLS0tMjQ0pI8//pgqKiqa3D77R5s87XpWQUEBAEBfXx8AMHjwYAQFBUmfPL1+/TrCw8Ph7e0trdOYMk9TUFDAqVOnEB0djWnTpkEikSA1NRUjRozAuHHjsHTp0jp1GjNnCGj+vCEiwkcffQRzc3MsWrSoEb+pjiWnOAd7r+/FobhDyC/Ph7aqNt62fxuzXpkFEy2TButt3LgRFRUVL1z+t7Gio6MxePBgODo6IioqCgcOHMD+/fuxadOmFmlfbgmdfi8iFovpjTfeoCFDhtTatmzZMhKJRKSkpEQikYg2btxYp96LytQnIyODzM3NafLkyWRubk7Tpk0jiURSb9msrCwCQBcvXqy1fcmSJeTs7Cz9PGrUKPr8889rlTly5AhZWlo+ty9//fWXXJ4GFFcU08nEk7QmeA2tDl5N60PXU3B6MFVUN36ksWnTJlJUVKS4uLgmHbu+37eTkxP5+vrW2rZixYpa/49Z07X58JkzZw51796d7ty5I922f/9+MjMzo/3799ONGzdo3759pK+vT3v27GlSmYaEhoYSAOrRowdVVVU1WK4x4XP79m0CQOrq6tSpUyfpj5qaGtnY2DTYdkVFBdna2tKIESMaDL+OplpcTRczL5LfeT/pKdbhuMOUV5bX5LbKy8vJysqKPDw8mvT7ezZ8bt26RQDo1q1btcqtWbOG+vXr1+R+sX+06fCZO3cumZmZUVpaWq3tZmZmtHPnzlrb1q9fT3Z2dk0qU5+cnByys7Oj0aNHk5GREc2bN6/BshUVFaSoqFjnX8pp06bRmDFjiIjo+PHjpK+vT8nJyXV+nvcumi+//JIUFBToxo0bz+1vR5GUm0Q7ruyQhs73kd/T7bzbL9Xm8ePHCQCdPHmy0XWeDZ8jR46QsrIyicXiWuUmTZpE77333kv1T961yed8iAjz58/HsWPHEBISAktLy1r7S0tL68xgVlRUhEQiaVKZZ+Xm5sLd3R329vY4fPgwkpKSMHz4cKiqqmLr1q11yquoqMDJyQlBQUEYN24cgJo5Q0FBQZg3bx6AmvcOFxUVwcTEpNHzhh48eIC1a9dizpw56NOnT6PqtFe5pbk4nXIayY+TAQCdlDvBvYc7+hv1h4Lo5S5Jjh49GiNHjsRHH30EDw8PqKioNLkNLS0tiMViVFVVQVVVFUDN/Lpjx47hxIkTL9U/uSd0+tXHx8eHdHR0KCQkhO7duyf9KS0tJSKi6dOnk6mpqfQ2+tGjR8nAwICWLl0qbaMxZZ4mFotpwIAB9Prrr9e6ixETE0P6+vq0bdu2eus9b84QETVr3tCHH35Ienp6lJub29RfXbtRVlVGAckBtDZkLa0OXk3rQtbR6ZTTVFZV1qLHiY2NJUVFRdqyZUuDZYqKiig6Opqio6MJAG3bto2io6MpIyOD8vPzSV9fnxYtWkSpqakUFBRE9vb29P7777doP+VRmwwfAPX+7N69m4iICgsLaeHChWRubk5qamrUo0cPWrlyZa3QaEyZZ505c4bKyur+4Y+Kiqp1zelZz5szRNS0eUNRUVEkEolo3aZ1z/sVtVtiiZiuZl2lTeGbpKdYv934jXJLZBe0H875kDS1NBt8Vis4OLjeP2/Tp08nIqKwsDBydHSU/jny8/Or9TgGax6e29WGEBGGvDoE11Kvofuy7pjcdzJWDVsFFcWmny60RbfzbyMgJQA5xTVvD+yi0QWe1p6w1reWyfEqxZW4kHkBgXGB+Pq9r/HOhHfw008/yeRYrOk4fNqQw4cPY9KkSbBeYI0qyyoAQGeNzvjE9RO82+ddgXvXfPnl+QhMDUTcwzgAgJqSGl6zeA0DTAZAUaHlXwFLRLj54CYC0wJRWFEIAEg9nYrfNv2Gq1evwtHRscWPyZqOw6eNKCsrQ8+ePdGvXz8cOXYEG8I24GDcQZRXlwMAehr0xIYRG+Bk4iRwTxuvUlyJ8MxwXLxzEdWSaoggwgCTAXjN8jVoKMvmpV3ZRdnwT/bHncI7AABdNV14WHnARtcGr7zyCvT09BAWFsaTc9sADp82Yv369Vi/fj3i4uJgY2MDAMgqyMKKcysQlhkGIoKCSAHulu7YOGIjumh2EbjHDSMixD6Ixdm0s9KRh6WuJbysvWCoafiC2s1TXFmMoLQgROdEAwCUFZTxavdX4WrmCmVFZQA169mPGjUKBw4cwOTJkxvVrp+fH44ePYqEhASoq6tj8ODB2LRpk1ws0ChrHD5twN27d2FnZ4e5c+fW+17mC5kXsOrcKqTlpwEA1JXVMaPfDCx2XdzmVq7IKsxCQEqAdOShp6YHDysP9DToKZPRRrWkGlfuXkFoRigqxZUAgH6G/eDewx3aqtp1yo8bNw5RUVFISEho1KMPXl5eeOeddzBw4EBUV1djxYoVuHnzJuLj49GpU6cW/z7yhMOnDXjvvfcQGBiI5ORkaGvX/QsD1LwiZM/1PdhxZQfyK/IBAIaahljpthJjeo5pxd7Wr6iiCEHpQYjJiQEAqCiq4FXzV+HazRVKCi3/OBkRIfFRIs6knsHjsscAAFMtU3jbeMNM26zBeikpKejduzdWrlyJzz//vMnHffjwIbp27YrQ0FAMHToUAJCZmYnly5fD398fIpEI3t7e2LlzJ/T09Jr35eQEh4/ALl68iCFDhuCnn35q1OJ/ZZVlWB2yGscSjqFKUnNRup9hP3wx8gvYd7GXdXfrqJZU4/LdywjLCJOOPPob9Ye7pTu0VOt/mfvLelDyAAEpAUjLqxkJaqloYWSPkehr2LdRo6vly5fj66+/RmJiIrp169akY6ekpMDGxgaxsbFwcHBASkoKXF1d4ePjg6lTp6K4uBi+vr7o06cP31l7AQ4fAUkkEri4uEAikSAiIqJJp1Cpj1OxPGg5rmZdBYGgpKAEb2tv/Oe1/0BHXUeGva5BREjITcCZ1DPIK88DAJhpm8Hb2hum2qYyOWZpVSlCbofgavZVSEgCRZEiBncbDDdzN6gqqTa6naKiItja2uK1117D77//3uh6EokEY8aMQX5+PsLDwwEAHh4ecHV1rTWD/o8//sCSJUuQlpbW+C8nhzh8BLRnzx7MnDkT58+fh5ubW7PaCEwNxNrQtbhbeBdAzShgttNs+A7wldn1oPvF9xGQEoD0/HTpMUdZjUKfrn1kcl1HQhJczb6K4PRglFWXAQDsDezhYeUBPfXmndo053fv4+MDf39/hIeHw8zMDBkZGbzY4Evg8BHIk399hw8fjv37979UW2KxGN9e/Ra7ru1CUWURAKCbdjd8PuxzjLIa1RLdBVAz8ghOD8bV7H9GW09GHrJ6EDL1cSpOp57Gg5IHAADDTobwsvaCpZ7lC2o+35NRJxEhIiKiwdVOn5g3bx6OHz+OsLAw6VzDEydOYObMmQ0uNmhqKpsRYEfB4SOQTz/9FNu3b0dCQgLMzc1bpM2CsgJ8FvwZ/FP8pc/VDDQdiE0jN73UX1axRIyr2VcRcjtEOvLo1aUXRvUY1eyRx4s8LnuM0ymnkfio5t3dGsoaeM3iNTiZOL30hNMnnlxv+/nnnzFz5sx6y9Azk5yfPAYBAP7+/rzY4Evg8BFAamoqevXqhU8//RRr1qxp8fZvPriJlUErcf3+dQA1z7yM7zkea4evhbpK05b/TX2cioCUADwsfQgAMNI0gpe1Fyx0LVq62wCAiuoKhGWE4fLdyxCTGAoiBTibOmNY92FQV275pYunTp0qfeNlfXcafX198fvvv+P48eO1nu3R0dFBWVmZdPS6atUqdOrUCSkpKQgICMBXX33V4n3taDh8BDB+/HhcvXoViYmJMv0X89itY/jiwhe4X1yzRrmumi7mO8/HjH4zXng96FHpI5xJPVNr5OFu6Y5XjF9psZHH04gIMTkxCEoPQnFlMQDAWt8anlae6NJJNg9UFlYU4uDFg5j3+jwsnL+w3mesGrqGtXv3bsyYMQMRERFYtmwZoqKiQESwsbHB9OnTsWDBApn0uSPh8GllT56y3b9/P9555x2ZH08sFmPzxc3Yd2MfyqpqTpms9Kyw7rV1GGI+pE75+kYeLqYuGGYxDGpKajLpY2ZBJgJSApBdlA0A6KzeGZ7WnrDRt5HJBewqcRUu3b2E8xnnUSWpQti+MIT/Gl7r6XImexw+rai6uhr9+/eHrq4uzp8/36rzix4WP8SKcysQlB4ECUkgEokw1HwoNo7YCFMdU0hIUjPySAtCSVUJAMBG3wae1p4w0DCQSZ8KygtwNu0sYh/EAgBUFVUxzGIYXExdZDbh9FbuLZxJPYP88nwANRfmh5sNx4iBI9C/f38cP368xY/L6sfh04q+/fZbzJs3D5GRkXByEmaC6LXsa1h5biUSchMA1Mww97DygG1nW+SW5gIADDQM4GnlCZvOshkFVImrcOHOBVzIvIAqSRVEEMHR2BEjLEegk4pspizkFOfAP9kfGQUZAABtVW14WHmgd5feEIlEOHToECZPnowzZ85g1KiWu0PIGsbh00oeP34MGxsbjBs3Dv/73/+E7g5+vfErtl3aJp2aoKmiicHdBmNm/5lwNnWW2cgj7mEcAlMDUVBRsxxSd53u8LL2grGWcYsfDwBKKktwLv0cou5FSR8PcDN3w5BuQ6QTTp/0bdiwYXj06BGuX78OJaU2+YbhDoXDp5UsWLAAe/bsQVJSEoyMjITuDoCaV17868S/pK+86KbdDY7Gjlg0aNFLP0fzrHtF9+Cf4o/MgkwAgI6qDjysPNCrSy+ZnH6KJWJEZEUgNCNU+loSh64OGNVjFHTU6n8CPDo6Gk5OTvj666+l7+BmssPh0wri4uLQr18/+Pn5vXChwNYWdS8KB2IPIC0/DY/LHkNCEigpKMHTyhNzBsx56flZxZXFOJd+DtH3okEgKCsow83cDYO7Da418mhJyY+SEZASgEdljwAAxprG8LL2Qnfd7i+sO3v2bBw5cgTJycno3LmzTPrHanD4yBgRwdPTE+np6bh586Z0BYS2IupeFE4knoBdZzv0NeyL7Ve2I+lRzbQALRUtvN/vfUywn/DCJ4CfJZaIcSXrCkJvh6JCXAEA6NO1D0b2GNngyONltcRKGA8ePICNjQ3ef/997Ny5Uyb9ZDU4fGTs5MmTGDNmDI4fP44xY4R/9cWzng6fKX2mQCKRICA1AD9F/SS9HmSubY75LvMx0HTgC9sjIiQ/TsbplNPSkYeJlgm8rL1grtMyT3I/q7y6HCG3QxCRFSGdcDrIbBCGdh/apAmnT3z55ZdYtmwZYmJi4ODgIIMeM4DDR6YqKirg4OAAS0tLnD59uk2+uvPZ8HmivLocu6N341jCMemrMlxMXTDfZX6D78t5WPIQASkBSM1LBVBzEdvdsmbkIasJp1H3onAu/RxKq0oBAHad7eBh5YHOGs0/ZaqsrISDgwPMzc0RGBjYJv+/dQR8SV+Gvv76a6Snp+PPP/9sd3+A1ZTU4DPQB+Ptx+PrK1/j4p2LuJJ1BVF/RmGM3Rh88MoH0FCpeTq7rKoMIbdDEJkdKR15uHZzxavmrzZr5NEYslwJQ0VFBf/973/x5ptv4sSJExg7duxLt8nq4pGPjOTk5MDW1hYzZszA119/LXR3GtTQyKe+cl9f+Rq3828DqHk96oz+M2CsaYyQjBDpyKOnQU94WHlAX11fJv3NK8tDYFog4h/GA5DdShhEhNdffx1JSUmIj49vc9fqOgIOHxn5+OOPsXfvXiQnJ0NfXzZ/EVtCY8MHqHkNxbGEY9h3fZ/0OR1dNV1Y61ujh14PeFl7oYdeD5n0U4iVMG7duoW+ffvi66+/ho+Pj0yOIc/4tEtG1q1bh8mTJ7fp4GkqBQUFvN3rbXhae+Kry1/hXPo55Jfno7CiEGbaZjDs1PIrUzxZCSMwNVD6riJZr4TxhL29PYKCguDq6irT48grDh8Z0dTUhIuLi9DdkAlNFU3Md56P4spi3M67DRMtE0Tdi0L8w3gMtxiOgSYDW+QUKKswC/4p/tK3NOqp6cHT2hN2ne1a7Rrak5fEs5bH4cOaTU1JDQ6GDni/7/sISAnAveJ7CEgJwNXsqy81N6yooghn085K30ekoqiCod2HYpDZIJmshMGEwf8n2UvrrtsdHzp9KJ0Vn1uai99if2vyrPhqSTUu3bmE85nnW20lDCYcDh/WIhRECnA0dkSvLr0QlhGGK3evIPlxMlIjU1/4PiAhVsJgwuPwYS3qySs6nIydcDr1NJIeJeHS3Uu4fv96vW9CfHYlDG1VbYzsMVJmK2GwtoPDh8lEZ43OeLfPu0h5nIKAlADklubiZNJJRGZHwsvaC107da2zEsaQbkMwxHyIzFbCYG0Lhw+TKWt9a/gM8KlZd+t2MHKKc7AnZk+tMr279MYoq1HQVdMVpI9MGBw+TOYUFRThYuaCPoZ9EJwejMjsSACAkoIS3u/7fqNedcE6npZfhoCxBmgoa+AN2zegrlSzBM7UPlM5eOQYhw9rdU/e08wXlOUbhw9jTBAcPowxQXD4MMYEweHDGBMEhw9jTBAcPowxQXD4MMYEweHDGBMEhw9jTBAcPowxQXD4MMYEweHDGBMEhw9jTBAcPowxQXD4MMYEweHDGBMEhw9jTBAcPowxQXD4MMYEweHDGBMEhw9jTBAcPowxQXD4MMYEweHDGBMEhw9jTBAcPowxQXD4MMYEweHDGBMEhw9jTBAcPowxQXD4MMYEweHDGBMEhw9jTBAcPowxQXD4MMYEweHDGBMEhw9jTBBKQneAtZzc3FyUlJQ0qc69B/eQn5OPhxUPkaGd0eh6heWFyM/Jh5KCEjIyGl8PAB7fe4z8snzczbwLUYGoSXV1dXWho6PTpDqsbRIREQndCdYyZs6ciT179gjdDZnasGEDVqxYIXQ3WAvg8OlA4uLikJ2dLXQ3ZMra2hqWlpZCd4O1AA4fxpgg+IIzY0wQHD6MMUFw+DDGBMHhwxgTBIePHCooKMDs2bNhbW0Ne3t73Lt3r1H1qqursWHDBri6usLR0RHTp09HYGCgzI7HOjYOHzk0d+5cxMbGYvPmzcjIyEBZWRkA4KOPPsLOnTsbrLd8+XJ8++23cHd3x7hx41BRUYE333wTM2fOxPNumjb3eKyDIyZ39PX1KSoqioiINDU1KTU1lYiI/P39acCAAQ3WMzY2ptDQ0Frb0tLSqFevXrR58+YWPx7r2HjkI4eICFpaWnW229jYIDk5ucF6JSUlMDMzq7XN0tISO3bswK5du1r8eKxj4/CRQ97e3vjtt9/qbC8pKYFI1PBcKzc3N+zdu7fOdktLy+c+Wd3c47GOjSeWyiE/Pz8MGDAAQM2oRCQSoby8HOvXr4ejo2OD9TZt2oQhQ4YgLy8P8+fPh42NDaqqqrBjxw706tWrxY/HOjhhz/qYUJKTk8nDw4NEIhEZGBiQqqoqdenShSIjI59bLyoqigYMGEAikYhUVVVJSUmJDAwMKDw8XCbHYx0Xz+2Sc5mZmbh+/TqUlZXh4uICPT29RtVLTExEXFwctLS04OLiAm1tbZkej3U8HD6MMUHwBWc5k5ubi82bN2P8+PFwdXWFq6srxo8fjy1btuDhw4fNavPOnTuYNWtWvfvKysoQHh6O+Pj4OvvKy8uxb9++Zh2TtX888pEjkZGR8PT0hIaGBkaOHAlDQ0MAwP379xEUFITS0lKcPn1aenG4sa5fvw5HR0eIxeJa25OSkuDh4YHMzEyIRCK4ubnhwIEDMDY2lh7XxMSkTj0mHzh85MigQYPQr18/fP/993VucRMR5syZgxs3buDSpUu19p04ceK57aalpeGTTz6pEyLjx49HVVUV9uzZg/z8fCxatAjx8fEICQmBubk5h4+c4/CRI+rq6oiOjkbPnj3r3Z+QkIBXXnlFOv3hCQUFBYhEoudOoRCJRHVCxNDQEGfPnkWfPn0A1AScr68vTp06heDgYHTq1InDR47xNR85YmRkhIiIiAb3R0RESE/FnmZsbIyjR49CIpHU+xMVFVVve2VlZVBS+udRMpFIhO+++w6jR4/GsGHDkJSU9PJfirVb/JChHFm8eDFmz56Na9euwd3dvc41nx9//BFbt26tU8/JyQnXrl3D2LFj6223oVFRz549cfXqVdjb29fa/mQy6ZgxY172K7H2TIiHi5hwDhw4QC4uLqSkpEQikYhEIhEpKSmRi4sLHTx4sN46YWFh5O/v32CbxcXFFBISUmf7xo0bydvbu8F6Pj4+JBKJmv4lWIfA13zkVFVVFXJzcwEABgYGUFZWFrhHTN5w+DDGBMEXnBljguDwYYwJgsOHMSYIDh858+DBg3pvpwPA9u3bG3wpWGvXY3JA2JttrLXFx8eTkZER+fr61tq+ePFiMjAwoJiYmDZRj3V8HD5yKCEhgUxNTWnmzJkkFotp/vz5ZGhoSNevX29T9VjHxrfa5VRqairc3d2hrKyM0tJSnD17ts6TyG2hHuu4+JqPnLKysoKrqytSU1MxcOBA2NnZtcl6rOPi8JFDRIT33nsPly9fRmhoKBITEzFp0iRUV1e3qXqsgxP0pI+1uqqqKpo4cSJZW1tTZmYmERHl5OSQg4MDjR49mioqKtpEPdbx8chHzkRERCA5ORnnz59Ht27dANS8dyc4OBg5OTk4f/58m6jHOj6+4CyH6P/XzmrsdqHqsY6Nw4cxJgg+7WKMCYLDhzEmCA4fxpggOHwYY4Lg8JEzzV1BtLXrMTkg1ANGrPUlJiZS9+7dSSQSkYKCAg0dOpSys7Ol+3NyckhBQUHwekw+8MhHjixbtgwODg548OABEhMToaWlhSFDhiAzM7NN1WNyQuj0Y62na9eudOPGDelniURCc+bMIXNzc0pNTW1wJNLa9Zh84JGPHGnuCqKtXY/JB16xVI40dwXR1q7H5AOPfOTI+PHjsX///nr37dy5E1OmTKl32ePWrsfkA8/tYowJgkc+cubWrVvYvXs3EhISAAAJCQnw8fHBrFmzcO7cuTZTj8kBQS93s1bl7+9PKioqpK+vT2pqauTv709dunShkSNH0ogRI0hRUZGCgoIEr8fkA4ePHHF1daWVK1cSEdH+/ftJT0+PVqxYId2/fPlyGjVqlOD1mHzg8JEj2tralJycTEREYrGYlJSUKCoqSro/NjaWDA0NBa/H5ANf85EzT94cqKCgADU1Nejo6Ej3aWlpoaCgoE3UYx0fh48csbCwQHJysvTzpUuXYG5uLv2cmZkJY2Njwesx+cAPGcoRHx8fiMVi6WcHB4da+/39/TFixAjB6zH5wM/5MMYEwaddjDFBcPgwxgTB4cMYEwSHD2NMEBw+jDFBcPgwxgTB4cMYEwSHD2NMEBw+jDFBcPgwxgTB4cMYEwSHD2NMEBw+jDFBcPgwxgTB4cMYEwSHD2Os0YqLi7F69Wp4eXlBX18fIpEIe/bsaVZbHD6MsUbLzc3FunXrcOvWLfTr1++l2uLXqDLGGs3Y2Bj37t2DkZERrl69ioEDBza7LR75MCaArKwsfPDBBzAxMYGqqiosLS3h4+ODyspKAEBaWhomTpwIfX19aGhoYNCgQfj7779rtRESEgKRSIRDhw5hw4YNMDMzg5qaGtzd3ZGSkiItN2/ePGhqaqK0tLROP6ZMmQIjI6Na79p+HlVVVRgZGb3EN/8Hj3wYa2XZ2dlwdnZGfn4+Zs+ejZ49eyIrKwtHjhxBaWkp8vLyMHjwYJSWlmLBggXo3Lkz9u7dizFjxuDIkSMYP358rfa++OILKCgoYPHixSgoKMDmzZsxdepUXLlyBQAwefJkfPPNN/j7778xceJEab3S0lKcPHkSM2bMgKKiYqv+DgDwcsmMtbZp06aRgoICRUZG1tknkUho0aJFBIDOnz8v3V5UVESWlpZkYWFBYrGYiIiCg4MJANnb21NFRYW07Pbt2wkAxcbGSts0NTWlt99+u9axDh06RAAoLCysWd8jMjKSANDu3bubVZ9PuxhrRRKJBH/++SdGjx6NAQMG1NkvEolw6tQpODs7w83NTbpdU1MTs2fPxu3btxEfH1+rzsyZM6GioiL9/OqrrwKoOXV70ubEiRNx6tQpFBcXS8sdPHgQpqamtY7Tmjh8GGtFDx8+RGFhYZ01zJ6WkZEBOzu7Otvt7e2l+5/29EKMAKCnpwcAyMvLk26bPHkyysrKcOLECQA1t8xPnTqFiRMnSleVbW0cPoy1cw1dr6GnluQbNGgQLCwscOjQIQDAyZMnUVZWhsmTJ7dKH+vD4cNYK+rSpQu0tbVx8+bNBst0794diYmJdbYnJCRI9zfHpEmTEBAQgMLCQhw8eBAWFhYYNGhQs9pqCRw+jLUiBQUFjBs3DidPnsTVq1fr7CcivP7664iIiMClS5ek20tKSrBr1y5YWFigV69ezTr25MmTUVFRgb179yIgIACTJk1q9vdoCXyrnbFWtnHjRpw5cwbDhg3D7NmzYW9vj3v37uHw4cMIDw/H8uXLsX//fnh7e2PBggXQ19fH3r17kZ6ejj/++AMKCs0bMzg6OsLa2horV65ERUVFs0+5du7cifz8fGRnZwOoOYW7e/cuAGD+/PnQ0dFpXEPNukfGGHspGRkZNG3aNOrSpQupqqpSjx49aO7cudJb5qmpqTRhwgTS1dUlNTU1cnZ2pr/++qtWG09utR8+fLjW9vT09AZvga9cuZIAkLW1dbP73r17dwJQ7096enqj2xERPXVVijHGWglf82GMCYKv+TDGUFxcXOsBxPp06dKlRadhcPgwxrB161asXbv2uWXS09NhYWHRYsfkaz6MMaSlpUmnYzTEzc0NampqLXZMDh/GmCD4gjNjTBAcPowxQXD4MMYEweHDGBMEhw9jTBAcPowxQXD4dBBFRUWQSCRCd0PmKioqUF5eLnQ3WAvg8OkgfH19MWbMGKG7IVNVVVVwdHTEli1bhO4KawEcPh3ApUuX8Ouvv2LcuHFCd0WmlJWV8cYbb8DPzw937twRujvsJfETzu2cRCLBoEGDUF1djcjISGHWX2pFhYWFsLGxwciRI/Hbb78J3R32Enjk08798ssviIyMxPbt2zt88ACAtrY2/Pz88Pvvv+PixYtNqvvNN9/AwsICampqcHFxQUREhIx6yRqDRz7tWFFREezs7PDqq6/i4MGDQnen1UgkEjg7O0MkEuHKlSuNeq3owYMHMW3aNHz//fdwcXHBV199hcOHDyMxMRFdu3ZthV6zOpr9LkUmuE8//ZTU1NTo9u3bQnel1Z0/f75Jq2U6OzvT3LlzpZ/FYjGZmJiQn5+fdFtGRgZNmTKFdHV1SU9Pj9599116/PhxS3ed/T8+7Wqn0tLS8OWXX2Lp0qXNXkqlPXNzc8M777yD5cuXo7Cw8LllKysrce3aNYwcOVK6TUFBASNHjpSuEJGSkgInJydYW1vj8uXLCAwMREpKCpYsWSLT7yHXhE4/1jzjx48nMzMzKi4uFrorgsnIyCB1dXVatmzZc8tlZWURALp48WKt7UuWLCFnZ2ciIho1ahR9/vnntfYfOXKELC0tW7bTTIpHPu3QuXPncOzYMWzevBmdOnUSujuCMTc3x7Jly/Df//4XqampzW4nIyMDgYGB2LJlCzQ1NaU/7733HpSU+GWfssIXnNuZ6upqODo6QktLC+Hh4YKts91WlJaWomfPnnBycsKxY8fqLVNZWQkNDQ0cOXKk1rNQ06dPR35+Pj744APMnDkTV65cqVNXXV0dpqamsuq+XONYb2d+/PFHxMbGIjIyUu6DBwA0NDSwefNmTJkyBWfPnq11XecJFRUVODk5ISgoSBo+EokEQUFBmDdvHpSVlVFUVAQTExNoaGi08jeQY0Kf97HGe/ToEXXu3JlmzpwpdFfaFIlEQoNcB5F9L3uqqqqqt8yBAwdIVVWV9uzZQ/Hx8TR79mzS1dWlnJwc6e/17bffppiYGEpOTiZ/f39auHBh634ROcPh044sWLCANDU16d69e0J3pc2orK6kkPQQ8tnlQyKRiHbu3Nlg2R07dpC5uTmpqKiQs7MzXb58WbrvypUrNHz4cNLW1iYtLS1ydHSk7du3t8ZXkFt8zaediI+PR9++fbFx40YsXbpU6O4IjogQ/zAeZ1LPoKCiAABwbvs5xIXFITk5Gfr6+gL3kL0Ih087QETw8vJCamoq4uLioKqqKnSXBHWv6B4CUgKQUZABANBR1cEoq1EwkBjA1tYWM2bMwNdffy1wL9mLtMlb7X5+fhg4cCC0tLTQtWtXjBs3DomJidL9YrEYq1atgqWlJdTV1WFlZYX169fj6RxtTJmWIus5Q3///TfOnDmDL7/8Uq6Dp6SyBCcTT2LXtV3IKMiAsoIyhlsMxzzneXDo6gAjIyOsWrUK3377LeLi4hrdblhYGEaPHg0TExOIRCL8+eefsvsS7B/CnfE1zNPTk3bv3k03b96kmJgYev3118nc3Fz6QN2GDRuoc+fO9Ndff1F6ejodPnyYNDU1a52jN6bMs8LDw6mysrLO9ri4OMrJyam3zoEDB0hFRYV+/vlniouLow8//JB0dXXp/v37L/lbqFFRUUHW1tY0cuRIkkgkLdJme1MtrqYLmRdoY9hGWh28mlYHr6YjcUcovyy/Ttny8nKytramUaNGNfr3derUKVq5ciUdPXqUANCxY8da+Buw+rTJ8HnWgwcPCACFhoYSEdEbb7xBs2bNqlXmrbfeoqlTp0o/N6bM08RiMfXr148mTJhA1dXV0u0JCQlkaGhImzZtqrdeY+YMETV/3tCWLVtIUVGRYmNjX1i2o5FIJJSYm0hfX/5aGjrfR35PGfkZz6134sQJAkDHjx9v8jEbCp/Y2Fjy9vYmLS0tMjQ0pI8//pgqKiqa3D77R5s87XpWQUHNBcUnFxEHDx6MoKAgJCUlAQCuX7+O8PBweHt7S+s0pszTFBQUcOrUKURHR2PatGmQSCRITU3FiBEjMG7cuHov8jZmzhDQ/HlD9+/fx/r16zFnzhw4ODg05lfVYTwseYjfYn/D77G/41HZI3RS7oSxdmMx22k2zHXMn1v3zTffhIeHBz755BNUVFS8dF+io6MxePBgODo6IioqCgcOHMD+/fuxadOml25brgmdfi8iFovpjTfeoCFDhtTatmzZMhKJRKSkpEQikYg2btxYp96LytQnIyODzM3NafLkyWRubk7Tpk1rcPjemDlDRM2fN/Svf/2L9PT0KDc394X97ihKK0vJP9mf1oaspdXBq2ldyDo6k3KGyqvKm9ROXFwcKSoq0ubNm5tUD/WMfJycnMjX17fWthUrVtT6f8yars2Hz5w5c6h79+50584d6bb9+/eTmZkZ7d+/n27cuEH79u0jfX192rNnT5PKNCQ0NJQAUI8ePRp8aI2oceFz+/ZtAkDq6urUqVMn6Y+amhrZ2Ng02Pa1a9dIJBLRjh07XtjfjkAsEVNkViRtCt8kPcX6/cbvlFvS/OCdP38+aWlpNem5qGfD59atWwSAbt26VavcmjVrqF+/fs3uG2vj4TN37lwyMzOjtLS0WtvNzMzqPEy2fv16srOza1KZ+uTk5JCdnR2NHj2ajIyMaN68eQ2WraioIEVFxTr/Uk6bNo3GjBlDRETHjx8nfX19Sk5OrvNz9+7detuVSCTk5uZGvXv3fm74dRRpj9Po24hvpaGz88pOSnmU8tLtPnr0iPT19etc+3ueZ8PnyJEjpKysTGKxuFa5SZMm0XvvvffSfZRnbXJuFxFh/vz5OHbsGEJCQmBpaVlrf2lpaZ231ykqKtZaOqYxZZ6Vm5sLd3d32Nvb4/Dhw0hKSsLw4cOhqqqKrVu31in/ojlDAJo1b+jQoUMIDw9HYGBgh55VnVeWhzOpZ3Ar9xYAQF1JHa9ZvoYBJgOgIHr5y5H6+vpYv3495s2bB19fXzg5OTW5DS0tLYjFYlRVVUkfc0hPT8exY8dw4sSJl+6jXBM6/erj4+NDOjo6FBISQvfu3ZP+lJaWEhHR9OnTydTUVHob/ejRo2RgYEBLly6VttGYMk8Ti8U0YMAAev3112vdxYiJiSF9fX3atm1bvfWeN2eIiJo8b6ikpITMzc2lI6eOqKK6gs6mnqX1oetpdfBqWhO8hv5O+ptKKkta/FhVVVXk4OBAQ4YMafDaXVFREUVHR1N0dDQBoG3btlF0dDRlZGRQfn4+6evr06JFiyg1NZWCgoLI3t6e3n///Rbvq7xpk+EDoN6fJ6/MLCwspIULF5K5uTmpqalRjx49aOXKlbVCozFlnnXmzBkqKyursz0qKqrWNadnPW/OEFHT5g2tXbuWRIoimvDdBHpQ9OB5v6Z2RyKRUMy9GNp6Yav0FGtvzF7KKar/GaqWUFheSGt/XksAaP/+/fWWCQ4OrvfP2/Tp04mIKCwsjBwdHaV/jvz8/Go9jsGah6dXtCF37tyBja0NVAerQm+0HtSV1PF+v/exdPBSKCm279Ovu4V34Z/sj6yiLACAnpoePK09YdfZTiavBqmWVOPy3csIywhDpbgSh1YdQuHtQiQlJvFrM9oIDp825N1338W5c+ew+vBq/BD3A/LL8wEAXTt1xadun2K8/XhhO9gMhRWFCEoLwvX71wEAKooqGNp9KAaZDYKSQssHKhEhITcBZ1LPIK88DwBgpm2Gngo94e7ijk8//RRr1qxp8eOypuPwaSMuXLgANzc3/Pzzz5g5cybKKsuwJmwNjsYfRZWkCgDQx7AP/Nz94NC17T9wWC2pxqU7l3A+8zwqxZUAgFeMXsEIyxHQUtWSyTHvF99HQEoA0vPTAQBaKloYZTUKfbr2gUgkwqeffoqvvvoKiYmJMDd//oOKT/j5+eHo0aNISEiAuro6Bg8ejE2bNsHOzk4m30GecPi0AU/WoQKAiIiIWnfp0vPSsfzsckRkRYBAUFJQgpe1Fza8tgE66jpCdblBRIRbubdwJvWMdOTWTbsbvKy9YKotm9eRllaVIjg9GFezr0p/R4O7DYabuRtUFFWk5YqKimBra4vhw4dj//79jWrby8sL77zzDgYOHIjq6mqsWLECN2/eRHx8vFy/P7slcPi0Abt378asWbMQHh6OIUOG1FsmMDUQ60LX4U5hzRrlWipa+MDxA8wfOL/NrFSaU5yDgJQA3M6/DQDQVtXGqB6j4NDVQSbXdcQSMa5mX0Xw7WCUV5cDAHp16YVRPUZBT12v3jp79+7FjBkzEBYWhldffbXJx3z48CG6du2K0NBQDB06FACQmZmJ5cuXw9/fHyKRCN7e3ti5cyf09OrvA6vB4SOwwsJC2NraYsSIEfj999+fW1YsFuO7a9/hh6s/oKiyCEDN9YxVQ1fB09qzNbpbr5LKEgTfDsa17GvSkceQbkMwxHxIrZFHS0p5nILTKafxsPQhAMCwkyG8bbxhoWvx3Hovu7Z9SkoKbGxsEBsbCwcHB6SkpMDV1RU+Pj6YOnUqiouL4evriz59+uCnn35q7teTCxw+Alu2bBl27NiBxMREdOvWrVF1CsoK8HnI5/g7+W9US6ohgggDTAbgi5FfwErfSsY9/odYIkZkdiRCbodIRx69u/TGKKtR0FXTlckxH5U+wunU00h6VDNhWENZAyMsR8DR2LHRDyZeunQJgwcPxk8//YQPPvig0ceWSCQYM2YM8vPzER4eDgDw8PCAq6sr1q5dKy33xx9/YMmSJUhLS2vCN5M/HD4CSklJQe/evbFixQqsXr26yfVvPbyFT4M+RUxODABAWUEZY+3GYs1ra6CpotnCva0t+VEyTqeeRm5pLgDASNMI3tbe6K4rm9VTy6vLEZYRhit3r0BMYiiIFOBi6oJhFsOgpqTW5Pbee+89BAYGIjk5Gdra2o2q4+PjA39/f4SHh8PMzAwZGRmwsLCAurp6ret0YrEY3bp1k75RgdWPw0dA48aNQ1RUFBISEl7q2ZMTCSewMXwjcopzANS8VnSe8zzM6j+rxa8H5Zbm4nTKaSQ/TgYAdFLuhBGWI/CK8SstMiXiWRKSICYnBkFpQSipKgEA2OjbwNPaEwYaBs1u9+7du7Czs8PcuXOxefPmF5afN28ejh8/jrCwMOl0nxMnTvB6Xy+Bw0cggYGB8PDwwIEDBzB58uSXbk8sFmPrpa3Yc30PyqrKAAA9dHtg3Yh1cDN3e+n2y6vLEXo7FFeyrkBCEiiIFDDIbBCGdh/arJFHY2QWZMI/2R/3iu8BADqrd4aXtRdsOtu0SPv/+c9/sG7dOsTFxcHGpv426Zl5hk+X8/f3x9ixY5Gfn88PLjYDh48Aqqur0a9fP+jr6yMsLKxF7wQ9LH6IFedWICg9CBKSQCQSwa2bG75w/wKmOk3/l1hCEkTfi8a59HPSkYdtZ1t4WHm81MjjeQrKCxCYFoibD24CANSU1DCs+zA4mzpDUaHlRnJlZWWwt7dH3759G5wk6uvri99//x3Hjx+v9WyPjo4OysrKpLfuV61ahU6dOiElJQUBAQH46quvWqyfHRWHjwB27tyJBQsW4OrVq3B0dJTJMaKyo7Dy3ErpjHFVRVVMdpiMz4Z+1ug7ULfzbyMgJUB6OmegYQAvay9Y61vLpM9V4ipcuHMB4Znh0gvpjsaOGGE5Ap1UZPNMza59u/Dv6f/G6dOn4eHhUWd/Q/8w7N69GzNmzEBERASWLVuGqKgoEBFsbGwwffp0LFiwQCb97Ug4fFrZo0ePYGNjg7feeqtVbsX+Hvs7vrz0JR6VPgIAdNbojE9cP8G7fd5tsE5+eT4CUwMR97BmBQg1JTUMtxiOgSYDW3Tk8QQR4eaDmwhMC0RhRSEAoLtOd3jbeMNI06jFjwcAxZXFOJd+DlHZUdj70V6oVarhxvUbUFZWlsnxWF0cPq1s/vz52Lt3L5KTk2FoaNgqx6wUV2JD2AYcuHkAFeKadxr3NOiJDSM2wMnEqVa58MxwXLxzUTrycDJxwmsWr8ls5JFdlI2AlABkFmQCAHTVdOFh5QF7A3uZPZh4JesKQm+HSn8Xmo80sXTiUmzfvh3z589v8WOy+nH4tKKbN2+if//++OKLL7B48eJWP35WQRaWBy1H+J1wEBEURApwt3THxhEbca/kHs6mnZWOPCx0LeBt7Q1DTdkEZHFlMYLSghCTEwMCQVlBGa92fxWuZq5QVmz50QcRIelREs6knsGjsppRoImWCbytvdFNpxv+/e9/49ChQ0hOToaBgWyuZbHaOHxaCRFh1KhRyMzMxM2bN6GiIpsnfxvjQuYFrDq3Cmn5NQ/BqSiqoK9hX/Qy6AV9DX14Wnmip0FPmb3q4srdKwjLCJOOPPoa9sXIHiOhrdq4522a6mHJQwSkBCA1LxUAoKmiiZE9RqKfYT/pd3z48CFsbGwwdepUfPPNNzLpB6uNw6eVHD9+HOPGjcPJkyfx5ptvCt0diMVi7L6+G19e/BJl1TW35m0722L1sNUYYl7//LKX8WTkcTr1NB6XPQYAmGqZwsvaC910Gvdkd1OVVZUh5HYIIrMjISEJFEWKcO3milfNX4WqUt2VX7dt24YlS5YgJiYGffr0kUmf2D84fFpBRUUFevfuDSsrKwQEBMhkRNFc5zPOY9OFTbidf1t669zZ1BkLXBbATNusRY7xoOQBTqecfu7IoyVJSIJr2dcQfDsYpVWlAGqucXlYeUBfXb/BepWVlejTpw/MzMxw9uzZNvX/qSPi8GkFmzdvxooVK3Djxg306tVL6O7UEnUvCicST8CwkyHuFt3FxcyL0mswb9q+iQ8dP4SGSvMeoCurKkPw7ZpXXTwZeTx51UV9I4+WkJ6XDv8UfzwoeQCg5kVsXtZe6KHXo1H1T506hTfeeAPHjh2TLgrAZIPDR8ZycnJgY2ODWbNmYfv27UJ3p44n4WPX2Q5T+kxB9L1ofB3xNdLzal7Ipaemhxn9Z2C07eg6q4E0REKSmlddpAdLT+nsDezhYeXR4KsuXlZLroTx+uuvIzExEXFxcVBTk83T24zDR+ZmzZqFEydOIDk5uU2+3+XZ8AFqZm8fTzyOvdf3Sl8I1kOvBxa6LEQ/o37PbS8tLw0BKQG1Rh7e1t6w1LN8br3mqqiukD4e8GTC6QCTARhuMRways0bsSUkJKBPnz5Yv349li9f3sI9Zk9w+MjQ1atX4ezsjG+++QY+Pj5Cd6de9YXPE6WVpdgVtQt/J/2NKkkVRBDBzdwN853no6tm11plH5c9xpnUM0jITQBQM/IYYTkCTiZOMplwSkS4fv86zqadRXFlMYCagPSy9kLXTl1fUPvFPvroI/z0009ISkqCsbHxS7fH6uLwkREigpubGwoLCxEdHd1mF/97Xvg8kVmQie1XtuNa9jUANVM13u71Nmb0mwEC4XzmeVy6c0k68hhoMhDDLYZDXVldJn1+diUMffWaxwNsO9u22EXivLw82Nra4s0338Tu3btbpE1WG4ePjOzfvx/vvvsuzp49C3d3d6G706DGhM8Tl+5cws6IndK/9OrK6rDWs0Znjc4AACs9K3hZe6FLpy4y6WthRSHOpp3Fjfs3ANSE4NDuQ+Fi5iKTlTB++OEHzJkzBxERERg4cGCLty/v2uY/xx2AoqIiZs+e3aaDp6lcu7nC2dQZh+IOYe/1vSirKkPsg1h00+mGuQPnwsXURSa3p6vEVbh09xLOZ5yXnv71N+oP9x7uMn1p2r/+9S+cPn0apaWlMjuGPOORj5xrysjnaXcK7mBF0ArcLbqLV81fhZKCEpxNnTGs+7AWO91qaCUMbxtvmGiZtMgxmHB45MOaRVtVG1b6Vuim0w32BvZIfJSIy3cv48b9G01+p3J9WnslDNb6OHzYS9FQ1sCUPlOQ+jgVASkBeFj6EH8l/YXIrMhGrSbxrPpWwnAzd8PgboNlthIGEwaHD2sRVvpWmDNgDq5mX0XI7RDcL7mPPTF7XriO1hNiiRgRWREIzQiVroTh0NUBI3uMlNlKGExYHD6sxSgqKMLFzAV9DPvUTOjMikT8w3gkPUqqdwXRJ55dCcNY0xhe1l4yWwmDtQ0cPqzFaShr4HWb1+Fk7ITTqaeRlpeGsIwwRN+LxsgeI9HXsC9EIlG9K2G493BHf6P+MnkwkbUtHD5MZgw1DfF+3/eR+CgRp1NOI688D8cSjiE8Mxwayhq4U3hHOuHUxcxFpithsLaHw4fJlEgkQk+DnrDWt8blu5cRlhEmXeIYqHmHkKeVp/RBRSY/eGzLWsWTu1bznf95R/Kr5q/i3T7vcvDIKQ4f1qq0VLWkLy1rzXXlWdvD4cMYEwSHD2NMEBw+jDFBcPgwxgTB4cMYEwSHD2NMEBw+jDFBcPgwxgTB4cMYEwSHD2NMEBw+jDFBcPgwxgTB4cMYEwSHD2NMEBw+jDFBcPgwxgTB4cMYEwSHD2NMEBw+jDFBcPgwxgTB4cMYEwSHD2NMEBw+jDFBcPgwxgTB4cMYEwSHD2NMEBw+jDFBcPgwxgTB4cMYEwSHD2NMEBw+jDFBcPgwxgTB4cMYEwSHD2NMEBw+jDFBcPgwxgTB4cMYEwSHD2NMECIiIqE7wVrGt99+i5CQkCbVySvPQ3ZhNrRUtWCuY97oelXiKiQ9SoJIJEKvLr2adMyUxymoqK6AhZ4FOil3alLdd955B2+99VaT6rC2SUnoDrCWU1ZWhoKCgibVEUEEUxVTgNDkuqYqpgCaXs9AwQBQAapLq1GAptWtqKhoUnnWdvHIhzEmCL7mwxgTBIcPY0wQHD6MMUFw+DDGBMHhwxgTBIePHCooKMDs2bNhbW0Ne3t73Lt3r1H1qqursWHDBri6usLR0RHTp09HYGCgzI7HOjYOHzk0d+5cxMbGYvPmzcjIyEBZWRkA4KOPPsLOnTsbrLd8+XJ8++23cHd3x7hx41BRUYE333wTM2fOxPOe2Gju8VgHR0zu6OvrU1RUFBERaWpqUmpqKhER+fv704ABAxqsZ2xsTKGhobW2paWlUa9evWjz5s0tfjzWsfHIRw4REbS0tOpst7GxQXJycoP1SkpKYGZmVmubpaUlduzYgV27drX48VjHxuEjh7y9vfHbb7/V2V5SUgKRSNRgPTc3N+zdu7fOdktLS2RnZ7f48VjHxnO75JCfnx8GDBgAoGZUIhKJUF5ejvXr18PR0bHBeps2bcKQIUOQl5eH+fPnw8bGBlVVVdixYwd69Wp4cmlzj8c6OGHP+phQkpOTycPDg0QiERkYGJCqqip16dKFIiMjn1svKiqKBgwYQCKRiFRVVUlJSYkMDAwoPDxcJsdjHRdPLJVzmZmZuH79OpSVleHi4gI9Pb1G1UtMTERcXBy0tLTg4uICbW1tmR6PdTwcPowxQfAFZzmTm5uLzZs3Y/z48XB1dYWrqyvGjx+PLVu24OHDh81q886dO5g1a1a9+8rKyhAeHo74+Pg6+8rLy7Fv375mHZO1fzzykSORkZHw9PSEhoYGRo4cCUNDQwDA/fv3ERQUhNLSUpw+fVp6cbixrl+/DkdHR4jF4lrbk5KS4OHhgczMTIhEIri5ueHAgQMwNjaWHtfExKROPSYfOHzkyKBBg9CvXz98//33dW5xExHmzJmDGzdu4NKlS7X2nThx4rntpqWl4ZNPPqkTIuPHj0dVVRX27NmD/Px8LFq0CPHx8QgJCYG5uTmHj5zj8JEj6urqiI6ORs+ePevdn5CQgFdeeUU6/eEJBQUFiESi506hEIlEdULE0NAQZ8+eRZ8+fQDUBJyvry9OnTqF4OBgdOrUicNHjvE1HzliZGSEiIiIBvdHRERIT8WeZmxsjKNHj0IikdT7ExUVVW97ZWVlUFL651EykUiE7777DqNHj8awYcOQlJT08l+KtVv8kKEcWbx4MWbPno1r167B3d29zjWfH3/8EVu3bq1Tz8nJCdeuXcPYsWPrbbehUVHPnj1x9epV2Nvb19r+ZDLpmDFjXvYrsfZMiIeLmHAOHDhALi4upKSkRCKRiEQiESkpKZGLiwsdPHiw3jphYWHk7+/fYJvFxcUUEhJSZ/vGjRvJ29u7wXo+Pj4kEoma/iVYh8DXfORUVVUVcnNzAQAGBgZQVlYWuEdM3nD4MMYEwRecGWOC4PBhjAmCw4cxJggOHznz4MGDem+nA8D27dsbfClYa9djckDYm22stcXHx5ORkRH5+vrW2r548WIyMDCgmJiYNlGPdXwcPnIoISGBTE1NaebMmSQWi2n+/PlkaGhI169fb1P1WMfGt9rlVGpqKtzd3aGsrIzS0lKcPXu2zpPIbaEe67j4mo+csrKygqurK1JTUzFw4EDY2dm1yXqs4+LwkUNEhPfeew+XL19GaGgoEhMTMWnSJFRXV7epeqyDE/Skj7W6qqoqmjhxIllbW1NmZiYREeXk5JCDgwONHj2aKioq2kQ91vHxyEfOREREIDk5GefPn0e3bt0A1Lx3Jzg4GDk5OTh//nybqMc6Pr7gLIfo/9fOaux2oeqxjo3DhzEmCD7tYowJgsOHMSYIDh/GmCA4fBhjguDwkTPNXUG0tesxOSDUA0as9SUmJlL37t1JJBKRgoICDR06lLKzs6X7c3JySEFBQfB6TD7wyEeOLFu2DA4ODnjw4AESExOhpaWFIUOGIDMzs03VY3JC6PRjradr165048YN6WeJREJz5swhc3NzSk1NbXAk0tr1mHzgkY8cae4Koq1dj8kHXrFUjjR3BdHWrsfkA4985Mj48eOxf//+evft3LkTU6ZMqXfZ49aux+QDz+1ijAmCRz5y5tatW9i9ezcSEhIAAAkJCfDx8cGsWbNw7ty5NlOPyQFBL3ezVuXv708qKiqkr69Pampq5O/vT126dKGRI0fSiBEjSFFRkYKCggSvx+QDh48ccXV1pZUrVxIR0f79+0lPT49WrFgh3b98+XIaNWqU4PWYfODwkSPa2tqUnJxMRERisZiUlJQoKipKuj82NpYMDQ0Fr8fkA1/zkTNP3hyooKAANTU16OjoSPdpaWmhoKCgTdRjHR+HjxyxsLBAcnKy9POlS5dgbm4u/ZyZmQljY2PB6zH5wA8ZyhEfHx+IxWLpZwcHh1r7/f39MWLECMHrMfnAz/kwxgTBp12MMUFw+DDGBMHhwxgTBIcPY0wQHD6MMUFw+DDGBMHhwxgTBIcPY0wQHD6MMUFw+DDGBMHhwxgTBIcPY0wQHD6MMUFw+DDGBMHhwxgTBIcPY6zRIiMjMW/ePPTu3RudOnWCubk5Jk2a1Kylr/llYoyxRpswYQIuXLiAiRMnom/fvsjJycHOnTtRXFyMy5cv13lb5fNw+DDGGu3ixYsYMGAAVFRUpNuSk5PRp08fTJgwAb/++muj2+LTLsYEkJWVhQ8++AAmJiZQVVWFpaUlfHx8UFlZCQBIS0vDxIkToa+vDw0NDQwaNAh///13rTZCQkIgEolw6NAhbNiwAWZmZlBTU4O7uztSUlKk5ebNmwdNTU2UlpbW6ceUKVNgZGRU613bzzN48OBawQMANjY26N27N27dutWk3wG/QJ6xVpadnQ1nZ2fk5+dj9uzZ6NmzJ7KysnDkyBGUlpYiLy8PgwcPRmlpKRYsWIDOnTtj7969GDNmDI4cOYLx48fXau+LL76AgoICFi9ejIKCAmzevBlTp07FlStXAACTJ0/GN998g7///hsTJ06U1istLcXJkycxY8YMKCoqNvv7EBHu37+P3r17N7kiY6wVTZs2jRQUFCgyMrLOPolEQosWLSIAdP78een2oqIisrS0JAsLCxKLxUREFBwcTADI3t6eKioqpGW3b99OACg2NlbapqmpKb399tu1jnXo0CECQGFhYS/1fX755RcCQP/73/+aVI/Dh7FWJBaLSVtbm8aOHdtgGVtbW3J2dq6z3c/Pr1aoPAmfzZs31yoXFRVFAOj48ePSbYsWLSJ1dXUqKiqSbnv77bfJ1NSUJBJJs7/PrVu3SFtbm1xdXam6urpJdfmaD2Ot6OHDhygsLHzuXaGMjAzY2dnV2W5vby/d/7SnF2IEAD09PQBAXl6edNvkyZNRVlaGEydOAACKi4tx6tQpTJw4UbqqbFPl5OTgjTfegI6ODo4cOdLkUzcOH8bauYb+0tNTN7IHDRoECwsLHDp0CABw8uRJlJWVYfLkyc06ZkFBAby9vZGfn4+AgACYmJg0uQ0OH8ZaUZcuXaCtrY2bN282WKZ79+5ITEyssz0hIUG6vzkmTZqEgIAAFBYW4uDBg7CwsMCgQYOa3E55eTlGjx6NpKQk/PXXX+jVq1ez+sPhw1grUlBQwLhx43Dy5ElcvXq1zn4iwuuvv46IiAhcunRJur2kpAS7du2ChYVFs/+yT548GRUVFdi7dy8CAgIwadKkJrchFosxefJkXLp0CYcPH4arq2uz+gLwrXbGWt3GjRtx5swZDBs2DLNnz4a9vT3u3buHw4cPIzw8HMuXL8f+/fvh7e2NBQsWQF9fH3v37kV6ejr++OMPKCg0b8zg6OgIa2trrFy5EhUVFc065frkk09w4sQJjB49Go8fP67zUOF7773X+MaafZmbMdZsGRkZNG3aNOrSpQupqqpSjx49aO7cudJb5qmpqTRhwgTS1dUlNTU1cnZ2pr/++qtWG0/udh0+fLjW9vT0dAJAu3fvrnPclStXEgCytrZuVr+HDRtGABr8aQqeXsEYEwRf82GMCYKv+TDGUFxcjOLi4ueW6dKly0tNw3gWhw9jDFu3bsXatWufWyY9PR0WFhYtdky+5sMYQ1paGtLS0p5bxs3NDWpqai12TA4fxpgg+IIzY0wQHD6MMUFw+DDGBMHhwxgTBIcPY0wQHD6MMUFw+HQQf/zxBw4ePCh0N2RKLBZjx44dtV41wdovfs6nAygqKoKtrS2GDx+O/fv3C90dmZFIJHB2dgYARERENPvVEqxt4P97HcDGjRtRUFCATZs2Cd0VmVJQUMD27dtx7do17N27V+jusJfE4dPOpaamYtu2bVi6dGmdF4l3REOGDMGUKVPw6aeforCwsEl1v/nmG1hYWEBNTQ0uLi6IiIiQUS9ZozTrjUKszRg3bhyZmZlRSUmJ0F1pNZmZmaSurk5Lly5tdJ0DBw6QiooK/fzzzxQXF0cffvgh6erq0v3792XYU/Y8HD7t2NmzZwkA7d+/X+iutLq1a9eSsrIyJScnN6q8s7MzzZ07V/pZLBaTiYkJ+fn5SbdlZGTQlClTSFdXl/T09Ojdd9+lx48ft3jfWQ0+7WqnqqursWjRIgwZMqTZy5+0Z4sXL4axsTEWL178wrKVlZW4du0aRo4cKd2moKCAkSNHSu+cpaSkwMnJCdbW1rh8+TICAwORkpKCJUuWyOw7yDt+n087tWvXLsTFxSEyMrLZi761ZxoaGti8eTPeeecdBAYGYtSoUQ2Wzc3NhVgshqGhYa3thoaG0uVofH194evrW+udNkuXLuXwkSEe+bRDjx8/xqpVqzBz5kw4OTkJ3R3BTJo0CW5ubli0aBGqq6ub3U5GRgYCAwOxZcsWaGpqSn/ee+89KCnxv8+ywr/ZdmjNmjWoqqrChg0bhO6KoEQiEbZv344BAwbg+++/x7x58+otZ2BgAEVFRdy/f7/W9vv378PIyAjXr1+Hvr4+rly5Uqeuurq6TPrOeOTT7sTFxeHbb7/FqlWrYGRkJHR3BOfo6IgPPvgAn3/+OR49elRvGRUVFTg5OSEoKEi6TSKRICgoCK6urlBWVkZRURFMTExgbW1d68fU1LS1vor8EfqKN2s8iURCo0aNImtrayovLxe6O21GTk4OaWtr07x58xosc+DAAVJVVaU9e/ZQfHw8zZ49m3R1dSknJ4cePXpEnTt3prfffptiYmIoOTmZ/P39aeHCha33JeQQh087cuLECQJAx48fF7orbUpRRRHNXDqTFBUVKTY2tsFyO3bsIHNzc1JRUSFnZ2e6fPmydN+VK1do+PDhpK2tTVpaWuTo6Ejbt29vje7LLZ7b1U5UVFTAwcEBlpaWOH36tFze4XpWtaQaV+5eQVhGGErLS/H9B9+jr21fnA08y7+fdoAvOLcTX3/9NdLT0/Hnn3/K/V8sIkLSoyScTj2Nx2WPAQDd9Lth85bNmPXOLJw4cQJjx44VuJfsRdrkBWc/Pz8MHDgQWlpa6Nq1K8aNG4fExETpfrFYjFWrVsHS0hLq6uqwsrLC+vXr8fQgrjFlWoqs5wzdv38f69evh6+vL3r37t2ibbc3D0se4tcbv2L/zf14XPYYmiqaGNdzHD50/BAzJs2Ap6cnPvnkE1RUVDS6zbCwMIwePRomJiYQiUT4888/ZfcF2D8EPelrgKenJ+3evZtu3rxJMTEx9Prrr5O5uTkVFxcTEdGGDRuoc+fO9Ndff1F6ejodPnyYNDU1a52jN6bMs8LDw6mysrLO9ri4OMrJyam3TmvMGfrggw9IX1+fHj161GJttjellaV0KukUrQ1ZS6uDV9O6kHUUmBpI5VW1L7zHx8eToqIibdq0qdFtnzp1ilauXElHjx4lAHTs2LEW7j2rT5sMn2c9ePCAAFBoaCgREb3xxhs0a9asWmXeeustmjp1qvRzY8o8TSwWU79+/WjChAlUXV0t3Z6QkECGhoYN/mFuzJwhoubPG7p69SqJRCLauXPnC8t2RGKJmCLuRtAX57+g1cGraXXwatofu58elTYcxAsXLiRNTU26d+9ek4/XUPjExsaSt7c3aWlpkaGhIX388cdUUVHR5PbZP9rkadezCgoKAAD6+voAgMGDByMoKAhJSUkAgOvXryM8PBze3t7SOo0p8zQFBQWcOnUK0dHRmDZtGiQSCVJTUzFixAiMGzcOS5curVOnMXOGgObPGyIiLFy4EL169cK///3vxvyqOpS0vDR8f/V7/J38N8qqy9C1U1dM6zcN7zi8A311/QbrrV69GqqqqlixYkWL9CM6OhqDBw+Go6MjoqKicODAAezfv7/Dvz9J5oROvxcRi8X0xhtv0JAhQ2ptW7ZsGYlEIlJSUiKRSEQbN26sU+9FZeqTkZFB5ubmNHnyZDI3N6dp06aRRCKpt2xWVhYBoIsXL9bavmTJEnJ2dpZ+HjVqFH3++ee1yhw5coQsLS2f25f9+/cTADp79uwL+92RPC59TAdiD0hHOl+c/4Ii7kaQWCJudBvffvstAaDIyMgmHRv1jHycnJzI19e31rYVK1bU+n/Mmq7Nh8+cOXOoe/fudOfOHem2/fv3k5mZGe3fv59u3LhB+/btI319fdqzZ0+TyjQkNDSUAFCPHj2oqqqqwXKNCZ/bt28TAFJXV6dOnTpJf9TU1MjGxqbBtktKSqhbt240bty4F/a3oyivKqfA1EBaF7KOVgevprUha+lU0ikqrSxtcltVVVXUp08fGjx4cIP/eNTn2fC5desWAaBbt27VKrdmzRrq169fk/vF/tGmw2fu3LlkZmZGaWlptbabmZnVuQayfv16srOza1KZ+uTk5JCdnR2NHj2ajIyMnvvUbEVFBSkqKtb5l3LatGk0ZswYIiI6fvw46evrU3Jycp2fu3fvNtj2mjVrSEVFhVJSUp7b345AIpFQ9L1o2nJhi3S0sy9mH90vfrmL9kFBQQSAfv/990bXeTZ8jhw5QsrKyiQW1x51TZo0id57772X6p+8a5PP+RAR5s+fj2PHjiEkJASWlpa19peWltZ5ebiioiIkEkmTyjwrNzcX7u7usLe3x+HDh5GUlIThw4dDVVUVW7durVP+6TlD48aNA/DPnKEnkxyfnjekoaHRqO+fmZmJTZs24aOPPoKVlVWj6rRXdwruICAlAFlFWQAAfXV9eFp5wraz7Us/zzRixAi89dZbWLp0KcaMGYNOnTo1uQ0tLS2IxWJUVVVBVVUVAJCeno5jx47hxIkTL9U/uSd0+tXHx8eHdHR0KCQkhO7duyf9KS2tGX5Pnz6dTE1NpbfRjx49SgYGBrVeq9mYMk8Ti8U0YMAAev3112vdxYiJiSF9fX3atm1bvfWeN2eIiJo1b+idd94hIyMjKiwsbOqvrt0oKC+gP+L/kI50NoZtpPCMcKoSN3ya2xypqamkqqpa55rb04qKiig6Opqio6MJAG3bto2io6MpIyOD8vPzSV9fnxYtWkSpqakUFBRE9vb29P7777doP+VRmwwfAPX+7N69m4iICgsLaeHChWRubk5qamrUo0cPWrlyZa3QaEyZZ505c4bKysrqbI+Kiqp1zelZz5szRNS0eUPnz58nALTuq3XP+xW1W5XVlRR6O5T+E/ofWh28mtYEr6E/b/1JRRVFMjvm/E/mk6qaKt2+fbve/cHBwfX+eZs+fToREYWFhZGjo6P0z5Gfn1+txzFY8/DcrjZEIpGgn2M/JOclw2SRCbxsvfCfEf957m3l9oKIEP8wHoFpgcgvzwcAmOuYw8vaCyZaJjI5ZkllCYJvB+NiykV8M/0beI3w6vALK7YnHD5tyM8//4wPPvgA/Vf0R16XPACApoomZvafiUUui6CoqChwD5snpzgH/sn+yCjIAABoq2rDw8oDvbv0lsk8NbFEjIisCIRmhKK8uhwAkHsxF9+s/AZhYWF49dVXW/yYrOk4fNqIwsJC2NjYYOTIkdi3bx92XduFb69+i6LKIgCAqZYpVg5diddtXhe4p41XUlmCc+nnEHUvCgSCsoIyhpgPwZBuQ6CsqCyTYyY/Ssbp1NPILc0FABhrGsPL2gvdtLvB1dUVVVVViIyMbLdB3pFw+LQRS5cuxTfffIPExESYmZkBAIori/HZuc/wd9LfqJJUAQCcjJ3g5+4HWwNbIbv7XE9GHiG3Q1Ahrpng6dDVAaN6jIKOmo5MjplbmovTKaeR/DgZANBJuRPce7ijv1F/KIhq7npevnwZrq6u+PHHH/Gvf/2rUe36+fnh6NGjSEhIgLq6OgYPHoxNmzbBzs5OJt9DnnD4tAHJycno3bs3Vq1ahVWrVtXZn5SbhGVnlyE6JxoAoKygjNF2o7HutXXQVNFs7e4+V/KjZASkBOBRWc0rTZ+MPLrrdpfJ8cqryxF6OxRXsq5AQhIoihThYuaCod2HQk1JrU75adOmISAgAMnJydDReXEQenl54Z133sHAgQNRXV2NFStW4ObNm4iPj2/WrXv2Dw6fNmDs2LGIiYmR/uvakL+S/sLGsI3ILs4GUHPtZO7AufjXK/8S/DQitzQXASkBSHmcAqD+kUdLkpAE0feiEZQehNKqUgCAbWdbeFp5orNG5wbrZWVlwdbWFr6+vtiyZUuTj/vw4UN07doVoaGhGDp0KICa57KWL18Of39/iEQieHt7Y+fOndDT02vel5MTHD4CO3PmDDw9PXHo0CFMnDjxheXFYjG+vPwl9sTskf6ls9C1wLrh6zDUYqisu1tHeXU5Qm6HICIrQjryGGQ2CEO7D4WqkqpMjnk7/zYCUgKQU5wDADDQMICXtRes9a0bVX/Dhg1Yu3Ytbt68CVvbpp2+pqSkwMbGBrGxsXBwcEBKSgpcXV3h4+ODqVOnori4GL6+vujTpw9++umnJn83ecLhI6Cqqir0798fnTt3RmhoaJPu/Dwue4wVQSsQmBoIMYkhggiDuw2G30g/mOuYy7DXNSQkQdS9KJxLPycNQbvOdvCw8njuyONl5JfnIzA1EHEP4wAAakpqGG4xHANNBkJRofEjv7KyMvTq1QsODg44efJko+tJJBKMGTMG+fn5CA8PBwB4eHjA1dW11mKDf/zxB5YsWYK0tLRGty2POHwEtGPHDixcuBDXrl3DK6+80qw2Yu7FYOW5ldK/kKqKqpjQawI+e/UzqKvIZs2p2/m34Z/sj/slNetgddHoAi9rL1jpy2YqSKW4EuGZ4bh45yKqJdUQQYQBJgPwmuVr0FBu3JSVZ/3xxx+YMGECAgIC4Onp2ag6Pj4+8Pf3R3h4OMzMzJCRkQELCwuoq6vXmsojFovRrVs36etcWP04fATy6NEj2NjYYMKECdi1a9dLt3fw5kFsubhFeotZX10fH7t+jPf6vvfSbT+RV5aHwLRAxD+MB1Az8njN4jUMMBnQpJFHYxHR/7V353FVlfkfwD+XHQERZAcRBATcQ2RRcwFUsDQdtxodTZtUXErN0tFMyy21KU1LW2bUmtLSNLUAF2R1ARUElH1XFgFlXy/3fn9/8PMWAQrI5SD3+369+OOec57lon58zvKcB3EFcbiUfglltWUAAOte1vC29YaxtvFTSj+9bg8PDzx48AAxMTFQVX3yrf8VK1bgzJkzCA0Nlc01PHv2LBYuXNjiYoO85teTcfgIZMWKFfj++++RkpICIyOjDqmzTlKHnWE7cezOMdnDdfa97bHdYzuczZ2fqd6OHnk8TU5ZDgJSA3Cv7B4AoJdGL0yymQQHA4cOezAxJiYGTk5O+Oyzz/DWW281ewz9ZZKznZ2dbJ+/vz9eeeUVlJSUtHrSMPsDh48A4uLiMGzYMOzZswdr1qzp8PpzSnOw4fIGhGaHgoigJFLCeKvx+NjzYxhqG7a6HiJC7INYXEq/JHvYsaNGHi0pry1HYEYgbuffBgCoKavhRcsX4d7HHSpKHf8ShiVLluDnn39GSkoKDAwMmuxftmwZfvzxR5w5c6bRsz26urqorq5G//79MW7cOGzatAlaWlpITU1FQEAA9u7d2+F97W44fDoZEcHLywv3799HXFwc1NTU5NbW1eyr2BS0CWnFaQAATVVNzB8yH++NfO+pt+bvl91HQGoA7pfdBwDoaehhku0k2Pe2l8uUiHppPa7fv47QrFDUSeoAAEONh8Krnxd01HU6vD0AKKgswM+RP+O9qe9hwbwFOHjwYJNjWvquhw8fxuuvv47IyEisW7cOUVFRICLY2dlhwYIFLY6k2B84fDrZr7/+iunTp+O3337DSy+9JPf2pFIpjtw+gs8jP5dN6DTWMsb60esx3XF6k+PLa8txKf0SYh7EAGgYeYzpOwZuFm5yGXkQEZIeJuF86nkU1zTMZ7PoaQFvW29Y9LTo8PYAoFpcjaDMINzMvQkpSRFxMgIXDl5AdHQ0hgwZIpc2WVMcPp2otrYWAwYMQP/+/eHn59epi/9V11VjS+gWnIo/JZuqMdh4MHZ67sQgo0Gol9bj2r1rCMsOk408hpkMg6e1p9xGHg8qHiAgNQAZJRkAAB01HXj188IQ4yFy+d1ISYqbuTcRlBGE6vpqAICjgSPGW47HWNexMDU1RWBgoMIvythZOHw60a5du/D+++8jNjYWjo6OgvQhozgD6y+tR2ROJAgEFSUVvGDyApxMnFArbZiHZdHTAj62PjDvKZ+7NVXiKgRlNIw8HvdhZJ+RGG05GmrK8jkNTS9OR0BqAAoqCwA0jP68bb1hrddw5yogIAA+Pj44deoUpk9vOiJkHY/Dp5Pk5eWhf//++Oc//4nPPvtM6O7gYtpFfBTykexukoaKBtws3LDKdRWGmgyV26sububeRHBmsGzkMcBwACb0mwA9TflMRXhU/QgX0i4gsSgRANBDtQfGW43HcLPhTaZ9vPTSS0hISEB8fDw0NJrOC2Mdi8OnkyxcuBDnzp1DSkpKl5nzI5FIsPbiWvin+qOmvgaWupaw0bfBSpeVcDJ16tC20h6lISA1AIVVhQAaRh4+dj6w6mXVoe08Vltfi7DsMFy7dw0SkkBJpIQRZiMwzmocNFWbf/gyKSkJgwYNwkcffYR//etfcukX+wOHTye4ceMGXFxccPDgQSxdulTo7jQSlReFk3dPIqc8B7nluRBLxQ1TNSxHYqXLSphomzxT/Q+rHuJC2gUkPUwC0DDy8LD2gJOpk1wmnBIRbuffRmBGICrqKgAANno28Lb1hqHW0x8zeOedd/DVV18hOTkZZmbyecMia8DhI2dEhFGjRqGyshJRUVGCzz7/q6i8KJxNOgv73vYYbTkan0d8jhu5NwA0TNWY7jgdC4ctbPMk0dr6WoRmheL6/euykYeLuQvG9h3b4sjjWd0rvQf/VH/kljfM+tfX1Ie3rTfs9O1afRpZUlKC/v37Y/LkyThy5Ihc+skacPjI2Y8//oi5c+fi8uXLGD9+vNDdaeLP4fPa4NcAABH3I7A/cr/sGR8DTQO84fQGJtlMarIc0V9JSdow8kgPRKW4EgBgq28Lb1tvGPRo+hBfRyitKcWl9EuIK4gD0BCaY63GwtXctV3TPr755hssXrwYERERcHFx6ejusv/H4SNHlZWVsLe3h5ubG06ePCl0d5rVXPgADc8HnYg/ge9jv5edvjj0dsDbbm/D0bD5O3XZpdnwT/FHXkUeAKC3Zu+GkUdvu2aPf1ZiiRhX711FeHa47HTxBdMX4GHt8UwvWZNIJBg+fDg0NDRw9erVpwYuax8OHzn64IMPsHv3biQkJDRZ+LCraCl8HiurKcOhW4dwPvW87PRpnNU4rBixAvo9GlbVKK0pxcX0i7hTcAdAw8hjnNU4uJi7yG3C6d3Cu7iYdhGltaUAGlbC8LH1gamOaYe0ERISgnHjxuH777/HvHkdNzmX/YHDR06ysrLg4OCANWvWYPv27UJ3p0VPC5/H0h6l4fOIz2VPPmuqaGLmgJno26svIu5HyEYeTqZO8LD2gJaafF4xmleeB/9Uf2SXZgMAdNV1MdFmIgYYDujwxwNmz56NK1euICkpCdraXet1td0Bh4+cvPPOOzh27BiSk5O79F/c1obPY8GZwTh085DsLYJaqlqw1bfFMJNh8Lb17rCRx19V1lUiMCMQ0XnRspUwRluOxsg+I+W2EkZmZiYcHR3x2Wefdbm7lN1Bl1yrvTvYtWsX3nzzzS4dPO0xzmocRluOxlc3v8LpxNOoFFdCLBVDQ0VDLq9NlUgliMiJQEhmiGwljMFGg+HVz0tuK2E8ZmVlhZs3b2LAgAFybUdRcfjIiYqKChwcHITuhlyoKKlg3pB5yC3Pxb2yezDRNkHSwySkPkqFex93vGj54jMHEREh5VEKzqeel62EYaZjBm9b7055TexjAwcO7LS2FA2HD2s3VWVV9O/dH0uGL0FAagDSitMQnh2O2/m34dXPC0ON2zdNo7CyEOfTzstWwtBW04andcNKGDzps/vg8GHPzFDLEPOGzEPyw2ScTzuPR9WP8Gvir4jMiYSPrQ/66PZpVT3V4mqEZIU0Wgmjo0ZSrOvh8GEdQiQSwd7AHjb6Ng3rpGeGILc8F/+J/g+GGA+BVz8v9FTv2WxZKUlxK/cWgjKDZCthOBg4YKLNROhr6nfm12CdiMOHdajHr8cYYjwElzMuIzovGrEPYpFQmNDs3amM4gwEpAZ02koYrOvg8GFyoa2mjan2UzHCbITsuZygzCBE50djQr8JMNMxw4W0C0goSgDQ8NzQeOuGlTDkMeGUdT0cPkyuTHVMsXDYQtkTySU1JTgRf0K2X0mkBGczZ4yzGie3lTBY18Thw+ROJBJhkNEg2Pe2x5V7VxCcGQyg4QHFBcMWwEirY5YOYs8XHt+yTqOqrIpxVuOgq97wcOCsgbM4eBQYhw/rdPKaDsGeLxw+jDFBcPgwxgTB4cMYEwSHD2NMEBw+jDFBcPgwxgTB4cMYEwSHD2NMEBw+jDFBcPgwxgTB4cMYEwSHD2NMEBw+jDFBcPgwxgTB4cMYEwSHD2NMEBw+jDFBcPgwxgTB4cMYEwSHD2NMEBw+jDFBcPgwxgTB4cMYEwSHD2NMEBw+jDFBcPgwxgTB4cMYEwSHD2NMEBw+jDFBcPgwxgTB4cMYEwSHD2NMEBw+jDFBcPgwxgTB4cMYEwSHD2NMEBw+jDFBqAjdAdZx0tPTUVRU1KYyiUWJyMnKgbKuMiKrI1tdrqK2AjkJOVBWUkakRuvLAUDm3UyU1pTituQ2CnQK2lTWwsICZmZmbSrDuiYREZHQnWAdY+HChThy5IjQ3ZCr7du3Y8OGDUJ3g3UADp9u5N69eyguLha6G3JlYmICIyMjobvBOgCHD2NMEHzBmTEmCA4fxpggOHwYY4Lg8GGMCYLDRwGVlpZi8eLFsLW1haOjI/Ly8lpVrr6+Htu3b4e7uzucnJywYMECXLx4UW7tse6Nw0cBLV++HHFxcdi9ezeysrJQXV0NAFi9ejUOHDjQYrn169fjyy+/hKenJ6ZNm4ba2lq8/PLLWLhwIZ5007S97bFujpjC0dfXp6ioKCIi0tbWprS0NCIi8vf3J2dn5xbLmZqaUkhISKNt6enpNGDAANq9e3eHt8e6Nx75KCAigo6OTpPtdnZ2SElJabFcZWUlLCwsGm2ztrbG/v378fXXX3d4e6x74/BRQD4+Pvjhhx+abK+srIRIJGqx3OjRo3H06NEm262trZGbm9vh7bHujSeWKqCdO3fC2dkZQMOoRCQSoaamBlu3boWTk1OL5Xbt2oVRo0ahuLgYK1euhJ2dHcRiMfbv348BAwZ0eHusmxP2rI8JJSUlhSZOnEgikYgMDAxIXV2dDA0N6caNG08sFxUVRc7OziQSiUhdXZ1UVFTIwMCAwsPD5dIe6754bpeCy87ORkxMDFRVVeHq6go9Pb1WlUtKSsLdu3eho6MDV1dX9OzZU67tse6Hw4cxJgi+4KxgioqKsHv3bkyfPh3u7u5wd3fH9OnTsWfPHhQWFrarznv37mHRokXN7quurkZ4eDji4+Ob7KupqcF3333XrjbZ849HPgrkxo0bmDRpEnr06AEvLy8YGxsDAB48eIDAwEBUVVXh/PnzsovDrRUTEwMnJydIJJJG25OTkzFx4kRkZ2dDJBJh9OjROH78OExNTWXtmpmZNSnHFAOHjwJxc3PD0KFDcejQoSa3uIkIS5cuRWxsLK5du9Zo39mzZ59Yb3p6Ot55550mITJ9+nSIxWIcOXIEJSUlWLVqFeLj4xEcHAxLS0sOHwXH4aNANDU1ER0dDQcHh2b3JyYm4oUXXpBNf3hMSUkJIpHoiVMoRCJRkxAxNjbGpUuXMHjwYAANAbds2TL4+fkhKCgIWlpaHD4KjK/5KBATExNERrb8svfIyEjZqdifmZqa4tSpU5BKpc3+REVFNVtfdXU1VFT+eJRMJBLh4MGDmDJlCsaOHYvk5ORn/1LsucUPGSqQtWvXYvHixbh16xY8PT2bXPP55ptv8MknnzQpN3z4cNy6dQuvvPJKs/W2NCpycHDAzZs34ejo2Gj748mkU6dOfdavxJ5nQjxcxIRz/PhxcnV1JRUVFRKJRCQSiUhFRYVcXV3pp59+arZMaGgo+fv7t1hnRUUFBQcHN9m+Y8cO8vHxabGcr68viUSitn8J1i3wNR8FJRaLZWt8GRgYQFVVVeAeMUXD4cMYEwRfcGaMCYLDhzEmCA4fxpggOHwUTEFBQbO30wFg3759Lb4UrLPLMQUg7M021tni4+PJxMSEli1b1mj72rVrycDAgG7fvt0lyrHuj8NHASUmJpK5uTktXLiQJBIJrVy5koyNjSkmJqZLlWPdG99qV1BpaWnw9PSEqqoqqqqqcOnSpSZPIneFcqz74ms+CsrGxgbu7u5IS0vDiBEjYG9v3yXLse6Lw0cBERHmzZuH69evIyQkBElJSZg9ezbq6+u7VDnWzQl60sc6nVgsplmzZpGtrS1lZ2cTEVF+fj4NGjSIpkyZQrW1tV2iHOv+eOSjYCIjI5GSkoKwsDD06dMHQMN7d4KCgpCfn4+wsLAuUY51f3zBWQHR/6+d1drtQpVj3RuHD2NMEHzaxRgTBIcPY0wQHD6MMUFw+DDGBMHho2Dau4JoZ5djCkCoB4xY50tKSqK+ffuSSCQiJSUlGjNmDOXm5sr25+fnk5KSkuDlmGLgkY8CWbduHQYNGoSCggIkJSVBR0cHo0aNQnZ2dpcqxxSE0OnHOo+RkRHFxsbKPkulUlq6dClZWlpSWlpaiyORzi7HFAOPfBRIe1cQ7exyTDHwiqUKpL0riHZ2OaYYeOSjQKZPn45jx441u+/AgQN47bXXml32uLPLMcXAc7sYY4LgkY+CSUhIwOHDh5GYmAgASExMhK+vLxYtWoTLly93mXJMAQh6uZt1Kn9/f1JTUyN9fX3S0NAgf39/MjQ0JC8vL/Lw8CBlZWUKDAwUvBxTDBw+CsTd3Z02btxIRETHjh0jPT092rBhg2z/+vXracKECYKXY4qBw0eB9OzZk1JSUoiISCKRkIqKCkVFRcn2x8XFkbGxseDlmGLgaz4K5vGbA5WUlKChoQFdXV3ZPh0dHZSWlnaJcqz74/BRIFZWVkhJSZF9vnbtGiwtLWWfs7OzYWpqKng5phj4IUMF4uvrC4lEIvs8aNCgRvv9/f3h4eEheDmmGPg5H8aYIPi0izEmCA4fxpggOHwYY4Lg8GGMCYLDhzEmCA4fxpggOHwYY4Lg8GGMCYLDhzEmCA4fxpggOHwYY4Lg8GGMCYLDhzEmCA4fxpggOHwYY4Lg8GGMtdrdu3cxa9Ys9OvXDz169ICBgQHGjBmDc+fOtbkufpMhY6zVsrKyUF5ejgULFsDMzAxVVVX45ZdfMHXqVHz11VdYvHhxq+viNxkyxp6JRCLB8OHDUVNTI1scsjX4tIsxAeTk5OCNN96AmZkZ1NXVYW1tDV9fX9TV1QEA0tPTMWvWLOjr66NHjx5wc3PD77//3qiO4OBgiEQi/Pzzz9i+fTssLCygoaEBT09PpKamyo5bsWIFtLW1UVVV1aQfr732GkxMTBq9a7utlJWV0adPH5SUlLSpHJ92MdbJcnNz4eLigpKSEixevBgODg7IycnByZMnUVVVheLiYowcORJVVVV466230Lt3bxw9ehRTp07FyZMnMX369Eb1ffzxx1BSUsLatWtRWlqK3bt3Y+7cuYiIiAAAzJkzB1988QV+//13zJo1S1auqqoK586dw+uvvw5lZeU2fYfKykpUV1ejtLQUZ8+ehb+/P+bMmdO2X4Swy4Yxpnjmz59PSkpKdOPGjSb7pFIprVq1igBQWFiYbHt5eTlZW1uTlZUVSSQSIiIKCgoiAOTo6Ei1tbWyY/ft20cAKC4uTlanubk5zZgxo1FbP//8MwGg0NDQNn+HJUuWEAACQEpKSjRz5kx69OhRm+rg0y7GOpFUKsWvv/6KKVOmwNnZucl+kUgEPz8/uLi4YPTo0bLt2traWLx4MTIzMxEfH9+ozMKFC6Gmpib7/OKLLwJoOHV7XOesWbPg5+eHiooK2XE//fQTzM3NG7XTWqtWrcLFixdx9OhR+Pj4QCKRyE4ZW4vDh7FOVFhYiLKysiZrmP1ZVlYW7O3tm2x3dHSU7f+zPy/ECAB6enoAgOLiYtm2OXPmoLq6GmfPngUAVFRUwM/PD7NmzZKtKtsWDg4O8PLywvz58/Hbb7+hoqICU6ZMAbXh/hWHD2PPuZau1/w5CNzc3GBlZYWff/4ZAHDu3DlUV1e3/TpNC2bOnIkbN24gOTm51WU4fBjrRIaGhujZsyfu3LnT4jF9+/ZFUlJSk+2Pb2P37du3XW3Pnj0bAQEBKCsrw08//QQrKyu4ubm1q66/qq6uBgCUlpa2ugyHD2OdSElJCdOmTcO5c+dw8+bNJvuJCJMnT0ZkZCSuXbsm215ZWYmvv/4aVlZWGDBgQLvanjNnDmpra3H06FEEBARg9uzZba6joKCgyTaxWIzvvvsOmpqabeob32pnrJPt2LEDFy5cwNixY7F48WI4OjoiLy8PJ06cQHh4ONavX49jx47Bx8cHb731FvT19XH06FFkZGTgl19+gZJS+8YMTk5OsLW1xcaNG1FbW9uuU64lS5agrKwMY8aMgbm5OfLz8/HDDz8gMTER//73v6Gtrd36ytp8j40x9syysrJo/vz5ZGhoSOrq6tSvXz9avny57JZ5WloazZw5k3r16kUaGhrk4uJCv/32W6M6Ht9qP3HiRKPtGRkZBIAOHz7cpN2NGzcSALK1tW1Xv48dO0ZeXl5kbGxMKioqpKenR15eXnTmzJk218XTKxhjguBrPowxQfA1H8YYKioqGj2A2BxDQ8M2T8N4Eg4fxhg++eQTfPjhh088JiMjA1ZWVh3WJl/zYYwhPT1dNh2jJaNHj4aGhkaHtcnhwxgTBF9wZowJgsOHMSYIDh/GmCA4fBhjguDwYYwJgsOHMSYIDp9uorCwELW1tUJ3Q+5KS0tRXl4udDdYB+Dw6SbefPNN+Pj4CN0NuRKLxRg2bBi2bt0qdFdYB+Dw6QYuXryIM2fOYMmSJUJ3Ra5UVVWxaNEi7N27FykpKUJ3hz0jfsL5OVdfX49hw4ZBT08PoaGh7XoZ+POkuroaDg4OGDp0qOxl6K31xRdfYM+ePcjPz8fQoUOxf/9+uLi4yKmn7Gl45POc++qrrxAfH499+/Z1++ABAE1NTezZswfnzp3DhQsXWl3up59+wpo1a7B582ZERUVh6NChmDRpUrOvBWWdpF2vM2NdQlFREenp6dEbb7whdFc6lVQqpRdffJEGDBhAdXV1rSrj4uJCy5cvl32WSCRkZmZGO3fulG3Lysqi1157jXr16kV6enr097//vc0L4bHW45HPc2zLli2or6/H9u3bhe5KpxKJRNi3bx8SEhJw6NChpx5fV1eHW7duwcvLS7ZNSUkJXl5espe0p6amYvjw4bC1tcX169dx8eJFpKam4t1335Xb91B4Qqcfa5+4uDhSVlamPXv2CN0Vwbz55pvUq1cvKiwsfOJxOTk5BICuXr3aaPu7775LLi4uREQ0YcIE+uCDDxrtP3nyJFlbW3dsp5kMj3yeQ0SE1atXo1+/fnjrrbeE7o5gtm3bBqlUis2bNz9TPVlZWbh48SL27NkDbW1t2c+8efOgosLv25MX/s0+h86dO4dLly7h7NmzjdboVjRGRkbYvHkz3n33XSxduhSDBw9u9jgDAwMoKyvjwYMHjbY/ePAAJiYmiImJgb6+PiIiIpqU1dTUlEvfGd9qf+7U1tZi4MCBsLGxQUBAgELc4XqSuro6DB48GBYWFrh06VKLvw9XV1e4uLhg//79AACpVApLS0usWLECQ4cOxSuvvIKSkhL06NGjM7uv2AQ+7WNttGvXLlJWVqa7d+8K3ZUu47fffiMAdPr06RaPOX78OKmrq9ORI0coPj6eFi9eTL169aL8/Hx6+PAh9e7dm2bMmEG3b9+mlJQU8vf3p7fffrvTvoMi4vB5juTl5ZG2tja99dZbQnelS5FKpeQxwYOsrK2ourq6xeP2799PlpaWpKamRi4uLnT9+nXZvoiICBo3bhz17NmTdHR0yMnJifbt29cZ3VdYfNr1HFm0aBHOnj2LlJQU6OnpCd2dLqFKXIXgzGD4XfXDoX8ewrat27B+/Xqhu8VagcPnOXHz5k24uLjgwIEDWLZsmdDdEZxEKsHN3JsIzgxGdX01ACDyP5EI+zUMycnJMDU1FbiH7Gk4fJ4DRITRo0ejrKwM0dHRCn/7N+1RGgJSA1BYVQgAMNYyhretN3qhF+zs7DBlyhQcPnxY4F6yp+mSz/ns3LkTI0aMgI6ODoyMjDBt2jQkJSXJ9kskEmzatAnW1tbQ1NSEjY0Ntm7dij/naGuO6ShffPEFrKysoKGhAVdXV0RGRnZo/cePH8fVq1exd+9ehQ6eR9WPcCzuGL6P/R6FVYXoodoDL/d/GUucl8Bazxp6enrYtm0bjhw5ghs3brS63tDQUEyZMgVmZmYQiUT49ddf5fcl2B8Eu9r0BJMmTaLDhw/TnTt36Pbt2zR58mSytLSkiooKIiLavn079e7dm3777TfKyMigEydOkLa2dqMLhK055q/Cw8ObnSt09+5dys/Pb7bM8ePHSU1Njf773//S3bt3ZU/dPnjw4Bl/Cw0qKirIwsKCpk+f3iH1PY9qxDV0IfUCfRT8EW0O2kwfBn9I/in+VFVX1eTY+vp6GjJkCLm5uZFUKm1V/X5+frRx40Y6derUU++asY7TJcPnrwoKCggAhYSEEBHRSy+9RIsWLWp0zN/+9jeaO3eu7HNrjvkziURCQ4cOpZkzZ1J9fb1se2JiIhkbG9OuXbuaLdeaCYtE7Z+0+MEHH5CamhqlpaU99djuRiqVUlRuFO25soc2B22mzUGb6fuY76mgouCJ5S5fvkwA6H//+1+b22wpfOLi4sjHx4d0dHTI2NiY1qxZQ7W1tW2un/2hS552/VVpaSkAQF9fHwAwcuRIBAYGIjk5GQAQExOD8PDwRm/ya80xf6akpAQ/Pz9ER0dj/vz5kEqlSEtLg4eHB6ZNm4b33nuvSZnWTFgE2j9pMTs7G7t378aaNWvQr1+/1vyquo3s0mx8E/UNziSdQUVdBXpr9sbfB/8dcwfPhaGW4RPLjh8/HjNmzMC6detQWVn5zH2Jjo7GyJEj4eTkhKioKBw/fhzHjh3Drl27nrluhSZ0+j2NRCKhl156iUaNGtVo27p160gkEpGKigqJRCLasWNHk3JPO6Y5WVlZZGlpSXPmzCFLS0uaP39+i8P31kxYJGr/pMU5c+aQiYkJlZWVPbXf3UVJdQmdvHtSNtLZEbqDrmZfpXpJ/dML/0l6ejqpq6vTpk2b2lQOzYx8hg8fTsuWLWu0bcOGDY3+jFnbdfnwWbp0KfXt25fu3bsn23bs2DGysLCgY8eOUWxsLH333Xekr69PR44cadMxLQkJCSEA1K9fPxKLxS0e15rwyczMJACkqalJWlpash8NDQ2ys7Nrse7Q0FAC0Kr+dgd19XUUlBFE20K20eagzbQlaAudTTxLFbUV7a5zw4YNpKGhQRkZGa0u89fwSUhIIACUkJDQ6LgtW7bQ0KFD29031sXDZ/ny5WRhYUHp6emNtltYWNCBAwcabdu6dSvZ29u36Zjm5Ofnk729PU2ZMoVMTExoxYoVLR5bW1tLysrKTf6nnD9/Pk2dOpWIiM6cOUP6+vqUkpLS5Of+/fvN1ltfX08vvPACjRgxgiQSyRP7+7yTSqUU9yCOPr36qWy089+o/1JuWe4z111eXk6mpqY0a9asVpf5a/icPHmSVFVVm/w5zJ49m+bNm/fMfVRkXfK+LRFh5cqVOH36NIKDg2Ftbd1of1VVFZSUGl+uUlZWhlQqbdMxf1VUVARPT084OjrixIkTSE5Oxrhx46Curo5PPvmkyfFqamoYPnw4AgMDMW3aNAANExYDAwOxYsUKAA0vPS8vL4eZmVmrJy0ePnwY0dHRuHr1apPv0J3klefBP9Uf2aXZAABddV1MtJmIAYYDOmTCrLa2Nnbt2oX58+cjJCQEY8eObXMdOjo6kEgkEIvFUFdXBwBkZGTg9OnTbX6HNPsLodOvOb6+vqSrq0vBwcGUl5cn+6mqari1umDBAjI3N5fdRj916hQZGBjQe++9J6ujNcf8mUQiIWdnZ5o8eXKjuxi3b98mfX19+vTTT5st96QJi0TU5kmLJSUlZGRk1OJdue6gvLacziSeoS1BW2hz0GbaFrKNgjOCqa6+da9EbQuJREKurq40bNiwRncxG/WnvJyio6MpOjqaANCnn35K0dHRlJWVRSUlJaSvr0+rVq2itLQ0CgwMJEdHR/rHP/7R4X1VNF0yfAA0+3P48GEiIiorK6O3336bLC0tSUNDg/r160cbN25sFBqtOeavLly40OzExKioqEbXnP7qSRMWido2aXHNmjWkpKZEkw5MoqTCpCf9mp479ZJ6upJ9hXaE7pCdYp28e5JKqkvk1mZBRQFt/n4zAaCvv/662WOCgoKa/fu2YMECImq4/ubk5CT7e7Rz584Wg4y1Hk+v6EKSk5MxcNBAaHlpodfEXlBVUsVL/V/CNo9t0FbTFrp77UZESH6YjAtpF/Cw+iEAwEzHDD62Puij20cubVaLqxGSFYLInEhISYpfd/6KnOgcpCSnoFevXnJpk7UNh08XMmXKFMTFxeGzc59hT8Qe5FbkAgB6qveE73BfLB6+GMrKygL3sm0KKwsRkBqAtOI0AIC2mja8+nlhqPFQubwITUpSROVF4XLGZVSJqwAA9r3tMVhzMNyGuWHJkiX497//3eHtsrbj8Okizp8/D29vb5w4cQIzZ86ERCLB3oi9OHz7MCrqKgAAfXX74sNxH2Kc9ThB+9oa1eJqBGcG40buDUhJCmWRMtz7uONFyxehrqIulzYzijMQkBqAB5UNr0s17GEIb1tv2OjbAAB27NiBzZs3486dO7C3t29VnTt37sSpU6eQmJgITU1NjBw5Ert27Wp1edYyDp8uQCwWY+jQoTA0NERwcHCjEcGj6kd4//L7OJ96HhKSQAQR3CzcsGvCLljqWgrY6+ZJSYpbubcQlBkkG3k4GDhgos1E6Gvqy6XN4upiXEi7gISiBACAhooGxluNh7OZM5SV/hgp1tTUwNHREQMHDsRvv/3Wqrq9vb3x6quvYsSIEaivr8eGDRtw584dxMfHQ0tLSy7fR1Fw+HQBn3/+OVatWoWoqCgMGzas2WNiH8RiQ+AG3Cm4AwBQU1bDjAEz8MGLH0BTrWu85DyjOAP+qf4oqGxYBdRIywjett7opyefqSF1kjqEZYXh2v1rqJfWQwQRRpiPwDirceih2vxjDadOncKMGTPg5+fX4lSbJyksLISRkRFCQkIwZswYAA3TYNavXw9/f3+IRCL4+PjgwIED/MK3p+DwEVhRURHs7Owwe/ZsfPXVV089/pf4X/DxlY9RWNnwLhs9TT2sdl2N+cPmy7urLfrryENTRRMe1h4YbjYcSqKOf06JiBD7IBaX0i+hvK4cAGDdyxrett4w1jZ+allPT0/k5uYiLi4OqqqqbWo7NTUVdnZ2iIuLw6BBg5Camgp3d3f4+vpi7ty5qKiowLJlyzB48GB8++237f6OioDDR2DLli3Djz/+iJSUFBgaPnnC5GN1kjrsCt+FH+J+QE19DQDArrcdto/fDhcLF3l2t5Ha+lqEZ4fj6r2rkJAESiIljDBrGHloqspnNHa/7D4CUgNwv+w+AEBPQw+TbCfBvrd9qy9gx8bG4oUXXsC///1vrFq1qtVtS6VSTJ06FSUlJQgPDwcATJw4Ee7u7vjwww9lx/3yyy949913kZ6e3vovpoA4fAT0+B/BJ598gtWrV7e5fH5FPtZfXI+Q7BAQEZREShjXdxx2eO2AibaJHHrcgIgQ8yAGl9IvyS6G2+jZYJLtJBhpGcmlzfLaclxKv4SYBzEAGk47x/QdAzcLN6gotf1BfV9fXxw7dqxNoe/r6wt/f3+Eh4fDwsICWVlZsLKygqamZqMn0SUSCfr06SN7owJrHoePQB4P/3NychAXF/dMi/9dv3cdm4I3IeVhCoCG0565Q+bivVHvQU25YxcVvF92H/4p/sgpzwEA6GvqY5LNJPTv3V8ut87rpfW4du8awrLDUCepAwAMMxkGT2tP6KjrtLvewsJC9O/fH6+++ioOHjz41ONXrFiBM2fOIDQ0VDbd5+zZs1i4cGGLiw2am5u3u3+KgMNHIKdPn8bf/vY3/P7775g8eXKH1Hkk+gj2Re5DcXUxgIYLvutGrcOMATOeue6y2jJcSr+E2AexAAB1ZXWM6TsGrhau7Rp5PA0RIaEoARfSLqCkpgQA0KdnH3jbesO8Z8f8o963bx/WrFmDqKgoDB06tMV+/HmeoZ2dnWyfv78/Lzb4DDh8BFBTU4MBAwbAwcEBfn5+HVp3dV01Pgr7CL/E/yIbKQwyGoQdnjswxHhIm+sTS8S4dv8awrLCIJaKIYKoYeTRz1NuT13nV+QjIDUAmSWZABoespzQbwIGGQ3q0NGVWCzGkCFDYGJigsuXLzdb9+NrcmfOnGn0bI+uri6qq6vRv39/jBs3Dps2bYKWlhZSU1MREBCAvXv3dlg/uysOHwHs3LkTH3zwAeLi4uDg4CCXNjKKM/CvwH8h4n4ECARlkTIm2U7CNo9trXreprmRh6WuJbxtvWGmYyaXPlfWVSIoMwi3cm+BQFBRUsGoPqMwynJUh58+Pnb45GEsmrUIJ0+exIwZTUeILYXd4cOH8frrryMyMhLr1q1DVFQUiAh2dnZYsGAB3nrrLbn0tzvh8Olkubm56N+/PxYvXoxPP/1U7u1dzriMLcFbZK+t0FbTxqIXFuFtl7dbnKrR3Mhjos1EDDQcKJfrOhKpBDdybyA4M1h2926g4UBMsJmAXhq9Orw9AHhY9RAX0i4g6WESjm04hprcGiQlJkFDQ0Mu7bGmOHw62euvv47ff/8dKSmdN8FRIpHg4K2D+OrmV7LnYsx1zPHB2A8wyXaS7LjKukpczriMqLwoEAiqSqoYZTkKo/qMgqpy256Haa3UR6kISA1AUVURAMBE2wQ+tj7o26uvXNqrra9FaFYort+/Lns8wKzODL6TffHhhx9iw4YNcmmXNcXh04kiIyPh6uqKQ4cOYcmSJZ3efllNGTYFbYJfih/EUjEAwNnMGTs8duBh9UOEZIXIRh6DjAZhQr8J0NXQlUtfHlY9xPm080h+2HA7WktVCx7WHnjB9AW5PJgoJSlu599GYHogKsUNL5W31beFt603DHoYYO3atTh06BCSkpL4LlUn4fDpJFKpFCNHjkR1dTWioqIEnZ2eUJiADYEbEJ0fDQBQFinDwcABL5i+ILujJK+RR019DUIyQxCREwEpSaEkUoKbhRvG9B0DDRX5nPJkl2bDP8UfeRV5AIDemr3hbesNu95/3LkqLS2FnZ0dvL298d1338mlH6wxDp9O8r///Q//+Mc/EBQUhHHjxgndHQDAb8m/YUPgBpTVlgEAbPRtsNptNSbbTu7w17dKSYrovGhczrgsG3n0790fE20mwqCHQYe29VhpTSkupl+UzYdTV1bHOKtxcDF3aTTh9LFvv/0Wb775Jq5duwY3Nze59In9gcOnE1RUVMDe3h4jR47EiRMnhO5OIzfu38Duq7uR+ihVdorVv3d/vO36NgYaDeyQNrJKsuCf6o/8inwAgEEPA0yymdRo5NGRxBIxrty7givZV2SPBziZOsHD2gNaai3PRJdIJHB2doaamhquXbvWrd+f3RVw+HSCTZs2Yc+ePUhMTISVlZXQ3WkkKi8KZ5POoq9uXxRVFeF82nnZDPFxVuOw3GV5u0cmJTUluJh2EXcL7wJoeNXFOKtxGGE2otmRx7MiItwtvIuLaRdRWtuw0GRf3b7wtvWGqY5pq+oIDQ3F2LFj8d133+Ef//hHh/eR/YHDR84yMzPh4OCAtWvXYtu2bUJ3p4nH4WPf2x6vDX4NGcUZ2Ht9r2wOlaaKJmYNnIV/DPlHq+941UnqcCX7Cq7cuyILsuFmwzHeavwTRx7PIrc8FwGpAbJHCnpp9MJEm4lwNHBs8+MBc+bMQVhYGJKTk6Gt/fy+vrar4/CRs1mzZuHq1atISkrqkn+R/xo+j4VmhuLgzYOyi7TGWsZY6rwU463Ht1gXESGuIA6X0i/JriNZ9bKCt6233Ca6VtRVIDA9ELfzb8seD3ix74twt3Bv9+MBWVlZcHBwwJo1a7B9+/YO7jF7jMNHjoKDgzF+/Hh8//33mDdvntDdaVZL4QM0TOr8Me5HHL9zXPZWwsFGg7HKbZXs1aSP5ZTlICA1APfK7gFoGHlMspkEBwMHuU04jbgfgdCsUNRKagEAQ4yHwKufF3qq93zm+jdv3oxdu3YhPj4e/frJ52Voio7DR04kEgmGDx8OTU1NXLlypctevHxS+Dz2qOoRvrz5JS5nXJa9j3mCzQQsc14GkUiEwIyGkQfQ8KqLFy1fhHsfd7lNOE1+mIzzaefxqPoRgIYHJr1tvTt0JYzKyko4ODjAxcUFv/zyS4fVy/7QJVcs7Q7+85//ICYmBhEREV02eFpLv4c+3h/zPmY4zsDnEZ8joSgBAakBuJxxGda9rGGibQKRSIShxkPh1c/rmV518SQFlQU4n3q+U1bC0NLSwq5duzB37lwEBQVh/PiWTzdZ+3D4yEnfvn2xceNGuLh03psF5c3R0BFfTP4CF9Mv4uCNgyipLUHSwyRU11dj+YjlcLVwlUu7QqyEAQCvvfYawsLC0LPns5/Gsab4tEvBtea0qzmFlYVYf2k9MkszMbrPaIhEIgw2Ggyvfl4dNiVDSlLczL2JoIwgVNdXAwAcDRwxwWaC3FbCYJ2HRz6sXdSU1dBHtw/Me5rD2cwZUXlRiCuIQ2JRYodMRk0vTkdAakCnrYTBOh+HD3smaspqmGI/Bc5mzghIDUBWaRaCM4MRnReNCTYT2vwajkfVj3Ah7QISixIByH8lDCYcDh/WIUx1TPH6sNcRXxiPC2kXUFpbipPxJxGpGwkfW5+nPmFcW1+LsOwwXLt3rdNWwmDC4vBhHUYkEmGg0UD0790fV+9dRXh2OLJLs/H1ra/xgukL8LD2aPLq1ZZWwvC29YahVutWlWDPJw4f1uFUlVUx1moshpkMw6X0S4griENUXhTuFtzFWKuxcDV3hbKSMu6V3oN/qj9yy3MBNKyE4W3rDTt9O7k8mMi6Fg4fJje6GrqYMWAGXMxdZCFzIe0CwrLCoCRSkr1aQ11ZHWOtxsLF3EUuDyayron/pJnc9dHtgzed3mxyegVA9qoLea2Ewbouvn3AOoVI1LDkzkqXlbJtk2wmYar9VA4eBcXhwzqVuoq67P1ArX3HDuueOHwYY4Lg8GGMCYLDhzEmCA4fxpggOHwYY4Lg8GGMCYLDhzEmCA4fxpggOHwYY4Lg8GGMCYLDhzEmCA4fxpggOHwYY4Lg8GGMCYLDhzEmCA4fxpggOHwYY4Lg8GGMCYLDhzEmCA4fxpggOHwYY4Lg8GGMCYLDhzEmCA4fxpggOHwYY4Lg8GGMCYLDhzEmCA4fxpggOHwYY4Lg8GGMCYLDhzEmCA4fxpggOHwYY4Lg8GGMCYLDhzEmCA4fxpggOHwYY4Lg8GGMCUJERCR0J1jH+Pjjj+Hv79+mMpV1lXhY/RCaqpow7GHY6nISqQQ55TkQiUTo07NPm9rMq8iDWCKGsZYx1FXU21T2zTffxLx589pUhnVNKkJ3gHUcfX19WFpadlp71rBuVzlLtL+PPXv2bHdZ1rXwyIcxJgi+5sMYEwSHD2NMEBw+jDFBcPgwxgTB4cMYEwSHjwIqLS3F4sWLYWtrC0dHR+Tl5bWqXH19PbZv3w53d3c4OTlhwYIFuHjxotzaY90bh48CWr58OeLi4rB7925kZWWhuroaALB69WocOHCgxXLr16/Hl19+CU9PT0ybNg21tbV4+eWXsXDhQjzpiY32tse6OWIKR19fn6KiooiISFtbm9LS0oiIyN/fn5ydnVssZ2pqSiEhIY22paen04ABA2j37t0d3h7r3njko4CICDo6Ok2229nZISUlpcVylZWVsLCwaLTN2toa+/fvx9dff93h7bHujcNHAfn4+OCHH35osr2yshIikajFcqNHj8bRo0ebbLe2tkZubm6Ht8e6N57bpYB27twJZ2dnAA2jEpFIhJqaGmzduhVOTk4tltu1axdGjRqF4uJirFy5EnZ2dhCLxdi/fz8GDBjQ4e2xbk7Ysz4mlJSUFJo4cSKJRCIyMDAgdXV1MjQ0pBs3bjyxXFRUFDk7O5NIJCJ1dXVSUVEhAwMDCg8Pl0t7rPviiaUKLjs7GzExMVBVVYWrqyv09PRaVS4pKQl3796Fjo4OXF1dWz3bvL3tse6Hw4cxJgi+4KxgioqKsHv3bkyfPh3u7u5wd3fH9OnTsWfPHhQWFrarznv37mHRokXN7quurkZ4eDji4+Ob7KupqcF3333XrjbZ849HPgrkxo0bmDRpEnr06AEvLy8YGxsDAB48eIDAwEBUVVXh/PnzsovDrRUTEwMnJydIJJJG25OTkzFx4kRkZ2dDJBJh9OjROH78OExNTWXtmpmZNSnHFAOHjwJxc3PD0KFDcejQoSa3uIkIS5cuRWxsLK5du9Zo39mzZ59Yb3p6Ot55550mITJ9+nSIxWIcOXIEJSUlWLVqFeLj4xEcHAxLS0sOHwXH4aNANDU1ER0dDQcHh2b3JyYm4oUXXpBNf3hMSUkJIpHoiVMoRCJRkxAxNjbGpUuXMHjwYAANAbds2TL4+fkhKCgIWlpaHD4KjK/5KBATExNERka2uD8yMlJ2KvZnpqamOHXqFKRSabM/UVFRzdZXXV0NFZU/HiUTiUQ4ePAgpkyZgrFjxyI5OfnZvxR7bvFDhgpk7dq1WLx4MW7dugVPT88m13y++eYbfPLJJ03KDR8+HLdu3cIrr7zSbL0tjYocHBxw8+ZNODo6Ntr+eDLp1KlTn/UrseeZEA8XMeEcP36cXF1dSUVFhUQiEYlEIlJRUSFXV1f66aefmi0TGhpK/v7+LdZZUVFBwcHBTbbv2LGDfHx8Wizn6+tLIpGo7V+CdQt8zUdBicViFBUVAQAMDAygqqoqcI+YouHwYYwJgi84M8YEweHDGBMEhw9jTBAcPgqmoKCg2dvpALBv374WXwrW2eWYAhD2ZhvrbPHx8WRiYkLLli1rtH3t2rVkYGBAt2/f7hLlWPfH4aOAEhMTydzcnBYuXEgSiYRWrlxJxsbGFBMT06XKse6Nb7UrqLS0NHh6ekJVVRVVVVW4dOlSkyeRu0I51n3xNR8FZWNjA3d3d6SlpWHEiBGwt7fvkuVY98Xho4CICPPmzcP169cREhKCpKQkzJ49G/X19V2qHOvmBD3pY51OLBbTrFmzyNbWlrKzs4mIKD8/nwYNGkRTpkyh2traLlGOdX888lEwkZGRSElJQVhYGPr06QOg4b07QUFByM/PR1hYWJcox7o/vuCsgOj/185q7XahyrHujcOHMSYIPu1ijAmCw4cxJggOH8aYIDh8GGOC4PBRMO1dQbSzyzEFINQDRqzzJSUlUd++fUkkEpGSkhKNGTOGcnNzZfvz8/NJSUlJ8HJMMfDIR4GsW7cOgwYNQkFBAZKSkqCjo4NRo0YhOzu7S5VjCkLo9GOdx8jIiGJjY2WfpVIpLV26lCwtLSktLa3FkUhnl2OKgUc+CqS9K4h2djmmGHjFUgXS3hVEO7scUww88lEg06dPx7Fjx5rdd+DAAbz22mvNLnvc2eWYYuC5XYwxQfDIR8EkJCTg8OHDSExMBAAkJibC19cXixYtwuXLl7tMOaYABL3czTqVv78/qampkb6+PmloaJC/vz8ZGhqSl5cXeXh4kLKyMgUGBgpejikGDh8F4u7uThs3biQiomPHjpGenh5t2LBBtn/9+vU0YcIEwcsxxcDho0B69uxJKSkpREQkkUhIRUWFoqKiZPvj4uLI2NhY8HJMMfA1HwXz+M2BSkpK0NDQgK6urmyfjo4OSktLu0Q51v1x+CgQKysrpKSkyD5fu3YNlpaWss/Z2dkwNTUVvBxTDPyQoQLx9fWFRCKRfR40aFCj/f7+/vDw8BC8HFMM/JwPY0wQfNrFGBMEhw9jTBAcPowxQXD4MMYEweHDGBMEhw9jTBAcPowxQXD4MMYEweHDGBMEhw9jTBAcPowxQXD4MMYEweHDGBMEhw9jTBAcPowxQXD4MMbabfv27RCJRE1eFNca/DIxxli73L9/H/b29hCJRLCyssKdO3faVJ7DhzHWLq+++ioKCwshkUhQVFTU5vDh0y7GBJCTk4M33ngDZmZmUFdXh7W1NXx9fVFXVwcASE9Px6xZs6Cvr48ePXrAzc0Nv//+e6M6goODIRKJ8PPPP2P79u2wsLCAhoYGPD09kZqaKjtuxYoV0NbWRlVVVZN+vPbaazAxMWn0ru3WCA0NxcmTJ7F37962f/n/xy+QZ6yT5ebmwsXFBSUlJVi8eDEcHByQk5ODkydPoqqqCsXFxRg5ciSqqqrw1ltvoXfv3jh69CimTp2KkydPYvr06Y3q+/jjj6GkpIS1a9eitLQUu3fvxty5cxEREQEAmDNnDr744gv8/vvvmDVrlqxcVVUVzp07h9dffx3Kysqt7r9EIsHKlSvxz3/+E4MHD27/L0LIRcMYU0Tz588nJSUlunHjRpN9UqmUVq1aRQAoLCxMtr28vJysra3JysqKJBIJEREFBQURAHJ0dKTa2lrZsfv27SMAFBcXJ6vT3NycZsyY0aitn3/+mQBQaGhom/p/4MAB0tXVpYKCAiIiGjt2LA0cOLBNdRDxooGMdSqpVIpff/0VU6ZMgbOzc5P9IpEIfn5+cHFxwejRo2XbtbW1sXjxYmRmZiI+Pr5RmYULF0JNTU32+cUXXwTQcOr2uM5Zs2bBz88PFRUVsuN++uknmJubN2rnaR4+fIgPPvgAmzZtgqGhYavLNYfDh7FOVFhYiLKysifems7KyoK9vX2T7Y6OjrL9f/bnhRgBQE9PDwBQXFws2zZnzhxUV1fj7NmzAICKigr4+flh1qxZslVlW+P999+Hvr4+Vq5c2eoyLeHwYew519L1GvrTjWw3NzdYWVnh559/BgCcO3cO1dXVmDNnTqvbSUlJwddff4233noLubm5yMzMRGZmJmpqaiAWi5GZmYlHjx61uj4OH8Y6kaGhIXr27PnE29J9+/ZFUlJSk+2JiYmy/e0xe/ZsBAQEoKysDD/99BOsrKzg5ubW6vI5OTmQSqV46623YG1tLfuJiIhAcnIyrK2t8dFHH7W6Pg4fxjqRkpISpk2bhnPnzuHmzZtN9hMRJk+ejMjISFy7dk22vbKyEl9//TWsrKwwYMCAdrU9Z84c1NbW4ujRowgICMDs2bPbVH7QoEE4ffp0k5+BAwfC0tISp0+fxhtvvNH6Ctt8iZox9kzu379PJiYm1KNHD1q1ahV99dVXtGXLFho4cCAVFxdTfn4+GRsbk66uLm3atIk+++wzGjZsGIlEIjp16pSsnsd3u06cONGo/oyMDAJAhw8fbtK2ra0t6ejoEAC6detWh3yf9t7t4ud8GOtk5ubmiIiIwKZNm/DDDz+grKwM5ubm8PHxQY8ePdCrVy9cvXoV69atw/79+1FTU4MhQ4bg3LlzeOmll56p7Tlz5mD79u2wtbWFk5NTB32j9uHpFYwxQfA1H8aYIPi0izGGioqKRg8gNsfQ0LBN0zCehsOHMYZPPvkEH3744ROPycjIgJWVVYe1ydd8GGNIT0+XTcdoyejRo6GhodFhbXL4MMYEwRecGWOC4PBhjAmCw4cxJggOH8aYIDh8GGOC4PBhjAmCw6eb+M9//oP9+/cL3Q25EovFeP/993HhwgWhu8I6AIdPN1BUVIS1a9ciLi5O6K7IlYqKCsLDw/H2229DLBYL3R32jDh8uoHNmzdDKpVi27ZtQndFrkQiEfbu3Yvk5GR8+eWXQneHPSMOn+dcXFwcDh06hA8++ABGRkZCd0fuhg0bhn/+85/YsmULioqK2lT2iy++gJWVFTQ0NODq6orIyEg59ZK1Soe8yowJQiqVkoeHB9nZ2TVat6m7KygoIF1dXfL19W11mePHj5Oamhr997//pbt379Kbb75JvXr1ogcPHsixp+xJOHyeY6dPnyYA9NtvvwndlU736aefkpKSEsXExLTqeBcXF1q+fLnss0QiITMzM9q5c6dsW1ZWFr322mvUq1cv0tPTo7///e/06NGjDu87a8CnXc+pmpoavPPOO/D29sbkyZOF7k6nW758Oezs7LBq1apGS8Q0p66uDrdu3YKXl5dsm5KSEry8vGQvaU9NTcXw4cNha2uL69ev4+LFi0hNTcW7774r1++hyDh8nlN79+5FdnY2Pv300zYt+tZdqKmp4bPPPkNQUBBOnz79xGOLioogkUhgbGzcaLuxsTHy8/MBAMuWLcOyZcvw0Ucfwd7eHsOHD8d7772Hy5cvy+07KDoOn+dQXl4etm/fjuXLl8tWsVREPj4+8PHxwdq1a1FTU9PuerKysnDx4kXs2bMH2trasp958+ZBRYXftycv/Jt9Dm3YsAHq6urYvHmz0F0R3KefforBgwfjs88+w7/+9a9mjzEwMICysjIePHjQaPuDBw9gYmKCmJgY6OvrIyIioklZTU1NufSb8cjnuXPjxg0cOXIE27Ztk63JrcgcHBywcuVKbN++Hbm5uc0eo6amhuHDhyMwMFC2TSqVIjAwEO7u7lBVVUV5eTnMzMxga2vb6Mfc3LyzvoriEfqKN2s9qVRK7u7uNGTIEKqvrxe6O11GcXExGRgY0Pz581s85vjx46Surk5Hjhyh+Ph4Wrx4MfXq1Yvy8/Pp4cOH1Lt3b5oxYwbdvn2bUlJSyN/fn95+++3O+xIKiMPnOfLDDz8QALp8+bLQXelSiiqL6I0P3iAAFBER0eJx+/fvJ0tLS1JTUyMXFxe6fv26bF9ERASNGzeOevbsSTo6OuTk5ET79u3rjO4rLH6H83OisrIS9vb2cHV1xS+//CJ0d7qEmvoahGaFIuJ+BMT1Yvxn6X9g0dsC165eg5ISX1Ho6vhP6Dmxa9cuFBUVYc+ePUJ3RXBSkiIqLwr7I/bj6r2rkJAE9ob2+PLAl4iMiMSPP/4odBdZK3TJ8Nm5cydGjBgBHR0dGBkZYdq0aUhKSpLtl0gk2LRpE6ytraGpqQkbGxts3bq10cNmrTmmo8h7zlBmZib27NmDd955B/369evQup83WSVZ+ObWNzibdBaV4koY9DDA3MFzMXfIXLwy6RXMnDkT69ate+oCeH8WGhqKKVOmwMzMDCKRCL/++qv8vgD7g7Bnfc2bNGkSHT58mO7cuUO3b9+myZMnk6WlJVVUVBAR0fbt26l3797022+/UUZGBp04cYK0tbUbnaO35pi/Cg8Pp7q6uibb7969S/n5+c2W6Yw5Q7NmzSJTU1MqLy/vsDqfN8XVxfTznZ9pc9Bm2hy0mXaG7aSr2VepXtL4wntGRgapq6vTxo0bW123n58fbdy4kU6dOkUA6PTp0x3ce9acLhk+f1VQUEAAKCQkhIiIXnrpJVq0aFGjY/72t7/R3LlzZZ9bc8yfSSQSGjp0KM2cObPRnaTExEQyNjamXbt2NVuuNXOGiNo/bygkJIQA0NGjR596bHdUW19Ll9Mv09aQrbQ5aDNtCdpC55LOUUVtRYtl3n//fVJXV6eMjIw2t9dS+MTFxZGPjw/p6OiQsbExrVmzRqEm88pDlzzt+qvS0lIAgL6+PgBg5MiRCAwMRHJyMgAgJiYG4eHh8PHxkZVpzTF/pqSkBD8/P0RHR2P+/PmQSqVIS0uDh4cHpk2bhvfee69JmdbMGQLaP29IIpHg7bffhouLC+bNm9eaX1W3QUSIexCHA5EHEJIVgnppPax6WWGJ8xK83P9laKlptVh2/fr1MDAw6LB5WdHR0Rg5ciScnJwQFRWF48eP49ixY9i1a1eH1K+whE6/p5FIJPTSSy/RqFGjGm1bt24diUQiUlFRIZFIRDt27GhS7mnHNCcrK4ssLS1pzpw5ZGlpSfPnzyepVNrssTk5OQSArl692mj7u+++Sy4uLrLPEyZMoA8++KDRMSdPniRra+sn9uXrr78mAHTt2rWn9rs7ySnLoW9vfSs7xfrs2md0t+Bui38Ozfn+++8JAAUFBbWpbTQz8hk+fDgtW7as0bYNGzY0+jNmbdflw2fp0qXUt29funfvnmzbsWPHyMLCgo4dO0axsbH03Xffkb6+Ph05cqRNx7Tk8alOv379SCwWt3hca8InMzOTAJCmpiZpaWnJfjQ0NMjOzq7FuouLi8nQ0JD+8Y9/PLW/3UV5bTn9mvArbQnaQpuDNtO2kG0UkhlCdfVNr8M9jUQiITc3tzY/kPnX8ElISCAAlJCQ0Oi4LVu20NChQ9vcL/aHLh0+y5cvJwsLC0pPT2+03cLCgg4cONBo29atW8ne3r5NxzQnPz+f7O3tacqUKWRiYkIrVqxo8dja2lpSVlZu8j/l/PnzaerUqUREdObMGdLX16eUlJQmP/fv32+x7jVr1pCWltYTj+kuxBIxhWeF0/bQ7bLRzi/xv1BpTekz1RsREUEA6NChQ60u89fwOXnyJKmqqpJEIml03OzZs2nevHnP1D9F1yUnlhIRVq5cidOnTyM4OBjW1taN9ldVVTV5iExZWRlSqbRNx/xVUVERPD094ejoiBMnTiA5ORnjxo2Duro6PvnkkybH/3nO0LRp0wD8MWdoxYoVANBo3lCPHj1a9f2TkpLw+eefY8uWLd16bhERIelhEi6kXcCj6kcAAHMdc/jY+cCip8Uz1+/i4oIFCxbg/fffx5w5c9CrV68216GjowOJRAKxWAx1dXUAQEZGBk6fPo2zZ88+cx8VmtDp1xxfX1/S1dWl4OBgysvLk/1UVVUREdGCBQvI3Nxcdhv91KlTZGBgQO+9956sjtYc82cSiYScnZ1p8uTJje5i3L59m/T19enTTz9tttyT5gwRUbvmDb300ktkZWUl+77d0YOKB3T09lHZSOeTK5/Q7bzbbbqu0xq5ubmkra1Nq1evbvGY8vJyio6OpujoaAJAn376KUVHR1NWVhaVlJSQvr4+rVq1itLS0igwMJAcHR0V6nRYXrpk+ABo9ufw4cNERFRWVkZvv/02WVpakoaGBvXr1482btzYKDRac8xfXbhwgaqrq5tsj4qKanTN6a+eNGeIqG3zhvz8/AgAbf5i8xN+Q8+vqroq+j35d/ow+EPaHLSZPgr+iC6lXaIacY3c2ly7aS2pqKg0uW7zWFBQULN/3xYsWEBERKGhoeTk5CT7e7Rz506e2NsBeG5XFyIWi+Ew0AF5lAejZUZw6+OGnZ47Ya1n/fTCXZyUpLiZexNBGUGorq8GADgaOGKizUToacrn1SDF1cW4kHYBcblxOLjwIFyGuMDf318ubbG24/DpQvbt24c1a9Zg9PbRyNLIAgCoKathuuN0bBmzBZpqz+eLrdKL0xGQGoCCygIAgLGWMbxtveUWqrX1tQjLDsO1e9cgIQmUREqou1OH7Su24/fff1fId153RRw+XURhYSHs7Ozw6quv4tChQ/gl/hfsurJL9g9WT1MPb7u8jddfeF3YjrbBo+pHOJ96HkkPG+bl9VDtgfFW4zHcbDiURB3/fCsRIeZBDC6lX0JFXcPcrn56/eBt6w3DHobw8vJCTk4OYmNjoaam1uHts7bh8OkifH19cezYMaSkpMDQ0BAAUCepw+4ru/FD7A+yUxVbfVtsG78Nbn3chOzuE9XW1yI0KxTX71+XjTxczF0wtu9YaKrKZ/R2r/QeAlIDkFOeAwDQ19THJJtJ6N+7v+wF+3FxcRg2bBg++eQTrF69ulX17ty5E6dOnUJiYiI0NTUxcuRI7Nq1C/b29nL5HoqEw6cLiImJgZOTE/79739j1apVTfbnV+Rjw6UNCM4KhpSkEIlEGGs5Fh9P+Bgm2iad3+EWEBFu599GYEagbORho2fTMPLQMpRLm2W1ZbiUfgmxD2IBAOrK6hjTdwxcLVyhotT0SZJly5bhxx9/RHJycqtWePX29sarr76KESNGoL6+Hhs2bMCdO3cQHx8PLa2Wp3iwp+PwERgRwcPDA/n5+YiNjYWqqmqLx0bej8T7Qe8j+WHDfDUNFQ3MHTwX60avg5qysKcR2aXZCEgNQG55w3uUe2v2xiTbSbDTt5PL0j5iiRjX7l9DWFYYxFIxRBBhmMkwePbzhLaadovlioqKYGdnh9mzZ+Orr75qc7uFhYUwMjJCSEgIxowZAwDIzs7G+vXr4e/vD5FIBB8fHxw4cIDfsf0UHD4CO3XqFGbMmAE/P78WJ73+1Xe3v8PeiL2yB/MMtQzx3sj3MGvgLHl2tVmlNaW4lH4JcQVxABpGHmOtxsLV3BXKSsod3h4RIaEoARfSLqCkpgQA0KdnH/jY+cBMx6xVdXz++edYtWoVoqKiMGzYsDa1n5qaCjs7O8TFxWHQoEFITU2Fu7s7fH19MXfuXFRUVGDZsmUYPHgwvv322zZ+O8XC4SOgmpoaODo6YsCAAfj999/bVLa6rhrbwrbhRPwJ1EnqAAADDQdip9dODDEeIo/uNiKWiHH13lWEZ4fLRh4vmL4AD2uPJ448nkV+RT78U/yRVdpwJ7Cnek9M6DcBg4wGtWl0JRaLMXToUBgZGSEoKKjVZaVSKaZOnYqSkhKEh4cDACZOnAh3d3d8+OGHsuN++eUXvPvuu0hPT2/Dt1M8HD4C2rFjBzZv3ow7d+60+wJmdmk21l9cj2v3r4FAUBYpY4LNBOzw3AF9Tf0O7nHDyONu4V1cTLuI0tqGV51Y6lrCx9YHpjqmHd4eAFTWVeJyxmVE5UWBQFBRUsFoy9EY2Wdku083z58/D29vb5w4cQIzZ85sVRlfX1/4+/sjPDwcFhYWyMrKgpWVFTQ1NRtN5ZFIJOjTp4/sdS6seRw+AsnJyYG9vT2WLFmCf//7389cX3BGMDYHb5aNCrTUtLBo2CKscl0FZeWOOf3JK8+Df6o/skuzAQC66rqYaDMRAwwHyOW6jkQqQWROJEKyQlBT37Ai6SCjQZjQbwJ0NXSfuf4pU6YgLi4OCQkJT10ccMWKFThz5gxCQ0Nlcw3Pnj2LhQsXtrjYYHeel9cROHwEMn/+fAQEBCA5ObldEx6bI5FI8PWtr3Hw1kGU1ZYBAMy0zfD+2Pcx2a79D9ZV1FXgcsZlROdFg0BQVVKVjTxUlVu+QP4sUh6mICA1AA+rHwIATLVN4W3rjb69+nZYG8nJyRg0aBA++OADvP/++80eQ3+Z5GxnZyfb5+/vj1deeQUlJSWtnjTM/sDhI4CIiAi4ubnhq6++wuLFizu8/oq6Cmy6vAm/Jf8GsVQMAHAydcLHnh+jv0H/VtcjkUoQkROBkMwQ1EpqAQCDjQbDq59Xh4w8mlNUVYTzqeeR8igFAKClqgXPfp4YZjJMLg8mrl27FgcPHkRycnKzI5XHt+bPnDnT6NRYV1cX1dXV6N+/P8aNG4dNmzZBS0sLqampCAgIwN69ezu8r90Nh08nk0qlGDlyJGpqanDr1q0OOyVqTnJRMv4V+C/cyrsFAFBVUsVL/V/CNo9tT7woTERIeZSC86nnZSMPMx0zeNt6w1LXUi59ramvQXBmMCJzIiElKZRFynC1cMWYvmOgoaIhlzYzSzJx+vZpbJ6xGVMnT8X/vv9fk2NaOp08fPgwXn/9dURGRmLdunWIiooCEcHOzg4LFizAW2+9JZc+dyccPp3s+++/x/z58xEcHIyxY8d2Spt+KX7YFrINuRUNz+D0VO8J3+G+WDx8cZPwK6wsxPm080h9lAoA0FbThqd1w8hDHtd1Hq/BdTnjMqrEVQAA+972mGgzEb179O7w9gCgpKYEF9Mu4m7hXQBAXEAcTu06hatXr8Ld3V0ubbKmOHw6UUVFBfr374/Ro0fj559/7tS2JRIJ9kbsxX+i/yP7R95Xty8+HPchxlmPQ7W4GsGZwbiRe0M28nDv444XLV+Euoq6XPqUWZKJgNQA5FfkAwAMexhiku0k2OrbyqW9OkkdrmRfwZV7V1AvrYcIIgw3G44xfcZg/OjxUFZWxvXr13m1007C4dOJNm7ciE8//RQJCQmwsrISpA+Pqh9hQ+AGXEy7CAlJIIIIVr2s4G7hDjWVhtvWDgYOmGgzUS636oGGV11cTL+I+MJ4AA1Pao+3Gg9nM2e5PZgYVxCHS+mXZBfirXpZwcfWB8baxgCAsLAwjBkzBkeOHMGCBQs6vA+sKQ6fTpKRkQFHR0e8++672Lp1q9DdQeyDWPzr0r9kpx4qSipwM3fDxhc3wtHIUS5t1knqEJ4djqv3rspGHs5mzhhvPR49VOVztyinLAcBqQG4V3YPANBLoxcm2UyCg4FDk9PIV199FaGhoUhKSoKOjo5c+sP+wOHTSWbOnInr168jKSmpS01I3B66HcfvHEd5XTn66vaFsbYxlgxfAs9+nh3WxuORx8W0iyivKwcAWPeyhrett2zk0dHKa8sRmBGI2/m3ATS8F+lFyxfh3se92QmnQMMcLXt7e6xevRo7duyQS7/YHzh8OkFQUBA8PDzwv//9D3PnzhW6O41E5UXh14RfUVRdhMySTNn1oIFGA7HKdRXsets9pYYnu192HwGpAbhfdh8AoKehh0m2k2Df214uF7DrpfW4fv86QrNCZdNOhhoPhVc/L+ioP300s2XLFuzcuRMJCQno169fh/eP/YHDR84kEgmcnJygpaWFK1euyOUf3LOIyovC2aSzsO9tDx87H3wR+QUCMwJlF529+nnBd4Qvemn0alO95bXluJR+CTEPYgA0jDzG9B0DNwu3Fkcez4L+fyWM86nnUVxTDACw6GkBb1vvNq2EUVVVBXt7e4wYMQKnTp3q8H6yP3TJpXO6k2+//RaxsbGIjIzscsHzV700emHjmI2YOWAm9kXsQ3xhPM6nnUd4djj+PuTvmDNwzlODo15aj2v3riEsO0w28hhmMgye1p6tGnm0R0FlAQJSA5Be3DCRU0dNB179vDDEeEibf+c9evTA7t278fe//x2XL1+Gh4eHPLrMwCMfuSouLoadnR1efvllHDlyROjuNOvPI5/XBr/WaN/FtIv4OuprFFYWAmhYU2v5iOUYaTmyST1EhMSiRFxIu9Bo5OFj6wPznvKZ41Qlrmp4PCDnhmzC6cg+IzHacvQzvd+IiPDiiy+irKwMUVFRUFHh/6PlgcNHjlavXo1vv/0WycnJMDWVz4zvZ/Wk8AGAuvo6HIk5glMJp2STO4ebDcfbrm/LnnZ+UPEAAakByCjJANAw8phgMwGDjQbLbcLpzdybCM4Mlr1edoDhAEzoN6HDVsK4desWRowYgS+++AK+vr4dUidrjMNHThISEjBkyBBs3boV69evF7o7LXpa+DxWUFGAAzcOICwrTDbK8LT2hK2+LeIL4zt05PEkaY/SEJAagMKqhtGYPFfCeOONN3DmzBkkJydDX18+zzwpMg4fOVm/fj1OnDiBu3fvQkNDPnOTOkJrw+ex2Aex+Dzic9n0C3Vlddjo22Bc33GYaDuxzRemW6u5lTA8rD3gZOoklwmnAJCfn4/+/fvjs88+wxtvvCGXNhQZh4+cEBHy8vJgZta6V3sKpa3hAzRMjj1+5zi+i/0ONfU1GGYyDEOMh8Db1rvVrzJtLSFWwviz3NzcLv9n+LziK2lyIhKJuu1fWiUlJbzU/yUkFCUgryIPhj0MkV2ajW9ufdOql7i3RnMrYdjq22KSzSS5rYTRnO76Z9gVcPiwdlNWUoZVLyusdF0pW74mOj8a8YXxT1y+5mk6eyUMJgwOH/bMeqr3xN8c/4YRZiNkC/ddTL+IW3m3mizc9yTNrYQxzmocXMxd5DLhlAmLw4d1mD66ffBPp38i9kEsLqZfxKPqRzh25xhs9GwwyXYSjLSaX6RPLBHjyr0ruJJ9RbYShpOpEzysPaCl1nXmwbGOxeHDOpRIJMJQk6FwMHCQzWBPK07DoZuHGmawW42XXShubiWMvrp94W3rLbeVMFjXweHD5EJdRR2e/TzhZOqEC2kXkFCUgMicSMQ9iMN46/Ew1zHH+bTznbYSBut6OHyYXOlp6mHOoDnIKM5AQGoAHlQ+gF+Kn2x/Z6yEwbomDh/WKaz1rLHEeQlu5d7C7ykNq7Ma9DDA/KHz0VO9p8C9Y0Lgl9WyTqMkUsII8xHordnwYviX+7/MwaPAOHxYp+NrOgzg8GGMCYTDhzEmCA4fxpggOHwYY4Lg8GGMCYLDhzEmCA4fxpggOHwYY4Lg8GGMCYLDhzEmCA4fxpggOHwYY4Lg8GGMCYLDhzEmCA4fxpggOHwYY4Lg8GGMCYLDhzEmCA4fxpggOHwYY4Lg8GGMCYLDhzEmCA4fxpggOHwYY4Lg8GGMCYLDhzEmCA4fxpggOHwYY4Lg8GGMCYLDhzEmCA4fxpggOHwYY4Lg8GGMCYLDhzEmCA4fxpggOHwYY4Lg8GGMCUJF6A6wjnP79m3cv3+/TWVSH6UiOScZlTqV0MnSaXW5yrpKJCclQ1mkjN/KfmtTm3eS76CstgyB+YEw1jZuU1l7e3vY2dm1qQzrmkREREJ3gnWMhQsX4siRI0J3Q662b9+ODRs2CN0N1gE4fLqR0tJS1NTUCN0NudLW1oaWlpbQ3WAdgMOHMSYIvuDMGBMEhw9jTBAcPowxQXD4MMYEweGjgEpLS7F48WLY2trC0dEReXl5rSpXX1+P7du3w93dHU5OTliwYAEuXrwot/ZY98bho4CWL1+OuLg47N69G1lZWaiurgYArF69GgcOHGix3Pr16/Hll1/C09MT06ZNQ21tLV5++WUsXLgQT7pp2t72WDdHTOHo6+tTVFQUERFpa2tTWloaERH5+/uTs7Nzi+VMTU0pJCSk0bb09HQaMGAA7d69u8PbY90bj3wUEBFBR6fpVAo7OzukpKS0WK6yshIWFhaNtllbW2P//v34+uuvO7w91r1x+CggHx8f/PDDD022V1ZWQiQStVhu9OjROHr0aJPt1tbWyM3N7fD2WPfGE0sV0M6dO+Hs7AygYVQiEolQU1ODrVu3wsnJqcVyu3btwqhRo1BcXIyVK1fCzs4OYrEY+/fvx4ABAzq8PdbNCXvWx4SSkpJCEydOJJFIRAYGBqSurk6GhoZ048aNJ5aLiooiZ2dnEolEpK6uTioqKmRgYEDh4eFyaY91Xzy3S8FlZ2cjJiYGqqqqcHV1hZ6eXqvKJSUl4e7du9DR0YGrqyt69uwp1/ZY98PhwxgTBF9wVjBFRUXYvXs3pk+fDnd3d7i7u2P69OnYs2cPCgsL21XnvXv3sGjRomb3VVdXIzw8HPHx8U321dTU4LvvvmtXm+z5xyMfBXLjxg1MmjQJPXr0gJeXF4yNG94i+ODBAwQGBqKqqgrnz5+XXRxurZiYGDg5OUEikTTanpycjIkTJyI7OxsikQijR4/G8ePHYWpqKmvXzMysSTmmGDh8FIibmxuGDh2KQ4cONbnFTURYunQpYmNjce3atUb7zp49+8R609PT8c477zQJkenTp0MsFuPIkSMoKSnBqlWrEB8fj+DgYFhaWnL4KDgOHwWiqamJ6OhoODg4NLs/MTERL7zwgmz6w2NKSkoQiURPnEIhEomahIixsTEuXbqEwYMHA2gIuGXLlsHPzw9BQUHQ0tLi8FFgfM1HgZiYmCAyMrLF/ZGRkbJTsT8zNTXFqVOnIJVKm/2Jiopqtr7q6mqoqPzxKJlIJMLBgwcxZcoUjB07FsnJyc/+pdhzix8yVCBr167F4sWLcevWLXh6eja55vPNN9/gk08+aVJu+PDhuHXrFl555ZVm621pVOTg4ICbN2/C0dGx0fbHk0mnTp36rF+JPc+EeLiICef48ePk6upKKioqJBKJSCQSkYqKCrm6utJPP/3UbJnQ0FDy9/dvsc6KigoKDg5usn3Hjh3k4+PTYjlfX18SiURt/xKsW+BrPgpKLBajqKgIAGBgYABVVVWBe8QUDYcPY0wQfMGZMSYIDh/GmCA4fBhjguDwUTAFBQXN3k4HgH379rX4UrDOLscUgLA321hni4+PJxMTE1q2bFmj7WvXriUDAwO6fft2lyjHuj8OHwWUmJhI5ubmtHDhQpJIJLRy5UoyNjammJiYLlWOdW98q11BpaWlwdPTE6qqqqiqqsKlS5eaPIncFcqx7ouv+SgoGxsbuLu7Iy0tDSNGjIC9vX2XLMe6Lw4fBUREmDdvHq5fv46QkBAkJSVh9uzZqK+v71LlWDcn6Ekf63RisZhmzZpFtra2lJ2dTURE+fn5NGjQIJoyZQrV1tZ2iXKs++ORj4KJjIxESkoKwsLC0KdPHwAN790JCgpCfn4+wsLCukQ51v3xBWcFRP+/dlZrtwtVjnVvHD6MMUHwaRdjTBAcPowxQXD4MMYEweHDGBMEh4+Cae8Kop1djikAoR4wYp0vKSmJ+vbtSyKRiJSUlGjMmDGUm5sr25+fn09KSkqCl2OKgUc+CmTdunUYNGgQCgoKkJSUBB0dHYwaNQrZ2dldqhxTEEKnH+s8RkZGFBsbK/sslUpp6dKlZGlpSWlpaS2ORDq7HFMMPPJRIO1dQbSzyzHFwCuWKpD2riDa2eWYYuCRjwKZPn06jh071uy+AwcO4LXXXmt22ePOLscUA8/tYowJgkc+CiYhIQGHDx9GYmIiACAxMRG+vr5YtGgRLl++3GXKMQUg6OVu1qn8/f1JTU2N9PX1SUNDg/z9/cnQ0JC8vLzIw8ODlJWVKTAwUPByTDFw+CgQd3d32rhxIxERHTt2jPT09GjDhg2y/evXr6cJEyYIXo4pBg4fBdKzZ09KSUkhIiKJREIqKioUFRUl2x8XF0fGxsaCl2OKga/5KJjHbw5UUlKChoYGdHV1Zft0dHRQWlraJcqx7o/DR4FYWVkhJSVF9vnatWuwtLSUfc7Ozoapqang5Zhi4IcMFYivry8kEons86BBgxrt9/f3h4eHh+DlmGLg53wYY4Lg0y7GmCA4fBhjguDwYYwJgsOHMSYIDh/GmCA4fBhjguDwYYwJgsOHMSYIDh/GmCA4fBhjgvg/i+GuXSCVwbgAAAAASUVORK5CYII=", + "image/png": "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", "text/plain": [ "
" ] @@ -936,7 +941,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 49, "id": "8e448746-66f8-484f-8971-1c89c8f75135", "metadata": {}, "outputs": [], @@ -1043,7 +1048,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 50, "id": "6affd332-fe3a-49dd-b0c2-e20fcb974a04", "metadata": {}, "outputs": [], @@ -1172,7 +1177,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 51, "id": "2d7ce968-f33c-46dd-88e3-8ad47480a9e7", "metadata": {}, "outputs": [ @@ -1198,7 +1203,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 52, "id": "9c89a710-1e68-485c-9325-0b06c7b52b75", "metadata": {}, "outputs": [], @@ -1210,10 +1215,29 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 56, "id": "ef3c9902-3673-4c88-a98e-1a4553a5990f", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + " warnings.warn(\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + " warnings.warn(\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + " warnings.warn(\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + " warnings.warn(\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + " warnings.warn(\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", + " warnings.warn(\n" + ] + } + ], "source": [ "lit_mod = EquiTritonLitModule(\n", " EquiTritonModel,\n", @@ -1221,7 +1245,7 @@ " \"initial_atom_dim\": 64,\n", " \"num_layers\": 3,\n", " \"output_dim\": 48,\n", - " \"l_values\": [0, 1, 2, 4, 6, 8],\n", + " \"l_values\": [0, 1, 2, 5, 6],\n", " \"edge_dim\": 10,\n", " \"hidden_dim\": 16,\n", " \"radius_cutoff\": 6.0,\n", @@ -1235,20 +1259,93 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 57, "id": "1104fe2c-ada6-448b-9e3d-cab1bc491c95", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "GPU available: True (cuda), used: True\n", + "TPU available: False, using: 0 TPU cores\n", + "IPU available: False, using: 0 IPUs\n", + "HPU available: False, using: 0 HPUs\n" + ] + } + ], "source": [ "trainer = pl.Trainer(max_epochs=30, accelerator=\"gpu\")" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 58, "id": "e30a0671-efa3-403b-9d8b-cc2cb63bff20", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]\n", + "\n", + " | Name | Type | Params\n", + "------------------------------------------------\n", + "0 | model | EquiTritonModel | 630 K \n", + "1 | loss | MSELoss | 0 \n", + "2 | output_head | Linear | 49 \n", + "------------------------------------------------\n", + "630 K Trainable params\n", + "0 Non-trainable params\n", + "630 K Total params\n", + "2.522 Total estimated model params size (MB)\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Sanity Checking: | …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/pytorch_lightning/trainer/connectors/data_connector.py:441: The 'val_dataloader' does not have many workers which may be a bottleneck. Consider increasing the value of the `num_workers` argument` to `num_workers=27` in the `DataLoader` to improve performance.\n", + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/pytorch_lightning/trainer/connectors/data_connector.py:441: The 'train_dataloader' does not have many workers which may be a bottleneck. Consider increasing the value of the `num_workers` argument` to `num_workers=27` in the `DataLoader` to improve performance.\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "592377c70fe5446c8474681ceb2ef7c0", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Training: | …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/pytorch_lightning/trainer/call.py:54: Detected KeyboardInterrupt, attempting graceful shutdown...\n" + ] + } + ], "source": [ "trainer.fit(lit_mod, datamodule=dm)" ] From a4c782966dabf826550a845080a0d28046b990ee Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Wed, 28 Aug 2024 12:26:44 -0700 Subject: [PATCH 050/116] chore: bumping version to 0.2.0 --- src/equitriton/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equitriton/__init__.py b/src/equitriton/__init__.py index de4cdee..4e69aba 100644 --- a/src/equitriton/__init__.py +++ b/src/equitriton/__init__.py @@ -26,4 +26,4 @@ if _will_patch: from equitriton import patch # noqa: F401 -__version__ = "0.1.0" +__version__ = "0.2.0" From cc7ab79293823c36a22bfd29685885f4ef57e29a Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Wed, 28 Aug 2024 12:33:51 -0700 Subject: [PATCH 051/116] chore: adding jsonargparse as dependency --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 514cfc5..4cbc7fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,8 @@ train = [ "torch-scatter", "torch-sparse", "torch-cluster", - "pytorch-lightning==2.2.4" + "pytorch-lightning==2.2.4", + "jsonargparse[signatures]" ] [tool.setuptools.dynamic] From a60ab7802ba4988e99acac596cc5ff35d675a8e4 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Wed, 28 Aug 2024 12:36:46 -0700 Subject: [PATCH 052/116] feat: added CLI script to train QM9 --- scripts/train_model_qm9.py | 231 +++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 scripts/train_model_qm9.py diff --git a/scripts/train_model_qm9.py b/scripts/train_model_qm9.py new file mode 100644 index 0000000..8dd261a --- /dev/null +++ b/scripts/train_model_qm9.py @@ -0,0 +1,231 @@ +from __future__ import annotations + +from math import ceil +from typing import Literal + +import pytorch_lightning as pl +from pytorch_lightning.cli import LightningCLI +import torch +from torch.optim.adamw import AdamW +from torch import nn +from torch.utils.data import DataLoader +from torch.utils.data import random_split +from torch_geometric.datasets import QM9 +from torch_geometric.data import Data as PyGGraph + + +class LightningQM9(pl.LightningDataModule): + def __init__( + self, + root_path: str = "./qm9_data", + batch_size: int = 16, + train_frac: float = 0.8, + val_frac: float = 0.1, + num_workers: int = 0, + ): + """ + Custom data module for QM9 dataset. + + Parameters + ---------- + root_path : str, optional (default: "./qm9_data") + Path to the QM9 dataset. + batch_size : int, optional (default: 16) + Number of samples in each mini-batch. + train_frac : float, optional (default: 0.8) + Fraction of data used for training. + val_frac : float, optional (default: 0.1) + Fraction of data used for validation. + num_workers : int, optional (default: 0) + Number of worker processes to use for loading data. + + Examples + -------- + >>> dm = LightningQM9(root_path="/path/to/qm9_data", batch_size=32) + + Attributes + ---------- + dataset : QM9 + Loaded QM9 dataset. + hparams : dict + Hyperparameters of the data module. + + Methods + ------- + setup(stage: str) + Setup data splits for training, validation and testing. + train_dataloader() + Returns a DataLoader instance for training data. + val_dataloader() + Returns a DataLoader instance for validation data. + test_dataloader() + Returns a DataLoader instance for testing data. + """ + super().__init__() + self.dataset = QM9(root_path) + self.save_hyperparameters() + + def setup(self, stage: str): + hparams = self.hparams + num_samples = len(self.dataset) + num_train = int(num_samples * hparams["train_frac"]) + num_val = int(num_samples * hparams["val_frac"]) + num_test = ceil( + num_samples * (1 - (hparams["train_frac"] + hparams["val_frac"])) + ) + # generate random splits + train_split, val_split, test_split = random_split( + self.dataset, lengths=[num_train, num_val, num_test] + ) + self.splits = {"train": train_split, "val": val_split, "test": test_split} + + def train_dataloader(self): + return DataLoader( + self.splits["train"], + batch_size=self.hparams["batch_size"], + shuffle=True, + num_workers=self.hparams["num_workers"], + ) + + def val_dataloader(self): + return DataLoader( + self.splits["val"], + batch_size=self.hparams["batch_size"], + shuffle=False, + num_workers=self.hparams["num_workers"], + ) + + def test_dataloader(self): + return DataLoader( + self.splits["test"], + batch_size=self.hparams["batch_size"], + shuffle=False, + num_workers=self.hparams["num_workers"], + ) + + +class AtomWeightedMSE(nn.Module): + """ + Calculates the mean-squared-error between predicted and targets, + weighted by the number of atoms within each graph. + + From matsciml + """ + + def forward( + self, + input: torch.Tensor, + target: torch.Tensor, + atoms_per_graph: torch.Tensor, + ) -> torch.Tensor: + if atoms_per_graph.size(0) != target.size(0): + raise RuntimeError( + "Dimensions for atom-weighted loss do not match:" + f" expected atoms_per_graph to have {target.size(0)} elements; got {atoms_per_graph.size(0)}." + "This loss is intended to be applied to scalar targets only." + ) + # check to make sure we are broad casting correctly + if (input.ndim != target.ndim) and target.size(-1) == 1: + input.unsqueeze_(-1) + # for N-d targets, we might want to keep unsqueezing + while atoms_per_graph.ndim < target.ndim: + atoms_per_graph.unsqueeze_(-1) + # ensures that atoms_per_graph is type cast correctly + squared_error = ((input - target) / atoms_per_graph.to(input.dtype)) ** 2.0 + return squared_error.mean() + + +class EquiTritonLitModule(pl.LightningModule): + def __init__( + self, + model_class: type, + model_kwargs, + e_mean: float, + e_std: float, + lr: float = 1e-3, + weight_decay: float = 0.0, + atom_weighted_loss: bool = True, + ): + """ + Initializes the EquiTritonLitModule clas. + + Parameters + ---------- + model_class : type + Th class of the model to be used. + model_kwargs : dict + Keyword argument for the model initialization. + e_mean : float + The mean of the energy values. + e_std : float + The standard deviation of the energy values. + lr : float, optional + The learning rate (default is 1e-3) for AdamW. + weight_decay : float, optional + Weight decay value (default is 0.0). + atom_weighted_loss : bool, optional + Whether to use atom-weighted loss or not (default is True). + """ + super().__init__() + self.model = model_class(**model_kwargs) + if atom_weighted_loss: + self.loss = AtomWeightedMSE() + else: + self.loss = nn.MSELoss() + self.output_head = nn.Linear(self.model.output_dim, 1) + self.save_hyperparameters() + + def configure_optimizers(self): + return AdamW( + self.parameters(), + lr=self.hparams["lr"], + weight_decay=self.hparams["weight_decay"], + ) + + def step(self, graph: PyGGraph, stage: Literal["train", "test", "val"]): + """ + Performs a single step of the training, validation or testing + process. + + Parameters + ---------- + graph : PyGGraph + The input graph. + stage : Literal["train", "test", "val"] + The current stage (training, testing or validation). + + Returns + ------- + loss : float + The calculated loss value. + """ + g_z, z = self.model(graph) + pred_energy = self.output_head(g_z) + target_energy = graph.y[:, 12].unsqueeze(-1) + norm_energy = (target_energy - self.hparams["e_mean"]) / self.hparams["e_std"] + if self.hparams["atom_weighted_loss"]: + loss = self.loss(pred_energy, norm_energy, torch.diff(graph.ptr)) + else: + loss = self.loss(pred_energy, norm_energy) + batch_size = getattr(graph, "batch_size", 1) + self.log( + f"{stage}_loss", loss, prog_bar=True, on_step=True, batch_size=batch_size + ) + return loss + + def training_step(self, batch): + loss = self.step(batch, "train") + return loss + + def validation_step(self, batch): + loss = self.step(batch, "val") + return loss + + def test_step(self, batch): + loss = self.step(batch, "test") + return loss + + +if __name__ == "__main__": + # use LightningCLI for easy configuration + cli = LightningCLI(EquiTritonLitModule, LightningQM9) From fc5136b2a902dad86d0cbe333900956911b5448c Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Wed, 28 Aug 2024 15:20:42 -0700 Subject: [PATCH 053/116] fix: resolving e3nn nn import Signed-off-by: Kin Long Kelvin Lee --- src/equitriton/model/blocks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/equitriton/model/blocks.py b/src/equitriton/model/blocks.py index d84a86e..c748808 100644 --- a/src/equitriton/model/blocks.py +++ b/src/equitriton/model/blocks.py @@ -6,6 +6,7 @@ from torch import nn import e3nn from e3nn import o3 +from e3nn import nn as e3nn_nn from torch_scatter import scatter from matplotlib import pyplot as plt from torch_geometric.data import Data as PyGGraph @@ -219,7 +220,7 @@ def __init__( self.spherical_harmonics = SphericalHarmonicEmbedding( l_values, **sph_harm_kwargs ) - self.fc = e3nn.nn.FullyConnectedNet( + self.fc = e3nn_nn.FullyConnectedNet( [edge_dim, hidden_dim, self.tensor_product.weight_numel], activation ) From 783245f7486f096a49c19bb1775f481bd84a5218 Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Wed, 28 Aug 2024 15:27:31 -0700 Subject: [PATCH 054/116] fix: correcting data loader import to pyg Signed-off-by: Kin Long Kelvin Lee --- scripts/train_model_qm9.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/train_model_qm9.py b/scripts/train_model_qm9.py index 8dd261a..d848706 100644 --- a/scripts/train_model_qm9.py +++ b/scripts/train_model_qm9.py @@ -8,10 +8,10 @@ import torch from torch.optim.adamw import AdamW from torch import nn -from torch.utils.data import DataLoader from torch.utils.data import random_split from torch_geometric.datasets import QM9 from torch_geometric.data import Data as PyGGraph +from torch_geometric.loader import DataLoader class LightningQM9(pl.LightningDataModule): From 95edbd4b2a705af4bd86988762e335a823b3abb8 Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Thu, 29 Aug 2024 10:56:08 -0700 Subject: [PATCH 055/116] fix: addressing config overwrite and data loader spawning Signed-off-by: Kin Long Kelvin Lee --- scripts/train_model_qm9.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/scripts/train_model_qm9.py b/scripts/train_model_qm9.py index d848706..672dc74 100644 --- a/scripts/train_model_qm9.py +++ b/scripts/train_model_qm9.py @@ -80,27 +80,33 @@ def setup(self, stage: str): self.splits = {"train": train_split, "val": val_split, "test": test_split} def train_dataloader(self): + num_workers = self.hparams["num_workers"] return DataLoader( self.splits["train"], batch_size=self.hparams["batch_size"], shuffle=True, - num_workers=self.hparams["num_workers"], + num_workers=num_workers, + persistent_workers=True if num_workers > 0 else False, ) def val_dataloader(self): + num_workers = self.hparams["num_workers"] return DataLoader( self.splits["val"], batch_size=self.hparams["batch_size"], shuffle=False, - num_workers=self.hparams["num_workers"], + num_workers=num_workers, + persistent_workers=True if num_workers > 0 else False, ) def test_dataloader(self): + num_workers = self.hparams["num_workers"] return DataLoader( self.splits["test"], batch_size=self.hparams["batch_size"], shuffle=False, - num_workers=self.hparams["num_workers"], + num_workers=num_workers, + persistent_workers=True if num_workers > 0 else False, ) @@ -227,5 +233,8 @@ def test_step(self, batch): if __name__ == "__main__": + torch.multiprocessing.set_start_method("spawn") # use LightningCLI for easy configuration - cli = LightningCLI(EquiTritonLitModule, LightningQM9) + cli = LightningCLI( + EquiTritonLitModule, LightningQM9, save_config_kwargs={"overwrite": True} + ) From 4674a9203975358a5b2865cb5ce3c28367782948 Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Thu, 29 Aug 2024 10:56:28 -0700 Subject: [PATCH 056/116] config: added config files for QM9 experiments Signed-off-by: Kin Long Kelvin Lee --- scripts/model_configs/baseline.yaml | 46 +++++++++++++++++++++++++ scripts/model_configs/baseline_big.yaml | 46 +++++++++++++++++++++++++ scripts/model_configs/equivariant.yaml | 46 +++++++++++++++++++++++++ scripts/model_configs/even.yaml | 46 +++++++++++++++++++++++++ scripts/model_configs/full.yaml | 46 +++++++++++++++++++++++++ scripts/model_configs/skipped.yaml | 46 +++++++++++++++++++++++++ 6 files changed, 276 insertions(+) create mode 100644 scripts/model_configs/baseline.yaml create mode 100644 scripts/model_configs/baseline_big.yaml create mode 100644 scripts/model_configs/equivariant.yaml create mode 100644 scripts/model_configs/even.yaml create mode 100644 scripts/model_configs/full.yaml create mode 100644 scripts/model_configs/skipped.yaml diff --git a/scripts/model_configs/baseline.yaml b/scripts/model_configs/baseline.yaml new file mode 100644 index 0000000..d64fe14 --- /dev/null +++ b/scripts/model_configs/baseline.yaml @@ -0,0 +1,46 @@ +# pytorch_lightning==2.2.4 +seed_everything: 21616 +trainer: + accelerator: auto + strategy: auto + devices: auto + num_nodes: 1 + precision: null + logger: + class_path: pytorch_lightning.loggers.WandbLogger + init_args: + project: "equitriton-qm9" + entity: "laserkelvin" + log_model: true + callbacks: + - class_path: pytorch_lightning.callbacks.EarlyStopping + init_args: + monitor: "val_loss_epoch" + patience: 5 + mode: "min" + max_epochs: 300 + min_epochs: 15 +model: + model_class: equitriton.model.EquiTritonModel + model_kwargs: + initial_atom_dim: 64 + num_layers: 3 + output_dim: 1 + l_values: [0,] + edge_dim: 20 + hidden_dim: 32 + radius_cutoff: 6.0 + degree_norm: 6.08275253 # sqrt(37), avg degree + sph_harm_kwargs: + use_e3nn: false + e_mean: -76.1160 + e_std: 10.3238 + lr: 0.001 + weight_decay: 0.0 + atom_weighted_loss: false +data: + root_path: ./qm9_data + batch_size: 32 + train_frac: 0.8 + val_frac: 0.1 + num_workers: 4 diff --git a/scripts/model_configs/baseline_big.yaml b/scripts/model_configs/baseline_big.yaml new file mode 100644 index 0000000..284e3fa --- /dev/null +++ b/scripts/model_configs/baseline_big.yaml @@ -0,0 +1,46 @@ +# pytorch_lightning==2.2.4 +seed_everything: 21616 +trainer: + accelerator: auto + strategy: auto + devices: auto + num_nodes: 1 + precision: null + logger: + class_path: pytorch_lightning.loggers.WandbLogger + init_args: + project: "equitriton-qm9" + entity: "laserkelvin" + log_model: true + callbacks: + - class_path: pytorch_lightning.callbacks.EarlyStopping + init_args: + monitor: "val_loss_epoch" + patience: 5 + mode: "min" + max_epochs: 300 + min_epochs: 15 +model: + model_class: equitriton.model.EquiTritonModel + model_kwargs: + initial_atom_dim: 64 + num_layers: 3 + output_dim: 1 + l_values: [0,] + edge_dim: 20 + hidden_dim: 512 + radius_cutoff: 6.0 + degree_norm: 6.08275253 # sqrt(37), avg degree + sph_harm_kwargs: + use_e3nn: false + e_mean: -76.1160 + e_std: 10.3238 + lr: 0.001 + weight_decay: 0.0 + atom_weighted_loss: false +data: + root_path: ./qm9_data + batch_size: 32 + train_frac: 0.8 + val_frac: 0.1 + num_workers: 4 diff --git a/scripts/model_configs/equivariant.yaml b/scripts/model_configs/equivariant.yaml new file mode 100644 index 0000000..7dd9a43 --- /dev/null +++ b/scripts/model_configs/equivariant.yaml @@ -0,0 +1,46 @@ +#pytorch_lightning==2.2.4 +seed_everything: 21616 +trainer: + accelerator: auto + strategy: auto + devices: auto + num_nodes: 1 + precision: null + logger: + class_path: pytorch_lightning.loggers.WandbLogger + init_args: + project: "equitriton-qm9" + entity: "laserkelvin" + log_model: true + callbacks: + - class_path: pytorch_lightning.callbacks.EarlyStopping + init_args: + monitor: "val_loss_epoch" + patience: 5 + mode: "min" + max_epochs: 300 + min_epochs: 15 +model: + model_class: equitriton.model.EquiTritonModel + model_kwargs: + initial_atom_dim: 64 + num_layers: 3 + output_dim: 1 + l_values: [0, 1, 2] # this is the canonical set + edge_dim: 20 + hidden_dim: 32 + radius_cutoff: 6.0 + degree_norm: 6.08275253 # sqrt(37), avg degree + sph_harm_kwargs: + use_e3nn: false + e_mean: -76.1160 + e_std: 10.3238 + lr: 0.001 + weight_decay: 0.0 + atom_weighted_loss: false +data: + root_path: ./qm9_data + batch_size: 32 + train_frac: 0.8 + val_frac: 0.1 + num_workers: 4 diff --git a/scripts/model_configs/even.yaml b/scripts/model_configs/even.yaml new file mode 100644 index 0000000..86b968f --- /dev/null +++ b/scripts/model_configs/even.yaml @@ -0,0 +1,46 @@ +# pytorch_lightning==2.2.4 +seed_everything: 21616 +trainer: + accelerator: auto + strategy: auto + devices: auto + num_nodes: 1 + precision: null + logger: + class_path: pytorch_lightning.loggers.WandbLogger + init_args: + project: "equitriton-qm9" + entity: "laserkelvin" + log_model: true + callbacks: + - class_path: pytorch_lightning.callbacks.EarlyStopping + init_args: + monitor: "val_loss_epoch" + patience: 5 + mode: "min" + max_epochs: 300 + min_epochs: 15 +model: + model_class: equitriton.model.EquiTritonModel + model_kwargs: + initial_atom_dim: 64 + num_layers: 3 + output_dim: 1 + l_values: [0, 1, 2, 4, 6, 8, 10] # this is just even parity + edge_dim: 20 + hidden_dim: 32 + radius_cutoff: 6.0 + degree_norm: 6.08275253 # sqrt(37), avg degree + sph_harm_kwargs: + use_e3nn: false + e_mean: -76.1160 + e_std: 10.3238 + lr: 0.001 + weight_decay: 0.0 + atom_weighted_loss: false +data: + root_path: ./qm9_data + batch_size: 32 + train_frac: 0.8 + val_frac: 0.1 + num_workers: 4 diff --git a/scripts/model_configs/full.yaml b/scripts/model_configs/full.yaml new file mode 100644 index 0000000..981f484 --- /dev/null +++ b/scripts/model_configs/full.yaml @@ -0,0 +1,46 @@ +# pytorch_lightning==2.2.4 +seed_everything: 21616 +trainer: + accelerator: auto + strategy: auto + devices: auto + num_nodes: 1 + precision: null + logger: + class_path: pytorch_lightning.loggers.WandbLogger + init_args: + project: "equitriton-qm9" + entity: "laserkelvin" + log_model: true + callbacks: + - class_path: pytorch_lightning.callbacks.EarlyStopping + init_args: + monitor: "val_loss_epoch" + patience: 5 + mode: "min" + max_epochs: 300 + min_epochs: 15 +model: + model_class: equitriton.model.EquiTritonModel + model_kwargs: + initial_atom_dim: 64 + num_layers: 3 + output_dim: 1 + l_values: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # this up to l=10 + edge_dim: 20 + hidden_dim: 32 + radius_cutoff: 6.0 + degree_norm: 6.08275253 # sqrt(37), avg degree + sph_harm_kwargs: + use_e3nn: false + e_mean: -76.1160 + e_std: 10.3238 + lr: 0.001 + weight_decay: 0.0 + atom_weighted_loss: false +data: + root_path: ./qm9_data + batch_size: 32 + train_frac: 0.8 + val_frac: 0.1 + num_workers: 4 diff --git a/scripts/model_configs/skipped.yaml b/scripts/model_configs/skipped.yaml new file mode 100644 index 0000000..076a1a5 --- /dev/null +++ b/scripts/model_configs/skipped.yaml @@ -0,0 +1,46 @@ +# pytorch_lightning==2.2.4 +seed_everything: 21616 +trainer: + accelerator: auto + strategy: auto + devices: auto + num_nodes: 1 + precision: null + logger: + class_path: pytorch_lightning.loggers.WandbLogger + init_args: + project: "equitriton-qm9" + entity: "laserkelvin" + log_model: true + callbacks: + - class_path: pytorch_lightning.callbacks.EarlyStopping + init_args: + monitor: "val_loss_epoch" + patience: 5 + mode: "min" + max_epochs: 300 + min_epochs: 15 +model: + model_class: equitriton.model.EquiTritonModel + model_kwargs: + initial_atom_dim: 64 + num_layers: 3 + output_dim: 1 + l_values: [0, 1, 2, 5, 6] # is a higher order odd/even pair + edge_dim: 20 + hidden_dim: 32 + radius_cutoff: 6.0 + degree_norm: 6.08275253 # sqrt(37), avg degree + sph_harm_kwargs: + use_e3nn: false + e_mean: -76.1160 + e_std: 10.3238 + lr: 0.001 + weight_decay: 0.0 + atom_weighted_loss: false +data: + root_path: ./qm9_data + batch_size: 32 + train_frac: 0.8 + val_frac: 0.1 + num_workers: 4 From 4a66d62ec8e5ad01527b9e14bd70e4b77d412662 Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Thu, 29 Aug 2024 11:23:28 -0700 Subject: [PATCH 057/116] git: ignoring logging and dataset folders Signed-off-by: Kin Long Kelvin Lee --- scripts/.gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 scripts/.gitignore diff --git a/scripts/.gitignore b/scripts/.gitignore new file mode 100644 index 0000000..c226bbf --- /dev/null +++ b/scripts/.gitignore @@ -0,0 +1,3 @@ +qm9_data/ +wandb/ +lightning_logs/ From c35864a9772c100ad55e3cea9d212035cb4708e7 Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Thu, 29 Aug 2024 11:28:21 -0700 Subject: [PATCH 058/116] chore: added requirements file for experiments Signed-off-by: Kin Long Kelvin Lee --- scripts/experiment-requirements.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 scripts/experiment-requirements.txt diff --git a/scripts/experiment-requirements.txt b/scripts/experiment-requirements.txt new file mode 100644 index 0000000..ecff73f --- /dev/null +++ b/scripts/experiment-requirements.txt @@ -0,0 +1,3 @@ +phate==1.0.11 +rdkit==2023.9.5 +wandb==0.17.7 From 013c9ce973c58810f93213cc9b8e1e19bcca819f Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Thu, 29 Aug 2024 11:34:32 -0700 Subject: [PATCH 059/116] refactor: moving lightning components to equitriton module Signed-off-by: Kin Long Kelvin Lee --- src/equitriton/model/lightning.py | 231 ++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 src/equitriton/model/lightning.py diff --git a/src/equitriton/model/lightning.py b/src/equitriton/model/lightning.py new file mode 100644 index 0000000..81cd5c0 --- /dev/null +++ b/src/equitriton/model/lightning.py @@ -0,0 +1,231 @@ +from __future__ import annotations + +from math import ceil +from typing import Literal + +import pytorch_lightning as pl +import torch +from torch.optim.adamw import AdamW +from torch import nn +from torch.utils.data import random_split +from torch_geometric.datasets import QM9 +from torch_geometric.data import Data as PyGGraph +from torch_geometric.loader import DataLoader + + +class LightningQM9(pl.LightningDataModule): + def __init__( + self, + root_path: str = "./qm9_data", + batch_size: int = 16, + train_frac: float = 0.8, + val_frac: float = 0.1, + num_workers: int = 0, + ): + """ + Custom data module for QM9 dataset. + + Parameters + ---------- + root_path : str, optional (default: "./qm9_data") + Path to the QM9 dataset. + batch_size : int, optional (default: 16) + Number of samples in each mini-batch. + train_frac : float, optional (default: 0.8) + Fraction of data used for training. + val_frac : float, optional (default: 0.1) + Fraction of data used for validation. + num_workers : int, optional (default: 0) + Number of worker processes to use for loading data. + + Examples + -------- + >>> dm = LightningQM9(root_path="/path/to/qm9_data", batch_size=32) + + Attributes + ---------- + dataset : QM9 + Loaded QM9 dataset. + hparams : dict + Hyperparameters of the data module. + + Methods + ------- + setup(stage: str) + Setup data splits for training, validation and testing. + train_dataloader() + Returns a DataLoader instance for training data. + val_dataloader() + Returns a DataLoader instance for validation data. + test_dataloader() + Returns a DataLoader instance for testing data. + """ + super().__init__() + self.dataset = QM9(root_path) + self.save_hyperparameters() + + def setup(self, stage: str): + hparams = self.hparams + num_samples = len(self.dataset) + num_train = int(num_samples * hparams["train_frac"]) + num_val = int(num_samples * hparams["val_frac"]) + num_test = ceil( + num_samples * (1 - (hparams["train_frac"] + hparams["val_frac"])) + ) + # generate random splits + train_split, val_split, test_split = random_split( + self.dataset, lengths=[num_train, num_val, num_test] + ) + self.splits = {"train": train_split, "val": val_split, "test": test_split} + + def train_dataloader(self): + num_workers = self.hparams["num_workers"] + return DataLoader( + self.splits["train"], + batch_size=self.hparams["batch_size"], + shuffle=True, + num_workers=num_workers, + persistent_workers=True if num_workers > 0 else False, + ) + + def val_dataloader(self): + num_workers = self.hparams["num_workers"] + return DataLoader( + self.splits["val"], + batch_size=self.hparams["batch_size"], + shuffle=False, + num_workers=num_workers, + persistent_workers=True if num_workers > 0 else False, + ) + + def test_dataloader(self): + num_workers = self.hparams["num_workers"] + return DataLoader( + self.splits["test"], + batch_size=self.hparams["batch_size"], + shuffle=False, + num_workers=num_workers, + persistent_workers=True if num_workers > 0 else False, + ) + + +class AtomWeightedMSE(nn.Module): + """ + Calculates the mean-squared-error between predicted and targets, + weighted by the number of atoms within each graph. + + From matsciml + """ + + def forward( + self, + input: torch.Tensor, + target: torch.Tensor, + atoms_per_graph: torch.Tensor, + ) -> torch.Tensor: + if atoms_per_graph.size(0) != target.size(0): + raise RuntimeError( + "Dimensions for atom-weighted loss do not match:" + f" expected atoms_per_graph to have {target.size(0)} elements; got {atoms_per_graph.size(0)}." + "This loss is intended to be applied to scalar targets only." + ) + # check to make sure we are broad casting correctly + if (input.ndim != target.ndim) and target.size(-1) == 1: + input.unsqueeze_(-1) + # for N-d targets, we might want to keep unsqueezing + while atoms_per_graph.ndim < target.ndim: + atoms_per_graph.unsqueeze_(-1) + # ensures that atoms_per_graph is type cast correctly + squared_error = ((input - target) / atoms_per_graph.to(input.dtype)) ** 2.0 + return squared_error.mean() + + +class EquiTritonLitModule(pl.LightningModule): + def __init__( + self, + model_class: type, + model_kwargs, + e_mean: float, + e_std: float, + lr: float = 1e-3, + weight_decay: float = 0.0, + atom_weighted_loss: bool = True, + ): + """ + Initializes the EquiTritonLitModule clas. + + Parameters + ---------- + model_class : type + Th class of the model to be used. + model_kwargs : dict + Keyword argument for the model initialization. + e_mean : float + The mean of the energy values. + e_std : float + The standard deviation of the energy values. + lr : float, optional + The learning rate (default is 1e-3) for AdamW. + weight_decay : float, optional + Weight decay value (default is 0.0). + atom_weighted_loss : bool, optional + Whether to use atom-weighted loss or not (default is True). + """ + super().__init__() + self.model = model_class(**model_kwargs) + if atom_weighted_loss: + self.loss = AtomWeightedMSE() + else: + self.loss = nn.MSELoss() + self.output_head = nn.Linear(self.model.output_dim, 1) + self.save_hyperparameters() + + def configure_optimizers(self): + return AdamW( + self.parameters(), + lr=self.hparams["lr"], + weight_decay=self.hparams["weight_decay"], + ) + + def step(self, graph: PyGGraph, stage: Literal["train", "test", "val"]): + """ + Performs a single step of the training, validation or testing + process. + + Parameters + ---------- + graph : PyGGraph + The input graph. + stage : Literal["train", "test", "val"] + The current stage (training, testing or validation). + + Returns + ------- + loss : float + The calculated loss value. + """ + g_z, z = self.model(graph) + pred_energy = self.output_head(g_z) + target_energy = graph.y[:, 12].unsqueeze(-1) + norm_energy = (target_energy - self.hparams["e_mean"]) / self.hparams["e_std"] + if self.hparams["atom_weighted_loss"]: + loss = self.loss(pred_energy, norm_energy, torch.diff(graph.ptr)) + else: + loss = self.loss(pred_energy, norm_energy) + batch_size = getattr(graph, "batch_size", 1) + self.log( + f"{stage}_loss", loss, prog_bar=True, on_step=True, batch_size=batch_size + ) + return loss + + def training_step(self, batch): + loss = self.step(batch, "train") + return loss + + def validation_step(self, batch): + loss = self.step(batch, "val") + return loss + + def test_step(self, batch): + loss = self.step(batch, "test") + return loss From 366eeacc8e1ba1d7fce0802840fdc1c14e2370be Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Thu, 29 Aug 2024 11:35:33 -0700 Subject: [PATCH 060/116] refactor: using imported lightning modules in train script Signed-off-by: Kin Long Kelvin Lee --- scripts/train_model_qm9.py | 228 +------------------------------------ 1 file changed, 1 insertion(+), 227 deletions(-) diff --git a/scripts/train_model_qm9.py b/scripts/train_model_qm9.py index 672dc74..1c5ef1d 100644 --- a/scripts/train_model_qm9.py +++ b/scripts/train_model_qm9.py @@ -1,235 +1,9 @@ from __future__ import annotations -from math import ceil -from typing import Literal - -import pytorch_lightning as pl from pytorch_lightning.cli import LightningCLI import torch -from torch.optim.adamw import AdamW -from torch import nn -from torch.utils.data import random_split -from torch_geometric.datasets import QM9 -from torch_geometric.data import Data as PyGGraph -from torch_geometric.loader import DataLoader - - -class LightningQM9(pl.LightningDataModule): - def __init__( - self, - root_path: str = "./qm9_data", - batch_size: int = 16, - train_frac: float = 0.8, - val_frac: float = 0.1, - num_workers: int = 0, - ): - """ - Custom data module for QM9 dataset. - - Parameters - ---------- - root_path : str, optional (default: "./qm9_data") - Path to the QM9 dataset. - batch_size : int, optional (default: 16) - Number of samples in each mini-batch. - train_frac : float, optional (default: 0.8) - Fraction of data used for training. - val_frac : float, optional (default: 0.1) - Fraction of data used for validation. - num_workers : int, optional (default: 0) - Number of worker processes to use for loading data. - - Examples - -------- - >>> dm = LightningQM9(root_path="/path/to/qm9_data", batch_size=32) - - Attributes - ---------- - dataset : QM9 - Loaded QM9 dataset. - hparams : dict - Hyperparameters of the data module. - - Methods - ------- - setup(stage: str) - Setup data splits for training, validation and testing. - train_dataloader() - Returns a DataLoader instance for training data. - val_dataloader() - Returns a DataLoader instance for validation data. - test_dataloader() - Returns a DataLoader instance for testing data. - """ - super().__init__() - self.dataset = QM9(root_path) - self.save_hyperparameters() - - def setup(self, stage: str): - hparams = self.hparams - num_samples = len(self.dataset) - num_train = int(num_samples * hparams["train_frac"]) - num_val = int(num_samples * hparams["val_frac"]) - num_test = ceil( - num_samples * (1 - (hparams["train_frac"] + hparams["val_frac"])) - ) - # generate random splits - train_split, val_split, test_split = random_split( - self.dataset, lengths=[num_train, num_val, num_test] - ) - self.splits = {"train": train_split, "val": val_split, "test": test_split} - - def train_dataloader(self): - num_workers = self.hparams["num_workers"] - return DataLoader( - self.splits["train"], - batch_size=self.hparams["batch_size"], - shuffle=True, - num_workers=num_workers, - persistent_workers=True if num_workers > 0 else False, - ) - - def val_dataloader(self): - num_workers = self.hparams["num_workers"] - return DataLoader( - self.splits["val"], - batch_size=self.hparams["batch_size"], - shuffle=False, - num_workers=num_workers, - persistent_workers=True if num_workers > 0 else False, - ) - - def test_dataloader(self): - num_workers = self.hparams["num_workers"] - return DataLoader( - self.splits["test"], - batch_size=self.hparams["batch_size"], - shuffle=False, - num_workers=num_workers, - persistent_workers=True if num_workers > 0 else False, - ) - - -class AtomWeightedMSE(nn.Module): - """ - Calculates the mean-squared-error between predicted and targets, - weighted by the number of atoms within each graph. - - From matsciml - """ - - def forward( - self, - input: torch.Tensor, - target: torch.Tensor, - atoms_per_graph: torch.Tensor, - ) -> torch.Tensor: - if atoms_per_graph.size(0) != target.size(0): - raise RuntimeError( - "Dimensions for atom-weighted loss do not match:" - f" expected atoms_per_graph to have {target.size(0)} elements; got {atoms_per_graph.size(0)}." - "This loss is intended to be applied to scalar targets only." - ) - # check to make sure we are broad casting correctly - if (input.ndim != target.ndim) and target.size(-1) == 1: - input.unsqueeze_(-1) - # for N-d targets, we might want to keep unsqueezing - while atoms_per_graph.ndim < target.ndim: - atoms_per_graph.unsqueeze_(-1) - # ensures that atoms_per_graph is type cast correctly - squared_error = ((input - target) / atoms_per_graph.to(input.dtype)) ** 2.0 - return squared_error.mean() - - -class EquiTritonLitModule(pl.LightningModule): - def __init__( - self, - model_class: type, - model_kwargs, - e_mean: float, - e_std: float, - lr: float = 1e-3, - weight_decay: float = 0.0, - atom_weighted_loss: bool = True, - ): - """ - Initializes the EquiTritonLitModule clas. - - Parameters - ---------- - model_class : type - Th class of the model to be used. - model_kwargs : dict - Keyword argument for the model initialization. - e_mean : float - The mean of the energy values. - e_std : float - The standard deviation of the energy values. - lr : float, optional - The learning rate (default is 1e-3) for AdamW. - weight_decay : float, optional - Weight decay value (default is 0.0). - atom_weighted_loss : bool, optional - Whether to use atom-weighted loss or not (default is True). - """ - super().__init__() - self.model = model_class(**model_kwargs) - if atom_weighted_loss: - self.loss = AtomWeightedMSE() - else: - self.loss = nn.MSELoss() - self.output_head = nn.Linear(self.model.output_dim, 1) - self.save_hyperparameters() - - def configure_optimizers(self): - return AdamW( - self.parameters(), - lr=self.hparams["lr"], - weight_decay=self.hparams["weight_decay"], - ) - - def step(self, graph: PyGGraph, stage: Literal["train", "test", "val"]): - """ - Performs a single step of the training, validation or testing - process. - - Parameters - ---------- - graph : PyGGraph - The input graph. - stage : Literal["train", "test", "val"] - The current stage (training, testing or validation). - - Returns - ------- - loss : float - The calculated loss value. - """ - g_z, z = self.model(graph) - pred_energy = self.output_head(g_z) - target_energy = graph.y[:, 12].unsqueeze(-1) - norm_energy = (target_energy - self.hparams["e_mean"]) / self.hparams["e_std"] - if self.hparams["atom_weighted_loss"]: - loss = self.loss(pred_energy, norm_energy, torch.diff(graph.ptr)) - else: - loss = self.loss(pred_energy, norm_energy) - batch_size = getattr(graph, "batch_size", 1) - self.log( - f"{stage}_loss", loss, prog_bar=True, on_step=True, batch_size=batch_size - ) - return loss - - def training_step(self, batch): - loss = self.step(batch, "train") - return loss - - def validation_step(self, batch): - loss = self.step(batch, "val") - return loss - def test_step(self, batch): - loss = self.step(batch, "test") - return loss +from equitriton.model.lightning import EquiTritonLitModule, LightningQM9 if __name__ == "__main__": From fcdf0efb934b898f5dbc57d64eb0ef25e276393d Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Fri, 30 Aug 2024 14:06:53 -0700 Subject: [PATCH 061/116] script: adding SMILES output to phate analysis Signed-off-by: Kin Long Kelvin Lee --- scripts/generate_phate_embeddings.py | 112 +++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 scripts/generate_phate_embeddings.py diff --git a/scripts/generate_phate_embeddings.py b/scripts/generate_phate_embeddings.py new file mode 100644 index 0000000..4cadc4e --- /dev/null +++ b/scripts/generate_phate_embeddings.py @@ -0,0 +1,112 @@ +from __future__ import annotations + +from pathlib import Path +from argparse import ArgumentParser + +import torch +from rdkit import Chem +import wandb +import numpy as np +from tqdm import tqdm +from phate import PHATE + +from equitriton.model.lightning import EquiTritonLitModule, LightningQM9 + + +def graph_to_rdkit(batched_graph): + mols = [Chem.MolFromSmiles(smi, sanitize=False) for smi in batched_graph.smiles] + return mols + + +def score_molecule(molecule) -> dict[str, int]: + enum = {"SP": 1, "SP2": 2, "SP3": 3} + scores = {"stereo": 0, "hybrid": 0, "aromatic": 0, "heavy_atoms": 0} + for atom in tqdm( + molecule.GetAtoms(), desc="Atoms in a molecule", leave=False, position=3 + ): + hybrid = enum.get(str(atom.GetHybridization()), 0) + # loop over bonds on the atom to check if it has stereoisomers + has_stereo = any( + [ + True if b.GetStereo() == Chem.BondStereo.STEREOE else False + for b in atom.GetBonds() + ] + ) + s = 2 if has_stereo else 1 + r = int(atom.GetIsAromatic()) + heavy_atoms = sum( + [neighbor.GetAtomicNum() > 1 for neighbor in atom.GetNeighbors()] + ) + scores["stereo"] += s + scores["hybrid"] += hybrid + scores["aromatic"] += r + scores["heavy_atoms"] += heavy_atoms + return scores + + +def calculate_scores_for_batch(molecules) -> list[dict[str, int]]: + """ + Calculates scores for every graph in a batch. + """ + scores = [ + score_molecule(mol) + for mol in tqdm(molecules, desc="Scoring molecules", leave=False, position=2) + ] + return scores + + +def run_phate_projection(results: list[dict], **phate_kwargs) -> np.ndarray: + phate_kwargs.setdefault("knn", 10) + phate_kwargs.setdefault("random_state", 21516) + # collect up all the embeddings + embeddings = torch.vstack([r["embeddings"][1] for r in results]).numpy() + phate = PHATE(**phate_kwargs) + phate_embeddings = phate.fit_transform(embeddings) + return phate_embeddings + + +def main(): + parser = ArgumentParser() + parser.add_argument( + "artifact_path", type=str, help="wandb path to a model artifact." + ) + + args = parser.parse_args() + + inference_run = wandb.init( + job_type="eval", + entity="laserkelvin", + project="equitriton-qm9", + tags=["inference", "embeddings", "qm9"], + ) + + artifact = inference_run.use_artifact(args.artifact_path, type="model") + artifact_dir = artifact.download() + ckpt_path = Path(artifact_dir).joinpath("model.ckpt") + + datamodule = LightningQM9("./qm9_data", num_workers=0) + model = EquiTritonLitModule.load_from_checkpoint(str(ckpt_path)) + + datamodule.setup("test") + test_loader = datamodule.test_dataloader() + + results = [] + for index, batch in tqdm( + enumerate(test_loader), desc="Batches to process", leave=False, position=1 + ): + embeddings = model.model.embed(batch.to("cuda")) + mols = graph_to_rdkit(batch) + scores = calculate_scores_for_batch(mols) + package = { + "embeddings": embeddings["graph_z"], + "scores": scores, + "smi": batch.smiles, + } + results.append(package) + phate_embeddings = run_phate_projection(results) + to_save = {"phate": phate_embeddings, "data": results} + torch.save(to_save, Path(artifact_dir).joinpath("results.pt")) + + +if __name__ == "__main__": + main() From 399ba697cbb1b62cf11aeb123473bcd465c508fb Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Fri, 30 Aug 2024 14:07:38 -0700 Subject: [PATCH 062/116] feat: adding embedding function to model Signed-off-by: Kin Long Kelvin Lee --- src/equitriton/model/blocks.py | 39 ++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/equitriton/model/blocks.py b/src/equitriton/model/blocks.py index c748808..fc3e362 100644 --- a/src/equitriton/model/blocks.py +++ b/src/equitriton/model/blocks.py @@ -394,6 +394,45 @@ def visualize(self, **kwargs): fig.tight_layout() return fig, axarray + def embed(self, graph: PyGGraph) -> dict[str, tuple[str, torch.Tensor]]: + """ + Generate embeddings for a given graph, either batched or + unbatched. + + This proceeds more or less the same way as the ``forward`` + pass, but instead emits a dictionary that maps each layer name + with both the embedding at that layer as well as the irreducible + representations. + """ + # determine if the graph is batched or not + is_batched = hasattr(graph, "ptr") + for key in ["pos", "edge_index", "z"]: + assert hasattr(graph, key) + # get atom embeddings + atom_z = self.atomic_embedding(graph.z) # [nodes, initial_atom_dim] + # first message passing step + z = self.initial_layer(atom_z, graph.pos, graph.edge_index) + outputs = { + "initial": ( + str(self.initial_layer.output_irreps), + z.detach().clone().cpu(), + ), + } + for layer_name, layer in self.conv_layers.items(): + new_z = layer(z, graph.pos, graph.edge_index) + # add residual connections + if self.skip_connections and new_z.shape == z.shape: + new_z += z + z = new_z + outputs[layer_name] = (str(layer.output_irreps), z.detach().clone().cpu()) + if is_batched: + graph_z = scatter(z, graph.batch, dim=0, dim_size=graph.batch_size) + else: + # for a single graph, just sum up the node features + graph_z = z.sum(dim=0, keepdims=True) + outputs["graph_z"] = (str(layer.output_irreps), graph_z.detach().clone().cpu()) + return outputs + def forward(self, graph: PyGGraph) -> tuple[torch.Tensor, torch.Tensor]: """ Forward pass for a generic equivariant convolution model. From fad8e4fe7aae37ba00af57b5bf86f1f1bbbdcf34 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Fri, 30 Aug 2024 14:26:13 -0700 Subject: [PATCH 063/116] feat: updating __all__ definition for utils --- src/equitriton/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/equitriton/utils.py b/src/equitriton/utils.py index b43fe7d..fef0872 100644 --- a/src/equitriton/utils.py +++ b/src/equitriton/utils.py @@ -6,7 +6,11 @@ import triton from e3nn import o3 -__all__ = ["pad_tensor_to_power", "calculate_lastdim_num_blocks"] +__all__ = [ + "pad_tensor_to_power", + "calculate_lastdim_num_blocks", + "spherical_harmonics_irreps", +] def pad_tensor_to_power( From 08cf6a366815b6a568de815054ba1142e640807e Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Fri, 30 Aug 2024 14:27:49 -0700 Subject: [PATCH 064/116] feat: added utility function for number of projections --- src/equitriton/utils.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/equitriton/utils.py b/src/equitriton/utils.py index fef0872..68214f2 100644 --- a/src/equitriton/utils.py +++ b/src/equitriton/utils.py @@ -10,9 +10,28 @@ "pad_tensor_to_power", "calculate_lastdim_num_blocks", "spherical_harmonics_irreps", + "num_irreps_projections", ] +def num_irreps_projections(l: int) -> int: + """ + Calculate the number of projections for a given order + of spherical harmonic. + + Parameters + ---------- + l : int + Order of spherical harmonic. + + Returns + ------- + int + Number of projections, i.e. 2l + 1 + """ + return 2 * l + 1 + + def pad_tensor_to_power( input_tensor: torch.Tensor, ) -> tuple[torch.Tensor, torch.Tensor]: From fd01042553302241eb937526baa4d4dfbb6b689d Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Fri, 30 Aug 2024 15:46:47 -0700 Subject: [PATCH 065/116] feat: adding utility functions for grabbing triton implementations --- src/equitriton/sph_harm/direct/utils.py | 77 +++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/src/equitriton/sph_harm/direct/utils.py b/src/equitriton/sph_harm/direct/utils.py index 1fe0816..62095fc 100644 --- a/src/equitriton/sph_harm/direct/utils.py +++ b/src/equitriton/sph_harm/direct/utils.py @@ -1,12 +1,89 @@ from __future__ import annotations from importlib import import_module +from typing import Callable import torch __all__ = ["torch_spherical_harmonic", "triton_spherical_harmonic"] +def _get_fwd_kernel(l: int) -> Callable: + """ + Reach into the module of a specified l value and grab + the corresponding forward Triton kernel function. + + Parameters + ---------- + l : int + Spherical harmonic l value to search for. + + Returns + ------- + Callable + Triton forward kernel + + Raises + ------ + ModuleNotFoundError: + If the l value is not implemented, the module will + not exist and raises a ``ModuleNotFoundError``. + RuntimeError: + If the module exists but we aren't able to find + a forward kernel defined, it's broken. + """ + try: + target_module = import_module(f"equitriton.sph_harm.direct.y_{l}") + except ModuleNotFoundError as e: + raise ModuleNotFoundError( + f"Spherical harmonic order l={l} requested, but not found!" + ) from e + defined_objs = dir(target_module) + for key in defined_objs: + if "order_fwd" in key: + sph_harm_func = getattr(target_module, key) + return sph_harm_func + raise RuntimeError(f"Namespace for module l={l} is broken!") + + +def _get_bwd_kernel(l: int) -> Callable: + """ + Reach into the module of a specified l value and grab + the corresponding backward Triton kernel function. + + Parameters + ---------- + l : int + Spherical harmonic l value to search for. + + Returns + ------- + Callable + Triton backward kernel + + Raises + ------ + ModuleNotFoundError: + If the l value is not implemented, the module will + not exist and raises a ``ModuleNotFoundError``. + RuntimeError: + If the module exists but we aren't able to find + a backward kernel defined, it's broken. + """ + try: + target_module = import_module(f"equitriton.sph_harm.direct.y_{l}") + except ModuleNotFoundError as e: + raise ModuleNotFoundError( + f"Spherical harmonic order l={l} requested, but not found!" + ) from e + defined_objs = dir(target_module) + for key in defined_objs: + if "order_bwd" in key: + sph_harm_func = getattr(target_module, key) + return sph_harm_func + raise RuntimeError(f"Namespace for module l={l} is broken!") + + def torch_spherical_harmonic(l: int, coords: torch.Tensor) -> torch.Tensor: """ Utility function that will call the PyTorch implementation From facb0d8896713dfc6284b36fcf0d22cf9f6a96da Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 20:48:59 -0700 Subject: [PATCH 066/116] refactor: changing spherical harmonic interface with preallocation --- src/equitriton/sph_harm/direct/utils.py | 128 +++++++++++++++++++++--- 1 file changed, 115 insertions(+), 13 deletions(-) diff --git a/src/equitriton/sph_harm/direct/utils.py b/src/equitriton/sph_harm/direct/utils.py index 62095fc..09ba75d 100644 --- a/src/equitriton/sph_harm/direct/utils.py +++ b/src/equitriton/sph_harm/direct/utils.py @@ -4,9 +4,14 @@ from typing import Callable import torch +import numpy as np + +from equitriton.utils import num_irreps_projections, calculate_lastdim_num_blocks __all__ = ["torch_spherical_harmonic", "triton_spherical_harmonic"] +BLOCK_SIZE = 64 + def _get_fwd_kernel(l: int) -> Callable: """ @@ -133,7 +138,7 @@ def torch_spherical_harmonic(l: int, coords: torch.Tensor) -> torch.Tensor: def triton_spherical_harmonic( - l: int, coords: torch.Tensor, mask: torch.Tensor | None = None + l_values: int | list[int], coords: torch.Tensor, mask: torch.Tensor | None = None ) -> torch.Tensor: """ Utility function that will call the Triton implementation @@ -168,17 +173,114 @@ def triton_spherical_harmonic( If the shape of the last dimension of the ``coords`` tensor is not equal to three. """ - try: - target_module = import_module(f"equitriton.sph_harm.direct.y_{l}") - except ModuleNotFoundError as e: - raise ModuleNotFoundError( - f"Spherical harmonic order l={l} requested, but not found!" - ) from e - defined_classes: list = getattr(target_module, "__all__") - # there should only be one entry in __all__, which is the autograd wrapper - sph_harm_func = getattr(target_module, defined_classes[0], None) - if not sph_harm_func: - raise RuntimeError(f"Triton implementation of l={l} not found.") if coords.size(-1) != 3: raise RuntimeError("Expects last dimension of coordinate tensor to be 3!") - return sph_harm_func.apply(coords) + if isinstance(l_values, int): + l_values = [ + l_values, + ] + # ensure we are in ascending order + l_values = list(sorted(l_values)) + dims = [num_irreps_projections(l) for l in l_values] + offsets = np.zeros_like(dims) + # prepend zero, since we start with zero offset + offsets[1:] = np.cumsum(dims[:-1]) + + # convert into a list, since np.int64 is not desired + offsets = offsets.tolist() + # preallocate a tensor that holds all of the spherical harmonic terms + output_tensor = torch.empty( + (*coords.shape[:-1], sum(dims)), + device=coords.device, + dtype=coords.dtype, + requires_grad=True, + ) + for l, offset in zip(l_values, offsets): + sph_harm_func = _get_fwd_kernel(l) + sph_harm_func.apply(coords, output_tensor, mask, BLOCK_SIZE, offset) + return output_tensor + + +class TritonSphericalHarmonic(torch.autograd.Function): + __l_values__: list + __offsets__: list + + @staticmethod + def forward( + ctx, + l_values: int | list[int], + coords: torch.Tensor, + mask: torch.Tensor | None = None, + ): + if coords.size(-1) != 3: + raise RuntimeError("Expects last dimension of coordinate tensor to be 3!") + if isinstance(l_values, int): + l_values = [ + l_values, + ] + # ensure we are in ascending order + l_values = list(sorted(l_values)) + dims = [num_irreps_projections(l) for l in l_values] + offsets = np.zeros_like(dims) + # prepend zero, since we start with zero offset + offsets[1:] = np.cumsum(dims[:-1]) + # convert into a list, since np.int64 is not desired + offsets = offsets.tolist() + # preallocate a tensor that holds all of the spherical harmonic terms + output_tensor = torch.empty( + (*coords.shape[:-1], sum(dims)), + device=coords.device, + dtype=coords.dtype, + requires_grad=True, + ) + coord_numel = coords.numel() + output_numel = output_tensor.numel() + # this corresponds to the number of projections + output_stride = output_tensor.stride(-2) + num_blocks = calculate_lastdim_num_blocks(coords, BLOCK_SIZE) + for l, offset in zip(l_values, offsets): + sph_harm_func = _get_fwd_kernel(l) + sph_harm_func[num_blocks,]( + coords, + output_tensor, + BLOCK_SIZE, + coord_numel, + output_numel, + offset, + output_stride, + ) + ctx.save_for_backward(coords) + # stash values as class attributes, as they are the same + # and ctx can only hold tensors + TritonSphericalHarmonic.__l_values__ = l_values + TritonSphericalHarmonic.__offsets__ = offsets + return output_tensor + + @staticmethod + def backward(ctx, sph_harm_grads: torch.Tensor): + (coords,) = ctx.saved_tensors + # grab from private class variables + l_values = TritonSphericalHarmonic.__l_values__ + offsets = TritonSphericalHarmonic.__offsets__ + coord_grad_output = torch.zeros_like(coords) + # combine start and end together to slice the gradient tensor + coord_numel = coords.numel() + grads_numel = sph_harm_grads.numel() + # this corresponds to the number of projections + output_stride = sph_harm_grads.stride(-2) + num_blocks = calculate_lastdim_num_blocks(coords, BLOCK_SIZE) + for l, offset in zip(l_values, offsets): + sph_harm_bwd = _get_bwd_kernel(l) + sph_harm_bwd[num_blocks,]( + coords, + coord_grad_output, + sph_harm_grads, + BLOCK_SIZE, + coord_numel, + grads_numel, + offset, + output_stride, + ) + # first element ise None becausey are l_values which + # can't have gradients + return None, coord_grad_output From 5ae81982443072d8d9e45931a01a9ae9d2c432c0 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 20:52:35 -0700 Subject: [PATCH 067/116] refactor: making first and third order accept offset values --- src/equitriton/sph_harm/direct/y_1.py | 37 +++++++++++++++++++------- src/equitriton/sph_harm/direct/y_3.py | 38 ++++++++++++++++++++------- 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_1.py b/src/equitriton/sph_harm/direct/y_1.py index 4bb9050..17af709 100644 --- a/src/equitriton/sph_harm/direct/y_1.py +++ b/src/equitriton/sph_harm/direct/y_1.py @@ -14,6 +14,7 @@ def forward( coords: torch.Tensor, mask: torch.Tensor | None = None, block_size: int = 64, + col_offset: int = 0, ): output_tensor = torch.empty( (*coords.shape[:-1], 3), dtype=coords.dtype, device=coords.device @@ -23,14 +24,14 @@ def forward( num_blocks = calculate_lastdim_num_blocks(coords, block_size) # apply the kernel first_order_fwd[num_blocks,]( - coords, output_tensor, block_size, coord_numel, output_numel + coords, output_tensor, block_size, coord_numel, output_numel, col_offset ) ctx.save_for_backward(coords) return output_tensor @staticmethod def backward( - ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 + ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64, col_offset: int = 0 ) -> torch.Tensor: (coords,) = ctx.saved_tensors coord_grad_output = torch.zeros_like(coords) @@ -42,6 +43,7 @@ def backward( block_size, coords.numel(), sph_grad_tensor.numel(), + col_offset, ) return coord_grad_output @@ -85,6 +87,8 @@ def first_order_fwd( block_size: tl.constexpr, coord_numel: tl.constexpr, output_numel: tl.constexpr, + col_offset: tl.constexpr, + output_stride: tl.constexpr, ): # these are hardcoded because they are predetermined; coord_stride = 3 @@ -104,9 +108,10 @@ def first_order_fwd( Y10 = CONST_00 * x Y11 = CONST_00 * y Y12 = CONST_00 * z - output_stride = 3 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride - output_row_offset = output_striding + (block_size * output_stride * block_id) + output_row_offset = ( + output_striding + (block_size * output_stride * block_id) + col_offset + ) tl.store(output_ptr + output_row_offset, Y10, mask=output_row_offset < output_numel) tl.store( output_ptr + output_row_offset + 1, @@ -122,11 +127,14 @@ def first_order_fwd( @triton.jit def first_order_bwd( + coord_ptr: tl.tensor, # noqa: F403 coord_grad_ptr: tl.tensor, sph_grad_ptr: tl.tensor, block_size: tl.constexpr, coord_numel: tl.constexpr, output_numel: tl.constexpr, + col_offset: tl.constexpr, + output_stride: tl.constexpr, ): # work out the row offsets block_id = tl.program_id(0) @@ -135,9 +143,10 @@ def first_order_bwd( coord_striding = tl.arange(0, block_size) * coord_stride # as the name suggests, this is effectively every node/atom coord_row_offset = coord_striding + (block_size * coord_stride * block_id) - output_stride = 3 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride - output_row_offset = output_striding + (block_size * output_stride * block_id) + output_row_offset = ( + output_striding + (block_size * output_stride * block_id) + col_offset + ) # load in gradients w.r.t. spherical harmonic projections g_Y10 = tl.load( sph_grad_ptr + output_row_offset, mask=output_row_offset < output_numel @@ -148,10 +157,20 @@ def first_order_bwd( g_Y12 = tl.load( sph_grad_ptr + output_row_offset + 2, mask=output_row_offset + 2 < output_numel ) + # read in current gradients + g_x = tl.load( + coord_grad_ptr + coord_row_offset, mask=coord_row_offset < coord_numel + ) + g_y = tl.load( + coord_grad_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + g_z = tl.load( + coord_grad_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) CONST_00 = tl.sqrt(3.0) - g_x = CONST_00 * g_Y10 - g_y = CONST_00 * g_Y11 - g_z = CONST_00 * g_Y12 + g_x += CONST_00 * g_Y10 + g_y += CONST_00 * g_Y11 + g_z += CONST_00 * g_Y12 # write out gradients tl.store( coord_grad_ptr + coord_row_offset, g_x, mask=coord_row_offset < coord_numel diff --git a/src/equitriton/sph_harm/direct/y_3.py b/src/equitriton/sph_harm/direct/y_3.py index 141f1f2..96dace7 100644 --- a/src/equitriton/sph_harm/direct/y_3.py +++ b/src/equitriton/sph_harm/direct/y_3.py @@ -12,28 +12,37 @@ class ThirdOrderSphericalHarmonic(torch.autograd.Function): def forward( ctx, coords: torch.Tensor, + output_tensor: torch.Tensor | None = None, mask: torch.Tensor | None = None, block_size: int = 64, + col_offset: int = 0, ): - output_tensor = torch.empty( - (*coords.shape[:-1], 7), dtype=coords.dtype, device=coords.device - ) + # allocate a tensor if one isn't given + if not isinstance(output_tensor, torch.Tensor): + output_tensor = torch.empty( + (*coords.shape[:-1], 7), dtype=coords.dtype, device=coords.device + ) coord_numel = coords.numel() output_numel = output_tensor.numel() num_blocks = calculate_lastdim_num_blocks(coords, block_size) # apply the kernel third_order_fwd[num_blocks,]( - coords, output_tensor, block_size, coord_numel, output_numel + coords, output_tensor, block_size, coord_numel, output_numel, col_offset ) ctx.save_for_backward(coords) return output_tensor @staticmethod def backward( - ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 + ctx, + sph_grad_tensor: torch.Tensor, + coord_grad_output: torch.Tensor | None = None, + block_size: int = 64, + col_offset: int = 0, ) -> torch.Tensor: (coords,) = ctx.saved_tensors - coord_grad_output = torch.zeros_like(coords) + if not isinstance(coord_grad_output, torch.Tensor): + coord_grad_output = torch.zeros_like(coords) num_blocks = calculate_lastdim_num_blocks(coords, block_size) # call backward kernel third_order_bwd[num_blocks,]( @@ -43,6 +52,7 @@ def backward( block_size, coords.numel(), sph_grad_tensor.numel(), + col_offset, ) return coord_grad_output @@ -107,6 +117,8 @@ def third_order_fwd( block_size: tl.constexpr, coord_numel: tl.constexpr, output_numel: tl.constexpr, + col_offset: tl.constexpr, + output_stride: tl.constexpr, ): # these are hardcoded because they are predetermined; coord_stride = 3 @@ -146,9 +158,11 @@ def third_order_fwd( Y04 = CONST010 * VAR25 + z * (CONST004 * VAR17 + CONST010 * VAR08) Y05 = CONST002 * y * (CONST007 * VAR08 + VAR26) Y06 = -CONST006 * VAR25 + CONST008 * VAR08 * z - output_stride = 7 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride - output_row_offset = output_striding + (block_size * output_stride * block_id) + # zero on the row offset is the first spherical harmonic term of this order + output_row_offset = ( + output_striding + (block_size * output_stride * block_id) + col_offset + ) tl.store(output_ptr + output_row_offset, Y00, mask=output_row_offset < output_numel) tl.store( output_ptr + output_row_offset + 1, @@ -190,6 +204,8 @@ def third_order_bwd( block_size: tl.constexpr, coord_numel: tl.constexpr, output_numel: tl.constexpr, + col_offset: tl.constexpr, + output_stride: tl.constexpr, ): # work out the row offsets block_id = tl.program_id(0) @@ -205,9 +221,11 @@ def third_order_bwd( z = tl.load( coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel ) - output_stride = 7 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride - output_row_offset = output_striding + (block_size * output_stride * block_id) + # zero on the row offset is the first spherical harmonic term of this order + output_row_offset = ( + output_striding + (block_size * output_stride * block_id) + col_offset + ) # load in gradients w.r.t. spherical harmonic projections g_0 = tl.load( sph_grad_ptr + output_row_offset, mask=output_row_offset < output_numel From 636602b8a9cc4b7c90e84091eb62ee84b6db9c9c Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 20:54:40 -0700 Subject: [PATCH 068/116] refactor: second order fwd with col offset --- src/equitriton/sph_harm/direct/y_2.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_2.py b/src/equitriton/sph_harm/direct/y_2.py index 9fe7f37..9679637 100644 --- a/src/equitriton/sph_harm/direct/y_2.py +++ b/src/equitriton/sph_harm/direct/y_2.py @@ -91,6 +91,8 @@ def second_order_fwd( block_size: tl.constexpr, coord_numel: tl.constexpr, output_numel: tl.constexpr, + col_offset: tl.constexpr, + output_stride: tl.constexpr, ): # these are hardcoded because they are predetermined; coord_stride = 3 @@ -115,9 +117,10 @@ def second_order_fwd( Y23 = CONST_00 * y * z # looks jarring but just helping the compiler ;) Y22 = CONST_02 * x * x + CONST_01 * y * y + CONST_02 * z * z Y24 = -CONST_03 * x * x + CONST_03 * z * z - output_stride = 5 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride - output_row_offset = output_striding + (block_size * output_stride * block_id) + output_row_offset = ( + output_striding + (block_size * output_stride * block_id) + col_offset + ) tl.store(output_ptr + output_row_offset, Y20, mask=output_row_offset < output_numel) tl.store( output_ptr + output_row_offset + 1, From f45c4a1638aabe8f9e461fc3dcf3d6c457f9654b Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 20:55:37 -0700 Subject: [PATCH 069/116] refactor: second order backward with col offset --- src/equitriton/sph_harm/direct/y_2.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_2.py b/src/equitriton/sph_harm/direct/y_2.py index 9679637..cbef7ee 100644 --- a/src/equitriton/sph_harm/direct/y_2.py +++ b/src/equitriton/sph_harm/direct/y_2.py @@ -152,6 +152,8 @@ def second_order_bwd( block_size: tl.constexpr, coord_numel: tl.constexpr, output_numel: tl.constexpr, + col_offset: tl.constexpr, + output_stride: tl.constexpr, ): # work out the row offsets block_id = tl.program_id(0) @@ -167,9 +169,10 @@ def second_order_bwd( z = tl.load( coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel ) - output_stride = 5 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride - output_row_offset = output_striding + (block_size * output_stride * block_id) + output_row_offset = ( + output_striding + (block_size * output_stride * block_id) + col_offset + ) CONST_00 = 3.87298334620742 CONST_01 = 2.23606797749979 CONST_02 = 4.47213595499958 From 4c340d093592a63090447b48a4a174c9eb43d24e Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 20:57:26 -0700 Subject: [PATCH 070/116] refactor: updating autograd.Function for second order --- src/equitriton/sph_harm/direct/y_2.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_2.py b/src/equitriton/sph_harm/direct/y_2.py index cbef7ee..7cf65ed 100644 --- a/src/equitriton/sph_harm/direct/y_2.py +++ b/src/equitriton/sph_harm/direct/y_2.py @@ -15,15 +15,24 @@ def forward( mask: torch.Tensor | None = None, block_size: int = 64, ): + num_projections = 5 # 2l + 1 output_tensor = torch.empty( - (*coords.shape[:-1], 5), dtype=coords.dtype, device=coords.device + (*coords.shape[:-1], num_projections), + dtype=coords.dtype, + device=coords.device, ) coord_numel = coords.numel() output_numel = output_tensor.numel() num_blocks = calculate_lastdim_num_blocks(coords, block_size) # apply the kernel second_order_fwd[num_blocks,]( - coords, output_tensor, block_size, coord_numel, output_numel + coords, + output_tensor, + block_size, + coord_numel, + output_numel, + 0, + num_projections, ) ctx.save_for_backward(coords) return output_tensor @@ -32,6 +41,7 @@ def forward( def backward( ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 ) -> torch.Tensor: + num_projections = 5 # 2l + 1 (coords,) = ctx.saved_tensors coord_grad_output = torch.zeros_like(coords) num_blocks = calculate_lastdim_num_blocks(coords, block_size) @@ -43,6 +53,8 @@ def backward( block_size, coords.numel(), sph_grad_tensor.numel(), + 0, + num_projections, ) return coord_grad_output From b58794ed1529d19c5b7b31bb91bdb4817321c8dd Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 20:59:07 -0700 Subject: [PATCH 071/116] refactor: making third order autograd Function accept output stride --- src/equitriton/sph_harm/direct/y_3.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/equitriton/sph_harm/direct/y_3.py b/src/equitriton/sph_harm/direct/y_3.py index 96dace7..0a2b3d5 100644 --- a/src/equitriton/sph_harm/direct/y_3.py +++ b/src/equitriton/sph_harm/direct/y_3.py @@ -27,7 +27,13 @@ def forward( num_blocks = calculate_lastdim_num_blocks(coords, block_size) # apply the kernel third_order_fwd[num_blocks,]( - coords, output_tensor, block_size, coord_numel, output_numel, col_offset + coords, + output_tensor, + block_size, + coord_numel, + output_numel, + col_offset, + output_tensor.stride(-2), ) ctx.save_for_backward(coords) return output_tensor @@ -53,6 +59,7 @@ def backward( coords.numel(), sph_grad_tensor.numel(), col_offset, + sph_grad_tensor.stride(-2), ) return coord_grad_output From 39a087163e16f61c13d12a078921fc375f7be183 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:01:17 -0700 Subject: [PATCH 072/116] refactor: making second order autograd.Function consistent with third order --- src/equitriton/sph_harm/direct/y_2.py | 28 ++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_2.py b/src/equitriton/sph_harm/direct/y_2.py index 7cf65ed..02f7cf6 100644 --- a/src/equitriton/sph_harm/direct/y_2.py +++ b/src/equitriton/sph_harm/direct/y_2.py @@ -12,15 +12,19 @@ class SecondOrderSphericalHarmonic(torch.autograd.Function): def forward( ctx, coords: torch.Tensor, + output_tensor: torch.Tensor | None = None, mask: torch.Tensor | None = None, block_size: int = 64, + col_offset: int = 0, ): num_projections = 5 # 2l + 1 - output_tensor = torch.empty( - (*coords.shape[:-1], num_projections), - dtype=coords.dtype, - device=coords.device, - ) + # allocate a tensor if one isn't given + if not isinstance(output_tensor, torch.Tensor): + output_tensor = torch.empty( + (*coords.shape[:-1], num_projections), + dtype=coords.dtype, + device=coords.device, + ) coord_numel = coords.numel() output_numel = output_tensor.numel() num_blocks = calculate_lastdim_num_blocks(coords, block_size) @@ -31,17 +35,19 @@ def forward( block_size, coord_numel, output_numel, - 0, - num_projections, + col_offset, + output_tensor.stride(-2), ) ctx.save_for_backward(coords) return output_tensor @staticmethod def backward( - ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 + ctx, + sph_grad_tensor: torch.Tensor, + block_size: int = 64, + col_offset: int = 0, ) -> torch.Tensor: - num_projections = 5 # 2l + 1 (coords,) = ctx.saved_tensors coord_grad_output = torch.zeros_like(coords) num_blocks = calculate_lastdim_num_blocks(coords, block_size) @@ -53,8 +59,8 @@ def backward( block_size, coords.numel(), sph_grad_tensor.numel(), - 0, - num_projections, + col_offset, + sph_grad_tensor.stride(-2), ) return coord_grad_output From a57ae4ee89f7ed1badaa805e75a0b0f0c8aeaa09 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:03:19 -0700 Subject: [PATCH 073/116] refactor: making fourth order kernels accept stride and col offset --- src/equitriton/sph_harm/direct/y_4.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_4.py b/src/equitriton/sph_harm/direct/y_4.py index 485adfa..75aeda2 100644 --- a/src/equitriton/sph_harm/direct/y_4.py +++ b/src/equitriton/sph_harm/direct/y_4.py @@ -128,6 +128,8 @@ def fourth_order_fwd( block_size: tl.constexpr, coord_numel: tl.constexpr, output_numel: tl.constexpr, + col_offset: tl.constexpr, + output_stride: tl.constexpr, ): # these are hardcoded because they are predetermined; coord_stride = 3 @@ -188,9 +190,10 @@ def fourth_order_fwd( ) Y07 = y * (CONST011 * VAR08 * z - CONST017 * VAR25) Y08 = CONST005 * VAR06 + CONST005 * VAR24 + CONST012 * VAR08 * VAR26 - output_stride = 9 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride - output_row_offset = output_striding + (block_size * output_stride * block_id) + output_row_offset = ( + output_striding + (block_size * output_stride * block_id) + col_offset + ) tl.store(output_ptr + output_row_offset, Y00, mask=output_row_offset < output_numel) tl.store( output_ptr + output_row_offset + 1, @@ -242,6 +245,8 @@ def fourth_order_bwd( block_size: tl.constexpr, coord_numel: tl.constexpr, output_numel: tl.constexpr, + col_offset: tl.constexpr, + output_stride: tl.constexpr, ): # work out the row offsets block_id = tl.program_id(0) @@ -257,9 +262,10 @@ def fourth_order_bwd( z = tl.load( coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel ) - output_stride = 7 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride - output_row_offset = output_striding + (block_size * output_stride * block_id) + output_row_offset = ( + output_striding + (block_size * output_stride * block_id) + col_offset + ) # load in gradients w.r.t. spherical harmonic projections g_0 = tl.load( sph_grad_ptr + output_row_offset, mask=output_row_offset < output_numel From bc07c01d718713920a17e82d7134180e7790eaa6 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:04:36 -0700 Subject: [PATCH 074/116] refactor: updating fourth order interface for consistency --- src/equitriton/sph_harm/direct/y_4.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_4.py b/src/equitriton/sph_harm/direct/y_4.py index 75aeda2..7ee616b 100644 --- a/src/equitriton/sph_harm/direct/y_4.py +++ b/src/equitriton/sph_harm/direct/y_4.py @@ -12,25 +12,37 @@ class FourthOrderSphericalHarmonic(torch.autograd.Function): def forward( ctx, coords: torch.Tensor, + output_tensor: torch.Tensor | None = None, mask: torch.Tensor | None = None, block_size: int = 64, + col_offset: int = 0, ): - output_tensor = torch.empty( - (*coords.shape[:-1], 9), dtype=coords.dtype, device=coords.device - ) + if not isinstance(output_tensor, torch.Tensor): + output_tensor = torch.empty( + (*coords.shape[:-1], 9), dtype=coords.dtype, device=coords.device + ) coord_numel = coords.numel() output_numel = output_tensor.numel() num_blocks = calculate_lastdim_num_blocks(coords, block_size) # apply the kernel fourth_order_fwd[num_blocks,]( - coords, output_tensor, block_size, coord_numel, output_numel + coords, + output_tensor, + block_size, + coord_numel, + output_numel, + col_offset, + output_tensor.stride(-2), ) ctx.save_for_backward(coords) return output_tensor @staticmethod def backward( - ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 + ctx, + sph_grad_tensor: torch.Tensor, + block_size: int = 64, + col_offset: int = 0, ) -> torch.Tensor: (coords,) = ctx.saved_tensors coord_grad_output = torch.zeros_like(coords) @@ -43,6 +55,8 @@ def backward( block_size, coords.numel(), sph_grad_tensor.numel(), + col_offset, + sph_grad_tensor.stride(-2), ) return coord_grad_output From 63858338bac69d75e541c9d958b7b8d7e880ffa7 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:06:48 -0700 Subject: [PATCH 075/116] refactor: making fifth order accept col offset and stride --- src/equitriton/sph_harm/direct/y_5.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_5.py b/src/equitriton/sph_harm/direct/y_5.py index d6e3f68..94598e1 100644 --- a/src/equitriton/sph_harm/direct/y_5.py +++ b/src/equitriton/sph_harm/direct/y_5.py @@ -158,6 +158,8 @@ def fifth_order_fwd( block_size: tl.constexpr, coord_numel: tl.constexpr, output_numel: tl.constexpr, + col_offset: tl.constexpr, + output_stride: tl.constexpr, ): # these are hardcoded because they are predetermined; coord_stride = 3 @@ -250,9 +252,10 @@ def fifth_order_fwd( ) Y09 = y * (CONST018 * VAR06 + CONST018 * VAR24 + CONST020 * VAR08 * VAR26) Y10 = CONST001 * VAR23 + CONST009 * VAR06 * z + CONST023 * VAR08 * VAR25 - output_stride = 11 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride - output_row_offset = output_striding + (block_size * output_stride * block_id) + output_row_offset = ( + output_striding + (block_size * output_stride * block_id) + col_offset + ) tl.store(output_ptr + output_row_offset, Y00, mask=output_row_offset < output_numel) tl.store( output_ptr + output_row_offset + 1, @@ -314,6 +317,8 @@ def fifth_order_bwd( block_size: tl.constexpr, coord_numel: tl.constexpr, output_numel: tl.constexpr, + col_offset: tl.constexpr, + output_stride: tl.constexpr, ): # work out the row offsets block_id = tl.program_id(0) @@ -329,9 +334,10 @@ def fifth_order_bwd( z = tl.load( coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel ) - output_stride = 11 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride - output_row_offset = output_striding + (block_size * output_stride * block_id) + output_row_offset = ( + output_striding + (block_size * output_stride * block_id) + col_offset + ) # load in gradients w.r.t. spherical harmonic projections g_0 = tl.load( sph_grad_ptr + output_row_offset, mask=output_row_offset < output_numel From 79d840bff9560bb595cf88ed5d30756ab3d6b567 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:09:01 -0700 Subject: [PATCH 076/116] refactor: updating fifth order autograd.Function interface --- src/equitriton/sph_harm/direct/y_5.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_5.py b/src/equitriton/sph_harm/direct/y_5.py index 94598e1..93a52f1 100644 --- a/src/equitriton/sph_harm/direct/y_5.py +++ b/src/equitriton/sph_harm/direct/y_5.py @@ -12,25 +12,37 @@ class FifthOrderSphericalHarmonic(torch.autograd.Function): def forward( ctx, coords: torch.Tensor, + output_tensor: torch.Tensor | None = None, mask: torch.Tensor | None = None, block_size: int = 64, + col_offset: int = 0, ): - output_tensor = torch.empty( - (*coords.shape[:-1], 11), dtype=coords.dtype, device=coords.device - ) + if not isinstance(output_tensor, torch.Tensor): + output_tensor = torch.empty( + (*coords.shape[:-1], 11), dtype=coords.dtype, device=coords.device + ) coord_numel = coords.numel() output_numel = output_tensor.numel() num_blocks = calculate_lastdim_num_blocks(coords, block_size) # apply the kernel fifth_order_fwd[num_blocks,]( - coords, output_tensor, block_size, coord_numel, output_numel + coords, + output_tensor, + block_size, + coord_numel, + output_numel, + col_offset, + output_tensor.stride(-2), ) ctx.save_for_backward(coords) return output_tensor @staticmethod def backward( - ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 + ctx, + sph_grad_tensor: torch.Tensor, + block_size: int = 64, + col_offset: int = 0, ) -> torch.Tensor: (coords,) = ctx.saved_tensors coord_grad_output = torch.zeros_like(coords) @@ -43,6 +55,8 @@ def backward( block_size, coords.numel(), sph_grad_tensor.numel(), + col_offset, + sph_grad_tensor.stride(-2), ) return coord_grad_output From 12fd185ce26940408bbed5fae332c42d041125f0 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:11:30 -0700 Subject: [PATCH 077/116] refactor: allowing sixth order to accept col offset and output stride --- src/equitriton/sph_harm/direct/y_6.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_6.py b/src/equitriton/sph_harm/direct/y_6.py index 8f6b258..4d7c9c8 100644 --- a/src/equitriton/sph_harm/direct/y_6.py +++ b/src/equitriton/sph_harm/direct/y_6.py @@ -196,6 +196,8 @@ def sixth_order_fwd( block_size: tl.constexpr, coord_numel: tl.constexpr, output_numel: tl.constexpr, + col_offset: tl.constexpr, + output_stride: tl.constexpr, ): # these are hardcoded because they are predetermined; coord_stride = 3 @@ -326,9 +328,10 @@ def sixth_order_fwd( + CONST043 * VAR04 - CONST043 * VAR22 ) - output_stride = 13 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride - output_row_offset = output_striding + (block_size * output_stride * block_id) + output_row_offset = ( + output_striding + (block_size * output_stride * block_id) + col_offset + ) tl.store(output_ptr + output_row_offset, Y00, mask=output_row_offset < output_numel) tl.store( output_ptr + output_row_offset + 1, @@ -400,6 +403,8 @@ def sixth_order_bwd( block_size: tl.constexpr, coord_numel: tl.constexpr, output_numel: tl.constexpr, + col_offset: tl.constexpr, + output_stride: tl.constexpr, ): # work out the row offsets block_id = tl.program_id(0) @@ -415,9 +420,10 @@ def sixth_order_bwd( z = tl.load( coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel ) - output_stride = 13 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride - output_row_offset = output_striding + (block_size * output_stride * block_id) + output_row_offset = ( + output_striding + (block_size * output_stride * block_id) + col_offset + ) # load in gradients w.r.t. spherical harmonic projections g_0 = tl.load( sph_grad_ptr + output_row_offset, mask=output_row_offset < output_numel From ebf13b6731c02d734d985a8e00cf98115fec8644 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:13:05 -0700 Subject: [PATCH 078/116] refactor: updating sixth order to accept col offset --- src/equitriton/sph_harm/direct/y_6.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_6.py b/src/equitriton/sph_harm/direct/y_6.py index 4d7c9c8..4d0284d 100644 --- a/src/equitriton/sph_harm/direct/y_6.py +++ b/src/equitriton/sph_harm/direct/y_6.py @@ -12,25 +12,34 @@ class SixthOrderSphericalHarmonic(torch.autograd.Function): def forward( ctx, coords: torch.Tensor, + output_tensor: torch.Tensor | None = None, mask: torch.Tensor | None = None, block_size: int = 64, + col_offset: int = 0, ): - output_tensor = torch.empty( - (*coords.shape[:-1], 13), dtype=coords.dtype, device=coords.device - ) + if not isinstance(output_tensor, torch.Tensor): + output_tensor = torch.empty( + (*coords.shape[:-1], 13), dtype=coords.dtype, device=coords.device + ) coord_numel = coords.numel() output_numel = output_tensor.numel() num_blocks = calculate_lastdim_num_blocks(coords, block_size) # apply the kernel sixth_order_fwd[num_blocks,]( - coords, output_tensor, block_size, coord_numel, output_numel + coords, + output_tensor, + block_size, + coord_numel, + output_numel, + col_offset, + output_tensor.stride(-2), ) ctx.save_for_backward(coords) return output_tensor @staticmethod def backward( - ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 + ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64, col_offset: int = 0 ) -> torch.Tensor: (coords,) = ctx.saved_tensors coord_grad_output = torch.zeros_like(coords) @@ -43,6 +52,8 @@ def backward( block_size, coords.numel(), sph_grad_tensor.numel(), + col_offset, + sph_grad_tensor.stride(-2), ) return coord_grad_output From ad1aae94dc3831cf1968fa8b8a86b07253226b96 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:14:29 -0700 Subject: [PATCH 079/116] refactor: updating seventh order to accept col offset and output stride --- src/equitriton/sph_harm/direct/y_7.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_7.py b/src/equitriton/sph_harm/direct/y_7.py index 583994b..5033cc6 100644 --- a/src/equitriton/sph_harm/direct/y_7.py +++ b/src/equitriton/sph_harm/direct/y_7.py @@ -284,6 +284,8 @@ def seventh_order_fwd( block_size: tl.constexpr, coord_numel: tl.constexpr, output_numel: tl.constexpr, + col_offset: tl.constexpr, + output_stride: tl.constexpr, ): # these are hardcoded because they are predetermined; coord_stride = 3 @@ -486,9 +488,10 @@ def seventh_order_fwd( + CONST073 * VAR04 * z - CONST079 * VAR21 ) - output_stride = 15 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride - output_row_offset = output_striding + (block_size * output_stride * block_id) + output_row_offset = ( + output_striding + (block_size * output_stride * block_id) + col_offset + ) tl.store(output_ptr + output_row_offset, Y00, mask=output_row_offset < output_numel) tl.store( output_ptr + output_row_offset + 1, @@ -570,6 +573,8 @@ def seventh_order_bwd( block_size: tl.constexpr, coord_numel: tl.constexpr, output_numel: tl.constexpr, + col_offset: tl.constexpr, + output_stride: tl.constexpr, ): # work out the row offsets block_id = tl.program_id(0) @@ -585,9 +590,10 @@ def seventh_order_bwd( z = tl.load( coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel ) - output_stride = 15 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride - output_row_offset = output_striding + (block_size * output_stride * block_id) + output_row_offset = ( + output_striding + (block_size * output_stride * block_id) + col_offset + ) # load in gradients w.r.t. spherical harmonic projections g_0 = tl.load( sph_grad_ptr + output_row_offset, mask=output_row_offset < output_numel From f4816639cf3a9d975fc87c0889e8e4eb6f3dd3e5 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:16:27 -0700 Subject: [PATCH 080/116] refactor: updating seventh order autograd.Function interface --- src/equitriton/sph_harm/direct/y_7.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_7.py b/src/equitriton/sph_harm/direct/y_7.py index 5033cc6..06deb1a 100644 --- a/src/equitriton/sph_harm/direct/y_7.py +++ b/src/equitriton/sph_harm/direct/y_7.py @@ -12,25 +12,37 @@ class SeventhOrderSphericalHarmonic(torch.autograd.Function): def forward( ctx, coords: torch.Tensor, + output_tensor: torch.Tensor | None = None, mask: torch.Tensor | None = None, block_size: int = 64, + col_offset: int = 0, ): - output_tensor = torch.empty( - (*coords.shape[:-1], 15), dtype=coords.dtype, device=coords.device - ) + if not isinstance(output_tensor, torch.Tensor): + output_tensor = torch.empty( + (*coords.shape[:-1], 15), dtype=coords.dtype, device=coords.device + ) coord_numel = coords.numel() output_numel = output_tensor.numel() num_blocks = calculate_lastdim_num_blocks(coords, block_size) # apply the kernel seventh_order_fwd[num_blocks,]( - coords, output_tensor, block_size, coord_numel, output_numel + coords, + output_tensor, + block_size, + coord_numel, + output_numel, + col_offset, + output_tensor.stride(-2), ) ctx.save_for_backward(coords) return output_tensor @staticmethod def backward( - ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 + ctx, + sph_grad_tensor: torch.Tensor, + block_size: int = 64, + col_offset: int = 0, ) -> torch.Tensor: (coords,) = ctx.saved_tensors coord_grad_output = torch.zeros_like(coords) @@ -43,6 +55,8 @@ def backward( block_size, coords.numel(), sph_grad_tensor.numel(), + col_offset, + sph_grad_tensor.stride(-2), ) return coord_grad_output From 9a8ca9e64bb54af8ca6b775f02191176802d894e Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:18:16 -0700 Subject: [PATCH 081/116] refactor: eighth order with col offset and output stride --- src/equitriton/sph_harm/direct/y_8.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_8.py b/src/equitriton/sph_harm/direct/y_8.py index d2d32a0..a26abd7 100644 --- a/src/equitriton/sph_harm/direct/y_8.py +++ b/src/equitriton/sph_harm/direct/y_8.py @@ -374,6 +374,8 @@ def eighth_order_fwd( block_size: tl.constexpr, coord_numel: tl.constexpr, output_numel: tl.constexpr, + col_offset: tl.constexpr, + output_stride: tl.constexpr, ): # these are hardcoded because they are predetermined; coord_stride = 3 @@ -664,9 +666,10 @@ def eighth_order_fwd( + CONST074 * VAR04 * VAR26 + CONST074 * VAR08 * VAR22 ) - output_stride = 17 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride - output_row_offset = output_striding + (block_size * output_stride * block_id) + output_row_offset = ( + output_striding + (block_size * output_stride * block_id) + col_offset + ) tl.store(output_ptr + output_row_offset, Y00, mask=output_row_offset < output_numel) tl.store( output_ptr + output_row_offset + 1, @@ -758,6 +761,8 @@ def eighth_order_bwd( block_size: tl.constexpr, coord_numel: tl.constexpr, output_numel: tl.constexpr, + col_offset: tl.constexpr, + output_stride: tl.constexpr, ): # work out the row offsets block_id = tl.program_id(0) @@ -773,9 +778,10 @@ def eighth_order_bwd( z = tl.load( coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel ) - output_stride = 17 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride - output_row_offset = output_striding + (block_size * output_stride * block_id) + output_row_offset = ( + output_striding + (block_size * output_stride * block_id) + col_offset + ) # load in gradients w.r.t. spherical harmonic projections g_0 = tl.load( sph_grad_ptr + output_row_offset, mask=output_row_offset < output_numel From ad84c008c373881ceab90b81c882857a2b30b00b Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:19:52 -0700 Subject: [PATCH 082/116] refactor: updating eighth order Function interface --- src/equitriton/sph_harm/direct/y_8.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_8.py b/src/equitriton/sph_harm/direct/y_8.py index a26abd7..812a643 100644 --- a/src/equitriton/sph_harm/direct/y_8.py +++ b/src/equitriton/sph_harm/direct/y_8.py @@ -12,25 +12,37 @@ class EighthOrderSphericalHarmonic(torch.autograd.Function): def forward( ctx, coords: torch.Tensor, + output_tensor: torch.Tensor | None = None, mask: torch.Tensor | None = None, block_size: int = 64, + col_offset: int = 0, ): - output_tensor = torch.empty( - (*coords.shape[:-1], 17), dtype=coords.dtype, device=coords.device - ) + if not isinstance(output_tensor, torch.Tensor): + output_tensor = torch.empty( + (*coords.shape[:-1], 17), dtype=coords.dtype, device=coords.device + ) coord_numel = coords.numel() output_numel = output_tensor.numel() num_blocks = calculate_lastdim_num_blocks(coords, block_size) # apply the kernel eighth_order_fwd[num_blocks,]( - coords, output_tensor, block_size, coord_numel, output_numel + coords, + output_tensor, + block_size, + coord_numel, + output_numel, + col_offset, + output_tensor.stride(-2), ) ctx.save_for_backward(coords) return output_tensor @staticmethod def backward( - ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 + ctx, + sph_grad_tensor: torch.Tensor, + block_size: int = 64, + col_offset: int = 0, ) -> torch.Tensor: (coords,) = ctx.saved_tensors coord_grad_output = torch.zeros_like(coords) @@ -43,6 +55,8 @@ def backward( block_size, coords.numel(), sph_grad_tensor.numel(), + col_offset, + sph_grad_tensor.stride(-2), ) return coord_grad_output From 690cd43c87b55194646e9f67dd2bafe56dc39f90 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:21:46 -0700 Subject: [PATCH 083/116] refactor: ninth order with col offset and output stride --- src/equitriton/sph_harm/direct/y_9.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_9.py b/src/equitriton/sph_harm/direct/y_9.py index 8e57ca7..ea5f833 100644 --- a/src/equitriton/sph_harm/direct/y_9.py +++ b/src/equitriton/sph_harm/direct/y_9.py @@ -502,6 +502,8 @@ def ninth_order_fwd( block_size: tl.constexpr, coord_numel: tl.constexpr, output_numel: tl.constexpr, + col_offset: tl.constexpr, + output_stride: tl.constexpr, ): # these are hardcoded because they are predetermined; coord_stride = 3 @@ -918,9 +920,10 @@ def ninth_order_fwd( + CONST091 * VAR06 * VAR23 + CONST105 * VAR08 * VAR21 ) - output_stride = 19 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride - output_row_offset = output_striding + (block_size * output_stride * block_id) + output_row_offset = ( + output_striding + (block_size * output_stride * block_id) + col_offset + ) tl.store(output_ptr + output_row_offset, Y00, mask=output_row_offset < output_numel) tl.store( output_ptr + output_row_offset + 1, @@ -1022,6 +1025,8 @@ def ninth_order_bwd( block_size: tl.constexpr, coord_numel: tl.constexpr, output_numel: tl.constexpr, + col_offset: tl.constexpr, + output_stride: tl.constexpr, ): # work out the row offsets block_id = tl.program_id(0) @@ -1037,9 +1042,10 @@ def ninth_order_bwd( z = tl.load( coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel ) - output_stride = 19 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride - output_row_offset = output_striding + (block_size * output_stride * block_id) + output_row_offset = ( + output_striding + (block_size * output_stride * block_id) + col_offset + ) # load in gradients w.r.t. spherical harmonic projections g_0 = tl.load( sph_grad_ptr + output_row_offset, mask=output_row_offset < output_numel From c0eca7df48004411529c0d48aedbadd061fd4b56 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:22:52 -0700 Subject: [PATCH 084/116] refactor: updating ninth order Function with new interface --- src/equitriton/sph_harm/direct/y_9.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_9.py b/src/equitriton/sph_harm/direct/y_9.py index ea5f833..d47497a 100644 --- a/src/equitriton/sph_harm/direct/y_9.py +++ b/src/equitriton/sph_harm/direct/y_9.py @@ -12,25 +12,38 @@ class NinthOrderSphericalHarmonic(torch.autograd.Function): def forward( ctx, coords: torch.Tensor, + output_tensor: torch.Tensor | None = None, mask: torch.Tensor | None = None, block_size: int = 64, + col_offset: int = 0, ): - output_tensor = torch.empty( - (*coords.shape[:-1], 19), dtype=coords.dtype, device=coords.device - ) + # allocate a tensor if one isn't given + if not isinstance(output_tensor, torch.Tensor): + output_tensor = torch.empty( + (*coords.shape[:-1], 19), dtype=coords.dtype, device=coords.device + ) coord_numel = coords.numel() output_numel = output_tensor.numel() num_blocks = calculate_lastdim_num_blocks(coords, block_size) # apply the kernel ninth_order_fwd[num_blocks,]( - coords, output_tensor, block_size, coord_numel, output_numel + coords, + output_tensor, + block_size, + coord_numel, + output_numel, + col_offset, + output_tensor.stride(-2), ) ctx.save_for_backward(coords) return output_tensor @staticmethod def backward( - ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 + ctx, + sph_grad_tensor: torch.Tensor, + block_size: int = 64, + col_offset: int = 0, ) -> torch.Tensor: (coords,) = ctx.saved_tensors coord_grad_output = torch.zeros_like(coords) @@ -43,6 +56,8 @@ def backward( block_size, coords.numel(), sph_grad_tensor.numel(), + col_offset, + sph_grad_tensor.stride(-2), ) return coord_grad_output From 0839101e7b08fe77089beb31d141c6652382fc00 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:24:19 -0700 Subject: [PATCH 085/116] refactor: tenth order with col offset and output stride --- src/equitriton/sph_harm/direct/y_10.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_10.py b/src/equitriton/sph_harm/direct/y_10.py index 1c41062..82e7db4 100644 --- a/src/equitriton/sph_harm/direct/y_10.py +++ b/src/equitriton/sph_harm/direct/y_10.py @@ -602,6 +602,8 @@ def tenth_order_fwd( block_size: tl.constexpr, coord_numel: tl.constexpr, output_numel: tl.constexpr, + col_offset: tl.constexpr, + output_stride: tl.constexpr, ): # these are hardcoded because they are predetermined; coord_stride = 3 @@ -1116,9 +1118,10 @@ def tenth_order_fwd( + CONST183 * VAR00 - CONST183 * VAR18 ) - output_stride = 21 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride - output_row_offset = output_striding + (block_size * output_stride * block_id) + output_row_offset = ( + output_striding + (block_size * output_stride * block_id) + col_offset + ) tl.store(output_ptr + output_row_offset, Y00, mask=output_row_offset < output_numel) tl.store( output_ptr + output_row_offset + 1, @@ -1230,6 +1233,8 @@ def tenth_order_bwd( block_size: tl.constexpr, coord_numel: tl.constexpr, output_numel: tl.constexpr, + col_offset: tl.constexpr, + output_stride: tl.constexpr, ): # work out the row offsets block_id = tl.program_id(0) @@ -1245,9 +1250,10 @@ def tenth_order_bwd( z = tl.load( coord_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel ) - output_stride = 21 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride - output_row_offset = output_striding + (block_size * output_stride * block_id) + output_row_offset = ( + output_striding + (block_size * output_stride * block_id) + col_offset + ) # load in gradients w.r.t. spherical harmonic projections g_0 = tl.load( sph_grad_ptr + output_row_offset, mask=output_row_offset < output_numel From 6830d4c797a5987dcee9887dc7648b3bab804dfd Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:25:17 -0700 Subject: [PATCH 086/116] refactor: updating tenth order Function interface --- src/equitriton/sph_harm/direct/y_10.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_10.py b/src/equitriton/sph_harm/direct/y_10.py index 82e7db4..6c98b89 100644 --- a/src/equitriton/sph_harm/direct/y_10.py +++ b/src/equitriton/sph_harm/direct/y_10.py @@ -12,25 +12,38 @@ class TenthOrderSphericalHarmonic(torch.autograd.Function): def forward( ctx, coords: torch.Tensor, + output_tensor: torch.Tensor | None = None, mask: torch.Tensor | None = None, block_size: int = 64, + col_offset: int = 0, ): - output_tensor = torch.empty( - (*coords.shape[:-1], 21), dtype=coords.dtype, device=coords.device - ) + # allocate a tensor if one isn't given + if not isinstance(output_tensor, torch.Tensor): + output_tensor = torch.empty( + (*coords.shape[:-1], 21), dtype=coords.dtype, device=coords.device + ) coord_numel = coords.numel() output_numel = output_tensor.numel() num_blocks = calculate_lastdim_num_blocks(coords, block_size) # apply the kernel tenth_order_fwd[num_blocks,]( - coords, output_tensor, block_size, coord_numel, output_numel + coords, + output_tensor, + block_size, + coord_numel, + output_numel, + col_offset, + output_tensor.stride(-2), ) ctx.save_for_backward(coords) return output_tensor @staticmethod def backward( - ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 + ctx, + sph_grad_tensor: torch.Tensor, + block_size: int = 64, + col_offset: int = 0, ) -> torch.Tensor: (coords,) = ctx.saved_tensors coord_grad_output = torch.zeros_like(coords) @@ -43,6 +56,8 @@ def backward( block_size, coords.numel(), sph_grad_tensor.numel(), + col_offset, + sph_grad_tensor.stride(-2), ) return coord_grad_output From db8f09e12b4374991becf8c4bf79b3553587ca6c Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:30:53 -0700 Subject: [PATCH 087/116] refactor: making zeroth order consistent --- src/equitriton/sph_harm/direct/y_0.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_0.py b/src/equitriton/sph_harm/direct/y_0.py index 04ba291..6a1b5b1 100644 --- a/src/equitriton/sph_harm/direct/y_0.py +++ b/src/equitriton/sph_harm/direct/y_0.py @@ -60,10 +60,27 @@ def zeroth_order_fwd( block_size: tl.constexpr, coord_numel: tl.constexpr, output_numel: tl.constexpr, + col_offset: tl.constexpr, + output_stride: tl.constexpr, ): # work out the row offsets block_id = tl.program_id(0) - output_stride = 1 # [2l + 1] output_striding = tl.arange(0, block_size) * output_stride - output_row_offset = output_striding + (block_size * output_stride * block_id) + output_row_offset = ( + output_striding + (block_size * output_stride * block_id) + col_offset + ) tl.store(output_ptr + output_row_offset, 1.0, mask=output_row_offset < output_numel) + + +@triton.jit +def zeroth_order_bwd( + output_ptr: tl.tensor, + block_size: tl.constexpr, + coord_numel: tl.constexpr, + output_numel: tl.constexpr, + col_offset: tl.constexpr, + output_stride: tl.constexpr, +): + # work out the row offsets + block_id = tl.program_id(0) # noqa: F841 + # do nothing in this function because no gradient contributions! From bdd9cb1af8dec9c5f225557fc8938eb0f3cacd3f Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:32:44 -0700 Subject: [PATCH 088/116] refactor: adding coord ptr back to zeroth order for consistency --- src/equitriton/sph_harm/direct/y_0.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/equitriton/sph_harm/direct/y_0.py b/src/equitriton/sph_harm/direct/y_0.py index 6a1b5b1..06c32ba 100644 --- a/src/equitriton/sph_harm/direct/y_0.py +++ b/src/equitriton/sph_harm/direct/y_0.py @@ -56,6 +56,7 @@ def _torch_fwd(coords: torch.Tensor) -> torch.Tensor: @triton.jit def zeroth_order_fwd( + coord_ptr: tl.tensor, output_ptr: tl.tensor, block_size: tl.constexpr, coord_numel: tl.constexpr, From 5e08128e686542e047c613b5d08210dc51db2cd3 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:35:33 -0700 Subject: [PATCH 089/116] refactor: updating zeroth order Function interface for consistency --- src/equitriton/sph_harm/direct/y_0.py | 39 +++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_0.py b/src/equitriton/sph_harm/direct/y_0.py index 06c32ba..3eb2df0 100644 --- a/src/equitriton/sph_harm/direct/y_0.py +++ b/src/equitriton/sph_harm/direct/y_0.py @@ -2,6 +2,8 @@ import torch from triton import language as tl +from equitriton.utils import calculate_lastdim_num_blocks + __all__ = ["ZerothOrderSphericalHarmonic"] @@ -10,21 +12,48 @@ class ZerothOrderSphericalHarmonic(torch.autograd.Function): def forward( ctx, coords: torch.Tensor, + output_tensor: torch.Tensor | None = None, mask: torch.Tensor | None = None, block_size: int = 64, + col_offset: int = 0, ): - output_tensor = torch.ones( - (*coords.shape[:-1], 1), dtype=coords.dtype, device=coords.device - ) + if not isinstance(output_tensor, torch.Tensor): + output_tensor = torch.ones( + (*coords.shape[:-1], 1), dtype=coords.dtype, device=coords.device + ) ctx.save_for_backward(coords) + coord_numel = coords.numel() + output_numel = output_tensor.numel() + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + zeroth_order_fwd[num_blocks,]( + coords, + output_tensor, + block_size, + coord_numel, + output_numel, + col_offset, + output_tensor.stride(-2), + ) return output_tensor @staticmethod def backward( - ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64 + ctx, sph_grad_tensor: torch.Tensor, block_size: int = 64, col_offset: int = 0 ) -> torch.Tensor: (coords,) = ctx.saved_tensors - return torch.zeros_like(coords) + coord_grad_output = torch.zeros_like(coords) + num_blocks = calculate_lastdim_num_blocks(coords, block_size) + # call backward kernel + zeroth_order_bwd[num_blocks,]( + coord_grad_output, + sph_grad_tensor, + block_size, + coords.numel(), + sph_grad_tensor.numel(), + col_offset, + sph_grad_tensor.stride(-2), + ) + return coord_grad_output def _torch_fwd(coords: torch.Tensor) -> torch.Tensor: From 1f25519cebbb23815e96581b0107904d4b227d48 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:36:29 -0700 Subject: [PATCH 090/116] refactor: correcting zeroth order bwd for consistency --- src/equitriton/sph_harm/direct/y_0.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/equitriton/sph_harm/direct/y_0.py b/src/equitriton/sph_harm/direct/y_0.py index 3eb2df0..ddf54ec 100644 --- a/src/equitriton/sph_harm/direct/y_0.py +++ b/src/equitriton/sph_harm/direct/y_0.py @@ -104,7 +104,9 @@ def zeroth_order_fwd( @triton.jit def zeroth_order_bwd( - output_ptr: tl.tensor, + coord_ptr: tl.tensor, + coord_grad_ptr: tl.tensor, + sph_grad_ptr: tl.tensor, block_size: tl.constexpr, coord_numel: tl.constexpr, output_numel: tl.constexpr, From eb699b285e4b4a6c0f1712e8db259734f8ac2d7e Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:39:05 -0700 Subject: [PATCH 091/116] fix: making second order add to gradients instead of overwrite --- src/equitriton/sph_harm/direct/y_2.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_2.py b/src/equitriton/sph_harm/direct/y_2.py index 02f7cf6..3448158 100644 --- a/src/equitriton/sph_harm/direct/y_2.py +++ b/src/equitriton/sph_harm/direct/y_2.py @@ -210,14 +210,23 @@ def second_order_bwd( g_Y24 = tl.load( sph_grad_ptr + output_row_offset + 4, mask=output_row_offset + 4 < output_numel ) - g_x = ( + g_x = tl.load( + coord_grad_ptr + coord_row_offset, mask=coord_row_offset < coord_numel + ) + g_y = tl.load( + coord_grad_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + g_z = tl.load( + coord_grad_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + g_x += ( CONST_00 * g_Y20 * z + CONST_00 * g_Y21 * y - CONST_01 * g_Y22 * x - CONST_00 * g_Y24 * x ) - g_y = CONST_00 * g_Y21 * x + CONST_02 * g_Y22 * y + CONST_00 * g_Y23 * z - g_z = ( + g_y += CONST_00 * g_Y21 * x + CONST_02 * g_Y22 * y + CONST_00 * g_Y23 * z + g_z += ( CONST_00 * g_Y20 * x - CONST_01 * g_Y22 * z + CONST_00 * g_Y23 * y From 86ba0bc9af729715523857128811bf95d429fabc Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:40:02 -0700 Subject: [PATCH 092/116] refactor: making third order add to gradients --- src/equitriton/sph_harm/direct/y_3.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_3.py b/src/equitriton/sph_harm/direct/y_3.py index 0a2b3d5..1d39314 100644 --- a/src/equitriton/sph_harm/direct/y_3.py +++ b/src/equitriton/sph_harm/direct/y_3.py @@ -271,7 +271,16 @@ def third_order_bwd( VAR17 = y * y VAR26 = z * z # -------------------- kernel implementations - g_x = ( + g_x = tl.load( + coord_grad_ptr + coord_row_offset, mask=coord_row_offset < coord_numel + ) + g_y = tl.load( + coord_grad_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + g_z = tl.load( + coord_grad_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + g_x += ( CONST008 * g_6 * x * z - CONST009 * g_1 * y * z + CONST009 * g_5 * x * y @@ -280,14 +289,14 @@ def third_order_bwd( + g_0 * (CONST011 * VAR08 - CONST011 * VAR26) + g_2 * (CONST002 * VAR17 + CONST013 * VAR08 + CONST015 * VAR26) ) - g_y = ( + g_y += ( CONST005 * g_2 * x * y + CONST005 * g_4 * y * z - CONST009 * g_1 * x * z + g_3 * (CONST007 * VAR08 + CONST007 * VAR26 - CONST010 * VAR17) + g_5 * (CONST012 * VAR08 - CONST012 * VAR26) ) - g_z = ( + g_z += ( -CONST008 * g_0 * x * z - CONST009 * g_1 * x * y - CONST009 * g_5 * y * z From 378dc60f003847b0438cdd7d4deb42039db974de Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:40:55 -0700 Subject: [PATCH 093/116] refactor: making fourth order add to gradients --- src/equitriton/sph_harm/direct/y_4.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_4.py b/src/equitriton/sph_harm/direct/y_4.py index 7ee616b..ba964c3 100644 --- a/src/equitriton/sph_harm/direct/y_4.py +++ b/src/equitriton/sph_harm/direct/y_4.py @@ -337,7 +337,16 @@ def fourth_order_bwd( VAR25 = z * z * z VAR26 = z * z # -------------------- kernel implementations - g_x = ( + g_x = tl.load( + coord_grad_ptr + coord_row_offset, mask=coord_row_offset < coord_numel + ) + g_y = tl.load( + coord_grad_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + g_z = tl.load( + coord_grad_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + g_x += ( CONST015 * g_7 * x * y * z + CONST022 * g_5 * x * y * z + g_0 * (CONST017 * VAR08 * z - CONST025 * VAR25) @@ -349,7 +358,7 @@ def fourth_order_bwd( + g_6 * (-CONST016 * VAR07 + CONST019 * VAR17 * x) + g_8 * (CONST017 * VAR26 * x - CONST025 * VAR07) ) - g_y = ( + g_y += ( CONST000 * g_6 * y * (CONST023 * VAR08 - CONST023 * VAR26) + CONST014 * g_2 * x * y * z + g_1 * (-CONST020 * VAR26 * x + CONST027 * VAR07) @@ -358,7 +367,7 @@ def fourth_order_bwd( + g_5 * (CONST026 * VAR25 + z * (CONST012 * VAR17 + CONST026 * VAR08)) + g_7 * (CONST020 * VAR08 * z - CONST027 * VAR25) ) - g_z = ( + g_z += ( -CONST015 * g_1 * x * y * z + CONST022 * g_3 * x * y * z + g_0 * (-CONST017 * VAR26 * x + CONST025 * VAR07) From 2cc5f11f8662d603bc9b57f276d9699d9d121d33 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:43:00 -0700 Subject: [PATCH 094/116] refactor: making fifth order add to gradients --- src/equitriton/sph_harm/direct/y_5.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_5.py b/src/equitriton/sph_harm/direct/y_5.py index 93a52f1..22065af 100644 --- a/src/equitriton/sph_harm/direct/y_5.py +++ b/src/equitriton/sph_harm/direct/y_5.py @@ -452,7 +452,16 @@ def fifth_order_bwd( VAR25 = z * z * z VAR26 = z * z # -------------------- kernel implementations - g_x = ( + g_x = tl.load( + coord_grad_ptr + coord_row_offset, mask=coord_row_offset < coord_numel + ) + g_y = tl.load( + coord_grad_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + g_z = tl.load( + coord_grad_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + g_x += ( g_0 * (CONST009 * VAR06 + CONST009 * VAR24 + CONST040 * VAR08 * VAR26) + g_1 * y * (CONST038 * VAR08 * z - CONST052 * VAR25) + g_10 * (CONST029 * VAR07 * z + CONST043 * VAR25 * x) @@ -478,7 +487,7 @@ def fifth_order_bwd( + g_8 * (CONST008 * VAR25 * x + z * (CONST039 * VAR17 * x - CONST054 * VAR07)) + g_9 * y * (CONST024 * VAR07 + CONST038 * VAR26 * x) ) - g_y = ( + g_y += ( g_1 * (CONST052 * VAR07 * z - CONST052 * VAR25 * x) + g_2 * (-CONST039 * VAR26 * x * y + CONST053 * VAR07 * y) + g_3 * (CONST058 * VAR07 * z + x * (CONST034 * VAR17 * z + CONST057 * VAR25)) @@ -501,7 +510,7 @@ def fifth_order_bwd( + g_8 * (CONST021 * VAR25 * y + CONST039 * VAR08 * y * z) + g_9 * (CONST027 * VAR06 + CONST027 * VAR24 + CONST044 * VAR08 * VAR26) ) - g_z = ( + g_z += ( g_0 * (CONST029 * VAR25 * x + CONST043 * VAR07 * z) + g_1 * y * (-CONST038 * VAR26 * x + CONST052 * VAR07) + g_10 * (CONST009 * VAR06 + CONST009 * VAR24 + CONST040 * VAR08 * VAR26) From 27f95a570845594aecae391b6fc8c47c727dea6b Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:43:44 -0700 Subject: [PATCH 095/116] refactor: making sixth order add to gradients --- src/equitriton/sph_harm/direct/y_6.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_6.py b/src/equitriton/sph_harm/direct/y_6.py index 4d0284d..c376c61 100644 --- a/src/equitriton/sph_harm/direct/y_6.py +++ b/src/equitriton/sph_harm/direct/y_6.py @@ -559,7 +559,16 @@ def sixth_order_bwd( VAR23 = VAR25 * VAR26 VAR24 = VAR26 * VAR26 # -------------------- kernel implementations - g_x = ( + g_x = tl.load( + coord_grad_ptr + coord_row_offset, mask=coord_row_offset < coord_numel + ) + g_y = tl.load( + coord_grad_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + g_z = tl.load( + coord_grad_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + g_x += ( g_0 * (CONST054 * VAR08 * VAR25 - CONST065 * VAR06 * z - CONST080 * VAR23) + g_1 * y * (CONST028 * VAR06 + CONST028 * VAR24 + CONST048 * VAR08 * VAR26) + g_10 @@ -619,7 +628,7 @@ def sixth_order_bwd( + g_9 * (CONST053 * VAR16 * x * z + y * (CONST042 * VAR07 * z - CONST073 * VAR25 * x)) ) - g_y = ( + g_y += ( CONST000 * g_2 * y * (CONST066 * VAR07 * z - CONST066 * VAR25 * x) + g_1 * (CONST007 * VAR05 + CONST028 * VAR24 * x + CONST062 * VAR07 * VAR26) + g_10 @@ -669,7 +678,7 @@ def sixth_order_bwd( + CONST083 * VAR23 ) ) - g_z = ( + g_z += ( g_0 * (CONST054 * VAR07 * VAR26 - CONST065 * VAR24 * x - CONST080 * VAR05) + g_1 * y * (CONST052 * VAR07 * z - CONST052 * VAR25 * x) + g_10 From ae07fc6248eb00a2f7a7eb56fcbf8f9e8ba39c12 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:44:42 -0700 Subject: [PATCH 096/116] refactor: making seventh order add to gradients --- src/equitriton/sph_harm/direct/y_7.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_7.py b/src/equitriton/sph_harm/direct/y_7.py index 06deb1a..deece19 100644 --- a/src/equitriton/sph_harm/direct/y_7.py +++ b/src/equitriton/sph_harm/direct/y_7.py @@ -787,7 +787,16 @@ def seventh_order_bwd( VAR23 = VAR25 * VAR26 VAR24 = VAR26 * VAR26 # -------------------- kernel implementations - g_x = ( + g_x = tl.load( + coord_grad_ptr + coord_row_offset, mask=coord_row_offset < coord_numel + ) + g_y = tl.load( + coord_grad_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + g_z = tl.load( + coord_grad_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + g_x += ( g_0 * ( CONST082 * VAR08 * VAR24 @@ -881,7 +890,7 @@ def seventh_order_bwd( + y * (CONST018 * VAR24 * x + CONST119 * VAR05 + CONST131 * VAR07 * VAR26) ) ) - g_y = ( + g_y += ( g_1 * (CONST039 * VAR23 * x + CONST095 * VAR07 * VAR25 - CONST125 * VAR05 * z) + g_10 * ( @@ -970,7 +979,7 @@ def seventh_order_bwd( - CONST152 * VAR22 ) ) - g_z = ( + g_z += ( g_0 * (CONST069 * VAR07 * VAR25 - CONST109 * VAR05 * z - CONST109 * VAR23 * x) + g_1 * y From 8ada103a1d46f5bf7397857ebc5c87c7ca98ccca Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:45:19 -0700 Subject: [PATCH 097/116] refactor: making eighth order add gradients --- src/equitriton/sph_harm/direct/y_8.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_8.py b/src/equitriton/sph_harm/direct/y_8.py index 812a643..ae987c4 100644 --- a/src/equitriton/sph_harm/direct/y_8.py +++ b/src/equitriton/sph_harm/direct/y_8.py @@ -1050,7 +1050,16 @@ def eighth_order_bwd( VAR22 = VAR25 * VAR25 VAR23 = VAR25 * VAR26 # -------------------- kernel implementations - g_x = ( + g_x = tl.load( + coord_grad_ptr + coord_row_offset, mask=coord_row_offset < coord_numel + ) + g_y = tl.load( + coord_grad_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + g_z = tl.load( + coord_grad_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + g_x += ( g_0 * ( CONST049 * VAR08 * VAR23 @@ -1205,7 +1214,7 @@ def eighth_order_bwd( * (CONST117 * VAR14 * x + CONST170 * VAR05 * y + CONST171 * VAR07 * VAR16) ) ) - g_y = ( + g_y += ( CONST000 * g_14 * y @@ -1355,7 +1364,7 @@ def eighth_order_bwd( ) ) ) - g_z = ( + g_z += ( g_0 * ( -CONST049 * VAR05 * VAR26 From 126c536d2ff5b28ebdf233c3271aef19960ba1a8 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:46:33 -0700 Subject: [PATCH 098/116] refactor: ninth order adds grads --- src/equitriton/sph_harm/direct/y_9.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_9.py b/src/equitriton/sph_harm/direct/y_9.py index d47497a..a188f21 100644 --- a/src/equitriton/sph_harm/direct/y_9.py +++ b/src/equitriton/sph_harm/direct/y_9.py @@ -1399,7 +1399,16 @@ def ninth_order_bwd( VAR22 = VAR25 * VAR25 VAR23 = VAR25 * VAR26 # -------------------- kernel implementations - g_x = ( + g_x = tl.load( + coord_grad_ptr + coord_row_offset, mask=coord_row_offset < coord_numel + ) + g_y = tl.load( + coord_grad_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + g_z = tl.load( + coord_grad_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + g_x += ( g_0 * ( CONST021 * VAR20 @@ -1618,7 +1627,7 @@ def ninth_order_bwd( ) ) ) - g_y = ( + g_y += ( CONST001 * g_16 * y @@ -1838,7 +1847,7 @@ def ninth_order_bwd( + CONST070 * VAR02 ) ) - g_z = ( + g_z += ( g_0 * ( CONST132 * VAR07 * VAR23 From 477dd46b554b175fcb7b5848eb252e087ac33153 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:47:04 -0700 Subject: [PATCH 099/116] refactor: tenth order adds grads --- src/equitriton/sph_harm/direct/y_10.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/equitriton/sph_harm/direct/y_10.py b/src/equitriton/sph_harm/direct/y_10.py index 6c98b89..af925fb 100644 --- a/src/equitriton/sph_harm/direct/y_10.py +++ b/src/equitriton/sph_harm/direct/y_10.py @@ -1700,7 +1700,16 @@ def tenth_order_bwd( VAR21 = VAR24 * VAR25 VAR22 = VAR25 * VAR25 # -------------------- kernel implementations - g_x = ( + g_x = tl.load( + coord_grad_ptr + coord_row_offset, mask=coord_row_offset < coord_numel + ) + g_y = tl.load( + coord_grad_ptr + coord_row_offset + 1, mask=coord_row_offset + 1 < coord_numel + ) + g_z = tl.load( + coord_grad_ptr + coord_row_offset + 2, mask=coord_row_offset + 2 < coord_numel + ) + g_x += ( g_0 * ( CONST093 * VAR02 * z @@ -2015,7 +2024,7 @@ def tenth_order_bwd( + CONST331 * VAR16 * VAR22 ) ) - g_y = ( + g_y += ( CONST000 * g_18 * y @@ -2289,7 +2298,7 @@ def tenth_order_bwd( ) ) ) - g_z = ( + g_z += ( g_0 * ( CONST093 * VAR20 * x From 1c1b14108d98ed373927b06fc3cd127b756a9d0a Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:49:56 -0700 Subject: [PATCH 100/116] refactor: changing direct module namespace --- src/equitriton/sph_harm/direct/__init__.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/equitriton/sph_harm/direct/__init__.py b/src/equitriton/sph_harm/direct/__init__.py index 111e106..88250f5 100644 --- a/src/equitriton/sph_harm/direct/__init__.py +++ b/src/equitriton/sph_harm/direct/__init__.py @@ -1,13 +1,11 @@ -from equitriton.sph_harm.direct.y_2 import SecondOrderSphericalHarmonic -from equitriton.sph_harm.direct.y_5 import FifthOrderSphericalHarmonic -from equitriton.sph_harm.direct.y_10 import TenthOrderSphericalHarmonic from equitriton.sph_harm.direct.special import FusedSecondOrderSphericalHarmonic -from equitriton.sph_harm.direct.utils import triton_spherical_harmonic +from equitriton.sph_harm.direct.utils import ( + triton_spherical_harmonic, + TritonSphericalHarmonic, +) __all__ = [ - "SecondOrderSphericalHarmonic", - "FifthOrderSphericalHarmonic", - "TenthOrderSphericalHarmonic", "FusedSecondOrderSphericalHarmonic", "triton_spherical_harmonic", + "TritonSphericalHarmonic", ] From 6e9732ea6d6c829a5632bfd9f1f721aa1cd00318 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:51:06 -0700 Subject: [PATCH 101/116] refactor: using preallocated interface for production --- src/equitriton/model/blocks.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/equitriton/model/blocks.py b/src/equitriton/model/blocks.py index fc3e362..48be47d 100644 --- a/src/equitriton/model/blocks.py +++ b/src/equitriton/model/blocks.py @@ -12,7 +12,7 @@ from torch_geometric.data import Data as PyGGraph from equitriton.utils import spherical_harmonics_irreps -from equitriton.sph_harm.direct import triton_spherical_harmonic +from equitriton.sph_harm.direct import TritonSphericalHarmonic __all__ = [ @@ -124,9 +124,7 @@ def forward(self, coords: torch.Tensor) -> torch.Tensor: if not self.use_e3nn: if self.normalize: coords = torch.nn.functional.normalize(coords, dim=-1) - # TODO concatenation is slow; work directly on pre-allocated tensors - outputs = [triton_spherical_harmonic(l, coords) for l in self.l_values] - outputs = torch.cat(outputs, dim=-1) + outputs = TritonSphericalHarmonic.apply(self.l_values, coords) if self.normalization == "integral": outputs /= (4.0 * torch.pi) ** 0.5 return outputs From 1ff79fa411a4ea5faa01437e3351941f9d61d7b0 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Sun, 1 Sep 2024 21:57:12 -0700 Subject: [PATCH 102/116] refactor: supporting triton_spherical_harmonics_function for backwards compatibility --- src/equitriton/sph_harm/direct/utils.py | 39 ++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/equitriton/sph_harm/direct/utils.py b/src/equitriton/sph_harm/direct/utils.py index 09ba75d..8c66642 100644 --- a/src/equitriton/sph_harm/direct/utils.py +++ b/src/equitriton/sph_harm/direct/utils.py @@ -13,6 +13,43 @@ BLOCK_SIZE = 64 +def _get_autograd_func(l: int) -> type[torch.autograd.Function]: + """ + Function that will grab the autograd.Function for a specified + l order. + + Parameters + ---------- + l : int + Order of spherical harmonic to compute. + + Returns + ------- + type[torch.autograd.Function] + Class reference to the autograd Function. + + Raises + ------ + ModuleNotFoundError: + If the order of spherical harmonic is not implemented, + the module will not exist. + RuntimeError: + If the autograd.Function can't be found. + """ + try: + target_module = import_module(f"equitriton.sph_harm.direct.y_{l}") + except ModuleNotFoundError as e: + raise ModuleNotFoundError( + f"Spherical harmonic order l={l} requested, but not found!" + ) from e + defined_objs = dir(target_module) + for key in defined_objs: + if "SphericalHarmonic" in key: + sph_harm_func = getattr(target_module, key) + return sph_harm_func + raise RuntimeError(f"Namespace for module l={l} is broken!") + + def _get_fwd_kernel(l: int) -> Callable: """ Reach into the module of a specified l value and grab @@ -196,7 +233,7 @@ def triton_spherical_harmonic( requires_grad=True, ) for l, offset in zip(l_values, offsets): - sph_harm_func = _get_fwd_kernel(l) + sph_harm_func = _get_autograd_func(l) sph_harm_func.apply(coords, output_tensor, mask, BLOCK_SIZE, offset) return output_tensor From d2d25fdd03d095ef999282c3a0162f18cc704ee5 Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Tue, 3 Sep 2024 11:39:23 -0700 Subject: [PATCH 103/116] feat: added model property to count irrep shapes Signed-off-by: Kin Long Kelvin Lee --- src/equitriton/model/blocks.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/equitriton/model/blocks.py b/src/equitriton/model/blocks.py index 48be47d..26e43ed 100644 --- a/src/equitriton/model/blocks.py +++ b/src/equitriton/model/blocks.py @@ -1,6 +1,7 @@ from __future__ import annotations from typing import Literal, Any, Callable +from collections import Counter import torch from torch import nn @@ -369,6 +370,12 @@ def __init__( self.skip_connections = skip_connections self.output_dim = output_dim + @property + def output_irrep_shapes(self) -> dict[str, int]: + # this returns a dictionary for each l-order, the number + # of expected elements for that particular order in the output + return dict(Counter(self.initial_layer.output_irreps.ls)) + def visualize(self, **kwargs): """ Visualize the sequence of tensor products within the model. From bc1bd7e494211119eed8f1a2dfbcb758f98d8c35 Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Tue, 3 Sep 2024 11:51:32 -0700 Subject: [PATCH 104/116] feat: added embedding separation utility Signed-off-by: Kin Long Kelvin Lee --- src/equitriton/utils.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/equitriton/utils.py b/src/equitriton/utils.py index 68214f2..90dfae4 100644 --- a/src/equitriton/utils.py +++ b/src/equitriton/utils.py @@ -1,9 +1,11 @@ from __future__ import annotations +from collections import Counter import math import torch import triton +import numpy as np from e3nn import o3 __all__ = [ @@ -170,3 +172,37 @@ def spherical_harmonics_irreps(l_values: list[int], num_feat: int = 1) -> o3.Irr parity = "e" if (-1) ** l > 0 else "o" joint.append(f"{num_feat}x{l}{parity}") return o3.Irreps("+".join(joint)) + + +def separate_embedding_irreps( + embeddings: torch.Tensor, irreps: o3.Irreps, return_numpy: bool = True +) -> dict[int, torch.Tensor]: + """ + Utility function that will split a joint embedding tensor + into embeddings for individual orders. + + Parameters + ---------- + embeddings : torch.Tensor + PyTorch N-d tensor containing embeddings for all irreps. + irreps : o3.Irreps + Object containing information on which orders of + representations, and how many. + + Returns + ------- + dict[int, torch.Tensor] + Dictionary mapping a tensor chunk with its corresponding order. + """ + # just for safety, clone the tensor for chunking + embeddings = embeddings.detach().cpu() + irrep_dims = dict(Counter(irreps.ls)) + splits = np.cumsum(list(irrep_dims.values())).tolist() + return_dict = {} + chunks = torch.tensor_split(embeddings, splits, dim=-1) + # should be an extra empty chunk but zip should skip it + for key, chunk in zip(irrep_dims.keys(), chunks): + if return_numpy: + chunk = chunk.numpy() + return_dict[key] = chunk + return return_dict From a3292c53c8793a90ec7c02bda94c9889ce33be13 Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Tue, 3 Sep 2024 11:52:24 -0700 Subject: [PATCH 105/116] refactor: adding splitting utility into __all__ Signed-off-by: Kin Long Kelvin Lee --- src/equitriton/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/equitriton/utils.py b/src/equitriton/utils.py index 90dfae4..4b1900c 100644 --- a/src/equitriton/utils.py +++ b/src/equitriton/utils.py @@ -13,6 +13,7 @@ "calculate_lastdim_num_blocks", "spherical_harmonics_irreps", "num_irreps_projections", + "separate_embedding_irreps", ] From d10b0b50e9d4ce2c9c64f0887dd81e27b944d4aa Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Tue, 3 Sep 2024 13:19:17 -0700 Subject: [PATCH 106/116] refactor: letting embedding split function take numpy arrays Signed-off-by: Kin Long Kelvin Lee --- src/equitriton/utils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/equitriton/utils.py b/src/equitriton/utils.py index 4b1900c..9eed927 100644 --- a/src/equitriton/utils.py +++ b/src/equitriton/utils.py @@ -176,7 +176,7 @@ def spherical_harmonics_irreps(l_values: list[int], num_feat: int = 1) -> o3.Irr def separate_embedding_irreps( - embeddings: torch.Tensor, irreps: o3.Irreps, return_numpy: bool = True + embeddings: torch.Tensor | np.ndarray, irreps: o3.Irreps, return_numpy: bool = True ) -> dict[int, torch.Tensor]: """ Utility function that will split a joint embedding tensor @@ -196,7 +196,10 @@ def separate_embedding_irreps( Dictionary mapping a tensor chunk with its corresponding order. """ # just for safety, clone the tensor for chunking - embeddings = embeddings.detach().cpu() + if isinstance(embeddings, torch.Tensor): + embeddings = embeddings.detach().cpu() + if isinstance(embeddings, np.ndarray): + embeddings = torch.from_numpy(embeddings) irrep_dims = dict(Counter(irreps.ls)) splits = np.cumsum(list(irrep_dims.values())).tolist() return_dict = {} From 1f7d9321db1e6e7cf26995ababb6c26de489d240 Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Tue, 8 Oct 2024 09:03:35 -0700 Subject: [PATCH 107/116] configs: updating configs used in experiments Signed-off-by: Kin Long Kelvin Lee --- scripts/model_configs/10-lonely.yaml | 46 ++++++++++++++++++ scripts/model_configs/4-lonely.yaml | 46 ++++++++++++++++++ scripts/model_configs/6-lonely.yaml | 46 ++++++++++++++++++ scripts/model_configs/8-lonely.yaml | 46 ++++++++++++++++++ .../{baseline.yaml => baseline.yaml.ignore} | 15 +++--- scripts/model_configs/baseline_big.yaml | 17 +++---- scripts/model_configs/e3nn.yaml | 47 +++++++++++++++++++ scripts/model_configs/equivariant.yaml | 14 +++--- scripts/model_configs/even.yaml | 16 +++---- scripts/model_configs/full.yaml | 18 +++---- scripts/model_configs/micro-full.yaml | 45 ++++++++++++++++++ scripts/model_configs/mini-full.yaml | 45 ++++++++++++++++++ scripts/model_configs/mini-long.yaml | 39 +++++++++++++++ scripts/model_configs/nano-full.yaml | 45 ++++++++++++++++++ scripts/model_configs/nano-long.yaml | 45 ++++++++++++++++++ scripts/model_configs/skipped.yaml | 14 +++--- scripts/model_configs/unconventional.yaml | 45 ++++++++++++++++++ .../model_configs/unconventional_long.yaml | 45 ++++++++++++++++++ 18 files changed, 588 insertions(+), 46 deletions(-) create mode 100644 scripts/model_configs/10-lonely.yaml create mode 100644 scripts/model_configs/4-lonely.yaml create mode 100644 scripts/model_configs/6-lonely.yaml create mode 100644 scripts/model_configs/8-lonely.yaml rename scripts/model_configs/{baseline.yaml => baseline.yaml.ignore} (79%) create mode 100644 scripts/model_configs/e3nn.yaml create mode 100644 scripts/model_configs/micro-full.yaml create mode 100644 scripts/model_configs/mini-full.yaml create mode 100644 scripts/model_configs/mini-long.yaml create mode 100644 scripts/model_configs/nano-full.yaml create mode 100644 scripts/model_configs/nano-long.yaml create mode 100644 scripts/model_configs/unconventional.yaml create mode 100644 scripts/model_configs/unconventional_long.yaml diff --git a/scripts/model_configs/10-lonely.yaml b/scripts/model_configs/10-lonely.yaml new file mode 100644 index 0000000..d29e04e --- /dev/null +++ b/scripts/model_configs/10-lonely.yaml @@ -0,0 +1,46 @@ +# pytorch_lightning==2.2.4 +seed_everything: 21616 +trainer: + accelerator: auto + strategy: auto + devices: auto + num_nodes: 1 + precision: null + logger: + class_path: pytorch_lightning.loggers.WandbLogger + init_args: + project: "equitriton-qm9" + entity: "laserkelvin" + log_model: true + #callbacks: + # - class_path: pytorch_lightning.callbacks.EarlyStopping + # init_args: + # monitor: "val_loss_epoch" + # patience: 5 + # mode: "min" + max_epochs: 100 + min_epochs: 15 +model: + model_class: equitriton.model.EquiTritonModel + model_kwargs: + initial_atom_dim: 64 + num_layers: 3 + output_dim: 1 + l_values: [0, 1, 2, 10] # just including 10 + edge_dim: 20 + hidden_dim: 16 + radius_cutoff: 6.0 + degree_norm: 6.08275253 # sqrt(37), avg degree + sph_harm_kwargs: + use_e3nn: false + e_mean: -76.1160 + e_std: 10.3238 + lr: 0.001 + weight_decay: 0.0 + atom_weighted_loss: false +data: + root_path: ./qm9_data + batch_size: 32 + train_frac: 0.8 + val_frac: 0.1 + num_workers: 4 diff --git a/scripts/model_configs/4-lonely.yaml b/scripts/model_configs/4-lonely.yaml new file mode 100644 index 0000000..8c705b8 --- /dev/null +++ b/scripts/model_configs/4-lonely.yaml @@ -0,0 +1,46 @@ +# pytorch_lightning==2.2.4 +seed_everything: 21616 +trainer: + accelerator: auto + strategy: auto + devices: auto + num_nodes: 1 + precision: null + logger: + class_path: pytorch_lightning.loggers.WandbLogger + init_args: + project: "equitriton-qm9" + entity: "laserkelvin" + log_model: true + #callbacks: + # - class_path: pytorch_lightning.callbacks.EarlyStopping + # init_args: + # monitor: "val_loss_epoch" + # patience: 5 + # mode: "min" + max_epochs: 100 + min_epochs: 15 +model: + model_class: equitriton.model.EquiTritonModel + model_kwargs: + initial_atom_dim: 64 + num_layers: 3 + output_dim: 1 + l_values: [0, 1, 2, 4] # is a higher order odd/even pair + edge_dim: 20 + hidden_dim: 32 + radius_cutoff: 6.0 + degree_norm: 6.08275253 # sqrt(37), avg degree + sph_harm_kwargs: + use_e3nn: false + e_mean: -76.1160 + e_std: 10.3238 + lr: 0.001 + weight_decay: 0.0 + atom_weighted_loss: false +data: + root_path: ./qm9_data + batch_size: 32 + train_frac: 0.8 + val_frac: 0.1 + num_workers: 4 diff --git a/scripts/model_configs/6-lonely.yaml b/scripts/model_configs/6-lonely.yaml new file mode 100644 index 0000000..8eecae8 --- /dev/null +++ b/scripts/model_configs/6-lonely.yaml @@ -0,0 +1,46 @@ +# pytorch_lightning==2.2.4 +seed_everything: 21616 +trainer: + accelerator: auto + strategy: auto + devices: auto + num_nodes: 1 + precision: null + logger: + class_path: pytorch_lightning.loggers.WandbLogger + init_args: + project: "equitriton-qm9" + entity: "laserkelvin" + log_model: true + #callbacks: + # - class_path: pytorch_lightning.callbacks.EarlyStopping + # init_args: + # monitor: "val_loss_epoch" + # patience: 5 + # mode: "min" + max_epochs: 100 + min_epochs: 15 +model: + model_class: equitriton.model.EquiTritonModel + model_kwargs: + initial_atom_dim: 64 + num_layers: 3 + output_dim: 1 + l_values: [0, 1, 2, 6] # is a higher order odd/even pair + edge_dim: 20 + hidden_dim: 32 + radius_cutoff: 6.0 + degree_norm: 6.08275253 # sqrt(37), avg degree + sph_harm_kwargs: + use_e3nn: false + e_mean: -76.1160 + e_std: 10.3238 + lr: 0.001 + weight_decay: 0.0 + atom_weighted_loss: false +data: + root_path: ./qm9_data + batch_size: 32 + train_frac: 0.8 + val_frac: 0.1 + num_workers: 4 diff --git a/scripts/model_configs/8-lonely.yaml b/scripts/model_configs/8-lonely.yaml new file mode 100644 index 0000000..00338c3 --- /dev/null +++ b/scripts/model_configs/8-lonely.yaml @@ -0,0 +1,46 @@ +# pytorch_lightning==2.2.4 +seed_everything: 21616 +trainer: + accelerator: auto + strategy: auto + devices: auto + num_nodes: 1 + precision: null + logger: + class_path: pytorch_lightning.loggers.WandbLogger + init_args: + project: "equitriton-qm9" + entity: "laserkelvin" + log_model: true + #callbacks: + # - class_path: pytorch_lightning.callbacks.EarlyStopping + # init_args: + # monitor: "val_loss_epoch" + # patience: 5 + # mode: "min" + max_epochs: 100 + min_epochs: 15 +model: + model_class: equitriton.model.EquiTritonModel + model_kwargs: + initial_atom_dim: 64 + num_layers: 3 + output_dim: 1 + l_values: [0, 1, 2, 8] # just including 8 + edge_dim: 20 + hidden_dim: 32 + radius_cutoff: 6.0 + degree_norm: 6.08275253 # sqrt(37), avg degree + sph_harm_kwargs: + use_e3nn: false + e_mean: -76.1160 + e_std: 10.3238 + lr: 0.001 + weight_decay: 0.0 + atom_weighted_loss: false +data: + root_path: ./qm9_data + batch_size: 32 + train_frac: 0.8 + val_frac: 0.1 + num_workers: 4 diff --git a/scripts/model_configs/baseline.yaml b/scripts/model_configs/baseline.yaml.ignore similarity index 79% rename from scripts/model_configs/baseline.yaml rename to scripts/model_configs/baseline.yaml.ignore index d64fe14..851dc8c 100644 --- a/scripts/model_configs/baseline.yaml +++ b/scripts/model_configs/baseline.yaml.ignore @@ -12,13 +12,14 @@ trainer: project: "equitriton-qm9" entity: "laserkelvin" log_model: true - callbacks: - - class_path: pytorch_lightning.callbacks.EarlyStopping - init_args: - monitor: "val_loss_epoch" - patience: 5 - mode: "min" - max_epochs: 300 + tags: ["baseline"] + #callbacks: + # - class_path: pytorch_lightning.callbacks.EarlyStopping + # init_args: + # monitor: "val_loss_epoch" + # patience: 5 + # mode: "min" + max_epochs: 100 min_epochs: 15 model: model_class: equitriton.model.EquiTritonModel diff --git a/scripts/model_configs/baseline_big.yaml b/scripts/model_configs/baseline_big.yaml index 284e3fa..2dad118 100644 --- a/scripts/model_configs/baseline_big.yaml +++ b/scripts/model_configs/baseline_big.yaml @@ -12,13 +12,14 @@ trainer: project: "equitriton-qm9" entity: "laserkelvin" log_model: true - callbacks: - - class_path: pytorch_lightning.callbacks.EarlyStopping - init_args: - monitor: "val_loss_epoch" - patience: 5 - mode: "min" - max_epochs: 300 + tags: ["baseline"] + #callbacks: + # - class_path: pytorch_lightning.callbacks.EarlyStopping + # init_args: + # monitor: "val_loss_epoch" + # patience: 5 + # mode: "min" + max_epochs: 100 min_epochs: 15 model: model_class: equitriton.model.EquiTritonModel @@ -28,7 +29,7 @@ model: output_dim: 1 l_values: [0,] edge_dim: 20 - hidden_dim: 512 + hidden_dim: 128 radius_cutoff: 6.0 degree_norm: 6.08275253 # sqrt(37), avg degree sph_harm_kwargs: diff --git a/scripts/model_configs/e3nn.yaml b/scripts/model_configs/e3nn.yaml new file mode 100644 index 0000000..6784b53 --- /dev/null +++ b/scripts/model_configs/e3nn.yaml @@ -0,0 +1,47 @@ +# pytorch_lightning==2.2.4 +seed_everything: 21616 +trainer: + accelerator: auto + strategy: auto + devices: auto + num_nodes: 1 + precision: null + logger: + class_path: pytorch_lightning.loggers.WandbLogger + init_args: + project: "equitriton-qm9" + entity: "laserkelvin" + log_model: true + tags: ["baseline", "e3nn"] + #callbacks: + # - class_path: pytorch_lightning.callbacks.EarlyStopping + # init_args: + # monitor: "val_loss_epoch" + # patience: 5 + # mode: "min" + max_epochs: 100 + min_epochs: 15 +model: + model_class: equitriton.model.EquiTritonModel + model_kwargs: + initial_atom_dim: 64 + num_layers: 3 + output_dim: 1 + l_values: [0,1,2] + edge_dim: 20 + hidden_dim: 32 + radius_cutoff: 6.0 + degree_norm: 6.08275253 # sqrt(37), avg degree + sph_harm_kwargs: + use_e3nn: true + e_mean: -76.1160 + e_std: 10.3238 + lr: 0.001 + weight_decay: 0.0 + atom_weighted_loss: false +data: + root_path: ./qm9_data + batch_size: 32 + train_frac: 0.8 + val_frac: 0.1 + num_workers: 4 diff --git a/scripts/model_configs/equivariant.yaml b/scripts/model_configs/equivariant.yaml index 7dd9a43..513f795 100644 --- a/scripts/model_configs/equivariant.yaml +++ b/scripts/model_configs/equivariant.yaml @@ -12,13 +12,13 @@ trainer: project: "equitriton-qm9" entity: "laserkelvin" log_model: true - callbacks: - - class_path: pytorch_lightning.callbacks.EarlyStopping - init_args: - monitor: "val_loss_epoch" - patience: 5 - mode: "min" - max_epochs: 300 + #callbacks: + # - class_path: pytorch_lightning.callbacks.EarlyStopping + # init_args: + # monitor: "val_loss_epoch" + # patience: 5 + # mode: "min" + max_epochs: 100 min_epochs: 15 model: model_class: equitriton.model.EquiTritonModel diff --git a/scripts/model_configs/even.yaml b/scripts/model_configs/even.yaml index 86b968f..2d155b1 100644 --- a/scripts/model_configs/even.yaml +++ b/scripts/model_configs/even.yaml @@ -12,13 +12,13 @@ trainer: project: "equitriton-qm9" entity: "laserkelvin" log_model: true - callbacks: - - class_path: pytorch_lightning.callbacks.EarlyStopping - init_args: - monitor: "val_loss_epoch" - patience: 5 - mode: "min" - max_epochs: 300 + #callbacks: + # - class_path: pytorch_lightning.callbacks.EarlyStopping + # init_args: + # monitor: "val_loss_epoch" + # patience: 5 + # mode: "min" + max_epochs: 100 min_epochs: 15 model: model_class: equitriton.model.EquiTritonModel @@ -40,7 +40,7 @@ model: atom_weighted_loss: false data: root_path: ./qm9_data - batch_size: 32 + batch_size: 16 train_frac: 0.8 val_frac: 0.1 num_workers: 4 diff --git a/scripts/model_configs/full.yaml b/scripts/model_configs/full.yaml index 981f484..3ccef52 100644 --- a/scripts/model_configs/full.yaml +++ b/scripts/model_configs/full.yaml @@ -12,13 +12,13 @@ trainer: project: "equitriton-qm9" entity: "laserkelvin" log_model: true - callbacks: - - class_path: pytorch_lightning.callbacks.EarlyStopping - init_args: - monitor: "val_loss_epoch" - patience: 5 - mode: "min" - max_epochs: 300 + #callbacks: + # - class_path: pytorch_lightning.callbacks.EarlyStopping + # init_args: + # monitor: "val_loss_epoch" + # patience: 5 + # mode: "min" + max_epochs: 100 min_epochs: 15 model: model_class: equitriton.model.EquiTritonModel @@ -28,7 +28,7 @@ model: output_dim: 1 l_values: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # this up to l=10 edge_dim: 20 - hidden_dim: 32 + hidden_dim: 16 radius_cutoff: 6.0 degree_norm: 6.08275253 # sqrt(37), avg degree sph_harm_kwargs: @@ -40,7 +40,7 @@ model: atom_weighted_loss: false data: root_path: ./qm9_data - batch_size: 32 + batch_size: 16 train_frac: 0.8 val_frac: 0.1 num_workers: 4 diff --git a/scripts/model_configs/micro-full.yaml b/scripts/model_configs/micro-full.yaml new file mode 100644 index 0000000..c837821 --- /dev/null +++ b/scripts/model_configs/micro-full.yaml @@ -0,0 +1,45 @@ +# pytorch_lightning==2.2.4 +seed_everything: 21616 +trainer: + accelerator: auto + strategy: auto + devices: auto + num_nodes: 1 + precision: null + logger: + class_path: pytorch_lightning.loggers.WandbLogger + init_args: + project: "equitriton-qm9" + entity: "laserkelvin" + log_model: "all" + #callbacks: + # - class_path: pytorch_lightning.callbacks.EarlyStopping + # init_args: + # monitor: "val_loss_epoch" + # patience: 5 + # mode: "min" + max_epochs: 30 +model: + model_class: equitriton.model.EquiTritonModel + model_kwargs: + initial_atom_dim: 64 + num_layers: 3 + output_dim: 1 + l_values: [0, 1, 2, 3, 4, 5, 6, 7, 8] # not quite up to ten + edge_dim: 20 + hidden_dim: 16 + radius_cutoff: 6.0 + degree_norm: 6.08275253 # sqrt(37), avg degree + sph_harm_kwargs: + use_e3nn: false + e_mean: -76.1160 + e_std: 10.3238 + lr: 0.001 + weight_decay: 0.0 + atom_weighted_loss: false +data: + root_path: ./qm9_data + batch_size: 32 + train_frac: 0.8 + val_frac: 0.1 + num_workers: 4 diff --git a/scripts/model_configs/mini-full.yaml b/scripts/model_configs/mini-full.yaml new file mode 100644 index 0000000..95d0665 --- /dev/null +++ b/scripts/model_configs/mini-full.yaml @@ -0,0 +1,45 @@ +# pytorch_lightning==2.2.4 +seed_everything: 21616 +trainer: + accelerator: auto + strategy: auto + devices: auto + num_nodes: 1 + precision: null + logger: + class_path: pytorch_lightning.loggers.WandbLogger + init_args: + project: "equitriton-qm9" + entity: "laserkelvin" + log_model: "all" + #callbacks: + # - class_path: pytorch_lightning.callbacks.EarlyStopping + # init_args: + # monitor: "val_loss_epoch" + # patience: 5 + # mode: "min" + max_epochs: 30 +model: + model_class: equitriton.model.EquiTritonModel + model_kwargs: + initial_atom_dim: 64 + num_layers: 3 + output_dim: 1 + l_values: [0, 1, 2, 3, 4, 5, 6] # not quite up to ten + edge_dim: 20 + hidden_dim: 16 + radius_cutoff: 6.0 + degree_norm: 6.08275253 # sqrt(37), avg degree + sph_harm_kwargs: + use_e3nn: false + e_mean: -76.1160 + e_std: 10.3238 + lr: 0.001 + weight_decay: 0.0 + atom_weighted_loss: false +data: + root_path: ./qm9_data + batch_size: 32 + train_frac: 0.8 + val_frac: 0.1 + num_workers: 4 diff --git a/scripts/model_configs/mini-long.yaml b/scripts/model_configs/mini-long.yaml new file mode 100644 index 0000000..fe944b0 --- /dev/null +++ b/scripts/model_configs/mini-long.yaml @@ -0,0 +1,39 @@ +# pytorch_lightning==2.2.4 +seed_everything: 21616 +trainer: + accelerator: auto + strategy: auto + devices: auto + num_nodes: 1 + precision: null + logger: + class_path: pytorch_lightning.loggers.WandbLogger + init_args: + project: "equitriton-qm9" + entity: "laserkelvin" + log_model: "all" + max_epochs: 100 +model: + model_class: equitriton.model.EquiTritonModel + model_kwargs: + initial_atom_dim: 64 + num_layers: 3 + output_dim: 1 + l_values: [0, 1, 2, 3, 4, 5, 6] # not quite up to ten + edge_dim: 20 + hidden_dim: 16 + radius_cutoff: 6.0 + degree_norm: 6.08275253 # sqrt(37), avg degree + sph_harm_kwargs: + use_e3nn: false + e_mean: -76.1160 + e_std: 10.3238 + lr: 0.001 + weight_decay: 0.0 + atom_weighted_loss: false +data: + root_path: ./qm9_data + batch_size: 32 + train_frac: 0.8 + val_frac: 0.1 + num_workers: 4 diff --git a/scripts/model_configs/nano-full.yaml b/scripts/model_configs/nano-full.yaml new file mode 100644 index 0000000..c1cd5f9 --- /dev/null +++ b/scripts/model_configs/nano-full.yaml @@ -0,0 +1,45 @@ +# pytorch_lightning==2.2.4 +seed_everything: 21616 +trainer: + accelerator: auto + strategy: auto + devices: auto + num_nodes: 1 + precision: null + logger: + class_path: pytorch_lightning.loggers.WandbLogger + init_args: + project: "equitriton-qm9" + entity: "laserkelvin" + log_model: "all" + #callbacks: + # - class_path: pytorch_lightning.callbacks.EarlyStopping + # init_args: + # monitor: "val_loss_epoch" + # patience: 5 + # mode: "min" + max_epochs: 30 +model: + model_class: equitriton.model.EquiTritonModel + model_kwargs: + initial_atom_dim: 64 + num_layers: 3 + output_dim: 1 + l_values: [0, 1, 2, 3, 4] # not quite up to ten + edge_dim: 20 + hidden_dim: 16 + radius_cutoff: 6.0 + degree_norm: 6.08275253 # sqrt(37), avg degree + sph_harm_kwargs: + use_e3nn: false + e_mean: -76.1160 + e_std: 10.3238 + lr: 0.001 + weight_decay: 0.0 + atom_weighted_loss: false +data: + root_path: ./qm9_data + batch_size: 32 + train_frac: 0.8 + val_frac: 0.1 + num_workers: 4 diff --git a/scripts/model_configs/nano-long.yaml b/scripts/model_configs/nano-long.yaml new file mode 100644 index 0000000..de855b5 --- /dev/null +++ b/scripts/model_configs/nano-long.yaml @@ -0,0 +1,45 @@ +# pytorch_lightning==2.2.4 +seed_everything: 21616 +trainer: + accelerator: auto + strategy: auto + devices: auto + num_nodes: 1 + precision: null + logger: + class_path: pytorch_lightning.loggers.WandbLogger + init_args: + project: "equitriton-qm9" + entity: "laserkelvin" + log_model: "all" + #callbacks: + # - class_path: pytorch_lightning.callbacks.EarlyStopping + # init_args: + # monitor: "val_loss_epoch" + # patience: 5 + # mode: "min" + max_epochs: 100 +model: + model_class: equitriton.model.EquiTritonModel + model_kwargs: + initial_atom_dim: 64 + num_layers: 3 + output_dim: 1 + l_values: [0, 1, 2, 3, 4] # not quite up to ten + edge_dim: 20 + hidden_dim: 16 + radius_cutoff: 6.0 + degree_norm: 6.08275253 # sqrt(37), avg degree + sph_harm_kwargs: + use_e3nn: false + e_mean: -76.1160 + e_std: 10.3238 + lr: 0.001 + weight_decay: 0.0 + atom_weighted_loss: false +data: + root_path: ./qm9_data + batch_size: 32 + train_frac: 0.8 + val_frac: 0.1 + num_workers: 4 diff --git a/scripts/model_configs/skipped.yaml b/scripts/model_configs/skipped.yaml index 076a1a5..13efb81 100644 --- a/scripts/model_configs/skipped.yaml +++ b/scripts/model_configs/skipped.yaml @@ -12,13 +12,13 @@ trainer: project: "equitriton-qm9" entity: "laserkelvin" log_model: true - callbacks: - - class_path: pytorch_lightning.callbacks.EarlyStopping - init_args: - monitor: "val_loss_epoch" - patience: 5 - mode: "min" - max_epochs: 300 + #callbacks: + # - class_path: pytorch_lightning.callbacks.EarlyStopping + # init_args: + # monitor: "val_loss_epoch" + # patience: 5 + # mode: "min" + max_epochs: 100 min_epochs: 15 model: model_class: equitriton.model.EquiTritonModel diff --git a/scripts/model_configs/unconventional.yaml b/scripts/model_configs/unconventional.yaml new file mode 100644 index 0000000..bfea9da --- /dev/null +++ b/scripts/model_configs/unconventional.yaml @@ -0,0 +1,45 @@ +# pytorch_lightning==2.2.4 +seed_everything: 21616 +trainer: + accelerator: auto + strategy: auto + devices: auto + num_nodes: 1 + precision: null + logger: + class_path: pytorch_lightning.loggers.WandbLogger + init_args: + project: "equitriton-qm9" + entity: "laserkelvin" + log_model: "all" + #callbacks: + # - class_path: pytorch_lightning.callbacks.EarlyStopping + # init_args: + # monitor: "val_loss_epoch" + # patience: 5 + # mode: "min" + max_epochs: 30 +model: + model_class: equitriton.model.EquiTritonModel + model_kwargs: + initial_atom_dim: 64 + num_layers: 3 + output_dim: 1 + l_values: [0, 3, 4, 5, 6] # not quite up to ten + edge_dim: 20 + hidden_dim: 16 + radius_cutoff: 6.0 + degree_norm: 6.08275253 # sqrt(37), avg degree + sph_harm_kwargs: + use_e3nn: false + e_mean: -76.1160 + e_std: 10.3238 + lr: 0.001 + weight_decay: 0.0 + atom_weighted_loss: false +data: + root_path: ./qm9_data + batch_size: 32 + train_frac: 0.8 + val_frac: 0.1 + num_workers: 4 diff --git a/scripts/model_configs/unconventional_long.yaml b/scripts/model_configs/unconventional_long.yaml new file mode 100644 index 0000000..28ce5a1 --- /dev/null +++ b/scripts/model_configs/unconventional_long.yaml @@ -0,0 +1,45 @@ +# pytorch_lightning==2.2.4 +seed_everything: 21616 +trainer: + accelerator: auto + strategy: auto + devices: auto + num_nodes: 1 + precision: null + logger: + class_path: pytorch_lightning.loggers.WandbLogger + init_args: + project: "equitriton-qm9" + entity: "laserkelvin" + log_model: "all" + #callbacks: + # - class_path: pytorch_lightning.callbacks.EarlyStopping + # init_args: + # monitor: "val_loss_epoch" + # patience: 5 + # mode: "min" + max_epochs: 100 +model: + model_class: equitriton.model.EquiTritonModel + model_kwargs: + initial_atom_dim: 64 + num_layers: 3 + output_dim: 1 + l_values: [0, 3, 4, 5, 6] # not quite up to ten + edge_dim: 20 + hidden_dim: 16 + radius_cutoff: 6.0 + degree_norm: 6.08275253 # sqrt(37), avg degree + sph_harm_kwargs: + use_e3nn: false + e_mean: -76.1160 + e_std: 10.3238 + lr: 0.001 + weight_decay: 0.0 + atom_weighted_loss: false +data: + root_path: ./qm9_data + batch_size: 32 + train_frac: 0.8 + val_frac: 0.1 + num_workers: 4 From 3de00b2dda1e4209dae1abf1a1cff0a8e82617b3 Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Tue, 8 Oct 2024 13:40:59 -0700 Subject: [PATCH 108/116] git: ignoring artifact download from wandb Signed-off-by: Kin Long Kelvin Lee --- scripts/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/.gitignore b/scripts/.gitignore index c226bbf..22b1587 100644 --- a/scripts/.gitignore +++ b/scripts/.gitignore @@ -1,3 +1,4 @@ qm9_data/ wandb/ lightning_logs/ +artifacts/ From f29b1905b811cd397727afaa2b61c841828e2c4c Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Tue, 8 Oct 2024 13:41:32 -0700 Subject: [PATCH 109/116] script: committing phate script used for experiments Signed-off-by: Kin Long Kelvin Lee --- scripts/generate_phate_embeddings.py | 82 ++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 6 deletions(-) diff --git a/scripts/generate_phate_embeddings.py b/scripts/generate_phate_embeddings.py index 4cadc4e..e4fadbb 100644 --- a/scripts/generate_phate_embeddings.py +++ b/scripts/generate_phate_embeddings.py @@ -9,8 +9,11 @@ import numpy as np from tqdm import tqdm from phate import PHATE +from e3nn import o3 +import pytorch_lightning as pl from equitriton.model.lightning import EquiTritonLitModule, LightningQM9 +from equitriton.utils import separate_embedding_irreps def graph_to_rdkit(batched_graph): @@ -55,14 +58,26 @@ def calculate_scores_for_batch(molecules) -> list[dict[str, int]]: return scores -def run_phate_projection(results: list[dict], **phate_kwargs) -> np.ndarray: +def run_phate_projection( + results: list[dict], irreps: o3.Irreps, **phate_kwargs +) -> dict[str, np.ndarray]: phate_kwargs.setdefault("knn", 10) phate_kwargs.setdefault("random_state", 21516) - # collect up all the embeddings embeddings = torch.vstack([r["embeddings"][1] for r in results]).numpy() + # separate embeddings into individual chunks + chunk_dict = separate_embedding_irreps(embeddings, irreps, return_numpy=True) + embeddings_dict = {} + for order, chunk in chunk_dict.items(): + print(f"Running PHATE on order {order}") + # collect up all the embeddings + phate = PHATE(**phate_kwargs) + phate_embeddings = phate.fit_transform(chunk) + embeddings_dict[order] = phate_embeddings + # run once more on the full embedding set phate = PHATE(**phate_kwargs) phate_embeddings = phate.fit_transform(embeddings) - return phate_embeddings + embeddings_dict["full"] = phate_embeddings + return embeddings_dict def main(): @@ -70,6 +85,7 @@ def main(): parser.add_argument( "artifact_path", type=str, help="wandb path to a model artifact." ) + pl.seed_everything(215162) args = parser.parse_args() @@ -85,27 +101,81 @@ def main(): ckpt_path = Path(artifact_dir).joinpath("model.ckpt") datamodule = LightningQM9("./qm9_data", num_workers=0) - model = EquiTritonLitModule.load_from_checkpoint(str(ckpt_path)) + model = EquiTritonLitModule.load_from_checkpoint(str(ckpt_path)).eval() datamodule.setup("test") test_loader = datamodule.test_dataloader() results = [] + all_smi = [] + all_error = [] + score_dict = {} for index, batch in tqdm( - enumerate(test_loader), desc="Batches to process", leave=False, position=1 + enumerate(test_loader), + desc="Batches to process", + leave=False, + position=1, + total=len(test_loader), ): embeddings = model.model.embed(batch.to("cuda")) + with torch.no_grad(): + g_z, z = model.model(batch) + pred_energies = model.output_head(g_z) + # un-normalize energy + pred_energies = (model.hparams["e_std"] * pred_energies) + model.hparams[ + "e_mean" + ] + # retrieve targets + target_energies = batch.y[:, 12].unsqueeze(-1) + error = (pred_energies - target_energies).pow(2.0).cpu().tolist() mols = graph_to_rdkit(batch) scores = calculate_scores_for_batch(mols) package = { "embeddings": embeddings["graph_z"], "scores": scores, "smi": batch.smiles, + "error": error, } + all_smi.extend(batch.smiles) + all_error.extend(error) + # reformat scores into a flat dictionary + for score in scores: + for key, value in score.items(): + if key not in score_dict: + score_dict[key] = [] + score_dict[key].append(value) results.append(package) - phate_embeddings = run_phate_projection(results) + print("Running PHATE on each Irreps") + phate_embeddings = run_phate_projection( + results, model.model.initial_layer.output_irreps + ) to_save = {"phate": phate_embeddings, "data": results} + # save a local version of the results torch.save(to_save, Path(artifact_dir).joinpath("results.pt")) + # formatting stuff to log to wandb + embedding_table = wandb.Table( + columns=["F1", "F2"], data=phate_embeddings["full"].tolist() + ) + for key, array in phate_embeddings.items(): + if key != "full": + for axis in [0, 1]: + embedding_table.add_column(name=f"O{key}_{axis}", data=array[:, axis]) + # this initializes the table + joint_table = wandb.Table(columns=["smiles"]) + # i'm not sure why, but the table kept fussing about not being + # to add the list of smiles directly, which is why it's written as a loop + for smi in all_smi: + joint_table.add_data(smi) + joint_table.add_column(name="squared_error_eV", data=all_error) + # now add the descriptors as well + for key, value in score_dict.items(): + joint_table.add_column(name=key, data=value) + # package stuff up and log to wandb + inference_artifact = wandb.Artifact("qm9_inference", type="eval") + inference_artifact.add(embedding_table, "phate") + inference_artifact.add(joint_table, "descriptors") + inference_run.log_artifact(inference_artifact) + wandb.finish() if __name__ == "__main__": From 647bf3f2eef0e61bfcd69fc20131432494da043f Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Tue, 8 Oct 2024 14:22:43 -0700 Subject: [PATCH 110/116] docs: added docstrings for phate embedding script Signed-off-by: Kin Long Kelvin Lee --- scripts/generate_phate_embeddings.py | 59 ++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/scripts/generate_phate_embeddings.py b/scripts/generate_phate_embeddings.py index e4fadbb..a689372 100644 --- a/scripts/generate_phate_embeddings.py +++ b/scripts/generate_phate_embeddings.py @@ -11,17 +11,42 @@ from phate import PHATE from e3nn import o3 import pytorch_lightning as pl +from torch_geometric.data import Data from equitriton.model.lightning import EquiTritonLitModule, LightningQM9 from equitriton.utils import separate_embedding_irreps -def graph_to_rdkit(batched_graph): +def graph_to_rdkit(batched_graph: Data) -> Chem.Mol: + """ + Simple list comprehension to unpack a batch into SMILES, then ``Mol`` + objects. + + Parameters + ---------- + batched_graph : Data + Batched graph structure from QM9 that contains multiple + individual molecules. Individual SMILES are extracted from + the ``smiles`` attribute. + """ mols = [Chem.MolFromSmiles(smi, sanitize=False) for smi in batched_graph.smiles] return mols -def score_molecule(molecule) -> dict[str, int]: +def score_molecule(molecule: Chem.Mol) -> dict[str, int]: + """ + Given an RDKit molecule, compute the NSPS related metrics. + + Parameters + ---------- + molecule : Chem.Mol + Molecule representation in RDKit. + + Returns + ------- + dict[str, int] + Dictionary mapping of property and value. + """ enum = {"SP": 1, "SP2": 2, "SP3": 3} scores = {"stereo": 0, "hybrid": 0, "aromatic": 0, "heavy_atoms": 0} for atom in tqdm( @@ -61,6 +86,34 @@ def calculate_scores_for_batch(molecules) -> list[dict[str, int]]: def run_phate_projection( results: list[dict], irreps: o3.Irreps, **phate_kwargs ) -> dict[str, np.ndarray]: + """ + Wrapper function that applies the PHATE method to individual + irreducible representations, given a set of ``o3.Irreps``. + + We apply the same PHATE hyperparameters to each decomposed + irreducible representation, followed by the same method + applied to the joint embeddings (i.e. a vector comprising + all representations). + + Parameters + ---------- + results : list[dict] + List of dictionaries containing the results from inference, + which contains graph embeddings under the ``embeddings`` key. + irreps : o3.Irreps + Object corresponding to the group of irreducible representations + contained in the set of embeddings. This is passed into the + ``separate_embedding_irreps`` function that splits them into + a order/vector mapping. + + Returns + ------- + dict[str, np.ndarray] + Dictionary mapping of keys (irrep. order) and PHATE projections. + Each order key is represented by the number, with the exception + of ``full`` which contains PHATE projections for the joint graph + embeddings. + """ phate_kwargs.setdefault("knn", 10) phate_kwargs.setdefault("random_state", 21516) embeddings = torch.vstack([r["embeddings"][1] for r in results]).numpy() @@ -110,7 +163,7 @@ def main(): all_smi = [] all_error = [] score_dict = {} - for index, batch in tqdm( + for _, batch in tqdm( enumerate(test_loader), desc="Batches to process", leave=False, From 481bdd99914564a2ac59de465b22f159a556beef Mon Sep 17 00:00:00 2001 From: Kin Long Kelvin Lee Date: Tue, 8 Oct 2024 14:23:58 -0700 Subject: [PATCH 111/116] style: ran sort on generate phate imports --- scripts/generate_phate_embeddings.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/generate_phate_embeddings.py b/scripts/generate_phate_embeddings.py index a689372..8641c9c 100644 --- a/scripts/generate_phate_embeddings.py +++ b/scripts/generate_phate_embeddings.py @@ -1,17 +1,17 @@ from __future__ import annotations -from pathlib import Path from argparse import ArgumentParser +from pathlib import Path +import numpy as np +import pytorch_lightning as pl import torch -from rdkit import Chem import wandb -import numpy as np -from tqdm import tqdm -from phate import PHATE from e3nn import o3 -import pytorch_lightning as pl +from phate import PHATE +from rdkit import Chem from torch_geometric.data import Data +from tqdm import tqdm from equitriton.model.lightning import EquiTritonLitModule, LightningQM9 from equitriton.utils import separate_embedding_irreps From 377d7062b4e5e3e2befd85d99ff6a4a51a401e00 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Tue, 8 Oct 2024 14:30:47 -0700 Subject: [PATCH 112/116] docs: updated citation --- README.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1de1534..b7417e8 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,9 @@ contributions will be licensed under this license. Citation -------- -If you find this repo useful, please consider citing the corresponding paper: +If you find this repo useful, please consider citing the respective papers. + +For the original EquiTriton implementation, please use/read the following citation: ```bibtex @inproceedings{lee2024scaling, @@ -141,4 +143,16 @@ If you find this repo useful, please consider citing the corresponding paper: year={2024}, url={https://openreview.net/forum?id=ftK00FO5wq} } -``` \ No newline at end of file +``` + +For the refactored spherical harmonics up to $l=10$, and subsequent PHATE embedding analysis, see: + +```bibtex +@inproceedings{lee2024deconstructing, + title={Deconstructing equivariant representations in molecular systems}, + author={Kin Long Kelvin Lee and Mikhail Galkin and Santiago Miret}, + booktitle={AI for Accelerated Materials Design - NeurIPS 2024}, + year={2024}, + url={https://openreview.net/forum?id=pshyLoyzRn} +} +``` From 09acc2e17c665f8f189b26b0c5e7e21e0f590a79 Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Tue, 8 Oct 2024 15:13:51 -0700 Subject: [PATCH 113/116] docs: added note on new interface --- README.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/README.md b/README.md index b7417e8..af7e082 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,45 @@ nodes and masking in the forward pass. The script `scripts/dynamic_shapes.py` wi let you test the performance over a range of shapes; we encourage you to test it before performing full-scale training/inference. +## Decoupled spherical harmonics kernels + +We recently published a paper at the AI4Mat workshop at NeurIPS 2024, which as part +of that work, we went back into ``sympy`` to refactor the spherical harmonics up to $l=10$, +such that computations of a particular order are _independent_ from others. This allows +arbitrary orders to be freely composed without incurring a performance penalty, in +the case that one wishes to calculate $l=8$, but not $l=7$, for example. + +Functionally, these kernels are intended to behave in the same way as their original +implementation, i.e. they still provide equivariant properties when used to map +cartesian point clouds. However, because of the aggressive refactoring and heavy use +of hard-coded literals, they may (or will) differ numerically from even the initial _EquiTriton_ +kernels, particularly at higher orders. + +> [!IMPORTANT] +> For the above reason, while the kernels can be drop-in replacements, we do not recommend +> using them from already trained models, at least without some testing on the user's part, +> as the results may differ. We have also not yet attempted to use these kernels as part of +> simulation-based workflows (i.e. molecular dynamics), however our training experiments do +> show that training indeed does converge. + +To use the new set of decoupled kernels, the main `torch.autograd` binding is through +the `equitriton.sph_harm.direct.TritonSphericalHarmonic`: + +```python +import torch +from equitriton.sph_harm.direct import TritonSphericalHarmonic + +coords = torch.rand(100, 3) +sph_harm = TritonSphericalHarmonic.apply( + l_values=[0, 1, 2, 6, 10], + coords=coords +) +``` + +The improvements to performance are expected to come from (1) decoupling of each spherical +harmonic order, and (2) pre-allocation of an output tensor as to avoid using `torch.cat`, +which calculates each order followed by copying. + ### Development and usage on Intel XPU Development on Intel XPUs such as the Data Center GPU Max Series 1550 requires From db06712b531e33a685c74423998a63eb96ed14ea Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Tue, 8 Oct 2024 15:45:52 -0700 Subject: [PATCH 114/116] notebook: cleaned up notebook for pedagogy Signed-off-by: Kelvin Lee --- notebooks/Baseline model development.ipynb | 401 +++++++++------------ 1 file changed, 168 insertions(+), 233 deletions(-) diff --git a/notebooks/Baseline model development.ipynb b/notebooks/Baseline model development.ipynb index 3ee803b..3a7c1c4 100644 --- a/notebooks/Baseline model development.ipynb +++ b/notebooks/Baseline model development.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 27, + "execution_count": 1, "id": "fd832b52-7df1-4d42-a2ea-a7139df2196b", "metadata": {}, "outputs": [], @@ -28,9 +28,21 @@ "from equitriton.utils import spherical_harmonics_irreps" ] }, + { + "cell_type": "markdown", + "id": "1e68d7b9-c046-492c-8380-b801d6a0f209", + "metadata": {}, + "source": [ + "# Baseline model development\n", + "\n", + "This notebook was used to develop the simple graph convolution model used in the paper _Deconstructing equivariant molecular representations in molecular systems_ at the AI4Mat workshop at NeurIPS 2024.\n", + "\n", + "If you are looking to use this architecture in your own testing, please look at the `equitriton.model.blocks` module for the \"production ready\" version (i.e. please don't copy-paste this code unless you're looking to modify things!)." + ] + }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 2, "id": "a0307714-1036-4110-8b53-0c50062ee913", "metadata": {}, "outputs": [], @@ -40,17 +52,61 @@ }, { "cell_type": "code", - "execution_count": 108, + "execution_count": 3, "id": "79f96b13-929a-4b2e-a4d9-991194fd98ba", "metadata": {}, "outputs": [], "source": [ "class AtomEmbedding(nn.Module):\n", + " \"\"\"\n", + " A PyTorch module for embedding atomic numbers into dense vectors.\n", + "\n", + " Parameters\n", + " ----------\n", + " num_atoms : int\n", + " The number of distinct atomic types in the dataset.\n", + " atom_dim : int\n", + " The dimensionality of the embedded atomic vectors.\n", + "\n", + " Example\n", + " --------\n", + " >>> # Create an instance of the AtomEmbedding module\n", + " >>> atom_embedding = AtomEmbedding(num_atoms=10, atom_dim=128)\n", + "\n", + " >>> # Embed a batch of atomic numbers\n", + " >>> embedded_vectors = atom_embedding(torch.tensor([1, 2, 3, 4]))\n", + "\n", + " >>> print(embedded_vectors.shape) # Output: torch.Size([4, 128])\n", + " \"\"\"\n", + "\n", " def __init__(self, num_atoms: int, atom_dim: int):\n", + " \"\"\"\n", + " Initializes the AtomEmbedding module.\n", + "\n", + " Parameters\n", + " ----------\n", + " num_atoms : int\n", + " The number of distinct atomic types in the dataset.\n", + " atom_dim : int\n", + " The dimensionality of the embedded atomic vectors.\n", + " \"\"\"\n", " super().__init__()\n", " self.embedding = nn.Embedding(num_atoms, atom_dim, padding_idx=0)\n", "\n", " def forward(self, atomic_numbers: torch.LongTensor) -> torch.Tensor:\n", + " \"\"\"\n", + " Embeds a batch of atomic numbers into a tensor of embedded vectors.\n", + "\n", + " Parameters\n", + " ----------\n", + " atomic_numbers : torch.LongTensor\n", + " A tensor of shape (batch_size,) containing the atomic numbers to embed.\n", + "\n", + " Returns\n", + " -------\n", + " torch.Tensor\n", + " A tensor of shape (batch_size, atom_dim) containing the embedded atomic vectors.\n", + " \"\"\"\n", " return self.embedding(atomic_numbers)\n", "\n", "\n", @@ -294,35 +350,36 @@ " skip_connections: bool = True,\n", " ):\n", " \"\"\"\n", - " A neural network model designed for processing molecular graphs.\n", - "\n", - " This class implements a hierarchical architecture with multiple interaction blocks,\n", - " allowing for efficient and scalable processing of large molecular datasets.\n", - "\n", - " Parameters:\n", - " initial_atom_dim (int): The dimensionality of the atomic embeddings.\n", - " num_layers (int): The number of convolutional layers to use.\n", - " output_dim (int): The dimensionality of the final scalar features.\n", - " l_values (int): A list of spherical harmonics order to consider.\n", - " edge_dim (int): The dimensionality of the edge features.\n", - " hidden_dim (int): The dimensionality of the hidden state in each interaction block.\n", - " radius_cutoff (float): The cutoff distance for radial basis functions.\n", - " degree_norm (float): The normalization constant for edge features. Typically square root of the average degree.\n", - " edge_kwargs (dict[str, Any], optional): Keyword arguments to pass to the InteractionBlock. Defaults to {}.\n", - " sph_harm_kwargs (dict[str, Any], optional): Keyword arguments to pass to the InteractionBlock. Defaults to {}.\n", - " activation (Callable, optional): The activation function to use in each interaction block. Defaults to nn.functional.silu.\n", - " num_atoms (int, optional): The number of atoms in the embedding table (i.e. unique elements). Defaults to 100.\n", - " skip_connections (bool, optional): Whether to enable residual connections between layers. Defaults to True.\n", - "\n", - " Returns:\n", - " tuple[torch.Tensor, torch.Tensor]: A tuple containing the graph-level scalar features and the node features.\n", - "\n", - " Examples:\n", - " >>> model = EquiTritonModel(...)\n", - " >>> graph = PyGGraph(...).to(device=\"cuda\")\n", - " >>> graph_z, z = model(graph)\n", - "\n", - " Note: This class uses PyTorch Geometric's Graph data structure and assumes that the input graph has already been processed using a suitable preprocessing step.\n", + " End-to-end simple model that uses the EquiTriton kernels.\n", + "\n", + " Parameters\n", + " ----------\n", + " initial_atom_dim : int\n", + " The dimensionality of the atomic embeddings.\n", + " num_layers : int\n", + " The number of convolutional layers in the model.\n", + " output_dim : int\n", + " The dimensionality of the graph-level scalar features.\n", + " l_values : int\n", + " The maximum value of the L indices for spherical harmonics.\n", + " edge_dim : int\n", + " The dimensionality of the edge features.\n", + " hidden_dim : int\n", + " The hidden dimensionality of the interaction blocks.\n", + " radius_cutoff : float\n", + " The cutoff distance for the radial basis functions.\n", + " degree_norm : float\n", + " The normalization factor for the degree of each node.\n", + " edge_kwargs : dict, optional\n", + " Additional keyword arguments for the edge embedding layer. Defaults to {}.\n", + " sph_harm_kwargs : dict, optional\n", + " Additional keyword arguments for the spherical harmonics layer. Defaults to {}.\n", + " activation : Callable, optional\n", + " The activation function used in the model. Defaults to nn.functional.silu.\n", + " num_atoms : int, optional\n", + " The number of atoms in each graph. Defaults to 100.\n", + " skip_connections : bool, optional\n", + " Whether to use skip connections between layers. Defaults to True.\n", " \"\"\"\n", " sph_harm_kwargs.setdefault(\"use_e3nn\", False)\n", "\n", @@ -404,7 +461,7 @@ }, { "cell_type": "code", - "execution_count": 102, + "execution_count": 4, "id": "4e568b72-3990-4652-8a32-db375c65a81b", "metadata": {}, "outputs": [], @@ -415,8 +472,41 @@ " max_radius: float = 1.5,\n", " coord_scale: float = 1.0,\n", " max_atomic_number: int = 100,\n", - " device=\"cuda\",\n", + " device=\"xpu\",\n", "):\n", + " \"\"\"\n", + " Generate a fake graph with the specified number of nodes and edges.\n", + "\n", + " Parameters\n", + " ----------\n", + " num_nodes : int\n", + " The number of nodes in the graph.\n", + " num_edges : int\n", + " The number of edges in the graph.\n", + " max_radius : float, optional\n", + " The maximum radius for node connections. Defaults to 1.5.\n", + " coord_scale : float, optional\n", + " The scaling factor for node coordinates. Defaults to 1.0.\n", + " max_atomic_number : int, optional\n", + " The maximum atomic number for nodes. Defaults to 100.\n", + " device : str or torch.device, optional\n", + " The device to use for computations.\n", + "\n", + " Returns\n", + " -------\n", + " tuple[torch.Tensor, torch.Tensor, torch.Tensor]\n", + " A tuple containing:\n", + " - coords (torch.Tensor): Node coordinates with shape ``(num_nodes, 3)``.\n", + " - edge_index (torch.Tensor): Edge indices with shape ``(2, num_edges)``.\n", + " - atomic_numbers (torch.Tensor): Atomic numbers for nodes with shape ``(num_nodes,)``.\n", + "\n", + " Examples\n", + " --------\n", + " >>> coords, edge_index, atomic_numbers = make_fake_graph(10, 20)\n", + " >>> print(coords.shape) # (10, 3)\n", + " >>> print(edge_index.shape) # (2, 20)\n", + " >>> print(atomic_numbers.shape) # (10,)\n", + " \"\"\"\n", " coords = torch.rand(num_nodes, 3, device=device) * coord_scale\n", " edge_src, edge_dst = radius_graph(\n", " coords, max_radius, max_num_neighbors=num_nodes - 1\n", @@ -430,7 +520,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 5, "id": "8671f0e8-0da8-4250-9d02-fb30ecb977fe", "metadata": {}, "outputs": [], @@ -440,7 +530,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 6, "id": "f4971d48-3574-44d0-b2fb-27306bc3aeac", "metadata": {}, "outputs": [], @@ -453,7 +543,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 7, "id": "55f14f94-51ca-4e5c-ad63-ca830404eaf9", "metadata": {}, "outputs": [], @@ -463,7 +553,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 8, "id": "67f6e836-f4e0-48cf-981d-4419dda0159b", "metadata": {}, "outputs": [], @@ -474,7 +564,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 9, "id": "46778ea6-92f2-4d7a-9bd8-aa58013cd1c6", "metadata": {}, "outputs": [ @@ -484,7 +574,7 @@ "torch.Size([2, 240])" ] }, - "execution_count": 37, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -495,7 +585,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 10, "id": "0c82e80f-c4bd-43fe-8846-dab203757992", "metadata": {}, "outputs": [ @@ -526,7 +616,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 11, "id": "a0913f1e-b000-4a3b-b723-84a92d16425d", "metadata": {}, "outputs": [ @@ -687,6 +777,16 @@ ").to(\"cuda\")" ] }, + { + "cell_type": "markdown", + "id": "f2633995-0e41-4bf2-8093-5f68a92242ae", + "metadata": {}, + "source": [ + "### Rotation check\n", + "\n", + "This performs a random rotation to the coordinates, and we check the embeddings with and without the rotation." + ] + }, { "cell_type": "code", "execution_count": 45, @@ -717,9 +817,7 @@ "source": [ "### Rotation + translation\n", "\n", - "If all atoms are shifted by a vector, the Bessel embedding should also be the same as it works solely on interatom distances.\n", - "\n", - "The output shouldn't need to be shifted." + "If all atoms are shifted by a vector, the Bessel embedding should also be the same as it works solely on interatom distances, i.e. we do not need to shift the output embedding." ] }, { @@ -747,190 +845,6 @@ "assert torch.allclose(rot_before, rot_after, rtol=1e-7, atol=1e-4)" ] }, - { - "cell_type": "markdown", - "id": "061c7307-5b1c-4b31-b4b7-6bde7158c49e", - "metadata": {}, - "source": [ - "### Mike equivariance check\n", - "\n", - "Need to figure out what is different between these two..." - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "id": "8a38dc82", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", - " warnings.warn(\n", - "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", - " warnings.warn(\n", - "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", - " warnings.warn(\n", - "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", - " warnings.warn(\n", - "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", - " warnings.warn(\n", - "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", - " warnings.warn(\n", - "/home/kelvin/miniforge3/envs/equitriton/lib/python3.11/site-packages/torch/jit/_check.py:178: UserWarning: The TorchScript type system doesn't support instance-level annotations on empty non-base types in `__init__`. Instead, either 1) use a type annotation in the class body, or 2) wrap the type in `torch.jit.Attribute`.\n", - " warnings.warn(\n" - ] - }, - { - "data": { - "text/plain": [ - "(tensor([[ 1.2719, -0.0476, 0.3774, ..., -0.5458, 0.2401, -0.3716],\n", - " [ 1.2811, -0.6789, 2.2231, ..., 1.5065, 0.1164, 2.3330],\n", - " [ 0.8893, -0.5574, 1.1240, ..., -0.3095, 0.4741, 0.1339],\n", - " ...,\n", - " [ 1.4313, -0.4890, 1.0785, ..., -0.0667, 0.4573, -0.6801],\n", - " [ 0.5933, -0.7581, 1.4494, ..., -0.4565, 0.2007, -0.0230],\n", - " [ 1.0046, 0.8931, -0.0857, ..., -0.1521, 0.1383, -0.6058]],\n", - " device='cuda:0', grad_fn=),\n", - " tensor([[ 1.2719, -0.0476, 0.3774, ..., -0.4330, -0.5297, 0.1103],\n", - " [ 1.2811, -0.6789, 2.2231, ..., -0.3310, 0.6223, 0.1604],\n", - " [ 0.8893, -0.5574, 1.1240, ..., 1.1496, 0.9203, -0.6469],\n", - " ...,\n", - " [ 1.4313, -0.4890, 1.0785, ..., -0.3615, -0.6725, -0.3254],\n", - " [ 0.5933, -0.7581, 1.4494, ..., -0.3065, 0.0335, 0.0163],\n", - " [ 1.0046, 0.8931, -0.0857, ..., 0.0519, -0.4086, 0.0626]],\n", - " device='cuda:0', grad_fn=))" - ] - }, - "execution_count": 47, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# generate a random rotation matrix (bs, 3, 3)\n", - "from scipy.spatial.transform import Rotation\n", - "\n", - "random_rot = torch.tensor(\n", - " Rotation.random(coords.size(0)).as_matrix(), dtype=torch.float32\n", - ").to(coords.device)\n", - "\n", - "# random translation (bs, 3)\n", - "random_t = torch.rand_like(\n", - " coords\n", - ") # I think it should be a group action, not individual atoms\n", - "\n", - "# rotate coords: (bs, 3, 3) x (bs, 3, 1) -> (bs, 3)\n", - "rotated_coords = (random_rot @ coords.unsqueeze(-1)).squeeze(-1)\n", - "\n", - "# o_transl = layer(atom_z, rotated_coords + random_t, edge_index)\n", - "og_graph = PyGGraph(pos=coords, z=atomic_numbers, edge_index=edge_index).to(\"cuda\")\n", - "rot_graph = PyGGraph(\n", - " pos=coords @ rot_matrix.T.to(\"cuda\"), z=atomic_numbers, edge_index=edge_index\n", - ").to(\"cuda\")\n", - "\n", - "model = EquiTritonModel(32, 4, 4, [0, 1, 2], 32, 32, 1.0, 17**0.5).to(\"cuda\")\n", - "# o1 = model(og_graph)\n", - "# o2 = model(rot_graph)\n", - "atom_z = atom_embedder(atomic_numbers)\n", - "o1 = layer(atom_z, og_graph.pos, edge_index=og_graph.edge_index)\n", - "o2 = layer(atom_z, rot_graph.pos, edge_index=rot_graph.edge_index)\n", - "\n", - "o1, o2" - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "id": "4482c983-55e6-4419-bf3a-04705010adae", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(
,\n", - " array([,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ], dtype=object))" - ] - }, - "execution_count": 48, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAASAAAASnCAYAAABFDqVHAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzdeVhU1f8H8PfMsO+rrLIICLgLCOKuuJdbpqaVppW5lVZWLj+zLMW9THMrUyu3NC1NcUfcRcUNEBh2ZAfZZ4BZPr8/+HKTGGBYB5nz6vF54t57ljt3+HDuufecwyMiAsMwjArwVV0BhmHUFwtADMOoDAtADMOoDAtADMOoDAtADMOoDAtADMOoDAtADMOoDAtADMOoDAtADMOoDAtADPOSuXLlCng8Hq5cuaLqqjQaC0CM2tm3bx94PB7u3bun6qoAAEQiEb766qs2EVDqiwUghlExkUiEr7/+mgUghmGYxiopKVH6WBaAGAbAO++8AwMDA6SmpmL8+PEwMDCApaUlFi9eDJlMxh2XmJgIHo+HjRs34rvvvoOjoyN0dXUxcOBAhIeHV8lz0KBBGDRokMKynJycuPwsLS0BAF9//TV4PB54PB6++uqretX/2rVrmDRpEhwcHKCtrY327dvj448/hlgs5o7Zu3cveDweHjx4UC39mjVrIBAIkJqaym27c+cORo4cCWNjY+jp6WHgwIG4ceNGlXRfffUVeDweIiMjMW3aNJiamqJfv35K15sFIIb5H5lMhhEjRsDc3BwbN27EwIEDsWnTJuzevbvasb/++it++OEHzJ8/H0uXLkV4eDiGDBmCzMzMepVpaWmJHTt2AAAmTJiA3377Db/99htee+21euVz9OhRiEQizJ07F1u3bsWIESOwdetWTJ8+nTvm9ddfh66uLg4cOFAt/YEDBzBo0CDY2dkBAC5fvowBAwagsLAQK1euxJo1a5Cfn48hQ4YgNDS0WvpJkyZBJBJhzZo1eP/995WvODGMmtm7dy8BoLt373LbZsyYQQBo1apVVY7t2bMneXt7cz8nJCQQANLV1aVnz55x2+/cuUMA6OOPP+a2DRw4kAYOHFit/BkzZpCjoyP3c3Z2NgGglStXKlX/4OBgAkDBwcHcNpFIVO24wMBA4vF4lJSUxG2bOnUq2drakkwm47aFhYURANq7dy8REcnlcnJzc6MRI0aQXC6vUoazszMNGzaM27Zy5UoCQFOnTlWq7v/FWkAM84I5c+ZU+bl///6Ij4+vdtz48eO51gIA+Pr6ws/PD2fOnGn2Oiqiq6vL/X9JSQlycnLQp08fEFGVW67p06cjLS0NwcHB3LYDBw5AV1cXEydOBAA8fPgQQqEQ06ZNQ25uLnJycpCTk4OSkhIEBATg6tWrkMvlVcr/7+emLI0GpWKYNkhHR4frj6lkamqKvLy8ase6ublV29axY0f88ccfzVa/2iQnJ+PLL7/EyZMnq9W3oKCA+/9hw4bBxsYGBw4cQEBAAORyOQ4dOoRx48bB0NAQACAUCgEAM2bMqLG8goICmJqacj87Ozs3qN4sADHM/wgEgibNj8fjgRTMePxip3ZTkMlkGDZsGJ4/f44vvvgCHh4e0NfXR2pqKt55550qrRWBQIBp06bhp59+wvbt23Hjxg2kpaXhrbfe4o6pPH7Dhg3o0aOHwjINDAyq/PxiC6w+WABimAaobCW8KCYmhnu6BVS0nhTdviUlJVX5mcfjNaouT548QUxMDPbv31+l0/nChQsKj58+fTo2bdqEU6dOISgoCJaWlhgxYgS338XFBQBgZGSEoUOHNqpudWF9QAzTAH/99VeVR9ahoaG4c+cORo0axW1zcXFBVFQUsrOzuW2PHj2q9ihbT08PAJCfn9+gulS23F5sbRERtmzZovD4bt26oVu3bvj555/x559/4o033oCGxr9tEW9vb7i4uGDjxo0oLi6ulv7F82ks1gJimAZwdXVFv379MHfuXJSVleH777+Hubk5Pv/8c+6YWbNmYfPmzRgxYgTeffddZGVlYefOnejcuTMKCwu543R1ddGpUyccOXIEHTt2hJmZGbp06YIuXbooVRcPDw+4uLhg8eLFSE1NhZGREf7880+FfVeVpk+fjsWLFwNAldsvAODz+fj5558xatQodO7cGTNnzoSdnR1SU1MRHBwMIyMjnDp1qj4fV41YC4hhGmD69On48MMPsW3bNqxevRqdO3fG5cuXYWNjwx3j6emJX3/9FQUFBfjkk09w8uRJ/Pbbb/Dy8qqW388//ww7Ozt8/PHHmDp1Ko4dO6Z0XTQ1NXHq1Cn06NEDgYGB+Prrr+Hm5oZff/21xjRvvvkmBAIBOnbsCF9f32r7Bw0ahFu3bsHHxwfbtm3Dhx9+iH379sHa2hoff/yx0nWrC48U9ZIxDKNQYmIinJ2dsWHDBq4F8TLKycmBjY0NvvzyS6xYsUJl9WAtIIZRQ/v27YNMJsPbb7+t0nqwPiCGUSOXL19GZGQkVq9ejfHjx1d5aqcKLAAxjBpZtWoVbt68ib59+2Lr1q2qrg7rA2IYRnVYHxDDMCrDAhDDMCrDAhDDMCrDAhDDMCrDAhDDMCrDAhDDMCrDAhDDMCrDAlAbIZFIFE5+1dYQESQSiaqrwTQRFoDaiNWrV2PEiBHV5uptS4gIr776KlauXKnqqjBNhAWgNiA5ORnr1q2Dt7c3+Py2e0l5PB58fHywadMmhTMNMi8fNhSjDXjjjTcQEhKCmJgYbmLxtqqkpAQeHh7o1asXjh8/rurqMI3Udv9cqolr167hyJEjCAwMbPPBBwD09fWxbt06nDhxApcvX65X2h9//BFOTk7Q0dGBn5+fwgX2mJbFWkAvMZlMhl69ekEgEODOnTtt+vbrRUSEfv36oaioCGFhYVXmM67JkSNHMH36dOzcuRN+fn74/vvvcfToUURHR6Ndu3YtUGtGoQYtZ8i0Cj///DMBoJs3b6q6Ki3u7t27BIC2b9+u1PG+vr40f/587meZTEa2trYUGBjIbUtKSqKpU6eSiYkJmZqa0rRp0+j58+dNXnfmX+rxJ7MNKigowLJly/Dmm2/C399f1dVpcT4+Ppg5cyZWrFhR6+TrAFBeXo779+9XWWKGz+dj6NChuHXrFgAgNjYW3t7ecHV1xe3bt3HhwgXExsbis88+a9bzUHcsAL2kvv32WxQXF2Pt2rWqrorKrFmzBmVlZfj6669rPS4nJwcymQxWVlZVtltZWSEjIwMAMG/ePMybNw+rVq2Cu7s7vL298fnnn9e7n4mpHxaAXkJCoRBbtmzB0qVLYW9vr+rqqIy1tTX+7//+D9u2bUNkZGSD80lKSsKFCxewYcMGGBgYcP/eeustpfqXmIZjn+5L6NNPP4WtrS0+/fRTVVdF5RYtWoSffvoJH3/8Mc6ePatwlVELCwsIBAJkZmZW2Z6ZmQlra2s8evQIZmZmuHPnTrW0DV1ymFEOawG9ZM6dO4dTp05hw4YN7JcDgLa2NjZt2oTz58/j9OnTCo/R0tKCt7c3Ll26xG2Ty+W4dOkS/P39oampiaKiItja2sLV1bXKPzs7u5Y6FbXEHsO/RCQSCbp37w5LS0tcuXKl0WuKtxVEhKHDhiI5KRkRERHQ0tKqdsyRI0cwY8YM7Nq1C76+vvj+++/xxx9/ICoqCpqamujYsSMGDRqEFStWQF9fH7GxsTh79iy+//77lj8hdaLip3BMPWzZsoV4PB49ePBA1VVpNeRyOT3OeEyLf1tMfAGfNm7cWOOxW7duJQcHB9LS0iJfX1+6ffs2t+/OnTs0aNAgMjIyIkNDQ/Ly8qItW7a0xCmoNdYCeknk5OTAzc0NkydPxq5du1RdnVYhtTAVZ2PPIqUwBQBwadslPL7wGDExMdWeeDGtEwtAL4n58+fj999/h1AoVPs3d4vKinAp4RIeZjwEAGgJtNDPoR866nZEJ49OmDhxIn766SfVVpJRykvbCb1jxw5069YNRkZGMDIygr+/P4KCgrj9gYGB6NWrFwwNDdGuXTuMHz8e0dHRzVKX5h5j9OTJE+zcuRMrV65U6+AjlUtxPfk6toZu5YJPd6vuWOC7AAMcB8C6nTVWrVqFPXv2ICwsTOl8r169ijFjxsDW1hY8Hg9//fVX85wAU81LG4Ds7e2xdu1a3L9/H/fu3cOQIUMwbtw4REREAABCQkIwf/587q1WiUSC4cOHo6SkpMY8b9y4oXCyq8jIyGqPcCsdOXIEn3zyCVauXImwsDB0794dI0aMQFZWVpOcJxFh4cKFcHV1xYIFC5okz5cNESEqJwrb727HxfiLKJeVw87QDu95vYcJnhNgpG3EHTtnzhx06tQJCxcuVHqCtpKSEnTv3h0//vhjc50CUxMV9j81OVNTU/r5558V7svKyiIAFBISonC/TCaj7t270+uvv05SqZTbHhUVRVZWVrRu3TqF6ZQZY0TU8HFGx48fJwB0+vTpOo9tizKLM2n/w/20MnglrQxeSRtvbKSH6Q9JLpfXmObChQsEgI4cOVLv8gDQiRMnqm1/8uQJjRo1igwNDcnKyoo++eQTKisrq3f+TFVtIgBJpVI6dOgQaWlpUUREhMJjhEIhAaAnT57UmE9qaiq5uLjQtGnTSCaTUWxsLNna2tIHH3yg8PiysjISCATVvrDTp0+nsWPHVinbwsKCVqxYQVFRUXTv3j3y9fWld999t9bzEovF5OzsTKNGjar1uLaopLyETsecpq+Cv6KVwSvpm5Bv6GLcRSqTKvdLP3bsWHJwcKCSkpJ6lasoAIWFhZGhoSEtX76chEIhBQcHk42NDa1atapeeTPVvdQB6PHjx6Svr08CgYCMjY1rbCXIZDJ65ZVXqG/fvnXmmZSURA4ODjRlyhRycHCg6dOn1/jXNjU1VeFo9M8++4x8fX25n4cNG0ZffvlllWOOHTtGzs7OtdYlMDCQNDQ06OnTp3XWu62QyqR0O+U2rb22lmv1HAk/Qs9F9RuVLhQKSVNTk77++ut6pVMUgLy9vWnevHlVti1btqzKNWYa5qUeiuHu7o6HDx+ioKAAx44dw4wZMxASEoJOnTpVOW7+/PkIDw/H9evX68zTwcEBv/32GwYOHIgOHTpgz549jXrhr3Kc0fXr17Fp0yZuu0wmQ/v27WtMl5aWhm+//RYLFiyAh4dHg8t/mcQ9j8PZ2LPIFmUDAKz0rTDSdSScTZ3rnZerqys+/vhjrF27FjNnzqz1s65NVFQU7t+/j99//73Kdi0tLZSVlTUoT+ZfL3UA0tLSgqurKwDA29sbd+/exZYtW6q8J7NgwQL8888/uHr1qlIDNzMzMzF79myMGTMGd+/exccff4ytW7cqPLauMUYAGjzOaNmyZdDV1cWXX35ZZ51fds/Fz3Eu9hyicyueUupp6mGI8xB42XiBz2v4c5Lly5dj//79+OKLL3Dw4MEG5REREcG9Kf2iyMhIdO3atcF1Yyq81AHov+RyOfdXiYjw4Ycf4sSJE7hy5Qqcnev+K5qTk4OAgAB4enri6NGjiImJwaBBg6CtrY2NGzdWO/7FMUbjx4/n6nDp0iXuidWL44z09PSUOo/Q0FDs378fO3fuhKmpqZJn//Ipk5bhatJV3H52GzKSgc/jw9fOFwMdB0JXs/Hj3IyMjBAYGIhZs2Zh/vz56Nu3b73zMDQ0hEwmg0Qigba2NgAgISEBJ06cwMmTJxtdR7Wn6nvAhlqyZAmFhIRQQkICPX78mJYsWUI8Ho/Onz9PRERz584lY2NjunLlCqWnp3P/RCKRwvxkMhn5+PjQ6NGjqzzdePjwIZmZmdHmzZsVpjt8+DBpa2vTvn37KDIykmbPnk0mJiaUkZFBRES5ublkbm5OEydOpIcPH5JQKKSgoCBauHChwvzkcjn17t2bunXrVuVpXFsil8spLC2MNtzYwPXz/PboN8oqzmrysmQyGXl7e5O3tzfJZDKFxxQVFdGDBw/owYMHBIA2b95MDx48oKSkJMrPzyczMzNatGgRxcXF0aVLl8jT05PefvvtJq+rOnppA9CsWbPI0dGRtLS0yNLSkgICArjgQ1TRmajo3969e2vM8/z58yQWi6ttDwsLo5SUlBrT1TbGiKh+44x+//13AkBj14ylXFFuHZ/CyycpP4l23dvFBZ4fbv9A0TnRtT5Wb4xyaTltPbqVANAvv/yi8Jjg4GCF35UZM2YQEdHVq1fJy8uLdHR0qEOHDhQYGNhm/zi0NDYUoxUpLi5GB7cOKLEqgeU7ljDQMsCsnrOw0HchBAKBqqvXKAWlBbgYfxFPsp4AALQF2hjoNBB+dn4Q8Jv+3IgIkdmROB93HgVlBTj+7XFkPsmEUCiEkZFR3RkwLYIFoFZkxYoV2LBhA5YeXopjacdQVF4EALA3sseKASswwnWEimtYfxKZBDdSbuBG8g1I5BLwwIOXjReGOA+BvpZ+s5SZXpSOs7FnkVSQBAAw1jZGV52uGNt3LD766CO1nsa2tWEBqJVITEyEh4cHFi9ejG+//RYF4gJ8eeVLnBaehlQuBQ88eNt6Y93QdXAxc1F1detERIjIjsCFuAsoKCsAADgaO2Kk60jYGNo0S5kl5SW4lHAJD9IfgEDQ5Guir0Nf9G3fF5oCTaxatQqrV69GREQE9/S0LoGBgTh+/DiioqKgq6uLPn36YN26dXB3d2+Wc1A3LAC1EpMmTcLNmzcRHR0NAwMDbvvT7KdYemkpN/hSk6+Jce7j8NXgr2CgZVBDbqqVXpSOoNggJBckA6hogQx3GY5Olp2aZRI1mVyGO6l3EJIYgjJZxVPQru26YmiHoTDWMeaOE4lE8PDwgJeXl9IDTkeOHIk33ngDvXr1glQqxbJlyxAeHo7IyEjo6zdPC06dsADUCoSEhGDQoEH47bff8NZbbyk85mTUSQReD0R6cTqAil/q+b3m492e77aa/qHi8mJcTrhcpQXSz6Ef+rTvA02BZpOXR0QQPhfiXOw55IpzAQC2hrYY6ToSDsYOCtMcOXIEb7zxBs6fP49hw4bVu8zs7Gy0a9cOISEhGDBgAAAgOTkZS5YsQVBQEHg8HkaNGoVt27a16VcomgoLQComk8ng7e0NXV1d3Lhxo9bVTWUyGTbd3oS9D/dCLBEDADqYdMBXg77CAKcBLVXl6vVSsgXSlLJLsnEu7hxin8cCAAy0DBDgHIAe1j1qbWUREQYMGIC8vDw8fPiw3qtexMbGws3NDU+ePEGXLl0QGxsLf39/zJ07F2+++SaKi4sxb948dO3aFT///HOjzlEdsACkYrt378YHH3yAO3fuwNfXV6k02cXZWB68HBfjL0JOcvB4PPSx74P1Q9fDzrjlJlEnIsTkxuB83PkqLZBRrqPQ3rhhQx/qIpaIEZIUgtDUUMhJDgFPgN72vTHAcQC0NbSVyiMsLAw+Pj7YunUr5s+fr3TZcrkcY8eORX5+PjesZ/jw4fD396+yNtmff/6Jzz77DPHx8fU7OTXEApAK5efnw83NDaNHj8b+/fvrnf5h+kMsvbQUT3OeAqh4tD2582SsGLgCWoLqE7M3peySbJyNPYu4vDgAFS2QoR2GortV92bp55GTHGHpYbiccBkiiQgA4G7ujuEuw2GuZ17v/N5//338+eefEAqFMDdXLv3cuXMRFBSE69evw97eHklJSXBycoKurm6VlmvlOL+YmJh610vdsACkQp988gl2796NmJgY2NraNjifI0+OYP2t9cgVVbRCzPXM8an/p5jWdVpTVZUjlohxJfEK7qbd5Vog/u390d+hv9ItkPpKyEvA2dizyCypGHNnqWeJka4jG/U0MDMzE25ubpgxY0aNY/1etGDBAvz999+4evUqN6zn5MmTmDlzZo3j/NiSPnVjAUhFoqOj0aVLF6xatQpLly5tdH7lsnKsvroaRyKOoFRaCgDwsPDA6iGr4W3r3ej85STH/bT7CE4M5logHhYeGO4yHGa6Zo3OX5E8cR7Ox53nWni6GroY7DwYPrY+jRqkWmnjxo1YsmQJHj58iC5duig8hv4zptDNzY3bFxQUhHHjxiE/P1/pcX5MVSwAqcgrr7yCp0+fIjIyEjo6Ok2Wb2pBKpZcWoLrKddBRODz+AhwDsCaIWtgaWDZoDwT8hIQFBuErJKKaWbb6bfDSNeR6GDaocnq/aJyWTmuJV3DrWe3IJVLwefx4WPrg0FOg6Cn2XS/6OXl5ejSpQscHR1x/vx5hbeO8+bNw8GDB/H3339XeffH2NgYYrGYrSfWSCwAqcCZM2fwyiuv4Pjx45gwYUKzlHEj+QZWXF6B+PyKjlBdTV3M6D4Dn/l/pvRje0UtkCHOQ+Bt690kLZD/IiI8znyMi/EXubfAO5h2wEjXkWin3zyT8f9x4g9MeW0K/v77b4wdO7ba/pr6s/bu3Yt33nkHoaGh+OKLLxAWFgYi4m7rPvroo2apb1vDAlALKy8vR7du3WBra4tLly416+qmMpkM+x7tww93fuDeRrYysMLyfssx1qP6L1ulMmkZriVfw62UW9w0Gb1se2GQ06AmmSZDkWeFzxAkDEJqUSoAwFTHFCNcR8Dd3L1ZPiOpXIrbz24jJDEE+z7bB1mODE8jn3JTbjAtgwWgFvbdd99h8eLFePDgAbp169YiZYrKRfjqylc4EXUCEnnFqh/drbojcGggOln+O3skEeFR5iNcjL+I4vJiAICLqQtGuI5othZIUVkRLsZfxKPMRwAq1vga4DgAve17Q4Pf9NNV0f9W2Dgfdx55pXkAAH4OH6veWIU1a9bg888/b/IymZqxANSCsrOz4ebmhmnTpmH79u0tXn7c8zgsubQE91LvgUDQ4GtglOsofDv4WxSWF+Js7FmuBWKma4YRLiPQ0bxjs7VAbqXcwrXkayiXlQMAelr3xBDnITDUNmzy8gAgszgTZ2PPIiE/AQBgqGWIYS7D0LVdVyxatAi//PILhEIhN5sl0/xYAGpBc+bMwZEjRyAUCmFhYaGyelyIu4CvQ77Gs8JnAAAdDR30sO6BjmYdoaupiwGOA+Bn79dsLZCnOU9xPu488kvzAQDtjdpjpOtI2Bk1z2NrkUSE4IRg3Ev7N/D2ad8H/Rz6ce9L5eXlwc3NDePGjcOePXuapR5MdSwAtZCHDx/Cy8sL33//favooJTJZNh6dyt23tvJPbZ3NXXFqiGr0Kd9n2YpM7M4E0GxQUjMTwQAGGkbYViHYejSrkuzDVK9l3YPwYnB3Dl2suyEYR2GwVS3+jitHTt2YP78+QgNDYWPj0+T14epjgWgFkBEGDx4MLKysvDo0SNoajb9wMyGCk0NxZfBXyIuLw52hnbggYe+Dn3xke9HaGfQNP0+JeUlCE4Mxv20+1wLpG/7vujr0LfZ3tiOfR6Lc7HnuBU2rA2sMdJ1JJxMnGpMI5VK4eXlBUNDQ1y/fr1ZHxAwFVgAagHHjh3DpEmTcPbsWYwY0bomFYvOicah8EPQ1dBFjigHd9PuAqgY1jHRcyLe6fEOtDQaFiRkchnupt3FlcQrXAuks2VnDHMZBhMdk6Y6hSpyRbk4F3cOMbkVwyD0NPUQ4ByAnjY9lXp14PLlywgICMDBgwcxderUZqkj8y8WgJqZWCyGp6cnunbtilOnTqm6OtVUBiB7I3u85/UebqXcwo93f+T6hyz0LDDbazaGuQyrV4sg9nkszsaeRY4oB0BFC2SU6yg4mjg2y3mUSktxNekq7jy7w7064Gfnh4FOA6GjUb8XPV977TXcvXsXUVFRbM6fZtamluVpjTZv3oy0tDScP39e1VVRin97f/jZ+eFIxBEceHIAOaIcrLm+BieiTuAjv4/gaelZa/r/tkD0NfUxxHmI0i2Q+pKTHA8zHuJS/CWUSEoAAG5mbhjhOgIWeg3r6N+4cSM8PT2xYcMGfPXVV01YW+a/WAuoGaWmpqJjx46YN28eNmzYoOrqKPTfFtCLCksLsf3edlyIu8C1KoY4D8E8n3kw06s6/quyBXL72W3ISQ4+j89Nk1HfFoiykguSESQM4iZps9CzwAiXEXAzd6sjZd2WLVuG7777DtHR0XBwUDy5GdN4LAA1o7fffhvnz59HTEwMjI2bZ2KuxqotAFWKzY3FljtbuBUt9DT1MKXLFLzZ9U3weXw8SH+AywmXuRZIR/OOGO4yvMEtkLoUlBbgQvwFhGeFA6h4jWCg40D42vk22QobRUVFcHd3x4ABA3D48OEmyZOpjgWgZnLr1i306dMHP/30E957T/EvdmugTACqdDnhMnbe28kNSjXQMoCLqQv3SNtCzwIjXUfC1Uy5Cd/rq3KFjevJ16tM1D/YaXCzrLCxf/9+vPPOO7h69Sr69+/f5PkzrA+o2Tx79gwBAQGYOXOmqqvSZIY4D8EAxwH47dFvOBJxBMXlxXiU+QjWBtaY1XMWApwDmm2Nr/CscFyIv4DCskIAgJOJE0a6joS1QfO9tfz222/jwIEDePbsWbOVoe5YC6gZEVGrf5ekPi2gF2UUZ+DTc58irSgNvna+MNQ2hL+9P/o59GvSicnSitJwNvYst8KGiY4JhrsMh6eFZ4t8ti/DNXyZsRZQM2rLX1wLPQu4mbvB1tAWHc07IqUwBdeSr+FhxkMM7TAU3ay6Ner8i8uLcSn+Eh5mPORW2Ojv2B/+9v7NssJGTdryNWwNWABiGkVfSx9vdn0TiQWJOBd7DnmleTgRdQKhqaEY5TYK9kb29cpPKpfizrM7uJp0lVtho5tVNwztMBRG2mxJ5baGBSCm0Xg8HjwsPOBq5oo7z+4gJCkEqUWp+DnsZ6WDBxEhOjca5+PO47n4OQDAztAOI11HNtsKG4zqsQDENBkNvgb6OvRFd+vuuBR/CQ8yHuBx5mM8zX5a6+1TVkkWzsaeRXxexeyNBloGGNZhWKNv45jWjwUgpskZaBlgnMc4+Nj64GzsWaQUpuBywmWEpYdV6UAWS8QITqyYJqNyhY3KaTKaa4UNpnVhAYhpNnZGdpjVcxb3CD2/NB9/RPwBB2MHWOpZIjI7EmJpxQqvnhaeGO4yXOE0GUzbxQIQ06x4PB66WnWFu4U7biTfwI2UG0guSOYeq7fTb4dRrqPgbOqs4poyqtD0owMZRgEtgRYGOw/GAt8F3DZnE2fM8ZnDgo8aYwGIaVEmOiZwNK6YkqOXXa9mGSHPvDzY1WcYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmV4RESqrgTTNP766y9cvHixXmnyxHkQPhfCQMsAnSw7KZ1OTnLcS7sHAPC28YaAL1A67dPspygqL4KrmSvMdM3qVd9Bgwbh9ddfr1capvXSUHUFmKaTmJiI69ev1ysNgSAnOUpRiuvR9UsrIxkA4FbCrXqlk5McBEJEQgR44NUrraOjY72OZ1o31gJiGEZlWB8QwzAqwwIQwzAqwwIQwzAqwwIQwzAqwwIQwzAqwwKQGiooKMDs2bPh6uoKT09PpKenK5VOKpVi9erV8Pf3h5eXF2bMmIELFy40W3lM28cCkBqaP38+njx5gvXr1yMpKQlisRgA8PHHH2Pbtm01pluyZAm2b9+OgIAAjB8/HmVlZXj11Vcxc+ZM1PY2R0PLY9QAMWrHzMyMwsLCiIjIwMCA4uLiiIgoKCiIfHx8akxnY2NDISEhVbbFx8dTp06daP369U1eHtP2sRaQGiIiGBoaVtvu5uYGoVBYY7qSkhLY29tX2ebs7IytW7di9+7dTV4e0/axAKSGRo0ahQMHDlTbXlJSAh6v5qER/fr1w/79+6ttd3Z2RlpaWpOXx7R9bCyYGgoMDISPjw+AitYJj8dDaWkpvvnmG3h5edWYbt26dejbty/y8vLw4Ycfws3NDRKJBFu3bkWnTjUPZG1oeYwaUO0dIKMqQqGQhg8fTjwejywsLEhbW5ssLS3p7t27taYLCwsjHx8f4vF4pK2tTRoaGmRhYUHXr19vlvKYto0NRlVzycnJePToETQ1NeHn5wdTU1Ol0kVHRyMiIgKGhobw8/ODkZFRs5bHtE0sADEMozKsE1rN5OTkYP369ZgwYQL8/f3h7++PCRMmYMOGDcjOzm5QnikpKZg1a5bCfWKxGNevX0dkZGS1faWlpfj1118bVCbTNrAWkBq5e/cuRowYAT09PQwdOhRWVlYAgMzMTFy6dAkikQjnzp3jOoyV9ejRI3h5eUEmk1XZHhMTg+HDhyM5ORk8Hg/9+vXD4cOHYWNjw5Vra2tbLR2jPlgAUiO9e/dG9+7dsXPnzmqPv4kIc+bMwePHj3HrVtUZDk+ePFlrvvHx8fj000+rBZIJEyZAIpFg3759yM/Px6JFixAZGYkrV67AwcGBBSCGBSB1oquriwcPHsDDw0Ph/qioKPTs2ZMbKlGJz+eDx+PVOtyCx+NVCyRWVla4ePEiunbtCqAiyM2bNw9nzpxBcHAw9PX1WQBSc6wPSI1YW1sjNDS0xv2hoaHcbdmLbGxscPz4ccjlcoX/wsLCFOYnFouhofHvq2Y8Hg87duzAmDFjMHDgQMTExDT+pJiXGnsRUY0sXrwYs2fPxv379xEQEFCtD+inn37Cxo0bq6Xz9vbG/fv3MW7cOIX51tQ68vDwwL179+Dp6Vlle+UA1LFjxzb2lJiXnSpePmJU5/Dhw+Tn50caGhrE4/GIx+ORhoYG+fn50ZEjRxSmuXr1KgUFBdWYZ3FxMV25cqXa9jVr1tCoUaNqTDd37lzi8Xj1PwmmzWB9QGpKIpEgJycHAGBhYQFNTU0V14hRRywAMQyjMqwTmmEYlWEBiGEYlWEBiGEYlWEBSM1kZWUpfNQOAFu2bKlxYrGWTseoCdU+hGNaWmRkJFlbW9O8efOqbF+8eDFZWFjQw4cPW0U6Rj2wAKSGoqKiyM7OjmbOnEkymYw+/PBDsrKyokePHrWqdEzbxx7Dq6m4uDgEBARAU1MTIpEIFy9erPbGcmtIx7RtrA9ITbm4uMDf3x9xcXHo1asX3N3dW2U6pm1jAUgNERHeeust3L59GyEhIYiOjsbkyZMhlUpbVTpGDaj0BpBpcRKJhCZNmkSurq6UnJxMREQZGRnUpUsXGjNmDJWVlbWKdIx6YC0gNRMaGgqhUIhr166hffv2ACrm7QkODkZGRgauXbvWKtIx6oF1Qqsh+t/aXMpuV1U6pu1jAYhhGJVht2AMw6gMC0AMw6gMC0AMw6gMC0AMw6gMC0BqpqErlbZ0OkZNqOoFJKblRUdHk6OjI/F4POLz+TRgwABKS0vj9mdkZBCfz1d5OkZ9sBaQGvniiy/QpUsXZGVlITo6GoaGhujbty+Sk5NbVTpGjag6AjItp127dvT48WPuZ7lcTnPmzCEHBweKi4ursUXS0ukY9cFaQGqkoSuVtnQ6Rn2wlVHVSENXKm3pdIz6YC0gNTJhwgQcOnRI4b5t27Zh6tSpCpdYbul0jPpgY8EYhlEZ1gJSM0+fPsXevXsRFRUFAIiKisLcuXMxa9YsXL58udWkY9SESrvAmRYVFBREWlpaZGZmRjo6OhQUFESWlpY0dOhQGjJkCAkEArp06ZLK0zHqgwUgNeLv70/Lly8nIqJDhw6RqakpLVu2jNu/ZMkSGjZsmMrTMeqDBSA1YmRkREKhkIiIZDIZaWhoUFhYGLf/yZMnZGVlpfJ0jPpgfUBqpnIGQj6fDx0dHRgbG3P7DA0NUVBQ0CrSMeqBBSA14uTkBKFQyP1869YtODg4cD8nJyfDxsZG5ekY9cFeRFQjc+fOhUwm437u0qVLlf1BQUEYMmSIytMx6oO9B8QwjMqwWzCGYVSGBSCGYVSGBSCGYVSGBSCGYVSGBSCGYVSGBSCGYVSGBSCGYVSGBSCGYVSGBSCGYVSGBSCGYVSGBSCGYVSGBSCGYVSGBSCGYVSGBSCGYVSGBSCGYVSGBSCGYeqlrKwMX3zxBWxtbaGrqws/Pz9cuHChQXmxAMQwTL2888472Lx5M958801s2bIFAoEAo0ePxvXr1+udF5sRkWEYpYWGhsLPzw8bNmzA4sWLAQClpaXo0qUL2rVrh5s3b9YrP9YCYhgVSE1NxbvvvgtbW1toa2vD2dkZc+fORXl5OQAgPj4ekyZNgpmZGfT09NC7d2+cPn26Sh5XrlwBj8fDH3/8gdWrV8Pe3h46OjoICAhAbGwsd9yCBQtgYGAAkUhUrR5Tp06FtbV1lbm7a3Ps2DEIBALMnj2b26ajo4N3330Xt27dQkpKSr0+BzYpPcO0sLS0NPj6+iI/Px+zZ8+Gh4cHUlNTcezYMYhEIuTl5aFPnz4QiUT46KOPYG5ujv3792Ps2LE4duwYJkyYUCW/tWvXgs/nY/HixSgoKMD69evx5ptv4s6dOwCAKVOm4Mcff8Tp06cxadIkLp1IJMKpU6fwzjvvQCAQKFX3Bw8eoGPHjjAyMqqy3dfXFwDw8OFDtG/fXvkPQ7XLkjGM+pk+fTrx+Xy6e/dutX1yuZwWLVpEAOjatWvc9qKiInJ2diYnJyeSyWRERBQcHEwAyNPTk8rKyrhjt2zZQgDoyZMnXJ52dnY0ceLEKmX98ccfBICuXr2qdN07d+5MQ4YMqbY9IiKCANDOnTuVzouILUzIMC1KLpfjr7/+wpgxY+Dj41NtP4/Hw5kzZ+Dr64t+/fpx2w0MDDB79mwkJiYiMjKySpqZM2dCS0uL+7l///4AKm7jKvOcNGkSzpw5g+LiYu64I0eOwM7Orko5dRGLxdDW1q62XUdHh9tfHywAMUwLys7ORmFhYbU10l6UlJQEd3f3ats9PT25/S96cbFHADA1NQUA5OXlcdumTJkCsViMkydPAgCKi4tx5swZTJo0iVu9Vhm6urooKyurtr20tJTbXx8sADHMS66m/ht64QF379694eTkhD/++AMAcOrUKYjFYkyZMqVeZdnY2CA9Pb3a9spttra29cqPBSCGaUGWlpYwMjJCeHh4jcc4OjoiOjq62vaoqChuf0NMnjwZZ8+eRWFhIY4cOQInJyf07t27Xnn06NEDMTExKCwsrLK9ssO7R48e9cqPBSCGaUF8Ph/jx4/HqVOncO/evWr7iQijR49GaGgobt26xW0vKSnB7t274eTkhE6dOjWo7ClTpqCsrAz79+/H2bNnMXny5Hrn8frrr0Mmk2H37t3ctrKyMuzduxd+fn71ewIG9hieYVrcmjVrcP78eQwcOBCzZ8+Gp6cn0tPTcfToUVy/fh1LlizBoUOHMGrUKHz00UcwMzPD/v37kZCQgD///BN8fsPaDV5eXnB1dcXy5ctRVlZW79svAPDz88OkSZOwdOlSZGVlwdXVFfv370diYiL27NlT/0rV65kZwzBNIikpiaZPn06Wlpakra1NHTp0oPnz53OP0+Pi4uj1118nExMT0tHRIV9fX/rnn3+q5FH5GP7o0aNVtickJBAA2rt3b7Vyly9fTgDI1dW1wXUXi8W0ePFisra2Jm1tberVqxedPXu2QXmxoRgMw6gM6wNiGEZlWB8QwzAoLi6u8pKiIpaWlkoP2VAWC0AMw2Djxo34+uuvaz0mISEBTk5OTVou6wNiGAbx8fHc0I2a9OvXjxty0VRYAGIYRmVYJzTDMCrDAhDDMCrDAhDDMCrDAhDDMCrDAhDDMCrDAhDDMCrDAlAbcfv2bZw6dQpt/a2KoKAg3LhxQ9XVYJoIew+oDZDJZOjVqxc0NDRw+/btBk/X0NoREfr374+CggI8ePAAGhrsRf6XXdv8pqqZvXv34sGDB9iyZUubDT5AxeTqW7ZsQURERJUJsZiXV5v8tu7YsQPdunWDkZERjIyM4O/vj6CgIG5/YGAgevXqBUNDQ7Rr1w7jx49XOAVmY7VEOQUFBVi2bBneeust+Pv7N2nerZG3tzdmzpyJFStW4Pnz5/VK++OPP8LJyQk6Ojrw8/NDaGhoM9WSUVqDZyVqxU6ePEmnT5+mmJgYio6OpmXLlpGmpiaFh4cTEdGIESNo7969FB4eTg8fPqTRo0eTg4MDFRcX15jn9evXqby8vNr2iIgIysjIUJimIeXU16effkp6enr07NmzJsuztUtPTydDQ0P68MMPlU5z+PBh0tLSol9++YUiIiLo/fffJxMTE8rMzGzGmjJ1aZMBSBFTU1P6+eefFe7LysoiABQSEqJwv0wmo+7du9Prr79OUqmU2x4VFUVWVla0bt06pepQUzlJSUk0depUMjExIVNTU5o2bRo9f/68zvyio6NJQ0ODvv32W6XKb0vWrVtHAoGAIiIilDre19eX5s+fz/0sk8nI1taWAgMDuW0NvQ5Mw7XJW7AXyWQyHD58GCUlJTXeohQUFAAAzMzMFO7n8/k4c+YMHjx4gOnTp0MulyMuLg5DhgzB+PHj8fnnnytVF0XlxMbGwtvbG66urrh9+zYuXLiA2NhYfPbZZ3Xm9+mnn8LOzg6ffPKJUuW3JQsXLoSTkxMWLVpU55O/8vJy3L9/H0OHDuW28fl8DB06lJv4vTHXgWkEVUfA5vL48WPS19cngUBAxsbGdPr0aYXHyWQyeuWVV6hv37515pmUlEQODg40ZcoUcnBwoOnTp5NcLleqPjWVM2zYMPryyy+rbDt27Bg5OzvXml9QUJDC+YDVyd9//00A6OTJk7Uel5qaSgDo5s2bVbZ/9tln5OvrS0QNvw5M47TZAFRWVkZCoZDu3btHS5YsIQsLC4XN9Tlz5pCjoyOlpKQolW9ISAgBoA4dOpBEIlG6PorKSUxMJACkq6tL+vr63D8dHR1yc3OrMa/y8nLy8PCggQMHKh0A2yK5XE7Dhg0jV1dXKi0trfG4ugJQQ68D03htNgD9V0BAAM2ePbvKtvnz55O9vT3Fx8crlUdGRga5u7vTmDFjyNramhYsWKBUuprK+fvvv8nMzIyEQmG1f7V1Kn///ffE5/PpwYMHSpXfloWHh5NAIKD169fXeExZWRkJBAI6ceJEle3Tp0+nsWPHNvg6MI2nNgFo8ODBNGPGDCKq+Ms5f/58srW1pZiYGKXSZ2dnU+fOnWn8+PEkkUgoIiKCLC0t6dNPP60xTV3lnDlzhjQ1NamkpETp88jOziYTExP64IMPlE7T1i1YsIAMDQ1rfBpJVNEJ/eIfDJlMRnZ2dhQYGNig68A0jTYZgJYsWUIhISGUkJBAjx8/piVLlhCPx6Pz588TEdHcuXPJ2NiYrly5Qunp6dw/kUikMD+ZTEY+Pj40evRobt0mIqKHDx+SmZkZbd68WWG6usrJzc0lc3NzmjhxIj18+JCEQiEFBQXRwoULazy3yjyzsrIa+Om0Pbm5uWRmZkbvvvtujcccPnyYtLW1ad++fRQZGUmzZ88mExMTysjIaNB1YJpGmwxAs2bNIkdHR9LS0iJLS0sKCAjggg8REQCF/xQt5Fbp/PnzJBaLq20PCwursf9ImXLu3LlDgwYNIiMjIzI0NCQvLy/asmWLwvwePXpEPD6Pur7dlbKLspX7MNRAUVkRDZo3iHg8Ht27d6/G47Zu3UoODg6kpaVFvr6+dPv2bW5ffa4D03TYWLCXBBGh/6D+uP30NvQ/0oeejh5m9piJVYNWqe2YKJlMhh33d2DXvV0oFBciY1MGejj2wO2bt8Hj8VRdPUYJbf49oLbixIkTuHH1BiYsnABdHV2IpWJsv7cdnbZ3wuEnh1VdvRZ3JeEKBv86GBtvbkRReREMdQ0xc8lMhN4OxZEjR1RdPUZJL20Aai3jva5evYoxY8bA1tYWPB4Pf/31V5OXUVpaik8//RSjR4/G0eVHET4nHIOdBkPAEyBLlIW5p+ei/y/9EZEV0eRltzbJBcl449gbmPn3TCQXJEPAE2C022hcnXkVOz/aiXHjxuHzzz+HSCRSOs+WuIaMYi9tALK3t8fatWtx//593Lt3D0OGDMG4ceMQEVHxSxgSEoL58+dzb7VKJBIMHz4cJSUlNeZ548YNSCSSatsjIyORmZmpME1JSQm6d++OH3/8sWlOTIHNmzfj2bNn2Lx5MwDAwsACJ944gX+m/QMXMxcQCE+ynmDA3gF46/hbKC6tfYXLl5G4XIyll5Zi6K9DcfvZbRAIXdp1wYk3TmD7K9thplvxdvnGjRuRmZmJDRs2KJ13S1xDpgYq7oNqUqoe7wWg2rsmlZ48eUKjRo0iQ0NDsrKyok8++aTKE7WapKamkr6+Pn388cc1HrPr7i5y/M6RjAONyTjQmGw32VLg1cAaj3/Z7H+wn3rs7EGO3zmS43eO5LPbh45FHKvx+C+++IJ0dXUpKSmp3mXVdA0bev2Y2rWJACSVSunQoUOkpaVV4+BEoVBIAOjJkyc15pOamkouLi40bdo0kslkFBsbS7a2tkq/c1PTlzcsLIwMDQ1p+fLlJBQKKTg4mGxsbGjVqlV15jl9+nSysLCgvLy8Wo8TS8S04PQCslxvyQWiTts60eloxUNQXgZ3Uu7Q0F+HcoHHfas7rbqyisqktf/iFxYWkrW1Nb3xxhv1LlPRNWzM9WNq91IHoNY23qumAOTt7U3z5s2rsm3ZsmXcOKSa3L59mwDQrl27lCqfiCglL4VG/DaCTNaakHGgMZmsNaGA/QEkzBEqnYeqpRel08wTM8n5e2dy/M6RnL53ohnHZ1B6UbrSeezdu5cA0LVr1+pVtqJr2NDrx9TtpQ5ArW28l6Iv79OnTwkAPX36tMr2r776irp3715jXjKZjPz8/Kh79+5VbgmVdV54nrpu78q1hizWW9D7J98nsaT6u0ytRZm0jFZdWUUeWz24Vs/QX4fSreRb9c6r8uVRLy8vkslkSqf77zVs6PVjlPNSB6D/UuV4LyLFAejYsWOkqalZ7Zdg8uTJ9NZbb9WY16+//koA6MqVK0qXr8jGGxvJfpM9F4gcNjvQD7d/aFSezeFYxDHy2e3DBZ4eO3vQ3rC9jcrzxo0bBID27NmjdJr/XsOGXj9GOW0qAKlivNeLFAWgc+fOEZ/PrzJaOz4+njQ1NSkoKEhhPkVFRWRra0uvv/66UuXWpUhcRDNOzCDzdeZcIOq5oyeFJCjukG9JjzIe0SsHXuECj9sPbrTk4hISlSkeFlNf06ZNo3bt2lFBQYFSx//3Gjbk+jHKe2kDUGsZ71VUVEQPHjygBw8eEADavHkzPXjwgHsCk5+fT2ZmZrRo0SKKi4ujS5cukaenJ7399ts1ntvy5ctJW1ubEhISGvjpKBaVHUUD9w4kk8CK/iHTtab0yu+v1KtvpankinJp7j9zqcP3HSr6eb5zoilHp1D8c+VaqspKSUkhPT09+uyzz2o8prZr2JDrxyjvpQ1ArWW8V3BwsMJyKltiRERXr14lLy8v0tHRoQ4dOlBgYGCN/Trx8fGkqaVJ7y6seWBlY/0Z8Sd1/KEj1xpqt74dLQpaVK/+roaSSqW06eYm6vxjZ67V0/+X/nQp/lKzlTnvs3kk0BDU2BKu6xrW5/ox9cPGgrUy4yeMx6ngUzD+1BjDPIZh15hdMNExafJypFIpVl9fjV33d0EkqXhr2FzXHCsGrsA7Pd5p8vIA4KzwLL65+g1Si1IBAIZahvjA5wPM9Z4LgUDQ5OXll+Zj7j9zcS7qHAo3FWJ4v+E4c+pMk5fDNBwLQK1IcHAwhgwZAsu3LFHeuRwAoKephw+8P8DyfsubZdBpfmk+Pjj1AS7GX4SMZAAAd3N37HhlB7xsvZqkjNjcWHxx6QvcT7sPANDga+CVjq/gm0HfwEjHqEnKeJFUKkXgjUDsvLcTJZKKN9+1orSQvT8b586dw/Dhw5u8TKZhWABqJWQyGby8vKCvr4+QkBB8dukzHHpyCGWyMgCAtYE11gWswzjPcc1SflhaGOafmY+nOU8BAAKeAEOch+CnsT81uAVWXF6MlcErcTL6JCTyiiEuPa17Yk3AGnhaejZV1av4J+YffH7+c6QVpwEAtARamNplKjYO3YiAgADk5ubi4cOH0NTUbJbymXpS5f1fbbZv305du3YlQ0NDMjQ0pN69e9OZM2e4/WvWrCEfHx8yMDAgS0tLGjduHEVFRTVLXbZt20aOjo6kra1Nvr6+dOfOnSYvY+fOnQSAQkNDuW3pRen06oFXyXStacVLhYEmNGDvAIrKbp7zJCLa/3A/ddjSgesfst5gTf938f/q1T8klUpp592d1G17N66fp/dPvelU9Klmq7cwR0iD9w2u8gLmqN9GUUrev313YWFhxOPx6IcflH8NoSW/Z+qo1Qag1rK4YEssaPf8+XMyNzev0nH9opCEEOq5o+e/LxWus6AZJ2ZQkbioyerwIolEQovPLSarDVZcmR1/6Eh/RvxZZ9qQhBAauHcgF3g8t3nSuuvrmq3TViwR07t/v0sW6yy4unbb3o0uxSnu1H7//ffJ1NSUcnJylMq/JRaXVGetNgApoorBpsosaEfUuEXtFi1aRAYGBpSWllbrcdvubCOHzQ7cL5r9JnvaeGOjUmU0RHpROo09OLZKC6z/L/0pMiuy2rEp+Sk07dg0cvrOiRy/c6QO33eg90++T7mi3Gar33e3vqP2m9tzn0f7ze3pu1vf1ZomMzOTjIyMqlzT+lD0PWMLGjbcSxGAVDXYtK7VFF4s28LCglasWEFRUVF079498vX1rXWO4kqRkZGkoaFRLaDVRCwR0/sn3yeL9f/+xe+6vSudjz1fd+IGupZ0jbx2enHlma8zp+nHp1ORuIhKJaW0/NJy6vhDR67VM+r3UfQg7UGz1edy/GXqtqNblRbhu3+/q/Qwk40bNxKfz6fHjx/Xu+z/fs8ac+2ZVh6AVD3YVJkF7Ygat6jdyJEjqUOHDgrfP6qNMEdIAfsDqvR5jPhtRJU+j6a2PXQ7OXz3bwvMdK0p2Wy04QKP9y5vOvz4cLOVn5KXQqN+G1XlnAfvG1zvgbZlZWXk5uZGQ4YMqde6aoq+Z2xBw8Zp1QFI1YNNlQlAjVnU7vTp07XOIaSMoJgg6rStExcULNdb0rx/5lGppOaF+hpDLBHT1GNTSedbHRJ8LSDB1wLS/VaX3vv7vTqnyWgoiURCH535qMpUI55bPemvyL8anOc///xT78/+v98ztqBh471Uj+GHDh0KFxcX7Nq1i9u2YMEC/P3337h69SqcnZ3rzCMzMxMDBw5Ex44dcffuXbz++uvYunWrwmPLy8uhp6eHY8eOYfz48dz2GTNmID8/H3///TdOnjyJmTNn4s6dO9XS6+rqws7Orsa8u3btCnt7e1y8eLHRk6ivvbYWW+9uRUl5xXsvpjqmWNpvKWb7zG5Uvi8qLC3Ejns7cD7uPEokJQjPCoeMZDDWNoaupi4GOw7GmqFrYG1g3WRl7gnbg2+vfYs8cR4AQF9TH3N85mBp36WNei+KiDB69GjExMQgMjIS2tratR6v6HvW0GvPvEDFAbBeVDHYtLYF7YgatrggEdGmTZsa3A9RkyJxEb3555tkttaMayl47fKim8k3605cC5lMRgcfH6RXDrxCA/cOpIF7B9KcU3Pou1vf0ay/ZpH3Lm/uNsxjqwd9c+WbRreGQp+FUq/dvbjzMFtrRm8cfYPyxHmNyvdFkZGRJBAIaO3atTUeU9v3jC1o2HitNgC1lsGmtS1oR9SwxQUrn8T8d5KrphKeGU799vTjBp2arTWj8YfGN2gtsVspt+jNP9/kAs/EIxPprPAsyWQyupVyi1YGr6SDjw/S3rC99Zo2tSbZRdk04fAELoiaBJpQnz196FH6o3rnpYyFCxfW+gSytu8ZW9Cw8VptAGotg02Jal/Qjqj+i9q99957JNAT0Jg9Y5p89PeLDj8+TG5b3Kq8VPj5+c+VeqkwOT+ZFp9bzAWe4b8Op133dlGZ5N/gnVKQQiuDV9L66+tJLpeTqExESy4uIbcf3LhA9MqBV+hRRt3BQyKR0NKLS8l6gzVXX9ctrnTg0YFGfQa1Cc8Mp95bexNfn09vT1c8ur2u7xlb0LBxXqo+oLbgwYMH8Pb2hsl4ExgNMIImXxMTPCbg60FfQ1dLt8nLk0qlWBmyEr88+AViqRgAYKlnia8HfY1p3aZVO15ULsLusN04HXMaErkEPPDQz6EfPvT9EO0M2lXNWy5F4LVAyEiGhX4LYaprCgBIyEvA0ktLcefZHRAIAp4AI1xH4Nsh33KrV7zoWOQxLLu4DFmiLACAjoYOZnSfgdWDVzfL+Lfi0mLMC5qHM8IzkMqlKLtThtK/ShEaGopevXo1eXlMzVgAakFEhEGDBiE7OxtfH/4aG+5sQFZJxS+diY4JPvT9EO90f6dZRobnFOdgzuk5CE4Mhoxk4IGHTpadsOOVHehm3Q1yuRwnY05i38N9yC/NBwB0MO2Aj3w/Qg+bHjXm+9P9n5BalIqJnhPR1aprlX2XEy7jqytfIbkgGQBgoGWAWT1nYaHvQggEAkRkRWDOP3MQnhXOBaoBjgPw06s/wcLAosk/AwDYcH0DtoRuQXF5xdJFxjrG+KL3F9j1wS7o6+vjxo0bbFXVFsQCUAs6evQoJk+ejLNnz2LEiBGQyWRYf3M9fn38K8SSitaJi6kLVg1ehb4OfZulDndT72L+mfmIyY0BUDHo1NvGGxb6FkgrqhjAaaJjgpk9ZmJMxzHg82tfOi5IGIQ7qXfQ2743RrqOrLb/xeWTi8qLAADW+tbQ1tBGRHYEpHIpAMDFzAXbRm2Df3v/pjxdzoXYC/jkwidIKUgBUDFIdaLnRHw38jvoaOhwMxEcOHAA06ZVbxkyzYMFoBYiFovh4eGB7t274+TJk1X2ZRdnY8mlJQhODIac5ODxeBjgMABrhqyBnXHzPMrdE7YH31z9Btkl2ZCRDAK+AA6GDnjf633M6TUHelp6SuXzOPMxjj89Dnsje7zn9V6NxxWWFmJF8AocDj+MvNI8yEkOAU8AS31LfDngyyZ9XeBFifmJmH1yNu6m3wURgcfjwdfWF7vG7IKTiVOVYydOnIg7d+4gOjoa+vr6zVIfpqqXdmXUl83GjRuRnp6OTZs2VdtnaWCJPeP24PDEw3A3dwcRISQpBAG/BeDrK1+jXFbepHWRyWXwtPTEu93fRUeLjuDz+CAiFEmK8I/wH4Slhymdl72RPQAgvSida80oEpkdifCscAj4AvD+95+prik8LDwgJSlKpaWNPq8XlUpLMfefufD9yRehaaEgIrQ3bo9Drx3CubfPVQs+ALBhwwbk5ORg3bp1TVoXpmasBdQCnj17Bnd3d8yfPx/r16+v8/jfH/+Ozbc247n4OQDAQs8Cn/l/hildpzSqHkSEmNwYnI87j1xxLgDAxsAG4VnhuJFyA7miXPB4PPB4PPRr3w9rA9bW2QIjImy4uQEiiQjve70PO6Oqx6cVpmHppaW4mnwVRAQ5yWGua46e1j1RLi/Hs8JnACpmY3zX612MdBlZ521fXXbe3YnAG4EoKC0AUNH3tNB3IT7r91mdaZcvX47Nmzfj6dOncHJyalQ9mLqxANQC3nrrLVy4cAFCoRBGRsrNAFguK8c3Id/gj4g/uEnJPC08ERgQWGuncE2yS7JxNvYs4vLiAFT8Ug7tMBTdrbrjcPhhROdGw8HIAUcjj3KTkmkLtPFGlzewfMByaAm0asz74JODiMmNwSjXUfCz9+PqH3gtEIfCD3GtG3dzd0zuPBkphSlwM3PD1C5TcezpMfz26Deuf6ijeUcs9FuIzu061/scrydfx8Kghdw5avA1MNptNLaP2g4DHQOl8iguLkbHjh3Rr18//PHHH/WuA1M/LAA1s5s3b6Jv3774+eef8e6779Y7fXJBMpZeXIqbz26CqOJJUUCHAKwdulbhI+3/EkvEuJJ4BXfT7nL9Lv7t/dHfoT+0NSqGH9xKuYVzcefgZuaGN7u9iYNPDmLTrU3IFVW0ksz1zPGp/6eY1lVx52xIYgiCE4PRtV1XTOw0EUfCj2DDzQ3IEeUAAMx0zfCJ/yd4q9tbOBx+GFE5URjaYSj6OfQDUDFz4o67O3Au7hykcil44GGQ0yDM950PC726n4ZlFGdg9snZuJ5yvaIPDTx0s+qGna/shGe7+s+8+Ouvv2LGjBkICQnBgAED6p2eUR4LQM1ILpfDz88PcrkcoaGhjXq8fjXxKr688iUS8xMBVMwV/U6Pd/Bp708V5isnOe6n3cflhMvc+z8eFh4Y7jK8WuBKL0rHrvu7oC3Qxhf9vgCfx0e5rByrr67G4fDDXAvMw8IDq4eshretd5X0cc/j8Nvj3yAqFyEiOwIR2REAKlpQr3d6Hf/X//+gq6ULIsL6G+shlorxntd7XP9RpYS8BHx/+3s8ynwEANDV0MXrnV7H293ehpZG9RaYVCrFF5e+wIEnB7hWlpW+FQIDAvFap9fq+xH/+9nJ5fD390d5eTnu3bvXLK9FMBVYAGpG+/btw8yZM3Ht2jX069ev0fnJZDL8/OBn/Hj3RxSWFQKo6MNZPmA5Xu34KndcQl4CgmKDuHeM2um3w0jXkehg2kFhvnKSY/2N9SiVlmK292zYGtpy+1ILUrHk0hJcT7kOIgKfx0eAcwDWDFkDSwNLAEBaURrGHhqLuOdxMNQ2hIAnQJ/2fRA4NBAOxg5cXpnFmdhxbwe0BFr4ou8XEPAV/2JfTbyKHfd2IL04nav/HJ85GOI8hDvm10e/YlXIKq6Vpauhi/e93seXA75skpcXb9++DX9/f+zevRvvv/9+o/NjFGMBqJkUFhaiY8eOGDx4MA4dOtSkeReXF+PL4C9xKvpUlcnev+j7BZIKkrg+HF0NXQx2HgwfWx/webV37Fb24wx3GY4+7ftU23896Tq+DP4S8fnxFXlr6uLtrm9DwBdg/6P9yCjOgJzkcDNzw4ZhGzDIeVC1PO48u4Og2CC4mLrg7e5v11ofqVyKg08O4nD4YW7ZoK7tumKo81CsvbG2SSfPr8n06dNx9uxZxMTEwMSkafNmKrAA1EwCAwPxzTffICoqCg4ODnUnaICYnBgsu7wM99LuAaj4ZXQ3d4e3rTf6tO+DQU6DoKup3PCOmyk3cT7uPNzN3TG161SFx8hkMvzy8BdsC92GXHFuxcuTvIpAJ+AL0LVdVyz0XYhhrsMUpj8SfgRPc54iwDkA/R37K1Wv56Ln2H5vOy7GX0RqYSpyRDkgVHxlm3r5oP9KTU2Fu7s7PvvsM6xcubJZylB3TT/QhgEAfPLJJxgwYECzBR8A6GjREccmH8MZ4Rksu7QM+aX5iMyJBPEIvex6QVtQ+xw3L3I0dgQAJBUkQU5yhS0mgUCAN7q8AX0NfWy6vQnxefEgIljqW+I1j9cgkoiQUZKhMH8iQlJBEgAofAenJiY6JvCy8cK52HPIFmUDqOj/WhOwptkWUKxkZ2eHc+fOwdvbu+6DmQZhAaiZaGtro2/f5hlO8V+j3Uajs0VnfHbxMzzJegJRuQibb23GyeiT+MjvI3Sz6lZnHjaGNtAWaKNUWorM4kzYGNpU2S+VS3Er5RauJV9Duawc3ay6QYOvAQKhs2VnFJQVICw9DLniXEzpPKVap3G2KBsiiQiafM0qfUy1iciKwJY7WxCZHYm0ojRo8DXgbOqMgxMOws3CTfkPqBFa6hqqKxaA2oj2Ju3hbeMNV1NXCPgC3Hp2C7HPY7EwaCH6O/bHgl4Lqo1mfxGfx4eDsQOEz4VIKkjiAhARISonCufjziOvtGJWwvZG7WGkbQQtgRYIhI7mHWGiY4Kw9DDE5MZg/c31mOAxAZ0sO3EDOyuf3rU3bl9j53OlHFEOtoVuQ0hiCGQkQ44oB/ZG9tDV0EU/h37oYKa4M515+bChGG0En8eHq5krtDS0MMhpEPaM3QMvGy8QCFeTrmL6X9Pxc9jPKJfWPKzD0aTiNqwyWGQWZ+LXR7/iSMQR5JXmwUjbCBM9J2Jql6koLi+Gma4ZzHTNUFJegqldpmKw82BoC7SRUpCCo5FHse/hPqQXVTzJSsqv+/arXFqOPWF78Pbxt3El8QrkJIeehh5GuIxAZ8vO6GnTE+4W7nUGMOblwVpAbYibuRueZD2B8LkQAR0CsHnEZtxIvoHtd7cjtSgVvz/+HefizuEDrw8w1GVotfSVwUH4XIhT0acQlh4GAkGDr4G+7fuir0NfaAm0EJEVATnJYW9kDwFfgIziDMTlxaG3XW+US8uhp6kHOcmRVJCE3fd3o6d1T270fWVf039dTriMnfd2cq8OWOtbo7t1d5TJyqAl0IIWXwvFkmK4mbXMrRfTMlgAakNcTF3AAw8ZxRkoLCuEkbYR+jr0hZ+9H45EHMHBxweRXZKNb699i+NRx7HQbyHcLdy59O302iGjOAOxz2NRUFoAAy0DdLbsjGEuw6o84hY+FwKoCHgCXkUAEuYK4WnpCQFfAFNdU0ztMhUX4i8gPCsc15KvISw9DC5mLtUmrI99Hostt7fgSdYTABUdzG90eQMOxg64kngFPPAwynUU/o7+GwDgaubazJ8i05LYLVgboq+lzw0GjX0ey23X4Gvgza5v4sDEAxjhMgJ8Hh+R2ZGYe3ouVl9djfzSfMQ+j8XusN3ILMmEVC6FgCfAzB4zManzpCrBh4ggzP1fADJzg5u5G1deZedyZnEmdDUr3mKe1XMWNPmakJEMWSVZ2H1/N4S5QhSWFmLt9bX44NQHeJL1BHweH0M7DMXvE35HT+ueuJJ4BQAwym0U90TOxsAGhtqGzf0xMi2ItYDaGDczNzwrfAZhrhBeNlXfjzHRMcHS/ksx0XMitoRuQURWBC7EX8CVpCtwNnGGjYENbPRtYKlniR42Pbg+oRelF6ejRFICbYE2HIwdwOPxoKOhA7FUjKKyIhhpG6GwrBDpRelwNHGEg7EDetj0QK44FzzwkCPKwcabGxGfF8+9ROlp4YlFvRfB3cIdSflJ+CvqLwCAv70/fO18cSzyWMW5mbPbr7aGtYDamMpf0vi8eMjkMoXHdLToiB9H/4gVA1bAVMcUEpkEMbkxyBPnYXKXybA1tEVKQQoUvaNa2frpYNoBAr6A6/wGKm7NKsd3VU6zQURILkiGjaENpnSZgoKyAkTnRkMil8BI2wjL+y/Hjld3wN3CHbmiXBwOPwwZyeBp4YnhLsMhJznXmmP9P20PC0BtjI2BDfQ19VEmK+PmYq5JQIcAHHjtAFzNXKHB14CAL8DlhMuIzonGc/Fz7sW/F73Y/1OpMjAIc4WwM6y4BawMQM/Fz5EjykF0TjSCE4PB5/Eh4AngYuqCQxMPYZhLxVvTJeUlOPDkAMRSMewM7fCa52vg8Xh4VvgMpdJS6GroVptriHn5sQDUxvB4PC44VAaL2uhp6eEVt1fgZ+cHYx1jCHgClEpLEZoaimORx6rMxlhSXoLUwlQAVVsjlS2g9OJ0rr/oWeEzSGQSHHt6DKGpoRBJRRDwBDDSNoKfvR9Gu42GvlbFtKcSmQSHww/jufg5THVMMbXrVGgKNCvO4X8tLlcz1zrHszEvH3ZF26DK4PBiR3Rtulp1haZAE/qa+pjtPRseFh6QkxzBicHYFroNjzMfg4gQlxcHAsHawLpKZ7C+lj7X8ikpLwEPPMTlxWHjzY24FH8JcpLD3dwd73m9B0MtQ2gJtNClXRcAFbdof0X9hZTCFOho6ODNbm/CQOvfycMUtbiYtoN1QrdBHUw7gM/jI6skC/ml+XWOEnc2cYaeph5KJCUQSUT4wPsDFJYVIqUwBQWlBTj+9Djupt6FjCr6lBT1xbiZuyG1KBVhGWGIy4uruP0joExahk6WnTDHew4kcgmKyougo6HDtZouxl9ERHYEBLyKcWYvTkBWWFaIjOIM8MCDi6lL031ATKvBWkBtkK6mLtcZrEwrSMAXoJNlJwBAeFY47I3tYWNogx7WPeBj6wMtgRaSC5LxR8QfiMqJgo2BTbU8bAxsEJ0Tzd228Xl82BjaoJtVN9gY2KC9cXs8yax416eTZScI+ALcS7uHGyk3AADjPMZVe0u6su62hrbc7RrTtrAA1Ea92DGsjK7tKhYVrJxnp71Rey6IfOj7IeyN7CGVS5EjysHxqOO4nnwdUrkUUrkUN5Jv4FjkMeSIciCVS+Fi6gI/Oz/I5BXL/dgZ2XHvHgFAl3ZdIMwV4nTMaQDAYKfBCgfMcu8bsduvNovdgrVRbuZuuJRwCfF58ZDKpdDg136pHYwduHd4Yp/HwtHEEQn5CUjMT4SPrQ9czFzgZeMFsUQMqVyKi/EXcTH+YpU8XMxcoKuhCy8bLzzJeoKY3Bi4mrnCycQJcXlxEEvFMNAygI6GDvY93AcCoYd1DwxwrD7vskwuQ3xexeRn7PF728VaQG2Ulb4VDLUMIZFLuIGgteHxeOhsWbESRXhWOHc7lJifyL39bKRthA+8P8AEjwkw1Kr6RvJ4j/GY5zMPxjrGyCzOhLZAG7niXJSUl8DR2BHhWeEAKvqbDj05hHJZOZxNnDGm4xiFSyEnFySjTFYGfU19pafvYF4+LAC1UfV9HA+AW9s9Oica7fTbQYOvgeLyYiQVJHHzM7uZu6G7dXd86Pchl66XbS/0sO4BV3NX8MBDligL2hraKJOWoVhSDGsDa0TlREEqlyI6JxpF5UWw1LPElC5TahzZXllnVzNXtlZ7G8YCUBtW334gGwMbmOmaQSKXIO55HPdo/UZyRUexnaEd1xmsJdDCG13eAFDRYpLIJNDT1OM6vyuX9NHkayIxPxGl0lIk5ieiTFYGAy0DvNntTeho6NRYF9b/ox5YAGrDOph2gIAnQK44lwsIteHxeNz7OS/ehlUu1fzfYNDRvCNMdUwhlorxOPNxlWMqW0wA8CTzCYS5QvB5fGgJtDCt67RaXw3IL81HtiibPX5XAywAtWHaGtrcsjhKv5T4v6dhsc9jYW1gDTnJEZUTBSKq1hnM5/Hha+cLALiTeqfKMWlFadxSzOfiziG9OB1W+lZ4vdPrdfbpVLZ+2hu3V3pSfeblxAJQG1fffiBLfUtY6VtBRjIUlheipLwEJZIS8Hl8hYGjp01PaAm0kFWShYT8BFgbWEODp4FSaSl4PB4yizMRmR0JPU09TPScWGX+oZqwwafqgwWgNq7ylzgxPxESmUSpNJW3YdE50VwHsL6WvsLOYB0NHfSw7gEAuP3sNng8HjeUgg8+nuY8RZmsDIOcBqF3+951li2VS/99/M76f9o8FoDaOAs9C5jomEAqlyIhP0GpNJUBKCEvoWLtLwAavJrfI6q8DRPmCvFc/BwaAg1IZBJklmRCJBFBS6CFmT1mKlV2Un4SJHIJDLUMYaVvpVQa5uXFAlAbx+Px6v00zFTXFPZG9hBLxdyUHGWyMoXzAwEVQc7NzA0EQmhqKArLCpFVkgWJTAINngbsDO2qjPGqzYuDT9nj97aPBSA18GI/kLIL4XZp1wXPxc8hkohgrGMMsVSM/NL8Go/3s/cDUPHI/mbKTQj4AmgINGCobQgzXTNuSZ+6vDjdK9P2sQCkBpxMnKDB10B+aT5yRDlKpels2RnPxc9RXF4MJ2MnAOBWNlXExdQF5rrmuJN6B2lFaTDXNYeJjgl4PB4s9S25CcpqkyvKRa44F3weHx1M2dpf6oAFIDWgJdCqsuSOMl58/G2mZwbg3/XCFOHxeODz+EgrSkNRWRF8bX2hJdCCroYuNPmaSgWgyqdfjsaO0NZQfllp5uXFApCaqG8/UHJBMsx0zaAl0AL+d9dWWwC6n3YfGcUZkMgkMNYxhgwyaAu0YW1gjfzSfG4mxdqwycfUDwtAaqLylzq5IBll0rI6jxfmCmGpZwkLPQtI5BKIJRV9QIr6gWKfx+K08DSkcikcjB2graGNxLxEWOhbwNHEEc/Fz5FRnAGpXFpjeRKZhAtwrP9HfbAApCbMdM1grmsOGcmUehwvfC6EpkATPax7QIOvwQWP/46szyjOwNGIo5CTHFYGVhXLQROhsLwQrqauMNQyRHF5MaRyKbdMsyIJ+QmQyqUw0TFR+okZ8/JjAUiNcE/D6rgNyxPnIUeUAz6Pj8FOgwEApdJSEFGV27DCskIcfHIQZbIyOJs4w9nEGToaOtyLiMbaxtDga0BDoAGxVIzUoppvw158+sUev6sPFoDUyIvrd9X2OL6yL8bB2AHdrbtDk68JPo/PTc0BVMz1fPDJQRSWFcJCzwKTO09GSmEKSiQlsDawBp/HR2FZIWwMbWCkZYRcUW6NHdFEVGX6DUZ9sBkR1YiTiRM0+Zrci4JWBorfNH6xNaIl0EJH844QS8VILkiGobYh8kvzcTrmNDKKM6CvqY83u74JGcmQI8pBdkk27A3tKzqveRUzGxppGyG5ILnGAJQjykF+aT4EPAGcTZ2b7fyZ1oe1gNSIBl+D+wWv6XG8RCbh+ogqb9m6tOsCDX7FbZRcLsdvj3+r6CPia2Ja12kw1TVFUn4SiAgiqQhaGloY6TISAJBVkgUDLQMUlBUgV5SL4vLiamVW1sXJxKkicDFqgwUgNVPX4/jE/ERI5VIYaxvDUs+yIo25G7QF2tAR6CAqNwo3k2+CBx4mdprIrVaamJ+IovIiaPG1oCXQwisdX4G+pj6kcinkJIeuhi7ySvMUPo5nk4+pLxaA1EzlL3lKYQo30PRFisZiafA14GnpCSlJ8TT7KfJL8zHCdQQ8LDy4dIn5icgszoSJjgk8LDygq6kLb1tv8Hg8iKViGGobKuwHKpP+u4Q0e/yuflgAUjMmOiaw1LOEnOTctBeVKiefB6oHA3Ndc6QXpaNEUgJTXVN0sujE7SspL0FWSRayRdkw0THhRtP3su0FPo8PmVwGDZ4GnoufI6UwpUq+8XnxkJGs4jUBPfPmOGWmFWMBSA3VNElZrjgXeaV51TqDn4uf42bKTWjyNWGsYwxzXXMkFyZz+5MKkpBfmg9NviaMtI24aVQNtQ3R2bIzTHRMIJKKUCYrg/C5EHKSc2m5Fhdr/aglFoDU0Itrx7/4OL5yLNaLncEiiQgHHh+AWCqGh4UHPMw9kC3KrvI+UGJ+IrJKsmCiY8KtelrJz94PAr4APFTczqUXpXMDYono39kPWf+PWmIBSA05GDtAW6CN4vLiKpPH/7czWCqX4nD4YeSKc2GsbYw5PnNgrmeO7JJsxOXFcekS8hKq3X5Vsjeyh72RPcx0zSCVS/Fc/JzrB8oqyUJhWSE0+ZrVlmVm1AMLQGpIwBdw011UBp1yWTnXqnE1cwUR4a+ov5BckAxtgTbe7PYmOll2Qnuj9pCRDDE5MSguL4ZIIuLW/LIzsoOjiWO18vzs/GCma8aNJ6vse6q8/XI2da5z5VambWIBSE39tx8oIS8BMpLBVMcU5rrmuJxwGeFZ4eDz+JjSZQra6bcDj8dDT5ue0NfSR1ZJFpLyk5CUn4TMkkzoaerBy8YLfF71r1Qny05op98O+lr6KC4v5pbwYZOPMSwAqanKIQ+phakQSURVHr8/yHiAa8nXAABjOo6pMjlYl3ZdYKJtglxxLoS5QsTlxSFXlKvw9quSgC9AL7tesDGwQWFZIYS5QuSL87knYmz4hfpiAUhNGWkbwdrAGoSKR++VrRFtgTb+ifkHADDQcSB62vSsks7GwAZOJk6Qkxx30+4iNDUUMpLBwciBW0lVEW8bb9gZ2UFOcqQWpeL2s9uQkxyWepYw1TVtvhNlWjUWgNRY5a3P/bT7KCgrQKm0FLee3YKc5Ohm1Q2DnAZVS8Pj8dDPoR8AICIrgrud6ufQr9ZR7Ppa+ujXvh90NXSRK8pFSFJIRR3Y0y+1xgKQGqv85b+bdhelklKkFKRAKpfC0dgRY93H1hhQfGx9oK+pj2dFz/Cs8Bn0NPXQy65XneX1cegDCz0LFJcX4/az2xV1YP0/ao0FIDVmb2QPHQ0dpBal4m7aXehp6sFCzwJvdHmj1qdSlvqWcDZ1RkFZAfJL8+Fg5IB2+u3qLM/awBo9rHtARjLE58VDk6/JLR3NqCf27LMNycvLQ2FhYb3S6Bbr4mnsU2jwNNBNuxsGGA9AVlpWnelceC7Iz8gHEcHF1QXJycl1pgGA/kb98Xvu7yiXlkOWJ8OzlLonq3+RoaEhzMzM6pWGab14pOxCUUyr98UXX2D9+vWqrkazWrhwIb7//ntVV4NpIiwAtSFCoRCJiYkNSlsmLXsplsJxcHCAu7u7qqvBNBEWgBiGURnWCc0wjMqwAMQwjMqwAMQwjMqwAMQwjMqwAKSGCgoKMHv2bLi6usLT0xPp6TWvWPoiqVSK1atXw9/fH15eXpgxYwYuXLjQbOUxbR8LQGpo/vz5ePLkCdavX4+kpCSIxRWT03/88cfYtm1bjemWLFmC7du3IyAgAOPHj0dZWRleffVVzJw5s9aFDhtaHqMGiFE7ZmZmFBYWRkREBgYGFBcXR0REQUFB5OPjU2M6GxsbCgkJqbItPj6eOnXqROvXr2/y8pi2j7WA1BARwdDQsNp2Nzc3CIU1rxtfUlICe3v7KtucnZ2xdetW7N69u8nLY9o+FoDU0KhRo3DgwIFq20tKSmqdUqNfv37Yv39/te3Ozs5IS0tr8vKYto8NRlVDgYGB8PHxAVDROuHxeCgtLcU333wDLy+vGtOtW7cOffv2RV5eHj788EO4ublBIpFg69at6NSpU43pGloeowZUewfIqIpQKKThw4cTj8cjCwsL0tbWJktLS7p7926t6cLCwsjHx4d4PB5pa2uThoYGWVhY0PXr15ulPKZtY2PB1FxycjIePXoETU1N+Pn5wdRUuelRo6OjERERAUNDQ/j5+cHIyKhZy2PaJhaAGIZRGdYJrWZycnKwfv16TJgwAf7+/vD398eECROwYcMGZGdnNyjPlJQUzJo1S+E+sViM69evIzIystq+0tJS/Prrrw0qk2kbWAtIjdy9excjRoyAnp4ehg4dCisrKwBAZmYmLl26BJFIhHPnznEdxsp69OgRvLy8IJPJqmyPiYnB8OHDkZycXDGZfb9+OHz4MGxsbLhybW1tq6Vj1AcLQGqkd+/e6N69O3bu3Fnt8TcRYc6cOXj8+DFu3bpVZd/JkydrzTc+Ph6ffvpptUAyYcIESCQS7Nu3D/n5+Vi0aBEiIyNx5coVODg4sADEsACkTnR1dfHgwQN4eHgo3B8VFYWePXtyQyUq8fl88Hi8Wodb8Hi8aoHEysoKFy9eRNeuXQFUBLl58+bhzJkzCA4Ohr6+PgtAao71AakRa2trhIaG1rg/NDSUuy17kY2NDY4fPw65XK7wX1hYmML8xGIxNDT+fdWMx+Nhx44dGDNmDAYOHIiYmJjGnxTzUmMvIqqRxYsXY/bs2bh//z4CAgKq9QH99NNP2LhxY7V03t7euH//PsaNG6cw35paRx4eHrh37x48PT2rbK8cgDp27NjGnhLzslPFy0eM6hw+fJj8/PxIQ0ODeDwe8Xg80tDQID8/Pzpy5IjCNFevXqWgoKAa8ywuLqYrV65U275mzRoaNWpUjenmzp1LPB6v/ifBtBmsD0hNSSQS5OTkAAAsLCygqamp4hox6ogFIIZhVIZ1QjMMozIsADEMozIsADEMozIsAKmZrKwshY/aAWDLli01TizW0ukYNaHah3BMS4uMjCRra2uaN29ele2LFy8mCwsLevjwYatIx6gHFoDUUFRUFNnZ2dHMmTNJJpPRhx9+SFZWVvTo0aNWlY5p+9hjeDUVFxeHgIAAaGpqQiQS4eLFi9XeWG4N6Zi2jfUBqSkXFxf4+/sjLi4OvXr1gru7e6tMx7RtLACpISLCW2+9hdu3byMkJATR0dGYPHkypFJpq0rHqAGV3gAyLU4ikdCkSZPI1dWVkpOTiYgoIyODunTpQmPGjKGysrJWkY5RD6wFpGZCQ0MhFApx7do1tG/fHkDFvD3BwcHIyMjAtWvXWkU6Rj2wTmg1RP9bm0vZ7apKx7R9LAAxDKMy7BaMYRiVYQGIYRiVYQGIYRiVYQGIYRiVYQFIzTR0pdKWTseoCVW9gMS0vOjoaHJ0dCQej0d8Pp8GDBhAaWlp3P6MjAzi8/kqT8eoD9YCUiNffPEFunTpgqysLERHR8PQ0BB9+/ZFcnJyq0rHqBFVR0Cm5bRr144eP37M/SyXy2nOnDnk4OBAcXFxNbZIWjodoz5YC0iNNHSl0pZOx6gPtjKqGmnoSqUtnY5RH6wFpEYmTJiAQ4cOKdy3bds2TJ06VeESyy2djlEfbCwYwzAqw1pAaubp06fYu3cvoqKiAABRUVGYO3cuZs2ahcuXL7eadIyaUGkXONOigoKCSEtLi8zMzEhHR4eCgoLI0tKShg4dSkOGDCGBQECXLl1SeTpGfbAApEb8/f1p+fLlRER06NAhMjU1pWXLlnH7lyxZQsOGDVN5OkZ9sACkRoyMjEgoFBIRkUwmIw0NDQoLC+P2P3nyhKysrFSejlEfrA9IzVTOQMjn86GjowNjY2Nun6GhIQoKClpFOkY9sACkRpycnCAUCrmfb926BQcHB+7n5ORk2NjYqDwdoz7Yi4hqZO7cuZDJZNzPXbp0qbI/KCgIQ4YMUXk6Rn2w94AYhlEZdgvGMIzKsADEMIzKsADEMIzKsADEMIzKsADEMIzKsADEMIzKsADEMIzKsADEMIzKsADEMIzKsADEMIzKsADEMIzKsADEMIzKsADEMIzKsADEMIzKsADEMIzKsADEMIzSiouLsXLlSowcORJmZmbg8XjYt29fg/NjAYhhGKXl5ORg1apVePr0Kbp3797o/NiUrAzDKM3Gxgbp6emwtrbGvXv30KtXr0blx1pADKMCqampePfdd2FrawttbW04Oztj7ty5KC8vBwDEx8dj0qRJMDMzg56eHnr37o3Tp09XyePKlSvg8Xj4448/sHr1atjb20NHRwcBAQGIjY3ljluwYAEMDAwgEomq1WPq1KmwtrauMnd3bbS1tWFtbd2IM6+KtYAYpoWlpaXB19cX+fn5mD17Njw8PJCamopjx45BJBIhLy8Pffr0gUgkwkcffQRzc3Ps378fY8eOxbFjxzBhwoQq+a1duxZ8Ph+LFy9GQUEB1q9fjzfffBN37twBAEyZMgU//vgjTp8+jUmTJnHpRCIRTp06hXfeeQcCgaBFPwOOqhcmYxh1M336dOLz+XT37t1q++RyOS1atIgA0LVr17jtRUVF5OzsTE5OTiSTyYiIKDg4mACQp6cnlZWVccdu2bKFANCTJ0+4PO3s7GjixIlVyvrjjz8IAF29erVB53H37l0CQHv37m1QeiK2MCHDtCi5XI6//voLY8aMgY+PT7X9PB4PZ86cga+vL/r168dtNzAwwOzZs5GYmIjIyMgqaWbOnAktLS3u5/79+wOouI2rzHPSpEk4c+YMiouLueOOHDkCOzu7KuW0NBaAGKYFZWdno7CwsNoaaS9KSkqCu7t7te2enp7c/he9uNgjAJiamgIA8vLyuG1TpkyBWCzGyZMnAVQ8Tj9z5gwmTZrErV6rCiwAMcxLrqb+G3phyb/evXvDyckJf/zxBwDg1KlTEIvFmDJlSovUsSYsADFMC7K0tISRkRHCw8NrPMbR0RHR0dHVtkdFRXH7G2Ly5Mk4e/YsCgsLceTIETg5OaF3794NyqupsADEMC2Iz+dj/PjxOHXqFO7du1dtPxFh9OjRCA0Nxa1bt7jtJSUl2L17N5ycnNCpU6cGlT1lyhSUlZVh//79OHv2LCZPntzg82gq7DE8w7SwNWvW4Pz58xg4cCBmz54NT09PpKen4+jRo7h+/TqWLFmCQ4cOYdSoUfjoo49gZmaG/fv3IyEhAX/++Sf4/Ia1G7y8vODq6orly5ejrKyswbdf27ZtQ35+PtLS0gBU3M49e/YMAPDhhx/C2NhY+cwa/PyMYZgGS0pKounTp5OlpSVpa2tThw4daP78+dzj9Li4OHr99dfJxMSEdHR0yNfXl/75558qeVQ+hj969GiV7QkJCTU+Hl++fDkBIFdX1wbX3dHRkQAo/JeQkFCvvHhEL/RUMQzDtCDWB8QwjMqwPiCGYVBcXFzlJUVFLC0tm3zIBgtADMNg48aN+Prrr2s9JiEhAU5OTk1aLusDYhgG8fHx3NCNmvTr1w86OjpNWi4LQAzDqAzrhGYYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGIYRmVYAGojRCIRJBKJqqvR7KRSKUpKSlRdDaaJsADURqxYsQL9+vWDXC5XdVWaDRFh8ODBWLJkiaqrwjQRFoDagOjoaPzwww8YN25cg+eKeRnweDyMGzcOO3bsqHVGQeblwd6EbgNeeeUVREZG4unTp03+qnxrU15eji5dusDBwQEXLlxQ6YTqTOO1yT+XO3bsQLdu3WBkZAQjIyP4+/sjKCiI2x8YGIhevXrB0NAQ7dq1w/jx4xXOwdtYLVFOUFAQzpw5g02bNrX54AMAWlpa2Lx5My5dusSt8KCsH3/8EU5OTtDR0YGfnx9CQ0ObqZaM0ho8LVordvLkSTp9+jTFxMRQdHQ0LVu2jDQ1NSk8PJyIiEaMGEF79+6l8PBwevjwIY0ePZocHByouLi4xjyvX79O5eXl1bZHRERQRkaGwjQNKac+ysvLyd3dnQYPHkxyubxJ8nwZyOVyGj58OLm4uFBpaalSaQ4fPkxaWlr0yy+/UEREBL3//vtkYmJCmZmZzVxbpjZtMgApYmpqSj///LPCfVlZWQSAQkJCFO6XyWTUvXt3ev3110kqlXLbo6KiyMrKitatW6dUHWoqJykpiaZOnUomJiZkampK06ZNo+fPn9eZ33fffUd8Pp8ePXqkVPltSUREBAkEAqU/e19fX5o/fz73s0wmI1tbWwoMDOS2NfQ6MA3X5gOQVCqlQ4cOkZaWFkVERCg8RigUVlnKVpHU1FRycXGhadOmkUwmo9jYWLK1taUPPvhA6booKkcoFJKFhQWtWLGCoqKi6N69e+Tr60vvvvturXllZWWRsbExzZkzR+ny25qPPvqIDAwMKD09vdbjysrKSCAQ0IkTJ6psnz59Oo0dO5aIGn4dmMZpswHo8ePHpK+vTwKBgIyNjen06dMKj5PJZPTKK69Q375968wzKSmJHBwcaMqUKeTg4EDTp09X+tanpnKGDRtGX375ZZVtx44dI2dn51rz++CDD8jExISysrKUKr8tev78OZmbm9PMmTNrPS41NZUA0M2bN6ts/+yzz8jX15eIGn4dmMZpswGorKyMhEIh3bt3j5YsWUIWFhYKW0Bz5swhR0dHSklJUSrfkJAQAkAdOnQgiUSidH0UlZOYmEgASFdXl/T19bl/Ojo65ObmVmNeDx48IB6PR99//73S5bdV27dvJwB09+7dGo+pKwA19DowjddmA9B/BQQE0OzZs6tsmz9/Ptnb21N8fLxSeWRkZJC7uzuNGTOGrK2tacGCBUqlq6mcv//+m8zMzEgoFFb79+zZM4V5yeVyGjhwIHl4eCjsFFc3EomEunTpQn369KmxNVrXLVhDrgPTNNQmAA0ePJhmzJhBRBW/xPPnzydbW1uKiYlRKn12djZ17tyZxo8fTxKJhCIiIsjS0pI+/fTTGtPUVc6ZM2dIU1OTSkpKlD6PY8eOEQAKCgpSOk1bd/HiRQJABw8erPEYX1/fKn8wZDIZ2dnZUWBgYIOuA9M02mQAWrJkCYWEhFBCQgI9fvyYlixZQjwej86fP09ERHPnziVjY2O6cuUKpaenc/9EIpHC/GQyGfn4+NDo0aO5heOIiB4+fEhmZma0efNmhenqKic3N5fMzc1p4sSJ9PDhQxIKhRQUFEQLFy5UmJ9IJCKb9jbUf2j/Rnw6bdPwV4eTla1Vja84HD58mLS1tWnfvn0UGRlJs2fPJhMTE8rIyKj3dWCaTpsMQLNmzSJHR0fS0tIiS0tLCggI4IIPEdW4qqOilSQrnT9/nsRicbXtYWFhNfYfKVPOnTt3aNCgQWRkZESGhobk5eVFW7ZsUZjft99+S+CDjD81pmnHplGRuEi5D6QNE5WJ6PMLn5PTl07E0+BV60h+0datW8nBwYG0tLTI19eXbt++ze2rz3Vgmg4bivGSSE1NhVtHN2j6aYI3vGL4gb6WPub5zMMXfb6Ahob6rbC078E+bAndgjxxHgCg/Hw58oLzEB0dDQcHBxXXjlFGmxyK0RYtWbIEBvoGiD8aj7e7vQ0tgRZKykuw4eYGdNvZDf/E/KPqKraY2ym3MfTXofgq5CvkifOgq6GL97zew5ODT2BiYoLPP/9c1VVklKXqJlhDbd++nbp27UqGhoZkaGhIvXv3pjNnznD716xZQz4+PmRgYECWlpY0btw4ioqKavJ6hISE0Kuvvko2NjYEoNqTlqZw69YtAkC7d+/mtqXkpdCo30aRyVoTMg40JpO1JjR432AS5gibvPzWIr0onWYcn0FO3zuR43eO5Py9M808MZPSi/59EXHfvn0EgK5evap0vi1xDRnFXtoWkL29PdauXYv79+/j3r17GDJkCMaNG4eIiAgAQEhICObPn4/bt2/jwoULkEgkGD58eK2TWd24cUPhpF6RkZHIzMxUmKakpATdu3fHjz/+2DQn9h9yuRwLFy5Ejx49MGvWLG67vYk9zrx1Bn9O+hOOxo4gIoSlh8H/F3+8d/I9lEpLm6U+qlAuK8fXV77GoH2DcCXpCogIHc074vDEw/hl/C+wNrDmjn377bfRq1cvLFy4EDKZTKn8m/saMrVQdQRsSqoe74Va/no+efKERo0aRYaGhmRlZUWffPJJlSdqNdm/f3+t9a703a3vqP3m9mQcaEzGgcbUfnN7+uHWD3Xm39r9Ef4H+ez2IcfvHMnxO0fqubMn7X+wv9Y0N2/eJAD0008/1bu8mq5hQ68fU7s2EYBay3ivmr68YWFhZGhoSMuXLyehUEjBwcFkY2NDq1atqjW/wsJCsrGxocmTJytVvlgippl/zSSLdRZcIOq+ozuFJNQevFqjRxmPaPTvo7nA4/aDGy27uIxEZYpflfivN998k9q1a0f5+fn1KlfRNWzo9WPq9lIHoNY23qumAOTt7U3z5s2rsm3ZsmXcOKSaLF26lHR0dCgxMVGp8itFZ0fToL2DqvQPjf59NKXkKTfcRJVyRbn0wakPqMP3HcjxO0dy+s6Jph6dSkn5SfXKJyUlhfT09Gjx4sX1SqfoGjb0+jF1e6kDUGsb76Xoy/v06VMCQE+fPq2y/auvvqLu3bvXmFdcXBxpa2vTihUrlC7/v/6K/Is8tnpwrSHL9Zb00ZmP6nVOLUUqldLGGxvJc5sn1+oZ8MsACo4PbnCe33zzDWlqalJ0dLTSaf57DRt6/RjlvNQB6L9UOd6LSHEAOnbsGGlqapJMJquyffLkyfTWW2/VmNdrr71GdnZ2jZ68TCKR0Korq8h2oy0XiJy/d6af7yvuK1OF0zGnyf8nfy7wdN3elbaHbq/SF9cQIpGIHB0d6dVXX1U6zX+vYUOvH6OcNhWAVDHe60WKAtC5c+eIz+dXmbkvPj6eNDU1axzPdenSJQJABw4cUKpcZeSJ8+iNo2+Q2VozLhD12t2LQp+FNlkZ9RWdHU3jD43nAo/rFlf6OOhjKipruje8jx49SgDo7NmzSh3/32vYkOvHKO+lDUCtZbxXUVERPXjwgB48eEAAaPPmzfTgwQNKSqros8jPzyczMzNatGgRxcXF0aVLl8jT05PefvtthfkpM7q7MR6lPyL/n/3JJLCif8hsrRlNPDyRsouym7ysmhSVFdHCoIXkusWVCz6vHX6NorOVv1VSllwupwEDBpCnp2eNswfUdg3re/2Y+nlpA1BrGe8VHByssJzKlhgR0dWrV8nLy4t0dHSoQ4cOFBgYWOPtReX8Nj1W9KCo7KZ/cbLSgUcHyGWLC9cast5gTcsuLmvW/iGpVErb72ynrtu7coHH/yd/Oh2j+OFBU4jKjiLvr70JPNQ4tquua1if68fUDxsL1ork5eXB2cUZ4g5i6L6uCw2+Bka7jcb2UdthoGPQ5OVJpVIsvbwUvz3+jXtx0UrfCqsDVuP1Tq83aVlXEq5g5ZWVSCpIAgDoaerh3Z7vYpHfIggEgiYtCwBKpaWYd3oeTkWfgkQugfiEGFpRWkiIS4CFhUWTl8c0DAtArciiRYuwZ88eLD2yFLtidqGgtAAAYKBlgIW+C/FZv8+apdzM4kzM+WcOQpJCICc5eOChS7su2PnqTnRu17lReScXJGPJhSW49ewWCAQBT4BhLsOwJmANzHTNmugMqtp0cxO23NmCwrJCAICxtjE+8PgA66eux7Rp07B9+/ZmKZdpANU2wGrWWsZ6ERFt27aNHB0dSVtbm3x9fenOnTtNXkblKg9r164looqXCuecmkMW6/99qbDL9i50Xni+jpwa7nrSdfLa5cWVZ77OnN78880GTfshKhPRsovLyO0HN+52a/Tvo+lRevOt4HEp7hJ13d6Vq7/Fegt6/+T7JJZU3FZv3ry53quItOT3TB212gDUWtb2aon1pGpb5yohL4GG7h9a5aXC4b8Op4S8hCYr/792hO4gx+8cuV9ku012FHg1sO6E/7P/wX7qubMnF3h8dvvQH+F/NFt9U/JSaMRvI6p8RgH7A6oNzC0rK6OOHTvWax215l7bTd212gCkiCrGeimznhRR49aUOnXqFAGgv/76q8ZjgmKCqPOPnau8VDjn1Bzur3tTE0vENPefuWS53pIrs/OPnSkopuZHz3dS7tDQX4dygcd9qzuturKKyqTNM2ZKIpHQgtMLqtSx07ZOdCr6VI1pTp8+TQDo+PHjDSpT0feMrSfWcC9FAFLVWC9l1pOqLLuha0qVlZWRm5sbDR06VKm/yoFXA8lukx33C+fwnQPtCN1RZ7qGSsxLpOG/Dq/Suhi6f2iVFlh6UTrNPDGTnL93rhg+8b0TzTg+o8o0GU1t191d5PS9E/c52G6ypW9DvlXqKd6oUaPI2dlZ4RPPuvz3e8bWE2ucVh2AVD3WS5n1pIgat6bUxo0bic/n1xo4/6tIXERvH3+bzNeZc7+AXju96FrSNaXzqK/zwvPUZXuXKv0r7/79Lq24vII8tnpwrZ6A/QF0K/lWs9XjZvJN8tnlw9XDbK1Zvaenffr0KWloaNCaNWvqVbai7xlbT6xxWnUAUvVYL2UCUGPWlMrIyCAjI6Mqt3j1EZ4ZTv1/6c+9VGi61pTGHhzbrC2P9dfWk/0me9JfrU8aX2uQ5ipNslhnQT129qC9YXubrdzsomwaf2g89ya3SaAJ9dvTj8IzwxuU36JFi0hfX59SU1OVTvPf7xlbT6zxWnUA+q+WHuulzC1YY9aUeu+998jU1JRycnKUqntNjkYcpY4/dORaBVYbrGjxucXN8lJhUn4SzTs9j9qtb0caX2uQ4GsBGaw2oBG/jWiWJ1wSiYQ+P/85WW+w5s7PbYsbHXp8qFH5Pn/+nCwsLKq8MFobRd8ztp5Y471UAUgVY71qW0+KqGFrexER3b9/n3g8Hm3durVe6WoikUho2cVlVX5RXba40IFHTTOerKisiDbd3EQB+wNo4N6BNGjvIBq6byh5bPUgm4025PidI3X4vgN9cOoDyhXlNkmZhx4fItctrlXe1l56YWmTBdadO3cSgFpfq6jte8bWE2u8VhuAWstYr9rWkyKq/9peRBVf6n79+lHnzp2bvJWSU5xDEw9PrDLotPfPvelh2sMG5SeTyehYxDEae3AsDdw7kAbuHUjv/v0uPcp4ROdjz9PK4JX0bci3NOCXAVw/kOc2T9p4Y2ODhys8Sn9Effb0qTJebcKhCU0+Xk0qlVK3bt3Iz8+v2mj3SrV9z9h6Yo3XagNQaxnrRVT7elJE9V9T6siRIwSANv66sZZPoHFCn4WS727fKp21k/+YTHniPKXzuJ92n9756x0u8Iw/NJ7+jvqb+2WNzIqklcEracfdHSSVSmlH6I5GjfMqEhfRtGPTqgRPn10+dDP5Zt2JG2jNr2sIAP32228K99f1PWPriTUOG4rRwkQiEVw6uqDQtBDt3muHPvZ9sH7oetgZ2zVLefse7sOqkFV4Ln4OANDX1Mds79lY3m95jWuJZRRn4Ic7P+BWSsXwCU2+Jsa6j8W7Pd+FnpYed1xRWRE23doEHnhY2n8ptARaKC4vxorLK/BPzD+QyCsm+O9p3RPrhq5DR4uOCsuTSqVYd3Mdtt/bjpLyikUDTHVNsbTvUsz2md2UHwcnNjcWc07Pwf30+yj5vQRGWUZIjEuEgUHTj7ljasYCUAtbtWoVvvn2G/gH+iNZkAwA0BZoY3LnyVgxcAW0BFpNXqZUKsXii4tx6MkhlMnKAADWBtZYF7AO4zzHcceVSkux98Fe/BX1F3ecr50vPvL7CPZG9grz3nxrMwrLCvFOj3fgZOLEbY/JicHSS0txP/0+AECTr4lXOr6Cb4d8CwOtf3/J/4n5B59f+BxpRWkAAC2BFqZ0noLvhn/XLIstlkpL8VHQRzgRdQISWUWAtJZYI2F1Aj7/7HN88803TV4mUzMWgFpQSkoK3N3d8eGHH2LdunU4+OQgNt3ahFxRLgDAXM8cn/p/imldpzVL+RnFGXjv7/dw89lNbtBpd+vu2PnKTsTnx2NP2B7kiivq4mDkgAW+C+Br71trnn9E/IHI7EgM7TAU/Rz6Vdt/RngG34Z8i7TiigBjpG2Eud5zEeAcgDln5uBBxgMQEXg8Hvzt/LH71d2wN1Ec7Bpr6+2t2HhrIwrKKgb5GmoZYlHvRfi0z6f4v//7P2zcuBFRUVFwcnJqlvKZ6lgAakHTpk3D5cuXERMTAyMjIwAVa16tvroah8MPc60ODwsPrB6yGt623s1Sj6uJV7Hw7EIk5CdATnLISQ4zXTM4GjnCRNcEb3d/G697vg4+v+5l426m3MT5uPPwtPDElC5TFB4jk8nw/Z3vsefBHpSUlyC/NB+lslJo8jWhwdeAo7EjvhvxHYZ0GNLUpwqg4nwXnV2E+Px4ABWtsVc7vopto7ZBX1sfAFBcXAx3d3f06dMHR48ebZZ6MNWxANRCbty4gX79+mHPnj1VFhislFqQiiWXluB6ynUQEfg8PgKcA7BmyBpYGlg2eX3EEjEWnV2EQ+GHUC4rBwCY65pj5cCV9ep3SS5Ixi8PfoGhliE+8f8EPB6vxmP33N+DJZeWIK+0Yi13AV+AQU6DcOC1A7DQa/o5empq8e16dRfcLdyrHf/777/j7bffxpUrVzBw4MAmrw9THQtALUAul8PXt+JWJjQ0tNaWxY3kG1hxeQX311pXUxfvdH8Hi/0XN8nEXXKS417aPQQnBEMsFaO0vBQnY04ioyQDBloG4PP4cDd3x5qANUq1wCQyCQKvB0JOcnzc+2MY6xhXO+Zh+kMsvbQUT3OeQk5yFJQWwEDLAE4mTtAUaEJHQweTOk2qWPNeo/F9YDX1ea0ZsgavdXqtxnRyuRx9+vRBaWkp7t+/3ywTpTFVsQDUAvbu3YtZs2bh+vXr6Nu3b53Hy2Qy7H20F1vvbOX6K6wMrLC833KM9Rjb4HrE58XjbOxZZJVkAQDa6bfDKNdROB93HlHZUYjLj8OTrCdcC2yw02CsDVhbZwts171dSC9Ox+TOk9HJshO3Pbs4G8uDl+Ni/MWKFgiPhy6WXeBm5gZXc1d0s+qG7Xe3cx3Q7fTb4QPvDxDQIaDB57jv4T58E/IN15elp6mHD7w/qPWp34vu3LmD3r17Y9euXZg9u3mewDH/YgGomRUWFqJjx44YMmQIDh48WK+0xeXFWHVlVcUTm/890u5u1R1rh66Fp6Wn0vk8Fz+vCDI5UQAqfikHOw2Gt603+Dw+zsWew61nt+Bj6wNTHVN8Gfwl4vLiAFS0wKZ3m47P+3xeY4vgdMxp3E27iz7t+2C4y3DIZDJsvLUR+x/th0giAgA4mzjj60Ffo1hSjNDUUPja+WK022hI5VIcfnIYB8MPcsd2btcZi/wWwc3cTelzDEsLw9zTcxGdGw0AEPAEGNphKHaN2QUTHROl8wGAGTNm4MyZMxAKhTAxqV9apn5YAGpmX3zxBbZu3Yro6Gi0b9++QXnEPY/DkktLcC/1HggEDb4GRrmOwreDv4WxbvVbnkpl0jJcS76GWym3ICMZ+Dw+etn2wiCnQdDV1OWOi8qJwuHww7DQs8AC3wWQyWTY92gftoZuRX5pPoCKFtiSvkswwXNCtXIeZjzEX1F/wcHYAe3022H11dVIL04HUDEd6gLfBZjVYxYEAgF23N2BzJLMaq2l/NJ8/Bj6Iy4lXIKc5FwAmdtrbq0BJL80H++ffB+XEy5DRjIAgKeFJ34c/SO8bL3q8zFz0tLS0LFjR8yePRubN29uUB6MclgAakaxsbHo3Lkzli1bhpUrVzY6v/Nx57EqZBWeFT4DUPEYebb3bMzzmVeldUJEeJjxEJcSLqG4vBgA4GLqgpGuI2GpX/12SiwRY/2N9SAQFvdZzL2nIy4XY+WVldVaYKsDVqNLuy5c+hxRDlYGr8TlxMsQlYvA4/GgydfEOPdx+GrwV1x+IokI62+sBwB81ucz6GvpV6tLdE40ttzZgsjsSAAVL05O6zYNUzpPgQb/31soqVSKr0K+wp4HeyCWigEAFnoW+HLgl5jefXoDP+F/BQYG4ssvv0R4eDjc3at3WDNNgwWgZjR+/HiEhYUhKioKenp6dSdQgkwmw9a7W7EnbA+KyosAAO2N2uPLgV9imMswpBSkICg2iOtXMdM1w0jXkXAzc6v1CdXOezuRUZyBSZ0mVZuIPiEvAUsuLkFoaijXAhvpOhKrB68GAKwIXoED4Qcgk8tgqGUIP3s/rBu6Di5mLlXyeZr9FEcijsBSzxLzfefXep4X4i5gd9huZJdkAwDsDO0wr9c89HXoi+ORx7H00lJklmQCAHQ0dPB2t7cROCSwyV5eLC0tRadOneDp6YnTp083SZ5MdSwANZMLFy5g+PDhOHz4MKZMUfx+TGMUiAuwPHg5zsaehVQuBQA4GDvA184XBloG0BZoY6DTQPjZ+UHAr/tpTpAwCHdS76CXbS+80vEVhcdciLuAVSGrkFKYAiKCnOQAD+CDjxJJCXQ1dPFZ38/wkd9HCtOfjT2L289u11rGi8ql5dj3aB+OPz2OUmkpymXlSC1M5Trm+Tw++jv0x64xu2BtYF1nfvV1/PhxTJw4EadPn8bo0aObPH8GqPtNM6ZBZDIZpk6dismTJzdL/sa6xtg2ehv+euMvdLXqCqDinZwTUSeQVZKFD7w/QJ/2fZQKPgC4YRSJ+Yk1HjPMZRiuzLiC93u+D4lcgqLyIhSVFaFcVo5XO76KmT1mor1Rzf1clXk7mjgqVSctDS3M9p6Nn1/9GQZaBojKieKCj4upC05OPYm/p/7dLMEHACZMmIC33noLcrm8WfJnWABqNiNHjsTBgwdrve1pCl3adcGpqaewoNcCGGobQiaXITI7ErP/mY3zseehbAO3Mihki7K5AaH/VVJegqC4IEhJilFuo6CroQtdDV2Mch0FIy0jCJ8LEZ8XrzCtWCJGZnHFLdOLY8bqcin+EhadX4TE/ETISQ4tgRZmdJ+B+x/cVzj0oynxeDz89ttvePXVV5u1HHXW9KP9GJWY5zsPRWVFCM8OR7GkGDmiHKy5vgYnok5gYe+F8LDwqDW9nqYe2um3Q1ZJFpIKkqo8oZLJZQhNDUVIUgi3gqqZrhkGOg0EEcFczxxaAi08znqMM8Iz8LLxwgDHAVVaX8kFySAQLPQsqgxGrYkwV4jv73yPiKwI5JfmQywRw8bABkOch+DbId828FNiWhsWgNoIPU09tDdpDx6fhyHOQ3Az5SYuxF3A05ynmHd6HoY4D8E8n3kw06t5NVInEydklWQhMT+RC0DCXCHOxZ1DjigHAGBjYIMRLiNwNPIopDIpF1SmdJmCuLw4ZBRn4ETUCYRnhWOE6wiu85u7/TKu/fYrvzQfO+7uwIX4C5CTHCKJCOa65uhp3ROaAk10s+oGQ23DpvnQGJVjAagNcTNzw7PCZ0gvSseSfksw0XMifrjzA55kPcHF+Iu4mXITb3R5A9O6TqvySLuSk4kTQlNDkZSfhBxRDs7FnoPwuRBAxePwgA4B6GHdAxnFGSiRlMBCzwJ8Hh9iqRgCngCTOk3C+bjzkMgkyBXn4uCTg9zj/8oAVNPtV+ULiYfCD6FEUnEL2N64Pcx0zGCqawqZXAYBX1CvlxOZ1o8FoDbEzdwNwYnBiM+Lh0wug5u5G7aO3orLCZex895OZJVk4ZcHvyBIGIR5veahv2P/KukdjR0hlUtxI+UGUotSocHXgIAngJ+9HwY4DoCOhg6AilZRZXl8Hh/hWeEQPheivXF72BjawMnECfZG9riVcgtxeXH44c4PSMhPgKOxo8IAdCP5Bn68+yP36oClviXe6PwGYp/HQiwVw8PCAwl5CSiTlcHNjAWgtoR1QrchNgY20NfUR5msDMkFydz2Ic5DcOC1A5jefTp0NXSRXpyOFcErsOjsIiTkJQCoGKQalROF8KxwPCt8hjxxHtzN3TGv1zwMdxnOBR8AXKvI1cyVCwjCXCHsDCtmdcwszkSAcwDm+86Hh4UH8kvz8azwGcKzwhGVE1Xx+B5AUn4SPjn3CZZfXo60ojToaOjgrW5vYfcru/Gs8BnEUjHsjezRy7YXymRl0NXQhZ1R88wcyagGawG1ITweD27mbniY8RDC58L/Z++8w6Oqtjb+numTZJJJr6QHEpJQQkhAUJAqHaQpXvGqVwTBhg316vV6lWa5IEXFguhFVLAgvQlI7yWFkF5I7236zPr+mC9HQibJJAQCmf17Hp6HnLPbmZN5s/fae62FIOcg/p5YKMYTfZ/AxB4Tseb0GhzKPoSLRRfx1Lan0NerL3wUPqjWVsNObAedUYd+Pv3wcPTDTfqo19UjvyYfgHnJJ+DMf8MK6wphL7aHSCCC2qBGhboCrnaueCjqIWgNWmRWZsJObIcdaTtwJPcIiuqKcL7wPAwmAzhwuDfgXizovwDOcmdsuLQBlZpKOMuc8XDUwzh57SQAs+A19MfoGrC32cW4fkZiCTc7N/xr6L+wauwqdHfpDoPJgDMFZ7ArfRcq1ZUYFzYOsT6xfIygG8mozACB4OXgBYVUAXuJPT/zyarKgreDNwAgvzafr6Mz6hDrE4tx3cehSlOFPel7cDr/NAwmA0JdQvHJmE/w7v3vwt3eHb+m/IprNdcgF8nxSK9HYC+x52dczP7T9WAC1MUIdg6GgBOgVFXKO5JaIsojCp+N/wzz+8+HTCSDzqiDRChBhboCdbo6FNcVQ61XN6nH23+us8U0CENaRRofO7rBX01r0KKgtgC1ulpUqishEUqgNWohE8nwdL+nsW78Ov4g5b7MfUguTYaQE+KhqIfgZueGGm0NiuqKwIFDiHNj1w7G3Q8ToC6GXCznTyM3NwtqQCAQYHrkdEzuMRnBzsGo19ejXF2OlLIUJJcm8w6hDZjIhPSKdACNZyMNYpRRkcGfSm4QoOTSZFwpu4Lk0mSUq8tRp6tDkDIIE3tMxMPRD/PB2c7kn8HxvOMAgMnhk/mDkQ39+Tr6WnReZdzdMAHqglw/I7GG3l694e/kj0j3SPT16gsnqROK64ux+vRqHMk5wvua5dfkQ21QQy6SN8qS4aPw4Y3fDSe/C2oLcCj7EFadXoWiuiIoZUr09uyNXh69EKAMQG/P3nz91PJU7EzbCcBsMG+YEQGWZ1yMrgMToC5Iw5c1qzKLF4+WiPKIAgcOpapSDAkcgidjnoSj1BGlqlIcyDqA1adXI7k0GanlqQCAEJeQRsZgjuMQ6hIKACisKYRKr8LJayexNWUrylRlcJQ64vE+j2N48HDeg70hnEdhbSG2JG8BgdDXqy/u9f/raIDRZOQDozH7T9eECVAXxMPeA45SR+hN+hadSxtQSBX8kiepJAlxvnHo69UX/k7+kIvkqNJU4aekn/Dt5W9Rp6uzOBsJcw1Dva4e3yd+j4zKDGgMGmgNWvg5+qGvV1/E+8UjqSQJBIK/kz+cZE6o1lTj+4TvoTPqEOwcjPHdxzfyncutzoXOqIO92J43bjO6FkyAuiAcx/Ei0WBDaY2GGUlCSQIcpY5wtXOFh70HxnUfhyEBQ2A0GZFTlYNzBeeQVpHWyGFVpVchrTwNZwvOIqc6B/ZiewQ4BaC7a3d42HvAWe4MpUyJxJJEvi+NQYONCRtRq6uFh70HZkTOaOK5f/3u16126mV0DuwcUBclzDUM5wrPIa08DQ+EPtBq+Z7uPbEzbSeK6opQpipDoDIQFeoKFNQWYFTIKAgFQqSUpUBj1CCxJBHpFem4L+A+cODwZ86fUBvUcJQ6QiKUYHTwaKRVpiG1IhUe9h4IcApAhboC+bX54MChh2sPbE7ajJL6EjhIHPBI9CONDjo2wOw/XR8mQF2UIGUQhJwQ5epylKvK4Wrn2mJ5O7EdQpxDkFaRhsSSRAQqA3G+8DxyqnIAmHNsRXpEItwtHNWaahTWFWJvxl6+vqe9J2b1moUrpVdgJCM4cLhWcw1KmRKBykB+9hOkDMLhnMPIqMyAWCDGrOhZFlP5VGmqUKoqBQcOwc7BHfjJMO4k2BKsiyIVSeHv5A+gHcuw4gT4O5rrFtYVQqVTIaPCbAy+1/9ePNXvKUzs8Vd6oCBlEJ6OfRr3+d8HALhWew2OMkfU6mpRo61pJEB6kx7nC8+DA4dpPafBR+FjcSwNs59uTt0aBdBndC2YAHVh2rodH+4WDpFAhHJ1OdQGNZxlzjCRCafyT0Fr1MJebA8fhQ8EnAAx3jEYEmDOHmowGSDgBObT0RIFdEYdVDoViAgGkwE6ow4l9SUoqy9DVpXZ92xM2BiL2Ukb4O0/bPnVpWEC1IVp+PJmV2U361pxPVKRFN1duwMAvwwDgNP5pwGYfbGuNwbH+sRCyAmRV5OHgtqCRtvxRfVFAMx52BNLElGtqUaJqgQigQgD/QYizjeu2XEYTAbeSZZtv3dtmAB1Ydzs3KCUKWEwGazajgf+WoYlliTyS7jLJZcBNBUDhVTBZ9BocBhtKFNQYw6tYSITTuSdQEJJAtzs3BDhFoGRISNbHEN2VTb0Jj0UEgU87T2tGjfj7oQJUBfm+u341twyGghzCYNUKEW1thoigQgagwYFNQUwkcmiL1a8bzwA8/mhWm0tgp2DQUSoUFfARCZoDVr8kf0HTGRCtEc0Hox4sFWP9uvjDbHt964NE6AuzvV2IGsC1IuFYj5+dF5NHnRGHQgEO7GdRWOwr6Mvujl2g5GMOFtwFjKRDA4SBxAIEqGEX34FOAXg0d6PQiwUtzoGZv+xHZgAdXGClEEQCUSo0lTxcZ1bo2EZ1nByGQBkwqbndBqI9zPPgs4WnIXBZIBMJAMRoUZbg1JVKQwmA+b0m2NVMPpyVTkq1BUQckK2/W4DMAHq4oiFYt6YbO1uWLBzMOzEdqjV1aKs3ixaDVEMLRHhFgFHqSPq9fW8aFVqKlGlqYKRjPBw8EB/3/5W9d1wZMDfyR9SkdSqOoy7FyZANkBb7UBCgRA93XuiWlONen09JEIJ6nR10Bv1zZbv72MWmGO5x5Bckmx21SDAQeIAdzt3GE1Gq/pmwcdsCyZANkDDlzm3Ohdag9aqOlEeUShXl6NaUw0fhQ9MMCGvJq/Z8v18+kEkEOFUwSlcKr4EhVQBB6k5RbS7nTsfcL4ldEYdv1vH7D+2ARMgG8BF7gJXuSuMZGw2c+mNBDgFQKVXwUhG/rRyS1v5dmI7dHPshoTiBFRrqxHqEgqFRAEBJ4CTzIkPUNYS2VXZMJgMUMqUcLNzs2qcjLsbJkA2QltPRVdqKmEntgMHDvZicyTCBr8wS9Roa5BVlQWV3nwCOtw1HAJOAGe5MzQGjVUCdL3zKdt+tw2YANkI19uBrNmOT69Ih4e9B5xkTuaZkMmIazXXLNqBtAYtNl7eCBOZIBPJ4GrnisyqTChlSvg7+qNcVY782vwW+yUiZv+xQZgA2QgBygCIBWLU6mr5qIQtkVaeBoVEgUBlIJ9qx0jGJjMZo8mIzcmbUVxfDCMZ0d+nP0xkQnFdMbo5doOXgxeqNFWo09WhWlvdbH9lqjJUaaogEoiazZ7K6HowAbIRRAIRf66mtd0wvVGPrKoscByHe/3vBcdx/C5WTvVfyzAiws60nUivSIdYIEY/737wUfhALBDDSEb4OflBJBTBYDLwM6jmaJj9BCoDIRFKbvZxGXcJTIBsCGvtQA3GYCepEwb7DwYAqPVq6I2NQ7wezzuOc4XnwIHD1J5TUa+rh5GMcJY7AzDPjpxlznCQOKBSU9myALHgYzYJEyAbosFTPa86z2LOrwaut8V4OnjC094TjjJHlKnKcK3mGgwmA5JKkrAvcx8A4IHQBxDmEobc6lw+mqJCqoDGoIFCqoCj1NFsB6rJt9if1qDlZ1bM/mNbMAGyIZQyJTzsPUCgZrfjiajJbCTKIwpykRzV2moYTAacvnYav6b8CgAY4DcA8X7xKKgtgN6kR5Wmyjxz6jYYHMehSlMFhVTBh3e1dCAxszITJjLBVe4KF7nLLXp6xp0IEyAbg98Na2YZVq4uR6WmEkJOyOeWj/KIAsdxMJEJ1ZpqfHv5WxhMBoS7hWNUyCgAZtuQzqiDiUzgOA5TIqYAAKrUZsOykYyo1lajqK6oSZ9s98t2YQJkYzQsw5rbjm+Y/VxvDHaWO8PP0Q/2YnucvHYSRXVF8FH4NAqtkV2VzecA83bwRnfX7ghzCYNAIIDBZICj1JEPTH8918+4GsbGsB2YANkY/k7+kAqlqNfXo7CusMn95mYj4W7hKKwrRIW6AnqjHjMiZ/ACZTQZkVudi+K6YihlSj6zaYOXfJ2uDvZie5SrypsYoovri1Grq4VYIGbb7zYIEyAbQygQNrsdrzPq+NPO1+9GERGyKrOg0WtgggkhLiGo0dbw9wvrClGrrYVKr4K92B6R7uYoiSHOIXCzc4NCooDWqOVPS19PwxiCnM1hQxi2BRMgG6S57fjMykwYyQgXuUsjY/AfWX8gozIDLnYuCHMOQ52urtF2fE5VDkrqS+Akc0KAMoBPs8NxHOJ94yEXy6E36mEiEzIqMqDSq/i6LPiYbcMEyAZp+LLn1+Q3ynBqyRfrXME5HMk9AgCY1nMafB19UVJf0sgvLLsq2yxAUidEe0Q36qu3V2/IRDLYS8z+ZBXqCn47Xq1XI6/a7GHPDNC2CRMgG0QhVcDLwQsEQkalOd+XJV+s9Ip07EjbAQAYEjAEU8KnwFnujDpdHa6UXYHRZISJTEgpS0GtrhYuchf0dO/ZqC+JUIIY7xi4yl2hMWhQoa7gRSejMgMEgrudO5Qy5W16esadBBMgG+XGIGUl9SWo0dZALBAjwCkAxXXF2Jy0GSYyobdnbwwNHAq5WI7enr0hFoiRX5OPgtoCFNUV4VrNNYgEIkR5RPEzneuJ842Dk8wJJjKhTleHpNKkRn2z2Y/twgTIRrl+lmMiEz/7CXIOgtqgxsaEjdAatQhUBmJij4n8kizaMxpOMieU1JcguyobWZVZKK4vhpPUCb29elvsSylTItI9Eh72HqjV1iKxJBEmk4kPv8rsP7YLEyAbxc/RD3KRHGqDGvk1+bwYBDgF4PuE71GjrYGbnRtmRs6EUCDk64W7hcNN7ga1QY2LRRdxufgyVHoVXOQufDYNS8T7xcNb4Y16fT2u1VxDclky6vX1kAr/SiHNsD2YANkoAk6AEBdznq+k0iTkVueCiJBUkoSiuiLYi+3xSPQjTVLxSIQS9PPpBwA4V3gOZwvOAgD6eveFTNR85owApwBEukdCLBSjoLYAf+b8CcAcAP96gWPYFkyAbJiGpc+JvBMwmowori9GQV0BxAIxZkXP4r3ab2RQt0EQCUTIqshCRkUGhAIh7vW/t8W+OI7D0MChcJI6oUZbgyM55p01Zv+xbZgA2TANrg9Xy68iszITdbo6cODwYMSD8HX0bbZed7fucJO7oVxdjlJVKVzlri0uvxqI9oxGgGMA9CY9n+6ZuV/YNkyAbBh7iT18HHyQW52L5NJkuMpdMSpkFCLcI1qsJxKI0Me7D6p11ajR1ph3xqzIeCoSiPBA2AMwmAwoqSuBm9wNjlLHjnocxl0IR9YECGbcFWzcuBFbt25tU52C2gJcLr4MASdAlEcUnwGjNcpV5TiWdwwAMNBvINzt3a2qpzPqsDdjL4wmIyI9Its8Axo7diz+/ve/t6kO486FOd90IdRqNaqrm4+7bAmZSYZ+Lv1Qra2GncnO6vpCEiLGOQYcOIj14jb1G+kYCQeJA4ScsM3jVaubD6TGuPtgMyAGg9FpMBsQg8HoNJgAMRiMToMJEIPB6DSYADEYjE6DCRCDweg0mADZINXV1ZgzZw5CQ0MRERGBwsKmsaEtYTAY8P7772PgwIGIiYnBY489hn379t2y/hhdHyZANsj8+fORkJCA5cuXIycnhz9b8+KLL2L16tXN1lu0aBHWrl2L4cOHY/LkydBqtRg/fjwef/xxixk2brY/hg1ADJvDxcWFzp8/T0REDg4OlJGRQUREu3btotjY2GbreXt70+HDhxtdy8zMpJ49e9Ly5cs7vD9G14fNgGwQIoJCoWhyPSwsDGlpzeeNr6+vh5+fX6NrQUFBWLVqFdatW9fh/TG6PkyAbJAxY8Zg48aNTa7X19fzkQ8tMXjwYGzYsKHJ9aCgIBQUFHR4f4yuD/MFs0GWLFmC2NhYAObZCcdx0Gg0+M9//oOYmJhm6y1btgyDBg1CZWUlnn32WYSFhUGv12PVqlXo2bNns/Xa2x/DBujcFSCjs0hLS6NRo0YRx3Hk5uZGUqmU3N3d6cyZMy3WO3/+PMXGxhLHcSSVSkkkEpGbmxsdPXr0lvTH6NowZ1QbJzc3F5cuXYJYLEZ8fDycnS1HQbyRq1evIikpCQqFAvHx8XB0tC6uT3v7Y3RNmAAxGIxOgxmhbYyysjIsX74cU6ZMwcCBAzFw4EBMmTIFH3zwAUpLS9vVZl5eHp544gmL99RqNY4ePYrk5OQm9zQaDb799tt29cnoGrAZkA1x5swZjB49GnZ2dhgxYgQ8PT0BAMXFxThw4ABUKhX27NnDG4yt5dKlS4iJiYHRaGx0PTU1FaNGjUJubi44jsPgwYPxww8/wNvbm+/Xx8enST2G7cAEyIYYMGAAevfujc8++6zJ9jcRYe7cubh8+TJOnDjR6N7vv//eYruZmZl46aWXmgjJlClToNfr8c0336CqqgovvPACkpOTcejQIfj7+zMBYjABsiXkcjkuXLiA8HDLGSxSUlLQt2/fJmFPBQIBOI5r0d2C47gmQuLp6Yn9+/cjOjoagFnknnnmGezcuRMHDx6Evb09EyAbh9mAbAgvLy+cPn262funT5/ml2XX4+3tjV9++QUmk8niv/Pnz1tsT61WQyT666gZx3H49NNPMWHCBAwZMgSpqak3/1CMuxp2ENGGePnllzFnzhycO3cOw4cPb2ID+uKLL/Dhhx82qdevXz+cO3cOkyZNsthuc7Oj8PBwnD17FhERjdP8NDigTpw48WYfiXG30xmHjxidxw8//EDx8fEkEomI4zjiOI5EIhHFx8fTjz/+aLHOn3/+Sbt27Wq2zbq6Ojp06FCT64sXL6YxY8Y0W2/evHnEcVzbH4LRZWA2IBtFr9ejrKwMAODm5gaxuPXEggxGR8MEiMFgdBrMCM1gMDoNJkAMBqPTYALEYDA6DSZANkZJSYnFrXYAWLlyZbOBxW53PYaN0LmbcIzbTXJyMnl5edEzzzzT6PrLL79Mbm5udPHixTuiHsM2YAJkg6SkpJCvry89/vjjZDQa6dlnnyVPT0+6dOnSHVWP0fVh2/A2SkZGBoYPHw6xWAyVSoX9+/c3ObF8J9RjdG2YDchGCQkJwcCBA5GRkYH+/fujR48ed2Q9RteGCZANQkT429/+hpMnT+Lw4cO4evUqZsyYAYPBcEfVY9gAnboAZNx29Ho9TZ8+nUJDQyk3N5eIiIqKiigqKoomTJhAWq32jqjHsA3YDMjGOH36NNLS0nDkyBF069YNgDluz8GDB1FUVIQjR47cEfUYtgEzQtsg9P+5uay93ln1GF0fJkAMBqPTYEswBoPRaTABYjAYnQYTIAaD0WkwAWIwGJ0GEyAbo72ZSm93PYaN0FkHkBi3n6tXr1JAQABxHEcCgYDuu+8+Kigo4O8XFRWRQCDo9HoM24HNgGyI1157DVFRUSgpKcHVq1ehUCgwaNAg5Obm3lH1GDZEZysg4/bh4eFBly9f5n82mUw0d+5c8vf3p4yMjGZnJLe7HsN2YDMgG6K9mUpvdz2G7cAyo9oQ7c1UervrMWwHNgOyIaZMmYJNmzZZvLd69Wo8/PDDFlMs3+56DNuB+YIxGIxOg82AbIwrV65g/fr1SElJAQCkpKRg3rx5eOKJJ/DHH3/cMfUYNkKnmsAZt5Vdu3aRRCIhFxcXkslktGvXLnJ3d6cRI0bQsGHDSCgU0oEDBzq9HsN2YAJkQwwcOJDefPNNIiLatGkTOTs70xtvvMHfX7RoEY0cObLT6zFsByZANoSjoyOlpaUREZHRaCSRSETnz5/n7yckJJCnp2en12PYDswGZGM0RCAUCASQyWRwcnLi7ykUClRXV98R9Ri2ARMgGyIwMBBpaWn8zydOnIC/vz//c25uLry9vTu9HsN2YAcRbYh58+bBaDTyP0dFRTW6v2vXLgwbNqzT6zFsB3YOiMFgdBpsCcZgMDoNJkAMBqPTYALEYDA6DSZADAaj02ACxGAwOg0mQAwGo9NgAsRgMDoNJkAMBqPTYALEYDA6DSZADAaj02ACxGAwOg0mQAwGo9NgAsRgMDoNJkAMBqPTYALEYDA6DSZADAbDas6cOYMFCxYgMjIS9vb28Pf3x4wZM9qdZpsFJGMwGFYzbdo0HDt2DNOnT0evXr1QVFSE1atXo66uDidPnmwS9bI1mAAxGAyrOX78OGJjYyGRSPhraWlpiI6OxrRp0/C///2vTe2xJRiD0Qnk5+fjySefhI+PD6RSKYKCgjBv3jzodDoAQGZmJqZPnw4XFxfY2dlhwIAB2LFjR6M2Dh06BI7j8NNPP+H999+Hn58fZDIZhg8fjvT0dL7cggUL4ODgAJVK1WQcDz/8MLy8vBrF7m6Je+65p5H4AEBYWBgiIyNx5cqVtn4MLCg9g3G7KSgoQFxcHKqqqjBnzhyEh4cjPz8fW7ZsgUqlQmVlJe655x6oVCo899xzcHV1xYYNGzBx4kRs2bIFU6ZMadTe0qVLIRAI8PLLL6O6uhrLly/HI488glOnTgEAZs6ciTVr1mDHjh2YPn06X0+lUmHbtm34+9//DqFQ2O7nISIUFxcjMjKyXZUZDMZtZPbs2SQQCOjMmTNN7plMJnrhhRcIAB05coS/XltbS0FBQRQYGEhGo5GIiA4ePEgAKCIigrRaLV925cqVBIASEhL4Nn19fWnq1KmN+vrpp58IAP3555839TzfffcdAaCvvvqqzXWZADEYtxGj0UiOjo40adKkZst0796d4uLimlxfsmRJI2FpEKDly5c3Knf+/HkCQFu3buWvvfDCCySXy6m2tpa/NnXqVPL19SWTydTu57ly5Qo5OjrSwIEDyWAwtLk+swExGLeR0tJS1NTUtLhblJOTgx49ejS5HhERwd+/nuuTPQKAs7MzAKCyspK/NnPmTKjVavz+++8AgLq6OuzcuRPTp0/ns9e2laKiIowbNw5OTk7YsmVLu5ZxTIAYjLuc5r74dN0G94ABAxAYGIiffvoJALBt2zao1WrMnDmzXX1WV1djzJgxqKqqwu7du+Hj49OudpgAMRi3EXd3dzg6OiIxMbHZMgEBAbh69WqT6ykpKfz99jBjxgzs3r0bNTU1+PHHHxEYGIgBAwa0uR2NRoMJEyYgNTUV27dvR8+ePds1HoAJEINxWxEIBJg8eTK2bduGs2fPNrlPRBg7dixOnz6NEydO8Nfr6+uxbt06BAYGtvsLP3PmTGi1WmzYsAG7d+/GjBkz2tyG0WjEzJkzceLECWzevBkDBw5s11gaYNvwDMZtZvHixdi7dy+GDBmCOXPmICIiAoWFhdi8eTOOHj2KRYsWYdOmTRgzZgyee+45uLi4YMOGDcjKysLPP/8MgaB984aYmBiEhobizTffhFarbdfy66WXXsLvv/+OCRMmoKKiosnBw7/97W9ta7Dd5m8Gg9FucnJyaPbs2eTu7k5SqZSCg4Np/vz5/HZ6RkYGTZs2jZRKJclkMoqLi6Pt27c3aqNhF2zz5s2NrmdlZREAWr9+fZN+33zzTQJAoaGh7Rr3kCFDCECz/9oKc8VgMBidBrMBMRiMToPZgBgMBurq6lBXV9diGXd395ty2bAEEyAGg4EPP/wQ//73v1ssk5WVhcDAwA7tl9mAGAwGMjMzkZmZ2WKZwYMHQyaTdWi/TIAYDEanwYzQDAaj02ACxGAwOg0mQAwGo9NgAsRgMDoNJkAMBqPTYALEYDA6DSZAXYT9+/fjyy+/hMlk6uyh3DKICN988w12797d2UNhdBDsHFAXQKfTITo6Gn5+fti/f3+7Q2ze6dD/x8pJTU1FcnIypFJpZw+JcZOwGVAXYM2aNUhPT8eKFSu6rPgAAMdx+Pjjj5GTk4MVK1Z09nAYHUCXFKBPP/0UvXr1gqOjIxwdHTFw4EDs2rWLv79kyRL0798fCoUCHh4emDx5ssUQmDfL7einpKQE//73v/H0008jOjq6Q9u+E4mIiMCCBQvw3nvvobCwsE1116xZg8DAQMhkMsTHx+P06dO3aJQMq2lXVKI7nN9//5127NhBqampdPXqVXrjjTdILBZTYmIiERGNHj2a1q9fT4mJiXTx4kUaO3Ys+fv7U11dXbNtHj16lHQ6XZPrSUlJVFRUZLFOe/ppK3PmzCGlUkmlpaUd1uadTkVFBbm6utLf//53q+v88MMPJJFI6Ouvv6akpCR66qmnSKlUUnFx8S0cKaM1uqQAWcLZ2Zm+/PJLi/dKSkoIAB0+fNjifaPRSL1796Zp06Y1yn2UkpJCnp6etGzZMqvG0Fw/OTk59PDDD5NSqSRnZ2eaNWsWVVRUtNrehQsXiOM4WrlypVX9dyU+/fRTAkCnT5+2qnxcXBzNnz+f/9loNJKPjw8tWbKEv9be98BoP11egAwGA23atIkkEgklJSVZLJOWltYo4Zsl8vPzKSQkhGbNmkVGo5HS09PJx8eHnn76aavHYqmftLQ0cnNzo7feeotSUlLo7NmzFBcXR08++WSLbZlMJrrvvvsoIiLC4sysq2MwGKhXr140cODAVhPrabVaEgqF9Ouvvza6Pnv2bJo4cSIRtf89MG6OLitAly9fJnt7exIKheTk5EQ7duywWM5oNNK4ceNo0KBBrbaZk5ND/v7+NHPmTPL396fZs2dbnVWyuX5GjhxJb7/9dqNrW7ZsoaCgoBbb27x5MwGg3bt3W9V/V+SPP/4gALRx48YWy+Xn5xMAOn78eKPrr7zyCp+BtL3vgXFzdFkB0mq1lJaWRmfPnqVFixaRm5ubxRnQ3LlzKSAggPLy8qxq9/DhwwSAgoODSa/XWz0eS/1kZ2cTAJLL5WRvb8//k8lkFBYW1mxbKpWKAgICaPz48Vb331V58MEHydfXt0W7WmsC1N73wLh5uqwA3cjw4cNpzpw5ja7Nnz+f/Pz8KDMz06o2ioqKqEePHjRhwgTy8vKiBQsWWFWvuX62bt1KLi4ulJaW1uTftWvXmm3vP//5D4nFYrp69apV/XdlMjIySCqV0ltvvdVsmdaWYO19D4ybx2YE6P7776fHHnuMiMz2k/nz55OPjw+lpqZaVb+0tJQiIyNp8uTJpNfrKSkpidzd3emll15qtk5r/ezcuZPEYjHV19db/Rx5eXlkZ2dHL7/8stV1ujpvvPEGyWQyys7ObrZMXFxcoz8YRqORfH19acmSJe16D4yOoUsK0KJFi+jw4cOUlZVFly9fpkWLFhHHcbR3714iIpo3bx45OTnRoUOHqLCwkP+nUqkstmc0Gik2NpbGjh3L520iIrp48SK5uLjQxx9/bLFea/2Ul5eTq6srTZ06lS5evEhpaWm0a9cuev7555t9tkceeYQ8PDyoqqqqnZ9O16O2tpa8vb1pxowZzZb54YcfSCqV0jfffEPJycn88YWioqJ2vQdGx9AlBeiJJ56ggIAAkkgk5O7uTsOHD+fFh4iaTapmKZFbA3v37iW1Wt3k+vnz55u1H1nTz6lTp2jo0KHk6OhICoWCYmJimt1WP378OAGg7o93p7SyNOs+DBvgWtU1GvTsoBaPUhARrVq1ivz9/UkikVBcXBydPHmSv9eW98DoOJgv2F2CyWRCv7h+SChMgP18e0hEEkzoMQFrx62FTNSxgcLvFnRGHZYcWYJNiZug1qlR/EkxwpzCcPH8xQ5PH8O4NXRJV4yuyHfffYeL5y5iwVsL4CR3gt6kxy9XfkH3Vd2x4uSKzh7ebefHhB9xz1f3YP3F9dAYNHC1d8Wi9xYh8XIivv76684eHsNaOnsK1l7Wrl1L0dHRpFAoSKFQ0IABA2jnzp38/cWLF1NsbCw5ODiQu7s7TZo0iVJSUjp8HIcPH6bx48eTt7c3AWiy09IR1NTUkJeXF82cOZOIiNR6NT259UlyW+5GTkucyGmJE/Va24sOZBzo8L7vNC4UXKAHvnuAAv4bQAH/DaDun3Snfx74J2kNZtvco48+Su7u7m2ykd2Od8iwzF07A/Lz88PSpUtx7tw5nD17FsOGDcOkSZOQlJQEADh8+DDmz5+PkydPYt++fdDr9Rg1ahTq6+ubbfPYsWPQ6/VNricnJ6O4uNhinfr6evTu3Rtr1qzpmAezwOLFi1FVVYXly5cDAGQiGb6c+CVOPHECMd4x4DgOOdU5mLp5Ksb+byyuVV27ZWPpLCrUFZizbQ6m/jQVV8qugOM4DOo2CPtm78N/hv0HEqEEgNkBWKVS4T//+Y/Vbd+Od8hohs5WwI6ks/290MJfz4SEBBozZgwpFAry9PSkhQsXNtpRa46MjAySSCT0r3/9q9ky265uo4hVEfxsyH25Oz2789k2HZS8UzEYDLTs6DKKWB3Bz3qGrB9Ch7OaNza/9957JBKJ2jXjbe4dtvf9MVqmSwjQneLv1dwv7/nz50mhUNCbb75JaWlpdPDgQfL29qZ333231TanTJlCfn5+rZ5R0ev19N7h98jnQx9eiAJXBNKX5ywL8t3AtqvbaMAXA3jhiV4bTZ+d+azRHwhLqFQqCgwMpHHjxrW5T0vv8GbeH6Nl7moButP8vZoToH79+tEzzzzT6Nobb7zB+yE1x/79+wkAff/991b1T0RUq66lWVtmkctSF16IYj+PpdPXrPMavxNILkmmyZsm88ITujKUXtz9ItVqa61uY8uWLQSAdu3a1aa+Lb3D9r4/Ruvc1QJ0p/l7WfrlvXLlCgGgK1euNLr+zjvvUO/evZttS6/XU1RUFA0aNMhqAbyeS4WX6J6v7iHlEiU5LXEil6UuNGXTFCqtvXPjBlWrq+m5Xc9RyMoQXnym/jiVrpa23eXEZDLR0KFDKTw8vE3RAm58h+19fwzruGuN0AAgkUgQGhqKfv36YcmSJejduzdWrlzZqMyCBQuwfft2HDx4EH5+fq22WVxcjDlz5mDChAlQqVR48cUXb2qMSUlJEIvF6N69e6PrycnJLUYwXLduHZKSkrBy5cp2hVnt5dULx544hk/HfQp3O3cYyYg/sv9A9GfReGP/GzAYDG1u81ZhNBqx+vRqDPp6ELambIXBZICvwhdrx63Flhlb0N2te+uN3ADHcVixYgVSU1Oxdu3ado+tve+PYSWdrYAdSWf4e10PLMyA9uzZQwKBgDQaDX8tMzOTxGJxs8uDBteAJ554wqp+W0Ov19Ore18lrw+8+GVZ2Mow2nR5U4e0fzMcyDxA9359Lz/jiVwTSR8d/6hVO4+1PP300+Tk5EQlJSVWlb/xHbbn/TGs564VoDvF36u2tpYuXLhAFy5cIAD08ccf04ULFygnJ4eIiKqqqsjFxYVeeOEFysjIoAMHDlBERAQ9+uijzT7bc889RwqFggoLC9v78ViktLaUJm+azNuHlEuUdM9X91BicWKH9mMNmRWZNHPzTAr8byAF/DeAglcE07zt86hcVd6h/ZSUlJCTkxPNnTu32TItvcP2vD+G9dy1AnSn+HsdPHjQYj8NMzEioj///JNiYmJIJpNRcHAwLVmypNm/8ElJSSQQCuiJVzpm9mOJ47nHKfbzWH425LLUhWZtmUW1auuNvO1FpVXRov2LKOyTMH7WM27jOLpUdOmW9Tn3jbnECTi6dMlyH629w7a8P0bbYL5gdxBEhGEjhuHIpSNweNEBsd1i8eXELxGoDLwl/a07uw5Lji5BpaYSAGAvscczsc/gtXteg0gk6vD+vrnwDVaeXolKtbk/D3sPvDboNUztObXD+wKA7KpsPL3taZzKO4W6lXUYED4ARw4d6dKpi+42mADdQWzbtg0TJ05EwNwAVAVUAQDEQjGmhE/BJ2M+uSVOpwaDAS/ufRE/Jv0InVEHAPBV+GLZyGUY3318h/RxMu8k/nnwn0ivSAcAyEVyPNLrEbw66FX+BHNHojFosHDPQmxJ3sI/k1OeE3LX5uLnn3/Ggw8+2OF9MtoHE6A7BK1Wi6ioKAQFBWHPnj34+MTHWHFyBWp1tQAAJ6kTXh30KubHzb8l/V+ruoantj+Fk/knQUTgOA79vPvhs3GfIdQ1tF1tFtUVYdG+RTicexhEBAEnwNCAoVg8YjG8HLw6+AnMfHbmMyw5tgTVmmoAgIPEAQv6L8Ciexdh3LhxSE5OxpUrVyCT2WYEgTuOTlv8tcKd4mxKRLR69WoKCAggqVRKcXFxdOrUqQ7v44MPPiChUMjnLiMyHyp87NfHyHWZK2+v6ftp3xbdEG6Wfen7qNfaXnx/bsvd6KmtT5Fa39Q21hxag5bePfQu9VjVg7fzjPh2BJ3MO9l65XZyJOcIxXwWw4/bdZkrPfrLo43sWikpKSQSiej999+3ut3b+Xtmi9yxAnSnJBe8HQntioqKSKFQ0LPPPmvxfkppCt339X38oULnpc40fuN4Kqzt2F2y6/nw2Ifk95Ef/4X2/9ifPjnxSav1fkr8iWLXxfLC0+ezPrThwoZbNs7C2kKa+P1Ecl7qzO/s3fv1vc3u7C1cuJDs7e0pPz/fqvZvR3JJW+aOFSBLdIazqTUJ7YhuLqndk08+SS4uLlRe3vIW9M9JP1P3T7rzouCx3IMW7l54y5xO1Xo1Pf7b4+S27K+wH70/7W1xBnap6BKN2ziOF56wT8Lo9f2vk0pr+djDzaLX6+nlPS+T5wee/Ni6f9KdNidtbrFeZWUlubu70+zZs9vVr6XfM5bQsP3cFQLUWc6m1iS0a+i7vUntzp49SxzH0erVq1stS2T+4r1z8B3y/tCb/+IFrwimDRdv3SwjpTSFhqwfQsqlf83Axv5vLBXWFlK5qpye3vY0Ba8IpoD/BlDgfwPpoc0PUXZl8wHib5aNlzZSyMoQ/vm9PvCiN/a/YbUQr1u3jgA0CslqLTf+nrGEhjfHHS1Ane1sak1CO6L2J7UzmUw0aNAgioyMbPMsplJdSTN+mtHI6TRuXRydyz/Xpnbawm/Jv1GPVT3IaYkTOS52JMViBbktdSO/D/0o4L8BdN/X99HBzIO3rP9LhZdo4JcDG51fmvrD1Db7txkMBurduzfFx8eT0Wi0up6l3zOW0PDmuKMFqLOdTa0RoJtJavfDDz8QANq/f79V47bEufxzNOCLAY2+lNN+nEaV6sp2t9kSer2e5mydQ/L/yEn4byEJ/y0k+Xtymrd13i07nFeprqSHNj/URGxvxsP/0KFDBIC+/fZbq+vc+HvGEhrePHfVNvyIESMQEhKCzz//nL+2YMECbN26FX/++SeCgoJabaO4uBhDhgxB9+7dcebMGUybNg2rVq2yWFan08HOzg5btmzB5MmT+euPPfYYqqqqsHXrVvz+++94/PHHcerUqSb15XI5fH19LbatUqkQHh6Ofv364ddff2113K3x7aVv8e9D/0a5uhwAYCe2wz/6/gNv3/d2hx0qLKgpwKrTq3Di2gkYDAYklCZAb9JDKVNCIpSgr1dfLB6+GBHuER3Sn8FgwPtH38e6c+tQrzdHsnSRu+DtIW/j733+ftPtz5gxA8eOHcPVq1fh4ODQYllLv2ftffeM6+hsBWwLneFs2lJCO6L2JRckModzkEgklJ6e3qZ6LaHX62nh7oXksdyjkWH256Sfb6pdtV5Na06voZHfjqQh64fQkPVD6NW9r9KX576k+dvn031f38cbn0NWhtBzO5+jKtXN5S27frnXYHB/ftfzHWpwz8rKIplMRm+++WazZVr6PWMJDW+eO1aA7hRn05YS2hG1L7lgTk4OyeQyeu2119r56bRMYW0hjd84vtHW9H1f30cppW07v2I0GmlH6g6a8sMUXnhm/zKbX/qcunaK/nXwX/Tdpe9oR+oOuufLe3ghiloTRatOrWrzsiy1LLWRwVu5VElj/zeWrlXdmhTJb731FkmkkmbTc7f0e8YSGt48d6wA3SnOpkQtJ7QjantSu+kzppPIUUTDPh9GZ/PPtvAp3ByWDuc99stjVjmdJhYn0lO/P8ULz/iN4+nHxB8bGW3za/LpXwf/RUuPLCWTyUQGg4E+Ov4RRa6J5IXo3q/vpQOZrWfraMuWf0dxOOswRa2IIoGTgCZNnmSxTGu/Zyyh4c1xV9mAugJHjx7FvffeC9eHXeEQ5wABJ8D9gfdj6fClcHdwvyV9rjm9BsuPL+fdExQSBZ6Lew6vDH6lSdkyVRlWn16Nw9mHQSCIBCKMCR2Dp2OfhoOksZ3EaDJiydElMJgMeDbuWbjauQIwZ7D45x//xJ70PTCSERw4xPvFY8nwJQhybmqnW3FyBT4+8TFqtDUAzG4nLw98Gc8OeLajPwoAZreTp3c8jePXjoOIoL+oh+pHFQ4ePIihQ4fekj4ZlmECdBsxmUzo378/BAIBPt78Md45/A4yKjMAmB00Z/eejVfvefWWZPXUGDR4cfeL2HJlC/RGc+qhbk7d8PHIjzEydCR0Bh2+u/wdtiRvgdqgBgD09eqL5+KfsygaDXx94WvkVudiSvgU9Pbq3eje5eLLeOPAG0gsSQQASIQSTI2YirfvextyiRx/ZP6BF/a8gNzqXABmx9tJPSZh9djVt8zx9qV9L2FT4ibeSdXHwQeLhy3G8n8sR319Pc6fP8+yqt5GmADdRr7++ms8+eSTOH78OAYOHAij0YhvLn2DVadXoUpTBQDwtPfEosGLMCViyi0ZQ3ZVNp76/SmcLTzLO50GKYPgbeeNeqN5p8nbwRvP9H8G9wbc22p7e9L34MS1E+jv0x/juo+zWObn5J+x7NgylNSXAAAcxA4gEArqCv5yfPXqh8/Gt9/xtTW+Ov8V3j/yPirUFQAAe7E95sbOxeuDXodIJMKZM2cQFxeHzz77DE8//fQtGQOjKUyAbhM1NTUICwvDiBEjsHHjxkb31Do13vnzHfyS/Av0JvPsJNozGkuGL0GUR9QtGc/utN387MNEJgg4AXwVvnht8Gv4e5+/QySwbus+qSQJm5M3w0fhgzn95jRbriGP+5rTa1CpqQTB7B3fzbEbVo5Z2WGhP27kTP4ZzN85H6nlqQAAISfEqJBR+HT8p1DKlI3KPv7449i2bRvS0tLg7Ox8S8bDaMxdHZT+buK9995DXV0dli1b1uSeXCLHshHLsPfRvYj3jQcHDgnFCZj8w2Qs2LkA1erqDh2L1qCFUCDEI9GPIMY7BiKBCBw4aAwarDu3DltTtlrdlp+jOdB/UV0Rv7SzxK60XdiZvhMSoQRCgRACTgBPe0/4O/njUtElfgbYUZTVlWHaj9PwwP8eQGp5Kjhw6OneEwcfO4hN0zY1ER/AnIFWq9Xi3Xff7dCxMJqHzYBuA2lpaYiMjMRbb72Ft956q9Xy+zL24d3D7yKvJg+A2Wj8ZMyTeLb/szdlnzCRCReLLuJA5gH+YF+IcwiulF3BkZwjyK/Nh4lMAMwzsMXDFiPas+XMD0SEj058hDpdHZ7o+wT8nfwb3U8uScbrB17HpeJLAAAOHHwUPoj1joWBDEgqNafSthfb4+Goh/FQ9ENWz74sYTAY8K/D/8LXF77mbVludm54d+i7mNVrVqv1ly5dirfeegsJCQkIDw9v9zgY1sEE6DYwadIkXLx4ESkpKZDL5VbVMRqNWHt2LdadW8cHJfNz9MNb972F0aGj2zyG3Opc7ErbhcK6QgCAq9wVD4Q+gDDXMPyU9BOSS5MR4RaBX1N+xalrp0AgCDkhRoeOxnvD3oOL3KXZtn9I/AEpZSkYFTIK93S7BwBQra7GPw/+E7vSd8FgMoADh/6+/TEtYhqSy5IR7haOh6IewoHMA/js3GcorS8FAPgofPBM/2cw2H9wm59xS/IWvHngTRTXFwMAZCIZHuv9GN6//32rT4NrNBpERkaiR48e2LlzZ5vHwGgbTIBuMXv37sXo0aPx008/Yfr06W2ub+mLHOsbi6XDlyLEJaT1+ppq7Mvcx+9ESYVSDA0cijjfOAgF5tnU6fzT2Jm2EyHOIXi096P4I+sPvHPoHX53ykHigCf6PoHn4563OAM7mnsU+zP3I9I9Eg+GP4g1Z9fgi3NfNBLOfw35F0aGjMTGyxuRVpGG0SGjMbDbQACAzqDDt5e/xZbkLdAYNACAGO8YPB//PAKUAa0+Y1JJEuZun4vEkkTetjQkYAi+GP8F3BzcrPiUG/Pbb79hypQp2LFjB8aOHdvm+gzrYQJ0C9Hr9ejTpw9cXV1x+PDhmwqGfuNSRiwQY3L4ZLwz9B3YS+yb9m3U41jeMRzLPQa9SQ8OHGK8YzAsaFiT8sV1xfj07KcQC8RYNHgRhAIhjEYjPj33KT4/+zkvJL6Ovnj7vrebzMCyq7LxzcVvUFRXhKSSpEZLxzn95uCZ2GcgFAphIhOWHV0GrVGLp/s9DW+Fd6N2SupKsPrMahzJOdLqGSQAqNPUYe7OudidvhsGkznRYohLCFaPWc2LW3sgIowcORJ5eXlISEiARNLxcasZZpgA3UJWrVqF559/HufOnUPfvn07pM2tKVux+OhiFNeZlxlKqRLz4+bjiT5PQCgUgoiQVJqEfRn7UK01G68DnALwQOgDTb7wDRARPjj+AVR6FZ7s+yS6OXXj71Wrq/H2obexI20H/yWP9YnF0hFLEepi3jJPKUvB1J+mIr8mH45SR0iEEowJHYP37n8PTnInvq2C2gKsO7cOMpEMrw56FQLO8h5IYnEiVpxawQexd5I64dHej+LB8AchEJjrLD2yFKvPrEadrs78OciUWDRoEeb2n9vuz/Z6EhIS0KdPH3zwwQdYuHBhh7TJaAoToFtEeXk5wsLCMG3aNKxbt65D2zYajfjg+AfYcHkD1HqzoTVYGYznBjyHCnUFv3RykjphVMgo9HTv2ers68fEH3Gl7ApGBI+waH+5UnoFrx94HReLLgIwz8DGhI2BgBNgR+oOVKgrYCQjYrxj8MmYTyweHziedxx7M/aiu2t3zIpu2SBsMpmwM20nvrrwFZ82KFAZiBivGKw7vw7Xaq4BMB9unNZzGj4e/XGHH16cP38+/ve//yEtLQ0eHh4d2jbDDBOgW8TSpUuxZMmSW/rLW1pXijf+eAMHsg7ARCZw4BCoDMQ9fvdgdNhoDPQbCLFQbFVbJ6+dxO703Qh1CcXfev2t2XLbU7fjvT/fQ151Hr/LJBfJYS+xR1+vvniq31MYETzCYt3vE75HanlqI2N1a2gMGnx5/kv8fOVnZFdm82LEcRzifOLwxYQv4K/0b6WV9tHwR+SFF17A22+/fUv6sHXYOaBbxKuvvooTJ07c0r+c7g7u+GLiF9g8fTM87T1BIGRVZeF04WlkVWa1yebUkPyw4WBic8T7xmNev3kIUprdM4gIAU4BmBU1C/5O/vzM5EZMZOJnZm1JtCgSiOAodYRGr+HFRylT4qepP2HPo3tumfgAgKurK44ePYo333zzlvVh63R8+ksGAEAgEKBnz563pa9+Pv3w60O/4o39b+B80XnoDDqsv7geu9J3YW7sXAwNHNpqG572npCL5FAb1CisLYSvY+NgWmq9GodzDuN0/mmYyITurt3BcRwIhO6u3VGvr8eZgjMoUZVgWs9pTYzGxXXF0Bg0kAqlVucEO5p7FGvPrEV2VTaK6oogFUoR7haO76Z8d0uF53pu1zu0VZgAdRG8HbwR5RkFfyd/KKQK/JH1B4rqivDOoXfQ27M3nh/wPIKdg5utz3Ec/J38cbX8KrKrsnkBMpEJ5wrO4WD2Qaj0KgBAuFu4OQqiSAIiQg+3HvB28Mb5ovPIqcrB0iNLMaHHBMT6xPJb/dlV2QAAfyf/Zo3PDWRXZWPlyZW4UHQBRpMRFeoKhLiEQCaS4Z5u98DPya8DPjHGnQBbgnUROI5DqEsoBAIBenv1xsYHN2JY0DAIOAEuFV/CU78/heXHlqNGU9NsGw1LowaxyKrMwudnP8eOtB1Q6VXwsPfA7N6zMaH7BFRrquEid4GrnStqtDUY3308xoSOgb3YHiWqEuxK34XPzn6GjAqzt39OdU6jPixRp6vDR8c/wj9+/wcuFF0AEUEpU2Jij4no4doDvTx7oYdrj1YFjHH3wIzQXYgGx1B3O3c+hXNySTI+OfUJUspTAJgPFT7a61FM7zmd39JuoLC2EJ+f+xwmMiHCLQJXy68CMBuZ7w+6H7E+sWZBK7qEX1N+hbeDNwScAPm1+ZjUYxIq1BX4M+dPOEodYSQjP2Pq7tIdVyuuggOHf8T8g/cfa8BkMuGXlF+w4eIG/sxRqHMowt3CUaevg1wkh4AToF5fjwcjHkQvz1639HNk3D7YEqwLEeISAgEnQKmqFFWaKihlSvT06Im149ZiT8YefHn+S5Sry/Hp2U+xI3UH5sfNR7xfPF9fKVPiWs01ZFZmol5XDyeZE2J9YjE0cCjsxHZ8uYbzOWGuYbwApVWkobdnb3AcB7lYjsf7PM7bjM4Xnce5gnMIcg6Ci6yxS8eZ/DNYfXo1P0NykbvgHzH/AAcOp/JPQcgJMb77eGxO3gwOHH/2iNE1YHPZLoRMJEM3R/MhwrTyNP66QCDAmLAx2Dh1I2ZGzoRUKEVuTS5e2/8aXt33KvKq83Cp6BLWnlmLSnUlTGSCndgOc2PnYmzY2EbiYyLTXwLkEoYwlzAAQEZFBm9cLq0vhYAT4IHQBzAvdh4cJOb4P1WaKqw9uxYXCi8gvyYfr+17Da/sewU51TmQCCWYETkD30/9Hm52bjiVb840MTl8MrRGLQDzSezrx8K4+2EzoC5GmGsYcqpzkFaRhv6+/Rvdk4lkmNd/HqZETMGqU6twPO84TuefxmO/PYYApwD4OfrB38kf3gpvRLhHwMO+6RGC/Jp8qA1qyEVy+Dr6ggMHe7E96vX1qNRUQilTokpThfzafAQ7B8Pd3h3hbuEoV5VDyAlRo63BJ6c+QW51LoxkBAAM9BuIZ+OehY+jD1LLU7ErbRcAYHjQcER7RuPHxB/Nz/b/YsfoOrAZUBej4UuaVZnFu07ciJeDF94f/j4+Gv0RfBW+MJEJWVVZqNJUYUzYGLjKXZs9D5RWYZ5ZNSz3GozfgHnW1WDfya/JB2A+J5RbnQsXuQvGdx+Pak01sqqyYCQjvBy88OGoD7FkxBL4OPqgoLYAm5M2g0CI8Y7BYP/BMJqMfNjaMFcmQF0NJkBdDA97DzhKHaE36fndrOaI8Y7Bhskb0MujF6RCKQScAMdyjyGpNAllqjLe3+x6GpZ2189GGoQhreIvAWo4kFiqKkVJfQkSSxLxZ+6f4DgOEqEEkR6R+G7Kd4j1iQVg9tr/PuF76E16hDiHYFzYOHAch9zqXOiMOtiL7eHtYNmXjXH3wgSoi8FxHC8O19uBmkMkFGFkyEjE+cbBw94DEqEEJjLhbMFZ/O/y/1Cvq+fL1mprUVhX2MQYHOIcAg4cSupLoJAoAJgFqF5Xj/9d/h/OFpyFiUyQCCVwt3dHvG88RgWP4t1ENAYNNiZsRJ2uDp72npgeOZ0/P9Qw4wpzDbupaAKMOxMmQF2Q62ck1hDlEQWhQAihQIi5sXPR19vsuX8q/5Q5FXPeCRhNRt747KPwaRTSQy6W8x70tbpacOBwtfwqlh9bjhN5JwAAfbz6YF7sPIgFYggFQt5Z1Wgy4qekn3jxmhU9q5FTqaUZF6PrwIzQXZAgZRCEnBAV6gqUq8r5fF3NEaAMgEKiQK2uFmWqMjze53GUq8qRW50LtV6NPRl7+FkMYNkWE+YShtzqXJy4dgIp5SkorC2EWCCGkYzo49UHj/d9HJWaSqj0KtiL7RHkHAQiwvbU7ciszIREKMGs6Flwkv0VvqNSXYlSlXlHraVT3Iy7FzYD6oJIRVI+kqA1syABJ0CkRyQAILEkEd4O3vCw90C0RzTu9b8X9mJ7lKpK8Xvq77hcfBmu8qaC5ip3RUJxArZd3QYOHMQCMbo5dUO0RzTc7Nzgo/BBQnECACDSIxICToAjuUdwoegCOHCY1nNak3hFDTOubo7dIBdbF8qWcXfBBKiL0hY7EABEe5iDz6eUpcBIRnRz7AaO46CQKvBc/HMIcwmDyWRCra4Wv1z5BbvTd0Nj0EBj0GBvxl5sSd6CWl0tiAjhruGI94tHvb4eHMehm2M3EBFSysynsaM8opBQnIA/sv4AAIwNG4vurt2bjOl6+w+ja8KWYF2UMNcw7MnYg5zqHOiMOkiELYcV9VH4wFnmjEpNJVLLUxGoDERGZQayq7IR7xcPLwcv9PftD4PRAALh5LWTOHntZKM2oj2iIRQIEeEegaTSJKSUpaC7a3cEKgORXpEOrVELJ6kTTCYTfkv5DQBwT7d7mpxXAgCDyYCsyizzszD7T5eFzYC6KK5yVzjLnGEwGVrdjgfMu2cNhuHEkkTeaTSnOgdEhLSKNNiJ7fBEzBN4tNejcLf7K489Bw6PRD+Cf8T8A3ZiOxTWFkIukqNCXYE6XR0ClAFIKDEvv7o5dsOPST/CSEb0dO+JkcEjLY4nuyobepMejlJHiwciGV0DJkBdlBsPCFpDQw6wtPI0uMhdIBaIodKrkFGZgZL6EnDgEOIcghCXEMyNnQtHqSMAczyiMNcwBDsHQ8AJUKGpgEgggs6oQ72uHu527kgtT4XOqMOVsitQG9Twc/TDlPApzW6tN4w51CWUbb93YZgAdWGu3463JuiBh70HPOw9YCQjUstT+a31Y7nHAADdnP4yBgsFQkzqMQkAcLn4sjnYmEjKJyYsU5UBAMRCMTIqM6A1aPnT2c4yZzwc9XCL4WJ5+w9bfnVpmAB1YYKUQRAJRKjSVPGC0BqWlmHnC88DaCoGwc7BcLdzh86o44PVN5QprDUnQOTA4XLRZaSUpUAqksJObIdHej1iMZVQA+WqclSoKyDkhGz7vYvDBKgLIxaKeRFpy6FEAMiszISbnRtMZMLV8qsgoia7URzH8eE8Tl07BROZEOYaBiJCQV0BTGSCiUzYmb4TpapSeDt4Y2bUTLjZtZwssGGs/k7+kIqkbXlkxl0GE6AuTlu3413kLvBV+PLhM+p0ddAYNBBwAnjaezYp38uzF2QiGSo1lUgrT4O7nTvEQjF0Rh0EnADXaq4hvSIdDhIHPBz9sFUB6fnTz2z7vcvDBKiL0/AlzqnOgdagtapOwywopSwFHMwGYAepg0VjsEQoQT/vfgDMqX04juMD0je4ZGgNWowJHWNVJEOdUcfv2jH7T9eHCVAXx0XuAle5K0xkQmZlplV1Ij0iwcHsiV6nN2ceFXJNc8I30N+3PzhwyKrKQnFdMYScEFqDFsX15kwYcrG81USEDWRVmkN1KGXKVpdqjLsfJkA2QFudUx2ljghQBkCtV6NSVQkOHDQGTbM7aUqZEhHuEQDMtqBKjdmHy2AyQCwQw8fBB0qZ0qq+r9/9YtvvXR8mQDbA9XYga3MQRHlEoUJdgXp9PVzkLtAYNKhQVzRbPt7XbIw+lncMx/OOQywQQywUw0HiAFc7V6t24YioUbxpRteHCZANEKAMgFggRq2uFsX1TYOMWaKne09UaipRr6/nz/a0dKLa38kfnvaeOJ1/GsV1xXC3d4ezzBkcx8Hd3h35tfmt9lmmKkOVpgoigYjPvMro2jABsgFEAhF/nsba3TCxQMwboJ1lzgBaFiAA0Bq1KFWVok5XhzjfOIgEItiJ7fjdsNZoWH4FKgOtzmnPuLthAmQjtNUOlF2VDVc7V0hFUhjJCCLi/cIscSzvGCrUFdCb9HCWO0OlU0EuksPHwQdVmirrBIgFH7M5mADZCA1+YXnVeVDr1a2WT6tIg5udGzzsPGAiE9QGNWq0NajUVDYpm1iSiP2Z+6Ez6hDqHAqxQIyc6hy42bkhQBmAclU5SupLoDPqmu1Pa9DyucGY/cd2YAJkIyhlSnjYe4BAfJaJ5iAipJanQiQQoZ9PPwgFQhhN5hQ6Ny7Dcqtz+dAa/o7+6OXZCwRCvb4eIS4hsJfYQ6VXwWgy8u4ZlsiszISJTHCVu8JF7tJsOUbXggmQDWHtqehydTmqNFUQckIMDRwKAFDr1SCiRgJUrirHpoRNMJgMCHcLh7fCGxKhBAqpOTC9QqLgd8Pq9fUtLsNY8DHbhAmQDdHw5U6vSG9xO75BoAKVgYh0j4RUKIVYKEaNtgY5VWY7UL2uHhsTNkJtUMNX4YupEVORW52LWl0tfBx8IOAEqNHWwFvhDUepIyrUFc0KEBEx+4+NwgTIhujm2A1SoRT1+noU1BY0W+762YhYKEa4WzgcpY4oVZWiWluNMlUZfkj8ARXqCihlSjwc/TBUepX5AGJ9KXwdfdHDtQc4joPOqIOj1BHlqvJmt+KL64tRq6uFWCDmY1kzbAMmQDaEUCBEiEsIgOZ3w7QGLXKq/t8Y/P+zkYa0PVqDFiaTCesvrkdeTR5kIhkeiX4EDhIHfodMY9BAJBBhbNhYAEBxXTHsxfao1dWiQl2BGm1Nkz4bZj/BzsEQCViUYFuCCZCN0SAqDSeOb6QhbbKL3IVP5xPsHAw7sR3kYjkul1zG+cLzEHJCzIycCXd7c2jW7KpsVGurIRPJIBPJMCpkFByljiAQTGSCndiu2WUYs//YLkyAbIyG7fj8mvxGWU8bsGSLEQqE6OneEzqjDukV6ajSVGFij4kIcv7rtHJOVQ6K64qhlCnR070npCIp+vuYg82r9CooJApUqCv4nPENqPVq5FXnNRobw3ZgAmRjKKQKeDl4WdyObwg+DzSdjThIHFBYWwiVXgUPe49GtppabS1KVaUoU5XBSebEh/Po59OPX1IJOAEq1BXIq8lr1G5GZQYIBHc7d6sdVhldByZANkhz2/El9SWo0daYjcFOfwlMUV0RjuUeg0wkg6vcFQqJgrcTAeblV6W6ElKRFEqZkg86Zie2Qy/PXnCSOUGlV0Fn1OFq+VU+w+r1Y2DLL9uECZANcv12fCMx+P/ZT5BzEO+LVaOtwfcJ30Nv0iPaMxrdXbujVFXa6DxQTnUOSupLoJQpEeluznraQLxvPAScgI8nVFxXjOI6s0NsI+93tv1ukzABskH8HP0gF8mhNqgb2WRutP9oDVp8n/A9arQ1cLdzx9zYuXCRu6BMVYb0yr+M2BkVGShTlUEpU/LLrwY8HTwRpAyCi50LDCaD2Q70/9vxBbUFqNfXQyr8K5sGw7ZgAmSDCDhBk+14jUHD22dCXUJhIhM2J29GUV0R7MX2eKTXIwhxDkGQcxBMZEJ6eTpqtDWo09UhtTwVRjLC38kffo5+TfqL94uHq9wVKr0K1ZpqZFRkNOo72DkYQkHzERcZXRcmQDbKjXagjIoMmMjEG4N3pO5AekU6xAIxZkXPglKmBMdx6OPVBwqJAiX1JciuyjbvftUXw0HigBjvGItRDLu7doeXgxcUUgXqdHW4WHyxUd/M/mO7MAGyUUJdQsGBQ2FdIWq1tY12v47nHce5wnPgwGFqz6nwdfTl60V5REEpU6JCXYGrZVeRVp6GCnUFnKROTZZfDQg4AeJ84+Cj8EGNtgYZFRkoV5Xzp7GZ/cd2YQJko9hL7OGj8AFgnok0GIOJCPsy9wEARoeORrhbeKN6HvYeCHEOAYFwpuAMTuWb84EFOQdZTNvTQF/vvvBT+IFAyK/Nx7G8YyAQPzNi2CZMgGyYhqXPmYIzqNPVQaVX4VT+KQDm3asBfgMs1hvsPxgAcKX0ChJLEgEA9/rf22IQeZlIhiGBQ8wnolUVOJxz2DwGNvuxaZgA2TANX/5zBedQr6tHfk0+TGRCD9ceGB06utl6/Xz6wUHigILaAhTUFsBebI9Yn9hW+xvgNwBudm5Q6VU4fe20eQzM/mPTMAGyYXwUPrAX2+Na7TWcKTjDL8um9pza6CzPjTjLnRHqEopqbTWqNFUIcg7i/cZaws3ODf19+sNIRmRXZ0MqlFrcNWPYDsz1uAuRl5eHwsLmow5aQpOtQdKFJIiFYkSGRKK7S3dcPHex1XpulW6oSK8AEcHDzQOnT5+2qr+eup5QZ6uhNWpR7lSOs9KzbRqvp6cnAgJYyI6uAkfWJopi3PG89tprWL58eWcP45by/PPPY8WKFZ09DEYHwQSoC1FYWIjS0tJ21TWYDHdFLB43Nzf4+Ph09jAYHQQTIAaD0WkwIzSDweg0mAAxGIxOgwkQg8HoNJgAMRiMToMJkA1SXV2NOXPmIDQ0FBEREVafHTIYDHj//fcxcOBAxMTE4LHHHsO+fftuWX+Mrg8TIBtk/vz5SEhIwPLly5GTkwO12pwr/sUXX8Tq1aubrbdo0SKsXbsWw4cPx+TJk6HVajF+/Hg8/vjjLSY6bG9/DBuAGDaHi4sLnT9/noiIHBwcKCMjg4iIdu3aRbGxsc3W8/b2psOHDze6lpmZST179qTly5d3eH+Mrg+bAdkgRASFomkIjLCwMKSlNZ83vr6+Hn5+jX23goKCsGrVKqxbt67D+2N0fZgA2SBjxozBxo0bm1yvr69vMaTG4MGDsWHDhibXg4KCUFDQfKrn9vbH6Prc+WfvGR3OkiVLEBtrDp9BROA4DhqNBv/5z38QExPTbL1ly5Zh0KBBqKysxLPPPouwsDDo9XqsWrUKPXv27PD+GDZA564AGZ1FWloajRo1ijiOIzc3N5JKpeTu7k5nzpxpsd758+cpNjaWOI4jqVRKIpGI3Nzc6OjRo7ekP0bXhvmC2Ti5ubm4dOkSxGIx4uPj4ezsbFW9q1evIikpCQqFAvHx8XB0dLyl/TG6JkyAGAxGp8GM0DZGWVkZli9fjilTpmDgwIEYOHAgpkyZgg8++KDdoTzy8vLwxBNPWLynVqtx9OhRJCcnN7mn0Wjw7bfftqtPRteAzYBsiDNnzmD06NGws7PDiBEj4OlpzmJRXFyMAwcOQKVSYc+ePbzB2FouXbqEmJgYGI3GRtdTU1MxatQo5ObmguM4DB48GD/88AO8vb35fn18fJrUY9gOTIBsiAEDBqB379747LPPmmx/ExHmzp2Ly5cv48SJE43u/f777y22m5mZiZdeeqmJkEyZMgV6vR7ffPMNqqqq8MILLyA5ORmHDh2Cv78/EyAGEyBbQi6X48KFCwgPD7d4PyUlBX379uVdJRoQCATgOK5FdwuO45oIiaenJ/bv34/o6GgAZpF75plnsHPnThw8eBD29vZMgGwcZgOyIby8vFoMHn/69Gl+WXY93t7e+OWXX2AymSz+O3/+vMX21Go1RKK/jppxHIdPP/0UEyZMwJAhQ5CamnrzD8W4q2EHEW2Il19+GXPmzMG5c+cwfPjwJjagL774Ah9++GGTev369cO5c+cwadIki+02NzsKDw/H2bNnERER0eh6gwPqxIkTb/aRGHc7nXH4iNF5/PDDDxQfH08ikYg4jiOO40gkElF8fDz9+OOPFuv8+eeftGvXrmbbrKuro0OHDjW5vnjxYhozZkyz9ebNm0ccx7X9IRhdBmYDslH0ej3KysoAmDNNiMXiTh4RwxZhAsRgMDoNZoRmMBidBhMgBoPRaTABYjAYnQYTIBujpKTE4lY7AKxcubLZwGK3ux7DRujcTTjG7SY5OZm8vLzomWeeaXT95ZdfJjc3N7p48eIdUY9hGzABskFSUlLI19eXHn/8cTIajfTss8+Sp6cnXbp06Y6qx+j6sG14GyUjIwPDhw+HWCyGSqXC/v37m5xYvhPqMbo2zAZko4SEhGDgwIHIyMhA//790aNHjzuyHqNrwwTIBiEi/O1vf8PJkydx+PBhXL16FTNmzIDBYLij6jFsgE5dADJuO3q9nqZPn06hoaGUm5tLRERFRUUUFRVFEyZMIK1We0fUY9gGbAZkY5w+fRppaWk4cuQIunXrBsAct+fgwYMoKirCkSNH7oh6DNuAGaFtEPr/3FzWXu+seoyuDxMgBoPRabAlGIPB6DSYADEYjE6DCRCDweg0mAAxGIxOgwmQjdHeTKW3ux7DRuisA0iM28/Vq1cpICCAOI4jgUBA9913HxUUFPD3i4qKSCAQdHo9hu3AZkA2xGuvvYaoqCiUlJTg6tWrUCgUGDRoEHJzc++oegwborMVkHH78PDwoMuXL/M/m0wmmjt3Lvn7+1NGRkazM5LbXY9hO7AZkA3R3kylt7sew3ZgmVFtiPZmKr3d9Ri2A5sB2RBTpkzBpk2bLN5bvXo1Hn74YYsplm93PYbtwHzBGAxGp8FmQDbGlStXsH79eqSkpAAAUlJSMG/ePDzxxBP4448/7ph6DBuhU03gjNvKrl27SCKRkIuLC8lkMtq1axe5u7vTiBEjaNiwYSQUCunAgQOdXo9hOzABsiEGDhxIb775JhERbdq0iZydnemNN97g7y9atIhGjhzZ6fUYtgMTIBvC0dGR0tLSiIjIaDSSSCSi8+fP8/cTEhLI09Oz0+sxbAdmA7IxGiIQCgQCyGQyODk58fcUCgWqq6vviHoM24AJkA0RGBiItLQ0/ucTJ07A39+f/zk3Nxfe3t6dXo9hO7CDiDbEvHnzYDQa+Z+joqIa3d+1axeGDRvW6fUYtgM7B8RgMDoNtgRjMBidBhMgBoPRaTABYjAYnQYTIAaD0WkwAWIwGJ0GEyAGg9FpMAFiMBidBhMgBoPRaTABYjAYnQYTIAaD0WkwAWIwGJ0GEyAGg9FpMAFiMBidBhMgBoPRaTABYjAYnQYTIAaDYTVJSUmYPn06goODYWdnBzc3N9x3333Ytm1bu9pjEREZDIbV5OTkoLa2Fo899hh8fHygUqnw888/Y+LEifj8888xZ86cNrXHIiIyGIybwmg0ol+/ftBoNHwCSmthSzAGoxPIz8/Hk08+CR8fH0ilUgQFBWHevHnQ6XQAgMzMTEyfPh0uLi6ws7PDgAEDsGPHjkZtHDp0CBzH4aeffsL7778PPz8/yGQyDB8+HOnp6Xy5BQsWwMHBASqVqsk4Hn74YXh5eTWK3d1WhEIhunXrhqqqqjbXZUswBuM2U1BQgLi4OFRVVWHOnDkIDw9Hfn4+tmzZApVKhcrKStxzzz1QqVR47rnn4Orqig0bNmDixInYsmULpkyZ0qi9pUuXQiAQ4OWXX0Z1dTWWL1+ORx55BKdOnQIAzJw5E2vWrMGOHTswffp0vp5KpcK2bdvw97//HUKhsE3PUF9fD7Vajerqavz+++/YtWsXZs6c2fYPo3PTkjEYtsfs2bNJIBDQmTNnmtwzmUz0wgsvEAA6cuQIf722tpaCgoIoMDCQjEYjEREdPHiQAFBERARptVq+7MqVKwkAJSQk8G36+vrS1KlTG/X1008/EQD6888/2/wMTz/9NAEgACQQCGjatGlUUVHR5nbYEozBuI2YTCb89ttvmDBhAmJjY5vc5zgOO3fuRFxcHAYPHsxfd3BwwJw5c5CdnY3k5ORGdR5//HFIJBL+53vvvReAeRnX0Ob06dOxc+dO1NXV8eV+/PFH+Pr6NurHWl544QXs27cPGzZswJgxY2A0GvnlY1tgAsRg3EZKS0tRU1PTJEfa9eTk5KBHjx5NrkdERPD3r+f6ZI8A4OzsDACorKzkr82cORNqtRq///47AKCurg47d+7E9OnT+ey1bSE8PBwjRozA7NmzsX37dtTV1WHChAmgNu5pMQFiMO5ymrPfXC8GAwYMQGBgIH766ScAwLZt26BWq9tnt7HAtGnTcObMGaSmprapHhMgBuM24u7uDkdHRyQmJjZbJiAgAFevXm1yvWGLOyAgoF19z5gxA7t370ZNTQ1+/PFHBAYGYsCAAe1q60bUajUAoLq6uk31mAAxGLcRgUCAyZMnY9u2bTh79myT+0SEsWPH4vTp0zhx4gR/vb6+HuvWrUNgYCB69uzZrr5nzpwJrVaLDRs2YPfu3ZgxY0ab2ygpKWlyTa/X49tvv4VcLm/z2Ng2PINxm1m8eDH27t2LIUOGYM6cOYiIiEBhYSE2b96Mo0ePYtGiRdi0aRPGjBmD5557Di4uLtiwYQOysrLw888/QyBo37whJiYGoaGhePPNN6HVatu1/Hr66adRU1OD++67D76+vigqKsLGjRuRkpKCjz76CA4ODm1rsM37ZgwG46bJycmh2bNnk7u7O0mlUgoODqb58+fz2+kZGRk0bdo0UiqVJJPJKC4ujrZv396ojYZt+M2bNze6npWVRQBo/fr1Tfp98803CQCFhoa2a9ybNm2iESNGkKenJ4lEInJ2dqYRI0bQ1q1b29Uec8VgMBidBrMBMRiMToPZgBgMBurq6hodUrSEu7t7m102WoMJEIPBwIcffoh///vfLZbJyspCYGBgh/bLbEAMBgOZmZm860ZzDB48GDKZrEP7ZQLEYDA6DWaEZjAYnQYTIAaD0WkwAWIwGJ0GEyAGg9FpMAFiMBidBhMgBoPRaTAB6iJUVVWhvr6+s4dxy1GpVKioqOjsYTA6CCZAXYRXX30VAwcOhMlk6uyh3DKICPfeey8WLlzY2UNhdBBMgLoAFy5cwJdffomnnnqq3bFi7gY4jsOcOXOwYcMGnD59urOHw+gAuuRv66effopevXrB0dERjo6OGDhwIHbt2sXfX7JkCfr37w+FQgEPDw9MnjzZYgjMm+V29ENEeP755xEREYG5c+d2aNt3Iv/4xz/Qq1cvPP/8820OgL5mzRoEBgZCJpMhPj6eididQLuiCN3h/P7777Rjxw5KTU2lq1ev0htvvEFisZgSExOJiGj06NG0fv16SkxMpIsXL9LYsWPJ39+f6urqmm3z6NGjpNPpmlxPSkqioqIii3Xa009bacjttGfPng5r806nIRDX//73P6vr/PDDDySRSOjrr7+mpKQkeuqpp0ipVFJxcfEtHCmjNbqkAFnC2dmZvvzyS4v3SkpKCAAdPnzY4n2j0Ui9e/emadOmkcFg4K+npKSQp6cnLVu2zKoxNNdPTk4OPfzww6RUKsnZ2ZlmzZplVZI3lUpF/v7+NGHCBKv670pMnTqVfHx8qLa21qrycXFxNH/+fP5no9FIPj4+tGTJEv5ae98Do/10ySXY9RiNRvzwww+or6/HwIEDLZZpiOTv4uJi8b5AIMDOnTtx4cIFzJ49GyaTCRkZGRg2bBgmT56MV1991aqxWOonPT0d/fr1Q2hoKE6ePIl9+/YhPT0dr7zySqvtffjhhygsLMRHH31kVf9diQ8++ADl5eVYtmxZq2V1Oh3OnTuHESNG8NcEAgFGjBjBB36/mffAuAk6WwFvFZcvXyZ7e3sSCoXk5OREO3bssFjOaDTSuHHjaNCgQa22mZOTQ/7+/jRz5kzy9/en2bNnk8lksmo8zfUzcuRIevvttxtd27JlCwUFBbXYXm5uLsnlcnrllVes6r8r8uabb5JUKqWsrKwWy+Xn5xMAOn78eKPrr7zyCsXFxRFR+98D4+bosgKk1WopLS2Nzp49S4sWLSI3NzdKSkpqUm7u3LkUEBBAeXl5VrV7+PBhAkDBwcGk1+utHo+lfrKzswkAyeVysre35//JZDIKCwtrsb1Zs2aRh4cHVVdXWz2GrkZtbS35+PjQtGnTWizXmgDdzHtg3BxdVoBuZPjw4TRnzpxG1+bPn09+fn6UmZlpVRtFRUXUo0cPmjBhAnl5edGCBQusqtdcP1u3biUXFxdKS0tr8u/atWvNtnfs2DECQF999ZVV/Xdlvv32WwJAhw4daraMVqsloVBIv/76a6Prs2fPpokTJ7b7PTBuHpsRoPvvv58ee+wxIiIymUw0f/588vHxodTUVKvql5aWUmRkJE2ePJn0ej0lJSWRu7s7vfTSS83Waa2fnTt3klgspvr6equfw2g0UmxsLMXExJDRaLS6XlfFaDRSfHw89e7du9EGwY3ExcU1+oNhNBrJ19eXlixZ0q73wOgYuqQALVq0iA4fPkxZWVl0+fJlWrRoEXEcR3v37iUionnz5pGTkxMdOnSICgsL+X8qlcpiew1f+rFjx/J5m4iILl68SC4uLvTxxx9brNdaP+Xl5eTq6kpTp06lixcvUlpaGu3atYuef/75Zp9t/fr1BICOHDnSzk+n63Hy5EkCQJ9//nmzZX744QeSSqX0zTffUHJyMs2ZM4eUSiUVFRW16z0wOoYuKUBPPPEEBQQEkEQiIXd3dxo+fDgvPkREACz+s5TIrYG9e/eSWq1ucv38+fPN2o+s6efUqVM0dOhQcnR0JIVCQTExMbRy5UqL7VVXV5OzmzMNHTfUug/Chhg3bRwpXZRUWVnZbJlVq1aRv78/SSQSiouLo5MnT/L32vIeGB0Hiwl9F/Hqq6/iwxUfQvGSAgN6DsDnEz5HoDKws4fVqZTWleKNP97Anot7kL84H8/OfRYrVqzo7GExrIQJ0F1CRkYGevbsCacRTtDdqwMASIQSTOs5DR+P/hgyUcdmK7jTMRqN+PDEh/jm0jdQ69UAAMFRAfK25iExMRE9evTo5BEyrKHLH0TsKrz88svw9PRE9uZsLBq0CA4SB+iMOnyf8D3CV4fjs7OfdfYQbxu/p/yOe9bfg0/Pfgq1Xg0nqRPeuu8tJGxIgJ+fH/OWv5vo3BVg+1m7di1FR0eTQqEghUJBAwYMoJ07d/L3Fy9eTLGxseTg4EDu7u40adIkSklJ6fBxHD58mMaPH0/e3t4EoMlWb0ewb98+AkCbNm3ir9Wqa+mRnx8h12Wu5LTEiZyWOFHM5zF0PPd4Cy3d3SSXJNPE7ydSwH8DKOC/ARS6MpRe2fMK1Wn/8q37+eefCUCzB08tcTveIcMyd+0MyM/PD0uXLsW5c+dw9uxZDBs2DJMmTUJSUhIA4PDhw5g/fz5/rF6v12PUqFEtBu06duwY9Hp9k+vJyckoLi62WKe+vh69e/fGmjVrOubBbsBgMOCFF17AoEGDMHPmTP66g8wB/3vwfzj898OI9ogGBw4ZFRkY9/04TPlhCsrqym7JeDqDanU1nt35LCZsmoBLxZfAgUN/3/7Y/bfdWD5qOewl9nzZKVOm4P7778fChQuh0+msav9Wv0NGC3S2AnYkne1wihb+eiYkJNCYMWNIoVCQp6cnLVy4sNGWfnOsXr2aOI6js2fPtlhuc9Jm6v5Jd3425PmBJ7285+U2nda+0zAYDPTJyU8oak0UP+sZ9NUg2pu+t8V6ly5dIoFA0OzxiJZo7h229/0xWqZLCJDBYKBNmzaRRCKx6G5BRJSWlkYAKCEhodl28vPzKSQkhGbNmkVGo5HS09PJx8eHnn76aavG0dwv7/nz50mhUNCbb75JaWlpdPDgQfL29qZ33323xfbKysrI2dmZnnzySav61+v19Mb+N8jrAy9eiEJXhtLGSxutqn8nsTd9Lw3+ajAvPFFroui/J/7b4mHD65k7dy45OTlRSUlJm/q19A7b+/4YrXNXC9Cd5nDanAD169ePnnnmmUbX3njjDd4RsjkWLFhACoWi2XhDzVFaW0pTf5hKLktdyGmJEymXKGnglwPpUuGlNrXTGWRWZNL0n6ZT4H8DKeC/ARSyMoTm75hPVaqqNrVTUlJCSqXS6j8eDVh6h+19f4zWuasF6E5zOLX0y3vlyhUCQFeuXGl0/Z133qHevXs321ZiYiIJhUJavny51f3fyOlrp6n/uv78bMhlqQs9tPkhqlRXtrvNW4VKq6JX971KoStD+VnPxO8nUkJx8zPW1lixYgVxHEcXLlywus6N77C9749hHXe1AN1IZzqcElkWoC1btpBYLG7itzVjxgz629/+ZrEdk8lEI0eOpNDQUNJoNFb33xxfnvuSglYE8ULk86EPvXvo3TvCPmQwGOjLc19S709788LTf11/+iX5l5tuW6fTUXh4OA0ZMqTds9j2vD+G9XQpAeoMh9PrsSRAe/bsIYFA0EhIMjMzSSwW065duyy2s3XrVgJAv//+u1X9WoNer6fndj5H7svdeSGKWBVBvyX/1mF9tJWjOUdp2DfDeOEJXx1Oi/9cbLWdxxp2795NAGjz5s1Wlb/xHbbn/TGs564VoDvF4bS2tpYuXLhAFy5cIAD08ccf04ULFygnJ4eIiKqqqsjFxYVeeOEFysjIoAMHDlBERAQ9+uijFtvTaDQUHBxMo0aNsvqvdlvIq8yjsf8bS8qlSrN9aKmShq4fSmllaR3eV3Ncq7pGs3+ZTYErzHaeoBVB9MRvT1BJbdsMxtYybtw4CggIaPbdt/QO2/r+GG3jrhWgO8XhtCFA+o3/GmZiRER//vknxcTEkEwmo+DgYFqyZEmzf+WXLVtGEIB6vN2DDmdZPjLQERzOOky9P+3Nz4bclrnR4789Tmp90+fvKLQGLb1z8B3qsaoHP+sZ9e0oOpV36pb1eSTnCEW8E0EQgN577z2LZVp7h215f4y2wXzB7iCKiooQEhYCQy8D5BPk4DgO9/jdg8/HfQ4/pd8t6XPFyRX4+MTHqNHWAAAcpY5YOHAhXhjwQof282PCj/jgxAcoU5kPSLrIXbBw4EL8rdffOrSfBorqivDU70/hWN4xmMgEzU4NcBbISMuAr6/vLemT0XaYAN1BPPnkk/jtt9+wcvtKvHfuPRTVFQEApEIpHop6CB+N/AgikajD+9UYNFiwcwG2Xt0KvdF8EjzAKQD/Hf1fDAsedlNtXyy8iDf+eAPJpckAzM8yI3IG3hryFiRCyU2P/UYMBgNeO/AaNiZshMagAQB42nvin3H/xEsTXsLo0aPx3XffdXi/jHbSuROw5rlTfL2IzKeRAwICSCqVUlxcHJ061fFLhjNnzhDHcbRmzRoiMhuN/33w3+TzoQ+/TApaEUTrL6zv8L4bSCtLo/u/ub+RfWjMd2Mor9K64wvXU64qp6d+f4qCVwRTwH8DKHBFIM3aMotyqnJuwcjNbLi4gYJXBPOfl/eH3vTWgbf43b4vvviCANCJEyesbvN2/p7ZInesAN0pyQVvR0I7k8lE99xzD0VFRTXZGq9UV9JDmx/iDxU6LXGi/uv60+lrpzus/xvZdnUbRayK4PtzX+5Oz+18zqpte4PBQMuOLqOI1RG8nWfI+iF0KKv5mM03y7n8czTgiwGNzjtN+3Fak/NOBoOB+vTpQ3FxcVaHs70dySVtmTtWgCzRGb5e1iS0I7q5pHbff/89AaD9+/c3W+ZS4SUa+OVAUi5R8l+yqT9MpdLaUqv6aCt6vZ7eO/xeoxlY4IpA+vKc5c+fyCxcA74YwAtP9Npo+uzMZ7fMYFuprqRpP01rJM5x6+LoXP65Zus0HDLdsGFDu/q09HvGEhq2n7tCgDrL16u1bArX9+3m5kZvvfUWpaSk0NmzZykuLs4qH666ujry8/OjKVOmtFqWiGjjpY0UsjKE/8J5feBFb+x/45YdKqxR19CsLbMafclj18U2moGllKbQ5E2TG4XJeHH3i1SrtS5raVvR6/X01oG3yPtDb35MwSuCacNF60RlxowZ5O3tTTU1NW3u+8bfs5t594w7XIA629fLmoR2RDeX1O5f//oXSSQSysjIaLVsA3q9nl7e8zJ5fuDJfwG7f9KdNidZd9iuPVwqvET3fHVPoxnY+I3j6R9b/0EhK0N48Zn641S6Wnr1lo3j56SfG3n9eyz3oIW7F7ZJgLOzs0kmk9Hrr7/epr4t/Z6xhIY3xx0tQJ3t62WNAN1MUrucnBySyWS0aNEiq8Z9I4W1hTRp0yRyXurMO50O/nowJRYntqs9a9h0eROFrAwhu/fsSPRvEYnfFZNyiZLiP4+nHanWBwFrKymlKXTf1/fxAui81JnGbxxPhbWF7Wrv7bffbrPw3/h7xhIa3jx3tADdyO329bJmCXYzSe1mzpxJXl5e7VoKXM+RnCMU83kMPytwXeZKj/z8CNWqO34JdLnoMj3282Pk95Efid8Vk/DfQnJc7GhVnJ72UKuupcd+faxR5Me+n/a96UOadXV15OvrSw8++KBV5S39nrGEhjfPXSVAneHr1VJCO6L2JRckMp+uBUDffPNNm+q1xKenP6WA/wbwX1Tfj3xpyZ9LWq9oBSV1JfTWH2/R0PVDacj6ITR8w3Ca+uNUivkshvw+8jNvtf83kKb/NJ0yK6z7Y9Aay48uJ7+P/Pjn8f/Yn1afWt0hbRMRbdy4kQDQgQMHmi3T0u8ZS2h489yxAnSn+Hq1lNCOqH3JBQ0GA/Xt25f69+/f4dlN1Xo1zds+r5HTaeSaSNqV2j7HSa1eS1+c+4JGfzeahqwfQkPWD6EXd79I2ZXZdCDzAP3r4L9o5YmVTWI1v7rnVVJpLb+L1tibtpei1kb95Say3I3mbpvb4W4iDccfoqOjm12Kt/R7xhIa3jx3rADdKb5eRC0ntCNqe1K7L7/8kgDQ+xvfb+ETuDmyKrNo1LejGh0qHLFhBGVVZlndxr70fTTtp2m88MzaMouO5hzl76eUptC/Dv6Ln5X8kvwLxa2L44Wo96e96ctzX1q9DZ9dmX3TY24r//zunwSA1q5da/F+a79nLKHhzcFcMW4z1dXVCAoJgi5QB7e/uSHKIwqLhy9GL89et6S/fen7sHDfQuRV5wEAxEIxpkVMw38f+G+zucRSy1Ox8uRKJJWaA/zbi+0xK3oWZkbNhEjwlytIva4eHxz/ABw4vDb4NchEMhiNRiw/vhzfXv6Wz9cV4hyCd+9/F4P8B1nsT2PQYOGehdiSvAU6ozmQvJ+jHz4c+SEeCHugwz6L6zmTfwbP7HgGaRVpUG9RQ5IhQW5mLpydnW9JfwzLMAG6zbzyyitYs3YNRnw8AgnqBBAIQk6I0aGj8d6w9+Aid7kl/X5w9AN8cvoT1OpqAQBOMie8es+rmB83ny9TpanC2jNrsT9zP0xkgoATYGTwSMzrPw9KmdJiuytPrkSlphKze89GsHMwf720rhSLDizCweyDMJEJHMfhPv/7sHjYYvg6/eUMuu7sOiw+uhhVmioAgIPEAQv6L8Ciexd1/IcAoKyuDHO2z8HhnMMwkhEcOIRKQ5H8VjKe+sdTLKvqbYYJ0G0kNTUVUVFRePvtt/HPf/4Tf2T9gXcOvYPc6lwA5i/fE32fwPNxz0MoFHZ4/3WaOizYvQDbU7fDYDIAAIKVwVj5wErk1uTi+4TvUa83py2K9IjE8/HPo7tr9xbb/Dn5ZySUJGBY0DDcF3Bfk/tn8s/gnwf/iatlVwEAMpEMD0c9jKFBQ7Fwz0JkVGQAAEQCER4IfQCfjf0MDjKHDnxqMwaDAW8fehvrL66H2mCembnbueM/9/8HD0U/hGXLluHNN9/E5cuX0bNnzw7vn2EZJkC3kQkTJiAhIQFXrlyBXC4HYE4x/Om5T/H52c/52YmvwhdvD3kbo0NH35JxXC27ijnb5uBy8WUYyQiDyQClVIlA50D4Ovpibr+5GB483Kq2Tl47id3pu9HdtTtmRc9qttz/Lv8PH5/4GGX1ZajUVEJr1EIilEAsECPaIxqfjv8UkR6RHfWIjfgh4Qe8ffBtlKhKAABykRyP93kc7w59l48uoNVqERkZiZCQEOzevRscx92SsTAac9cmJrzb2LNnD7Zv344PP/yQFx8AEAqFWBC3AEcfP4pJPSZBJBAhvzYfT29/GtN+msbPEDqSHm49sPXhrXiy75MQCUQwkQkVmgrkVOUg1isWQwOGWt2Wn6M5TtG1mmto6W/Zw5EPY2bkTKgNamgMGpjIBIPJgIHdBmLDlA23RHwSixIx6OtBmLdjHkpUJRByQgwLHIaEuQlYPGJxo9AmUqkUH330Efbu3YsdO3Z0+FgYlmEzoNuAXq9H79694e7ujkOHDrX41/VK6RW8ceANXCi6AAAQC8SY2GMi/n3/v+Egufmlic6ow9HcoziedxwGkwE6vQ77svYhpzoHMpEMAk4AHwcf/HPIPzE2bGyr7RlMBiw5sgRGMuL5+OfhLG9qxN2Tvgf/+fM/uFZzDSYyQaVTwVnuDB+FDziOg0ggwuiQ0ZjXf16HPGOdpg5P73gau9N3w0hGAECYSxg+GfMJBnYb2Gw9IsKoUaOQk5ODxMRESCQdH6+I0RgmQLeBTz75BC+88ALOnz+PPn36WFVne+p2vP/n+yisKwQAOEmd8Ez/Z/CPvv9ol32IiJBQkoD9mfv56IeBykCMCR2DbanbkFmZidyqXJzKPwW9yRyUrJ93PywZvgTd3Vq2A31x7gvk1+ZjasRURHtG89czKjLw2v7XcK7gHAgEkUCEeN94dHPshiDnIAz2H4yVp1YitTwVAKCQKPBo70cxLWIaBIK2T84NBgOWHV+GtWfXol5ntmU5y53x+qDXMSd2jlVtJCYmonfv3li+fDleeumlNo+B0TaYAN1iysrKEBYWhhkzZuDzzz9vU12j0YiPTn6Eby5+A5VeBcAsGu8OfRf3BTY1+DZHfk0+dqfvRl6NeSteKVNidMhohLuFg+M47MvYh2N5x9DXqy8i3CKw6MAinC88D8A8AxvXfRzeG/Zes7OTXWm7cCr/FOJ94zEmbAzqdHV45+A75giL/y9mfbz6YMnwJUivSMe5wnMY6DcQo0NHw2QyYXfGbnx5/ktUqCsAAP6O/ng2/ln09+1v9TNuT92O1/a9hvzafACARCjBzMiZ+O+o/7Y5iuSCBQvw3XffITU1FZ6enm2qy2gbTIBuMc888wy+//57pKWlwd3dvV1tlKvK8fqB13Eg8wC/dXxPt3uwbMQy+Dk1Hyu6VluLA1kHcLHoIgDzl/Je/3sxsNvARud50srTsDFhI5xlznh+wPMAgJ1pO/He4fdQUFcAwBwrel6/eZjTb06TGVhCcQJ+vvIzfBzMS6rVp1ejWlsNAPB28Mbrg1/HxPCJAIDVp1ejTFWGh6IeQrhbON+GxqDB1xe+xm8pv/FngeJ94/Fs/LO8nckS6eXpeHr70zhfdB5EBI7jMNB3INaNX9fuONrl5eUICwvD1KlT8cUXX7SrDYZ1MAG6hVy+fBl9+/bFhx9+iBdffPGm27tYeBGvH3gdV8quAGg+vrLBZMDJayfxZ86f/Je5t2dvjAgeAYVU0aRdrUGLpUeXgkB4ccCLcJI5ATDPwFacWoGvLnzFz8ACnALw76H/xtCgoXz9SnUlFu5ZiMPZ/3+2huMgF5t3ml4a8BIvWHW6Onx4/ENw4PDqoFchF8tvHAoKagqw+sxqHM87DuAvG9iTfZ+EncSOL3cr41gDwOrVq/Hcc8/h7NmziImJuen2GJZhAnSLICIMHz4cBQUFuHz5cocaNG/MMOFq54qFAxZiVvQsXC2/ij3pe1CpqQRg3qV6IPSBFmcRALDu3DoU1BbgwYgHm5zKrlBX4I0Db2Bfxj5+BjbQbyCWjlwKIYR4Zd8r2J2xGyYywVHqiAdCH8D7978Pd4fGM77EkkRsSd4CLwcvzI2d2+J4zhacxerTq5FdlQ0AcJY54/G+j2N82HisOb0GH574kJ9lKaQKvDjgRSwcuNDqz7A1DAYD+vTpA2dnZ/z5559sW/4WwQToFvHrr7/iwQcfxI4dOzB2bOu7SW1FZ9Rh8ZHF+CHxBz77g4e9Bwb4DoCLnQsUEgVGBI9AL89eVn159mbsxfG844jxjsHEHhMtlrlcdBmvH3gdSaVJICLeviMWiKHSq+Akc8Lb972N2X1mW6y/I3UHzhSc4W1FrWEymfBryq/49tK3qNZWQ61X41rNNf6wpFggxoQeE7B23Npm3Upuhn379mHUqFH48ccfMWPGjA5vn8HOAd0yPDw88Pzzz98S8QHM9px3hr6DA48ewJCAIeDAoaS+BNvStuFazTU81vsx9PbqbfVf7kBlIAAgpyqn2TK9vHphxyM78NLAl8zb6XoVVHoVDGTAQ5EP4W+9/gZ7iX2z9RtmMw19tYZAIMDUnlPx2fjPYC+2R2p5Ki8+fbz64OgTR/H1pK9vifgAwMiRI7Fw4UJmiL6FdHySKQYAYNCgQRg0yLLzZUfi6+SLDVM24LtL32HlqZUoV5UjvSIdf9/6dzwS/QhmRs60akvb38kfHDiUq8tRq621aCuqUFdgb8ZelKvLMSZ0DHal7wKB8EDoA+AEHBKKE5rN9VWvq0epqhQAEKAMsOrZTCYTtlzZgm8vfousqiwA5lPMz8U/h9fvfd2qNm6Wjz766Lb0Y6swAeoiTI+cjoyKDKRXpqNGW4MabQ0+P/c5dqTtwPz+81s8gAeYfbS8HLxQWFeI7KrsRud5tAYtjuQewYm8EzCSEQJOAHd7dwwJGALAvPQTckJcVl/Gvox9iPKIwqiQUY1mJjnVOXxZO7EdWuNM/hmsOrUKuTW5qFBXwGA0wN/JH8OChvE7dYy7HyZAXQSZSGaeWXDAyKCRSCxNxC9XfsG1mmt4/cDr6O/TH8/GPwt/J/9m2whUBqKwrhA51TmI9owGEeFS8SXsz9yPOl0dAHNojVEho/DNxW/g4eABIoJCosATfZ9AXk0esquysS9jH66WXcXw4OHo49UHAk5g9fLrWs01fHLqE5zOPw0AUOvV6ObYDV4OXpCKpIj0iLRKwBh3B0yAuhBhrmHIqc5BTk0Ono59GpPDJ2PV6VU4lnsMZwrO4MmtT2J89/F4KuapRlvaDQQoA3Di2glkV2UjrzoPu9J3oaDWfA7IRe6CB0IfQJhLGPJq8qA2qOFh5wGBQACVXgW1QY0p4VOwP3M/wAH1+nr8fvV3nMk/gwdCH2hVgFQ6Fb668BV+v/o79CY9OHDo7todjlLHRnalMJewDv/cGJ0HE6AuRJhLGPZn7kdWZRb0Rj08HTzx3rD3cLHwIj45/QkyKzPxa8qvOJh9EI/1fgyTekxqZB8KcAqAzqjDnzl/oqC2ABKhBFKhFEMChyDeNx5Cgfk8T1p5mrk/1zAIOAEuFV9CWnkafB194WrnikCnQPRw64HDOYdRWFeIdefWIa0iDcHOwQhwamz/MZlM2J62HesvrOePDgQqA/Fw1MM4W3AWepMefTz7f+DuXQAAU+NJREFUIKk0CXqTHmGuTIC6EmwXrAvhYe8BR6kj9CY9b3MBgD7effDlhC/x4oAXoZQpUaWpwspTK/HktidxsfAiAEBv1ON0/mkklSShpL4E1ZpqxHjH4Nn4Z3FPt3t48QGAtIq/BKhBENIq0vizRoV1hRjgNwDPxj2Lft79UK2pRkl9CZJKknA6/zR/OPJy8WXM2T4HH5/4GJWaSjhJnfBs3LP4eNTHSChJgN6kR6hLKCI9IqE36eEgcYC3g/ft+CgZtwk2A+pCcByHMJcwnCs8h7TyNIS6hPL3BAIBJoVPwsjgkfji/BfYnrodWZVZeHHPiwhQBiDYORgmMvH+XgP8Blg8D1SjrUFRXRE4cAhxDoGAE/BHABri+2iNWpSpyuBu744JPSagRleDa7XXYCe2w+GcwziSewTZVdnIqszinVTHhY3DU/2egkggwtcXvkadrg6e9p6Y3nM6DmUfAgCEuoSyA4FdDDYD6mI0zEhSy1Mtxuexk9jh+QHP46tJX6G/T38QCNlV2TiccxgV6gpM6zkNfbz68B7zN5JekQ4A8HX0hb3EHnKxHN2cugEAMisz4aPwAWA2JjdQq61Fb8/emNZzGqo0Vfgz509kVmaCQOjn0w9fTfwKLw58EXKRHD8m/oiS+hIoJArMip4FqUj614yL2X+6HEyAuhjBzsEQckJUaip573JL+Dv544NRH+Cf9/0TCokCRpMRMpEMmZWZqFBXoKS+hA9pcT28/ec6MWj4f1p5WqMAZYB5F6u4rhgV6gpkVWZBKpTCYDLAQeKARYMW4aNRHyFAGQAiwrbUbciqyoJEKMEjvR6Bk8wJlepKlKnKIOAECHEJ6bDPiXFnwASoiyERSviDfg0zh5YYETwCU8KnoIdrD6j0KtTqapFZmYmEkgRcKLzQqKzRZERmZSYANDIGN/w/szITXg5eAMCHxbhYdBGXii8hozIDtbpa1Ovr0cO1ByaFT2qU8eJI7hFcLLoIASfA9J7T+XYaZlzdHLvdshPPjM6DCVAX5PoZiTVEe0bDW+GNCLcIDPYfDBe5CyrUFfjs3GfYnb6bT6+TV5MHrVELe7F9I2Owp70nFBIF9CY9TGQCYI5BtP3qdnx69lNUqCvgLHPGPd3uQaR7JLwV3ujl8ZfD6+Xiy/gj6w8AwNiwsY3E7XqDN6PrwQSoC9LwZc2uyuZ3nFqip3tPCDgBSlWl6OvVF/P6z4ObnRsqNZU4ee0kVp1ehTP5Z/jMFjcagzmO4/vMr81HtaYaJ6+dxP6s/ahQV8BV7oq5sXPR36c/iuuLwYHjY0BnV2Vja8pWAMCgboMQ6xPLt6s36pFVaXbBYPafrgkToC6Iq9wVzjJnGMnIf4Fbwl5iz+f0SixJRG/P3ojyiEKocyiUMiVUehV2pO3A+ovrUamutDgbCXMJQ5WmChsubsC1mmv8bCjEOQTRntHo49UHiSWJAIAg5yA4SBxQpirDj4k/wkhG9HTviRHBIxq1mVOdA71JD0epIzzsPTrgk2HcaTAB6oJcPyOxxg4EAFEeUQCAhJIE2Int4G7nDme5M0YGjzQHpyegqK4Il4ov4ULhBVSqK/m6VZoqXCi8gEtFl1BcXwwHiQNCXUIR6BQIZ7kzXOQuUEgVvABFeUShXlePjZc3Qm0wu1pMCZ/SZIv9eoM3237vmrBzQF2UMJcwnM4/jbTyND5UaUtEuEVgu2A7ylRlKK4vRqAyEKWqUuTV5OGB0AegM+qQVpGGen09MiozsObMGvTz7geO43C24Kw5t5hMCblYjgdCH8CVsitIKU+Bt4O3ua36UhTXF0PICRHqHIpNiZtQqamEs8wZD0U9BLFQ3Gg8RMQHq2f2n64LE6AuSqAyECKBCNXaapSqSltdwkhFUoS5hOFK2RUkliQiQBmAMwVneB+uvOo8hLmGIcY7BlWaKmRWZuJU/im+fpAyCLHesThbeBZqgxoCToDC2kK4yF0QqAxEQkkCALMz6670XbhWcw1ykbzZGEIV6gpUaioh5IQIUgZ13AfDuKNgS7Auilgo5r+41u6GNSzDEksSeZ+t4rpi1Gpr+e33ON84PNrrUTwc9TBfr4drD8zuPRvxfvEAzGeAnCROqNXVolZbC39Hf375VaurxZWyKxByQjwU9RBc7VwtjqVh6RigDIBUJG3r4zPuEpgAdWHaagfq7todEqEEVZoqVGmq4GbnBgLh5LWT0Jv0UEgU8LT3BMdx6OHWgzcaN8RmdrNzg1KmhMFkQI3OfJLaBBNUBhUq1BUorivmDyhODp/cYmAySwceGV0PJkBdmIYvb251LrQGbavlxUIxnyrn+llQQ2yeMNfGxuAY7xiIBWIU1RUhtzqX90UDzAZrABBxIiQUJ6BcVY4KdQWEAiGGBw1vFPDsRnRGHb/0Y/afrg0ToC6Ms9wZbnZuMJGJX0K1RsMyLKk0iQ9ellBstt/cOBuxE9vxGTQa7EHXnwciIpjIhGN5x5Bcmgx3e3fEeMdgsP/gFseQVZkFIxnhLHOGq9zyEo3RNWAC1MVp8Ii3dhkW4hwCuUjOR0BU69Uoqi+CiUz8WaHrabD7XCm9gipNFQKVgSAiVGmqQCCoDWoczj4MjuMQ6xOLcWHjWt2Raxgr837v+jAB6uJc75ZhTQYmoUCInu49Afx1EBAA7MX2Fo3BHvYeCHYOBoFwJv8MJEIJFBJzQHuJQIKE4gTU6moR4hyCh6IeahRXyBJE1CjgGaNrwwSoixOgDIBYIEatrhbF9cVW1WlYhiWXJvO+XRJR84kV433Ns6BzheegM+ogFUtBRKjUVKJcXQ4TmTAvdp5VzqSlqlJUa6shEojY9rsNwASoiyMSiPilk7Xb8QHKACgkCtTr6lFWb86+SqbmZ09hrmFwljlDY9DgcvFlGI1Gc3ofXS1MZIKXgxd6efVqtv71NIwxUBnY5HAio+vBBMgGaOt2vIATINIjElWaKtTr6yETyVCjrWl2J03ACXhb0LHcY7hcfBkagwYcx8FR6ghXuatVu3DXj5Ftv9sGTIBsgIYvc151Hh9aozWiPKJQri5HtbYavo6+AGfezm+OPl59IBFKzHGlS5PgKHWEQqKAWCCGh70HHx+oJTQGDd8Hs//YBkyAbAAnmRM87D1AIGRUZlhVx8fBB2q9GiYy8bF/rg90fyMykQw+Ch8klyajRluDcLdwOEgcIBAI4Ch1RH5N6wKUWZkJE5ngKneFi9zFuodj3NUwAbIR2hqkrFxdDoVUAQEn4I3HDYcDLVGmKkNWZRZUehU4jkOQMggCTgA3uRvq9fWNYkQ3B9v9sj2YANkIDV/q9Ip0q7bj0yvS4WHvYY4HpFPBYDKgoLbAYoCzhtAaJjJBIVHARe6C9Ip0OMuc0c2pGyrUFbhWc63FfomID7/K7D+2AxMgG6GbYzdIhVLU6+v5bKctkVaRBgeJA0KcQyARSaAxaGAiE/Kq8xqV0xv1fGgNjuPQ37c/TGRCaX0pujl1g6e9J6o0VVAb1C0GyS+uL0atrhZigbhFHzFG14IJkI0gFAj5rBKt7YZpDVrkVJntPfcF3AfALDRA42UYEeHXlF/50BoxXjHwsPeAVCiFCSb4KnwhFAhhJCP0Rn2LhuiG5VewczBEAhYlxlZgAmRDWGsHyqoy+2K5yF0wyH8QALMoXe8kCgD7M/cjuTQZQk6ImVEzUaWtgsFkgLPcGYDZqdTNzg0KiQKVmsoW7UAs+Lxtwv7U2BANfmEFtQWo19VbDAQGNA6F4SJ3ga/CF2q9GqX1pZCL5NAZdbhUdAnH8o4BACaFT4Kfox+u1VxDqaoUQcogEBH0Jj3kIjkcpY68HcgSar2aX9pdn82V0fVhMyAbQiFVwNvBG4S/DL43QkRNZiNRHlHmw4i6GhjJiCO5R7AzbScA4P7A+9HLsxeu1VyDwWRAtbYaDhIHDAkYAsAcL9pR6ohyVTkKawv5pdz1ZFRmgEC80ZthOzABsjFaOxVdUl+CGm0NxAIxApWBAIBIj0gIOAFA5u327xO+B4HQx6sPbyPKqcqB1qDl409PiZgCDhyqtdXgwIHjOD6v/I2w4GO2CxMgG6PhS55RkcE7ml5PgzAFOQfxxmBHqSMClAGwE9vhdP5plNWXIdg5GBO6T+DDZWRXZaNUVQonqRO6OXZDgDIAEe4REHACGMkIhVSBcnV5k2WYpRkXw3ZgAmRj+Dr6Qi6SQ21QW7TJNDcb6e7aHddqr6FKUwUjGTElfAofWsNgMiCvJg/FdcVQypR8tMMGL/k6XR3sRHYW7UAFtQVQ6VWQCqXo5titw5+XcWfDBMjGEHAC3tB7ox1IY9Agr8ZsDL5+NmI0GXGl7Apvvwl2DkaZqoy/X1BbgFptLTRGDezF9nw8IX8nf3g7eMNJ6gSNQYMabQ2yqhonSmyY/YS4hLQaK4jR9WACZIPwdqAbtuMblmXudu68MZiIsCNtB/Kq8+Bh74Eerj1Qo61p5BeWXZWNkvoSKKVKBLsEw0HiAMCcIDHeLx5SkRQGkwGAOdxqQ7TF68fA7D+2CRMgGyTEOQQcOBTWFaJWW8tft2SLOZZ3DOcLz4MDh0eiH4G3whsl9SWNUj7zAiRTItqjcbD5KI8o2IvtoZAqYCJTIztQve6vU9ls+902YQJkg9hL7OGj8AHw1zLMki9WYkki9mfuBwA8EPoAHgh9AK5yV6j0KqSUpcBgMvDLs3p9PVzkLnxWjQZEAhFifWLhIneB1qBFhbqCP/OTXpEOAsHLwQsKqeK2PDvjzoIJkI1y43Z8YV0h6nR1kAgl8HfyR251Ln5L+Q0AMMBvAL+Uaoj7U1BbgPyafBTUFqCgpgBigRi9PXtDLpY36SvWJxbOMmcQCPW6ej5LKgs+xmACZKNcvx1vNBl5W0yIcwiqNFXYlLAJBpMB4W7hGBUyiq8X7RkNJ5kTvwzLqsxCcX0xnGROzYZdVUgViPaMhqeDJ2q0NUgqTYLBZEBGhTk2Edt+t12YANkoPgof2IvtoTVqkVeTx89G/Jz8sDFhI9QGNXwVvpgaMdV8CPH/CXMJg4edB7RGLc4XncfF4ovQGDRwk7uhu2v3ZvuL9403BzkzqFFQU4BLRZegNqghF8nh5+h3y5+XcWfCBMhG4TiON/wmFCcgvyYfJjLhQuEFVKgroJQp8XD0w00Cw4uFYvT36Q8AOF94HucKzwEA+vn2g0TYfOYMX0df9PLsBYlQgsK6QhzJOQLAvP1+vcAxbAv25m2YhqXPyWsnYSITCusKUaYqg0wkwyPRj/Db6TdyT7d7IBaKkV2ZjczKTIgEItzb7d5W+xsSOARKmRK12locyTULELP/2DZMgGyYhu34tIo0XC2/Cq1Baw6tETkT7vbuzddzCYGHvQcqNZUoqy+Du507Ql1b30aPcItAkDIIepMeSaVJ4MCx7XcbhwmQDSMXy+Hr6IucqhyklafBVe6KiT0mIsi55YSAQoEQ/bz7oUZbg1pdLfp49bEqiJhQIMS47uNgMBlQpiqDq9y12ZAgDNvg/9q78/iYrv9/4K87a7aZ7HtkIbEltohEimpRSik+LaorXRSl1S9tVT+6k1I+tRdd0M9H0aq2lGhRYilCQpB1ZCX7vk0mmeX9+2N+uZVmm0RkYuY8Hw+PR3PvPefcyc28e+5ZOTJkgWDmvrB161bs2bOnTWlKlCVIL08HBw7d7bsbvBxGZW0l4gvjQSAEOgdCLpUblE5LWlzOvgwd6eBr5wt3mXub7vfJJ5/EwoUL25SG6brYgmQmxM7ODt7e3m1K46nzRBCCUFpT2uJr1z8REfx8/cCBg1QkbVNDsoOrA9++1Nb5X/b29m26nunaWA2IYRijYW1ADMMYDQtADMMYDQtADMMYDQtADMMYDQtADMMYDQtAZqi8vBxz5syBv78/+vTpg9zcXIPSaTQarFixAuHh4QgODsYLL7yAY8eO3bPyGNPHApAZeu2113D9+nWsXr0amZmZqKmpAQC8+eab2LRpU7Ppli5dii1btmD06NGYMmUKamtrMXHiRMyePRstjeZob3mMGSDG7Dg4OFBsbCwREdnY2FBqaioREUVGRlJISEiz6dzd3SkqKqrBsbS0NOrbty+tXr26w8tjTB+rAZkhIoJM1ngJ1ICAACgUze8bX11dDS+vhmv3+Pn5YePGjdi+fXuHl8eYPhaAzND48eOxe/fuRserq6v5jQabMnz4cOzatavRcT8/P+Tk5HR4eYzpY3PBzFBERARCQkIAgN9KWaVS4ZNPPkFwcHCz6VatWoVhw4ahtLQUCxcuREBAANRqNTZu3Ii+fft2eHmMGTDuGyBjLAqFgsaOHUscx5GTkxNJpVJydnamS5cutZguNjaWQkJCiOM4kkqlJBKJyMnJic6ePXtPymNMG5uMauaysrIQFxcHsViMsLAwg2ebJycnIz4+HjKZDGFhYZDLDVuOo73lMaaJBSCGYYyGNUKbmaKiIqxevRpTp05FeHg4wsPDMXXqVHz++ecoLCxsV563bt3Ciy++2OS5mpoanD17FgkJCY3OqVQqfPfdd+0qkzENrAZkRi5duoRx48bBysoKY8aMgaurKwAgPz8fJ06cgFKpxO+//843GBsqLi4OwcHB0Gq1DY6npKRg7NixyMrKAsdxGD58OPbu3Qt3d3e+XA8Pj0bpGPPBApAZGTp0KAYMGICtW7c26v4mIsydOxfXrl3D+fPnG5w7ePBgi/mmpaVh8eLFjQLJ1KlToVarsXPnTpSVlWHRokVISEjAqVOn4O3tzQIQwwKQObG0tMSVK1fQu3fvJs8nJSVh0KBB/FSJegKBABzHtTjdguO4RoHE1dUVx48fR79+/QDog9z8+fNx5MgRnDx5EtbW1iwAmTnWBmRG3NzcEB0d3ez56Oho/rXsTu7u7jhw4AB0Ol2T/2JjY5vMr6amBiLR30PNOI7Dl19+iUmTJmHkyJFISUm5+w/F3NfYQEQzsmTJEsyZMwcxMTEYPXp0ozagr776CmvWrGmUbvDgwYiJicHkyZObzLe52lHv3r1x+fJl9OnTp8Hx+gmojz/++N1+JOZ+Z4zBR4zx7N27l8LCwkgkEhHHccRxHIlEIgoLC6N9+/Y1meb06dMUGRnZbJ5VVVV06tSpRsdXrlxJ48ePbzbdvHnziOO4tn8IxmSwNiAzpVarUVRUBABwcnKCWCxuJQXDdDwWgBiGMRrWCM0wjNGwAMQwjNGwAMQwjNGwAGRmCgoKmuxqB4D169c3u7BYZ6djzIRxO+GYzpaQkEBubm40f/78BseXLFlCTk5OdPXq1S6RjjEPLACZoaSkJPL09KTZs2eTVqulhQsXkqurK8XFxXWpdIzpY93wZio1NRWjR4+GWCyGUqnE8ePHG41Y7grpGNPG2oDMVI8ePRAeHo7U1FQMGTIEvXr16pLpGNPGApAZIiI8++yzuHDhAqKiopCcnIzp06dDo9F0qXSMGTDqCyDT6dRqNU2bNo38/f0pKyuLiIjy8vIoKCiIJk2aRLW1tV0iHWMeWA3IzERHR0OhUODMmTPo1q0bAP26PSdPnkReXh7OnDnTJdIx5oE1Qpsh+v97cxl63FjpGNPHAhDDMEbDXsEYhjEaFoAYhjEaFoAYhjEaFoAYhjEaFoDMTHt3Ku3sdIyZMNYAJKbzJScnk4+PD3EcRwKBgB588EHKycnhz+fl5ZFAIDB6OsZ8sBqQGXnnnXcQFBSEgoICJCcnQyaTYdiwYcjKyupS6RgzYuwIyHQeFxcXunbtGv+zTqejuXPnkre3N6WmpjZbI+nsdIz5YDUgM9LenUo7Ox1jPtjOqGakvTuVdnY6xnywGpAZmTp1Kvbs2dPkuU2bNmHmzJlNbrHc2ekY88HmgjEMYzSsBmRmEhMTsWPHDiQlJQEAkpKSMG/ePLz44ov4888/u0w6xkwYtQmc6VSRkZEkkUjIwcGBLCwsKDIykpydnWnMmDE0atQoEgqFdOLECaOnY8wHC0BmJDw8nN577z0iItqzZw/Z29vTsmXL+PNLly6lRx55xOjpGPPBApAZkcvlpFAoiIhIq9WSSCSi2NhY/vz169fJ1dXV6OkY88HagMxM/QqEAoEAFhYWsLW15c/JZDKUl5d3iXSMeWAByIz4+vpCoVDwP58/fx7e3t78z1lZWXB3dzd6OsZ8sIGIZmTevHnQarX8z0FBQQ3OR0ZGYtSoUUZPx5gPNg6IYRijYa9gDMMYDQtADMMYDQtADMMYDQtADMMYDQtADMMYDQtADMMYDQtADMMYDQtADMMYDQtADMMYDQtADMMYDQtADMMYDQtADMMYDQtADMMYDQtADMMYDQtADMMYDQtADMO024oVK8BxXKPF5gzFFiRjGKZdbt++jV69eoHjOPj6+uLGjRttzoMFIIZh2uWpp55CYWEhtFotioqK2hWA2CsYwxhBdnY2XnrpJXh4eEAqlcLPzw/z5s1DXV0dACAtLQ3Tpk2Dg4MDrKysMHToUBw+fLhBHqdOnQLHcfjhhx+wYsUKeHl5wcLCAqNHj8bNmzf56xYsWAAbGxsolcpG9zFz5ky4ubk1WLvbEKdPn8b+/fuxbt26tn/4O7BF6Rmmk+Xk5CA0NBRlZWWYM2cOevfujezsbOzfvx9KpRKlpaV44IEHoFQq8frrr8PR0RG7du3C448/jv3792Pq1KkN8vvss88gEAiwZMkSlJeXY/Xq1XjmmWdw8eJFAMCMGTOwefNmHD58GNOmTePTKZVKHDp0CLNmzYJQKDT4/rVaLRYuXIiXX34Z/fr1u7tfhjE3JWMYc/T888+TQCCgS5cuNTqn0+lo0aJFBIDOnDnDH6+srCQ/Pz/y9fUlrVZLREQnT54kANSnTx+qra3lr12/fj0BoOvXr/N5enp60hNPPNGgrB9++IEA0OnTp9t0/5s2bSJbW1sqKCggIqKRI0dSYGBgm/Kox17BGKYT6XQ6/PLLL5g0aRJCQkIanec4DkeOHEFoaCiGDx/OH7exscGcOXOQkZGBhISEBmlmz54NiUTC/zxixAgA+te4+jynTZuGI0eOoKqqir9u37598PT0bFBOa4qLi/H+++9j+fLlcHZ2Njhdc1gAYphOVFhYiIqKiha7rTMzM9GrV69Gx/v06cOfv9Odmz0CgL29PQCgtLSUPzZjxgzU1NTg4MGDAICqqiocOXIE06ZN43evNcS///1vODg4YOHChQanaQkLQAxzn2uu/Ybu6OAeOnQofH198cMPPwAADh06hJqaGsyYMcPgchQKBbZv347XX38dOTk5yMjIQEZGBlQqFdRqNTIyMlBSUtKme2cBiGE6kbOzM+RyeYtd1j4+PkhOTm50PCkpiT/fHtOnT8fRo0dRUVGBffv2wdfXF0OHDjU4fXZ2NnQ6HV5//XX4+fnx/y5evIiUlBT4+fnh448/btM9sQDEMJ1IIBBgypQpOHToEC5fvtzoPBFhwoQJiI6Oxvnz5/nj1dXV2L59O3x9fdG3b992lT1jxgzU1tZi165dOHr0KKZPn96m9EFBQfj5558b/QsMDIS3tzd+/vlnvPTSS227qXY1XTMM0263b98mNzc3srKyokWLFtG2bdvoww8/pMDAQCotLaW8vDxydXUlW1tbWr58OX3xxRc0cOBA4jiODhw4wOdT3wv2448/Nsg/PT2dANCOHTsale3v708ymYwAUExMTId8nrvpBWPjgBimk3l6euLixYtYvnw5du/ejYqKCnh6emL8+PGwsrKCnZ0d/vrrL7zzzjvYuHEjVCoV+vfvj0OHDuGxxx67q7JnzJiBFStWwN/fH8HBwR30idqPTcVgGMZoWBsQwzBGw17BGIZBVVVVg0GKTXF2dm7TlA1DsADEMAzWrFmDjz76qMVr0tPT4evr26HlsjYghmGQlpbGT91ozvDhw2FhYdGh5bIAxDCM0bBGaIZhjIYFIIZhjIYFIIZhjIYFIIZhjIYFIIZhjIYFIIZhjIYFIBNx4MABrFixAjqdzti3cs8QEVatWoV9+/YZ+1aYDsICkAlQKpVYtGgRLl26BIHAdB8px3GIiYnBm2++2eq0Aeb+YLp/rWbk888/R35+PtasWWPsW7nnVq9ejdLSUkRERBj7VpgOYJIB6Msvv0T//v0hl8shl8sRHh6OyMhI/nxERASGDBkCmUwGFxcXTJkypcklMO9WZ5Rz69YtrFq1CosWLYK/v3+H5t0V+fr6YsmSJVi7di3S09PblHbz5s3w9fWFhYUFwsLCEB0dfY/ukjFYhyyJ1sUcPHiQDh8+TCkpKZScnEzLli0jsVhMN27cICKicePG0Y4dO+jGjRt09epVmjBhAnl7e1NVVVWzeZ49e5bq6uoaHY+Pj6e8vLwm07SnnLaaOXMmubq6Unl5eYfl2dVVVVU1uc9VS/bu3UsSiYS+/fZbio+Pp1deeYXs7OwoPz//Ht4p0xqTDEBNsbe3p6+//rrJcwUFBQSAoqKimjyv1WppwIAB9OSTT5JGo+GPJyUlkaurK61atcqge2iunMzMTJo5cybZ2dmRvb09Pf3001RSUtJqfmfOnCEA9O233xpUvin53//+RwDozz//NOj60NBQeu211/iftVoteXh4UEREBH+svc+BaT+TD0AajYb27NlDEomE4uPjm7xGoVA02EmyKdnZ2dSjRw96+umnSavV0s2bN8nDw4NeffVVg++lqXIUCgU5OTnR8uXLKSkpiS5fvkyhoaH00ksvtZiXVqul4OBgGjx4ML9TpjnR6XQUHh5O/fv3J7Va3eK1tbW1JBQK6eeff25w/Pnnn6fHH3+ciNr/HJi7Y7IB6Nq1a2RtbU1CoZBsbW3p8OHDTV6n1Wrpscceo2HDhrWaZ2ZmJnl7e9OMGTPI29ubnn/+edLpdAbdT3PlPPLII/T+++83OLZ//37y8/NrMb9vvvmGANC5c+cMKt8URUdHEwD68ssvW7wuOzubANBff/3V4Phbb71FoaGhRNT+58DcHZMNQLW1taRQKOjy5cu0dOlScnJyarIGNHfuXPLx8aFbt24ZlG9UVBQBoO7du7f6f97WysnIyCAAZGlpSdbW1vw/CwsLCggIaDav8vJycnV1pZkzZxpcvql64YUXyNHRscVXpdYCUHufA3P3TDYA/dPo0aNpzpw5DY699tpr5OXlRWlpaQblkZeXR7169aJJkyaRm5sbLViwwKB0zZXz66+/koODAykUikb/bt++3Wx+b7/9NllaWlJWVpZB5ZuynJwcsrGxoUWLFjV7TWuvYO19DszdM5sA9PDDD9MLL7xARPr2g9dee408PDwoJSXFoPSFhYUUGBhIU6ZMIbVaTfHx8eTs7EyLFy9uNk1r5Rw5coTEYjFVV1cb/DlSUlJILBbTRx99ZHAaUxcREUEikYgSEhKavSY0NLTB/zC0Wi15enpSREREu54D0zFMMgAtXbqUoqKiKD09na5du0ZLly4ljuPojz/+ICKiefPmka2tLZ06dYpyc3P5f0qlssn8tFothYSE0IQJE6i2tpY/fvXqVXJwcKD//Oc/TaZrrZzi4mJydHSkJ554gq5evUoKhYIiIyPpjTfeaPazTZo0iby9vdmX5Q41NTXUvXt3GjduXLPX7N27l6RSKe3cuZMSEhJozpw5ZGdnR3l5ee16DkzHMMkA9OKLL5KPjw9JJBJydnam0aNH88GHiAhAk/+a2kmy3h9//EE1NTWNjsfGxjbbfmRIORcvXqSHHnqI5HI5yWQyCg4OpvXr1zd7DwDI72U/OpN5xrBfhhm4nn+dBr85mAA029lARLRx40by9vYmiURCoaGhdOHCBf5cW54D03HYmtD3CY1Gg8B+gUirTYPVK1YQCoQY4T0C2yZtg5uNm7FvzyjKa8rx3sn3cPTmUai1ahR+WQgP8kBCfAIkEomxb48xgElOxTBFW7duhSJZgZWfr4SbjRt0pENUZhQGbh2Ixb8vhkajMfYtdhqtVot1F9Zh+I7h+C3lN2h0GnjbemPdunVIT0vHpk2bjH2LjKGMXQVrry1btlC/fv1IJpORTCajoUOH0pEjR/jzK1eupJCQELKxsSFnZ2eaPHkyJSUldfh9REVF0cSJE8nd3Z0ANOpp6QhFRUVkb29PL7/8MhERqdVqWn5iObl97ka2EbZkG2FLPdb3oF1Xd3V42V3NUcVRGvbNMPL5wod8vvChoM1BtOHCBn6E+vz580kul7dpikVnPEOmafdtDcjLywufffYZYmJicPnyZYwaNQqTJ09GfHw8ACAqKgqvvfYaLly4gGPHjkGtVmPs2LGorq5uNs9z585BrVY3Op6QkID8/Pwm01RXV2PAgAHYvHlzx3ywJnzwwQfQarX49NNPAQAikQgfj/oYiQsS8Uj3RyDkhChSFuH1yNcR/nU4YnNi79m9GEtqSSqm/TgNc3+bi9sVtyESiDCp5yScnX0WC8MW8jt2fvzxxxAKhfj3v/9tcN6d8QyZZhg7AnYkY8/3Qgv/97x+/TqNHz+eZDIZubq60v/93/816FFrzrVr10ggENCaNWuavSYmO4ZCt4fytSGHzxxo+g/TqbSmtNX8u7rK2kpafHQx+a/352s9k/dMpoSC5rvc169fTxzH0ZUrV9pcXnPPsL3Pj2mZSQSgrjLfq7k/3tjYWJLJZPTee++RQqGgkydPkru7O3388cct5qfT6Wj06NEUEBBg0B/7jis7yG+dHx+I3Ne404cnP2zTiO2uQqPR0PbL26n/lv584An9KpR+Tfy11bR1dXXUp08fevDBBw2eKlOvqWfY3ufHtO6+DkBdbb5XcwFo8ODBNH/+/AbHli1bxs9Das4vv/xCAOjQoUMGlU+kbx96I/INclntwgeiXht70S8Jvxich7GdzTxLD+94mA88vTf1ps/OfNagZtqao0ePEgD64Ycf2lR2U8+wvc+Pad19HYC62nyvpv54ExMTCQAlJiY2OP7hhx/SgAEDms1LpVLxg+va+n9xIqLcylyauHsi2X9mT7YRtmQXYUcjd4ykpMKOb4jvKLfLbtOzPz1Lvut8yecLH/Jb50cv//oyFVQWtCu/+kGbzQ0wbco/n2F7nx9jmPu2ERoAJBIJ/P39MXjwYERERGDAgAFYv359g2sWLFiA3377DSdPnoSXl1ereebn52POnDmYNGkSlEol3nzzzbu6x/j4eIjFYvTs2bPB8YSEBPTr16/ZdOvWrUNmZia++OILcBzX5nLdbNxw6OlD+GXGL+hu1x0EwtW8qxj+7XDM+mUWqlRdZ03lOm0dPjj5AUZ9Nwpnss6AiNDbqTd+nPYjvnr8KzjbOLcr37Vr1yI3N/eulqpt7/NjDGTsCNiRjDHf605oogb0+++/k0AgIJVKxR9LS0sjsVhMkZGRTeZTP8GyI6cCbLiwgbz/482/lnmt9aI155pv2O4su6/tpuBtwfzrVvC2YNp9bXeH5b9kyRKysrIyuPb7z2fYnufHGO6+DUBdZb5XZWUlXblyha5cuUIA6D//+Q9duXKFMjMziYiorKyMHBwcaNGiRZSamkonTpygPn360HPPPdfsZ5s1a1arS0y0R426hl45+Ao5rXbiA1G/Lf3oD8UfrSfuYDE5MfTofx/lA0/PDT3p/T/fp1pNx/YslZWVkYuLCz3zzDPNXtPSM2zP82MMd98GoK4y3+vkyZNNllNfEyMiOn36NAUHB5OFhQV1796dIiIimm1QrV9k65mlzX9h7paiSEGjd40mu8/s9O1Dn9nRuP+Oo1ulhtUS7kZBZQG9/OvL5LfOj3y+8CHfdb707E/P0u2ye7fsxez3Z7e4eFtrz7Atz49pGzYXrAshIoSEhSDuVhysF1ijp3NPbHlsC4Z4Drkn5R1JOYK3jr2F7MpsAIBEKMG0wGn4YuwXkIg6di6VVqvFmvNrsDNuJ2rUNQCA7nbd8dHDH2GEz4gOLavepexLWHBkAZIKk1C9pRp9nfviasxVk9477X7DAlAX8v333+OZZ55B4JJAZDtmg0AQckKM9BmJ7RO3w8nGqcPL1Ol0WHVuFTZd2oTqOv0ocTsLOywbvgxzQuZ0SBkHkw5i5dmVyKvKAwDYSm2xIHQBXhz4Ij+CuSOVqcrwysFX8Gf6n9CSFgDgWeqJhNUJ2LlzJ1544YUOL5NpHxaAuojq6mr06tULYWFh+Omnn7D3+l4sP7kchcpCAIClyBKzB87Gxw99DJFI1OHlV6mqMPfIXEQqIvkvbQ+HHtg0fhPCu4W3K8/EwkS8e+JdXM27CgAQC8SY3GsyPnz4Q9hIbDrq1nkajQYfRH2Ab698ixqNvpblZOWE90e+j+cHPI+nnnoKUVFRSElJgUwm6/DymXYw4utfi7rKZFMiok2bNpGPjw9JpVIKDQ2lixcvdngZy5cvJ6lUSqmpqfwxtVpNb//xdoNJpwHrA2jPtT0dXn69G/k3aPg3w8kuwo6f1jFlzxQqrCw0OI8yZRktPLyQeqzvoW/n+cKXntz3JN0svnnP7vvH+B+p54ae/O/J9XNXWvL7kgbjuDIzM8nCwoKWLl1qcL6d+XdmjrpsAOoqmwt2xoZ2GRkZZGFhQe+++26T5wsrC2nKnink8JkDP6hw+DfD6Ub+jQ67h3/ac20PBawP4L/Qbp+70dt/vN3iwEyNRkMbLmygoM1BfO/WsG+G0VHF0Xt2nzfyb9CIb0fwAdP+M3t6/PvHKbcyt8nrP/jgA5JIJHTzpmHBsDM2lzRnXTYANcUYk00N2dCO6O42tZs+fTq5u7tTRUVFi9f9lfUXhWwLaTDp9On9T1NlTaVB5bSVWq2mZceXNaiB+a/3p91xjcfpnEg7QcO/Gc4HnsDNgfTF+S/uWW9RZU0lPXfgOXJc5cjfW/DW4FZXiqyuriYvLy+aMmVKu8pt6u+MbWjYfvdFADLWZFNDNrSrL7u9m9rVT/vYtcvwtXy2XdpGPl/48F88j7Ue9GnUp/ds0mlhZSH9a++/GtTAHvj6AYrLjaO0kjSa/sN08v1CP32ix/oe9Nrh16hMWXZP7oWIaPWZ1eS51pP//N5feNOX0S3vDXan77//ngDQ8ePH21z2P//O2IaGd6dLByBjTzY1ZEM7ovZvaqfRaGjgwIEUGhra5t1N1Wo1LTi8gJxXO/NfxL6b+tKhZMMnrrZV9O1oGrJ9CNlG2JJ8pZysV1iTfKWcvNZ4kc8XPjTx+4l0Pb/5/wHcrd8Vv1Pg5kD+8zqvdqa5h+ZSjbrx2K2W6HQ6GjZsGAUFBbUpaDf1d8Y2NLw7XToAGXuyqSEB6G42tdu+fTsBoPPnzxt03025VXqLxv13XINBhaN3jSZFkaLdebbmzSNvktWnViT8SEjCj4Rk8YkFvXrw1Xv2upVemk5jdo1p8BnHfjeW0kvT253n5cuXieM42rx5s8Fp/vl3xjY0vHv3VTf8mDFj0KNHD2zbto0/tmDBAvz66684ffo0/Pz8Ws0jPz8fI0eORM+ePXHp0iU8+eST2LhxY5PX1tXVwcrKCvv378eUKVP44y+88ALKysrw66+/4uDBg5g9ezYuXrzYKL2lpSU8PT2bzLu8vBwBAQEYN24c/vvf/7Z63605dvMYFh9bjKzyLACAWCjG1N5TsWH8BliILO46fwBILkrGuovrkFiYCI1Gg6TiJNTp6mAjsYFEKEF3u+74eNTHGO49vEPKU2lUePPom/gp8SfUaesAAN1su+HzMZ/j0YBH7zr/l156Cb/88gsUCgUcHBxavLapv7P2PnvmDsaOgG1hjMmmLW1oR9S+zQWJiBYvXkxWVlYdvvPmmnNryGut19/tI//xpg3nN9xVnqU1pfRJ1Cf08M6HaeSOkTRq5yiKOBNB31/7nhb/vpjG/3d8h0+t2BK9hby/+HvyrOdaT1p1pvVVKdsiNzeXZDIZLVy4sNlrWvo7Yxsa3r0uG4C6ymTTlja0I2rf5oJJSUkkEonu2Yp6yjolzf55Njmt+nvS6YAvB1BUetM9hM1Ra9W06+ouGv+/8TRyx0gauWMkLTi8gFKK9F/Ey9mX6YOTH9Cuq7vocvZlGvffcXc9ufRM5hkK3hrM37fjKkd67sBz96ynLyIigoRCYbOdGy39nbENDe9elw1AXWWyKVHLG9oRtX1Tu3Hjx5HYUUyhm0PpcErzG+ndraTCJBq5YyTfdmL/mT1N+N+EZsfI3OlM5hl66sen+MAz7YdpdCLtRINr8irz6IOTH9DK0ytJq9M3ord3eY3cylx6/PvHGyygNuLbEfd0rNPuuN3UY20PEjoK6aFRDzXZGdHa3xnb0PDu3FdtQKYgMjISEyZMgNtsN0j7SwEAg9wGYdWYVejp1LOV1O3zS+IvWHpiKT8XSyqUYma/mVgzZk2jaR3ppenYcHEDruRdAQBYiCwwre80PNf/uUYTVHWkw2dnP0Odtg7zh8yHi7ULAP0CYytOr8DeG3tRq60FAPR26o0Vo1ZgsMfgBnloNBq8c+Id7L6+GyqNCgDgau2KiNER+Ffff3X8LwPAtbxrmHt4LhILE0EgaBO1qPquCgcPHsSkSZPuSZlM01gA6kRqtRr9+/eHm5sbtv6wFcv+XIaY3BgA+nlSj/V8DJ+O+vSezZNacXYFtsVsg1KtBAA4WDrg/ZHvY9bAWaiqq8K2y9sQeTMSGp0GHDiM9BmJBWEL4GTV/CTYnVd3IqMsA5N7TcYg90ENzmWXZ2PpiaU4e+ssiAgCToDRfqOxctRKONs447u47/Bx1McoUhYB0M93eyX4Fbz/4Pv3ZL5bmaoM836bhz9S/+Dnu/V07IlN4zfh3y/+G+np6bhx4wakUmmHl800jQWgTrR+/Xr83//9H2JjYzFgwAAAwBHFEXwa9SlyqnIAAHKpHPMGz8OcwXPu2UzxVw+9iuNpx6ElLYgIjlaO8JR7QsjpywtwCMAbYW8gyDWo1fyOpx3H2ayzGOw+GJN6NV17OJ1xGh+e+hBpZWkAAAEnQJ2mDhW1FRAIBBByQozyG4WvHv8KdhZ2HfZZ62k0GkSci8DWy1tRrdbP+L8z+AL6pVcHDBiAiIgIvPXWWx1+D0zTWADqJIWFhQgICMBTTz2FrVu3Njin1Wqx7uI6fHPlG7524mPrg48e+ggP+T10T+7nSu4VvHzwZSQUJoBA4DgOnjaeWDVmFab2mWrwmjmJhYnYF78PrtaumDdkXrPXabVabLm8BR9HfYxSVSkAfSDyd/DH/6b+D8EewR3yuf7p18Rf8e6Jd/kA39Lr58KFC7Fr1y4oFAq4urrek/thGmIrM3WS999/HwDwySefNDonFAqx+IHFOPviWTzq/yiEnBCZ5ZmY/etsPL3/aX5sT0eprK1ERlkGJvWahFF+oyAVSiHkhKjT1eHDqA+x5fIWaLVag/LykusX+i+oLuDH6vyTVqvF11e+xlexX8FCaAGxQAyRQARvW2+4WLtgb/xe3K643WGfDwBuFt/EQzsfwqyDs5BTlQOO4zCs2zDEvBKDdY+ua/IV76OPPoJYLMZ7773XoffCNI/VgDpBXFwcgoODsXbtWixatKjV66/lX8O7x99FfKF+m2mJUIIn+z6J5SOWw1Ji2e770Og0OH/rPM5kneGDxQDXAbiRdwNnb59Felk6f9zb1hsfPvQhRvmNajXfL85/gfLacswaOAu+dr4Nzp3OOI33T72PjLIMAPq2Ll87X4R6hkIHHS7cugACQSwQY2LPiXgl+BVYSaza/RlVGhXmH56PQ8mHoNbpt9n2tfPFhkc34EHfB1tNv3nzZixcuBCXLl3C4MGDW72euTssAN1jRIRRo0YhLy8P165dg1gsNjjtj/E/YvVfq1FYrV+UzMHSAYvCFuH5gc+3+R6SipLwe+rvKFOVAdDXXMb7j4en3BM/JfyE6wXXMdBlIP5I/wO/3/wdWtKCA4cwrzBEjI6An33zo8x/iP8BCYUJGNN9DD8KOqs8C+8efxd/3fqLX9nxkR6PYGLPibiccxl9nftieuB0xObGYmP0RqSXpgMA7C3sMWvgLEzqOanNS6euu7AO/zn/H1TUVgDQt6e9Ff4WFg5daHAeGo0GAwcOhJ2dHc6cOdOuLZEYw7EAdI8dOHAATzzxBI4cOYLx48e3OX2dtg6rzq5q0E0d4BiAFQ+vQKhXaKvp86vycfTmUaSX6b/gMokMj/R4BP1c+vFfrpicGBxKOQRfO1/MGjgL1/KvYdmJZbhRcAOAvgb2RN8n8P6I95usgf116y/8kfoHejv1xuSek/HpmU+xP2E/3wUf6ByIFaNWYKD7QHwX9x3SStMwIWACQj3196/T6fBr8q/YeXUnymvLAQDd7btj0dBF6O/av9XP+Gfan3jz9zeRWZ4JQD8NZXKvydg0YVO7pqEcP34cjzzyCPbu3YsZM2a0OT1jOBaA7iGVSoU+ffqgb9++OHz48F3llVeVh6XHliIqK4rv0n7I5yGsHLMSbjZuja5XqpX4M/1PxOTEgEAQCUR4oNsDGO49HBJhw/E8RcoibIreBJFAhKXDl0Ik0LeP/JTwE1adW4WC6gIAgL2lPd4Me7NRDSyrPAvfXvkWycXJSCpM4huZnayc8PYDb2N60HQAgFanxWdnP4Nap24wboi/5zoltsdux+GUw1Dr1ODAYbj3cCwMXQgXm4bXAsDtstuY89scnM8+DyJ9Q/ogt0HYPnE7/B392/mb1psyZQpiY2ORlJQEK6v2vxIyLWMB6B5auXIlPvjgA9y4cQO9evXqkDwv3LqAf5/8N26W3ASgHzvzTP9n8PawtyERSqDVaXEp5xJOZZzia0yBzoF4pMcjzXZxExHWnl+LqrqqRu04ddo6rD63Gruv7ebXWQ5wDMCnD3+KMK8wAMDF2xfxzIFnUKQsglwqh5XYCjODZuLdEe82CHa3ym/hmyvfwEpshbceeKvZ15us8iysv7geMTn6MVJSoRRP9H0CswbMgkQkgUajweJji7Hnxh6+zcrDxgOrx67GxJ4T2//LvcPNmzcRGBiIZcuW4YMPPuiQPJnGWAC6R7Kzs9GrVy+8+uqrWLt2bYfnv/PKTqyPXo/SGn1tw8XaBc/2fxYCTsAP7HOzccOj/o82ahhuyv6E/bhRcAMP+z6Mkb4jG53Pq8rDsuPLcDLzJF/bGOIxBAIIEJ0TjXJVObSkxUifkdgyYQs8bRvPBD+TeQYn0k+gj1MfzAhq/dXmXNY5bLm0hd82yMnKCR4yDxxKOcR/bmuxNeYPmY93HninwwcvLl26FBs2bEBSUhK8vb07NG9GjwWge2Tt2rVYtWoVUlJSYGdnd0/KqKmrwUenP8KBxAN/1wRkHhjpOxJP9nkSg9wHQcAZ1pB7KfsSDisOw8/ODy8MbH7bmgu3LmD5yeW4UXBDX8Pi9NM1nC2dMcRzCKYHTsc4/3FNpv3ftf/hZslNjPcfz9eeWqPRafBj/I/4+srXSClKQWVdJQBAyAnxqP+j2PbYNthYdPzIcQCorKxEz5498cYbb2Dp0qX3pAxz1/Hj3RkAwOLFizFz5sx7FnwAwFJiic/GfIZXB7+KF399Eell6cipzMHF2xfhYuWC3k69YS2xNiiv+lrSrYpb0Og0fDvQP3nIPTAtcBoA8NNI+jr1xVCvoahWV/O1lX/S6rT8eCYfOx+DP2Odpg4F1QWoVFXywcfVxhU/Pvkj+ru13kB9N2QyGWJiYuDu7n5PyzFnLADdQx4eHp1Sjp+9H/ZN24cPTn6A6OxoaHQa/Jr8K05lnMLzA57H1N6tj2x2snKCtdga1epq5FTmwNu24StHuaocx9OO43rBdQD6cUI6nQ7EEXztfKHWqXE17yryqvIwuddkOFo5NkifW5WLOm0dLEWWcLVufZSxTqfDoZRD2HF1B/Kr8lGgLICl2BIDXQfim8e/gYe8c363nfUMzRULQCbC2coZPR17wtnKGZ62njiqOIpSVSk2Rm/Ebym/YWHYQgS7Nz/dgeM4+Nj5IKEwARllGXwAUmvVOHfrHM5lneN7poLdg2FvYQ+pSAoiQk/HnvB38MeV3CvIq8rDqnOrMN5/PIZ5D+MboTPL9F3kPnY+rY6tuZp7FRuiNyCtNA0anQYVdRUIcg6CWCjGA90egLuM1UhMBQtAJoLjOPg7+CMmNwbecm/s/tdufHPlGxxMPoj0snQs/n0xwruF4/Ww15vstgf0r2H1AWiE9wjEF8bjWOoxfmyOj60PHvV/FNYSa8TkxsDBwgHggPLacozwGYHM8kwcTzuO0ppSRGVG4UreFYzpPgb9XPrxI6F9bJt//SqoKsCG6A04l3UOBIIAArjZuGGkz0hU1lVCLpUjwDGADQ40IawR2oQkFSVh7429sLewx+thr4PjONyuuI2NFzfiYrZ+3WKpUIopvadg9qDZjQbpFVQXYMulLajR1CDAIYCfn2UrtcXYHmPR17kvOI5DbG4sDiYfhJfcCwJOgKzyLEzsORFVdVU4mX4STlZO0JKWH3XtKfNEWmkapCIpXh38aqMaTJ2mDjuu7sCBxAP84MXB7oPhKfdERW0F5FI5dKRDVV0VpvWdhkCXwHv8m2Q6C6sBmZDu9t0h5IQoVZWipKYEjlaO8JJ7YdUjqxB9OxqbojchqyIL++L34Xjacbwc/DLG9RjHtw9ZiiyRUZaBjLIM1Gnq4GTlhOHew/FAtwcgFv49hURRrACgX7ajPgApihUI8QjhaycLQhfw886SipIQkxuDbrbdYCX+e1CfTqfDsbRj+Cr2K37ogJfcC/ND5iO/Oh9x+XGQCCWYEDABe2/shYAToIdDj876dTKdgAUgEyIRSuBj54O00jQoShQNGoJDvUKx02Mn9ifux3/j/ovimmKsOrcKvyT9ggWhC1BZV4mojChU1VUBABytHLEwbCHkUnmDMrQ6LdJK9ev6BDjqA9CJ9BNIK03DYz0fAwAU1xRDrVVjhM8IDHQbiE3RmwDoRzpvvrQZD/o8CDsLO2yO3ozEokQAgI3EBs/0ewYzAmfgdNZpxOXHQcAJMD1wOkpqSgAA3eTdOmyHD6ZrYAHIxAQ4BOgDULECQ72GNjgnEOi/0I/2eBTbYrbh6M2jSC5OxuuRr8ND5gFfO18EOASgm7wbfO18GwUfQD9KuVZbC2uxNdxt9K9SMokMlXWVKKwuhKOlI4pripFdmQ1/B3/IpDL42Pkg2D0YEqEE1XXV2Hp5K7IrsvlJqmN7jMW8kHmQW8gRlxeHUxmnAACPBTwGfwd/7L62W//ZHAPu7S+P6XRsPSATU/8lzSjLaHZ9HrmFHG8NewtfP/41ejv1BoGQXZmNitoKjPAeAblUjqzyLGh1jdcEUpQo+HI4jgPHcXyZihIFPOX6EdD17Uc60iGrPAsyiQwjuo1AVV0VblfcBoEQ4BCAbZO24Z3h70BuIUd6aToOJh8EAAz3Ho7BHoOh1qr5ibQBDiwAmRoWgEyMo6Uj7C3soSUtv8RFc/zs/bDlsS0I9wrnByxezL6IuPw45FflI7cqt1GaO9t/6tX/t6JYwS9QVh+A8qvykVuZi6t5VxGdEw0CwUpshVDPUGyduBX+DvpJo4XVhdgXvw9a0iLQORCj/UYD0AdSjU4DuVTeaPIqc/9jAcjE/LNG0hoBJ8CDPg8ixD0E3Wy7wVpiDZFAhLj8OHwT+w3f/gLo15MuVBZCwAnQ3b47f7y+8bu4pphvZM6uyEaJsgRfx36NuPw4CAVCWImt0E3eDUM8hmCkz0gIBfo1qKvqqvjlRrrJu2Fqn6l8YzZf43Jg3e+miAUgE3RnjcSQURZBLkHgOA4anQZzB8/FsG7DwIFDXH4cNkdvxrHUY6jV1PK1n27ybrAU/70ukFQk5QcuVqgqwIFDfGE81pxfg7j8OHDg8EC3BzBvyDz9jhschyAX/YL3aq0ae67vQZmqDA6WDpjZbyY/DYSI/q5xsfYfk8QaoU2Qr50vRAIRymvLUagsbPXVxUvuBTsLO5SpynCr4haeCnoKOZU5yCzPhEanwblb5xCXHwe1Vg0iajIYBDjqG79PZZxCfGE8CqoLYCmyhJATIsQjhM+zVlsLuVSun8pBOvyU+BOyK7NhJbbCM/2eadBNX1JTglJVKYScsEGNizEdrAZkgsRCMfzs9Euo1tcgWnJnjeRGwQ242rjC0coRfZz6YEz3MXC0dERFbQWOpR1DbG4sLEWNV0W0FlsjNjcWx9OPQyQQwVJkiR72PdDbqTfsLe3hbuOO6/n6eWT1Na5jqceQVJQEkUCEp4KeajR/rP71y8fOp9EiaoxpYAHIRLWlHQgAH4AUJQrUaevgbesNjuMgFAgxf8h8fdAAh1ptLQ4mH8RPCT+hXFWOitoKHEg8gAOJB/i5YoPcBmGI5xCUqkrBcRy8bb2hJS1SilP4si7evojzt88DAKb0ntJo8ivQdIM3Y1rYK5iJqv/SZpVnQaVRtTqAz9XaFc5WzihUFiKpKAm+dr5IKU5BRlkGHuj2AORSOcK8wsBxHAScANcLrvMz4wF9Y/YQjyHQkhbett5ILEpEamkqejn2go+tD5KLkqHWqeFg6YAKVQWO3jwKABjTfQwf/O5Up63j54+x9h/TxWpAJsre0h5OVk7QkY4fudySf76G1a8PlFWeBR3poChWQCKU4Nl+z2LO4DkNJpVKhBK8MvgVPDfgOUiEEuRU5sBKZIXSmlJU1VXB186XD1buNu74KfEnEAiD3QdjWLdhTd5Pemk6tKSFvYU9HC0dm7yGuf+xAGTC7uwNM0R9AEorTYNMIoNUKIVKo0JiYSKKa4r5xmB3mTtmDZwFT5l+0GGgcyA/krq+8VsgEECtU0OpVsLewh6pJalQaVSIL4yHWqdGD/semBAwodmu9X8OeGRMEwtAJuzOdiBDuuMdrRzhIfOAjnRIKkri22XO39K31XjbekMqkgLQ15jql169XnAdSrUSEqGErznV72UmFoqRUpKCWm0t/0rlau2K6YHT+XFA/9Sg+521/5g0FoBMmLetNyRCCarqqpBXlWdQmqZew67kXwHQuC2mm7wbPGQe0Og0/A4W9QEjp1K/FzsIiMuLQ3xBPKzEVpBJZHi639N8IGtKobIQ5bXlEAlEBi2oz9y/WAAyYSKBiB8/Y2hvWKCzfq2dzPJM/ZQOnZYf0PjP2gjHcQjz1C8ufynnErQ6LQIcA0BEyKvKg4500JIWh1MOo1RVCk+ZJ57u9zRsLWxbvIf62o+fnV+DZUAY08MCkIlrazuQrYUt38BcXFOManU16rR1EAqEcLJyanR9oEsgbCQ2qKitQGJRIhwsHSAVSaHWqSHkhMgsz0RWRRbkUjmeH/C8Qcup3tn+w5g2FoBMXP1kz9sVt1GjrjEoTf1rWGJRIr+tj43YpsnGYJFAhBCPEADAhdsXAICf2KqDDqklqajV1OJfvf9lUEBRaVT87hms/cf0sQBk4mwtbOFi7QICIbU01aA0fZ37QsAJkF2RjXKVfj3olnqiQjxCIOSEuF1xG9kV2RBwAqg0KuRX5UOlUcFaYo0nA580qOy00jToSKef1W9pb1Aa5v7FApAZaOtrmLXEGt3tu0OpVqJMVQYBJ0CNpqbZnjQbiQ1fa7pw+wKKqotQqCyEjnSQiqRwt3FvMMerJWzyqXlhAcgMtLU7HtC/hpXUlKCqrgoOlg6o09ahUFnY7PX1O52eyzqHc7fOQSqUQiKUwEZsAycrJ75bviVE1GD5Dcb0sQBkBrrJu0EqlEKpVv7dPd6K3k69UaYqQ42mhh8PVD+OpykeMg94yDxwOfcyCqsL4WbtBnsLe3AcBycrJ36BspbkVeWhqq4KYoG4TbunMvcvFoDMgFAg5HeTMLQ7ngPHDxR0sHQA0HIAIiJU1FagWFkMpVqJMK8wCAVCvkG6uS2b71R/b93tuze7NTRjWlgAMhNtbQdKK02Dk5UTLMWW/DpAmWWZzb7CHUs7hgpVBbSkhYOlA786oqfME6WqUoNqQKz9x/ywAGQm6rvjsyuz+a13WqIoUcDR0hGu1q4gEJRqJarV1fz+XXe6lH0Jf936CyqtCr2dekMkEOF2xW24WLvA29YbJTUlKKwuRK2mttnylGolH6RY+4/5YAHITMikMn4bndSSlrvj6+diCQVChHqGQsAJoCMdgMavYSnFKTiiOAIA6OnYkx9JrVQr0cO+ByzFllCqldCRrsXXsNSSVBAILtYurY6UZkwHC0BmxNBFyvKr81FZVwmxQIyHfB8CoA8oRITM8kz+utzKXOxP2A8CYZDbINhb2EMkEPH7iVmJrSAVSmEhskBVXRWyK5oPQKz3yzyxAGRG6r/cqSWpfI2mKTdLbgLQNwb3cuwFS5ElpCIpylRlyCjLABGhXFWO769/jzptHbrbd8djAY8hszwT5bXl8JJ78ctyuNq4Qi6Vo7imuNl2ICLiy2TtP+aFBSAz4in3hKXIEjWamhYbhesbg/0d/CEUCNHXuS8fRKrqqpBdmY3d13ejsq4SLtYumB44HZV1laiorUCRsghuNm4IdA7Uj4hWqyCTyFBSU6LfkLCJRuycyhwo1UpIhVJ0k3e7Z5+f6XpYADIjAk7AN0Y31xtWo67BrYpbAP6ujQS5BEHACVCrqYVGp8G3V75FQXUBv5+7hcgCGWUZ0JEOKo0KQoEQE3tOBAAUKAtgJbZCVV0VSlWlKK8tb1Rm/etXD4ceza4RxJgmFoDMTGvtQPVzsZytnGFnYQdAvyuFTCKDpcgSV3Kv4EbBDUiEEjzT7xm+wTijLANlqjJYia1gLbbGKL9R/PghHelgLbFGaU3T3fFs8THzxQKQmelh3wMcOORV5aGitqLR+aaWwhBwAgS6BEKpUSKjLAPlqnI80eeJBktrZJZnIr8qH3YWdujr3JfvQQOA6rpqyCSyJtuB6l/pgL+HCjDmgwUgM2MtsYanXL+Wc33Db72WlkIVCoQoqC5AjaYGnnJPOFs78+fKVGUoqSlBcU0xbKW26OfaDwAw0G0gJEIJ/1pVUlPCL7VRr35IgLuNO2RSWQd+UuZ+wAKQGWpuVHRuVS6q1dWQCqUN9unKLMvE+VvnYSW2gqu1K9/mUy+jLAPFSv3IZwdLB74h2UJkgUFugyCXylGjroFGp4GiWAGNTsOnZYuPmTcWgMxQ/Zc9rTQNWp2WP14fkLrbd+drLcXKYuy9sRc60iHEIwQBDgEoqC5oEIAyyzJRUF0AOws7BLoENlg7qH4go0ggAoFQUF2A/Kp8APq2Ib77nbX/mCUWgMyQu407rMXWqNXWNngl+mdtpLquGruv79a/dsk8MXfwXNhb2qNYWYybJTf5LvWbJTf/fv1y6degLEcrRwQ4BsDRyhFqrbpBO9DtittQaVSwFFnyr4WMeWEByAxxHNeoN6y6rpofqRzgEAC1Vo29N/aipKYE9hb2mNlvJrxsvfhZ9aklqShTlaFcVY6bJTehIx387P3gZuPWqLwwzzA4WDpApVGhoraCr/XcOd6ofulXxrywp26m6l956oNBaql+LpabjRtsJDb4Oeln3Kq4BQuRBZ7u9zRsJDYA9A3LMqmMfw3LLM9EfnU+ZFIZBrkNanLp1u723eEp84SdhR2q6qoQlx8HgLX/MCwAma3u9t3BgUNBdQHKVGUNer+Opx1HQmEChJwQTwU91aDHK8glCHYWdihVlSK5OBnJRckorSmFndSO7/36J47jEOYVBg8bD1TUViCtNA15VXnIq8oDBw497Ht0ymdmuh4WgMyUpdgS3Wz1vVUpxSl8TahGU4Nzt84BACb3ntxoY0AHSwf0cuwFALiYfRHR2dEgEPwd/Jvctqdef9f+6GbbDRw4ZFdk41yWvgwPmQe/aBljflgAMmP1r2HRt6NRo6lBVV0VLudcBgA87Psw+rv2bzLdCO8R4MAhpSgFCYUJAIDh3sNbLEsilOAh34dgJbZCqaoUUZlR+ntgr19mjQUgM1b/5Y/Ni0VFbQW/XvRAt4F40OfBZtMNdNe3A+VU5iCnMgc2EhsM9hjcanlhXmFwsXZBjboGl7Iv6e+Bdb+bNRaAzJirtStkEhmyK7IRnR0NuVQOPzs/TOo5qcV9wORSOXo59kJFbQXKVeUIcAjg5421xM7CDuHdwqEhDW5V3IKV2AoeMo8O/ETM/Yat/G1CEhMTkZpq2OaD9fIy8nDj+g2IhWL07t4bMq0MkbcjW00nviVG0VX98qwSsQS//fabQeW5lblBeUOJOm0dbupu4nDp4Tbdr5+fHwIDA9uUhum6ODJ0oyimy3vnnXewevVqY9/GPfXGG29g3bp1xr4NpoOwAGRCKisroVQq25WWiFp87eoqrKysIJOxSaumggUghmGMhjVCMwxjNCwAMQxjNCwAMQxjNCwAMQxjNCwAmaHy8nLMmTMH/v7+6NOnD3Jzcw1Kp9FosGLFCoSHhyM4OBgvvPACjh07ds/KY0wfC0Bm6LXXXsP169exevVqZGZmoqamBgDw5ptvYtOmTc2mW7p0KbZs2YLRo0djypQpqK2txcSJEzF79uwm9/u62/IYM0CM2XFwcKDY2FgiIrKxsaHU1FQiIoqMjKSQkJBm07m7u1NUVFSDY2lpadS3b19avXp1h5fHmD5WAzJDRNTkYL6AgAAoFM3vG19dXQ0vL68Gx/z8/LBx40Zs3769w8tjTB8LQGZo/Pjx2L17d6Pj1dXVLY6GHj58OHbt2tXouJ+fH3Jycjq8PMb0scmoZigiIgIhISEA/p6CoVKp8MknnyA4OLjZdKtWrcKwYcNQWlqKhQsXIiAgAGq1Ghs3bkTfvn07vDzGDBj3DZAxFoVCQWPHjiWO48jJyYmkUik5OzvTpUuXWkwXGxtLISEhxHEcSaVSEolE5OTkRGfPnr0n5TGmjc0FM3NZWVmIi4uDWCxGWFgY7O3tDUqXnJyM+Ph4yGQyhIWFQS6X39PyGNPEAhDDMEbDGqHNTFFREVavXo2pU6ciPDwc4eHhmDp1Kj7//HMUFha2K89bt27hxRdfbPJcTU0Nzp49i4SEhEbnVCoVvvvuu3aVyZgGVgMyI5cuXcK4ceNgZWWFMWPGwNXVFQCQn5+PEydOQKlU4vfff+cbjA0VFxeH4OBgaLXaBsdTUlIwduxYZGVlgeM4DB8+HHv37oW7uztfroeHR6N0jPlgAciMDB06FAMGDMDWrVsbdX8TEebOnYtr167h/PnzDc4dPHiwxXzT0tKwePHiRoFk6tSpUKvV2LlzJ8rKyrBo0SIkJCTg1KlT8Pb2ZgGIYQHInFhaWuLKlSvo3bt3k+eTkpIwaNAgfqpEPYFAAI7jWpxuwXFco0Di6uqK48ePo18//YaFRIT58+fjyJEjOHnyJKytrVkAMnOsDciMuLm5ITo6utnz0dHR/GvZndzd3XHgwAHodLom/8XGxjaZX01NDUSiv4eacRyHL7/8EpMmTcLIkSORkpJy9x+Kua+xgYhmZMmSJZgzZw5iYmIwevToRm1AX331FdasWdMo3eDBgxETE4PJkyc3mW9ztaPevXvj8uXL6NOnT4Pj9RNQH3/88bv9SMz9zhiDjxjj2bt3L4WFhZFIJCKO44jjOBKJRBQWFkb79u1rMs3p06cpMjKy2Tyrqqro1KlTjY6vXLmSxo8f32y6efPmEcdxbf8QjMlgbUBmSq1Wo6hIv6+Xk5MTxGKxke+IMUcsADEMYzSsEZphGKNhAYhhGKNhAYhhGKNhAcjMFBQUNNnVDgDr169vdmGxzk7HmAnjdsIxnS0hIYHc3Nxo/vz5DY4vWbKEnJyc6OrVq10iHWMeWAAyQ0lJSeTp6UmzZ88mrVZLCxcuJFdXV4qLi+tS6RjTx7rhzVRqaipGjx4NsVgMpVKJ48ePNxqx3BXSMaaNtQGZqR49eiA8PBypqakYMmQIevXq1SXTMaaNBSAzRER49tlnceHCBURFRSE5ORnTp0+HRqPpUukYM2DUF0Cm06nVapo2bRr5+/tTVlYWERHl5eVRUFAQTZo0iWpra7tEOsY8sBqQmYmOjoZCocCZM2fQrVs3APp1e06ePIm8vDycOXOmS6RjzANrhDZD9P/35jL0uLHSMaaPBSCGYYyGvYIxDGM0LAAxDGM0LAAxDGM0LAAxDGM0LACZmfbuVNrZ6RgzYawBSEznS05OJh8fH+I4jgQCAT344IOUk5PDn8/LyyOBQGD0dIz5YDUgM/LOO+8gKCgIBQUFSE5Ohkwmw7Bhw5CVldWl0jFmxNgRkOk8Li4udO3aNf5nnU5Hc+fOJW9vb0pNTW22RtLZ6RjzwWpAZqS9O5V2djrGfLCdUc1Ie3cq7ex0jPlgNSAzMnXqVOzZs6fJc5s2bcLMmTOb3GK5s9Mx5oPNBWMYxmhYDcjMJCYmYseOHUhKSgIAJCUlYd68eXjxxRfx559/dpl0jJkwahM406kiIyNJIpGQg4MDWVhYUGRkJDk7O9OYMWNo1KhRJBQK6cSJE0ZPx5gPFoDMSHh4OL333ntERLRnzx6yt7enZcuW8eeXLl1KjzzyiNHTMeaDBSAzIpfLSaFQEBGRVqslkUhEsbGx/Pnr16+Tq6ur0dMx5oO1AZmZ+hUIBQIBLCwsYGtry5+TyWQoLy/vEukY88ACkBnx9fWFQqHgfz5//jy8vb35n7OysuDu7m70dIz5YAMRzci8efOg1Wr5n4OCghqcj4yMxKhRo4yejjEfbBwQwzBGw17BGIYxGhaAGIYxGhaAGIYxGhaAGIYxGhaAGIYxGhaAGIYxGhaAGIYxGhaAGIYxGhaAGIYxGhaAGIYxmv8HfYZZoS/1ujIAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "model.visualize()" - ] - }, - { - "cell_type": "code", - "execution_count": 165, - "id": "07f5254f-825e-47a8-8bf5-82c8ec08faa1", - "metadata": {}, - "outputs": [], - "source": [ - "tests = []\n", - "for axis in range(o1.size(-1)):\n", - " tests.append(torch.allclose(o1[:, axis], o2[:, axis], rtol=1e-7, atol=1e-4))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dae3c6e7-8d8f-49bd-9239-8b4b5c6af499", - "metadata": {}, - "outputs": [], - "source": [ - "p = next_layer(o, coords, edge_index)" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "a322cad7-60c8-4710-9257-2e3f0d4405be", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "torch.Size([16, 1024])" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "o.shape" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "599ae7e0-5a54-429d-af9b-749de7ce2434", - "metadata": {}, - "outputs": [], - "source": [ - "o + p" - ] - }, { "cell_type": "markdown", "id": "090ad5de-1172-4bdf-83c9-92eda962a398", @@ -941,7 +855,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 12, "id": "8e448746-66f8-484f-8971-1c89c8f75135", "metadata": {}, "outputs": [], @@ -1048,7 +962,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 13, "id": "6affd332-fe3a-49dd-b0c2-e20fcb974a04", "metadata": {}, "outputs": [], @@ -1177,7 +1091,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 14, "id": "2d7ce968-f33c-46dd-88e3-8ad47480a9e7", "metadata": {}, "outputs": [ @@ -1203,7 +1117,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 15, "id": "9c89a710-1e68-485c-9325-0b06c7b52b75", "metadata": {}, "outputs": [], @@ -1213,6 +1127,27 @@ "e_std = values.std()" ] }, + { + "cell_type": "code", + "execution_count": 16, + "id": "1738835c-76e4-4ef9-acf7-df3d5f20d449", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(tensor(-76.1160), tensor(10.3238))" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "e_mean, e_std" + ] + }, { "cell_type": "code", "execution_count": 56, From c010843a7d255e866a2ad50d3550844f3004026d Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Tue, 8 Oct 2024 16:10:56 -0700 Subject: [PATCH 115/116] notebook: cleaned up notebook for pedagogy Signed-off-by: Kelvin Lee --- ...ct evaluation of spherical harmonics.ipynb | 105 +++--------------- 1 file changed, 14 insertions(+), 91 deletions(-) diff --git a/notebooks/Direct evaluation of spherical harmonics.ipynb b/notebooks/Direct evaluation of spherical harmonics.ipynb index 35408cc..e76694e 100644 --- a/notebooks/Direct evaluation of spherical harmonics.ipynb +++ b/notebooks/Direct evaluation of spherical harmonics.ipynb @@ -13,8 +13,8 @@ "from pathlib import Path\n", "\n", "import sympy\n", - "from sympy import symbols, sqrt, diff, Symbol, latex, cos, sin, acos, simplify, pi\n", - "from sympy.functions.special.spherical_harmonics import Ynm, Znm\n", + "from sympy import symbols, sqrt, diff, Symbol, acos, simplify\n", + "from sympy.functions.special.spherical_harmonics import Ynm\n", "from sympy.functions.elementary.complexes import sign\n", "from sympy.simplify.radsimp import collect_const, collect_sqrt\n", "from e3nn.o3._spherical_harmonics import _spherical_harmonics\n", @@ -41,91 +41,11 @@ ] }, { - "cell_type": "code", - "execution_count": 2, - "id": "9b9baf24-7d79-404f-8d12-b68b5dd0ded2", - "metadata": {}, - "outputs": [], - "source": [ - "def derive_sph_harm(n: int, real_only: bool = True):\n", - " # define symbols and conversions\n", - " x, y, z = symbols(\"x y z\", real=real_only)\n", - " theta, phi = symbols(\"theta phi\")\n", - " sph_to_cart = {\n", - " \"theta\": acos(z / sqrt(x**2.0 + y**2.0 + z**2)),\n", - " \"phi\": sign(y) * acos(x / sqrt(x**2 + y**2.0)),\n", - " }\n", - " num_projections = 2 * n + 1\n", - " terms = {}\n", - " for m in range(-n, n + 1):\n", - " sph_harm = (\n", - " Ynm(n, m, theta, phi)\n", - " .subs(sph_to_cart)\n", - " .expand(func=True)\n", - " .cancel()\n", - " .simplify()\n", - " )\n", - " terms[f\"{n},{m}\"] = sph_harm\n", - " return terms\n", - "\n", - "\n", - "def numerical_evaluation(expr, _x: float = 1.0, _y: float = 1.0, _z: float = 1.0):\n", - " return expr.evalf().subs({\"x\": _x, \"y\": _y, \"z\": _z})" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "1e2d450d-a978-404b-92b4-f8600e5f0aae", - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\frac{\\sqrt{5} \\left(- x^{2.0} - y^{2.0} + 2 z^{2}\\right)}{4 \\sqrt{\\pi} \\left(x^{2.0} + y^{2.0} + z^{2}\\right)}$" - ], - "text/plain": [ - "sqrt(5)*(-x**2.0 - y**2.0 + 2*z**2)/(4*sqrt(pi)*(x**2.0 + y**2.0 + z**2))" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "derive_sph_harm(2, real_only=True)[\"2,0\"]" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "f7893df2-4c98-4cbc-b5af-8b05d065dd50", + "cell_type": "markdown", + "id": "ed9f0d48-ce14-40e5-80b3-865453916815", "metadata": {}, - "outputs": [], "source": [ - "test_tensor = torch.tensor([[1.0, 1.0, 1.0]])" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "eec63b4e-db2c-48c3-aa7c-6dd64f3a33a1", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "tensor([[1.0000, 1.7321, 1.7321, 1.7321, 3.8730, 3.8730, 0.0000, 3.8730, 0.0000]])" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "_spherical_harmonics(2, test_tensor[:, 0], test_tensor[:, 1], test_tensor[:, 2])" + "This notebook combines the `e3nn` spherical harmonic implementations with `sympy` to simultaneously refactor each order to be purely functions of $x,y,z$ - rather than recursively (or autoregressively, I guess?) through order $l$ - as well as yield pseudo-Python code that can be used to implement the Triton kernel in a relatively straightforward manner. I was unable to get the formatting perfectly behaving the way I wanted, and so the code contained in `equitriton.sph_harm.direct` submodules will be slightly different as redundant literals will be pruned to some extent by hand." ] }, { @@ -133,9 +53,9 @@ "id": "4feef347-fa61-4ea3-afce-2c004c12be51", "metadata": {}, "source": [ - "## From `e3nn` equations\n", + "## `e3nn` equations\n", "\n", - "The cell below is incredibly long, as they were directly copied from `e3nn`." + "The following equations are copied over from the `e3nn` implementation, and are used as the basis for the symbolic manipulations." ] }, { @@ -1004,7 +924,9 @@ "id": "f323ee83-1a65-4f45-a524-9bee84cd9443", "metadata": {}, "source": [ - "## Numerical checks with `e3nn`" + "## Numerical checks with `e3nn`\n", + "\n", + "This is an elementwise, manual check on specific projections within the spherical harmonics, comparing the symbolic -> numerical evaluation with the corresponding `e3nn` value." ] }, { @@ -1156,7 +1078,9 @@ "id": "a08d5640-8b8d-48f1-86cf-21eeae568001", "metadata": {}, "source": [ - "## Operations count comparison" + "## Operations count comparison\n", + "\n", + "This set of cells were used to analyze the number of floating point operations required to compute a given order of spherical harmonics, compared to the copy-pasted `e3nn` equations. Note that this should be interpreted as an upper bound: `torchscript` and `torch.compile` will likely give some operation fusion, and eliminate differences to bring them closer in actual computation." ] }, { @@ -1282,8 +1206,7 @@ "outputs": [], "source": [ "import json\n", - "import os\n", - "import string" + "import os" ] }, { From cd8c0838b5e28bcd203f3a163286a63fc1cc80da Mon Sep 17 00:00:00 2001 From: Kelvin Lee Date: Tue, 8 Oct 2024 16:17:10 -0700 Subject: [PATCH 116/116] docs: linking new kernels to notebook --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index af7e082..06e7058 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,8 @@ sph_harm = TritonSphericalHarmonic.apply( The improvements to performance are expected to come from (1) decoupling of each spherical harmonic order, and (2) pre-allocation of an output tensor as to avoid using `torch.cat`, -which calculates each order followed by copying. +which calculates each order followed by copying. See the "Direct spherical harmonics evaluation" +notebook in the notebooks folder for derivation. ### Development and usage on Intel XPU