From 46138613ef12acad6c673770797db8ca673a84a7 Mon Sep 17 00:00:00 2001
From: Andreas Kloeckner <inform@tiker.net>
Date: Mon, 26 Feb 2018 18:23:20 -0600
Subject: [PATCH] Add PDE-reduced translation notebook

---
 .gitignore                                    |   1 +
 .../PDE-reduction and translations.ipynb      | 305 ++++++++++++++++++
 2 files changed, 306 insertions(+)
 create mode 100644 contrib/translations/PDE-reduction and translations.ipynb

diff --git a/.gitignore b/.gitignore
index d22b6772..7fcc2940 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,4 @@ a.out
 doc/_build
 .cache
 .DS_Store
+.ipynb_checkpoints
diff --git a/contrib/translations/PDE-reduction and translations.ipynb b/contrib/translations/PDE-reduction and translations.ipynb
new file mode 100644
index 00000000..043f61c4
--- /dev/null
+++ b/contrib/translations/PDE-reduction and translations.ipynb	
@@ -0,0 +1,305 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 53,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import pyopencl as cl\n",
+    "import sumpy.toys as t\n",
+    "import numpy as np\n",
+    "import numpy.linalg as la\n",
+    "import matplotlib.pyplot as plt\n",
+    "from sumpy.visualization import FieldPlotter\n",
+    "from pytools import add_tuples\n",
+    "\n",
+    "from sumpy.expansion.local import VolumeTaylorLocalExpansion\n",
+    "from sumpy.expansion.multipole import VolumeTaylorMultipoleExpansion\n",
+    "from sumpy.kernel import (YukawaKernel, HelmholtzKernel, LaplaceKernel)\n",
+    "\n",
+    "order = 4\n",
+    "\n",
+    "knl = LaplaceKernel(2)\n",
+    "\n",
+    "pde = [(1, (2,0)), (1, (0, 2))]\n",
+    "\n",
+    "mpole_expn = VolumeTaylorMultipoleExpansion(knl, order)\n",
+    "local_expn = VolumeTaylorLocalExpansion(knl, order)\n",
+    "\n",
+    "cl_ctx = cl.create_some_context(answers=[\"port\"])\n",
+    "\n",
+    "tctx = t.ToyContext(\n",
+    "        cl_ctx,\n",
+    "        knl,\n",
+    "        mpole_expn_class=type(mpole_expn),\n",
+    "        local_expn_class=type(local_expn),\n",
+    "    \n",
+    "        #YukawaKernel(2), extra_kernel_kwargs={\"lam\": 5},\n",
+    "        #HelmholtzKernel(2), extra_kernel_kwargs={\"k\": 0.3},\n",
+    "        )\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 54,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "pt_src = t.PointSources(\n",
+    "        tctx,\n",
+    "        np.random.rand(2, 50) - 0.5,\n",
+    "        np.ones(50))\n",
+    "\n",
+    "mexp = t.multipole_expand(pt_src, [0, 0], order)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 55,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "array([  5.00000000e+01,   2.49673488e+00,   1.47014861e-01,\n",
+       "         2.60554926e+00,  -9.96815120e-01,   2.40178914e+00,\n",
+       "         5.42629997e-02,   3.38317711e-03,   2.93648067e-01,\n",
+       "        -5.10921327e-03,   3.78790947e-02,  -2.80879544e-02,\n",
+       "         1.38914975e-01,  -2.60301325e-02,   3.05226127e-02])"
+      ]
+     },
+     "execution_count": 55,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "mexp.coeffs"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 56,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def build_pde_mat(expn, pde):\n",
+    "    coeff_ids = expn.get_coefficient_identifiers()\n",
+    "    id_to_index = expn._storage_loc_dict\n",
+    "    \n",
+    "    # FIXME: specific to scalar PDEs\n",
+    "    pde_mat = np.zeros((len(coeff_ids), len(coeff_ids)))\n",
+    "    \n",
+    "    row = 0\n",
+    "    for base_coeff_id in coeff_ids:\n",
+    "        valid = True\n",
+    "        \n",
+    "        for pde_coeff, coeff_id_offset in pde:\n",
+    "            other_coeff = add_tuples(base_coeff_id, coeff_id_offset)\n",
+    "            if not other_coeff in id_to_index:\n",
+    "                valid = False\n",
+    "                break\n",
+    "                \n",
+    "            pde_mat[row, id_to_index[other_coeff]] = pde_coeff\n",
+    "                \n",
+    "        if valid:\n",
+    "            row += 1\n",
+    "        else:\n",
+    "            pde_mat[row] = 0\n",
+    "            \n",
+    "    return pde_mat[:row]\n",
+    "\n",
+    "pde_mat = build_pde_mat(mpole_expn, pde)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 57,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def find_nullspace(mat, tol=1e-10):\n",
+    "    u, sig, vt = la.svd(pde_mat, full_matrices=True)\n",
+    "    zerosig = np.where(np.abs(sig) < tol)[0]\n",
+    "    if zerosig:\n",
+    "        nullsp_start = zerosig[0]\n",
+    "        assert np.array_equal(zerosig, np.arange(nullsp_start, pde_mat.shape[1]))\n",
+    "    else:\n",
+    "        nullsp_start = pde_mat.shape[0]\n",
+    "                              \n",
+    "    return vt[nullsp_start:].T\n",
+    "    \n",
+    "nullsp = find_nullspace(pde_mat)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 64,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "7.3852783309125538e-16"
+      ]
+     },
+     "execution_count": 64,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "la.norm(pde_mat @ nullsp)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 61,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def build_translation_mat(mexp, to_center):\n",
+    "    n = len(mexp.coeffs)\n",
+    "    result = np.zeros((n, n))\n",
+    "    \n",
+    "    for j in range(n):\n",
+    "        unit_coeffs = np.zeros(n)\n",
+    "        unit_coeffs[j] = 1\n",
+    "        unit_mexp = type(mexp)(\n",
+    "            mexp.toy_ctx, mexp.center, mexp.rscale, mexp.order,\n",
+    "            unit_coeffs, derived_from=mexp)\n",
+    "        \n",
+    "        result[:, j] = t.multipole_expand(unit_mexp, to_center).coeffs\n",
+    "        \n",
+    "    return result\n",
+    "\n",
+    "tmat = build_translation_mat(mexp, np.array([0, 0.5]))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 62,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "array([[ 1.        ,  0.        ,  0.        ,  0.        ,  0.        ,\n",
+       "         0.        ,  0.        ,  0.        ,  0.        ,  0.        ,\n",
+       "         0.        ,  0.        ,  0.        ,  0.        ,  0.        ],\n",
+       "       [ 0.        ,  1.        ,  0.        ,  0.        ,  0.        ,\n",
+       "         0.        ,  0.        ,  0.        ,  0.        ,  0.        ,\n",
+       "         0.        ,  0.        ,  0.        ,  0.        ,  0.        ],\n",
+       "       [ 0.5       ,  0.        ,  1.        ,  0.        ,  0.        ,\n",
+       "         0.        ,  0.        ,  0.        ,  0.        ,  0.        ,\n",
+       "         0.        ,  0.        ,  0.        ,  0.        ,  0.        ],\n",
+       "       [ 0.        ,  0.        ,  0.        ,  1.        ,  0.        ,\n",
+       "         0.        ,  0.        ,  0.        ,  0.        ,  0.        ,\n",
+       "         0.        ,  0.        ,  0.        ,  0.        ,  0.        ],\n",
+       "       [ 0.        ,  0.5       ,  0.        ,  0.        ,  1.        ,\n",
+       "         0.        ,  0.        ,  0.        ,  0.        ,  0.        ,\n",
+       "         0.        ,  0.        ,  0.        ,  0.        ,  0.        ],\n",
+       "       [ 0.125     ,  0.        ,  0.5       ,  0.        ,  0.        ,\n",
+       "         1.        ,  0.        ,  0.        ,  0.        ,  0.        ,\n",
+       "         0.        ,  0.        ,  0.        ,  0.        ,  0.        ],\n",
+       "       [ 0.        ,  0.        ,  0.        ,  0.        ,  0.        ,\n",
+       "         0.        ,  1.        ,  0.        ,  0.        ,  0.        ,\n",
+       "         0.        ,  0.        ,  0.        ,  0.        ,  0.        ],\n",
+       "       [ 0.        ,  0.        ,  0.        ,  0.5       ,  0.        ,\n",
+       "         0.        ,  0.        ,  1.        ,  0.        ,  0.        ,\n",
+       "         0.        ,  0.        ,  0.        ,  0.        ,  0.        ],\n",
+       "       [ 0.        ,  0.125     ,  0.        ,  0.        ,  0.5       ,\n",
+       "         0.        ,  0.        ,  0.        ,  1.        ,  0.        ,\n",
+       "         0.        ,  0.        ,  0.        ,  0.        ,  0.        ],\n",
+       "       [ 0.02083333,  0.        ,  0.125     ,  0.        ,  0.        ,\n",
+       "         0.5       ,  0.        ,  0.        ,  0.        ,  1.        ,\n",
+       "         0.        ,  0.        ,  0.        ,  0.        ,  0.        ],\n",
+       "       [ 0.        ,  0.        ,  0.        ,  0.        ,  0.        ,\n",
+       "         0.        ,  0.        ,  0.        ,  0.        ,  0.        ,\n",
+       "         1.        ,  0.        ,  0.        ,  0.        ,  0.        ],\n",
+       "       [ 0.        ,  0.        ,  0.        ,  0.        ,  0.        ,\n",
+       "         0.        ,  0.5       ,  0.        ,  0.        ,  0.        ,\n",
+       "         0.        ,  1.        ,  0.        ,  0.        ,  0.        ],\n",
+       "       [ 0.        ,  0.        ,  0.        ,  0.125     ,  0.        ,\n",
+       "         0.        ,  0.        ,  0.5       ,  0.        ,  0.        ,\n",
+       "         0.        ,  0.        ,  1.        ,  0.        ,  0.        ],\n",
+       "       [ 0.        ,  0.02083333,  0.        ,  0.        ,  0.125     ,\n",
+       "         0.        ,  0.        ,  0.        ,  0.5       ,  0.        ,\n",
+       "         0.        ,  0.        ,  0.        ,  1.        ,  0.        ],\n",
+       "       [ 0.00260417,  0.        ,  0.02083333,  0.        ,  0.        ,\n",
+       "         0.125     ,  0.        ,  0.        ,  0.        ,  0.5       ,\n",
+       "         0.        ,  0.        ,  0.        ,  0.        ,  1.        ]])"
+      ]
+     },
+     "execution_count": 62,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "tmat"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 63,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "<matplotlib.image.AxesImage at 0x7f981a252748>"
+      ]
+     },
+     "execution_count": 63,
+     "metadata": {},
+     "output_type": "execute_result"
+    },
+    {
+     "data": {
+      "image/png": "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\n",
+      "text/plain": [
+       "<matplotlib.figure.Figure at 0x7f981a4e8780>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "plt.imshow(tmat)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.6.4+"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
-- 
GitLab