SPB Git

spb/metrika

Public

Stata-class statistics, GPU-accelerated by Apple Silicon. Native Swift — no Electron, no Python runtime, no compromises.

docs: README with badges, screenshots, and the v1.0 story

- hero: console session showing cluster-robust factor regression and the
  GPU-batched 10k bootstrap; screenshot grid (Charts by(), Metal 2M,
  data browser, manual)
- shields badges: release, macOS 14+, Swift 6, arm64, Metal+MLX, 116
  tests, R-validated 1e-10, notarized DMG
- five-minute tour, feature matrix, numerical-validation section,
  architecture, build instructions, extensibility examples
- METRIKA_AUTOPANE debug hook (headless pane selection for screenshots)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
simon-pierre boucher committed (Aug 5, 2026) parent 18c6874 Browse files

9 changed files +207 −3

modified Metrika.xcodeproj/project.pbxproj +2 −2
@@ -353,7 +353,7 @@
353 "$(inherited)", 353 "$(inherited)",
354 "@executable_path/../Frameworks", 354 "@executable_path/../Frameworks",
355 ); 355 );
356− MARKETING_VERSION = 0.1.0; 356+ MARKETING_VERSION = 1.0.0;
357 ONLY_ACTIVE_ARCH = YES; 357 ONLY_ACTIVE_ARCH = YES;
358 PRODUCT_BUNDLE_IDENTIFIER = ai.spboucher.metrika; 358 PRODUCT_BUNDLE_IDENTIFIER = ai.spboucher.metrika;
359 SDKROOT = macosx; 359 SDKROOT = macosx;
@@ -400,7 +400,7 @@
400 "$(inherited)", 400 "$(inherited)",
401 "@executable_path/../Frameworks", 401 "@executable_path/../Frameworks",
402 ); 402 );
403− MARKETING_VERSION = 0.1.0; 403+ MARKETING_VERSION = 1.0.0;
404 ONLY_ACTIVE_ARCH = YES; 404 ONLY_ACTIVE_ARCH = YES;
405 PRODUCT_BUNDLE_IDENTIFIER = ai.spboucher.metrika; 405 PRODUCT_BUNDLE_IDENTIFIER = ai.spboucher.metrika;
406 SDKROOT = macosx; 406 SDKROOT = macosx;
modified Metrika/Sources/MetrikaApp.swift +13 −1
@@ -32,7 +32,19 @@ struct ContentView: View {
32 } 32 }
33 33
34 @Environment(SessionModel.self) private var model 34 @Environment(SessionModel.self) private var model
35− @State private var pane: Pane = .console 35+ @State private var pane: Pane = ContentView.initialPane()
36+
37+ /// Debug hook for headless screenshots: METRIKA_AUTOPANE selects the
38+ /// pane shown at launch.
39+ private static func initialPane() -> Pane {
40+ #if DEBUG
41+ if let name = ProcessInfo.processInfo.environment["METRIKA_AUTOPANE"],
42+ let selected = Pane(rawValue: name) {
43+ return selected
44+ }
45+ #endif
46+ return .console
47+ }
36 48
37 var body: some View { 49 var body: some View {
38 NavigationSplitView { 50 NavigationSplitView {
added README.md +192 −0
@@ -0,0 +1,192 @@
1+<div align="center">
2+
3+<img src="docs/screenshots/icon.png" width="140" alt="Metrika icon">
4+
5+# Metrika
6+
7+**Stata-class statistics, GPU-accelerated by Apple Silicon.**
8+Native Swift. No Electron. No Python runtime. No compromises.
9+
10+[![Release](https://img.shields.io/badge/release-v1.0.0-4f8cff)](../../releases/latest)
11+[![macOS](https://img.shields.io/badge/macOS-14%2B-black?logo=apple)](#requirements)
12+[![Swift](https://img.shields.io/badge/Swift-6-F05138?logo=swift&logoColor=white)](#building-from-source)
13+[![Apple Silicon](https://img.shields.io/badge/Apple%20Silicon-arm64-333)](#requirements)
14+[![GPU](https://img.shields.io/badge/GPU-Metal%20%2B%20MLX-9b6cff)](#the-gpu-invisibly)
15+[![Tests](https://img.shields.io/badge/tests-116%20passing-34c759)](#numerical-validation)
16+[![Validated](https://img.shields.io/badge/R--validated-1e--10-34c759)](#numerical-validation)
17+[![Notarized](https://img.shields.io/badge/DMG-signed%20%26%20notarized-0a84ff)](../../releases/latest)
18+
19+<img src="docs/screenshots/console-regression.png" width="820" alt="Cluster-robust regression with factor variables and a 10,000-replicate GPU bootstrap">
20+
21+*One session: a cluster-robust factor-variable regression, then a
22+10,000-replicate pairs bootstrap — **batched on the Apple GPU**, exactly
23+reproducible from `seed(42)` on any backend.*
24+
25+</div>
26+
27+---
28+
29+## Why Metrika
30+
31+- **One line, publication-ready output.** `reg log_rev price i.region, cluster(firm_id)` — the Stata mental model, with factor variables, `if`/`in` qualifiers, robust and cluster-robust inference.
32+- **The GPU is invisible.** A planner dispatches every command to CPU (LAPACK) or GPU (MLX) automatically. Large bootstrap runs execute as batched Metal solves; you never choose a backend.
33+- **Reproducibility is a feature, not an accident.** All randomness flows through a counter-based Philox4x32 generator: `set seed 42` produces *bit-identical* resamples on CPU and GPU, in any chunk order, across any parallelism.
34+- **Numbers you can defend.** Every CPU estimator is validated against R to **1e-10 relative tolerance** — coefficients, standard errors (classical, HC0–HC3, cluster), p-values, marginal effects. Penalized and boosted models cross-validate against glmnet and xgboost.
35+- **Big data on a laptop.** DuckDB columnar engine underneath; a Metal point-sprite renderer takes over scatter plots past 100k points and shrugs at 2,000,000.
36+
37+## Screenshots
38+
39+| | |
40+|:---:|:---:|
41+| **Swift Charts with `by()` groups**<br><img src="docs/screenshots/scatter-by.png" width="420"> | **2,000,000 points — Metal renderer**<br><img src="docs/screenshots/metal-2m.png" width="420"> |
42+| **Virtualized data browser with expression filter**<br><img src="docs/screenshots/data-browser.png" width="420"> | **Built-in manual for all 38 commands**<br><img src="docs/screenshots/manual.png" width="420"> |
43+
44+## Install
45+
46+Download **[Metrika.dmg](../../releases/latest)** — signed, notarized, and stapled. Drag into Applications. macOS 14+ on Apple Silicon.
47+
48+## A five-minute tour
49+
50+```stata
51+. sysuse sales // bundled 200-firm × 5-year panel
52+. gen log_rev = ln(revenue)
53+. summarize revenue price, detail
54+
55+. reg log_rev price i.region, robust // HC1 SEs, factor expansion
56+. predict yhat
57+. margins, dydx(price) // delta-method standard errors
58+
59+. xtset firm_id
60+. xtreg log_rev price, fe cluster(firm_id)
61+. ivregress 2sls log_rev (price = z1 z2), robust
62+
63+. logit purchase price
64+. margins, dydx(price)
65+
66+. bootstrap, reps(100000) seed(42): reg log_rev price // GPU batched
67+. permute, reps(10000) seed(42): reg log_rev price // exact p-values
68+. bayes, mcmcsize(20000) seed(42): reg log_rev price // Gibbs sampler
69+
70+. lasso log_rev price z1 z2 orders, lambda(0.05) // glmnet-exact
71+. boost log_rev z1 z2 orders, rounds(100) maxdepth(3) // xgboost-exact
72+
73+. scatter log_rev price, by(region)
74+. histogram revenue, bins(20)
75+. save results.dta, replace // native Stata .dta 118
76+```
77+
78+Everything above works identically in the console, in `.zyq` do-files
79+(⌘R in the editor), and headlessly through `metrika-cli`.
80+
81+## What's inside
82+
83+| Pillar | Contents |
84+|---|---|
85+| **Data** | parquet, csv, json, arrow, native Stata **.dta** (read 117–119, write 118) · DuckDB engine · explicit missing-value semantics with listwise-deletion reporting |
86+| **Estimation** | OLS (QR, never X'X) · logit / probit / poisson · 2SLS · panel fixed effects · summarize / tabulate / correlate |
87+| **Inference** | robust HC0–HC3 · cluster-robust with Stata small-sample factors · **GPU pairs bootstrap** · permutation tests · Bayesian regression (Gibbs) |
88+| **Machine learning** | lasso & elastic net (coordinate descent, glmnet-exact) · gradient-boosted trees (xgboost-exact) |
89+| **Post-estimation** | `predict` (xb, residuals, pr, n) · `margins, dydx()` with delta-method SEs |
90+| **Graphics** | Swift Charts scatter/line/histogram/kdensity · **Metal renderer** for millions of points |
91+| **Extensibility** | `.zyq` script commands with `args` macros · native Swift `ZQCommandPlugin`s with syntax validation and mutation gating |
92+
93+## Numerical validation
94+
95+Metrika's test suite doesn't check that code runs — it checks that the
96+*numbers are right*:
97+
98+- **116 tests** compare against golden values generated by R (`Tests/Fixtures/generate.R`): OLS coefficients, every SE variant, t/F/χ² p-values into the far tails (p = 4×10⁻²² matches R exactly), GLM likelihoods, marginal effects **and their delta-method SEs** — all at 1e-10 relative tolerance.
99+- Lasso/elastic-net coefficients match **glmnet** (including its subtle gaussian y-standardization convention); the selection pattern — which coefficients are exactly zero — matches exactly.
100+- Boosted-tree predictions match **xgboost** observation-by-observation.
101+- The GPU bootstrap's resample indices are asserted **bit-identical** to the CPU Philox reference; Philox itself is pinned to the Random123 known-answer vectors.
102+- With diffuse priors, the Bayesian posterior reproduces the frequentist answer within Monte-Carlo error — asserted, not assumed.
103+
104+## Architecture
105+
106+```
107+┌─────────────────────────────────────────────────────────┐
108+│ Metrika (SwiftUI) │
109+│ Console · Data browser · Do-file editor · Manual │
110+└──────────────────────────┬──────────────────────────────┘
111+ │
112+┌──────────────────────────▼──────────────────────────────┐
113+│ MetrikaKit (Swift package) │
114+│ │
115+│ ZQParser command grammar → typed AST │
116+│ ZQPlanner AST → CPU / GPU / hybrid dispatch │
117+│ ZQEngine sessions, execution, logging, help │
118+│ ZQData DataFrame façade over DuckDB + .dta │
119+│ ZQStats LAPACK estimators (Accelerate) │
120+│ ZQGPU MLX batched solves, Philox RNG │
121+│ ZQGraphics plot specs → Swift Charts / Metal │
122+│ ZQPlugins user commands & sandboxing │
123+└──────────────────────────────────────────────────────────┘
124+```
125+
126+`MetrikaKit` has zero UI dependencies and is fully testable with
127+`swift test`. `ZQGPU` is the only module allowed to touch MLX/Metal;
128+`ZQStats` the only one touching Accelerate — backends stay swappable.
129+
130+## Building from source
131+
132+```bash
133+git clone https://github.com/spboucher-ai/metrika && cd metrika
134+./scripts/install_hooks.sh
135+
136+# Library + CLI + tests
137+cd MetrikaKit
138+swift build && swift test # 116 tests (GPU suites auto-skip)
139+xcodebuild test -scheme MetrikaKit-Package \
140+ -destination 'platform=macOS' -skipPackagePluginValidation # + GPU suites
141+
142+# App
143+cd .. && xcodegen generate
144+xcodebuild -project Metrika.xcodeproj -scheme Metrika \
145+ -skipPackagePluginValidation build
146+
147+# Signed, notarized DMG
148+./scripts/release.sh
149+```
150+
151+> SwiftPM's CLI cannot compile Metal shaders, so `swift test` skips the
152+> GPU suites and CLI builds fall back to CPU automatically; `xcodebuild`
153+> runs give you everything.
154+
155+## Documentation
156+
157+- **In the app**: the Manual tab, or `help <command>` in the console.
158+- **Static site**: [`docs/index.html`](docs/index.html) — regenerated from the same registry by `metrika-cli docs`, so it can never drift from the app.
159+
160+## Extending Metrika
161+
162+Drop a `.zyq` script into `~/Library/Application Support/Metrika/Commands/`:
163+
164+```stata
165+// logreg.zyq — its filename becomes the command
166+args response predictor
167+gen __log = ln(`response')
168+reg __log `predictor', robust
169+drop __log
170+```
171+
172+…or compile a Swift plugin into the app:
173+
174+```swift
175+public struct ZScorePlugin: ZQCommandPlugin {
176+ public static let verb = "zscore"
177+ public static let syntax = ZQSyntaxSpec(mutates: true)
178+ public func execute(_ ctx: ZQContext) async throws -> ZQResult { … }
179+}
180+```
181+
182+Plugins that shadow built-ins are rejected at startup; datasets can only
183+be mutated through a declared, gated channel.
184+
185+---
186+
187+<div align="center">
188+
189+**© 2026 Simon-Pierre Boucher. All rights reserved.**
190+[contact@spboucher.ai](mailto:contact@spboucher.ai)
191+
192+</div>
added docs/screenshots/console-regression.png +0 −0

Binary file not shown.

added docs/screenshots/data-browser.png +0 −0

Binary file not shown.

added docs/screenshots/icon.png +0 −0

Binary file not shown.

added docs/screenshots/manual.png +0 −0

Binary file not shown.

added docs/screenshots/metal-2m.png +0 −0

Binary file not shown.

added docs/screenshots/scatter-by.png +0 −0

Binary file not shown.

Menu

Theme