Back to courses

MATH40006 Practice Paper 1 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: 34bbfe2f56476ca1d48b68e3d6eadaf85115702f589b2af4b45a6860fd7c3dee
Source date: 2026-08-06

Notes — cell 1

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

**Time allowed:** 60 minutes 
**Total marks:** 50 
**Role:** 2025-26 regular-assessment structure reinforcement (the only deliberately close paper)

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

Notes — cell 3

## Question 1 (20 marks)

**Recognition signal.** A rectangular domain, a scalar field, two grid resolutions and requests for imshow/contour/quiver/wireframe.

**First key step.** Create the one-dimensional coordinate arrays before evaluating the function.

**Course-method chain.** linspace -> meshgrid -> vectorised expression -> imshow/contour; repeat on a coarse grid for quiver; overlay; then create 3D axes and wireframe.

**Marking points.** 2+2+3+2+3+3+2+3.

**Common errors.** Wrong array order in extent; default upper origin; evaluating derivatives on the fine rather than coarse grid; omitting 3D axes.

**Final self-check.** Check both grid shapes, axis limits and that the vector components are `(cos(y),-x)`.

### Historical-basis and difficulty audit

- **Historical formal-question basis:** 2025-26 Assessment 2 Q1(a-i)
- **Accessible course-material basis:** 2025-26 official marking scheme 
- **Historical ability gap filled:** retain only one paper reinforcing the current structure: the complete chain of grids, colour maps, contours, vector fields and wireframes 
- **Equivalent 2026 difficulty unit:** 2026 Q1(20 mark visualisation difficulty unit )
- **Why equivalent:** 8 marking steps, two grid resolutions and 5 graph types; computational load, abstraction and an estimated 18-20 minutes are directly aligned .

Code — cell 4

x_values1=np.linspace(-2,2,121); y_values1=np.linspace(-np.pi,np.pi,121)
x1,y1=np.meshgrid(x_values1,y_values1)
V=x1**2/2+np.sin(y1)
plt.figure(); plt.imshow(V,origin='lower',extent=[-2,2,-np.pi,np.pi],aspect='auto'); plt.title('imshow of v'); plt.show()
plt.figure(); plt.contour(x1,y1,V); plt.title('contours of v'); plt.show()
x_values2=np.linspace(-2,2,21); y_values2=np.linspace(-np.pi,np.pi,21)
x2,y2=np.meshgrid(x_values2,y_values2)
plt.figure(); plt.quiver(x2,y2,np.cos(y2),-x2); plt.title('rotated gradient'); plt.show()
plt.figure(); plt.contour(x1,y1,V,colors='red'); plt.quiver(x2,y2,np.cos(y2),-x2,color='blue'); plt.show()
fig=plt.figure(); ax=fig.add_subplot(111,projection='3d'); ax.plot_wireframe(x1,y1,V,rstride=5,cstride=5); plt.show()
print(x1.shape,x2.shape,float(V.min()),float(V.max()))
Original notebook output
<Figure size 640x480 with 1 Axes>
Original notebook output
<Figure size 640x480 with 1 Axes>
Original notebook output
<Figure size 640x480 with 1 Axes>
Original notebook output
<Figure size 640x480 with 1 Axes>
Original notebook output
<Figure size 640x480 with 1 Axes>
(121, 121) (21, 21) -1.0 3.0

Notes — cell 5

## Question 2 (20 marks)

**Recognition signal.** The statement supplies `tile`, `flatnonzero`, a periodic Boolean sieve state and the stopping expression `min(p**2,l+1)`.

**First key step.** Initialize all four state variables exactly, then code one update before enclosing it in a while loop.

**Course-method chain.** mark current p -> extend periodic pattern -> suppress p and its multiples -> use flatnonzero to find the next p -> stop -> combine the two prime sources.

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

**Common errors.** Using an array too short for indexing; starting a slice at `p**2` instead of `p**2-1`; choosing element 0 rather than element 1 of nonzero positions; losing prime 2.

**Final self-check.** Compare small outputs with known prime lists and verify divisibility for the five-digit test.

### Historical-basis and difficulty audit

- **Historical formal-question basis:** 2025-26 Assessment 2 Q2(a-i)
- **Accessible course-material basis:** 2025-26 official marking scheme 
- **Historical ability gap filled:** retain the current Pritchard complete sieve implementation and boundary tests 
- **Equivalent 2026 difficulty unit:** 2026 Q2(20 mark long-algorithm difficulty unit )
- **Why equivalent:** initialisation, one-step update, loop encapsulation and five types of tests form a total of 5 stages; code length and an estimated 25 minutes are directly aligned .

Code — cell 6

print(np.tile([True,False,True],4))
print(np.flatnonzero([0,5,0,2]))

def pritchard_sieve(n):
    if n<2: return np.array([],dtype=int)
    sieving_array=np.array([True])
    discards=np.zeros(n+2,dtype=bool)
    l,p=1,2
    while min(p**2,l+1)<=n:
        discards[p]=True
        if l<n:
            sieving_array=np.tile(sieving_array,p)[:n]
            l*=p
        if p<=l:
            sieving_array[p-1]=False
        sieving_array[p**2-1::p]=False
        positions=np.flatnonzero(sieving_array)
        p=l+1 if len(positions)==1 else positions[1]+1
    return np.append(np.flatnonzero(discards),np.flatnonzero(sieving_array[1:])+2)

for n in [1,2,3,47,10007]:
    ans=pritchard_sieve(n)
    ok=all(k>=2 and all(k%d for d in range(2,int(k**0.5)+1)) for k in ans)
    print(n,ans[:20],len(ans),ok)
[ True False True True False True True False True True False True]
[1 3]
1 [] 0 True
2 [2] 1 True
3 [2 3] 2 True
47 [ 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47] 15 True
10007 [ 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71] 1230 True

Notes — cell 7

## Question 3 (10 marks)

**Recognition signal.** Adjacent vector sums, an alternating-list helper and conversion of numerator-denominator vectors to Rational values.

**First key step.** First compute the list of adjacent mediants; its length is one less than the parent list.

**Course-method chain.** neighbor sums -> interleave parents with new values -> repeat n times -> select odd positions -> construct `sp.Rational`.

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

**Common errors.** Dropping the final parent; selecting even positions; dividing NumPy integers instead of constructing exact Rational values.

**Final self-check.** Generation 0 must be `[1]`; generation 3 must contain eight positive rationals in increasing order.

### Historical-basis and difficulty audit

- **Historical formal-question basis:** 2025-26 Assessment 2 Q3(a-f)
- **Accessible course-material basis:** 2025-26 official marking scheme 
- **Historical ability gap filled:** retain current short-sequence / exact-rational structural practice 
- **Equivalent 2026 difficulty unit:** 2026 Q3(10 mark short-synthesis difficulty unit )
- **Why equivalent:** adjacent terms, alternation, iteration, , Rational four steps; about 10-12 minutes, with the same marks and workload as the regular examination .

Code — cell 8

def neighbor_mediants(lis): return [lis[i]+lis[i+1] for i in range(len(lis)-1)]
def interleave(left,middle):
    out=left+middle
    out[::2]=left; out[1::2]=middle
    return out
def refine(lis): return interleave(lis,neighbor_mediants(lis))
def sb_generation(n):
    lis=[np.array([0,1]),np.array([1,1]),np.array([1,0])]
    for _ in range(n): lis=refine(lis)
    return [sp.Rational(int(v[0]),int(v[1])) for v in lis[1::2]]
print(sb_generation(0)); print(sb_generation(3))
[1]
[1/4, 2/5, 3/5, 3/4, 4/3, 5/3, 5/2, 4]