Back to courses

MATH40006 Practice Paper 5 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: b4d5fd1bafd6c51adda4a60f7f1323921418449018853db919ce215573b36808
Source date: 2026-08-06

Notes — cell 1

# MATH40006 Revised Historical-Coverage Mock 5 - Course-Method Audited Solutions

**Time allowed:** 60 minutes 
**Total marks:** 50 
**Role:** 2021-22 gap repair: escape-time arrays, symbolic dynamics and ordered lookup

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()

records=[('Ant','small insect'),('Bear','large mammal'),('Cat','domestic feline'),('Dog','domestic canine'),('Eel','long fish'),('Fox','wild canine'),('Goat','hoofed mammal'),('Hare','fast mammal')]

Notes — cell 3

## Question 1 (20 marks)

**Recognition signal.** A state array must be updated selectively across a complex grid until escape or an iteration limit.

**First key step.** Copy z0 inside the function and initialise the result array before the loop.

**Course-method chain.** grid -> initial mask -> one masked iteration -> reusable masked loop -> unresolved fill -> logarithmic display for two parameter regimes.

**Marking points.** 3+3+3+7+4.

**Common errors.** Updating all points after they escape; modifying the caller array; recording escape after rather than before the matching iteration; wrong image orientation.

**Final self-check.** Output shape equals input shape, entries are integers in `[0,max_iterations]`, and the original z0 is unchanged.

### Historical-basis and difficulty audit

- **Historical formal-question basis:** 2021-22 Assessment 3 Q2(a-f)
- **Accessible course-material basis:** 2021-22 official marking scheme 
- **Historical ability gap filled:** add complex iteration, Boolean masks and escape-time plots: these are 2026 historical high-mark skills absent from that paper 
- **Equivalent 2026 difficulty unit:** 2026 Q1(20 mark array / graphical difficulty unit )
- **Why equivalent:** again uses two-dimensional grids, vectorisation and graphical output; iteration is more abstract but there are fewer graph types; estimated 20 minutes .

Code — cell 4

xr=np.linspace(-1.6,1.6,220); yr=np.linspace(-1.6,1.6,220); X,Y=np.meshgrid(xr,yr); grid=X+1j*Y
et=-np.ones(grid.shape); et[(et==-1)&(np.abs(grid)>=2)]=0
z=grid.copy(); unresolved=et==-1; z[unresolved]=z[unresolved]**2+grid[unresolved]; et[(et==-1)&(np.abs(z)>=2)]=1

def escape_time(c,z0,max_iterations):
    z=np.array(z0,dtype=complex,copy=True); ans=-np.ones(z.shape,dtype=int)
    for r in range(max_iterations):
        ans[(ans==-1)&(np.abs(z)>=2)]=r
        mask=ans==-1
        z[mask]=z[mask]**2+c[mask]
    ans[ans==-1]=max_iterations
    return ans
for c,z0,title in [(grid,np.zeros_like(grid),'parameter plane'),(np.full_like(grid,-0.12+0.74j),grid,'fixed c')]:
    t=escape_time(c,z0,80); plt.figure(); plt.imshow(np.log(t+1),origin='lower',extent=[-1.6,1.6,-1.6,1.6]); plt.title(title); plt.show()
Original notebook output
<Figure size 640x480 with 1 Axes>
Original notebook output
<Figure size 640x480 with 1 Axes>

Notes — cell 5

## Question 2 (20 marks)

**Recognition signal.** The entrance signal is a polynomial iteration, fixed/periodic points and the stability boundary `|derivative|=1`.

**First key step.** Solve `f-z=0`, then substitute each root into the derivative before separating real and imaginary parts.

**Course-method chain.** fixed roots -> derivative modulus -> lambdify/contour -> factor out fixed points from f2-z -> genuine period-2 roots -> repeat stability calculation -> interpretation.

**Marking points.** 2+3+4+5+4+2.

**Common errors.** Treating fixed points of f2 as all period-2; forgetting real assumptions on a,b; using absolute value on a symbolic expression in a way lambdify cannot vectorise; unequal axes.

**Final self-check.** The quotient roots must not satisfy f(z)=z generically, and contours should render without shape errors.

### Historical-basis and difficulty audit

