@@ -4,9 +4,10 @@ Validates and analyzes a same-stack, restart-per-run Phase 2 ABBA campaign.
44
55. DESCRIPTION
66Consumes a CSV manifest with one row per independent run. Every formal campaign
7- contains three four-run blocks in ABBA, BAAB, ABBA order. Adjacent runs are
8- paired, B/A log-ratios are summarized by their median, and a deterministic
9- paired bootstrap produces a 95% confidence interval.
7+ contains three four-run blocks in ABBA, BAAB, ABBA order. By default adjacent
8+ runs are paired. Pass -IndependentSamples when every run executes concurrently
9+ on its own runner; A and B are then resampled independently and the estimator is
10+ median(B) / median(A).
1011
1112Required manifest columns:
1213Scenario,Block,Position,Variant,RunId,StackSha256,ArtifactSha256,CaptureMethod,SourcePath
4950
5051 [switch ]$AllowIncomplete ,
5152
53+ [switch ]$IndependentSamples ,
54+
5255 [string ]$OutputJson ,
5356
5457 [switch ]$Overwrite ,
@@ -295,6 +298,42 @@ function Get-BootstrapSummary {
295298 }
296299}
297300
301+ function Get-IndependentBootstrapSummary {
302+ param (
303+ [Parameter (Mandatory = $true )][double []]$AValues ,
304+ [Parameter (Mandatory = $true )][double []]$BValues ,
305+ [Parameter (Mandatory = $true )][int ]$Iterations ,
306+ [Parameter (Mandatory = $true )][int ]$RandomSeed
307+ )
308+
309+ if ($AValues.Count -eq 0 -or $BValues.Count -eq 0 ) {
310+ throw " Independent bootstrap requires non-empty A and B samples."
311+ }
312+ if (@ ($AValues + $BValues | Where-Object { $_ -le 0.0 }).Count -ne 0 ) {
313+ throw " Independent ratio bootstrap requires strictly positive metric values."
314+ }
315+ $random = [Random ]::new($RandomSeed )
316+ $bootstrap = New-Object ' double[]' $Iterations
317+ for ($iteration = 0 ; $iteration -lt $Iterations ; $iteration ++ ) {
318+ $aSample = New-Object ' double[]' $AValues.Count
319+ $bSample = New-Object ' double[]' $BValues.Count
320+ for ($index = 0 ; $index -lt $AValues.Count ; $index ++ ) {
321+ $aSample [$index ] = $AValues [$random.Next ($AValues.Count )]
322+ }
323+ for ($index = 0 ; $index -lt $BValues.Count ; $index ++ ) {
324+ $bSample [$index ] = $BValues [$random.Next ($BValues.Count )]
325+ }
326+ $bootstrap [$iteration ] = [math ]::Log(
327+ (Get-Median - Values $bSample ) / (Get-Median - Values $aSample ))
328+ }
329+ return [pscustomobject ]@ {
330+ Median = [math ]::Log(
331+ (Get-Median - Values $BValues ) / (Get-Median - Values $AValues ))
332+ Lower = Get-NearestRankPercentile - Values $bootstrap - Percentile 0.025
333+ Upper = Get-NearestRankPercentile - Values $bootstrap - Percentile 0.975
334+ }
335+ }
336+
298337function Round-Metric {
299338 param ([Parameter (Mandatory = $true )][double ]$Value )
300339 return [math ]::Round($Value , 6 , [MidpointRounding ]::AwayFromZero)
@@ -462,6 +501,7 @@ function Get-ScenarioResult {
462501 [Parameter (Mandatory = $true )][string ]$MetricPath ,
463502 [Parameter (Mandatory = $true )][string ]$MetricDirection ,
464503 [Parameter (Mandatory = $true )][bool ]$PermitIncomplete ,
504+ [bool ]$UseIndependentSamples = $false ,
465505 [Parameter (Mandatory = $true )][int ]$Iterations ,
466506 [Parameter (Mandatory = $true )][int ]$RandomSeed ,
467507 [switch ]$Prepared
@@ -674,8 +714,18 @@ function Get-ScenarioResult {
674714 }
675715 }
676716
677- [double []]$logRatios = @ ($pairs | ForEach-Object { [double ]$_.LogRatio })
678- $bootstrap = Get-BootstrapSummary - LogRatios $logRatios - Iterations $Iterations - RandomSeed $RandomSeed
717+ if ($UseIndependentSamples ) {
718+ [double []]$aValues = @ ($evidenceRuns.ToArray () | Where-Object variant -eq " A" |
719+ ForEach-Object { [double ]$_.value })
720+ [double []]$bValues = @ ($evidenceRuns.ToArray () | Where-Object variant -eq " B" |
721+ ForEach-Object { [double ]$_.value })
722+ $bootstrap = Get-IndependentBootstrapSummary - AValues $aValues - BValues $bValues `
723+ - Iterations $Iterations - RandomSeed $RandomSeed
724+ } else {
725+ [double []]$logRatios = @ ($pairs | ForEach-Object { [double ]$_.LogRatio })
726+ $bootstrap = Get-BootstrapSummary - LogRatios $logRatios `
727+ - Iterations $Iterations - RandomSeed $RandomSeed
728+ }
679729 $medianRatio = [math ]::Exp($bootstrap.Median )
680730 $lowerRatio = [math ]::Exp($bootstrap.Lower )
681731 $upperRatio = [math ]::Exp($bootstrap.Upper )
@@ -688,6 +738,12 @@ function Get-ScenarioResult {
688738 $improvementLower = ($lowerRatio - 1.0 ) * 100.0
689739 $improvementUpper = ($upperRatio - 1.0 ) * 100.0
690740 }
741+ $reportedPairs = New-Object ' System.Collections.Generic.List[object]'
742+ if (-not $UseIndependentSamples ) {
743+ foreach ($pair in $pairs ) {
744+ $reportedPairs.Add ($pair )
745+ }
746+ }
691747
692748 return [ordered ]@ {
693749 scenario = $ScenarioName
@@ -707,12 +763,13 @@ function Get-ScenarioResult {
707763 }
708764 captureMethod = $captureMethods [0 ]
709765 runCount = $Runs.Count
710- pairCount = $pairs.Count
766+ samplingMode = if ($UseIndependentSamples ) { " independent-runners" } else { " paired-adjacent" }
767+ pairCount = if ($UseIndependentSamples ) { 0 } else { $pairs.Count }
711768 medianBRatioToA = Round- Metric $medianRatio
712769 ratioBootstrap95Ci = @ ((Round- Metric $lowerRatio ), (Round- Metric $upperRatio ))
713770 improvementPercent = Round- Metric $improvement
714771 improvementBootstrap95CiPercent = @ ((Round- Metric $improvementLower ), (Round- Metric $improvementUpper ))
715- pairs = @ ($pairs .ToArray () | ForEach-Object {
772+ pairs = @ ($reportedPairs .ToArray () | ForEach-Object {
716773 [ordered ]@ {
717774 block = $_.Block
718775 positions = $_.Positions
@@ -822,6 +879,15 @@ function Invoke-AnalyzerSelfTest {
822879 [math ]::Abs($result.improvementPercent - 20.0 ) -gt 0.000001 ) {
823880 throw " Self-test paired log-ratio failed."
824881 }
882+ $independentResult = Get-ScenarioResult - Runs $runs - ScenarioName " SELFTEST" `
883+ - MetricPath " metric" - MetricDirection " LowerIsBetter" - PermitIncomplete $false `
884+ - UseIndependentSamples $true - Iterations 1000 - RandomSeed 7 - Prepared
885+ if ($independentResult.samplingMode -ne " independent-runners" -or
886+ $independentResult.pairCount -ne 0 -or
887+ $independentResult.pairs.Count -ne 0 -or
888+ [math ]::Abs($independentResult.medianBRatioToA - 0.8 ) -gt 0.000001 ) {
889+ throw " Self-test independent A/B bootstrap failed."
890+ }
825891 if ($null -ne $result.abFactor -or $result.configSha256.Count -ne 0 -or
826892 $result.legacyTextComponentCache.provenancePresent ) {
827893 throw " Self-test legacy manifest compatibility failed."
@@ -991,6 +1057,10 @@ function Invoke-AnalyzerSelfTest {
9911057 medianBRatioToA = $result.medianBRatioToA
9921058 ratioBootstrap95Ci = $result.ratioBootstrap95Ci
9931059 improvementPercent = $result.improvementPercent
1060+ independentSamplingModePassed = (
1061+ $independentResult.samplingMode -eq " independent-runners" -and
1062+ $independentResult.pairCount -eq 0
1063+ )
9941064 enforcesFormalOrder = $true
9951065 enforcesSameStackAndArtifacts = $true
9961066 rejectsJfrDataLoss = $rejectedJfrDataLoss
@@ -1035,6 +1105,7 @@ for ($index = 0; $index -lt $scenarioGroups.Count; $index++) {
10351105 $group = $scenarioGroups [$index ]
10361106 $results.Add ((Get-ScenarioResult - Runs @ ($group.Group ) - ScenarioName $group.Name `
10371107 - MetricPath $Metric - MetricDirection $Direction - PermitIncomplete $AllowIncomplete.IsPresent `
1108+ - UseIndependentSamples $IndependentSamples.IsPresent `
10381109 - Iterations $BootstrapIterations - RandomSeed ($Seed + $index )))
10391110}
10401111
@@ -1103,8 +1174,16 @@ Write-JsonResult ([ordered]@{
11031174 minimumSeconds = $MinimumSeconds
11041175 bootstrapIterations = $BootstrapIterations
11051176 seed = $Seed
1106- pairing = " adjacent positions 1-2 and 3-4 within each restart-per-run block"
1107- estimator = " exp(median(paired log(B/A)))"
1108- confidenceInterval = " deterministic paired bootstrap percentile 95% CI"
1177+ pairing = if ($IndependentSamples ) { $null } else {
1178+ " adjacent positions 1-2 and 3-4 within each restart-per-run block"
1179+ }
1180+ estimator = if ($IndependentSamples ) { " median(B) / median(A)" } else {
1181+ " exp(median(paired log(B/A)))"
1182+ }
1183+ confidenceInterval = if ($IndependentSamples ) {
1184+ " deterministic independent A/B bootstrap percentile 95% CI"
1185+ } else {
1186+ " deterministic paired bootstrap percentile 95% CI"
1187+ }
11091188 results = @ ($results.ToArray ())
11101189})
0 commit comments