About

Alejandro J. Soto Franco
Alejandro José Soto Franco
Maths · Rust · CUDA · Quant
About
I was trained as a biomedical engineer at Johns Hopkins University, where research on active matter physics and fluid mechanics pulled me into differential geometry and PDE theory as the right languages for physical systems. The same Riemannian geometric framework that describes defect dynamics in curved active nematic substrates applied to price dynamics on non-stationary diffusion manifolds. That connection shapes everything I build.
My research sits at the boundary of geometric analysis, fluid mechanics, and active matter physics. The active nematics work I did at Hopkins produced a co-authored paper published in Proceedings of the National Academy of Sciences (PNAS 123, e2516670123; arXiv:2503.10880), showing that +1/2 topological defects in confined two-dimensional active nematics generically lock into periodic orbits (golden and silver braids) whose periodicity is governed by metallic ratios and whose chaotic mixing properties are captured by the Burau representation of braid groups.
I previously worked as a Rust trading strategies developer at Anti Capital in New York, where I shipped a pre-debit balance guard that eliminated insufficient-balance order rejections across a multi-exchange market-making execution stack.
I now lead Holonomy Securities as Founding Principal, where a three-person team builds systematic trading engines across prediction markets and equity options on a shared platform substrate. That substrate, Colosseum, is a multi-asset quantitative backtesting and execution platform with a WASM strategy sandbox, CLOB-native data, configurable fill models, and full audit trail, built on a Rust engine with an axum API and a Next.js frontend; we launched it this month. Our primary engine on top of Colosseum is Polybius, a Polymarket-native binary options system with non-stationary SDE regime models and CLOB execution; it has returned roughly 81% since going live. Alongside Polybius we are building three further engines: Malliavin (regime-conditional QQQ options with directional spreads and vol selling), Bismut (volatility surface curvature signals from SSVI-fit Riemannian geometry), and Hsu (manifold-valued realized covariance on SPD(N) with affine-invariant metrics).
In parallel I maintain open-source Rust libraries for geometric computing and stochastic analysis, published on crates.io and PyPI: cartan (Riemannian geometry, Lie-group optimisation, and stochastic processes on manifolds), pathwise (non-Markovian SDE simulation with manifold-valued state), volterra (covariant active nematics on simplicial meshes), and elworthy (a JIT compiler that specialises Bismut-Elworthy-Li formulas into SIMD kernels for unbiased Monte Carlo Greeks on non-stationary SDEs).
Through mermin, my Rust toolkit for k-atic alignment analysis of fluorescence microscopy, I collaborate with the University of Pittsburgh School of Medicine on studies of human vaginal fibroblasts: Minkowski tensor shape descriptors, multiscale structure tensors, persistent homology, and SO(3) holonomy-based topological defect detection applied to the orientational order of cell monolayers.
In April 2026 I landed my first contribution to Mathlib4 (PR #38022): closure lemmas showing that functions with compact multiplicative support form a submonoid, with follow-on results for List, Multiset, and Finset products. In May I followed with NVlabs cuda-oxide (PR #27): converted three silent miscompiles in the Rust→PTX codegen pipeline into hard build errors so kernels that would have run with dropped operations now refuse to compile. Three further PRs have since merged: fma contraction and an -O3 codegen pass (#117), emit-ltoir for cargo-oxide (#256), and cached-backend invalidation on source change (#257).
Outside of work: I cook, DJ, lift, and play Supreme Commander: Forged Alliance.
Education
BS/MSE Biomedical Engineering
Johns Hopkins University · 2025
Experience
Rust Trading Strategies Developer
Anti Capital · New York
Currently
Founding Principal
Holonomy SecuritiesUpstream contributions
| Date | Project | Description | Reference |
|---|---|---|---|
| 12 Aug 2026 | alakazam.jl | The forms module carried ⋆, ⨼ and ∧ and no d, so the field strength of a gauge potential, the Bianchi identity that follows and the Cartan structure equations could not be written. d is the antisymmetrised partial derivative, taken in its p+1 term form, with the slots read once from the expression's free indices and reused for every term: reading them off each term instead orders ∂_a⟦A_b⟧ and ∂_b⟦A_a⟧ differently, and d∘d then fails to close. | !10 · 3c02a43 |
| 12 Aug 2026 | alakazam.jl | ∧ rebuilt each factor through set_indices! and passed only the form indices, so every index belonging to another set was dropped and a spinor-valued one-form came back bosonic. The factors then no longer anticommute, the epsilon contraction antisymmetrises them, and a gravitino bilinear collapses to zero where the form antisymmetry and the Grassmann antisymmetry should cancel. Only the form indices are substituted now, in order, and every other index stays where it was. | !7 · cf5633f |
| 12 Aug 2026 | alakazam.jl | dimension answered for an Index and for an index-position pair, both by reaching through to the set's range field, while IndexSet itself, the object holding the answer, raised MethodError. A script opening on the dimension of the space it works in had to read the field instead. | !5 · ee7cda0 |
| 12 Aug 2026 | alakazam.jl | bench/README.md named the 26-name alphabet ceiling as the outcome to expect from the index supply section. The fall-through landed eleven minutes before that README, so the file described dead behaviour from the day both merged. It now states what the script reports, and keeps the alternative documented, since the script still prints it. | !4 · 9c80de1 |
| 10 Aug 2026 | cuda-oxide | A kernel parameter pointing into shared or local memory reached the PTX entry signature as .ptr .shared. ptxas assembles that, and the driver then refuses the whole module, so every kernel in it becomes unreachable. generated_intrinsics_blackwell shipped such a kernel, so the module that example builds could never be loaded on the architecture it pins. The export now refuses the parameter, naming the kernel, the parameter index and the space; the example declares its barrier as a shared static mut, and its packed-FP8 and TF32 conversions are checked bit-exact against the two formats over 42 cases rather than only compiled. | #772 · 9604db6 |
| 10 Aug 2026 | cuda-oxide | An intrinsic call took its result type from the destination's local and stored the result over that local, discarding any projection on the place, so RET.1 = bswap(x) on a (f64, u8) return carried the whole tuple and (*RET) = bswap(x) stored over the pointer rather than through it. Ordinary calls never showed this, since rustc lowers a projected call destination into a call to a temporary followed by a store, leaving custom_mir as the only way to reach it. destination_type now asks Place::ty for the projected type and store_result_to_place writes through the projection, modelling a deref, a field, a runtime index and a constant index, and refusing anything longer than one element. | #769 · a134e7a |
| 10 Aug 2026 | cuda-oxide | Rust's bswap on a single byte is identity, so mir-lower returned early through a bitcast that checked nothing about its target type, while every other arm of the intrinsic reached cast_integer_value_to_type and refused a type carrying no integer width. An aggregate result therefore emitted bitcast i8 7 to { double, i8, [7 x i8] }, and the failure surfaced as an LLVM parse error against a generated .ll file rather than against the source. Reading the result width before the bitcast restores the property the other arms already hold, that a bad result type is reported against the source. | #768 · 8c76725 |
| 10 Aug 2026 | cuda-oxide | The fuzzer's adapter captured Rust types with [^;]+, which reads a type as everything up to the first semicolon, so [u128; 1] was captured as [u128 and the type RET = ..; anchor left the remainder of the type stranded after the inserted trace declarations. split_type_at_semicolon tracks bracket depth at both capture sites. Neither fault was reachable as checked in, because composite_count = 0 kept tuples and arrays out of the generated program entirely, which also kept the fuzzer clear of the aggregate-with-padding space that produced its most valuable find; raising it to 3 takes aggregate reach over 60 seeds from 0 seeds to 25. | #766 · f501110 |
| 10 Aug 2026 | cuda-oxide | CudaEvent carried both halves of the completion question and CudaStream only the blocking half, so asking whether a stream had finished cost the calling thread its progress. Adds CudaStream::query on the mapping CudaEvent::query already uses, CUDA_ERROR_NOT_READY reading as a completion answer rather than a fault. The stream module's own documentation advertises launch_host_function as the bridge to Rust async, and a Future::poll completes that bridge only when it can answer without parking the executor's thread. The test holds query to returning inside a fraction of the interval the callback occupies, so a body that synchronised first would fail it. | #764 · eb5e179 |
| 10 Aug 2026 | cuda-oxide | Every stream came from cuStreamCreate, so ordering a latency-sensitive kernel against a long background one meant reaching through cuda_core::sys and reproducing the range handling by hand. Adds StreamPriorityRange, new_stream_with_priority and CudaStream::priority, holding two properties of the CUDA model that defeat the obvious reading of the raw API: lower numbers are higher priorities, so a (least, greatest) pair read positionally gives the ordering backwards, and an out-of-range priority is clamped silently, i32::MIN yielding the greatest supported priority with CUDA_SUCCESS and nothing to mark the substitution. clamp matched the driver at all seven probes on a device whose range is least=0 greatest=-5. | #762 · a4321f7 |
| 10 Aug 2026 | cuda-oxide | CudaContext wrapped one of the seven CUlimit values, so raising the device printf FIFO meant calling cuCtxSetLimit by hand with a raw CUlimit. Adds ContextLimit with limit and set_limit, stack_size and set_stack_size keeping their signatures and delegating to the pair. The printf FIFO is the limit that costs users output: it is circular, so a launch that fills it overwrites the oldest entries and the driver reports nothing, leaving a truncated log that reads as a kernel which stopped early. That limit and the malloc heap are also refused once any kernel using printf or device malloc has run in the process, since CudaContext retains the device's primary context and the ordering therefore counts every launch rather than those made through the handle doing the write. | #760 · d268ce6 |
| 8 Aug 2026 | cuda-oxide | verify_operation's doc comment described it as mir-importer pipeline plumbing outside the frontend contract, understating its in-crate role. The first version of this PR replaced that with a claim that mir-importer never calls it, which was wrong: it calls it at mir-importer/src/pipeline.rs:319 through the __private re-export, as the per-function post-translation verification step on the live #[kernel] path. The corrected comment names the full consumer set and records why the function is pub plus #[doc(hidden)], so a later demotion to pub(crate) does not look safe. | #714 · dc159c7 |
| 8 Aug 2026 | cuda-oxide | find_inner_verification_error re-walked the operation tree recursively to name the operation that failed verification. Rewrites the descent as two passes over an explicit stack, preserving the children-before-parent order the recursive contract required, so which operation a multi-failure tree reports stays fixed. Depth tracks region nesting rather than module size, so this was never a live crash, and nothing in the function held that bound in place. First tests for the file, one of which asserts the middle of three malformed siblings is the operation returned. | #713 · 2883eb6 |
| 8 Aug 2026 | cuda-oxide | Static shared-memory globals were named from a process-global AtomicUsize, so the __shared_mem_N index depended on how many other modules the process had lowered first and in what thread order, and two builds of identical source could emit the same globals under different names. Moves the counter onto MirToLlvmConversionDriver, which is already instantiated fresh once per module. The maintainer extended the same fix to __device_global_N on top, which makes device codegen naming reproducible end to end. | #711 · 8fd93f8 |
| 8 Aug 2026 | cuda-oxide | CudaContext exposed no way to choose the primary context's CU_CTX_SCHED_* policy, so reaching CU_CTX_SCHED_BLOCKING_SYNC meant going through cuda_core::sys directly and re-deriving the primary-context caveat at every call site. Adds a SyncPolicy enum with set_sync_policy and sync_policy over cuDevicePrimaryCtxGetState and cuDevicePrimaryCtxSetFlags_v2, replacing only the three scheduling bits so any independently set flag survives. Device-scoped, matching the primary context CudaContext retains, rather than cuCtxSetFlags, which follows whatever context is current on the calling thread. | #710 · c1a00cb |
| 8 Aug 2026 | cuda-oxide | Reading one field of a tuple held in an array copied the whole array first, once per field, because mir.field_addr's verifier accepted struct, union and enum pointees while rejecting tuples, which sent the read down the value path that materialises the entire array. Adds the tuple arm at all three layers with no new MIR op, resolving the GEP slot through StructLayoutInfo::of_tuple so it names the memory slot under rustc's reordering of (u8, u32). A 256-entry table drops from 878 st.local and a 4 KiB per-thread depot to 512 and 2 KiB. | #709 · dcf8b6c |
| 6 Aug 2026 | cuda-oxide | Adds lowering for mutating a canonical-storage enum payload, such as a bool payload stored as a full i8, by rebuilding the enum around the new value and storing it whole. #652 correctly refuses to hand out a raw address for a payload whose bytes are held in converted form. | #673 · e224458 |
| 6 Aug 2026 | cuda-oxide | Adds partial-warp reductions for blocks whose width is not a multiple of 32, so the tail warp has something to call. #655's warp_sums example carried an ad hoc butterfly correct only for a power-of-two tail; this clamps every shuffle source to the last live lane and handles an arbitrary live-lane count in ceil(log2(live)) steps. | #672 · 51aabe9 |
| 6 Aug 2026 | cuda-oxide | Removes a redundant bounds check from ThreadRunMut32::at, which re-derived the pointer and length by hand instead of deferring to the view each variant already holds. The &mut-through-enum-downcast restriction that justified the raw-parts path no longer holds after #652's payload addressing. | #671 · e5be6f5 |
| 6 Aug 2026 | cuda-oxide | Fixes device codegen for constructing a DisjointSlice inside a kernel: the constructor's struct literal only sometimes folds away before import, and when it survives crossing a call the aggregate lowering mistook the slice for a scalar-lowered newtype and found no field to write. Adds mir.construct_disjoint_slice alongside the fixed-arity mir.construct_slice. | #670 · d6fbf02 |
| 5 Aug 2026 | cuda-oxide | Made the warp a ThreadIndex index space so a warp reduction writes its result through the ordinary bounds-checked get_mut, closing #584 with no new uniqueness-witness mechanism. warp_index() mints a witness only for lane 0 of each warp, so no unsafe is needed at the write site. | #655 · 20c1db0 |
| 5 Aug 2026 | cuda-oxide | Gave a thread its whole contiguous run through ThreadRunMut32, closing #583. LinearTiles<N> already proved ownership of N elements for a whole tile; this adds the clipped tail for the thread whose run straddles the end of the buffer, and grid-stride iteration over runs. | #654 · f1b52c0 |
| 5 Aug 2026 | cuda-oxide | Bound a DisjointSlice's runtime row width into its index space at the host boundary, closing #516 and reworking the design closed in #515. A per-call witness cannot carry uniformity across a thread-varying selection, so two threads could disagree about the row width; binding it once at launch removes the choice. | #653 · cd5ef39 |
| 5 Aug 2026 | cuda-oxide | Added enum payload addressing to the MIR importer's projection walker, closing #651. A mutable borrow of a payload had no address to write through and was refused outright, and (x as Variant).field = v failed separately as an unimplemented projection pair; one address fixes both. | #652 · a32ef11 |
| 2 Aug 2026 | alakazam.jl | A coverage and timing benchmark for the simplification API under a new bench/. Twelve expressions whose answers are known are put through every public entry point, and five of them reduce under exactly one, a different one each time, so a caller has to know which entry point a case needs. Canonicalisation is timed against the number of contracted dummy pairs, with both orderings of each expression compared so the timing covers work that happened. | !3 · d4a0166 |
| 2 Aug 2026 | alakazam.jl | generate_indices looked the first declared index up in ORDERED_POOLS and stopped at the pool holding it, so an index set was capped at one 26-name alphabet though the pools hold 1425 names across 55, and a template name in no pool at all raised where the Index constructor accepts it. Generation now searches the template's own pool first and the rest after, leaving the names unchanged wherever one alphabet suffices, and returns nothing on exhaustion, which is the case the six call sites already tested. | !2 · 866bc27 |
| 2 Aug 2026 | alakazam.jl | test/runtests.jl opens with using Test, and Project.toml declared no [extras] or [targets], so Pkg.test() exited on Package Test not found in current path before running anything. A REPL that already holds Test resolves it from the session, which is why the suite ran interactively and failed only where CI would run it. With the target declared, a clean checkout runs the 247 tests to completion. | !1 · e105c1e |
| 31 Jul 2026 | cuda-oxide | Opt-in libdevice linking for the standalone PTX API, closing #485. A frontend driving cuda_oxide_codegen::experimental stopped at the first sqrt, since float intrinsics lower to libdevice __nv_* calls and v1 rejected every unresolved external symbol, at a site sitting directly above the IR-level libdevice link that would have resolved them. Linking::SelfContained stays the default and changes no compilation that succeeds today. | #596 · a39a089 |
| 28 Jul 2026 | cuda-oxide | An exact launch-contract block shape carried into the compiled artifact as .reqntid, so the CUDA driver enforces it per axis at every launch, including raw _unchecked ones that bypass preparation. | #514 · 73301f6 |
| 27 Jul 2026 | cuda-oxide | Workspace test targets linted in CI. The workspace clippy step was the only one of three missing --all-targets, which is why an unnecessary_mut_passed inside a #[cfg(test)] module had stayed invisible. | #486 · b392c9a |
| 27 Jul 2026 | cuda-oxide | Fuzzer traces widened to f32 and f64, folded as raw bits with no ULP tolerance, which keeps the trace comparison bit-for-bit. Runs under --no-fmad, since contraction is on by default and GPU fma.rn would otherwise diverge from the CPU oracle's separate roundings. | #484 · 5b98fc5 |
| 27 Jul 2026 | cuda-oxide | NVVM IR target rejections reported at the input stage, closing a follow-up the maintainer left on #416: the resolver had labelled target-attributable rejections as export-stage failures. | #483 · b5a39fc |
| 27 Jul 2026 | cuda-oxide | SwitchInt arms compared at the discriminant width, so a match on a 128-bit scrutinee compiles. Found by the differential fuzzer: a u64::try_from clamp on the arm values refused a case the 64-bit enum carrier limit never covered. | #482 · 2fa7cf6 |
| 24 Jul 2026 | cuda-oxide | Report the shared cache in doctor. | #447 · fe6d5be |
| 24 Jul 2026 | cuda-oxide | Publish the backend to the shared cache on setup. | #445 · d123479 |
| 24 Jul 2026 | cuda-oxide | The standalone compiler's scratch directory backed by tempfile. | #417 · 610949e |
| 24 Jul 2026 | cuda-oxide | Target selection attributed to whoever chose the target. | #416 · 2e131b5 |
| 23 Jul 2026 | cuda-oxide | A fourth silent miscompile: aggregate constant fields read at the wrong layout offsets, with no diagnostic. | #394 · 124595d |
| 22 Jul 2026 | cuda-oxide | Tightened the standalone compiler's clone, liveness and diagnostic paths: an erase guard on the panic path, an opt-in module clone, and toolchain selection recorded for the caller. | #415 · 9fa990e |
| 22 Jul 2026 | cuda-oxide | Ran fuzzer seeds whose device code calls libdevice, loading through kernels::load so cubin, PTX, NVVM IR and LTOIR each dispatch to the right loader. | #395 · d6a051b |
| 13 Jul 2026 | Daft | Dashboard build support for OUT_DIR on a filesystem other than the source tree's, falling back to copy-then-remove when rename(2) returns EXDEV. | #7246 · 03c72cb |
| 11 Jul 2026 | txm | Font-alphabet commands, inline symbols, single-token macro arguments and accents, which together carry geometric-algebra and quaternion notation in a terminal. | #14 · 4df00e9 |
| 10 Jul 2026 | Daft | Lowered the release opt-level for opendal-service-oss, sidestepping an LLVM SLP-vectoriser stall that hung the build for tens of minutes. | #7249 · 5a7d8d9 |
| 4 Jul 2026 | cuda-oxide | cuda-oxide-codegen: extracted the dialect-MIR-to-PTX backend into a rustc-independent crate, so front ends other than the Rust path can drive the same pipeline. | #314 · 7efa409 |
| 20 Jun 2026 | cuda-oxide | Rebuild the cached backend when its source advances, eliminating stale compiled artefacts after an upstream update. | #257 · 8b3e45e |
| 20 Jun 2026 | cuda-oxide | cargo oxide emit-ltoir, building a crate's LTOIR in one step, which enables LTO across the Rust and CUDA C boundary. | #256 · cdfeac0 |
| 18 Jun 2026 | cuda-oxide | Fused-multiply-add contraction as the default, matching nvcc --fmad=true, with an -O3 pass. | #117 · 81cf422 |
| 12 May 2026 | cuda-oxide | Converted three silent miscompiles in the Rust-to-PTX code generator into hard build errors, each with a regression-test crate. | #27 · 3697238 |
| 14 Apr 2026 | Mathlib4 | HasCompactMulSupport closure under product operations: submonoid, List, Multiset and Finset variants, with @[to_additive]. | #38022 |