Files
FluidSimCpp/README.md

399 lines
22 KiB
Markdown

# FluidSimCpp
Solver performance measurements and reproducible SIMD/threading experiments are
documented in [the optimization audit](verification/OPTIMIZATION.md). Current
scenarios use serial pipe execution because per-substep threading costs more than
it saves at their graph sizes. Portable compiler SIMD remains enabled by default.
FluidSimCpp aims to reproduce sound from a physically accurate simulation of
compressible flow in pipes and connected volumes, sampled at 44,100 Hz. Each
audio sample advances physical time by 1/44100 second; adaptive internal
substeps resolve the fluid dynamics. Every subsystem must ultimately follow
physical laws with explicit units, assumptions and validation. Matching the
C# reference is a regression aid, not proof of physical accuracy.
This is a work in progress. Physical correctness is the acceptance goal, not
a claim about all current models. Validation must include conservation of
mass, momentum and energy, analytic acoustic modes, boundary reflection and
radiation, thermodynamics and convergence under time/space refinement.
## Build and run (Windows and Linux)
Both platforms use CMake 3.28 or newer, Git, and a C++20 compiler. The presets
build with optimizations and debugging symbols (`RelWithDebInfo`). The first
desktop configure fetches SFML 3.0.2 if it is not installed and needs network
access. Visual Studio is not required.
### Windows
Install CMake and Git and ensure both are on PATH. Then run from this folder
in a regular PowerShell terminal:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\Setup-Windows.ps1
cmake --preset windows
cmake --build --preset windows
ctest --preset windows
.\out\build\windows-gcc\fluidsim_desktop.exe
```
The setup script installs the portable [WinLibs](https://winlibs.com/) x64 UCRT
GCC bundle, including GDB and Ninja, in
`%LOCALAPPDATA%/FluidSimTools/mingw64`. It verifies the download's SHA-256
checksum, removes the downloaded archive, and reuses an existing installation.
It needs no administrator access and does not change your system PATH.
Each built executable gets its GCC runtime DLLs copied beside it so it also
runs directly from a terminal or Explorer.
### Linux
Install CMake 3.28 or newer, GCC with C++20 support, Make, GDB, and the SFML
development dependencies. For Ubuntu 24.04 / Debian with a suitable CMake:
```sh
sudo apt-get update
sudo apt-get install build-essential cmake git gdb pkg-config \
libx11-dev libxrandr-dev libxcursor-dev libxi-dev libudev-dev \
libgl1-mesa-dev libfreetype6-dev libflac-dev libogg-dev libvorbis-dev \
fonts-dejavu-core
cmake --preset linux
cmake --build --preset linux
ctest --preset linux
./build-desktop/fluidsim_desktop
```
Other distributions need equivalent packages; see
[SFML's source-build dependencies](https://www.sfml-dev.org/tutorials/3.0/getting-started/build-from-source/).
Running the desktop app requires a graphical session and an audio device.
### VS Code
Install the recommended **C/C++** and **CMake Tools** extensions and open this
folder. Select the `windows` or `linux` CMake preset for your platform. Choose
**FluidSim desktop**, **FluidSim two-stroke**, or **FluidSim Saab B205** in
**Run and Debug**, then press F5 or the green Run button. The launch
configurations select the platform's executable and GDB automatically; the
build task configures and builds the desktop target before launch.
Ctrl+F5 runs without debugging; Ctrl+Shift+B builds the desktop target only.
The command-line builds above also build the CLI, benchmarks, and tests.
The UI uses `../fonts/FiraCodeNerdFont-Medium.ttf` when available, then tries
installed Windows or Linux system fonts. To bundle another font, add
`-DFLUIDSIM_FONT_FILE=/path/to/font.ttf` to the configure command. For a
headless build, add `-DFLUIDSIM_BUILD_DESKTOP=OFF`; this skips SFML and the
desktop's graphics/audio dependencies.
Controls: hold `W` for throttle, `C` for clutch, `A` / `D` to shift, `Z` for
neutral, up/down to adjust throttle target, left/right to adjust engine load,
`M` toggles mechanical engine noise, mouse wheel changes simulation playback
speed, and Space toggles real time.
The command-line runner is useful for repeatable checks:
```powershell
.\out\build\windows-gcc\fluidsim_cpp.exe --seconds 1 --throttle 0.35
.\out\build\windows-gcc\fluidsim_cpp.exe --scenario two-stroke --seconds 1 --throttle 0.3
.\out\build\windows-gcc\fluidsim_cpp.exe --scenario helmholtz --seconds 1
.\out\build\windows-gcc\fluidsim_cpp.exe --scenario pulse --seconds 1
```
On Linux, use `./build-desktop/fluidsim_cpp` with the same arguments.
To compare deterministic checkpoints from the active C# twin scenario against
the C++ port (energy variation is disabled for repeatability), build the C++
trace runner and execute the PowerShell comparison script:
```powershell
cmake --build build-msvc --target fluidsim_parity_trace
.\verification\Compare-ScenarioTrace.ps1
```
## Audio fidelity and playback
Desktop playback starts at real time: one solver output frame per 44.1 kHz
stereo device frame. A simulation worker fills a bounded 8192-frame queue in
512-frame batches with a 4096-frame startup reserve (about 93 ms). The device
callback only reads samples, removes DC, converts to PCM and handles underruns;
it performs no simulation, allocation or waiting. An underrun fades out and
recovery crossfades over 64 samples rather than stepping abruptly to silence.
The callback never advances physical time to conceal missing samples.
The source is a linear compact-monopole approximation,
`p(r,t) = (d mass_flow/dt)/(4*pi*r)`. Intake and exhaust have separate static
positions, fractional-sample propagation delays, inverse-distance spreading,
and left/right ear paths. Playback calibration is 200 Pa per device full scale;
this is not calibrated speaker SPL. A second-order 6 kHz monitoring filter
reduces sharp sample-to-sample transients without saturation or synthetic bangs.
A roughly 3.5 Hz device DC blocker and final PCM clipping protection remain.
Use 1x playback for acoustic evaluation.
The default environment is a nearby wall. Press **R** to cycle dry, field,
wall and woodland. Ground/wall image paths provide early reflections; the
woodland scatterers and decaying feedback-delay tail are approximate effects,
not validated outdoor acoustics. Synthetic mechanical noise remains optional.
The compact source does not implement pipe-end radiation impedance, validated
directivity, or exhaust afterfire chemistry. Filtering cannot recover aliased
source information. See [audio verification](verification/AUDIO_RENDERING.md)
for tests, capture commands, limitations and measured transient reductions.
Launching `fluidsim_desktop` without `--scenario` opens a small scenario menu.
Click the twin, YZ125 or Saab B205 card to start, or select with Up/Down and press Enter.
Escape closes the menu. Simulation and audio start after selection. Passing
`--scenario twin`, `--scenario two-stroke` or `--scenario b205` bypasses the menu; `--scene` and
`--exhaust` options also apply when choosing through the menu.
To test the two desktop engines from the repository root:
```sh
cmake --build build-desktop --target fluidsim_desktop -j 4
./build-desktop/fluidsim_desktop --scenario two-stroke --scene wall
# Or:
./build-desktop/fluidsim_desktop --scenario twin --scene field
```
Hold **W** for throttle, use **Up/Down** to adjust the persistent pedal request,
**Space** to restore real-time playback and **R** to change environment. The two-stroke desktop uses
the existing 125 cc configuration; its dimensions and calibrations must be
adapted before it represents a particular engine. Both scenarios use the same
motorcycle test model, dashboard and rolling peak dyno graph (kW and Nm versus
RPM). Start in neutral, hold **C** to disengage the clutch, press **D** for first
gear, apply throttle and release **C** to pull away. **D/A** shift up/down;
**Z** selects neutral. Left/right adjust an external engine load, not a wheel
brake. The graph records simulated crank power/torque, not measured wheel power.
The two-stroke now uses a YZ125 baseline: published 2022 primary, gearbox and
final reductions, 54 x 54.5 mm geometry, and an estimated racing expansion
chamber. Ports use piston-uncovered window areas. The pipe display follows
physical cell diameter and length. Compare exhausts with `--exhaust racing`
(default) or `--exhaust straight`; the desktop, CLI and WAV capture tool accept
this option. See [YZ125 verification](verification/YZ125_BASELINE.md) for the
geometry, assumptions, speed sweep and remaining unloaded-RPM limitation.
## 1980s American V8
Choose **1980s American 5.0 V8** in the desktop menu, or run:
```sh
./build-desktop/fluidsim_desktop --scenario v8 --scene field
```
This is an estimated naturally aspirated 302-class cross-plane V8: eight
101.6 x 76.2 mm cylinders, 7:1 compression, mild pushrod-style valve timing,
1-5-4-2-6-3-7-8 firing order, 750 RPM idle target and 7000 RPM limiter
(6750 RPM recovery). Crank/flywheel inertia is 1.1 kg m², versus the original
.28. Eight unequal .60--.78 m headers feed two independent 1.5-litre bank
collectors: cylinders 1--4 and 5--8. Each collector has its own 50 mm outlet
pipe (1.8 / 1.95 m), and each outlet has a separate spatial audio emitter. A
nominal 3000 kg five-speed car uses the existing clutch/gear controls.
The V8 clutch has 1100 Nm capacity to shorten launch slip with the heavy
flywheel and car; engagement remains progressive rather than forcibly locking.
Final drive is approximately 3.624:1 (6.16 / 1.7), making gearing 1.7x taller
than the previous setup: 1.7x road speed at the same RPM and selected gear.
Throttle opening uses a 65 ms response and 2.3 travels/second limit; closing
uses 160 ms and 1.4 travels/second for a slower release.
The V8 uses the shared ideal EFI fuel dose with ECU throttle/idle control,
ignition, overrun fuel cut and rev limiting. Its earlier carb setup has been
removed. Independent exhaust banks retain their uneven cross-plane pulse
spacing rather than blending both banks in a shared collector. Numerical
diffusion in the pipe solver and audio filtering still limit high-frequency rasp.
Dimensions and ignition/cam calibration are design estimates;
the output is not a validated period engine dyno curve.
The intake uses one 70 mm inlet duct, one 65 mm throttle body and a shared
4-litre plenum feeding eight 38 mm diameter, 12 cm runners (previously 30 cm).
The revised mesh has 19 pipes and 190 cells, down from 310. Runner cells are
6 cm long and exhaust cells approximately 5--6.5 cm long. This coarsening trades
some high-frequency resolution for more real-time headroom.
The earlier lift-off slowdown came from the CFL timestep falling below the
22.7 microsecond audio interval: the whole fluid network advanced almost twice
per sample, doubling boundary solves and cell updates. The revised 16-second
rev/coast benchmark stayed at one substep per sample and took .51--.57 wall
seconds per simulated second here, including coast-down (previously up to
1.28). It returned to 759 RPM with finite audio and zero pipe positivity
repairs. The CFL limit remains enforced; other operating points may still
require subdivisions. The desktop retains the 4x playback gain.
Benchmark or capture with:
```sh
./build-desktop/fluidsim_scenario_benchmark 8 transient v8
./build-desktop/fluidsim_scenario_benchmark 20 coast v8
./build-desktop/fluidsim_audio_capture --scenario v8 --seconds 6 --throttle .3 --out /tmp/v8
```
## Twin with a VZ21-size turbo
Choose **600 cc twin + VZ21-size turbo** in the startup menu, or bypass it:
```sh
./build-desktop/fluidsim_desktop --scenario twin --turbo vz21 --scene field
./build-audit/fluidsim_cpp --scenario twin --turbo vz21 --seconds 5 --throttle 0.7
```
`Turbocharger` connects to four existing pipe ends and couples turbine and
compressor work through a rigid rotor. Small housing volumes preserve unsteady
exhaust pressure. Backpressure, charge temperature and spool speed emerge from
flow restrictions and energy exchange. A pressure-can/spring wastegate opens a
bypass; no boost pressure is imposed. HUD pressures are absolute. The default
unboosted twin is unchanged unless `--turbo vz21` is selected.
This is an estimated VZ21-size model, not a measured turbo map. Engine exhaust
pulses are implemented; blade-passing whistle/wakes are still pending. The
seven-second twin transient and all nine CTest suites passed. The optimized
7,000-RPM workload takes about 0.65 wall seconds per simulated second on the audit
machine; actual playback headroom depends on the computer. See
[turbo verification](verification/TURBO_VZ21.md) for assumptions and results.
## Saab B205 / 9-5 wagon baseline
The Saab menu entry now includes an estimated GT17 turbo and ECU wastegate boost
control (up to 0.45 bar gauge). Launch with `--scenario b205 --turbo gt17`, or use
`--turbo none` for the naturally aspirated baseline. See [GT17 verification](verification/SAAB_GT17.md)
for controller behavior, assumptions and tests. Charge heating and backpressure
are simulated; an intercooler is not yet included.
Select **Saab B205 / 9-5 wagon** from the desktop menu, or launch directly:
```sh
./build-desktop/fluidsim_desktop --scenario b205 --scene field
./build-audit/fluidsim_cpp --scenario b205 --seconds 4 --throttle 0.5
```
The shared four-stroke scenario now supports two or four cylinders. The B205
uses 90 x 78 mm geometry, 8.8:1 compression, two intake/two exhaust valves per
cylinder and 1-3-4-2 firing order. Four intake runners and four exhaust headers
connect shared plenums and a tailpipe. The vehicle approximates a 9-5 wagon:
1675 kg with driver, five-speed manual, FM55 B05 overall ratios
13.70 / 7.12 / 4.77 / 3.62 / 2.67, and 4.05 final drive. No sixth gear is offered.
Controls and dyno match the other scenarios. The original atmospheric-breathing
geometry baseline remains available with `--turbo none`. Pipe/cam dimensions,
friction, idle/ignition maps, clutch, drag and loaded tire radius are estimates.
Turbo calibration and acoustics are approximate; factory power is not validated.
See [B205 verification](verification/B205_BASELINE.md) for sources and checks.
## Current port status
The C++ core now includes the shared `EngineCylinder` model with four-stroke and
two-stroke cycle implementations, crankshaft, crankcase, atmosphere and
configurable 0-D volume components (including thermal relaxation and moving
volume work), profiled compressible 1-D pipes and boundaries, adaptive solver
stepping, the impulse-clutch motorcycle/road-load model, and the active 600 cc
twin topology. Audio includes the flow-derivative source, spatialized
intake/exhaust radiation, outdoor feedback-delay reverb, optional mechanical
noise, a lock-free simulation audio buffer, a `SoundEngine` lifecycle wrapper,
and an SFML desktop frontend. The shared 50-RPM-bin dyno history and graph are
also ported. A crankcase-scavenged 125 cc two-stroke scenario is available in
the core and desktop, with an ECU, finite-response throttle and dynamic reed.
This is still an in-progress port, not a claimed complete replacement. All four
current C# scenarios (600 cc twin, 125 cc two-stroke, Helmholtz resonator, and
pressure pulse) have headless C++ counterparts and can be selected by the CLI.
Remaining parity work includes completing the C# drawing/diagnostic controls in
the desktop UI and resolving numerical differences reported by the comparison
harness. Earlier Windows builds passed CTest, but the current audio and two-stroke
changes still need Windows runtime verification. The Linux desktop builds and
runs; installed-font fallbacks provide labels when no bundled font is present.
The one-second twin trace remains substantially outside its 1% tolerance. The
latest comparison's largest relative difference is 40.6745 for cylinder 1
pressure at checkpoint 33075 (69.8 kPa in C# versus 2.91 MPa in C++). A dense
intake trace matches closely at first, then the C# plenum pressure begins
rising at checkpoint 63 while C++ remains near ambient. This divergence needs
to be resolved before claiming numerical parity. CTest covers cylinder
geometry and valve timing, dyno-bin interpolation, configurable 0-D volume
thermodynamics, crankcase and atmosphere state, profiled-pipe geometry,
inertive and closed boundaries, gearing and clutch momentum transfer, finite
runs for every scenario, and finite audio/reverb output.
## Fuel and throttle control
Desktop playback has automatic gain enabled by default for every scenario.
Press **G** to toggle it; the dashboard shows its state. It tracks stereo RMS
and peaks after DC removal, lifting quiet idle audio gradually (1.5-second
gain-rise time constant) and reducing gain faster for loud revs (30 ms).
Extra gain is capped at 12x above the existing fixed 4x playback gain, with
linked stereo peak protection. Silence does not request additional boost and
underruns freeze the detector. Disabling smoothly returns to fixed playback
gain. Simulation pressures and WAV capture levels are unaffected.
Both desktop engines use `Ecu` (`include/FluidSim/Ecu.hpp`). The desktop and
CLI throttle values are pedal requests; the actual throttle is advanced every
simulation sample, independently of rendering. Opening and closing use an
80 ms first-order actuator response and a maximum travel rate of two full
travels per second. The four-stroke uses an ideal butterfly projection; the
two-stroke uses the circular-segment aperture uncovered by a translating slide.
Shaft obstruction and edge leakage are not modeled.
The four-strokes use ideal sensors and direct access to trapped fresh-air mass
at intake-valve closure to calculate each cylinder's fuel dose. This explicitly
permitted idealization replaces injector flow/duty-cycle and fuel-film models.
Their nominal AFR is 14.7, enriched progressively toward 12.8 above 70% throttle.
A PI idle controller targets 1800 RPM with filtered RPM feedback, bounded idle
authority and anti-windup. Overrun fuel cut engages above 3500 RPM on a closed
pedal and restores fuel below 2400 RPM to catch idle. The twin's rev limiter cuts fuel
and spark at 10000 RPM, recovering below 9750 RPM.
The two-stroke uses a simulated 38.1 mm slide carburetor instead of the EFI
dose. Effective pilot, needle and main restrictions draw liquid fuel from a
finite float bowl with the orifice relation `m_dot = Cd*A*sqrt(2*rho*delta_p)`.
The jet pressure signal comes from simulated mass flow, local venturi dynamic
pressure and the measured static pressure drop. A wall film deposits and
evaporates fuel during transients. Delivered fuel is added downstream of the
carb as a conserved species and travels through the plenum, reed, crankcase,
transfer duct and cylinder. Combustion is therefore limited by the fuel and
oxygen actually trapped, rather than receiving a cylinder-side AFR dose.
The two-stroke has no rev limiter and deliberately does not use electronic
overrun fuel cut. On a high-RPM throttle lift, wet-wall and pilot fuel can pass
through unlit cycles if the trapped mixture cannot ignite. Spark remains at
the CDI timing; there is no scheduled overrun firing pattern. Unburned fuel
is transported out through the exhaust without pipe combustion. No sound
samples are mixed in.
All ECU parameters are accessible calibrations, not measured engine maps.
There is no starter motor, automatic restart, knock sensor, lambda sensor,
catalyst, cold-start temperature model or injector electrical model. Idle
control can still stall under excessive load and does not impose crankshaft
speed or add artificial sustaining torque. Existing combustion/thermal models
remain approximations; ECU changes do not establish their physical accuracy.
The two-stroke targets 3000 RPM idle and uses idle ignition trim in addition to
throttle control. Its
reed is a damped lumped cantilever model with finite opening time and seating
contacts. Cylinder and crankcase moving-volume work use adiabatic energy changes.
Jet dimensions are effective flow calibrations rather than measured Mikuni
parts. Fuel uses the gas model after wall-film release; droplet size,
atomization, evaporation heat and oil are not modeled. Reed material,
combustion and controller parameters still require calibration.
C# trace parity is intentionally changed by these control and physics changes.
VS Code's default build task builds the desktop through CMake. Use
Ctrl+Shift+B to build, or choose **FluidSim desktop** in Run and Debug and
press F5 to build and run. The single-file C/C++ editor Run shortcut is disabled
because this project requires multiple sources and SFML; IntelliSense gets
include paths from CMake Tools. Open the repository root as the VS Code folder.
## Fluid physics verification and performance
See [the fluid physics audit](verification/FLUID_PHYSICS_AUDIT.md) for corrected
conservation/boundary equations, analytic tests, measured performance and
remaining model limits. The validation executable exercises 29 cases covering
walls, pressure outlets, nozzles, inertance, pipe joints, volumes, species,
acoustic modes, shocks and invalid states. The default coupled solver updates
components on every substep, uses area-weighted shared fluxes and matches
orifice discharge to duct characteristics. These changes intentionally alter
older C# trace comparisons.
Build headless with CMake, then run `ctest --test-dir build-audit
--output-on-failure`. `fluidsim_physics_benchmark` and
`fluidsim_thread_benchmark` reproduce the performance experiments.
`FLUIDSIM_ENABLE_SIMD=OFF` provides a scalar comparison; fast-math is not used.
Production fluid solving remains serial because threading was slower for the
current small pipe graph. SIMD and scalar Linux checks are covered; Windows
execution is still unverified.
The twin intake is supplied by an explicit 300 K atmospheric reservoir rather
than a pressure-only boundary with extrapolated entropy. The repeated-blip
`fluidsim_intake_regression` checks fresh-air supply and pressure/idle recovery.
Manifold vacuum at closed throttle remains part of the simulated pumping cycle.