When I first joined the Claude Ambassador programme at the University of Cambridge, I had a simple goal: make advanced AI tools feel approachable and accessible to everyone — not just Computer Scientists.
Over the past two academic terms, that goal evolved into something much bigger than I initially imagined.
What started as a small initiative quickly grew into a vibrant community of students, researchers, and builders eager to explore the future of AI together. Across the year, we organised more than 10 workshops, hosted 2 hackathons, and welcomed over 350 sign-ups from students across a wide range of disciplines.
More importantly, we created a space where people felt empowered to experiment, build, and learn.
There’s a particular satisfaction in writing a few hundred lines of Verilog, hitting compile, and watching a piece of programmable silicon become exactly the circuit you described. I recently spent some time designing and testing three digital systems on an Intel Cyclone V FPGA: a pedestrian-activated traffic light controller, a countdown-timer extension to it, and a Morse code encoder. This post walks through the design decisions, the things I learned about FSM design and FPGA timing, and what each circuit looks like running on real hardware.
Logistic regression is one of the first models you meet in machine learning, and it’s tempting to treat it as a solved problem — call sklearn.linear_model.LogisticRegression, fit, predict, done. But the moment you implement it from scratch on a non-trivial dataset, the abstraction leaks. “Fitting the model” is a gradient ascent problem with its own convergence behaviour, the model’s representational capacity depends entirely on the features you feed it, and the wrong learning rate can make a perfectly good model look broken.
This project was a build-from-scratch implementation of binary logistic regression on a 2D classification problem with overlapping classes. The plan was straightforward: derive the gradient by hand, implement gradient ascent in NumPy, fit a linear model, then enrich the features with Gaussian radial basis functions (RBFs) for non-linear structure. The surprising part — the part I want to write about — was discovering that the supposedly “best” model only revealed its quality after I went back and fixed the learning rate.
Medical imaging is one of the rare domains where you can hold a 3D array of numbers in memory and reasonably call it a person. A CT volume is a stack of 2D slices, each one a thin cross-section captured at a different depth. The engineering question is what to do with it: how do you turn millions of voxels into something a clinician can see, navigate, and trust?
This project was a build-from-scratch volumetric viewer in C++ with Vulkan, working with a real CT dataset of a human skull. It covered three pieces of the pipeline: arbitrary 2D reslicing, surface extraction via marching cubes, and a real-time shader path with fly-through navigation. Each part had its own surprise — usually about how much the geometry of the data dictates what you can do with it.
The inverted pendulum is one of those problems that sounds almost trivial when you describe it — a stick balanced upright on a moving cart — but turns into something genuinely humbling once you try to keep one upright with a controller you designed yourself. I spent two sessions working with a rig that could be configured in two modes: a crane (pendulum hanging below the carriage, stable) and an inverted pendulum (pendulum balanced above, unstable). My goal was to design state-feedback controllers for both configurations and compare what the linear theory predicted with what the hardware actually did.
What made the project interesting wasn’t just getting the system to work. It was watching the tidy linear theory collide with the messy reality of a physical rig — sometimes confirming it, sometimes contradicting it in ways that turned out to be more instructive than the theory itself.
This post is a write-up of that experience: the design process, the experiments, and a few moments where the maths and the hardware had something to say to each other.
I’ve spent years typing on keyboards I didn’t really understand. Every laptop, every cheap office board, every “mechanical gaming” keyboard from a big-box retailer — somewhere underneath the chassis was a PCB, a switch matrix, and a microcontroller doing things I’d never thought about. So when I decided I wanted a split ergonomic keyboard, I made a slightly more ambitious decision than I realised at the time: I’d build one from scratch.
The target was a Lily58 Pro — a popular open-source 58-key split design. Open-source meaning the PCB files, BOM, and firmware are all freely available; I just had to source the parts, solder it together, and bring it up. How hard could it be?
Harder than expected. The build itself was the fun part. The debug was where I actually learned something.
Most of the time, when I need a random number from a particular distribution, I call np.random.normal() and move on. But “where do these samples actually come from?” is one of those questions that keeps coming back. Behind every one-line library call is a chain of transformations starting from a single primitive — a uniform random number on [0, 1] — and ending with a sample from something far less obvious, like a heavy-tailed distribution that doesn’t even have a finite variance.
This project was a tour through that chain. I started by checking that the basics actually work (do samples from np.random.normal() really look Gaussian?), then built up the sampling toolkit piece by piece: the Jacobian formula for transforming densities, the inverse-CDF method, and finally the Chambers–Mallows–Stuck algorithm for alpha-stable distributions, which have no closed-form PDF at all.
There’s a counterintuitive result in control theory: sometimes the human trying to stabilise an aircraft is the reason it becomes unstable. The phenomenon is called pilot-induced oscillation (PIO), and it’s been responsible for some genuinely alarming moments in aviation history — including, famously, one of the early space shuttle landings.
I recently spent some time working through a series of flight-control experiments that build up from “human pilot trying to keep a plane level” to a full PID autopilot with anti-windup protection. The exercise turned out to be one of the cleanest demonstrations I’ve encountered of why classical control theory is the way it is — every concept (phase margin, time delay, integrator wind-up) showed up as a thing I could feel through a joystick rather than an abstract inequality on a page.
This post walks through what I built, what surprised me, and what each stage taught me about the gap between a model and reality.
Radio receivers are everywhere — in your phone, your car, your Wi-Fi router — but the elegant signal-processing chain that turns a faint electromagnetic wave into intelligible sound is rarely visible to the people who use it. I recently spent time building and characterising a superheterodyne AM radio receiver on the medium-wave band, working through each stage from the antenna to the audio output. This post walks through what the architecture does, what I measured at each stage, and what I took away from designing around real-world non-idealities.
Over the past 15 months, I had the opportunity to work as an intern at Arm, contributing to projects that spanned embedded systems, AI-driven tools, process automation, web development, and community engagement. This experience not only allowed me to sharpen my technical skills but also gave me a deeper appreciation for collaboration, knowledge-sharing, and the impact of education in technology.