NB 10
NB 10
Some of the material from this lesson is copied from the following, and more comprehensive, tutorial: link (http://www.scipy-
lectures.org/intro/numpy/index.html)
1.14.0
[1 2 3 4]
Exercise 0 (ungraded). One reason to consider Numpy is that it "can be much faster," as noted above. But how much faster is that? Run the ex
below to see.
In [3]: n = 1000000
In [4]: L = range(n)
%timeit [i**2 for i in L]
273 ms ± 3.44 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [6]: A = np.arange(n)
%timeit A**2
690 µs ± 2.34 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
print(B)
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
2
(3, 4)
3
In [9]: C1 = [[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11]]
C = np.array([C1, C2])
print(C)
print(C.ndim)
print(C.shape)
print(len (C))
[[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]]
3
(2, 3, 4)
2
There are routines for creating various kinds of structured matrices as well, which are similar to those found in MATLAB
(http://www.mathworks.com/products/matlab/) and Octave (https://www.gnu.org/software/octave/).
[[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]]
[[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]]
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]
[[1 0 0]
[0 2 0]
[0 0 3]]
You can also create empty (uninitialized) arrays. What does the following produce?
[[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]]
Exercise 1 (ungraded). The following code creates an identity matrix in two different ways, which are found to be equal according to the assert
there is a subtle difference between the I and I_u matrices created below; can you spot it?
In [15]: n = 3
I = np.eye(n)
print("==> I = eye(n):")
print(I)
u = [1] * n
I_u = np.diag(u)
print("\n==> u:\n", u)
print("==> I_u = diag (u):\n", I_u)
assert np.all(I_u == I)
==> I = eye(n):
[[1. 0. 0.]
[0. 1.
[0. 0.
0.]
1.]]
floats
==> u:
[1, 1, 1]
==> I_u = diag (u):
[[1 0 0]
[0 1 0]
[0 0 1]] integers
Answer. Give this some thought before you read the answer that follows!
The difference is in the element types. The eye() function returns an identity matrix and uses a floating-point type as the element type. By con
which expects a list of initializer values upon input, derives the element type from that input. In this case, u contains values that will be stored a
therefore, diag() constructs its output assuming integer elements.
Try running print(I_u.dtype) and print(I.dtype) to confirm that these element types differ.
In [16]: # Recall: C
print (C)
[[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]]
What part of C will the following slice extract? Run the code to find out.
[ 8 9 10 11]
What will the following slice return? Run the code to find out.
[15 14 13 12]
Exercise 2 (5 points). Consider the following matrix, which has 4 different subsets highlighted.
For each subset illustrated above, write an indexing or slicing expression that extracts the subset. Store the result of each slice into Z_green, Z
Z_orange, and Z_cyan.
In [19]: Z= np.array([[0,1,2,3,4,5],[10,11,12,13,14,15],[20,21,22,23,24,25],[30,31,32,33,34,35],[40,41,42
],[50,51,52,53,54,55]])
print("==> Z:\n", Z)
assert (Z == np.array([np.arange(0, 6),
np.arange(10, 16),
np.arange(20, 26),
np.arange(30, 36),
np.arange(40, 46),
np.arange(50, 56)])).all()
print("\n(Passed!)")
==> Z:
[[ 0 1 2 3 4 5]
[10 11 12 13 14 15]
[20 21 22 23 24 25]
[30 31 32 33 34 35]
[40 41 42 43 44 45]
[50 51 52 53 54 55]]
(Passed!)
[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]]
In [22]: C_view = C[1, 0::2, 1::2] # Question: What does this produce?
print ("==> C_view: %s" % str (C_view.shape))
print (C_view)
[[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[[ 12 -23 14 -21]
[ 16 17 18 19]
[ 20 -15 22 -13]]]
You can force a copy using the .copy() method:
==> C_view:
[[-23 -21]
[-15 -13]]
==> C_copy:
[[13 15]
[21 23]]
And to check whether two Numpy array variables point to the same object, you can use the numpy.may_share_memory() function:
In [26]: print ("C and C_view share memory: %s" % np.may_share_memory (C, C_view))
print ("C and C_copy share memory: %s" % np.may_share_memory (C, C_copy))
Indirect addressing
Two other common ways to index a Numpy array are to use a boolean mask or to use a set of integer indices.
In [27]: np.random.seed(3)
te
Yements
x = np.random.randint(0, 20, 15) # 15 random ints in [0, 20)
print(x)
[10 3 8 0 19 10 11 9 10 6 0 12 7 14 17]
[ 0 9 10 7]
Before looking at how to use a boolean mask for indexing, let's create one.
Exercise 3 (1 point). Given the input array, x[:], above, create an array, mask_mult_3[:] such that mask_mult_3[i] is true only if x[i] is positive
a
multiple of 3.
print ("x:", x)
print ("mask_mult_3:", mask_mult_3)
print ("==> x[mask_mult_3]:", x[mask_mult_3])
x: [10 3 8 0 19 10 11 9 10 6 0 12 7 14 17]
mask_mult_3: [False True False False False False False True False True False True
False False False]
==> x[mask_mult_3]: [ 3 9 6 12]
Exercise 4 (3 points). Complete the prime number sieve algorithm, which is illustrated below.
That is, given a positive integer , the algorithm iterates from , repeatedly "crossing out" values that are strict multiples
"Crossing out" means maintaining an array of, say, booleans, and setting values that are multiples of to False.
of i
In [31]: from math import sqrt
def sieve(n):
"""
Returns the prime number 'sieve' shown above.
is_prime = sieve(20)
assert len (is_prime) == 21
assert (is_prime == np.array([False, False, True, True, False, True, False, True, False, False,
1.14.0
Random numbers
Numpy has a rich collection of (pseudo) random number generators. Here is an example; see the documentation for numpy.random()
(https://docs.scipy.org/doc/numpy/reference/routines.random.html) for more details.
In [2]: A = np.random.randint(-10, 10, size=(4, 3)) # return random integers from -10 (inclusive) to 10
e)
print(A)
[[ 5 7 6]
[-10 7 7]
[ 6 3 -10]
[ -2 -6 -10]]
Aggregations or reductions
Suppose you want to reduce the values of a Numpy array to a smaller number of values. Numpy provides a number of such functions that aggr
Examples of aggregations include sums, min/max calculations, and averaging, among others.
In [3]: print("np.max =", np.max(A),"; np.amax =", np.amax(A)) # np.max() and np.amax() are synonyms
print("np.min =",np.min(A),"; np.amin =", np.amin(A)) # same
print("np.sum =",np.sum(A))
print("np.mean =",np.mean(A))
print("np.std =",np.std(A))
np.max = 7 ; np.amax = 7
np.min = -10 ; np.amin = -10
np.sum = 3
np.mean = 0.25
np.std = 7.025252071396916
The above examples aggregate over all values. But you can also aggregate along a dimension using the optional axis parameter.
In [4]: print("Max in each column:", np.amax(A, axis=0)) # i.e., aggregate along axis 0, the rows, produ
n maxes
print("Max in each row:", np.amax(A, axis=1)) # i.e., aggregate along axis 1, the columns, produ
axes
Universal functions
Universal functions apply a given function elementwise to one or more Numpy objects.
[[ 5 7 6]
[-10 7 7]
[ 6 3 -10]
[ -2 -6 -10]]
==>
[[ 5 7 6]
[10 7 7]
[ 6 3 10]
[ 2 6 10]]
Some universal functions accept multiple, compatible arguments. For instance, here, we compute the elementwise maximum between two mat
, producing a new matrix such that .
The matrices must have compatible shapes, which we will elaborate on below when we discuss Numpy's broadcasting rule.
[[ 5 7 6]
[-10 7 7]
[ 6 3 -10]
[ -2 -6 -10]]
[[ 5 5 -5]
[ -6 9 -10]
[ -9 -3 -7]
[ 3 -3 7]]
[[ 5 7 6]
[-6 9 7]
[ 6 3 -7]
[ 3 -3 7]]
You can also build your own universal functions! For instance, suppose we want a way to compute, elementwise, and we have a sc
that can do so:
This function accepts one input (x) and returns a single output. The following will create a new Numpy universal function f_np. See the docume
np.frompyfunc() (https://docs.scipy.org/doc/numpy/reference/generated/numpy.frompyfunc.html) for more details.
[[ 5 7 6]
[-10 7 7]
[ 6 3 -10]
[ -2 -6 -10]]
=>
[[1.3887943864964021e-11 5.242885663363464e-22 2.3195228302435696e-16]
[3.720075976020836e-44 5.242885663363464e-22 5.242885663363464e-22]
[2.3195228302435696e-16 0.00012340980408667956 3.720075976020836e-44]
[0.01831563888873418 2.3195228302435696e-16 3.720075976020836e-44]]
Broadcasting
Sometimes we want to combine operations on Numpy arrays that have different shapes but are compatible.
In [11]: print(A)
print()
print(A + 3)
[[ 5 7 6]
[-10 7 7]
[ 6 3 -10]
[ -2 -6 -10]]
[[ 8 10 9]
[-7 10 10]
[ 9 6 -7]
[ 1 -3 -7]]
Technically, A and 3 have different shapes: the former is a matrix, while the latter is a scalar ( ). However, they are compatible becau
knows how to extend---or broadcast---the value 3 into an equivalent matrix object of the same shape in order to combine them.
To see a more sophisticated example, suppose each row A[i, :] are the coordinates of a data point, and we want to compute the centroid o
points (or center-of-mass, if we imagine each point is a unit mass). That's the same as computing the mean coordinate for each column:
[[ 5 7 6]
[-10 7 7]
[ 6 3 -10]
[ -2 -6 -10]]
=>
[-0.25 2.75 -1.75]
Now, suppose you want to shift the points so that their mean is zero. Even though they don't have the same shape, Numpy will interpret A - A
as precisely this operation, effectively extending or "replicating" A_row_means into rows of a matrix of the same shape as A, in order to then p
elementwise subtraction.
Suppose you instead want to mean-center the columns instead of the rows. You could start by computing column means:
[[ 5 7 6]
[-10 7 7]
[ 6 3 -10]
[ -2 -6 -10]]
=>
[ 6. 1.33333333 -0.33333333 -6. ]
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-15-d3357eda1460> in <module>()
----> 1 A - A_col_means # Fails!
ValueError: operands could not be broadcast together with shapes (4,3) (4,)
The error reports that these shapes are not compatible. So how can you fix it?
Broadcasting rule. One way is to learn Numpy's convention for broadcasting (https://docs.scipy.org/doc/numpy/reference/ufuncs.html#b
Numpy starts by looking at the shapes of the objects:
(4, 3) (3,)
These are compatible if, starting from right to left, the dimensions match or one of the dimensions is 1. This convention of moving from right to
to as matching the trailing dimensions. In this example, the rightmost dimensions of each object are both 3, so they match. Since A_row_mean
dimensions, it can be replicated to match the remaining dimensions of A.
(4, 3) (4,)
In this case, per the broadcasting rule, the trailing dimensions of 3 and 4 do not match. Therefore, the broadcast rule fails. To make it work, we
modify A_col_means to have a unit trailing dimension. Use Numpy's reshape()
(https://docs.scipy.org/doc/numpy/reference/generated/numpy.reshape.html) to convert A_col_means into a shape that has an explicit trailing
size 1.
[[ 6. ]
[ 1.33333333]
[-0.33333333]
[-6. ]] => (4, 1)
Now the trailing dimension equals 1, so it can be matched against the trailing dimension of A. The next dimension is the same between the two
Numpy knows it can replicate accordingly.
A - A_col_means2
[[ 5 7 6]
[-10 7 7]
[ 6 3 -10]
[ -2 -6 -10]]
- [[ 6. ]
[ 1.33333333]
[-0.33333333]
[-6. ]]
=>
[[ -1. 1. 0. ]
[-11.33333333 5.66666667 5.66666667]
[ 6.33333333 3.33333333 -9.66666667]
[ 4. 0. -4. ]]
Part 2: Dense matrix storage
This part of the lab is a brief introduction to efficient storage of matrices.
1.14.0
By way of background, physical storage---whether it be memory or disk---is basically one big array. And because of how physical storage is im
turns out that it is much faster to access consecutive elements in memory than, say, to jump around randomly.
A matrix is a two-dimensional object. Thus, when it is stored in memory, it must be mapped in some way to the one-dimensional physical array
many possible mappings, but the two most common conventions are known as the column-major and row-major layouts:
Exercise 1 (2 points). Let be an matrix stored in column-major format. Let be an matrix stored in row-major format.
Based on the preceding discussion, recall that these objects will be mapped to 1-D arrays of length , behind the scenes. Let's call the 1-D a
representations and . Thus, the element of , , will map to some element of ; similarly, will map to some element of . array
Determine formulae to compute the 1-D index values, and , in terms of . Assume that all indices are 0-based, i.e.,
, and .
print ("(Passed.)")
(Passed.)
Historically numerical linear algebra libraries were developed assuming column-major layout. This layout happens to be the default when you d
array in the Fortran programming language. By contrast, in the C and C++ programming languages, the default convention for a 2-D array is row
layout. So the Numpy default is the C/C++ convention.
In your programs, you can request either order of Numpy using the order parameter. For linear algebra operations (common), we recommend
column-major convention.
In either case, here is how you would create column- and row-major matrices.
In [5]: n = 5000
A_colmaj = np.ones((n, n), order='F') # column-major (Fortran convention)
A_rowmaj = np.ones((n, n), order='C') # row-major (C/C++ convention)
Exercise 2 (1 point). Given a matrix , write a function that scales each column, by . Then compare the speed of applying that function
row and column major order.
311 ms ± 3.08 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
28.3 ms ± 202 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [9]: # Generate random values, for use in populating the matrix and vector
from random import gauss
928 µs ± 124 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
iatrix
Exercise 3 (3 points). Implement a matrix-vector product that operates on native Python lists. Assume the 1-D column-major storage of the m
(Passed!)
1.66 s ± 30.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Fin! If you've reached this point and everything executed without error, you can submit this part and move on to the next one.
Note: When you submit this notebook to the autograder, there will be a time-limit of about 180 seconds (3 minutes). If your notebook ta
more time than that to run, it's likely you are not writing efficient enough code.
Start by running the following code cell to get some of the key modules you'll need
Start by running the following code cell to get some of the key modules you ll need.
import numpy as np
print(np.__version__)
import pandas as pd
print(pd.__version__)
Sample data
For this part, you'll need to download the dataset below. It's a list of pairs of strings. The strings, it turns out, correspond to anonymized Yelp! u
IDs
iser
Apair exists if user is friends on Yelp! with user .
Exercise 0 (ungraded). Verify that you can obtain the dataset and take a peek by running the two code cells that follow.
local_filename = './resource/asnlib/publicdata/UserEdges-1M.csv'
checksum = '4668034bbcd2fa120915ea2d15eafa8d'
with io.open(local_filename, 'r', encoding='utf-8', errors='replace') as f:
body = f.read()
body_checksum = hashlib.md5(body.encode('utf-8')).hexdigest()
assert body_checksum == checksum, \
"Data file '{}' has incorrect checksum: '{}' instead of '{}'".format(local_filename,
body_checksum,
checksum)
print("==> Checksum test passes: {}".format(checksum))
Source Target
0 18kPq7GPye-YQ3LyKyAZPw rpOyqD_893cqmDAtJLbdog
1 18kPq7GPye-YQ3LyKyAZPw 4U9kSBLuBDU391x6bxU-YA
2 18kPq7GPye-YQ3LyKyAZPw fHtTaujcyKvXglE33Z5yIw
3 18kPq7GPye-YQ3LyKyAZPw 8J4IIYcqBlFch8T90N923A
4 18kPq7GPye-YQ3LyKyAZPw wy6l_zUo7SN0qrvNRWgySw
...
`edges_raw` has 1000000 entries.
num_edges = len(edges)
num_verts = len(V_names)
print("==> |V| == {}, |E| == {}".format(num_verts, num_edges))
Answer. Give this question some thought before peeking at our suggested answer, which follows.
Recall that the input dataframe, edges_raw, has a row if and are friends. But here is what is unclear at the outset: if is an entry
inthis
is also an entry? The code in the above cell effectively figures that out, by computing a dataframe, edges, that contains both and
ba w
no additional duplicates, i.e., no copies of .
It also uses sets to construct a set, V_names, that consists of all the names. Evidently, the dataset consists of 107,456 unique names and 441,3
pairs, or 882,640 pairs when you "symmetrize" to ensure that both and appear.
320um
Graphs
One way a computer scientist thinks of this collection of pairs is as a graph: https://en.wikipedia.org/wiki/Graph_(discrete_mathematics%29
(https://en.wikipedia.org/wiki/Graph_(discrete_mathematics%29))
The names or user IDs are nodes or vertices of this graph; the pairs are edges, or arrows that connect vertices. That's why the final output obje
V_names (for vertex names) and edges (for the vertex-to-vertex relationships). The process or calculation to ensure that both and
in edges is sometimes referred to as symmetrizing the graph: it ensures that if an edge exists, then so does . If that's true for all e
the graph is undirected. The Wikipedia page linked to above explains these terms with some examples and helpful pictures, so take a moment
material before moving on.
We'll also refer to this collection of vertices and edges as the connectivity graph.
Hint: Recall that you implemented a dense matrix-vector multiply in Part 2. Think about how to adapt that same piece of code when th
structure for storing A has changed to the sparse representation given in this exercise.
A = sparse_matrix ()
A[0][1] = -2.5
A[0][2] = 1.2
A[1][0] = 0.1
A[1][1] = 1.
A[2][0] = 6.
A[2][1] = -1.
print ("\n(Passed.)")
(Passed.)
Next, let's convert the edges input into a sparse matrix representing its connectivity graph. To do so, we'll first want to map names to integers.
id2name[k] = v
name2id[v] = k
Exercise 3 (3 points). Given id2name and name2id as computed above, convert edges into a sparse matrix, G, where there is an entry G[s][
wherever an edge (s, t) exists.
to
Note - This step might take time for the kernel to process as there are 1 million rows
In [9]: G = sparse_matrix()
print ("\n(Passed.)")
(Passed.)
Exercise 4 (3 points). In the above, we asked you to construct G using integer keys. However, since we are, after all, using default dictionaries,
use the vertex names as keys. Construct a new sparse matrix, H, which uses the vertex names as keys instead of integers.
we can
In [11]: H = sparse_matrix()
### BEGIN SOLUTION
for i in range(len(edges)): # edges is the table above
s = edges['Source'].iloc[i]
t = edges['Target'].iloc[i]
H[s][t] = 1.0
### END SOLUTION everyedge t nowmapped is avalue with of1
In [12]: # Test cell: `create_H_test`
print ("\n(Passed.)")
(Passed.)
Exercise 5 (3 points). Implement a sparse matrix-vector multiply for matrices with named keys. In this case, it will be convenient to have vector
ais
that
have named keys; assume we use dictionaries to hold these vectors as suggested in the code skeleton, below.
Hint: Go back to Exercise 2 and see what you did there. If it was implemented well, a modest change to the solution for Exercise 2 is l
be all you need here.
y = vector_keyed(keys=A.keys(), values=0.0)
### BEGIN SOLUTION
for i, A_i in A.items():
for j, a_ij in A_i.items():
y[i] += a_ij * x[j]
### END SOLUTION
return y
A_keyed = sparse_matrix ()
A_keyed['row']['your'] = -2.5
A_keyed['row']['boat'] = 1.2
A_keyed['your']['row'] = 0.1
A_keyed['your']['your'] = 1.
A_keyed['boat']['row'] = 6.
A_keyed['boat']['your'] = -1.
print ("\n(Passed.)")
(Passed.)
Let's benchmark spmv() against spmv_keyed() on the full data set. Do they perform differently?
If this benchmark or any of the subsequent ones take an excessively long time to run, including autograder failure, then it's likely you ha
implemented spmv_keyed efficiently. You'll need to look closely and ensure your implementation is not doing something that leads to
excessive running time or memory consumption, which might happen if you don't really understand how dictionaries work.
Alternative formats:
Take a look at the following slides: link (https://www.dropbox.com/s/4fwq21dy60g4w4u/cse6040-matrix-storage-notes.pdf?dl=0). These slides
basics of two list-based sparse matrix formats known as coordinate format (COO) and compressed sparse row (CSR). We will also discuss them
below.
Hint - Think of what rows, columns and values mean conceptually when you relate it with our dataset of edges
print ("\n(Passed.)")
(Passed.)
Exercise 7 (3 points). Implement a sparse matrix-vector multiply routine for COO implementation.
y = dense_vector(num_rows)
return y
A_coo_rows = [0, 0, 1, 1, 2, 2]
A_coo_cols = [1, 2, 0, 1, 0, 1]
A_coo_vals = [-2.5, 1.2, 0.1, 1., 6., -1.]
x = dense_vector([1, 2, 3])
y0 = dense_vector([-1.4, 2.1, 4.0])
print("\n(Passed.)")
==> A_coo: [(0, 1, -2.5), (0, 2, 1.2), (1, 0, 0.1), (1, 1, 1.0), (2, 0, 6.0), (2, 1, -1.0)]
==> x: [1.0, 2.0, 3.0]
==> True solution, y0: [-1.4, 2.1, 4.0]
==> Your solution: [-1.4000000000000004, 2.1, 4.0]
==> Residual (infinity norm): 4.440892098500626e-16
(Passed.)
rows = [0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6]
cols = [1, 2, 4, 0, 2, 3, 0, 1, 3, 4, 1, 2, 5, 6, 0, 2, 5, 3, 4, 6, 3, 5]
values = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
If there are nnz nonzeroes, then this representation requires 3*nnz units of storage since it needs three arrays, each of length nnz.
Observe that when we sort by row index, values are repeated wherever there is more than one nonzero in a row. The idea behind CSR is to exp
redundancy. From the sorted COO representation, we keep the column indices and values as-is. Then, instead of storing every row index, we ju
starting offset of each row in those two lists, which we'll refer to as the row pointers, stored as the list rowptr, below:
Exercise 8 (3 points). Complete the function, coo2csr(coo_rows, coo_cols, coo_vals), below. The inputs are three Python lists corres
sparse matrix in COO format, like the example illustrated above. Your function should return a triple, (csr_ptrs, csr_inds, csr_vals), c
to the same matrix but stored in CSR format, again, like what is shown above, where csr_ptrs would be the row pointers (rowptr), csr_ind
the column indices (colind), and csr_vals would be the values (values).
To help you out, we show how to calculate csr_inds and csr_vals. You need to figure out how to compute csr_ptrs. The function is set u
these three lists.
Your solution needs to handle empty rows, too. See the second test cell for an example.
Noteremember
list thatnotallrowswillhave
# Your task: Compute `csr_ptrs`
### BEGIN SOLUTION
nowpointers non zero elements
def solution_0(C): # "Basic"
C_rows = [i for i, _, _ in C] # Sorted row indices
ptrs = [] # Output
i_previous = None # ID of the previously visited row
for k, i in enumerate(C_rows):
if i_previous is None: # `i` is the first row
ptrs += [0] * (i+1) # `i` may be greater than 0!
elif i != i_previous: # `i` is a new row index
ptrs += [k] * (i - i_previous) # handles all-zero rows
i_previous = i
ptrs += [len(C_rows)]
return ptrs
csr_ptrs = solution_0(C)
### END SOLUTION
print ("\n(Passed.)")
(Passed.)
# B csr_ptrs
# / 0 0 0 0 0 0 0 \ 0
# | 0 0 0 0 0 0 0 | 0
# | 1 0 0 1 0 1 0 | 0
# | 0 0 0 0 0 0 0 | --> 3
# | 0 0 0 0 0 0 0 | 3
# | 0 1 0 0 1 0 1 | 3
# \ 0 0 1 1 0 0 0 / 6
# 8
B coo rows = [2 2 2 5 5 5 6 6]
B_coo_rows = [2, 2, 2, 5, 5, 5, 6, 6]
B_coo_cols = [5, 0, 3, 1, 4, 6, 2, 3]
B_coo_vals = [1, 1, 1, 1, 1, 1, 1, 1]
B_csr_ptrs = [0, 0, 0, 3, 3, 3, 6, 8]
COO format:
coo_rows = [2, 2, 2, 5, 5, 5, 6, 6]
coo_cols = [5, 0, 3, 1, 4, 6, 2, 3]
coo_vals = [1, 1, 1, 1, 1, 1, 1, 1]
CSR pointers:
==> True solution: [0, 0, 0, 3, 3, 3, 6, 8]
==> Your solution: [0, 0, 0, 3, 3, 3, 6, 8]
(Passed!)
# Given the COO format of our dataset of edges, we will empty randomly selected 3 consecutive ro
i = np.random.randint(num_verts-3)
idx = np.concatenate((np.where(np.array(coo_rows)==i)[0], np.where(np.array(coo_rows)==i+1)[0],
np.where(np.array(coo_rows)==i+2)[0]), axis=0)
coo_rows_empty = np.delete(np.array(coo_rows), idx).tolist()
coo_cols_empty = np.delete(np.array(coo_cols), idx).tolist()
coo_vals_empty = np.delete(np.array(coo_vals), idx).tolist()
# Output of coo2csr:
csr_ptrs_empty, _, _ = coo2csr(coo_rows_empty, coo_cols_empty, coo_vals_empty)
assert max(csr_ptrs_empty) == num_edges - len(idx), f'The largest entry of your output is {max(c
pty)} instead of {num_edges - len(idx)}'
assert len(csr_ptrs_empty) == num_verts + 1, f'Your output has {len(csr_ptrs_empty)} entries ins
um_verts+1}.'
assert sorted(coo_rows_empty) == coo_rows_converted, "The function doesn't handle the empty rows
y."
print ("\n(Passed.)")
(Passed.)
y = dense_vector(num_rows)
### BEGIN SOLUTION
for i in range(num_rows):
for k in range(ptr[i], ptr[i+1]):
y[i] += val[k] * x[ind[k]]
### END SOLUTION
return y returnsthe inthecsrformatand
multiplicationof stored it x
thevector
A_csr_ptrs = [ 0, 2, 4, 6]
A_csr_cols = [ 1, 2, 0, 1, 0, 1]
A_csr_vals = [-2.5, 1.2, 0.1, 1., 6., -1.]
x = dense_vector([1, 2, 3])
y0 = dense_vector([-1.4, 2.1, 4.0])
print ("\n(Passed.)")
(Passed.)