Hi, I'm Akshay Nair. I'm a third-year undergraduate at Stevens Institute of Technology, majoring in Mathematics with a minor in Quantitative Finance.
I'm building Phasm, a simulation startup that makes analysis for drone and aircraft design fast and effortless. I've also done nanomaterials research in Prof. Eui-Hyeok Yang's Advanced Quantum Materials Lab at Stevens, mentored by Nabajit Baidya.
Sep 2025Joined Prof. EH Yang’s Advanced Quantum Materials Lab at Stevens.
Projects
CV
Education
2024–2028BS, MathematicsStevens Institute of Technology · Minor in Quantitative Finance · Expected May 2028Activities: Launchpad · Sigma Phi Epsilon
2024Bergen County AcademiesAcademy of Engineering & Design Technology
Experience
2026–Founder & CTO, PhasmBuilt Dutchman, an engine that reads a drone’s CAD, generates flight and landing load cases, and runs finite element analysis to return stress results and ranked weak spots, cutting structural checks from days to minutes. Launched it with a design-partner program for UAV teams.
2025–26Undergraduate ResearcherAdvanced Quantum Materials Lab, Stevens · PI: Prof. EH Yang · Mentor: Nabajit BaidyaGrew 2D materials, including MoS₂, by chemical vapor deposition, and inspected the growths by optical microscopy.
2023–24Media Lab InternSUNY Fashion Institute of Technology, New YorkRebuilt 3D printers for the Animation, Interactive Media & Game Design Department, and supported game design and digital media projects.
Leadership & activities
2022Eagle Scout, Boy Scouts of AmericaLed a bat-box Eagle project from design and fundraising to installation
2020–Independent TutorMath, writing & computer science for high school students
Musician (piano, guitar)Music-therapy performances at the Jewish Home at Rockleigh · Piano score for For the Love of Art, Manhattan Film Festival 2022
Coursework
Now (Fall 2026) — Probability & Statistics · Discrete Mathematics · Intro to Quantitative Finance · Intro to Computer Science · Macroeconomics
Mathematics — Linear Algebra · Differential Equations · Multivariable Calculus I & II · Infinite Series · Vectors & Matrices · Calculus I & II
Engineering & physics — Mechanics of Materials · Statics · Dynamics · Thermodynamics · Circuits & Systems · Data Acquisition Systems · Mechanics · Electricity & Magnetism · Intro to Nanotechnology · Intro to Programming
Entrepreneurship — Stevens Launchpad: Entrepreneurial Thinking · Entrepreneurship Experience I & II
METHODtwo engines, cycle insertion and a stabilizer chain; the shortest verified solution wins
SEARCHparallel Web Workers, each with its own random seed
LENGTH~620–660 twists in 1–10 s; ~520–560 at 30 s and up; best 477
CHECKevery solution is replayed before it is accepted
CODEJavaScript, no dependencies, runs in the browser
CONTENTS
The state is 216 numbers
A twist is one of 23 rotations
Seeing in four dimensions
Commutators move almost nothing
The stabilizer chain
Cycle insertion
Parallel search, checked twice
The record method
EXHIBIT A: THE STATE IS 216 NUMBERS
Every sticker position has a number from 0 to 215, and the whole puzzle is one array: state[p] is the sticker sitting at position p. A twist is a fixed permutation of positions, so applying one is a single pass over the array. The puzzle is solved exactly when state[p] = p everywhere.
src/engine.jsLINES 220–239
function solvedState() {
const s = new Uint8Array(N_STICKERS);
for (let i = 0; i < N_STICKERS; i++) s[i] = i;
return s;
}
function applyGen(state, genId) {
const perm = gens[genId].perm;
const next = new Uint8Array(N_STICKERS);
for (let p = 0; p < N_STICKERS; p++) next[perm[p]] = state[p];
return next;
}
function applyWord(state, word) {
let s = state;
for (const g of word) s = applyGen(s, g);
return s;
}
function isSolved(state) {
for (let p = 0; p < N_STICKERS; p++) if (state[p] !== p) return false;
return true;
}
STATE, ONE ROW PER CELLAT HOME
EACH SQUARE IS A POSITION; ITS COLOR IS THE STICKER THERE NOW. TWIST THE CUBE TO WATCH THE ARRAY CHANGE.
EXHIBIT B: A TWIST IS ONE OF 23 ROTATIONS
A twist grabs one cell and turns it the way you would turn a cube: 24 rotations, minus the identity. In 4D each one is a signed permutation matrix that fixes the cell’s own axis and has determinant +1. A twist acts on 81 stickers, the cell’s own 27 plus 9 from each of its 6 neighbors, and 8 cells × 23 rotations give 184 twists.
src/engine.jsLINES 86–108
// All 24 proper rotations of the 3-space spanned by the axes != k (identity included).
if (permSign(p) * sg[0] * sg[1] * sg[2] !== 1) continue; // det must be +1
const m = new Array(16).fill(0);
m[k * 4 + k] = 1;
// e_{oa[i]} maps to sg[i] * e_{oa[p[i]]}
for (let i = 0; i < 3; i++) m[oa[p[i]] * 4 + oa[i]] = sg[i];
out.push(m);
}
}
return out; // 24 matrices
}
TRY THEM ON THE −W CELLSTICKERS MOVED
ON THE CUBE ITSELF: CLICK A FACE-CENTER STICKER FOR 90°, AN EDGE STICKER FOR 180°, A CORNER STICKER FOR 120°.
EXHIBIT C: SEEING IN FOUR DIMENSIONS
Every sticker is a small cube sitting in 4D. Each frame, its corners are turned in 4D, divided by their distance from the eye along w (4D to 3D), turned in 3D, divided by depth (3D to 2D), and the faces are painted back to front. The cell farthest from the eye lands small in the middle; the nearest one wraps around everything, as the shell, and is hidden here.
One twist moves 76 or 78 stickers. The commutator A·B·A⁻¹·B⁻¹ undoes most of that: if A and B touched disjoint sets of stickers, the four steps would cancel completely, so whatever is left comes from where they overlap. Commutators and conjugates of short words are how both solvers find moves that change almost nothing.
src/solver.jsLINES 360–362, 374–391
// Commutators [u,v] = u v u^-1 v^-1 and conjugates w u w^-1 of SHORT table
// words tend to fix most stickers, i.e. they sift deep down the chain while
// staying short — exactly where naive random training leaves long words.
…
for (let k = 0; k < count; k++) {
const u = shorts[Math.floor(rng() * shorts.length)];
const v = shorts[Math.floor(rng() * shorts.length)];
if (u === v) continue;
if (rng() < 0.6) {
// commutator
const word = u.word.concat(v.word, invWordIds(u.word), invWordIds(v.word));
The first solver is a Schreier–Sims stabilizer chain with short-word tables. The 208 stickers that can move are put in order b₁, b₂, …, and level i holds, for each position p, a short word that fixes b₁ … bᵢ₋₁ and sends bᵢ to p. Solving sifts the scramble down the chain: each level applies the inverse of its matching word, which puts bᵢ home without disturbing the ones before it. When nothing is left out of place, the collected words, deepest level first, are the solution. At solve time a beam search tries many alternative words per level and keeps the shortest result.
src/solver.jsLINES 140–157
// Plain single-path factorization (used during training probes).
function factor(perm) {
let cur = Uint8Array.from(perm);
const parts = [];
for (let i = 0; i < B.length; i++) {
const b = B[i];
const p = cur[b];
if (p === b) continue;
const e = table[i].get(p);
if (!e) return null;
parts.push(e.word);
cur = permMul(cur, e.inv);
}
if (!permIsId(cur)) return null;
const out = [];
for (let i = parts.length - 1; i >= 0; i--) out.push(...parts[i]);
return out;
}
TABLELOADING…
EXHIBIT F: CYCLE INSERTION
The second solver is an algorithmic version of 3-style, the commutator method blindfold solvers use. It builds the solution from conjugates w⁻¹·ℓ·w of library primitives ℓ: short words that move only a few pieces, such as 3-cycles, twirls and double cycles. Each step looks for setups w that make a primitive match what is still unsolved exactly, on its whole support. A hash join does the matching: one side indexes where the residue sends every sticker under each setup, the other probes it with each primitive. When no insertion beats the chain’s rate of twists per sticker fixed, the rest goes to the stabilizer chain, and the shorter of the two answers wins.
src/insertion.jsLINES 245–259, 281–289
// backward table over (a,b) pairs: b = w2⁻¹(R(w2(a))), b != a
const buckets = new Array(N * N);
for (let i2 = 0; i2 < W2.length; i2++) {
const w2 = W2[i2];
for (let a = 0; a < N; a++) {
let x = a;
for (let j = 0; j < w2.length; j++) x = E.gens[w2[j]].perm[x];
let b = R[x];
for (let j = w2.length - 1; j >= 0; j--) b = E.gens[E.gens[w2[j]].inv].perm[b];
if (b === a) continue;
const key = a * N + b;
if (buckets[key] === undefined) buckets[key] = [];
buckets[key].push(i2);
}
}
…
for (let idx = 0; idx < fj.n; idx++) {
if (idx > maxProbe && out.length > 0) break;
const y0 = fj.y0[idx], ly0 = fj.ly0[idx];
let hits = buckets[y0 * N + ly0];
if (hits !== undefined) {
for (const i2 of hits) {
if (verifyFlat(idx, -1, W2[i2]) && emit(idx, -1, i2)) return;
}
}
LIBRARYLOADING…
EXHIBIT G: PARALLEL SEARCH, CHECKED TWICE
The timed tiers below, 4 S and 60 S, run the insertion solver in parallel Web Workers, each with its own random seed. Up to 10 s is the instant tier: every worker runs one fast solve. Above that, each worker keeps running independent solves until its time is up, and posts every improvement the moment it is found, after checking that it really solves the puzzle. The page stops when the best length stops improving or the cap is reached, replays the winner once more against the current state, and only then plays it. RECORD, the default for SOLVE, is the method in Exhibit H.
The fewest-moves records follow the human method. Build a 2×2×2×2 block of eleven pieces, extend it axis by axis until every piece outside one cell is home, which is the first two layers, then turn the last cell's pieces the right way round and finish that cell as a virtual 3×3×3. The computer-assisted record from 2025, 122 moves, built its first two layers with a search program and finished the cell by hand.
SOLVE runs that method here. The first two layers come from a blockbuilding search modelled on that program. It tracks solved blocks, each a box of layers with an attitude. A twist splits the blocks it touches, rotates the inside parts and merges what fits. Each pairing step looks a few twists ahead from thousands of carried candidates. To turn the last cell's pieces round, the page searches over every pure algorithm of six twists or fewer, about fifteen thousand, with exact distance tables for each kind of piece as its guide. A two-phase solver then solves the virtual 3×3×3, each face turn becomes a twist of a neighbouring cell, and a short tail of twists repays what those cells owe.
It takes a minute or two and lands around 130 to 145 twists, against 550 to 650 for the timed tiers in Exhibit G and a hand record of 191. Most of the gap to 122 is in the last step, which pays a rotation of the cell for almost every face move.
f2l.jsLINES 215–226
function twistBlocks(blocks, t) {
const out = [];
for (let k = 0; k < blocks.length; k++) {
const b = blocks[k], L = layersOf(b);
const i = restrictActive(L, t.grip);
if (i < 0) { out.push(b); continue; }
out.push(blk(rotateLayers(t.e, i), MUL[t.e * NE + attOf(b)]));
const o = restrictInactive(L, t.grip);
if (o >= 0) out.push(blk(o, attOf(b)));
}
return mergeAll(out);
}
THE LAST RECORD SOLVE ON THIS PAGE
F2L—
OLC—
PLC—
TOTAL—
NO LOCK SET
THE PANELS BELOW SHOW THE LOCK SET ON THIS KEYPAD.
THE LOCK
KEYnone. Four rounds of twenty digits you call random set it; one more round opens it. The pad is 1 2 3 / q w e / a s d / x on a keyboard
LOOKS ATwhich digit you pick after which, the time from each press to the next, and how long each key is held
MODELa smoothed bigram over the digits; log-normal intervals with means built up from your pace, how far the hand travels, the digit and the pair; log-normal holds
SCOREhow much likelier the round is under your model than under a generic typist’s, in bits per press
BARset from the rounds that set the lock: 1.5 standard deviations below their middle, at least half a bit
UNSUREa try within a quarter bit of the bar gets twenty more digits before a verdict
LEARNSa try that opens it by half a bit or more joins the key, up to sixteen rounds; the oldest learned round goes first
STAYSin this browser (localStorage); nothing leaves the page
CODEJavaScript, no dependencies; the model is 180 lines
CONTENTS
What a round records
What you call random
Your rhythm
The score and the bar
Impostors
EXHIBIT A: WHAT A ROUND RECORDS
A round is twenty presses. For each one the lock keeps the digit, the moment the key went down and the moment it came up. From those come the three things it scores: the digit, given the one before it; the interval from the previous press to this one; and the hold, down to up. Intervals and holds are taken in logarithms, since a slow typist’s spread is wider in milliseconds but about the same in proportion. The table is your latest round, with the evidence each press gave once the round was judged.
Asked for random digits, people aren’t. They avoid repeating the last digit, step to a neighbour more than chance would, and have favourites. The lock counts which digit you pick and which you pick after which, then smooths the counts in a hierarchy: a pair of digits seen once or twice borrows from how far you tend to jump and from how often you pick that digit at all, and those borrow from uniform. The grid is your transition table: rows are the digit before, columns the digit you picked next, darker likelier.
assets/randlock/model.jsLINES 62–70
// Digits: which you pick, and which you pick after which. Counts are smoothed in a hierarchy, each
// level's prior the one above it: uniform → how often you pick each digit → how far you jump from the
// last one (0 is a repeat) → which digit you pick after each digit.
WHICH DIGIT FOLLOWS WHICHROW: BEFORE · COLUMN: NEXT
EXHIBIT C: YOUR RHYTHM
Intervals are modelled as log-normal. The mean time for a pair of digits is built in layers, each a group’s average leftover, pulled toward zero while the group is small: your overall pace; how far the hand travels on the pad, a repeat, a neighbour, a diagonal or a reach, which four well-filled groups estimate where a hundred pairs can’t; the digit you land on; and the pair itself, once it has been seen enough to earn its own mean. One spread serves all pairs, estimated robustly from the residuals and inflated a little, since a fresh round scatters more than the rounds the fit came from. Holds get the same treatment, one mean per digit. The histogram is every interval the key holds; below it, how your time changes with the distance travelled, and how long you take to reach each digit.
for (let p = 0; p < 10; p++) for (let d = 0; d < 10; d++) muPair[p * 10 + d] = dtMu + clsOff[cls(p, d)] + dOff[d] + pairOff[p * 10 + d];
EVERY INTERVAL THE KEY HOLDS100 MS TO 2 S, LOG SCALE
TIME BY DISTANCE TRAVELLED ON THE PAD
TIME TO REACH EACH DIGIT
EXHIBIT D: THE SCORE AND THE BAR
Every press is scored as a log-likelihood ratio: how much likelier its digit, interval and hold are under your model than under a background typist who picks digits uniformly and types at a generic pace. Summed and divided by the presses, that is the round’s bits per press; positive means more you than anyone. The bar comes from the four rounds that set the lock: each is scored against a lock built from everything else the key holds, and the bar sits 1.5 standard deviations under their middle, at least half a bit, so an off day still opens it. The spread is taken from round to round and from press to press within the rounds, whichever is larger, since four rounds can understate it. Rounds learned later sharpen the model but are never the yardstick: they were chosen for scoring well, and measuring them would make you look steadier than you are. A try within a quarter bit of the bar is not called; it gets twenty more digits, and the verdict comes from all forty.
ROUNDSCORED AGAINST A LOCK SET FROM THE OTHERSBITS/KEY
EXHIBIT E: IMPOSTORS
What catches what. Random digits at your rhythm test the digit model; your digits shuffled test the pair counts in particular; your digits at another tempo test the rhythm. A metronome playing your averages back scores higher than you ever do, since every press lands dead on a mean: the ratio alone cannot tell it from you, so a second check compares how much the round’s intervals scatter with how much yours do, and turns away anything too regular. A recording of you, replayed exactly, opens it: this lock knows how you type, not that it is you typing. Nor can it stop someone who has watched you: your pace is there to be seen, and on the same keyboard holds are much alike, so what is left to catch an imitator is the fine structure, how your time changes with the digit and the distance, and your steadiness. In simulation, with typists given their own speeds, holds and habits, the owner opens it about nine times in ten and a stranger about one in ten; a friend who matched the pace and hold within ten percent and half-copied the digit habits got in a quarter of the time from a fresh key. Two things wear that down: a try near the bar gets twenty more digits, and a clear open is learned, so the key sharpens with use while its yardstick stays the four rounds that set it. The key is still a few dozen keystrokes long, and a patient mimic with feedback beats every behavioural biometric of that size.