From 2d6ec66cabae99fde270a519342cfd466170474c Mon Sep 17 00:00:00 2001 From: Arend Lammertink Date: Fri, 27 Jan 2023 13:46:10 +0100 Subject: [PATCH 01/16] First steps implementing simulator for new theory --- examples/Test-LGrid.ipynb | 417 +++++++++++++++++++++++ fdtd/__init__.py | 1 + fdtd/lgrid.py | 691 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 1109 insertions(+) create mode 100644 examples/Test-LGrid.ipynb create mode 100644 fdtd/lgrid.py diff --git a/examples/Test-LGrid.ipynb b/examples/Test-LGrid.ipynb new file mode 100644 index 0000000..a7eecde --- /dev/null +++ b/examples/Test-LGrid.ipynb @@ -0,0 +1,417 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "enabling-default", + "metadata": {}, + "source": [ + "# 01. Basic Example\n", + "A simple example on how to use the FDTD Library" + ] + }, + { + "cell_type": "markdown", + "id": "academic-rough", + "metadata": {}, + "source": [ + "## Imports" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "tracked-hindu", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "import fdtd\n", + "import fdtd.backend as bd" + ] + }, + { + "cell_type": "markdown", + "id": "upset-drain", + "metadata": {}, + "source": [ + "## Set Backend" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "breathing-belief", + "metadata": {}, + "outputs": [], + "source": [ + "fdtd.set_backend(\"numpy\")" + ] + }, + { + "cell_type": "markdown", + "id": "copyrighted-custody", + "metadata": {}, + "source": [ + "## Constants" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "unlike-perfume", + "metadata": {}, + "outputs": [], + "source": [ + "WAVELENGTH = 1550e-9\n", + "SPEED_LIGHT: float = 299_792_458.0 # [m/s] speed of light" + ] + }, + { + "cell_type": "markdown", + "id": "fourth-vocabulary", + "metadata": {}, + "source": [ + "## Simulation" + ] + }, + { + "cell_type": "markdown", + "id": "desirable-thread", + "metadata": {}, + "source": [ + "create FDTD Grid" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "vertical-south", + "metadata": {}, + "outputs": [], + "source": [ + "grid = fdtd.LGrid(\n", + " (2.5e-5, 1.5e-5, 1),\n", + " grid_spacing=0.1 * WAVELENGTH,\n", + " permittivity=1.0,\n", + " permeability=1.0,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "balanced-motel", + "metadata": {}, + "source": [ + "boundaries" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "ordered-message", + "metadata": {}, + "outputs": [], + "source": [ + "# grid[0, :, :] = fdtd.PeriodicBoundary(name=\"xbounds\")\n", + "grid[0:10, :, :] = fdtd.PML(name=\"pml_xlow\")\n", + "grid[-10:, :, :] = fdtd.PML(name=\"pml_xhigh\")\n", + "\n", + "# grid[:, 0, :] = fdtd.PeriodicBoundary(name=\"ybounds\")\n", + "grid[:, 0:10, :] = fdtd.PML(name=\"pml_ylow\")\n", + "grid[:, -10:, :] = fdtd.PML(name=\"pml_yhigh\")\n", + "\n", + "grid[:, :, 0] = fdtd.PeriodicBoundary(name=\"zbounds\")" + ] + }, + { + "cell_type": "markdown", + "id": "interesting-onion", + "metadata": {}, + "source": [ + "sources" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "official-happening", + "metadata": {}, + "outputs": [], + "source": [ + "grid[50:55, 70:75, 0] = fdtd.LineSource(\n", + " period=WAVELENGTH / SPEED_LIGHT, name=\"linesource\"\n", + ")\n", + "grid[100, 60, 0] = fdtd.PointSource(\n", + " period=WAVELENGTH / SPEED_LIGHT, name=\"pointsource\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "facial-giant", + "metadata": {}, + "source": [ + "detectors" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "included-cherry", + "metadata": {}, + "outputs": [], + "source": [ + "grid[12e-6, :, 0] = fdtd.LineDetector(name=\"detector\")" + ] + }, + { + "cell_type": "markdown", + "id": "chronic-surprise", + "metadata": {}, + "source": [ + "objects" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "corresponding-consequence", + "metadata": {}, + "outputs": [], + "source": [ + "grid[11:32, 30:84, 0:1] = fdtd.AnisotropicObject(permittivity=2.5, name=\"object\")" + ] + }, + { + "cell_type": "markdown", + "id": "separate-variance", + "metadata": {}, + "source": [ + "## Run simulation" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "genuine-audience", + "metadata": {}, + "outputs": [], + "source": [ + "grid.run(50, progress_bar=False)" + ] + }, + { + "cell_type": "markdown", + "id": "norman-bulgarian", + "metadata": {}, + "source": [ + "## Visualization" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "purple-generic", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAdcAAAGZCAYAAADb1nvdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8qNh9FAAAACXBIWXMAAA9hAAAPYQGoP6dpAABLyklEQVR4nO2dfcxcxXX/5+6uwXZ+wfCzajcpGLcIZAwK71T5JWC7rdWKyLz8U1URqQFFAgWVFKlITUlxWxIJl7YJyotSEjBNo77EaQhKozYUkkiNQ1IXIxynIqUu5kVQE2Mi6mAD+/j8/lid5zl7dubemTszd+999vuRHu2zd+eembn73Zlz58zMLYiIDAAAAACS0Zt2AQAAAIDFBjpXAAAAIDHoXAEAAIDEoHMFAAAAEoPOFQAAAEgMOlcAAAAgMehcAQAAgMSgcwUAAAASg84VAAAASEwrO9cHHnjAFEVh/v3f/33aRQEgGOgXdBHoNi2t7FwBAACALoPOFQAAAEhMZzvXm266ySxdutQ8/vjj88eOHz9ufvVXf9WsXr3avPTSS0H2iMhcccUVZuXKlea5556bP/7666+bc845x5x99tnmZz/7mfnXf/1XUxSF+du//dsJG1/84hdNURRm9+7d9SsGFjV33nmnGQwG5vnnn5/47IYbbjArV640x44d87Z31113mV6vZ77+9a+PHb/uuuvM8uXLzQ9/+ENz5MgRc/LJJ5sbb7xx4vwDBw6Yfr9v7r777vDKgJlj48aNpigK698DDzwQZGvRt7nUQnbs2EHGGNq9e7czzdGjR+n888+nX/qlX6JXX32ViIjuuOMO6vV69PDDD4+lPf300+n000+vzPfQoUN06qmn0i//8i/Tm2++SUREW7dupWXLltHevXvn011wwQX0nve8Z+L8Sy65hC655BKPGoLFTJl+Dx48SCeeeCLdfvvtY8dfeeUVWrZsGd12221ENNKdMYaeeeaZ0ryOHz9OV1xxBZ1yyil04MABIiK6//77yRhDX/jCF+bT3XrrrfS2t72NfvrTn46df9ttt9HSpUvp0KFDdaoKFhE+7e6PfvQjeuyxx8b+fu3Xfo36/T5973vfm0+HNpeos50rEdHTTz9NJ510El199dX0yCOPUK/Xo49+9KMT6c444ww644wzvPL+7ne/S4PBgH73d3/X2kjJ8j3xxBPzx/7t3/6NjDH0V3/1V175gMVLlX63bt1Kq1atojfeeGP+2Pbt26nX6813pjfccAP1+/35DrMMbqAuvfRS2rNnDy1fvpyuvfbasTT79++nXq9Hn/jEJ+aPHT16lFauXEnXX399eCXBosO33ZXcfffdZIyhe++9d+w42tyOd65ERH//939PxhhaunQpbdiwgYbDYXT+27dvJ2MMnXjiiRONFBHRsWPHaNWqVfTBD35w/tgHPvAB+rmf+zk6duxYdP6g21Tpd8+ePWSMoS996UtERDQ3N0dr166lLVu21M5z165dNBgMaOnSpbRu3To6cuTIRJorr7ySzjzzTDp+/DgREd13331kjKHHH3+8dr5g8RDauf7N3/wNFUVhvaEJZTG2uZ3vXI8cOUKrV68mYww99NBDSfJ/4YUX6IQTTiBjDD355JPWNH/4h39Iy5cvp1dffZVefvllOvHEE+kjH/lIkvxBt/HR72WXXUbvfve7iYjooYceImMMffOb36yd53A4pPPOO4+MMXTPPfdY0zz66KNj+Vx44YXzZQAgpN391re+RSeccAJt3bo1Sd6Lsc3tfOd67bXX0vLly+mcc86h008/nQ4fPhyV93A4pA0bNtDq1atpzZo1dPHFF48N3zEvvvgiLVmyhP78z/+cPv7xj1O/36dnn302Km+wOPDR786dO+fvGjdv3kxnnXXW/B1lHW6//Xbq9Xp00UUX0YoVK2j//v3WdOeeey5t2bKFdu3aNXb3DIBvu/vkk0/SSSedRJs3b56Pk8awWNvcTneun//858kYQ/fffz/t37+fVqxYQVdddVVU3txIPfLII/TYY4/RkiVL6JZbbrGmff/7309nnHEGnXbaaXT11VdH5QsWDz76HQ6HtGbNGtq4cSMVRUGf/OQna+f38MMPU6/XozvuuIMOHz5c2kDde++91Ov16PLLL6fVq1db04DZxEe3zz77LL3zne+k888/n1577bUk+S7WNrfVnev27dtp586dE38/+9nPaO/evbRs2bKxYYmvfOUrZIwZm7RB5B9c50Zq27Zt88f+7M/+jIwx9NWvfnUi/Q9+8AMyxpAxhh555JG61QWLDB/9Ei3EmWyzeH0nNL344ou0atUq2rRpE83NzRERzTdQH/7whyfSv/7667Ry5UoyxiSJlYHFg49u169fT8uXL6evfvWrE7OGX3755XlbaHNb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maIozKc//emx49u2bTP9ft/8y7/8iyEic+aZZ5pf//Vfnzj/yJEjZsWKFebmm28OzhssbrSG77zzTjMYDMzzzz8/kfaGG24wK1euNMeOHfO2f9ddd5ler2e+/vWvjx2/7rrrzPLly80Pf/hDc+TIEXPyySebG2+8ceL8AwcOmH6/b+6+++6QaoFFjq3t1UPI8m/jxo3BeSzadpdaxI4dO8gYQ7t37x47/ulPf5qMMfQP//APY8fvuusuMsbQQw89RERE+/bto+XLl9O11147lm7btm1kjKFvf/vblWW46aab6IQTTpgvw6OPPkq9Xo8++tGPzqe55557qCgK+s///M+xcz/zmc+QMYZ+9KMfedcZLC58NXzw4EE68cQT6fbbbx9L98orr9CyZcvotttuIyKirVu3kjGGnnnmmdJ8jx8/TldccQWdcsopdODAASIiuv/++8kYQ1/4whfm09166630tre9jX7605+OnX/bbbfR0qVL6dChQ7XqDbpNSNv78ssv02OPPTb29xd/8RdkjKEPfehD8+lmvd1tZef6/e9/n9566y363//9X/rnf/5n+vmf/3m6/PLL6a233hpLzw3KySefTPv27aP169fTunXr6MiRI2Pp/viP/5j6/T595zvfqSzDsWPH6IILLqBf/MVfpP/4j/+g1atX04YNG2g4HM6nee211+jtb387ffjDHx47d/369bRp06b6FwB0nhANb926lVatWkVvvPHG/LHt27dTr9eb70xvuOEG6vf78x1mGYcOHaJTTz2VLr30UtqzZ4/V0dy/fz/1ej36xCc+MX/s6NGjtHLlSrr++uvjKg86S2jbK3nqqado5cqVtGnTpjEtz3q728rOVf+dffbZ9Oqrr1rPOXToEJ122mm0dOlSWrZsGe3duze6HE8//TSddNJJtHTpUlq1ahW9+OKLE2luueUWWrFixXxH/uijj1rvrsFsEaLhPXv2kDGGvvSlLxER0dzcHK1du5a2bNlSO/9du3bRYDCgpUuXWh1NIqIrr7ySzjzzTDp+/DgREd13331kjKHHH3+8dr6g29Rpe4mIXnrpJVq7di2de+65E6MhoSy2dreVnesXv/hF2r17N33rW9+iG2+8kYwx9Bu/8RvO826++WYyxtA111yTrCzve9/7yBhDt956q/Xzp59+mnq9Hv3lX/4lERFdc801dOqpp455WmD2CNXwZZddRu9+97uJiOihhx4iYwx985vfrJ3/cDik8847j4wxdM8991jTcIPE+Vx44YXzZQCzSZ2297XXXqMLLriAfuEXfoGef/75JOVYTO1uKztXPe7/wQ9+kIwxtHPnzolzHn74Yer1enTppZeSMYa+8pWvRJfj85//PBlj6NJLL6UlS5bQ97//fWu6973vffSud72LnnvuOer3+/Sxj30sOm/QbUI1vHPnzvm7xs2bN9NZZ501f0dZh9tvv516vR5ddNFFtGLFCtq/f7813bnnnktbtmyhXbt2jd09g9kkVLdvvvkmbd68mU466SR68sknk5RhsbW7nehcDx8+TKeccgqdffbZNDc3N3/8xRdfpFWrVtGmTZtoOBzSlVdeSStWrKD//u//rl2GvXv30rJly+i3f/u36Y033qCLLrqITj/9dDp8+PBE2ocffpiMMbRp0yY64YQT6ODBg7XzBYuDUA0Ph0Nas2YNbdy4kYqioE9+8pO182ZH84477qDDhw/TmjVr6OKLLx6LgzH33nsv9Xo9uvzyy2n16tXWNGB2CNXtBz7wAVqyZAk98sgjSfJfjO1uJzpXIqI//dM/JWMM/fVf/zURjRqlDRs20OrVq+mll14iIppvUC655JJagfUjR47QunXraP369fNj+vv376cVK1bQVVddZT1n/fr1ZIyZmDgCZpMQDTPbt28nY4x1Fq/vhCbpaHIj+Nhjj9GSJUsmJoAQEb3++uu0cuVKMsaMzcgEs0mIbvn97/zO70zMGpYzdme93e1M53r06FFas2YNnXnmmTQcDueHvx599NGxdN/73vdoMBiMNSi+U8KvvfZaWr58+cSUbh66kzMsmT/6oz+an2UHQIiGmQMHDpAxhm666aaJc3yW4tgcTebuu+8mYww9+OCDE+ddd911NBgM6IUXXvCvIFiUhOiWNWn727Bhw/x5s97uFkREXgtigZWLL77YFEVhdu/ePe2igI7yqU99ytxyyy1m37595pxzzmkkzzfffNOsXbvWvPe97zVf/vKXG8kTgFR0od1t5faHbee1114z+/btM//4j/9oHn/8cfPggw9Ou0iggzzxxBPmmWeeMX/yJ39irrrqqkY61p/85Cfmxz/+sdmxY4c5ePCg+f3f//3seQKQgq61u+hca7Bnzx6zadMms3LlSrNt2zZz9dVXT7tI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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, axes = plt.subplots(2, 3, squeeze=False)\n", + "titles = [\"Lx: xy\", \"Ly: xy\", \"Lz: xy\", \"Rx: xy\", \"Ry: xy\", \"Rz: xy\"]\n", + "\n", + "fields = bd.stack(\n", + " [\n", + " grid.E[:, :, 0, 0],\n", + " grid.E[:, :, 0, 1],\n", + " grid.E[:, :, 0, 2],\n", + " grid.H[:, :, 0, 0],\n", + " grid.H[:, :, 0, 1],\n", + " grid.H[:, :, 0, 2],\n", + " ]\n", + ")\n", + "\n", + "m = max(abs(fields.min().item()), abs(fields.max().item()))\n", + "\n", + "for ax, field, title in zip(axes.ravel(), fields, titles):\n", + " ax.set_axis_off()\n", + " ax.set_title(title)\n", + " ax.imshow(bd.numpy(field), vmin=-m, vmax=m, cmap=\"RdBu\")\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "hearing-kelly", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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zjZb/URERNbGqsO/Mvi/++47HDhwAF5eXibLO3XqBHt7e6Smpkrzbt68iQsXLqB79+419qtSqeDu7m4yERERyYVFz/kXFRXh+++/l15nZmZCp9PB09MTWq0Ww4cPx5kzZ/DZZ5/BYDBI5/E9PT3h4OAAtVqNCRMmYPbs2fDy8oKnpyfmzJmDNm3aoE+fPpYaFhERkVWzaPifPn0avXv3ll7PmjULADB27FjExcVhz549AID27dubrHf48GGEh4cDAFavXg2lUokRI0agpKQEERERSEpKgkKheCxjICIisjVWc5+/JfE+f6LHg/f5E1kHqz7nT0RERHWP4U9ERCQzDH8iIiKZYfgTERHJDMOfiIhIZhj+REREMsPwJyIikhmGPxERkcww/ImIiGSG4U9ERCQzDH8iIiKZYfgTERHJDMOfiIhIZhj+REREMsPwJyIikhmGPxERkcww/ImIiGSG4U9ERCQzDH8iIiKZYfgTERHJDMOfiIhIZhj+REREMsPwJyIikhmGPxERkcww/ImIiGSG4U9ERCQzDH8iIiKZYfgTERHJDMOfiIhIZhj+REREMsPwJyIikhmGPxERkcww/ImIiGSG4U9ERCQzDH8iIiKZYfgTERHJDMOfiIhIZhj+REREMsPwJyIikhmLhv+xY8cwePBgaLVaCIKA5OTkGttOmjQJgiBgzZo1JvNLS0sxbdo0eHt7w8XFBUOGDMFPP/1k3sKJiIhsmEXD//bt22jXrh0SExMf2C45ORlff/01tFptlWUxMTHYvXs3duzYgbS0NBQVFWHQoEEwGAzmKpuIiMimKS258QEDBmDAgAEPbHPjxg1MnToV+/btw8CBA02W6fV6vPvuu9i6dSv69OkDANi2bRv8/f1x4MAB9OvXz2y1ExER2SqrPudvNBoxevRozJ07F61bt66yPCMjA+Xl5YiMjJTmabVahISEID09vcZ+S0tLUVBQYDIRERHJhVWH//Lly6FUKjF9+vRql+fm5sLBwQENGjQwme/r64vc3Nwa+42Pj4darZYmf3//Oq2biIjImllt+GdkZOBf//oXkpKSIAjCI60riuID14mNjYVer5em7Ozsv1ouERGRzbDa8D9+/Djy8vIQEBAApVIJpVKJ69evY/bs2QgMDAQAaDQalJWVIT8/32TdvLw8+Pr61ti3SqWCu7u7yURERCQXVhv+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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure()\n", + "grid.visualize(z=0)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "c1054d71", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " ...,\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]]],\n", + "\n", + "\n", + " [[[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " ...,\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]]],\n", + "\n", + "\n", + " [[[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " ...,\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]]],\n", + "\n", + "\n", + " ...,\n", + "\n", + "\n", + " [[[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " ...,\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]]],\n", + "\n", + "\n", + " [[[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " ...,\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]]],\n", + "\n", + "\n", + " [[[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " ...,\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]],\n", + "\n", + " [[0., 0., 0.]]]])" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "grid.E" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "851c22e1", + "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.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/fdtd/__init__.py b/fdtd/__init__.py index f6d5f3c..a0efaac 100644 --- a/fdtd/__init__.py +++ b/fdtd/__init__.py @@ -4,6 +4,7 @@ __version__ = "0.2.5" from .grid import Grid +from .lgrid import LGrid from .sources import PointSource, LineSource, PlaneSource from .detectors import LineDetector, BlockDetector, CurrentDetector from .objects import Object, AbsorbingObject, AnisotropicObject diff --git a/fdtd/lgrid.py b/fdtd/lgrid.py new file mode 100644 index 0000000..7deb867 --- /dev/null +++ b/fdtd/lgrid.py @@ -0,0 +1,691 @@ +""" The FDTD LGrid + +The grid is the core of the FDTD Library. It is where everything comes +together and where the biggest part of the calculations are done. + +""" + +## Imports + +# standard library +import os +from os import path, makedirs, chdir, remove +from subprocess import check_call, CalledProcessError +from glob import glob +from datetime import datetime + +# 3rd party +from tqdm import tqdm +from numpy import savez + +# typing +from .typing_ import Tuple, Number, Tensorlike + +# relative +from .backend import backend as bd +from . import constants as const + +## Functions +def curl_E(E: Tensorlike) -> Tensorlike: + """Transforms an E-type field into an H-type field by performing a curl + operation + + Args: + E: Electric field to take the curl of (E-type field located on the + edges of the grid cell [integer gridpoints]) + + Returns: + The curl of E (H-type field located on the faces of the grid [half-integer grid points]) + """ + curl = bd.zeros(E.shape) + + curl[:, :-1, :, 0] += E[:, 1:, :, 2] - E[:, :-1, :, 2] + curl[:, :, :-1, 0] -= E[:, :, 1:, 1] - E[:, :, :-1, 1] + + curl[:, :, :-1, 1] += E[:, :, 1:, 0] - E[:, :, :-1, 0] + curl[:-1, :, :, 1] -= E[1:, :, :, 2] - E[:-1, :, :, 2] + + curl[:-1, :, :, 2] += E[1:, :, :, 1] - E[:-1, :, :, 1] + curl[:, :-1, :, 2] -= E[:, 1:, :, 0] - E[:, :-1, :, 0] + + return curl + + +def curl_H(H: Tensorlike) -> Tensorlike: + """Transforms an H-type field into an E-type field by performing a curl + operation + + Args: + H: Magnetic field to take the curl of (H-type field located on half-integer grid points) + + Returns: + The curl of H (E-type field located on the edges of the grid [integer grid points]) + + """ + curl = bd.zeros(H.shape) + + curl[:, 1:, :, 0] += H[:, 1:, :, 2] - H[:, :-1, :, 2] + curl[:, :, 1:, 0] -= H[:, :, 1:, 1] - H[:, :, :-1, 1] + + curl[:, :, 1:, 1] += H[:, :, 1:, 0] - H[:, :, :-1, 0] + curl[1:, :, :, 1] -= H[1:, :, :, 2] - H[:-1, :, :, 2] + + curl[1:, :, :, 2] += H[1:, :, :, 1] - H[:-1, :, :, 1] + curl[:, 1:, :, 2] -= H[:, 1:, :, 0] - H[:, :-1, :, 0] + + return curl + + +def div_E(E: Tensorlike) -> Tensorlike: + """Computes the divergence of an E-type field located on the edges of the grid cells + Args: + E: E-type field of shape (x, y, z, 3) + Returns: + The divergence of E, a scalar field of shape (x, y, z, 1) + + """ + + div = bd.zeros(E.shape[:-1] + (1,)) + + div[:, :, :-1, 0] += E[:, :, 1:, 0] - E[:, :, :-1, 0] + div[:, :-1, :, 0] += E[:, 1:, :, 1] - E[:, :-1, :, 1] + div[:-1, :, :, 0] += E[1:, :, :, 2] - E[:-1, :, :, 2] + + return div + +def div_H(H: Tensorlike) -> Tensorlike: + """Computes the divergence of an H-type field located on the faces of the grid + Args: + H: H-type field of shape (x, y, z, 3) + Returns: + The divergence of H, a scalar field of shape (x, y, z, 1) + """ + + div = bd.zeros(H.shape[:-1]+(1,)) + + div[:, :-1, :, 0] += H[:, :-1, :, 2] - H[:, 1:, :, 2] + div[:, :, :-1, 0] -= H[:, :, :-1, 1] - H[:, :, 1:, 1] + div[:-1, :, :, 0] += H[:-1, :, :, 0] - H[1:, :, :, 0] + + return div + + +def grad_E(E: Tensorlike) -> Tensorlike: + """Computes the gradient of a scalar field + Args: + E: Electric field of shape (x, y, z, 3) + Returns: + The gradient of E, a vector field of shape (x, y, z, 3) located on the edges of the grid cells + """ + + grad = bd.zeros((*E.shape[:-1], 3)) + + grad[:-1, :, :, 0] = E[1:, :, :, 0] - E[:-1, :, :, 0] + grad[:, :-1, :, 1] = E[:, 1:, :, 0] - E[:, :-1, :, 0] + grad[:, :, :-1, 2] = E[:, :, 1:, 0] - E[:, :, :-1, 0] + + return grad + + + +def grad_H(H: Tensorlike) -> Tensorlike: + """Computes the gradient of a scalar field + Args: + H: Magnetic field of shape (x, y, z, 3) + Returns: + The gradient of H, a vector field of shape (x, y, z, 3) located on the faces of the grid + + """ + + grad = bd.zeros((*H.shape[:-1], 3)) + + grad[:, :-1, :, 0] = H[:, :-1, :, 0] - H[:, 1:, :, 0] + grad[:-1, :, :, 1] = H[:-1, :, :, 0] - H[1:, :, :, 0] + grad[:, :, :-1, 2] = H[:, :, :-1, 0] - H[:, :, 1:, 0] + + return grad + + + +## FDTD LGrid Class +class LGrid: + """The FDTD LGrid + + The LGrid is the core of the FDTD Library. It is where everything comes + together and where the biggest part of the calculations are done. + + This implementation implements fields based on the Laplace operator + rather than Maxwells equations + + """ + + from .visualization import visualize + + def __init__( + self, + shape: Tuple[Number, Number, Number], + grid_spacing: float = 155e-9, + permittivity: float = 1.0, + permeability: float = 1.0, + courant_number: float = None, + ): + """ + Args: + shape: shape of the FDTD grid. + grid_spacing: distance between the grid cells. + permittivity: the relative permittivity of the background. + permeability: the relative permeability of the background. + courant_number: the courant number of the FDTD simulation. + Defaults to the inverse of the square root of the number of + dimensions > 1 (optimal value). The timestep of the simulation + will be derived from this number using the CFL-condition. + """ + # save the grid spacing + self.grid_spacing = float(grid_spacing) + + # save grid shape as integers + self.Nx, self.Ny, self.Nz = self._handle_tuple(shape) + + # dimension of the simulation: + self.D = int(self.Nx > 1) + int(self.Ny > 1) + int(self.Nz > 1) + + # courant number of the simulation (optimal value) + max_courant_number = float(self.D) ** (-0.5) + if courant_number is None: + # slight stability factor added + self.courant_number = 0.99 * max_courant_number + elif courant_number > max_courant_number: + raise ValueError( + f"courant_number {courant_number} too high for " + f"a {self.D}D simulation" + ) + else: + self.courant_number = float(courant_number) + + # timestep of the simulation + self.time_step = self.courant_number * self.grid_spacing / const.c + + # save electric and magnetic field + #self.E = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + #self.H = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + + self.k = float(8.987551787368176e+16) + + self.C = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + + self.P = bd.zeros((self.Nx, self.Ny, self.Nz, 1)) + self.A = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + + self.E = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + self.H = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + + self.F = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + self.I = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + + self.T = bd.zeros((self.Nx, self.Ny, self.Nz, 1)) + self.Chi = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + + self.Eambda = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + self.Psi = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + + self.Y = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + + + + + # save the inverse of the relative permittiviy and the relative permeability + # these tensors can be anisotropic! + + if bd.is_array(permittivity) and len(permittivity.shape) == 3: + permittivity = permittivity[:, :, :, None] + self.inverse_permittivity = bd.ones((self.Nx, self.Ny, self.Nz, 3)) / float( + permittivity + ) + + if bd.is_array(permeability) and len(permeability.shape) == 3: + permeability = permeability[:, :, :, None] + self.inverse_permeability = bd.ones((self.Nx, self.Ny, self.Nz, 3)) / float( + permeability + ) + + # save current time index + self.time_steps_passed = 0 + + # dictionary containing the sources: + self.sources = [] + + # dictionary containing the boundaries + self.boundaries = [] + + # dictionary containing the detectors + self.detectors = [] + + # dictionary containing the objects in the grid + self.objects = [] + + # folder path to store the simulation + self.folder = None + + def _handle_distance(self, distance: Number) -> int: + """ transform a distance to an integer number of gridpoints """ + if not isinstance(distance, int): + return int(float(distance) / self.grid_spacing + 0.5) + return distance + + def _handle_time(self, time: Number) -> int: + """ transform a time value to an integer number of timesteps """ + if not isinstance(time, int): + return int(float(time) / self.time_step + 0.5) + return time + + def _handle_tuple( + self, shape: Tuple[Number, Number, Number] + ) -> Tuple[int, int, int]: + """ validate the grid shape and transform to a length-3 tuple of ints """ + if len(shape) != 3: + raise ValueError( + f"invalid grid shape {shape}\n" + f"grid shape should be a 3D tuple containing floats or ints" + ) + x, y, z = shape + x = self._handle_distance(x) + y = self._handle_distance(y) + z = self._handle_distance(z) + return x, y, z + + def _handle_slice(self, s: slice) -> slice: + """ validate the slice and transform possibly float values to ints """ + start = ( + s.start + if not isinstance(s.start, float) + else self._handle_distance(s.start) + ) + stop = ( + s.stop if not isinstance(s.stop, float) else self._handle_distance(s.stop) + ) + step = ( + s.step if not isinstance(s.step, float) else self._handle_distance(s.step) + ) + return slice(start, stop, step) + + def _handle_single_key(self, key): + """ transform a single index key to a slice or list """ + try: + len(key) + return [self._handle_distance(k) for k in key] + except TypeError: + if isinstance(key, slice): + return self._handle_slice(key) + else: + return [self._handle_distance(key)] + return key + + @property + def x(self) -> int: + """ get the number of grid cells in the x-direction """ + return self.Nx * self.grid_spacing + + @property + def y(self) -> int: + """ get the number of grid cells in the y-direction """ + return self.Ny * self.grid_spacing + + @property + def z(self) -> int: + """ get the number of grid cells in the y-direction """ + return self.Nz * self.grid_spacing + + @property + def shape(self) -> Tuple[int, int, int]: + """ get the shape of the FDTD grid """ + return (self.Nx, self.Ny, self.Nz) + + @property + def time_passed(self) -> float: + """ get the total time passed """ + return self.time_steps_passed * self.time_step + + def run(self, total_time: Number, progress_bar: bool = True): + """run an FDTD simulation. + + Args: + total_time: the total time for the simulation to run. + progress_bar: choose to show a progress bar during + simulation + + """ + if isinstance(total_time, float): + total_time /= self.time_step + time = range(0, int(total_time), 1) + if progress_bar: + time = tqdm(time) + for _ in time: + self.step() + + def step(self): + """do a single FDTD step by first updating the electric field and then + updating the magnetic field + """ + #self.update_E() # [E] += c*dt*inv(ε)*curl(H) + #self.update_H() + + """ + self.update_P() # [P] = div [C] + self.update_A() # [A] = curl [C] + + self.update_L() # [L] = grad [P] + self.update_R() # [R] = curl [Omega] + + self.update_F() # [F] = [L] + [R] + self.update_I() # [I] = k [F] + + self.update_T() # [T] = div [I] + self.update_Chi() # [Chi] = curl [I] + + self.update_Lambda()# [Lambda] = grad [T] + self.update_Psi() # [Psi] = curl [Chi] + + self.update_Y() # [Y] = [Lambda] + [Psi] + """ + + self.update_C() # [C] += [F]*dt + [Y]*dt^2 + + + self.time_steps_passed += 1 + + def update_E(self): + """ update the electric field by using the curl of the magnetic field """ + + # update boundaries: step 1 + for boundary in self.boundaries: + boundary.update_phi_E() + + curl = curl_H(self.H) + self.E += self.courant_number * self.inverse_permittivity * curl + + # update objects + for obj in self.objects: + obj.update_E(curl) + + # update boundaries: step 2 + for boundary in self.boundaries: + boundary.update_E() + + # add sources to grid: + for src in self.sources: + src.update_E() + + # detect electric field + for det in self.detectors: + det.detect_E() + + def update_H(self): + """ update the magnetic field by using the curl of the electric field """ + + # update boundaries: step 1 + for boundary in self.boundaries: + boundary.update_phi_H() + + curl = curl_E(self.E) + self.H -= self.courant_number * self.inverse_permeability * curl + + # update objects + for obj in self.objects: + obj.update_H(curl) + + # update boundaries: step 2 + for boundary in self.boundaries: + boundary.update_H() + + # add sources to grid: + for src in self.sources: + src.update_H() + + # detect electric field + for det in self.detectors: + det.detect_H() + + + def update_C(self): + """ update the C field by using the vector Laplace operator """ + + # update boundaries: step 1 + #for boundary in self.boundaries: + # boundary.update_phi_H() + + #curl = curl_E(self.E) + #self.H -= self.courant_number * self.inverse_permeability * curl + + # Since C represents a force density, it would be located on + # the faces of the grid and thus be a H-type field. + + self.P = div_H (self.C) + self.A = curl_H(self.C) + + self.E = grad_H(self.P) + self.H = curl_E(self.A) + + self.F = self.E + self.H + + self.I = self.k * self.F + + self.T = div_H(self.I) + self.Chi = curl_H(self.I) + + self.Eambda = grad_H(self.T) + self.Psi = curl_H(self.Chi) + + self.Y = self.Eambda + self.Psi + + + self.C -= self.courant_number * self.k * self.F + self.C -= self.courant_number * self.courant_number * self.k * self.Y + + + # update objects + #for obj in self.objects: + # obj.update_H(curl) + + # update boundaries: step 2 + #for boundary in self.boundaries: + # boundary.update_H() + + # add sources to grid: + for src in self.sources: + src.update_H() + src.update_E() + + + # detect electric field + #for det in self.detectors: + # det.detect_H() + + + + def reset(self): + """ reset the grid by setting all fields to zero """ + #self.H *= 0.0 + #self.E *= 0.0 + self.C *= 0.0 + self.P *= 0.0 + self.A *= 0.0 + self.E *= 0.0 + self.H *= 0.0 + self.F *= 0.0 + self.I *= 0.0 + self.T *= 0.0 + self.Chi *= 0.0 + self.Eambda *= 0.0 + self.Psi *= 0.0 + self.Y *= 0.0 + + self.time_steps_passed *= 0 + + def add_source(self, name, source): + """ add a source to the grid """ + source._register_grid(self) + self.sources[name] = source + + def add_boundary(self, name, boundary): + """ add a boundary to the grid """ + boundary._register_grid(self) + self.boundaries[name] = boundary + + def add_detector(self, name, detector): + """ add a detector to the grid """ + detector._register_grid(self) + self.detectors[name] = detector + + def add_object(self, name, obj): + """ add an object to the grid """ + obj._register_grid(self) + self.objects[name] = obj + + def __setitem__(self, key, attr): + if not isinstance(key, tuple): + x, y, z = key, slice(None), slice(None) + elif len(key) == 1: + x, y, z = key[0], slice(None), slice(None) + elif len(key) == 2: + x, y, z = key[0], key[1], slice(None) + elif len(key) == 3: + x, y, z = key + else: + raise KeyError("maximum number of indices for the grid is 3") + + attr._register_grid( + grid=self, + x=self._handle_single_key(x), + y=self._handle_single_key(y), + z=self._handle_single_key(z), + ) + + def __repr__(self): + return ( + f"{self.__class__.__name__}(shape=({self.Nx},{self.Ny},{self.Nz}), " + f"grid_spacing={self.grid_spacing:.2e}, courant_number={self.courant_number:.2f})" + ) + + def __str__(self): + """string representation of the grid + + lists all the components and their locations in the grid. + """ + s = repr(self) + "\n" + if self.sources: + s = s + "\nsources:\n" + for src in self.sources: + s += str(src) + if self.detectors: + s = s + "\ndetectors:\n" + for det in self.detectors: + s += str(det) + if self.boundaries: + s = s + "\nboundaries:\n" + for bnd in self.boundaries: + s += str(bnd) + if self.objects: + s = s + "\nobjects:\n" + for obj in self.objects: + s += str(obj) + return s + + def save_simulation(self, sim_name=None): + """ + Creates a folder and initializes environment to store simulation or related details. + saveSimulation() needs to be run before running any function that stores data (generate_video(), save_data()). + + Parameters:- + (optional) sim_name (string): Preferred name for simulation + """ + makedirs("fdtd_output", exist_ok=True) # Output master folder declaration + # making full_sim_name with timestamp + full_sim_name = ( + str(datetime.now().year) + + "-" + + str(datetime.now().month) + + "-" + + str(datetime.now().day) + + "-" + + str(datetime.now().hour) + + "-" + + str(datetime.now().minute) + + "-" + + str(datetime.now().second) + ) + # Simulation name (optional) + if sim_name is not None: + full_sim_name = full_sim_name + " (" + sim_name + ")" + folder = "fdtd_output_" + full_sim_name + # storing folder path for saving simulation + self.folder = os.path.abspath(path.join("fdtd_output", folder)) + # storing timestamp title for self.generate_video + self.full_sim_name = full_sim_name + makedirs(self.folder, exist_ok=True) + return self.folder + + def generate_video(self, delete_frames=False): + """Compiles frames into a video + + These framed should be saved through ``fdtd.Grid.visualize(save=True)`` while having ``fdtd.Grid.save_simulation()`` enabled. + + Args: + delete_frames (optional, bool): delete stored frames after conversion to video. + + Returns: + the filename of the generated video. + + Note: + this function requires ``ffmpeg`` to be available in your path. + """ + if self.folder is None: + raise Exception( + "Save location not initialized. Please read about 'fdtd.Grid.saveSimulation()' or try running 'grid.saveSimulation()'." + ) + cwd = path.abspath(os.getcwd()) + chdir(self.folder) + try: + check_call( + [ + "ffmpeg", + "-y", + "-framerate", + "8", + "-i", + "file%04d.png", + "-r", + "30", + "-pix_fmt", + "yuv420p", + "fdtd_sim_video_" + self.full_sim_name + ".mp4", + ] + ) + except (FileNotFoundError, CalledProcessError): + raise CalledProcessError( + "Error when calling ffmpeg. Is ffmpeg installed and available in your path?" + ) + if delete_frames: # delete frames + for file_name in glob("*.png"): + remove(file_name) + video_path = path.abspath( + path.join(self.folder, f"fdtd_sim_video_{self.full_sim_name}.mp4") + ) + chdir(cwd) + return video_path + + def save_data(self): + """ + Saves readings from all detectors in the grid into a numpy zip file. Each detector is stored in separate arrays. Electric and magnetic field field readings of each detector are also stored separately with suffix " (E)" and " (H)" (Example: ['detector0 (E)', 'detector0 (H)']). Therefore, the numpy zip file contains arrays twice the number of detectors. + REQUIRES 'fdtd.Grid.save_simulation()' to be run before this function. + + Parameters: None + """ + if self.folder is None: + raise Exception( + "Save location not initialized. Please read about 'fdtd.Grid.saveSimulation()' or try running 'grid.saveSimulation()'." + ) + dic = {} + for detector in self.detectors: + dic[detector.name + " (E)"] = [x for x in detector.detector_values()["E"]] + dic[detector.name + " (H)"] = [x for x in detector.detector_values()["H"]] + savez(path.join(self.folder, "detector_readings"), **dic) From 5c4deeb8b87e8deb52c2a6cb98dd69847bc3d220 Mon Sep 17 00:00:00 2001 From: Arend Lammertink Date: Sun, 8 Oct 2023 19:15:25 +0200 Subject: [PATCH 02/16] Quite a lot of investigations into the div, grad and curl operators. --- fdtd/constants.py | 3 + fdtd/lgrid.py | 1184 ++++++++++++++++++++++++++++++------- fdtd/operators.py | 1444 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 2421 insertions(+), 210 deletions(-) create mode 100644 fdtd/operators.py diff --git a/fdtd/constants.py b/fdtd/constants.py index 3fd97ca..a314f87 100644 --- a/fdtd/constants.py +++ b/fdtd/constants.py @@ -5,6 +5,9 @@ c: float = 299792458.0 """ speed of light """ +k: float = c*c +""" quantum circulation constant (8.987551787368176e+16)""" + X: int = 0 """ x-index (useful for indexing) """ diff --git a/fdtd/lgrid.py b/fdtd/lgrid.py index 7deb867..e949953 100644 --- a/fdtd/lgrid.py +++ b/fdtd/lgrid.py @@ -1,150 +1,852 @@ """ The FDTD LGrid -The grid is the core of the FDTD Library. It is where everything comes -together and where the biggest part of the calculations are done. +The grid is the core of the FDTD Library. It is where everything comes together +and where the biggest part of the calculations are done. + +The LGrid class is under construction and is to become the grid for simulating a +new theory based on the vector LaPlace operator. It is not yet functional and +should become an alternative implementation of the existing Grid class in the +FDTD library, which is made for simulating the electromagnetic Maxwell +equations. The end goal of the project is to build a simulator for a new theory, +based on the existing electromagnetic simulation library FDTD of which this +project is a fork. + +So, for now, the goal of the current project is to refactor / rework the +existing Grid class for the electromagnetic fields into a new Grid class for the +new theory. The first step of impelementing the new theory is to implement the +operators used and the update functions for the fields. + +Once these are working and tested, the next step is to implement the sources, +boundaries, detectors and objects. These will have to be adapted from their +electromagnetic counterparts to the new theory and have to be consistend with a +staggered Yee grid. + +The new theory is based on the vector LaPlace operator and the discovery of the +quantum circulation constant k, with a value equal to c*c but a unit of +measurement in [m/s^2]. This is a new constant of nature and represents the +quantum circulation constant, kinematic viscocity or diffusivity of the medium: + + k = c*c = 8.987551787368176e+16 [m/s^2]. + +The fundamental equation of the new theory is the definition of the time +derivative of any given vector field within the superfluid medium: + + dF/dt = -k * delta(F), + +where F is any given vector field, delta is the vector LaPlace operator and k is +the quantum circulation constant. -""" +Note that this definition only has units of measurement in meters and seconds +and thus it represents a fundamental equation of nature describing the +fundamental dynamics of space-time itself and is decoupled from the substance of +the medium, the latter being represented by mass density rho in the model. -## Imports +We believe that one day this equation will be recognized as one of the biggest +scientific breakthroughs of the 21st century, because there simply is no +argument to be made against such a simple and straightforward application of the +vector Laplacian using only one single Universal constant: k. -# standard library -import os -from os import path, makedirs, chdir, remove -from subprocess import check_call, CalledProcessError -from glob import glob -from datetime import datetime +The vector LaPlace operator is commonly defined as follows: -# 3rd party -from tqdm import tqdm -from numpy import savez + delta(F) = grad(div(F)) - curl(curl(F)), + +where grad is the gradient operator, div is the divergence operator and curl is +the curl operator. However, during the implementation of this simulator, it was +found that because of the design of the staggered Yee grid, the curl operator +actually has to be split into two separate operators, one for the left handed +curl and one for the right handed curl, so we redefine the vector LaPlace as +follows: + + delta(F) = grad(div(F)) - curl_l(curl_r(F)), + +where curl_l is the left handed curl operator and curl_r is the right handed +curl operator. -# typing -from .typing_ import Tuple, Number, Tensorlike +This definitiion can be negated: -# relative -from .backend import backend as bd -from . import constants as const + - delta(F) = - grad(div(F)) + curl_l(curl_r(F)), -## Functions -def curl_E(E: Tensorlike) -> Tensorlike: - """Transforms an E-type field into an H-type field by performing a curl - operation +And thus we can compute the time derivative of any given vector field F along: - Args: - E: Electric field to take the curl of (E-type field located on the - edges of the grid cell [integer gridpoints]) + dF/dt = -k * delta(F) = -k * grad(div(F)) + k * curl_l(curl_r(F)), - Returns: - The curl of E (H-type field located on the faces of the grid [half-integer grid points]) - """ - curl = bd.zeros(E.shape) +or: - curl[:, :-1, :, 0] += E[:, 1:, :, 2] - E[:, :-1, :, 2] - curl[:, :, :-1, 0] -= E[:, :, 1:, 1] - E[:, :, :-1, 1] + dF/dt = -k * delta(F) = k ( -grad(div(F)) + curl_l(curl_r(F)) ), - curl[:, :, :-1, 1] += E[:, :, 1:, 0] - E[:, :, :-1, 0] - curl[:-1, :, :, 1] -= E[1:, :, :, 2] - E[:-1, :, :, 2] +which can be rewritten to an update equation for F: - curl[:-1, :, :, 2] += E[1:, :, :, 1] - E[:-1, :, :, 1] - curl[:, :-1, :, 2] -= E[:, 1:, :, 0] - E[:, :-1, :, 0] + F += -k * delta(F) * dt = k ( -grad(div(F)) + curl_l(curl_r(F)) ) * dt, + +with dt the time step of the simulation. - return curl +Using the definition of the vector LaPlace operator, we can define several +fields within one consistent framework. The most simple field to define is the +flow velocity vector field v, so we can compute it's time derivate, the +acceleration vector field a, as follows: + a = dv/dt = -k * delta(v) = k ( -grad(div(v)) + curl_l(curl_r(v)) ). -def curl_H(H: Tensorlike) -> Tensorlike: - """Transforms an H-type field into an E-type field by performing a curl - operation +This is known as the velocity diffusion equation and is the fundamental equation +of fluid dynamics. - Args: - H: Magnetic field to take the curl of (H-type field located on half-integer grid points) +And, by applying Newton's F=ma, we can define the 3D force density vector field +F as follows: - Returns: - The curl of H (E-type field located on the edges of the grid [integer grid points]) + F = rho * a = -k * rho * delta(v) = k * rho * (-grad(div(v)) + + curl_l(curl_r(v)) ). + +One of the big benefits of the the fundamental equation of the new theory, +defining the time derivative of any given vector field F, is that we can now +apply this equation to the acceleration vector field a to compute the time +derivative of a, the jerk vector field j: - """ - curl = bd.zeros(H.shape) + j = da/dt = -k * delta(a) = k ( -grad(div(a)) + curl_l(curl_r(a)) ). + +And, by applying Y=mj, with Yank the time derivative of Force, we van the 3D +yank density vector field Y as follows: + + Y = rho * j = -k * rho * delta(a) = k * rho * (-grad(div(a)) + + curl_l(curl_r(a)) ). + +Thus, we come to a higher order model incorporating not only acceleration and +force density vector fields, but also their time derivatives: the jerk and yank +density vector fields. + +Note that we have used lower case for the v, a and j vector, contrary to the +convention of this library to denote vector fields with capital letters, because +these are commonly used symbols for velocity, acceleration and jerk. + +This means that we have obtained a model that is superior to both Maxwell's +equations as well as Navier-Stokes equations, because it is a higher order +model. This has far reaching consequences for our understanding of physics and +the medium, because it is known that the intermediate fields we obtain by +writing out the definition of the vector LaPlace operator, are 100% +mathematically consistent and free of any paradoxes or inconsistencies. Also, +any wave equations that can be derived from this model will be invariant to the +Galilean coordinate transformation, bringing the Lorentz tranform on which +Einstein's theory of relativity is based, into question. + +Furthermore, the introduction of the jerk and yank vector fields into the model +enables us to model the dynamics of the medium in a much more accurate way, as +from control systems theory it is known that not only the acceleration of an +actuator needs to be controlled, but also the jerk. Otherwise, all kinds of +vibrations and oscillations will occur in the system. + +In other words: since it is known from control systems that the jerk of the +system under control needs to be controlled as well, it is only logical that +this should be the case for the medium as well. And, since neither Maxwell's +equations nor Navier-Stokes are higher order equations, it is clear that the old +models are incomplete and therefore not capable of describing either the medium +or a fluid accurately. + +Now let's consider the negated definition of the vector LaPlace operator for a +field C: + + - delta(C) = - grad(div(C)) + curl_l(curl_r(C)), + +with C = rho * k * v, resulting in a unit of measurement of: + + [C] = [rho] * [k] * [v] = [kg/m^3] * [m^2/s] * [m/s] = [kg/s^2]. + +Thus, C represents a thusfar unknown quantity that has a unit of measurement in +[kg/s^2], [N/m], [J/m^2] or [Pa-m]. And since in our model the Ampere +representing electric current also has a unit of measurement in [kg/s^2], we +have chosen to use the symbol C. However, since electric current in our model is +considered to represent turbulence within the medium, it can be speculated that +C also represents some quantity related to turbulence. For now, we shall refer +to C as our primary dynamic field. + +From this, we can write out intermediate fields similar to Maxwell's equations, +with the big difference that these intermediate fields are derived from a higher +order equation, unlike Maxwell's: + + phi = div(C) + + A = curl_r(C) + + E = -grad(phi) + + B = curl_l(A) - curl[:, 1:, :, 0] += H[:, 1:, :, 2] - H[:, :-1, :, 2] - curl[:, :, 1:, 0] -= H[:, :, 1:, 1] - H[:, :, :-1, 1] +And because of vector identities we can also write: - curl[:, :, 1:, 1] += H[:, :, 1:, 0] - H[:, :, :-1, 0] - curl[1:, :, :, 1] -= H[1:, :, :, 2] - H[:-1, :, :, 2] + curl(E) = 0 + + div(B) = 0, + +since the divergence of a curl of the gradient of any twice-differentiable +scalar field p is always the zero vector (curl(grad(p))=0), and the divergence +of the curl of any vector field A is always zero as well (div(curl(A))=0). - curl[1:, :, :, 2] += H[1:, :, :, 1] - H[:-1, :, :, 1] - curl[:, 1:, :, 2] -= H[:, 1:, :, 0] - H[:, :-1, :, 0] +Thus, we have shown that the fields derived from application of the vector +Laplacian yield a Helmholtz decomposition of the field C, decomposing it into a +divergence free component B and a curl free component E. - return curl +From wikipedia: +"In physics and mathematics, in the area of vector calculus, Helmholtz's +theorem, also known as the fundamental theorem of vector calculus, states that +any sufficiently smooth, rapidly decaying vector field in three dimensions can +be resolved into the sum of an irrotational (curl-free) vector field and a +solenoidal (divergence-free) vector field; this is known as the Helmholtz +decomposition or Helmholtz representation. It is named after Hermann von +Helmholtz." -def div_E(E: Tensorlike) -> Tensorlike: - """Computes the divergence of an E-type field located on the edges of the grid cells - Args: - E: E-type field of shape (x, y, z, 3) - Returns: - The divergence of E, a scalar field of shape (x, y, z, 1) +In other words: the fields we have defined are consistent with the fundamental +theorem of vector calculus. - """ +We can compare this with Maxwell's equations: + + E = -grad(phi) - dA/dt, + + B = curl(A), - div = bd.zeros(E.shape[:-1] + (1,)) + curl(E) = -dB/dt, - div[:, :, :-1, 0] += E[:, :, 1:, 0] - E[:, :, :-1, 0] - div[:, :-1, :, 0] += E[:, 1:, :, 1] - E[:, :-1, :, 1] - div[:-1, :, :, 0] += E[1:, :, :, 2] - E[:-1, :, :, 2] + div(B) = 0, - return div + div(E) = rho / eps, + + curl(B) = mu * J - 1/c^2 * dE/dt, + +Where obviously the curl of the electric field is not equal zero and therefore +Maxwell's equations are not consistent with the fundamental theorem of vector +calculus: the Helmholtz theorem or Helmholtz decomposition as fundamentally +described by the vector Laplace operator. -def div_H(H: Tensorlike) -> Tensorlike: - """Computes the divergence of an H-type field located on the faces of the grid - Args: - H: H-type field of shape (x, y, z, 3) - Returns: - The divergence of H, a scalar field of shape (x, y, z, 1) - """ +In other words: we have shown that Maxwell's equations are mathematically +inconsistent. + +Note that when taking the curl on both sides of this equation: + + E = -grad(phi) - dA/dt, + +we obtain the Maxwell-Faraday equation, representing Faraday's law of induction: + + curl(E) = -dB/dt. + +Faraday's law of induction is a basic law of electromagnetism predicting how a +magnetic field will interact with an electric circuit to produce an +electromotive force (EMF), which is thus a law that applies at the macroscopic +level. Because of the inconsistencies in Maxwell's equations with respect to the +fundamental theorem of vector calculus, it is now clear that this entanglement +of the fundamental E and B fields in Maxwell's equations is responsible for a +lot of problems in current main stream physics, for starters the non-invariance +of Maxwell's equations to the Galilean transform. + +This is described by C.K. Thornhill in his paper "Real or Imaginary Space-Time? +Reality or Relativity?": + + https://etherphysics.net/CKT4.pdf + + "ABSTRACT + + The real space-time of Newtonian mechanics and the ether concept is + contrasted with the imaginary space-time of the non-ether concept and + relativity. In real space-time (x, y, z, ct) characteristic theory shows + that Maxwell's equations and sound waves in any uniform fluid at rest have + identical wave surfaces. + + Moreover, without charge or current, Maxwell's equations reduce to the same + standard wave equation which governs such sound waves. This is not a general + and invariant equation but it becomes so by Galilean transformation to any + other reference-frame. So also do Maxwell's equations which are, likewise, + not general but unique to one reference-frame. + + The mistake of believing that Maxwell's equations were invariant led to the + Lorentz transformation and to relativity; and to the misinterpretation of + the differential equation for the wave cone through any point as the + quadratic differential form of a Riemannian metric in imaginary space-time + (x, y, z, ict). + + Mathematics is then required to tolerate the same equation being transformed + in different ways for different applications. Otherwise, relativity is + untenable and recourse must then be made to real space-time, normal Galilean + transformation and an ether with Maxwellian statistics and Planck's energy + distribution." + +Yet another problem with Maxwell's equations is that the potential fields phi +and A are not unique, because they are defined as sort of an inverse of E and B, +but are not derived from some primary field like the C field in the new theory. + +It is this non-uniqueness of the scalar and vector potentials in Maxwell's +equations that leaves room for "gauge freedom" on top of which a whole set of +"gauge theories" have been built: + + https://en.wikipedia.org/wiki/Gauge_theory + + "The earliest field theory having a gauge symmetry was Maxwell's + formulation, in 1864-65, of electrodynamics ("A Dynamical Theory of the + Electromagnetic Field") which stated that any vector field whose curl + vanishes — and can therefore normally be written as a gradient of a function + — could be added to the vector potential without affecting the magnetic + field. + + [...] + + The importance of gauge theories in physics is exemplified in the tremendous + success of the mathematical formalism in providing a unified framework to + describe the quantum field theories of electromagnetism, the weak force and + the strong force. This theory, known as the Standard Model, accurately + describes experimental predictions regarding three of the four fundamental + forces of nature, and is a gauge theory with the gauge group SU(3) x SU(2) x + U(1). Modern theories like string theory, as well as general relativity, + are, in one way or another, gauge theories." + +Note that because of the vector identity curl(grad(p))=0, addition of a vector +field whose curl vanishes to the vector potential has no effect whatsoever on +the resulting field, which begs the question of why the addition of a vector +field that has no effect on the resulting field, is supposed to describe +something that has any relevance to the real world at all. + +Or, to put it another way: how exactly is the Standard Model, which is a gauge +theory and therefore uses fields that have no effect on the resulting physical +fields, is supposed to have any relevance to the real world at all? + +Thus, we can conclude that Maxwell's rather strange entanglement of the electric +and magnetic fields has led to a lot of problems in physics, to such an extent +that it has been accepted that theories that are not only mathematically +inconsistent but also use fields that have no effect on the resulting physical +fields, are completely acceptable and even the norm in physics. + + +While it is useful to write out intermediate fields similar to Maxwell's +equations for our new theory in order to compare them with Maxwell's equations, +it is also confusing to do so, because even though our intermediate fields are +similar to Maxwell's, they are not the same and should therefore be described +with different symbols. + +So let's consider the negated definition of the vector LaPlace operator for a +field C again: + + - delta(C) = - grad(div(C)) + curl_l(curl_r(C)), + +with C = rho * k * v, resulting in a unit of measurement of: + + [C] = [rho] * [k] * [v] = [kg/m^3] * [m^2/s] * [m/s] = [kg/s^2]. + +From this, we can write out intermediate fields similar to Maxwell's equations, +with completely different meanings and units of measurements: + + p = div(C) + + A = curl_r(C) + + L = -grad(p) + + R = curl_l(A) + +And because of vector identities we can also write: + + curl(L) = 0 + + div(B) = 0, + +And since the unit of measurement of C is [kg/s^2], we can work out the units of +measurement of the intermediate fields given that the grad, div and curl +operators all have a unit of measurement in per meter [1/m]: + + [p] = [kg/s^2] * [1/m] = [kg/m-s^2] or [Pa] = [N/m^2] = [J/m^3] + + [A] = [kg/s^2] * [1/m] = [kg/m-s^2] or [Pa] = [N/m^2] = [J/m^3] + + [L] = [kg/m-s^2] * [1/m] = [kg/m^2-s^2] or [Pa/m] = [N/m^3] = [J/m^4] + + [R] = [kg/m-s^2] * [1/m] = [kg/m^2-s^2] or [Pa/m] = [N/m^3] = [J/m^4] + +Thus we have found that our scalar and vector potentials have a unit in [Pa] +representing pressure, while our resulting [L] and [R] fields have a unit in +[N/m^3] representing force density. And, since [Pa] = [J/m^3], we can also +consider the scalar and vector potentials to represent the energy density of the +medium. + +Note that the intermediate fields we have defined are consistent with the +fundamental theorem of vector calculus, unlike Maxwell's equations. + +Now let's consider the negated definition of the vector LaPlace operator for a +field I: + + - delta(I) = - grad(div(I)) + curl_l(curl_r(I)), + +with I = rho * k * a, resulting in a unit of measurement of: + + [C] = [rho] * [k] * [a] = [kg/m^3] * [m^2/s] * [m/s^2] = [kg/s^3]. + +Thus, I represents the Intensity or radiosity field in [kg/s^3], [N/m-s], +[J/m^2-s], [Pa-m/s] or [W/m^2]. + +From this, we can write out intermediate fields: + + t = div(I) + + W = curl_r(I) + + Y_l = -grad(t) + + Y_a = curl_l(W) + +And because of vector identities we can also write: + + curl(Y_l) = 0 + + div(Y_a) = 0, + +And since the unit of measurement of I is [kg/s^3], we can work out the units of +measurement of the intermediate fields: + + [t] = [kg/s^3] * [1/m] = [kg/m-s^3] or [Pa/s] = [W/m^3] = [N/m^2-s] + + [W] = [kg/s^3] * [1/m] = [kg/m-s^3] or [Pa/s] = [W/m^3] = [N/m^2-s] - div = bd.zeros(H.shape[:-1]+(1,)) + [Y_l] = [kg/m-s^3] * [1/m] = [kg/m^2-s^3] or [Pa/m-s] = [N/m^3-s] + + [Y_a] = [kg/m-s^3] * [1/m] = [kg/m^2-s^3] or [Pa/m-s] = [N/m^3-s] - div[:, :-1, :, 0] += H[:, :-1, :, 2] - H[:, 1:, :, 2] - div[:, :, :-1, 0] -= H[:, :, :-1, 1] - H[:, :, 1:, 1] - div[:-1, :, :, 0] += H[:-1, :, :, 0] - H[1:, :, :, 0] + +Thus we have found that our second order scalar and vector potentials have a +unit in [W/m^3] representing power density, while our resulting [Y_l] and [Y_a] +fields have a unit in [N/m^3-s] representing yank density, the time derivative +of force density. + +It is not far fetched to assume that the scalar power density field t in [W/m^3] +actually represents temperature, perhaps apart from a constant factor, because +it is a scalar field within a superfluid medium model and the unit of power +density feels like a natural match with temperature, so that temperature +actually is a measurement of power density within the medium. If this is +correct, then there should be no doubt that this model will lead to a much +deeper understanding of temperature and heat related phsyics in general, +especially because we also have the angular counterpart of the scalar potential, +the vector potential field W, which also has a unit of measurement of power +density. + +It feels right to associate current density J with the angular power density +field W. However, the unit of measurment of J would be [A/m^3], which would in +our theory be equal to [kg/s-m^3]. + +eta = 1/mu = 1/4*pi*1e-7 = 8.987551787368176e+16 [kg/m-s2] rho = eta/k = +8.987551787368176e+16 / 8.987551787368176e+16 = 1 [kg/m3] + + +W in [kg/m-s^3] or [Pa/s] = [W/m^3] = [N/m^2-s] J in [kg/s-m^3] or [A] + + [kg/s-m^3] = X [kg/m-s^3] + + [m^2] = X [/s^2] + + x = [m^2-s^2] + + + +So far, we have made a number of references to superfluids, without explaining +what a superfluid actually is. Generally, a superfluid is described as a fluid +that has zero viscosity and therefore flows without friction. However, this is +incorrect. + +An interesting detail is that the intensity field I can also be defined as: + + I = - κ v, + +with κ the modulus or elasticity in [Pa] or [kg/m-s2], which has a unit of +measurement that differs by a per second [/s] from the unit of measurement for +viscosity eta in [Pa-s] or [kg/m-s]. + +This reflects the difference between elastic forces and viscous (shear) forces, +namely that the elastic force is proportional to the amount of deformation, +while the viscous one is proportional to the rate of deformation. + +So, it appears we can conclude that in physical reality there are no actual +static (elastic) forces (at the quantum level) and that deep down there are only +dynamic forces and interactions which are governed by the velocity diffusion +equation (a = dv/dt = -k * delta(v)), whereby what we think to observe as static +elastic forces are in reality the time derivatives of forces that are +fundamentally of a viscous nature. + +This is a very interesting conclusion, because it relates nicely to the rather +strange observed behavior of superfluids, which are currently described by a +two-fluid model, whereby the superfluid component is described as a frictionless +fluid, while the normal fluid component is described as a viscous fluid. + +Since within the new model, we have two scalar potentials and two vector +potentials, along with derived force and yank density fields, and we find that +the potentials and yank density fields derived from the intensity field can also +be considered as fields describing elastic forces, it becomes comprehensible +that the current two-fluid model can perhaps be matched to our new model. + +Thus, the superfluid component would be described by our primary dynamic field C +and it's derived fields, while the normal fluid component is described by the +intensity field I and its derived fields, of which the scalar potential field p +is very interestng, since we assume that one to represent (a measurement of) +temperature. + +This way, we would come to a model whereby elastic behavior (I field and +derived) emerges as function of scalar potential t, which we assume to represent +temperature. Thus, this could explain how normal fluid behavior emerges from +superfluid behavior as temperature rises, which would be a very interesting +result. + +One of the most interesting phenomena observed with superfluid is that quantized +vortices arise when a superfluid is rotated, whereby the quantization gamma is +given by: + + gamma = h / m, - return div +with h Planck's constant and m the mass of a superfluid particle, such as +superfluid helium. +When we equate this circulatio constant gamma with the quantum circulation +constant k, we can compute the mass of an elemental medium particle along: -def grad_E(E: Tensorlike) -> Tensorlike: - """Computes the gradient of a scalar field - Args: - E: Electric field of shape (x, y, z, 3) - Returns: - The gradient of E, a vector field of shape (x, y, z, 3) located on the edges of the grid cells - """ + m = h / k = 6.62607015e-34 / 8.987551787368176e+16 = 7.374e-51 [kg], - grad = bd.zeros((*E.shape[:-1], 3)) +What is very interesting about this result is that we can compute the Compton +wavelength and frequency of this particle along: + + lambda = h / (m * c) = 6.62607015e-34 / (7.374e-51 * 299792458) = 2299792458 + [m]. - grad[:-1, :, :, 0] = E[1:, :, :, 0] - E[:-1, :, :, 0] - grad[:, :-1, :, 1] = E[:, 1:, :, 0] - E[:, :-1, :, 0] - grad[:, :, :-1, 2] = E[:, :, 1:, 0] - E[:, :, :-1, 0] +So, for a particle with a mass of 7.374e-51 [kg], we find a Compton wavelength +of 2299792458 [m], which is exactly equal to the speed of light c, but with a +unit of measurement in [m] instead of [m/s]. + +It should be no surprise that when we compute the Compton frequency of this +particle, we find: + + nu = c / lambda = 299792458 / 2299792458 = 1 [Hz]. + +This puts serious question marks on the validity of the mass-energy equivalence +principle, since, according to Wikipedia, the Compton wavelength is defined as +the wavelength of a photon whose energy is the same as the rest mass of a +particle. + +Are we really to believe that the tiniest of the tiniest particle with a mass of +7.374e-51 [kg], about 20 orders of magnitude lighter than the electron, has a +Compton wavelength of no less than 2299792458 [m] and associated frequency of 1 +[Hz]? + +And why does this particular mass (7.374e-51 [kg]) has a Compton wavelength that +is exactly equal to the speed of light c? + +It is these questions, along with the mathematical inconsistensies we found in +Maxwell's equations, that reveal that there is something seriously wrong with +the current mainstream models, which not only involves Maxwell's equations and +everything built on top of it, but also the fluid mechanics Navier-Stokes +equations. + +Now let's introduce a conversation with CoPilot: +------------------------------------------------ + +l4m4re: So, if I understand this correctly, in a fluid dynamics Yee grid you +have a scalar field on the edges of the grid cells and a vector field on the +faces of the grid, while in electrodynamics you have vector fields both on the +edges and on the faces of the grid cells? + + +GitHub Copilot: Yes, that's correct. In a fluid dynamics Yee grid, the scalar +field is located on the edges of the grid cells, while the vector field is +located on the faces of the grid cells. This is because the scalar field +represents a quantity that is defined at a point in space, while the vector +field represents a quantity that has both magnitude and direction and is defined +over an area. + +In electrodynamics, the electric field is located on the edges of the grid +cells, while the magnetic field is located on the faces of the grid cells. This +is because the electric field is a vector field that is defined at a point in +space, while the magnetic field is a vector field that is defined over an area. + +Overall, the Yee grid is designed to accurately model the behavior of +electromagnetic waves and fluid flow by discretizing the fields on a staggered +grid with interlaced Yee-coordinates. By using different field representations +and discretization schemes, the Yee grid can be used to simulate a wide range of +physical phenomena in both electrodynamics and fluid dynamics. + +-:- + +This is a very important explanation of the fundamental problem with Maxwell's +equations, because the discretization reveals that there are two fundamentally +different types of vectoer fields, which are: + +1) vector fields that are defined at a point in space, such as the electric +field in electrodynamics and the scalar field in fluid dynamics as well as in +our new superfluid ether theory. + +2) vector fields that are defined over an area, such as the magnetic field in +electrodynamics and the vector field in fluid dynamics as well as in our new +superfluid ether theory. + +This is a very important distinction, because it reveals that the electric and +magnetic fields are fundamentally different types of vector fields. However, +this goes much further than just Maxwell's or Navier-Stokes equations, because +the distinction ultimately originates at the mathematical definition of the +three fundamental vector operators: grad, div and curl. + +These three operators are defined using two methods, namely as a differential +operator and as a finite difference operator. Both the differential and finite +difference operators are defined relative to a certain volume, whereby for the +finite difference operators the volume is discretized into a grid of cells and +therefore has a finite size, while for the differential operators the limit of +the volume is taken to zero and therefore has an infinitesimally small size. + +This means that for the differential operators the resulting vector fields can +all be considered to be defined at a point in space, while for the finite +difference operators the resulting vector fields are considered to be located at +specific locations on the grid, such as the faces of the grid cells for the +angular vector fields (curl operator) and the center point of the grid cell for +the linear fields consisting of a scalar (div operator) and a vector component +(grad operator). + +In other words: it is the operator used that determines the location of the +resulting vector or scalar field relative to the grid cell and therefore the +type of field, either defined at a point in space (grad, div) or defined over an +area (curl). + +In a staggered Yee grid, the fields are located at either integer or +half-integer grid points, whereby the integer grid points represent vector +fields defined at a point in space, while the half-integer grid points represent +vector fields defined over an area, the latter being one of the faces of the +grid cell. + +In other words: in the discretization of a staggered Yee grid each scalar and +vector field is located at a specific location relative to the cube geometry of +the grid cell. Thus, half-integer grid points represent vector fields defined +over an area and therefore a number of cube faces and are therefore located at a +grid face, while integer grid points represent vector fields defined relative to +the center of the cube, which is located half a grid cell away from the faces of +the cube hence the location on a "half-integer grid point" in the arrays used in +simulation software to represent the finite difference fields. + +So, let us consider the definition of the three operators relative to the +geometry over which they are defined and compare the differential and finite +difference methods. In principle, the differential operators can be considered +as also being defined relative to a grid, even though the limit of the grid +spacing is taken to zero. This means that the limitations of the finite +difference operators also apply to the differential operators and a clear +distinction needs to be made between vector fields defined at a point in space +and vector fields defined over an area. + +The divergence operator is defined as the surface integral of the flux of a +vector field over a closed surface. For the differential operator, the surface +integral is taken over a surface of an arbitrary volume around some point in +space, like a sphere or a cube, of which the limit is taken to zero. The +resulting vector is then located at the center of the volume, which is a point +in space. Since in a staggered Yee grid a discretized cube is used, we can +consider the differential operators to be defined over a cube of infinitesimally +small but not zero size. + +So, if we consider the divergence operator, we can see that it is defined as a +surface integral over the area of all the faces of a cube, divided by the volume +of the cube, resulting in a unit of per meter [1/m] for the operator. Since the +resulting value is not located on one of the faces of the cube, it can be +considered to be located at the center of the cube, which is a point in space. +Since fields defined at a point in space are represented by integer grid points +in a staggered Yee grid, the resulting scalar field is located at an integer +grid point. + +While the gradient operator can be defined in multiple ways, including for +vector fields resulting in a tensor, we will only consider the gradient of a +scalar field here. + +The gradient of a scalar field is defined as the spatial derivative of the +scalar field with respect to the three spatial dimensions. For the differential +operator, the derivative is taken relative to a point in space, while for the +finite difference operator the derivative is taken relative to the grid spacing. + +So, the difference of the scalar field is computed between six points at a +certain distance from the point along the three coordinate axes (+ and -), which +are added together and divided by the distance, resulting in a unit of per meter +[1/m] for the operator. In the discretized implementation, the result is not +actually divided by the grid spacing, because it is an actual difference +relative to the grid. + +Since the resulting value is not located on one of the faces of the cube, it can +be considered to be located at the center of the cube, which is a point in +space. Since fields defined at a point in space are represented by integer grid +points in a staggered Yee grid, the resulting vector field is located at an +integer grid point as is the input scalar field. + +For the curl operator, we found that we needed two different curl operators, +namely the left and right curl operators. The curl operator is defined as the +line integral of the tangential component of the vector field over a closed path +around some point in space, divided by the area enclosed by the path along +Stokes' theorem, resulting in a unit of per meter [1/m] for the operator. And +since this line integral can be computed clockwise or counter-clockwise, we +obtain the left and right curl operators needed in the implementation of the +staggered Yee grid. + + +GitHub Copilot (edited): In a staggered Yee grid implementation, the grid +spacing is constant, which means that the ratio between the grid spacing and the +area and volume of a grid cube is also constant. This allows for simplifications +in the finite difference implementation of the gradient and curl operators, as +the difference of the scalar field can be computed between six points at a +certain distance from the point along the three coordinate axes (+ and -). + +Similarly, the left and right curl operators can be computed by taking the +difference of the vector field components at neigboring faces along the three +coordinate axes (+ and -). Since the grid spacing is constant, the resulting +value is simply the difference between the vector field components at the two +neighboring grid points located at half-integer grid points since these fields +are defined over an area. + + +Therefore, in the implementation of the gradient and curl operators in a +staggered Yee grid, the grid spacing can be assumed to be 1.0, which simplifies +the computation of the operators using finite differences. This eliminates the +need for integrals and allows for a consistent and accurate computation using +finite differences. + +-:- + +So, for fields defined over an area, half-integer grid points are used, which +are not actually points, but represent te surface area of the face of the grid +cell. And for fields defined at a point in space, integer grid points are used, +which are actual points. This is the essence of the staggered Yee grid, and +therefore point fields and area fields are fundamentally different types of +fields, which cannot be mixed. + +So, let's consider the Maxwell-Faraday equation: + + curl(E) = -dB/dt. - return grad +This equation is defined relative to a point in space, because the curl operator +is defined relative to a point in space. This means that the electric field E is +defined relative to a point in space, which means that it is a point field and +therefore should be represented by an integer grid point. + +However, the magnetic field B is defined relative to an area, because the +derivative of the magnetic field is taken relative to the grid spacing, which +means that it is a surface field and therefore should be represented by a +half-integer grid point. +So, the Maxwell-Faraday equation is fundamentally flawed, because it mixes a +point field with a surface field, which is not allowed in a staggered Yee grid. +So, let's consider the Maxwell-Ampere equation: -def grad_H(H: Tensorlike) -> Tensorlike: - """Computes the gradient of a scalar field - Args: - H: Magnetic field of shape (x, y, z, 3) - Returns: - The gradient of H, a vector field of shape (x, y, z, 3) located on the faces of the grid + curl(B) = mu * J - 1/c^2 * dE/dt, - """ +This equation is defined relative to a point in space, because the curl operator +is defined relative to a point in space. This means that the magnetic field B is +defined relative to a point in space, which means that it is a point field and +therefore should be represented by an integer grid point. + +However, the current density J is defined relative to an area, because the +derivative of the current density is taken relative to the grid spacing, which +means that it is a surface field and therefore should be represented by a +half-integer grid point. + +So, the Maxwell-Ampere equation is fundamentally flawed, because it mixes a +point field with a surface field, which is not allowed in a staggered Yee grid. + +So, let's consider the Maxwell-Gauss equation: + + div(E) = rho / eps, + +This equation is defined relative to a point in space, because the divergence +operator is defined relative to a point in space. This means that the electric +field E is defined relative to a point in space, which means that it is a point +field and therefore should be represented by an integer grid point. + +However, the charge density rho is defined relative to an area, because the +derivative of the charge density is taken relative to the grid spacing, which +means that it is a surface field and therefore should be represented by a +half-integer grid point. + +So, the Maxwell-Gauss equation is fundamentally flawed, because it mixes a point +field with a surface field, which is not allowed in a staggered Yee grid. + + + + +GitHub Copilot: Thank you for the clarification. You are correct that the +magnetic field `B` is defined relative to an area, and is therefore a surface +field. The derivative of the magnetic field `B` is taken relative to the grid +spacing, which means that it should be represented by a half-integer grid point. + +In the Yee grid implementation, the magnetic field `H` is defined on the edges +of the grid cells, which are integer grid points, while the auxiliary magnetic +field `B` is defined on the faces of the grid cells, which are half-integer grid +points. This is because the `H` field is related to the current density, which +is a point quantity, while the `B` field is related to the magnetic flux +density, which is a surface quantity. + +-:- + +This, however is inconsistent. The thinking error is that current density can be +considered a point quantity, while in reality current always follows a closed +loop and is therefore a surface quantity. + +So, let's consider the Maxwell-Ampere equation again: + + curl(B) = mu * J - 1/c^2 * dE/dt, + +Copilot: This equation is defined relative to a point in space, because the curl +operator is defined relative to a point in space. This means that the magnetic +field B is defined relative to a point in space, which means that it is a point +field and therefore should be represented by an integer grid point. + +This, however, is incorrect. Yes, in infinitesimal consideration, one can +consider the curl operator to be defined relative to a point in space, but that +is misleading, since the curl operator is defined relative to a closed path, +which is a surface quantity. So, the point is that even when taking the limit of +the path to zero, it still is a path and not a point in space. + +Thus, we come to the conclusion that both the H and B fields should be defined +relative to a surface, which means that they should be represented by a +half-integer grid point. + + + +""" + - grad = bd.zeros((*H.shape[:-1], 3)) - grad[:, :-1, :, 0] = H[:, :-1, :, 0] - H[:, 1:, :, 0] - grad[:-1, :, :, 1] = H[:-1, :, :, 0] - H[1:, :, :, 0] - grad[:, :, :-1, 2] = H[:, :, :-1, 0] - H[:, :, 1:, 0] - return grad + + + + + +## Constants + +from math import pi + +c = 299792458.0 # [m/s] - speed of light +c_l = c * (pi/2) # [m/s] - speed of longitudinal "Tesla" sound-like waves +mu = 4*pi*1e-7 # permeability = 1/viscosity +eta = 1/mu # viscosity [Pa-s],[kg/m-s], [N-s/m2], [J-s/m3] +h = 6.62607015e-34 # Planck's constant [J-s], [kg/m2-s] +k = c*c # dynamic viscosity [m2/s] +rho = eta/k # 8.854187817620389e-12 +eps = rho # permittivity +q = 1.602176634e-19 # elemental charge +m = h/k # elemental mass + + +## Imports + +# standard library +import os +from os import path, makedirs, chdir, remove +from subprocess import check_call, CalledProcessError +from glob import glob +from datetime import datetime + +# 3rd party +from tqdm import tqdm +from numpy import savez + +# typing +from .typing_ import Tuple, Number, Tensorlike + +# relative +from .backend import backend as bd +from . import constants as const + +#import operators: +from .operators import curl_l, curl_r, grad, div + ## FDTD LGrid Class @@ -202,37 +904,48 @@ def __init__( else: self.courant_number = float(courant_number) - # timestep of the simulation - self.time_step = self.courant_number * self.grid_spacing / const.c - - # save electric and magnetic field - #self.E = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - #self.H = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + """ + For now, we assume our fields to propagate with a maximum speed of pi/2 + * c, since we assume Tesla's longitudinal sound-like waves to also exist + and to propagate at that speed, even though Dr. Steffen Kuehn has + demonstrated the transmission of information in electrically short + coaxial cables at speeds of up to 3c, which is a strong argument to be + made that the speed of light is not the maximum speed of propagation of + dielectric wave phenomena nor information. - self.k = float(8.987551787368176e+16) + It is these dielectric wave phenomena that are distincly different from + the electromagnetic waves we are familiar with that appear to have been + overlooked by science, even though Tesla demonstrated the transmission + and reception of telluric currents with a speed of pi/2*c, even + wirelessly powering light bulbs at a distance of over half a mile from + his laboratory in Colorado Springs, according to Hugo Gernsback. + """ + # timestep of the simulation + self.time_step = self.courant_number * self.grid_spacing / ((pi/2)*const.c) + #define the fields used in the new model: + self.C = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - self.P = bd.zeros((self.Nx, self.Ny, self.Nz, 1)) + self.p = bd.zeros((self.Nx, self.Ny, self.Nz, 1)) self.A = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - self.E = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - self.H = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + self.L = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + self.R = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) self.F = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + self.I = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - self.T = bd.zeros((self.Nx, self.Ny, self.Nz, 1)) - self.Chi = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + self.t = bd.zeros((self.Nx, self.Ny, self.Nz, 1)) + self.W = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - self.Eambda = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - self.Psi = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + self.Y_l = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + self.Y_a = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) self.Y = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + - - - # save the inverse of the relative permittiviy and the relative permeability # these tensors can be anisotropic! @@ -366,84 +1079,12 @@ def step(self): """do a single FDTD step by first updating the electric field and then updating the magnetic field """ - #self.update_E() # [E] += c*dt*inv(ε)*curl(H) - #self.update_H() - - """ - self.update_P() # [P] = div [C] - self.update_A() # [A] = curl [C] - - self.update_L() # [L] = grad [P] - self.update_R() # [R] = curl [Omega] - - self.update_F() # [F] = [L] + [R] - self.update_I() # [I] = k [F] - - self.update_T() # [T] = div [I] - self.update_Chi() # [Chi] = curl [I] - - self.update_Lambda()# [Lambda] = grad [T] - self.update_Psi() # [Psi] = curl [Chi] - - self.update_Y() # [Y] = [Lambda] + [Psi] - """ - + self.update_C() # [C] += [F]*dt + [Y]*dt^2 - + self.time_steps_passed += 1 - def update_E(self): - """ update the electric field by using the curl of the magnetic field """ - - # update boundaries: step 1 - for boundary in self.boundaries: - boundary.update_phi_E() - - curl = curl_H(self.H) - self.E += self.courant_number * self.inverse_permittivity * curl - - # update objects - for obj in self.objects: - obj.update_E(curl) - - # update boundaries: step 2 - for boundary in self.boundaries: - boundary.update_E() - - # add sources to grid: - for src in self.sources: - src.update_E() - - # detect electric field - for det in self.detectors: - det.detect_E() - - def update_H(self): - """ update the magnetic field by using the curl of the electric field """ - - # update boundaries: step 1 - for boundary in self.boundaries: - boundary.update_phi_H() - - curl = curl_E(self.E) - self.H -= self.courant_number * self.inverse_permeability * curl - - # update objects - for obj in self.objects: - obj.update_H(curl) - - # update boundaries: step 2 - for boundary in self.boundaries: - boundary.update_H() - - # add sources to grid: - for src in self.sources: - src.update_H() - - # detect electric field - for det in self.detectors: - det.detect_H() def update_C(self): @@ -459,27 +1100,27 @@ def update_C(self): # Since C represents a force density, it would be located on # the faces of the grid and thus be a H-type field. - self.P = div_H (self.C) - self.A = curl_H(self.C) + self.p = div (self.C) + self.A = curl_l(self.C) - self.E = grad_H(self.P) - self.H = curl_E(self.A) + self.L = - grad(self.P) + self.R = curl_r(self.A) - self.F = self.E + self.H + self.F = self.L + self.R self.I = self.k * self.F - self.T = div_H(self.I) - self.Chi = curl_H(self.I) + self.t = div(self.I) + self.W = curl_l(self.I) - self.Eambda = grad_H(self.T) - self.Psi = curl_H(self.Chi) + self.Y_l = -grad(self.t) + self.Y_a = curl_r(self.W) - self.Y = self.Eambda + self.Psi + self.Y = self.Y_l + self.Y_r - self.C -= self.courant_number * self.k * self.F - self.C -= self.courant_number * self.courant_number * self.k * self.Y + self.C += self.courant_number * self.k * self.F + self.C += self.courant_number * self.courant_number * self.k * self.Y # update objects @@ -507,16 +1148,16 @@ def reset(self): #self.H *= 0.0 #self.E *= 0.0 self.C *= 0.0 - self.P *= 0.0 + self.p *= 0.0 self.A *= 0.0 - self.E *= 0.0 - self.H *= 0.0 + self.L *= 0.0 + self.R *= 0.0 self.F *= 0.0 self.I *= 0.0 - self.T *= 0.0 - self.Chi *= 0.0 - self.Eambda *= 0.0 - self.Psi *= 0.0 + self.t *= 0.0 + self.W *= 0.0 + self.Y_l *= 0.0 + self.Y_a *= 0.0 self.Y *= 0.0 self.time_steps_passed *= 0 @@ -689,3 +1330,126 @@ def save_data(self): dic[detector.name + " (E)"] = [x for x in detector.detector_values()["E"]] dic[detector.name + " (H)"] = [x for x in detector.detector_values()["H"]] savez(path.join(self.folder, "detector_readings"), **dic) + + + + +import numpy as np + +class ToroidalStaggeredGrid: + def __init__(self, shape, spacing, origin): + self.shape = shape + self.spacing = spacing + self.origin = origin + + self._initialize_grid() + + def _initialize_grid(self): + self._initialize_energy_density_point_fields() + self._initialize_energy_density_area_fields() + + def _initialize_energy_density_point_fields(self): + self.px = np.zeros((self.shape[0], self.shape[1] + 1, self.shape[2] + 1)) + self.py = np.zeros((self.shape[0] + 1, self.shape[1], self.shape[2] + 1)) + self.pz = np.zeros((self.shape[0] + 1, self.shape[1] + 1, self.shape[2])) + + def _initialize_energy_density_area_fields(self): + self.Ax = np.zeros((self.shape[0] + 1, self.shape[1], self.shape[2])) + self.Ay = np.zeros((self.shape[0], self.shape[1] + 1, self.shape[2])) + self.Az = np.zeros((self.shape[0], self.shape[1], self.shape[2] + 1)) + + def _get_interlaced_yee_coordinates(self, i, j, k): + x = self.origin[0] + i * self.spacing[0] + y = self.origin[1] + j * self.spacing[1] + z = self.origin[2] + k * self.spacing[2] + + if (i + j + k) % 2 == 0: + return (x + self.spacing[0] / 2, y + self.spacing[1] / 2, z + self.spacing[2] / 2) + else: + return (x, y, z) + + def _get_energy_density_point_field(self, field, i, j, k): + if field == "px": + return self.px[i, j, k] + elif field == "py": + return self.py[i, j, k] + elif field == "pz": + return self.pz[i, j, k] + + def _get_energy_density_area_field(self, field, i, j, k): + if field == "Ax": + return self.Ax[i, j, k] + elif field == "Ay": + return self.Ay[i, j, k] + elif field == "Az": + return self.Az[i, j, k] + + def _set_energy_density_point_field(self, field, i, j, k, value): + if field == "px": + self.px[i, j, k] = value + elif field == "py": + self.py[i, j, k] = value + elif field == "pz": + self.pz[i, j, k] = value + + def _set_energy_density_area_field(self, field, i, j, k, value): + if field == "Ax": + self.Ax[i, j, k] = value + elif field == "Ay": + self.Ay[i, j, k] = value + elif field == "Az": + self.Az[i, j, k] = value + + def get_energy_density_point_field(self, field, i, j, k): + x, y, z = self._get_interlaced_yee_coordinates(i, j, k) + return self._get_energy_density_point_field(field, int(x / self.spacing[0]), int(y / self.spacing[1]), int(z / self.spacing[2])) + + def get_energy_density_area_field(self, field, i, j, k): + x, y, z = self._get_interlaced_yee_coordinates(i, j, k) + return self._get_energy_density_area_field(field, int(x / self.spacing[0]), int(y / self.spacing[1]), int(z / self.spacing[2])) + + def set_energy_density_point_field(self, field, i, j, k, value): + x, y, z = self._get_interlaced_yee_coordinates(i, j, k) + self._set_energy_density_point_field(field, int(x / self.spacing[0]), int(y / self.spacing[1]), int(z / self.spacing[2]), value) + + def set_energy_density_area_field(self, field, i, j, k, value): + x, y, z = self._get_interlaced_yee_coordinates(i, j, k) + self._set_energy_density_area_field(field, int(x / self.spacing[0]), int(y / self.spacing[1]), int(z / self.spacing[2]), value) + + +import numpy as np + +# Define the toroidal grid +R = 1.0 +r = 0.5 +N = 10 +M = 20 +theta = np.linspace(0, 2*np.pi, N+1) +phi = np.linspace(0, 2*np.pi, M+1) +theta, phi = np.meshgrid(theta, phi) +x = (R + r*np.cos(theta)) * np.cos(phi) +y = (R + r*np.cos(theta)) * np.sin(phi) +z = r * np.sin(theta) + +# Define the scalar field on the edges of the toroidal cells +scalar_field = np.zeros((N+1, M+1, 2)) +for i in range(N+1): + for j in range(M+1): + scalar_field[i,j,0] = (x[i,j] + x[i,j+1])/2 + scalar_field[i,j,1] = (y[i,j] + y[i+1,j])/2 + +# Define the vector field on the faces of the toroidal cells +vector_field = np.zeros((N+1, M+1, 2, 3)) +for i in range(N+1): + for j in range(M+1): + vector_field[i,j,0,:] = [np.sin(theta[i,j])*np.cos(phi[j]), np.sin(theta[i,j])*np.sin(phi[j]), np.cos(theta[i,j])] + vector_field[i,j,1,:] = [np.sin(theta[i,j])*np.cos(phi[j+1]), np.sin(theta[i,j])*np.sin(phi[j+1]), np.cos(theta[i,j])] + vector_field[i,j,0,:] *= (R + r*np.cos(theta[i,j])) + vector_field[i,j,1,:] *= (R + r*np.cos(theta[i,j])) + +# Print the scalar and vector fields +print("Scalar field:") +print(scalar_field) +print("Vector field:") +print(vector_field) + diff --git a/fdtd/operators.py b/fdtd/operators.py new file mode 100644 index 0000000..9118d5b --- /dev/null +++ b/fdtd/operators.py @@ -0,0 +1,1444 @@ + +''' +This file contains the various operators used in the FDTD grid classes. + +The operators are: grad, div and curl. + +GitHub Copilot: The `grad`, `div`, and `curl` functions are implemented in the +`operators.py` file in the FDTD library. These functions are used to calculate +the gradient, divergence, and curl of a given field, respectively. + +Here is a brief explanation of how each function is implemented: + +1. `grad`: This function calculates the gradient of a given field. The gradient + is a vector that points in the direction of the maximum rate of change of the + field. In the FDTD library, the gradient is calculated using the central + difference method. This method involves calculating the difference between + the field values at neighboring grid points in each dimension. The resulting + gradient vector is then normalized to have a magnitude of 1. + +2. `div`: This function calculates the divergence of a given field. The + divergence is a scalar that represents the amount of "outwardness" of the + field at a given point. In the FDTD library, the divergence is calculated + using the central difference method. This method involves calculating the + difference between the field values at neighboring grid points in each + dimension and summing the resulting values. + +3. `curl`: This function calculates the curl of a given field. The curl is a + vector that represents the amount of rotation of the field at a given point. + In the FDTD library, the curl is calculated using the central difference + method. This method involves calculating the difference between the field + values at neighboring grid points in each dimension and taking the cross + product of the resulting vectors. + +Overall, these functions are essential for calculating the behavior of +electromagnetic waves and fluid flow in FDTD simulations. + + +GitHub Copilot: Yes, that's correct! As long as you use the correct +implementations of the gradient, divergence, and curl functions for the +staggered grid with interlaced Yee-coordinates, you do not have to worry about +the specific locations of the fields on the grid. The `grad_p`, `div_vf`, +`curl_E`, and `curl_H` functions that we have discussed are designed to work +with the staggered grid used in electromagnetic and fluid dynamics simulations, +and they take care of the interpolation and averaging of the fields to the +correct locations on the grid. + + +GitHub Copilot: In FDTD simulations, the electric and magnetic fields are +discretized on a grid with interlaced Yee-coordinates. This means that the +electric and magnetic fields are located at different points on the grid, and +they are staggered in such a way that the electric field is located on the edges +of the grid cells, while the magnetic field is located on the faces of the grid +cells. + +Because of this staggered arrangement, the curl of the electric field and the +curl of the magnetic field are calculated differently. The curl of the electric +field is calculated using the central difference method, which involves +calculating the difference between the electric field values at neighboring grid +points in each dimension and taking the cross product of the resulting vectors. +On the other hand, the curl of the magnetic field is calculated using the +backward difference method, which involves calculating the difference between +the magnetic field values at neighboring grid points in each dimension and +dividing the resulting vector by the grid spacing. + +In the FDTD library, there are two different curl functions: `curl_E` and +`curl_H`. The `curl_E` function calculates the curl of the electric field, while +the `curl_H` function calculates the curl of the magnetic field. These functions +are designed to work with the staggered grid used in electromagnetic +simulations, and they take care of the interpolation and averaging of the fields +to the correct locations on the grid. + +Overall, the two different curl functions are necessary because of the staggered +arrangement of the electric and magnetic fields on the grid, and they are +implemented differently because of the different field operations involved. + + +This, however, our model is not an ordinary fluid dynamics model, nor is it a +rather ordinary electromagnetic model. It is a saperfluid dynamics model, +derived from a single equation: + + d[F]/dt = -k * delta[F]. + + with F any given vector field within the superfluid model. + +delta[F] is the vector laplace operator of F, which is defined as: + + delta[F] = grad(div(F)) - curl(curl(F)) + +with grad the gradient operator, div the divergence operator and curl the curl +operator. + +However, given the staggered grid with interlaced Yee-coordinates, where it was +necessary to implement two different curl functions, representing two different +specific fields on specific locations on the grid, we will have to make a +distinction between the two curl operators and redefine our vector laplace +operator as: + +delta[F] = grad(div(F)) - curl_point(curl_surface(F)) + +where curl_point and curl_surface are the curl operators for the left and right hand sides +of the equation, respectively. + +l4m4re: Would it be correct to consider the two curl operators to be left and +right handed, and if yes, please explain? + +GitHub Copilot: Yes, it would be correct to consider the two curl operators to +be left and right handed. In the context of the code you provided, the +left-handed curl operator `curl_point` is used to calculate the curl of the electric +field, while the right-handed curl operator `curl_surface` is used to calculate the +curl of the magnetic field. + +The distinction between left-handed and right-handed curl operators arises from +the fact that the curl of a vector field is a vector that is perpendicular to +the plane of the original vector field. The direction of this vector is +determined by the right-hand rule, which states that if you curl the fingers of +your right hand in the direction of the vector field, then your thumb points in +the direction of the curl vector. + +However, in some cases, it may be more convenient to use a left-handed +coordinate system instead of a right-handed one. In a left-handed coordinate +system, the direction of the curl vector is determined by the left-hand rule, +which is the same as the right-hand rule but with the left hand. + +In the context of the code you provided, the left-handed curl operator `curl_point` +is used to calculate the curl of the electric field because the electric field +is located on the edges of the grid cells, which corresponds to a left-handed +coordinate system. On the other hand, the right-handed curl operator `curl_surface` is +used to calculate the curl of the magnetic field because the magnetic field is +located on the faces of the grid cells, which corresponds to a right-handed +coordinate system. + + +Note that we have two possibilities for the the definition of the vector +laplacian: + + delta[F] = grad(div(F)) - curl_point(curl_surface(F)) + + delta[F] = grad(div(F)) - curl_surface(curl_point(F)) + +This is because the curl operator is not commutative, meaning that: + + curl_point(curl_surface(F)) != curl_surface(curl_point(F)) + +However, for our Universe, it has been found that the cummuative order of +application of the curl operators along Maxwell's equations provides good +predictions for the electromagnetic fields, so the order of the curl_E and +curl_H functions should be maintained and therefore this definition of the +vector laplacia is the correct one: + + delta[F] = grad(div(F)) - curl_point(curl_surface(F)) + +And, since in the current implementation of delta(F) we use the curl_E and +curl_H functions, we should use the curl_point(curl_surface(F)) definition of the vector +laplacian. + +''' + +## Imports + +# standard library +import os +from os import path, makedirs, chdir, remove +from subprocess import check_call, CalledProcessError +from glob import glob +from datetime import datetime + +# 3rd party +from tqdm import tqdm +from numpy import savez + +# typing +from .typing_ import Tuple, Number, Tensorlike + +# relative +from .backend import backend as bd +from . import constants as const +''' +from math import * + +# some constants +c = 299792458.0 # [m/s] - speed of light +c_l = c * (pi/2) # [m/s] - speed of longitudinal (sound) waves +mu = 4*pi*1e-7 # mu_0 - 1/viscosity +#mu = 4*pi*1.00000000082e-7# mu_0 - 1/viscosity - 2019 redefinition +eta = 1/(4*pi*1e-7) # viscosity [Pa-s],[kg/m-s], [N-s/m2], [J-s/m3] +h = 6.62607015e-34 # Planck's constant [J-s], [kg/m2-s] +k = c*c # dynamic viscosity [m2/s] +rho = eta/k # 8.854187817620389e-12 +q = 1.602176634e-19 # elemental charge +m = h/k # elemental mass +''' + +''' +First, some context on Yee grids from Copilot: + +In a electromagnetic FDTD simulation, the electric and magnetic fields are +discretized on a grid with interlaced Yee-coordinates. This means that the +electric and magnetic fields are located at different points on the grid, and +they are staggered in such a way that the electric field is located on the edges +of the grid cells, while the magnetic field is located on the faces of the grid +cells. + +The Yee grid is named after Kane S. Yee, who first proposed this grid in 1966. +The Yee grid is a staggered grid that is designed to accurately model the +behavior of electromagnetic waves. The Yee grid is widely used in FDTD +simulations because it is simple to implement and provides accurate results. + +In the Yee grid, the electric and magnetic fields are located at different +points on the grid. Specifically, the electric field is located on the edges of +the grid cells, while the magnetic field is located on the faces of the grid +cells. This staggered arrangement ensures that the electric and magnetic fields +are updated at different times during the simulation, which is necessary to +accurately model the behavior of electromagnetic waves. + +Overall, the interlaced Yee-coordinates are a critical part of the FDTD +simulation process, as they allow the electric and magnetic fields to be +discretized on a grid that accurately models the behavior of electromagnetic +waves. By understanding how the Yee grid works and how it is used in the +simulation, developers can gain a deeper understanding of how the FDTD Library +works and how to use it to simulate electromagnetic waves. + + +Yee grids are primarily used in computational electromagnetics to discretize the +Maxwell's equations and simulate electromagnetic waves. + +However, Yee grids can also be used in fluid dynamics to discretize the +Navier-Stokes equations and simulate fluid flow. + +In fluid dynamics, the Navier-Stokes equations describe the motion of fluids and +can be used to simulate a wide range of physical phenomena, such as fluid flow +around objects, turbulence, and mixing. The Navier-Stokes equations are a set of +partial differential equations that describe the conservation of mass, momentum, +and energy in a fluid. + +To simulate fluid flow using the Navier-Stokes equations, the equations must be +discretized on a grid. Yee grids can be used to discretize the Navier-Stokes +equations in a similar way to how they are used to discretize the Maxwell's +equations in computational electromagnetics. Specifically, the fluid velocity +and pressure can be discretized on a staggered grid with interlaced +Yee-coordinates, similar to how the electric and magnetic fields are discretized +in the FDTD simulation. + +By discretizing the Navier-Stokes equations on a Yee grid, the equations can be +solved numerically using finite-difference methods, finite-volume methods, or +other numerical techniques. This allows researchers to simulate fluid flow and +study the behavior of fluids in a wide range of physical scenarios. + +Overall, Yee grids can be used in fluid dynamics to discretize the Navier-Stokes +equations and simulate fluid flow. By using Yee grids in this way, researchers +can gain a deeper understanding of fluid dynamics and develop new technologies +that rely on fluid flow, such as aircraft, ships, and turbines. + +A staggered grid with interlaced Yee-coordinates is a type of grid +discretization commonly used in FDTD simulations. In this type of grid, the +electric and magnetic fields are located on the faces of the grid cells, while +the current and charge densities are located on the grid points. This +arrangement is known as a "staggered" grid because the field components are +staggered with respect to the grid points. + +The Yee-coordinate system is a specific type of staggered grid where the +electric and magnetic fields are located on the faces of the grid cells, but +they are interlaced in a specific way. In the Yee-coordinate system, the +electric field components are located on the faces of the grid cells that are +perpendicular to the x, y, and z axes, while the magnetic field components are +located on the faces of the grid cells that are parallel to the x, y, and z +axes. This interlacing of the field components ensures that the update equations +for the electric and magnetic fields are consistent with each other and with the +underlying physics. + +The Yee-coordinate system is named after Kane S. Yee, who first proposed this +type of grid discretization in 1966. Since then, the Yee-coordinate system has +become a standard in FDTD simulations and is widely used in many different +applications, including electromagnetic wave propagation, antenna design, and +photonics. + + +In fluid dynamics, a staggered grid with interlaced Yee-coordinates is a type of +grid discretization that can be used to simulate fluid flow using the +Navier-Stokes equations. In this type of grid, the fluid velocity components are +located on the faces of the grid cells that are perpendicular to the x, y, and z +axes, while the pressure is located on the grid points. This arrangement is +similar to the Yee grid used in FDTD simulations, where the electric and +magnetic fields are located on the faces of the grid cells and the current and +charge densities are located on the grid points. + +By discretizing the Navier-Stokes equations on a staggered grid with interlaced +Yee-coordinates, the equations can be solved numerically using finite-difference +methods, finite-volume methods, or other numerical techniques. This allows +researchers to simulate fluid flow and study the behavior of fluids in a wide +range of physical scenarios. + +The staggered grid with interlaced Yee-coordinates ensures that the update +equations for the fluid velocity and pressure are consistent with each other and +with the underlying physics. This type of grid discretization is widely used in +fluid dynamics to simulate fluid flow around objects, turbulence, and mixing. By +using this type of grid, researchers can gain a deeper understanding of fluid +dynamics and develop new technologies that rely on fluid flow, such as aircraft, +ships, and turbines. + + + + +Let me summarize what I understood from what you said: + +A staggered grid with interlaced Yee-coordinates is a type of grid +discretization commonly used in FDTD simulations. + +In fluid dynamics, a staggered grid with interlaced Yee-coordinates is a type of +grid discretization that can be used to simulate fluid flow using the +Navier-Stokes equations. In this type of grid, the fluid velocity components are +located on the faces of the grid cells that are perpendicular to the x, y, and z +axes, while the pressure is located on the grid points. + +In electrodynamics, a staggered grid with interlaced Yee-coordinates is a type +of grid discretization that can be used to simulate the electromagnetic fields +using Maxwell's equations. In this type of grid, the electric and magnetic +fields are located on the faces of the grid cells, while the current and charge +densities are located on the grid points. This arrangement is known as a +"staggered" grid because the field components are staggered with respect to the +grid points. + +In the electrodynamics Yee-coordinate system, the electric field components are +located on the faces of the grid cells that are perpendicular to the x, y, and z +axes, while the magnetic field components are located on the faces of the grid +cells that are parallel to the x, y, and z axes. + +Do I understand correctly that with interlaced Yee-coordinates vector fields are +always located on the faces of the grid cells, either on the faces that are +parallel to the x, y, and z axes or on the faces that are perpendicular to the +x, y, and z axes? + +Do I also understand correctly that with interlaced Yee-coordinates scalar +fields are always located on the grid points? + + +GitHub Copilot: Yes, you have understood correctly. + +In both electrodynamics and fluid dynamics, a staggered grid with interlaced +Yee-coordinates is a type of grid discretization where the vector fields are +located on the faces of the grid cells, either on the faces that are parallel to +the x, y, and z axes or on the faces that are perpendicular to the x, y, and z +axes. This arrangement is known as a "staggered" grid because the field +components are staggered with respect to the grid points. + +In contrast, scalar fields are always located on the grid points in a staggered +grid with interlaced Yee-coordinates. This is because the scalar fields are +associated with the physical quantities that are defined at the grid points, +such as the pressure in fluid dynamics or the charge density in electrodynamics. + +''' + +''' + +In the grid class, which is to use the operators, there are two types of fields, +namely sclar fields such as: + + self.P = bd.zeros((self.Nx, self.Ny, self.Nz, 1)) + +And vector fields such as: + + self.C = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + +Each of these fields can be located at either the faces or the edges of the grid +cells, meaning at either integer or half-integer grid points. + +This is why we need multiple versions of the grid, div and curl operators. + +The first operators we need are the curl and the gradient of a velocity field, +which is a vector field, which should be located on the faces of the grid cells. + + + + +Is it correct that in the curl_E and curl_H functions for a staggered grid with +interlaced Yee-coordinates the E and H fields are both located on the faces of +the grid, with the only difference being the faces perpendicular to the x, y, +and z axes for the E field, while for the H field they are the faces parallel to +the x, y and z axes? Thus, are both fields actually located on integer grid +points? + + +GitHub Copilot: Yes, that's correct! + +In the staggered grid with interlaced Yee-coordinates used in electromagnetic +simulations, the electric field `E` and magnetic field `H` are both located on +the faces of the grid cells. The only difference is that the electric field `E` +is located on the faces of the grid cells that are perpendicular to the x, y, +and z axes, while the magnetic field `H` is located on the faces of the grid +cells that are parallel to the x, y, and z axes. + +Both fields are located on half-integer grid points, which means that their +components are located halfway between the grid points. However, when we +interpolate the fields to the grid points, we use the values of the fields at +the nearest faces of the grid cells to the grid points. This means that the +electric field `E` is located on the edges of the grid cells that are +perpendicular to the x, y, and z axes, while the magnetic field `H` is located +on the faces of the grid cells that are parallel to the x, y, and z axes. + +Therefore, when we compute the curl of the electric field `E` using the `curl_E` +function, the resulting magnetic field `H` is located on the faces of the grid +cells that are parallel to the x, y, and z axes, which are integer grid points. +Similarly, when we compute the curl of the magnetic field `H` using the `curl_H` +function, the resulting electric field `E` is located on the edges of the grid +cells that are perpendicular to the x, y, and z axes, which are also integer +grid points. + + + +l4m4re: Do I understand correctly that we do not have to worry about where the +various fields are located on the grids, provided we use the correct +implementations of grad, div and the two curl functions? + +GitHub Copilot: Yes, that's correct! As long as you use the correct +implementations of the gradient, divergence, and curl functions for the +staggered grid with interlaced Yee-coordinates, you do not have to worry about +the specific locations of the fields on the grid. The `grad_p`, `div_vf`, +`curl_E`, and `curl_H` functions that we have discussed are designed to work +with the staggered grid used in electromagnetic and fluid dynamics simulations, +and they take care of the interpolation and averaging of the fields to the +correct locations on the grid. + +However, it is important to note that the input vector field `F` that you pass +to these functions must be located on the faces of the grid cells that are +perpendicular to the x, y, and z axes for fluid dynamics, or on the faces of the +grid cells that are perpendicular to the time axis for electromagnetic +simulations. If the input field is not located on the correct faces of the grid +cells, the results of the gradient, divergence, and curl operations may not be +correct. + +Thus, if I pass a fluid dynamics velocity field in the shape of a 4D array ith +dimensions (Nx+1, Ny+1, Nz+1, 3) as F to the delta(F) funtion we designed, it +should correctly compute the delta vector laplace operator? + +GitHub Copilot: Yes, that's correct! If you pass a fluid dynamics velocity field +in the shape of a 4D array with dimensions `(Nx+1, Ny+1, Nz+1, 3)` as `F` to the +`delta` function we designed, it should correctly compute the vector Laplace +operator `delta` of the field. + +The `delta` function takes a vector field `F` as input and computes the vector +Laplace operator of the field using the `grad_p`, `div_vf`, `curl_E`, and +`curl_H` functions that are designed to work with the staggered grid used in +fluid dynamics simulations. The output vector field `delta_F` is located on the +faces of the grid cells that are perpendicular to the x, y, and z axes, which is +consistent with the staggered grid used in fluid dynamics. + +Therefore, if you pass a fluid dynamics velocity field in the shape of a 4D +array with dimensions `(Nx+1, Ny+1, Nz+1, 3)` as `F` to the `delta` function, it +should correctly compute the vector Laplace operator of the field, regardless of +the specific locations of the field components on the grid. + + +Now let's refer to the original readme file of the fdtd library for more details +and edit a bit: + +-+- + +According to the Yee discretization algorithm, there are inherently two types of +fields on the grid: `E`-type point fields on integer grid locations and `H`-type +surface fields on half-integer grid locations. + +The beauty of these interlaced coordinates is that they enable a very natural +way of writing the curl of the electric and magnetic fields: the curl of an +H-type surface field will be an E-type point field and vice versa. + +This way, the curl of E can be written as + +```python + curl(E)[m,n,p] = (dEz/dy - dEy/dz, dEx/dz - dEz/dx, dEy/dx - dEx/dy)[m,n,p] + =( ((Ez[m,n+1,p]-Ez[m,n,p])/dy - (Ey[m,n,p+1]-Ey[m,n,p])/dz), + ((Ex[m,n,p+1]-Ex[m,n,p])/dz - (Ez[m+1,n,p]-Ez[m,n,p])/dx), + ((Ey[m+1,n,p]-Ey[m,n,p])/dx - (Ex[m,n+1,p]-Ex[m,n,p])/dy) + ) + =(1/du)*( ((Ez[m,n+1,p]-Ez[m,n,p]) - + (Ey[m,n,p+1]-Ey[m,n,p])), [assume dx=dy=dz=du] + ((Ex[m,n,p+1]-Ex[m,n,p]) - + (Ez[m+1,n,p]-Ez[m,n,p])), ((Ey[m+1,n,p]-Ey[m,n,p]) + - (Ex[m,n+1,p]-Ex[m,n,p])) ) + +``` + +this can be written efficiently with array slices (note that the factor `(1/du)` +was left out): + +```python def curl_E(E): + curl_E = np.zeros(E.shape) curl_E[:,:-1,:,0] += E[:,1:,:,2] - E[:,:-1,:,2] + curl_E[:,:,:-1,0] -= E[:,:,1:,1] - E[:,:,:-1,1] + + curl_E[:,:,:-1,1] += E[:,:,1:,0] - E[:,:,:-1,0] curl_E[:-1,:,:,1] -= + E[1:,:,:,2] - E[:-1,:,:,2] + + curl_E[:-1,:,:,2] += E[1:,:,:,1] - E[:-1,:,:,1] curl_E[:,:-1,:,2] -= + E[:,1:,:,0] - E[:,:-1,:,0] return curl_E +``` + + +The curl for H can be obtained in a similar way (note again that the factor +`(1/du)` was left out): + +```python def curl_H(H): + curl_H = np.zeros(H.shape) + + curl_H[:,1:,:,0] += H[:,1:,:,2] - H[:,:-1,:,2] curl_H[:,:,1:,0] -= + H[:,:,1:,1] - H[:,:,:-1,1] + + curl_H[:,:,1:,1] += H[:,:,1:,0] - H[:,:,:-1,0] curl_H[1:,:,:,1] -= + H[1:,:,:,2] - H[:-1,:,:,2] + + curl_H[1:,:,:,2] += H[1:,:,:,1] - H[:-1,:,:,1] curl_H[:,1:,:,2] -= + H[:,1:,:,0] - H[:,:-1,:,0] return curl_H +``` + + +The update equations can now be rewritten as + +```python + E += (c*dt/du)*inv(ε)*curl_H H -= (c*dt/du)*inv(µ)*curl_E +``` + +The number `(c*dt/du)` is a dimensionless parameter called the _Courant number_ +`sc`. For stability reasons, the Courant number should always be smaller than +`1/√D`, with `D` the dimension of the simulation. This can be intuitively be +understood as the condition that information should always travel slower than +the speed of light through the grid. In the FDTD method described here, +information can only travel to the neighboring grid cells (through application +of the curl). It would therefore take `D` time steps to travel over the diagonal +of a `D`-dimensional cube (square in `2D`, cube in `3D`), the Courant condition +follows then automatically from the fact that the length of this diagonal is +`1/√D`. + +This yields the final update equations for the FDTD algorithm: + +```python + E += sc*inv(ε)*curl_H H -= sc*inv(µ)*curl_E +``` + +-+- + +The fist thing we note is : "[assume dx=dy=dz=du]", which means that the grid +spacing is the same in all three dimensions. Further, we note that the factor +1/du is left out in the implementation of the curl functions, because it can be +taken outside the parentheses if dx=dy=dz=du as shown in the simplified +definition for curl_E: + +curl(E)[m,n,p] =(1/du)*( ((Ez[m,n+1,p]-Ez[m,n,p]) - (Ey[m,n,p+1]-Ey[m,n,p])), + ((Ex[m,n,p+1]-Ex[m,n,p]) - (Ez[m+1,n,p]-Ez[m,n,p])), + ((Ey[m+1,n,p]-Ey[m,n,p]) - (Ex[m,n+1,p]-Ex[m,n,p])) ) + +This way, the implementation of curl_E and curl_H can be simplified, but they +actually compute the curl apart from this factor (1/du), which is a very +important detail, because it means that the curl functions are not actually +computing the curl, but the curl multiplied by the grid spacing, which is +formally incorrect, but works in practice since the factor (1/du) is applied +afterwards in the update function, because the factor (1/du) is a part of the +courant number sc, which is applied in the update function. + +Thus, if this is correct: + +"According to the Yee discretization algorithm, there are inherently two types +of fields on the grid: `E`-type point fields on integer grid locations and +`H`-type surface fields on half-integer grid locations." + +The actual update statements are: + + curl = curl_H(self.H) self.E += self.courant_number * + self.inverse_permittivity * curl + + curl = curl_E(self.E) self.H -= self.courant_number * + self.inverse_permeability * curl + +Then the curl_E function should take a E-type point field as input and return a +H-type surface field as output, while the curl_H function should take a H-type +surface field as input and return a E-type point field as output. + +However, this is not the case, because the curl_E function takes a H-type +surface field as input and returns a E-type point field as output, while the +curl_H function takes a E-type point field as input and returns a H-type surface +field as output. + +So, the curl_E function should be called curl_H and the curl_H function should +be called curl_E, because they are the wrong way around. + +For reference, we include previous implementations of the curl functions: + +def curl_E(E: Tensorlike) -> Tensorlike: + """Transforms an E-type field into an H-type field by performing a curl + operation + + Args: + E: Electric field to take the curl of (E-type field located on the + edges of the grid cell [integer gridpoints]) + + Returns: + The curl of E (H-type field located on the faces of the grid + [half-integer grid points]) + """ curl = bd.zeros(E.shape) + + curl[:, :-1, :, 0] += E[:, 1:, :, 2] - E[:, :-1, :, 2] curl[:, :, :-1, 0] -= + E[:, :, 1:, 1] - E[:, :, :-1, 1] + + curl[:, :, :-1, 1] += E[:, :, 1:, 0] - E[:, :, :-1, 0] curl[:-1, :, :, 1] -= + E[1:, :, :, 2] - E[:-1, :, :, 2] + + curl[:-1, :, :, 2] += E[1:, :, :, 1] - E[:-1, :, :, 1] curl[:, :-1, :, 2] -= + E[:, 1:, :, 0] - E[:, :-1, :, 0] + + return curl + + +def curl_H(H: Tensorlike) -> Tensorlike: + """Transforms an H-type field into an E-type field by performing a curl + operation + + Args: + H: Magnetic field to take the curl of (H-type field located on + half-integer grid points) + + Returns: + The curl of H (E-type field located on the edges of the grid [integer + grid points]) + + """ curl = bd.zeros(H.shape) + + curl[:, 1:, :, 0] += H[:, 1:, :, 2] - H[:, :-1, :, 2] curl[:, :, 1:, 0] -= + H[:, :, 1:, 1] - H[:, :, :-1, 1] + + curl[:, :, 1:, 1] += H[:, :, 1:, 0] - H[:, :, :-1, 0] curl[1:, :, :, 1] -= + H[1:, :, :, 2] - H[:-1, :, :, 2] + + curl[1:, :, :, 2] += H[1:, :, :, 1] - H[:-1, :, :, 1] curl[:, 1:, :, 2] -= + H[:, 1:, :, 0] - H[:, :-1, :, 0] + + return curl + + +def curl_point(F: Tensorlike) -> Tensorlike: + """ Compute the point curl of a vector field F located on the grid points + using the right-handed curl operator. F is a 4D array with dimensions (Nx+1, + Ny+1, Nz+1, 3). The last dimension represents the x, y, and z components of + the vector field. The first three dimensions represent the grid points. + + Returns a 4D array with dimensions (Nx+1, Ny+1, Nz+1, 3). The first three + dimensions represent the grid points. The last dimension represents the x, + y, and z components of the curl. + + This implementation takes a vector field `F` as input and computes the point + curl of the field in each direction using the `bd` backend and the + right-handed curl operator. The output vector field `curl_F` is located on + the faces of the grid cells that are perpendicular to the x, y, and z axes, + and are stored at half-integer grid points in the arrays and is therefore a + surface field. + + Args: + F: Point field to take the curl of (E-type point field located on the + edges of the grid [integer grid points]) + + Returns: + The curl of F (H-type surface field located on the faces of the grid + cell [half-integer gridpoints]) + + """ curl = bd.zeros(F.shape) + + curl[:, :-1, :, 0] += F[:, 1:, :, 2] - F[:, :-1, :, 2] curl[:, :, :-1, 0] -= + F[:, :, 1:, 1] - F[:, :, :-1, 1] + + curl[:, :, :-1, 1] += F[:, :, 1:, 0] - F[:, :, :-1, 0] curl[:-1, :, :, 1] -= + F[1:, :, :, 2] - F[:-1, :, :, 2] + + curl[:-1, :, :, 2] += F[1:, :, :, 1] - F[:-1, :, :, 1] curl[:, :-1, :, 2] -= + F[:, 1:, :, 0] - F[:, :-1, :, 0] + + return curl + +def curl_point_v2(v: Tensorlike) -> Tensorlike: + """ Compute the point curl of a vector field v located on the edges of the + grid cells using the right-handed curl operator. v is a 4D array with + dimensions (Nx, Ny, Nz, 3). The last dimension represents the x, y, and z + components of the vector field. The first three dimensions represent the + grid points. + + Returns a 4D array with dimensions (Nx, Ny, Nz, 3). The first three + dimensions represent the grid points. The last dimension represents the x, + y, and z components of the curl. + + This implementation assumes that the input vector field `v` is located on + the edges of the grid cells, stored at integer grid points in the arrays, + and is therefore assumed to be a point field. The edges are considered to be + perpendicular to the x, y, and z axes, which is consistent with the + staggered grid used in fluid dynamics and electrodynamics. + + The output vector field `curl_v` is located on the faces of the grid cells + that are perpendicular to the x, y, and z axes, and are stored at + half-integer grid points in the arrays, and is therefore a surface field. + + Args: + v: Point field to take the curl of (velocity point field located on the + edges of the grid [integer grid points]) + + Returns: + The curl of v (vorticity surface field located on the faces of the grid + cell [half-integer gridpoints]) + + """ curl = bd.zeros(v.shape) + + curl[1:-1, :, :, 0] += v[1:-1, :, :, 2] - v[:-2, :, :, 2] curl[:, 1:-1, :, + 0] -= v[:, 1:-1, :, 1] - v[:, :-2, :, 1] + + curl[:, 1:-1, :, 1] += v[:, 1:-1, :, 0] - v[:, :-2, :, 0] curl[:-1, :, :, 1] + -= v[1:, :, :, 2] - v[:-1, :, :, 2] + + curl[:-1, :, :, 2] += v[1:, :, :, 1] - v[:-1, :, :, 1] curl[:, 1:-1, :, 2] + -= v[:, 1:-1, :, 0] - v[:, :-2, :, 0] + + return curl + + +def curl_surface_v2(F: Tensorlike) -> Tensorlike: + """ Compute the surface curl of a vector field F located on the faces of the + grid cells using the right-handed curl operator. F is a 4D array with + dimensions (Nx, Ny, Nz, 3). The last dimension represents the x, y, and z + components of the vector field. The first three dimensions represent the + grid points. Returns a 4D array with dimensions (Nx, Ny, Nz, 3). The first + three dimensions represent the grid points. The last dimension represents + the x, y, and z components of the curl. + + This implementation assumes that the input vector field `F` is located on + the faces of the grid cells, stored at half-integer grid points in the + arrays, and is therefore assumed to be a surface field. The faces are + considered to be perpendicular to the x, y, and z axes, which is consistent + with the staggered grid used in fluid dynamics and electrodynamics. + + The output field `curl_F` is located on the edges of the grid cells that are + perpendicular to the x, y, and z axes, and are stored at integer grid points + in the arrays, and is therefore a point field. + + Args: + F: Surface field to take the curl of (vorticity surface field located on + half-integer grid points) + + Returns: + The curl of F (strain rate point field located on the edges of the grid + [integer grid points]) + + """ curl = bd.zeros(F.shape) + + curl[:, :-1, :, 0] += F[:, 1:, :, 2] - F[:, :-1, :, 2] curl[:, :, :-1, 0] -= + F[:, :, 1:, 1] - F[:, :, :-1, 1] + + curl[:, :, :-1, 1] += F[:, :, 1:, 0] - F[:, :, :-1, 0] curl[:-1, :, :, 1] -= + F[1:, :, :, 2] - F[:-1, :, :, 2] + + curl[:-1, :, :, 2] += F[1:, :, :, 1] - F[:-1, :, :, 1] curl[:, :-1, :, 2] -= + F[:, 1:, :, 0] - F[:, :-1, :, 0] + + return curl + + +def curl_surface(F: Tensorlike) -> Tensorlike: + """ Compute the surface curl of a vector field F located on the faces of the + grid cells using the right-handed curl operator. F is a 4D array with + dimensions (Nx+1, Ny+1, Nz+1, 3). The last dimension represents the x, y, + and z components of the vector field. The first three dimensions represent + the grid points. Returns a 4D array with dimensions (Nx+1, Ny+1, Nz+1, 3). + The first three dimensions represent the grid points. The last dimension + represents the x, y, and z components of the curl. + + This implementation assumes that the input vector field `F` is located on + the faces of the grid cells, stored at half-integer grid points in the + arrays, and is therefore assumed to be a surface field. The faces are + considered to be perpendicular to the x, y, and z axes, which is consistent + with the staggered grid used in fluid dynamics and electrodynamics. + + The output field `curl_F` is located on the edges of the grid cells that are + perpendicular to the x, y, and z axes, and are stored at integer grid points + in the arrays, and is therefore a point field. + + Args: + F: Surface field to take the curl of (H-type field located on + half-integer grid points) + + Returns: + The curl of F (E-type field located on the edges of the grid [integer + grid points]) + + """ curl = bd.zeros(F.shape) + + curl[1:-1, :, :, 0] += F[1:-1, :, :, 2] - F[:-2, :, :, 2] curl[:, 1:-1, :, + 0] -= F[:, 1:-1, :, 1] - F[:, :-2, :, 1] + + curl[:, 1:-1, :, 1] += F[:, 1:-1, :, 0] - F[:, :-2, :, 0] curl[:-1, :, :, 1] + -= F[1:, :, :, 2] - F[:-1, :, :, 2] + + curl[:-1, :, :, 2] += F[1:, :, :, 1] - F[:-1, :, :, 1] curl[:, 1:-1, :, 2] + -= F[:, 1:-1, :, 0] - F[:, :-2, :, 0] + + return curl + + +-:- + +Q: are these correct? + +GitHub Copilot: Yes, these functions are correct and consistent with a staggered +Yee grid. The `curl_point` function computes the point curl of a vector field +`F` located on the grid points, which are the edges of the grid cells and are +stored at integer grid points in the arrays. The output vector field `curl_F` is +located on the grid points and is therefore a point field. + +The `curl_surface` function computes the surface curl of a vector field `F` +located on the faces of the grid cells, which are perpendicular to the x, y, and +z axes, and are stored at half-integer grid points in the arrays. The output +field `curl_F` is located on the faces of the grid cells that are perpendicular +to the x, y, and z axes, and are stored at half-integer grid points in the +arrays, and is therefore a surface field. + +These functions are consistent with the staggered Yee grid, which is commonly +used in electromagnetics and fluid dynamics simulations. + +-:- + +Above, we have 4 curl functions, about which CoPilot says: + +GitHub Copilot: The four curl functions in the code are `curl_E`, `curl_H`, +`curl_point`, and `curl_surface`. + +`curl_E` takes an E-type field located on the edges of the grid cell [integer +gridpoints] as input and returns an H-type field located on the faces of the +grid [half-integer grid points] as output. The output field is located on the +faces of the grid cells, perpendicular to the x, y, and z axes. + +`curl_H` takes an H-type field located on half-integer grid points as input and +returns an E-type field located on the edges of the grid [integer grid points] +as output. The output field is located on the edges of the grid cells, +perpendicular to the x, y, and z axes. + +`curl_point` takes an E-type point field located on the edges of the grid +[integer grid points] as input and returns an H-type surface field located on +the faces of the grid cell [half-integer gridpoints] as output. The output field +is located on the faces of the grid cells, perpendicular to the x, y, and z +axes. + +`curl_surface` takes an H-type surface field located on half-integer grid points +as input and returns an E-type field located on the edges of the grid [integer +grid points] as output. The output field is located on the edges of the grid +cells, perpendicular to the x, y, and z axes. + +In summary, the four curl functions differ in the type of field they take as +input and output, and in the location of the fields on the grid. The indexing of +the arrays in the functions is also different, depending on the location of the +fields on the grid. + +-:- + +Previously, CoPilot also said: + +"So, the curl_E function should be called curl_H and the curl_H function should +be called curl_E, because they are the wrong way around." + +So, what I did was copy the implementation of the curl_E function and called it +curl_surface, and I copied the implementation of the curl_H function and called +it curl_point, so that the names of the functions are consistent with the type +of field they take as input and output, and with the location of the fields on +the grid. + +However, the comments were not correct, so I asked CoPilot to fix them. + +Then, it decidid to also change the indexing in the function, changing the +implementation. + +@CoPilot: l4m4re: explain the difference in array slice indexing betwen the +curl_H and curl_point functions + +GitHub Copilot: The difference in array slice indexing between the `curl_H` and +`curl_point` functions is due to the fact that they operate on different types +of fields. + +The `curl_H` function takes an H-type field as input, which is located on +half-integer grid points, and returns an E-type field located on integer grid +points. Therefore, the array slice indexing in `curl_H` is shifted by one index +in the first dimension compared to `curl_point`. Specifically, in `curl_H`, the +slice `[:, 1:, :, 0]` corresponds to the x-component of the curl, while in +`curl_point`, the slice `[:, :-1, :, 0]` corresponds to the x-component of the +curl. + +On the other hand, the `curl_point` function takes an E-type point field as +input, which is located on integer grid points, and returns an H-type surface +field located on half-integer grid points. Therefore, the array slice indexing +in `curl_point` is shifted by one index in the first dimension compared to +`curl_H`. Specifically, in `curl_point`, the slice `[:, :-1, :, 0]` corresponds +to the x-component of the curl, while in `curl_H`, the slice `[:, 1:, :, 0]` +corresponds to the x-component of the curl. + +l4m4re: Does this mean that the curl_H function applies to a H field that is +actually a point field (and therefore incorrect), while the curl_point function +is correct and consistend with a staggered Yee grid? + +GitHub Copilot: No, that's not correct. The `curl_H` function takes a magnetic +field `H` as input, which is defined on the edges of the grid cells and is +therefore a point field. The output of the `curl_H` function is an electric +field `E`, which is also defined on the edges of the grid cells and is therefore +a point field. The `curl_H` function is consistent with the Yee grid +implementation, where the magnetic field `H` and the electric field `E` are both +defined on the edges of the grid cells. + +The `curl_point` function, on the other hand, takes a point field `F` as input, +which is defined on the edges of the grid cells and is therefore an integer grid +point. The output of the `curl_point` function is a magnetic field `curl_F`, +which is defined on the faces of the grid cells and is therefore a half-integer +grid point. The `curl_point` function is also consistent with the Yee grid +implementation, where the magnetic field `H` and the auxiliary magnetic field +`B` are defined on the faces of the grid cells. + +Therefore, both the `curl_H` and `curl_point` functions are consistent with the +Yee grid implementation, but they operate on different input vector fields and +compute the curl of those fields using different finite difference +approximations. + +-:- + +Q: + +l4m4re: Please suggest alternative implementations for the following +curl_surface and curl_point functions such that we can compute +curl_surface(curl_point(v)) for a point vector field v directly, under the +assumption that v is a point field representing the flow velocity in a fluid +dynamics staggered Yee grid: + +# previous implementation of point and surface curls + +GitHub Copilot: To compute `curl_surface(curl_point(v))` for a point vector +field `v` directly, we can use the following alternative implementations for the +`curl_point` and `curl_surface` functions: + +''' + +def curl_point(v: Tensorlike) -> Tensorlike: + """ + Compute the point curl of a vector field v located on the edges of the grid + cells using the right-handed curl operator. v is a 4D array with dimensions + (Nx, Ny, Nz, 3). The last dimension represents the x, y, and z components of + the vector field. The first three dimensions represent the grid points. + + Returns a 4D array with dimensions (Nx, Ny, Nz, 3). The first three + dimensions represent the grid points. The last dimension represents the x, + y, and z components of the curl. + + This implementation assumes that the input vector field `v` is located on + the edges of the grid cells, stored at integer grid points in the arrays, + and is therefore assumed to be a point field. The edges are considered to be + perpendicular to the x, y, and z axes, which is consistent with the + staggered grid used in fluid dynamics and electrodynamics. + + The output vector field `curl_v` is located on the faces of the grid cells + that are perpendicular to the x, y, and z axes, and are stored at + half-integer grid points in the arrays, and is therefore a surface field. + + Args: + v: Point field to take the curl of (velocity point field located on the + edges of the grid [integer grid points]) + + Returns: + The curl of v (vorticity surface field located on the faces of the grid + cell [half-integer gridpoints]) + + """ + curl = bd.zeros(v.shape) + + curl[1:-1, :, :, 0] += v[1:-1, :, :, 2] - v[:-2, :, :, 2] + curl[:, 1:-1, :, 0] -= v[:, 1:-1, :, 1] - v[:, :-2, :, 1] + + curl[:, 1:-1, :, 1] += v[:, 1:-1, :, 0] - v[:, :-2, :, 0] + curl[:-1, :, :, 1] -= v[1:, :, :, 2] - v[:-1, :, :, 2] + + curl[:-1, :, :, 2] += v[1:, :, :, 1] - v[:-1, :, :, 1] + curl[:, 1:-1, :, 2] -= v[:, 1:-1, :, 0] - v[:, :-2, :, 0] + + return curl + + +def curl_surface(F: Tensorlike) -> Tensorlike: + """ + Compute the surface curl of a vector field F located on the faces of the + grid cells using the right-handed curl operator. F is a 4D array with + dimensions (Nx, Ny, Nz, 3). The last dimension represents the x, y, and z + components of the vector field. The first three dimensions represent the + grid points. Returns a 4D array with dimensions (Nx, Ny, Nz, 3). The first + three dimensions represent the grid points. The last dimension represents + the x, y, and z components of the curl. + + This implementation assumes that the input vector field `F` is located on + the faces of the grid cells, stored at half-integer grid points in the + arrays, and is therefore assumed to be a surface field. The faces are + considered to be perpendicular to the x, y, and z axes, which is consistent + with the staggered grid used in fluid dynamics and electrodynamics. + + The output field `curl_F` is located on the edges of the grid cells that are + perpendicular to the x, y, and z axes, and are stored at integer grid + points in the arrays, and is therefore a point field. + + Args: + F: Surface field to take the curl of (vorticity surface field located + on half-integer grid points) + + Returns: + The curl of F (strain rate point field located on the edges of the grid + [integer grid points]) + + """ + curl = bd.zeros(F.shape) + + curl[:, :-1, :, 0] += F[:, 1:, :, 2] - F[:, :-1, :, 2] + curl[:, :, :-1, 0] -= F[:, :, 1:, 1] - F[:, :, :-1, 1] + + curl[:, :, :-1, 1] += F[:, :, 1:, 0] - F[:, :, :-1, 0] + curl[:-1, :, :, 1] -= F[1:, :, :, 2] - F[:-1, :, :, 2] + + curl[:-1, :, :, 2] += F[1:, :, :, 1] - F[:-1, :, :, 1] + curl[:, :-1, :, 2] -= F[:, 1:, :, 0] - F[:, :-1, :, 0] + + return curl + + +''' +We can then compute `curl_surface(curl_point(v))` for a point vector field `v` +directly using the following code: + + +import numpy as np + +# Define the velocity point field v v = np.zeros((Nx, Ny, Nz, 3)) + +# Compute the point curl of the velocity point field v curl_v = curl_point(v) + +# Compute the surface curl of the vorticity surface field curl_v strain_rate = +curl_surface(curl_v) + +''' + + +def div(vf: Tensorlike) -> Tensorlike: + """ + Compute the divergence of a velocity field vf located on the faces of the + grid cells. vf is a 4D array with dimensions (Nx+1, Ny+1, Nz+1, 3). The last + dimension represents the x, y, and z components of the velocity field. The + first three dimensions represent the grid points. Returns a 4D array with + dimensions (Nx+1, Ny+1, Nz+1, 1). The first three dimensions represent the + grid points. The last dimension represents the scalar divergence. + + GitHub Copilot: The implementation of the div function using array slices + results in a field located at an integer grid point because in a staggered + Yee grid, the resulting vector fields are located at either integer or + half-integer grid points. The integer grid points represent vector fields + defined at a point in space, while the half-integer grid points represent + vector fields defined over an area. + + The div function is defined as the surface integral of the flux of a vector + field over a closed surface. In a staggered Yee grid, the discretized cube + is used to represent the finite difference fields. The resulting value of + the div function is not located on one of the faces of the cube, but rather + at the center of the cube, which is a point in space. Since fields defined + at a point in space are represented by integer grid points in a staggered + Yee grid, the resulting vector field is located at an integer grid point. + + Therefore, when implementing the div function using array slices, the + resulting field is located at an integer grid point because the div function + is defined as a surface integral over the area of all the faces of a cube, + divided by the volume of the cube, resulting in a unit of per meter [1/m]. + The resulting value is not located on one of the faces of the cube, but + rather at the center of the cube, which is a point in space represented by + an integer grid point in a staggered Yee grid. + + GitHub Copilot: The `div` function computes the divergence of a velocity + field `vf` located on the faces of the grid cells. The input velocity field + `vf` should be a 4D array with dimensions `(Nx+1, Ny+1, Nz+1, 3)` to be + consistent with the staggered grid used in fluid dynamics. + + In a staggered Yee grid, the velocity field is located on the faces of the + grid cells that are perpendicular to the x, y, and z axes. The resulting + vector fields are located at either integer or half-integer grid points. The + integer grid points represent vector fields defined at a point in space, + while the half-integer grid points represent vector fields defined over an + area. + + The `div` function is defined as the surface integral of the flux of a + vector field over a closed surface. In a staggered Yee grid, the discretized + cube is used to represent the finite difference fields. The resulting value + of the `div` function is not located on one of the faces of the cube, but + rather at the center of the cube, which is a point in space. Since fields + defined at a point in space are represented by integer grid points in a + staggered Yee grid, the resulting vector field is located at an integer grid + point. + + The implementation of the `div` function using array slices results in a + field located at an integer grid point because the resulting value is not + located on one of the faces of the cube, but rather at the center of the + cube, which is a point in space represented by an integer grid point in a + staggered Yee grid. + + The implementation of the `div` function computes the x, y, and z components + of the divergence using array slices. The resulting divergence field is a 4D + array with dimensions `(Nx+1, Ny+1, Nz+1, 1)`. The first three dimensions + represent the grid points, and the last dimension represents the scalar + divergence. + + """ + div_vf = bd.zeros((vf.shape[0], vf.shape[1], vf.shape[2], 1)) + # Compute the x-component of the divergence + div_vf[1:-1, :, :, 0] += (vf[1:-1, :, :, 0] - vf[:-2, :, :, 0]) + div_vf[1:-1, :, :, 0] -= (vf[2:, :, :, 0] - vf[1:-1, :, :, 0]) + # Compute the y-component of the divergence + div_vf[:, 1:-1, :, 0] += (vf[:, 1:-1, :, 1] - vf[:, :-2, :, 1]) + div_vf[:, 1:-1, :, 0] -= (vf[:, 2:, :, 1] - vf[:, 1:-1, :, 1]) + # Compute the z-component of the divergence + div_vf[:, :, 1:-1, 0] += (vf[:, :, 1:-1, 2] - vf[:, :, :-2, 2]) + div_vf[:, :, 1:-1, 0] -= (vf[:, :, 2:, 2] - vf[:, :, 1:-1, 2]) + return div_vf + + +def grad(p: Tensorlike) -> Tensorlike: + """ + Compute the gradient of a scalar field p located on the grid points. p is a + 4D array with dimensions (Nx+1, Ny+1, Nz+1, 1). The first three dimensions + represent the grid points. The last dimension represents the scalar field. + Returns a 4D array with dimensions (Nx+1, Ny+1, Nz+1, 3). The first three + dimensions represent the grid points. The last dimension represents the x, + y, and z components of the gradient. + + The input scalar field `p` should be a 4D array with dimensions `(Nx+1, + Ny+1, Nz+1, 1)` to be consistent with the staggered grid used in fluid + dynamics. + + This implementation takes a scalar field `p` as input and computes the + gradient of the field in each direction using the `bd` backend. The output + vector field `grad_p` is located on the faces of the grid cells that are + perpendicular to the x, y, and z axes. Note that this implementation assumes + that the input scalar field `p` is located on the grid points, which is + consistent with the staggered grid used in fluid dynamics. + + + """ + grad = bd.zeros((p.shape[0], p.shape[1], p.shape[2], 3)) + # Compute the x-component of the gradient + grad[:-1, :, :, 0] = (p[1:, :, :, 0] - p[:-1, :, :, 0]) + # Compute the y-component of the gradient + grad[:, :-1, :, 1] = (p[:, 1:, :, 0] - p[:, :-1, :, 0]) + # Compute the z-component of the gradient + grad[:, :, :-1, 2] = (p[:, :, 1:, 0] - p[:, :, :-1, 0]) + return grad + + + + +def delta(F: Tensorlike) -> Tensorlike: + """ + Compute the vector Laplace operator of a vector field F located on the faces + of the grid cells.· F is a 4D array with dimensions (Nx+1, Ny+1, Nz+1, 3). + The last dimension represents the x, y, and z components of the vector + field. The first three dimensions represent the grid points. Returns a 4D + array with dimensions (Nx+1, Ny+1, Nz+1, 3). The first three dimensions + represent the grid points. The last dimension represents the x, y, and z + components of the vector Laplace operator. + + This implementation takes a vector field `F` as input and computes the + vector Laplace operator of the field using the `bd` backend. The output + vector field `delta_F` is located on the faces of the grid cells that are + perpendicular to the x, y, and z axes. Note that this implementation assumes + that the input vector field `F` is located on the faces of the grid cells + that are perpendicular to the x, y, and z axes, which is consistent with the + staggered grid used in fluid dynamics and electrodynamics. + + + The `delta` function computes the vector Laplace operator of a vector field + using the `div`, `grad`, `curl_point`, and `curl_surface` functions. The + `div` and `grad` functions compute the divergence and gradient of a vector + field, respectively, using finite differences between the components of the + vector field at different points in the grid cell. The output fields are + located on the faces of the grid cells that are perpendicular to the x, y, + and z axes, which is consistent with the staggered Yee grid. + + The field types are: + + - `F`: A vector field located on the edges of the grid cells (point field). + - `phi`: A scalar field located on the grid points (cell-centered field). + - `E`: A vector field located on the edges of the grid cells (point field). + - `A`: A vector field located on the faces of the grid cells (surface + field). + - `H`: A vector field located on the edges of the grid cells (point field). + - `delta_F`: A vector field located on the edges of the grid cells (point + field). + + The `div` function computes the divergence of the vector field `F`, which + results in a scalar field `phi` located on the grid points. + + The `grad` function computes the gradient of the scalar field `phi`, which + results in a vector field `E` located on the edges of the grid cells. + + The `curl_point` function computes the point curl of the vector field `F`, + which results in a vector field `A` located on the faces of the grid cells. + + The `curl_surface` function computes the surface curl of the vector field + `A`, which results in a vector field `H` located on the edges of the grid + cells. + + Finally, the `delta_F` vector field is computed as the difference between + the vector field `E` and the vector field `H`, both located on the edges of + the grid cells. + + l4m4re: This makes sense. In the above code, it is the vector field A that + is a surface field, while the vector field H is a point field. + + GitHub Copilot: Yes, that's correct. In the above code, the vector field `A` + is a surface field located on the faces of the grid cells, while the vector + field `H` is a point field located on the edges of the grid cells. + + The `curl_point` function computes the point curl of the vector field `F`, + which results in a vector field `A` located on the faces of the grid cells. + The `curl_surface` function then computes the surface curl of the vector + field `A`, which results in a vector field `H` located on the edges of the + grid cells. + + The resulting vector field `H` represents the curl of the vector field `F` + at the edges of the grid cells that are perpendicular to the x, y, and z + axes, and is defined on the edges of the grid cells, which are integer grid + points. + + """ + phi = div(F) + E = grad(phi) + A = curl_point(F) + H = curl_surface(A) + delta_F = E - H + return delta_F + + + +def delta_C(C: Tensorlike) -> Tensorlike: + """ + Compute the vector Laplace operator of a vector field F located on the faces of the grid + cells.· + F is a 4D array with dimensions (Nx+1, Ny+1, Nz+1, 3). + The last dimension represents the x, y, and z components of the vector field. + The first three dimensions represent the grid points. + Returns a 4D array with dimensions (Nx+1, Ny+1, Nz+1, 3). + The first three dimensions represent the grid points. + The last dimension represents the x, y, and z components of the vector Laplace operator. + + This implementation takes a vector field `F` as input and computes the vector Laplace + operator of the field using the `bd` backend. The output vector field `delta_F` is + located on the faces of the grid cells that are perpendicular to the x, y, and z axes. + Note that this implementation assumes that the input vector field `F` is located on the + faces of the grid cells that are perpendicular to the x, y, and z axes, which is + consistent with the staggered grid used in fluid dynamics and electrodynamics. + + Calculate the vector Laplacian of a vector field C using the left-handed + and right-handed curl operators. + + In our superfluid medium model, the vector field C has a unit of measurement + in Amperes per second, or, in basic unit: + + kilograms per second squared [kg/s^2], + + It is defined by the equation: + + C = - k * rho [v], + + with rho the mass density of the superfluid medium, which in this model + is taken equal to the electric permittivity of the medium, so for + the vacuum + + rho = epsilon_0 = eta/k = 8.854187817620389e-12 [kg/m^3]. + + And, for the medium or vaccuum, k the quantum circulation constant, + diffusivity or kine-matic viscosity of the medium with a value of: + + k = c*c = 8.987551787368176e+16 [m^2/s], + + Thus, we indeed define k to have a value equal to c*c, but a unit of + measurement of meters squared per second [m^2/s] representing + the quantum circulation konstant, diffusivity or kinematic viscosity of + the medium. + + Thus, C has a unit of measurement in [m^2/s] * [kg/m^3] * [m/s] = [kg/s^2], + the unit we equate to the Ampere in this model, hence the choice of C, for + "current" in "Amperes" or kilograms per second squared. + + in the following code, lower case is used for scalar fields, upper case + for vector fields. + + """ + p = div(C) # p is a scalar field representing pressure in [Pa/m^3] + L = grad(p) # a vector field representing linear force density in [N/m^3] + + A = curl_point(C) # the vector potential, also in [Pa/m^3]. + R = curl_surface(A) # a vector field representing angular force density in [N/m^3] + + d_C = L - R # Delta C, the vector laplace operator of C in [N/m^3] + + return d_C # net force density in [N/m^3]. + + + + + +def delta_I(I: Tensorlike) -> Tensorlike: + """ + Compute the vector Laplace operator of a vector field F located on the faces of the grid + cells.· + F is a 4D array with dimensions (Nx+1, Ny+1, Nz+1, 3). + The last dimension represents the x, y, and z components of the vector field. + The first three dimensions represent the grid points. + Returns a 4D array with dimensions (Nx+1, Ny+1, Nz+1, 3). + The first three dimensions represent the grid points. + The last dimension represents the x, y, and z components of the vector Laplace operator. + + + In our superfluid medium model, the vector field I has a unit of measurement + in Watts per second, or, in basic unit: + kilograms per seconds to the third [kg/s^3] + + This repreents the time derivative of the field C, whe have defined earlier. + + It is defined by the equation: + + I = - k * rho [a], + + with rho the mass density of the superfluid medium, which in this model + is taken equal to the electric permittivity of the medium, so for + the vacuum + + rho = epsilon_0 = eta/k = 8.854187817620389e-12 [kg/m^3]. + + And, for the medium or vaccuum, k the quantum circulation constant, + diffusivity or kinematic viscosity of the medium with a value of: + + k = c*c = 8.987551787368176e+16 [m^2/s], + + Thus, I has a unit of measurement in [m^2/s] * [kg/m^3] * [m/s^2] = [kg/s^3], + the unit we equate to Intensity in this model, hence the choice of I, for + "intensity" in "Watts per meter squared" or kilograms per second to the third. + + For this field, we can also define derived fields, reesulting in a Yank + density field Y, with a unit of measurment in Newton per cubic meter - second [N/m^3-s], + the time derivative of Force density F. + + in the following code, lower case is used for scalar fields, upper case + for vector fields. + + """ + t = div(I) # t is a scalar field representing power density in [W/m^3] , presumed to represent temperarure + Y_l = grad(t) # a vector field representing linear yank density in [N/m^3-s] + + # the vector potential, also in [kg/m-s3], [N/m2-s], [J/m3-s], [Pa/s], + # [W/m3], [K] or [kg/m-s3] representing angular power or heat density, force + # flux, yank density flux, or angular temperature + W = curl_point(I) + Y_a = curl_surface(W) # a vector field representing angular yank density in [N/m^3-s] + + d_I = Y_l - Y_a # Delta I, the vector laplace operator of I in [N/m^3-s] + + return d_I # net yank density or current flux in [N/m^3-s]. + + + +''' + +Thus, we have defined fields similar to Maxwell and/or Navier-Stokes equations, +but not only in first order, like Maxwell and Navier-Stokes, but also in second +order in full 3D vector calculus. + +In contrast to Maxwell and Navier-Stokes, we have defined the fields C and I as +our primary vector fields, so our potential fields p and A are uniquely defined +and derived from a primary field along an operator defined by the vector laplace +operator. + +In both Maxwell and Navier-Stokes, the potential fields are defined as kind of +the reversal of the gradient and curl opertors on E and H, or B in fluid +dynamics, but there is no unique definition of the potential fields, leaving +room for "gauge freedom" + +''' + + + + +''' + +import numpy as np + +# Generate a random velocity field F located on the faces of the grid cells that +# are perpendicular to the x, y, and z axes +Nx, Ny, Nz = 10, 10, 10 +F = np.random.rand(Nx+1, Ny+1, Nz+1, 3) + +# Compute the vector Laplace operator delta_F using the delta function +delta_F = delta(F) + +# Compute the analytical solution of the vector Laplace operator for the given +# velocity field F Here we assume that the velocity field is divergence-free, so +# the Laplacian of the velocity field is zero Therefore, the vector Laplace +# operator of the velocity field is simply the negative of the curl of the curl +# of the velocity field +curl_F = curl_E(F) +curl_curl_F = curl_H(curl_F) +analytical_delta_F = -curl_curl_F + +# Compare the computed delta_F with the analytical solution of the vector +# Laplace operator for the given velocity field F +assert np.allclose(delta_F, analytical_delta_F) + + + + + +import numpy as np +import subprocess + +# Generate a random vector field F +Nx, Ny, Nz = 10, 10, 10 +F = np.random.rand(Nx+1, Ny+1, Nz+1, 3) + +# Compute the gradient of F using your implementation +grad_F = grad(F) + +# Compute the divergence of F using your implementation +div_F = div(F) + +# Compute the curl of F using your implementation +curl_F = curl_E(F) + +# Call Octave to compute the gradient, divergence, and curl of F +octave_cmd = f"octave --eval \"pkg load 'nurbs-1.3.13'; F = {F.tolist()}; grad_F = gradient(F); div_F = divergence(F); curl_F = curl(F); disp(grad_F); disp(div_F); disp(curl_F);\"" +result = subprocess.run(octave_cmd, shell=True, capture_output=True, text=True) + +# Parse the output of Octave to obtain the computed gradient, divergence, and curl of F +output = result.stdout.strip().split("\n") +grad_F_octave = np.array(eval(output[0])) +div_F_octave = np.array(eval(output[1])) +curl_F_octave = np.array(eval(output[2])) + +# Compare the computed results from your implementation with the results from Octave +assert np.allclose(grad_F, grad_F_octave) +assert np.allclose(div_F, div_F_octave) +assert np.allclose(curl_F, curl_F_octave) + +''' \ No newline at end of file From 64b6b06221e5bd9988830bda9dc7b5c98a75fd1f Mon Sep 17 00:00:00 2001 From: Arend Lammertink Date: Thu, 12 Oct 2023 20:41:31 +0200 Subject: [PATCH 03/16] Quite some theory and explanation. --- examples/00-quick-start.ipynb | 172 ++++++++++++++++++++++++++------ examples/01-basic-example.ipynb | 14 +-- fdtd/lgrid.test.py | 40 ++++++++ fdtd/operators.py | 55 ---------- fdtd/operators2.py | 85 ++++++++++++++++ fdtd/project_outline.txt | 92 +++++++++++++++++ 6 files changed, 357 insertions(+), 101 deletions(-) create mode 100644 fdtd/lgrid.test.py create mode 100644 fdtd/operators2.py create mode 100644 fdtd/project_outline.txt diff --git a/examples/00-quick-start.ipynb b/examples/00-quick-start.ipynb index 4fc212c..f1fe540 100644 --- a/examples/00-quick-start.ipynb +++ b/examples/00-quick-start.ipynb @@ -18,7 +18,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -52,7 +52,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -100,9 +100,18 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Grid(shape=(161,97,1), grid_spacing=1.55e-07, courant_number=0.70)\n", + "\n" + ] + } + ], "source": [ "grid = fdtd.Grid(\n", " shape = (25e-6, 15e-6, 1), # 25um x 15um x 1 (grid_spacing) --> 2D FDTD\n", @@ -143,7 +152,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -159,9 +168,19 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Object(name='object')\n", + " @ x=11:32, y=30:84, z=0:1\n", + "\n" + ] + } + ], "source": [ "print(grid.object)" ] @@ -175,7 +194,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -191,9 +210,17 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[Object(name='object'), Object(name=None)]\n" + ] + } + ], "source": [ "print(grid.objects)" ] @@ -237,7 +264,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -255,9 +282,19 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " LineSource(period=14, amplitude=1.0, phase_shift=0.0, name='source')\n", + " @ x=[48, ... , 51], y=[76, ... , 83], z=[0, ... , 0]\n", + "\n" + ] + } + ], "source": [ "print(grid.source)" ] @@ -291,7 +328,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -300,9 +337,19 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " LineDetector(name='detector')\n", + " @ x=[77, ... , 77], y=[0, ... , 96], z=[0, ... , 0]\n", + "\n" + ] + } + ], "source": [ "print(grid.detector)" ] @@ -330,7 +377,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -361,9 +408,42 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Grid(shape=(161,97,1), grid_spacing=1.55e-07, courant_number=0.70)\n", + "\n", + "sources:\n", + " LineSource(period=14, amplitude=1.0, phase_shift=0.0, name='source')\n", + " @ x=[48, ... , 51], y=[76, ... , 83], z=[0, ... , 0]\n", + "\n", + "detectors:\n", + " LineDetector(name='detector')\n", + " @ x=[77, ... , 77], y=[0, ... , 96], z=[0, ... , 0]\n", + "\n", + "boundaries:\n", + " PML(name='pml_xlow')\n", + " @ x=0:10, y=:, z=:\n", + " PML(name='pml_xhigh')\n", + " @ x=-10:, y=:, z=:\n", + " PML(name='pml_ylow')\n", + " @ x=:, y=0:10, z=:\n", + " PML(name='pml_yhigh')\n", + " @ x=:, y=-10:, z=:\n", + "\n", + "objects:\n", + " Object(name='object')\n", + " @ x=11:32, y=30:84, z=0:1\n", + " Object(name=None)\n", + " @ x=84:116, y=32:52, z=0:1\n", + "\n" + ] + } + ], "source": [ "print(grid)" ] @@ -385,9 +465,17 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|████████████████████████████████████████████████████████████████| 100/100 [00:00<00:00, 137.49it/s]\n" + ] + } + ], "source": [ "grid.run(total_time=100)" ] @@ -426,18 +514,36 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAf8AAAHeCAYAAACVAQItAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8qNh9FAAAACXBIWXMAAA9hAAAPYQGoP6dpAADoTklEQVR4nOy9ebgcVZk//jmnqrr77tlIbsKWgGGXzSCD+AWU1S+O8uACAyoIIoqKgIID6LCDgCIDfmXQ30hQh5lxGwZHUAGVVYZFoiyKCpFEIASy3eQu3VXnnN8f57ynTlVXL3cjN8n5PE/ndlfXWaq60593f5lSSsHDw8PDw8NjiwHf2Bvw8PDw8PDweGPhyd/Dw8PDw2MLgyd/Dw8PDw+PLQye/D08PDw8PLYwePL38PDw8PDYwhBu7A14eHh4vNEQQiCO4429DQ+PSUEURQiCoOk5nvw9PDy2GCilsGLFCqxdu3Zjb8XDY1Ixbdo09Pf3gzFW+L4nfw8Pjy0GRPyzZ89GZ2dnwx9GD49NFUopDA0NYeXKlQCAuXPnFp7nyd/Dw2OLgBDCEv/MmTM39nY8PCYNHR0dAICVK1di9uzZhS4AH/Dn4eGxRYB8/J2dnRt5Jx4ekw/6njeKbfHk7+HhsUXBm/o9tgS0+p578vfw8PDYjDB//nxcf/314z7HY/OGJ38PDw+PTQTLly/Hqaeeinnz5qFUKmH77bfHZz/7WaxatWpU8zz22GP4+Mc/PmH78sLEpgcf8Ofh4bFlYdUqIIo29i6yCAKgRRDiCy+8gAMOOAA77bQT/v3f/x0LFizAM888g3PPPRd33XUXHnnkEcyYMaOt5bbaaquJ2LXHJgyv+Xt4eGxZEAJIkqn1EKLltj/1qU+hVCrhF7/4BQ4++GBst912eNe73oV77rkHL730Ei688EJ77vr163HCCSegu7sb8+bNw4033piZK6+pr1u3Dh//+Mcxe/Zs9Pb24p3vfCd+97vfZcbccccdWLRoESqVCmbNmoVjjz0WAHDIIYfgxRdfxNlnnw3GmPU1v/jii/j7v/97TJ8+HV1dXdh9991x5513jvVT85hgePL38PDwmOJYvXo1fv7zn+OMM86waVyE/v5+nHjiifjP//xPKKUAANdeey323HNP/Pa3v8X555+Ps88+G3fffXfh3EopHH300VixYgXuvPNOPPHEE9h3331x6KGHYvXq1QCAn/70pzj22GNx9NFH48knn8S9996LRYsWAQB+/OMfY5tttsGll16KV155Ba+88goALaxUq1Xcf//9eOqpp3D11Veju7t7sm6Rxyjhzf4eHh4eUxx//vOfoZTCrrvuWvj+rrvuijVr1uC1114DABx44IH4x3/8RwDATjvthIceeghf+9rXcPjhh9eN/dWvfoWnnnoKK1euRLlcBgB85Stfwe23344f/vCH+PjHP44rrrgCxx9/PC655BI7bq+99gIAzJgxA0EQoKenB/39/fb9ZcuW4X3vex/e/OY3AwB22GGHCbgTHhMFr/l7eHh4bOIgjZ9M7gcccEDm/QMOOAB/+MMfCsc+8cQT2LBhA2bOnInu7m77WLp0KZ5//nkAwJIlS3DooYeOak9nnnkmLr/8chx44IG46KKL8Pvf/360l+UxidhsyP8b3/gGFixYgEqlgre85S144IEHNvaWPDw8PCYEb3rTm8AYw7PPPlv4/h//+EdMnz4ds2bNajhHo7xvKSXmzp2LJUuWZB7PPfcczj33XACoczW0g4997GN44YUX8OEPfxhPPfUUFi1aVBd74LHxsFmY/f/zP/8TZ511Fr7xjW/gwAMPxM0334x3vetdePbZZ7Hddtu1HC+lxMsvv4yenh5fAMTDYxOHUgrr16/HvHnzwPnmod/MnDkThx9+OL7xjW/g7LPPzpDxihUr8G//9m/4yEc+Yn+/Hnnkkcz4Rx55BLvsskvh3Pvuuy9WrFiBMAwxf/78wnP23HNP3HvvvfjoRz9a+H6pVIIoCFrcdttt8YlPfAKf+MQncP755+Nb3/oWPvOZz7RzyR6TjM2C/K+77jqceuqp+NjHPgYAuP766/Hzn/8cN910E6666qqW419++WVsu+22k71NDw+PNxDLly/HNttss7G3MWH4+te/jre97W048sgjcfnll2dS/bbeemtcccUV9tyHHnoI11xzDY455hjcfffd+MEPfoCf/vSnhfMedthhOOCAA3DMMcfg6quvxs4774yXX34Zd955J4455hgsWrQIF110EQ499FDsuOOOOP7445EkCe666y6cd955AHT2wP3334/jjz8e5XIZs2bNwllnnYV3vetd2GmnnbBmzRr88pe/bBiz4PHGY5Mn/1qthieeeMIGtxCOOOIIPPzww23N0dPTAwC4/Sd3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URThrW99K/7whz+Map358+dnGrrNnTvXmuyff/551Gq1zDXOmDEDO++8c2aOJ598EhdffDGWLFmC1atXQxrr2rJly7DbbrvZ85rd/9deew3Lly/HqaeeitNOO80eT5LEBk+efPLJOPzww7HzzjvjqKOOwrvf/W4cccQRo7pegid/j00ORdojMPmk747Na/ruWBpGne/arTaolDHzK50NECfSlvYNoIv8hBR057gHmK2+l7VUBNDFfzhTCLhCKdQkH1ImgrMvQVkHiUTVWBqESImcMejOgIzZfXBu+ghACyF0b4ou15UBiu5dkSOA2gUzU+d4LK2INye84x3vwE033YQoijBv3jxEUQQAWLp0KQDgpz/9KbbeOlteuVwuZ153dXW1XKdI2G2naFMjdHR0jHoMoLVe8tnvvvvu+N///V9cccUV+OQnP5lWzGyyV8553X7JTeKC7iNBx9hIO18rDA4O4ogjjsARRxyB733ve9hqq62wbNkyHHnkkajVaplzm91/WvNb3/pWxr0BAEGgrXX77rsvli5dirvuugv33HMPPvjBD+Kwww7LxHe0C0/+HpsMGpG+xShI352mHaGBxrpR9Zb0ndOJKNsl/Hx1QEGpe+Zt0vA16TMELK2pb+eS1DOgSEtOG/6QVSB1KQjdCjgWGI4lhms6JmAwSX2xJc7QYWr9qyhAOQSUoij8er8/Ld2wzC+yP6iuBaBYAEgbCHGJtoSpdhFMm4aFDz80IXONZw/toqurC29605vqju+2224ol8tYtmxZxsQ/WvT29mLevHl48MEHrXkdAB5++GG89a1vtWt997vfxfDwsCX1Rx55pOm8e+65J+69915ccsklY94boAlweHgYAPCmN70JpVIJDz74IE444QQAmtgff/xxm3+/1VZbYf369RgcHLSkS7UI2sWb3vQmRFGERx55BNtttx0AYM2aNfjTn/5k7/Uf//hHvP766/jyl7+MbbfdFgDw+OOPj/r65syZg6233hovvPACTjzxxIbn9fb24rjjjsNxxx2H97///TjqqKOwevXqUcU5AJ78PTYRNCX+dkjfGVtE+u0EAaoc8bdv1i/eQ34+NwuAMd1WlxkTf2hIn+aXTlS/ez228Q6l4TEGzgGSJoRp/JMkEsOxwOCITvtbPVLDQBxDKGA4FugpBagEAXpKETqiwLTwhS44ZNL+ImeN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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "grid.visualize(z=0, show=False)\n", "import matplotlib.pyplot as plt" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -451,7 +557,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.8" + "version": "3.9.13" } }, "nbformat": 4, diff --git a/examples/01-basic-example.ipynb b/examples/01-basic-example.ipynb index 26350ef..fdefcca 100644 --- a/examples/01-basic-example.ipynb +++ b/examples/01-basic-example.ipynb @@ -254,21 +254,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "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.8.8" } }, "nbformat": 4, diff --git a/fdtd/lgrid.test.py b/fdtd/lgrid.test.py new file mode 100644 index 0000000..cd4f642 --- /dev/null +++ b/fdtd/lgrid.test.py @@ -0,0 +1,40 @@ +import sys +sys.path.append('/home/arend/work/vscode/fdtd') +import sys +print(sys.path) + + +import numpy as np +import pytest +from fdtd.lgrid import LGrid + +def test_update_C(): + # create a 3x3x3 Yee grid + grid = LGrid((3, 3, 3), (1, 1, 1), 0.5) + + # set the courant number + grid.courant_number = 0.5 + + # set the C field to some initial values + grid.C = np.array([ + [[1, 2, 3], [4, 5, 6], [7, 8, 9]], + [[10, 11, 12], [13, 14, 15], [16, 17, 18]], + [[19, 20, 21], [22, 23, 24], [25, 26, 27]] + ]) + + # call the update_C function + grid.update_C() + + # check that the C field has been updated correctly + assert np.allclose(grid.C, np.array([ + [[-0.125, -0.25, -0.375], [-0.5, -0.625, -0.75], [-0.875, -1.0, -1.125]], + [[-1.25, -1.375, -1.5], [-1.625, -1.75, -1.875], [-2.0, -2.125, -2.25]], + [[-2.375, -2.5, -2.625], [-2.75, -2.875, -3.0], [-3.125, -3.25, -3.375]] + ])) + + +def main(): + test_update_C() + +if __name__ == '__main__': + main() diff --git a/fdtd/operators.py b/fdtd/operators.py index 9118d5b..da08b7e 100644 --- a/fdtd/operators.py +++ b/fdtd/operators.py @@ -95,63 +95,8 @@ distinction between the two curl operators and redefine our vector laplace operator as: -delta[F] = grad(div(F)) - curl_point(curl_surface(F)) - -where curl_point and curl_surface are the curl operators for the left and right hand sides -of the equation, respectively. - -l4m4re: Would it be correct to consider the two curl operators to be left and -right handed, and if yes, please explain? - -GitHub Copilot: Yes, it would be correct to consider the two curl operators to -be left and right handed. In the context of the code you provided, the -left-handed curl operator `curl_point` is used to calculate the curl of the electric -field, while the right-handed curl operator `curl_surface` is used to calculate the -curl of the magnetic field. - -The distinction between left-handed and right-handed curl operators arises from -the fact that the curl of a vector field is a vector that is perpendicular to -the plane of the original vector field. The direction of this vector is -determined by the right-hand rule, which states that if you curl the fingers of -your right hand in the direction of the vector field, then your thumb points in -the direction of the curl vector. - -However, in some cases, it may be more convenient to use a left-handed -coordinate system instead of a right-handed one. In a left-handed coordinate -system, the direction of the curl vector is determined by the left-hand rule, -which is the same as the right-hand rule but with the left hand. - -In the context of the code you provided, the left-handed curl operator `curl_point` -is used to calculate the curl of the electric field because the electric field -is located on the edges of the grid cells, which corresponds to a left-handed -coordinate system. On the other hand, the right-handed curl operator `curl_surface` is -used to calculate the curl of the magnetic field because the magnetic field is -located on the faces of the grid cells, which corresponds to a right-handed -coordinate system. - - -Note that we have two possibilities for the the definition of the vector -laplacian: - - delta[F] = grad(div(F)) - curl_point(curl_surface(F)) - delta[F] = grad(div(F)) - curl_surface(curl_point(F)) -This is because the curl operator is not commutative, meaning that: - - curl_point(curl_surface(F)) != curl_surface(curl_point(F)) - -However, for our Universe, it has been found that the cummuative order of -application of the curl operators along Maxwell's equations provides good -predictions for the electromagnetic fields, so the order of the curl_E and -curl_H functions should be maintained and therefore this definition of the -vector laplacia is the correct one: - - delta[F] = grad(div(F)) - curl_point(curl_surface(F)) - -And, since in the current implementation of delta(F) we use the curl_E and -curl_H functions, we should use the curl_point(curl_surface(F)) definition of the vector -laplacian. ''' diff --git a/fdtd/operators2.py b/fdtd/operators2.py new file mode 100644 index 0000000..4a99914 --- /dev/null +++ b/fdtd/operators2.py @@ -0,0 +1,85 @@ + +'''GitHub Copilot: Sure, here are some possible implementations of the gradient +And curl operators in a staggered Yee grid: + +**Gradient operator:** +''' + +def gradient(field, dx): + """ + Compute the gradient of a scalar field using finite differences. + + Parameters + ---------- + field : ndarray + Scalar field. + dx : float + Grid spacing. + + Returns + ------- + ndarray + Gradient of the scalar field. + """ + grad_x = np.gradient(field, dx, axis=0) + grad_y = np.gradient(field, dx, axis=1) + grad_z = np.gradient(field, dx, axis=2) + return np.stack((grad_x, grad_y, grad_z), axis=-1) + + +''' +**Point curl operator:** +''' + +def curl_point(field, dx): + """ + Compute the point curl of a vector field using finite differences. + + Parameters + ---------- + field : ndarray + Vector field. + dx : float + Grid spacing. + + Returns + ------- + ndarray + Point curl of the vector field. + """ + curl_x = np.gradient(field[..., 2], dx, axis=1) - np.gradient(field[..., 1], dx, axis=2) + curl_y = np.gradient(field[..., 0], dx, axis=2) - np.gradient(field[..., 2], dx, axis=0) + curl_z = np.gradient(field[..., 1], dx, axis=0) - np.gradient(field[..., 0], dx, axis=1) + return np.stack((curl_x, curl_y, curl_z), axis=-1) + +''' +**Surface curl operator:** +''' +def curl_surface(field, dx): + """ + Compute the surface curl of a vector field using finite differences. + + Parameters + ---------- + field : ndarray + Vector field. + dx : float + Grid spacing. + + Returns + ------- + ndarray + Surface curl of the vector field. + """ + curl_x = np.gradient(field[..., 2], dx, axis=1) - np.gradient(field[..., 1], dx, axis=2) + curl_y = np.gradient(field[..., 0], dx, axis=2) - np.gradient(field[..., 2], dx, axis=0) + curl_z = np.gradient(field[..., 1], dx, axis=0) - np.gradient(field[..., 0], dx, axis=1) + return np.stack((curl_x[..., None], curl_y[..., None], curl_z[..., None]), axis=-1) + +''' + +Note that these implementations use the `np.gradient` function from NumPy to +compute the finite differences. The `dx` parameter is the grid spacing, which is +assumed to be constant in a staggered Yee grid implementation. + +''' diff --git a/fdtd/project_outline.txt b/fdtd/project_outline.txt new file mode 100644 index 0000000..c151c32 --- /dev/null +++ b/fdtd/project_outline.txt @@ -0,0 +1,92 @@ + + +The goal of the project is to make a simumulator new superfluid equations, which have similarities with both Maxwell's equations as +well as Navier-Stokes. + +The idea is to refactor an existing FDTD project, which was written for Maxwell's equations, by replacing Maxwell's equations with the new ones. + +Since the grid is one of the most important parts of the simulator, I started refactoring the existing Grid class in a new class named LGrid. + + + + +### Maxwell equations + +An electromagnetic FDTD solver solves the time-dependent Maxwell Equations + +```python + curl(H) = ε*ε0*dE/dt + curl(E) = -µ*µ0*dH/dt +``` + +These two equations are called _Ampere's Law_ and _Faraday's Law_ respectively. + +In these equations, ε and µ are the relative permittivity and permeability +tensors respectively. ε0 and µ0 are the vacuum permittivity and permeability +and their square root can be absorbed into E and H respectively, such that `E := √ε0*E` and `H := √µ0*H`. + +Doing this, the Maxwell equations can be written as update equations: + +```python + E += c*dt*inv(ε)*curl(H) + H -= c*dt*inv(µ)*curl(E) +``` + +The electric and magnetic field can then be discretized on a grid with interlaced Yee-coordinates. + +### New superfluid equations + +The new equations build opon the vector Laplace operator del, the second spatial derivative in 3D, given by: + +```python + del(F) = grad(div(F)) + curl(curl(F)) +``` + +With this operator, we can define the time derivative of any given vector field [F] within the superfluid by: + +d[F]/dt = -k del([F]), + +with k the quantum circulation constant of the superfluid, given by k = h/m, +with h planck's constant and m the mass of a molecule of the superfluid. + +This way, we can write an update equation for any given vector field [F] within the superfluid by: + +```python + F -= k*del(F)*dt +``` + +We can apply this to a superfluid by working with the flow velocity field [v] and define the update equation: + +```python + v -= k*del(v)*dt +``` + +However, in order to have access to the intermediate results, we define intermediate fields by writing out the definition of del: + +```python + C = k*rho*v + P = div(C) + A = curl(C) + L = grad(P) + R = curl(A) + F = R + L +``` + +with rho the mass density in [kg/m^3] and F force density in [N/m^3] + +the update eqiaton then becomes: + +```python + v -= inv(rho)*F +``` + +Please suggest an implementation of the Grid class. + + + + + + + + + From c7593fec2ceba504355d3d8ca00bdf73af82a901 Mon Sep 17 00:00:00 2001 From: Arend Lammertink Date: Sat, 4 May 2024 22:44:50 +0200 Subject: [PATCH 04/16] Quite some corrections --- examples/01-basic-example.ipynb | 2 +- fdtd/constants.py | 2 + fdtd/lgrid.py | 166 +++++++++++++++++++++++--------- fdtd/operators.py | 42 ++++++++ fdtd/project_outline.txt | 23 +++-- 5 files changed, 182 insertions(+), 53 deletions(-) diff --git a/examples/01-basic-example.ipynb b/examples/01-basic-example.ipynb index fdefcca..7e31966 100644 --- a/examples/01-basic-example.ipynb +++ b/examples/01-basic-example.ipynb @@ -162,7 +162,7 @@ "metadata": {}, "outputs": [], "source": [ - "grid[12e-6, :, 0] = fdtd.LineDetector(name=\"detector\")" + "grid[12e-6, :, 0] = fdtd.LineDetector(name=\"detector\")\n" ] }, { diff --git a/fdtd/constants.py b/fdtd/constants.py index a314f87..0864097 100644 --- a/fdtd/constants.py +++ b/fdtd/constants.py @@ -25,3 +25,5 @@ eta0: float = mu0 * c """ free space impedance """ + + diff --git a/fdtd/lgrid.py b/fdtd/lgrid.py index e949953..83ed98f 100644 --- a/fdtd/lgrid.py +++ b/fdtd/lgrid.py @@ -18,15 +18,15 @@ Once these are working and tested, the next step is to implement the sources, boundaries, detectors and objects. These will have to be adapted from their -electromagnetic counterparts to the new theory and have to be consistend with a +electromagnetic counterparts to the new theory and have to be consistent with a staggered Yee grid. The new theory is based on the vector LaPlace operator and the discovery of the quantum circulation constant k, with a value equal to c*c but a unit of -measurement in [m/s^2]. This is a new constant of nature and represents the +measurement in [m^2/s]. This is a new constant of nature and represents the quantum circulation constant, kinematic viscocity or diffusivity of the medium: - k = c*c = 8.987551787368176e+16 [m/s^2]. + k = c*c = 8.987551787368176e+16 [m^2/s]. The fundamental equation of the new theory is the definition of the time derivative of any given vector field within the superfluid medium: @@ -46,7 +46,7 @@ argument to be made against such a simple and straightforward application of the vector Laplacian using only one single Universal constant: k. -The vector LaPlace operator is commonly defined as follows: +The vector LaPlace operator is defined as follows: delta(F) = grad(div(F)) - curl(curl(F)), @@ -57,26 +57,37 @@ curl and one for the right handed curl, so we redefine the vector LaPlace as follows: - delta(F) = grad(div(F)) - curl_l(curl_r(F)), + delta(F) = grad(div(F)) - curl_surface(curl_point(F)), -where curl_l is the left handed curl operator and curl_r is the right handed -curl operator. +where curl_surface is the surface field curl operator and curl_point is the +point field curl operator. + +According to the Yee discretization algorithm, there are inherently two types of +fields on the grid: `E`-type point fields on integer grid locations and `H`-type +surface fields on half-integer grid locations. + +The beauty of these interlaced coordinates is that they enable a very natural +way of writing the curl of the electric and magnetic fields: the curl of an +H-type surface field will be an E-type point field and vice versa. + + This definitiion can be negated: - - delta(F) = - grad(div(F)) + curl_l(curl_r(F)), + - delta(F) = - grad(div(F)) + curl_surface(curl_point(F)), And thus we can compute the time derivative of any given vector field F along: - dF/dt = -k * delta(F) = -k * grad(div(F)) + k * curl_l(curl_r(F)), + dF/dt = -k * delta(F) = -k * grad(div(F)) + k * curl_surface(curl_point(F)), or: - dF/dt = -k * delta(F) = k ( -grad(div(F)) + curl_l(curl_r(F)) ), + dF/dt = -k * delta(F) = k ( -grad(div(F)) + curl_surface(curl_point(F)) ), which can be rewritten to an update equation for F: - F += -k * delta(F) * dt = k ( -grad(div(F)) + curl_l(curl_r(F)) ) * dt, + F += -k * delta(F) * dt = k ( -grad(div(F)) + curl_surface(curl_point(F)) ) + * dt, with dt the time step of the simulation. @@ -85,7 +96,8 @@ flow velocity vector field v, so we can compute it's time derivate, the acceleration vector field a, as follows: - a = dv/dt = -k * delta(v) = k ( -grad(div(v)) + curl_l(curl_r(v)) ). + a = dv/dt = -k * delta(v) = k ( -grad(div(v)) + curl_surface(curl_point(v)) + ). This is known as the velocity diffusion equation and is the fundamental equation of fluid dynamics. @@ -94,20 +106,21 @@ F as follows: F = rho * a = -k * rho * delta(v) = k * rho * (-grad(div(v)) + - curl_l(curl_r(v)) ). + curl_surface(curl_point(v)) ). One of the big benefits of the the fundamental equation of the new theory, defining the time derivative of any given vector field F, is that we can now apply this equation to the acceleration vector field a to compute the time derivative of a, the jerk vector field j: - j = da/dt = -k * delta(a) = k ( -grad(div(a)) + curl_l(curl_r(a)) ). + j = da/dt = -k * delta(a) = k ( -grad(div(a)) + curl_surface(curl_point(a)) + ). And, by applying Y=mj, with Yank the time derivative of Force, we van the 3D yank density vector field Y as follows: Y = rho * j = -k * rho * delta(a) = k * rho * (-grad(div(a)) + - curl_l(curl_r(a)) ). + curl_surface(curl_point(a)) ). Thus, we come to a higher order model incorporating not only acceleration and force density vector fields, but also their time derivatives: the jerk and yank @@ -143,7 +156,7 @@ Now let's consider the negated definition of the vector LaPlace operator for a field C: - - delta(C) = - grad(div(C)) + curl_l(curl_r(C)), + - delta(C) = - grad(div(C)) + curl_surface(curl_point(C)), with C = rho * k * v, resulting in a unit of measurement of: @@ -163,11 +176,11 @@ phi = div(C) - A = curl_r(C) + A = curl_point(C) E = -grad(phi) - B = curl_l(A) + B = curl_surface(A) And because of vector identities we can also write: @@ -229,7 +242,65 @@ Faraday's law of induction is a basic law of electromagnetism predicting how a magnetic field will interact with an electric circuit to produce an electromotive force (EMF), which is thus a law that applies at the macroscopic -level. Because of the inconsistencies in Maxwell's equations with respect to the +level. + +''' +''' + +An interesting detail is how the fields can be characterizeed in a descretized +version of Maxwell's equations, as can be read further down in this document: + +"In electrodynamics, the electric field is located on the edges of the grid +cells, while the magnetic field is located on the faces of the grid cells. This +is because the electric field is a vector field that is defined at a point in +space, while the magnetic field is a vector field that is defined over an area." + + +This illustrates that geometry with respect to definition of our operators +matters, and since this is correctly implemented in the discretized alrogirims +in this simulator, we should realize that the infenitesimal fields can also +be fundamentally distinguished by fields defined at a point in space and fields +defined over an area. + +phi is a point field, while A is a surface field, and E is a point field, while +B is a surface field. This is a very important distinction, because it reveals +that the electric and magnetic fields are fundamentally different types of +vector fields. + +Based on this, what can we conclude about this equation because of the mixture of +point fields defined at a point in space and surface fields defined over an area? + + curl(E) = -dB/dt. + +@copilot: What are the rules with regards to addition of point and surface fields +and what does this mean for this equation? + +GitHub Copilot: In a staggered Yee grid, the fields are located at either integer +or half-integer grid points, whereby the integer grid points represent vector +fields defined at a point in space, while the half-integer grid points represent +vector fields defined over an area. This means that the electric field E is +located at integer grid points and the magnetic field B is located at +half-integer grid points. + +In the equation curl(E) = -dB/dt, the curl of the electric field E is a point +field located at integer grid points, while the time derivative of the magnetic +field B is a surface field located at half-integer grid points. This means that +the two fields are defined at different locations on the grid, which is why the +equation is not mathematically consistent. + +l4m4re: So, what can we conclude from this? + +GitHub Copilot: The equation curl(E) = -dB/dt is not mathematically consistent +because it mixes point fields defined at a point in space with surface fields +defined over an area. In order to make the equation mathematically consistent, +the fields should be defined at the same location on the grid. + + +''' +''' + + +Because of the inconsistencies in Maxwell's equations with respect to the fundamental theorem of vector calculus, it is now clear that this entanglement of the fundamental E and B fields in Maxwell's equations is responsible for a lot of problems in current main stream physics, for starters the non-invariance @@ -297,12 +368,12 @@ Note that because of the vector identity curl(grad(p))=0, addition of a vector field whose curl vanishes to the vector potential has no effect whatsoever on the resulting field, which begs the question of why the addition of a vector -field that has no effect on the resulting field, is supposed to describe +field that has no effect on the resulting field, is supposed to descri`be something that has any relevance to the real world at all. Or, to put it another way: how exactly is the Standard Model, which is a gauge theory and therefore uses fields that have no effect on the resulting physical -fields, is supposed to have any relevance to the real world at all? +fields, supposed to have any relevance to the real world at all? Thus, we can conclude that Maxwell's rather strange entanglement of the electric and magnetic fields has led to a lot of problems in physics, to such an extent @@ -320,7 +391,7 @@ So let's consider the negated definition of the vector LaPlace operator for a field C again: - - delta(C) = - grad(div(C)) + curl_l(curl_r(C)), + - delta(C) = - grad(div(C)) + curl_surface(curl_point(C)), with C = rho * k * v, resulting in a unit of measurement of: @@ -331,11 +402,11 @@ p = div(C) - A = curl_r(C) + A = curl_point(C) L = -grad(p) - R = curl_l(A) + R = curl_surface(A) And because of vector identities we can also write: @@ -367,11 +438,11 @@ Now let's consider the negated definition of the vector LaPlace operator for a field I: - - delta(I) = - grad(div(I)) + curl_l(curl_r(I)), + - delta(I) = - grad(div(I)) + curl_surface(curl_point(I)), with I = rho * k * a, resulting in a unit of measurement of: - [C] = [rho] * [k] * [a] = [kg/m^3] * [m^2/s] * [m/s^2] = [kg/s^3]. + [I] = [rho] * [k] * [a] = [kg/m^3] * [m^2/s] * [m/s^2] = [kg/s^3]. Thus, I represents the Intensity or radiosity field in [kg/s^3], [N/m-s], [J/m^2-s], [Pa-m/s] or [W/m^2]. @@ -380,11 +451,11 @@ t = div(I) - W = curl_r(I) + W = curl_point(I) Y_l = -grad(t) - Y_a = curl_l(W) + Y_a = curl_surface(W) And because of vector identities we can also write: @@ -425,17 +496,24 @@ our theory be equal to [kg/s-m^3]. eta = 1/mu = 1/4*pi*1e-7 = 8.987551787368176e+16 [kg/m-s2] rho = eta/k = -8.987551787368176e+16 / 8.987551787368176e+16 = 1 [kg/m3] +8.987551787368176e+16 [kg/m3] + +W in [kg/m-s^3] or [Pa/s] = [W/m^3] = [N/m^2-s] J in [kg/m^3-s] or [A/m^3] + +J = W / c^2 ?? + +Dimensons match: + +[kg/m^3-s] = [kg/m-s^3] / [m^2/s^2] + + = [kg/m-s^3] * [s^2/m^2] + + = [kg/m^3-s^3] * [s^2] + + = [kg/m^3-s] -W in [kg/m-s^3] or [Pa/s] = [W/m^3] = [N/m^2-s] J in [kg/s-m^3] or [A] - [kg/s-m^3] = X [kg/m-s^3] - - [m^2] = X [/s^2] - - x = [m^2-s^2] - So far, we have made a number of references to superfluids, without explaining @@ -665,12 +743,12 @@ integer grid point as is the input scalar field. For the curl operator, we found that we needed two different curl operators, -namely the left and right curl operators. The curl operator is defined as the +namely the point and surface curl operators. The curl operator is defined as the line integral of the tangential component of the vector field over a closed path around some point in space, divided by the area enclosed by the path along Stokes' theorem, resulting in a unit of per meter [1/m] for the operator. And since this line integral can be computed clockwise or counter-clockwise, we -obtain the left and right curl operators needed in the implementation of the +obtain the point and surface curl operators needed in the implementation of the staggered Yee grid. @@ -681,7 +759,7 @@ the difference of the scalar field can be computed between six points at a certain distance from the point along the three coordinate axes (+ and -). -Similarly, the left and right curl operators can be computed by taking the +Similarly, the point and surface curl operators can be computed by taking the difference of the vector field components at neigboring faces along the three coordinate axes (+ and -). Since the grid spacing is constant, the resulting value is simply the difference between the vector field components at the two @@ -845,7 +923,7 @@ from . import constants as const #import operators: -from .operators import curl_l, curl_r, grad, div +from .operators import curl_surface, curl_point, grad, div @@ -1101,20 +1179,20 @@ def update_C(self): # the faces of the grid and thus be a H-type field. self.p = div (self.C) - self.A = curl_l(self.C) + self.A = curl_surface(self.C) self.L = - grad(self.P) - self.R = curl_r(self.A) + self.R = curl_point(self.A) self.F = self.L + self.R self.I = self.k * self.F self.t = div(self.I) - self.W = curl_l(self.I) + self.W = curl_surface(self.I) self.Y_l = -grad(self.t) - self.Y_a = curl_r(self.W) + self.Y_a = curl_point(self.W) self.Y = self.Y_l + self.Y_r diff --git a/fdtd/operators.py b/fdtd/operators.py index da08b7e..0f37f2d 100644 --- a/fdtd/operators.py +++ b/fdtd/operators.py @@ -963,6 +963,24 @@ def curl_surface(F: Tensorlike) -> Tensorlike: return curl +''' + +Note that curl_surface can only work on curl_point, so +we can define a curl_curl bij + +curl_curl(F) = curl_surface(curl_point(F)) + +In other words: curl_surface is ALWAYS the curl of the curl! + +Thus, both operators MUST be applied in this particular manner. + + + +''' + + + + ''' We can then compute `curl_surface(curl_point(v))` for a point vector field `v` @@ -1013,6 +1031,29 @@ def div(vf: Tensorlike) -> Tensorlike: rather at the center of the cube, which is a point in space represented by an integer grid point in a staggered Yee grid. + + Note that the resulting field is a scalar field, as the divergence is a + scalar quantity that represents the rate at which a vector field expands or + contracts at a given point in space. This does not have a direction, but is + rather a quantity. + + This yields the question of how we shouldt interpret Maxwells equation for + ampere's law: + + curl E = -dB/dt. + + and: + + E = - grad Phi - d[A]/dt. + + + + + + + + + GitHub Copilot: The `div` function computes the divergence of a velocity field `vf` located on the faces of the grid cells. The input velocity field `vf` should be a 4D array with dimensions `(Nx+1, Ny+1, Nz+1, 3)` to be @@ -1245,6 +1286,7 @@ def delta_C(C: Tensorlike) -> Tensorlike: + def delta_I(I: Tensorlike) -> Tensorlike: """ Compute the vector Laplace operator of a vector field F located on the faces of the grid diff --git a/fdtd/project_outline.txt b/fdtd/project_outline.txt index c151c32..47376e2 100644 --- a/fdtd/project_outline.txt +++ b/fdtd/project_outline.txt @@ -42,32 +42,39 @@ The new equations build opon the vector Laplace operator del, the second spatial del(F) = grad(div(F)) + curl(curl(F)) ``` -With this operator, we can define the time derivative of any given vector field [F] within the superfluid by: +With this operator, we can define the time derivative of any given vector field +[F] within the superfluid by: d[F]/dt = -k del([F]), with k the quantum circulation constant of the superfluid, given by k = h/m, with h planck's constant and m the mass of a molecule of the superfluid. -This way, we can write an update equation for any given vector field [F] within the superfluid by: +This way, we can write an update equation for any given vector field [F] within +the superfluid by: ```python - F -= k*del(F)*dt + F += k*ndel(F)*dt, + + with ndel the negative of the vector Laplace operator. ``` -We can apply this to a superfluid by working with the flow velocity field [v] and define the update equation: +We can apply this to a superfluid by working with the flow velocity field [v] +and define the update equation: ```python - v -= k*del(v)*dt + v += k*ndel(v)*dt ``` -However, in order to have access to the intermediate results, we define intermediate fields by writing out the definition of del: +However, in order to have access to the intermediate results, we define +intermediate fields by writing out the definition of ndel, wherein we take the +negative of the gradient: ```python C = k*rho*v - P = div(C) + p = div(C) A = curl(C) - L = grad(P) + L = - grad(p) R = curl(A) F = R + L ``` From ff969e06850a9d6977781f703e23cb90332cf08b Mon Sep 17 00:00:00 2001 From: Arend Lammertink Date: Sat, 6 Jul 2024 13:34:46 +0200 Subject: [PATCH 05/16] Big clean-up and rework using ChatGPT. --- fdtd/operators.py | 1266 ++++----------------------------------------- 1 file changed, 112 insertions(+), 1154 deletions(-) diff --git a/fdtd/operators.py b/fdtd/operators.py index 0f37f2d..07a4b18 100644 --- a/fdtd/operators.py +++ b/fdtd/operators.py @@ -1,1105 +1,147 @@ -''' -This file contains the various operators used in the FDTD grid classes. - -The operators are: grad, div and curl. - -GitHub Copilot: The `grad`, `div`, and `curl` functions are implemented in the -`operators.py` file in the FDTD library. These functions are used to calculate -the gradient, divergence, and curl of a given field, respectively. - -Here is a brief explanation of how each function is implemented: - -1. `grad`: This function calculates the gradient of a given field. The gradient - is a vector that points in the direction of the maximum rate of change of the - field. In the FDTD library, the gradient is calculated using the central - difference method. This method involves calculating the difference between - the field values at neighboring grid points in each dimension. The resulting - gradient vector is then normalized to have a magnitude of 1. - -2. `div`: This function calculates the divergence of a given field. The - divergence is a scalar that represents the amount of "outwardness" of the - field at a given point. In the FDTD library, the divergence is calculated - using the central difference method. This method involves calculating the - difference between the field values at neighboring grid points in each - dimension and summing the resulting values. - -3. `curl`: This function calculates the curl of a given field. The curl is a - vector that represents the amount of rotation of the field at a given point. - In the FDTD library, the curl is calculated using the central difference - method. This method involves calculating the difference between the field - values at neighboring grid points in each dimension and taking the cross - product of the resulting vectors. - -Overall, these functions are essential for calculating the behavior of -electromagnetic waves and fluid flow in FDTD simulations. - - -GitHub Copilot: Yes, that's correct! As long as you use the correct -implementations of the gradient, divergence, and curl functions for the -staggered grid with interlaced Yee-coordinates, you do not have to worry about -the specific locations of the fields on the grid. The `grad_p`, `div_vf`, -`curl_E`, and `curl_H` functions that we have discussed are designed to work -with the staggered grid used in electromagnetic and fluid dynamics simulations, -and they take care of the interpolation and averaging of the fields to the -correct locations on the grid. - - -GitHub Copilot: In FDTD simulations, the electric and magnetic fields are -discretized on a grid with interlaced Yee-coordinates. This means that the -electric and magnetic fields are located at different points on the grid, and -they are staggered in such a way that the electric field is located on the edges -of the grid cells, while the magnetic field is located on the faces of the grid -cells. - -Because of this staggered arrangement, the curl of the electric field and the -curl of the magnetic field are calculated differently. The curl of the electric -field is calculated using the central difference method, which involves -calculating the difference between the electric field values at neighboring grid -points in each dimension and taking the cross product of the resulting vectors. -On the other hand, the curl of the magnetic field is calculated using the -backward difference method, which involves calculating the difference between -the magnetic field values at neighboring grid points in each dimension and -dividing the resulting vector by the grid spacing. - -In the FDTD library, there are two different curl functions: `curl_E` and -`curl_H`. The `curl_E` function calculates the curl of the electric field, while -the `curl_H` function calculates the curl of the magnetic field. These functions -are designed to work with the staggered grid used in electromagnetic -simulations, and they take care of the interpolation and averaging of the fields -to the correct locations on the grid. - -Overall, the two different curl functions are necessary because of the staggered -arrangement of the electric and magnetic fields on the grid, and they are -implemented differently because of the different field operations involved. - - -This, however, our model is not an ordinary fluid dynamics model, nor is it a -rather ordinary electromagnetic model. It is a saperfluid dynamics model, -derived from a single equation: - - d[F]/dt = -k * delta[F]. - - with F any given vector field within the superfluid model. - -delta[F] is the vector laplace operator of F, which is defined as: - - delta[F] = grad(div(F)) - curl(curl(F)) - -with grad the gradient operator, div the divergence operator and curl the curl -operator. - -However, given the staggered grid with interlaced Yee-coordinates, where it was -necessary to implement two different curl functions, representing two different -specific fields on specific locations on the grid, we will have to make a -distinction between the two curl operators and redefine our vector laplace -operator as: - - delta[F] = grad(div(F)) - curl_surface(curl_point(F)) - - -''' - ## Imports -# standard library -import os -from os import path, makedirs, chdir, remove -from subprocess import check_call, CalledProcessError -from glob import glob -from datetime import datetime - -# 3rd party -from tqdm import tqdm -from numpy import savez - # typing from .typing_ import Tuple, Number, Tensorlike # relative from .backend import backend as bd from . import constants as const -''' -from math import * - -# some constants -c = 299792458.0 # [m/s] - speed of light -c_l = c * (pi/2) # [m/s] - speed of longitudinal (sound) waves -mu = 4*pi*1e-7 # mu_0 - 1/viscosity -#mu = 4*pi*1.00000000082e-7# mu_0 - 1/viscosity - 2019 redefinition -eta = 1/(4*pi*1e-7) # viscosity [Pa-s],[kg/m-s], [N-s/m2], [J-s/m3] -h = 6.62607015e-34 # Planck's constant [J-s], [kg/m2-s] -k = c*c # dynamic viscosity [m2/s] -rho = eta/k # 8.854187817620389e-12 -q = 1.602176634e-19 # elemental charge -m = h/k # elemental mass -''' - -''' -First, some context on Yee grids from Copilot: - -In a electromagnetic FDTD simulation, the electric and magnetic fields are -discretized on a grid with interlaced Yee-coordinates. This means that the -electric and magnetic fields are located at different points on the grid, and -they are staggered in such a way that the electric field is located on the edges -of the grid cells, while the magnetic field is located on the faces of the grid -cells. - -The Yee grid is named after Kane S. Yee, who first proposed this grid in 1966. -The Yee grid is a staggered grid that is designed to accurately model the -behavior of electromagnetic waves. The Yee grid is widely used in FDTD -simulations because it is simple to implement and provides accurate results. - -In the Yee grid, the electric and magnetic fields are located at different -points on the grid. Specifically, the electric field is located on the edges of -the grid cells, while the magnetic field is located on the faces of the grid -cells. This staggered arrangement ensures that the electric and magnetic fields -are updated at different times during the simulation, which is necessary to -accurately model the behavior of electromagnetic waves. - -Overall, the interlaced Yee-coordinates are a critical part of the FDTD -simulation process, as they allow the electric and magnetic fields to be -discretized on a grid that accurately models the behavior of electromagnetic -waves. By understanding how the Yee grid works and how it is used in the -simulation, developers can gain a deeper understanding of how the FDTD Library -works and how to use it to simulate electromagnetic waves. - - -Yee grids are primarily used in computational electromagnetics to discretize the -Maxwell's equations and simulate electromagnetic waves. - -However, Yee grids can also be used in fluid dynamics to discretize the -Navier-Stokes equations and simulate fluid flow. - -In fluid dynamics, the Navier-Stokes equations describe the motion of fluids and -can be used to simulate a wide range of physical phenomena, such as fluid flow -around objects, turbulence, and mixing. The Navier-Stokes equations are a set of -partial differential equations that describe the conservation of mass, momentum, -and energy in a fluid. - -To simulate fluid flow using the Navier-Stokes equations, the equations must be -discretized on a grid. Yee grids can be used to discretize the Navier-Stokes -equations in a similar way to how they are used to discretize the Maxwell's -equations in computational electromagnetics. Specifically, the fluid velocity -and pressure can be discretized on a staggered grid with interlaced -Yee-coordinates, similar to how the electric and magnetic fields are discretized -in the FDTD simulation. - -By discretizing the Navier-Stokes equations on a Yee grid, the equations can be -solved numerically using finite-difference methods, finite-volume methods, or -other numerical techniques. This allows researchers to simulate fluid flow and -study the behavior of fluids in a wide range of physical scenarios. - -Overall, Yee grids can be used in fluid dynamics to discretize the Navier-Stokes -equations and simulate fluid flow. By using Yee grids in this way, researchers -can gain a deeper understanding of fluid dynamics and develop new technologies -that rely on fluid flow, such as aircraft, ships, and turbines. - -A staggered grid with interlaced Yee-coordinates is a type of grid -discretization commonly used in FDTD simulations. In this type of grid, the -electric and magnetic fields are located on the faces of the grid cells, while -the current and charge densities are located on the grid points. This -arrangement is known as a "staggered" grid because the field components are -staggered with respect to the grid points. - -The Yee-coordinate system is a specific type of staggered grid where the -electric and magnetic fields are located on the faces of the grid cells, but -they are interlaced in a specific way. In the Yee-coordinate system, the -electric field components are located on the faces of the grid cells that are -perpendicular to the x, y, and z axes, while the magnetic field components are -located on the faces of the grid cells that are parallel to the x, y, and z -axes. This interlacing of the field components ensures that the update equations -for the electric and magnetic fields are consistent with each other and with the -underlying physics. - -The Yee-coordinate system is named after Kane S. Yee, who first proposed this -type of grid discretization in 1966. Since then, the Yee-coordinate system has -become a standard in FDTD simulations and is widely used in many different -applications, including electromagnetic wave propagation, antenna design, and -photonics. - - -In fluid dynamics, a staggered grid with interlaced Yee-coordinates is a type of -grid discretization that can be used to simulate fluid flow using the -Navier-Stokes equations. In this type of grid, the fluid velocity components are -located on the faces of the grid cells that are perpendicular to the x, y, and z -axes, while the pressure is located on the grid points. This arrangement is -similar to the Yee grid used in FDTD simulations, where the electric and -magnetic fields are located on the faces of the grid cells and the current and -charge densities are located on the grid points. - -By discretizing the Navier-Stokes equations on a staggered grid with interlaced -Yee-coordinates, the equations can be solved numerically using finite-difference -methods, finite-volume methods, or other numerical techniques. This allows -researchers to simulate fluid flow and study the behavior of fluids in a wide -range of physical scenarios. - -The staggered grid with interlaced Yee-coordinates ensures that the update -equations for the fluid velocity and pressure are consistent with each other and -with the underlying physics. This type of grid discretization is widely used in -fluid dynamics to simulate fluid flow around objects, turbulence, and mixing. By -using this type of grid, researchers can gain a deeper understanding of fluid -dynamics and develop new technologies that rely on fluid flow, such as aircraft, -ships, and turbines. - - - - -Let me summarize what I understood from what you said: - -A staggered grid with interlaced Yee-coordinates is a type of grid -discretization commonly used in FDTD simulations. - -In fluid dynamics, a staggered grid with interlaced Yee-coordinates is a type of -grid discretization that can be used to simulate fluid flow using the -Navier-Stokes equations. In this type of grid, the fluid velocity components are -located on the faces of the grid cells that are perpendicular to the x, y, and z -axes, while the pressure is located on the grid points. - -In electrodynamics, a staggered grid with interlaced Yee-coordinates is a type -of grid discretization that can be used to simulate the electromagnetic fields -using Maxwell's equations. In this type of grid, the electric and magnetic -fields are located on the faces of the grid cells, while the current and charge -densities are located on the grid points. This arrangement is known as a -"staggered" grid because the field components are staggered with respect to the -grid points. - -In the electrodynamics Yee-coordinate system, the electric field components are -located on the faces of the grid cells that are perpendicular to the x, y, and z -axes, while the magnetic field components are located on the faces of the grid -cells that are parallel to the x, y, and z axes. - -Do I understand correctly that with interlaced Yee-coordinates vector fields are -always located on the faces of the grid cells, either on the faces that are -parallel to the x, y, and z axes or on the faces that are perpendicular to the -x, y, and z axes? - -Do I also understand correctly that with interlaced Yee-coordinates scalar -fields are always located on the grid points? - - -GitHub Copilot: Yes, you have understood correctly. - -In both electrodynamics and fluid dynamics, a staggered grid with interlaced -Yee-coordinates is a type of grid discretization where the vector fields are -located on the faces of the grid cells, either on the faces that are parallel to -the x, y, and z axes or on the faces that are perpendicular to the x, y, and z -axes. This arrangement is known as a "staggered" grid because the field -components are staggered with respect to the grid points. - -In contrast, scalar fields are always located on the grid points in a staggered -grid with interlaced Yee-coordinates. This is because the scalar fields are -associated with the physical quantities that are defined at the grid points, -such as the pressure in fluid dynamics or the charge density in electrodynamics. - -''' - -''' - -In the grid class, which is to use the operators, there are two types of fields, -namely sclar fields such as: - - self.P = bd.zeros((self.Nx, self.Ny, self.Nz, 1)) - -And vector fields such as: - - self.C = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - -Each of these fields can be located at either the faces or the edges of the grid -cells, meaning at either integer or half-integer grid points. - -This is why we need multiple versions of the grid, div and curl operators. - -The first operators we need are the curl and the gradient of a velocity field, -which is a vector field, which should be located on the faces of the grid cells. - - - - -Is it correct that in the curl_E and curl_H functions for a staggered grid with -interlaced Yee-coordinates the E and H fields are both located on the faces of -the grid, with the only difference being the faces perpendicular to the x, y, -and z axes for the E field, while for the H field they are the faces parallel to -the x, y and z axes? Thus, are both fields actually located on integer grid -points? - - -GitHub Copilot: Yes, that's correct! - -In the staggered grid with interlaced Yee-coordinates used in electromagnetic -simulations, the electric field `E` and magnetic field `H` are both located on -the faces of the grid cells. The only difference is that the electric field `E` -is located on the faces of the grid cells that are perpendicular to the x, y, -and z axes, while the magnetic field `H` is located on the faces of the grid -cells that are parallel to the x, y, and z axes. - -Both fields are located on half-integer grid points, which means that their -components are located halfway between the grid points. However, when we -interpolate the fields to the grid points, we use the values of the fields at -the nearest faces of the grid cells to the grid points. This means that the -electric field `E` is located on the edges of the grid cells that are -perpendicular to the x, y, and z axes, while the magnetic field `H` is located -on the faces of the grid cells that are parallel to the x, y, and z axes. - -Therefore, when we compute the curl of the electric field `E` using the `curl_E` -function, the resulting magnetic field `H` is located on the faces of the grid -cells that are parallel to the x, y, and z axes, which are integer grid points. -Similarly, when we compute the curl of the magnetic field `H` using the `curl_H` -function, the resulting electric field `E` is located on the edges of the grid -cells that are perpendicular to the x, y, and z axes, which are also integer -grid points. - - - -l4m4re: Do I understand correctly that we do not have to worry about where the -various fields are located on the grids, provided we use the correct -implementations of grad, div and the two curl functions? - -GitHub Copilot: Yes, that's correct! As long as you use the correct -implementations of the gradient, divergence, and curl functions for the -staggered grid with interlaced Yee-coordinates, you do not have to worry about -the specific locations of the fields on the grid. The `grad_p`, `div_vf`, -`curl_E`, and `curl_H` functions that we have discussed are designed to work -with the staggered grid used in electromagnetic and fluid dynamics simulations, -and they take care of the interpolation and averaging of the fields to the -correct locations on the grid. - -However, it is important to note that the input vector field `F` that you pass -to these functions must be located on the faces of the grid cells that are -perpendicular to the x, y, and z axes for fluid dynamics, or on the faces of the -grid cells that are perpendicular to the time axis for electromagnetic -simulations. If the input field is not located on the correct faces of the grid -cells, the results of the gradient, divergence, and curl operations may not be -correct. - -Thus, if I pass a fluid dynamics velocity field in the shape of a 4D array ith -dimensions (Nx+1, Ny+1, Nz+1, 3) as F to the delta(F) funtion we designed, it -should correctly compute the delta vector laplace operator? - -GitHub Copilot: Yes, that's correct! If you pass a fluid dynamics velocity field -in the shape of a 4D array with dimensions `(Nx+1, Ny+1, Nz+1, 3)` as `F` to the -`delta` function we designed, it should correctly compute the vector Laplace -operator `delta` of the field. - -The `delta` function takes a vector field `F` as input and computes the vector -Laplace operator of the field using the `grad_p`, `div_vf`, `curl_E`, and -`curl_H` functions that are designed to work with the staggered grid used in -fluid dynamics simulations. The output vector field `delta_F` is located on the -faces of the grid cells that are perpendicular to the x, y, and z axes, which is -consistent with the staggered grid used in fluid dynamics. - -Therefore, if you pass a fluid dynamics velocity field in the shape of a 4D -array with dimensions `(Nx+1, Ny+1, Nz+1, 3)` as `F` to the `delta` function, it -should correctly compute the vector Laplace operator of the field, regardless of -the specific locations of the field components on the grid. - - -Now let's refer to the original readme file of the fdtd library for more details -and edit a bit: - --+- - -According to the Yee discretization algorithm, there are inherently two types of -fields on the grid: `E`-type point fields on integer grid locations and `H`-type -surface fields on half-integer grid locations. - -The beauty of these interlaced coordinates is that they enable a very natural -way of writing the curl of the electric and magnetic fields: the curl of an -H-type surface field will be an E-type point field and vice versa. - -This way, the curl of E can be written as - -```python - curl(E)[m,n,p] = (dEz/dy - dEy/dz, dEx/dz - dEz/dx, dEy/dx - dEx/dy)[m,n,p] - =( ((Ez[m,n+1,p]-Ez[m,n,p])/dy - (Ey[m,n,p+1]-Ey[m,n,p])/dz), - ((Ex[m,n,p+1]-Ex[m,n,p])/dz - (Ez[m+1,n,p]-Ez[m,n,p])/dx), - ((Ey[m+1,n,p]-Ey[m,n,p])/dx - (Ex[m,n+1,p]-Ex[m,n,p])/dy) - ) - =(1/du)*( ((Ez[m,n+1,p]-Ez[m,n,p]) - - (Ey[m,n,p+1]-Ey[m,n,p])), [assume dx=dy=dz=du] - ((Ex[m,n,p+1]-Ex[m,n,p]) - - (Ez[m+1,n,p]-Ez[m,n,p])), ((Ey[m+1,n,p]-Ey[m,n,p]) - - (Ex[m,n+1,p]-Ex[m,n,p])) ) - -``` - -this can be written efficiently with array slices (note that the factor `(1/du)` -was left out): - -```python def curl_E(E): - curl_E = np.zeros(E.shape) curl_E[:,:-1,:,0] += E[:,1:,:,2] - E[:,:-1,:,2] - curl_E[:,:,:-1,0] -= E[:,:,1:,1] - E[:,:,:-1,1] - - curl_E[:,:,:-1,1] += E[:,:,1:,0] - E[:,:,:-1,0] curl_E[:-1,:,:,1] -= - E[1:,:,:,2] - E[:-1,:,:,2] - - curl_E[:-1,:,:,2] += E[1:,:,:,1] - E[:-1,:,:,1] curl_E[:,:-1,:,2] -= - E[:,1:,:,0] - E[:,:-1,:,0] return curl_E -``` - - -The curl for H can be obtained in a similar way (note again that the factor -`(1/du)` was left out): - -```python def curl_H(H): - curl_H = np.zeros(H.shape) - - curl_H[:,1:,:,0] += H[:,1:,:,2] - H[:,:-1,:,2] curl_H[:,:,1:,0] -= - H[:,:,1:,1] - H[:,:,:-1,1] - - curl_H[:,:,1:,1] += H[:,:,1:,0] - H[:,:,:-1,0] curl_H[1:,:,:,1] -= - H[1:,:,:,2] - H[:-1,:,:,2] - - curl_H[1:,:,:,2] += H[1:,:,:,1] - H[:-1,:,:,1] curl_H[:,1:,:,2] -= - H[:,1:,:,0] - H[:,:-1,:,0] return curl_H -``` - - -The update equations can now be rewritten as - -```python - E += (c*dt/du)*inv(ε)*curl_H H -= (c*dt/du)*inv(µ)*curl_E -``` - -The number `(c*dt/du)` is a dimensionless parameter called the _Courant number_ -`sc`. For stability reasons, the Courant number should always be smaller than -`1/√D`, with `D` the dimension of the simulation. This can be intuitively be -understood as the condition that information should always travel slower than -the speed of light through the grid. In the FDTD method described here, -information can only travel to the neighboring grid cells (through application -of the curl). It would therefore take `D` time steps to travel over the diagonal -of a `D`-dimensional cube (square in `2D`, cube in `3D`), the Courant condition -follows then automatically from the fact that the length of this diagonal is -`1/√D`. - -This yields the final update equations for the FDTD algorithm: - -```python - E += sc*inv(ε)*curl_H H -= sc*inv(µ)*curl_E -``` - --+- - -The fist thing we note is : "[assume dx=dy=dz=du]", which means that the grid -spacing is the same in all three dimensions. Further, we note that the factor -1/du is left out in the implementation of the curl functions, because it can be -taken outside the parentheses if dx=dy=dz=du as shown in the simplified -definition for curl_E: - -curl(E)[m,n,p] =(1/du)*( ((Ez[m,n+1,p]-Ez[m,n,p]) - (Ey[m,n,p+1]-Ey[m,n,p])), - ((Ex[m,n,p+1]-Ex[m,n,p]) - (Ez[m+1,n,p]-Ez[m,n,p])), - ((Ey[m+1,n,p]-Ey[m,n,p]) - (Ex[m,n+1,p]-Ex[m,n,p])) ) - -This way, the implementation of curl_E and curl_H can be simplified, but they -actually compute the curl apart from this factor (1/du), which is a very -important detail, because it means that the curl functions are not actually -computing the curl, but the curl multiplied by the grid spacing, which is -formally incorrect, but works in practice since the factor (1/du) is applied -afterwards in the update function, because the factor (1/du) is a part of the -courant number sc, which is applied in the update function. - -Thus, if this is correct: - -"According to the Yee discretization algorithm, there are inherently two types -of fields on the grid: `E`-type point fields on integer grid locations and -`H`-type surface fields on half-integer grid locations." - -The actual update statements are: - - curl = curl_H(self.H) self.E += self.courant_number * - self.inverse_permittivity * curl - - curl = curl_E(self.E) self.H -= self.courant_number * - self.inverse_permeability * curl - -Then the curl_E function should take a E-type point field as input and return a -H-type surface field as output, while the curl_H function should take a H-type -surface field as input and return a E-type point field as output. - -However, this is not the case, because the curl_E function takes a H-type -surface field as input and returns a E-type point field as output, while the -curl_H function takes a E-type point field as input and returns a H-type surface -field as output. - -So, the curl_E function should be called curl_H and the curl_H function should -be called curl_E, because they are the wrong way around. -For reference, we include previous implementations of the curl functions: - +## Functions def curl_E(E: Tensorlike) -> Tensorlike: """Transforms an E-type field into an H-type field by performing a curl operation Args: + E: Electric field to take the curl of (E-type field located on the edges of the grid cell [integer gridpoints]) Returns: + The curl of E (H-type field located on the faces of the grid [half-integer grid points]) - """ curl = bd.zeros(E.shape) + """ + curl = bd.zeros(E.shape) - curl[:, :-1, :, 0] += E[:, 1:, :, 2] - E[:, :-1, :, 2] curl[:, :, :-1, 0] -= - E[:, :, 1:, 1] - E[:, :, :-1, 1] + curl[:, :-1, :, 0] += E[:, 1:, :, 2] - E[:, :-1, :, 2] + curl[:, :, :-1, 0] -= E[:, :, 1:, 1] - E[:, :, :-1, 1] - curl[:, :, :-1, 1] += E[:, :, 1:, 0] - E[:, :, :-1, 0] curl[:-1, :, :, 1] -= - E[1:, :, :, 2] - E[:-1, :, :, 2] + curl[:, :, :-1, 1] += E[:, :, 1:, 0] - E[:, :, :-1, 0] + curl[:-1, :, :, 1] -= E[1:, :, :, 2] - E[:-1, :, :, 2] - curl[:-1, :, :, 2] += E[1:, :, :, 1] - E[:-1, :, :, 1] curl[:, :-1, :, 2] -= - E[:, 1:, :, 0] - E[:, :-1, :, 0] + curl[:-1, :, :, 2] += E[1:, :, :, 1] - E[:-1, :, :, 1] + curl[:, :-1, :, 2] -= E[:, 1:, :, 0] - E[:, :-1, :, 0] return curl -def curl_H(H: Tensorlike) -> Tensorlike: - """Transforms an H-type field into an E-type field by performing a curl - operation +def curl_edge_to_face(v: Tensorlike) -> Tensorlike: + """ + Compute the curl of a vector field where the vector components are defined + on the edges of the grid cells. The result is a vector field defined on the + faces of the grid cells. - Args: - H: Magnetic field to take the curl of (H-type field located on - half-integer grid points) + Parameters: + + v (ndarray): Input vector field with shape (Nx, Ny, Nz, 3), where each + component (vx, vy, vz) is defined on the edges of the grid cells. Returns: - The curl of H (E-type field located on the edges of the grid [integer - grid points]) - - """ curl = bd.zeros(H.shape) - - curl[:, 1:, :, 0] += H[:, 1:, :, 2] - H[:, :-1, :, 2] curl[:, :, 1:, 0] -= - H[:, :, 1:, 1] - H[:, :, :-1, 1] - - curl[:, :, 1:, 1] += H[:, :, 1:, 0] - H[:, :, :-1, 0] curl[1:, :, :, 1] -= - H[1:, :, :, 2] - H[:-1, :, :, 2] - - curl[1:, :, :, 2] += H[1:, :, :, 1] - H[:-1, :, :, 1] curl[:, 1:, :, 2] -= - H[:, 1:, :, 0] - H[:, :-1, :, 0] - - return curl - - -def curl_point(F: Tensorlike) -> Tensorlike: - """ Compute the point curl of a vector field F located on the grid points - using the right-handed curl operator. F is a 4D array with dimensions (Nx+1, - Ny+1, Nz+1, 3). The last dimension represents the x, y, and z components of - the vector field. The first three dimensions represent the grid points. - Returns a 4D array with dimensions (Nx+1, Ny+1, Nz+1, 3). The first three - dimensions represent the grid points. The last dimension represents the x, - y, and z components of the curl. - - This implementation takes a vector field `F` as input and computes the point - curl of the field in each direction using the `bd` backend and the - right-handed curl operator. The output vector field `curl_F` is located on - the faces of the grid cells that are perpendicular to the x, y, and z axes, - and are stored at half-integer grid points in the arrays and is therefore a - surface field. + ndarray: The curl of the input vector field with shape (Nx, Ny, Nz, 3), + where each component is defined on the faces of the grid cells. + """ - Args: - F: Point field to take the curl of (E-type point field located on the - edges of the grid [integer grid points]) - - Returns: - The curl of F (H-type surface field located on the faces of the grid - cell [half-integer gridpoints]) - - """ curl = bd.zeros(F.shape) - - curl[:, :-1, :, 0] += F[:, 1:, :, 2] - F[:, :-1, :, 2] curl[:, :, :-1, 0] -= - F[:, :, 1:, 1] - F[:, :, :-1, 1] - - curl[:, :, :-1, 1] += F[:, :, 1:, 0] - F[:, :, :-1, 0] curl[:-1, :, :, 1] -= - F[1:, :, :, 2] - F[:-1, :, :, 2] - - curl[:-1, :, :, 2] += F[1:, :, :, 1] - F[:-1, :, :, 1] curl[:, :-1, :, 2] -= - F[:, 1:, :, 0] - F[:, :-1, :, 0] - - return curl - -def curl_point_v2(v: Tensorlike) -> Tensorlike: - """ Compute the point curl of a vector field v located on the edges of the - grid cells using the right-handed curl operator. v is a 4D array with - dimensions (Nx, Ny, Nz, 3). The last dimension represents the x, y, and z - components of the vector field. The first three dimensions represent the - grid points. - - Returns a 4D array with dimensions (Nx, Ny, Nz, 3). The first three - dimensions represent the grid points. The last dimension represents the x, - y, and z components of the curl. - - This implementation assumes that the input vector field `v` is located on - the edges of the grid cells, stored at integer grid points in the arrays, - and is therefore assumed to be a point field. The edges are considered to be - perpendicular to the x, y, and z axes, which is consistent with the - staggered grid used in fluid dynamics and electrodynamics. - - The output vector field `curl_v` is located on the faces of the grid cells - that are perpendicular to the x, y, and z axes, and are stored at - half-integer grid points in the arrays, and is therefore a surface field. + return curl_E(v) - Args: - v: Point field to take the curl of (velocity point field located on the - edges of the grid [integer grid points]) - - Returns: - The curl of v (vorticity surface field located on the faces of the grid - cell [half-integer gridpoints]) - """ curl = bd.zeros(v.shape) - curl[1:-1, :, :, 0] += v[1:-1, :, :, 2] - v[:-2, :, :, 2] curl[:, 1:-1, :, - 0] -= v[:, 1:-1, :, 1] - v[:, :-2, :, 1] - - curl[:, 1:-1, :, 1] += v[:, 1:-1, :, 0] - v[:, :-2, :, 0] curl[:-1, :, :, 1] - -= v[1:, :, :, 2] - v[:-1, :, :, 2] - - curl[:-1, :, :, 2] += v[1:, :, :, 1] - v[:-1, :, :, 1] curl[:, 1:-1, :, 2] - -= v[:, 1:-1, :, 0] - v[:, :-2, :, 0] - - return curl - - -def curl_surface_v2(F: Tensorlike) -> Tensorlike: - """ Compute the surface curl of a vector field F located on the faces of the - grid cells using the right-handed curl operator. F is a 4D array with - dimensions (Nx, Ny, Nz, 3). The last dimension represents the x, y, and z - components of the vector field. The first three dimensions represent the - grid points. Returns a 4D array with dimensions (Nx, Ny, Nz, 3). The first - three dimensions represent the grid points. The last dimension represents - the x, y, and z components of the curl. - - This implementation assumes that the input vector field `F` is located on - the faces of the grid cells, stored at half-integer grid points in the - arrays, and is therefore assumed to be a surface field. The faces are - considered to be perpendicular to the x, y, and z axes, which is consistent - with the staggered grid used in fluid dynamics and electrodynamics. - - The output field `curl_F` is located on the edges of the grid cells that are - perpendicular to the x, y, and z axes, and are stored at integer grid points - in the arrays, and is therefore a point field. - - Args: - F: Surface field to take the curl of (vorticity surface field located on - half-integer grid points) - - Returns: - The curl of F (strain rate point field located on the edges of the grid - [integer grid points]) - - """ curl = bd.zeros(F.shape) - - curl[:, :-1, :, 0] += F[:, 1:, :, 2] - F[:, :-1, :, 2] curl[:, :, :-1, 0] -= - F[:, :, 1:, 1] - F[:, :, :-1, 1] - - curl[:, :, :-1, 1] += F[:, :, 1:, 0] - F[:, :, :-1, 0] curl[:-1, :, :, 1] -= - F[1:, :, :, 2] - F[:-1, :, :, 2] - - curl[:-1, :, :, 2] += F[1:, :, :, 1] - F[:-1, :, :, 1] curl[:, :-1, :, 2] -= - F[:, 1:, :, 0] - F[:, :-1, :, 0] - - return curl - - -def curl_surface(F: Tensorlike) -> Tensorlike: - """ Compute the surface curl of a vector field F located on the faces of the - grid cells using the right-handed curl operator. F is a 4D array with - dimensions (Nx+1, Ny+1, Nz+1, 3). The last dimension represents the x, y, - and z components of the vector field. The first three dimensions represent - the grid points. Returns a 4D array with dimensions (Nx+1, Ny+1, Nz+1, 3). - The first three dimensions represent the grid points. The last dimension - represents the x, y, and z components of the curl. - - This implementation assumes that the input vector field `F` is located on - the faces of the grid cells, stored at half-integer grid points in the - arrays, and is therefore assumed to be a surface field. The faces are - considered to be perpendicular to the x, y, and z axes, which is consistent - with the staggered grid used in fluid dynamics and electrodynamics. - - The output field `curl_F` is located on the edges of the grid cells that are - perpendicular to the x, y, and z axes, and are stored at integer grid points - in the arrays, and is therefore a point field. +def curl_H(H: Tensorlike) -> Tensorlike: + """Transforms an H-type field into an E-type field by performing a curl + operation Args: - F: Surface field to take the curl of (H-type field located on - half-integer grid points) + H: Magnetic field to take the curl of (H-type field located on half-integer grid points) Returns: - The curl of F (E-type field located on the edges of the grid [integer - grid points]) + The curl of H (E-type field located on the edges of the grid [integer grid points]) - """ curl = bd.zeros(F.shape) + """ + curl = bd.zeros(H.shape) - curl[1:-1, :, :, 0] += F[1:-1, :, :, 2] - F[:-2, :, :, 2] curl[:, 1:-1, :, - 0] -= F[:, 1:-1, :, 1] - F[:, :-2, :, 1] + curl[:, 1:, :, 0] += H[:, 1:, :, 2] - H[:, :-1, :, 2] + curl[:, :, 1:, 0] -= H[:, :, 1:, 1] - H[:, :, :-1, 1] - curl[:, 1:-1, :, 1] += F[:, 1:-1, :, 0] - F[:, :-2, :, 0] curl[:-1, :, :, 1] - -= F[1:, :, :, 2] - F[:-1, :, :, 2] + curl[:, :, 1:, 1] += H[:, :, 1:, 0] - H[:, :, :-1, 0] + curl[1:, :, :, 1] -= H[1:, :, :, 2] - H[:-1, :, :, 2] - curl[:-1, :, :, 2] += F[1:, :, :, 1] - F[:-1, :, :, 1] curl[:, 1:-1, :, 2] - -= F[:, 1:-1, :, 0] - F[:, :-2, :, 0] + curl[1:, :, :, 2] += H[1:, :, :, 1] - H[:-1, :, :, 1] + curl[:, 1:, :, 2] -= H[:, 1:, :, 0] - H[:, :-1, :, 0] return curl --:- - -Q: are these correct? - -GitHub Copilot: Yes, these functions are correct and consistent with a staggered -Yee grid. The `curl_point` function computes the point curl of a vector field -`F` located on the grid points, which are the edges of the grid cells and are -stored at integer grid points in the arrays. The output vector field `curl_F` is -located on the grid points and is therefore a point field. - -The `curl_surface` function computes the surface curl of a vector field `F` -located on the faces of the grid cells, which are perpendicular to the x, y, and -z axes, and are stored at half-integer grid points in the arrays. The output -field `curl_F` is located on the faces of the grid cells that are perpendicular -to the x, y, and z axes, and are stored at half-integer grid points in the -arrays, and is therefore a surface field. - -These functions are consistent with the staggered Yee grid, which is commonly -used in electromagnetics and fluid dynamics simulations. - --:- - -Above, we have 4 curl functions, about which CoPilot says: - -GitHub Copilot: The four curl functions in the code are `curl_E`, `curl_H`, -`curl_point`, and `curl_surface`. - -`curl_E` takes an E-type field located on the edges of the grid cell [integer -gridpoints] as input and returns an H-type field located on the faces of the -grid [half-integer grid points] as output. The output field is located on the -faces of the grid cells, perpendicular to the x, y, and z axes. - -`curl_H` takes an H-type field located on half-integer grid points as input and -returns an E-type field located on the edges of the grid [integer grid points] -as output. The output field is located on the edges of the grid cells, -perpendicular to the x, y, and z axes. - -`curl_point` takes an E-type point field located on the edges of the grid -[integer grid points] as input and returns an H-type surface field located on -the faces of the grid cell [half-integer gridpoints] as output. The output field -is located on the faces of the grid cells, perpendicular to the x, y, and z -axes. - -`curl_surface` takes an H-type surface field located on half-integer grid points -as input and returns an E-type field located on the edges of the grid [integer -grid points] as output. The output field is located on the edges of the grid -cells, perpendicular to the x, y, and z axes. - -In summary, the four curl functions differ in the type of field they take as -input and output, and in the location of the fields on the grid. The indexing of -the arrays in the functions is also different, depending on the location of the -fields on the grid. - --:- - -Previously, CoPilot also said: - -"So, the curl_E function should be called curl_H and the curl_H function should -be called curl_E, because they are the wrong way around." - -So, what I did was copy the implementation of the curl_E function and called it -curl_surface, and I copied the implementation of the curl_H function and called -it curl_point, so that the names of the functions are consistent with the type -of field they take as input and output, and with the location of the fields on -the grid. - -However, the comments were not correct, so I asked CoPilot to fix them. - -Then, it decidid to also change the indexing in the function, changing the -implementation. - -@CoPilot: l4m4re: explain the difference in array slice indexing betwen the -curl_H and curl_point functions - -GitHub Copilot: The difference in array slice indexing between the `curl_H` and -`curl_point` functions is due to the fact that they operate on different types -of fields. - -The `curl_H` function takes an H-type field as input, which is located on -half-integer grid points, and returns an E-type field located on integer grid -points. Therefore, the array slice indexing in `curl_H` is shifted by one index -in the first dimension compared to `curl_point`. Specifically, in `curl_H`, the -slice `[:, 1:, :, 0]` corresponds to the x-component of the curl, while in -`curl_point`, the slice `[:, :-1, :, 0]` corresponds to the x-component of the -curl. - -On the other hand, the `curl_point` function takes an E-type point field as -input, which is located on integer grid points, and returns an H-type surface -field located on half-integer grid points. Therefore, the array slice indexing -in `curl_point` is shifted by one index in the first dimension compared to -`curl_H`. Specifically, in `curl_point`, the slice `[:, :-1, :, 0]` corresponds -to the x-component of the curl, while in `curl_H`, the slice `[:, 1:, :, 0]` -corresponds to the x-component of the curl. - -l4m4re: Does this mean that the curl_H function applies to a H field that is -actually a point field (and therefore incorrect), while the curl_point function -is correct and consistend with a staggered Yee grid? - -GitHub Copilot: No, that's not correct. The `curl_H` function takes a magnetic -field `H` as input, which is defined on the edges of the grid cells and is -therefore a point field. The output of the `curl_H` function is an electric -field `E`, which is also defined on the edges of the grid cells and is therefore -a point field. The `curl_H` function is consistent with the Yee grid -implementation, where the magnetic field `H` and the electric field `E` are both -defined on the edges of the grid cells. - -The `curl_point` function, on the other hand, takes a point field `F` as input, -which is defined on the edges of the grid cells and is therefore an integer grid -point. The output of the `curl_point` function is a magnetic field `curl_F`, -which is defined on the faces of the grid cells and is therefore a half-integer -grid point. The `curl_point` function is also consistent with the Yee grid -implementation, where the magnetic field `H` and the auxiliary magnetic field -`B` are defined on the faces of the grid cells. - -Therefore, both the `curl_H` and `curl_point` functions are consistent with the -Yee grid implementation, but they operate on different input vector fields and -compute the curl of those fields using different finite difference -approximations. - --:- - -Q: - -l4m4re: Please suggest alternative implementations for the following -curl_surface and curl_point functions such that we can compute -curl_surface(curl_point(v)) for a point vector field v directly, under the -assumption that v is a point field representing the flow velocity in a fluid -dynamics staggered Yee grid: - -# previous implementation of point and surface curls - -GitHub Copilot: To compute `curl_surface(curl_point(v))` for a point vector -field `v` directly, we can use the following alternative implementations for the -`curl_point` and `curl_surface` functions: - -''' - -def curl_point(v: Tensorlike) -> Tensorlike: +def curl_face_to_edge(A: Tensorlike) -> Tensorlike: """ - Compute the point curl of a vector field v located on the edges of the grid - cells using the right-handed curl operator. v is a 4D array with dimensions - (Nx, Ny, Nz, 3). The last dimension represents the x, y, and z components of - the vector field. The first three dimensions represent the grid points. + Compute the curl of a vector field where the vector components are defined + on the faces of the grid cells. The result is a vector field defined on the + edges of the grid cells. - Returns a 4D array with dimensions (Nx, Ny, Nz, 3). The first three - dimensions represent the grid points. The last dimension represents the x, - y, and z components of the curl. - - This implementation assumes that the input vector field `v` is located on - the edges of the grid cells, stored at integer grid points in the arrays, - and is therefore assumed to be a point field. The edges are considered to be - perpendicular to the x, y, and z axes, which is consistent with the - staggered grid used in fluid dynamics and electrodynamics. - - The output vector field `curl_v` is located on the faces of the grid cells - that are perpendicular to the x, y, and z axes, and are stored at - half-integer grid points in the arrays, and is therefore a surface field. + Parameters: - Args: - v: Point field to take the curl of (velocity point field located on the - edges of the grid [integer grid points]) + A (ndarray): Input vector field with shape (Nx, Ny, Nz, 3), where each + component (Ax, Ay, Az) is defined on the faces of the grid cells. Returns: - The curl of v (vorticity surface field located on the faces of the grid - cell [half-integer gridpoints]) + ndarray: The curl of the input vector field with shape (Nx, Ny, Nz, 3), + where each component is defined on the edges of the grid cells. """ - curl = bd.zeros(v.shape) - - curl[1:-1, :, :, 0] += v[1:-1, :, :, 2] - v[:-2, :, :, 2] - curl[:, 1:-1, :, 0] -= v[:, 1:-1, :, 1] - v[:, :-2, :, 1] - - curl[:, 1:-1, :, 1] += v[:, 1:-1, :, 0] - v[:, :-2, :, 0] - curl[:-1, :, :, 1] -= v[1:, :, :, 2] - v[:-1, :, :, 2] - - curl[:-1, :, :, 2] += v[1:, :, :, 1] - v[:-1, :, :, 1] - curl[:, 1:-1, :, 2] -= v[:, 1:-1, :, 0] - v[:, :-2, :, 0] + return curl_H(A) - return curl -def curl_surface(F: Tensorlike) -> Tensorlike: +def div(v: Tensorlike) -> Tensorlike: """ - Compute the surface curl of a vector field F located on the faces of the - grid cells using the right-handed curl operator. F is a 4D array with - dimensions (Nx, Ny, Nz, 3). The last dimension represents the x, y, and z - components of the vector field. The first three dimensions represent the - grid points. Returns a 4D array with dimensions (Nx, Ny, Nz, 3). The first - three dimensions represent the grid points. The last dimension represents - the x, y, and z components of the curl. - - This implementation assumes that the input vector field `F` is located on - the faces of the grid cells, stored at half-integer grid points in the - arrays, and is therefore assumed to be a surface field. The faces are - considered to be perpendicular to the x, y, and z axes, which is consistent - with the staggered grid used in fluid dynamics and electrodynamics. - - The output field `curl_F` is located on the edges of the grid cells that are - perpendicular to the x, y, and z axes, and are stored at integer grid - points in the arrays, and is therefore a point field. + Compute the divergence of a vector field v located on the edges of the grid + cells. The result is a scalar field located on the faces of the grid cells. Args: - F: Surface field to take the curl of (vorticity surface field located - on half-integer grid points) + v (Tensorlike): Input vector field with shape (Nx, Ny, Nz, 3), where + each component + (vx, vy, vz) is defined on the edges of the grid cells. Returns: - The curl of F (strain rate point field located on the edges of the grid - [integer grid points]) - + Tensorlike: The divergence of the input vector field with shape (Nx, Ny, + Nz, 1), + where the scalar field is defined on the faces of the grid + cells. + + ChatGPT: + + This implementation will now correctly compute the divergence for the velocity + field located at the edges of the grid cells, giving a scalar field at the faces + of the grid cells, which can be used further in the simulator's computations. """ - curl = bd.zeros(F.shape) - - curl[:, :-1, :, 0] += F[:, 1:, :, 2] - F[:, :-1, :, 2] - curl[:, :, :-1, 0] -= F[:, :, 1:, 1] - F[:, :, :-1, 1] - - curl[:, :, :-1, 1] += F[:, :, 1:, 0] - F[:, :, :-1, 0] - curl[:-1, :, :, 1] -= F[1:, :, :, 2] - F[:-1, :, :, 2] - - curl[:-1, :, :, 2] += F[1:, :, :, 1] - F[:-1, :, :, 1] - curl[:, :-1, :, 2] -= F[:, 1:, :, 0] - F[:, :-1, :, 0] - - return curl - -''' + div_v = bd.zeros((v.shape[0], v.shape[1], v.shape[2], 1)) -Note that curl_surface can only work on curl_point, so -we can define a curl_curl bij - -curl_curl(F) = curl_surface(curl_point(F)) - -In other words: curl_surface is ALWAYS the curl of the curl! - -Thus, both operators MUST be applied in this particular manner. - - - -''' - - - - - -''' -We can then compute `curl_surface(curl_point(v))` for a point vector field `v` -directly using the following code: - - -import numpy as np - -# Define the velocity point field v v = np.zeros((Nx, Ny, Nz, 3)) + # Compute the x-component of the divergence + div_v[1:-1, :, :, 0] += (v[1:-1, :, :, 0] - v[:-2, :, :, 0]) + div_v[:-2, :, :, 0] -= (v[1:-1, :, :, 0] - v[2:, :, :, 0]) -# Compute the point curl of the velocity point field v curl_v = curl_point(v) + # Compute the y-component of the divergence + div_v[:, 1:-1, :, 0] += (v[:, 1:-1, :, 1] - v[:, :-2, :, 1]) + div_v[:, :-2, :, 0] -= (v[:, 1:-1, :, 1] - v[:, 2:, :, 1]) -# Compute the surface curl of the vorticity surface field curl_v strain_rate = -curl_surface(curl_v) + # Compute the z-component of the divergence + div_v[:, :, 1:-1, 0] += (v[:, :, 1:-1, 2] - v[:, :, :-2, 2]) + div_v[:, :, :-2, 0] -= (v[:, :, 1:-1, 2] - v[:, :, 2:, 2]) -''' + return div_v -def div(vf: Tensorlike) -> Tensorlike: - """ - Compute the divergence of a velocity field vf located on the faces of the - grid cells. vf is a 4D array with dimensions (Nx+1, Ny+1, Nz+1, 3). The last - dimension represents the x, y, and z components of the velocity field. The - first three dimensions represent the grid points. Returns a 4D array with - dimensions (Nx+1, Ny+1, Nz+1, 1). The first three dimensions represent the - grid points. The last dimension represents the scalar divergence. - - GitHub Copilot: The implementation of the div function using array slices - results in a field located at an integer grid point because in a staggered - Yee grid, the resulting vector fields are located at either integer or - half-integer grid points. The integer grid points represent vector fields - defined at a point in space, while the half-integer grid points represent - vector fields defined over an area. - - The div function is defined as the surface integral of the flux of a vector - field over a closed surface. In a staggered Yee grid, the discretized cube - is used to represent the finite difference fields. The resulting value of - the div function is not located on one of the faces of the cube, but rather - at the center of the cube, which is a point in space. Since fields defined - at a point in space are represented by integer grid points in a staggered - Yee grid, the resulting vector field is located at an integer grid point. - - Therefore, when implementing the div function using array slices, the - resulting field is located at an integer grid point because the div function - is defined as a surface integral over the area of all the faces of a cube, - divided by the volume of the cube, resulting in a unit of per meter [1/m]. - The resulting value is not located on one of the faces of the cube, but - rather at the center of the cube, which is a point in space represented by - an integer grid point in a staggered Yee grid. - - - Note that the resulting field is a scalar field, as the divergence is a - scalar quantity that represents the rate at which a vector field expands or - contracts at a given point in space. This does not have a direction, but is - rather a quantity. - - This yields the question of how we shouldt interpret Maxwells equation for - ampere's law: - - curl E = -dB/dt. - - and: - - E = - grad Phi - d[A]/dt. - - - - - - - - - - GitHub Copilot: The `div` function computes the divergence of a velocity - field `vf` located on the faces of the grid cells. The input velocity field - `vf` should be a 4D array with dimensions `(Nx+1, Ny+1, Nz+1, 3)` to be - consistent with the staggered grid used in fluid dynamics. - - In a staggered Yee grid, the velocity field is located on the faces of the - grid cells that are perpendicular to the x, y, and z axes. The resulting - vector fields are located at either integer or half-integer grid points. The - integer grid points represent vector fields defined at a point in space, - while the half-integer grid points represent vector fields defined over an - area. - - The `div` function is defined as the surface integral of the flux of a - vector field over a closed surface. In a staggered Yee grid, the discretized - cube is used to represent the finite difference fields. The resulting value - of the `div` function is not located on one of the faces of the cube, but - rather at the center of the cube, which is a point in space. Since fields - defined at a point in space are represented by integer grid points in a - staggered Yee grid, the resulting vector field is located at an integer grid - point. - - The implementation of the `div` function using array slices results in a - field located at an integer grid point because the resulting value is not - located on one of the faces of the cube, but rather at the center of the - cube, which is a point in space represented by an integer grid point in a - staggered Yee grid. - - The implementation of the `div` function computes the x, y, and z components - of the divergence using array slices. The resulting divergence field is a 4D - array with dimensions `(Nx+1, Ny+1, Nz+1, 1)`. The first three dimensions - represent the grid points, and the last dimension represents the scalar - divergence. - - """ - div_vf = bd.zeros((vf.shape[0], vf.shape[1], vf.shape[2], 1)) - # Compute the x-component of the divergence - div_vf[1:-1, :, :, 0] += (vf[1:-1, :, :, 0] - vf[:-2, :, :, 0]) - div_vf[1:-1, :, :, 0] -= (vf[2:, :, :, 0] - vf[1:-1, :, :, 0]) - # Compute the y-component of the divergence - div_vf[:, 1:-1, :, 0] += (vf[:, 1:-1, :, 1] - vf[:, :-2, :, 1]) - div_vf[:, 1:-1, :, 0] -= (vf[:, 2:, :, 1] - vf[:, 1:-1, :, 1]) - # Compute the z-component of the divergence - div_vf[:, :, 1:-1, 0] += (vf[:, :, 1:-1, 2] - vf[:, :, :-2, 2]) - div_vf[:, :, 1:-1, 0] -= (vf[:, :, 2:, 2] - vf[:, :, 1:-1, 2]) - return div_vf - def grad(p: Tensorlike) -> Tensorlike: """ @@ -1124,17 +166,19 @@ def grad(p: Tensorlike) -> Tensorlike: """ grad = bd.zeros((p.shape[0], p.shape[1], p.shape[2], 3)) + # Compute the x-component of the gradient grad[:-1, :, :, 0] = (p[1:, :, :, 0] - p[:-1, :, :, 0]) + # Compute the y-component of the gradient grad[:, :-1, :, 1] = (p[:, 1:, :, 0] - p[:, :-1, :, 0]) + # Compute the z-component of the gradient grad[:, :, :-1, 2] = (p[:, :, 1:, 0] - p[:, :, :-1, 0]) + return grad - - def delta(F: Tensorlike) -> Tensorlike: """ Compute the vector Laplace operator of a vector field F located on the faces @@ -1155,7 +199,7 @@ def delta(F: Tensorlike) -> Tensorlike: The `delta` function computes the vector Laplace operator of a vector field - using the `div`, `grad`, `curl_point`, and `curl_surface` functions. The + using the `div`, `grad`, `curl_edge_to_face`, and `curl_face_to_edge` functions. The `div` and `grad` functions compute the divergence and gradient of a vector field, respectively, using finite differences between the components of the vector field at different points in the grid cell. The output fields are @@ -1179,10 +223,10 @@ def delta(F: Tensorlike) -> Tensorlike: The `grad` function computes the gradient of the scalar field `phi`, which results in a vector field `E` located on the edges of the grid cells. - The `curl_point` function computes the point curl of the vector field `F`, + The `curl_edge_to_face` function computes the point curl of the vector field `F`, which results in a vector field `A` located on the faces of the grid cells. - The `curl_surface` function computes the surface curl of the vector field + The `curl_face_to_edge` function computes the surface curl of the vector field `A`, which results in a vector field `H` located on the edges of the grid cells. @@ -1197,9 +241,9 @@ def delta(F: Tensorlike) -> Tensorlike: is a surface field located on the faces of the grid cells, while the vector field `H` is a point field located on the edges of the grid cells. - The `curl_point` function computes the point curl of the vector field `F`, + The `curl_edge_to_face` function computes the point curl of the vector field `F`, which results in a vector field `A` located on the faces of the grid cells. - The `curl_surface` function then computes the surface curl of the vector + The `curl_face_to_edge` function then computes the surface curl of the vector field `A`, which results in a vector field `H` located on the edges of the grid cells. @@ -1211,8 +255,8 @@ def delta(F: Tensorlike) -> Tensorlike: """ phi = div(F) E = grad(phi) - A = curl_point(F) - H = curl_surface(A) + A = curl_edge_to_face(F) + H = curl_face_to_edge(A) delta_F = E - H return delta_F @@ -1220,21 +264,23 @@ def delta(F: Tensorlike) -> Tensorlike: def delta_C(C: Tensorlike) -> Tensorlike: """ - Compute the vector Laplace operator of a vector field F located on the faces of the grid - cells.· - F is a 4D array with dimensions (Nx+1, Ny+1, Nz+1, 3). - The last dimension represents the x, y, and z components of the vector field. - The first three dimensions represent the grid points. - Returns a 4D array with dimensions (Nx+1, Ny+1, Nz+1, 3). - The first three dimensions represent the grid points. - The last dimension represents the x, y, and z components of the vector Laplace operator. + Compute the vector Laplace operator of a vector field F located on the faces + of the grid cells.· - This implementation takes a vector field `F` as input and computes the vector Laplace - operator of the field using the `bd` backend. The output vector field `delta_F` is - located on the faces of the grid cells that are perpendicular to the x, y, and z axes. - Note that this implementation assumes that the input vector field `F` is located on the - faces of the grid cells that are perpendicular to the x, y, and z axes, which is - consistent with the staggered grid used in fluid dynamics and electrodynamics. + F is a 4D array with dimensions (Nx+1, Ny+1, Nz+1, 3). The last dimension + represents the x, y, and z components of the vector field. The first three + dimensions represent the grid points. Returns a 4D array with dimensions + (Nx+1, Ny+1, Nz+1, 3). The first three dimensions represent the grid points. + The last dimension represents the x, y, and z components of the vector + Laplace operator. + + This implementation takes a vector field `F` as input and computes the + vector Laplace operator of the field using the `bd` backend. The output + vector field `delta_F` is located on the faces of the grid cells that are + perpendicular to the x, y, and z axes. Note that this implementation assumes + that the input vector field `F` is located on the faces of the grid cells + that are perpendicular to the x, y, and z axes, which is consistent with the + staggered grid used in fluid dynamics and electrodynamics. Calculate the vector Laplacian of a vector field C using the left-handed and right-handed curl operators. @@ -1248,9 +294,8 @@ def delta_C(C: Tensorlike) -> Tensorlike: C = - k * rho [v], - with rho the mass density of the superfluid medium, which in this model - is taken equal to the electric permittivity of the medium, so for - the vacuum + with rho the mass density of the superfluid medium, which in this model is + taken equal to the electric permittivity of the medium, so for the vacuum rho = epsilon_0 = eta/k = 8.854187817620389e-12 [kg/m^3]. @@ -1259,24 +304,23 @@ def delta_C(C: Tensorlike) -> Tensorlike: k = c*c = 8.987551787368176e+16 [m^2/s], - Thus, we indeed define k to have a value equal to c*c, but a unit of - measurement of meters squared per second [m^2/s] representing - the quantum circulation konstant, diffusivity or kinematic viscosity of - the medium. + Thus, we indeed define k to have a value equal to c*c, but a unit of + measurement of meters squared per second [m^2/s] representing the quantum + circulation konstant, diffusivity or kinematic viscosity of the medium. Thus, C has a unit of measurement in [m^2/s] * [kg/m^3] * [m/s] = [kg/s^2], the unit we equate to the Ampere in this model, hence the choice of C, for "current" in "Amperes" or kilograms per second squared. - in the following code, lower case is used for scalar fields, upper case - for vector fields. + in the following code, lower case is used for scalar fields, upper case for + vector fields. """ p = div(C) # p is a scalar field representing pressure in [Pa/m^3] L = grad(p) # a vector field representing linear force density in [N/m^3] - A = curl_point(C) # the vector potential, also in [Pa/m^3]. - R = curl_surface(A) # a vector field representing angular force density in [N/m^3] + A = curl_edge_to_face(C) # the vector potential, also in [Pa/m^3]. + R = curl_face_to_edge(A) # a vector field representing angular force density in [N/m^3] d_C = L - R # Delta C, the vector laplace operator of C in [N/m^3] @@ -1284,9 +328,6 @@ def delta_C(C: Tensorlike) -> Tensorlike: - - - def delta_I(I: Tensorlike) -> Tensorlike: """ Compute the vector Laplace operator of a vector field F located on the faces of the grid @@ -1338,94 +379,11 @@ def delta_I(I: Tensorlike) -> Tensorlike: # the vector potential, also in [kg/m-s3], [N/m2-s], [J/m3-s], [Pa/s], # [W/m3], [K] or [kg/m-s3] representing angular power or heat density, force # flux, yank density flux, or angular temperature - W = curl_point(I) - Y_a = curl_surface(W) # a vector field representing angular yank density in [N/m^3-s] + W = curl_edge_to_face(I) + Y_a = curl_face_to_edge(W) # a vector field representing angular yank density in [N/m^3-s] d_I = Y_l - Y_a # Delta I, the vector laplace operator of I in [N/m^3-s] return d_I # net yank density or current flux in [N/m^3-s]. - -''' - -Thus, we have defined fields similar to Maxwell and/or Navier-Stokes equations, -but not only in first order, like Maxwell and Navier-Stokes, but also in second -order in full 3D vector calculus. - -In contrast to Maxwell and Navier-Stokes, we have defined the fields C and I as -our primary vector fields, so our potential fields p and A are uniquely defined -and derived from a primary field along an operator defined by the vector laplace -operator. - -In both Maxwell and Navier-Stokes, the potential fields are defined as kind of -the reversal of the gradient and curl opertors on E and H, or B in fluid -dynamics, but there is no unique definition of the potential fields, leaving -room for "gauge freedom" - -''' - - - - -''' - -import numpy as np - -# Generate a random velocity field F located on the faces of the grid cells that -# are perpendicular to the x, y, and z axes -Nx, Ny, Nz = 10, 10, 10 -F = np.random.rand(Nx+1, Ny+1, Nz+1, 3) - -# Compute the vector Laplace operator delta_F using the delta function -delta_F = delta(F) - -# Compute the analytical solution of the vector Laplace operator for the given -# velocity field F Here we assume that the velocity field is divergence-free, so -# the Laplacian of the velocity field is zero Therefore, the vector Laplace -# operator of the velocity field is simply the negative of the curl of the curl -# of the velocity field -curl_F = curl_E(F) -curl_curl_F = curl_H(curl_F) -analytical_delta_F = -curl_curl_F - -# Compare the computed delta_F with the analytical solution of the vector -# Laplace operator for the given velocity field F -assert np.allclose(delta_F, analytical_delta_F) - - - - - -import numpy as np -import subprocess - -# Generate a random vector field F -Nx, Ny, Nz = 10, 10, 10 -F = np.random.rand(Nx+1, Ny+1, Nz+1, 3) - -# Compute the gradient of F using your implementation -grad_F = grad(F) - -# Compute the divergence of F using your implementation -div_F = div(F) - -# Compute the curl of F using your implementation -curl_F = curl_E(F) - -# Call Octave to compute the gradient, divergence, and curl of F -octave_cmd = f"octave --eval \"pkg load 'nurbs-1.3.13'; F = {F.tolist()}; grad_F = gradient(F); div_F = divergence(F); curl_F = curl(F); disp(grad_F); disp(div_F); disp(curl_F);\"" -result = subprocess.run(octave_cmd, shell=True, capture_output=True, text=True) - -# Parse the output of Octave to obtain the computed gradient, divergence, and curl of F -output = result.stdout.strip().split("\n") -grad_F_octave = np.array(eval(output[0])) -div_F_octave = np.array(eval(output[1])) -curl_F_octave = np.array(eval(output[2])) - -# Compare the computed results from your implementation with the results from Octave -assert np.allclose(grad_F, grad_F_octave) -assert np.allclose(div_F, div_F_octave) -assert np.allclose(curl_F, curl_F_octave) - -''' \ No newline at end of file From 31535e1ed61ff0f0e80e79ba777451b644fb9ff7 Mon Sep 17 00:00:00 2001 From: Arend Lammertink Date: Sat, 6 Jul 2024 18:36:32 +0200 Subject: [PATCH 06/16] Quite some clean up --- fdtd/lgrid.py | 978 -------------------------------------------------- 1 file changed, 978 deletions(-) diff --git a/fdtd/lgrid.py b/fdtd/lgrid.py index 83ed98f..cf6e404 100644 --- a/fdtd/lgrid.py +++ b/fdtd/lgrid.py @@ -16,864 +16,6 @@ new theory. The first step of impelementing the new theory is to implement the operators used and the update functions for the fields. -Once these are working and tested, the next step is to implement the sources, -boundaries, detectors and objects. These will have to be adapted from their -electromagnetic counterparts to the new theory and have to be consistent with a -staggered Yee grid. - -The new theory is based on the vector LaPlace operator and the discovery of the -quantum circulation constant k, with a value equal to c*c but a unit of -measurement in [m^2/s]. This is a new constant of nature and represents the -quantum circulation constant, kinematic viscocity or diffusivity of the medium: - - k = c*c = 8.987551787368176e+16 [m^2/s]. - -The fundamental equation of the new theory is the definition of the time -derivative of any given vector field within the superfluid medium: - - dF/dt = -k * delta(F), - -where F is any given vector field, delta is the vector LaPlace operator and k is -the quantum circulation constant. - -Note that this definition only has units of measurement in meters and seconds -and thus it represents a fundamental equation of nature describing the -fundamental dynamics of space-time itself and is decoupled from the substance of -the medium, the latter being represented by mass density rho in the model. - -We believe that one day this equation will be recognized as one of the biggest -scientific breakthroughs of the 21st century, because there simply is no -argument to be made against such a simple and straightforward application of the -vector Laplacian using only one single Universal constant: k. - -The vector LaPlace operator is defined as follows: - - delta(F) = grad(div(F)) - curl(curl(F)), - -where grad is the gradient operator, div is the divergence operator and curl is -the curl operator. However, during the implementation of this simulator, it was -found that because of the design of the staggered Yee grid, the curl operator -actually has to be split into two separate operators, one for the left handed -curl and one for the right handed curl, so we redefine the vector LaPlace as -follows: - - delta(F) = grad(div(F)) - curl_surface(curl_point(F)), - -where curl_surface is the surface field curl operator and curl_point is the -point field curl operator. - -According to the Yee discretization algorithm, there are inherently two types of -fields on the grid: `E`-type point fields on integer grid locations and `H`-type -surface fields on half-integer grid locations. - -The beauty of these interlaced coordinates is that they enable a very natural -way of writing the curl of the electric and magnetic fields: the curl of an -H-type surface field will be an E-type point field and vice versa. - - - -This definitiion can be negated: - - - delta(F) = - grad(div(F)) + curl_surface(curl_point(F)), - -And thus we can compute the time derivative of any given vector field F along: - - dF/dt = -k * delta(F) = -k * grad(div(F)) + k * curl_surface(curl_point(F)), - -or: - - dF/dt = -k * delta(F) = k ( -grad(div(F)) + curl_surface(curl_point(F)) ), - -which can be rewritten to an update equation for F: - - F += -k * delta(F) * dt = k ( -grad(div(F)) + curl_surface(curl_point(F)) ) - * dt, - -with dt the time step of the simulation. - -Using the definition of the vector LaPlace operator, we can define several -fields within one consistent framework. The most simple field to define is the -flow velocity vector field v, so we can compute it's time derivate, the -acceleration vector field a, as follows: - - a = dv/dt = -k * delta(v) = k ( -grad(div(v)) + curl_surface(curl_point(v)) - ). - -This is known as the velocity diffusion equation and is the fundamental equation -of fluid dynamics. - -And, by applying Newton's F=ma, we can define the 3D force density vector field -F as follows: - - F = rho * a = -k * rho * delta(v) = k * rho * (-grad(div(v)) + - curl_surface(curl_point(v)) ). - -One of the big benefits of the the fundamental equation of the new theory, -defining the time derivative of any given vector field F, is that we can now -apply this equation to the acceleration vector field a to compute the time -derivative of a, the jerk vector field j: - - j = da/dt = -k * delta(a) = k ( -grad(div(a)) + curl_surface(curl_point(a)) - ). - -And, by applying Y=mj, with Yank the time derivative of Force, we van the 3D -yank density vector field Y as follows: - - Y = rho * j = -k * rho * delta(a) = k * rho * (-grad(div(a)) + - curl_surface(curl_point(a)) ). - -Thus, we come to a higher order model incorporating not only acceleration and -force density vector fields, but also their time derivatives: the jerk and yank -density vector fields. - -Note that we have used lower case for the v, a and j vector, contrary to the -convention of this library to denote vector fields with capital letters, because -these are commonly used symbols for velocity, acceleration and jerk. - -This means that we have obtained a model that is superior to both Maxwell's -equations as well as Navier-Stokes equations, because it is a higher order -model. This has far reaching consequences for our understanding of physics and -the medium, because it is known that the intermediate fields we obtain by -writing out the definition of the vector LaPlace operator, are 100% -mathematically consistent and free of any paradoxes or inconsistencies. Also, -any wave equations that can be derived from this model will be invariant to the -Galilean coordinate transformation, bringing the Lorentz tranform on which -Einstein's theory of relativity is based, into question. - -Furthermore, the introduction of the jerk and yank vector fields into the model -enables us to model the dynamics of the medium in a much more accurate way, as -from control systems theory it is known that not only the acceleration of an -actuator needs to be controlled, but also the jerk. Otherwise, all kinds of -vibrations and oscillations will occur in the system. - -In other words: since it is known from control systems that the jerk of the -system under control needs to be controlled as well, it is only logical that -this should be the case for the medium as well. And, since neither Maxwell's -equations nor Navier-Stokes are higher order equations, it is clear that the old -models are incomplete and therefore not capable of describing either the medium -or a fluid accurately. - -Now let's consider the negated definition of the vector LaPlace operator for a -field C: - - - delta(C) = - grad(div(C)) + curl_surface(curl_point(C)), - -with C = rho * k * v, resulting in a unit of measurement of: - - [C] = [rho] * [k] * [v] = [kg/m^3] * [m^2/s] * [m/s] = [kg/s^2]. - -Thus, C represents a thusfar unknown quantity that has a unit of measurement in -[kg/s^2], [N/m], [J/m^2] or [Pa-m]. And since in our model the Ampere -representing electric current also has a unit of measurement in [kg/s^2], we -have chosen to use the symbol C. However, since electric current in our model is -considered to represent turbulence within the medium, it can be speculated that -C also represents some quantity related to turbulence. For now, we shall refer -to C as our primary dynamic field. - -From this, we can write out intermediate fields similar to Maxwell's equations, -with the big difference that these intermediate fields are derived from a higher -order equation, unlike Maxwell's: - - phi = div(C) - - A = curl_point(C) - - E = -grad(phi) - - B = curl_surface(A) - -And because of vector identities we can also write: - - curl(E) = 0 - - div(B) = 0, - -since the divergence of a curl of the gradient of any twice-differentiable -scalar field p is always the zero vector (curl(grad(p))=0), and the divergence -of the curl of any vector field A is always zero as well (div(curl(A))=0). - -Thus, we have shown that the fields derived from application of the vector -Laplacian yield a Helmholtz decomposition of the field C, decomposing it into a -divergence free component B and a curl free component E. - -From wikipedia: - -"In physics and mathematics, in the area of vector calculus, Helmholtz's -theorem, also known as the fundamental theorem of vector calculus, states that -any sufficiently smooth, rapidly decaying vector field in three dimensions can -be resolved into the sum of an irrotational (curl-free) vector field and a -solenoidal (divergence-free) vector field; this is known as the Helmholtz -decomposition or Helmholtz representation. It is named after Hermann von -Helmholtz." - -In other words: the fields we have defined are consistent with the fundamental -theorem of vector calculus. - -We can compare this with Maxwell's equations: - - E = -grad(phi) - dA/dt, - - B = curl(A), - - curl(E) = -dB/dt, - - div(B) = 0, - - div(E) = rho / eps, - - curl(B) = mu * J - 1/c^2 * dE/dt, - -Where obviously the curl of the electric field is not equal zero and therefore -Maxwell's equations are not consistent with the fundamental theorem of vector -calculus: the Helmholtz theorem or Helmholtz decomposition as fundamentally -described by the vector Laplace operator. - -In other words: we have shown that Maxwell's equations are mathematically -inconsistent. - -Note that when taking the curl on both sides of this equation: - - E = -grad(phi) - dA/dt, - -we obtain the Maxwell-Faraday equation, representing Faraday's law of induction: - - curl(E) = -dB/dt. - -Faraday's law of induction is a basic law of electromagnetism predicting how a -magnetic field will interact with an electric circuit to produce an -electromotive force (EMF), which is thus a law that applies at the macroscopic -level. - -''' -''' - -An interesting detail is how the fields can be characterizeed in a descretized -version of Maxwell's equations, as can be read further down in this document: - -"In electrodynamics, the electric field is located on the edges of the grid -cells, while the magnetic field is located on the faces of the grid cells. This -is because the electric field is a vector field that is defined at a point in -space, while the magnetic field is a vector field that is defined over an area." - - -This illustrates that geometry with respect to definition of our operators -matters, and since this is correctly implemented in the discretized alrogirims -in this simulator, we should realize that the infenitesimal fields can also -be fundamentally distinguished by fields defined at a point in space and fields -defined over an area. - -phi is a point field, while A is a surface field, and E is a point field, while -B is a surface field. This is a very important distinction, because it reveals -that the electric and magnetic fields are fundamentally different types of -vector fields. - -Based on this, what can we conclude about this equation because of the mixture of -point fields defined at a point in space and surface fields defined over an area? - - curl(E) = -dB/dt. - -@copilot: What are the rules with regards to addition of point and surface fields -and what does this mean for this equation? - -GitHub Copilot: In a staggered Yee grid, the fields are located at either integer -or half-integer grid points, whereby the integer grid points represent vector -fields defined at a point in space, while the half-integer grid points represent -vector fields defined over an area. This means that the electric field E is -located at integer grid points and the magnetic field B is located at -half-integer grid points. - -In the equation curl(E) = -dB/dt, the curl of the electric field E is a point -field located at integer grid points, while the time derivative of the magnetic -field B is a surface field located at half-integer grid points. This means that -the two fields are defined at different locations on the grid, which is why the -equation is not mathematically consistent. - -l4m4re: So, what can we conclude from this? - -GitHub Copilot: The equation curl(E) = -dB/dt is not mathematically consistent -because it mixes point fields defined at a point in space with surface fields -defined over an area. In order to make the equation mathematically consistent, -the fields should be defined at the same location on the grid. - - -''' -''' - - -Because of the inconsistencies in Maxwell's equations with respect to the -fundamental theorem of vector calculus, it is now clear that this entanglement -of the fundamental E and B fields in Maxwell's equations is responsible for a -lot of problems in current main stream physics, for starters the non-invariance -of Maxwell's equations to the Galilean transform. - -This is described by C.K. Thornhill in his paper "Real or Imaginary Space-Time? -Reality or Relativity?": - - https://etherphysics.net/CKT4.pdf - - "ABSTRACT - - The real space-time of Newtonian mechanics and the ether concept is - contrasted with the imaginary space-time of the non-ether concept and - relativity. In real space-time (x, y, z, ct) characteristic theory shows - that Maxwell's equations and sound waves in any uniform fluid at rest have - identical wave surfaces. - - Moreover, without charge or current, Maxwell's equations reduce to the same - standard wave equation which governs such sound waves. This is not a general - and invariant equation but it becomes so by Galilean transformation to any - other reference-frame. So also do Maxwell's equations which are, likewise, - not general but unique to one reference-frame. - - The mistake of believing that Maxwell's equations were invariant led to the - Lorentz transformation and to relativity; and to the misinterpretation of - the differential equation for the wave cone through any point as the - quadratic differential form of a Riemannian metric in imaginary space-time - (x, y, z, ict). - - Mathematics is then required to tolerate the same equation being transformed - in different ways for different applications. Otherwise, relativity is - untenable and recourse must then be made to real space-time, normal Galilean - transformation and an ether with Maxwellian statistics and Planck's energy - distribution." - -Yet another problem with Maxwell's equations is that the potential fields phi -and A are not unique, because they are defined as sort of an inverse of E and B, -but are not derived from some primary field like the C field in the new theory. - -It is this non-uniqueness of the scalar and vector potentials in Maxwell's -equations that leaves room for "gauge freedom" on top of which a whole set of -"gauge theories" have been built: - - https://en.wikipedia.org/wiki/Gauge_theory - - "The earliest field theory having a gauge symmetry was Maxwell's - formulation, in 1864-65, of electrodynamics ("A Dynamical Theory of the - Electromagnetic Field") which stated that any vector field whose curl - vanishes — and can therefore normally be written as a gradient of a function - — could be added to the vector potential without affecting the magnetic - field. - - [...] - - The importance of gauge theories in physics is exemplified in the tremendous - success of the mathematical formalism in providing a unified framework to - describe the quantum field theories of electromagnetism, the weak force and - the strong force. This theory, known as the Standard Model, accurately - describes experimental predictions regarding three of the four fundamental - forces of nature, and is a gauge theory with the gauge group SU(3) x SU(2) x - U(1). Modern theories like string theory, as well as general relativity, - are, in one way or another, gauge theories." - -Note that because of the vector identity curl(grad(p))=0, addition of a vector -field whose curl vanishes to the vector potential has no effect whatsoever on -the resulting field, which begs the question of why the addition of a vector -field that has no effect on the resulting field, is supposed to descri`be -something that has any relevance to the real world at all. - -Or, to put it another way: how exactly is the Standard Model, which is a gauge -theory and therefore uses fields that have no effect on the resulting physical -fields, supposed to have any relevance to the real world at all? - -Thus, we can conclude that Maxwell's rather strange entanglement of the electric -and magnetic fields has led to a lot of problems in physics, to such an extent -that it has been accepted that theories that are not only mathematically -inconsistent but also use fields that have no effect on the resulting physical -fields, are completely acceptable and even the norm in physics. - - -While it is useful to write out intermediate fields similar to Maxwell's -equations for our new theory in order to compare them with Maxwell's equations, -it is also confusing to do so, because even though our intermediate fields are -similar to Maxwell's, they are not the same and should therefore be described -with different symbols. - -So let's consider the negated definition of the vector LaPlace operator for a -field C again: - - - delta(C) = - grad(div(C)) + curl_surface(curl_point(C)), - -with C = rho * k * v, resulting in a unit of measurement of: - - [C] = [rho] * [k] * [v] = [kg/m^3] * [m^2/s] * [m/s] = [kg/s^2]. - -From this, we can write out intermediate fields similar to Maxwell's equations, -with completely different meanings and units of measurements: - - p = div(C) - - A = curl_point(C) - - L = -grad(p) - - R = curl_surface(A) - -And because of vector identities we can also write: - - curl(L) = 0 - - div(B) = 0, - -And since the unit of measurement of C is [kg/s^2], we can work out the units of -measurement of the intermediate fields given that the grad, div and curl -operators all have a unit of measurement in per meter [1/m]: - - [p] = [kg/s^2] * [1/m] = [kg/m-s^2] or [Pa] = [N/m^2] = [J/m^3] - - [A] = [kg/s^2] * [1/m] = [kg/m-s^2] or [Pa] = [N/m^2] = [J/m^3] - - [L] = [kg/m-s^2] * [1/m] = [kg/m^2-s^2] or [Pa/m] = [N/m^3] = [J/m^4] - - [R] = [kg/m-s^2] * [1/m] = [kg/m^2-s^2] or [Pa/m] = [N/m^3] = [J/m^4] - -Thus we have found that our scalar and vector potentials have a unit in [Pa] -representing pressure, while our resulting [L] and [R] fields have a unit in -[N/m^3] representing force density. And, since [Pa] = [J/m^3], we can also -consider the scalar and vector potentials to represent the energy density of the -medium. - -Note that the intermediate fields we have defined are consistent with the -fundamental theorem of vector calculus, unlike Maxwell's equations. - -Now let's consider the negated definition of the vector LaPlace operator for a -field I: - - - delta(I) = - grad(div(I)) + curl_surface(curl_point(I)), - -with I = rho * k * a, resulting in a unit of measurement of: - - [I] = [rho] * [k] * [a] = [kg/m^3] * [m^2/s] * [m/s^2] = [kg/s^3]. - -Thus, I represents the Intensity or radiosity field in [kg/s^3], [N/m-s], -[J/m^2-s], [Pa-m/s] or [W/m^2]. - -From this, we can write out intermediate fields: - - t = div(I) - - W = curl_point(I) - - Y_l = -grad(t) - - Y_a = curl_surface(W) - -And because of vector identities we can also write: - - curl(Y_l) = 0 - - div(Y_a) = 0, - -And since the unit of measurement of I is [kg/s^3], we can work out the units of -measurement of the intermediate fields: - - [t] = [kg/s^3] * [1/m] = [kg/m-s^3] or [Pa/s] = [W/m^3] = [N/m^2-s] - - [W] = [kg/s^3] * [1/m] = [kg/m-s^3] or [Pa/s] = [W/m^3] = [N/m^2-s] - - [Y_l] = [kg/m-s^3] * [1/m] = [kg/m^2-s^3] or [Pa/m-s] = [N/m^3-s] - - [Y_a] = [kg/m-s^3] * [1/m] = [kg/m^2-s^3] or [Pa/m-s] = [N/m^3-s] - - -Thus we have found that our second order scalar and vector potentials have a -unit in [W/m^3] representing power density, while our resulting [Y_l] and [Y_a] -fields have a unit in [N/m^3-s] representing yank density, the time derivative -of force density. - -It is not far fetched to assume that the scalar power density field t in [W/m^3] -actually represents temperature, perhaps apart from a constant factor, because -it is a scalar field within a superfluid medium model and the unit of power -density feels like a natural match with temperature, so that temperature -actually is a measurement of power density within the medium. If this is -correct, then there should be no doubt that this model will lead to a much -deeper understanding of temperature and heat related phsyics in general, -especially because we also have the angular counterpart of the scalar potential, -the vector potential field W, which also has a unit of measurement of power -density. - -It feels right to associate current density J with the angular power density -field W. However, the unit of measurment of J would be [A/m^3], which would in -our theory be equal to [kg/s-m^3]. - -eta = 1/mu = 1/4*pi*1e-7 = 8.987551787368176e+16 [kg/m-s2] rho = eta/k = -8.987551787368176e+16 [kg/m3] - -W in [kg/m-s^3] or [Pa/s] = [W/m^3] = [N/m^2-s] J in [kg/m^3-s] or [A/m^3] - -J = W / c^2 ?? - -Dimensons match: - -[kg/m^3-s] = [kg/m-s^3] / [m^2/s^2] - - = [kg/m-s^3] * [s^2/m^2] - - = [kg/m^3-s^3] * [s^2] - - = [kg/m^3-s] - - - - - -So far, we have made a number of references to superfluids, without explaining -what a superfluid actually is. Generally, a superfluid is described as a fluid -that has zero viscosity and therefore flows without friction. However, this is -incorrect. - -An interesting detail is that the intensity field I can also be defined as: - - I = - κ v, - -with κ the modulus or elasticity in [Pa] or [kg/m-s2], which has a unit of -measurement that differs by a per second [/s] from the unit of measurement for -viscosity eta in [Pa-s] or [kg/m-s]. - -This reflects the difference between elastic forces and viscous (shear) forces, -namely that the elastic force is proportional to the amount of deformation, -while the viscous one is proportional to the rate of deformation. - -So, it appears we can conclude that in physical reality there are no actual -static (elastic) forces (at the quantum level) and that deep down there are only -dynamic forces and interactions which are governed by the velocity diffusion -equation (a = dv/dt = -k * delta(v)), whereby what we think to observe as static -elastic forces are in reality the time derivatives of forces that are -fundamentally of a viscous nature. - -This is a very interesting conclusion, because it relates nicely to the rather -strange observed behavior of superfluids, which are currently described by a -two-fluid model, whereby the superfluid component is described as a frictionless -fluid, while the normal fluid component is described as a viscous fluid. - -Since within the new model, we have two scalar potentials and two vector -potentials, along with derived force and yank density fields, and we find that -the potentials and yank density fields derived from the intensity field can also -be considered as fields describing elastic forces, it becomes comprehensible -that the current two-fluid model can perhaps be matched to our new model. - -Thus, the superfluid component would be described by our primary dynamic field C -and it's derived fields, while the normal fluid component is described by the -intensity field I and its derived fields, of which the scalar potential field p -is very interestng, since we assume that one to represent (a measurement of) -temperature. - -This way, we would come to a model whereby elastic behavior (I field and -derived) emerges as function of scalar potential t, which we assume to represent -temperature. Thus, this could explain how normal fluid behavior emerges from -superfluid behavior as temperature rises, which would be a very interesting -result. - -One of the most interesting phenomena observed with superfluid is that quantized -vortices arise when a superfluid is rotated, whereby the quantization gamma is -given by: - - gamma = h / m, - -with h Planck's constant and m the mass of a superfluid particle, such as -superfluid helium. - -When we equate this circulatio constant gamma with the quantum circulation -constant k, we can compute the mass of an elemental medium particle along: - - m = h / k = 6.62607015e-34 / 8.987551787368176e+16 = 7.374e-51 [kg], - -What is very interesting about this result is that we can compute the Compton -wavelength and frequency of this particle along: - - lambda = h / (m * c) = 6.62607015e-34 / (7.374e-51 * 299792458) = 2299792458 - [m]. - -So, for a particle with a mass of 7.374e-51 [kg], we find a Compton wavelength -of 2299792458 [m], which is exactly equal to the speed of light c, but with a -unit of measurement in [m] instead of [m/s]. - -It should be no surprise that when we compute the Compton frequency of this -particle, we find: - - nu = c / lambda = 299792458 / 2299792458 = 1 [Hz]. - -This puts serious question marks on the validity of the mass-energy equivalence -principle, since, according to Wikipedia, the Compton wavelength is defined as -the wavelength of a photon whose energy is the same as the rest mass of a -particle. - -Are we really to believe that the tiniest of the tiniest particle with a mass of -7.374e-51 [kg], about 20 orders of magnitude lighter than the electron, has a -Compton wavelength of no less than 2299792458 [m] and associated frequency of 1 -[Hz]? - -And why does this particular mass (7.374e-51 [kg]) has a Compton wavelength that -is exactly equal to the speed of light c? - -It is these questions, along with the mathematical inconsistensies we found in -Maxwell's equations, that reveal that there is something seriously wrong with -the current mainstream models, which not only involves Maxwell's equations and -everything built on top of it, but also the fluid mechanics Navier-Stokes -equations. - -Now let's introduce a conversation with CoPilot: ------------------------------------------------- - -l4m4re: So, if I understand this correctly, in a fluid dynamics Yee grid you -have a scalar field on the edges of the grid cells and a vector field on the -faces of the grid, while in electrodynamics you have vector fields both on the -edges and on the faces of the grid cells? - - -GitHub Copilot: Yes, that's correct. In a fluid dynamics Yee grid, the scalar -field is located on the edges of the grid cells, while the vector field is -located on the faces of the grid cells. This is because the scalar field -represents a quantity that is defined at a point in space, while the vector -field represents a quantity that has both magnitude and direction and is defined -over an area. - -In electrodynamics, the electric field is located on the edges of the grid -cells, while the magnetic field is located on the faces of the grid cells. This -is because the electric field is a vector field that is defined at a point in -space, while the magnetic field is a vector field that is defined over an area. - -Overall, the Yee grid is designed to accurately model the behavior of -electromagnetic waves and fluid flow by discretizing the fields on a staggered -grid with interlaced Yee-coordinates. By using different field representations -and discretization schemes, the Yee grid can be used to simulate a wide range of -physical phenomena in both electrodynamics and fluid dynamics. - --:- - -This is a very important explanation of the fundamental problem with Maxwell's -equations, because the discretization reveals that there are two fundamentally -different types of vectoer fields, which are: - -1) vector fields that are defined at a point in space, such as the electric -field in electrodynamics and the scalar field in fluid dynamics as well as in -our new superfluid ether theory. - -2) vector fields that are defined over an area, such as the magnetic field in -electrodynamics and the vector field in fluid dynamics as well as in our new -superfluid ether theory. - -This is a very important distinction, because it reveals that the electric and -magnetic fields are fundamentally different types of vector fields. However, -this goes much further than just Maxwell's or Navier-Stokes equations, because -the distinction ultimately originates at the mathematical definition of the -three fundamental vector operators: grad, div and curl. - -These three operators are defined using two methods, namely as a differential -operator and as a finite difference operator. Both the differential and finite -difference operators are defined relative to a certain volume, whereby for the -finite difference operators the volume is discretized into a grid of cells and -therefore has a finite size, while for the differential operators the limit of -the volume is taken to zero and therefore has an infinitesimally small size. - -This means that for the differential operators the resulting vector fields can -all be considered to be defined at a point in space, while for the finite -difference operators the resulting vector fields are considered to be located at -specific locations on the grid, such as the faces of the grid cells for the -angular vector fields (curl operator) and the center point of the grid cell for -the linear fields consisting of a scalar (div operator) and a vector component -(grad operator). - -In other words: it is the operator used that determines the location of the -resulting vector or scalar field relative to the grid cell and therefore the -type of field, either defined at a point in space (grad, div) or defined over an -area (curl). - -In a staggered Yee grid, the fields are located at either integer or -half-integer grid points, whereby the integer grid points represent vector -fields defined at a point in space, while the half-integer grid points represent -vector fields defined over an area, the latter being one of the faces of the -grid cell. - -In other words: in the discretization of a staggered Yee grid each scalar and -vector field is located at a specific location relative to the cube geometry of -the grid cell. Thus, half-integer grid points represent vector fields defined -over an area and therefore a number of cube faces and are therefore located at a -grid face, while integer grid points represent vector fields defined relative to -the center of the cube, which is located half a grid cell away from the faces of -the cube hence the location on a "half-integer grid point" in the arrays used in -simulation software to represent the finite difference fields. - -So, let us consider the definition of the three operators relative to the -geometry over which they are defined and compare the differential and finite -difference methods. In principle, the differential operators can be considered -as also being defined relative to a grid, even though the limit of the grid -spacing is taken to zero. This means that the limitations of the finite -difference operators also apply to the differential operators and a clear -distinction needs to be made between vector fields defined at a point in space -and vector fields defined over an area. - -The divergence operator is defined as the surface integral of the flux of a -vector field over a closed surface. For the differential operator, the surface -integral is taken over a surface of an arbitrary volume around some point in -space, like a sphere or a cube, of which the limit is taken to zero. The -resulting vector is then located at the center of the volume, which is a point -in space. Since in a staggered Yee grid a discretized cube is used, we can -consider the differential operators to be defined over a cube of infinitesimally -small but not zero size. - -So, if we consider the divergence operator, we can see that it is defined as a -surface integral over the area of all the faces of a cube, divided by the volume -of the cube, resulting in a unit of per meter [1/m] for the operator. Since the -resulting value is not located on one of the faces of the cube, it can be -considered to be located at the center of the cube, which is a point in space. -Since fields defined at a point in space are represented by integer grid points -in a staggered Yee grid, the resulting scalar field is located at an integer -grid point. - -While the gradient operator can be defined in multiple ways, including for -vector fields resulting in a tensor, we will only consider the gradient of a -scalar field here. - -The gradient of a scalar field is defined as the spatial derivative of the -scalar field with respect to the three spatial dimensions. For the differential -operator, the derivative is taken relative to a point in space, while for the -finite difference operator the derivative is taken relative to the grid spacing. - -So, the difference of the scalar field is computed between six points at a -certain distance from the point along the three coordinate axes (+ and -), which -are added together and divided by the distance, resulting in a unit of per meter -[1/m] for the operator. In the discretized implementation, the result is not -actually divided by the grid spacing, because it is an actual difference -relative to the grid. - -Since the resulting value is not located on one of the faces of the cube, it can -be considered to be located at the center of the cube, which is a point in -space. Since fields defined at a point in space are represented by integer grid -points in a staggered Yee grid, the resulting vector field is located at an -integer grid point as is the input scalar field. - -For the curl operator, we found that we needed two different curl operators, -namely the point and surface curl operators. The curl operator is defined as the -line integral of the tangential component of the vector field over a closed path -around some point in space, divided by the area enclosed by the path along -Stokes' theorem, resulting in a unit of per meter [1/m] for the operator. And -since this line integral can be computed clockwise or counter-clockwise, we -obtain the point and surface curl operators needed in the implementation of the -staggered Yee grid. - - -GitHub Copilot (edited): In a staggered Yee grid implementation, the grid -spacing is constant, which means that the ratio between the grid spacing and the -area and volume of a grid cube is also constant. This allows for simplifications -in the finite difference implementation of the gradient and curl operators, as -the difference of the scalar field can be computed between six points at a -certain distance from the point along the three coordinate axes (+ and -). - -Similarly, the point and surface curl operators can be computed by taking the -difference of the vector field components at neigboring faces along the three -coordinate axes (+ and -). Since the grid spacing is constant, the resulting -value is simply the difference between the vector field components at the two -neighboring grid points located at half-integer grid points since these fields -are defined over an area. - - -Therefore, in the implementation of the gradient and curl operators in a -staggered Yee grid, the grid spacing can be assumed to be 1.0, which simplifies -the computation of the operators using finite differences. This eliminates the -need for integrals and allows for a consistent and accurate computation using -finite differences. - --:- - -So, for fields defined over an area, half-integer grid points are used, which -are not actually points, but represent te surface area of the face of the grid -cell. And for fields defined at a point in space, integer grid points are used, -which are actual points. This is the essence of the staggered Yee grid, and -therefore point fields and area fields are fundamentally different types of -fields, which cannot be mixed. - -So, let's consider the Maxwell-Faraday equation: - - curl(E) = -dB/dt. - -This equation is defined relative to a point in space, because the curl operator -is defined relative to a point in space. This means that the electric field E is -defined relative to a point in space, which means that it is a point field and -therefore should be represented by an integer grid point. - -However, the magnetic field B is defined relative to an area, because the -derivative of the magnetic field is taken relative to the grid spacing, which -means that it is a surface field and therefore should be represented by a -half-integer grid point. - -So, the Maxwell-Faraday equation is fundamentally flawed, because it mixes a -point field with a surface field, which is not allowed in a staggered Yee grid. - -So, let's consider the Maxwell-Ampere equation: - - curl(B) = mu * J - 1/c^2 * dE/dt, - -This equation is defined relative to a point in space, because the curl operator -is defined relative to a point in space. This means that the magnetic field B is -defined relative to a point in space, which means that it is a point field and -therefore should be represented by an integer grid point. - -However, the current density J is defined relative to an area, because the -derivative of the current density is taken relative to the grid spacing, which -means that it is a surface field and therefore should be represented by a -half-integer grid point. - -So, the Maxwell-Ampere equation is fundamentally flawed, because it mixes a -point field with a surface field, which is not allowed in a staggered Yee grid. - -So, let's consider the Maxwell-Gauss equation: - - div(E) = rho / eps, - -This equation is defined relative to a point in space, because the divergence -operator is defined relative to a point in space. This means that the electric -field E is defined relative to a point in space, which means that it is a point -field and therefore should be represented by an integer grid point. - -However, the charge density rho is defined relative to an area, because the -derivative of the charge density is taken relative to the grid spacing, which -means that it is a surface field and therefore should be represented by a -half-integer grid point. - -So, the Maxwell-Gauss equation is fundamentally flawed, because it mixes a point -field with a surface field, which is not allowed in a staggered Yee grid. - - - - -GitHub Copilot: Thank you for the clarification. You are correct that the -magnetic field `B` is defined relative to an area, and is therefore a surface -field. The derivative of the magnetic field `B` is taken relative to the grid -spacing, which means that it should be represented by a half-integer grid point. - -In the Yee grid implementation, the magnetic field `H` is defined on the edges -of the grid cells, which are integer grid points, while the auxiliary magnetic -field `B` is defined on the faces of the grid cells, which are half-integer grid -points. This is because the `H` field is related to the current density, which -is a point quantity, while the `B` field is related to the magnetic flux -density, which is a surface quantity. - --:- - -This, however is inconsistent. The thinking error is that current density can be -considered a point quantity, while in reality current always follows a closed -loop and is therefore a surface quantity. - -So, let's consider the Maxwell-Ampere equation again: - - curl(B) = mu * J - 1/c^2 * dE/dt, - -Copilot: This equation is defined relative to a point in space, because the curl -operator is defined relative to a point in space. This means that the magnetic -field B is defined relative to a point in space, which means that it is a point -field and therefore should be represented by an integer grid point. - -This, however, is incorrect. Yes, in infinitesimal consideration, one can -consider the curl operator to be defined relative to a point in space, but that -is misleading, since the curl operator is defined relative to a closed path, -which is a surface quantity. So, the point is that even when taking the limit of -the path to zero, it still is a path and not a point in space. - -Thus, we come to the conclusion that both the H and B fields should be defined -relative to a surface, which means that they should be represented by a -half-integer grid point. - - """ @@ -1411,123 +553,3 @@ def save_data(self): - -import numpy as np - -class ToroidalStaggeredGrid: - def __init__(self, shape, spacing, origin): - self.shape = shape - self.spacing = spacing - self.origin = origin - - self._initialize_grid() - - def _initialize_grid(self): - self._initialize_energy_density_point_fields() - self._initialize_energy_density_area_fields() - - def _initialize_energy_density_point_fields(self): - self.px = np.zeros((self.shape[0], self.shape[1] + 1, self.shape[2] + 1)) - self.py = np.zeros((self.shape[0] + 1, self.shape[1], self.shape[2] + 1)) - self.pz = np.zeros((self.shape[0] + 1, self.shape[1] + 1, self.shape[2])) - - def _initialize_energy_density_area_fields(self): - self.Ax = np.zeros((self.shape[0] + 1, self.shape[1], self.shape[2])) - self.Ay = np.zeros((self.shape[0], self.shape[1] + 1, self.shape[2])) - self.Az = np.zeros((self.shape[0], self.shape[1], self.shape[2] + 1)) - - def _get_interlaced_yee_coordinates(self, i, j, k): - x = self.origin[0] + i * self.spacing[0] - y = self.origin[1] + j * self.spacing[1] - z = self.origin[2] + k * self.spacing[2] - - if (i + j + k) % 2 == 0: - return (x + self.spacing[0] / 2, y + self.spacing[1] / 2, z + self.spacing[2] / 2) - else: - return (x, y, z) - - def _get_energy_density_point_field(self, field, i, j, k): - if field == "px": - return self.px[i, j, k] - elif field == "py": - return self.py[i, j, k] - elif field == "pz": - return self.pz[i, j, k] - - def _get_energy_density_area_field(self, field, i, j, k): - if field == "Ax": - return self.Ax[i, j, k] - elif field == "Ay": - return self.Ay[i, j, k] - elif field == "Az": - return self.Az[i, j, k] - - def _set_energy_density_point_field(self, field, i, j, k, value): - if field == "px": - self.px[i, j, k] = value - elif field == "py": - self.py[i, j, k] = value - elif field == "pz": - self.pz[i, j, k] = value - - def _set_energy_density_area_field(self, field, i, j, k, value): - if field == "Ax": - self.Ax[i, j, k] = value - elif field == "Ay": - self.Ay[i, j, k] = value - elif field == "Az": - self.Az[i, j, k] = value - - def get_energy_density_point_field(self, field, i, j, k): - x, y, z = self._get_interlaced_yee_coordinates(i, j, k) - return self._get_energy_density_point_field(field, int(x / self.spacing[0]), int(y / self.spacing[1]), int(z / self.spacing[2])) - - def get_energy_density_area_field(self, field, i, j, k): - x, y, z = self._get_interlaced_yee_coordinates(i, j, k) - return self._get_energy_density_area_field(field, int(x / self.spacing[0]), int(y / self.spacing[1]), int(z / self.spacing[2])) - - def set_energy_density_point_field(self, field, i, j, k, value): - x, y, z = self._get_interlaced_yee_coordinates(i, j, k) - self._set_energy_density_point_field(field, int(x / self.spacing[0]), int(y / self.spacing[1]), int(z / self.spacing[2]), value) - - def set_energy_density_area_field(self, field, i, j, k, value): - x, y, z = self._get_interlaced_yee_coordinates(i, j, k) - self._set_energy_density_area_field(field, int(x / self.spacing[0]), int(y / self.spacing[1]), int(z / self.spacing[2]), value) - - -import numpy as np - -# Define the toroidal grid -R = 1.0 -r = 0.5 -N = 10 -M = 20 -theta = np.linspace(0, 2*np.pi, N+1) -phi = np.linspace(0, 2*np.pi, M+1) -theta, phi = np.meshgrid(theta, phi) -x = (R + r*np.cos(theta)) * np.cos(phi) -y = (R + r*np.cos(theta)) * np.sin(phi) -z = r * np.sin(theta) - -# Define the scalar field on the edges of the toroidal cells -scalar_field = np.zeros((N+1, M+1, 2)) -for i in range(N+1): - for j in range(M+1): - scalar_field[i,j,0] = (x[i,j] + x[i,j+1])/2 - scalar_field[i,j,1] = (y[i,j] + y[i+1,j])/2 - -# Define the vector field on the faces of the toroidal cells -vector_field = np.zeros((N+1, M+1, 2, 3)) -for i in range(N+1): - for j in range(M+1): - vector_field[i,j,0,:] = [np.sin(theta[i,j])*np.cos(phi[j]), np.sin(theta[i,j])*np.sin(phi[j]), np.cos(theta[i,j])] - vector_field[i,j,1,:] = [np.sin(theta[i,j])*np.cos(phi[j+1]), np.sin(theta[i,j])*np.sin(phi[j+1]), np.cos(theta[i,j])] - vector_field[i,j,0,:] *= (R + r*np.cos(theta[i,j])) - vector_field[i,j,1,:] *= (R + r*np.cos(theta[i,j])) - -# Print the scalar and vector fields -print("Scalar field:") -print(scalar_field) -print("Vector field:") -print(vector_field) - From fedaec367daea70d706fe553c1a4ec60e8fba05f Mon Sep 17 00:00:00 2001 From: Arend Lammertink Date: Sun, 7 Jul 2024 07:29:39 +0200 Subject: [PATCH 07/16] First step towards implementing a grid for the new theory --- fdtd/{lgrid.py => aethergrid.py} | 330 +++++++++++++++++++------------ 1 file changed, 201 insertions(+), 129 deletions(-) rename fdtd/{lgrid.py => aethergrid.py} (63%) diff --git a/fdtd/lgrid.py b/fdtd/aethergrid.py similarity index 63% rename from fdtd/lgrid.py rename to fdtd/aethergrid.py index cf6e404..6737e5a 100644 --- a/fdtd/lgrid.py +++ b/fdtd/aethergrid.py @@ -1,48 +1,58 @@ -""" The FDTD LGrid +""" The FDTD Aether Grid The grid is the core of the FDTD Library. It is where everything comes together and where the biggest part of the calculations are done. -The LGrid class is under construction and is to become the grid for simulating a -new theory based on the vector LaPlace operator. It is not yet functional and -should become an alternative implementation of the existing Grid class in the -FDTD library, which is made for simulating the electromagnetic Maxwell -equations. The end goal of the project is to build a simulator for a new theory, -based on the existing electromagnetic simulation library FDTD of which this -project is a fork. +This grid is an experimental implementation of a new aether theory, based on the +discovery of the quantum circulation constant, kinematic viscosity or +diffusivity k, with a value equal to light speed c squared but a unit of +[m^2/s]. + +This theory is documented in this jupyter notebook: + +https://github.com/l4m4re/notebooks/blob/main/aether_physics.ipynb + +Key point is that the quantum circulation constant can be combined with the +vector LaPlace operator to define the time derivative of any given vector field +F within the aether by: -So, for now, the goal of the current project is to refactor / rework the -existing Grid class for the electromagnetic fields into a new Grid class for the -new theory. The first step of impelementing the new theory is to implement the -operators used and the update functions for the fields. +dF/dt = -k Delta F, +with Delta the LaPlace operator. -""" +From here, we can define the acceleration field a as the time derivative of the +velocity field v by: +a = d/dt v = -k Delta v, - - - - +and jerk j as the time derivative of the acceleration field a by: +j = d/dt a = -k Delta a = k^2 Delta^2 v. +This way, we obtain a second order model, in contrast to Maxwell's equations as +well as Navier-Stokes equations, so we can define second order LaPlace and +Poisson equations in full 3D, which was heretofore impossible. +An interesting detail is that the wave equation for the velocity field v can be +written as: -## Constants +j/c^2 + a/k = 0, -from math import pi +which is a full 3D second order wave equation, illustrating the expressive power +of utilizing the vector LaPlace operator, the second spatial derivative, when +combined with the quantum circulation constant k. -c = 299792458.0 # [m/s] - speed of light -c_l = c * (pi/2) # [m/s] - speed of longitudinal "Tesla" sound-like waves -mu = 4*pi*1e-7 # permeability = 1/viscosity -eta = 1/mu # viscosity [Pa-s],[kg/m-s], [N-s/m2], [J-s/m3] -h = 6.62607015e-34 # Planck's constant [J-s], [kg/m2-s] -k = c*c # dynamic viscosity [m2/s] -rho = eta/k # 8.854187817620389e-12 -eps = rho # permittivity -q = 1.602176634e-19 # elemental charge -m = h/k # elemental mass +By writing out the definition of the vector LaPlacian for the acceleration and +jerk fields, various fields can be defined, amongst others the electric and +magnetic fields as well as uniquely defined scalar and vector potentials, +leaving no room for "gauge fixing". +Another key point is that there are only three units of measurement in this +model: the meter, the second and the kilogram. Within this model, electric +charge has a unit in [kg/s], while the electric field has a unit of velocity in +[m/s]. And the magnetic field has a unit in + +""" ## Imports @@ -62,23 +72,54 @@ # relative from .backend import backend as bd -from . import constants as const -#import operators: -from .operators import curl_surface, curl_point, grad, div +from .operators import curl_edge_to_face, curl_face_to_edge, grad, div + + +from math import pi + +## Constants + +# base constants + +c = 299792458.0 # speed of light [m/s] + +eta = 1/(4*pi*1e-7) # viscosity (1/mu_0) [kg/m-s], [Pa-s] + +h = 6.62607015e-34 # Planck's constant [kg-m^2/s], [J-s] +e = 1.602176634e-19 # elementary charge [kg/s] +# derived constants -## FDTD LGrid Class -class LGrid: - """The FDTD LGrid +k = c**2 # quantum circulation constant + # 8.987551787368176e+16 [m^2/s] - The LGrid is the core of the FDTD Library. It is where everything comes +rho = eta/k # mass density (eps_0) + # 8.85418781762039e-12 [kg/m^3] + +m = h/k # elementary mass + # 7.372497323812708e-51 [kg] + +rho_q0 = e/m * rho # vacuum charge density + # 1.9241747011042014e+20 [kg/m^3-s] + +tau = e/k # vacuum torque density + # 1.7826619216278975e-36 [N-m/m^3] + + +inv_rho_q0 = 1/rho_q0 +rho_tau = rho/tau # = eta/e +tau_rho = tau/rho # = e/eta + +## FDTD Grid Class +class AetherGrid: + """The FDTD Aether Grid + + The grid is the core of the FDTD Library. It is where everything comes together and where the biggest part of the calculations are done. - This implementation implements fields based on the Laplace operator - rather than Maxwells equations - + """ from .visualization import visualize @@ -123,7 +164,7 @@ def __init__( ) else: self.courant_number = float(courant_number) - + """ For now, we assume our fields to propagate with a maximum speed of pi/2 * c, since we assume Tesla's longitudinal sound-like waves to also exist @@ -143,42 +184,53 @@ def __init__( # timestep of the simulation self.time_step = self.courant_number * self.grid_spacing / ((pi/2)*const.c) - #define the fields used in the new model: - - self.C = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + # define fields + # velocity + self.v = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - self.p = bd.zeros((self.Nx, self.Ny, self.Nz, 1)) - self.A = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - - self.L = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - self.R = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - - self.F = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - - self.I = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + # first order scalar and vector potentials + self.p = bd.zeros((self.Nx, self.Ny, self.Nz)) + self.A = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + + # first order force and torque density fields + self.L = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + self.R = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + + # first order electric and magnetic fields + self.E = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + self.H = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + + # acceleration + self.a = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - self.t = bd.zeros((self.Nx, self.Ny, self.Nz, 1)) - self.W = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + # second order scalar and vector potentials + self.dpdt = bd.zeros((self.Nx, self.Ny, self.Nz)) + self.dAdt = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - self.Y_l = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - self.Y_a = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + # second order force and torque density fields + self.dLdt = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + self.dRdt = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - self.Y = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - + # second order electric and magnetic fields + self.dEdt = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + self.dHdt = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + + # jerk + self.j = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) # save the inverse of the relative permittiviy and the relative permeability # these tensors can be anisotropic! if bd.is_array(permittivity) and len(permittivity.shape) == 3: permittivity = permittivity[:, :, :, None] - self.inverse_permittivity = bd.ones((self.Nx, self.Ny, self.Nz, 3)) / float( - permittivity + self.inverse_permittivity = bd.ones((self.Nx, self.Ny, self.Nz, 3)) / bd.array( + permittivity, dtype=bd.float ) if bd.is_array(permeability) and len(permeability.shape) == 3: permeability = permeability[:, :, :, None] - self.inverse_permeability = bd.ones((self.Nx, self.Ny, self.Nz, 3)) / float( - permeability + self.inverse_permeability = bd.ones((self.Nx, self.Ny, self.Nz, 3)) / bd.array( + permeability, dtype=bd.float ) # save current time index @@ -200,13 +252,13 @@ def __init__( self.folder = None def _handle_distance(self, distance: Number) -> int: - """ transform a distance to an integer number of gridpoints """ + """transform a distance to an integer number of gridpoints""" if not isinstance(distance, int): return int(float(distance) / self.grid_spacing + 0.5) return distance def _handle_time(self, time: Number) -> int: - """ transform a time value to an integer number of timesteps """ + """transform a time value to an integer number of timesteps""" if not isinstance(time, int): return int(float(time) / self.time_step + 0.5) return time @@ -214,7 +266,7 @@ def _handle_time(self, time: Number) -> int: def _handle_tuple( self, shape: Tuple[Number, Number, Number] ) -> Tuple[int, int, int]: - """ validate the grid shape and transform to a length-3 tuple of ints """ + """validate the grid shape and transform to a length-3 tuple of ints""" if len(shape) != 3: raise ValueError( f"invalid grid shape {shape}\n" @@ -227,7 +279,7 @@ def _handle_tuple( return x, y, z def _handle_slice(self, s: slice) -> slice: - """ validate the slice and transform possibly float values to ints """ + """validate the slice and transform possibly float values to ints""" start = ( s.start if not isinstance(s.start, float) @@ -242,7 +294,7 @@ def _handle_slice(self, s: slice) -> slice: return slice(start, stop, step) def _handle_single_key(self, key): - """ transform a single index key to a slice or list """ + """transform a single index key to a slice or list""" try: len(key) return [self._handle_distance(k) for k in key] @@ -255,27 +307,27 @@ def _handle_single_key(self, key): @property def x(self) -> int: - """ get the number of grid cells in the x-direction """ + """get the number of grid cells in the x-direction""" return self.Nx * self.grid_spacing @property def y(self) -> int: - """ get the number of grid cells in the y-direction """ + """get the number of grid cells in the y-direction""" return self.Ny * self.grid_spacing @property def z(self) -> int: - """ get the number of grid cells in the y-direction """ + """get the number of grid cells in the y-direction""" return self.Nz * self.grid_spacing @property def shape(self) -> Tuple[int, int, int]: - """ get the shape of the FDTD grid """ + """get the shape of the FDTD grid""" return (self.Nx, self.Ny, self.Nz) @property def time_passed(self) -> float: - """ get the total time passed """ + """get the total time passed""" return self.time_steps_passed * self.time_step def run(self, total_time: Number, progress_bar: bool = True): @@ -294,21 +346,18 @@ def run(self, total_time: Number, progress_bar: bool = True): time = tqdm(time) for _ in time: self.step() - + def step(self): - """do a single FDTD step by first updating the electric field and then - updating the magnetic field + """do a single FDTD step by first computing acceleration, jerk + and the intermediate fields, and then updating the velocity field. """ - - self.update_C() # [C] += [F]*dt + [Y]*dt^2 - + + self.update() self.time_steps_passed += 1 - - - - def update_C(self): - """ update the C field by using the vector Laplace operator """ + + def update(self): + """ update the fields along the vector Laplace operator """ # update boundaries: step 1 #for boundary in self.boundaries: @@ -317,32 +366,41 @@ def update_C(self): #curl = curl_E(self.E) #self.H -= self.courant_number * self.inverse_permeability * curl - # Since C represents a force density, it would be located on - # the faces of the grid and thus be a H-type field. + # potential fields + self.p = eta * div (self.v) + self.A = e * curl_edge_to_face(self.v) - self.p = div (self.C) - self.A = curl_surface(self.C) + # force and torque density fields + self.L = - grad(self.p) + self.R = curl_face_to_edge(self.A) - self.L = - grad(self.P) - self.R = curl_point(self.A) + # electric and magnetic fields + self.E = inv_rho_q0 * self.L + self.H = rho_tau * self.R - self.F = self.L + self.R + # acceleration field + self.a = rho_q0 * self.E + tau_rho * self.H - self.I = self.k * self.F + # second order potential fields + self.dpdt = eta * div (self.a) + self.dAdt = e * curl_edge_to_face(self.a) - self.t = div(self.I) - self.W = curl_surface(self.I) + #second order yank and d/dt torque density fields + self.dLdt = - grad(self.dpdt) + self.dRdt = curl_face_to_edge(self.dAdt) - self.Y_l = -grad(self.t) - self.Y_a = curl_point(self.W) + # second order (time derivative) of electric and magnetic fields + self.dEdt = inv_rho_q0 * self.dLdt + self.dHdt = rho_tau * self.dRdt - self.Y = self.Y_l + self.Y_r + # jerk field + self.j = rho_q0 * self.dEdt + tau_rho * self.dHdt + # update velocity field + self.v += self.courant_number * self.a + self.v += self.courant_number**2 * self.j + - self.C += self.courant_number * self.k * self.F - self.C += self.courant_number * self.courant_number * self.k * self.Y - - # update objects #for obj in self.objects: # obj.update_H(curl) @@ -352,55 +410,63 @@ def update_C(self): # boundary.update_H() # add sources to grid: - for src in self.sources: - src.update_H() - src.update_E() + #for src in self.sources: + # src.update_H() + # src.update_E() # detect electric field #for det in self.detectors: - # det.detect_H() - + # det.detect_H() + def reset(self): - """ reset the grid by setting all fields to zero """ - #self.H *= 0.0 - #self.E *= 0.0 - self.C *= 0.0 - self.p *= 0.0 - self.A *= 0.0 - self.L *= 0.0 - self.R *= 0.0 - self.F *= 0.0 - self.I *= 0.0 - self.t *= 0.0 - self.W *= 0.0 - self.Y_l *= 0.0 - self.Y_a *= 0.0 - self.Y *= 0.0 + """reset the grid by setting all fields to zero""" + self.v *= 0.0 + self.p *= 0.0 + self.A *= 0.0 + self.L *= 0.0 + self.R *= 0.0 + self.H *= 0.0 + self.E *= 0.0 + + self.a *= 0.0 + self.dpdt *= 0.0 + self.dAdt *= 0.0 + self.dLdt *= 0.0 + self.dRdt *= 0.0 + self.dEdt *= 0.0 + self.dHdt *= 0.0 + + self.j *= 0.0 self.time_steps_passed *= 0 def add_source(self, name, source): - """ add a source to the grid """ + """add a source to the grid""" source._register_grid(self) self.sources[name] = source def add_boundary(self, name, boundary): - """ add a boundary to the grid """ + """add a boundary to the grid""" boundary._register_grid(self) self.boundaries[name] = boundary def add_detector(self, name, detector): - """ add a detector to the grid """ + """add a detector to the grid""" detector._register_grid(self) self.detectors[name] = detector def add_object(self, name, obj): - """ add an object to the grid """ + """add an object to the grid""" obj._register_grid(self) self.objects[name] = obj + + def promote_dtypes_to_complex(self): + self.E = self.E.astype(bd.complex) + self.H = self.H.astype(bd.complex) + [boundary.promote_dtypes_to_complex() for boundary in self.boundaries] def __setitem__(self, key, attr): if not isinstance(key, tuple): @@ -541,15 +607,21 @@ def save_data(self): Parameters: None """ + def _numpyfy(item): + if isinstance(item, list): + return [_numpyfy(el) for el in item] + elif bd.is_array(item): + return bd.numpy(item) + else: + return item + if self.folder is None: raise Exception( "Save location not initialized. Please read about 'fdtd.Grid.saveSimulation()' or try running 'grid.saveSimulation()'." ) dic = {} for detector in self.detectors: - dic[detector.name + " (E)"] = [x for x in detector.detector_values()["E"]] - dic[detector.name + " (H)"] = [x for x in detector.detector_values()["H"]] + values = detector.detector_values() + dic[detector.name + " (E)"] = _numpyfy(values['E']) + dic[detector.name + " (H)"] = _numpyfy(values['H']) savez(path.join(self.folder, "detector_readings"), **dic) - - - From 6553597714567fdcb6ab66be22e50bf8e3ed9aa7 Mon Sep 17 00:00:00 2001 From: Arend Lammertink Date: Sun, 7 Jul 2024 07:33:17 +0200 Subject: [PATCH 08/16] Revert some changes back to upstream --- examples/Test-LGrid.ipynb | 417 -------------------------------------- fdtd/__init__.py | 1 - 2 files changed, 418 deletions(-) delete mode 100644 examples/Test-LGrid.ipynb diff --git a/examples/Test-LGrid.ipynb b/examples/Test-LGrid.ipynb deleted file mode 100644 index a7eecde..0000000 --- a/examples/Test-LGrid.ipynb +++ /dev/null @@ -1,417 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "enabling-default", - "metadata": {}, - "source": [ - "# 01. Basic Example\n", - "A simple example on how to use the FDTD Library" - ] - }, - { - "cell_type": "markdown", - "id": "academic-rough", - "metadata": {}, - "source": [ - "## Imports" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "tracked-hindu", - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "import fdtd\n", - "import fdtd.backend as bd" - ] - }, - { - "cell_type": "markdown", - "id": "upset-drain", - "metadata": {}, - "source": [ - "## Set Backend" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "breathing-belief", - "metadata": {}, - "outputs": [], - "source": [ - "fdtd.set_backend(\"numpy\")" - ] - }, - { - "cell_type": "markdown", - "id": "copyrighted-custody", - "metadata": {}, - "source": [ - "## Constants" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "unlike-perfume", - "metadata": {}, - "outputs": [], - "source": [ - "WAVELENGTH = 1550e-9\n", - "SPEED_LIGHT: float = 299_792_458.0 # [m/s] speed of light" - ] - }, - { - "cell_type": "markdown", - "id": "fourth-vocabulary", - "metadata": {}, - "source": [ - "## Simulation" - ] - }, - { - "cell_type": "markdown", - "id": "desirable-thread", - "metadata": {}, - "source": [ - "create FDTD Grid" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "vertical-south", - "metadata": {}, - "outputs": [], - "source": [ - "grid = fdtd.LGrid(\n", - " (2.5e-5, 1.5e-5, 1),\n", - " grid_spacing=0.1 * WAVELENGTH,\n", - " permittivity=1.0,\n", - " permeability=1.0,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "balanced-motel", - "metadata": {}, - "source": [ - "boundaries" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "ordered-message", - "metadata": {}, - "outputs": [], - "source": [ - "# grid[0, :, :] = fdtd.PeriodicBoundary(name=\"xbounds\")\n", - "grid[0:10, :, :] = fdtd.PML(name=\"pml_xlow\")\n", - "grid[-10:, :, :] = fdtd.PML(name=\"pml_xhigh\")\n", - "\n", - "# grid[:, 0, :] = fdtd.PeriodicBoundary(name=\"ybounds\")\n", - "grid[:, 0:10, :] = fdtd.PML(name=\"pml_ylow\")\n", - "grid[:, -10:, :] = fdtd.PML(name=\"pml_yhigh\")\n", - "\n", - "grid[:, :, 0] = fdtd.PeriodicBoundary(name=\"zbounds\")" - ] - }, - { - "cell_type": "markdown", - "id": "interesting-onion", - "metadata": {}, - "source": [ - "sources" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "official-happening", - "metadata": {}, - "outputs": [], - "source": [ - "grid[50:55, 70:75, 0] = fdtd.LineSource(\n", - " period=WAVELENGTH / SPEED_LIGHT, name=\"linesource\"\n", - ")\n", - "grid[100, 60, 0] = fdtd.PointSource(\n", - " period=WAVELENGTH / SPEED_LIGHT, name=\"pointsource\",\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "facial-giant", - "metadata": {}, - "source": [ - "detectors" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "included-cherry", - "metadata": {}, - "outputs": [], - "source": [ - "grid[12e-6, :, 0] = fdtd.LineDetector(name=\"detector\")" - ] - }, - { - "cell_type": "markdown", - "id": "chronic-surprise", - "metadata": {}, - "source": [ - "objects" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "corresponding-consequence", - "metadata": {}, - "outputs": [], - "source": [ - "grid[11:32, 30:84, 0:1] = fdtd.AnisotropicObject(permittivity=2.5, name=\"object\")" - ] - }, - { - "cell_type": "markdown", - "id": "separate-variance", - "metadata": {}, - "source": [ - "## Run simulation" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "genuine-audience", - "metadata": {}, - "outputs": [], - "source": [ - "grid.run(50, progress_bar=False)" - ] - }, - { - "cell_type": "markdown", - "id": "norman-bulgarian", - "metadata": {}, - "source": [ - "## Visualization" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "purple-generic", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAdcAAAGZCAYAAADb1nvdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8qNh9FAAAACXBIWXMAAA9hAAAPYQGoP6dpAABLyklEQVR4nO2dfcxcxXX/5+6uwXZ+wfCzajcpGLcIZAwK71T5JWC7rdWKyLz8U1URqQFFAgWVFKlITUlxWxIJl7YJyotSEjBNo77EaQhKozYUkkiNQ1IXIxynIqUu5kVQE2Mi6mAD+/j8/lid5zl7dubemTszd+999vuRHu2zd+eembn73Zlz58zMLYiIDAAAAACS0Zt2AQAAAIDFBjpXAAAAIDHoXAEAAIDEoHMFAAAAEoPOFQAAAEgMOlcAAAAgMehcAQAAgMSgcwUAAAASg84VAAAASEwrO9cHHnjAFEVh/v3f/33aRQEgGOgXdBHoNi2t7FwBAACALoPOFQAAAEhMZzvXm266ySxdutQ8/vjj88eOHz9ufvVXf9WsXr3avPTSS0H2iMhcccUVZuXKlea5556bP/7666+bc845x5x99tnmZz/7mfnXf/1XUxSF+du//dsJG1/84hdNURRm9+7d9SsGFjV33nmnGQwG5vnnn5/47IYbbjArV640x44d87Z31113mV6vZ77+9a+PHb/uuuvM8uXLzQ9/+ENz5MgRc/LJJ5sbb7xx4vwDBw6Yfr9v7r777vDKgJlj48aNpigK698DDzwQZGvRt7nUQnbs2EHGGNq9e7czzdGjR+n888+nX/qlX6JXX32ViIjuuOMO6vV69PDDD4+lPf300+n000+vzPfQoUN06qmn0i//8i/Tm2++SUREW7dupWXLltHevXvn011wwQX0nve8Z+L8Sy65hC655BKPGoLFTJl+Dx48SCeeeCLdfvvtY8dfeeUVWrZsGd12221ENNKdMYaeeeaZ0ryOHz9OV1xxBZ1yyil04MABIiK6//77yRhDX/jCF+bT3XrrrfS2t72NfvrTn46df9ttt9HSpUvp0KFDdaoKFhE+7e6PfvQjeuyxx8b+fu3Xfo36/T5973vfm0+HNpeos50rEdHTTz9NJ510El199dX0yCOPUK/Xo49+9KMT6c444ww644wzvPL+7ne/S4PBgH73d3/X2kjJ8j3xxBPzx/7t3/6NjDH0V3/1V175gMVLlX63bt1Kq1atojfeeGP+2Pbt26nX6813pjfccAP1+/35DrMMbqAuvfRS2rNnDy1fvpyuvfbasTT79++nXq9Hn/jEJ+aPHT16lFauXEnXX399eCXBosO33ZXcfffdZIyhe++9d+w42tyOd65ERH//939PxhhaunQpbdiwgYbDYXT+27dvJ2MMnXjiiRONFBHRsWPHaNWqVfTBD35w/tgHPvAB+rmf+zk6duxYdP6g21Tpd8+ePWSMoS996UtERDQ3N0dr166lLVu21M5z165dNBgMaOnSpbRu3To6cuTIRJorr7ySzjzzTDp+/DgREd13331kjKHHH3+8dr5g8RDauf7N3/wNFUVhvaEJZTG2uZ3vXI8cOUKrV68mYww99NBDSfJ/4YUX6IQTTiBjDD355JPWNH/4h39Iy5cvp1dffZVefvllOvHEE+kjH/lIkvxBt/HR72WXXUbvfve7iYjooYceImMMffOb36yd53A4pPPOO4+MMXTPPfdY0zz66KNj+Vx44YXzZQAgpN391re+RSeccAJt3bo1Sd6Lsc3tfOd67bXX0vLly+mcc86h008/nQ4fPhyV93A4pA0bNtDq1atpzZo1dPHFF48N3zEvvvgiLVmyhP78z/+cPv7xj1O/36dnn302Km+wOPDR786dO+fvGjdv3kxnnXXW/B1lHW6//Xbq9Xp00UUX0YoVK2j//v3WdOeeey5t2bKFdu3aNXb3DIBvu/vkk0/SSSedRJs3b56Pk8awWNvcTneun//858kYQ/fffz/t37+fVqxYQVdddVVU3txIPfLII/TYY4/RkiVL6JZbbrGmff/7309nnHEGnXbaaXT11VdH5QsWDz76HQ6HtGbNGtq4cSMVRUGf/OQna+f38MMPU6/XozvuuIMOHz5c2kDde++91Ov16PLLL6fVq1db04DZxEe3zz77LL3zne+k888/n1577bUk+S7WNrfVnev27dtp586dE38/+9nPaO/evbRs2bKxYYmvfOUrZIwZm7RB5B9c50Zq27Zt88f+7M/+jIwx9NWvfnUi/Q9+8AMyxpAxhh555JG61QWLDB/9Ei3EmWyzeH0nNL344ou0atUq2rRpE83NzRERzTdQH/7whyfSv/7667Ry5UoyxiSJlYHFg49u169fT8uXL6evfvWrE7OGX3755XlbaHNb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maIozKc//emx49u2bTP9ft/8y7/8iyEic+aZZ5pf//Vfnzj/yJEjZsWKFebmm28OzhssbrSG77zzTjMYDMzzzz8/kfaGG24wK1euNMeOHfO2f9ddd5ler2e+/vWvjx2/7rrrzPLly80Pf/hDc+TIEXPyySebG2+8ceL8AwcOmH6/b+6+++6QaoFFjq3t1UPI8m/jxo3BeSzadpdaxI4dO8gYQ7t37x47/ulPf5qMMfQP//APY8fvuusuMsbQQw89RERE+/bto+XLl9O11147lm7btm1kjKFvf/vblWW46aab6IQTTpgvw6OPPkq9Xo8++tGPzqe55557qCgK+s///M+xcz/zmc+QMYZ+9KMfedcZLC58NXzw4EE68cQT6fbbbx9L98orr9CyZcvotttuIyKirVu3kjGGnnnmmdJ8jx8/TldccQWdcsopdODAASIiuv/++8kYQ1/4whfm09166630tre9jX7605+OnX/bbbfR0qVL6dChQ7XqDbpNSNv78ssv02OPPTb29xd/8RdkjKEPfehD8+lmvd1tZef6/e9/n9566y363//9X/rnf/5n+vmf/3m6/PLL6a233hpLzw3KySefTPv27aP169fTunXr6MiRI2Pp/viP/5j6/T595zvfqSzDsWPH6IILLqBf/MVfpP/4j/+g1atX04YNG2g4HM6nee211+jtb387ffjDHx47d/369bRp06b6FwB0nhANb926lVatWkVvvPHG/LHt27dTr9eb70xvuOEG6vf78x1mGYcOHaJTTz2VLr30UtqzZ4/V0dy/fz/1ej36xCc+MX/s6NGjtHLlSrr++uvjKg86S2jbK3nqqado5cqVtGnTpjEtz3q728rOVf+dffbZ9Oqrr1rPOXToEJ122mm0dOlSWrZsGe3duze6HE8//TSddNJJtHTpUlq1ahW9+OKLE2luueUWWrFixXxH/uijj1rvrsFsEaLhPXv2kDGGvvSlLxER0dzcHK1du5a2bNlSO/9du3bRYDCgpUuXWh1NIqIrr7ySzjzzTDp+/DgREd13331kjKHHH3+8dr6g29Rpe4mIXnrpJVq7di2de+65E6MhoSy2dreVnesXv/hF2r17N33rW9+iG2+8kYwx9Bu/8RvO826++WYyxtA111yTrCzve9/7yBhDt956q/Xzp59+mnq9Hv3lX/4lERFdc801dOqpp455WmD2CNXwZZddRu9+97uJiOihhx4iYwx985vfrJ3/cDik8847j4wxdM8991jTcIPE+Vx44YXzZQCzSZ2297XXXqMLLriAfuEXfoGef/75JOVYTO1uKztXPe7/wQ9+kIwxtHPnzolzHn74Yer1enTppZeSMYa+8pWvRJfj85//PBlj6NJLL6UlS5bQ97//fWu6973vffSud72LnnvuOer3+/Sxj30sOm/QbUI1vHPnzvm7xs2bN9NZZ501f0dZh9tvv516vR5ddNFFtGLFCtq/f7813bnnnktbtmyhXbt2jd09g9kkVLdvvvkmbd68mU466SR68sknk5RhsbW7nehcDx8+TKeccgqdffbZNDc3N3/8xRdfpFWrVtGmTZtoOBzSlVdeSStWrKD//u//rl2GvXv30rJly+i3f/u36Y033qCLLrqITj/9dDp8+PBE2ocffpiMMbRp0yY64YQT6ODBg7XzBYuDUA0Ph0Nas2YNbdy4kYqioE9+8pO182ZH84477qDDhw/TmjVr6OKLLx6LgzH33nsv9Xo9uvzyy2n16tXWNGB2CNXtBz7wAVqyZAk98sgjSfJfjO1uJzpXIqI//dM/JWMM/fVf/zURjRqlDRs20OrVq+mll14iIppvUC655JJagfUjR47QunXraP369fNj+vv376cVK1bQVVddZT1n/fr1ZIyZmDgCZpMQDTPbt28nY4x1Fq/vhCbpaHIj+Nhjj9GSJUsmJoAQEb3++uu0cuVKMsaMzcgEs0mIbvn97/zO70zMGpYzdme93e1M53r06FFas2YNnXnmmTQcDueHvx599NGxdN/73vdoMBiMNSi+U8KvvfZaWr58+cSUbh66kzMsmT/6oz+an2UHQIiGmQMHDpAxhm666aaJc3yW4tgcTebuu+8mYww9+OCDE+ddd911NBgM6IUXXvCvIFiUhOiWNWn727Bhw/x5s97uFkREXgtigZWLL77YFEVhdu/ePe2igI7yqU99ytxyyy1m37595pxzzmkkzzfffNOsXbvWvPe97zVf/vKXG8kTgFR0od1t5faHbee1114z+/btM//4j/9oHn/8cfPggw9Ou0iggzzxxBPmmWeeMX/yJ39irrrqqkY61p/85Cfmxz/+sdmxY4c5ePCg+f3f//3seQKQgq61u+hca7Bnzx6zadMms3LlSrNt2zZz9dVXT7tI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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "fig, axes = plt.subplots(2, 3, squeeze=False)\n", - "titles = [\"Lx: xy\", \"Ly: xy\", \"Lz: xy\", \"Rx: xy\", \"Ry: xy\", \"Rz: xy\"]\n", - "\n", - "fields = bd.stack(\n", - " [\n", - " grid.E[:, :, 0, 0],\n", - " grid.E[:, :, 0, 1],\n", - " grid.E[:, :, 0, 2],\n", - " grid.H[:, :, 0, 0],\n", - " grid.H[:, :, 0, 1],\n", - " grid.H[:, :, 0, 2],\n", - " ]\n", - ")\n", - "\n", - "m = max(abs(fields.min().item()), abs(fields.max().item()))\n", - "\n", - "for ax, field, title in zip(axes.ravel(), fields, titles):\n", - " ax.set_axis_off()\n", - " ax.set_title(title)\n", - " ax.imshow(bd.numpy(field), vmin=-m, vmax=m, cmap=\"RdBu\")\n", - "\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "hearing-kelly", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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zjZb/URERNbGqsO/Mvi/++47HDhwAF5eXibLO3XqBHt7e6Smpkrzbt68iQsXLqB79+419qtSqeDu7m4yERERyYVFz/kXFRXh+++/l15nZmZCp9PB09MTWq0Ww4cPx5kzZ/DZZ5/BYDBI5/E9PT3h4OAAtVqNCRMmYPbs2fDy8oKnpyfmzJmDNm3aoE+fPpYaFhERkVWzaPifPn0avXv3ll7PmjULADB27FjExcVhz549AID27dubrHf48GGEh4cDAFavXg2lUokRI0agpKQEERERSEpKgkKheCxjICIisjVWc5+/JfE+f6LHg/f5E1kHqz7nT0RERHWP4U9ERCQzDH8iIiKZYfgTERHJDMOfiIhIZhj+REREMsPwJyIikhmGPxERkcww/ImIiGSG4U9ERCQzDH8iIiKZYfgTERHJDMOfiIhIZhj+REREMsPwJyIikhmGPxERkcww/ImIiGSG4U9ERCQzDH8iIiKZYfgTERHJDMOfiIhIZhj+REREMsPwJyIikhmGPxERkcww/ImIiGSG4U9ERCQzDH8iIiKZYfgTERHJDMOfiIhIZhj+REREMsPwJyIikhmGPxERkcww/ImIiGSG4U9ERCQzDH8iIiKZYfgTERHJDMOfiIhIZhj+REREMsPwJyIikhmLhv+xY8cwePBgaLVaCIKA5OTkGttOmjQJgiBgzZo1JvNLS0sxbdo0eHt7w8XFBUOGDMFPP/1k3sKJiIhsmEXD//bt22jXrh0SExMf2C45ORlff/01tFptlWUxMTHYvXs3duzYgbS0NBQVFWHQoEEwGAzmKpuIiMimKS258QEDBmDAgAEPbHPjxg1MnToV+/btw8CBA02W6fV6vPvuu9i6dSv69OkDANi2bRv8/f1x4MAB9OvXz2y1ExER2SqrPudvNBoxevRozJ07F61bt66yPCMjA+Xl5YiMjJTmabVahISEID09vcZ+S0tLUVBQYDIRERHJhVWH//Lly6FUKjF9+vRql+fm5sLBwQENGjQwme/r64vc3Nwa+42Pj4darZYmf3//Oq2biIjImllt+GdkZOBf//oXkpKSIAjCI60riuID14mNjYVer5em7Ozsv1ouERGRzbDa8D9+/Djy8vIQEBAApVIJpVKJ69evY/bs2QgMDAQAaDQalJWVIT8/32TdvLw8+Pr61ti3SqWCu7u7yURERCQXVhv+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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "plt.figure()\n", - "grid.visualize(z=0)" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "c1054d71", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[[[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " ...,\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]]],\n", - "\n", - "\n", - " [[[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " ...,\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]]],\n", - "\n", - "\n", - " [[[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " ...,\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]]],\n", - "\n", - "\n", - " ...,\n", - "\n", - "\n", - " [[[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " ...,\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]]],\n", - "\n", - "\n", - " [[[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " ...,\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]]],\n", - "\n", - "\n", - " [[[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " ...,\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]],\n", - "\n", - " [[0., 0., 0.]]]])" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "grid.E" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "851c22e1", - "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.9.13" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/fdtd/__init__.py b/fdtd/__init__.py index f2098b5..dc30c53 100644 --- a/fdtd/__init__.py +++ b/fdtd/__init__.py @@ -4,7 +4,6 @@ __version__ = "0.3.6" from .grid import Grid -from .lgrid import LGrid from .sources import PointSource, LineSource, PlaneSource from .detectors import LineDetector, BlockDetector, CurrentDetector from .objects import Object, AbsorbingObject, AnisotropicObject From 209a184a29d53626bdab5ace6e21cbba9eaf651b Mon Sep 17 00:00:00 2001 From: Arend Lammertink Date: Sun, 7 Jul 2024 07:36:52 +0200 Subject: [PATCH 09/16] revert more changes back to upstream --- examples/00-quick-start.ipynb | 172 ++++++-------------------------- examples/01-basic-example.ipynb | 16 ++- fdtd/lgrid.test.py | 40 -------- 3 files changed, 47 insertions(+), 181 deletions(-) delete mode 100644 fdtd/lgrid.test.py diff --git a/examples/00-quick-start.ipynb b/examples/00-quick-start.ipynb index f1fe540..4fc212c 100644 --- a/examples/00-quick-start.ipynb +++ b/examples/00-quick-start.ipynb @@ -18,7 +18,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -52,7 +52,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -100,18 +100,9 @@ }, { "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Grid(shape=(161,97,1), grid_spacing=1.55e-07, courant_number=0.70)\n", - "\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "grid = fdtd.Grid(\n", " shape = (25e-6, 15e-6, 1), # 25um x 15um x 1 (grid_spacing) --> 2D FDTD\n", @@ -152,7 +143,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -168,19 +159,9 @@ }, { "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " Object(name='object')\n", - " @ x=11:32, y=30:84, z=0:1\n", - "\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "print(grid.object)" ] @@ -194,7 +175,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -210,17 +191,9 @@ }, { "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[Object(name='object'), Object(name=None)]\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "print(grid.objects)" ] @@ -264,7 +237,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -282,19 +255,9 @@ }, { "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " LineSource(period=14, amplitude=1.0, phase_shift=0.0, name='source')\n", - " @ x=[48, ... , 51], y=[76, ... , 83], z=[0, ... , 0]\n", - "\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "print(grid.source)" ] @@ -328,7 +291,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -337,19 +300,9 @@ }, { "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " LineDetector(name='detector')\n", - " @ x=[77, ... , 77], y=[0, ... , 96], z=[0, ... , 0]\n", - "\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "print(grid.detector)" ] @@ -377,7 +330,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -408,42 +361,9 @@ }, { "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Grid(shape=(161,97,1), grid_spacing=1.55e-07, courant_number=0.70)\n", - "\n", - "sources:\n", - " LineSource(period=14, amplitude=1.0, phase_shift=0.0, name='source')\n", - " @ x=[48, ... , 51], y=[76, ... , 83], z=[0, ... , 0]\n", - "\n", - "detectors:\n", - " LineDetector(name='detector')\n", - " @ x=[77, ... , 77], y=[0, ... , 96], z=[0, ... , 0]\n", - "\n", - "boundaries:\n", - " PML(name='pml_xlow')\n", - " @ x=0:10, y=:, z=:\n", - " PML(name='pml_xhigh')\n", - " @ x=-10:, y=:, z=:\n", - " PML(name='pml_ylow')\n", - " @ x=:, y=0:10, z=:\n", - " PML(name='pml_yhigh')\n", - " @ x=:, y=-10:, z=:\n", - "\n", - "objects:\n", - " Object(name='object')\n", - " @ x=11:32, y=30:84, z=0:1\n", - " Object(name=None)\n", - " @ x=84:116, y=32:52, z=0:1\n", - "\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "print(grid)" ] @@ -465,17 +385,9 @@ }, { "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "100%|████████████████████████████████████████████████████████████████| 100/100 [00:00<00:00, 137.49it/s]\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "grid.run(total_time=100)" ] @@ -514,36 +426,18 @@ }, { "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAf8AAAHeCAYAAACVAQItAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8qNh9FAAAACXBIWXMAAA9hAAAPYQGoP6dpAADoTklEQVR4nOy9ebgcVZk//jmnqrr77tlIbsKWgGGXzSCD+AWU1S+O8uACAyoIIoqKgIID6LCDgCIDfmXQ30hQh5lxGwZHUAGVVYZFoiyKCpFEIASy3eQu3VXnnN8f57ynTlVXL3cjN8n5PE/ndlfXWaq60593f5lSSsHDw8PDw8NjiwHf2Bvw8PDw8PDweGPhyd/Dw8PDw2MLgyd/Dw8PDw+PLQye/D08PDw8PLYwePL38PDw8PDYwhBu7A14eHh4vNEQQiCO4429DQ+PSUEURQiCoOk5nvw9PDy2GCilsGLFCqxdu3Zjb8XDY1Ixbdo09Pf3gzFW+L4nfw8Pjy0GRPyzZ89GZ2dnwx9GD49NFUopDA0NYeXKlQCAuXPnFp7nyd/Dw2OLgBDCEv/MmTM39nY8PCYNHR0dAICVK1di9uzZhS4AH/Dn4eGxRYB8/J2dnRt5Jx4ekw/6njeKbfHk7+HhsUXBm/o9tgS0+p578vfw8PDYjDB//nxcf/314z7HY/OGJ38PDw+PTQTLly/Hqaeeinnz5qFUKmH77bfHZz/7WaxatWpU8zz22GP4+Mc/PmH78sLEpgcf8Ofh4bFlYdUqIIo29i6yCAKgRRDiCy+8gAMOOAA77bQT/v3f/x0LFizAM888g3PPPRd33XUXHnnkEcyYMaOt5bbaaquJ2LXHJgyv+Xt4eGxZEAJIkqn1EKLltj/1qU+hVCrhF7/4BQ4++GBst912eNe73oV77rkHL730Ei688EJ77vr163HCCSegu7sb8+bNw4033piZK6+pr1u3Dh//+Mcxe/Zs9Pb24p3vfCd+97vfZcbccccdWLRoESqVCmbNmoVjjz0WAHDIIYfgxRdfxNlnnw3GmPU1v/jii/j7v/97TJ8+HV1dXdh9991x5513jvVT85hgePL38PDwmOJYvXo1fv7zn+OMM86waVyE/v5+nHjiifjP//xPKKUAANdeey323HNP/Pa3v8X555+Ps88+G3fffXfh3EopHH300VixYgXuvPNOPPHEE9h3331x6KGHYvXq1QCAn/70pzj22GNx9NFH48knn8S9996LRYsWAQB+/OMfY5tttsGll16KV155Ba+88goALaxUq1Xcf//9eOqpp3D11Veju7t7sm6Rxyjhzf4eHh4eUxx//vOfoZTCrrvuWvj+rrvuijVr1uC1114DABx44IH4x3/8RwDATjvthIceeghf+9rXcPjhh9eN/dWvfoWnnnoKK1euRLlcBgB85Stfwe23344f/vCH+PjHP44rrrgCxx9/PC655BI7bq+99gIAzJgxA0EQoKenB/39/fb9ZcuW4X3vex/e/OY3AwB22GGHCbgTHhMFr/l7eHh4bOIgjZ9M7gcccEDm/QMOOAB/+MMfCsc+8cQT2LBhA2bOnInu7m77WLp0KZ5//nkAwJIlS3DooYeOak9nnnkmLr/8chx44IG46KKL8Pvf/360l+UxidhsyP8b3/gGFixYgEqlgre85S144IEHNvaWPDw8PCYEb3rTm8AYw7PPPlv4/h//+EdMnz4ds2bNajhHo7xvKSXmzp2LJUuWZB7PPfcczj33XACoczW0g4997GN44YUX8OEPfxhPPfUUFi1aVBd74LHxsFmY/f/zP/8TZ511Fr7xjW/gwAMPxM0334x3vetdePbZZ7Hddtu1HC+lxMsvv4yenh5fAMTDYxOHUgrr16/HvHnzwPnmod/MnDkThx9+OL7xjW/g7LPPzpDxihUr8G//9m/4yEc+Yn+/Hnnkkcz4Rx55BLvsskvh3Pvuuy9WrFiBMAwxf/78wnP23HNP3HvvvfjoRz9a+H6pVIIoCFrcdttt8YlPfAKf+MQncP755+Nb3/oWPvOZz7RzyR6TjM2C/K+77jqceuqp+NjHPgYAuP766/Hzn/8cN910E6666qqW419++WVsu+22k71NDw+PNxDLly/HNttss7G3MWH4+te/jre97W048sgjcfnll2dS/bbeemtcccUV9tyHHnoI11xzDY455hjcfffd+MEPfoCf/vSnhfMedthhOOCAA3DMMcfg6quvxs4774yXX34Zd955J4455hgsWrQIF110EQ499FDsuOOOOP7445EkCe666y6cd955AHT2wP3334/jjz8e5XIZs2bNwllnnYV3vetd2GmnnbBmzRr88pe/bBiz4PHGY5Mn/1qthieeeMIGtxCOOOIIPPzww23N0dPTAwC4/Sd3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URThrW99K/7whz+Map358+dnGrrNnTvXmuyff/551Gq1zDXOmDEDO++8c2aOJ598EhdffDGWLFmC1atXQxrr2rJly7DbbrvZ85rd/9deew3Lly/HqaeeitNOO80eT5LEBk+efPLJOPzww7HzzjvjqKOOwrvf/W4cccQRo7pegid/j00ORdojMPmk747Na/ruWBpGne/arTaolDHzK50NECfSlvYNoIv8hBR057gHmK2+l7VUBNDFfzhTCLhCKdQkH1ImgrMvQVkHiUTVWBqESImcMejOgIzZfXBu+ghACyF0b4ou15UBiu5dkSOA2gUzU+d4LK2INye84x3vwE033YQoijBv3jxEUQQAWLp0KQDgpz/9KbbeOlteuVwuZ153dXW1XKdI2G2naFMjdHR0jHoMoLVe8tnvvvvu+N///V9cccUV+OQnP5lWzGyyV8553X7JTeKC7iNBx9hIO18rDA4O4ogjjsARRxyB733ve9hqq62wbNkyHHnkkajVaplzm91/WvNb3/pWxr0BAEGgrXX77rsvli5dirvuugv33HMPPvjBD+Kwww7LxHe0C0/+HpsMGpG+xShI352mHaGBxrpR9Zb0ndOJKNsl/Hx1QEGpe+Zt0vA16TMELK2pb+eS1DOgSEtOG/6QVSB1KQjdCjgWGI4lhms6JmAwSX2xJc7QYWr9qyhAOQSUoij8er8/Ld2wzC+yP6iuBaBYAEgbCHGJtoSpdhFMm4aFDz80IXONZw/toqurC29605vqju+2224ol8tYtmxZxsQ/WvT29mLevHl48MEHrXkdAB5++GG89a1vtWt997vfxfDwsCX1Rx55pOm8e+65J+69915ccsklY94boAlweHgYAPCmN70JpVIJDz74IE444QQAmtgff/xxm3+/1VZbYf369RgcHLSkS7UI2sWb3vQmRFGERx55BNtttx0AYM2aNfjTn/5k7/Uf//hHvP766/jyl7+MbbfdFgDw+OOPj/r65syZg6233hovvPACTjzxxIbn9fb24rjjjsNxxx2H97///TjqqKOwevXqUcU5AJ78PTYRNCX+dkjfGVtE+u0EAaoc8bdv1i/eQ34+NwuAMd1WlxkTf2hIn+aXTlS/ez228Q6l4TEGzgGSJoRp/JMkEsOxwOCITvtbPVLDQBxDKGA4FugpBagEAXpKETqiwLTwhS44ZNL+ImeNfEhA5pobwN4SlfZAcF8zpC4NqhrIJ7CyH+N8zMF2Uwk9PT34/Oc/j7PPPhtSSrz97W/HwMAAHn74YXR3d+Okk05qe65zzz0XF110EXbccUfsvffeuOWWW7BkyRL827/9GwDghBNOwIUXXohTTz0VX/ziF/HXv/4VX/nKV5rOef755+PNb34zzjjjDHziE59AqVTCr371K3zgAx/ArFmzGo5buXIlRkZGrNn/u9/9Lt7//vcD0ILQJz/5SZx77rmYMWMGtttuO1xzzTUYGhrCqaeeCgDYf//90dnZiQsuuACf+cxn8Oijj2Lx4sVt3wsA6O7uxqmnnopzzz0XM2fOxJw5c3DhhReC89RVtt1226FUKuHGG2/EJz7xCTz99NO47LLLRrUO4eKLL8aZZ56J3t5evOtd70K1WsXjjz+ONWvW4JxzzsHXvvY1zJ07F3vvvTc45/jBD36A/v7+uqyGduDJ32NKI0MgDQLO2iXcuqFtaPp5kk7XbE767dQIyM/HHfM2d6Lp3ap+1LVP5iwEgCZ+HRug3QSu9i0c8q+aKoJDVYGBkRgDtQTr4xixUOiIOCpBiOnlMrqiEJ2lAB3lAJVQVxmMnCqDnDMbYED+eTf+oOi6mblYt5SvfW1eaiuGDlxUUjmWlYkXADYXXHbZZZg9ezauuuoqvPDCC5g2bRr23XdfXHDBBaOa58wzz8TAwAA+97nPYeXKldhtt91wxx13YOHChQA0Gf7kJz/BJz7xCeyzzz7YbbfdcPXVV+N973tfwzl32mkn/OIXv8AFF1yAt771rejo6MD++++Pf/iHf2i6F/Krh2GIbbfdFqeffjouvvhi+/6Xv/xlSCnx4Q9/GOvXr8eiRYvw85//HNOnTwegffPf+973cO655+Kb3/wmDjvsMFx88cX4+Mc/Pqp7cu2112LDhg14z3veg56eHnzuc5/DunXr7PtbbbUVFi9ejAsuuAA33HAD9t13X3zlK1/Be97znlGtAwAf+9jH0NnZiWuvvRbnnXceurq68OY3v9laM7q7u3H11Vfjz3/+M4IgwH777Yc777wzI4y0C6Z89QwMDAygr68PS57+YybwYzIwb968SZ3/DcMkt/RtSPpAS03fjm/iHmgV+d+Y9Jtr+Xn3gDtFnXvAuZZW65JLgFL+EiEzxM9NMaDAuAbCXBe+hAIJTaGfkZru+DdQjVEzWQUhZ+iJQpRCjq5yqOsNmLS/KOCFfQnqkPt+56/l5Zdepkuty46ge2atGY5Jg4IL06DHxg2IZk3rRl9fH9atW4fe3l57fGRkBEuXLsWCBQtQqVSaX4eHxyaOVt93r/l7TDkUEn8bmn5mbAP3wGjN++6aTUk/l+PvErM2X7cxD4OjPaf7EjYIUGcAKPMaIKsA09o+1+RIBXEARxN3luHQwkIp5OhlkRmvo/87QoZSFJigPm4C+WCLE8FJzXazDew1Fdy3umuke5Kr4MfAbHVCbQFIGwyRRUAqhcBr/B4e44Ynf48pgwkhfXesM34shX0yleRamPYpYC+/9QzptwhWy88njQWBNH1BZYBhiBfaBx+YYMCI80wGgJN/AMYUmFTQxX4DcC4hFQeUSsv2GqsBM2fVEh0AKKRCLNPrC7iuERAYAcKWIEbryntFZXytBABN7FT+WDEG2cB8oy0D3vTv4TFWePL3mBKo07bbJP3M2Ikm/QZztCJ9GmrjAlqRvjETuPNJZWr/U3U/RRyZatuUAUBE3KgaH2BaAjOAM46QAzLkmboIjDHjFtBNfKqJjv6vxgJCKoxIiQoRfhSgIwJKIe2FQyoG3tDPUgx7TxyLB6UKKgVKXigIJMy6UDw8PEaPKV3b/6qrrsJ+++2Hnp4ezJ49G8cccwyee+65zDlKKVx88cWYN28eOjo6cMghh+CZZ57ZSDv2GC2a1e+nOu5tjc2o2+n4wjEyS/zuWrZoDatf3635Tx31Mj59pO18iYzdfHoaL21XP/2guvw0X+ZumPlCrgv1lJxOfbrGvjbRMyAzt7Rd99LOfy6hul3yEqkwVEuwbjjG6+trWLF2BMvXDOHPa9bjT2s3YNmGIbw+PIL1cWIDB5l7jY5AMlbk6/lTmWPXlcHdz8fDw2NcmNLkf9999+FTn/oUHnnkEdx9991IkgRHHHFEpo3lNddcg+uuuw5f//rX8dhjj6G/vx+HH354pjGDx9TEaGq858dlSN9VYVEsNLgNf2xEubsejKZe0Na3EemTBkoPS4S5Vr4Z0s89tGk/1XRpLxTcFjBtki+FKfFTU52ASF9pAq8JiWoibJvgmmkbTA2GlEobAJGWX411qt/awRivD1SxYv0I/rxuA15YN4hla0bw+oYY1USCASgHen3aQ8Xk/0cBR2DSElt9Zs0e7mfPHOGJ0hcp35/ukYeHx9gxpc3++W5St9xyC2bPno0nnngCBx10EJRSuP7663HhhRfaWsi33nor5syZg9tuuw2nn376xti2RwuMlfQLx7ZwD+Qj+AuHtojcL3YNpGtazzqvFziginPy3fXzsQEuuMoec9/NN/2hdUgLDziD4gAjX70JsDO3A1IBsVCoJVpwGEkEqkKCA+iKAnSXAnQEAWZWyugsBbqjX0lH/4cBQ8mUBW6liWeq+eU+B7dSmw1MdD4PauzD8xKbZ38Pj3FhSpN/HpRbSZWMli5dihUrVmRqG5fLZRx88MF4+OGHPflPQTQz8Y9lXLOxjcr4tkv6NK4uV52IqcEcrUifxpIJu1kZ4Ewco9kW5foT6VMGABlAFNcFeVLrBBGpriVA10PadBgApYCjpxyhMwoxo1JCFOia/5WQW8KPQo4wZ3ZXSu/HHmsQhKcK7qd7jFkhANbvnwYupkJA3kLg4eExNmwy5K+UwjnnnIO3v/3t2GOPPQAAK1asAKDLIrqYM2cOXnzxxYZzVatVVKtV+3pgYGASduzhYqykP5ax4yJ9M6gR6TcifHfdNF/dyfd39pBJkWvhK2eMWWJ3C/xo0ldG+08b9lDN/8A07OGAaf+brqusAMDtuJAxJCWu4x4A62qIgrTLn1I65S9x3AhEylRbgIi77hpUmgqpcvcl8xlZ14fSFQ0lMvfIE76Hx8RgkyH/T3/60/j973+PBx98sO69Zs0dinDVVVeNu860R/tolDc/lnHNxhZF/WfObEK0eRN/fp12SN8lN1d4oHAE1oD06fta5KJQOUGCrAl0qg4G5LbTX2hIP2DkJ2c2ZN5q0dDjQwAMHIwrIOR2H9YaAV1YqGZq/uvGP1rwoGuJQh2IWA45GDiCwrubu19Ir4+ux70PtEklUyGAMhc9+Xt4TAw2CfL/zGc+gzvuuAP3339/po9yf38/AG0BmDt3rj2+cuXKOmuAi/PPPx/nnHOOfT0wMGAbMnhMLMZC/GOxEoylop87rmjoaGIC8tXqaL8cufiA3FxWE7ZdxJz53XNya3Omu+xRxAHjaYAgpfUVX4yzN64FhkCmAoswwXe1RKKWSAzHuhLg+uEYI0KiKhIwMHSWQnQEOg6gswyTdkhNhAruJcteG11fnRCgUouCYrrUsZJAyOHL+np4TCCmdLS/Ugqf/vSn8eMf/xi//OUvsWDBgsz7CxYsQH9/P+6++257rFar4b777sPb3va2hvOWy2X09vZmHh4Tj7oI7oIo/GZj8mMbnd+Q+BkyUfeNxhX541mT8UXR//q4fp986aSNUwZAJu3Ppvspa0qnErx1gYFOmht19QtNlL3WugOUKeresK+UupQvkXgt0e16k0TaVEDKBLDNfqRELRYYHEmwbijGawNVLF8zhOfXrMczq9bj+bWD+Nv6KlZXa6glEkopbXkweyLrRiMw5y9zXqfWjbSmAd0P9/4I2UTA2wKwcuVKnH766dhuu+1QLpfR39+PI488Er/5zW829tY8NkFMac3/U5/6FG677Tb893//N3p6eqyPv6+vDx0dHWCM4ayzzsKVV16JhQsXYuHChbjyyivR2dlp2zx6vLFoFBw32nGtxjcs4+to2e2Mc33xrca3LgGcjf7PWwwaBQLm90AmdXqe3QnLXLLrMlDKkKQh0UxrYEcQoX1SoB6dlwgq7iMxXEvw+nANa6s1jCQSQzWB7rKuLzC9FGF6JUJXOURXOUClFGQEj/w9c8sVU9lh7pTylcYPQQKXjXFQ0O8RTOYCl2jdY2AzxPve9z7EcYxbb70VO+ywA1599VXce++9WL169aStWavVUCqVJm1+j42HKU3+N910EwDgkEMOyRy/5ZZbcPLJJwMAzjvvPAwPD+OMM87AmjVrsP/+++MXv/jFpDfo8dAYT9pesznaKuU7FuLPmdUnhvRHVxFQyJT0rZ8d9RkAjQiOOfugaPt805/E6TVgrQZKl+alfHk9hy6nS5q2thYIVBMJqSQ4A3rKATojjmnlEnqjCF2VEN0Vne8fhVyXFXa2WhRz47jyARJAlHnwtBeCVNDd/Egwksq2E2AMkMq4NFrE9bQDKRXWDNXGNcd4Mb2z1LQyI2Ht2rV48MEH8etf/9r2kd9+++3x1re+1Z6zbNkyfOYzn8G9994LzjmOOuoo3HjjjdYFevLJJ2Pt2rW4/fbb7ZizzjoLS5Yswa9//WsA+rd2jz32QKlUwne+8x3svvvuuO+++/DMM8/gvPPOwwMPPAClFPbee28sXrwYO+64IwD9m3zNNddg6dKlmD9/Ps4880ycccYZALQAcc455+BHP/oR1qxZg/7+fpx++uk4//zzJ+IWeowRU5r8mxELgTGGiy++ONPq0WPy0eqzGWtAX1ukDxSo7KO3EjQjbDuuIPjOPX80pG8DAZ31bVVBIFeXv+A6pLJR9zSf296XyJuK+VD0P2O6DHDgFiFybh5j5KvnZq8hwoCjsxRASGW1+kopQDlIG/9QUKBU2lQPACKRmYI8+pYpe38YZ5rhzY3jUGlHAKcmAVMUP5Hec7KWcAU9ZpzK/5qhGt5y+T3jm2SceOKLh2Fmd7nled3d3eju7sbtt9+Ov/u7v0O5nB2jlMIxxxyDrq4u3HfffUiSBGeccQaOO+44S+zt4tZbb8UnP/lJPPTQQ1BK4aWXXsJBBx2EQw45BL/85S/R29uLhx56CEmSAAC+9a1v4aKLLsLXv/517LPPPnjyySdx2mmnoaurCyeddBJuuOEG3HHHHfj+97+P7bbbDsuXL8fy5ctHtSePiceUJn+PqYk6Ei4IJhvVHG2MqSPwcZC+O34spD/uQEBnrkYpf40i/5VM3QXkPqACP0Km8QdW2+cp8dsyuTy9Bh1Qp8eHgYKQHJVQ2uqDug6AJn8SHkgrtw2HTKBeGHDwRCIKuc3XL0Kau2+EA0Vkz4zQAkhJGQ71HK8KvnObO8IwxOLFi3HaaafhX/7lX7Dvvvvi4IMPxvHHH48999wT99xzD37/+99j6dKlNnj5u9/9LnbffXc89thj2G+//dpe601vehOuueYa+/qCCy5AX18f/uM//gNRFAEAdtppJ/v+ZZddhq9+9au20NqCBQvw7LPP4uabb8ZJJ52EZcuWYeHChXj7298Oxhi23377ibglHuOEJ3+PCcVYA/raPr8N0s+MG4VroFE1wHa0fDu+mXsAxvxN87E0Kt+m+hVZCkiAcNdyXnOzMcYBxVONnAg7cFwJtJFMGiNnCMDAmYLkWtOnPHzmrCeUwkisXQJxomzQH2MMUcBQiRQiqRAohQDZa2sEWyfA1B7gioFLBcHIolEsQ2iDwpYV+f++970PRx99NB544AH85je/wc9+9jNcc801+P/+v//PZiy5WUu77bYbpk2bhj/84Q+jIv9FixZlXi9ZsgT/5//8H0v8Ll577TUsX74cp556Kk477TR7PEkS9PX1AdDuhsMPPxw777wzjjrqKLz73e/OFGbz2Djw5O8xajDmBJ21ScbA6LT9scYSTCTpZ4Y20/KdsY327Wrb+b1Q9oDV5AvInuWeU3wAGKDqQgJzVgUzl3QECvoE85eUGOe7G5QYJzoQsBrrTn/r4wSJUBhOEnSEIbrCAB3lQOf82701scjk1PlsAR9T3Edp/75kafbDlkPzjVGpVHD44Yfj8MMPxz/90z/hYx/7GC666CKcc845Db+fdJxzXvf9jOO4bkxXV1fmdUdHR8P9UIrqt771Ley///6Z94JAV33Yd999sXTpUtx1112455578MEPfhCHHXYYfvjDH7ZxxR6TBU/+HmOCW5RmtGb+Scvzty/cQc3HNqoGaIc2I7FRBAK2qghIpE8k7e6BNHaq0JeZm05S9c9pX4lM2wPTWtYSwFJhAeYcQEf+x0JZ0t8wEmNdLcbqkRrWjSQIAx3Bv1WnQilg6GIhODNFhhqkR9L89amZKlvi2FgCOGn+xh2gkAZEToQgML2zhCe+eNgEzDS+PYwHu+22G26//XbstttuWLZsGZYvX261/2effRbr1q3DrrvuCgDYaqut8PTTT2fGL1mypFCjd7Hnnnvi1ltvRRzHdefOmTMHW2+9NV544QWceOKJDefo7e3Fcccdh+OOOw7vf//7cdRRR2H16tW2VLvHGw9P/h5jxmhNrpNR1S8zZrSk74ypCyUYI+nnXQSNSJBIH0ir99F8RMak4bvleYusBjbVz8yrA/5MMKDjl5dCphq0ESSIqBlzyBnQUf9C1waoJQIbYoGRRCAxAYDdJY5yEGCrjjJ6KpGN/i+FxSl/0uzLNiBy7xm0JYKbfbiCADc9CljB12K8Jn/OWVvBdlMBq1atwgc+8AGccsop2HPPPdHT04PHH38c11xzDd773vfisMMOw5577okTTzwR119/vQ34O/jgg60Z/53vfCeuvfZafOc738EBBxyA733ve3j66aexzz77NF3705/+NG688UYcf/zxOP/889HX14dHHnkEb33rW7Hzzjvj4osvxplnnone3l68613vQrVaxeOPP441a9bgnHPOwde+9jXMnTsXe++9Nzjn+MEPfoD+/n5MmzbtDbhzHo3gyd/jDcEbpu1vRNJvx1JAkfpASohZ/31Wyy8KBsxOmhYaIiGCSF+Y6P9ESmPCpzV0yh+gKxCS7zy/Ar3mjKEScAQ8QiUMwBlDTxSiEgXoLIeoRByViNvAQHevJEwQ6dNehMzeP27JniFgJPCk5YYBlmletKX5ALq7u7H//vvja1/7Gp5//nnEcYxtt90Wp512Gi644AIwxnD77bfjM5/5DA466KBMqh/hyCOPxJe+9CWcd955GBkZwSmnnIKPfOQjeOqpp5quPXPmTPzyl7/Eueeei4MPPhhBEGDvvffGgQceCAD42Mc+hs7OTlx77bU477zz0NXVhTe/+c0466yz7N6vvvpq/PnPf0YQBNhvv/1w5513gvMpXWNuswdT7eTTbeYYGBhAX18fljz9x0mvDzBv3rxJnf8Nw8svT9hUE1kCuNnYViWAG40dD+k3ivwH0mOkuVttvwHpF83lChDCvO+2+KUaAIRM4x9TdZBSDcljoAAIY/aP6W8i0zk4QyXiprUvt3nqDADfemswQ95K6RgCVzB56aWXrGCi6F4YPwRjsG6DINAdBIOA1d2DVt+NWdO60dfXh3Xr1mWqd46MjGDp0qVYsGABKpVK0zk8PDZ1tPq+e83fY0Iwlsp+byjpA02i+VoTtztPK9O+O9b137tbtxZ8movzQo1WydQdkO8SWJcF4Gj2MBH8kiuEKtWsLeHDMfk712GFCs5QCoFEykylQJhxNId2EWgBQQHgNaHbAZu5pWPm11aPNIVPGSuFUunmhRFGIqkgQ44IXBcmclv7enh4jBue/D3GhYki8MmIBxgv6eeHjoX081t2099csq+LDchF/xPZ190CN2DPhgTU74sCB91AP1rX1abpbxAwBABCyayJ3q0vIKXCSCKRCImRWCIROko/qCboKGnfv67nk95IsmoQJAAh0yBDIZUVLIRiiBSAUAf8ReBbZElfD4/Jgid/jzFhLD76RuMmNR6ggCwbjW2W8sdyT1pV89PH6Nx0TFPCz0X/Z4r5uHth6VwuqdOcriuhCJQ/n2kiZJ3pej6XtIVDzkJIVIWOJajGutXviNAFeDujEKWaQBhwhFyBTAOMAYqlhYc4U1DGxyAZIBkVGgJgXRYcWjwAEHIwphAAbZXC9fDwaA1P/h4Tgsmo6lc0ptW4iU75axUIOBrSb1a219XsXdJ39+Omubkm+/p5md0HBRjmgwETkcYEKGcUxRrY26gUEqUgJCzhj8QCQ4nA2lrNdiKsBAECxhAa+z51IITx/0ultfpIRxqCc4VYKISQeh+M3AAm5kAKQHGb/aCtGrzpffTw8Ggfnvw9xo03StsfdQlgYNKi/5sHAaZrNnIRNKvm55K9exluR75GGQBuRgERvg38cx6JVNb3rteCcQlo4nbjHIj4dfCfxLDQaX+xkNDV/Ti6ohAdUWADAYNA5/wDRP5pqWGlGJSpKFgDwCARM0AqbgMUtQCgwARDyE1rX6bAlK4B4OHhMT548vcYE9oNvHrDtX0g4/IebTxAM7O8HecMHU2ef6vIfxpP5nz9Wv91S/Pa+YzWDWSj/l1TPtX7FyotsCMyAkba7jeA1qpdoUNBjwsDhVBobT4MOLojiT4R2dflkKOzHKBcCVGOAhu1rwP5mWkZpJ+HgaPRRxyCMzAhAUgIqZ9LlbowqG+BUAqBysYoeHh4jA2e/D0mDaOp6pc/v51x4yoBPJYgQKCJtj96Ld9d1g2GaxQb4AoMec2etpMPNKTztOUAUIyBKWPXdwIAKfXPBgSmy9s0QikV4khaU79SRmgw40shBy8FtgsgctdE0H57hiAAQqmQMGmbBgkhUWXcBgHSNUllSiAzXf8f8AKAh8d44MnfY9Iwmh/n0VoIJjTlr814gLphLaL/G2n6TmxdSxeBFhbqAwElDNErZ25kI/8zF1cAch1wZIMGef46AIRKQXLd4c8lZvdUPUcqqNBe3fulzFi3s18Y6IA+xoCEMzAubX0ApXRtAlcQ8Zq/h8f44cnfY6NjNBaCia4E2GxssyBAl2BHE/kPUBe+9qwFjUoAu/PRWLeRjy3c0yZH2uBAAMI0a8lX/bP3kWXHUQwBoPP8w8AE+wE2j59Ba/w1IXXAojLFhcjawBlCyZBwhSBgSBLTVliShSJ3IQrN5BoPD48W8OTvMSEYizY2EW6BVmPHGwRYMKxhEKAdW+AaYDlhg6E4aj2v5Wd8+Oa5FRjQpOlPmy4D8ulTUCDFDuQ3rg3tJiffsTQIpRAnOnuAMYANxaiUAhvsJ123A2cYiUXGzRDItIof4wwhAzjjCBizwYn5++bh4TF++OLKHhOCsZphWZFWV4BG8QDNSDhLYs4DzSLlibkdMzoNM08ajpVOql5RTABjacS+Q/w0Vso0Kt/157vbD8wcIWMIOUfI0056nLFs62AT5CeEhDC5+bGQqJqo/VoiMVITGKZHTA+ZPsx7Q855gzWpH1WBDSMCg9UEQ7Uknaem0wFHYpE5NlRNMFgVGKolGKrqx3AsUE10AyFyCQQBR2QeuoSwFhAyfQO2MCng5JNPtt+hKIowZ84cHH744fj2t79t2+q2g4svvhh77733hO9v/vz5uP766yd8Xo/Jg9f8PcaF8fhf3+gSwKO1EIxW08+v3W5MgBuklykHjNTFUBgQ6G6UZBZZbzHI1A9QaZMdJXUOv34vrbRnYgFpsZy/PbUcCKUFi0RogYVzAEKCJwxSMpuuJ+y8CkPVxO5fE7tESWiSVyZQMAiMEMMAKGb7AACpa2NLxFFHHYVbbrkFQgi8+uqr+NnPfobPfvaz+OEPf4g77rgDYbjp/5zXajWUSuNrc+zRHrzm7zEuTOYP8Vii/8dSAtjV9rOD0FzTLyD+jIYPWC2/LoiPNH2XfHOCA0XRB3nN3jFHECEL07o3kQqx1Jp9Ylryxom0DXoSIU36nxEujCUi4EAY6Dz8UsQQhQzliKMSMpRDhop5lEsc5Yib9zjKYYByKUBHKUA51No6Q1oCWNcJ0OvWEolqLG2hIG0NMA9jXagmEoljBWA824ioWY2D0UIqidUjqzfqQ6r2tfZyuYz+/n5svfXW2HfffXHBBRfgv//7v3HXXXdh8eLFAIB169bh4x//OGbPno3e3l68853vxO9+9zsAwOLFi3HJJZfgd7/7nf2etjOOcMcdd2DRokWoVCqYNWsWjj32WADAIYccghdffBFnn312nTXuRz/6EXbffXeUy2XMnz8fX/3qVzNzzp8/H5dffjlOPvlk9PX14bTTTkOtVsOnP/1pzJ07F5VKBfPnz8dVV1012o/XowU2fVHRY5MAkfJk1gcoQlvjiuSFNrR9GuqSvhnaVGjI+/QpPz+v5bt97RvtI1+9TzkuA7vNumwAPZ6bSH+r29tgwVQoyNQXUOm6tIatFKh0MSBRDgCklgQlAQZ9fiKVLQWcSIWIM4RMa/1lESCJjJWgBNPQJw0GdK95ogTOtdW1OPg/D56QucaK+467DzMqM8Y8/p3vfCf22msv/PjHP8app56Ko48+GjNmzMCdd96Jvr4+3HzzzTj00EPxpz/9Cccddxyefvpp/OxnP8M999wDAOjr64NSqum4GTNm4Kc//SmOPfZYXHjhhfjud7+LWq2Gn/70pwCAH//4x9hrr73w8Y9/HKeddprd2xNPPIEPfvCDuPjii3Hcccfh4YcfxhlnnIGZM2fi5JNPtudde+21+NKXvoQvfvGLAIAbbrgBd9xxB77//e9ju+22w/Lly7F8+fIx3yOPYnjy3wywqaQ+jZX43/AxbWqWNNo9txnx06A6AwNjgPkMuUPMzcrYppX8nNiAfIAiEbYbAOjst+5YQRAhc+eya6dWChGmLgUhFUQ5tFp/IhUCoQMUYyEhlS7Qm0BbJ6TiqEIgVtKM55AqBJSCNPUCSuCZ+7kpfM/faOyyyy74/e9/j1/96ld46qmnsHLlSpTLZQDAV77yFdx+++344Q9/iI9//OPo7u5GGIbo7++343/5y1+2HHfFFVfg+OOPxyWXXGLH7bXXXgCAGTNmIAgC9PT0ZOa97rrrcOihh+JLX/oSAGCnnXbCs88+i2uvvTZD/u985zvx+c9/3r5etmwZFi5ciLe//e1gjGH77bef+Jvm4cl/U8dYSI/GjeuHdN68UZ0+qpVGqfWPx8efH8qK2C43rkkCQQs3QeP0P6uRE+G6Knpuz7aan0pT6dyyvu5+rIfAmbfpa2fZOqEmNycAcJVmCATmesidIaRCTSiEAUMt0W6LjiiwOfwAEAuFmgA4dFU/MF3THwBQMkGOJg7Aoxj0f/mJJ57Ahg0bMHPmzMz7w8PDeP755xuOb2fckiVLMlp9O/jDH/6A9773vZljBx54IK6//noIIRAE2kq0aNGizDknn3wyDj/8cOy888446qij8O53vxtHHHHEqNb2aA1P/pswXNIbT6rdZGlTo03lK8JkjGtF/M0q/DUj/ka+6JbE76zbKKBNOSSbMevnhJFMMCDL5vvnLQutrrk91JvkqVmQlAqRkEgERxRIhIFERylALCSAEImUUMrEKSiFRAjw2LkpTKZWCL5pWLc2Bv7whz9gwYIFkFJi7ty5+PWvf113zrRp0xqOb2dcR0fHqPdV9JtUJKh3dXVlXu+7775YunQp7rrrLtxzzz344Ac/iMMOOww//OEPR70Hj8bw5L8ZoR0BYKyWgtHuY7LHZAUf/bcVNzQi/XbHqNyxjGZcQNqNSN+6C5xzU19/4zmyAYLZkr6ZcY753iV9lhMEWl13kaDjLFF33bbYECe3AcCYbu8bmqY+XeUANcERcAkhJUqJtDUCqFAQpScmAVBNGBByBCabYCIFgGnlabjvuPsmbL6x7mE8IJP92WefjW222QYrVqxAGIaYP39+4fmlUgnCxF0Q9t1335bj9txzT9x777346Ec/2va8u+22Gx588MHMsYcffhg77bST1fobobe3F8cddxyOO+44vP/978dRRx2F1atXY8aMscdHeGThyX8TxUSmwI12rYnWwPJE3i6J0/ntjC0iX0KehOvHZgncnbO5hcEJ6HP85BR456bRFVj57Rw2RU+lLXjpuI3DM3txSZ/nSJ83iCHICylW2MgJGHbzZsPWXdBgLcaYySLQZB8GDJ2VEFEiEQUMsRECEqF9/kyk5B9LhSBR4Ewi5EwHCJoghon6/nHGxxVs90ajWq1ixYoVmVS/q666Cu9+97vxkY98BJxzHHDAATjmmGNw9dVXY+edd8bLL7+MO++8E8cccwwWLVqE+fPnY+nSpViyZAm22WYb9PT04LDDDms57qKLLsKhhx6KHXfcEccffzySJMFdd92F8847D4CO2r///vtx/PHHo1wuY9asWfjc5z6H/fbbD5dddhmOO+44/OY3v8HXv/51fOMb32h6nV/72tcwd+5c7L333uCc4wc/+AH6+/ubWi88Rg9P/psgxqO9583NEyUwjFewGAuJA4216EZj8iSeD37Lj2tkrs8o2o3GImuuV5b10z3TZ5D/LFyBgSLnpcqa/mltInw4JOwKAPn95WMHXDeCW+mP1oPSXQGhVN39dq0KOh0xbRTEzeuAm30FDOVAV++LAo5YSJQChprgSIRCICRt0M5PpYMDpRBIpRsGbaHW/5/97GeYO3cuwjDE9OnTsddee+GGG27ASSedBM51nMSdd96JCy+8EKeccgpee+019Pf346CDDsKcOXMAAO973/vw4x//GO94xzuwdu1a3HLLLTj55JNbjjvkkEPwgx/8AJdddhm+/OUvo7e3FwcddJDd26WXXorTTz8dO+64I6rVKpRS2HffffH9738f//RP/4TLLrsMc+fOxaWXXpoJ9itCd3c3rr76avz5z39GEATYb7/9cOedd9pr9JgYMPVG2IGnOAYGBtDX14clT/8RPT09k7rWvFEGyhVhogLcmpF/M7N6Oz5xd41mYxqReNG4RiZ4N8WuXfN7ur9iU3gjbdhdB8ia1vNjpUOsbsVAuj6bt456rdwle9vj3rUeKFo/JeCMxu+Y3t3z3fF2TjO//psN2CMBgQr10GvGdGvdvGuB6hGEQZqbHwUcjAEhZ1jxyiv2M0hMAaDYFAqqCQkh02JDnDOEXBcDKoccoan618iCQZg1rRt9fX1Yt24dent77fGRkREsXboUCxYsQKVSaTqHh8emjlbfd6/5b2KYKH86Y+l7zTTl/PnN1zF/aQwaa/GNfMpZgmzPBD9a8zuBsXbGFggOjAGqsWDjmuqJ+KUz1vXtF5n7tTbupNCp+uA+2oYlfCoCRHvKXTsRqnUdKNioe1FA+rbEsHPMvS4gG2tgTf2AJf0w0MQfhRxRwFAKOBKhg/iCgKHEGFQAlAKOWOpKf7EpVCSkytwbsnTQPfDBfx4e44Mn/y0I7ZjWG2nw7ZjigcZE3nBPufGTFU+Q32PLfbnWhdx4oEVkvzPO/Qtat4nlWuXINUv2DBypb98187uR/O6e8xYIMulTid9G2n7ezZA/ruAE6JlNcsagoBBxbqsTEvFTnf7BmtCvFc+4ByJwBEyb9cnUT/uvc1208wF6eHg0hSf/TRQToZW3HOM8b6bF0/yjJfIiUlVmkfzIVv73Zhp86iJwxue09+JrcjRumohl70XT+0Hm/rzggKyvP79eSrrpHDQOOV9+vptfOk+qrZO2T1X2bNldl8wdrT7zGdJlG8FDMYCDOa2GFRKlzfUAEEtp2/lyxtAR6g5/5dCQ/0iCcsShIuga/owjcEz5EbkCZNrSl4Sd0du8PDw8GsGT/yaEsaSqZcbD8dcCGfNzO2OLzmzmT286X4tTR2uZaGedZnssii1ohpak32DuTLW6zLjGBGwj61FM+tx8njRPXlvPa/nuOen+FALGtKYNZt5jmTiDvEWAiDqRHDGTutGPApSp4AfA/u0IA5QCjvUjCWoJRy1RqIQMshSgJBVCIwAw06WQS6arAvI0UNIr+x4eEwdP/ps5ioisHR95frwrMEz0nsZixagzpTdZJ+8rb2uPRQdb7K2hYOE8cc302bEN3AQUI4DUWuCm1AFk+chZDhoQP8hnbuZWNkaAOWvWC00AnDTDVLCgRkFCcgiptN9eSnDBEEtpBAEFlSgMJwwbqjE6hS70E4cBalKhEgUohwrlkNsUQXIFKKWse4G3Kah6eHi0hif/TQxj0frryNKahwE4edP5qPYic3XjNZTzPDXFF5nwC8e0mJ/OKZ6nXaGhPlWt5ZjcayLiRnPUEbgTgFdUbEfvK+ujz+zT0e6b5uw7ZniX+AX5zpFOSNlyrruAsXSP7nPAsRaptMYAtdmlNRITsU/dA2uJxEgiEEuJRCam26DA2loNVREijBl6ohCxCCCEgow4gBClgCMMjduA091OO99RD4LxwCc4eWwJaPU99+S/iWDiovzrTc4TsZ4lh3bN8XadYlKfDCWP5VTaxkF7zhikgolbE6BROqE7zhV8Mn5+FEf4A7mYjNz5lEvvokgbJ3KmvZJer90FWZLP1AMwa6aNfQpuork/FPTnChqxIwDUEolKwlFLJCKurQFDSYIADFJJDMV6f1o4UBAyQKIUOqIAFRUgCrm9x1bQodiHMX45oigCAAwNDY2pXK2Hx6aEoaEhAOn3Pg9P/psg8ibvZppvQ398mxq5e6ydOgJ2f86e2tXMJxuN/f3NxxU1uHEJudEarlme5smb/DPZARn3R0rATWv+I0v8aU2ArJDjEjqlBTIGBCybp5+/3nYhbXqg1vqt9h8LlGpaCGBM5/oLBQwLgQQKgzFDZyIhZYREBs59YtYCYPfDRi+UugiCANOmTcPKlSsBAJ2dnT5l0GOzg1IKQ0NDWLlyJaZNm9awlLIn/00Io/3do0CuZuOKfvxIi6c1LYmbf5oJGsVrtL3lzFztjmv/PGaFmNEIPu65zQhf5c5z0awYUNE+i3z8edio/Rzx0xy0dyJ8chcQ4bvFgBqhKLPCnd9eH0/nKoXcWAIUyjFHOQpQjQXCgGN9uYSqkIiVNuXXpIRMYnCmMwj0fRJWEAnDbFW38ZI1tZwlAcDDY3PFtGnTMi2W8/Dkv4mjma+8WINP/fFtR/rnxuTHFvnF3yik2nXzVZm132fN7q32mp+2cUXARuObN9Nxff3ufpoRvxutn6bupZkCdHrgED5V32tqRXDmdUv+Fgkp7v4Cx5Kg3Qm6wE8Y6PfKocSwqdDXW4lQTXT73poUGFYJpFKoCv2XSgQH5t65QsVEgDGGuXPnYvbs2YjjuPUAD49NEFEUtWye5Ml/E8NojZ6trAVN8+PHElxo/o7l57qdoL/xIkP8o9QiWxH5aM9vZilpqvE7Wr5bh98VIPQc2qxP5B8GxbXR85X9Mg8SMqjpjlmEYg9CzhAE3HbtK4c8Y1Eomfz+KJAohxx9HSGGalrrH4p1QZ+qFKhKiaqUKCVcVwgMOMJQN/WJwMEmUAAAtAug1Y+jh8fmjE2qU8JVV10FxhjOOusse0wphYsvvhjz5s1DR0cHDjnkEDzzzDMbb5NTEHn/r/7bnv/eRVuEVzhfy2F1Y5stxRgy6W+Z4w3cGOn76bE8MTfKzydttljjb3yvGhG/UioTqJfdvzs2O06aqHpbftfR+N05qHKe+8inyWlCl4gTiWoiMRJLDNcEhmoCg1WBDVWB9SMJ1g/HWDekH2uHYqwdjLF6Qw2rN9Swan0Nr6+v4fWBKl5fX7XH1w7FGKomJgNAEz03hX46yiE6ywF6KiH6KhFmVcroK5XQFYYom8YtiVBIEqkfMu0r4OHhMXHYZDT/xx57DN/85jex5557Zo5fc801uO6667B48WLstNNOuPzyy3H44Yfjueeem/QmPRsL2eIsLc4d7dx2jfR1fpX8umRRH8t6zB3c6twmp45GLxyNBp+Pzm/Wb8B9v9X8eXN/Zk1nvfoxab1/EiCoCh6NBXNq/fNs2V93TCy0+Z3S8xLnL0XtU6nd4UQgkQo1qf9yBsRSocR1054SD9BTClEOtY+/sxSgoxSgHPLUlB9ydJZD3bAn0PX9wRhYFRimtYRCFQIlwVGTHKGQWoAZhZvKw8OjNTYJ8t+wYQNOPPFEfOtb38Lll19ujyulcP311+PCCy/EscceCwC49dZbMWfOHNx22204/fTTN9aWJxSNtMt2/PYZYnaCthqNbYeLRxMvMBq064d3z9XPi/3XxeMaB/sVnp8bO164fnUSGvJrFAkNaUleV+PPjUEa0GfrApjxQiowBmsxEJK66OmofArQcwWAqpComcewEEikzt2vCQUJhcGqJmalFMKAobes8/T7ShGmlUvoKmsBIBYhopCjEmkze8QZglJogxEDxhDFAiEDGPT+tYCSFimaKhkjHh6bCzYJs/+nPvUpHH300TjssMMyx5cuXYoVK1bgiCOOsMfK5TIOPvhgPPzwww3nq1arGBgYyDw2FqgOe+vz3oDNGORrzrc/LjXH54/XnZsf16Yfvs7qkNPMm43LV8VrtpZ7HY199tmiSJmxDQg8T/yZAjx2bNE4t96/a/1JTf3ZSH6zR5gYAQVD7JrMq4k2+btpeaTxu/UC3H3RS6EUaonCUCwxMCLw2mCCl9bV8OKaKv6yahhL1w3h+XUbsHzdEFatr2HVhhrWDcUYrCaIE+0GCAOGchSgqxyitxKguxKitxKhs6Rz/Dkz94cEHdne/xMPD4/2MOU1///4j//Ab3/7Wzz22GN1761YsQIAMGfOnMzxOXPm4MUXX2w451VXXYVLLrlkYjf6BmAsP31FZvxG7WjdFL+6Ocw/jQgzzQIYwyZHiaKMAzpeeD6yGnZTc3xBVPtEIK2Ol/XTZ/fVXOiSKq3W5wZWUnCg29aX6v3T50kBgolI4wZioZwqgGlWh80SYBwlxVEKJMqSawsA5ygFAsOJQCwURozVYENNYjiWGK5JrBlKUIk4pneG2LonxlZxBTM6SlAKOu8/5CgHFAio3QJgAowxY4XQbX+hFBTSyoK+m5+Hx8RhSpP/8uXL8dnPfha/+MUvUKlUGp5XFFDVTKs7//zzcc4559jXAwMD2Hbbbce/4SkM81tapyXn7xWdZ187x0e/ZnMTeyNho925gda+dXu+HTc6N4G7Vv5cV3un84r2kynbq9LqeNk1itd2tX73WgLHzJIv2AOk+6JMAPLdJyaCXzguIPLJc84Kr01IbSB03QFDSYKuKMFQIlAJE1RGBGISKJTC+qqAkAocwIixNgipMDwUo6MUQJlrCDhDxDk6SnofYQAkgkMBCHk2GFNhdC4bDw+PxpjS5P/EE09g5cqVeMtb3mKPCSFw//334+tf/zqee+45ANoCMHfuXHvOypUr66wBLsrlMsrl8uRtvE1MVMle971WuecZ4ke9IpUhfpZqg/r1ZPj5WUvyzJxfN340a412b43fyxN/K1CwHv3Nvtfaoa3MYAXYm6C18zSoj4QAmj9P/OTvJxM6Mz52XUgvFRxszr65CUopMOh2wErpv9VYoJZIDMUCays1rK3GWDeS6NQ9oTBSS7SZXyi8NhhjeJrEiBCIhmIopc3+lShAxLnN5Q/NdcRMIjHf5bwwo7+PXgTw8BgvpjT5H3rooXjqqacyxz760Y9il112wRe+8AXssMMO6O/vx91334199tkHAFCr1XDffffh6quv3hhbfsNBzXOK+KNOi2/wm+kW78ljMn9nrTk+p8W3PX4UQkk757bD43l/ff69htkARtvPa/3Ndu420nHPd1MXXTM/CU3kHrBNfqi5j5nINggy89DrgNWX/qUPiXMGacwP2uev4wNGYoFptQhDVYE11Rq6ywHWDSd4dQPDwIiABDAwIrBqMAEAlIZGbJyCNJaoUhSAc4YS1xYBzhm40PulwkQeHh4TiylN/j09Pdhjjz0yx7q6ujBz5kx7/KyzzsKVV16JhQsXYuHChbjyyivR2dmJE044YWNseUrDjfYHsr7mVhhtgZt2UTRT47iCgmsYpQm/CPnAPXfGQvO989zdWzvr1KGJG8IVGkjj59CETFq/9dG75nGV+vDdrAAYQYEqHQZE+rw+ZgBI5y0SasqhDtyrJgFqia4R0FUN0VeKsKajht7KCFZuSFBNJFYN6diAVYMJSiM1lKjYEEsLGYWBXisKTJEgpt0E3AgKrtDj4eExfkxp8m8H5513HoaHh3HGGWdgzZo12H///fGLX/xiyuf4T3TkcqvfxfpANpb5mz53zMuZ8c2C/WAJJXO8jX3l52lF5u7b7ZJBK0GhKGK/EVzip+esgeXFHeO+aFSnoc4FUzSZo+03rwKo7MOdnzOAcW4r/rnleRuV0S2an6oFhgFHVxno61AYrCboqYToGYnQFYXojEawdiTGiInwX18VUIMJykENABAFHFEAE/QXaEuGcQEwaPM/re+J38NjYsGUz5/BwMAA+vr6sOTpP0660DBv3jwA9eTfquJeMx9zIxJwc8r1azq/+ZhGaxWNyc+djyMoIvRGteJpjaLrL0Kzc4u08navtZGVw63K595XIGt2d++Re66QqRnfuNl18B7PVhCk/cuC/bEmxO8GB1r/vnlN2rMbI1BE+q3mIMEj4Awlp+0uAJsqOFhNTHXABGurNby8YRjDicS6EYGB11/F9I4QnRHHnI4O9HVE6OkI0VUO0REFtoyvMkWIyPJgqxS2YXmaNa0bfX19WLduHXp7e5ue6+GxpWKT1/w9NDQ3FKS/wTFnszbHOJp8PhivFVJXAv1tbcJPj7Wv3Y1XExyrzJs3x9vjaO6/bwQqu9uOlaQZ8dMelIL181N8AZE2uQhCU/K3qJ5AbAi8mqS1AKgQEJ0ecJ2j3xHpwj0dpcBU7eMIA6BiqvtVogClIQ6lgNXVGqqJwhqpMFgTqAqJSlDTtfsDjlKoa/8H5i4yzhCaMiRS0g0Yww328PAohCf/LQBjTanT2mlKThONibbkFmn9zc9vP3DPPcf64IuCBNoAaf1FbX7dfWXGOMJXkf/brdxH5n6aw9XWA0PS+Wsjgh+OJaqx0HX+qwIbagmGE4HV1ZpZRyLgHNPLEbqjCL2VEH2dEboroSZ704KXyvhScF8lDBAwhpHO0GYMjEipUwVjgVKshYAygMDsj+6NUtTqd+ypoR4eHll48t8C0UprzkeX6zHtjZ3oPY0+eC9LnOn+s66KRoF7+txiTX60tFPo2lEKiojf2WO9VUZl1nTdEO65Npff6cTn1gWggDnuNPnJw0buJxKDIwmGagIbRhKsHB7Ba8NVrB0WeHF1FYmU2DCio/ZndJfRGXHMn1HGtj0dmNtdQV9nhBndJZRN+95KFEBWVNpgiDGs7yhhWAhsqAkopTCcJBiJA1TiACOBMB39nPtFfyf5++fhsaXBk/8WgMI4ATQOsGuEiQiwGysoo7Hx+8X+8eZz1gfutbPjTM49AKbQMFe/QYB/HZm7MQKF57P0eX5+qt4nzF9yS6SpfMx2+nPviTApgDVT5ncklhisJhgwxP/iuhH8bW0Vq9bXsHTFAIaHE6xbN4I4TtDTUwHnDHPndGP72d1489wu7DqzB1IBvR0hpnVGYMzk8gfcCid9pRJQq6Ea6NcjTPcN6BASJaFb/CqpMi18R/s99fDwaA1P/psJmv02jtZcWuSPB9qI+M+tWYTR7KMumLCBAOBqyRnzPA1oENtQv44+v9GtTEMh0sI39EZTNwEJI4Cd3xJ/bgwF+rnXndkDqz/XJX7Kxadzrekd9daPOJEQSmEkNm19a9rcvyGOsb4WY2BEB+6tHaph/foaBgdrWLd6ACNDIxhcV0Zci7Fu3TS8tmoIK9f1YvV2CfaOE2zT24lYKPRWQjCmo/krpQC9CpjWWdJ+fh4gcZz5OsBPdxgMOUPEuCd8D49JhCf/TQyNiLyVZjza+dsNyCvaz2h/tNuN2m9+/qiWrGtBbIcrXUZW6/L6b0OhAaOPhchr8a5W697LRtedLXcLa/Knh1DpvJRVQLEFLqSCFRpikfr5N8QJRoQm5c4oQEcpRGjK/lZHatiwbgOwdiUSHgBrV2Dla/1Y2TMTK1+diQ0jMQZrEm/dRiIKGKKAocMU8OksaZ9/Z5lDyACJVBhKgFjqboGxkIgSiURwyCh7c70Q4OEx8fDkv4mjrQI9+sQxzN36nLFo8u2fb/4CdRpyOwKDJsDW7Ydt8B5Lg/laXjrT5n7zNOO/bziEuXtjmb+0f1cYqeu7kN+3Uo4FwBTzUQqKUWOe+swAihGgqH5q8kPCY9k43BmAkHOEgR43EgtEUQCRCKxRAKpDgJLA0DqgOohVSmJZVwUBY6hEDJ1hiIAzTOuMUI50AF8p5OgshYiF3n9QZRiRzAo1+nMwggxXNvLfw8Nj4uHJfwtEMyJtVEfgjYQbhOeSIUMT078d62rxjYP3ihdu70Qie+Wc2+4tymv9dmmV1gOgmvzmjUJXhPt23npB2n6Rxk/tet16/4Cutd/BAgScIRGBDdALOUM5YOirBHhleie6u0t4qbsDG9YNYl1c1ZPGI8DIIAbWbsBLpQB/7iljq64I3SUtAHSWQ5tiWAo5OiIOBh31zxOK5tdTSaT1DTw8PCYPnvw3AxRFizc7F875rYi9VfGhdjGmJkb5cQ388e4pmTFtBPC5wXt1BFtwzEV6L1MtvlXwnnt+/hpc4s9ddoO9uxkLqu58xmBT5PICHxUFUEAm/a8UmrK6nKErDCGUwtxOiV1mSqweqeHPszrw7KwuLHt1PZ7nDCNDIxhZsxaQAnE1xprVG7BiTReeX1XB7K4SesohqrFApRSAc93Gt2IsAdS9T8hUAFW56/Imfw+PyYEn/80IkxGNP5E/wEUpdQ3PRT15ttpLXgjI++/d62aKCuAw/e4oZRO35n2RyX80ZYPdBj7UjIfGcaYb9fA2hDRmrAQU2W/LAEPvIxFSt/UV2s+vLQ3ZLn5BqLvsdRltPeDMnjs4kmB+bxd226oLL6ztxZP9PVixZhivvLIegxuqqHToCP8gYBiqCawZiTFYFVg/koAZKwI1CSqFHJWSDupLTIxByBmoAoEWUNjogyo8PDzagif/zRhUq7/doLmxzN+ORp8vkAM0NuEX5d1n3kcxH7gR+xm/uXN+M+GhmZZfH9BIe68vA9xuDEbWQkBav9OQx5wr0b7vO43qh23UY+sC0JyOZSEMGP6ycgAzu8uY3llGYhrplEJuyZ8gpUIl4uiuhJjeVcI2PZ3YuqeEp18dwm9Djlde3QAAGBlJEMcSA0MxXh9MMNATY1ocISnrK2KcoRRwhFwX+gEEOONa+3fW81Z/D4/JhSf/TRSNAt4apuKN4tx2MTqzf/YHvZVJvSh4b3Sbyy7Qrgm/XRSd3zQNsUGsQuZcoK46XwAGxWBr69OcJNjk98QYS/39zj6tMOFYFjiAK+58FktfH8RBC7fCu/fcGrvN7QVjDKVAIijp4L9qLMCZjtjv7YggpEJ3JURXOcBWHRVs1RXi6ZldGByJ8drACKZ1ltBZ1j8tsZSmiJA0ZG+EEgVEAYNSHIAEl2lgIgkxHh4ekwdP/lsAitL22h/bQFMu0NBHkx3QnsuhIHhPZU347azX6HwyleeD95ohnwrpBvDVre1cB/0ttHTQOebhpOpDGpWdk0WgkRXHCHO2Ba7zoHUSCvIzlobHX1yDv6zUGvuvnluJXz23Egtnd+M9e22Dg3faClIxDNcEQs4wq7eMOb1lADBBfAGUihBwht5yhO36OrB8YARLV49o0z9D6ts3rgYhtOBBtQfCgNtrYkyBA7ajn4eHx+TCk/8mBPLd5o/Z5+2YnJv4+9vN189H4zfbQ75Mbea4CcVv6vvP5O1lyazh9bp2f+f8ooW0luk0MHKup9XdzN/LbNZE/bUX3Sdb8heO1u8KBJxp4nfmzV8GmfqBNMqfHgBsdL8QCrFICwF9/4llddf055Ub8NW7/4ib7/8L5s3qBhjHgjm9OHqXmSgF0zGzp4xE6DLAsVDoLOuOfJ2lAJ1hiHLIsKGm0wg7DfmTS0PYwES9r4gzqIBBKQYI2j9p/14E8PCYTHjy34wx2kY3ReObBtjl/jaqpJdqvsUbaWsdJ+WtXuDIEnjDeVxNPWcNsYSfi9inY63mdrfvxh24aXsMADdm/Ia1Cuih0qBHqYBAMRBvNtpK/p7QNbu5/XEiNRFLhb5KhErIMZLIurk2VBP86aW1UErhhZfW4tW1Q6guEti2p8tG6HeUAkzrjNBVDtBZDjC9EiGWHRhKEkilEHGOzihIhRCTZsida7cthhVsiiK82d/DY9LhyX8TRJ40RqsktRsx32reIktE43nrffhFBJipcgcrT2iz/ChN/u4czYL+GG0oh7wQUIRG/QAypXpJ+GEpoTe6CBvwR/eAMWsVCMCKYxcYwJRDpCx9rpSClDrSfySWGKoJJEIiFgof+bsd8A/7bY/7//IafvK7l/DS2uHC60uUxJLnV+IvL6/BDv192G2bPuw6uxNvmtYDxkyEv2nzO71cQjng1r/PwKywQXUMGNMWAd29T0KBQyktgHBP/B4ebwg8+W9CGGsE9Gh88RN1XsPxzhNjyR/1jz358fNWBmsBgGrrZrVyozRau/X+zF/zT6r5M011jNXFC6SWB+eJE/1v5ytcsP79Rpp/NRY6/S6OoRRQDjmO2HUujlu0HZ5+ZR1ue+Sv+M0LqwqX2TAc4/dLX8fTf12FnbeZhg/suzW6yiFqibRNgsohR8gjax2QUHUNhVwBTAtP+hw6XlScyMPDY2LhyX8zxmgb+uShmpjxUx90NoCvWVR7xvxPzI8mAXmZwanmXDS9S+RuAF87HMIKpIB2uadZhUQ3fQ/QV8rBbOR+lgSpzW+2r0LbgY3OnhWQmZexVPuOhcKqkSpWDlUhzCIzO0rYOu7AzrN78bXj9sVrG6r4t//9K/77yZcwTLV4HUil8Ifla3D539Zgv/kz8Z69tsEe83pRiQIg0gF+0vQMEFIhDLit4KfsBtP5mPmH5e39XgDw8Jg0ePLfhDAaM3sezUk5nbUd4lG5MXYOE23uonEjonrSbBnkpTSBgjUwf2P02nx+f02DD+1eG59LRGt99nDulwLAjYlepRO4wgdpwu3CDQ40ZYvqAgsDru+BVEAtkVg1UsWfXx/BUCyxfjjG9K4I20wbwcLpnZg/rQvbzOjAxe95M84+fBf86InluO1//4q/ral3CUgF/O/SVfjfpauw85weHPfW7XDoLnOgFMNILCAzGQjFN5aEHg7av+d8D483Ap78NxO00+immQDgnue+bvd818xcZC0oKjhEBJ4ncjeAr4jM8/uqJ/AmUXFobbpvFL3v7pue5O9P3dyO2Z225T7cjAHGlNXUORQkHOGgCdIsAQCQ4IwhCnRKnXtNoanYFzAGIRUGhmp4afUQVq0P8PKaYfxtbRVv2TrGcE1gZk8J0zojfOSA+fjQ383HA39aicUPL8Vjf11duIfnXl2PS3/yDG689884cf/t8e49t9ZrQRcNCjjLaP8M2bgIEmJaVTL08PCYGHjy34LQDpG7pvNG57uE7BJhEemn8+csBWZRS+8NAu5cf3Zm3VzwHwkMeQHDVrhD9lhR1b5mr+2aLZTyjOBhlH0KdFOO5i+VjvyvG+uIOnkNPhcOYOdUSkfSg+niOVqASMcGXLfXLUeahIVSWF9N8NLqITz//CooqSClRHdPBc8vmIHtZq3Hm+d2YuvuDsztrmBWTxnv3HUODt2tH394eR1ueWgpfv7MK6gWZAmsGaphqJa6Cqi3gGsRcgUeqLREsn14AcDDY9LhyX+KoO3Ws2Mcn/nBbTJ/o7z6RnOqInJuFiugVOF11GnzNCfL1t5vy4dPm8vsdeyEki9P7GYjUOpeI1d1JnNBaeJ3I/qVc10MlKevbFpfZq4G+9M59LATK8WQCI4wSK87CjgqUYBKxNFbitBVDsEYEFdjDK4fRHXtOrweBBhYO4Q/dlfwl/nTsf3sbuze34XdZ/VguKab82zVU8FZh+2ME966PX753Kv4ye9exivrUpdAyBmO2K3fBgHygFlC55zVafaNrskLAB4ekwtP/ps58ib/dsv6kg+/0ZlF2nNjk39qxs+cj1ygWsHe8us3GzNaf38jFI3JWhzSTSh7LGthcJ+7TX6kUoDUKW1SMZOil2rrZPbXRoBs4yB3b/b6obV+irZPoAk4CiSk0ul3gGnZWwrQWQ6xVUcF2/aNYHlnCeWOMjas2wAMrASG1mH1hjVYzTjWr9sRr86bjlfX9uKluTVsO62E6R0RZpRL6CtHKIccx+6zLd6373Z4/K+r8KPfLsdvl63B/1m4FcphgFgoe13UICjkWf8/dSMks7++b570PTzeCHjy34RQRG72vbYIvYUmryfKRawDxVq8qn/OiovwpHssFgKQG2PN345WrJpdfME6RecyNj5ysUF8RmvPpO4Vrc/SayGhQVIyojLd+hQyH4rrbmH54841kNCjlIRQOq0uERJKASJgCLhABQHKUTpXKWTo6whRjUvYYXoHXphexbRpFax9PQCqQ4BIgNUvAQDWrf4b1s3cFsvmboO/bDsLPT1lTO+rYN6MTmw3vYLtppexTXcHOoIQb9qqB/941O54cdUgooChGkuEAUMQaDdDFHJEgW4bHATcXpN70/JuJN/O18NjcuHJfxPEZP8w5gvtNDPjtz0n0h/2PDeTgFGvPeszrcDQxiWPNr2xsPgQmizluC6U0ql7QioEnIQrCtJL0/ZIk3fvqVQKgSINOKvxMyhjGUiXlUoZl0H9PWJmL0LSQ58gpF6fmuwAQDkK0NcZYfukC2/ZJsbgyCwwxvC8kKi++EdgaF3ql3h9GYYH12BZdRd09nRiQ/80jNQEBkcSrBpKsLIvxoyOENMqETrDAH1dEcpBYKL8DfEHHKUgDTZ0kRb/UZkvR7MiSB4eHhMDT/5bOPJ1+usC4doYn32tZyoqwtNofLNOhETERRaFImtBnvetFt3iOlrB7aRH2j+g69Np4SRH4krZvH1kxugofir7q1Q2ZiANeMt9DhQ4ZwhUl8RVGeKvJRLVWIIxYCTWZn4wTbqlkBvtn2NWTxlRwFFLJPAmIBEKg4M1vDhcBV59QQsAhOH1SF58BgOd05DECzA4WMPaaSN4bUYHXllTxla9FWwzrYS5vSX0lSKUAo6Aa02fYgw6SoE1/buQ2gOSWlI84Xt4vGHw5L8JolFaX6NiM63nSkuvFp+T1Wgbme2J/fJE7Y4pssg31bTdfRb4wBuf38ZJTVAXgJiP4qfUNGP6Z8oE/9Eejb+bAvg4STDuGtACAEX9W7N+bj0SymSOJFl6MgCt6VeFtE10EmGEIaXQVQ7RVUn/u/d1RuifVtFpeIyhXArQ21vGS8tnYPVf/gwMDwCDa/XJUQVIqqiN1DC8YRhhyNHZGWIoClBNBGqJFj44A0qcoxSmwYWlkFvBI723aYqfJM2fbjqYFXQ8PDwmD578N0HUE/3EWUkz5u8G2hj57skvT66BfNR7fkw7Pvv8mhn/f4tgQDrWrtE/9S87Yxq4OPIgk7xQKu285wT+WZeFFQAAmZ/Scl5e0sgGNMJYCihFMDBvUs5+6ETRx1JiOBEYShIMJQmGkwi1RKIm9F4qUWCtB9O7Svo1Y+grRdh+egW/n9uL57bqwbq1w1jz2hrEwyOIOiqIqzE6ezpR7iyjs7OE3u4yZnSXsVV3CbO6Q/SVI/SUInSUAlSiwP4Ng3oil9J1UaiMwGQuX//1AoCHx6TBk/8minxp2Kbnmr/NTrPaOktdvuT/zhMimbXrSFZpg3+jDbk+fBSMb2QtGC0Ki/6gMZlk3B0FlgVrdXDOhzNGKkAogOf88ZwZkz4V78mNVagPFGTIVviTSscAMEP+eYSBzuGn+vmxlBhMEry6oaZN72EVfdUStk46IKVCX2eEvk4dBUhaeSnk6OkIMbe7AzvOrOCpOV1YtnoYL70+AwMDVTDGMDRUQ19fBeVyiGndJWwzswszu0Js21fG7M4ypldK6CmH6K6EqEQBuspBRtCw91oqJA7xUwOgAKnViO6Vh4fH5MGT/yYC1+rsgsg5z2uFqXjOP8UafXGkfxEhNl2nAf9n3AGOGT8/lvaXv+Z2CaGo9LAbIV90fqr5a1ImU76bRmit7O5YpMKSNKZvKWFJz9X8OVOZID47AbL3TJvzc7ECUpvKYy6hlI6cJ4QBR2RIvDMMMZwIcAaMxBKDNYXBmi7GUxUS1USilkj0dISITDR+JeKY2V2ypL1jXzdWDlXx6tAIXh9MEAuFtcMJOkocIWPoKnPM6S6hMwwws1JGTzlER0lr+13lEGGgUwzzxJ8IiZqp958Iqe8FuYrypn7P/h4ekwpP/h4W+WI27fjhGQWd2TlgidMN+mumyY/GIgGM3cVRTPzZv0Cx+yLvtrA+eKT3jTGt/XOWjmHIlu1lTlCi1f4VwLh+X5f1zZr9KTCOcyCUKlMoJwx0MZ9ESFQjjo4oQFBlGEkkVg8lWDOUQClg1WCC3sowdpzeia3jDtSSEvo6I3RXQjCmybocBejtCFFNJOYMV7BDtQvDNYFaoq0J1I0w4hxdJoivo6THlUOuH6aKYP5eUxvhWEgkQmv9BG7zIklQ8j5/D4/Jhif/LRSNteBG5+cD4JzodmTb6JJCJ6EQ5P30hkXdtLjs+/Vrp1p7fp7RoeE1IyVwa+wwfnbuCC/MmPBt/r5R+e31SkAyBZkq7k7VPmP+d+ILaF0rALhCgyNtKKUr+AVKIXb9/Y5mXQo5OksBaolEXxxhekeIoZoO/tswkkBB4W/rgKFYYl0txtbVDsypVTC9K0Kn0dwBEiZ0wN60JEJNSMSJRCLTQkWc6UJC3BTuIQtCwFmdtk/mfSL9OJHGTaLA4WQxMCAwf73W7+Ex+fDkvwmgYYR9k/SoZv7yVgGCdZbpFmZzIjgF6iuXWg7ypKYcUhybR799ZIIX0ZpTbPqeZn1rvXAD0TJETvwPOAF5ClLpAEBN4ukjMDEA3JJ6VgDQe02FDFpXKk2iigFIpCFgZgkb0Pn7dF4tkZjTWUFNKKwZTrBhRD/WD8eoxQIrN8R4pa+GnWfGmDPSoeMAOkJ0lsO0GA9nCEoBKgj0/QDSqHwg3StQ79dXWZ9+IoyfX0jEMhVo6FpJaPBav4fHGwdP/lMIE5nrTGSdD05Lg6par+PGE7RDngDs3Bm/uBuciMklfdf9MJpBLhFzJ29f5awdhX5/pQMdde6+aemL9D5rv7+2CtTFbqj6+8GN+ZvWkMZHDgAJYzq4kDHbsIfubUcpAANQEwr9QqEqBFZ1Jlg3FGP9cIy166sYqiZ4de0wXllTwtrhBP09I1gwrQP9XR3o7QjRZYL2QlOZD0jJPd+IKHMLFUXuwyF9abR+ZQv6JE7ZXxiyd7MWWL4nNCb2/4WHh4eGJ/8tAK2sBlmtXmUIKdXgWxfuyQfHNUK+fG92X2P36dfNlXnSZP/E5laL108EafOZAD7tw+eU3keavJlT57AzSKkMkSk7PVkNXIEiDRjMukLofDcFTmv2MiOIiZBnLAClkKOrHECpCFJ1QgEohQwvratZQh6uCawbqmHZao5XB2pYNRRj674RbNVRxuyOCrorIcpm3lLIdf6+Iwi4Fh23Y2FicvalE9FPwoAr9HDT6peEF+vCyH0s+RLSXgDw8Jg4ePLfzNGupu3+OGeD2Fjmb6O5i1Lr6tdI/f3N9pFds9X5Ddwe7p5YceEhRnEECkb7T/3akJq0JXMD+HRAXsBNPIDx72uTf65yn0nY42YBzpkpe2tIU6auFE3+6XXoSn6Ux6+PkdYcC4mASySCI5EKHaWgTkvv7YhQiQLHn64nWT8cY81gDXFcw2BN6Ih7qbBhpAMrBkro761hu2k1zOooYXq5jO5yiEpEMQBa7w8CboVBxoyVw9w7Tf6pFYBi+pQy1Q7NtXMAPOAIjYuB4gfqPkvn+2i+CV4A8PCYIHjy38RhC8QY7bZVCp97/nh+R92c/UywHFq7CCwRs/Y0/fG4CvRtSbX+jA/fTMxyiyhD5Nz68GHT/ui6yTSfL95jNX+lTOGfNDiQov3JHUDruLECRPY85wtnJqbCLY6jK/jp+TpLASrGAkBR+NM7I6tdVwKOzohj+doa/lYOsWZDFQAwOKxdAtVY6LiAqkRfuYZZ3SOY0RGhOwrRGYbojkLdmS9IAw2poZGVnwqkNCJ9GxzINeGXQm47/WWa/dB9dCv/YWwBnh4eHo3hyX+K4Y00b7azVjNzv6s9Z4QAlSXZIhRF8E8k3Na7yolZcOnJLcPLzPUpY1aHibBnUmv/TMEhZhO4xwGutCBgLQZm7YxtH2TG12OlsZvTOjZWQJI/XJN9wKk9rybJhKXBfyOJRMilFQISofftugC6KjrnvhRq7b2vVMJWXUOY0Rnib+vKGKomWBWOgDGGREgMjsRYxRnWDXGsr0ZYVUnQUw7QWwnQXQpRCTjKQaDr9zOGiHMrANH9tPEKRnhhzNQhCFLSJ20/KCD+1BKSWnxGE3Pi4eHRHjz5b6Fwzev2GLIkSeQpYYLg6t2ylvgpV3usVfnahTVcNM10SPfuXgMrEEjIH5+ppe+Y/iWr1+KtwOBq/0hdBmT6d6P+OWdQ0kTxO/uUdh0FwYDAqRLITUOeRCqEiQRP9BsjQmA4EZBKoVuEGIkDxCK061aiwGrnlL8fcobuSoiucoBppRLm9gxjxYYYL1VCVGOJDdUYcaKDCuNEYrAmIJQWMmpCFwrqKnFUwgDlgCPiDCVuXA7cdPDjXH9HAh3AFxgtPzICSBRwlKJAB/cVlP0FYGr9p8IbA+rSRT08PMaPenvbFMNLL72ED33oQ5g5cyY6Ozux995744knnrDvK6Vw8cUXY968eejo6MAhhxyCZ555ZiPuuD1sbDNmnqNTUmN1ggEJAq42VgQi0fzYwnWa7KuVAFEUm2DnRhqRn+45bSSjTEBadpz7SMmXgv+IkNzqfORL12Z5JwXQuW5doEfZPbjXT+sCel6dCqdMPn32mkKui/CUTCGdjlCTriZngTXVGtaPJFgzWMPqDTWsHYpRS6RN0QO0W2B6V4RZPWXMn9GJ3Wf14S39fThw+17sv3039t6mB7vM7cZ2Mzswu7eMmV0RukqBySYAYhvEJxFLCaGARGlhgRsrQGiEFSr2U4kCdJiufnQs4qywCJA0qYDU4lc2/QZ4eHiMF1Na81+zZg0OPPBAvOMd78Bdd92F2bNn4/nnn8e0adPsOddccw2uu+46LF68GDvttBMuv/xyHH744XjuuefQ09Oz8TY/wSiKxB8rWuXt2zWRWq9tMZoCrdv14Y9mf0X1C4osEs2uo5HfIC+skB7JFDLCh9XIkdXIbfQ9AAGknfnAbOCfMv5+bqwDtC6zc2SvNWBu8JuClLCV7ihWQJvLg0zzHgreE0IiFhyx6dw3IgSGEoHhWGB9nKAnCjG9VkJfpyb+zrIutwsAUcgRASiHHD2VEEMdAt3DIWbWShiuCaztjDEQx4iFxPqaLg9MdfcrJpiwEgaIjIbfEQaIOEclCKxJPwp0wR/X1RByhnLIwSmyv8C/bxv9GGGJvkBkWfHw8Jh4TGnyv/rqq7Htttvilltuscfmz59vnyulcP311+PCCy/EscceCwC49dZbMWfOHNx22204/fTT3+gtvyGor8GvbER7s/NckDm80W+r5rnUFE6E7MYAZObKj3X21sz/344QUrdvZ+95/s8E8cGxDijowj3SvOdE0mt/PDNEriP4XRO+G5AnpVOql6X5+67fP7U0AErlq/eZVEGmW/0KfZOQmLFMKHAmoYK0fj+Z/8tRYIvmdCcRqkJiRAhUhQSqienmJ5AohWqs6/fXEonOUoAo4DaivlIKUI60Jh4nEsOxxIy4hGosEZv6/4lUiKUWMgKTqUA+ftLyKQCQSD50yD9yovijBiZ+5ZC+UApKptUSKfbB/U5tbGuZh8fmhClN/nfccQeOPPJIfOADH8B9992HrbfeGmeccQZOO+00AMDSpUuxYsUKHHHEEXZMuVzGwQcfjIcffrgh+VerVVSrVft6YGBgci/kDYIyjJ750Wygjbet/bd4j6LNW2n7zdYZU6leZE37bnqga8a3gXUgH3vaIS/vw3fN92ThsKZ/Q/yS6WI3dL4if76CDeSj/dFYtzgOCRrcCAAMOrCQUuZMFAAUOAKeXlfAGcoRBxA6MQUK62o1DPAEVSGxvqqwIdC++s5qgK2SCkZiiS5jAegqB9bkzhjTVoEy0Jlo0o+Nfz9OpC3U43YupMA+7vjsbcQ+CQJO2d8i8z6QRvJbbd+6VVSG5DPFojzve3hMKKY0+b/wwgu46aabcM455+CCCy7Ao48+ijPPPBPlchkf+chHsGLFCgDAnDlzMuPmzJmDF198seG8V111FS655JJJ3ftUQhExNyTi3PlFvuqm4wsOp6bwxl31ms2ZnpeerwWP4vWY83DHCqNSMgUngA82sl6pNIJfppIFJBiEJO0+9Rmk0e2AonFwc/fdGAY9iIoFCcXAZCooCKkgYIQMc50BZyiF6VVUTOAexRBQjfzOKMFANcFQLJGMKFNUh2EoEZhRK2FapYRaIlFLdN5+RynIdAWktr4o6wY8bstd2r90rRdIi/1wU52PYh8aET59buTXF+YeUXc/EuK4cakwznOFjjz7e3hMJKY0+UspsWjRIlx55ZUAgH322QfPPPMMbrrpJnzkIx+x5+V/GFpps+effz7OOecc+3pgYADbbrvtBO9+7NhY1cxcS0BxVcCUANLa7M3HFJX6RdPXQN56USS8kKujUSoiM+zPGLLNdCxp6uOQrkneaP+meh941m9PAgRZDgiU66/jB1Kitx35lBYe3EY8nKUR8UopCHMtmnDTcDcpFcoRt9dGTXdIQKAZu6IYUVDF+qoeO1gTWD0co5oIDNRibBVX0FVK0FEK0GmK90SmeA/NS3/DIHVdWLcJ0nuRFwLzJnoXkgL4lH5eFdKa+6UjWFCwJN1/8vdbC46Hh8eEYkqT/9y5c7Hbbrtlju2666740Y9+BADo7+8HAKxYsQJz586156xcubLOGuCiXC6jXC5Pwo5Hh2Y++QldB8iYvhlrLGC4fvQ0WE4PbKrtq/o16s8riFUYBaw/HbSeKTKUKuMA0iA+zgABJ6BO7yJTvc8NKuMMlvAVB5RQdk0SGLjx4dP1MAZApi4AidTNII2FgVIFXe0/ACCNaTwRadAbBQ4KmWYlMKZT9giVUmD86mn535AzxFKiltRQFQrrqwK1RI/tLMUYTgQ6oxCzKmV0xxIdpQCViCMWOh6gpGCFk9Q1YD+5UX1O0vjuZc6sL6RCLZY244I+UwC6qBJzsijIqtLEkuDh4TF2TGnyP/DAA/Hcc89ljv3pT3/C9ttvDwBYsGAB+vv7cffdd2OfffYBANRqNdx33324+uqr3/D9bkyMKmiOyB3FP+skLDAgIwWo/HsN5nbXoClcP27DIER3Y44mnxFekH1BPvx8UCGZ/akgDw2SxgTvkjLV7mcMphZAGtTHoU35dE1UuleqtOiPDfyj9ZBmGUilwCSgcvEVRGxuK1zX1A7IjDCgANs6F9AaeldZ74Oi7UPGUQk4BmqJsSIAQ7HAuhEBxmJEXAcF9lRDdEUhekqhrd9fiXTUPgUXUsMdCmTkPLv/vLUnvTfmuWPaF0aIEQqITYMiqSjmwdxnE4BJ9yN0Kht6eHhMPKY0+Z999tl429vehiuvvBIf/OAH8eijj+Kb3/wmvvnNbwLQPxpnnXUWrrzySixcuBALFy7ElVdeic7OTpxwwgkbefcTD0r3o0C1OpO5+afeN58tw2vPLzCZZ4MAm6xVoN27Y/JrNPsNLzInu3MWXqcDaS48E1jHnIczjzIWA6mQVu9jTtwBYEzxWvsXJg3Q3Wt90R+6h3oCcjO45XupuU3A0zFUwS8xhCe0qSJD+mQyF1IX7+koBWmXPc7QVdY59FTGtxxydFVriDjDcCKwephhsCqQCIXBqoBSCoOlBB1hgN44RGdVl+6tmDoClKbnBvJRYB/dH+SsQ8oh/dTVkTXr03eB7gPNRcQfOoKQJX7uid/DY7Iwpcl/v/32w3/913/h/PPPx6WXXooFCxbg+uuvx4knnmjPOe+88zA8PIwzzjgDa9aswf77749f/OIXm3yOf9PoeBT7wdsa65jzgWItPn+OPsbqzsm+p+rmtel4uT3lNUaVG1S0FiPGcciG6EhCa5FFUf+UiueuRf7+jEmepaZuJdPAP5Uu6wTypS1/aau25C+Z/lXqMqC4Aq6yZYIDzoCQOy1w9XFdWEgHw8VCt8RNBLc1Acqm2Q6Q+ug1+XOTEsjRFYZYW41RCWtYGyV2nqpQiEcEhrlEIiUGeIKuKERnGKAzDFFx8vOpjr/tuofUxw+k30PXykEg4s+DPjfOUqEiNBaHkKVWEK/xe3hMLph6I5zOUxwDAwPo6+vDkqf/OOlCw7x58zKvm93+RpHx+UAsF2mQFMuMafRjnKmK55h0Xa3NjcYnvyxDGrletK+8Nk/V8PJ7ooCwLClkr8M9l3zIdCXM2VfAWWYdmpty48msDsCatENjSg94eh/ommkMFbuh6wlymipjLLMWVeqj9D13DBXDce91Nda59dVYYKgmMBJLJCbfnnzfYcCtn54sAOWQZz63REjUEokNVaHnqur51tdiDCUJBmoJEqnT+aQCSoGuEthhiveUggAdpmhPxDlKXAsC7r3NfzZwPgf3HuW/X5QNsPLVV2x2AN1DyhigXgBFAbz572wzzJrWjb6+Pqxbtw69vb1Nz/Xw2FIxpTV/j9GjmT/enlPnGmj8g5rxtxe4CdI5stp/kfWgcH6rPhqtHApBLoiPsVz7XaQmZoAC9dLqfQRuHPFaYHFN99ofrjV1ZbTyNLgsYNrczzn5pl1NNtX6XXCzvlJZSwMJG9KOkZkKfoxpMzvtOxVUdABfYoSPkDMkIkQ15qiVTDyAMfmHQRoHoIULHchXiQQ6agJdcYDhmkBnqAP/hoVATehIhqqQ2jcvtE9eKQUuNPlHnFtBIGTZcsYZC0DB8+yxtEuhrQmQI/1GZn5boMl+YZp+nTw8PNqEJ/8tBHkTbZHPvuh8IGemz8yVS8mrcxVkXzfOMKCUPW2OLwriI9N/vVnd7Mr48I3b3Wr/lngY0xH8SkEYL752ASgbwZ8J4kNa9Eex1KSdSRWUsMICXYdbvY9a/SqkQgCA4gp+jBvffmDz3itJgBFooh4yPoEgZqaLn0ItkSZ1L8gEA0YhR2TM9x2lANVEYrgm0G2a+FQTiZFEIJYSw2bexFxfyJjNV5BKIRYAICEZQ8i46WLIwDkJT0bocwofuUKC6zagUr/MmPgpqLAR6bsCnr2/Tb5HHh4e7cOT/0ZGq+j3Vhp28TigKIhPP3f87OafoqA/ChIsWks548bzG+wKGPSC9kVFZdx6Asxo4VSKl1LhpEnfA9djKQ/fjmNO+12uc/GJ7Dm5Exjqq/7BkByaF/ChMQAsMXLb0U+ZinakxRpzPzgYUxlfeinkTiyEgpABUANiKRHHQFVowk6URDUJMRIL1JIQsZDWFVAK64v3VIREOdTWgGosTcEfU9VPaDdKTUobjJdIlemkF3AGjjT6Py1ulJrqXZeLJfwc+VNcAo1DIxO/SisekrBpT2vHtOXh4dESnvw3UWSIE6km3yx/vyjiH2j9e8rMBMWFf1yhIRdM2MK64LoTUnO+smtpK3s6AecMUpg0PHNMKorGZzZ/38TN2ch6zmDL8AaGf4VKtVvS4iVLW/BSmp/bvIdS+MhtwFi99m9z+KkPgLlOCuLTVgNp7yt3CvgEnNlofvdeJ1KhJnQ3vQ2xQCwV1rMElVqA6UJhJBboLKdCQBSwTF2AMODo4gxKBaiVJBKhbDnfWFBMhLTWibTZkBF8Csz7FOuQj7VwCT9j1jeCQxTywu+nS/qULUDQ1hxT+c9r/B4eEwJP/pswGlbiQzGZExG5vnOguS8/v16rn96GQonZWNH0jEzNKl2DTPquKR5wourdaHxk8/dpHFMAN7vhhqAVB4gWqZa8smNMWprM7pOTy0Baxd1eGwUiAmmRHAZktGC9Pz2AAgldRTbkDJHR2BljpvJekFouzNxDSWLT5oRSGEwSsCoQxRy1RKIaB6hEJiiwJBGZ4ELXvVDhAWSgIBXXsQXKBDWKtBIf5eW7mndeA6cASe4IARlLgGP6dwME6zR9JzDSFcjc7zDPfe+8AODhMX548t9M0Urrbgf1RK4nbfRD7NYIcN0MhXMbM76d39H8FXTzHGqu44oymkSIiLTQYKvFsVSwsSZ8zu04Mt8rYwlI3R+mxCxV73PM0QEHIFMLgHWbkPYPc1zB7pNzhohBpwQG3Gj6ptIdkDa1McRHefUUp0AWgHLEHVO71sBrQqIqBIYSaQICJYQChpIQXbHO2e8sh6gmEmUjCJRDZVPqaH8cDKGRgvIleImMpfP5uDImYykhp6SeNkUC6rM78lDumipd060LYMshZ75wDaf08PAYBTz5b8YYT2BU4yJCqQCgCtZwTf+tliZXBFOqTtKgucG0+TwN4CMtUlnCoW56Wlul7nvM+vMtQcEp4MMKuvAZ4nFjBoDU2hBo+7MpGazfo45/TOVdIMy0wtWavTAPKuOriU463fyAUsCtBYAIuqOk7xOlCQacYbAmEDCdvqcAxEJgME50YGAs0CckysZ6UDapgaWQ22JAlL9P+6S1XFA6ZatUVNca0Oy7RpYEIaTV8hXqhQ36Gti6DZw+A6/xe3hMJDz5TwFMVNBfM79/q4qARa4CImd3LRIA6Ge6KOgvbzFoBDeFj4NlNE1aKx/4R/OTGV8yJxUPxl/NmdUqtenfafNbEMRH9y7/YCy913a82Wtqoob17QuWxhnA7DHkgAwYhGSQnCFWqV9dWwCYfS0ifcz115ds9T6uLQqmAl854Fgfc4wk0t634USgJnV3gYAxdCUhOuIAw6GwgkAYcJSC7HxAfTe+VGsfG+Hm6z6Qhh9LVUf4NhHC3PRsjEFa5jf9PDw8PMYLT/6bMJqRbDMyVw7JFp1XtAbLreeOa1S9r61ryGn/GaeCWSRfIphbP7qyHfgk06ZkBW2O5ywN4FMumTQL4lOwLoNsqWCWbfpD1gLzN5F6n/kKftyY8qVSkAEz7XpTi4EQqY87DhzTN7J1/MkNwBhQDnUefxhwlGocw7EAZzpfvwqBRCoMJwJS6Q565YCjEgboTkJENW1ZKIVpLEApcCL0WRq5T9dtRYDclyQVNNOPKl+1MeM+MH9rsbAav0LWnQBku/tpQSt97onfw2Pi4Ml/E0Dzcr35TnnpD3WR398l86w/t3Wdf1ti1xknddJa/TosjdgnzT1vwnf35Gr/yOzNNNKBypjiXRM+THabcOYkTZNK+cpM696UzCGzZnwYrZ+0edenTVYS8r9T6h+gz090a59MnQGYfYYBN3sCSqEutUvNe/RDgGrfJ4LbZjtuLEBkyu5ypgMEKxFHuabPq8YCg0mCkURnElSFvhuxlBCxFixCpgWBKEjT7kJqCsTTVD43kt92PHSuJ/8dcklf3/v6Wv90XixIWsh+Ocms72YKcLiVBT3xe3hMJDz5T1EUEbeLVhq2G3jXLN8fSH/Ipaoncve85vttnvJnVyrw4ZNgQdq/6/8lt0OQuxm2ba+pwmcFHrOgNITPmfa7uz5j0v4pQDBtxJMWGLLuAud+m6B5fR5L11FKux4UdCwCZ1nLQRjonH5m7pOUHFJJJKaJj/ugGvwdpjZAOeSolLQbQGvkmqzLQqIWGSEhlhiJtSWglkjUpASP07TCRCojFOgaAWFC1fukIX5p4wCKcvfpfgOpoFb/2bouoVT7p9f2vrl+fcfMz521beog8y19PTwmC578pwhGay7XY7LBdZbMSdtGc9N/XvtPLQatAwXdcwrXocXg+n5hffxKpoRcVL4XNMYIBuS/z6b9IZu/bwQB8uOTP55iAvKFfwIy41NEP+BorPXuhrz/3953mRYaYrozPXQZ35S4As6gAoZIcbsOBf+RABELbQFIhISUgc7Bj3RDn3LEMxH0VMqXc4aOSGEkNtX8TBGfziiEkMoWBgJ0vQClgAQSQigIxREIlamxn8/bp8/IjeIfCxdngkKRzslzAoeOkzBChzf1e3hMGjz5T2G00v6BFn5/VW/KLxqfGVNwLL+Gcv8WuAyIJFMCp71ocFOwJTtGn2hNyWYMA9NmeZnuK99hjjNN4Fzp3ae+cwo0M3EBCrZQDJntucnf19ydCgA6GA8Az2rx5GqgYMNU4FJIJGm82hmiFBCFWQuAUqkpne5JnEgkzAgDABKh59VV+LixBlDHviBNgQNQiQIgAsKAoSwUapEu3xubCn61hNscfjK560h7LcTo8sZGGFO6dK+QOjWQyebk77pgstai+uMMTunfnImfSJ9iDry27+Ex+fDkv4mgXb9/EXnXnY8cgTcZk3EVICV8Ikjrqze+/yJBw438BoxpHQXNe4jIjVVC0nWTX9/x37s97blSNoAvLdyja/gzlS3fS2yUD/6ji7Omf7CMBcB1naTZAsz4s9MceUo3VApAyMGlypB1FHIETgU9AlXZqwnp+MwVEhmYinzaty+kQmTS9tz7XI4ClCPYboBUxa9mqvgJp4ofWRvos3E/a5qSBCWhUgtL6kZC1lpjzEwu0dM5rvDAmc4uIG1/NKSf36eHh8f44Ml/ioLM+M20/3ZcBe0IA/niPfk1yVUAS4qpoAGVBv25AgC9T6TvpvHRuXXNe2gtlgYA2usz90KYGv6Z4D9WVL0vJW4q4JOv+mevD7BmZk3les/CbJ4ZF4Xr9061WJU2t2FmHJUJNPc238aX2tmWI565z7VEnxMLPTaWCiyRkFITuW7moy0AtYijZDICIqeev44vYFChDhxMIk36+bbGaaphei/S4Mzs58/tfTakjyy502fgEr37N3A0/TDkmuyRFgLKWxLS/aSfvfth+cY+Hh7jhyf/KYSMBu/84DUXALLnAikpF41xA+yyWQLKeKuLawRkqvEha8Zniog3q83TXqwGbY8pqILNUQteW4HPiReQypj+OasTimxKHF0HS4P4iPCpaQ032n+aiw+dwqebBNi5hRNEyAAwJ9jAbd+rjFAhhVO8BwATCpFxOeg4Rp5xAUSmFW8U8JQIGVlJtOm9JgUS0wcg4tr3PxIzVKIAsdDafynUogyZ0NP7oSv46QyCtJSvKwAQ+VMgHn0dZO4LVWTGz5TsBQqJ39XwSbsvcW41/mY+/YZd/dqRZj08PFrCk/8URSGpN9B4SNd2xyjnn0aFeBzXvJ2/KK0OzrnMXTAzFrYcryr4dSYzti7JC6vJ0xg3U8DV/MHSmu/W1WAIHa753wgMVEufwbgIrDBlGusYArG+f8oaIIHDXY/cACQ8AJksBc6BgMhfafIVtogNwISElOQa0HuhtD+aoxQySBU4AXcMAReoJRJShUiULuNbFcLGHgipLQAjJt2vlkidv29S90jLpjV0JL+yY8lFIVS6V1tsRzkugfz3y/kuuMITQ5b4idzprxsvEAQtTPuZzyzvlspaqDw8PMYOT/6bONx0vroIfjg8nfNbk/ZfZMpnICEiX7rX5OFbUlT0htXU4PiNlREWrJkYKfFLGzgAgGctDkQkrtZHxOQ21nHN/5lUvMwgSxt2z1xl3Q0u8evYAXIbmKA/un5kiU8H/zEzt67gp5SuGyCVQiL0ylqr1hp4lCNnQJf1pQj3gOngveGaAGMMiWBAovP1q1JorV0FiLhO2UsERzWgYEBuc/dLgdawXUEDgK3vD6QNfIj83VgOV7BD7vtjPnLnnuTq/OeeZ2oEFJn2Vfodlare8qA/I0/6Hh4TCU/+UwyN/PipP7bILF9vJXDHNdL+G4E08rzrwBa0UWlTFgDWp5uWYdXX4KbiQaaETwKAIFM7kBEA0kAyZz9mPZf8GUtb8NJrbWJOyZxujA3kI+0/N4+bw0+R/wTS+gXT/n+3hC/l8JOFQCoJIZkVALSmrS0CSaAgI71QVFDHnzTjKJHGQiBRjbUQMGKq9tWURE0K1CTAEoZEaKtBOQpQTbgt/0sd/aJA2Yh6EgQI5B5wP9+Me6ZFPAndOyDrEmgnTbSI8Ol1ZlmW1vkfzXfYw8OjOTz5T2E0I/W6c1Fv+neR1/7zY+H8iMOcSxp94Rhm9Xij1ad/6X3A+NShqCSeTttTVOyF2dd5bZz2Qi6A1PcPa/YnUlZwagY4Y7Q/P039o/tgq/chq5UCaengAMho/YJiDsxrV4Om3PgoIEuAJlkhlRECACUUlPHfS6mQhMpG7ls/OWcoIQ2ISwlcopRwJEIiZBwSCrHUWQE1KaGEvi9xIm0cAf2NwrR6XxjITKvdfMCeG+3vfDPGhVRgNJYU09iHyL4o1oAEXc5Yxt/gid/DY+LgyX8zQqvofzdIrlEQn+tTzf/WMsPCrIGEoVT9IPKpM4d97Q++8YGTb53GuoF/0ggPVisEoKQRZMDrUvhoLHXgy/j8wXRgn5M2WOc2kE76H41TqBOqOMtWKYwCgDNuLpF8/aaTnZR2rJAKNc5QEmkjH03S6f5tTwCpjCYvEcXaCsBZmg1QExJCKfs3MO6EShiAM2SEAPpLmQYhT6v4aSFA5QS21hp89rNPiTtvOVDOZx6bQEO41hxHQLCuGOi+C6lA51oYvBTg4TFeePLfhOCStwvy4ef52CXz4nK/sP7+TLMaGoN6AYCz1C/u/iBbE72zVuE1oImLInNN6SNgLM0AcIhCKMeMr7INdWwNAkb3jdl7SI981UAba8AolU/Z+v3kPkkEnasQAHWBde78AEx+PZAoZVP2YgHrEkiEQtkIAdSyF0ij95kh8VIoMRJylCOdu1+KhY3cD5jeFAXw8UTPkQhlNX49t7Tzhtxt6EOuj6yPvsiSQx91/iPMNItSzrGceT9OpH1O59jFjASYSR+kPZElwPO+h8eEwJP/FERrDb5F1D/ywVrmtxUF2jxSIUCmv9r1JnimffIShix46rs3QfmOdq5ftVOHoBGIiAMGnYpnjkvlkku2hK9L8kSiih4ya15mxoSfN/+T2Z8rZawWTk1+lVoEoCSk2ZwbvBfShpmzFlNQJl/fLbITm0I8iSnlWw6V7bjnxhSEARBJjpALJBFHNZYoh9wW8QkTE1NgIvgBIFHa+hAohkQyhEJZTT//oHz7wHwZSBiwpndkA/xc1AsB9FdlPjOY12S1yH8tuAkwsbUByAICp7kPz6/u4eExVnjy38RgiVzlo68bBQpmTfkuyGJAKXyWwJElctdVwI3/3ua5O9o47Y8q5OWL6ZCQ4JrQLZcW7I+ImMjYzd93tUm3hG9mrywb/Oem8FHwodvEh5CN4ocN6NON+LQ/QiqGiNoCOgIAYwxRmOsCGBtt17gCNPnrDgSxSQ+sJRyxSd0rhaZlr1O8h9r6KgWUAlO6Vyhdwc+U8o2FLuijFBAYKYWCMyl+QUiVqdvvdvBzn7t/AccVAGTiQ+hzywicue+h+4riLeDM49YE4AxgXFf9s819PPF7eEw4PPlPcYwq6M9o8EToru+VtPGiWv8kBHAwS6qEIjN+SgBmkQIrhJvvT4TPway/nTZHllwyEVPr3fx6lAanGExfABJ4DJmbuvgKrik+DfxTxnacCf5zTP/5PH67HgeUYkgAQOqSwQppFT9lyvpJXl9pTyrUma1J8yXtPzFV/KgEbyy0Vh+FOo2PWu9S6VvGgHJk0vmkQhzqQEBrRXDmUYrWSYUeuq02BkJoywTV9HeDAPOFfDLxIrnvXRHc8+hzpmtwn+dJnyEVRPLfBQ8Pj4mBJ/8pimYm80a+f6CBPxZpu958BT7XnE9xA81+bOkHmciDxri+28wPPml3YEbrBJhMg8BofzBEDGM1sP57h7RhyNzW0c9cSKrF0xWSH97o+VCCSNDs3bm/RDgEKjurhR9TyEdHqUE47gYZ6LsXgYMJmUmnK4UcmVr4jIHFdJ0SIpEm117Pn5jxtUSiHJErQAsRUZCa5+m6wgAIAwWldAW/xMYUZOv4UxU/KuVblEdPVg6q55/Gh5jPxBEAXBdAauHIfu5wnpOwmGr42SJAqQCQLRTkSd/DY/LgyX8TQCPtv5Hp3y3iQ4V/gGKffB6p79xdB4Xn5wUNRcdUfQqdndto/9LxL9BTKZXN9ycLAK3jav6ZaHy6JkOg3JByXQofY5AsrVxIwoowvn/KGnDN/xQFn2YkMLtPahwEobV/ep+I2i1YVDIlfEOH+Gwd/0SaOSUSAdPSVwtK1ZihFgUohRwlE61fCrkN1AOypXyFVChLjsRUGUzMI20dnFb3IwEgH3jnfofyZJ63ANB9zb/PzUlu+iU9LwWpRu8SPs2FJqTvm/t4eEwcPPlPYYxF+2/lJiA/fV77Z2bCVNtrvp4rYFDKoCb+NNCvKGiQaUUcJCrYwj9mAWkivvOmYevCsPtRVlslM7xL4m55Ymq8QzEEUug96jG6FW86ubQmZyA1P9s2vaZhDzN7ThQ9l5DU5U+lpnpAC0JlHiDgWtIIudbcicQTR4BwNXXG0lK+pZCjFGrNnlL3QuoLkMsQCFWq6bslfK0AINMUOyo9nK+m2Mysn7ECmBPqSB9p3IDrPggDPnrCz3+fWb3g6+HhMTp48t8MQRo5/bW/nU2JGZk4gXSIsj/uRZYGqsZH8QKg9chMLuutAG7uv2s90FYKZn/c3Q1SGpozizFju6TVOPXPDVDkzCW9LPHRIlTEh5HFwfj2iaSZiTugPehmPMwQK4dSsARNoOdRwBFwYVPuaiY1z6YFUuS+eQRcopboRj7VTAU/mekY6AYdMmNij8x9TU3+qeZPrpf8fZD5L4GDrMmf2WOuoJZ/7loHSlRuuEkAX74CYHZ939zHw2Mi4Ml/E0cz07/7154PRyhwxuZdBdnfXE0Q1K43ux5skCFDWslNWxHMWJmtC5CamPVGmH2Rv7Z6/zHnDAFSf7OQqaXDdR/QMG2yT9ezUe0qaz2QxpcPQatl/fecadM6jPavyV6/Z5vl0HzQaX2R1P76fMe90LEqRAFHNRYIA6aD9kzkvgvKDlBK5/DHQiEKpB1fCrkVJKg2gGu9SGMfzDWbveQb6Nj0vAZCYF3Qp3liyd75nOAcy7xuQPp5wnf3lKkn0cBd5eHhMTp48p/icAOv8ub8ZqZ/SjOzY4zWiwKt313Lrfpnf3xhit+gXuFyAwbd5kBudbeAI7MmadPCCAUCyqbwAWlcgmL1awWGbARgqwBSCp8lfaeCnxvAqDve6ej2REjHCkJ+fEBxdz2VKb2rhR9t/gcAJFILDEj96oJrC0DAGYTSeysFHDAETXuhfP7IFOKJQolqLFHjDEHCkEhlXAnGSiFNZgG0MBAboo9CZbv6kTvBFvGhfbvk62jhvEB9dgW/JgYAPV/ms0nXaBdFhC+d7x/tgSxEYHnrj4eHx1jhyX8zQLOcfwYnMM8IAPRjK5HtpqfHppq8yv0wU8pcatpN9+C6GgBY5lBGAClyAdB8HGkbYdqnMOcD2Sh8u0fnwVlqqtYZeMYKYUzE7jXaNTlDqGD8/WnFOZGxeUtLqIQw4FBK3zk6h1wQ1L1Pp9vpz0AIBpGr4+922qOaAJEJ6KsmEiWj/dcCZtL2lI1pSOsEKCTSZFBwhkhQM6C0lr9byY8xXSlR3y/WkKxd4WAiYbX5XOqhQmqBcI/rzeg/VGgo25bIw8NjPPDk72Cgtg6yJlqfOA50jFTGNC5vineJloKp8ue7QV359C63gEreJ+/6hEVunDvWzQm3WluB9uYGeLnar7uWNZs7Y6mNq1uFzh3nBrQlMisEBdyY2xnLRMcTEiGdqHjtZ6f7xhkRJ2wtfHfftlStyauvCd2wpiakDdZTSpmOf0DItZav/fPk82d1n1kiJBKlIJhCDQoJJGJIJFC6Yp/S59D9BlJhyiV8bq474LpLIF2P7kaY3lf382ucqteeJJAv9ATU++yVAlaNrMlYlqjOvzsHfbeZkTJpvxxpCmazffGRWlt79vDYkuHJ38Hp/3sygo5gY2/Dw8NjHBDDkyvAe3hsDvCWNA8PDw8Pjy0Mnvw9PDw8PDy2MHjy9/Dw8PDw2MLgff4Obt5/Mbp7uid1jf7+/nGNb5SKReFP+eA9G/BnguPcoCqgcVCdG1BHeey0ro4cz3ZiowA2aSPRlakxr2xQFzPBc2GgI891tbc0eEtQKVpFBW7SIEKqCx9wk3Of229ax14iFrCV7BTSwDaK3M+PpeuNhYRQptCO0Ln6MNdEKXM6mt6pphdk5WddqEcH/tWEQmKa9sTSzAedkWBL3zJd+CbkDEFAgYEsvcaCvHjlfCbSCXhMMw5gAg9VmmkBNA0YTb9H9SV83Tx7yvRw3ky/f7nsD3rfXee1V1fUFQNyg/oArZFQYGKz6n+NML23Ews+uWBUYzw8tjR48nfQW+pDT6lnUteYXpkxrvFu/nw+Dt/+OOd+LImQbelYM57Od6PDiWyIYBKpIEzTGDdjgMgpCIjQeXac02hGE3ra2S40aW1EbkFuzXyHOqVU2tCGM5sWVzQ2MWOtMKCQyTQITWEcnSNfT9zUEY/mobK49l4xZiP3rTCREwBoL7UkbbJTc5rtJCIrgFGFvsgUAAoYt9fopusVIZMtkSvn6x4vKp5D36Nm3JotsOOQf67gT0YocIr7MHcOBiTlkTrCZ6jvIDgW0ifMqEyuAO/hsTlgSpv9kyTBF7/4RSxYsAAdHR3YYYcdcOmll0LKtAKaUgoXX3wx5s2bh46ODhxyyCF45plnNuKu3zi0+9Po5nRz5wdVwcn/R5YUqCCMWyiGgVL6stXg4IxLx8Jq26Ql6xr5jlXBaPqkEbvX5V6bSd3PEJ1rxXCvjRkrBLcpbymJ0N6pdK7V7p11iWxdASVwCuVIh9hrQuq/ibQpeLSX0FTeq0QclShAh/lbNo16tHYPWxUxMXNVE4mRmsBITWCoJjBcExiOBUZigTiRtqkQgXNmS/yWQl0CuBwFdt1KxNFRStevROk5+i+VCuZaoMk/nFTHdh4ZoSj3mtYIOEPEOUKWzk2fF2PaQuSr+Hl4TC5Grfnfc889OOywwwrfu/nmm3H66aePe1OEq6++Gv/yL/+CW2+9Fbvvvjsef/xxfPSjH0VfXx8++9nPAgCuueYaXHfddVi8eDF22mknXH755Tj88MPx3HPPoadncrX4jYGmFf+cf/JFfzR5KUimK99RLj5TsBXxuLaRp+NgCug485MbAZyBSWW6AObWgs6VT7vvKVNZ0OyZ6ep5VpsGs2MZ05uicsOkRbtcwJmyWrib2885Q6BSoldMm7wVacOMtF+mi/Qw6Fx+pOP1dXOzP8qpT9v4psSr51Gmpa92JWS7CWoCBQIuDTlqCwBnMNq/tJYNt+0uZwxSMXChEAUMiXSq9ymy0mRdPFbgcsSmfPc+cgnkLQD2c6ErK6jt4K6TfZ1q96nm7xzLvR+aPVshtE0Nv6jGv4eHx9gxavI/+uij8elPfxpXXXUVSqUSAOC1117DKaecgoceemhCyf83v/kN3vve9+Loo48GAMyfPx///u//jscffxyA/kG4/vrrceGFF+LYY48FANx6662YM2cObrvttgndy1SFpsh6NOp6Rj/EnDmFf4zar1QDM7BxDXBXUGDpedaNkPETI0MC+V3aEr5IyYj2FTAGZfzqupSuTJvPgAgSkExBMJjiNQ6p8LToEOcs1e6VrmoIqUv8psjV8efUP0CvDXCSmGw9f3IHKOhufspU/gsMSbv3Xhf7UUbDlUg4Q2IEAio0xFiq0UsTd6ArDzIEUiHhDKGo17TJMsNQ3ECJhAFlpC5XgMkLAHC+DumYeuRdAUh5vLBYkHuM3Det4Hbzq4ttYY2/3x4eHu1h1Gb/+++/Hz/5yU+w33774ZlnnsFPf/pT7LHHHtiwYQN+97vfTejm3v72t+Pee+/Fn/70JwDA7373Ozz44IP4v//3/wIAli5dihUrVuCII46wY8rlMg4++GA8/PDDDeetVqsYGBjIPDYluD7UPPJaXDomaxp3NS5tVlfpQ6q6MWmVuDRgjuXH5kzSLL+BIuT2StpiYHz7FAjnmt7deAQy4WdM4SyNJbCV9BirG1sTEnGi/fvkjycQiZP53u6Fpx0OhYKJDVDWZF8T0pbjdfdEMQZlcgWUAnSUtBne1vk37W7dz8TOHRuXQJx91BJpmwHRNeTdKOl9dUzzpi0wrVsKuO4PYI8x00a4/kHvh0Fq3o8cNwHdO1ora9Yv/h4oqu7ouILoe6XcR8F3xsPDY/QYtea///7748knn8QnPvEJvOUtb4GUEpdffjnOPffcCZfEv/CFL2DdunXYZZddEAQBhBC44oor8A//8A8AgBUrVgAA5syZkxk3Z84cvPjiiw3nveqqq3DJJZdM6F43JvJ6Nf1A5iO2qf0uU9ngLBpDmr/bUIf87+TDZ07DHTqNfOmAAs818GHGRG/Pdf6S9k9z0Li0617aJliYJj4UmyAUwHLuAFLeaa/acpBquFKmQXAQEoIx855u1qMzCVzhypT4ZcYVYNwAQsK4XUxTIKFvmq7nr+ckEozC7P8J6woQEgFXCDlDbKwAIifMCOW4HBhF6CtLpgEDEsdnTqWMGQMCmQ2gY851ud8J5yvgfOKTDysYOdYHfTz3GnmLgtf4PTwmAmOK9n/uuefw2GOPYZtttsHLL7+MP/7xjxgaGkJXV9eEbu4///M/8b3vfQ+33XYbdt99dyxZsgRnnXUW5s2bh5NOOsmeV1TXvtkPxPnnn49zzjnHvh4YGMC22247oXufbDTz/RPc4L38WM7SFDCoLDlrH7lrtjW+ZGb86DntnjRhmIA+kAnfEFW6H2bHWg1XadM2nMY/NH2aRuhck7OuHg/rsiCF13VtuGMlM8QtlQ0+1JYAAKCuedz2BaBrD+z90+Z73RFQCxNE0NJIGVowkRDGQiKUKkwttK4AEycQBmkQIgkAFAeQ8dUDukWyUkiQpmiSZcRNvyQrT/4vfQdIKHC/FxOJonr/rnWoiOjdv7nJPOl7eEwgRm32//KXv4wDDjgAhx9+OJ5++mk89thjePLJJ7HnnnviN7/5zYRu7txzz8U//uM/4vjjj8eb3/xmfPjDH8bZZ5+Nq666CkCaM08WAMLKlSvrrAEuyuUyent7M4/NDUU/oK4mWKTguT/E7hhLJjxNzaIBpIm77gbS9G2DHIomd/zUAAWhITVXuy4Hu3b9wyUtpbLmYbtvpE15Qs6MuVo/AlNfgPbgpiYmTpqgvhZmo/cja/bmNg3PzQKg8bHjArDZALLeLUKuAMoK0C6BQGcFmONkPs83FqI9UkqidTuYv9VEYiTJHqcHpVK66ZTuQzqPjMm94CFl/SM/n91r/rnKPfLr5z5TDw+PicOoNf9//ud/xu233453vetdAIDdd98djz76KC644AIccsghqFarE7a5oaEhcJ6VT4IgsKl+CxYsQH9/P+6++27ss88+AIBarYb77rsPV1999YTtY6rC1f6JiF3UB+Glpn/S5KVSdWZ8IOs6YMbcz6HPZTAtg51x9VokrafdDJwBCQOYzEaeSxOIZtV27mqlLKO5A+Y0s45rok+FD+1KsK4G836ArAAhlTYDSGM1EI4VRRkrgFJZjZ38/Qg5uAk6ZCIN1lMi7YQojf8kYQzCtOYVNm8/G5ynrQw6W0AqhUBqa4FLljbF0RJy+nmTywWKCgilggvdI/rLnPtG1oHM5+V8EegzyIOhgXYOJ3jQHkj/KKWQSFnntydLXZGlynXBWNdFgQvDw8NjdBg1+T/11FOYNWtW5lgURbj22mvx7ne/e8I2BgB///d/jyuuuALbbbcddt99dzz55JO47rrrcMoppwDQPwBnnXUWrrzySixcuBALFy7ElVdeic7OTpxwwgkTupdNAUU/ykSIrn83T+SkshOvukKDjaKHjpQPOHS0PFjDtYh8aXxgTOQhBwRgGVwaj79SCor87xmBA/oMns7lmsBd4tdeAx1zoFiWuCgbgDGl0/EYaaH6KqgoDkDEya0w5aYCAsaaACJQDs40WVOef1qPgNoE65RAaXz6YcAh3Wh9np2bgyHgCoIrhBnyh7UeuMV79JqueV3HCNBnLUCpkfpamXNvrECQu1/c+ewyn3I7fKuKX5JQ4AZV6uM0dWrxcZejhyu8eOL38Bg/Rk3+eeJ3cfDBB49rM3nceOON+NKXvoQzzjgDK1euxLx583D66afjn/7pn+w55513HoaHh3HGGWdgzZo12H///fGLX/xis8zxL4KrMeXhKMmZ8zmo8h2DojAyG02tyTMfN0EkLqQmU+uLdtaSSgsF+cA/wGi6JmtOAVaAoLHunil+wM1dZ4xK/zr+fZUSvzBrUuwAuL5OMtsDsGZ6xvS4gDOtrZu9QxrhxGw01VBlxgJAJG2FCRN4yIwJPUFKzER2QgGB2T/FAwQBQ6jq4wG0m0GXQXZLHocyFVRETgig+2Z9647JXLjCHOi5MjEAylpIXOHAfW335exvtLBpjLKx794SO0vdRm7ZX9cq4eHhMT4w5Z1qGBgYQF9fH5Y8/cdJFxrmzZs34XO6AVRAvfbvmsfpfCJvMsETkRI5kI+50ViVe05z0A93vlcAkJJU6s9N53ED0NwANbfcMEBmb6rw56xrYM3XDGn0O1C3j/+/vbOPkas67//33Du7a2PsDbaDl21sAgoNSexCYkgqigIIapcCLiUJEAg4La3SFwiOgQBNo9CqwSR/FFrRJGqEcAOJXFXFFqFRg2l5iWUpgJETDG0JrQMG2/EvirWLeVnv3nN+f5zznJd778yu17ueWc/3Iw0zc++de8+dMft9zvN2pPWwxMzjlrt1He0yBV+2FhtGfi2DIsSybUtk694eK1IBbTjrw5fEJSWJSGL7TX+vmjBAHEqR996Q8ueBD21UwkFIDQREoovyvngDSttrPhPv/8XePelvhejfl/z2COJfDkdMxPBY+K5j0d/fj6GhoaMyn4eQqYC9/Y8CmmX+h1gqkLr+w+xcFFvDQPreiHs2zyCO+Urs1Z4/avXrxcbu83FoHQQ5dNCDK+UT74EKXgQDGCVibccUz94buW3a43ruJOeJy8aUM0S0cd0GEcW6ves+8/FxbULr3EIHg8ZWKMgaAtYjEAtlaKRjy/UybaAKDYXMNiPKIjH2RpqByW1pYJFFnfvEs6DCdxZ/5xJ+iI2n2AgSr0BcJqhN8AwFj7tJ/t34LdHvH3xC4beODYI4HCD/imK1V+XnaF8cVohn+XL8VPb5J4TUQ/E/SkgSpuD+gJdi4nUGgMT/lTMAEhe8CXH0ur+9EkKQXIBCBwGWdrq5ktl8agSIaCsjC+coL+AFDHThRNvAuderbnfrSVBe5OIkQjsr1n6c4gmIZ9mNTNr4Wmymf7g/bazLvHCJfDCAMToYD6XSPTHAlMqQu7yCrNAojHLuezmvW7FQyVhDuV4R5QJkpfsW4s59eeTyLydS1j0b98PK7xy3Zva/O0qx99IhqrRRjKH0M/LvLMzuFYA8V8n75HVJ8OPzEEKmFor/UUZ59t/6WJv9j8gTEEQhzMS1m8HXrSwnBoCBgtq7268a6D7qwu+uMZBSUPHsH7A198aV1kUxa8CtC6Bsk12VKehoRgikdaomdoWbkBgnMXdjAJ0p//CdA2UMcenbwAlu5h/q0sUDkGn7We3i9bGbXmL1Ur+vCo1MWQ/AmALGIEYZgnse8AaOdo2C4nCDUga5bt3C1/0S0TidN6cUCkjCNYjCRKV/MOV/PiraVjeTB1LxT2bspfcN58VJwgwUfEKOOBT/o4i62b9QN/u3n0FQJJ99j0rMV2bzsfjG11UKMNY37UVHjhIBypQVmiwyAiDubNdGPyy+A+dFMNBKuX78sQchHYfKFHJYt3tmbOLemPMujGkDXWiMFfYzuav774P0mrexeAMbBhjNM5cAaPzsXxfGJ6tp56YvMln5r1q6J94FbaxHQRU2OVCWB45n4toYGB3K9MQAyFRYbllWVpSFi+pyA8rhATEGvOCXxN8nTCIKSfj/tEAlT+Ha0X5V2iZDlTUUDtWdz4V9CJlaKP5HIT6pC/V/x+OENXH/iwHgXdc1n7EnBsoGhL9uppBptz+O4wNuhmuvkynboS6TmK8ItzLeDa5jMUIQSW3gstCDWzwWETmXsvEEZ4wYFAgeAVXAl9D1NjK/0lyubAZ+1shQmND4R9zkOpouF64WXzwKdQv65Jly/QUyZFloyhM3NNJegENLXzGAoNyiPio1BJQKnfzEGxC7zcO/gzQ/I/yWwSCIfr2Kx6iVDdAqLCDXjo+LcyTGIxlX3SBU+m+YEHLoUPyPMsrJfxUPgKn+sa4zAOqYsAEQxfPjRDcgTqQrJfY5b4DM3qX5TmxAqOgcJjqHvQcTksZczLxHZcgK+yVoA+hC+xm8NtqXw5meHA0V1b3nCpmxWRD2vpUXbJ9cCEDnWWjlm1vPQdoa2D43cmfYZDYcEHezkxp+reP7MiigYLSGdkZOnSHgOy/quH2vGEZVQyC89q+qv2/lN6/9p1A6T7xtcoJc7vXfbDzSOIolf4QcHhT/o5BWtf+tPhMbAEAzMZAscGtdJDNACTn4EILyRoivCkAkngCywngXuZ+5OiGDsS7sci/4+POF6xinEMrEcmPj+UopNBoKqojHX2DUjWe0MMig7TUbGRpQyJx4Z8bO2AENpTKMFgaATnIIJE9BuwV9tDboyTOYXFUMgLg3QMOkwp928Yuy810ORuE8DhoI4q6DyEt2vPcKlJ7rYuzN/w2Ut9UeOmnKs/pK2CHYAOk4IB4tzvgJmQoo/kcpIsDx7F9E0jSZuScGgN3Q2guA9I+0csIY5aC5xD43FhFyJRn67rU2SetdwM7e5fOSzOZtE21d+EDkHfD3BGRGoeG66clSsn3SXcgYGKNdXgIwZmxiXp4p6MwaDr7rnQkL8BijvZshJNIZjEWelMIABhra2LUAbG+A9DuOW/nmWdSwxzXysc/Oe6NDuEG+N8m7iA0BBePaJ6deAfGGSEVDWNgnCvuEn7rJv6FDo5I8WBLzeLlhY1Lxb2auKhlgjdeKEDI5KP5HOWX3f+wxrZtF+eQ9+csc7a/7w+7/qDuxAuDEKCwvKzP5OBSgUY3nixtfKdjcgTiWL7F9N34pETRKzuni845GZg0AWVAoyxT6GpmfQsrqecYAxs28s0wl349NDrTvda7gmxy768bZ8loBxi3ta/IM2miXE2BLA+PVDYHgCbAJkIDJVFSl4AyCLOrg575rad0rZZHyW8Uu/1zbHbFXIDYCfNteld5r1RCol+OJepbKwi8LEsWfrHPxy2+QhCrGvRoh5FCg+B/F1MX/BRMJR727V1z34Y+4UnWJYk78ZCrtyDPYpWeVZNSHEIAyQGbCDNqfB3ZG75sPGZfdXxJOyQ2w5QGAyeD6BoQV/sa0FX9tDLQzAJRS6M0yoMeNqzBhRm1CEp4y6cw4y2xZn/0OMtsNUIXOekkTHQO3YJANBRRGIde2vLCuXE8MDMB6LEwk+HEXP3mdqVCmF4urbFPyQyspsxRDwESCmvb4tzNrMdzCuGoFV8kVm2Dq38p3NDZeMp+7hjcAkjEFI5AQcnhQ/I9ymnX/A4IB0Oqz7sjoQ/Bu+zhKIEZBfA2tDHIf8xavgjMCoqqAeoPCjk0b46e75cz+PLdZ9Lk2GIG2IlsYjBWAMRqjSmG00JjVk9ulc7MMSgE9WQY0AAWNMR1f1HUEjL6X0JXQlgEWykBBI1O2mKDQLjlPDBkXHZAmRpk2treAsUmEuUq7Dcbfs40Q2O+2cK9tp0UVwgJKwgAIlQI6/DTy8FUJTkWN10x7c84BAjHwqh4gkxgCyT60pk7TfZhI1+2tv0Z8fZ+M2eR4QsihQfHvAloZAMD4SVTpPnE3S0MgO1PPFaCV6xLoZ8MK2gkhXFzfN/lxs3ejw0w2jAfR7DbO9g8lfrnUiytbntdrADTs8WOZwdiYTQYstAJQWM9ED9AjsXiX5Kfc7F++I2k1XG5qJCEMG8bI7CI92nXxc255ldyD7eJXQDwcgHJCnuu0j3+dN0ByBaT8UcICpuQVMMagsA0UvDEgIR5/vug7Ve6NcT+lig8qvfQJgtHZJiO68UfisErlfM5AkaqNLDJS2OqXkKmF4t8lNDMAxnP/153HHw87b/Tldllo2yv4en0DIANU1Ovff8a+8rPCsNRvmhdQuOmuzQkwvl2vyqwB0ON6/llR1NBjVqBHx2zvflGhhkuIawBAbr0Qch3bcMeg4ZoO1YmyUgbG1e1nkqmv4zUB4AXWGGng4xoEuXK9PLOzesmNyEx9iV7cyjdz3QatJyGUHTZMqZMfwm+Q2HqlEIs8q7p9KtT9J2IdGwJIGe/fj3fjB6sibBehj19T9AmZNij+XcREDAA5biLnss/OEHDtcpMe85BQgG2yY7QVj0ppn5zTCW3mfNchmS8kuEn8G7Azw0au0Jtn/rVCBvSGcY6Oab8S4EFVWCHpydDjQhA5AJ2VvAzuOnB5C+VkvXLGfqVm3xhfty+GQOFOrF2sRDr2SU//tIlPfU9/uW7cxlca9pR7EMTJiMEYUMm2+LjWv3Xpvd9e/XdS0vUkdwIqCqPELnw0EXy3k6JPyNRD8e8yYgMAmFwYoO6cyvprAdhZrNYuLo00Fm2ApLRPKVRELs7sl9wAGWdcE58phTFtZ6gN12o3zxR6nQEgBkbhGugXGigK25Qnd/kIuRMaiaXL/WvniVCwdfwi1uVZuTKy7nwo3dPaoMicN0ApbxCJIeTd9cqV6Unr3lIHv3J4oPx9Z94QgC+jFGNA7sO47DlviAFA5Zjy71/+fWt+8/i3Km2rrSJwG3KlEqFH+Tj5PAWfkGmF4t+FxKVa4gXwLl5vFEzcC1A2FpRSbilb+DCA1OV7jwCkl334fO3CQVFmv4Fb0tcJ3pjWMIXt3NdwLXl7XFJfI1OY1ZMDAEYLif3bGHxsPGSRiCqUE+bkM8Yn8uUZ6oU4U76UTWe23K7IbFhAR4mBEg4Y86UBrrcAgMKJfO4Ev3DGgGT5K1UtGYxb+MYze/ucJlPKCn5lQ0C2xZ/1vy0iUS//NvGsPhpC3axfxpgshBR9mGJPyJGF4t+llA2AZoxXzy274zp/IPzBV8rOPONcAHFTh9cuHGCiWWHZ5Z27IDwya6xo26nP9soHGqaw7v0eK/xeZBSQjymMjWm41gFJMiGceOaRhElDwDgM4MvnIiFWqC/bywxgMtuFL49yA3LXwAdaXPRIPAHKnbtQ1niQ/gDiAcgyu+qfeCCyJsIZh2Tk10jq6F0IIDYMok9PKBQQrtVse2oYyHFZyYsxHs3+/dFYIOTwoPh3MfIH9FBbAQuxmBuXKBfO7VaXc6IZN/kpXGta6ZLnRdmX/4V+9bFHQAwAnSsUOkPh6vy1Nhg1NrsfgG3mAxsCkLK+TNnGPqLj3iWOaMlhl5iXqfqV8ADrBVAqap4TjdNn70e6lMOGGAoVPAEKIftfWvjK7yC5AciUXeRH22z+TCnkBiEfwHfvi4S15EaPSb0VyZ7a33+i/yRqQwIthHk80Y57/EdP4fNyHNjml5DDgeJPJmUE1OYK+H22/E8j9PcPolhdvU/c+IUJZXdytixTaEjM3SUV9iLKH3C9+iUXALA5BTbpLxgAdrYOvyxvcu9ioygbkzbu8hphJUG5r9gNLoJvE/ZsvD1O1BMXvU/m0wZjyi7uM6atQSDnjxf2AaIVBDObe6Bhvy+F2NBwBoA07Uli6SFsE7vdmzd0Km+r/92nivF6/Ifj0vEYlPIDCCGTguJPPIfrCXDl5omYSL1/IkZZunpfoV3CnfSy16ENrIJLhMsV+qJe/b3GzuitOmhgLPTIHwGgxrQfR54pKNiAvVZlcbEGivdOuOMLbXzdfhDo0BFQWupKol4jz6ANnNvfJAv7SBxfEhy1Cwkk/fwzSQxM8w/s9Z3BoUOYRjwUzVr2xp375H1WMgqAqlse/v3k5HU8L4L09q+GIZobAHacpSTBVskIhJBxofiTChMxAhJtMEHkbM/7qsAGoyBqnZvJ6nnWAzAGAG7RHm1C+ZyBjeOPNjLM6sl9TL/X1fVbrAGgDWAKg9HMiX9PZpfnlbh+FsoGw82kHgDjhFIr617WxlYJjLnxSL1+nKVfGLeeQK7QiIQ/XtdHcgJyWEOgUDbZr3BdCrVbOrgqhuG9cfkRytjx+bh/WdQTowCA9xK4+0XIW6j8nu5q02EAFCXxj49s5uKXE9DNT8jUQfEnTSn/sW2efCUvbExfuv/VGQGAuAeCwQAnzEpc4pm47wsc1LZW/6ACRkY1xsY0Zvc10NewXoA+ZVfeUaMKSmmMFaHhzlihbeZ8j0LDz3yVrx2wsX/xVoRxZsomwWcuDFCoUJ8v9xG38hXBlja+OpNV/aqhAPlOG3kIf2RGQWdp9z7jx1Zu2hO7y23OQPzeWWLyc0A590Xmxp+0Fa7zFPgf1V5tsnJrKi8sYTVGUz22fPmSdyLdN8mBEUIAUPzJIdBs5uXb4wL+j7KCldmyERBn0PtZq8TGnUtfFRpoZK57XYGDY7ZcT48aHBzLMKoN5jgDQGL6qkeSDDXGVKgoKFwuQW5kdbswyJA3kPqQZYZuxwvXOVDDyLPxFXI+H0C8AiL+hbZLChtj1zXIUW0WFK/s55v2lLr3GZd9H6oCEMYeewhiIyGu4pDQhTMKsmjWH+63ZlvlBar7IkxpX62ZaNIlfWsphx/cIyQ3Kgo/IVMAxZ8cNsHVXPIUuJljbAQk8V9J6ivlCeR5hnhhmUID+qDBO2MFDo5Z9/tYYXDsrAZm9eZoKDEAXFZ8ob172Q0E2hjfYCZDKYtfKW/AxPdk++tr39ffNvJx+QCFrpTqFe46RWZd/9rYpYBzo/wqf61q9aWNr8z+AVUxAura+BpjYJRyhojMrFPjQMlGCZybeOEe99zC1V+b1V97ZD3lUFLtdWq8ELX9/Zt9nhAyYSj+ZNrwvQRcOCCZiiKIk3YNb+IyuSxT6FWZn/lJpv47owVGRgubcJdZo2J2b46eTKEBBeW8AaNOnKWJUOzAlpm95BNo959ModJfQJL6FDLkyqCh3WI+bjyFiUrltCwQZFfTy4ysMAjv0rfNgFQ4d01jo7ifv9bGewfsJdJOftq5IcJCPwj3FH/J4eYr1/OHqNRLUzJRqp+rbIkNiXoS0Y4m8a3a/QJhRcfKOQghk4LiT6YVFauBqS7hG89eg1seXoh7GiGpryjsjHhkVOPt0QJZprzAq57ct+u1eYCZbyQUeyZEbLV2SXfGeONAwy3nG40/xOpdWMGFJZTK7HK7Lr+gfL8+n8AlDEp+QJxjkCkkC/ok31fp+j5UEYUIgNQY0PEzRNRV8l2XZbNlkt0EqFQJlL4Hv02lx5dzDOoEP24NTMEnZGqh+JMjhs0LkDm49QIEkQyuc4MgjJmyPfsFW+tf4OCoXab34GiBd3I7M+xtZIkB4OPpcrUoti9irvxM2fUmsCvzVPr4i7dAGesFyKR3vw5u/0YpBu/zINy1NawwKxPK9JJnhAS9WAhr2wlHngFA+TBBvAiSD7WIzJrkyY/Lv/a/Snj2123ye1a2l0Ly5Va+4T6rlQZs+0vIkYPiT44o4Y958AIAzrWN0Pu/cG7szM2QG3mGWe7zmQLeQlg0aKywCYF5ppDlrvxOzueOiWPOUmUgBoCJrplBQTkjRJrzxGPPnbjlsBn6IrZxHF5yDJKkPOdhANwqhT7MERr1iHBmziqJDQK/xn1N0iAQDJ3MALLIj72uu2/UlNeJZ0DelkMEE/gt68Q/ekoEXSn4MsjK5yn2hBxRKP6kLfisbRcKSErvIDNll6mvFBqZzZbvbWQAGlAKGB1Lu/oVrpe+zwy3V7Kz7doxhMZE4rkvjF2jIFN2TNDV2XcsuJCxGqTiLhn7LnlPR8cCkRibUDkQCJ6HTGbLzgCIlzNu5hVw79LrTUDoTXUg487+7XWbbS+5/oFKySMhpD1Q/En7UbBeAOcOh89edwIKgzENZwQowBkABzON0UJb8ZGZPUJMHUhL+1DaF8/+Ec3MYWwPf7/anwqGQrP2uJkrpfPxfOM68ylAabfYj7t+uV2w9xzIAP3Y0j7+3huggkfEGgpVQyAeG1An0KlxEH8/k6VOz1utMdCKco//9CSHdi5CSBWKP2krlTCAi8OLQGoEgQSsuz1TIRHQ2QpJlrkk5gFuXzL7jbsNhqV4dSLOroMegrs9js/XxePLImuv64yA2JBx2yUHAJHXQJ5NdJ7cdk0KBoDzCmRusR9pSORzGPz3OjFxrIrzuB+Zclot5lM2TpRSvu0yF/chZPJQ/ElH4JMBnfJlUW56Ec2QJSaeK1vWp1RoGZvogPt45BRwi+UEI8AbACq04RVjI5TMGW94KETeAkQr+5WeJacgg4HKMuf6t+cv/AI+ypUK2oEVzhAI45c8BHthDdtnABIKMPAGQZbJ2OJ7ki8hfL9yD5XvChMzFA6V2tBC1H+hWQgi+RqQjtkvwzy1QyWk66D4k46hzgAwxq20Vzo2c6ECnbl2vSZkqEtSv+QVZDp4DkLiW4iPq8wm+fkYPsKKfsbY7n0ixsa4cj/ArebnKhCcCIs7Pl63Pu7kJ70P/GI9Mot1GwqXcJggxo8YCCr09Y+rIqyx49YeAErJdyYxAKpabyoGwmRpNXP3v0Nplt+q3a8YXTB14yaETAaKP+koygZAngVXvJu4+9mgUvBlfd4V7PIFknNmCkpb978k3ykfyw8CDVc2Z9wYfNa+MX72ro2B0lbAG64dcabipX3dKoQuea4RlSmGhX7srF+7fADl7kcrBaVdaZ6JlhKO7sU5CpBBkgjtmLXcT/SJajZ9WOzHb4q+qvLiQMlHJ0BdeL4s/mM17X2bpRmoaD81n5CpheJPOo6yAQApWndoVyGgnNBClgT22fauTNCgMttVUfa/0rbvvnfTu7i5DaRXBce4xACZuY+5ZX8VbNtfcf3HhoCtQEgX9wGcZ8BEiX2ub0BDevuj1NPfDyJMf704eo9CSUhr3O4q8g7E52jmJSjtbsl4uYJ+QaQJUK4UiL0VYrQw3k/I5KH4k46kzgMgk0YRAknGs+vtpJ4BE7sIkM7+4XZJroAYANKEBk6YtVIwiNr3IfJCuF4AkqAnuh4LfbzEbyPP/FK+cZKg9A0AopX9ohBDssiPkbtvje83EH+f8nXId1hzGjUFBsD4Y4sHUz13JTcBIZcCSNv8EkImD8WfdCzNcgBcvl7UKEfJZD246QGfbR9n4pcdCb6vfxaO87NyYxPzcqUwpmwDYAX4dr6F1PAb+Na9hTZe/E1u+/ib3K4GCGQwCrZTYCSs5UY91o2PtG2v8wQIScOeKDwgYQ/xGiTfJ5B6EJCOo1JdF31e1Qh1+beqo7I1UvV4X12731j02eqXkKmF4k86mtgAkCiAGADJ7DBTME6UZeYsZXWJgNiJfXSMS/5zKlqelRsTu9NFxOEFXyvYhDwTVg4UEdbGrgCYZW6pX2lZqMM6Bs3q4CU3IF7EJxXzMK60s2DwfJTDAKYmlDFR4nLKiVLOKZC3dYmF5e11og9Q+AmZKrLxD5k+nnrqKVxyySUYHByEUgqbNm1K9htjcMcdd2BwcBCzZ8/GueeeixdeeCE5ZmRkBDfccAMWLlyIOXPmYNWqVXjttdeO4F2Q6SZxyUMy/eMDEMTUHS8lelqbpLxMjpNzxcfVBa1rk/miRyNy75f35ZG4a2Nsy2LXibBwyxKPFRpjhW7aWlcWI8pd6CB9hJCCjKEnz9Dj9snrnmi//4x7VMbc5D59OKPJI3yvacWDrLWQq+gcsImaWWWfC5tEoROlXMhGKQo/IVNIW8X/zTffxGmnnYZ77723dv/Xv/51/O3f/i3uvfdePPPMMxgYGMBv//Zv44033vDHrFmzBhs3bsSGDRuwZcsWHDhwABdffDGKojhSt0GOAGUDAEBon+uEWzkR8YlwCOIeryMflukVz0L1uHDdYDDEQtjbyNDbyNDXk2FWT4a+RobZvTn6enL/vqdhRVjGbIztWWCFX2NMDABtMBoZAnJP4/Xajw2DWqMkV+hxYl82AnL3iA0BMQYaeVZ7zvEedWNJtqlwjVjo4zwJ+b4p+IRML211+1944YW48MILa/cZY3DPPffgS1/6Ei677DIAwD/90z9h0aJF+N73vofPfe5zGBoawn333YcHHngAF1xwAQDgwQcfxOLFi/HYY49h5cqVR+xeyPQTVgUM0etYH5XzD8fxf6gQv/euZJf8F7v/JUwAbfwyu3JNwCX0ZXAi5TLzc+Xd/TKWOGkujae7pYN9wp2NX4hLW0IImQothevq2tM4OSrjDMeLwZF+T6a0vY4JJuSn42qi0WUXfzlhj+JOSHto68y/FTt37sTevXuxYsUKv62vrw/nnHMOtm7dCgDYtm0bRkdHk2MGBwexdOlSfww5+igLKxAS32yiGELt/DjufzkuzP5dIl9p5p15t7udRfc27Oy+r5FhVk/uHnZ7r3PLyww3rkwI1QjGx+rFgyHhgMJYb4D1CGiMjrlHYR9j8bHjeAl8QmRpFj7Z2X3TWb8aZ797xDP6iQh/MJqqD0LI5OnYhL+9e/cCABYtWpRsX7RoEV555RV/TG9vL4477rjKMfL5OkZGRjAyMuLfDw8PT9WwyTRTlwAIILR9VTZrXtr1FlFteZz9LxUCyELJnxgAPvmvphlQ3KgnJs7MBxCy9V3mvRaPRVSGl3gG3Pi0y8oTgyT2LIjRE9YZiBb4MaHcENH30kpgm2bot2Eynoh5/UuP95aYajkiIWRidKz4C5UZ3gT+hx/vmHXr1uGv/uqvpmR85MgTu/+TtrDGCawK2f9xVr+U6OXOMlCZQmYr8HznOQPbzEfa50rmebPs/HhMYngYZ0D4jHtlhT3Oxq8QZ+6Li94Jv9ay2I/yDYVy189fy0JDpfHKRdIwRP24jwQh/FCuJwwv65cZrhm3C+Ww3p+QydOxbv+BgQEAqMzg9+3b570BAwMDOHjwIPbv39/0mDpuv/12DA0N+ceuXbumePRkugkr67V2/0tWeRzXT9zkSmbzKgkBSM2+JOONjmmMjGmMjBY46NzwY4X2bnedhBRaJeCFxDtJgvMLAskYXSLgaKExVpRc/CYND/jXLmHQVxG4scu2iT70BB6+3XGLx7jnN+Fewjb4sEt8v7Kdnn5Cpo6OFf+TTjoJAwMD2Lx5s9928OBBPPnkkzjrrLMAAMuXL0dPT09yzJ49e7Bjxw5/TB19fX2YN29e8iAzkChjX4hF3ZehuX0SAjBRAbxfpa9USSAz7kIbHHTCHz/LQ2LxsZAllQVRjF3i7I3IIJAM9zgRThuE8xkThM8E4SwiwRQjRR6xeIoBEFcWxMeVDZiq8FYfrfZVBL0k7LG4j4kBI2WQ8vsAyUN+s1YeAkLIodFWt/+BAwfw8ssv+/c7d+7E9u3bMX/+fCxZsgRr1qzBnXfeiVNOOQWnnHIK7rzzThxzzDG46qqrAAD9/f247rrrcNNNN2HBggWYP38+br75Zixbtsxn/5Ojl3L8X9DGrdCnZMGetOyvcK7+XBrrSvxfQgnOfe9FRwQYsBn6zpMg3oWwqp/y7nWZzedZNRtf1qQXl7bSNjQA95xngNHB3S2x/NiFL8IYT4clxBAf678r6YYYNlT2pcdXj5vwCjtNtNnAGmDJtkppZbiAip7jsTPOT8jh01bxf/bZZ3Heeef592vXrgUArF69GuvXr8cXv/hFvP322/izP/sz7N+/Hx/72Mfw6KOPYu7cuf4zd999NxqNBi6//HK8/fbbOP/887F+/XrkeX7E74cceZrG/xEy+ZWyiX5+JgmX/KcNlIizK/9TLlSgXQadF+HICJDXNvFMQRXpwj7yLIaBfR3E1xsAkZhrY6+XIy6Li+4zuq/ytvieZVzGVBMKQ0vf9ERxPkBFVusugqpN0GqcMc0W9vGfq8nXib83f2yL/AtCyPgoQx8ahoeH0d/fj+07/jsxLKaDwcHBaT1/W9m9uy2XFTe+iJ/gXenOjT6mg1tZGs5kSnkDQD6rTxj0Qj9W6MS1Lm59WdTHfi7NrpfEwDwS/0bmGuuoUPpWptDBwNDRWNObjRIDk+8g+i4QxtJsWx1Na/Vrj22S+Nj05PZpz57d5U3J+VTpIxWDKTqo2RgWvutY9Pf3Y2hoiCE9QprQ8dn+hIxHeQGgeNEbyQpXUfmfiGqhjW3cY2Qmbg+wTYKs1ZBnyu23jzG45DNtoKG8SMMZCLFYxfXvOlfoQQZkdp/RISQg5JktC8wB6JJBEifJx/3+y8eYkgcEAIw3AuT7ivZNwPSv8/aPW3UTexdCOkPiui9/urwvNqjiMARn/IQcPhR/clRQZwD42n84179z44uAalXt/ifnkrI9WcgnXtl3rIBbxc95CpwXwPYLCMl+9vIGBpkLBZgoJ8DlDJQMgHRhH0AkMS1pVJERYI9JvAE1CwEZ95+K1is5Zxh3uE66rezSj2fhNadMhDqEMtL3aSw/OkdNfgJFn5Cpg+JPjhqUE32JxYsBILX/Cm5WbwDt9mvYhLtmcWQpE8x8AgFgsjQTXTwFRpXc/67Dnwg9EMoItSQqarlO+Fyzewuv/au0L0DJUDAu+y/2EHjxl+l86bOlq447wy8LdOllJYEvj3IdysdQ7Ak5clD8ydFFTcZZ7P7P7H9gdCgpK4wVYqkQSE7nBDJTtve+zNpzMQayEC7ItUmEreH2xavlxd7wwh2vELL044HXub8rt1sjpmWjIJy5GhLw+5pm6Jc8E/WHNc0XkDEKeV0WIyj0hBxpKP7kqKLZ7F/c/3Xxfyhls/hhbHy/dD7xl+c2FuC3K2WQZQa5+4xcx13GC52d/QfvA+BPCRP74iu6aEoGgAlu8wl4Cup3VcMIfkgt4v91cf/qNZttj2b/NYmOhJAjD8WfHHU0i/97938U/weC+96WC8LX8sfnyxR8m16tbA8Am6BXk5EP+Ex1eV2Hj8P7WH36eXvt+HzO+4BYaMsGQvo9tPqOqtuaHn7EqBglzb5c0FtAyOFA8SdHLwqpAQBE2f8h/h+7/6Fhl/V1RoKvAPDZ99IzQFzo4wtQJVNfhhddu1yq6A0Hn6IPn5gYPBhuV8UgcJ83ptYoiM/fDloJvGl1HJxHw3+fXNiHkMlC8SdHJeL+T5LaSu5/if9DG5/ML/F/IF3VT86ZutPrhadSnqcQmu+UM/OR5gFAXkflivIiFnR4wa9us2MN564X/+Z3cLh62jx/AMkaCIeaf6BU6TckhEwaij85ainH/z0yYXfx/0xFgg2XDCgz/+ij4g2Qc7e6bnxJESyf5Q9Ai+SLZyIKUzRTv7BugUoNGiDyDJQ8BrKvPGZjKoZC8v2Mc2+T6Q1mTEn8K/vrz+nHbQ7fMCGEWCj+5Kimrv9/4v6P4v86NgD8f9x5ZE8yu67G2msb1ETGgPT0FyWTHgGypoAxxjflqavL9wmMAMoGjb2vYAzE+5IxVb6k+rflmn/VwjApj7F2O6q9/SfEeA2FCCGHDMWfdAcivO6tn/y7bP7M+c3FAPCryxlZVc59zglRXP8u2fzKPUt9P2AX6SknD4oRYBsMRl4BEf64EkDwMYIw847HEh+m3DlU6aM+DFL+akxJ6P2LtNBPm3Lh36Eh+Q3lMAfQPHNCfqOK8aJAg4CQw4DiT456ygJZR6YkIq9QuHCALHE7Jv383dK0gHVfq0j041a+smRvlkkHQVPbyS8Xg0ObkJyIKAlRjBUD3wfAJy4iNSjcYQmVPAKEa5SPq69ZqDgQJlDul37XdQI9kbl/HKYQ4S/39ieETB6KP+kKapf/NUH0VKaQacCXAbi4v3ZCE69DD8A/A/CirxTQyDOMOQOgkSloZwRkxnhDodJFUPoBRM154qhDWi2QVgfY/e4ekN5bmZai2yxJTzU/rOxZUNFY/PE1LvvaJEN5Lgk+ou2c8RMydVD8SdfQzAAIygNX0698Mx9jAJPZF1opaInLR+eIl6k1RkM70de5Qm4Uci2hAOPCAulstjzGuJogbd+bGgRB/KuiH3sKpoI6oS/vA1LxrjtB9FWn4RD/+fRc0VdB0SdkCqH4k64kFjMVeQAAeHe97LcHZ4AyyAqNwth8fSAs6iNoE3r2G9jwQJEpZFq8Awa5VlHIwPgmQsB4xoAdTLqKXzUHLzEG4uOTgybyLU3A1V+XQxC/LhkDrXr7J59t8n0QQqYGij/pKmT2X86lUyWVU0qhkVtxbmQGeWbQ0AY6V9b938j8cr4uZG+FPxY7Y1BA2eV/lSwCpFAo44U/eQa8MVCuGCjfA4CKh8DfT2y4oGocNN023pdXotZ930Sr42ZJJVumcgwhZPqh+JOuo84AAFA7zbUtfJ0xkFmh19qgcO74whkAxoRkPC3TcVcapyHPTtwBP9uPnxNvABDlCKRu8WYL/CA6prS3ahy0iOVPhokaAuXER0JIe6D4k66kHP/3JWjWj18pz5PMfGMMdKbQcIKva551Ygj44HuUIwAoGC/CMuOXioOwEJB4BEyS7a5cTKJZn4Fm9xteH/73N52wvz8h0w/Fn3Q9kiWfJs0FgU2OLRkC9rneCIiNAbgeAibKL7CXCfF7qSyws34xQEzwDDQxAsRDIINPEuXQPHzQTibb3z90MARL/gg5DCj+pGuJ3f9xmZz32iMqo6sR0Dj2Hsr0Qu9+7wHwxoByxoD9XLl5UNzPv3ChAgUxCuLlfEPowNoh47z344/SAJtl5Uf3OlmaFRhIAqQ9pnqQKRkAKnpO99hBSqJmJxk1hMwUKP6kq2lmAKD8WjQnMgbGK9OzQiddA20rX+1S8XVkKMhre50QHijX+WvxRgT9i64N5xmIG+EEI8Bv8t6F1LNRls+p0tOyxsf9Eez+KBehyTmCAcMFfQiZKij+pOtpZQAApVmqqKKRBjbN3enl5j2Zge8QKF4A416H3gEhPCBKVzEIou1+WDUGgbxvKvTRvnh/ckzTDYdA9PXF6yeMh1wynv1LHqU/hrN+QiYFxZ8QtAgB1HSsQ7Rf8gMmUp4n4QE5TxwiaPoaAIxCZdGh0pikrW65o17aACgNYci9+vHGxkTlCypvOKTdHl0efPk8dZUMSJMb2eaXkMOH4k9ITJR0ZwAvquMRL9sruQKtvAJxiCDt2pd6AcQAyCsGQbWO35REv/lYo9eAq0gsJdaVxztZpW1SUlgNM1TDD2V3fyL8hJDDguJPiMMLdexvtjv8Ma0MgdgroMQQaJEwWL5uK2MgXKNqBIjzPvFKTDB7fjxaudVr95TDDqXdWXS+2mRDpIZA+XtL8hbo8idk0lD8CSkRlux1G0zyVKGsp/FMOsx0pUTt0I0BlFr62vPWdfVTkTcgalicGAKl3PlDNAaajTXZ1uSNgl8yoTa/oJqvkPb2b3VNQsihQfEnpAa/TK4zAsSdb3MD4PfZY8c/nxfZKGGw9KL1LLtiEJSqAUqnreYFRK+bbI/31yU+JuMZZ5x1xyllOya2+gzFnpAjA8WfkBao8vQ/ehkbAsDEZtHNPAJA1SuQXr/52OoOmaj4i8pO1ep/8biqV0nd/pUQAUWekCMGxZ+QCVD2BNjXpXI0dehCGrwHqtZNP1HPQLPxhvfNr930gOTY8vmrZXcTGY9ib39COgKKPyGHQJ0RYDckB1U+N55B0EyIEwOjSZhhsjPmQzMmJraNEDIzoPgTMgmqwpnWyFfL6SZWMVC3PxQdiOERrhMfXy/QM1Ohm31HM/V+COk0KP6ETAFll39daACoGgIxhyp4cec7uVb4zOTO2Q4ONUzSSWMnZKZC8SdTx+Bgu0fQdsoz/BYOgsrmVqIWd/FLrtH0+GhMTc6XjrvppaeUCS3XW0dS5UADgJDDheJPyDRRFqg6Y6Dswgfq9TBk9h+66JXPV28MHPJpJ8eRug4hpCUUf0KOEHVx/zpjoJW8T4V2TsQY6GQ46yfk8KH4E9IG6gSsNkzg97nPtTjnZA2DqZyMT4ssU+sJmXIo/oR0CK0qApqFC5JjpmNQ7aLJzXDWT8jUQPEnpANp5hkI+yd2niMWyz9UDlHDKfqETC0Uf0JmCIdaImg/M/HzH66hMKFrHcJ4KPiETB/Z+IdMH0899RQuueQSDA4OQimFTZs2+X2jo6O49dZbsWzZMsyZMweDg4O49tprsXv37uQcIyMjuOGGG7Bw4ULMmTMHq1atwmuvvXaE74SQ9qGUavo4tPMc3mOq74EQMn20VfzffPNNnHbaabj33nsr+9566y0899xz+PKXv4znnnsODz30EF566SWsWrUqOW7NmjXYuHEjNmzYgC1btuDAgQO4+OKLURTFkboNQjqWVoZBJz0IIUcWZaZyOa/DQCmFjRs34tJLL216zDPPPIOPfvSjeOWVV7BkyRIMDQ3h3e9+Nx544AFcccUVAIDdu3dj8eLF+MEPfoCVK1dO6NrDw8Po7+/H9h3/jblz507F7TRlkI1wSBdT9txNBwvfdSz6+/sxNDSEefPmTfv1CJmJtHXmf6gMDQ1BKYV3vetdAIBt27ZhdHQUK1as8McMDg5i6dKl2Lp1a5tGSQghhHQ2Mybh75133sFtt92Gq666ylvze/fuRW9vL4477rjk2EWLFmHv3r1NzzUyMoKRkRH/fnh4eHoGTQghhHQgM2LmPzo6iiuvvBJaa3zjG98Y9/jxen+vW7cO/f39/rF48eKpHC4hhBDS0XS8+I+OjuLyyy/Hzp07sXnz5iSGNzAwgIMHD2L//v3JZ/bt24dFixY1Peftt9+OoaEh/9i1a9e0jZ8QQgjpNDpa/EX4f/azn+Gxxx7DggULkv3Lly9HT08PNm/e7Lft2bMHO3bswFlnndX0vH19fZg3b17yIIQQQrqFtsb8Dxw4gJdfftm/37lzJ7Zv34758+djcHAQn/zkJ/Hcc8/hkUceQVEUPo4/f/589Pb2or+/H9dddx1uuukmLFiwAPPnz8fNN9+MZcuW4YILLmjXbRFCCCEdTVvF/9lnn8V5553n369duxYAsHr1atxxxx14+OGHAQCnn3568rnHH38c5557LgDg7rvvRqPRwOWXX463334b559/PtavX488z4/IPRBCCCEzjY6p828nrPMn5MjAOn9COoOOjvkTQgghZOqh+BNCCCFdBsWfEEII6TIo/oQQQkiXQfEnhBBCugyKPyGEENJlUPwJIYSQLoPiTwghhHQZFH9CCCGky6D4E0IIIV0GxZ8QQgjpMij+hBBCSJdB8SeEEEK6DIo/IYQQ0mVQ/AkhhJAug+JPCCGEdBkUf0IIIaTLoPgTQgghXQbFnxBCCOkyKP6EEEJIl0HxJ4QQQroMij8hhBDSZVD8CSGEkC6D4k8IIYR0GRR/QgghpMug+BNCCCFdBsWfEEII6TIo/oQQQkiXQfEnhBBCugyKPyGEENJlUPwJIYSQLoPiTwghhHQZFH9CCCGky6D4E0IIIV0GxZ8QQgjpMij+hBBCSJdB8SeEEEK6DIo/IYQQ0mVQ/AkhhJAuo63i/9RTT+GSSy7B4OAglFLYtGlT02M/97nPQSmFe+65J9k+MjKCG264AQsXLsScOXOwatUqvPbaa9M7cEIIIWQG01bxf/PNN3Haaafh3nvvbXncpk2b8OMf/xiDg4OVfWvWrMHGjRuxYcMGbNmyBQcOHMDFF1+Moiima9iEEELIjKbRzotfeOGFuPDCC1se8/rrr+P666/HD3/4Q1x00UXJvqGhIdx333144IEHcMEFFwAAHnzwQSxevBiPPfYYVq5cOW1jJ4QQQmYqHR3z11rjmmuuwS233IIPfehDlf3btm3D6OgoVqxY4bcNDg5i6dKl2Lp1a9PzjoyMYHh4OHkQQggh3UJHi//XvvY1NBoNfP7zn6/dv3fvXvT29uK4445Lti9atAh79+5tet5169ahv7/fPxYvXjyl4yaEEEI6mY4V/23btuHv/u7vsH79eiilDumzxpiWn7n99tsxNDTkH7t27Trc4RJCCCEzho4V/x/96EfYt28flixZgkajgUajgVdeeQU33XQT3vve9wIABgYGcPDgQezfvz/57L59+7Bo0aKm5+7r68O8efOSByGEENItdKz4X3PNNfjpT3+K7du3+8fg4CBuueUW/PCHPwQALF++HD09Pdi8ebP/3J49e7Bjxw6cddZZ7Ro6IYQQ0tG0Ndv/wIEDePnll/37nTt3Yvv27Zg/fz6WLFmCBQsWJMf39PRgYGAA73//+wEA/f39uO6663DTTTdhwYIFmD9/Pm6++WYsW7bMZ/8TQgghJKWt4v/ss8/ivPPO8+/Xrl0LAFi9ejXWr18/oXPcfffdaDQauPzyy/H222/j/PPPx/r165Hn+XQMmRBCCJnxKGOMafcg2s3w8DD6+/uxfcd/Y+7cudN6rbpGRYR0C7t37572ayx817Ho7+/H0NAQ83kIaULHxvwJIYQQMj1Q/AkhhJAug+JPCCGEdBkUf0IIIaTLoPgTQgghXQbFnxBCCOkyKP6EEEJIl0HxJ4QQQroMij8hhBDSZVD8CSGEkC6D4k8IIYR0GRR/QgghpMug+BNCCCFdBsWfEEII6TIo/oQQQkiXQfEnhBBCugyKPyGEENJlUPwJIYSQLoPiTwghhHQZFH9CCCGky2i0ewCdxP/b9wu89dab7R4GIUctv/jF3mm/xsJ3vW/ar0HITIczf0IIIaTLoPgTQgghXQbFnxBCCOkyKP6EEEJIl0HxJ4QQQroMij8hhBDSZVD8CSGEkC6D4k8IIYR0GRR/QgghpMug+BNCCCFdBsWfEEII6TIo/oQQQkiXQfEnhBBCugyKPyGEENJlUPwJIYSQLoPiTwghhHQZFH9CCCGky6D4E0IIIV1GW8X/qaeewiWXXILBwUEopbBp06bKMf/1X/+FVatWob+/H3PnzsVv/uZv4tVXX/X7R0ZGcMMNN2DhwoWYM2cOVq1ahddee+0I3gUhhBAys2ir+L/55ps47bTTcO+999bu/9///V+cffbZOPXUU/HEE0/gJz/5Cb785S9j1qxZ/pg1a9Zg48aN2LBhA7Zs2YIDBw7g4osvRlEUR+o2CCGEkBmFMsaYdg8CAJRS2LhxIy699FK/7corr0RPTw8eeOCB2s8MDQ3h3e9+Nx544AFcccUVAIDdu3dj8eLF+MEPfoCVK1dO6NrDw8Po7+/H5v98EnOOPfaw76UVixYNTOv5CelkfvGLvdN+jQ+9/33o7+/H0NAQ5s2bN+3XI2Qm0rExf601/u3f/g2//uu/jpUrV+L444/Hxz72sSQ0sG3bNoyOjmLFihV+2+DgIJYuXYqtW7c2PffIyAiGh4eTByGEENItdKz479u3DwcOHMBdd92F3/md38Gjjz6K3//938dll12GJ598EgCwd+9e9Pb24rjjjks+u2jRIuzd23yGsW7dOvT39/vH4sWLp/VeCCGEkE6i0e4BNENrDQD4vd/7PXzhC18AAJx++unYunUrvvWtb+Gcc85p+lljDJRSTffffvvtWLt2rX8/PDyMxYsX46PLT6ebkJBp5OQlg9N+DXryCBmfjhX/hQsXotFo4IMf/GCy/QMf+AC2bNkCABgYGMDBgwexf//+ZPa/b98+nHXWWU3P3dfXh76+Pv9e0h74R4OQmY/8f9wh6UyEdCQdK/69vb0488wz8T//8z/J9pdeegknnngiAGD58uXo6enB5s2bcfnllwMA9uzZgx07duDrX//6hK/1xhtvAADd/4QcRbzxxhvo7+9v9zAI6UjaKv4HDhzAyy+/7N/v3LkT27dvx/z587FkyRLccsstuOKKK/Dxj38c5513Hv793/8d3//+9/HEE08AAPr7+3HdddfhpptuwoIFCzB//nzcfPPNWLZsGS644IIJj2NwcBC7du2CMQZLlizBrl27ZrT7X8IYvI/OgPdxZDHG4I033sDg4PSHGAiZsZg28vjjjxsAlcfq1av9Mffdd5953/veZ2bNmmVOO+00s2nTpuQcb7/9trn++uvN/PnzzezZs83FF19sXn311UmNZ2hoyAAwQ0NDh3NbbYf30VnwPgghnUbH1Pl3AlLvP9Prg3kfnQXvgxDSaXRsqR8hhBBCpgeKf0RfXx++8pWvJJUAMxHeR2fB+yCEdBp0+xNCCCFdBmf+hBBCSJdB8SeEEEK6DIo/IYQQ0mVQ/CO+8Y1v4KSTTsKsWbOwfPly/OhHP2r3kJqybt06nHnmmZg7dy6OP/54XHrppZVuiMYY3HHHHRgcHMTs2bNx7rnn4oUXXmjTiCfGunXroJTCmjVr/LaZch+vv/46PvOZz2DBggU45phjcPrpp2Pbtm1+/0y4j7GxMfzlX/4lTjrpJMyePRsnn3wy/vqv/9qvtQHMjPsghIxD+1oMdBYbNmwwPT095tvf/rZ58cUXzY033mjmzJljXnnllXYPrZaVK1ea+++/3+zYscNs377dXHTRRWbJkiXmwIED/pi77rrLzJ071/zrv/6ref75580VV1xhTjjhBDM8PNzGkTfn6aefNu9973vNb/zGb5gbb7zRb58J9/GrX/3KnHjiieazn/2s+fGPf2x27txpHnvsMfPyyy/7Y2bCffzN3/yNWbBggXnkkUfMzp07zb/8y7+YY4891txzzz3+mJlwH4SQ1lD8HR/96EfNn/zJnyTbTj31VHPbbbe1aUSHxr59+wwA8+STTxpjjNFam4GBAXPXXXf5Y9555x3T399vvvWtb7VrmE154403zCmnnGI2b95szjnnHC/+M+U+br31VnP22Wc33T9T7uOiiy4yf/iHf5hsu+yyy8xnPvMZY8zMuQ9CSGvo9gdw8OBBbNu2DStWrEi2r1ixAlu3bm3TqA6NoaEhAMD8+fMB2HUS9u7dm9xTX18fzjnnnI68pz//8z/HRRddVFmTYabcx8MPP4wzzjgDn/rUp3D88cfjwx/+ML797W/7/TPlPs4++2z8x3/8B1566SUAwE9+8hNs2bIFv/u7vwtg5twHIaQ1Hbuq35Hkl7/8JYqiwKJFi5LtixYtwt69e9s0qoljjMHatWtx9tlnY+nSpQDgx113T6+88soRH2MrNmzYgOeeew7PPPNMZd9MuY//+7//wze/+U2sXbsWf/EXf4Gnn34an//859HX14drr712xtzHrbfeiqGhIZx66qnI8xxFUeCrX/0qPv3pTwOYOb8HIaQ1FP8IpVTy3hhT2daJXH/99fjpT3+KLVu2VPZ1+j3t2rULN954Ix599FHMmjWr6XGdfh9aa5xxxhm48847AQAf/vCH8cILL+Cb3/wmrr32Wn9cp9/HP//zP+PBBx/E9773PXzoQx/C9u3bsWbNGgwODmL16tX+uE6/D0JIa+j2B7Bw4ULkeV6Z5e/bt68yw+k0brjhBjz88MN4/PHH8Z73vMdvHxgYAICOv6dt27Zh3759WL58ORqNBhqNBp588kn8/d//PRqNhh9rp9/HCSecgA9+8IPJtg984AN49dVXAcyc3+OWW27BbbfdhiuvvBLLli3DNddcgy984QtYt24dgJlzH4SQ1lD8AfT29mL58uXYvHlzsn3z5s0466yz2jSq1hhjcP311+Ohhx7Cf/7nf+Kkk05K9p900kkYGBhI7ungwYN48sknO+qezj//fDz//PPYvn27f5xxxhm4+uqrsX37dpx88skz4j5+67d+q1Jq+dJLL+HEE08EMHN+j7feegtZlv5ZyPPcl/rNlPsghIxDG5MNOwop9bvvvvvMiy++aNasWWPmzJljfv7zn7d7aLX86Z/+qenv7zdPPPGE2bNnj3+89dZb/pi77rrL9Pf3m4ceesg8//zz5tOf/vSMKMmKs/2NmRn38fTTT5tGo2G++tWvmp/97Gfmu9/9rjnmmGPMgw8+6I+ZCfexevVq82u/9mu+1O+hhx4yCxcuNF/84hf9MTPhPgghraH4R/zDP/yDOfHEE01vb6/5yEc+4svmOhEAtY/777/fH6O1Nl/5ylfMwMCA6evrMx//+MfN888/375BT5Cy+M+U+/j+979vli5davr6+sypp55q/vEf/zHZPxPuY3h42Nx4441myZIlZtasWebkk082X/rSl8zIyIg/ZibcByGkNVzVjxBCCOkyGPMnhBBCugyKPyGEENJlUPwJIYSQLoPiTwghhHQZFH9CCCGky6D4E0IIIV0GxZ8QQgjpMij+hBBCSJdB8SeEEEK6DIo/IYQQ0mVQ/AkhhJAug+JPjmq+853vYMGCBRgZGUm2f+ITn8C1117bplERQkh7ofiTo5pPfepTKIoCDz/8sN/2y1/+Eo888gj+4A/+oI0jI4SQ9kHxJ0c1s2fPxlVXXYX777/fb/vud7+L97znPTj33HPbNzBCCGkjFH9y1PPHf/zHePTRR/H6668DAO6//3589rOfhVKqzSMjhJD2oIwxpt2DIGS6Wb58OT75yU9i5cqVOPPMM/Hzn/8cixcvbvewCCGkLTTaPQBCjgR/9Ed/hLvvvhuvv/46LrjgAgo/IaSr4cyfdAXDw8M44YQTMDY2hu985zu44oor2j0kQghpG4z5k65g3rx5+MQnPoFjjz0Wl156abuHQwghbYXiT7qGPXv24Oqrr0ZfX1+7h0IIIW2Fbn9y1POrX/0Kjz76KK6++mq8+OKLeP/739/uIRFCSFthwh856vnIRz6C/fv342tf+xqFnxBCwJk/IYQQ0nUw5k8IIYR0GRR/QgghpMug+BNCCCFdBsWfEEII6TIo/oQQQkiXQfEnhBBCugyKPyGEENJlUPwJIYSQLoPiTwghhHQZ/x+QIdqjvjUXNwAAAABJRU5ErkJggg==\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "grid.visualize(z=0, show=False)\n", "import matplotlib.pyplot as plt" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -557,7 +451,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.8.8" } }, "nbformat": 4, diff --git a/examples/01-basic-example.ipynb b/examples/01-basic-example.ipynb index 7e31966..26350ef 100644 --- a/examples/01-basic-example.ipynb +++ b/examples/01-basic-example.ipynb @@ -162,7 +162,7 @@ "metadata": {}, "outputs": [], "source": [ - "grid[12e-6, :, 0] = fdtd.LineDetector(name=\"detector\")\n" + "grid[12e-6, :, 0] = fdtd.LineDetector(name=\"detector\")" ] }, { @@ -254,9 +254,21 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" } }, "nbformat": 4, diff --git a/fdtd/lgrid.test.py b/fdtd/lgrid.test.py deleted file mode 100644 index cd4f642..0000000 --- a/fdtd/lgrid.test.py +++ /dev/null @@ -1,40 +0,0 @@ -import sys -sys.path.append('/home/arend/work/vscode/fdtd') -import sys -print(sys.path) - - -import numpy as np -import pytest -from fdtd.lgrid import LGrid - -def test_update_C(): - # create a 3x3x3 Yee grid - grid = LGrid((3, 3, 3), (1, 1, 1), 0.5) - - # set the courant number - grid.courant_number = 0.5 - - # set the C field to some initial values - grid.C = np.array([ - [[1, 2, 3], [4, 5, 6], [7, 8, 9]], - [[10, 11, 12], [13, 14, 15], [16, 17, 18]], - [[19, 20, 21], [22, 23, 24], [25, 26, 27]] - ]) - - # call the update_C function - grid.update_C() - - # check that the C field has been updated correctly - assert np.allclose(grid.C, np.array([ - [[-0.125, -0.25, -0.375], [-0.5, -0.625, -0.75], [-0.875, -1.0, -1.125]], - [[-1.25, -1.375, -1.5], [-1.625, -1.75, -1.875], [-2.0, -2.125, -2.25]], - [[-2.375, -2.5, -2.625], [-2.75, -2.875, -3.0], [-3.125, -3.25, -3.375]] - ])) - - -def main(): - test_update_C() - -if __name__ == '__main__': - main() From ca813363b51d64f0b792aa7d23560251264ddbe5 Mon Sep 17 00:00:00 2001 From: Arend Lammertink Date: Sun, 7 Jul 2024 07:39:40 +0200 Subject: [PATCH 10/16] some clean up --- fdtd/operators.py | 210 --------------------------------------- fdtd/project_outline.txt | 99 ------------------ 2 files changed, 309 deletions(-) delete mode 100644 fdtd/project_outline.txt diff --git a/fdtd/operators.py b/fdtd/operators.py index 07a4b18..c7db24c 100644 --- a/fdtd/operators.py +++ b/fdtd/operators.py @@ -177,213 +177,3 @@ def grad(p: Tensorlike) -> Tensorlike: grad[:, :, :-1, 2] = (p[:, :, 1:, 0] - p[:, :, :-1, 0]) return grad - - -def delta(F: Tensorlike) -> Tensorlike: - """ - Compute the vector Laplace operator of a vector field F located on the faces - of the grid cells.· F is a 4D array with dimensions (Nx+1, Ny+1, Nz+1, 3). - The last dimension represents the x, y, and z components of the vector - field. The first three dimensions represent the grid points. Returns a 4D - array with dimensions (Nx+1, Ny+1, Nz+1, 3). The first three dimensions - represent the grid points. The last dimension represents the x, y, and z - components of the vector Laplace operator. - - This implementation takes a vector field `F` as input and computes the - vector Laplace operator of the field using the `bd` backend. The output - vector field `delta_F` is located on the faces of the grid cells that are - perpendicular to the x, y, and z axes. Note that this implementation assumes - that the input vector field `F` is located on the faces of the grid cells - that are perpendicular to the x, y, and z axes, which is consistent with the - staggered grid used in fluid dynamics and electrodynamics. - - - The `delta` function computes the vector Laplace operator of a vector field - using the `div`, `grad`, `curl_edge_to_face`, and `curl_face_to_edge` functions. The - `div` and `grad` functions compute the divergence and gradient of a vector - field, respectively, using finite differences between the components of the - vector field at different points in the grid cell. The output fields are - located on the faces of the grid cells that are perpendicular to the x, y, - and z axes, which is consistent with the staggered Yee grid. - - The field types are: - - - `F`: A vector field located on the edges of the grid cells (point field). - - `phi`: A scalar field located on the grid points (cell-centered field). - - `E`: A vector field located on the edges of the grid cells (point field). - - `A`: A vector field located on the faces of the grid cells (surface - field). - - `H`: A vector field located on the edges of the grid cells (point field). - - `delta_F`: A vector field located on the edges of the grid cells (point - field). - - The `div` function computes the divergence of the vector field `F`, which - results in a scalar field `phi` located on the grid points. - - The `grad` function computes the gradient of the scalar field `phi`, which - results in a vector field `E` located on the edges of the grid cells. - - The `curl_edge_to_face` function computes the point curl of the vector field `F`, - which results in a vector field `A` located on the faces of the grid cells. - - The `curl_face_to_edge` function computes the surface curl of the vector field - `A`, which results in a vector field `H` located on the edges of the grid - cells. - - Finally, the `delta_F` vector field is computed as the difference between - the vector field `E` and the vector field `H`, both located on the edges of - the grid cells. - - l4m4re: This makes sense. In the above code, it is the vector field A that - is a surface field, while the vector field H is a point field. - - GitHub Copilot: Yes, that's correct. In the above code, the vector field `A` - is a surface field located on the faces of the grid cells, while the vector - field `H` is a point field located on the edges of the grid cells. - - The `curl_edge_to_face` function computes the point curl of the vector field `F`, - which results in a vector field `A` located on the faces of the grid cells. - The `curl_face_to_edge` function then computes the surface curl of the vector - field `A`, which results in a vector field `H` located on the edges of the - grid cells. - - The resulting vector field `H` represents the curl of the vector field `F` - at the edges of the grid cells that are perpendicular to the x, y, and z - axes, and is defined on the edges of the grid cells, which are integer grid - points. - - """ - phi = div(F) - E = grad(phi) - A = curl_edge_to_face(F) - H = curl_face_to_edge(A) - delta_F = E - H - return delta_F - - - -def delta_C(C: Tensorlike) -> Tensorlike: - """ - Compute the vector Laplace operator of a vector field F located on the faces - of the grid cells.· - - F is a 4D array with dimensions (Nx+1, Ny+1, Nz+1, 3). The last dimension - represents the x, y, and z components of the vector field. The first three - dimensions represent the grid points. Returns a 4D array with dimensions - (Nx+1, Ny+1, Nz+1, 3). The first three dimensions represent the grid points. - The last dimension represents the x, y, and z components of the vector - Laplace operator. - - This implementation takes a vector field `F` as input and computes the - vector Laplace operator of the field using the `bd` backend. The output - vector field `delta_F` is located on the faces of the grid cells that are - perpendicular to the x, y, and z axes. Note that this implementation assumes - that the input vector field `F` is located on the faces of the grid cells - that are perpendicular to the x, y, and z axes, which is consistent with the - staggered grid used in fluid dynamics and electrodynamics. - - Calculate the vector Laplacian of a vector field C using the left-handed - and right-handed curl operators. - - In our superfluid medium model, the vector field C has a unit of measurement - in Amperes per second, or, in basic unit: - - kilograms per second squared [kg/s^2], - - It is defined by the equation: - - C = - k * rho [v], - - with rho the mass density of the superfluid medium, which in this model is - taken equal to the electric permittivity of the medium, so for the vacuum - - rho = epsilon_0 = eta/k = 8.854187817620389e-12 [kg/m^3]. - - And, for the medium or vaccuum, k the quantum circulation constant, - diffusivity or kine-matic viscosity of the medium with a value of: - - k = c*c = 8.987551787368176e+16 [m^2/s], - - Thus, we indeed define k to have a value equal to c*c, but a unit of - measurement of meters squared per second [m^2/s] representing the quantum - circulation konstant, diffusivity or kinematic viscosity of the medium. - - Thus, C has a unit of measurement in [m^2/s] * [kg/m^3] * [m/s] = [kg/s^2], - the unit we equate to the Ampere in this model, hence the choice of C, for - "current" in "Amperes" or kilograms per second squared. - - in the following code, lower case is used for scalar fields, upper case for - vector fields. - - """ - p = div(C) # p is a scalar field representing pressure in [Pa/m^3] - L = grad(p) # a vector field representing linear force density in [N/m^3] - - A = curl_edge_to_face(C) # the vector potential, also in [Pa/m^3]. - R = curl_face_to_edge(A) # a vector field representing angular force density in [N/m^3] - - d_C = L - R # Delta C, the vector laplace operator of C in [N/m^3] - - return d_C # net force density in [N/m^3]. - - - -def delta_I(I: Tensorlike) -> Tensorlike: - """ - Compute the vector Laplace operator of a vector field F located on the faces of the grid - cells.· - F is a 4D array with dimensions (Nx+1, Ny+1, Nz+1, 3). - The last dimension represents the x, y, and z components of the vector field. - The first three dimensions represent the grid points. - Returns a 4D array with dimensions (Nx+1, Ny+1, Nz+1, 3). - The first three dimensions represent the grid points. - The last dimension represents the x, y, and z components of the vector Laplace operator. - - - In our superfluid medium model, the vector field I has a unit of measurement - in Watts per second, or, in basic unit: - kilograms per seconds to the third [kg/s^3] - - This repreents the time derivative of the field C, whe have defined earlier. - - It is defined by the equation: - - I = - k * rho [a], - - with rho the mass density of the superfluid medium, which in this model - is taken equal to the electric permittivity of the medium, so for - the vacuum - - rho = epsilon_0 = eta/k = 8.854187817620389e-12 [kg/m^3]. - - And, for the medium or vaccuum, k the quantum circulation constant, - diffusivity or kinematic viscosity of the medium with a value of: - - k = c*c = 8.987551787368176e+16 [m^2/s], - - Thus, I has a unit of measurement in [m^2/s] * [kg/m^3] * [m/s^2] = [kg/s^3], - the unit we equate to Intensity in this model, hence the choice of I, for - "intensity" in "Watts per meter squared" or kilograms per second to the third. - - For this field, we can also define derived fields, reesulting in a Yank - density field Y, with a unit of measurment in Newton per cubic meter - second [N/m^3-s], - the time derivative of Force density F. - - in the following code, lower case is used for scalar fields, upper case - for vector fields. - - """ - t = div(I) # t is a scalar field representing power density in [W/m^3] , presumed to represent temperarure - Y_l = grad(t) # a vector field representing linear yank density in [N/m^3-s] - - # the vector potential, also in [kg/m-s3], [N/m2-s], [J/m3-s], [Pa/s], - # [W/m3], [K] or [kg/m-s3] representing angular power or heat density, force - # flux, yank density flux, or angular temperature - W = curl_edge_to_face(I) - Y_a = curl_face_to_edge(W) # a vector field representing angular yank density in [N/m^3-s] - - d_I = Y_l - Y_a # Delta I, the vector laplace operator of I in [N/m^3-s] - - return d_I # net yank density or current flux in [N/m^3-s]. - - diff --git a/fdtd/project_outline.txt b/fdtd/project_outline.txt deleted file mode 100644 index 47376e2..0000000 --- a/fdtd/project_outline.txt +++ /dev/null @@ -1,99 +0,0 @@ - - -The goal of the project is to make a simumulator new superfluid equations, which have similarities with both Maxwell's equations as -well as Navier-Stokes. - -The idea is to refactor an existing FDTD project, which was written for Maxwell's equations, by replacing Maxwell's equations with the new ones. - -Since the grid is one of the most important parts of the simulator, I started refactoring the existing Grid class in a new class named LGrid. - - - - -### Maxwell equations - -An electromagnetic FDTD solver solves the time-dependent Maxwell Equations - -```python - curl(H) = ε*ε0*dE/dt - curl(E) = -µ*µ0*dH/dt -``` - -These two equations are called _Ampere's Law_ and _Faraday's Law_ respectively. - -In these equations, ε and µ are the relative permittivity and permeability -tensors respectively. ε0 and µ0 are the vacuum permittivity and permeability -and their square root can be absorbed into E and H respectively, such that `E := √ε0*E` and `H := √µ0*H`. - -Doing this, the Maxwell equations can be written as update equations: - -```python - E += c*dt*inv(ε)*curl(H) - H -= c*dt*inv(µ)*curl(E) -``` - -The electric and magnetic field can then be discretized on a grid with interlaced Yee-coordinates. - -### New superfluid equations - -The new equations build opon the vector Laplace operator del, the second spatial derivative in 3D, given by: - -```python - del(F) = grad(div(F)) + curl(curl(F)) -``` - -With this operator, we can define the time derivative of any given vector field -[F] within the superfluid by: - -d[F]/dt = -k del([F]), - -with k the quantum circulation constant of the superfluid, given by k = h/m, -with h planck's constant and m the mass of a molecule of the superfluid. - -This way, we can write an update equation for any given vector field [F] within -the superfluid by: - -```python - F += k*ndel(F)*dt, - - with ndel the negative of the vector Laplace operator. -``` - -We can apply this to a superfluid by working with the flow velocity field [v] -and define the update equation: - -```python - v += k*ndel(v)*dt -``` - -However, in order to have access to the intermediate results, we define -intermediate fields by writing out the definition of ndel, wherein we take the -negative of the gradient: - -```python - C = k*rho*v - p = div(C) - A = curl(C) - L = - grad(p) - R = curl(A) - F = R + L -``` - -with rho the mass density in [kg/m^3] and F force density in [N/m^3] - -the update eqiaton then becomes: - -```python - v -= inv(rho)*F -``` - -Please suggest an implementation of the Grid class. - - - - - - - - - From 3ee076fece32dabd6a31f43a23bf7d6006b2aa53 Mon Sep 17 00:00:00 2001 From: Arend Lammertink Date: Sun, 7 Jul 2024 07:41:18 +0200 Subject: [PATCH 11/16] reverted constants back to upstream --- fdtd/constants.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/fdtd/constants.py b/fdtd/constants.py index 0864097..3fd97ca 100644 --- a/fdtd/constants.py +++ b/fdtd/constants.py @@ -5,9 +5,6 @@ c: float = 299792458.0 """ speed of light """ -k: float = c*c -""" quantum circulation constant (8.987551787368176e+16)""" - X: int = 0 """ x-index (useful for indexing) """ @@ -25,5 +22,3 @@ eta0: float = mu0 * c """ free space impedance """ - - From 3c319815790bf845134896a4c8ec2d6648574d9e Mon Sep 17 00:00:00 2001 From: Arend Lammertink Date: Sun, 7 Jul 2024 07:43:41 +0200 Subject: [PATCH 12/16] More clean up --- fdtd/operators2.py | 85 ---------------------------------------------- 1 file changed, 85 deletions(-) delete mode 100644 fdtd/operators2.py diff --git a/fdtd/operators2.py b/fdtd/operators2.py deleted file mode 100644 index 4a99914..0000000 --- a/fdtd/operators2.py +++ /dev/null @@ -1,85 +0,0 @@ - -'''GitHub Copilot: Sure, here are some possible implementations of the gradient -And curl operators in a staggered Yee grid: - -**Gradient operator:** -''' - -def gradient(field, dx): - """ - Compute the gradient of a scalar field using finite differences. - - Parameters - ---------- - field : ndarray - Scalar field. - dx : float - Grid spacing. - - Returns - ------- - ndarray - Gradient of the scalar field. - """ - grad_x = np.gradient(field, dx, axis=0) - grad_y = np.gradient(field, dx, axis=1) - grad_z = np.gradient(field, dx, axis=2) - return np.stack((grad_x, grad_y, grad_z), axis=-1) - - -''' -**Point curl operator:** -''' - -def curl_point(field, dx): - """ - Compute the point curl of a vector field using finite differences. - - Parameters - ---------- - field : ndarray - Vector field. - dx : float - Grid spacing. - - Returns - ------- - ndarray - Point curl of the vector field. - """ - curl_x = np.gradient(field[..., 2], dx, axis=1) - np.gradient(field[..., 1], dx, axis=2) - curl_y = np.gradient(field[..., 0], dx, axis=2) - np.gradient(field[..., 2], dx, axis=0) - curl_z = np.gradient(field[..., 1], dx, axis=0) - np.gradient(field[..., 0], dx, axis=1) - return np.stack((curl_x, curl_y, curl_z), axis=-1) - -''' -**Surface curl operator:** -''' -def curl_surface(field, dx): - """ - Compute the surface curl of a vector field using finite differences. - - Parameters - ---------- - field : ndarray - Vector field. - dx : float - Grid spacing. - - Returns - ------- - ndarray - Surface curl of the vector field. - """ - curl_x = np.gradient(field[..., 2], dx, axis=1) - np.gradient(field[..., 1], dx, axis=2) - curl_y = np.gradient(field[..., 0], dx, axis=2) - np.gradient(field[..., 2], dx, axis=0) - curl_z = np.gradient(field[..., 1], dx, axis=0) - np.gradient(field[..., 0], dx, axis=1) - return np.stack((curl_x[..., None], curl_y[..., None], curl_z[..., None]), axis=-1) - -''' - -Note that these implementations use the `np.gradient` function from NumPy to -compute the finite differences. The `dx` parameter is the grid spacing, which is -assumed to be constant in a staggered Yee grid implementation. - -''' From 56da601a71dd0be5ab7a75077591dbbd7e09c58b Mon Sep 17 00:00:00 2001 From: Arend Lammertink Date: Sun, 7 Jul 2024 16:21:58 +0200 Subject: [PATCH 13/16] Added units for B and H. --- fdtd/aethergrid.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fdtd/aethergrid.py b/fdtd/aethergrid.py index 6737e5a..8a4b3e3 100644 --- a/fdtd/aethergrid.py +++ b/fdtd/aethergrid.py @@ -50,7 +50,8 @@ Another key point is that there are only three units of measurement in this model: the meter, the second and the kilogram. Within this model, electric charge has a unit in [kg/s], while the electric field has a unit of velocity in -[m/s]. And the magnetic field has a unit in +[m/s]. And the magnetic field B has a unit in [/m-s], while the magnetizing field +H has a unit in [kg/m^2-s^2]. """ From 1fc4279bf139c7eb98fac1dbc36740af64644dbe Mon Sep 17 00:00:00 2001 From: Arend Lammertink Date: Sun, 7 Jul 2024 16:24:30 +0200 Subject: [PATCH 14/16] Added note about confusing angular momentum and torque. Will fix later. --- fdtd/aethergrid.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/fdtd/aethergrid.py b/fdtd/aethergrid.py index 8a4b3e3..51e364b 100644 --- a/fdtd/aethergrid.py +++ b/fdtd/aethergrid.py @@ -105,8 +105,14 @@ rho_q0 = e/m * rho # vacuum charge density # 1.9241747011042014e+20 [kg/m^3-s] -tau = e/k # vacuum torque density - # 1.7826619216278975e-36 [N-m/m^3] +tau = e/k # vacuum angular momentum density + # 1.7826619216278975e-36 [kg-m/m^3] + +""" +Note: I made a mistake confusing angular momentum and torque. + +For now, I'll leave the symbol tau in, will fix this later. +""" inv_rho_q0 = 1/rho_q0 From f779555c788b69cc656188e097acea057b2a57fb Mon Sep 17 00:00:00 2001 From: Arend Lammertink Date: Sun, 7 Jul 2024 22:02:19 +0200 Subject: [PATCH 15/16] Resolved problem with symbol tau and interpretation. New symbol: sigma. --- fdtd/aethergrid.py | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/fdtd/aethergrid.py b/fdtd/aethergrid.py index 51e364b..4a42c6f 100644 --- a/fdtd/aethergrid.py +++ b/fdtd/aethergrid.py @@ -104,20 +104,13 @@ rho_q0 = e/m * rho # vacuum charge density # 1.9241747011042014e+20 [kg/m^3-s] - -tau = e/k # vacuum angular momentum density - # 1.7826619216278975e-36 [kg-m/m^3] - -""" -Note: I made a mistake confusing angular momentum and torque. - -For now, I'll leave the symbol tau in, will fix this later. -""" +sigma = e/k # Surface mass density + # 1.7826619216278975e-36 [kg/m^2], [kg-m/m^3] inv_rho_q0 = 1/rho_q0 -rho_tau = rho/tau # = eta/e -tau_rho = tau/rho # = e/eta +rho_sigma = rho/sigma # = eta/e +sigma_rho = sigma/rho # = e/eta ## FDTD Grid Class class AetherGrid: @@ -189,7 +182,7 @@ def __init__( his laboratory in Colorado Springs, according to Hugo Gernsback. """ # timestep of the simulation - self.time_step = self.courant_number * self.grid_spacing / ((pi/2)*const.c) + self.time_step = self.courant_number * self.grid_spacing / ((pi/2) * c) # define fields # velocity @@ -383,10 +376,10 @@ def update(self): # electric and magnetic fields self.E = inv_rho_q0 * self.L - self.H = rho_tau * self.R + self.H = rho_sigma * self.R # acceleration field - self.a = rho_q0 * self.E + tau_rho * self.H + self.a = rho_q0 * self.E + sigma_rho * self.H # second order potential fields self.dpdt = eta * div (self.a) @@ -398,10 +391,10 @@ def update(self): # second order (time derivative) of electric and magnetic fields self.dEdt = inv_rho_q0 * self.dLdt - self.dHdt = rho_tau * self.dRdt + self.dHdt = rho_sigma * self.dRdt # jerk field - self.j = rho_q0 * self.dEdt + tau_rho * self.dHdt + self.j = rho_q0 * self.dEdt + sigma_rho * self.dHdt # update velocity field self.v += self.courant_number * self.a From 7e5a750a0a5fdbd8133225102c1413cd0a6d4ee4 Mon Sep 17 00:00:00 2001 From: Arend Lammertink Date: Wed, 7 Aug 2024 12:36:12 +0200 Subject: [PATCH 16/16] Updated fields with a/o new definition for the vector potential. --- fdtd/aethergrid.py | 165 ++++++++++++++++++++++++++++----------------- 1 file changed, 102 insertions(+), 63 deletions(-) diff --git a/fdtd/aethergrid.py b/fdtd/aethergrid.py index 4a42c6f..0b9a3e9 100644 --- a/fdtd/aethergrid.py +++ b/fdtd/aethergrid.py @@ -105,12 +105,12 @@ rho_q0 = e/m * rho # vacuum charge density # 1.9241747011042014e+20 [kg/m^3-s] -sigma = e/k # Surface mass density - # 1.7826619216278975e-36 [kg/m^2], [kg-m/m^3] +eta_e = eta/e +e_eta = e/eta +inv_rho = 1/rho inv_rho_q0 = 1/rho_q0 -rho_sigma = rho/sigma # = eta/e -sigma_rho = sigma/rho # = e/eta + ## FDTD Grid Class class AetherGrid: @@ -188,13 +188,14 @@ def __init__( # velocity self.v = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - # first order scalar and vector potentials - self.p = bd.zeros((self.Nx, self.Ny, self.Nz)) - self.A = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + # first order scalar potential and torque density + self.p = bd.zeros((self.Nx, self.Ny, self.Nz)) + self.omega = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + self.tau = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - # first order force and torque density fields - self.L = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - self.R = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + # first order force density and vector potential + self.f = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + self.A = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) # first order electric and magnetic fields self.E = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) @@ -203,20 +204,21 @@ def __init__( # acceleration self.a = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - # second order scalar and vector potentials - self.dpdt = bd.zeros((self.Nx, self.Ny, self.Nz)) - self.dAdt = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + # second order scalar potential and torque density time derivative + self.dpdt = bd.zeros((self.Nx, self.Ny, self.Nz)) + self.alpha = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + self.dtau_dt = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - # second order force and torque density fields - self.dLdt = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - self.dRdt = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + # second order yank density and vector potential + self.y = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + self.dAdt = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) # second order electric and magnetic fields - self.dEdt = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) - self.dHdt = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + self.dEdt = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + self.dHdt = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) # jerk - self.j = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) + self.j = bd.zeros((self.Nx, self.Ny, self.Nz, 3)) # save the inverse of the relative permittiviy and the relative permeability # these tensors can be anisotropic! @@ -359,87 +361,124 @@ def step(self): def update(self): """ update the fields along the vector Laplace operator """ - # update boundaries: step 1 - #for boundary in self.boundaries: - # boundary.update_phi_H() - - #curl = curl_E(self.E) - #self.H -= self.courant_number * self.inverse_permeability * curl + self.updateBoundaries() - # potential fields + + # scalar potential and torque density fields self.p = eta * div (self.v) - self.A = e * curl_edge_to_face(self.v) + self.omega = curl_edge_to_face(self.v) + self.tau = eta * self.omega - # force and torque density fields - self.L = - grad(self.p) - self.R = curl_face_to_edge(self.A) + # linear force density field + self.f = - grad(self.p) # electric and magnetic fields - self.E = inv_rho_q0 * self.L - self.H = rho_sigma * self.R + self.E = inv_rho_q0 * self.f + self.H = eta_e * self.tau + + # update E and H + self.updateEH() + + # vector potential + self.A = curl_face_to_edge(e_eta * self.H) # acceleration field - self.a = rho_q0 * self.E + sigma_rho * self.H + self.a = rho_q0 * self.E + inv_rho * self.A - # second order potential fields - self.dpdt = eta * div (self.a) - self.dAdt = e * curl_edge_to_face(self.a) + + # second order scalar potential and time derivative + # of torque density fields + self.dpdt = eta * div (self.a) + self.alpha = curl_edge_to_face(self.a) + self.dtau_dt = eta * self.alpha - #second order yank and d/dt torque density fields - self.dLdt = - grad(self.dpdt) - self.dRdt = curl_face_to_edge(self.dAdt) + # linear yank density field + self.y = - grad(self.dpdt) - # second order (time derivative) of electric and magnetic fields - self.dEdt = inv_rho_q0 * self.dLdt - self.dHdt = rho_sigma * self.dRdt + # time derivative of electric and magnetic fields + self.dEdt = inv_rho_q0 * self.y + self.dHdt = eta_e * self.dtau_dt + + # update dEdt and dHdt ? + #self.updatedEHdt() + + # vector potential + self.dAdt = curl_face_to_edge(e_eta * self.dHdt) + + # acceleration field + self.j = rho_q0 * self.dEdtE + inv_rho * self.dAdt - # jerk field - self.j = rho_q0 * self.dEdt + sigma_rho * self.dHdt # update velocity field self.v += self.courant_number * self.a self.v += self.courant_number**2 * self.j - - + + + def updateBoundaries(self): + + # update boundaries: step 1 + #for boundary in self.boundaries: + # boundary.update_phi_E() + # boundary.update_phi_H() + + return + + + def updateEH(self): # update objects #for obj in self.objects: + # obj.update_E(curl) # obj.update_H(curl) # update boundaries: step 2 #for boundary in self.boundaries: + # boundary.update_E() # boundary.update_H() # add sources to grid: #for src in self.sources: - # src.update_H() # src.update_E() + # src.update_H() # detect electric field #for det in self.detectors: + # det.detect_E() # det.detect_H() + return + def reset(self): """reset the grid by setting all fields to zero""" - self.v *= 0.0 - self.p *= 0.0 - self.A *= 0.0 - self.L *= 0.0 - self.R *= 0.0 - self.H *= 0.0 - self.E *= 0.0 - - self.a *= 0.0 - self.dpdt *= 0.0 - self.dAdt *= 0.0 - self.dLdt *= 0.0 - self.dRdt *= 0.0 - self.dEdt *= 0.0 - self.dHdt *= 0.0 + self.v *= 0.0 + + self.p *= 0.0 + self.omega *= 0.0 + self.tau *= 0.0 + + self.f *= 0.0 + + self.E *= 0.0 + self.H *= 0.0 + + self.A *= 0.0 - self.j *= 0.0 + self.a *= 0.0 + + self.dpdt *= 0.0 + self.alpha *= 0.0 + self.dtau_dt *= 0.0 + + self.y *= 0.0 + + self.dEdt *= 0.0 + self.dHdt *= 0.0 + + self.dAdt *= 0.0 + + self.j *= 0.0 self.time_steps_passed *= 0