// // PermuteTests.swift // Metrika // // Author: Simon-Pierre Boucher // Contact: contact@spboucher.ai // Copyright © 2026 Simon-Pierre Boucher. All rights reserved. // import Foundation import Testing import ZQEngine import ZQGPU @Suite("Permutation tests", .serialized) struct PermuteTests { let fixtures: Fixtures let session: ZQSession init() async throws { self.fixtures = try Fixtures() self.session = try ZQSession(discoverUserCommands: false) _ = try await session.execute("use \(fixtures.datasetURL.path)") _ = try await session.execute("gen log_rev = ln(revenue)") } @Test("permutation indices form a valid permutation") func validPermutation() { let generator = Philox4x32(seed: 42) for replicate in [0, 1, 99] { let order = ZQResampling.permutationIndices( replicate: replicate, sampleSize: 257, generator: generator ) #expect(order.sorted() == Array(0..<257), "replicate \(replicate)") } } @Test("permutations are replicate-addressable and seed-deterministic") func determinism() { let a = ZQResampling.permutationIndices( replicate: 5, sampleSize: 100, generator: Philox4x32(seed: 42) ) let b = ZQResampling.permutationIndices( replicate: 5, sampleSize: 100, generator: Philox4x32(seed: 42) ) let c = ZQResampling.permutationIndices( replicate: 5, sampleSize: 100, generator: Philox4x32(seed: 43) ) let d = ZQResampling.permutationIndices( replicate: 6, sampleSize: 100, generator: Philox4x32(seed: 42) ) #expect(a == b) #expect(a != c) #expect(a != d) } @Test("permutation stream does not collide with the bootstrap stream") func streamSeparation() { let generator = Philox4x32(seed: 42) let bootstrap = ZQResampling.pairsBootstrapIndices( replicate: 0, sampleSize: 100, generator: generator ) let permutation = ZQResampling.permutationIndices( replicate: 0, sampleSize: 100, generator: generator ) // A collision would make the permutation a deterministic function // of the bootstrap draw; these must be unrelated streams. #expect(bootstrap != permutation) } @Test("strong relationship gets an extreme empirical p-value") func strongRelationship() async throws { let result = try await session.execute( "permute, reps(200) seed(42): reg log_rev price" ) // R² ≈ 0.88 on n=57 — no permutation should beat the observed |β|. #expect(result.scalars["p_price"] == 0) #expect(result.scalars["c_price"] == 0) #expect(result.scalars["reps"] == 200) #expect(result.text.contains("Permutation test")) // The degenerate constant row is omitted. #expect(result.scalars["p__cons"] == nil) } @Test("permute is reproducible for a fixed seed") func reproducible() async throws { let first = try await session.execute( "permute, reps(50) seed(7): reg log_rev orders" ) let second = try await session.execute( "permute, reps(50) seed(7): reg log_rev orders" ) #expect(first.scalars["c_orders"] == second.scalars["c_orders"]) } @Test("histogram and kdensity build sensible plot specs") func singleVariablePlots() async throws { _ = try await session.execute("histogram revenue") let histogram = try #require(await session.lastPlot) #expect(histogram.kind == .histogram) #expect(histogram.series[0].y.reduce(0, +) == 60) // counts sum to N _ = try await session.execute("kdensity revenue") let density = try #require(await session.lastPlot) #expect(density.kind == .kdensity) // Density integrates to ~1 (trapezoid over the grid). let series = density.series[0] var integral = 0.0 for i in 1..