diff --git a/build/Site.hs b/build/Site.hs
index f061984..6c54a7c 100644
--- a/build/Site.hs
+++ b/build/Site.hs
@@ -29,6 +29,7 @@ import Compilers (essayCompiler, postCompiler, pageCompiler, poetryCompiler, fi
import Catalog (musicCatalogCtx)
import Commonplace (commonplaceCtx)
import Now (nowCtx)
+import Vita (vitaCtx)
import Contexts (siteCtx, essayCtx, postCtx, pageCtx, poetryCtx, fictionCtx, compositionCtx,
contentKindField, recentFirstByDisplay,
tagLinksFieldExcludingTopSegment, isProvedConfidence)
@@ -270,6 +271,13 @@ rules = do
-- /current.html. Re-compiles current.html when the YAML changes.
match "data/now.yaml" $ compile getResourceBody
+ -- CV/résumé YAML — the same files the PDF pipeline builds from
+ -- (yaml-source/data/). Loaded by Vita.vitaCtx so /about.html renders
+ -- education, publications, presentations, and experience from one
+ -- source instead of a hand-typed copy. Matching them here is what
+ -- makes about.html recompile when the CV data changes.
+ match "yaml-source/data/*.yml" $ compile getResourceBody
+
-- Per-build stamp — written by Main.main before Hakyll starts, so it
-- always exists and always differs from the previous run. Matched
-- (not routed) purely so the telemetry pages can `load` it as a
@@ -352,8 +360,21 @@ rules = do
>>= loadAndApplyTemplate "templates/default.html" essayCtx
>>= relativizeUrls
+ -- Vita — the academic-homepage view, driven by yaml-source/data/*.yml.
+ -- Prose (research interests, pointers, contact) stays in the markdown
+ -- body; education, publications, presentations, and experience are
+ -- generated by Vita.vitaCtx and assembled by templates/vita.html.
+ match "content/about.md" $ do
+ route $ stripPrefixRoute "content/"
+ `composeRoutes` setExtension "html"
+ compile $ pageCompiler
+ >>= loadAndApplyTemplate "templates/vita.html" vitaCtx
+ >>= loadAndApplyTemplate "templates/default.html" vitaCtx
+ >>= relativizeUrls
+
match ("content/*.md"
.&&. complement "content/index.md"
+ .&&. complement "content/about.md"
.&&. complement "content/commonplace.md"
.&&. complement "content/colophon.md"
.&&. complement "content/current.md"
diff --git a/build/Vita.hs b/build/Vita.hs
new file mode 100644
index 0000000..18653e3
--- /dev/null
+++ b/build/Vita.hs
@@ -0,0 +1,543 @@
+{-# LANGUAGE GHC2021 #-}
+{-# LANGUAGE OverloadedStrings #-}
+-- | Vita page: renders education, publications, presentations, and
+-- experience for @/about.html@ from @yaml-source/data/*.yml@ — the same
+-- files that drive the CV and résumé PDFs via the Jinja/xelatex pipeline.
+--
+-- The point is single-sourcing. Before this module the vita page carried a
+-- hand-typed copy of all four sections, and it had drifted from the PDFs in
+-- roughly fifteen places (stale roles, a superseded line count, a talk still
+-- listed as forthcoming after it was given). Anything rendered here is read
+-- from the same YAML the PDFs are built from, so the two cannot disagree.
+--
+-- Sections the site already owns are deliberately absent: in-progress work
+-- belongs to @/current@ (data\/now.yaml, see "Now"), the engineering index to
+-- @/cv/projects/@, and the personal narrative to @/me/@. This module renders
+-- only what nothing else did.
+--
+-- The YAML is LaTeX-flavoured, because its first consumer is xelatex. Values
+-- may contain @\\textbf{}@, @\\href{}{}@, @$\\times$@, @~@, @--@ and friends,
+-- so every value goes through 'latexToHtml' on the way out. See that function
+-- for the full supported set and why escaping happens before conversion.
+module Vita
+ ( vitaCtx
+ ) where
+
+import Data.Aeson (FromJSON (..), Object, Value (..), withObject, (.:), (.:?), (.!=))
+import Data.Aeson.Types (Parser, typeMismatch)
+import Data.List (isPrefixOf, sortOn)
+import Data.Maybe (mapMaybe)
+import Data.Scientific (isInteger, toRealFloat)
+import qualified Data.Aeson.Key as K
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as TE
+import qualified Data.Yaml as Y
+import Hakyll hiding (escapeHtml)
+import Contexts (siteCtx)
+import Utils (escapeHtml)
+
+-- ---------------------------------------------------------------------------
+-- Loose scalars
+-- ---------------------------------------------------------------------------
+
+-- | A YAML scalar that may be written as either a string or a number for the
+-- same logical field. @publications.yml@ has both @year: 2026@ (parsed as a
+-- number) and @year: "2026--2027"@ (a string); @experience.yml@ has
+-- @start: "2025"@ next to @start: July 2026@. Accept either and normalise
+-- to 'String' rather than forcing the YAML to be quoted consistently — the
+-- PDF pipeline does not care, and this module should not make it care.
+newtype Loose = Loose { unLoose :: String }
+
+instance FromJSON Loose where
+ parseJSON (String t) = pure (Loose (T.unpack t))
+ parseJSON (Number n)
+ | isInteger n = pure (Loose (show (truncate (toRealFloat n :: Double) :: Integer)))
+ | otherwise = pure (Loose (show (toRealFloat n :: Double)))
+ parseJSON (Bool b) = pure (Loose (if b then "true" else "false"))
+ parseJSON v = typeMismatch "string or number" v
+
+reqLoose :: Object -> String -> Parser String
+reqLoose o k = unLoose <$> o .: K.fromString k
+
+optLoose :: Object -> String -> Parser (Maybe String)
+optLoose o k = fmap unLoose <$> o .:? K.fromString k
+
+-- ---------------------------------------------------------------------------
+-- Entry types
+-- ---------------------------------------------------------------------------
+
+data Link = Link
+ { lkLabel :: String
+ , lkHref :: String
+ }
+
+instance FromJSON Link where
+ parseJSON = withObject "Link" $ \o -> Link
+ <$> o .: "label"
+ <*> o .: "href"
+
+data Edu = Edu
+ { edInstitution :: String
+ , edLocation :: Maybe String
+ , edDegree :: String
+ , edStart :: String
+ , edEnd :: Maybe String
+ , edGpa :: Maybe String
+ , edNotes :: Maybe String
+ , edVisible :: Bool
+ }
+
+instance FromJSON Edu where
+ parseJSON = withObject "Edu" $ \o -> Edu
+ <$> o .: "institution"
+ <*> optLoose o "location"
+ <*> reqLoose o "degree"
+ <*> reqLoose o "start"
+ <*> optLoose o "end"
+ <*> optLoose o "gpa"
+ <*> optLoose o "notes_cv"
+ <*> o .:? "cv_visible" .!= True
+
+newtype EduDoc = EduDoc { unEduDoc :: [Edu] }
+
+instance FromJSON EduDoc where
+ parseJSON = withObject "EduDoc" $ \o -> EduDoc <$> o .: "education"
+
+data Pub = Pub
+ { pbAuthors :: String
+ , pbTitle :: Maybe String
+ , pbVenue :: String
+ , pbYear :: String
+ , pbMonth :: Maybe String
+ , pbTarget :: Maybe String
+ , pbLinks :: [Link]
+ , pbNote :: Maybe String
+ , pbVisible :: Bool
+ }
+
+instance FromJSON Pub where
+ parseJSON = withObject "Pub" $ \o -> Pub
+ <$> reqLoose o "authors"
+ <*> optLoose o "title"
+ <*> reqLoose o "venue"
+ <*> reqLoose o "year"
+ <*> optLoose o "month"
+ <*> optLoose o "target"
+ <*> o .:? "links" .!= []
+ <*> optLoose o "equal_contrib_note"
+ <*> o .:? "cv_visible" .!= True
+
+newtype PubDoc = PubDoc { unPubDoc :: [Pub] }
+
+instance FromJSON PubDoc where
+ parseJSON = withObject "PubDoc" $ \o -> PubDoc <$> o .: "publications"
+
+data Pres = Pres
+ { prAuthors :: String
+ , prTitle :: String
+ , prVenue :: String
+ , prKind :: Maybe String
+ , prYear :: String
+ , prMonth :: Maybe String
+ , prStatus :: Maybe String
+ , prVisible :: Bool
+ }
+
+instance FromJSON Pres where
+ parseJSON = withObject "Pres" $ \o -> Pres
+ <$> reqLoose o "authors"
+ <*> reqLoose o "title"
+ <*> reqLoose o "venue"
+ <*> optLoose o "kind"
+ <*> reqLoose o "year"
+ <*> optLoose o "month"
+ <*> optLoose o "status"
+ <*> o .:? "cv_visible" .!= True
+
+newtype PresDoc = PresDoc { unPresDoc :: [Pres] }
+
+instance FromJSON PresDoc where
+ parseJSON = withObject "PresDoc" $ \o -> PresDoc <$> o .: "presentations"
+
+data Exp = Exp
+ { exOrg :: String
+ , exRole :: Maybe String
+ , exLocation :: Maybe String
+ , exLocUrl :: Maybe String
+ , exStart :: String
+ , exEnd :: Maybe String
+ , exSection :: Maybe String
+ , exOrder :: Int
+ , exPreamble :: Maybe String
+ , exBullets :: [String]
+ , exVisible :: Bool
+ }
+
+instance FromJSON Exp where
+ parseJSON = withObject "Exp" $ \o -> Exp
+ <$> reqLoose o "organization"
+ <*> optLoose o "role"
+ <*> optLoose o "location"
+ <*> optLoose o "location_url"
+ <*> reqLoose o "start"
+ <*> optLoose o "end"
+ <*> optLoose o "cv_section"
+ <*> o .:? "cv_order" .!= 99
+ <*> optLoose o "cv_preamble"
+ <*> o .:? "bullets" .!= []
+ <*> o .:? "cv_visible" .!= True
+
+newtype ExpDoc = ExpDoc { unExpDoc :: [Exp] }
+
+instance FromJSON ExpDoc where
+ parseJSON = withObject "ExpDoc" $ \o -> ExpDoc <$> o .: "experience"
+
+-- | @personal.yml@ also carries a @display@ string per link (the value the
+-- CV prints in full, since paper cannot be clicked). It is deliberately
+-- not read here — see 'renderContact'.
+data ProfileLink = ProfileLink
+ { plLabel :: String
+ , plHref :: String
+ , plVisible :: Bool
+ }
+
+instance FromJSON ProfileLink where
+ parseJSON = withObject "ProfileLink" $ \o -> ProfileLink
+ <$> reqLoose o "label"
+ <*> reqLoose o "href"
+ <*> o .:? "cv_visible" .!= True
+
+-- | Contact details from @personal.yml@. The phone number is deliberately
+-- not parsed: it is printed on the CV PDF, which is a document handed to
+-- a chosen reader, whereas this page is crawled. Nothing here should hand
+-- a scraper a phone number it did not already have to go looking for.
+data Person = Person
+ { pnEmail :: String
+ , pnLinks :: [ProfileLink]
+ }
+
+instance FromJSON Person where
+ parseJSON = withObject "Person" $ \o -> Person
+ <$> reqLoose o "email"
+ <*> o .:? "links" .!= []
+
+-- ---------------------------------------------------------------------------
+-- LaTeX → HTML
+-- ---------------------------------------------------------------------------
+
+-- | Convert the LaTeX subset that actually appears in the CV YAML into HTML.
+--
+-- Callers must escape HTML /before/ calling this, never after: this
+-- function emits real tags, so escaping afterwards would turn them into
+-- visible @<strong>@. Escaping first is safe because none of the
+-- LaTeX constructs contain @<@, @>@ or @&@ — and an @&@ inside an
+-- @\\href@ URL becomes @&@, which is what an HTML attribute wants
+-- anyway.
+--
+-- The supported set is deliberately closed and matches what the YAML
+-- contains today (@\\textbf@, @\\textit@, @\\texttt@, @\\href@,
+-- @$\\times$@, @$\\delta$@, @\\#@, @{,}@, @~@, @--@, @---@). An unhandled
+-- command passes through verbatim and is therefore visible on the page —
+-- the intended failure mode, since a silently swallowed @\\emph@ would
+-- drop its argument's text.
+latexToHtml :: String -> String
+latexToHtml =
+ substAll "---" "—"
+ . substAll "--" "–"
+ . substAll "$\\times$" "×"
+ . substAll "$\\delta$" "δ"
+ . substAll "\\#" "#"
+ . substAll "{,}" ","
+ . substAll "~" " "
+ . rewriteCmd2 "href" (\u t -> "" ++ t ++ "")
+ . rewriteCmd1 "textbf" (\x -> "" ++ x ++ "")
+ . rewriteCmd1 "textit" (\x -> "" ++ x ++ "")
+ . rewriteCmd1 "texttt" (\x -> "" ++ x ++ "")
+
+-- | Escape, then convert. The one-step form every renderer should use.
+tex :: String -> String
+tex = latexToHtml . escapeHtml
+
+substAll :: String -> String -> String -> String
+substAll _ _ [] = []
+substAll pat rep s@(c:cs)
+ | pat `isPrefixOf` s = rep ++ substAll pat rep (drop (length pat) s)
+ | otherwise = c : substAll pat rep cs
+
+-- | Split a leading @{...}@ group, tracking brace depth so nested groups
+-- survive. Returns the group's contents and whatever follows it.
+takeGroup :: String -> Maybe (String, String)
+takeGroup ('{':rest) = go (0 :: Int) "" rest
+ where
+ go _ _ [] = Nothing
+ go d acc ('}':cs)
+ | d == 0 = Just (reverse acc, cs)
+ | otherwise = go (d - 1) ('}':acc) cs
+ go d acc ('{':cs) = go (d + 1) ('{':acc) cs
+ go d acc (c:cs) = go d (c:acc) cs
+takeGroup _ = Nothing
+
+-- | Rewrite every @\\cmd{arg}@ with a function of its argument.
+rewriteCmd1 :: String -> (String -> String) -> String -> String
+rewriteCmd1 name f = go
+ where
+ marker = '\\' : name
+ go [] = []
+ go s@(c:cs)
+ | marker `isPrefixOf` s
+ , Just (arg, rest) <- takeGroup (drop (length marker) s)
+ = f (go arg) ++ go rest
+ | otherwise = c : go cs
+
+-- | Rewrite every @\\cmd{a}{b}@ with a function of both arguments.
+rewriteCmd2 :: String -> (String -> String -> String) -> String -> String
+rewriteCmd2 name f = go
+ where
+ marker = '\\' : name
+ go [] = []
+ go s@(c:cs)
+ | marker `isPrefixOf` s
+ , Just (a, rest1) <- takeGroup (drop (length marker) s)
+ , Just (b, rest2) <- takeGroup rest1
+ = f a (go b) ++ go rest2
+ | otherwise = c : go cs
+
+-- ---------------------------------------------------------------------------
+-- Shared rendering pieces
+-- ---------------------------------------------------------------------------
+
+-- | @start – end@, or just @start@ when the entry has no end.
+dateRange :: String -> Maybe String -> String
+dateRange s me = tex s ++ maybe "" (\e -> " – " ++ tex e) me
+
+-- | The grey line under an entry heading: dates, then location if present.
+metaLine :: String -> Maybe String -> Maybe String -> String
+metaLine dates mloc murl = concat
+ [ "
" + , concatMap one ls + , "
" + ] + where + one l = concat + [ "" + , tex (lkLabel l) + , "" + ] + +renderBullets :: [String] -> String +renderBullets [] = "" +renderBullets bs = concat + [ "", tex (edDegree e) + , maybe "" (\g -> " · GPA " ++ tex g ++ "") (edGpa e) + , "
" + , metaLine (dateRange (edStart e) (edEnd e)) (edLocation e) Nothing + , maybe "" (\n -> "" ++ tex n ++ "
") (edNotes e) + , "" ++ tex n ++ "
" + [] -> "" + dateOf p = tex (pbYear p) ++ maybe "" (\m -> ", " ++ tex m) (pbMonth p) + one p = concat + [ "", tex (pbVenue p) + , maybe "" (\t -> " " ++ tex t) (pbTarget p) + , "
" + ] + Just t -> concat + [ "", tex (pbVenue p), "
" + ] + , "" + , renderLinks (pbLinks p) + , "" + , maybe "" (\k -> tex k ++ ", ") (prKind p) + , tex (prVenue p) + , "
" + , "" + , "" ++ tex r ++ "
") (exRole e) + , metaLine (dateRange (exStart e) (exEnd e)) (exLocation e) (exLocUrl e) + , maybe "" (\p -> "" ++ tex p ++ "
") (exPreamble e) + , renderBullets (exBullets e) + , "" + , "" + , escapeHtml (pnEmail p) + , "" + , concatMap one (filter plVisible (pnLinks p)) + , "
" + ] + where + -- Chips carry the label, not personal.yml's `display` value. The CV + -- prints "ORCID: 0009-0002-0162-3587" because a printed page cannot be + -- clicked; a chip reading "0009-0002-0162-3587" alone identifies + -- nothing, and "github.com/levineuwirth" is a URL doing a label's job. + one l = concat + [ "" + , tex (plLabel l) + , "" + ] + +-- --------------------------------------------------------------------------- +-- Load +-- --------------------------------------------------------------------------- + +-- | Same UTF-8 round-trip as "Now": Hakyll hands back a 'String' of Unicode +-- codepoints and the yaml library wants a UTF-8 'ByteString'. +-- 'Data.ByteString.Char8.pack' would truncate every 'Char' to 8 bits and +-- silently mangle the em-dashes and daggers this data is full of. +loadYaml :: FromJSON a => FilePath -> Compiler a +loadYaml path = do + raw <- load (fromFilePath path) :: Compiler (Item String) + case Y.decodeEither' (TE.encodeUtf8 (T.pack (itemBody raw))) of + Left err -> fail (path ++ ": " ++ show err) + Right doc -> return doc + +-- | Render a section, or drop the field entirely when it comes out empty so +-- the template's @$if(...)$@ guards behave. +sectionField :: String -> Compiler String -> Context String +sectionField name gen = field name $ \_ -> do + html <- gen + if null html then noResult (name ++ ": empty") else return html + +-- --------------------------------------------------------------------------- +-- Context +-- --------------------------------------------------------------------------- + +vitaCtx :: Context String +vitaCtx = + constField "vita" "true" + <> sectionField "vita-education-html" + (renderEducation . unEduDoc <$> loadYaml "yaml-source/data/education.yml") + <> sectionField "vita-publications-html" + (renderPublications . unPubDoc <$> loadYaml "yaml-source/data/publications.yml") + <> sectionField "vita-presentations-html" + (renderPresentations . unPresDoc <$> loadYaml "yaml-source/data/presentations.yml") + <> sectionField "vita-experience-html" + (renderExperience . unExpDoc <$> loadYaml "yaml-source/data/experience.yml") + <> sectionField "vita-contact-html" + (renderContact <$> loadYaml "yaml-source/data/personal.yml") + <> siteCtx diff --git a/content/about.md b/content/about.md index a85f662..03bcecf 100644 --- a/content/about.md +++ b/content/about.md @@ -3,72 +3,23 @@ title: Levi Neuwirth — Vita tags: meta --- -For a less formal, more detailed introduction to who I am, see [[Me]]. +The formal record. For a less formal, more detailed introduction to who I am, see [[Me]]; for what I am actively working on this month, see [Current](/current.html). ## Documents -These are probably what you're looking for. A summary of the key points follows below! -- **[Curriculum Vitae (PDF)](/cv.pdf)** -- **[Resume (PDF)](/resume.pdf)** +- **[Curriculum Vitae (PDF)](/cv.pdf)** — the complete record, including grants, affiliations, languages, and technical skills. +- **[Resume (PDF)](/resume.pdf)** — one page, engineering-facing. +- **[Project index](/cv/projects/)** — engineering artifacts in depth, with links to writeups and source. -## Education - -- **Technical University of Denmark (DTU)** — MSc in Computer Science and Engineering. September 2026 – expected 2028. Expecting PhD studies after. -- **Brown University** — Sc.B. in Computer Science and Mathematics. August 2022 – May 2026 -- **DIS Copenhagen / Københavns Universitet** — Semester abroad. Fall 2024 +The sections below are generated from the same data as the two PDFs, so they cannot fall out of step with them. ## Research Interests My work clusters into four threads: -- **AI safety and applied AI** — zero-knowledge proofs for cryptographic verification of large language models, as a MARS V fellow with the [Cambridge AI Safety Hub](https://caish.org/mars) (mentored by James Petrie, Future of Life Institute); a Magic: The Gathering reinforcement-learning project on OSCAR; agentic-systems work at xAI on `grok-code-fast-1`; and reasoning, evaluation, and red-teaming research contracts. -- **Mathematics** — graph theory, number theory, and theoretical computer science. Public results so far are graph-theory-centered: static coverage and persistence in tree-ball geometry ([preprint](/essays/branch-based-local-capture-in-tree-balls/)); more is in progress. +- **AI safety and applied AI** — zero-knowledge proofs for cryptographic verification of large language models, as a MARS V fellow with the [Cambridge AI Safety Hub](https://caish.org/mars) (mentored by James Petrie, Future of Life Institute); a Magic: The Gathering reinforcement-learning project; and reasoning, evaluation, and red-teaming research contracts. +- **Mathematics** — graph theory, number theory, and theoretical computer science. Public results so far are graph-theory-centered: static coverage and persistence in tree-ball geometry ([preprint](/essays/branch-based-local-capture-in-tree-balls/)), and the annealed critical window for growing-radius domination in random regular graphs ([preprint](/essays/near-critical-growing-radius-domination.html)). - **Computer systems and high-performance computing** — the Weenix kernel, a TCP/IP networking stack from scratch in Go, and micro-architectural performance work (SIMD, hardware counters via PAPI, RAPL energy, cross-ISA ports across AVX2 / ARM NEON-SVE / RISC-V V) on Brown's OSCAR HPC cluster. - **Machine learning** — order-invariant ICD-10-CM embeddings (under review at *JAMIA*, deployed calculator), the NeuroPose 3D-kinematics system in Liqi Shu's lab at Brown Neurology, and ongoing research engineering at [NeuroAI](https://neuroai.health). Undergraduate work has been clinically focused; graduate study broadens the scope. Computer vision and security thread through all four but do not stand on their own. - -## Research - -### Published / In Submission - -- **Neuwirth L.** *Branch-Tube Persistence and Static Coverage in Tree-Ball Geometry.* Preprint, July 2026. [Preprint](/essays/branch-based-local-capture-in-tree-balls/) -- **Neuwirth L.** *Where Does SIMD Help Post-Quantum Cryptography? A Micro-Architectural Study of ML-KEM on x86 AVX2.* Technical report, Brown University Department of Computer Science, April 2026. [Report](/essays/where-does-simd-help-post-quantum-cryptography/) · [Artifact](https://git.levineuwirth.org/neuwirth/where-simd-helps) -- **Shu L, Neuwirth L†, Wang X†, Zheng H†.** *Beyond Comorbidity Indices: An Order-Invariant ICD-10-CM Embedding for Readmission and Mortality Prediction.* Under review at the *Journal of the American Medical Informatics Association* (JAMIA), 2026. [Preprint](/essays/beyond-comorbidity-indices/) · [Calculator](https://levineuwirth.github.io/icd_embeddings/) · [Code](https://github.com/levineuwirth/icd_embeddings) - -### In Preparation / In Progress - -- **Zero-knowledge proofs for LLM verification.** MARS V fellowship, [Cambridge AI Safety Hub](https://caish.org/mars), July–October 2026; cryptographic verification of claims about model training, inference, and deployment. Public write-up expected October 2026. -- **[NeuroPose](/essays/neuropose/) clinical-implications manuscript.** In preparation; target submission 2026–2027. -- **SIMD / PQC Phase 2 & Phase 3.** Hardware performance counters (PAPI), RAPL energy, and cross-ISA ports (ARM NEON/SVE, RISC-V V). -- **Semantic-embeddings citation project.** Early-stage work with [NeuroAI](https://neuroai.health), preprint expected summer 2026. -- **Magic: The Gathering reinforcement learning project.** Early-stage work through Brown's HPC, expected late 2026. - -### Presentations - -- **Early Detection of Neurological Disorders through Video-Captured Kinematic Analysis.** Ma J, Arms S, Kaneira L, Lall M, Chen K, Cabral W, Man D, Neuwirth L, Shu L. Poster, Brown / Rhode Island Hospital Neurology Summer UTRA Symposium, August 2025. -- **"Order-Invariant ICD-10-CM Embedding for Readmission and Mortality Prediction: Toward Multimodal Generative Patient Models"**Shu L, Neuwirth L†, Wang X†, Zheng H†. IEEE/ACM Conference on Connected Health: Applications, Systems and Engineering Technologies (CHASE), August 2026, accepted. -- **Neuwirth L, Dasher AS.** "Proving What a Datacenter Did: Verified Inference Between Adversaries." Talk, MARS V, Cambridge AI Safety Hub, July 2026. Accepted. - -†Equal-contribution undergraduate authors. - -## Experience -See [resume (PDF)](/resume.pdf). - -- **Cambridge AI Safety Hub** — *MARS V Fellow.* July – October 2026, Cambridge, UK. Selected for [MARS V](https://caish.org/mars), a competitive part-time AI-safety research fellowship; working under James Petrie (Future of Life Institute) on zero-knowledge proofs for cryptographic verification of large language models. -- **Shu Laboratory, Brown Department of Neurology** — *Undergraduate Researcher and Technical Lead.* October 2023 – Present. Technical lead on [NeuroPose](/essays/neuropose/); co-lead developer on the [ICD-10-CM embedding model](/essays/beyond-comorbidity-indices/). -- **NeuroAI** Present. [neuroai.health](https://neuroai.health). Early-stage venture of academics and clinicians integrating deep learning, reinforcement learning, and generative AI into clinical and research workflows; leading research-engineering across model development, deployment infrastructure, and system design. -- **xAI** Summer 2025, remote. Contributed to the training of `grok-code-fast-1`, xAI's agentic coding model; built LLM integrations into autonomous agent frameworks and resolved 50+ agentic tool-execution failures. -- **Independent Research Contracting** — *Various AI laboratories.* 2025 – Present. Expert reasoning contributions in code and mathematics for agentic workflows, agentic task design and evaluation, AI safety, and red-teaming. - -## Selected Projects - -- **[Weenix](/essays/weenix/)** — Unix-like kernel in ~7,000 lines of C: virtual memory, VFS, system calls, threading, device drivers, interrupt handlers, and file systems. Custom linker support for running userspace x86-64 ELF binaries; extended with pipes and userspace preemption. -- **[Networking Stack from Scratch](/essays/networking-stack/)** — TCP/IP, RIP, UDP, and DNS in Go, supporting file transmission of up to 1 GB across 8-node networks. Extended with a fully RFC-compliant SSH implementation (2,000+ additional lines) supporting sustained sessions of arbitrary length. -- **[SIMD / PQC Performance Study](/essays/where-does-simd-help-post-quantum-cryptography/)** — Hand-written AVX2 assembly for ML-KEM / Kyber. 35×–56× speedup over compiler-optimized C for core NTT arithmetic; 5.4×–7.1× end-to-end KEM speedup, with a full statistical-analysis pipeline on Brown's OSCAR cluster. - -For the complete index — additional artifacts, deployed ML, and smaller tools — see [/cv/projects/](/cv/projects/). - -## Contact - -[ln@levineuwirth.org](mailto:ln@levineuwirth.org) · [ORCID 0009-0002-0162-3587](https://orcid.org/0009-0002-0162-3587) · [GitHub](https://github.com/levineuwirth) · [Forgejo](https://git.levineuwirth.org/neuwirth) diff --git a/levineuwirth.cabal b/levineuwirth.cabal index 240db82..1a0e9b7 100644 --- a/levineuwirth.cabal +++ b/levineuwirth.cabal @@ -19,6 +19,7 @@ executable site Catalog Commonplace Now + Vita Backlinks Dingbat SimilarLinks diff --git a/static/css/vita.css b/static/css/vita.css new file mode 100644 index 0000000..abdbc21 --- /dev/null +++ b/static/css/vita.css @@ -0,0 +1,304 @@ +/* vita.css — /about.html. + * + * The vita is a record surface. Where Now answers "what is moving right + * now" with a status chip per item, the vita answers "what is on file" + * and carries no temporality beyond the dates themselves — so it borrows + * item-card.css's card rhythm and now.css's section headings, but drops + * the badge column entirely. Entries here have no state to report. + * + * The one affordance this page has that the CV PDF cannot: every artifact + * is one click away. Link chips are therefore the only element given any + * interaction polish; everything else stays deliberately quiet so the + * chips read as the page's active surface. + */ + +/* ============================================================ + INTRO PROSE + Body content from about.md — the pointers and the four research + threads — set above the generated sections. Mirrors .now-intro + so the two data-driven pages open in the same register. + ============================================================ */ + +.vita-intro { + margin: 0 0 3rem; + font-family: var(--font-serif); + font-size: 1rem; + color: var(--text-muted); + line-height: 1.6; +} + +.vita-intro h2 { + font-family: var(--font-serif); + font-size: 1.15rem; + font-variant: all-small-caps; + font-feature-settings: "smcp" 1; + letter-spacing: 0.09em; + color: var(--text-muted); + font-weight: 400; + margin: 2rem 0 0.6rem; +} + +.vita-intro p:last-child { + margin-bottom: 0; +} + +/* ============================================================ + SECTIONS + Identical treatment to .now-section — same size, tracking, and + ink — because the two pages are siblings and a reader moving + between them should not have to re-learn the hierarchy. + ============================================================ */ + +.vita-section { + margin-bottom: 2.75rem; +} + +.vita-section-heading { + font-family: var(--font-serif); + font-size: 1.15rem; + font-variant: all-small-caps; + font-feature-settings: "smcp" 1; + letter-spacing: 0.09em; + color: var(--text-muted); + text-transform: none; + font-weight: 400; + margin: 0 0 0.85rem 0; +} + +/* ============================================================ + ENTRIES + .vita-card composes on .item-card, which is a flex row sized + for a badge column. The vita emits no badge, so the single + .item-card-main child simply takes the full width. + ============================================================ */ + +.vita-list { + margin: 0; +} + +.vita-entry-title { + font-family: var(--font-serif); + font-size: 1rem; + font-weight: 600; + color: var(--text); + line-height: 1.35; + margin: 0; +} + +/* Degree, role, position. Serif italic — the scholarly convention + for a title held, and it separates the role from the institution + above it without another weight or size step. */ +.vita-role { + font-family: var(--font-serif); + font-size: 0.95rem; + font-style: italic; + color: var(--text-muted); + margin: 0.15rem 0 0; + line-height: 1.4; +} + +.vita-gpa { + font-style: normal; + color: var(--text-faint); +} + +/* Author lists run long and are reference material rather than + reading material — sans, small, muted, tight. */ +.vita-authors { + font-family: var(--font-sans); + font-size: 0.82rem; + color: var(--text-muted); + margin: 0.2rem 0 0; + line-height: 1.45; +} + +/* Venue: journal, conference, or publication state. Italic serif is + the citation convention and distinguishes it from the author line + directly above without adding a color step. */ +.vita-venue { + font-family: var(--font-serif); + font-size: 0.92rem; + font-style: italic; + color: var(--text-muted); + margin: 0.2rem 0 0; + line-height: 1.45; +} + +/* Dates and location. Matches .item-card-date's register exactly — + tabular numerals so date columns stay optically aligned down the + list even though they are inline here. */ +.vita-meta { + font-family: var(--font-sans); + font-size: 0.72rem; + color: var(--text-faint); + margin: 0.25rem 0 0; + font-variant-numeric: tabular-nums; + line-height: 1.5; +} + +.vita-sep { + user-select: none; +} + +.vita-location { + color: var(--text-faint); +} + +a.vita-location { + text-decoration: underline; + text-decoration-color: var(--border); + text-decoration-thickness: 0.08em; + text-underline-offset: 0.2em; + transition: color var(--transition-fast), text-decoration-color var(--transition-fast); +} + +a.vita-location:hover { + color: var(--text-muted); + text-decoration-color: var(--border-muted); +} + +/* Presentation state ("Presented", "Accepted"). Sits on the meta + line rather than in a badge column, so it is set as small-caps + text with a leading separator instead of a bordered chip — a + frame here would compete with the link chips below it. */ +.vita-status { + font-family: var(--font-sans); + font-variant: all-small-caps; + letter-spacing: 0.07em; + color: var(--text-muted); +} + +.vita-status::before { + content: " · "; + color: var(--text-faint); +} + +/* Preamble / note lines — the CV's connective prose. Same register + as .item-card-abstract.is-full, unclamped. */ +.vita-note { + font-family: var(--font-sans); + font-size: var(--text-size-small); + color: var(--text-muted); + margin: 0.4rem 0 0; + line-height: 1.55; +} + +.vita-bullets { + margin: 0.4rem 0 0; + padding-left: 1.05rem; + list-style: none; +} + +.vita-bullets li { + font-family: var(--font-sans); + font-size: var(--text-size-small); + color: var(--text-muted); + line-height: 1.55; + margin: 0.3rem 0 0; + position: relative; +} + +/* Hairline dash rather than a bullet glyph: the entries are claims, + not an enumerated sequence, and a disc would assert more structure + than the content has. */ +.vita-bullets li::before { + content: ""; + position: absolute; + left: -1.05rem; + top: 0.72em; + width: 0.5rem; + height: 1px; + background: var(--border-muted); +} + +/* ============================================================ + LINK CHIPS + The page's one interactive surface, and the reason a vita on the + web beats a vita on paper. Geometry is lifted from .now-status so + the chip shape is already familiar from /current; the difference + is that these are links and therefore respond. + ============================================================ */ + +.vita-links { + margin: 0.5rem 0 0; + display: flex; + flex-wrap: wrap; + gap: 0.35rem; +} + +.vita-chip { + display: inline-block; + font-family: var(--font-sans); + font-size: 0.66rem; + font-variant: all-small-caps; + letter-spacing: 0.08em; + line-height: 1; + padding: 0.32em 0.55em 0.28em; + border: 1px solid var(--border); + border-radius: 2px; + color: var(--text-muted); + background: transparent; + white-space: nowrap; + text-decoration: none; + transition: + color var(--transition-fast), + border-color var(--transition-fast), + background-color var(--transition-fast); +} + +.vita-chip:hover { + color: var(--text); + border-color: var(--text-muted); + background: var(--surface-raised, transparent); +} + +.vita-chip:focus-visible { + outline: 2px solid var(--text-muted); + outline-offset: 2px; +} + +/* The equal-contribution legend. Belongs to the section, not to any + one entry, so it is set below the list in the faintest register + the page uses. */ +.vita-footnote { + font-family: var(--font-serif); + font-size: 0.85rem; + font-style: italic; + color: var(--text-faint); + margin: 0.9rem 0 0; +} + +/* ============================================================ + MOBILE (≤540px) + item-card.css already stacks its header at this breakpoint. The + vita's entries are single-column by construction, so only the + type steps down. + ============================================================ */ + +@media (max-width: 540px) { + .vita-entry-title { + font-size: 0.95rem; + overflow-wrap: anywhere; + } + + .vita-role, + .vita-venue { + font-size: 0.9rem; + } + + .vita-meta { + font-size: 0.68rem; + } + + .vita-chip { + font-size: 0.62rem; + padding: 0.28em 0.45em 0.24em; + } +} + +@media (prefers-reduced-motion: reduce) { + .vita-chip, + a.vita-location { + transition: none; + } +} diff --git a/templates/partials/head.html b/templates/partials/head.html index 8a03421..d5e7d26 100644 --- a/templates/partials/head.html +++ b/templates/partials/head.html @@ -48,6 +48,8 @@ $if(catalog)$$endif$ $if(commonplace)$$endif$ $if(now)$$endif$ $if(now)$$endif$ +$if(vita)$$endif$ +$if(vita)$$endif$ $if(build)$$endif$ $if(reading)$$endif$ $if(composition)$$endif$ diff --git a/templates/vita.html b/templates/vita.html new file mode 100644 index 0000000..ab380fc --- /dev/null +++ b/templates/vita.html @@ -0,0 +1,11 @@ +