├── .gitignore ├── 3dGraphing.ipynb ├── Autocorrelation.ipynb ├── BayesianProbabilityBasics.ipynb ├── ComplexNumbers.ipynb ├── Cross-correlation.ipynb ├── DotProductOrthogonality.ipynb ├── DrawingLines.ipynb ├── EarthHorizon.ipynb ├── ExpectedValue.ipynb ├── LinearTransformations.ipynb ├── MarkovChain.ipynb ├── MonteCarloEstimation.ipynb ├── PoissonDistribution.ipynb ├── PrimeWaves.ipynb ├── README.md ├── Scratch.ipynb ├── Sudoku.ipynb ├── UnderstandingCorrelateFunction.ipynb ├── WhatIsMultiplication.ipynb ├── fixed-point-functions.ipynb ├── genetic_algorithm.ipynb ├── images ├── bull_bear_markov.png ├── complex_plane.jpg └── earth_dist.JPG ├── js ├── d3.js └── d3.min.js ├── monte_carlo_curve_area.py ├── monte_carlo_pi.py ├── nature_of_waves.ipynb └── solve-for-golden-ratio.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | 37 | .ipynb_checkpoints 38 | -------------------------------------------------------------------------------- /Autocorrelation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "Title: Fuzzy sequence matching with cross-correlation\n", 8 | "\n", 9 | "## Cross-correlation\n", 10 | "\n", 11 | "I am going to generate a \"random file\" - an array with values ranging between -1 and 1, and then randomly choose a \"random slice\" - a subarray portion from the \"random file\". I'm then going to randomly mutate some of the values in teh subarray, and see if I can still find where in the \"random file\" the \"random slice\" came from." 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 2, 17 | "metadata": { 18 | "collapsed": false 19 | }, 20 | "outputs": [], 21 | "source": [ 22 | "import random\n", 23 | "import numpy as np\n", 24 | "\n", 25 | "FILE_SIZE = 100000\n", 26 | "SLICE_SIZE = 100\n", 27 | "\n", 28 | "random_file = np.asarray([random.uniform(-1,1) for i in range(FILE_SIZE)])\n", 29 | "random_slice_index = random.randint(0, FILE_SIZE-SLICE_SIZE)\n", 30 | "random_slice = random_file[random_slice_index:random_slice_index+SLICE_SIZE]\n", 31 | "\n", 32 | "# Randomly change the sample by about 50% - simulating a 50% noise\n", 33 | "random_slice_mutated = np.asarray([i + random.uniform(-0.5,0.5) for i in random_slice])" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "metadata": {}, 39 | "source": [ 40 | "So now, we are going to try to find where in `random_file` the `random_slice_mutated` came from. We know it should be `random_slice_index`.\n", 41 | "\n", 42 | "### First, let's write our own cross-correlation function to find it." 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 3, 48 | "metadata": { 49 | "collapsed": false 50 | }, 51 | "outputs": [], 52 | "source": [ 53 | "# Coding our own cross-correlation algorithm\n", 54 | "\n", 55 | "def find_sample_index(container, sample):\n", 56 | " best_match_index = 0\n", 57 | " largest_dot_product = 0\n", 58 | " sample_size = len(sample)\n", 59 | " for index in range(len(container) - sample_size):\n", 60 | " dot = np.dot(container[index:index+sample_size], sample)\n", 61 | " if dot > largest_dot_product:\n", 62 | " best_match_index = index\n", 63 | " largest_dot_product = dot\n", 64 | " return (best_match_index, largest_dot_product)\n", 65 | " " 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": 4, 71 | "metadata": { 72 | "collapsed": false 73 | }, 74 | "outputs": [ 75 | { 76 | "data": { 77 | "text/plain": [ 78 | "(14108, 33.238798044766)" 79 | ] 80 | }, 81 | "execution_count": 4, 82 | "metadata": {}, 83 | "output_type": "execute_result" 84 | } 85 | ], 86 | "source": [ 87 | "find_sample_index(random_file, random_slice_mutated)" 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": 6, 93 | "metadata": { 94 | "collapsed": false 95 | }, 96 | "outputs": [ 97 | { 98 | "name": "stdout", 99 | "output_type": "stream", 100 | "text": [ 101 | "Actual index (should match): 14108\n" 102 | ] 103 | } 104 | ], 105 | "source": [ 106 | "print(\"Actual index (should match):\", random_slice_index)" 107 | ] 108 | }, 109 | { 110 | "cell_type": "markdown", 111 | "metadata": {}, 112 | "source": [ 113 | "### Now, let's use the `scipy.signal.correlate()` function instead of our own one.\n", 114 | "\n", 115 | "Note that the numpy implementation returns a vector of all of the correlation values, not just the largest one, so to find the largest index, we need to find the largest value." 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": 7, 121 | "metadata": { 122 | "collapsed": false 123 | }, 124 | "outputs": [ 125 | { 126 | "data": { 127 | "text/plain": [ 128 | "14108" 129 | ] 130 | }, 131 | "execution_count": 7, 132 | "metadata": {}, 133 | "output_type": "execute_result" 134 | } 135 | ], 136 | "source": [ 137 | "import scipy.signal\n", 138 | "result = scipy.signal.correlate(random_file, random_slice)\n", 139 | "max_correlation_index = np.argmax(result) - (SLICE_SIZE-1)\n", 140 | "max_correlation_index" 141 | ] 142 | }, 143 | { 144 | "cell_type": "markdown", 145 | "metadata": {}, 146 | "source": [ 147 | "### Cross-correlation algorithm" 148 | ] 149 | }, 150 | { 151 | "cell_type": "code", 152 | "execution_count": 8, 153 | "metadata": { 154 | "collapsed": false 155 | }, 156 | "outputs": [ 157 | { 158 | "data": { 159 | "text/plain": [ 160 | "array([ 3, 8, 14, 20, 26, 14, 5])" 161 | ] 162 | }, 163 | "execution_count": 8, 164 | "metadata": {}, 165 | "output_type": "execute_result" 166 | } 167 | ], 168 | "source": [ 169 | "a = np.array([1,2,3,4,5])\n", 170 | "b = np.array([1,2,3])\n", 171 | "scipy.signal.correlate(a,b)" 172 | ] 173 | }, 174 | { 175 | "cell_type": "markdown", 176 | "metadata": {}, 177 | "source": [ 178 | "For vectors:\n", 179 | "\n", 180 | "A=\n", 181 | "\n", 182 | "\n", 183 | "
12345
\n", 184 | "\n", 185 | "B=\n", 186 | "\n", 187 | "\n", 188 | "
123
\n", 189 | "\n", 190 | "The cross-correlate algorithm zero-pads each vector as such (where the blank cells represent 0):\n", 191 | "\n", 192 | "#### Step 1\n", 193 | "\n", 194 | "\n", 195 | "\n", 196 | "
12345
123
\n", 197 | "\n", 198 | " 0*1 + 0*2 + 1*3 + 2*0 + 3*0 + 4*0 + 5*0 = 3\n", 199 | "\n", 200 | "#### Step 2\n", 201 | "\n", 202 | "\n", 203 | "\n", 204 | "
12345
123
\n", 205 | "\n", 206 | " 0*1 + 1*2 + 2*3 + 3*0 + 4*0 + 5*0 = 8\n", 207 | "\n", 208 | "#### Step 3\n", 209 | "\n", 210 | "\n", 211 | "\n", 212 | "
12345
123
\n", 213 | "\n", 214 | " 1*1 + 2*2 + 3*3 + 4*0 + 5*0 = 14\n", 215 | "\n", 216 | "## ...\n", 217 | "\n", 218 | "#### Last step\n", 219 | "\n", 220 | "\n", 221 | "\n", 222 | "
12345
123
\n", 223 | "\n", 224 | "So each step consists of doing the appropriate zero-padding and taking the dot product of the 2 vectors, and then moving to the right one spot." 225 | ] 226 | }, 227 | { 228 | "cell_type": "code", 229 | "execution_count": null, 230 | "metadata": { 231 | "collapsed": false 232 | }, 233 | "outputs": [], 234 | "source": [] 235 | } 236 | ], 237 | "metadata": { 238 | "kernelspec": { 239 | "display_name": "Python 3", 240 | "language": "python", 241 | "name": "python3" 242 | }, 243 | "language_info": { 244 | "codemirror_mode": { 245 | "name": "ipython", 246 | "version": 3 247 | }, 248 | "file_extension": ".py", 249 | "mimetype": "text/x-python", 250 | "name": "python", 251 | "nbconvert_exporter": "python", 252 | "pygments_lexer": "ipython3", 253 | "version": "3.5.1" 254 | } 255 | }, 256 | "nbformat": 4, 257 | "nbformat_minor": 0 258 | } 259 | -------------------------------------------------------------------------------- /Cross-correlation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "Title: Fuzzy sequence matching with cross-correlation\n", 8 | "\n", 9 | "## Cross-correlation\n", 10 | "\n", 11 | "I am going to generate a \"random file\" - an array with values ranging between -1 and 1, and then randomly choose a \"random slice\" - a subarray portion from the \"random file\". I'm then going to randomly mutate some of the values in teh subarray, and see if I can still find where in the \"random file\" the \"random slice\" came from." 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 2, 17 | "metadata": { 18 | "collapsed": false 19 | }, 20 | "outputs": [], 21 | "source": [ 22 | "import random\n", 23 | "import numpy as np\n", 24 | "\n", 25 | "FILE_SIZE = 100000\n", 26 | "SLICE_SIZE = 100\n", 27 | "\n", 28 | "random_file = np.asarray([random.uniform(-1,1) for i in range(FILE_SIZE)])\n", 29 | "random_slice_index = random.randint(0, FILE_SIZE-SLICE_SIZE)\n", 30 | "random_slice = random_file[random_slice_index:random_slice_index+SLICE_SIZE]\n", 31 | "\n", 32 | "# Randomly change the sample by about 50% - simulating a 50% noise\n", 33 | "random_slice_mutated = np.asarray([i + random.uniform(-0.5,0.5) for i in random_slice])" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "metadata": {}, 39 | "source": [ 40 | "So now, we are going to try to find where in `random_file` the `random_slice_mutated` came from. We know it should be `random_slice_index`.\n", 41 | "\n", 42 | "### First, let's write our own cross-correlation function to find it." 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 3, 48 | "metadata": { 49 | "collapsed": false 50 | }, 51 | "outputs": [], 52 | "source": [ 53 | "# Coding our own cross-correlation algorithm\n", 54 | "\n", 55 | "def find_sample_index(container, sample):\n", 56 | " best_match_index = 0\n", 57 | " largest_dot_product = 0\n", 58 | " sample_size = len(sample)\n", 59 | " for index in range(len(container) - sample_size):\n", 60 | " dot = np.dot(container[index:index+sample_size], sample)\n", 61 | " if dot > largest_dot_product:\n", 62 | " best_match_index = index\n", 63 | " largest_dot_product = dot\n", 64 | " return (best_match_index, largest_dot_product)\n", 65 | " " 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": 4, 71 | "metadata": { 72 | "collapsed": false 73 | }, 74 | "outputs": [ 75 | { 76 | "data": { 77 | "text/plain": [ 78 | "(14108, 33.238798044766)" 79 | ] 80 | }, 81 | "execution_count": 4, 82 | "metadata": {}, 83 | "output_type": "execute_result" 84 | } 85 | ], 86 | "source": [ 87 | "find_sample_index(random_file, random_slice_mutated)" 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": 6, 93 | "metadata": { 94 | "collapsed": false 95 | }, 96 | "outputs": [ 97 | { 98 | "name": "stdout", 99 | "output_type": "stream", 100 | "text": [ 101 | "Actual index (should match): 14108\n" 102 | ] 103 | } 104 | ], 105 | "source": [ 106 | "print(\"Actual index (should match):\", random_slice_index)" 107 | ] 108 | }, 109 | { 110 | "cell_type": "markdown", 111 | "metadata": {}, 112 | "source": [ 113 | "### Now, let's use the `scipy.signal.correlate()` function instead of our own one.\n", 114 | "\n", 115 | "Note that the numpy implementation returns a vector of all of the correlation values, not just the largest one, so to find the largest index, we need to find the largest value." 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": 7, 121 | "metadata": { 122 | "collapsed": false 123 | }, 124 | "outputs": [ 125 | { 126 | "data": { 127 | "text/plain": [ 128 | "14108" 129 | ] 130 | }, 131 | "execution_count": 7, 132 | "metadata": {}, 133 | "output_type": "execute_result" 134 | } 135 | ], 136 | "source": [ 137 | "import scipy.signal\n", 138 | "result = scipy.signal.correlate(random_file, random_slice)\n", 139 | "max_correlation_index = np.argmax(result) - (SLICE_SIZE-1)\n", 140 | "max_correlation_index" 141 | ] 142 | }, 143 | { 144 | "cell_type": "markdown", 145 | "metadata": {}, 146 | "source": [ 147 | "### Cross-correlation algorithm" 148 | ] 149 | }, 150 | { 151 | "cell_type": "code", 152 | "execution_count": 8, 153 | "metadata": { 154 | "collapsed": false 155 | }, 156 | "outputs": [ 157 | { 158 | "data": { 159 | "text/plain": [ 160 | "array([ 3, 8, 14, 20, 26, 14, 5])" 161 | ] 162 | }, 163 | "execution_count": 8, 164 | "metadata": {}, 165 | "output_type": "execute_result" 166 | } 167 | ], 168 | "source": [ 169 | "a = np.array([1,2,3,4,5])\n", 170 | "b = np.array([1,2,3])\n", 171 | "scipy.signal.correlate(a,b)" 172 | ] 173 | }, 174 | { 175 | "cell_type": "markdown", 176 | "metadata": {}, 177 | "source": [ 178 | "For vectors:\n", 179 | "\n", 180 | "A=\n", 181 | "\n", 182 | "\n", 183 | "
12345
\n", 184 | "\n", 185 | "B=\n", 186 | "\n", 187 | "\n", 188 | "
123
\n", 189 | "\n", 190 | "The cross-correlate algorithm zero-pads each vector as such (where the blank cells represent 0):\n", 191 | "\n", 192 | "#### Step 1\n", 193 | "\n", 194 | "\n", 195 | "\n", 196 | "
12345
123
\n", 197 | "\n", 198 | " 0*1 + 0*2 + 1*3 + 2*0 + 3*0 + 4*0 + 5*0 = 3\n", 199 | "\n", 200 | "#### Step 2\n", 201 | "\n", 202 | "\n", 203 | "\n", 204 | "
12345
123
\n", 205 | "\n", 206 | " 0*1 + 1*2 + 2*3 + 3*0 + 4*0 + 5*0 = 8\n", 207 | "\n", 208 | "#### Step 3\n", 209 | "\n", 210 | "\n", 211 | "\n", 212 | "
12345
123
\n", 213 | "\n", 214 | " 1*1 + 2*2 + 3*3 + 4*0 + 5*0 = 14\n", 215 | "\n", 216 | "## ...\n", 217 | "\n", 218 | "#### Last step\n", 219 | "\n", 220 | "\n", 221 | "\n", 222 | "
12345
123
\n", 223 | "\n", 224 | "So each step consists of doing the appropriate zero-padding and taking the dot product of the 2 vectors, and then moving to the right one spot." 225 | ] 226 | }, 227 | { 228 | "cell_type": "code", 229 | "execution_count": null, 230 | "metadata": { 231 | "collapsed": false 232 | }, 233 | "outputs": [], 234 | "source": [] 235 | } 236 | ], 237 | "metadata": { 238 | "kernelspec": { 239 | "display_name": "Python 3", 240 | "language": "python", 241 | "name": "python3" 242 | }, 243 | "language_info": { 244 | "codemirror_mode": { 245 | "name": "ipython", 246 | "version": 3 247 | }, 248 | "file_extension": ".py", 249 | "mimetype": "text/x-python", 250 | "name": "python", 251 | "nbconvert_exporter": "python", 252 | "pygments_lexer": "ipython3", 253 | "version": "3.5.1" 254 | } 255 | }, 256 | "nbformat": 4, 257 | "nbformat_minor": 0 258 | } 259 | -------------------------------------------------------------------------------- /DotProductOrthogonality.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Dot Product and Orthogonality\n", 8 | "\n", 9 | "$$\\vec{A} \\cdot \\vec{B} = \\sum\\limits_{i=1}^n A_i B_i $$\n", 10 | "\n", 11 | "Conceptually, the dot product can be thought of as a measure of similarity between 2 vectors. Here's how I think of the value of the dot product:\n", 12 | "\n", 13 | "* Large **positive** values mean the vectors \"agree\" - they are very similar\n", 14 | " - This is used for finding the similarity between 2 vectors in the [Cross-correlation](http://en.wikipedia.org/wiki/Cross-correlation) algorithm.\n", 15 | "* Large **negative** values mean the vectors \"disagree\"\n", 16 | "* Value of (or near) **zero** means they don't even give answers to the same questions - they are orthogonal.\n", 17 | " - Let's explore orthogonality more below.\n" 18 | ] 19 | }, 20 | { 21 | "cell_type": "markdown", 22 | "metadata": {}, 23 | "source": [ 24 | "### Orthogonality\n", 25 | "\n", 26 | "Let's think more about what it means for vectors to be orthogonal. Geometrically, you can think of orthogonal vector being at a \"right angle\". For instance, (1,0) and (0,1) form a right angle if you plot them:" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": 1, 32 | "metadata": { 33 | "collapsed": false 34 | }, 35 | "outputs": [ 36 | { 37 | "data": { 38 | "image/png": "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\n", 39 | "text/plain": [ 40 | "" 41 | ] 42 | }, 43 | "metadata": {}, 44 | "output_type": "display_data" 45 | } 46 | ], 47 | "source": [ 48 | "%matplotlib inline\n", 49 | "import numpy as np\n", 50 | "import matplotlib.pyplot as plt\n", 51 | "import matplotlib.lines as lines\n", 52 | "\n", 53 | "fig, ax = plt.subplots()\n", 54 | "\n", 55 | "fig.set_size_inches(6,6) # Make graph square\n", 56 | "_ = plt.scatter([-0.1,1.1],[-0.1,1.1],s=0.01) # Move graph window a little left and down\n", 57 | "\n", 58 | "ax.arrow(0, 0, 1, 0, head_width=0.05, head_length=0.1, fc='k', ec='k')\n", 59 | "ax.arrow(0, 0, 0, 1, head_width=0.05, head_length=0.1, fc='k', ec='k')\n", 60 | "\n", 61 | "plt.show()" 62 | ] 63 | }, 64 | { 65 | "cell_type": "markdown", 66 | "metadata": {}, 67 | "source": [ 68 | "### Dot product and vector projection\n", 69 | "\n", 70 | "The dot product is used in the calculations for [vector projection](http://en.wikipedia.org/wiki/Vector_projection). When thinking about a vector being projected on to another, it makes sense that projecting one vector onto another which is orthogonal to it would result in a value of 0. Imagine a light shining directly down from above the vertical arrow in the graph above - it would create no shadow. But if the vectors were pointing in almost the same direction, the shadow would almost cover the bottom vector. And if the top vector were at an angle almost 180 degrees from the bottom vector, the shadow would look like an arrow pointing in the opposite direction of the bottom vector." 71 | ] 72 | }, 73 | { 74 | "cell_type": "markdown", 75 | "metadata": {}, 76 | "source": [ 77 | "## Waves of different frequencies are orthoganal\n", 78 | "\n", 79 | "**Ya! It's pretty crazy - waves of different frequencies are orthogonal to each other**. This means you can actually think of different frequencies as different dimensions.\n", 80 | "\n", 81 | "Consider the 2 waves, one has a frequency of 1Hz, and the othe has a frequency of 2Hz." 82 | ] 83 | }, 84 | { 85 | "cell_type": "code", 86 | "execution_count": 2, 87 | "metadata": { 88 | "collapsed": false 89 | }, 90 | "outputs": [ 91 | { 92 | "data": { 93 | "image/png": "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\nAABJRU5ErkJggg==\n", 94 | "text/plain": [ 95 | "" 96 | ] 97 | }, 98 | "metadata": {}, 99 | "output_type": "display_data" 100 | } 101 | ], 102 | "source": [ 103 | "x = np.linspace(0, 2, 1000) # In seconds\n", 104 | "wave1 = np.sin(1 * 2*np.pi*x) # 1Hz wave\n", 105 | "wave2 = np.sin(2 * 2*np.pi*x) # 2Hz wave\n", 106 | "\n", 107 | "_ = plt.plot(x, wave1, 'blue', x, wave2, 'red')" 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "metadata": {}, 113 | "source": [ 114 | "Let's now look at each of these waves as vectors (the vectors are simply values of the waves at various points in time) and take their dot product; it should come out to about 0." 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": 3, 120 | "metadata": { 121 | "collapsed": false 122 | }, 123 | "outputs": [ 124 | { 125 | "data": { 126 | "text/plain": [ 127 | "1.2447887772631949e-14" 128 | ] 129 | }, 130 | "execution_count": 3, 131 | "metadata": {}, 132 | "output_type": "execute_result" 133 | } 134 | ], 135 | "source": [ 136 | "np.dot(wave1, wave2)" 137 | ] 138 | }, 139 | { 140 | "cell_type": "markdown", 141 | "metadata": {}, 142 | "source": [ 143 | "As you can see, the value is very near **zero**. This phenomenon is related to how the [Fourier Transform](http://en.wikipedia.org/wiki/Fourier_transform) can find the power in a given wavelength by multiplying the whole complex wave by that wavelength." 144 | ] 145 | } 146 | ], 147 | "metadata": { 148 | "kernelspec": { 149 | "display_name": "Python 3", 150 | "language": "python", 151 | "name": "python3" 152 | }, 153 | "language_info": { 154 | "codemirror_mode": { 155 | "name": "ipython", 156 | "version": 3 157 | }, 158 | "file_extension": ".py", 159 | "mimetype": "text/x-python", 160 | "name": "python", 161 | "nbconvert_exporter": "python", 162 | "pygments_lexer": "ipython3", 163 | "version": "3.5.1" 164 | } 165 | }, 166 | "nbformat": 4, 167 | "nbformat_minor": 0 168 | } 169 | -------------------------------------------------------------------------------- /DrawingLines.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [ 10 | { 11 | "data": { 12 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAbkAAAGoCAYAAADb3psWAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAE+pJREFUeJzt3GuM5Xd93/HP197SqgFZNShOWWPS0lBXiEujxHUFqg5x\nWxZSyYgHDXZFCxWRpeI0Uh/gUhV5KzUieVCJ0pREi1wUKiErgkghLS5OiafIKgZHwjYJu76QyNjL\nJYIQqiIh7VrfPphhO2zmxpyzMzvffb2k0Z7Lb/7/308zPm//zmWquwMAE1112BMAgEtF5AAYS+QA\nGEvkABhL5AAYS+QAGGslkauqe6rqG1X12Db3315Vj258PVhVr1zFeQFgJ6vayX04yRt2uP+Pkvy9\n7n51kn+f5EMrOi8AbOvYKg7S3Q9W1Ut3uP+hTVcfSnJ8FecFgJ0cxmty70xy3yGcF4ArzEp2cntV\nVa9P8o4krzvI8wJwZTqwyFXVq5KcSnKiu7+9wzh/TBOAH9DdtZ/vW+XTlbXx9efvqLohyceTvK27\nv7zbgbp75Nfdd9996HOwPuuzvnlfk9fWvdy+ZyU7uar6aJJFkhdW1VeS3J3keUm6u08leW+Sa5N8\nsKoqybnuvmkV5waA7azq3ZW373L/zyf5+VWcCwD2yl88OUCLxeKwp3BJWd/RZn1H1+S1LauWfb5z\n1aqqL7c5AXB4qip9GbzxBAAuKyIHwFgiB8BYIgfAWCIHwFgiB8BYIgfAWCIHwFgiB8BYIgfAWCIH\nwFgiB8BYIgfAWCIHwFgiB8BYIgfAWCIHwFgiB8BYIgfAWCIHwFgiB8BYIgfAWCIHwFgiB8BYIgfA\nWCIHwFgiB8BYIgfAWCIHwFgiB8BYIgfAWCIHwFgiB8BYIgfAWCIHwFgiB8BYIgfAWCIHwFgiB8BY\nIgfAWCIHwFgiB8BYIgfAWCIHwFgiB8BYK4lcVd1TVd+oqsd2GPOBqnqyqh6pqtes4rwAsJNV7eQ+\nnOQN291ZVW9M8rLu/okkdyT59RWdFwC2tZLIdfeDSb69w5Bbk3xkY+znklxTVdet4twAsJ2Dek3u\neJJnNl0/u3EbAFwy3nhyUKrWvwA4MMcO6Dxnk7xk0/XrN27b0smTJy9cXiwWWSwWl2peAFxm1tbW\nsra2tpJjVXev5kBVP57kd7r7lVvc96Yk7+run62qm5O8v7tv3uY4vao5XVa+v4ubuDaAS6iq0t37\neipsJTu5qvpokkWSF1bVV5LcneR5Sbq7T3X3J6vqTVX1VJLvJnnHKs4LADtZ2U5uVezkANhsmZ2c\nN54AMJbIATCWyAEwlsgBMJbIATCWyAEwlsgBMJbIATCWyAEwlsgBMJbIATCWyAEwlsgBMJbIATCW\nyAEwlsgBMJbIATCWyAEwlsgBMJbIATCWyAEwlsgBMJbIATCWyAEwlsgBMJbIATCWyAEwlsgBMJbI\nATCWyAEwlsgBMJbIATCWyAEwlsgBMJbIATCWyAEwlsgBMJbIATCWyAEwlsgBMJbIATCWyAEwlsgB\nMJbIATCWyAEwlsgBMJbIATDWSiJXVSeq6kxVPVFVd21x/wur6r6qeqSqvlhVb1/FeQFgJ9Xdyx2g\n6qokTyS5JclXkzyc5K3dfWbTmLuT/KXufk9VvSjJ40mu6+7zWxyvl53TZalq/d+JawO4hKoq3V37\n+d5V7ORuSvJkdz/d3eeS3Jvk1ovGfD3JCzYuvyDJt7YKHACs0rEVHON4kmc2XX826+Hb7ENJPl1V\nX03y/CQ/t4LzAsCODuqNJ+9J8mh3vzjJ307yn6vq+Qd0bgCuUKvYyZ1NcsOm69dv3LbZa5P8UpJ0\n95er6o+T3Jjk97c64MmTJy9cXiwWWSwWK5gmAEfB2tpa1tbWVnKsVbzx5Oqsv5HkliRfS/L5JLd1\n9+lNY/5Dkv/T3f+uqq7Letxe3d1/usXxvPEEgAuWeePJ0ju57n6uqu5Mcn/Wn/68p7tPV9Ud63f3\nqSTvS/Lhqno0SSV591aBA4BVWnont2p2cgBsdtgfIQCAy5LIATCWyAEwlsgBMJbIATCWyAEwlsgB\nMJbIATCWyAEwlsgBMJbIATCWyAEwlsgBMJbIATCWyAEwlsgBMJbIATCWyAEwlsgBMJbIATCWyAEw\nlsgBMJbIATCWyAEwlsgBMJbIATCWyAEwlsgBMJbIATCWyAEwlsgBMJbIATCWyAEwlsgBMJbIATCW\nyAEwlsgBMJbIATCWyAEwlsgBMJbIATCWyAEwlsgBMJbIATCWyAEwlsgBMJbIATDWSiJXVSeq6kxV\nPVFVd20zZlFVX6iqP6iqB1ZxXgDYSXX3cgeouirJE0luSfLVJA8neWt3n9k05pok/zvJP+zus1X1\nou7+5jbH62XndFmqWv934toALqGqSnfXfr53FTu5m5I82d1Pd/e5JPcmufWiMbcn+Xh3n02S7QIH\nAKu0isgdT/LMpuvPbty22cuTXFtVD1TVw1X1thWcFwB2dOwAz/OTSX4myY8k+WxVfba7nzqg8wNw\nBVpF5M4muWHT9es3btvs2STf7O7vJfleVX0myauTbBm5kydPXri8WCyyWCxWME0AjoK1tbWsra2t\n5FireOPJ1Ukez/obT76W5PNJbuvu05vG3JjkPyU5keQvJvlckp/r7i9tcTxvPAHggmXeeLL0Tq67\nn6uqO5Pcn/XX+O7p7tNVdcf63X2qu89U1aeSPJbkuSSntgocAKzS0ju5VbOTA2Czw/4IAQBclkQO\ngLFEDoCxRA6AsUQOgLFEDoCxRA6AsUQOgLFEDoCxRA6AsUQOgLFEDoCxRA6AsUQOgLFEDoCxRA6A\nsUQOgLFEDoCxRA6AsUQOgLFEDoCxRA6AsUQOgLFEDoCxRA6AsUQOgLFEDoCxRA6AsUQOgLFEDoCx\nRA6AsUQOgLFEDoCxRA6AsUQOgLFEDoCxRA6AsUQOgLFEDoCxRA6AsUQOgLFEDoCxRA6AsUQOgLFE\nDoCxRA6AsVYSuao6UVVnquqJqrprh3E/XVXnquotqzgvAOxk6chV1VVJfjXJG5K8IsltVXXjNuN+\nOcmnlj0nAOzFKnZyNyV5sruf7u5zSe5NcusW434hyceS/MkKzgkAu1pF5I4neWbT9Wc3brugql6c\n5M3d/WtJagXnBIBdHdQbT96fZPNrdUIHwCV3bAXHOJvkhk3Xr9+4bbOfSnJvVVWSFyV5Y1Wd6+5P\nbHXAkydPXri8WCyyWCxWME0AjoK1tbWsra2t5FjV3csdoOrqJI8nuSXJ15J8Pslt3X16m/EfTvI7\n3f1b29zfy87pslQbm9eJawO4hKoq3b2vZwCX3sl193NVdWeS+7P+9Oc93X26qu5Yv7tPXfwty54T\nAPZi6Z3cqtnJAbDZMjs5f/EEgLFEDoCxRA6AsUQOgLFEDoCxRA6AsUQOgLFEDoCxRA6AsUQOgLFE\nDoCxRA6AsUQOgLFEDoCxRA6AsUQOgLFEDoCxRA6AsUQOgLFEDoCxRA6AsUQOgLFEDoCxRA6AsUQO\ngLFEDoCxRA6AsUQOgLFEDoCxRA6AsUQOgLFEDoCxRA6AsUQOgLFEDoCxRA6AsUQOgLFEDoCxRA6A\nsUQOgLFEDoCxRA6AsUQOgLFEDoCxRA6AsUQOgLFEDoCxVhK5qjpRVWeq6omqumuL+2+vqkc3vh6s\nqleu4rwAsJPq7uUOUHVVkieS3JLkq0keTvLW7j6zaczNSU5393eq6kSSk9198zbH62XndFmqWv93\n4toALqGqSnfXfr53FTu5m5I82d1Pd/e5JPcmuXXzgO5+qLu/s3H1oSTHV3BeANjRKiJ3PMkzm64/\nm50j9s4k963gvACwo2MHebKqen2SdyR53UGeF4Ar0yoidzbJDZuuX79x2w+oqlclOZXkRHd/e6cD\nnjx58sLlxWKRxWKxgmkCcBSsra1lbW1tJcdaxRtPrk7yeNbfePK1JJ9Pclt3n9405oYkn07ytu5+\naJfjeeMJABcs88aTpXdy3f1cVd2Z5P6sv8Z3T3efrqo71u/uU0nem+TaJB+sqkpyrrtvWvbcALCT\npXdyq2YnB8Bmh/0RAgC4LIkcAGOJHABjiRwAY4kcAGOJHABjiRwAY4kcAGOJHABjiRwAY4kcAGOJ\nHABjiRwAY4kcAGOJHABjiRwAY4kcAGOJHABjiRwAY4kcAGOJHABjiRwAY4kcAGOJHABjiRwAY4kc\nAGOJHABjiRwAY4kcAGOJHABjiRwAY4kcAGOJHABjiRwAY4kcAGOJHABjiRwAY4kcAGOJHABjiRwA\nY4kcAGOJHABjiRwAY4kcAGOJHABjiRwAY4kcAGOtJHJVdaKqzlTVE1V11zZjPlBVT1bVI1X1mlWc\nFwB2snTkquqqJL+a5A1JXpHktqq68aIxb0zysu7+iSR3JPn1Zc8LALtZxU7upiRPdvfT3X0uyb1J\nbr1ozK1JPpIk3f25JNdU1XUrODcAbGsVkTue5JlN15/duG2nMWe3GAMAK3XssCdwpak67BkAE3Uf\n9gwuT6uI3NkkN2y6fv3GbRePeckuYy44efLkhcuLxSKLxWLZOQJwRKytrWVtbW0lx6peMv9VdXWS\nx5PckuRrST6f5LbuPr1pzJuSvKu7f7aqbk7y/u6+eZvj9bJzAmCOqkp37+t5sKV3ct39XFXdmeT+\nrL/Gd093n66qO9bv7lPd/cmqelNVPZXku0nesex5AWA3S+/kVs1ODoDNltnJ+YsnAIwlcgCMJXIA\njCVyAIwlcgCMJXIAjCVyAIwlcgCMJXIAjCVyAIwlcgCMJXIAjCVyAIwlcgCMJXIAjCVyAIwlcgCM\nJXIAjCVyAIwlcgCMJXIAjCVyAIwlcgCMdUVH7vz584c9BQAuoSs2cufPn88DD/wvoQMYrLr7sOfw\nA6qqD2pO58+fz7Fjxw7kXADsT1Wlu2s/33vF7uSSCBzAcFd05ACYTeQAGEvkABhL5AAYS+QAGEvk\nABhL5AAYS+QAGEvkABhL5AAYS+QAGEvkABhL5AAYS+QAGEvkABhL5AAYS+QAGEvkABhL5AAYS+QA\nGGupyFXVX6mq+6vq8ar6VFVds8WY66vq96rqD6vqi1X1L5c5JwDs1bI7uX+d5H92999M8ntJ3rPF\nmPNJ/lV3vyLJ303yrqq6ccnzHklra2uHPYVLyvqONus7uiavbVnLRu7WJL+xcfk3krz54gHd/fXu\nfmTj8v9NcjrJ8SXPeyRN/0W0vqPN+o6uyWtb1rKR+9Hu/kayHrMkP7rT4Kr68SSvSfK5Jc8LALs6\nttuAqvrdJNdtvilJJ/m3WwzvHY7z/CQfS/KLGzs6ALikqnvbLu3+zVWnkyy6+xtV9WNJHujuv7XF\nuGNJ/luS+7r7P+5yzP1PCICRurv283277uR28Ykkb0/yK0n+WZLf3mbcf0nypd0Cl+x/IQBwsWV3\nctcm+c0kL0nydJJ/3N1/VlV/NcmHuvsfVdVrk3wmyRez/nRmJ/k33f0/lp49AOxgqcgBwOXsUP/i\nydQPk1fViao6U1VPVNVd24z5QFU9WVWPVNVrDnqOy9htfVV1e1U9uvH1YFW98jDmuV97+fltjPvp\nqjpXVW85yPktY4+/m4uq+kJV/UFVPXDQc1zGHn43X1hV9238d/fFqnr7IUxz36rqnqr6RlU9tsOY\nI/nYstva9v240t2H9pX11/LevXH5riS/vMWYH0vymo3Lz0/yeJIbD3Peu6zpqiRPJXlpkr+Q5JGL\n55vkjUn++8blv5PkocOe94rXd3OSazYun5i2vk3jPp31N1S95bDnvcKf3TVJ/jDJ8Y3rLzrsea94\nfXcned/315bkW0mOHfbcf4g1vi7rH8N6bJv7j/Jjy25r29fjymH/7cqJHya/KcmT3f10d59Lcm/W\n17nZrUk+kiTd/bkk11TVdTkadl1fdz/U3d/ZuPpQLu+f18X28vNLkl/I+kdi/uQgJ7ekvazt9iQf\n7+6zSdLd3zzgOS5jL+v7epIXbFx+QZJvdff5A5zjUrr7wSTf3mHIkX1s2W1t+31cOezITfww+fEk\nz2y6/mz+/A/j4jFntxhzudrL+jZ7Z5L7LumMVmvX9VXVi5O8ubt/LeufGz0q9vKze3mSa6vqgap6\nuKredmCzW95e1vehJK+oqq8meTTJLx7Q3A7KUX5s+WHs+XFl2Y8Q7MqHya9cVfX6JO/I+tMQk7w/\n60+vf99RCt1ujiX5ySQ/k+RHkny2qj7b3U8d7rRW5j1JHu3u11fVy5L8blW9ymPK0fHDPq5c8sh1\n9z/Y7r6NFxmv6///YfItn/rZ+DD5x5L81+7e7rN4l4uzSW7YdP36jdsuHvOSXcZcrvayvlTVq5Kc\nSnKiu3d6euVys5f1/VSSe6uqsv66zhur6lx3f+KA5rhfe1nbs0m+2d3fS/K9qvpMkldn/bWuy91e\n1vfaJL+UJN395ar64yQ3Jvn9A5nhpXeUH1t2tZ/HlcN+uvL7HyZPVvRh8svAw0n+RlW9tKqel+St\nWV/nZp9I8k+TpKpuTvJn33/a9gjYdX1VdUOSjyd5W3d/+RDmuIxd19fdf33j669l/X++/sURCFyy\nt9/N307yuqq6uqr+ctbfvHD6gOe5X3tZ3+kkfz9JNl6renmSPzrQWS6vsv2zB0f5sSXZYW37fVy5\n5Du5XfxKkt+sqn+ejQ+TJ8kWHyb/J0m+WFVfyGX+YfLufq6q7kxyf9b/J+Ke7j5dVXes392nuvuT\nVfWmqnoqyXezvvU+EvayviTvTXJtkg9u7HbOdfdNhzfrvdvj+n7gWw58kvu0x9/NM1X1qSSPJXku\nyanu/tIhTnvP9vize1+SD1fVo1l/MH13d//p4c36h1NVH02ySPLCqvpK1t8t+rwMeGzZbW3Z5+OK\nD4MDMNZhP10JAJeMyAEwlsgBMJbIATCWyAEwlsgBMJbIATCWyAEw1v8DMFJViSCICJQAAAAASUVO\nRK5CYII=\n", 13 | "text/plain": [ 14 | "" 15 | ] 16 | }, 17 | "metadata": {}, 18 | "output_type": "display_data" 19 | } 20 | ], 21 | "source": [ 22 | "%matplotlib inline\n", 23 | "import matplotlib.pyplot as plt\n", 24 | "import matplotlib.lines as lines\n", 25 | "\n", 26 | "fig, ax = plt.subplots()\n", 27 | "\n", 28 | "fig.set_size_inches(7,7) # Make graph square\n", 29 | "plt.scatter([-0.1],[-0.1],s=0.01) # Move graph window a little left and down\n", 30 | "\n", 31 | "line1 = [(0,0), (1,0)]\n", 32 | "line2 = [(0,0), (0,1)]\n", 33 | "\n", 34 | "# Note that the Line2D takes a list of x values and a list of y values,\n", 35 | "# not 2 points as one might expect. So we have to convert our points\n", 36 | "# an x-list and a y-list.\n", 37 | "(line1_xs, line1_ys) = zip(*line1)\n", 38 | "(line2_xs, line2_ys) = zip(*line2)\n", 39 | "\n", 40 | "ax.add_line(lines.Line2D(line1_xs, line1_ys, linewidth=2, color='blue'))\n", 41 | "ax.add_line(lines.Line2D(line2_xs, line2_ys, linewidth=2, color='red'))\n", 42 | "plt.plot()\n", 43 | "plt.show()" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": 1, 49 | "metadata": { 50 | "collapsed": false 51 | }, 52 | "outputs": [], 53 | "source": [] 54 | } 55 | ], 56 | "metadata": { 57 | "kernelspec": { 58 | "display_name": "Python 3", 59 | "language": "python", 60 | "name": "python3" 61 | }, 62 | "language_info": { 63 | "codemirror_mode": { 64 | "name": "ipython", 65 | "version": 3 66 | }, 67 | "file_extension": ".py", 68 | "mimetype": "text/x-python", 69 | "name": "python", 70 | "nbconvert_exporter": "python", 71 | "pygments_lexer": "ipython3", 72 | "version": "3.5.1" 73 | } 74 | }, 75 | "nbformat": 4, 76 | "nbformat_minor": 0 77 | } 78 | -------------------------------------------------------------------------------- /EarthHorizon.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Earth Horizon calculation" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "![earth math graph](images/earth_dist.JPG)" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 49, 20 | "metadata": { 21 | "collapsed": true 22 | }, 23 | "outputs": [], 24 | "source": [ 25 | "import math\n", 26 | "\n", 27 | "earth_radius = 3959 # mi\n", 28 | "earth_circumference = earth_radius * 2 * math.pi # 24875 mi\n", 29 | "feet_per_mile = 5280" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": 50, 35 | "metadata": { 36 | "collapsed": true 37 | }, 38 | "outputs": [], 39 | "source": [ 40 | "def min_visible_height(arc_distance):\n", 41 | " # Find angle in radians by dividing arc distance by circumference and mult by 2pi\n", 42 | " angle = (arc_distance / earth_circumference) * 2 * math.pi\n", 43 | " hyp = earth_radius / math.cos(angle)\n", 44 | " return (hyp - earth_radius) * feet_per_mile" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": 52, 50 | "metadata": { 51 | "collapsed": false 52 | }, 53 | "outputs": [ 54 | { 55 | "data": { 56 | "text/plain": [ 57 | "1734.5579244608962" 58 | ] 59 | }, 60 | "execution_count": 52, 61 | "metadata": {}, 62 | "output_type": "execute_result" 63 | } 64 | ], 65 | "source": [ 66 | "# Min visible height (above center of earth) visible from 51 miles away is 1735 feet\n", 67 | "min_visible_height(51)" 68 | ] 69 | }, 70 | { 71 | "cell_type": "markdown", 72 | "metadata": {}, 73 | "source": [ 74 | "So at 51 miles, you should be able to see something about 1735 feet high - roughly the height of the sears tower, assuming your eyes are at the level of the lake. But, nobody is looking from the level of the lake.\n", 75 | "\n", 76 | "\"To compute the greatest distance at which an observer can see the top of an object above the horizon, compute the distance to the horizon for a hypothetical observer on top of that object, and add it to the real observer's distance to the horizon. For example, for an observer with a height of 1.70 m standing on the ground, the horizon is 4.65 km away. For a tower with a height of 100 m, the horizon distance is 35.7 km. Thus an observer on a beach can see the top of the tower as long as it is not more than 40.35 km away.\" (https://en.wikipedia.org/wiki/Horizon)\n", 77 | "\n", 78 | "So say there is someone standing where on a boat where their eyes are 6 feet above the water. How far away could they see the top of the sears tower" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": 53, 84 | "metadata": { 85 | "collapsed": false 86 | }, 87 | "outputs": [ 88 | { 89 | "data": { 90 | "text/plain": [ 91 | "1734.5579244608962" 92 | ] 93 | }, 94 | "execution_count": 53, 95 | "metadata": {}, 96 | "output_type": "execute_result" 97 | } 98 | ], 99 | "source": [ 100 | "min_visible_height(51)" 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": 55, 106 | "metadata": { 107 | "collapsed": false 108 | }, 109 | "outputs": [ 110 | { 111 | "data": { 112 | "text/plain": [ 113 | "6.001516969190561" 114 | ] 115 | }, 116 | "execution_count": 55, 117 | "metadata": {}, 118 | "output_type": "execute_result" 119 | } 120 | ], 121 | "source": [ 122 | "min_visible_height(3)" 123 | ] 124 | }, 125 | { 126 | "cell_type": "markdown", 127 | "metadata": {}, 128 | "source": [ 129 | "Basically, they could see 51 + 3 miles away" 130 | ] 131 | }, 132 | { 133 | "cell_type": "markdown", 134 | "metadata": { 135 | "collapsed": true 136 | }, 137 | "source": [ 138 | "Also, the [Mirage](https://en.wikipedia.org/wiki/Mirage) effect can also change this: \n", 139 | "\n", 140 | "Double-check: according to https://en.wikipedia.org/wiki/Horizon:\n", 141 | "\n", 142 | "For an observer standing at the top of the Burj Khalifa (828 metres (2,717 ft) in height), the horizon is at a distance of 103 kilometres (64 mi)." 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": 44, 148 | "metadata": { 149 | "collapsed": false 150 | }, 151 | "outputs": [ 152 | { 153 | "data": { 154 | "text/plain": [ 155 | "0.5173586827822874" 156 | ] 157 | }, 158 | "execution_count": 44, 159 | "metadata": {}, 160 | "output_type": "execute_result" 161 | } 162 | ], 163 | "source": [ 164 | "# Check Burj Khalifa distance\n", 165 | "min_visible_height(64)" 166 | ] 167 | }, 168 | { 169 | "cell_type": "markdown", 170 | "metadata": {}, 171 | "source": [ 172 | "So at 64 miles, the minimum height you could see is 0.51 miles, which is 2692 feet. The Burj Khalifa is 2717 feet, which checks out." 173 | ] 174 | }, 175 | { 176 | "cell_type": "code", 177 | "execution_count": null, 178 | "metadata": { 179 | "collapsed": true 180 | }, 181 | "outputs": [], 182 | "source": [] 183 | } 184 | ], 185 | "metadata": { 186 | "kernelspec": { 187 | "display_name": "Python 3", 188 | "language": "python", 189 | "name": "python3" 190 | }, 191 | "language_info": { 192 | "codemirror_mode": { 193 | "name": "ipython", 194 | "version": 3 195 | }, 196 | "file_extension": ".py", 197 | "mimetype": "text/x-python", 198 | "name": "python", 199 | "nbconvert_exporter": "python", 200 | "pygments_lexer": "ipython3", 201 | "version": "3.5.1" 202 | } 203 | }, 204 | "nbformat": 4, 205 | "nbformat_minor": 0 206 | } 207 | -------------------------------------------------------------------------------- /ExpectedValue.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "values = [1]*10 + [5]*1000 + [10000]" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "# Median" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 9, 24 | "metadata": { 25 | "collapsed": false 26 | }, 27 | "outputs": [ 28 | { 29 | "data": { 30 | "text/plain": [ 31 | "5" 32 | ] 33 | }, 34 | "execution_count": 9, 35 | "metadata": {}, 36 | "output_type": "execute_result" 37 | } 38 | ], 39 | "source": [ 40 | "median = values[len(values)//2]\n", 41 | "median" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": {}, 47 | "source": [ 48 | "# Mode" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": 3, 54 | "metadata": { 55 | "collapsed": false 56 | }, 57 | "outputs": [ 58 | { 59 | "data": { 60 | "text/plain": [ 61 | "Counter({1: 10, 5: 1000, 10000: 1})" 62 | ] 63 | }, 64 | "execution_count": 3, 65 | "metadata": {}, 66 | "output_type": "execute_result" 67 | } 68 | ], 69 | "source": [ 70 | "import collections\n", 71 | "counts = collections.Counter(values)\n", 72 | "counts" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": 7, 78 | "metadata": { 79 | "collapsed": false 80 | }, 81 | "outputs": [ 82 | { 83 | "data": { 84 | "text/plain": [ 85 | "5" 86 | ] 87 | }, 88 | "execution_count": 7, 89 | "metadata": {}, 90 | "output_type": "execute_result" 91 | } 92 | ], 93 | "source": [ 94 | "mode = counts.most_common()[0][0]\n", 95 | "mode" 96 | ] 97 | }, 98 | { 99 | "cell_type": "markdown", 100 | "metadata": {}, 101 | "source": [ 102 | "# Mean/average" 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": 2, 108 | "metadata": { 109 | "collapsed": false 110 | }, 111 | "outputs": [ 112 | { 113 | "data": { 114 | "text/plain": [ 115 | "14.846686449060336" 116 | ] 117 | }, 118 | "execution_count": 2, 119 | "metadata": {}, 120 | "output_type": "execute_result" 121 | } 122 | ], 123 | "source": [ 124 | "average = sum(values) / len(values)\n", 125 | "average" 126 | ] 127 | }, 128 | { 129 | "cell_type": "markdown", 130 | "metadata": {}, 131 | "source": [ 132 | "# Expected Value\n", 133 | "\n", 134 | "The Expected Value is the same as the Mean, but I'm calculating it in a probabilistic way to show they are the same" 135 | ] 136 | }, 137 | { 138 | "cell_type": "code", 139 | "execution_count": 11, 140 | "metadata": { 141 | "collapsed": false 142 | }, 143 | "outputs": [ 144 | { 145 | "data": { 146 | "text/plain": [ 147 | "14.846686449060336" 148 | ] 149 | }, 150 | "execution_count": 11, 151 | "metadata": {}, 152 | "output_type": "execute_result" 153 | } 154 | ], 155 | "source": [ 156 | "expected_value = 1 * (10/len(values)) + 5 * (1000/len(values)) + 10000 * (1/len(values))\n", 157 | "expected_value" 158 | ] 159 | }, 160 | { 161 | "cell_type": "code", 162 | "execution_count": null, 163 | "metadata": { 164 | "collapsed": true 165 | }, 166 | "outputs": [], 167 | "source": [] 168 | } 169 | ], 170 | "metadata": { 171 | "kernelspec": { 172 | "display_name": "Python 3", 173 | "language": "python", 174 | "name": "python3" 175 | }, 176 | "language_info": { 177 | "codemirror_mode": { 178 | "name": "ipython", 179 | "version": 3 180 | }, 181 | "file_extension": ".py", 182 | "mimetype": "text/x-python", 183 | "name": "python", 184 | "nbconvert_exporter": "python", 185 | "pygments_lexer": "ipython3", 186 | "version": "3.5.1" 187 | } 188 | }, 189 | "nbformat": 4, 190 | "nbformat_minor": 0 191 | } 192 | -------------------------------------------------------------------------------- /LinearTransformations.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### Rotation" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 1, 13 | "metadata": { 14 | "collapsed": false 15 | }, 16 | "outputs": [ 17 | { 18 | "data": { 19 | "text/plain": [ 20 | "" 21 | ] 22 | }, 23 | "execution_count": 1, 24 | "metadata": {}, 25 | "output_type": "execute_result" 26 | }, 27 | { 28 | "data": { 29 | "image/png": "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\n", 30 | "text/plain": [ 31 | "" 32 | ] 33 | }, 34 | "metadata": {}, 35 | "output_type": "display_data" 36 | } 37 | ], 38 | "source": [ 39 | "%matplotlib inline\n", 40 | "import matplotlib.pyplot as plt\n", 41 | "import numpy as np\n", 42 | "\n", 43 | "# Make graph nice\n", 44 | "fig, ax = plt.subplots()\n", 45 | "fig.set_size_inches(6,6)\n", 46 | "bounds = [(10,10), (10,-10), (-10,10), (-10,-10)]\n", 47 | "(bx,by) = zip(*bounds)\n", 48 | "plt.scatter(bx, by, s=0.01)\n", 49 | "\n", 50 | "# Smiley face points\n", 51 | "points = [ (4,5), (6,5),\n", 52 | " (3,3), (4,3), (5,3), (6,3), (7,3)]\n", 53 | "\n", 54 | "# Plot points\n", 55 | "(xs, ys) = zip(*points)\n", 56 | "plt.scatter(xs, ys, c='blue')\n", 57 | "\n", 58 | "# Rotation matrix\n", 59 | "rotation_mat = np.asarray([[0, -1], [1, 0]])\n", 60 | "points_mat = np.asarray(points)\n", 61 | "\n", 62 | "# Plot rotated matrix\n", 63 | "rotated = np.dot(rotation_mat, points_mat.T).T\n", 64 | "(rx, ry) = zip(*rotated)\n", 65 | "plt.scatter(rx, ry, c='red')\n" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": null, 71 | "metadata": { 72 | "collapsed": true 73 | }, 74 | "outputs": [], 75 | "source": [] 76 | } 77 | ], 78 | "metadata": { 79 | "kernelspec": { 80 | "display_name": "Python 3", 81 | "language": "python", 82 | "name": "python3" 83 | }, 84 | "language_info": { 85 | "codemirror_mode": { 86 | "name": "ipython", 87 | "version": 3 88 | }, 89 | "file_extension": ".py", 90 | "mimetype": "text/x-python", 91 | "name": "python", 92 | "nbconvert_exporter": "python", 93 | "pygments_lexer": "ipython3", 94 | "version": "3.5.1" 95 | } 96 | }, 97 | "nbformat": 4, 98 | "nbformat_minor": 0 99 | } 100 | -------------------------------------------------------------------------------- /MarkovChain.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Markov Chain\n", 8 | "\n", 9 | "![Bull Bear Markov Chain](images/bull_bear_markov.png)" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 13, 15 | "metadata": { 16 | "collapsed": true 17 | }, 18 | "outputs": [], 19 | "source": [ 20 | "import numpy as np\n", 21 | "import collections\n", 22 | "import random" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 19, 28 | "metadata": { 29 | "collapsed": false 30 | }, 31 | "outputs": [], 32 | "source": [ 33 | "# [bull, bear, stagnant][bull, bear, stagnant]\n", 34 | "index_to_name = ['bull', 'bear', 'stagnant']\n", 35 | "transition_matrix = np.array([[0.9, 0.075, 0.025],[0.15, 0.8, 0.05], [0.25, 0.25, 0.5]])" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": 15, 41 | "metadata": { 42 | "collapsed": false 43 | }, 44 | "outputs": [ 45 | { 46 | "data": { 47 | "text/plain": [ 48 | "0.074999999999999997" 49 | ] 50 | }, 51 | "execution_count": 15, 52 | "metadata": {}, 53 | "output_type": "execute_result" 54 | } 55 | ], 56 | "source": [ 57 | "# bull -> bear\n", 58 | "transition_matrix[0][1]" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": 51, 64 | "metadata": { 65 | "collapsed": false 66 | }, 67 | "outputs": [ 68 | { 69 | "name": "stdout", 70 | "output_type": "stream", 71 | "text": [ 72 | "bull stationary probability: 0.627322\n", 73 | "bear stationary probability: 0.3105\n", 74 | "stagnant stationary probability: 0.062178\n" 75 | ] 76 | } 77 | ], 78 | "source": [ 79 | "# Find stationary distribution\n", 80 | "def get_next_state(state):\n", 81 | " r = random.random()\n", 82 | " prob_sum = 0\n", 83 | " new_state = len(transition_matrix) - 1\n", 84 | " for i in range(len(transition_matrix)):\n", 85 | " prob_sum += transition_matrix[state][i]\n", 86 | " if prob_sum >= r:\n", 87 | " new_state = i\n", 88 | " break\n", 89 | " return new_state\n", 90 | " \n", 91 | "\n", 92 | "num_samples = 1000000\n", 93 | "state = 0\n", 94 | "count = collections.Counter()\n", 95 | "for i in range(num_samples):\n", 96 | " count[state] += 1\n", 97 | " state = get_next_state(state)\n", 98 | "\n", 99 | "for i in range(len(index_to_name)):\n", 100 | " print('{} stationary probability: {}'.format(index_to_name[i], count[i] / num_samples))" 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": null, 106 | "metadata": { 107 | "collapsed": true 108 | }, 109 | "outputs": [], 110 | "source": [] 111 | } 112 | ], 113 | "metadata": { 114 | "kernelspec": { 115 | "display_name": "Python 3", 116 | "language": "python", 117 | "name": "python3" 118 | }, 119 | "language_info": { 120 | "codemirror_mode": { 121 | "name": "ipython", 122 | "version": 3 123 | }, 124 | "file_extension": ".py", 125 | "mimetype": "text/x-python", 126 | "name": "python", 127 | "nbconvert_exporter": "python", 128 | "pygments_lexer": "ipython3", 129 | "version": "3.5.1" 130 | } 131 | }, 132 | "nbformat": 4, 133 | "nbformat_minor": 0 134 | } 135 | -------------------------------------------------------------------------------- /PoissonDistribution.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Poisson Distribution\n", 8 | "\n", 9 | "probability density function: \n", 10 | "\n", 11 | "\\begin{equation}\n", 12 | "\\frac{\\lambda^k e^{-\\lambda}}{k!}\n", 13 | "\\end{equation}\n", 14 | "\n", 15 | "mean:\n", 16 | "\\begin{equation}\n", 17 | "\\lambda\n", 18 | "\\end{equation}\n", 19 | "\n", 20 | "variance:\n", 21 | "\\begin{equation}\n", 22 | "\\lambda\n", 23 | "\\end{equation}\n" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 25, 29 | "metadata": { 30 | "collapsed": true 31 | }, 32 | "outputs": [], 33 | "source": [ 34 | "import math\n", 35 | "import numpy as np\n", 36 | "import scipy.stats as stats\n", 37 | "import pandas as pd\n", 38 | "import matplotlib.pyplot as plt\n", 39 | "%matplotlib inline" 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "metadata": {}, 45 | "source": [ 46 | "## Discrete pmf" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 24, 52 | "metadata": { 53 | "collapsed": false 54 | }, 55 | "outputs": [ 56 | { 57 | "data": { 58 | "text/plain": [ 59 | "" 60 | ] 61 | }, 62 | "execution_count": 24, 63 | "metadata": {}, 64 | "output_type": "execute_result" 65 | }, 66 | { 67 | "data": { 68 | "image/png": "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\ndGdWAAAAAElFTkSuQmCC\n", 69 | "text/plain": [ 70 | "" 71 | ] 72 | }, 73 | "metadata": {}, 74 | "output_type": "display_data" 75 | } 76 | ], 77 | "source": [ 78 | "a = range(16)\n", 79 | "lambda1 = 1.5\n", 80 | "\n", 81 | "plt.bar(a, stats.poisson.pmf(a, lambda1), color=\"#348ABD\",\n", 82 | " label=\"$\\lambda = %.1f$\" % lambda1, alpha=0.60,\n", 83 | " edgecolor=\"#348ABD\", lw=\"3\")" 84 | ] 85 | }, 86 | { 87 | "cell_type": "markdown", 88 | "metadata": {}, 89 | "source": [ 90 | "## Continuous pdf" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": 18, 96 | "metadata": { 97 | "collapsed": false 98 | }, 99 | "outputs": [ 100 | { 101 | "data": { 102 | "text/plain": [ 103 | "" 104 | ] 105 | }, 106 | "execution_count": 18, 107 | "metadata": {}, 108 | "output_type": "execute_result" 109 | }, 110 | { 111 | "data": { 112 | "image/png": "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\n", 113 | "text/plain": [ 114 | "" 115 | ] 116 | }, 117 | "metadata": {}, 118 | "output_type": "display_data" 119 | } 120 | ], 121 | "source": [ 122 | "a = np.linspace(0, 16, 100)\n", 123 | "lambda1 = 0.5\n", 124 | "plt.plot(a, stats.expon.pdf(a, scale=1/lambda1), lw=3,\n", 125 | " color=\"#348ABD\", label=\"$\\lambda = %.1f$\" % lambda1)\n", 126 | "plt.fill_between(a, stats.expon.pdf(a, scale=1/lambda1), color=\"#348ABD\", alpha=.33)\n" 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": 20, 132 | "metadata": { 133 | "collapsed": true 134 | }, 135 | "outputs": [], 136 | "source": [ 137 | "def poisson(lamb, k):\n", 138 | " return (lamb**k * math.e**(-1*k)) / math.factorial(k)" 139 | ] 140 | }, 141 | { 142 | "cell_type": "code", 143 | "execution_count": 35, 144 | "metadata": { 145 | "collapsed": false 146 | }, 147 | "outputs": [ 148 | { 149 | "data": { 150 | "text/plain": [ 151 | "0.02800522595692347" 152 | ] 153 | }, 154 | "execution_count": 35, 155 | "metadata": {}, 156 | "output_type": "execute_result" 157 | } 158 | ], 159 | "source": [ 160 | "poisson(1.5, 3)" 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": 30, 166 | "metadata": { 167 | "collapsed": false 168 | }, 169 | "outputs": [ 170 | { 171 | "data": { 172 | "text/plain": [ 173 | "" 174 | ] 175 | }, 176 | "execution_count": 30, 177 | "metadata": {}, 178 | "output_type": "execute_result" 179 | }, 180 | { 181 | "data": { 182 | "image/png": "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\n", 183 | "text/plain": [ 184 | "" 185 | ] 186 | }, 187 | "metadata": {}, 188 | "output_type": "display_data" 189 | } 190 | ], 191 | "source": [ 192 | "x = range(16)\n", 193 | "prob = [poisson(1.5, i) for i in x]\n", 194 | "\n", 195 | "pd.Series(prob, index=x).plot(kind='bar', lw=3,\n", 196 | " color=\"#348ABD\", alpha=0.60,\n", 197 | " edgecolor=\"#348ABD\")" 198 | ] 199 | }, 200 | { 201 | "cell_type": "code", 202 | "execution_count": 23, 203 | "metadata": { 204 | "collapsed": false 205 | }, 206 | "outputs": [ 207 | { 208 | "data": { 209 | "text/plain": [ 210 | "array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])" 211 | ] 212 | }, 213 | "execution_count": 23, 214 | "metadata": {}, 215 | "output_type": "execute_result" 216 | } 217 | ], 218 | "source": [ 219 | "np.arange(16)" 220 | ] 221 | }, 222 | { 223 | "cell_type": "code", 224 | "execution_count": null, 225 | "metadata": { 226 | "collapsed": true 227 | }, 228 | "outputs": [], 229 | "source": [] 230 | } 231 | ], 232 | "metadata": { 233 | "kernelspec": { 234 | "display_name": "Python 3", 235 | "language": "python", 236 | "name": "python3" 237 | }, 238 | "language_info": { 239 | "codemirror_mode": { 240 | "name": "ipython", 241 | "version": 3 242 | }, 243 | "file_extension": ".py", 244 | "mimetype": "text/x-python", 245 | "name": "python", 246 | "nbconvert_exporter": "python", 247 | "pygments_lexer": "ipython3", 248 | "version": "3.5.1" 249 | } 250 | }, 251 | "nbformat": 4, 252 | "nbformat_minor": 0 253 | } 254 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | math-with-python 2 | ================ 3 | 4 | Various math-related things in Python code 5 | -------------------------------------------------------------------------------- /Sudoku.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Solving Sudoku\n", 8 | "\n", 9 | "I'm going to attempt to solve Sudoku puzzles using 2 methods:\n", 10 | "\n", 11 | "* Linear Algebra\n", 12 | " - When I first saw Sudoku years ago, the first thing I thought of was a system of linear equations - the puzzle is a matrix after all. So I want to explore the idea of writing a system of linear equations to describe a Sudoku puzzle, and use some numeric optimization algorithms to try to find a solution (by minimizing the \"error\").\n", 13 | "* Backtrack searching\n", 14 | " - This is the most obvious and probably the best way to solve Sudoku\n", 15 | " \n", 16 | "In both cases, we'll develop a solution on a 4x4 version of sudoku before moving to the normal 9x9 board.\n", 17 | "\n", 18 | "## Solving Sudoku with Linear Algebra\n", 19 | "\n", 20 | "### Pretty-print matrix function" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 159, 26 | "metadata": { 27 | "collapsed": false 28 | }, 29 | "outputs": [ 30 | { 31 | "data": { 32 | "text/html": [ 33 | "
12
34
" 34 | ], 35 | "text/plain": [ 36 | "" 37 | ] 38 | }, 39 | "execution_count": 159, 40 | "metadata": {}, 41 | "output_type": "execute_result" 42 | } 43 | ], 44 | "source": [ 45 | "from IPython.display import HTML\n", 46 | "\n", 47 | "def print_matrix(mat):\n", 48 | " t = [\"\"]\n", 49 | " for row in mat:\n", 50 | " t.append(\"\")\n", 51 | " for col in row:\n", 52 | " t.append(\"\")\n", 53 | " t.append(\"\")\n", 54 | " t.append(\"
\" + str(col) + \"
\")\n", 55 | " return HTML(''.join(t))\n", 56 | "\n", 57 | "print_matrix([[1,2],[3,4]])" 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": 160, 63 | "metadata": { 64 | "collapsed": false 65 | }, 66 | "outputs": [], 67 | "source": [ 68 | "import numpy as np\n", 69 | "import random" 70 | ] 71 | }, 72 | { 73 | "cell_type": "markdown", 74 | "metadata": {}, 75 | "source": [ 76 | "## 4x4 Sudoku\n", 77 | "\n", 78 | "
\n", 79 | "\n", 80 | "Puzzle\n", 81 | "\n", 82 | "\n", 83 | "\n", 84 | "\n", 85 | "\n", 86 | "
1   
 4 
3  
  2 
\n", 87 | "\n", 88 | "
\n", 89 | "\n", 90 | "Solution\n", 91 | "\n", 92 | "\n", 93 | "\n", 94 | "\n", 95 | "\n", 96 | "
1432
3241
2314
4123
\n", 97 | "\n", 98 | "
\n", 99 | "\n", 100 | "\n" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": 161, 109 | "metadata": { 110 | "collapsed": false 111 | }, 112 | "outputs": [ 113 | { 114 | "data": { 115 | "text/html": [ 116 | "
a01a02a03a04
a05a06a07a08
a09a10a11a12
a13a14a15a16
" 117 | ], 118 | "text/plain": [ 119 | "" 120 | ] 121 | }, 122 | "execution_count": 161, 123 | "metadata": {}, 124 | "output_type": "execute_result" 125 | } 126 | ], 127 | "source": [ 128 | "m1 = [['a01', 'a02', 'a03', 'a04'],\n", 129 | " ['a05', 'a06', 'a07', 'a08'],\n", 130 | " ['a09', 'a10', 'a11', 'a12'],\n", 131 | " ['a13', 'a14', 'a15', 'a16']]\n", 132 | "\n", 133 | "print_matrix(m1)" 134 | ] 135 | }, 136 | { 137 | "cell_type": "markdown", 138 | "metadata": {}, 139 | "source": [ 140 | "### System of Equations to describe puzzle\n", 141 | "\n", 142 | "#### Sudoku-constant equations\n", 143 | "\n", 144 | "These first 12 equations are the same for all 4x4 sudoku puzzles - they are the rules of the game:\n", 145 | "\n", 146 | "By Rows:\n", 147 | "\n", 148 | "* a01 + a02 + a03 + a04 = 10\n", 149 | "* a05 + a06 + a07 + a08 = 10\n", 150 | "* a09 + a10 + a11 + a12 = 10\n", 151 | "* a13 + a14 + a15 + a16 = 10\n", 152 | "\n", 153 | "By Columns:\n", 154 | "\n", 155 | "* a01 + a05 + a09 + a13 = 10\n", 156 | "* a02 + a06 + a10 + a14 = 10\n", 157 | "* a03 + a07 + a11 + a15 = 10\n", 158 | "* a04 + a08 + a12 + a16 = 10\n", 159 | "\n", 160 | "By Quandrants:\n", 161 | "\n", 162 | "* a01 + a02 + a05 + a06 = 10\n", 163 | "* a03 + a04 + a07 + a08 = 10\n", 164 | "* a09 + a10 + a13 + a14 = 10\n", 165 | "* a11 + a12 + a15 + a16 = 10\n", 166 | "\n", 167 | "\n", 168 | "#### Puzzle-specific equations\n", 169 | "\n", 170 | "These last 4 equations are what we are given for this particular puzzle:\n", 171 | "\n", 172 | "* a01 = 1\n", 173 | "* a07 = 4\n", 174 | "* a10 = 3\n", 175 | "* a15 = 2\n", 176 | "\n" 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": 162, 182 | "metadata": { 183 | "collapsed": false 184 | }, 185 | "outputs": [], 186 | "source": [ 187 | "a = [[1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 188 | " [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", 189 | " [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0],\n", 190 | " [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1],\n", 191 | " \n", 192 | " [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0],\n", 193 | " [0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0],\n", 194 | " [0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0],\n", 195 | " [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1],\n", 196 | " \n", 197 | " [1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 198 | " [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", 199 | " [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],\n", 200 | " [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1],\n", 201 | " \n", 202 | " [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 203 | " [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 204 | " [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],\n", 205 | " [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0]]\n", 206 | "\n", 207 | "b = [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 1, 4, 3, 2]" 208 | ] 209 | }, 210 | { 211 | "cell_type": "code", 212 | "execution_count": 163, 213 | "metadata": { 214 | "collapsed": false 215 | }, 216 | "outputs": [ 217 | { 218 | "data": { 219 | "text/plain": [ 220 | "array([[ 1. , 3.47387651, 0.66316228, 4.86296121],\n", 221 | " [ 3.92477232, 1.60135117, 4. , 0.47387651],\n", 222 | " [ 2.04450137, 3. , 3.33683772, 1.61866091],\n", 223 | " [ 3.03072631, 1.92477232, 2. , 3.04450137]])" 224 | ] 225 | }, 226 | "execution_count": 163, 227 | "metadata": {}, 228 | "output_type": "execute_result" 229 | } 230 | ], 231 | "source": [ 232 | "x = np.linalg.lstsq(a,b)\n", 233 | "np.reshape(x[0], (4,-1))" 234 | ] 235 | }, 236 | { 237 | "cell_type": "code", 238 | "execution_count": 164, 239 | "metadata": { 240 | "collapsed": false 241 | }, 242 | "outputs": [ 243 | { 244 | "data": { 245 | "text/plain": [ 246 | "array([[1, 3, 1, 5],\n", 247 | " [4, 2, 4, 0],\n", 248 | " [2, 3, 3, 2],\n", 249 | " [3, 2, 2, 3]])" 250 | ] 251 | }, 252 | "execution_count": 164, 253 | "metadata": {}, 254 | "output_type": "execute_result" 255 | } 256 | ], 257 | "source": [ 258 | "solution = np.reshape([int(round(i)) for i in x[0]], (4, -1))\n", 259 | "solution" 260 | ] 261 | }, 262 | { 263 | "cell_type": "markdown", 264 | "metadata": {}, 265 | "source": [ 266 | "# Linear Regression Implementation\n", 267 | "\n", 268 | "I wanted to implement my own linear regression algorithm to better understand how it works." 269 | ] 270 | }, 271 | { 272 | "cell_type": "code", 273 | "execution_count": 165, 274 | "metadata": { 275 | "collapsed": false 276 | }, 277 | "outputs": [], 278 | "source": [ 279 | "# TODO: Add precesion early break\n", 280 | "def linear_regression(a, b, precision=0.01, learning_rate=0.1, max_iters = 200):\n", 281 | " m = len(b)\n", 282 | " omega = np.ones(m)\n", 283 | " \n", 284 | " for iter in range(max_iters):\n", 285 | " for i in range(m):\n", 286 | " omega[i] = omega[i] - learning_rate * (1.0/m) * sum((np.dot(a, omega) - b) * a[i])\n", 287 | " \n", 288 | " return omega" 289 | ] 290 | }, 291 | { 292 | "cell_type": "markdown", 293 | "metadata": {}, 294 | "source": [ 295 | "### Test problem to solve" 296 | ] 297 | }, 298 | { 299 | "cell_type": "code", 300 | "execution_count": 166, 301 | "metadata": { 302 | "collapsed": false 303 | }, 304 | "outputs": [ 305 | { 306 | "data": { 307 | "text/plain": [ 308 | "array([-5. , 5.5])" 309 | ] 310 | }, 311 | "execution_count": 166, 312 | "metadata": {}, 313 | "output_type": "execute_result" 314 | } 315 | ], 316 | "source": [ 317 | "# 3x+4y=7 and 5x+6y=8\n", 318 | "a1=[[3,4],[5,6]]\n", 319 | "b1=[7,8]\n", 320 | "linear_regression(a1,b1)" 321 | ] 322 | }, 323 | { 324 | "cell_type": "markdown", 325 | "metadata": {}, 326 | "source": [ 327 | "### Compare with numpy implementation's solution" 328 | ] 329 | }, 330 | { 331 | "cell_type": "code", 332 | "execution_count": 167, 333 | "metadata": { 334 | "collapsed": false 335 | }, 336 | "outputs": [ 337 | { 338 | "data": { 339 | "text/plain": [ 340 | "(-4.9999999999999769, 5.4999999999999813)" 341 | ] 342 | }, 343 | "execution_count": 167, 344 | "metadata": {}, 345 | "output_type": "execute_result" 346 | } 347 | ], 348 | "source": [ 349 | "((x,y),_,_,_) = np.linalg.lstsq(a1,b1)\n", 350 | "(x,y)" 351 | ] 352 | }, 353 | { 354 | "cell_type": "code", 355 | "execution_count": 168, 356 | "metadata": { 357 | "collapsed": false 358 | }, 359 | "outputs": [ 360 | { 361 | "data": { 362 | "text/plain": [ 363 | "array([ 4.71143849, 4.688242 , 4.66519049, -0.71713789,\n", 364 | " 1.9568043 , -6.86351593, 1.06255254, 17.08008666,\n", 365 | " -1.34069241, 10.54590296, -3.6327123 , 7.43473406,\n", 366 | " -0.87012213, 7.11479335, -4.74101031, -2.80275689])" 367 | ] 368 | }, 369 | "execution_count": 168, 370 | "metadata": {}, 371 | "output_type": "execute_result" 372 | } 373 | ], 374 | "source": [ 375 | "result = linear_regression(a,b)\n", 376 | "result" 377 | ] 378 | }, 379 | { 380 | "cell_type": "markdown", 381 | "metadata": {}, 382 | "source": [ 383 | "# Trying on Normal (9x9) Sudoku Puzzle" 384 | ] 385 | }, 386 | { 387 | "cell_type": "code", 388 | "execution_count": 169, 389 | "metadata": { 390 | "collapsed": false 391 | }, 392 | "outputs": [ 393 | { 394 | "data": { 395 | "text/html": [ 396 | "
a0a1a2a3a4a5a6a7a8
a9a10a11a12a13a14a15a16a17
a18a19a20a21a22a23a24a25a26
a27a28a29a30a31a32a33a34a35
a36a37a38a39a40a41a42a43a44
a45a46a47a48a49a50a51a52a53
a54a55a56a57a58a59a60a61a62
a63a64a65a66a67a68a69a70a71
a72a73a74a75a76a77a78a79a80
" 397 | ], 398 | "text/plain": [ 399 | "" 400 | ] 401 | }, 402 | "execution_count": 169, 403 | "metadata": {}, 404 | "output_type": "execute_result" 405 | } 406 | ], 407 | "source": [ 408 | "s9x9_flat = np.array(['a'+str(i) for i in range(81)])\n", 409 | "s9x9 = np.reshape(s9x9_flat, (9, -1))\n", 410 | "\n", 411 | "print_matrix(s9x9)" 412 | ] 413 | }, 414 | { 415 | "cell_type": "markdown", 416 | "metadata": {}, 417 | "source": [ 418 | "### Generate equations (in matrix form)" 419 | ] 420 | }, 421 | { 422 | "cell_type": "code", 423 | "execution_count": 170, 424 | "metadata": { 425 | "collapsed": false 426 | }, 427 | "outputs": [], 428 | "source": [ 429 | "sudoku_base = []\n", 430 | "num_rows = 9\n", 431 | "num_cols = 9\n", 432 | "\n", 433 | "# Generate equations for rows\n", 434 | "for row in range(num_rows):\n", 435 | " row_offset = num_rows * row\n", 436 | " new_row = [0 for i in range(num_rows * num_cols)]\n", 437 | " for i in range(num_rows):\n", 438 | " new_row[row_offset + i] = 1\n", 439 | " sudoku_base.append(new_row)\n", 440 | "\n", 441 | "# Generate equations for columns\n", 442 | "for col in range(num_cols):\n", 443 | " col_offset = num_cols * col\n", 444 | " new_row = [0 for i in range(num_rows * num_cols)]\n", 445 | " for i in range(num_cols):\n", 446 | " new_row[i * 9 + col] = 1\n", 447 | " sudoku_base.append(new_row)\n", 448 | "\n", 449 | "# Generate equations for squares\n", 450 | "current_cell = 0\n", 451 | "for square_start in [0, 3, 6, 27, 30, 33, 54, 57, 60]:\n", 452 | " new_row = [0 for i in range(num_rows * num_cols)]\n", 453 | " for cell_offset in [0, 1, 2, 9, 10, 11, 18, 19, 20]:\n", 454 | " new_row[square_start+cell_offset] = 1\n", 455 | " sudoku_base.append(new_row)" 456 | ] 457 | }, 458 | { 459 | "cell_type": "code", 460 | "execution_count": 171, 461 | "metadata": { 462 | "collapsed": false 463 | }, 464 | "outputs": [], 465 | "source": [ 466 | "# This is a wide matrix\n", 467 | "#print_matrix(sudoku_base)" 468 | ] 469 | }, 470 | { 471 | "cell_type": "code", 472 | "execution_count": 172, 473 | "metadata": { 474 | "collapsed": false 475 | }, 476 | "outputs": [], 477 | "source": [ 478 | "# Definition of sample puzzle\n", 479 | "puzzle1 = {0:8, 3:1, 4:6, 9:4, 10:1, \n", 480 | " 12:9, 14:8, 17:6, 25:1, 31:8, \n", 481 | " 32:6, 35:3, 37:8, 38:9, 40:3, \n", 482 | " 42:2, 43:5, 45:3, 48:2, 49:9, \n", 483 | " 55:2, 63:7, 66:8, 68:4, 70:6, \n", 484 | " 71:5, 76:5, 77:7, 80:4}" 485 | ] 486 | }, 487 | { 488 | "cell_type": "code", 489 | "execution_count": 173, 490 | "metadata": { 491 | "collapsed": false 492 | }, 493 | "outputs": [], 494 | "source": [ 495 | "def print_puzzle(puzzle):\n", 496 | " puzzle_mat = [' ' for i in range(81)]\n", 497 | " for (k,v) in puzzle.items():\n", 498 | " puzzle_mat[k] = v\n", 499 | " return print_matrix(np.reshape(puzzle_mat, (9, -1)))" 500 | ] 501 | }, 502 | { 503 | "cell_type": "code", 504 | "execution_count": 174, 505 | "metadata": { 506 | "collapsed": false 507 | }, 508 | "outputs": [ 509 | { 510 | "data": { 511 | "text/html": [ 512 | "
8 16
41 9 8 6
1
86 3
89 3 25
3 29
2
7 8 4 65
57 4
" 513 | ], 514 | "text/plain": [ 515 | "" 516 | ] 517 | }, 518 | "execution_count": 174, 519 | "metadata": {}, 520 | "output_type": "execute_result" 521 | } 522 | ], 523 | "source": [ 524 | "print_puzzle(puzzle1)" 525 | ] 526 | }, 527 | { 528 | "cell_type": "code", 529 | "execution_count": 175, 530 | "metadata": { 531 | "collapsed": false 532 | }, 533 | "outputs": [ 534 | { 535 | "name": "stdout", 536 | "output_type": "stream", 537 | "text": [ 538 | "Number of equations: 45\n" 539 | ] 540 | } 541 | ], 542 | "source": [ 543 | "print(\"Number of equations:\", len(puzzle1)+len(a))" 544 | ] 545 | }, 546 | { 547 | "cell_type": "code", 548 | "execution_count": 176, 549 | "metadata": { 550 | "collapsed": false 551 | }, 552 | "outputs": [ 553 | { 554 | "name": "stdout", 555 | "output_type": "stream", 556 | "text": [ 557 | "Number of unknowns: 52\n" 558 | ] 559 | } 560 | ], 561 | "source": [ 562 | "print(\"Number of unknowns:\", 81 - len(puzzle1))" 563 | ] 564 | }, 565 | { 566 | "cell_type": "code", 567 | "execution_count": 177, 568 | "metadata": { 569 | "collapsed": false 570 | }, 571 | "outputs": [], 572 | "source": [ 573 | "def get_puzzle_rep(puzzle):\n", 574 | " a = sudoku_base[:]\n", 575 | " b = [sum(range(1,10)) for i in range(len(a))]\n", 576 | " for (k,v) in puzzle.items():\n", 577 | " new_a_row = [0 for i in range(81)]\n", 578 | " new_a_row[k] = 1\n", 579 | " a.append(new_a_row)\n", 580 | " b.append(v)\n", 581 | " return (a,b)\n" 582 | ] 583 | }, 584 | { 585 | "cell_type": "code", 586 | "execution_count": 178, 587 | "metadata": { 588 | "collapsed": false 589 | }, 590 | "outputs": [], 591 | "source": [ 592 | "p1_a, p1_b = get_puzzle_rep(puzzle1)" 593 | ] 594 | }, 595 | { 596 | "cell_type": "code", 597 | "execution_count": 179, 598 | "metadata": { 599 | "collapsed": false 600 | }, 601 | "outputs": [ 602 | { 603 | "data": { 604 | "text/plain": [ 605 | "56" 606 | ] 607 | }, 608 | "execution_count": 179, 609 | "metadata": {}, 610 | "output_type": "execute_result" 611 | } 612 | ], 613 | "source": [ 614 | "len(p1_a)" 615 | ] 616 | }, 617 | { 618 | "cell_type": "code", 619 | "execution_count": 180, 620 | "metadata": { 621 | "collapsed": false 622 | }, 623 | "outputs": [ 624 | { 625 | "data": { 626 | "text/plain": [ 627 | "array([[ 8, 75, -29, 1, 6, -1, -10, 11, -15],\n", 628 | " [ 4, 1, 11, 9, 3, 8, 3, 0, 6],\n", 629 | " [ 6, -29, -1, 17, 2, 0, 22, 1, 29],\n", 630 | " [ -8, 1, 11, 6, 8, 6, 10, 9, 3],\n", 631 | " [ 8, 8, 9, -6, 3, 11, 2, 5, 5],\n", 632 | " [ 3, 1, 14, 2, 9, 5, 1, 6, 4],\n", 633 | " [ 7, 2, 14, 6, 2, 5, -1, 7, 4],\n", 634 | " [ 7, -11, 17, 8, 7, 4, 2, 6, 5],\n", 635 | " [ 12, -2, -1, 2, 5, 7, 16, 1, 4]])" 636 | ] 637 | }, 638 | "execution_count": 180, 639 | "metadata": {}, 640 | "output_type": "execute_result" 641 | } 642 | ], 643 | "source": [ 644 | "x = np.linalg.lstsq(p1_a,p1_b)\n", 645 | "solution = np.reshape([int(round(i)) for i in x[0]], (9, -1))\n", 646 | "solution" 647 | ] 648 | }, 649 | { 650 | "cell_type": "markdown", 651 | "metadata": {}, 652 | "source": [ 653 | "# Constrained Linear Regression - scipy.optimize.nnls" 654 | ] 655 | }, 656 | { 657 | "cell_type": "code", 658 | "execution_count": 181, 659 | "metadata": { 660 | "collapsed": false 661 | }, 662 | "outputs": [], 663 | "source": [ 664 | "import scipy.optimize" 665 | ] 666 | }, 667 | { 668 | "cell_type": "code", 669 | "execution_count": 182, 670 | "metadata": { 671 | "collapsed": false 672 | }, 673 | "outputs": [], 674 | "source": [ 675 | "result = scipy.optimize.nnls(p1_a, p1_b)" 676 | ] 677 | }, 678 | { 679 | "cell_type": "code", 680 | "execution_count": 183, 681 | "metadata": { 682 | "collapsed": false 683 | }, 684 | "outputs": [ 685 | { 686 | "data": { 687 | "text/plain": [ 688 | "array([[ 8, 30, 0, 1, 6, 0, 0, 0, 0],\n", 689 | " [ 4, 1, 0, 9, 0, 8, 17, 0, 6],\n", 690 | " [ 0, 2, 0, 0, 1, 20, 21, 1, 0],\n", 691 | " [23, 0, 0, 0, 8, 6, 5, 0, 3],\n", 692 | " [ 0, 8, 9, 17, 3, 0, 2, 5, 1],\n", 693 | " [ 3, 2, 0, 2, 9, 0, 0, 3, 26],\n", 694 | " [ 0, 2, 21, 8, 13, 0, 0, 1, 0],\n", 695 | " [ 7, 0, 15, 8, 0, 4, 0, 6, 5],\n", 696 | " [ 0, 0, 0, 0, 5, 7, 0, 29, 4]])" 697 | ] 698 | }, 699 | "execution_count": 183, 700 | "metadata": {}, 701 | "output_type": "execute_result" 702 | } 703 | ], 704 | "source": [ 705 | "solution = np.reshape([int(round(i)) for i in result[0]], (9, -1))\n", 706 | "solution" 707 | ] 708 | }, 709 | { 710 | "cell_type": "markdown", 711 | "metadata": {}, 712 | "source": [ 713 | "# scipy.optimize.fmin_slsqp\n" 714 | ] 715 | }, 716 | { 717 | "cell_type": "code", 718 | "execution_count": 184, 719 | "metadata": { 720 | "collapsed": false 721 | }, 722 | "outputs": [], 723 | "source": [ 724 | "import scipy.optimize" 725 | ] 726 | }, 727 | { 728 | "cell_type": "code", 729 | "execution_count": 185, 730 | "metadata": { 731 | "collapsed": false 732 | }, 733 | "outputs": [], 734 | "source": [ 735 | "def objective_func(x):\n", 736 | " mat_result = sum((np.dot(p1_a, x) - p1_b)**2)\n", 737 | " #duplicate_count = 2 * sum([ (9 - len(set(x[row_start:row_start+9]))) for row_start in xrange(0, 81, 9)])\n", 738 | " return mat_result # + duplicate_count" 739 | ] 740 | }, 741 | { 742 | "cell_type": "code", 743 | "execution_count": 186, 744 | "metadata": { 745 | "collapsed": false 746 | }, 747 | "outputs": [ 748 | { 749 | "data": { 750 | "text/plain": [ 751 | "4.833449373902292e-27" 752 | ] 753 | }, 754 | "execution_count": 186, 755 | "metadata": {}, 756 | "output_type": "execute_result" 757 | } 758 | ], 759 | "source": [ 760 | "objective_func(result[0])" 761 | ] 762 | }, 763 | { 764 | "cell_type": "code", 765 | "execution_count": 187, 766 | "metadata": { 767 | "collapsed": false 768 | }, 769 | "outputs": [ 770 | { 771 | "data": { 772 | "text/plain": [ 773 | "1672" 774 | ] 775 | }, 776 | "execution_count": 187, 777 | "metadata": {}, 778 | "output_type": "execute_result" 779 | } 780 | ], 781 | "source": [ 782 | "random_guess = [random.randint(1, 9) for i in range(81)]\n", 783 | "objective_func(random_guess)" 784 | ] 785 | }, 786 | { 787 | "cell_type": "code", 788 | "execution_count": 188, 789 | "metadata": { 790 | "collapsed": false 791 | }, 792 | "outputs": [ 793 | { 794 | "name": "stdout", 795 | "output_type": "stream", 796 | "text": [ 797 | "Optimization terminated successfully. (Exit mode 0)\n", 798 | " Current function value: 2.29797100389e-07\n", 799 | " Iterations: 32\n", 800 | " Function evaluations: 2686\n", 801 | " Gradient evaluations: 32\n" 802 | ] 803 | } 804 | ], 805 | "source": [ 806 | "slsqp_result = scipy.optimize.fmin_slsqp(func=objective_func, x0=random_guess, bounds=[(1,9) for i in range(81)])" 807 | ] 808 | }, 809 | { 810 | "cell_type": "code", 811 | "execution_count": 189, 812 | "metadata": { 813 | "collapsed": false 814 | }, 815 | "outputs": [ 816 | { 817 | "data": { 818 | "text/plain": [ 819 | "array([ 8.00003261, 6.35940875, 4.93648889, 1.00000169, 5.99997624,\n", 820 | " 4.29737636, 7.04763379, 3.92579154, 3.43323742, 3.99996711,\n", 821 | " 1.00004054, 2.86312341, 9. , 2.67226732, 8.00000642,\n", 822 | " 5.71913519, 5.74536874, 6.00004407, 5.37036786, 7.29314808,\n", 823 | " 5.17739899, 6.17573459, 5.39561016, 2.45905979, 5.88239592,\n", 824 | " 1. , 6.24634972, 3.72172385, 7.22664993, 4.04442496,\n", 825 | " 3.75455248, 7.99992386, 6.00006481, 3.23362232, 6.0189181 ,\n", 826 | " 3.00007657, 3.1967975 , 7.99994184, 9. , 5.28159176,\n", 827 | " 3.00001894, 4.88903597, 1.99993536, 4.9999054 , 4.63270593,\n", 828 | " 3.0000033 , 3.1900495 , 3.62047293, 1.99994369, 9. ,\n", 829 | " 3.07474676, 7.94825758, 6.5796729 , 6.58696652, 7.07995089,\n", 830 | " 1.99997988, 5.20345885, 5.29346834, 2.51483276, 5.27972021,\n", 831 | " 5.92777755, 5.60008543, 6.10076261, 6.99997325, 4.10688862,\n", 832 | " 3.74539955, 8.00006622, 3.41739541, 4.00005442, 3.73028178,\n", 833 | " 5.9999851 , 4.99999989, 3.63107425, 5.8239441 , 6.40926133,\n", 834 | " 4.49461762, 5.00000789, 6.99994006, 3.51079067, 5.13024736,\n", 835 | " 4.00007619])" 836 | ] 837 | }, 838 | "execution_count": 189, 839 | "metadata": {}, 840 | "output_type": "execute_result" 841 | } 842 | ], 843 | "source": [ 844 | "slsqp_result" 845 | ] 846 | }, 847 | { 848 | "cell_type": "code", 849 | "execution_count": 190, 850 | "metadata": { 851 | "collapsed": false 852 | }, 853 | "outputs": [ 854 | { 855 | "data": { 856 | "text/plain": [ 857 | "array([[8, 6, 5, 1, 6, 4, 7, 4, 3],\n", 858 | " [4, 1, 3, 9, 3, 8, 6, 6, 6],\n", 859 | " [5, 7, 5, 6, 5, 2, 6, 1, 6],\n", 860 | " [4, 7, 4, 4, 8, 6, 3, 6, 3],\n", 861 | " [3, 8, 9, 5, 3, 5, 2, 5, 5],\n", 862 | " [3, 3, 4, 2, 9, 3, 8, 7, 7],\n", 863 | " [7, 2, 5, 5, 3, 5, 6, 6, 6],\n", 864 | " [7, 4, 4, 8, 3, 4, 4, 6, 5],\n", 865 | " [4, 6, 6, 4, 5, 7, 4, 5, 4]])" 866 | ] 867 | }, 868 | "execution_count": 190, 869 | "metadata": {}, 870 | "output_type": "execute_result" 871 | } 872 | ], 873 | "source": [ 874 | "z = np.reshape([int(round(i)) for i in slsqp_result], (9, -1))\n", 875 | "z" 876 | ] 877 | }, 878 | { 879 | "cell_type": "code", 880 | "execution_count": 191, 881 | "metadata": { 882 | "collapsed": false 883 | }, 884 | "outputs": [ 885 | { 886 | "data": { 887 | "text/plain": [ 888 | "2.2979710038864857e-07" 889 | ] 890 | }, 891 | "execution_count": 191, 892 | "metadata": {}, 893 | "output_type": "execute_result" 894 | } 895 | ], 896 | "source": [ 897 | "objective_func(slsqp_result)" 898 | ] 899 | }, 900 | { 901 | "cell_type": "markdown", 902 | "metadata": {}, 903 | "source": [ 904 | "# Genetic Algorithm\n", 905 | "\n", 906 | "The fmin_slsqp result seemed so close, I was curious if running the GA on its output would tweak the last few values into place. It turns out not to work." 907 | ] 908 | }, 909 | { 910 | "cell_type": "code", 911 | "execution_count": 192, 912 | "metadata": { 913 | "collapsed": false 914 | }, 915 | "outputs": [], 916 | "source": [ 917 | "def sudoku_fitness(a, b, attempt):\n", 918 | " return sum((np.dot(a, attempt) - b)**2)\n", 919 | "\n", 920 | "def sudoku_mutation_swap(current, max_mutations=3):\n", 921 | " num_mutations = random.randint(1, max_mutations)\n", 922 | " baby = current[:]\n", 923 | " for i in range(num_mutations):\n", 924 | " index1 = random.randint(0,80)\n", 925 | " index2 = random.randint(0,80)\n", 926 | " baby[index1], baby[index2] = baby[index2], baby[index1]\n", 927 | " return baby\n", 928 | "\n", 929 | "def sudoku_mutation_tweak(current, max_mutations=3):\n", 930 | " num_mutations = random.randint(1, max_mutations)\n", 931 | " baby = current[:]\n", 932 | " for i in range(max_mutations):\n", 933 | " index1 = random.randint(0,80)\n", 934 | " index2 = random.randint(0,80)\n", 935 | " tweak_amount = random.randint(1, 3)\n", 936 | " baby[index1] = ((baby[index1]+tweak_amount) % 9) + 1\n", 937 | " baby[index2] = ((baby[index2]+tweak_amount) % 9) + 1\n", 938 | " return baby\n", 939 | "\n", 940 | "def sudoku_genetic_algorithm(fitness_func, mutation_func, initial_guess=None, max_iters=1000):\n", 941 | " iter_count = 0\n", 942 | " error_steps = []\n", 943 | " current = np.array([i for i in range(1,10) for j in range(1,10)])\n", 944 | " current_fitness = 100000000 # really big - assume the guess is really bad\n", 945 | "\n", 946 | " if initial_guess != None:\n", 947 | " current = initial_guess[:]\n", 948 | " current_fitness = fitness_func(current)\n", 949 | " \n", 950 | " for i in range(max_iters):\n", 951 | " baby = mutation_func(current)\n", 952 | " baby_fitness = fitness_func(baby)\n", 953 | " \n", 954 | " # If we found a perfect solution, just return it!\n", 955 | " if baby_fitness == 0:\n", 956 | " return (baby, 0)\n", 957 | " \n", 958 | " # Compare baby fitness to current\n", 959 | " elif baby_fitness < current_fitness:\n", 960 | " print(\"Doing switch: baby_fitness=%d, current_fitness=%d\" % (baby_fitness, current_fitness))\n", 961 | " current = baby[:]\n", 962 | " current_fitness = baby_fitness\n", 963 | " \n", 964 | " iter_count += 1\n", 965 | " if iter_count % 10000 == 0:\n", 966 | " print(\"Error (steps=%d): %d\" % (iter_count, current_fitness))\n", 967 | " error_steps.append(current_fitness)\n", 968 | " \n", 969 | " return (current, error_steps)" 970 | ] 971 | }, 972 | { 973 | "cell_type": "code", 974 | "execution_count": 193, 975 | "metadata": { 976 | "collapsed": false 977 | }, 978 | "outputs": [ 979 | { 980 | "name": "stdout", 981 | "output_type": "stream", 982 | "text": [ 983 | "Initial fitness: 17\n" 984 | ] 985 | }, 986 | { 987 | "name": "stderr", 988 | "output_type": "stream", 989 | "text": [ 990 | "/usr/local/lib/python3.5/site-packages/ipykernel/__main__.py:30: FutureWarning: comparison to `None` will result in an elementwise object comparison in the future.\n" 991 | ] 992 | }, 993 | { 994 | "name": "stdout", 995 | "output_type": "stream", 996 | "text": [ 997 | "Error (steps=10000): 17\n", 998 | "Error (steps=20000): 17\n", 999 | "Error (steps=30000): 17\n", 1000 | "Error (steps=40000): 17\n", 1001 | "Error (steps=50000): 17\n" 1002 | ] 1003 | } 1004 | ], 1005 | "source": [ 1006 | "initial_g = np.array([int(round(i)) for i in slsqp_result])\n", 1007 | "fitness = lambda attempt: sudoku_fitness(p1_a, p1_b, attempt)\n", 1008 | "print(\"Initial fitness:\", fitness(initial_g))\n", 1009 | "(ga_result, ga_error) = sudoku_genetic_algorithm(fitness, sudoku_mutation_tweak, initial_guess=initial_g, max_iters=50000)\n" 1010 | ] 1011 | }, 1012 | { 1013 | "cell_type": "code", 1014 | "execution_count": 194, 1015 | "metadata": { 1016 | "collapsed": false 1017 | }, 1018 | "outputs": [ 1019 | { 1020 | "data": { 1021 | "text/plain": [ 1022 | "array([[8, 6, 5, 2, 8, 8, 6, 7, 7],\n", 1023 | " [3, 8, 5, 2, 6, 2, 2, 3, 8],\n", 1024 | " [7, 3, 9, 2, 4, 1, 5, 3, 2],\n", 1025 | " [4, 3, 7, 1, 2, 2, 3, 2, 4],\n", 1026 | " [5, 3, 7, 3, 7, 6, 3, 1, 1],\n", 1027 | " [4, 3, 1, 5, 8, 5, 5, 8, 9],\n", 1028 | " [5, 4, 7, 6, 6, 5, 7, 5, 2],\n", 1029 | " [8, 2, 3, 1, 1, 5, 7, 8, 9],\n", 1030 | " [7, 6, 9, 8, 9, 4, 9, 2, 6]])" 1031 | ] 1032 | }, 1033 | "execution_count": 194, 1034 | "metadata": {}, 1035 | "output_type": "execute_result" 1036 | } 1037 | ], 1038 | "source": [ 1039 | "np.reshape([int(round(i)) for i in ga_result], (9, -1))" 1040 | ] 1041 | }, 1042 | { 1043 | "cell_type": "code", 1044 | "execution_count": 195, 1045 | "metadata": { 1046 | "collapsed": false 1047 | }, 1048 | "outputs": [ 1049 | { 1050 | "data": { 1051 | "text/plain": [ 1052 | "array([[8, 6, 5, 2, 8, 8, 6, 7, 7],\n", 1053 | " [3, 8, 5, 2, 6, 2, 2, 3, 8],\n", 1054 | " [7, 3, 9, 2, 4, 1, 5, 3, 2],\n", 1055 | " [4, 3, 7, 1, 2, 2, 3, 2, 4],\n", 1056 | " [5, 3, 7, 3, 7, 6, 3, 1, 1],\n", 1057 | " [4, 3, 1, 5, 8, 5, 5, 8, 9],\n", 1058 | " [5, 4, 7, 6, 6, 5, 7, 5, 2],\n", 1059 | " [8, 2, 3, 1, 1, 5, 7, 8, 9],\n", 1060 | " [7, 6, 9, 8, 9, 4, 9, 2, 6]])" 1061 | ] 1062 | }, 1063 | "execution_count": 195, 1064 | "metadata": {}, 1065 | "output_type": "execute_result" 1066 | } 1067 | ], 1068 | "source": [ 1069 | "np.reshape([int(round(i)) for i in initial_g], (9, -1))" 1070 | ] 1071 | }, 1072 | { 1073 | "cell_type": "markdown", 1074 | "metadata": { 1075 | "collapsed": false 1076 | }, 1077 | "source": [ 1078 | "# Backtracking\n", 1079 | "\n", 1080 | "Okay, enough messing around; let's do it the right way now: backtracking.\n", 1081 | "\n", 1082 | "### Define the representation" 1083 | ] 1084 | }, 1085 | { 1086 | "cell_type": "code", 1087 | "execution_count": 196, 1088 | "metadata": { 1089 | "collapsed": true 1090 | }, 1091 | "outputs": [], 1092 | "source": [ 1093 | "N = None\n", 1094 | "puzzle = [[1, N, N, N],\n", 1095 | " [N, N, 4, N],\n", 1096 | " [N, 3, N, N],\n", 1097 | " [N, N, 2, N]]" 1098 | ] 1099 | }, 1100 | { 1101 | "cell_type": "markdown", 1102 | "metadata": {}, 1103 | "source": [ 1104 | "### has_duplicates\n", 1105 | "\n", 1106 | "This function returns true if there are no duplicates in the list. We'll use it to determine if a possible solution is breaking any rules of sudoku - that there are duplicate numbers in either a row, column, or \"section\" (board quandrant for a 4x4 board)." 1107 | ] 1108 | }, 1109 | { 1110 | "cell_type": "code", 1111 | "execution_count": 197, 1112 | "metadata": { 1113 | "collapsed": true 1114 | }, 1115 | "outputs": [], 1116 | "source": [ 1117 | "def has_duplicates(lst):\n", 1118 | " seen = set()\n", 1119 | " for i in lst:\n", 1120 | " if i is not None:\n", 1121 | " if i in seen:\n", 1122 | " return True\n", 1123 | " else:\n", 1124 | " seen.add(i)\n", 1125 | " return False" 1126 | ] 1127 | }, 1128 | { 1129 | "cell_type": "code", 1130 | "execution_count": 198, 1131 | "metadata": { 1132 | "collapsed": false 1133 | }, 1134 | "outputs": [ 1135 | { 1136 | "data": { 1137 | "text/plain": [ 1138 | "False" 1139 | ] 1140 | }, 1141 | "execution_count": 198, 1142 | "metadata": {}, 1143 | "output_type": "execute_result" 1144 | } 1145 | ], 1146 | "source": [ 1147 | "has_duplicates([1,2,3,4,5])" 1148 | ] 1149 | }, 1150 | { 1151 | "cell_type": "code", 1152 | "execution_count": 199, 1153 | "metadata": { 1154 | "collapsed": false 1155 | }, 1156 | "outputs": [ 1157 | { 1158 | "data": { 1159 | "text/plain": [ 1160 | "True" 1161 | ] 1162 | }, 1163 | "execution_count": 199, 1164 | "metadata": {}, 1165 | "output_type": "execute_result" 1166 | } 1167 | ], 1168 | "source": [ 1169 | "has_duplicates([1,2,3,4,5,1])" 1170 | ] 1171 | }, 1172 | { 1173 | "cell_type": "markdown", 1174 | "metadata": {}, 1175 | "source": [ 1176 | "### get_row" 1177 | ] 1178 | }, 1179 | { 1180 | "cell_type": "code", 1181 | "execution_count": 200, 1182 | "metadata": { 1183 | "collapsed": true 1184 | }, 1185 | "outputs": [], 1186 | "source": [ 1187 | "def get_row(board, index):\n", 1188 | " return board[index]" 1189 | ] 1190 | }, 1191 | { 1192 | "cell_type": "code", 1193 | "execution_count": 201, 1194 | "metadata": { 1195 | "collapsed": false 1196 | }, 1197 | "outputs": [ 1198 | { 1199 | "data": { 1200 | "text/plain": [ 1201 | "[1, None, None, None]" 1202 | ] 1203 | }, 1204 | "execution_count": 201, 1205 | "metadata": {}, 1206 | "output_type": "execute_result" 1207 | } 1208 | ], 1209 | "source": [ 1210 | "get_row(puzzle, 0)" 1211 | ] 1212 | }, 1213 | { 1214 | "cell_type": "markdown", 1215 | "metadata": {}, 1216 | "source": [ 1217 | "### get_column" 1218 | ] 1219 | }, 1220 | { 1221 | "cell_type": "code", 1222 | "execution_count": 202, 1223 | "metadata": { 1224 | "collapsed": true 1225 | }, 1226 | "outputs": [], 1227 | "source": [ 1228 | "def get_column(board, index):\n", 1229 | " return [board[i][index] for i in range(len(board))]" 1230 | ] 1231 | }, 1232 | { 1233 | "cell_type": "code", 1234 | "execution_count": 203, 1235 | "metadata": { 1236 | "collapsed": false 1237 | }, 1238 | "outputs": [ 1239 | { 1240 | "data": { 1241 | "text/plain": [ 1242 | "[1, None, None, None]" 1243 | ] 1244 | }, 1245 | "execution_count": 203, 1246 | "metadata": {}, 1247 | "output_type": "execute_result" 1248 | } 1249 | ], 1250 | "source": [ 1251 | "get_column(puzzle, 0)" 1252 | ] 1253 | }, 1254 | { 1255 | "cell_type": "markdown", 1256 | "metadata": {}, 1257 | "source": [ 1258 | "### get_section" 1259 | ] 1260 | }, 1261 | { 1262 | "cell_type": "code", 1263 | "execution_count": 204, 1264 | "metadata": { 1265 | "collapsed": false 1266 | }, 1267 | "outputs": [], 1268 | "source": [ 1269 | "import math\n", 1270 | "\n", 1271 | "def get_section(board, index):\n", 1272 | " sqrt = int(math.sqrt(len(board)))\n", 1273 | " start_row = (index // sqrt) * sqrt\n", 1274 | " start_col = (index % sqrt) * sqrt\n", 1275 | " section = []\n", 1276 | " for i in range(sqrt):\n", 1277 | " section += board[start_row][start_col:start_col+sqrt]\n", 1278 | " start_row += 1\n", 1279 | " return section" 1280 | ] 1281 | }, 1282 | { 1283 | "cell_type": "code", 1284 | "execution_count": 205, 1285 | "metadata": { 1286 | "collapsed": false 1287 | }, 1288 | "outputs": [ 1289 | { 1290 | "data": { 1291 | "text/plain": [ 1292 | "[None, None, 2, None]" 1293 | ] 1294 | }, 1295 | "execution_count": 205, 1296 | "metadata": {}, 1297 | "output_type": "execute_result" 1298 | } 1299 | ], 1300 | "source": [ 1301 | "get_section(puzzle, 3)" 1302 | ] 1303 | }, 1304 | { 1305 | "cell_type": "markdown", 1306 | "metadata": {}, 1307 | "source": [ 1308 | "### copy_and_set\n", 1309 | "\n", 1310 | "Return a new board with the specified value set." 1311 | ] 1312 | }, 1313 | { 1314 | "cell_type": "code", 1315 | "execution_count": 206, 1316 | "metadata": { 1317 | "collapsed": true 1318 | }, 1319 | "outputs": [], 1320 | "source": [ 1321 | "def copy_and_set(board, row, col, value):\n", 1322 | " board_new = [row.copy() for row in board]\n", 1323 | " board_new[row][col] = value\n", 1324 | " return board_new" 1325 | ] 1326 | }, 1327 | { 1328 | "cell_type": "code", 1329 | "execution_count": 207, 1330 | "metadata": { 1331 | "collapsed": false 1332 | }, 1333 | "outputs": [ 1334 | { 1335 | "data": { 1336 | "text/plain": [ 1337 | "[[1, 1, None, None],\n", 1338 | " [None, None, 4, None],\n", 1339 | " [None, 3, None, None],\n", 1340 | " [None, None, 2, None]]" 1341 | ] 1342 | }, 1343 | "execution_count": 207, 1344 | "metadata": {}, 1345 | "output_type": "execute_result" 1346 | } 1347 | ], 1348 | "source": [ 1349 | "copy_and_set(puzzle, 0, 1, 1)" 1350 | ] 1351 | }, 1352 | { 1353 | "cell_type": "markdown", 1354 | "metadata": {}, 1355 | "source": [ 1356 | "### is_valid" 1357 | ] 1358 | }, 1359 | { 1360 | "cell_type": "code", 1361 | "execution_count": 208, 1362 | "metadata": { 1363 | "collapsed": true 1364 | }, 1365 | "outputs": [], 1366 | "source": [ 1367 | "def is_valid(board):\n", 1368 | " for row_index in range(len(board)):\n", 1369 | " row = get_row(board, row_index)\n", 1370 | " if has_duplicates(row):\n", 1371 | " return False\n", 1372 | " for col_index in range(len(board)):\n", 1373 | " col = get_column(board, col_index)\n", 1374 | " if has_duplicates(col):\n", 1375 | " return False\n", 1376 | " for section_index in range(len(board)):\n", 1377 | " section = get_section(board, section_index)\n", 1378 | " if has_duplicates(section):\n", 1379 | " return False\n", 1380 | " return True" 1381 | ] 1382 | }, 1383 | { 1384 | "cell_type": "code", 1385 | "execution_count": 209, 1386 | "metadata": { 1387 | "collapsed": false 1388 | }, 1389 | "outputs": [ 1390 | { 1391 | "data": { 1392 | "text/plain": [ 1393 | "False" 1394 | ] 1395 | }, 1396 | "execution_count": 209, 1397 | "metadata": {}, 1398 | "output_type": "execute_result" 1399 | } 1400 | ], 1401 | "source": [ 1402 | "# This should be not be valid, since it would be 2 '1's in the first row\n", 1403 | "is_valid(copy_and_set(puzzle, 0, 1, 1))" 1404 | ] 1405 | }, 1406 | { 1407 | "cell_type": "code", 1408 | "execution_count": 210, 1409 | "metadata": { 1410 | "collapsed": false 1411 | }, 1412 | "outputs": [ 1413 | { 1414 | "data": { 1415 | "text/plain": [ 1416 | "True" 1417 | ] 1418 | }, 1419 | "execution_count": 210, 1420 | "metadata": {}, 1421 | "output_type": "execute_result" 1422 | } 1423 | ], 1424 | "source": [ 1425 | "# This should be valid, since there are no duplicates in any rows, columns, or sections\n", 1426 | "is_valid(copy_and_set(puzzle, 0, 1, 2))" 1427 | ] 1428 | }, 1429 | { 1430 | "cell_type": "code", 1431 | "execution_count": 211, 1432 | "metadata": { 1433 | "collapsed": false 1434 | }, 1435 | "outputs": [ 1436 | { 1437 | "data": { 1438 | "text/plain": [ 1439 | "False" 1440 | ] 1441 | }, 1442 | "execution_count": 211, 1443 | "metadata": {}, 1444 | "output_type": "execute_result" 1445 | } 1446 | ], 1447 | "source": [ 1448 | "# This should not be valid because there are duplicate '1's in the first column\n", 1449 | "is_valid(copy_and_set(puzzle, 2, 0, 1))" 1450 | ] 1451 | }, 1452 | { 1453 | "cell_type": "code", 1454 | "execution_count": 212, 1455 | "metadata": { 1456 | "collapsed": false 1457 | }, 1458 | "outputs": [ 1459 | { 1460 | "data": { 1461 | "text/plain": [ 1462 | "False" 1463 | ] 1464 | }, 1465 | "execution_count": 212, 1466 | "metadata": {}, 1467 | "output_type": "execute_result" 1468 | } 1469 | ], 1470 | "source": [ 1471 | "# This should not be valid because there are duplicate '1's in the first section\n", 1472 | "is_valid(copy_and_set(puzzle, 1, 1, 1))" 1473 | ] 1474 | }, 1475 | { 1476 | "cell_type": "markdown", 1477 | "metadata": {}, 1478 | "source": [ 1479 | "### solve_sudoku" 1480 | ] 1481 | }, 1482 | { 1483 | "cell_type": "code", 1484 | "execution_count": 213, 1485 | "metadata": { 1486 | "collapsed": false 1487 | }, 1488 | "outputs": [], 1489 | "source": [ 1490 | "def solve_sudoku(board, index=0):\n", 1491 | " row_index = index // len(board)\n", 1492 | " col_index = index % len(board)\n", 1493 | " \n", 1494 | " if (index > len(board)**2 - 1) or \\\n", 1495 | " (index == len(board)**2 - 1 and board[row_index][col_index] is not None):\n", 1496 | " if is_valid(board):\n", 1497 | " print('Solution found!')\n", 1498 | " return board\n", 1499 | " else:\n", 1500 | " # No solution to this puzzle\n", 1501 | " print('No solution to puzzle')\n", 1502 | " return None\n", 1503 | " \n", 1504 | " if board[row_index][col_index] != None: \n", 1505 | " # Meaning it's a hard-coded value in the puzzle\n", 1506 | " return solve_sudoku(board, index+1)\n", 1507 | " \n", 1508 | " for num in range(1, len(board)+1):\n", 1509 | " with_num_set = copy_and_set(board, row_index, col_index, num)\n", 1510 | " if is_valid(with_num_set):\n", 1511 | " solution_or_none = solve_sudoku(with_num_set, index+1)\n", 1512 | " if solution_or_none is not None:\n", 1513 | " return solution_or_none\n", 1514 | "\n", 1515 | " # No solution found on this path\n", 1516 | " return None" 1517 | ] 1518 | }, 1519 | { 1520 | "cell_type": "code", 1521 | "execution_count": 214, 1522 | "metadata": { 1523 | "collapsed": false 1524 | }, 1525 | "outputs": [ 1526 | { 1527 | "name": "stdout", 1528 | "output_type": "stream", 1529 | "text": [ 1530 | "Solution found!\n" 1531 | ] 1532 | }, 1533 | { 1534 | "data": { 1535 | "text/plain": [ 1536 | "array([[1, 4, 3, 2],\n", 1537 | " [3, 2, 4, 1],\n", 1538 | " [2, 3, 1, 4],\n", 1539 | " [4, 1, 2, 3]])" 1540 | ] 1541 | }, 1542 | "execution_count": 214, 1543 | "metadata": {}, 1544 | "output_type": "execute_result" 1545 | } 1546 | ], 1547 | "source": [ 1548 | "solution = solve_sudoku(puzzle)\n", 1549 | "np.reshape(solution, (4, -1))" 1550 | ] 1551 | }, 1552 | { 1553 | "cell_type": "markdown", 1554 | "metadata": {}, 1555 | "source": [ 1556 | "## Let's try the backtracking solution on a 9x9 now" 1557 | ] 1558 | }, 1559 | { 1560 | "cell_type": "code", 1561 | "execution_count": 215, 1562 | "metadata": { 1563 | "collapsed": false 1564 | }, 1565 | "outputs": [], 1566 | "source": [ 1567 | "p_9x9 = [[8, N, N, 1, 6, N, N, N, N],\n", 1568 | " [4, 1, N, 9, N, 8, N, N, 6],\n", 1569 | " [N, N, N, N, N, N, N, 1, N],\n", 1570 | " [N, N, N, N, 8, 6, N, N, 3],\n", 1571 | " [N, 8, 9, N, 3, N, 2, 5, N],\n", 1572 | " [3, N, N, 2, 9, N, N, N, N],\n", 1573 | " [N, 2, N, N, N, N, N, N, N],\n", 1574 | " [7, N, N, 8, N, 4, N, 6, 5],\n", 1575 | " [N, N, N, N, 5, 7, N, N, 4]]" 1576 | ] 1577 | }, 1578 | { 1579 | "cell_type": "code", 1580 | "execution_count": 216, 1581 | "metadata": { 1582 | "collapsed": false 1583 | }, 1584 | "outputs": [ 1585 | { 1586 | "name": "stdout", 1587 | "output_type": "stream", 1588 | "text": [ 1589 | "Solution found!\n" 1590 | ] 1591 | }, 1592 | { 1593 | "data": { 1594 | "text/plain": [ 1595 | "array([[8, 3, 5, 1, 6, 2, 7, 4, 9],\n", 1596 | " [4, 1, 2, 9, 7, 8, 5, 3, 6],\n", 1597 | " [9, 7, 6, 5, 4, 3, 8, 1, 2],\n", 1598 | " [2, 5, 1, 7, 8, 6, 4, 9, 3],\n", 1599 | " [6, 8, 9, 4, 3, 1, 2, 5, 7],\n", 1600 | " [3, 4, 7, 2, 9, 5, 6, 8, 1],\n", 1601 | " [5, 2, 4, 6, 1, 9, 3, 7, 8],\n", 1602 | " [7, 9, 3, 8, 2, 4, 1, 6, 5],\n", 1603 | " [1, 6, 8, 3, 5, 7, 9, 2, 4]])" 1604 | ] 1605 | }, 1606 | "execution_count": 216, 1607 | "metadata": {}, 1608 | "output_type": "execute_result" 1609 | } 1610 | ], 1611 | "source": [ 1612 | "solution = solve_sudoku(p_9x9)\n", 1613 | "np.reshape(solution, (9, -1))" 1614 | ] 1615 | }, 1616 | { 1617 | "cell_type": "markdown", 1618 | "metadata": {}, 1619 | "source": [ 1620 | "# Conclusion\n", 1621 | "\n", 1622 | "So backtracking, unsurprisingly, works well. But still, it was cool to see what Sudoku looks like as a system of equations right? :)" 1623 | ] 1624 | } 1625 | ], 1626 | "metadata": { 1627 | "kernelspec": { 1628 | "display_name": "Python 3", 1629 | "language": "python", 1630 | "name": "python3" 1631 | }, 1632 | "language_info": { 1633 | "codemirror_mode": { 1634 | "name": "ipython", 1635 | "version": 3 1636 | }, 1637 | "file_extension": ".py", 1638 | "mimetype": "text/x-python", 1639 | "name": "python", 1640 | "nbconvert_exporter": "python", 1641 | "pygments_lexer": "ipython3", 1642 | "version": "3.5.1" 1643 | } 1644 | }, 1645 | "nbformat": 4, 1646 | "nbformat_minor": 0 1647 | } 1648 | -------------------------------------------------------------------------------- /UnderstandingCorrelateFunction.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import scipy.signal\n", 12 | "import numpy as np" 13 | ] 14 | }, 15 | { 16 | "cell_type": "markdown", 17 | "metadata": {}, 18 | "source": [ 19 | "### Same-sized arrays" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 2, 25 | "metadata": { 26 | "collapsed": false 27 | }, 28 | "outputs": [ 29 | { 30 | "data": { 31 | "text/plain": [ 32 | "array([ 3, 8, 14, 8, 3])" 33 | ] 34 | }, 35 | "execution_count": 2, 36 | "metadata": {}, 37 | "output_type": "execute_result" 38 | } 39 | ], 40 | "source": [ 41 | "v1 = np.asarray([1,2,3])\n", 42 | "v2 = np.asarray([1,2,3])\n", 43 | "scipy.signal.correlate(v1,v2)" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": 3, 49 | "metadata": { 50 | "collapsed": false 51 | }, 52 | "outputs": [ 53 | { 54 | "data": { 55 | "text/plain": [ 56 | "array([ 8, 14, 8])" 57 | ] 58 | }, 59 | "execution_count": 3, 60 | "metadata": {}, 61 | "output_type": "execute_result" 62 | } 63 | ], 64 | "source": [ 65 | "scipy.signal.correlate(v1,v2,mode=\"same\")" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": 4, 71 | "metadata": { 72 | "collapsed": false 73 | }, 74 | "outputs": [ 75 | { 76 | "data": { 77 | "text/plain": [ 78 | "array([14])" 79 | ] 80 | }, 81 | "execution_count": 4, 82 | "metadata": {}, 83 | "output_type": "execute_result" 84 | } 85 | ], 86 | "source": [ 87 | "scipy.signal.correlate(v1,v2,mode=\"valid\")" 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": 5, 93 | "metadata": { 94 | "collapsed": false 95 | }, 96 | "outputs": [ 97 | { 98 | "data": { 99 | "text/plain": [ 100 | "14" 101 | ] 102 | }, 103 | "execution_count": 5, 104 | "metadata": {}, 105 | "output_type": "execute_result" 106 | } 107 | ], 108 | "source": [ 109 | "np.dot(v1,v2)" 110 | ] 111 | }, 112 | { 113 | "cell_type": "markdown", 114 | "metadata": {}, 115 | "source": [ 116 | "### Different-sized arrays" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": 6, 122 | "metadata": { 123 | "collapsed": false 124 | }, 125 | "outputs": [ 126 | { 127 | "data": { 128 | "text/plain": [ 129 | "array([ 6, 17, 28, 39, 50, 61, 30])" 130 | ] 131 | }, 132 | "execution_count": 6, 133 | "metadata": {}, 134 | "output_type": "execute_result" 135 | } 136 | ], 137 | "source": [ 138 | "v3 = np.asarray([1,2,3,4,5,6])\n", 139 | "v4 = np.asarray([5,6])\n", 140 | "scipy.signal.correlate(v3,v4,mode=\"full\")" 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": 7, 146 | "metadata": { 147 | "collapsed": false 148 | }, 149 | "outputs": [ 150 | { 151 | "data": { 152 | "text/plain": [ 153 | "array([ 6, 17, 28, 39, 50, 61])" 154 | ] 155 | }, 156 | "execution_count": 7, 157 | "metadata": {}, 158 | "output_type": "execute_result" 159 | } 160 | ], 161 | "source": [ 162 | "scipy.signal.correlate(v3,v4,mode=\"same\")" 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": 8, 168 | "metadata": { 169 | "collapsed": false 170 | }, 171 | "outputs": [ 172 | { 173 | "data": { 174 | "text/plain": [ 175 | "array([17, 28, 39, 50, 61])" 176 | ] 177 | }, 178 | "execution_count": 8, 179 | "metadata": {}, 180 | "output_type": "execute_result" 181 | } 182 | ], 183 | "source": [ 184 | "scipy.signal.correlate(v3,v4,mode=\"valid\")" 185 | ] 186 | } 187 | ], 188 | "metadata": { 189 | "kernelspec": { 190 | "display_name": "Python 3", 191 | "language": "python", 192 | "name": "python3" 193 | }, 194 | "language_info": { 195 | "codemirror_mode": { 196 | "name": "ipython", 197 | "version": 3 198 | }, 199 | "file_extension": ".py", 200 | "mimetype": "text/x-python", 201 | "name": "python", 202 | "nbconvert_exporter": "python", 203 | "pygments_lexer": "ipython3", 204 | "version": "3.5.1" 205 | } 206 | }, 207 | "nbformat": 4, 208 | "nbformat_minor": 0 209 | } 210 | -------------------------------------------------------------------------------- /fixed-point-functions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "tender-finland", 6 | "metadata": {}, 7 | "source": [ 8 | "# Fixed-point functions\n", 9 | "\n", 10 | "### First, let's find the golden ratio\n", 11 | "\n", 12 | "a:b is as b:a+b.\n", 13 | "\n", 14 | "Another way to write this is:\n", 15 | "\n", 16 | "a/b = b/(a+b)\n", 17 | "\n", 18 | "Alternatively we could flip the fractions:\n", 19 | "\n", 20 | "b/a = (a+b)/b.\n", 21 | "\n", 22 | "If we assume a=1, we can simplify to:\n", 23 | "\n", 24 | "b = (b+1)/b.\n", 25 | "\n", 26 | "We could also write this as:\n", 27 | "\n", 28 | "f(x) = (x+1)/x.\n", 29 | "\n", 30 | "Remember we are trying to find x. So:\n", 31 | "\n", 32 | "x = f(x)\n", 33 | "\n", 34 | "If this is the case, x = f(x) = f( f(x) ) = f( f( f(x) ) ), etc.\n", 35 | "\n", 36 | "So we can do fixed-point iteration to find the solution..." 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 2, 42 | "id": "monthly-active", 43 | "metadata": {}, 44 | "outputs": [], 45 | "source": [ 46 | "def golden(x):\n", 47 | " return (x+1)/x" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": 4, 53 | "id": "activated-klein", 54 | "metadata": {}, 55 | "outputs": [ 56 | { 57 | "data": { 58 | "text/plain": [ 59 | "2.0" 60 | ] 61 | }, 62 | "execution_count": 4, 63 | "metadata": {}, 64 | "output_type": "execute_result" 65 | } 66 | ], 67 | "source": [ 68 | "golden(1)" 69 | ] 70 | }, 71 | { 72 | "cell_type": "code", 73 | "execution_count": 5, 74 | "id": "solid-offense", 75 | "metadata": {}, 76 | "outputs": [ 77 | { 78 | "data": { 79 | "text/plain": [ 80 | "1.5" 81 | ] 82 | }, 83 | "execution_count": 5, 84 | "metadata": {}, 85 | "output_type": "execute_result" 86 | } 87 | ], 88 | "source": [ 89 | "golden(golden(1))" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": 6, 95 | "id": "alternative-average", 96 | "metadata": {}, 97 | "outputs": [ 98 | { 99 | "data": { 100 | "text/plain": [ 101 | "1.6666666666666667" 102 | ] 103 | }, 104 | "execution_count": 6, 105 | "metadata": {}, 106 | "output_type": "execute_result" 107 | } 108 | ], 109 | "source": [ 110 | "golden(golden(golden(1)))" 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": 7, 116 | "id": "administrative-captain", 117 | "metadata": {}, 118 | "outputs": [ 119 | { 120 | "data": { 121 | "text/plain": [ 122 | "1.6" 123 | ] 124 | }, 125 | "execution_count": 7, 126 | "metadata": {}, 127 | "output_type": "execute_result" 128 | } 129 | ], 130 | "source": [ 131 | "golden(golden(golden(golden(1))))" 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": 8, 137 | "id": "contemporary-veteran", 138 | "metadata": {}, 139 | "outputs": [ 140 | { 141 | "data": { 142 | "text/plain": [ 143 | "1.625" 144 | ] 145 | }, 146 | "execution_count": 8, 147 | "metadata": {}, 148 | "output_type": "execute_result" 149 | } 150 | ], 151 | "source": [ 152 | "golden(golden(golden(golden(golden(1)))))" 153 | ] 154 | }, 155 | { 156 | "cell_type": "code", 157 | "execution_count": 9, 158 | "id": "cooperative-harmony", 159 | "metadata": {}, 160 | "outputs": [ 161 | { 162 | "data": { 163 | "text/plain": [ 164 | "1.6153846153846154" 165 | ] 166 | }, 167 | "execution_count": 9, 168 | "metadata": {}, 169 | "output_type": "execute_result" 170 | } 171 | ], 172 | "source": [ 173 | "golden(golden(golden(golden(golden(golden(1))))))" 174 | ] 175 | }, 176 | { 177 | "cell_type": "code", 178 | "execution_count": 10, 179 | "id": "elect-reset", 180 | "metadata": {}, 181 | "outputs": [ 182 | { 183 | "data": { 184 | "text/plain": [ 185 | "1.619047619047619" 186 | ] 187 | }, 188 | "execution_count": 10, 189 | "metadata": {}, 190 | "output_type": "execute_result" 191 | } 192 | ], 193 | "source": [ 194 | "golden(golden(golden(golden(golden(golden(golden(1)))))))" 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": 11, 200 | "id": "latest-sleeve", 201 | "metadata": {}, 202 | "outputs": [ 203 | { 204 | "data": { 205 | "text/plain": [ 206 | "1.6176470588235294" 207 | ] 208 | }, 209 | "execution_count": 11, 210 | "metadata": {}, 211 | "output_type": "execute_result" 212 | } 213 | ], 214 | "source": [ 215 | "golden(golden(golden(golden(golden(golden(golden(golden(1))))))))" 216 | ] 217 | }, 218 | { 219 | "cell_type": "code", 220 | "execution_count": 12, 221 | "id": "sophisticated-alignment", 222 | "metadata": {}, 223 | "outputs": [ 224 | { 225 | "data": { 226 | "text/plain": [ 227 | "1.6181818181818182" 228 | ] 229 | }, 230 | "execution_count": 12, 231 | "metadata": {}, 232 | "output_type": "execute_result" 233 | } 234 | ], 235 | "source": [ 236 | "golden(golden(golden(golden(golden(golden(golden(golden(golden(1)))))))))" 237 | ] 238 | }, 239 | { 240 | "cell_type": "markdown", 241 | "id": "legendary-renewal", 242 | "metadata": {}, 243 | "source": [ 244 | "This should also work, no matter the starting point..." 245 | ] 246 | }, 247 | { 248 | "cell_type": "code", 249 | "execution_count": 13, 250 | "id": "organizational-graphic", 251 | "metadata": {}, 252 | "outputs": [ 253 | { 254 | "data": { 255 | "text/plain": [ 256 | "1.6176559204579692" 257 | ] 258 | }, 259 | "execution_count": 13, 260 | "metadata": {}, 261 | "output_type": "execute_result" 262 | } 263 | ], 264 | "source": [ 265 | "golden(golden(golden(golden(golden(golden(golden(golden(golden(97)))))))))" 266 | ] 267 | }, 268 | { 269 | "cell_type": "markdown", 270 | "id": "bored-infrared", 271 | "metadata": {}, 272 | "source": [ 273 | "This function is defined such that a recursive execution of it is like a honing missle for the golden ratio.\n", 274 | "\n", 275 | "This seems very similar to how evolution may work. People output people, and by iterating, we get better.\n", 276 | "\n", 277 | "Perhaps any function that can be so framed \"automatically\" hones in to an optimal solution." 278 | ] 279 | }, 280 | { 281 | "cell_type": "code", 282 | "execution_count": 14, 283 | "id": "multiple-paraguay", 284 | "metadata": {}, 285 | "outputs": [ 286 | { 287 | "data": { 288 | "text/plain": [ 289 | "(1.2-0.4j)" 290 | ] 291 | }, 292 | "execution_count": 14, 293 | "metadata": {}, 294 | "output_type": "execute_result" 295 | } 296 | ], 297 | "source": [ 298 | "golden(1+2j)" 299 | ] 300 | }, 301 | { 302 | "cell_type": "code", 303 | "execution_count": 15, 304 | "id": "scientific-ivory", 305 | "metadata": {}, 306 | "outputs": [ 307 | { 308 | "data": { 309 | "text/plain": [ 310 | "(1.6178585436004707-0.0002614720878546216j)" 311 | ] 312 | }, 313 | "execution_count": 15, 314 | "metadata": {}, 315 | "output_type": "execute_result" 316 | } 317 | ], 318 | "source": [ 319 | "golden(golden(golden(golden(golden(golden(golden(golden(golden(1+2j)))))))))" 320 | ] 321 | }, 322 | { 323 | "cell_type": "markdown", 324 | "id": "inclusive-champagne", 325 | "metadata": {}, 326 | "source": [ 327 | "# What about other fixed-point functions?" 328 | ] 329 | }, 330 | { 331 | "cell_type": "code", 332 | "execution_count": 16, 333 | "id": "widespread-sword", 334 | "metadata": {}, 335 | "outputs": [], 336 | "source": [ 337 | "def fixed1(x):\n", 338 | " return x*2+1" 339 | ] 340 | }, 341 | { 342 | "cell_type": "code", 343 | "execution_count": 17, 344 | "id": "other-leave", 345 | "metadata": {}, 346 | "outputs": [ 347 | { 348 | "data": { 349 | "text/plain": [ 350 | "3" 351 | ] 352 | }, 353 | "execution_count": 17, 354 | "metadata": {}, 355 | "output_type": "execute_result" 356 | } 357 | ], 358 | "source": [ 359 | "fixed1(1)" 360 | ] 361 | }, 362 | { 363 | "cell_type": "code", 364 | "execution_count": 18, 365 | "id": "important-hungary", 366 | "metadata": {}, 367 | "outputs": [ 368 | { 369 | "data": { 370 | "text/plain": [ 371 | "7" 372 | ] 373 | }, 374 | "execution_count": 18, 375 | "metadata": {}, 376 | "output_type": "execute_result" 377 | } 378 | ], 379 | "source": [ 380 | "fixed1(fixed1(1))" 381 | ] 382 | }, 383 | { 384 | "cell_type": "code", 385 | "execution_count": 19, 386 | "id": "allied-collector", 387 | "metadata": {}, 388 | "outputs": [ 389 | { 390 | "data": { 391 | "text/plain": [ 392 | "15" 393 | ] 394 | }, 395 | "execution_count": 19, 396 | "metadata": {}, 397 | "output_type": "execute_result" 398 | } 399 | ], 400 | "source": [ 401 | "fixed1(fixed1(fixed1(1)))" 402 | ] 403 | }, 404 | { 405 | "cell_type": "code", 406 | "execution_count": 20, 407 | "id": "polar-brain", 408 | "metadata": {}, 409 | "outputs": [ 410 | { 411 | "data": { 412 | "text/plain": [ 413 | "31" 414 | ] 415 | }, 416 | "execution_count": 20, 417 | "metadata": {}, 418 | "output_type": "execute_result" 419 | } 420 | ], 421 | "source": [ 422 | "fixed1(fixed1(fixed1(fixed1(1))))" 423 | ] 424 | }, 425 | { 426 | "cell_type": "markdown", 427 | "id": "injured-persian", 428 | "metadata": {}, 429 | "source": [ 430 | "Clearly, some iterated-on functions diverge. This is like a repulsive force.\n", 431 | "\n", 432 | "Recursively-iterated functions which diverge are like repulsers. Recursively-iterated functions which converge are like attractors.\n", 433 | "\n", 434 | "Question: Is there a simple way to know if a function converges or diverges? Also, some functions will converge in a specific area and diverge in others, like the Mandelbrot function: z=z**2+c." 435 | ] 436 | }, 437 | { 438 | "cell_type": "code", 439 | "execution_count": 21, 440 | "id": "exclusive-wilderness", 441 | "metadata": {}, 442 | "outputs": [], 443 | "source": [ 444 | "def mandelbrot(z, c):\n", 445 | " return z**2+c" 446 | ] 447 | }, 448 | { 449 | "cell_type": "code", 450 | "execution_count": 22, 451 | "id": "humanitarian-eclipse", 452 | "metadata": {}, 453 | "outputs": [ 454 | { 455 | "data": { 456 | "text/plain": [ 457 | "2" 458 | ] 459 | }, 460 | "execution_count": 22, 461 | "metadata": {}, 462 | "output_type": "execute_result" 463 | } 464 | ], 465 | "source": [ 466 | "mandelbrot(1, 1)" 467 | ] 468 | }, 469 | { 470 | "cell_type": "code", 471 | "execution_count": 23, 472 | "id": "mineral-canadian", 473 | "metadata": {}, 474 | "outputs": [ 475 | { 476 | "data": { 477 | "text/plain": [ 478 | "5" 479 | ] 480 | }, 481 | "execution_count": 23, 482 | "metadata": {}, 483 | "output_type": "execute_result" 484 | } 485 | ], 486 | "source": [ 487 | "mandelbrot(mandelbrot(1, 1), 1)" 488 | ] 489 | }, 490 | { 491 | "cell_type": "code", 492 | "execution_count": 25, 493 | "id": "optical-cattle", 494 | "metadata": {}, 495 | "outputs": [ 496 | { 497 | "data": { 498 | "text/plain": [ 499 | "26" 500 | ] 501 | }, 502 | "execution_count": 25, 503 | "metadata": {}, 504 | "output_type": "execute_result" 505 | } 506 | ], 507 | "source": [ 508 | "# diverge at 1+0j\n", 509 | "mandelbrot(mandelbrot(mandelbrot(1, 1), 1), 1)" 510 | ] 511 | }, 512 | { 513 | "cell_type": "code", 514 | "execution_count": 27, 515 | "id": "bridal-rover", 516 | "metadata": {}, 517 | "outputs": [ 518 | { 519 | "data": { 520 | "text/plain": [ 521 | "0" 522 | ] 523 | }, 524 | "execution_count": 27, 525 | "metadata": {}, 526 | "output_type": "execute_result" 527 | } 528 | ], 529 | "source": [ 530 | "# converge at 0+0j\n", 531 | "mandelbrot(mandelbrot(mandelbrot(0, 0), 0), 0)" 532 | ] 533 | }, 534 | { 535 | "cell_type": "code", 536 | "execution_count": 29, 537 | "id": "successful-qatar", 538 | "metadata": {}, 539 | "outputs": [ 540 | { 541 | "data": { 542 | "text/plain": [ 543 | "0" 544 | ] 545 | }, 546 | "execution_count": 29, 547 | "metadata": {}, 548 | "output_type": "execute_result" 549 | } 550 | ], 551 | "source": [ 552 | "# converge at -1+0j\n", 553 | "mandelbrot(mandelbrot(mandelbrot(-1, -1), -1), -1)" 554 | ] 555 | }, 556 | { 557 | "cell_type": "markdown", 558 | "id": "protected-registrar", 559 | "metadata": {}, 560 | "source": [ 561 | "# The line between attraction and repulsion\n", 562 | "\n", 563 | "One very simple function that is an attractor in some places and a repulser in another is: f(x) = x**2. If x<1, it converges, and if x>1 it diverges..." 564 | ] 565 | }, 566 | { 567 | "cell_type": "code", 568 | "execution_count": 30, 569 | "id": "romance-leader", 570 | "metadata": {}, 571 | "outputs": [], 572 | "source": [ 573 | "def square(x):\n", 574 | " return x**2" 575 | ] 576 | }, 577 | { 578 | "cell_type": "code", 579 | "execution_count": 31, 580 | "id": "breathing-screen", 581 | "metadata": {}, 582 | "outputs": [ 583 | { 584 | "data": { 585 | "text/plain": [ 586 | "4" 587 | ] 588 | }, 589 | "execution_count": 31, 590 | "metadata": {}, 591 | "output_type": "execute_result" 592 | } 593 | ], 594 | "source": [ 595 | "square(2)" 596 | ] 597 | }, 598 | { 599 | "cell_type": "code", 600 | "execution_count": 32, 601 | "id": "composite-nightlife", 602 | "metadata": {}, 603 | "outputs": [ 604 | { 605 | "data": { 606 | "text/plain": [ 607 | "16" 608 | ] 609 | }, 610 | "execution_count": 32, 611 | "metadata": {}, 612 | "output_type": "execute_result" 613 | } 614 | ], 615 | "source": [ 616 | "square(square(2))" 617 | ] 618 | }, 619 | { 620 | "cell_type": "code", 621 | "execution_count": 33, 622 | "id": "figured-hurricane", 623 | "metadata": {}, 624 | "outputs": [ 625 | { 626 | "data": { 627 | "text/plain": [ 628 | "256" 629 | ] 630 | }, 631 | "execution_count": 33, 632 | "metadata": {}, 633 | "output_type": "execute_result" 634 | } 635 | ], 636 | "source": [ 637 | "square(square(square(2)))" 638 | ] 639 | }, 640 | { 641 | "cell_type": "code", 642 | "execution_count": 34, 643 | "id": "fundamental-force", 644 | "metadata": {}, 645 | "outputs": [ 646 | { 647 | "data": { 648 | "text/plain": [ 649 | "65536" 650 | ] 651 | }, 652 | "execution_count": 34, 653 | "metadata": {}, 654 | "output_type": "execute_result" 655 | } 656 | ], 657 | "source": [ 658 | "square(square(square(square(2))))" 659 | ] 660 | }, 661 | { 662 | "cell_type": "markdown", 663 | "id": "serial-sally", 664 | "metadata": {}, 665 | "source": [ 666 | "For brevity, let's define a function to iterate a function so we don't keep having to." 667 | ] 668 | }, 669 | { 670 | "cell_type": "code", 671 | "execution_count": 38, 672 | "id": "spiritual-kernel", 673 | "metadata": {}, 674 | "outputs": [], 675 | "source": [ 676 | "def iterate(func, start, num_times):\n", 677 | " val = start\n", 678 | " val_list = []\n", 679 | " for i in range(num_times+1):\n", 680 | " val_list.append(val)\n", 681 | " val = func(val)\n", 682 | " return val_list" 683 | ] 684 | }, 685 | { 686 | "cell_type": "code", 687 | "execution_count": 39, 688 | "id": "accessible-flash", 689 | "metadata": {}, 690 | "outputs": [ 691 | { 692 | "data": { 693 | "text/plain": [ 694 | "[2, 4, 16, 256, 65536]" 695 | ] 696 | }, 697 | "execution_count": 39, 698 | "metadata": {}, 699 | "output_type": "execute_result" 700 | } 701 | ], 702 | "source": [ 703 | "iterate(square, 2, 4)" 704 | ] 705 | }, 706 | { 707 | "cell_type": "code", 708 | "execution_count": 42, 709 | "id": "imported-convert", 710 | "metadata": {}, 711 | "outputs": [ 712 | { 713 | "data": { 714 | "text/plain": [ 715 | "[1,\n", 716 | " 2.0,\n", 717 | " 1.5,\n", 718 | " 1.6666666666666667,\n", 719 | " 1.6,\n", 720 | " 1.625,\n", 721 | " 1.6153846153846154,\n", 722 | " 1.619047619047619,\n", 723 | " 1.6176470588235294,\n", 724 | " 1.6181818181818182,\n", 725 | " 1.6179775280898876,\n", 726 | " 1.6180555555555556,\n", 727 | " 1.6180257510729612]" 728 | ] 729 | }, 730 | "execution_count": 42, 731 | "metadata": {}, 732 | "output_type": "execute_result" 733 | } 734 | ], 735 | "source": [ 736 | "iterate(golden, 1, 12)" 737 | ] 738 | }, 739 | { 740 | "cell_type": "code", 741 | "execution_count": 50, 742 | "id": "popular-century", 743 | "metadata": {}, 744 | "outputs": [ 745 | { 746 | "data": { 747 | "text/plain": [ 748 | "[1.1,\n", 749 | " 1.2100000000000002,\n", 750 | " 1.4641000000000004,\n", 751 | " 2.143588810000001,\n", 752 | " 4.594972986357221,\n", 753 | " 21.1137767453526,\n", 754 | " 445.7915684525922]" 755 | ] 756 | }, 757 | "execution_count": 50, 758 | "metadata": {}, 759 | "output_type": "execute_result" 760 | } 761 | ], 762 | "source": [ 763 | "# >1 diverges\n", 764 | "iterate(square, 1.1, 6)" 765 | ] 766 | }, 767 | { 768 | "cell_type": "code", 769 | "execution_count": 51, 770 | "id": "periodic-inquiry", 771 | "metadata": {}, 772 | "outputs": [ 773 | { 774 | "data": { 775 | "text/plain": [ 776 | "[0.9,\n", 777 | " 0.81,\n", 778 | " 0.6561000000000001,\n", 779 | " 0.43046721000000016,\n", 780 | " 0.18530201888518424,\n", 781 | " 0.03433683820292518,\n", 782 | " 0.001179018457773862]" 783 | ] 784 | }, 785 | "execution_count": 51, 786 | "metadata": {}, 787 | "output_type": "execute_result" 788 | } 789 | ], 790 | "source": [ 791 | "# <1 converges\n", 792 | "iterate(square, 0.9, 6)" 793 | ] 794 | }, 795 | { 796 | "cell_type": "code", 797 | "execution_count": 54, 798 | "id": "directed-needle", 799 | "metadata": {}, 800 | "outputs": [ 801 | { 802 | "data": { 803 | "text/plain": [ 804 | "[-1,\n", 805 | " -2,\n", 806 | " -33,\n", 807 | " -39135394,\n", 808 | " -91801241053644953553642221885011784225,\n", 809 | " -6519927434790921611644421835063525424902891356389795386439001633774343881310329902383751163504348619662623089904493770596045628788998692217412903744200798649266250348434521475456717275390626]" 810 | ] 811 | }, 812 | "execution_count": 54, 813 | "metadata": {}, 814 | "output_type": "execute_result" 815 | } 816 | ], 817 | "source": [ 818 | "iterate(lambda x: x**5-1, -1, 5)" 819 | ] 820 | }, 821 | { 822 | "cell_type": "code", 823 | "execution_count": 56, 824 | "id": "technical-effort", 825 | "metadata": {}, 826 | "outputs": [ 827 | { 828 | "data": { 829 | "text/plain": [ 830 | "[1,\n", 831 | " 3.0,\n", 832 | " 1.6666666666666667,\n", 833 | " 2.2,\n", 834 | " 1.909090909090909,\n", 835 | " 2.047619047619048,\n", 836 | " 1.9767441860465114,\n", 837 | " 2.0117647058823533,\n", 838 | " 1.9941520467836253,\n", 839 | " 2.0029325513196485,\n", 840 | " 1.9985358711566619,\n", 841 | " 2.0007326007326007,\n", 842 | " 1.9996338337605273]" 843 | ] 844 | }, 845 | "execution_count": 56, 846 | "metadata": {}, 847 | "output_type": "execute_result" 848 | } 849 | ], 850 | "source": [ 851 | "iterate(lambda x: (x+2)/x, 1, 12)" 852 | ] 853 | }, 854 | { 855 | "cell_type": "code", 856 | "execution_count": 57, 857 | "id": "meaning-breeding", 858 | "metadata": {}, 859 | "outputs": [ 860 | { 861 | "data": { 862 | "text/plain": [ 863 | "[1,\n", 864 | " 4.0,\n", 865 | " 1.75,\n", 866 | " 2.7142857142857144,\n", 867 | " 2.1052631578947367,\n", 868 | " 2.425,\n", 869 | " 2.2371134020618557,\n", 870 | " 2.3410138248847927,\n", 871 | " 2.281496062992126,\n", 872 | " 2.3149266609145815,\n", 873 | " 2.295937383525904,\n", 874 | " 2.306655844155844,\n", 875 | " 2.300584136814695]" 876 | ] 877 | }, 878 | "execution_count": 57, 879 | "metadata": {}, 880 | "output_type": "execute_result" 881 | } 882 | ], 883 | "source": [ 884 | "iterate(lambda x: (x+3)/x, 1, 12)" 885 | ] 886 | }, 887 | { 888 | "cell_type": "code", 889 | "execution_count": 58, 890 | "id": "united-absolute", 891 | "metadata": {}, 892 | "outputs": [ 893 | { 894 | "data": { 895 | "text/plain": [ 896 | "[1,\n", 897 | " 11.0,\n", 898 | " 1.9090909090909092,\n", 899 | " 6.238095238095238,\n", 900 | " 2.6030534351145036,\n", 901 | " 4.841642228739003,\n", 902 | " 3.0654149000605693,\n", 903 | " 4.262201146018573,\n", 904 | " 3.3462055537527235,\n", 905 | " 3.9884595669220433,\n", 906 | " 3.507233640509776,\n", 907 | " 3.851250023521815,\n", 908 | " 3.5965595427261814]" 909 | ] 910 | }, 911 | "execution_count": 58, 912 | "metadata": {}, 913 | "output_type": "execute_result" 914 | } 915 | ], 916 | "source": [ 917 | "iterate(lambda x: (x+10)/x, 1, 12)" 918 | ] 919 | }, 920 | { 921 | "cell_type": "code", 922 | "execution_count": 65, 923 | "id": "completed-carry", 924 | "metadata": {}, 925 | "outputs": [ 926 | { 927 | "data": { 928 | "text/plain": [ 929 | "[1,\n", 930 | " 0.5,\n", 931 | " 0.16666666666666666,\n", 932 | " 0.023809523809523808,\n", 933 | " 0.0005537098560354374,\n", 934 | " 3.0642493416460287e-07,\n", 935 | " 9.389621150564078e-14,\n", 936 | " 8.8164985351112e-27,\n", 937 | " 7.773064641961794e-53,\n", 938 | " 6.042053392811663e-105,\n", 939 | " 3.6506409201586933e-209]" 940 | ] 941 | }, 942 | "execution_count": 65, 943 | "metadata": {}, 944 | "output_type": "execute_result" 945 | } 946 | ], 947 | "source": [ 948 | "iterate(lambda x: (x**2)/(x+1), 1, 10)" 949 | ] 950 | }, 951 | { 952 | "cell_type": "code", 953 | "execution_count": null, 954 | "id": "bibliographic-highland", 955 | "metadata": {}, 956 | "outputs": [], 957 | "source": [] 958 | } 959 | ], 960 | "metadata": { 961 | "kernelspec": { 962 | "display_name": "Python 3", 963 | "language": "python", 964 | "name": "python3" 965 | }, 966 | "language_info": { 967 | "codemirror_mode": { 968 | "name": "ipython", 969 | "version": 3 970 | }, 971 | "file_extension": ".py", 972 | "mimetype": "text/x-python", 973 | "name": "python", 974 | "nbconvert_exporter": "python", 975 | "pygments_lexer": "ipython3", 976 | "version": "3.8.6" 977 | } 978 | }, 979 | "nbformat": 4, 980 | "nbformat_minor": 5 981 | } 982 | -------------------------------------------------------------------------------- /genetic_algorithm.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Genetic Algorithm\n", 8 | "\n", 9 | "The genetic algorithm emulates Evolution by \"breeding\" solutions from previous solutions and applying mutation. The likelihood that a solution \"survives\" is based on its \"fitness\" value (as defined by some \"fitness function\").\n", 10 | "\n", 11 | "### Problem to solve\n", 12 | "\n", 13 | "Let's try using it to solve a simple equation:\n", 14 | "\n", 15 | "* x − y = −1\n", 16 | "* 3x + y = 9\n", 17 | "\n", 18 | "**(The real solution is x=2, y=3)**\n", 19 | "\n", 20 | "## Generic Genetic Algorithm\n", 21 | "\n", 22 | "First, let's write a basic genetic algorithm.\n", 23 | "\n", 24 | "Each \"individual\" will be a list in this form: `[fitness, val1, val2, ...]`. This way, we can simply do a `sort()` to determine fitness, since a sort of a list first considers the first value in the list." 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 15, 30 | "metadata": { 31 | "collapsed": false 32 | }, 33 | "outputs": [], 34 | "source": [ 35 | "import random\n", 36 | "\n", 37 | "def generate_couples(population):\n", 38 | " couples = []\n", 39 | " for i in range(0, len(population)-1, 2):\n", 40 | " c = (population[i], population[i+1])\n", 41 | " couples.append(c)\n", 42 | " return couples\n", 43 | "\n", 44 | "def genetic_algorithm(fit_func, cross_func, mutate_func, init_pop, max_iter=1000,\n", 45 | " max_pop=100, quit_at_err=0.01, mut_prob=0.1, mut_mu=0, mut_sigma=1):\n", 46 | " population = init_pop\n", 47 | " num_solutions_considered = 0\n", 48 | " top_dog = init_pop[0]\n", 49 | " \n", 50 | " for i in range(max_iter): \n", 51 | " # Calculate fitness function for each individual\n", 52 | " for individual in population:\n", 53 | " fitness = individual[0]\n", 54 | " if fitness < 0: # Meaning it has not been calculated, since fitness is always positive or 0\n", 55 | " fitness = fit_func(individual)\n", 56 | " individual[0] = fitness\n", 57 | "\n", 58 | " # Sort population by fitness - this is needed both to find the top dog (to see if we have a\n", 59 | " # good-enough solution) and to put the population in order for pariing up as couples.\n", 60 | " population.sort()\n", 61 | " num_solutions_considered += len(population)\n", 62 | " \n", 63 | " # Find the \"most fit\" individual. If this solution is close enough (error less than quit_at_err),\n", 64 | " # then just stop the algorithm and return this solution.\n", 65 | " top_dog = population[0]\n", 66 | " if top_dog[0] < quit_at_err:\n", 67 | " print('Solutions considered: {}, Error: {}'.format(num_solutions_considered, top_dog[0]))\n", 68 | " return top_dog\n", 69 | " \n", 70 | " # Make couples - they pair up according to fitness\n", 71 | " couples = generate_couples(population)\n", 72 | " \n", 73 | " # Mate - the baby's genetics are created by taking the x from one of the parents and y from the other,\n", 74 | " # and then applying mutation (to randomly tweak the 'genes')\n", 75 | " babies = [mutate(have_sex(couple[0], couple[1]), mut_prob, mut_mu, mut_sigma) for couple in couples]\n", 76 | " population += babies\n", 77 | " \n", 78 | " # Sort by fitness and cull (simulating limited resources) - only the strong survive\n", 79 | " population.sort()\n", 80 | " population = population[:max_pop]\n", 81 | "\n", 82 | " print('Solutions considered: {}, Error: {}'.format(num_solutions_considered, top_dog[0]))\n", 83 | " return top_dog\n" 84 | ] 85 | }, 86 | { 87 | "cell_type": "markdown", 88 | "metadata": {}, 89 | "source": [ 90 | "## Write problem-specific functions" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": 16, 96 | "metadata": { 97 | "collapsed": false 98 | }, 99 | "outputs": [ 100 | { 101 | "name": "stdout", 102 | "output_type": "stream", 103 | "text": [ 104 | "Solutions considered: 5583, Error: 0.0051585675279162005\n" 105 | ] 106 | }, 107 | { 108 | "data": { 109 | "text/plain": [ 110 | "[0.0051585675279162005, 1.9800869495118212, 2.988398522965479]" 111 | ] 112 | }, 113 | "execution_count": 16, 114 | "metadata": {}, 115 | "output_type": "execute_result" 116 | } 117 | ], 118 | "source": [ 119 | "def fitness_function(individual):\n", 120 | " fitness, x, y = individual\n", 121 | " # We square the error so it's always positive\n", 122 | " eq1_error = ((x - y) - (-1))**2\n", 123 | " eq2_error = ((3*x + y) - 9)**2\n", 124 | " return eq1_error + eq2_error\n", 125 | "\n", 126 | "def have_sex(a, b):\n", 127 | " \"\"\" Given parents a and b, take the x value from a and the y value from b.\n", 128 | " This is similar to how humans get half their genetic material from the father and half\n", 129 | " from the mother. \"\"\"\n", 130 | " x = a[1]\n", 131 | " y = b[2]\n", 132 | " # Set fitness at -1, indicating it hasn't been calculated yet (i.e. the babies haven't \n", 133 | " # been tested in the wild yet)\n", 134 | " return [-1, x, y]\n", 135 | "\n", 136 | "def mutate(a, mutation_probability, mu, sigma):\n", 137 | " \"\"\" Mutate the values for x and y according to the probability of mutation - \n", 138 | " if mutation_probability is 0.01, then there is a 1% chance of a value being mutated.\n", 139 | " \n", 140 | " If we mutate, we do so by generating a random number according to the normal distribution\n", 141 | " as specified by mu (mean) and sigma (standard deviation).\n", 142 | " \"\"\"\n", 143 | " mutant = [-1]\n", 144 | " for var in a[1:]:\n", 145 | " if random.random() <= mutation_probability:\n", 146 | " new_var = var + random.gauss(mu, sigma)\n", 147 | " mutant.append(new_var)\n", 148 | " else:\n", 149 | " mutant.append(var)\n", 150 | " return mutant\n", 151 | "\n", 152 | "initial_population = [[-1, 0, 0], [-1, 10, 10]] \n", 153 | "most_fit_solution = genetic_algorithm(fitness_function, have_sex, mutate, initial_population)\n", 154 | "most_fit_solution" 155 | ] 156 | }, 157 | { 158 | "cell_type": "markdown", 159 | "metadata": {}, 160 | "source": [ 161 | "**Wow, so that's a pretty close solution in not too many generations (or solutions considered).**\n", 162 | "\n", 163 | "Now, let's compare it these 2 types of random searches:\n", 164 | "\n", 165 | "* Random walk\n", 166 | "* Uniform random search (in a constrained window)\n", 167 | "\n", 168 | "## Comparison with random walk solution" 169 | ] 170 | }, 171 | { 172 | "cell_type": "code", 173 | "execution_count": 17, 174 | "metadata": { 175 | "collapsed": false 176 | }, 177 | "outputs": [ 178 | { 179 | "name": "stdout", 180 | "output_type": "stream", 181 | "text": [ 182 | "Solutions considered: 1000000, Error: 2674475.7667086828\n" 183 | ] 184 | }, 185 | { 186 | "data": { 187 | "text/plain": [ 188 | "[-517.4518389625891, 14.596513748922629]" 189 | ] 190 | }, 191 | "execution_count": 17, 192 | "metadata": {}, 193 | "output_type": "execute_result" 194 | } 195 | ], 196 | "source": [ 197 | "def random_walk_next_solution(prev_solution, mu, sigma):\n", 198 | " return [prev_solution[0] + random.gauss(mu, sigma), prev_solution[1] + random.gauss(mu, sigma)]\n", 199 | "\n", 200 | "def error_function(individual):\n", 201 | " x, y = individual\n", 202 | " # We square the error so it's always positive\n", 203 | " eq1_error = ((x - y) - (-1))**2\n", 204 | " eq2_error = ((3*x + y) - 9)**2\n", 205 | " return eq1_error + eq2_error\n", 206 | "\n", 207 | "def do_random_walk_search(mu, sigma, init_guess, max_iter=100*10000+1, quit_at_err=0.01):\n", 208 | " current_solution = init_guess\n", 209 | " \n", 210 | " for i in range(max_iter):\n", 211 | " current_solution = random_walk_next_solution(current_solution, 0, .5)\n", 212 | " current_solution_err = error_function(current_solution)\n", 213 | " if current_solution_err < quit_at_err:\n", 214 | " break\n", 215 | " \n", 216 | " print('Solutions considered: {}, Error: {}'.format(i, current_solution_err))\n", 217 | " return current_solution\n", 218 | "\n", 219 | "do_random_walk_search(0, 0.5, [0, 0])" 220 | ] 221 | }, 222 | { 223 | "cell_type": "markdown", 224 | "metadata": { 225 | "collapsed": true 226 | }, 227 | "source": [ 228 | "That is a pretty bad solution, and a ton of error.\n", 229 | "\n", 230 | "## Comparison with random (non-walk) solution" 231 | ] 232 | }, 233 | { 234 | "cell_type": "code", 235 | "execution_count": 20, 236 | "metadata": { 237 | "collapsed": false 238 | }, 239 | "outputs": [ 240 | { 241 | "name": "stdout", 242 | "output_type": "stream", 243 | "text": [ 244 | "Solutions considered: 258008, Error: 0.003331292698078634\n" 245 | ] 246 | }, 247 | { 248 | "data": { 249 | "text/plain": [ 250 | "[2.0172999771900813, 2.9610554044041812]" 251 | ] 252 | }, 253 | "execution_count": 20, 254 | "metadata": {}, 255 | "output_type": "execute_result" 256 | } 257 | ], 258 | "source": [ 259 | "def random_next_solution(x_min, x_max, y_min, y_max):\n", 260 | " return [random.uniform(x_min, x_max), random.uniform(y_min, y_max)]\n", 261 | "\n", 262 | "def do_random_search(x_min, x_max, y_min, y_max, max_iter=100*10000+1, quit_at_err=0.01):\n", 263 | " for i in range(max_iter):\n", 264 | " # Note that with this solution, we are artificially limiting the search window\n", 265 | " current_solution = random_next_solution(x_min, x_max, y_min, y_max)\n", 266 | " current_solution_err = error_function(current_solution)\n", 267 | " if current_solution_err < quit_at_err:\n", 268 | " break\n", 269 | "\n", 270 | " print('Solutions considered: {}, Error: {}'.format(i, current_solution_err))\n", 271 | " return current_solution\n", 272 | "\n", 273 | "do_random_search(-20, 20, -20, 20)" 274 | ] 275 | }, 276 | { 277 | "cell_type": "markdown", 278 | "metadata": {}, 279 | "source": [ 280 | "So looks like the random uniform search is descent (though nowhere near the efficiency as the Genetic Algorithm). BUT, we had to limit the search window for x and y to (-20, 20). The Genetic Algorithm is is really nice because no such limitation of the window is necessary. With some problems, we don't have such a good idea of where the solution (or a good-enough solution) lies.\n", 281 | "\n", 282 | "Now, look how poorly the random search algorithm behaves when we expand the window to (-100, 100):" 283 | ] 284 | }, 285 | { 286 | "cell_type": "code", 287 | "execution_count": 21, 288 | "metadata": { 289 | "collapsed": false 290 | }, 291 | "outputs": [ 292 | { 293 | "name": "stdout", 294 | "output_type": "stream", 295 | "text": [ 296 | "Solutions considered: 1000000, Error: 13621.719576470292\n" 297 | ] 298 | }, 299 | { 300 | "data": { 301 | "text/plain": [ 302 | "[8.392531191861323, -84.92413223145496]" 303 | ] 304 | }, 305 | "execution_count": 21, 306 | "metadata": {}, 307 | "output_type": "execute_result" 308 | } 309 | ], 310 | "source": [ 311 | "do_random_search(-100, 100, -100, 100)" 312 | ] 313 | }, 314 | { 315 | "cell_type": "markdown", 316 | "metadata": {}, 317 | "source": [ 318 | "*So when we expand the search space, the results are much worse for the uniform random search.*\n", 319 | "\n", 320 | "# Conclusion\n", 321 | "\n", 322 | "Clearly, the genetic algorithm is much more efficient than either a random walk search or a random uniform search." 323 | ] 324 | }, 325 | { 326 | "cell_type": "code", 327 | "execution_count": null, 328 | "metadata": { 329 | "collapsed": true 330 | }, 331 | "outputs": [], 332 | "source": [] 333 | } 334 | ], 335 | "metadata": { 336 | "kernelspec": { 337 | "display_name": "Python 3", 338 | "language": "python", 339 | "name": "python3" 340 | }, 341 | "language_info": { 342 | "codemirror_mode": { 343 | "name": "ipython", 344 | "version": 3 345 | }, 346 | "file_extension": ".py", 347 | "mimetype": "text/x-python", 348 | "name": "python", 349 | "nbconvert_exporter": "python", 350 | "pygments_lexer": "ipython3", 351 | "version": "3.5.1" 352 | } 353 | }, 354 | "nbformat": 4, 355 | "nbformat_minor": 0 356 | } 357 | -------------------------------------------------------------------------------- /images/bull_bear_markov.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebmadrigal/math-with-python/55180de74cbeaf7ee46d8b8503a4ead2017d7063/images/bull_bear_markov.png -------------------------------------------------------------------------------- /images/complex_plane.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebmadrigal/math-with-python/55180de74cbeaf7ee46d8b8503a4ead2017d7063/images/complex_plane.jpg -------------------------------------------------------------------------------- /images/earth_dist.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebmadrigal/math-with-python/55180de74cbeaf7ee46d8b8503a4ead2017d7063/images/earth_dist.JPG -------------------------------------------------------------------------------- /monte_carlo_curve_area.py: -------------------------------------------------------------------------------- 1 | import random, math 2 | 3 | NUM_POINTS = 10000 4 | 5 | # Function for which we want to find area from (x=0 to 10). 6 | f = lambda x: 5 * math.sin(6 * x) + 3 * math.sin(2 * x) + 7 7 | 8 | # Sample rectangle will be (x,y) such that 0 <= x <= 10 and 0 <= y <= 14. 9 | rect_width = 10 10 | rect_height = 14 11 | 12 | # Funcitions to generate samples for x and y respectively. 13 | rand_x = lambda: random.uniform(0, rect_width) 14 | rand_y = lambda: random.uniform(0, rect_height) 15 | 16 | # Generate random sample points. 17 | points = [(rand_x(), rand_y()) for i in xrange(NUM_POINTS)] 18 | 19 | # Find points under our function 20 | points_under = filter(lambda point: point[1] <= f(point[0]), points) 21 | 22 | # Area = area of domain rectangle * num_points_under/num_points_total 23 | area = rect_width * rect_height * len(points_under)*1.0/len(points) 24 | print "Estimate of area under the curve:", area 25 | 26 | -------------------------------------------------------------------------------- /monte_carlo_pi.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | NUM_POINTS = 10000 4 | 5 | # Generates random numbers between -1 and 1 6 | def rand(): return random.uniform(-1,1) 7 | 8 | # Generate a bunch of random points in the square which inscribes the unit circle. 9 | points = [(rand(), rand()) for i in xrange(NUM_POINTS)] 10 | 11 | # Find all points which are inside the circle - i.e. points which match the formula for 12 | # a circle: x**2 + y**2 <= 1 13 | points_in_circle = filter(lambda point: point[0]**2 + point[1]**2 <= 1, points) 14 | 15 | print "Estimate of pi:", 4.0 * len(points_in_circle) / len(points) 16 | 17 | -------------------------------------------------------------------------------- /solve-for-golden-ratio.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "french-outreach", 6 | "metadata": {}, 7 | "source": [ 8 | "# Solve for golden ratio\n", 9 | "\n", 10 | "### First, let's find the golden ratio\n", 11 | "\n", 12 | "a:b is as b:a+b.\n", 13 | "\n", 14 | "Another way to write this is:\n", 15 | "\n", 16 | "a/b = b/(a+b)\n", 17 | "\n", 18 | "Alternatively we could flip the fractions:\n", 19 | "\n", 20 | "b/a = (a+b)/b.\n", 21 | "\n", 22 | "If we assume a=1, we can simplify to:\n", 23 | "\n", 24 | "b = (b+1)/b.\n", 25 | "\n", 26 | "We could also write this as:\n", 27 | "\n", 28 | "x = (x+1)/x.\n", 29 | "\n", 30 | "Let's see if we can find it making guesses, and trying to minimize error.\n", 31 | "\n", 32 | "To define the error of a given guess, it is simply one side of the equation minus the other side..." 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 3, 38 | "id": "addressed-upgrade", 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "def golden_error(x):\n", 43 | " return x - (x+1)/x" 44 | ] 45 | }, 46 | { 47 | "cell_type": "markdown", 48 | "id": "generic-harassment", 49 | "metadata": {}, 50 | "source": [ 51 | "We should be able to find the golden " 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": 4, 57 | "id": "devoted-mobility", 58 | "metadata": {}, 59 | "outputs": [ 60 | { 61 | "data": { 62 | "text/plain": [ 63 | "-1.0" 64 | ] 65 | }, 66 | "execution_count": 4, 67 | "metadata": {}, 68 | "output_type": "execute_result" 69 | } 70 | ], 71 | "source": [ 72 | "golden_error(1)" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": 5, 78 | "id": "large-requirement", 79 | "metadata": {}, 80 | "outputs": [ 81 | { 82 | "data": { 83 | "text/plain": [ 84 | "0.5" 85 | ] 86 | }, 87 | "execution_count": 5, 88 | "metadata": {}, 89 | "output_type": "execute_result" 90 | } 91 | ], 92 | "source": [ 93 | "golden_error(2)" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": 6, 99 | "id": "apparent-namibia", 100 | "metadata": {}, 101 | "outputs": [ 102 | { 103 | "data": { 104 | "text/plain": [ 105 | "18.95" 106 | ] 107 | }, 108 | "execution_count": 6, 109 | "metadata": {}, 110 | "output_type": "execute_result" 111 | } 112 | ], 113 | "source": [ 114 | "golden_error(20)" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": 7, 120 | "id": "hired-chambers", 121 | "metadata": {}, 122 | "outputs": [ 123 | { 124 | "data": { 125 | "text/plain": [ 126 | "-10.9" 127 | ] 128 | }, 129 | "execution_count": 7, 130 | "metadata": {}, 131 | "output_type": "execute_result" 132 | } 133 | ], 134 | "source": [ 135 | "golden_error(-10)" 136 | ] 137 | }, 138 | { 139 | "cell_type": "markdown", 140 | "id": "returning-contest", 141 | "metadata": {}, 142 | "source": [ 143 | "We should be able to use the error to help us move in the right direction." 144 | ] 145 | }, 146 | { 147 | "cell_type": "code", 148 | "execution_count": 11, 149 | "id": "floating-material", 150 | "metadata": {}, 151 | "outputs": [ 152 | { 153 | "data": { 154 | "text/plain": [ 155 | "0.5" 156 | ] 157 | }, 158 | "execution_count": 11, 159 | "metadata": {}, 160 | "output_type": "execute_result" 161 | } 162 | ], 163 | "source": [ 164 | "estimate = 2\n", 165 | "error = golden_error(estimate)\n", 166 | "error" 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": 22, 172 | "id": "saving-crystal", 173 | "metadata": {}, 174 | "outputs": [ 175 | { 176 | "data": { 177 | "text/plain": [ 178 | "-0.9967568255595112" 179 | ] 180 | }, 181 | "execution_count": 22, 182 | "metadata": {}, 183 | "output_type": "execute_result" 184 | } 185 | ], 186 | "source": [ 187 | "estimate = estimate - error\n", 188 | "error = golden_error(estimate)\n", 189 | "error" 190 | ] 191 | }, 192 | { 193 | "cell_type": "code", 194 | "execution_count": 23, 195 | "id": "handed-league", 196 | "metadata": {}, 197 | "outputs": [ 198 | { 199 | "data": { 200 | "text/plain": [ 201 | "0.49797433101288857" 202 | ] 203 | }, 204 | "execution_count": 23, 205 | "metadata": {}, 206 | "output_type": "execute_result" 207 | } 208 | ], 209 | "source": [ 210 | "estimate = estimate - error\n", 211 | "error = golden_error(estimate)\n", 212 | "error" 213 | ] 214 | }, 215 | { 216 | "cell_type": "code", 217 | "execution_count": 24, 218 | "id": "authentic-magazine", 219 | "metadata": {}, 220 | "outputs": [ 221 | { 222 | "data": { 223 | "text/plain": [ 224 | "-0.16608114257080686" 225 | ] 226 | }, 227 | "execution_count": 24, 228 | "metadata": {}, 229 | "output_type": "execute_result" 230 | } 231 | ], 232 | "source": [ 233 | "estimate = estimate - error\n", 234 | "error = golden_error(estimate)\n", 235 | "error" 236 | ] 237 | }, 238 | { 239 | "cell_type": "code", 240 | "execution_count": 25, 241 | "id": "systematic-corps", 242 | "metadata": {}, 243 | "outputs": [ 244 | { 245 | "data": { 246 | "text/plain": [ 247 | "0.06642168617965782" 248 | ] 249 | }, 250 | "execution_count": 25, 251 | "metadata": {}, 252 | "output_type": "execute_result" 253 | } 254 | ], 255 | "source": [ 256 | "estimate = estimate - error\n", 257 | "error = golden_error(estimate)\n", 258 | "error" 259 | ] 260 | }, 261 | { 262 | "cell_type": "code", 263 | "execution_count": 26, 264 | "id": "unexpected-sending", 265 | "metadata": {}, 266 | "outputs": [ 267 | { 268 | "data": { 269 | "text/plain": [ 270 | "-0.02490981492141464" 271 | ] 272 | }, 273 | "execution_count": 26, 274 | "metadata": {}, 275 | "output_type": "execute_result" 276 | } 277 | ], 278 | "source": [ 279 | "estimate = estimate - error\n", 280 | "error = golden_error(estimate)\n", 281 | "error" 282 | ] 283 | }, 284 | { 285 | "cell_type": "code", 286 | "execution_count": 27, 287 | "id": "intensive-filing", 288 | "metadata": {}, 289 | "outputs": [ 290 | { 291 | "data": { 292 | "text/plain": [ 293 | "0.009580459077144443" 294 | ] 295 | }, 296 | "execution_count": 27, 297 | "metadata": {}, 298 | "output_type": "execute_result" 299 | } 300 | ], 301 | "source": [ 302 | "estimate = estimate - error\n", 303 | "error = golden_error(estimate)\n", 304 | "error" 305 | ] 306 | }, 307 | { 308 | "cell_type": "code", 309 | "execution_count": 28, 310 | "id": "beautiful-portfolio", 311 | "metadata": {}, 312 | "outputs": [ 313 | { 314 | "data": { 315 | "text/plain": [ 316 | "-0.003649733917237219" 317 | ] 318 | }, 319 | "execution_count": 28, 320 | "metadata": {}, 321 | "output_type": "execute_result" 322 | } 323 | ], 324 | "source": [ 325 | "estimate = estimate - error\n", 326 | "error = golden_error(estimate)\n", 327 | "error" 328 | ] 329 | }, 330 | { 331 | "cell_type": "code", 332 | "execution_count": 29, 333 | "id": "precious-paintball", 334 | "metadata": {}, 335 | "outputs": [ 336 | { 337 | "data": { 338 | "text/plain": [ 339 | "0.0013954813790570952" 340 | ] 341 | }, 342 | "execution_count": 29, 343 | "metadata": {}, 344 | "output_type": "execute_result" 345 | } 346 | ], 347 | "source": [ 348 | "estimate = estimate - error\n", 349 | "error = golden_error(estimate)\n", 350 | "error" 351 | ] 352 | }, 353 | { 354 | "cell_type": "code", 355 | "execution_count": 30, 356 | "id": "photographic-atmosphere", 357 | "metadata": {}, 358 | "outputs": [ 359 | { 360 | "data": { 361 | "text/plain": [ 362 | "-0.0005328209108332871" 363 | ] 364 | }, 365 | "execution_count": 30, 366 | "metadata": {}, 367 | "output_type": "execute_result" 368 | } 369 | ], 370 | "source": [ 371 | "estimate = estimate - error\n", 372 | "error = golden_error(estimate)\n", 373 | "error" 374 | ] 375 | }, 376 | { 377 | "cell_type": "code", 378 | "execution_count": 31, 379 | "id": "direct-solution", 380 | "metadata": {}, 381 | "outputs": [ 382 | { 383 | "data": { 384 | "text/plain": [ 385 | "0.00020354945238376665" 386 | ] 387 | }, 388 | "execution_count": 31, 389 | "metadata": {}, 390 | "output_type": "execute_result" 391 | } 392 | ], 393 | "source": [ 394 | "estimate = estimate - error\n", 395 | "error = golden_error(estimate)\n", 396 | "error" 397 | ] 398 | }, 399 | { 400 | "cell_type": "code", 401 | "execution_count": 32, 402 | "id": "loved-energy", 403 | "metadata": {}, 404 | "outputs": [ 405 | { 406 | "data": { 407 | "text/plain": [ 408 | "1.6181812822229917" 409 | ] 410 | }, 411 | "execution_count": 32, 412 | "metadata": {}, 413 | "output_type": "execute_result" 414 | } 415 | ], 416 | "source": [ 417 | "estimate" 418 | ] 419 | }, 420 | { 421 | "cell_type": "markdown", 422 | "id": "prospective-treaty", 423 | "metadata": {}, 424 | "source": [ 425 | "This works. So we can write this as an algorithm..." 426 | ] 427 | }, 428 | { 429 | "cell_type": "code", 430 | "execution_count": 37, 431 | "id": "balanced-application", 432 | "metadata": {}, 433 | "outputs": [], 434 | "source": [ 435 | "def find_estimate(error_func, start, iterations):\n", 436 | " estimates = []\n", 437 | " estimate = start\n", 438 | " for i in range(iterations+1):\n", 439 | " estimates.append(estimate)\n", 440 | " error = error_func(estimate)\n", 441 | " estimate = estimate - error\n", 442 | " \n", 443 | " return estimates" 444 | ] 445 | }, 446 | { 447 | "cell_type": "code", 448 | "execution_count": 38, 449 | "id": "stock-infection", 450 | "metadata": {}, 451 | "outputs": [ 452 | { 453 | "data": { 454 | "text/plain": [ 455 | "[2,\n", 456 | " 1.5,\n", 457 | " 1.6666666666666667,\n", 458 | " 1.6,\n", 459 | " 1.625,\n", 460 | " 1.6153846153846154,\n", 461 | " 1.619047619047619,\n", 462 | " 1.6176470588235294,\n", 463 | " 1.6181818181818182,\n", 464 | " 1.6179775280898876,\n", 465 | " 1.6180555555555556]" 466 | ] 467 | }, 468 | "execution_count": 38, 469 | "metadata": {}, 470 | "output_type": "execute_result" 471 | } 472 | ], 473 | "source": [ 474 | "find_estimate(golden_error, 2, 10)" 475 | ] 476 | }, 477 | { 478 | "cell_type": "code", 479 | "execution_count": null, 480 | "id": "catholic-scotland", 481 | "metadata": {}, 482 | "outputs": [], 483 | "source": [] 484 | } 485 | ], 486 | "metadata": { 487 | "kernelspec": { 488 | "display_name": "Python 3", 489 | "language": "python", 490 | "name": "python3" 491 | }, 492 | "language_info": { 493 | "codemirror_mode": { 494 | "name": "ipython", 495 | "version": 3 496 | }, 497 | "file_extension": ".py", 498 | "mimetype": "text/x-python", 499 | "name": "python", 500 | "nbconvert_exporter": "python", 501 | "pygments_lexer": "ipython3", 502 | "version": "3.8.6" 503 | } 504 | }, 505 | "nbformat": 4, 506 | "nbformat_minor": 5 507 | } 508 | --------------------------------------------------------------------------------