├── LICENSE ├── README.md ├── ch01 ├── big_or_little.c ├── binary32.c ├── ints.c ├── logistic.py ├── nan_inf.c ├── roundoff.py ├── sign_extend.c └── sum1.c ├── ch02 ├── NumberSets.py └── urandom.py ├── ch03 ├── boolean.c ├── boolean.py ├── examples.py ├── g.py └── z.py ├── ch04 ├── equivalence.py └── rviewer.py ├── ch05 ├── binary.c ├── bubble.py ├── gnome.py └── root.c ├── ch06 ├── collatz.py ├── collatz_factors_2.py ├── collatz_is_prime.py ├── collatz_max_hist.txt ├── collatz_plot.py ├── collatz_unique_9232.npy ├── ex2.py ├── ex3.py ├── fact.c ├── fib.py ├── fib1.scm ├── fib_test.py ├── fibonacci.py ├── logistic_cycle.py ├── logistic_map.py ├── lucas.py ├── mandel.py ├── mandelbrot.py ├── minstd.c ├── powers.py ├── quicksort.py └── towers.c ├── ch07 ├── asm65 │ ├── 6502_assembly_in_one_step.txt │ ├── asm65.pl │ ├── gcd │ └── gcd.s ├── divide_examples │ ├── div.4th │ ├── div.c │ ├── div.clp │ ├── div.cpp │ ├── div.cs │ ├── div.java │ ├── div.js │ ├── div.scm │ ├── div.sno │ ├── div2.nim │ ├── divide.p │ └── divz.zig ├── erdos-straus.py ├── euclidean.py ├── harshad.py ├── harshad_multiple.py ├── midi.zip ├── minv.py ├── naive_mod.c └── ntheory.py ├── ch08 ├── combination_plot.py ├── combinatorics.py ├── heap.c ├── heap_test.npy ├── heap_test.py ├── inclusion_exclusion.py ├── permutations.py ├── sierp.py └── sierp_p.py ├── ch09 ├── bfs.gif ├── combinatorics.py ├── dfs.gif ├── graphs.py └── mystic.py ├── ch10 ├── animals.pkl ├── trees.c └── trees.py ├── ch11 ├── bernoulli.py ├── central.py ├── collies.py ├── continuous.py ├── dice.py ├── egypt.c ├── egypt.py ├── flips.py ├── ford.py ├── large.py ├── marbles.py ├── normal.py └── sequential.py ├── ch12 ├── anscombe.py ├── iris.py ├── memory.py └── stats.py ├── ch13 ├── GaussJordan.py ├── affine.py ├── determinant.py ├── gauss_jordan.py ├── ifs.py ├── ifs_maps.txt ├── island.gif ├── linear.py ├── matmul.c ├── matmul.py ├── rotation.py ├── treasure.png └── treasure.py ├── ch14 ├── DX.LISP ├── README.txt ├── abs.py ├── chain.py ├── dual.py ├── dual_test.py ├── extrema.py ├── gd_1d.py ├── gd_2d.py ├── make_slope_movie.py ├── make_slope_plot.py ├── minmax.py ├── newton.py ├── numeric.py ├── slopes.mp4 └── symbolic.py ├── ch15 ├── adaptive.py ├── darts.py ├── darts_plot.py ├── monte.py ├── monte_vs_naive.py ├── simp.py ├── sym_def.py ├── sym_indef.py └── trap.py ├── ch16 ├── SIR.py ├── SIRD.py ├── SIR_plot.py ├── color_map_names.txt ├── euler.py ├── lorenz.py ├── numeric_example.py ├── pendulum.py ├── pendulum_damped.py ├── projectile.py └── projectile_range.py ├── tutorial.pdf └── video ├── AND.mp4 ├── NOT.mp4 ├── OR.mp4 ├── README.txt ├── jameco1.png ├── jameco2.png └── jameco3.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 rkneusel9 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MathForProgramming 2 | Source code for the book "Math for Programming" (No Starch Press) 3 | 4 | -------------------------------------------------------------------------------- /ch01/big_or_little.c: -------------------------------------------------------------------------------- 1 | // Is your system big or little endian? 2 | #include 3 | #include 4 | 5 | int main() { 6 | uint32_t v = 0x11223344; 7 | uint8_t *p = (uint8_t *)&v; 8 | 9 | printf("Your system is "); 10 | 11 | switch (*p) { 12 | case 0x11: 13 | printf("big-endian\n"); 14 | break; 15 | case 0x44: 16 | printf("little-endian\n"); 17 | break; 18 | default: 19 | printf("something else\n"); 20 | } 21 | 22 | return 0; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /ch01/binary32.c: -------------------------------------------------------------------------------- 1 | // The bits of a 32-bit float 2 | #include 3 | #include 4 | 5 | int main() { 6 | float v = 2.718; 7 | uint32_t *p = (uint32_t *)&v; 8 | printf("%08x\n", *p); 9 | printf("%1.8f\n", v); 10 | return 0; 11 | } 12 | 13 | -------------------------------------------------------------------------------- /ch01/ints.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | unsigned char x; 5 | 6 | x = 164; printf("%d\n", x); // 10100100 7 | x = 255; printf("%d\n", x); // 11111111 8 | x = 1066; printf("%d\n", x); // 00101010 9 | x = 1963; printf("%d\n", x); // 10101011 10 | 11 | return 0; 12 | } 13 | -------------------------------------------------------------------------------- /ch01/logistic.py: -------------------------------------------------------------------------------- 1 | # Logistic map 2 | import sys 3 | import numpy as np 4 | import matplotlib.pylab as plt 5 | 6 | r = 3.9 7 | 8 | def A(x): 9 | return r*x*(1-x) 10 | 11 | def B(x): 12 | return r*x - r*x*x 13 | 14 | def C(x): 15 | return x*(r - r*x) 16 | 17 | def D(x): 18 | return r*x - r*x**2 19 | 20 | N = int(sys.argv[1]) 21 | x = [[0.25, 0.25, 0.25, 0.25]] 22 | 23 | for i in range(N): 24 | if (i < 10) or (i > N-11): 25 | print("%6d: %0.16f %0.16f %0.16f %0.16f" % (i,x[-1][0],x[-1][1],x[-1][2],x[-1][3])) 26 | x.append([A(x[-1][0]),B(x[-1][1]),C(x[-1][2]),D(x[-1][3])]) 27 | 28 | z = np.array(x) 29 | 30 | plt.subplot(2,2,1) 31 | plt.plot(z[0:100,0], color='k', marker='o', fillstyle='none', linewidth=0.6) 32 | plt.xlim((50,100)) 33 | plt.subplot(2,2,2) 34 | plt.plot(z[0:100,1], color='k', marker='s', fillstyle='none', linewidth=0.6) 35 | plt.xlim((50,100)) 36 | plt.subplot(2,2,3) 37 | plt.plot(z[0:100,2], color='k', marker='d', fillstyle='none', linewidth=0.6) 38 | plt.xlim((50,100)) 39 | plt.subplot(2,2,4) 40 | plt.plot(z[0:100,3], color='k', marker='^', fillstyle='none', linewidth=0.6) 41 | plt.xlim((50,100)) 42 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 43 | plt.savefig("logistic_plot0.png", dpi=300) 44 | plt.savefig("logistic_plot0.eps", dpi=300) 45 | plt.show() 46 | plt.close() 47 | 48 | plt.plot(np.arange(100),z[0:100,0], color='k', marker='o', fillstyle='none', linestyle='none') 49 | plt.plot(np.arange(100),z[0:100,3], color='k', marker='^', fillstyle='none', linestyle='none') 50 | plt.plot([54.5,54.5],[0,1],linestyle='dashed',color='k') 51 | plt.show() 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /ch01/nan_inf.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() { 6 | double y; 7 | y = log(-4.3); 8 | printf("log(-4.3) = %0.8f\n", y); 9 | y = exp(1000); 10 | printf("exp(1000)= %0.8f\n", y); 11 | return 0; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /ch01/roundoff.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: roundoff.py 3 | # 4 | # Demonstrate catastrophic roundoff error 5 | # 6 | # RTK, 07-Aug-2022 7 | # Last update: 07-Aug-2022 8 | # 9 | ################################################################ 10 | 11 | import numpy as np 12 | import sys 13 | from fractions import * 14 | 15 | if (len(sys.argv) == 1): 16 | print() 17 | print("roundoff ") 18 | print() 19 | print(" - number of times to repeat the sequence (e.g. 5)") 20 | print() 21 | exit(0) 22 | 23 | N = int(sys.argv[1]) 24 | 25 | p = Fraction(np.pi) 26 | pi = p 27 | for i in range(N): 28 | p= p*p*p + 33*p*p + Fraction(1,1) 29 | p= p - Fraction(1,1) 30 | p= p - 33*pi**2 31 | p= p/(pi*pi) 32 | pi = p 33 | 34 | p = np.pi 35 | for i in range(N): 36 | p= p*p*p + 33*p*p + 1 37 | p= p - 1 38 | p= p - 33*np.pi**2 39 | p= p/(np.pi*np.pi) 40 | pf = p 41 | 42 | print("pi : %0.18f" % np.pi) 43 | print("rational : %0.18f" % float(pi)) 44 | print("computer : %0.18f" % pf) 45 | print() 46 | 47 | -------------------------------------------------------------------------------- /ch01/sign_extend.c: -------------------------------------------------------------------------------- 1 | // sign extension 2 | #include 3 | #include 4 | 5 | int main() { 6 | int8_t n = -42; 7 | 8 | printf("as signed 8-bit int : %d\n", n); 9 | printf("as signed 16-bit int: %d\n", (int16_t)n); 10 | printf("as unsigned 8-bit hex : %x\n", (uint8_t)n); 11 | printf("as unsigned 16-bit hex: %x\n", (uint16_t)n); 12 | 13 | return 0; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /ch01/sum1.c: -------------------------------------------------------------------------------- 1 | // 0.1 is a repeating binary "decimal" 2 | #include 3 | 4 | int main() { 5 | float s = 0.0; 6 | 7 | for(int i=0; i < 100; i++) 8 | s += 0.1; 9 | 10 | printf("%0.16f\n", s); 11 | return 0; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /ch02/NumberSets.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: NumberSets.py 3 | # 4 | # Multiplies any order lists representing reals, 5 | # complex, quaternions, octonions, and sedenions, ... 6 | # 7 | # CayleyDickson code from: 8 | # https://www.johndcook.com/blog/2018/07/10/cayley-dickson/ 9 | # used with permission 10 | # 11 | # RTK, 20-Feb-2022 12 | # Last update: 20-Feb-2022 13 | # 14 | ################################################################ 15 | 16 | import numpy as np 17 | 18 | # 19 | # Base code from John Cook's site. Will multiply any 20 | # number from R, C, H, O, S, and higher. Pass the number 21 | # as a NumPy vector. 22 | # 23 | def conj(x): 24 | xstar = -x 25 | xstar[0] *= -1 26 | return xstar 27 | 28 | def CayleyDickson(x, y): 29 | n = len(x) 30 | 31 | if n == 1: 32 | return x*y 33 | 34 | m = n // 2 35 | 36 | a, b = x[:m], x[m:] 37 | c, d = y[:m], y[m:] 38 | z = np.zeros(n) 39 | z[:m] = CayleyDickson(a, c) - CayleyDickson(conj(d), b) 40 | z[m:] = CayleyDickson(d, a) + CayleyDickson(b, conj(c)) 41 | return z 42 | 43 | 44 | # 45 | # RTK additions 46 | # 47 | 48 | ################################################################ 49 | # Number 50 | # 51 | class Number: 52 | """Base implementation""" 53 | 54 | def __init__(self, v): 55 | """Constructor""" 56 | self.v = v 57 | 58 | def __iter__(self): 59 | return iter(self.v) 60 | 61 | def __add__(self, z): 62 | ans = [] 63 | for i in range(len(self.v)): 64 | ans.append(self.v[i]+z.v[i]) 65 | return Number(ans) 66 | 67 | def __sub__(self, z): 68 | ans = [] 69 | for i in range(len(self.v)): 70 | ans.append(self.v[i]-z.v[i]) 71 | return Number(ans) 72 | 73 | def __mul__(self, z): 74 | A = np.array(self.v) 75 | B = np.array(list(z)) 76 | return Number(list(CayleyDickson(A,B))) 77 | 78 | def __neg__(self): 79 | ans = [] 80 | for i in range(len(self.v)): 81 | ans.append(-self.v[i]) 82 | return Number(ans) 83 | 84 | def conj(self): 85 | ans = [self.v[0]] 86 | for i in range(1,len(self.v)): 87 | ans.append(-self.v[i]) 88 | return Number(ans) 89 | 90 | def __abs__(self): 91 | c = self * self.conj() 92 | return np.sqrt(c.v[0]) 93 | 94 | def inv(self): 95 | m = self.__abs__()**2 96 | return Number([v/m for v in list(self.conj())]) 97 | 98 | def __truediv__(self, z): 99 | zinv = np.array(list(z.conj())) / abs(z)**2 100 | A = np.array(self.v) 101 | return Number(list(CayleyDickson(A,zinv))) 102 | 103 | 104 | ################################################################ 105 | # Quaternion 106 | # 107 | class Quaternion: 108 | """A simple quaternion class""" 109 | 110 | def __init__(self, a, b=None, c=None, d=None): 111 | """Constructor""" 112 | if (b == None): 113 | self.n = Number(a) 114 | else: 115 | self.n = Number([a,b,c,d]) 116 | def __str__(self): 117 | a,b,c,d = list(self.n) 118 | ans = "%g%+gi%+gj%+gk" % (a,b,c,d) 119 | return ans 120 | def __repr__(self): 121 | return self.__str__() 122 | def __iter__(self): 123 | return iter(list(self.n)) 124 | def __add__(self, z): 125 | return Quaternion(list(self.n + z.n)) 126 | def __sub__(self, z): 127 | return Quaternion(list(self.n - z.n)) 128 | def __mul__(self, z): 129 | return Quaternion(list(self.n * z.n)) 130 | def __neg__(self): 131 | return Quaternion(list(-self.n)) 132 | def conj(self): 133 | return Quaternion(list(self.n.conj())) 134 | def __abs__(self): 135 | return abs(self.n) 136 | def __truediv__(self, z): 137 | return Quaternion(list(self.n / z.n)) 138 | 139 | 140 | ################################################################ 141 | # Octonion 142 | # 143 | class Octonion: 144 | """A simple octonion class""" 145 | 146 | def __init__(self, a, b=None, c=None, d=None, e=None, f=None, g=None, h=None): 147 | """Constructor""" 148 | if (b == None): 149 | self.n = Number(a) 150 | else: 151 | self.n = Number([a,b,c,d,e,f,g,h]) 152 | def __str__(self): 153 | a,b,c,d,e,f,g,h = list(self.n) 154 | ans = "%g,%g,%g,%g,%g,%g,%g,%g" % (a,b,c,d,e,f,g,h) 155 | return ans 156 | def __repr__(self): 157 | return self.__str__() 158 | def __iter__(self): 159 | return iter(list(self.n)) 160 | def __add__(self, z): 161 | return Octonion(list(self.n + z.n)) 162 | def __sub__(self, z): 163 | return Octonion(list(self.n - z.n)) 164 | def __mul__(self, z): 165 | return Octonion(list(self.n * z.n)) 166 | def __neg__(self): 167 | return Octonion(list(-self.n)) 168 | def conj(self): 169 | return Octonion(list(self.n.conj())) 170 | def __abs__(self): 171 | return abs(self.n) 172 | def __truediv__(self, z): 173 | return Octonion(list(self.n / z.n)) 174 | 175 | 176 | ################################################################ 177 | # Sedenion 178 | # 179 | class Sedenion: 180 | """A simple sedenion class""" 181 | 182 | def __init__(self, a, b=None, c=None, d=None, e=None, f=None, g=None, h=None, 183 | i=None, j=None, k=None, l=None, m=None, n=None, o=None, p=None): 184 | """Constructor""" 185 | if (b == None): 186 | self.n = Number(a) 187 | else: 188 | self.n = Number([a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p]) 189 | def __str__(self): 190 | a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p = list(self.n) 191 | ans = "%g,%g,%g,%g,%g,%g,%g,%g,%g,%g,%g,%g,%g,%g,%g,%g" % (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) 192 | return ans 193 | def __repr__(self): 194 | return self.__str__() 195 | def __iter__(self): 196 | return iter(list(self.n)) 197 | def __add__(self, z): 198 | return Sedenion(list(self.n + z.n)) 199 | def __sub__(self, z): 200 | return Sedenion(list(self.n - z.n)) 201 | def __mul__(self, z): 202 | return Sedenion(list(self.n * z.n)) 203 | def __neg__(self): 204 | return Sedenion(list(-self.n)) 205 | def conj(self): 206 | return Sedenion(list(self.n.conj())) 207 | def __abs__(self): 208 | return abs(self.n) 209 | 210 | -------------------------------------------------------------------------------- /ch02/urandom.py: -------------------------------------------------------------------------------- 1 | def PhysicalRandom(f): 2 | return ord(f.read(1)) % 10 3 | f = open("/dev/urandom","rb") 4 | print("0.", end="") 5 | while True: print("%d" % PhysicalRandom(f), end="") 6 | 7 | -------------------------------------------------------------------------------- /ch03/boolean.c: -------------------------------------------------------------------------------- 1 | // Example Boolean functions 2 | #include 3 | #include 4 | 5 | uint8_t f(uint8_t x, uint8_t y) { 6 | return !x && (x || !y); 7 | } 8 | 9 | uint8_t fd(uint8_t x, uint8_t y) { 10 | return !x || (x && !y); 11 | } 12 | 13 | uint8_t fc(uint8_t x, uint8_t y) { 14 | return x || (!x && y); 15 | } 16 | 17 | uint8_t g(uint8_t a, uint8_t b, uint8_t c) { 18 | return (a || b&&!c) && (a&&b || !b&&c); 19 | } 20 | 21 | int main() { 22 | uint8_t x,y,a,b,c; 23 | 24 | printf("x y f\n-----\n"); 25 | for(x=0; x<2; x++) 26 | for(y=0; y<2; y++) 27 | printf("%d %d %d\n", x, y, f(x,y)); 28 | printf("\n"); 29 | 30 | printf("x y fd\n-----\n"); 31 | for(x=0; x<2; x++) 32 | for(y=0; y<2; y++) 33 | printf("%d %d %d\n", x, y, fd(x,y)); 34 | printf("\n"); 35 | 36 | printf("x y fc\n-----\n"); 37 | for(x=0; x<2; x++) 38 | for(y=0; y<2; y++) 39 | printf("%d %d %d\n", x, y, fc(x,y)); 40 | printf("\n"); 41 | 42 | printf("a b c g\n-------\n"); 43 | for(a=0; a<2; a++) 44 | for(b=0; b<2; b++) 45 | for(c=0; c<2; c++) 46 | printf("%d %d %d %d\n", a,b,c, g(a,b,c)); 47 | printf("\n"); 48 | 49 | return 0; 50 | } 51 | 52 | -------------------------------------------------------------------------------- /ch03/boolean.py: -------------------------------------------------------------------------------- 1 | # Example Boolean functions 2 | 3 | def f(x,y): 4 | return not x and (x or not y) 5 | 6 | print("x y f") 7 | print("-----") 8 | for x in [0,1]: 9 | for y in [0,1]: 10 | print("%d %d %d" % (x,y,f(x,y))) 11 | print() 12 | 13 | def fc(x,y): 14 | return x or not x and y 15 | 16 | print("x y fc") 17 | print("------") 18 | for x in [0,1]: 19 | for y in [0,1]: 20 | print("%d %d %d" % (x,y,fc(x,y))) 21 | print() 22 | 23 | def g(a,b,c): 24 | return (a or b and not c) and (a and b or not b and c) 25 | 26 | print("a b c g") 27 | print("-------") 28 | for a in [0,1]: 29 | for b in [0,1]: 30 | for c in [0,1]: 31 | print("%d %d %d %d" % (a,b,c,g(a,b,c))) 32 | print() 33 | 34 | def gc(a,b,c): 35 | return not a and (not b or c) or (not a or not b) and (b or not c) 36 | 37 | print("a b c gc") 38 | print("--------") 39 | for a in [0,1]: 40 | for b in [0,1]: 41 | for c in [0,1]: 42 | print("%d %d %d %d" % (a,b,c,gc(a,b,c))) 43 | print() 44 | 45 | def gd(a,b,c): 46 | return (a and (b or not c)) or ((a or b) and (not b or c)) 47 | 48 | print("a b c gd") 49 | print("--------") 50 | for a in [0,1]: 51 | for b in [0,1]: 52 | for c in [0,1]: 53 | print("%d %d %d %d" % (a,b,c,gd(a,b,c))) 54 | print() 55 | 56 | def e(a,b,c): 57 | return (a and not b and c) or (not a and b and not c) or (a and b and c) 58 | 59 | print("a b c e") 60 | print("-------") 61 | for a in [0,1]: 62 | for b in [0,1]: 63 | for c in [0,1]: 64 | print("%d %d %d %d" % (a,b,c,e(a,b,c))) 65 | print() 66 | 67 | def ed(a,b,c): 68 | return (a or not b or c) and (not a or b or not c) and (a or b or c) 69 | 70 | print("a b c ed") 71 | print("--------") 72 | for a in [0,1]: 73 | for b in [0,1]: 74 | for c in [0,1]: 75 | print("%d %d %d %d" % (a,b,c,ed(a,b,c))) 76 | print() 77 | 78 | def ec(a,b,c): 79 | return (not a or b or not c) and (a or not b or c) and (not a or not b or not c) 80 | 81 | print("a b c ec") 82 | print("--------") 83 | for a in [0,1]: 84 | for b in [0,1]: 85 | for c in [0,1]: 86 | print("%d %d %d %d" % (a,b,c,ec(a,b,c))) 87 | 88 | -------------------------------------------------------------------------------- /ch03/examples.py: -------------------------------------------------------------------------------- 1 | # exercises 2 | 3 | def IsTheSame2(F,G): 4 | """Do the expressions generate the same truth table?""" 5 | 6 | def Apply(a,b,E): 7 | """Apply a and b to an expression, E""" 8 | return eval(E) 9 | 10 | A = [] 11 | B = [] 12 | for a in [0,1]: 13 | for b in [0,1]: 14 | A.append(Apply(a,b,F)) 15 | B.append(Apply(a,b,G)) 16 | return A == B 17 | 18 | def IsTheSame3(F,G): 19 | def Apply(a,b,c,E): 20 | return eval(E) 21 | A = [] 22 | B = [] 23 | for a in [0,1]: 24 | for b in [0,1]: 25 | for c in [0,1]: 26 | A.append(Apply(a,b,c,F)) 27 | B.append(Apply(a,b,c,G)) 28 | return A == B 29 | 30 | # 31 | # main 32 | # 33 | print(IsTheSame3("not (a and b and c)", "not a or not b or not c")) 34 | print(IsTheSame2("not a or not b or a and b", "1")) 35 | print(IsTheSame2("not a and (not a or not b)", "not a")) 36 | print(IsTheSame3("a and not c or not a and b or not (a and c)", "not (a and c)")) 37 | print(IsTheSame2("not (a or (a and b or not b))", "not a and b")) 38 | print() 39 | print(IsTheSame3("(a and b) or (a and c)", "(b or c) and a")) 40 | 41 | -------------------------------------------------------------------------------- /ch03/g.py: -------------------------------------------------------------------------------- 1 | # SOP, POS, and complements 2 | 3 | def g(a,b,c): 4 | return (a or b and not c) and (a and b or not b and c) 5 | 6 | def SOP(a,b,c): 7 | return a and not b and c or a and b and not c or a and b and c 8 | 9 | def POS(a,b,c): 10 | return (a or b or c) and (a or b or not c) and (a or not b or c) and (a or not b or not c) and (not a or b or c) 11 | 12 | def gc(a,b,c): 13 | return not a and not b and not c or not a and not b and c or not a and b and not c or not a and b and c or a and not b and not c 14 | 15 | def gc2(a,b,c): 16 | return (not a or b or not c) and (not a or not b or c) and (not a or not b or not c) 17 | 18 | def simp(a,b,c): 19 | return a and (not b and c or b) 20 | 21 | def k(a,b,c): 22 | return a and b or a and c #a and (b or c) 23 | 24 | print("a b c g S P C 2 s k") 25 | print("-------------------") 26 | for a in [0,1]: 27 | for b in [0,1]: 28 | for c in [0,1]: 29 | print("%d %d %d %d %d %d %d %d %d %d" % (a,b,c, g(a,b,c), SOP(a,b,c), POS(a,b,c), gc(a,b,c), gc2(a,b,c), simp(a,b,c), k(a,b,c))) 30 | 31 | -------------------------------------------------------------------------------- /ch03/z.py: -------------------------------------------------------------------------------- 1 | # Karnaugh with z(a,b,c,d) 2 | 3 | def z(a,b,c,d): 4 | return a and not b and not c and not d or not a and not b and c and not d or a and b and not c or not a and not c and d or a and c 5 | 6 | def m(a,b,c,d): 7 | return b and not c and not d or not a and not c and d or not a and b and d or a and not b and d or a and b and c and not d 8 | 9 | print("a b c d z m") 10 | print("-----------") 11 | for a in [0,1]: 12 | for b in [0,1]: 13 | for c in [0,1]: 14 | for d in [0,1]: 15 | print("%d %d %d %d %d %d" % (a,b,c,d, z(a,b,c,d), m(a,b,c,d))) 16 | 17 | -------------------------------------------------------------------------------- /ch04/equivalence.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: equivalence.py 3 | # 4 | # Print the elements of the relation (assuming an equivalence relation) 5 | # 6 | # RTK, 19-Nov 7 | # Last update: 19-Nov-2022 8 | # 9 | ################################################################ 10 | 11 | import os 12 | import sys 13 | import matplotlib.pylab as plt 14 | import numpy as np 15 | 16 | def InRelation(a,b,R): 17 | """Return True if a,b in the relation, R""" 18 | return eval(R) 19 | 20 | # 21 | # main: 22 | # 23 | if (len(sys.argv) == 1): 24 | print() 25 | print("equivalence ") 26 | print() 27 | print(" - code for the relation") 28 | print(" ,, - x and y axis limits and step size") 29 | print() 30 | exit(0) 31 | 32 | R = sys.argv[1] 33 | xlo,xhi,xstep = float(sys.argv[2]), float(sys.argv[3]), float(sys.argv[4]) 34 | ylo,yhi,ystep = float(sys.argv[5]), float(sys.argv[6]), float(sys.argv[7]) 35 | 36 | nx = int((xhi-xlo)/xstep) + 1 37 | ny = int((yhi-ylo)/ystep) + 1 38 | A = np.linspace(xlo,xhi,nx) 39 | B = np.linspace(ylo,yhi,ny) 40 | 41 | points = [] 42 | for a in A: 43 | for b in B: 44 | if (InRelation(a,b,R)): 45 | points.append((a,b)) 46 | 47 | if (len(points) == 0): 48 | print("The relation is empty") 49 | exit(1) 50 | points = np.array(points) 51 | 52 | for i in range(len(points)): 53 | print("(%0.4f,%0.4f), " % (points[i,0],points[i,1]), end="") 54 | if (i!=0) and (i%18==0): 55 | print() 56 | print() 57 | 58 | -------------------------------------------------------------------------------- /ch04/rviewer.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: rviewer.py 3 | # 4 | # Visualize relations 5 | # 6 | # RTK, 15-Nov-2022 (happy birthday to human #8 billion!) 7 | # Last update: 15-Nov-2022 8 | # 9 | ################################################################ 10 | 11 | import os 12 | import sys 13 | import matplotlib.pylab as plt 14 | import numpy as np 15 | 16 | def InRelation(a,b,R): 17 | """Return True if a,b in the relation, R""" 18 | return eval(R) 19 | 20 | # 21 | # main: 22 | # 23 | if (len(sys.argv) == 1): 24 | print() 25 | print("rviewer ") 26 | print() 27 | print(" - code for the relation") 28 | print(" ,, - x and y axis limits and step size") 29 | print(" - output plot filename") 30 | print() 31 | exit(0) 32 | 33 | R = sys.argv[1] 34 | xlo,xhi,xstep = float(sys.argv[2]), float(sys.argv[3]), float(sys.argv[4]) 35 | ylo,yhi,ystep = float(sys.argv[5]), float(sys.argv[6]), float(sys.argv[7]) 36 | oname = sys.argv[8] 37 | 38 | nx = int((xhi-xlo)/xstep) + 1 39 | ny = int((yhi-ylo)/ystep) + 1 40 | A = np.linspace(xlo,xhi,nx) 41 | B = np.linspace(ylo,yhi,ny) 42 | 43 | points = [] 44 | for a in A: 45 | for b in B: 46 | if (InRelation(a,b,R)): 47 | points.append((a,b)) 48 | 49 | if (len(points) == 0): 50 | print("The relation is empty") 51 | exit(1) 52 | points = np.array(points) 53 | 54 | plt.plot(points[:,0], points[:,1], marker='o', markersize=0.5, linestyle='none', color='k') 55 | plt.xlim((xlo,xhi)) 56 | plt.ylim((ylo,yhi)) 57 | plt.xlabel("$a$") 58 | plt.ylabel("$b$") 59 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 60 | plt.savefig(oname, dpi=300) 61 | plt.show() 62 | 63 | -------------------------------------------------------------------------------- /ch05/binary.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int binary(int *A, int n, int v) { 4 | int mid, lo=0, hi=n-1; 5 | 6 | while (lo <= hi) { 7 | printf("%d %d %d\n",A[lo],v,A[hi]); 8 | mid = (lo + hi) / 2; 9 | if (A[mid] == v) 10 | return mid; 11 | else 12 | if (A[mid] < v) 13 | lo = mid + 1; 14 | else 15 | hi = mid - 1; 16 | } 17 | } 18 | 19 | 20 | int main() { 21 | int A[] = {0,2,4,5,6,8,11,12,13,17,22,23,25,34,38,42,66,72,88,99}; 22 | int v = 8; 23 | 24 | printf("A[%d]=%d\n", binary(A,20,v),v); 25 | return 0; 26 | } 27 | 28 | -------------------------------------------------------------------------------- /ch05/bubble.py: -------------------------------------------------------------------------------- 1 | def Bubble(A, display=False): 2 | """Bubble sort in place""" 3 | 4 | for i in range(len(A)-1): 5 | for j in range(i+1,len(A)): 6 | if (A[i] > A[j]): 7 | A[i], A[j] = A[j], A[i] 8 | if (display): 9 | print(A) 10 | 11 | if (__name__ == "__main__"): 12 | A = ['Moe','Larry','Shemp','Curly'] 13 | Bubble(A, display=True) 14 | 15 | -------------------------------------------------------------------------------- /ch05/gnome.py: -------------------------------------------------------------------------------- 1 | def Gnome(A, display=False): 2 | """Gnome sort in place""" 3 | 4 | p = 0 5 | while (p < len(A)): 6 | if (p == 0) or (A[p] >= A[p-1]): 7 | p += 1 8 | else: 9 | A[p], A[p-1] = A[p-1], A[p] 10 | p -= 1 11 | if (display): 12 | print(A, p) 13 | 14 | if (__name__ == "__main__"): 15 | A = ['Moe','Larry','Shemp','Curly'] 16 | Gnome(A, display=True) 17 | 18 | -------------------------------------------------------------------------------- /ch05/root.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char *argv[]) { 5 | int i=1,r=0,s=atoi(argv[1]); 6 | while (s>0) s-=i,i+=2,r++; 7 | printf("%d\n", r); return0; } 8 | 9 | -------------------------------------------------------------------------------- /ch06/collatz.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | def collatz(a, n=1000): 4 | """Return the sequence for a up to n terms""" 5 | 6 | seq = [a] 7 | while (a != 1) and (len(seq) < n): 8 | if ((a%2) == 0): 9 | a = a//2 10 | else: 11 | a = 3*a+1 12 | seq.append(a) 13 | return seq 14 | 15 | 16 | if (__name__ == "__main__"): 17 | if (len(sys.argv) == 1): 18 | print() 19 | print("collatz ") 20 | print() 21 | print(" -- a positive integer") 22 | print() 23 | exit(0) 24 | 25 | print(collatz(int(sys.argv[1]))) 26 | 27 | -------------------------------------------------------------------------------- /ch06/collatz_factors_2.py: -------------------------------------------------------------------------------- 1 | # Find the unique odd starting values that lead to a 2 | # maximum of 9232 3 | 4 | from sympy import isprime 5 | import numpy as np 6 | from collatz import * 7 | 8 | def factor2(n): 9 | k = 0 10 | while True: 11 | n = n // 2 12 | k += 1 13 | if (n%2) == 1: 14 | return k,n 15 | 16 | s = [] 17 | r = [] 18 | for i in range(1,1001): 19 | if (i%2) == 0: 20 | k,v = factor2(i) 21 | f = collatz(v) 22 | else: 23 | f = collatz(i) 24 | v = i 25 | s.append(max(f)) 26 | r.append(v) 27 | s = np.array(s) 28 | r = np.array(r) 29 | 30 | b = np.zeros(max(s)+1, dtype="uint16") 31 | for t in s: 32 | b[t] += 1 33 | 34 | idx = np.where(b!=0)[0] 35 | v = b[idx] 36 | k = idx.copy() 37 | i = np.argsort(v)[::-1] 38 | v = v[i] 39 | k = k[i] 40 | 41 | for i in range(len(v)): 42 | if (v[i] > 9): 43 | print("%7d %d" % (k[i],v[i])) 44 | 45 | i = np.where(s==9232)[0] 46 | z = np.unique(r[i]) # all unique, reduced n w/max 9232 47 | z.sort() 48 | print() 49 | print("%d unique reduced values w/9232 as maximum" % len(z)) 50 | print() 51 | print(z) 52 | np.save("collatz_unique_9232.npy", z) 53 | 54 | prime = [isprime(i) for i in z] 55 | nprime = len(np.where(prime)[0]) 56 | print() 57 | print("%d prime out of %d = %0.5f" % (nprime, len(z), nprime/len(z))) 58 | print() 59 | 60 | -------------------------------------------------------------------------------- /ch06/collatz_is_prime.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from sympy import isprime 3 | from scipy.stats import ttest_1samp 4 | import matplotlib.pylab as plt 5 | import random 6 | 7 | s = [] 8 | for i in range(1000): 9 | v = (np.argsort(np.random.random(1000))+1)[:188] 10 | p = 0 11 | for t in v: 12 | if (isprime(t)): 13 | p += 1 14 | s.append(p/188) 15 | 16 | s = np.array(s) 17 | print() 18 | print("Fraction prime: %0.7f +/- %0.7f" % (s.mean(), s.std(ddof=1)/np.sqrt(len(s)))) 19 | t,p = ttest_1samp(s,0.35106) 20 | print("(t=%0.6f, p=%0.8f)" % (t,p)) 21 | print() 22 | 23 | h,x = np.histogram(s, bins=60) 24 | h = h / h.sum() 25 | x = 0.5*(x[:-1]+x[1:]) 26 | plt.bar(x,h, color='k', width=0.8*(x[1]-x[0])) 27 | plt.plot([0.35106,0.35106],[0,s.max()], linewidth=1.2, color='k') 28 | plt.xlabel("Fraction prime") 29 | plt.ylabel("Fraction") 30 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 31 | plt.savefig("collatz_fraction_prime.png", dpi=300) 32 | plt.savefig("collatz_fraction_prime.eps", dpi=300) 33 | plt.show() 34 | 35 | -------------------------------------------------------------------------------- /ch06/collatz_max_hist.txt: -------------------------------------------------------------------------------- 1 | 9232 354 2 | 4372 28 3 | 2752 19 4 | 808 16 5 | 304 14 6 | 160 13 7 | 952 13 8 | 52 12 9 | 1672 11 10 | 1024 11 11 | 628 11 12 | 2248 10 13 | 592 9 14 | 916 9 15 | 39364 9 16 | 520 8 17 | 196 8 18 | 736 8 19 | 88 8 20 | 4192 7 21 | 1600 7 22 | 13120 7 23 | 3616 6 24 | 1384 6 25 | 232 6 26 | 2896 6 27 | 340 6 28 | 1492 6 29 | 448 6 30 | 16 6 31 | 3076 5 32 | 3220 5 33 | 2968 5 34 | 1888 5 35 | 4264 4 36 | 8080 4 37 | 1816 4 38 | 2536 4 39 | 1204 4 40 | 784 3 41 | 2176 3 42 | 820 3 43 | 832 3 44 | 868 3 45 | 928 3 46 | 1960 3 47 | 964 3 48 | 2464 3 49 | 976 3 50 | 904 3 51 | 688 3 52 | 724 3 53 | 256 3 54 | 21688 3 55 | 40 3 56 | 64 3 57 | 14308 3 58 | 100 3 59 | 112 3 60 | 136 3 61 | 148 3 62 | 712 3 63 | 208 3 64 | 244 3 65 | 184 3 66 | 5812 3 67 | 424 3 68 | 640 3 69 | 616 3 70 | 544 3 71 | 532 3 72 | 3940 3 73 | 472 3 74 | 280 3 75 | 1636 3 76 | 400 3 77 | 352 3 78 | 1192 2 79 | 1216 2 80 | 1264 2 81 | 1360 2 82 | 1156 2 83 | 1120 2 84 | 1396 2 85 | 1108 2 86 | 1408 2 87 | 1432 2 88 | 1480 2 89 | 1072 2 90 | 1048 2 91 | 1300 2 92 | 250504 2 93 | 3328 2 94 | 5992 2 95 | 2500 2 96 | 10528 2 97 | 41524 2 98 | 8584 2 99 | 4480 2 100 | 3508 2 101 | 4912 2 102 | 2608 2 103 | 3544 2 104 | 312 1 105 | 336 1 106 | 324 1 107 | 320 1 108 | 1624 1 109 | 308 1 110 | 5128 1 111 | 288 1 112 | 5776 1 113 | 276 1 114 | 272 1 115 | 264 1 116 | 260 1 117 | 296 1 118 | 4840 1 119 | 344 1 120 | 408 1 121 | 4336 1 122 | 468 1 123 | 464 1 124 | 456 1 125 | 452 1 126 | 4408 1 127 | 416 1 128 | 404 1 129 | 240 1 130 | 4804 1 131 | 392 1 132 | 384 1 133 | 372 1 134 | 368 1 135 | 360 1 136 | 356 1 137 | 6424 1 138 | 224 1 139 | 6964 1 140 | 15856 1 141 | 12148 1 142 | 84 1 143 | 80 1 144 | 72 1 145 | 68 1 146 | 15064 1 147 | 56 1 148 | 48 1 149 | 11392 1 150 | 18952 1 151 | 32 1 152 | 24 1 153 | 20 1 154 | 8 1 155 | 4 1 156 | 2 1 157 | 96 1 158 | 104 1 159 | 228 1 160 | 176 1 161 | 488 1 162 | 212 1 163 | 7504 1 164 | 200 1 165 | 192 1 166 | 9556 1 167 | 180 1 168 | 168 1 169 | 11176 1 170 | 9880 1 171 | 152 1 172 | 10024 1 173 | 144 1 174 | 132 1 175 | 128 1 176 | 116 1 177 | 480 1 178 | 528 1 179 | 512 1 180 | 944 1 181 | 996 1 182 | 984 1 183 | 980 1 184 | 2452 1 185 | 960 1 186 | 2512 1 187 | 948 1 188 | 936 1 189 | 2416 1 190 | 2560 1 191 | 920 1 192 | 912 1 193 | 190996 1 194 | 896 1 195 | 2632 1 196 | 852 1 197 | 2440 1 198 | 2368 1 199 | 840 1 200 | 1876 1 201 | 1576 1 202 | 1540 1 203 | 1684 1 204 | 1696 1 205 | 1732 1 206 | 1792 1 207 | 1840 1 208 | 1972 1 209 | 2308 1 210 | 2080 1 211 | 2116 1 212 | 2128 1 213 | 2152 1 214 | 2164 1 215 | 2224 1 216 | 2260 1 217 | 848 1 218 | 836 1 219 | 3976 1 220 | 3688 1 221 | 648 1 222 | 3472 1 223 | 624 1 224 | 612 1 225 | 608 1 226 | 600 1 227 | 596 1 228 | 576 1 229 | 680 1 230 | 564 1 231 | 560 1 232 | 552 1 233 | 3796 1 234 | 536 1 235 | 3904 1 236 | 1588 1 237 | 672 1 238 | 692 1 239 | 2728 1 240 | 768 1 241 | 2836 1 242 | 816 1 243 | 2848 1 244 | 800 1 245 | 792 1 246 | 788 1 247 | 2884 1 248 | 744 1 249 | 696 1 250 | 740 1 251 | 2944 1 252 | 2992 1 253 | 720 1 254 | 3256 1 255 | 708 1 256 | 704 1 257 | 1 1 258 | -------------------------------------------------------------------------------- /ch06/collatz_plot.py: -------------------------------------------------------------------------------- 1 | from collatz import * 2 | import numpy as np 3 | import matplotlib.pylab as plt 4 | 5 | # Figure 11.11 in Pickover, "Computers, Pattern, Chaos and Beauty" (1990) 6 | x = [] 7 | y = [] 8 | for n in range(1,1001): 9 | s = collatz(n) 10 | for t in s: 11 | if (t <= 1000): 12 | x.append(n) 13 | y.append(t) 14 | 15 | plt.plot(x,y, marker='o', markersize=0.3, color='k', linestyle='none') 16 | plt.xlabel("$n$") 17 | plt.ylabel("Sequence") 18 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 19 | plt.savefig("collatz_sequence_plot.png", dpi=300) 20 | plt.savefig("collatz_sequence_plot.eps", dpi=300) 21 | plt.close() 22 | 23 | # Bar plot of maximum sequence values 24 | s = [] 25 | for n in range(5,201): 26 | s.append(max(collatz(n))) 27 | plt.bar(range(5,201), s, width=0.6, color='k') 28 | plt.xlabel("$n$") 29 | plt.ylabel("Maximum sequence value") 30 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 31 | plt.savefig("collatz_max_plot.png", dpi=300) 32 | plt.savefig("collatz_max_plot.eps", dpi=300) 33 | plt.close() 34 | 35 | # Bar plot of sequence lengths 36 | s = [] 37 | for n in range(5,201): 38 | s.append(len(collatz(n))) 39 | plt.bar(range(5,201), s, width=0.6, color='k') 40 | plt.xlabel("$n$") 41 | plt.ylabel("Sequence length") 42 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 43 | plt.savefig("collatz_length_plot.png", dpi=300) 44 | plt.savefig("collatz_length_plot.eps", dpi=300) 45 | plt.close() 46 | 47 | -------------------------------------------------------------------------------- /ch06/collatz_unique_9232.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkneusel9/MathForProgramming/ae6d990fa612dff8b39e6806923deae566c0ff1d/ch06/collatz_unique_9232.npy -------------------------------------------------------------------------------- /ch06/ex2.py: -------------------------------------------------------------------------------- 1 | # Compare complex a_n to the recurrence 2 | 3 | def A(n): 4 | return complex(-3/8,-1/2)*complex(0,2)**n + complex(-3/8,1/2)*complex(0,-2)**n 5 | 6 | s = [A(n) for n in range(1,7)] 7 | print("a_n gives:") 8 | for t in s: 9 | print(t," ", end="") 10 | print() 11 | 12 | print() 13 | print("recurrence gives:") 14 | a = [2,3] 15 | print("%d %d " % (a[0],a[1]), end="") 16 | 17 | for i in range(4): 18 | v = -4*a[-2] 19 | print("%d " % v, end="") 20 | a.append(v) 21 | print() 22 | 23 | -------------------------------------------------------------------------------- /ch06/ex3.py: -------------------------------------------------------------------------------- 1 | def a(n): 2 | return 4*2**n - 7*n 3 | 4 | s = [a(n) for n in range(1,7)] 5 | print("a_n gives:") 6 | for t in s: 7 | print(t," ", end="") 8 | print() 9 | 10 | print() 11 | print("recurrence gives:") 12 | a = [1,2] 13 | print("%d %d " % (a[0],a[1]), end="") 14 | 15 | for n in range(3,7): 16 | v = 2*a[-1] - a[-2] + 2**n 17 | print("%d " % v, end="") 18 | a.append(v) 19 | print() 20 | 21 | -------------------------------------------------------------------------------- /ch06/fact.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | uint64_t fact(uint64_t n) { 5 | if (n < 2) 6 | return 1; 7 | else 8 | return n * fact(n-1); 9 | } 10 | 11 | int main() { 12 | for(int n=0; n<21; n++) 13 | printf("%2d! = %" PRIu64 "\n", n, fact(n)); 14 | return 0; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /ch06/fib.py: -------------------------------------------------------------------------------- 1 | # a_n = (1/sqrt(5))*(phi**n - (1-phi)**n) 2 | from decimal import * 3 | 4 | getcontext().prec = 50 5 | phi = (Decimal(1)+Decimal(5).sqrt()) / Decimal(2) 6 | C = Decimal(1) / Decimal(5).sqrt() 7 | 8 | def F(n): 9 | return C*(phi**n - (Decimal(1)-phi)**n) 10 | 11 | for n in range(1,100): 12 | f = str(F(n)).split(".")[0] 13 | print("F(%2d) = %s" % (n,f)) 14 | 15 | -------------------------------------------------------------------------------- /ch06/fib1.scm: -------------------------------------------------------------------------------- 1 | ; Scheme supports tail recursion, no limit on recursive calls 2 | ; 3 | ; > sudo apt-get install racket <--- install 4 | ; > racket -f fib1.scm <--- run 5 | 6 | (define (fib1 n) 7 | (define (f a b n) 8 | (if (= n 0) 9 | a 10 | (f b (+ a b) (- n 1)))) ; tail-recursive call 11 | (f 0 1 n) ) 12 | 13 | (display (fib1 4000)) 14 | (newline) 15 | 16 | -------------------------------------------------------------------------------- /ch06/fib_test.py: -------------------------------------------------------------------------------- 1 | from fibonacci import * 2 | import numpy as np 3 | import time 4 | import sys 5 | 6 | f0 = [] 7 | f1 = [] 8 | ff = [] 9 | 10 | N = 20 11 | M = int(sys.argv[1]) 12 | 13 | for i in range(N): 14 | s = time.time(); _ = fib0(M); f0.append(time.time()-s) 15 | s = time.time(); _ = fib1(M); f1.append(time.time()-s) 16 | s = time.time(); _ = fib(M); ff.append(time.time()-s) 17 | 18 | f0 = np.array(f0) 19 | f1 = np.array(f1) 20 | ff = np.array(ff) 21 | 22 | print() 23 | print("Time to calculate F_%d (%d reps)" % (M,N)) 24 | print() 25 | print("fib0: %0.10f +/- %0.10f" % (f0.mean(), f0.std(ddof=1)/np.sqrt(N))) 26 | print("fib1: %0.10f +/- %0.10f" % (f1.mean(), f1.std(ddof=1)/np.sqrt(N))) 27 | print("fib : %0.10f +/- %0.10f" % (ff.mean(), ff.std(ddof=1)/np.sqrt(N))) 28 | print() 29 | print("fib0 is %0.3f times faster than fib1" % (f1.mean()/f0.mean(),)) 30 | print("fib0 is %0.3f times faster than fib" % (ff.mean()/f0.mean(),)) 31 | print() 32 | 33 | -------------------------------------------------------------------------------- /ch06/fibonacci.py: -------------------------------------------------------------------------------- 1 | # Recursive Fibonacci sequence generator 2 | import time 3 | import matplotlib.pylab as plt 4 | 5 | # iterative, no recursion 6 | def fib0(n): 7 | a,b = 0,1 8 | while (n > 0): 9 | a,b = b,a+b 10 | n -= 1 11 | return a 12 | 13 | # push down single recursive call 14 | def fib1(n): 15 | def f(a,b,n): 16 | if (n == 0): 17 | return a 18 | else: 19 | return f(b,a+b,n-1) 20 | return f(0,1,n) 21 | 22 | # horrible push up double recursive call 23 | def fib(n): 24 | if (n < 3): 25 | return 1 26 | else: 27 | return fib(n-1) + fib(n-2) 28 | 29 | 30 | if (__name__ == "__main__"): 31 | N = [2,5,10,15,20,25,30,35,40] 32 | t = [] 33 | 34 | for n in N: 35 | s = time.time() 36 | _ = fib(n) 37 | t.append(time.time()-s) 38 | 39 | plt.plot(N,t, marker='o', fillstyle='none', color='k') 40 | plt.xlabel("$F_n$") 41 | plt.ylabel("Time (s)") 42 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 43 | plt.savefig("fibonacci_recursive.png", dpi=300) 44 | plt.savefig("fibonacci_recursive.eps", dpi=300) 45 | plt.close() 46 | 47 | -------------------------------------------------------------------------------- /ch06/logistic_cycle.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: logistic_cycle.py 3 | # 4 | # Generate the bifurcation plot by varying r 5 | # 6 | # RTK, 26-Dec-2022 7 | # Last update: 26-Dec-2022 8 | # 9 | ################################################################ 10 | 11 | import numpy as np 12 | import matplotlib.pylab as plt 13 | 14 | # 1 2 4 8 chaos 15 | for k,r in enumerate([2.4,3.3,3.5,3.5644072661,3.9]): 16 | X = []; R = [] 17 | x = 0.01 18 | for i in range(1500): 19 | x = r*x*(1-x) 20 | for i in range(60): 21 | X.append(x) 22 | R.append(i) 23 | x = r*x*(1-x) 24 | 25 | plt.figure(figsize=(14,3)) 26 | plt.plot(R,X, marker='o', linewidth=0.7, color='k') 27 | plt.xlabel("Iteration") 28 | plt.ylabel("$x$") 29 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 30 | plt.savefig("logistic_cycle_%d.png" % k, dpi=300) 31 | plt.savefig("logistic_cycle_%d.eps" % k, dpi=300) 32 | plt.close() 33 | 34 | -------------------------------------------------------------------------------- /ch06/logistic_map.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: logistic_map.py 3 | # 4 | # Generate the bifurcation plot by varying r 5 | # 6 | # RTK, 26-Dec-2022 7 | # Last update: 26-Dec-2022 8 | # 9 | ################################################################ 10 | 11 | import numpy as np 12 | import matplotlib.pylab as plt 13 | 14 | X = [] 15 | R = [] 16 | 17 | for r in np.linspace(2.2,3.99,10000): 18 | x = 0.01 19 | for i in range(1500): 20 | x = r*x*(1-x) 21 | for i in range(600): 22 | X.append(x) 23 | R.append(r) 24 | x = r*x*(1-x) 25 | 26 | plt.plot(R,X, marker=',', linestyle='none', color='k') 27 | plt.plot([2.4,2.4],[0,1], linewidth=0.7, color='k') 28 | plt.plot([3.3,3.3],[0,1], linewidth=0.7, color='k') 29 | plt.plot([3.5,3.5],[0,1], linewidth=0.7, color='k') 30 | plt.plot([3.5644072661,3.5644072661],[0,1], linewidth=0.7, color='k') 31 | plt.plot([3.9,3.9],[0,1], linewidth=0.7, color='k') 32 | plt.xlabel("$r$") 33 | plt.ylabel("$x$") 34 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 35 | plt.savefig("logistic_map_plot.png", dpi=600) 36 | plt.show() 37 | 38 | -------------------------------------------------------------------------------- /ch06/lucas.py: -------------------------------------------------------------------------------- 1 | # a_n = (2-sqrt(5))*phi**n + (2+sqrt(5))*(1-phi)**n 2 | from decimal import * 3 | 4 | getcontext().prec = 50 5 | phi = (Decimal(1)+Decimal(5).sqrt()) / Decimal(2) 6 | A = Decimal(1) 7 | B = Decimal(1) 8 | 9 | def L(n): 10 | return A*phi**n + B*(Decimal(1)-phi)**n 11 | 12 | for n in range(1,100): 13 | f = str(L(n)).split(".")[0] 14 | print("L(%2d) = %s" % (n,f)) 15 | 16 | # 1, 3, 4, 7, 11, 18, 29, 47, 76, 123, ... 17 | 18 | -------------------------------------------------------------------------------- /ch06/mandel.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: mandel.py 3 | # 4 | # Generate a Mandelbrot set image more efficiently 5 | # 6 | # time python3 -W ignore mandel.py -2 0.6 0.001 -1.1 1.1 0.001 400 mandel.png 7 | # 8 | # RTK, 06-Feb-2022 9 | # Last update: 26-Dec-2022 10 | # 11 | ################################################################ 12 | 13 | import sys 14 | import numpy as np 15 | from matplotlib import cm 16 | from PIL import Image 17 | 18 | 19 | ################################################################ 20 | # Mandelbrot 21 | # 22 | def Mandelbrot(x0,x1,xinc, y0,y1,yinc, nmax): 23 | """Generate the image""" 24 | 25 | x = x0 26 | i = 0 27 | while (x <= x1): 28 | y = y0 29 | j = 0 30 | while (y <= y1): 31 | y += yinc 32 | j += 1 33 | x += xinc 34 | i += 1 35 | 36 | c = np.zeros((j,i), dtype="complex64") 37 | z = np.zeros((j,i), dtype="complex64") 38 | img = 255*np.ones((j,i), dtype="uint8") 39 | 40 | x = x0 41 | i = 0 42 | while (x <= x1): 43 | y = y0 44 | j = 0 45 | while (y <= y1): 46 | c[j,i] = complex(x,y) 47 | y += yinc 48 | j += 1 49 | x += xinc 50 | i += 1 51 | 52 | for k in range(nmax): 53 | z = z*z + c 54 | 55 | img[np.where(np.abs(z) < 2)] = 0 56 | return img 57 | 58 | 59 | if (len(sys.argv) == 1): 60 | print() 61 | print("mandelbrot ") 62 | print() 63 | print(" - low, high, inc in x") 64 | print(" - low, high, inc in y") 65 | print(" - max iterations per point") 66 | print(" - output image name") 67 | print() 68 | exit(0) 69 | 70 | x0 = float(sys.argv[1]) 71 | x1 = float(sys.argv[2]) 72 | xinc = float(sys.argv[3]) 73 | 74 | y0 = float(sys.argv[4]) 75 | y1 = float(sys.argv[5]) 76 | yinc = float(sys.argv[6]) 77 | 78 | nmax = int(sys.argv[7]) 79 | oname= sys.argv[8] 80 | 81 | img = Mandelbrot(x0,x1,xinc, y0,y1,yinc, nmax) 82 | Image.fromarray(img).save(oname) 83 | 84 | -------------------------------------------------------------------------------- /ch06/mandelbrot.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: mandelbrot.py 3 | # 4 | # Generate a Mandelbrot set image 5 | # 6 | # E.g. 7 | # time python3 -W ignore mandelbrot.py -2 0.6 0.001 -1.1 1.1 0.001 400 tab20b mandelbrot.png 8 | # 9 | # Matplotlib color tables: 10 | # https://matplotlib.org/stable/tutorials/colors/colormaps.html 11 | # 12 | # RTK, 06-Feb-2022 13 | # Last update: 27-Dec-2022 14 | # 15 | ################################################################ 16 | 17 | import sys 18 | import numpy as np 19 | from matplotlib import cm 20 | from PIL import Image 21 | 22 | 23 | ################################################################ 24 | # Mandelbrot 25 | # 26 | def Mandelbrot(x0,x1,xinc, y0,y1,yinc, nmax, cname): 27 | """Generate a collection of Mandelbrot points and colors""" 28 | 29 | try: 30 | cmap = cm.get_cmap(cname) 31 | except: 32 | cmap = cm.get_cmap("inferno") 33 | 34 | X = [] 35 | Y = [] 36 | C = [] 37 | 38 | x = x0 39 | i = 0 40 | while (x <= x1): 41 | y = y0 42 | j = 0 43 | while (y <= y1): 44 | c = complex(x,y) 45 | z = 0+0j 46 | for k in range(nmax): 47 | z = z*z + c 48 | if (np.abs(z) > 2): 49 | break 50 | X.append(i) 51 | Y.append(j) 52 | w = cmap(int(256*(k/nmax))) 53 | if (k == nmax-1): 54 | C.append((0,0,0)) 55 | else: 56 | C.append((w[0],w[1],w[2])) 57 | y += yinc 58 | j += 1 59 | x += xinc 60 | i += 1 61 | 62 | return X,Y,C 63 | 64 | 65 | ################################################################ 66 | # CreateOutputImage 67 | # 68 | def CreateOutputImage(X,Y,C): 69 | """Take the Mandelbrot points and create the output image""" 70 | 71 | x = np.array(X) 72 | y = np.array(Y) 73 | xmin = x.min(); xmax = x.max() 74 | dx = xmax - xmin 75 | ymin = y.min(); ymax = y.max() 76 | dy = ymax - ymin 77 | img = np.zeros((dy,dx,3), dtype="uint8") 78 | 79 | for i in range(len(x)): 80 | xx = int((dx-1)*(x[i] - xmin) / dx) 81 | yy = int((dy-1)*(y[i] - ymin) / dy) 82 | c = C[i] 83 | img[ymax-yy-1,xx,0] = int(255*c[0]) 84 | img[ymax-yy-1,xx,1] = int(255*c[1]) 85 | img[ymax-yy-1,xx,2] = int(255*c[2]) 86 | 87 | return img 88 | 89 | 90 | if (len(sys.argv) == 1): 91 | print() 92 | print("mandelbrot ") 93 | print() 94 | print(" - low, high, inc in x") 95 | print(" - low, high, inc in y") 96 | print(" - max iterations per point") 97 | print(" - color map name") 98 | print(" - output image name") 99 | print() 100 | exit(0) 101 | 102 | x0 = float(sys.argv[1]) 103 | x1 = float(sys.argv[2]) 104 | xinc = float(sys.argv[3]) 105 | 106 | y0 = float(sys.argv[4]) 107 | y1 = float(sys.argv[5]) 108 | yinc = float(sys.argv[6]) 109 | 110 | nmax = int(sys.argv[7]) 111 | 112 | cname = sys.argv[8] 113 | oname= sys.argv[9] 114 | 115 | X,Y,C = Mandelbrot(x0,x1,xinc, y0,y1,yinc, nmax, cname) 116 | Image.fromarray(CreateOutputImage(X,Y,C)).save(oname) 117 | 118 | -------------------------------------------------------------------------------- /ch06/minstd.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | uint64_t x = 1; 5 | 6 | uint64_t minstd() { 7 | x = (48271*x) % 2147483647; 8 | return x; 9 | } 10 | 11 | float minstdf() { 12 | return minstd() / 2147483647.0; 13 | } 14 | 15 | 16 | int main() { 17 | FILE *f; 18 | uint8_t b; 19 | f = fopen("minstd.bin","wb"); 20 | for(int i=0; i<10000000; i++) { 21 | b = (uint8_t)(minstd() & 0xff); 22 | fwrite((void*)&b, sizeof(uint8_t), 1, f); 23 | } 24 | fclose(f); 25 | return 0; 26 | } 27 | 28 | -------------------------------------------------------------------------------- /ch06/powers.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: powers.py 3 | # 4 | # Compare loop and recursive power functions 5 | # 6 | # RTK, 20-Dec-2022 7 | # Last update: 30-Dec-2022 8 | # 9 | ################################################################ 10 | 11 | import time 12 | import numpy as np 13 | import matplotlib.pylab as plt 14 | 15 | def PowerLoop(b, n): 16 | """Return b**n using a loop""" 17 | 18 | ans = 1 19 | while (n > 0): 20 | ans *= b 21 | n -= 1 22 | return ans 23 | 24 | 25 | def PowerRecursive(b, n): 26 | """Use recursion to calculate b**n""" 27 | 28 | if (n==0): 29 | return 1 30 | elif ((n%2)==0): 31 | return PowerRecursive(b,n//2)*PowerRecursive(b,n//2) 32 | else: 33 | return PowerRecursive(b,(n-1)//2)*b*PowerRecursive(b,(n-1)//2) 34 | 35 | 36 | if (__name__ == "__main__"): 37 | loop = [] 38 | recursive = [] 39 | n = [10,100,500,1000,5000,10000,20000,30000,40000,50000,80000,100000,200000,300000,400000,500000] 40 | for t in n: 41 | s = time.time(); 42 | PowerLoop(4,t); 43 | loop.append(time.time()-s) 44 | s = time.time(); 45 | PowerRecursive(4,t); 46 | recursive.append(time.time()-s) 47 | 48 | plt.plot(n,loop, marker='o', fillstyle='none', linewidth=0.7, color='k', label='loop') 49 | plt.plot(n,recursive, marker='s', fillstyle='none', linewidth=0.7, color='k', label='recursive') 50 | plt.xlabel("Exponent") 51 | plt.ylabel("Time (s)") 52 | plt.legend(loc='best') 53 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 54 | plt.savefig("powers.png", dpi=300) 55 | plt.savefig("powers.eps", dpi=300) 56 | plt.close() 57 | 58 | -------------------------------------------------------------------------------- /ch06/quicksort.py: -------------------------------------------------------------------------------- 1 | # vanilla quicksort 2 | import random 3 | 4 | def quicksort(arr): 5 | if (len(arr) < 2): 6 | return arr 7 | pivot = arr[0] 8 | low = []; same = []; high = [] 9 | for a in arr: 10 | if (a < pivot): 11 | low.append(a) 12 | elif (a > pivot): 13 | high.append(a) 14 | else: 15 | same.append(a) 16 | return quicksort(low) + same + quicksort(high) 17 | 18 | 19 | if (__name__ == "__main__"): 20 | arr = [random.randint(0,999) for i in range(60)] 21 | print(arr) 22 | print() 23 | print(quicksort(arr)) 24 | 25 | -------------------------------------------------------------------------------- /ch06/towers.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void move(int n, int src, int dst, int spare) { 4 | if (n == 0) 5 | printf("move %d from %d to %d\n", n, src, dst); 6 | else { 7 | move(n-1, src, spare, dst); 8 | printf("move %d from %d to %d\n", n, src, dst); 9 | move(n-1, spare, dst, src); 10 | } 11 | } 12 | 13 | int main() { 14 | // move *4* disks from peg 1 to peg 3 15 | move(3,1,3,2); 16 | return 0; 17 | } 18 | 19 | -------------------------------------------------------------------------------- /ch07/asm65/gcd: -------------------------------------------------------------------------------- 1 | CALL-151 2 | 0302 : A2 F8 8E 00 03 A2 28 8E 3 | 030A : 01 03 AD 00 03 CD 01 03 4 | 0312 : F0 1C 90 0D AD 00 03 38 5 | 031A : ED 01 03 8D 00 03 4C 0C 6 | 0322 : 03 AD 01 03 38 ED 00 03 7 | 032A : 8D 01 03 4C 0C 03 60 00 8 | BSAVE GCD,A770,L54 9 | -------------------------------------------------------------------------------- /ch07/asm65/gcd.s: -------------------------------------------------------------------------------- 1 | ; GCD using Euclid's algorithm: gcd(248,40) = 8 2 | ; RTK, 14-Jan-2023 3 | 4 | a equ $0300 5 | b equ $0301 6 | 7 | org $0302 8 | 9 | ; Setup, a=248, b=40 10 | ldx #$f8 11 | stx a 12 | ldx #$28 13 | stx b 14 | 15 | ; Loop until a=b 16 | loop lda a 17 | cmp b 18 | beq done 19 | bcc less 20 | 21 | ; a>b 22 | lda a 23 | sec 24 | sbc b 25 | sta a 26 | jmp loop 27 | 28 | ; a 2 | 3 | int main() { 4 | printf("%4d %4d %4d %4d\n", 33,7,33/7,33%7); 5 | printf("%4d %4d %4d %4d\n", -33,7,-33/7,-33%7); 6 | printf("%4d %4d %4d %4d\n", 33,-7,33/-7,33%-7); 7 | printf("%4d %4d %4d %4d\n", -33,-7,-33/-7,-33%-7); 8 | return 0; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /ch07/divide_examples/div.clp: -------------------------------------------------------------------------------- 1 | ; install CLIPS, run with 'clips -f2 div.clp', see clipsrules.net 2 | (printout t 33 " " 7 " " (div 33 7) " " (mod 33 7) crlf) 3 | (printout t -33 " " 7 " " (div -33 7) " " (mod -33 7) crlf) 4 | (printout t 33 " " -7 " " (div 33 -7) " " (mod 33 -7) crlf) 5 | (printout t -33 " " -7 " " (div -33 -7) " " (mod -33 -7) crlf) 6 | (exit) 7 | 8 | -------------------------------------------------------------------------------- /ch07/divide_examples/div.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | int main() { 5 | cout << 33 << " " << 7 << " " << 33/7 << " " << 33%7 << " " << endl; 6 | cout << -33 << " " << 7 << " " << -33/7 << " " << -33%7 << " " << endl; 7 | cout << 33 << " " << -7 << " " << 33/-7 << " " << 33%-7 << " " << endl; 8 | cout << -33 << " " << -7 << " " << -33/-7 << " " << -33%-7 << " " << endl; 9 | return 0; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /ch07/divide_examples/div.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public class Divide 4 | { 5 | public static void Main(string[] args) 6 | { 7 | Console.WriteLine((33/7)+" "+(33%7)); 8 | Console.WriteLine((-33/7)+" "+(-33%7)); 9 | Console.WriteLine((33/-7)+" "+(33%-7)); 10 | Console.WriteLine((-33/-7)+" "+(-33%-7)); 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /ch07/divide_examples/div.java: -------------------------------------------------------------------------------- 1 | public class Div { 2 | public static void main(String[] args) 3 | { 4 | System.out.println(33+" "+7+" "+(33/7)+" "+(33%7)); 5 | System.out.println(-33+" "+7+" "+(-33/7)+" "+(-33%7)); 6 | System.out.println(33+" "+-7+" "+(33/-7)+" "+(33%-7)); 7 | System.out.println(-33+" "+-7+" "+(-33/-7)+" "+(-33%-7)); 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /ch07/divide_examples/div.js: -------------------------------------------------------------------------------- 1 | // sudo apt-get install nodejs <== install 2 | // node div.js <== run 3 | a=33; b=7; q=Math.trunc(a/b); r=a%b; 4 | console.log(`${a} ${b} ${q} ${r}`); 5 | a=-33; b=7; q=Math.trunc(a/b); r=a%b; 6 | console.log(`${a} ${b} ${q} ${r}`); 7 | a=33; b=-7; q=Math.trunc(a/b); r=a%b; 8 | console.log(`${a} ${b} ${q} ${r}`); 9 | a=-33; b=-7; q=Math.trunc(a/b); r=a%b; 10 | console.log(`${a} ${b} ${q} ${r}`); 11 | 12 | -------------------------------------------------------------------------------- /ch07/divide_examples/div.scm: -------------------------------------------------------------------------------- 1 | ; Mimic C 2 | (display (quotient 33 7)) (display " ") (display (remainder 33 7)) (newline) 3 | (display (quotient -33 7)) (display " ") (display (remainder -33 7)) (newline) 4 | (display (quotient 33 -7)) (display " ") (display (remainder 33 -7)) (newline) 5 | (display (quotient -33 -7)) (display " ") (display (remainder -33 -7)) (newline) 6 | (newline) 7 | 8 | ; Mimic Python 9 | (display (floor (/ 33 7))) (display " ") (display (modulo 33 7)) (newline) 10 | (display (floor (/ -33 7))) (display " ") (display (modulo -33 7)) (newline) 11 | (display (floor (/ 33 -7))) (display " ") (display (modulo 33 -7)) (newline) 12 | (display (floor (/ -33 -7))) (display " ") (display (modulo -33 -7)) (newline) 13 | 14 | -------------------------------------------------------------------------------- /ch07/divide_examples/div.sno: -------------------------------------------------------------------------------- 1 | * SNOBOL4 -- see www.snobol4.com 2 | output = 33 " " 7 " " (33 / 7) " " REMDR(33,7) 3 | output = -33 " " 7 " " (-33 / 7) " " REMDR(-33,7) 4 | output = 33 " " -7 " " (33 / -7) " " REMDR(33,-7) 5 | output = -33 " " -7 " " (-33 / -7) " " REMDR(-33,-7) 6 | end 7 | 8 | -------------------------------------------------------------------------------- /ch07/divide_examples/div2.nim: -------------------------------------------------------------------------------- 1 | # sudo apt-get install nim <== install 2 | # nim c div2.nim <== compile 3 | # ./div2 <== run 4 | echo 33 div 7, " ", 33 mod 7 5 | echo -33 div 7, " ", -33 mod 7 6 | echo 33 div -7, " ", 33 mod -7 7 | echo -33 div -7, " ", -33 mod -7 8 | 9 | -------------------------------------------------------------------------------- /ch07/divide_examples/divide.p: -------------------------------------------------------------------------------- 1 | program Divide(input, output, stderr); 2 | 3 | var 4 | a,b,q,r : integer; 5 | 6 | begin 7 | a:=33; b:=7; q:=a div b; r:=a mod b; 8 | writeLn(a:4, b:4, q:4, r:4); 9 | a:=-33; b:=7; q:=a div b; r:=a mod b; 10 | writeLn(a:4, b:4, q:4, r:4); 11 | a:=33; b:=-7; q:=a div b; r:=a mod b; 12 | writeLn(a:4, b:4, q:4, r:4); 13 | a:=-33; b:=-7; q:=a div b; r:=a mod b; 14 | writeLn(a:4, b:4, q:4, r:4); 15 | end. 16 | 17 | -------------------------------------------------------------------------------- /ch07/divide_examples/divz.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn main() !void { 4 | const stdout = std.io.getStdOut().writer(); 5 | 6 | try stdout.print("Using divTrunc and mod:{s}", .{"\n"}); 7 | try stdout.print(" {d} {d} {d} {d}\n", .{ 33, 7, @divTrunc(33,7), @mod(33,7)}); 8 | try stdout.print(" {d} {d} {d} {d}\n", .{-33, 7, @divTrunc(-33,7), @mod(-33,7)}); 9 | try stdout.print(" {d} {d} {d} {d}\n", .{ 33,-7, @divTrunc(33,-7), @mod(33,-7)}); 10 | try stdout.print(" {d} {d} {d} {d}\n\n", .{-33,-7, @divTrunc(-33,-7),@mod(-33,-7)}); 11 | try stdout.print("Using divTrunc and rem:{s}", .{"\n"}); 12 | try stdout.print(" {d} {d} {d} {d}\n", .{ 33, 7, @divTrunc(33,7), @rem(33,7)}); 13 | try stdout.print(" {d} {d} {d} {d}\n", .{-33, 7, @divTrunc(-33,7), @rem(-33,7)}); 14 | try stdout.print(" {d} {d} {d} {d}\n", .{ 33,-7, @divTrunc(33,-7), @rem(33,-7)}); 15 | try stdout.print(" {d} {d} {d} {d}\n\n", .{-33,-7,@divTrunc(-33,-7), @rem(-33,-7)}); 16 | 17 | try stdout.print("Using divFloor and mod:{s}", .{"\n"}); 18 | try stdout.print(" {d} {d} {d} {d}\n", .{ 33, 7, @divFloor(33,7), @mod(33,7)}); 19 | try stdout.print(" {d} {d} {d} {d}\n", .{-33, 7, @divFloor(-33,7), @mod(-33,7)}); 20 | try stdout.print(" {d} {d} {d} {d}\n", .{ 33,-7, @divFloor(33,-7), @mod(33,-7)}); 21 | try stdout.print(" {d} {d} {d} {d}\n\n", .{-33,-7, @divFloor(-33,-7),@mod(-33,-7)}); 22 | try stdout.print("Using divFloor and rem:{s}", .{"\n"}); 23 | try stdout.print(" {d} {d} {d} {d}\n", .{ 33, 7, @divFloor(33,7), @rem(33,7)}); 24 | try stdout.print(" {d} {d} {d} {d}\n", .{-33, 7, @divFloor(-33,7), @rem(-33,7)}); 25 | try stdout.print(" {d} {d} {d} {d}\n", .{ 33,-7, @divFloor(33,-7), @rem(33,-7)}); 26 | try stdout.print(" {d} {d} {d} {d}\n\n", .{-33,-7,@divFloor(-33,-7), @rem(-33,-7)}); 27 | } 28 | 29 | -------------------------------------------------------------------------------- /ch07/erdos-straus.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: erdos-straus.py 3 | # 4 | # Brute force search for Erdos-Straus solutions for small n 5 | # 6 | # 4/n = 1/a + 1/b + 1/c, integer a,b,c positive integers, 7 | # integer n>=2 8 | # 9 | # Above true if: 10 | # 11 | # 4abc = n(bc+ac+ab) (rewrite the equation) 12 | # 13 | ################################################################ 14 | 15 | import pickle 16 | 17 | solns = [[]]*9 18 | limit = 250 19 | 20 | for a in range(1,limit+1): 21 | for b in range(1,limit+1): 22 | for c in range(1,limit+1): 23 | lhs = 4*a*b*c 24 | rhs = (b*c+a*c+a*b) 25 | if (2*rhs == lhs): 26 | solns[0] = solns[0] + [(a,b,c)] 27 | if (3*rhs == lhs): 28 | solns[1] = solns[1] + [(a,b,c)] 29 | if (4*rhs == lhs): 30 | solns[2] = solns[2] + [(a,b,c)] 31 | if (5*rhs == lhs): 32 | solns[3] = solns[3] + [(a,b,c)] 33 | if (6*rhs == lhs): 34 | solns[4] = solns[4] + [(a,b,c)] 35 | if (7*rhs == lhs): 36 | solns[5] = solns[5] + [(a,b,c)] 37 | if (8*rhs == lhs): 38 | solns[6] = solns[6] + [(a,b,c)] 39 | if (9*rhs == lhs): 40 | solns[7] = solns[7] + [(a,b,c)] 41 | if (10*rhs == lhs): 42 | solns[8] = solns[8] + [(a,b,c)] 43 | 44 | for i,s in enumerate(solns): 45 | print("%2d: % 3d solutions" % (i+2, len(s))) 46 | print() 47 | 48 | pickle.dump(solns, open("erdos-straus_solutions.pkl","wb")) 49 | 50 | -------------------------------------------------------------------------------- /ch07/euclidean.py: -------------------------------------------------------------------------------- 1 | # div and mod functions implementing Euclidean division 2 | # RTK, 18-Jan-2023 (Happy 87th TC!) 3 | 4 | def divmod(a,b): 5 | """calculate the integer quotient and remainder""" 6 | if ((a>0) and (b>0)) or ((a<0) and (b>0)): 7 | return a//b, a%b 8 | elif (a>0) and (b<0): 9 | return -(a//abs(b)), a%abs(b) 10 | else: 11 | return abs(a//abs(b)), a%abs(b) 12 | 13 | def div(a,b): 14 | """Euclidean integer division""" 15 | return divmod(a,b)[0] 16 | 17 | def mod(a,b): 18 | """Euclidean integer modulo""" 19 | return divmod(a,b)[1] 20 | 21 | -------------------------------------------------------------------------------- /ch07/harshad.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | from ntheory import * 3 | N = 800_000 4 | M = 800 5 | x = list(range(1,N,M)) 6 | y = [len(Harshad(i))/i for i in x] 7 | plt.plot(x,y,color='k') 8 | plt.ylim((0.097,0.15)) 9 | plt.xlabel("$n$") 10 | plt.ylabel("Fraction") 11 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 12 | plt.savefig("harshad.png", dpi=300) 13 | plt.savefig("harshad.eps", dpi=300) 14 | plt.close() 15 | 16 | -------------------------------------------------------------------------------- /ch07/harshad_multiple.py: -------------------------------------------------------------------------------- 1 | # MHN chain lengths 2 | 3 | import numpy as np 4 | from ntheory import * 5 | 6 | N = 100_000_000 7 | w = [] 8 | 9 | for i in range(1,N+1): 10 | if isHarshad(i): 11 | k = 1 12 | v = sum([int(d) for d in str(i)]) 13 | n = i//v 14 | last = 0 15 | if isHarshad(n): 16 | k = 0 17 | while isHarshad(n) and (n != last): 18 | k += 1 19 | v = sum([int(d) for d in str(n)]) 20 | last = n 21 | n = n//v 22 | w.append((i,k)) 23 | 24 | h = np.array(w) 25 | 26 | # frequency 27 | mh = h[:,1].max() 28 | for i in range(1,mh+1): 29 | t = len(np.where(h[:,1]==i)[0]) 30 | print("% 2d: % 9d" % (i,t)) 31 | print() 32 | print(h[np.where(h[:,1]==mh)[0],0]) 33 | print() 34 | print("(%d harshad numbers <= %d)" % (h.shape[0],N)) 35 | print() 36 | 37 | 38 | -------------------------------------------------------------------------------- /ch07/midi.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkneusel9/MathForProgramming/ae6d990fa612dff8b39e6806923deae566c0ff1d/ch07/midi.zip -------------------------------------------------------------------------------- /ch07/minv.py: -------------------------------------------------------------------------------- 1 | # modular multiplicative inverse -- from Wikipedia article "Extended Euclidean algorithm" 2 | 3 | def naive(a,n): 4 | """Naive search for the inverse of a mod n""" 5 | for i in range(0,n-1): 6 | if ((a*i) % n) == 1: 7 | return i 8 | return None 9 | 10 | def minv(a,n): 11 | """Return the multiplicative inverse of a mod n or None""" 12 | t,r,newt,newr = 0,n,1,a 13 | 14 | while (newr != 0): 15 | quotient = r // newr 16 | t,newt = newt, t-quotient*newt 17 | r,newr = newr, r-quotient*newr 18 | 19 | if (r > 1): 20 | return None # no inverse 21 | if (t < 0): 22 | t += n 23 | return t 24 | 25 | def inv(a,n): 26 | """Recursive inverse""" 27 | return a if (a<=1) else n - (n//a)*inv(n%a,n) % n 28 | 29 | -------------------------------------------------------------------------------- /ch07/naive_mod.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char *argv[]) { 5 | unsigned int a,n,i; 6 | 7 | a = atoi(argv[1]); 8 | n = atoi(argv[2]); 9 | 10 | for (i=0; i < n-1; i++) 11 | if (((a*i) % n) == 1) { 12 | printf("%d\n", i); 13 | return; 14 | } 15 | printf("no inverse\n"); 16 | return 0; 17 | } 18 | 19 | -------------------------------------------------------------------------------- /ch07/ntheory.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: ntheory.py 3 | # 4 | # A collection of number theory routines 5 | # 6 | # RTK, 05-Jan-2023 7 | # Last update: 31-Jan-2023 8 | # 9 | ################################################################ 10 | 11 | import random 12 | from math import floor, exp 13 | 14 | def PrimesBrute(n): 15 | """Use brute force division checks to find primes less than or equal to n""" 16 | primes = [] 17 | for p in range(2,n+1): 18 | prime = True 19 | for d in range(2,p//2+1): 20 | if (p%d) == 0: 21 | prime = False 22 | break 23 | if (prime): 24 | primes.append(p) 25 | return primes 26 | 27 | def Primes(n): 28 | """Use Eratosthenes' sieve, algorithm in Sorenson 1990""" 29 | s = [True]*(n+1) 30 | p = 2 31 | while (p*p <= n): 32 | for f in range(p, n//p+1): 33 | s[p*f] = False 34 | p += 1 35 | while (not s[p]): 36 | p += 1 37 | primes = [] 38 | for i,v in enumerate(s[2:-1]): 39 | if (v): 40 | primes.append(i+2) 41 | return primes 42 | 43 | def PrimeFactors(n, only=False): 44 | """Return the prime factors of n using trial division""" 45 | p = {} 46 | t = 2 47 | while (n > 1): 48 | if ((n%t) == 0): 49 | if (t in p): 50 | p[t] += 1 51 | else: 52 | p[t] = 1 53 | n = n // t 54 | else: 55 | t += 1 56 | w = [] 57 | for k in sorted(list(p.keys())): 58 | w.append((k,p[k])) 59 | if (only): 60 | w = [i[0] for i in w] 61 | return w 62 | 63 | def NaiveTotient(n): 64 | """A naive implementation of Euler's totient function""" 65 | count = 0 66 | for m in range(1,n+1): 67 | if (GCD(m,n) == 1): 68 | count += 1 69 | return count 70 | 71 | def Totient(n): 72 | """Euler's formula for phi(n)""" 73 | pf = [p[0] for p in PrimeFactors(n)] 74 | tot = n 75 | for p in pf: 76 | tot *= (1 - 1/p) 77 | return int(tot) 78 | 79 | def isHarshad(n): 80 | """Is n a harshad number?""" 81 | v = sum([int(d) for d in str(n)]) 82 | return (n%v) == 0 83 | 84 | def Harshad(N): 85 | """Return harshad numbers <= N""" 86 | return [n for n in range(1, N+1) if isHarshad(n)] 87 | 88 | def Divisors(n): 89 | """Return the divisors of n""" 90 | d = [] 91 | for i in range(1,n//2+1): 92 | if ((n%i) == 0): 93 | d.append(i) 94 | d.append(n) 95 | return d 96 | 97 | def Sigma(n, e=1): 98 | """Return sigma(n) == sigma_1(n)""" 99 | return sum([i**e for i in Divisors(n)]) 100 | 101 | def isPrime(n): 102 | """True if n is a prime""" 103 | pf = PrimeFactors(n) 104 | return (len(pf) == 1) and (pf[0][1] == 1) 105 | 106 | def isComposite(n): 107 | """True if n is a composite number""" 108 | return not isPrime(n) 109 | 110 | def isTwinPrime(a,b): 111 | """True if a and b are twin primes""" 112 | return isPrime(a) and isPrime(b) and (abs(a-b) == 2) 113 | 114 | def isSemiprime(n): 115 | """True if n is a semiprime""" 116 | pf = PrimeFactors(n) 117 | if (len(pf) > 2): 118 | return False 119 | if (len(pf) == 1): 120 | return True if (pf[0][1] == 2) else False 121 | if (pf[0][1]==1) and (pf[1][1]==1): 122 | return True 123 | return False 124 | 125 | def AliquotSum(n): 126 | """Aliquot sum of n""" 127 | return Sigma(n)-n 128 | 129 | def AliquotSequence(n, m=50): 130 | """Aliquot sequence up to m terms""" 131 | seq = [n] 132 | while (len(seq) <= 100): 133 | if (seq[-1] == 0): # or (Perfect(seq[-1]) 134 | break 135 | seq.append(AliquotSum(seq[-1])) 136 | return seq 137 | 138 | def isPerfect(n): 139 | """True if n is a perfect number""" 140 | return AliquotSum(n) == n 141 | 142 | def GCD(a,b): 143 | """Return the greatest common divisor of a and b""" 144 | while (b): 145 | a,b = b,a%b 146 | return abs(a) 147 | 148 | def GCD2(a,b): 149 | """Euclid's algorithm using differences, positive a,b only""" 150 | while (a != b): 151 | if (a>b): 152 | a -= b 153 | else: 154 | b -= a 155 | return a 156 | 157 | def ExtendedGCD(a,b): 158 | """the extended Euclidean algorithm, positive a,b only""" 159 | r0,r1 = a,b 160 | s0,s1 = 1,0 161 | t0,t1 = 0,1 162 | while (r1 != 0): 163 | r = r0 - (r0//r1)*r1 164 | s = s0 - (r0//r1)*s1 165 | t = t0 - (r0//r1)*t1 166 | r0,r1 = r1,r 167 | s0,s1 = s1,s 168 | t0,t1 = t1,t 169 | return r0,s0,t0 170 | 171 | def LCM(a,b): 172 | """Return the least common multiple of a and b""" 173 | return a*b//GCD(a,b) 174 | 175 | def Coprime(a,b): 176 | """Are a and b coprime?""" 177 | return GCD(a,b) == 1 178 | 179 | def MillerRabin(n, rounds=5): 180 | """Implementation of Miller-Rabin based on Wikipedia pseudocode""" 181 | # Sanity checks 182 | if (n==2): 183 | return True 184 | if (n%2 == 0): 185 | return False 186 | 187 | # Write n = d*2**s + 1 by incrementing s and dividing d=n by 2 until d is odd 188 | s = 0 189 | d = n-1 190 | while (d%2 == 0): 191 | s += 1 192 | d //= 2 193 | 194 | for k in range(rounds): 195 | a = int(random.randint(1,n-1)) # failure: n=65, a=8|18|47|57|64 (nonwitness numbers for 65) 196 | x = pow(a,d,n) #(a**d) % n 197 | if (x==1) or (x == n-1): 198 | continue 199 | b = False 200 | for j in range(s-1): 201 | x = pow(x,2,n) #x**2 % n 202 | if (x == n-1): 203 | b = True 204 | break 205 | if (b): 206 | continue 207 | return False 208 | return True 209 | 210 | def Cows(n): 211 | """Return the first n Narayana's cows numbers""" 212 | cows = [1,1,1] 213 | if (n > 0) and (n < 4): 214 | return cows[:n] 215 | while (len(cows) < n): 216 | cows.append(cows[-1] + cows[-3]) 217 | return cows 218 | 219 | def isRuthAaron(n): 220 | """Is the sum of the prime factors of n equal to those of n+1?""" 221 | return sum(PrimeFactors(n,only=True)) == sum(PrimeFactors(n+1,only=True)) 222 | 223 | def HighlyComposite(n): 224 | """Return a list of highly composite numbers up to n""" 225 | if (n < 2): 226 | return [1] 227 | dv = [len(Divisors(i)) for i in range(1,n+1)] 228 | hc = [1] 229 | for k in range(1,len(dv)): 230 | if (dv[k] > max(dv[:k])): 231 | hc.append(k+1) 232 | return hc 233 | 234 | def Factorial(n): 235 | """Iterative factorial""" 236 | if (n < 2): 237 | return 1 238 | f = 1 239 | while (n > 0): 240 | n,f = n-1, f*n 241 | return f 242 | 243 | from decimal import * 244 | def Subfactorial(n): 245 | """Return !n, the number of derangements""" 246 | getcontext().prec = 1000 247 | return int((Decimal(Factorial(n)) / Decimal(1).exp()).to_integral_exact()) 248 | 249 | def Goldbach(n): 250 | """Return all the unique ways to write n as the sum of two primes""" 251 | primes = Primes(n) 252 | ans = [] 253 | for p in primes: 254 | for q in primes: 255 | if (p < q): 256 | continue 257 | if ((p+q) == n): 258 | ans.append((p,q)) 259 | return ans 260 | 261 | def NaiveModularInverse(a,n): 262 | """Naive search for the inverse of a mod n""" 263 | if (GCD(a,n) != 1): 264 | return None 265 | for i in range(0,n-1): 266 | if ((a*i) % n) == 1: 267 | return i 268 | 269 | def ModularInverse(a,n): 270 | """Use the extended Euclidean algorithm to find the inverse of a""" 271 | g,s,t = ExtendedGCD(a,n) 272 | return None if (g!=1) else s 273 | 274 | # end ntheory.py 275 | 276 | -------------------------------------------------------------------------------- /ch08/combination_plot.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | from combinatorics import * 3 | import sys 4 | 5 | n = int(sys.argv[1]) 6 | r = range(1,n) 7 | m = [nCr(n,r) for r in r] 8 | plt.bar(r,m, fill=False, color='k', width=0.9) 9 | plt.xlabel("$r$") 10 | plt.ylabel("$C(%d,r)$" % n) 11 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 12 | plt.savefig("combination_plot.png", dpi=300) 13 | plt.savefig("combination_plot.eps", dpi=300) 14 | plt.close() 15 | 16 | -------------------------------------------------------------------------------- /ch08/combinatorics.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: combinatorics.py 3 | # 4 | # Python combinatorics functions 5 | # 6 | # RTK, 28-Feb-2023 7 | # Last update: 04-Jun-2023 8 | # 9 | ################################################################ 10 | 11 | import random 12 | 13 | def Factorial(n): 14 | """Recursive factorial""" 15 | if (n < 2): 16 | return 1 17 | else: 18 | return n * Factorial(n-1) 19 | 20 | 21 | def Fact(n): 22 | """Iterative factorial""" 23 | fact = 1 24 | while (n>0): 25 | fact *= n 26 | n -= 1 27 | return fact 28 | 29 | 30 | def PowerSet(A): 31 | """Return the power set of a list""" 32 | def zbin(v,n): 33 | return format(v,'b').zfill(n) 34 | 35 | pset = [[]] 36 | n = len(A) 37 | for k in range(1,2**len(A)): 38 | s = zbin(k,n) 39 | p = [] 40 | for i,c in enumerate(s): 41 | if (c=="1"): 42 | p.append(A[i]) 43 | pset.append(p) 44 | return pset 45 | 46 | 47 | def nPr(n,r): 48 | """Return the number of permutations of n things taken r at a time""" 49 | return Fact(n) // Fact(n-r) 50 | 51 | def P(n,r): 52 | """Use a reduction to find P(n,r)""" 53 | from functools import reduce 54 | return reduce(lambda x,y: x*y, range(n,n-r,-1)) 55 | 56 | def nCr(n,r): 57 | """Return the number of combinations of n things taken r at a time""" 58 | return nPr(n,r) // Fact(r) 59 | 60 | 61 | def Combinations(A,m): 62 | """Return all combinations of the elements of A taken m at a time""" 63 | comb = [] 64 | n = len(A) 65 | f = lambda a,b: format(a,'b').zfill(b) 66 | for k in range(2**len(A)): 67 | s = f(k,n) 68 | if (m == len([i for i in s if i=='1'])): 69 | p = [] 70 | for i,c in enumerate(s): 71 | if (c=="1"): 72 | p.append(A[i]) 73 | comb.append(p) 74 | return comb 75 | 76 | 77 | def Permutations(A): 78 | """Return a list of the permutations of the elements of A""" 79 | def heap(k,A): 80 | if (k==1): 81 | perms.append(A[:]) 82 | else: 83 | heap(k-1,A) 84 | for i in range(k-1): 85 | if (k%2 == 0): 86 | A[i], A[k-1] = A[k-1], A[i] 87 | else: 88 | A[0], A[k-1] = A[k-1], A[0] 89 | heap(k-1,A) 90 | 91 | perms = [] 92 | heap(len(A),A) 93 | return perms 94 | 95 | 96 | def Perms(A,m): 97 | """Return all permutations of A taken m elements at a time""" 98 | 99 | perm = [] 100 | n = len(A) 101 | for k in range(2**len(A)): 102 | s = (lambda a,b: format(a,'b').zfill(b))(k,n) 103 | if (m == len([i for i in s if i=='1'])): 104 | p = [] 105 | for i,c in enumerate(s): 106 | if (c=="1"): 107 | p.append(A[i]) 108 | for t in Permutations(p): 109 | perm.append(t) 110 | return perm 111 | 112 | 113 | def Pigeon0(n): 114 | """Select n+1 random natural numbers, return any whose difference is divisible by n""" 115 | ans = [] 116 | m = [random.randint(1,99) for i in range(n+1)] 117 | for i in range(len(m)): 118 | for j in range(len(m)): 119 | if ((m[i]-m[j]) % n == 0) and (m[i] > m[j]): 120 | ans.append((m[i],m[j])) 121 | return ans 122 | 123 | 124 | def Pigeon1(): 125 | """Select 10 random natural numbers and return a consecutive set that sum to a multiple of ten""" 126 | m = [random.randint(1,99) for i in range(10)] 127 | s = [sum(m[:i]) for i in range(1,11)] 128 | for i in range(9): 129 | for j in range(i+1,10): 130 | if ((s[j] - s[i]) % 10 == 0): 131 | t = m[(i+1):(j+1)] 132 | return s[j], s[i], j, i, sum(t), t 133 | 134 | 135 | def ProbPigeon(m=10, n=5): 136 | """Probabilistic pigeonhole principle""" 137 | return 1.0 - Fact(m) / (Fact(m-n)*m**n) 138 | 139 | 140 | def ProbPigeonSim(trials, m=10, n=5): 141 | """Simulate the probabilistic pigeonhole principle placing n items in m boxes""" 142 | count = 0 143 | for t in range(trials): 144 | boxes = [0]*m 145 | for k in range(n): 146 | boxes[random.randint(0,m-1)] += 1 147 | if (max(boxes) > 1): 148 | count += 1 149 | return count / trials 150 | 151 | 152 | # end combinatorics.py 153 | 154 | -------------------------------------------------------------------------------- /ch08/heap.c: -------------------------------------------------------------------------------- 1 | // Permutation generation 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | uint8_t *A; 8 | uint64_t count = 0; 9 | int n; 10 | 11 | void pp(uint8_t *A) { 12 | count++; 13 | return; 14 | //for(int i=0; i0): 25 | fact *= n 26 | n -= 1 27 | return fact 28 | 29 | 30 | def PowerSet(A): 31 | """Return the power set of a list""" 32 | def zbin(v,n): 33 | return format(v,'b').zfill(n) 34 | 35 | pset = [[]] 36 | n = len(A) 37 | for k in range(1,2**len(A)): 38 | s = zbin(k,n) 39 | p = [] 40 | for i,c in enumerate(s): 41 | if (c=="1"): 42 | p.append(A[i]) 43 | pset.append(p) 44 | return pset 45 | 46 | 47 | def nPr(n,r): 48 | """Return the number of permutations of n things taken r at a time""" 49 | return Fact(n) // Fact(n-r) 50 | 51 | def P(n,r): 52 | """Use a reduction to find P(n,r)""" 53 | from functools import reduce 54 | return reduce(lambda x,y: x*y, range(n,n-r,-1)) 55 | 56 | def nCr(n,r): 57 | """Return the number of combinations of n things taken r at a time""" 58 | return nPr(n,r) // Fact(r) 59 | 60 | 61 | def Combinations(A,m): 62 | """Return all combinations of the elements of A taken m at a time""" 63 | comb = [] 64 | n = len(A) 65 | f = lambda a,b: format(a,'b').zfill(b) 66 | for k in range(2**len(A)): 67 | s = f(k,n) 68 | if (m == len([i for i in s if i=='1'])): 69 | p = [] 70 | for i,c in enumerate(s): 71 | if (c=="1"): 72 | p.append(A[i]) 73 | comb.append(p) 74 | return comb 75 | 76 | 77 | def Permutations(A): 78 | """Return a list of the permutations of the elements of A""" 79 | def heap(k,A): 80 | if (k==1): 81 | perms.append(A[:]) 82 | else: 83 | heap(k-1,A) 84 | for i in range(k-1): 85 | if (k%2 == 0): 86 | A[i], A[k-1] = A[k-1], A[i] 87 | else: 88 | A[0], A[k-1] = A[k-1], A[0] 89 | heap(k-1,A) 90 | 91 | perms = [] 92 | heap(len(A),A) 93 | return perms 94 | 95 | 96 | def Perms(A,m): 97 | """Return all permutations of A taken m elements at a time""" 98 | 99 | perm = [] 100 | n = len(A) 101 | for k in range(2**len(A)): 102 | s = (lambda a,b: format(a,'b').zfill(b))(k,n) 103 | if (m == len([i for i in s if i=='1'])): 104 | p = [] 105 | for i,c in enumerate(s): 106 | if (c=="1"): 107 | p.append(A[i]) 108 | for t in Permutations(p): 109 | perm.append(t) 110 | return perm 111 | 112 | 113 | def Pigeon0(n): 114 | """Select n+1 random natural numbers, return any whose difference is divisible by n""" 115 | ans = [] 116 | m = [random.randint(1,99) for i in range(n+1)] 117 | for i in range(len(m)): 118 | for j in range(len(m)): 119 | if ((m[i]-m[j]) % n == 0) and (m[i] > m[j]): 120 | ans.append((m[i],m[j])) 121 | return ans 122 | 123 | 124 | def Pigeon1(): 125 | """Select 10 random natural numbers and return a consecutive set that sum to a multiple of ten""" 126 | m = [random.randint(1,99) for i in range(10)] 127 | s = [sum(m[:i]) for i in range(1,11)] 128 | for i in range(9): 129 | for j in range(i+1,10): 130 | if ((s[j] - s[i]) % 10 == 0): 131 | t = m[(i+1):(j+1)] 132 | return s[j], s[i], j, i, sum(t), t 133 | 134 | 135 | def ProbPigeon(m=10, n=5): 136 | """Probabilistic pigeonhole principle""" 137 | return 1.0 - Fact(m) / (Fact(m-n)*m**n) 138 | 139 | 140 | def ProbPigeonSim(trials, m=10, n=5): 141 | """Simulate the probabilistic pigeonhole principle placing n items in m boxes""" 142 | count = 0 143 | for t in range(trials): 144 | boxes = [0]*m 145 | for k in range(n): 146 | boxes[random.randint(0,m-1)] += 1 147 | if (max(boxes) > 1): 148 | count += 1 149 | return count / trials 150 | 151 | 152 | # end combinatorics.py 153 | 154 | -------------------------------------------------------------------------------- /ch09/dfs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkneusel9/MathForProgramming/ae6d990fa612dff8b39e6806923deae566c0ff1d/ch09/dfs.gif -------------------------------------------------------------------------------- /ch09/graphs.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: graphs.py 3 | # 4 | # Graph routines 5 | # 6 | # RTK, 21-Jun-2023 7 | # Last update: 16-Jul-2023 8 | # 9 | ################################################################ 10 | 11 | # N.B. directed graphs must have positive weights > 0 (this is just a pedagogical library, after all) 12 | 13 | # Three isomorphic graphs, first is left of Figure 9-1 14 | A = [{1,2},{0,3,4},{0,5},{1},{1,5},{2,4}] 15 | B = [{3},{4,5},{3,4},{0,2,5},{1,2},{1,3}] 16 | C = [{1,2,5},{0,3},{0},{1,4},{3,5},{0,4}] 17 | 18 | # A graph with the same number of nodes that is not isomorphic to A,B,C 19 | D = [{1},{0,4},{4,5},{4},{1,2,3},{2}] 20 | 21 | # A directed graph (right of Figure 9-1) 22 | E = [{1},{3},{0,5},set(),{1,5},set()] 23 | 24 | # Weighted undirected graph (weighted version of A) 25 | F = [{(1,3),(2,2)}, {(0,3),(3,2),(4,6)}, {(0,2),(5,5)}, {(1,3)}, {(1,6),(5,2)}, {(2,5),(4,2)}] 26 | 27 | # Weighted directed graph (weighted version of E) 28 | G = [{(1,3)},{(3,2)},{(0,2),(5,5)},set(),{(1,6),(5,2)},set()] 29 | 30 | # Star graph 31 | S = [{1,2,3,4},{0,5},{0,6},{0,7},{0,8},{1,9},{2,10},{3,11},{4,12},{5},{6},{7},{8}] 32 | 33 | # Shortest path examples 34 | U = [{1,2,3},{0,4},{0,5},{0,5,6},{1,6},{2,3,7},{3,4,7},{5,6}] # undirected, unweighted 35 | T = [{1,2,3},set(),set(),{5,6},{1,6},{2,7},{7},set()] # directed, unweighted 36 | 37 | # undirected, weighted 38 | W = [{(1,2),(2,3),(3,2)},{(0,2),(4,1)},{(0,3),(5,7)},{(0,2),(5,4),(6,3)},{(1,1),(6,1)},{(2,7),(3,4),(7,9)},{(3,3),(4,1),(7,3)},{(5,9),(6,3)}] 39 | 40 | # directed, weighted 41 | V = [{(1,2),(2,3),(3,2)},set(),set(),{(5,4),(6,3)},{(1,1),(6,1)},{(2,7),(7,9)},{(7,3)},set()] 42 | 43 | # Topological sorting 44 | dress = [{6},{3},{4,5,9},{4,6},set(),{7},set(),set(),set(),set()] 45 | cycle = [{1,2},{4},{3,4},{0,4},set()] # 0 -> 2 -> 3 -> 0 46 | nocycle = [{1,2},{4},{4},{0,4},set()] # remove 2 -> 3 edge 47 | 48 | # Utility functions 49 | def ListToMatrix(lst): 50 | """Convert an adjacency list to a matrix""" 51 | n = len(lst) 52 | mat = [] 53 | [mat.append([0]*n) for i in range(n)] 54 | for i in range(n): 55 | for j in lst[i]: 56 | if type(j) is tuple: 57 | if (j[0] is None): 58 | continue 59 | mat[i][j[0]] = j[1] 60 | else: 61 | if (j is None): 62 | continue 63 | mat[i][j] = 1 64 | return mat 65 | 66 | def MatrixToList(mat, force=False): 67 | """Convert an adjacency matrix to a list, force weighted if True""" 68 | n = len(mat) 69 | lst = [] 70 | for i in range(n): 71 | l = set() 72 | for j in range(n): 73 | v = mat[i][j] 74 | if (v == 1): 75 | if (force): 76 | l.add((j,1)) 77 | else: 78 | l.add(j) 79 | elif (v > 1): 80 | l.add((j,v)) 81 | lst.append(l) 82 | return lst 83 | 84 | 85 | # 86 | # Traversals only 87 | # 88 | def DepthFirst(graph, node, visited=None, weighted=False): 89 | """Depth-first traversal of a graph""" 90 | if visited is None: 91 | visited = [] 92 | visited.append(node) 93 | if (weighted): 94 | # weighted 95 | for neighbor,weight in graph[node]: 96 | if neighbor not in visited: 97 | DepthFirst(graph, neighbor, visited=visited, weighted=weighted) 98 | else: 99 | # unweighted 100 | for neighbor in graph[node]: 101 | if neighbor not in visited: 102 | DepthFirst(graph, neighbor, visited=visited, weighted=weighted) 103 | return visited 104 | 105 | 106 | def BreadthFirst(graph, start, weighted=False): 107 | """Breadth-first traveral of a graph""" 108 | visited, queue = [start], [start] 109 | while queue: 110 | node = queue.pop(0) 111 | if (weighted): 112 | # weighted 113 | for neighbor,weight in graph[node]: 114 | if neighbor not in visited: 115 | visited.append(neighbor) 116 | queue.append(neighbor) 117 | else: 118 | # unweighted 119 | for neighbor in graph[node]: 120 | if neighbor not in visited: 121 | visited.append(neighbor) 122 | queue.append(neighbor) 123 | return visited 124 | 125 | # 126 | # Searches (unweighted graphs only): 127 | # 128 | 129 | # Node data for graphs A-E 130 | people = { 131 | 0: ['Drofo', 'hafling'], 132 | 1: ['Aranorg', 'human'], 133 | 2: ['Yowen', 'human'], 134 | 3: ['Fangald', 'wizard'], 135 | 4: ['Lelogas', 'elf'], 136 | 5: ['Milgi', 'dwarf'], 137 | } 138 | 139 | def DepthFirstSearch(graph, node, visited=None, name=None, data=None): 140 | """Depth-first search of a graph for a name""" 141 | if (visited is None): 142 | visited= [] 143 | if data[node][0] == name: 144 | return True, data[node][1] 145 | visited.append(node) 146 | for neighbor in graph[node]: 147 | if neighbor not in visited: 148 | found, type = DepthFirstSearch(graph, neighbor, visited=visited, name=name, data=data) 149 | if (found): 150 | return found, type 151 | return False, None 152 | 153 | 154 | def BreadthFirstSearch(graph, start, name=None, data=None): 155 | """Breadth-first search of a graph for a name""" 156 | visited, queue = [start], [start] 157 | while queue: 158 | node = queue.pop(0) 159 | if (data[node][0] == name): 160 | return True, data[node][1] 161 | for neighbor in graph[node]: 162 | if neighbor not in visited: 163 | visited.append(neighbor) 164 | queue.append(neighbor) 165 | return False, None 166 | 167 | 168 | # 169 | # Paths: 170 | # 171 | def ShortestPath(graph, start, end): 172 | """BFS to locate the shortest path in an unweighted graph""" 173 | visited, queue = [start], [[start]] 174 | while queue: 175 | path = queue.pop(0) 176 | node = path[-1] 177 | if (node == end): 178 | return path 179 | for neighbor in graph[node]: 180 | if neighbor not in visited: 181 | visited.append(neighbor) 182 | queue.append(path + [neighbor]) 183 | return [] 184 | 185 | 186 | def Dijkstra(graph, start, end=None): 187 | """Find the shortest path between two nodes in a weighted graph""" 188 | 189 | def AllPaths(shortest_path): 190 | paths = [] 191 | for i in range(len(shortest_path)): 192 | path = [] 193 | while i is not None: 194 | path.append(i) 195 | i = shortest_path[i] 196 | path.reverse() 197 | paths.append(path) 198 | return paths 199 | 200 | # distances from start to each node, updated as graph traversed 201 | googol = 1E100 # assume no distance greater than a googol 202 | n = len(graph) # number of nodes in the graph 203 | distances = [googol] * n 204 | distances[start] = 0 # distance to start from start 205 | 206 | # initialize a list to store the shortest path 207 | shortest_path = [None] * n 208 | 209 | # the set of unvisited nodes 210 | unvisited = {i for i in range(n)} 211 | current_node = start 212 | 213 | while True: 214 | # check all the neighbors of the current node 215 | for neighbor, weight in graph[current_node]: 216 | new_distance = distances[current_node] + weight 217 | 218 | # update the neighbor's distance if a shorter path is found 219 | if (new_distance < distances[neighbor]): 220 | distances[neighbor] = new_distance 221 | shortest_path[neighbor] = current_node 222 | 223 | # mark the current node as visited 224 | unvisited.remove(current_node) 225 | 226 | # all nodes considered or end node found 227 | if (end == None): 228 | if (not unvisited): 229 | break 230 | else: 231 | if (not unvisited) or (current_node == end): 232 | break 233 | 234 | # the next node is the unvisited node with the smallest distance 235 | k = [i for i in unvisited] 236 | d = [distances[i] for i in unvisited] 237 | current_node = k[d.index(min(d))] 238 | 239 | # return the path and total distance 240 | if (end == None): 241 | return AllPaths(shortest_path), distances 242 | else: 243 | path = [] 244 | while end is not None: 245 | path.append(end) 246 | end = shortest_path[end] 247 | path.reverse() 248 | if distances[path[-1]] < googol: 249 | return path, distances[path[-1]] 250 | return [], 0 251 | 252 | 253 | # 254 | # Topological sorting: 255 | # 256 | def TopologicalSort(graph, weighted=False): 257 | """Return a topological sort of a DAG (assumed valid)""" 258 | 259 | def DFS(graph, node, visited, result, weighted=False): 260 | visited.add(node) 261 | if (not weighted): 262 | for neighbor in graph[node]: 263 | if (neighbor not in visited): 264 | DFS(graph, neighbor, visited, result, weighted=weighted) 265 | else: 266 | for neighbor, weight in graph[node]: 267 | if (neighbor not in visited): 268 | DFS(graph, neighbor, visited, result, weighted=weighted) 269 | result.insert(0, node) 270 | 271 | visited = set() 272 | result = [] 273 | for node in range(len(graph)): 274 | if (node not in visited): 275 | DFS(graph, node, visited, result, weighted=weighted) 276 | return result 277 | 278 | 279 | # 280 | # Graph isomorphism: 281 | # 282 | def Isomorphic(g1, g2): 283 | """Brute force check if two undirected, unweighted graphs are isomorphic""" 284 | from combinatorics import Permutations 285 | 286 | # isomorphic graphs must have the same number of vertices 287 | if (len(g1) != len(g2)): 288 | return False 289 | 290 | # g2 as a sorted list of vertices with each 291 | # vertex list sorted as well 292 | g2_sorted = sorted([sorted(list(v)) for v in g2]) 293 | 294 | for perm in Permutations(list(range(len(g1)))): 295 | # map the vertices of g1 to the permutation 296 | mapping = [perm[i] for i in range(len(perm))] 297 | 298 | # rebuild g1 according to the mapping 299 | g1_perm = [] 300 | for v in range(len(g1)): 301 | t = [] 302 | for n in g1[v]: 303 | t.append(mapping[n]) 304 | g1_perm.append(sorted(t)) 305 | g1_perm.sort() 306 | 307 | # if the same, then there is a permutation that works 308 | if (g1_perm == g2_sorted): 309 | return True, perm 310 | 311 | # no permutation works, not isomorphic 312 | return False, None 313 | 314 | 315 | -------------------------------------------------------------------------------- /ch09/mystic.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: mystic.py 3 | # 4 | # Generate complete graph images, i.e., "mystic roses" 5 | # 6 | # RTK, 24-Jun-2024 7 | # Last update: 24-Jun-2024 8 | # 9 | ################################################################ 10 | 11 | import sys 12 | import numpy as np 13 | import matplotlib.pylab as plt 14 | 15 | if (len(sys.argv) == 1): 16 | print() 17 | print("mystic []") 18 | print() 19 | print(" - number of points (min 2)") 20 | print(" - optional output file base name (e.g. rose_4)") 21 | print() 22 | exit(0) 23 | 24 | N = int(sys.argv[1]) 25 | oname = "" if (len(sys.argv) < 3) else sys.argv[2] 26 | 27 | x = [] 28 | y = [] 29 | 30 | # place N points equidistant on the unit circle 31 | dt = 2*np.pi / N 32 | for i in range(N): 33 | x.append(np.cos(i*dt)) 34 | y.append(np.sin(i*dt)) 35 | 36 | # for each point, draw a segment to every other point 37 | fig, ax = plt.subplots() 38 | ax.axis('equal') 39 | for i in range(N): 40 | for j in range(N): 41 | if (i == j): 42 | continue 43 | plt.plot([x[i],x[j]],[y[i],y[j]], marker='o', linewidth=0.7, color='k') 44 | 45 | # generate and save the plot 46 | plt.axis('off') 47 | plt.axis('off') 48 | ax.get_xaxis().set_visible(False) 49 | ax.get_yaxis().set_visible(False) 50 | 51 | if (oname != ""): 52 | plt.savefig(oname+".png", dpi=500, bbox_inches='tight', pad_inches=0) 53 | plt.savefig(oname+".eps", dpi=300, bbox_inches='tight', pad_inches=0) 54 | 55 | plt.show() 56 | 57 | 58 | -------------------------------------------------------------------------------- /ch10/animals.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkneusel9/MathForProgramming/ae6d990fa612dff8b39e6806923deae566c0ff1d/ch10/animals.pkl -------------------------------------------------------------------------------- /ch10/trees.c: -------------------------------------------------------------------------------- 1 | // 2 | // file: trees.c 3 | // 4 | // A simple binary search tree example in C 5 | // 6 | // Compile with: gcc trees.c -o trees 7 | // 8 | // RTK, 17-Jul-2023 9 | // Last update: 17-Jul-2023 10 | // 11 | //////////////////////////////////////////////////////////////// 12 | #include 13 | #include 14 | 15 | typedef struct node { 16 | int key; 17 | char value; 18 | struct node *left; 19 | struct node *right; 20 | } node_t; 21 | 22 | node_t *new_node(int key, char value) { 23 | node_t *node = (node_t *)malloc(sizeof(node_t)); 24 | node->key = key; // assume malloc succeeded 25 | node->value = value; 26 | node->left = NULL; 27 | node->right = NULL; 28 | return node; 29 | } 30 | 31 | node_t *insert(node_t *node, int key, char value) { 32 | if (!node) return new_node(key, value); 33 | if (key < node->key) 34 | node->left = insert(node->left, key, value); 35 | else if (key > node->key) 36 | node->right = insert(node->right, key, value); 37 | return node; 38 | } 39 | 40 | node_t *search(node_t *root, int key) { 41 | if ((!root) || (root->key == key)) 42 | return root; 43 | if (root->key < key) 44 | return search(root->right, key); 45 | return search(root->left, key); 46 | } 47 | 48 | void pre_order(node_t *node) { 49 | if (!node) return; 50 | printf("%c ", node->value); 51 | pre_order(node->left); 52 | pre_order(node->right); 53 | } 54 | 55 | void in_order(node_t *node) { 56 | if (!node) return; 57 | in_order(node->left); 58 | printf("%c ", node->value); 59 | in_order(node->right); 60 | } 61 | 62 | void post_order(node_t *node) { 63 | if (!node) return; 64 | post_order(node->left); 65 | post_order(node->right); 66 | printf("%c ", node->value); 67 | } 68 | 69 | void delete(node_t *node) { 70 | if (!node) return; 71 | delete(node->left); 72 | delete(node->right); 73 | free(node); 74 | } 75 | 76 | 77 | void search_tree(node_t *root, int key) { 78 | node_t *result = search(root, key); 79 | if (!result) 80 | printf("Key %d not found\n", key); 81 | else 82 | printf("Key %d found, value = %c\n", key, result->value); 83 | } 84 | 85 | 86 | int main(void) { 87 | node_t *root = NULL; 88 | 89 | // build a tree 90 | root = insert(root, 5, '-'); 91 | insert(root, 3, '*'); 92 | insert(root, 7, '*'); 93 | insert(root, 1, '+'); 94 | insert(root, 4, 'b'); 95 | insert(root, 6, '4'); 96 | insert(root, 8, 'c'); 97 | insert(root, 0, '2'); 98 | insert(root, 2, 'a'); 99 | 100 | // search 101 | search_tree(root, 7); 102 | search_tree(root,11); 103 | search_tree(root, 6); 104 | search_tree(root,10); 105 | search_tree(root, 3); 106 | printf("\n"); 107 | 108 | // pre-order 109 | printf("pre-order : "); 110 | pre_order(root); 111 | printf("\n"); 112 | 113 | // in-order 114 | printf("in-order : "); 115 | in_order(root); 116 | printf("\n"); 117 | 118 | // post-order 119 | printf("post-order: "); 120 | post_order(root); 121 | printf("\n\n"); 122 | 123 | // destroy the tree (also post-order) 124 | delete(root); 125 | root = (node_t *)NULL; 126 | 127 | return 0; 128 | } 129 | 130 | -------------------------------------------------------------------------------- /ch10/trees.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: trees.py 3 | # 4 | # Tree algorithms (binary search trees) 5 | # 6 | # RTK, 17-Jul-2023 7 | # Last update: 12-Aug-2023 8 | # 9 | ################################################################ 10 | 11 | # 12 | # Math section (graphs ala Chapter 9): 13 | # 14 | 15 | # DFS to locate a spanning tree (G is Fig 9-1, W is G with weights) 16 | G = [{1,2}, {0,3,4}, {0,5}, {1}, {1,5}, {2,4}] 17 | W = [{(1,2),(2,1)}, {(0,2),(3,1),(4,4)}, {(0,1),(5,3)}, {(1,1)}, {(1,4),(5,2)}, {(2,3),(4,2)}] 18 | 19 | # Complete graphs 20 | K3 = [{1,2}, {0,2}, {0,1}] 21 | K4 = [{1,2,3}, {0,2,3}, {0,1,3}, {0,1,2}] 22 | K5 = [{1,2,3,4}, {0,2,3,4}, {0,1,3,4}, {0,1,2,4}, {0,1,2,3}] 23 | K7 = [{1,2,3,4,5,6},{0,2,3,4,5,6},{0,1,3,4,5,6},{0,1,2,4,5,6},{0,1,2,3,5,6},{0,1,2,3,4,6},{0,1,2,3,4,5}] 24 | 25 | def SpanningTree(graph, start=0): 26 | """Return a spanning tree for an unweighted graph""" 27 | def DFS(graph, vertex, visited, spanning): 28 | visited[vertex] = True 29 | for neighbor in graph[vertex]: 30 | if not visited[neighbor]: 31 | spanning[vertex].add(neighbor) 32 | spanning[neighbor].add(vertex) 33 | DFS(graph, neighbor, visited, spanning) 34 | 35 | n = len(graph) 36 | visited = [False] * n 37 | spanning = [set() for i in range(n)] 38 | DFS(graph, start, visited, spanning) 39 | return spanning 40 | 41 | 42 | # 43 | # Kirchhoff's Matrix-Tree theorem 44 | # 45 | def NumberOfSpanningTrees(graph): 46 | """Return the number of spanning trees in a graph""" 47 | 48 | def LaplacianMatrix(graph): 49 | """Generate the Laplacian matrix as a list of lists""" 50 | n = len(graph) 51 | L = [[0] * n for i in range(n)] # n by n 52 | for i in range(n): 53 | L[i][i] = len(graph[i]) 54 | for j in graph[i]: 55 | L[i][j] = -1 56 | return L 57 | 58 | def Determinant(matrix): 59 | """Recursively calculate the determinant of a square matrix""" 60 | n = len(matrix) 61 | if (n == 1): 62 | return matrix[0][0] 63 | det = 0 64 | for c in range(n): 65 | submatrix = [row[:c] + row[c+1:] for row in matrix[1:]] 66 | sign = 1 if c % 2 == 0 else -1 67 | det += sign * matrix[0][c] * Determinant(submatrix) 68 | return det 69 | 70 | L = LaplacianMatrix(graph) 71 | L_prime = [row[:-1] for row in L[:-1]] 72 | return Determinant(L_prime) 73 | 74 | 75 | # 76 | # Prim's MST 77 | # 78 | def Prim(graph): 79 | """Return the minimum spanning tree for the given weighted graph""" 80 | n = len(graph) # number of nodes 81 | visited = [False]*n # visited nodes 82 | googol = 1e100 # 'infinity' 83 | dist = [googol]*n # track smallest weights (ala Dijkstra in Ch 9) 84 | parent = [None]*n # parent of each node (to build the MST) 85 | 86 | # Start with node 0 87 | dist[0] = 0 88 | 89 | for i in range(n): 90 | # Find the unvisited node with the minimum distance 91 | m, u = googol, 0 92 | for v in range(n): 93 | if (not visited[v]) and (dist[v] < m): 94 | m, u = dist[v], v 95 | 96 | # Mark the vertex as visited 97 | visited[u] = True 98 | 99 | # Examine the neighbors of u 100 | for neighbor, weight in graph[u]: 101 | if (not visited[neighbor]) and (weight < dist[neighbor]): 102 | dist[neighbor] = weight 103 | parent[neighbor] = u 104 | 105 | # Build the MST as an adjacency list 106 | mst = [set() for i in range(n)] 107 | for v in range(1, n): 108 | u = parent[v] 109 | for i,w in graph[v]: 110 | if (i == u): 111 | weight = w 112 | mst[u].add((v, weight)) # undirected graphs have edges from 113 | mst[v].add((u, weight)) # u to v and v to u 114 | return mst 115 | 116 | 117 | # 118 | # Code section: 119 | # 120 | 121 | # A binary tree node 122 | class Node: 123 | """A single node""" 124 | def __init__(self, key, value=None): 125 | self.key = key 126 | self.value = value 127 | self.left = None 128 | self.right = None 129 | 130 | # Add to the tree 131 | def Insert(key, value=None, node=None): 132 | if (key < node.key): 133 | if (node.left is None): 134 | node.left = Node(key, value) 135 | else: 136 | Insert(key, value, node=node.left) 137 | else: 138 | if (node.right is None): 139 | node.right = Node(key, value) 140 | else: 141 | Insert(key, value, node=node.right) 142 | 143 | # Find a key in the tree 144 | def Search(key, node=None): 145 | if (key < node.key): 146 | if (node.left is None): 147 | return False, None 148 | return Search(key, node=node.left) 149 | elif (key > node.key): 150 | if (node.right is None): 151 | return False, None 152 | return Search(key, node=node.right) 153 | else: 154 | return True, node.value 155 | 156 | # Traversals 157 | def Preorder(root, leaves=False): 158 | """Pre-order traversal""" 159 | def preorder(node, traversal, leaves=False): 160 | """node > left > right""" 161 | if (node): 162 | if (leaves): 163 | if (node.left is None) and (node.right is None): 164 | traversal.append(node.value if node.value is not None else node.key) 165 | else: 166 | traversal.append(node.value if node.value is not None else node.key) 167 | preorder(node.left, traversal, leaves=leaves) 168 | preorder(node.right, traversal, leaves=leaves) 169 | return traversal 170 | traversal = [] 171 | preorder(root, traversal, leaves=leaves) 172 | return traversal 173 | 174 | def Inorder(root): 175 | """In-order traversal""" 176 | def inorder(node, traversal): 177 | """left > node > right""" 178 | if (node): 179 | inorder(node.left, traversal) 180 | traversal.append(node.value if node.value is not None else node.key) 181 | inorder(node.right, traversal) 182 | return traversal 183 | traversal = [] 184 | inorder(root, traversal) 185 | return traversal 186 | 187 | def Postorder(root): 188 | """Post-order traversal""" 189 | def postorder(node, traversal): 190 | """left > right > node""" 191 | if (node): 192 | postorder(node.left, traversal) 193 | postorder(node.right, traversal) 194 | traversal.append(node.value if node.value is not None else node.key) 195 | return traversal 196 | traversal = [] 197 | postorder(root, traversal) 198 | return traversal 199 | 200 | def BreadthFirst(root): 201 | queue = [root] 202 | traversal = [] 203 | while (queue): 204 | node = queue.pop(0) 205 | traversal.append(node.value if node.value is not None else node.key) 206 | if (node.left): 207 | queue.append(node.left) 208 | if (node.right): 209 | queue.append(node.right) 210 | return traversal 211 | 212 | def Leaves(root): 213 | return Preorder(root, leaves=True) 214 | 215 | # Build a binary search tree from data 216 | def BuildTree(n): 217 | """Create a tree from a list""" 218 | if (type(n[0]) is tuple) or (type(n[0]) is list): 219 | tree = Node(n[0][0],n[0][1]) 220 | [Insert(i[0],i[1],tree) for i in n[1:]] 221 | else: 222 | tree = Node(n[0]) 223 | [Insert(i, node=tree) for i in n[1:]] 224 | return tree 225 | 226 | # Build an abstract syntax tree by hand 227 | ast = Node("-") 228 | ast.left = Node("*") 229 | ast.left.left = Node("+") 230 | ast.left.left.left = Node("2") 231 | ast.left.left.right = Node("a") 232 | ast.left.right = Node("b") 233 | ast.right = Node("*") 234 | ast.right.left = Node("4") 235 | ast.right.right = Node("c") 236 | 237 | # Evaluating an AST using post-order traversal and a stack 238 | def Evaluate(ast, a=1, b=2, c=3): 239 | """Evaluate an ast using a stack""" 240 | stack = [] 241 | for token in Postorder(ast): 242 | if (token in ["+","-","*","/"]): 243 | y,x = stack.pop(), stack.pop() 244 | stack.append(eval(str(x) + token + str(y))) 245 | else: 246 | stack.append(eval(token)) 247 | print(stack[-1]) 248 | 249 | # 250 | # Animals game 251 | # 252 | def Animals(root=None): 253 | """Play a game of animals""" 254 | def PlayRound(node, parent=None): 255 | learned = False 256 | if (node.left is None) and (node.right is None): 257 | # Leaf nodes are animal guesses 258 | answer = input("Is it a " + node.key + "? (yes/no): ").lower() 259 | if (answer == "yes"): 260 | print("I won!") 261 | else: 262 | # Computer lost, get new animal info 263 | learned = True 264 | animal = input("I give up. What animal were you thinking of? ") 265 | question = input("What question would distinguish a " + animal + " from a " + node.key + "? ") 266 | answer = input("What is the correct answer for a " + animal + "? (yes/no) ").lower() 267 | animal_node = Node(animal) 268 | question_node = Node(question) 269 | question_node.left = animal_node if (answer == "yes") else node 270 | question_node.right = node if (answer == "yes") else animal_node 271 | if (parent): 272 | if (parent.left == node): 273 | parent.left = question_node 274 | else: 275 | parent.right = question_node 276 | return question_node, learned 277 | else: 278 | # Non-leaf nodes are questions 279 | answer = input(node.key + " (yes/no): ").lower() 280 | if (answer == "yes"): 281 | node.left, learned = PlayRound(node.left, node) 282 | else: 283 | node.right, learned = PlayRound(node.right, node) 284 | return node, learned 285 | 286 | # Play the game 287 | if (root is None): 288 | root = Node("mosquito") 289 | while (True): 290 | input("Think of an animal and press 'enter' when ready... ") 291 | root, learned = PlayRound(root) 292 | if (input("Play again? (yes/no): ").lower() != "yes"): 293 | break 294 | if (learned): 295 | print("I now know %d animals!" % len(Leaves(root))) 296 | print("Goodbye!") 297 | return root # return the tree 298 | 299 | # end trees.py 300 | 301 | -------------------------------------------------------------------------------- /ch11/bernoulli.py: -------------------------------------------------------------------------------- 1 | # Generate Bernoulli samples 2 | import numpy as np 3 | 4 | def bernoulli(p, size=1): 5 | """Samples from a Bernoulli distribution""" 6 | n = np.random.random(size) 7 | return (n < p).astype("uint8") 8 | 9 | -------------------------------------------------------------------------------- /ch11/central.py: -------------------------------------------------------------------------------- 1 | # Central limit theorem 2 | import numpy as np 3 | import matplotlib.pylab as plt 4 | 5 | def sequential(v): 6 | """Sequential search""" 7 | probs = v / v.sum() 8 | k = 0 9 | u = np.random.random() 10 | while u > 0: 11 | u -= probs[k] 12 | k += 1 13 | return k-1 14 | 15 | # Beta(5,2) plot 16 | #n = np.random.beta(5,2, size=100_000_000) 17 | #h,x = np.histogram(n, bins=1000) 18 | #h = h / h.sum() 19 | #x = 0.5*(x[1:] + x[:-1]) 20 | #plt.plot(x,h, color='k') 21 | #plt.xlabel("$x$") 22 | #plt.ylabel("$y$") 23 | #plt.tight_layout(pad=0, w_pad=0, h_pad=0) 24 | #plt.savefig("beta.eps", dpi=300) 25 | #plt.savefig("beta.png", dpi=300) 26 | 27 | # Example 1 -- an arbitrary discrete distribution to estimate the 28 | # distribution mean 29 | dm = (2*0 + 4*1 + 6*2 + 1*3 + 9*4) / (2+4+6+1+9) 30 | 31 | print("Example 1 -- mean of an arbitrary discrete distribution:") 32 | print() 33 | print(" CLT LLN") 34 | v = np.array([2,4,6,1,9]) 35 | m = [] 36 | for i in range(3): 37 | n = np.array([sequential(v) for i in range(40)]) 38 | m.append(n.mean()) 39 | m = np.array(m) 40 | t = np.array([sequential(v) for i in range(40*3)]) 41 | d0= np.abs(m.mean()-dm) 42 | d1= np.abs(t.mean()-dm) 43 | print("Mean of the means (n= 3) is %0.4f (%0.4f), %0.4f (%0.4f)" % (m.mean(), d0, t.mean(), d1), d0 2 | #include 3 | #include 4 | 5 | uint32_t seed; 6 | uint32_t xorshift32(void) { 7 | seed ^= (seed<<13); 8 | seed ^= (seed>>17); 9 | seed ^= (seed<< 5); 10 | return seed; 11 | } 12 | 13 | double xorshift(void) { 14 | return xorshift32() / (double)0xFFFFFFFF; 15 | } 16 | 17 | 18 | int main(int argc, char *argv[]) { 19 | int i,c=0, n=1000000; 20 | double r; 21 | 22 | // seed xorshift 23 | seed = (uint32_t)time(NULL); 24 | 25 | // simulate 26 | for(i=0; i < n; i++) { 27 | r = xorshift(); 28 | if (r < 0.75) { 29 | if (xorshift() < 0.02) c++; 30 | } else { 31 | if (r < 0.98) { 32 | if (xorshift() < 0.03) c++; 33 | } else { 34 | if (xorshift() < 0.10) c++; 35 | } 36 | } 37 | } 38 | 39 | printf("probability of priestly class = %0.5f\n", c/(double)n); 40 | 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /ch11/egypt.py: -------------------------------------------------------------------------------- 1 | # Simulate the Thebes, Tanis, Abydos example 2 | from random import random 3 | 4 | c, n = 0, 1_000_000 5 | for i in range(n): 6 | r = random() 7 | if (r < 0.75): 8 | if (random() < 0.02): c += 1 9 | elif (r < (0.75+0.23)): 10 | if (random() < 0.03): c += 1 11 | else: 12 | if (random() < 0.10): c += 1 13 | 14 | print("probability of priestly class = %0.5f" % (c/n)) 15 | 16 | -------------------------------------------------------------------------------- /ch11/flips.py: -------------------------------------------------------------------------------- 1 | # Test von Neumann's de-bias trick 2 | 3 | from random import random 4 | 5 | def Flip(p=0.7): 6 | return "H" if (random() < p) else "T" 7 | 8 | # show bias 9 | flips = [Flip(0.9) for i in range(100_000)] 10 | print("biased : H= %d, T= %d" % (flips.count('H'), flips.count('T'))) 11 | 12 | # de-bias 13 | raw = [(Flip(0.9), Flip(0.9)) for i in range(100_000)] 14 | flips = [i[0] for i in raw if i[0] != i[1]] 15 | print("unbiased: H= %d, T= %d" % (flips.count('H'), flips.count('T'))) 16 | 17 | -------------------------------------------------------------------------------- /ch11/ford.py: -------------------------------------------------------------------------------- 1 | # Simulate selecting a car from Crazy Ernie's lot 2 | from random import randint 3 | 4 | # 41 red Fords, 33 red Teslas, 37 blue Fords, 10 blue Teslas 5 | cars = [0]*41 + [1]*33 + [2]*37 + [3]*10 6 | 7 | c, n = 0, 100_000 8 | for i in range(n): 9 | k = randint(0,120) # pick a vehicle 10 | if (cars[k] != 3): c += 1 # red or a Ford 11 | 12 | print("Probability of red or Ford = %0.5f" % (c/n)) 13 | 14 | -------------------------------------------------------------------------------- /ch11/large.py: -------------------------------------------------------------------------------- 1 | # Law of large numbers 2 | import numpy as np 3 | import matplotlib.pylab as plt 4 | 5 | m = [] 6 | for n in np.linspace(1,8,30): 7 | t = np.random.normal(1,1,size=int(10**n)) 8 | m.append(t.mean()) 9 | 10 | plt.plot(np.linspace(1,8,30), m) 11 | plt.plot([1,8],[1,1], linestyle="--", color='k') 12 | plt.xlabel("Exponent $10^n$") 13 | plt.ylabel("Single sample mean") 14 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 15 | plt.savefig("large_numbers.eps", dpi=300) 16 | #plt.show() 17 | 18 | -------------------------------------------------------------------------------- /ch11/marbles.py: -------------------------------------------------------------------------------- 1 | # Simulate selecting marbles from a bag 2 | from random import randint 3 | red, blue = 11, 7 4 | n = 1_000_000 5 | 6 | # Independent events -- return the marble to the bag 7 | bag = [0]*blue + [1]*red 8 | c = 0 9 | for i in range(n): 10 | k = randint(0, len(bag)-1) 11 | if (bag[k] == 0): continue 12 | k = randint(0, len(bag)-1) 13 | if (bag[k] == 1): c += 1 14 | print("Probability of reds (independent): %0.5f" % (c/n)) 15 | 16 | # Dependent events -- pick a red, then pick red again 17 | c = 0 18 | for i in range(n): 19 | bag = [0]*blue + [1]*red 20 | k = randint(0, len(bag)-1) 21 | if (bag[k] == 0): continue 22 | bag.pop(k) 23 | k = randint(0, len(bag)-1) 24 | if (bag[k] == 1): c += 1 25 | print("Probability of reds (dependent) : %0.5f" % (c/n)) 26 | 27 | -------------------------------------------------------------------------------- /ch11/normal.py: -------------------------------------------------------------------------------- 1 | # Use the Box-Muller transform to sample from 2 | # a normal distribution 3 | import numpy as np 4 | 5 | def norm(mu=0, sigma=1): 6 | if (norm.state): 7 | norm.state = False 8 | return sigma*norm.z2 + mu 9 | else: 10 | u1,u2 = np.random.random(2) 11 | m = np.sqrt(-2.0*np.log(u1)) 12 | z1 = m*np.cos(2*np.pi*u2) 13 | norm.z2 = m*np.sin(2*np.pi*u2) 14 | norm.state = True 15 | return sigma*z1 + mu 16 | norm.state = False 17 | 18 | def normal(mu=0, sigma=1, size=1): 19 | """Return samples from N(mu,sigma)""" 20 | return np.array([norm(mu,sigma) for i in range(size)]) 21 | 22 | 23 | def main(): 24 | """An example""" 25 | import matplotlib.pylab as plt 26 | 27 | mu, sigma, n = 5.0, 2.0, 10_000 28 | 29 | # plot PDF 30 | x = np.linspace(-3,15,1000) 31 | y = (1/np.sqrt(2*np.pi*2))*np.exp(-0.5*((x-mu)**2/sigma**2)) 32 | y = y / y.max() # force y.max = 1 33 | plt.plot(x,y, color='k', linewidth=0.7) 34 | 35 | # simulate with samples 36 | s = normal(mu, sigma, n) 37 | h,x = np.histogram(s, bins=60) 38 | x = 0.5*(x[1:] + x[:-1]) 39 | h = h / h.sum() 40 | h = h / h.max() # force h.max = 1 41 | plt.bar(x,h, width=0.8*(x[1]-x[0]), fill=False, color='k') 42 | 43 | plt.xlabel("$x$") 44 | plt.ylabel("$y$") 45 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 46 | plt.savefig("normal.eps", dpi=300) 47 | plt.show() 48 | 49 | if (__name__ == "__main__"): 50 | main() 51 | 52 | -------------------------------------------------------------------------------- /ch11/sequential.py: -------------------------------------------------------------------------------- 1 | # Sample an arbitrary discrete distribution via sequential search 2 | import numpy as np 3 | 4 | def sequential(v): 5 | """Sequential search""" 6 | probs = v / v.sum() 7 | k = 0 8 | u = np.random.random() 9 | while u > 0: 10 | u -= probs[k] 11 | k += 1 12 | return k-1 13 | 14 | -------------------------------------------------------------------------------- /ch12/anscombe.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | from stats import * 4 | 5 | fig, ax = plt.subplots(2,2) 6 | 7 | ax[0,0].plot(x0,y0, marker='+', linestyle='none', color='k') 8 | ax[0,1].plot(x1,y1, marker='+', linestyle='none', color='k') 9 | ax[1,0].plot(x2,y2, marker='+', linestyle='none', color='k') 10 | ax[1,1].plot(x3,y3, marker='+', linestyle='none', color='k') 11 | 12 | for i in range(2): 13 | for j in range(2): 14 | ax[i,j].set_xlim((0,20)) 15 | ax[i,j].set_ylim((0,13)) 16 | 17 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 18 | plt.savefig("anscombe.eps", dpi=300) 19 | plt.close() 20 | 21 | d = np.zeros((11,4)) 22 | d[:,0] = y0 23 | d[:,1] = y1 24 | d[:,2] = y2 25 | d[:,3] = y3 26 | 27 | plt.boxplot(d, showmeans=True, labels=['$y_0$','$y_1$','$y_2$','$y_3$']) 28 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 29 | plt.savefig("anscombe_box.eps", dpi=300) 30 | plt.show() 31 | 32 | -------------------------------------------------------------------------------- /ch12/iris.py: -------------------------------------------------------------------------------- 1 | # CIs for dataset means 2 | from stats import * 3 | 4 | # Petal width (cm) and length (cm) for I. setosa 5 | width = [ 6 | 0.2, 0.2, 0.2, 0.2, 0.2, 0.4, 0.3, 0.2, 0.2, 0.1, 0.2, 0.2, 0.1, 7 | 0.1, 0.2, 0.4, 0.4, 0.3, 0.3, 0.3, 0.2, 0.4, 0.2, 0.5, 0.2, 0.2, 8 | 0.4, 0.2, 0.2, 0.2, 0.2, 0.4, 0.1, 0.2, 0.2, 0.2, 0.2, 0.1, 0.2, 9 | 0.2, 0.3, 0.3, 0.2, 0.6, 0.4, 0.3, 0.2, 0.2, 0.2, 0.2 10 | ] 11 | 12 | length = [ 13 | 1.4, 1.4, 1.3, 1.5, 1.4, 1.7, 1.4, 1.5, 1.4, 1.5, 1.5, 1.6, 1.4, 14 | 1.1, 1.2, 1.5, 1.3, 1.4, 1.7, 1.5, 1.7, 1.5, 1. , 1.7, 1.9, 1.6, 15 | 1.6, 1.5, 1.4, 1.6, 1.6, 1.5, 1.5, 1.4, 1.5, 1.2, 1.3, 1.4, 1.3, 16 | 1.5, 1.3, 1.3, 1.3, 1.6, 1.9, 1.4, 1.6, 1.4, 1.5, 1.4 17 | ] 18 | 19 | # Means and std dev 20 | wm, ws = amean(width), std(width) 21 | lm, ls = amean(length), std(length) 22 | 23 | # Critical values for 95th percentile CIs (alpha = 0.05) 24 | z = 1.96 25 | wt = t_critical(width,width, alpha=0.05, paired=True) 26 | lt = t_critical(length,length, alpha=0.05, paired=True) 27 | 28 | # CIs 29 | n = len(width) 30 | wtlo, wthi = wm-wt*ws/sqrt(n), wm+wt*ws/sqrt(n) 31 | wzlo, wzhi = wm-z*ws/sqrt(n), wm+z*ws/sqrt(n) 32 | ltlo, lthi = lm-lt*ls/sqrt(n), lm+lt*ls/sqrt(n) 33 | lzlo, lzhi = lm-z*ls/sqrt(n), lm+z*ls/sqrt(n) 34 | 35 | print("Width: (alpha = 0.05)") 36 | print(" t: [%0.5f, %0.3f, %0.5f]" % (wtlo,wm,wthi)) 37 | print(" z: [%0.5f, %0.3f, %0.5f]" % (wzlo,wm,wzhi)) 38 | print() 39 | print("Length:") 40 | print(" t: [%0.5f, %0.3f, %0.5f]" % (ltlo,lm,lthi)) 41 | print(" z: [%0.5f, %0.3f, %0.5f]" % (lzlo,lm,lzhi)) 42 | print() 43 | 44 | # Critical values for 99th percentile CIs (alpha = 0.01) 45 | z = 2.5758 46 | wt = t_critical(width,width, alpha=0.01, paired=True) 47 | lt = t_critical(length,length, alpha=0.01, paired=True) 48 | 49 | # CIs 50 | n = len(width) 51 | wtlo, wthi = wm-wt*ws/sqrt(n), wm+wt*ws/sqrt(n) 52 | wzlo, wzhi = wm-z*ws/sqrt(n), wm+z*ws/sqrt(n) 53 | ltlo, lthi = lm-lt*ls/sqrt(n), lm+lt*ls/sqrt(n) 54 | lzlo, lzhi = lm-z*ls/sqrt(n), lm+z*ls/sqrt(n) 55 | 56 | print("Width: (alpha = 0.01)") 57 | print(" t: [%0.5f, %0.3f, %0.5f]" % (wtlo,wm,wthi)) 58 | print(" z: [%0.5f, %0.3f, %0.5f]" % (wzlo,wm,wzhi)) 59 | print() 60 | print("Length:") 61 | print(" t: [%0.5f, %0.3f, %0.5f]" % (ltlo,lm,lthi)) 62 | print(" z: [%0.5f, %0.3f, %0.5f]" % (lzlo,lm,lzhi)) 63 | print() 64 | 65 | -------------------------------------------------------------------------------- /ch12/memory.py: -------------------------------------------------------------------------------- 1 | from stats import * 2 | import numpy as np 3 | import matplotlib.pylab as plt 4 | from scipy.stats import ttest_ind, ttest_rel, mannwhitneyu, wilcoxon 5 | 6 | # Single-test at end of the experiment 7 | np.random.seed(8675309) 8 | N = 40 9 | control = np.random.randint(80,92,N) 10 | treatment = np.random.randint(83,95,N) 11 | 12 | print("Control:") 13 | print(control) 14 | print() 15 | print("Treatment:") 16 | print(treatment) 17 | print() 18 | 19 | print("Summary stats:") 20 | mm,md,mo,st,ma = amean(control),median(control),mode(control),std(control),mad(control) 21 | print(" Controls : mean: %0.2f, median: %0.2f, std: %0.2f, mad: %0.2f, mode:" % (mm,md,st,ma),mo) 22 | mm,md,mo,st,ma = amean(treatment),median(treatment),mode(treatment),std(treatment),mad(treatment) 23 | print(" Treatment: mean: %0.2f, median: %0.2f, std: %0.2f, mad: %0.2f, mode:" % (mm,md,st,ma),mo) 24 | print() 25 | 26 | ds = np.zeros((N,2)) 27 | ds[:,0] = control 28 | ds[:,1] = treatment 29 | 30 | plt.boxplot(ds, showmeans=True, labels=['Control','Treatment']) 31 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 32 | plt.savefig("memory_boxplot_single.eps", dpi=300) 33 | plt.close() 34 | 35 | t,p = ttest_ind(treatment, control, equal_var=False) 36 | _,u = mannwhitneyu(treatment, control) 37 | tcrit = t_critical(treatment, control, alpha=0.05) 38 | lo = (amean(treatment) - amean(control)) - tcrit*np.sqrt(std(treatment)**2/len(treatment) + std(control)**2/len(control)) 39 | hi = (amean(treatment) - amean(control)) + tcrit*np.sqrt(std(treatment)**2/len(treatment) + std(control)**2/len(control)) 40 | 41 | print("Single test at end of the experiment:") 42 | print(" t-test: (t=%0.5f, p=%0.6f), Mann-Whitney U (p=%0.5f)" % (t,p,u)) 43 | print(" Cohen's d for independent samples = %0.4f" % Cohen_d(treatment,control)) 44 | print(" 95%% CI: [%0.5f, %0.5f]" % (lo,hi)) 45 | print() 46 | 47 | # Paired tests -- pre and post treatment tests 48 | control0 = np.random.randint(80,92,N) 49 | treatment0 = np.random.randint(80,92,N) 50 | 51 | control1 = np.random.randint(80,92,N) 52 | treatment1 = np.random.randint(83,95,N) 53 | 54 | print("Control:") 55 | print(control0) 56 | print(control1) 57 | print() 58 | print("Treatment:") 59 | print(treatment0) 60 | print(treatment1) 61 | print() 62 | 63 | print("Paired tests: pre and post results:") 64 | 65 | tc,pc = ttest_rel(control1, control0) 66 | _,wc = wilcoxon(control1, control0) 67 | tcrit = t_critical(control1,control0, alpha=0.05, paired=True) 68 | d = [control1[i] - control0[i] for i in range(len(control1))] 69 | lo_c = amean(d) - tcrit * std(d) / np.sqrt(len(control1)) 70 | hi_c = amean(d) + tcrit * std(d) / np.sqrt(len(control1)) 71 | 72 | tt,pt = ttest_rel(treatment1, treatment0) 73 | _,wt = wilcoxon(treatment1, treatment0) 74 | tcrit = t_critical(treatment1,treatment0, alpha=0.05, paired=True) 75 | d = [treatment1[i] - treatment0[i] for i in range(len(treatment1))] 76 | lo_t = amean(d) - tcrit * std(d) / np.sqrt(len(treatment1)) 77 | hi_t = amean(d) + tcrit * std(d) / np.sqrt(len(treatment1)) 78 | 79 | print(" Controls : paired t-test: (t=%0.5f, p=%0.6f), paired Wilcoxon (p=%0.5f)" % (tc,pc,wc)) 80 | print(" Cohen d for paired samples = %0.4f" % Cohen_d(control1,control0,paired=True)) 81 | print(" 95%% CI: [%0.5f, %0.5f]" % (lo_c,hi_c)) 82 | print() 83 | print(" Treatment: paired t-test: (t=%0.5f, p=%0.6f), paired Wilcoxon (p=%0.5f)" % (tt,pt,wt)) 84 | print(" Cohen d for paired samples = %0.4f" % Cohen_d(treatment1,treatment0,paired=True)) 85 | print(" 95%% CI: [%0.5f, %0.5f]" % (lo_t,hi_t)) 86 | print() 87 | 88 | -------------------------------------------------------------------------------- /ch12/stats.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: stats.py 3 | # 4 | # Assorted stats-related routines 5 | # 6 | # RTK, 01-Oct-2023 7 | # Last update: 07-Oct-2023 8 | # 9 | ################################################################ 10 | 11 | import random 12 | from math import sqrt 13 | 14 | def pmean(v, power=1.0): 15 | """Power mean""" 16 | s = 0.0 17 | for t in v: 18 | s += t**power 19 | return (s/len(v))**(1/power) 20 | 21 | def amean(v, weights=None): 22 | """Arithmetic mean of a vector""" 23 | if (weights is None): 24 | return pmean(v) 25 | n = sum(weights) 26 | weights = [weights[i]/n for i in range(len(weights))] 27 | s = 0.0 28 | for i in range(len(v)): 29 | s += weights[i] * v[i] 30 | return s 31 | 32 | def gmean(v): 33 | """Geometric mean""" 34 | return pmean(v, power=1e-9) 35 | 36 | def hmean(v): 37 | """Harmonic mean""" 38 | return pmean(v, power=-1.0) 39 | 40 | def median(v): 41 | """Median of a list""" 42 | t,n = sorted(v), len(v) 43 | if (n % 2): return t[n//2] 44 | return (t[n//2-1] + t[n//2]) / 2 45 | 46 | def mode(v): 47 | """Modes of a list""" 48 | # count occurrences 49 | d = {} 50 | for t in v: 51 | if (t in d): 52 | d[t] += 1 53 | else: 54 | d[t] = 1 55 | 56 | # find the mode, if any 57 | mx,idx = 1,-1 58 | for k in d.keys(): 59 | if (d[k] > mx): 60 | mx = d[k] 61 | idx = k 62 | if (mx == 1): 63 | return [] 64 | 65 | # look for multiple modes 66 | modes = [idx] 67 | for k in d.keys(): 68 | if (d[k] == mx) and (k != modes[0]): 69 | modes.append(k) 70 | return modes 71 | 72 | def std(v): 73 | """Calculate the unbiased standard deviation""" 74 | xb = amean(v) 75 | s = 0.0 76 | for t in v: 77 | s += (xb-t)**2 78 | return sqrt(s / (len(v)-1)) 79 | 80 | def mad(v): 81 | """Median absolute deviation""" 82 | md = median(v) 83 | m = [] 84 | for t in v: 85 | m.append(abs(md-t)) 86 | return median(m) 87 | 88 | def pearson(x,y): 89 | """Calculate the Pearson correlation between x and y""" 90 | mx,my = amean(x), amean(y) 91 | cov = xd = yd = 0.0 92 | for i in range(len(x)): 93 | a,b = x[i]-mx, y[i]-my 94 | cov += a*b 95 | xd += a**2 96 | yd += b**2 97 | return cov / (sqrt(xd)*sqrt(yd)) 98 | 99 | try: 100 | from scipy.stats import spearmanr 101 | def spearman(x,y): 102 | """A wrapper on spearmanr""" 103 | return spearmanr(x,y)[0] 104 | except: 105 | pass 106 | 107 | def Cohen_d(x,y, paired=False): 108 | """Calculate Cohen's d for independent or paired data""" 109 | 110 | if (not paired): 111 | m1, s1 = amean(x), std(x) 112 | m2, s2 = amean(y), std(y) 113 | n1, n2 = len(x), len(y) 114 | sp = sqrt(((n1-1)*s1**2+(n2-1)*s2**2)/(n1+n2-2)) 115 | return (m1-m2) / sp 116 | ds = [x[i]-y[i] for i in range(len(x))] 117 | return amean(ds) / std(ds) 118 | 119 | def t_critical(x1,x2, alpha=0.05, paired=False): 120 | """Return the critical t value for the CI at alpha""" 121 | from scipy.stats import t 122 | 123 | if (not paired): 124 | m1,s1,n1 = amean(x1), std(x1), len(x1) 125 | m2,s2,n2 = amean(x2), std(x2), len(x2) 126 | num = (s1**2/n1 + s2**2/n2)**2 127 | den = s1**4/(n1**2*(n1-1)) + s2**4/(n2**2*(n2-1)) 128 | df = num / den 129 | else: 130 | df = len(x1) - 1 131 | return t.ppf(1-alpha/2, df) 132 | 133 | 134 | # 135 | # Examples 136 | # 137 | def Means(): 138 | """An example involving means""" 139 | 140 | # HM <= GM <= AM 141 | v = [1,3,2,6,3,9] 142 | print("HM <= GM <= AM:", v) 143 | print(" harmonic %0.5f" % hmean(v)) 144 | print(" geometric %0.5f" % gmean(v)) 145 | print(" arithmetic %0.5f" % amean(v)) 146 | print() 147 | 148 | # Outlier sensitivity 149 | v = [1,3,2,6,3,100,5,4,7] 150 | t = [1,3,2,6,3,5,4,7] 151 | print("Sensitivity:", v) 152 | print(" harmonic %8.5f" % hmean(v)) 153 | print(" geometric %8.5f" % gmean(v)) 154 | print(" arithmetic %8.5f" % amean(v)) 155 | print(" no outlier %8.5f" % amean(t)) 156 | print() 157 | 158 | 159 | def SE(): 160 | """SE as estimate in standard deviation of the means""" 161 | 162 | for n in [100, 1000, 10_000, 100_000, 1_000_000, 10_000_000]: 163 | p = [random.randint(0,99) for i in range(n)] 164 | se = std(p) / sqrt(n) 165 | ms = [] 166 | for m in range(10): 167 | p = [random.randint(0,99) for i in range(n)] 168 | ms.append(amean(p)) 169 | sd = std(ms) 170 | print("SE = %0.5f, SD means = %0.5f (%8d)" % (se,sd,n)) 171 | print() 172 | 173 | 174 | def MAD(): 175 | """Compare std and mad as outliers are added""" 176 | v = [random.randint(0,99) for i in range(30)] 177 | a,b = std(v), mad(v) 178 | print("std: %0.5f, mad: %0.5f" % (a,b)) 179 | v += [127] 180 | a,b = std(v), mad(v) 181 | print("std: %0.5f, mad: %0.5f" % (a,b)) 182 | v += [213] 183 | a,b = std(v), mad(v) 184 | print("std: %0.5f, mad: %0.5f" % (a,b)) 185 | 186 | 187 | def Quantile(): 188 | """Show quantiles histograms""" 189 | import numpy as np 190 | import matplotlib.pylab as plt 191 | s = np.random.beta(2,10,60_000_000) 192 | m = s.mean() 193 | h,x = np.histogram(s, bins=1000) 194 | h = h / h.sum() 195 | x = 0.5*(x[1:] + x[:-1]) 196 | q10,q25,q50,q75,q90 = np.quantile(s,[0.1,0.25,0.5,0.75,0.9],interpolation='midpoint') 197 | plt.plot(x,h, color='k', linewidth=0.7) 198 | plt.plot([m,m],[0,h.max()], color='k', linestyle='solid') 199 | plt.plot([q10,q10],[0,h.max()], color='k', linestyle='dotted') 200 | plt.plot([q25,q25],[0,h.max()], color='k', linestyle='dotted') 201 | plt.plot([q50,q50],[0,h.max()], color='k', linestyle='dashed') 202 | plt.plot([q75,q75],[0,h.max()], color='k', linestyle='dotted') 203 | plt.plot([q90,q90],[0,h.max()], color='k', linestyle='dotted') 204 | plt.xlabel("$x$") 205 | plt.ylabel("$y$") 206 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 207 | plt.savefig("quantiles_beta.png", dpi=300) 208 | plt.savefig("quantiles_beta.eps", dpi=300) 209 | plt.show() 210 | 211 | s = np.random.normal(size=60_000_000) 212 | m = s.mean() 213 | h,x = np.histogram(s, bins=1000) 214 | h = h / h.sum() 215 | x = 0.5*(x[1:] + x[:-1]) 216 | q10,q16,q25,q50,q75,q84,q90 = np.quantile(s,[0.1,0.16,0.25,0.5,0.75,0.84,0.9],interpolation='midpoint') 217 | plt.plot(x,h, color='k', linewidth=0.7) 218 | plt.plot([m,m],[0,h.max()], color='k', linestyle='solid') 219 | plt.plot([q10,q10],[0,h.max()], color='k', linestyle='dotted') 220 | plt.plot([q25,q25],[0,h.max()], color='k', linestyle='dotted') 221 | plt.plot([q50,q50],[0,h.max()], color='k', linestyle='dashed') 222 | plt.plot([q75,q75],[0,h.max()], color='k', linestyle='dotted') 223 | plt.plot([q90,q90],[0,h.max()], color='k', linestyle='dotted') 224 | plt.plot([q16,q16],[0,h.max()], color='k', linestyle='dashdot') 225 | plt.plot([q84,q84],[0,h.max()], color='k', linestyle='dashdot') 226 | plt.xlim((-4,4)) 227 | plt.xlabel("$x$") 228 | plt.ylabel("$y$") 229 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 230 | plt.savefig("quantiles_normal.png", dpi=300) 231 | plt.savefig("quantiles_normal.eps", dpi=300) 232 | plt.show() 233 | 234 | 235 | def BoxPlot(): 236 | """An example box plot""" 237 | import numpy as np 238 | import matplotlib.pylab as plt 239 | ds = np.zeros((100,3)) 240 | for i in range(100): 241 | ds[i,:] = [np.random.gamma(1,20), np.random.beta(2,7), np.random.beta(20,10)] 242 | ds[:,0] = ds[:,0] / ds[:,0].max() 243 | ds[:,1] = ds[:,1] / ds[:,1].max() 244 | ds[:,2] = ds[:,2] / ds[:,2].max() 245 | plt.boxplot(ds) 246 | plt.xlabel("Variable") 247 | plt.ylabel("$y$") 248 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 249 | plt.savefig("boxplot.eps", dpi=300) 250 | plt.savefig("boxplot.png", dpi=300) 251 | plt.show() 252 | 253 | 254 | def Pearson(): 255 | """A Pearson correlation example""" 256 | import numpy as np 257 | import matplotlib.pylab as plt 258 | ds = np.zeros((100,2)) 259 | 260 | # positive correlation 261 | for i in range(100): 262 | v = 10*np.random.random() 263 | ds[i,:] = [v, v**2 + v + 35*np.random.normal()] 264 | x,y = ds[:,0], ds[:,1] 265 | x = x / x.max() 266 | y = y / y.max() 267 | r = pearson(x,y) 268 | m,b = np.polyfit(x,y,1) 269 | plt.plot(x,y, marker='+', linestyle='none', color='k') 270 | x.sort() 271 | plt.plot(x, m*x+b, color='k', linewidth=1.2) 272 | plt.xlabel("$x$") 273 | plt.ylabel("$y$") 274 | plt.title("Pearson(x,y) = %0.5f" % r) 275 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 276 | plt.savefig("pearson_pos.eps", dpi=300) 277 | plt.show() 278 | 279 | # negative correlation 280 | for i in range(100): 281 | v = 10*np.random.random() 282 | ds[i,:] = [v, -(v**2 + v + 35*np.random.normal())] 283 | x,y = ds[:,0], ds[:,1] 284 | x = x / x.max() 285 | y = y / y.max() 286 | r = pearson(x,y) 287 | m,b = np.polyfit(x,y,1) 288 | plt.plot(x,y, marker='+', linestyle='none', color='k') 289 | x.sort() 290 | plt.plot(x, m*x+b, color='k', linewidth=1.2) 291 | plt.xlabel("$x$") 292 | plt.ylabel("$y$") 293 | plt.title("Pearson(x,y) = %0.5f" % r) 294 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 295 | plt.savefig("pearson_neg.eps", dpi=300) 296 | plt.show() 297 | 298 | # no correlation 299 | ds = np.random.random(size=(100,2)) 300 | x,y = ds[:,0], ds[:,1] 301 | r = pearson(x,y) 302 | m,b = np.polyfit(x,y,1) 303 | plt.plot(x,y, marker='+', linestyle='none', color='k') 304 | x.sort() 305 | plt.plot(x, m*x+b, color='k', linewidth=1.2) 306 | plt.xlabel("$x$") 307 | plt.ylabel("$y$") 308 | plt.title("Pearson(x,y) = %0.5f" % r) 309 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 310 | plt.savefig("pearson_none.eps", dpi=300) 311 | plt.show() 312 | 313 | 314 | def Correlation(): 315 | """Compare the Pearson and Spearman correlation coefficients""" 316 | import numpy as np 317 | import matplotlib.pylab as plt 318 | x = np.linspace(0,3,30) 319 | y = x**7*np.pi**x 320 | plt.plot(x,y, color='k', label="$y=x^7\pi^x$") 321 | plt.xlabel("$x$") 322 | plt.ylabel("$y$") 323 | plt.title("Pearson = %0.5f, Spearman = %0.5f" % (pearson(x,y), spearman(x,y))) 324 | plt.legend(loc='best') 325 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 326 | plt.savefig("correlations.eps", dpi=300) 327 | plt.show() 328 | 329 | # Anscombe's quartet: 330 | 331 | x0 = [10.0,8.0,13.0,9.0,11.0,14.0,6.0,4.0,12.0,7.0,5.0] 332 | y0 = [8.04,6.95,7.58,8.81,8.33,9.96,7.24,4.26,10.84,4.82,5.68] 333 | 334 | x1 = [10.0,8.0,13.0,9.0,11.0,14.0,6.0,4.0,12.0,7.0,5.0] 335 | y1 = [9.14,8.14,8.74,8.77,9.26,8.1,6.13,3.1,9.13,7.26,4.74] 336 | 337 | x2 = [10.0,8.0,13.0,9.0,11.0,14.0,6.0,4.0,12.0,7.0,5.0] 338 | y2 = [7.46,6.77,12.74,7.11,7.81,8.84,6.08,5.39,8.15,6.42,5.73] 339 | 340 | x3 = [8.0,8.0,8.0,8.0,8.0,8.0,8.0,19.0,8.0,8.0,8.0] 341 | y3 = [6.58,5.76,7.71,8.84,8.47,7.04,5.25,12.5,5.56,7.91,6.89] 342 | 343 | # end stats.py 344 | 345 | -------------------------------------------------------------------------------- /ch13/GaussJordan.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: GaussJordan.py 3 | # 4 | # Implements Gauss-Jordan elimination of a matrix represented 5 | # as a list of lists. 6 | # 7 | # By Jarno Elonen (elonen@iki.fi), April 2005, public domain 8 | # (https://elonen.iki.fi/code/misc-notes/python-gaussj/index.html) 9 | # 10 | ################################################################ 11 | 12 | def GaussJordan(m, eps = 1e-10): 13 | """Put matrix into reduced row-echelon form. In-place calculation. 14 | Returns True if successful, False if m is singular""" 15 | 16 | (h, w) = (len(m), len(m[0])) 17 | for y in range(0,h): 18 | maxrow = y 19 | for y2 in range(y+1, h): # Find max pivot 20 | if abs(m[y2][y]) > abs(m[maxrow][y]): 21 | maxrow = y2 22 | (m[y], m[maxrow]) = (m[maxrow], m[y]) 23 | if abs(m[y][y]) <= eps: # Singular? 24 | return False 25 | for y2 in range(y+1, h): # Eliminate column y 26 | c = m[y2][y] / m[y][y] 27 | for x in range(y, w): 28 | m[y2][x] -= m[y][x] * c 29 | for y in range(h-1, 0-1, -1): # Backsubstitute 30 | c = m[y][y] 31 | for y2 in range(0,y): 32 | for x in range(w-1, y-1, -1): 33 | m[y2][x] -= m[y][x] * m[y2][y] / c 34 | m[y][y] /= c 35 | for x in range(h, w): # Normalize row y 36 | m[y][x] /= c 37 | return True 38 | 39 | -------------------------------------------------------------------------------- /ch13/affine.py: -------------------------------------------------------------------------------- 1 | # Apply a given 2D affine transformation 2 | 3 | import sys 4 | import numpy as np 5 | import matplotlib.pylab as plt 6 | from PIL import Image 7 | 8 | if (len(sys.argv) == 1): 9 | print() 10 | print("affine [output]") 11 | print() 12 | print(" -- linear transformation matrix (no spaces or in quotes)") 13 | print(" -- translation vector as [e,f]") 14 | print(" -- output image file (optional)") 15 | print() 16 | exit(0) 17 | 18 | M = eval("np.array("+sys.argv[1]+")") 19 | t = eval("np.array("+sys.argv[2]+").reshape((2,1))") 20 | 21 | # image points -- keep every third point 22 | im = np.array(Image.open("emil.png")) 23 | x,y = np.where(1-im.T) 24 | y = 512-y 25 | x = x[::3] 26 | y = y[::3] 27 | 28 | # apply the transformation 29 | X=[] 30 | Y=[] 31 | for i in range(len(x)): 32 | v = M @ np.array([[x[i]],[y[i]]]) + t 33 | X.append(v[0]) 34 | Y.append(v[1]) 35 | 36 | # plot the result 37 | plt.plot(X,Y, marker=',', color='k', linestyle='none') 38 | plt.axis('equal') 39 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 40 | if (len(sys.argv) == 4): 41 | plt.savefig(sys.argv[3], dpi=300) 42 | plt.show() 43 | 44 | -------------------------------------------------------------------------------- /ch13/determinant.py: -------------------------------------------------------------------------------- 1 | # Calculate the determinant recursively using cofactors 2 | 3 | def determinant(A): 4 | """Calculate the determinant recursively""" 5 | 6 | def minor(A,i,j): 7 | m = A[:i] + A[i+1:] # remove row i 8 | for i in range(len(m)): 9 | m[i] = m[i][:j] + m[i][j+1:] # and column j 10 | return m 11 | 12 | # base case 13 | if (len(A) == 1) and (len(A[0]) == 1): 14 | return A[0][0] 15 | 16 | # recursive case 17 | det = 0 18 | for j in range(len(A[0])): # columns, expand across row 0 19 | cof = (-1)**j * A[0][j] * determinant(minor(A,0,j)) 20 | det += cof 21 | return det 22 | 23 | 24 | -------------------------------------------------------------------------------- /ch13/gauss_jordan.py: -------------------------------------------------------------------------------- 1 | def gauss_jordan(m, eps = 1.0/(10**10)): 2 | """Puts given matrix (2D array) into the Reduced Row Echelon Form. 3 | Returns True if successful, False if 'm' is singular. 4 | NOTE: make sure all the matrix items support fractions! Int matrix will NOT work! 5 | Written by Jarno Elonen in April 2005, released into Public Domain""" 6 | (h, w) = (len(m), len(m[0])) 7 | for y in range(0,h): 8 | maxrow = y 9 | for y2 in range(y+1, h): # Find max pivot 10 | if abs(m[y2][y]) > abs(m[maxrow][y]): 11 | maxrow = y2 12 | (m[y], m[maxrow]) = (m[maxrow], m[y]) 13 | if abs(m[y][y]) <= eps: # Singular? 14 | return False 15 | for y2 in range(y+1, h): # Eliminate column y 16 | c = m[y2][y] / m[y][y] 17 | for x in range(y, w): 18 | m[y2][x] -= m[y][x] * c 19 | for y in range(h-1, 0-1, -1): # Backsubstitute 20 | c = m[y][y] 21 | for y2 in range(0,y): 22 | for x in range(w-1, y-1, -1): 23 | m[y2][x] -= m[y][x] * m[y2][y] / c 24 | m[y][y] /= c 25 | for x in range(h, w): # Normalize row y 26 | m[y][x] /= c 27 | return True 28 | 29 | -------------------------------------------------------------------------------- /ch13/ifs_maps.txt: -------------------------------------------------------------------------------- 1 | 2 maps: ifs_map_1.png 2 | probs: 3 | [0.88984395 0.11015605] 4 | maps: 5 | [[[-0.91043556 0.03639535 0.05363721] 6 | [-0.1371923 0.81682879 0.30670304] 7 | [ 0. 0. 1. ]] 8 | 9 | [[ 0.25641965 0.06851596 0.88185653] 10 | [-0.15141152 -0.70544388 0.30245044] 11 | [ 0. 0. 1. ]]] 12 | 13 | 14 | 3 maps: ifs_map_2.png 15 | probs: 16 | [0.23450904 0.12531587 0.6401751 ] 17 | maps: 18 | [[[-0.33874576 -0.00171543 -0.42038818] 19 | [-0.62359846 0.38561149 0.23911836] 20 | [ 0. 0. 1. ]] 21 | 22 | [[-0.39066911 0.04922537 0.63596603] 23 | [ 0.15533464 0.51052594 0.49165595] 24 | [ 0. 0. 1. ]] 25 | 26 | [[-0.47868698 0.02915705 0.97357222] 27 | [-0.22544873 0.64292262 0.56547079] 28 | [ 0. 0. 1. ]]] 29 | 30 | 31 | 3 maps: ifs_map_3.png 32 | probs: 33 | [0.38232399 0.27023033 0.34744568] 34 | maps: 35 | [[[ 0.77051446 -0.15308019 -0.4565936 ] 36 | [ 0.06454036 -0.63499591 0.4723162 ] 37 | [ 0. 0. 1. ]] 38 | 39 | [[ 0.78843838 0.0521445 0.66928487] 40 | [-0.47904384 -0.05131221 -0.99834453] 41 | [ 0. 0. 1. ]] 42 | 43 | [[ 0.91428597 -0.18053622 -0.5080789 ] 44 | [ 0.11977151 -0.67769667 -0.06410512] 45 | [ 0. 0. 1. ]]] 46 | 47 | 48 | 4 maps: ifs_map_4.png 49 | probs: 50 | [0.48742783 0.30967671 0.01969816 0.1831973 ] 51 | maps: 52 | [[[-0.00725611 0.84068034 -0.01058963] 53 | [ 0.50345102 -0.25057733 0.25844304] 54 | [ 0. 0. 1. ]] 55 | 56 | [[-0.19121422 0.38657388 -0.84134972] 57 | [ 0.31611652 0.061848 -0.90039419] 58 | [ 0. 0. 1. ]] 59 | 60 | [[ 0.0806402 0.46236479 0.00545904] 61 | [-0.53974738 0.05717904 0.38553329] 62 | [ 0. 0. 1. ]] 63 | 64 | [[ 0.25478011 -0.15424439 -0.43594171] 65 | [ 0.54464303 -0.73886623 -0.71832613] 66 | [ 0. 0. 1. ]]] 67 | 68 | 69 | 5 maps: ifs_map_5.png 70 | probs: 71 | [0.36691833 0.01153257 0.32437943 0.19407022 0.10309945] 72 | maps: 73 | [[[ 1.19111370e-01 -5.92517278e-01 -6.12477640e-01] 74 | [ 5.73384883e-01 2.37558033e-01 -7.97510740e-01] 75 | [ 0.00000000e+00 0.00000000e+00 1.00000000e+00]] 76 | 77 | [[-3.10915049e-01 -2.69668573e-01 6.29933416e-03] 78 | [-2.36147893e-01 -5.39873162e-01 9.79459039e-01] 79 | [ 0.00000000e+00 0.00000000e+00 1.00000000e+00]] 80 | 81 | [[-7.23076845e-03 6.30744254e-01 -6.82057447e-01] 82 | [-5.46471490e-01 1.61958365e-01 5.62557475e-01] 83 | [ 0.00000000e+00 0.00000000e+00 1.00000000e+00]] 84 | 85 | [[-4.51931340e-01 1.13449790e-01 -6.99874548e-01] 86 | [ 6.25483514e-01 5.18686239e-01 -3.13379365e-01] 87 | [ 0.00000000e+00 0.00000000e+00 1.00000000e+00]] 88 | 89 | [[-6.65665170e-01 -8.21680922e-02 1.13062644e-04] 90 | [ 3.45448134e-01 -8.24110505e-01 9.90867192e-01] 91 | [ 0.00000000e+00 0.00000000e+00 1.00000000e+00]]] 92 | 93 | 94 | -------------------------------------------------------------------------------- /ch13/island.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkneusel9/MathForProgramming/ae6d990fa612dff8b39e6806923deae566c0ff1d/ch13/island.gif -------------------------------------------------------------------------------- /ch13/linear.py: -------------------------------------------------------------------------------- 1 | # Apply a given 2D linear transformation 2 | 3 | import sys 4 | import numpy as np 5 | import matplotlib.pylab as plt 6 | from PIL import Image 7 | 8 | if (len(sys.argv) == 1): 9 | print() 10 | print("linear [output]") 11 | print() 12 | print(" -- transformation matrix (no spaces or in quotes)") 13 | print(" -- output image file (optional)") 14 | print() 15 | exit(0) 16 | 17 | M = eval("np.array("+sys.argv[1]+")") 18 | 19 | # image points -- keep every third point 20 | im = np.array(Image.open("emil.png")) 21 | x,y = np.where(1-im.T) 22 | y = 512-y 23 | x = x[::3] 24 | y = y[::3] 25 | 26 | # apply the transformation 27 | X=[] 28 | Y=[] 29 | for i in range(len(x)): 30 | v = M @ np.array([[x[i]],[y[i]]]) 31 | X.append(v[0]) 32 | Y.append(v[1]) 33 | 34 | # plot the result 35 | plt.plot(X,Y, marker=',', color='k', linestyle='none') 36 | plt.axis('equal') 37 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 38 | if (len(sys.argv) == 3): 39 | plt.savefig(sys.argv[2], dpi=300) 40 | plt.show() 41 | 42 | -------------------------------------------------------------------------------- /ch13/matmul.c: -------------------------------------------------------------------------------- 1 | // Naive matrix multiplication -- necessary checks ignored to make the code 2 | // easier to read. 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | // index into a 2D array stored in row-major order for element (i,j) (m is column size) 9 | #define IX(i,j,m) ((i)*(m)+(j)) 10 | 11 | // multiply matrices, n x m times m x p --> n x p output 12 | int *matmul(int *A, int *B, int n, int m, int p) { 13 | int *C = (int *)malloc(n*p*sizeof(int)); 14 | int i,j,k; 15 | 16 | // zero the output matrix 17 | memset((void*)C, 0, n*p*sizeof(int)); 18 | 19 | // update each element 20 | for(i=0; i x,y): 93 | x0 = polar2rect(200, 0) 94 | x1 = polar2rect(130,-45) 95 | x2 = polar2rect( 75, 15) 96 | x3 = polar2rect(120, 95) 97 | x4 = polar2rect(130,135) 98 | 99 | print("Treasure map vectors and sum:") 100 | print(" ", pp(x0)) 101 | print(" ", pp(x1)) 102 | print(" ", pp(x2)) 103 | print(" ", pp(x3)) 104 | print("+ ", pp(x4)) 105 | print("-----------------------") 106 | print(" ", pp(x0+x1+x2+x3+x4), "<< vector sum") 107 | print(" ", pp(v), "<< turtle position") 108 | print() 109 | 110 | # event loop 111 | screen.mainloop() 112 | 113 | -------------------------------------------------------------------------------- /ch14/DX.LISP: -------------------------------------------------------------------------------- 1 | (DE DERIV (F) 2 | (COND 3 | ((EQ F 'X) '1) 4 | ((ATOM F) '0) 5 | ((EQ (CAR F) 'PLUS) (LIST 'PLUS (DERIV (CADR F)) 6 | (DERIV (CADDR F)))) 7 | ((EQ (CAR F) 'DIFFERENCE) (LIST 'DIFFERENCE (DERIV (CADR F)) 8 | (DERIV (CADDR F)))) 9 | ((EQ (CAR F) 'TIMES) (LIST 'PLUS (LIST 'TIMES (DERIV (CADR F)) 10 | (CADDR F)) 11 | (LIST 'TIMES (CADR F) 12 | (DERIV (CADDR F))))) 13 | ((EQ (CAR F) 'QUOTIENT) 14 | (LIST 'QUOTIENT (LIST 'DIFFERENCE 15 | (LIST 'TIMES (DERIV (CADR F)) 16 | (CADDR F)) 17 | (LIST 'TIMES (DERIV (CADDR F)) 18 | (CADR F))) 19 | (LIST 'TIMES (CADDR F) (CADDR F)))) 20 | 21 | ((EQ (CAR F) 'SIN) (LIST 'TIMES (LIST 'COS (CADR F)) 22 | (DERIV (CADR F)))) 23 | ((EQ (CAR F) 'COS) (LIST 'TIMES (LIST 'MINUS (LIST 'SIN (CADR F))) 24 | (DERIV (CADR F)))) 25 | ((EQ (CAR F) 'TAN) (LIST 'TIMES (LIST 'SEC**2 (CADR F)) 26 | (DERIV (CADR F)))) 27 | ((EQ (CAR F) 'EXP) (LIST 'TIMES (LIST 'EXP (CADR F)) 28 | (DERIV (CADR F)))) 29 | ((EQ (CAR F) 'LN) (LIST 'QUOTIENT (DERIV (CADR F)) (CADR F))) 30 | ((EQ (CAR F) 'MINUS) (LIST 'MINUS (DERIV (CADR F)))) 31 | ((EQ (CAR F) 'POWER) (LIST 'TIMES (LIST 'TIMES (CADDR F) 32 | (LIST 'POWER (CADR F) 33 | (DIFFERENCE (CADDR F) 1))) 34 | (DERIV (CADR F)))) 35 | (T (LIST 'Syntax 'Error 'From (CAR F))) 36 | )) 37 | 38 | (DE SIMPLIFY (F) 39 | (COND 40 | ((ATOM F) F) 41 | ((AND (EQ (CAR F) 'PLUS) 42 | (EQ (SIMPLIFY (CADR F)) '0)) (SIMPLIFY (CADDR F))) 43 | ((AND (EQ (CAR F) 'PLUS) 44 | (EQ (SIMPLIFY (CADDR F)) '0)) (SIMPLIFY (CADR F))) 45 | ((EQ (CAR F) 'PLUS) (LIST 'PLUS (SIMPLIFY (CADR F)) 46 | (SIMPLIFY (CADDR F)))) 47 | 48 | ((AND (EQ (CAR F) 'TIMES) 49 | (EQ (SIMPLIFY (CADDR F)) '0)) '0) 50 | ((AND (EQ (CAR F) 'TIMES) 51 | (EQ (SIMPLIFY (CADR F)) '0)) '0) 52 | ((AND (EQ (CAR F) 'TIMES) 53 | (EQ (SIMPLIFY (CADR F)) '1)) (SIMPLIFY (CADDR F))) 54 | ((AND (EQ (CAR F) 'TIMES) 55 | (EQ (SIMPLIFY (CADDR F)) '1)) (SIMPLIFY (CADR F))) 56 | ((EQ (CAR F) 'TIMES) (LIST 'TIMES (SIMPLIFY (CADR F)) 57 | (SIMPLIFY (CADDR F)))) 58 | 59 | ((AND (EQ (CAR F) 'DIFFERENCE) 60 | (EQ (SIMPLIFY (CADR F)) '0)) (LIST 'MINUS (SIMPLIFY (CADDR F)))) 61 | ((AND (EQ (CAR F) 'DIFFERENCE) 62 | (EQ (SIMPLIFY (CADDR F)) '0)) (SIMPLIFY (CADR F))) 63 | ((EQ (CAR F) 'DIFFERENCE) (LIST 'DIFFERENCE (SIMPLIFY (CADR F)) 64 | (SIMPLIFY (CADDR F)))) 65 | 66 | ((EQ (CAR F) 'MINUS) (LIST 'MINUS (SIMPLIFY (CADR F)))) 67 | ((EQ (CAR F) 'QUOTIENT) (LIST 'QUOTIENT (SIMPLIFY (CADR F)) 68 | (SIMPLIFY (CADDR F)))) 69 | (T F) 70 | )) 71 | 72 | (DE INFIX (F) 73 | (COND 74 | ((ATOM F) F) 75 | ((EQ (CAR F) 'PLUS) 76 | (LIST (INFIX (CADR F)) '+ (INFIX (CADDR F)))) 77 | ((EQ (CAR F) 'DIFFERENCE) 78 | (LIST (INFIX (CADR F)) '- (INFIX (CADDR F)))) 79 | ((EQ (CAR F) 'TIMES) 80 | (LIST (INFIX (CADR F)) '* (INFIX (CADDR F)))) 81 | ((EQ (CAR F) 'QUOTIENT) 82 | (LIST (INFIX (CADR F)) '/ (INFIX (CADDR F)))) 83 | ((EQ (CAR F) 'MINUS) 84 | (LIST '- (INFIX (CADR F)))) 85 | ((EQ (CAR F) 'SIN) 86 | (LIST 'SIN (INFIX (CADR F)))) 87 | ((EQ (CAR F) 'COS) 88 | (LIST 'COS (INFIX (CADR F)))) 89 | ((EQ (CAR F) 'TAN) 90 | (LIST 'TAN (INFIX (CADR F)))) 91 | ((EQ (CAR F) 'SEC**2) 92 | (LIST 'SEC**2 (INFIX (CADR F)))) 93 | ((EQ (CAR F) 'EXP) 94 | (LIST 'EXP (INFIX (CADR F)))) 95 | ((EQ (CAR F) 'LN) 96 | (LIST 'LN (INFIX (CADR F)))) 97 | ((EQ (CAR F) 'POWER) 98 | (LIST (INFIX (CADR F)) '** (CADDR F))) 99 | (T (LIST 'Syntax 'Error 'From (CAR F))) 100 | )) 101 | 102 | (DE DX (F) 103 | (INFIX (SIMPLIFY (DERIV F)))) 104 | 105 | -------------------------------------------------------------------------------- /ch14/README.txt: -------------------------------------------------------------------------------- 1 | The file DX.LISP contains 1980s era Lisp code that implements 2 | symbolic differentiation. It originally ran on a Data General MV/8000 3 | mainframe using a Lisp interpreter written in FORTRAN. 4 | 5 | The main function is at the bottom, DX. It expects a list representing 6 | the function to differentiate using standard Lisp prefix notation: 7 | 8 | cos(3*x-4) 9 | 10 | becomes: 11 | 12 | (COS (- (* 3 X) 4)) 13 | 14 | which, for the old Lisp interpreter becomes: 15 | 16 | (COS (DIFFERENCE (TIMES 3 X) 4)) 17 | 18 | The output of DX is the derivative of the argument 19 | to DERIV given in the above format after passing the derivative 20 | to SIMPLIFY to handle easy transformations and then INFIX 21 | to convert the prefix format answer to a fully-parenthesized 22 | infix expression. 23 | 24 | I'm putting the code here for two purposes. First, as an example 25 | of how compact symbolic manipulation can be and second, as a challenge 26 | exercise for you to transform the 40 year old code into a modern 27 | language like Python or Scheme. 28 | 29 | If you do, and you get it working, let me know: rkneuselbooks@gmail.com 30 | 31 | -------------------------------------------------------------------------------- /ch14/abs.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pylab as plt 3 | x = np.linspace(-2,8,1000) 4 | y = np.abs(x-3) 5 | plt.plot(x,y, color='k') 6 | plt.xlabel("$x$") 7 | plt.ylabel("$f(x)$") 8 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 9 | plt.savefig("abs.eps", dpi=300) 10 | plt.show() 11 | 12 | -------------------------------------------------------------------------------- /ch14/chain.py: -------------------------------------------------------------------------------- 1 | # Show that the chain rule does, indeed, produce a function 2 | # that determines the slope of the tangent line at any given point 3 | 4 | import sys 5 | import numpy as np 6 | import matplotlib.pylab as plt 7 | 8 | if (len(sys.argv) == 1): 9 | print() 10 | print("chain ") 11 | print() 12 | print("plot a curve and the tangent line at where in [0,2]") 13 | print() 14 | exit(0) 15 | 16 | def h(x): 17 | return 3*(np.sin(x**3+2*x))**2 18 | 19 | def dh(x): 20 | return 6.0*(3.0*x**2+2.0)*np.sin(x**3+2*x)*np.cos(x**3+2*x) 21 | 22 | def PlotTangent(x): 23 | m = dh(x) 24 | b = h(x) - m*x 25 | xx = np.linspace(x-0.3, x+0.3, 10) 26 | yy = m*xx + b 27 | plt.plot(xx,yy, color='k', linewidth=0.7) 28 | plt.plot(x,h(x), marker='o', fillstyle='none', color='k') 29 | 30 | t = float(sys.argv[1]) 31 | x = np.linspace(0,2,600) 32 | plt.plot(x,h(x), color='k') 33 | PlotTangent(t) 34 | plt.xlim((0,2.05)) 35 | plt.ylim((0,3.05)) 36 | plt.xlabel("$x$") 37 | plt.ylabel("$y$") 38 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 39 | plt.show() 40 | plt.close() 41 | 42 | plt.plot(x,dh(x), linestyle='dashed', color='k') 43 | plt.xlabel("$x$") 44 | plt.ylabel("$y$") 45 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 46 | plt.show() 47 | plt.close() 48 | 49 | -------------------------------------------------------------------------------- /ch14/dual.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: dual.py 3 | # 4 | # A simple dual numbers class 5 | # 6 | # RTK, 16-Feb-2022 7 | # Last update: 16-Feb-2022 8 | # 9 | ################################################################ 10 | 11 | from math import sin, cos, log, exp, sqrt 12 | 13 | # 14 | # For automatic differentiation, x --> Dual(x,1) -- variable 15 | # c --> Dual(c,0) -- some constant 16 | # 17 | 18 | ################################################################ 19 | # Dual 20 | # 21 | class Dual: 22 | """Basic dual numbers""" 23 | 24 | def __init__(self, a,b): 25 | """Constructor""" 26 | self.a = a 27 | self.b = b 28 | 29 | def __str__(self): 30 | if (self.b >= 0.0): 31 | return "%f+%fe" % (self.a, self.b) 32 | else: 33 | return "%f-%fe" % (self.a, abs(self.b)) 34 | 35 | def __repr__(self): 36 | return self.__str__() 37 | 38 | def __add__(self, z): 39 | return Dual(self.a+z.a, self.b+z.b) 40 | 41 | def __sub__(self, z): 42 | return Dual(self.a-z.a, self.b-z.b) 43 | 44 | def __mul__(self, z): 45 | return Dual(self.a*z.a, self.a*z.b+self.b*z.a) 46 | 47 | def __truediv__(self, z): 48 | return Dual(self.a/z.a, (self.b*z.a-self.a*z.b)/(z.a*z.a)) 49 | 50 | def __pow__(self, z): 51 | return Dual(self.a**z, z*self.b*self.a**(z-1.0)) 52 | 53 | def sqrt(self): 54 | t = sqrt(self.a) 55 | return Dual(t, 0.5*self.b/t) 56 | 57 | def sin(self): 58 | return Dual(sin(self.a), self.b*cos(self.a)) 59 | 60 | def cos(self): 61 | return Dual(cos(self.a), -self.b*sin(self.a)) 62 | 63 | def tan(self): 64 | return self.sin() / self.cos() 65 | 66 | def exp(self): 67 | return Dual(exp(self.a), self.b*exp(self.a)) 68 | 69 | def log(self): 70 | return Dual(log(self.a), self.b/self.a) 71 | 72 | -------------------------------------------------------------------------------- /ch14/dual_test.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: dual_test.py 3 | # 4 | # Generate f(x) and f'(x) 5 | # 6 | # RTK, 16-Feb-2022 7 | # Last update: 09-Nov-2023 8 | # 9 | ################################################################ 10 | 11 | from dual import * 12 | import numpy as np 13 | import matplotlib.pylab as plt 14 | 15 | N = 200 16 | x = np.linspace(-10,10,N) 17 | y,yp = np.zeros(N), np.zeros(N) 18 | for i in range(N): 19 | u = Dual(x[i],1) 20 | v = u*u.sin() + u*u.cos() 21 | y[i], yp[i] = v.a, v.b 22 | 23 | plt.plot(x,y, color='k', label='f(x)') 24 | plt.plot(x,yp,color='k', linestyle='dashed', label="f'(x)") 25 | plt.legend(loc='upper right') 26 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 27 | plt.savefig("dual_plot_1.eps", dpi=300) 28 | plt.show() 29 | 30 | x = np.linspace(-5,5,N) 31 | y,yp = np.zeros(N), np.zeros(N) 32 | for i in range(N): 33 | v = (Dual(-0.5,0)*Dual(x[i],1)**2).exp() 34 | y[i], yp[i] = v.a, v.b 35 | 36 | plt.plot(x,y, color='k', label='f(x)') 37 | plt.plot(x,yp,color='k', linestyle='dashed', label="f'(x)") 38 | plt.legend(loc='upper right') 39 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 40 | plt.savefig("dual_plot_2.eps", dpi=300) 41 | plt.show() 42 | 43 | x = np.linspace(-10,10,N) 44 | y,yp = np.zeros(N), np.zeros(N) 45 | for i in range(N): 46 | v = (Dual(1,0) + (Dual(-1,0)*Dual(x[i],1)).exp())**(-1) 47 | y[i], yp[i] = v.a, v.b 48 | 49 | plt.plot(x,y, color='k', label='f(x)') 50 | plt.plot(x,yp,color='k', linestyle='dashed', label="f'(x)") 51 | plt.legend(loc='upper right') 52 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 53 | plt.savefig("dual_plot_3.eps", dpi=300) 54 | plt.show() 55 | 56 | x = np.linspace(0,10,N) 57 | y,yp = np.zeros(N), np.zeros(N) 58 | for i in range(N): 59 | u = Dual(x[i],1) 60 | v = Dual(2,0)*(Dual(3,0)*u).sin() + Dual(20,0)*(Dual(-0.5,0)*(u-Dual(8,0))**2/Dual(0.6,0)).exp() 61 | y[i], yp[i] = v.a, v.b 62 | 63 | plt.plot(x,y, color='k', label='f(x)') 64 | plt.plot(x,yp,color='k', linestyle='dashed', label="f'(x)") 65 | plt.legend(loc='upper right') 66 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 67 | plt.savefig("dual_plot_4.eps", dpi=300) 68 | plt.show() 69 | 70 | -------------------------------------------------------------------------------- /ch14/extrema.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pylab as plt 3 | 4 | def f(x): 5 | return x**3 -3*x**2-3*x+4 6 | 7 | x = np.linspace(-2.5,4.5,100) 8 | y = f(x) 9 | r0,r1 = 1-np.sqrt(2), 1+np.sqrt(2) 10 | 11 | plt.plot(x,y, color='k') 12 | plt.plot([r0-1,r0+1],[f(r0),f(r0)], color='k', linewidth=0.7) 13 | plt.plot([r0,r0],[f(r0)-3,f(r0)+3], color='k', linewidth=0.7) 14 | plt.plot([r1-1,r1+1],[f(r1),f(r1)], color='k', linewidth=0.7) 15 | plt.plot([r1,r1],[f(r1)-3,f(r1)+3], color='k', linewidth=0.7) 16 | plt.xlabel("$x$") 17 | plt.ylabel("$y$") 18 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 19 | plt.savefig("extrema.eps", dpi=300) 20 | plt.show() 21 | 22 | -------------------------------------------------------------------------------- /ch14/gd_1d.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: gd_1d.py 3 | # 4 | # 1D example of GD 5 | # 6 | # RTK, 14-Feb-2021 7 | # Last update: 08-Nov-2023 8 | # 9 | ################################################################ 10 | 11 | import sys 12 | import os 13 | import numpy as np 14 | import matplotlib.pylab as plt 15 | 16 | def f(x): 17 | return 6*x**2 - 12*x + 3 18 | 19 | def deriv(f,x, h=1e-5): 20 | return (f(x+h) - f(x-h)) / (2*h) 21 | 22 | # Show a series of gradient descent steps 23 | x = np.linspace(-1,3,1000) 24 | plt.plot(x,f(x), color='k') 25 | 26 | x = -0.9 27 | eta = 0.03 28 | for i in range(15): 29 | plt.plot(x, f(x), marker='o', color='k') 30 | x = x - eta * deriv(f,x) 31 | 32 | plt.xlabel("$x$") 33 | plt.ylabel("$y$") 34 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 35 | plt.savefig("gd_1d_steps.eps", dpi=300) 36 | plt.show() 37 | plt.close() 38 | print("Minimum at (%0.6f, %0.6f)" % (x, f(x))) 39 | 40 | # Show oscillation if step size too large 41 | x = np.linspace(0.75,1.25,1000) 42 | plt.plot(x,f(x), color='k') 43 | x = xold = 0.75 44 | for i in range(14): 45 | plt.plot([xold,x], [f(xold),f(x)], marker='o', linestyle='dotted', color='k') 46 | xold = x 47 | x = x - 0.15 * deriv(f,x) 48 | 49 | plt.xlabel("$x$") 50 | plt.ylabel("$y$") 51 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 52 | plt.savefig("gd_1d_oscillating.eps", dpi=300) 53 | plt.show() 54 | 55 | -------------------------------------------------------------------------------- /ch14/gd_2d.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: gd_2d.py 3 | # 4 | # 2D example of gradient descent 5 | # 6 | # RTK, 08-Nov-2023 7 | # Last update: 08-Nov-2023 8 | # 9 | ################################################################ 10 | 11 | import numpy as np 12 | import matplotlib.pylab as plt 13 | 14 | def f(x,y): 15 | return 6*x**2 + 9*y**2 - 12*x - 14*y + 3 16 | 17 | def dx(f,x,y, h=1e-5): 18 | return (f(x+h,y) - f(x-h,y)) / (2*h) 19 | 20 | def dy(f,x,y, h=1e-5): 21 | return (f(x,y+h) - f(x,y-h)) / (2*h) 22 | 23 | # Gradient descent steps 24 | N = 100 25 | x,y = np.meshgrid(np.linspace(-1,3,N), np.linspace(-1,3,N)) 26 | z = f(x,y) 27 | plt.contourf(x,y,z,10, cmap="Greys") 28 | plt.contour(x,y,z,10, colors='k', linewidths=1) 29 | plt.plot([0,0],[-1,3],color='k',linewidth=1) 30 | plt.plot([-1,3],[0,0],color='k',linewidth=1) 31 | plt.plot(1,0.7777778,color='k',marker='+') 32 | 33 | # Step size 34 | eta = 0.02 35 | 36 | x = xold = -0.5 37 | y = yold = 2.9 38 | for i in range(12): 39 | plt.plot([xold,x],[yold,y], marker='o', linestyle='dotted', color='k') 40 | xold = x 41 | yold = y 42 | x = x - eta * dx(f,x,y) 43 | y = y - eta * dy(f,x,y) 44 | 45 | x = xold = 1.5 46 | y = yold = -0.8 47 | for i in range(12): 48 | plt.plot([xold,x],[yold,y], marker='s', linestyle='dotted', color='k') 49 | xold = x 50 | yold = y 51 | x = x - eta * dx(f,x,y) 52 | y = y - eta * dy(f,x,y) 53 | 54 | x = xold = 2.7 55 | y = yold = 2.3 56 | for i in range(12): 57 | plt.plot([xold,x],[yold,y], marker='<', linestyle='dotted', color='k') 58 | xold = x 59 | yold = y 60 | x = x - eta * dx(f,x,y) 61 | y = y - eta * dy(f,x,y) 62 | 63 | plt.xlabel("$x$") 64 | plt.ylabel("$y$") 65 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 66 | plt.savefig("gd_2d_steps.eps", dpi=300) 67 | plt.show() 68 | plt.close() 69 | 70 | # New function 71 | def f(x,y): 72 | return 6*x**2 + 40*y**2 - 12*x - 30*y + 3 73 | 74 | N = 100 75 | x,y = np.meshgrid(np.linspace(-1,3,N), np.linspace(-1,3,N)) 76 | z = f(x,y) 77 | plt.contourf(x,y,z,10, cmap="Greys") 78 | plt.contour(x,y,z,10, colors='k', linewidths=1) 79 | plt.plot([0,0],[-1,3],color='k',linewidth=1) 80 | plt.plot([-1,3],[0,0],color='k',linewidth=1) 81 | plt.plot(1,0.375,color='k',marker='+') 82 | 83 | x = xold = -0.5 84 | y = yold = 2.3 85 | for i in range(14): 86 | plt.plot([xold,x],[yold,y], marker='o', linestyle='dotted', color='k') 87 | xold = x 88 | yold = y 89 | x = x - eta * dx(f,x,y) 90 | y = y - eta * dy(f,x,y) 91 | 92 | x = xold = 2.3 93 | y = yold = 2.3 94 | for i in range(14): 95 | plt.plot([xold,x],[yold,y], marker='s', linestyle='dotted', color='k') 96 | xold = x 97 | yold = y 98 | x = x - (eta/2) * dx(f,x,y) 99 | y = y - (eta/2) * dy(f,x,y) 100 | 101 | plt.xlabel("$x$") 102 | plt.ylabel("$y$") 103 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 104 | plt.savefig("gd_2d_oscillating.eps", dpi=300) 105 | plt.show() 106 | plt.close() 107 | 108 | -------------------------------------------------------------------------------- /ch14/make_slope_movie.py: -------------------------------------------------------------------------------- 1 | # Create frames showing the slope when moving 2 | # over a curve 3 | # 4 | # ffmpeg -framerate 8 -i frames/frame_%04d.png -c:v libx264 -vf "fps=8,scale=-1:-1" -pix_fmt yuv420p slopes.mp4 5 | # 6 | 7 | import os 8 | import numpy as np 9 | import matplotlib.pylab as plt 10 | 11 | def PlotTangent(df,yf,x): 12 | m = eval(df) 13 | b = eval(yf) - m*x 14 | xx = np.linspace(x-0.02, x+0.02, 10) 15 | yy = m*xx + b 16 | plt.plot(xx,yy, color='k', linewidth=0.7) 17 | plt.plot(x,eval(yf), marker="o", fillstyle='none', color='k') 18 | 19 | x = np.linspace(0,0.25,100) 20 | yf = "np.sin(8*np.pi*x) + 0.2*np.sin(25*np.pi*x)" 21 | df = "8*np.pi*np.cos(8*np.pi*x) + 0.2*25*np.pi*np.cos(25*np.pi*x)" 22 | y = eval(yf) 23 | 24 | os.system("rm -rf frames; mkdir frames") 25 | 26 | k, p = 0, 0.01 27 | while (p < 0.24): 28 | plt.plot(x,y, color='k') 29 | PlotTangent(df,yf, p) 30 | plt.xlim((0,0.25)) 31 | plt.ylim((y.min()-0.1*np.abs(y.min()),y.max()+0.1*y.max())) 32 | plt.xlabel("$x$") 33 | plt.ylabel("$y$") 34 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 35 | plt.savefig("frames/frame_%04d.png" % k, dpi=100) 36 | plt.close() 37 | p += 0.0017 38 | k += 1 39 | 40 | -------------------------------------------------------------------------------- /ch14/make_slope_plot.py: -------------------------------------------------------------------------------- 1 | # Create the slope0.eps plot 2 | 3 | import numpy as np 4 | import matplotlib.pylab as plt 5 | 6 | def PlotTangent(df,yf,x): 7 | m = eval(df) 8 | b = eval(yf) - m*x 9 | xx = np.linspace(x-0.03, x+0.03, 10) 10 | yy = m*xx + b 11 | plt.plot(xx,yy, color='k', linewidth=0.7) 12 | plt.plot(x,eval(yf), marker="o", fillstyle='none', color='k') 13 | 14 | def PlotSecant(df,yf,x): 15 | m = eval(df) 16 | b = eval(yf) - m*x + 0.03 17 | xx = np.linspace(x-0.03, x+0.03, 10) 18 | yy = m*xx + b 19 | plt.plot(xx,yy, color='k', linewidth=0.7) 20 | 21 | x = np.linspace(0,0.25,600) 22 | yf = "np.sin(8*np.pi*x) + 0.2*np.sin(25*np.pi*x)" 23 | df = "8*np.pi*np.cos(8*np.pi*x) + 0.2*25*np.pi*np.cos(25*np.pi*x)" 24 | y = eval(yf) 25 | 26 | # tangent lines 27 | plt.plot(x,y, color='k') 28 | PlotTangent(df,yf, 0.0624) 29 | PlotTangent(df,yf, 0.1382) 30 | PlotTangent(df,yf, 0.2082) 31 | PlotTangent(df,yf, 0.2349) 32 | plt.xlim((0,0.25)) 33 | plt.ylim((y.min()-0.1*np.abs(y.min()),y.max()+0.1*y.max())) 34 | plt.xlabel("$x$") 35 | plt.ylabel("$y$") 36 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 37 | plt.savefig("slope0.eps", dpi=300) 38 | plt.close() 39 | 40 | # secant line 41 | plt.plot(x,y, color='k') 42 | PlotTangent(df,yf, 0.0624) 43 | PlotSecant(df,yf, 0.0624) 44 | x = 0.05224; y = eval(yf) 45 | plt.plot([x,x],[y,y], marker="o", color='k') 46 | x = 0.07415; y = eval(yf) 47 | plt.plot([x,x],[y,y], marker="o", color='k') 48 | plt.xlim((0.02,0.1)) 49 | plt.ylim((0.65,0.95)) 50 | plt.xlabel("$x$") 51 | plt.ylabel("$y$") 52 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 53 | plt.savefig("slope1.eps", dpi=300) 54 | plt.show() 55 | 56 | -------------------------------------------------------------------------------- /ch14/minmax.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: minmax.py 3 | # 4 | # Plot a function, its derivative, and highlight the zeros of 5 | # the derivative as corresponding to the minima and maxima 6 | # of the function 7 | # 8 | # RTK, 02-Nov-2023 9 | # Last update: 02-Nov-2023 10 | # 11 | ################################################################ 12 | 13 | import numpy as np 14 | import matplotlib.pylab as plt 15 | 16 | def f(x): 17 | return x**2*np.exp(-x**2) 18 | 19 | def df(x): 20 | return 2*x*np.exp(-x**2)*(1-x**2) 21 | 22 | lo,hi,n = -3,3,300 23 | x = np.linspace(lo,hi,n) 24 | plt.plot(x,f(x), color='k', linestyle='solid') 25 | plt.plot(x,df(x), color='k', linestyle='dashed') 26 | plt.plot([lo,hi],[0,0], color='k', linestyle='solid', linewidth=0.5) 27 | plt.plot([0,0], [min(df(x)),max(df(x))], color='k', linewidth=0.5) 28 | plt.plot([-1,-1],[0,f(-1)], color='k', linewidth=0.5) 29 | plt.plot([+1,+1],[0,f(-1)], color='k', linewidth=0.5) 30 | plt.xlabel("$x$") 31 | plt.ylabel("$y$") 32 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 33 | plt.savefig("minmax.eps", dpi=300) 34 | plt.show() 35 | 36 | -------------------------------------------------------------------------------- /ch14/newton.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: newton.py 3 | # 4 | # Newton's method to locate the zeros of a function 5 | # 6 | # RTK, 02-Nov-2023 7 | # Last update: 03-Nov-2023 8 | # 9 | ################################################################ 10 | 11 | import sys 12 | import numpy as np 13 | import matplotlib.pylab as plt 14 | 15 | def f(x): 16 | return x**3 + 3*x**2 - 3*x - 10 17 | 18 | def df(x): 19 | return 3*x**2 + 6*x - 3 20 | 21 | def Newton(f, df, x=1.0, eps=1e-7): 22 | """Newton's method to locate a zero of a function""" 23 | xold = x 24 | x = x - f(x) / df(x) 25 | while (abs(x-xold) > eps): 26 | xold = x 27 | x = x - f(x) / df(x) 28 | return x 29 | 30 | # Locate the roots using different starting positions on the command line 31 | # Roots at x = -2.7912878, -2.0, 1.7912878 32 | x0 = float(sys.argv[1]) 33 | root = Newton(f, df, x0) 34 | print("Root at %0.7f" % root) 35 | 36 | lo,hi,n = -3.4,2.2,600 37 | x = np.linspace(lo,hi,n) 38 | plt.plot(x,f(x), color='k') 39 | plt.plot([lo,hi],[0,0], color='k', linewidth=0.5) 40 | plt.plot([0,0],[min(f(x)),max(f(x))], color='k', linewidth=0.5) 41 | plt.plot([x0,x0],[0,0], marker='s', fillstyle='none', color='k') 42 | plt.plot([root,root],[0,0], marker='o', fillstyle='none', color='k') 43 | plt.xlabel("$x$") 44 | plt.ylabel("$y$") 45 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 46 | plt.savefig("newton.eps", dpi=300) 47 | plt.show() 48 | 49 | -------------------------------------------------------------------------------- /ch14/numeric.py: -------------------------------------------------------------------------------- 1 | # A simple numeric differentiation example 2 | 3 | import numpy as np 4 | import matplotlib.pylab as plt 5 | 6 | def f(x): 7 | return x**2 + np.sin(3*x) 8 | 9 | def deriv(f,x, h=1e-4): 10 | return (f(x+h) - f(x)) / h 11 | 12 | # Plot the function 13 | x = np.linspace(0,2,400) 14 | plt.plot(x,f(x), color='k', label='function') 15 | 16 | # And the first derivative calculated numerically 17 | plt.plot(x,deriv(f,x), color='k', linewidth=0.7, label='numeric') 18 | 19 | # And the symbolic derivative offset slightly to show the shape 20 | # matches 21 | y = 2*x + 3*np.cos(3*x) + 0.2 22 | plt.plot(x,y, linestyle='dotted', color='k', linewidth=0.7, label='symbolic') 23 | 24 | plt.xlabel("$x$") 25 | plt.ylabel("$y$") 26 | plt.legend(loc='best') 27 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 28 | #plt.savefig('numeric.eps', dpi=300) 29 | plt.show() 30 | 31 | # Compare forward and central differences 32 | def cderiv(f,x, h=1e-4): 33 | return (f(x+h)-f(x-h)) / (2*h) 34 | 35 | print("Forward Exact Central") 36 | for i in range(10): 37 | fd = deriv(f,x[i]) 38 | cd = cderiv(f,x[i]) 39 | ex = 2*x[i] + 3*np.cos(3*x[i]) 40 | print("%0.8f %0.8f %0.8f" % (fd,ex,cd)) 41 | 42 | -------------------------------------------------------------------------------- /ch14/slopes.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkneusel9/MathForProgramming/ae6d990fa612dff8b39e6806923deae566c0ff1d/ch14/slopes.mp4 -------------------------------------------------------------------------------- /ch14/symbolic.py: -------------------------------------------------------------------------------- 1 | # A brief symbolic differentiation example 2 | 3 | import numpy as np 4 | import matplotlib.pylab as plt 5 | from sympy import symbols, diff, lambdify, sin, exp 6 | 7 | # 'x' is a symbol 8 | x = symbols('x') 9 | 10 | # f(x) 11 | f = 2*sin(3*x) + 20*exp(-0.5*((x - 8)**2)/0.6) 12 | 13 | # f'(x) 14 | fp = diff(f,x) 15 | print(fp) 16 | 17 | # Make the SymPy expressions into NumPy functions 18 | f_numpy = lambdify(x, f, modules=['numpy']) 19 | fp_numpy = lambdify(x, fp, modules=['numpy']) 20 | 21 | # NumPy f(x) and f'(x) 22 | xv = np.linspace(0, 10, 200) 23 | y = f_numpy(xv) 24 | yp = fp_numpy(xv) 25 | 26 | import pdb; pdb.set_trace() 27 | 28 | # Plot 29 | plt.plot(xv,y, color='k', label='f(x)') 30 | plt.plot(xv,yp, color='k', linestyle='dashed', label="f'(x)") 31 | plt.legend(loc='upper right') 32 | plt.xlabel("$x$") 33 | plt.ylabel("$y$") 34 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 35 | plt.show() 36 | 37 | -------------------------------------------------------------------------------- /ch15/adaptive.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: adaptive.py 3 | # 4 | # Numeric integration via adaptive Simpson's rule 5 | # 6 | # E.g., 7 | # python3 adaptive.py 'x**2*sin(3*x-4)*exp(-x**2)+cos(sin(3*x**3))' 0 6 8 | # (function too complex for SymPy) 9 | # 10 | # RTK, 15-Nov-2023 11 | # Last update: 15-Nov-2023 12 | # 13 | ################################################################ 14 | 15 | import sys 16 | from math import sqrt,sin,cos,tan,log,exp 17 | 18 | def Simpson(f, a, b, n=2): 19 | """Standard Simpson's rule""" 20 | if (n % 2): 21 | n += 1 22 | h = (b-a)/n 23 | x = a 24 | area = eval(f) 25 | x = b 26 | area += eval(f) 27 | for i in range(1,n): 28 | x = a + i*h 29 | if (i % 2): 30 | area += 4*eval(f) 31 | else: 32 | area += 2*eval(f) 33 | return area*(h/3) 34 | 35 | 36 | def AdaptiveSimpson(f, a, b, epsilon=1e-6, max_depth=100, depth=0): 37 | """Adaptive Simpson's rule that recurses on subdivisions""" 38 | m = (a + b) / 2 39 | s_ab = Simpson(f, a, b) # whole interval 40 | s_am = Simpson(f, a, m) # first half 41 | s_mb = Simpson(f, m, b) # second half 42 | 43 | # The difference is below the threshold, return 44 | if (abs(s_ab - (s_am + s_mb)) < epsilon): 45 | return s_am + s_mb 46 | 47 | # Max recursion depth is reached, return the current approximation 48 | if (depth >= max_depth): 49 | return s_am + s_mb 50 | 51 | # Apply Simpson's rule recursively to each half of the interval 52 | left = AdaptiveSimpson(f, a, m, epsilon=epsilon, depth=depth+1) 53 | right = AdaptiveSimpson(f, m, b, epsilon=epsilon, depth=depth+1) 54 | return left + right 55 | 56 | 57 | if (len(sys.argv) == 1): 58 | print() 59 | print("adaptive ") 60 | print() 61 | print(" - expression to integrate") 62 | print(" , - limits") 63 | print() 64 | exit(0) 65 | 66 | expr = sys.argv[1] 67 | a,b = float(sys.argv[2]), float(sys.argv[3]) 68 | 69 | print("Area under the curve = %0.8f" % AdaptiveSimpson(expr,a,b, epsilon=1e-10)) 70 | 71 | -------------------------------------------------------------------------------- /ch15/darts.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: darts.py 3 | # 4 | # Random darts approach to finding the area under a curve 5 | # 6 | # RTK, 11-Nov-2023 7 | # Last update: 15-Nov-2023 8 | # 9 | ################################################################ 10 | 11 | import sys 12 | import numpy as np 13 | from math import sqrt,sin,cos,tan,log,exp 14 | 15 | if (len(sys.argv) == 1): 16 | print("darts ") 17 | print() 18 | print(" - the curve") 19 | print(" , - limits in x") 20 | print(" - number of darts") 21 | print() 22 | exit(0) 23 | 24 | expr = sys.argv[1] 25 | a,b = float(sys.argv[2]), float(sys.argv[3]) 26 | n = int(sys.argv[4]) 27 | 28 | c, ymax, m = 0, -1, 1000 29 | 30 | for i in range(m): 31 | x = a + i*(b-a)/m 32 | y = eval(expr) 33 | if (y > ymax): 34 | ymax = y 35 | 36 | for i in range(n): 37 | x = a + (b-a)*np.random.random() 38 | y = ymax*np.random.random() 39 | f = eval(expr) 40 | if (y < f): 41 | c += 1 42 | 43 | area = (c/n)*(b-a)*ymax 44 | print("Area under the curve = %0.8f" % area) 45 | 46 | -------------------------------------------------------------------------------- /ch15/darts_plot.py: -------------------------------------------------------------------------------- 1 | # Make the darts.eps plot 2 | import numpy as np 3 | import matplotlib.pylab as plt 4 | x = np.linspace(1,3,1000) 5 | y = x**2*np.exp(x) 6 | ym = 3**2*np.exp(3) 7 | 8 | np.random.seed(8675309) 9 | n = 100 10 | xp = 1.0 + 2.0*np.random.random(n) 11 | yp = 0.0 + ym*np.random.random(n) 12 | 13 | plt.plot(x,y, color='k') 14 | plt.plot(xp,yp, color='k', marker='+', linestyle='none') 15 | plt.plot([1,3],[ym,ym], color='k', linewidth=0.7) 16 | plt.plot([1,3],[0,0], color='k', linewidth=0.7) 17 | plt.plot([1,1],[0,ym], color='k', linewidth=0.7) 18 | plt.plot([3,3],[0,ym], color='k', linewidth=0.7) 19 | plt.xlim((0.9,3.1)) 20 | plt.xlabel("$x$") 21 | plt.ylabel("$f(x)$") 22 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 23 | plt.savefig('darts.eps', dpi=300) 24 | plt.show() 25 | 26 | -------------------------------------------------------------------------------- /ch15/monte.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: monte.py 3 | # 4 | # Monte Carlo integration 5 | # 6 | # RTK, 11-Nov-2023 7 | # Last update: 11-Nov-2023 8 | # 9 | ################################################################ 10 | 11 | import sys 12 | import numpy as np 13 | from math import sqrt,sin,cos,tan,log,exp 14 | 15 | if (len(sys.argv) == 1): 16 | print() 17 | print("monte ") 18 | print() 19 | print(" - the expression to integrate from to ") 20 | print(" - number of random samples to use") 21 | print() 22 | exit(0) 23 | 24 | expr = sys.argv[1] 25 | a,b = float(sys.argv[2]), float(sys.argv[3]) 26 | n = int(sys.argv[4]) 27 | 28 | samples = a + (b-a)*np.random.random(n) 29 | f = np.zeros(n) 30 | for i in range(n): 31 | x = samples[i] 32 | f[i] = eval(expr) 33 | 34 | area = (b-a)*f.mean() 35 | print("Area under the curve = %0.8f" % area) 36 | 37 | -------------------------------------------------------------------------------- /ch15/monte_vs_naive.py: -------------------------------------------------------------------------------- 1 | # Compare mean calculated area as a function of the number of samples 2 | import os 3 | import numpy as np 4 | 5 | def run(name, n): 6 | cmd = "python3 %s.py 'x**2' 10 12 %d >/tmp/tmp" % (name,n) 7 | os.system(cmd) 8 | return float(open("/tmp/tmp").read()[:-1].split()[-1]) 9 | 10 | M = 10 11 | N = [100,1000,5000,10_000,30_000,60_000,120_000,240_000,480_000,960_000] 12 | 13 | for n in N: 14 | a = [] 15 | for j in range(M): 16 | a.append(run("monte",n)) 17 | monte,mse = np.array(a).mean(), np.array(a).std(ddof=1) / np.sqrt(M) 18 | a = [] 19 | for j in range(M): 20 | a.append(run("naive",n)) 21 | naive,nse = np.array(a).mean(), np.array(a).std(ddof=1) / np.sqrt(M) 22 | print("%6d: %0.5f (%0.5f) %0.5f (%0.5f)" % (n,naive,nse,monte,mse)) 23 | 24 | -------------------------------------------------------------------------------- /ch15/simp.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: simp.py 3 | # 4 | # Numeric integration via Simpson's rule 5 | # 6 | # RTK, 12-Nov-2023 7 | # Last update: 12-Nov-2023 8 | # 9 | ################################################################ 10 | 11 | import sys 12 | from math import sqrt,sin,cos,tan,log,exp 13 | 14 | if (len(sys.argv) == 1): 15 | print() 16 | print("simp ") 17 | print() 18 | print(" - expression to integrate") 19 | print(" , - limits") 20 | print(" - number of trapezoids") 21 | print() 22 | exit(0) 23 | 24 | expr = sys.argv[1] 25 | a,b = float(sys.argv[2]), float(sys.argv[3]) 26 | n = int(sys.argv[4]) 27 | 28 | # n must be even 29 | if (n % 2): 30 | n += 1 31 | 32 | h = (b-a)/n # bin width 33 | 34 | # f(a) + f(b) 35 | x = a 36 | area = eval(expr) 37 | x = b 38 | area += eval(expr) 39 | 40 | for i in range(1,n): 41 | x = a + i*h 42 | if (i % 2): 43 | area += 4*eval(expr) 44 | else: 45 | area += 2*eval(expr) 46 | 47 | area *= h/3 48 | 49 | print("Area under the curve = %0.8f" % area) 50 | 51 | -------------------------------------------------------------------------------- /ch15/sym_def.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: sym_def.py 3 | # 4 | # Definite integrals using SymPy 5 | # 6 | # RTK, 12-Nov-2023 7 | # Last update: 12-Nov-2023 8 | # 9 | ################################################################ 10 | 11 | import sys 12 | from sympy import symbols, integrate 13 | from sympy import sqrt,sin,cos,tan,log,exp 14 | 15 | if (len(sys.argv) == 1): 16 | print() 17 | print("sym_def ") 18 | print() 19 | print(" - expression to integrate") 20 | print(" , - limits") 21 | print() 22 | exit(0) 23 | 24 | expr = sys.argv[1] 25 | a,b = float(sys.argv[2]), float(sys.argv[3]) 26 | 27 | x = symbols('x') 28 | f = eval(expr) 29 | 30 | anti = integrate(f,x) 31 | defi = anti.subs(x,b) - anti.subs(x,a) 32 | area = defi.evalf() 33 | 34 | print("Area under the curve = %0.8f" % area) 35 | 36 | -------------------------------------------------------------------------------- /ch15/sym_indef.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: sym_indef.py 3 | # 4 | # Indefinite integrals using SymPy 5 | # 6 | # RTK, 12-Nov-2023 7 | # Last update: 12-Nov-2023 8 | # 9 | ################################################################ 10 | 11 | import sys 12 | from sympy import symbols, integrate 13 | from sympy import sqrt,sin,cos,tan,log,exp 14 | 15 | if (len(sys.argv) == 1): 16 | print() 17 | print("sym_indef ") 18 | print() 19 | print(" - expression to integrate") 20 | print() 21 | exit(0) 22 | 23 | expr = sys.argv[1] 24 | x = symbols('x') 25 | f = eval(expr) 26 | 27 | anti = integrate(f,x) 28 | print("Indefinite integral is:\n f(x) =", anti, "+ C") 29 | 30 | -------------------------------------------------------------------------------- /ch15/trap.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: trap.py 3 | # 4 | # Numeric integration via the trapezoidal rule. 5 | # 6 | # RTK, 12-Nov-2023 7 | # Last update: 12-Nov-2023 8 | # 9 | ################################################################ 10 | 11 | import sys 12 | from math import sqrt,sin,cos,tan,log,exp 13 | 14 | if (len(sys.argv) == 1): 15 | print() 16 | print("trap ") 17 | print() 18 | print(" - expression to integrate") 19 | print(" , - limits") 20 | print(" - number of trapezoids") 21 | print() 22 | exit(0) 23 | 24 | expr = sys.argv[1] 25 | a,b = float(sys.argv[2]), float(sys.argv[3]) 26 | n = int(sys.argv[4]) 27 | 28 | h = (b-a)/n # trapezoid width 29 | area = 0.0 # area under the curve 30 | 31 | for i in range(n): 32 | x = a + i*h 33 | e0 = eval(expr) 34 | x = a + (i+1)*h 35 | e1 = eval(expr) 36 | area += 0.5*(e0 + e1) * h 37 | 38 | print("Area under the curve = %0.8f" % area) 39 | 40 | -------------------------------------------------------------------------------- /ch16/SIR.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: SIR.py 3 | # 4 | # The SIR model for epidemics 5 | # 6 | # RTK, 18-Nov-2023 7 | # Last update: 24-Nov-2023 8 | # 9 | ################################################################ 10 | 11 | import sys 12 | import numpy as np 13 | import matplotlib.pylab as plt 14 | 15 | def RK4(state, beta, gamma, N, h=1): 16 | """Runge-Kutta 4 step""" 17 | 18 | def derivatives(state, beta, gamma, N): 19 | S,I,R = state 20 | dSdt = -beta*S*I/N 21 | dIdt = beta*S*I/N - gamma*I 22 | dRdt = gamma*I 23 | return np.array([dSdt, dIdt, dRdt]) 24 | 25 | k1 = derivatives(state, beta, gamma, N) 26 | k2 = derivatives(state + 0.5*k1, beta, gamma, N) 27 | k3 = derivatives(state + 0.5*k2, beta, gamma, N) 28 | k4 = derivatives(state + k3, beta, gamma, N) 29 | return state + (h/6)*(k1 + 2*k2 + 2*k3 + k4) 30 | 31 | 32 | if (len(sys.argv) == 1): 33 | print() 34 | print("SIR []") 35 | print() 36 | print(" - SIR beta parameter (e.g. 0.25)") 37 | print(" - SIR gamma parameter (e.g. 0.15)") 38 | print(" - fraction initially susceptible, e.g. 0.95") 39 | print(" - fraction initially infected, e.g. 0.05") 40 | print(" - population size") 41 | print(" - number of timesteps (e.g. 100 days)") 42 | print(" - if present, suppress plot and stores rates in a NumPy file") 43 | print() 44 | exit(0) 45 | 46 | beta = float(sys.argv[1]) 47 | gamma = float(sys.argv[2]) 48 | fS = float(sys.argv[3]) 49 | fI = float(sys.argv[4]) 50 | N = int(sys.argv[5]) 51 | ts = int(sys.argv[6]) 52 | 53 | # initial state 54 | S0 = int(fS*N) 55 | I0 = int(fI*N) 56 | R0 = 0 57 | state = np.array([S0,I0,R0]) 58 | S,I,R,t = [S0], [I0], [R0], [0] 59 | 60 | for i in range(ts): 61 | state = RK4(state, beta, gamma, N) 62 | S.append(state[0]) 63 | I.append(state[1]) 64 | R.append(state[2]) 65 | t.append(t[-1] + 1) 66 | 67 | if (len(sys.argv) == 7): 68 | plt.plot(t,S, color='k', linestyle='solid', label='susceptible') 69 | plt.plot(t,I, color='k', linestyle='dashed', label='infected') 70 | plt.plot(t,R, color='k', linestyle='dotted', label='recovered') 71 | plt.legend(loc='best') 72 | plt.xlabel("Time") 73 | plt.ylabel("People") 74 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 75 | plt.show() 76 | else: 77 | d = np.zeros((len(t),4)) 78 | d[:,0] = t 79 | d[:,1] = S 80 | d[:,2] = I 81 | d[:,3] = R 82 | np.save(sys.argv[7], d) 83 | 84 | -------------------------------------------------------------------------------- /ch16/SIRD.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: SIRD.py 3 | # 4 | # The SIRD model for epidemics 5 | # 6 | # RTK, 18-Nov-2023 7 | # Last update: 24-Nov-2023 8 | # 9 | ################################################################ 10 | 11 | import sys 12 | import numpy as np 13 | import matplotlib.pylab as plt 14 | 15 | def RK4(state, beta, gamma, mu, N, h=1): 16 | """Runge-Kutta 4 step""" 17 | 18 | def derivatives(state, beta, gamma, mu, N): 19 | S,I,R,D = state 20 | dSdt = -beta*S*I/N 21 | dIdt = beta*S*I/N - gamma*I - mu*I 22 | dRdt = gamma*I 23 | dDdt = mu*I 24 | return np.array([dSdt, dIdt, dRdt, dDdt]) 25 | 26 | k1 = derivatives(state, beta, gamma, mu, N) 27 | k2 = derivatives(state + 0.5*k1, beta, gamma, mu, N) 28 | k3 = derivatives(state + 0.5*k2, beta, gamma, mu, N) 29 | k4 = derivatives(state + k3, beta, gamma, mu, N) 30 | return state + (h/6)*(k1 + 2*k2 + 2*k3 + k4) 31 | 32 | 33 | if (len(sys.argv) == 1): 34 | print() 35 | print("SIRD []") 36 | print() 37 | print(" - SIRD beta parameter (e.g. 0.25)") 38 | print(" - SIRD gamma parameter (e.g. 0.15)") 39 | print(" - SIRD mu parameter (e.g. 0.025)") 40 | print(" - fraction initially susceptible, e.g. 0.95") 41 | print(" - fraction initially infected, e.g. 0.05") 42 | print(" - population size") 43 | print(" - number of timesteps (e.g. 100 days)") 44 | print(" - if present, suppress plot and stores rates in a NumPy file") 45 | print() 46 | exit(0) 47 | 48 | beta = float(sys.argv[1]) 49 | gamma = float(sys.argv[2]) 50 | mu = float(sys.argv[3]) 51 | fS = float(sys.argv[4]) 52 | fI = float(sys.argv[5]) 53 | N = int(sys.argv[6]) 54 | ts = int(sys.argv[7]) 55 | 56 | # initial state 57 | S0 = int(fS*N) 58 | I0 = int(fI*N) 59 | R0 = 0 60 | D0 = 0 61 | state = np.array([S0,I0,R0,D0]) 62 | S,I,R,D,t = [S0], [I0], [R0], [D0], [0] 63 | 64 | for i in range(ts): 65 | state = RK4(state, beta, gamma, mu, N) 66 | S.append(state[0]) 67 | I.append(state[1]) 68 | R.append(state[2]) 69 | D.append(state[3]) 70 | t.append(t[-1] + 1) 71 | 72 | if (len(sys.argv) == 8): 73 | plt.plot(t,S, color='k', linestyle='solid', label='susceptible') 74 | plt.plot(t,I, color='k', linestyle='dashed', label='infected') 75 | plt.plot(t,R, color='k', linestyle='dotted', label='recovered') 76 | plt.plot(t,D, color='k', linestyle='dashdot', label='deceased') 77 | plt.legend(loc='best') 78 | plt.xlabel("Time") 79 | plt.ylabel("People") 80 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 81 | plt.savefig("SIRD_highly_fatal.eps", dpi=300) 82 | plt.show() 83 | else: 84 | d = np.zeros((len(t),5)) 85 | d[:,0] = t 86 | d[:,1] = S 87 | d[:,2] = I 88 | d[:,3] = R 89 | d[:,4] = D 90 | np.save(sys.argv[8], d) 91 | 92 | -------------------------------------------------------------------------------- /ch16/SIR_plot.py: -------------------------------------------------------------------------------- 1 | # Plot SIR results for several diseases 2 | import os 3 | import numpy as np 4 | import matplotlib.pylab as plt 5 | 6 | # Measles 7 | os.system("python3 SIR.py 1.25 0.0833 0.98 0.02 10000 100 /tmp/ttt.npy") 8 | measles = np.load("/tmp/ttt.npy") 9 | 10 | # Rubella 11 | os.system("python3 SIR.py 0.46 0.0714 0.98 0.02 10000 100 /tmp/ttt.npy") 12 | rubella = np.load("/tmp/ttt.npy") 13 | 14 | # COVID 15 | os.system("python3 SIR.py 0.28 0.1111 0.98 0.02 10000 100 /tmp/ttt.npy") 16 | covid = np.load("/tmp/ttt.npy") 17 | 18 | # S plot 19 | plt.plot(measles[:,0], measles[:,1], color='k', linestyle='solid', label='Measles') 20 | plt.plot(rubella[:,0], rubella[:,1], color='k', linestyle='dashed', label='Rubella') 21 | plt.plot(covid[:,0], covid[:,1], color='k', linestyle='dotted', label='COVID-19') 22 | plt.xlabel("Days") 23 | plt.ylabel("People") 24 | plt.title("Susceptible") 25 | plt.legend(loc='best') 26 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 27 | plt.savefig('SIR_plot_S.eps', dpi=300) 28 | plt.show() 29 | 30 | # I plot 31 | plt.plot(measles[:,0], measles[:,2], color='k', linestyle='solid', label='Measles') 32 | plt.plot(rubella[:,0], rubella[:,2], color='k', linestyle='dashed', label='Rubella') 33 | plt.plot(covid[:,0], covid[:,2], color='k', linestyle='dotted', label='COVID-19') 34 | plt.xlabel("Days") 35 | plt.ylabel("People") 36 | plt.title("Infected") 37 | plt.legend(loc='best') 38 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 39 | plt.savefig('SIR_plot_I.eps', dpi=300) 40 | plt.show() 41 | 42 | # R plot 43 | plt.plot(measles[:,0], measles[:,3], color='k', linestyle='solid', label='Measles') 44 | plt.plot(rubella[:,0], rubella[:,3], color='k', linestyle='dashed', label='Rubella') 45 | plt.plot(covid[:,0], covid[:,3], color='k', linestyle='dotted', label='COVID-19') 46 | plt.xlabel("Days") 47 | plt.ylabel("People") 48 | plt.title("Recovered") 49 | plt.legend(loc='best') 50 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 51 | plt.savefig('SIR_plot_R.eps', dpi=300) 52 | plt.show() 53 | 54 | -------------------------------------------------------------------------------- /ch16/color_map_names.txt: -------------------------------------------------------------------------------- 1 | viridis 2 | plasma 3 | inferno 4 | magma 5 | cividis 6 | Greys 7 | Purples 8 | Blues 9 | Greens 10 | Oranges 11 | Reds 12 | YlOrBr 13 | YlOrRd 14 | OrRd 15 | PuRd 16 | RdPu 17 | BuPu 18 | GnBu 19 | PuBu 20 | YlGnBu 21 | PuBuGn 22 | BuGn 23 | YlGn 24 | binary 25 | gist_yarg 26 | gist_gray 27 | gray 28 | bone 29 | pink 30 | spring 31 | summer 32 | autumn 33 | winter 34 | cool 35 | Wistia 36 | hot 37 | afmhot 38 | gist_heat 39 | copper 40 | PiYG 41 | PRGn 42 | BrBG 43 | PuOr 44 | RdGy 45 | RdBu 46 | RdYlBu 47 | RdYlGn 48 | Spectral 49 | coolwarm 50 | bwr 51 | seismic 52 | twilight 53 | twilight_shifted 54 | hsv 55 | Pastel1 56 | Pastel2 57 | Paired 58 | Accent 59 | Dark2 60 | Set1 61 | Set2 62 | Set3 63 | tab10 64 | tab20 65 | tab20b 66 | tab20c 67 | flag 68 | prism 69 | ocean 70 | gist_earth 71 | terrain 72 | gist_stern 73 | gnuplot 74 | gnuplot2 75 | CMRmap 76 | cubehelix 77 | brg 78 | gist_rainbow 79 | rainbow 80 | jet 81 | turbo 82 | nipy_spectral 83 | gist_ncar 84 | -------------------------------------------------------------------------------- /ch16/euler.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | 4 | def pendulum_euler(theta0, omega0, length, g=9.81, dt=0.01, T=10): 5 | """ 6 | Simulate a nonlinear pendulum using the Euler method. 7 | 8 | Parameters: 9 | theta0 : float 10 | Initial angle (in radians). 11 | omega0 : float 12 | Initial angular velocity (in radians per second). 13 | length : float 14 | Length of the pendulum (in meters). 15 | g : float, optional 16 | Acceleration due to gravity (in meters per second squared). 17 | dt : float, optional 18 | Time step for the Euler method (in seconds). 19 | T : float, optional 20 | Total time of simulation (in seconds). 21 | 22 | Returns: 23 | t : ndarray 24 | Array of time points. 25 | theta : ndarray 26 | Array of angles over time. 27 | omega : ndarray 28 | Array of angular velocities over time. 29 | """ 30 | 31 | # Number of steps 32 | N = int(T / dt) 33 | 34 | # Arrays to store time, angle, and angular velocity 35 | t = np.linspace(0, T, N) 36 | theta = np.zeros(N) 37 | omega = np.zeros(N) 38 | 39 | # Initial conditions 40 | theta[0] = theta0 41 | omega[0] = omega0 42 | 43 | # Euler's method 44 | for i in range(N - 1): 45 | theta[i + 1] = theta[i] + omega[i] * dt 46 | omega[i + 1] = omega[i] - (g / length) * np.sin(theta[i]) * dt 47 | 48 | return t, theta, omega 49 | 50 | # Example usage 51 | theta0 = np.pi / 4 # Initial angle of 45 degrees 52 | omega0 = 0.0 # Initial angular velocity 53 | length = 1.0 # Length of the pendulum in meters 54 | 55 | t, theta, omega = pendulum_euler(theta0, omega0, length) 56 | 57 | # Plotting 58 | plt.figure(figsize=(12, 6)) 59 | plt.subplot(2, 1, 1) 60 | plt.plot(t, theta) 61 | plt.title('Pendulum Angle Over Time') 62 | plt.ylabel('Angle (rad)') 63 | 64 | plt.subplot(2, 1, 2) 65 | plt.plot(t, omega) 66 | plt.title('Pendulum Angular Velocity Over Time') 67 | plt.ylabel('Angular Velocity (rad/s)') 68 | plt.xlabel('Time (s)') 69 | plt.show() 70 | 71 | -------------------------------------------------------------------------------- /ch16/lorenz.py: -------------------------------------------------------------------------------- 1 | # The Lorenz attractor 2 | import sys 3 | import numpy as np 4 | import matplotlib.pylab as plt 5 | from matplotlib import cm 6 | 7 | # Lorenz's original values: 8 | s,r,b = 10, 28, 8/3 9 | 10 | def derivatives(p): 11 | """Calculate the derivatives""" 12 | x,y,z = p 13 | xd = s*(y-x) 14 | yd = r*x - y - x*z 15 | zd = x*y - b*z 16 | return np.array([xd,yd,zd]) 17 | 18 | def rk4(p,h): 19 | """RK4 update""" 20 | k1 = derivatives(p) 21 | k2 = derivatives(p + 0.5*k1*h) 22 | k3 = derivatives(p + 0.5*k2*h) 23 | k4 = derivatives(p + k3*h) 24 | return (h/6)*(k1 + 2*k2 + 2*k3 + k4) 25 | 26 | if (len(sys.argv) == 1): 27 | print() 28 | print("lorenz
") 29 | print() 30 | print(" x0,y0,z0 - initial position, no spaces (e.g. 0.0,1.05,1.0)") 31 | print(" - number of steps (e.g. 25000)") 32 | print(" - timestep (e.g. 0.0025)") 33 | print(" - Matplotlib color map name (e.g. 'inferno')") 34 | print(" - euler|rk4 (numeric mode)") 35 | print() 36 | exit(0) 37 | 38 | x0,y0,z0 = [float(i) for i in sys.argv[1].split(",")] 39 | n, h = int(sys.argv[2]), float(sys.argv[3]) 40 | cmap = cm.get_cmap(sys.argv[4]) 41 | mode = sys.argv[5].lower() 42 | 43 | # points and initial position 44 | p = np.zeros((n,3)) 45 | c = [cmap(0)] 46 | p[0] = (x0, y0, z0) 47 | 48 | # run the simulation 49 | for i in range(1,n): 50 | if (mode == 'euler'): 51 | p[i] = p[i-1] + derivatives(p[i-1]) * h 52 | else: 53 | p[i] = p[i-1] + rk4(p[i-1],h) 54 | c.append(cmap(int(256*i/n))) 55 | 56 | # Print final 10 trajectory points 57 | print(p[-10:,:]) 58 | 59 | # Plot the attractor 60 | ax = plt.figure().add_subplot(projection='3d') 61 | ax.scatter(p[:,0],p[:,1],p[:,2], c=c, marker='.', s=1) 62 | ax.set_xlabel("$x$") 63 | ax.set_ylabel("$y$") 64 | ax.set_zlabel("$z$") 65 | plt.show() 66 | 67 | -------------------------------------------------------------------------------- /ch16/numeric_example.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: numeric_example.py 3 | # 4 | # Compare solutions to y' = -2y, y(0)=3 ==> y(t) = 3e^{-2t} 5 | # using the exact solution, Euler approximation and 6 | # Runge-Kutta 4 approximation 7 | # 8 | # RTK, 17-Nov-2023 9 | # Last update: 18-Nov-2023 10 | # 11 | ################################################################ 12 | 13 | import sys 14 | from math import exp 15 | import matplotlib.pylab as plt 16 | 17 | def dydt(y): 18 | return -2.0*y 19 | 20 | def Euler(y,h): 21 | return y + dydt(y) * h 22 | 23 | def RK4(y,h): 24 | k1 = dydt(y) 25 | k2 = dydt(y + 0.5*k1*h) 26 | k3 = dydt(y + 0.5*k2*h) 27 | k4 = dydt(y + k3*h) 28 | return y + (h/6)*(k1 + 2*k2 + 2*k3 + k4) 29 | 30 | 31 | if (len(sys.argv) == 1 ): 32 | print() 33 | print("numeric_example euler|rk4") 34 | print() 35 | print(" - timestep (e.g. 0.1)") 36 | print(" euler|rk4 - which technique to use") 37 | print() 38 | exit(0) 39 | 40 | h = float(sys.argv[1]) 41 | mode = sys.argv[2].lower() 42 | 43 | # initial conditions 44 | y = [3.0] 45 | t = [0.0] 46 | 47 | # iterate 48 | while (t[-1] < 4.0): 49 | t.append(t[-1] + h) 50 | if (mode == 'euler'): 51 | y.append(Euler(y[-1], h)) 52 | else: 53 | y.append(RK4(y[-1], h)) 54 | 55 | # exact solution 56 | et = [t[-1]*(i/500) for i in range(500)] 57 | ex = [3.0*exp(-2.0*i) for i in et] 58 | 59 | plt.plot(et, ex, color='k', label='exact') 60 | if (mode == 'euler'): 61 | plt.plot(t, y, color='k', marker='s', fillstyle='none', linestyle='none', label='Euler') 62 | else: 63 | plt.plot(t, y, color='k', marker='o', fillstyle='none', linestyle='none', label='RK4') 64 | plt.legend(loc='upper right') 65 | plt.xlabel("$x$") 66 | plt.ylabel("$y$") 67 | plt.tight_layout(pad=0, h_pad=0, w_pad=0) 68 | plt.show() 69 | 70 | -------------------------------------------------------------------------------- /ch16/pendulum.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: pendulum.py 3 | # 4 | # Simulate a simple pendulum by transforming the second-order 5 | # equation of motion into a system of first-order equations. 6 | # 7 | # RTK, 17-Nov-2023 8 | # Last update: 20-Nov-2023 9 | # 10 | ################################################################ 11 | 12 | import sys 13 | from math import sqrt, sin, cos 14 | import matplotlib.pylab as plt 15 | 16 | def SmallAngle(t): 17 | """Exact solution using the small angle approximation""" 18 | return theta0*cos(sqrt(g/length)*t) 19 | 20 | # Use RK4 for each equation in the system 21 | def dtheta(omega): 22 | """dtheta/dt = omega""" 23 | return omega 24 | 25 | def domega(theta): 26 | """domega/dt = -(g/l)*sin(theta)""" 27 | return -(g/length)*sin(theta) 28 | 29 | def Euler(theta, omega, h): 30 | theta_new = theta + dtheta(omega) * h 31 | omega_new = omega + domega(theta) * h 32 | 33 | return theta_new, omega_new 34 | 35 | def RK4(theta, omega, h): 36 | k1_theta = dtheta(omega) 37 | k1_omega = domega(theta) 38 | 39 | k2_theta = dtheta(omega + 0.5*h*k1_omega) 40 | k2_omega = domega(theta + 0.5*h*k1_theta) 41 | 42 | k3_theta = dtheta(omega + 0.5*h*k2_omega) 43 | k3_omega = domega(theta + 0.5*h*k2_theta) 44 | 45 | k4_theta = dtheta(omega + h*k3_omega) 46 | k4_omega = domega(theta + h*k3_theta) 47 | 48 | theta_new = theta + (h/6)*(k1_theta + 2*k2_theta + 2*k3_theta + k4_theta) 49 | omega_new = omega + (h/6)*(k1_omega + 2*k2_omega + 2*k3_omega + k4_omega) 50 | 51 | return theta_new, omega_new 52 | 53 | 54 | if (len(sys.argv) == 1): 55 | print() 56 | print("pendulum []") 57 | print() 58 | print(" - euler|rk4") 59 | print(" - pendulum length (m)") 60 | print(" - initial angle (degrees)") 61 | print(" - ending time (s)") 62 | print(" - delta-t time (step size)") 63 | print(" - output file to store theta over time (optional)") 64 | print() 65 | exit(0) 66 | 67 | mode = sys.argv[1].lower() 68 | length = float(sys.argv[2]) 69 | theta0 = float(sys.argv[3]) * (3.14150265/180) # to radians 70 | t1 = float(sys.argv[4]) 71 | h = float(sys.argv[5]) 72 | 73 | # initial conditions 74 | g = 9.81 # m/s^2 75 | theta, omega, t0 = theta0, 0.0, 0.0 # omega0 = 0, release from rest 76 | thetas, omegas, times = [theta], [omega], [t0] 77 | small = [theta] 78 | 79 | # run the simulation 80 | while (times[-1] <= t1): 81 | if (mode == 'euler'): 82 | theta, omega = Euler(theta, omega, h) 83 | else: 84 | theta, omega = RK4(theta, omega, h) 85 | thetas.append(theta) 86 | omegas.append(omega) 87 | small.append(SmallAngle(times[-1])) 88 | times.append(times[-1] + h) 89 | 90 | # save, if requested 91 | if (len(sys.argv) == 7): 92 | import numpy as np 93 | xy = np.zeros((len(times),2)) 94 | xy[:,0] = times 95 | xy[:,1] = thetas 96 | np.save(sys.argv[6], xy) 97 | 98 | # plot the results showing position and velocity over time 99 | plt.figure(figsize=(12,6)) 100 | plt.subplot(2,1,1) 101 | plt.plot(times, thetas, color='k', label='Euler' if (mode=='euler') else 'RK4') 102 | plt.plot(times, small, color='k', linestyle='dashed', label='Small') 103 | plt.title("Angular Position") 104 | plt.legend(loc='best') 105 | plt.ylabel("Angle (rad)") 106 | plt.subplot(2,1,2) 107 | plt.plot(times, omegas, color='k') 108 | plt.title("Angular Velocity") 109 | plt.xlabel("Time (s)") 110 | plt.ylabel("Angular Velocity (rad/s)") 111 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 112 | plt.show() 113 | 114 | -------------------------------------------------------------------------------- /ch16/pendulum_damped.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: pendulum_damped.py 3 | # 4 | # Simulate a simple pendulum by transforming the second-order 5 | # equation of motion into a system of first-order equations. 6 | # 7 | # This version include a damping term to represent friction 8 | # and air resistance. 9 | # 10 | # RTK, 17-Nov-2023 11 | # Last update: 17-Nov-2023 12 | # 13 | ################################################################ 14 | 15 | import sys 16 | from math import sqrt, sin, cos 17 | import matplotlib.pylab as plt 18 | 19 | # Use RK4 for each equation in the system 20 | def dtheta(omega): 21 | """dtheta/dt = omega""" 22 | return omega 23 | 24 | def domega(theta, omega): 25 | """domega/dt = -gamma*omega -(g/l)*sin(theta)""" 26 | return -gamma*omega - (g/length)*sin(theta) 27 | 28 | def RK4(theta, omega, h): 29 | """A single time step for both coupled equations""" 30 | 31 | k1_theta = dtheta(omega) 32 | k1_omega = domega(theta, omega) 33 | 34 | k2_theta = dtheta(omega + 0.5*h*k1_omega) 35 | k2_omega = domega(theta + 0.5*h*k1_theta, omega + 0.5*h*k1_omega) 36 | 37 | k3_theta = dtheta(omega + 0.5*h*k2_omega) 38 | k3_omega = domega(theta + 0.5*h*k2_theta, omega + 0.5*h*k2_omega) 39 | 40 | k4_theta = dtheta(omega + h*k3_omega) 41 | k4_omega = domega(theta + h*k3_theta, omega + h*k3_omega) 42 | 43 | theta_new = theta + (h/6)*(k1_theta + 2*k2_theta + 2*k3_theta + k4_theta) 44 | omega_new = omega + (h/6)*(k1_omega + 2*k2_omega + 2*k3_omega + k4_omega) 45 | 46 | return theta_new, omega_new 47 | 48 | 49 | if (len(sys.argv) == 1): 50 | print() 51 | print("pendulum_damped []") 52 | print() 53 | print(" - pendulum length (m)") 54 | print(" - initial angle (degrees)") 55 | print(" - damping coefficient (e.g. 0.1-1)") 56 | print(" - ending time (s)") 57 | print(" - delta-t time (step size)") 58 | print(" - output file to store theta over time (optional)") 59 | print() 60 | exit(0) 61 | 62 | length = float(sys.argv[1]) 63 | theta0 = float(sys.argv[2]) * (3.14150265/180) # to radians 64 | gamma = float(sys.argv[3]) 65 | t1 = float(sys.argv[4]) 66 | h = float(sys.argv[5]) 67 | 68 | # initial conditions 69 | g = 9.81 # m/s^2 70 | theta, omega, t0 = theta0, 0.0, 0.0 # omega0 = 0, release from rest 71 | thetas, omegas, times = [theta], [omega], [t0] 72 | 73 | # run the simulation 74 | while (times[-1] <= t1): 75 | theta, omega = RK4(theta, omega, h) 76 | thetas.append(theta) 77 | omegas.append(omega) 78 | times.append(times[-1] + h) 79 | 80 | # save, if requested 81 | if (len(sys.argv) == 7): 82 | import numpy as np 83 | xy = np.zeros((len(times),2)) 84 | xy[:,0] = times 85 | xy[:,1] = thetas 86 | np.save(sys.argv[6], xy) 87 | 88 | # plot the results showing position and velocity over time 89 | plt.figure(figsize=(12,6)) 90 | plt.subplot(2,1,1) 91 | plt.plot(times, thetas, color='k') 92 | plt.title("Angular Position") 93 | plt.ylabel("Angle (rad)") 94 | plt.subplot(2,1,2) 95 | plt.plot(times, omegas, color='k') 96 | plt.title("Angular Velocity") 97 | plt.xlabel("Time (s)") 98 | plt.ylabel("Angular Velocity (rad/s)") 99 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 100 | plt.show() 101 | 102 | -------------------------------------------------------------------------------- /ch16/projectile.py: -------------------------------------------------------------------------------- 1 | # 2 | # file: projectile.py 3 | # 4 | # Projectile motion with air resistance 5 | # 6 | # RTK, 17-Nov-2023 7 | # Last update: 22-Nov-2023 8 | # 9 | ################################################################ 10 | 11 | import sys 12 | import numpy as np 13 | import matplotlib.pylab as plt 14 | 15 | def RK4(state, h, k): 16 | """Runge-Kutta 4 step""" 17 | 18 | def derivatives(state, k): 19 | x, y, vx, vy = state 20 | dxdt = vx 21 | dydt = vy 22 | vmag = np.sqrt(vx**2 + vy**2) 23 | dvxdt = -k*vmag*vx 24 | dvydt = -9.81 - k*vmag*vy 25 | return np.array([dxdt, dydt, dvxdt, dvydt]) 26 | 27 | k1 = derivatives(state, k) 28 | k2 = derivatives(state + 0.5*k1*h, k) 29 | k3 = derivatives(state + 0.5*k2*h, k) 30 | k4 = derivatives(state + k3*h, k) 31 | return state + (h/6)*(k1 + 2*k2 + 2*k3 + k4) 32 | 33 | 34 | if (len(sys.argv) == 1): 35 | print() 36 | print("projectile ") 37 | print() 38 | print(" - launch angle relative to x-axis (degrees)") 39 | print(" - muzzle velocity (e.g. 100 m/s)") 40 | print(" - k drag coefficient (e.g. 0.01 for 10 cm cannonball)") 41 | print(" - time step (e.g. 0.05 s)") 42 | print() 43 | exit(0) 44 | 45 | vangle = float(sys.argv[1]) * (np.pi/180) # radians 46 | vmag = float(sys.argv[2]) 47 | k = float(sys.argv[3]) 48 | h = float(sys.argv[4]) 49 | 50 | # initial state 51 | vx = vmag*np.cos(vangle) 52 | vy = vmag*np.sin(vangle) 53 | state = np.array([0.0, 0.0, vx, vy]) 54 | 55 | x,y = [state[0]],[state[1]] 56 | 57 | while (True): 58 | state = RK4(state, h, k) 59 | if (state[1] <= 0): 60 | print("Hit the ground (x = %0.2f meters)" % state[0]) 61 | break 62 | x.append(state[0]) 63 | y.append(state[1]) 64 | 65 | if (len(sys.argv) == 5): 66 | plt.plot(x,y, marker='+', linestyle='none', color='k') 67 | plt.xlabel("Range (m)") 68 | plt.ylabel("Altitude (m)") 69 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 70 | plt.show() 71 | 72 | -------------------------------------------------------------------------------- /ch16/projectile_range.py: -------------------------------------------------------------------------------- 1 | # Search for the angle leading to maximum range 2 | import numpy as np 3 | import sys 4 | import os 5 | 6 | if (len(sys.argv) == 1): 7 | print() 8 | print("projectile_range ") 9 | print() 10 | print(" - muzzle velocity (m/s)") 11 | print(" - drag coefficient") 12 | print() 13 | exit(0) 14 | 15 | muzzle = float(sys.argv[1]) 16 | k = float(sys.argv[2]) 17 | 18 | mag, rnge = 0, -1 19 | for angle in np.linspace(1,50,99): 20 | cmd = "python3 projectile.py " + str(angle) + " " + str(muzzle) + " " + str(k) + " 0.001 no >/tmp/ttt" 21 | os.system(cmd) 22 | t = open("/tmp/ttt").read()[:-1] 23 | r = float(t.split()[-2]) 24 | if (r > rnge): 25 | rnge = r 26 | mag = angle 27 | 28 | print("Maximum range of %0.2f meters at %0.1f degrees" % (rnge, mag)) 29 | 30 | 31 | -------------------------------------------------------------------------------- /tutorial.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkneusel9/MathForProgramming/ae6d990fa612dff8b39e6806923deae566c0ff1d/tutorial.pdf -------------------------------------------------------------------------------- /video/AND.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkneusel9/MathForProgramming/ae6d990fa612dff8b39e6806923deae566c0ff1d/video/AND.mp4 -------------------------------------------------------------------------------- /video/NOT.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkneusel9/MathForProgramming/ae6d990fa612dff8b39e6806923deae566c0ff1d/video/NOT.mp4 -------------------------------------------------------------------------------- /video/OR.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkneusel9/MathForProgramming/ae6d990fa612dff8b39e6806923deae566c0ff1d/video/OR.mp4 -------------------------------------------------------------------------------- /video/README.txt: -------------------------------------------------------------------------------- 1 | Transistor AND, OR, and NOT gate videos 2 | --------------------------------------- 3 | 4 | Each video shows a breadboard implementation of the corresponding logic 5 | gate. The gates implement the circuits in Chapter 3 of Math for Programming. 6 | 7 | The resistors are 8 | 9 | R1 = 5 k ohm (I used 5.1 k in the videos) 10 | R2 = 10 k ohm 11 | 12 | R1 is there to drop the voltage on the common when the transistor is on. 13 | R2 limits the current through the transistor when there's an input on the base. 14 | 15 | The transistors themselves are NPN. I used decades old 2N3904, which are 16 | still available from Jameco for about $0.25 each. I suspect any small 17 | signal transistor will work, but make sure it's NPN. The circuit for a 18 | PNP transistor is different. 19 | 20 | I connected push button switches from the 5V source to the transistor bases to act 21 | as inputs. Switch closed is 1, open is 0. 22 | 23 | I ran the outputs through a 1 k ohm resistor followed by an LED to ground. When 24 | the LED is on, the output is 1, otherwise the output is 0. 25 | 26 | For the videos, I used the 5V output on my benchtop power supply. If you want to 27 | use a battery, I recommend a 9V battery running through a 5V voltage regulator. 28 | 29 | Jameco electronics has everything necessary for about $20, see the parts list 30 | in jameco1.png, jameco2.png, and jameco3.png. 31 | 32 | -------------------------------------------------------------------------------- /video/jameco1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkneusel9/MathForProgramming/ae6d990fa612dff8b39e6806923deae566c0ff1d/video/jameco1.png -------------------------------------------------------------------------------- /video/jameco2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkneusel9/MathForProgramming/ae6d990fa612dff8b39e6806923deae566c0ff1d/video/jameco2.png -------------------------------------------------------------------------------- /video/jameco3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkneusel9/MathForProgramming/ae6d990fa612dff8b39e6806923deae566c0ff1d/video/jameco3.png --------------------------------------------------------------------------------