- **Historical formal-question basis:** 2021-22 Assessment 3 Q3(a-h)
- **Accessible course-material basis:** 2021-22 official marking scheme 
- **Historical ability gap filled:** add fixed points, period- 2 points, derivative stability and equal-value boundaries 
- **Equivalent 2026 difficulty unit:** 2026 Q2(20 mark highly abstract method-chain difficulty unit )
- **Why equivalent:** symbolic solution , lambdify, composite mappings and contours form 6 steps; code length is controlled, estimated 23-25 minutes .

Code — cell 6

z=sp.symbols('z'); a,b=sp.symbols('a b',real=True); c=a+sp.I*b; f=z**2+c
fixed=sp.solve(sp.Eq(f,z),z); df=sp.diff(f,z)
def modsq(expr): return sp.simplify(sp.re(expr)**2+sp.im(expr)**2)
fixed_mod=[modsq(df.subs(z,r)) for r in fixed]
A,B=np.meshgrid(np.linspace(-2,1,151),np.linspace(-1.5,1.5,151))
plt.figure()
for e in fixed_mod:
    fn=sp.lambdify((a,b),e,'numpy'); plt.contour(A,B,fn(A,B),levels=[1])
plt.axis('equal'); plt.title('fixed-point stability boundaries'); plt.show()
f2=sp.expand(f.subs(z,f)); quotient=sp.factor(sp.cancel((f2-z)/(f-z))); period2=sp.solve(sp.Eq(quotient,0),z); df2=sp.diff(f2,z)
period_mod=[modsq(df2.subs(z,r)) for r in period2]
plt.figure()
for e in period_mod:
    fn=sp.lambdify((a,b),e,'numpy'); plt.contour(A,B,fn(A,B),levels=[1])
plt.axis('equal'); plt.title('period-2 stability boundaries'); plt.show()
print(fixed); print(quotient); print(period2)
Original notebook output
<Figure size 640x480 with 1 Axes>
Original notebook output
<Figure size 640x480 with 1 Axes>
[1/2 - sqrt(-4*a - 4*I*b + 1)/2, sqrt(-4*a - 4*I*b + 1)/2 + 1/2]
a + I*b + z**2 + z + 1
[-sqrt(-4*a - 4*I*b - 3)/2 - 1/2, sqrt(-4*a - 4*I*b - 3)/2 - 1/2]

Notes — cell 7

## Question 3 (10 marks)

**Recognition signal.** The data are ordered by key, inviting comparison of linear search, binary search and indexed containers.

**First key step.** For binary search maintain inclusive `lo,hi` indices and update past the midpoint.

**Course-method chain.** linear search -> complexity -> iterative binary search -> hit/miss tests -> dict/Series conversion -> repeated timing with cautious interpretation.

**Marking points.** 2+1+4+1+2.

**Common errors.** Infinite loop from failing to move past mid; slicing at every recursive call; treating one timing as an exact complexity proof; Series lookup by numeric position.

**Final self-check.** All four methods must return the same value for a successful key; both search functions return failed for misses.

### Historical-basis and difficulty audit

- **Historical formal-question basis:** 2023-24 Assessment 3 Q3(i-d to ii-e)
- **Accessible course-material basis:** 2023-24 official marking scheme 
- **Historical ability gap filled:** add sequential/ bisection /dict/Series four search entry points and transfer of complexity analysis 
- **Equivalent 2026 difficulty unit:** 2026 Q3(10 mark short-comparison difficulty unit )
- **Why equivalent:** only core searches and small-scale timing are required; about 4-5 steps, estimated 10 minutes .

Code — cell 8

def lookup_linear(records,key):
    for k,v in records:
        if k==key:return v
    return 'failed'
def lookup_binary(records,key):
    lo,hi=0,len(records)-1
    while lo<=hi:
        mid=(lo+hi)//2
        if records[mid][0]==key:return records[mid][1]
        if records[mid][0]<key:lo=mid+1
        else:hi=mid-1
    return 'failed'
for key in ['Eel','Aardvark','Zebra']: print(key,lookup_linear(records,key),lookup_binary(records,key))
dic=dict(records); ser=pd.Series(dic)
for name,fn in [('linear',lambda:lookup_linear(records,'Eel')),('binary',lambda:lookup_binary(records,'Eel')),('dict',lambda:dic['Eel']),('Series',lambda:ser['Eel'])]:
    t=perf_counter(); [fn() for _ in range(10000)]; print(name,perf_counter()-t)
Eel long fish long fish
Aardvark failed failed
Zebra failed failed
linear 0.0015993869999419985
binary 0.007337145999940731
dict 0.00042346800000814255
Series 0.013616321000085918