Assignment 4: Matrix Operations
Discussion on Monday 2/10/25
Workshop in class on Wednesday 2/12/25
Submission due by 9:00 p.m. on Friday 2/14/25.
Preliminaries
In your work on this assignment, make sure to abide by the collaboration policies of the course.
For each problem in this problem set, we will be writing or evaluating some Python code. You are encouraged to use the Spyder IDE which will be discussed/presented in class, but you are welcome to use another IDE if you choose.
If you have questions while working on this assignment, please post them on Piazza! This is the best way to get a quick response from your classmates and the course staff.
Programming Guidelines
-
Refer to the class Coding Standards for important style guidelines. The grader will be awarding/deducting points for writing code that comforms to these standards.
-
Every program file must begin with a descriptive header comment that includes your name, username/BU email, and a brief description of the work contained in the file.
-
Every function must include a descriptive docstring that explains what the function does and identifies/defines each of the parameters to the function.
-
Your functions must have the exact names specified below, or we won’t be able to test them. Note in particular that the case of the letters matters (all of them should be lowercase), and that some of the names include an underscore character (
_
). -
If a function takes more than one input, you must keep the inputs in the order that we have specified.
-
You should not use any Python features that we have not discussed in class or read about in the textbook.
-
Your functions do not need to handle bad inputs – inputs with a type or value that doesn’t correspond to the description of the inputs provided in the problem.
-
You must test each function after you write it.
Warnings: Individual Work and Academic Conduct!!
-
This is an individual assignment. You may discuss the problem statement/requirements, Python syntax, test cases, and error messages with your classmates. However, each student must write their own code without copying or referring to other student’s work.
-
It is strictly forbidden to use any code that you find from online websites including but not limited to as CourseHero, Chegg, or any other sites that publish homework solutions.
-
It is strictly forbidden to use any generative AI (e.g., ChatGPT or any similar tools**) to write solutions for for any assignment.
Students who submit work that is not authentically their own individual work will earn a grade of 0 on this assignment and a reprimand from the office of the Dean.
If you have questions while working on this assignment, please post them on Piazza! This is the best way to get a quick response from your classmates and the course staff.
Background Reading: Matrix Algebra
You must be familiar with basic matrix algebra notation and operations. The following web pages are an excellent resource. If you haven’t taken a matrix algebra class (or even if you have!) please read these pages and try out any examples you need to so that you feel confident about the subject matter:
Objective and Overview
Objective: Become proficient at using 2-dimensional lists, iteration with nested loops, and indexing to find elements. Practice with functions and recursion.
Overview: In this assignment, you will implement several basic linear algebra operations. The underlying storage of data elements will be as a 2-dimensional list within Python. Each operation will be implemented as its own a function, and you will be responsible for running unit tests on each function to ensure it works correctly. As always, you must write a concise and descriptive doc string for each function, and include comments as necessary throughout the code.
About python library functions
In a future assignment, we will use the numpy
numerical computing package
to perform these operations, but it’s important and satisfying to learn
how to do these operations the long way.
You MAY NOT use any functions or tools from the numpy
library or similar
pre-existing linear-algebra toolkits. The goal of this assignment is to practice
working with definite loops, 2-dimensional lists, and indexing.
Task 1: Implementing a Matrix as a 2-D List
25 points; individual-only
Do this entire task in a file called a4task1.py
.
-
Write the function
print_matrix(m, label)
, that takes two parameters,m
which is a 2-dimension list (the matrix) andlabel
(a string), and creates a nicely-formatted printout.Example:
>>> A = [[3,0,2,1],[2,0,-2,3]] >>> print_matrix(A, 'A') A = [[3.00, 0.00, 2.00, 1.00] [2.00, 0.00, -2.00, 3.00]]
Notes:
-
The
print_matrix
function must not change any values of the matrix. In particular, do not change the vales into strings. Rather, you should iterate over each element of the matrix and print out a string representation of that element, without changing the matrix’s underlying elements in any way. -
The printout must have each row on its own line, square brackets as needed to show the begin/end of each row, and commas to separate the columns, as per the example above.
-
The printout of the matrix should always present numeric values as floating-point numbers with 2 digits of precision after the decimal point.
-
Note that the second parameter
label
is optional. You can create an optional parameter by giving it a default value in the function definition, i.e.,def print_matrix(A, label = ''):
When the function is called with only one parameter, the default value is used for
label
.>>> A = [[3,0,2,1],[2,0,-2,3]] >>> print_matrix(A) # no label provided, so don't show a label [[3.00, 0.00, 2.00, 1.00] [2.00, 0.00, -2.00, 3.00]]
Hints:
-
Use a pair of nested for loops to iterate over all row/column indices of the matrix.
-
The default behavior at the end of a print statement is to add a line break. We can change this behavior!
To print an individual list element, you could use something similar the following
print
statement, which includes the necessary formatting string:print(f'{matrix[r][c]:.2f}', end=', ')
To force a line break at the end of a colunmn, use a regular
print()
statement.
-
-
Write the function
zeros(n, m)
, that creates and returns ann * m
matrix containing all zeros.Examples:
>>> print_matrix(zeros(3,2)) [[0.00, 0.00] [0.00, 0.00] [0.00, 0.00]] >>> print_matrix(zeros(2,2)) [[0.00, 0.00] [0.00, 0.00]] >>> print_matrix(zeros(3,5)) [[0.00, 0.00, 0.00, 0.00, 0.00] [0.00, 0.00, 0.00, 0.00, 0.00] [0.00, 0.00, 0.00, 0.00, 0.00]]
The function should also use a default parameter for m
, such that it is possible to call
the function with only one parameter, and obtain a square n*n
matrix:
:::python >>> print_matrix(zeros(3)) [[0.00, 0.00, 0.00] [0.00, 0.00, 0.00] [0.00, 0.00, 0.00]]
-
Write the function
identity_matrix(n)
, that creates and returns ann * n
identity matrix containing the value of 1 along the diagonal.Example:
>>> I = identity_matrix(3) >>> print_matrix(I, 'I') I = [[1.00, 0.00, 0.00] [0.00, 1.00, 0.00] [0.00, 0.00, 1.00]]
Hints:
- Re-use your
zeros(n)
function to create the new matrix, then just modify the diagonal elements.
- Re-use your
-
Write the function
transpose(M)
, that creates and returns the transpose of a matrix.Example:
>>> A = [[1,2,3],[4,5,6]] >>> print_matrix(A) [[1.00, 2.00, 3.00] [4.00, 5.00, 6.00]] >>> AT = transpose(A) >>> print_matrix(AT, 'AT') AT = [[1.00, 4.00] [2.00, 5.00] [3.00, 6.00]]
Hints:
-
Do not modify the parameter matrix
M
at all. Instead, create a new matrix, fill in the required values, and return this matrix as the result of the function. -
Your function must work for matrices of any dimensions. Include additional test cases for other dimensions.
-
-
Write the function
swap_rows(M, src, dest)
to perform the elementary row operation that exchanges two rows within the matrix. This function will modify the matrixM
such that its row order has changed, but none of the values within the rows have changed.Examples:
>>> A = [[3, 0, 2], [2, 0, -2], [0, 1, 1]] >>> print_matrix(A) >>> swap_rows(A, 1, 2) # swap rows 1 and 2 >>> print_matrix(A) [[3.00, 0.00, 2.00] [0.00, 1.00, 1.00] [2.00, 0.00, -2.00]] >>> swap_rows(A, 0, 2) >>> print_matrix(A) [[2.00, 0.00, -2.00] [0.00, 1.00, 1.00] [3.00, 0.00, 2.00]]
Hints:
-
This function does not return a value! Rather, you will modify the parameter matrix
M
directly. -
Your function must verify that the row index
row
is valid and in-bounds for this matrix. -
No loops are required in this function! Use assignment statements only. It will be helpful to create a temporary variable to refer to at least one of the rows in this matrix.
-
-
Write the function
mult_row_scalar(M, row, scalar)
to perform the elementary row operation that multiplies all values in the rowrow
by the numerical valuescalar
.Examples:
>>> A = [[3, 0, 2], [2, 0, -2], [0, 1, 1]] >>> print_matrix(A) [[3.00, 0.00, 2.00] [2.00, 0.00, -2.00] [0.00, 1.00, 1.00]] >>> mult_row_scalar(A, 1, -1) # multiply row 1 by -1 >>> print_matrix(A) [[3.00, 0.00, 2.00] [-2.00, 0.00, 2.00] [0.00, 1.00, 1.00]] >>> mult_row_scalar(A, 1, 0.5) # multiply row 1 by 0.5 >>> print_matrix(A) [[3.00, 0.00, 2.00] [-1.00, 0.00, 1.00] [0.00, 1.00, 1.00]]
Hints:
-
This function does not return a value. Rather, you will modify the parameter matrix
M
directly. -
Your function must verify that the row index
row
is valid and in-bounds for this matrix.
-
-
Write the function
add_row_into(M, src, dest)
, that performs the elementary-row operation to add thesrc
row into thedest
row. That is, each element of rowsrc
is to be added into the corresponding element of rowdest
.Examples:
>>> A = [[3, 0, 2], [2, 0, -2], [0, 1, 1]] >>> print_matrix(A) [[3.00, 0.00, 2.00] [2.00, 0.00, -2.00] [0.00, 1.00, 1.00]] >>> add_row_into(A, 2, 1) # add row 2 into row 1 >>> print_matrix(A) [[3.00, 0.00, 2.00] [2.00, 1.00, -1.00] [0.00, 1.00, 1.00]] >>> add_row_into(A, 2, 1) # add row 2 into row 1 >>> print_matrix(A) [[3.00, 0.00, 2.00] [2.00, 2.00, 0.00] [0.00, 1.00, 1.00]]
Hints:
-
This function does not return a value. Rather, you will modify the parameter matrix
M
directly. -
Your function must verify that the row indices
src
anddest
are valid and in-bounds for this matrix.
-
Task 2: Matrix Operations
40 points; individual-only
Do this entire task in a file called a4task2.py
. You will need to import your work
from Task 1 to complete and test this part. Add this import statement to the top of
your a4task2.py
file:
from a4task1 import *
Note: if you have any unit-test code (i.e., function calls or print statements
that are in the global scope in your a4task1.py
file), those statements will run
when you import the file. One way to leave that unit test code in place, but prevent
it from running when you import the file is to put the unit test code in a special
block at the bottom of your a4task1.py
file, like this:
## unit test code if __name__ == '__main__': A = [[3,0,2,1],[2,0,-2,3]] print_matrix(A, 'A') ## end of unit test code
The expression __name__ == '__main__'
will evaluate to True
only when you run
the file, but not when the file is imported.
The following notes apply to all remaining problems:
-
The original matrix must not be modified by your function, but rather you must create and return a new matrix.
-
Your finished function should not print out anything, but merely return the new matrix. You may include print statements in your code while developing the function, but comment them out from the final version.
-
Write the function
add_matrices(A, B)
, that takes as parameters 2 matrices (2d lists) and returns a new matrix which is the element-wise sum of the matricesA
andB
.Example:
>>> A = [[1,2,3],[4,5,6]] >>> B = [[4,5,6],[1,2,3]] >>> S = add_matrices(A, B) >>> print_matrix(S, 'S') S = [[5.00, 7.00, 9.00] [5.00, 7.00, 9.00]]
-
Verify that the matrices
A
andB
are compatible for this operation, i.e., the number of rows and columns in each matrix must be the same. For example, you can add a3x5
matrix with another3x5
matrix, but you cannot add a3x5
matrix to a3x4
matrix because the number of columns does not match.Use the
assert
statement to check this condition and print an error message if needed.Example:
>>> A = [[1,2],[4,5]] >>> B = [[4,3,2],[3,2,1]] >>> S = add_matrices(A,B) Traceback (most recent call last): File "<pyshell#37>", line 1, in <module> S = add_matrices(A,B) File "/Users/azs/code/fe459/assignments/matrix2d.py", line 101, in add_matrices assert(len(A[0]) == len(B[0])), f"incompatible dimensions: cannot add ({len(A)},{len(A[0])}) with ({len(B)},{len(B[0])})!" AssertionError: incompatible dimensions: cannot add (2,2) with (2,3)!
-
-
Write the function
mult_scalar(M, s)
, that takes as a parameter a 2-dimension listM
(the matrix) and a scalar values
(i.e., anint
orfloat
), and returns a new matrix containing the values of the original matrix multiplied by the scalar value.Example:
>>> A = [[3,0,2,1],[2,0,-2,3]] print_matrix(A) [[3.00, 0.00, 2.00, 1.00] [2.00, 0.00, -2.00, 3.00]] >>> B = mult_scalar(A, 3) >>> print_matrix(B) [[9.00, 0.00, 6.00, 3.00] [6.00, 0.00, -6.00, 9.00]]
-
Write the function
sub_matrices(A, B)
, that takes as parameters 2 matrices (2d lists) and returns a new matrix which is the element-wise difference of the matricesA
andB
.Example:
>>> A = [[1,2,3],[4,5,6]] >>> B = [[4,5,6],[1,2,3]] >>> D = sub_matrices(A, B) >>> print_matrix(D, 'D') D = [[-3.00, -3.00, -3.00] [3.00, 3.00, 3.00]]
Important!
- You must implement the
sub_matrices
function without using any loops. -
Instead, you should call your
mult_scalar
andadd_matrices
functions.Recall that
A - B = A + (-1) * B
-
You will not need to explicitly check for compatible dimensions, since this will be handled by the
add_matrices
function.
-
Write the function
element_product(A, B)
, that takes as parameters two matricesA
andB
, and returns a new matrix containing element-wise product of these matrices.Example:
>>> A = [[1,2,3,4]] print_matrix(A) [[1.00, 2.00, 3.00, 4.00]] >>> B = [[4,2,4,2]] print_matrix(B) [[4.00, 2.00, 4.00, 2.00]] >>> P = element_product(A, B) >>> print_matrix(P, 'P') P = [[4.00, 4.00, 12.00, 8.00]]
Another Example:
>>> A = [[3,0,2], [2,0,-2], [0,1,1]] print_matrix(A) [[3.00, 0.00, 2.00] [2.00, 0.00, -2.00] [0.00, 1.00, 1.00]] >>> B = mult_scalar(A, 2) print_matrix(B) [[6.00, 0.00, 4.00] [4.00, 0.00, -4.00] [0.00, 2.00, 2.00]] >>> P = element_product(A, B) >>> print_matrix(P, 'P') P = [[18.00, 0.00, 8.00] [8.00, 0.00, 8.00] [0.00, 2.00, 2.00]]
Notes:
-
Verify that the parameter matrices
A
andB
are compatible for this operation, i.e., the height and width must be the same between the two matrices.Use the
assert
statement to check this condition and print an error message if needed.Example:
>>> A = [[1,2,3,4]] >>> B = [[4,3,2],[3,2,1]] >>> P = element_product(A,B) AssertionError: incompatible row dimensions: 1 != 2 >>> A = [[4,3,2],[3,2,1]] >>> B = [[1,2,3,4]] >>> P = element_product(A,B) AssertionError: incompatible row dimensions: 2 != 1
-
You may assume that
M
andN
are rectangular matrices (i.e., the row lengths are consistent within each one).
-
-
Write the function
dot_product(A, B)
, that takes as parameters two matricesM
andN
, and returns a new matrix containing dot product of these matrices.Example:
>>> A = [[1,2,3],[4,5,6]] print_matrix(A) [[1.00, 2.00, 3.00] [4.00, 5.00, 6.00]] >>> B = [[3,2],[4,1],[5,0]] print_matrix(B) [[3.00, 2.00] [4.00, 1.00] [5.00, 0.00]] >>> P = dot_product(A,B) >>> print_matrix(P, 'P') P = [[26.00, 4.00] [62.00, 13.00]]
Notes:
-
Verify that the matrices
A
andB
are compatible for this operation, i.e., the inner dimensions must be the same. For example, you can create a dot product of a3x5
matrix times a5x4
matrix and get a3x4
result. However, you cannot find a dot product of a3x5
matrix times a4x3
matrix, because the inner dimensions do not match.Use the
assert
statement to check this condition and print an error message if needed.Example:
>>> A = [[1,2,3],[4,5,6]] >>> B = [[4,3,2],[3,2,1]] >>> P = dot_product(A,B) AssertionError: incompatible dimensions: cannot dot-product (2,3) with (2,3)!
-
OPTIONAL BUT CHALLENGING FUNCTIONS:
The following functions (create_sub_matrix
, determinant
, and inverse_matrix
)
are optional for this assigment, but will provide an additional challenge for
students with an interest in linear algebra.
In a future assignment, we will use the numpy
numerical computing package
to perform these operations, but it’s satisfying to learn how to do some of these
operations.
- You may choose to continue on to part 3 of this assignment without writing these functions.
Here are some additional readings about calculating the determinant and inverse of a matrix:
Using to Recursion
This optional part of the assignment requires the use of recursion, which is a functional programming strategy for solving certain kinds of problems by creating a simpler sub-problem. Here are some materials about recursion:
-
A video mini-lecture that introduces recursion
-
Optional Write function called
create_sub_matrix(M, exclude_row, exclude_col)
that returns a sub-matrix of M, with all values that are not in rowexclude_row
or columnexclude_col
.Example:
>>> A = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]] >>> print_matrix(A) [[1.00, 2.00, 3.00, 4.00] [5.00, 6.00, 7.00, 8.00] [9.00, 10.00, 11.00, 12.00] [13.00, 14.00, 15.00, 16.00]] >>> >>> print_matrix(create_sub_matrix(A, 0, 0)) # exclude row 0 and column 0 [[6.00, 7.00, 8.00] [10.00, 11.00, 12.00] [14.00, 15.00, 16.00]] >>> >>> print_matrix(create_sub_matrix(A, 2, 3)) # exclude row 2 and column 3 [[1.00, 2.00, 3.00] [5.00, 6.00, 7.00] [13.00, 14.00, 15.00]]
The
determinant
andmatrix_of_minors
functions that follow will require you to create a sub-matrix that includes all elements of the original matrix, excluding elements from a certain row or column. -
Optional Write the function
determinant(M)
, that takes a parameterM
that is a (non-singular) matrix, and returns its determinant. The “Laplace expansion” method of calculating a determinant is a relatively simple recursive algorithm.Refer to the webpage about determinant [determinant of a matrix][matrix_determinant] and using the “Laplace expansion” method to calculate a determinant of a matrix. Your function must use the Laplace expansion method.
Example:
>>> A = [[3,4],[8,6]] >>> d = determinant(A) >>> print(d) -14 >>> B = [[2,-1,0],[3,-5,2], [1,4,-2]] >>> d = determinant(B) >>> print(d) -4 >>> # a 4x4 matrix example >>> C = [[3, 2, 0, 1], [4, 0, 1, 2], [3, 0, 2, 1], [9, 2, 3, 1]] >>> d = determinant(C) >>> print(d) 24
Notes:
-
There is a special case to find the determinant of a
1x1
matrix. The determinant of this matrix is its scalar value, i.e.,>>> A = [[3]] >>> determinant(A) 3
-
You can only find the determinant of a square and non-singular matrix whose dimensions are at least
2x2
. Includeassert
statements at the top of your function to test for cases in which you cannot find a determinant.>>> A = [[3,4],[8,6], [10,7]] >>> determinant(A) Traceback (most recent call last): File "<pyshell#85>", line 1, in <module> determinant(A) File "/Users/azs/code/mf602/assignments/a6task2.py", line 161, in determinant assert(len(A[0]) == len(A)), "incompatible dimensions: %s!" % dim AssertionError: incompatible dimensions: (3,2)!
Hints:
-
For a
2x2
matrix, the determinant is simple arithmetic. Test this solution before you try any other dimensions. -
For a larger (e.g. a
3x3
) matrix, you should use the Laplace Expansion method described in the webapge about the [determinant of a matrix][matrix_determinant]. It will be necessary to create several sub-matrices and find the determinant of each. You must use the helper functioncreate_sub_matrix(M, exclude_row, exclude_col)
(see note above).This is a recursive algorithm, and you should use a recursive function call to find the determinant of each sub-matrix. However, you will also need to use a loop to build the sub-matrices (since you do not know in advance how many you need to create).
Begin by determining how many sub-matrices you need to create. For a 3x3 matrix, you will need to create 3 sub-matrices (use a loop!).
To build each sub-matrix, you will need a pair of nested loops to add the appropriate elements to that matrix. Then you can use recursion to find the determinant of that sub-matrix.
-
Computing the inverse of a matrix
We want to calculate the inverse of a matrix, and you will implement 2 functions to do this. Read the following helpful webpage about calculating the [inverse of a matrix][matrix_inverse]. You should use the approach described therein.
-
Optional Write a helper function called
matrix_of_minors(M)
, that takes a matrix and returns the corresponding matrix of minors.In your
matrix_of_minors(M)
, function, begin this function by thinking about the result matrix that you want to create. Use two loops to iterate over each element in the desired result matrix.To calculate each element in the result matrix, create the relevant sub-matrix (you should use the helper function
create_sub_matrix(M, exclude_row, exclude_col)
from above), and call your existingdeterminant(M)
function to calculate its determinant.Here is a little test code that might be helpful for you:
>>> A = [[3,0,2],[2,0,-2],[0,1,1]] >>> print_matrix(A, 'A') A = [[3.00, 0.00, 2.00] [2.00, 0.00, -2.00] [0.00, 1.00, 1.00]] >>> M = matrix_of_minors(A) >>> print_matrix(M) [[2.00, 2.00, 2.00] [-2.00, 3.00, 3.00] [0.00, -10.00, 0.00]]
-
Optional Write the function
inverse_matrix(M)
, that takes a parameter that is a (non-singular) matrix, and returns its inverse.Example (1): a
2x2
matrix:>>> A = [[7,8],[9,10]] >>> print_matrix(A) [[7.00, 8.00] [9.00, 10.00]] >>> determinant(A) -2 >>> AI = inverse_matrix(A) >>> print_matrix(AI) [[-5.00, 4.00] [4.50, -3.50]] >>> # check that we have the correct inverse; A * AI should be the identity matrix >>> DP = dot_product(A,AI) >>> print_matrix(DP, 'DP') DP = [[1.00, 0.00] [0.00, 1.00]]
Example (2): a
3x3
matrix:>>> A = [[3,0,2],[2,0,-2],[0,1,1]] >>> print_matrix(A, 'A') A = [[3.00, 0.00, 2.00] [2.00, 0.00, -2.00] [0.00, 1.00, 1.00]] >>> AI = inverse_matrix(A) >>> print_matrix(AI,'AI') AI = [[0.20, 0.20, 0.00] [-0.20, 0.30, 1.00] [0.20, -0.30, 0.00]] >>> # check that we have the correct inverse; A * AI should be the identity matrix >>> DP = dot_product(A,AI) >>> print_matrix(DP, 'dot_product(A, AI)') dot_product(A, AI) = [[1.00, 0.00, 0.00] [0.00, 1.00, 0.00] [0.00, 0.00, 1.00]]
Example (3): a
4x4
matrix:>>> A = [[3,2,0,1],[4,0,1,2],[3,0,2,1],[9,2,3,1]] >>> print_matrix(A, 'A') A = [[3.00, 2.00, 0.00, 1.00] [4.00, 0.00, 1.00, 2.00] [3.00, 0.00, 2.00, 1.00] [9.00, 2.00, 3.00, 1.00]] >>> determinant(A) 24 >>> AI = inverse_matrix(A) >>> print_matrix(AI, 'AI') AI = [[-0.25, 0.25, -0.50, 0.25] [0.67, -0.50, 0.50, -0.17] [0.17, -0.50, 1.00, -0.17] [0.42, 0.25, 0.50, -0.42]] >>> # check that we have the correct inverse; A * AI should be the identity matrix >>> DP = dot_product(A,AI) >>> print_matrix(DP) [[1.00, 0.00, 0.00, 0.00] [-0.00, 1.00, 0.00, 0.00] [-0.00, 0.00, 1.00, 0.00] [-0.00, 0.00, 0.00, 1.00]]
Notes:
-
Use the approach described in the web page about the [inverse of a matrix][matrix_inverse], using the matrix of minors, the cofactor matrix, and the adjugate matrix.
-
When calculating the cofactor matrix, use definite loops and think about creating a general way to determine which sign to use (i.e., positive or negative) for each element.
-
You should re-use your code! Call any of your other functions (i.e.,
zeros(n)
,determinant(M)
,mult_scalar(M,s)
, etc.) as helper functions from within yourinverse_matrix(M)
ormatrix_of_minors(M)
functions as needed.
-
Task 3: Matrix Applications: Portfolio Return, Bond Pricing, and Duration
15 points; individual-only
Do this entire task in a file called a4task3.py
.
For this task, you will need to re-use some functions from assignment 2 and 4.
from a2task1 import cashflow_times, bond_cashflows, discount_factors # bond functions from a4task1 import * from a4task2 import *
-
Write the function
portfolio_return(weights, returns)
to calculate and return the portfolio return for an investment portfolio.The portfolio return is a weighted-average of the individual assets’ returns, as represented by this matrix multiplication:
where
e
is a (nx1 column vector) matrix of the expected returns for each asset:and
w
is a matrix (nx1 column vector) matrix of the weights of each asset within the portfolio.For example:
>>> weights = [[0.3, 0.4, 0.3]] >>> returns = [[0.12, 0.10, 0.05]] >>> portfolio_return(weights, returns) 0.0910
Important Notes
-
Your function must use your matrix algebra functions from Task 1 and Task 2 for
dot_product
andtranspose
. (no loop with an accumulator pattern or recursion). -
To be able to use your
dot_product
function, you will need to ensure that the parametersweights
andreturns
have compatible dimensions, and you might need to convert them from column vector (1d list) into matrix form (2d list).That is, the following function call must also work correctly:
>>> weights = [0.3, 0.4, 0.3] # notice 1-d list! >>> returns = [0.12, 0.10, 0.05] >>> portfolio_return(weights, returns) 0.0910
-
Write a new implementation of the
bond_price(fv, c, n, m, ytm)
function to calculate and return the price of a bond using linear algebra. The parameters are:
fv
is the future (maturity) value of the bond;
c
is the annual coupon rate expressed as percentage per year;
n
is the number of years until maturity;
m
is the number of coupon payments per year;
andytm
, the annualized yield to maturity of the bondFor example:
>>> bond_price(1000, 0.08, 5, 2, 0.08) # coupon bond at par 999.9999999999998 >>> bond_price(1000, 0.08, 5, 2, 0.09) # coupon bond at discount 960.4364091144498 >>> bond_price(1000, 0.00, 5, 2, 0.09) # zero-coupon bond 643.927682030043
Implementation Notes:
-
Your function must use a matrix algebra (no loop with an accumulator pattern or recursion) and requires no
if-elif-else
logic. The price of the bond is given by the present values of the cashflows of that bond, as represented by this matrix multiplication:where
df
is the matrix of discount factors for the bond’s payments,CF
is a matrix of the bond’s cashflows, and the resultB
is a matrix containing the price of the bond. -
Re-use your
bond_cashflows(fv, c, n,m)
function froma2task1
to find the cashflows associated with this bond. -
Re-use your
discount_factors(r, n, m)
function froma2task1
to calculate the relevant discount factors.
-
Important Notes
-
Your function must use your matrix algebra functions from Task 1 and Task 2 for
dot_product
andtranspose
. (no loop with an accumulator pattern or recursion). -
You will need to convert the data types appropriately. Specifically, note that the results of
bond_cashflows
anddiscount_factors
are 1-dimensional lists, and the return value must be a floating-point number (the bond price in dollars). In between, you will need to have matrices (represented by 2-dimensional lists) to do the computations using your existing linear algebra functions. -
To be able to use your
dot_product
function, you will need to ensure that the parametersweights
andreturns
have compatible dimensions, and you might need to convert them from column vector (1d list) into matrix form (2d list).
-
Write a new implementation of the
bond_duration(fv, c, n, m, ytm)
function to calculate and return the annulized duration of a bond using linear algebra. The parameters are:
fv
is the future (maturity) value of the bond;
c
is the annual coupon rate expressed as percentage per year;
n
is the number of years until maturity;
m
is the number of coupon payments per year;
andytm
, the annualized yield to maturity of the bondFor example:
>>> bond_duration(1000, 0.05, 2, 2, 0.05) # coupon bond at par >>> 1.9280117816050262 >>> bond_duration(1000, 0.00, 2, 2, 0.05) # zero coupon bond >>> 2.0 >>> >>> bond_duration(1000, 0.05, 10, 2, 0.03) # coupon bond at premium >>> 8.169425098281545
Implementation Notes:
-
Your function must use a matrix algebra (no loop with an accumulator pattern or recursion) and requires no
if-elif-else
logic. The duration of the bond is computed using by the cashflows, discount factors, and payment times of that bond, as represented by this matrix multiplication:where
CF
is a matrix of the bond’s cashflows;df
is a matrix of discount factors for the bond’s payments,T
is a matrix of the cashflow times, andB
is a matrix containing the price of the bond. -
Re-use your
cashflow_times(n,m)
function froma3task1
to find the times of the cashflows of this bond. -
Re-use your
bond_cashflows(fv, c, n, m)
function froma3task1
to find the cashflows associated with this bond. -
Re-use your
discount_factors(r, n, m)
function froma3task1
to calculate the relevant discount factors.
-
Important Notes
-
Your function must use your matrix algebra functions from Task 1 and Task 2 for
element_product
,dot_product
andtranspose
where appropriate. (no loop with an accumulator pattern or recursion). -
You will need to convert the data types appropriately. Specifically, note that the results of
cashflow_times
,bond_cashflows
anddiscount_factors
are 1-dimensional lists. -
The return value is the annualized duration (a floating-point number).
-
You will need to have matrices (represented by 2-dimensional lists) to do the computations using your existing linear algebra functions, and extract the scalar (e.g., floating-point) values from the result matrices as appropriate.
OPTIONAL BUT INTERESTING FUNCTION
-
The following
bootstrap
function is optional for this assigment. -
It requires that you have written the optional functions in part 2 (above).
-
The bootstrap method is a technique to find the implied prices of zero-coupon bonds (i.e., implied discount factors) by using the casflows and prices of some coupon-paying bonds at similar maturities.
Write the function
bootstrap(cashflows, prices)
to implement the bootstrap method. This function will take parameterscashflows
, which is a matrix (2-dimensional list) containing the cashflows for some bonds, andprices
which is a column matrix (2-dimensional list) containing the prices of these bonds.We can find the prices of the implied zero-coupon bonds (i.e., discount factors) using this equation:
where P is a matrix containing the price of the bond, CF is the matrix of the bond’s cashflows, and D is the matrix of implied discount factors.
For example:
>>> CF = [[105,0,0],[6,106,0],[7,7,107]] >>> P = [[99.5], [101.25], [100.35]] >>> D = bootstrap(CF, P) >>> print_matrix(CF, 'Bond Cashflows') Bond Cashflows = [[105.00, 0.00, 0.00] [6.00, 106.00, 0.00] [7.00, 7.00, 107.00]] >>> print_matrix(P, 'Bond Prices') Bond Prices = [[99.50] [101.25] [100.35]] >>> print_matrix(D, 'Implied Discount Factors') Implied Discount Factors = [[0.95] [0.90] [0.82]] >>> D [[0.9476190476190477], [0.9015498652291104], [0.8168768000940457]]
Notes:
- Your function must use your matrix algebra functions (no loops).
Submitting Your Work
20 points; will be assignmed by code review
Use the link to GradeScope (left) to submit your work.
Be sure to name your files correctly!
Under the heading for Assignment 4, attach these 4 required files:
a2task1.py
,
a4task1.py
, a4task2.py
, and a4task3.py
.
When you upload the files, the autograder will test your functions/programs.
Warning: Beware of Global print
statements
- The autograder script cannot handle
print
statements in the global scope, and their inclusion causes this error:
* Why does this happen? When the autograder imports your file, the `print` statement(s) execute (at import time), which causes this error. * You can prevent this error by not having any `print` statements in the global scope. Instead, create an `if __name__ == '__main__':` section at the bottom of the file, and put any test cases/print statements in that controlled block. For example: if __name__ == '__main__': ## put test cases here: print('fv_lump_sum(0.05, 2, 100)', fv_lump_sum(0.05, 2, 100)) * `print` statements inside of functions do not cause this problem.
Notes:
- You may resubmit multiple times, but only the last submission will be graded.