This tells you that it took about $34 \\mathrm{ms}$ to add two (random) matrices of size $(100, 100)$.>>> %timeit add_matrix(random_matrix(100), random_matrix(100)) 10 loops, best of 3: 33.7 ms per loop
This second example tells you that it took about $3.42 \\mathrm{s}$ to add two (random) matrices of size $(1000, 1000)$. Our addition functon is in $O(n^2)$, so going from $n=100$ to $n=1000$ corresponds to multiply $n^2$ by $10^2 = 100$. Therefore, we can be proud to see that the computation time has also been multiplied by $100$ ! (indeed $3.4 \\mathrm{s} = 100 \\times 34 \\mathrm{ms} \\dots$). > You can do more examples like this if you want to practice, or observe the same effect for other values (try to go from $n=500$ to $n=1000$, it will also take a time $4$ times greater). > It took 1.06 seconds for $n=500$ on my laptop, and 4.28 seconds for $n=1000$ : nice right? --- #### Examples with `product_matrix`>>> %timeit add_matrix(random_matrix(1000), random_matrix(1000)) 1 loops, best of 3: 3.42 s per loop
This tells you that it took about $265 \\mathrm{ms}$ to multiply two (random) matrices of size $(100, 100)$. For this `product\_matrix` function, you now need to figure out **by yourself** what is its order of complexity: - You can first do this *analytically*, based either on the mathematical formula for the product (given above), or the Python code of your functions (count the number and sizes of interlaced loops). - And then verify *concretely* (with this `%timeit` command, as for `add\_matrix`) that the order of time complexity you found is correct\footnote{It will probably be one of $O(1)$, $O(n)$, $O(n \\log(n))$, $O(n^2)$, $O(n^2 \\log(n))$, $O(n^3)$ or $O(n^4)$ $\\dots$}. - You can take three values for $n$ (e.g. $100, 200$ and $400$), and try to see how the computation time evolves. ----- #### Solution>>> %timeit product_matrix(random_matrix(100), random_matrix(100)) 1 loops, best of 3: 263 ms per loop
This second example tells you that it took about $2.1 s$ to multiply two (random) matrices of size $(200, 200)$. Our multiplication function is in $O(n^3)$, so going from $n=100$ to $n=200$ corresponds to multiply $n^2$ by $2^3 = 8$. Therefore, we can be proud to see that the computation time has also been multiplied by $8$ ! (indeed $2.1 s = 8 \\times 263 ms \\dots$). > You can do more examples like this if you want to practice, or observe the same effect for other values (try to go from $n=200$ to $n=400$, it should also take a time $8$ times greater). > It took 39 seconds for $n=500$ on my laptop, and 5 minutes 24 seconds for $n=1000$ : nice right? ------ ### Bonus (if you are *really in advance*) about **matrix exponentation** 1. _Bonus:_ after writing the product of two matrices (Problem 9 Question 2), write a small function `square_matrix` that will raise a matrix $M$ to the power $2$ (ie. computes $M^2 = M \\times M$), 2. _Bonus:_ write a function `pow_matrix` that raises its first argument $M$ to the power of any non-negative integer, $k$ (its second argument). Calling this function with `pow_matrix(M, k)` will compute (and _return_) $M^k$. 3. _Bonus:_ what is the time complexity of this function `pow_matrix`? (in term of size $n$ of $M$ and in term of $k$). Can you improve it? _Hint:_ remember the recursive algorithm for quick exponentation saw in Prof. Arya's lecture? You could adapt it here ($M^{2k} = {(M^k)}^{2}$ and $M^{2k+1} = M {(M^k)}^{2}$). --- ## Example programs > All this will also be on Moodle by next Friday, along with the solution for lab #9. #### For the problem 9.1>>> %timeit product_matrix(random_matrix(200), random_matrix(200)) 1 loops, best of 3: 2.1 s per loop
We can do this in one line also! We use a **lambda** statement, with two arguments, and two **list comprehension** :def add_matrix(A, B): """ Add two square matrices A and B (of the same size). There is O(n^2) elementary additions, and O(n^2) memory accesses (then all take a worst case time of O(n) because all the lists are of size n). The function uses an extra matrix C of size O(n^2). """ n = len(A) # We create an empty matrix of size (n, n), filled with zero C = [ [0]\*n ]\*n # We assume that A and B are square matrices for i in xrange(n): for j in xrange(n): C[i][j] = A[i][j] + B[i][j] # Finally this matrix C is returned return C
#### For the problem 9.2add_matrix = lambda A, B: [ [ A[i][j] + B[i][j] for j in xrange(len(A)) ] for i in xrange(len(A)) ]
We can again do this in one line also:def product_matrix(A, B): """ Multiply two square matrices A and B (of the same size). There is O(n^2) additions and O(n^3) multiplications, and O(n^3) memory accesses (then all take a worst case time of O(n) because all the lists are of size n) The function uses an extra matrix C of size O(n^2). """ n = len(A) # We create an empty matrix of size (n, n), filled with zero C = [ [0]\*n ]\*n # We assume that A and B are square matrices for i in xrange(n): for j in xrange(n): C[i][j] = sum([ A[i][k] * B[k][j] for k in xrange(n) ]) # We use the builtin sum function, with a list comprehension to avoid a 3rd loop # Finally this matrix C is returned return C
#### For the problem 9.4product_matrix = lambda A, B: [ [ sum([ A[i][k] * B[k][j] for k in xrange(len(A)) ]) for j in xrange(len(A)) ] for i in xrange(len(A)) ]
We can again do this in two lines also:import random def random_matrix(n): """ Random matrix of size (n, n) filled with integers taken in [-10, 10]. """ # We create an empty matrix of size (n, n), filled with zero C = [ [0]\*n ]\*n for i in xrange(n): for j in xrange(n): C[i][j] = random.randint(-10, 10) return C
---from random import randint random_matrix = lambda n: [ [ random.randint(-10, 10) for j in xrange(n) ] for i in xrange(n) ]
This outline for the lab #10 is a preliminary version, it might change.
If needed, please fill a bug report?