├── .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 | "1 | 2 | 3 | 4 | 5 |
\n",
183 | "
\n",
184 | "\n",
185 | "B=\n",
186 | "\n",
187 | "1 | 2 | 3 |
\n",
188 | "
\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 | " | | 1 | 2 | 3 | 4 | 5 |
\n",
195 | "1 | 2 | 3 | | | | |
\n",
196 | "
\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 | " | 1 | 2 | 3 | 4 | 5 |
\n",
203 | "1 | 2 | 3 | | | |
\n",
204 | "
\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 | "1 | 2 | 3 | 4 | 5 |
\n",
211 | "1 | 2 | 3 | | |
\n",
212 | "
\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 | "1 | 2 | 3 | 4 | 5 | | |
\n",
221 | " | | | | 1 | 2 | 3 |
\n",
222 | "
\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 | "1 | 2 | 3 | 4 | 5 |
\n",
183 | "
\n",
184 | "\n",
185 | "B=\n",
186 | "\n",
187 | "1 | 2 | 3 |
\n",
188 | "
\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 | " | | 1 | 2 | 3 | 4 | 5 |
\n",
195 | "1 | 2 | 3 | | | | |
\n",
196 | "
\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 | " | 1 | 2 | 3 | 4 | 5 |
\n",
203 | "1 | 2 | 3 | | | |
\n",
204 | "
\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 | "1 | 2 | 3 | 4 | 5 |
\n",
211 | "1 | 2 | 3 | | |
\n",
212 | "
\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 | "1 | 2 | 3 | 4 | 5 | | |
\n",
221 | " | | | | 1 | 2 | 3 |
\n",
222 | "
\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 | ""
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 | ""
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": "iVBORw0KGgoAAAANSUhEUgAAAXcAAAEACAYAAABI5zaHAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3XmUXGd55/HvU0vvu6TW0q1dlrxLNrYs22AMhiCYg01W\n7CwTHBIcEkMmycmYbAPnZBYnYRKSOA44AwwQjByWgIdgULBpg2xsy9bmRbtkSa21W2r1ol6r6pk/\nqlRd3epd1X2rqn+fc+ro3lu3bj3q5Vdvv/e97zV3R0RECkso6AJERCT7FO4iIgVI4S4iUoAU7iIi\nBUjhLiJSgBTuIiIFaELhbmYbzWyPme0zs4dGeP7tZnbezLalHn+W/VJFRGSiIuPtYGYh4BHgLuAE\nsNXMvuPue4bt+mN3v3saahQRkUmaSMt9PbDf3Y+4+wCwCbhnhP0sq5WJiMiUTSTcG4BjGevNqW3D\n3WpmO8zs383s6qxUJyIiUzJut8wEvQIscfduM3sv8G1gdZaOLSIikzSRcD8OLMlYb0xtS3P3rozl\np8zsUTOrc/dzmfuZmSayERGZAnefVNf3RLpltgKrzGypmRUB9wJPZu5gZvMzltcDNjzYL7rpr56m\npbMXd8/Zxyc/+cnAa1CdqjNfa1Sd2X9Mxbgtd3ePm9mDwGaSHwafd/fdZvZA8ml/DPgFM/soMAD0\nAB8c65h7z3Qyt6J4SgWLiMj4JtTn7u7fB9YM2/a5jOV/BP5xom+653Qnt6+YO9HdRURkkgK5QnX3\nqc4g3nbC7rzzzqBLmBDVmV35UGc+1AiqMxfYVPtzpvRmZn7TXz3N/Mpivvvbt8/Y+4qI5DMzw6fh\nhGrWne7s43zPQBBvLSIyKwQ2cdje07ndNSMiks8U7iIiBSiwcN99uiOotxYRKXjBhXuOj5gREcln\nMx7uodT53uPtvXT1xWb67UVEZoUZD/d5GVem7juj1ruIyHSY8XBfWFWSXt5zumuMPUVEZKpmPNwX\nDAl3nVQVEZkOgbbcdVJVRGR6zHi4z68sSd+P72hbNz398ZkuQUSk4M14uBdFQsyrTJ5UTXhy+l8R\nEcmuQMa5L64pTS+/flL97iIi2RZMuNeWpZdfO9keRAkiIgUt8Jb7a2q5i4hkXSDhvqCqhEjqUtVT\nHX20dfcHUYaISMEKJNzDIWNR9eCQSPW7i4hkV2ATh2X2uyvcRUSyK7hwz+h3f1UnVUVEsirAlvtg\nuL9xspOZvJeriEihCyzc68qKKIuGAejsi9F8vieoUkRECk5g4W5mNNbqYiYRkekQWLjDsCtVTync\nRUSyJdhwzxgx8+oJhbuISLYEGu6NGS33fWc6GYgnAqxGRKRwBBruFcUR6sqiAAzEnQMtujOTiEg2\nBBruAI01mZOIqWtGRCQbAg/3zPHuO4/rYiYRkWwIPNyX1Q223BXuIiLZEXi4L6zOnCGyl9auvoAr\nEhHJf4GHeyQUUteMiEiWBR7uAEszumZ2KdxFRC5bboR7xsVM25vPB1iJiEhhyI1wz2i57zvTRe9A\nPMBqRETy34TC3cw2mtkeM9tnZg+Nsd/NZjZgZj83mSLKiiLUVxQDEHfXPDMiIpdp3HA3sxDwCPAe\n4BrgPjO7cpT9HgZ+MJVC1O8uIpI9E2m5rwf2u/sRdx8ANgH3jLDfx4BvAGemUkhmuO9Qv7uIyGWZ\nSLg3AMcy1ptT29LMbBHwAXf/J8CmUsiQlvuJDhK6M5OIyJRFsnSczwCZffGjBvzjj36aaDj5mbJ2\n/W2su+V2AOaWF1FeFOZCf5yuvhhHznWzfE55lsoTEckfTU1NNDU1XdYxbLx7l5rZBuBT7r4xtf4J\nwN39LzP2OXRxEZgLXAA+4u5PDjuWP7nrBGVF4RHf60svHWH3qU4A/vQ9V/KB6xdN6T8lIlJIzAx3\nn1SvyES6ZbYCq8xsqZkVAfcCQ0Lb3VekHstJ9rv/zvBgn4hltep3FxHJhnG7Zdw9bmYPAptJfhh8\n3t13m9kDyaf9seEvmWoxOqkqIpIdE+pzd/fvA2uGbfvcKPv+xlSLaawpJRIyYgnneHsvZzr7qK8s\nnurhRERmrZy4QvWiSDjEkozW+7ZjbQFWIyKSv3Iq3AFWZIyQ2XZMXTMiIlOR0+G+9aha7iIiU5Fz\n4b64tjR9847m8z26eYeIyBTkXLhHw0Nv3qGuGRGRycu5cIehXTMv66SqiMik5Wa4z80I9yMKdxGR\nycrJcF9SW0Y41e9+TP3uIiKTlpPhHg2HWJLZ766rVUVEJiUnwx2G9ru/oiGRIiKTkrPhvnxIuKvl\nLiIyGTkb7pn97kfaujl7oT/gikRE8kfOhntRJMTimsF+95ePnguwGhGR/JKz4Q6wal5FevnFNxXu\nIiITldPhfkVGuP/08DnGu2uUiIgk5XS4N9aUUhJJlth6oZ83z3UHXJGISH7I6XAPh2zI1arqmhER\nmZicDncY2jXzgsJdRGRC8irctx1rIxZPBFiNiEh+yPlwn1NeRE1pFICegQSvnuwIuCIRkdyX8+Fu\nZkNa7+p3FxEZX86HOwzrdz98NsBKRETyQ16E+8p55VhqeffpTjp7BwKtR0Qk1+VFuJcXRVhUXQJA\nwuFlTSQmIjKmvAh3GHa16pvqmhERGUv+hHv9YLg/d+ispiIQERlD3oT7srry9FQEZzr7ONh6IeCK\nRERyV96Eezhkl7TeRURkZHkT7gBr6ivTyz852BpgJSIiuS1vw/3VE+10aEikiMiI8ircK0siNKbu\nzpRweOGwrlYVERlJXoU7wJXzB1vvWw6pa0ZEZCR5He7PHzpLPKEhkSIiw+VduC+qLqGiOAJAe2+M\nN05plkgRkeHyLtxDZqzJGBK5RUMiRUQuMaFwN7ONZrbHzPaZ2UMjPH+3me00s+1m9rKZvTP7pQ7K\n7Jr5yQH1u4uIDDduuJtZCHgEeA9wDXCfmV05bLcfuvtad78BuB94LOuVZrhiXgWh1DSR+1u6ONXR\nO51vJyKSdybScl8P7Hf3I+4+AGwC7sncwd27M1YrgGltTpdEw6ycO9g18+yBlul8OxGRvDORcG8A\njmWsN6e2DWFmHzCz3cD3gI9np7zRXbOwKr38o30KdxGRTFk7oeru33b3q4D3A1/J1nFHc/WCyvQN\nPLY3n+d8d/90v6WISN6ITGCf48CSjPXG1LYRufsWM4uY2Rx3v2Qoy+OPfppoOPmZsnb9bay75fZJ\nlpxUVRJlcW0pR9t6SDj8+GArd1+3aErHEhHJJU1NTTQ1NV3WMWy8edHNLAzsBe4CTgIvAfe5++6M\nfVa6+8HU8o3A19195QjH8id3naCsKHxZRV/07IEWnnrjNABvXTGHv/35tVk5rohILjEz3N3G33PQ\nuN0y7h4HHgQ2A68Dm9x9t5k9YGYfSe3282b2mpltA/4O+OAka5+SaxYM9ru/eOQc3f2xmXhbEZGc\nN27LPatvluWWO8BnfrSfU519ADx897XctaY+a8cWEckF09Jyz3VDRs3s16gZEREosHDfcrCVgXgi\nwGpERHJD3of7wqoSasuiAFzoj7P1aFvAFYmIBC/vw93MhpxY/Y/dpwOsRkQkN+R9uAOsbahOLz+z\nv4X+mLpmRGR2K4hwb6wppS7VNdPdH+enhzUNsIjMbgUR7mbG2oaa9PrmPeqaEZHZrSDCHYZ2zTx7\noJWe/niA1YiIBKtgwn1+ZTH1lcUA9MUS/EQ3zxaRWaxgwt3MWLtosPW+WaNmRGQWK5hwB7g+o2vm\nuUNn6erTXDMiMjsVVLjPqyimoboEgFjCadJ0BCIySxVUuMPQ1vv33zgVYCUiIsEpuHDP7HfferSN\nM6kZI0VEZpOCC/easiJWzi0HIOHwlFrvIjILFVy4A9y4ePCCpu++dpKZnLNeRCQXFGS4X7uwiqLU\nfVrfPNfNG6c6A65IRGRmFWS4F0fCXLdocKbI7752MsBqRERmXkGGOwztmvnB7tOaKVJEZpWCDffl\nc8qpKU3OFNnZF+MnBzUdgYjMHgUb7iEz3jLsxKqIyGxRsOEOQ7tmnj98ltYujXkXkdmhoMN9Tnkx\ny+rKgOSY9/+n1ruIzBIFHe4AtyyrSy9/a8dx4gmNeReRwlfw4X7twirKisIAnOrs44U3zwVckYjI\n9Cv4cI+GQ0NOrH5rx/EAqxERmRkFH+4A65cOds1sOdTK6c7eAKsREZl+syLc51UUD5lM7Du7TgRc\nkYjI9JoV4Q5wS0br/du7ThBL6IpVESlcsybcr15YSUXqxGpLVz/PHTwbcEUiItNn1oR7JBTipiW1\n6fUntjUHWI2IyPSaNeEOsGFZHSFLLm892saBlq5gCxIRmSazKtxryoq4ZuHgVMCbXjkWYDUiItNn\nVoU7wFtXzE0vf++NU7R19wdYjYjI9Jh14b6ktpTGmlIABuLON3VRk4gUoFkX7mbGW1fMSa9/fftx\n3chDRArOhMLdzDaa2R4z22dmD43w/C+b2c7UY4uZXZf9UrPn2kVVVJVEADjX3c8P954OuCIRkewa\nN9zNLAQ8ArwHuAa4z8yuHLbbIeAOd18L/Hfgn7NdaDZFQiFuzZgt8l+2HsNds0WKSOGYSMt9PbDf\n3Y+4+wCwCbgncwd3f8Hd21OrLwAN2S0z+9YvrSMaTo6L3N/SpdkiRaSgTCTcG4DMMYPNjB3evwk8\ndTlFzYTy4gg3Z1zU9MUX3gyuGBGRLMvqCVUzewdwP3BJv3wuumPl3PRFTdub29nZfD7YgkREsiQy\ngX2OA0sy1htT24Yws+uBx4CN7t422sEef/TTRMPJz5S1629j3S23T6rgbKopK+KGxhpeOZYM9S++\neITPNNaM8yoRkenV1NREU1PTZR3DxjuRaGZhYC9wF3ASeAm4z913Z+yzBHga+DV3f2GMY/mTu06k\n74yUC8509vG3P9rPxa/CV3/9ZlbXVwZak4hIJjPD3W0yrxm3W8bd48CDwGbgdWCTu+82swfM7COp\n3f4cqAMeNbPtZvbSJGsPTH1l8ZApCb704pEAqxERyY5xW+5ZfbMcbLkDNJ/v4ZEfHwQgZPD1D29g\nSW1ZwFWJiCRNS8t9NmisKWX1vAogeaemf37ucMAViYhcHoV7yl1r6tPLP9h9moOtmg5YRPKXwj1l\naV0ZV85Pnkh11HoXkfymcM/w7ozW+9P7Wth7ujPAakREpk7hnqGhpnTIyJnPbjkUYDUiIlOncB/m\n3WvquXhKesuhs7x6on3M/UVEcpHCfZgFVSVc31CdXn/k2YOaMVJE8o7CfQTvWlOfnnNmW/N5fnyg\nNdiCREQmSeE+gnkVxdySMd/73z97gFhcd2sSkfyhcB/Fu1bXUxJJfnmOtvXwzZ2616qI5A+F+yjK\niyO8Y/W89Po/P3eYzt6BACsSEZk4hfsYbls+h9qyKADtvTG+8IImFROR/KBwH0M0HGLjVQvS65te\nOcaRc90BViQiMjEK93Fcv6iKpakZImMJ569/uFdDI0Uk5yncx2Fm3H39wvSFTS8eaePpfS2B1iQi\nMh6F+wQ0VJeyYfng0Mi/eWYfF/pjAVYkIjI2hfsE/cyV86koTt5ytqWrn88//2awBYmIjEHhPkGl\n0TDvu3p+ev3xV45xoEVzvotIblK4T8INjTUsq0ueXI0nnL/4/m5iCV25KiK5R+E+CWbGz65dRDg1\n8cwbpzr52svNAVclInIphfskza8s4a6MK1c/u+WQxr6LSM5RuE/B21fNY2FVCQD98QT/4we7SWjs\nu4jkEIX7FIRDxi/e0JCeFnh7cztf367uGRHJHQr3KVpUXcqdVwx2z/x900EOn70QYEUiIoMU7pfh\nnVfMY0FlMZDsnvmz775Of0yjZ0QkeAr3yxAJh7j3LYuJpPpn9p3p4p90U20RyQEK98u0oKqE9149\nOHPkv2w9yotvnguwIhERhXtW3La8jtX1Fen1Tz31Bucu9AdYkYjMdgr3LDAzfnFdA+VFYQBau/r5\ns+++Tjyh4ZEiEgyFe5ZUlkT5pRsa01MDbz3axmPPqf9dRIKhcM+iNfMrh9x39QsvHGHLwdYAKxKR\n2UrhnmXvWlPPFfPK0+v/7d/f4Pj5ngArEpHZSOGeZSEz7r1xMdUlybnfO/ti/OG/7aKrTzf3EJGZ\no3CfBuXFEX7lpiXp2SMPtl7gz3WCVURmkMJ9miypK+Pn1i5Kr285dJZ/ePZAgBWJyGyicJ9Gb1lc\ny52r5qbXv/ryMb6960SAFYnIbDGhcDezjWa2x8z2mdlDIzy/xsyeN7NeM/uD7JeZv37mqvlcvaAy\nvf7w5j0aQSMi027ccDezEPAI8B7gGuA+M7ty2G5ngY8Bf531CvNcyIwP3tiYnv897vCJJ1/j1RPt\nAVcmIoVsIi339cB+dz/i7gPAJuCezB3cvdXdXwE0JGQExZEw929YSm1ZFIC+WIL/8s2dmiJYRKbN\nRMK9ATiWsd6c2iaTUFUS5cMblqWnKOjojfGxr+/gZLvGwItI9kVm+g0ff/TTRMPJz5S1629j3S23\nz3QJgZlbUcz9G5bx2HOH6Y8nON3Zx28/sZ3P3XsjC1LdNiIiTU1NNDU1XdYxzMe596eZbQA+5e4b\nU+ufANzd/3KEfT8JdLr734xyLH9y1wnKUq3X2Wp/Sxf/98Uj6XHvjTWlfO7eG6lP3fhDRCSTmeHu\nNv6egybSLbMVWGVmS82sCLgXeHKsOiZTwGx0xbwKfu3mJYQt+aVqPt/DR5/YTmtXX8CViUihGDfc\n3T0OPAhsBl4HNrn7bjN7wMw+AmBm883sGPD7wJ+a2VEzqxj9qHLl/Ep+5ebF6ZtsH23r5re+to0T\n6oMXkSwYt1smq2+mbplLvHayg8dfPsrFmQnqK4p55JfWsXxO+dgvFJFZY7q6ZWQaXbuwil+9eUn6\nPqxnuvr4yNe2sftUR8CViUg+U7jngKsXVPGhW5ZSlBpFdL5ngN/etJ3nD58NuDIRyVcK9xyxal4F\nv3nbMkqjyS6r7oE4f/DNnXxr5/GAKxORfKRwzyFLast44PblVJckr2SNO/yvzXv5h2cPkJjBcyMi\nkv8U7jlmQVUJv3vHChqqBy9q+vJLR3XDDxGZFIV7DqoqifKR25dz1fzB2SS3HDzLr3/lZc1HIyIT\nonDPUcWRML+2fgl3rBycD/5oWzcf+srLPLP3TICViUg+ULjnsJAZ77tmAfe9ZTHRcHKoZPdAnIee\nfI2H/2MvfbF4wBWKSK5SuOeBtQ3VfPStK9JTBgN8c8dx7v+XV3hT3TQiMgKFe55YVF3Kx9++imsX\nVqW37W/p4le/vJUnth3TaBoRGULTD+QZd+fFI21897WTxBKD37ubltTw5xuvYlF1aYDVich0mMr0\nAwr3PHWyvZcnth3jVOfgTJLlRWF+520r+fl1DYRDmpxTpFAo3GeZWDzB0/taaNrfQuZ38dqFVfzx\nz6xhdX3lqK8VkfyhcJ+ljrZ186/bmmm90J/eFjbjg29p5LduW05F8YzfcEtEskjhPovF4gmaDrTy\no30txDO+p7VlUX7nbSt5/7UL1VUjkqcU7sKZzj7+bddxDp/tHrJ9dX0FD96xkg3L6jBTyIvkE4W7\nAMkRNbtOdPC910/R3jsw5LmbltTw4B2ruCZjSKWI5DaFuwzRH0vw44OtPLu/hYHE0O/z21bO5cO3\nLlPIi+QBhbuMqKN3gKf3nuGlo20M/3bfuryOD29YxtrGmmCKE5FxKdxlTK1dfWzec4ZdJ9ovee66\nRcnb/b191TydeBXJMQp3mZDTHb08s7+FnccvDfnGmlJ+YV0D779uIVUl0RFeLSIzTeEuk3Kms49n\nD7Swo7l9yPBJgOJIiI1Xzedn1zZw9YJKjbARCZDCXaako3eAnx4+x0/fPEvvQOKS51fNLefu6xfx\n3qvmU1NWFECFIrObwl0uS38swY7j5/np4XOc7Oi95PlwyLh1eR3vvWoBd6yaS0lU30eRmaBwl6xw\nd46c6+alo23sOt4+ZPbJi0qjYd66cg7vWl3PbSvmKOhFppHCXbKudyDOzuPtvHy0jWPne0bcpyQa\nYsOyOdyxai5vXTGHWnXdiGSVwl2m1dkLfexobmd78/khk5RlMuDaRVXcumwOty6v46oFVRpaKXKZ\nFO4yI9yd0519vHqinV0nOmjp6ht136qSCG9ZXMtNS2q5eWkty+rKNPJGZJIU7hKIlq4+dp/q5PVT\nHRw9181YP1F1ZVHWNtRww+Ia1jVUc8W8CiJh3e1RZCwKdwnchf4YB1ousO9MJ3vPdNLVFx9z/+JI\niKsXVHLtwmquXljFVfMrWVRdota9SAaFu+QUd+dMZx8HWi9woKWLw2cv0Bu7dBz9cNUlEVbXV7K6\nviL979K6MqJq4csspXCXnJZIhf3hsxc4fPYCR851094bm9BrwyFjaV0ZK+aUs2JOOcvmlLF8TjmL\na0spjujnSQqbwl3yTkfvAMfaejja1k3z+R6On++ZUOv+IgMWVJWwuLaUJbVlNNaU0lBTSmNNKQur\nSygv0i0GJf8p3CXvuTvnugc42d7DiY5eTpzv4WRH3yU3HZmo6tIoi6pKWJB6zK8sZn5VCfUVxdRX\nFjO3vEgndCXnKdylYPXF4pzu7ON0Ry8tXf2c6ujlTFcf7T0DY47OGY+RvM/snPJi5lYUMbe8mLry\nIurKiphTHqW2rIja0iJqy6LUlEWJhPRBIDNv2sLdzDYCnwFCwOfd/S9H2OfvgfcCF4APufuOEfZR\nuEtWxeIJznX303qhn9aufs5293G2q5+z3f109MQume3yclUUR6gpjVJdGqW6JEpVSYTq0iiVxREq\nU+uVxREqSiJUFEWoKE4+yovC+gtBpmwq4T5uh6SZhYBHgLuAE8BWM/uOu+/J2Oe9wEp3v8LMbgE+\nC2yYVPU5ZMeLz7HultuDLmNcqhMi4RD1lSXUV5Zc8lzCna6+GG3dA5zv6ed89wBtPQOc7+6nvTdG\nR+8AF/oHh2p2HNxB1cp1Y75fV1+Mrr4YzaNMxTCW4kiI8qIw5UURyorClBUlQ7+0KExZNExJNExp\n6lESDVMSDVESSa4XR0OURELsfOl53nbH2ymJhCiKhCmOhCiOhCiKhAjl0PDRpqYm7rzzzqDLGFe+\n1DkVEznbtB7Y7+5HAMxsE3APsCdjn3uALwO4+4tmVm1m8939dLYLngk7X3o+L0JTdY4tZEZVSZSq\nkihLKRtxn3gi+QHQ0TvAE69/m7dd/z66+mJ09sXo7B2gqy/Ohf4YF/rj9AyMPWZ/PH2xBH2xBOe6\np3b+AOD45n+l4VDpiM9Fw0ZROBn00XCIovDFf5PbI+EQ0bARTW2PhCy9HgkZkVCISNiSy+ltyUc4\nFMpYTv4burhsyX8vrofNePw7T1G1ci3hUIiwkXzOjJAZoVDyexMyI2zJVmk4lHru4nrq31DqtSEM\nM4bsc/HfyzHbw70BOJax3kwy8Mfa53hqW16Gu8we4ZAlu1hKo8yrKOaWZXWj7ptwp2cgTnf/xUeM\n7oE4Pang704919MfpzcWp3cgTm8q0Ptjics6NzARA3FnIB4f8tdIUI6/dortm7bPyHsZydA3I/0B\nwMVtkNpuqf2SG0KWfN2Bl47ywqNb0scgtT25mDzmxfewjONxyXOD2yy1DYZ++GTuT8bxMjdaxmts\n6FOTNuPjxPpjwf/gjWcgnqA7B35BxqM6s2sidRpGeVEkNcSyeMLHdncG4k5fLE5/PJFuxQ/Ek8Hf\nH0/QH/fkemrbxeWBeIKBuBOLJ2gvDlNXVkQskSCWcGJxJ+5OfIRpmWcLh+S5Fc/cMjE9A3HOjjIJ\nXr4b94SqmW0APuXuG1PrnwA886SqmX0W+JG7P5Fa3wO8fXi3jJnN3p9AEZHLkPUTqsBWYJWZLQVO\nAvcC9w3b50ngd4EnUh8G50fqb59scSIiMjXjhru7x83sQWAzg0Mhd5vZA8mn/TF3/56Zvc/MDpAc\nCnn/9JYtIiJjmdGLmEREZGbM2FUVZrbRzPaY2T4ze2im3ncyzKzRzJ4xs9fN7FUz+3jQNY3GzEJm\nts3Mngy6ltGkhsR+3cx2p76mtwRd00jM7I9T9e0ys6+aWU7cJ9DMPm9mp81sV8a2WjPbbGZ7zewH\nZlYdZI2pmkaq869S3/cdZvZNM6sKssZUTZfUmfHcH5pZwsxGHy41Q0ar08w+lvqavmpmD493nBkJ\n94wLod4DXAPcZ2ZXzsR7T1IM+AN3vwa4FfjdHK0T4PeAN4IuYhx/B3zP3a8C1gK7A67nEqlzSb8F\n3ODu15Psqrw32KrSvkjydybTJ4Afuvsa4Bngj2e8qkuNVOdm4Bp3XwfsJ3frxMwagXcDR2a8opFd\nUqeZ3Qm8H7jO3a8DPj3eQWaq5Z6+EMrdB4CLF0LlFHc/dXHaBHfvIhlGDcFWdanUD+P7gP8TdC2j\nSbXU3ubuXwRw95i7dwRc1kg6gH6g3MwiQBnJK7ED5+5bgLZhm+8BvpRa/hLwgRktagQj1enuP3T3\ni9N7vgA0znhhw4zy9QT4W+CPZricUY1S50eBh909ltqndbzjzFS4j3QhVM6FZiYzWwasA14MtpIR\nXfxhzOUTJsuBVjP7Yqr76DEzG/nSygC5exvwv4GjJC++O+/uPwy2qjHVXxyJ5u6ngPqA65mI3wCe\nCrqIkZjZ3cAxd3816FrGsRq4w8xeMLMfmdlN471AMxmNwMwqgG8Av5dqwecMM/tPwOnUXxiZF8Pl\nmghwI/CP7n4j0E2ySyGnmNkK4PeBpcAioMLMfjnYqiYllz/gMbM/BQbc/fGgaxku1dj4E+CTmZsD\nKmc8EaDW3TcA/xX41/FeMFPhfhxYkrHemNqWc1J/mn8D+Iq7fyfoekZwO3C3mR0Cvga8w8y+HHBN\nI2km2SJ6ObX+DZJhn2tuAp5z93PuHge+BdwWcE1jOW1m8wHMbAFwJuB6RmVmHyLZfZirH5YrgWXA\nTjM7TDKXXjGzXPxr6BjJn03cfSuQMLM5Y71gpsI9fSFUaiTCvSQvfMpFXwDecPe/C7qQkbj7n7j7\nEndfQfLr+Iy7/+eg6xou1XVwzMxWpzbdRW6eAN4LbDCzEktO6nEXuXXid/hfZ08CH0ot/zqQKw2Q\nIXWmpgliNRP8AAAA7klEQVT/I+Bud+8LrKpLpet099fcfYG7r3D35SQbJDe4ey58YA7/vn8beCdA\n6ncq6u5nxzrAjIR7qkV08UKo14FN7p5Lv0AAmNntwK8A7zSz7am+4o1B15XHPg581cx2kBwt8z8D\nrucS7r6T5IymrwA7Sf5CPRZoUSlm9jjwPLDazI6a2f3Aw8C7zWwvyQ+icYfETbdR6vwHoAL4j9Tv\n0aOBFsmodWZycqBbZpQ6vwCsMLNXgceBcRt0uohJRKQA6YSqiEgBUriLiBQghbuISAFSuIuIFCCF\nu4hIAVK4i4gUIIW7iEgBUriLiBSg/w8ReQZ4gAYWLgAAAABJRU5ErkJggg==\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 | ""
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(\"\" + str(col) + \" | \")\n",
53 | " t.append(\"
\")\n",
54 | " t.append(\"
\")\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 | "1 | | | | \n",
83 | " | | 4 | | \n",
84 | " | 3 | | | \n",
85 | " | | 2 | | \n",
86 | " \n",
87 | "\n",
88 | " | \n",
89 | "\n",
90 | "Solution\n",
91 | "\n",
92 | "1 | 4 | 3 | 2 | \n",
93 | "3 | 2 | 4 | 1 | \n",
94 | "2 | 3 | 1 | 4 | \n",
95 | "4 | 1 | 2 | 3 | \n",
96 | " \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 | "a01 | a02 | a03 | a04 |
a05 | a06 | a07 | a08 |
a09 | a10 | a11 | a12 |
a13 | a14 | a15 | a16 |
"
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 | "a0 | a1 | a2 | a3 | a4 | a5 | a6 | a7 | a8 |
a9 | a10 | a11 | a12 | a13 | a14 | a15 | a16 | a17 |
a18 | a19 | a20 | a21 | a22 | a23 | a24 | a25 | a26 |
a27 | a28 | a29 | a30 | a31 | a32 | a33 | a34 | a35 |
a36 | a37 | a38 | a39 | a40 | a41 | a42 | a43 | a44 |
a45 | a46 | a47 | a48 | a49 | a50 | a51 | a52 | a53 |
a54 | a55 | a56 | a57 | a58 | a59 | a60 | a61 | a62 |
a63 | a64 | a65 | a66 | a67 | a68 | a69 | a70 | a71 |
a72 | a73 | a74 | a75 | a76 | a77 | a78 | a79 | a80 |
"
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 | | | 1 | 6 | | | | |
4 | 1 | | 9 | | 8 | | | 6 |
| | | | | | | 1 | |
| | | | 8 | 6 | | | 3 |
| 8 | 9 | | 3 | | 2 | 5 | |
3 | | | 2 | 9 | | | | |
| 2 | | | | | | | |
7 | | | 8 | | 4 | | 6 | 5 |
| | | | 5 | 7 | | | 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 |
--------------------------------------------------------------------------------