[home]

all projects

pack-ev
Prices every card a Magic: The Gathering booster can contain, weighted by Wizards of the Coast's published slot odds, and compares the result with the sealed pack's price for 39 sets and 94 booster products, week by week since June 2021. The full value distribution of a pack comes from convolving per-slot distributions on a one-cent grid. Weekly TCGplayer history lives in a Parquet store that is backfilled once per product and then updated incrementally, so refreshing every set takes about five minutes. Finds that Collector Boosters are a coin flip at launch and worth a median 76% of their sealed price by year two, while Play Boosters sit at break-even only if bulk commons can actually be sold.[dashboard] [blog] [code]
TM-Env
Oracle-verified Terra Mystica environment for long-horizon multi-agent reinforcement learning: a PettingZoo AEC wrapper and a Gymnasium single-agent wrapper over a rules engine that replays 3,552/3,553 tournament games with zero per-move mismatches. Legal action masks are read from the engine's own move generation rather than restated, so a divergent second rulebook is structurally impossible; observations and full game state round-trip to bytes without pickle; rewards are terminal-VP-based with a documented dense variant, plus an offline-RL export over the 20.5M logged human decisions in the corpus (1.20M of them tmtour Div 1-3). Masked random play measures 59.1 VP per player at four seats against 119.0 for the imitation net and 105.2 for max^n MCTS at the champion search config. PettingZoo's api_test, its seed test and Gymnasium's check_env pass at 2-5 players. Building it surfaced a cross-process determinism bug in the engine: bridge spans were unpacked from a frozenset, so hash randomisation flipped their orientation between processes and shifted every later action index.[code]
bgelo
Glicko-1 rating engine for multiplayer board games, decomposing n-player results into 1v1 matchups weighted by game complexity, relative familiarity, elo spread, and duration. Algorithm auto-tuned by backtester that scores pre-play forecasts to tune weighting mechanisms. Currently forecaster accurately calls 62.5% of decisive pairwise matchups against a 50% baseline. Accuracy should improve with data, currently very low sample-size (n<10 for most games).[stats] [blog]
ercot-nodal
Eight-bus security-constrained dispatch model of ERCOT, built to work out how locational prices actually form and where grid-scale or residential storage is worth siting. Each hour is a linear program over a DC network with two generic transmission constraints and reserves co-optimised against a demand curve; every bus price is read off the duals as the system lambda minus its shift-factor-weighted shadow prices. A home-battery fleet is layered on as a price taker, its schedule and the resulting price iterated to a fixed point by the method of successive averages. Verified rather than trusted: shift factors reproduce an independently coded DC power flow, and the price at every bus equals the finite-difference derivative of total dispatch cost with respect to load there. Finds that congestion lives in surplus hours rather than peak ones, and that the cheapest bus on the system is the worst place to put a battery — a binding export constraint moves the price level, not the time spread storage is paid for.[model]
openbsl
Reads Biopac Student Lab physiology recordings (ECG, pulse, heart sounds) without the proprietary BSL software. Exports them to CSV and builds a static browser viewer that reproduces BSL's measurement workflow: drag-select a region, read off Delta T, BPM and peak-to-peak amplitude, label it, and the lesson's data report tables fill themselves in. Beat detection is tested against synthetic ECG with known ground truth. Automatic P/T-wave delineation was benchmarked on real student leads and rejected: on a noisy lead its beat-to-beat jitter was as large as the effects the lab measures. Lessons 6 and 16 are automated where the signal allows it: the mean electrical axis from Lead I and III QRS amplitudes, and Korotkoff sounds found by their lock to the heartbeat rather than their loudness, validated on synthetic recordings with known pressures.[case study] [code] [blog]
dr.active
Predicts protein-ligand binding affinity (pChEMBL) from a SMILES string and a target name, over 55,176 ChEMBL activity records across six well-populated, mechanistically different targets. A random forest on RDKit descriptors and Morgan fingerprints reaches RMSE 0.648 log units on a random split and 0.761 on a scaffold-disjoint one, against 1.18 for a per-target-mean baseline. The experimental message-passing GNN loses on every split, and the frequency-weighted scaffold split refutes the one thing it looked good at: its apparent robustness to held-out chemotypes was a property of the split, not the model. Two independent scaffold splits agree to 0.02 log units, so the honest reading is that an unseen scaffold costs about 0.12 log units — and that 0.76 is still a factor of six in concentration, good for ranking rather than for predicting a number. The forest's single most important feature is a target indicator, which is most of what the baseline already captures.[code]
Ripple
Minnehack 2025 Finalist. Built for the theme “finding community,” users complete community-driven challenges with peer-based verification to climb local leaderboards.[demo] [code]
GopherGuessr
GeoGuessr for the UMN Twin Cities campus, built by a student team. I contributed to the React frontend: the play screen's guess map, the results and end-of-game dialogs, and the home page's stats carousel.[play] [code]
BioScrolls
Mines PubMed abstracts on five neurological diseases for gene-disease, drug-disease and gene-drug associations. A BioBERT encoder fine-tuned on BioRED reaches 0.845 micro-F1 on entity recognition and 0.655 typed F1 on relation extraction on the held-out test split, against 0.452 for a co-occurrence baseline. Extractions over 3,033 abstracts become a time-indexed SQLite knowledge graph of 8,921 typed, dated edges, queryable by entity, year and per-year trend. 520 of those abstracts are non-English-language articles, mined through the English titles and abstracts PubMed supplies for them — the models themselves are English-only. End to end, from raw text to normalised entity ids, it scores 0.275 F1: dictionary normalisation, not extraction, is the bottleneck.[code]
dr.optimus
A MolDQN-style double-DQN agent that edits molecular graphs under valence-checked RDKit actions, reproducing Zhou et al. 2019 on the three standard benchmarks and reporting where the reproduction falls short. On QED a graph-neural-network Q-head reaches final-episode top-three 0.822/0.811/0.805 against the paper's 0.934, beating both a random-edit baseline (0.211) and a fingerprint MLP given four times the episodes — one seed per configuration, so suggestive rather than established. Penalized logP fails and the failure is the interesting part: with terminal-only reward the policy is beaten by its own random baseline, and paying the per-step change in objective instead recovers about half the gap, which locates the cause in reward scale over a 40-step horizon rather than in the agent. On the similarity-constrained task it loses to random editing outright (+0.92 mean improvement against +1.38, at 27.8% of molecules improved against 36.4%) and tripling the training budget does not help; its greedy policy degenerates to making no edit at all.[code]
folduzz
A DCGAN over 64x64 CA-CA distance matrices cut from 735 PDB structures, kept because of what measuring it carefully showed. It learns real geometry: neighbouring residues land 4.24 A apart against a true 3.81, triangle violations fall to 2.54% where a matrix with the identical distance histogram breaks them on 9.57%, and contact density, contact order and radius of gyration all sit within 10% of the real distributions. It is not memorising either — samples are further from the training set than real held-out windows are. But the output is not a valid distance matrix: symmetry is never learned, 19.5% of the eigenvalue mass is negative, and after MDS embedding only 30.1% of consecutive residues sit at a plausible bond length against 97.6% for real windows. Scoring every checkpoint shows everything geometric plateaus by epoch 60 while only the distance histogram keeps improving — the adversarial objective never asks for geometric consistency, so training longer cannot fix it.[code]

← back home