Improve physical geometry, fluid solvers, audio, and profiling

This commit is contained in:
max
2026-10-30 18:57:56 +01:00
parent bf8621ad39
commit eb2b6366c2
130 changed files with 14649 additions and 862 deletions

22
.gitignore vendored
View File

@@ -1,6 +1,28 @@
/build/
/build-*/
/out/
*.wav
*.mp3
*.ogg
*.flac
*.aiff
*.aif
# Local editor and operating system artifacts
.DS_Store
Thumbs.db
*.swp
*.swo
*.bak
*.tmp
# Generated profiling and benchmark output
*.prof
*.profraw
*.gcda
*.gcno
*.gcov
*.log
/.vs/
CMakeUserPresets.json
/verification/bin/

27
.vscode/tasks.json vendored
View File

@@ -48,9 +48,32 @@
"dependsOn": "FluidSim: configure desktop",
"group": {
"kind": "build",
"isDefault": true
"isDefault": false
},
"problemMatcher": "$gcc"
},
{
"type": "cppbuild",
"label": "C/C++: cl.exe build active file",
"command": "cl.exe",
"args": [
"/Zi",
"/EHsc",
"/nologo",
"/Fe${fileDirname}\\${fileBasenameNoExtension}.exe",
"${file}"
],
"options": {
"cwd": "${fileDirname}"
},
"problemMatcher": [
"$msCompile"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "Task generated by Debugger."
}
]
}
}

View File

@@ -20,6 +20,7 @@ add_library(fluidsim_core
src/scenario.cpp
src/scenario_snapshot.cpp
src/scenario_geometry.cpp
src/physical_geometry.cpp
src/solver.cpp
src/vehicle.cpp
src/simulation_ring_buffer.cpp
@@ -46,6 +47,8 @@ target_link_libraries(fluidsim_cpp PRIVATE fluidsim_core)
add_executable(fluidsim_parity_trace tools/parity_trace.cpp)
target_link_libraries(fluidsim_parity_trace PRIVATE fluidsim_core)
add_executable(fluidsim_migrate_geometry tools/migrate_geometry.cpp)
target_link_libraries(fluidsim_migrate_geometry PRIVATE fluidsim_core)
if(FLUIDSIM_BUILD_DESKTOP)
find_package(SFML 3.0.2 COMPONENTS Graphics Audio QUIET)
@@ -114,6 +117,19 @@ endif()
include(CTest)
if(BUILD_TESTING)
add_executable(fluidsim_physical_geometry_tests tests/physical_geometry_tests.cpp)
target_link_libraries(fluidsim_physical_geometry_tests PRIVATE fluidsim_core)
target_include_directories(fluidsim_physical_geometry_tests SYSTEM PRIVATE third_party/tomlplusplus)
add_test(NAME fluidsim_physical_geometry_tests COMMAND fluidsim_physical_geometry_tests)
add_executable(fluidsim_mesh_resolution_tests tests/mesh_resolution_tests.cpp)
target_link_libraries(fluidsim_mesh_resolution_tests PRIVATE fluidsim_core)
add_test(NAME fluidsim_mesh_resolution_tests COMMAND fluidsim_mesh_resolution_tests)
add_executable(fluidsim_junction_tests tests/junction_tests.cpp)
target_link_libraries(fluidsim_junction_tests PRIVATE fluidsim_core)
add_test(NAME fluidsim_junction_tests COMMAND fluidsim_junction_tests)
add_executable(fluidsim_live_tuning_tests tests/live_tuning_tests.cpp)
target_link_libraries(fluidsim_live_tuning_tests PRIVATE fluidsim_core)
add_test(NAME fluidsim_live_tuning_tests COMMAND fluidsim_live_tuning_tests)
add_executable(fluidsim_engine_config_tests tests/engine_config_tests.cpp)
target_link_libraries(fluidsim_engine_config_tests PRIVATE fluidsim_core)
add_test(NAME fluidsim_engine_config_tests COMMAND fluidsim_engine_config_tests)
@@ -136,6 +152,8 @@ add_executable(fluidsim_physics_benchmark tools/physics_benchmark.cpp)
target_link_libraries(fluidsim_physics_benchmark PRIVATE fluidsim_core)
add_executable(fluidsim_scenario_benchmark tools/scenario_benchmark.cpp)
target_link_libraries(fluidsim_scenario_benchmark PRIVATE fluidsim_core)
add_executable(fluidsim_scenario_profile tools/scenario_profile.cpp)
target_link_libraries(fluidsim_scenario_profile PRIVATE fluidsim_core)
if(BUILD_TESTING)
add_test(NAME fluidsim_v8_exhaust_regression COMMAND fluidsim_scenario_benchmark 8 coast v8)
endif()
@@ -145,6 +163,7 @@ if(BUILD_TESTING)
add_test(NAME fluidsim_fluid_validation COMMAND fluidsim_fluid_validation)
add_test(NAME fluidsim_fluid_validation_hlle COMMAND fluidsim_fluid_validation hlle)
add_test(NAME fluidsim_fluid_validation_hllc COMMAND fluidsim_fluid_validation hllc)
add_test(NAME fluidsim_fluid_validation_muscl COMMAND fluidsim_fluid_validation muscl)
endif()
find_package(Threads REQUIRED)
target_link_libraries(fluidsim_core PUBLIC Threads::Threads)
@@ -156,6 +175,11 @@ if(BUILD_TESTING)
add_test(NAME fluidsim_intake_regression COMMAND fluidsim_intake_regression)
endif()
add_executable(fluidsim_acoustic_benchmark tools/acoustic_benchmark.cpp)
target_link_libraries(fluidsim_acoustic_benchmark PRIVATE fluidsim_core)
if(BUILD_TESTING)
add_test(NAME fluidsim_acoustic_benchmark COMMAND fluidsim_acoustic_benchmark "${CMAKE_CURRENT_BINARY_DIR}/acoustic-validation")
endif()
add_executable(fluidsim_audio_capture tools/audio_capture.cpp)
target_link_libraries(fluidsim_audio_capture PRIVATE fluidsim_core)
add_executable(fluidsim_two_stroke_benchmark tools/two_stroke_benchmark.cpp)

137
README.md
View File

@@ -87,7 +87,9 @@ Controls: W/S raise/lower the pedal over 0.5 seconds, C disengages the clutch,
V applies starter torque while held,
A/D shift, Z selects neutral, and left/right adjust engine load. The on-screen
pedal is direct and springs back on release; zero pedal retains ECU idle control.
Page Up/Down change playback speed; Space toggles real time.
Space toggles real-time and manual playback. In manual mode, Shift+scroll or
Page Up/Down changes speed; ordinary scrolling zooms the view. Rendering uses
display VSync independently of simulation speed, including 0.01x slow motion.
The command-line runner is useful for repeatable checks:
@@ -113,7 +115,8 @@ cmake --build build-msvc --target fluidsim_parity_trace
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
up to 128-frame batches with a 4096-frame startup reserve (about 93 ms). Batches
shrink with playback speed so 0.01x does not advance in large bursts. 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.
@@ -155,10 +158,18 @@ or exhaust afterfire chemistry. Filtering cannot recover aliased
source information. See [audio verification](verification/AUDIO_RENDERING.md)
for capture commands, current renderer details and limitations.
Use the **Cells/m - / +** control in the sidebar to change the active scenario's
pipe resolution. Changes rebuild the mesh in the background and restart the
engine, retaining solver, listener, scene and driving-control inputs. Values are
remembered separately per scenario until the app closes. Set persistent defaults
in each engine TOML's `[simulation] cells_per_meter`. The desktop engine editor
has been removed; edit engine calibration in TOML.
Launching `fluidsim_desktop` without `--scenario` opens a small scenario menu.
Choose the twin, YZ125, Saab B205, or American V8 card, or select with Up/Down
and press Enter.
Escape returns from a running scenario to the menu, stopping its simulation and
Escape returns from the scenario to the menu, stopping its simulation and
audio so another scenario can be selected. Escape closes the menu at the category
screen. Simulation and audio start after selection. Passing
`--scenario twin`, `--scenario two-stroke`, `--scenario b205`, or `--scenario v8`
@@ -176,7 +187,9 @@ cmake --build build-desktop --target fluidsim_desktop -j 4
Hold **W** to raise the pedal and **S** to lower it. Releasing the keyboard
returns the pedal over 0.5 seconds. The UI lever is direct and springs back. Use
**Space** to restore real-time playback and **R** to change environment. The two-stroke desktop uses
**Space** to toggle real-time/manual playback and **R** to change environment.
In manual mode use **Shift+scroll** or **Page Up/Down** to adjust speed;
ordinary scrolling continues to zoom the view. 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
@@ -310,7 +323,7 @@ runs for every scenario, and finite audio/reverb output.
## Fuel and throttle control
Desktop playback has automatic gain enabled by default for every scenario.
Desktop playback has automatic gain disabled 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).
@@ -410,8 +423,8 @@ Manifold vacuum at closed throttle remains part of the simulated pumping cycle.
## Pipe solver comparisons
The desktop accepts `--solver rusanov|hlle|hllc`. Press **F3** during playback to
cycle Rusanov, HLLE and HLLC; the dashboard shows the current solver. All pipes
The desktop accepts `--solver rusanov|hlle|hllc|muscl`. Press **F3** during playback to
cycle Rusanov, HLLE, HLLC and MUSCL; the dashboard shows the current solver. All pipes
switch together between simulation samples. Rusanov remains the startup default.
Changing solver affects the ongoing engine state; allow several cycles and the
audio queue to settle before judging it. Use the same scene and auto-gain setting.
@@ -499,16 +512,20 @@ geometry-resolving junction model. Momentum changes
are reacted by the fitting. Branch angle losses are not modeled. The separate
`Junction` class remains available for explicit finite-volume chambers.
Each V8 bank now has a 4-2-1 header: four equal 750 mm x 38 mm primaries feed two
equal 400 mm x 45 mm secondaries, then a 50 mm exhaust. Pairings are 1+2 / 3+4
Each V8 bank has a 4-2-1 header: four primaries feed two secondaries, then a
50 mm exhaust. Pairings are 1+2 / 3+4
and 5+7 / 6+8 with this simulation's bank numbering and firing order. Two T
junctions and a 780 mm x 38 mm tube form the H-pipe. Small configurable port-coupled junction volumes are used by the V8. The intake
plenum also remains a storage volume.
The TOML `primaries` pipe group configures primary length/diameter, while
The TOML `primaries` pipe group configures diameter; measured
`geometry.pipes` centerlines configure length and position. Headers now use
direct swept bends with revised lengths, and the short/long variants have
distinct physical layouts. See [geometry definitions](docs/ENGINE_CONFIG.md).
The
`[junctions] h_pipe` enables the H crossover;
pass it as the fourth `FourStrokeScenario` constructor argument. The renderer
routes the primary pairs into Y merges, then the final Y collectors and H-pipe,
using local pipe diameter and pressure colors without collector boxes.
uses the same centerlines, cross-sections and active storage-volume dimensions
as the fluid system, with local pressure colors and mitered hard corners.
The previous header optimization measurements applied to the earlier finite-volume
4-1 layout and do not establish an optimum for this new 4-2-1 network.
@@ -619,3 +636,99 @@ The tool also prints CSV diagnostics: run without arguments for the four main
operating points, or use `--sweep single-450` for a denser sweep of one scenario.
See [carb calibration results](verification/CARB_CALIBRATION.md) for operating
conditions, AFR values and the distinction between carb and recirculated charge.
### Source-separated audio diagnostics
The capture tool writes `-intake.wav`, `-exhaust.wav`, `-exhaust-2.wav`, and
`-valve-contact.wav` alongside the existing rendered, dry, and unfiltered mixes.
Source stems contain direct sound and early reflections, before the shared diffuse
reverb; `-tail.wav` contains that shared tail. With all sources enabled, the four
stems plus the tail reconstruct the rendered mix before WAV quantization/clipping.
Absent sources produce silent files. Peak, RMS, maximum sample delta, and clipping
counts are reported for every stem, in device full-scale units, without normalization.
Use `--solo intake`, `--solo exhaust`, `--solo exhaust-2`, or
`--solo valve-contact` to select a source in the output mix (default: `--solo all`).
All diagnostic stems remain active during soloing. These controls affect audio only;
they do not change the fluid simulation. Source controls are simulation-thread APIs.
```powershell
.\out\build\windows-gcc\fluidsim_audio_capture.exe --example single-450 --rpm 3000 --throttle .3 --warmup 1 --seconds 2 --scene dry --out out/audio/single-450-baseline
```
For intake-only listening, repeat with `--solo intake` and a different output prefix.
The shared tail can retain decay when controls change during playback.
Captures use 32-bit IEEE float WAV to preserve very quiet sources without clipping.
For audible, individually peak-normalized PCM16 listening copies, run:
```powershell
py tools/audio_preview.py out/audio/single-450-baseline
```
Previews appear in `out/audio/previews/`, with gain reported for each file.
Silent sources are skipped. Preview loudness does not represent relative source
strength; use the original captures and metrics for calibration.
### Acoustic pulse diagnostics
The `fluidsim_acoustic_benchmark` target compares straight connections, idealized
three-way junctions, and finite-volume fittings across meshes, timesteps, and
all three flux solvers. It exports pressure/velocity traces and acoustic energy
ratios, with conservation and numerical-repair diagnostics. See
[the benchmark guide and initial findings](docs/ACOUSTIC_BENCHMARK.md).
### Listening comparison: limited second-order pipes
Desktop auto gain now starts **off**. Press **G** to enable it if desired.
For a direct comparison on the current engine, keep throttle and master volume
fixed and press **F3** to cycle `rusanov ? hlle ? hllc ? muscl`.
The dashboard shows the selected solver. Rusanov remains the startup default.
To start directly in the new mode:
```powershell
.\out\build\windows-gcc\fluidsim_desktop.exe --example single-450 --solver muscl --scene dry
```
`muscl` uses minmod-limited primitive MUSCL-Hancock reconstruction with HLLC
fluxes on uniform pipe sections. Matching direct connections share reconstruction
stencils. Component boundaries and area/profile transitions retain first-order
coupling; nonphysical predicted edges fall back to cell averages, and HLLC keeps
its existing HLLE fallback. Reconstruction buffers are allocated at construction.
This is a listening candidate, not a calibrated real-engine sound model.
At 3000 RPM / 0.3 pedal, four-second dry captures are in
`out/audio/pipe-ab-rusanov-rendered.wav` and `out/audio/pipe-ab-muscl-rendered.wav`.
They preserve the actual level difference. New wave resolution can also change
cylinder filling, combustion, and torque. Restart in each solver for clean
comparisons; live switching retains the engine's current state.
For a comparison at equal RMS loudness, use
`out/audio/pipe-ab-rusanov-level-matched.wav` and
`out/audio/pipe-ab-muscl-level-matched.wav`. Both are PCM16 listening copies;
original float captures keep their physical output scale. Equal RMS does not
imply identical perceived loudness or identical engine state.
In one local loaded V8 check, MUSCL took 1.23 wall seconds per simulated second,
versus 0.78 for Rusanov. Large engines may underrun in real-time playback;
use the single-450 for the first listening comparison.
### Direction-aware characteristic junctions
A nonlinear zero-storage exhaust junction solver is now available separately
from the pipe solver. Start with Rusanov pipes to isolate the junction change:
```powershell
.\out\build\windows-gcc\fluidsim_desktop.exe --example v8-h --solver rusanov --junctions characteristic --junction-loss 0.5 --scene dry
```
Use `--junctions volume` to compare against the original volume fittings.
`--junctions legacy` selects the old dissipative zero-storage Rusanov node.
Default engine behavior still uses volume fittings. Direction losses use local
route tangents, with explicit TOML branch direction overrides supported. The
loss coefficient is an uncalibrated engineering parameter; zero disables angle
losses. Auto gain still defaults off.
The single-450 also supports these switches, but its two-port collector cannot
exercise multi-branch turning. Selecting a zero-storage node there removes its
collector-volume resonance. See [junction details](docs/JUNCTION_SOLVER.md).

110
docs/ACOUSTIC_BENCHMARK.md Normal file
View File

@@ -0,0 +1,110 @@
# Acoustic pulse benchmark
Run from the repository root:
```powershell
cmake --build --preset windows --target fluidsim_acoustic_benchmark
.\out\build\windows-gcc\fluidsim_acoustic_benchmark.exe out/acoustics
ctest --preset windows -R 'acoustic_benchmark|fluid_validation' --output-on-failure
```
`summary.csv` contains 408 cases. Each case also has a pressure/velocity probe
trace in CSV. No engine configuration or production solver behavior is changed.
## Cases and measurement
- A continuous 4 m pipe and an equivalent pair of directly connected 2 m pipes.
- Equal-area characteristic and legacy zero-storage three-way junctions, and an unequal-area characteristic junction (an idealized T/Y, without angles).
- Two-port and three-port finite-volume fittings, with 50, 500, and 5000 cc storage
and discharge coefficients 1.0 and 0.7. Opening area equals branch pipe area;
discharge variation represents restriction, not resolved entry geometry.
- Rusanov, HLLE, HLLC, and limited MUSCL-Hancock/HLLC; 40, 80, and 160 cells per 2 m; nominal Courant
settings 0.4 and 0.8. Solver CFL protection remains active. The requested
timestep uses reference sound speed plus a small wave-speed margin.
All pipes have area 0.001 m² and initially rest at 101325 Pa, 300 K. A 100 Pa
Gaussian pressure pulse, centered 0.6 m from the inlet with width parameter
0.12 m, is initialized as a right-going linear acoustic wave. Explicit pipe
damping and thermal relaxation are disabled.
The upstream probe is near x=1.4 m, and downstream probes are near x=2.6 m
(0.6 m into each outlet branch). Probe positions use containing cell centers.
The 9 ms integration ends before far-end reflections return to the probes.
For every probe, characteristics are `p± = (p' ± rho*c*u)/2`.
Acoustic pulse energy is the integral of `area * p±² / (rho*c)` over time.
Transmitted energy sums all outlet branches. Ratios divide by incident energy
at the upstream probe, so incident-path attenuation before that probe is excluded.
`transmission_over_straight` and `reflection_over_straight` divide the measured
ratios by transmission through the continuous pipe at matching mesh, scheme,
and timestep. This approximately removes shared propagation attenuation. Junction
filtering can change spectra, so this correction is not a complete de-embedding
of frequency-dependent pipe loss.
The ideal equal-area lossless three-way junction has reflected energy 1/9 and
total transmitted energy 8/9. Straight-pipe ideals are 0 and 1. Finite-volume
fittings have no constant analytic coefficient in this benchmark: their storage
and nozzle models introduce frequency and amplitude dependence.
`unaccounted_ratio` is acoustic energy missing from the measured outgoing pulse,
not a total conserved-energy error. It includes numerical attenuation, interface
loss, and any energy still stored or delayed beyond the measurement window.
It must not automatically be interpreted as physical dissipation.
Mass and total-energy drift, limited fluxes, positivity repairs, and substeps are
reported separately. Cases with limiting or outgoing acoustic energy above 1.05
times incident are flagged; they are not accepted as physical loss measurements.
The test fails for nonfinite results, positivity repairs, conservation drift
above 100 ppm, straight-pipe reflection above 0.0001, split-pipe mismatch, or a
failure of straight-pipe transmission to improve with mesh refinement at 0.4.
Flagged volume cases are retained as diagnostics rather than silently discarded.
## Initial findings
For Rusanov at nominal Courant 0.4:
| Cells per 2 m | Straight transmission | Three-way transmission / straight |
| --- | --- | --- |
| 40 | 0.6783 | 0.2215 |
| 80 | 0.7172 | 0.2219 |
| 160 | 0.7707 | 0.2222 |
The split pipe matches the continuous pipe. Straight-pipe attenuation decreases
with refinement, identifying spatial numerical diffusion as a contributor.
The legacy zero-storage junction's corrected transmission remains near 2/9 instead of
the ideal 8/9, indicating a separate interface problem that refinement does not
remove. Total mass and energy conservation alone does not establish correct
acoustic scattering.
The original three solvers flagged five coarse, high-timestep 50 cc three-port volume cases for flux
limiting or acoustic amplification. These show sensitivity to component storage
and explicit exchange timesteps; a pipe CFL limit alone does not validate that
coupling. A component timestep study should precede acoustic calibration of those
cases. Other cases passing robustness checks are not thereby physically validated.
These are small-signal, zero-mean-flow results for one pulse bandwidth. They do
not establish the dominant loss at every engine operating point. The single-450
and current V8 use storage volumes, so a zero-storage junction fix alone will not
address their sound. The next production-model investigation should address
pipe diffusion and finite-volume coupling stability; retain the zero-storage
scattering result as a separate junction regression target. Frequency sweeps,
phase comparison, mean-flow cases, and measured fitting data remain follow-up work.
## Production listening candidate
`muscl` is now a fourth selectable solver in the desktop, CLI, and capture tool.
Its straight-pipe energy transmission at Courant 0.4 is 0.7834, 0.8637, and
0.9446 for 40, 80, and 160 cells per 2 m, versus Rusanov's 0.6783, 0.7172,
and 0.7707. The benchmark also checks at least 0.05 improvement over Rusanov at
matching mesh/timestep. Split-pipe equivalence still passes without relaxed
thresholds. Adding MUSCL and the new characteristic/unequal-area comparisons brings the sweep to 408 cases, with seven flagged
small-volume cases; the component-coupling timestep issue is still open.
The new reconstruction applies to uniform pipe sections and matching direct
connections. Fittings, component boundaries, and profile transitions retain
first-order coupling. Production evaluation is opt-in (`--solver muscl` or F3);
Rusanov remains the startup default. Auto gain defaults off for listening.
The new characteristic solver corrects the legacy scattering error. Its corrected
transmission is about 8/9 for equal branches; unequal-area checks and transmitted
500/1000 Hz gain/phase checks are now included. See [junction solver details](JUNCTION_SOLVER.md).

View File

@@ -10,13 +10,16 @@ Start by copying a similar engine, such as `single-450.toml`, `five.toml`,
calibration values, including the previous carb and idle tunes.
```toml
schema_version = 1
schema_version = 2
id = "my-single"
name = "My four-stroke single"
category = "Singles / four-stroke"
description = "My calibration"
order = 100
cycle = "four_stroke"
[simulation]
cells_per_meter = 20.0
```
`id`, `name`, `category`, `cycle` and `schema_version` are required. IDs use
@@ -25,6 +28,74 @@ ID. Duplicate IDs, unknown keys, unsupported versions, incorrect types and
invalid physical values stop loading with a diagnostic identifying the file
and field. TOML syntax errors also report their location.
Version 2 requires explicit physical geometry. Version 1 remains readable and
receives a generated layout for compatibility. All bundled scenarios use version 2.
## Physical geometry
Pipe centerlines and storage boxes are shared by simulation, rendering, mouth
positions, and junction directions. Coordinates use millimetres, with Y up.
Pipe indices follow the expanded pipe-group ordering below.
```toml
[[geometry.pipes]]
index = 0
points_mm = [[-200.0, 300.0, 0.0], [-100.0, 300.0, 0.0], [-100.0, 200.0, 0.0]]
[[geometry.volumes]]
id = "intake_plenum"
size_mm = [200.0, 100.0, 50.0]
center_mm = [0.0, 200.0, 0.0]
[[geometry.cylinders]]
origin_mm = [0.0, 0.0, 0.0] # crank axis origin
tilt_deg = 0.0 # rotation about X
```
That pipe is 200 mm long: its length is the sum of the two straight segments.
There is no independent pipe-length field in version 2 definitions. Mesh cells
are distributed along the measured centerline; hard corners remain hard corners
in both views. The 3D tube uses bounded miter joins rather than a spline.
Paths require 2..64 finite points without consecutive duplicates.
The box above is 1 litre. Storage volumes support either `size_mm` with optional
`center_mm` (the center defaults to the origin), or two opposite corners:
```toml
[[geometry.volumes]]
id = "intake_plenum"
corners_mm = [[-100.0, 150.0, -25.0], [100.0, 250.0, 25.0]]
```
Use one form per box. Corner order can be reversed. Zero or negative sizes,
duplicate IDs, missing required volumes, or mismatched pipe/cylinder counts
are rejected. Four-stroke storage IDs are `intake_plenum`, `exhaust_collector`
for a volume collector, and `junction_0`, `junction_1`, etc. in topology order.
Two-strokes use `intake_plenum` and `crankcase`; the latter defines crankcase
clearance volume, and the renderer adds the live piston displacement to its box.
Only active fluid storage components are drawn as pressure-colored boxes.
Cylinder gas volume comes from bore, stroke, rod length, compression, and piston
position; cylinder placement supplies the crank origin and bank tilt. The 3D
cutaway uses these dimensions for sleeves, pistons, rods, heads, and valves.
All 32 bundled layouts have explicit cylinder placements and routed plumbing.
The four-stroke headers use direct swept bends and realistic collector placement,
with new lengths measured from their geometry rather than folded detours to
retain the old numerical lengths. This intentionally changes exhaust tuning.
See [header length changes](../verification/geometry-header-lengths.csv).
Intake, tailpipe, crossover and two-stroke profile lengths retain their existing
dimensions. Smooth manufactured bends are explicitly sampled centerlines;
authored hard corners still use miter joins.
Profiled two-stroke exhaust and silencers retain their sectional dimensions.
Their axial section proportions are mapped onto the measured centerline, so
changing the overall route scales section lengths while preserving diameters.
Silencer `diameter_mm` and `length_mm` define its cylindrical casing size;
straight-through gas uses the separate core diameter. Rendering shows the gas
profile and a cutaway casing. Bends do not add a new distributed bend-loss model.
The existing named engine topologies still define connections; arbitrary graph
editing is a future layer over this shared geometry.
## File discovery and distribution
The first available directory is used:
@@ -59,8 +130,11 @@ Four-stroke definitions use these sections:
| Section | Contents |
| --- | --- |
| `[engine]` | Cylinder count, layout, bank angle, phases, intake volumes, throttle, startup, load, and exhaust topology |
| `[cylinder]` | Bore/stroke/rod, compression, valve geometry and timing, combustion and heat transfer |
| `[engine]` | Cylinder count, layout, bank angle, phases, throttle, startup, load, and exhaust topology |
| `[cylinder]` | Bore/stroke/rod, compression, valve diameters/counts, combustion and heat transfer |
| `[cam.intake]`, `[cam.exhaust]` | Cam lift curves as `[angle_deg, lift_mm]` points over a 720 degree cycle |
| `[valves.intake]`, `[valves.exhaust]` | Valve mass, spring, damping, seat preload, travel and contact restitution |
| `[valve_sound]` | Contact resonance and radiating area for four-stroke valve impacts |
| `[crankshaft]` | Inertia and mechanical friction |
| `[ecu]` | Idle, actuator response, ignition curve, limiter and overrun thresholds |
| `[vehicle]` | Vehicle mass, gears, clutch, wheel geometry and road resistance |
@@ -68,12 +142,22 @@ Four-stroke definitions use these sections:
| `[junctions]` | Fitting volume and H crossover selection |
| `[silencers]` | Connecting pipes, resonator and muffler profiles, when used |
| `[[pipes]]` | Ordered pipe groups for the selected exhaust topology |
| `[[geometry.pipes]]`, `[[geometry.volumes]]`, `[[geometry.cylinders]]` | Physical routes, storage sizes and component placements |
`engine.phase_degrees` contains one phase per cylinder in cylinder index order,
within `[0,720)`. It records the actual crank offsets rather than a firing-order
label. `ecu.ignition_map` is an array of `[RPM, degrees BTDC]` pairs with strictly
increasing RPM. Negative advance means ignition after TDC.
Four-stroke valve lift is driven by the cam profile and a one dimensional
mass-spring model. Cam points must cover 0 through 720 degrees, have strictly
increasing angles, and start and end at zero lift. A valve can separate from
the cam at high RPM, producing valve float; seat, cam and travel-stop impacts
excite a damped contact mode spatialized from the engine through the existing
stereo, ear-distance and travel-delay paths. These profiles and dynamics apply
only to four-stroke cylinders. Valve masses are per valve; the model scales
effective mass, spring force and contact impulse by the configured valve count.
For a carbureted four-stroke, use `ecu.idle_control = "fixed"` and set
`ecu.idle_base_opening`. Its limiter and overrun control cut ignition while the
carburetor continues supplying fuel. Jet diameters in `[fuel]` are effective
@@ -90,6 +174,34 @@ Required cylinder geometry must be explicit. Optional calibration fields use
the typed config defaults; a misspelled field is always an error. Existing files
include explicit values so their behavior does not depend on future defaults.
## Scenario mesh resolution
Every bundled scenario defines a shared pipe density, for example:
```toml
[simulation]
cells_per_meter = 20.0
```
This applies to all intake, runner, header, tailpipe, silencer and two-stroke
profile sections. Uniform pipes use `max(1, ceil(length_m * cells_per_meter))`;
profiled pipes subdivide each geometry section at that maximum cell length.
Geometry and integrated pipe volumes are preserved. Tiny floating-point rounding
at exact integer counts is ignored. Legacy per-pipe `cells` and exhaust cell
length fields remain readable for older definitions without a global density;
when density is present it controls the mesh.
Valid density is 1..500 cells/m, with existing per-pipe and network cell caps
also enforced. Higher resolution reduces numerical diffusion and costs more CPU.
In the desktop sidebar, **Cells/m - / +** changes density in steps of five.
The mesh builds in the background and the engine restarts at its configured RPM;
solver, scene, listener and driving-control inputs are retained. Each scenario's
selection is remembered for the current app session. These controls do not write
TOML; edit `cells_per_meter` in the scenario file for a permanent default.
The desktop cam/valve, fuel and geometry editor has been removed. Engine
calibration remains available through TOML. Escape returns directly to the menu.
## Pipe groups and exhaust topologies
These definitions configure the existing topology builders. They do not yet
@@ -101,10 +213,11 @@ unique; `count` repeats its dimensions across adjacent pipes.
id = "primaries"
count = 8
diameter_mm = 38.0
length_mm = 750.0
cells = 15
```
Each expanded pipe gets its own `geometry.pipes` centerline. A group's diameter
remains the cross-section geometry; its length is derived from that centerline.
The order is significant because the topology builder connects the expanded
groups to fixed cylinder and junction ports:
@@ -123,4 +236,52 @@ A collector outlet can instead use `profile = "silenced"`. Its dimensions and
mesh then come from `[silencers]`, `[silencers.resonator]` and
`[silencers.muffler]`; omit the group's uniform pipe dimensions. The loader
checks expanded pipe counts and mesh limits before construction. Pipe positions
continue to use the shared scene routes for rendering and sound sources.
and sound-source locations come from the explicit centerlines.
## Characteristic exhaust junctions
Existing files default to the finite-volume model. To opt into nonlinear,
zero-storage exhaust junctions, add:
```toml
[junctions]
model = "characteristic" # "volume" (default), "characteristic", or "legacy"
turning_loss = 0.5 # dimensionless, 0..10; 0 gives ideal angle-independent coupling
```
This applies to exhaust header fittings, staged collectors, and H-pipe nodes.
For `collector` topology it replaces the exhaust collector storage volume with
a zero-storage node, including the two-port single-cylinder transition. That
changes the physical model and removes collector compliance. Intake plenums,
cylinders, silencers, and intentional pipe volumes remain unchanged. The
storage dimensions come from the corresponding geometry boxes in version 2.
`junction_volume_cc` is retained only for legacy version 1 layout generation.
Branch directions point **outward from the node into the pipe**. By default they
come from the first/last segment of the physical pipe centerline. Optional
direction overrides remain available for measured fitting axes. For example:
```toml
[[junctions.branches]]
pipe = 2
left = false
direction = [-1.0, 0.0, 0.0]
[[junctions.branches]]
pipe = 3
left = true
direction = [1.0, 0.0, 0.0]
```
Directions are normalized, must be finite and nonzero, and must identify active
node endpoints in characteristic/legacy mode. Pipe indices follow expanded
scenario ordering, not TOML pipe-group indices. `left` is the endpoint at pipe
coordinate zero; it does not prescribe flow direction. Reversal is automatic.
Opposing outward axes describe straight-through flow; perpendicular axes describe
a 90-degree turn. A common world rotation leaves the solution unchanged.
For listening without editing TOML, desktop and capture accept
`--junctions characteristic --junction-loss 0.5`; use `--junctions volume` for
the original fittings. Restart the scenario between models. These switches
apply to four-stroke scenarios; two-stroke exhaust is currently a profiled pipe.
See [junction solver details and validation](JUNCTION_SOLVER.md).

98
docs/JUNCTION_SOLVER.md Normal file
View File

@@ -0,0 +1,98 @@
# Direction-aware characteristic junctions
## Model and scope
The new `BoundarySystem` node solves a nonlinear common-pressure mass balance
using each branch's outgoing Euler acoustic wave curve: an isentropic
rarefaction or Rankine-Hugoniot shock curve. Branch area enters mass flow,
and pipe-coordinate orientation is handled independently of physical direction.
Incoming total enthalpy and species are mixed by incoming mass flux. Outgoing
contact density follows the mixed total enthalpy and the local pressure/velocity.
Outgoing fluxes share that enthalpy and composition, conserving total energy,
air, and fuel at the zero-storage node. Fitting forces react momentum; axial
momentum is not summed as a scalar across differently directed branches.
Pressure is found by a bracketed Newton iteration. Setup allocates scratch
arrays; node updates allocate no memory. Outgoing mass is normalized to incoming
mass to remove root roundoff. A shared donor budget scales advective fluxes when
necessary, preserving node conservation, and reports `junction_limited_fluxes`.
This is a subsonic, common-pressure engineering closure. It has not been proven
entropy stable for arbitrary mixtures, large thermal contrasts, and shocks.
Endpoint Mach numbers at or above 0.95, predicted face Mach numbers at or above
0.98, and unsuccessful pressure solves select the conservative legacy Rusanov
fallback. `junction_fallbacks` reports these cases. It is not a choked/supersonic
multi-port Riemann solver. Frequent fallback or limiting indicates a case needing
further timestep/model investigation, not a validated acoustic measurement.
The broader research on entropy-preserving Euler junctions uses common total
enthalpy and entropy-based coupling; the implemented common-pressure/mixed-enthalpy
closure is a distinct engineering choice. See
[Lang and Mindt, 2018](https://www.aimsciences.org/article/doi/10.3934/nhm.2018008)
for the stronger mathematical coupling conditions and their subsonic assumptions.
## Direction and turning
Each endpoint can specify a unit axis pointing out of the node. For a receiving
branch, the loss coefficient is the incoming-mass-weighted average of
`turning_loss * (1 + dot(outward_receiver, outward_donor)) / 2`.
Opposite axes have zero turning loss; perpendicular axes have half the configured
loss. A local signed pressure-drop relation uses `K * rho * u² / 2`, with three
outer updates of the donor-weighted direction coefficients. Donors are recomputed
as flow reverses. This is a quasi-steady uncalibrated turning-loss approximation.
It does not resolve vortices, bend radii, separation, or transverse acoustic modes.
At zero mean flow, ideal low-frequency compact-junction scattering depends on
branch impedances, not a stereo direction or a branch-angle audio filter. Angles
enter the mean-flow/turning model. Defaults use schematic route tangents, which
can be overridden with actual branch axes in TOML. No 3D CFD is involved.
## Production selection
```powershell
.\out\build\windows-gcc\fluidsim_desktop.exe --example v8-h --solver rusanov --junctions characteristic --junction-loss 0.5 --scene dry
```
Use `--junctions volume` for the original finite-volume fittings or
`--junctions legacy` for the old zero-storage numerical node. The volume model
remains the default. Pipe solvers and junction models are independent. Restart
for model comparisons; auto gain remains off by default.
The same options work in `fluidsim_audio_capture`. Captures report junction
iterations, fallback, limiting, and pipe positivity repairs. On collector-topology
engines, selecting characteristic/legacy explicitly replaces collector storage
with a node. Changes there include removal of that physical compliance, so they
cannot all be attributed to numerical accuracy. The V8 and staged exhaust paths
also change from storage fittings to zero-storage nodes.
See [the configuration guide](ENGINE_CONFIG.md#characteristic-exhaust-junctions)
for branch direction overrides and endpoint indexing.
## Validation
`fluidsim_junction_tests` checks mass, total energy, air/fuel conservation,
unequal areas and thermal states, opposite donor configurations, all-pipe
coordinate reversal, world rotation, straight-versus-turning flow preference,
invalid direction rejection, and explicit supersonic fallback reporting.
The 408-case acoustic benchmark compares volume fittings, characteristic nodes,
and legacy nodes across all four pipe schemes, meshes, and timesteps. Equal-area
characteristic scattering approaches reflected energy 1/9 and transmitted energy
8/9 after approximately removing common pipe attenuation. With outlet areas
2A and 3A and inlet area A, the targets are 4/9 reflected and 5/9 transmitted.
Characteristic pulse cases require no fallback. At the finest mesh, transmitted
pressure gain and phase at 500 and 1000 Hz are compared against ideal equal-area
gain 2/3 and zero extra phase, after straight-pipe de-embedding.
For Rusanov, 160 cells per 2 m, Courant 0.4, corrected transmission is about
0.889 with the characteristic node versus 0.222 with the legacy node. Seven
small-volume cases remain flagged in the separate volume model. Full measured
junction spectra, geometry calibration, high-Mach coupling, and entropy
admissibility remain follow-up work.
Listening captures are in `out/audio/junction-ab-single-{volume,characteristic}-rendered.wav`
and `out/audio/junction-ab-v8-{volume,characteristic}-rendered.wav`. Their
`-level-matched.wav` copies use RMS 0.08 with constant per-file gain. The single
comparison is 3000 RPM / pedal 0.3; V8 is 3000 RPM / pedal 0.5 with turning-loss
coefficient 0.5 in the characteristic case. Both use dry Rusanov rendering.
These comparisons include the change in storage model described above.

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "big-carb"
name = "8.0 litre / carbureted big-block"
category = "V8 / big-block"
@@ -7,9 +7,11 @@ description = "Cross-plane firing / American 4-2-1 + H / carb fuel transport"
order = 29
cycle = "four_stroke"
[simulation]
cells_per_meter = 20.0
[engine]
bank_angle_deg = 90.0
collector_volume_litres = 2.0
cylinders = 8
initial_rpm = 1120.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 30.0
minimum_substeps = 1
phase_degrees = [0.0, 450.0, 270.0, 540.0, 630.0, 360.0, 180.0, 89.99998]
pipe_damping = 1.0
plenum_volume_litres = 8.0
starter_torque_nm = 201.1122
throttle_diameter_mm = 85.0
topology = "american_v8"
@@ -31,18 +32,12 @@ burn_duration_deg = 65.0
burn_start_deg = 5.0
compression = 9.0
energy_variation = 0.05
exhaust_close_deg = 370.0
exhaust_open_deg = 140.0
exhaust_valve_count = 1
exhaust_valve_diameter_mm = 36.576
exhaust_valve_lift_mm = 10.287
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 570.0
intake_open_deg = 350.0
intake_valve_count = 1
intake_valve_diameter_mm = 43.434
intake_valve_lift_mm = 11.43
misfire_probability = 0.0
rod_mm = 176.4
stroke_mm = 98.0
@@ -113,51 +108,619 @@ bowl_refill_kg_per_s = 0.1
[junctions]
h_pipe = true
junction_volume_cc = 50.0
[[pipes]]
id = "intake"
cells = 3
diameter_mm = 70.0
length_mm = 180.0
[[pipes]]
id = "runners"
count = 8
cells = 2
diameter_mm = 38.0
length_mm = 120.0
[[pipes]]
id = "primaries"
count = 8
cells = 15
diameter_mm = 38.0
length_mm = 750.0
[[pipes]]
id = "secondaries"
count = 4
cells = 8
diameter_mm = 45.0
length_mm = 400.0
[[pipes]]
id = "bank_collectors"
count = 2
cells = 11
diameter_mm = 50.0
length_mm = 550.0
[[pipes]]
id = "tailpipes"
count = 2
cells = 25
diameter_mm = 50.0
length_mm = 1250.0
[[pipes]]
id = "crossover"
cells = 16
diameter_mm = 38.0
length_mm = 780.0
[cam.intake]
points = [
[0.000, 0.000000],
[350.000, 0.000000],
[355.000, 0.266213],
[360.000, 0.996153],
[365.000, 2.086769],
[370.000, 3.435011],
[375.000, 4.937829],
[380.000, 6.492171],
[385.000, 7.994989],
[390.000, 9.343231],
[395.000, 10.433847],
[400.000, 11.163787],
[405.000, 11.430000],
[515.000, 11.430000],
[520.000, 11.163787],
[525.000, 10.433847],
[530.000, 9.343231],
[535.000, 7.994989],
[540.000, 6.492171],
[545.000, 4.937829],
[550.000, 3.435011],
[555.000, 2.086769],
[560.000, 0.996153],
[565.000, 0.266213],
[570.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[140.000, 0.000000],
[145.000, 0.219826],
[150.000, 0.825192],
[155.000, 1.734933],
[160.000, 2.867881],
[165.000, 4.142870],
[170.000, 5.478734],
[175.000, 6.794307],
[180.000, 8.008421],
[185.000, 9.039912],
[190.000, 9.807611],
[195.000, 10.230353],
[197.500, 10.287000],
[312.500, 10.287000],
[317.500, 10.067174],
[322.500, 9.461808],
[327.500, 8.552067],
[332.500, 7.419119],
[337.500, 6.144130],
[342.500, 4.808266],
[347.500, 3.492693],
[352.500, 2.278579],
[357.500, 1.247088],
[362.500, 0.479389],
[367.500, 0.056647],
[370.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ -198.45, 0, 0 ]
tilt_deg = -45.0
[[geometry.cylinders]]
origin_mm = [ -66.15, 0, 0 ]
tilt_deg = -45.0
[[geometry.cylinders]]
origin_mm = [ 66.15, 0, 0 ]
tilt_deg = -45.0
[[geometry.cylinders]]
origin_mm = [ 198.45, 0, 0 ]
tilt_deg = -45.0
[[geometry.cylinders]]
origin_mm = [ -198.45, 0, 0 ]
tilt_deg = 45.0
[[geometry.cylinders]]
origin_mm = [ -66.15, 0, 0 ]
tilt_deg = 45.0
[[geometry.cylinders]]
origin_mm = [ 66.15, 0, 0 ]
tilt_deg = 45.0
[[geometry.cylinders]]
origin_mm = [ 198.45, 0, 0 ]
tilt_deg = 45.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -444.6, 334.9036, 0 ],
[ -264.6, 334.9036, 0 ]
]
# runners 1
[[geometry.pipes]]
index = 1
points_mm = [
[ -198.45, 314.716, -130.0582 ],
[ -198.45, 194.716, -130.0582 ]
]
# runners 2
[[geometry.pipes]]
index = 2
points_mm = [
[ -66.15, 314.716, -130.0582 ],
[ -66.15, 194.716, -130.0582 ]
]
# runners 3
[[geometry.pipes]]
index = 3
points_mm = [
[ 66.15, 314.716, -130.0582 ],
[ 66.15, 194.716, -130.0582 ]
]
# runners 4
[[geometry.pipes]]
index = 4
points_mm = [
[ 198.45, 314.716, -130.0582 ],
[ 198.45, 194.716, -130.0582 ]
]
# runners 5
[[geometry.pipes]]
index = 5
points_mm = [
[ -198.45, 314.716, 130.0582 ],
[ -198.45, 194.716, 130.0582 ]
]
# runners 6
[[geometry.pipes]]
index = 6
points_mm = [
[ -66.15, 314.716, 130.0582 ],
[ -66.15, 194.716, 130.0582 ]
]
# runners 7
[[geometry.pipes]]
index = 7
points_mm = [
[ 66.15, 314.716, 130.0582 ],
[ 66.15, 194.716, 130.0582 ]
]
# runners 8
[[geometry.pipes]]
index = 8
points_mm = [
[ 198.45, 314.716, 130.0582 ],
[ 198.45, 194.716, 130.0582 ]
]
# primaries 1
[[geometry.pipes]]
index = 9
points_mm = [
[ -198.45, 121.9759, -202.7982 ],
[ -197.0012, 111.4774, -210.8704 ],
[ -192.907, 102.0521, -218.6645 ],
[ -186.5452, 93.5225, -226.147 ],
[ -178.2937, 85.7113, -233.2843 ],
[ -168.5305, 78.441, -240.0431 ],
[ -157.6335, 71.5342, -246.3898 ],
[ -145.9807, 64.8134, -252.291 ],
[ -133.9499, 58.1011, -257.7131 ],
[ -121.9191, 51.2201, -262.6227 ],
[ -110.2663, 43.9927, -266.9864 ],
[ -99.3693, 36.2416, -270.7706 ],
[ -89.6061, 27.7894, -273.9418 ],
[ -81.3546, 18.4586, -276.4666 ],
[ -74.9928, 8.0717, -278.3115 ],
[ -70.8985, -3.5486, -279.443 ],
[ -69.4498, -16.5798, -279.8276 ]
]
# primaries 2
[[geometry.pipes]]
index = 10
points_mm = [
[ -66.15, 121.9759, -202.7982 ],
[ -66.0127, 113.4631, -209.2701 ],
[ -65.6247, 105.1542, -215.8763 ],
[ -65.0218, 97.0014, -222.5408 ],
[ -64.2399, 88.9567, -229.1874 ],
[ -63.3147, 80.9722, -235.7399 ],
[ -62.282, 73.0001, -242.1222 ],
[ -61.1777, 64.9925, -248.2581 ],
[ -60.0376, 56.9014, -254.0714 ],
[ -58.8975, 48.6789, -259.486 ],
[ -57.7932, 40.2772, -264.4258 ],
[ -56.7605, 31.6483, -268.8146 ],
[ -55.8353, 22.7444, -272.5762 ],
[ -55.0534, 13.5175, -275.6344 ],
[ -54.4505, 3.9198, -277.9132 ],
[ -54.0625, -6.0967, -279.3363 ],
[ -53.9252, -16.5798, -279.8276 ]
]
# primaries 3
[[geometry.pipes]]
index = 11
points_mm = [
[ 66.15, 121.9759, -202.7982 ],
[ 67.5987, 111.4774, -210.8704 ],
[ 71.693, 102.0521, -218.6645 ],
[ 78.0548, 93.5225, -226.147 ],
[ 86.3063, 85.7113, -233.2843 ],
[ 96.0695, 78.441, -240.0431 ],
[ 106.9665, 71.5342, -246.3898 ],
[ 118.6193, 64.8134, -252.291 ],
[ 130.6501, 58.1011, -257.7131 ],
[ 142.6809, 51.2201, -262.6227 ],
[ 154.3337, 43.9927, -266.9864 ],
[ 165.2307, 36.2416, -270.7706 ],
[ 174.9939, 27.7894, -273.9418 ],
[ 183.2454, 18.4586, -276.4666 ],
[ 189.6072, 8.0717, -278.3115 ],
[ 193.7014, -3.5486, -279.443 ],
[ 195.1502, -16.5798, -279.8276 ]
]
# primaries 4
[[geometry.pipes]]
index = 12
points_mm = [
[ 198.45, 121.9759, -202.7982 ],
[ 198.5873, 113.4631, -209.2701 ],
[ 198.9753, 105.1542, -215.8763 ],
[ 199.5782, 97.0014, -222.5408 ],
[ 200.3601, 88.9567, -229.1874 ],
[ 201.2853, 80.9722, -235.7399 ],
[ 202.318, 73.0001, -242.1222 ],
[ 203.4223, 64.9925, -248.2581 ],
[ 204.5624, 56.9014, -254.0714 ],
[ 205.7025, 48.6789, -259.486 ],
[ 206.8068, 40.2772, -264.4258 ],
[ 207.8394, 31.6483, -268.8146 ],
[ 208.7647, 22.7444, -272.5762 ],
[ 209.5466, 13.5175, -275.6344 ],
[ 210.1495, 3.9198, -277.9132 ],
[ 210.5375, -6.0967, -279.3363 ],
[ 210.6748, -16.5798, -279.8276 ]
]
# primaries 5
[[geometry.pipes]]
index = 13
points_mm = [
[ -198.45, 121.9759, 202.7982 ],
[ -197.277, 108.2502, 212.6484 ],
[ -193.869, 94.7077, 221.8246 ],
[ -188.3921, 81.4455, 230.332 ],
[ -181.0126, 68.5602, 238.1762 ],
[ -171.8968, 56.1489, 245.3622 ],
[ -161.2109, 44.3085, 251.8955 ],
[ -149.1211, 33.1359, 257.7814 ],
[ -135.7937, 22.7279, 263.0251 ],
[ -121.395, 13.1814, 267.632 ],
[ -106.0911, 4.5935, 271.6075 ],
[ -90.0483, -2.9391, 274.9567 ],
[ -73.433, -9.3194, 277.6851 ],
[ -56.4112, -14.4506, 279.7979 ],
[ -39.1493, -18.2357, 281.3005 ],
[ -21.8136, -20.5778, 282.1981 ],
[ -4.5702, -21.38, 282.4962 ]
]
# primaries 6
[[geometry.pipes]]
index = 14
points_mm = [
[ -66.15, 121.9759, 202.7982 ],
[ -64.977, 108.2502, 212.6484 ],
[ -61.569, 94.7077, 221.8246 ],
[ -56.0921, 81.4455, 230.332 ],
[ -48.7126, 68.5602, 238.1762 ],
[ -39.5968, 56.1489, 245.3622 ],
[ -28.9109, 44.3085, 251.8955 ],
[ -16.8211, 33.1359, 257.7814 ],
[ -3.4937, 22.7279, 263.0251 ],
[ 10.905, 13.1814, 267.632 ],
[ 26.2089, 4.5935, 271.6075 ],
[ 42.2516, -2.9391, 274.9567 ],
[ 58.867, -9.3194, 277.6851 ],
[ 75.8888, -14.4506, 279.7979 ],
[ 93.1506, -18.2357, 281.3005 ],
[ 110.4864, -20.5778, 282.1981 ],
[ 127.7298, -21.38, 282.4962 ]
]
# primaries 7
[[geometry.pipes]]
index = 15
points_mm = [
[ 66.15, 121.9759, 202.7982 ],
[ 65.6316, 113.1956, 209.4857 ],
[ 64.1664, 104.7363, 216.2519 ],
[ 61.8898, 96.5327, 223.0266 ],
[ 58.937, 88.5195, 229.7393 ],
[ 55.4433, 80.6312, 236.3196 ],
[ 51.5438, 72.8027, 242.6971 ],
[ 47.3738, 64.9684, 248.8014 ],
[ 43.0685, 57.063, 254.562 ],
[ 38.7633, 49.0212, 259.9086 ],
[ 34.5933, 40.7777, 264.7708 ],
[ 30.6938, 32.2671, 269.0781 ],
[ 27.2, 23.424, 272.7602 ],
[ 24.2472, 14.1831, 275.7465 ],
[ 21.9706, 4.4791, 277.9668 ],
[ 20.5055, -5.7534, 279.3506 ],
[ 19.9871, -16.5798, 279.8276 ]
]
# primaries 8
[[geometry.pipes]]
index = 16
points_mm = [
[ 198.45, 121.9759, 202.7982 ],
[ 197.9315, 113.1956, 209.4857 ],
[ 196.4664, 104.7363, 216.2519 ],
[ 194.1898, 96.5327, 223.0266 ],
[ 191.237, 88.5195, 229.7393 ],
[ 187.7432, 80.6312, 236.3196 ],
[ 183.8437, 72.8027, 242.6971 ],
[ 179.6738, 64.9684, 248.8014 ],
[ 175.3685, 57.063, 254.562 ],
[ 171.0633, 49.0212, 259.9086 ],
[ 166.8933, 40.7777, 264.7708 ],
[ 162.9938, 32.2671, 269.0781 ],
[ 159.5, 23.424, 272.7602 ],
[ 156.5472, 14.1831, 275.7465 ],
[ 154.2706, 4.4791, 277.9668 ],
[ 152.8055, -5.7534, 279.3506 ],
[ 152.2871, -16.5798, 279.8276 ]
]
# secondaries 1
[[geometry.pipes]]
index = 17
points_mm = [
[ -33.8798, -36.9883, -290.0681 ],
[ -8.445, -37.449, -290.0681 ],
[ 16.3173, -38.751, -290.0681 ],
[ 40.5034, -40.7741, -290.0681 ],
[ 64.209, -43.3982, -290.0681 ],
[ 87.5305, -46.503, -290.0681 ],
[ 110.5638, -49.9683, -290.0681 ],
[ 133.4049, -53.6741, -290.0681 ],
[ 156.15, -57.5, -290.0681 ],
[ 178.8951, -61.3259, -290.0681 ],
[ 201.7362, -65.0317, -290.0681 ],
[ 224.7695, -68.497, -290.0681 ],
[ 248.0909, -71.6018, -290.0681 ],
[ 271.7966, -74.2259, -290.0681 ],
[ 295.9826, -76.249, -290.0681 ],
[ 320.745, -77.551, -290.0681 ],
[ 346.1798, -78.0117, -290.0681 ]
]
# secondaries 2
[[geometry.pipes]]
index = 18
points_mm = [
[ 230.7201, -40.4426, -290.0681 ],
[ 238.6832, -40.8257, -290.0681 ],
[ 246.3475, -41.9085, -290.0681 ],
[ 253.7557, -43.5909, -290.0681 ],
[ 260.9506, -45.773, -290.0681 ],
[ 267.9748, -48.355, -290.0681 ],
[ 274.871, -51.2367, -290.0681 ],
[ 281.6818, -54.3184, -290.0681 ],
[ 288.45, -57.5, -290.0681 ],
[ 295.2181, -60.6816, -290.0681 ],
[ 302.029, -63.7633, -290.0681 ],
[ 308.9251, -66.645, -290.0681 ],
[ 315.9494, -69.227, -290.0681 ],
[ 323.1442, -71.4091, -290.0681 ],
[ 330.5525, -73.0915, -290.0681 ],
[ 338.2168, -74.1743, -290.0681 ],
[ 346.1798, -74.5574, -290.0681 ]
]
# secondaries 3
[[geometry.pipes]]
index = 19
points_mm = [
[ 32.2702, -37.3632, 290.0681 ],
[ 53.3195, -37.8155, 290.0681 ],
[ 73.7968, -39.0937, 290.0681 ],
[ 93.7839, -41.0799, 290.0681 ],
[ 113.3624, -43.656, 290.0681 ],
[ 132.614, -46.704, 290.0681 ],
[ 151.6206, -50.106, 290.0681 ],
[ 170.4636, -53.744, 290.0681 ],
[ 189.225, -57.5, 290.0681 ],
[ 207.9864, -61.256, 290.0681 ],
[ 226.8294, -64.894, 290.0681 ],
[ 245.8359, -68.296, 290.0681 ],
[ 265.0875, -71.344, 290.0681 ],
[ 284.6661, -73.9201, 290.0681 ],
[ 304.6531, -75.9063, 290.0681 ],
[ 325.1305, -77.1845, 290.0681 ],
[ 346.1798, -77.6368, 290.0681 ]
]
# secondaries 4
[[geometry.pipes]]
index = 20
points_mm = [
[ 164.5702, -38.7945, 290.0681 ],
[ 176.8768, -39.2146, 290.0681 ],
[ 188.801, -40.402, 290.0681 ],
[ 200.3974, -42.247, 290.0681 ],
[ 211.7207, -44.64, 290.0681 ],
[ 222.8255, -47.4714, 290.0681 ],
[ 233.7664, -50.6316, 290.0681 ],
[ 244.598, -54.011, 290.0681 ],
[ 255.375, -57.5, 290.0681 ],
[ 266.152, -60.989, 290.0681 ],
[ 276.9836, -64.3684, 290.0681 ],
[ 287.9245, -67.5286, 290.0681 ],
[ 299.0292, -70.36, 290.0681 ],
[ 310.3525, -72.753, 290.0681 ],
[ 321.9489, -74.598, 290.0681 ],
[ 333.8732, -75.7854, 290.0681 ],
[ 346.1798, -76.2055, 290.0681 ]
]
# bank_collectors 1
[[geometry.pipes]]
index = 21
points_mm = [
[ 383.0201, -80, -294.0319 ],
[ 417.8478, -80, -295.272 ],
[ 451.5734, -80, -298.7767 ],
[ 484.3543, -80, -304.2224 ],
[ 516.3481, -80, -311.2857 ],
[ 547.7121, -80, -319.643 ],
[ 578.6038, -80, -328.9709 ],
[ 609.1806, -80, -338.9457 ],
[ 639.6, -80, -349.2441 ],
[ 670.0194, -80, -359.5425 ],
[ 700.5962, -80, -369.5174 ],
[ 731.4879, -80, -378.8453 ],
[ 762.852, -80, -387.2026 ],
[ 794.8456, -80, -394.2659 ],
[ 827.6266, -80, -399.7116 ],
[ 861.3521, -80, -403.2163 ],
[ 896.1799, -80, -404.4564 ]
]
# bank_collectors 2
[[geometry.pipes]]
index = 22
points_mm = [
[ 383.0201, -80, 294.0319 ],
[ 417.8478, -80, 295.272 ],
[ 451.5734, -80, 298.7767 ],
[ 484.3543, -80, 304.2224 ],
[ 516.3481, -80, 311.2857 ],
[ 547.7121, -80, 319.643 ],
[ 578.6038, -80, 328.9709 ],
[ 609.1806, -80, 338.9457 ],
[ 639.6, -80, 349.2441 ],
[ 670.0194, -80, 359.5425 ],
[ 700.5962, -80, 369.5174 ],
[ 731.4879, -80, 378.8453 ],
[ 762.852, -80, 387.2026 ],
[ 794.8456, -80, 394.2659 ],
[ 827.6266, -80, 399.7116 ],
[ 861.3521, -80, 403.2163 ],
[ 896.1799, -80, 404.4564 ]
]
# tailpipes 1
[[geometry.pipes]]
index = 23
points_mm = [
[ 933.0202, -80, -408.4201 ],
[ 2183.0201, -80, -408.4201 ]
]
# tailpipes 2
[[geometry.pipes]]
index = 24
points_mm = [
[ 933.0202, -80, 408.4201 ],
[ 2183.0201, -80, 408.4201 ]
]
# crossover
[[geometry.pipes]]
index = 25
points_mm = [
[ 914.6, -80, -390 ],
[ 914.6, -80, 390 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 334.9036, 0 ]
size_mm = [ 529.2, 40.3753, 374.4163 ]
[[geometry.volumes]]
id = "junction_0"
center_mm = [ -52.3, -35, -290.0681 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_1"
center_mm = [ 212.3, -35, -290.0681 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_2"
center_mm = [ 13.85, -35, 290.0681 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_3"
center_mm = [ 146.15, -35, 290.0681 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_4"
center_mm = [ 364.6, -80, -290.0681 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_5"
center_mm = [ 364.6, -80, 290.0681 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_6"
center_mm = [ 914.6, -80, -408.4201 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_7"
center_mm = [ 914.6, -80, 408.4201 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "busso"
name = "Busso-inspired / 3.0 V6"
category = "V6"
@@ -7,9 +7,11 @@ description = "Estimated concept calibration / EFI"
order = 26
cycle = "four_stroke"
[simulation]
cells_per_meter = 20.0
[engine]
bank_angle_deg = 60.0
collector_volume_litres = 1.5
cylinders = 6
initial_rpm = 1400.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 30.0
minimum_substeps = 1
phase_degrees = [0.0, 600.0, 480.0, 360.0, 240.0, 120.0]
pipe_damping = 1.0
plenum_volume_litres = 6.0
starter_torque_nm = 73.97478
throttle_diameter_mm = 65.0
topology = "collector"
@@ -31,18 +32,12 @@ burn_duration_deg = 60.0
burn_start_deg = 5.0
compression = 10.0
energy_variation = 0.05
exhaust_close_deg = 390.0
exhaust_open_deg = 130.0
exhaust_valve_count = 1
exhaust_valve_diameter_mm = 29.76
exhaust_valve_lift_mm = 8.370001
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 590.0
intake_open_deg = 340.0
intake_valve_count = 1
intake_valve_diameter_mm = 35.34
intake_valve_lift_mm = 9.3
misfire_probability = 0.0
rod_mm = 130.68
stroke_mm = 72.6
@@ -101,26 +96,348 @@ system = "injection"
[[pipes]]
id = "intake"
cells = 4
diameter_mm = 75.0
length_mm = 200.0
[[pipes]]
id = "runners"
count = 6
cells = 5
diameter_mm = 35.0
length_mm = 250.0
[[pipes]]
id = "primaries"
count = 6
cells = 13
diameter_mm = 38.0
length_mm = 650.0
[[pipes]]
id = "tailpipe"
cells = 30
diameter_mm = 60.0
length_mm = 1500.0
[cam.intake]
points = [
[0.000, 0.000000],
[340.000, 0.000000],
[345.000, 0.169037],
[350.000, 0.638054],
[355.000, 1.349914],
[360.000, 2.247475],
[365.000, 3.273600],
[370.000, 4.371149],
[375.000, 5.482982],
[380.000, 6.551962],
[385.000, 7.520947],
[390.000, 8.332800],
[395.000, 8.930381],
[400.000, 9.256550],
[402.500, 9.300000],
[527.500, 9.300000],
[532.500, 9.130963],
[537.500, 8.661946],
[542.500, 7.950086],
[547.500, 7.052525],
[552.500, 6.026400],
[557.500, 4.928851],
[562.500, 3.817018],
[567.500, 2.748038],
[572.500, 1.779053],
[577.500, 0.967200],
[582.500, 0.369619],
[587.500, 0.043450],
[590.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[130.000, 0.000000],
[135.000, 0.140960],
[140.000, 0.533364],
[145.000, 1.131493],
[150.000, 1.889632],
[155.000, 2.762062],
[160.000, 3.703068],
[165.000, 4.666933],
[170.000, 5.607939],
[175.000, 6.480369],
[180.000, 7.238508],
[185.000, 7.836637],
[190.000, 8.229041],
[195.000, 8.370001],
[325.000, 8.370001],
[330.000, 8.229041],
[335.000, 7.836637],
[340.000, 7.238508],
[345.000, 6.480369],
[350.000, 5.607939],
[355.000, 4.666933],
[360.000, 3.703068],
[365.000, 2.762062],
[370.000, 1.889632],
[375.000, 1.131493],
[380.000, 0.533364],
[385.000, 0.140960],
[390.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ -111, 0, 0 ]
tilt_deg = -30.0
[[geometry.cylinders]]
origin_mm = [ 0, 0, 0 ]
tilt_deg = -30.0
[[geometry.cylinders]]
origin_mm = [ 111, 0, 0 ]
tilt_deg = -30.0
[[geometry.cylinders]]
origin_mm = [ -111, 0, 0 ]
tilt_deg = 30.0
[[geometry.cylinders]]
origin_mm = [ 0, 0, 0 ]
tilt_deg = 30.0
[[geometry.cylinders]]
origin_mm = [ 111, 0, 0 ]
tilt_deg = 30.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -366.5, 459.3216, 0 ],
[ -166.5, 459.3216, 0 ]
]
# runners 1
[[geometry.pipes]]
index = 1
points_mm = [
[ -111, 413.2667, -51.3072 ],
[ -111, 163.2667, -51.3072 ]
]
# runners 2
[[geometry.pipes]]
index = 2
points_mm = [
[ 0, 413.2667, -51.3072 ],
[ 0, 163.2667, -51.3072 ]
]
# runners 3
[[geometry.pipes]]
index = 3
points_mm = [
[ 111, 413.2667, -51.3072 ],
[ 111, 163.2667, -51.3072 ]
]
# runners 4
[[geometry.pipes]]
index = 4
points_mm = [
[ -111, 413.2667, 51.3072 ],
[ -111, 163.2667, 51.3072 ]
]
# runners 5
[[geometry.pipes]]
index = 5
points_mm = [
[ 0, 413.2667, 51.3072 ],
[ 0, 163.2667, 51.3072 ]
]
# runners 6
[[geometry.pipes]]
index = 6
points_mm = [
[ 111, 413.2667, 51.3072 ],
[ 111, 163.2667, 51.3072 ]
]
# primaries 1
[[geometry.pipes]]
index = 7
points_mm = [
[ -111, 121.4167, -123.7935 ],
[ -109.1317, 108.6131, -136.7179 ],
[ -103.8516, 95.8547, -143.7139 ],
[ -95.6472, 83.2494, -145.3017 ],
[ -85.0059, 70.9048, -142.0016 ],
[ -72.415, 58.9287, -134.3337 ],
[ -58.3619, 47.4287, -122.818 ],
[ -43.334, 36.5127, -107.9749 ],
[ -27.8188, 26.2884, -90.3245 ],
[ -12.3035, 16.8635, -70.387 ],
[ 2.7243, 8.3456, -48.6825 ],
[ 16.7774, 0.8427, -25.7311 ],
[ 29.3683, -5.5377, -2.0532 ],
[ 40.0096, -10.6877, 21.8312 ],
[ 48.214, -14.4997, 45.4019 ],
[ 53.4941, -16.8659, 68.1387 ],
[ 55.3624, -17.6786, 89.5215 ]
]
# primaries 2
[[geometry.pipes]]
index = 8
points_mm = [
[ 0, 121.4167, -123.7935 ],
[ 0.8855, 110.0026, -134.8242 ],
[ 3.3879, 98.2756, -140.1187 ],
[ 7.2762, 86.3804, -140.21 ],
[ 12.3195, 74.4619, -135.6312 ],
[ 18.2867, 62.6649, -126.9155 ],
[ 24.947, 51.1341, -114.596 ],
[ 32.0692, 40.0143, -99.2058 ],
[ 39.4224, 29.4503, -81.2781 ],
[ 46.7756, 19.5869, -61.3459 ],
[ 53.8978, 10.5688, -39.9424 ],
[ 60.558, 2.5409, -17.6007 ],
[ 66.5253, -4.352, 5.1461 ],
[ 71.5685, -9.9652, 27.7648 ],
[ 75.4569, -14.1539, 49.7224 ],
[ 77.9593, -16.7733, 70.4856 ],
[ 78.8447, -17.6786, 89.5215 ]
]
# primaries 3
[[geometry.pipes]]
index = 9
points_mm = [
[ 111, 121.4167, -123.7935 ],
[ 110.9026, 110.4379, -134.231 ],
[ 110.6273, 99.0339, -138.9924 ],
[ 110.1996, 87.3613, -138.6148 ],
[ 109.6449, 75.5763, -133.6355 ],
[ 108.9885, 63.8354, -124.5916 ],
[ 108.2558, 52.2949, -112.0203 ],
[ 107.4724, 41.1112, -96.4587 ],
[ 106.6635, 30.4408, -78.4441 ],
[ 105.8547, 20.4401, -58.5136 ],
[ 105.0712, 11.2653, -37.2044 ],
[ 104.3386, 3.073, -15.0537 ],
[ 103.6822, -3.9805, 7.4014 ],
[ 103.1275, -9.7389, 29.6236 ],
[ 102.6997, -14.0456, 51.0758 ],
[ 102.4245, -16.7443, 71.2209 ],
[ 102.3271, -17.6786, 89.5215 ]
]
# primaries 4
[[geometry.pipes]]
index = 10
points_mm = [
[ -111, 121.4167, 123.7935 ],
[ -110.0686, 112.54, 133.3156 ],
[ -107.3627, 102.9428, 140.7747 ],
[ -103.0145, 92.8035, 146.3708 ],
[ -97.1563, 82.3007, 150.3036 ],
[ -89.9205, 71.6126, 152.7732 ],
[ -81.4393, 60.9178, 153.9794 ],
[ -71.845, 50.3947, 154.1221 ],
[ -61.2699, 40.2217, 153.4011 ],
[ -49.8463, 30.5773, 152.0164 ],
[ -37.7065, 21.6399, 150.1678 ],
[ -24.9827, 13.5879, 148.0551 ],
[ -11.8074, 6.5997, 145.8784 ],
[ 1.6873, 0.8539, 143.8375 ],
[ 15.369, -3.4712, 142.1322 ],
[ 29.1054, -6.1971, 140.9624 ],
[ 42.7643, -7.1453, 140.5281 ]
]
# primaries 5
[[geometry.pipes]]
index = 11
points_mm = [
[ 0, 121.4167, 123.7935 ],
[ 0.7587, 115.5283, 130.3663 ],
[ 2.9028, 109.4543, 135.6078 ],
[ 6.2345, 103.19, 139.6432 ],
[ 10.5557, 96.7306, 142.5977 ],
[ 15.6686, 90.0711, 144.5966 ],
[ 21.3753, 83.2068, 145.7648 ],
[ 27.4778, 76.1328, 146.2277 ],
[ 33.7783, 68.8444, 146.1105 ],
[ 40.0787, 61.3366, 145.5382 ],
[ 46.1812, 53.6047, 144.6361 ],
[ 51.8879, 45.6438, 143.5293 ],
[ 57.0008, 37.4491, 142.3431 ],
[ 61.3221, 29.0157, 141.2026 ],
[ 64.6537, 20.339, 140.2329 ],
[ 66.7978, 11.4139, 139.5593 ],
[ 67.5565, 2.2357, 139.307 ]
]
# primaries 6
[[geometry.pipes]]
index = 12
points_mm = [
[ 111, 121.4167, 123.7935 ],
[ 110.9165, 116.1043, 129.5562 ],
[ 110.6807, 110.3205, 134.1964 ],
[ 110.3142, 104.1052, 137.8177 ],
[ 109.8389, 97.4984, 140.5239 ],
[ 109.2765, 90.54, 142.4182 ],
[ 108.6487, 83.2702, 143.6045 ],
[ 107.9774, 75.7288, 144.1863 ],
[ 107.2844, 67.9558, 144.267 ],
[ 106.5913, 59.9913, 143.9504 ],
[ 105.9201, 51.8753, 143.3399 ],
[ 105.2923, 43.6476, 142.5392 ],
[ 104.7299, 35.3484, 141.6518 ],
[ 104.2546, 27.0177, 140.7813 ],
[ 103.8881, 18.6953, 140.0313 ],
[ 103.6522, 10.4213, 139.5053 ],
[ 103.5688, 2.2357, 139.307 ]
]
# tailpipe
[[geometry.pipes]]
index = 13
points_mm = [
[ 157.2357, -55, 146.7572 ],
[ 1657.2357, -55, 146.7572 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 459.3216, 0 ]
size_mm = [ 333, 92.1099, 195.6144 ]
[[geometry.volumes]]
id = "exhaust_collector"
center_mm = [ 100, -55, 146.7572 ]
size_mm = [ 114.4714, 114.4714, 114.4714 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "ej20"
name = "EJ20-inspired / 2.0 flat four NA"
category = "Boxer / flat four"
@@ -7,9 +7,11 @@ description = "Estimated concept calibration / EFI"
order = 27
cycle = "four_stroke"
[simulation]
cells_per_meter = 20.0
[engine]
bank_angle_deg = 180.0
collector_volume_litres = 1.0
cylinders = 4
initial_rpm = 1400.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 30.0
minimum_substeps = 1
phase_degrees = [0.0, 540.0, 360.0, 180.0]
pipe_damping = 1.0
plenum_volume_litres = 4.0
starter_torque_nm = 49.85708
throttle_diameter_mm = 65.0
topology = "collector"
@@ -31,18 +32,12 @@ burn_duration_deg = 60.0
burn_start_deg = 5.0
compression = 10.0
energy_variation = 0.05
exhaust_close_deg = 390.0
exhaust_open_deg = 130.0
exhaust_valve_count = 1
exhaust_valve_diameter_mm = 29.44
exhaust_valve_lift_mm = 8.280001
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 590.0
intake_open_deg = 340.0
intake_valve_count = 1
intake_valve_diameter_mm = 34.96
intake_valve_lift_mm = 9.2
misfire_probability = 0.0
rod_mm = 135.0
stroke_mm = 75.0
@@ -101,26 +96,278 @@ system = "injection"
[[pipes]]
id = "intake"
cells = 4
diameter_mm = 75.0
length_mm = 200.0
[[pipes]]
id = "runners"
count = 4
cells = 5
diameter_mm = 35.0
length_mm = 250.0
[[pipes]]
id = "primaries"
count = 4
cells = 13
diameter_mm = 38.0
length_mm = 650.0
[[pipes]]
id = "tailpipe"
cells = 30
diameter_mm = 60.0
length_mm = 1500.0
[cam.intake]
points = [
[0.000, 0.000000],
[340.000, 0.000000],
[345.000, 0.167219],
[350.000, 0.631194],
[355.000, 1.335398],
[360.000, 2.223309],
[365.000, 3.238400],
[370.000, 4.324147],
[375.000, 5.424026],
[380.000, 6.481510],
[385.000, 7.440077],
[390.000, 8.243200],
[395.000, 8.834355],
[400.000, 9.157018],
[402.500, 9.200000],
[527.500, 9.200000],
[532.500, 9.032781],
[537.500, 8.568806],
[542.500, 7.864602],
[547.500, 6.976691],
[552.500, 5.961600],
[557.500, 4.875853],
[562.500, 3.775974],
[567.500, 2.718490],
[572.500, 1.759923],
[577.500, 0.956800],
[582.500, 0.365645],
[587.500, 0.042982],
[590.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[130.000, 0.000000],
[135.000, 0.139445],
[140.000, 0.527629],
[145.000, 1.119326],
[150.000, 1.869313],
[155.000, 2.732363],
[160.000, 3.663250],
[165.000, 4.616751],
[170.000, 5.547638],
[175.000, 6.410688],
[180.000, 7.160675],
[185.000, 7.752372],
[190.000, 8.140556],
[195.000, 8.280001],
[325.000, 8.280001],
[330.000, 8.140556],
[335.000, 7.752372],
[340.000, 7.160675],
[345.000, 6.410688],
[350.000, 5.547638],
[355.000, 4.616751],
[360.000, 3.663250],
[365.000, 2.732363],
[370.000, 1.869313],
[375.000, 1.119326],
[380.000, 0.527629],
[385.000, 0.139445],
[390.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ -55, 0, 0 ]
tilt_deg = -90.0
[[geometry.cylinders]]
origin_mm = [ 55, 0, 0 ]
tilt_deg = -90.0
[[geometry.cylinders]]
origin_mm = [ -55, 0, 0 ]
tilt_deg = 90.0
[[geometry.cylinders]]
origin_mm = [ 55, 0, 0 ]
tilt_deg = 90.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -310, 307.5713, 0 ],
[ -110, 307.5713, 0 ]
]
# runners 1
[[geometry.pipes]]
index = 1
points_mm = [
[ -55, 286.8, -172.8334 ],
[ -55, 36.8, -172.8334 ]
]
# runners 2
[[geometry.pipes]]
index = 2
points_mm = [
[ 55, 286.8, -172.8334 ],
[ 55, 36.8, -172.8334 ]
]
# runners 3
[[geometry.pipes]]
index = 3
points_mm = [
[ -55, 286.8, 172.8334 ],
[ -55, 36.8, 172.8334 ]
]
# runners 4
[[geometry.pipes]]
index = 4
points_mm = [
[ 55, 286.8, 172.8334 ],
[ 55, 36.8, 172.8334 ]
]
# primaries 1
[[geometry.pipes]]
index = 5
points_mm = [
[ -55, -46, -172.8334 ],
[ -53.4569, -70.3798, -166.4422 ],
[ -49.0959, -88.6617, -155.8208 ],
[ -42.3196, -101.4701, -141.3612 ],
[ -33.5305, -109.4294, -123.455 ],
[ -23.1312, -113.164, -102.4941 ],
[ -11.5242, -113.2982, -78.8702 ],
[ 0.8879, -110.4563, -52.9752 ],
[ 13.7025, -105.2628, -25.2009 ],
[ 26.5171, -98.342, 4.0609 ],
[ 38.9292, -90.3182, 34.4185 ],
[ 50.5362, -81.8159, 65.48 ],
[ 60.9355, -73.4593, 96.8536 ],
[ 69.7246, -65.873, 128.1476 ],
[ 76.5009, -59.6811, 158.97 ],
[ 80.8619, -55.5081, 188.9292 ],
[ 82.405, -53.9784, 217.6334 ]
]
# primaries 2
[[geometry.pipes]]
index = 6
points_mm = [
[ 55, -46, -172.8334 ],
[ 55.448, -69.1222, -166.546 ],
[ 56.7141, -86.4707, -155.833 ],
[ 58.6814, -98.6364, -141.125 ],
[ 61.2331, -106.2101, -122.8528 ],
[ 64.2522, -109.7826, -101.4471 ],
[ 67.622, -109.9447, -77.3387 ],
[ 71.2255, -107.2873, -50.9582 ],
[ 74.9459, -102.4012, -22.7365 ],
[ 78.6663, -95.8772, 6.8957 ],
[ 82.2698, -88.3061, 37.5077 ],
[ 85.6395, -80.2789, 68.6687 ],
[ 88.6587, -72.3862, 99.948 ],
[ 91.2104, -65.219, 130.9149 ],
[ 93.1777, -59.3681, 161.1385 ],
[ 94.4438, -55.4243, 190.1883 ],
[ 94.8918, -53.9784, 217.6334 ]
]
# primaries 3
[[geometry.pipes]]
index = 7
points_mm = [
[ -55, -46, 172.8334 ],
[ -54.3082, -53.2985, 172.4701 ],
[ -52.3079, -58.8583, 173.7033 ],
[ -49.1117, -62.8543, 176.3161 ],
[ -44.8324, -65.4615, 180.0913 ],
[ -39.5825, -66.8548, 184.8119 ],
[ -33.4748, -67.2092, 190.2608 ],
[ -26.6219, -66.6995, 196.2209 ],
[ -19.1364, -65.5008, 202.4752 ],
[ -11.1311, -63.7879, 208.8066 ],
[ -2.7185, -61.7358, 214.9981 ],
[ 5.9887, -59.5194, 220.8326 ],
[ 14.8778, -57.3138, 226.093 ],
[ 23.8361, -55.2938, 230.5623 ],
[ 32.751, -53.6343, 234.0234 ],
[ 41.5099, -52.5103, 236.2592 ],
[ 50, -52.0968, 237.0527 ]
]
# primaries 4
[[geometry.pipes]]
index = 8
points_mm = [
[ 55, -46, 172.8334 ],
[ 55.2388, -48.968, 172.7016 ],
[ 55.9138, -51.2704, 173.2603 ],
[ 56.9625, -52.9727, 174.4452 ],
[ 58.3228, -54.1402, 176.1922 ],
[ 59.9323, -54.8385, 178.4371 ],
[ 61.7286, -55.1329, 181.1157 ],
[ 63.6496, -55.0888, 184.1638 ],
[ 65.6329, -54.7716, 187.5172 ],
[ 67.6162, -54.2468, 191.1118 ],
[ 69.5372, -53.5797, 194.8832 ],
[ 71.3336, -52.8358, 198.7675 ],
[ 72.943, -52.0805, 202.7003 ],
[ 74.3033, -51.3792, 206.6174 ],
[ 75.3521, -50.7972, 210.4548 ],
[ 76.027, -50.4001, 214.1482 ],
[ 76.2658, -50.2532, 217.6334 ]
]
# tailpipe
[[geometry.pipes]]
index = 9
points_mm = [
[ 150, -55, 267.6333 ],
[ 1649.9999, -55, 267.6333 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 307.5713, 0 ]
size_mm = [ 220, 41.5426, 437.6667 ]
[[geometry.volumes]]
id = "exhaust_collector"
center_mm = [ 100, -55, 267.6333 ]
size_mm = [ 100, 100, 100 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "f20"
name = "F20C-inspired / 2.0 high-rev four"
category = "Inline four / specials"
@@ -7,9 +7,11 @@ description = "Unequal primaries / small junctions / staged collectors / EFI"
order = 22
cycle = "four_stroke"
[simulation]
cells_per_meter = 20.0
[engine]
bank_angle_deg = 60.0
collector_volume_litres = 1.0
cylinders = 4
initial_rpm = 1400.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 30.0
minimum_substeps = 1
phase_degrees = [0.0, 180.0, 540.0, 360.0]
pipe_damping = 0.0
plenum_volume_litres = 4.0
starter_torque_nm = 49.9353
throttle_diameter_mm = 65.0
topology = "staged"
@@ -31,18 +32,12 @@ burn_duration_deg = 60.0
burn_start_deg = 5.0
compression = 10.0
energy_variation = 0.05
exhaust_close_deg = 390.0
exhaust_open_deg = 130.0
exhaust_valve_count = 1
exhaust_valve_diameter_mm = 27.84
exhaust_valve_lift_mm = 7.83
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 590.0
intake_open_deg = 340.0
intake_valve_count = 1
intake_valve_diameter_mm = 33.06
intake_valve_lift_mm = 8.7
misfire_probability = 0.0
rod_mm = 151.2
stroke_mm = 84.0
@@ -101,59 +96,356 @@ system = "injection"
[junctions]
h_pipe = true
junction_volume_cc = 50.0
[[pipes]]
id = "intake"
cells = 4
diameter_mm = 75.0
length_mm = 200.0
[[pipes]]
id = "runners"
count = 4
cells = 5
diameter_mm = 35.0
length_mm = 250.0
[[pipes]]
id = "primary_1"
cells = 11
diameter_mm = 38.0
length_mm = 520.0
[[pipes]]
id = "primary_2"
cells = 12
diameter_mm = 38.0
length_mm = 585.0
[[pipes]]
id = "primary_3"
cells = 13
diameter_mm = 38.0
length_mm = 650.0
[[pipes]]
id = "primary_4"
cells = 11
diameter_mm = 38.0
length_mm = 520.0
[[pipes]]
id = "tailpipe_9"
cells = 30
diameter_mm = 60.0
length_mm = 1500.0
[[pipes]]
id = "secondary_10"
cells = 9
diameter_mm = 48.0
length_mm = 420.0
[[pipes]]
id = "secondary_11"
cells = 11
diameter_mm = 48.0
length_mm = 540.0
[cam.intake]
points = [
[0.000, 0.000000],
[340.000, 0.000000],
[345.000, 0.158131],
[350.000, 0.596890],
[355.000, 1.262822],
[360.000, 2.102477],
[365.000, 3.062400],
[370.000, 4.089139],
[375.000, 5.129242],
[380.000, 6.129254],
[385.000, 7.035725],
[390.000, 7.795200],
[395.000, 8.354227],
[400.000, 8.659354],
[402.500, 8.700000],
[527.500, 8.700000],
[532.500, 8.541869],
[537.500, 8.103110],
[542.500, 7.437178],
[547.500, 6.597523],
[552.500, 5.637600],
[557.500, 4.610861],
[562.500, 3.570758],
[567.500, 2.570746],
[572.500, 1.664275],
[577.500, 0.904800],
[582.500, 0.345773],
[587.500, 0.040646],
[590.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[130.000, 0.000000],
[135.000, 0.131866],
[140.000, 0.498953],
[145.000, 1.058493],
[150.000, 1.767720],
[155.000, 2.583864],
[160.000, 3.464160],
[165.000, 4.365840],
[170.000, 5.246136],
[175.000, 6.062280],
[180.000, 6.771507],
[185.000, 7.331047],
[190.000, 7.698134],
[195.000, 7.830000],
[325.000, 7.830000],
[330.000, 7.698134],
[335.000, 7.331047],
[340.000, 6.771507],
[345.000, 6.062280],
[350.000, 5.246136],
[355.000, 4.365840],
[360.000, 3.464160],
[365.000, 2.583864],
[370.000, 1.767720],
[375.000, 1.058493],
[380.000, 0.498953],
[385.000, 0.131866],
[390.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ -157.5, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ -52.5, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ 52.5, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ 157.5, 0, 0 ]
tilt_deg = 0.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -410, 194.5333, -363.1 ],
[ -210, 194.5333, -363.1 ]
]
# runners 1
[[geometry.pipes]]
index = 1
points_mm = [
[ -157.5, 194.5333, -284.8 ],
[ -157.5, 194.5333, -34.8 ]
]
# runners 2
[[geometry.pipes]]
index = 2
points_mm = [
[ -52.5, 194.5333, -284.8 ],
[ -52.5, 194.5333, -34.8 ]
]
# runners 3
[[geometry.pipes]]
index = 3
points_mm = [
[ 52.5, 194.5333, -284.8 ],
[ 52.5, 194.5333, -34.8 ]
]
# runners 4
[[geometry.pipes]]
index = 4
points_mm = [
[ 157.5, 194.5333, -284.8 ],
[ 157.5, 194.5333, -34.8 ]
]
# primary_1
[[geometry.pipes]]
index = 5
points_mm = [
[ -157.5, 194.5333, 43.5 ],
[ -156.9599, 191.2299, 56.8381 ],
[ -155.4334, 185.7695, 68.1689 ],
[ -153.0615, 178.3497, 77.6508 ],
[ -149.9852, 169.1678, 85.4419 ],
[ -146.3452, 158.4214, 91.7006 ],
[ -142.2825, 146.3079, 96.5849 ],
[ -137.9379, 133.0247, 100.2531 ],
[ -133.4525, 118.7693, 102.8634 ],
[ -128.9671, 103.7391, 104.5741 ],
[ -124.6226, 88.1316, 105.5434 ],
[ -120.5599, 72.1442, 105.9294 ],
[ -116.9199, 55.9744, 105.8905 ],
[ -113.8435, 39.8197, 105.5848 ],
[ -111.4716, 23.8774, 105.1705 ],
[ -109.9452, 8.3451, 104.806 ],
[ -109.4051, -6.5798, 104.6493 ]
]
# primary_2
[[geometry.pipes]]
index = 6
points_mm = [
[ -52.5, 194.5333, 43.5 ],
[ -53.0401, 191.2299, 56.8381 ],
[ -54.5666, 185.7695, 68.1689 ],
[ -56.9385, 178.3497, 77.6508 ],
[ -60.0148, 169.1678, 85.4419 ],
[ -63.6548, 158.4214, 91.7006 ],
[ -67.7175, 146.3079, 96.5849 ],
[ -72.062, 133.0247, 100.2531 ],
[ -76.5475, 118.7693, 102.8634 ],
[ -81.0329, 103.7391, 104.5741 ],
[ -85.3774, 88.1316, 105.5434 ],
[ -89.4401, 72.1442, 105.9294 ],
[ -93.0801, 55.9744, 105.8905 ],
[ -96.1565, 39.8197, 105.5848 ],
[ -98.5284, 23.8774, 105.1705 ],
[ -100.0548, 8.3451, 104.806 ],
[ -100.5949, -6.5798, 104.6493 ]
]
# primary_3
[[geometry.pipes]]
index = 7
points_mm = [
[ 52.5, 194.5333, 43.5 ],
[ 53.0401, 191.1567, 58.187 ],
[ 54.5666, 185.761, 71.4839 ],
[ 56.9385, 178.5161, 83.4517 ],
[ 60.0148, 169.5922, 94.1515 ],
[ 63.6548, 159.1591, 103.6443 ],
[ 67.7175, 147.387, 111.9912 ],
[ 72.062, 134.4458, 119.2533 ],
[ 76.5475, 120.5056, 125.4917 ],
[ 81.0329, 105.7364, 130.7675 ],
[ 85.3774, 90.3081, 135.1416 ],
[ 89.4401, 74.3909, 138.6753 ],
[ 93.0801, 58.1546, 141.4295 ],
[ 96.1565, 41.7694, 143.4654 ],
[ 98.5284, 25.4053, 144.8439 ],
[ 100.0548, 9.2322, 145.6263 ],
[ 100.5949, -6.5798, 145.8735 ]
]
# primary_4
[[geometry.pipes]]
index = 8
points_mm = [
[ 157.5, 194.5333, 43.5 ],
[ 156.9599, 191.1567, 58.187 ],
[ 155.4334, 185.761, 71.4839 ],
[ 153.0615, 178.5161, 83.4517 ],
[ 149.9852, 169.5922, 94.1515 ],
[ 146.3452, 159.1591, 103.6443 ],
[ 142.2825, 147.387, 111.9912 ],
[ 137.9379, 134.4458, 119.2533 ],
[ 133.4525, 120.5056, 125.4917 ],
[ 128.9671, 105.7364, 130.7675 ],
[ 124.6226, 90.3081, 135.1416 ],
[ 120.5599, 74.3909, 138.6753 ],
[ 116.9199, 58.1546, 141.4295 ],
[ 113.8435, 41.7694, 143.4654 ],
[ 111.4716, 25.4053, 144.8439 ],
[ 109.9452, 9.2322, 145.6263 ],
[ 109.4051, -6.5798, 145.8735 ]
]
# tailpipe_9
[[geometry.pipes]]
index = 9
points_mm = [
[ 218.4202, -70, 170.25 ],
[ 1718.4201, -70, 170.25 ]
]
# secondary_10
[[geometry.pipes]]
index = 10
points_mm = [
[ -86.5798, -27.7177, 113.8736 ],
[ -68.2776, -28.1621, 114.4661 ],
[ -50.5923, -29.4178, 116.1404 ],
[ -33.4358, -31.3689, 118.7419 ],
[ -16.7199, -33.8997, 122.1162 ],
[ -0.3565, -36.8941, 126.1088 ],
[ 15.7426, -40.2362, 130.5649 ],
[ 31.6653, -43.8101, 135.3302 ],
[ 47.5, -47.5, 140.25 ],
[ 63.3347, -51.1899, 145.1698 ],
[ 79.2574, -54.7638, 149.9351 ],
[ 95.3565, -58.1059, 154.3912 ],
[ 111.7199, -61.1003, 158.3838 ],
[ 128.4358, -63.6311, 161.7581 ],
[ 145.5923, -65.5822, 164.3596 ],
[ 163.2777, -66.838, 166.0339 ],
[ 181.5798, -67.2823, 166.6264 ]
]
# secondary_11
[[geometry.pipes]]
index = 11
points_mm = [
[ 123.4202, -33.7253, 158.1585 ],
[ 127.6728, -34.0347, 158.2616 ],
[ 131.6784, -34.9091, 158.553 ],
[ 135.4722, -36.2677, 159.0059 ],
[ 139.0895, -38.0299, 159.5933 ],
[ 142.5657, -40.115, 160.2883 ],
[ 145.9359, -42.4421, 161.064 ],
[ 149.2356, -44.9307, 161.8936 ],
[ 152.5, -47.5, 162.75 ],
[ 155.7644, -50.0693, 163.6064 ],
[ 159.0641, -52.5579, 164.436 ],
[ 162.4343, -54.8851, 165.2117 ],
[ 165.9105, -56.9701, 165.9067 ],
[ 169.5278, -58.7323, 166.4941 ],
[ 173.3216, -60.0909, 166.947 ],
[ 177.3272, -60.9653, 167.2384 ],
[ 181.5798, -61.2747, 167.3415 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 194.5333, -363.1 ]
size_mm = [ 420, 60.8162, 156.6 ]
[[geometry.volumes]]
id = "junction_0"
center_mm = [ -105, -25, 110.25 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_1"
center_mm = [ 105, -25, 155.25 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_2"
center_mm = [ 200, -70, 170.25 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "fifty"
name = "50 cc / little screamer"
category = "Two-stroke / 50 cc"
@@ -7,16 +7,19 @@ description = "Concept single / transported carb fuel"
order = 15
cycle = "two_stroke"
[simulation]
cells_per_meter = 100.0
[two_stroke]
reed_pipe_diameter_mm = 40
transfer_pipe_diameter_mm = 32
initial_rpm = 3000.0
starter_torque_nm = 8.0
maximum_load_torque_nm = 10.0
crankshaft_inertia_kg_m2 = 0.004006997
bore_mm = 40.0
carburetor_diameter_mm = 19.0
crankcase_clearance_cc = 60.10495
exhaust = "racing"
exhaust_cell_length_mm = 15.0
exhaust_duration_deg = 190.0
exhaust_width_mm = 26.54048
friction_bar = 0.4
@@ -66,3 +69,69 @@ final_drive_ratio = 3.769231
mass_kg = 170
wheel_radius_mm = 331.3
gear_ratios = [2.384615, 1.933333, 1.588235, 1.352941, 1.176471, 1.055556]
[[geometry.cylinders]]
origin_mm = [ 0, 0, 0 ]
tilt_deg = 0.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -228.9558, 1.6102, 0 ],
[ -128.9558, 1.6102, 0 ]
]
# reed passage
[[geometry.pipes]]
index = 1
points_mm = [
[ -49.5857, 1.6102, 0 ],
[ -19.5857, 1.6102, 0 ]
]
# transfer passage
[[geometry.pipes]]
index = 2
points_mm = [
[ 19.5857, 21.1959, 0 ],
[ 23.8339, 23.9696, 0 ],
[ 27.519, 26.7484, 0 ],
[ 30.6412, 29.5324, 0 ],
[ 33.2004, 32.3215, 0 ],
[ 35.1967, 35.1159, 0 ],
[ 36.63, 37.9155, 0 ],
[ 37.5003, 40.7202, 0 ],
[ 37.8077, 43.5301, 0 ],
[ 37.5521, 46.3452, 0 ],
[ 36.7336, 49.1655, 0 ],
[ 35.352, 51.9909, 0 ],
[ 33.4076, 54.8215, 0 ],
[ 30.9001, 57.6574, 0 ],
[ 27.8297, 60.4984, 0 ],
[ 24.1963, 63.3446, 0 ],
[ 20, 66.1959, 0 ]
]
# exhaust chamber
[[geometry.pipes]]
index = 3
points_mm = [
[ 20, 75.9959, 0 ],
[ 130.844, 35.652, 0 ],
[ 186.9265, -84.6172, 0 ],
[ 106.5828, -199.3597, 0 ],
[ 10.7422, -199.3597, 0 ],
[ -134.7406, -146.4083, 0 ],
[ -311.6772, -146.4083, 0 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ -89.2708, 1.6102, 0 ]
size_mm = [ 79.3701, 79.3701, 79.3701 ]
[[geometry.volumes]]
id = "crankcase"
center_mm = [ 0, 1.6102, 0 ]
size_mm = [ 39.1715, 39.1715, 39.1715 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "five"
name = "Audi-inspired / 2.2 inline five"
category = "Inline five"
@@ -7,9 +7,11 @@ description = "Unequal primaries / small junctions / staged collectors / EFI"
order = 23
cycle = "four_stroke"
[simulation]
cells_per_meter = 20.0
[engine]
bank_angle_deg = 60.0
collector_volume_litres = 1.25
cylinders = 5
initial_rpm = 1400.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 30.0
minimum_substeps = 1
phase_degrees = [0.0, 576.0, 144.0, 432.0, 288.0]
pipe_damping = 0.0
plenum_volume_litres = 5.0
starter_torque_nm = 55.65237
throttle_diameter_mm = 65.0
topology = "staged"
@@ -31,18 +32,12 @@ burn_duration_deg = 60.0
burn_start_deg = 5.0
compression = 10.0
energy_variation = 0.05
exhaust_close_deg = 390.0
exhaust_open_deg = 130.0
exhaust_valve_count = 1
exhaust_valve_diameter_mm = 25.92
exhaust_valve_lift_mm = 7.29
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 590.0
intake_open_deg = 340.0
intake_valve_count = 1
intake_valve_diameter_mm = 30.78
intake_valve_lift_mm = 8.1
misfire_probability = 0.0
rod_mm = 155.52
stroke_mm = 86.4
@@ -101,65 +96,395 @@ system = "injection"
[junctions]
h_pipe = true
junction_volume_cc = 50.0
[[pipes]]
id = "intake"
cells = 4
diameter_mm = 75.0
length_mm = 200.0
[[pipes]]
id = "runners"
count = 5
cells = 5
diameter_mm = 35.0
length_mm = 250.0
[[pipes]]
id = "primary_1"
cells = 11
diameter_mm = 38.0
length_mm = 520.0
[[pipes]]
id = "primary_2"
cells = 12
diameter_mm = 38.0
length_mm = 585.0
[[pipes]]
id = "primary_3"
cells = 13
diameter_mm = 38.0
length_mm = 650.0
[[pipes]]
id = "primary_4"
cells = 11
diameter_mm = 38.0
length_mm = 520.0
[[pipes]]
id = "primary_5"
cells = 12
diameter_mm = 38.0
length_mm = 585.0
[[pipes]]
id = "tailpipe_11"
cells = 30
diameter_mm = 60.0
length_mm = 1500.0
[[pipes]]
id = "secondary_12"
cells = 9
diameter_mm = 48.0
length_mm = 420.0
[[pipes]]
id = "secondary_13"
cells = 11
diameter_mm = 48.0
length_mm = 540.0
[cam.intake]
points = [
[0.000, 0.000000],
[340.000, 0.000000],
[345.000, 0.147226],
[350.000, 0.555725],
[355.000, 1.175731],
[360.000, 1.957478],
[365.000, 2.851200],
[370.000, 3.807130],
[375.000, 4.775501],
[380.000, 5.706547],
[385.000, 6.550502],
[390.000, 7.257600],
[395.000, 7.778074],
[400.000, 8.062157],
[402.500, 8.100000],
[527.500, 8.100000],
[532.500, 7.952774],
[537.500, 7.544275],
[542.500, 6.924269],
[547.500, 6.142522],
[552.500, 5.248800],
[557.500, 4.292870],
[562.500, 3.324499],
[567.500, 2.393453],
[572.500, 1.549498],
[577.500, 0.842400],
[582.500, 0.321926],
[587.500, 0.037843],
[590.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[130.000, 0.000000],
[135.000, 0.122772],
[140.000, 0.464543],
[145.000, 0.985494],
[150.000, 1.645808],
[155.000, 2.405667],
[160.000, 3.225253],
[165.000, 4.064747],
[170.000, 4.884333],
[175.000, 5.644192],
[180.000, 6.304506],
[185.000, 6.825457],
[190.000, 7.167228],
[195.000, 7.290000],
[325.000, 7.290000],
[330.000, 7.167228],
[335.000, 6.825457],
[340.000, 6.304506],
[345.000, 5.644192],
[350.000, 4.884333],
[355.000, 4.064747],
[360.000, 3.225253],
[365.000, 2.405667],
[370.000, 1.645808],
[375.000, 0.985494],
[380.000, 0.464543],
[385.000, 0.122772],
[390.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ -198, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ -99, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ 0, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ 99, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ 198, 0, 0 ]
tilt_deg = 0.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -447.5, 200.32, -355.3 ],
[ -247.5, 200.32, -355.3 ]
]
# runners 1
[[geometry.pipes]]
index = 1
points_mm = [
[ -198, 200.32, -282.4 ],
[ -198, 200.32, -32.4 ]
]
# runners 2
[[geometry.pipes]]
index = 2
points_mm = [
[ -99, 200.32, -282.4 ],
[ -99, 200.32, -32.4 ]
]
# runners 3
[[geometry.pipes]]
index = 3
points_mm = [
[ 0, 200.32, -282.4 ],
[ 0, 200.32, -32.4 ]
]
# runners 4
[[geometry.pipes]]
index = 4
points_mm = [
[ 99, 200.32, -282.4 ],
[ 99, 200.32, -32.4 ]
]
# runners 5
[[geometry.pipes]]
index = 5
points_mm = [
[ 198, 200.32, -282.4 ],
[ 198, 200.32, -32.4 ]
]
# primary_1
[[geometry.pipes]]
index = 6
points_mm = [
[ -198, 200.32, 40.5 ],
[ -196.9791, 196.8636, 54.8898 ],
[ -194.0939, 191.2973, 66.9724 ],
[ -189.6107, 183.8025, 76.938 ],
[ -183.7958, 174.5606, 84.977 ],
[ -176.9157, 163.7531, 91.2796 ],
[ -169.2366, 151.5614, 96.0361 ],
[ -161.0248, 138.1668, 99.4366 ],
[ -152.5467, 123.7508, 101.6714 ],
[ -144.0686, 108.4947, 102.9308 ],
[ -135.8568, 92.5801, 103.405 ],
[ -128.1777, 76.1882, 103.2843 ],
[ -121.2975, 59.5006, 102.759 ],
[ -115.4827, 42.6985, 102.0192 ],
[ -110.9995, 25.9635, 101.2552 ],
[ -108.1143, 9.4769, 100.6573 ],
[ -107.0934, -6.5798, 100.4157 ]
]
# primary_2
[[geometry.pipes]]
index = 7
points_mm = [
[ -99, 200.32, 40.5 ],
[ -99, 196.9527, 53.8104 ],
[ -99, 191.3077, 65.0919 ],
[ -99, 183.5998, 74.5059 ],
[ -99, 174.0438, 82.2139 ],
[ -99, 162.8545, 88.3773 ],
[ -99, 150.2468, 93.1578 ],
[ -99, 136.4356, 96.7166 ],
[ -99, 121.6356, 99.2152 ],
[ -99, 106.0616, 100.8153 ],
[ -99, 89.9286, 101.678 ],
[ -99, 73.4513, 101.9651 ],
[ -99, 56.8446, 101.8379 ],
[ -99, 40.3233, 101.4579 ],
[ -99, 24.1023, 100.9866 ],
[ -99, 8.3963, 100.5854 ],
[ -99, -6.5798, 100.4157 ]
]
# primary_3
[[geometry.pipes]]
index = 8
points_mm = [
[ 0, 200.32, 40.5 ],
[ -1.0209, 196.8636, 54.8898 ],
[ -3.9061, 191.2973, 66.9724 ],
[ -8.3893, 183.8025, 76.938 ],
[ -14.2042, 174.5606, 84.977 ],
[ -21.0843, 163.7531, 91.2796 ],
[ -28.7634, 151.5614, 96.0361 ],
[ -36.9752, 138.1668, 99.4366 ],
[ -45.4533, 123.7508, 101.6714 ],
[ -53.9314, 108.4947, 102.9308 ],
[ -62.1432, 92.5801, 103.405 ],
[ -69.8223, 76.1882, 103.2843 ],
[ -76.7025, 59.5006, 102.759 ],
[ -82.5173, 42.6985, 102.0192 ],
[ -87.0005, 25.9635, 101.2552 ],
[ -89.8857, 9.4769, 100.6573 ],
[ -90.9066, -6.5798, 100.4157 ]
]
# primary_4
[[geometry.pipes]]
index = 9
points_mm = [
[ 99, 200.32, 40.5 ],
[ 99.5105, 196.8588, 55.4115 ],
[ 100.9531, 191.2967, 68.8484 ],
[ 103.1947, 183.8133, 80.8814 ],
[ 106.1021, 174.5883, 91.5813 ],
[ 109.5421, 163.8012, 101.0186 ],
[ 113.3817, 151.6317, 109.2643 ],
[ 117.4876, 138.2594, 116.3889 ],
[ 121.7267, 123.8639, 122.4633 ],
[ 125.9657, 108.6248, 127.5582 ],
[ 130.0716, 92.7219, 131.7443 ],
[ 133.9112, 76.3346, 135.0923 ],
[ 137.3512, 59.6426, 137.673 ],
[ 140.2586, 42.8255, 139.5571 ],
[ 142.5003, 26.063, 140.8153 ],
[ 143.9429, 9.5347, 141.5183 ],
[ 144.4533, -6.5798, 141.737 ]
]
# primary_5
[[geometry.pipes]]
index = 10
points_mm = [
[ 198, 200.32, 40.5 ],
[ 197.4895, 196.8588, 55.4115 ],
[ 196.0469, 191.2967, 68.8484 ],
[ 193.8053, 183.8133, 80.8814 ],
[ 190.8979, 174.5883, 91.5813 ],
[ 187.4578, 163.8012, 101.0186 ],
[ 183.6183, 151.6317, 109.2643 ],
[ 179.5124, 138.2594, 116.3889 ],
[ 175.2733, 123.8639, 122.4633 ],
[ 171.0343, 108.6248, 127.5582 ],
[ 166.9284, 92.7219, 131.7443 ],
[ 163.0888, 76.3346, 135.0923 ],
[ 159.6487, 59.6426, 137.673 ],
[ 156.7414, 42.8255, 139.5571 ],
[ 154.4998, 26.063, 140.8153 ],
[ 153.0571, 9.5347, 141.5183 ],
[ 152.5467, -6.5798, 141.737 ]
]
# tailpipe_11
[[geometry.pipes]]
index = 11
points_mm = [
[ 218.4202, -70, 165.75 ],
[ 1718.4201, -70, 165.75 ]
]
# secondary_12
[[geometry.pipes]]
index = 12
points_mm = [
[ -80.5798, -27.7723, 109.4464 ],
[ -62.6699, -28.2154, 110.0372 ],
[ -45.3699, -29.4676, 111.7068 ],
[ -28.5928, -31.4134, 114.3012 ],
[ -12.2514, -33.9372, 117.6662 ],
[ 3.7414, -36.9233, 121.6478 ],
[ 19.4729, -40.2562, 126.0916 ],
[ 35.03, -43.8203, 130.8438 ],
[ 50.5, -47.5, 135.75 ],
[ 65.97, -51.1797, 140.6562 ],
[ 81.5271, -54.7438, 145.4084 ],
[ 97.2586, -58.0767, 149.8522 ],
[ 113.2514, -61.0628, 153.8337 ],
[ 129.5928, -63.5866, 157.1988 ],
[ 146.3699, -65.5324, 159.7932 ],
[ 163.6699, -66.7846, 161.4628 ],
[ 181.5798, -67.2277, 162.0536 ]
]
# secondary_13
[[geometry.pipes]]
index = 13
points_mm = [
[ 166.9202, -41.0953, 156.1151 ],
[ 168.1884, -41.2391, 156.1631 ],
[ 169.3158, -41.6457, 156.2986 ],
[ 170.3225, -42.2774, 156.5091 ],
[ 171.2287, -43.0968, 156.7823 ],
[ 172.0544, -44.0662, 157.1054 ],
[ 172.8198, -45.1483, 157.4661 ],
[ 173.5449, -46.3054, 157.8518 ],
[ 174.25, -47.5, 158.25 ],
[ 174.955, -48.6946, 158.6482 ],
[ 175.6802, -49.8517, 159.0339 ],
[ 176.4456, -50.9338, 159.3946 ],
[ 177.2713, -51.9032, 159.7177 ],
[ 178.1775, -52.7226, 159.9909 ],
[ 179.1842, -53.3543, 160.2014 ],
[ 180.3117, -53.7609, 160.337 ],
[ 181.5798, -53.9047, 160.3849 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 200.32, -355.3 ]
size_mm = [ 495, 69.2799, 145.8 ]
[[geometry.volumes]]
id = "junction_0"
center_mm = [ -99, -25, 105.75 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_1"
center_mm = [ 148.5, -25, 150.75 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_2"
center_mm = [ 200, -70, 165.75 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "fivehundred"
name = "500 cc / big single"
category = "Two-stroke / 500 cc"
@@ -7,16 +7,19 @@ description = "Concept single / transported carb fuel"
order = 16
cycle = "two_stroke"
[simulation]
cells_per_meter = 100.0
[two_stroke]
reed_pipe_diameter_mm = 40
transfer_pipe_diameter_mm = 32
initial_rpm = 3000.0
starter_torque_nm = 8.0
maximum_load_torque_nm = 10.0
crankshaft_inertia_kg_m2 = 0.04002316
bore_mm = 86.0
carburetor_diameter_mm = 41.0
crankcase_clearance_cc = 600.3475
exhaust = "racing"
exhaust_cell_length_mm = 15.0
exhaust_duration_deg = 190.0
exhaust_width_mm = 57.15746
friction_bar = 0.4
@@ -66,3 +69,69 @@ final_drive_ratio = 3.769231
mass_kg = 170
wheel_radius_mm = 331.3
gear_ratios = [2.384615, 1.933333, 1.588235, 1.352941, 1.176471, 1.055556]
[[geometry.cylinders]]
origin_mm = [ 0, 0, 0 ]
tilt_deg = 0.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -251.5498, 49.468, 0 ],
[ -151.5498, 49.468, 0 ]
]
# reed passage
[[geometry.pipes]]
index = 1
points_mm = [
[ -72.1797, 49.468, 0 ],
[ -42.1797, 49.468, 0 ]
]
# transfer passage
[[geometry.pipes]]
index = 2
points_mm = [
[ 42.1797, 91.6478, 0 ],
[ 46.452, 94.3834, 0 ],
[ 50.1614, 97.1292, 0 ],
[ 53.3081, 99.8853, 0 ],
[ 55.8919, 102.6516, 0 ],
[ 57.913, 105.4282, 0 ],
[ 59.3712, 108.215, 0 ],
[ 60.2667, 111.0122, 0 ],
[ 60.5993, 113.8195, 0 ],
[ 60.3692, 116.6372, 0 ],
[ 59.5763, 119.465, 0 ],
[ 58.2206, 122.3032, 0 ],
[ 56.302, 125.1516, 0 ],
[ 53.8207, 128.0103, 0 ],
[ 50.7766, 130.8792, 0 ],
[ 47.1697, 133.7584, 0 ],
[ 43, 136.6478, 0 ]
]
# exhaust chamber
[[geometry.pipes]]
index = 3
points_mm = [
[ 43, 192.6478, 0 ],
[ 281.7131, 105.7633, 0 ],
[ 402.492, -153.248, 0 ],
[ 229.4644, -400.3569, 0 ],
[ 23.0624, -400.3569, 0 ],
[ -290.2485, -286.3211, 0 ],
[ -671.2983, -286.321, 0 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ -111.8648, 49.468, 0 ]
size_mm = [ 79.3701, 79.3701, 79.3701 ]
[[geometry.volumes]]
id = "crankcase"
center_mm = [ 0, 49.468, 0 ]
size_mm = [ 84.3595, 84.3595, 84.3595 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "flat6"
name = "911-inspired / 3.0 flat six"
category = "Boxer / flat six"
@@ -7,9 +7,11 @@ description = "Estimated concept calibration / EFI"
order = 28
cycle = "four_stroke"
[simulation]
cells_per_meter = 20.0
[engine]
bank_angle_deg = 180.0
collector_volume_litres = 1.5
cylinders = 6
initial_rpm = 1400.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 30.0
minimum_substeps = 1
phase_degrees = [0.0, 600.0, 480.0, 360.0, 240.0, 120.0]
pipe_damping = 1.0
plenum_volume_litres = 6.0
starter_torque_nm = 74.85159
throttle_diameter_mm = 65.0
topology = "collector"
@@ -31,18 +32,12 @@ burn_duration_deg = 60.0
burn_start_deg = 5.0
compression = 10.0
energy_variation = 0.05
exhaust_close_deg = 390.0
exhaust_open_deg = 130.0
exhaust_valve_count = 1
exhaust_valve_diameter_mm = 30.4
exhaust_valve_lift_mm = 8.55
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 590.0
intake_open_deg = 340.0
intake_valve_count = 1
intake_valve_diameter_mm = 36.1
intake_valve_lift_mm = 9.5
misfire_probability = 0.0
rod_mm = 126.72
stroke_mm = 70.4
@@ -101,26 +96,348 @@ system = "injection"
[[pipes]]
id = "intake"
cells = 4
diameter_mm = 75.0
length_mm = 200.0
[[pipes]]
id = "runners"
count = 6
cells = 5
diameter_mm = 35.0
length_mm = 250.0
[[pipes]]
id = "primaries"
count = 6
cells = 13
diameter_mm = 38.0
length_mm = 650.0
[[pipes]]
id = "tailpipe"
cells = 30
diameter_mm = 60.0
length_mm = 1500.0
[cam.intake]
points = [
[0.000, 0.000000],
[340.000, 0.000000],
[345.000, 0.172672],
[350.000, 0.651776],
[355.000, 1.378944],
[360.000, 2.295808],
[365.000, 3.344000],
[370.000, 4.465152],
[375.000, 5.600896],
[380.000, 6.692864],
[385.000, 7.682688],
[390.000, 8.512000],
[395.000, 9.122432],
[400.000, 9.455616],
[402.500, 9.500000],
[527.500, 9.500000],
[532.500, 9.327328],
[537.500, 8.848224],
[542.500, 8.121056],
[547.500, 7.204192],
[552.500, 6.156000],
[557.500, 5.034848],
[562.500, 3.899104],
[567.500, 2.807136],
[572.500, 1.817312],
[577.500, 0.988000],
[582.500, 0.377568],
[587.500, 0.044384],
[590.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[130.000, 0.000000],
[135.000, 0.143992],
[140.000, 0.544834],
[145.000, 1.155826],
[150.000, 1.930269],
[155.000, 2.821461],
[160.000, 3.782704],
[165.000, 4.767296],
[170.000, 5.728539],
[175.000, 6.619731],
[180.000, 7.394174],
[185.000, 8.005166],
[190.000, 8.406008],
[195.000, 8.550000],
[325.000, 8.550000],
[330.000, 8.406008],
[335.000, 8.005166],
[340.000, 7.394174],
[345.000, 6.619731],
[350.000, 5.728539],
[355.000, 4.767296],
[360.000, 3.782704],
[365.000, 2.821461],
[370.000, 1.930269],
[375.000, 1.155826],
[380.000, 0.544834],
[385.000, 0.143992],
[390.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ -113, 0, 0 ]
tilt_deg = -90.0
[[geometry.cylinders]]
origin_mm = [ 0, 0, 0 ]
tilt_deg = -90.0
[[geometry.cylinders]]
origin_mm = [ 113, 0, 0 ]
tilt_deg = -90.0
[[geometry.cylinders]]
origin_mm = [ -113, 0, 0 ]
tilt_deg = 90.0
[[geometry.cylinders]]
origin_mm = [ 0, 0, 0 ]
tilt_deg = 90.0
[[geometry.cylinders]]
origin_mm = [ 113, 0, 0 ]
tilt_deg = 90.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -369.5, 309.1467, 0 ],
[ -169.5, 309.1467, 0 ]
]
# runners 1
[[geometry.pipes]]
index = 1
points_mm = [
[ -113, 288, -161.7422 ],
[ -113, 38, -161.7422 ]
]
# runners 2
[[geometry.pipes]]
index = 2
points_mm = [
[ 0, 288, -161.7422 ],
[ 0, 38, -161.7422 ]
]
# runners 3
[[geometry.pipes]]
index = 3
points_mm = [
[ 113, 288, -161.7422 ],
[ 113, 38, -161.7422 ]
]
# runners 4
[[geometry.pipes]]
index = 4
points_mm = [
[ -113, 288, 161.7422 ],
[ -113, 38, 161.7422 ]
]
# runners 5
[[geometry.pipes]]
index = 5
points_mm = [
[ 0, 288, 161.7422 ],
[ 0, 38, 161.7422 ]
]
# runners 6
[[geometry.pipes]]
index = 6
points_mm = [
[ 113, 288, 161.7422 ],
[ 113, 38, 161.7422 ]
]
# primaries 1
[[geometry.pipes]]
index = 7
points_mm = [
[ -113, -47.5, -161.7422 ],
[ -110.9337, -71.4523, -155.6934 ],
[ -105.0942, -89.3819, -145.9139 ],
[ -96.0205, -101.9065, -132.7278 ],
[ -84.2516, -109.644, -116.4591 ],
[ -70.3266, -113.2121, -97.4318 ],
[ -54.7846, -113.2287, -75.9701 ],
[ -38.1644, -110.3116, -52.3978 ],
[ -21.0052, -105.0785, -27.0392 ],
[ -3.8461, -98.1474, -0.2181 ],
[ 12.7741, -90.136, 27.7413 ],
[ 28.3162, -81.6621, 56.515 ],
[ 42.2412, -73.3435, 85.779 ],
[ 54.01, -65.798, 115.2091 ],
[ 63.0837, -59.6435, 144.4815 ],
[ 68.9232, -55.4977, 173.2719 ],
[ 70.9895, -53.9785, 201.2565 ]
]
# primaries 2
[[geometry.pipes]]
index = 8
points_mm = [
[ 0, -47.5, -161.7422 ],
[ 0.9701, -69.4678, -155.8573 ],
[ 3.7116, -85.9244, -145.9331 ],
[ 7.9716, -97.4347, -132.3551 ],
[ 13.4969, -104.5636, -115.5088 ],
[ 20.0344, -107.8759, -95.7796 ],
[ 27.3312, -107.9366, -73.5531 ],
[ 35.1341, -105.3106, -49.2148 ],
[ 43.19, -100.5627, -23.1501 ],
[ 51.246, -94.2577, 4.2554 ],
[ 59.0489, -86.9607, 32.6163 ],
[ 66.3456, -79.2365, 61.547 ],
[ 72.8832, -71.65, 90.6621 ],
[ 78.4085, -64.7661, 119.5761 ],
[ 82.6684, -59.1496, 147.9035 ],
[ 85.41, -55.3654, 175.2588 ],
[ 86.3801, -53.9785, 201.2565 ]
]
# primaries 3
[[geometry.pipes]]
index = 9
points_mm = [
[ 113, -47.5, -161.7422 ],
[ 112.8739, -68.8834, -155.9056 ],
[ 112.5175, -84.9063, -145.9387 ],
[ 111.9637, -96.1179, -132.2453 ],
[ 111.2454, -103.0675, -115.2289 ],
[ 110.3955, -106.3045, -95.2931 ],
[ 109.4469, -106.3782, -72.8414 ],
[ 108.4326, -103.8379, -48.2775 ],
[ 107.3853, -99.2328, -22.0049 ],
[ 106.338, -93.1123, 5.5727 ],
[ 105.3236, -86.0257, 34.0518 ],
[ 104.3751, -78.5223, 63.0288 ],
[ 103.5252, -71.1513, 92.1001 ],
[ 102.8069, -64.4622, 120.8621 ],
[ 102.2531, -59.0041, 148.9112 ],
[ 101.8967, -55.3265, 175.8439 ],
[ 101.7706, -53.9785, 201.2565 ]
]
# primaries 4
[[geometry.pipes]]
index = 10
points_mm = [
[ -113, -47.5, 161.7422 ],
[ -111.9277, -57.604, 161.0304 ],
[ -108.8344, -65.2317, 162.1579 ],
[ -103.9057, -70.635, 164.8772 ],
[ -97.3272, -74.0655, 168.941 ],
[ -89.2844, -75.775, 174.1016 ],
[ -79.9628, -76.0151, 180.1117 ],
[ -69.548, -75.0376, 186.7238 ],
[ -58.2255, -73.0943, 193.6904 ],
[ -46.1809, -70.4369, 200.7643 ],
[ -33.5998, -67.3171, 207.6977 ],
[ -20.6676, -63.9866, 214.2434 ],
[ -7.57, -60.6972, 220.1539 ],
[ 5.5076, -57.7006, 225.1816 ],
[ 18.3795, -55.2484, 229.0793 ],
[ 30.8603, -53.5926, 231.5993 ],
[ 42.7643, -52.9847, 232.4943 ]
]
# primaries 5
[[geometry.pipes]]
index = 11
points_mm = [
[ 0, -47.5, 161.7422 ],
[ 0.2446, -51.0321, 161.6825 ],
[ 0.9577, -53.7288, 162.6064 ],
[ 2.1082, -55.6739, 164.3788 ],
[ 3.6652, -56.9512, 166.8645 ],
[ 5.5977, -57.6445, 169.9283 ],
[ 7.8745, -57.8378, 173.4349 ],
[ 10.4648, -57.6147, 177.2493 ],
[ 13.3376, -57.0593, 181.2361 ],
[ 16.4617, -56.2552, 185.2602 ],
[ 19.8062, -55.2863, 189.1865 ],
[ 23.34, -54.2365, 192.8796 ],
[ 27.0322, -53.1895, 196.2044 ],
[ 30.8518, -52.2293, 199.0257 ],
[ 34.7676, -51.4397, 201.2084 ],
[ 38.7488, -50.9044, 202.617 ],
[ 42.7643, -50.7073, 203.1167 ]
]
# primaries 6
[[geometry.pipes]]
index = 12
points_mm = [
[ 113, -47.5, 161.7422 ],
[ 112.9404, -49.8804, 161.6759 ],
[ 112.7719, -51.7189, 162.2366 ],
[ 112.51, -53.0691, 163.3623 ],
[ 112.1704, -53.9844, 164.9911 ],
[ 111.7686, -54.5186, 167.0612 ],
[ 111.3201, -54.7253, 169.5108 ],
[ 110.8405, -54.6579, 172.2777 ],
[ 110.3453, -54.3701, 175.3004 ],
[ 109.8501, -53.9154, 178.5167 ],
[ 109.3705, -53.3476, 181.8648 ],
[ 108.922, -52.72, 185.2829 ],
[ 108.5202, -52.0865, 188.7091 ],
[ 108.1806, -51.5004, 192.0814 ],
[ 107.9187, -51.0154, 195.338 ],
[ 107.7502, -50.6851, 198.417 ],
[ 107.6906, -50.5631, 201.2565 ]
]
# tailpipe
[[geometry.pipes]]
index = 13
points_mm = [
[ 157.2357, -55, 258.4922 ],
[ 1657.2357, -55, 258.4922 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 309.1467, 0 ]
size_mm = [ 339, 42.2934, 418.4844 ]
[[geometry.volumes]]
id = "exhaust_collector"
center_mm = [ 100, -55, 258.4922 ]
size_mm = [ 114.4714, 114.4714, 114.4714 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "ltwin"
name = "916-inspired / 90-degree V twin"
category = "Twins / V"
@@ -7,9 +7,11 @@ description = "Estimated concept calibration / EFI"
order = 20
cycle = "four_stroke"
[simulation]
cells_per_meter = 20.0
[engine]
bank_angle_deg = 90.0
collector_volume_litres = 0.5
cylinders = 2
initial_rpm = 1400.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 30.0
minimum_substeps = 1
phase_degrees = [0.0, 450.0]
pipe_damping = 1.0
plenum_volume_litres = 2.0
starter_torque_nm = 22.90127
throttle_diameter_mm = 65.0
topology = "collector"
@@ -31,18 +32,12 @@ burn_duration_deg = 60.0
burn_start_deg = 5.0
compression = 10.0
energy_variation = 0.05
exhaust_close_deg = 390.0
exhaust_open_deg = 130.0
exhaust_valve_count = 1
exhaust_valve_diameter_mm = 30.08
exhaust_valve_lift_mm = 8.46
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 590.0
intake_open_deg = 340.0
intake_valve_count = 1
intake_valve_diameter_mm = 35.72
intake_valve_lift_mm = 9.4
misfire_probability = 0.0
rod_mm = 118.8
stroke_mm = 66.0
@@ -101,26 +96,208 @@ system = "injection"
[[pipes]]
id = "intake"
cells = 4
diameter_mm = 75.0
length_mm = 200.0
[[pipes]]
id = "runners"
count = 2
cells = 5
diameter_mm = 35.0
length_mm = 250.0
[[pipes]]
id = "primaries"
count = 2
cells = 13
diameter_mm = 38.0
length_mm = 650.0
[[pipes]]
id = "tailpipe"
cells = 30
diameter_mm = 60.0
length_mm = 1500.0
[cam.intake]
points = [
[0.000, 0.000000],
[340.000, 0.000000],
[345.000, 0.170854],
[350.000, 0.644915],
[355.000, 1.364429],
[360.000, 2.271642],
[365.000, 3.308800],
[370.000, 4.418150],
[375.000, 5.541939],
[380.000, 6.622413],
[385.000, 7.601818],
[390.000, 8.422400],
[395.000, 9.026406],
[400.000, 9.356083],
[402.500, 9.400000],
[527.500, 9.400000],
[532.500, 9.229146],
[537.500, 8.755085],
[542.500, 8.035571],
[547.500, 7.128358],
[552.500, 6.091200],
[557.500, 4.981850],
[562.500, 3.858061],
[567.500, 2.777587],
[572.500, 1.798182],
[577.500, 0.977600],
[582.500, 0.373594],
[587.500, 0.043917],
[590.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[130.000, 0.000000],
[135.000, 0.142476],
[140.000, 0.539099],
[145.000, 1.143660],
[150.000, 1.909950],
[155.000, 2.791761],
[160.000, 3.742886],
[165.000, 4.717114],
[170.000, 5.668239],
[175.000, 6.550050],
[180.000, 7.316340],
[185.000, 7.920901],
[190.000, 8.317524],
[195.000, 8.460000],
[325.000, 8.460000],
[330.000, 8.317524],
[335.000, 7.920901],
[340.000, 7.316340],
[345.000, 6.550050],
[350.000, 5.668239],
[355.000, 4.717114],
[360.000, 3.742886],
[365.000, 2.791761],
[370.000, 1.909950],
[375.000, 1.143660],
[380.000, 0.539099],
[385.000, 0.142476],
[390.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ 0, 0, 0 ]
tilt_deg = -45.0
[[geometry.cylinders]]
origin_mm = [ 0, 0, 0 ]
tilt_deg = 45.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -256, 418.5291, 0 ],
[ -56, 418.5291, 0 ]
]
# runners 1
[[geometry.pipes]]
index = 1
points_mm = [
[ 0, 383.4546, -80.2802 ],
[ 0, 133.4546, -80.2802 ]
]
# runners 2
[[geometry.pipes]]
index = 2
points_mm = [
[ 0, 383.4546, 80.2802 ],
[ 0, 133.4546, 80.2802 ]
]
# primaries 1
[[geometry.pipes]]
index = 3
points_mm = [
[ 0, 73.6334, -140.1014 ],
[ 0.9822, 57.5171, -149.2024 ],
[ 3.7581, 42.9223, -151.9312 ],
[ 8.0713, 29.7825, -148.899 ],
[ 13.6657, 18.0314, -140.717 ],
[ 20.285, 7.6023, -127.9962 ],
[ 27.6731, -1.5711, -111.348 ],
[ 35.5736, -9.5553, -91.3833 ],
[ 43.7303, -16.4169, -68.7134 ],
[ 51.887, -22.2222, -43.9495 ],
[ 59.7875, -27.0379, -17.7026 ],
[ 67.1755, -30.9303, 9.416 ],
[ 73.7948, -33.9659, 36.7953 ],
[ 79.3892, -36.2112, 63.8239 ],
[ 83.7025, -37.7327, 89.8909 ],
[ 86.4783, -38.5968, 114.385 ],
[ 87.4606, -38.8701, 136.6951 ]
]
# primaries 2
[[geometry.pipes]]
index = 4
points_mm = [
[ 0, 73.6334, 140.1014 ],
[ 0.7766, 67.5842, 144.4533 ],
[ 2.9712, 61.9218, 148.2705 ],
[ 6.3814, 56.5771, 151.588 ],
[ 10.8045, 51.4813, 154.4409 ],
[ 16.0379, 46.5657, 156.8641 ],
[ 21.8791, 41.7612, 158.8927 ],
[ 28.1254, 36.9992, 160.5619 ],
[ 34.5744, 32.2106, 161.9065 ],
[ 41.0233, 27.3267, 162.9617 ],
[ 47.2696, 22.2785, 163.7626 ],
[ 53.1108, 16.9973, 164.344 ],
[ 58.3442, 11.4142, 164.7412 ],
[ 62.7673, 5.4602, 164.9891 ],
[ 66.1775, -0.9334, 165.1228 ],
[ 68.3721, -7.8355, 165.1773 ],
[ 69.1487, -15.315, 165.1877 ]
]
# tailpipe
[[geometry.pipes]]
index = 5
points_mm = [
[ 139.685, -55, 176.3802 ],
[ 1639.685, -55, 176.3802 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 418.5291, 0 ]
size_mm = [ 112, 70.1489, 254.5604 ]
[[geometry.volumes]]
id = "exhaust_collector"
center_mm = [ 100, -55, 176.3802 ]
size_mm = [ 79.3701, 79.3701, 79.3701 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "rb26"
name = "RB26-inspired / 2.6 inline six NA"
category = "Inline six"
@@ -7,9 +7,11 @@ description = "Unequal primaries / small junctions / staged collectors / EFI"
order = 24
cycle = "four_stroke"
[simulation]
cells_per_meter = 20.0
[engine]
bank_angle_deg = 60.0
collector_volume_litres = 1.5
cylinders = 6
initial_rpm = 1400.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 30.0
minimum_substeps = 1
phase_degrees = [0.0, 240.0, 480.0, 120.0, 600.0, 360.0]
pipe_damping = 0.0
plenum_volume_litres = 6.0
starter_torque_nm = 64.21635
throttle_diameter_mm = 65.0
topology = "staged"
@@ -31,18 +32,12 @@ burn_duration_deg = 60.0
burn_start_deg = 5.0
compression = 10.0
energy_variation = 0.05
exhaust_close_deg = 390.0
exhaust_open_deg = 130.0
exhaust_valve_count = 1
exhaust_valve_diameter_mm = 27.52
exhaust_valve_lift_mm = 7.740001
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 590.0
intake_open_deg = 340.0
intake_valve_count = 1
intake_valve_diameter_mm = 32.68
intake_valve_lift_mm = 8.6
misfire_probability = 0.0
rod_mm = 132.66
stroke_mm = 73.7
@@ -101,71 +96,434 @@ system = "injection"
[junctions]
h_pipe = true
junction_volume_cc = 50.0
[[pipes]]
id = "intake"
cells = 4
diameter_mm = 75.0
length_mm = 200.0
[[pipes]]
id = "runners"
count = 6
cells = 5
diameter_mm = 35.0
length_mm = 250.0
[[pipes]]
id = "primary_1"
cells = 11
diameter_mm = 38.0
length_mm = 520.0
[[pipes]]
id = "primary_2"
cells = 12
diameter_mm = 38.0
length_mm = 585.0
[[pipes]]
id = "primary_3"
cells = 13
diameter_mm = 38.0
length_mm = 650.0
[[pipes]]
id = "primary_4"
cells = 11
diameter_mm = 38.0
length_mm = 520.0
[[pipes]]
id = "primary_5"
cells = 12
diameter_mm = 38.0
length_mm = 585.0
[[pipes]]
id = "primary_6"
cells = 13
diameter_mm = 38.0
length_mm = 650.0
[[pipes]]
id = "tailpipe_13"
cells = 30
diameter_mm = 60.0
length_mm = 1500.0
[[pipes]]
id = "secondary_14"
cells = 9
diameter_mm = 48.0
length_mm = 420.0
[[pipes]]
id = "secondary_15"
cells = 11
diameter_mm = 48.0
length_mm = 540.0
[cam.intake]
points = [
[0.000, 0.000000],
[340.000, 0.000000],
[345.000, 0.156314],
[350.000, 0.590029],
[355.000, 1.248307],
[360.000, 2.078310],
[365.000, 3.027200],
[370.000, 4.042138],
[375.000, 5.070285],
[380.000, 6.058803],
[385.000, 6.954854],
[390.000, 7.705600],
[395.000, 8.258202],
[400.000, 8.559821],
[402.500, 8.600000],
[527.500, 8.600000],
[532.500, 8.443686],
[537.500, 8.009971],
[542.500, 7.351693],
[547.500, 6.521690],
[552.500, 5.572800],
[557.500, 4.557862],
[562.500, 3.529715],
[567.500, 2.541197],
[572.500, 1.645146],
[577.500, 0.894400],
[582.500, 0.341798],
[587.500, 0.040179],
[590.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[130.000, 0.000000],
[135.000, 0.130350],
[140.000, 0.493218],
[145.000, 1.046327],
[150.000, 1.747401],
[155.000, 2.554165],
[160.000, 3.424343],
[165.000, 4.315658],
[170.000, 5.185836],
[175.000, 5.992600],
[180.000, 6.693674],
[185.000, 7.246783],
[190.000, 7.609651],
[195.000, 7.740001],
[325.000, 7.740001],
[330.000, 7.609651],
[335.000, 7.246783],
[340.000, 6.693674],
[345.000, 5.992600],
[350.000, 5.185836],
[355.000, 4.315658],
[360.000, 3.424343],
[365.000, 2.554165],
[370.000, 1.747401],
[375.000, 1.046327],
[380.000, 0.493218],
[385.000, 0.130350],
[390.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ -260, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ -156, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ -52, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ 52, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ 156, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ 260, 0, 0 ]
tilt_deg = 0.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -512, 169.6989, -361.8 ],
[ -312, 169.6989, -361.8 ]
]
# runners 1
[[geometry.pipes]]
index = 1
points_mm = [
[ -260, 169.6989, -284.4 ],
[ -260, 169.6989, -34.4 ]
]
# runners 2
[[geometry.pipes]]
index = 2
points_mm = [
[ -156, 169.6989, -284.4 ],
[ -156, 169.6989, -34.4 ]
]
# runners 3
[[geometry.pipes]]
index = 3
points_mm = [
[ -52, 169.6989, -284.4 ],
[ -52, 169.6989, -34.4 ]
]
# runners 4
[[geometry.pipes]]
index = 4
points_mm = [
[ 52, 169.6989, -284.4 ],
[ 52, 169.6989, -34.4 ]
]
# runners 5
[[geometry.pipes]]
index = 5
points_mm = [
[ 156, 169.6989, -284.4 ],
[ 156, 169.6989, -34.4 ]
]
# runners 6
[[geometry.pipes]]
index = 6
points_mm = [
[ 260, 169.6989, -284.4 ],
[ 260, 169.6989, -34.4 ]
]
# primary_1
[[geometry.pipes]]
index = 7
points_mm = [
[ -260, 169.6989, 43 ],
[ -258.9425, 166.7079, 55.9219 ],
[ -255.954, 162.0061, 66.9219 ],
[ -251.3103, 155.731, 76.1501 ],
[ -245.2874, 148.0196, 83.7568 ],
[ -238.161, 139.0093, 89.8919 ],
[ -230.207, 128.8373, 94.7057 ],
[ -221.7012, 117.6409, 98.3485 ],
[ -212.9196, 105.5573, 100.9702 ],
[ -204.138, 92.7238, 102.7211 ],
[ -195.6323, 79.2776, 103.7514 ],
[ -187.6783, 65.3559, 104.2112 ],
[ -180.5519, 51.0961, 104.2507 ],
[ -174.5289, 36.6354, 104.02 ],
[ -169.8852, 22.111, 103.6693 ],
[ -166.8968, 7.6602, 103.3488 ],
[ -165.8393, -6.5798, 103.2085 ]
]
# primary_2
[[geometry.pipes]]
index = 8
points_mm = [
[ -156, 169.6989, 43 ],
[ -156, 166.8166, 54.605 ],
[ -156, 162.0189, 64.6276 ],
[ -156, 155.4836, 73.1827 ],
[ -156, 147.389, 80.3855 ],
[ -156, 137.9129, 86.3509 ],
[ -156, 127.2335, 91.194 ],
[ -156, 115.5287, 95.0298 ],
[ -156, 102.9766, 97.9735 ],
[ -156, 89.7552, 100.14 ],
[ -156, 76.0426, 101.6444 ],
[ -156, 62.0167, 102.6017 ],
[ -156, 47.8557, 103.1269 ],
[ -156, 33.7375, 103.3352 ],
[ -156, 19.8401, 103.3415 ],
[ -156, 6.3417, 103.261 ],
[ -156, -6.5798, 103.2085 ]
]
# primary_3
[[geometry.pipes]]
index = 9
points_mm = [
[ -52, 169.6989, 43 ],
[ -53.0575, 166.7079, 55.9219 ],
[ -56.046, 162.0061, 66.9219 ],
[ -60.6896, 155.731, 76.1501 ],
[ -66.7126, 148.0196, 83.7568 ],
[ -73.839, 139.0093, 89.8919 ],
[ -81.793, 128.8373, 94.7057 ],
[ -90.2988, 117.6409, 98.3485 ],
[ -99.0804, 105.5573, 100.9702 ],
[ -107.8619, 92.7238, 102.7211 ],
[ -116.3677, 79.2776, 103.7514 ],
[ -124.3217, 65.3559, 104.2112 ],
[ -131.4481, 51.0961, 104.2507 ],
[ -137.4711, 36.6354, 104.02 ],
[ -142.1148, 22.111, 103.6693 ],
[ -145.1033, 7.6602, 103.3488 ],
[ -146.1607, -6.5798, 103.2085 ]
]
# primary_4
[[geometry.pipes]]
index = 10
points_mm = [
[ 52, 169.6989, 43 ],
[ 53.0575, 166.6343, 57.2699 ],
[ 56.046, 161.9975, 70.224 ],
[ 60.6896, 155.8982, 81.9166 ],
[ 66.7126, 148.446, 92.4023 ],
[ 73.839, 139.7507, 101.7358 ],
[ 81.793, 129.9218, 109.9715 ],
[ 90.2988, 119.0691, 117.164 ],
[ 99.0804, 107.3023, 123.3678 ],
[ 107.8619, 94.731, 128.6374 ],
[ 116.3677, 81.465, 133.0275 ],
[ 124.3217, 67.6138, 136.5926 ],
[ 131.4481, 53.2872, 139.3871 ],
[ 137.4711, 38.5949, 141.4657 ],
[ 142.1148, 23.6465, 142.8829 ],
[ 145.1033, 8.5517, 143.6932 ],
[ 146.1607, -6.5798, 143.9512 ]
]
# primary_5
[[geometry.pipes]]
index = 11
points_mm = [
[ 156, 169.6989, 43 ],
[ 156, 166.7349, 56.0518 ],
[ 156, 162.0093, 68.1017 ],
[ 156, 155.6694, 79.1718 ],
[ 156, 147.8627, 89.284 ],
[ 156, 138.7365, 98.4604 ],
[ 156, 128.4383, 106.7232 ],
[ 156, 117.1154, 114.0943 ],
[ 156, 104.9152, 120.5959 ],
[ 156, 91.9852, 126.25 ],
[ 156, 78.4727, 131.0786 ],
[ 156, 64.5251, 135.1038 ],
[ 156, 50.2899, 138.3477 ],
[ 156, 35.9144, 140.8323 ],
[ 156, 21.546, 142.5797 ],
[ 156, 7.3321, 143.612 ],
[ 156, -6.5798, 143.9512 ]
]
# primary_6
[[geometry.pipes]]
index = 12
points_mm = [
[ 260, 169.6989, 43 ],
[ 258.9425, 166.6343, 57.2699 ],
[ 255.954, 161.9975, 70.224 ],
[ 251.3103, 155.8982, 81.9166 ],
[ 245.2874, 148.446, 92.4023 ],
[ 238.161, 139.7507, 101.7358 ],
[ 230.207, 129.9218, 109.9715 ],
[ 221.7012, 119.0691, 117.164 ],
[ 212.9196, 107.3023, 123.3678 ],
[ 204.138, 94.731, 128.6374 ],
[ 195.6323, 81.465, 133.0275 ],
[ 187.6783, 67.6138, 136.5926 ],
[ 180.5519, 53.2872, 139.3871 ],
[ 174.5289, 38.5949, 141.4657 ],
[ 169.8852, 23.6465, 142.8829 ],
[ 166.8968, 8.5517, 143.6932 ],
[ 165.8393, -6.5798, 143.9512 ]
]
# tailpipe_13
[[geometry.pipes]]
index = 13
points_mm = [
[ 218.4202, -70, 169.5 ],
[ 1718.4201, -70, 169.5 ]
]
# secondary_14
[[geometry.pipes]]
index = 14
points_mm = [
[ -137.5798, -27.3284, 112.6045 ],
[ -115.9354, -27.7815, 113.2086 ],
[ -94.9697, -29.0619, 114.9159 ],
[ -74.5859, -31.0515, 117.5686 ],
[ -54.6869, -33.632, 121.0094 ],
[ -35.1758, -36.6853, 125.0805 ],
[ -15.9556, -40.0932, 129.6243 ],
[ 3.0707, -43.7375, 134.4834 ],
[ 22, -47.5, 139.5 ],
[ 40.9293, -51.2625, 144.5166 ],
[ 59.9556, -54.9068, 149.3757 ],
[ 79.1758, -58.3147, 153.9195 ],
[ 98.6869, -61.368, 157.9906 ],
[ 118.5859, -63.9485, 161.4314 ],
[ 138.9697, -65.9381, 164.0842 ],
[ 159.9354, -67.2185, 165.7914 ],
[ 181.5798, -67.6716, 166.3955 ]
]
# secondary_15
[[geometry.pipes]]
index = 15
points_mm = [
[ 174.0108, -43.4202, 160.6401 ],
[ 174.1004, -44.1614, 160.6706 ],
[ 174.3536, -44.8101, 160.7569 ],
[ 174.7471, -45.3796, 160.8911 ],
[ 175.2574, -45.8829, 161.065 ],
[ 175.8613, -46.3335, 161.2709 ],
[ 176.5352, -46.7443, 161.5006 ],
[ 177.2559, -47.1288, 161.7463 ],
[ 178, -47.5, 162 ],
[ 178.7441, -47.8712, 162.2536 ],
[ 179.4648, -48.2557, 162.4994 ],
[ 180.1387, -48.6665, 162.7291 ],
[ 180.7426, -49.1171, 162.935 ],
[ 181.2529, -49.6204, 163.1089 ],
[ 181.6464, -50.1899, 163.2431 ],
[ 181.8996, -50.8386, 163.3294 ],
[ 181.9892, -51.5798, 163.3599 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 169.6989, -361.8 ]
size_mm = [ 624, 62.1149, 154.8 ]
[[geometry.volumes]]
id = "junction_0"
center_mm = [ -156, -25, 109.5 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_1"
center_mm = [ 156, -25, 154.5 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_2"
center_mm = [ 200, -70, 169.5 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "saab-chambered"
name = "Saab B205 / chambered"
category = "Inline four / Saab"
@@ -7,9 +7,11 @@ description = "Expansion resonator and two-chamber rear muffler"
order = 4
cycle = "four_stroke"
[simulation]
cells_per_meter = 40.0
[engine]
bank_angle_deg = 56.0
collector_volume_litres = 0.6
cylinders = 4
initial_rpm = 1200.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 200.0
minimum_substeps = 2
phase_degrees = [0.0, 180.0, 540.0, 360.0]
pipe_damping = 0.0
plenum_volume_litres = 3.0
starter_torque_nm = 65.0
throttle_diameter_mm = 52.0
topology = "collector"
@@ -31,18 +32,12 @@ burn_duration_deg = 60.0
burn_start_deg = 5.0
compression = 8.8
energy_variation = 0.05
exhaust_close_deg = 390.0
exhaust_open_deg = 130.0
exhaust_valve_count = 2
exhaust_valve_diameter_mm = 28.0
exhaust_valve_lift_mm = 8.0
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 590.0
intake_open_deg = 340.0
intake_valve_count = 2
intake_valve_diameter_mm = 32.0
intake_valve_lift_mm = 8.5
misfire_probability = 0.0
rod_mm = 153.0
stroke_mm = 78.0
@@ -115,7 +110,7 @@ loss_coefficient = 0.1
passage_diameter_mm = 60.0
resistance = 2.0
type = "expansion"
volume_litres = 6.0
diameter_mm = 138.1976604
[silencers.muffler]
chambers = 2
@@ -125,28 +120,282 @@ loss_coefficient = 0.35
passage_diameter_mm = 60.0
resistance = 4.0
type = "chambered"
volume_litres = 18.0
diameter_mm = 187.7736449
[[pipes]]
id = "intake"
cells = 8
diameter_mm = 60.0
length_mm = 150.0
[[pipes]]
id = "runners"
count = 4
cells = 10
diameter_mm = 32.0
length_mm = 250.0
[[pipes]]
id = "primaries"
count = 4
cells = 10
diameter_mm = 34.0
length_mm = 280.0
[[pipes]]
id = "tailpipe"
profile = "silenced"
[cam.intake]
points = [
[0.000, 0.000000],
[340.000, 0.000000],
[345.000, 0.154496],
[350.000, 0.583168],
[355.000, 1.233792],
[360.000, 2.054144],
[365.000, 2.992000],
[370.000, 3.995136],
[375.000, 5.011328],
[380.000, 5.988352],
[385.000, 6.873984],
[390.000, 7.616000],
[395.000, 8.162176],
[400.000, 8.460288],
[402.500, 8.500000],
[527.500, 8.500000],
[532.500, 8.345504],
[537.500, 7.916832],
[542.500, 7.266208],
[547.500, 6.445856],
[552.500, 5.508000],
[557.500, 4.504864],
[562.500, 3.488672],
[567.500, 2.511648],
[572.500, 1.626016],
[577.500, 0.884000],
[582.500, 0.337824],
[587.500, 0.039712],
[590.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[130.000, 0.000000],
[135.000, 0.134729],
[140.000, 0.509786],
[145.000, 1.081475],
[150.000, 1.806099],
[155.000, 2.639964],
[160.000, 3.539372],
[165.000, 4.460628],
[170.000, 5.360036],
[175.000, 6.193901],
[180.000, 6.918525],
[185.000, 7.490214],
[190.000, 7.865271],
[195.000, 8.000000],
[325.000, 8.000000],
[330.000, 7.865271],
[335.000, 7.490214],
[340.000, 6.918525],
[345.000, 6.193901],
[350.000, 5.360036],
[355.000, 4.460628],
[360.000, 3.539372],
[365.000, 2.639964],
[370.000, 1.806099],
[375.000, 1.081475],
[380.000, 0.509786],
[385.000, 0.134729],
[390.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ -162, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ -54, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ 54, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ 162, 0, 0 ]
tilt_deg = 0.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -366, 194, -367 ],
[ -216, 194, -367 ]
]
# runners 1
[[geometry.pipes]]
index = 1
points_mm = [
[ -162, 194, -286 ],
[ -162, 194, -36 ]
]
# runners 2
[[geometry.pipes]]
index = 2
points_mm = [
[ -54, 194, -286 ],
[ -54, 194, -36 ]
]
# runners 3
[[geometry.pipes]]
index = 3
points_mm = [
[ 54, 194, -286 ],
[ 54, 194, -36 ]
]
# runners 4
[[geometry.pipes]]
index = 4
points_mm = [
[ 162, 194, -286 ],
[ 162, 194, -36 ]
]
# primaries 1
[[geometry.pipes]]
index = 5
points_mm = [
[ -162, 194, 45 ],
[ -160.763, 188.9129, 64.0687 ],
[ -157.153, 180.2477, 79.8812 ],
[ -151.3211, 168.5436, 92.717 ],
[ -143.4188, 154.3395, 102.8556 ],
[ -133.5973, 138.1743, 110.5766 ],
[ -122.008, 120.5871, 116.1596 ],
[ -108.8022, 102.1169, 119.884 ],
[ -94.1311, 83.3026, 122.0296 ],
[ -78.1462, 64.6831, 122.8757 ],
[ -60.9986, 46.7976, 122.702 ],
[ -42.8397, 30.185, 121.7881 ],
[ -23.8208, 15.3842, 120.4134 ],
[ -4.0933, 2.9342, 118.8576 ],
[ 16.1917, -6.6259, 117.4002 ],
[ 36.8826, -12.7573, 116.3208 ],
[ 57.8284, -14.9208, 115.8988 ]
]
# primaries 2
[[geometry.pipes]]
index = 6
points_mm = [
[ -54, 194, 45 ],
[ -52.5634, 190.4508, 60.6385 ],
[ -48.5035, 184.9694, 73.8883 ],
[ -42.1951, 177.702, 84.9399 ],
[ -34.0128, 168.7945, 93.9836 ],
[ -24.3315, 158.3933, 101.2098 ],
[ -13.526, 146.6443, 106.8088 ],
[ -1.9709, 133.6937, 110.971 ],
[ 9.959, 119.6875, 113.8869 ],
[ 21.8888, 104.7719, 115.7468 ],
[ 33.4439, 89.093, 116.741 ],
[ 44.2495, 72.7969, 117.0599 ],
[ 53.9308, 56.0297, 116.8939 ],
[ 62.113, 38.9374, 116.4335 ],
[ 68.4215, 21.6663, 115.8689 ],
[ 72.4814, 4.3623, 115.3905 ],
[ 73.9179, -12.8284, 115.1887 ]
]
# primaries 3
[[geometry.pipes]]
index = 7
points_mm = [
[ 54, 194, 45 ],
[ 54.4291, 190.6029, 58.7971 ],
[ 55.6418, 184.9872, 70.6803 ],
[ 57.5261, 177.3561, 80.7907 ],
[ 59.9702, 167.9128, 89.2697 ],
[ 62.862, 156.8603, 96.2586 ],
[ 66.0897, 144.4017, 101.8985 ],
[ 69.5412, 130.7403, 106.3308 ],
[ 73.1046, 116.0791, 109.6968 ],
[ 76.6681, 100.6212, 112.1377 ],
[ 80.1196, 84.5698, 113.7948 ],
[ 83.3472, 68.1279, 114.8094 ],
[ 86.2391, 51.4988, 115.3227 ],
[ 88.6831, 34.8854, 115.476 ],
[ 90.5675, 18.4911, 115.4106 ],
[ 91.7801, 2.5188, 115.2677 ],
[ 92.2093, -12.8284, 115.1887 ]
]
# primaries 4
[[geometry.pipes]]
index = 8
points_mm = [
[ 162, 194, 45 ],
[ 161.4216, 190.5899, 58.9539 ],
[ 159.7871, 184.9857, 70.9535 ],
[ 157.2474, 177.3856, 81.144 ],
[ 153.9532, 167.9879, 89.6711 ],
[ 150.0555, 156.9908, 96.6801 ],
[ 145.7053, 144.5927, 102.3166 ],
[ 141.0532, 130.9918, 106.7259 ],
[ 136.2503, 116.3863, 110.0536 ],
[ 131.4474, 100.9746, 112.445 ],
[ 126.7953, 84.9549, 114.0457 ],
[ 122.445, 68.5255, 115.001 ],
[ 118.5474, 51.8846, 115.4565 ],
[ 115.2532, 35.2305, 115.5575 ],
[ 112.7134, 18.7614, 115.4496 ],
[ 111.0789, 2.6757, 115.2782 ],
[ 110.5006, -12.8284, 115.1887 ]
]
# tailpipe
[[geometry.pipes]]
index = 9
points_mm = [
[ 142.1717, -55, 129.5 ],
[ 3742.1715, -55, 129.5 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 194, -367 ]
size_mm = [ 432, 42.8669, 162 ]
[[geometry.volumes]]
id = "exhaust_collector"
center_mm = [ 100, -55, 129.5 ]
size_mm = [ 84.3433, 84.3433, 84.3433 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "saab-expansion"
name = "Saab B205 / expansion silencers"
category = "Inline four / Saab"
@@ -7,9 +7,11 @@ description = "Two single expansion chambers, same body volumes"
order = 5
cycle = "four_stroke"
[simulation]
cells_per_meter = 40.0
[engine]
bank_angle_deg = 56.0
collector_volume_litres = 0.6
cylinders = 4
initial_rpm = 1200.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 200.0
minimum_substeps = 2
phase_degrees = [0.0, 180.0, 540.0, 360.0]
pipe_damping = 0.0
plenum_volume_litres = 3.0
starter_torque_nm = 65.0
throttle_diameter_mm = 52.0
topology = "collector"
@@ -31,18 +32,12 @@ burn_duration_deg = 60.0
burn_start_deg = 5.0
compression = 8.8
energy_variation = 0.05
exhaust_close_deg = 390.0
exhaust_open_deg = 130.0
exhaust_valve_count = 2
exhaust_valve_diameter_mm = 28.0
exhaust_valve_lift_mm = 8.0
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 590.0
intake_open_deg = 340.0
intake_valve_count = 2
intake_valve_diameter_mm = 32.0
intake_valve_lift_mm = 8.5
misfire_probability = 0.0
rod_mm = 153.0
stroke_mm = 78.0
@@ -115,7 +110,7 @@ loss_coefficient = 0.1
passage_diameter_mm = 60.0
resistance = 2.0
type = "expansion"
volume_litres = 6.0
diameter_mm = 138.1976604
[silencers.muffler]
chambers = 1
@@ -125,28 +120,282 @@ loss_coefficient = 0.35
passage_diameter_mm = 60.0
resistance = 4.0
type = "expansion"
volume_litres = 18.0
diameter_mm = 187.7736449
[[pipes]]
id = "intake"
cells = 8
diameter_mm = 60.0
length_mm = 150.0
[[pipes]]
id = "runners"
count = 4
cells = 10
diameter_mm = 32.0
length_mm = 250.0
[[pipes]]
id = "primaries"
count = 4
cells = 10
diameter_mm = 34.0
length_mm = 280.0
[[pipes]]
id = "tailpipe"
profile = "silenced"
[cam.intake]
points = [
[0.000, 0.000000],
[340.000, 0.000000],
[345.000, 0.154496],
[350.000, 0.583168],
[355.000, 1.233792],
[360.000, 2.054144],
[365.000, 2.992000],
[370.000, 3.995136],
[375.000, 5.011328],
[380.000, 5.988352],
[385.000, 6.873984],
[390.000, 7.616000],
[395.000, 8.162176],
[400.000, 8.460288],
[402.500, 8.500000],
[527.500, 8.500000],
[532.500, 8.345504],
[537.500, 7.916832],
[542.500, 7.266208],
[547.500, 6.445856],
[552.500, 5.508000],
[557.500, 4.504864],
[562.500, 3.488672],
[567.500, 2.511648],
[572.500, 1.626016],
[577.500, 0.884000],
[582.500, 0.337824],
[587.500, 0.039712],
[590.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[130.000, 0.000000],
[135.000, 0.134729],
[140.000, 0.509786],
[145.000, 1.081475],
[150.000, 1.806099],
[155.000, 2.639964],
[160.000, 3.539372],
[165.000, 4.460628],
[170.000, 5.360036],
[175.000, 6.193901],
[180.000, 6.918525],
[185.000, 7.490214],
[190.000, 7.865271],
[195.000, 8.000000],
[325.000, 8.000000],
[330.000, 7.865271],
[335.000, 7.490214],
[340.000, 6.918525],
[345.000, 6.193901],
[350.000, 5.360036],
[355.000, 4.460628],
[360.000, 3.539372],
[365.000, 2.639964],
[370.000, 1.806099],
[375.000, 1.081475],
[380.000, 0.509786],
[385.000, 0.134729],
[390.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ -162, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ -54, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ 54, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ 162, 0, 0 ]
tilt_deg = 0.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -366, 194, -367 ],
[ -216, 194, -367 ]
]
# runners 1
[[geometry.pipes]]
index = 1
points_mm = [
[ -162, 194, -286 ],
[ -162, 194, -36 ]
]
# runners 2
[[geometry.pipes]]
index = 2
points_mm = [
[ -54, 194, -286 ],
[ -54, 194, -36 ]
]
# runners 3
[[geometry.pipes]]
index = 3
points_mm = [
[ 54, 194, -286 ],
[ 54, 194, -36 ]
]
# runners 4
[[geometry.pipes]]
index = 4
points_mm = [
[ 162, 194, -286 ],
[ 162, 194, -36 ]
]
# primaries 1
[[geometry.pipes]]
index = 5
points_mm = [
[ -162, 194, 45 ],
[ -160.763, 188.9129, 64.0687 ],
[ -157.153, 180.2477, 79.8812 ],
[ -151.3211, 168.5436, 92.717 ],
[ -143.4188, 154.3395, 102.8556 ],
[ -133.5973, 138.1743, 110.5766 ],
[ -122.008, 120.5871, 116.1596 ],
[ -108.8022, 102.1169, 119.884 ],
[ -94.1311, 83.3026, 122.0296 ],
[ -78.1462, 64.6831, 122.8757 ],
[ -60.9986, 46.7976, 122.702 ],
[ -42.8397, 30.185, 121.7881 ],
[ -23.8208, 15.3842, 120.4134 ],
[ -4.0933, 2.9342, 118.8576 ],
[ 16.1917, -6.6259, 117.4002 ],
[ 36.8826, -12.7573, 116.3208 ],
[ 57.8284, -14.9208, 115.8988 ]
]
# primaries 2
[[geometry.pipes]]
index = 6
points_mm = [
[ -54, 194, 45 ],
[ -52.5634, 190.4508, 60.6385 ],
[ -48.5035, 184.9694, 73.8883 ],
[ -42.1951, 177.702, 84.9399 ],
[ -34.0128, 168.7945, 93.9836 ],
[ -24.3315, 158.3933, 101.2098 ],
[ -13.526, 146.6443, 106.8088 ],
[ -1.9709, 133.6937, 110.971 ],
[ 9.959, 119.6875, 113.8869 ],
[ 21.8888, 104.7719, 115.7468 ],
[ 33.4439, 89.093, 116.741 ],
[ 44.2495, 72.7969, 117.0599 ],
[ 53.9308, 56.0297, 116.8939 ],
[ 62.113, 38.9374, 116.4335 ],
[ 68.4215, 21.6663, 115.8689 ],
[ 72.4814, 4.3623, 115.3905 ],
[ 73.9179, -12.8284, 115.1887 ]
]
# primaries 3
[[geometry.pipes]]
index = 7
points_mm = [
[ 54, 194, 45 ],
[ 54.4291, 190.6029, 58.7971 ],
[ 55.6418, 184.9872, 70.6803 ],
[ 57.5261, 177.3561, 80.7907 ],
[ 59.9702, 167.9128, 89.2697 ],
[ 62.862, 156.8603, 96.2586 ],
[ 66.0897, 144.4017, 101.8985 ],
[ 69.5412, 130.7403, 106.3308 ],
[ 73.1046, 116.0791, 109.6968 ],
[ 76.6681, 100.6212, 112.1377 ],
[ 80.1196, 84.5698, 113.7948 ],
[ 83.3472, 68.1279, 114.8094 ],
[ 86.2391, 51.4988, 115.3227 ],
[ 88.6831, 34.8854, 115.476 ],
[ 90.5675, 18.4911, 115.4106 ],
[ 91.7801, 2.5188, 115.2677 ],
[ 92.2093, -12.8284, 115.1887 ]
]
# primaries 4
[[geometry.pipes]]
index = 8
points_mm = [
[ 162, 194, 45 ],
[ 161.4216, 190.5899, 58.9539 ],
[ 159.7871, 184.9857, 70.9535 ],
[ 157.2474, 177.3856, 81.144 ],
[ 153.9532, 167.9879, 89.6711 ],
[ 150.0555, 156.9908, 96.6801 ],
[ 145.7053, 144.5927, 102.3166 ],
[ 141.0532, 130.9918, 106.7259 ],
[ 136.2503, 116.3863, 110.0536 ],
[ 131.4474, 100.9746, 112.445 ],
[ 126.7953, 84.9549, 114.0457 ],
[ 122.445, 68.5255, 115.001 ],
[ 118.5474, 51.8846, 115.4565 ],
[ 115.2532, 35.2305, 115.5575 ],
[ 112.7134, 18.7614, 115.4496 ],
[ 111.0789, 2.6757, 115.2782 ],
[ 110.5006, -12.8284, 115.1887 ]
]
# tailpipe
[[geometry.pipes]]
index = 9
points_mm = [
[ 142.1717, -55, 129.5 ],
[ 3742.1715, -55, 129.5 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 194, -367 ]
size_mm = [ 432, 42.8669, 162 ]
[[geometry.volumes]]
id = "exhaust_collector"
center_mm = [ 100, -55, 129.5 ]
size_mm = [ 84.3433, 84.3433, 84.3433 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "saab-open"
name = "Saab B205 / open exhaust"
category = "Inline four / Saab"
@@ -7,9 +7,11 @@ description = "Silencers bypassed; naturally aspirated baseline"
order = 6
cycle = "four_stroke"
[simulation]
cells_per_meter = 40.0
[engine]
bank_angle_deg = 56.0
collector_volume_litres = 0.6
cylinders = 4
initial_rpm = 1200.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 200.0
minimum_substeps = 2
phase_degrees = [0.0, 180.0, 540.0, 360.0]
pipe_damping = 0.0
plenum_volume_litres = 3.0
starter_torque_nm = 65.0
throttle_diameter_mm = 52.0
topology = "collector"
@@ -31,18 +32,12 @@ burn_duration_deg = 60.0
burn_start_deg = 5.0
compression = 8.8
energy_variation = 0.05
exhaust_close_deg = 390.0
exhaust_open_deg = 130.0
exhaust_valve_count = 2
exhaust_valve_diameter_mm = 28.0
exhaust_valve_lift_mm = 8.0
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 590.0
intake_open_deg = 340.0
intake_valve_count = 2
intake_valve_diameter_mm = 32.0
intake_valve_lift_mm = 8.5
misfire_probability = 0.0
rod_mm = 153.0
stroke_mm = 78.0
@@ -115,7 +110,7 @@ loss_coefficient = 0.1
passage_diameter_mm = 60.0
resistance = 2.0
type = "straight_through"
volume_litres = 6.0
diameter_mm = 138.1976604
[silencers.muffler]
chambers = 1
@@ -125,28 +120,282 @@ loss_coefficient = 0.35
passage_diameter_mm = 60.0
resistance = 4.0
type = "straight_through"
volume_litres = 18.0
diameter_mm = 187.7736449
[[pipes]]
id = "intake"
cells = 8
diameter_mm = 60.0
length_mm = 150.0
[[pipes]]
id = "runners"
count = 4
cells = 10
diameter_mm = 32.0
length_mm = 250.0
[[pipes]]
id = "primaries"
count = 4
cells = 10
diameter_mm = 34.0
length_mm = 280.0
[[pipes]]
id = "tailpipe"
profile = "silenced"
[cam.intake]
points = [
[0.000, 0.000000],
[340.000, 0.000000],
[345.000, 0.154496],
[350.000, 0.583168],
[355.000, 1.233792],
[360.000, 2.054144],
[365.000, 2.992000],
[370.000, 3.995136],
[375.000, 5.011328],
[380.000, 5.988352],
[385.000, 6.873984],
[390.000, 7.616000],
[395.000, 8.162176],
[400.000, 8.460288],
[402.500, 8.500000],
[527.500, 8.500000],
[532.500, 8.345504],
[537.500, 7.916832],
[542.500, 7.266208],
[547.500, 6.445856],
[552.500, 5.508000],
[557.500, 4.504864],
[562.500, 3.488672],
[567.500, 2.511648],
[572.500, 1.626016],
[577.500, 0.884000],
[582.500, 0.337824],
[587.500, 0.039712],
[590.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[130.000, 0.000000],
[135.000, 0.134729],
[140.000, 0.509786],
[145.000, 1.081475],
[150.000, 1.806099],
[155.000, 2.639964],
[160.000, 3.539372],
[165.000, 4.460628],
[170.000, 5.360036],
[175.000, 6.193901],
[180.000, 6.918525],
[185.000, 7.490214],
[190.000, 7.865271],
[195.000, 8.000000],
[325.000, 8.000000],
[330.000, 7.865271],
[335.000, 7.490214],
[340.000, 6.918525],
[345.000, 6.193901],
[350.000, 5.360036],
[355.000, 4.460628],
[360.000, 3.539372],
[365.000, 2.639964],
[370.000, 1.806099],
[375.000, 1.081475],
[380.000, 0.509786],
[385.000, 0.134729],
[390.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ -162, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ -54, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ 54, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ 162, 0, 0 ]
tilt_deg = 0.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -366, 194, -367 ],
[ -216, 194, -367 ]
]
# runners 1
[[geometry.pipes]]
index = 1
points_mm = [
[ -162, 194, -286 ],
[ -162, 194, -36 ]
]
# runners 2
[[geometry.pipes]]
index = 2
points_mm = [
[ -54, 194, -286 ],
[ -54, 194, -36 ]
]
# runners 3
[[geometry.pipes]]
index = 3
points_mm = [
[ 54, 194, -286 ],
[ 54, 194, -36 ]
]
# runners 4
[[geometry.pipes]]
index = 4
points_mm = [
[ 162, 194, -286 ],
[ 162, 194, -36 ]
]
# primaries 1
[[geometry.pipes]]
index = 5
points_mm = [
[ -162, 194, 45 ],
[ -160.763, 188.9129, 64.0687 ],
[ -157.153, 180.2477, 79.8812 ],
[ -151.3211, 168.5436, 92.717 ],
[ -143.4188, 154.3395, 102.8556 ],
[ -133.5973, 138.1743, 110.5766 ],
[ -122.008, 120.5871, 116.1596 ],
[ -108.8022, 102.1169, 119.884 ],
[ -94.1311, 83.3026, 122.0296 ],
[ -78.1462, 64.6831, 122.8757 ],
[ -60.9986, 46.7976, 122.702 ],
[ -42.8397, 30.185, 121.7881 ],
[ -23.8208, 15.3842, 120.4134 ],
[ -4.0933, 2.9342, 118.8576 ],
[ 16.1917, -6.6259, 117.4002 ],
[ 36.8826, -12.7573, 116.3208 ],
[ 57.8284, -14.9208, 115.8988 ]
]
# primaries 2
[[geometry.pipes]]
index = 6
points_mm = [
[ -54, 194, 45 ],
[ -52.5634, 190.4508, 60.6385 ],
[ -48.5035, 184.9694, 73.8883 ],
[ -42.1951, 177.702, 84.9399 ],
[ -34.0128, 168.7945, 93.9836 ],
[ -24.3315, 158.3933, 101.2098 ],
[ -13.526, 146.6443, 106.8088 ],
[ -1.9709, 133.6937, 110.971 ],
[ 9.959, 119.6875, 113.8869 ],
[ 21.8888, 104.7719, 115.7468 ],
[ 33.4439, 89.093, 116.741 ],
[ 44.2495, 72.7969, 117.0599 ],
[ 53.9308, 56.0297, 116.8939 ],
[ 62.113, 38.9374, 116.4335 ],
[ 68.4215, 21.6663, 115.8689 ],
[ 72.4814, 4.3623, 115.3905 ],
[ 73.9179, -12.8284, 115.1887 ]
]
# primaries 3
[[geometry.pipes]]
index = 7
points_mm = [
[ 54, 194, 45 ],
[ 54.4291, 190.6029, 58.7971 ],
[ 55.6418, 184.9872, 70.6803 ],
[ 57.5261, 177.3561, 80.7907 ],
[ 59.9702, 167.9128, 89.2697 ],
[ 62.862, 156.8603, 96.2586 ],
[ 66.0897, 144.4017, 101.8985 ],
[ 69.5412, 130.7403, 106.3308 ],
[ 73.1046, 116.0791, 109.6968 ],
[ 76.6681, 100.6212, 112.1377 ],
[ 80.1196, 84.5698, 113.7948 ],
[ 83.3472, 68.1279, 114.8094 ],
[ 86.2391, 51.4988, 115.3227 ],
[ 88.6831, 34.8854, 115.476 ],
[ 90.5675, 18.4911, 115.4106 ],
[ 91.7801, 2.5188, 115.2677 ],
[ 92.2093, -12.8284, 115.1887 ]
]
# primaries 4
[[geometry.pipes]]
index = 8
points_mm = [
[ 162, 194, 45 ],
[ 161.4216, 190.5899, 58.9539 ],
[ 159.7871, 184.9857, 70.9535 ],
[ 157.2474, 177.3856, 81.144 ],
[ 153.9532, 167.9879, 89.6711 ],
[ 150.0555, 156.9908, 96.6801 ],
[ 145.7053, 144.5927, 102.3166 ],
[ 141.0532, 130.9918, 106.7259 ],
[ 136.2503, 116.3863, 110.0536 ],
[ 131.4474, 100.9746, 112.445 ],
[ 126.7953, 84.9549, 114.0457 ],
[ 122.445, 68.5255, 115.001 ],
[ 118.5474, 51.8846, 115.4565 ],
[ 115.2532, 35.2305, 115.5575 ],
[ 112.7134, 18.7614, 115.4496 ],
[ 111.0789, 2.6757, 115.2782 ],
[ 110.5006, -12.8284, 115.1887 ]
]
# tailpipe
[[geometry.pipes]]
index = 9
points_mm = [
[ 142.1717, -55, 129.5 ],
[ 1892.1716, -55, 129.5 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 194, -367 ]
size_mm = [ 432, 42.8669, 162 ]
[[geometry.volumes]]
id = "exhaust_collector"
center_mm = [ 100, -55, 129.5 ]
size_mm = [ 84.3433, 84.3433, 84.3433 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "saab-straight"
name = "Saab B205 / straight-through"
category = "Inline four / Saab"
@@ -7,9 +7,11 @@ description = "6 L resonator and 18 L straight-through rear muffler"
order = 3
cycle = "four_stroke"
[simulation]
cells_per_meter = 40.0
[engine]
bank_angle_deg = 56.0
collector_volume_litres = 0.6
cylinders = 4
initial_rpm = 1200.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 200.0
minimum_substeps = 2
phase_degrees = [0.0, 180.0, 540.0, 360.0]
pipe_damping = 0.0
plenum_volume_litres = 3.0
starter_torque_nm = 65.0
throttle_diameter_mm = 52.0
topology = "collector"
@@ -31,18 +32,12 @@ burn_duration_deg = 60.0
burn_start_deg = 5.0
compression = 8.8
energy_variation = 0.05
exhaust_close_deg = 390.0
exhaust_open_deg = 130.0
exhaust_valve_count = 2
exhaust_valve_diameter_mm = 28.0
exhaust_valve_lift_mm = 8.0
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 590.0
intake_open_deg = 340.0
intake_valve_count = 2
intake_valve_diameter_mm = 32.0
intake_valve_lift_mm = 8.5
misfire_probability = 0.0
rod_mm = 153.0
stroke_mm = 78.0
@@ -115,7 +110,7 @@ loss_coefficient = 0.1
passage_diameter_mm = 60.0
resistance = 2.0
type = "straight_through"
volume_litres = 6.0
diameter_mm = 138.1976604
[silencers.muffler]
chambers = 1
@@ -125,28 +120,282 @@ loss_coefficient = 0.35
passage_diameter_mm = 60.0
resistance = 4.0
type = "straight_through"
volume_litres = 18.0
diameter_mm = 187.7736449
[[pipes]]
id = "intake"
cells = 8
diameter_mm = 60.0
length_mm = 150.0
[[pipes]]
id = "runners"
count = 4
cells = 10
diameter_mm = 32.0
length_mm = 250.0
[[pipes]]
id = "primaries"
count = 4
cells = 10
diameter_mm = 34.0
length_mm = 280.0
[[pipes]]
id = "tailpipe"
profile = "silenced"
[cam.intake]
points = [
[0.000, 0.000000],
[340.000, 0.000000],
[345.000, 0.154496],
[350.000, 0.583168],
[355.000, 1.233792],
[360.000, 2.054144],
[365.000, 2.992000],
[370.000, 3.995136],
[375.000, 5.011328],
[380.000, 5.988352],
[385.000, 6.873984],
[390.000, 7.616000],
[395.000, 8.162176],
[400.000, 8.460288],
[402.500, 8.500000],
[527.500, 8.500000],
[532.500, 8.345504],
[537.500, 7.916832],
[542.500, 7.266208],
[547.500, 6.445856],
[552.500, 5.508000],
[557.500, 4.504864],
[562.500, 3.488672],
[567.500, 2.511648],
[572.500, 1.626016],
[577.500, 0.884000],
[582.500, 0.337824],
[587.500, 0.039712],
[590.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[130.000, 0.000000],
[135.000, 0.134729],
[140.000, 0.509786],
[145.000, 1.081475],
[150.000, 1.806099],
[155.000, 2.639964],
[160.000, 3.539372],
[165.000, 4.460628],
[170.000, 5.360036],
[175.000, 6.193901],
[180.000, 6.918525],
[185.000, 7.490214],
[190.000, 7.865271],
[195.000, 8.000000],
[325.000, 8.000000],
[330.000, 7.865271],
[335.000, 7.490214],
[340.000, 6.918525],
[345.000, 6.193901],
[350.000, 5.360036],
[355.000, 4.460628],
[360.000, 3.539372],
[365.000, 2.639964],
[370.000, 1.806099],
[375.000, 1.081475],
[380.000, 0.509786],
[385.000, 0.134729],
[390.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ -162, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ -54, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ 54, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ 162, 0, 0 ]
tilt_deg = 0.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -366, 194, -367 ],
[ -216, 194, -367 ]
]
# runners 1
[[geometry.pipes]]
index = 1
points_mm = [
[ -162, 194, -286 ],
[ -162, 194, -36 ]
]
# runners 2
[[geometry.pipes]]
index = 2
points_mm = [
[ -54, 194, -286 ],
[ -54, 194, -36 ]
]
# runners 3
[[geometry.pipes]]
index = 3
points_mm = [
[ 54, 194, -286 ],
[ 54, 194, -36 ]
]
# runners 4
[[geometry.pipes]]
index = 4
points_mm = [
[ 162, 194, -286 ],
[ 162, 194, -36 ]
]
# primaries 1
[[geometry.pipes]]
index = 5
points_mm = [
[ -162, 194, 45 ],
[ -160.763, 188.9129, 64.0687 ],
[ -157.153, 180.2477, 79.8812 ],
[ -151.3211, 168.5436, 92.717 ],
[ -143.4188, 154.3395, 102.8556 ],
[ -133.5973, 138.1743, 110.5766 ],
[ -122.008, 120.5871, 116.1596 ],
[ -108.8022, 102.1169, 119.884 ],
[ -94.1311, 83.3026, 122.0296 ],
[ -78.1462, 64.6831, 122.8757 ],
[ -60.9986, 46.7976, 122.702 ],
[ -42.8397, 30.185, 121.7881 ],
[ -23.8208, 15.3842, 120.4134 ],
[ -4.0933, 2.9342, 118.8576 ],
[ 16.1917, -6.6259, 117.4002 ],
[ 36.8826, -12.7573, 116.3208 ],
[ 57.8284, -14.9208, 115.8988 ]
]
# primaries 2
[[geometry.pipes]]
index = 6
points_mm = [
[ -54, 194, 45 ],
[ -52.5634, 190.4508, 60.6385 ],
[ -48.5035, 184.9694, 73.8883 ],
[ -42.1951, 177.702, 84.9399 ],
[ -34.0128, 168.7945, 93.9836 ],
[ -24.3315, 158.3933, 101.2098 ],
[ -13.526, 146.6443, 106.8088 ],
[ -1.9709, 133.6937, 110.971 ],
[ 9.959, 119.6875, 113.8869 ],
[ 21.8888, 104.7719, 115.7468 ],
[ 33.4439, 89.093, 116.741 ],
[ 44.2495, 72.7969, 117.0599 ],
[ 53.9308, 56.0297, 116.8939 ],
[ 62.113, 38.9374, 116.4335 ],
[ 68.4215, 21.6663, 115.8689 ],
[ 72.4814, 4.3623, 115.3905 ],
[ 73.9179, -12.8284, 115.1887 ]
]
# primaries 3
[[geometry.pipes]]
index = 7
points_mm = [
[ 54, 194, 45 ],
[ 54.4291, 190.6029, 58.7971 ],
[ 55.6418, 184.9872, 70.6803 ],
[ 57.5261, 177.3561, 80.7907 ],
[ 59.9702, 167.9128, 89.2697 ],
[ 62.862, 156.8603, 96.2586 ],
[ 66.0897, 144.4017, 101.8985 ],
[ 69.5412, 130.7403, 106.3308 ],
[ 73.1046, 116.0791, 109.6968 ],
[ 76.6681, 100.6212, 112.1377 ],
[ 80.1196, 84.5698, 113.7948 ],
[ 83.3472, 68.1279, 114.8094 ],
[ 86.2391, 51.4988, 115.3227 ],
[ 88.6831, 34.8854, 115.476 ],
[ 90.5675, 18.4911, 115.4106 ],
[ 91.7801, 2.5188, 115.2677 ],
[ 92.2093, -12.8284, 115.1887 ]
]
# primaries 4
[[geometry.pipes]]
index = 8
points_mm = [
[ 162, 194, 45 ],
[ 161.4216, 190.5899, 58.9539 ],
[ 159.7871, 184.9857, 70.9535 ],
[ 157.2474, 177.3856, 81.144 ],
[ 153.9532, 167.9879, 89.6711 ],
[ 150.0555, 156.9908, 96.6801 ],
[ 145.7053, 144.5927, 102.3166 ],
[ 141.0532, 130.9918, 106.7259 ],
[ 136.2503, 116.3863, 110.0536 ],
[ 131.4474, 100.9746, 112.445 ],
[ 126.7953, 84.9549, 114.0457 ],
[ 122.445, 68.5255, 115.001 ],
[ 118.5474, 51.8846, 115.4565 ],
[ 115.2532, 35.2305, 115.5575 ],
[ 112.7134, 18.7614, 115.4496 ],
[ 111.0789, 2.6757, 115.2782 ],
[ 110.5006, -12.8284, 115.1887 ]
]
# tailpipe
[[geometry.pipes]]
index = 9
points_mm = [
[ 142.1717, -55, 129.5 ],
[ 3742.1715, -55, 129.5 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 194, -367 ]
size_mm = [ 432, 42.8669, 162 ]
[[geometry.volumes]]
id = "exhaust_collector"
center_mm = [ 100, -55, 129.5 ]
size_mm = [ 84.3433, 84.3433, 84.3433 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "single-450"
name = "450 cc / carbureted four-stroke thumper"
category = "Singles / four-stroke"
@@ -7,9 +7,11 @@ description = "40 mm carb / mild valve timing / fixed idle stop"
order = 17
cycle = "four_stroke"
[simulation]
cells_per_meter = 20.0
[engine]
bank_angle_deg = 60.0
collector_volume_litres = 0.25
cylinders = 1
initial_rpm = 1960.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 30.0
minimum_substeps = 1
phase_degrees = [0.0]
pipe_damping = 1.0
plenum_volume_litres = 1.0
starter_torque_nm = 11.21926
throttle_diameter_mm = 40.0
topology = "collector"
@@ -31,18 +32,12 @@ burn_duration_deg = 60.0
burn_start_deg = 5.0
compression = 10.0
energy_variation = 0.05
exhaust_close_deg = 370.0
exhaust_open_deg = 145.0
exhaust_valve_count = 1
exhaust_valve_diameter_mm = 30.72
exhaust_valve_lift_mm = 8.640001
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 565.0
intake_open_deg = 355.0
intake_valve_count = 1
intake_valve_diameter_mm = 36.48
intake_valve_lift_mm = 9.6
misfire_probability = 0.0
rod_mm = 111.6
stroke_mm = 62.0
@@ -73,8 +68,8 @@ overrun_resume_rpm = 2400.0
power_afr = 12.8
rev_limit_enabled = true
rev_limit_fuel_cut_enabled = false
rev_limit_rpm = 7000.0
rev_resume_rpm = 6750.0
rev_limit_rpm = 13000.0
rev_resume_rpm = 12990.0
stoichiometric_afr = 14.7
throttle_closing_rate = 0.0
throttle_closing_time_constant_s = 0.0
@@ -113,24 +108,165 @@ bowl_refill_kg_per_s = 0.1
[[pipes]]
id = "intake"
cells = 4
diameter_mm = 75.0
length_mm = 200.0
[[pipes]]
id = "runners"
cells = 5
diameter_mm = 35.0
length_mm = 250.0
[[pipes]]
id = "primaries"
cells = 13
diameter_mm = 38.0
length_mm = 650.0
[[pipes]]
id = "tailpipe"
cells = 30
diameter_mm = 60.0
length_mm = 1500.0
[cam.intake]
points = [
[0.000, 0.000000],
[355.000, 0.000000],
[360.000, 0.244639],
[365.000, 0.912213],
[370.000, 1.903207],
[375.000, 3.118108],
[380.000, 4.457402],
[385.000, 5.821574],
[390.000, 7.111111],
[395.000, 8.226498],
[400.000, 9.068222],
[405.000, 9.536767],
[407.500, 9.600000],
[512.500, 9.600000],
[517.500, 9.355361],
[522.500, 8.687787],
[527.500, 7.696793],
[532.500, 6.481892],
[537.500, 5.142598],
[542.500, 3.778426],
[547.500, 2.488889],
[552.500, 1.373502],
[557.500, 0.531778],
[562.500, 0.063233],
[565.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[145.000, 0.000000],
[150.000, 0.192664],
[155.000, 0.722110],
[160.000, 1.515520],
[165.000, 2.500077],
[170.000, 3.602963],
[175.000, 4.751361],
[180.000, 5.872451],
[185.000, 6.893417],
[190.000, 7.741441],
[195.000, 8.343705],
[200.000, 8.627391],
[201.250, 8.640001],
[313.750, 8.640001],
[318.750, 8.447337],
[323.750, 7.917891],
[328.750, 7.124481],
[333.750, 6.139924],
[338.750, 5.037038],
[343.750, 3.888640],
[348.750, 2.767550],
[353.750, 1.746584],
[358.750, 0.898560],
[363.750, 0.296296],
[368.750, 0.012610],
[370.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ 0, 0, 0 ]
tilt_deg = 0.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -257, 141.4889, -374.8 ],
[ -57, 141.4889, -374.8 ]
]
# runners
[[geometry.pipes]]
index = 1
points_mm = [
[ 0, 141.4889, -288.4 ],
[ 0, 141.4889, -38.4 ]
]
# primaries
[[geometry.pipes]]
index = 2
points_mm = [
[ 0, 141.4889, 48 ],
[ 0.943, 138.6704, 60.5191 ],
[ 3.6081, 134.2865, 71.5364 ],
[ 7.7491, 128.4585, 81.1477 ],
[ 13.1202, 121.3077, 89.4488 ],
[ 19.4754, 112.9555, 96.5354 ],
[ 26.5685, 103.5233, 102.5034 ],
[ 34.1536, 93.1325, 107.4485 ],
[ 41.9848, 81.9043, 111.4664 ],
[ 49.8159, 69.9601, 114.6529 ],
[ 57.4011, 57.4214, 117.1039 ],
[ 64.4942, 44.4093, 118.915 ],
[ 70.8493, 31.0454, 120.1821 ],
[ 76.2204, 17.451, 121.0009 ],
[ 80.3615, 3.7474, 121.4672 ],
[ 83.0265, -9.944, 121.6767 ],
[ 83.9696, -23.502, 121.7253 ]
]
# tailpipe
[[geometry.pipes]]
index = 3
points_mm = [
[ 131.4981, -55, 135.8 ],
[ 1631.4981, -55, 135.8 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 141.4889, -374.8 ]
size_mm = [ 114, 50.7635, 172.8 ]
[[geometry.volumes]]
id = "exhaust_collector"
center_mm = [ 100, -55, 135.8 ]
size_mm = [ 62.9961, 62.9961, 62.9961 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "single-650"
name = "650 cc / big thumper"
category = "Singles / four-stroke"
@@ -7,9 +7,11 @@ description = "Estimated concept calibration / EFI"
order = 18
cycle = "four_stroke"
[simulation]
cells_per_meter = 20.0
[engine]
bank_angle_deg = 60.0
collector_volume_litres = 0.25
cylinders = 1
initial_rpm = 1400.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 30.0
minimum_substeps = 1
phase_degrees = [0.0]
pipe_damping = 1.0
plenum_volume_litres = 1.0
starter_torque_nm = 16.29701
throttle_diameter_mm = 65.0
topology = "collector"
@@ -31,18 +32,12 @@ burn_duration_deg = 60.0
burn_start_deg = 5.0
compression = 10.0
energy_variation = 0.05
exhaust_close_deg = 390.0
exhaust_open_deg = 130.0
exhaust_valve_count = 1
exhaust_valve_diameter_mm = 32.0
exhaust_valve_lift_mm = 9.000001
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 590.0
intake_open_deg = 340.0
intake_valve_count = 1
intake_valve_diameter_mm = 38.0
intake_valve_lift_mm = 10.0
misfire_probability = 0.0
rod_mm = 149.4
stroke_mm = 83.0
@@ -101,24 +96,171 @@ system = "injection"
[[pipes]]
id = "intake"
cells = 4
diameter_mm = 75.0
length_mm = 200.0
[[pipes]]
id = "runners"
cells = 5
diameter_mm = 35.0
length_mm = 250.0
[[pipes]]
id = "primaries"
cells = 13
diameter_mm = 38.0
length_mm = 650.0
[[pipes]]
id = "tailpipe"
cells = 30
diameter_mm = 60.0
length_mm = 1500.0
[cam.intake]
points = [
[0.000, 0.000000],
[340.000, 0.000000],
[345.000, 0.181760],
[350.000, 0.686080],
[355.000, 1.451520],
[360.000, 2.416640],
[365.000, 3.520000],
[370.000, 4.700160],
[375.000, 5.895680],
[380.000, 7.045120],
[385.000, 8.087040],
[390.000, 8.960000],
[395.000, 9.602560],
[400.000, 9.953280],
[402.500, 10.000000],
[527.500, 10.000000],
[532.500, 9.818240],
[537.500, 9.313920],
[542.500, 8.548480],
[547.500, 7.583360],
[552.500, 6.480000],
[557.500, 5.299840],
[562.500, 4.104320],
[567.500, 2.954880],
[572.500, 1.912960],
[577.500, 1.040000],
[582.500, 0.397440],
[587.500, 0.046720],
[590.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[130.000, 0.000000],
[135.000, 0.151570],
[140.000, 0.573509],
[145.000, 1.216659],
[150.000, 2.031862],
[155.000, 2.969959],
[160.000, 3.981794],
[165.000, 5.018207],
[170.000, 6.030042],
[175.000, 6.968139],
[180.000, 7.783342],
[185.000, 8.426492],
[190.000, 8.848431],
[195.000, 9.000001],
[325.000, 9.000001],
[330.000, 8.848431],
[335.000, 8.426492],
[340.000, 7.783342],
[345.000, 6.968139],
[350.000, 6.030042],
[355.000, 5.018207],
[360.000, 3.981794],
[365.000, 2.969959],
[370.000, 2.031862],
[375.000, 1.216659],
[380.000, 0.573509],
[385.000, 0.151570],
[390.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ 0, 0, 0 ]
tilt_deg = 0.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -259, 192.1222, -380 ],
[ -59, 192.1222, -380 ]
]
# runners
[[geometry.pipes]]
index = 1
points_mm = [
[ 0, 192.1222, -290 ],
[ 0, 192.1222, -40 ]
]
# primaries
[[geometry.pipes]]
index = 2
points_mm = [
[ 0, 192.1222, 50 ],
[ 0.9799, 188.5111, 65.2858 ],
[ 3.7492, 182.718, 78.469 ],
[ 8.0523, 174.9286, 89.7037 ],
[ 13.6334, 165.3287, 99.144 ],
[ 20.2371, 154.1038, 106.9438 ],
[ 27.6077, 141.4398, 113.2573 ],
[ 35.4896, 127.5224, 118.2383 ],
[ 43.627, 112.5371, 122.0411 ],
[ 51.7645, 96.6697, 124.8196 ],
[ 59.6463, 80.106, 126.7279 ],
[ 67.0169, 63.0315, 127.9199 ],
[ 73.6206, 45.632, 128.5498 ],
[ 79.2018, 28.0933, 128.7716 ],
[ 83.5049, 10.6009, 128.7393 ],
[ 86.2742, -6.6594, 128.607 ],
[ 87.2541, -23.502, 128.5287 ]
]
# tailpipe
[[geometry.pipes]]
index = 3
points_mm = [
[ 131.4981, -55, 140 ],
[ 1631.498, -55, 140 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 192.1222, -380 ]
size_mm = [ 118, 47.081, 180 ]
[[geometry.volumes]]
id = "exhaust_collector"
center_mm = [ 100, -55, 140 ]
size_mm = [ 62.9961, 62.9961, 62.9961 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "six-long"
name = "3.0 litre / long-stroke inline six"
category = "Inline six"
@@ -7,9 +7,11 @@ description = "Unequal primaries / small junctions / staged collectors / EFI"
order = 25
cycle = "four_stroke"
[simulation]
cells_per_meter = 20.0
[engine]
bank_angle_deg = 60.0
collector_volume_litres = 1.5
cylinders = 6
initial_rpm = 1400.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 30.0
minimum_substeps = 1
phase_degrees = [0.0, 240.0, 480.0, 120.0, 600.0, 360.0]
pipe_damping = 0.0
plenum_volume_litres = 6.0
starter_torque_nm = 74.93359
throttle_diameter_mm = 65.0
topology = "staged"
@@ -31,18 +32,12 @@ burn_duration_deg = 60.0
burn_start_deg = 5.0
compression = 10.0
energy_variation = 0.05
exhaust_close_deg = 390.0
exhaust_open_deg = 130.0
exhaust_valve_count = 1
exhaust_valve_diameter_mm = 27.52
exhaust_valve_lift_mm = 7.740001
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 590.0
intake_open_deg = 340.0
intake_valve_count = 1
intake_valve_diameter_mm = 32.68
intake_valve_lift_mm = 8.6
misfire_probability = 0.0
rod_mm = 154.8
stroke_mm = 86.0
@@ -101,71 +96,434 @@ system = "injection"
[junctions]
h_pipe = true
junction_volume_cc = 50.0
[[pipes]]
id = "intake"
cells = 4
diameter_mm = 75.0
length_mm = 200.0
[[pipes]]
id = "runners"
count = 6
cells = 5
diameter_mm = 35.0
length_mm = 250.0
[[pipes]]
id = "primary_1"
cells = 11
diameter_mm = 38.0
length_mm = 520.0
[[pipes]]
id = "primary_2"
cells = 12
diameter_mm = 38.0
length_mm = 585.0
[[pipes]]
id = "primary_3"
cells = 13
diameter_mm = 38.0
length_mm = 650.0
[[pipes]]
id = "primary_4"
cells = 11
diameter_mm = 38.0
length_mm = 520.0
[[pipes]]
id = "primary_5"
cells = 12
diameter_mm = 38.0
length_mm = 585.0
[[pipes]]
id = "primary_6"
cells = 13
diameter_mm = 38.0
length_mm = 650.0
[[pipes]]
id = "tailpipe_13"
cells = 30
diameter_mm = 60.0
length_mm = 1500.0
[[pipes]]
id = "secondary_14"
cells = 9
diameter_mm = 48.0
length_mm = 420.0
[[pipes]]
id = "secondary_15"
cells = 11
diameter_mm = 48.0
length_mm = 540.0
[cam.intake]
points = [
[0.000, 0.000000],
[340.000, 0.000000],
[345.000, 0.156314],
[350.000, 0.590029],
[355.000, 1.248307],
[360.000, 2.078310],
[365.000, 3.027200],
[370.000, 4.042138],
[375.000, 5.070285],
[380.000, 6.058803],
[385.000, 6.954854],
[390.000, 7.705600],
[395.000, 8.258202],
[400.000, 8.559821],
[402.500, 8.600000],
[527.500, 8.600000],
[532.500, 8.443686],
[537.500, 8.009971],
[542.500, 7.351693],
[547.500, 6.521690],
[552.500, 5.572800],
[557.500, 4.557862],
[562.500, 3.529715],
[567.500, 2.541197],
[572.500, 1.645146],
[577.500, 0.894400],
[582.500, 0.341798],
[587.500, 0.040179],
[590.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[130.000, 0.000000],
[135.000, 0.130350],
[140.000, 0.493218],
[145.000, 1.046327],
[150.000, 1.747401],
[155.000, 2.554165],
[160.000, 3.424343],
[165.000, 4.315658],
[170.000, 5.185836],
[175.000, 5.992600],
[180.000, 6.693674],
[185.000, 7.246783],
[190.000, 7.609651],
[195.000, 7.740001],
[325.000, 7.740001],
[330.000, 7.609651],
[335.000, 7.246783],
[340.000, 6.693674],
[345.000, 5.992600],
[350.000, 5.185836],
[355.000, 4.315658],
[360.000, 3.424343],
[365.000, 2.554165],
[370.000, 1.747401],
[375.000, 1.046327],
[380.000, 0.493218],
[385.000, 0.130350],
[390.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ -260, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ -156, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ -52, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ 52, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ 156, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ 260, 0, 0 ]
tilt_deg = 0.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -512, 199.3556, -361.7999 ],
[ -312, 199.3556, -361.7999 ]
]
# runners 1
[[geometry.pipes]]
index = 1
points_mm = [
[ -260, 199.3556, -284.3999 ],
[ -260, 199.3556, -34.4 ]
]
# runners 2
[[geometry.pipes]]
index = 2
points_mm = [
[ -156, 199.3556, -284.3999 ],
[ -156, 199.3556, -34.4 ]
]
# runners 3
[[geometry.pipes]]
index = 3
points_mm = [
[ -52, 199.3556, -284.3999 ],
[ -52, 199.3556, -34.4 ]
]
# runners 4
[[geometry.pipes]]
index = 4
points_mm = [
[ 52, 199.3556, -284.3999 ],
[ 52, 199.3556, -34.4 ]
]
# runners 5
[[geometry.pipes]]
index = 5
points_mm = [
[ 156, 199.3556, -284.3999 ],
[ 156, 199.3556, -34.4 ]
]
# runners 6
[[geometry.pipes]]
index = 6
points_mm = [
[ 260, 199.3556, -284.3999 ],
[ 260, 199.3556, -34.4 ]
]
# primary_1
[[geometry.pipes]]
index = 7
points_mm = [
[ -260, 199.3556, 43 ],
[ -258.9279, 195.9039, 57.4758 ],
[ -255.8981, 190.3736, 69.6486 ],
[ -251.1903, 182.9408, 79.7072 ],
[ -245.0842, 173.7821, 87.8407 ],
[ -237.8593, 163.0735, 94.2378 ],
[ -229.7954, 150.9915, 99.0876 ],
[ -221.1722, 137.7124, 102.5789 ],
[ -212.2693, 123.4124, 104.9007 ],
[ -203.3664, 108.2679, 106.2418 ],
[ -194.7432, 92.4553, 106.7912 ],
[ -186.6793, 76.1507, 106.7377 ],
[ -179.4545, 59.5305, 106.2704 ],
[ -173.3483, 42.7711, 105.578 ],
[ -168.6405, 26.0487, 104.8496 ],
[ -165.6107, 9.5396, 104.274 ],
[ -164.5387, -6.5798, 104.0402 ]
]
# primary_2
[[geometry.pipes]]
index = 8
points_mm = [
[ -156, 199.3556, 43 ],
[ -156, 196.0021, 56.2873 ],
[ -156, 190.385, 67.5779 ],
[ -156, 182.7176, 77.0291 ],
[ -156, 173.2129, 84.798 ],
[ -156, 162.084, 91.042 ],
[ -156, 149.544, 95.9182 ],
[ -156, 135.8061, 99.5838 ],
[ -156, 121.0833, 102.1961 ],
[ -156, 105.5888, 103.9123 ],
[ -156, 89.5356, 104.8895 ],
[ -156, 73.137, 105.2851 ],
[ -156, 56.606, 105.2562 ],
[ -156, 40.1556, 104.96 ],
[ -156, 23.9992, 104.5538 ],
[ -156, 8.3496, 104.1948 ],
[ -156, -6.5798, 104.0402 ]
]
# primary_3
[[geometry.pipes]]
index = 9
points_mm = [
[ -52, 199.3556, 43 ],
[ -53.0721, 195.9039, 57.4758 ],
[ -56.1019, 190.3736, 69.6486 ],
[ -60.8097, 182.9408, 79.7072 ],
[ -66.9158, 173.7821, 87.8407 ],
[ -74.1407, 163.0735, 94.2378 ],
[ -82.2046, 150.9915, 99.0876 ],
[ -90.8278, 137.7124, 102.5789 ],
[ -99.7307, 123.4124, 104.9007 ],
[ -108.6336, 108.2679, 106.2418 ],
[ -117.2568, 92.4553, 106.7912 ],
[ -125.3207, 76.1507, 106.7377 ],
[ -132.5455, 59.5305, 106.2704 ],
[ -138.6517, 42.7711, 105.578 ],
[ -143.3595, 26.0487, 104.8496 ],
[ -146.3893, 9.5396, 104.274 ],
[ -147.4613, -6.5798, 104.0402 ]
]
# primary_4
[[geometry.pipes]]
index = 10
points_mm = [
[ 52, 199.3556, 43 ],
[ 53.0721, 195.8364, 58.7577 ],
[ 56.1019, 190.3657, 72.8486 ],
[ 60.8097, 183.0945, 85.3623 ],
[ 66.9158, 174.1738, 96.3887 ],
[ 74.1407, 163.7546, 106.0175 ],
[ 82.2046, 151.9878, 114.3382 ],
[ 90.8278, 139.0244, 121.4408 ],
[ 99.7307, 125.0154, 127.4148 ],
[ 108.6336, 110.1119, 132.35 ],
[ 117.2568, 94.4647, 136.3361 ],
[ 125.3207, 78.2248, 139.4628 ],
[ 132.5455, 61.5433, 141.8198 ],
[ 138.6517, 44.5711, 143.4969 ],
[ 143.3595, 27.4592, 144.5837 ],
[ 146.3893, 10.3586, 145.1701 ],
[ 147.4613, -6.5798, 145.3456 ]
]
# primary_5
[[geometry.pipes]]
index = 11
points_mm = [
[ 156, 199.3556, 43 ],
[ 156, 195.9285, 57.6414 ],
[ 156, 190.3764, 70.9037 ],
[ 156, 182.8848, 82.847 ],
[ 156, 173.6392, 93.5311 ],
[ 156, 162.8252, 103.0159 ],
[ 156, 150.6282, 111.3615 ],
[ 156, 137.234, 118.6277 ],
[ 156, 122.8279, 124.8746 ],
[ 156, 107.5956, 130.1621 ],
[ 156, 91.7225, 134.55 ],
[ 156, 75.3944, 138.0984 ],
[ 156, 58.7966, 140.8673 ],
[ 156, 42.1147, 142.9164 ],
[ 156, 25.5343, 144.3059 ],
[ 156, 9.2409, 145.0956 ],
[ 156, -6.5798, 145.3456 ]
]
# primary_6
[[geometry.pipes]]
index = 12
points_mm = [
[ 260, 199.3556, 43 ],
[ 258.9279, 195.8364, 58.7577 ],
[ 255.8981, 190.3657, 72.8486 ],
[ 251.1903, 183.0945, 85.3623 ],
[ 245.0842, 174.1738, 96.3887 ],
[ 237.8593, 163.7546, 106.0175 ],
[ 229.7954, 151.9878, 114.3382 ],
[ 221.1722, 139.0244, 121.4408 ],
[ 212.2693, 125.0154, 127.4148 ],
[ 203.3664, 110.1119, 132.35 ],
[ 194.7432, 94.4647, 136.3361 ],
[ 186.6793, 78.2248, 139.4628 ],
[ 179.4545, 61.5433, 141.8198 ],
[ 173.3483, 44.5711, 143.4969 ],
[ 168.6405, 27.4592, 144.5837 ],
[ 165.6107, 10.3586, 145.1701 ],
[ 164.5387, -6.5798, 145.3456 ]
]
# tailpipe_13
[[geometry.pipes]]
index = 13
points_mm = [
[ 218.4202, -70, 169.5 ],
[ 1718.4201, -70, 169.5 ]
]
# secondary_14
[[geometry.pipes]]
index = 14
points_mm = [
[ -137.5798, -27.3284, 112.6045 ],
[ -115.9354, -27.7815, 113.2086 ],
[ -94.9697, -29.0619, 114.9159 ],
[ -74.5859, -31.0515, 117.5686 ],
[ -54.6869, -33.632, 121.0094 ],
[ -35.1758, -36.6853, 125.0805 ],
[ -15.9556, -40.0932, 129.6243 ],
[ 3.0707, -43.7375, 134.4834 ],
[ 22, -47.5, 139.5 ],
[ 40.9293, -51.2625, 144.5166 ],
[ 59.9556, -54.9068, 149.3757 ],
[ 79.1758, -58.3147, 153.9195 ],
[ 98.6869, -61.368, 157.9906 ],
[ 118.5859, -63.9485, 161.4314 ],
[ 138.9697, -65.9381, 164.0842 ],
[ 159.9354, -67.2185, 165.7914 ],
[ 181.5798, -67.6716, 166.3955 ]
]
# secondary_15
[[geometry.pipes]]
index = 15
points_mm = [
[ 174.0108, -43.4202, 160.6401 ],
[ 174.1004, -44.1614, 160.6706 ],
[ 174.3536, -44.8101, 160.7569 ],
[ 174.7471, -45.3796, 160.8911 ],
[ 175.2574, -45.8829, 161.065 ],
[ 175.8613, -46.3335, 161.2709 ],
[ 176.5352, -46.7443, 161.5006 ],
[ 177.2559, -47.1288, 161.7463 ],
[ 178, -47.5, 162 ],
[ 178.7441, -47.8712, 162.2536 ],
[ 179.4648, -48.2557, 162.4994 ],
[ 180.1387, -48.6665, 162.7291 ],
[ 180.7426, -49.1171, 162.935 ],
[ 181.2529, -49.6204, 163.1089 ],
[ 181.6464, -50.1899, 163.2431 ],
[ 181.8996, -50.8386, 163.3294 ],
[ 181.9892, -51.5798, 163.3599 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 199.3556, -361.7999 ]
size_mm = [ 624, 62.1149, 154.8 ]
[[geometry.volumes]]
id = "junction_0"
center_mm = [ -156, -25, 109.5 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_1"
center_mm = [ 156, -25, 154.5 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_2"
center_mm = [ 200, -70, 169.5 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "triple"
name = "1.0 litre / inline triple"
category = "Inline three"
@@ -7,9 +7,11 @@ description = "Unequal primaries / small junctions / staged collectors / EFI"
order = 21
cycle = "four_stroke"
[simulation]
cells_per_meter = 20.0
[engine]
bank_angle_deg = 60.0
collector_volume_litres = 0.75
cylinders = 3
initial_rpm = 1400.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 30.0
minimum_substeps = 1
phase_degrees = [0.0, 480.0, 240.0]
pipe_damping = 0.0
plenum_volume_litres = 3.0
starter_torque_nm = 24.99852
throttle_diameter_mm = 65.0
topology = "staged"
@@ -31,18 +32,12 @@ burn_duration_deg = 60.0
burn_start_deg = 5.0
compression = 10.0
energy_variation = 0.05
exhaust_close_deg = 390.0
exhaust_open_deg = 130.0
exhaust_valve_count = 1
exhaust_valve_diameter_mm = 25.28
exhaust_valve_lift_mm = 7.110001
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 590.0
intake_open_deg = 340.0
intake_valve_count = 1
intake_valve_diameter_mm = 30.02
intake_valve_lift_mm = 7.900001
misfire_probability = 0.0
rod_mm = 122.4
stroke_mm = 68.0
@@ -101,47 +96,285 @@ system = "injection"
[junctions]
h_pipe = true
junction_volume_cc = 50.0
[[pipes]]
id = "intake"
cells = 4
diameter_mm = 75.0
length_mm = 200.0
[[pipes]]
id = "runners"
count = 3
cells = 5
diameter_mm = 35.0
length_mm = 250.0
[[pipes]]
id = "primary_1"
cells = 11
diameter_mm = 38.0
length_mm = 520.0
[[pipes]]
id = "primary_2"
cells = 12
diameter_mm = 38.0
length_mm = 585.0
[[pipes]]
id = "primary_3"
cells = 13
diameter_mm = 38.0
length_mm = 650.0
[[pipes]]
id = "tailpipe_7"
cells = 30
diameter_mm = 60.0
length_mm = 1500.0
[[pipes]]
id = "secondary_8"
cells = 9
diameter_mm = 48.0
length_mm = 420.0
[cam.intake]
points = [
[0.000, 0.000000],
[340.000, 0.000000],
[345.000, 0.143590],
[350.000, 0.542003],
[355.000, 1.146701],
[360.000, 1.909146],
[365.000, 2.780800],
[370.000, 3.713127],
[375.000, 4.657588],
[380.000, 5.565646],
[385.000, 6.388762],
[390.000, 7.078401],
[395.000, 7.586023],
[400.000, 7.863092],
[402.500, 7.900001],
[527.500, 7.900001],
[532.500, 7.756411],
[537.500, 7.357998],
[542.500, 6.753300],
[547.500, 5.990855],
[552.500, 5.119201],
[557.500, 4.186874],
[562.500, 3.242413],
[567.500, 2.334355],
[572.500, 1.511239],
[577.500, 0.821600],
[582.500, 0.313978],
[587.500, 0.036909],
[590.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[130.000, 0.000000],
[135.000, 0.119741],
[140.000, 0.453072],
[145.000, 0.961161],
[150.000, 1.605171],
[155.000, 2.346268],
[160.000, 3.145617],
[165.000, 3.964384],
[170.000, 4.763733],
[175.000, 5.504830],
[180.000, 6.148840],
[185.000, 6.656929],
[190.000, 6.990260],
[195.000, 7.110001],
[325.000, 7.110001],
[330.000, 6.990260],
[335.000, 6.656929],
[340.000, 6.148840],
[345.000, 5.504830],
[350.000, 4.763733],
[355.000, 3.964384],
[360.000, 3.145617],
[365.000, 2.346268],
[370.000, 1.605171],
[375.000, 0.961161],
[380.000, 0.453072],
[385.000, 0.119741],
[390.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ -97, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ 0, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ 97, 0, 0 ]
tilt_deg = 0.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -345.5, 155.9556, -352.7 ],
[ -145.5, 155.9556, -352.7 ]
]
# runners 1
[[geometry.pipes]]
index = 1
points_mm = [
[ -97, 155.9556, -281.6 ],
[ -97, 155.9556, -31.6 ]
]
# runners 2
[[geometry.pipes]]
index = 2
points_mm = [
[ 0, 155.9556, -281.6 ],
[ 0, 155.9556, -31.6 ]
]
# runners 3
[[geometry.pipes]]
index = 3
points_mm = [
[ 97, 155.9556, -281.6 ],
[ 97, 155.9556, -31.6 ]
]
# primary_1
[[geometry.pipes]]
index = 4
points_mm = [
[ -97, 155.9556, 39.5 ],
[ -96.0215, 153.1933, 51.498 ],
[ -93.2563, 148.862, 61.7643 ],
[ -88.9596, 143.0867, 70.4309 ],
[ -83.3866, 135.9922, 77.6302 ],
[ -76.7926, 127.7034, 83.4941 ],
[ -69.4328, 118.3452, 88.1548 ],
[ -61.5625, 108.0425, 91.7445 ],
[ -53.437, 96.9201, 94.3953 ],
[ -45.3115, 85.103, 96.2394 ],
[ -37.4412, 72.716, 97.4088 ],
[ -30.0814, 59.884, 98.0358 ],
[ -23.4874, 46.7319, 98.2525 ],
[ -17.9144, 33.3846, 98.191 ],
[ -13.6177, 19.9669, 97.9834 ],
[ -10.8525, 6.6038, 97.762 ],
[ -9.874, -6.5798, 97.6589 ]
]
# primary_2
[[geometry.pipes]]
index = 5
points_mm = [
[ 0, 155.9556, 39.5 ],
[ 0, 153.2938, 50.2812 ],
[ 0, 148.8738, 59.6443 ],
[ 0, 142.8582, 67.689 ],
[ 0, 135.4095, 74.515 ],
[ 0, 126.6903, 80.2221 ],
[ 0, 116.8632, 84.9099 ],
[ 0, 106.0908, 88.678 ],
[ 0, 94.5355, 91.6263 ],
[ 0, 82.36, 93.8543 ],
[ 0, 69.7268, 95.4619 ],
[ 0, 56.7985, 96.5485 ],
[ 0, 43.7377, 97.2141 ],
[ 0, 30.7069, 97.5582 ],
[ 0, 17.8686, 97.6806 ],
[ 0, 5.3855, 97.6809 ],
[ 0, -6.5798, 97.6589 ]
]
# primary_3
[[geometry.pipes]]
index = 6
points_mm = [
[ 97, 155.9556, 39.5 ],
[ 96.0215, 153.1933, 51.498 ],
[ 93.2563, 148.862, 61.7643 ],
[ 88.9596, 143.0867, 70.4309 ],
[ 83.3866, 135.9922, 77.6302 ],
[ 76.7926, 127.7034, 83.4941 ],
[ 69.4328, 118.3452, 88.1548 ],
[ 61.5625, 108.0425, 91.7445 ],
[ 53.437, 96.9201, 94.3953 ],
[ 45.3115, 85.103, 96.2394 ],
[ 37.4412, 72.716, 97.4088 ],
[ 30.0814, 59.884, 98.0358 ],
[ 23.4874, 46.7319, 98.2525 ],
[ 17.9144, 33.3846, 98.191 ],
[ 13.6177, 19.9669, 97.9834 ],
[ 10.8525, 6.6038, 97.762 ],
[ 9.874, -6.5798, 97.6589 ]
]
# tailpipe_7
[[geometry.pipes]]
index = 7
points_mm = [
[ 218.4202, -70, 164.25 ],
[ 1718.4201, -70, 164.25 ]
]
# secondary_8
[[geometry.pipes]]
index = 8
points_mm = [
[ 18.4202, -29.1445, 109.776 ],
[ 29.9012, -29.5568, 110.3258 ],
[ 40.8688, -30.722, 111.8793 ],
[ 51.3963, -32.5324, 114.2932 ],
[ 61.5571, -34.8806, 117.4242 ],
[ 71.4246, -37.659, 121.1287 ],
[ 81.072, -40.7601, 125.2635 ],
[ 90.5727, -44.0763, 129.685 ],
[ 100, -47.5, 134.25 ],
[ 109.4274, -50.9237, 138.815 ],
[ 118.9281, -54.2399, 143.2365 ],
[ 128.5754, -57.341, 147.3713 ],
[ 138.4429, -60.1194, 151.0758 ],
[ 148.6037, -62.4676, 154.2068 ],
[ 159.1312, -64.278, 156.6207 ],
[ 170.0988, -65.4432, 158.1742 ],
[ 181.5798, -65.8555, 158.724 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 155.9556, -352.7 ]
size_mm = [ 291, 72.4984, 142.2 ]
[[geometry.volumes]]
id = "junction_0"
center_mm = [ 0, -25, 104.25 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_1"
center_mm = [ 200, -70, 164.25 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "twin-270"
name = "700 cc / 270-degree parallel twin"
category = "Twins / parallel"
@@ -7,9 +7,11 @@ description = "Estimated concept calibration / EFI"
order = 19
cycle = "four_stroke"
[simulation]
cells_per_meter = 50.0
[engine]
bank_angle_deg = 60.0
collector_volume_litres = 0.5
cylinders = 2
initial_rpm = 1400.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 30.0
minimum_substeps = 1
phase_degrees = [0.0, 450.0]
pipe_damping = 1.0
plenum_volume_litres = 2.0
starter_torque_nm = 17.09026
throttle_diameter_mm = 65.0
topology = "collector"
@@ -31,18 +32,12 @@ burn_duration_deg = 60.0
burn_start_deg = 5.0
compression = 10.0
energy_variation = 0.05
exhaust_close_deg = 390.0
exhaust_open_deg = 130.0
exhaust_valve_count = 1
exhaust_valve_diameter_mm = 25.6
exhaust_valve_lift_mm = 7.2
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 590.0
intake_open_deg = 340.0
intake_valve_count = 1
intake_valve_diameter_mm = 30.4
intake_valve_lift_mm = 8.0
misfire_probability = 0.0
rod_mm = 122.4
stroke_mm = 68.0
@@ -101,26 +96,208 @@ system = "injection"
[[pipes]]
id = "intake"
cells = 4
diameter_mm = 75.0
length_mm = 200.0
[[pipes]]
id = "runners"
count = 2
cells = 5
diameter_mm = 35.0
length_mm = 250.0
[[pipes]]
id = "primaries"
count = 2
cells = 13
diameter_mm = 38.0
length_mm = 650.0
[[pipes]]
id = "tailpipe"
cells = 30
diameter_mm = 60.0
length_mm = 1500.0
[cam.intake]
points = [
[0.000, 0.000000],
[340.000, 0.000000],
[345.000, 0.145408],
[350.000, 0.548864],
[355.000, 1.161216],
[360.000, 1.933312],
[365.000, 2.816000],
[370.000, 3.760128],
[375.000, 4.716544],
[380.000, 5.636096],
[385.000, 6.469632],
[390.000, 7.168000],
[395.000, 7.682048],
[400.000, 7.962624],
[402.500, 8.000000],
[527.500, 8.000000],
[532.500, 7.854592],
[537.500, 7.451136],
[542.500, 6.838784],
[547.500, 6.066688],
[552.500, 5.184000],
[557.500, 4.239872],
[562.500, 3.283456],
[567.500, 2.363904],
[572.500, 1.530368],
[577.500, 0.832000],
[582.500, 0.317952],
[587.500, 0.037376],
[590.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[130.000, 0.000000],
[135.000, 0.121256],
[140.000, 0.458807],
[145.000, 0.973327],
[150.000, 1.625489],
[155.000, 2.375967],
[160.000, 3.185435],
[165.000, 4.014565],
[170.000, 4.824033],
[175.000, 5.574511],
[180.000, 6.226673],
[185.000, 6.741193],
[190.000, 7.078744],
[195.000, 7.200000],
[325.000, 7.200000],
[330.000, 7.078744],
[335.000, 6.741193],
[340.000, 6.226673],
[345.000, 5.574511],
[350.000, 4.824033],
[355.000, 4.014565],
[360.000, 3.185435],
[365.000, 2.375967],
[370.000, 1.625489],
[375.000, 0.973327],
[380.000, 0.458807],
[385.000, 0.121256],
[390.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ -49, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ 49, 0, 0 ]
tilt_deg = 0.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -298, 155.9556, -354 ],
[ -98, 155.9556, -354 ]
]
# runners 1
[[geometry.pipes]]
index = 1
points_mm = [
[ -49, 155.9556, -282 ],
[ -49, 155.9556, -32 ]
]
# runners 2
[[geometry.pipes]]
index = 2
points_mm = [
[ 49, 155.9556, -282 ],
[ 49, 155.9556, -32 ]
]
# primaries 1
[[geometry.pipes]]
index = 3
points_mm = [
[ -49, 155.9556, 40 ],
[ -47.6414, 152.9696, 53.5851 ],
[ -43.8021, 148.472, 65.1692 ],
[ -37.8363, 142.566, 74.9076 ],
[ -30.0984, 135.3552, 82.9554 ],
[ -20.943, 126.9429, 89.4678 ],
[ -10.7243, 117.4325, 94.5999 ],
[ 0.2032, 106.9274, 98.5068 ],
[ 11.485, 95.5311, 101.3437 ],
[ 22.7669, 83.347, 103.2658 ],
[ 33.6944, 70.4784, 104.4283 ],
[ 43.913, 57.0288, 104.9862 ],
[ 53.0685, 43.1016, 105.0948 ],
[ 60.8063, 28.8002, 104.9091 ],
[ 66.7721, 14.2281, 104.5844 ],
[ 70.6115, -0.5115, 104.2758 ],
[ 71.9701, -15.315, 104.1385 ]
]
# primaries 2
[[geometry.pipes]]
index = 4
points_mm = [
[ 49, 155.9556, 40 ],
[ 49.465, 153.1235, 51.7218 ],
[ 50.7792, 148.49, 61.9229 ],
[ 52.8211, 142.2161, 70.709 ],
[ 55.4697, 134.4629, 78.1854 ],
[ 58.6034, 125.3916, 84.4576 ],
[ 62.1011, 115.1632, 89.631 ],
[ 65.8413, 103.9388, 93.8112 ],
[ 69.7029, 91.8796, 97.1037 ],
[ 73.5645, 79.1467, 99.6138 ],
[ 77.3048, 65.9012, 101.447 ],
[ 80.8024, 52.3041, 102.7088 ],
[ 83.9362, 38.5167, 103.5048 ],
[ 86.5847, 24.6999, 103.9402 ],
[ 88.6267, 11.015, 104.1207 ],
[ 89.9409, -2.377, 104.1516 ],
[ 90.4059, -15.315, 104.1385 ]
]
# tailpipe
[[geometry.pipes]]
index = 5
points_mm = [
[ 139.685, -55, 119 ],
[ 1639.685, -55, 119 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 155.9556, -354 ]
size_mm = [ 196, 70.8617, 144 ]
[[geometry.volumes]]
id = "exhaust_collector"
center_mm = [ 100, -55, 119 ]
size_mm = [ 79.3701, 79.3701, 79.3701 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "twin"
name = "600 cc parallel twin"
category = "Twins / parallel"
@@ -7,9 +7,11 @@ description = "EFI motorcycle baseline"
order = 0
cycle = "four_stroke"
[simulation]
cells_per_meter = 50.0
[engine]
bank_angle_deg = 56.0
collector_volume_litres = 0.15
cylinders = 2
initial_rpm = 2000.0
initialize_phased_charge = false
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 30.0
minimum_substeps = 1
phase_degrees = [0.0, 180.0]
pipe_damping = 1.0
plenum_volume_litres = 1.0
starter_torque_nm = 18.0
throttle_diameter_mm = 26.0
topology = "collector"
@@ -31,18 +32,12 @@ burn_duration_deg = 60.0
burn_start_deg = 5.0
compression = 11.5
energy_variation = 0.05
exhaust_close_deg = 390.0
exhaust_open_deg = 130.0
exhaust_valve_count = 1
exhaust_valve_diameter_mm = 26.0
exhaust_valve_lift_mm = 7.5
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 590.0
intake_open_deg = 340.0
intake_valve_count = 1
intake_valve_diameter_mm = 30.0
intake_valve_lift_mm = 8.0
misfire_probability = 0.0
rod_mm = 112.0
stroke_mm = 68.0
@@ -101,26 +96,208 @@ system = "injection"
[[pipes]]
id = "intake"
cells = 10
diameter_mm = 27.5
length_mm = 60.0
[[pipes]]
id = "runners"
count = 2
cells = 10
diameter_mm = 27.5
length_mm = 120.0
[[pipes]]
id = "primaries"
count = 2
cells = 6
diameter_mm = 28.0
length_mm = 200.0
[[pipes]]
id = "tailpipe"
cells = 10
diameter_mm = 28.0
length_mm = 400.0
[cam.intake]
points = [
[0.000, 0.000000],
[340.000, 0.000000],
[345.000, 0.145408],
[350.000, 0.548864],
[355.000, 1.161216],
[360.000, 1.933312],
[365.000, 2.816000],
[370.000, 3.760128],
[375.000, 4.716544],
[380.000, 5.636096],
[385.000, 6.469632],
[390.000, 7.168000],
[395.000, 7.682048],
[400.000, 7.962624],
[402.500, 8.000000],
[527.500, 8.000000],
[532.500, 7.854592],
[537.500, 7.451136],
[542.500, 6.838784],
[547.500, 6.066688],
[552.500, 5.184000],
[557.500, 4.239872],
[562.500, 3.283456],
[567.500, 2.363904],
[572.500, 1.530368],
[577.500, 0.832000],
[582.500, 0.317952],
[587.500, 0.037376],
[590.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[130.000, 0.000000],
[135.000, 0.126309],
[140.000, 0.477924],
[145.000, 1.013883],
[150.000, 1.693218],
[155.000, 2.474966],
[160.000, 3.318161],
[165.000, 4.181839],
[170.000, 5.025034],
[175.000, 5.806782],
[180.000, 6.486117],
[185.000, 7.022076],
[190.000, 7.373691],
[195.000, 7.500000],
[325.000, 7.500000],
[330.000, 7.373691],
[335.000, 7.022076],
[340.000, 6.486117],
[345.000, 5.806782],
[350.000, 5.025034],
[355.000, 4.181839],
[360.000, 3.318161],
[365.000, 2.474966],
[370.000, 1.693218],
[375.000, 1.013883],
[380.000, 0.477924],
[385.000, 0.126309],
[390.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ -46.5, 0, 0 ]
tilt_deg = 0.0
[[geometry.cylinders]]
origin_mm = [ 46.5, 0, 0 ]
tilt_deg = 0.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -153, 144.4762, -217.5 ],
[ -93, 144.4762, -217.5 ]
]
# runners 1
[[geometry.pipes]]
index = 1
points_mm = [
[ -46.5, 144.4762, -150 ],
[ -46.5, 144.4762, -30 ]
]
# runners 2
[[geometry.pipes]]
index = 2
points_mm = [
[ 46.5, 144.4762, -150 ],
[ 46.5, 144.4762, -30 ]
]
# primaries 1
[[geometry.pipes]]
index = 3
points_mm = [
[ -46.5, 144.4762, 37.5 ],
[ -45.0739, 141.4466, 51.4126 ],
[ -41.0434, 136.9192, 63.2857 ],
[ -34.7808, 130.9928, 73.2768 ],
[ -26.658, 123.7661, 81.5435 ],
[ -17.047, 115.3378, 88.2433 ],
[ -6.3199, 105.8067, 93.5338 ],
[ 5.1513, 95.2715, 97.5727 ],
[ 16.9945, 83.831, 100.5174 ],
[ 28.8377, 71.584, 102.5255 ],
[ 40.3089, 58.6292, 103.7547 ],
[ 51.036, 45.0653, 104.3624 ],
[ 60.6469, 30.9911, 104.5063 ],
[ 68.7698, 16.5054, 104.344 ],
[ 75.0324, 1.7069, 104.0329 ],
[ 79.0628, -13.3057, 103.7307 ],
[ 80.489, -28.4335, 103.5949 ]
]
# primaries 2
[[geometry.pipes]]
index = 4
points_mm = [
[ 46.5, 144.4762, 37.5 ],
[ 47.0208, 141.6097, 49.4384 ],
[ 48.4927, 136.9383, 59.8461 ],
[ 50.7797, 130.622, 68.8281 ],
[ 53.7461, 122.8206, 76.4893 ],
[ 57.2559, 113.6941, 82.9347 ],
[ 61.1733, 103.4022, 88.2691 ],
[ 65.3624, 92.1049, 92.5975 ],
[ 69.6874, 79.9621, 96.0248 ],
[ 74.0124, 67.1336, 98.6559 ],
[ 78.2015, 53.7794, 100.5958 ],
[ 82.1189, 40.0593, 101.9494 ],
[ 85.6288, 26.1331, 102.8216 ],
[ 88.5951, 12.1609, 103.3173 ],
[ 90.8821, -1.6975, 103.5415 ],
[ 92.354, -15.2823, 103.5991 ],
[ 92.8748, -28.4335, 103.5949 ]
]
# tailpipe
[[geometry.pipes]]
index = 5
points_mm = [
[ 126.5665, -55, 113.75 ],
[ 526.5663, -55, 113.75 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 144.4762, -217.5 ]
size_mm = [ 186, 39.8248, 135 ]
[[geometry.volumes]]
id = "exhaust_collector"
center_mm = [ 100, -55, 113.75 ]
size_mm = [ 53.1329, 53.1329, 53.1329 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "v10"
name = "5.0 litre / even-fire V10"
category = "V10"
@@ -7,9 +7,11 @@ description = "Separate five-cylinder banks / junction collectors / dual outlets
order = 30
cycle = "four_stroke"
[simulation]
cells_per_meter = 20.0
[engine]
bank_angle_deg = 72.0
collector_volume_litres = 2.5
cylinders = 10
initial_rpm = 1400.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 30.0
minimum_substeps = 1
phase_degrees = [0.0, 576.0, 432.0, 288.0, 144.0, 648.0001, 504.0, 360.0, 216.0, 72.00003]
pipe_damping = 0.0
plenum_volume_litres = 10.0
starter_torque_nm = 124.9751
throttle_diameter_mm = 65.0
topology = "staged"
@@ -31,18 +32,12 @@ burn_duration_deg = 60.0
burn_start_deg = 5.0
compression = 10.0
energy_variation = 0.05
exhaust_close_deg = 390.0
exhaust_open_deg = 130.0
exhaust_valve_count = 1
exhaust_valve_diameter_mm = 29.44
exhaust_valve_lift_mm = 8.280001
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 590.0
intake_open_deg = 340.0
intake_valve_count = 1
intake_valve_diameter_mm = 34.96
intake_valve_lift_mm = 9.2
misfire_probability = 0.0
rod_mm = 135.36
stroke_mm = 75.2
@@ -101,101 +96,607 @@ system = "injection"
[junctions]
h_pipe = true
junction_volume_cc = 50.0
[[pipes]]
id = "intake"
cells = 4
diameter_mm = 75.0
length_mm = 200.0
[[pipes]]
id = "runners"
count = 10
cells = 5
diameter_mm = 35.0
length_mm = 250.0
[[pipes]]
id = "primary_1"
cells = 11
diameter_mm = 38.0
length_mm = 520.0
[[pipes]]
id = "primary_2"
cells = 12
diameter_mm = 38.0
length_mm = 585.0
[[pipes]]
id = "primary_3"
cells = 13
diameter_mm = 38.0
length_mm = 650.0
[[pipes]]
id = "primary_4"
cells = 11
diameter_mm = 38.0
length_mm = 520.0
[[pipes]]
id = "primary_5"
cells = 12
diameter_mm = 38.0
length_mm = 585.0
[[pipes]]
id = "primary_6"
cells = 13
diameter_mm = 38.0
length_mm = 650.0
[[pipes]]
id = "primary_7"
cells = 11
diameter_mm = 38.0
length_mm = 520.0
[[pipes]]
id = "primary_8"
cells = 12
diameter_mm = 38.0
length_mm = 585.0
[[pipes]]
id = "primary_9"
cells = 13
diameter_mm = 38.0
length_mm = 650.0
[[pipes]]
id = "primary_10"
cells = 11
diameter_mm = 38.0
length_mm = 520.0
[[pipes]]
id = "tailpipe_21"
cells = 30
diameter_mm = 60.0
length_mm = 1500.0
[[pipes]]
id = "tailpipe_22"
cells = 28
diameter_mm = 60.0
length_mm = 1380.0
[[pipes]]
id = "secondary_23"
cells = 9
diameter_mm = 48.0
length_mm = 420.0
[[pipes]]
id = "secondary_24"
cells = 11
diameter_mm = 48.0
length_mm = 540.0
[cam.intake]
points = [
[0.000, 0.000000],
[340.000, 0.000000],
[345.000, 0.167219],
[350.000, 0.631194],
[355.000, 1.335398],
[360.000, 2.223309],
[365.000, 3.238400],
[370.000, 4.324147],
[375.000, 5.424026],
[380.000, 6.481510],
[385.000, 7.440077],
[390.000, 8.243200],
[395.000, 8.834355],
[400.000, 9.157018],
[402.500, 9.200000],
[527.500, 9.200000],
[532.500, 9.032781],
[537.500, 8.568806],
[542.500, 7.864602],
[547.500, 6.976691],
[552.500, 5.961600],
[557.500, 4.875853],
[562.500, 3.775974],
[567.500, 2.718490],
[572.500, 1.759923],
[577.500, 0.956800],
[582.500, 0.365645],
[587.500, 0.042982],
[590.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[130.000, 0.000000],
[135.000, 0.139445],
[140.000, 0.527629],
[145.000, 1.119326],
[150.000, 1.869313],
[155.000, 2.732363],
[160.000, 3.663250],
[165.000, 4.616751],
[170.000, 5.547638],
[175.000, 6.410688],
[180.000, 7.160675],
[185.000, 7.752372],
[190.000, 8.140556],
[195.000, 8.280001],
[325.000, 8.280001],
[330.000, 8.140556],
[335.000, 7.752372],
[340.000, 7.160675],
[345.000, 6.410688],
[350.000, 5.547638],
[355.000, 4.616751],
[360.000, 3.663250],
[365.000, 2.732363],
[370.000, 1.869313],
[375.000, 1.119326],
[380.000, 0.527629],
[385.000, 0.139445],
[390.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ -220, 0, 0 ]
tilt_deg = -36.0
[[geometry.cylinders]]
origin_mm = [ -110, 0, 0 ]
tilt_deg = -36.0
[[geometry.cylinders]]
origin_mm = [ 0, 0, 0 ]
tilt_deg = -36.0
[[geometry.cylinders]]
origin_mm = [ 110, 0, 0 ]
tilt_deg = -36.0
[[geometry.cylinders]]
origin_mm = [ 220, 0, 0 ]
tilt_deg = -36.0
[[geometry.cylinders]]
origin_mm = [ -220, 0, 0 ]
tilt_deg = 36.0
[[geometry.cylinders]]
origin_mm = [ -110, 0, 0 ]
tilt_deg = 36.0
[[geometry.cylinders]]
origin_mm = [ 0, 0, 0 ]
tilt_deg = 36.0
[[geometry.cylinders]]
origin_mm = [ 110, 0, 0 ]
tilt_deg = 36.0
[[geometry.cylinders]]
origin_mm = [ 220, 0, 0 ]
tilt_deg = 36.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -475, 450.3337, 0 ],
[ -275, 450.3337, 0 ]
]
# runners 1
[[geometry.pipes]]
index = 1
points_mm = [
[ -220, 411.8457, -72.1005 ],
[ -220, 161.8457, -72.1005 ]
]
# runners 2
[[geometry.pipes]]
index = 2
points_mm = [
[ -110, 411.8457, -72.1005 ],
[ -110, 161.8457, -72.1005 ]
]
# runners 3
[[geometry.pipes]]
index = 3
points_mm = [
[ 0, 411.8457, -72.1005 ],
[ 0, 161.8457, -72.1005 ]
]
# runners 4
[[geometry.pipes]]
index = 4
points_mm = [
[ 110, 411.8457, -72.1005 ],
[ 110, 161.8457, -72.1005 ]
]
# runners 5
[[geometry.pipes]]
index = 5
points_mm = [
[ 220, 411.8457, -72.1005 ],
[ 220, 161.8457, -72.1005 ]
]
# runners 6
[[geometry.pipes]]
index = 6
points_mm = [
[ -220, 411.8457, 72.1005 ],
[ -220, 161.8457, 72.1005 ]
]
# runners 7
[[geometry.pipes]]
index = 7
points_mm = [
[ -110, 411.8457, 72.1005 ],
[ -110, 161.8457, 72.1005 ]
]
# runners 8
[[geometry.pipes]]
index = 8
points_mm = [
[ 0, 411.8457, 72.1005 ],
[ 0, 161.8457, 72.1005 ]
]
# runners 9
[[geometry.pipes]]
index = 9
points_mm = [
[ 110, 411.8457, 72.1005 ],
[ 110, 161.8457, 72.1005 ]
]
# runners 10
[[geometry.pipes]]
index = 10
points_mm = [
[ 220, 411.8457, 72.1005 ],
[ 220, 161.8457, 72.1005 ]
]
# primary_1
[[geometry.pipes]]
index = 11
points_mm = [
[ -220, 113.1771, -139.0871 ],
[ -218.6901, 101.7204, -149.7241 ],
[ -214.8997, 90.254, -158.5286 ],
[ -208.8379, 78.8812, -165.6588 ],
[ -200.7136, 67.7054, -171.2728 ],
[ -190.7357, 56.8299, -175.5288 ],
[ -179.1134, 46.358, -178.5849 ],
[ -166.0556, 36.3931, -180.5993 ],
[ -151.7712, 27.0384, -181.7302 ],
[ -136.4693, 18.3972, -182.1357 ],
[ -120.3588, 10.573, -181.974 ],
[ -103.6488, 3.6691, -181.4032 ],
[ -86.5482, -2.2113, -180.5816 ],
[ -69.2661, -6.9648, -179.6672 ],
[ -52.0114, -10.4881, -178.8183 ],
[ -34.9931, -12.6778, -178.1929 ],
[ -18.4202, -13.4307, -177.9493 ]
]
# primary_2
[[geometry.pipes]]
index = 12
points_mm = [
[ -110, 113.1771, -139.0871 ],
[ -108.9293, 105.9052, -146.1577 ],
[ -105.9035, 98.9449, -152.2659 ],
[ -101.2019, 92.2234, -157.4816 ],
[ -95.1037, 85.6677, -161.8746 ],
[ -87.8884, 79.2051, -165.5148 ],
[ -79.8351, 72.7626, -168.472 ],
[ -71.2232, 66.2675, -170.816 ],
[ -62.332, 59.6468, -172.6168 ],
[ -53.4407, 52.8278, -173.9442 ],
[ -44.8288, 45.7375, -174.8679 ],
[ -36.7756, 38.3031, -175.458 ],
[ -29.5602, 30.4518, -175.7841 ],
[ -23.462, 22.1107, -175.9161 ],
[ -18.7604, 13.207, -175.924 ],
[ -15.7346, 3.6677, -175.8775 ],
[ -14.6639, -6.5798, -175.8465 ]
]
# primary_3
[[geometry.pipes]]
index = 13
points_mm = [
[ 0, 113.1771, -139.0871 ],
[ 0, 107.1157, -144.7979 ],
[ 0, 100.8007, -149.8969 ],
[ 0, 94.2457, -154.4176 ],
[ 0, 87.4644, -158.3936 ],
[ 0, 80.4706, -161.8586 ],
[ 0, 73.2778, -164.846 ],
[ 0, 65.8998, -167.3895 ],
[ 0, 58.3503, -169.5227 ],
[ 0, 50.6429, -171.2791 ],
[ 0, 42.7913, -172.6924 ],
[ 0, 34.8092, -173.7961 ],
[ 0, 26.7103, -174.6238 ],
[ 0, 18.5083, -175.2091 ],
[ 0, 10.2168, -175.5856 ],
[ 0, 1.8495, -175.7868 ],
[ 0, -6.5798, -175.8465 ]
]
# primary_4
[[geometry.pipes]]
index = 14
points_mm = [
[ 110, 113.1771, -139.0871 ],
[ 108.9293, 105.9052, -146.1577 ],
[ 105.9035, 98.9449, -152.2659 ],
[ 101.2019, 92.2234, -157.4816 ],
[ 95.1037, 85.6677, -161.8746 ],
[ 87.8884, 79.2051, -165.5148 ],
[ 79.8351, 72.7626, -168.472 ],
[ 71.2232, 66.2675, -170.816 ],
[ 62.332, 59.6468, -172.6168 ],
[ 53.4407, 52.8278, -173.9442 ],
[ 44.8288, 45.7375, -174.8679 ],
[ 36.7756, 38.3031, -175.458 ],
[ 29.5602, 30.4518, -175.7841 ],
[ 23.462, 22.1107, -175.9161 ],
[ 18.7604, 13.207, -175.924 ],
[ 15.7346, 3.6677, -175.8775 ],
[ 14.6639, -6.5798, -175.8465 ]
]
# primary_5
[[geometry.pipes]]
index = 15
points_mm = [
[ 220, 113.1771, -139.0871 ],
[ 218.6901, 101.7204, -149.7241 ],
[ 214.8997, 90.254, -158.5286 ],
[ 208.8379, 78.8812, -165.6588 ],
[ 200.7136, 67.7054, -171.2728 ],
[ 190.7357, 56.8299, -175.5288 ],
[ 179.1134, 46.358, -178.5849 ],
[ 166.0556, 36.3931, -180.5993 ],
[ 151.7712, 27.0384, -181.7302 ],
[ 136.4693, 18.3972, -182.1357 ],
[ 120.3588, 10.573, -181.974 ],
[ 103.6488, 3.6691, -181.4032 ],
[ 86.5483, -2.2113, -180.5816 ],
[ 69.2661, -6.9648, -179.6672 ],
[ 52.0114, -10.4881, -178.8183 ],
[ 34.9931, -12.6778, -178.1929 ],
[ 18.4202, -13.4307, -177.9493 ]
]
# primary_6
[[geometry.pipes]]
index = 16
points_mm = [
[ -220, 113.1771, 139.0871 ],
[ -218.6901, 101.7204, 149.7241 ],
[ -214.8997, 90.254, 158.5286 ],
[ -208.8379, 78.8812, 165.6588 ],
[ -200.7136, 67.7054, 171.2728 ],
[ -190.7357, 56.8299, 175.5288 ],
[ -179.1134, 46.358, 178.5849 ],
[ -166.0556, 36.3931, 180.5993 ],
[ -151.7712, 27.0384, 181.7302 ],
[ -136.4693, 18.3972, 182.1357 ],
[ -120.3588, 10.573, 181.974 ],
[ -103.6488, 3.6691, 181.4032 ],
[ -86.5482, -2.2113, 180.5816 ],
[ -69.2661, -6.9648, 179.6672 ],
[ -52.0114, -10.4881, 178.8183 ],
[ -34.9931, -12.6778, 178.1929 ],
[ -18.4202, -13.4307, 177.9493 ]
]
# primary_7
[[geometry.pipes]]
index = 17
points_mm = [
[ -110, 113.1771, 139.0871 ],
[ -108.9293, 105.9052, 146.1577 ],
[ -105.9035, 98.9449, 152.2659 ],
[ -101.2019, 92.2234, 157.4816 ],
[ -95.1037, 85.6677, 161.8746 ],
[ -87.8884, 79.2051, 165.5148 ],
[ -79.8351, 72.7626, 168.472 ],
[ -71.2232, 66.2675, 170.816 ],
[ -62.332, 59.6468, 172.6168 ],
[ -53.4407, 52.8278, 173.9442 ],
[ -44.8288, 45.7375, 174.8679 ],
[ -36.7756, 38.3031, 175.458 ],
[ -29.5602, 30.4518, 175.7841 ],
[ -23.462, 22.1107, 175.9161 ],
[ -18.7604, 13.207, 175.924 ],
[ -15.7346, 3.6677, 175.8775 ],
[ -14.6639, -6.5798, 175.8465 ]
]
# primary_8
[[geometry.pipes]]
index = 18
points_mm = [
[ 0, 113.1771, 139.0871 ],
[ 0, 107.1157, 144.7979 ],
[ 0, 100.8007, 149.8969 ],
[ 0, 94.2457, 154.4176 ],
[ 0, 87.4644, 158.3936 ],
[ 0, 80.4706, 161.8586 ],
[ 0, 73.2778, 164.846 ],
[ 0, 65.8998, 167.3895 ],
[ 0, 58.3503, 169.5227 ],
[ 0, 50.6429, 171.2791 ],
[ 0, 42.7913, 172.6924 ],
[ 0, 34.8092, 173.7961 ],
[ 0, 26.7103, 174.6238 ],
[ 0, 18.5083, 175.2091 ],
[ 0, 10.2168, 175.5856 ],
[ 0, 1.8495, 175.7868 ],
[ 0, -6.5798, 175.8465 ]
]
# primary_9
[[geometry.pipes]]
index = 19
points_mm = [
[ 110, 113.1771, 139.0871 ],
[ 108.9293, 105.9052, 146.1577 ],
[ 105.9035, 98.9449, 152.2659 ],
[ 101.2019, 92.2234, 157.4816 ],
[ 95.1037, 85.6677, 161.8746 ],
[ 87.8884, 79.2051, 165.5148 ],
[ 79.8351, 72.7626, 168.472 ],
[ 71.2232, 66.2675, 170.816 ],
[ 62.332, 59.6468, 172.6168 ],
[ 53.4407, 52.8278, 173.9442 ],
[ 44.8288, 45.7375, 174.8679 ],
[ 36.7756, 38.3031, 175.458 ],
[ 29.5602, 30.4518, 175.7841 ],
[ 23.462, 22.1107, 175.9161 ],
[ 18.7604, 13.207, 175.924 ],
[ 15.7346, 3.6677, 175.8775 ],
[ 14.6639, -6.5798, 175.8465 ]
]
# primary_10
[[geometry.pipes]]
index = 20
points_mm = [
[ 220, 113.1771, 139.0871 ],
[ 218.6901, 101.7204, 149.7241 ],
[ 214.8997, 90.254, 158.5286 ],
[ 208.8379, 78.8812, 165.6588 ],
[ 200.7136, 67.7054, 171.2728 ],
[ 190.7357, 56.8299, 175.5288 ],
[ 179.1134, 46.358, 178.5849 ],
[ 166.0556, 36.3931, 180.5993 ],
[ 151.7712, 27.0384, 181.7302 ],
[ 136.4693, 18.3972, 182.1357 ],
[ 120.3588, 10.573, 181.974 ],
[ 103.6488, 3.6691, 181.4032 ],
[ 86.5483, -2.2113, 180.5816 ],
[ 69.2661, -6.9648, 179.6672 ],
[ 52.0114, -10.4881, 178.8183 ],
[ 34.9931, -12.6778, 178.1929 ],
[ 18.4202, -13.4307, 177.9493 ]
]
# tailpipe_21
[[geometry.pipes]]
index = 21
points_mm = [
[ 218.4202, -70, -181.5005 ],
[ 1718.4201, -70, -181.5005 ]
]
# tailpipe_22
[[geometry.pipes]]
index = 22
points_mm = [
[ 218.4202, -70, 181.5005 ],
[ 1598.4201, -70, 181.5005 ]
]
# secondary_23
[[geometry.pipes]]
index = 23
points_mm = [
[ 18.4202, -29.1445, -164.7842 ],
[ 29.5589, -29.5568, -164.9719 ],
[ 40.3211, -30.722, -165.5024 ],
[ 50.7606, -32.5324, -166.3268 ],
[ 60.9312, -34.8806, -167.3961 ],
[ 70.8866, -37.659, -168.6613 ],
[ 80.6807, -40.7601, -170.0733 ],
[ 90.3673, -44.0763, -171.5833 ],
[ 100, -47.5, -173.1423 ],
[ 109.6328, -50.9237, -174.7013 ],
[ 119.3193, -54.2399, -176.2114 ],
[ 129.1134, -57.341, -177.6234 ],
[ 139.0688, -60.1194, -178.8886 ],
[ 149.2394, -62.4676, -179.9578 ],
[ 159.6789, -64.278, -180.7822 ],
[ 170.4412, -65.4432, -181.3128 ],
[ 181.5798, -65.8555, -181.5005 ]
]
# secondary_24
[[geometry.pipes]]
index = 24
points_mm = [
[ 18.4202, -29.1445, 164.7842 ],
[ 29.5589, -29.5568, 164.9719 ],
[ 40.3211, -30.722, 165.5024 ],
[ 50.7606, -32.5324, 166.3268 ],
[ 60.9312, -34.8806, 167.3961 ],
[ 70.8866, -37.659, 168.6613 ],
[ 80.6807, -40.7601, 170.0733 ],
[ 90.3673, -44.0763, 171.5833 ],
[ 100, -47.5, 173.1423 ],
[ 109.6328, -50.9237, 174.7013 ],
[ 119.3193, -54.2399, 176.2114 ],
[ 129.1134, -57.341, 177.6234 ],
[ 139.0688, -60.1194, 178.8886 ],
[ 149.2394, -62.4676, 179.9578 ],
[ 159.6789, -64.278, 180.7822 ],
[ 170.4412, -65.4432, 181.3128 ],
[ 181.5798, -65.8555, 181.5005 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 450.3337, 0 ]
size_mm = [ 550, 76.976, 236.201 ]
[[geometry.volumes]]
id = "junction_0"
center_mm = [ 0, -25, -181.5005 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_1"
center_mm = [ 0, -25, 181.5005 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_2"
center_mm = [ 200, -70, -181.5005 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_3"
center_mm = [ 200, -70, 181.5005 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "v12"
name = "250-inspired / 3.0 V12"
category = "V12"
@@ -7,9 +7,11 @@ description = "Separate six-cylinder banks / junction collectors / dual outlets"
order = 31
cycle = "four_stroke"
[simulation]
cells_per_meter = 20.0
[engine]
bank_angle_deg = 60.0
collector_volume_litres = 3.0
cylinders = 12
initial_rpm = 1400.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 30.0
minimum_substeps = 1
phase_degrees = [0.0, 600.0, 480.0, 360.0, 240.0, 120.0, 660.0, 540.0, 420.0, 300.0, 180.0, 59.99998]
pipe_damping = 0.0
plenum_volume_litres = 12.0
starter_torque_nm = 73.83022
throttle_diameter_mm = 65.0
topology = "staged"
@@ -31,18 +32,12 @@ burn_duration_deg = 60.0
burn_start_deg = 5.0
compression = 10.0
energy_variation = 0.05
exhaust_close_deg = 390.0
exhaust_open_deg = 130.0
exhaust_valve_count = 1
exhaust_valve_diameter_mm = 23.36
exhaust_valve_lift_mm = 6.57
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 590.0
intake_open_deg = 340.0
intake_valve_count = 1
intake_valve_diameter_mm = 27.74
intake_valve_lift_mm = 7.3
misfire_probability = 0.0
rod_mm = 110.0
stroke_mm = 58.8
@@ -101,113 +96,685 @@ system = "injection"
[junctions]
h_pipe = true
junction_volume_cc = 50.0
[[pipes]]
id = "intake"
cells = 4
diameter_mm = 75.0
length_mm = 200.0
[[pipes]]
id = "runners"
count = 12
cells = 5
diameter_mm = 35.0
length_mm = 250.0
[[pipes]]
id = "primary_1"
cells = 11
diameter_mm = 38.0
length_mm = 520.0
[[pipes]]
id = "primary_2"
cells = 12
diameter_mm = 38.0
length_mm = 585.0
[[pipes]]
id = "primary_3"
cells = 13
diameter_mm = 38.0
length_mm = 650.0
[[pipes]]
id = "primary_4"
cells = 11
diameter_mm = 38.0
length_mm = 520.0
[[pipes]]
id = "primary_5"
cells = 12
diameter_mm = 38.0
length_mm = 585.0
[[pipes]]
id = "primary_6"
cells = 13
diameter_mm = 38.0
length_mm = 650.0
[[pipes]]
id = "primary_7"
cells = 11
diameter_mm = 38.0
length_mm = 520.0
[[pipes]]
id = "primary_8"
cells = 12
diameter_mm = 38.0
length_mm = 585.0
[[pipes]]
id = "primary_9"
cells = 13
diameter_mm = 38.0
length_mm = 650.0
[[pipes]]
id = "primary_10"
cells = 11
diameter_mm = 38.0
length_mm = 520.0
[[pipes]]
id = "primary_11"
cells = 12
diameter_mm = 38.0
length_mm = 585.0
[[pipes]]
id = "primary_12"
cells = 13
diameter_mm = 38.0
length_mm = 650.0
[[pipes]]
id = "tailpipe_25"
cells = 30
diameter_mm = 60.0
length_mm = 1500.0
[[pipes]]
id = "tailpipe_26"
cells = 28
diameter_mm = 60.0
length_mm = 1380.0
[[pipes]]
id = "secondary_27"
cells = 9
diameter_mm = 48.0
length_mm = 420.0
[[pipes]]
id = "secondary_28"
cells = 11
diameter_mm = 48.0
length_mm = 540.0
[cam.intake]
points = [
[0.000, 0.000000],
[340.000, 0.000000],
[345.000, 0.132685],
[350.000, 0.500838],
[355.000, 1.059610],
[360.000, 1.764147],
[365.000, 2.569600],
[370.000, 3.431117],
[375.000, 4.303846],
[380.000, 5.142938],
[385.000, 5.903539],
[390.000, 6.540800],
[395.000, 7.009869],
[400.000, 7.265894],
[402.500, 7.300000],
[527.500, 7.300000],
[532.500, 7.167315],
[537.500, 6.799162],
[542.500, 6.240390],
[547.500, 5.535853],
[552.500, 4.730400],
[557.500, 3.868883],
[562.500, 2.996154],
[567.500, 2.157062],
[572.500, 1.396461],
[577.500, 0.759200],
[582.500, 0.290131],
[587.500, 0.034106],
[590.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[130.000, 0.000000],
[135.000, 0.110646],
[140.000, 0.418662],
[145.000, 0.888161],
[150.000, 1.483259],
[155.000, 2.168070],
[160.000, 2.906709],
[165.000, 3.663291],
[170.000, 4.401930],
[175.000, 5.086741],
[180.000, 5.681839],
[185.000, 6.151338],
[190.000, 6.459354],
[195.000, 6.570000],
[325.000, 6.570000],
[330.000, 6.459354],
[335.000, 6.151338],
[340.000, 5.681839],
[345.000, 5.086741],
[350.000, 4.401930],
[355.000, 3.663291],
[360.000, 2.906709],
[365.000, 2.168070],
[370.000, 1.483259],
[375.000, 0.888161],
[380.000, 0.418662],
[385.000, 0.110646],
[390.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ -227.5, 0, 0 ]
tilt_deg = -30.0
[[geometry.cylinders]]
origin_mm = [ -136.5, 0, 0 ]
tilt_deg = -30.0
[[geometry.cylinders]]
origin_mm = [ -45.5, 0, 0 ]
tilt_deg = -30.0
[[geometry.cylinders]]
origin_mm = [ 45.5, 0, 0 ]
tilt_deg = -30.0
[[geometry.cylinders]]
origin_mm = [ 136.5, 0, 0 ]
tilt_deg = -30.0
[[geometry.cylinders]]
origin_mm = [ 227.5, 0, 0 ]
tilt_deg = -30.0
[[geometry.cylinders]]
origin_mm = [ -227.5, 0, 0 ]
tilt_deg = 30.0
[[geometry.cylinders]]
origin_mm = [ -136.5, 0, 0 ]
tilt_deg = 30.0
[[geometry.cylinders]]
origin_mm = [ -45.5, 0, 0 ]
tilt_deg = 30.0
[[geometry.cylinders]]
origin_mm = [ 45.5, 0, 0 ]
tilt_deg = 30.0
[[geometry.cylinders]]
origin_mm = [ 136.5, 0, 0 ]
tilt_deg = 30.0
[[geometry.cylinders]]
origin_mm = [ 227.5, 0, 0 ]
tilt_deg = 30.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -473, 452.582, 0 ],
[ -273, 452.582, 0 ]
]
# runners 1
[[geometry.pipes]]
index = 1
points_mm = [
[ -227.5, 384.0538, -43.6787 ],
[ -227.5, 134.0538, -43.6787 ]
]
# runners 2
[[geometry.pipes]]
index = 2
points_mm = [
[ -136.5, 384.0538, -43.6787 ],
[ -136.5, 134.0538, -43.6787 ]
]
# runners 3
[[geometry.pipes]]
index = 3
points_mm = [
[ -45.5, 384.0538, -43.6787 ],
[ -45.5, 134.0538, -43.6787 ]
]
# runners 4
[[geometry.pipes]]
index = 4
points_mm = [
[ 45.5, 384.0538, -43.6787 ],
[ 45.5, 134.0538, -43.6787 ]
]
# runners 5
[[geometry.pipes]]
index = 5
points_mm = [
[ 136.5, 384.0538, -43.6787 ],
[ 136.5, 134.0538, -43.6787 ]
]
# runners 6
[[geometry.pipes]]
index = 6
points_mm = [
[ 227.5, 384.0538, -43.6787 ],
[ 227.5, 134.0538, -43.6787 ]
]
# runners 7
[[geometry.pipes]]
index = 7
points_mm = [
[ -227.5, 384.0538, 43.6787 ],
[ -227.5, 134.0538, 43.6787 ]
]
# runners 8
[[geometry.pipes]]
index = 8
points_mm = [
[ -136.5, 384.0538, 43.6787 ],
[ -136.5, 134.0538, 43.6787 ]
]
# runners 9
[[geometry.pipes]]
index = 9
points_mm = [
[ -45.5, 384.0538, 43.6787 ],
[ -45.5, 134.0538, 43.6787 ]
]
# runners 10
[[geometry.pipes]]
index = 10
points_mm = [
[ 45.5, 384.0538, 43.6787 ],
[ 45.5, 134.0538, 43.6787 ]
]
# runners 11
[[geometry.pipes]]
index = 11
points_mm = [
[ 136.5, 384.0538, 43.6787 ],
[ 136.5, 134.0538, 43.6787 ]
]
# runners 12
[[geometry.pipes]]
index = 12
points_mm = [
[ 227.5, 384.0538, 43.6787 ],
[ 227.5, 134.0538, 43.6787 ]
]
# primary_1
[[geometry.pipes]]
index = 13
points_mm = [
[ -227.5, 101.2038, -100.5766 ],
[ -226.1083, 90.9653, -112.2028 ],
[ -222.0863, 80.6518, -121.6751 ],
[ -215.6643, 70.3646, -129.1873 ],
[ -207.0722, 60.2053, -134.9332 ],
[ -196.5399, 50.2753, -139.1062 ],
[ -184.2975, 40.6763, -141.9002 ],
[ -170.575, 31.5097, -143.5089 ],
[ -155.6025, 22.877, -144.1259 ],
[ -139.6098, 14.8798, -143.9449 ],
[ -122.8271, 7.6195, -143.1596 ],
[ -105.4844, 1.1977, -141.9638 ],
[ -87.8116, -4.2841, -140.551 ],
[ -70.0387, -8.7245, -139.115 ],
[ -52.3959, -12.0219, -137.8495 ],
[ -35.113, -14.0747, -136.9481 ],
[ -18.4202, -14.7816, -136.6046 ]
]
# primary_2
[[geometry.pipes]]
index = 14
points_mm = [
[ -136.5, 101.2038, -100.5766 ],
[ -135.8239, 93.9039, -108.5849 ],
[ -133.853, 85.9308, -115.3225 ],
[ -130.6732, 77.4423, -120.8934 ],
[ -126.3704, 68.5964, -125.4017 ],
[ -121.0305, 59.551, -128.9513 ],
[ -114.7394, 50.4638, -131.6465 ],
[ -107.5832, 41.4928, -133.5911 ],
[ -99.6476, 32.7959, -134.8893 ],
[ -91.0187, 24.5308, -135.6451 ],
[ -81.7824, 16.8556, -135.9625 ],
[ -72.0245, 9.928, -135.9457 ],
[ -61.8311, 3.906, -135.6986 ],
[ -51.288, -1.0526, -135.3253 ],
[ -40.4812, -4.79, -134.9298 ],
[ -29.4966, -7.1481, -134.6162 ],
[ -18.4202, -7.9693, -134.4886 ]
]
# primary_3
[[geometry.pipes]]
index = 15
points_mm = [
[ -45.5, 101.2038, -100.5766 ],
[ -45.0636, 96.0541, -106.4923 ],
[ -43.8303, 90.6495, -111.6666 ],
[ -41.9139, 84.9992, -116.1491 ],
[ -39.4283, 79.1125, -119.9895 ],
[ -36.4873, 72.9988, -123.2374 ],
[ -33.2048, 66.6673, -125.9425 ],
[ -29.6945, 60.1273, -128.1544 ],
[ -26.0705, 53.3882, -129.9227 ],
[ -22.4464, 46.4591, -131.297 ],
[ -18.9362, 39.3495, -132.3271 ],
[ -15.6537, 32.0686, -133.0625 ],
[ -12.7127, 24.6257, -133.5529 ],
[ -10.2271, 17.0302, -133.8479 ],
[ -8.3107, 9.2912, -133.9971 ],
[ -7.0774, 1.4181, -134.0503 ],
[ -6.641, -6.5798, -134.057 ]
]
# primary_4
[[geometry.pipes]]
index = 16
points_mm = [
[ 45.5, 101.2038, -100.5766 ],
[ 45.0636, 96.0541, -106.4923 ],
[ 43.8303, 90.6495, -111.6666 ],
[ 41.9139, 84.9992, -116.1491 ],
[ 39.4283, 79.1125, -119.9895 ],
[ 36.4873, 72.9988, -123.2374 ],
[ 33.2048, 66.6673, -125.9425 ],
[ 29.6945, 60.1273, -128.1544 ],
[ 26.0705, 53.3882, -129.9227 ],
[ 22.4464, 46.4591, -131.297 ],
[ 18.9362, 39.3495, -132.3271 ],
[ 15.6537, 32.0686, -133.0625 ],
[ 12.7127, 24.6257, -133.5529 ],
[ 10.2271, 17.0302, -133.8479 ],
[ 8.3107, 9.2912, -133.9971 ],
[ 7.0774, 1.4181, -134.0503 ],
[ 6.641, -6.5798, -134.057 ]
]
# primary_5
[[geometry.pipes]]
index = 17
points_mm = [
[ 136.5, 101.2038, -100.5766 ],
[ 135.8239, 93.9039, -108.5849 ],
[ 133.853, 85.9308, -115.3225 ],
[ 130.6732, 77.4423, -120.8934 ],
[ 126.3704, 68.5964, -125.4017 ],
[ 121.0305, 59.551, -128.9513 ],
[ 114.7394, 50.4638, -131.6465 ],
[ 107.5832, 41.4928, -133.5911 ],
[ 99.6476, 32.7959, -134.8893 ],
[ 91.0187, 24.5308, -135.6451 ],
[ 81.7824, 16.8556, -135.9625 ],
[ 72.0245, 9.928, -135.9457 ],
[ 61.8311, 3.906, -135.6986 ],
[ 51.288, -1.0526, -135.3253 ],
[ 40.4812, -4.79, -134.9298 ],
[ 29.4966, -7.1481, -134.6162 ],
[ 18.4202, -7.9693, -134.4886 ]
]
# primary_6
[[geometry.pipes]]
index = 18
points_mm = [
[ 227.5, 101.2038, -100.5766 ],
[ 226.1083, 90.9653, -112.2028 ],
[ 222.0863, 80.6518, -121.6751 ],
[ 215.6643, 70.3646, -129.1873 ],
[ 207.0722, 60.2053, -134.9332 ],
[ 196.5399, 50.2753, -139.1062 ],
[ 184.2975, 40.6763, -141.9002 ],
[ 170.575, 31.5097, -143.5089 ],
[ 155.6025, 22.877, -144.1259 ],
[ 139.6098, 14.8798, -143.9449 ],
[ 122.8271, 7.6195, -143.1596 ],
[ 105.4844, 1.1977, -141.9638 ],
[ 87.8116, -4.2841, -140.551 ],
[ 70.0387, -8.7245, -139.115 ],
[ 52.3959, -12.0219, -137.8495 ],
[ 35.113, -14.0747, -136.9481 ],
[ 18.4202, -14.7816, -136.6046 ]
]
# primary_7
[[geometry.pipes]]
index = 19
points_mm = [
[ -227.5, 101.2038, 100.5766 ],
[ -226.1083, 90.9653, 112.2028 ],
[ -222.0863, 80.6518, 121.6751 ],
[ -215.6643, 70.3646, 129.1873 ],
[ -207.0722, 60.2053, 134.9332 ],
[ -196.5399, 50.2753, 139.1062 ],
[ -184.2975, 40.6763, 141.9002 ],
[ -170.575, 31.5097, 143.5089 ],
[ -155.6025, 22.877, 144.1259 ],
[ -139.6098, 14.8798, 143.9449 ],
[ -122.8271, 7.6195, 143.1596 ],
[ -105.4844, 1.1977, 141.9638 ],
[ -87.8116, -4.2841, 140.551 ],
[ -70.0387, -8.7245, 139.115 ],
[ -52.3959, -12.0219, 137.8495 ],
[ -35.113, -14.0747, 136.9481 ],
[ -18.4202, -14.7816, 136.6046 ]
]
# primary_8
[[geometry.pipes]]
index = 20
points_mm = [
[ -136.5, 101.2038, 100.5766 ],
[ -135.8239, 93.9039, 108.5849 ],
[ -133.853, 85.9308, 115.3225 ],
[ -130.6732, 77.4423, 120.8934 ],
[ -126.3704, 68.5964, 125.4017 ],
[ -121.0305, 59.551, 128.9513 ],
[ -114.7394, 50.4638, 131.6465 ],
[ -107.5832, 41.4928, 133.5911 ],
[ -99.6476, 32.7959, 134.8893 ],
[ -91.0187, 24.5308, 135.6451 ],
[ -81.7824, 16.8556, 135.9625 ],
[ -72.0245, 9.928, 135.9457 ],
[ -61.8311, 3.906, 135.6986 ],
[ -51.288, -1.0526, 135.3253 ],
[ -40.4812, -4.79, 134.9298 ],
[ -29.4966, -7.1481, 134.6162 ],
[ -18.4202, -7.9693, 134.4886 ]
]
# primary_9
[[geometry.pipes]]
index = 21
points_mm = [
[ -45.5, 101.2038, 100.5766 ],
[ -45.0636, 96.0541, 106.4923 ],
[ -43.8303, 90.6495, 111.6666 ],
[ -41.9139, 84.9992, 116.1491 ],
[ -39.4283, 79.1125, 119.9895 ],
[ -36.4873, 72.9988, 123.2374 ],
[ -33.2048, 66.6673, 125.9425 ],
[ -29.6945, 60.1273, 128.1544 ],
[ -26.0705, 53.3882, 129.9227 ],
[ -22.4464, 46.4591, 131.297 ],
[ -18.9362, 39.3495, 132.3271 ],
[ -15.6537, 32.0686, 133.0625 ],
[ -12.7127, 24.6257, 133.5529 ],
[ -10.2271, 17.0302, 133.8479 ],
[ -8.3107, 9.2912, 133.9971 ],
[ -7.0774, 1.4181, 134.0503 ],
[ -6.641, -6.5798, 134.057 ]
]
# primary_10
[[geometry.pipes]]
index = 22
points_mm = [
[ 45.5, 101.2038, 100.5766 ],
[ 45.0636, 96.0541, 106.4923 ],
[ 43.8303, 90.6495, 111.6666 ],
[ 41.9139, 84.9992, 116.1491 ],
[ 39.4283, 79.1125, 119.9895 ],
[ 36.4873, 72.9988, 123.2374 ],
[ 33.2048, 66.6673, 125.9425 ],
[ 29.6945, 60.1273, 128.1544 ],
[ 26.0705, 53.3882, 129.9227 ],
[ 22.4464, 46.4591, 131.297 ],
[ 18.9362, 39.3495, 132.3271 ],
[ 15.6537, 32.0686, 133.0625 ],
[ 12.7127, 24.6257, 133.5529 ],
[ 10.2271, 17.0302, 133.8479 ],
[ 8.3107, 9.2912, 133.9971 ],
[ 7.0774, 1.4181, 134.0503 ],
[ 6.641, -6.5798, 134.057 ]
]
# primary_11
[[geometry.pipes]]
index = 23
points_mm = [
[ 136.5, 101.2038, 100.5766 ],
[ 135.8239, 93.9039, 108.5849 ],
[ 133.853, 85.9308, 115.3225 ],
[ 130.6732, 77.4423, 120.8934 ],
[ 126.3704, 68.5964, 125.4017 ],
[ 121.0305, 59.551, 128.9513 ],
[ 114.7394, 50.4638, 131.6465 ],
[ 107.5832, 41.4928, 133.5911 ],
[ 99.6476, 32.7959, 134.8893 ],
[ 91.0187, 24.5308, 135.6451 ],
[ 81.7824, 16.8556, 135.9625 ],
[ 72.0245, 9.928, 135.9457 ],
[ 61.8311, 3.906, 135.6986 ],
[ 51.288, -1.0526, 135.3253 ],
[ 40.4812, -4.79, 134.9298 ],
[ 29.4966, -7.1481, 134.6162 ],
[ 18.4202, -7.9693, 134.4886 ]
]
# primary_12
[[geometry.pipes]]
index = 24
points_mm = [
[ 227.5, 101.2038, 100.5766 ],
[ 226.1083, 90.9653, 112.2028 ],
[ 222.0863, 80.6518, 121.6751 ],
[ 215.6643, 70.3646, 129.1873 ],
[ 207.0722, 60.2053, 134.9332 ],
[ 196.5399, 50.2753, 139.1062 ],
[ 184.2975, 40.6763, 141.9002 ],
[ 170.575, 31.5097, 143.5089 ],
[ 155.6025, 22.877, 144.1259 ],
[ 139.6098, 14.8798, 143.9449 ],
[ 122.8271, 7.6195, 143.1596 ],
[ 105.4844, 1.1977, 141.9638 ],
[ 87.8116, -4.2841, 140.551 ],
[ 70.0387, -8.7245, 139.115 ],
[ 52.3959, -12.0219, 137.8495 ],
[ 35.113, -14.0747, 136.9481 ],
[ 18.4202, -14.7816, 136.6046 ]
]
# tailpipe_25
[[geometry.pipes]]
index = 25
points_mm = [
[ 218.4202, -70, -139.7787 ],
[ 1718.4201, -70, -139.7787 ]
]
# tailpipe_26
[[geometry.pipes]]
index = 26
points_mm = [
[ 218.4202, -70, 139.7787 ],
[ 1598.4201, -70, 139.7787 ]
]
# secondary_27
[[geometry.pipes]]
index = 27
points_mm = [
[ 18.4202, -29.1445, -126.905 ],
[ 29.5401, -29.5568, -127.0496 ],
[ 40.2911, -30.722, -127.4582 ],
[ 50.7257, -32.5324, -128.0931 ],
[ 60.8969, -34.8806, -128.9165 ],
[ 70.8571, -37.659, -129.8909 ],
[ 80.6593, -40.7601, -130.9783 ],
[ 90.356, -44.0763, -132.1412 ],
[ 100, -47.5, -133.3419 ],
[ 109.644, -50.9237, -134.5425 ],
[ 119.3407, -54.2399, -135.7054 ],
[ 129.1429, -57.341, -136.7929 ],
[ 139.1031, -60.1194, -137.7672 ],
[ 149.2742, -62.4676, -138.5907 ],
[ 159.7089, -64.278, -139.2256 ],
[ 170.4599, -65.4432, -139.6342 ],
[ 181.5798, -65.8555, -139.7787 ]
]
# secondary_28
[[geometry.pipes]]
index = 28
points_mm = [
[ 18.4202, -29.1445, 126.905 ],
[ 29.5401, -29.5568, 127.0496 ],
[ 40.2911, -30.722, 127.4582 ],
[ 50.7257, -32.5324, 128.0931 ],
[ 60.8969, -34.8806, 128.9165 ],
[ 70.8571, -37.659, 129.8909 ],
[ 80.6593, -40.7601, 130.9783 ],
[ 90.356, -44.0763, 132.1412 ],
[ 100, -47.5, 133.3419 ],
[ 109.644, -50.9237, 134.5425 ],
[ 119.3407, -54.2399, 135.7054 ],
[ 129.1429, -57.341, 136.7929 ],
[ 139.1031, -60.1194, 137.7672 ],
[ 149.2742, -62.4676, 138.5907 ],
[ 159.7089, -64.278, 139.2256 ],
[ 170.4599, -65.4432, 139.6342 ],
[ 181.5798, -65.8555, 139.7787 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 452.582, 0 ]
size_mm = [ 546, 137.0564, 160.3575 ]
[[geometry.volumes]]
id = "junction_0"
center_mm = [ 0, -25, -139.7787 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_1"
center_mm = [ 0, -25, 139.7787 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_2"
center_mm = [ 200, -70, -139.7787 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_3"
center_mm = [ 200, -70, 139.7787 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]

View File

@@ -1,15 +1,17 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "v8-dual"
name = "American 5.0 / independent banks"
category = "V8 / American 5.0"
description = "Same 4-2-1 headers with H crossover disconnected"
description = "Swept primaries / independent dual outlets"
order = 8
cycle = "four_stroke"
[simulation]
cells_per_meter = 20.0
[engine]
bank_angle_deg = 56.0
collector_volume_litres = 1.5
cylinders = 8
initial_rpm = 1000.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 300.0
minimum_substeps = 1
phase_degrees = [0.0, 450.0, 270.0, 540.0, 630.0, 360.0, 180.0, 89.99998]
pipe_damping = 1.0
plenum_volume_litres = 4.0
starter_torque_nm = 180.0
throttle_diameter_mm = 65.0
topology = "american_v8"
@@ -31,18 +32,12 @@ burn_duration_deg = 65.0
burn_start_deg = 5.0
compression = 7.0
energy_variation = 0.05
exhaust_close_deg = 370.0
exhaust_open_deg = 140.0
exhaust_valve_count = 1
exhaust_valve_diameter_mm = 37.0
exhaust_valve_lift_mm = 10.0
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 570.0
intake_open_deg = 350.0
intake_valve_count = 1
intake_valve_diameter_mm = 45.0
intake_valve_lift_mm = 10.0
misfire_probability = 0.0
rod_mm = 129.0
stroke_mm = 76.2
@@ -101,51 +96,609 @@ system = "injection"
[junctions]
h_pipe = false
junction_volume_cc = 50.0
[[pipes]]
id = "intake"
cells = 3
diameter_mm = 70.0
length_mm = 180.0
[[pipes]]
id = "runners"
count = 8
cells = 2
diameter_mm = 38.0
length_mm = 120.0
[[pipes]]
id = "primaries"
count = 8
cells = 15
diameter_mm = 38.0
length_mm = 750.0
[[pipes]]
id = "secondaries"
count = 4
cells = 8
diameter_mm = 45.0
length_mm = 400.0
[[pipes]]
id = "bank_collectors"
count = 2
cells = 11
diameter_mm = 50.0
length_mm = 550.0
[[pipes]]
id = "tailpipes"
count = 2
cells = 25
diameter_mm = 50.0
length_mm = 1250.0
[[pipes]]
id = "crossover"
cells = 16
diameter_mm = 38.0
length_mm = 780.0
[cam.intake]
points = [
[0.000, 0.000000],
[350.000, 0.000000],
[355.000, 0.232908],
[360.000, 0.871525],
[365.000, 1.825695],
[370.000, 3.005259],
[375.000, 4.320060],
[380.000, 5.679940],
[385.000, 6.994741],
[390.000, 8.174305],
[395.000, 9.128475],
[400.000, 9.767092],
[405.000, 10.000000],
[515.000, 10.000000],
[520.000, 9.767092],
[525.000, 9.128475],
[530.000, 8.174305],
[535.000, 6.994741],
[540.000, 5.679940],
[545.000, 4.320060],
[550.000, 3.005259],
[555.000, 1.825695],
[560.000, 0.871525],
[565.000, 0.232908],
[570.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[140.000, 0.000000],
[145.000, 0.213693],
[150.000, 0.802170],
[155.000, 1.686529],
[160.000, 2.787869],
[165.000, 4.027287],
[170.000, 5.325881],
[175.000, 6.604751],
[180.000, 7.784992],
[185.000, 8.787704],
[190.000, 9.533985],
[195.000, 9.944933],
[197.500, 10.000000],
[312.500, 10.000000],
[317.500, 9.786307],
[322.500, 9.197830],
[327.500, 8.313471],
[332.500, 7.212131],
[337.500, 5.972713],
[342.500, 4.674119],
[347.500, 3.395249],
[352.500, 2.215008],
[357.500, 1.212296],
[362.500, 0.466015],
[367.500, 0.055067],
[370.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ -179.4, 0, 0 ]
tilt_deg = -28.0
[[geometry.cylinders]]
origin_mm = [ -59.8, 0, 0 ]
tilt_deg = -28.0
[[geometry.cylinders]]
origin_mm = [ 59.8, 0, 0 ]
tilt_deg = -28.0
[[geometry.cylinders]]
origin_mm = [ 179.4, 0, 0 ]
tilt_deg = -28.0
[[geometry.cylinders]]
origin_mm = [ -179.4, 0, 0 ]
tilt_deg = 28.0
[[geometry.cylinders]]
origin_mm = [ -59.8, 0, 0 ]
tilt_deg = 28.0
[[geometry.cylinders]]
origin_mm = [ 59.8, 0, 0 ]
tilt_deg = 28.0
[[geometry.cylinders]]
origin_mm = [ 179.4, 0, 0 ]
tilt_deg = 28.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -419.2, 312.6411, 0 ],
[ -239.2, 312.6411, 0 ]
]
# runners 1
[[geometry.pipes]]
index = 1
points_mm = [
[ -179.4, 290.7697, -44.7722 ],
[ -179.4, 170.7697, -44.7722 ]
]
# runners 2
[[geometry.pipes]]
index = 2
points_mm = [
[ -59.8, 290.7697, -44.7722 ],
[ -59.8, 170.7697, -44.7722 ]
]
# runners 3
[[geometry.pipes]]
index = 3
points_mm = [
[ 59.8, 290.7697, -44.7722 ],
[ 59.8, 170.7697, -44.7722 ]
]
# runners 4
[[geometry.pipes]]
index = 4
points_mm = [
[ 179.4, 290.7697, -44.7722 ],
[ 179.4, 170.7697, -44.7722 ]
]
# runners 5
[[geometry.pipes]]
index = 5
points_mm = [
[ -179.4, 290.7697, 44.7722 ],
[ -179.4, 170.7697, 44.7722 ]
]
# runners 6
[[geometry.pipes]]
index = 6
points_mm = [
[ -59.8, 290.7697, 44.7722 ],
[ -59.8, 170.7697, 44.7722 ]
]
# runners 7
[[geometry.pipes]]
index = 7
points_mm = [
[ 59.8, 290.7697, 44.7722 ],
[ 59.8, 170.7697, 44.7722 ]
]
# runners 8
[[geometry.pipes]]
index = 8
points_mm = [
[ 179.4, 290.7697, 44.7722 ],
[ 179.4, 170.7697, 44.7722 ]
]
# primaries 1
[[geometry.pipes]]
index = 9
points_mm = [
[ -179.4, 127.8412, -125.509 ],
[ -178.0076, 119.9376, -135.7592 ],
[ -174.0725, 112.269, -144.828 ],
[ -167.9579, 104.7529, -152.7871 ],
[ -160.0272, 97.307, -159.7084 ],
[ -150.6434, 89.8488, -165.6635 ],
[ -140.17, 82.2959, -170.7242 ],
[ -128.9701, 74.5658, -174.9623 ],
[ -117.4069, 66.5761, -178.4496 ],
[ -105.8437, 58.2443, -181.2578 ],
[ -94.6438, 49.4881, -183.4588 ],
[ -84.1704, 40.225, -185.1243 ],
[ -74.7866, 30.3725, -186.326 ],
[ -66.8559, 19.8482, -187.1357 ],
[ -60.7413, 8.5696, -187.6252 ],
[ -56.8062, -3.5455, -187.8663 ],
[ -55.4138, -16.5798, -187.9307 ]
]
# primaries 2
[[geometry.pipes]]
index = 10
points_mm = [
[ -59.8, 127.8412, -125.509 ],
[ -59.5988, 121.2536, -133.7587 ],
[ -59.0302, 114.2313, -141.3427 ],
[ -58.1467, 106.7977, -148.2793 ],
[ -57.0008, 98.9764, -154.587 ],
[ -55.6449, 90.7908, -160.2843 ],
[ -54.1316, 82.2644, -165.3894 ],
[ -52.5133, 73.4207, -169.921 ],
[ -50.8425, 64.2831, -173.8973 ],
[ -49.1717, 54.8751, -177.3368 ],
[ -47.5534, 45.2202, -180.258 ],
[ -46.0401, 35.3419, -182.6792 ],
[ -44.6842, 25.2636, -184.6188 ],
[ -43.5383, 15.0088, -186.0954 ],
[ -42.6548, 4.601, -187.1273 ],
[ -42.0862, -5.9364, -187.7329 ],
[ -41.885, -16.5798, -187.9307 ]
]
# primaries 3
[[geometry.pipes]]
index = 11
points_mm = [
[ 59.8, 127.8412, -125.509 ],
[ 61.1924, 119.9376, -135.7592 ],
[ 65.1275, 112.269, -144.828 ],
[ 71.2421, 104.7529, -152.7871 ],
[ 79.1728, 97.307, -159.7084 ],
[ 88.5566, 89.8488, -165.6635 ],
[ 99.03, 82.2959, -170.7242 ],
[ 110.2299, 74.5658, -174.9623 ],
[ 121.7931, 66.5761, -178.4496 ],
[ 133.3563, 58.2443, -181.2578 ],
[ 144.5562, 49.4881, -183.4588 ],
[ 155.0296, 40.225, -185.1243 ],
[ 164.4134, 30.3725, -186.326 ],
[ 172.3441, 19.8482, -187.1357 ],
[ 178.4587, 8.5696, -187.6252 ],
[ 182.3938, -3.5455, -187.8663 ],
[ 183.7862, -16.5798, -187.9307 ]
]
# primaries 4
[[geometry.pipes]]
index = 12
points_mm = [
[ 179.4, 127.8412, -125.509 ],
[ 179.6012, 121.2536, -133.7587 ],
[ 180.1698, 114.2313, -141.3427 ],
[ 181.0533, 106.7977, -148.2793 ],
[ 182.1992, 98.9764, -154.587 ],
[ 183.5551, 90.7908, -160.2843 ],
[ 185.0684, 82.2644, -165.3894 ],
[ 186.6867, 73.4207, -169.921 ],
[ 188.3575, 64.2831, -173.8973 ],
[ 190.0283, 54.8751, -177.3368 ],
[ 191.6466, 45.2202, -180.258 ],
[ 193.1599, 35.3419, -182.6792 ],
[ 194.5158, 25.2636, -184.6188 ],
[ 195.6617, 15.0088, -186.0954 ],
[ 196.5452, 4.601, -187.1273 ],
[ 197.1138, -5.9364, -187.7329 ],
[ 197.315, -16.5798, -187.9307 ]
]
# primaries 5
[[geometry.pipes]]
index = 13
points_mm = [
[ -179.4, 127.8412, 125.509 ],
[ -178.324, 117.6211, 137.7817 ],
[ -175.1941, 106.5763, 148.386 ],
[ -170.1577, 94.9116, 157.4428 ],
[ -163.362, 82.8316, 165.073 ],
[ -154.9545, 70.5413, 171.3976 ],
[ -145.0824, 58.2453, 176.5376 ],
[ -133.893, 46.1485, 180.614 ],
[ -121.5338, 34.4556, 183.7476 ],
[ -108.1519, 23.3715, 186.0596 ],
[ -93.8947, 13.1008, 187.6708 ],
[ -78.9095, 3.8484, 188.7023 ],
[ -63.3436, -4.1809, 189.2749 ],
[ -47.3445, -10.7824, 189.5097 ],
[ -31.0593, -15.7513, 189.5277 ],
[ -14.6354, -18.8828, 189.4497 ],
[ 1.7798, -19.9721, 189.3969 ]
]
# primaries 6
[[geometry.pipes]]
index = 14
points_mm = [
[ -59.8, 127.8412, 125.509 ],
[ -58.724, 117.6211, 137.7817 ],
[ -55.5941, 106.5763, 148.386 ],
[ -50.5577, 94.9116, 157.4428 ],
[ -43.762, 82.8316, 165.073 ],
[ -35.3545, 70.5413, 171.3976 ],
[ -25.4824, 58.2453, 176.5376 ],
[ -14.293, 46.1485, 180.614 ],
[ -1.9338, 34.4556, 183.7476 ],
[ 11.4481, 23.3715, 186.0596 ],
[ 25.7053, 13.1008, 187.6708 ],
[ 40.6905, 3.8484, 188.7023 ],
[ 56.2563, -4.1809, 189.2749 ],
[ 72.2555, -10.7824, 189.5097 ],
[ 88.5407, -15.7513, 189.5277 ],
[ 104.9646, -18.8828, 189.4497 ],
[ 121.3798, -19.9721, 189.3969 ]
]
# primaries 7
[[geometry.pipes]]
index = 15
points_mm = [
[ 59.8, 127.8412, 125.509 ],
[ 59.4056, 121.1641, 133.8948 ],
[ 58.2909, 114.0977, 141.5798 ],
[ 56.5589, 106.6586, 148.586 ],
[ 54.3124, 98.8628, 154.9355 ],
[ 51.6544, 90.7267, 160.6503 ],
[ 48.6876, 82.2665, 165.7524 ],
[ 45.5151, 73.4986, 170.264 ],
[ 42.2397, 64.4391, 174.2071 ],
[ 38.9643, 55.1044, 177.6036 ],
[ 35.7918, 45.5106, 180.4758 ],
[ 32.8251, 35.6741, 182.8455 ],
[ 30.167, 25.6112, 184.735 ],
[ 27.9206, 15.3381, 186.1662 ],
[ 26.1885, 4.871, 187.1611 ],
[ 25.0739, -5.7737, 187.742 ],
[ 24.6794, -16.5798, 187.9307 ]
]
# primaries 8
[[geometry.pipes]]
index = 16
points_mm = [
[ 179.4, 127.8412, 125.509 ],
[ 179.0056, 121.164, 133.8948 ],
[ 177.8909, 114.0977, 141.5798 ],
[ 176.1589, 106.6586, 148.586 ],
[ 173.9124, 98.8628, 154.9355 ],
[ 171.2544, 90.7267, 160.6503 ],
[ 168.2876, 82.2665, 165.7524 ],
[ 165.1151, 73.4986, 170.264 ],
[ 161.8397, 64.4391, 174.2071 ],
[ 158.5643, 55.1044, 177.6036 ],
[ 155.3918, 45.5106, 180.4758 ],
[ 152.4251, 35.6741, 182.8455 ],
[ 149.767, 25.6112, 184.735 ],
[ 147.5205, 15.3381, 186.1662 ],
[ 145.7885, 4.871, 187.1611 ],
[ 144.6739, -5.7737, 187.742 ],
[ 144.2794, -16.5798, 187.9307 ]
]
# secondaries 1
[[geometry.pipes]]
index = 17
points_mm = [
[ -21.1798, -37.1882, -195.8922 ],
[ 1.7283, -37.6445, -195.8922 ],
[ 24.0223, -38.9338, -195.8922 ],
[ 45.7896, -40.9372, -195.8922 ],
[ 67.1182, -43.5357, -195.8922 ],
[ 88.0958, -46.6102, -195.8922 ],
[ 108.8101, -50.0418, -195.8922 ],
[ 129.3489, -53.7114, -195.8922 ],
[ 149.8, -57.5, -195.8922 ],
[ 170.251, -61.2886, -195.8922 ],
[ 190.7898, -64.9582, -195.8922 ],
[ 211.5042, -68.3898, -195.8922 ],
[ 232.4818, -71.4643, -195.8922 ],
[ 253.8104, -74.0628, -195.8922 ],
[ 275.5777, -76.0662, -195.8922 ],
[ 297.8716, -77.3555, -195.8922 ],
[ 320.7798, -77.8118, -195.8922 ]
]
# secondaries 2
[[geometry.pipes]]
index = 18
points_mm = [
[ 218.0202, -40.9377, -195.8922 ],
[ 225.1524, -41.3097, -195.8922 ],
[ 232.0008, -42.361, -195.8922 ],
[ 238.6058, -43.9946, -195.8922 ],
[ 245.008, -46.1134, -195.8922 ],
[ 251.2479, -48.6204, -195.8922 ],
[ 257.3662, -51.4185, -195.8922 ],
[ 263.4034, -54.4107, -195.8922 ],
[ 269.4, -57.5, -195.8922 ],
[ 275.3966, -60.5892, -195.8922 ],
[ 281.4338, -63.5815, -195.8922 ],
[ 287.552, -66.3796, -195.8922 ],
[ 293.792, -68.8866, -195.8922 ],
[ 300.1942, -71.0054, -195.8922 ],
[ 306.7992, -72.6389, -195.8922 ],
[ 313.6476, -73.6903, -195.8922 ],
[ 320.7798, -74.0623, -195.8922 ]
]
# secondaries 3
[[geometry.pipes]]
index = 19
points_mm = [
[ 38.6202, -37.5985, 195.8922 ],
[ 57.5671, -38.0455, 195.8922 ],
[ 75.9893, -39.3087, 195.8922 ],
[ 93.9616, -41.2717, 195.8922 ],
[ 111.5591, -43.8177, 195.8922 ],
[ 128.8567, -46.8301, 195.8922 ],
[ 145.9294, -50.1924, 195.8922 ],
[ 162.8522, -53.7879, 195.8922 ],
[ 179.7, -57.5, 195.8922 ],
[ 196.5478, -61.2121, 195.8922 ],
[ 213.4705, -64.8076, 195.8922 ],
[ 230.5433, -68.1699, 195.8922 ],
[ 247.8409, -71.1823, 195.8922 ],
[ 265.4383, -73.7283, 195.8922 ],
[ 283.4107, -75.6913, 195.8922 ],
[ 301.8329, -76.9545, 195.8922 ],
[ 320.7798, -77.4015, 195.8922 ]
]
# secondaries 4
[[geometry.pipes]]
index = 20
points_mm = [
[ 158.2202, -39.157, 195.8922 ],
[ 169.2733, -39.569, 195.8922 ],
[ 179.9691, -40.7334, 195.8922 ],
[ 190.3587, -42.5426, 195.8922 ],
[ 200.4932, -44.8892, 195.8922 ],
[ 210.4235, -47.6657, 195.8922 ],
[ 220.2007, -50.7647, 195.8922 ],
[ 229.8759, -54.0786, 195.8922 ],
[ 239.5, -57.5, 195.8922 ],
[ 249.1241, -60.9214, 195.8922 ],
[ 258.7993, -64.2353, 195.8922 ],
[ 268.5765, -67.3343, 195.8922 ],
[ 278.5068, -70.1108, 195.8922 ],
[ 288.6412, -72.4574, 195.8922 ],
[ 299.0309, -74.2666, 195.8922 ],
[ 309.7267, -75.431, 195.8922 ],
[ 320.7798, -75.843, 195.8922 ]
]
# bank_collectors 1
[[geometry.pipes]]
index = 21
points_mm = [
[ 357.6201, -80, -203.01 ],
[ 395.2968, -80, -204.5045 ],
[ 431.5154, -80, -208.8033 ],
[ 466.4613, -80, -215.6295 ],
[ 500.3197, -80, -224.7065 ],
[ 533.2757, -80, -235.7573 ],
[ 565.5144, -80, -248.5051 ],
[ 597.2213, -80, -262.6732 ],
[ 628.5813, -80, -277.9846 ],
[ 659.7797, -80, -294.1627 ],
[ 691.0017, -80, -310.9305 ],
[ 722.4326, -80, -328.0112 ],
[ 754.2572, -80, -345.128 ],
[ 786.6611, -80, -362.0041 ],
[ 819.8294, -80, -378.3627 ],
[ 853.9472, -80, -393.9269 ],
[ 889.1997, -80, -408.42 ]
]
# bank_collectors 2
[[geometry.pipes]]
index = 22
points_mm = [
[ 357.6201, -80, 203.01 ],
[ 395.2968, -80, 204.5045 ],
[ 431.5154, -80, 208.8033 ],
[ 466.4613, -80, 215.6295 ],
[ 500.3197, -80, 224.7065 ],
[ 533.2757, -80, 235.7573 ],
[ 565.5144, -80, 248.5051 ],
[ 597.2213, -80, 262.6732 ],
[ 628.5813, -80, 277.9846 ],
[ 659.7797, -80, 294.1627 ],
[ 691.0017, -80, 310.9305 ],
[ 722.4326, -80, 328.0112 ],
[ 754.2572, -80, 345.128 ],
[ 786.6611, -80, 362.0041 ],
[ 819.8294, -80, 378.3627 ],
[ 853.9472, -80, 393.9269 ],
[ 889.1997, -80, 408.42 ]
]
# tailpipes 1
[[geometry.pipes]]
index = 23
points_mm = [
[ 889.1997, -80, -408.42 ],
[ 2139.1997, -80, -408.42 ]
]
# tailpipes 2
[[geometry.pipes]]
index = 24
points_mm = [
[ 889.1997, -80, 408.42 ],
[ 2139.1997, -80, 408.42 ]
]
# crossover
[[geometry.pipes]]
index = 25
points_mm = [
[ 889.1997, -80, -408.42 ],
[ 889.1997, -80, 408.42 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 312.6411, 0 ]
size_mm = [ 478.4, 43.7429, 191.1445 ]
[[geometry.volumes]]
id = "junction_0"
center_mm = [ -39.6, -35, -195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_1"
center_mm = [ 199.6, -35, -195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_2"
center_mm = [ 20.2, -35, 195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_3"
center_mm = [ 139.8, -35, 195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_4"
center_mm = [ 339.2, -80, -195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_5"
center_mm = [ 339.2, -80, 195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]

View File

@@ -1,15 +1,17 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "v8-h"
name = "American 5.0 / 4-2-1 + H"
category = "V8 / American 5.0"
description = "750 mm primaries, 50 cc junctions, H crossover"
description = "Swept primaries / 50 cc junctions / H crossover"
order = 7
cycle = "four_stroke"
[simulation]
cells_per_meter = 20.0
[engine]
bank_angle_deg = 56.0
collector_volume_litres = 1.5
cylinders = 8
initial_rpm = 1000.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 300.0
minimum_substeps = 1
phase_degrees = [0.0, 450.0, 270.0, 540.0, 630.0, 360.0, 180.0, 89.99998]
pipe_damping = 1.0
plenum_volume_litres = 4.0
starter_torque_nm = 180.0
throttle_diameter_mm = 65.0
topology = "american_v8"
@@ -31,18 +32,12 @@ burn_duration_deg = 65.0
burn_start_deg = 5.0
compression = 7.0
energy_variation = 0.05
exhaust_close_deg = 370.0
exhaust_open_deg = 140.0
exhaust_valve_count = 1
exhaust_valve_diameter_mm = 37.0
exhaust_valve_lift_mm = 10.0
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 570.0
intake_open_deg = 350.0
intake_valve_count = 1
intake_valve_diameter_mm = 45.0
intake_valve_lift_mm = 10.0
misfire_probability = 0.0
rod_mm = 129.0
stroke_mm = 76.2
@@ -101,51 +96,619 @@ system = "injection"
[junctions]
h_pipe = true
junction_volume_cc = 50.0
[[pipes]]
id = "intake"
cells = 3
diameter_mm = 70.0
length_mm = 180.0
[[pipes]]
id = "runners"
count = 8
cells = 2
diameter_mm = 38.0
length_mm = 120.0
[[pipes]]
id = "primaries"
count = 8
cells = 15
diameter_mm = 38.0
length_mm = 750.0
[[pipes]]
id = "secondaries"
count = 4
cells = 8
diameter_mm = 45.0
length_mm = 400.0
[[pipes]]
id = "bank_collectors"
count = 2
cells = 11
diameter_mm = 50.0
length_mm = 550.0
[[pipes]]
id = "tailpipes"
count = 2
cells = 25
diameter_mm = 50.0
length_mm = 1250.0
[[pipes]]
id = "crossover"
cells = 16
diameter_mm = 38.0
length_mm = 780.0
[cam.intake]
points = [
[0.000, 0.000000],
[350.000, 0.000000],
[355.000, 0.232908],
[360.000, 0.871525],
[365.000, 1.825695],
[370.000, 3.005259],
[375.000, 4.320060],
[380.000, 5.679940],
[385.000, 6.994741],
[390.000, 8.174305],
[395.000, 9.128475],
[400.000, 9.767092],
[405.000, 10.000000],
[515.000, 10.000000],
[520.000, 9.767092],
[525.000, 9.128475],
[530.000, 8.174305],
[535.000, 6.994741],
[540.000, 5.679940],
[545.000, 4.320060],
[550.000, 3.005259],
[555.000, 1.825695],
[560.000, 0.871525],
[565.000, 0.232908],
[570.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[140.000, 0.000000],
[145.000, 0.213693],
[150.000, 0.802170],
[155.000, 1.686529],
[160.000, 2.787869],
[165.000, 4.027287],
[170.000, 5.325881],
[175.000, 6.604751],
[180.000, 7.784992],
[185.000, 8.787704],
[190.000, 9.533985],
[195.000, 9.944933],
[197.500, 10.000000],
[312.500, 10.000000],
[317.500, 9.786307],
[322.500, 9.197830],
[327.500, 8.313471],
[332.500, 7.212131],
[337.500, 5.972713],
[342.500, 4.674119],
[347.500, 3.395249],
[352.500, 2.215008],
[357.500, 1.212296],
[362.500, 0.466015],
[367.500, 0.055067],
[370.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ -179.4, 0, 0 ]
tilt_deg = -28.0
[[geometry.cylinders]]
origin_mm = [ -59.8, 0, 0 ]
tilt_deg = -28.0
[[geometry.cylinders]]
origin_mm = [ 59.8, 0, 0 ]
tilt_deg = -28.0
[[geometry.cylinders]]
origin_mm = [ 179.4, 0, 0 ]
tilt_deg = -28.0
[[geometry.cylinders]]
origin_mm = [ -179.4, 0, 0 ]
tilt_deg = 28.0
[[geometry.cylinders]]
origin_mm = [ -59.8, 0, 0 ]
tilt_deg = 28.0
[[geometry.cylinders]]
origin_mm = [ 59.8, 0, 0 ]
tilt_deg = 28.0
[[geometry.cylinders]]
origin_mm = [ 179.4, 0, 0 ]
tilt_deg = 28.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -419.2, 312.6411, 0 ],
[ -239.2, 312.6411, 0 ]
]
# runners 1
[[geometry.pipes]]
index = 1
points_mm = [
[ -179.4, 290.7697, -44.7722 ],
[ -179.4, 170.7697, -44.7722 ]
]
# runners 2
[[geometry.pipes]]
index = 2
points_mm = [
[ -59.8, 290.7697, -44.7722 ],
[ -59.8, 170.7697, -44.7722 ]
]
# runners 3
[[geometry.pipes]]
index = 3
points_mm = [
[ 59.8, 290.7697, -44.7722 ],
[ 59.8, 170.7697, -44.7722 ]
]
# runners 4
[[geometry.pipes]]
index = 4
points_mm = [
[ 179.4, 290.7697, -44.7722 ],
[ 179.4, 170.7697, -44.7722 ]
]
# runners 5
[[geometry.pipes]]
index = 5
points_mm = [
[ -179.4, 290.7697, 44.7722 ],
[ -179.4, 170.7697, 44.7722 ]
]
# runners 6
[[geometry.pipes]]
index = 6
points_mm = [
[ -59.8, 290.7697, 44.7722 ],
[ -59.8, 170.7697, 44.7722 ]
]
# runners 7
[[geometry.pipes]]
index = 7
points_mm = [
[ 59.8, 290.7697, 44.7722 ],
[ 59.8, 170.7697, 44.7722 ]
]
# runners 8
[[geometry.pipes]]
index = 8
points_mm = [
[ 179.4, 290.7697, 44.7722 ],
[ 179.4, 170.7697, 44.7722 ]
]
# primaries 1
[[geometry.pipes]]
index = 9
points_mm = [
[ -179.4, 127.8412, -125.509 ],
[ -178.0076, 119.9376, -135.7592 ],
[ -174.0725, 112.269, -144.828 ],
[ -167.9579, 104.7529, -152.7871 ],
[ -160.0272, 97.307, -159.7084 ],
[ -150.6434, 89.8488, -165.6635 ],
[ -140.17, 82.2959, -170.7242 ],
[ -128.9701, 74.5658, -174.9623 ],
[ -117.4069, 66.5761, -178.4496 ],
[ -105.8437, 58.2443, -181.2578 ],
[ -94.6438, 49.4881, -183.4588 ],
[ -84.1704, 40.225, -185.1243 ],
[ -74.7866, 30.3725, -186.326 ],
[ -66.8559, 19.8482, -187.1357 ],
[ -60.7413, 8.5696, -187.6252 ],
[ -56.8062, -3.5455, -187.8663 ],
[ -55.4138, -16.5798, -187.9307 ]
]
# primaries 2
[[geometry.pipes]]
index = 10
points_mm = [
[ -59.8, 127.8412, -125.509 ],
[ -59.5988, 121.2536, -133.7587 ],
[ -59.0302, 114.2313, -141.3427 ],
[ -58.1467, 106.7977, -148.2793 ],
[ -57.0008, 98.9764, -154.587 ],
[ -55.6449, 90.7908, -160.2843 ],
[ -54.1316, 82.2644, -165.3894 ],
[ -52.5133, 73.4207, -169.921 ],
[ -50.8425, 64.2831, -173.8973 ],
[ -49.1717, 54.8751, -177.3368 ],
[ -47.5534, 45.2202, -180.258 ],
[ -46.0401, 35.3419, -182.6792 ],
[ -44.6842, 25.2636, -184.6188 ],
[ -43.5383, 15.0088, -186.0954 ],
[ -42.6548, 4.601, -187.1273 ],
[ -42.0862, -5.9364, -187.7329 ],
[ -41.885, -16.5798, -187.9307 ]
]
# primaries 3
[[geometry.pipes]]
index = 11
points_mm = [
[ 59.8, 127.8412, -125.509 ],
[ 61.1924, 119.9376, -135.7592 ],
[ 65.1275, 112.269, -144.828 ],
[ 71.2421, 104.7529, -152.7871 ],
[ 79.1728, 97.307, -159.7084 ],
[ 88.5566, 89.8488, -165.6635 ],
[ 99.03, 82.2959, -170.7242 ],
[ 110.2299, 74.5658, -174.9623 ],
[ 121.7931, 66.5761, -178.4496 ],
[ 133.3563, 58.2443, -181.2578 ],
[ 144.5562, 49.4881, -183.4588 ],
[ 155.0296, 40.225, -185.1243 ],
[ 164.4134, 30.3725, -186.326 ],
[ 172.3441, 19.8482, -187.1357 ],
[ 178.4587, 8.5696, -187.6252 ],
[ 182.3938, -3.5455, -187.8663 ],
[ 183.7862, -16.5798, -187.9307 ]
]
# primaries 4
[[geometry.pipes]]
index = 12
points_mm = [
[ 179.4, 127.8412, -125.509 ],
[ 179.6012, 121.2536, -133.7587 ],
[ 180.1698, 114.2313, -141.3427 ],
[ 181.0533, 106.7977, -148.2793 ],
[ 182.1992, 98.9764, -154.587 ],
[ 183.5551, 90.7908, -160.2843 ],
[ 185.0684, 82.2644, -165.3894 ],
[ 186.6867, 73.4207, -169.921 ],
[ 188.3575, 64.2831, -173.8973 ],
[ 190.0283, 54.8751, -177.3368 ],
[ 191.6466, 45.2202, -180.258 ],
[ 193.1599, 35.3419, -182.6792 ],
[ 194.5158, 25.2636, -184.6188 ],
[ 195.6617, 15.0088, -186.0954 ],
[ 196.5452, 4.601, -187.1273 ],
[ 197.1138, -5.9364, -187.7329 ],
[ 197.315, -16.5798, -187.9307 ]
]
# primaries 5
[[geometry.pipes]]
index = 13
points_mm = [
[ -179.4, 127.8412, 125.509 ],
[ -178.324, 117.6211, 137.7817 ],
[ -175.1941, 106.5763, 148.386 ],
[ -170.1577, 94.9116, 157.4428 ],
[ -163.362, 82.8316, 165.073 ],
[ -154.9545, 70.5413, 171.3976 ],
[ -145.0824, 58.2453, 176.5376 ],
[ -133.893, 46.1485, 180.614 ],
[ -121.5338, 34.4556, 183.7476 ],
[ -108.1519, 23.3715, 186.0596 ],
[ -93.8947, 13.1008, 187.6708 ],
[ -78.9095, 3.8484, 188.7023 ],
[ -63.3436, -4.1809, 189.2749 ],
[ -47.3445, -10.7824, 189.5097 ],
[ -31.0593, -15.7513, 189.5277 ],
[ -14.6354, -18.8828, 189.4497 ],
[ 1.7798, -19.9721, 189.3969 ]
]
# primaries 6
[[geometry.pipes]]
index = 14
points_mm = [
[ -59.8, 127.8412, 125.509 ],
[ -58.724, 117.6211, 137.7817 ],
[ -55.5941, 106.5763, 148.386 ],
[ -50.5577, 94.9116, 157.4428 ],
[ -43.762, 82.8316, 165.073 ],
[ -35.3545, 70.5413, 171.3976 ],
[ -25.4824, 58.2453, 176.5376 ],
[ -14.293, 46.1485, 180.614 ],
[ -1.9338, 34.4556, 183.7476 ],
[ 11.4481, 23.3715, 186.0596 ],
[ 25.7053, 13.1008, 187.6708 ],
[ 40.6905, 3.8484, 188.7023 ],
[ 56.2563, -4.1809, 189.2749 ],
[ 72.2555, -10.7824, 189.5097 ],
[ 88.5407, -15.7513, 189.5277 ],
[ 104.9646, -18.8828, 189.4497 ],
[ 121.3798, -19.9721, 189.3969 ]
]
# primaries 7
[[geometry.pipes]]
index = 15
points_mm = [
[ 59.8, 127.8412, 125.509 ],
[ 59.4056, 121.1641, 133.8948 ],
[ 58.2909, 114.0977, 141.5798 ],
[ 56.5589, 106.6586, 148.586 ],
[ 54.3124, 98.8628, 154.9355 ],
[ 51.6544, 90.7267, 160.6503 ],
[ 48.6876, 82.2665, 165.7524 ],
[ 45.5151, 73.4986, 170.264 ],
[ 42.2397, 64.4391, 174.2071 ],
[ 38.9643, 55.1044, 177.6036 ],
[ 35.7918, 45.5106, 180.4758 ],
[ 32.8251, 35.6741, 182.8455 ],
[ 30.167, 25.6112, 184.735 ],
[ 27.9206, 15.3381, 186.1662 ],
[ 26.1885, 4.871, 187.1611 ],
[ 25.0739, -5.7737, 187.742 ],
[ 24.6794, -16.5798, 187.9307 ]
]
# primaries 8
[[geometry.pipes]]
index = 16
points_mm = [
[ 179.4, 127.8412, 125.509 ],
[ 179.0056, 121.164, 133.8948 ],
[ 177.8909, 114.0977, 141.5798 ],
[ 176.1589, 106.6586, 148.586 ],
[ 173.9124, 98.8628, 154.9355 ],
[ 171.2544, 90.7267, 160.6503 ],
[ 168.2876, 82.2665, 165.7524 ],
[ 165.1151, 73.4986, 170.264 ],
[ 161.8397, 64.4391, 174.2071 ],
[ 158.5643, 55.1044, 177.6036 ],
[ 155.3918, 45.5106, 180.4758 ],
[ 152.4251, 35.6741, 182.8455 ],
[ 149.767, 25.6112, 184.735 ],
[ 147.5205, 15.3381, 186.1662 ],
[ 145.7885, 4.871, 187.1611 ],
[ 144.6739, -5.7737, 187.742 ],
[ 144.2794, -16.5798, 187.9307 ]
]
# secondaries 1
[[geometry.pipes]]
index = 17
points_mm = [
[ -21.1798, -37.1882, -195.8922 ],
[ 1.7283, -37.6445, -195.8922 ],
[ 24.0223, -38.9338, -195.8922 ],
[ 45.7896, -40.9372, -195.8922 ],
[ 67.1182, -43.5357, -195.8922 ],
[ 88.0958, -46.6102, -195.8922 ],
[ 108.8101, -50.0418, -195.8922 ],
[ 129.3489, -53.7114, -195.8922 ],
[ 149.8, -57.5, -195.8922 ],
[ 170.251, -61.2886, -195.8922 ],
[ 190.7898, -64.9582, -195.8922 ],
[ 211.5042, -68.3898, -195.8922 ],
[ 232.4818, -71.4643, -195.8922 ],
[ 253.8104, -74.0628, -195.8922 ],
[ 275.5777, -76.0662, -195.8922 ],
[ 297.8716, -77.3555, -195.8922 ],
[ 320.7798, -77.8118, -195.8922 ]
]
# secondaries 2
[[geometry.pipes]]
index = 18
points_mm = [
[ 218.0202, -40.9377, -195.8922 ],
[ 225.1524, -41.3097, -195.8922 ],
[ 232.0008, -42.361, -195.8922 ],
[ 238.6058, -43.9946, -195.8922 ],
[ 245.008, -46.1134, -195.8922 ],
[ 251.2479, -48.6204, -195.8922 ],
[ 257.3662, -51.4185, -195.8922 ],
[ 263.4034, -54.4107, -195.8922 ],
[ 269.4, -57.5, -195.8922 ],
[ 275.3966, -60.5892, -195.8922 ],
[ 281.4338, -63.5815, -195.8922 ],
[ 287.552, -66.3796, -195.8922 ],
[ 293.792, -68.8866, -195.8922 ],
[ 300.1942, -71.0054, -195.8922 ],
[ 306.7992, -72.6389, -195.8922 ],
[ 313.6476, -73.6903, -195.8922 ],
[ 320.7798, -74.0623, -195.8922 ]
]
# secondaries 3
[[geometry.pipes]]
index = 19
points_mm = [
[ 38.6202, -37.5985, 195.8922 ],
[ 57.5671, -38.0455, 195.8922 ],
[ 75.9893, -39.3087, 195.8922 ],
[ 93.9616, -41.2717, 195.8922 ],
[ 111.5591, -43.8177, 195.8922 ],
[ 128.8567, -46.8301, 195.8922 ],
[ 145.9294, -50.1924, 195.8922 ],
[ 162.8522, -53.7879, 195.8922 ],
[ 179.7, -57.5, 195.8922 ],
[ 196.5478, -61.2121, 195.8922 ],
[ 213.4705, -64.8076, 195.8922 ],
[ 230.5433, -68.1699, 195.8922 ],
[ 247.8409, -71.1823, 195.8922 ],
[ 265.4383, -73.7283, 195.8922 ],
[ 283.4107, -75.6913, 195.8922 ],
[ 301.8329, -76.9545, 195.8922 ],
[ 320.7798, -77.4015, 195.8922 ]
]
# secondaries 4
[[geometry.pipes]]
index = 20
points_mm = [
[ 158.2202, -39.157, 195.8922 ],
[ 169.2733, -39.569, 195.8922 ],
[ 179.9691, -40.7334, 195.8922 ],
[ 190.3587, -42.5426, 195.8922 ],
[ 200.4932, -44.8892, 195.8922 ],
[ 210.4235, -47.6657, 195.8922 ],
[ 220.2007, -50.7647, 195.8922 ],
[ 229.8759, -54.0786, 195.8922 ],
[ 239.5, -57.5, 195.8922 ],
[ 249.1241, -60.9214, 195.8922 ],
[ 258.7993, -64.2353, 195.8922 ],
[ 268.5765, -67.3343, 195.8922 ],
[ 278.5068, -70.1108, 195.8922 ],
[ 288.6412, -72.4574, 195.8922 ],
[ 299.0309, -74.2666, 195.8922 ],
[ 309.7267, -75.431, 195.8922 ],
[ 320.7798, -75.843, 195.8922 ]
]
# bank_collectors 1
[[geometry.pipes]]
index = 21
points_mm = [
[ 357.6201, -80, -203.0101 ],
[ 393.845, -80, -205.237 ],
[ 428.4088, -80, -211.5304 ],
[ 461.549, -80, -221.3095 ],
[ 493.5029, -80, -233.9932 ],
[ 524.5076, -80, -249.0007 ],
[ 554.8005, -80, -265.751 ],
[ 584.6189, -80, -283.6631 ],
[ 614.2, -80, -302.1562 ],
[ 643.781, -80, -320.6493 ],
[ 673.5994, -80, -338.5614 ],
[ 703.8922, -80, -355.3117 ],
[ 734.897, -80, -370.3192 ],
[ 766.8508, -80, -383.0029 ],
[ 799.9911, -80, -392.782 ],
[ 834.5549, -80, -399.0755 ],
[ 870.7797, -80, -401.3024 ]
]
# bank_collectors 2
[[geometry.pipes]]
index = 22
points_mm = [
[ 357.6201, -80, 203.0101 ],
[ 393.845, -80, 205.237 ],
[ 428.4088, -80, 211.5304 ],
[ 461.549, -80, 221.3095 ],
[ 493.5029, -80, 233.9932 ],
[ 524.5076, -80, 249.0007 ],
[ 554.8005, -80, 265.751 ],
[ 584.6189, -80, 283.6631 ],
[ 614.2, -80, 302.1562 ],
[ 643.781, -80, 320.6493 ],
[ 673.5994, -80, 338.5614 ],
[ 703.8922, -80, 355.3117 ],
[ 734.897, -80, 370.3192 ],
[ 766.8508, -80, 383.0029 ],
[ 799.9911, -80, 392.782 ],
[ 834.5549, -80, 399.0755 ],
[ 870.7797, -80, 401.3024 ]
]
# tailpipes 1
[[geometry.pipes]]
index = 23
points_mm = [
[ 907.62, -80, -408.4202 ],
[ 2157.62, -80, -408.4202 ]
]
# tailpipes 2
[[geometry.pipes]]
index = 24
points_mm = [
[ 907.62, -80, 408.4202 ],
[ 2157.62, -80, 408.4202 ]
]
# crossover
[[geometry.pipes]]
index = 25
points_mm = [
[ 889.1999, -80, -390 ],
[ 889.1999, -80, 390 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 312.6411, 0 ]
size_mm = [ 478.4, 43.7429, 191.1445 ]
[[geometry.volumes]]
id = "junction_0"
center_mm = [ -39.6, -35, -195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_1"
center_mm = [ 199.6, -35, -195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_2"
center_mm = [ 20.2, -35, 195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_3"
center_mm = [ 139.8, -35, 195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_4"
center_mm = [ 339.2, -80, -195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_5"
center_mm = [ 339.2, -80, 195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_6"
center_mm = [ 889.1999, -80, -408.4202 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_7"
center_mm = [ 889.1999, -80, 408.4202 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]

View File

@@ -1,15 +1,17 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "v8-large"
name = "American 5.0 / larger primaries"
category = "V8 / American 5.0"
description = "42 mm x 750 mm primaries with H crossover"
description = "42 mm swept primaries / H crossover"
order = 11
cycle = "four_stroke"
[simulation]
cells_per_meter = 20.0
[engine]
bank_angle_deg = 56.0
collector_volume_litres = 1.5
cylinders = 8
initial_rpm = 1000.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 300.0
minimum_substeps = 1
phase_degrees = [0.0, 450.0, 270.0, 540.0, 630.0, 360.0, 180.0, 89.99998]
pipe_damping = 1.0
plenum_volume_litres = 4.0
starter_torque_nm = 180.0
throttle_diameter_mm = 65.0
topology = "american_v8"
@@ -31,18 +32,12 @@ burn_duration_deg = 65.0
burn_start_deg = 5.0
compression = 7.0
energy_variation = 0.05
exhaust_close_deg = 370.0
exhaust_open_deg = 140.0
exhaust_valve_count = 1
exhaust_valve_diameter_mm = 37.0
exhaust_valve_lift_mm = 10.0
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 570.0
intake_open_deg = 350.0
intake_valve_count = 1
intake_valve_diameter_mm = 45.0
intake_valve_lift_mm = 10.0
misfire_probability = 0.0
rod_mm = 129.0
stroke_mm = 76.2
@@ -101,51 +96,619 @@ system = "injection"
[junctions]
h_pipe = true
junction_volume_cc = 50.0
[[pipes]]
id = "intake"
cells = 3
diameter_mm = 70.0
length_mm = 180.0
[[pipes]]
id = "runners"
count = 8
cells = 2
diameter_mm = 38.0
length_mm = 120.0
[[pipes]]
id = "primaries"
count = 8
cells = 15
diameter_mm = 42.0
length_mm = 750.0
[[pipes]]
id = "secondaries"
count = 4
cells = 8
diameter_mm = 45.0
length_mm = 400.0
[[pipes]]
id = "bank_collectors"
count = 2
cells = 11
diameter_mm = 50.0
length_mm = 550.0
[[pipes]]
id = "tailpipes"
count = 2
cells = 25
diameter_mm = 50.0
length_mm = 1250.0
[[pipes]]
id = "crossover"
cells = 16
diameter_mm = 38.0
length_mm = 780.0
[cam.intake]
points = [
[0.000, 0.000000],
[350.000, 0.000000],
[355.000, 0.232908],
[360.000, 0.871525],
[365.000, 1.825695],
[370.000, 3.005259],
[375.000, 4.320060],
[380.000, 5.679940],
[385.000, 6.994741],
[390.000, 8.174305],
[395.000, 9.128475],
[400.000, 9.767092],
[405.000, 10.000000],
[515.000, 10.000000],
[520.000, 9.767092],
[525.000, 9.128475],
[530.000, 8.174305],
[535.000, 6.994741],
[540.000, 5.679940],
[545.000, 4.320060],
[550.000, 3.005259],
[555.000, 1.825695],
[560.000, 0.871525],
[565.000, 0.232908],
[570.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[140.000, 0.000000],
[145.000, 0.213693],
[150.000, 0.802170],
[155.000, 1.686529],
[160.000, 2.787869],
[165.000, 4.027287],
[170.000, 5.325881],
[175.000, 6.604751],
[180.000, 7.784992],
[185.000, 8.787704],
[190.000, 9.533985],
[195.000, 9.944933],
[197.500, 10.000000],
[312.500, 10.000000],
[317.500, 9.786307],
[322.500, 9.197830],
[327.500, 8.313471],
[332.500, 7.212131],
[337.500, 5.972713],
[342.500, 4.674119],
[347.500, 3.395249],
[352.500, 2.215008],
[357.500, 1.212296],
[362.500, 0.466015],
[367.500, 0.055067],
[370.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ -179.4, 0, 0 ]
tilt_deg = -28.0
[[geometry.cylinders]]
origin_mm = [ -59.8, 0, 0 ]
tilt_deg = -28.0
[[geometry.cylinders]]
origin_mm = [ 59.8, 0, 0 ]
tilt_deg = -28.0
[[geometry.cylinders]]
origin_mm = [ 179.4, 0, 0 ]
tilt_deg = -28.0
[[geometry.cylinders]]
origin_mm = [ -179.4, 0, 0 ]
tilt_deg = 28.0
[[geometry.cylinders]]
origin_mm = [ -59.8, 0, 0 ]
tilt_deg = 28.0
[[geometry.cylinders]]
origin_mm = [ 59.8, 0, 0 ]
tilt_deg = 28.0
[[geometry.cylinders]]
origin_mm = [ 179.4, 0, 0 ]
tilt_deg = 28.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -419.2, 312.6411, 0 ],
[ -239.2, 312.6411, 0 ]
]
# runners 1
[[geometry.pipes]]
index = 1
points_mm = [
[ -179.4, 290.7697, -44.7722 ],
[ -179.4, 170.7697, -44.7722 ]
]
# runners 2
[[geometry.pipes]]
index = 2
points_mm = [
[ -59.8, 290.7697, -44.7722 ],
[ -59.8, 170.7697, -44.7722 ]
]
# runners 3
[[geometry.pipes]]
index = 3
points_mm = [
[ 59.8, 290.7697, -44.7722 ],
[ 59.8, 170.7697, -44.7722 ]
]
# runners 4
[[geometry.pipes]]
index = 4
points_mm = [
[ 179.4, 290.7697, -44.7722 ],
[ 179.4, 170.7697, -44.7722 ]
]
# runners 5
[[geometry.pipes]]
index = 5
points_mm = [
[ -179.4, 290.7697, 44.7722 ],
[ -179.4, 170.7697, 44.7722 ]
]
# runners 6
[[geometry.pipes]]
index = 6
points_mm = [
[ -59.8, 290.7697, 44.7722 ],
[ -59.8, 170.7697, 44.7722 ]
]
# runners 7
[[geometry.pipes]]
index = 7
points_mm = [
[ 59.8, 290.7697, 44.7722 ],
[ 59.8, 170.7697, 44.7722 ]
]
# runners 8
[[geometry.pipes]]
index = 8
points_mm = [
[ 179.4, 290.7697, 44.7722 ],
[ 179.4, 170.7697, 44.7722 ]
]
# primaries 1
[[geometry.pipes]]
index = 9
points_mm = [
[ -179.4, 127.8412, -125.509 ],
[ -178.0076, 119.9376, -135.7592 ],
[ -174.0725, 112.269, -144.828 ],
[ -167.9579, 104.7529, -152.7871 ],
[ -160.0272, 97.307, -159.7084 ],
[ -150.6434, 89.8488, -165.6635 ],
[ -140.17, 82.2959, -170.7242 ],
[ -128.9701, 74.5658, -174.9623 ],
[ -117.4069, 66.5761, -178.4496 ],
[ -105.8437, 58.2443, -181.2578 ],
[ -94.6438, 49.4881, -183.4588 ],
[ -84.1704, 40.225, -185.1243 ],
[ -74.7866, 30.3725, -186.326 ],
[ -66.8559, 19.8482, -187.1357 ],
[ -60.7413, 8.5696, -187.6252 ],
[ -56.8062, -3.5455, -187.8663 ],
[ -55.4138, -16.5798, -187.9307 ]
]
# primaries 2
[[geometry.pipes]]
index = 10
points_mm = [
[ -59.8, 127.8412, -125.509 ],
[ -59.5988, 121.2536, -133.7587 ],
[ -59.0302, 114.2313, -141.3427 ],
[ -58.1467, 106.7977, -148.2793 ],
[ -57.0008, 98.9764, -154.587 ],
[ -55.6449, 90.7908, -160.2843 ],
[ -54.1316, 82.2644, -165.3894 ],
[ -52.5133, 73.4207, -169.921 ],
[ -50.8425, 64.2831, -173.8973 ],
[ -49.1717, 54.8751, -177.3368 ],
[ -47.5534, 45.2202, -180.258 ],
[ -46.0401, 35.3419, -182.6792 ],
[ -44.6842, 25.2636, -184.6188 ],
[ -43.5383, 15.0088, -186.0954 ],
[ -42.6548, 4.601, -187.1273 ],
[ -42.0862, -5.9364, -187.7329 ],
[ -41.885, -16.5798, -187.9307 ]
]
# primaries 3
[[geometry.pipes]]
index = 11
points_mm = [
[ 59.8, 127.8412, -125.509 ],
[ 61.1924, 119.9376, -135.7592 ],
[ 65.1275, 112.269, -144.828 ],
[ 71.2421, 104.7529, -152.7871 ],
[ 79.1728, 97.307, -159.7084 ],
[ 88.5566, 89.8488, -165.6635 ],
[ 99.03, 82.2959, -170.7242 ],
[ 110.2299, 74.5658, -174.9623 ],
[ 121.7931, 66.5761, -178.4496 ],
[ 133.3563, 58.2443, -181.2578 ],
[ 144.5562, 49.4881, -183.4588 ],
[ 155.0296, 40.225, -185.1243 ],
[ 164.4134, 30.3725, -186.326 ],
[ 172.3441, 19.8482, -187.1357 ],
[ 178.4587, 8.5696, -187.6252 ],
[ 182.3938, -3.5455, -187.8663 ],
[ 183.7862, -16.5798, -187.9307 ]
]
# primaries 4
[[geometry.pipes]]
index = 12
points_mm = [
[ 179.4, 127.8412, -125.509 ],
[ 179.6012, 121.2536, -133.7587 ],
[ 180.1698, 114.2313, -141.3427 ],
[ 181.0533, 106.7977, -148.2793 ],
[ 182.1992, 98.9764, -154.587 ],
[ 183.5551, 90.7908, -160.2843 ],
[ 185.0684, 82.2644, -165.3894 ],
[ 186.6867, 73.4207, -169.921 ],
[ 188.3575, 64.2831, -173.8973 ],
[ 190.0283, 54.8751, -177.3368 ],
[ 191.6466, 45.2202, -180.258 ],
[ 193.1599, 35.3419, -182.6792 ],
[ 194.5158, 25.2636, -184.6188 ],
[ 195.6617, 15.0088, -186.0954 ],
[ 196.5452, 4.601, -187.1273 ],
[ 197.1138, -5.9364, -187.7329 ],
[ 197.315, -16.5798, -187.9307 ]
]
# primaries 5
[[geometry.pipes]]
index = 13
points_mm = [
[ -179.4, 127.8412, 125.509 ],
[ -178.324, 117.6211, 137.7817 ],
[ -175.1941, 106.5763, 148.386 ],
[ -170.1577, 94.9116, 157.4428 ],
[ -163.362, 82.8316, 165.073 ],
[ -154.9545, 70.5413, 171.3976 ],
[ -145.0824, 58.2453, 176.5376 ],
[ -133.893, 46.1485, 180.614 ],
[ -121.5338, 34.4556, 183.7476 ],
[ -108.1519, 23.3715, 186.0596 ],
[ -93.8947, 13.1008, 187.6708 ],
[ -78.9095, 3.8484, 188.7023 ],
[ -63.3436, -4.1809, 189.2749 ],
[ -47.3445, -10.7824, 189.5097 ],
[ -31.0593, -15.7513, 189.5277 ],
[ -14.6354, -18.8828, 189.4497 ],
[ 1.7798, -19.9721, 189.3969 ]
]
# primaries 6
[[geometry.pipes]]
index = 14
points_mm = [
[ -59.8, 127.8412, 125.509 ],
[ -58.724, 117.6211, 137.7817 ],
[ -55.5941, 106.5763, 148.386 ],
[ -50.5577, 94.9116, 157.4428 ],
[ -43.762, 82.8316, 165.073 ],
[ -35.3545, 70.5413, 171.3976 ],
[ -25.4824, 58.2453, 176.5376 ],
[ -14.293, 46.1485, 180.614 ],
[ -1.9338, 34.4556, 183.7476 ],
[ 11.4481, 23.3715, 186.0596 ],
[ 25.7053, 13.1008, 187.6708 ],
[ 40.6905, 3.8484, 188.7023 ],
[ 56.2563, -4.1809, 189.2749 ],
[ 72.2555, -10.7824, 189.5097 ],
[ 88.5407, -15.7513, 189.5277 ],
[ 104.9646, -18.8828, 189.4497 ],
[ 121.3798, -19.9721, 189.3969 ]
]
# primaries 7
[[geometry.pipes]]
index = 15
points_mm = [
[ 59.8, 127.8412, 125.509 ],
[ 59.4056, 121.1641, 133.8948 ],
[ 58.2909, 114.0977, 141.5798 ],
[ 56.5589, 106.6586, 148.586 ],
[ 54.3124, 98.8628, 154.9355 ],
[ 51.6544, 90.7267, 160.6503 ],
[ 48.6876, 82.2665, 165.7524 ],
[ 45.5151, 73.4986, 170.264 ],
[ 42.2397, 64.4391, 174.2071 ],
[ 38.9643, 55.1044, 177.6036 ],
[ 35.7918, 45.5106, 180.4758 ],
[ 32.8251, 35.6741, 182.8455 ],
[ 30.167, 25.6112, 184.735 ],
[ 27.9206, 15.3381, 186.1662 ],
[ 26.1885, 4.871, 187.1611 ],
[ 25.0739, -5.7737, 187.742 ],
[ 24.6794, -16.5798, 187.9307 ]
]
# primaries 8
[[geometry.pipes]]
index = 16
points_mm = [
[ 179.4, 127.8412, 125.509 ],
[ 179.0056, 121.164, 133.8948 ],
[ 177.8909, 114.0977, 141.5798 ],
[ 176.1589, 106.6586, 148.586 ],
[ 173.9124, 98.8628, 154.9355 ],
[ 171.2544, 90.7267, 160.6503 ],
[ 168.2876, 82.2665, 165.7524 ],
[ 165.1151, 73.4986, 170.264 ],
[ 161.8397, 64.4391, 174.2071 ],
[ 158.5643, 55.1044, 177.6036 ],
[ 155.3918, 45.5106, 180.4758 ],
[ 152.4251, 35.6741, 182.8455 ],
[ 149.767, 25.6112, 184.735 ],
[ 147.5205, 15.3381, 186.1662 ],
[ 145.7885, 4.871, 187.1611 ],
[ 144.6739, -5.7737, 187.742 ],
[ 144.2794, -16.5798, 187.9307 ]
]
# secondaries 1
[[geometry.pipes]]
index = 17
points_mm = [
[ -21.1798, -37.1882, -195.8922 ],
[ 1.7283, -37.6445, -195.8922 ],
[ 24.0223, -38.9338, -195.8922 ],
[ 45.7896, -40.9372, -195.8922 ],
[ 67.1182, -43.5357, -195.8922 ],
[ 88.0958, -46.6102, -195.8922 ],
[ 108.8101, -50.0418, -195.8922 ],
[ 129.3489, -53.7114, -195.8922 ],
[ 149.8, -57.5, -195.8922 ],
[ 170.251, -61.2886, -195.8922 ],
[ 190.7898, -64.9582, -195.8922 ],
[ 211.5042, -68.3898, -195.8922 ],
[ 232.4818, -71.4643, -195.8922 ],
[ 253.8104, -74.0628, -195.8922 ],
[ 275.5777, -76.0662, -195.8922 ],
[ 297.8716, -77.3555, -195.8922 ],
[ 320.7798, -77.8118, -195.8922 ]
]
# secondaries 2
[[geometry.pipes]]
index = 18
points_mm = [
[ 218.0202, -40.9377, -195.8922 ],
[ 225.1524, -41.3097, -195.8922 ],
[ 232.0008, -42.361, -195.8922 ],
[ 238.6058, -43.9946, -195.8922 ],
[ 245.008, -46.1134, -195.8922 ],
[ 251.2479, -48.6204, -195.8922 ],
[ 257.3662, -51.4185, -195.8922 ],
[ 263.4034, -54.4107, -195.8922 ],
[ 269.4, -57.5, -195.8922 ],
[ 275.3966, -60.5892, -195.8922 ],
[ 281.4338, -63.5815, -195.8922 ],
[ 287.552, -66.3796, -195.8922 ],
[ 293.792, -68.8866, -195.8922 ],
[ 300.1942, -71.0054, -195.8922 ],
[ 306.7992, -72.6389, -195.8922 ],
[ 313.6476, -73.6903, -195.8922 ],
[ 320.7798, -74.0623, -195.8922 ]
]
# secondaries 3
[[geometry.pipes]]
index = 19
points_mm = [
[ 38.6202, -37.5985, 195.8922 ],
[ 57.5671, -38.0455, 195.8922 ],
[ 75.9893, -39.3087, 195.8922 ],
[ 93.9616, -41.2717, 195.8922 ],
[ 111.5591, -43.8177, 195.8922 ],
[ 128.8567, -46.8301, 195.8922 ],
[ 145.9294, -50.1924, 195.8922 ],
[ 162.8522, -53.7879, 195.8922 ],
[ 179.7, -57.5, 195.8922 ],
[ 196.5478, -61.2121, 195.8922 ],
[ 213.4705, -64.8076, 195.8922 ],
[ 230.5433, -68.1699, 195.8922 ],
[ 247.8409, -71.1823, 195.8922 ],
[ 265.4383, -73.7283, 195.8922 ],
[ 283.4107, -75.6913, 195.8922 ],
[ 301.8329, -76.9545, 195.8922 ],
[ 320.7798, -77.4015, 195.8922 ]
]
# secondaries 4
[[geometry.pipes]]
index = 20
points_mm = [
[ 158.2202, -39.157, 195.8922 ],
[ 169.2733, -39.569, 195.8922 ],
[ 179.9691, -40.7334, 195.8922 ],
[ 190.3587, -42.5426, 195.8922 ],
[ 200.4932, -44.8892, 195.8922 ],
[ 210.4235, -47.6657, 195.8922 ],
[ 220.2007, -50.7647, 195.8922 ],
[ 229.8759, -54.0786, 195.8922 ],
[ 239.5, -57.5, 195.8922 ],
[ 249.1241, -60.9214, 195.8922 ],
[ 258.7993, -64.2353, 195.8922 ],
[ 268.5765, -67.3343, 195.8922 ],
[ 278.5068, -70.1108, 195.8922 ],
[ 288.6412, -72.4574, 195.8922 ],
[ 299.0309, -74.2666, 195.8922 ],
[ 309.7267, -75.431, 195.8922 ],
[ 320.7798, -75.843, 195.8922 ]
]
# bank_collectors 1
[[geometry.pipes]]
index = 21
points_mm = [
[ 357.6201, -80, -203.0101 ],
[ 393.845, -80, -205.237 ],
[ 428.4088, -80, -211.5304 ],
[ 461.549, -80, -221.3095 ],
[ 493.5029, -80, -233.9932 ],
[ 524.5076, -80, -249.0007 ],
[ 554.8005, -80, -265.751 ],
[ 584.6189, -80, -283.6631 ],
[ 614.2, -80, -302.1562 ],
[ 643.781, -80, -320.6493 ],
[ 673.5994, -80, -338.5614 ],
[ 703.8922, -80, -355.3117 ],
[ 734.897, -80, -370.3192 ],
[ 766.8508, -80, -383.0029 ],
[ 799.9911, -80, -392.782 ],
[ 834.5549, -80, -399.0755 ],
[ 870.7797, -80, -401.3024 ]
]
# bank_collectors 2
[[geometry.pipes]]
index = 22
points_mm = [
[ 357.6201, -80, 203.0101 ],
[ 393.845, -80, 205.237 ],
[ 428.4088, -80, 211.5304 ],
[ 461.549, -80, 221.3095 ],
[ 493.5029, -80, 233.9932 ],
[ 524.5076, -80, 249.0007 ],
[ 554.8005, -80, 265.751 ],
[ 584.6189, -80, 283.6631 ],
[ 614.2, -80, 302.1562 ],
[ 643.781, -80, 320.6493 ],
[ 673.5994, -80, 338.5614 ],
[ 703.8922, -80, 355.3117 ],
[ 734.897, -80, 370.3192 ],
[ 766.8508, -80, 383.0029 ],
[ 799.9911, -80, 392.782 ],
[ 834.5549, -80, 399.0755 ],
[ 870.7797, -80, 401.3024 ]
]
# tailpipes 1
[[geometry.pipes]]
index = 23
points_mm = [
[ 907.62, -80, -408.4202 ],
[ 2157.62, -80, -408.4202 ]
]
# tailpipes 2
[[geometry.pipes]]
index = 24
points_mm = [
[ 907.62, -80, 408.4202 ],
[ 2157.62, -80, 408.4202 ]
]
# crossover
[[geometry.pipes]]
index = 25
points_mm = [
[ 889.1999, -80, -390 ],
[ 889.1999, -80, 390 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 312.6411, 0 ]
size_mm = [ 478.4, 43.7429, 191.1445 ]
[[geometry.volumes]]
id = "junction_0"
center_mm = [ -39.6, -35, -195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_1"
center_mm = [ 199.6, -35, -195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_2"
center_mm = [ 20.2, -35, 195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_3"
center_mm = [ 139.8, -35, 195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_4"
center_mm = [ 339.2, -80, -195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_5"
center_mm = [ 339.2, -80, 195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_6"
center_mm = [ 889.1999, -80, -408.4202 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_7"
center_mm = [ 889.1999, -80, 408.4202 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]

View File

@@ -1,15 +1,17 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "v8-long"
name = "American 5.0 / long headers"
category = "V8 / American 5.0"
description = "900 mm primaries with H crossover"
description = "Extended swept headers / H crossover"
order = 10
cycle = "four_stroke"
[simulation]
cells_per_meter = 20.0
[engine]
bank_angle_deg = 56.0
collector_volume_litres = 1.5
cylinders = 8
initial_rpm = 1000.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 300.0
minimum_substeps = 1
phase_degrees = [0.0, 450.0, 270.0, 540.0, 630.0, 360.0, 180.0, 89.99998]
pipe_damping = 1.0
plenum_volume_litres = 4.0
starter_torque_nm = 180.0
throttle_diameter_mm = 65.0
topology = "american_v8"
@@ -31,18 +32,12 @@ burn_duration_deg = 65.0
burn_start_deg = 5.0
compression = 7.0
energy_variation = 0.05
exhaust_close_deg = 370.0
exhaust_open_deg = 140.0
exhaust_valve_count = 1
exhaust_valve_diameter_mm = 37.0
exhaust_valve_lift_mm = 10.0
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 570.0
intake_open_deg = 350.0
intake_valve_count = 1
intake_valve_diameter_mm = 45.0
intake_valve_lift_mm = 10.0
misfire_probability = 0.0
rod_mm = 129.0
stroke_mm = 76.2
@@ -101,51 +96,619 @@ system = "injection"
[junctions]
h_pipe = true
junction_volume_cc = 50.0
[[pipes]]
id = "intake"
cells = 3
diameter_mm = 70.0
length_mm = 180.0
[[pipes]]
id = "runners"
count = 8
cells = 2
diameter_mm = 38.0
length_mm = 120.0
[[pipes]]
id = "primaries"
count = 8
cells = 18
diameter_mm = 38.0
length_mm = 900.0
[[pipes]]
id = "secondaries"
count = 4
cells = 8
diameter_mm = 45.0
length_mm = 400.0
[[pipes]]
id = "bank_collectors"
count = 2
cells = 11
diameter_mm = 50.0
length_mm = 550.0
[[pipes]]
id = "tailpipes"
count = 2
cells = 25
diameter_mm = 50.0
length_mm = 1250.0
[[pipes]]
id = "crossover"
cells = 16
diameter_mm = 38.0
length_mm = 780.0
[cam.intake]
points = [
[0.000, 0.000000],
[350.000, 0.000000],
[355.000, 0.232908],
[360.000, 0.871525],
[365.000, 1.825695],
[370.000, 3.005259],
[375.000, 4.320060],
[380.000, 5.679940],
[385.000, 6.994741],
[390.000, 8.174305],
[395.000, 9.128475],
[400.000, 9.767092],
[405.000, 10.000000],
[515.000, 10.000000],
[520.000, 9.767092],
[525.000, 9.128475],
[530.000, 8.174305],
[535.000, 6.994741],
[540.000, 5.679940],
[545.000, 4.320060],
[550.000, 3.005259],
[555.000, 1.825695],
[560.000, 0.871525],
[565.000, 0.232908],
[570.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[140.000, 0.000000],
[145.000, 0.213693],
[150.000, 0.802170],
[155.000, 1.686529],
[160.000, 2.787869],
[165.000, 4.027287],
[170.000, 5.325881],
[175.000, 6.604751],
[180.000, 7.784992],
[185.000, 8.787704],
[190.000, 9.533985],
[195.000, 9.944933],
[197.500, 10.000000],
[312.500, 10.000000],
[317.500, 9.786307],
[322.500, 9.197830],
[327.500, 8.313471],
[332.500, 7.212131],
[337.500, 5.972713],
[342.500, 4.674119],
[347.500, 3.395249],
[352.500, 2.215008],
[357.500, 1.212296],
[362.500, 0.466015],
[367.500, 0.055067],
[370.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ -179.4, 0, 0 ]
tilt_deg = -28.0
[[geometry.cylinders]]
origin_mm = [ -59.8, 0, 0 ]
tilt_deg = -28.0
[[geometry.cylinders]]
origin_mm = [ 59.8, 0, 0 ]
tilt_deg = -28.0
[[geometry.cylinders]]
origin_mm = [ 179.4, 0, 0 ]
tilt_deg = -28.0
[[geometry.cylinders]]
origin_mm = [ -179.4, 0, 0 ]
tilt_deg = 28.0
[[geometry.cylinders]]
origin_mm = [ -59.8, 0, 0 ]
tilt_deg = 28.0
[[geometry.cylinders]]
origin_mm = [ 59.8, 0, 0 ]
tilt_deg = 28.0
[[geometry.cylinders]]
origin_mm = [ 179.4, 0, 0 ]
tilt_deg = 28.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -419.2, 312.6411, 0 ],
[ -239.2, 312.6411, 0 ]
]
# runners 1
[[geometry.pipes]]
index = 1
points_mm = [
[ -179.4, 290.7697, -44.7722 ],
[ -179.4, 170.7697, -44.7722 ]
]
# runners 2
[[geometry.pipes]]
index = 2
points_mm = [
[ -59.8, 290.7697, -44.7722 ],
[ -59.8, 170.7697, -44.7722 ]
]
# runners 3
[[geometry.pipes]]
index = 3
points_mm = [
[ 59.8, 290.7697, -44.7722 ],
[ 59.8, 170.7697, -44.7722 ]
]
# runners 4
[[geometry.pipes]]
index = 4
points_mm = [
[ 179.4, 290.7697, -44.7722 ],
[ 179.4, 170.7697, -44.7722 ]
]
# runners 5
[[geometry.pipes]]
index = 5
points_mm = [
[ -179.4, 290.7697, 44.7722 ],
[ -179.4, 170.7697, 44.7722 ]
]
# runners 6
[[geometry.pipes]]
index = 6
points_mm = [
[ -59.8, 290.7697, 44.7722 ],
[ -59.8, 170.7697, 44.7722 ]
]
# runners 7
[[geometry.pipes]]
index = 7
points_mm = [
[ 59.8, 290.7697, 44.7722 ],
[ 59.8, 170.7697, 44.7722 ]
]
# runners 8
[[geometry.pipes]]
index = 8
points_mm = [
[ 179.4, 290.7697, 44.7722 ],
[ 179.4, 170.7697, 44.7722 ]
]
# primaries 1
[[geometry.pipes]]
index = 9
points_mm = [
[ -179.4, 127.8412, -125.509 ],
[ -173.96, 112.9085, -143.9462 ],
[ -163.5939, 99.0794, -159.0769 ],
[ -149.0058, 86.1075, -171.1943 ],
[ -130.8994, 73.7466, -180.5915 ],
[ -109.9786, 61.7504, -187.562 ],
[ -86.9474, 49.8725, -192.3988 ],
[ -62.5096, 37.8667, -195.3952 ],
[ -37.369, 25.4867, -196.8445 ],
[ -12.2297, 12.4862, -197.0399 ],
[ 12.2045, -1.3811, -196.2746 ],
[ 35.2299, -16.3615, -194.8419 ],
[ 56.1423, -32.7012, -193.0351 ],
[ 74.2381, -50.6466, -191.1473 ],
[ 88.8132, -70.444, -189.4718 ],
[ 99.1639, -92.3396, -188.3019 ],
[ 104.5862, -116.5798, -187.9307 ]
]
# primaries 2
[[geometry.pipes]]
index = 10
points_mm = [
[ -59.8, 127.8412, -125.509 ],
[ -57.476, 114.8912, -140.4956 ],
[ -51.6334, 101.9895, -153.0346 ],
[ -42.7747, 89.0591, -163.3339 ],
[ -31.4028, 76.0233, -171.6011 ],
[ -18.0202, 62.8053, -178.0441 ],
[ -3.1297, 49.3284, -182.8705 ],
[ 12.7659, 35.5157, -186.2882 ],
[ 29.1641, 21.2904, -188.5048 ],
[ 45.562, 6.5757, -189.7281 ],
[ 61.4571, -8.7051, -190.166 ],
[ 76.3465, -24.6288, -190.026 ],
[ 89.7276, -41.2724, -189.516 ],
[ 101.0977, -58.7124, -188.8437 ],
[ 109.9541, -77.0258, -188.2169 ],
[ 115.7941, -96.2893, -187.8433 ],
[ 118.115, -116.5798, -187.9307 ]
]
# primaries 3
[[geometry.pipes]]
index = 11
points_mm = [
[ 59.8, 127.8412, -125.509 ],
[ 65.24, 112.9085, -143.9462 ],
[ 75.6061, 99.0794, -159.0769 ],
[ 90.1942, 86.1075, -171.1943 ],
[ 108.3006, 73.7466, -180.5915 ],
[ 129.2214, 61.7504, -187.562 ],
[ 152.2526, 49.8725, -192.3988 ],
[ 176.6904, 37.8667, -195.3952 ],
[ 201.831, 25.4867, -196.8445 ],
[ 226.9703, 12.4862, -197.0399 ],
[ 251.4045, -1.3811, -196.2746 ],
[ 274.4299, -16.3615, -194.8419 ],
[ 295.3423, -32.7012, -193.0351 ],
[ 313.4381, -50.6466, -191.1473 ],
[ 328.0132, -70.444, -189.4718 ],
[ 338.3639, -92.3396, -188.3019 ],
[ 343.7862, -116.5798, -187.9307 ]
]
# primaries 4
[[geometry.pipes]]
index = 12
points_mm = [
[ 179.4, 127.8412, -125.509 ],
[ 181.724, 114.8912, -140.4956 ],
[ 187.5666, 101.9895, -153.0346 ],
[ 196.4253, 89.0591, -163.3339 ],
[ 207.7972, 76.0233, -171.6011 ],
[ 221.1798, 62.8053, -178.0441 ],
[ 236.0703, 49.3284, -182.8705 ],
[ 251.9659, 35.5157, -186.2882 ],
[ 268.3641, 21.2904, -188.5048 ],
[ 284.762, 6.5757, -189.7281 ],
[ 300.6571, -8.7051, -190.166 ],
[ 315.5465, -24.6288, -190.026 ],
[ 328.9276, -41.2724, -189.516 ],
[ 340.2977, -58.7124, -188.8437 ],
[ 349.1541, -77.0258, -188.2169 ],
[ 354.9941, -96.2893, -187.8433 ],
[ 357.315, -116.5798, -187.9307 ]
]
# primaries 5
[[geometry.pipes]]
index = 13
points_mm = [
[ -179.4, 127.8412, 125.509 ],
[ -175.5507, 109.015, 145.6312 ],
[ -168.0603, 89.4647, 162.0723 ],
[ -157.2099, 69.4408, 175.1602 ],
[ -143.2803, 49.1937, 185.223 ],
[ -126.5526, 28.9742, 192.5886 ],
[ -107.3076, 9.0326, 197.5849 ],
[ -85.8263, -10.3803, 200.5401 ],
[ -62.3898, -29.0142, 201.7819 ],
[ -37.2788, -46.6185, 201.6385 ],
[ -10.7745, -62.9425, 200.4378 ],
[ 16.8423, -77.7358, 198.5078 ],
[ 45.2906, -90.7478, 196.1764 ],
[ 74.2894, -101.728, 193.7716 ],
[ 103.5579, -110.4258, 191.6215 ],
[ 132.815, -116.5907, 190.0539 ],
[ 161.7798, -119.9721, 189.3969 ]
]
# primaries 6
[[geometry.pipes]]
index = 14
points_mm = [
[ -59.8, 127.8412, 125.509 ],
[ -55.9507, 109.015, 145.6312 ],
[ -48.4603, 89.4647, 162.0723 ],
[ -37.6099, 69.4408, 175.1602 ],
[ -23.6803, 49.1937, 185.223 ],
[ -6.9526, 28.9742, 192.5886 ],
[ 12.2924, 9.0326, 197.5849 ],
[ 33.7737, -10.3803, 200.5401 ],
[ 57.2102, -29.0142, 201.7819 ],
[ 82.3212, -46.6185, 201.6385 ],
[ 108.8255, -62.9425, 200.4378 ],
[ 136.4423, -77.7358, 198.5078 ],
[ 164.8906, -90.7478, 196.1764 ],
[ 193.8894, -101.728, 193.7716 ],
[ 223.1579, -110.4258, 191.6215 ],
[ 252.415, -116.5907, 190.0539 ],
[ 281.3798, -119.9721, 189.3969 ]
]
# primaries 7
[[geometry.pipes]]
index = 15
points_mm = [
[ 59.8, 127.8412, 125.509 ],
[ 60.6291, 115.8136, 139.2457 ],
[ 64.2478, 103.3813, 150.8635 ],
[ 70.2577, 90.538, 160.5356 ],
[ 78.2604, 77.2777, 168.4348 ],
[ 87.8572, 63.5944, 174.7343 ],
[ 98.6499, 49.482, 179.607 ],
[ 110.2399, 34.9344, 183.226 ],
[ 122.2286, 19.9456, 185.7644 ],
[ 134.2178, 4.5093, 187.3951 ],
[ 145.8088, -11.3803, 188.2912 ],
[ 156.6031, -27.7294, 188.6258 ],
[ 166.2025, -44.5441, 188.5717 ],
[ 174.2082, -61.8304, 188.3022 ],
[ 180.2219, -79.5944, 187.9902 ],
[ 183.8452, -97.8422, 187.8086 ],
[ 184.6794, -116.5798, 187.9307 ]
]
# primaries 8
[[geometry.pipes]]
index = 16
points_mm = [
[ 179.4, 127.8412, 125.509 ],
[ 180.2291, 115.8135, 139.2456 ],
[ 183.8478, 103.3811, 150.8634 ],
[ 189.8577, 90.5378, 160.5354 ],
[ 197.8604, 77.2775, 168.4346 ],
[ 207.4573, 63.5942, 174.7341 ],
[ 218.2499, 49.4818, 179.6068 ],
[ 229.8399, 34.9342, 183.2258 ],
[ 241.8287, 19.9453, 185.7642 ],
[ 253.8178, 4.5092, 187.395 ],
[ 265.4088, -11.3804, 188.2911 ],
[ 276.2032, -27.7295, 188.6257 ],
[ 285.8025, -44.5441, 188.5717 ],
[ 293.8082, -61.8304, 188.3022 ],
[ 299.8219, -79.5944, 187.9901 ],
[ 303.4452, -97.8422, 187.8086 ],
[ 304.2794, -116.5798, 187.9307 ]
]
# secondaries 1
[[geometry.pipes]]
index = 17
points_mm = [
[ 138.8202, -137.1882, -195.8922 ],
[ 161.7283, -137.6445, -195.8922 ],
[ 184.0223, -138.9338, -195.8922 ],
[ 205.7896, -140.9372, -195.8922 ],
[ 227.1182, -143.5357, -195.8922 ],
[ 248.0958, -146.6102, -195.8922 ],
[ 268.8101, -150.0418, -195.8922 ],
[ 289.3489, -153.7114, -195.8922 ],
[ 309.8, -157.5, -195.8922 ],
[ 330.251, -161.2886, -195.8922 ],
[ 350.7898, -164.9582, -195.8922 ],
[ 371.5042, -168.3898, -195.8922 ],
[ 392.4818, -171.4643, -195.8922 ],
[ 413.8104, -174.0628, -195.8922 ],
[ 435.5777, -176.0662, -195.8922 ],
[ 457.8716, -177.3555, -195.8922 ],
[ 480.7798, -177.8118, -195.8922 ]
]
# secondaries 2
[[geometry.pipes]]
index = 18
points_mm = [
[ 378.0202, -140.9377, -195.8922 ],
[ 385.1524, -141.3097, -195.8922 ],
[ 392.0008, -142.361, -195.8922 ],
[ 398.6058, -143.9946, -195.8922 ],
[ 405.008, -146.1134, -195.8922 ],
[ 411.2479, -148.6204, -195.8922 ],
[ 417.3662, -151.4185, -195.8922 ],
[ 423.4034, -154.4107, -195.8922 ],
[ 429.4, -157.5, -195.8922 ],
[ 435.3966, -160.5892, -195.8922 ],
[ 441.4338, -163.5815, -195.8922 ],
[ 447.552, -166.3796, -195.8922 ],
[ 453.792, -168.8866, -195.8922 ],
[ 460.1942, -171.0054, -195.8922 ],
[ 466.7992, -172.6389, -195.8922 ],
[ 473.6476, -173.6903, -195.8922 ],
[ 480.7798, -174.0623, -195.8922 ]
]
# secondaries 3
[[geometry.pipes]]
index = 19
points_mm = [
[ 198.6202, -137.5985, 195.8922 ],
[ 217.5671, -138.0455, 195.8922 ],
[ 235.9893, -139.3087, 195.8922 ],
[ 253.9616, -141.2717, 195.8922 ],
[ 271.5591, -143.8177, 195.8922 ],
[ 288.8567, -146.8301, 195.8922 ],
[ 305.9294, -150.1924, 195.8922 ],
[ 322.8522, -153.7879, 195.8922 ],
[ 339.7, -157.5, 195.8922 ],
[ 356.5478, -161.2121, 195.8922 ],
[ 373.4705, -164.8076, 195.8922 ],
[ 390.5433, -168.1699, 195.8922 ],
[ 407.8409, -171.1823, 195.8922 ],
[ 425.4383, -173.7283, 195.8922 ],
[ 443.4107, -175.6913, 195.8922 ],
[ 461.8329, -176.9545, 195.8922 ],
[ 480.7798, -177.4015, 195.8922 ]
]
# secondaries 4
[[geometry.pipes]]
index = 20
points_mm = [
[ 318.2202, -139.157, 195.8922 ],
[ 329.2733, -139.569, 195.8922 ],
[ 339.9691, -140.7334, 195.8922 ],
[ 350.3587, -142.5426, 195.8922 ],
[ 360.4932, -144.8892, 195.8922 ],
[ 370.4235, -147.6657, 195.8922 ],
[ 380.2007, -150.7647, 195.8922 ],
[ 389.8759, -154.0786, 195.8922 ],
[ 399.5, -157.5, 195.8922 ],
[ 409.1241, -160.9214, 195.8922 ],
[ 418.7993, -164.2353, 195.8922 ],
[ 428.5765, -167.3343, 195.8922 ],
[ 438.5068, -170.1108, 195.8922 ],
[ 448.6412, -172.4574, 195.8922 ],
[ 459.0309, -174.2666, 195.8922 ],
[ 469.7267, -175.431, 195.8922 ],
[ 480.7798, -175.843, 195.8922 ]
]
# bank_collectors 1
[[geometry.pipes]]
index = 21
points_mm = [
[ 517.6201, -180, -203.0101 ],
[ 553.845, -180, -205.237 ],
[ 588.4088, -180, -211.5304 ],
[ 621.549, -180, -221.3095 ],
[ 653.5029, -180, -233.9932 ],
[ 684.5076, -180, -249.0007 ],
[ 714.8005, -180, -265.751 ],
[ 744.6189, -180, -283.6631 ],
[ 774.2, -180, -302.1562 ],
[ 803.781, -180, -320.6493 ],
[ 833.5994, -180, -338.5614 ],
[ 863.8922, -180, -355.3117 ],
[ 894.897, -180, -370.3192 ],
[ 926.8508, -180, -383.0029 ],
[ 959.9911, -180, -392.782 ],
[ 994.5549, -180, -399.0755 ],
[ 1030.7797, -180, -401.3024 ]
]
# bank_collectors 2
[[geometry.pipes]]
index = 22
points_mm = [
[ 517.6201, -180, 203.0101 ],
[ 553.845, -180, 205.237 ],
[ 588.4088, -180, 211.5304 ],
[ 621.549, -180, 221.3095 ],
[ 653.5029, -180, 233.9932 ],
[ 684.5076, -180, 249.0007 ],
[ 714.8005, -180, 265.751 ],
[ 744.6189, -180, 283.6631 ],
[ 774.2, -180, 302.1562 ],
[ 803.781, -180, 320.6493 ],
[ 833.5994, -180, 338.5614 ],
[ 863.8922, -180, 355.3117 ],
[ 894.897, -180, 370.3192 ],
[ 926.8508, -180, 383.0029 ],
[ 959.9911, -180, 392.782 ],
[ 994.5549, -180, 399.0755 ],
[ 1030.7797, -180, 401.3024 ]
]
# tailpipes 1
[[geometry.pipes]]
index = 23
points_mm = [
[ 1067.62, -180, -408.4202 ],
[ 2317.62, -180, -408.4202 ]
]
# tailpipes 2
[[geometry.pipes]]
index = 24
points_mm = [
[ 1067.62, -180, 408.4202 ],
[ 2317.62, -180, 408.4202 ]
]
# crossover
[[geometry.pipes]]
index = 25
points_mm = [
[ 1049.1999, -180, -390 ],
[ 1049.1999, -180, 390 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 312.6411, 0 ]
size_mm = [ 478.4, 43.7429, 191.1445 ]
[[geometry.volumes]]
id = "junction_0"
center_mm = [ 120.4, -135, -195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_1"
center_mm = [ 359.6, -135, -195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_2"
center_mm = [ 180.2, -135, 195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_3"
center_mm = [ 299.8, -135, 195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_4"
center_mm = [ 499.2, -180, -195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_5"
center_mm = [ 499.2, -180, 195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_6"
center_mm = [ 1049.1999, -180, -408.4202 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_7"
center_mm = [ 1049.1999, -180, 408.4202 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]

View File

@@ -1,15 +1,17 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "v8-short"
name = "American 5.0 / short headers"
category = "V8 / American 5.0"
description = "600 mm primaries with H crossover"
description = "Compact swept headers / H crossover"
order = 9
cycle = "four_stroke"
[simulation]
cells_per_meter = 20.0
[engine]
bank_angle_deg = 56.0
collector_volume_litres = 1.5
cylinders = 8
initial_rpm = 1000.0
initialize_phased_charge = true
@@ -18,7 +20,6 @@ maximum_load_torque_nm = 300.0
minimum_substeps = 1
phase_degrees = [0.0, 450.0, 270.0, 540.0, 630.0, 360.0, 180.0, 89.99998]
pipe_damping = 1.0
plenum_volume_litres = 4.0
starter_torque_nm = 180.0
throttle_diameter_mm = 65.0
topology = "american_v8"
@@ -31,18 +32,12 @@ burn_duration_deg = 65.0
burn_start_deg = 5.0
compression = 7.0
energy_variation = 0.05
exhaust_close_deg = 370.0
exhaust_open_deg = 140.0
exhaust_valve_count = 1
exhaust_valve_diameter_mm = 37.0
exhaust_valve_lift_mm = 10.0
fuel_lhv_j_per_kg = 4.4e+07
heat_transfer = 100.0
intake_close_deg = 570.0
intake_open_deg = 350.0
intake_valve_count = 1
intake_valve_diameter_mm = 45.0
intake_valve_lift_mm = 10.0
misfire_probability = 0.0
rod_mm = 129.0
stroke_mm = 76.2
@@ -101,51 +96,619 @@ system = "injection"
[junctions]
h_pipe = true
junction_volume_cc = 50.0
[[pipes]]
id = "intake"
cells = 3
diameter_mm = 70.0
length_mm = 180.0
[[pipes]]
id = "runners"
count = 8
cells = 2
diameter_mm = 38.0
length_mm = 120.0
[[pipes]]
id = "primaries"
count = 8
cells = 12
diameter_mm = 38.0
length_mm = 600.0
[[pipes]]
id = "secondaries"
count = 4
cells = 8
diameter_mm = 45.0
length_mm = 400.0
[[pipes]]
id = "bank_collectors"
count = 2
cells = 11
diameter_mm = 50.0
length_mm = 550.0
[[pipes]]
id = "tailpipes"
count = 2
cells = 25
diameter_mm = 50.0
length_mm = 1250.0
[[pipes]]
id = "crossover"
cells = 16
diameter_mm = 38.0
length_mm = 780.0
[cam.intake]
points = [
[0.000, 0.000000],
[350.000, 0.000000],
[355.000, 0.232908],
[360.000, 0.871525],
[365.000, 1.825695],
[370.000, 3.005259],
[375.000, 4.320060],
[380.000, 5.679940],
[385.000, 6.994741],
[390.000, 8.174305],
[395.000, 9.128475],
[400.000, 9.767092],
[405.000, 10.000000],
[515.000, 10.000000],
[520.000, 9.767092],
[525.000, 9.128475],
[530.000, 8.174305],
[535.000, 6.994741],
[540.000, 5.679940],
[545.000, 4.320060],
[550.000, 3.005259],
[555.000, 1.825695],
[560.000, 0.871525],
[565.000, 0.232908],
[570.000, 0.000000],
[720.000, 0.000000]
]
[cam.exhaust]
points = [
[0.000, 0.000000],
[140.000, 0.000000],
[145.000, 0.213693],
[150.000, 0.802170],
[155.000, 1.686529],
[160.000, 2.787869],
[165.000, 4.027287],
[170.000, 5.325881],
[175.000, 6.604751],
[180.000, 7.784992],
[185.000, 8.787704],
[190.000, 9.533985],
[195.000, 9.944933],
[197.500, 10.000000],
[312.500, 10.000000],
[317.500, 9.786307],
[322.500, 9.197830],
[327.500, 8.313471],
[332.500, 7.212131],
[337.500, 5.972713],
[342.500, 4.674119],
[347.500, 3.395249],
[352.500, 2.215008],
[357.500, 1.212296],
[362.500, 0.466015],
[367.500, 0.055067],
[370.000, 0.000000],
[720.000, 0.000000]
]
[valves.intake]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valves.exhaust]
mass_g = 35.0
spring_rate_n_per_mm = 38.0
damping_n_s_per_m = 24.0
seat_force_n = 75.0
maximum_lift_mm = 15.0
seat_restitution = 0.005
cam_restitution = 0.01
stop_restitution = 0.01
[valve_sound]
mode_hz = 3600.0
effective_mass_g = 180.0
radiation_area_cm2 = 10.0
damping_ratio = 0.9
[[geometry.cylinders]]
origin_mm = [ -179.4, 0, 0 ]
tilt_deg = -28.0
[[geometry.cylinders]]
origin_mm = [ -59.8, 0, 0 ]
tilt_deg = -28.0
[[geometry.cylinders]]
origin_mm = [ 59.8, 0, 0 ]
tilt_deg = -28.0
[[geometry.cylinders]]
origin_mm = [ 179.4, 0, 0 ]
tilt_deg = -28.0
[[geometry.cylinders]]
origin_mm = [ -179.4, 0, 0 ]
tilt_deg = 28.0
[[geometry.cylinders]]
origin_mm = [ -59.8, 0, 0 ]
tilt_deg = 28.0
[[geometry.cylinders]]
origin_mm = [ 59.8, 0, 0 ]
tilt_deg = 28.0
[[geometry.cylinders]]
origin_mm = [ 179.4, 0, 0 ]
tilt_deg = 28.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -419.2, 312.6411, 0 ],
[ -239.2, 312.6411, 0 ]
]
# runners 1
[[geometry.pipes]]
index = 1
points_mm = [
[ -179.4, 290.7697, -44.7722 ],
[ -179.4, 170.7697, -44.7722 ]
]
# runners 2
[[geometry.pipes]]
index = 2
points_mm = [
[ -59.8, 290.7697, -44.7722 ],
[ -59.8, 170.7697, -44.7722 ]
]
# runners 3
[[geometry.pipes]]
index = 3
points_mm = [
[ 59.8, 290.7697, -44.7722 ],
[ 59.8, 170.7697, -44.7722 ]
]
# runners 4
[[geometry.pipes]]
index = 4
points_mm = [
[ 179.4, 290.7697, -44.7722 ],
[ 179.4, 170.7697, -44.7722 ]
]
# runners 5
[[geometry.pipes]]
index = 5
points_mm = [
[ -179.4, 290.7697, 44.7722 ],
[ -179.4, 170.7697, 44.7722 ]
]
# runners 6
[[geometry.pipes]]
index = 6
points_mm = [
[ -59.8, 290.7697, 44.7722 ],
[ -59.8, 170.7697, 44.7722 ]
]
# runners 7
[[geometry.pipes]]
index = 7
points_mm = [
[ 59.8, 290.7697, 44.7722 ],
[ 59.8, 170.7697, 44.7722 ]
]
# runners 8
[[geometry.pipes]]
index = 8
points_mm = [
[ 179.4, 290.7697, 44.7722 ],
[ 179.4, 170.7697, 44.7722 ]
]
# primaries 1
[[geometry.pipes]]
index = 9
points_mm = [
[ -179.4, 127.8412, -125.509 ],
[ -177.371, 121.2287, -133.8812 ],
[ -173.803, 114.8124, -141.5497 ],
[ -168.9157, 108.526, -148.5375 ],
[ -162.9293, 102.3031, -154.8678 ],
[ -156.0637, 96.0772, -160.5636 ],
[ -148.5387, 89.7821, -165.6479 ],
[ -140.5743, 83.3514, -170.1438 ],
[ -132.3905, 76.7187, -174.0743 ],
[ -124.2073, 69.8176, -177.4624 ],
[ -116.2444, 62.5817, -180.3313 ],
[ -108.722, 54.9447, -182.704 ],
[ -101.8599, 46.8403, -184.6035 ],
[ -95.8781, 38.202, -186.0529 ],
[ -90.9965, 28.9634, -187.0752 ],
[ -87.4351, 19.0583, -187.6934 ],
[ -85.4138, 8.4202, -187.9307 ]
]
# primaries 2
[[geometry.pipes]]
index = 10
points_mm = [
[ -59.8, 127.8412, -125.509 ],
[ -59.7929, 122.0275, -132.471 ],
[ -60.0906, 115.9827, -139.0794 ],
[ -60.6495, 109.7091, -145.3225 ],
[ -61.4262, 103.2093, -151.1886 ],
[ -62.3771, 96.4855, -156.6662 ],
[ -63.4586, 89.54, -161.7434 ],
[ -64.6273, 82.3754, -166.4087 ],
[ -65.8396, 74.9939, -170.6503 ],
[ -67.052, 67.3979, -174.4566 ],
[ -68.221, 59.5899, -177.8159 ],
[ -69.303, 51.5721, -180.7165 ],
[ -70.2545, 43.3469, -183.1467 ],
[ -71.032, 34.9168, -185.095 ],
[ -71.5919, 26.284, -186.5495 ],
[ -71.8908, 17.4511, -187.4986 ],
[ -71.885, 8.4202, -187.9307 ]
]
# primaries 3
[[geometry.pipes]]
index = 11
points_mm = [
[ 59.8, 127.8412, -125.509 ],
[ 61.829, 121.2287, -133.8812 ],
[ 65.397, 114.8124, -141.5497 ],
[ 70.2843, 108.526, -148.5375 ],
[ 76.2707, 102.3031, -154.8678 ],
[ 83.1363, 96.0772, -160.5636 ],
[ 90.6613, 89.7821, -165.6479 ],
[ 98.6257, 83.3514, -170.1438 ],
[ 106.8095, 76.7187, -174.0743 ],
[ 114.9927, 69.8176, -177.4624 ],
[ 122.9556, 62.5817, -180.3313 ],
[ 130.478, 54.9447, -182.704 ],
[ 137.3401, 46.8403, -184.6035 ],
[ 143.3219, 38.202, -186.0529 ],
[ 148.2035, 28.9634, -187.0752 ],
[ 151.7649, 19.0583, -187.6934 ],
[ 153.7862, 8.4202, -187.9307 ]
]
# primaries 4
[[geometry.pipes]]
index = 12
points_mm = [
[ 179.4, 127.8412, -125.509 ],
[ 179.4071, 122.0275, -132.471 ],
[ 179.1094, 115.9827, -139.0794 ],
[ 178.5505, 109.7091, -145.3225 ],
[ 177.7738, 103.2093, -151.1886 ],
[ 176.8229, 96.4855, -156.6662 ],
[ 175.7414, 89.54, -161.7434 ],
[ 174.5727, 82.3754, -166.4087 ],
[ 173.3604, 74.9939, -170.6503 ],
[ 172.148, 67.3979, -174.4566 ],
[ 170.979, 59.5899, -177.8159 ],
[ 169.897, 51.5721, -180.7165 ],
[ 168.9455, 43.3469, -183.1467 ],
[ 168.168, 34.9168, -185.095 ],
[ 167.6081, 26.284, -186.5495 ],
[ 167.3092, 17.4511, -187.4986 ],
[ 167.315, 8.4202, -187.9307 ]
]
# primaries 5
[[geometry.pipes]]
index = 13
points_mm = [
[ -179.4, 127.8412, 125.509 ],
[ -177.6937, 118.7511, 135.5531 ],
[ -174.4999, 109.2369, 144.5084 ],
[ -169.9162, 99.4299, 152.4353 ],
[ -164.0401, 89.4615, 159.3942 ],
[ -156.9692, 79.4628, 165.4457 ],
[ -148.8009, 69.5652, 170.65 ],
[ -139.6328, 59.8999, 175.0676 ],
[ -129.5624, 50.5982, 178.759 ],
[ -118.6873, 41.7914, 181.7846 ],
[ -107.1049, 33.6108, 184.2048 ],
[ -94.9128, 26.1876, 186.0801 ],
[ -82.2085, 19.6531, 187.4708 ],
[ -69.0896, 14.1386, 188.4374 ],
[ -55.6536, 9.7754, 189.0404 ],
[ -41.9979, 6.6947, 189.34 ],
[ -28.2202, 5.0279, 189.3969 ]
]
# primaries 6
[[geometry.pipes]]
index = 14
points_mm = [
[ -59.8, 127.8412, 125.509 ],
[ -58.0937, 118.7511, 135.5531 ],
[ -54.8999, 109.2369, 144.5084 ],
[ -50.3162, 99.4299, 152.4353 ],
[ -44.4401, 89.4615, 159.3942 ],
[ -37.3692, 79.4628, 165.4457 ],
[ -29.2009, 69.5652, 170.65 ],
[ -20.0328, 59.8999, 175.0676 ],
[ -9.9624, 50.5982, 178.759 ],
[ 0.9127, 41.7914, 181.7846 ],
[ 12.4951, 33.6108, 184.2048 ],
[ 24.6872, 26.1876, 186.0801 ],
[ 37.3915, 19.6531, 187.4708 ],
[ 50.5104, 14.1386, 188.4374 ],
[ 63.9464, 9.7754, 189.0404 ],
[ 77.6021, 6.6947, 189.34 ],
[ 91.3798, 5.0279, 189.3969 ]
]
# primaries 7
[[geometry.pipes]]
index = 15
points_mm = [
[ 59.8, 127.8412, 125.509 ],
[ 58.7638, 121.5646, 133.1409 ],
[ 56.5137, 115.2888, 140.2458 ],
[ 53.2231, 108.9797, 146.83 ],
[ 49.0655, 102.6033, 152.8998 ],
[ 44.2144, 96.1253, 158.4616 ],
[ 38.8431, 89.5118, 163.5217 ],
[ 33.1251, 82.7287, 168.0866 ],
[ 27.2338, 75.7417, 172.1625 ],
[ 21.3427, 68.517, 175.7558 ],
[ 15.6253, 61.0203, 178.8729 ],
[ 10.2549, 53.2175, 181.5202 ],
[ 5.405, 45.0747, 183.7039 ],
[ 1.2491, 36.5576, 185.4305 ],
[ -2.0394, 27.6322, 186.7062 ],
[ -4.2872, 18.2645, 187.5375 ],
[ -5.3206, 8.4202, 187.9307 ]
]
# primaries 8
[[geometry.pipes]]
index = 16
points_mm = [
[ 179.4, 127.8412, 125.509 ],
[ 178.3638, 121.5646, 133.1409 ],
[ 176.1137, 115.2887, 140.2457 ],
[ 172.8231, 108.9796, 146.8299 ],
[ 168.6655, 102.6031, 152.8997 ],
[ 163.8144, 96.1252, 158.4614 ],
[ 158.4431, 89.5117, 163.5216 ],
[ 152.7251, 82.7285, 168.0865 ],
[ 146.8338, 75.7416, 172.1624 ],
[ 140.9428, 68.5169, 175.7558 ],
[ 135.2253, 61.0202, 178.8729 ],
[ 129.8549, 53.2175, 181.5201 ],
[ 125.005, 45.0747, 183.7039 ],
[ 120.8491, 36.5576, 185.4304 ],
[ 117.5606, 27.6322, 186.7062 ],
[ 115.3128, 18.2645, 187.5375 ],
[ 114.2794, 8.4202, 187.9307 ]
]
# secondaries 1
[[geometry.pipes]]
index = 17
points_mm = [
[ -51.1798, -12.1882, -195.8922 ],
[ -28.2717, -12.6445, -195.8922 ],
[ -5.9777, -13.9338, -195.8922 ],
[ 15.7896, -15.9372, -195.8922 ],
[ 37.1182, -18.5357, -195.8922 ],
[ 58.0958, -21.6102, -195.8922 ],
[ 78.8101, -25.0418, -195.8922 ],
[ 99.3489, -28.7114, -195.8922 ],
[ 119.8, -32.5, -195.8922 ],
[ 140.251, -36.2886, -195.8922 ],
[ 160.7898, -39.9582, -195.8922 ],
[ 181.5042, -43.3898, -195.8922 ],
[ 202.4818, -46.4643, -195.8922 ],
[ 223.8104, -49.0628, -195.8922 ],
[ 245.5777, -51.0662, -195.8922 ],
[ 267.8716, -52.3555, -195.8922 ],
[ 290.7798, -52.8118, -195.8922 ]
]
# secondaries 2
[[geometry.pipes]]
index = 18
points_mm = [
[ 188.0202, -15.9377, -195.8922 ],
[ 195.1524, -16.3097, -195.8922 ],
[ 202.0008, -17.361, -195.8922 ],
[ 208.6058, -18.9946, -195.8922 ],
[ 215.008, -21.1134, -195.8922 ],
[ 221.2479, -23.6204, -195.8922 ],
[ 227.3662, -26.4185, -195.8922 ],
[ 233.4034, -29.4107, -195.8922 ],
[ 239.4, -32.5, -195.8922 ],
[ 245.3966, -35.5892, -195.8922 ],
[ 251.4338, -38.5815, -195.8922 ],
[ 257.552, -41.3796, -195.8922 ],
[ 263.792, -43.8866, -195.8922 ],
[ 270.1942, -46.0054, -195.8922 ],
[ 276.7992, -47.6389, -195.8922 ],
[ 283.6476, -48.6903, -195.8922 ],
[ 290.7798, -49.0623, -195.8922 ]
]
# secondaries 3
[[geometry.pipes]]
index = 19
points_mm = [
[ 8.6202, -12.5985, 195.8922 ],
[ 27.5671, -13.0455, 195.8922 ],
[ 45.9893, -14.3087, 195.8922 ],
[ 63.9616, -16.2717, 195.8922 ],
[ 81.5591, -18.8177, 195.8922 ],
[ 98.8567, -21.8301, 195.8922 ],
[ 115.9294, -25.1924, 195.8922 ],
[ 132.8522, -28.7879, 195.8922 ],
[ 149.7, -32.5, 195.8922 ],
[ 166.5478, -36.2121, 195.8922 ],
[ 183.4705, -39.8076, 195.8922 ],
[ 200.5433, -43.1699, 195.8922 ],
[ 217.8409, -46.1823, 195.8922 ],
[ 235.4383, -48.7283, 195.8922 ],
[ 253.4107, -50.6913, 195.8922 ],
[ 271.8329, -51.9545, 195.8922 ],
[ 290.7798, -52.4015, 195.8922 ]
]
# secondaries 4
[[geometry.pipes]]
index = 20
points_mm = [
[ 128.2202, -14.157, 195.8922 ],
[ 139.2733, -14.569, 195.8922 ],
[ 149.9691, -15.7334, 195.8922 ],
[ 160.3587, -17.5426, 195.8922 ],
[ 170.4932, -19.8892, 195.8922 ],
[ 180.4235, -22.6657, 195.8922 ],
[ 190.2007, -25.7647, 195.8922 ],
[ 199.8759, -29.0786, 195.8922 ],
[ 209.5, -32.5, 195.8922 ],
[ 219.1241, -35.9214, 195.8922 ],
[ 228.7993, -39.2353, 195.8922 ],
[ 238.5765, -42.3343, 195.8922 ],
[ 248.5068, -45.1108, 195.8922 ],
[ 258.6412, -47.4574, 195.8922 ],
[ 269.0309, -49.2666, 195.8922 ],
[ 279.7267, -50.431, 195.8922 ],
[ 290.7798, -50.843, 195.8922 ]
]
# bank_collectors 1
[[geometry.pipes]]
index = 21
points_mm = [
[ 327.6201, -55, -203.0101 ],
[ 363.845, -55, -205.237 ],
[ 398.4088, -55, -211.5304 ],
[ 431.549, -55, -221.3095 ],
[ 463.5029, -55, -233.9932 ],
[ 494.5076, -55, -249.0007 ],
[ 524.8005, -55, -265.751 ],
[ 554.6189, -55, -283.6631 ],
[ 584.2, -55, -302.1562 ],
[ 613.781, -55, -320.6493 ],
[ 643.5994, -55, -338.5614 ],
[ 673.8922, -55, -355.3117 ],
[ 704.897, -55, -370.3192 ],
[ 736.8508, -55, -383.0029 ],
[ 769.9911, -55, -392.782 ],
[ 804.5549, -55, -399.0755 ],
[ 840.7797, -55, -401.3024 ]
]
# bank_collectors 2
[[geometry.pipes]]
index = 22
points_mm = [
[ 327.6201, -55, 203.0101 ],
[ 363.845, -55, 205.237 ],
[ 398.4088, -55, 211.5304 ],
[ 431.549, -55, 221.3095 ],
[ 463.5029, -55, 233.9932 ],
[ 494.5076, -55, 249.0007 ],
[ 524.8005, -55, 265.751 ],
[ 554.6189, -55, 283.6631 ],
[ 584.2, -55, 302.1562 ],
[ 613.781, -55, 320.6493 ],
[ 643.5994, -55, 338.5614 ],
[ 673.8922, -55, 355.3117 ],
[ 704.897, -55, 370.3192 ],
[ 736.8508, -55, 383.0029 ],
[ 769.9911, -55, 392.782 ],
[ 804.5549, -55, 399.0755 ],
[ 840.7797, -55, 401.3024 ]
]
# tailpipes 1
[[geometry.pipes]]
index = 23
points_mm = [
[ 877.62, -55, -408.4202 ],
[ 2127.62, -55, -408.4202 ]
]
# tailpipes 2
[[geometry.pipes]]
index = 24
points_mm = [
[ 877.62, -55, 408.4202 ],
[ 2127.62, -55, 408.4202 ]
]
# crossover
[[geometry.pipes]]
index = 25
points_mm = [
[ 859.1999, -55, -390 ],
[ 859.1999, -55, 390 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ 0, 312.6411, 0 ]
size_mm = [ 478.4, 43.7429, 191.1445 ]
[[geometry.volumes]]
id = "junction_0"
center_mm = [ -69.6, -10, -195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_1"
center_mm = [ 169.6, -10, -195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_2"
center_mm = [ -9.8, -10, 195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_3"
center_mm = [ 109.8, -10, 195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_4"
center_mm = [ 309.2, -55, -195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_5"
center_mm = [ 309.2, -55, 195.8922 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_6"
center_mm = [ 859.1999, -55, -408.4202 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]
[[geometry.volumes]]
id = "junction_7"
center_mm = [ 859.1999, -55, 408.4202 ]
size_mm = [ 36.8403, 36.8403, 36.8403 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "yz-fat"
name = "125 / wide belly chamber"
category = "Two-stroke / 125 cc"
@@ -7,16 +7,19 @@ description = "Estimated chamber geometry / same 125 cc engine"
order = 14
cycle = "two_stroke"
[simulation]
cells_per_meter = 100.0
[two_stroke]
reed_pipe_diameter_mm = 40
transfer_pipe_diameter_mm = 32
initial_rpm = 3000.0
starter_torque_nm = 8.0
maximum_load_torque_nm = 10.0
crankshaft_inertia_kg_m2 = 0.01
bore_mm = 54.0
carburetor_diameter_mm = 38.1
crankcase_clearance_cc = 150.0
exhaust = "racing"
exhaust_cell_length_mm = 10.0
exhaust_duration_deg = 190.0
exhaust_width_mm = 36.0
friction_bar = 0.4
@@ -66,3 +69,69 @@ final_drive_ratio = 3.769231
mass_kg = 170
wheel_radius_mm = 331.3
gear_ratios = [2.384615, 1.933333, 1.588235, 1.352941, 1.176471, 1.055556]
[[geometry.cylinders]]
origin_mm = [ 0, 0, 0 ]
tilt_deg = 0.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -235.9365, 16.1836, 0 ],
[ -135.9365, 16.1836, 0 ]
]
# reed passage
[[geometry.pipes]]
index = 1
points_mm = [
[ -56.5664, 16.1836, 0 ],
[ -26.5664, 16.1836, 0 ]
]
# transfer passage
[[geometry.pipes]]
index = 2
points_mm = [
[ 26.5664, 42.75, 0 ],
[ 30.8157, 45.5218, 0 ],
[ 34.5021, 48.2991, 0 ],
[ 37.6254, 51.0817, 0 ],
[ 40.1858, 53.8698, 0 ],
[ 42.1833, 56.6633, 0 ],
[ 43.6178, 59.4623, 0 ],
[ 44.4893, 62.2667, 0 ],
[ 44.7979, 65.0764, 0 ],
[ 44.5435, 67.8916, 0 ],
[ 43.7262, 70.7123, 0 ],
[ 42.3459, 73.5383, 0 ],
[ 40.4026, 76.3698, 0 ],
[ 37.8964, 79.2067, 0 ],
[ 34.8272, 82.0491, 0 ],
[ 31.1951, 84.8968, 0 ],
[ 27, 87.75, 0 ]
]
# exhaust chamber
[[geometry.pipes]]
index = 3
points_mm = [
[ 27, 112.25, 0 ],
[ 177.3508, 57.5268, 0 ],
[ 253.4221, -105.6086, 0 ],
[ 144.4426, -261.2475, 0 ],
[ 14.4426, -261.2475, 0 ],
[ -182.8929, -189.4233, 0 ],
[ -422.8929, -189.4233, 0 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ -96.2515, 16.1836, 0 ]
size_mm = [ 79.3701, 79.3701, 79.3701 ]
[[geometry.volumes]]
id = "crankcase"
center_mm = [ 0, 16.1836, 0 ]
size_mm = [ 53.1329, 53.1329, 53.1329 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "yz-racing"
name = "YZ125 / expansion chamber"
category = "Two-stroke / 125 cc"
@@ -7,16 +7,19 @@ description = "Racing pipe / calibrated carb / 3% idle stop"
order = 1
cycle = "two_stroke"
[simulation]
cells_per_meter = 100.0
[two_stroke]
reed_pipe_diameter_mm = 40
transfer_pipe_diameter_mm = 32
initial_rpm = 3000.0
starter_torque_nm = 8.0
maximum_load_torque_nm = 10.0
crankshaft_inertia_kg_m2 = 0.01
bore_mm = 54.0
carburetor_diameter_mm = 38.1
crankcase_clearance_cc = 150.0
exhaust = "racing"
exhaust_cell_length_mm = 10.0
exhaust_duration_deg = 190.0
exhaust_width_mm = 36.0
friction_bar = 0.4
@@ -66,3 +69,69 @@ final_drive_ratio = 3.769231
mass_kg = 170
wheel_radius_mm = 331.3
gear_ratios = [2.384615, 1.933333, 1.588235, 1.352941, 1.176471, 1.055556]
[[geometry.cylinders]]
origin_mm = [ 0, 0, 0 ]
tilt_deg = 0.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -235.9365, 16.1836, 0 ],
[ -135.9365, 16.1836, 0 ]
]
# reed passage
[[geometry.pipes]]
index = 1
points_mm = [
[ -56.5664, 16.1836, 0 ],
[ -26.5664, 16.1836, 0 ]
]
# transfer passage
[[geometry.pipes]]
index = 2
points_mm = [
[ 26.5664, 42.75, 0 ],
[ 30.8157, 45.5218, 0 ],
[ 34.5021, 48.2991, 0 ],
[ 37.6254, 51.0817, 0 ],
[ 40.1858, 53.8698, 0 ],
[ 42.1833, 56.6633, 0 ],
[ 43.6178, 59.4623, 0 ],
[ 44.4893, 62.2667, 0 ],
[ 44.7979, 65.0764, 0 ],
[ 44.5435, 67.8916, 0 ],
[ 43.7262, 70.7123, 0 ],
[ 42.3459, 73.5383, 0 ],
[ 40.4026, 76.3698, 0 ],
[ 37.8964, 79.2067, 0 ],
[ 34.8272, 82.0491, 0 ],
[ 31.1951, 84.8968, 0 ],
[ 27, 87.75, 0 ]
]
# exhaust chamber
[[geometry.pipes]]
index = 3
points_mm = [
[ 27, 112.25, 0 ],
[ 177.3508, 57.5268, 0 ],
[ 253.4221, -105.6086, 0 ],
[ 144.4426, -261.2475, 0 ],
[ 14.4426, -261.2475, 0 ],
[ -182.8929, -189.4233, 0 ],
[ -422.8929, -189.4233, 0 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ -96.2515, 16.1836, 0 ]
size_mm = [ 79.3701, 79.3701, 79.3701 ]
[[geometry.volumes]]
id = "crankcase"
center_mm = [ 0, 16.1836, 0 ]
size_mm = [ 53.1329, 53.1329, 53.1329 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "yz-short"
name = "125 / short high-RPM chamber"
category = "Two-stroke / 125 cc"
@@ -7,16 +7,19 @@ description = "Estimated chamber geometry / same 125 cc engine"
order = 13
cycle = "two_stroke"
[simulation]
cells_per_meter = 100.0
[two_stroke]
reed_pipe_diameter_mm = 40
transfer_pipe_diameter_mm = 32
initial_rpm = 3000.0
starter_torque_nm = 8.0
maximum_load_torque_nm = 10.0
crankshaft_inertia_kg_m2 = 0.01
bore_mm = 54.0
carburetor_diameter_mm = 38.1
crankcase_clearance_cc = 150.0
exhaust = "racing"
exhaust_cell_length_mm = 10.0
exhaust_duration_deg = 190.0
exhaust_width_mm = 36.0
friction_bar = 0.4
@@ -66,3 +69,69 @@ final_drive_ratio = 3.769231
mass_kg = 170
wheel_radius_mm = 331.3
gear_ratios = [2.384615, 1.933333, 1.588235, 1.352941, 1.176471, 1.055556]
[[geometry.cylinders]]
origin_mm = [ 0, 0, 0 ]
tilt_deg = 0.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -235.9365, 16.1836, 0 ],
[ -135.9365, 16.1836, 0 ]
]
# reed passage
[[geometry.pipes]]
index = 1
points_mm = [
[ -56.5664, 16.1836, 0 ],
[ -26.5664, 16.1836, 0 ]
]
# transfer passage
[[geometry.pipes]]
index = 2
points_mm = [
[ 26.5664, 42.75, 0 ],
[ 30.8157, 45.5218, 0 ],
[ 34.5021, 48.2991, 0 ],
[ 37.6254, 51.0817, 0 ],
[ 40.1858, 53.8698, 0 ],
[ 42.1833, 56.6633, 0 ],
[ 43.6178, 59.4623, 0 ],
[ 44.4893, 62.2667, 0 ],
[ 44.7979, 65.0764, 0 ],
[ 44.5435, 67.8916, 0 ],
[ 43.7262, 70.7123, 0 ],
[ 42.3459, 73.5383, 0 ],
[ 40.4026, 76.3698, 0 ],
[ 37.8964, 79.2067, 0 ],
[ 34.8272, 82.0491, 0 ],
[ 31.1951, 84.8968, 0 ],
[ 27, 87.75, 0 ]
]
# exhaust chamber
[[geometry.pipes]]
index = 3
points_mm = [
[ 27, 112.25, 0 ],
[ 150.2877, 67.377, 0 ],
[ 212.6661, -66.3941, 0 ],
[ 123.3029, -194.018, 0 ],
[ 16.7029, -194.018, 0 ],
[ -145.1122, -135.1221, 0 ],
[ -341.9122, -135.1221, 0 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ -96.2515, 16.1836, 0 ]
size_mm = [ 79.3701, 79.3701, 79.3701 ]
[[geometry.volumes]]
id = "crankcase"
center_mm = [ 0, 16.1836, 0 ]
size_mm = [ 53.1329, 53.1329, 53.1329 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "yz-straight"
name = "YZ125 / straight exhaust"
category = "Two-stroke / 125 cc"
@@ -7,16 +7,19 @@ description = "Straight exhaust / calibrated pilot and needle / 3% idle stop"
order = 2
cycle = "two_stroke"
[simulation]
cells_per_meter = 100.0
[two_stroke]
reed_pipe_diameter_mm = 40
transfer_pipe_diameter_mm = 32
initial_rpm = 3000.0
starter_torque_nm = 8.0
maximum_load_torque_nm = 10.0
crankshaft_inertia_kg_m2 = 0.01
bore_mm = 54.0
carburetor_diameter_mm = 38.1
crankcase_clearance_cc = 150.0
exhaust = "straight"
exhaust_cell_length_mm = 10.0
exhaust_duration_deg = 190.0
exhaust_width_mm = 36.0
friction_bar = 0.4
@@ -66,3 +69,64 @@ final_drive_ratio = 3.769231
mass_kg = 170
wheel_radius_mm = 331.3
gear_ratios = [2.384615, 1.933333, 1.588235, 1.352941, 1.176471, 1.055556]
[[geometry.cylinders]]
origin_mm = [ 0, 0, 0 ]
tilt_deg = 0.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -235.9365, 16.1836, 0 ],
[ -135.9365, 16.1836, 0 ]
]
# reed passage
[[geometry.pipes]]
index = 1
points_mm = [
[ -56.5664, 16.1836, 0 ],
[ -26.5664, 16.1836, 0 ]
]
# transfer passage
[[geometry.pipes]]
index = 2
points_mm = [
[ 26.5664, 42.75, 0 ],
[ 30.8157, 45.5218, 0 ],
[ 34.5021, 48.2991, 0 ],
[ 37.6254, 51.0817, 0 ],
[ 40.1858, 53.8698, 0 ],
[ 42.1833, 56.6633, 0 ],
[ 43.6178, 59.4623, 0 ],
[ 44.4893, 62.2667, 0 ],
[ 44.7979, 65.0764, 0 ],
[ 44.5435, 67.8916, 0 ],
[ 43.7262, 70.7123, 0 ],
[ 42.3459, 73.5383, 0 ],
[ 40.4026, 76.3698, 0 ],
[ 37.8964, 79.2067, 0 ],
[ 34.8272, 82.0491, 0 ],
[ 31.1951, 84.8968, 0 ],
[ 27, 87.75, 0 ]
]
# exhaust chamber
[[geometry.pipes]]
index = 3
points_mm = [
[ 27, 112.25, 0 ],
[ 1137, 112.25, 0 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ -96.2515, 16.1836, 0 ]
size_mm = [ 79.3701, 79.3701, 79.3701 ]
[[geometry.volumes]]
id = "crankcase"
center_mm = [ 0, 16.1836, 0 ]
size_mm = [ 53.1329, 53.1329, 53.1329 ]

View File

@@ -1,5 +1,5 @@
# Engine definition. Dimensional keys state their units.
schema_version = 1
schema_version = 2
id = "yz-torque"
name = "125 / long torque chamber"
category = "Two-stroke / 125 cc"
@@ -7,16 +7,19 @@ description = "Estimated chamber geometry / same 125 cc engine"
order = 12
cycle = "two_stroke"
[simulation]
cells_per_meter = 100.0
[two_stroke]
reed_pipe_diameter_mm = 40
transfer_pipe_diameter_mm = 32
initial_rpm = 3000.0
starter_torque_nm = 8.0
maximum_load_torque_nm = 10.0
crankshaft_inertia_kg_m2 = 0.01
bore_mm = 54.0
carburetor_diameter_mm = 38.1
crankcase_clearance_cc = 150.0
exhaust = "racing"
exhaust_cell_length_mm = 10.0
exhaust_duration_deg = 190.0
exhaust_width_mm = 36.0
friction_bar = 0.4
@@ -66,3 +69,69 @@ final_drive_ratio = 3.769231
mass_kg = 170
wheel_radius_mm = 331.3
gear_ratios = [2.384615, 1.933333, 1.588235, 1.352941, 1.176471, 1.055556]
[[geometry.cylinders]]
origin_mm = [ 0, 0, 0 ]
tilt_deg = 0.0
# intake
[[geometry.pipes]]
index = 0
points_mm = [
[ -235.9365, 16.1836, 0 ],
[ -135.9365, 16.1836, 0 ]
]
# reed passage
[[geometry.pipes]]
index = 1
points_mm = [
[ -56.5664, 16.1836, 0 ],
[ -26.5664, 16.1836, 0 ]
]
# transfer passage
[[geometry.pipes]]
index = 2
points_mm = [
[ 26.5664, 42.75, 0 ],
[ 30.8157, 45.5218, 0 ],
[ 34.5021, 48.2991, 0 ],
[ 37.6254, 51.0817, 0 ],
[ 40.1858, 53.8698, 0 ],
[ 42.1833, 56.6633, 0 ],
[ 43.6178, 59.4623, 0 ],
[ 44.4893, 62.2667, 0 ],
[ 44.7979, 65.0764, 0 ],
[ 44.5435, 67.8916, 0 ],
[ 43.7262, 70.7123, 0 ],
[ 42.3459, 73.5383, 0 ],
[ 40.4026, 76.3698, 0 ],
[ 37.8964, 79.2067, 0 ],
[ 34.8272, 82.0491, 0 ],
[ 31.1951, 84.8968, 0 ],
[ 27, 87.75, 0 ]
]
# exhaust chamber
[[geometry.pipes]]
index = 3
points_mm = [
[ 27, 112.25, 0 ],
[ 204.414, 47.6766, 0 ],
[ 294.1781, -144.8232, 0 ],
[ 165.5822, -328.4771, 0 ],
[ 12.1822, -328.4771, 0 ],
[ -220.6736, -243.7245, 0 ],
[ -503.8736, -243.7245, 0 ]
]
[[geometry.volumes]]
id = "intake_plenum"
center_mm = [ -96.2515, 16.1836, 0 ]
size_mm = [ 79.3701, 79.3701, 79.3701 ]
[[geometry.volumes]]
id = "crankcase"
center_mm = [ 0, 16.1836, 0 ]
size_mm = [ 53.1329, 53.1329, 53.1329 ]

273
improvements.md Normal file
View File

@@ -0,0 +1,273 @@
# Sound realism improvement plan
## Goal
Make engine sound emerge from the simulated engine and its surroundings. The 1D
fluid model should provide the coherent pressure and flow behavior; separate
acoustic source and propagation models should represent phenomena the 1D model
cannot resolve. The real-time path should remain affordable, deterministic when
requested, and diagnosable by source.
## Current sound path and limitations
The four-stroke and two-stroke scenarios currently pass intake and exhaust
open-end volume flow to `OutdoorAudio`. It derives a compact monopole pressure
signal from the time derivative of that flow, applies distance and ear delays,
simple image-source reflections, and an approximate late reverb. The engine's
3D scene supplies source/listener positions, but visible geometry does not yet
control detailed acoustic propagation.
This captures coherent engine pulses, but has no direct model for turbulent
noise at valves, throttle plates, sharp area changes, bends, collectors, or pipe
exits. A nonzero flow or a large opening alone is not evidence that an audible
turbulent source should be active. The 1D pipe state is cross-section averaged;
it cannot resolve vortices, transverse velocity, or separated shear layers.
The first-order pipe update also adds numerical damping, which can soften pulses
before they reach the acoustic renderer.
## 1. Separate acoustic sources
Represent and capture each major sound contribution independently before
mixing them:
- **Coherent intake and exhaust pulses:** derive from simulated pressure and
volume-flow waves at ports and mouths.
- **Intake aerodynamic noise:** generate at the throttle, valves, filter,
bends, contractions, and other restrictions when their local operating
conditions and geometry support turbulent flow or separation.
- **Exhaust aerodynamic noise:** generate at exhaust valves, collectors, abrupt
expansions/contractions, and outlets where jets or separated flow are
expected.
- **Combustion events:** retain cycle timing and add afterfire or misfire
sounds only when the combustion/fuel model predicts those events.
- **Mechanical sources:** add valve train, injector, gear, belt, and rotating
imbalance sources as their physical models become available.
Each source should have an independent output/capture channel and enable
control. This makes level and spectral calibration possible without masking
errors by changing unrelated sources.
### Flow-noise source model
Use local flow and feature data to estimate source strength, rather than mapping
area or mass flow directly to noise. Useful inputs include:
- mass or volume flow, density, local velocity, and flow direction;
- upstream/downstream pressure and pressure drop;
- effective opening area and area ratio;
- hydraulic diameter, edge radius/sharpness, bend angle, and expansion ratio;
- gas temperature and viscosity, for Reynolds-number estimates;
- opening motion and rate of change for valves and throttle plates.
Use these inputs to estimate whether the feature creates a jet, separation, or
vortex shedding, and how strongly it is excited. Reynolds number is a useful
regime indicator, not an audible-noise switch. Mean flow alone should not force
a noise source to be audible. A smooth, well-matched passage and an abrupt,
partly closed restriction should produce different results at comparable flow.
For an initial real-time model, use a seeded, band-limited stochastic source
with a smooth operating-point envelope and a short stateful correlation time.
Shape its spectrum and level from local flow power/jet speed, pressure drop,
feature geometry, and a calibrated source model. Avoid unfiltered white noise,
hard on/off thresholds, and arbitrary random pops. Keep stochastic source
generation out of the conserved pipe state in the first implementation; this
avoids changing mass, momentum, or energy merely to create audio.
Treat unsteady separated flow as a modeled source, not a directly measured
quantity in the current 1D cells. Proxies such as rapid changes in flow or
pressure, reversals, and valve motion may modulate the source, but they do not
resolve turbulent fluctuations. For higher-fidelity source data, use transient
3D CFD/LES or measurements at selected features and reduce those results to a
real-time source model.
## 2. Improve pipe and component acoustic coupling
### Diagnose wave loss before changing the model
Build small repeatable interface cases and record pressure on both sides of an
interface:
1. Send a pressure pulse through an equal-area straight connection.
2. Measure reflection and transmission at T/Y junctions.
3. Connect a pipe to a known volume and vary the volume and opening geometry.
4. Repeat at several cell sizes, CFL/time-step settings, and flux schemes.
5. Track incident, reflected, and transmitted wave energy, mass/energy
balance, `limited_fluxes`, and positivity repairs.
If pulse loss decreases with mesh refinement, spatial numerical diffusion is a
major contributor. If loss is concentrated at an interface and does not
converge with refinement, improve the boundary/junction model. Compare with
analytic acoustic impedance/scattering results where applicable and with
measured junction data for realistic geometries.
### Zero-volume pipe junctions
The current ideal N-way junction uses a zero-storage Rusanov star state. Improve
this with a characteristic junction solve that enforces mass and energy
conservation while accounting for each branch's area, orientation, flow
direction, and pressure-wave impedance. Add calibrated geometry-dependent
losses where the junction is not ideal. Validate frequency-dependent reflection
and transmission, not just steady flow or conservation.
### Pipe-to-volume transitions
The current pipe-to-component connection uses an orifice/nozzle boundary and a
shared component pressure/state. Represent the actual entry geometry where it
matters: opening area, contraction/expansion, short profiled transitions, and
calibrated discharge/loss coefficients. A shared volume pressure is appropriate
for a well-mixed compact reservoir, but may be too simple for a collector or
plenum with strong spatial pressure differences. Use resolved pipe sections or
a richer multi-port model where those differences materially affect pulse
transfer.
### Pipe wave resolution
Compare the current first-order Rusanov update with higher-order limited
finite-volume schemes that preserve shocks without excessive damping. Measure
waveform, phase, and spectrum convergence with mesh and timestep refinement.
Retain robust positivity handling and conservation checks. Do not select a
scheme on shock-tube error alone; validate acoustic pulse propagation and
engine-relevant frequencies as well.
## 3. Add acoustic networks between flow ports and listener
Separate the simulated fluid flow network from the acoustic radiation network.
For each source, represent how its sound travels through connected ducts,
plenums, filters, resonators, and mufflers before it radiates. Options include:
- **Transfer matrices / frequency-domain network models:** efficient for
linear acoustic behavior and useful for calibrating duct and chamber
resonances.
- **Digital waveguides / delay-line networks:** suitable for real-time,
time-domain wave travel, reflections, and frequency-dependent losses.
- **Reduced-order component models:** compact state-space filters derived from
measured or higher-detail simulations for complex elements.
Model acoustic reflection, transmission, resonance, and attenuation at
junctions and openings. Include frequency-dependent losses from wall friction,
viscothermal effects, and porous/absorptive parts where data supports them.
Use separate paths for intake and exhaust so changes to one system do not
unintentionally alter the other.
## 4. Use the 3D environment for propagation and radiation
The engine's 3D placement can improve more than stereo panning, provided the
scene contains usable acoustic geometry and materials. Give each source a
position, orientation, size, and frequency-dependent directivity. Relevant
sources include intake mouth, individual or grouped exhaust outlets, engine
surfaces, valve train, and drivetrain.
Use scene geometry for:
- **Directivity:** tailpipe and intake radiation vary with direction and
frequency; source orientation should affect the listener signal.
- **Occlusion and diffraction:** engine parts, chassis, bodywork, and nearby
obstacles can block or bend sound. A simple visibility test is useful only
for the frequencies where geometric acoustics applies.
- **Early reflections:** compute paths from actual ground, walls, vehicle body,
and nearby surfaces, with material-dependent reflection and absorption.
- **Late reverberation:** derive or tune the diffuse tail from the environment
rather than relying only on fixed scene presets.
- **Listener motion:** update source/listener distance, direction, delay, and
Doppler shift consistently; smooth changes without breaking phase/history.
Use a **hybrid propagation model**. Ray tracing is relatively efficient for
high-frequency reflections and occlusion. Low frequencies require wave or modal
methods because they diffract and form strong cavity/vehicle resonances. A
boundary-element, finite-element, or finite-difference time-domain (FDTD) solve
can be run offline for representative geometry, then reduced to filters or
modal data for real-time playback. The visible engine mesh alone is not enough:
acoustic propagation also needs appropriate geometry detail and surface
impedance/absorption data.
## 5. Calibration and validation
Record real intake and exhaust audio at fixed engine speed, load, throttle, and
microphone position. Keep recordings unnormalized and disable automatic gain
for comparisons. Add transient throttle sweeps and lift-off cases after the
steady points.
Compare:
- engine-order peak frequencies, amplitudes, and phase/timing;
- broadband noise level and spectral shape;
- intake/exhaust level balance;
- transient envelope and throttle response;
- changes with listener position and direction;
- reflection/decay behavior in each environment.
Use separate source captures and bypassable acoustic stages to identify where
an error enters. Calibrate source strength first, then duct/network filtering,
then 3D radiation and environment. Avoid compensating for a weak physical
source by boosting reverb or global gain.
## Implementation status and scope
- Every scenario TOML now supplies `[simulation] cells_per_meter`. The desktop
exposes a per-scenario resolution stepper with background mesh rebuild and
engine restart, retaining each selection during the app session. The desktop
calibration/geometry editor has been removed. Uniform and profiled pipe
resolution is controlled by the shared density, preserving geometry/volume.
- Source-separated captures are implemented for intake, both exhaust emitters,
and valve contact, with a separate shared diffuse-tail capture, per-source
metrics, and capture-tool solo controls. Desktop UI controls remain future work.
- Rusanov, HLLE, and HLLC are already selectable for comparison. The V8 uses
finite-volume port-coupled fittings; zero-storage junctions remain a comparison path.
- 3D CFD/LES work is out of scope. Use the 1D model, reduced acoustic source
models, and measurements for this improvement effort.
- Pulse benchmarks are implemented: 408 straight/split, zero-storage junction,
and finite-volume fitting cases across meshes, timesteps, and flux solvers.
See `docs/ACOUSTIC_BENCHMARK.md` for methodology, limitations, and findings.
- Findings: straight-pipe numerical attenuation converges with refinement;
zero-storage junction scattering has additional mesh-independent error;
small finite-volume fittings can require tighter component timesteps.
- A production-selectable `muscl` pipe solver now compares limited second-order
MUSCL-Hancock/HLLC against the original solvers via F3 or `--solver muscl`.
Automatic desktop gain defaults off. Pulse attenuation improves, with original
conservation/robustness checks retained. The sweep now covers 408 cases.
- A nonlinear characteristic zero-storage exhaust junction is available with
`--junctions characteristic`, alongside volume and legacy comparison modes.
It supports unequal areas, branch orientation, automatic flow reversal,
enthalpy/species mixing, and configurable direction-dependent turning losses.
Equal/unequal-area acoustic scattering and 500/1000 Hz phase/gain checks are
included; see `docs/JUNCTION_SOLVER.md` for scope and limitations.
- Next: listen at fixed operating points, check spectra and performance across
engine families, and investigate component coupling timestep stability before
changing acoustic source strength. The new mode remains opt-in for evaluation.
## Suggested implementation order
1. Add source-separated captures and per-source diagnostics.
2. Build pulse reflection/transmission tests for straight connections,
junctions, and pipe-volume boundaries.
3. Determine whether junction loss or first-order pipe diffusion dominates;
improve the responsible model and validate convergence.
4. Add a flow/geometry-driven intake restriction-noise source with deterministic
seeded, band-limited output and a separate capture channel.
5. Add corresponding exhaust valve/collector/outlet sources; model afterfire
only when supported by combustion state.
6. Add an acoustic network (waveguide or reduced-order transfer model) for
intake and exhaust ducts and volumes.
7. Use 3D geometry for source directivity, occlusion, early reflections, and
listener motion; add offline wave-based/acoustic calculations for important
low-frequency resonances as needed.
8. Calibrate against controlled microphone recordings and retain objective
regression measurements alongside listening tests.
## References and further reading
- Li et al., *LES-FEM coupled analysis and experimental research on aerodynamic
noise of the vehicle intake system* (2017):
https://doi.org/10.1016/j.apacoust.2016.09.009
- Ih et al., *Prediction of intake noise of an automotive engine in run-up
condition* (2009): https://doi.org/10.1016/j.apacoust.2008.03.007
- Morel et al., *Modeling of Engine Exhaust Acoustics* (1999):
https://doi.org/10.4271/1999-01-1665
- Parrondo et al., *Noise transmission through duct divisions in air circuits*
(2006), experimental scattering coefficients for duct junctions:
https://www.sciencedirect.com/science/article/abs/pii/S0022460X06002239
- Lang and Mindt, *Entropy-Preserving Coupling Conditions for One-dimensional
Euler Systems at Junctions* (2017): https://arxiv.org/abs/1704.04035
- Hu, Morfey and Sandham, *Sound radiation in turbulent channel flows* (2003):
https://doi.org/10.1017/S002211200200277X

View File

@@ -24,7 +24,7 @@ private:
std::array<std::int16_t, block_size * 2> output_{};
std::atomic<std::uint64_t> underrun_blocks_{};
std::atomic<double> speed_{1.0};
std::atomic<bool> auto_gain_enabled_{true};
std::atomic<bool> auto_gain_enabled_{false};
float rms_power_{}, peak_envelope_{}, automatic_gain_{1.f};
float level_alpha_{}, peak_decay_{}, gain_rise_alpha_{}, gain_fall_alpha_{};
float previous_input_left_{}, previous_output_left_{};

View File

@@ -3,7 +3,7 @@
namespace fluidsim::port {
struct AudioFrame { float left{}, right{}; };
struct Vec3 { float x{}, y{}, z{}; };
struct Vec3 { float x{}, y{}, z{}; bool operator==(const Vec3&) const = default; };
struct AudioListener { Vec3 position{}, right{1,0,0}; };
// Simple flow-derivative source used by the Helmholtz and pulse scenarios.

View File

@@ -3,6 +3,8 @@
#include "FluidSim/DynoCurve.hpp"
#include "FluidSim/Desktop/PedalControl.hpp"
#include "FluidSim/Desktop/EngineRenderer3D.hpp"
#include <optional>
#include <string>
#include <SFML/Graphics.hpp>
namespace fluidsim::port::desktop {
struct PlaybackInfo {
@@ -24,8 +26,17 @@ public:
bool handle_event(const sf::Event&,sf::Vector2u);
AudioListener listener(const ScenarioSnapshot&,sf::Vector2u);
void draw(sf::RenderWindow&,const sf::Font*,const ScenarioSnapshot&,const PlaybackInfo&);
void configure_resolution(float density) { resolution_=density; }
std::optional<float> take_resolution_request() {auto value=resolution_request_;resolution_request_.reset();return value;}
void resolution_started() {resolution_busy_=true;resolution_status_="Building mesh...";}
void resolution_finished(float density,const std::string& message) {resolution_=density;resolution_busy_=false;resolution_status_=message;}
static std::array<sf::FloatRect,2> resolution_buttons(sf::Vector2u size);
private:
DynoCurve dyno_;
float resolution_{20};
bool resolution_busy_{};
std::optional<float> resolution_request_;
std::string resolution_status_;
EngineRenderer3D renderer_;
OrbitCamera camera_;
bool three_d_{true},camera_fitted_{},pan_drag_{},pedal_drag_{};
@@ -34,5 +45,8 @@ private:
sf::Vector2i previous_mouse_{};
bool dragging_{};
float zoom_{1};
float camera_aspect_{};
WorkspaceLayout workspace_layout(sf::Vector2u size) const;
void fit_camera(float aspect,const ScenarioSnapshot&);
};
}

View File

@@ -0,0 +1,47 @@
#pragma once
#include "FluidSim/ScenarioCatalog.hpp"
#include "FluidSim/ScenarioSnapshot.hpp"
#include <SFML/Graphics.hpp>
#include <optional>
namespace fluidsim::port::desktop {
// Owns an editable design only. Simulation state is exchanged through snapshots
// and explicit actions; mouse handlers never touch running components.
class EngineEditor {
public:
void configure(const ScenarioExample& definition);
bool handle_event(const sf::Event& event,sf::Vector2u size);
void draw(sf::RenderWindow& window,const sf::Font* font,const ScenarioSnapshot& state) const;
bool close();
bool visible() const { return visible_; }
const EngineConfig& draft() const { return draft_; }
std::optional<EngineConfig> take_live_update();
std::optional<EngineConfig> take_rebuild_request();
bool take_save_request();
void finish_rebuild(bool success,const std::string& message);
void set_status(std::string message) { status_=std::move(message); }
static sf::FloatRect bounds(sf::Vector2u size);
static sf::FloatRect toggle_bounds(sf::Vector2u size);
private:
enum class Tab { Valves, Fuel, Pipes };
enum class Key { Mass,Spring,Damping,Preload,SoundArea,SoundDamping,Ignition,Idle,Pilot,Main,Needle,Stoich,Power,Length,Diameter };
struct Field { Key key;std::string label,unit;float value,minimum,maximum;int precision; };
bool supported_{},visible_{},exhaust_{},live_changed_{},rebuild_requested_{},save_requested_{},busy_{};
Tab tab_{Tab::Valves};
EngineConfig original_,draft_,rebuilding_;
std::vector<PipeConfig> applied_pipes_;
std::vector<EngineConfig> undo_,redo_;
std::size_t pipe_{};
int dragging_field_{-1},dragging_point_{-1};
std::string status_{"Drag a cam point or slider. W/S controls the throttle."};
std::vector<Field> fields() const;
void set_field(const Field& field,float value);
void begin_edit();
void undo();
void redo();
void reset();
void drag(sf::Vector2f position,sf::Vector2u size);
sf::FloatRect graph_bounds(sf::Vector2u size) const;
sf::FloatRect field_bounds(sf::Vector2u size,std::size_t index) const;
};
}

View File

@@ -7,8 +7,7 @@
#include <utility>
namespace fluidsim::port::desktop {
struct PipeAnchor {float distance;Vec3 position;};
// Knots are distances along the simulated pipe, in metres. Display positions
// define its schematic route; changing them never changes fluid cell lengths.
// Piecewise straight centerline; knot distances are measured from its geometry.
class PipePath {
public:
explicit PipePath(std::vector<PipeAnchor> anchors):anchors_(std::move(anchors)) {
@@ -24,10 +23,8 @@ public:
auto it=std::upper_bound(anchors_.begin(),anchors_.end(),distance,[](float s,const PipeAnchor& a){return s<a.distance;});
std::size_t i=std::clamp<std::size_t>(it-anchors_.begin(),1,anchors_.size()-1)-1;
const auto& a=anchors_[i];const auto& b=anchors_[i+1];
const float span=b.distance-a.distance,t=(distance-a.distance)/span,t2=t*t,t3=t2*t;
const auto ma=tangent_at(i),mb=tangent_at(i+1);
auto coordinate=[&](float pa,float pb,float ta,float tb){return (2*t3-3*t2+1)*pa+(t3-2*t2+t)*span*ta+(-2*t3+3*t2)*pb+(t3-t2)*span*tb;};
return {coordinate(a.position.x,b.position.x,ma.x,mb.x),coordinate(a.position.y,b.position.y,ma.y,mb.y),coordinate(a.position.z,b.position.z,ma.z,mb.z)};
const float t=(distance-a.distance)/(b.distance-a.distance);
return {std::lerp(a.position.x,b.position.x,t),std::lerp(a.position.y,b.position.y,t),std::lerp(a.position.z,b.position.z,t)};
}
Vec3 tangent(float distance) const {
const float epsilon=anchors_.back().distance*.0001f;
@@ -35,12 +32,21 @@ public:
const float x=b.x-a.x,y=b.y-a.y,z=b.z-a.z,n=std::sqrt(x*x+y*y+z*z);
return n>1e-8f?Vec3{x/n,y/n,z/n}:Vec3{1,0,0};
}
private:
Vec3 tangent_at(std::size_t i) const {
const auto& a=anchors_[i?i-1:0];const auto& b=anchors_[std::min(i+1,anchors_.size()-1)];
const float d=b.distance-a.distance;
return {(b.position.x-a.position.x)/d,(b.position.y-a.position.y)/d,(b.position.z-a.position.z)/d};
Vec3 miter_offset(float distance,Vec3 radial) const {
const auto unit=[](Vec3 v){const float n=std::sqrt(v.x*v.x+v.y*v.y+v.z*v.z);return Vec3{v.x/n,v.y/n,v.z/n};};
for(std::size_t i=1;i+1<anchors_.size();++i)if(std::abs(distance-anchors_[i].distance)<1e-6f) {
const auto a=anchors_[i-1].position,b=anchors_[i].position,c=anchors_[i+1].position;
const auto u=unit({b.x-a.x,b.y-a.y,b.z-a.z}),v=unit({c.x-b.x,c.y-b.y,c.z-b.z});
const float cosine=std::clamp(u.x*v.x+u.y*v.y+u.z*v.z,-1.f,1.f);
if(cosine>.99999f)return radial;
const auto bend=unit({u.x-v.x,u.y-v.y,u.z-v.z});
const float scale=std::min(3.f,1/std::sqrt(std::max(.0001f,(1+cosine)*.5f)));
const float extension=(radial.x*bend.x+radial.y*bend.y+radial.z*bend.z)*(scale-1);
return {radial.x+bend.x*extension,radial.y+bend.y*extension,radial.z+bend.z*extension};
}
return radial;
}
private:
std::vector<PipeAnchor> anchors_;
};
}

View File

@@ -0,0 +1,24 @@
#pragma once
#include <SFML/Window/Event.hpp>
#include <algorithm>
#include <cmath>
namespace fluidsim::port::desktop {
class PlaybackControl {
public:
bool realtime() const {return realtime_;}
double target_speed() const {return realtime_?1.0:manual_speed_;}
bool handle_event(const sf::Event& event,bool shift=false) {
if(const auto* key=event.getIf<sf::Event::KeyPressed>()) {
if(key->code==sf::Keyboard::Key::Space){realtime_=!realtime_;return true;}
if(!realtime_&&key->code==sf::Keyboard::Key::PageUp){adjust(1);return true;}
if(!realtime_&&key->code==sf::Keyboard::Key::PageDown){adjust(-1);return true;}
}
if(const auto* wheel=event.getIf<sf::Event::MouseWheelScrolled>();wheel&&!realtime_&&shift) {adjust(wheel->delta);return true;}
return false;
}
private:
bool realtime_{true};
double manual_speed_{1};
void adjust(double steps){manual_speed_=std::clamp(manual_speed_*std::exp(steps*.12),.0001,1.0);}
};
}

View File

@@ -3,10 +3,29 @@
#include <algorithm>
#include <cmath>
namespace fluidsim::port::desktop {
// Fixed absolute scale: vacuum blue, atmosphere green, >=1 MPa red.
inline Vec3 pressure_tint(float pressure) {
// Keep the established absolute vacuum palette. Above atmosphere, use a
// narrower positive range so modest pulses stand out from the green baseline.
inline Vec3 pressure_tint(float pressure, bool exhaust=false, float temperature=300.f) {
if(pressure<=101325) {float t=std::clamp(pressure/101325,0.f,1.f);return {.05f,.15f+.70f*t,1-.95f*t};}
float t=std::sqrt(std::clamp((pressure-101325)/(1000000-101325),0.f,1.f));
return {.05f+.95f*t,.85f-.80f*t,.05f-.03f*t};
const float temperature_boost=std::sqrt(std::clamp(temperature/300.f,.25f,9.f));
const float pulse=(pressure-101325.f)*temperature_boost;
// Exhaust reaches the warm colors sooner, while using the same continuous
// ramp so the atmosphere boundary and pulse edges stay smooth.
const float scale=exhaust?45000.f:150000.f;
const float t=pulse/(pulse+scale);
const auto mix=[](Vec3 a,Vec3 b,float u) {
u=u*u*(3.f-2.f*u);
return Vec3{a.x+(b.x-a.x)*u,a.y+(b.y-a.y)*u,a.z+(b.z-a.z)*u};
};
constexpr Vec3 green{.05f,.85f,.05f},yellow{1.f,1.f,.03f},orange{1.f,.52f,.02f},red{1.f,0.f,0.f};
if(t<.35f)return mix(green,yellow,t/.35f);
if(t<.68f)return mix(yellow,orange,(t-.35f)/.33f);
return mix(orange,red,(t-.68f)/.32f);
}
inline float pressure_expansion(float pressure,float temperature=300.f) {
if(pressure<=101325.f)return 0.f;
const float temperature_boost=std::sqrt(std::clamp(temperature/300.f,.25f,9.f));
const float pulse=(pressure-101325.f)*temperature_boost;
return .10f*pulse/(pulse+80000.f);
}
}

View File

@@ -11,7 +11,7 @@ class DesktopScenario {
public:
explicit DesktopScenario(const ScenarioExample& e)
: DesktopScenario(e.kind,e.two,e.exhaust,e.junctions,e.engine,e.title) {}
explicit DesktopScenario(ScenarioKind kind, TwoStrokeConfig config = {}, ExhaustConfig exhaust = {}, JunctionConfig junctions = {}, EngineConfig engine = {}, std::string name = {}) : name_(std::move(name)) {
explicit DesktopScenario(ScenarioKind kind, TwoStrokeConfig config = {}, ExhaustConfig exhaust = {}, JunctionConfig junctions = {}, EngineConfig engine = {}, std::string name = {}) : name_(std::move(name)),exhaust_(exhaust),junctions_(junctions) {
if (kind == ScenarioKind::TwoStroke)
two_ = std::make_unique<TwoStrokeScenario>(44100, config);
else {
@@ -21,33 +21,74 @@ public:
}
}
AudioFrame process() {
const std::lock_guard lock(mutex_);
if (four_)
return four_->process();
const std::lock_guard lock(mutex_);
return two_->process();
}
void set_controls(float pedal, float load, float clutch) {
visit([&](auto& scenario) { scenario.set_controls(pedal, load, clutch); });
visit([&](auto& scenario) { pedal_=pedal;load_=load;clutch_=clutch;scenario.set_controls(pedal, load, clutch); });
}
void set_starter(bool engaged) { visit([&](auto& scenario) { scenario.set_starter(engaged); }); }
void set_starter(bool engaged) { visit([&](auto& scenario) { starter_=engaged;scenario.set_starter(engaged); }); }
void shift_up() { visit([](auto& scenario) { scenario.shift_up(); }); }
void shift_down() { visit([](auto& scenario) { scenario.shift_down(); }); }
void set_neutral() { visit([](auto& scenario) { scenario.set_neutral(); }); }
void set_pipe_solver(PipeSolver solver) { visit([&](auto& scenario) { scenario.set_pipe_solver(solver); }); }
void set_pipe_solver(PipeSolver solver) { visit([&](auto& scenario) { solver_=solver;scenario.set_pipe_solver(solver); }); }
void set_audio_scene(OutdoorScene scene) {
visit([&](auto& scenario) { scenario.set_audio_scene(scene); });
visit([&](auto& scenario) { scene_=scene;scenario.set_audio_scene(scene); });
}
void set_listener(AudioListener listener) {
visit([&](auto& scenario) { scenario.set_listener(listener); });
visit([&](auto& scenario) { listener_=listener;scenario.set_listener(listener); });
}
void set_tuning(const EngineConfig& tuning) {
const std::lock_guard lock(mutex_);
if(four_)four_->set_tuning(tuning);
}
// Build on a background thread. Publishing takes the same lock as process(),
// so no solver/port pointers can be observed during the swap.
void rebuild_engine(const EngineConfig& engine) {
validate_engine_config(engine,"pipe rebuild");
auto replacement=std::make_unique<FourStrokeScenario>(44100,engine,exhaust_,junctions_);
{
const std::lock_guard lock(mutex_);
if(!four_)throw std::invalid_argument("Live editor requires a four-stroke engine");
replacement->set_controls(pedal_,load_,clutch_);
replacement->set_starter(starter_);replacement->set_pipe_solver(solver_);
replacement->set_audio_scene(scene_);replacement->set_listener(listener_);
four_.swap(replacement);
}
}
void set_cells_per_meter(float density) {
mesh_cell_count(.1f,density);
EngineConfig engine;TwoStrokeConfig two_config;bool is_two;
{
const std::lock_guard lock(mutex_);
is_two=bool(two_);
if(is_two) two_config=two_->configuration; else engine=four_->engine_config;
}
std::unique_ptr<FourStrokeScenario> next_four;
std::unique_ptr<TwoStrokeScenario> next_two;
if(is_two) { two_config.cells_per_meter=density;two_config.exhaust_cell_length=1/density;next_two=std::make_unique<TwoStrokeScenario>(44100,two_config); }
else { engine.cells_per_meter=density;next_four=std::make_unique<FourStrokeScenario>(44100,engine,exhaust_,junctions_); }
{
const std::lock_guard lock(mutex_);
const auto restore=[&](auto& scenario) {
scenario.set_controls(pedal_,load_,clutch_);scenario.set_starter(starter_);
scenario.set_pipe_solver(solver_);scenario.set_audio_scene(scene_);scenario.set_listener(listener_);
};
if(is_two) {restore(*next_two);two_.swap(next_two);}
else {restore(*next_four);four_.swap(next_four);}
}
}
ScenarioSnapshot snapshot() const {
if (four_) { auto s=four_->snapshot();s.example_name=name_;return s; }
const std::lock_guard lock(mutex_);
if (four_) { auto s=four_->snapshot();s.example_name=name_;s.cells_per_meter=four_->engine_config.cells_per_meter;return s; }
const auto source = two_->snapshot();
ScenarioSnapshot s;
s.example_name=name_;
s.pipe_solver=two_->pipes.front().solver;
s.two_stroke = true;
s.cells_per_meter=two_->configuration.cells_per_meter;
s.cylinder_angles[0]=two_->crankshaft.angle;
s.cylinder_count = 1;
s.racing_exhaust = two_->configuration.exhaust == TwoStrokeExhaust::Racing;
@@ -79,6 +120,7 @@ public:
s.intake_flow = two_->intake_flow;
s.exhaust_flow = two_->exhaust_flow;
s.capture_pipes(two_->pipes);
two_->capture_geometry(s);
return s;
}
@@ -89,6 +131,13 @@ private:
else function(*two_);
}
std::string name_;
ExhaustConfig exhaust_;
JunctionConfig junctions_;
float pedal_{},load_{},clutch_{};
bool starter_{};
PipeSolver solver_{PipeSolver::Rusanov};
OutdoorScene scene_{OutdoorScene::Wall};
AudioListener listener_{};
std::unique_ptr<FourStrokeScenario> four_;
std::unique_ptr<TwoStrokeScenario> two_;
mutable std::mutex mutex_;

View File

@@ -5,6 +5,7 @@
#include <array>
#include <cstddef>
#include <random>
#include <vector>
namespace fluidsim::port {
@@ -33,11 +34,9 @@ private:
};
struct CylinderConfig {
float intake_open{340}, intake_close{590}, exhaust_open{130}, exhaust_close{390};
float bore{0.075f}, stroke{0.068f}, rod{0.112f}, compression{11.5f};
float intake_valve_diameter{0.030f}, exhaust_valve_diameter{0.028f};
int intake_valve_count{1}, exhaust_valve_count{1};
float intake_valve_lift{0.005f}, exhaust_valve_lift{0.005f};
float spark_advance{20.0f}, wiebe_a{5.0f}, wiebe_m{2.0f}, burn_duration{60.0f}, burn_start{5.0f};
float afr{14.7f}, fuel_lhv{44.0e6f}, energy_variation{0.05f}, misfire_probability{};
float wall_area{0.02f}, heat_transfer{100.0f}, ambient_temperature{300.0f};
@@ -79,14 +78,44 @@ protected:
static bool crossed_angle(float previous, float current, float event) {
return current >= previous ? previous < event && current >= event : event > previous || event <= current;
}
static bool crossed_window_close(float previous, float current, float open, float close);
void update_rev_limiter(float rpm, float dt);
void sync_ports();
};
class FourStrokeCylinder final : public EngineCylinder {
public:
struct CamPoint { float angle_degrees{}, lift_metres{}; };
struct ValveDynamics {
float mass_kg{0.035f};
float spring_rate_n_per_m{30000.f};
float damping_n_s_per_m{24.f};
float seat_force_n{100.f};
float maximum_lift_m{0.015f};
float seat_restitution{0.005f}, cam_restitution{0.01f}, stop_restitution{0.01f};
};
struct CamProfile { std::vector<CamPoint> points; };
void set_valve_profiles(const CamProfile& intake_cam, const ValveDynamics& intake_valve,
const CamProfile& exhaust_cam, const ValveDynamics& exhaust_valve);
float valve_area(float cycle_degrees, bool inlet) const override;
void pre_step(Crankshaft& crank, float dt, float throttle) override;
float take_contact_impulse() { const float value=contact_impulse_;contact_impulse_=0;return value; }
bool valve_floating() const { return intake_floating_ || exhaust_floating_; }
float intake_lift() const { return intake_lift_; }
float exhaust_lift() const { return exhaust_lift_; }
static float cam_lift_at(const CamProfile& profile,float degrees) { return sample_cam(profile,degrees).lift_metres; }
private:
struct ValveState { float lift{},velocity{};bool floating{}; };
const CamProfile* intake_cam_{};
const CamProfile* exhaust_cam_{};
ValveDynamics intake_dynamics_{},exhaust_dynamics_{};
ValveState intake_state_{},exhaust_state_{};
float intake_lift_{},exhaust_lift_{},contact_impulse_{};
bool intake_floating_{},exhaust_floating_{};
static CamPoint sample_cam(const CamProfile& profile,float degrees);
float advance_valve(ValveState& state,const CamProfile& cam,const ValveDynamics& dynamics,
bool inlet,const Crankshaft& crank,float dt);
};
} // namespace fluidsim::port

View File

@@ -1,9 +1,12 @@
#pragma once
#include "FluidSim/MeshResolution.hpp"
#include "FluidSim/Engine.hpp"
#include "FluidSim/Ecu.hpp"
#include "FluidSim/Vehicle.hpp"
#include "FluidSim/Carburetor.hpp"
#include "FluidSim/PhysicalGeometry.hpp"
#include <string>
#include <array>
#include <vector>
namespace fluidsim::port {
@@ -17,11 +20,14 @@ struct PipeConfig {
float diameter{}, length{}; // metres
int cells{}, count{1};
bool silenced{};
bool operator==(const PipeConfig&) const = default;
};
// Parsed calibration only. Runtime state belongs to the scenario components.
// Dimensional fields use SI units after loading.
struct EngineConfig {
PhysicalGeometry geometry;
float cells_per_meter{}; // Zero retains legacy programmatic meshes. TOML scenarios specify a density.
int cylinders{};
EngineLayout layout{EngineLayout::Inline};
ExhaustTopology topology{ExhaustTopology::Collector};
@@ -31,6 +37,9 @@ struct EngineConfig {
EcuConfig ecu;
VehicleConfig vehicle;
CarburetorConfig fuel;
FourStrokeCylinder::CamProfile intake_cam,exhaust_cam;
FourStrokeCylinder::ValveDynamics intake_valve,exhaust_valve;
struct ValveSound { float mode_hz{3600}, effective_mass_kg{0.18f}, radiation_area{0.001f}, damping_ratio{0.9f}; } valve_sound;
bool carbureted{}, initialize_phased_charge{true};
float initial_rpm{}, starter_torque{}, maximum_load_torque{};
float plenum_volume{}, collector_volume{}, throttle_diameter{}, pipe_damping{};
@@ -45,7 +54,14 @@ struct EngineConfig {
int header_groups() const { return cylinders==3?1:2; }
};
struct JunctionBranchConfig {
int pipe{}; bool left{};
std::array<float,3> direction{};
};
struct JunctionConfig {
std::string model{"volume"}; // volume, characteristic, or legacy
float turning_loss{0};
std::vector<JunctionBranchConfig> branches;
bool h_pipe{true};
float junction_volume{50e-6f};
};

View File

@@ -4,6 +4,8 @@
#include "FluidSim/OpenEndRadiation.hpp"
#include "FluidSim/PipeSolver.hpp"
#include <algorithm>
#include <array>
#include <span>
#include <cmath>
#include <deque>
#include <limits>
@@ -181,12 +183,17 @@ public:
std::vector<GasCell> cells;
GasState left_ghost{}, right_ghost{};
std::uint64_t positivity_repairs{};
std::size_t scratch_bytes() const {
return next_cells_.capacity()*sizeof(GasCell)+states_.capacity()*sizeof(GasState)+
reconstructed_left_.capacity()*sizeof(GasState)+reconstructed_right_.capacity()*sizeof(GasState)+
waves_.capacity()*sizeof(float)+faces_.capacity()*sizeof(Flux)+roe_states_.capacity()*sizeof(RoeState);
}
Pipe1D(float a, float length, int count)
: Pipe1D(uniform(a, count), uniform(length / std::max(count, 1), count)) {}
Pipe1D(std::vector<float> areas, std::vector<float> lengths)
: area(areas.empty() ? 0 : areas.front()), dx(lengths.empty() ? 0 : lengths.front()),
cell_areas(std::move(areas)), cell_lengths(std::move(lengths)), cells(cell_areas.size()),
next_cells_(cells.size()), states_(cells.size()), waves_(cells.size()), faces_(cells.size() + 1), roe_states_(cells.size()) {
next_cells_(cells.size()), states_(cells.size()), waves_(cells.size()), faces_(cells.size() + 1) {
if (cells.empty() || cell_lengths.size() != cells.size())
throw std::invalid_argument("pipe profiles must be nonempty and have equal length");
for (std::size_t i = 0; i < cells.size(); ++i)
@@ -212,6 +219,16 @@ public:
// Solver preparation is valid until advance; boundary resolution changes
// fluxes/ghosts, not cells. Standalone advance still prepares its own cache.
float prepare_step(float cfl = 0.8f) {
if(!scratch_solver_initialized_ || scratch_solver_!=solver) {
if(solver==PipeSolver::Muscl) {
if(reconstructed_left_.size()!=cells.size()) reconstructed_left_.resize(cells.size());
if(reconstructed_right_.size()!=cells.size()) reconstructed_right_.resize(cells.size());
} else if(solver==PipeSolver::Hlle || solver==PipeSolver::Hllc) {
if(roe_states_.size()!=cells.size()) roe_states_.resize(cells.size());
}
scratch_solver_=solver;
scratch_solver_initialized_=true;
}
float dt = std::numeric_limits<float>::max();
for (std::size_t i = 0; i < cells.size(); ++i) {
states_[i] = primitive(cells[i]);
@@ -225,7 +242,7 @@ public:
throw std::runtime_error("nonphysical pipe state in preparation");
dt = std::min(dt, cfl * cell_lengths[i] / waves_[i]);
}
if (solver != PipeSolver::Rusanov)
if (solver == PipeSolver::Hlle || solver == PipeSolver::Hllc)
for (std::size_t i=0; i<cells.size(); ++i) {
const auto& s=states_[i];
const double energy=s.pressure*pipe_gm1_inverse+.5*double(s.rho)*s.velocity*s.velocity;
@@ -327,6 +344,18 @@ public:
static Flux numerical_flux(const GasState& a,const GasState& b,PipeSolver solver) {
return numerical_flux(a,b,solver,std::sqrt(gamma*a.pressure/a.rho),std::sqrt(gamma*b.pressure/b.rho));
}
void set_acoustic_neighbor(bool left,GasState state) {
if(left) { left_acoustic_neighbor_=state; has_left_acoustic_neighbor_=true; }
else { right_acoustic_neighbor_=state; has_right_acoustic_neighbor_=true; }
}
void prepare_acoustic_reconstruction(float dt) {
if(solver!=PipeSolver::Muscl) return;
if(!prepared_) prepare_step();
reconstruct(dt); reconstructed_prepared_=true;
}
GasState acoustic_edge(bool left) const {
return left?reconstructed_left_.front():reconstructed_right_.back();
}
void advance(float dt) {
if (dt <= 0)
return;
@@ -338,7 +367,18 @@ public:
faces_[0] = left_override_ ? left_flux_ : scaled(numerical_flux(left_ghost, states_[0], solver), cell_areas[0]);
faces_[n] =
right_override_ ? right_flux_ : scaled(numerical_flux(states_[n - 1], right_ghost, solver), cell_areas[n - 1]);
if (solver == PipeSolver::Rusanov) {
if(solver==PipeSolver::Muscl) {
if(!reconstructed_prepared_) reconstruct(dt);
for(std::size_t i=1;i<n;++i) {
bool fallback=false;
const auto& a=reconstructed_right_[i-1];
const auto& b=reconstructed_left_[i];
faces_[i]=scaled(numerical_flux(a,b,PipeSolver::Hllc,
std::sqrt(gamma*a.pressure/a.rho),std::sqrt(gamma*b.pressure/b.rho),&fallback),
.5f*(cell_areas[i-1]+cell_areas[i]));
hllc_fallbacks+=fallback;
}
} else if (solver == PipeSolver::Rusanov) {
for (std::size_t i = 1; i < n; ++i)
faces_[i] = scaled(riemann(states_[i-1], states_[i], std::max(waves_[i-1], waves_[i])),
.5f*(cell_areas[i-1]+cell_areas[i]));
@@ -400,6 +440,7 @@ public:
}
cells.swap(next_cells_);
prepared_ = false;
reconstructed_prepared_=has_left_acoustic_neighbor_=has_right_acoustic_neighbor_=false;
left_override_ = right_override_ = false;
}
static Flux scaled(Flux f, float a) {
@@ -409,11 +450,53 @@ public:
private:
std::vector<GasCell> next_cells_;
std::vector<GasState> states_;
std::vector<GasState> reconstructed_left_, reconstructed_right_;
std::vector<float> waves_;
std::vector<Flux> faces_;
std::vector<RoeState> roe_states_;
PipeSolver scratch_solver_{PipeSolver::Rusanov};
bool scratch_solver_initialized_{};
Flux left_flux_{}, right_flux_{};
bool left_override_{}, right_override_{}, prepared_{};
GasState left_acoustic_neighbor_{}, right_acoustic_neighbor_{};
bool has_left_acoustic_neighbor_{}, has_right_acoustic_neighbor_{}, reconstructed_prepared_{};
// Primitive minmod MUSCL-Hancock on uniform sections. Component interfaces
// and area/profile transitions retain the existing first-order coupling.
void reconstruct(float dt) {
const auto minmod=[](float a,float b) {
return a*b<=0 ? 0.f : std::copysign(std::min(std::abs(a),std::abs(b)),a);
};
for(std::size_t i=0;i<states_.size();++i) {
const auto q=states_[i];
reconstructed_left_[i]=reconstructed_right_[i]=q;
if((i==0 && !has_left_acoustic_neighbor_) ||
(i+1==states_.size() && !has_right_acoustic_neighbor_) ||
(i>0 && (cell_areas[i-1]!=cell_areas[i] || cell_lengths[i-1]!=cell_lengths[i])) ||
(i+1<states_.size() && (cell_areas[i+1]!=cell_areas[i] || cell_lengths[i+1]!=cell_lengths[i]))) continue;
const auto a=i==0?left_acoustic_neighbor_:states_[i-1];
const auto b=i+1==states_.size()?right_acoustic_neighbor_:states_[i+1];
const float dr=minmod(q.rho-a.rho,b.rho-q.rho);
const float du=minmod(q.velocity-a.velocity,b.velocity-q.velocity);
const float dp=minmod(q.pressure-a.pressure,b.pressure-q.pressure);
const float da=minmod(q.air-a.air,b.air-q.air);
const float df=minmod(q.fuel-a.fuel,b.fuel-q.fuel);
const float h=.5f*dt/cell_lengths[i];
const GasState half{q.rho-h*(q.velocity*dr+q.rho*du),
q.velocity-h*(q.velocity*du+dp/q.rho),
q.pressure-h*(q.velocity*dp+gamma*q.pressure*du),
q.air-h*q.velocity*da,q.fuel-h*q.velocity*df};
const auto edge=[&](float sign) { return GasState{half.rho+sign*dr,
half.velocity+sign*du,half.pressure+sign*dp,half.air+sign*da,half.fuel+sign*df}; };
const auto left=edge(-.5f), right=edge(.5f);
const auto physical=[](const GasState& v) {
return v.rho>1e-12f && v.pressure>1e-6f && std::isfinite(v.pressure) &&
std::isfinite(v.rho) && std::isfinite(v.velocity) && v.air>=0 && v.fuel>=0 && v.air+v.fuel<=1.000001f;
};
if(physical(left) && physical(right)) {
reconstructed_left_[i]=left; reconstructed_right_[i]=right;
}
}
}
static std::vector<float> uniform(float v, int n) {
if (n <= 0)
throw std::invalid_argument("pipe count must be positive");
@@ -514,25 +597,39 @@ public:
claim(b, left_b);
connections_.push_back({a, b, left_a, left_b});
}
struct JunctionFace { int pipe; bool left; };
void add_junction(std::initializer_list<JunctionFace> ends) {
if(ends.size()<2) throw std::invalid_argument("junction needs at least two faces");
JunctionNode node;
struct JunctionFace {
int pipe; bool left;
// Unit vector pointing OUT of the node into this branch, in world axes.
std::array<float,3> direction{};
};
enum class JunctionMethod { Characteristic, LegacyRusanov };
void add_junction(std::initializer_list<JunctionFace> ends,
JunctionMethod method=JunctionMethod::Characteristic,float turning_loss=0) {
add_junction(std::span(ends.begin(),ends.size()),method,turning_loss);
}
void add_junction(std::span<const JunctionFace> ends,
JunctionMethod method=JunctionMethod::Characteristic,float turning_loss=0) {
if(ends.size()<2 || !std::isfinite(turning_loss) || turning_loss<0 || turning_loss>10)
throw std::invalid_argument("invalid junction size or turning loss");
JunctionNode node; node.method=method; node.turning_loss=turning_loss;
for(auto e:ends) {
check_face(e.pipe,e.left);
for(auto previous:node.ends) {
for(auto previous:node.ends)
if(previous.pipe==e.pipe && previous.left==e.left) throw std::invalid_argument("duplicate junction face");
}
double norm=0;
for(float v:e.direction) { if(!std::isfinite(v)) throw std::invalid_argument("invalid junction direction"); norm+=double(v)*v; }
if(norm>0) for(float& v:e.direction) v/=std::sqrt(norm);
else if(turning_loss>0) throw std::invalid_argument("direction required for turning loss");
node.ends.push_back(e);
}
for(auto e:ends) claim(e.pipe,e.left);
node.states.resize(ends.size());
node.states.resize(ends.size()); node.faces.resize(ends.size());
nodes_.push_back(std::move(node));
}
float open_end_mass_flow(std::size_t i) const { return opens_.at(i).last_mass_flow; }
float open_end_volume_flow(std::size_t i) const { return opens_.at(i).last_volume_flow; }
float open_end_pressure(std::size_t i) const { return opens_.at(i).last_face_pressure; }
std::uint64_t limited_fluxes{}, nonlinear_iterations{};
std::uint64_t limited_fluxes{}, nonlinear_iterations{}, junction_fallbacks{}, junction_iterations{}, junction_limited_fluxes{};
void validate_topology() const {
if (claims_.size() != 2 * pipes_.size())
throw std::runtime_error("every pipe endpoint needs an explicit boundary or connection");
@@ -544,6 +641,20 @@ public:
b.mass = .25f * (*b.air + (b.exhaust ? *b.exhaust : 0) + (b.fuel ? *b.fuel : 0));
b.heat = b.energy ? .25f * (*b.energy) : std::numeric_limits<float>::max();
}
// Supply matching direct-pipe neighbors before reconstruction, preserving
// the same stencil across artificial pipe splits and all orientations.
for(const auto& o:connections_) {
auto& a=pipes_[o.a]; auto& b=pipes_[o.b];
const int ia=end(a,o.left_a), ib=end(b,o.left_b);
if(a.solver==PipeSolver::Muscl && b.solver==PipeSolver::Muscl &&
a.cell_area(ia)==b.cell_area(ib) && a.cell_length(ia)==b.cell_length(ib)) {
auto sa=a.state(ia), sb=b.state(ib);
const float sign=o.left_a==o.left_b?-1.f:1.f;
sa.velocity*=sign; sb.velocity*=sign;
a.set_acoustic_neighbor(o.left_a,sb); b.set_acoustic_neighbor(o.left_b,sa);
}
}
for(auto& pipe:pipes_) pipe.prepare_acoustic_reconstruction(dt);
for (auto &o : orifices_)
resolve_orifice(o, dt);
for (auto &o : opens_)
@@ -557,12 +668,21 @@ private:
struct JunctionNode {
std::vector<JunctionFace> ends;
std::vector<GasState> states;
struct Face { double pressure{},rho{},velocity{},mass{},enthalpy{},air{},fuel{},loss{}; };
std::vector<Face> faces;
JunctionMethod method{JunctionMethod::Characteristic};
double turning_loss{};
};
std::vector<JunctionNode> nodes_;
// Zero-storage Rusanov star state. Integrated mass, total energy and
// species fluxes sum to zero algebraically across every connected face.
// The node has no conserved state, timestep or storage volume of its own.
void resolve_junction(JunctionNode& node,float) {
void resolve_junction(JunctionNode& node,float dt) {
if(node.method==JunctionMethod::Characteristic && characteristic_junction(node,dt)) return;
if(node.method==JunctionMethod::Characteristic) ++junction_fallbacks;
legacy_junction(node);
}
void legacy_junction(JunctionNode& node) {
double speed=0;
for(std::size_t k=0;k<node.ends.size();++k) {
auto e=node.ends[k];auto& p=pipes_[e.pipe];auto s=p.state(end(p,e.left));
@@ -593,6 +713,135 @@ private:
p.set_boundary_flux(e.left,{float(orientation*m),float(momentum),float(orientation*heat),float(orientation*y),float(orientation*f)});
}
}
// Nonlinear outgoing acoustic-wave curves (shock or rarefaction), with
// common node pressure, conservative stagnation-enthalpy/species mixing,
// and outgoing contact density set from the mixed enthalpy. This is a
// compact subsonic engineering closure, not a resolved 3D junction.
bool characteristic_junction(JunctionNode& node,float dt) {
double low=std::numeric_limits<double>::max(), high=0;
for(std::size_t k=0;k<node.ends.size();++k) {
const auto e=node.ends[k]; const auto& pipe=pipes_[e.pipe];
auto state=pipe.state(end(pipe,e.left));
state.velocity*=e.left?-1:1; // positive INTO the node
const double c=std::sqrt(gamma*state.pressure/state.rho);
if(std::abs(state.velocity)>=.95*c) return false;
node.states[k]=state;
low=std::min(low,double(state.pressure)); high=std::max(high,double(state.pressure));
node.faces[k].loss=0;
}
const auto wave_curve=[](const GasState& q,double p) {
if(p>q.pressure) {
const double a=2/((gamma+1)*q.rho), b=(gamma-1)/(gamma+1)*q.pressure;
return (p-q.pressure)*std::sqrt(a/(p+b));
}
const double c=std::sqrt(gamma*q.pressure/q.rho);
return 2*c/(gamma-1)*(std::pow(p/q.pressure,(gamma-1)/(2*gamma))-1);
};
const auto density=[](const GasState& q,double p) {
const double ratio=p/q.pressure, b=(gamma-1)/(gamma+1);
return p>q.pressure ? q.rho*(ratio+b)/(b*ratio+1) : q.rho*std::pow(ratio,1/gamma);
};
// Each pressure evaluation writes scratch face states only, never pipes.
const auto residual=[&](double pressure) {
double incoming=0, heat=0, air=0, fuel=0;
for(std::size_t k=0;k<node.ends.size();++k) {
const auto q=node.states[k]; auto& f=node.faces[k];
double p=pressure;
for(int iteration=0;iteration<(f.loss>0?8:1);++iteration) {
const double u=q.velocity-wave_curve(q,p), rho=density(q,p);
const double target=std::clamp(pressure+std::copysign(.5*f.loss*rho*u*u,u),pressure*.05,pressure*20);
p=.5*(p+target);
}
f.pressure=p; f.rho=density(q,p); f.velocity=q.velocity-wave_curve(q,p);
const auto e=node.ends[k]; const auto& pipe=pipes_[e.pipe];
f.mass=pipe.cell_area(end(pipe,e.left))*f.rho*f.velocity;
f.enthalpy=gamma/(gamma-1)*p/f.rho+.5*f.velocity*f.velocity;
f.air=q.air; f.fuel=q.fuel;
if(f.mass>0) { incoming+=f.mass; heat+=f.mass*f.enthalpy; air+=f.mass*q.air; fuel+=f.mass*q.fuel; }
}
double balance=0;
for(std::size_t k=0;k<node.ends.size();++k) {
auto& f=node.faces[k];
if(f.mass<0 && incoming>1e-20) {
const double h=heat/incoming, thermal=h-.5*f.velocity*f.velocity;
if(!(thermal>0)) return std::numeric_limits<double>::quiet_NaN();
f.rho=gamma/(gamma-1)*f.pressure/thermal;
const auto e=node.ends[k]; const auto& pipe=pipes_[e.pipe];
f.mass=pipe.cell_area(end(pipe,e.left))*f.rho*f.velocity;
f.enthalpy=h; f.air=air/incoming; f.fuel=fuel/incoming;
}
balance+=f.mass;
}
return balance;
};
// Iterate the donor-weighted direction coefficients separately from the
// scalar pressure root; K=0 remains the ideal acoustic scattering case.
double node_pressure=.5*(low+high);
for(int geometry=0;geometry<(node.turning_loss>0?3:1);++geometry) {
double lo=low*.01, hi=high*100;
double fl=residual(lo), fh=residual(hi);
if(!std::isfinite(fl) || !std::isfinite(fh) || fl<0 || fh>0) return false;
node_pressure=std::clamp(node_pressure,lo,hi);
bool converged=false;
for(int iteration=0;iteration<40;++iteration) {
const double value=residual(node_pressure); ++junction_iterations;
if(!std::isfinite(value)) return false;
double magnitude=0; for(const auto& face:node.faces) magnitude+=std::abs(face.mass);
if(std::abs(value)<1e-14+1e-8*magnitude) { converged=true; break; }
if(value>0) lo=node_pressure; else hi=node_pressure;
const double delta=std::max(1e-3,node_pressure*1e-5);
const double derivative=(residual(node_pressure+delta)-value)/delta;
const double next=node_pressure-value/derivative;
node_pressure=std::isfinite(next) && next>lo && next<hi ? next : .5*(lo+hi);
}
if(!converged) return false;
residual(node_pressure);
if(geometry+1<(node.turning_loss>0?3:1)) {
for(std::size_t k=0;k<node.ends.size();++k) {
auto& f=node.faces[k]; double weight=0, loss=0;
if(f.mass<0) for(std::size_t j=0;j<node.ends.size();++j) {
const double m=std::max(0.,node.faces[j].mass); double dot=0;
for(int axis=0;axis<3;++axis) dot+=node.ends[k].direction[axis]*node.ends[j].direction[axis];
weight+=m; loss+=m*.5*(1+std::clamp(dot,-1.,1.));
}
f.loss=weight>0?node.turning_loss*loss/weight:0;
}
}
}
double incoming=0, outgoing=0, heat=0, air=0, fuel=0, scale=1;
for(std::size_t k=0;k<node.ends.size();++k) {
const auto& f=node.faces[k];
if(!std::isfinite(f.mass) || !std::isfinite(f.enthalpy)) return false;
if(std::abs(f.velocity)>=.98*std::sqrt(gamma*f.pressure/f.rho)) return false;
if(f.mass>0) { incoming+=f.mass;heat+=f.mass*f.enthalpy;air+=f.mass*f.air;fuel+=f.mass*f.fuel; }
else outgoing-=f.mass;
}
if(incoming>1e-16 && outgoing<=1e-16) {
if(incoming>1e-11) return false;
// Residual tolerance at rest must not create a one-sided mass source.
for(auto& face:node.faces) face.mass=0;
incoming=heat=air=fuel=0;
}
// Remove root roundoff by scaling all outflows together. A shared donor
// budget scales advective fluxes together, preserving node conservation.
for(std::size_t k=0;k<node.ends.size();++k) {
auto& f=node.faces[k];
if(f.mass<0) { f.mass*=outgoing>0?incoming/outgoing:0; f.enthalpy=incoming>0?heat/incoming:0; f.air=incoming>0?air/incoming:0; f.fuel=incoming>0?fuel/incoming:0; }
if(f.mass>0) {
const auto e=node.ends[k]; const auto& pipe=pipes_[e.pipe];const int i=end(pipe,e.left);
scale=std::min(scale,.25*pipe.cell_mass(i)/(f.mass*dt));
scale=std::min(scale,.25*double(pipe.cells[i].energy)*pipe.cell_area(i)*pipe.cell_length(i)/(f.mass*f.enthalpy*dt));
}
}
if(scale<1) { ++limited_fluxes; ++junction_limited_fluxes; }
for(std::size_t k=0;k<node.ends.size();++k) {
const auto e=node.ends[k]; auto& pipe=pipes_[e.pipe]; const auto& f=node.faces[k];
const double orientation=e.left?-1:1, m=f.mass*scale;
const double momentum=m*f.velocity+f.pressure*pipe.cell_area(end(pipe,e.left));
pipe.set_boundary_flux(e.left,{float(orientation*m),float(momentum),float(orientation*m*f.enthalpy),float(orientation*m*f.air),float(orientation*m*f.fuel)});
}
return true;
}
struct Budget {
float *air{}, *exhaust{}, *fuel{}, *energy{};
float mass{}, heat{};
@@ -864,6 +1113,9 @@ private:
auto &b = pipes_[o.b];
int ia = end(a, o.left_a), ib = end(b, o.left_b);
auto sa = a.state(ia), sb = b.state(ib);
if(a.solver==PipeSolver::Muscl && b.solver==PipeSolver::Muscl) {
sa=a.acoustic_edge(o.left_a); sb=b.acoustic_edge(o.left_b);
}
const float da = o.left_a ? -1.f : 1.f, db = o.left_b ? 1.f : -1.f;
sa.velocity *= da;
sb.velocity *= db;

View File

@@ -0,0 +1,13 @@
#pragma once
#include <algorithm>
#include <cmath>
#include <stdexcept>
namespace fluidsim::port {
inline int mesh_cell_count(float length,float cells_per_meter) {
if(!std::isfinite(cells_per_meter) || cells_per_meter<1 || cells_per_meter>500 || !std::isfinite(length) || length<=0)
throw std::invalid_argument("cells_per_meter must be in 1..500 with positive pipe length");
const double count=double(length)*cells_per_meter;
if(count>4096.001) throw std::invalid_argument("pipe mesh exceeds 4096 cells");
return std::max(1,int(std::ceil(count-1e-6*std::max(1.,count))));
}
}

View File

@@ -17,7 +17,9 @@ struct OutdoorAudioConfig {
Vec3 intake_position{-0.2f, 0.7f, 0.0f};
Vec3 exhaust_position{0.55f, 0.5f, 0.0f};
Vec3 second_exhaust_position{-0.55f, 0.5f, 0.0f};
Vec3 valve_position{0.08f,0.42f,0.0f};
bool dual_exhaust{};
bool valve_contact_enabled{};
float ear_spacing{0.18f}, sound_speed{343.0f};
float ground_y{0.0f}, wall_x{-1.5f};
float ground_reflection{0.35f}, wall_reflection{0.65f};
@@ -29,10 +31,15 @@ struct OutdoorAudioConfig {
// approximate diffuse tail. No feedback into the fluid/combustion simulation.
class OutdoorAudio {
public:
static constexpr std::array<const char*,4> source_names{"intake", "exhaust", "exhaust-2", "valve-contact"};
struct SourceFrames { AudioFrame rendered{}, dry{}, unfiltered{}; };
const auto& source_frames() const { return sources_; }
AudioFrame tail_frame() const { return last_tail_; }
void set_source_enabled(std::size_t index,bool enabled) { enabled_.at(index)=enabled; }
explicit OutdoorAudio(int sample_rate = 44100, OutdoorAudioConfig config = {});
// Acoustic source inputs are ambient-density times mouth volume flow, kg/s.
// Fluid transport still uses actual donor-density mass flux independently.
AudioFrame process(float intake_mass_flow, float exhaust_mass_flow, float second_exhaust_mass_flow = 0);
AudioFrame process(float intake_mass_flow, float exhaust_mass_flow, float second_exhaust_mass_flow = 0, float valve_contact_mass_flow = 0);
void set_scene(OutdoorScene scene) { requested_scene_.store(scene, std::memory_order_relaxed); }
OutdoorScene scene() const { return requested_scene_.load(std::memory_order_relaxed); }
// Call on the simulation thread or under the scenario's simulation lock.
@@ -66,12 +73,14 @@ private:
std::atomic<OutdoorScene> requested_scene_{OutdoorScene::Wall};
OutdoorScene active_scene_{OutdoorScene::Wall};
bool monitor_filter_{true};
std::array<Emitter, 3> emitters_;
std::array<Emitter, 4> emitters_;
OutdoorExhaustReverb late_reverb_;
std::array<float, 5> weights_{1, 0, 0, 0, 0};
float scene_alpha_{}, tail_mix_{};
float geometry_alpha_{}, reverb_input_{};
AudioFrame last_dry_{}, last_raw_{};
AudioFrame last_dry_{}, last_raw_{}, last_tail_{};
std::array<SourceFrames,4> sources_{};
std::array<bool,4> enabled_{true,true,true,true};
void configure_emitter(Emitter &emitter, Vec3 position);
void update_geometry();
AudioFrame render(Emitter &emitter, float flow, AudioFrame &dry, AudioFrame &raw);

View File

@@ -0,0 +1,42 @@
#pragma once
#include "FluidSim/AudioProcessing.hpp"
#include <string>
#include <vector>
namespace fluidsim::port {
struct EngineConfig;
struct ExhaustConfig;
struct JunctionConfig;
struct TwoStrokeConfig;
struct GeometryPath {
bool operator==(const GeometryPath&) const = default;
std::vector<Vec3> points;
float length() const;
Vec3 position(float distance) const;
};
struct GeometryBox {
bool operator==(const GeometryBox&) const = default;
Vec3 center{},size{};
float volume() const {return size.x*size.y*size.z;}
Vec3 minimum() const {return {center.x-size.x*.5f,center.y-size.y*.5f,center.z-size.z*.5f};}
Vec3 maximum() const {return {center.x+size.x*.5f,center.y+size.y*.5f,center.z+size.z*.5f};}
};
struct GeometryVolume {std::string id;GeometryBox box;bool operator==(const GeometryVolume&) const = default;};
struct CylinderPlacement {Vec3 origin{};float tilt_degrees{};bool operator==(const CylinderPlacement&) const = default;};
struct PhysicalGeometry {
bool operator==(const PhysicalGeometry&) const = default;
std::vector<GeometryPath> pipes;
std::vector<GeometryVolume> volumes;
std::vector<CylinderPlacement> cylinders;
const GeometryBox* volume(const std::string& id) const;
GeometryBox* volume(const std::string& id);
bool empty() const {return pipes.empty();}
};
float geometry_distance(Vec3 a,Vec3 b);
Vec3 cylinder_point(const CylinderPlacement& cylinder,Vec3 local);
PhysicalGeometry default_geometry(const EngineConfig&,const ExhaustConfig&,const JunctionConfig&);
PhysicalGeometry default_geometry(const TwoStrokeConfig&);
EngineConfig resolve_engine_geometry(EngineConfig,const ExhaustConfig&,const JunctionConfig&);
TwoStrokeConfig resolve_two_stroke_geometry(TwoStrokeConfig);
void validate_geometry(const PhysicalGeometry&,std::size_t pipes,std::size_t cylinders);
}

View File

@@ -6,6 +6,10 @@ namespace fluidsim::port {
struct PipeSection {
float length, inlet_diameter, outlet_diameter;
};
inline int profile_section_cells(float length,float maximum_cell_length) {
const double count=double(length)/maximum_cell_length;
return std::max(1,static_cast<int>(std::ceil(count-1e-6*std::max(1.,count))));
}
// Linear diameter within each section. Cell area is the exact frustum-volume
// average, so refining the mesh preserves the specified chamber volume.
@@ -20,7 +24,7 @@ inline Pipe1D make_profiled_pipe(std::span<const PipeSection> sections, float ma
section.inlet_diameter <= 0 || section.outlet_diameter <= 0 ||
std::abs(section.inlet_diameter - previous_diameter) > 1e-6f)
throw std::invalid_argument("invalid/discontinuous pipe profile");
const int count = static_cast<int>(std::ceil(section.length / maximum_cell_length));
const int count = profile_section_cells(section.length,maximum_cell_length);
for (int i = 0; i < count; ++i) {
const float d0 = std::lerp(section.inlet_diameter, section.outlet_diameter, float(i) / count);
const float d1 = std::lerp(section.inlet_diameter, section.outlet_diameter, float(i + 1) / count);

View File

@@ -2,12 +2,13 @@
#include <stdexcept>
#include <string_view>
namespace fluidsim::port {
enum class PipeSolver { Rusanov, Hlle, Hllc };
enum class PipeSolver { Rusanov, Hlle, Hllc, Muscl };
inline const char* pipe_solver_name(PipeSolver solver) {
switch(solver) {
case PipeSolver::Rusanov: return "rusanov";
case PipeSolver::Hlle: return "hlle";
case PipeSolver::Hllc: return "hllc";
case PipeSolver::Muscl: return "muscl";
}
return "unknown";
}
@@ -15,6 +16,7 @@ inline PipeSolver parse_pipe_solver(std::string_view name) {
if(name=="rusanov") return PipeSolver::Rusanov;
if(name=="hlle") return PipeSolver::Hlle;
if(name=="hllc") return PipeSolver::Hllc;
throw std::invalid_argument("choose rusanov, hlle or hllc pipe solver");
if(name=="muscl") return PipeSolver::Muscl;
throw std::invalid_argument("choose rusanov, hlle, hllc or muscl pipe solver");
}
} // namespace fluidsim::port

View File

@@ -0,0 +1,59 @@
#pragma once
#include <array>
#include <chrono>
#include <cstddef>
#include <algorithm>
#include <cstdint>
#include <string_view>
namespace fluidsim::port {
enum class ProfileStage : std::size_t { Total, Controls, Solver, Boundary, Pipe, Components, Audio, Count };
inline constexpr std::array<std::string_view,7> profile_stage_names{
"total", "controls", "solver", "boundary", "pipe", "components", "audio"};
struct ProfileTotals {
std::array<std::uint64_t,static_cast<std::size_t>(ProfileStage::Count)> nanoseconds{};
std::uint64_t sampled_steps{};
std::array<std::uint64_t,512> frame_histogram{};
};
inline double profile_percentile_ms(const ProfileTotals& totals,double quantile) {
if(!totals.sampled_steps) return 0;
const auto target=static_cast<std::uint64_t>(quantile*(totals.sampled_steps-1));
std::uint64_t count=0;
for(std::size_t i=0;i<totals.frame_histogram.size();++i) {
count+=totals.frame_histogram[i];
if(count>target) return (i+.5)*.001;
}
return .512;
}
class RuntimeProfiler {
public:
void enable(bool value) { enabled_=value; frame_=0; totals_={}; }
bool begin_step() { return enabled_ && (frame_++%2==0); }
void add(ProfileStage stage,std::uint64_t ns) { totals_.nanoseconds[static_cast<std::size_t>(stage)]+=ns; }
void finish_step(std::uint64_t elapsed_ns) {
++totals_.sampled_steps;
const auto bin=std::min<std::uint64_t>(elapsed_ns/1000,totals_.frame_histogram.size()-1);
++totals_.frame_histogram[bin];
}
double frame_percentile_ms(double quantile) const {
return profile_percentile_ms(totals_,quantile);
}
const ProfileTotals& totals() const { return totals_; }
bool enabled() const { return enabled_; }
private:
bool enabled_{};
std::uint64_t frame_{};
ProfileTotals totals_{};
};
class ProfileScope {
public:
ProfileScope(RuntimeProfiler* profiler,bool active,ProfileStage stage)
: profiler_(active?profiler:nullptr),stage_(stage),start_(profiler_?Clock::now():Clock::time_point{}) {}
~ProfileScope() { if(profiler_) profiler_->add(stage_,std::chrono::duration_cast<std::chrono::nanoseconds>(Clock::now()-start_).count()); }
std::uint64_t elapsed_ns() const {
return profiler_?std::chrono::duration_cast<std::chrono::nanoseconds>(Clock::now()-start_).count():0;
}
private:
using Clock=std::chrono::steady_clock;
RuntimeProfiler* profiler_{}; ProfileStage stage_{}; Clock::time_point start_{};
};
}

View File

@@ -12,6 +12,7 @@
#include "FluidSim/Fluid.hpp"
#include "FluidSim/OutdoorAudio.hpp"
#include "FluidSim/Solver.hpp"
#include "FluidSim/RuntimeProfiler.hpp"
#include "FluidSim/Vehicle.hpp"
#include "FluidSim/MotorcycleTest.hpp"
#include <memory>
@@ -35,13 +36,22 @@ public:
int cylinder_count() const { return engine_config.cylinders; }
std::array<FourStrokeCylinder*, 12> cylinders() { return {&cylinder1, &cylinder2, &cylinder3, &cylinder4,
&cylinder5, &cylinder6, &cylinder7, &cylinder8,&cylinder9,&cylinder10,&cylinder11,&cylinder12}; }
std::array<const FourStrokeCylinder*, 12> cylinders() const { return {&cylinder1, &cylinder2, &cylinder3, &cylinder4,
&cylinder5, &cylinder6, &cylinder7, &cylinder8,&cylinder9,&cylinder10,&cylinder11,&cylinder12}; }
AudioFrame process();
void enable_profiling(bool enabled) { profiler_.enable(enabled); }
const ProfileTotals& profile_totals() const { return profiler_.totals(); }
void set_controls(float throttle_value, float load_value, float clutch_value);
void set_audio_scene(OutdoorScene scene);
void set_listener(AudioListener listener);
void set_pipe_solver(PipeSolver solver);
void set_monitor_filter(bool enabled);
void set_starter(bool engaged);
// Replaces calibration at a simulation step boundary, preserving all state.
void set_tuning(const EngineConfig& tuning);
const auto& source_audio() const { return audio_.source_frames(); }
AudioFrame tail_audio() const { return audio_.tail_frame(); }
void set_audio_source_enabled(std::size_t index,bool enabled) { audio_.set_source_enabled(index,enabled); }
AudioFrame dry_audio() const;
AudioFrame unfiltered_audio() const;
void shift_up();
@@ -69,6 +79,12 @@ private:
BoundarySystem boundaries_;
Solver solver_;
OutdoorAudio audio_;
RuntimeProfiler profiler_;
std::shared_ptr<const PhysicalGeometry> geometry_;
std::shared_ptr<const EngineConfig> active_tuning_;
std::array<float,181> intake_lift_trace_{},exhaust_lift_trace_{};
float last_contact_impulse_{};
float valve_sound_displacement_{},valve_sound_velocity_{};
mutable std::mutex mutex_;
std::vector<std::unique_ptr<Junction>> exhaust_junctions_;
std::unique_ptr<Carburetor> carb_;

View File

@@ -16,8 +16,13 @@ struct ScenarioExample {
EngineConfig engine{};
std::string category;
int order{};
std::filesystem::path source_path;
};
ScenarioExample load_engine_file(const std::filesystem::path& path);
void migrate_engine_geometry(const std::filesystem::path& path,bool relayout=false);
// Writes a distinct, validated engine file; never overwrites a preset.
std::filesystem::path save_tuned_engine(const ScenarioExample& source,const EngineConfig& tuning,
const std::filesystem::path& directory);
std::vector<ScenarioExample> load_engine_directory(const std::filesystem::path& directory);
// FLUIDSIM_ENGINE_DIR overrides discovery. Otherwise search the working directory,
// beside the executable, then the source tree for developer tools.

View File

@@ -7,10 +7,11 @@ struct ScenarioSnapshot;
// Fractions refer to simulated pipe length; positions are world metres.
// Both the pipe mesh and its acoustic mouths use these same routes.
struct PipeRoute {
std::array<Vec3, 5> points{};
std::array<float, 5> fractions{};
std::array<Vec3, 64> points{};
std::array<float, 64> fractions{};
unsigned count{};
};
std::array<PipeRoute, 32> scenario_pipe_routes(const ScenarioSnapshot& state);
bool scenario_pipe_is_exhaust(const ScenarioSnapshot& state, std::size_t pipe_index);
std::array<Vec3, 3> scenario_sound_sources(const ScenarioSnapshot& state);
}

View File

@@ -7,10 +7,19 @@
#include <cstdint>
#include <string>
#include <vector>
#include <memory>
namespace fluidsim::port {
struct ScenarioSnapshot {
struct StorageVolume {std::string id;GeometryBox box;float pressure{};};
struct PipeHousing {std::size_t pipe{};float start{},length{},diameter{};};
std::shared_ptr<const PhysicalGeometry> geometry;
std::vector<StorageVolume> storage_volumes;
std::vector<PipeHousing> pipe_housings;
float cylinder_bore{},cylinder_stroke{},cylinder_rod{},cylinder_clearance{};
float intake_valve_diameter{},exhaust_valve_diameter{};
std::string example_name;
float cells_per_meter{};
EngineLayout layout{EngineLayout::Inline};
float bank_angle{};
bool v8_headers{};
@@ -20,6 +29,8 @@ struct ScenarioSnapshot {
float rev_limit_rpm{10000};
std::array<float, 12> cylinder_angles{};
std::array<float, 12> cylinder_pressures{}, piston_fractions{}, intake_areas{}, exhaust_areas{};
std::array<float,12> intake_lifts{},exhaust_lifts{};
std::uint64_t junction_fallbacks{}, junction_iterations{}, boundary_limited_fluxes{}, junction_limited_fluxes{};
std::uint64_t solver_substeps{}, boundary_iterations{}, pipe_positivity_repairs{};
float rpm{}, speed_kmh{}, kinetic_energy{}, average_power{}, average_torque{};
float cylinder_pressure1{}, cylinder_pressure2{};
@@ -30,6 +41,10 @@ struct ScenarioSnapshot {
std::array<float,2> crossover_pressures{};
float throttle{}, pedal{}, target_afr{}, clutch{}, intake_flow{}, exhaust_flow{};
bool idle_control{}, fuel_cut{}, ignition_cut{}, two_stroke{}, reed_open{};
bool valve_float{};
// Cylinder 1 lift over the most recently sampled 720 degree cycle (4 degree bins, metres).
std::array<float,181> intake_cam_lifts{},exhaust_cam_lifts{},intake_lift_trace{},exhaust_lift_trace{};
float intake_lift{},exhaust_lift{},valve_contact_impulse{};
bool racing_exhaust{};
bool silencers{}, h_pipe{};
ExhaustConfig exhaust_config{};

View File

@@ -46,7 +46,7 @@ inline Pipe1D make_silenced_exhaust(const ExhaustConfig& c) {
add(c.outlet_length,c.diameter,c.diameter);
auto pipe=make_profiled_pipe(sections,c.cell_length);
for(std::size_t i=0;i<sections.size();++i) {
const int n=int(std::ceil(sections[i].length/c.cell_length));
const int n=profile_section_cells(sections[i].length,c.cell_length);
for(int j=0;j<n;++j) { pipe.linear_resistance.push_back(linear[i]);pipe.quadratic_resistance.push_back(quadratic[i]); }
}
return pipe;

View File

@@ -2,6 +2,7 @@
#include "FluidSim/Component.hpp"
#include "FluidSim/Fluid.hpp"
#include "FluidSim/RuntimeProfiler.hpp"
#include <cstdint>
#include <vector>
@@ -13,13 +14,15 @@ public:
std::uint64_t substep_count() const { return substep_count_; }
int minimum_substeps{4};
void add_component(Component& component);
void step(float dt);
void set_profiler(RuntimeProfiler* profiler) { profiler_=profiler; }
void step(float dt,bool profile_sample=false);
std::uint64_t step_count() const { return step_count_; }
private:
std::vector<Pipe1D>& pipes_;
BoundarySystem& boundaries_;
std::vector<Component*> components_;
std::uint64_t step_count_{}, substep_count_{};
RuntimeProfiler* profiler_{};
};
} // namespace fluidsim::port

View File

@@ -1,7 +1,9 @@
#pragma once
#include "FluidSim/MeshResolution.hpp"
#include "FluidSim/PipeProfile.hpp"
#include "FluidSim/Ecu.hpp"
#include "FluidSim/Vehicle.hpp"
#include "FluidSim/PhysicalGeometry.hpp"
#include <array>
namespace fluidsim::port {
@@ -10,6 +12,8 @@ enum class TwoStrokeExhaust { Racing, Straight };
// YZ125-sized baseline. Port/chamber/combustion dimensions are explicit design
// estimates, not a measured Yamaha cylinder or factory exhaust/ignition map.
struct TwoStrokeConfig {
PhysicalGeometry geometry;
float cells_per_meter{}; // Zero retains legacy programmatic meshes. TOML scenarios specify a density.
EcuConfig ecu=[] {
EcuConfig e;
e.idle_control=EcuConfig::IdleControl::FixedOpening;
@@ -42,6 +46,7 @@ struct TwoStrokeConfig {
float jet_scale{1.f}; // multiplier on effective jet diameters
float crankcase_clearance{150e-6f};
float carburetor_diameter{.0381f};
float reed_pipe_diameter{.040f},transfer_pipe_diameter{.032f};
float idle_spark_gain{.05f}; // spark degrees per RPM error at the fixed carb stop
float idle_minimum_advance{-10.f};
float idle_slide_opening{.045f}; // fixed mechanical stop, normalized 0..1 (4.5%)

View File

@@ -23,6 +23,9 @@ public:
void set_crankcase(Crankcase& crankcase) { crankcase_ = &crankcase; }
void set_carburetor(Carburetor& carburetor) { carburetor_ = &carburetor; }
float transfer_width{.050f}, exhaust_width{.036f};
// Retained as two-stroke compatibility metadata; port areas come from the
// transfer and exhaust geometry above rather than poppet valve motion.
float intake_valve_lift{}, exhaust_valve_lift{};
float minimum_burn_seconds{.0008f};
std::uint64_t combustion_count() const { return combustion_count_; }

View File

@@ -5,6 +5,7 @@
#include "FluidSim/Carburetor.hpp"
#include "FluidSim/Fluid.hpp"
#include "FluidSim/OutdoorAudio.hpp"
#include "FluidSim/RuntimeProfiler.hpp"
#include "FluidSim/Ecu.hpp"
#include "FluidSim/ReedValve.hpp"
#include "FluidSim/MotorcycleTest.hpp"
@@ -15,6 +16,7 @@
#include <vector>
namespace fluidsim::port {
struct ScenarioSnapshot;
struct TwoStrokeSnapshot {
float rpm{}, cylinder_pressure{}, crankcase_pressure{}, intake_pressure{};
@@ -31,6 +33,8 @@ public:
void set_listener(AudioListener listener) { audio_.set_listener(listener); }
explicit TwoStrokeScenario(int sample_rate = 44100, TwoStrokeConfig config = {});
AudioFrame process();
void enable_profiling(bool enabled) { profiler_.enable(enabled); }
const ProfileTotals& profile_totals() const { return profiler_.totals(); }
void set_throttle(float value);
void set_controls(float pedal, float load_value, float clutch_value);
void shift_up() { vehicle.shift_up(); }
@@ -39,9 +43,13 @@ public:
void set_pipe_solver(PipeSolver solver) { for(auto& pipe : pipes) pipe.solver=solver; }
void set_audio_scene(OutdoorScene scene) { audio_.set_scene(scene); }
void set_monitor_filter(bool enabled) { audio_.set_monitor_filter(enabled); }
const auto& source_audio() const { return audio_.source_frames(); }
AudioFrame tail_audio() const { return audio_.tail_frame(); }
void set_audio_source_enabled(std::size_t index,bool enabled) { audio_.set_source_enabled(index,enabled); }
AudioFrame dry_audio() const { return audio_.dry_frame(); }
AudioFrame unfiltered_audio() const { return audio_.unfiltered_frame(); }
TwoStrokeSnapshot snapshot() const;
void capture_geometry(ScenarioSnapshot&) const;
float throttle{};
const TwoStrokeConfig configuration;
@@ -65,6 +73,8 @@ private:
BoundarySystem boundaries_;
Solver solver_;
OutdoorAudio audio_;
RuntimeProfiler profiler_;
std::shared_ptr<const PhysicalGeometry> geometry_;
static float area_for_diameter(float diameter);
static float slide_area(float diameter, float opening);

View File

@@ -2,6 +2,7 @@
#include "FluidSim/SoundEngine.hpp"
#include "FluidSim/Desktop/DesktopUi.hpp"
#include "FluidSim/Desktop/ScenarioMenu.hpp"
#include "FluidSim/Desktop/PlaybackControl.hpp"
#include <SFML/Graphics.hpp>
#include <algorithm>
@@ -11,10 +12,12 @@
#include <iostream>
#include <cmath>
#include <filesystem>
#include <future>
#include <iomanip>
#include <optional>
#include <sstream>
#include <thread>
#include <unordered_map>
namespace {
using namespace fluidsim::port;
@@ -31,13 +34,15 @@ constexpr float throttle_lerp_rate = 10.0f;
int main(int argc, char** argv) try {
using namespace fluidsim::port;
bool scenario_from_args=false;
const auto& examples=scenario_examples();
std::string junction_model;
std::optional<float> junction_loss;
auto examples=scenario_examples();
auto selected=examples.front();
PipeSolver selected_solver=PipeSolver::Rusanov;
OutdoorScene selected_scene=OutdoorScene::Wall;
for(int i=1;i<argc;++i){
const std::string option=argv[i];
if((option=="--example"||option=="--scenario"||option=="--scene"||option=="--exhaust"||option=="--solver")&&i+1<argc){
if((option=="--example"||option=="--scenario"||option=="--scene"||option=="--exhaust"||option=="--solver"||option=="--junctions"||option=="--junction-loss")&&i+1<argc){
const std::string value=argv[++i];
if(option=="--example") {
const auto found=std::find_if(examples.begin(),examples.end(),[&](const auto& e){return e.id==value;});
@@ -47,7 +52,7 @@ int main(int argc, char** argv) try {
if(value!="twin"&&value!="two-stroke"&&value!="b205"&&value!="v8"){std::cerr<<"Choose twin, two-stroke, b205 or v8\n";return 2;}
selected=scenario_example(value=="v8"?"v8-h":value=="b205"?"saab-straight":value=="two-stroke"?"yz-racing":"twin");
scenario_from_args=true;
}else if(option=="--solver"){
}else if(option=="--solver"||option=="--junctions"||option=="--junction-loss"){
try { selected_solver=parse_pipe_solver(value); }
catch(const std::exception& e) { std::cerr<<e.what()<<"\n"; return 2; }
}else if(option=="--exhaust"){
@@ -62,15 +67,15 @@ int main(int argc, char** argv) try {
else if(value=="tunnel")selected_scene=OutdoorScene::Tunnel;
else {std::cerr<<"Choose dry, field, wall, woodland or tunnel\n";return 2;}
}
}else{std::cerr<<"Usage: fluidsim_desktop [--example catalog-id] [--scenario twin|two-stroke|b205|v8] [--scene dry|field|wall|woodland|tunnel] [--exhaust racing|straight] [--solver rusanov|hlle|hllc]\n";return 2;}
}else{std::cerr<<"Usage: fluidsim_desktop [--example catalog-id] [--scenario twin|two-stroke|b205|v8] [--scene dry|field|wall|woodland|tunnel] [--exhaust racing|straight] [--solver rusanov|hlle|hllc|muscl] [--junctions volume|characteristic|legacy] [--junction-loss 0..10]\n";return 2;}
}
const sf::Vector2u initial_size = scenario_from_args ? sf::Vector2u{window_width, window_height}
: sf::Vector2u{720, 606};
sf::ContextSettings context;
context.depthBits=24;context.antiAliasingLevel=4;context.majorVersion=2;context.minorVersion=1;
sf::RenderWindow window(sf::VideoMode(initial_size), "FluidSim",sf::Style::Default,sf::State::Windowed,context);
window.setVerticalSyncEnabled(false);
window.setFramerateLimit(60);
window.setFramerateLimit(0);
window.setVerticalSyncEnabled(true);
window.setKeyRepeatEnabled(false);
sf::Font font;
@@ -85,6 +90,7 @@ int main(int argc, char** argv) try {
if(!has_font)std::cerr<<"No UI font available; configure FLUIDSIM_FONT_FILE.\n";
const sf::Font* font_ptr=has_font?&font:nullptr;
std::unordered_map<std::string,float> scenario_resolution;
while (window.isOpen()) {
if (!scenario_from_args) {
window.setTitle("FluidSim - Examples");
@@ -99,13 +105,20 @@ int main(int argc, char** argv) try {
scenario_from_args=false;
window.setTitle("FluidSim - "+selected.title);
window.setMinimumSize(sf::Vector2u{1040,800});
if(!junction_model.empty()) selected.junctions.model=junction_model;
if(junction_loss) selected.junctions.turning_loss=*junction_loss;
if(selected.kind==ScenarioKind::TwoStroke && (!junction_model.empty() || junction_loss))
throw std::invalid_argument("two-stroke exhaust has no configurable pipe junctions");
if(const auto found=scenario_resolution.find(selected.id);found!=scenario_resolution.end())
selected.engine.cells_per_meter=selected.two.cells_per_meter=found->second;
DesktopScenario scenario(selected);
scenario.set_pipe_solver(selected_solver);
scenario.set_audio_scene(selected_scene);
desktop::DesktopUi ui;
ui.configure_resolution(selected.kind==ScenarioKind::TwoStroke?selected.two.cells_per_meter:selected.engine.cells_per_meter);
scenario.set_listener(ui.listener(scenario.snapshot(),window.getSize()));
std::cout << "Desktop started: scenario=" << selected.id
<< " solver=" << pipe_solver_name(selected_solver) << " scene=" << scene_name(selected_scene) << std::endl;
<< " junctions=" << selected.junctions.model << " solver=" << pipe_solver_name(selected_solver) << " scene=" << scene_name(selected_scene) << std::endl;
SimulationRingBuffer ring(ring_capacity);
for (std::size_t i = 0; i < preroll_samples; ++i) {
const AudioFrame audio = scenario.process();
@@ -117,8 +130,10 @@ int main(int argc, char** argv) try {
audio.set_volume(100.0f);
audio.start();
std::jthread simulation([&](std::stop_token stop) {
constexpr std::size_t batch_size = 512;
while (ring.wait_for_space(target_fill - batch_size, stop)) {
while(!stop.stop_requested()) {
// Keep bursts short in wall time even at 0.01x playback.
const auto batch_size=std::clamp<std::size_t>(static_cast<std::size_t>(std::ceil(128*audio.speed())),1,128);
if(!ring.wait_for_space(target_fill-batch_size,stop))break;
for (std::size_t i = 0; i < batch_size; ++i) {
const AudioFrame frame = scenario.process();
if (!ring.write(frame.left, frame.right, stop)) return;
@@ -126,15 +141,14 @@ int main(int argc, char** argv) try {
}
});
double desired_speed = 1.0;
double current_speed = desired_speed;
double last_normal_speed = 1.0;
bool time_warp = false;
desktop::PlaybackControl playback_control;
double current_speed = 1.0;
float throttle_current = 0.0f;
float brake_target = 0.0f, brake_current = 0.0f;
float clutch_current = 0.0f;
bool throttle_held = false, throttle_down = false, clutch_held = false;
auto previous_frame = std::chrono::steady_clock::now();
std::future<void> pipe_rebuild;
bool return_to_menu=false;
while (window.isOpen() && !return_to_menu) {
@@ -144,19 +158,12 @@ int main(int argc, char** argv) try {
return_to_menu=true;
break;
}
if(playback_control.handle_event(*event,sf::Keyboard::isKeyPressed(sf::Keyboard::Key::LShift)||sf::Keyboard::isKeyPressed(sf::Keyboard::Key::RShift)))continue;
if(ui.handle_event(*event,window.getSize())) continue;
if(event->is<sf::Event::FocusLost>()) { throttle_held=false; throttle_down=false; clutch_held=false; scenario.set_starter(false); }
if (event->is<sf::Event::Closed>()) window.close();
if (const auto* key = event->getIf<sf::Event::KeyPressed>()) {
switch (key->code) {
case sf::Keyboard::Key::Space:
time_warp = !time_warp;
if (!time_warp) { desired_speed = last_normal_speed; }
break;
case sf::Keyboard::Key::PageUp:
if(!time_warp) {desired_speed=std::min(1.,desired_speed*1.1);last_normal_speed=desired_speed;} break;
case sf::Keyboard::Key::PageDown:
if(!time_warp) {desired_speed=std::max(.0001,desired_speed/1.1);last_normal_speed=desired_speed;} break;
case sf::Keyboard::Key::W: throttle_held = true; break;
case sf::Keyboard::Key::V: scenario.set_starter(true); break;
case sf::Keyboard::Key::C: clutch_held = true; break;
@@ -169,7 +176,7 @@ int main(int argc, char** argv) try {
case sf::Keyboard::Key::Z: scenario.set_neutral(); break;
case sf::Keyboard::Key::S: throttle_down=true; break;
case sf::Keyboard::Key::F3:
selected_solver=static_cast<PipeSolver>((static_cast<int>(selected_solver)+1)%3);
selected_solver=static_cast<PipeSolver>((static_cast<int>(selected_solver)+1)%4);
scenario.set_pipe_solver(selected_solver);
std::cout << "Pipe solver: " << pipe_solver_name(selected_solver) << std::endl;
break;
@@ -192,10 +199,24 @@ int main(int argc, char** argv) try {
if (!window.isOpen() || return_to_menu) break;
if(pipe_rebuild.valid() && pipe_rebuild.wait_for(std::chrono::seconds(0))==std::future_status::ready) {
try {
pipe_rebuild.get();const float density=scenario.snapshot().cells_per_meter;
scenario_resolution[selected.id]=density;
ui.resolution_finished(density,"Mesh applied. Engine restarted.");
} catch(const std::exception& error) {
ui.resolution_finished(scenario.snapshot().cells_per_meter,error.what());
}
}
if(auto density=ui.take_resolution_request();density && !pipe_rebuild.valid()) {
ui.resolution_started();
pipe_rebuild=std::async(std::launch::async,[&scenario,value=*density] {scenario.set_cells_per_meter(value);});
}
const auto now = std::chrono::steady_clock::now();
const float frame_dt = std::chrono::duration<float>(now - previous_frame).count();
previous_frame = now;
const double target_speed = time_warp ? 1.0 : desired_speed;
const double target_speed = playback_control.target_speed();
current_speed += (target_speed - current_speed) * (1.0 - std::exp(-8.0 * frame_dt));
audio.set_speed(current_speed);
@@ -208,16 +229,18 @@ int main(int argc, char** argv) try {
scenario.set_controls(throttle_current, brake_current, clutch_current);
const desktop::PlaybackInfo playback{throttle_current,brake_current,current_speed,
ring.available_samples(),ring_capacity,audio.underrun_blocks(),audio.auto_gain(),time_warp};
ring.available_samples(),ring_capacity,audio.underrun_blocks(),audio.auto_gain(),!playback_control.realtime()};
const auto state=scenario.snapshot();
scenario.set_listener(ui.listener(state,window.getSize()));
ui.draw(window,font_ptr,state,playback);
window.display();
}
simulation.request_stop();
audio.stop();
simulation.join();
if(pipe_rebuild.valid())try {pipe_rebuild.get();scenario_resolution[selected.id]=scenario.snapshot().cells_per_meter;}catch(const std::exception& error){std::cerr<<"Pipe rebuild: "<<error.what()<<'\n';}
}
return 0;
} catch(const std::exception& error) {

View File

@@ -26,9 +26,16 @@ WorkspaceLayout WorkspaceLayout::calculate(sf::Vector2u size) {
{{left,bottom},{content*.60f-8,178}},{{left+content*.60f+8,bottom},{content*.40f-8,178}},{{32,h<800?382.f:414.f},{side-32,26}}};
}
bool DesktopUi::handle_event(const sf::Event& event,sf::Vector2u size) {
const auto layout=WorkspaceLayout::calculate(size);
const auto layout=workspace_layout(size);
const auto set_pedal=[&](int x) {pedal_.set((x-layout.pedal.position.x)/layout.pedal.size.x);};
if(const auto* p=event.getIf<sf::Event::MouseButtonPressed>()) {
const auto buttons=resolution_buttons(size);
if(p->button==sf::Mouse::Button::Left) for(int i=0;i<2;++i)
if(buttons[i].contains(sf::Vector2f(p->position))) {
if(!resolution_busy_) resolution_request_=std::clamp(resolution_+(i==0?-5.f:5.f),1.f,500.f);
return true;
}
if(p->button==sf::Mouse::Button::Left && layout.pedal.contains(sf::Vector2f(p->position))) {
pedal_drag_=true;set_pedal(p->position.x);return true;
}
@@ -61,20 +68,46 @@ bool DesktopUi::handle_event(const sf::Event& event,sf::Vector2u size) {
return false;
}
AudioListener DesktopUi::listener(const ScenarioSnapshot& s,sf::Vector2u size) {
const auto viewport=WorkspaceLayout::calculate(size).viewport;
if(!camera_fitted_) {
camera_.reset(viewport.size.x/(viewport.size.y-56));
camera_fitted_=true;
const auto viewport=workspace_layout(size).viewport;
fit_camera(viewport.size.x/(viewport.size.y-56),s);
return camera_.listener();
}
WorkspaceLayout DesktopUi::workspace_layout(sf::Vector2u size) const {
return WorkspaceLayout::calculate(size);
}
std::array<sf::FloatRect,2> DesktopUi::resolution_buttons(sf::Vector2u size) {
const auto layout=WorkspaceLayout::calculate(size);
const float y=layout.pedal.position.y+40+12+28+(size.y<800?19.f:23.f);
return {sf::FloatRect{{162,y-2},{24,19}},sf::FloatRect{{240,y-2},{24,19}}};
}
void DesktopUi::fit_camera(float aspect,const ScenarioSnapshot& s) {
if(!camera_fitted_ || std::abs(camera_aspect_-aspect)>.01f) {
camera_.reset(aspect);camera_fitted_=true;camera_aspect_=aspect;
if(s.geometry) {
Vec3 lo{1e9f,1e9f,1e9f},hi{-1e9f,-1e9f,-1e9f};
const auto include=[&](Vec3 p) {lo={std::min(lo.x,p.x),std::min(lo.y,p.y),std::min(lo.z,p.z)};hi={std::max(hi.x,p.x),std::max(hi.y,p.y),std::max(hi.z,p.z)};};
for(const auto& pipe:s.geometry->pipes)for(auto point:pipe.points)include(point);
for(const auto& v:s.storage_volumes){include(v.box.minimum());include(v.box.maximum());}
for(const auto& c:s.geometry->cylinders){include(c.origin);include(cylinder_point(c,{0,s.cylinder_rod+s.cylinder_stroke*.5f,0}));}
camera_.target={(lo.x+hi.x)*.5f,(lo.y+hi.y)*.5f,(lo.z+hi.z)*.5f};
const float yaw=camera_.yaw*.017453293f,pitch=camera_.pitch*.017453293f;
float distance=.35f;
for(int corner=0;corner<8;++corner) {
const Vec3 p{(corner&1?hi.x:lo.x)-camera_.target.x,(corner&2?hi.y:lo.y)-camera_.target.y,(corner&4?hi.z:lo.z)-camera_.target.z};
const float x=std::cos(yaw)*p.x+std::sin(yaw)*p.z,z=-std::sin(yaw)*p.x+std::cos(yaw)*p.z;
const float y=std::cos(pitch)*p.y-std::sin(pitch)*z,depth=std::sin(pitch)*p.y+std::cos(pitch)*z;
distance=std::max(distance,depth+1.12f*std::max((std::abs(x)+.035f)/(.414214f*aspect),(std::abs(y)+.035f)/.414214f));
}
camera_.distance=distance;
}
}
return camera_.listener(s.silencers?1.8f:s.cylinder_count==8?1.4f:1.f,
s.silencers?1.1f:s.cylinder_count==8?.7f:0.f);
}
void DesktopUi::draw(sf::RenderWindow& w,const sf::Font* font,const ScenarioSnapshot& s,const PlaybackInfo& p) {
const auto size=w.getSize();const float width=float(size.x),height=float(size.y);
w.setView(sf::View(sf::FloatRect({0,0},{width,height})));w.clear(background);
const auto l=WorkspaceLayout::calculate(size);
const auto l=workspace_layout(size);
label(w,font,"FLUIDSIM",20,15,24,accent);label(w,font,"ENGINE WORKSPACE",20,48,10,muted);
label(w,font,title(s),l.viewport.position.x+2,22,19);label(w,font,"LIVE / "+number(float(p.speed),3)+"x",width-160,25,14,accent);
label(w,font,title(s),l.viewport.position.x+2,22,19);label(w,font,std::string(p.time_warp?"MANUAL / ":"REALTIME / ")+number(float(p.speed),3)+"x",width-192,25,14,accent);
box(w,l.sidebar);box(w,l.viewport);box(w,l.chart);box(w,l.inspector);
const float x=32;float y=97;
label(w,font,"ENGINE",x,y,11,accent);y+=25;
@@ -92,14 +125,22 @@ void DesktopUi::draw(sf::RenderWindow& w,const sf::Font* font,const ScenarioSnap
box(w,{{l.pedal.position.x+l.pedal.size.x*p.pedal_target-5,l.pedal.position.y+2},{10,22}},sf::Color(225,225,225));
y=l.pedal.position.y+40;
y+=12;label(w,font,"AUDIO & SIMULATION",x,y,11,accent);y+=28;
row("Solver [F3]",pipe_solver_name(s.pipe_solver));row("Scene [R]",scene_name(s.audio_scene));row("Auto gain [G]",p.auto_gain?"On":"Off");
row("Solver [F3]",pipe_solver_name(s.pipe_solver));
const auto buttons=resolution_buttons(size);
label(w,font,"Cells/m",x,y,12,muted);
box(w,buttons[0]);box(w,buttons[1]);
label(w,font,"-",buttons[0].position.x+7,buttons[0].position.y,13);
label(w,font,"+",buttons[1].position.x+7,buttons[1].position.y,13);
label(w,font,number(resolution_),192,y,12);
y+=height<800?19.f:23.f;
row("Scene [R]",scene_name(s.audio_scene));row("Auto gain [G]",p.auto_gain?"On":"Off");
row("Buffer",std::to_string(p.buffered)+" / "+std::to_string(p.capacity));row("Underruns",std::to_string(p.underruns));
label(w,font,s.fuel_cut?"FUEL CUT":s.ignition_cut?"IGNITION CUT":s.idle_control?"IDLE CONTROL":"RUNNING",x,y+8,12,(s.fuel_cut||s.ignition_cut)?sf::Color(255,171,96):accent);
if(height>=800) label(w,font,"W/S Raise / lower pedal\nA/D Shift Z Neutral\nC Clutch V Starter\nArrows Load End Release\nPgUp/Dn Speed Space 1x",x,height-141,11,muted);
label(w,font,three_d_?"ENGINE VIEW / 3D":"ENGINE VIEW / SCHEMATIC",l.viewport.position.x+16,93,11,accent);
label(w,font,three_d_?"Drag: orbit Right: pan Scroll: zoom Home: fit Tab: 2D":"Drag: pan Scroll: zoom Home: reset Tab: 3D",l.viewport.position.x+16,117,11,muted);
label(w,font,s.valve_float?"VALVE FLOAT":s.fuel_cut?"FUEL CUT":s.ignition_cut?"IGNITION CUT":s.idle_control?"IDLE CONTROL":"RUNNING",x,y+8,12,(s.valve_float||s.fuel_cut||s.ignition_cut)?sf::Color(255,171,96):accent);
if(height>=800) label(w,font,"W/S Raise / lower pedal\nA/D Shift Z Neutral\nC Clutch V Starter\nArrows Load End Release\nSpace Realtime / manual\nShift+scroll / PgUp/Dn speed",x,height-141,11,muted);
label(w,font,three_d_?"ENGINE GEOMETRY / 3D":"ENGINE GEOMETRY / 2D",l.viewport.position.x+16,93,11,accent);
label(w,font,l.viewport.size.x<600?"Drag: orbit Scroll: zoom Tab: 2D":three_d_?"Drag: orbit Right: pan Scroll: zoom Home: fit Tab: 2D":"Drag: pan Scroll: zoom Home: reset Tab: 3D",l.viewport.position.x+16,117,11,muted);
const sf::FloatRect drawing{{l.viewport.position.x,l.viewport.position.y+56},{l.viewport.size.x,l.viewport.size.y-56}};
if(!camera_fitted_) {camera_.reset(drawing.size.x/drawing.size.y);camera_fitted_=true;}
fit_camera(drawing.size.x/drawing.size.y,s);
if(three_d_) renderer_.draw(w,s,drawing,camera_);
else {
sf::View view(sf::FloatRect({0,0},{1280,600}));
@@ -122,7 +163,8 @@ void DesktopUi::draw(sf::RenderWindow& w,const sf::Font* font,const ScenarioSnap
label(w,font,std::to_string(i+1)+" "+number(pressure/1000)+" "+number(stroke*100)+"% "+(intake?"O":"-")+" / "+(exhaust?"O":"-"),ix+(s.cylinder_count>8?i/6*(l.inspector.size.x/2-4):0),iy+42+(s.cylinder_count>8?i%6:i)*15,10);
}
label(w,font,"Esc: menu End: release pedal",20,height-29,11,muted);
label(w,font,resolution_status_.empty()?"Cells/m changes restart the engine":resolution_status_,l.viewport.position.x+16,l.viewport.position.y+l.viewport.size.y-22,11,muted);
label(w,font,"Pressure: blue vacuum / green atmosphere / red 1 MPa",l.viewport.position.x+2,height-29,11,muted);
w.display();
}
}

View File

@@ -90,32 +90,61 @@ void EngineCylinder::initialize(const Crankshaft& /*crank*/) {
}
float FourStrokeCylinder::valve_area(float cycle_degrees, bool inlet) const {
const float opens = inlet ? intake_open : exhaust_open;
float closes = inlet ? intake_close : exhaust_close;
const float peak_lift = inlet ? intake_valve_lift : exhaust_valve_lift;
(void)cycle_degrees;
const float lift = inlet ? intake_lift_ : exhaust_lift_;
const float diameter = inlet ? intake_valve_diameter : exhaust_valve_diameter;
if (closes < opens) closes += 720.0f;
float mapped = std::fmod(cycle_degrees, 720.0f);
if (mapped < 0.0f) mapped += 720.0f;
if (mapped < opens) mapped += 720.0f;
const float duration = closes - opens;
if (mapped < opens || mapped > closes || duration <= 0.0f) return 0.0f;
const float ramp = duration * 0.25f;
const float hold = duration - 2.0f * ramp;
float lift = 0.0f;
if (mapped < opens + ramp) {
const float t = (mapped - opens) / ramp;
lift = peak_lift * t * t * (3.0f - 2.0f * t);
} else if (mapped < opens + ramp + hold) {
lift = peak_lift;
} else if (ramp > 0.0f) {
const float t = (mapped - (opens + ramp + hold)) / ramp;
lift = peak_lift * (1.0f - t) * (1.0f - t) * (1.0f + 2.0f * t);
}
return pi * diameter * lift * (inlet ? intake_valve_count : exhaust_valve_count);
}
void FourStrokeCylinder::set_valve_profiles(const CamProfile& ic,const ValveDynamics& iv,const CamProfile& ec,const ValveDynamics& ev) {
intake_cam_=&ic; intake_dynamics_=iv; exhaust_cam_=&ec; exhaust_dynamics_=ev;
}
FourStrokeCylinder::CamPoint FourStrokeCylinder::sample_cam(const CamProfile& profile,float degrees) {
if(profile.points.empty()) return {};
degrees=std::fmod(degrees,720.f); if(degrees<0)degrees+=720.f;
auto upper=std::upper_bound(profile.points.begin(),profile.points.end(),degrees,
[](float angle,const CamPoint& point){return angle<point.angle_degrees;});
if(upper==profile.points.begin())return *upper;
if(upper==profile.points.end())return profile.points.back();
const auto& b=*upper;const auto& a=*(upper-1);
const float t=(degrees-a.angle_degrees)/(b.angle_degrees-a.angle_degrees);
return {degrees,a.lift_metres+(b.lift_metres-a.lift_metres)*t};
}
float FourStrokeCylinder::advance_valve(ValveState& state,const CamProfile& cam,const ValveDynamics& d,
bool inlet,const Crankshaft& crank,float dt) {
const float phase_rad=phase;
const float now=wrap_degrees((crank.angle+phase_rad)*180.f/pi);
const float before=wrap_degrees((crank.previous_angle+phase_rad)*180.f/pi);
const auto cam_now=sample_cam(cam,now),cam_before=sample_cam(cam,before);
const float cam_velocity=dt>0?(cam_now.lift_metres-cam_before.lift_metres)/dt:0;
const float count=float(inlet?intake_valve_count:exhaust_valve_count);
const float mass=d.mass_kg*count, spring=d.spring_rate_n_per_m*count, damping=d.damping_n_s_per_m*count;
const float diameter=inlet?intake_valve_diameter:exhaust_valve_diameter;
const float port_pressure=inlet?intake.pressure:exhaust.pressure;
const float pressure_force=(port_pressure-pressure)*(pi*.25f*diameter*diameter)*count;
const float acceleration=(pressure_force-count*d.seat_force_n-spring*state.lift-damping*state.velocity)/std::max(mass,1e-7f);
state.velocity+=acceleration*dt;
const float old_lift=state.lift;
state.lift+=state.velocity*dt;
const float lower=cam_now.lift_metres;
if(state.lift<lower || (state.lift<=lower && state.velocity<cam_velocity)) {
const float relative=state.velocity-cam_velocity;
if(relative<0) contact_impulse_+=mass*(-relative)*(1+d.cam_restitution);
state.lift=lower;state.velocity=cam_velocity-std::min(relative,0.f)*d.cam_restitution;
}
if(state.lift<0 || (state.lift<=0 && state.velocity<0)) {
if(state.velocity<0)contact_impulse_+=mass*(-state.velocity)*(1+d.seat_restitution);
state.lift=0;state.velocity=std::max(0.f,-state.velocity*d.seat_restitution);
}
if(state.lift>d.maximum_lift_m) {
if(state.velocity>0)contact_impulse_+=mass*state.velocity*(1+d.stop_restitution);
state.lift=d.maximum_lift_m;state.velocity=-std::max(0.f,state.velocity)*d.stop_restitution;
}
state.floating=state.lift>lower+5e-5f;
(void)old_lift;
return state.lift;
}
void FourStrokeCylinder::pre_step(Crankshaft& crank, float dt, float /*throttle*/) {
const float rpm = crank.omega * 60.0f / (2.0f * pi);
if (ecu) {
@@ -127,6 +156,9 @@ void FourStrokeCylinder::pre_step(Crankshaft& crank, float dt, float /*throttle*
}
const float theta = crank.angle + phase;
if(intake_cam_) intake_lift_=advance_valve(intake_state_,*intake_cam_,intake_dynamics_,true,crank,dt);
if(exhaust_cam_) exhaust_lift_=advance_valve(exhaust_state_,*exhaust_cam_,exhaust_dynamics_,false,crank,dt);
intake_floating_=intake_state_.floating;exhaust_floating_=exhaust_state_.floating;
const float previous_volume = volume;
volume = compute_volume(theta);
pressure = (gamma - 1.0f) * energy / std::max(volume, 1e-12f);
@@ -141,7 +173,8 @@ void FourStrokeCylinder::pre_step(Crankshaft& crank, float dt, float /*throttle*
const float previous_degrees = wrap_degrees((crank.previous_angle + phase) * 180.0f / pi);
const float current_degrees = wrap_degrees(theta * 180.0f / pi);
if (crossed_window_close(previous_degrees, current_degrees, intake_open, intake_close)) {
const bool intake_closed_this_step=intake_cam_ && sample_cam(*intake_cam_,previous_degrees).lift_metres>0 && sample_cam(*intake_cam_,current_degrees).lift_metres<=0;
if (intake_closed_this_step) {
if (!transported_fuel)
fuel_mass = ecu ? ecu->fuel_for_air(air_mass) : air_mass / std::max(afr, 1e-6f);
const float charge_afr = air_mass / std::max(fuel_mass, 1e-12f);
@@ -250,10 +283,6 @@ float EngineCylinder::wrap_degrees(float value, float cycle_degrees) {
return value < 0.0f ? value + cycle_degrees : value;
}
bool EngineCylinder::crossed_window_close(float previous, float current, float open, float close) {
return previous >= open && previous < close && current >= close;
}
void EngineCylinder::update_rev_limiter(float rpm, float dt) {
if (!rev_limit_enabled) {
rev_limit_active = false;

View File

@@ -3,6 +3,8 @@
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <limits>
#include <set>
#include <sstream>
@@ -22,6 +24,7 @@ class Reader {
std::string path_;
public:
Reader(const toml::table& table,std::string path):table_(table),path_(std::move(path)) {}
const std::string& context() const {return path_;}
[[noreturn]] void fail(const std::string& message) const { throw std::runtime_error(path_+": "+message); }
void required(std::initializer_list<const char*> keys) const {
for(const auto* key:keys)if(!table_.contains(key))fail(std::string("missing required field '")+key+"'");
@@ -67,10 +70,10 @@ public:
if(n && n->as_array()->size()>4096)fail(std::string(key)+" has too many entries");
return n?n->as_array():nullptr;
}
float number(const toml::node& n) const {
float number(const toml::node& n,double scale=1) const {
auto value=n.value<double>();
if(!value || !std::isfinite(*value) || std::abs(*value)>std::numeric_limits<float>::max())fail("array entries must be finite numbers");
return static_cast<float>(*value);
if(!value || !std::isfinite(*value) || std::abs(*value*scale)>std::numeric_limits<float>::max())fail("array entries must be finite numbers");
return static_cast<float>(*value*scale);
}
void field(const char* key,std::vector<float>& values) {
if(const auto* a=array(key)) {values.clear();for(const auto& n:*a)values.push_back(number(n));}
@@ -84,6 +87,51 @@ template<class T> void require(bool condition,const T& where,const char* message
if(!condition)throw std::runtime_error(std::string(where)+": "+message);
}
Vec3 read_xyz(Reader& r,const toml::node& node) {
const auto* a=node.as_array();if(!a||a->size()!=3)r.fail("coordinates require three numbers in mm");
return {r.number(*a->get(0),.001),r.number(*a->get(1),.001),r.number(*a->get(2),.001)};
}
void read_geometry(Reader r,PhysicalGeometry& g) {
if(const auto* pipes=r.array("pipes"))for(const auto& value:*pipes) {
if(!value.is_table())r.fail("geometry pipes must be tables");
Reader p(*value.as_table(),r.context()+".pipes");int index=-1;p.field("index",index);
if(index!=int(g.pipes.size()))p.fail("pipe indices must be consecutive, starting at zero");
const auto* points=p.array("points_mm");if(!points)p.fail("points_mm is required");
GeometryPath path;for(const auto& point:*points)path.points.push_back(read_xyz(p,point));
p.finish();g.pipes.push_back(std::move(path));
}
if(const auto* volumes=r.array("volumes"))for(const auto& value:*volumes) {
if(!value.is_table())r.fail("geometry volumes must be tables");
Reader v(*value.as_table(),r.context()+".volumes");GeometryVolume volume;v.field("id",volume.id);
const auto* size=v.node("size_mm");const auto* center=v.node("center_mm");const auto* corners=v.array("corners_mm");
if(corners) {
if(size||center||corners->size()!=2)v.fail("use two corners_mm or size_mm with center_mm");
auto a=read_xyz(v,*corners->get(0)),b=read_xyz(v,*corners->get(1));
volume.box.center={(a.x+b.x)*.5f,(a.y+b.y)*.5f,(a.z+b.z)*.5f};
volume.box.size={std::abs(a.x-b.x),std::abs(a.y-b.y),std::abs(a.z-b.z)};
} else {
if(!size)v.fail("volume requires size_mm or corners_mm");
volume.box.size=read_xyz(v,*size);if(center)volume.box.center=read_xyz(v,*center);
}
v.finish();g.volumes.push_back(std::move(volume));
}
if(const auto* cylinders=r.array("cylinders"))for(const auto& value:*cylinders) {
if(!value.is_table())r.fail("geometry cylinders must be tables");
Reader c(*value.as_table(),r.context()+".cylinders");CylinderPlacement placement;
const auto* origin=c.node("origin_mm");if(!origin)c.fail("origin_mm is required");
placement.origin=read_xyz(c,*origin);c.field("tilt_deg",placement.tilt_degrees);c.finish();g.cylinders.push_back(placement);
}
r.finish();
}
toml::table geometry_table(const PhysicalGeometry& geometry) {
const auto xyz=[](Vec3 p){const auto mm=[](float x){return std::round(double(x)*1e7)/1e4;};return toml::array{mm(p.x),mm(p.y),mm(p.z)};};
toml::array pipes,volumes,cylinders;
for(std::size_t i=0;i<geometry.pipes.size();++i){toml::array points;for(auto p:geometry.pipes[i].points)points.push_back(xyz(p));pipes.push_back(toml::table{{"index",int(i)},{"points_mm",std::move(points)}});}
for(const auto& v:geometry.volumes)volumes.push_back(toml::table{{"id",v.id},{"center_mm",xyz(v.box.center)},{"size_mm",xyz(v.box.size)}});
for(const auto& c:geometry.cylinders)cylinders.push_back(toml::table{{"origin_mm",xyz(c.origin)},{"tilt_deg",double(c.tilt_degrees)}});
return toml::table{{"pipes",std::move(pipes)},{"volumes",std::move(volumes)},{"cylinders",std::move(cylinders)}};
}
// Scalar readers below keep the file units explicit at the conversion boundary.
void read_cylinder(Reader r,CylinderConfig& c) {
r.required({"bore_mm","stroke_mm","rod_mm","compression"});
@@ -94,18 +142,12 @@ void read_cylinder(Reader r,CylinderConfig& c) {
r.field("burn_start_deg",c.burn_start);
r.field("compression",c.compression);
r.field("energy_variation",c.energy_variation);
r.field("exhaust_close_deg",c.exhaust_close);
r.field("exhaust_open_deg",c.exhaust_open);
r.field("exhaust_valve_count",c.exhaust_valve_count);
r.field("exhaust_valve_diameter_mm",c.exhaust_valve_diameter,0.001);
r.field("exhaust_valve_lift_mm",c.exhaust_valve_lift,0.001);
r.field("fuel_lhv_j_per_kg",c.fuel_lhv);
r.field("heat_transfer",c.heat_transfer);
r.field("intake_close_deg",c.intake_close);
r.field("intake_open_deg",c.intake_open);
r.field("intake_valve_count",c.intake_valve_count);
r.field("intake_valve_diameter_mm",c.intake_valve_diameter,0.001);
r.field("intake_valve_lift_mm",c.intake_valve_lift,0.001);
r.field("misfire_probability",c.misfire_probability);
r.field("rod_mm",c.rod,0.001);
r.field("stroke_mm",c.stroke,0.001);
@@ -114,6 +156,21 @@ void read_cylinder(Reader r,CylinderConfig& c) {
r.field("wiebe_m",c.wiebe_m);
r.finish();
}
void read_cam(Reader r,FourStrokeCylinder::CamProfile& cam) {
const auto* curve=r.array("points");
if(curve) for(const auto& node:*curve) {
const auto* point=node.as_array();
if(!point || point->size()!=2)r.fail("cam points must be [angle_deg, lift_mm]");
cam.points.push_back({r.number(*point->get(0)),r.number(*point->get(1),0.001)});
}
r.finish();
}
void read_valve(Reader r,FourStrokeCylinder::ValveDynamics& v) {
r.field("mass_g",v.mass_kg,0.001);r.field("spring_rate_n_per_mm",v.spring_rate_n_per_m,1000);
r.field("damping_n_s_per_m",v.damping_n_s_per_m);r.field("seat_force_n",v.seat_force_n);
r.field("maximum_lift_mm",v.maximum_lift_m,0.001);r.field("seat_restitution",v.seat_restitution);
r.field("cam_restitution",v.cam_restitution);r.field("stop_restitution",v.stop_restitution);r.finish();
}
void read_crankshaft(Reader r,CrankshaftConfig& c) {
r.field("friction_constant_nm",c.friction_constant);
r.field("friction_quadratic_nm_s2",c.friction_quadratic);
@@ -183,6 +240,8 @@ void read_silencer(Reader r,SilencerConfig& c) {
r.field("passage_diameter_mm",c.passage_diameter,0.001);
r.field("resistance",c.resistance);
r.field("volume_litres",c.volume,0.001);
float diameter=0;r.field("diameter_mm",diameter,.001);
if(diameter>0)c.volume=pi*.25f*diameter*diameter*c.length;
const auto type=r.text("type","straight_through");
if(type=="straight_through")c.type=SilencerType::StraightThrough;
else if(type=="chambered")c.type=SilencerType::Chambered;
@@ -210,6 +269,8 @@ void read_two_stroke(Reader r,TwoStrokeConfig& c) {
r.field("bore_mm",c.bore,0.001);
r.field("carburetor_diameter_mm",c.carburetor_diameter,0.001);
r.field("crankcase_clearance_cc",c.crankcase_clearance,1e-06);
r.field("reed_pipe_diameter_mm",c.reed_pipe_diameter,.001);
r.field("transfer_pipe_diameter_mm",c.transfer_pipe_diameter,.001);
r.field("exhaust_cell_length_mm",c.exhaust_cell_length,0.001);
r.field("exhaust_duration_deg",c.exhaust_duration);
r.field("exhaust_width_mm",c.exhaust_width,0.001);
@@ -335,7 +396,7 @@ void validate_two(const TwoStrokeConfig& c,const std::string& path) {
require(c.idle_slide_opening>=0 && c.idle_slide_opening<=1 && c.initial_rpm>0 && c.initial_rpm<=30000 &&
c.crankshaft_inertia>0 && c.starter_torque>=0 && c.maximum_load_torque>=0 && c.idle_spark_gain>=0,
path,"two_stroke: invalid idle or crankshaft calibration");
require(c.carburetor_diameter>0 && c.minimum_burn_seconds>0 &&
require(c.carburetor_diameter>0 && c.reed_pipe_diameter>0&&c.reed_pipe_diameter<=2&&c.transfer_pipe_diameter>0&&c.transfer_pipe_diameter<=2&&c.minimum_burn_seconds>0 &&
c.friction_bar>=0 && c.friction_bar_per_mps>=0 && c.friction_bar_per_mps2>=0,
path,"two_stroke: invalid carburetor, burn time or friction");
for(float value:{c.jet_scale,c.pilot_jet_scale,c.main_jet_scale,c.needle_jet_scale,
@@ -354,6 +415,7 @@ void validate_two(const TwoStrokeConfig& c,const std::string& path) {
} // namespace
void validate_engine_config(const EngineConfig& c,const std::string& path) {
require(std::isfinite(c.cells_per_meter) && (c.cells_per_meter==0 || (c.cells_per_meter>=1 && c.cells_per_meter<=500)),path,"invalid cells_per_meter");
require(c.cylinders>=1 && c.cylinders<=12 && (c.layout==EngineLayout::Inline || c.cylinders%2==0),
path,"engine: expected 1..12 cylinders and even banked cylinder counts");
const auto& cylinder=c.cylinder;
@@ -362,11 +424,17 @@ void validate_engine_config(const EngineConfig& c,const std::string& path) {
path,"cylinder: invalid bore, stroke, rod or compression");
require(cylinder.intake_valve_count>=1 && cylinder.intake_valve_count<=8 &&
cylinder.exhaust_valve_count>=1 && cylinder.exhaust_valve_count<=8 &&
cylinder.intake_valve_diameter>0 && cylinder.exhaust_valve_diameter>0 &&
cylinder.intake_valve_lift>0 && cylinder.exhaust_valve_lift>0,path,"cylinder: invalid valve geometry");
for(float angle:{cylinder.intake_open,cylinder.intake_close,cylinder.exhaust_open,cylinder.exhaust_close})
require(angle>=0 && angle<720,path,"cylinder: valve events must be in [0,720) degrees");
require(cylinder.intake_open!=cylinder.intake_close && cylinder.exhaust_open!=cylinder.exhaust_close &&
cylinder.intake_valve_diameter>0 && cylinder.exhaust_valve_diameter>0,path,"cylinder: invalid valve geometry");
auto valid_cam=[&](const FourStrokeCylinder::CamProfile& cam,const FourStrokeCylinder::ValveDynamics& valve) {
require(cam.points.size()>=3 && cam.points.front().angle_degrees==0 && cam.points.back().angle_degrees==720,path,"cam: profile must cover 0..720 degrees");
float previous=-1;
for(const auto& point:cam.points) {require(point.angle_degrees>previous && point.angle_degrees<=720 && point.lift_metres>=0 && point.lift_metres<=valve.maximum_lift_m,path,"cam: points must increase and lifts fit valve travel");previous=point.angle_degrees;}
require(cam.points.front().lift_metres==0 && cam.points.back().lift_metres==0,path,"cam: both cycle endpoints must have zero lift");
require(valve.mass_kg>0 && valve.spring_rate_n_per_m>0 && valve.damping_n_s_per_m>=0 && valve.seat_force_n>0 && valve.maximum_lift_m>0 && valve.seat_restitution>=0 && valve.seat_restitution<=1 && valve.cam_restitution>=0 && valve.cam_restitution<=1 && valve.stop_restitution>=0 && valve.stop_restitution<=1,path,"valves: invalid mass, spring or restitution");
};
valid_cam(c.intake_cam,c.intake_valve);valid_cam(c.exhaust_cam,c.exhaust_valve);
require(c.valve_sound.mode_hz>0 && c.valve_sound.effective_mass_kg>0 && c.valve_sound.radiation_area>=0 && c.valve_sound.damping_ratio>=0,path,"valve_sound: invalid resonance calibration");
require(
cylinder.burn_duration>0 && cylinder.wiebe_a>0 && cylinder.wiebe_m>=0 &&
cylinder.energy_variation>=0 && cylinder.energy_variation<=1 &&
cylinder.misfire_probability>=0 && cylinder.misfire_probability<=1 &&
@@ -392,9 +460,9 @@ void validate_engine_config(const EngineConfig& c,const std::string& path) {
count==1+2*c.cylinders && p.count==1,path,"pipes: silenced profile must be the collector's final outlet");
++silenced;
} else {
require(p.diameter>0 && p.diameter<=2 && p.length>0 && p.length<=100 && p.cells>=1 && p.cells<=4096,
require(p.diameter>0 && p.diameter<=2 && p.length>0 && p.length<=100 && (c.cells_per_meter>0 || (p.cells>=1 && p.cells<=4096)),
path,"pipes: invalid diameter, length or cell count");
cells+=p.cells*p.count;
cells+=(c.cells_per_meter>0?mesh_cell_count(p.length,c.cells_per_meter):p.cells)*p.count;
}
count+=p.count;
}
@@ -419,22 +487,45 @@ ScenarioExample load_engine_file(const std::filesystem::path& path) {
catch(const toml::parse_error& error) {std::ostringstream message;message<<error;throw std::runtime_error(message.str());}
Reader root(table,path.string());
int version=0;root.field("schema_version",version);
if(version!=1)root.fail("schema_version must be 1");
if(version!=1&&version!=2)root.fail("schema_version must be 1 or 2");
if(version==2&&!table.contains("geometry"))root.fail("schema_version 2 requires geometry");
ScenarioExample e;
e.source_path=std::filesystem::absolute(path);
root.field("id",e.id);root.field("name",e.title);root.field("category",e.category);
root.field("description",e.description);root.field("order",e.order);
if(e.id.empty() || e.title.empty() || e.category.empty())root.fail("id, name and category are required");
if(e.id.find_first_not_of("abcdefghijklmnopqrstuvwxyz0123456789-_")!=std::string::npos)root.fail("id must use lowercase letters, digits, - or _");
auto simulation=root.section("simulation");
float density=0;
if(simulation.node("cells_per_meter")) {
simulation.field("cells_per_meter",density);
if(density<1 || density>500) simulation.fail("cells_per_meter must be in 1..500");
}
simulation.finish();
e.engine.cells_per_meter=e.two.cells_per_meter=density;
if(density>0) { e.two.exhaust_cell_length=1/density;e.exhaust.cell_length=1/density; }
const auto cycle=root.text("cycle");
if(cycle=="two_stroke") {
e.kind=ScenarioKind::TwoStroke;
read_two_stroke(root.section("two_stroke",true),e.two);
if(density>0) e.two.exhaust_cell_length=1/density;
read_ecu(root.section("ecu"),e.two.ecu);
read_vehicle(root.section("vehicle"),e.two.vehicle);
read_geometry(root.section("geometry"),e.two.geometry);
if(version==2&&e.two.geometry.empty())root.fail("geometry requires explicit pipe paths");
try {e.two=resolve_two_stroke_geometry(std::move(e.two));}catch(const std::exception& error){root.fail(error.what());}
validate_two(e.two,path.string());
} else if(cycle=="four_stroke") {
read_engine(root.section("engine",true),e.engine);
read_cylinder(root.section("cylinder",true),e.engine.cylinder);
auto cams=root.section("cam",true);
read_cam(cams.section("intake",true),e.engine.intake_cam);read_cam(cams.section("exhaust",true),e.engine.exhaust_cam);cams.finish();
auto valves=root.section("valves",true);
auto intake_valve=valves.section("intake",true),exhaust_valve=valves.section("exhaust",true);
read_valve(intake_valve,e.engine.intake_valve);read_valve(exhaust_valve,e.engine.exhaust_valve);valves.finish();
auto sound=root.section("valve_sound",true);
sound.field("mode_hz",e.engine.valve_sound.mode_hz);sound.field("effective_mass_g",e.engine.valve_sound.effective_mass_kg,0.001);
sound.field("radiation_area_cm2",e.engine.valve_sound.radiation_area,0.0001);sound.field("damping_ratio",e.engine.valve_sound.damping_ratio);sound.finish();
read_crankshaft(root.section("crankshaft",true),e.engine.crankshaft);
read_ecu(root.section("ecu",true),e.engine.ecu);
read_vehicle(root.section("vehicle",true),e.engine.vehicle);
@@ -442,6 +533,21 @@ ScenarioExample load_engine_file(const std::filesystem::path& path) {
read_silencers(root.section("silencers"),e.exhaust);
auto junctions=root.section("junctions");
junctions.field("h_pipe",e.junctions.h_pipe);junctions.field("junction_volume_cc",e.junctions.junction_volume,1e-6);
junctions.field("model",e.junctions.model);junctions.field("turning_loss",e.junctions.turning_loss);
if(e.junctions.model!="volume" && e.junctions.model!="characteristic" && e.junctions.model!="legacy") junctions.fail("model must be volume, characteristic, or legacy");
if(e.junctions.turning_loss<0 || e.junctions.turning_loss>10) junctions.fail("turning_loss outside 0..10");
if(const auto* branches=junctions.array("branches")) for(const auto& value:*branches) {
if(!value.is_table()) junctions.fail("branches entries must be tables");
Reader r(*value.as_table(),path.string()+".junctions.branches");
r.required({"pipe","left","direction"}); JunctionBranchConfig branch;
r.field("pipe",branch.pipe);r.field("left",branch.left);
std::vector<float> direction;r.field("direction",direction);
if(branch.pipe<0 || direction.size()!=3) r.fail("branch requires nonnegative pipe and three direction coordinates");
double norm=0;for(int i=0;i<3;++i) { branch.direction[i]=direction[i];norm+=double(direction[i])*direction[i]; }
if(!(norm>0)) r.fail("branch direction must be nonzero");
for(const auto& previous:e.junctions.branches) if(previous.pipe==branch.pipe && previous.left==branch.left) r.fail("duplicate branch endpoint");
r.finish();e.junctions.branches.push_back(branch);
}
junctions.finish();
if(const auto* pipes=root.array("pipes"))for(const auto& value:*pipes) {
if(!value.is_table())root.fail("pipes entries must be tables");
@@ -453,6 +559,11 @@ ScenarioExample load_engine_file(const std::filesystem::path& path) {
r.field("diameter_mm",p.diameter,.001);r.field("length_mm",p.length,.001);r.field("cells",p.cells);
r.finish();e.engine.pipes.push_back(std::move(p));
}
read_geometry(root.section("geometry"),e.engine.geometry);
if(version==2&&e.engine.geometry.empty())root.fail("geometry requires explicit pipe paths");
try {e.engine=resolve_engine_geometry(std::move(e.engine),e.exhaust,e.junctions);}catch(const std::exception& error){root.fail(error.what());}
if(density>0)for(auto& p:e.engine.pipes)if(!p.silenced)p.cells=mesh_cell_count(p.length,density);
if(density>0) e.exhaust.cell_length=1/density;
validate_engine_config(e.engine,path.string());
require(e.junctions.junction_volume>0 && e.junctions.junction_volume<=1,path.string(),"junction_volume_cc outside (0,1000000]");
if(e.engine.has_silenced_exhaust()) {
@@ -476,6 +587,127 @@ ScenarioExample load_engine_file(const std::filesystem::path& path) {
root.finish();return e;
}
void migrate_engine_geometry(const std::filesystem::path& path,bool relayout) {
const auto definition=load_engine_file(path);
auto geometry=definition.kind==ScenarioKind::TwoStroke?definition.two.geometry:definition.engine.geometry;
if(relayout)geometry=definition.kind==ScenarioKind::TwoStroke?default_geometry(definition.two):default_geometry(definition.engine,definition.exhaust,definition.junctions);
auto table=toml::parse_file(path.string());
const bool already_geometry=table["schema_version"].value_or<int>(1)==2;
if(already_geometry&&!relayout)return;
auto xyz=[](Vec3 p){const auto mm=[](float x){return std::round(double(x)*1e7)/1e4;};return toml::array{mm(p.x),mm(p.y),mm(p.z)};};
toml::array pipes,volumes,cylinders;
for(std::size_t i=0;i<geometry.pipes.size();++i) {
toml::array points;for(auto p:geometry.pipes[i].points)points.push_back(xyz(p));
pipes.push_back(toml::table{{"index",int(i)},{"points_mm",std::move(points)}});
}
for(const auto& v:geometry.volumes)volumes.push_back(toml::table{{"id",v.id},{"center_mm",xyz(v.box.center)},{"size_mm",xyz(v.box.size)}});
for(const auto& c:geometry.cylinders)cylinders.push_back(toml::table{{"origin_mm",xyz(c.origin)},{"tilt_deg",double(c.tilt_degrees)}});
table.insert_or_assign("geometry",toml::table{{"pipes",std::move(pipes)},{"volumes",std::move(volumes)},{"cylinders",std::move(cylinders)}});
table.insert_or_assign("schema_version",2);
if(definition.kind==ScenarioKind::TwoStroke) {
auto& two=*table["two_stroke"].as_table();two.erase("crankcase_clearance_cc");
two.insert_or_assign("reed_pipe_diameter_mm",double(definition.two.reed_pipe_diameter)*1000);
two.insert_or_assign("transfer_pipe_diameter_mm",double(definition.two.transfer_pipe_diameter)*1000);
} else {
auto& engine=*table["engine"].as_table();engine.erase("plenum_volume_litres");engine.erase("collector_volume_litres");
for(auto& pipe:*table["pipes"].as_array())pipe.as_table()->erase("length_mm");
if(auto* junction=table["junctions"].as_table())junction->erase("junction_volume_cc");
if(auto* silencers=table["silencers"].as_table())for(const auto* name:{"resonator","muffler"})if(auto* s=(*silencers)[name].as_table()) {
const auto& cfg=std::string(name)=="resonator"?definition.exhaust.resonator:definition.exhaust.muffler;
s->erase("volume_litres");s->insert_or_assign("diameter_mm",double(cfg.diameter())*1000);
}
}
auto temporary=path;temporary+=".geometry.tmp";
std::ifstream original(path);std::ostringstream preserved;std::string line,section;
const auto trim=[](std::string s){const auto a=s.find_first_not_of(" \t\r");return a==std::string::npos?std::string{}:s.substr(a,s.find_last_not_of(" \t\r")-a+1);};
while(std::getline(original,line)) {
const auto text=trim(line);const auto key=trim(text.substr(0,text.find('=')));
if(text.starts_with("[geometry")||text.starts_with("[[geometry"))break;
if(text.starts_with("["))section=text;
if(key=="schema_version"){preserved<<"schema_version = 2\n";continue;}
if(section=="[engine]"&&(key=="plenum_volume_litres"||key=="collector_volume_litres"))continue;
if(section=="[[pipes]]"&&key=="length_mm")continue;
if(section=="[junctions]"&&key=="junction_volume_cc")continue;
if(section=="[two_stroke]"&&key=="crankcase_clearance_cc")continue;
if((section=="[silencers.resonator]"||section=="[silencers.muffler]")&&key=="volume_litres") {
const auto& cfg=section=="[silencers.resonator]"?definition.exhaust.resonator:definition.exhaust.muffler;
preserved<<"diameter_mm = "<<std::setprecision(10)<<double(cfg.diameter())*1000<<'\n';continue;
}
preserved<<line<<'\n';
if(text=="[two_stroke]"&&!already_geometry)preserved<<"reed_pipe_diameter_mm = "<<definition.two.reed_pipe_diameter*1000<<"\ntransfer_pipe_diameter_mm = "<<definition.two.transfer_pipe_diameter*1000<<'\n';
}
{std::ofstream output(temporary);output.exceptions(std::ios::failbit|std::ios::badbit);output<<preserved.str()<<'\n'<<toml::table{{"geometry",*table["geometry"].as_table()}}<<'\n';}
load_engine_file(temporary);
std::filesystem::copy_file(temporary,path,std::filesystem::copy_options::overwrite_existing);
std::filesystem::remove(temporary);
}
std::filesystem::path save_tuned_engine(const ScenarioExample& source,const EngineConfig& requested,
const std::filesystem::path& directory) {
if(source.kind==ScenarioKind::TwoStroke)throw std::invalid_argument("Live tuning export requires a four-stroke engine");
const auto tuning=resolve_engine_geometry(requested,source.exhaust,source.junctions);
validate_engine_config(tuning,"export");
auto table=toml::parse_file((source.source_path.empty()?engine_directory()/(source.id+".toml"):source.source_path).string());
auto destination=directory/(source.id+"-tuned.toml");
for(int suffix=2;std::filesystem::exists(destination);++suffix)
destination=directory/(source.id+"-tuned-"+std::to_string(suffix)+".toml");
table.insert_or_assign("id",destination.stem().string());
table.insert_or_assign("name",source.title+" / tuned");
table.insert_or_assign("geometry",geometry_table(tuning.geometry));table.insert_or_assign("schema_version",2);
if(tuning.cells_per_meter>0) table.insert_or_assign("simulation",toml::table{{"cells_per_meter",double(tuning.cells_per_meter)}});
auto write_cam=[&](const char* name,const FourStrokeCylinder::CamProfile& cam) {
toml::array points;
for(const auto& p:cam.points)points.push_back(toml::array{double(p.angle_degrees),double(p.lift_metres)*1000});
table["cam"][name].as_table()->insert_or_assign("points",std::move(points));
};
write_cam("intake",tuning.intake_cam);write_cam("exhaust",tuning.exhaust_cam);
auto write_valve=[&](const char* name,const FourStrokeCylinder::ValveDynamics& v) {
auto& t=*table["valves"][name].as_table();
t.insert_or_assign("mass_g",double(v.mass_kg)*1000);
t.insert_or_assign("spring_rate_n_per_mm",double(v.spring_rate_n_per_m)/1000);
t.insert_or_assign("damping_n_s_per_m",double(v.damping_n_s_per_m));
t.insert_or_assign("seat_force_n",double(v.seat_force_n));
t.insert_or_assign("maximum_lift_mm",double(v.maximum_lift_m)*1000);
t.insert_or_assign("seat_restitution",double(v.seat_restitution));
t.insert_or_assign("cam_restitution",double(v.cam_restitution));
t.insert_or_assign("stop_restitution",double(v.stop_restitution));
};
write_valve("intake",tuning.intake_valve);write_valve("exhaust",tuning.exhaust_valve);
auto& sound=*table["valve_sound"].as_table();
sound.insert_or_assign("mode_hz",double(tuning.valve_sound.mode_hz));
sound.insert_or_assign("effective_mass_g",double(tuning.valve_sound.effective_mass_kg)*1000);
sound.insert_or_assign("radiation_area_cm2",double(tuning.valve_sound.radiation_area)*10000);
sound.insert_or_assign("damping_ratio",double(tuning.valve_sound.damping_ratio));
auto& ecu=*table["ecu"].as_table();toml::array ignition;
for(const auto& p:tuning.ecu.ignition_map)ignition.push_back(toml::array{double(p.rpm),double(p.degrees_btdc)});
ecu.insert_or_assign("ignition_map",std::move(ignition));
ecu.insert_or_assign("idle_base_opening",double(tuning.ecu.idle_base_opening));
ecu.insert_or_assign("idle_max_opening",double(tuning.ecu.idle_max_opening));
ecu.insert_or_assign("stoichiometric_afr",double(tuning.ecu.stoichiometric_afr));
ecu.insert_or_assign("power_afr",double(tuning.ecu.power_afr));
if(tuning.carbureted) {
auto& fuel=*table["fuel"].as_table();
fuel.insert_or_assign("pilot_jet_diameter_mm",double(tuning.fuel.pilot_jet_diameter)*1000);
fuel.insert_or_assign("main_jet_diameter_mm",double(tuning.fuel.main_jet_diameter)*1000);
fuel.insert_or_assign("needle_jet_diameter_mm",double(tuning.fuel.needle_jet_diameter)*1000);
}
auto& pipes=*table["pipes"].as_array();
if(pipes.size()!=tuning.pipes.size())throw std::invalid_argument("Pipe groups changed since the engine was opened");
for(std::size_t i=0;i<tuning.pipes.size();++i)if(!tuning.pipes[i].silenced) {
auto& pipe=*pipes[i].as_table();const auto& p=tuning.pipes[i];
pipe.insert_or_assign("diameter_mm",double(p.diameter)*1000);
if(tuning.geometry.empty())pipe.insert_or_assign("length_mm",double(p.length)*1000);else pipe.erase("length_mm");
pipe.insert_or_assign("cells",p.cells);
}
auto temporary=destination;temporary+=".tmp";
try {
{std::ofstream output(temporary);output.exceptions(std::ios::failbit|std::ios::badbit);output<<table<<'\n';}
load_engine_file(temporary); // Use the exact same parser and validation as startup.
std::filesystem::rename(temporary,destination);
} catch(...) {std::error_code ignored;std::filesystem::remove(temporary,ignored);throw;}
return destination;
}
std::vector<ScenarioExample> load_engine_directory(const std::filesystem::path& directory) {
require(std::filesystem::is_directory(directory),directory.string(),"engine directory does not exist");
std::vector<std::filesystem::path> files;

264
src/engine_editor.cpp Normal file
View File

@@ -0,0 +1,264 @@
#include "FluidSim/Desktop/EngineEditor.hpp"
#include "FluidSim/Desktop/DesktopUi.hpp"
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <sstream>
namespace fluidsim::port::desktop {
namespace {
const sf::Color ink{232,237,242},muted{152,164,176},blue{91,184,255},orange{255,184,104};
void text(sf::RenderWindow& w,const sf::Font* font,const std::string& value,float x,float y,unsigned size=12,sf::Color color=ink) {
if(!font)return;
sf::Text t(*font,value,size);t.setPosition({x,y});t.setFillColor(color);w.draw(t);
}
void rect(sf::RenderWindow& w,sf::FloatRect r,sf::Color color,sf::Color outline={55,66,78}) {
sf::RectangleShape shape(r.size);shape.setPosition(r.position);shape.setFillColor(color);
shape.setOutlineColor(outline);shape.setOutlineThickness(1);w.draw(shape);
}
void line(sf::RenderWindow& w,sf::Vector2f a,sf::Vector2f b,sf::Color color) {
const sf::Vertex vertices[]{{a,color},{b,color}};w.draw(vertices,2,sf::PrimitiveType::Lines);
}
std::string number(float v,int precision=1) {std::ostringstream s;s<<std::fixed<<std::setprecision(precision)<<v;return s.str();}
sf::FloatRect button(sf::FloatRect panel,float x,float y,float width) {return {{panel.position.x+x,panel.position.y+y},{width,26}};}
void draw_button(sf::RenderWindow& w,const sf::Font* font,sf::FloatRect r,const std::string& title,bool selected=false,bool enabled=true) {
rect(w,r,selected?sf::Color{35,68,91}:sf::Color{29,36,44},selected?blue:sf::Color{55,66,78});
text(w,font,title,r.position.x+9,r.position.y+5,11,enabled?(selected?blue:ink):muted);
}
}
sf::FloatRect EngineEditor::bounds(sf::Vector2u size) {
const auto viewport=WorkspaceLayout::calculate(size).viewport;
const float width=std::min(660.f,viewport.size.x);
return {{viewport.position.x+viewport.size.x-width,80},{width,float(size.y)-128}};
}
sf::FloatRect EngineEditor::toggle_bounds(sf::Vector2u size) {return {{float(size.x)-200,47},{180,24}};}
void EngineEditor::configure(const ScenarioExample& e) {
supported_=e.kind!=ScenarioKind::TwoStroke;visible_=supported_;
original_=draft_=e.engine;applied_pipes_=e.engine.pipes;
undo_.clear();redo_.clear();pipe_=0;live_changed_=false;
busy_=rebuild_requested_=save_requested_=false;dragging_field_=dragging_point_=-1;tab_=Tab::Valves;
}
bool EngineEditor::close() {if(!visible_)return false;visible_=false;dragging_field_=dragging_point_=-1;return true;}
std::vector<EngineEditor::Field> EngineEditor::fields() const {
if(!supported_)return {};
const auto& v=exhaust_?draft_.exhaust_valve:draft_.intake_valve;
if(tab_==Tab::Valves)return {
{Key::Mass,"Valve mass","g",v.mass_kg*1000,10,100,1},
{Key::Spring,"Spring rate","N/mm",v.spring_rate_n_per_m/1000,10,90,1},
{Key::Damping,"Valve damping","N s/m",v.damping_n_s_per_m,0,70,1},
{Key::Preload,"Seat preload","N",v.seat_force_n,20,250,1},
{Key::SoundArea,"Contact sound area","cm2",draft_.valve_sound.radiation_area*10000,0,40,1},
{Key::SoundDamping,"Sound damping","ratio",draft_.valve_sound.damping_ratio,.3f,1.2f,2}};
if(tab_==Tab::Fuel) {
const float trim=draft_.ecu.ignition_map.empty()?0:draft_.ecu.ignition_map.front().degrees_btdc-original_.ecu.ignition_map.front().degrees_btdc;
std::vector<Field> result{{Key::Ignition,"Ignition map trim","deg",trim,-15,15,1},
{Key::Idle,"Idle throttle stop","%",draft_.ecu.idle_base_opening*100,0,draft_.ecu.idle_max_opening*100,2}};
if(draft_.carbureted) {
result.push_back({Key::Pilot,"Pilot jet diameter","mm",draft_.fuel.pilot_jet_diameter*1000,original_.fuel.pilot_jet_diameter*650,original_.fuel.pilot_jet_diameter*1500,3});
result.push_back({Key::Main,"Main jet diameter","mm",draft_.fuel.main_jet_diameter*1000,original_.fuel.main_jet_diameter*650,original_.fuel.main_jet_diameter*1500,3});
result.push_back({Key::Needle,"Needle jet diameter","mm",draft_.fuel.needle_jet_diameter*1000,original_.fuel.needle_jet_diameter*650,original_.fuel.needle_jet_diameter*1500,3});
} else {
result.push_back({Key::Stoich,"Cruise AFR target","AFR",draft_.ecu.stoichiometric_afr,10,18,2});
result.push_back({Key::Power,"Power AFR target","AFR",draft_.ecu.power_afr,10,16,2});
}
return result;
}
if(draft_.pipes.empty() || draft_.pipes[pipe_].silenced)return {};
const auto& p=draft_.pipes[pipe_];const auto& base=original_.pipes[pipe_];
return {{Key::Length,"Pipe group length","mm",p.length*1000,50,std::max(2000.f,base.length*1750),1},
{Key::Diameter,"Pipe group diameter","mm",p.diameter*1000,5,std::max(120.f,base.diameter*1750),1}};
}
void EngineEditor::begin_edit() {
if(undo_.size()>=32)undo_.erase(undo_.begin());
undo_.push_back(draft_);redo_.clear();status_="Changes apply to the running engine.";
}
void EngineEditor::undo() {if(undo_.empty() || busy_)return;redo_.push_back(draft_);draft_=std::move(undo_.back());undo_.pop_back();live_changed_=true;status_="Undid edit.";}
void EngineEditor::redo() {if(redo_.empty() || busy_)return;undo_.push_back(draft_);draft_=std::move(redo_.back());redo_.pop_back();live_changed_=true;status_="Redid edit.";}
void EngineEditor::reset() {if(busy_)return;begin_edit();draft_=original_;live_changed_=true;status_="Restored original tuning. Apply any pending pipe changes to rebuild.";}
void EngineEditor::set_field(const Field& f,float value) {
value=std::clamp(value,f.minimum,f.maximum);
const float steps=std::pow(10.f,float(f.precision));value=std::round(value*steps)/steps;
value=std::clamp(value,f.minimum,f.maximum);
auto& v=exhaust_?draft_.exhaust_valve:draft_.intake_valve;
switch(f.key) {
case Key::Mass:v.mass_kg=value*.001f;break;
case Key::Spring:v.spring_rate_n_per_m=value*1000;break;
case Key::Damping:v.damping_n_s_per_m=value;break;
case Key::Preload:v.seat_force_n=value;break;
case Key::SoundArea:draft_.valve_sound.radiation_area=value*.0001f;break;
case Key::SoundDamping:draft_.valve_sound.damping_ratio=value;break;
case Key::Ignition:
draft_.ecu.ignition_map=original_.ecu.ignition_map;
for(auto& p:draft_.ecu.ignition_map)p.degrees_btdc=std::clamp(p.degrees_btdc+value,-90.f,90.f);
break;
case Key::Idle:draft_.ecu.idle_base_opening=value*.01f;break;
case Key::Pilot:draft_.fuel.pilot_jet_diameter=value*.001f;break;
case Key::Main:draft_.fuel.main_jet_diameter=value*.001f;break;
case Key::Needle:draft_.fuel.needle_jet_diameter=value*.001f;break;
case Key::Stoich:draft_.ecu.stoichiometric_afr=value;break;
case Key::Power:draft_.ecu.power_afr=value;break;
case Key::Length: {
auto& p=draft_.pipes[pipe_];const auto& base=original_.pipes[pipe_];p.length=value*.001f;
p.cells=std::clamp(int(std::ceil(p.length/(base.length/base.cells))),1,4096);return;
}
case Key::Diameter:draft_.pipes[pipe_].diameter=value*.001f;return;
}
live_changed_=true;
}
sf::FloatRect EngineEditor::graph_bounds(sf::Vector2u size) const {
const auto p=bounds(size);return {{p.position.x+40,p.position.y+151},{p.size.x-64,std::min(180.f,p.size.y*.22f)}};
}
sf::FloatRect EngineEditor::field_bounds(sf::Vector2u size,std::size_t i) const {
const auto p=bounds(size);const auto g=graph_bounds(size);
const float first=tab_==Tab::Valves?g.position.y+g.size.y+64:tab_==Tab::Fuel?p.position.y+172:p.position.y+246;
return {{p.position.x+22,first+float(i)*38},{p.size.x-44,9}};
}
void EngineEditor::drag(sf::Vector2f pos,sf::Vector2u size) {
if(dragging_field_>=0) {
const auto list=fields();const auto& f=list.at(std::size_t(dragging_field_));const auto r=field_bounds(size,std::size_t(dragging_field_));
set_field(f,std::lerp(f.minimum,f.maximum,std::clamp((pos.x-r.position.x)/r.size.x,0.f,1.f)));
}
if(dragging_point_>=0) {
auto& points=(exhaust_?draft_.exhaust_cam:draft_.intake_cam).points;
const auto i=std::size_t(dragging_point_);const auto g=graph_bounds(size);
const float maximum=(exhaust_?draft_.exhaust_valve:draft_.intake_valve).maximum_lift_m;
auto& point=points.at(i);
point.angle_degrees=std::clamp((pos.x-g.position.x)/g.size.x*720,points[i-1].angle_degrees+.1f,points[i+1].angle_degrees-.1f);
point.lift_metres=std::clamp((1-(pos.y-g.position.y)/g.size.y)*maximum,0.f,maximum);
live_changed_=true;
}
}
bool EngineEditor::handle_event(const sf::Event& event,sf::Vector2u size) {
if(const auto* key=event.getIf<sf::Event::KeyPressed>()) {
if(key->code==sf::Keyboard::Key::F4) {visible_=!visible_;dragging_field_=dragging_point_=-1;return true;}
if(visible_ && supported_ && !busy_ && key->control) {
if(key->code==sf::Keyboard::Key::Z){undo();return true;}
if(key->code==sf::Keyboard::Key::Y){redo();return true;}
if(key->code==sf::Keyboard::Key::S){save_requested_=true;return true;}
}
}
if(event.is<sf::Event::FocusLost>()){dragging_field_=dragging_point_=-1;return false;}
if(const auto* click=event.getIf<sf::Event::MouseButtonPressed>()) {
const sf::Vector2f pos(click->position);
if(click->button==sf::Mouse::Button::Left && toggle_bounds(size).contains(pos)) {visible_=!visible_;return true;}
if(!visible_ || !bounds(size).contains(pos))return false;
const auto p=bounds(size);
if(button(p,p.size.x-42,12,26).contains(pos)) {close();return true;}
if(!supported_ || busy_ || click->button!=sf::Mouse::Button::Left)return true;
for(int i=0;i<3;++i)if(button(p,16+float(i)*(p.size.x-32)/3,65,(p.size.x-32)/3-4).contains(pos)) {tab_=static_cast<Tab>(i);return true;}
const float footer=p.size.y-72;
if(button(p,16,footer,70).contains(pos)){undo();return true;}
if(button(p,94,footer,70).contains(pos)){redo();return true;}
if(button(p,172,footer,76).contains(pos)){reset();return true;}
if(button(p,p.size.x-146,footer,130).contains(pos)){save_requested_=true;return true;}
if(tab_==Tab::Valves) {
if(button(p,20,108,90).contains(pos)){exhaust_=false;return true;}
if(button(p,120,108,90).contains(pos)){exhaust_=true;return true;}
const auto& points=(exhaust_?draft_.exhaust_cam:draft_.intake_cam).points;const auto g=graph_bounds(size);
const float maximum=(exhaust_?draft_.exhaust_valve:draft_.intake_valve).maximum_lift_m;
float distance=100;
for(std::size_t i=1;i+1<points.size();++i) {
const sf::Vector2f v{g.position.x+points[i].angle_degrees/720*g.size.x,g.position.y+(1-points[i].lift_metres/maximum)*g.size.y};
const auto d=pos-v;const float squared=d.x*d.x+d.y*d.y;
if(squared<distance) {distance=squared;dragging_point_=int(i);}
}
if(dragging_point_>=0){begin_edit();drag(pos,size);return true;}
}
if(tab_==Tab::Pipes) {
if(button(p,20,108,32).contains(pos)){if(pipe_>0)--pipe_;return true;}
if(button(p,p.size.x-52,108,32).contains(pos)){if(pipe_+1<draft_.pipes.size())++pipe_;return true;}
if(button(p,20,370,220).contains(pos) && draft_.pipes!=applied_pipes_){rebuild_requested_=true;return true;}
}
const auto list=fields();
for(std::size_t i=0;i<list.size();++i) {
auto r=field_bounds(size,i);r.position.y-=19;r.size.y=34;
if(r.contains(pos)) {begin_edit();dragging_field_=int(i);drag(pos,size);return true;}
}
return true;
}
if(event.is<sf::Event::MouseButtonReleased>()) {
const bool captured=dragging_field_>=0 || dragging_point_>=0;dragging_field_=dragging_point_=-1;
if(captured)return true;
}
if(const auto* move=event.getIf<sf::Event::MouseMoved>();move && (dragging_field_>=0 || dragging_point_>=0)) {drag(sf::Vector2f(move->position),size);return true;}
if(const auto* wheel=event.getIf<sf::Event::MouseWheelScrolled>();wheel && visible_ && bounds(size).contains(sf::Vector2f(wheel->position)))return true;
return false;
}
std::optional<EngineConfig> EngineEditor::take_live_update() {
if(!live_changed_ || busy_)return std::nullopt;
live_changed_=false;auto config=draft_;config.pipes=applied_pipes_;return config;
}
std::optional<EngineConfig> EngineEditor::take_rebuild_request() {
if(!rebuild_requested_ || busy_)return std::nullopt;
rebuild_requested_=false;busy_=true;rebuilding_=draft_;status_="Building the edited pipe mesh...";return rebuilding_;
}
bool EngineEditor::take_save_request() {const bool result=save_requested_;save_requested_=false;return result;}
void EngineEditor::finish_rebuild(bool success,const std::string& message) {
busy_=false;if(success)applied_pipes_=rebuilding_.pipes;status_=message;
}
void EngineEditor::draw(sf::RenderWindow& w,const sf::Font* font,const ScenarioSnapshot& s) const {
draw_button(w,font,toggle_bounds(w.getSize()),visible_?"F4 Close live editor":"F4 Live engine editor",visible_);
if(!visible_)return;
const auto p=bounds(w.getSize());const float x=p.position.x,y=p.position.y;
rect(w,p,{18,25,33},{70,93,114});
text(w,font,"LIVE ENGINE EDITOR",x+18,y+13,19);
draw_button(w,font,button(p,p.size.x-42,12,26),"X");
text(w,font,"Tune, hear, compare. Edits affect every cylinder.",x+18,y+42,11,muted);
if(!supported_) {text(w,font,"Choose a four-stroke engine to use this demo.",x+20,y+110,14);return;}
const char* tabs[]{"CAM + VALVES","FUEL + IGNITION","PIPE DIMENSIONS"};
for(int i=0;i<3;++i)draw_button(w,font,button(p,16+float(i)*(p.size.x-32)/3,65,(p.size.x-32)/3-4),tabs[i],int(tab_)==i);
if(tab_==Tab::Valves) {
draw_button(w,font,button(p,20,108,90),"Intake",!exhaust_);draw_button(w,font,button(p,120,108,90),"Exhaust",exhaust_);
text(w,font,"Drag curve points",x+232,y+115,11,muted);
const auto g=graph_bounds(w.getSize());rect(w,g,{10,16,23});
const float maximum=(exhaust_?draft_.exhaust_valve:draft_.intake_valve).maximum_lift_m;
const auto project=[&](float angle,float lift){return sf::Vector2f{g.position.x+angle/720*g.size.x,g.position.y+(1-std::clamp(lift/maximum,0.f,1.f))*g.size.y};};
for(int angle=0;angle<=720;angle+=180) {
const auto a=project(float(angle),0);line(w,a,{a.x,g.position.y},{38,49,62});text(w,font,std::to_string(angle),a.x-10,g.position.y+g.size.y+5,10,muted);
}
text(w,font,number(maximum*1000,0),g.position.x-31,g.position.y-5,10,muted);
text(w,font,"mm",g.position.x-30,g.position.y+15,10,muted);
const auto& actual=exhaust_?s.exhaust_lift_trace:s.intake_lift_trace;
for(std::size_t i=1;i<actual.size();++i)line(w,project(float(i-1)*4,actual[i-1]),project(float(i)*4,actual[i]),orange);
const auto& points=(exhaust_?draft_.exhaust_cam:draft_.intake_cam).points;
for(std::size_t i=1;i<points.size();++i)line(w,project(points[i-1].angle_degrees,points[i-1].lift_metres),project(points[i].angle_degrees,points[i].lift_metres),blue);
for(std::size_t i=1;i+1<points.size();++i) {sf::CircleShape dot(3);dot.setOrigin({3,3});dot.setPosition(project(points[i].angle_degrees,points[i].lift_metres));dot.setFillColor(blue);w.draw(dot);}
const float angle=std::fmod(s.cylinder_angles[0]*180/pi,720.f);const auto cursor=project(angle,0);
line(w,{cursor.x,g.position.y},cursor,{90,106,120});
text(w,font,"Cam demand",g.position.x,g.position.y+g.size.y+22,11,blue);
text(w,font,"Actual / cylinder 1",g.position.x+125,g.position.y+g.size.y+22,11,orange);
text(w,font,number((exhaust_?s.exhaust_lift:s.intake_lift)*1000,2)+" mm",g.position.x+320,g.position.y+g.size.y+22,11,orange);
text(w,font,s.valve_float?"FLOAT":"CONTACT",g.position.x+g.size.x-75,g.position.y+g.size.y+22,11,s.valve_float?orange:muted);
} else if(tab_==Tab::Fuel) {
text(w,font,draft_.carbureted?"CARBURETOR / LIVE METERING":"INJECTION / LIVE AFR TARGETS",x+22,y+112,13,blue);
text(w,font,"Ignition trim offsets the existing RPM map.",x+22,y+139,11,muted);
text(w,font,"Measured AFR "+number(s.target_afr,2)+" Throttle "+number(s.throttle*100,1)+"%",x+22,y+410,13);
text(w,font,"Changes preserve RPM, gas charge and valve motion.",x+22,y+446,11,muted);
} else {
draw_button(w,font,button(p,20,108,32),"<");draw_button(w,font,button(p,p.size.x-52,108,32),">");
const auto& pipe=draft_.pipes[pipe_];
text(w,font,pipe.id+" ("+std::to_string(pipe_+1)+" / "+std::to_string(draft_.pipes.size())+")",x+66,y+114,13,blue);
text(w,font,std::to_string(pipe.count)+" pipe(s) in group / "+std::to_string(pipe.cells)+" cells per pipe",x+22,y+157,12);
text(w,font,pipe.silenced?"Silencer geometry is defined by its chamber profile.":"Length changes preserve the original cell spacing.",x+22,y+185,11,muted);
text(w,font,"Pipe edits apply together and restart the engine.",x+22,y+321,11,muted);
draw_button(w,font,button(p,20,370,220),busy_?"Building...":"Apply pipes + restart",false,!busy_ && draft_.pipes!=applied_pipes_);
text(w,font,draft_.pipes!=applied_pipes_?"Unapplied pipe changes":"Pipe geometry is applied",x+22,y+414,12,draft_.pipes!=applied_pipes_?orange:muted);
}
const auto list=fields();
for(std::size_t i=0;i<list.size();++i) {
const auto& f=list[i];const auto r=field_bounds(w.getSize(),i);
text(w,font,f.label,r.position.x,r.position.y-19,11,muted);
text(w,font,number(f.value,f.precision)+" "+f.unit,r.position.x+r.size.x-132,r.position.y-19,11);
rect(w,r,{10,16,23});const float t=std::clamp((f.value-f.minimum)/(f.maximum-f.minimum),0.f,1.f);
rect(w,{r.position,{r.size.x*t,r.size.y}},{47,109,149},blue);
rect(w,{{r.position.x+r.size.x*t-3,r.position.y-3},{6,15}},blue,blue);
}
const float footer=p.size.y-72;
draw_button(w,font,button(p,16,footer,70),"Undo",false,!undo_.empty());
draw_button(w,font,button(p,94,footer,70),"Redo",false,!redo_.empty());
draw_button(w,font,button(p,172,footer,76),"Reset");draw_button(w,font,button(p,p.size.x-146,footer,130),"Save TOML copy");
const auto columns=std::size_t(std::max(20.f,(p.size.x-36)/6.1f));
text(w,font,status_.substr(0,columns),x+18,y+p.size.y-37,10,muted);
if(status_.size()>columns)text(w,font,status_.substr(columns,columns),x+18,y+p.size.y-24,10,muted);
}
}

View File

@@ -67,7 +67,7 @@ Vec3 cross(Vec3 a,Vec3 b) {return {a.y*b.z-a.z*b.y,a.z*b.x-a.x*b.z,a.x*b.y-a.y*b
float dot(Vec3 a,Vec3 b) {return a.x*b.x+a.y*b.y+a.z*b.z;}
Vec3 unit(Vec3 a) {float l=length(a);return l>1e-7f?Vec3{a.x/l,a.y/l,a.z/l}:Vec3{1,0,0};}
struct PipeVisual {
struct Ring {unsigned first;std::size_t a,b;float weight;};
struct Ring {unsigned first;std::size_t a,b;float weight;Vec3 center;float expansion{};};
Mesh mesh;
std::vector<Vec3> colors;
std::vector<Ring> rings;
@@ -96,7 +96,10 @@ struct PipeVisual {
for(float d:samples) {
const auto position=path.position(d),tangent=path.tangent(d);
if(rings.empty())normal=unit(cross(tangent,std::abs(tangent.y)<.9f?Vec3{0,1,0}:Vec3{1,0,0}));
else {float v=dot(normal,tangent);normal=unit({normal.x-v*tangent.x,normal.y-v*tangent.y,normal.z-v*tangent.z});}
else {
float v=dot(normal,tangent);const Vec3 projected{normal.x-v*tangent.x,normal.y-v*tangent.y,normal.z-v*tangent.z};
normal=length(projected)>1e-6f?unit(projected):unit(cross(tangent,std::abs(tangent.y)<.9f?Vec3{0,1,0}:Vec3{1,0,0}));
}
const auto binormal=unit(cross(tangent,normal));
std::size_t b=std::min<std::size_t>(std::upper_bound(centers.begin(),centers.end(),d)-centers.begin(),centers.size()-1);
std::size_t a=b?b-1:0;
@@ -107,20 +110,32 @@ struct PipeVisual {
unsigned prev=first-sides,k=(j+1)%sides;
mesh.indices.insert(mesh.indices.end(),{prev+j,first+j,prev+k,prev+k,first+j,first+k});
}
rings.push_back({first,a,b,blend});
rings.push_back({first,a,b,blend,position,0.f});
for(unsigned j=0;j<sides;++j) {
float angle=2*pi3*j/sides,c=std::cos(angle),sn=std::sin(angle);
Vec3 n{normal.x*c+binormal.x*sn,normal.y*c+binormal.y*sn,normal.z*c+binormal.z*sn};
mesh.vertices.push_back({position.x+radius*n.x,position.y+radius*n.y,position.z+radius*n.z,n.x,n.y,n.z});
const auto offset=path.miter_offset(d,n);
mesh.vertices.push_back({position.x+radius*offset.x,position.y+radius*offset.y,position.z+radius*offset.z,n.x,n.y,n.z});
}
}
colors.resize(mesh.vertices.size());valid=true;
}
void update(const std::vector<GasState>& states) {
void update(const std::vector<GasState>& states,bool exhaust) {
constexpr unsigned sides=20;
for(auto ring:rings) {
for(auto& ring:rings) {
const float p=states[ring.a].pressure+(states[ring.b].pressure-states[ring.a].pressure)*ring.weight;
std::fill_n(colors.begin()+ring.first,sides,pressure_tint(p));
const float rho=states[ring.a].rho+(states[ring.b].rho-states[ring.a].rho)*ring.weight;
const float temperature=p/std::max(rho*287.f,1e-6f);
std::fill_n(colors.begin()+ring.first,sides,pressure_tint(p,exhaust,temperature));
const float expansion=pressure_expansion(p,temperature);
const float ratio=(1.f+expansion)/(1.f+ring.expansion);
for(unsigned j=0;j<sides;++j) {
auto& vertex=mesh.vertices[ring.first+j];
vertex.x=ring.center.x+(vertex.x-ring.center.x)*ratio;
vertex.y=ring.center.y+(vertex.y-ring.center.y)*ratio;
vertex.z=ring.center.z+(vertex.z-ring.center.z)*ratio;
}
ring.expansion=expansion;
}
}
};
@@ -142,7 +157,7 @@ struct EngineRenderer3D::Impl {
if(s.pipe_states[index].empty())return;
auto& visual=pipes[index];
if(!visual.matches(s,index,r))visual.build(s,index,r);
visual.update(s.pipe_states[index]);
visual.update(s.pipe_states[index],scenario_pipe_is_exhaust(s,index));
visual.mesh.draw(&visual.colors);
}
void cylinder_part(Vec3 center,float fraction,float pressure,float angle,float tilt=0) const {
@@ -159,6 +174,55 @@ struct EngineRenderer3D::Impl {
}
void engine(const ScenarioSnapshot& s) {
const auto routes=scenario_pipe_routes(s);
if(s.geometry) {
for(const auto& volume:s.storage_volumes)box(volume.box.center,volume.box.size,pressure_color(volume.pressure));
for(int i=0;i<s.cylinder_count;++i) {
const auto& c=s.geometry->cylinders[i];const float radius=s.cylinder_bore*.5f;
const float crank=s.cylinder_stroke*.5f,clearance=s.cylinder_clearance/(pi3*radius*radius);
const float head=s.cylinder_rod+crank+clearance;
const float fraction=s.two_stroke?s.cylinder_piston_fraction1:s.piston_fractions[i];
const float piston=head-clearance-s.cylinder_stroke*std::clamp(fraction,0.f,1.f);
const float pressure=s.two_stroke?s.cylinder_pressure1:s.cylinder_pressures[i];
glPushMatrix();glTranslatef(c.origin.x,c.origin.y,c.origin.z);glRotatef(c.tilt_degrees,1,0,0);
glPushMatrix();glTranslatef(0,s.cylinder_rod-crank-.004f,0);glScalef(radius+.002f,s.cylinder_stroke+clearance+.004f,radius+.002f);
color({.4f,.4f,.4f});shell.draw();glPopMatrix();
rod({0,piston,0},{0,head,0},radius,pressure_color(pressure));
rod({0,piston-.006f,0},{0,piston,0},radius,{.8f,.8f,.8f});
const float angle=s.cylinder_angles[i];const Vec3 pin{0,crank*std::cos(angle),crank*std::sin(angle)};
rod(pin,{0,piston,0},std::min(.008f,radius*.13f),{.65f,.65f,.65f});
rod({},pin,std::min(.012f,radius*.18f),{.42f,.42f,.42f});
box({0,head+radius*.12f,0},{s.cylinder_bore+.008f,radius*.24f,s.cylinder_bore+.008f},{.48f,.48f,.48f});
if(!s.two_stroke)for(int valve=0;valve<2;++valve) {
const float x=(valve?1.f:-1.f)*radius*.4f;
const float lift=valve?s.exhaust_lifts[i]:s.intake_lifts[i];
const float diameter=valve?s.exhaust_valve_diameter:s.intake_valve_diameter;
rod({x,head-lift,0},{x,head+.025f-lift,0},.0025f,{.68f,.68f,.68f});
rod({x,head-.002f-lift,0},{x,head-lift,0},diameter*.5f,{.72f,.72f,.72f});
}
glPopMatrix();
}
if(!s.geometry->cylinders.empty()) {
float first=s.geometry->cylinders.front().origin.x,last=first;
for(const auto& c:s.geometry->cylinders){first=std::min(first,c.origin.x);last=std::max(last,c.origin.x);}
rod({first-s.cylinder_bore*.4f,0,0},{last+s.cylinder_bore*.4f,0,0},s.cylinder_stroke*.13f,{.5f,.5f,.5f});
}
for(std::size_t p=0;p<s.geometry->pipes.size();++p)if(!(p==25&&s.v8_headers&&!s.h_pipe))pipe(s,p,routes[p]);
for(const auto& housing:s.pipe_housings) {
const auto& path=s.geometry->pipes[housing.pipe];float distance=0;
for(std::size_t k=1;k<path.points.size();++k) {
const float next=distance+geometry_distance(path.points[k-1],path.points[k]);
const float from=std::max(distance,housing.start),to=std::min(next,housing.start+housing.length);
if(to>from) {
const auto a=path.position(from),b=path.position(to),d=sub(b,a);const float len=length(d);
glPushMatrix();glTranslatef(a.x,a.y,a.z);
if(std::abs(d.x)+std::abs(d.z)>1e-6f)glRotatef(std::acos(std::clamp(d.y/len,-1.f,1.f))*180/pi3,d.z,0,-d.x);else if(d.y<0)glRotatef(180,1,0,0);
glScalef(housing.diameter*.5f,len,housing.diameter*.5f);color({.45f,.45f,.45f});shell.draw();glPopMatrix();
}
distance=next;
}
}
return;
}
if(s.two_stroke) {
cylinder_part({0,0,0},s.cylinder_piston_fraction1,s.cylinder_pressure1,s.cylinder_angles[0]);
box({0,.07f,0},{.21f,.14f,.19f},pressure_color(s.crankcase_pressure));
@@ -236,16 +300,24 @@ void EngineRenderer3D::draw(sf::RenderWindow& window,const ScenarioSnapshot& sta
glEnable(GL_DEPTH_TEST);glDepthFunc(GL_LEQUAL);glDisable(GL_BLEND);glDisable(GL_TEXTURE_2D);glDisable(GL_CULL_FACE);
glEnable(GL_NORMALIZE);glEnable(GL_LIGHTING);glEnable(GL_LIGHT0);glEnable(GL_COLOR_MATERIAL);glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);
glMatrixMode(GL_PROJECTION);glLoadIdentity();const double nearClip=.03,halfHeight=nearClip*.41421356;
glFrustum(-halfHeight*w/h,halfHeight*w/h,-halfHeight,halfHeight,nearClip,50);
glFrustum(-halfHeight*w/h,halfHeight*w/h,-halfHeight,halfHeight,nearClip,std::max(50.f,camera.distance*4));
glMatrixMode(GL_MODELVIEW);glLoadIdentity();
const GLfloat light[]{-.4f,1,1,0},ambient[]{.35f,.35f,.35f,1},diffuse[]{1,1,1,1};
glLightfv(GL_LIGHT0,GL_POSITION,light);glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuse);glLightModelfv(GL_LIGHT_MODEL_AMBIENT,ambient);
glTranslatef(0,0,-camera.distance*(state.silencers?1.8f:state.cylinder_count==8?1.4f:1.f));glRotatef(camera.pitch,1,0,0);glRotatef(camera.yaw,0,1,0);glTranslatef(-camera.target.x-(state.silencers?1.1f:state.cylinder_count==8?.7f:0.f),-camera.target.y,-camera.target.z);
glTranslatef(0,0,-camera.distance);glRotatef(camera.pitch,1,0,0);glRotatef(camera.yaw,0,1,0);glTranslatef(-camera.target.x,-camera.target.y,-camera.target.z);
// Grid is indexed triangles too; no per-frame geometry allocation.
float ground=-.025f;
if(state.geometry) {
for(std::size_t p=0;p<state.geometry->pipes.size();++p) {
float radius=0;for(float area:state.pipe_areas[p])radius=std::max(radius,std::sqrt(area/pi3));
for(auto point:state.geometry->pipes[p].points)ground=std::min(ground,point.y-radius-.015f);
}
for(const auto& volume:state.storage_volumes)ground=std::min(ground,volume.box.minimum().y-.015f);
}
for(int i=-8;i<=8;++i) {
float p=i*.2f;
impl_->box({p,-.025f,0},{.0015f,.001f,3.2f},{.16f,.16f,.16f});
impl_->box({0,-.025f,p},{3.2f,.001f,.0015f},{.16f,.16f,.16f});
impl_->box({p,ground,0},{.0015f,.001f,3.2f},{.16f,.16f,.16f});
impl_->box({0,ground,p},{3.2f,.001f,.0015f},{.16f,.16f,.16f});
}
impl_->engine(state);
glDisable(GL_SCISSOR_TEST);window.popGLStates();

View File

@@ -1,12 +1,13 @@
#include "FluidSim/Desktop/EngineSchematic.hpp"
#include "FluidSim/Desktop/PressurePalette.hpp"
#include "FluidSim/ScenarioGeometry.hpp"
#include <cmath>
#include <algorithm>
#include <iomanip>
#include <sstream>
namespace fluidsim::port::desktop {
sf::Color pressure_color(float pressure) {
auto c=pressure_tint(pressure);
sf::Color pressure_color(float pressure,bool exhaust=false) {
auto c=pressure_tint(pressure,exhaust);
return {static_cast<std::uint8_t>(255*c.x),static_cast<std::uint8_t>(255*c.y),static_cast<std::uint8_t>(255*c.z)};
}
@@ -44,9 +45,12 @@ void draw_pipe(sf::RenderWindow& window, const ScenarioSnapshot& snapshot, std::
const float cell_width = total_length > 0 ? (x1 - x0) * lengths[i] / total_length
: (x1 - x0) / cells.size();
const float thickness = areas.empty() ? 8 : 600 * std::sqrt(4 * areas[i] / pi);
sf::RectangleShape segment({cell_width + 1.0f, thickness});
segment.setPosition({x, center_y - thickness * .5f});
segment.setFillColor(pressure_color(cell.pressure));
const float temperature=cell.pressure/std::max(cell.rho*287.f,1e-6f);
const float expanded_thickness=thickness*(1.f+pressure_expansion(cell.pressure,temperature));
sf::RectangleShape segment({cell_width + 1.0f, expanded_thickness});
segment.setPosition({x, center_y - expanded_thickness * .5f});
auto tint=pressure_tint(cell.pressure,scenario_pipe_is_exhaust(snapshot,pipe_index),temperature);
segment.setFillColor({static_cast<std::uint8_t>(255*tint.x),static_cast<std::uint8_t>(255*tint.y),static_cast<std::uint8_t>(255*tint.z)});
window.draw(segment);
x += cell_width;
}
@@ -195,6 +199,47 @@ void draw_b205_scene(sf::RenderWindow& window, const sf::Font* font, const Scena
}
void draw_schematic(sf::RenderWindow& window, const sf::Font* font, const ScenarioSnapshot& state) {
if(state.geometry) {
const auto project=[](Vec3 p){return sf::Vector2f{p.x+p.z*.35f,-p.y+p.z*.15f};};
sf::Vector2f lo{1e9f,1e9f},hi{-1e9f,-1e9f};
auto include=[&](Vec3 p){auto v=project(p);lo={std::min(lo.x,v.x),std::min(lo.y,v.y)};hi={std::max(hi.x,v.x),std::max(hi.y,v.y)};};
for(const auto& pipe:state.geometry->pipes)for(auto p:pipe.points)include(p);
for(const auto& volume:state.storage_volumes){include(volume.box.minimum());include(volume.box.maximum());}
for(const auto& c:state.geometry->cylinders){include(c.origin);include(cylinder_point(c,{0,state.cylinder_rod+state.cylinder_stroke,0}));}
const float scale=std::min(1150.f/std::max(.1f,hi.x-lo.x),480.f/std::max(.1f,hi.y-lo.y));
const auto point=[&](Vec3 p){return (project(p)-(lo+hi)*.5f)*scale+sf::Vector2f{640,400};};
for(const auto& volume:state.storage_volumes) {
const auto a=point(volume.box.minimum()),b=point(volume.box.maximum());
draw_volume(window,volume.pressure,{{std::min(a.x,b.x),std::min(a.y,b.y)},{std::abs(a.x-b.x),std::abs(a.y-b.y)}});
draw_label(window,font,volume.id,{std::min(a.x,b.x),std::min(a.y,b.y)-16},sf::Color::White,11);
}
for(std::size_t p=0;p<state.geometry->pipes.size();++p) {
if(p==25&&state.v8_headers&&!state.h_pipe)continue;
const bool exhaust=scenario_pipe_is_exhaust(state,p);
const auto& path=state.geometry->pipes[p];float distance=0;
std::vector<float> knots{0};for(std::size_t k=1;k<path.points.size();++k)knots.push_back(knots.back()+geometry_distance(path.points[k-1],path.points[k]));
for(std::size_t i=0;i<state.pipe_states[p].size();++i) {
float end=distance+state.pipe_lengths[p][i],start=distance;
const float diameter=std::sqrt(4*state.pipe_areas[p][i]/pi);
const auto& cell=state.pipe_states[p][i];
const float temperature=cell.pressure/std::max(cell.rho*287.f,1e-6f);
const auto tint=pressure_tint(cell.pressure,exhaust,temperature);
const sf::Color cell_color{static_cast<std::uint8_t>(255*tint.x),static_cast<std::uint8_t>(255*tint.y),static_cast<std::uint8_t>(255*tint.z)};
const float thickness=std::max(1.f,diameter*scale)*(1.f+pressure_expansion(cell.pressure,temperature));
for(float knot:knots)if(knot>start&&knot<end){draw_graph_line(window,point(path.position(start)),point(path.position(knot)),cell_color,thickness);start=knot;}
draw_graph_line(window,point(path.position(start)),point(path.position(end)),cell_color,thickness);distance=end;
}
}
for(int i=0;i<state.cylinder_count;++i) {
const auto& c=state.geometry->cylinders[i];const float head=state.cylinder_rod+state.cylinder_stroke*.5f+state.cylinder_clearance/(pi*state.cylinder_bore*state.cylinder_bore*.25f);
const float fraction=state.two_stroke?state.cylinder_piston_fraction1:state.piston_fractions[i];
const float clearance=state.cylinder_clearance/(pi*state.cylinder_bore*state.cylinder_bore*.25f);
const auto top=cylinder_point(c,{0,head,0}),bottom=cylinder_point(c,{0,head-clearance-state.cylinder_stroke,0});
draw_graph_line(window,point(bottom),point(top),sf::Color(80,80,80),state.cylinder_bore*scale);
draw_graph_line(window,point(cylinder_point(c,{0,head-clearance-state.cylinder_stroke*fraction,0})),point(top),pressure_color(state.two_stroke?state.cylinder_pressure1:state.cylinder_pressures[i]),state.cylinder_bore*scale);
}
return;
}
if(state.staged_headers) {
const int n=state.cylinder_count, groups=n==3?1:2, split=n==3?n:(n+1)/2;

View File

@@ -44,7 +44,7 @@ OutdoorAudio::OutdoorAudio(int rate, OutdoorAudioConfig config) : rate_(rate), c
!std::isfinite(config.pressure_gain) || config.pressure_gain < 0 ||
!std::isfinite(config.ear_spacing) || config.ear_spacing < 0 ||
!finite(config.listener) || !finite(config.intake_position) || !finite(config.exhaust_position) ||
!finite(config.second_exhaust_position))
!finite(config.second_exhaust_position) || !finite(config.valve_position))
throw std::invalid_argument("invalid outdoor audio configuration");
const float norm = length(config_.listener_right);
if (!std::isfinite(norm) || norm < 1e-6f)
@@ -54,6 +54,7 @@ OutdoorAudio::OutdoorAudio(int rate, OutdoorAudioConfig config) : rate_(rate), c
configure_emitter(emitters_[0], config_.intake_position);
configure_emitter(emitters_[1], config_.exhaust_position);
if (config_.dual_exhaust) configure_emitter(emitters_[2], config_.second_exhaust_position);
if(config_.valve_contact_enabled)configure_emitter(emitters_[3],config_.valve_position);
late_reverb_.dry_mix = 0;
late_reverb_.early_mix = 0;
late_reverb_.tail_mix = 1;
@@ -81,6 +82,7 @@ void OutdoorAudio::update_geometry() {
configure_emitter(emitters_[0],config_.intake_position);
configure_emitter(emitters_[1],config_.exhaust_position);
if(config_.dual_exhaust)configure_emitter(emitters_[2],config_.second_exhaust_position);
if(config_.valve_contact_enabled)configure_emitter(emitters_[3],config_.valve_position);
}
void OutdoorAudio::configure_emitter(Emitter &emitter, Vec3 source) {
const bool initial=emitter.filtered.empty();
@@ -206,8 +208,8 @@ AudioFrame OutdoorAudio::render(Emitter &e, float flow, AudioFrame &dry, AudioFr
e.position = (e.position + 1) & (e.filtered.size() - 1);
return result;
}
AudioFrame OutdoorAudio::process(float intake, float exhaust, float second_exhaust) {
if (!std::isfinite(intake) || !std::isfinite(exhaust) || !std::isfinite(second_exhaust))
AudioFrame OutdoorAudio::process(float intake, float exhaust, float second_exhaust,float valve_contact) {
if (!std::isfinite(intake) || !std::isfinite(exhaust) || !std::isfinite(second_exhaust) || !std::isfinite(valve_contact))
throw std::runtime_error("nonfinite acoustic source flow");
const OutdoorScene scene = requested_scene_.load(std::memory_order_relaxed);
if (scene != active_scene_) {
@@ -247,12 +249,20 @@ AudioFrame OutdoorAudio::process(float intake, float exhaust, float second_exhau
last_dry_ = {};
last_raw_ = {};
reverb_input_ = 0;
const auto a = render(emitters_[0], intake, last_dry_, last_raw_);
const auto b = render(emitters_[1], exhaust, last_dry_, last_raw_);
const auto c = config_.dual_exhaust
? render(emitters_[2], second_exhaust, last_dry_, last_raw_)
: AudioFrame{};
const auto tail = late_reverb_.process_stereo(reverb_input_);
return {a.left + b.left + c.left + tail.left * tail_mix_, a.right + b.right + c.right + tail.right * tail_mix_};
const std::array<float,4> flows{intake,exhaust,second_exhaust,valve_contact};
AudioFrame mix{};
for(std::size_t i=0;i<sources_.size();++i) {
auto& source=sources_[i]; source={};
const float previous_reverb=reverb_input_;
if((i!=2 || config_.dual_exhaust) && (i!=3 || config_.valve_contact_enabled))
source.rendered=render(emitters_[i],flows[i],source.dry,source.unfiltered);
if(!enabled_[i]) { reverb_input_=previous_reverb; continue; }
mix.left+=source.rendered.left; mix.right+=source.rendered.right;
last_dry_.left+=source.dry.left; last_dry_.right+=source.dry.right;
last_raw_.left+=source.unfiltered.left; last_raw_.right+=source.unfiltered.right;
}
const auto tail=late_reverb_.process_stereo(reverb_input_);
last_tail_={tail.left*tail_mix_,tail.right*tail_mix_};
return {mix.left+last_tail_.left,mix.right+last_tail_.right};
}
} // namespace fluidsim::port

244
src/physical_geometry.cpp Normal file
View File

@@ -0,0 +1,244 @@
#include "FluidSim/PhysicalGeometry.hpp"
#include "FluidSim/EngineConfig.hpp"
#include "FluidSim/TwoStrokeConfig.hpp"
#include "FluidSim/Silencer.hpp"
#include <algorithm>
#include <cmath>
#include <stdexcept>
#include <set>
namespace fluidsim::port {
namespace {
Vec3 add(Vec3 a,Vec3 b) {return {a.x+b.x,a.y+b.y,a.z+b.z};}
Vec3 sub(Vec3 a,Vec3 b) {return {a.x-b.x,a.y-b.y,a.z-b.z};}
Vec3 mul(Vec3 a,float b) {return {a.x*b,a.y*b,a.z*b};}
bool finite(Vec3 a) {return std::isfinite(a.x)&&std::isfinite(a.y)&&std::isfinite(a.z);}
GeometryBox cube(float volume,Vec3 center) {const float side=std::cbrt(volume);return {center,{side,side,side}};}
Vec3 port(const GeometryBox& box,Vec3 toward) {
auto d=sub(toward,box.center);
if(geometry_distance(toward,box.center)<1e-8f)d={1,0,0};
const float scale=.5f/std::max({std::abs(d.x)/box.size.x,std::abs(d.y)/box.size.y,std::abs(d.z)/box.size.z});
return add(box.center,mul(d,scale));
}
GeometryPath routed(Vec3 a,Vec3 b,float requested) {
const float direct=geometry_distance(a,b),length=std::max(requested,direct);
if(length<=direct+1e-7f)return {{a,b}};
auto d=sub(b,a);Vec3 normal=std::abs(d.z)<std::abs(d.x)+std::abs(d.y)?Vec3{-d.y,d.x,0}:Vec3{0,-d.z,d.y};
float n=geometry_distance(normal,{});if(n<1e-8f){normal={0,0,1};n=1;}
if(normal.y>0)normal=mul(normal,-1);
const auto sample=[&](float height) {
const auto control=add(mul(add(a,b),.5f),mul(normal,height/n));GeometryPath path;
for(int i=0;i<=16;++i){const float t=float(i)/16,u=1-t;path.points.push_back(add(add(mul(a,u*u),mul(control,2*u*t)),mul(b,t*t)));}
path.points.front()=a;path.points.back()=b;return path;
};
float low=0,high=length*2;
for(int i=0;i<32;++i){const float middle=(low+high)*.5f;if(sample(middle).length()<length)low=middle;else high=middle;}
return sample((low+high)*.5f);
}
Vec3 unit(Vec3 v) {const float n=geometry_distance(v,{});return n>1e-8f?mul(v,1/n):Vec3{1,0,0};}
Vec3 boundary_axis(const PhysicalGeometry& geometry,Vec3 point,Vec3 fallback,bool entering) {
for(const auto& volume:geometry.volumes) {
const auto lo=volume.box.minimum(),hi=volume.box.maximum();constexpr float tolerance=1e-6f;
if(point.x<lo.x-tolerance||point.x>hi.x+tolerance||point.y<lo.y-tolerance||point.y>hi.y+tolerance||point.z<lo.z-tolerance||point.z>hi.z+tolerance)continue;
const std::array<float,6> distance{std::abs(point.x-lo.x),std::abs(point.x-hi.x),std::abs(point.y-lo.y),std::abs(point.y-hi.y),std::abs(point.z-lo.z),std::abs(point.z-hi.z)};
const auto face=std::min_element(distance.begin(),distance.end())-distance.begin();
if(distance[face]>tolerance)continue;
const std::array<Vec3,6> normals{{{-1,0,0},{1,0,0},{0,-1,0},{0,1,0},{0,0,-1},{0,0,1}}};
return mul(normals[face],entering?-1.f:1.f);
}
return unit(fallback);
}
// Author smooth bends as measured polyline geometry. The renderer never fits a
// spline through these points; their sampled length is also the fluid length.
GeometryPath swept(Vec3 a,Vec3 b,Vec3 start_axis,Vec3 end_axis) {
const float distance=geometry_distance(a,b),handle=distance*.36f;
const Vec3 c=add(a,mul(unit(start_axis),handle)),d=sub(b,mul(unit(end_axis),handle));
GeometryPath path;constexpr int segments=16;
for(int i=0;i<=segments;++i) {
const float t=float(i)/segments,u=1-t;
path.points.push_back(add(add(mul(a,u*u*u),mul(c,3*u*u*t)),add(mul(d,3*u*t*t),mul(b,t*t*t))));
}
path.points.front()=a;path.points.back()=b;return path;
}
float exhaust_length(const ExhaustConfig& e) {
return e.inlet_length+e.outlet_length+(e.enabled?e.connecting_length+e.resonator.length+e.muffler.length:0);
}
}
float geometry_distance(Vec3 a,Vec3 b) {const auto d=sub(a,b);return std::sqrt(d.x*d.x+d.y*d.y+d.z*d.z);}
float GeometryPath::length() const {double sum=0;for(std::size_t i=1;i<points.size();++i)sum+=geometry_distance(points[i-1],points[i]);return float(sum);}
Vec3 GeometryPath::position(float distance) const {
if(points.empty())return {};
distance=std::max(0.f,distance);
for(std::size_t i=1;i<points.size();++i) {
const float span=geometry_distance(points[i-1],points[i]);
if(distance<=span)return add(points[i-1],mul(sub(points[i],points[i-1]),distance/span));
distance-=span;
}
return points.back();
}
const GeometryBox* PhysicalGeometry::volume(const std::string& id) const {for(const auto& v:volumes)if(v.id==id)return &v.box;return nullptr;}
GeometryBox* PhysicalGeometry::volume(const std::string& id) {for(auto& v:volumes)if(v.id==id)return &v.box;return nullptr;}
Vec3 cylinder_point(const CylinderPlacement& c,Vec3 local) {
const float a=c.tilt_degrees*pi/180;
return add(c.origin,{local.x,local.y*std::cos(a)-local.z*std::sin(a),local.y*std::sin(a)+local.z*std::cos(a)});
}
PhysicalGeometry default_geometry(const EngineConfig& e,const ExhaustConfig& exhaust,const JunctionConfig& junctions) {
PhysicalGeometry g;std::vector<float> lengths;
for(const auto& p:e.pipes)for(int i=0;i<p.count;++i)lengths.push_back(p.silenced?exhaust_length(exhaust):p.length);
g.pipes.resize(lengths.size());
const int n=e.cylinders,per_bank=e.layout==EngineLayout::Inline?n:n/2;
const float spacing=e.cylinder.bore+.018f,head=e.cylinder.rod+e.cylinder.stroke*.5f+e.cylinder.stroke/(e.cylinder.compression-1);
std::vector<Vec3> intake,primary;
float max_z=0,max_y=0;
for(int i=0;i<n;++i) {
const float tilt=e.layout==EngineLayout::Inline?0:(i<per_bank?-1.f:1.f)*(e.layout==EngineLayout::Boxer?90:e.bank_angle*.5f);
g.cylinders.push_back({{(i%per_bank-(per_bank-1)*.5f)*spacing,0,0},tilt});
const float side=tilt<0?-1.f:1.f;
intake.push_back(cylinder_point(g.cylinders.back(),{0,head-.008f,-side*e.cylinder.bore*.4f}));
primary.push_back(cylinder_point(g.cylinders.back(),{0,head-.008f,side*e.cylinder.bore*.5f}));
max_z=std::max(max_z,std::abs(intake.back().z));max_y=std::max(max_y,intake.back().y);
}
const float width=per_bank*spacing,depth=2*max_z+e.cylinder.bore;
const float plenum_height=e.plenum_volume/(width*depth);
float runner=lengths.at(1);for(int i=1;i<n;++i)runner=std::min(runner,lengths.at(1+i));
GeometryBox plenum{{0,max_y+runner+plenum_height*.5f,0},{width,plenum_height,depth}};
if(e.layout==EngineLayout::Inline)plenum.center={0,max_y,intake.front().z-runner-depth*.5f};
g.volumes.push_back({"intake_plenum",plenum});
const auto inlet=Vec3{plenum.minimum().x,plenum.center.y,plenum.center.z};
g.pipes[0]={{add(inlet,{-lengths[0],0,0}),inlet}};
for(int i=0;i<n;++i)g.pipes[1+i]=routed(e.layout==EngineLayout::Inline?Vec3{intake[i].x,intake[i].y,plenum.maximum().z}:Vec3{intake[i].x,plenum.minimum().y,intake[i].z},intake[i],lengths[1+i]);
int junction_index=0;
auto node=[&](Vec3 center,const std::vector<std::pair<int,bool>>& ends,bool collector=false) {
GeometryBox box=cube(collector?e.collector_volume:junctions.junction_volume,center);
const bool storage=junctions.model=="volume";
if(storage)g.volumes.push_back({collector?"exhaust_collector":"junction_"+std::to_string(junction_index),box});
++junction_index;
for(auto [p,left]:ends) {
if(g.pipes[p].points.empty())g.pipes[p].points.resize(2);
g.pipes[p].points[left?0:1]=center;
}
return [=](Vec3 toward){return storage?port(box,toward):center;};
};
if(e.american_headers()) {
const std::array<std::array<int,2>,4> pairs{{{{0,1}},{{2,3}},{{4,6}},{{5,7}}}};
std::array<Vec3,4> paired;
const float bank_z=max_z+e.cylinder.bore*.7f+.08f;
const float tail_z=(lengths[25]+std::cbrt(junctions.junction_volume))*.5f;
std::array<Vec3,2> bank{{{width*.5f+.10f,-.08f,-bank_z},{width*.5f+.10f,-.08f,bank_z}}};
std::array<Vec3,2> tail{{{bank[0].x+lengths[21],bank[0].y,-tail_z},{bank[1].x+lengths[22],bank[1].y,tail_z}}};
for(int k=0;k<4;++k) {
auto a=primary[pairs[k][0]],b=primary[pairs[k][1]];
paired[k]={(a.x+b.x)*.5f+.08f,-.035f,k<2?bank[0].z:bank[1].z};
auto endpoint=node(paired[k],{{9+pairs[k][0],false},{9+pairs[k][1],false},{17+k,true}});
for(int cylinder:pairs[k])g.pipes[9+cylinder].points={primary[cylinder],endpoint(primary[cylinder])};
g.pipes[17+k].points[0]=endpoint(bank[k/2]);
}
for(int k=0;k<2;++k) {
auto endpoint=node(bank[k],{{17+2*k,false},{18+2*k,false},{21+k,true}});
g.pipes[17+2*k].points[1]=endpoint(paired[2*k]);g.pipes[18+2*k].points[1]=endpoint(paired[2*k+1]);
g.pipes[21+k].points[0]=endpoint(tail[k]);
}
g.pipes[25].points.resize(2);
for(int k=0;k<2;++k) {
if(junctions.h_pipe) {
auto endpoint=node(tail[k],{{21+k,false},{23+k,true},{25,k==0}});
g.pipes[21+k].points[1]=endpoint(bank[k]);
const auto start=endpoint(add(tail[k],{1,0,0}));g.pipes[23+k].points={start,add(start,{lengths[23+k],0,0})};
g.pipes[25].points[k]=endpoint(tail[1-k]);
} else {
g.pipes[21+k].points[1]=tail[k];g.pipes[23+k].points={tail[k],add(tail[k],{lengths[23+k],0,0})};
g.pipes[25].points[k]=tail[k];
}
}
} else if(e.staged_headers()) {
const int exit=1+2*n,secondary=exit+(e.dual_outlets()?2:1),groups=e.header_groups();
std::vector<Vec3> heads(groups);Vec3 merge{.20f,-.07f,e.layout==EngineLayout::Inline?e.cylinder.bore*.75f+.105f:0.f};
for(int group=0;group<groups;++group) {
std::vector<int> cylinders;float x=0;
for(int i=0;i<n;++i)if(groups==1 || (i<(n+1)/2?0:1)==group){cylinders.push_back(i);x+=primary[i].x;}
const float z=e.layout==EngineLayout::Inline?e.cylinder.bore*.75f+.045f+group*.045f:(group==0?-1.f:1.f)*(max_z+e.cylinder.bore*.7f+.045f);
heads[group]={x/cylinders.size(),-.025f,z};
std::vector<std::pair<int,bool>> ends{{secondary+group,true}};for(int i:cylinders)ends.push_back({1+n+i,false});
auto endpoint=node(heads[group],ends);
for(int i:cylinders)g.pipes[1+n+i].points={primary[i],endpoint(primary[i])};
g.pipes[secondary+group].points[0]=endpoint(merge);
}
if(e.dual_outlets())for(int k=0;k<2;++k) {
Vec3 m=merge;m.z=heads[k].z;auto endpoint=node(m,{{secondary+k,false},{exit+k,true}});
g.pipes[secondary+k].points[1]=endpoint(heads[k]);const auto start=endpoint(add(m,{1,0,0}));g.pipes[exit+k].points={start,add(start,{lengths[exit+k],0,0})};
} else {
std::vector<std::pair<int,bool>> ends{{exit,true}};for(int k=0;k<groups;++k)ends.push_back({secondary+k,false});
auto endpoint=node(merge,ends);for(int k=0;k<groups;++k)g.pipes[secondary+k].points[1]=endpoint(heads[k]);
const auto start=endpoint(add(merge,{1,0,0}));g.pipes[exit].points={start,add(start,{lengths[exit],0,0})};
}
} else {
const int exit=1+2*n;
Vec3 center{.10f,-.055f,max_z+e.cylinder.bore*.65f+.035f};
std::vector<std::pair<int,bool>> ends{{exit,true}};for(int i=0;i<n;++i)ends.push_back({1+n+i,false});
auto endpoint=node(center,ends,true);
for(int i=0;i<n;++i)g.pipes[1+n+i].points={primary[i],endpoint(primary[i])};
const auto start=endpoint(add(center,{1,0,0}));g.pipes[exit].points={start,add(start,{lengths[exit],0,0})};
}
for(std::size_t p=1+n;p<g.pipes.size();++p) {
const auto a=g.pipes[p].points.front(),b=g.pipes[p].points.back(),chord=sub(b,a);
const bool primary_pipe=p<std::size_t(1+2*n);
const bool header=primary_pipe||(e.american_headers()&&p<=22)||(e.staged_headers()&&p>=std::size_t(1+2*n+(e.dual_outlets()?2:1)));
if(header) {
auto start=boundary_axis(g,a,chord,false);
if(primary_pipe) {
const auto& cylinder=g.cylinders[p-(1+n)];const float side=cylinder.tilt_degrees<0?-1.f:1.f;
start=sub(cylinder_point(cylinder,{0,-.15f,side}),cylinder.origin);
}
g.pipes[p]=swept(a,b,start,boundary_axis(g,b,chord,true));
} else g.pipes[p]=routed(a,b,lengths[p]);
}
return g;
}
PhysicalGeometry default_geometry(const TwoStrokeConfig& e) {
PhysicalGeometry g;g.cylinders.push_back({{},0});g.pipes.resize(4);
auto crank=cube(e.crankcase_clearance,{});
crank.center.y=e.rod-e.stroke*.5f-.040f-crank.size.y*.5f;
auto plenum=cube(.5e-3f,{});plenum.center={crank.minimum().x-.030f-plenum.size.x*.5f,crank.center.y,0};
g.volumes={{"intake_plenum",plenum},{"crankcase",crank}};
const Vec3 inlet{plenum.minimum().x,plenum.center.y,0};g.pipes[0]={{add(inlet,{-.1f,0,0}),inlet}};
g.pipes[1]={{Vec3{plenum.maximum().x,plenum.center.y,0},Vec3{crank.minimum().x,crank.center.y,0}}};
g.pipes[2]=routed({crank.maximum().x,crank.maximum().y,0},{e.bore*.5f,e.rod-e.stroke*.5f+.005f,0},.060f);
float length=0;for(auto s:e.racing_pipe)length+=s.length;
const Vec3 start{e.bore*.5f,e.rod+e.stroke*.5f-.025f,0};
if(e.exhaust==TwoStrokeExhaust::Straight)g.pipes[3]={{start,add(start,{length,0,0})}};
else {
g.pipes[3].points.push_back(start);const std::array<float,6> angles{-20,-65,-125,-180,-200,-180};
for(std::size_t i=0;i<angles.size();++i) {
const float angle=angles[i]*pi/180;
g.pipes[3].points.push_back(add(g.pipes[3].points.back(),{std::cos(angle)*e.racing_pipe[i].length,std::sin(angle)*e.racing_pipe[i].length,0}));
}
}
return g;
}
void validate_geometry(const PhysicalGeometry& g,std::size_t pipes,std::size_t cylinders) {
if(g.pipes.size()!=pipes||g.cylinders.size()!=cylinders)throw std::invalid_argument("geometry pipe/cylinder count does not match the fluid topology");
for(const auto& p:g.pipes) {
if(p.points.size()<2||p.points.size()>64||!(p.length()>1e-6f)||p.length()>100)throw std::invalid_argument("geometry pipe requires 2..64 points and length in (0,100] m");
for(std::size_t i=0;i<p.points.size();++i)if(!finite(p.points[i])||(i&&geometry_distance(p.points[i-1],p.points[i])<1e-7f))throw std::invalid_argument("geometry pipe has nonfinite or repeated points");
}
std::set<std::string> ids;
for(const auto& v:g.volumes)if(v.id.empty()||!ids.insert(v.id).second||!finite(v.box.center)||!finite(v.box.size)||v.box.size.x<=0||v.box.size.y<=0||v.box.size.z<=0||!(v.box.volume()>0)||v.box.volume()>1)throw std::invalid_argument("invalid or duplicate geometry volume");
for(const auto& c:g.cylinders)if(!finite(c.origin)||!std::isfinite(c.tilt_degrees))throw std::invalid_argument("invalid cylinder placement");
}
EngineConfig resolve_engine_geometry(EngineConfig e,const ExhaustConfig& exhaust,const JunctionConfig& junctions) {
if(e.geometry.empty()) {validate_engine_config(e);e.geometry=default_geometry(e,exhaust,junctions);}
std::size_t count=0;for(auto p:e.pipes)count+=p.count;validate_geometry(e.geometry,count,e.cylinders);
const auto* plenum=e.geometry.volume("intake_plenum");if(!plenum)throw std::invalid_argument("geometry requires intake_plenum volume");e.plenum_volume=plenum->volume();
if(const auto* collector=e.geometry.volume("exhaust_collector"))e.collector_volume=collector->volume();
else if(e.collector_volume<=0)e.collector_volume=1e-6f; // unused in zero-storage/staged topology
if(junctions.model=="volume"&&!e.staged_headers()&&!e.american_headers()&&!e.geometry.volume("exhaust_collector"))throw std::invalid_argument("geometry requires exhaust_collector volume");
std::size_t index=0;for(auto& p:e.pipes){if(!p.silenced)p.length=e.geometry.pipes[index].length();index+=p.count;}
return e;
}
TwoStrokeConfig resolve_two_stroke_geometry(TwoStrokeConfig e) {
if(e.geometry.empty())e.geometry=default_geometry(e);
validate_geometry(e.geometry,4,1);
const auto* crank=e.geometry.volume("crankcase");if(!crank||!e.geometry.volume("intake_plenum"))throw std::invalid_argument("two-stroke geometry requires crankcase and intake_plenum volumes");
e.crankcase_clearance=crank->volume();return e;
}
}

View File

@@ -5,6 +5,7 @@
#include <algorithm>
#include <cmath>
#include <stdexcept>
namespace fluidsim::port {
FourStrokeScenario::FourStrokeScenario(int sample_rate, FourStrokePreset preset)
@@ -14,17 +15,22 @@ FourStrokeScenario::FourStrokeScenario(int sample_rate,const ScenarioExample& de
: FourStrokeScenario(sample_rate,definition.engine,definition.exhaust,definition.junctions) {}
FourStrokeScenario::FourStrokeScenario(int sample_rate, EngineConfig configuration, ExhaustConfig exhaust, JunctionConfig fittings)
: exhaust_config(exhaust), junction_config(fittings), engine_config(std::move(configuration)),
: exhaust_config(exhaust), junction_config(fittings), engine_config(resolve_engine_geometry(std::move(configuration),exhaust,fittings)),
intake_plenum(engine_config.plenum_volume,101325,300),
exhaust_collector(engine_config.collector_volume,101325,700),
pipes(create_pipes(engine_config,exhaust)), dt_(1.f/std::max(sample_rate,1)),
boundaries_(pipes), solver_(pipes,boundaries_), audio_(sample_rate,[&] {
OutdoorAudioConfig config;
config.dual_exhaust=engine_config.dual_outlets();
config.valve_contact_enabled=true;
return config;
}()) {
const auto& engine=engine_config;
geometry_=std::make_shared<PhysicalGeometry>(engine.geometry);
active_tuning_=std::make_shared<EngineConfig>(engine_config);
const auto& junctions=junction_config;
if(junctions.model!="volume" && junctions.model!="characteristic" && junctions.model!="legacy")
throw std::invalid_argument("invalid junction model");
const bool v8=engine.american_headers(),staged=engine.staged_headers();
const int count=cylinder_count();
static_cast<CrankshaftConfig&>(crankshaft)=engine.crankshaft;
@@ -48,6 +54,7 @@ FourStrokeScenario::FourStrokeScenario(int sample_rate, EngineConfig configurati
for (int i=0;i<count;++i) {
auto& c=*engines[i];
static_cast<CylinderConfig&>(c)=engine.cylinder;
c.set_valve_profiles(active_tuning_->intake_cam,engine.intake_valve,active_tuning_->exhaust_cam,engine.exhaust_valve);
c.phase=engine.phase_degrees.at(i)*pi/180;
c.transported_fuel=engine.carbureted;
c.rev_limit_enabled = ecu.rev_limit_enabled;
@@ -75,7 +82,7 @@ FourStrokeScenario::FourStrokeScenario(int sample_rate, EngineConfig configurati
auto& throttle_port = intake_plenum.create_port();
throttle_port_=&throttle_port;
solver_.add_component(intake_plenum);
if(!v8 && !staged) solver_.add_component(exhaust_collector);
if(!v8 && !staged && junctions.model=="volume") solver_.add_component(exhaust_collector);
boundaries_.add_orifice(throttle_port, 0, false, throttle_area_, .7f);
for (int i = 0; i < count; ++i) {
auto& feed = intake_plenum.create_port();
@@ -85,24 +92,53 @@ FourStrokeScenario::FourStrokeScenario(int sample_rate, EngineConfig configurati
boundaries_.add_orifice(feed, 1+i, true, runner_area_);
boundaries_.add_orifice(engines[i]->intake, 1+i, false, intake_valve_areas_[i]);
boundaries_.add_orifice(engines[i]->exhaust, 1+count+i, true, exhaust_valve_areas_[i]);
if (!v8 && !staged) boundaries_.add_orifice(exhaust_collector.create_port(), 1+count+i, false, header_area_);
if (!v8 && !staged && junctions.model=="volume") boundaries_.add_orifice(exhaust_collector.create_port(), 1+count+i, false, header_area_);
}
if(v8) {
auto junction=[&](std::initializer_list<JunctionEnd> ends) {
auto fitting=std::make_unique<Junction>(junctions.junction_volume,101325,700);
fitting->connect(boundaries_,pipes,std::span(ends.begin(),ends.size()));
// Node axes follow the last/first route segment, unless explicitly supplied.
ScenarioSnapshot geometry;
geometry.geometry=geometry_;
geometry.cylinder_count=count;geometry.layout=engine.layout;geometry.bank_angle=engine.bank_angle;
geometry.v8_headers=v8;geometry.staged_headers=staged;geometry.dual_exhaust=engine.dual_outlets();
geometry.h_pipe=junctions.h_pipe;geometry.silencers=engine.has_silenced_exhaust() && exhaust.enabled;geometry.exhaust_config=exhaust;
const auto routes=scenario_pipe_routes(geometry);
std::vector<bool> branch_used(junctions.branches.size(),false);
int junction_index=0;
auto junction=[&](std::span<const JunctionEnd> ends) {
if(junctions.model=="volume") {
const auto* box=engine.geometry.volume("junction_"+std::to_string(junction_index));
if(!box)throw std::invalid_argument("geometry missing junction_"+std::to_string(junction_index));
auto fitting=std::make_unique<Junction>(box->volume(),101325,700);
fitting->connect(boundaries_,pipes,ends);
solver_.add_component(*fitting);exhaust_junctions_.push_back(std::move(fitting));
};
} else {
std::vector<BoundarySystem::JunctionFace> faces;
for(const auto& e:ends) {
const auto& route=routes.at(e.pipe);
if(route.count<2) throw std::invalid_argument("junction branch has no route direction");
const auto a=route.points[e.left?0:route.count-1], b=route.points[e.left?1:route.count-2];
std::array<float,3> direction{b.x-a.x,b.y-a.y,b.z-a.z};
for(std::size_t j=0;j<junctions.branches.size();++j) {
const auto& override=junctions.branches[j];
if(override.pipe==e.pipe && override.left==e.left) { direction=override.direction;branch_used[j]=true; }
}
faces.push_back({e.pipe,e.left,direction});
}
boundaries_.add_junction(faces,junctions.model=="legacy"?BoundarySystem::JunctionMethod::LegacyRusanov:BoundarySystem::JunctionMethod::Characteristic,junctions.turning_loss);
}
++junction_index;
};
auto join=[&](std::initializer_list<JunctionEnd> ends) { junction(std::span(ends.begin(),ends.size())); };
if(v8) {
// Within each bank pair cylinders with the widest available firing gaps.
junction({{9,false},{10,false},{17,true}});
junction({{11,false},{12,false},{18,true}});
junction({{13,false},{15,false},{19,true}});
junction({{14,false},{16,false},{20,true}});
junction({{17,false},{18,false},{21,true}});
junction({{19,false},{20,false},{22,true}});
join({{9,false},{10,false},{17,true}});
join({{11,false},{12,false},{18,true}});
join({{13,false},{15,false},{19,true}});
join({{14,false},{16,false},{20,true}});
join({{17,false},{18,false},{21,true}});
join({{19,false},{20,false},{22,true}});
if(junctions.h_pipe) {
junction({{21,false},{23,true},{25,true}});
junction({{22,false},{24,true},{25,false}});
join({{21,false},{23,true},{25,true}});
join({{22,false},{24,true},{25,false}});
} else {
boundaries_.add_connection(21,false,23,true);
boundaries_.add_connection(22,false,24,true);
@@ -114,11 +150,6 @@ FourStrokeScenario::FourStrokeScenario(int sample_rate, EngineConfig configurati
const int exit=1+2*count;
const bool dual=engine.dual_outlets();
const int groups=engine.header_groups(), secondary=exit+(dual?2:1);
auto junction=[&](const std::vector<JunctionEnd>& ends) {
auto fitting=std::make_unique<Junction>(junctions.junction_volume,101325,700);
fitting->connect(boundaries_,pipes,ends);
solver_.add_component(*fitting);exhaust_junctions_.push_back(std::move(fitting));
};
for(int group=0;group<groups;++group) {
std::vector<JunctionEnd> ends;
for(int i=0;i<count;++i)
@@ -128,7 +159,7 @@ FourStrokeScenario::FourStrokeScenario(int sample_rate, EngineConfig configurati
if(dual) {
// Preserve independent bank pulse trains all the way to their mouths.
for(int bank=0;bank<2;++bank) {
junction({{secondary+bank,false},{exit+bank,true}});
join({{secondary+bank,false},{exit+bank,true}});
boundaries_.add_radiating_open_end(exit+bank,false,101325,pipes[exit+bank].area);
}
} else {
@@ -138,23 +169,37 @@ FourStrokeScenario::FourStrokeScenario(int sample_rate, EngineConfig configurati
boundaries_.add_radiating_open_end(exit,false,101325,pipes[exit].area);
}
} else {
auto& collector_out = exhaust_collector.create_port();
const int exit=1+2*count;
boundaries_.add_orifice(collector_out,exit,true,tail_area_);
if(junctions.model=="volume") {
auto& collector_out = exhaust_collector.create_port();
boundaries_.add_orifice(collector_out,exit,true,tail_area_);
} else {
std::vector<JunctionEnd> ends{{exit,true}};
for(int i=0;i<count;++i) ends.push_back({1+count+i,false});
junction(ends);
}
boundaries_.add_radiating_open_end(exit,false,101325,pipes[exit].area);
}
for(std::size_t i=0;i<branch_used.size();++i)
if(junctions.model!="volume" && !branch_used[i]) throw std::invalid_argument("junction direction override does not name an active characteristic/legacy junction branch");
boundaries_.add_radiating_open_end(0, true, 101325, pipes[0].area);
solver_.set_profiler(&profiler_);
audio_.set_source_positions(scenario_sound_sources(snapshot()));
}
AudioFrame FourStrokeScenario::process() {
const std::lock_guard guard(mutex_);
const bool profile=profiler_.begin_step();
ProfileScope total(&profiler_,profile,ProfileStage::Total);
float contact_impulse=0;
{ ProfileScope controls(&profiler_,profile,ProfileStage::Controls);
step_motorcycle(crankshaft, dt_, engine_config.maximum_load_torque);
ecu.step(dt_, throttle, crankshaft.omega * 60 / (2*pi));
const auto engines = cylinders();
for (int i = 0; i < cylinder_count(); ++i) {
auto& c = *engines[i];
c.pre_step(crankshaft, dt_, throttle);
contact_impulse+=c.take_contact_impulse();
const float degrees = std::fmod((crankshaft.angle+c.phase)*180/pi, 720.f);
intake_valve_areas_[i] = c.valve_area(degrees, true);
exhaust_valve_areas_[i] = c.valve_area(degrees, false);
@@ -167,7 +212,8 @@ AudioFrame FourStrokeScenario::process() {
carb_->step(dt_,throttle,opening,crankshaft.omega*60/(2*pi),std::max(throttle_port_->mass_flow,0.f),inlet.rho,inlet.pressure,intake_plenum.pressure());
intake_plenum.add_fuel_mass(carb_->fuel_flow()*dt_);
}
solver_.step(dt_);
}
solver_.step(dt_,profile);
intake_flow = boundaries_.open_end_mass_flow(engine_config.dual_outlets() ? 2 : 1);
exhaust_flow = boundaries_.open_end_mass_flow(0);
const float other_exhaust = engine_config.dual_outlets() ? boundaries_.open_end_mass_flow(1) : 0.f;
@@ -176,12 +222,57 @@ AudioFrame FourStrokeScenario::process() {
const float intake_source = ambient_density * boundaries_.open_end_volume_flow(engine_config.dual_outlets() ? 2 : 1);
const float exhaust_source = ambient_density * boundaries_.open_end_volume_flow(0);
const float other_source = engine_config.dual_outlets() ? ambient_density * boundaries_.open_end_volume_flow(1) : 0.f;
const auto rendered = audio_.process(intake_source, exhaust_source, other_source);
const auto& sound=active_tuning_->valve_sound;
const float mode_omega=2*pi*sound.mode_hz;
valve_sound_velocity_+=contact_impulse/sound.effective_mass_kg;
valve_sound_velocity_+=(-mode_omega*mode_omega*valve_sound_displacement_-2*sound.damping_ratio*mode_omega*valve_sound_velocity_)*dt_;
valve_sound_displacement_+=valve_sound_velocity_*dt_;
const float valve_contact_source=ambient_density*sound.radiation_area*valve_sound_velocity_;
ProfileScope audio_profile(&profiler_,profile,ProfileStage::Audio);
const auto rendered = audio_.process(intake_source, exhaust_source, other_source,valve_contact_source);
// Count sampled frames only after the complete process call has finished.
if(profile) profiler_.finish_step(total.elapsed_ns());
exhaust_flow += other_exhaust;
++sample_count;
const auto bin=std::clamp(int(std::fmod((crankshaft.angle+cylinder1.phase)*180/pi,720.f)/4),0,179);
intake_lift_trace_[bin]=cylinder1.intake_lift();exhaust_lift_trace_[bin]=cylinder1.exhaust_lift();
intake_lift_trace_[180]=intake_lift_trace_[0];exhaust_lift_trace_[180]=exhaust_lift_trace_[0];
last_contact_impulse_=contact_impulse;
return rendered;
}
void FourStrokeScenario::set_tuning(const EngineConfig& tuning) {
validate_engine_config(tuning,"live tuning");
// The live editor only changes calibration. Geometry is rebuilt separately.
const auto& a=tuning.cylinder;const auto& b=engine_config.cylinder;
if(tuning.geometry!=engine_config.geometry || tuning.cylinders!=engine_config.cylinders || tuning.layout!=engine_config.layout ||
tuning.topology!=engine_config.topology || tuning.phase_degrees!=engine_config.phase_degrees ||
tuning.carbureted!=engine_config.carbureted || tuning.throttle_diameter!=engine_config.throttle_diameter ||
a.bore!=b.bore || a.stroke!=b.stroke || a.rod!=b.rod || a.compression!=b.compression ||
tuning.cells_per_meter!=engine_config.cells_per_meter || tuning.pipes!=engine_config.pipes || tuning.plenum_volume!=engine_config.plenum_volume ||
tuning.collector_volume!=engine_config.collector_volume ||
a.intake_valve_count!=b.intake_valve_count || a.exhaust_valve_count!=b.exhaust_valve_count ||
a.intake_valve_diameter!=b.intake_valve_diameter || a.exhaust_valve_diameter!=b.exhaust_valve_diameter)
throw std::invalid_argument("Live tuning cannot change engine geometry; rebuild the engine instead");
auto next=std::make_shared<const EngineConfig>(tuning);
auto calibration=tuning.ecu;
// The scenario lock spans exactly one process() call. Copy/validation and
// retirement of the old profile happen on the editing thread outside it.
std::shared_ptr<const EngineConfig> retired;
{
const std::lock_guard guard(mutex_);
retired=std::move(active_tuning_);active_tuning_=std::move(next);
std::swap(static_cast<EcuConfig&>(ecu),calibration);
if(carb_) {
static_cast<CarburetorConfig&>(*carb_)=tuning.fuel;
ecu.rev_limit_fuel_cut_enabled=false;ecu.overrun_fuel_cut_enabled=false;ecu.overrun_ignition_cut_enabled=true;
}
const auto engines=cylinders();
for(int i=0;i<cylinder_count();++i)
engines[i]->set_valve_profiles(active_tuning_->intake_cam,tuning.intake_valve,active_tuning_->exhaust_cam,tuning.exhaust_valve);
}
}
void FourStrokeScenario::set_pipe_solver(PipeSolver solver) {
const std::lock_guard guard(mutex_);
for(auto& pipe : pipes) pipe.solver=solver;
@@ -242,10 +333,25 @@ float FourStrokeScenario::area_for_diameter(float diameter) {
std::vector<Pipe1D> FourStrokeScenario::create_pipes(const EngineConfig& engine,const ExhaustConfig& exhaust) {
validate_engine_config(engine);
std::vector<Pipe1D> pipes;
auto mesh_exhaust=exhaust;
if(engine.cells_per_meter>0) mesh_exhaust.cell_length=1/engine.cells_per_meter;
std::size_t index=0;
for(const auto& pipe:engine.pipes)for(int i=0;i<pipe.count;++i) {
if(pipe.silenced)pipes.push_back(make_silenced_exhaust(exhaust));
else pipes.emplace_back(area_for_diameter(pipe.diameter),pipe.length,pipe.cells);
const float length=engine.geometry.pipes.at(index++).length();
if(pipe.silenced) {
auto layout=mesh_exhaust;
const float original=layout.inlet_length+layout.outlet_length+(layout.enabled?layout.connecting_length+layout.resonator.length+layout.muffler.length:0);
const float scale=length/original;
layout.inlet_length*=scale;layout.outlet_length*=scale;layout.connecting_length*=scale;
layout.resonator.length*=scale;layout.resonator.volume*=scale;layout.muffler.length*=scale;layout.muffler.volume*=scale;
auto profile=make_silenced_exhaust(layout);
if(profile.cells.size()>4096) throw std::invalid_argument("silencer mesh exceeds 4096 cells");
pipes.push_back(std::move(profile));
}
else pipes.emplace_back(area_for_diameter(pipe.diameter),length,engine.cells_per_meter>0?mesh_cell_count(length,engine.cells_per_meter):pipe.cells);
}
std::size_t total=0;for(const auto& pipe:pipes)total+=pipe.cells.size();
if(total>32768) throw std::invalid_argument("network mesh exceeds 32768 cells");
return pipes;
}

View File

@@ -17,6 +17,18 @@ PipeRoute route(std::initializer_list<Anchor> anchors) {
}
std::array<PipeRoute, 32> scenario_pipe_routes(const ScenarioSnapshot& s) {
std::array<PipeRoute, 32> routes{};
if(s.geometry) {
for(std::size_t p=0;p<s.geometry->pipes.size();++p) {
const auto& path=s.geometry->pipes[p];auto& result=routes[p];float distance=0;
const float total=path.length();
for(std::size_t i=0;i<path.points.size();++i) {
if(i)distance+=geometry_distance(path.points[i-1],path.points[i]);
result.points[i]=path.points[i];result.fractions[i]=distance/total;
}
result.count=static_cast<unsigned>(path.points.size());result.fractions[result.count-1]=1;
}
return routes;
}
if (s.two_stroke) {
routes[0]=route({{0,{-1.05f,.35f,0}},{1,{-.64f,.35f,0}}});
routes[1]=route({{0,{-.46f,.35f,0}},{1.f/3,{-.28f,.35f,0}},{2.f/3,{-.28f,.1f,0}},{1,{-.105f,.1f,0}}});
@@ -75,6 +87,11 @@ std::array<PipeRoute, 32> scenario_pipe_routes(const ScenarioSnapshot& s) {
}
return routes;
}
bool scenario_pipe_is_exhaust(const ScenarioSnapshot& s,std::size_t p) {
if(s.two_stroke)return p==3;
if(s.v8_headers)return p>=9;
return p>=static_cast<std::size_t>(1+ s.cylinder_count);
}
std::array<Vec3,3> scenario_sound_sources(const ScenarioSnapshot& s) {
const auto routes=scenario_pipe_routes(s);
const int exhaust=s.two_stroke?3:s.v8_headers?23:1+2*s.cylinder_count;

View File

@@ -5,6 +5,20 @@ namespace fluidsim::port {
ScenarioSnapshot FourStrokeScenario::snapshot() const {
const std::lock_guard<std::mutex> guard(mutex_);
ScenarioSnapshot result;
result.geometry=geometry_;
result.cylinder_bore=engine_config.cylinder.bore;result.cylinder_stroke=engine_config.cylinder.stroke;
result.cylinder_rod=engine_config.cylinder.rod;result.cylinder_clearance=cylinder1.clearance_volume();
result.intake_valve_diameter=engine_config.cylinder.intake_valve_diameter;result.exhaust_valve_diameter=engine_config.cylinder.exhaust_valve_diameter;
result.storage_volumes.push_back({"intake_plenum",*engine_config.geometry.volume("intake_plenum"),intake_plenum.pressure()});
if(junction_config.model=="volume") {
if(!engine_config.american_headers()&&!engine_config.staged_headers())
result.storage_volumes.push_back({"exhaust_collector",*engine_config.geometry.volume("exhaust_collector"),exhaust_collector.pressure()});
for(std::size_t i=0;i<exhaust_junctions_.size();++i) {
const auto id="junction_"+std::to_string(i);
result.storage_volumes.push_back({id,*engine_config.geometry.volume(id),exhaust_junctions_[i]->pressure()});
}
}
result.cells_per_meter=engine_config.cells_per_meter;
result.layout=engine_config.layout;
result.bank_angle=engine_config.bank_angle;
result.v8_headers=engine_config.american_headers();
@@ -12,11 +26,21 @@ ScenarioSnapshot FourStrokeScenario::snapshot() const {
result.dual_exhaust=engine_config.dual_outlets();
result.silencers = engine_config.has_silenced_exhaust() && exhaust_config.enabled;
result.exhaust_config = exhaust_config;
if(result.silencers) {
const auto& e=exhaust_config;const int index=1+2*cylinder_count();
const float scale=engine_config.geometry.pipes[index].length()/(e.inlet_length+e.connecting_length+e.resonator.length+e.muffler.length+e.outlet_length);
result.pipe_housings.push_back({std::size_t(index),e.inlet_length*scale,e.resonator.length*scale,e.resonator.diameter()});
result.pipe_housings.push_back({std::size_t(index),(e.inlet_length+e.resonator.length+e.connecting_length)*scale,e.muffler.length*scale,e.muffler.diameter()});
}
result.h_pipe = engine_config.american_headers() && junction_config.h_pipe;
result.crossover_pressures = engine_config.american_headers() ? std::array<float,2>{pipes[21].state(0).pressure,pipes[22].state(0).pressure} : std::array<float,2>{};
result.pipe_solver = pipes.front().solver;
result.solver_substeps = solver_.substep_count();
result.boundary_iterations = boundaries_.nonlinear_iterations;
result.junction_fallbacks=boundaries_.junction_fallbacks;
result.junction_limited_fluxes=boundaries_.junction_limited_fluxes;
result.junction_iterations=boundaries_.junction_iterations;
result.boundary_limited_fluxes=boundaries_.limited_fluxes;
result.rpm = crankshaft.omega * 60.0f / (2.0f * pi);
result.speed_kmh = vehicle.speed * 3.6f;
result.kinetic_energy = vehicle.kinetic_energy();
@@ -52,7 +76,15 @@ ScenarioSnapshot FourStrokeScenario::snapshot() const {
result.rev_limit1 = cylinder1.rev_limit_active;
result.rev_limit2 = cylinder2.rev_limit_active;
result.cylinder_count = cylinder_count();
for(int i=0;i<cylinder_count();++i)result.valve_float=result.valve_float||cylinders()[i]->valve_floating();
result.rev_limit_rpm = ecu.rev_limit_rpm;
result.intake_lift=cylinder1.intake_lift();result.exhaust_lift=cylinder1.exhaust_lift();
result.valve_contact_impulse=last_contact_impulse_;
result.intake_lift_trace=intake_lift_trace_;result.exhaust_lift_trace=exhaust_lift_trace_;
for(std::size_t i=0;i<181;++i) {
result.intake_cam_lifts[i]=FourStrokeCylinder::cam_lift_at(active_tuning_->intake_cam,float(i)*4);
result.exhaust_cam_lifts[i]=FourStrokeCylinder::cam_lift_at(active_tuning_->exhaust_cam,float(i)*4);
}
const std::array<const FourStrokeCylinder*, 12> engines{&cylinder1, &cylinder2, &cylinder3, &cylinder4,
&cylinder5, &cylinder6, &cylinder7, &cylinder8,&cylinder9,&cylinder10,&cylinder11,&cylinder12};
for (int i = 0; i < cylinder_count(); ++i) {
@@ -61,6 +93,7 @@ ScenarioSnapshot FourStrokeScenario::snapshot() const {
result.piston_fractions[i] = (engines[i]->volume-engines[i]->clearance_volume())/engines[i]->swept_volume();
result.intake_areas[i] = intake_valve_areas_[i];
result.exhaust_areas[i] = exhaust_valve_areas_[i];
result.intake_lifts[i]=engines[i]->intake_lift();result.exhaust_lifts[i]=engines[i]->exhaust_lift();
}
result.capture_pipes(pipes);
return result;

View File

@@ -9,7 +9,8 @@ Solver::Solver(std::vector<Pipe1D> &pipes, BoundarySystem &boundaries)
void Solver::add_component(Component &component) { components_.push_back(&component); }
void Solver::step(float dt) {
void Solver::step(float dt,bool profile_sample) {
ProfileScope scope(profiler_,profile_sample,ProfileStage::Solver);
if (!std::isfinite(dt) || dt < 0)
throw std::invalid_argument("invalid solver timestep");
if (dt == 0)
@@ -25,11 +26,11 @@ void Solver::step(float dt) {
if (!(sub_dt > 0) || ++count > 1000000)
throw std::runtime_error("solver timestep failed to progress");
++substep_count_;
boundaries_.resolve(sub_dt);
for (auto &pipe : pipes_)
pipe.advance(sub_dt);
for (Component *component : components_)
component->update_state(sub_dt);
{ ProfileScope measured(profiler_,profile_sample,ProfileStage::Boundary);boundaries_.resolve(sub_dt); }
{ ProfileScope measured(profiler_,profile_sample,ProfileStage::Pipe);
for (auto &pipe : pipes_) pipe.advance(sub_dt); }
{ ProfileScope measured(profiler_,profile_sample,ProfileStage::Components);
for (Component *component : components_) component->update_state(sub_dt); }
remaining -= sub_dt;
}
++step_count_;

View File

@@ -9,14 +9,15 @@
namespace fluidsim::port {
TwoStrokeScenario::TwoStrokeScenario(int sample_rate, TwoStrokeConfig config)
: configuration(config),
: configuration(resolve_two_stroke_geometry(config)),
carburetor(config.carburetor_diameter),
cylinder(config.bore, config.stroke, config.rod, config.geometric_compression(),
config.transfer_duration, config.exhaust_duration),
crankcase(crankshaft, config.stroke * .5f, config.rod, config.bore, config.crankcase_clearance), pipes(create_pipes(config)),
crankcase(crankshaft, config.stroke * .5f, config.rod, config.bore, configuration.crankcase_clearance),
intake_plenum(configuration.geometry.volume("intake_plenum")->volume(),101325,300),pipes(create_pipes(configuration)),
dt_(1.0f / static_cast<float>(std::max(sample_rate, 1))), max_throttle_area_(area_for_diameter(config.carburetor_diameter)),
reed_in_area_(area_for_diameter(0.040f)), reed_out_area_(0.0f),
transfer_pipe_area_(area_for_diameter(0.032f)), transfer_valve_area_(0.0f), exhaust_valve_area_(0.0f),
reed_in_area_(area_for_diameter(config.reed_pipe_diameter)), reed_out_area_(0.0f),
transfer_pipe_area_(area_for_diameter(config.transfer_pipe_diameter)), transfer_valve_area_(0.0f), exhaust_valve_area_(0.0f),
boundaries_(pipes), solver_(pipes, boundaries_), audio_(sample_rate) {
if (!std::isfinite(config.idle_slide_opening) || config.idle_slide_opening<0 || config.idle_slide_opening>1)
throw std::invalid_argument("carb idle slide opening must be in [0,1]");
@@ -91,10 +92,15 @@ TwoStrokeScenario::TwoStrokeScenario(int sample_rate, TwoStrokeConfig config)
solver_.add_component(cylinder);
ScenarioSnapshot geometry;
geometry.two_stroke=true;
geometry_=std::make_shared<PhysicalGeometry>(configuration.geometry);geometry.geometry=geometry_;
solver_.set_profiler(&profiler_);
audio_.set_source_positions(scenario_sound_sources(geometry));
}
AudioFrame TwoStrokeScenario::process() {
const bool profile=profiler_.begin_step();
ProfileScope total(&profiler_,profile,ProfileStage::Total);
{ ProfileScope controls(&profiler_,profile,ProfileStage::Controls);
const float upstream = pipes[1].state(static_cast<int>(pipes[1].cells.size()) - 1).pressure;
reed_out_area_ = std::min(reed.step(dt_, upstream - crankcase.pressure()), reed_in_area_);
reed_open_ = reed.lift() > 1e-6f;
@@ -110,13 +116,17 @@ AudioFrame TwoStrokeScenario::process() {
cylinder.pre_step(crankshaft, dt_, ecu.throttle_opening());
transfer_valve_area_ = cylinder.valve_area(crankshaft.angle * 180.0f / pi, true);
exhaust_valve_area_ = cylinder.valve_area(crankshaft.angle * 180.0f / pi, false);
solver_.step(dt_);
}
solver_.step(dt_,profile);
++step_count_;
intake_flow = boundaries_.open_end_mass_flow(1);
exhaust_flow = boundaries_.open_end_mass_flow(0);
return audio_.process(ambient_density * boundaries_.open_end_volume_flow(1),
ambient_density * boundaries_.open_end_volume_flow(0));
ProfileScope audio_profile(&profiler_,profile,ProfileStage::Audio);
const auto result=audio_.process(ambient_density * boundaries_.open_end_volume_flow(1),
ambient_density * boundaries_.open_end_volume_flow(0));
if(profile) profiler_.finish_step(total.elapsed_ns());
return result;
}
void TwoStrokeScenario::set_throttle(float value) { throttle = std::clamp(value, 0.0f, 1.0f); }
@@ -155,21 +165,38 @@ float TwoStrokeScenario::slide_area(float diameter, float opening) {
}
std::vector<Pipe1D> TwoStrokeScenario::create_pipes(const TwoStrokeConfig& config) {
if(config.cells_per_meter!=0) mesh_cell_count(.1f,config.cells_per_meter);
std::vector<Pipe1D> result;
result.reserve(4);
result.emplace_back(area_for_diameter(config.carburetor_diameter), .100f, 10);
result.emplace_back(area_for_diameter(.040f), .030f, 4);
result.emplace_back(area_for_diameter(.032f), .060f, 6);
const auto count=[&](float length,int legacy) {return config.cells_per_meter>0?mesh_cell_count(length,config.cells_per_meter):legacy;};
const float cell_length=config.cells_per_meter>0?1/config.cells_per_meter:config.exhaust_cell_length;
for(int i=0;i<3;++i) {
const float diameter=i==0?config.carburetor_diameter:i==1?config.reed_pipe_diameter:config.transfer_pipe_diameter;
const float length=config.geometry.pipes[i].length();
result.emplace_back(area_for_diameter(diameter),length,count(length,i==0?10:i==1?4:6));
}
auto sections=config.racing_pipe;float original=0;for(const auto& s:sections)original+=s.length;
const float scale=config.geometry.pipes[3].length()/original;for(auto& s:sections)s.length*=scale;
if (config.exhaust == TwoStrokeExhaust::Racing) {
result.push_back(make_profiled_pipe(config.racing_pipe, config.exhaust_cell_length));
result.push_back(make_profiled_pipe(sections, cell_length));
} else {
float length = 0;
for (const auto& section : config.racing_pipe) length += section.length;
for (const auto& section : sections) length += section.length;
const PipeSection straight{length, config.racing_pipe.front().inlet_diameter,
config.racing_pipe.front().inlet_diameter};
result.push_back(make_profiled_pipe(std::span(&straight, 1), config.exhaust_cell_length));
result.push_back(make_profiled_pipe(std::span(&straight, 1), cell_length));
}
if(result.back().cells.size()>4096) throw std::invalid_argument("two-stroke exhaust exceeds 4096 cells");
return result;
}
void TwoStrokeScenario::capture_geometry(ScenarioSnapshot& s) const {
s.geometry=geometry_;s.cylinder_bore=configuration.bore;s.cylinder_stroke=configuration.stroke;
s.cylinder_rod=configuration.rod;s.cylinder_clearance=cylinder.clearance_volume();
s.storage_volumes.push_back({"intake_plenum",*configuration.geometry.volume("intake_plenum"),intake_plenum.pressure()});
auto box=*configuration.geometry.volume("crankcase");const float bottom=box.minimum().y;
box.size.y=crankcase.volume()/(box.size.x*box.size.z);box.center.y=bottom+box.size.y*.5f;
s.storage_volumes.push_back({"crankcase",box,crankcase.pressure()});
}
} // namespace fluidsim::port

View File

@@ -1,10 +1,29 @@
#include "FluidSim/Desktop/DesktopUi.hpp"
#include "FluidSim/Scenario.hpp"
#include "FluidSim/ScenarioCatalog.hpp"
#include "FluidSim/DesktopScenario.hpp"
#include "FluidSim/Desktop/PlaybackControl.hpp"
#include "FluidSim/Desktop/PipePath.hpp"
#include <algorithm>
#include <cmath>
#include <iostream>
#include <stdexcept>
using namespace fluidsim::port::desktop;
void check(bool condition,const char* reason) {if(!condition)throw std::runtime_error(reason);}
int main() {
int main(int argc,char** argv) {
PlaybackControl playback;
sf::Event space=sf::Event::KeyPressed{.code=sf::Keyboard::Key::Space};
sf::Event wheel=sf::Event::MouseWheelScrolled{sf::Mouse::Wheel::Vertical,-1.f,{400,300}};
check(playback.realtime()&&!playback.handle_event(wheel,true),"realtime mode intercepted camera scrolling");
check(playback.handle_event(space)&&!playback.realtime(),"Space did not disable realtime");
check(!playback.handle_event(wheel)&&playback.target_speed()==1,"regular scroll changed manual speed");
check(playback.handle_event(wheel,true)&&playback.target_speed()<1,"Shift+scroll did not change manual speed");
const auto manual=playback.target_speed();playback.handle_event(space);check(playback.target_speed()==1,"Space did not restore realtime");
playback.handle_event(space);check(playback.target_speed()==manual,"manual speed was lost");
PipePath corner({{0,{0,0,0}},{1,{1,0,0}},{2,{1,1,0}}});
check(corner.position(.5f)==fluidsim::port::Vec3{.5f,0,0}&&corner.position(1.5f)==fluidsim::port::Vec3{1,.5f,0},"sharp pipe bend was rounded into a spline");
const auto miter=corner.miter_offset(1,{.70710678f,-.70710678f,0});
check(std::abs(miter.x-1)<1e-5f&&std::abs(miter.y+1)<1e-5f,"90 degree pipe joint pinches the cross-section");
for(auto size : {sf::Vector2u{933,1010},sf::Vector2u{1920,1080},sf::Vector2u{1040,800},sf::Vector2u{800,700}}) {
const auto l=WorkspaceLayout::calculate(size);
for(auto r : {l.sidebar,l.viewport,l.chart,l.inspector,l.pedal}) {
@@ -46,5 +65,31 @@ int main() {
check(std::abs(std::cos(y)*listener.right.x+std::sin(y)*listener.right.z-1)<1e-6f,
"audio ears must rotate with the camera right axis");
}
std::cout<<"Desktop layout, spring pedal, keyboard travel and orbit camera checks passed\n";
ui.configure_resolution(20);
const auto buttons=DesktopUi::resolution_buttons(size);
const auto plus=buttons[1].position+sf::Vector2f{10,8};
ui.handle_event(sf::Event::MouseButtonPressed{sf::Mouse::Button::Left,{int(plus.x),int(plus.y)}},size);
check(ui.take_resolution_request()==25,"resolution plus button failed");
ui.resolution_started();
ui.handle_event(sf::Event::MouseButtonPressed{sf::Mouse::Button::Left,{int(plus.x),int(plus.y)}},size);
check(!ui.take_resolution_request(),"resolution changed while rebuilding");
ui.resolution_finished(25,"Applied");
if(argc==3 && std::string(argv[1])=="--preview") {
const std::filesystem::path directory=argv[2];std::filesystem::create_directories(directory);
sf::ContextSettings context;context.depthBits=24;context.majorVersion=2;context.minorVersion=1;
sf::RenderWindow window(sf::VideoMode({1440,900}),"Geometry preview",sf::Style::Default,sf::State::Windowed,context);
window.setVisible(false);sf::Font font("C:/Windows/Fonts/consola.ttf");
const auto& catalog=fluidsim::port::scenario_examples();
sf::RenderTexture contact({1280,static_cast<unsigned>((catalog.size()+3)/4)*200});contact.clear(sf::Color::Black);
unsigned index=0;
for(const auto& example:catalog) {
fluidsim::port::DesktopScenario simulation(example);for(int i=0;i<512;++i)simulation.process();
const auto state=simulation.snapshot();DesktopUi preview;preview.configure_resolution(state.cells_per_meter);preview.draw(window,&font,state,{});
sf::Texture texture(window.getSize());texture.update(window);
check(texture.copyToImage().saveToFile(directory/(example.id+".png")),"could not save geometry preview");
sf::Sprite thumbnail(texture);thumbnail.setScale({320.f/window.getSize().x,200.f/window.getSize().y});thumbnail.setPosition({float(index%4)*320,float(index/4)*200});contact.draw(thumbnail);++index;
}
contact.display();check(contact.getTexture().copyToImage().saveToFile(directory/"catalog.png"),"could not save catalog preview");
}
std::cout<<"Desktop controls, resolution stepper, and camera checks passed\n";
}

View File

@@ -56,14 +56,29 @@ int main() {
}
check(rejected,"invalid config accepted: "+expected);
};
auto changed=original;replace(changed,"schema_version = 1","schema_version = 99");reject(changed,"schema_version");
auto changed=original;replace(changed,"schema_version = 2","schema_version = 99");reject(changed,"schema_version");
changed=original;replace(changed,"bore_mm = 75.0","bore_mn = 75.0");reject(changed,"bore_mm");
changed=original;replace(changed,"bore_mm = 75.0","bore_mm = -75.0");reject(changed,"cylinder");
changed=original;replace(changed,"cylinders = 2","cylinders = 2.5");reject(changed,"integer");
changed=original;replace(changed,"layout = \"inline\"","layout = \"flatbread\"");reject(changed,"layout");
changed=original;replace(changed,"topology = \"collector\"","topology = \"american_v8\"");reject(changed,"V8");
changed=original;replace(changed,"cells = 10","cells = 1000000");reject(changed,"cell");
changed=original;replace(changed,"cells_per_meter = 50.0","cells_per_meter = 1000000");reject(changed,"cells_per_meter");
changed=original;replace(changed,"phase_degrees = [0.0, 180.0]","phase_degrees = [0.0]");reject(changed,"phase_degrees");
reject(original+"\n[junctions]\nmodel = \"mystery\"\n","model must be");
reject(original+"\n[junctions]\nturning_loss = -1\n","turning_loss");
reject(original+"\n[[junctions.branches]]\npipe=3\nleft=false\ndirection=[0,0,0]\n","nonzero");
{
std::ofstream output(file);
output<<original<<"\n[junctions]\nmodel=\"characteristic\"\nturning_loss=0.5\n"
<<"[[junctions.branches]]\npipe=3\nleft=false\ndirection=[-1,0,0]\n";
}
const auto directed=load_engine_file(file);
check(directed.junctions.model=="characteristic" && directed.junctions.branches.size()==1,"junction configuration was not loaded");
{
DesktopScenario scenario(directed);
for(int i=0;i<2048;++i) scenario.process();
check(scenario.snapshot().pipe_positivity_repairs==0,"configured characteristic engine needs positivity repairs");
}
reject(original+"\n[unknown_section]\nvalue = 1\n","unknown field");
reject("schema_version = [","Error");

View File

@@ -794,6 +794,38 @@ int main(int argc,char** argv) {
"thermal relaxation differs from exact exponential");
});
test("pipe solver scratch follows runtime solver changes", [] {
Pipe1D pipe(.001f,.2f,16);
pipe.left_ghost=pipe.state(0);
pipe.right_ghost=pipe.state(15);
pipe.solver=PipeSolver::Rusanov;
pipe.prepare_step();
const auto rusanov_bytes=pipe.scratch_bytes();
pipe.solver=PipeSolver::Hlle;
pipe.prepare_step();
const auto hlle_bytes=pipe.scratch_bytes();
pipe.solver=PipeSolver::Muscl;
pipe.prepare_step();
const auto muscl_bytes=pipe.scratch_bytes();
check(rusanov_bytes<hlle_bytes && hlle_bytes<muscl_bytes,
"solver-specific work buffers were not allocated on demand");
for(auto solver : {PipeSolver::Rusanov,PipeSolver::Hlle,PipeSolver::Hllc,
PipeSolver::Muscl,PipeSolver::Rusanov}) {
pipe.solver=solver;
for(int step=0;step<8;++step) {
pipe.left_ghost=pipe.state(0);
pipe.right_ghost=pipe.state(15);
const float dt=.5f*pipe.prepare_step();
pipe.advance(dt);
}
}
for(int i=0;i<16;++i) {
const auto state=pipe.state(i);
check(std::isfinite(state.rho)&&std::isfinite(state.pressure)&&
state.rho>0&&state.pressure>0,"solver switch produced a nonphysical pipe state");
}
});
test("sealed crankcase compression conserves mass above five bar", [] {
Crankshaft crank;
crank.angle = 0;

79
tests/junction_tests.cpp Normal file
View File

@@ -0,0 +1,79 @@
#include "FluidSim/Solver.hpp"
#include <array>
#include <cmath>
#include <iostream>
#include <stdexcept>
using namespace fluidsim::port;
namespace {
void check(bool condition,const char* message) { if(!condition) throw std::runtime_error(message); }
struct Outcome { std::array<double,3> mass{}, pressure{}; std::uint64_t fallbacks{}; };
Outcome flow(bool reverse,bool rotate,float loss,bool mixed=false,bool swap_donors=false) {
std::vector<Pipe1D> pipes;
for(int j=0;j<3;++j) {
pipes.emplace_back(.001f*(mixed?j+1:1),.2f,8);
for(auto& cell:pipes.back().cells) {
const double rho=mixed?(j==0?1.4:1.1):ambient_density;
const double p=mixed?(j==0?120000:101325):101325;
const double u=(swap_donors?(j==0?0:-50):(j==0?70:0))*(reverse?-1:1);
cell={float(rho),float(rho*u),float(p/pipe_gm1+.5*rho*u*u),j==0?.7f:1.f,j==0?.1f:0.f};
}
}
BoundarySystem boundaries(pipes);
std::array<std::array<float,3>,3> axes{{{-1,0,0},{1,0,0},{0,1,0}}};
if(rotate) for(auto& axis:axes) axis={-axis[1],axis[0],axis[2]};
boundaries.add_junction({{0,reverse,axes[0]},{1,!reverse,axes[1]},{2,!reverse,axes[2]}},BoundarySystem::JunctionMethod::Characteristic,loss);
boundaries.add_closed_end(0,!reverse);boundaries.add_closed_end(1,reverse);boundaries.add_closed_end(2,reverse);
const auto totals=[&] {
std::array<double,4> t{};
for(auto& p:pipes) for(int i=0;i<int(p.cells.size());++i) {
const double m=p.cell_mass(i);
t[0]+=m;t[1]+=double(p.cells[i].energy)*p.cell_area(i)*p.cell_length(i);
t[2]+=m*p.cells[i].air;t[3]+=m*p.cells[i].fuel;
}
return t;
};
const auto initial=totals();Solver solver(pipes,boundaries);solver.minimum_substeps=1;
// End before waves reflected by the sealed far ends reach the fitting.
for(int i=0;i<30;++i) solver.step(1e-5f);
const auto final=totals();
for(int j=0;j<4;++j) check(std::abs(final[j]-initial[j])<std::max(1e-10,std::abs(initial[j])*1e-5),"junction lost mass, energy, or species");
Outcome result;
for(int j=0;j<3;++j) {
const int endpoint=(j==0?reverse:!reverse)?0:7;
result.pressure[j]=pipes[j].state(endpoint).pressure;
for(int i=0;i<8;++i) result.mass[j]+=pipes[j].cell_mass(i);
check(pipes[j].positivity_repairs==0,"junction needed positivity repair");
}
result.fallbacks=boundaries.junction_fallbacks;
check(result.fallbacks==0,"subsonic junction unexpectedly fell back");
return result;
}
}
int main() {
try {
const auto ideal=flow(false,false,0);
check(std::abs(ideal.mass[1]-ideal.mass[2])<1e-10,"ideal equal-area outlets are asymmetric");
const auto directed=flow(false,false,1);
check(directed.mass[1]>directed.mass[2]+1e-8,"turning loss does not favor straight-through flow");
const auto rotated=flow(false,true,1), reversed=flow(true,false,1);
for(int j=0;j<3;++j) {
check(std::abs(rotated.mass[j]-directed.mass[j])<1e-10,"rotating junction changes flow");
check(std::abs(reversed.mass[j]-directed.mass[j])<1e-9,"pipe coordinate reversal changes flow");
}
flow(false,false,.5f,true);flow(true,true,.5f,true);
flow(false,false,.5f,true,true);flow(true,true,.5f,true,true);
// Supersonic endpoint data must explicitly select the conservative fallback.
std::vector<Pipe1D> pipes;for(int j=0;j<2;++j) pipes.emplace_back(.001f,.1f,4);
for(auto& cell:pipes[0].cells) { const float u=500;cell.momentum=cell.rho*u;cell.energy+=.5f*cell.rho*u*u; }
BoundarySystem boundaries(pipes);
boundaries.add_junction({{0,false},{1,true}});
boundaries.add_closed_end(0,true);boundaries.add_closed_end(1,false);
Solver solver(pipes,boundaries);solver.minimum_substeps=1;solver.step(1e-6f);
check(boundaries.junction_fallbacks==1,"supersonic fallback was not reported");
bool rejected=false;
try { BoundarySystem invalid(pipes);invalid.add_junction({{0,false},{1,true}},BoundarySystem::JunctionMethod::Characteristic,1); }
catch(const std::invalid_argument&) {rejected=true;}
check(rejected,"directionless turning-loss node was accepted");
std::cout<<"Junction conservation, species, direction, rotation, reversal, and fallback tests passed\n";
} catch(const std::exception& e) { std::cerr<<e.what()<<'\n';return 1; }
}

View File

@@ -0,0 +1,79 @@
#include "FluidSim/DesktopScenario.hpp"
#include <atomic>
#include <chrono>
#include <cmath>
#include <iostream>
#include <limits>
#include <numeric>
#include <stdexcept>
#include <thread>
using namespace fluidsim::port;
namespace {
void check(bool value,const char* message) {if(!value)throw std::runtime_error(message);}
bool near(float a,float b) {return std::abs(a-b)<1e-5f;}
struct TempDirectory {
std::filesystem::path path=std::filesystem::temp_directory_path()/
("fluidsim-live-tuning-"+std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
TempDirectory(){std::filesystem::create_directory(path);}
~TempDirectory(){std::error_code ignored;std::filesystem::remove_all(path,ignored);}
};
}
int main() {
const auto& source=scenario_example("single-450");
FourStrokeScenario simulation(44100,source);
for(int i=0;i<1024;++i)simulation.process();
const float rpm=simulation.crankshaft.omega,angle=simulation.crankshaft.angle;
const float pressure=simulation.cylinder1.pressure,lift=simulation.cylinder1.intake_lift();
const auto samples=simulation.sample_count;
auto tuned=source.engine;
tuned.intake_valve.mass_kg=.047f;tuned.intake_valve.spring_rate_n_per_m=45000;
tuned.intake_valve.damping_n_s_per_m=32;tuned.valve_sound.radiation_area=.0005f;
tuned.ecu.idle_base_opening=.055f;tuned.fuel.pilot_jet_diameter*=1.1f;
for(auto& p:tuned.intake_cam.points)p.lift_metres*=.8f;
for(auto& p:tuned.ecu.ignition_map)p.degrees_btdc+=4;
simulation.set_tuning(tuned);
check(simulation.crankshaft.omega==rpm && simulation.crankshaft.angle==angle && simulation.sample_count==samples,"live edit restarted the engine");
check(simulation.cylinder1.pressure==pressure && simulation.cylinder1.intake_lift()==lift,"live edit reset cylinder or valve state");
const auto snapshot=simulation.snapshot();
check(near(snapshot.intake_cam_lifts[120],FourStrokeCylinder::cam_lift_at(tuned.intake_cam,480)),"live cam profile was not published");
check(near(simulation.ecu.idle_base_opening,.055f),"ECU calibration was not applied");
for(int i=0;i<2048;++i) {
const auto frame=simulation.process();
check(std::isfinite(frame.left) && std::isfinite(frame.right),"live tuning produced nonfinite audio");
}
auto invalid=tuned;invalid.pipes[0].length*=1.5f;
bool rejected=false;try {simulation.set_tuning(invalid);}catch(const std::invalid_argument&){rejected=true;}
check(rejected,"mesh edits must require a rebuild");
invalid=tuned;invalid.intake_valve.mass_kg=std::numeric_limits<float>::quiet_NaN();
rejected=false;try {simulation.set_tuning(invalid);}catch(const std::exception&){rejected=true;}
check(rejected,"invalid calibration accepted");
TempDirectory temp;
const auto saved=save_tuned_engine(source,tuned,temp.path);
const auto loaded=load_engine_file(saved);
check(loaded.id=="single-450-tuned" && loaded.source_path!=source.source_path,"export replaced source identity");
check(near(loaded.engine.intake_valve.mass_kg,tuned.intake_valve.mass_kg),"valve mass export changed units");
check(near(loaded.engine.fuel.pilot_jet_diameter,tuned.fuel.pilot_jet_diameter),"carburetor edit was not exported");
check(near(loaded.engine.ecu.ignition_map[0].degrees_btdc,tuned.ecu.ignition_map[0].degrees_btdc),"ignition trim was not exported");
check(near(loaded.engine.intake_cam.points[10].lift_metres,tuned.intake_cam.points[10].lift_metres),"cam profile export changed units");
auto at_limit=tuned;at_limit.intake_cam.points[10].lift_metres=at_limit.intake_valve.maximum_lift_m;
check(save_tuned_engine(source,at_limit,temp.path)!=saved,"repeat export overwrote an engine");
DesktopScenario desktop(source);
desktop.set_controls(.2f,.1f,.6f);desktop.set_pipe_solver(PipeSolver::Hlle);desktop.set_audio_scene(OutdoorScene::Dry);
auto pipes=tuned;auto& route=pipes.geometry.pipes[0];const auto end=route.points.back(),start=route.points.front();
route.points.front()={end.x+(start.x-end.x)*1.2f,end.y+(start.y-end.y)*1.2f,end.z+(start.z-end.z)*1.2f};
pipes.pipes[0].length=route.length();
desktop.rebuild_engine(pipes);
const auto rebuilt=desktop.snapshot();
const float length=std::accumulate(rebuilt.pipe_lengths[0].begin(),rebuilt.pipe_lengths[0].end(),0.f);
check(near(length,pipes.pipes[0].length),"rebuilt mesh uses old dimensions");
check(rebuilt.pipe_solver==PipeSolver::Hlle && rebuilt.audio_scene==OutdoorScene::Dry && near(rebuilt.clutch,.6f),"rebuild lost scene or controls");
std::atomic<bool> finite{true};
std::jthread worker([&]{for(int i=0;i<4096;++i) {auto f=desktop.process();if(!std::isfinite(f.left)||!std::isfinite(f.right))finite=false;}});
for(int i=0;i<16;++i) {auto live=pipes;live.intake_valve.mass_kg+=float(i)*.0001f;desktop.set_tuning(live);desktop.snapshot();}
desktop.rebuild_engine(pipes);
worker.join();check(finite,"concurrent editing or rebuild corrupted the simulation");
std::cout<<"Live tuning state, export round trip, mesh rebuild and concurrent publication passed\n";
}

View File

@@ -0,0 +1,47 @@
#include "FluidSim/DesktopScenario.hpp"
#include <atomic>
#include <cmath>
#include <iostream>
#include <limits>
#include <stdexcept>
#include <thread>
using namespace fluidsim::port;
void check(bool value,const char* reason) {if(!value)throw std::runtime_error(reason);}
double volume(const ScenarioSnapshot& s) {
double sum=0;
for(int p=0;p<32;++p) for(std::size_t i=0;i<s.pipe_areas[p].size();++i)
sum+=double(s.pipe_areas[p][i])*s.pipe_lengths[p][i];
return sum;
}
int main() {
try {
check(mesh_cell_count(.1f,100)==10,"decimal cell count rounded upward");
for(const auto& example:scenario_examples())
check((example.kind==ScenarioKind::TwoStroke?example.two.cells_per_meter:example.engine.cells_per_meter)>0,"scenario lacks mesh density");
for(const auto* id:{"single-450","yz-racing","saab-chambered"}) {
DesktopScenario scenario(scenario_example(id));
scenario.set_controls(.3f,.1f,.6f);scenario.set_pipe_solver(PipeSolver::Hllc);scenario.set_audio_scene(OutdoorScene::Dry);
const double original_volume=volume(scenario.snapshot());
std::atomic<bool> finite{true};
std::jthread worker([&]{for(int i=0;i<2048;++i) {const auto f=scenario.process();if(!std::isfinite(f.left)||!std::isfinite(f.right))finite=false;}});
scenario.set_cells_per_meter(35);
worker.join();check(finite,"concurrent resolution rebuild corrupted audio");
auto state=scenario.snapshot();
check(state.cells_per_meter==35 && state.pipe_solver==PipeSolver::Hllc && state.audio_scene==OutdoorScene::Dry,"resolution rebuild lost options");
check(std::abs(volume(state)-original_volume)<original_volume*1e-5,"remeshing changed pipe volume");
for(const auto& lengths:state.pipe_lengths) for(float length:lengths)
check(length<=1.f/35+1e-6f,"profile or uniform pipe ignored cells/m");
check(std::abs(state.clutch-.6f)<1e-5f,"resolution rebuild lost controls");
const auto old_count=state.pipe_states[0].size();
for(float invalid:{0.f,-1.f,501.f,std::numeric_limits<float>::quiet_NaN()}) {
bool rejected=false;try{scenario.set_cells_per_meter(invalid);}catch(const std::exception&){rejected=true;}
check(rejected,"invalid density accepted");
check(scenario.snapshot().pipe_states[0].size()==old_count,"failed rebuild changed live mesh");
}
}
DesktopScenario a(scenario_example("single-450")),b(scenario_example("single-650"));
const float density=b.snapshot().cells_per_meter;a.set_cells_per_meter(50);
check(b.snapshot().cells_per_meter==density,"resolution leaked between scenarios");
std::cout<<"Uniform/profile mesh resolution, volume, rebuild, isolation and validation checks passed\n";
} catch(const std::exception& e) {std::cerr<<e.what()<<'\n';return 1;}
}

View File

@@ -181,6 +181,26 @@ int main() {
check(std::isfinite(frame.left) && std::isfinite(frame.right),"catalog listener wiring failed");
}
// Source stems reconstruct the mix, and muting leaves diagnostic stems alive.
auto stem_config=config();
stem_config.dual_exhaust=true; stem_config.valve_contact_enabled=true;
OutdoorAudio stems(44100,stem_config), muted(44100,stem_config);
for(std::size_t j=0;j<4;++j) muted.set_source_enabled(j,false);
for(int i=0;i<12000;++i) {
const float x=.01f*std::sin(i*.07f);
const auto mix=stems.process(x,x*.4f,-x*.2f,x*.1f);
auto sum=stems.tail_frame();
for(const auto& source:stems.source_frames()) {
sum.left+=source.rendered.left; sum.right+=source.rendered.right;
}
check(std::abs(sum.left-mix.left)<1e-6f && std::abs(sum.right-mix.right)<1e-6f,
"source stems do not reconstruct rendered mix");
const auto silent=muted.process(x,x*.4f,-x*.2f,x*.1f);
check(silent.left==0 && silent.right==0,"muted sources feed listening mix or reverb");
check(muted.source_frames()[0].rendered.left==stems.source_frames()[0].rendered.left,
"muting changes diagnostic source history");
}
ReedValve reed;
for (int i = 0; i < 10000; i++)
reed.step(1.f / 44100, 100);

View File

@@ -0,0 +1,67 @@
#include "FluidSim/DesktopScenario.hpp"
#include "FluidSim/ScenarioGeometry.hpp"
#include <toml.hpp>
#include <chrono>
#include <fstream>
#include <iostream>
#include <numeric>
#include <stdexcept>
using namespace fluidsim::port;
void check(bool value,const char* message){if(!value)throw std::runtime_error(message);}
int main() try {
for(const auto& e:scenario_examples()) {
DesktopScenario scenario(e);const auto state=scenario.snapshot();
check(bool(state.geometry),"scenario lacks physical geometry");const auto routes=scenario_pipe_routes(state);
for(std::size_t p=0;p<state.geometry->pipes.size();++p) {
const float mesh=std::accumulate(state.pipe_lengths[p].begin(),state.pipe_lengths[p].end(),0.f);
check(std::abs(mesh-state.geometry->pipes[p].length())<1e-5f*std::max(1.f,mesh),"fluid length differs from render centerline");
check(routes[p].count==state.geometry->pipes[p].points.size(),"renderer lost a bend");
for(unsigned k=0;k<routes[p].count;++k)check(geometry_distance(routes[p].points[k],state.geometry->pipes[p].points[k])<1e-7f,"render route differs from input geometry");
}
check(!state.storage_volumes.empty(),"storage volumes missing from rendering snapshot");
for(const auto& v:state.storage_volumes) {
check(v.box.volume()>0&&std::isfinite(v.pressure),"invalid physical storage volume");
const auto lo=v.box.minimum(),hi=v.box.maximum();int ports=0;
for(const auto& p:state.geometry->pipes)for(auto endpoint:{p.points.front(),p.points.back()}) {
constexpr float tolerance=2e-6f;
const bool inside=endpoint.x>=lo.x-tolerance&&endpoint.x<=hi.x+tolerance&&endpoint.y>=lo.y-tolerance&&endpoint.y<=hi.y+tolerance&&endpoint.z>=lo.z-tolerance&&endpoint.z<=hi.z+tolerance;
const bool surface=std::min({std::abs(endpoint.x-lo.x),std::abs(endpoint.x-hi.x),std::abs(endpoint.y-lo.y),std::abs(endpoint.y-hi.y),std::abs(endpoint.z-lo.z),std::abs(endpoint.z-hi.z)})<tolerance;
if(inside&&surface)++ports;
}
if(ports<2)throw std::runtime_error(e.id+": storage volume has disconnected pipe ports: "+v.id);
}
for(int i=0;i<state.cylinder_count;++i) {
const auto& c=state.geometry->cylinders[i];const float angle=-c.tilt_degrees*pi/180;
const auto attached=[&](Vec3 p) {
p={p.x-c.origin.x,p.y-c.origin.y,p.z-c.origin.z};
const float y=p.y*std::cos(angle)-p.z*std::sin(angle),z=p.y*std::sin(angle)+p.z*std::cos(angle);
return std::abs(p.x)<=state.cylinder_bore*.65f&&std::abs(z)<=state.cylinder_bore*.65f&&y>=state.cylinder_rod-state.cylinder_stroke*.5f-.005f&&y<=state.cylinder_rod+state.cylinder_stroke*.5f+state.cylinder_clearance/(pi*state.cylinder_bore*state.cylinder_bore*.25f)+.005f;
};
const auto& intake=state.geometry->pipes[state.two_stroke?2:1+i];
const auto& exhaust=state.geometry->pipes[state.two_stroke?3:1+state.cylinder_count+i];
if(!attached(intake.points.back())||!attached(exhaust.points.front()))throw std::runtime_error(e.id+": cylinder port is disconnected from its geometry");
}
if(e.kind!=ScenarioKind::TwoStroke){FourStrokeScenario four(44100,e);check(std::abs(four.intake_plenum.volume()-e.engine.geometry.volume("intake_plenum")->volume())<1e-8f,"plenum simulation ignores geometry");}
}
const auto average_primary=[](const ScenarioExample& e){float sum=0;for(int i=0;i<e.engine.cylinders;++i)sum+=e.engine.geometry.pipes[1+e.engine.cylinders+i].length();return sum/e.engine.cylinders;};
check(average_primary(scenario_example("v8-short"))<average_primary(scenario_example("v8-h"))&&average_primary(scenario_example("v8-h"))<average_primary(scenario_example("v8-long")),"short and long header variants lost their physical distinction");
auto table=toml::parse_file(scenario_example("single-450").source_path.string());
auto& volumes=*table["geometry"]["volumes"].as_array();
toml::table* plenum_table=nullptr;
for(auto& v:volumes)if((*v.as_table())["id"].value_or<std::string>("")=="intake_plenum")plenum_table=v.as_table();
check(plenum_table!=nullptr,"test volume not found");auto& plenum=*plenum_table;
plenum.erase("size_mm");plenum.erase("center_mm");
plenum.insert_or_assign("corners_mm",toml::array{toml::array{100.,200.,300.},toml::array{0.,0.,0.}});
auto& pipe=*table["geometry"]["pipes"].as_array()->get(0)->as_table();
pipe.insert_or_assign("points_mm",toml::array{toml::array{0.,0.,0.},toml::array{30.,40.,0.},toml::array{30.,40.,120.}});
const auto file=std::filesystem::temp_directory_path()/("fluidsim-geometry-"+std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())+".toml");
const auto write=[&]{{std::ofstream out(file);out<<table;}};
write();const auto loaded=load_engine_file(file);FourStrokeScenario changed(44100,loaded);
check(std::abs(changed.intake_plenum.volume()-.006f)<1e-8f,"corner-defined volume conversion is wrong");
check(geometry_distance(loaded.engine.geometry.volume("intake_plenum")->center,{.05f,.1f,.15f})<1e-7f,"corner center is wrong");
check(std::abs(std::accumulate(changed.pipes[0].cell_lengths.begin(),changed.pipes[0].cell_lengths.end(),0.f)-.17f)<1e-6f,"path length does not drive simulation");
pipe.insert_or_assign("points_mm",toml::array{toml::array{0.,0.,0.},toml::array{0.,0.,0.}});write();
bool rejected=false;try{load_engine_file(file);}catch(const std::exception& e){rejected=std::string(e.what()).find(file.filename().string())!=std::string::npos;}
std::filesystem::remove(file);check(rejected,"degenerate geometry accepted or missing file diagnostic");
std::cout<<"All scenario centerlines, storage dimensions, corner volumes and invalid geometry passed\n";
}catch(const std::exception& e){std::cerr<<e.what()<<'\n';return 1;}

View File

@@ -77,6 +77,14 @@ int main() {
require(std::abs(direction.x*direction.x+direction.y*direction.y+direction.z*direction.z-1)<1e-4f,"pipe path tangent must be normalized");
auto atm=desktop::pressure_tint(101325),vac=desktop::pressure_tint(0),peak=desktop::pressure_tint(1000000);
require(atm.y>.8f && vac.z>.99f && peak.x>.99f,"pressure palette must show atmosphere green, vacuum blue, spikes red");
const auto vacuum_before=desktop::pressure_tint(40000),vacuum_after=desktop::pressure_tint(40000,true);
const auto small_intake_pulse=desktop::pressure_tint(104325),small_exhaust_pulse=desktop::pressure_tint(104325,true);
require(vacuum_before.x==vacuum_after.x&&vacuum_before.y==vacuum_after.y&&vacuum_before.z==vacuum_after.z,
"exhaust highlighting must not change the established underpressure palette");
require(small_intake_pulse.x>.9f&&small_intake_pulse.y>.8f&&small_intake_pulse.z<.1f,
"a small positive intake pulse must be visible as yellow");
require(small_exhaust_pulse.x>.95f&&small_exhaust_pulse.y<.4f,
"a small positive exhaust pulse must be visible as orange-red");
Ecu fast_actuator;
for(int i=0;i<230;++i)fast_actuator.step(1.f/44100,1,5000);
require(fast_actuator.throttle_opening()>.99f,"throttle actuator must settle within about 5 ms");

View File

@@ -0,0 +1,172 @@
#include "FluidSim/Junction.hpp"
#include "FluidSim/Solver.hpp"
#include <array>
#include <cmath>
#include <complex>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <iomanip>
#include <string>
using namespace fluidsim::port;
namespace {
constexpr double pressure=101325, area=.001;
struct Result { double incident{}, reflected{}, transmitted{}, mass_drift{}, energy_drift{};
std::array<std::complex<double>,2> incident_spectrum{}, reflected_spectrum{}, transmitted_spectrum{};
std::uint64_t repairs{}, limited{}, steps{}, fallbacks{}; };
Result run(const std::string& kind,int n,float courant,PipeSolver scheme,
double volume,float discharge,const std::filesystem::path& trace) {
const bool straight=kind=="straight", split=kind=="split", star=kind=="star" || kind=="legacy-star" || kind=="star-unequal";
const int branches=star || kind=="volume-y" ? 3 : 2;
Junction chamber(float(volume),pressure,300);
const double rho=chamber.density(), c=std::sqrt(1.4*pressure/rho), z=rho*c;
std::vector<Pipe1D> pipes;
if(straight) pipes.emplace_back(area,4,2*n);
else for(int j=0;j<branches;++j) pipes.emplace_back(area*(kind=="star-unequal"?j+1:1),2,n);
for(auto& p:pipes) {
p.solver=scheme; p.damping_multiplier=0; p.energy_relaxation_rate=0;
for(int i=0;i<int(p.cells.size());++i) {
const double x=(i+.5)*p.dx;
const double pulse=&p==&pipes[0] ? 100*std::exp(-std::pow((x-.6)/.12,2)) : 0;
const double density=rho+pulse/(c*c), u=pulse/z;
p.cells[i]={float(density),float(density*u),float((pressure+pulse)/pipe_gm1+.5*density*u*u),1,0};
}
}
BoundarySystem boundaries(pipes);
boundaries.add_closed_end(0,true);
if(straight) boundaries.add_closed_end(0,false);
else {
for(int j=1;j<branches;++j) boundaries.add_closed_end(j,false);
if(split) boundaries.add_connection(0,false,1,true);
else if(star) boundaries.add_junction({{0,false},{1,true},{2,true}},kind=="legacy-star"?BoundarySystem::JunctionMethod::LegacyRusanov:BoundarySystem::JunctionMethod::Characteristic);
else {
std::vector<JunctionEnd> ends{{0,false,discharge},{1,true,discharge}};
if(branches==3) ends.push_back({2,true,discharge});
chamber.connect(boundaries,pipes,ends);
}
}
Solver solver(pipes,boundaries); solver.minimum_substeps=1;
const bool has_volume=!straight && !split && !star;
if(has_volume) solver.add_component(chamber);
auto totals=[&] {
std::array<double,2> t{has_volume?chamber.mass():0,has_volume?chamber.internal_energy():0};
for(const auto& p:pipes) for(int i=0;i<int(p.cells.size());++i) {
t[0]+=p.cell_mass(i); t[1]+=double(p.cells[i].energy)*p.cell_area(i)*p.cell_length(i);
}
return t;
};
const auto initial=totals(); Result r;
std::ofstream out(trace);
if(!out) throw std::runtime_error("cannot open trace");
out<<std::setprecision(10);
out<<"time_s,upstream_pressure_pa,upstream_velocity_m_s,downstream_pressure_pa,downstream_velocity_m_s\n";
const int up=int(1.4/pipes[0].dx);
const int down=int((straight?2.6:.6)/pipes.back().dx);
double time=0;
// Stop before far-end reflections return to either probe. Integrate acoustic
// characteristics p+/- = (p' +/- rho*c*u)/2, not squared total pressure.
while(time<.009) {
const float dt=float(std::min(.009-time,courant*(2./n)/(c+.5)));
const auto a=pipes[0].state(up), b=pipes.back().state(down);
const double plus=(a.pressure-pressure+z*a.velocity)/2;
const double minus=(a.pressure-pressure-z*a.velocity)/2;
for(int frequency=0;frequency<2;++frequency) {
const double hz=frequency==0?500:1000;
const auto weight=std::exp(std::complex<double>(0,-2*3.141592653589793*hz*time))*double(dt);
r.incident_spectrum[frequency]+=plus*weight;
r.reflected_spectrum[frequency]+=minus*weight;
r.transmitted_spectrum[frequency]+=(b.pressure-pressure+z*b.velocity)*.5*weight;
}
r.incident+=plus*plus*area/z*dt;
r.reflected+=minus*minus*area/z*dt;
if(straight) {
const double v=(b.pressure-pressure+z*b.velocity)/2;
r.transmitted+=v*v*area/z*dt;
} else for(int j=1;j<branches;++j) {
const auto q=pipes[j].state(down);
const double v=(q.pressure-pressure+z*q.velocity)/2;
r.transmitted+=v*v*pipes[j].cell_area(down)/z*dt;
}
out<<time<<','<<a.pressure-pressure<<','<<a.velocity<<','<<b.pressure-pressure<<','<<b.velocity<<'\n';
solver.step(dt); time+=dt;
}
const auto final=totals();
r.mass_drift=(final[0]-initial[0])/initial[0];
r.energy_drift=(final[1]-initial[1])/initial[1];
for(auto& p:pipes) r.repairs+=p.positivity_repairs;
r.fallbacks=boundaries.junction_fallbacks;
r.limited=boundaries.limited_fluxes; r.steps=solver.substep_count();
if(!(r.incident>0) || !std::isfinite(r.transmitted) || r.repairs ||
std::abs(r.mass_drift)>1e-4 || std::abs(r.energy_drift)>1e-4)
throw std::runtime_error("acoustic benchmark robustness/conservation failure: "+trace.string()+" mass="+std::to_string(r.mass_drift)+" energy="+std::to_string(r.energy_drift)+" limited="+std::to_string(r.limited)+" repairs="+std::to_string(r.repairs));
return r;
}
}
int main(int argc,char** argv) {
try {
const std::filesystem::path dir=argc>1?argv[1]:"out/acoustics";
std::filesystem::create_directories(dir);
std::ofstream summary(dir/"summary.csv");
summary<<std::setprecision(10);
summary<<"case,solver,cells_per_2m,courant,volume_m3,discharge,incident_j,reflected_ratio,transmitted_ratio,unaccounted_ratio,mass_relative_drift,energy_relative_drift,limited_fluxes,positivity_repairs,substeps,transmission_over_straight,reflection_over_straight,ideal_reflected_ratio,ideal_transmitted_ratio,status,junction_fallbacks,t500_gain_over_straight,t500_phase_error_rad,t1000_gain_over_straight,t1000_phase_error_rad\n";
double straight_transmission=0, straight_reflection=0, coarse_transmission=0;
std::array<double,6> first_order_transmission{};
std::array<std::complex<double>,2> straight_spectrum{};
int cases=0, flagged=0;
for(const auto* scheme:{"rusanov","hlle","hllc","muscl"})
for(int n:{40,80,160}) for(float courant:{.4f,.8f})
for(const auto* kind:{"straight","split","star","legacy-star","star-unequal","volume","volume-y"})
for(double volume:{.00005,.0005,.005}) {
if((std::string(kind)=="straight" || std::string(kind)=="split" || (std::string(kind)=="star" || std::string(kind)=="legacy-star" || std::string(kind)=="star-unequal")) && volume!=.00005) continue;
for(float discharge:{1.f,.7f}) {
if((std::string(kind)=="straight" || std::string(kind)=="split" || (std::string(kind)=="star" || std::string(kind)=="legacy-star" || std::string(kind)=="star-unequal")) && discharge!=1) continue;
const std::string id=std::string(kind)+"-"+scheme+"-"+std::to_string(n)+"-"+std::to_string(courant)+"-"+std::to_string(volume)+"-"+std::to_string(discharge);
const auto r=run(kind,n,courant,parse_pipe_solver(scheme),volume,discharge,dir/(id+".csv"));
const double reflected=r.reflected/r.incident, transmitted=r.transmitted/r.incident;
if(std::string(kind)=="straight") {
straight_transmission=transmitted; straight_reflection=reflected;
for(int frequency=0;frequency<2;++frequency)
straight_spectrum[frequency]=r.transmitted_spectrum[frequency]/r.incident_spectrum[frequency];
const int slot=(n==40?0:n==80?2:4)+(courant>.5f?1:0);
if(std::string(scheme)=="rusanov") first_order_transmission[slot]=transmitted;
if(std::string(scheme)=="muscl" && transmitted<first_order_transmission[slot]+.05)
throw std::runtime_error("MUSCL pulse attenuation improvement regressed");
if(reflected>1e-4 || transmitted<=0 || transmitted>1.01)
throw std::runtime_error("straight acoustic propagation regression");
if(courant==.4f) {
if(n>40 && transmitted<=coarse_transmission)
throw std::runtime_error("straight pulse does not converge with mesh refinement");
coarse_transmission=transmitted;
}
}
if(std::string(kind)=="split" &&
(std::abs(transmitted-straight_transmission)>1e-7 || std::abs(reflected-straight_reflection)>1e-7))
throw std::runtime_error("split connection differs from continuous pipe");
if(std::string(kind)=="star" || std::string(kind)=="star-unequal") {
const double expected_r=std::string(kind)=="star"?1./9:4./9;
const double expected_t=1-expected_r;
if(std::abs(reflected/straight_transmission-expected_r)>.04 ||
std::abs(transmitted/straight_transmission-expected_t)>.07 || r.fallbacks)
throw std::runtime_error("characteristic junction acoustic scattering regression: "+id+" R="+std::to_string(reflected/straight_transmission)+" T="+std::to_string(transmitted/straight_transmission)+" fallback="+std::to_string(r.fallbacks));
}
const bool contaminated=r.limited || reflected+transmitted>1.05;
++cases; flagged+=contaminated;
summary<<kind<<','<<scheme<<','<<n<<','<<courant<<','<<volume<<','<<discharge<<','<<r.incident<<','<<reflected<<','<<transmitted<<','<<1-reflected-transmitted<<','<<r.mass_drift<<','<<r.energy_drift<<','<<r.limited<<','<<r.repairs<<','<<r.steps<<','<<transmitted/straight_transmission<<','<<reflected/straight_transmission<<',';
if(std::string(kind)=="straight" || std::string(kind)=="split") summary<<"0,1,";
else if(std::string(kind)=="star-unequal") summary<<4./9<<','<<5./9<<',';
else if(std::string(kind)=="star" || std::string(kind)=="legacy-star") summary<<1./9<<','<<8./9<<',';
else summary<<",,";
summary<<(contaminated?"flagged":"ok")<<','<<r.fallbacks;
for(int frequency=0;frequency<2;++frequency) {
const auto corrected=r.transmitted_spectrum[frequency]/r.incident_spectrum[frequency]/straight_spectrum[frequency];
summary<<','<<std::abs(corrected)<<','<<std::arg(corrected);
if(std::string(kind)=="star" && n==160 && (std::abs(std::abs(corrected)-2./3)>.08 || std::abs(std::arg(corrected))>.15))
throw std::runtime_error("junction frequency/phase regression: "+id);
}
summary<<'\n';
}
}
std::cout<<"Validated "<<cases<<" cases ("<<flagged<<" flagged for limiting or acoustic amplification). Wrote acoustic pulse benchmark to "<<dir<<'\n';
} catch(const std::exception& e) { std::cerr<<e.what()<<'\n'; return 1; }
}

View File

@@ -0,0 +1,276 @@
#!/usr/bin/env python3
"""Create a Markdown summary from fluidsim_scenario_profile CSV output."""
import csv
import statistics
import sys
from pathlib import Path
def med(rows, field):
return statistics.median(float(row[field]) for row in rows)
def main():
source = Path(sys.argv[1] if len(sys.argv) > 1 else "profile-runs.csv")
target = Path(sys.argv[2] if len(sys.argv) > 2 else "profile-report.md")
rows = list(csv.DictReader(source.open(newline="", encoding="utf-8")))
if not rows:
raise SystemExit("No profile rows found")
engines = sorted({r["scenario"] for r in rows})
solvers = ["rusanov", "hlle", "hllc", "muscl"]
densities = sorted({int(r["cells_per_meter"]) for r in rows})
lines = [
"# FluidSim engine and solver performance profile",
"",
"**Run date:** 2026-10-24 ",
"**Raw measurements:** [profile-runs.csv](profile-runs.csv) ",
"**Harness:** `tools/scenario_profile.cpp` ",
"**Analyzer:** `tools/analyze_scenario_profile.py`",
"",
"These runs predate the geometry-defined header layouts. Current scenarios have revised header lengths; rerun the harness for current performance figures.",
"",
"## Summary",
"",
"The solver is the main cost in every tested configuration. At 20 cells/m it takes about 76–92% of measured `process()` time; controls take 3–9%, audio rendering 3–12%, and component updates 2–3% of solver time. Across the run matrix, the median boundary share of solver time falls from 56.5% at 20 cells/m to 30.5% at 80 cells/m; pipe updates rise from 32.8% to 55.0%. The bottleneck shifts from boundary resolution toward pipe-cell advancement as density grows.",
"",
"Mesh density has a nonlinear runtime effect because smaller cells also require more CFL substeps. Raising density from 20 to 80 cells/m increased median frame cost by 3.0×–6.4× across these engines. The V8 was most expensive: at 80 cells/m it used about 1,054 cells and took about 134 µs per frame in the profiled run.",
"",
"At a fixed density of 20 cells/m, Rusanov was the fastest solver. Relative to Rusanov at that same density, MUSCL cost 16–50% more depending on engine, while HLLE and HLLC were generally 3–25% more. At your solver-specific listening targets, MUSCL at 25 cells/m is the fastest overall because it reaches your preferred sound at a coarser mesh.",
"",
"Across all tested scenarios and densities, the median sampled frame cost was 11.6 µs for Rusanov, 13.0 µs for HLLE, 13.5 µs for HLLC, and 15.9 µs for MUSCL. Those pooled values summarize this exact sweep and are affected by its chosen scenario and density mix.",
"",
"No pipe positivity repairs or HLLC fallbacks occurred in the measured runs.",
"",
"## Method",
"",
f"- {len(engines)} TOML scenarios, four solvers, {len(densities)} mesh densities, and two repetitions: {len(rows)} measured runs.",
f"- Scenarios: {', '.join(engines)}. `yz-fat` is the two-stroke case; the others are four-stroke.",
f"- Densities: {', '.join(map(str, densities))} cells/m, applied to the scenario's configured geometry.",
"- Solvers: Rusanov, HLLE, HLLC, and MUSCL. Solver choice was applied to every pipe in each scenario.",
"- Each run warmed up for 5,000 frames, then measured 22,050 frames (0.5 simulated seconds) at 44.1 kHz. Engine speed was reset to the scenario's configured initial RPM (or 4,500 RPM when unset) before every frame; the two-stroke was held at 7,000 RPM. Throttle was 55%, load 10%, clutch 0%.",
"- Internal profiling sampled every other measured `process()` call. Each run therefore contains 11,025 profiled frames. End-to-end wall time includes all measured frames; stage timings come from sampled frames.",
"- Build: Release, MinGW GCC 16.2.0, Windows, Intel Core i5-12600K. The machine was otherwise used interactively, so small differences should not be over-interpreted.",
"",
"## Cost at 20 cells/m",
"",
"Each cell shows median end-to-end wall microseconds per frame and wall/simulated-time ratio across the two repetitions. A ratio below 1.0 means this isolated scenario ran faster than real time. Boundary, pipe, and component columns are their share of inclusive solver time; controls and audio are their share of total sampled process time.",
"",
"| Scenario | Solver | Cells | µs/frame | Wall / sim | Controls | Audio | Boundary / solver | Pipe / solver | Components / solver |",
"|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|",
]
for engine in engines:
for solver in solvers:
subset = [r for r in rows if r["scenario"] == engine and int(r["cells_per_meter"]) == 20 and r["solver"] == solver]
if not subset:
continue
total = med(subset, "total_ms_per_frame")
solver_ms = med(subset, "solver_ms_per_frame")
boundary = med(subset, "boundary_ms_per_frame") / solver_ms * 100
pipe = med(subset, "pipe_ms_per_frame") / solver_ms * 100
component = med(subset, "components_ms_per_frame") / solver_ms * 100
controls = med(subset, "controls_ms_per_frame") / total * 100
audio = med(subset, "audio_ms_per_frame") / total * 100
lines.append(f"| {engine} | {solver} | {subset[0]['total_cells']} | {med(subset, 'wall_us_per_frame'):.1f} | {med(subset, 'real_time_factor'):.2f} | {controls:.0f}% | {audio:.0f}% | {boundary:.0f}% | {pipe:.0f}% | {component:.0f}% |")
lines += [
"",
"The three solver sub-stages are nested inside the solver measurement, so their percentages should not be added to the solver percentage. Their shares also do not sum to 100%: solver setup, CFL preparation, and loop overhead make up the remainder.",
"",
"## Mesh-density scaling",
"",
"Median profiled total cost per frame, aggregated over all four solvers and both repetitions. Cell counts are the actual total across all pipes in the scenario.",
"",
"| Scenario | Density: cells / µs | 80 vs 20 cost |",
"|---|---|---:|",
]
for engine in engines:
row_parts = []
values = {}
for density in densities:
subset = [r for r in rows if r["scenario"] == engine and int(r["cells_per_meter"]) == density]
values[density] = med(subset, "total_ms_per_frame") * 1000
row_parts.append(f"{density}: {subset[0]['total_cells']} / {values[density]:.1f}")
factor = values[80] / values[20] if 20 in values and 80 in values else 0
lines.append(f"| {engine} | {'; '.join(row_parts)} | {factor:.2f}× |")
lines += [
"",
"## Solver cost at 20 cells/m",
"",
"Change in median `total_ms_per_frame` versus Rusanov for the same scenario and mesh density.",
"",
"| Scenario | Rusanov | HLLE | HLLC | MUSCL |",
"|---|---:|---:|---:|---:|",
]
for engine in engines:
subset = [r for r in rows if r["scenario"] == engine and int(r["cells_per_meter"]) == 20]
base = med([r for r in subset if r["solver"] == "rusanov"], "total_ms_per_frame")
values = []
for solver in solvers:
cost = med([r for r in subset if r["solver"] == solver], "total_ms_per_frame")
values.append(f"{(cost / base - 1) * 100:+.0f}%")
lines.append(f"| {engine} | {' | '.join(values)} |")
quality_files = [target.parent / f"quality-{solver}.csv" for solver in solvers]
scalar_files = [target.parent / f"no-simd-{solver}.csv" for solver in solvers]
lazy_files = [target.parent / f"lazy2-{solver}.csv" for solver in solvers]
fused_files = [target.parent / f"fused-{solver}.csv" for solver in solvers]
density50_file = target.parent / "profile-50-baseline.csv"
if density50_file.exists():
density50 = list(csv.DictReader(density50_file.open(newline="", encoding="utf-8")))
lines += [
"",
"## All solvers at 50 cells/m",
"",
"This fixed-density sweep isolates solver cost at your preferred mesh scale. Values are medians across six scenarios and two repetitions; boundary and pipe shares are fractions of inclusive solver time.",
"",
"[50 cells/m raw runs](profile-50-baseline.csv)",
"",
"| Solver | Median µs/frame | Boundary / solver | Pipe / solver |",
"|---|---:|---:|---:|",
]
for solver in solvers:
subset = [r for r in density50 if r["solver"] == solver]
boundary = statistics.median(float(r["boundary_ms_per_frame"]) / float(r["solver_ms_per_frame"]) * 100 for r in subset)
pipe = statistics.median(float(r["pipe_ms_per_frame"]) / float(r["solver_ms_per_frame"]) * 100 for r in subset)
lines.append(f"| {solver} | {med(subset, 'wall_us_per_frame'):.1f} | {boundary:.1f}% | {pipe:.1f}% |")
if all(path.exists() for path in quality_files):
quality_rows = [r for path in quality_files for r in csv.DictReader(path.open(newline="", encoding="utf-8"))]
combined = target.parent / "quality-target-runs.csv"
with combined.open("w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=list(quality_rows[0]))
writer.writeheader()
writer.writerows(quality_rows)
lines += [
"",
"## User-rated quality targets",
"",
"You reported that these densities sound great: Rusanov 70 cells/m, HLLE 60, HLLC 45, and MUSCL 25. The following timings compare those listening targets; the benchmark measures compute and output statistics, not subjective sound quality.",
"",
"[Quality-target raw runs](quality-target-runs.csv)",
"",
"| Solver | Cells/m | Median µs/frame (six scenarios) | Median wall/sim | V8 cells | V8 µs/frame | V8 wall/sim |",
"|---|---:|---:|---:|---:|---:|---:|",
]
for solver in solvers:
subset = [r for r in quality_rows if r["solver"] == solver]
v8 = [r for r in subset if r["scenario"] == "v8-h"]
lines.append(f"| {solver} | {subset[0]['cells_per_meter']} | {med(subset, 'wall_us_per_frame'):.1f} | {med(subset, 'real_time_factor'):.2f} | {v8[0]['total_cells']} | {med(v8, 'wall_us_per_frame'):.1f} | {med(v8, 'real_time_factor'):.2f} |")
lines += [
"",
"MUSCL at its 25 cells/m target had the lowest median compute cost across these cases. On the V8, it was about 30 µs/frame versus about 63 µs for HLLC at 45 cells/m, 68 µs for Rusanov at 70, and 76 µs for HLLE at 60. A wall/sim ratio above 1 means the isolated scenario takes longer than real time on this CPU.",
]
if all(path.exists() for path in scalar_files):
scalar_rows = [r for path in scalar_files for r in csv.DictReader(path.open(newline="", encoding="utf-8"))]
key = lambda r: (r["scenario"], r["solver"], r["repetition"])
fast = {key(r): r for r in quality_rows}
scalar = {key(r): r for r in scalar_rows}
lines += [
"",
"### Compiler SIMD comparison",
"",
"Compared the Release build (GCC auto-vectorization enabled) with `FLUIDSIM_ENABLE_SIMD=OFF`, using the same quality-target matrix and run lengths.",
"",
"| Solver | Median SIMD speedup | Matched audio output |",
"|---|---:|---|",
]
for solver in solvers:
keys = [k for k in fast.keys() & scalar.keys() if k[1] == solver]
ratios = [float(scalar[k]["wall_us_per_frame"]) / float(fast[k]["wall_us_per_frame"]) for k in keys]
max_audio_delta = max(abs(float(fast[k]["audio_checksum"]) - float(scalar[k]["audio_checksum"])) for k in keys)
lines.append(f"| {solver} | {(med([{'v': v} for v in ratios], 'v') - 1) * 100:+.1f}% | audio checksums identical (max delta {max_audio_delta:.0f}) |")
lines += [
"",
"Positive speedup means vectorization was faster. The changes are small and mixed (roughly 0–3% median, within the noise of these short runs), while measured audio RMS, peaks, and checksums matched exactly. The current AoS cell/flux layout is not yielding a large automatic-SIMD gain; an explicit SoA or batched kernel would need a focused prototype and a broader numerical comparison.",
"",
"Paired raw runs by solver: " + ", ".join(f"{solver} [SIMD](quality-{solver}.csv) / [scalar](no-simd-{solver}.csv)" for solver in solvers) + ".",
]
if all(path.exists() for path in lazy_files):
lazy_rows = [r for path in lazy_files for r in csv.DictReader(path.open(newline="", encoding="utf-8"))]
key = lambda r: (r["scenario"], r["solver"], r["repetition"])
before = {key(r): r for r in quality_rows}
after = {key(r): r for r in lazy_rows}
lines += [
"",
"### Lazy solver scratch buffers",
"",
"The pipe now allocates MUSCL reconstruction buffers only for MUSCL, and Roe caches only for HLLE/HLLC. Rusanov no longer allocates either unused cache. On the V8 at each solver's quality target:",
"",
"| Solver | Cells | Scratch before | Scratch after | Saved |",
"|---|---:|---:|---:|---:|",
]
for solver in solvers:
k = next(k for k in before if k[0] == "v8-h" and k[1] == solver)
b = before[k]
a = after[k]
old_bytes = 120 * int(b["total_cells"]) + 20 * int(b["pipe_count"])
new_bytes = int(a["scratch_bytes"])
lines.append(f"| {solver} | {b['total_cells']} | {old_bytes:,} B | {new_bytes:,} B | {old_bytes-new_bytes:,} B ({(old_bytes-new_bytes)/old_bytes*100:.0f}%) |")
keys = before.keys() & after.keys()
ratios = [float(after[k]["wall_us_per_frame"]) / float(before[k]["wall_us_per_frame"]) for k in keys]
max_delta = max(abs(float(after[k]["audio_checksum"]) - float(before[k]["audio_checksum"])) for k in keys)
lines += [
"",
f"The buffer change reduced solver scratch allocation without changing audio checksums (maximum delta {max_delta:.0f}). It did not produce a throughput gain in this short paired comparison (median frame time changed {(med([{'v': v} for v in ratios], 'v')-1)*100:+.1f}% after the change), so keep it as a memory-footprint reduction rather than counting it as a CPU optimization. Scratch stays allocated after a solver is first selected, allowing fast mode switches.",
"",
"Paired raw runs: " + ", ".join(f"{solver} [before](quality-{solver}.csv) / [after](lazy2-{solver}.csv)" for solver in solvers) + ".",
]
if all(path.exists() for path in fused_files):
fused_rows = [r for path in fused_files for r in csv.DictReader(path.open(newline="", encoding="utf-8"))]
fused = {key(r): r for r in fused_rows}
common = after.keys() & fused.keys()
changes = []
for solver in solvers:
keys = [k for k in common if k[1] == solver]
speedups = [float(after[k]["wall_us_per_frame"]) / float(fused[k]["wall_us_per_frame"]) for k in keys]
change = (med([{"v": v} for v in speedups], "v") - 1) * 100
changes.append(f"{solver} {change:+.1f}%")
audio_delta = max(abs(float(after[k]["audio_checksum"]) - float(fused[k]["audio_checksum"])) for k in common)
lines += [
"",
"A separate experiment fused primitive conversion, state validation, and minimum-timestep reduction into one pipe pass. Paired speed changes were " + ", ".join(changes) + f"; audio checksums stayed identical (maximum delta {audio_delta:.0f}). There was no consistent gain, so that loop change was reverted.",
"",
"Fused-pass raw runs: " + ", ".join(f"[{solver}](fused-{solver}.csv)" for solver in solvers) + ".",
]
lines += [
"",
"## CPU versus memory and threading",
"",
"A Windows process sample during the 50 cells/m sweep showed about 103% process CPU (approximately one full logical CPU), 7.1 MiB working set, and 3.8 MiB private bytes. The i5-12600K has 10 cores / 16 logical processors. At 50 cells/m, the V8 has 662 pipe cells; its pipe data plus solver scratch is about 96 KiB before the scratch-buffer reduction and about 60 KiB for Rusanov afterward. This is an inference of CPU-bound execution rather than RAM-capacity pressure: the working data is small and fits in the CPU's private L2 cache, but hardware cache-miss/bandwidth counters were not collected, so L1/L2 traffic is not ruled out.",
"",
"The existing per-step thread benchmark measured the V8's 26 pipes at 50 cells/m: 1 worker took 0.049 s, 2 took 0.188 s, and 4 took 0.267 s for 4,000 steps. Only its artificial 64-pipe × 256-cell case benefited (0.908 s with 1 worker, 0.546 s with 4). Real engine pipes are too small to amortize per-step synchronization; don’t parallelize each pipe step without a persistent task system and a much larger workload.",
"",
"An experimental GCC `-march=native` build crashed in the five-cylinder 50 cells/m profile with Windows access-violation exit `0xC0000005`; it was rejected. The default portable Release build remains the tested build.",
"",
"The focused post-change test run passed 8/8: mesh resolution, junctions, scenario catalog, Rusanov/HLLE/HLLC/MUSCL fluid validation, and outdoor audio.",
]
lines += [
"",
"## Interpretation and limits",
"",
"### Optimization priorities",
"",
"1. For the 50 cells/m listening range, start with pipe advancement for HLLE/HLLC: pipe updates use about 51–57% of solver time. At your target densities, they remain the largest stage for the V8 with Rusanov 70, HLLE 60, and HLLC 45. Reducing unnecessary per-cell passes or improving the cell/flux layout is the most promising CPU work.",
"2. Boundary resolution is still a large second target (about 30–47% of solver time at 50 cells/m), and is the largest stage for the twin and for MUSCL in several cases. Optimize the characteristic-junction root solve only with the acoustic junction benchmarks guarding its behavior.",
"3. Leave component updates and audio rendering for later; they are smaller absolute costs in the larger engines. Keep optimizing the pipe and boundary stages first.",
"",
"- Boundary resolution and pipe advancement trade places as density and solver change. At 50 cells/m, boundaries are largest for Rusanov, while pipe advancement leads for HLLE and HLLC; the V8's junction network still makes boundaries substantial.",
"- Increasing density raises both the number of cells and the number of solver substeps needed to meet the CFL limit. This explains why runtime growth can exceed the cell-count growth alone.",
"- At low densities, controls and audio are a larger fraction of total work because there are few cells to update. Their absolute time stays comparatively small as the mesh grows.",
"- Results are for this Release build and machine, with profiling enabled on alternating frames. Instrumentation adds some timing overhead; the tables are best used for relative comparisons within this run. Very short runs, other compilers, build flags, CPUs, and background load can change absolute timings.",
"- This benchmark isolates one scenario at a time. It excludes desktop rendering, UI work, output-device scheduling, and audio callback buffering. It does not compare sound quality or engine-state accuracy across mesh densities or solver methods.",
"- Two repetitions provide a directional profile, not a high-confidence microbenchmark. Repeat on a quiet machine before using small differences to drive optimization decisions.",
"",
"## Reproduce",
"",
"```powershell",
"cmake --build out/build/windows-gcc --target fluidsim_scenario_profile -j 8",
"out/build/windows-gcc/fluidsim_scenario_profile verification/performance/2026-10-24/profile-runs.csv 5000 22050 2",
"py -3 tools/analyze_scenario_profile.py verification/performance/2026-10-24/profile-runs.csv verification/performance/2026-10-24/profile-report.md",
"```",
"",
]
target.write_text("\n".join(lines), encoding="utf-8")
print(f"Wrote {target}")
if __name__ == "__main__":
main()

View File

@@ -2,6 +2,7 @@
#include "FluidSim/ScenarioCatalog.hpp"
#include "FluidSim/TwoStrokeScenario.hpp"
#include <algorithm>
#include <bit>
#include <cmath>
#include <cstdint>
#include <filesystem>
@@ -24,22 +25,21 @@ public:
if (!out_)
throw std::runtime_error("cannot open WAV: " + path);
out_.write("RIFF", 4);
integer(36 + frames * 4, 4);
integer(36 + frames * 8, 4);
out_.write("WAVEfmt ", 8);
integer(16, 4);
integer(1, 2);
integer(3, 2); // IEEE float preserves quiet diagnostic sources.
integer(2, 2);
integer(44100, 4);
integer(44100 * 4, 4);
integer(4, 2);
integer(16, 2);
integer(44100 * 8, 4);
integer(8, 2);
integer(32, 2);
out_.write("data", 4);
integer(frames * 4, 4);
integer(frames * 8, 4);
}
void sample(AudioFrame f) {
for (float x : {f.left, f.right}) {
const auto v = static_cast<std::int16_t>(std::lround(std::clamp(x, -1.f, 1.f) * 32767));
integer(static_cast<std::uint16_t>(v), 2);
integer(std::bit_cast<std::uint32_t>(x), 4);
}
}
};
@@ -66,7 +66,7 @@ struct Metrics {
}
};
template <class Scenario>
void capture(const std::string &prefix, int frames, float pedal, OutdoorScene scene, TwoStrokeConfig config, PipeSolver solver, float fixed_rpm, float warmup, const ScenarioExample* example=nullptr) {
void capture(const std::string &prefix, int frames, float pedal, OutdoorScene scene, TwoStrokeConfig config, PipeSolver solver, float fixed_rpm, float warmup, const ScenarioExample* example=nullptr, const std::string& solo="all") {
std::unique_ptr<Scenario> s;
if constexpr (std::is_same_v<Scenario, TwoStrokeScenario>) s = std::make_unique<Scenario>(44100, config);
else if constexpr (std::is_same_v<Scenario, FourStrokeScenario>) {
@@ -77,17 +77,34 @@ void capture(const std::string &prefix, int frames, float pedal, OutdoorScene sc
s->set_pipe_solver(solver);
s->throttle = pedal;
s->set_audio_scene(scene);
bool valid_solo=solo=="all";
for(std::size_t i=0;i<OutdoorAudio::source_names.size();++i) {
const bool selected=solo==OutdoorAudio::source_names[i];
valid_solo|=selected;
s->set_audio_source_enabled(i,solo=="all" || selected);
}
if(!valid_solo) throw std::runtime_error("choose --solo all, intake, exhaust, exhaust-2, or valve-contact");
for(int i=0; i<int(std::lround(warmup*44100)); ++i) {
if(fixed_rpm>0) s->crankshaft.omega=fixed_rpm*2*pi/60;
s->process();
}
Wav rendered(prefix + "-rendered.wav", frames), dry(prefix + "-dry.wav", frames),
raw(prefix + "-unfiltered.wav", frames);
Metrics m, d, r;
Metrics m, d, r, tail_metrics;
std::array<std::unique_ptr<Wav>,4> stems;
std::array<Metrics,4> source_metrics;
for(std::size_t i=0;i<stems.size();++i)
stems[i]=std::make_unique<Wav>(prefix+"-"+OutdoorAudio::source_names[i]+".wav",frames);
Wav tail(prefix+"-tail.wav",frames);
int cuts = 0;
for (int i = 0; i < frames; i++) {
if(fixed_rpm>0) s->crankshaft.omega=fixed_rpm*2*pi/60;
auto f = s->process(), df = s->dry_audio(), rf = s->unfiltered_audio();
for(std::size_t j=0;j<stems.size();++j) {
const auto stem=s->source_audio()[j].rendered;
stems[j]->sample(stem); source_metrics[j].add(stem);
}
tail.sample(s->tail_audio()); tail_metrics.add(s->tail_audio());
m.add(f);
d.add(df);
r.add(rf);
@@ -97,16 +114,27 @@ void capture(const std::string &prefix, int frames, float pedal, OutdoorScene sc
if (i % 4410 == 0)
cuts += s->snapshot().fuel_cut;
}
for(std::size_t j=0;j<stems.size();++j) source_metrics[j].print(OutdoorAudio::source_names[j]);
tail_metrics.print("shared tail");
m.print("rendered");
d.print("dry filtered");
r.print("unfiltered spatial");
std::uint64_t repairs=0, fallbacks=0;
for(const auto& pipe:s->pipes) { repairs+=pipe.positivity_repairs; fallbacks+=pipe.hllc_fallbacks; }
if constexpr (std::is_same_v<Scenario, FourStrokeScenario> || std::is_base_of_v<FourStrokeScenario,Scenario>) {
const auto diagnostics=s->snapshot();
std::cout<<"junction_model="<<s->junction_config.model<<" junction_fallbacks="<<diagnostics.junction_fallbacks
<<" junction_limited_fluxes="<<diagnostics.junction_limited_fluxes<<" junction_iterations="<<diagnostics.junction_iterations<<" limited_fluxes="<<diagnostics.boundary_limited_fluxes<<'\n';
}
std::cout<<"pipe_positivity_repairs="<<repairs<<" hllc_fallbacks="<<fallbacks<<'\n';
std::cout << "fuel-cut checkpoints=" << cuts << " final_rpm=" << s->snapshot().rpm
<< " scene=" << scene_name(scene) << '\n';
}
} // namespace
int main(int argc, char **argv) {
try {
std::string example_id;
std::string example_id, solo="all", junction_model;
float junction_loss=-1;
std::string scenario = "twin", prefix = "build/audio/twin", scene = "wall";
float seconds = 6, pedal = 1, fixed_rpm = 0, warmup = 0;
TwoStrokeConfig config;
@@ -116,7 +144,16 @@ int main(int argc, char **argv) {
if (i + 1 >= argc)
throw std::runtime_error("missing option value");
std::string value = argv[++i];
if(key=="--example") example_id=value;
if(key=="--junctions") {
if(value!="volume" && value!="characteristic" && value!="legacy") throw std::runtime_error("choose volume, characteristic, or legacy junctions");
junction_model=value;
}
else if(key=="--junction-loss") {
junction_loss=std::stof(value);
if(!std::isfinite(junction_loss) || junction_loss<0 || junction_loss>10) throw std::runtime_error("junction loss must be 0..10");
}
else if(key=="--solo") solo=value;
else if(key=="--example") example_id=value;
else if (key == "--scenario")
scenario = value;
else if (key == "--out")
@@ -160,21 +197,30 @@ int main(int argc, char **argv) {
if (!std::filesystem::path(prefix).parent_path().empty())
std::filesystem::create_directories(std::filesystem::path(prefix).parent_path());
const int frames = int(std::lround(seconds * 44100));
const auto examples=scenario_examples();
auto examples=scenario_examples();
if(!junction_model.empty() || junction_loss>=0) {
if(example_id.empty()) example_id=scenario=="v8"?"v8-h":scenario=="b205"?"saab-straight":scenario=="twin"?"twin":"yz-racing";
for(auto& example:examples) {
if(!junction_model.empty()) example.junctions.model=junction_model;
if(junction_loss>=0) example.junctions.turning_loss=junction_loss;
}
}
if(!example_id.empty()) {
const auto example=std::find_if(examples.begin(),examples.end(),[&](const auto& e){return e.id==example_id;});
if(example==examples.end()) throw std::runtime_error("unknown example: "+example_id);
if(example->kind==ScenarioKind::TwoStroke && (!junction_model.empty() || junction_loss>=0))
throw std::runtime_error("two-stroke exhaust has no configurable pipe junctions");
if(example->kind==ScenarioKind::TwoStroke)
capture<TwoStrokeScenario>(prefix,frames,pedal,selected,example->two,solver,fixed_rpm,warmup);
else capture<FourStrokeScenario>(prefix,frames,pedal,selected,config,solver,fixed_rpm,warmup,&*example);
capture<TwoStrokeScenario>(prefix,frames,pedal,selected,example->two,solver,fixed_rpm,warmup,nullptr,solo);
else capture<FourStrokeScenario>(prefix,frames,pedal,selected,config,solver,fixed_rpm,warmup,&*example,solo);
} else if (scenario == "twin")
capture<FourStrokeTwinScenario>(prefix, frames, pedal, selected, config, solver, fixed_rpm, warmup);
capture<FourStrokeTwinScenario>(prefix, frames, pedal, selected, config, solver, fixed_rpm, warmup,nullptr,solo);
else if (scenario == "b205")
capture<SaabB205Scenario>(prefix, frames, pedal, selected, config, solver, fixed_rpm, warmup);
capture<SaabB205Scenario>(prefix, frames, pedal, selected, config, solver, fixed_rpm, warmup,nullptr,solo);
else if (scenario == "two-stroke")
capture<TwoStrokeScenario>(prefix, frames, pedal, selected, config, solver, fixed_rpm, warmup);
capture<TwoStrokeScenario>(prefix, frames, pedal, selected, config, solver, fixed_rpm, warmup,nullptr,solo);
else if (scenario == "v8")
capture<AmericanV8Scenario>(prefix, frames, pedal, selected, config, solver, fixed_rpm, warmup);
capture<AmericanV8Scenario>(prefix, frames, pedal, selected, config, solver, fixed_rpm, warmup,nullptr,solo);
else
throw std::runtime_error("choose twin, two-stroke, b205 or v8");
} catch (const std::exception &e) {

59
tools/audio_preview.py Normal file
View File

@@ -0,0 +1,59 @@
"""Create peak-normalized listening copies of PCM16 or float32 capture WAVs.
Usage: py tools/audio_preview.py out/audio/single-450-baseline
Original files retain calibration; preview levels must not be compared by source.
"""
import argparse
import math
from pathlib import Path
import struct
import wave
def preview(path, destination):
data = path.read_bytes()
if data[:4] != b"RIFF" or data[8:12] != b"WAVE":
raise ValueError(f"Not a WAV: {path}")
offset = 12
fmt = payload = None
while offset + 8 <= len(data):
tag, size = struct.unpack_from("<4sI", data, offset)
chunk = data[offset + 8:offset + 8 + size]
if tag == b"fmt ":
fmt = struct.unpack_from("<HHIIHH", chunk)
elif tag == b"data":
payload = chunk
offset += 8 + size + size % 2
if fmt is None or payload is None:
raise ValueError(f"Missing WAV chunks: {path}")
encoding, channels, rate, _, _, bits = fmt
if (encoding, bits) == (3, 32):
values = [x[0] for x in struct.iter_unpack("<f", payload)]
elif (encoding, bits) == (1, 16):
values = [x[0] / 32768 for x in struct.iter_unpack("<h", payload)]
else:
raise ValueError(f"Unsupported WAV format: {path}")
if not all(math.isfinite(x) for x in values):
raise ValueError(f"Nonfinite audio: {path}")
peak = max(map(abs, values), default=0)
if peak == 0:
print(f"{path.name}: silent; no preview")
return
gain = 0.8 / peak
destination.mkdir(parents=True, exist_ok=True)
target = destination / (path.stem + "-preview.wav")
with wave.open(str(target), "wb") as out:
out.setparams((channels, 2, rate, 0, "NONE", "not compressed"))
out.writeframes(b"".join(struct.pack("<h", round(x * gain * 32767)) for x in values))
print(f"{target.name}: gain {20 * math.log10(gain):+.1f} dB")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("prefix", type=Path)
args = parser.parse_args()
paths = sorted(args.prefix.parent.glob(args.prefix.name + "-*.wav"))
if not paths:
parser.error("no matching capture WAVs")
for path in paths:
preview(path, args.prefix.parent / "previews")

View File

@@ -0,0 +1,9 @@
#include "FluidSim/ScenarioCatalog.hpp"
#include <iostream>
int main(int argc,char** argv) try {
const auto directory=argc>1?std::filesystem::path(argv[1]):fluidsim::port::engine_directory();
const bool relayout=argc>2&&std::string(argv[2])=="--relayout";
if(std::filesystem::is_regular_file(directory)){fluidsim::port::migrate_engine_geometry(directory,relayout);return 0;}
const auto definitions=fluidsim::port::load_engine_directory(directory);
for(const auto& definition:definitions){fluidsim::port::migrate_engine_geometry(definition.source_path,relayout);std::cout<<definition.id<<'\n';}
} catch(const std::exception& e){std::cerr<<e.what()<<'\n';return 1;}

View File

@@ -0,0 +1,91 @@
#include "FluidSim/Scenario.hpp"
#include "FluidSim/ScenarioCatalog.hpp"
#include "FluidSim/TwoStrokeScenario.hpp"
#include <chrono>
#include <algorithm>
#include <cmath>
#include <cmath>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <string>
#include <vector>
using namespace fluidsim::port;
using Clock = std::chrono::steady_clock;
template<class Scenario>
void measure_case(const ScenarioExample& definition, int density, PipeSolver solver,
int repetition, int warmup_frames, int measured_frames, Scenario& scenario, std::ostream& out) {
scenario.set_pipe_solver(solver);
float target_rpm=7000.f;
if constexpr (std::is_same_v<Scenario, FourStrokeScenario>) {
scenario.set_controls(.55f, .1f, 0.f);
target_rpm=definition.engine.initial_rpm > 0 ? definition.engine.initial_rpm : 4500.f;
} else {
scenario.set_controls(.55f, .1f, 0.f);
}
for (int i=0;i<warmup_frames;++i) { scenario.crankshaft.omega=target_rpm*2*pi/60; scenario.process(); }
scenario.enable_profiling(true);
const auto begin=Clock::now();
double audio_energy=0,audio_checksum=0;
float audio_peak=0;
for (int i=0;i<measured_frames;++i) {
scenario.crankshaft.omega=target_rpm*2*pi/60;
const auto frame=scenario.process();
if(!std::isfinite(frame.left)||!std::isfinite(frame.right)) throw std::runtime_error("nonfinite profiled audio");
audio_energy+=double(frame.left)*frame.left+double(frame.right)*frame.right;
audio_peak=std::max({audio_peak,std::abs(frame.left),std::abs(frame.right)});
audio_checksum+=double(frame.left)*(i+1)+double(frame.right)*(i%251+1);
}
const double wall=std::chrono::duration<double>(Clock::now()-begin).count();
const auto& profile=scenario.profile_totals();
std::size_t cells=0;
std::size_t scratch_bytes=0;
for(const auto& pipe:scenario.pipes) { cells+=pipe.cells.size();scratch_bytes+=pipe.scratch_bytes(); }
std::uint64_t repairs=0,fallbacks=0;
for(const auto& pipe:scenario.pipes) { repairs+=pipe.positivity_repairs; fallbacks+=pipe.hllc_fallbacks; }
double ms[7]{};
for(int i=0;i<7;++i) ms[i]=profile.sampled_steps ? profile.nanoseconds[i]/1e6/profile.sampled_steps : 0;
out<<definition.id<<','<<(definition.kind==ScenarioKind::TwoStroke?"two-stroke":"four-stroke")<<','<<density<<','
<<pipe_solver_name(solver)<<','<<repetition<<','<<scenario.pipes.size()<<','<<cells<<','<<measured_frames<<','
<<profile.sampled_steps<<','<<std::setprecision(8)<<wall<<','<<wall/measured_frames*1e6<<','<<wall/(measured_frames/44100.0)<<','
<<scratch_bytes<<',';
for(int i=0;i<7;++i) out<<ms[i]<<',';
out<<profile_percentile_ms(profile,.50)<<','<<profile_percentile_ms(profile,.95)<<','<<repairs<<','<<fallbacks<<','
<<std::sqrt(audio_energy/(2*measured_frames))<<','<<audio_peak<<','<<audio_checksum<<'\n';
}
int main(int argc,char** argv) {
const std::string output=argc>1?argv[1]:"scenario-profile.csv";
const int warmup=argc>2?std::stoi(argv[2]):5000;
const int frames=argc>3?std::stoi(argv[3]):22050;
const int reps=argc>4?std::stoi(argv[4]):2;
std::vector<std::string> ids={"single-450","twin","five","v8-h","yz-fat","saab-chambered"};
if(argc>6 && std::string(argv[6])!="all") ids={argv[6]};
const std::vector<int> densities=argc>5?std::vector<int>{std::stoi(argv[5])}:
std::vector<int>{10,20,40,50,80};
std::vector<PipeSolver> solvers={PipeSolver::Rusanov,PipeSolver::Hlle,PipeSolver::Hllc,PipeSolver::Muscl};
if(argc>7) solvers={parse_pipe_solver(argv[7])};
std::ofstream file(output);
if(!file) { std::cerr<<"cannot open output: "<<output<<'\n'; return 2; }
file<<"scenario,kind,cells_per_meter,solver,repetition,pipe_count,total_cells,frames,sampled_frames,wall_seconds,wall_us_per_frame,real_time_factor,scratch_bytes,total_ms_per_frame,controls_ms_per_frame,solver_ms_per_frame,boundary_ms_per_frame,pipe_ms_per_frame,components_ms_per_frame,audio_ms_per_frame,p50_frame_ms,p95_frame_ms,positivity_repairs,hllc_fallbacks,audio_rms,audio_peak,audio_checksum\n";
int completed=0, total=static_cast<int>(ids.size()*densities.size()*solvers.size()*reps);
for(const auto& id:ids) for(int density:densities) for(auto solver:solvers) for(int rep=1;rep<=reps;++rep) {
const auto& def=scenario_example(id);
auto config=def;
config.engine.cells_per_meter=static_cast<float>(density);
config.two.cells_per_meter=static_cast<float>(density);
if(def.kind==ScenarioKind::TwoStroke) {
config.two.exhaust_cell_length=1.0f/density;
TwoStrokeScenario scenario(44100,config.two);
measure_case(def,density,solver,rep,warmup,frames,scenario,file);
} else {
FourStrokeScenario scenario(44100,config);
measure_case(def,density,solver,rep,warmup,frames,scenario,file);
}
file.flush();
std::cerr<<"completed "<<++completed<<"/"<<total<<" "<<id<<" density="<<density<<" solver="<<pipe_solver_name(solver)<<" rep="<<rep<<'\n';
}
}

View File

@@ -0,0 +1,185 @@
engine,pipe_index,role,previous_length_mm,geometry_length_mm
big-carb,9,primaries,750.0,217.248
big-carb,10,primaries,750.0,162.56
big-carb,11,primaries,750.0,217.248
big-carb,12,primaries,750.0,162.56
big-carb,13,primaries,750.0,278.871
big-carb,14,primaries,750.0,278.871
big-carb,15,primaries,750.0,170.011
big-carb,16,primaries,750.0,170.011
big-carb,17,secondaries,400.0,382.761
big-carb,18,secondaries,400.0,121.48
big-carb,19,secondaries,400.0,317.058
big-carb,20,secondaries,400.0,186.27
big-carb,21,bank_collectors,550.0,527.514
big-carb,22,bank_collectors,550.0,527.514
busso,7,primaries,650.0,362.466
busso,8,primaries,650.0,315.14
busso,9,primaries,650.0,300.251
busso,10,primaries,650.0,223.793
busso,11,primaries,650.0,146.864
busso,12,primaries,650.0,125.698
ej20,5,primaries,650.0,458.345
ej20,6,primaries,650.0,432.05
ej20,7,primaries,650.0,139.544
ej20,8,primaries,650.0,56.452
f20,5,primary_1,520.0,235.578
f20,6,primary_2,585.0,235.578
f20,7,primary_3,650.0,250.978
f20,8,primary_4,520.0,250.978
f20,10,secondary_10,420.0,277.916
f20,11,secondary_11,540.0,66.47
five,6,primary_1,520.0,257.205
five,7,primary_2,585.0,234.66
five,8,primary_3,650.0,257.205
five,9,primary_4,520.0,255.345
five,10,primary_5,585.0,255.345
five,12,secondary_12,420.0,272.078
five,13,secondary_13,540.0,20.983
flat6,7,primaries,650.0,453.208
flat6,8,primaries,650.0,411.807
flat6,9,primaries,650.0,399.573
flat6,10,primaries,650.0,192.691
flat6,11,primaries,650.0,68.392
flat6,12,primaries,650.0,44.961
ltwin,3,primaries,650.0,361.659
ltwin,4,primaries,650.0,121.948
rb26,7,primary_1,520.0,230.146
rb26,8,primary_2,585.0,202.663
rb26,9,primary_3,650.0,230.146
rb26,10,primary_4,520.0,245.586
rb26,11,primary_5,585.0,220.149
rb26,12,primary_6,650.0,245.586
rb26,14,secondary_14,420.0,327.721
rb26,15,secondary_15,540.0,12.434
saab-chambered,5,primaries,280.0,351.447
saab-chambered,6,primaries,280.0,280.087
saab-chambered,7,primaries,280.0,241.712
saab-chambered,8,primaries,280.0,244.991
saab-expansion,5,primaries,280.0,351.447
saab-expansion,6,primaries,280.0,280.087
saab-expansion,7,primaries,280.0,241.712
saab-expansion,8,primaries,280.0,244.991
saab-open,5,primaries,280.0,351.447
saab-open,6,primaries,280.0,280.087
saab-open,7,primaries,280.0,241.712
saab-open,8,primaries,280.0,244.991
saab-straight,5,primaries,280.0,351.447
saab-straight,6,primaries,280.0,280.087
saab-straight,7,primaries,280.0,241.712
saab-straight,8,primaries,280.0,244.991
single-450,2,primaries,650.0,218.917
single-650,2,primaries,650.0,269.186
six-long,7,primary_1,520.0,258.773
six-long,8,primary_2,585.0,233.953
six-long,9,primary_3,650.0,258.773
six-long,10,primary_4,520.0,272.777
six-long,11,primary_5,585.0,249.455
six-long,12,primary_6,650.0,272.777
six-long,14,secondary_14,420.0,327.721
six-long,15,secondary_15,540.0,12.434
triple,4,primary_1,520.0,213.136
triple,5,primary_2,585.0,187.741
triple,6,primary_3,650.0,213.136
triple,8,secondary_8,420.0,176.671
twin-270,3,primaries,650.0,243.295
twin-270,4,primaries,650.0,204.507
twin,3,primaries,200.0,249.312
twin,4,primaries,200.0,208.232
v10,11,primary_1,520.0,266.257
v10,12,primary_2,585.0,167.815
v10,13,primary_3,650.0,128.915
v10,14,primary_4,520.0,167.815
v10,15,primary_5,585.0,266.257
v10,16,primary_6,650.0,266.257
v10,17,primary_7,520.0,167.815
v10,18,primary_8,585.0,128.915
v10,19,primary_9,650.0,167.815
v10,20,primary_10,520.0,266.257
v10,23,secondary_23,420.0,169.159
v10,24,secondary_24,540.0,169.159
v12,13,primary_1,520.0,267.408
v12,14,primary_2,585.0,182.128
v12,15,primary_3,650.0,125.028
v12,16,primary_4,520.0,125.028
v12,17,primary_5,585.0,182.128
v12,18,primary_6,650.0,267.408
v12,19,primary_7,520.0,267.408
v12,20,primary_8,585.0,182.128
v12,21,primary_9,650.0,125.028
v12,22,primary_10,520.0,125.028
v12,23,primary_11,585.0,182.128
v12,24,primary_12,650.0,267.408
v12,27,secondary_27,420.0,168.746
v12,28,secondary_28,540.0,168.746
v8-dual,9,primaries,750.0,215.493
v8-dual,10,primaries,750.0,164.663
v8-dual,11,primaries,750.0,215.493
v8-dual,12,primaries,750.0,164.663
v8-dual,13,primaries,750.0,267.946
v8-dual,14,primaries,750.0,267.946
v8-dual,15,primaries,750.0,168.157
v8-dual,16,primaries,750.0,168.157
v8-dual,17,secondaries,400.0,344.902
v8-dual,18,secondaries,400.0,109.109
v8-dual,19,secondaries,400.0,285.577
v8-dual,20,secondaries,400.0,167.554
v8-dual,21,bank_collectors,550.0,575.518
v8-dual,22,bank_collectors,550.0,575.518
v8-dual,25,crossover,780.0,816.84
v8-h,9,primaries,750.0,215.493
v8-h,10,primaries,750.0,164.663
v8-h,11,primaries,750.0,215.493
v8-h,12,primaries,750.0,164.663
v8-h,13,primaries,750.0,267.946
v8-h,14,primaries,750.0,267.946
v8-h,15,primaries,750.0,168.157
v8-h,16,primaries,750.0,168.157
v8-h,17,secondaries,400.0,344.902
v8-h,18,secondaries,400.0,109.109
v8-h,19,secondaries,400.0,285.577
v8-h,20,secondaries,400.0,167.554
v8-h,21,bank_collectors,550.0,558.179
v8-h,22,bank_collectors,550.0,558.179
v8-large,9,primaries,750.0,215.493
v8-large,10,primaries,750.0,164.663
v8-large,11,primaries,750.0,215.493
v8-large,12,primaries,750.0,164.663
v8-large,13,primaries,750.0,267.946
v8-large,14,primaries,750.0,267.946
v8-large,15,primaries,750.0,168.157
v8-large,16,primaries,750.0,168.157
v8-large,17,secondaries,400.0,344.902
v8-large,18,secondaries,400.0,109.109
v8-large,19,secondaries,400.0,285.577
v8-large,20,secondaries,400.0,167.554
v8-large,21,bank_collectors,550.0,558.179
v8-large,22,bank_collectors,550.0,558.179
v8-long,9,primaries,900.0,410.859
v8-long,10,primaries,900.0,329.5
v8-long,11,primaries,900.0,410.859
v8-long,12,primaries,900.0,329.5
v8-long,13,primaries,900.0,467.152
v8-long,14,primaries,900.0,467.152
v8-long,15,primaries,900.0,298.245
v8-long,16,primaries,900.0,298.245
v8-long,17,secondaries,400.0,344.902
v8-long,18,secondaries,400.0,109.109
v8-long,19,secondaries,400.0,285.577
v8-long,20,secondaries,400.0,167.554
v8-long,21,bank_collectors,550.0,558.179
v8-long,22,bank_collectors,550.0,558.179
v8-short,9,primaries,600.0,173.74
v8-short,10,primaries,600.0,140.089
v8-short,11,primaries,600.0,173.74
v8-short,12,primaries,600.0,140.089
v8-short,13,primaries,600.0,223.501
v8-short,14,primaries,600.0,223.501
v8-short,15,primaries,600.0,157.11
v8-short,16,primaries,600.0,157.11
v8-short,17,secondaries,400.0,344.902
v8-short,18,secondaries,400.0,109.109
v8-short,19,secondaries,400.0,285.577
v8-short,20,secondaries,400.0,167.554
v8-short,21,bank_collectors,550.0,558.179
v8-short,22,bank_collectors,550.0,558.179
1 engine pipe_index role previous_length_mm geometry_length_mm
2 big-carb 9 primaries 750.0 217.248
3 big-carb 10 primaries 750.0 162.56
4 big-carb 11 primaries 750.0 217.248
5 big-carb 12 primaries 750.0 162.56
6 big-carb 13 primaries 750.0 278.871
7 big-carb 14 primaries 750.0 278.871
8 big-carb 15 primaries 750.0 170.011
9 big-carb 16 primaries 750.0 170.011
10 big-carb 17 secondaries 400.0 382.761
11 big-carb 18 secondaries 400.0 121.48
12 big-carb 19 secondaries 400.0 317.058
13 big-carb 20 secondaries 400.0 186.27
14 big-carb 21 bank_collectors 550.0 527.514
15 big-carb 22 bank_collectors 550.0 527.514
16 busso 7 primaries 650.0 362.466
17 busso 8 primaries 650.0 315.14
18 busso 9 primaries 650.0 300.251
19 busso 10 primaries 650.0 223.793
20 busso 11 primaries 650.0 146.864
21 busso 12 primaries 650.0 125.698
22 ej20 5 primaries 650.0 458.345
23 ej20 6 primaries 650.0 432.05
24 ej20 7 primaries 650.0 139.544
25 ej20 8 primaries 650.0 56.452
26 f20 5 primary_1 520.0 235.578
27 f20 6 primary_2 585.0 235.578
28 f20 7 primary_3 650.0 250.978
29 f20 8 primary_4 520.0 250.978
30 f20 10 secondary_10 420.0 277.916
31 f20 11 secondary_11 540.0 66.47
32 five 6 primary_1 520.0 257.205
33 five 7 primary_2 585.0 234.66
34 five 8 primary_3 650.0 257.205
35 five 9 primary_4 520.0 255.345
36 five 10 primary_5 585.0 255.345
37 five 12 secondary_12 420.0 272.078
38 five 13 secondary_13 540.0 20.983
39 flat6 7 primaries 650.0 453.208
40 flat6 8 primaries 650.0 411.807
41 flat6 9 primaries 650.0 399.573
42 flat6 10 primaries 650.0 192.691
43 flat6 11 primaries 650.0 68.392
44 flat6 12 primaries 650.0 44.961
45 ltwin 3 primaries 650.0 361.659
46 ltwin 4 primaries 650.0 121.948
47 rb26 7 primary_1 520.0 230.146
48 rb26 8 primary_2 585.0 202.663
49 rb26 9 primary_3 650.0 230.146
50 rb26 10 primary_4 520.0 245.586
51 rb26 11 primary_5 585.0 220.149
52 rb26 12 primary_6 650.0 245.586
53 rb26 14 secondary_14 420.0 327.721
54 rb26 15 secondary_15 540.0 12.434
55 saab-chambered 5 primaries 280.0 351.447
56 saab-chambered 6 primaries 280.0 280.087
57 saab-chambered 7 primaries 280.0 241.712
58 saab-chambered 8 primaries 280.0 244.991
59 saab-expansion 5 primaries 280.0 351.447
60 saab-expansion 6 primaries 280.0 280.087
61 saab-expansion 7 primaries 280.0 241.712
62 saab-expansion 8 primaries 280.0 244.991
63 saab-open 5 primaries 280.0 351.447
64 saab-open 6 primaries 280.0 280.087
65 saab-open 7 primaries 280.0 241.712
66 saab-open 8 primaries 280.0 244.991
67 saab-straight 5 primaries 280.0 351.447
68 saab-straight 6 primaries 280.0 280.087
69 saab-straight 7 primaries 280.0 241.712
70 saab-straight 8 primaries 280.0 244.991
71 single-450 2 primaries 650.0 218.917
72 single-650 2 primaries 650.0 269.186
73 six-long 7 primary_1 520.0 258.773
74 six-long 8 primary_2 585.0 233.953
75 six-long 9 primary_3 650.0 258.773
76 six-long 10 primary_4 520.0 272.777
77 six-long 11 primary_5 585.0 249.455
78 six-long 12 primary_6 650.0 272.777
79 six-long 14 secondary_14 420.0 327.721
80 six-long 15 secondary_15 540.0 12.434
81 triple 4 primary_1 520.0 213.136
82 triple 5 primary_2 585.0 187.741
83 triple 6 primary_3 650.0 213.136
84 triple 8 secondary_8 420.0 176.671
85 twin-270 3 primaries 650.0 243.295
86 twin-270 4 primaries 650.0 204.507
87 twin 3 primaries 200.0 249.312
88 twin 4 primaries 200.0 208.232
89 v10 11 primary_1 520.0 266.257
90 v10 12 primary_2 585.0 167.815
91 v10 13 primary_3 650.0 128.915
92 v10 14 primary_4 520.0 167.815
93 v10 15 primary_5 585.0 266.257
94 v10 16 primary_6 650.0 266.257
95 v10 17 primary_7 520.0 167.815
96 v10 18 primary_8 585.0 128.915
97 v10 19 primary_9 650.0 167.815
98 v10 20 primary_10 520.0 266.257
99 v10 23 secondary_23 420.0 169.159
100 v10 24 secondary_24 540.0 169.159
101 v12 13 primary_1 520.0 267.408
102 v12 14 primary_2 585.0 182.128
103 v12 15 primary_3 650.0 125.028
104 v12 16 primary_4 520.0 125.028
105 v12 17 primary_5 585.0 182.128
106 v12 18 primary_6 650.0 267.408
107 v12 19 primary_7 520.0 267.408
108 v12 20 primary_8 585.0 182.128
109 v12 21 primary_9 650.0 125.028
110 v12 22 primary_10 520.0 125.028
111 v12 23 primary_11 585.0 182.128
112 v12 24 primary_12 650.0 267.408
113 v12 27 secondary_27 420.0 168.746
114 v12 28 secondary_28 540.0 168.746
115 v8-dual 9 primaries 750.0 215.493
116 v8-dual 10 primaries 750.0 164.663
117 v8-dual 11 primaries 750.0 215.493
118 v8-dual 12 primaries 750.0 164.663
119 v8-dual 13 primaries 750.0 267.946
120 v8-dual 14 primaries 750.0 267.946
121 v8-dual 15 primaries 750.0 168.157
122 v8-dual 16 primaries 750.0 168.157
123 v8-dual 17 secondaries 400.0 344.902
124 v8-dual 18 secondaries 400.0 109.109
125 v8-dual 19 secondaries 400.0 285.577
126 v8-dual 20 secondaries 400.0 167.554
127 v8-dual 21 bank_collectors 550.0 575.518
128 v8-dual 22 bank_collectors 550.0 575.518
129 v8-dual 25 crossover 780.0 816.84
130 v8-h 9 primaries 750.0 215.493
131 v8-h 10 primaries 750.0 164.663
132 v8-h 11 primaries 750.0 215.493
133 v8-h 12 primaries 750.0 164.663
134 v8-h 13 primaries 750.0 267.946
135 v8-h 14 primaries 750.0 267.946
136 v8-h 15 primaries 750.0 168.157
137 v8-h 16 primaries 750.0 168.157
138 v8-h 17 secondaries 400.0 344.902
139 v8-h 18 secondaries 400.0 109.109
140 v8-h 19 secondaries 400.0 285.577
141 v8-h 20 secondaries 400.0 167.554
142 v8-h 21 bank_collectors 550.0 558.179
143 v8-h 22 bank_collectors 550.0 558.179
144 v8-large 9 primaries 750.0 215.493
145 v8-large 10 primaries 750.0 164.663
146 v8-large 11 primaries 750.0 215.493
147 v8-large 12 primaries 750.0 164.663
148 v8-large 13 primaries 750.0 267.946
149 v8-large 14 primaries 750.0 267.946
150 v8-large 15 primaries 750.0 168.157
151 v8-large 16 primaries 750.0 168.157
152 v8-large 17 secondaries 400.0 344.902
153 v8-large 18 secondaries 400.0 109.109
154 v8-large 19 secondaries 400.0 285.577
155 v8-large 20 secondaries 400.0 167.554
156 v8-large 21 bank_collectors 550.0 558.179
157 v8-large 22 bank_collectors 550.0 558.179
158 v8-long 9 primaries 900.0 410.859
159 v8-long 10 primaries 900.0 329.5
160 v8-long 11 primaries 900.0 410.859
161 v8-long 12 primaries 900.0 329.5
162 v8-long 13 primaries 900.0 467.152
163 v8-long 14 primaries 900.0 467.152
164 v8-long 15 primaries 900.0 298.245
165 v8-long 16 primaries 900.0 298.245
166 v8-long 17 secondaries 400.0 344.902
167 v8-long 18 secondaries 400.0 109.109
168 v8-long 19 secondaries 400.0 285.577
169 v8-long 20 secondaries 400.0 167.554
170 v8-long 21 bank_collectors 550.0 558.179
171 v8-long 22 bank_collectors 550.0 558.179
172 v8-short 9 primaries 600.0 173.74
173 v8-short 10 primaries 600.0 140.089
174 v8-short 11 primaries 600.0 173.74
175 v8-short 12 primaries 600.0 140.089
176 v8-short 13 primaries 600.0 223.501
177 v8-short 14 primaries 600.0 223.501
178 v8-short 15 primaries 600.0 157.11
179 v8-short 16 primaries 600.0 157.11
180 v8-short 17 secondaries 400.0 344.902
181 v8-short 18 secondaries 400.0 109.109
182 v8-short 19 secondaries 400.0 285.577
183 v8-short 20 secondaries 400.0 167.554
184 v8-short 21 bank_collectors 550.0 558.179
185 v8-short 22 bank_collectors 550.0 558.179

View File

@@ -0,0 +1,25 @@
scenario,kind,cells_per_meter,solver,repetition,pipe_count,total_cells,frames,sampled_frames,wall_seconds,wall_us_per_frame,real_time_factor,total_ms_per_frame,controls_ms_per_frame,solver_ms_per_frame,boundary_ms_per_frame,pipe_ms_per_frame,components_ms_per_frame,audio_ms_per_frame,p50_frame_ms,p95_frame_ms,positivity_repairs,hllc_fallbacks
single-450,four-stroke,50,rusanov,1,4,131,5000,2500,0.0221766,4.43532,0.19559761,0.0045904,0.00029724,0.00371408,0.0013946,0.00172644,7.936e-05,0.00047056,0.0045,0.0045,0,0
single-450,four-stroke,50,hlle,1,4,131,5000,2500,0.0298873,5.97746,0.26360599,0.00608568,0.00031204,0.00520532,0.00142852,0.00296664,8.048e-05,0.00046164,0.0055,0.0065,0,0
single-450,four-stroke,50,hllc,1,4,131,5000,2500,0.0324929,6.49858,0.28658738,0.006661,0.00030784,0.00577968,0.00142544,0.00353328,8.296e-05,0.0004676,0.0065,0.0065,0,0
single-450,four-stroke,50,muscl,1,4,131,5000,2500,0.0427053,8.54106,0.37666075,0.0086688,0.00030336,0.00779792,0.00297476,0.00399228,8.168e-05,0.00046232,0.0085,0.0085,0,0
twin,four-stroke,50,rusanov,1,6,55,5000,2500,0.0234109,4.68218,0.20648414,0.0048414,0.00036636,0.003913,0.0025476,0.00091236,0.0001218,0.00045752,0.0045,0.0075,0,0
twin,four-stroke,50,hlle,1,6,55,5000,2500,0.0264447,5.28894,0.23324225,0.0054552,0.00036908,0.00453396,0.0024906,0.00148312,0.0001244,0.00045072,0.0045,0.0085,0,0
twin,four-stroke,50,hllc,1,6,55,5000,2500,0.0286684,5.73368,0.25285529,0.00591868,0.00037288,0.00498284,0.00260644,0.0018032,0.00012824,0.000457,0.0045,0.0095,0,0
twin,four-stroke,50,muscl,1,6,55,5000,2500,0.0337112,6.74224,0.29733278,0.00693444,0.00038912,0.00599156,0.00332472,0.00209188,0.0001288,0.00045044,0.0055,0.0105,0,0
five,four-stroke,50,rusanov,1,14,343,5000,2500,0.0895132,17.90264,0.78950642,0.01815772,0.00081088,0.01673892,0.00846708,0.0059292,0.00034728,0.0004972,0.0205,0.0215,0,0
five,four-stroke,50,hlle,1,14,343,5000,2500,0.133426,26.6852,1.1768173,0.02675072,0.0009624,0.02510568,0.00925144,0.01224052,0.00039596,0.0005586,0.0295,0.0325,0,0
five,four-stroke,50,hllc,1,14,343,5000,2500,0.172315,34.463,1.5198183,0.03039436,0.00094916,0.02872924,0.00944852,0.01554452,0.00043216,0.00059528,0.0315,0.0515,0,0
five,four-stroke,50,muscl,1,14,343,5000,2500,0.2017581,40.35162,1.7795064,0.04055332,0.0009302,0.03896732,0.01783364,0.01755344,0.00041908,0.00053652,0.0445,0.0475,0,0
v8-h,four-stroke,50,rusanov,1,26,662,5000,2500,0.1798743,35.97486,1.5864913,0.03607572,0.00136824,0.03395516,0.01562336,0.01415284,0.00061336,0.00063232,0.0425,0.0455,0,0
v8-h,four-stroke,50,hlle,1,26,662,5000,2500,0.2461426,49.22852,2.1709777,0.04931684,0.00139948,0.0470782,0.01617044,0.02484084,0.00063652,0.00071104,0.0575,0.0645,0,0
v8-h,four-stroke,50,hllc,1,26,662,5000,2500,0.2689101,53.78202,2.3717871,0.054181,0.00135556,0.05198136,0.01624432,0.02972492,0.00060668,0.00071292,0.0635,0.0835,0,0
v8-h,four-stroke,50,muscl,1,26,662,5000,2500,0.340461,68.0922,3.002866,0.06829276,0.00127412,0.06627316,0.02883468,0.03174308,0.00064368,0.00062556,0.0835,0.0905,0,0
yz-fat,two-stroke,50,rusanov,1,4,70,5000,2500,0.0563677,11.27354,0.49716311,0.01165048,0.00031712,0.01087784,0.00549588,0.00386268,0.00028332,0.00035296,0.0115,0.0135,0,0
yz-fat,two-stroke,50,hlle,1,4,70,5000,2500,0.0712716,14.25432,0.62861551,0.01464896,0.00034904,0.01385312,0.00542264,0.00644052,0.000286,0.00034304,0.0145,0.0165,0,0
yz-fat,two-stroke,50,hllc,1,4,70,5000,2500,0.0763663,15.27326,0.67355077,0.01571584,0.00031624,0.01495828,0.00534936,0.0076164,0.0003036,0.00034232,0.0155,0.0165,0,0
yz-fat,two-stroke,50,muscl,1,4,70,5000,2500,0.0906575,18.1315,0.79959915,0.01856376,0.00031808,0.01779808,0.0071104,0.00868528,0.00028244,0.00034488,0.0185,0.0195,0,0
saab-chambered,four-stroke,50,rusanov,1,10,301,5000,2500,0.0887478,17.74956,0.7827556,0.0179724,0.000745,0.0165752,0.00725352,0.006773,0.0003276,0.00053304,0.0165,0.0245,0,0
saab-chambered,four-stroke,50,hlle,1,10,301,5000,2500,0.1215358,24.30716,1.0719458,0.02448896,0.00072844,0.02313632,0.00709296,0.01260368,0.00030968,0.0005096,0.0235,0.0255,0,0
saab-chambered,four-stroke,50,hllc,1,10,301,5000,2500,0.1324411,26.48822,1.1681305,0.02655668,0.00070284,0.02523496,0.00696064,0.01487744,0.0003076,0.00049696,0.0255,0.0275,0,0
saab-chambered,four-stroke,50,muscl,1,10,301,5000,2500,0.1796392,35.92784,1.5844177,0.036017,0.0006922,0.03468728,0.01377652,0.01751864,0.00032228,0.00051936,0.0345,0.0365,0,0
1 scenario kind cells_per_meter solver repetition pipe_count total_cells frames sampled_frames wall_seconds wall_us_per_frame real_time_factor total_ms_per_frame controls_ms_per_frame solver_ms_per_frame boundary_ms_per_frame pipe_ms_per_frame components_ms_per_frame audio_ms_per_frame p50_frame_ms p95_frame_ms positivity_repairs hllc_fallbacks
2 single-450 four-stroke 50 rusanov 1 4 131 5000 2500 0.0221766 4.43532 0.19559761 0.0045904 0.00029724 0.00371408 0.0013946 0.00172644 7.936e-05 0.00047056 0.0045 0.0045 0 0
3 single-450 four-stroke 50 hlle 1 4 131 5000 2500 0.0298873 5.97746 0.26360599 0.00608568 0.00031204 0.00520532 0.00142852 0.00296664 8.048e-05 0.00046164 0.0055 0.0065 0 0
4 single-450 four-stroke 50 hllc 1 4 131 5000 2500 0.0324929 6.49858 0.28658738 0.006661 0.00030784 0.00577968 0.00142544 0.00353328 8.296e-05 0.0004676 0.0065 0.0065 0 0
5 single-450 four-stroke 50 muscl 1 4 131 5000 2500 0.0427053 8.54106 0.37666075 0.0086688 0.00030336 0.00779792 0.00297476 0.00399228 8.168e-05 0.00046232 0.0085 0.0085 0 0
6 twin four-stroke 50 rusanov 1 6 55 5000 2500 0.0234109 4.68218 0.20648414 0.0048414 0.00036636 0.003913 0.0025476 0.00091236 0.0001218 0.00045752 0.0045 0.0075 0 0
7 twin four-stroke 50 hlle 1 6 55 5000 2500 0.0264447 5.28894 0.23324225 0.0054552 0.00036908 0.00453396 0.0024906 0.00148312 0.0001244 0.00045072 0.0045 0.0085 0 0
8 twin four-stroke 50 hllc 1 6 55 5000 2500 0.0286684 5.73368 0.25285529 0.00591868 0.00037288 0.00498284 0.00260644 0.0018032 0.00012824 0.000457 0.0045 0.0095 0 0
9 twin four-stroke 50 muscl 1 6 55 5000 2500 0.0337112 6.74224 0.29733278 0.00693444 0.00038912 0.00599156 0.00332472 0.00209188 0.0001288 0.00045044 0.0055 0.0105 0 0
10 five four-stroke 50 rusanov 1 14 343 5000 2500 0.0895132 17.90264 0.78950642 0.01815772 0.00081088 0.01673892 0.00846708 0.0059292 0.00034728 0.0004972 0.0205 0.0215 0 0
11 five four-stroke 50 hlle 1 14 343 5000 2500 0.133426 26.6852 1.1768173 0.02675072 0.0009624 0.02510568 0.00925144 0.01224052 0.00039596 0.0005586 0.0295 0.0325 0 0
12 five four-stroke 50 hllc 1 14 343 5000 2500 0.172315 34.463 1.5198183 0.03039436 0.00094916 0.02872924 0.00944852 0.01554452 0.00043216 0.00059528 0.0315 0.0515 0 0
13 five four-stroke 50 muscl 1 14 343 5000 2500 0.2017581 40.35162 1.7795064 0.04055332 0.0009302 0.03896732 0.01783364 0.01755344 0.00041908 0.00053652 0.0445 0.0475 0 0
14 v8-h four-stroke 50 rusanov 1 26 662 5000 2500 0.1798743 35.97486 1.5864913 0.03607572 0.00136824 0.03395516 0.01562336 0.01415284 0.00061336 0.00063232 0.0425 0.0455 0 0
15 v8-h four-stroke 50 hlle 1 26 662 5000 2500 0.2461426 49.22852 2.1709777 0.04931684 0.00139948 0.0470782 0.01617044 0.02484084 0.00063652 0.00071104 0.0575 0.0645 0 0
16 v8-h four-stroke 50 hllc 1 26 662 5000 2500 0.2689101 53.78202 2.3717871 0.054181 0.00135556 0.05198136 0.01624432 0.02972492 0.00060668 0.00071292 0.0635 0.0835 0 0
17 v8-h four-stroke 50 muscl 1 26 662 5000 2500 0.340461 68.0922 3.002866 0.06829276 0.00127412 0.06627316 0.02883468 0.03174308 0.00064368 0.00062556 0.0835 0.0905 0 0
18 yz-fat two-stroke 50 rusanov 1 4 70 5000 2500 0.0563677 11.27354 0.49716311 0.01165048 0.00031712 0.01087784 0.00549588 0.00386268 0.00028332 0.00035296 0.0115 0.0135 0 0
19 yz-fat two-stroke 50 hlle 1 4 70 5000 2500 0.0712716 14.25432 0.62861551 0.01464896 0.00034904 0.01385312 0.00542264 0.00644052 0.000286 0.00034304 0.0145 0.0165 0 0
20 yz-fat two-stroke 50 hllc 1 4 70 5000 2500 0.0763663 15.27326 0.67355077 0.01571584 0.00031624 0.01495828 0.00534936 0.0076164 0.0003036 0.00034232 0.0155 0.0165 0 0
21 yz-fat two-stroke 50 muscl 1 4 70 5000 2500 0.0906575 18.1315 0.79959915 0.01856376 0.00031808 0.01779808 0.0071104 0.00868528 0.00028244 0.00034488 0.0185 0.0195 0 0
22 saab-chambered four-stroke 50 rusanov 1 10 301 5000 2500 0.0887478 17.74956 0.7827556 0.0179724 0.000745 0.0165752 0.00725352 0.006773 0.0003276 0.00053304 0.0165 0.0245 0 0
23 saab-chambered four-stroke 50 hlle 1 10 301 5000 2500 0.1215358 24.30716 1.0719458 0.02448896 0.00072844 0.02313632 0.00709296 0.01260368 0.00030968 0.0005096 0.0235 0.0255 0 0
24 saab-chambered four-stroke 50 hllc 1 10 301 5000 2500 0.1324411 26.48822 1.1681305 0.02655668 0.00070284 0.02523496 0.00696064 0.01487744 0.0003076 0.00049696 0.0255 0.0275 0 0
25 saab-chambered four-stroke 50 muscl 1 10 301 5000 2500 0.1796392 35.92784 1.5844177 0.036017 0.0006922 0.03468728 0.01377652 0.01751864 0.00032228 0.00051936 0.0345 0.0365 0 0

View File

@@ -0,0 +1,13 @@
scenario,kind,cells_per_meter,solver,repetition,pipe_count,total_cells,frames,sampled_frames,wall_seconds,wall_us_per_frame,real_time_factor,scratch_bytes,total_ms_per_frame,controls_ms_per_frame,solver_ms_per_frame,boundary_ms_per_frame,pipe_ms_per_frame,components_ms_per_frame,audio_ms_per_frame,p50_frame_ms,p95_frame_ms,positivity_repairs,hllc_fallbacks,audio_rms,audio_peak,audio_checksum
single-450,four-stroke,45,hllc,1,4,119,22050,11025,0.226753,10.283583,0.453506,9600,0.010504744,0.00033513832,0.0095517914,0.0025144943,0.0056832925,0.00013962812,0.0005081542,0.0115,0.0125,0,0,0.043393295,0.3492032,-33516.677
single-450,four-stroke,45,hllc,2,4,119,22050,11025,0.2273775,10.311905,0.454755,9600,0.010573279,0.00032452608,0.0096300952,0.0025129252,0.0057707211,0.00015024943,0.000506839,0.0115,0.0125,0,0,0.043393295,0.3492032,-33516.677
twin,four-stroke,45,hllc,1,6,51,22050,11025,0.1395075,6.3268707,0.279015,4200,0.0065239002,0.0004158458,0.0054552653,0.00292439,0.0019081633,0.00014010884,0.00053018594,0.0045,0.0105,0,0,0.020964235,0.087753639,-17579.35
twin,four-stroke,45,hllc,2,6,51,22050,11025,0.134874,6.1167347,0.269748,4200,0.0062841995,0.00041102041,0.0052507664,0.0028056871,0.0018331156,0.00014101587,0.00051551927,0.0045,0.0095,0,0,0.020964235,0.087753639,-17579.35
five,four-stroke,45,hllc,1,14,313,22050,11025,0.7043746,31.944426,1.4087492,25320,0.031781651,0.0009593288,0.030065261,0.010523184,0.015685488,0.00044372789,0.00061826757,0.0295,0.0505,0,0,0.024103642,0.069309026,29082.476
five,four-stroke,45,hllc,2,14,313,22050,11025,0.6817808,30.919764,1.3635616,25320,0.031089515,0.00089840363,0.029520154,0.010440871,0.015260671,0.00043317007,0.00055311565,0.0295,0.0345,0,0,0.024103642,0.069309026,29082.476
v8-h,four-stroke,45,hllc,1,26,601,22050,11025,1.4013227,63.55205,2.8026454,48600,0.063683184,0.0013378503,0.061552063,0.020429714,0.033837025,0.00095687075,0.00067591837,0.0605,0.0685,0,0,0.023209132,0.068203621,35894.278
v8-h,four-stroke,45,hllc,2,26,601,22050,11025,1.3803378,62.600354,2.7606756,48600,0.062786122,0.0013044082,0.06069327,0.020193968,0.033333188,0.0009505034,0.00067097506,0.0605,0.0665,0,0,0.023209132,0.068203621,35894.278
yz-fat,two-stroke,45,hllc,1,4,63,22050,11025,0.3248706,14.733361,0.6497412,5120,0.015149941,0.00033209977,0.01432127,0.0054405714,0.0069541587,0.00029183673,0.00039266213,0.0145,0.0155,0,0,0.012617898,0.062623143,6581.8354
yz-fat,two-stroke,45,hllc,2,4,63,22050,11025,0.3236508,14.678041,0.6473016,5120,0.015069868,0.00032561451,0.014249188,0.0054333424,0.0069055329,0.00028797279,0.00038923356,0.0145,0.0155,0,0,0.012617898,0.062623143,6581.8354
saab-chambered,four-stroke,45,hllc,1,10,273,22050,11025,0.5518963,25.029311,1.1037926,22040,0.025288934,0.00075125624,0.023891565,0.0070525533,0.013600027,0.00031453061,0.00053470295,0.0245,0.0255,0,0,0.0046967565,0.017882032,39673.188
saab-chambered,four-stroke,45,hllc,2,10,273,22050,11025,0.5494029,24.916231,1.0988058,22040,0.02511083,0.00069336961,0.023768771,0.0070486349,0.013515746,0.00031092063,0.00053026757,0.0245,0.0255,0,0,0.0046967565,0.017882032,39673.188
1 scenario kind cells_per_meter solver repetition pipe_count total_cells frames sampled_frames wall_seconds wall_us_per_frame real_time_factor scratch_bytes total_ms_per_frame controls_ms_per_frame solver_ms_per_frame boundary_ms_per_frame pipe_ms_per_frame components_ms_per_frame audio_ms_per_frame p50_frame_ms p95_frame_ms positivity_repairs hllc_fallbacks audio_rms audio_peak audio_checksum
2 single-450 four-stroke 45 hllc 1 4 119 22050 11025 0.226753 10.283583 0.453506 9600 0.010504744 0.00033513832 0.0095517914 0.0025144943 0.0056832925 0.00013962812 0.0005081542 0.0115 0.0125 0 0 0.043393295 0.3492032 -33516.677
3 single-450 four-stroke 45 hllc 2 4 119 22050 11025 0.2273775 10.311905 0.454755 9600 0.010573279 0.00032452608 0.0096300952 0.0025129252 0.0057707211 0.00015024943 0.000506839 0.0115 0.0125 0 0 0.043393295 0.3492032 -33516.677
4 twin four-stroke 45 hllc 1 6 51 22050 11025 0.1395075 6.3268707 0.279015 4200 0.0065239002 0.0004158458 0.0054552653 0.00292439 0.0019081633 0.00014010884 0.00053018594 0.0045 0.0105 0 0 0.020964235 0.087753639 -17579.35
5 twin four-stroke 45 hllc 2 6 51 22050 11025 0.134874 6.1167347 0.269748 4200 0.0062841995 0.00041102041 0.0052507664 0.0028056871 0.0018331156 0.00014101587 0.00051551927 0.0045 0.0095 0 0 0.020964235 0.087753639 -17579.35
6 five four-stroke 45 hllc 1 14 313 22050 11025 0.7043746 31.944426 1.4087492 25320 0.031781651 0.0009593288 0.030065261 0.010523184 0.015685488 0.00044372789 0.00061826757 0.0295 0.0505 0 0 0.024103642 0.069309026 29082.476
7 five four-stroke 45 hllc 2 14 313 22050 11025 0.6817808 30.919764 1.3635616 25320 0.031089515 0.00089840363 0.029520154 0.010440871 0.015260671 0.00043317007 0.00055311565 0.0295 0.0345 0 0 0.024103642 0.069309026 29082.476
8 v8-h four-stroke 45 hllc 1 26 601 22050 11025 1.4013227 63.55205 2.8026454 48600 0.063683184 0.0013378503 0.061552063 0.020429714 0.033837025 0.00095687075 0.00067591837 0.0605 0.0685 0 0 0.023209132 0.068203621 35894.278
9 v8-h four-stroke 45 hllc 2 26 601 22050 11025 1.3803378 62.600354 2.7606756 48600 0.062786122 0.0013044082 0.06069327 0.020193968 0.033333188 0.0009505034 0.00067097506 0.0605 0.0665 0 0 0.023209132 0.068203621 35894.278
10 yz-fat two-stroke 45 hllc 1 4 63 22050 11025 0.3248706 14.733361 0.6497412 5120 0.015149941 0.00033209977 0.01432127 0.0054405714 0.0069541587 0.00029183673 0.00039266213 0.0145 0.0155 0 0 0.012617898 0.062623143 6581.8354
11 yz-fat two-stroke 45 hllc 2 4 63 22050 11025 0.3236508 14.678041 0.6473016 5120 0.015069868 0.00032561451 0.014249188 0.0054333424 0.0069055329 0.00028797279 0.00038923356 0.0145 0.0155 0 0 0.012617898 0.062623143 6581.8354
12 saab-chambered four-stroke 45 hllc 1 10 273 22050 11025 0.5518963 25.029311 1.1037926 22040 0.025288934 0.00075125624 0.023891565 0.0070525533 0.013600027 0.00031453061 0.00053470295 0.0245 0.0255 0 0 0.0046967565 0.017882032 39673.188
13 saab-chambered four-stroke 45 hllc 2 10 273 22050 11025 0.5494029 24.916231 1.0988058 22040 0.02511083 0.00069336961 0.023768771 0.0070486349 0.013515746 0.00031092063 0.00053026757 0.0245 0.0255 0 0 0.0046967565 0.017882032 39673.188

View File

@@ -0,0 +1,13 @@
scenario,kind,cells_per_meter,solver,repetition,pipe_count,total_cells,frames,sampled_frames,wall_seconds,wall_us_per_frame,real_time_factor,scratch_bytes,total_ms_per_frame,controls_ms_per_frame,solver_ms_per_frame,boundary_ms_per_frame,pipe_ms_per_frame,components_ms_per_frame,audio_ms_per_frame,p50_frame_ms,p95_frame_ms,positivity_repairs,hllc_fallbacks,audio_rms,audio_peak,audio_checksum
single-450,four-stroke,60,hlle,1,4,156,22050,11025,0.2699482,12.242549,0.5398964,12560,0.012483556,0.00031012245,0.011569551,0.0027820771,0.006976263,0.00015788662,0.00049303401,0.0125,0.0135,0,0,0.042480939,0.44339529,-7325.8487
single-450,four-stroke,60,hlle,2,4,156,22050,11025,0.2861725,12.978345,0.572345,12560,0.013163283,0.00033742404,0.012185351,0.0029106122,0.0073480181,0.00017331519,0.00053260771,0.0125,0.0185,0,0,0.042480939,0.44339529,-7325.8487
twin,four-stroke,60,hlle,1,6,68,22050,11025,0.1870163,8.4814649,0.3740326,5560,0.0086773152,0.00037509297,0.0077045533,0.0038972154,0.002815229,0.00018769161,0.00049344218,0.0085,0.0125,0,0,0.019982735,0.077876091,-19911.264
twin,four-stroke,60,hlle,2,6,68,22050,11025,0.1922604,8.7192925,0.3845208,5560,0.0089856327,0.00041107483,0.007917034,0.0039755828,0.0029109297,0.00019119274,0.0005355737,0.0095,0.0125,0,0,0.019982735,0.077876091,-19911.264
five,four-stroke,60,hlle,1,14,411,22050,11025,0.7560888,34.289741,1.5121776,33160,0.034633116,0.00086585034,0.03310332,0.010930594,0.017191175,0.00045463946,0.00054821769,0.0325,0.0475,0,0,0.023922801,0.069260068,28186.437
five,four-stroke,60,hlle,2,14,411,22050,11025,0.7722134,35.021016,1.5444268,33160,0.035018812,0.00091385034,0.033422975,0.011102948,0.01721795,0.00045916553,0.00056463492,0.0315,0.0515,0,0,0.023922801,0.069260068,28186.437
v8-h,four-stroke,60,hlle,1,26,794,22050,11025,1.6621008,75.378721,3.3242016,64040,0.075711093,0.001263737,0.073679701,0.022620009,0.041107311,0.00085119274,0.00064839909,0.0655,0.0985,0,0,0.022692543,0.065287925,37056.354
v8-h,four-stroke,60,hlle,2,26,794,22050,11025,1.6683265,75.661066,3.336653,64040,0.076016934,0.0012778322,0.073936626,0.022752027,0.041154032,0.00085946485,0.0006872381,0.0655,0.0985,0,0,0.022692543,0.065287925,37056.354
yz-fat,two-stroke,60,hlle,1,4,81,22050,11025,0.3416302,15.493433,0.6832604,6560,0.015876027,0.00034703855,0.01504693,0.005505941,0.0073312744,0.00028359184,0.00037781406,0.0155,0.0175,0,0,0.013830628,0.069703102,6103.5286
yz-fat,two-stroke,60,hlle,2,4,81,22050,11025,0.3414487,15.485202,0.6828974,6560,0.01588137,0.00034490703,0.015047039,0.0054764626,0.0073373515,0.0002962449,0.00038029025,0.0155,0.0175,0,0,0.013830628,0.069703102,6103.5286
saab-chambered,four-stroke,60,hlle,1,10,356,22050,11025,0.6287173,28.513256,1.2574346,28680,0.028825741,0.0007111746,0.027438739,0.007574966,0.015542367,0.00032743764,0.00056229478,0.0265,0.0395,0,0,0.0038942062,0.013391916,30491.41
saab-chambered,four-stroke,60,hlle,2,10,356,22050,11025,0.6213095,28.177302,1.242619,28680,0.028399175,0.00070341043,0.027025832,0.007438966,0.015287093,0.00032465306,0.0005556644,0.0265,0.0385,0,0,0.0038942062,0.013391916,30491.41
1 scenario kind cells_per_meter solver repetition pipe_count total_cells frames sampled_frames wall_seconds wall_us_per_frame real_time_factor scratch_bytes total_ms_per_frame controls_ms_per_frame solver_ms_per_frame boundary_ms_per_frame pipe_ms_per_frame components_ms_per_frame audio_ms_per_frame p50_frame_ms p95_frame_ms positivity_repairs hllc_fallbacks audio_rms audio_peak audio_checksum
2 single-450 four-stroke 60 hlle 1 4 156 22050 11025 0.2699482 12.242549 0.5398964 12560 0.012483556 0.00031012245 0.011569551 0.0027820771 0.006976263 0.00015788662 0.00049303401 0.0125 0.0135 0 0 0.042480939 0.44339529 -7325.8487
3 single-450 four-stroke 60 hlle 2 4 156 22050 11025 0.2861725 12.978345 0.572345 12560 0.013163283 0.00033742404 0.012185351 0.0029106122 0.0073480181 0.00017331519 0.00053260771 0.0125 0.0185 0 0 0.042480939 0.44339529 -7325.8487
4 twin four-stroke 60 hlle 1 6 68 22050 11025 0.1870163 8.4814649 0.3740326 5560 0.0086773152 0.00037509297 0.0077045533 0.0038972154 0.002815229 0.00018769161 0.00049344218 0.0085 0.0125 0 0 0.019982735 0.077876091 -19911.264
5 twin four-stroke 60 hlle 2 6 68 22050 11025 0.1922604 8.7192925 0.3845208 5560 0.0089856327 0.00041107483 0.007917034 0.0039755828 0.0029109297 0.00019119274 0.0005355737 0.0095 0.0125 0 0 0.019982735 0.077876091 -19911.264
6 five four-stroke 60 hlle 1 14 411 22050 11025 0.7560888 34.289741 1.5121776 33160 0.034633116 0.00086585034 0.03310332 0.010930594 0.017191175 0.00045463946 0.00054821769 0.0325 0.0475 0 0 0.023922801 0.069260068 28186.437
7 five four-stroke 60 hlle 2 14 411 22050 11025 0.7722134 35.021016 1.5444268 33160 0.035018812 0.00091385034 0.033422975 0.011102948 0.01721795 0.00045916553 0.00056463492 0.0315 0.0515 0 0 0.023922801 0.069260068 28186.437
8 v8-h four-stroke 60 hlle 1 26 794 22050 11025 1.6621008 75.378721 3.3242016 64040 0.075711093 0.001263737 0.073679701 0.022620009 0.041107311 0.00085119274 0.00064839909 0.0655 0.0985 0 0 0.022692543 0.065287925 37056.354
9 v8-h four-stroke 60 hlle 2 26 794 22050 11025 1.6683265 75.661066 3.336653 64040 0.076016934 0.0012778322 0.073936626 0.022752027 0.041154032 0.00085946485 0.0006872381 0.0655 0.0985 0 0 0.022692543 0.065287925 37056.354
10 yz-fat two-stroke 60 hlle 1 4 81 22050 11025 0.3416302 15.493433 0.6832604 6560 0.015876027 0.00034703855 0.01504693 0.005505941 0.0073312744 0.00028359184 0.00037781406 0.0155 0.0175 0 0 0.013830628 0.069703102 6103.5286
11 yz-fat two-stroke 60 hlle 2 4 81 22050 11025 0.3414487 15.485202 0.6828974 6560 0.01588137 0.00034490703 0.015047039 0.0054764626 0.0073373515 0.0002962449 0.00038029025 0.0155 0.0175 0 0 0.013830628 0.069703102 6103.5286
12 saab-chambered four-stroke 60 hlle 1 10 356 22050 11025 0.6287173 28.513256 1.2574346 28680 0.028825741 0.0007111746 0.027438739 0.007574966 0.015542367 0.00032743764 0.00056229478 0.0265 0.0395 0 0 0.0038942062 0.013391916 30491.41
13 saab-chambered four-stroke 60 hlle 2 10 356 22050 11025 0.6213095 28.177302 1.242619 28680 0.028399175 0.00070341043 0.027025832 0.007438966 0.015287093 0.00032465306 0.0005556644 0.0265 0.0385 0 0 0.0038942062 0.013391916 30491.41

View File

@@ -0,0 +1,13 @@
scenario,kind,cells_per_meter,solver,repetition,pipe_count,total_cells,frames,sampled_frames,wall_seconds,wall_us_per_frame,real_time_factor,scratch_bytes,total_ms_per_frame,controls_ms_per_frame,solver_ms_per_frame,boundary_ms_per_frame,pipe_ms_per_frame,components_ms_per_frame,audio_ms_per_frame,p50_frame_ms,p95_frame_ms,positivity_repairs,hllc_fallbacks,audio_rms,audio_peak,audio_checksum
single-450,four-stroke,25,muscl,1,4,67,22050,11025,0.1207051,5.4741542,0.2414102,7048,0.0056283991,0.00030226757,0.0047359819,0.0022857687,0.0020405896,8.1705215e-05,0.00048489796,0.0055,0.0055,0,0,0.048889616,0.53468895,-4420.8798
single-450,four-stroke,25,muscl,2,4,67,22050,11025,0.1201182,5.4475374,0.2402364,7048,0.0056231474,0.00030870748,0.0047284898,0.0022821224,0.0020352109,8.0589569e-05,0.00048273923,0.0055,0.0055,0,0,0.048889616,0.53468895,-4420.8798
twin,four-stroke,25,muscl,1,6,28,22050,11025,0.101845,4.6188209,0.20369,3032,0.0047535964,0.00040931519,0.0037210249,0.0024937506,0.00087921088,0.00010722902,0.00051560998,0.0045,0.0055,0,0,0.021345219,0.095168315,-17220.858
twin,four-stroke,25,muscl,2,6,28,22050,11025,0.0966549,4.3834422,0.1933098,3032,0.0045384853,0.00037672562,0.0035658685,0.0023995374,0.00084745578,0.00010509751,0.00049126531,0.0045,0.0055,0,0,0.021345219,0.095168315,-17220.858
five,four-stroke,25,muscl,1,14,176,22050,11025,0.3300924,14.970177,0.6601848,18584,0.015122975,0.00083388662,0.013641433,0.0077705215,0.0049303764,0.00021189116,0.00053053968,0.0145,0.0155,0,0,0.024139173,0.088497825,30661.011
five,four-stroke,25,muscl,2,14,176,22050,11025,0.3492916,15.840889,0.6985832,18584,0.015937116,0.00093246259,0.014334494,0.0080950567,0.0052388027,0.00023365079,0.00055497506,0.0145,0.0225,0,0,0.024139173,0.088497825,30661.011
v8-h,four-stroke,25,muscl,1,26,333,22050,11025,0.6409428,29.067701,1.2818856,35152,0.029262204,0.0012698141,0.027210358,0.015206177,0.010328118,0.00039207256,0.00066624943,0.0285,0.0305,0,0,0.045346992,0.33563548,43853.021
v8-h,four-stroke,25,muscl,2,26,333,22050,11025,0.6774216,30.722068,1.3548432,35152,0.030821995,0.0013487256,0.028639175,0.016007918,0.010845333,0.00042159637,0.00071086621,0.0285,0.0345,0,0,0.045346992,0.33563548,43853.021
yz-fat,two-stroke,25,muscl,1,4,38,22050,11025,0.2847994,12.916073,0.5695988,4032,0.013311392,0.00036107029,0.012423347,0.0063281088,0.0049026667,0.00027940136,0.00041877551,0.0125,0.0165,0,0,0.010449031,0.050818499,6591.2704
yz-fat,two-stroke,25,muscl,2,4,38,22050,11025,0.2711458,12.296862,0.5422916,4032,0.01269151,0.00034397279,0.011848236,0.0060514014,0.0046448254,0.00027451247,0.00039845805,0.0125,0.0135,0,0,0.010449031,0.050818499,6591.2704
saab-chambered,four-stroke,25,muscl,1,10,155,22050,11025,0.4817611,21.848576,0.9635222,16320,0.022108399,0.00069845805,0.02073395,0.010338921,0.0088230295,0.0003119093,0.00056509751,0.0215,0.0225,0,0,0.0043141097,0.016827723,34823.247
saab-chambered,four-stroke,25,muscl,2,10,155,22050,11025,0.4796544,21.753034,0.9593088,16320,0.022006213,0.00069043991,0.020655819,0.010321016,0.0087824943,0.00030727438,0.00054684807,0.0215,0.0225,0,0,0.0043141097,0.016827723,34823.247
1 scenario kind cells_per_meter solver repetition pipe_count total_cells frames sampled_frames wall_seconds wall_us_per_frame real_time_factor scratch_bytes total_ms_per_frame controls_ms_per_frame solver_ms_per_frame boundary_ms_per_frame pipe_ms_per_frame components_ms_per_frame audio_ms_per_frame p50_frame_ms p95_frame_ms positivity_repairs hllc_fallbacks audio_rms audio_peak audio_checksum
2 single-450 four-stroke 25 muscl 1 4 67 22050 11025 0.1207051 5.4741542 0.2414102 7048 0.0056283991 0.00030226757 0.0047359819 0.0022857687 0.0020405896 8.1705215e-05 0.00048489796 0.0055 0.0055 0 0 0.048889616 0.53468895 -4420.8798
3 single-450 four-stroke 25 muscl 2 4 67 22050 11025 0.1201182 5.4475374 0.2402364 7048 0.0056231474 0.00030870748 0.0047284898 0.0022821224 0.0020352109 8.0589569e-05 0.00048273923 0.0055 0.0055 0 0 0.048889616 0.53468895 -4420.8798
4 twin four-stroke 25 muscl 1 6 28 22050 11025 0.101845 4.6188209 0.20369 3032 0.0047535964 0.00040931519 0.0037210249 0.0024937506 0.00087921088 0.00010722902 0.00051560998 0.0045 0.0055 0 0 0.021345219 0.095168315 -17220.858
5 twin four-stroke 25 muscl 2 6 28 22050 11025 0.0966549 4.3834422 0.1933098 3032 0.0045384853 0.00037672562 0.0035658685 0.0023995374 0.00084745578 0.00010509751 0.00049126531 0.0045 0.0055 0 0 0.021345219 0.095168315 -17220.858
6 five four-stroke 25 muscl 1 14 176 22050 11025 0.3300924 14.970177 0.6601848 18584 0.015122975 0.00083388662 0.013641433 0.0077705215 0.0049303764 0.00021189116 0.00053053968 0.0145 0.0155 0 0 0.024139173 0.088497825 30661.011
7 five four-stroke 25 muscl 2 14 176 22050 11025 0.3492916 15.840889 0.6985832 18584 0.015937116 0.00093246259 0.014334494 0.0080950567 0.0052388027 0.00023365079 0.00055497506 0.0145 0.0225 0 0 0.024139173 0.088497825 30661.011
8 v8-h four-stroke 25 muscl 1 26 333 22050 11025 0.6409428 29.067701 1.2818856 35152 0.029262204 0.0012698141 0.027210358 0.015206177 0.010328118 0.00039207256 0.00066624943 0.0285 0.0305 0 0 0.045346992 0.33563548 43853.021
9 v8-h four-stroke 25 muscl 2 26 333 22050 11025 0.6774216 30.722068 1.3548432 35152 0.030821995 0.0013487256 0.028639175 0.016007918 0.010845333 0.00042159637 0.00071086621 0.0285 0.0345 0 0 0.045346992 0.33563548 43853.021
10 yz-fat two-stroke 25 muscl 1 4 38 22050 11025 0.2847994 12.916073 0.5695988 4032 0.013311392 0.00036107029 0.012423347 0.0063281088 0.0049026667 0.00027940136 0.00041877551 0.0125 0.0165 0 0 0.010449031 0.050818499 6591.2704
11 yz-fat two-stroke 25 muscl 2 4 38 22050 11025 0.2711458 12.296862 0.5422916 4032 0.01269151 0.00034397279 0.011848236 0.0060514014 0.0046448254 0.00027451247 0.00039845805 0.0125 0.0135 0 0 0.010449031 0.050818499 6591.2704
12 saab-chambered four-stroke 25 muscl 1 10 155 22050 11025 0.4817611 21.848576 0.9635222 16320 0.022108399 0.00069845805 0.02073395 0.010338921 0.0088230295 0.0003119093 0.00056509751 0.0215 0.0225 0 0 0.0043141097 0.016827723 34823.247
13 saab-chambered four-stroke 25 muscl 2 10 155 22050 11025 0.4796544 21.753034 0.9593088 16320 0.022006213 0.00069043991 0.020655819 0.010321016 0.0087824943 0.00030727438 0.00054684807 0.0215 0.0225 0 0 0.0043141097 0.016827723 34823.247

Some files were not shown because too many files have changed in this diff Show More