MATH40006 Practice Paper 2 Solutions
English review edition prepared on 4 October 2026 from a preserved source copy. It is a later presentation, not the historical study interface. Source checks and difficulty judgements describe the original material author's own process; they do not indicate Imperial College London endorsement.
Source SHA-256: 0987656d4d137f045737729548b063ac50edd6ca1cd1b97a2f20225adf587df8Source date: 2026-08-06
Notes — cell 1
# MATH40006 Revised Historical-Coverage Mock 2 - Course-Method Audited Solutions **Time allowed:** 60 minutes **Total marks:** 50 **Role:** 2026 Alternative bridge: row operations, determinants and data export The main solutions use methods evidenced in the supplied official papers and mark schemes. No lecture notes or problem sheets were available in this window, so “course-method audited” here means audited against the official answer methods that are actually accessible.
Code — cell 2
import numpy as np
import sympy as sp
import matplotlib.pyplot as plt
import pandas as pd
from time import perf_counter
from copy import copy
import pickle
import ast
import math
sp.init_printing()
def row_add(mat,row_index,pivot_index,multiple):
mat[row_index,:]=mat[row_index,:]+multiple*mat[pivot_index,:]
Notes — cell 3
## Question 1 (20 marks) **Recognition signal.** The task asks for in-place elementary row operations followed by a copying wrapper for row echelon form. **First key step.** Use `.copy()` for a row before swapping and convert the matrix copy to floating type before division. **Course-method chain.** row multiply/swap -> eliminate one column -> loop through `min(m-1,n)` pivot positions -> test several shapes and input preservation. **Marking points.** 2+3+3+6+3+3. **Common errors.** Aliasing rows during swap; changing the caller input; looping beyond the last usable pivot row; integer truncation during division. **Final self-check.** All entries below the leading diagonal should be numerically zero and the original arrays should compare equal to saved copies. ### Historical-basis and difficulty audit - **Historical formal-question basis:** 2026 Alternative Q1(i-a to ii-h) - **Accessible course-material basis:** 2026 Alternative question paper (no marking scheme supplied ) - **Historical ability gap filled:** add row operations, elimination and general REF chain; although present in the old six sets, it must be separated from 2026 slots independently - **Equivalent 2026 difficulty unit:** 2026 Q1(20 mark multi-step implementation difficulty unit ) - **Why equivalent:** original Alternative 25 mark chain compressed to 20 marks, retaining in-place row operations, column-by-column elimination, rectangular matrices and tests that inputs are unchanged; estimated 20 minutes .
Code — cell 4
def row_multiply(mat,row_index,multiple): mat[row_index,:]=multiple*mat[row_index,:]
def row_swap(mat,i,j):
temp=mat[i,:].copy(); mat[i,:]=mat[j,:]; mat[j,:]=temp
def zero_column(mat,p):
for r in range(p+1,len(mat)):
row_add(mat,r,p,-mat[r,p]/mat[p,p])
def row_echelon_form(mat):
out=np.array(mat,dtype=float,copy=True)
m,n=out.shape
for p in range(min(m-1,n)):
zero_column(out,p)
return out
A=np.array([[1,2,3,4],[5,7,8,9],[2,4,9,3]],float); A0=A.copy()
B=np.array([[2,1,0],[4,3,1],[1,2,5],[3,4,2]],float); B0=B.copy()
print(row_echelon_form(A)); print(np.array_equal(A,A0))
print(row_echelon_form(B)); print(np.array_equal(B,B0))
[[ 1. 2. 3. 4.] [ 0. -3. -7. -11.] [ 0. 0. 3. -5.]] True [[2. 1. 0. ] [0. 1. 1. ] [0. 0. 3.5] [0. 0. 0. ]] True
Notes — cell 5
## Question 2 (20 marks) **Recognition signal.** A determinant via elimination fails at small/zero pivots, so the question explicitly asks for largest-absolute-value pivoting and swap parity. **First key step.** Inspect `abs(mat[p:,p])`, convert the local argmax to an absolute row index, and swap before division. **Course-method chain.** basic determinant -> complexity -> pivot selection -> REF with parity -> diagonal product with sign -> numerical validation. **Marking points.** 3+2+1+5+4+3+2. **Common errors.** Forgetting that argmax is relative to the slice; failing to return False on a zero column; omitting determinant sign; mutating integer arrays. **Final self-check.** Compare with `np.linalg.det`; singular matrices should produce a value close to zero. ### Historical-basis and difficulty audit - **Historical formal-question basis:** 2026 Alternative Q2(i-a to ii-h) - **Accessible course-material basis:** 2026 Alternative question paper (no marking scheme supplied ) - **Historical ability gap filled:** add elimination determinants, partial pivoting and swap signs - **Equivalent 2026 difficulty unit:** 2026 Q2(20 mark long-algorithm difficulty unit ) - **Why equivalent:** comparable long functions, boundary matrices, numerical checks and complexity explanations; omit 4 mark symbolic proof to keep the total duration within 60 minutes .
Code — cell 6
def my_det_basic(mat): return float(np.prod(np.diag(row_echelon_form(mat))))
def zero_column_pivot(mat,p):
candidates=np.abs(mat[p:,p]); local=int(np.argmax(candidates))
if candidates[local]==0: return False
r=p+local; swapped=r!=p
if swapped: row_swap(mat,p,r)
for i in range(p+1,len(mat)):
row_add(mat,i,p,-mat[i,p]/mat[p,p])
return swapped
def row_echelon_pivot(mat):
out=np.array(mat,dtype=float,copy=True); sign=1
m,n=out.shape
for p in range(min(m-1,n)):
if zero_column_pivot(out,p): sign=-sign
return out,sign
def my_det(mat):
ref,sign=row_echelon_pivot(mat)
return float(sign*np.prod(np.diag(ref)))
for M in [np.array([[0.,2,3],[5,6,7],[9,10,12]]),np.array([[1.,2,3],[2,4,6],[3,6,9]])]:
print(my_det(M),np.linalg.det(M))
-6.000000000000011 -6.0000000000000115 -0.0 0.0
Notes — cell 7
## Question 3 (10 marks) **Recognition signal.** A runtime comparison followed by persistence in pickle and tabular CSV form. **First key step.** Use the same randomly generated matrix for all three methods at each dimension. **Course-method chain.** time calls -> nested dict -> pickle.dump -> DataFrame -> to_csv -> existence check. **Marking points.** 4+2+2+2. **Common errors.** Timing different matrices for different methods; opening pickle in text mode; transposing the DataFrame unintentionally. **Final self-check.** The DataFrame index must be 3,4,5 and both output paths must exist. ### Historical-basis and difficulty audit - **Historical formal-question basis:** 2026 Alternative Q3(a-d) - **Accessible course-material basis:** 2026 Alternative question paper (no marking scheme supplied ) - **Historical ability gap filled:** add timing in nested dictionaries , pickle, DataFrame and CSV complete data pipeline - **Equivalent 2026 difficulty unit:** 2026 Q3(10 mark short-synthesis difficulty unit ) - **Why equivalent:** 4 direct steps, limited file operations and structural checks; estimated 8-10 minutes .
Code — cell 8
from pathlib import Path
def det_cofactor(mat):
if len(mat)==1: return mat[0,0]
return sum(((-1)**j)*mat[0,j]*det_cofactor(mat[1:,np.arange(len(mat))!=j]) for j in range(len(mat)))
def execution_time(mat,func):
t=perf_counter(); func(mat); return perf_counter()-t
times={'np.linalg.det':{},'my_det':{},'det_cofactor':{}}
rng=np.random.default_rng(4)
for n in [3,4,5]:
M=rng.random((n,n))
for name,fn in [('np.linalg.det',np.linalg.det),('my_det',my_det),('det_cofactor',det_cofactor)]: times[name][n]=execution_time(M,fn)
with open('revised_det_times.pkl','wb') as f: pickle.dump(times,f)
df=pd.DataFrame(times); df.to_csv('revised_det_times.csv')
print(df); print(Path('revised_det_times.pkl').exists(),Path('revised_det_times.csv').exists())
np.linalg.det my_det det_cofactor 3 0.000033 0.000107 0.000512 4 0.000021 0.000100 0.000788 5 0.000012 0.000113 0.004514 True True