{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Basic grid generation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", "\n", "import warnings\n", "warnings.simplefilter('ignore')\n", "\n", "import numpy\n", "from matplotlib import pyplot\n", "import seaborn\n", "clear_bkgd = {'axes.facecolor':'none', 'figure.facecolor':'none'}\n", "seaborn.set(style='ticks', context='notebook', rc=clear_bkgd, color_codes=True)\n", "\n", "import pygridgen\n", "\n", "from dochelpers import plot_grid" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Two basic quantities define the domain of a grid: the boundary coordinates (x & y) and the $\\beta$ parameters.\n", "The $\\beta$ parameter describes the direction of the turning at each boundary point and the sum of all beta parameters must be 4 for a given boundary.\n", "\n", "Conceptually, the boundaries are always traversed counter clockwise.\n", "Thus, if the boundary turns in a positive direction (according to the [right-hand rule](https://en.wikipedia.org/wiki/Right-hand_rule)), it's working to close the boundary and $\\beta = +1$.\n", "Conversely, if the turning works to keep the boundary open and creates a side channel, $\\beta = -1$.\n", "Neutral points that don't actively close or open the boundary are assigned values of $0$.\n", "\n", "In other words, in a simple, concave shape have four points where $\\beta = +1$ that act to define a pseudo-rectangle of curvi-linear space.\n", "\n", "And then, obviously, you define the number of rows and columns of nodes in the grid as two-tuple called `shape`.\n", "\n", "Let's look at a simple trapzoid as the most basic of examples." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x = numpy.array([0.5, 2.0, 2.5, 1.5, 0.5])\n", "y = numpy.array([0.5, 0.5, 2.0, 2.0, 0.5])\n", "beta = numpy.array([1, 1, 1, 1, 0])\n", "rows = 10\n", "cols = 5\n", "\n", "fig, (data_ax, cell_ax) = pyplot.subplots(figsize=(10, 5), ncols=2)\n", "grid = pygridgen.Gridgen(x, y, beta, shape=(rows, cols))\n", "plot_grid(grid, data_ax=data_ax, cell_ax=cell_ax, leg_loc='upper left')\n", "fig.tight_layout()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Without dramatically changing anything, we can add a neutral point to make a pentagon." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x = numpy.array([0.5, 2.0, 3.5, 2.5, 1.5, 0.5])\n", "y = numpy.array([0.5, 0.5, 1.0, 2.0, 2.0, 0.5])\n", "beta = numpy.array([1, 1, 0, 1, 1, 0])\n", "\n", "fig, (data_ax, cell_ax) = pyplot.subplots(figsize=(10, 5), ncols=2)\n", "grid = pygridgen.Gridgen(x, y, beta, shape=(10, 10))\n", "plot_grid(grid,data_ax=data_ax, cell_ax=cell_ax, leg_loc='upper right')\n", "fig.tight_layout()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Side channels: using $\\beta = -1$\n", "If you have complex shapes with side channels, and don't define an negative turning points, you'll likely get undesired results.\n", "In the example below, the grid does not work it's way into the right side channel." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x = numpy.array([0.5, 2.0, 2.0, 3.5, 3.5, 2.0, 2.0, 0.5, 0.5])\n", "y = numpy.array([0.5, 0.5, 1.5, 1.5, 2.5, 2.5, 3.5, 3.5, 0.5])\n", "beta = numpy.array([1, 1, 0, 0, 0, 0, 1, 1, 0])\n", "\n", "fig, (data_ax, cell_ax) = pyplot.subplots(figsize=(10, 5), ncols=2, sharex=True)\n", "grid = pygridgen.Gridgen(x, y, beta, shape=(10, 10))\n", "plot_grid(grid,data_ax=data_ax, cell_ax=cell_ax, leg_loc='upper right')\n", "fig.tight_layout()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The first thing we might try, is put two of the $\\beta = +1$ values in the out corners of the side channel. But, again, this really isn't want we want" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "beta = numpy.array([1, 0, 0, 1, 1, 0, 0, 1, 0])\n", "\n", "fig, (data_ax, cell_ax) = pyplot.subplots(figsize=(10, 5), ncols=2, sharex=True)\n", "grid = pygridgen.Gridgen(x, y, beta, shape=(10, 10))\n", "plot_grid(grid,data_ax=data_ax, cell_ax=cell_ax, leg_loc='upper right')\n", "fig.tight_layout()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The trick here is to use $\\beta = -1$ to define the left channel as a side channel, which tells the grid generator to squeeze in there a little more naturally.\n", "Note that if we add to points that atre $-1$, we'll need to add others that are $+1$ to seem the domain \"square\" (i.e., $\\sum \\beta = 4$)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "beta = numpy.array([1, 1, -1, 1, 1, -1, 1, 1, 0])\n", "\n", "fig, (data_ax, cell_ax) = pyplot.subplots(figsize=(10, 5), ncols=2, sharex=True)\n", "grid = pygridgen.Gridgen(x, y, beta, shape=(10, 10))\n", "plot_grid(grid,data_ax=data_ax, cell_ax=cell_ax, leg_loc='upper right')\n", "fig.tight_layout()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There, now we have a lovely, uniform grid throughout the domain." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Using focus\n", "What if the side channel was much more narrow than the example above, but we still wanted to model it with at least 4 grid nodes in the y-direction?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x = numpy.array([0.50, 2.00, 2.00, 3.50, 3.50, 2.00, 2.00, 0.50, 0.50])\n", "y = numpy.array([0.50, 0.50, 1.75, 1.75, 2.25, 2.25, 3.50, 3.50, 0.50])\n", "beta = numpy.array([1, 1, -1, 1, 1, -1, 1, 1, 0])\n", "\n", "fig, (data_ax, cell_ax) = pyplot.subplots(figsize=(10, 5), ncols=2, sharex=True)\n", "grid = pygridgen.Gridgen(x, y, beta, shape=(10, 10))\n", "plot_grid(grid,data_ax=data_ax, cell_ax=cell_ax, leg_loc='upper right')\n", "fig.tight_layout()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One option would be to take a brute-force approach and increase the grid resolution until we have 4 nodes in the side channel." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "grid = pygridgen.Gridgen(x, y, beta, shape=(20, 10))\n", "\n", "fig, (data_ax, cell_ax) = pyplot.subplots(figsize=(10, 5), ncols=2, sharex=True)\n", "plot_grid(grid,data_ax=data_ax, cell_ax=cell_ax, leg_loc='upper right')\n", "fig.tight_layout()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For such a simple case, this would be fine.\n", "However, in more complex models, this could dramatically increase the computational burden of the model.\n", "Instead, we could make the *y-*resolution of the nodes more dense in the side channel, and more sparse else where.\n", "\n", "To accomplish this, we use a concept introduced in the `prgridgen` library called \"focus\".\n", "Focus basically applies a gaussian transformation of the coordinates of the nodes.\n", "\n", "To use focus, you first create an empty `focus` object.\n", "Then use the :meth:`~pygridgen.Focus.add_focus` method to create an area of focus.\n", "The parameters for `add_focus` are:\n", "\n", " - `pos`, `axis`: the relative position and axis at which the focus will be places\n", " - `factor`: the scaling factor to be applied to the node density\n", " - `extent`: the relative \"area of influence\" of the focus area.\n", " \n", "`pos` and `extent` are in relative grid coordinates. In other words, they range from 0 to 1. The value of `axis` is either `\"x\"` or `\"y\"`, and factor should be greater than 1 to make the nodes more dense or less than one to make them more sparse.\n", "\n", "To continue with our running example, let's focus in the points in the size channel (`pos=0.5, axis='y'`) so that we get 4 rows of nodes in there.\n", "\n", "Unrelated to focus, let's bump up the column count to 20 so that when we use use `factor < 1` in the main body, it's more obvious." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "focus = pygridgen.Focus()\n", "focus.add_focus(0.50, 'y', factor=5, extent=0.25)\n", "\n", "grid = pygridgen.Gridgen(x, y, beta, shape=(10, 20), focus=focus)\n", "\n", "fig, (data_ax, cell_ax) = pyplot.subplots(figsize=(10, 5), ncols=2, sharex=True)\n", "plot_grid(grid,data_ax=data_ax, cell_ax=cell_ax, leg_loc='upper right')\n", "fig.tight_layout()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "The interactions between `factor` and `extent` can be tricky. The example above applies a 5x focus to the middle 25% of the grid (`extent = 0.25`).\n", "However, if we instead apply the focus to 50% of the grid, we no longer achieve our goal of having 3 nodes in the side channel.\n", "\n", "`¯\\_(ツ)_/¯`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "focus = pygridgen.Focus()\n", "focus.add_focus(0.50, 'y', factor=5, extent=0.50)\n", "\n", "grid = pygridgen.Gridgen(x, y, beta, shape=(10, 20), focus=focus)\n", "\n", "fig, (data_ax, cell_ax) = pyplot.subplots(figsize=(10, 5), ncols=2, sharex=True)\n", "plot_grid(grid,data_ax=data_ax, cell_ax=cell_ax, leg_loc='upper right')\n", "fig.tight_layout()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So now, let's make the horizontal resolution of the main body more sparse by using a `factor < 1`.\n", "This will have the side-effect of make the horizontal channel more dense.\n", "\n", "Thinking through this:\n", " - we want to make the columns more dense, so `dir='x'`\n", " - in total, we have 20 *x-*nodes\n", " - the center of the main body is at about the 5th node, so `pos=5/20`\n", " - the main body is about 10 nodes wide, so `extent=10/20`.\n", " \n", "Let's try this out with a `factor=0.5` and see how things are when they're half as dense:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "focus = pygridgen.Focus()\n", "focus.add_focus(0.50, 'y', factor=5.0, extent=0.25)\n", "focus.add_focus(0.25, 'x', factor=0.25, extent=0.50)\n", "\n", "grid = pygridgen.Gridgen(x, y, beta, shape=(10, 20), focus=focus)\n", "\n", "fig, (data_ax, cell_ax) = pyplot.subplots(figsize=(10, 5), ncols=2, sharex=True)\n", "plot_grid(grid,data_ax=data_ax, cell_ax=cell_ax, leg_loc='upper right')\n", "fig.tight_layout()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Wait second, the *side channel* got less dense.\n", "What's going?\n", "\n", "Well, we avoiding this successfully so far, but it finally bit us.\n", "By default, `pygridgen` assumes that the first coordinate pair of the boundary is the \"upper left\".\n", "But in this case, it's acutally the lower left.\n", "So a few things are backards.\n", "We can deal with it in two ways.\n", "\n", "In the first, we simple move where we apply the focus to `x=15/20` instead of `5/20`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "focus = pygridgen.Focus()\n", "focus.add_focus(0.50, 'y', factor=5.0, extent=0.25)\n", "focus.add_focus(0.75, 'x', factor=0.25, extent=0.50)\n", "\n", "grid = pygridgen.Gridgen(x, y, beta, shape=(10, 20), focus=focus)\n", "\n", "fig, (data_ax, cell_ax) = pyplot.subplots(figsize=(10, 5), ncols=2, sharex=True)\n", "plot_grid(grid,data_ax=data_ax, cell_ax=cell_ax, leg_loc='upper right')\n", "fig.tight_layout()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Or, we could use the `ul_idx` parameter to tell `pygridgen` the index of the coordinate of the upper left corner our boundary.\n", "For this example, the upper left coordinates are the 8th items in their arrays, so the index is `7`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "focus = pygridgen.Focus()\n", "focus.add_focus(0.50, 'y', factor=5.0, extent=0.25)\n", "focus.add_focus(0.25, 'x', factor=0.25, extent=0.50)\n", "\n", "grid = pygridgen.Gridgen(x, y, beta, shape=(10, 20), focus=focus, ul_idx=7)\n", "\n", "fig, (data_ax, cell_ax) = pyplot.subplots(figsize=(10, 5), ncols=2, sharex=True)\n", "plot_grid(grid,data_ax=data_ax, cell_ax=cell_ax, leg_loc='upper right')\n", "fig.tight_layout()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### More on the upper left index parameter\n", "\n", "This upper left index thing can really twist your mind around with more complex shapes, so let's use the simplest case to demostrate it's effects.\n", "Note that `gridgen` uses 1-based indexing for this paraneter, so use `ul_idx = 1` if your first point is the upper left corner.\n", "\n", "We'll use a simple rectangle with a 10 x 21 grid, with focus at `x = 7/21` and `y = 7/10`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x = numpy.array([0.5, 3.5, 3.5, 0.5, 0.5])\n", "y = numpy.array([0.5, 0.5, 2, 2, 0.5])\n", "beta = numpy.array([1, 1, 1, 1, 0])\n", "\n", "focus = pygridgen.Focus()\n", "focus.add_focus(0.33, 'y', factor=3, extent=0.25)\n", "focus.add_focus(0.70, 'x', factor=5, extent=0.10)\n", "\n", "fig, axes = pyplot.subplots(nrows=4, ncols=2, figsize=(10, 10), sharex=True)\n", "for n, axrow in enumerate(axes, 1):\n", " grid = pygridgen.Gridgen(x, y, beta, shape=(10, 20), focus=focus, ul_idx=n)\n", " plot_grid(grid, data_ax=axrow[0], cell_ax=axrow[1], leg_loc=(1.0, 0.5))\n", " axrow[0].set_ylabel('upper left index = {}'.format(n))\n", " \n", "fig.tight_layout()" ] } ], "metadata": { "kernelspec": { "display_name": "Python [default]", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.5" } }, "nbformat": 4, "nbformat_minor": 1 }