spb/metrika Public
Stata-class statistics, GPU-accelerated by Apple Silicon. Native Swift — no Electron, no Python runtime, no compromises.
Swift 92.4%
HTML 3.3%
R 3%
Shell 1.3%
1//2// PermuteTests.swift3// Metrika4//5// Author: Simon-Pierre Boucher6// Contact: contact@spboucher.ai7// Copyright © 2026 Simon-Pierre Boucher. All rights reserved.8//910import Foundation11import Testing12import ZQEngine13import ZQGPU1415@Suite("Permutation tests", .serialized)16struct PermuteTests {17 let fixtures: Fixtures18 let session: ZQSession1920 init() async throws {21 self.fixtures = try Fixtures()22 self.session = try ZQSession(discoverUserCommands: false)23 _ = try await session.execute("use \(fixtures.datasetURL.path)")24 _ = try await session.execute("gen log_rev = ln(revenue)")25 }2627 @Test("permutation indices form a valid permutation")28 func validPermutation() {29 let generator = Philox4x32(seed: 42)30 for replicate in [0, 1, 99] {31 let order = ZQResampling.permutationIndices(32 replicate: replicate, sampleSize: 257, generator: generator33 )34 #expect(order.sorted() == Array(0..<257), "replicate \(replicate)")35 }36 }3738 @Test("permutations are replicate-addressable and seed-deterministic")39 func determinism() {40 let a = ZQResampling.permutationIndices(41 replicate: 5, sampleSize: 100, generator: Philox4x32(seed: 42)42 )43 let b = ZQResampling.permutationIndices(44 replicate: 5, sampleSize: 100, generator: Philox4x32(seed: 42)45 )46 let c = ZQResampling.permutationIndices(47 replicate: 5, sampleSize: 100, generator: Philox4x32(seed: 43)48 )49 let d = ZQResampling.permutationIndices(50 replicate: 6, sampleSize: 100, generator: Philox4x32(seed: 42)51 )52 #expect(a == b)53 #expect(a != c)54 #expect(a != d)55 }5657 @Test("permutation stream does not collide with the bootstrap stream")58 func streamSeparation() {59 let generator = Philox4x32(seed: 42)60 let bootstrap = ZQResampling.pairsBootstrapIndices(61 replicate: 0, sampleSize: 100, generator: generator62 )63 let permutation = ZQResampling.permutationIndices(64 replicate: 0, sampleSize: 100, generator: generator65 )66 // A collision would make the permutation a deterministic function67 // of the bootstrap draw; these must be unrelated streams.68 #expect(bootstrap != permutation)69 }7071 @Test("strong relationship gets an extreme empirical p-value")72 func strongRelationship() async throws {73 let result = try await session.execute(74 "permute, reps(200) seed(42): reg log_rev price"75 )76 // R² ≈ 0.88 on n=57 — no permutation should beat the observed |β|.77 #expect(result.scalars["p_price"] == 0)78 #expect(result.scalars["c_price"] == 0)79 #expect(result.scalars["reps"] == 200)80 #expect(result.text.contains("Permutation test"))81 // The degenerate constant row is omitted.82 #expect(result.scalars["p__cons"] == nil)83 }8485 @Test("permute is reproducible for a fixed seed")86 func reproducible() async throws {87 let first = try await session.execute(88 "permute, reps(50) seed(7): reg log_rev orders"89 )90 let second = try await session.execute(91 "permute, reps(50) seed(7): reg log_rev orders"92 )93 #expect(first.scalars["c_orders"] == second.scalars["c_orders"])94 }9596 @Test("histogram and kdensity build sensible plot specs")97 func singleVariablePlots() async throws {98 _ = try await session.execute("histogram revenue")99 let histogram = try #require(await session.lastPlot)100 #expect(histogram.kind == .histogram)101 #expect(histogram.series[0].y.reduce(0, +) == 60) // counts sum to N102103 _ = try await session.execute("kdensity revenue")104 let density = try #require(await session.lastPlot)105 #expect(density.kind == .kdensity)106 // Density integrates to ~1 (trapezoid over the grid).107 let series = density.series[0]108 var integral = 0.0109 for i in 1..<series.x.count {110 integral += 0.5 * (series.y[i] + series.y[i - 1])111 * (series.x[i] - series.x[i - 1])112 }113 #expect(abs(integral - 1) < 0.05, "density integral \(integral)")114 }115}116