diff --git a/notebooks/50_gravity-inversion-with-model-slice.ipynb b/notebooks/50_gravity-inversion-with-model-slice.ipynb new file mode 100644 index 0000000..3f61b36 --- /dev/null +++ b/notebooks/50_gravity-inversion-with-model-slice.ipynb @@ -0,0 +1,1089 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c4b21011-75e3-4f3a-a357-e28fcaaabdf4", + "metadata": {}, + "source": [ + "# Gravity inversion with a model slice" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "b2d27e8b-5b33-4361-990b-8b6e724f5ac6", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:28.888718Z", + "iopub.status.busy": "2026-04-15T00:20:28.888537Z", + "iopub.status.idle": "2026-04-15T00:20:30.607377Z", + "shell.execute_reply": "2026-04-15T00:20:30.606281Z", + "shell.execute_reply.started": "2026-04-15T00:20:28.888699Z" + } + }, + "outputs": [], + "source": [ + "import discretize\n", + "import harmonica as hm\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import verde as vd\n", + "from simpeg.maps import IdentityMap\n", + "from simpeg.potential_fields.gravity import (\n", + " Point,\n", + " Simulation3DIntegral,\n", + " SourceField,\n", + " Survey,\n", + ")\n", + "from simpeg.utils import depth_weighting, model_builder\n", + "\n", + "import inversion_ideas as ii" + ] + }, + { + "cell_type": "markdown", + "id": "a7a0aea4-50f4-4eea-9b6e-e907c520534e", + "metadata": {}, + "source": [ + "## Define synthetic data with two prisms" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "dfce90d3-de10-43d3-a668-696993b0f73e", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:30.608178Z", + "iopub.status.busy": "2026-04-15T00:20:30.607862Z", + "iopub.status.idle": "2026-04-15T00:20:30.612379Z", + "shell.execute_reply": "2026-04-15T00:20:30.611261Z", + "shell.execute_reply.started": "2026-04-15T00:20:30.608160Z" + } + }, + "outputs": [], + "source": [ + "prisms = [\n", + " (-60, -40, -10, 10, -70, -50),\n", + " (40, 60, -10, 10, -70, -50),\n", + "]\n", + "densities = np.array([-200, 200]) # SI units\n", + "\n", + "region = (-100, 100, -100, 100)\n", + "shape = (31, 31)\n", + "height = 0\n", + "coordinates = vd.grid_coordinates(region, shape=shape, extra_coords=height)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "2403d600-451e-424e-8de9-3f824f1e2647", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:30.613390Z", + "iopub.status.busy": "2026-04-15T00:20:30.613133Z", + "iopub.status.idle": "2026-04-15T00:20:31.726634Z", + "shell.execute_reply": "2026-04-15T00:20:31.725722Z", + "shell.execute_reply.started": "2026-04-15T00:20:30.613370Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "stderr=np.float64(2.5745854651805945e-05)\n" + ] + } + ], + "source": [ + "gz = hm.prism_gravity(coordinates, prisms, densities, field=\"g_z\")\n", + "gz *= -1 # Invert sign to work with upward component\n", + "\n", + "# Add noise\n", + "stderr = vd.maxabs(gz) * 0.01\n", + "gz += np.random.default_rng(seed=51).normal(scale=stderr, size=gz.shape)\n", + "\n", + "print(f\"{stderr=}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "9612913a-b699-4ad4-8fe7-cbf83376f5d8", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:31.727573Z", + "iopub.status.busy": "2026-04-15T00:20:31.727355Z", + "iopub.status.idle": "2026-04-15T00:20:31.863772Z", + "shell.execute_reply": "2026-04-15T00:20:31.862693Z", + "shell.execute_reply.started": "2026-04-15T00:20:31.727557Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAh4AAAGdCAYAAABdD3qhAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAQxVJREFUeJzt3QmUVNW18PFdQ09ggwzKEAbRaNRgHNAwJAoEAY1DFJ9CXIuHL0piEBGBZQJoBBIlOKBfRCQmPMU4wLc+Q2KexgCJoiwGgeCAMT6NoKgggszQU9X91j52Vap6KGp39a3urvr/XMembp26fev27Vu7z7QDnud5AgAAkAXBbHwTAAAAAg8AAJBVtHgAAICsIfAAAABZQ+ABAACyhsADAABkDYEHAADIGgIPAACQNWHJA9FoVD799FMpLS2VQCDQ1IcDADDStS4PHDggXbt2lWDQn7+Zy8rKpKKiolH2VVhYKMXFxY2yr1yTF4GHBh3du3dv6sMAAGRo27Zt0q1bN1+Cjl49j5EdOyONsr/OnTvLli1bCD6yHXi88sorcu+998rGjRtl+/btsnTpUrniiiuSItiZM2fKo48+Knv27JG+ffvKww8/LF//+tfjdcrLy2XKlCnyzDPPyJEjR2TIkCEyf/5804WnLR3qK3OmSdCnCNQL+rvyfCBia6nxjH8QBAyH74U8X49drPWt5966/2jAt3PZkPrm44ka9+9zEoVAVcC3Y/dCxmOxfsY0zmdS/QL+1re+X+t9xHztGPYfLS+TrffMit/PG5u2dGjQsWVjT2lTmlmLyv4DUenV50O3T1o9shx4HDp0SM4880z5r//6L7nqqqtqPX/PPffI3Llz5fHHH5dTTjlFfvGLX8jQoUPl3XffjV9cEydOlD/96U+yePFi6dChg0yePFkuvfRSF8yEQundZWLdKxp0BEsIPOo8RwQeTRd4WAMDAo8mCzzMgYoVgcfRT5HP3eUadGQaeKAJA4+LL77Ylbpoa8eDDz4o06dPlxEjRrhtixYtkk6dOsnTTz8tP/rRj2Tfvn2ycOFC+d3vficXXnihq/Pkk0+6bpMVK1bI8OHD/Tx8AECeiXhRiXiZ7wP1a7KwTvu+duzYIcOGDYtvKyoqkoEDB8rq1avdY23VqKysTKqjA4t69+4drwMAQGOJitcoBc1wcKkGHUpbOBLp4w8//DBeR0cGt2vXrlad2OvrouNCtMTs37+/kY8eAJCLou6/zPeB+jV5R1bN/jrtgjlaH97R6syePVvatm0bL8xoAQAgzwMPnWqkarZc7Ny5M94KonV0VLDOeKmvTl2mTp3qxofEik6/AgDgaCKe1ygFzTDw6NWrlwssli9fHt+mQcbKlStlwIAB7nGfPn2koKAgqY5Oy928eXO8Tl10rEibNm2SCgAAR8MYjxY+xuPgwYPy/vvvJw0off3116V9+/bSo0cPN1X27rvvlpNPPtkV/XerVq3k2muvdfW1m+T66693U2h1Kq2+Ttf0OOOMM+KzXAAAQMvha+CxYcMGGTx4cPzxpEmT3NcxY8a4tTtuu+02tyjYuHHj4guILVu2LGmBmAceeEDC4bBcc8018QXE9LXpruEBAIClxSOS4awUZrWkFvB0pGaO01ktbpDp/5nFAmL1YAGxFFhArFG15JVLWUDsaD8A/zr7I2Vl8sHPp7lxe350n8c+J/71z85SmuECYgcOROWkU3f4dqwtXV7kaonxAl+WdASMy4JbZ09Z9+93dGhZGtm8cGCB7eR4QeNKodYl0K3H7/OS7OlekzEB6/EYAyc/A4kvX+BTXf1RVUqLXk7eyhpoWZkDP3JwIg15FXgAAJBKY8xKYVZLagQeAABU00aezBcQQ7NeQAwAAOQPWjwAAKgWaYRZLZm+PtcReAAAUE0z02aenZbTmQqBBwAA1Rjj4T/GeAAAgKyhxQMAgGpRCUjEvNhP7X2gfgQeAABUi3pflkxk+vpcR1cLAADIGlo8AACoFmmErpZMX5/r8irwCISjrqRX2bhvn9uOAgXWtjtrfhHDG7DmsfH8PZee8RuYbwnGXCfWfBXBaPM6Hmt+DkuCQVe/yr9jMbMeu+dvEjpzkjtjbhrrxW++dsS/4/es77WBCDz8R1cLAADImrxq8QAAIJWoF3AlE5m+PtcReAAAUI2uFv/R1QIAALKGFg8AAKpFJOhKJoxjivMOgQcAANW8RhjjoftA/Qg8AACoxhgP/zHGAwAAZA0tHgAAVIt4QVcyESFXS0oEHgAAJGSWjWbYGRC1LombZ/Ir8Ah6X5ZmIGA8Dmt9z7isdqCgyrd9W+tb16UOBKxrlNuqS7lx/2F/f1bWJeuDFbb9B6uazxLx1iXH/b7f+72Ee9C4LHiGf5gfnXX/1jGVlvPZPG7daASM8QAAoMbg0kyL1fz586VXr15SXFwsffr0kVdffTVl/ZUrV7p6Wv/EE0+UBQsW1Krz7LPPyumnny5FRUXu69KlS5Oenz17tpx33nlSWloqxx9/vFxxxRXy7rvv+n4tEHgAAFBjjEemxWLJkiUyceJEmT59umzatEnOP/98ufjii+Wjjz6qs/6WLVvku9/9rqun9adNmyYTJkxwgUbMmjVrZOTIkTJ69Gh544033NdrrrlG1q1blxS83HTTTbJ27VpZvny5VFVVybBhw+TQoUO+Xg8Bz/NyvgFr//790rZtW+m+4E4JlhRLc9DsuloMzfe+d7VYr0jz/o31y43xubVnydgVYmXuaqn0t6slVJF+3XzrarFmv/W7q8VrRl0tkfIyeX/ONNm3b5+0adNG/PqcWPrGydK61JgmuIZDByJy5ZnvpX2sffv2lXPOOUceeeSR+LbTTjvNtUBoq0RNP/nJT+S5556Td955J77txhtvdAGGBhxKgw59T3/+85/jdS666CJp166dPPPMM3Uex+eff+5aPjQgueCCC8QvtHgAAJA0uDTzovSDP7GUl5fXOs8VFRWyceNG19KQSB+vXr26zp+LBhc16w8fPlw2bNgglZWVKevUt0+lgZJq3769r9cDgQcAANV0RkskwxKbFdO9e3fXihIrdbVe7Nq1SyKRiHTq1Clpuz7esWNHnT8X3V5Xfe0q0f2lqlPfPrXzY9KkSfLtb39bevfu7ev1kF+zWgAAyJJt27YldbXoIM90Z+dpIJBqxl5d9Wtut+xz/Pjx8uabb8qqVavEbwQeAAA06gJiXwYBGnQcbYxHx44dJRQK1WqJ2LlzZ60Wi5jOnTvXWT8cDkuHDh1S1qlrnzfffLMbM/LKK69It27dxG90tQAAUE27SRqjpKuwsNBNi9VZJYn08YABA+p8Tf/+/WvVX7ZsmZx77rlSUFCQsk7iPrUFRFs6fv/738vf/vY3N503G2jxAACgWsQLuJIJ6+snTZrkprtq4KABw6OPPuqm0upMFTV16lT55JNP5IknnnCPdfu8efPc68aOHesGki5cuDBptsott9ziZqbMmTNHvve978kf//hHWbFiRVJXik6lffrpp91zupZHrIVEx6OUlJT4dk0QeAAA0IRGjhwpu3fvllmzZsn27dvd4M4XXnhBevbs6Z7XbYlremjLhD5/6623ysMPPyxdu3aVX/3qV3LVVVfF62jLxuLFi+X222+XO+64Q0466SS3XohO3Y2JTd8dNGhQ0vE89thjct111+XuOh4nnHCCfPjhh7W2jxs3zp1QffOLFi1Kek5PnC54ki7W8Tg61vFIgXU8UmIdj8bDOh5Nv47H45vOlFYZruNx+EBErjv7Dd+OtaVr8haP9evXu6lEMZs3b5ahQ4fK1VdfnbToiUZgiX1iDaGLcKW9EJc1/4Qxf4ZVwJLgQvspI7bhO8FQ+vv3gv4u2BWNWE++cXG1KuPQpqKor4GKZ829Utm8hmZZ84tYFvkKVhl3bV3rrcrnBcQ8fwMPK8/4eWoNKs37b16XshP1gq5kto+cX5ezZQcexx13XNLjX/7yl65JaODAgUlTkHSELgAAaNmaVbypK7g9+eST8oMf/CBprvHLL7/slnE95ZRT3EAanRKUiq4OV3PFOAAAjibTxcNiBfVrVmfnD3/4g+zduzdpUIsmynnqqafcVJ/777/fdc185zvfqXPp2RhdHS5xtThdPQ4AgKOJJsxsaWjxOaVPi9fkXS2JdDqQBho6QjdxtG+MjvTV6UY60vf555+XESNG1LkfnXqk04xitMWD4AMAgKbXbAIPndmic4x1IZNUunTp4gKP9957r946OiYk1dK0AADUxboAWH37QAsIPHTWio7juOSSS1LW07nOuv69BiAAADS/JdMJPFJpFmcnGo26wGPMmDFurfmYgwcPypQpU9yqbFu3bnWDTC+77DK3tv2VV17ZpMcMAABaaIuHdrHoqmw6myWRJs5566233DKxOuhUWzkGDx7sVl/T5V0BAGhMUdHBoZktmZ7p63Ndswg8hg0bFk/pm0jXiv/LX/7SJMcEAMg/dLXkSeABAEBz0BjrcLCORwsY4wEAAPJDXrV4BALp52oJF/07f0w6vKi/fXohQy4VFTXm/wgZcsF4xgQO1qwF1nMZqbIliIgal/cx53YxnvuAdf/mEyq+suYXMeVfMa7EFKp/XcFGYc7VYr0tRP0991HrtWDMvRKI+JfbxbPmBGqgqC4AZk1SU8c+UL+8CjwAADjaGhyZdpWwjkdqdLUAAICsocUDAIBqUS/oSiYyfX2uI/AAAKBaRAKuZCLT1+c6wjIAAJA1tHgAAFCNrhb/EXgAAFBNZwRn3tWCVOhqAQAAWUOLBwAA1ehq8R+BBwAA1UgS5z8CDwAAqnkSyDitve4D9curwCMYjrrih3DYNpwomGbOmJgC4/4tuVdUJJr+cJ+gMUFEVcQ2lMiaC8acniNgO56INe+C3zkljPk2AsbcN4Eqf/NzBA3nJ2j84Zpv99Y8M5X+5lKx8n2dKuO1ELV+ogR8zJODZiuvAg8AAFKhq8V/BB4AAFQjO63/mE4LAACyhhYPAACqRSToSiYyfX2uI/AAAKAaXS3+IywDAABZQ4sHAADVohJ0JROZvj7XEXgAAJCwbo957Z4aMn19riMsAwAAWUOLBwAA1Rhc6j8CDwAAqnle0GWozXQfqF9eBR6BkCfBUHrJE4oKbEkKQiFbIoGCoC3BRaExV4uVNbeLX3lgVEVVyFQ/aDz2qoht/2U+9+965lwqxv0b8wIFIwFf85eYcrUYL/uQMbdLqMLnZCpG1qEB0QLjtWP8NTd/fhqP3zOcfs+YN6ahIhJwJdN9oH6EZQAAIGvyqsUDAIBUot6X4zwy3QfqR+ABAEC1aCOM8cj09bmOswMAALKGFg8AAKpFJeBKJjJ9fa4j8AAAoBorl/qPrhYAAJA/gceMGTMkEAgklc6dO8ef9zzP1enatauUlJTIoEGD5O23327SYwYA5Pbg0kwL6tcszs7Xv/512b59e7y89dZb8efuuecemTt3rsybN0/Wr1/vgpKhQ4fKgQMHmvSYAQA5OsbDy7AwxqP5Bx7hcNgFFLFy3HHHxVs7HnzwQZk+fbqMGDFCevfuLYsWLZLDhw/L008/3dSHDQAAWmLg8d5777mulF69esmoUaPkgw8+cNu3bNkiO3bskGHDhsXrFhUVycCBA2X16tX17q+8vFz279+fVAAAOBqvelZLJkX3gWY8q6Vv377yxBNPyCmnnCKfffaZ/OIXv5ABAwa4cRwadKhOnTolvUYff/jhh/Xuc/bs2TJz5sxa2wvCUQmlmfOk0JirpThsq18Y8nn/xiQXma7U15j7riq0xcMHK4pM9Q9VFJrqV4ZsuV0iAeOyhdb6RqEKYz4Pv/OjlPmXB8aae8Va35rrxMyaq8V4fqK2S1miYX9zwViOxzO+14YiO20eBB4XX3xx/N9nnHGG9O/fX0466STXpdKvXz+3XQecJtIumJrbEk2dOlUmTZoUf6wtHt27d/fl+AEAuYOVS/OkqyVR69atXQCi3S+x2S2xlo+YnTt31moFSaTdMW3atEkqAACg6TW7wEPHZ7zzzjvSpUsXN+ZDg4/ly5fHn6+oqJCVK1e67hgAABpTxjNaqguacVfLlClT5LLLLpMePXq4lgwd46FdI2PGjHHdKRMnTpS7775bTj75ZFf0361atZJrr722qQ8dAJBjWDI9DwKPjz/+WL7//e/Lrl273DRaHdexdu1a6dmzp3v+tttukyNHjsi4ceNkz549bjDqsmXLpLS0tKkPHQAAtLTAY/HixSmf11YPXblUCwAAfmJWSx4EHgAANBcEHnk4uBQAAOQuWjwAAKhGi4f/CDwAAKhG4OE/uloAAEDW5FWLR3FhpYTSzANSWmhLQBEw5ttoY9x/q3CFqX5xyJbYoMRQ37o4ji5BbHEoYsulYmX9WUUituOPVNkSYlRV+Bz/G/NnWHO1WPNzWHK7FByx/azCh61vVnzN7SJRW/1ogfF3q8B4bRp/tfxOOxSw5IKx3QIbTN9Cpmnt/c2+1PLR4gEAQBOvXDp//ny3WndxcbH06dNHXn311ZT1dQVvraf1TzzxRFmwYEGtOs8++6ycfvrpLo2Ifl26dGnS86+88opbwFOzw+vSFX/4wx+ych0QeAAA0ISBx5IlS9wq3dOnT5dNmzbJ+eef7xKofvTRR3XW37Jli3z3u9919bT+tGnTZMKECS7QiFmzZo2MHDlSRo8eLW+88Yb7es0118i6devidQ4dOiRnnnmmzJs3L6s//4CnqV5znC7B3rZtWznz/02WUKv0UqjT1dJyulp2lbW27b/Stv99h0tM9Q8fTu8ai6k6ZOvxDB2w1Q8ftP28wkfE1/oFBwx16Wpp0q4Wc9ePsfM+auhqiVSUyZuPTZN9+/b5kvgz9jkx6H9+LOHWtt/hmqoOlcvLlz6S9rH27dtXzjnnHHnkkUfi20477TS54oorZPbs2bXq/+QnP5HnnnvO5TWLufHGG12AoQGH0qBD39Of//zneJ2LLrpI2rVrJ88880ytfWqLh7aI6Pf0Gy0eAAA0UYtHRUWFbNy4UYYNG5a0XR+vXr26ztdocFGz/vDhw2XDhg1SWVmZsk59+8ymvBpcCgBAtqbTaotDIh1rUVSU3JqiecoikYh06tQpabs+3rFjR5371+111a+qqnL70+zu9dWpb5/ZRIsHAAA+6N69u+u+iZW6uk0SuzoS6SiImtuOVr/mdus+s4UWDwAAqnlewJVMxF6/bdu2pDEeNVs7VMeOHSUUCtVqidi5c2etFouYzp0711k/HA5Lhw4dUtapb5/ZRIsHAADVdA2PxihKg47EUlfgUVhY6KbFLl++PGm7Ph4wYECdP5f+/fvXqr9s2TI599xzpaCgIGWd+vaZTbR4AADQhCZNmuSmu2rgoAHDo48+6qbS6kwVNXXqVPnkk0/kiSeecI91u06B1deNHTvWDSRduHBh0myVW265RS644AKZM2eOfO9735M//vGPsmLFClm1alW8zsGDB+X9999Pmqb7+uuvS/v27aVHjx6+vV8CDwAAmjBXy8iRI2X37t0ya9Ys2b59u/Tu3VteeOEF6dmzp3tetyWu6aELjenzt956qzz88MNuAbBf/epXctVVV8XraMvG4sWL5fbbb5c77rhDTjrpJLdeiE7djdFZMIMHD44/1kBGjRkzRh5//HHxC+t41IN1POrHOh6psY5Haqzj0XKWTM/HdTy+ufSWRlnH47Ur/49vx9rS5VWLR1GoSsLh9PJoFIWrTPtuFbblRjm28LCpfgdj/aKA7XiKgum/3+Kgbd+Ho7a7XVn0yz5KP/LMqG2HjjXVryi0/ZqUlduO35wWIuBv/oygMSdG2HZpSuHB9POphMusuVpsiWZCZcbEND6vtxgtsOX58cK23DRVrYx5hIpN1cUzzpgIGrKaeFU5v9Zl3sirwAMAgObW1ZJvCDwAAPBhOi3qRuABAEBC0JBpiwWBR2qs4wEAALKGFg8AAKp5jTCGmGGwqRF4AABQTVcd1f8yEVu5FHWjqwUAAGQNLR4AAFRjVov/CDwAAKimM1oCrOPhK7paAABA1tDiAQBANZ3RkvGsFqa1pJRXgUdxuFLC4fQaeQqDthwOx4TLfc290r1ot6l+abDMVL91MP3jjxgbyio9W36Iz6tKTfWti/1UltiOp7zK9mtyIGxLMFUZ9PkuZc3VYktTJMFK2zcIlXu+5V4p/MJ23QcqjblaIsb6QdvvimfM1RItMt7CzZea7XiskzkqSwwvyNKHOWM8/EdXCwAAyJq8avEAACAVWjz8R+ABAEA1ZrX4j8ADAIBqDC7NgzEes2fPlvPOO09KS0vl+OOPlyuuuELefffdpDrXXXedBAKBpNKvX78mO2YAANBCA4+VK1fKTTfdJGvXrpXly5dLVVWVDBs2TA4dOpRU76KLLpLt27fHywsvvNBkxwwAyOUWj0CGpanfRfPW5F0tL774YtLjxx57zLV8bNy4US644IL49qKiIuncuXMTHCEAIF8wuDQPWjxq2rdvn/vavn37pO0vv/yyC0hOOeUUGTt2rOzcubPefZSXl8v+/fuTCgAAaHrNKvDwPE8mTZok3/72t6V3797x7RdffLE89dRT8re//U3uv/9+Wb9+vXznO99xAUZ940batm0bL927d8/iuwAAtFReIxU0466WROPHj5c333xTVq1albR95MiR8X9rQHLuuedKz5495fnnn5cRI0bU2s/UqVNdABOjLR4EHwCAo6GrJY8Cj5tvvlmee+45eeWVV6Rbt24p63bp0sUFHu+9916dz+t4EC0AAKB5CTeH7hUNOpYuXerGcfTq1euor9m9e7ds27bNBSB+aVNoy/lwbMERU/124eRZO0dzXPiAqX6HkK3+scH0j784YEvmsT9qCwJLDcfy5fFUmuqXR22X/a6C1qb6BWFbPo+ycFT8FLCmF7GdTgmXGXO1lKX/fgv22XIgBQ/Z6sth27VmFrTlOgmEjblRSotth1Ng610PGTvjvYDt+CMF6edq8Yw5hBqsMfpK6Gtp3mM8dCrtk08+KU8//bRby2PHjh2uHDny5Q3h4MGDMmXKFFmzZo1s3brVBSeXXXaZdOzYUa688sqmPnwAQC7JeCptwO0DzbjF45FHHnFfBw0aVGtarS4cFgqF5K233pInnnhC9u7d61o5Bg8eLEuWLHGBCgAAjYWVS/OkqyWVkpIS+ctf/pK14wEAADkceAAA0Fwwq8V/BB4AAMQ0xhgNxng078GlAAAgf9DiAQBANQaX+o/AAwCAGNbx8B1dLQAAIGto8QAAoBqzWvxH4AEAQCKWPPdVXgUepYUVUlCYZt2wLVdL67AtR0S3wi9s+w/a9t81vN9Uv0PQkD8jYJtqVhq15aXpEPI3f8YXVceY6u8qstX/oqiVqf5+z1Y/ELDdFYNVtp9X0OfcLqHy9K+14BHjzssqTNW9g7Zr0ztkuzYDhQW2+m1sqzEHjclUwsbfXa/AlgsmELVem+nXj0aIBnJFXgUeAACkQleL/wg8AACIYVaL7wg8AACI0+6oTLPLkp02FabTAgCArKHFAwCAGLpafEfgAQBADIGH7+hqAQAAWUOLBwAAiSntM01rn+nrcxyBBwAA1chO6z+6WgAAQNbQ4gEAQAyDS32XV4FHYTAiBdZEFGlqGz5sql8csOWgOC5ky71SGrC9z2MC6eeUOCZoy9/QKmDLM/NZxJZvo9iYLKQ0ZMvDEzRmjIpEg77mXmlu/ceWfBsqfDj9n1egzPaz9fbbfk+8Mtu1GS03Xjte+nlpnIPGa8eYqyVQaLvlBytsxx8sDkmLxxgP39HVAgAAsiavWjwAAEhFGyCtjZB17QP1I/AAACCGMR6+I/AAACCGMR6+Y4wHAADIGlo8AACIoavFdwQeAADEEHj4jq4WAACQNbR4AAAQQ4uH7wg8AACIYVaL7+hqAQAAWZNXLR5RL+BKOgoCxhwLRgWBKlP91sbcLsUBWz6PVsFC8UuRIQ/Ml2y5WloHbPWDxp9t2Kf8PnE+p17xjOkzjJemOVeLZ7k2I8ZzH7H9bKNHbLlXrLyo7dwErO+3ITnf/RSx7d+SZsmYkqnBWLnUfy2mxWP+/PnSq1cvKS4ulj59+sirr77a1IcEAMjVMR6ZFrTswGPJkiUyceJEmT59umzatEnOP/98ufjii+Wjjz5q6kMDACDrf1yvXLnS1dP6J554oixYsKBWnWeffVZOP/10KSoqcl+XLl2a8ffNm8Bj7ty5cv3118sNN9wgp512mjz44IPSvXt3eeSRR5r60AAAyOof11u2bJHvfve7rp7WnzZtmkyYMMEFGjFr1qyRkSNHyujRo+WNN95wX6+55hpZt25dg79v3gQeFRUVsnHjRhk2bFjSdn28evXqOl9TXl4u+/fvTyoAABxNIGGcR4OLz39cL1iwQHr06OHqaX193Q9+8AO577774nX0uaFDh8rUqVPl1FNPdV+HDBnitjf0++ZN4LFr1y6JRCLSqVOnpO36eMeOHXW+Zvbs2dK2bdt40RMJAEDa02kzLSK1/gDWP4ob449rbc2oWX/48OGyYcMGqaysTFknts+GfN+8CTxiAjVGwnueV2tbjEZ2+/bti5dt27Zl6SgBAPiS/tGb+Eew/lHcGH9c6/a66ldVVbn9paoT22dDvm/eTKft2LGjhEKhWidi586dtU5YjA6k0QIAQFOtXKp/9LZp0ybps6kx/riur37N7ens0/p986LFo7Cw0I20Xb58edJ2fTxgwIAmOy4AQA5qxOm0GnQklroCj44N+OO6c+fOddYPh8PSoUOHlHVi+2zI982bwENNmjRJfvvb38p///d/yzvvvCO33nqrG3V74403NvWhAQCQ1T+u+/fvX6v+smXL5Nxzz5WCgoKUdWL7bMo/6pt9V4vSKUG7d++WWbNmyfbt26V3797ywgsvSM+ePZv60AAAOaQpVi6dNGmSm+6qgYMGDI8++mjSH9c6bvGTTz6RJ554wj3W7fPmzXOvGzt2rBtIunDhQnnmmWfi+7zlllvkggsukDlz5sj3vvc9+eMf/ygrVqyQVatWpf198zrwUOPGjXMlExXRkHjR9NaPPhK1LvNtcyhqG4Oy31i/U+iwqX7EMyw1bez+q/Rsy0AXGPdfYVwTvNz4sy2L2OoHjXedQNB4lzJWt67+7xnbQaPWH5ileoHxFmXsmw6WFJvqe3XMSmhU1r5145LsEgy26OX/czU77cij/HGt2xLX1tAFv/R5bf1/+OGHpWvXrvKrX/1KrrrqqngdbbVYvHix3H777XLHHXfISSed5Nbt6Nu3b9rfV/I98AAAIFeNS/HH9eOPP15r28CBA+Xvf/97yn3+x3/8hysN/b5+IfAAAKAJWzzyDYEHAADVyE7rvxYxqwUAAOQGWjwAAIhJWPK8wTJ9fY4j8AAAIIYxHr4j8AAAoBpjPPzHGA8AAJA1tHgAABBDV4vvCDwAAIhphCXTWccjNbpaAABA1tDi0Uis+T/KPVv9vdFWpvqfRWw5JSrliPilzDi1bK8xL80XkWNM9Q9HC031o8bkJZGo7f16xvrWiXr23CvG+mHj+w2nf0BegS0PT6C17fdEPGueHGMensJCf4+/uMi3c+/qhwK+5u3xwv79qBqMrhbfEXgAABBD4OE7uloAAEDW0OIBAEA11vHwHy0eAAAgawg8AABA1tDVAgBADINLfUfgAQBANcZ4+I/AAwCARNlaMyRPMcYDAABkDS0eAADEMMbDdwQeAABUY4yH//Iq8CiPhCQSSe8tH6qy5Vg4GCk21d8bseVkKAhETPVbB225Wsq8Ct+OpcySkEFEdhtzr+yoamuqv6fSdu4PVtquhYqILb+IF/E5V0vA79wutm9Q1Sr96yFYbjv3oarWpvqBImMulfL0f0++PCDjySywJcrxWtlytUSLbfuvKgn5mrfHUt+a0wjNV14FHgAApERXi+8IPAAAqEZXi/+Y1QIAALKGFg8AAGLoavEdgQcAADEEHr6jqwUAAGQNLR4AAFRjcKn/CDwAAIihq8V3BB4AAMQQePiOMR4AACD3A4+tW7fK9ddfL7169ZKSkhI56aST5M4775SKiuQliQOBQK2yYMGCpjpsAEAejPHItKAZdrX885//lGg0Kr/+9a/lq1/9qmzevFnGjh0rhw4dkvvuuy+p7mOPPSYXXXRR/HHbtrbcHDFRL+BKOvZX2HKvFIcqTfWLArb6hyO2nAxlUVtOhtJQWdp1CwJVpn0fiJSY6h+O2vJnfFLezlR/rzFXyyFjrpbyCtu5N7PmXrGl25Co8a4QMb7dqpL0/94JGnMmeUHb31LBI7bfw0CJ8XjCxpNpzZNjzb1iyJPTkDw80UJrrhZD3ahkB10tuRt4aCCRGEyceOKJ8u6778ojjzxSK/A49thjpXPnzk1wlAAAIGfHeOzbt0/at29fa/v48eOlY8eOct5557luFm0pSaW8vFz279+fVAAAOBq6WvJoVsu//vUveeihh+T+++9P2v7zn/9chgwZ4saB/PWvf5XJkyfLrl275Pbbb693X7Nnz5aZM2dm4agBADmFrpaW1+IxY8aMOgeEJpYNGzYkvebTTz913S5XX3213HDDDUnPaYDRv39/Oeuss1zQMWvWLLn33ntTHsPUqVNd60msbNu2rbHfJgAAaA4tHtotMmrUqJR1TjjhhKSgY/DgwS64ePTRR4+6/379+rmuk88++0w6depUZ52ioiJXAAAwocWj5QUeOhZDSzo++eQTF3T06dPHzVwJpjEifdOmTVJcXOwGnAIA0JgC9oljde4DzXCMh7Z0DBo0SHr06OFmsXz++efx52IzWP70pz/Jjh07XGuIjvF46aWXZPr06fLDH/6QFg0AAFqgJgs8li1bJu+//74r3bp1S3rO875cfaWgoEDmz58vkyZNcjNZdMqtjvG46aabmuioAQA5ja6W3A08rrvuOlcsa30AAOAnstPm0XRaAACaHC0e+bWAGAAAyG151eJxoLxYwuH0ptmWFqWfu0QdrrJN3/08UGqq3zpcbqpfaU3QYZBuvpuYgmDEVH+PMZfK/ipbXp0dR2zn/rAxV0skYovnvUp/43/PuHvrpVNVYrsewuXp16+K2A7GC9iOJVhoOzkBzzhNv3q8WtrVw7bjiRrrV7W2nc+IMVeLtX40nH79aDSLc0VI8uarvAo8AABIhTEe/qOrBQAAZA0tHgAAxDC41HcEHgAAVKOrxX90tQAAgKyhxQMAgBi6WnxH4AEAQDW6WvxHVwsAAMgaWjwAAIihq8V3BB4AAMQQePiOwAMAgGqM8fBfXgUeldGQRNPM/WDNz1EVDfqa7+RIpMBU/0DIlr8kHEg/n0pBIGra98GI7VyWGd/r3ooS2/FU2PJtlFcZ81tUGYdOBRpwZ/TxG0RtPy6J2tIISUXr9M+PFzTmXim2nfvwEWuuFlN1kYg1V4sxN0qhsb7x/Bh/FX3NC+Rj+ilkWV4FHgAApERXi+8IPAAAqBbwPFcykenrcx3TaQEAaCH27Nkjo0ePlrZt27qi/967d2/K13ieJzNmzJCuXbtKSUmJDBo0SN5+++2kOuXl5XLzzTdLx44dpXXr1nL55ZfLxx9/nFTnrrvukgEDBkirVq3k2GOPbfB7IPAAACD+Kd1IxSfXXnutvP766/Liiy+6ov/W4COVe+65R+bOnSvz5s2T9evXS+fOnWXo0KFy4MCBeJ2JEyfK0qVLZfHixbJq1So5ePCgXHrppRKJ/Hv8X0VFhVx99dXy4x//OKP3QFcLAAAtYFbLO++844KNtWvXSt++fd223/zmN9K/f39599135Wtf+1qdrR0PPvigTJ8+XUaMGOG2LVq0SDp16iRPP/20/OhHP5J9+/bJwoUL5Xe/+51ceOGFrs6TTz4p3bt3lxUrVsjw4cPdtpkzZ7qvjz/+eEbvgxYPAAB8sH///qSi3RmZWLNmjeteiQUdql+/fm7b6tWr63zNli1bZMeOHTJs2LD4tqKiIhk4cGD8NRs3bpTKysqkOtot07t373r3mwkCDwAAfOhq0RaD2FgMLbNnz87oPGsAcfzxx9fartv0ufpeo7SFI5E+jj2nXwsLC6Vdu3b11mlMdLUAAOBDV8u2bdukTZs2SS0NddGBn7FujPro2AwVCATq7E6pa3uims+n85p06jQEgQcAAD7QoCMx8KjP+PHjZdSoUSnrnHDCCfLmm2/KZ599Vuu5zz//vFaLRowOJFXactGlS5f49p07d8Zfo3V04KjOmEls9dA6OoulsRF4AADQhAuIdezY0ZWj0UGkOhD0tddek29+85tu27p169y2+gKEXr16ucBi+fLlcvbZZ7ttGmSsXLlS5syZ4x736dNHCgoKXJ1rrrnGbdu+fbts3rzZzYhpbAQeAAC0gFktp512mlx00UUyduxY+fWvf+22/fCHP3TTXhNntJx66qluPMmVV17pukp0quzdd98tJ598siv6b12LQ6fmKh1/cv3118vkyZOlQ4cO0r59e5kyZYqcccYZ8Vku6qOPPpIvvvjCfdVptjqVV331q1+VY445Ju33kVeBR2VVSKJp5t0IB235SALGK21XWStT/VbhSlP94nCVtFS7j9jOTdSYi+RguS0ZyaHDttwuXtTYJ2qtb8zz4xl/y6PW+oU+pi8x9i+HjJMGomHb+Pqgz79W1nPvhQK+7j9SYNy/NbeL4XjSzyaV20umP/XUUzJhwoT4DBRd6EvX50ikU2u1FSTmtttukyNHjsi4ceNcd4rOilm2bJmUlpbG6zzwwAMSDoddi4fWHTJkiJs2Gwr9+zPzZz/7mZuKGxNrQXnppZfcomTpCng6eiTH6TQmjehOe+YnEmqV3odISaHtg77Q+EFfELT9GhF4NF7gse9Isa+BR6TSls0qesT2aRA6ZNt/6Ijxw/uIqboUHBLfggNrIBEqt93OglXW+uIrAo/6RcrL5O0F09wHajrjJhr6OdHnmrskVGi7R9QUqSiTjf93um/H2tLlVYsHAABN1VWCLxF4AAAQo50AmXYE5H5HQkZYQAwAAGQNLR4AALSAWS25gsADAIAWMqslF9DVAgAA8iPw0CVgdXGTxPLTn/40qY4uVHLZZZdJ69at3cpuOn9ZV10DAKCxBaKNU9CMu1pmzZrlVmGLSVz9TFdGu+SSS+S4446TVatWye7du2XMmDEucc1DDz3UREcMAMhZdLXkfuChK6fFktjUpCur/eMf/3AZ/rp27eq23X///XLdddfJXXfdxcIsAAC0ME0+xkOT1Oja8GeddZYLJhK7UdasWSO9e/eOBx1q+PDhUl5eLhs3bqx3n/q8rkKXWAAASHdWS6YFzbTF45ZbbpFzzjnHpeHVbHtTp06VLVu2yG9/+9t4Gt+aqX61bmFhoXuuPpocZ+bMmbW2V1YF087VEjXmw6iK2mK4IuMS65VR2zLZhyqjvu2/MGQ79oqI7TKrSPNn9O/9G+tXhn1dAt2L2K6FQJUxV4vPPNvblYgxV4tYLk3jDdwL2s5lwJgnx7rEuhf09/iNtynzB6I1D499yXfDvrOVrIUFxFpei8eMGTNqDRitWTZs2ODq3nrrrTJw4ED5xje+ITfccIMsWLBAFi5c6MZyxGj9mnSMR13bYzSA0TXyY0W7agAAOBpaPFpgi8f48eNl1KhRR53NUpd+/fq5r++//77rftGxH+vWrUuqo5n1Kisra7WEJCoqKnIFAADkeOChU161NMSmTZvc1y5duriv/fv3d+M+tm/fHt+mA041qOjTp08jHjUAAMxqyekxHjpwdO3atTJ48GCXinj9+vWu6+Xyyy+XHj16uDrDhg2T008/XUaPHi333nuvfPHFFzJlyhQ3/ZZUwwCAxsaS6TkceGirxZIlS9wgUJ2F0rNnTxdQ3HbbbfE6oVBInn/+eRk3bpx861vfkpKSErn22mvlvvvua6rDBgAALTHw0Nks2uJxNNr68T//8z9ZOSYAQJ5jVkvuLyAGAEBzQVdLHiwgBgAA8gctHgAAxJCrxXcEHgAAVKOrxX90tQAAgKzJqxaPqObQSDOPhjVXiw6EtiirsJ36UND2DUIhW66WUDD9+gfKbKvCRox5bCzHosoqCmzHU+VvvO1VGHO1WBNKGV9gzf8hAX9zu1jyf1j3HS4Tf3OdGHO7+M7nn5U1V05O5GqJel+WTPeBeuVV4AEAQEqM8fAdgQcAAAmNSJmmtW9m7WLNDmM8AABA1tDiAQBADCuX+o7AAwCAakyn9R9dLQAAIGto8QAAIIZZLb4j8AAAoFrA81zJRKavz3V0tQAAgKyhxQMAgBhdONm2eHJtmb4+xxF4AABQja4W/+VV4BGpDIpXmV7vUiBkzIdhzOEQMOZeqTLmKQhU2fYfNCzVFzAu62fN1VJZaUso4RkTbkTTvAbi+zfmdglUGdctNF471vwiVubcKwX+LesYrLDturKVrX7Q+ntl/Es2EPE314mZ39dO0L/61n2j+cqrwAMAgJSY1eI7Ag8AAGJYudR3BB4AAFRj5VL/0WsGAACyhhYPAABi6GrxHYEHAAAJM5ess5dqyvT1uY6uFgAAkDW0eAAAEENXi+8IPAAAiGEdD9/R1QIAALKGFg8AAKqRq8V/eRV4aE6PdHOqWPN/eMb8JQFjW1MgaB0mbTv+KsP7DRrzzESMuU5cU6eluvVnZTweL2LMw2PMh2G8dOy5YKzfwHo+g/7lgokU+fxWq5rZbAXP5/fr48/KCfiYq8WY96bBGOPhO7paAABA1uRViwcAAClpK1KmLVvGlqh8Q+ABAEA1xnjkcFfLyy+/LIFAoM6yfv36eL26nl+wYEFTHTYAIOen03oZlqZ+E81bk7V4DBgwQLZv35607Y477pAVK1bIueeem7T9sccek4suuij+uG3btlk7TgAAkAOBR2FhoXTu3Dn+uLKyUp577jkZP368a9VIdOyxxybVBQDAF8xqyZ9ZLRp07Nq1S6677rpaz2kw0rFjRznvvPNcN0s0SgYeAIAPoo1U0PwHly5cuFCGDx8u3bt3T9r+85//XIYMGSIlJSXy17/+VSZPnuwClNtvv73efZWXl7sSs3//fl+PHQAANFGLx4wZM+odNBorGzZsSHrNxx9/LH/5y1/k+uuvr7U/DTD69+8vZ511lgs6Zs2aJffee2/KY5g9e7YbBxIrNYMZAABSzWrJtCCLLR7aLTJq1KiUdU444YRag0c7dOggl19++VH3369fP9eC8dlnn0mnTp3qrDN16lSZNGlS/LHWJ/gAABwVYzxaXuChYzG0pMvzPBd4/Od//qcUFBQctf6mTZukuLjYDTitT1FRkSsAAKB5afIxHn/7299ky5YtdXaz/OlPf5IdO3a4rhYd4/HSSy/J9OnT5Yc//GGDAgsvGnQlzdq2fVuTFBgFgsZ8IcZ8KpbDjxhzl0Qj/uZqseYWsQoY36/vjKczWii+5h0KlRmvh5B/OY2sg/qsuVcsx94Qfh+Pdf/W3C5Wllwwfh/Lv79R9Vocme4DzTfw0EGluqbHaaedVus5bQGZP3++6zbRmSwnnniiG+Nx0003NcmxAgByHIFH7gceTz/9dL3P6aJhiQuHAQCAlq3JAw8AAJoN7Y7KtHeVdTxaxgJiAAA0teY+nXbPnj0yevTo+HIR+u+9e/cedRKHLnXRtWtXN15y0KBB8vbbbyfV0bWvbr75Zjc5pHXr1m6WqS51EbN161Y3FrNXr15uHyeddJLceeedUlFRYX4PBB4AAPz7U7pxik+uvfZaef311+XFF190Rf+twUcq99xzj8ydO1fmzZvnkrBqCpKhQ4fKgQMH4nUmTpwoS5culcWLF8uqVavk4MGDcumll0okEnHP//Of/3RjLX/961+7oOWBBx5wK4lPmzbN/B7oagEAoAV45513XLCxdu1a6du3r9v2m9/8xs38fPfdd+VrX/tana0dDz74oJsROmLECLdt0aJFbh0sHWP5ox/9SPbt2+cmevzud7+TCy+80NV58skn3fpXmrhVVxWvOeZSJ3vo93zkkUfkvvvuM70PWjwAAIiJeo1TqhevTCyJqTwaYs2aNa57JRZ0xBbV1G2rV6+u8zW6XIUuSzFs2LD4Nl2OYuDAgfHXbNy40SVqTayj3TK9e/eud79KA5b27dub3weBBwAAPnS1aItBYvoOTeeRCQ0gjj/++FrbdZs+V99rVM2VvvVx7Dn9qhnj27VrV2+dmv71r3/JQw89JDfeeKP5fdDVAgCAD7Zt2yZt2rSJP65v4csZM2bIzJkzU+5Lx2YozXdWV3dKXdsT1Xw+ndfUV+fTTz913S5XX3213HDDDWJF4AEAwL8/bhthcOiXr9egIzHwyDTH2ZtvvunylNX0+eef15u7TAeSKm256NKlS3z7zp0746/ROjo7RWfMJLZ6aB1d4LNm0DF48GA3ruTRRx+VhiDwAACgCVcu7ZhmjjP9sNdxFa+99pp885vfdNvWrVvnttUMEGJ0+qsGFsuXL5ezzz7bbdMgY+XKlTJnzhz3uE+fPm6lcK1zzTXXuG3bt2+XzZs3uxkxMZ988okLOrS+5lgLBhs2WiOvAg+vKuBKOo7WBFVLyHihWqunedzx+j6O3gkErQkfjN/AmNvFs+ZSMeaxCRhzwQQqfc7tYl2cKNB88m2Y84X4nLvE5zQ/ZoEvZy76xjPe8a33EWOaH9P+s5arpRk77bTTXBfH2LFj3bRWpbnLdNpr4oyWU0891Y0nufLKK91nmU6Vvfvuu+Xkk092Rf/dqlUrNzVX6fgTXaNj8uTJLlO8DhidMmWKnHHGGfFZLtrSoet/9OjRw81i0VaWmq0q6cqrwAMAgJTcjJQMWzyqZ7X44amnnpIJEybEZ6DoQl+6PkcineaqrSAxt912mxw5ckTGjRvnulN0VsyyZcuktLQ0XkfX5QiHw67FQ+sOGTJEHn/8cQmFvozmtf7777/vSrdu3WqNBbEIeNZXtEA6jUkjum7zZkqwpDit11gbPPxu8TDXb0YtHl400LJbPCqDzarFw7x/a/UqW/2g8XgsLR7WFgDzX+jR/GrxsF4LzanFI1JWJv+6e5r7QE1n3ERDPycu7DFOwkF79vNEVdFyWfHRfN+OtaWj8QoAAGQNXS0AADTh4NJ8Q+ABAEALGeORCwg8AACIocXDd4zxAAAAWUOLBwAAMa6nJdMxHpzOVAg8AACIoavFd3S1AACArMmvFg9dyCrNxaw86yJTxkWsPJ+XvbYu5GN5QbTCeNkYz6W5mdL4ZgNl1lWRpHmxXgs+X2te2PNtQTnrX0Z+L3hlZV6S3edrzfdrobn9rjREVE9StBH2gfrkV+ABAEAqdLX4jq4WAACQNbR4AAAQQ4uH7wg8AACIYeVS39HVAgAAsoYWDwAAqnle1JVMZPr6XEfgAQBA4hiPTJO8kZ02JQIPAACSggYCDz8xxgMAAGQNLR4AACSuOmpe4rUGxnikROABAEAMXS2+y6vAI2AJZI1JB8w5Cqz5Swz5LRxzF6Vh/yF/89j4/l6tHYzGP378zvNjfb9eyFY/EDHu35o7xrJv48/KXN/nXCrWP5ytPyvztWC+T/mcKyfi377RfOVV4AEAQCpeNCpehl0tTKdNzdcY8q677pIBAwZIq1at5Nhjj62zzkcffSSXXXaZtG7dWjp27CgTJkyQioqKpDpvvfWWDBw4UEpKSuQrX/mKzJo1SzymKwEA/FoyPdOCpmnx0ADi6quvlv79+8vChQtrPR+JROSSSy6R4447TlatWiW7d++WMWPGuKDioYcecnX2798vQ4cOlcGDB8v69evlf//3f+W6665zgcrkyZP9PHwAANCSAo+ZM2e6r48//nidzy9btkz+8Y9/yLZt26Rr165u2/333+8CC20tadOmjTz11FNSVlbm9lFUVCS9e/d2wcfcuXNl0qRJEghYOy0BAKiHLh4WYB0PPzXpcJ01a9a4QCIWdKjhw4dLeXm5bNy4MV5Hu1k06Eis8+mnn8rWrVvr3K++XltKEgsAAEflukqiGRa6Wppt4LFjxw7p1KlT0rZ27dpJYWGhe66+OrHHsTo1zZ49W9q2bRsv3bt39+09AAAAHwOPGTNmuO6NVGXDhg1p76+urhId45G4vWad2MDS+rpZpk6dKvv27YsX7coBAOBovKjXKAWNOMZj/PjxMmrUqJR1TjjhhLT21blzZ1m3bl3Stj179khlZWW8VUPr1GzZ2Llzp/tasyUkRrtlErtmAABIf9VRVi5tVoGHTnnV0hh0tosOIt2+fbt06dIlPuBUg4Y+ffrE60ybNs3NkNEumFgdHReSboADAEA6XItFhoNLWe6hCcd46Bodr7/+uvuqU2f131oOHjzonh82bJicfvrpMnr0aNm0aZP89a9/lSlTpsjYsWPdjBZ17bXXukBEZ7ps3rxZli5dKnfffTczWgAAaIF8nU77s5/9TBYtWhR/fPbZZ7uvL730kgwaNEhCoZA8//zzMm7cOPnWt77lFgjTQOO+++6Lv0YHhy5fvlxuuukmOffcc93gU51Gq8UafUbLytI/eOsq1s1syfRMZ4Ol4rX0JdOtMmx1bezzYz6fPi5j3ZDzYzl+85Lj1iW+K/NryXTf0wtY6xuutWh5WVZaE6q88oyTvFWJ9cLKLwEvD9qEPv74Y2a2AEAO0MkC3bp1a/T96npRvXr1qne2pJWOT9yyZYsUFxc3yv5ySV4EHtFo1K37UVpamjQTRtf30Km2eiHHunbQuDjH2cF55hzn+rWsH1UHDhxw4/uCQX9GCWjwUTNlR0PpmESCjjxOEqcXaaoIWS9uAg9/cY6zg/PMOc7la1m73v2kgQLBgv9INAwAALKGwAMAAGRNXgceOk33zjvvZLExznGLx7XMOc4VXMu5Ly8GlwIAgOYhr1s8AABAdhF4AACArCHwAAAAWUPgAQAAsiYvAg/NgDtgwABp1aqVHHvssXXW0UR2l112mbRu3dpl350wYUKtFezeeustGThwoMsp85WvfEVmzZpFFsIUNHuwrhSbWH7605+azztSmz9/vlvqWRc+0qzOr776KqesgWbMmFHrmtWlr2N0LL7W0dUz9T6gOafefvttzvdRvPLKK+73XM+bntM//OEPSc+nc17Ly8vl5ptvdvcJvV9cfvnlLh0GWp68CDz0g+zqq6+WH//4x3U+r5lzL7nkEjl06JCsWrVKFi9eLM8++6xMnjw5aRnfoUOHul+M9evXy0MPPeSS2c2dOzeL76Tl0eBs+/bt8XL77bebzjtSW7JkiUycOFGmT5/uMjyff/75cvHFF7uADg3z9a9/Pema1T84Yu655x73Oz9v3jx3H9CgRO8LupQ36qe/42eeeaY7b3VJ57zqda7ZyfU+ofcLzXJ+6aWXuvsIWhgvjzz22GNe27Zta21/4YUXvGAw6H3yySfxbc8884xXVFTk7du3zz2eP3++e21ZWVm8zuzZs72uXbt60Wg0S++gZenZs6f3wAMP1Pt8OucdqX3zm9/0brzxxqRtp556qvfTn/6UU9cAd955p3fmmWfW+Zz+nnfu3Nn75S9/Gd+m9wO9LyxYsIDznSb92Fm6dKnpvO7du9crKCjwFi9eHK+j9w29f7z44ouc+xYmL1o8jmbNmjXSu3dv15oRM3z4cNe0t3Hjxngd7WbRxW0S62jyua1btzbJcbcEc+bMkQ4dOshZZ53lurwSu1HSOe+on55LPU/Dhg1L2q6PV69ezalroPfee89dk9p9NWrUKPnggw/cds00qplLE8+33g/0vsD5brh0zqte55WVlUl19Gek9w/OfcuTF0nijkYv+k6dOiVta9euncsuGEuRrF91zEKi2Gv0Ob1JIdktt9wi55xzjjuXr732mkydOtXdZH7729+mfd5Rv127drlm5prnUB9z/hqmb9++8sQTT8gpp5win332mfziF79w48N0vEHsnNZ1vj/88EMu1QZK57xqHb0v6P2hZh2u9ZYnmEuDwGqWDRs2pL0/rV+Ttgombq9ZJ7boa12vzVWW837rrbe6v1q+8Y1vyA033CALFiyQhQsXyu7du03nHanVdV1y/hpGx8dcddVVcsYZZ8iFF14ozz//vNu+aNEiznczvI651lumFtviMX78eNcMmkrNFor66ECmdevWJW3bs2ePa9qLReFap2ZkvXPnzjoj9VyWyXnv16+f+/r++++77pd0zjvqp6P7Q6FQndcl569x6OwJDUK0++WKK65w2/R8d+nShfPdSGKzhlKdV62jXYt6f0hs9dA62iKFliXYkm+6p556asqi0wvT0b9/f9m8ebMbwR6zbNky18+o0xNjdXRKWOIYBa2j/YzpBji5IJPzrrMuVOzmks55R/206VnP0/Lly5O262Nuxo1Dxxu988477prV7lT9AEw833o/WLlyJec7A+mcV73OCwoKkurofUPvH1zrLZCXBz788ENv06ZN3syZM71jjjnG/VvLgQMH3PNVVVVe7969vSFDhnh///vfvRUrVnjdunXzxo8fH9+Hjqru1KmT9/3vf9976623vN///vdemzZtvPvuu68J31nztXr1am/u3LnuPH/wwQfekiVL3Aygyy+/PF4nnfOO1HSUv472X7hwofePf/zDmzhxote6dWtv69atnLoGmDx5svfyyy+7a3bt2rXepZde6pWWlsbPp8680NkW+vuv9wG9H3Tp0sXbv38/5zsFvdfG7rv6sRO7N+i9Od3zqrO39P6g9wm9X3znO99xM5D0PoKWJS8CjzFjxriLvWZ56aWX4nX0F+CSSy7xSkpKvPbt27sPv8Sps+rNN9/0zj//fDfdU6d/zZgxg6m09di4caPXt29fdzMpLi72vva1r7mpiocOHUqql855R2oPP/ywm7pcWFjonXPOOd7KlSs5ZQ00cuRI94GnwZwGyiNGjPDefvvtpKmfeh3r77/eBy644AL3QYnU9F5b1z1Y783pntcjR464+4PeJ/R+oUHhRx99xKlvgQL6v6ZudQEAAPmhxY7xAAAALQ+BBwAAyBoCDwAAkDUEHgAAIGsIPAAAQNYQeAAAgKwh8AAAAFlD4AEAALKGwAMAAGQNgQcAAMgaAg8AAJA1BB4AAECy5f8DT9ybgvjWzuwAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tmp = plt.pcolormesh(*coordinates[:2], gz)\n", + "plt.gca().set_aspect(\"equal\")\n", + "plt.colorbar(tmp)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "f6d55c30-c5c9-4376-bc6d-ece83805b2d2", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:31.864447Z", + "iopub.status.busy": "2026-04-15T00:20:31.864275Z", + "iopub.status.idle": "2026-04-15T00:20:31.869152Z", + "shell.execute_reply": "2026-04-15T00:20:31.868131Z", + "shell.execute_reply.started": "2026-04-15T00:20:31.864429Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "961" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gz.size" + ] + }, + { + "cell_type": "markdown", + "id": "d460a7fd-9eed-497f-9b41-2de3a08f5750", + "metadata": {}, + "source": [ + "## Define SimPEG simulation" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "0edbb620-6a62-48bf-a8af-8981a76cc6f4", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:31.870162Z", + "iopub.status.busy": "2026-04-15T00:20:31.869910Z", + "iopub.status.idle": "2026-04-15T00:20:31.891564Z", + "shell.execute_reply": "2026-04-15T00:20:31.890484Z", + "shell.execute_reply.started": "2026-04-15T00:20:31.870142Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
TensorMesh64,000 cells
MESH EXTENTCELL WIDTHFACTOR
dirnCminmaxminmaxmax
x40-100.00100.005.005.001.00
y40-100.00100.005.005.001.00
z40-200.000.005.005.001.00
\n" + ], + "text/plain": [ + "\n", + " TensorMesh: 64,000 cells\n", + "\n", + " MESH EXTENT CELL WIDTH FACTOR\n", + " dir nC min max min max max\n", + " --- --- --------------------------- ------------------ ------\n", + " x 40 -100.00 100.00 5.00 5.00 1.00\n", + " y 40 -100.00 100.00 5.00 5.00 1.00\n", + " z 40 -200.00 0.00 5.00 5.00 1.00\n" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "h = [(5.0, 40)]\n", + "mesh = discretize.TensorMesh(h=[h, h, h], origin=\"CCN\")\n", + "mesh" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "8a890b93-78ec-4681-bbb5-cc91e1448a85", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:31.892988Z", + "iopub.status.busy": "2026-04-15T00:20:31.892672Z", + "iopub.status.idle": "2026-04-15T00:20:31.913954Z", + "shell.execute_reply": "2026-04-15T00:20:31.913159Z", + "shell.execute_reply.started": "2026-04-15T00:20:31.892961Z" + } + }, + "outputs": [], + "source": [ + "locations = np.vstack(tuple(c.ravel() for c in coordinates)).T\n", + "receivers = Point(locations, components=\"gz\")\n", + "source = SourceField(receiver_list=[receivers])\n", + "survey = Survey(source)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "1dd17123-2df8-45b6-9345-d5cb6f431d51", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:31.914848Z", + "iopub.status.busy": "2026-04-15T00:20:31.914682Z", + "iopub.status.idle": "2026-04-15T00:20:31.977687Z", + "shell.execute_reply": "2026-04-15T00:20:31.976613Z", + "shell.execute_reply.started": "2026-04-15T00:20:31.914832Z" + } + }, + "outputs": [], + "source": [ + "simulation_simpeg = Simulation3DIntegral(mesh, survey=survey, rhoMap=IdentityMap(mesh))" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "b4c9ad67-47fe-4408-b879-d99b8eec8ca2", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:31.978500Z", + "iopub.status.busy": "2026-04-15T00:20:31.978309Z", + "iopub.status.idle": "2026-04-15T00:20:31.982155Z", + "shell.execute_reply": "2026-04-15T00:20:31.981086Z", + "shell.execute_reply.started": "2026-04-15T00:20:31.978468Z" + } + }, + "outputs": [], + "source": [ + "def block_corners(prism):\n", + " p0 = np.array([prism[0], prism[2], prism[4]])\n", + " p1 = np.array([prism[1], prism[3], prism[5]])\n", + " return p0, p1" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "ffc07711-d8e3-49cf-9574-f2bde45ceec8", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:31.983121Z", + "iopub.status.busy": "2026-04-15T00:20:31.982955Z", + "iopub.status.idle": "2026-04-15T00:20:31.994057Z", + "shell.execute_reply": "2026-04-15T00:20:31.992890Z", + "shell.execute_reply.started": "2026-04-15T00:20:31.983104Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[-0.2 0.2]\n" + ] + } + ], + "source": [ + "model = np.zeros(mesh.n_cells)\n", + "densities_gcc = densities * 1e-3\n", + "print(densities_gcc)\n", + "\n", + "for prism, density in zip(prisms, densities_gcc, strict=True):\n", + " p0, p1 = block_corners(prism)\n", + " model = model_builder.add_block(mesh.cell_centers, model, p0, p1, density)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "0b89b688-a595-4ff3-81e3-8bba4d4ef7c8", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:31.995416Z", + "iopub.status.busy": "2026-04-15T00:20:31.995060Z", + "iopub.status.idle": "2026-04-15T00:20:32.131562Z", + "shell.execute_reply": "2026-04-15T00:20:32.130652Z", + "shell.execute_reply.started": "2026-04-15T00:20:31.995391Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(,)" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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AAAAlghUAAAAlghUAAAAlghUAAAAlghUAAAAlghUAAAAlghUAAAA/hKo5s+fL0lJSdK8eXNp3bp1hWUCAgLKLStWrPAos3fvXunbt6+EhoZKp06dJCMjQxzHqaejAAAAvqipeImioiIZOXKkJCYmyurVqystt2bNGhkyZIj7cXh4uPv/+fn5MnDgQOnfv7/s3LlT9u/fL+PGjZMWLVrItGnTLvkxAAAA3+Q1gSo9Pd18Xbt2bZXltPUqKiqqwm3PPfecFBQUmOcIDg6WuLg4E6qWLl0qKSkppkULAADAZ7v8aio5OVkiIiKkd+/epruvpKTEvW379u2mu0/DlMvgwYPl+PHjcujQoUqfs7Cw0LRulV4AAAB8MlA98sgj8uKLL0pWVpaMHj3adOMtWLDAvf3kyZMSGRnp8T2ux7qtMpmZmabr0LVER0dfwqMAAADepkEDVVpaWoUDyUsvu3btqvHzpaammjFWPXr0MGFKB5w/9thjHmXKduu5BqRX1d03c+ZMycvLcy+5ubm1PlYAAOC7mjZ095y2JFUlNjb2op8/ISHBdM+dOnXKtETp2KqyLVGnT582X8u2XJWmXYSluwkBAAAaTaDSsU66XCo5OTkSEhLinmZBW69mzZplrhgMCgoy6zZv3iwdO3a0Cm4AAMC/ec1VfkeOHJGzZ8+ar8XFxbJnzx6zvlu3btKyZUvZuHGjaX3S0KRzTGVnZ8vs2bNl4sSJ7talMWPGmKsFdaoEDVb/+te/zBirOXPmcIUfAADw/UCloWfdunXuxz179jRfNTj169dPmjVrJsuXLzfTH+iVfVdccYUZQzV58mT39+iA8rfeesusi4+PlzZt2pjyugAAAFysAIdpwmtNx2VpOItZOE8CQ0Iu+sUHAAD1p6SgQA5PTzUXmIWFhdXpc/vUtAkAAAANgUAFAABAoAIAAGhYtFABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAABYIlABAAD4S6CaP3++JCUlSfPmzaV169bltq9du1YCAgIqXE6fPm3KHDp0qMLtb7zxRgMcEQAA8BVNxUsUFRXJyJEjJTExUVavXl1u+6hRo2TIkCEe68aNGycFBQVy2WWXeazPysqSa6+91v24bdu2l3DPAQCAr/OaQJWenu5uiapIaGioWVzOnDkj77zzToXhq127dhIVFXUJ9xYAAPgTr+nyq63f/e53pnvwrrvuKrdt+PDhptWqT58+8tJLLzXI/gEAAN/hNS1UtfXMM8/ImDFjPFqtWrZsKUuXLjVBKjAwUF555RXTVbhu3Tr54Q9/WOlzFRYWmsUlPz//ku8/AADwHg3aQpWWllbpQHLXsmvXrlo/7/bt22Xfvn0yfvx4j/UREREydepUueGGGyQ+Pl4yMjJk0qRJsmjRoiqfLzMzU8LDw91LdHR0rfcJAAD4rgZtoUpOTpbRo0dXWSY2NrbWz7tq1Srp0aOH9OrVq9qyCQkJpnxVZs6cKSkpKR4tVIQqAADQKAKVthjpUpf+/e9/ywsvvGBalWoiJydHOnToUGWZ4OBgswAAAHj1GKojR47I2bNnzdfi4mLZs2ePWd+tWzczNspl/fr18s0338gPfvCDcs+hY6WaNWsmPXv2NGOoNm7cKE888YQsXLiwXo8FAAD4Fq8JVHPmzDGByEVDkcrOzpZ+/fq51+s0CSNGjJA2bdpU+Dzz5s2Tw4cPS5MmTeTKK680g9erGpAOAABQnQDHcZxqS8GDjqHSwekxC+dJYEgIrw4AAF6gpKBADk9Plby8PAkLC6vT5/bZeagAAADqC4EKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAEoEKAADAHwLVoUOHZPz48dKlSxcJDQ2Vrl27yty5c6WoqMij3JEjR2TYsGHSokULiYiIkClTppQrs3fvXunbt695nk6dOklGRoY4jlPPRwQAAHxJU/ECn3zyiZSUlMhTTz0l3bp1k48++kgmTJgg58+fl8WLF5syxcXFMnToUGnfvr1s3bpVvvzySxk7dqwJS8uWLTNl8vPzZeDAgdK/f3/ZuXOn7N+/X8aNG2cC2LRp0xr4KAEAgLcKcLy0eeaxxx6TJ598Uj7//HPz+PXXX5fbb79dcnNzpWPHjmbd888/bwLT6dOnJSwszJSfOXOmnDp1SoKDg02ZRx991ASuo0ePSkBAQI1+tgaz8PBwiVk4TwJDQi7hUQIAgLpSUlAgh6enSl5enskFftflVxF9Mdq2bet+vH37domLi3OHKTV48GApLCyU3bt3u8tod58rTLnKHD9+3HQrAgAA+GyXX1mfffaZaVVasmSJe93JkyclMjLSo1ybNm0kKCjIbHOViY2N9Sjj+h7dpmO0KqKhTJfSYc6VdAEAgHdwfW5fis65Bg1UaWlpkp6eXmUZHesUHx/vfqytSUOGDJGRI0fKfffd51G2oi47fdFKry9bxvWiVtXdl5mZWeF+5s6dV+W+AwCAxkfHWevQHZ8JVMnJyTJ69Ogqy5RuUdIwpQPKExMTZeXKlR7loqKiZMeOHR7rzp07JxcuXHC3QmkZV2uVi46vUmVbt0rTcVcpKSnux1999ZXExMSYqwrrukIaMx07Fh0dbcap1XXfc2PGcVPf/oD3Oe9zf5CXlyedO3f2GDLkE4FKpzbQpSaOHTtmwlSvXr1kzZo1EhjoOfxLQ9b8+fPlxIkT0qFDB7Nu8+bNZryUfo+rzKxZs8xUCtoV6Cqj467KdgWWps9RetyVi4YpfwoWLnrMHLf/oL79C/XtX/y1vgPLZIg6eU7xAtoy1a9fP9M6otMknDlzxrQ0lW5tGjRokFxzzTVy7733Sk5Ojrz99tvy8MMPm+kVXG+WMWPGmGCkV/7p1AsbNmyQBQsWmNanml7hBwAA4JWD0rUV6cCBA2a5/PLLKxwD1aRJE3nttddk0qRJ0qdPHzNxpwYo1zxVrhalt956SyZPnmzGZemgdQ1TpbvzAAAAfDJQaYuSLtXRftFXX321yjLXXXed/PWvf7XaH23l0pnaK+oG9GUcN/XtD3if8z73B7zPg+v8NfXaiT0BAAAaC68YQwUAANCYEagAAAAsEagAAAAsEagAAAAsEaiqoBOFJiUlSfPmzaV169YVltHZ0ocNGyYtWrQwk5ROmTLFTBxa2t69e81NmXUqh06dOklGRsYluY/QpbBlyxYzR1dFi94WyKWi7StWrBBvppO9lj2mGTNm1Lr+vYneJHz8+PHmvpb6fu3atau5orXsMflifavly5ebYw8JCTETAv/tb38TX6K30erdu7e0atVKLrvsMrnzzjvl008/9SijV1SXrduEhATxZnqbs7LHpHfOcNHzsZbRSZ71fa/zHn788cfi7So6h+miUwf5Ul3/9a9/NedhrT89hj//+c8e22tSv3q/3p/97GfmPK7n8+HDh8vRo0d9b9qEhqIfInrPQJ1hffXq1eW2FxcXy9ChQ6V9+/aydetWc2+gsWPHmsrTmze7bucwcOBAM8u7BpD9+/ebN7FW2LRp06Sx00Cps8+X9stf/lKysrI87rGodAZ7vc+iiy/clkfDr04O69KyZcta1b+3+eSTT6SkpESeeuop6datm5kAV4///PnzHnO6+WJ9r1+/Xh566CETqnQuO30NbrvtNtm3b5+ZksUXvPvuu+bDVEPVN998I7NnzzaTIusx6jnJRetV69fFdWcJb3bttdea85aLzl3osmjRIlm6dKmsXbtWrrzySpk3b545b2vY1PDprfQzR89TLvr7rMeln2u+VNfnz5+X73znO/LjH/9Yvv/975fbXpP61d/9jRs3yvPPPy/t2rUzn8+333677N692+O9UiWdNgFVW7NmjRMeHl5u/aZNm5zAwEDn2LFj7nV//OMfneDgYCcvL888Xr58ufnegoICd5nMzEynY8eOTklJide99EVFRc5ll13mZGRkeKzXt9KGDRscXxITE+P86le/qnR7TerfFyxatMjp0qWLz9f3DTfc4DzwwAMe66666ipnxowZjq86ffq0qct3333XvW7s2LHOHXfc4fiSuXPnOt/5zncq3Kbn4aioKOfRRx91r9PztZ63V6xY4fiSBx980Onatav7s8cX61rKnJtqUr9fffWV06xZM+f55593l9Hzup7f33jjjRr/bLr8LGzfvl3i4uJMM6LL4MGDTdOhplpXGe3uKz0JqJbR2+lo94q3eeWVV+SLL76ocKJVvdm1NpfqX7/a/aMtHd5u4cKF5q+VHj16mC7g0l1fNal/X7mZaEU3EvWl+tZ61TrT1prS9PG2bdvEV2ndqrL1q1392iWof81rC6XrJvLe7F//+pf5XdUu3dGjR8vnn39u1h88eNDcxqx03ev5Ws/bvlT3+h5/9tln5Sc/+YnHrdZ8sa5Lq0n96u/+hQsXPMroe0XP77V5D9DlZ0ErKTIy0mOd3s5Gm0xd9xnUr2VvvOz6Ht2mv9zeRLs+NTTofRVLe+SRR2TAgAGmf1rvo6jNpRq8UlNTxVs9+OCDcv3115s6ff/992XmzJnml3PVqlU1rn9v99lnn5nuyyVLlvh0feu+a9dI2frUx75Sl2XpH/N6262bbrrJfHC4aDendgnFxMSY97t28d9yyy3mQ8db7w5x4403yu9+9zsTGk6dOmW6fHQ4g46jcdVvRXV/+PBh8RU6ruirr77y+GPYF+u6rJrUr5bR87aev21+//0uUOnAtPT09Gr7ncuOD6pMRTdV1hNV6fVly7gGpDfkDZkv5nXQAXpvvvmmvPDCC+XKlv4g1dYc1/ijxvYBW5vjnjp1qntd9+7dzS/bXXfd5W61qmn9NwYXU9/aiqrjK/SEe99993llfddWRb+rja0u64q2MH744Ydm/F9po0aNcv9fg5a+J/QDV++VOmLECPFGGhxK335Mx8XqBRfr1q1zD8L29brXP4b1dSjdou6LdV2Zi6nf2r4H/C5Q6UlEm3urUrZFqTJ6lciOHTs81p07d840HbrSsJYpm3BdTaplE3Njfx104KIGCb36oTp6ktIB+frXYEMeZ13Wv+vEqzfp1tehJvXvrcetYUovpNAPnpUrV3ptfdeUdl3qwNOKfle98Xiqo1czafe9Xh1V9obzZXXo0MF8yGqXma/QAfgarPSY9EpHpXWvx+qLda8tMTog/+WXX/a7uo7639WcVdWvltEuUT1/l26l0jLakllTfheo9MSpS13QDxsdV6NXwbkqavPmzaapVC+5dpWZNWuWqSzX1RNaRv9KqGlwawyvgyZ1DVQ/+tGPpFmzZtWWz8nJMZeeVzbdhDfWvx6TctV1TerfG4/72LFjJkzpMWidBwYGem1915T+burxvvXWW/K9733PvV4f33HHHeIr9PdYw9SGDRvM2JmaDDnQq1dzc3M9Poy8nY5z/Oc//yk333yzeQ30A1XrumfPnma7nq/1ikhtjfYF+nus46T0qmR/q+suNahf/d3XzzUtc/fdd5t1el7XqyL1CsEaq7Oh9T7o8OHDTk5OjpOenu60bNnS/F+Xr7/+2mz/5ptvnLi4OGfAgAHOP/7xDycrK8u5/PLLneTkZPdz6NUDkZGRzj333OPs3bvXefnll52wsDBn8eLFjjfRY9O3y759+8pte+WVV5yVK1ea4ztw4IDz9NNPm2OcMmWK4622bdvmLF261NT3559/7qxfv95cmTl8+HB3mZrUv7fRK1u6devm3HLLLc7Ro0edEydOuBdfrm+lV/jolT6rV6827/OHHnrIadGihXPo0CHHV/z0pz81Vzdt2bLFo27/85//mO16bps2bZp5/x88eNDJzs52EhMTnU6dOjn5+fmOt9Jj0mPW3+X33nvPuf32251WrVq561avANPXRc/P+r7W83WHDh28+phdiouLnc6dOzvTp0/3WO9Ldf3111+7P5/1c8p17tbP8JrWr17hq+dvPY/r+VzPgXplqJ7na4pAVQW9pFQrp+yibzwXrbChQ4c6oaGhTtu2bc2HaekpEtSHH37o3HzzzeZyer18My0tzeumTNA3YFJSUoXbXn/9dadHjx4mdDZv3tyEjMcff9y5cOGC4612797t3HjjjeaXMCQkxPn2t79tLr0+f/68R7ma1L+3TRFS0Xu+9N9evljfLr/97W/NdBlBQUHO9ddf7zGdgC+orG613pUGq0GDBjnt27c34VI/iPU8eOTIEcebjRo1ynyA6jHpH0YjRoxwPv74Y/d2PR/r77een/U8/d3vftd88PqCN99809Txp59+6rHel+o6Ozu7wve1Hk9N6/e///2vOX/reVzP5xq6a/taBOg/l6ahDQAAwD8wDxUAAIAlAhUAAIAlAhUAAIAlAhUAAIAlAhUAAIAlAhUAAIAlAhUAAIAlAhUAAIAlAhUAAIAlAhUAAIAlAhUAv3fmzBlzR/oFCxa4X4sdO3ZIUFCQbN682e9fHwDV415+ACAimzZtkjvvvFO2bdsmV111lfTs2VOGDh0qjz/+OK8PgGoRqADgfyZPnixZWVnSu3dv+eCDD2Tnzp0SEhLC6wOgWgQqAPif//73vxIXFye5ubmya9cu6d69O68NgBphDBUA/M/nn38ux48fl5KSEjl8+DCvC4Aao4UKAESkqKhIbrjhBunRo4cZQ7V06VLZu3evREZG8voAqBaBCgBE5Oc//7m89NJLZuxUy5YtpX///tKqVSt59dVXeX0AVIsuPwB+b8uWLeZqvt///vcSFhYmgYGB5v9bt26VJ5980u9fHwDVo4UKAADAEi1UAAAAlghUAAAAlghUAAAAlghUAAAAlghUAAAAlghUAAAAlghUAAAAlghUAAAAlghUAAAAlghUAAAAlghUAAAAlghUAAAAYuf/AL3XHs+HR5vXAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "mesh.plot_slice(model, normal=\"Y\", slice_loc=0)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "ff50fbfa-9dd4-4c01-a8eb-245c3c0706a0", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:32.132354Z", + "iopub.status.busy": "2026-04-15T00:20:32.132160Z", + "iopub.status.idle": "2026-04-15T00:20:35.359609Z", + "shell.execute_reply": "2026-04-15T00:20:35.359008Z", + "shell.execute_reply.started": "2026-04-15T00:20:32.132334Z" + } + }, + "outputs": [], + "source": [ + "dpred = simulation_simpeg.dpred(model)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "094b1d30-c610-44ca-968b-7257aacbaa91", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:35.360705Z", + "iopub.status.busy": "2026-04-15T00:20:35.360209Z", + "iopub.status.idle": "2026-04-15T00:20:35.499179Z", + "shell.execute_reply": "2026-04-15T00:20:35.498295Z", + "shell.execute_reply.started": "2026-04-15T00:20:35.360682Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tmp = plt.pcolormesh(*coordinates[:2], dpred.reshape(coordinates[0].shape))\n", + "plt.gca().set_aspect(\"equal\")\n", + "plt.colorbar(tmp)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "137e7314-b148-46d9-b3b6-01496aaf8da4", + "metadata": {}, + "source": [ + "## Define inversion using the new framework" + ] + }, + { + "cell_type": "markdown", + "id": "001ed1bb-6919-486b-a954-4bd54de79b4e", + "metadata": {}, + "source": [ + "Wrap SimPEG's simulation into a child of the new `Simulation` class:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "a4303772-cfe0-45ed-90ec-c1caad768f39", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:35.499824Z", + "iopub.status.busy": "2026-04-15T00:20:35.499637Z", + "iopub.status.idle": "2026-04-15T00:20:35.503696Z", + "shell.execute_reply": "2026-04-15T00:20:35.502404Z", + "shell.execute_reply.started": "2026-04-15T00:20:35.499809Z" + } + }, + "outputs": [], + "source": [ + "simulation = ii.wrap_simulation(simulation_simpeg, store_jacobian=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "956f31ab-ed45-432f-a788-15cf73febf14", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:35.504439Z", + "iopub.status.busy": "2026-04-15T00:20:35.504223Z", + "iopub.status.idle": "2026-04-15T00:20:35.528635Z", + "shell.execute_reply": "2026-04-15T00:20:35.527125Z", + "shell.execute_reply.started": "2026-04-15T00:20:35.504420Z" + } + }, + "outputs": [], + "source": [ + "dpred = simulation(model)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "fd1dfb8c-fe88-4a46-b59e-70baaec550f7", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:35.531489Z", + "iopub.status.busy": "2026-04-15T00:20:35.530333Z", + "iopub.status.idle": "2026-04-15T00:20:35.678148Z", + "shell.execute_reply": "2026-04-15T00:20:35.676954Z", + "shell.execute_reply.started": "2026-04-15T00:20:35.531459Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tmp = plt.pcolormesh(*coordinates[:2], dpred.reshape(coordinates[0].shape))\n", + "plt.gca().set_aspect(\"equal\")\n", + "plt.colorbar(tmp)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "e916f176-b4b7-427a-847c-a282f85640a5", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:35.679103Z", + "iopub.status.busy": "2026-04-15T00:20:35.678870Z", + "iopub.status.idle": "2026-04-15T00:20:36.133418Z", + "shell.execute_reply": "2026-04-15T00:20:36.132548Z", + "shell.execute_reply.started": "2026-04-15T00:20:35.679085Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[-2.13782823e-05, -2.13372550e-05, -2.12555951e-05, ...,\n", + " -1.03258138e-07, -9.94137181e-08, -9.57099928e-08],\n", + " [-2.13691528e-05, -2.13828480e-05, -2.13554722e-05, ...,\n", + " -1.08606834e-07, -1.04571292e-07, -1.00679443e-07],\n", + " [-2.12872892e-05, -2.13554722e-05, -2.13828480e-05, ...,\n", + " -1.14213826e-07, -1.09984235e-07, -1.05900419e-07],\n", + " ...,\n", + " [-4.40011354e-06, -4.50945208e-06, -4.62019534e-06, ...,\n", + " -5.03316037e-02, -6.45257719e-03, -1.40158215e-03],\n", + " [-4.25670714e-06, -4.36399932e-06, -4.47284401e-06, ...,\n", + " -6.45257719e-03, -5.03316037e-02, -1.27241928e-02],\n", + " [-4.11623023e-06, -4.22130506e-06, -4.32805791e-06, ...,\n", + " -9.48031491e-04, -3.60945193e-03, -3.23499329e-02]],\n", + " shape=(961, 64000))" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "simulation.jacobian(model)" + ] + }, + { + "cell_type": "markdown", + "id": "2b28c265-4af8-4991-b0ad-ac99cdf42f6e", + "metadata": {}, + "source": [ + "Define a composed model: one part for densities, and another part for a dummy physical property.\n", + "\n", + "Build initial model dictionary and use it to create wires:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "931c7ff0-0f45-40cb-8227-c78e97823f79", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:36.134333Z", + "iopub.status.busy": "2026-04-15T00:20:36.134163Z", + "iopub.status.idle": "2026-04-15T00:20:36.138268Z", + "shell.execute_reply": "2026-04-15T00:20:36.137203Z", + "shell.execute_reply.started": "2026-04-15T00:20:36.134318Z" + } + }, + "outputs": [], + "source": [ + "initial_model_dict = {\n", + " \"density\": np.zeros(simulation.n_params),\n", + " \"dummy\": np.zeros(3),\n", + "}\n", + "\n", + "wires = ii.Wires.from_dict(initial_model_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "c862c062-c7e2-4d7b-8825-109fa7072bdd", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:36.139241Z", + "iopub.status.busy": "2026-04-15T00:20:36.139044Z", + "iopub.status.idle": "2026-04-15T00:20:36.157061Z", + "shell.execute_reply": "2026-04-15T00:20:36.155797Z", + "shell.execute_reply.started": "2026-04-15T00:20:36.139225Z" + } + }, + "outputs": [], + "source": [ + "uncertainties = stderr * np.ones(gz.size)\n", + "data_misfit = ii.DataMisfit(\n", + " gz.ravel(), uncertainties, simulation, model_slice=wires.density\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "9042f903-4d02-4abe-9de1-2dfacfb3b3a0", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:36.158271Z", + "iopub.status.busy": "2026-04-15T00:20:36.157926Z", + "iopub.status.idle": "2026-04-15T00:20:36.176136Z", + "shell.execute_reply": "2026-04-15T00:20:36.175047Z", + "shell.execute_reply.started": "2026-04-15T00:20:36.158229Z" + } + }, + "outputs": [], + "source": [ + "depth_weights = depth_weighting(mesh, 0) ** 2\n", + "\n", + "# Let's use a tikhonov zero for now, because I haven't implemented model slices in Smallness yet\n", + "smallness = ii.Smallness(\n", + " mesh=mesh, cell_weights=depth_weights, model_slice=wires.density\n", + ")\n", + "\n", + "flatness_x, flatness_y, flatness_z = tuple(\n", + " ii.Flatness(\n", + " mesh, direction=direction, cell_weights=depth_weights, model_slice=wires.density\n", + " )\n", + " for direction in (\"x\", \"y\", \"z\")\n", + ")\n", + "\n", + "alpha_x, alpha_y, alpha_z = 1e-2, 1e-2, 1e-2\n", + "regularization = (\n", + " smallness + alpha_x * flatness_x + alpha_y * flatness_y + alpha_z * flatness_z\n", + ").flatten()" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "ae115d7f-6b08-4c4e-b5f3-7277c24a49e4", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:36.176980Z", + "iopub.status.busy": "2026-04-15T00:20:36.176794Z", + "iopub.status.idle": "2026-04-15T00:20:36.186181Z", + "shell.execute_reply": "2026-04-15T00:20:36.184841Z", + "shell.execute_reply.started": "2026-04-15T00:20:36.176948Z" + } + }, + "outputs": [], + "source": [ + "phi = data_misfit + 1e2 * smallness" + ] + }, + { + "cell_type": "markdown", + "id": "e668a336-b9bb-45c8-ac17-b102e94dd935", + "metadata": {}, + "source": [ + "Convert the initial model dict to array using the wires" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "dbc4da3d", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:36.187245Z", + "iopub.status.busy": "2026-04-15T00:20:36.186983Z", + "iopub.status.idle": "2026-04-15T00:20:36.201664Z", + "shell.execute_reply": "2026-04-15T00:20:36.200474Z", + "shell.execute_reply.started": "2026-04-15T00:20:36.187222Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0., 0., 0., ..., 0., 0., 0.], shape=(64003,))" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "initial_model = wires.dict_to_array(initial_model_dict)\n", + "initial_model" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "924519fa-44b5-4ff5-b0d4-803882ed41a8", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:36.202912Z", + "iopub.status.busy": "2026-04-15T00:20:36.202582Z", + "iopub.status.idle": "2026-04-15T00:20:36.807675Z", + "shell.execute_reply": "2026-04-15T00:20:36.806315Z", + "shell.execute_reply.started": "2026-04-15T00:20:36.202885Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([423.61352886, 438.46600803, 452.99571158, ..., 0. ,\n", + " 0. , 0. ], shape=(64003,))" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data_misfit.hessian_diagonal(initial_model)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "463b4e7b", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:36.808656Z", + "iopub.status.busy": "2026-04-15T00:20:36.808399Z", + "iopub.status.idle": "2026-04-15T00:20:37.387391Z", + "shell.execute_reply": "2026-04-15T00:20:37.386434Z", + "shell.execute_reply.started": "2026-04-15T00:20:36.808635Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "preconditioner = ii.get_jacobi_preconditioner(phi, initial_model)\n", + "preconditioner" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "b7d8d1fc-af33-4658-bd94-a0ad75665bd3", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:37.388213Z", + "iopub.status.busy": "2026-04-15T00:20:37.387995Z", + "iopub.status.idle": "2026-04-15T00:20:37.824695Z", + "shell.execute_reply": "2026-04-15T00:20:37.823884Z", + "shell.execute_reply.started": "2026-04-15T00:20:37.388190Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "<64003x64003 BlockSquareMatrix with dtype=float64>" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data_misfit.hessian(initial_model)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "e2dfb4e8-1996-4f3f-8b05-781fc2d4537c", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:37.825523Z", + "iopub.status.busy": "2026-04-15T00:20:37.825340Z", + "iopub.status.idle": "2026-04-15T00:20:46.383990Z", + "shell.execute_reply": "2026-04-15T00:20:46.383442Z", + "shell.execute_reply.started": "2026-04-15T00:20:37.825506Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Elapsed time (no preconditioner): 8.55s\n" + ] + } + ], + "source": [ + "import time\n", + "\n", + "start = time.time()\n", + "inverted_model = ii.conjugate_gradient(\n", + " phi, initial_model, preconditioner=preconditioner\n", + ")\n", + "end = time.time()\n", + "print(f\"Elapsed time (no preconditioner): {end - start:.2f}s\")" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "82c1ddc2-d886-4cd1-a42d-1c32af5d4465", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:46.384850Z", + "iopub.status.busy": "2026-04-15T00:20:46.384520Z", + "iopub.status.idle": "2026-04-15T00:20:46.389090Z", + "shell.execute_reply": "2026-04-15T00:20:46.388455Z", + "shell.execute_reply.started": "2026-04-15T00:20:46.384826Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([-0.00123436, -0.00105846, -0.00090402, ..., 0. ,\n", + " 0. , 0. ], shape=(64003,))" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "inverted_model" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "05ef6fdf-55c0-4911-9ac8-2041af49b3cd", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:46.389690Z", + "iopub.status.busy": "2026-04-15T00:20:46.389501Z", + "iopub.status.idle": "2026-04-15T00:20:46.416993Z", + "shell.execute_reply": "2026-04-15T00:20:46.416244Z", + "shell.execute_reply.started": "2026-04-15T00:20:46.389668Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'density': array([-0.00123436, -0.00105846, -0.00090402, ..., -0.00049355,\n", + " 0.00065683, -0.00043417], shape=(64000,)),\n", + " 'dummy': array([0., 0., 0.])}" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "inverted_model_dict = wires.array_to_dict(inverted_model)\n", + "inverted_model_dict" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "d20f5357-efd0-42cb-a156-90fcd6b41f0b", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:46.417664Z", + "iopub.status.busy": "2026-04-15T00:20:46.417455Z", + "iopub.status.idle": "2026-04-15T00:20:46.423410Z", + "shell.execute_reply": "2026-04-15T00:20:46.422088Z", + "shell.execute_reply.started": "2026-04-15T00:20:46.417641Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([-0.00123436, -0.00105846, -0.00090402, ..., -0.00049355,\n", + " 0.00065683, -0.00043417], shape=(64000,))" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "inverted_density = inverted_model_dict[\"density\"]\n", + "inverted_density" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "70b2b5ea-d6dd-4cf3-855c-9e8fd600c7cd", + "metadata": { + "execution": { + "iopub.execute_input": "2026-04-15T00:20:46.426870Z", + "iopub.status.busy": "2026-04-15T00:20:46.426579Z", + "iopub.status.idle": "2026-04-15T00:20:46.580476Z", + "shell.execute_reply": "2026-04-15T00:20:46.579223Z", + "shell.execute_reply.started": "2026-04-15T00:20:46.426842Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(,)" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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LhRZl1GPSSm6XXX7pqTuLJHPDjroIHfcStHswpTrr79e5syZI/fcc4/st99+cs0117jjthabWnzmK/2do8epFP0+63Lp77VC2tZbt26Vb37zm/LP//zP8oMf/KDR6+lsX/3uz58/Xx544AHp3Lmz+/184oknytKlS+vtK83S2AQ07+677w7bt2/f6PknnngijMVi4ccff1z73B/+8IewtLQ03LRpk/v5lltuce+tqKiobTNr1qywZ8+eYTKZzLtVX1VVFe6xxx7h9OnT6z2vu9IjjzwSFpLevXuHv/rVr3b4ejrbvxBcf/31Yd++fQt+ex9xxBHhz3/+83rP7b///uFll10WFqrPPvvMbcvnn3++9rkxY8aEp5xySlhIrrrqqvCb3/xmk6/pcbh79+7htddeW/ucHq/1uH3bbbeFheSiiy4K991339rfPYW4raXBsSmd7btx48awuLg4fOCBB2rb6HFdj+9PPfVU2tPmlJ+HxYsXS//+/V03YsrIkSNd16FWtak2erqvbgiottHb6ejplXzz6KOPyueff95k0Kre7Fq7S/WvXz39oz0d+e66665zf60MGDDAnQKue+orne1fKDcTbepGooW0vXW76jbT3pq69OcXX3xRCpVuW9Vw++qpfj0lqH/Naw9l6iby+ezvf/+7+67qKd3Ro0fLBx984J5fuXKlu41Z3W2vx2s9bhfSttd9/L777pOf/vSn9W61Vojbuq50tq9+96urq+u10X1Fj++WfYBTfh50I3Xr1q3ec3o7G+0yTd1nUP9teOPl1Hv0Nf1y5xM99alFg95Xsa6rr75aRowY4c5P630UtbtUC68pU6ZIvrrooovksMMOc9v0lVdekcmTJ7sv55133pn29s9377//vjt9OXv27ILe3jrvemqk4fbUnwtlWzakf8zrbbe+9a1vuV8cKXqaU08J9e7d2+3veor/mGOOcb908vXuEEceeaT87ne/c0XDunXr3CkfHc6g42hS27epbf/hhx9KodBxRRs3bqz3x3AhbuuG0tm+2kaP23r89vn+t7iCSgemTZs2bafnnRuOD9qRpm6qrAequs83bJMakL47b8gcZT3oAL2nn35aHnzwwUZt6/4i1d6c1PijXPsFa1nuCRMm1D53yCGHuC/b6aefXttrle72zwVRtrf2our4Cj3gnnvuuXm5va2a+q7m2rbc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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "mesh.plot_slice(inverted_density, normal=\"Y\", slice_loc=0)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:inversion_ideas]", + "language": "python", + "name": "conda-env-inversion_ideas-py" + }, + "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.14.3" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": { + "55ba46e8dd294951a0f1d35b45845b32": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "d7af4393e22548319db206cf36433dea": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_55ba46e8dd294951a0f1d35b45845b32", + "msg_id": "", + "outputs": [ + { + "data": { + "text/html": "
┏━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓\n┃ Iteration  β         φ_d       φ_m       β φ_m     φ         χ         chi_target hit? ┃\n┡━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩\n│ 0         │ 1.00e+04 │ 1.67e+06 │ 0.00e+00 │ 0.00e+00 │ 1.67e+06 │ 1.74e+03 │ False           │\n│ 1         │ 1.00e+04 │ 4.62e+01 │ 4.30e-01 │ 4.30e+03 │ 4.35e+03 │ 4.81e-02 │ True            │\n└───────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴─────────────────┘\n
\n", + "text/plain": "┏━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓\n┃\u001b[1m \u001b[0m\u001b[1mIteration\u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mβ \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mφ_d \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mφ_m \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mβ φ_m \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mφ \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mχ \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mchi_target hit?\u001b[0m\u001b[1m \u001b[0m┃\n┡━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩\n│ 0 │ 1.00e+04 │ 1.67e+06 │ 0.00e+00 │ 0.00e+00 │ 1.67e+06 │ 1.74e+03 │ False │\n│ 1 │ 1.00e+04 │ 4.62e+01 │ 4.30e-01 │ 4.30e+03 │ 4.35e+03 │ 4.81e-02 │ True │\n└───────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴─────────────────┘\n" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "tabbable": null, + "tooltip": null + } + } + }, + "version_major": 2, + "version_minor": 0 + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/src/inversion_ideas/__init__.py b/src/inversion_ideas/__init__.py index fb407d1..067e4cc 100644 --- a/src/inversion_ideas/__init__.py +++ b/src/inversion_ideas/__init__.py @@ -23,6 +23,7 @@ ) from .regularization import Flatness, Smallness, SparseSmallness, TikhonovZero from .simulations import wrap_simulation +from .wires import Wires __all__ = [ "ChiTarget", @@ -43,6 +44,7 @@ "SparseSmallness", "TikhonovZero", "UpdateSensitivityWeights", + "Wires", "__version__", "base", "conjugate_gradient", diff --git a/src/inversion_ideas/data_misfit.py b/src/inversion_ideas/data_misfit.py index c5eff54..fc7ec27 100644 --- a/src/inversion_ideas/data_misfit.py +++ b/src/inversion_ideas/data_misfit.py @@ -12,6 +12,8 @@ from .base import Objective from .operators import get_diagonal from .typing import Model, SparseArray +from .utils import support_model_slice +from .wires import ModelSlice, MultiSlice class DataMisfit(Objective): @@ -96,6 +98,7 @@ def __init__( uncertainty: npt.NDArray[np.float64], simulation, *, + model_slice: ModelSlice | MultiSlice | None = None, build_hessian=False, estimate_hessian_diagonal=False, ): @@ -104,23 +107,29 @@ def __init__( self.data = data self.uncertainty = uncertainty self.simulation = simulation + self.model_slice = model_slice self.build_hessian = build_hessian self.estimate_hessian_diagonal = estimate_hessian_diagonal self.set_name("d") + @support_model_slice def __call__(self, model: Model) -> float: residual = self.residual(model) weights_matrix = self.weights_matrix return residual.T @ weights_matrix.T @ weights_matrix @ residual + @support_model_slice def gradient(self, model: Model) -> npt.NDArray[np.float64]: """ Gradient vector. """ jac = self.simulation.jacobian(model) weights_matrix = self.weights_matrix - return 2 * jac.T @ (weights_matrix.T @ weights_matrix @ self.residual(model)) + residual = self.simulation(model) - self.data + gradient = 2 * jac.T @ (weights_matrix.T @ weights_matrix @ residual) + return gradient + @support_model_slice def hessian( self, model: Model ) -> npt.NDArray[np.float64] | SparseArray | LinearOperator: @@ -143,6 +152,7 @@ def hessian( weights_matrix = aslinearoperator(self.weights_matrix) return 2 * jac.T @ weights_matrix.T @ weights_matrix @ jac + @support_model_slice def hessian_approx(self, model: Model) -> npt.NDArray[np.float64] | SparseArray: """ Approximated version of the Hessian. @@ -180,6 +190,7 @@ def hessian_approx(self, model: Model) -> npt.NDArray[np.float64] | SparseArray: return self.hessian(model) # type: ignore[return-value] return diags_array(self.hessian_diagonal(model)) + @support_model_slice def hessian_diagonal(self, model: Model) -> npt.NDArray[np.float64]: """ Get the main diagonal of the Hessian. @@ -242,6 +253,8 @@ def n_params(self): """ Number of model parameters. """ + if self.model_slice is not None: + return self.model_slice.full_size return self.simulation.n_params @property @@ -251,6 +264,7 @@ def n_data(self): """ return self.data.size + @support_model_slice(expand_return=False) def residual(self, model: Model): r""" Residual vector. @@ -286,7 +300,7 @@ def weights(self) -> npt.NDArray[np.float64]: return 1 / self.uncertainty**2 @property - def weights_matrix(self) -> dia_array: + def weights_matrix(self) -> dia_array[np.float64]: """ Diagonal matrix with the square root of the regularization weights. """ diff --git a/src/inversion_ideas/operators.py b/src/inversion_ideas/operators.py index 04cae29..41e3c98 100644 --- a/src/inversion_ideas/operators.py +++ b/src/inversion_ideas/operators.py @@ -4,9 +4,10 @@ import numpy as np import numpy.typing as npt +from scipy.sparse import dia_array from scipy.sparse.linalg import LinearOperator -from inversion_ideas.typing import HasDiagonal, SparseArray +from .typing import HasDiagonal, SparseArray def get_diagonal(operator: npt.NDArray | SparseArray | LinearOperator): @@ -101,3 +102,105 @@ def _get_standard_basis(ndim: int, dtype=np.float64): vector = np.zeros(ndim, dtype=dtype) vector[i] = 1 yield vector + + +class BlockSquareMatrix(LinearOperator): + r""" + Operator that represents a square matrix with a non-zero block surrounded by zeros. + + Use this class to represent hessian matrices that are built from smaller matrices + that operate only on a subset of the entire model. Only a block of that hessian will + be non-zero (the one related to the relevant model elements for that objective + function), while the rest of the matrix will be filled of zeros. + + Parameters + ---------- + block : array, sparse array or LinearOperator + Square block matrix. + slice_matrix : dia_array + Diagonal array to represent the slicing matrix. + + Notes + ----- + This ``LinearOperator`` represents square matrices like: + + .. math:: + + \mathbf{H} = \begin{bmatrix} + 0 & 0 & 0\\ + 0 & \mathbf{B} & 0\\ + 0 & 0 & 0 + \end{bmatrix} + + where :math:`\mathbf{B}` is a non-zero square block matrix that sits in the diagonal + of :math:`\mathbf{H}`. The matrix :math:`\mathbf{H}` can be built as: + + .. math:: + + \mathbf{H} = \mathbf{s}^T \mathbf{B} \mathbf{s} + + where :math:`\mathbf{s}` is the *slicing matrix*. + """ + + def __init__( + self, + block: npt.NDArray | LinearOperator | SparseArray, + slice_matrix: dia_array, + ): + if block.shape[0] != block.shape[1]: + msg = ( + f"Invalid block matrix '{block}' with shape '{block.shape}'. " + "It must be a square matrix." + ) + raise ValueError(msg) + + if slice_matrix.shape[0] != block.shape[1]: + msg = ( + f"Invalid block '{block}' and slice_matrix '{slice_matrix}' with " + f"shapes '{block.shape}' and {slice_matrix.shape}." + ) + raise ValueError(msg) + + if slice_matrix.shape[1] <= block.shape[1]: + # TODO: improve msg + msg = "block is larger than slice matrix" + raise ValueError(msg) + + _, full_size = slice_matrix.shape + shape = (full_size, full_size) + super().__init__(shape=shape, dtype=block.dtype) + + self._block = block + self._slice_matrix = slice_matrix + + @property + def block(self): + return self._block + + @property + def slice_matrix(self) -> dia_array: + return self._slice_matrix + + def _matvec(self, x): + """ + Dot product between the matrix and a vector. + """ + result = self.slice_matrix.T @ (self.block @ (self.slice_matrix @ x)) + return result + + def _rmatvec(self, x): + """ + Dot product between the transposed matrix and a vector. + """ + result = self.slice_matrix @ (self.block.T @ (self.slice_matrix.T @ x)) + return result + + def diagonal(self): + """ + Diagonal of the matrix. + """ + if not isinstance(self.block, HasDiagonal): + # TODO: Add msg + raise TypeError() + diagonal = self.block.diagonal() + return self.slice_matrix.T @ diagonal diff --git a/src/inversion_ideas/recipes.py b/src/inversion_ideas/recipes.py index 6b7bf5c..53b7a14 100644 --- a/src/inversion_ideas/recipes.py +++ b/src/inversion_ideas/recipes.py @@ -16,6 +16,7 @@ from .preconditioners import JacobiPreconditioner from .regularization import Flatness, Smallness from .typing import Model, Preconditioner +from .wires import ModelSlice, MultiSlice def create_l2_inversion( @@ -273,6 +274,7 @@ def create_tikhonov_regularization( alpha_y: float | None = None, alpha_z: float | None = None, reference_model_in_flatness: bool = False, + model_slice: ModelSlice | MultiSlice | None = None, ) -> Combo: """ Create a linear combination of Tikhonov (L2) regularization terms. @@ -321,6 +323,7 @@ def create_tikhonov_regularization( active_cells=active_cells, cell_weights=cell_weights, reference_model=reference_model, + model_slice=model_slice, ) if alpha_s is not None: smallness = alpha_s * smallness @@ -328,6 +331,7 @@ def create_tikhonov_regularization( kwargs = { "active_cells": active_cells, "cell_weights": cell_weights, + "model_slice": model_slice, } if reference_model_in_flatness: kwargs["reference_model"] = reference_model diff --git a/src/inversion_ideas/regularization/_mesh_based.py b/src/inversion_ideas/regularization/_mesh_based.py index 5decaf9..886a194 100644 --- a/src/inversion_ideas/regularization/_mesh_based.py +++ b/src/inversion_ideas/regularization/_mesh_based.py @@ -18,6 +18,8 @@ from .._utils import prod_arrays from ..base import Objective from ..typing import Model +from ..utils import support_model_slice +from ..wires import ModelSlice, MultiSlice class _MeshBasedRegularization(Objective): @@ -31,6 +33,8 @@ class _MeshBasedRegularization(Objective): @property def n_params(self) -> int: + if (model_slice := getattr(self, "model_slice", None)) is not None: + return model_slice.full_size return self.n_active @property @@ -150,6 +154,7 @@ def __init__( active_cells: npt.NDArray[np.bool] | None = None, cell_weights: npt.NDArray | dict[str, npt.NDArray] | None = None, reference_model: Model | None = None, + model_slice: ModelSlice | MultiSlice | None = None, ): self.mesh = mesh self.active_cells = ( @@ -157,6 +162,8 @@ def __init__( if active_cells is not None else np.ones(self.mesh.n_cells, dtype=bool) ) + # assign model_slice after active_cells so n_active is correct during __init__ + self.model_slice = model_slice # Assign the cell weights through the setter self.cell_weights = ( @@ -172,6 +179,7 @@ def __init__( ) self.set_name("s") + @support_model_slice def __call__(self, model: Model) -> float: """ Evaluate the regularization on a given model. @@ -193,6 +201,7 @@ def __call__(self, model: Model) -> float: @ model_diff ) + @support_model_slice def gradient(self, model: Model): """ Gradient vector. @@ -205,7 +214,7 @@ def gradient(self, model: Model): model_diff = model - self.reference_model weights_matrix = self.weights_matrix cell_volumes_sqrt = self._volumes_sqrt_matrix - return ( + gradient = ( 2 * cell_volumes_sqrt.T @ weights_matrix.T @@ -213,7 +222,9 @@ def gradient(self, model: Model): @ cell_volumes_sqrt @ model_diff ) + return gradient + @support_model_slice def hessian(self, model: Model): # noqa: ARG002 """ Hessian matrix. @@ -225,13 +236,14 @@ def hessian(self, model: Model): # noqa: ARG002 """ weights_matrix = self.weights_matrix cell_volumes_sqrt = self._volumes_sqrt_matrix - return ( + hessian = ( 2 * cell_volumes_sqrt.T @ weights_matrix.T @ weights_matrix @ cell_volumes_sqrt ) + return hessian @property def weights_matrix(self) -> dia_array[np.float64]: @@ -338,6 +350,7 @@ def __init__( active_cells: npt.NDArray[np.bool] | None = None, cell_weights: npt.NDArray | dict[str, npt.NDArray] | None = None, reference_model: Model | None = None, + model_slice: ModelSlice | MultiSlice | None = None, ): self.mesh = mesh self.direction = direction @@ -346,6 +359,8 @@ def __init__( if active_cells is not None else np.ones(self.mesh.n_cells, dtype=bool) ) + # assign model_slice after active_cells so n_active is correct during __init__ + self.model_slice = model_slice # Assign the cell weights through the setter self.cell_weights = ( @@ -361,6 +376,7 @@ def __init__( ) self.set_name(direction) + @support_model_slice def __call__(self, model: Model) -> float: """ Evaluate the regularization on a given model. @@ -385,6 +401,7 @@ def __call__(self, model: Model) -> float: @ model_diff ) + @support_model_slice def gradient(self, model: Model): """ Gradient vector. @@ -398,9 +415,9 @@ def gradient(self, model: Model): weights_matrix = self.weights_matrix cell_volumes_sqrt = self._volumes_sqrt_matrix cell_gradient = self._cell_gradient - return ( + gradient = ( 2 - * cell_gradient.T + @ cell_gradient.T @ cell_volumes_sqrt.T @ weights_matrix.T @ weights_matrix @@ -408,7 +425,9 @@ def gradient(self, model: Model): @ cell_gradient @ model_diff ) + return gradient + @support_model_slice def hessian(self, model: Model): # noqa: ARG002 """ Hessian matrix. @@ -421,7 +440,7 @@ def hessian(self, model: Model): # noqa: ARG002 weights_matrix = self.weights_matrix cell_gradient = self._cell_gradient cell_volumes_sqrt = self._volumes_sqrt_matrix - return ( + hessian = ( 2 * cell_gradient.T @ cell_volumes_sqrt.T @@ -430,6 +449,7 @@ def hessian(self, model: Model): # noqa: ARG002 @ cell_volumes_sqrt @ cell_gradient ) + return hessian @property def weights_matrix(self) -> dia_array[np.float64]: diff --git a/src/inversion_ideas/utils.py b/src/inversion_ideas/utils.py index 773ef01..cc45701 100644 --- a/src/inversion_ideas/utils.py +++ b/src/inversion_ideas/utils.py @@ -5,12 +5,14 @@ import functools import hashlib import logging +import warnings import numpy as np import numpy.typing as npt from ._utils import array_to_str from .typing import SparseArray +from .wires import ModelSlice, MultiSlice __all__ = [ "cache_on_model", @@ -217,6 +219,102 @@ def get_sensitivity_weights( return sensitivty_weights +def support_model_slice(func_=None, *, extract_model=True, expand_return=True): + """ + Apply a ``model_slice`` to the input and output of a method. + + This decorator will take the full model passed to the decorated method, extract only + the relevant slice for the instance based on its ``model_slice`` (if needed), and + then extend the result if it's a 1D array or a 2D square matrix, filling + non-relevant elements with zeros. + + .. important:: + + Use this decorator only for methods that take the ``model`` as the first + argument. + + .. important:: + + The instance needs to have a ``model_slice`` attribute. If it's None, then the + method will run without any modification. + + Parameters + ---------- + extract_model : bool, optional + If True, the ``model`` argument will be reduced using the ``model_slice`` + attribute of the object before being passed to the decorated function. + If False, the ``model`` argument will be passed as is to the decorated function. + expand_return : bool, optional + If True, 1D arrays and 2D square matrices returned by the decorated function + will be expanded to match the size of the full ``model``. + If False, the original output of the decorated function will be returned. + """ + if not extract_model and not expand_return: + # TODO: improve message. Check stacklevel. + msg = "Don't decorate the function" + warnings.warn(msg, stacklevel=2) + + def support_model_slice_decorator(func): + + @functools.wraps(func) + def wrapper(self, model, *args, **kwargs): + if not hasattr(self, "model_slice"): + # TODO: add msg + raise AttributeError() + + # Get model slice + model_slice: ModelSlice | MultiSlice = self.model_slice + + # Don't modify the model or the output if the object has no model_slice + if model_slice is None: + return func(self, model, *args, **kwargs) + + # Don't modify the model or the output if the model is already the + # reduced one + if model.size != model_slice.full_size: + if model.size != model_slice.size: + msg = ( + f"Invalid model of size '{model.size}'. " + f"It should be the full model " + f"(size of {model_slice.full_size}) " + f"or the reduced model (size of {model_slice.size})." + ) + raise ValueError(msg) + return func(self, model, *args, **kwargs) + + # Extract model using the model slice + # ----------------------------------- + if extract_model: + # Reduce the model and compute the output of the function with it + model_reduced = model_slice.extract(model) + result = func(self, model_reduced, *args, **kwargs) + else: + # Don't reduce the model, just compute the result of the function + result = func(self, model, *args, **kwargs) + + # Extend return of function + # ------------------------- + if expand_return: + if hasattr(result, "ndim"): + if result.ndim == 1: + result = model_slice.expand_array(result) + elif result.ndim == 2: + result = model_slice.expand_matrix(result) + else: + # TODO: add msg + raise ValueError() + else: + # TODO: add msg + raise TypeError() + return result + + return wrapper + + if func_ is None: + return support_model_slice_decorator + return support_model_slice_decorator(func_) + + class Counter: """ Simple counter callable class. diff --git a/src/inversion_ideas/wires.py b/src/inversion_ideas/wires.py new file mode 100644 index 0000000..43d1545 --- /dev/null +++ b/src/inversion_ideas/wires.py @@ -0,0 +1,411 @@ +""" +Model wiring. +""" + +from abc import ABC, abstractmethod +from collections.abc import Sequence +from numbers import Integral + +import numpy as np +import numpy.typing as npt +from scipy.sparse import dia_array, diags_array +from scipy.sparse.linalg import LinearOperator + +from .operators import BlockSquareMatrix +from .typing import Model, SparseArray + + +class Wires: + """ + Collection of model slices. + + The ``Wires`` have capabilities for handling models with multiple physical + properties. + + Parameters + ---------- + kwargs : dict + Keyword arguments. Each key represents the label of a portion of the model, and + its value should be an integer with the number of elements of that portion of + the model. + + Examples + -------- + Define a :class:`~inversion_ideas.Wires` object with two physical properties + (density and susceptibility), that hold different amount of model values for each. + + >>> import numpy as np + >>> wires = Wires(density=3, susceptibility=4) + >>> wires.size + 7 + >>> wires.keys() + dict_keys(['density', 'susceptibility']) + + Slice a model using the wires + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Define a model with 3 values for density and 4 for susceptibility: + + >>> model = np.array([0.1, 0.2, -0.1, 1e-3, 1e-2, 1e-1, 0.3]) + + Slice the model to get the density values: + + >>> wires.density.extract(model) + array([ 0.1, 0.2, -0.1]) + + Slice the model to get the susceptibility values: + + >>> wires.susceptibility.extract(model) + array([0.001, 0.01 , 0.1 , 0.3 ]) + + Convert a model to a *model dictionary* + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + >>> wires.array_to_dict(model) + {'density': array([ 0.1, 0.2, -0.1]), 'susceptibility': array([0.001, 0.01 , 0.1 , 0.3 ])} + + + Define a :class:`~inversion_ideas.Wires` object from a model dictionary + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + >>> model_dict = { + ... "conductivity": np.array([1e-4, 1e-2, 1e-1]), + ... "density": np.array([-0.3, 0.15, 0.01]), + ... } + >>> wires = Wires.from_dict(model_dict) + >>> wires.keys() + dict_keys(['conductivity', 'density']) + + We can use this ``Wires`` object to convert the model dictionary into an array: + + >>> model = wires.dict_to_array(model_dict) + >>> model + array([ 1.0e-04, 1.0e-02, 1.0e-01, -3.0e-01, 1.5e-01, 1.0e-02]) + + Defining multiple model slices + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Let's say we have a model for the parameters of a sphere: + + >>> model_sphere = { + ... "radius": np.array([10.0]), + ... "center": np.array([0.0, 0.0, -15.0]), + ... "density": np.array([0.2]), + ... "susceptibility": np.array([1e-2]), + ... "conductivity": np.array([1e-3]), + ... } + + We can create a ``Wires`` object from it: + + >>> wires = Wires.from_dict(model_sphere) + + We can access each individual ``ModelSlice`` as an attribute: + + >>> wires.center # doctest: +SKIP + + Or by indexing it: + + >>> wires["center"] # doctest: +SKIP + + And we can use these to extract pieces of the model array: + + >>> model = wires.dict_to_array(model_sphere) + >>> wires["center"].extract(model) + array([ 0., 0., -15.]) + + We can also extract multiple slices: + + >>> wires["radius", "center", "density"].extract(model) + array([ 10. , 0. , 0. , -15. , 0.2]) + + """ + + def __init__(self, **kwargs): + self._slices: dict[str, ModelSlice] = {} + + current_index: int = 0 + for key, value in kwargs.items(): + if not isinstance(key, str): + # TODO: Add msg + raise TypeError() + + if isinstance(value, Integral): + slice_ = slice(current_index, current_index + value) + current_index += int(value) + else: + # TODO: Add msg + raise TypeError() + + self._slices[key] = ModelSlice(name=key, slice=slice_, wires=self) + + self._size = sum([slice_.size for slice_ in self._slices.values()]) + + @property + def size(self) -> int: + """ + Total size of the model that can be sliced. + """ + return self._size + + def keys(self): + """ + Return keys in the wiring. + """ + return self._slices.keys() + + def __getattr__(self, value: str) -> "ModelSlice": + if value not in self._slices: + # TODO: Add msg + raise AttributeError() + return self._slices[value] + + def __getitem__(self, value: str | Sequence[str]) -> "ModelSlice | MultiSlice": + if not isinstance(value, str): + if not isinstance(value, Sequence): + # TODO: add msg + raise TypeError() + slices = {name: self._slices[name].slice for name in value} + return MultiSlice(slices=slices, wires=self) + return self._slices[value] + + @classmethod + def from_dict(cls, model_dict: dict[str, Model]): + """ + Create a ``Wires`` object from a model dictionary. + + Parameters + ---------- + model_dict : dict + Dictionary with string labels and 1d arrays as values. + + Returns + ------- + Wires + """ + kwargs = {key: array.size for key, array in model_dict.items()} + return cls(**kwargs) + + def array_to_dict(self, model: Model): + """ + Transform a model array into a dictionary. + + Parameters + ---------- + model : (n_params) array + Array with model values. + + Returns + ------- + dict + Dictionary with string labels and 1d arrays as values. + Each pair of key-value in the dictionary represents a portion of the model + array. + """ + if model.size != self.size: + # TODO: add msg + raise ValueError() + model_dict = {key: model[self[key].slice] for key in self.keys()} + return model_dict + + def dict_to_array(self, model_dict: dict[str, Model]): + """ + Ravel a model dictionary into a single 1D array. + + Parameters + ---------- + model_dict : dict + Dictionary with string labels and 1d arrays as values. + + Returns + ------- + array + 1D array with model values. + """ + # TODO: we should run sanity checks to ensure that the model_dict is compatible + # with this wiring. + model = np.hstack([model_dict[key] for key in self.keys()]) + return model + + +class _BaseModelSlice(ABC): + """ + Base class for a ModelSlice. + """ + + @property + @abstractmethod + def size(self) -> int: ... + + @property + @abstractmethod + def full_size(self) -> int: ... + + @property + @abstractmethod + def wires(self) -> Wires: ... + + @property + @abstractmethod + def slice_matrix(self) -> dia_array[np.float64]: ... + + def expand_array(self, array: npt.NDArray) -> npt.NDArray: + """ + Expand a 1D array by filling it with extra zeros. + + Parameters + ---------- + array : (n,) array + Array that will be filled. It must have the same number of elements as the + model slice. + + Returns + ------- + array : (m,) array + Array filled with zeros on elements outside of the model slice. + """ + return self.slice_matrix.T @ array + + def expand_matrix( + self, matrix: npt.NDArray | LinearOperator | SparseArray + ) -> "BlockSquareMatrix": + """ + Expand a square matrix into a ``BlockSquareMatrix`` filling blocks with zeros. + + Parameters + ---------- + matrix : array, sparse array or LinearOperator + Square matrix that will be expanded + + Returns + ------- + block_square_matrix : BlockSquareMatrix + LinearOperator that represents the matrix filled with zeros outside of the + block. + """ + return BlockSquareMatrix(block=matrix, slice_matrix=self.slice_matrix) + + +class ModelSlice(_BaseModelSlice): + """ + Class used to slice a model. + + .. important:: + + Don't instantiate this class. Use the ``Wires`` class instead. + """ + + def __init__(self, name: str, slice: slice, wires: Wires): + # TODO: Check that the slice has slice.start < slice.stop, no negative numbers, + # no None as start/stop, and step == 1. + if not isinstance(name, str): + # TODO: Add msg + raise TypeError() + self._name = name + self._slice = slice + self._wires = wires + + @property + def name(self) -> str: + return self._name + + @property + def slice(self) -> slice: + return self._slice + + @property + def size(self) -> int: + return self._slice.stop - self._slice.start + + @property + def full_size(self) -> int: + return self.wires.size + + @property + def wires(self) -> Wires: + return self._wires + + @property + def slice_matrix(self) -> dia_array[np.float64]: + """ + Return a matrix that can be used to slice a model array. + """ + ones = np.ones(self.size) + shape = (self.size, self.full_size) + return diags_array( + ones, offsets=self.slice.start, shape=shape, dtype=np.float64 + ) + + def extract(self, model: Model): + if model.size != self.wires.size: + # TODO: add msg + raise ValueError() + return model[self.slice] + + +class MultiSlice(_BaseModelSlice): + """ + Multiple slices. + + .. important:: + + Don't instantiate this class. Use the ``Wires`` class instead. + """ + + def __init__(self, slices: dict[str, slice], wires: Wires): + # TODO: Check that the slices have slice.start < slice.stop, no negative + # numbers, no None as start/stop, and step == 1. + # TODO: check that keys in slices dict are all strings + self._slices = slices + self._wires = wires + + @property + def names(self) -> list[str]: + """ + Slices names. + """ + return list(self.slices.keys()) + + @property + def slices(self) -> dict[str, slice]: + return self._slices + + @property + def wires(self) -> Wires: + return self._wires + + @property + def size(self) -> int: + return sum(s.stop - s.start for s in self.slices.values()) + + @property + def full_size(self) -> int: + return self.wires.size + + @property + def slice_matrix(self) -> dia_array[np.float64]: + """ + Return a matrix that can be used to slice a model array. + """ + if not self.slices: + # Add msg and choose right error type for empty slices + raise ValueError() + + shape = (self.size, self.full_size) + s = dia_array(shape) + row = 0 + for slice_ in self.slices.values(): + offset = slice_.start - row + index_start = row if offset >= 0 else row + offset + length = slice_.stop - slice_.start + diagonal = np.zeros(self.size, dtype=np.float64) + diagonal[index_start : index_start + length] = 1.0 + s += diags_array(diagonal, offsets=offset, shape=shape, dtype=np.float64) + row += length + return s + + def extract(self, model: Model): + if model.size != self.wires.size: + # TODO: add msg + raise ValueError() + parts = [model[s] for s in self.slices.values()] + return np.hstack(parts)