Port FluidSim core and scenarios to C++20
This commit is contained in:
11
verification/CSharpParityTrace.csproj
Normal file
11
verification/CSharpParityTrace.csproj
Normal file
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../FluidSim.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
72
verification/Compare-ScenarioTrace.ps1
Normal file
72
verification/Compare-ScenarioTrace.ps1
Normal file
@@ -0,0 +1,72 @@
|
||||
[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 }
|
||||
94
verification/Program.cs
Normal file
94
verification/Program.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using FluidSim.Core;
|
||||
using FluidSim.Components;
|
||||
using FluidSim.Tests;
|
||||
|
||||
static T PrivateField<T>(object target, string name)
|
||||
{
|
||||
var field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
?? throw new InvalidOperationException($"Could not find scenario field '{name}'.");
|
||||
return (T)(field.GetValue(target) ?? throw new InvalidOperationException($"Field '{name}' is null."));
|
||||
}
|
||||
|
||||
const int sampleRate = 44100;
|
||||
const int sampleCount = sampleRate;
|
||||
int[] checkpoints = [.. Enumerable.Range(1, 100), 300, 500, 1000, 4410, 11025, 22050, 33075, 44100];
|
||||
var scenario = new SingleCylScenario();
|
||||
var originalOutput = Console.Out;
|
||||
Console.SetOut(TextWriter.Null);
|
||||
scenario.Initialize(sampleRate);
|
||||
scenario.Throttle = 0.35f;
|
||||
scenario.Load = 0.0f;
|
||||
scenario.Clutch = 0.0f;
|
||||
|
||||
var crankshaft = PrivateField<Crankshaft>(scenario, "crankshaft");
|
||||
var cylinder1 = PrivateField<Cylinder>(scenario, "cylinder");
|
||||
var cylinder2 = PrivateField<Cylinder>(scenario, "cylinder2");
|
||||
var intakePlenum = PrivateField<Volume0D>(scenario, "intakePlenum");
|
||||
var exhaustCollector = PrivateField<Volume0D>(scenario, "exhaustCollector");
|
||||
var vehicle = PrivateField<Vehicle>(scenario, "vehicle");
|
||||
var boundaries = PrivateField<BoundarySystem>(scenario, "boundaries");
|
||||
var pipeSystem = PrivateField<PipeSystem>(scenario, "pipeSystem");
|
||||
cylinder1.EnergyVariationFraction = 0.0f;
|
||||
cylinder2.EnergyVariationFraction = 0.0f;
|
||||
|
||||
Console.SetOut(originalOutput);
|
||||
Console.WriteLine("step,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");
|
||||
var checkpointIndex = 0;
|
||||
Console.SetOut(TextWriter.Null);
|
||||
for (var step = 1; step <= sampleCount; ++step)
|
||||
{
|
||||
scenario.Process();
|
||||
if (checkpointIndex < checkpoints.Length && step == checkpoints[checkpointIndex])
|
||||
{
|
||||
Console.SetOut(originalOutput);
|
||||
Console.WriteLine(string.Join(',',
|
||||
step.ToString(CultureInfo.InvariantCulture),
|
||||
(crankshaft.AngularVelocity * 60f / (2f * MathF.PI)).ToString("R", CultureInfo.InvariantCulture),
|
||||
crankshaft.CrankAngle.ToString("R", CultureInfo.InvariantCulture),
|
||||
cylinder1.Pressure.ToString("R", CultureInfo.InvariantCulture),
|
||||
cylinder2.Pressure.ToString("R", CultureInfo.InvariantCulture),
|
||||
intakePlenum.Pressure.ToString("R", CultureInfo.InvariantCulture),
|
||||
exhaustCollector.Pressure.ToString("R", CultureInfo.InvariantCulture),
|
||||
vehicle.SpeedKmh.ToString("R", CultureInfo.InvariantCulture),
|
||||
cylinder1.IntakeValveArea.ToString("R", CultureInfo.InvariantCulture),
|
||||
cylinder2.IntakeValveArea.ToString("R", CultureInfo.InvariantCulture),
|
||||
boundaries.GetOpenEndMassFlow(0).ToString("R", CultureInfo.InvariantCulture),
|
||||
boundaries.GetOpenEndMassFlow(1).ToString("R", CultureInfo.InvariantCulture),
|
||||
pipeSystem.GetInteriorStateLeft(0).p.ToString("R", CultureInfo.InvariantCulture),
|
||||
pipeSystem.GetInteriorStateRight(0).p.ToString("R", CultureInfo.InvariantCulture),
|
||||
pipeSystem.GetInteriorStateLeft(1).p.ToString("R", CultureInfo.InvariantCulture),
|
||||
pipeSystem.GetInteriorStateRight(1).p.ToString("R", CultureInfo.InvariantCulture),
|
||||
pipeSystem.GetInteriorStateLeft(2).p.ToString("R", CultureInfo.InvariantCulture),
|
||||
pipeSystem.GetInteriorStateRight(2).p.ToString("R", CultureInfo.InvariantCulture),
|
||||
pipeSystem.GetInteriorStateLeft(3).p.ToString("R", CultureInfo.InvariantCulture),
|
||||
pipeSystem.GetInteriorStateRight(3).p.ToString("R", CultureInfo.InvariantCulture),
|
||||
pipeSystem.GetInteriorStateLeft(4).p.ToString("R", CultureInfo.InvariantCulture),
|
||||
pipeSystem.GetInteriorStateRight(4).p.ToString("R", CultureInfo.InvariantCulture),
|
||||
intakePlenum.Ports[0].MassFlowRate.ToString("R", CultureInfo.InvariantCulture),
|
||||
intakePlenum.Ports[2].MassFlowRate.ToString("R", CultureInfo.InvariantCulture),
|
||||
intakePlenum.Ports[3].MassFlowRate.ToString("R", CultureInfo.InvariantCulture),
|
||||
cylinder1.IntakePort.MassFlowRate.ToString("R", CultureInfo.InvariantCulture),
|
||||
cylinder2.IntakePort.MassFlowRate.ToString("R", CultureInfo.InvariantCulture),
|
||||
cylinder1.ExhaustPort.MassFlowRate.ToString("R", CultureInfo.InvariantCulture),
|
||||
cylinder2.ExhaustPort.MassFlowRate.ToString("R", CultureInfo.InvariantCulture),
|
||||
pipeSystem.GetInteriorStateRight(0).rho.ToString("R", CultureInfo.InvariantCulture),
|
||||
pipeSystem.GetInteriorStateRight(0).u.ToString("R", CultureInfo.InvariantCulture),
|
||||
pipeSystem.GetInteriorStateLeft(1).rho.ToString("R", CultureInfo.InvariantCulture),
|
||||
pipeSystem.GetInteriorStateLeft(1).u.ToString("R", CultureInfo.InvariantCulture),
|
||||
pipeSystem.GetInteriorStateRight(1).rho.ToString("R", CultureInfo.InvariantCulture),
|
||||
pipeSystem.GetInteriorStateRight(1).u.ToString("R", CultureInfo.InvariantCulture),
|
||||
pipeSystem.GetInteriorStateLeft(2).rho.ToString("R", CultureInfo.InvariantCulture),
|
||||
pipeSystem.GetInteriorStateLeft(2).u.ToString("R", CultureInfo.InvariantCulture),
|
||||
pipeSystem.GetInteriorStateRight(2).rho.ToString("R", CultureInfo.InvariantCulture),
|
||||
pipeSystem.GetInteriorStateRight(2).u.ToString("R", CultureInfo.InvariantCulture),
|
||||
intakePlenum.Density.ToString("R", CultureInfo.InvariantCulture),
|
||||
intakePlenum.Temperature.ToString("R", CultureInfo.InvariantCulture),
|
||||
pipeSystem.GetInteriorStateLeft(0).rho.ToString("R", CultureInfo.InvariantCulture),
|
||||
pipeSystem.GetInteriorStateLeft(0).u.ToString("R", CultureInfo.InvariantCulture)));
|
||||
Console.SetOut(TextWriter.Null);
|
||||
++checkpointIndex;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user