[CmdletBinding()] param( [string]$BuildDirectory = (Join-Path $PSScriptRoot '..\build-msvc'), [double]$Tolerance = 0.01, [switch]$ShowAllDifferences ) $cppRunner = Join-Path $BuildDirectory 'fluidsim_parity_trace.exe' $csProject = Join-Path $PSScriptRoot 'CSharpParityTrace.csproj' if (-not (Test-Path -LiteralPath $cppRunner)) { throw "C++ trace runner not found: $cppRunner" } $cppText = & $cppRunner if ($LASTEXITCODE -ne 0) { throw "C++ trace runner failed with exit code $LASTEXITCODE." } $csText = & dotnet run --project $csProject --configuration Release --no-launch-profile --verbosity quiet if ($LASTEXITCODE -ne 0) { throw "C# reference trace failed with exit code $LASTEXITCODE." } $cppRows = @($cppText | Where-Object { $_ -match '^(step,|\d+,)' } | ConvertFrom-Csv) $csRows = @($csText | Where-Object { $_ -match '^(step,|\d+,)' } | ConvertFrom-Csv) if ($cppRows.Count -ne $csRows.Count) { throw "Checkpoint count differs: C++=$($cppRows.Count), C#=$($csRows.Count)." } $columns = @('rpm', 'angle', 'cylinder1_pa', 'cylinder2_pa', 'intake_pa', 'exhaust_pa', 'speed_kmh', 'valve1_in', 'valve2_in', 'intake_flow', 'exhaust_flow', 'pipe0_left_pa', 'pipe0_right_pa', 'pipe1_left_pa', 'pipe1_right_pa', 'pipe2_left_pa', 'pipe2_right_pa', 'pipe3_left_pa', 'pipe3_right_pa', 'pipe4_left_pa', 'pipe4_right_pa', 'throttle_port_flow', 'runner1_port_flow', 'runner2_port_flow', 'cyl1_intake_flow', 'cyl2_intake_flow', 'cyl1_exhaust_flow', 'cyl2_exhaust_flow', 'pipe0_right_rho', 'pipe0_right_u', 'pipe1_left_rho', 'pipe1_left_u', 'pipe1_right_rho', 'pipe1_right_u', 'pipe2_left_rho', 'pipe2_left_u', 'pipe2_right_rho', 'pipe2_right_u', 'intake_rho', 'intake_temperature', 'pipe0_left_rho', 'pipe0_left_u') $largestDifference = 0.0 $largestField = '' $largestStep = 0 $differences = [System.Collections.Generic.List[object]]::new() for ($rowIndex = 0; $rowIndex -lt $cppRows.Count; ++$rowIndex) { if ($cppRows[$rowIndex].step -ne $csRows[$rowIndex].step) { throw 'Checkpoint steps differ.' } foreach ($column in $columns) { $cppValue = [double]::Parse($cppRows[$rowIndex].$column, [Globalization.CultureInfo]::InvariantCulture) $csValue = [double]::Parse($csRows[$rowIndex].$column, [Globalization.CultureInfo]::InvariantCulture) $difference = [Math]::Abs($cppValue - $csValue) $scale = [Math]::Max(1.0, [Math]::Abs($csValue)) $relativeDifference = $difference / $scale if ($relativeDifference -gt $largestDifference) { $largestDifference = $relativeDifference $largestField = $column $largestStep = [int]$cppRows[$rowIndex].step } if ($relativeDifference -gt 0.0) { $differences.Add([pscustomobject]@{ Step = [int]$cppRows[$rowIndex].step Field = $column CSharp = $csValue Cpp = $cppValue RelativeDifference = $relativeDifference }) } if ($ShowAllDifferences) { "step={0} {1}: C#={2:G9}, C++={3:G9}, rel={4:G6}" -f ` $cppRows[$rowIndex].step, $column, $csValue, $cppValue, $relativeDifference } } } "Compared {0} checkpoints across {1} fields; tolerance {2:G6}." -f $cppRows.Count, $columns.Count, $Tolerance "Largest relative difference: {0:G6} in {1} at step {2}." -f $largestDifference, $largestField, $largestStep if ($largestDifference -gt $Tolerance) { 'Largest remaining differences:' $differences | Sort-Object RelativeDifference -Descending | Select-Object -First 10 | ForEach-Object { " step={0} {1}: C#={2:G9}, C++={3:G9}, rel={4:G6}" -f ` $_.Step, $_.Field, $_.CSharp, $_.Cpp, $_.RelativeDifference } } if ($largestDifference -gt $Tolerance) { exit 1 }