{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "<p align=\"center\">\n",
    "    <img src=\"https://github.com/GeostatsGuy/GeostatsPy/blob/master/TCG_color_logo.png?raw=true\" width=\"220\" height=\"240\" />\n",
    "\n",
    "</p>\n",
    "\n",
    "## Interactive Variogram Modeling Demonstration\n",
    "\n",
    "\n",
    "### Michael Pyrcz, Associate Professor, University of Texas at Austin \n",
    "\n",
    "##### [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig)  | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1)\n",
    "\n",
    "\n",
    "### The Interactive Workflow\n",
    "\n",
    "Here's a simple workflow on detecting the major spatial continuity directions in a spatial dataset with variogram analysis and then building a consistent. positive definate variogram model in 2D. \n",
    "\n",
    "* this model will provide consistent variogram values for any offset (azimuth and distance) in 2D\n",
    "\n",
    "This information is essential to optimum well placement and prectiction away from wells.  First let's explain the concept of spatial continuity and the variogram.\n",
    "\n",
    "#### Spatial Continuity \n",
    "\n",
    "**Spatial Continuity** is the correlation between values over distance.\n",
    "\n",
    "* No spatial continuity – no correlation between values over distance, random values at each location in space regardless of separation distance.\n",
    "\n",
    "* Homogenous phenomenon have perfect spatial continuity, since all values as the same (or very similar) they are correlated. \n",
    "\n",
    "We need a statistic to quantify spatial continuity! A convenient method is the Semivariogram.\n",
    "\n",
    "#### The Semivariogram\n",
    "\n",
    "Function of difference over distance.\n",
    "\n",
    "* The expected (average) squared difference between values separated by a lag distance vector (distance and direction), $h$:\n",
    "\n",
    "\\begin{equation}\n",
    "\\gamma(\\bf{h}) = \\frac{1}{2 N(\\bf{h})} \\sum^{N(\\bf{h})}_{\\alpha=1} (z(\\bf{u}_\\alpha) - z(\\bf{u}_\\alpha + \\bf{h}))^2  \n",
    "\\end{equation}\n",
    "\n",
    "where $z(\\bf{u}_\\alpha)$ and $z(\\bf{u}_\\alpha + \\bf{h})$ are the spatial sample values at tail and head locations of the lag vector respectively.\n",
    "\n",
    "* Calculated over a suite of lag distances to obtain a continuous function.\n",
    "\n",
    "* the $\\frac{1}{2}$ term converts a variogram into a semivariogram, but in practice the term variogram is used instead of semivariogram.\n",
    "* We prefer the semivariogram because it relates directly to the covariance function, $C_x(\\bf{h})$ and univariate variance, $\\sigma^2_x$:\n",
    "\n",
    "\\begin{equation}\n",
    "C_x(\\bf{h}) = \\sigma^2_x - \\gamma(\\bf{h})\n",
    "\\end{equation}\n",
    "\n",
    "Note the correlogram is related to the covariance function as:\n",
    "\n",
    "\\begin{equation}\n",
    "\\rho_x(\\bf{h}) = \\frac{C_x(\\bf{h})}{\\sigma^2_x}\n",
    "\\end{equation}\n",
    "\n",
    "The correlogram provides of function of the $\\bf{h}-\\bf{h}$ scatter plot correlation vs. lag offset $\\bf{h}$.  \n",
    "\n",
    "\\begin{equation}\n",
    "-1.0 \\le \\rho_x(\\bf{h}) \\le 1.0\n",
    "\\end{equation}\n",
    "\n",
    "#### Variogram Observations\n",
    "\n",
    "The following are common observations for variograms that should assist with their practical use.\n",
    "\n",
    "##### Observation \\#1 - As distance increases, variability increase (in general).\n",
    "\n",
    "This is common since in general, over greater distance offsets, there is often more difference between the head and tail samples.\n",
    "\n",
    "In some cases, such as with spatial cyclicity of the hole effect variogram model the variogram may have negative slope over somelag distance intervals\n",
    "\n",
    "Negative slopes at lag distances greater than half the data extent are often caused by too few pairs for a reliable variogram calculation\n",
    "\n",
    "##### Observation \\#2 - Calculated with over all possible pairs separated by lag vector, $\\bf{𝐡}$.\n",
    "\n",
    "We scan through the entire data set, searching for all possible pair combinations with all other data.  We then calculate the variogram as one half the expectation of squared difference between all pairs.\n",
    "\n",
    "More pairs results in a more reliable measure.\n",
    "\n",
    "##### Observation \\#3 - Need to plot the sill to know the degree of correlation.\n",
    "\n",
    "**Sill** is the variance, $\\sigma^2_x$\n",
    "\n",
    "Given stationarity of the variance, $\\sigma^2_x$, and variogram $\\gamma(\\bf{h})$:\n",
    "\n",
    "we can define the covariance function:\n",
    "\n",
    "\\begin{equation}\n",
    "C_x(\\bf{h}) = \\sigma^2_x - \\gamma(\\bf{h})\n",
    "\\end{equation}\n",
    "\n",
    "The covariance measure is a measure of similarity over distance (the mirror image of the variogram as shown by the equation above).\n",
    "\n",
    "Given a standardized distribution $\\sigma^2_x = 1.0$, the covariance, $C_x(\\bf{h})$, is equal to the correlogram, $\\rho_x(\\bf{h})$: \n",
    "\n",
    "\\begin{equation}\n",
    "\\rho_x(\\bf{h}) = \\sigma^2_x - \\gamma(\\bf{h})\n",
    "\\end{equation}\n",
    "\n",
    "##### Observation \\#4 - The lag distance at which the variogram reaches the sill is know as the range.\n",
    "\n",
    "At the range, knowing the data value at the tail location provides no information about a value at the head location of the lag distance vector.\n",
    "\n",
    "##### Observation \\#5 - The nugget effect, a discontinuity at the origin\n",
    "\n",
    "Sometimes there is a discontinuity in the variogram at distances less than the minimum data spacing.  This is known as **nugget effect**.\n",
    "\n",
    "The ratio of nugget / sill, is known as relative nugget effect (%). Modeled as a discontinuity with no correlation structure that at lags, $h \\gt \\epsilon$, an infinitesimal lag distance, and perfect correlation at $\\bf{h} = 0$.\n",
    "Caution when including nuggect effect in the variogram model as measurement error, mixing populations cause apparent nugget effect\n",
    "\n",
    "This exercise demonstrates the semivariogram calculation with GeostatsPy. The steps include:\n",
    "\n",
    "1. generate a 2D model with sequential Gaussian simulation\n",
    "2. sample from the simulation\n",
    "3. calculate and visualize experimental semivariograms\n",
    "\n",
    "#### Variogram Modeling\n",
    "\n",
    "Spatial continuity can be modeled with nested, positive definate variogram structures:\n",
    "\n",
    "\\begin{equation}\n",
    "\\Gamma_x(\\bf{h}) = \\sum_{i=1}^{nst} \\gamma_i(\\bf{h})\n",
    "\\end{equation}\n",
    "\n",
    "where $\\Gamma_x(\\bf{h})$ is the nested variogram model resulting from the summation of $nst$ nested variograms  $\\gamma_i(\\bf{h})$.\n",
    "\n",
    "The types of structure commonly applied include:\n",
    "\n",
    "* spherical\n",
    "\n",
    "* exponential\n",
    "\n",
    "* gaussian\n",
    "\n",
    "* nugget\n",
    "\n",
    "Other less common models include:\n",
    "\n",
    "* hole effect\n",
    "\n",
    "* dampenned hole effect\n",
    "\n",
    "* power law\n",
    "\n",
    "these will not be covered here.\n",
    "\n",
    "Each one of these variogram structures, $\\gamma_i(\\bf{h})$, is based on a geometric anisotropy model parameterized by the orientation and range in the major and minor directions.  In 2D this is simply an azimuth and ranges, $azi$, $a_{maj}$ and $a_{min}$. Note, the range in the minor direction (orthogonal to the major direction).\n",
    "\n",
    "The geometric anisotropy model assumes that the range in all off-diagonal directions is based on an ellipse with the major and minor axes alligned with and set to the major and minor for the variogram.\n",
    "\n",
    "\\begin{equation}\n",
    "\\bf{h}_i = \\sqrt{\\left(\\frac{r_{maj}}{a_{maj_i}}\\right)^2 + \\left(\\frac{r_{maj}}{a_{maj_i}}\\right)^2}  \n",
    "\\end{equation}\n",
    "\n",
    "Therefore, if we know the major direction, range in major and minor directions, we may completely describe each nested componnent of the complete spatial continuity of the variable of interest, $i = 1,\\dots,nst$.\n",
    "\n",
    "Some comments on modeling nested variograms:\n",
    "\n",
    "* we can capture nugget, short and long range continuity structures\n",
    "\n",
    "* we rely on the geometric anisotropy model, so all structures must inform the same level of contribution (porportion of the sill) in all directions.\n",
    "\n",
    "* the geometric anisotropy model is based on azimuth of the major direction of continuity, range in the major direction and range in the minor direction (orthogonal to the major direction).  The range is interpolated between the major and minor azimuths with a ellipse model\n",
    "\n",
    "* we can vary the type of variogram, direction or azimuth of the major direction, and major and minor ranges by structure\n",
    "\n",
    "In this workflow we will explore methods to:\n",
    "\n",
    "1. detect directionality from a spatial dataset\n",
    "2. calculate the directional variograms in the major and minor directions \n",
    "3. build a consistent 2D model fit to the major and minor directions\n",
    "\n",
    "#### Objective \n",
    "\n",
    "In the PGE 383: Stochastic Subsurface Modeling class I want to provide hands-on experience with building subsurface modeling workflows. Python provides an excellent vehicle to accomplish this. I have coded a package called GeostatsPy with GSLIB: Geostatistical Library (Deutsch and Journel, 1998) functionality that provides basic building blocks for building subsurface modeling workflows. \n",
    "\n",
    "The objective is to remove the hurdles of subsurface modeling workflow construction by providing building blocks and sufficient examples. This is not a coding class per se, but we need the ability to 'script' workflows working with numerical methods.    \n",
    "\n",
    "#### Getting Started\n",
    "\n",
    "Here's the steps to get setup in Python with the GeostatsPy package:\n",
    "\n",
    "1. Install Anaconda 3 on your machine (https://www.anaconda.com/download/). \n",
    "2. From Anaconda Navigator (within Anaconda3 group), go to the environment tab, click on base (root) green arrow and open a terminal. \n",
    "3. In the terminal type: pip install geostatspy. \n",
    "4. Open Jupyter and in the top block get started by copy and pasting the code block below from this Jupyter Notebook to start using the geostatspy functionality. \n",
    "\n",
    "You will need to copy the data file to your working directory.  They are available here:\n",
    "\n",
    "* Tabular data - sample_data.csv at https://git.io/fh4gm.\n",
    "\n",
    "There are exampled below with these functions. You can go here to see a list of the available functions, https://git.io/fh4eX, other example workflows and source code. \n",
    "\n",
    "#### Load the required libraries\n",
    "\n",
    "The following code loads the required libraries."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\pm27995\\Anaconda3\\lib\\site-packages\\numpy\\_distributor_init.py:30: UserWarning: loaded more than 1 DLL from .libs:\n",
      "C:\\Users\\pm27995\\Anaconda3\\lib\\site-packages\\numpy\\.libs\\libopenblas.GK7GX5KEQ4F6UYO3P26ULGBQYHGQO7J4.gfortran-win_amd64.dll\n",
      "C:\\Users\\pm27995\\Anaconda3\\lib\\site-packages\\numpy\\.libs\\libopenblas.XWYDX2IKJW2NMTWSFYNGFUWKQU3LYTCZ.gfortran-win_amd64.dll\n",
      "  warnings.warn(\"loaded more than 1 DLL from .libs:\"\n"
     ]
    }
   ],
   "source": [
    "import geostatspy.GSLIB as GSLIB                       # GSLIB utilies, visualization and wrapper\n",
    "import geostatspy.geostats as geostats                 # GSLIB methods convert to Python    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We will also need some standard packages. These should have been installed with Anaconda 3."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "import os                                               # to set current working directory \n",
    "import sys                                              # supress output to screen for interactive variogram modeling\n",
    "import io\n",
    "import numpy as np                                      # arrays and matrix math\n",
    "import pandas as pd                                     # DataFrames\n",
    "import matplotlib.pyplot as plt                         # plotting\n",
    "from matplotlib.pyplot import cm                        # color maps\n",
    "from ipywidgets import interactive                      # widgets and interactivity\n",
    "from ipywidgets import widgets                            \n",
    "from ipywidgets import Layout\n",
    "from ipywidgets import Label\n",
    "from ipywidgets import VBox, HBox\n",
    "from matplotlib.patches import Ellipse                  # plot an ellipse"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you get a package import error, you may have to first install some of these packages. This can usually be accomplished by opening up a command window on Windows and then typing 'python -m pip install [package-name]'. More assistance is available with the respective package docs.  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "#### Set the working directory\n",
    "\n",
    "I always like to do this so I don't lose files and to simplify subsequent read and writes (avoid including the full address each time).  Also, in this case make sure to place the required (see above) GSLIB executables in this directory or a location identified in the environmental variable *Path*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "#os.chdir(\"c:/PGE383\")                                   # set the working directory"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Loading Tabular Data\n",
    "\n",
    "Here's the command to load our comma delimited data file in to a Pandas' DataFrame object. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Unnamed: 0</th>\n",
       "      <th>X</th>\n",
       "      <th>Y</th>\n",
       "      <th>Facies</th>\n",
       "      <th>Porosity</th>\n",
       "      <th>Perm</th>\n",
       "      <th>AI</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0</td>\n",
       "      <td>100.0</td>\n",
       "      <td>900.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.101319</td>\n",
       "      <td>1.996868</td>\n",
       "      <td>5590.417154</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>1</td>\n",
       "      <td>100.0</td>\n",
       "      <td>800.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.147676</td>\n",
       "      <td>10.711789</td>\n",
       "      <td>3470.845666</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>2</td>\n",
       "      <td>100.0</td>\n",
       "      <td>700.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.145912</td>\n",
       "      <td>17.818143</td>\n",
       "      <td>3586.988513</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>3</td>\n",
       "      <td>100.0</td>\n",
       "      <td>600.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.186167</td>\n",
       "      <td>217.109365</td>\n",
       "      <td>3732.114787</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>4</td>\n",
       "      <td>100.0</td>\n",
       "      <td>500.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.146088</td>\n",
       "      <td>16.717367</td>\n",
       "      <td>2534.551236</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   Unnamed: 0      X      Y  Facies  Porosity        Perm           AI\n",
       "0           0  100.0  900.0     0.0  0.101319    1.996868  5590.417154\n",
       "1           1  100.0  800.0     1.0  0.147676   10.711789  3470.845666\n",
       "2           2  100.0  700.0     1.0  0.145912   17.818143  3586.988513\n",
       "3           3  100.0  600.0     1.0  0.186167  217.109365  3732.114787\n",
       "4           4  100.0  500.0     1.0  0.146088   16.717367  2534.551236"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#df = pd.read_csv(\"sample_data_MV_biased.csv\")                     # read a .csv file in as a DataFrame\n",
    "df = pd.read_csv(\"https://raw.githubusercontent.com/GeostatsGuy/GeoDataSets/master/sample_data_MV_biased.csv\")\n",
    "\n",
    "#print(df.iloc[0:5,:])                                  # display first 4 samples in the table as a preview\n",
    "df.head()                                               # we could also use this command for a table preview "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We will work with all facies pooled together. I wanted to simplify this workflow and focus more on spatial continuity direction detection. Finally, by not using facies we do have more samples to support our statistical inference. Most often facies are essential in the subsurface model. Don't worry we will check if this is reasonable in a bit.   \n",
    "\n",
    "You are welcome to repeat this workflow on a by-facies basis.  The following code could be used to build DataFrames ('df_sand' and 'df_shale') for each facies.\n",
    "\n",
    "```p\n",
    "df_sand = pd.DataFrame.copy(df[df['Facies'] == 1]).reset_index()  # copy only 'Facies' = sand records\n",
    "df_shale = pd.DataFrame.copy(df[df['Facies'] == 0]).reset_index() # copy only 'Facies' = shale records\n",
    "```\n",
    "\n",
    "Let's look at summary statistics for all facies combined:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>count</th>\n",
       "      <th>mean</th>\n",
       "      <th>std</th>\n",
       "      <th>min</th>\n",
       "      <th>25%</th>\n",
       "      <th>50%</th>\n",
       "      <th>75%</th>\n",
       "      <th>max</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Unnamed: 0</th>\n",
       "      <td>368.0</td>\n",
       "      <td>293.260870</td>\n",
       "      <td>169.058258</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>150.500000</td>\n",
       "      <td>296.000000</td>\n",
       "      <td>439.500000</td>\n",
       "      <td>586.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>X</th>\n",
       "      <td>368.0</td>\n",
       "      <td>499.565217</td>\n",
       "      <td>289.770794</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>240.000000</td>\n",
       "      <td>500.000000</td>\n",
       "      <td>762.500000</td>\n",
       "      <td>990.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Y</th>\n",
       "      <td>368.0</td>\n",
       "      <td>520.644022</td>\n",
       "      <td>277.412187</td>\n",
       "      <td>9.000000</td>\n",
       "      <td>269.000000</td>\n",
       "      <td>539.000000</td>\n",
       "      <td>769.000000</td>\n",
       "      <td>999.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Facies</th>\n",
       "      <td>368.0</td>\n",
       "      <td>0.597826</td>\n",
       "      <td>0.491004</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>1.000000</td>\n",
       "      <td>1.000000</td>\n",
       "      <td>1.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Porosity</th>\n",
       "      <td>368.0</td>\n",
       "      <td>0.127026</td>\n",
       "      <td>0.030642</td>\n",
       "      <td>0.041122</td>\n",
       "      <td>0.103412</td>\n",
       "      <td>0.125842</td>\n",
       "      <td>0.148623</td>\n",
       "      <td>0.210258</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Perm</th>\n",
       "      <td>368.0</td>\n",
       "      <td>85.617362</td>\n",
       "      <td>228.362654</td>\n",
       "      <td>0.094627</td>\n",
       "      <td>2.297348</td>\n",
       "      <td>10.377292</td>\n",
       "      <td>50.581288</td>\n",
       "      <td>1991.097723</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>AI</th>\n",
       "      <td>368.0</td>\n",
       "      <td>4791.736646</td>\n",
       "      <td>974.560569</td>\n",
       "      <td>1981.177309</td>\n",
       "      <td>4110.728374</td>\n",
       "      <td>4713.325533</td>\n",
       "      <td>5464.043562</td>\n",
       "      <td>7561.250336</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "            count         mean         std          min          25%  \\\n",
       "Unnamed: 0  368.0   293.260870  169.058258     0.000000   150.500000   \n",
       "X           368.0   499.565217  289.770794     0.000000   240.000000   \n",
       "Y           368.0   520.644022  277.412187     9.000000   269.000000   \n",
       "Facies      368.0     0.597826    0.491004     0.000000     0.000000   \n",
       "Porosity    368.0     0.127026    0.030642     0.041122     0.103412   \n",
       "Perm        368.0    85.617362  228.362654     0.094627     2.297348   \n",
       "AI          368.0  4791.736646  974.560569  1981.177309  4110.728374   \n",
       "\n",
       "                    50%          75%          max  \n",
       "Unnamed: 0   296.000000   439.500000   586.000000  \n",
       "X            500.000000   762.500000   990.000000  \n",
       "Y            539.000000   769.000000   999.000000  \n",
       "Facies         1.000000     1.000000     1.000000  \n",
       "Porosity       0.125842     0.148623     0.210258  \n",
       "Perm          10.377292    50.581288  1991.097723  \n",
       "AI          4713.325533  5464.043562  7561.250336  "
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df.describe().transpose()                          # summary table of sand only DataFrame statistics"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's transform the porosity and permeaiblity data to standard normal (mean = 0.0, standard deviation = 1.0, Gaussian shape). This is required for sequential Gaussian simulation (common target for our variogram models) and the Gaussian transform assists with outliers and provides more interpretable variograms. \n",
    "\n",
    "Let's look at the inputs for the GeostatsPy nscore program.  Note the output include an ndarray with the transformed values (in the same order as the input data in Dataframe 'df' and column 'vcol'), and the transformation table in original values and also in normal score values. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<function geostatspy.geostats.nscore(df, vcol, wcol=None, ismooth=False, dfsmooth=None, smcol=0, smwcol=0)>"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "geostats.nscore                                         # see the input parameters required by the nscore function"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following command will transform the Porosity and Permeabilty to standard normal. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Transform to Gaussian by Facies\n",
    "df['NPor'], tvPor, tnsPor = geostats.nscore(df, 'Porosity') # nscore transform for all facies porosity \n",
    "df['NPerm'], tvPermSand, tnsPermSand = geostats.nscore(df, 'Perm')  # nscore transform for all facies permeability"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's look at the updated DataFrame to make sure that we now have the normal score porosity and permeability."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Unnamed: 0</th>\n",
       "      <th>X</th>\n",
       "      <th>Y</th>\n",
       "      <th>Facies</th>\n",
       "      <th>Porosity</th>\n",
       "      <th>Perm</th>\n",
       "      <th>AI</th>\n",
       "      <th>NPor</th>\n",
       "      <th>NPerm</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0</td>\n",
       "      <td>100.0</td>\n",
       "      <td>900.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.101319</td>\n",
       "      <td>1.996868</td>\n",
       "      <td>5590.417154</td>\n",
       "      <td>-0.749088</td>\n",
       "      <td>-0.767247</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>1</td>\n",
       "      <td>100.0</td>\n",
       "      <td>800.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.147676</td>\n",
       "      <td>10.711789</td>\n",
       "      <td>3470.845666</td>\n",
       "      <td>0.653263</td>\n",
       "      <td>0.017030</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>2</td>\n",
       "      <td>100.0</td>\n",
       "      <td>700.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.145912</td>\n",
       "      <td>17.818143</td>\n",
       "      <td>3586.988513</td>\n",
       "      <td>0.611663</td>\n",
       "      <td>0.336607</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>3</td>\n",
       "      <td>100.0</td>\n",
       "      <td>600.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.186167</td>\n",
       "      <td>217.109365</td>\n",
       "      <td>3732.114787</td>\n",
       "      <td>1.993601</td>\n",
       "      <td>1.211919</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>4</td>\n",
       "      <td>100.0</td>\n",
       "      <td>500.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.146088</td>\n",
       "      <td>16.717367</td>\n",
       "      <td>2534.551236</td>\n",
       "      <td>0.628172</td>\n",
       "      <td>0.279461</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   Unnamed: 0      X      Y  Facies  Porosity        Perm           AI  \\\n",
       "0           0  100.0  900.0     0.0  0.101319    1.996868  5590.417154   \n",
       "1           1  100.0  800.0     1.0  0.147676   10.711789  3470.845666   \n",
       "2           2  100.0  700.0     1.0  0.145912   17.818143  3586.988513   \n",
       "3           3  100.0  600.0     1.0  0.186167  217.109365  3732.114787   \n",
       "4           4  100.0  500.0     1.0  0.146088   16.717367  2534.551236   \n",
       "\n",
       "       NPor     NPerm  \n",
       "0 -0.749088 -0.767247  \n",
       "1  0.653263  0.017030  \n",
       "2  0.611663  0.336607  \n",
       "3  1.993601  1.211919  \n",
       "4  0.628172  0.279461  "
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df.head()                                               # preview sand DataFrame with nscore transforms"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "That looks good! One way to check is to see if the relative magnitudes of the normal score transformed values match the original values.  e.g. that the normal score transform of 0.10 porosity normal score is less than the normal score transform of 0.14 porsity.  Also, the normal score transform of values close to the mean value should be close to 0.0 \n",
    "\n",
    "Let's also check the original and transformed sand and shale porosity distributions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 54720 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 4 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.subplot(221)                                        # plot original sand and shale porosity histograms\n",
    "plt.hist(df['Porosity'], facecolor='red',bins=np.linspace(0.0,0.25,1000),histtype=\"stepfilled\",alpha=0.2,density=True,cumulative=True,edgecolor='black',label='Original')\n",
    "plt.xlim([0.05,0.25]); plt.ylim([0,1.0])\n",
    "plt.xlabel('Porosity (fraction)'); plt.ylabel('Frequency'); plt.title('Porosity')\n",
    "plt.legend(loc='upper left')\n",
    "plt.grid(True)\n",
    "\n",
    "plt.subplot(222)  \n",
    "plt.hist(df['NPor'], facecolor='blue',bins=np.linspace(-3.0,3.0,1000),histtype=\"stepfilled\",alpha=0.2,density=True,cumulative=True,edgecolor='black',label = 'Trans')\n",
    "plt.xlim([-3.0,3.0]); plt.ylim([0,1.0])\n",
    "plt.xlabel('Porosity (fraction)'); plt.ylabel('Frequency'); plt.title('Nscore Porosity')\n",
    "plt.legend(loc='upper left')\n",
    "plt.grid(True)\n",
    "\n",
    "plt.subplot(223)                                        # plot nscore transformed sand and shale histograms\n",
    "plt.hist(df['Perm'], facecolor='red',bins=np.linspace(0.0,1000.0,100000),histtype=\"stepfilled\",alpha=0.2,density=True,cumulative=True,edgecolor='black',label='Original')\n",
    "plt.xlim([0.0,1000.0]); plt.ylim([0,1.0])\n",
    "plt.xlabel('Porosity (fraction)'); plt.ylabel('Frequency'); plt.title('Permeability')\n",
    "plt.legend(loc='upper left')\n",
    "plt.grid(True)\n",
    "\n",
    "plt.subplot(224)                                        # plot nscore transformed sand and shale histograms\n",
    "plt.hist(df['NPerm'], facecolor='blue',bins=np.linspace(-3.0,3.0,100000),histtype=\"stepfilled\",alpha=0.2,density=True,cumulative=True,edgecolor='black',label = 'Trans')\n",
    "plt.xlim([-3.0,3.0]); plt.ylim([0,1.0])\n",
    "plt.xlabel('Permeability (mD)'); plt.ylabel('Frequency'); plt.title('Nscore Permeability')\n",
    "plt.legend(loc='upper left')\n",
    "plt.grid(True)\n",
    "\n",
    "plt.subplots_adjust(left=0.0, bottom=0.0, right=2.0, top=2.2, wspace=0.2, hspace=0.3)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The normal score transform has correctly transformed the porosity and permeability to standard normal.\n",
    "\n",
    "#### Method \\#1: Ocular Inspection of Posted Data\n",
    "\n",
    "Data visualization is very useful to detect patterns. Our brains are very good at pattern detection. I promote quantitative methods and recognize issues with cognitive bias, but it is important to recognize the value is expert intepretation based on data visualization.\n",
    "\n",
    "* This data visualization will also be important to assist with parameter selection for the quantitative methods later.\n",
    "\n",
    "Let's plot the location maps of normal score transforms of porosity and permeability for all facies. We will also include a cross plot of the nscore permeability vs. porosity colored by facies to aid with comparison in spatial features between the porosity and permeability data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 193928 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 5 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "cmap = plt.cm.plasma                    # color map\n",
    "plt.subplot(131)\n",
    "GSLIB.locmap_st(df,'X','Y','NPor',0,1000,0,1000,-3,3,'Nscore Porosity - All Facies','X (m)','Y (m)','Nscore Porosity',cmap)\n",
    "\n",
    "plt.subplot(132)\n",
    "GSLIB.locmap_st(df,'X','Y','NPerm',0,1000,0,1000,-3,3,'Nscore Permeability - All Facies','X (m)','Y (m)','Nscore Permeability',cmap)\n",
    "\n",
    "plt.subplot(133)\n",
    "facies = df['Facies'].values +0.01\n",
    "plt.scatter(df['NPor'],df['NPerm'],c = facies,edgecolor = 'black',cmap = plt.cm.inferno)\n",
    "#plt.plot([-3,3],[-3,3],color = 'black')\n",
    "plt.xlabel(r'Nscore Porosity')\n",
    "plt.ylabel(r'Nscore Permeability')\n",
    "plt.title('Nscore Permeability vs. Porosity')\n",
    "plt.xlim([-3,3])\n",
    "plt.ylim([-3,3])\n",
    "plt.grid(True)\n",
    "\n",
    "plt.subplots_adjust(left=0.0, bottom=0.0, right=2.0, top=0.8, wspace=0.5, hspace=0.3)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What do you see?  Here's my observations:\n",
    "\n",
    "* there is a high degree of spatial agreement between porosity and permeability, this is supported by the high correlation evident in the cross plot.\n",
    "* there are no discontinuities that could suggest that facies represent a distinct change, rather the porosity and permeability seem continuous and the assigned facies are a truncation of their continous behavoir, we doing 'ok' with no facies\n",
    "* suspect a 045 azimuth major direction of continuity (up - right)\n",
    "* there may be cycles in the 135 azimuth \n",
    "* there will not likely be a nugget effect, but there is an hint of some short scale discontinuity?\n",
    "\n",
    "**Do you agree?** If you have a different observations, drop me a line at mpyrcz@austin.utexas.edu and I'll add to this lesson with credit.\n",
    "\n",
    "#### Method \\#2: Variogram Maps\n",
    "\n",
    "Let's try out variogram maps. \n",
    "\n",
    "* I realized that I had not coded variogram maps in Python, so I just did it and added it to GeostatsPy.  I have added if here temporarily to support my students completely their assignments. I'm teaching this week at a company and it is safer not to update the package until I have time to test it.\n",
    "\n",
    "The inputs include: \n",
    "\n",
    "* **input data** - DataFrame, 'df', and columns for x, y and property of interest, 'x', 'y' and 'vcol', \n",
    "* **variogram map parameters** - number of cells in each direction to search, 'nxlag', nylag', the cell size / lag distance, 'dxlag' and 'dylag'''\n",
    "* **search** - the minimum number of pairs reuqired to assign a result, 'mnpairs'\n",
    "* **normalization** - 1 for standardize variance to 1.0 and 0 for not\n",
    "\n",
    "The output is a 2D ndarray with the variogram map and the number of pairs."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 18812 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The shape of the output is (23, 23)\n"
     ]
    }
   ],
   "source": [
    "vmap, npmap = geostats.varmapv(df,'X','Y','NPor',tmin=-999,tmax=999,nxlag=11,nylag=11,dxlag=50,dylag=50,minnp=1,isill=1)\n",
    "\n",
    "plt.subplot(121)\n",
    "GSLIB.pixelplt_st(vmap,-575,575,-575,575,50.0,0,1.6,'Nscore Porosity Variogram Map','X(m)','Y(m)','Nscore Variogram',cmap)\n",
    "\n",
    "#plt.subplot(122)\n",
    "#GSLIB.pixelplt_st(npmap,-575,575,-575,575,50.0,0,900,'Nscore Porosity Variogram Map Number of Pairs','X(m)','Y(m)','Number of Pairs',cmap)\n",
    "\n",
    "plt.subplots_adjust(left=0.0, bottom=0.0, right=3.0, top=1.2, wspace=0.2, hspace=0.2)\n",
    "plt.show()\n",
    "\n",
    "print('The shape of the output is ' + str(vmap.shape))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notice that the output ndarray is 23, 23 cells? We asked for the number of cells to extend in each direction, 11 and 11 in x and y.  The map has an origin (zero distance) cell in the middle and extends 11 in both positive and negative directions.  So we have 2*$nx$ + 1, 2*$ny$ + 1 cells in the resulting variogram map and the $xmin = -1 * (nx * x_{cellsize} + \\frac{1}{2} x_{cellsize})$ and the $xmax = nx * x_{cellsize} + \\frac{1}{2} x_{cellsize}$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What do you think of this variogram map? These are my observations:\n",
    "\n",
    "* major continuity direction is at azimuth 045\n",
    "* there is a high degree of geometric anisotropy\n",
    "* there is cyclicity in the 135 direction \n",
    "* there may be some cyclicity in the 045 direction\n",
    "\n",
    "From this variogram map we can immediately see directionality in our spatial data."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Method \\#3: Experimental Variograms\n",
    "\n",
    "Another method for exploring spatial data directionality is the calculation of multiple experimental variograms for a variety of directions.\n",
    "\n",
    "We can use the location maps to help determine good variogram calculation parameters.\n",
    "\n",
    "```p\n",
    "tmin = -9999.; tmax = 9999.; \n",
    "lag_dist = 100.0; lag_tol = 50.0; nlag = 7; bandh = 9999.9; azi = azi; atol = 22.5; isill = 1\n",
    "```\n",
    "* **tmin**, **tmax** are trimming limits - set to have no impact, no need to filter the data\n",
    "* **lag_dist**, **lag_tol** are the lag distance, lag tolerance - set based on the common data spacing (100m) and tolerance as 100% of lag distance for additonal smoothing\n",
    "* **nlag** is number of lags - set to extend just past 50 of the data extent\n",
    "* **bandh** is the horizontal band width - set to have no effect\n",
    "* **azi** is the azimuth -  it has not effect since we set atol, the azimuth tolerance, to 90.0\n",
    "* **isill** is a boolean to standardize the distribution to a variance of 1 - it has no effect since the nscore transform sets the variance to 1.0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "tmin = -9999.; tmax = 9999.                             # no trimming \n",
    "lag_dist = 100.0; lag_tol = 100.0; nlag = 8;            # maximum lag is 700m and tolerance > 1/2 lag distance for smoothing\n",
    "bandh = 9999.9; atol = 22.5                             # no bandwidth, directional variograms\n",
    "isill = 1                                               # standardize sill\n",
    "azi_mat = [0,22.5,45,67.5,90,112.5,135,157.5]           # directions in azimuth to consider"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's try running these variograms and visualizing them on separate plots.  I'll demonstrate a method to promgramtically loop over each direction for efficiency (and code brevity).\n",
    "\n",
    "* we have the direction in the list called 'azi_mat'\n",
    "* we use the command:\n",
    "```p\n",
    "for iazi in range(0,len(azi_mat)): \n",
    "```\n",
    "to loop over all the elements in the list with index 'iazi'\n",
    "\n",
    "* we run the variogram calculation and store the reuslts in 2D arrays, iy is direction, ix is the lag\n",
    "* we use subplots with the 'iazi' index to add each plot\n",
    "\n",
    "```p\n",
    "    plt.subplot(4,2,iazi+1)\n",
    "```\n",
    "we add one because the plot index must be $1,\\ldots,n$, but arrays / list index as $0,\\ldots,n-1$ in Python."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 81060 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 8 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Arrays to store the results\n",
    "lag = np.zeros((len(azi_mat),nlag+2)); gamma = np.zeros((len(azi_mat),nlag+2)); npp = np.zeros((len(azi_mat),nlag+2));\n",
    "\n",
    "for iazi in range(0,len(azi_mat)):                      # Loop over all directions\n",
    "    lag[iazi,:], gamma[iazi,:], npp[iazi,:] = geostats.gamv(df,\"X\",\"Y\",\"NPor\",tmin,tmax,lag_dist,lag_tol,nlag,azi_mat[iazi],atol,bandh,isill)\n",
    "    plt.subplot(4,2,iazi+1)\n",
    "    plt.plot(lag[iazi,:],gamma[iazi,:],'x',color = 'black',label = 'Azimuth ' +str(azi_mat[iazi]))\n",
    "    plt.plot([0,2000],[1.0,1.0],color = 'black')\n",
    "    plt.xlabel(r'Lag Distance $\\bf(h)$, (m)')\n",
    "    plt.ylabel(r'$\\gamma \\bf(h)$')\n",
    "    plt.title('Directional NSCORE Porosity Variogram')\n",
    "    plt.xlim([0,700])\n",
    "    plt.ylim([0,1.8])\n",
    "    plt.legend(loc='upper left')\n",
    "    plt.grid(True)\n",
    "\n",
    "plt.subplots_adjust(left=0.0, bottom=0.0, right=2.0, top=4.2, wspace=0.2, hspace=0.3)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The directional variograms provide a very clear image of directionality. The ranges vary from 300m, 500m to zonal anisotropy, to 500m, 350m, 280m, 250m and finally 280m.  We are observing the actually spatial continuity ellipse by exploring a variety of directions! \n",
    "\n",
    "* We can observe that Azimuth 045 is the major direction and Azimuth 135 is the minor direction.\n",
    "\n",
    "This is a very powerful tool for exploring directionality in spatial datasets.\n",
    "\n",
    "#### Variogram Modeling\n",
    "\n",
    "From above we can identify 045 as the major direction and 135 as the minor direction. We must build a single 2D nested variogram model based on the experiential variograms in each 045 and 135 directions.  First, let's look at these two experimental variograms again.\n",
    "\n",
    "Below edit the following code to select the major and minor directions from the plots above.  \n",
    "\n",
    "```python\n",
    "# Select the plot above for the major and minor\n",
    "imajor = 2\n",
    "iminor = 6\n",
    "```\n",
    "\n",
    "Note the first plot is 0 and then step through $0, 1, \\ldots, n$ where $n$ is the number of directional variograms calculated above.  For the provided example we selected the plots, 3rd (045 Azimuth) for the major direction and the 7th (135 Azimuth) for the minor direction."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Major direction is 45 azimuth.\n",
      "Minor direction is 135 azimuth.\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 24516 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Select the plot above for the major and minor\n",
    "imajor = 2\n",
    "iminor = 6\n",
    "\n",
    "print('Major direction is ' + str(azi_mat[imajor]) + ' azimuth.')\n",
    "print('Minor direction is ' + str(azi_mat[iminor]) + ' azimuth.')\n",
    "\n",
    "if not abs(azi_mat[imajor] - azi_mat[iminor]) == 90.0:\n",
    "    print('Major and minor directions must be orthogonal to each other.')\n",
    "    sys.exit()\n",
    "\n",
    "plt.subplot(1,2,1)\n",
    "plt.plot(lag[imajor,:],gamma[imajor,:],'x',color = 'black',label = 'Azimuth ' + str(azi_mat[imajor]))\n",
    "plt.plot([0,2000],[1.0,1.0],color = 'black')\n",
    "plt.xlabel(r'Lag Distance $\\bf(h)$, (m)')\n",
    "plt.ylabel(r'$\\gamma \\bf(h)$')\n",
    "plt.title('Directional NSCORE Porosity Variogram - Major ' + str(azi_mat[imajor]) + ' Azimuth')\n",
    "plt.xlim([0,700])\n",
    "plt.ylim([0,1.8])\n",
    "plt.legend(loc='upper left')\n",
    "\n",
    "plt.subplot(1,2,2)\n",
    "plt.plot(lag[iminor,:],gamma[iminor,:],'x',color = 'black',label = 'Azimuth ' +str(azi_mat[iminor]))\n",
    "plt.plot([0,2000],[1.0,1.0],color = 'black')\n",
    "plt.xlabel(r'Lag Distance $\\bf(h)$, (m)')\n",
    "plt.ylabel(r'$\\gamma \\bf(h)$')\n",
    "plt.title('Directional NSCORE Porosity Variogram  - Minor ' + str(azi_mat[iminor]) + ' Azimuth')\n",
    "plt.xlim([0,700])\n",
    "plt.ylim([0,1.8])\n",
    "plt.legend(loc='upper left')\n",
    "\n",
    "plt.subplots_adjust(left=0.0, bottom=0.0, right=2.2, top=1.6, wspace=0.2, hspace=0.3)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We will assume these models will be applied in sequential Gaussian simulation; therefore, we must model to the sill (or the global distribution will not be reproduced over the realizations).  We will also not use cyclicity for now, as we are just getting started.  \n",
    "\n",
    "* Let's build a reasonable model to the sill.\n",
    "\n",
    "##### make_variogram function\n",
    "\n",
    "We use the make_variogram function to make a variogram model \n",
    "\n",
    "* a dictionary for compact storage of the variogram model parameters to pass into plotting (below), kriging and simulation methods \n",
    "\n",
    "The variogram model parameter include:\n",
    "\n",
    "* **nug** - nugget effect contribution to sill\n",
    "* **nst** - number of nested structures (1 or 2)\n",
    "* **it** - type for this nested structure (1 - spherical, 2 - exponential, 3 - Gaussian)\n",
    "* **cc** - contribution of each nested structure (contributions + nugget must sum to the sill)\n",
    "* **azi** - the azimuth for this nested structure of the major direction, the minor is orthogonal\n",
    "* **hmaj** - the range for this nested structure in the major direction\n",
    "* **hmin** - the range for this nested structure in the minor direction\n",
    "\n",
    "We increment it, cc, azi, hmaj, and hmin for the 1st and 2nd structures\n",
    "\n",
    "* for only 1 structure plus optional nugget, omit the 2nd structure parameters and they will default to $cc2 = 0$, no contribution to the model\n",
    "\n",
    "Here's my model:\n",
    "\n",
    "```p\n",
    "nug = 0.0; nst = 2\n",
    "it1 = 1; cc1 = 0.6; azi1 = 45; hmaj1 = 350; hmin1 = 350                # first structure\n",
    "it2 = 1; cc2 = 0.4; azi2 = 45; hmaj2 = 9999.9; hmin2 = 400             # second structure\n",
    "```\n",
    "\n",
    "Some comments on our model:\n",
    "\n",
    "* we model to the sill of 1.0, since we applied the normal score transform ($nug + cc1 + cc2 = 1.0$)\n",
    "\n",
    "* we used 2 spherical structures to capture zontal anisotropy in the 045 azimuth\n",
    "\n",
    "* since the experimental variogram exceeds the sill with trend or cyclicity we could have attempted trend modeling and then worked with the residual, but we will not do this for workflow brevity and simplicity\n",
    "\n",
    "We input these model parameters to make a variogram model dictionary with the make_variogram function as follows:\n",
    "\n",
    "```p\n",
    "vario = GSLIB.make_variogram(nug,nst,it1,cc1,azi1,hmaj1,hmin1,it2,cc2,azi2,hmaj2,hmin2)\n",
    "```\n",
    "\n",
    "##### vmodel function\n",
    "\n",
    "To plot the variogram we use the vmodel function to project the model in the major and minor directions\n",
    "\n",
    "The inputs for vmodel are:\n",
    "\n",
    "* **nlag** - the number of points along the variogram to calculate for the projection\n",
    "\n",
    "* **xlag** - the size of a lag for the projection\n",
    "\n",
    "* **azm** - the direction of the projection in azimuth (this is all we need since we are working in 2D)\n",
    "\n",
    "* **vario** - the variogram model dictionary from the make_variogram function (above)\n",
    "\n",
    "Note: this function is just for visualization by projecting the variogram model in a direction, so the convention is to use a very small **xlag** and large **nlag** for a high resolution display of the variogram model\n",
    "\n",
    "The outputs from the vmodel program include:\n",
    "\n",
    "* **index** - the lag number for the projection\n",
    "\n",
    "* **lag distance** - the distance offset along the projection (the **h** in the variogram plot)\n",
    "\n",
    "* **variogram** - the variogram value at the lag distance for the projection (the $\\gamma$(**h**) in the variogram plot)\n",
    "\n",
    "* **covariance function** - the covariance function at the lag distance for the projection (for the C(**h**) plot)\n",
    "\n",
    "* **correlogram** - the correlogram at the lag distance for the projection (for the $\\rho$(**h**) plot)\n",
    "\n",
    "We have 2 structures and no nugget effect.  We needed the 2nd structure to capture the zonal anisotropy in the 045 direction.  Let's calculate the variogram model in these directions and plot them with the experimental variograms.   "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " x,y,z offsets = 7.071067805519558,7.071067818211393\n",
      " x,y,z offsets = 7.071067830903227,-7.071067792827723\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 37276 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "nug = 0.0; nst = 2                                             # 2 nest structure variogram model parameters\n",
    "it1 = 1; cc1 = 0.6; azi1 = azi_mat[imajor]; hmaj1 = 350; hmin1 = 350\n",
    "it2 = 1; cc2 = 0.4; azi2 = azi_mat[imajor]; hmaj2 = 9999.9; hmin2 = 400\n",
    "\n",
    "vario = GSLIB.make_variogram(nug,nst,it1,cc1,azi1,hmaj1,hmin1,it2,cc2,azi2,hmaj2,hmin2) # make model object\n",
    "nlag = 70; xlag = 10;                                          # project the model in the 045 azimuth\n",
    "index_maj,h_maj,gam_maj,cov_maj,ro_maj = geostats.vmodel(nlag,xlag,azi_mat[imajor],vario)                                                     # project the model in the 135 azimuth\n",
    "index_min,h_min,gam_min,cov_min,ro_min = geostats.vmodel(nlag,xlag,azi_mat[iminor],vario)\n",
    "\n",
    "plt.subplot(1,2,1)\n",
    "plt.plot(lag[imajor,:],gamma[imajor,:],'x',color = 'black',label = 'Azimuth ' +str(azi_mat[imajor]))\n",
    "plt.plot([0,2000],[1.0,1.0],color = 'black')\n",
    "plt.plot(h_maj,gam_maj,color = 'black')\n",
    "plt.xlabel(r'Lag Distance $\\bf(h)$, (m)')\n",
    "plt.ylabel(r'$\\gamma \\bf(h)$')\n",
    "plt.title('Directional NSCORE Porosity Variogram - Major ' + str(azi_mat[imajor]) + ' Azimuth')\n",
    "plt.xlim([0,700])\n",
    "plt.ylim([0,1.8])\n",
    "plt.legend(loc='upper left')\n",
    "\n",
    "plt.subplot(1,2,2)\n",
    "plt.plot(lag[iminor,:],gamma[iminor,:],'x',color = 'black',label = 'Azimuth ' +str(azi_mat[6]))\n",
    "plt.plot([0,2000],[1.0,1.0],color = 'black')\n",
    "plt.plot(h_min,gam_min,color = 'black')\n",
    "plt.xlabel(r'Lag Distance $\\bf(h)$, (m)')\n",
    "plt.ylabel(r'$\\gamma \\bf(h)$')\n",
    "plt.title('Directional NSCORE Porosity Variogram  - Minor ' + str(azi_mat[iminor]) + ' Azimuth')\n",
    "plt.xlim([0,700])\n",
    "plt.ylim([0,1.8])\n",
    "plt.legend(loc='upper left')\n",
    "\n",
    "plt.subplots_adjust(left=0.0, bottom=0.0, right=2.2, top=1.6, wspace=0.2, hspace=0.3)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Interactive Variogram Modeling\n",
    "\n",
    "The following code uses the ipywidgets and matplotlib packages to build a method for interactive variogram modeling."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "# interactive calculation of the sample set (control of source parametric distribution and number of samples)\n",
    "l = widgets.Text(value='                              Variogram Modeling, Michael Pyrcz, Associate Professor, The University of Texas at Austin',layout=Layout(width='950px', height='30px'))\n",
    "nug = widgets.FloatSlider(min = 0, max = 1.0, value = 0.0, step = 0.1, description = r'$c_{nugget}$',orientation='vertical',layout=Layout(width='80px', height='200px'))\n",
    "nug.style.handle_color = 'gray'\n",
    "it1 = widgets.Dropdown(options=['Spherical', 'Exponential', 'Gaussian'],value='Spherical',\n",
    "    description=r'$Type_1$:',disabled=False,layout=Layout(width='190px', height='30px'))\n",
    "c1 = widgets.FloatSlider(min=0.0, max = 1.0, value = 0.01, description = r'$c_1$',orientation='vertical',layout=Layout(width='80px', height='200px'))\n",
    "c1.style.handle_color = 'blue'\n",
    "hmaj1 = widgets.FloatSlider(min=0.01, max = 10000.0, value = 0.01, step = 100.0, description = r'$a_{1,maj}$',orientation='vertical',layout=Layout(width='80px', height='200px'))\n",
    "hmaj1.style.handle_color = 'red'\n",
    "hmin1 = widgets.FloatSlider(min = 0.01, max = 10000.0, value = 0.01, step = 100.0, description = r'$a_{1,min}$',orientation='vertical',layout=Layout(width='80px', height='200px'))\n",
    "hmin1.style.handle_color = 'green'\n",
    "\n",
    "it2 = widgets.Dropdown(options=['Spherical', 'Exponential', 'Gaussian'],value='Spherical',\n",
    "    description=r'$Type_2$:',disabled=False,layout=Layout(width='190px', height='30px'))\n",
    "c2 = widgets.FloatSlider(min=0.0, max = 1.0, value = 0.0, description = r'$c_2$',orientation='vertical',layout=Layout(width='80px', height='200px'))\n",
    "c2.style.handle_color = 'blue'\n",
    "hmaj2 = widgets.FloatSlider(min=0.01, max = 10000.0, value = 0.01, step = 100.0, description = r'$a_{2,maj}$',orientation='vertical',layout=Layout(width='80px', height='200px'))\n",
    "hmaj2.style.handle_color = 'red'\n",
    "hmin2 = widgets.FloatSlider(min = 0.01, max = 10000.0, value = 0.01, step = 100.0, description = r'$a_{2,min}$',orientation='vertical',layout=Layout(width='80px', height='200px'))\n",
    "hmin2.style.handle_color = 'green'\n",
    "\n",
    "ui1 = widgets.HBox([nug,it1,c1,hmaj1,hmin1,it2,c2,hmaj2,hmin2],)                   # basic widget formatting   \n",
    "#ui2 = widgets.HBox([it2,c2,hmaj2,hmin2],)                   # basic widget formatting   \n",
    "ui = widgets.VBox([l,ui1],)\n",
    "\n",
    "def convert_type(it):\n",
    "    if it == 'Spherical': \n",
    "        return 1\n",
    "    elif it == 'Exponential':\n",
    "        return 2\n",
    "    else: \n",
    "        return 3\n",
    "\n",
    "def f_make(nug,it1,c1, hmaj1, hmin1, it2, c2, hmaj2, hmin2):                       # function to take parameters, make sample and plot\n",
    "    text_trap = io.StringIO()\n",
    "    sys.stdout = text_trap\n",
    "    \n",
    "    it1 = convert_type(it1); it2 = convert_type(it2)\n",
    "    if c2 > 0.0:\n",
    "        nst = 2\n",
    "    else:\n",
    "        nst = 1\n",
    "    \n",
    "    vario = GSLIB.make_variogram(nug,nst,it1,c1,azi_mat[imajor],hmaj1,hmin1,it2,c2,azi_mat[imajor],hmaj2,hmin2) # make model object\n",
    "    nlag = 70; xlag = 10;                                     \n",
    "    index_maj,h_maj,gam_maj,cov_maj,ro_maj = geostats.vmodel(nlag,xlag,azi_mat[imajor],vario)   # project the model in the major azimuth                                                  # project the model in the 135 azimuth\n",
    "    index_min,h_min,gam_min,cov_min,ro_min = geostats.vmodel(nlag,xlag,azi_mat[iminor],vario) # project the model in the minor azimuth\n",
    "    \n",
    "    plt.subplot(2,2,1)\n",
    "    plt.plot(lag[imajor,:],gamma[imajor,:],'x',color = 'black',label = 'Azimuth ' +str(azi_mat[2]))\n",
    "    plt.plot([0,2000],[1.0,1.0],color = 'black')\n",
    "    plt.plot(h_maj,gam_maj,color = 'black')\n",
    "    plt.xlabel(r'Lag Distance $\\bf(h)$, (m)')\n",
    "    plt.ylabel(r'$\\gamma \\bf(h)$')\n",
    "    plt.title('Horizontal Major Directional NSCORE Porosity Variogram - Major ' + str(azi_mat[imajor]) + 'Azimuth')\n",
    "    plt.xlim([0,700])\n",
    "    plt.ylim([0,1.8])\n",
    "    plt.legend(loc='upper left')\n",
    "    \n",
    "    plt.subplot(2,2,2)\n",
    "    plt.plot(lag[iminor,:],gamma[iminor,:],'x',color = 'black',label = 'Azimuth ' +str(azi_mat[6]))\n",
    "    plt.plot([0,2000],[1.0,1.0],color = 'black')\n",
    "    plt.plot(h_min,gam_min,color = 'black')\n",
    "    plt.xlabel(r'Lag Distance $\\bf(h)$, (m)')\n",
    "    plt.ylabel(r'$\\gamma \\bf(h)$')\n",
    "    plt.title('Horizontal Minor Directional NSCORE Porosity Variogram - Minor ' + str(azi_mat[iminor]) + 'Azimuth')\n",
    "    plt.xlim([0,700])\n",
    "    plt.ylim([0,1.8])\n",
    "    plt.legend(loc='upper left')\n",
    "    \n",
    "    plt.subplot(2,2,3)\n",
    "    plt.plot([0,2000],[1.0,1.0],color = 'black')\n",
    "    plt.plot(h_maj,gam_maj,color = 'black',label = 'Azimuth ' + str(azi_mat[imajor]))    \n",
    "    plt.plot(h_min,gam_min,color = 'black',label = 'Azimuth ' + str(azi_mat[iminor]))\n",
    "    deltas = [22.5, 45, 67.5]; \n",
    "    ndelta = len(deltas); hd = np.zeros(ndelta); gamd = np.zeros(ndelta);\n",
    "    color=iter(cm.plasma(np.linspace(0,1,ndelta)))\n",
    "    for delta in deltas:\n",
    "        index,hd,gamd,cov,ro = geostats.vmodel(nlag,xlag,azi_mat[imajor]+delta,vario);\n",
    "        c=next(color)\n",
    "        plt.plot(hd,gamd,color = c,label = 'Azimuth ' + str(azi_mat[imajor]+delta))\n",
    "    plt.xlabel(r'Lag Distance $\\bf(h)$, (m)')\n",
    "    plt.ylabel(r'$\\gamma \\bf(h)$')\n",
    "    plt.title('Interpolated Azimuth NSCORE Porosity Variogram Models')\n",
    "    plt.xlim([0,700])\n",
    "    plt.ylim([0,1.8])\n",
    "    plt.legend(loc='upper left')\n",
    "    \n",
    "    \n",
    "    plt.subplot(2,2,4)\n",
    "    GSLIB.locmap_st(df,'X','Y','NPor',0,1000,0,1000,-3,3,'Nscore Porosity - All Facies','X (m)','Y (m)','Nscore Porosity',cmap)\n",
    "\n",
    "#     plt.scatter(x1,y1,color = 'blue', edgecolors = 'black', s = sk_weights[0,0]*1000)\n",
    "#     plt.scatter(x2,y2,color = 'red', edgecolors = 'black', s = sk_weights[0,1]*1000)\n",
    "#     plt.scatter(x3,y3,color = 'green', edgecolors = 'black', s = sk_weights[0,2]*1000)\n",
    "#     scatter = plt.scatter(500,500,color = 'gray', edgecolors = 'black', s = (1-sk_std)*1000)\n",
    "    ax = plt.gca()\n",
    "    plt.xlabel('X(m)'); plt.ylabel('Y(m)')\n",
    "    plt.title('Data and Variogram Ranges for Each Structure')\n",
    "    plt.xlim([0,1000])\n",
    "    plt.ylim([0,1000])\n",
    "\n",
    "    ellipse1 = Ellipse((500, 500),width=hmin1*2.0,height=hmaj1*2.0,angle = 360-azi_mat[imajor],facecolor='orange',alpha = 0.3,edgecolor='black',label='Structure 1')\n",
    "    ellipse2 = Ellipse((500, 500),width=hmin2*2.0,height=hmaj2*2.0,angle = 360-azi_mat[imajor],facecolor='red',alpha = 0.3,edgecolor='black',label='Structure 2')\n",
    "    ax = plt.gca()\n",
    "    ax.add_patch(ellipse1); ax.add_patch(ellipse2)\n",
    "    plt.legend()\n",
    "    \n",
    "    plt.subplots_adjust(left=0.0, bottom=0.0, right=2.0, top=2.1, wspace=0.3, hspace=0.3)\n",
    "    plt.show()\n",
    "    \n",
    "# connect the function to make the samples and plot to the widgets    \n",
    "interactive_plot = widgets.interactive_output(f_make, {'nug':nug, 'it1':it1,'c1':c1, 'hmaj1':hmaj1, 'hmin1':hmin1, 'it2':it2, 'c2':c2, 'hmaj2':hmaj2, 'hmin2':hmin2})\n",
    "interactive_plot.clear_output(wait = True)               # reduce flickering by delaying plot updating"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Interactive Nested Variogram Modeling Demostration\n",
    "\n",
    "* select the nested structures and their types, contributions and major and minor ranges \n",
    "\n",
    "#### Michael Pyrcz, Associate Professor, University of Texas at Austin \n",
    "\n",
    "##### [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig)  | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1) | [GeostatsPy](https://github.com/GeostatsGuy/GeostatsPy)\n",
    "\n",
    "### The Problem\n",
    "\n",
    "Fit a positive definite variogram model based on the addition of multiple structures each describing spatial components of the feature variance \n",
    "\n",
    "* **nug**: nugget effect\n",
    "\n",
    "* **c1 / c2**: contributions of the sill\n",
    "\n",
    "* **hmaj1 / hmaj2**: range in the major direction\n",
    "\n",
    "* **hmin1 / hmin2**: range in the minor direction"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "02c19a903ffc42baa680d24d0a44af9f",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "VBox(children=(Text(value='                              Variogram Modeling, Michael Pyrcz, Associate Professo…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "58f1276c869c44bf9f612b6beeaa8e7e",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Output(outputs=({'output_type': 'display_data', 'data': {'text/plain': '<Figure size 432x288 with 5 Axes>', 'i…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display(ui, interactive_plot)                            # display the interactive plot"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Comments\n",
    "\n",
    "This was a basic demonstration of vairogram modeling for spatial continuity analysis. Much more could be done, I have other demonstrations on the basics of working with DataFrames, ndarrays, univariate statistics, plotting data, declustering, data transformations and many other workflows available at https://github.com/GeostatsGuy/PythonNumericalDemos and https://github.com/GeostatsGuy/GeostatsPy. \n",
    "  \n",
    "#### The Author:\n",
    "\n",
    "### Michael Pyrcz, Associate Professor, University of Texas at Austin \n",
    "*Novel Data Analytics, Geostatistics and Machine Learning Subsurface Solutions*\n",
    "\n",
    "With over 17 years of experience in subsurface consulting, research and development, Michael has returned to academia driven by his passion for teaching and enthusiasm for enhancing engineers' and geoscientists' impact in subsurface resource development. \n",
    "\n",
    "For more about Michael check out these links:\n",
    "\n",
    "#### [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig)  | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1)\n",
    "\n",
    "#### Want to Work Together?\n",
    "\n",
    "I hope this content is helpful to those that want to learn more about subsurface modeling, data analytics and machine learning. Students and working professionals are welcome to participate.\n",
    "\n",
    "* Want to invite me to visit your company for training, mentoring, project review, workflow design and / or consulting? I'd be happy to drop by and work with you! \n",
    "\n",
    "* Interested in partnering, supporting my graduate student research or my Subsurface Data Analytics and Machine Learning consortium (co-PIs including Profs. Foster, Torres-Verdin and van Oort)? My research combines data analytics, stochastic modeling and machine learning theory with practice to develop novel methods and workflows to add value. We are solving challenging subsurface problems!\n",
    "\n",
    "* I can be reached at mpyrcz@austin.utexas.edu.\n",
    "\n",
    "I'm always happy to discuss,\n",
    "\n",
    "*Michael*\n",
    "\n",
    "Michael Pyrcz, Ph.D., P.Eng. Associate Professor The Hildebrand Department of Petroleum and Geosystems Engineering, Bureau of Economic Geology, The Jackson School of Geosciences, The University of Texas at Austin\n",
    "\n",
    "#### More Resources Available at: [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig)  | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1)  \n",
    "  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "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.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
