diff --git a/.gitignore b/.gitignore index ce0086e..7af441f 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 72f166e..502b60d 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -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." } ] -} +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f306c5..9132eea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/README.md b/README.md index c919830..35b84f8 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/docs/ACOUSTIC_BENCHMARK.md b/docs/ACOUSTIC_BENCHMARK.md new file mode 100644 index 0000000..f11d4bf --- /dev/null +++ b/docs/ACOUSTIC_BENCHMARK.md @@ -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). diff --git a/docs/ENGINE_CONFIG.md b/docs/ENGINE_CONFIG.md index e2e4eb2..81213d7 100644 --- a/docs/ENGINE_CONFIG.md +++ b/docs/ENGINE_CONFIG.md @@ -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). diff --git a/docs/JUNCTION_SOLVER.md b/docs/JUNCTION_SOLVER.md new file mode 100644 index 0000000..32bd08a --- /dev/null +++ b/docs/JUNCTION_SOLVER.md @@ -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. diff --git a/engines/big-carb.toml b/engines/big-carb.toml index 3ce177e..aa763eb 100644 --- a/engines/big-carb.toml +++ b/engines/big-carb.toml @@ -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 ] diff --git a/engines/busso.toml b/engines/busso.toml index 612870b..d8bf87c 100644 --- a/engines/busso.toml +++ b/engines/busso.toml @@ -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 ] diff --git a/engines/ej20.toml b/engines/ej20.toml index 4f8ab72..be961a4 100644 --- a/engines/ej20.toml +++ b/engines/ej20.toml @@ -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 ] diff --git a/engines/f20.toml b/engines/f20.toml index d8a93dc..d18d25a 100644 --- a/engines/f20.toml +++ b/engines/f20.toml @@ -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 ] diff --git a/engines/fifty.toml b/engines/fifty.toml index 0db4f89..b93968c 100644 --- a/engines/fifty.toml +++ b/engines/fifty.toml @@ -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 ] diff --git a/engines/five.toml b/engines/five.toml index aa86e23..11ec016 100644 --- a/engines/five.toml +++ b/engines/five.toml @@ -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 ] diff --git a/engines/fivehundred.toml b/engines/fivehundred.toml index 111664d..62bec90 100644 --- a/engines/fivehundred.toml +++ b/engines/fivehundred.toml @@ -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 ] diff --git a/engines/flat6.toml b/engines/flat6.toml index cccdf0c..11905dd 100644 --- a/engines/flat6.toml +++ b/engines/flat6.toml @@ -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 ] diff --git a/engines/ltwin.toml b/engines/ltwin.toml index 3ff4a97..525c9e3 100644 --- a/engines/ltwin.toml +++ b/engines/ltwin.toml @@ -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 ] diff --git a/engines/rb26.toml b/engines/rb26.toml index b95bf34..9e521d4 100644 --- a/engines/rb26.toml +++ b/engines/rb26.toml @@ -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 ] diff --git a/engines/saab-chambered.toml b/engines/saab-chambered.toml index e48527d..3f53b78 100644 --- a/engines/saab-chambered.toml +++ b/engines/saab-chambered.toml @@ -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 ] diff --git a/engines/saab-expansion.toml b/engines/saab-expansion.toml index a9405c5..3792730 100644 --- a/engines/saab-expansion.toml +++ b/engines/saab-expansion.toml @@ -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 ] diff --git a/engines/saab-open.toml b/engines/saab-open.toml index 22fbc2e..2ac347a 100644 --- a/engines/saab-open.toml +++ b/engines/saab-open.toml @@ -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 ] diff --git a/engines/saab-straight.toml b/engines/saab-straight.toml index f908d9b..3f03d98 100644 --- a/engines/saab-straight.toml +++ b/engines/saab-straight.toml @@ -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 ] diff --git a/engines/single-450.toml b/engines/single-450.toml index 0bff131..6bcc669 100644 --- a/engines/single-450.toml +++ b/engines/single-450.toml @@ -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 ] diff --git a/engines/single-650.toml b/engines/single-650.toml index 8593c48..0790e80 100644 --- a/engines/single-650.toml +++ b/engines/single-650.toml @@ -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 ] diff --git a/engines/six-long.toml b/engines/six-long.toml index 46598f8..c95b72a 100644 --- a/engines/six-long.toml +++ b/engines/six-long.toml @@ -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 ] diff --git a/engines/triple.toml b/engines/triple.toml index b159641..e1b6fe2 100644 --- a/engines/triple.toml +++ b/engines/triple.toml @@ -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 ] diff --git a/engines/twin-270.toml b/engines/twin-270.toml index fad1f9c..8128a93 100644 --- a/engines/twin-270.toml +++ b/engines/twin-270.toml @@ -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 ] diff --git a/engines/twin.toml b/engines/twin.toml index f10e92c..06c9726 100644 --- a/engines/twin.toml +++ b/engines/twin.toml @@ -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 ] diff --git a/engines/v10.toml b/engines/v10.toml index 74bc51e..b1ec40d 100644 --- a/engines/v10.toml +++ b/engines/v10.toml @@ -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 ] diff --git a/engines/v12.toml b/engines/v12.toml index f06d3b5..5266ddb 100644 --- a/engines/v12.toml +++ b/engines/v12.toml @@ -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 ] diff --git a/engines/v8-dual.toml b/engines/v8-dual.toml index 95b264a..979fbd2 100644 --- a/engines/v8-dual.toml +++ b/engines/v8-dual.toml @@ -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 ] diff --git a/engines/v8-h.toml b/engines/v8-h.toml index 8bfd0ac..95ddf94 100644 --- a/engines/v8-h.toml +++ b/engines/v8-h.toml @@ -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 ] diff --git a/engines/v8-large.toml b/engines/v8-large.toml index c780a01..cf27a89 100644 --- a/engines/v8-large.toml +++ b/engines/v8-large.toml @@ -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 ] diff --git a/engines/v8-long.toml b/engines/v8-long.toml index 774d912..f451a49 100644 --- a/engines/v8-long.toml +++ b/engines/v8-long.toml @@ -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 ] diff --git a/engines/v8-short.toml b/engines/v8-short.toml index d2562eb..6a37dfa 100644 --- a/engines/v8-short.toml +++ b/engines/v8-short.toml @@ -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 ] diff --git a/engines/yz-fat.toml b/engines/yz-fat.toml index 0d56eac..fbeb7d4 100644 --- a/engines/yz-fat.toml +++ b/engines/yz-fat.toml @@ -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 ] diff --git a/engines/yz-racing.toml b/engines/yz-racing.toml index e6f45de..69b754e 100644 --- a/engines/yz-racing.toml +++ b/engines/yz-racing.toml @@ -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 ] diff --git a/engines/yz-short.toml b/engines/yz-short.toml index c4510e1..a95de47 100644 --- a/engines/yz-short.toml +++ b/engines/yz-short.toml @@ -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 ] diff --git a/engines/yz-straight.toml b/engines/yz-straight.toml index 0046307..648f93c 100644 --- a/engines/yz-straight.toml +++ b/engines/yz-straight.toml @@ -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 ] diff --git a/engines/yz-torque.toml b/engines/yz-torque.toml index f0e20ea..0954006 100644 --- a/engines/yz-torque.toml +++ b/engines/yz-torque.toml @@ -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 ] diff --git a/improvements.md b/improvements.md new file mode 100644 index 0000000..8f92647 --- /dev/null +++ b/improvements.md @@ -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 diff --git a/include/FluidSim/AudioOutputStream.hpp b/include/FluidSim/AudioOutputStream.hpp index 8b92f62..1375641 100644 --- a/include/FluidSim/AudioOutputStream.hpp +++ b/include/FluidSim/AudioOutputStream.hpp @@ -24,7 +24,7 @@ private: std::array output_{}; std::atomic underrun_blocks_{}; std::atomic speed_{1.0}; - std::atomic auto_gain_enabled_{true}; + std::atomic 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_{}; diff --git a/include/FluidSim/AudioProcessing.hpp b/include/FluidSim/AudioProcessing.hpp index 26e66a7..5b7caf4 100644 --- a/include/FluidSim/AudioProcessing.hpp +++ b/include/FluidSim/AudioProcessing.hpp @@ -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. diff --git a/include/FluidSim/Desktop/DesktopUi.hpp b/include/FluidSim/Desktop/DesktopUi.hpp index 22502fb..973b682 100644 --- a/include/FluidSim/Desktop/DesktopUi.hpp +++ b/include/FluidSim/Desktop/DesktopUi.hpp @@ -3,6 +3,8 @@ #include "FluidSim/DynoCurve.hpp" #include "FluidSim/Desktop/PedalControl.hpp" #include "FluidSim/Desktop/EngineRenderer3D.hpp" +#include +#include #include 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 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 resolution_buttons(sf::Vector2u size); private: DynoCurve dyno_; + float resolution_{20}; + bool resolution_busy_{}; + std::optional 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&); }; } diff --git a/include/FluidSim/Desktop/EngineEditor.hpp b/include/FluidSim/Desktop/EngineEditor.hpp new file mode 100644 index 0000000..6f99ff6 --- /dev/null +++ b/include/FluidSim/Desktop/EngineEditor.hpp @@ -0,0 +1,47 @@ +#pragma once +#include "FluidSim/ScenarioCatalog.hpp" +#include "FluidSim/ScenarioSnapshot.hpp" +#include +#include + +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 take_live_update(); + std::optional 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 applied_pipes_; + std::vector 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 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; +}; +} diff --git a/include/FluidSim/Desktop/PipePath.hpp b/include/FluidSim/Desktop/PipePath.hpp index 0db5349..87574ef 100644 --- a/include/FluidSim/Desktop/PipePath.hpp +++ b/include/FluidSim/Desktop/PipePath.hpp @@ -7,8 +7,7 @@ #include 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 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(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.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 anchors_; }; } diff --git a/include/FluidSim/Desktop/PlaybackControl.hpp b/include/FluidSim/Desktop/PlaybackControl.hpp new file mode 100644 index 0000000..8195de7 --- /dev/null +++ b/include/FluidSim/Desktop/PlaybackControl.hpp @@ -0,0 +1,24 @@ +#pragma once +#include +#include +#include +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()) { + 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();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);} +}; +} diff --git a/include/FluidSim/Desktop/PressurePalette.hpp b/include/FluidSim/Desktop/PressurePalette.hpp index 3e0d9e3..40b0ef3 100644 --- a/include/FluidSim/Desktop/PressurePalette.hpp +++ b/include/FluidSim/Desktop/PressurePalette.hpp @@ -3,10 +3,29 @@ #include #include 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); } } diff --git a/include/FluidSim/DesktopScenario.hpp b/include/FluidSim/DesktopScenario.hpp index 116131f..f9b91cb 100644 --- a/include/FluidSim/DesktopScenario.hpp +++ b/include/FluidSim/DesktopScenario.hpp @@ -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(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(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 next_four; + std::unique_ptr next_two; + if(is_two) { two_config.cells_per_meter=density;two_config.exhaust_cell_length=1/density;next_two=std::make_unique(44100,two_config); } + else { engine.cells_per_meter=density;next_four=std::make_unique(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 four_; std::unique_ptr two_; mutable std::mutex mutex_; diff --git a/include/FluidSim/Engine.hpp b/include/FluidSim/Engine.hpp index f93630d..036446b 100644 --- a/include/FluidSim/Engine.hpp +++ b/include/FluidSim/Engine.hpp @@ -5,6 +5,7 @@ #include #include #include +#include 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 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 diff --git a/include/FluidSim/EngineConfig.hpp b/include/FluidSim/EngineConfig.hpp index 7a6cc13..8c1bfc4 100644 --- a/include/FluidSim/EngineConfig.hpp +++ b/include/FluidSim/EngineConfig.hpp @@ -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 +#include #include 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 direction{}; +}; struct JunctionConfig { + std::string model{"volume"}; // volume, characteristic, or legacy + float turning_loss{0}; + std::vector branches; bool h_pipe{true}; float junction_volume{50e-6f}; }; diff --git a/include/FluidSim/Fluid.hpp b/include/FluidSim/Fluid.hpp index 3fc8b64..24bcafa 100644 --- a/include/FluidSim/Fluid.hpp +++ b/include/FluidSim/Fluid.hpp @@ -4,6 +4,8 @@ #include "FluidSim/OpenEndRadiation.hpp" #include "FluidSim/PipeSolver.hpp" #include +#include +#include #include #include #include @@ -181,12 +183,17 @@ public: std::vector 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 areas, std::vector 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::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 next_cells_; std::vector states_; + std::vector reconstructed_left_, reconstructed_right_; std::vector waves_; std::vector faces_; std::vector 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;i0 && (cell_areas[i-1]!=cell_areas[i] || cell_lengths[i-1]!=cell_lengths[i])) || + (i+11e-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 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 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 direction{}; + }; + enum class JunctionMethod { Characteristic, LegacyRusanov }; + void add_junction(std::initializer_list 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 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::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 ends; std::vector states; + struct Face { double pressure{},rho{},velocity{},mass{},enthalpy{},air{},fuel{},loss{}; }; + std::vector faces; + JunctionMethod method{JunctionMethod::Characteristic}; + double turning_loss{}; }; std::vector 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::max(), high=0; + for(std::size_t k=0;k=.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;k0?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;k1e-20) { + const double h=heat/incoming, thermal=h-.5*f.velocity*f.velocity; + if(!(thermal>0)) return std::numeric_limits::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 && next0?3:1)) { + for(std::size_t k=0;k0?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=.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;k0?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 +#include +#include +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)))); +} +} diff --git a/include/FluidSim/OutdoorAudio.hpp b/include/FluidSim/OutdoorAudio.hpp index 85f81a0..a315ba9 100644 --- a/include/FluidSim/OutdoorAudio.hpp +++ b/include/FluidSim/OutdoorAudio.hpp @@ -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 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 requested_scene_{OutdoorScene::Wall}; OutdoorScene active_scene_{OutdoorScene::Wall}; bool monitor_filter_{true}; - std::array emitters_; + std::array emitters_; OutdoorExhaustReverb late_reverb_; std::array 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 sources_{}; + std::array 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); diff --git a/include/FluidSim/PhysicalGeometry.hpp b/include/FluidSim/PhysicalGeometry.hpp new file mode 100644 index 0000000..90f597e --- /dev/null +++ b/include/FluidSim/PhysicalGeometry.hpp @@ -0,0 +1,42 @@ +#pragma once +#include "FluidSim/AudioProcessing.hpp" +#include +#include + +namespace fluidsim::port { +struct EngineConfig; +struct ExhaustConfig; +struct JunctionConfig; +struct TwoStrokeConfig; +struct GeometryPath { + bool operator==(const GeometryPath&) const = default; + std::vector 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 pipes; + std::vector volumes; + std::vector 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); +} diff --git a/include/FluidSim/PipeProfile.hpp b/include/FluidSim/PipeProfile.hpp index cc3d0d6..26af987 100644 --- a/include/FluidSim/PipeProfile.hpp +++ b/include/FluidSim/PipeProfile.hpp @@ -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(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 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(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); diff --git a/include/FluidSim/PipeSolver.hpp b/include/FluidSim/PipeSolver.hpp index 5fd0289..b48b055 100644 --- a/include/FluidSim/PipeSolver.hpp +++ b/include/FluidSim/PipeSolver.hpp @@ -2,12 +2,13 @@ #include #include 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 diff --git a/include/FluidSim/RuntimeProfiler.hpp b/include/FluidSim/RuntimeProfiler.hpp new file mode 100644 index 0000000..86acc5a --- /dev/null +++ b/include/FluidSim/RuntimeProfiler.hpp @@ -0,0 +1,59 @@ +#pragma once +#include +#include +#include +#include +#include +#include +namespace fluidsim::port { +enum class ProfileStage : std::size_t { Total, Controls, Solver, Boundary, Pipe, Components, Audio, Count }; +inline constexpr std::array profile_stage_names{ + "total", "controls", "solver", "boundary", "pipe", "components", "audio"}; +struct ProfileTotals { + std::array(ProfileStage::Count)> nanoseconds{}; + std::uint64_t sampled_steps{}; + std::array frame_histogram{}; +}; +inline double profile_percentile_ms(const ProfileTotals& totals,double quantile) { + if(!totals.sampled_steps) return 0; + const auto target=static_cast(quantile*(totals.sampled_steps-1)); + std::uint64_t count=0; + for(std::size_t i=0;itarget) 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(stage)]+=ns; } + void finish_step(std::uint64_t elapsed_ns) { + ++totals_.sampled_steps; + const auto bin=std::min(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(Clock::now()-start_).count()); } + std::uint64_t elapsed_ns() const { + return profiler_?std::chrono::duration_cast(Clock::now()-start_).count():0; + } +private: + using Clock=std::chrono::steady_clock; + RuntimeProfiler* profiler_{}; ProfileStage stage_{}; Clock::time_point start_{}; +}; +} diff --git a/include/FluidSim/Scenario.hpp b/include/FluidSim/Scenario.hpp index 6abe8f0..d0474a9 100644 --- a/include/FluidSim/Scenario.hpp +++ b/include/FluidSim/Scenario.hpp @@ -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 @@ -35,13 +36,22 @@ public: int cylinder_count() const { return engine_config.cylinders; } std::array cylinders() { return {&cylinder1, &cylinder2, &cylinder3, &cylinder4, &cylinder5, &cylinder6, &cylinder7, &cylinder8,&cylinder9,&cylinder10,&cylinder11,&cylinder12}; } + std::array 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 geometry_; + std::shared_ptr active_tuning_; + std::array intake_lift_trace_{},exhaust_lift_trace_{}; + float last_contact_impulse_{}; + float valve_sound_displacement_{},valve_sound_velocity_{}; mutable std::mutex mutex_; std::vector> exhaust_junctions_; std::unique_ptr carb_; diff --git a/include/FluidSim/ScenarioCatalog.hpp b/include/FluidSim/ScenarioCatalog.hpp index 5a2665b..d7db517 100644 --- a/include/FluidSim/ScenarioCatalog.hpp +++ b/include/FluidSim/ScenarioCatalog.hpp @@ -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 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. diff --git a/include/FluidSim/ScenarioGeometry.hpp b/include/FluidSim/ScenarioGeometry.hpp index a0a92b2..07d2c8d 100644 --- a/include/FluidSim/ScenarioGeometry.hpp +++ b/include/FluidSim/ScenarioGeometry.hpp @@ -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 points{}; - std::array fractions{}; + std::array points{}; + std::array fractions{}; unsigned count{}; }; std::array scenario_pipe_routes(const ScenarioSnapshot& state); +bool scenario_pipe_is_exhaust(const ScenarioSnapshot& state, std::size_t pipe_index); std::array scenario_sound_sources(const ScenarioSnapshot& state); } diff --git a/include/FluidSim/ScenarioSnapshot.hpp b/include/FluidSim/ScenarioSnapshot.hpp index 496cd0c..c9b0e75 100644 --- a/include/FluidSim/ScenarioSnapshot.hpp +++ b/include/FluidSim/ScenarioSnapshot.hpp @@ -7,10 +7,19 @@ #include #include #include +#include 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 geometry; + std::vector storage_volumes; + std::vector 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 cylinder_angles{}; std::array cylinder_pressures{}, piston_fractions{}, intake_areas{}, exhaust_areas{}; + std::array 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 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 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{}; diff --git a/include/FluidSim/Silencer.hpp b/include/FluidSim/Silencer.hpp index 691d210..ccd33f2 100644 --- a/include/FluidSim/Silencer.hpp +++ b/include/FluidSim/Silencer.hpp @@ -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 #include @@ -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& pipes_; BoundarySystem& boundaries_; std::vector components_; std::uint64_t step_count_{}, substep_count_{}; + RuntimeProfiler* profiler_{}; }; } // namespace fluidsim::port diff --git a/include/FluidSim/TwoStrokeConfig.hpp b/include/FluidSim/TwoStrokeConfig.hpp index f2d2cd9..dc60d3b 100644 --- a/include/FluidSim/TwoStrokeConfig.hpp +++ b/include/FluidSim/TwoStrokeConfig.hpp @@ -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 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%) diff --git a/include/FluidSim/TwoStrokeCylinder.hpp b/include/FluidSim/TwoStrokeCylinder.hpp index 7a9fe0f..7f1ece8 100644 --- a/include/FluidSim/TwoStrokeCylinder.hpp +++ b/include/FluidSim/TwoStrokeCylinder.hpp @@ -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_; } diff --git a/include/FluidSim/TwoStrokeScenario.hpp b/include/FluidSim/TwoStrokeScenario.hpp index 8c79266..8aad8f8 100644 --- a/include/FluidSim/TwoStrokeScenario.hpp +++ b/include/FluidSim/TwoStrokeScenario.hpp @@ -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 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 geometry_; static float area_for_diameter(float diameter); static float slide_area(float diameter, float opening); diff --git a/src/desktop_main.cpp b/src/desktop_main.cpp index deeff48..cbdf91f 100644 --- a/src/desktop_main.cpp +++ b/src/desktop_main.cpp @@ -2,6 +2,7 @@ #include "FluidSim/SoundEngine.hpp" #include "FluidSim/Desktop/DesktopUi.hpp" #include "FluidSim/Desktop/ScenarioMenu.hpp" +#include "FluidSim/Desktop/PlaybackControl.hpp" #include #include @@ -11,10 +12,12 @@ #include #include #include +#include #include #include #include #include +#include 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 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 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(static_cast(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 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()) { throttle_held=false; throttle_down=false; clutch_held=false; scenario.set_starter(false); } if (event->is()) window.close(); if (const auto* key = event->getIf()) { 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((static_cast(selected_solver)+1)%3); + selected_solver=static_cast((static_cast(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(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: "<()) { + 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 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(); + } } diff --git a/src/engine.cpp b/src/engine.cpp index 0c4c956..e788d34 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -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_=⁣ 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 angle0?(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.liftd.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; diff --git a/src/engine_config.cpp b/src/engine_config.cpp index 4b5a4f4..5f318a0 100644 --- a/src/engine_config.cpp +++ b/src/engine_config.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #include #include @@ -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 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(); - if(!value || !std::isfinite(*value) || std::abs(*value)>std::numeric_limits::max())fail("array entries must be finite numbers"); - return static_cast(*value); + if(!value || !std::isfinite(*value) || std::abs(*value*scale)>std::numeric_limits::max())fail("array entries must be finite numbers"); + return static_cast(*value*scale); } void field(const char* key,std::vector& values) { if(const auto* a=array(key)) {values.clear();for(const auto& n:*a)values.push_back(number(n));} @@ -84,6 +87,51 @@ template 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;isize()!=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<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 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(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;ierase("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 = "<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 load_engine_directory(const std::filesystem::path& directory) { require(std::filesystem::is_directory(directory),directory.string(),"engine directory does not exist"); std::vector files; diff --git a/src/engine_editor.cpp b/src/engine_editor.cpp new file mode 100644 index 0000000..22a62a6 --- /dev/null +++ b/src/engine_editor.cpp @@ -0,0 +1,264 @@ +#include "FluidSim/Desktop/EngineEditor.hpp" +#include "FluidSim/Desktop/DesktopUi.hpp" +#include +#include +#include +#include + +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< 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 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()) { + 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()){dragging_field_=dragging_point_=-1;return false;} + if(const auto* click=event.getIf()) { + 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(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=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()) { + const bool captured=dragging_field_>=0 || dragging_point_>=0;dragging_field_=dragging_point_=-1; + if(captured)return true; + } + if(const auto* move=event.getIf();move && (dragging_field_>=0 || dragging_point_>=0)) {drag(sf::Vector2f(move->position),size);return true;} + if(const auto* wheel=event.getIf();wheel && visible_ && bounds(size).contains(sf::Vector2f(wheel->position)))return true; + return false; +} +std::optional 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 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"); + 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;icolumns)text(w,font,status_.substr(columns,columns),x+18,y+p.size.y-24,10,muted); +} +} diff --git a/src/engine_renderer_3d.cpp b/src/engine_renderer_3d.cpp index e656032..1fd5000 100644 --- a/src/engine_renderer_3d.cpp +++ b/src/engine_renderer_3d.cpp @@ -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 colors; std::vector 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::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& states) { + void update(const std::vector& 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;jcylinders[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;ppipes.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;kfrom) { + 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;ppipes.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(); diff --git a/src/engine_schematic.cpp b/src/engine_schematic.cpp index cc6ed4b..e9eeee3 100644 --- a/src/engine_schematic.cpp +++ b/src/engine_schematic.cpp @@ -1,12 +1,13 @@ #include "FluidSim/Desktop/EngineSchematic.hpp" #include "FluidSim/Desktop/PressurePalette.hpp" +#include "FluidSim/ScenarioGeometry.hpp" #include #include #include #include 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(255*c.x),static_cast(255*c.y),static_cast(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(255*tint.x),static_cast(255*tint.y),static_cast(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;ppipes.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 knots{0};for(std::size_t k=1;k(255*tint.x),static_cast(255*tint.y),static_cast(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&&knotcylinders[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; diff --git a/src/outdoor_audio.cpp b/src/outdoor_audio.cpp index f97cdf4..443a94b 100644 --- a/src/outdoor_audio.cpp +++ b/src/outdoor_audio.cpp @@ -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 flows{intake,exhaust,second_exhaust,valve_contact}; + AudioFrame mix{}; + for(std::size_t i=0;i +#include +#include +#include + +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)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()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.xhi.x+tolerance||point.yhi.y+tolerance||point.zhi.z+tolerance)continue; + const std::array 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 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 lengths; + for(const auto& p:e.pipes)for(int i=0;i intake,primary; + float max_z=0,max_y=0; + for(int i=0;i>& 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,4> pairs{{{{0,1}},{{2,3}},{{4,6}},{{5,7}}}}; + std::array 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 bank{{{width*.5f+.10f,-.08f,-bank_z},{width*.5f+.10f,-.08f,bank_z}}}; + std::array 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 heads(groups);Vec3 merge{.20f,-.07f,e.layout==EngineLayout::Inline?e.cylinder.bore*.75f+.105f:0.f}; + for(int group=0;group cylinders;float x=0; + for(int i=0;i> 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> ends{{exit,true}};for(int k=0;k> ends{{exit,true}};for(int i=0;i=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 angles{-20,-65,-125,-180,-200,-180}; + for(std::size_t i=0;i64||!(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 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; +} +} diff --git a/src/scenario.cpp b/src/scenario.cpp index e09537a..8607a25 100644 --- a/src/scenario.cpp +++ b/src/scenario.cpp @@ -5,6 +5,7 @@ #include #include +#include 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(engine.geometry); + active_tuning_=std::make_shared(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(crankshaft)=engine.crankshaft; @@ -48,6 +54,7 @@ FourStrokeScenario::FourStrokeScenario(int sample_rate, EngineConfig configurati for (int i=0;i(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 ends) { - auto fitting=std::make_unique(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 branch_used(junctions.branches.size(),false); + int junction_index=0; + auto junction=[&](std::span 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(box->volume(),101325,700); + fitting->connect(boundaries_,pipes,ends); solver_.add_component(*fitting);exhaust_junctions_.push_back(std::move(fitting)); - }; + } else { + std::vector 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 direction{b.x-a.x,b.y-a.y,b.z-a.z}; + for(std::size_t j=0;j 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& ends) { - auto fitting=std::make_unique(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 ends; for(int i=0;i ends{{exit,true}}; + for(int i=0;istep(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(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 retired; + { + const std::lock_guard guard(mutex_); + retired=std::move(active_tuning_);active_tuning_=std::move(next); + std::swap(static_cast(ecu),calibration); + if(carb_) { + static_cast(*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;iset_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 FourStrokeScenario::create_pipes(const EngineConfig& engine,const ExhaustConfig& exhaust) { validate_engine_config(engine); std::vector 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;i4096) 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; } diff --git a/src/scenario_geometry.cpp b/src/scenario_geometry.cpp index b5bbee8..f375763 100644 --- a/src/scenario_geometry.cpp +++ b/src/scenario_geometry.cpp @@ -17,6 +17,18 @@ PipeRoute route(std::initializer_list anchors) { } std::array scenario_pipe_routes(const ScenarioSnapshot& s) { std::array routes{}; + if(s.geometry) { + for(std::size_t p=0;ppipes.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());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 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(1+ s.cylinder_count); +} std::array 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; diff --git a/src/scenario_snapshot.cpp b/src/scenario_snapshot.cpp index 0ea3e5c..7c03be2 100644 --- a/src/scenario_snapshot.cpp +++ b/src/scenario_snapshot.cpp @@ -5,6 +5,20 @@ namespace fluidsim::port { ScenarioSnapshot FourStrokeScenario::snapshot() const { const std::lock_guard 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;ipressure()}); + } + } + 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{pipes[21].state(0).pressure,pipes[22].state(0).pressure} : std::array{}; 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;ivalve_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 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; diff --git a/src/solver.cpp b/src/solver.cpp index e502919..2ef6c7b 100644 --- a/src/solver.cpp +++ b/src/solver.cpp @@ -9,7 +9,8 @@ Solver::Solver(std::vector &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_; diff --git a/src/two_stroke_scenario.cpp b/src/two_stroke_scenario.cpp index ce467dd..f1c7d0f 100644 --- a/src/two_stroke_scenario.cpp +++ b/src/two_stroke_scenario.cpp @@ -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(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(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(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 TwoStrokeScenario::create_pipes(const TwoStrokeConfig& config) { + if(config.cells_per_meter!=0) mesh_cell_count(.1f,config.cells_per_meter); std::vector 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 diff --git a/tests/desktop_ui_tests.cpp b/tests/desktop_ui_tests.cpp index bbb5727..e19a70c 100644 --- a/tests/desktop_ui_tests.cpp +++ b/tests/desktop_ui_tests.cpp @@ -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 #include #include #include 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((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"; } diff --git a/tests/engine_config_tests.cpp b/tests/engine_config_tests.cpp index a601581..a91fe04 100644 --- a/tests/engine_config_tests.cpp +++ b/tests/engine_config_tests.cpp @@ -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<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; diff --git a/tests/junction_tests.cpp b/tests/junction_tests.cpp new file mode 100644 index 0000000..4c1cd73 --- /dev/null +++ b/tests/junction_tests.cpp @@ -0,0 +1,79 @@ +#include "FluidSim/Solver.hpp" +#include +#include +#include +#include +using namespace fluidsim::port; +namespace { +void check(bool condition,const char* message) { if(!condition) throw std::runtime_error(message); } +struct Outcome { std::array mass{}, pressure{}; std::uint64_t fallbacks{}; }; +Outcome flow(bool reverse,bool rotate,float loss,bool mixed=false,bool swap_donors=false) { + std::vector 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,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 t{}; + for(auto& p:pipes) for(int i=0;idirected.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 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< +#include +#include +#include +#include +#include +#include +#include + +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::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 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"; +} diff --git a/tests/mesh_resolution_tests.cpp b/tests/mesh_resolution_tests.cpp new file mode 100644 index 0000000..ba03070 --- /dev/null +++ b/tests/mesh_resolution_tests.cpp @@ -0,0 +1,47 @@ +#include "FluidSim/DesktopScenario.hpp" +#include +#include +#include +#include +#include +#include +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;i0,"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 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)::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< +#include +#include +#include +#include +#include +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;ppipes.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;kpipes[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)})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("")=="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<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<.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"); diff --git a/tools/acoustic_benchmark.cpp b/tools/acoustic_benchmark.cpp new file mode 100644 index 0000000..d68379e --- /dev/null +++ b/tools/acoustic_benchmark.cpp @@ -0,0 +1,172 @@ +#include "FluidSim/Junction.hpp" +#include "FluidSim/Solver.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +using namespace fluidsim::port; + +namespace { +constexpr double pressure=101325, area=.001; +struct Result { double incident{}, reflected{}, transmitted{}, mass_drift{}, energy_drift{}; + std::array,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 pipes; + if(straight) pipes.emplace_back(area,4,2*n); + else for(int j=0;j 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 t{has_volume?chamber.mass():0,has_volume?chamber.internal_energy():0}; + for(const auto& p:pipes) for(int i=0;i(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;j0) || !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< first_order_transmission{}; + std::array,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" && transmitted1e-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<.08 || std::abs(std::arg(corrected))>.15)) + throw std::runtime_error("junction frequency/phase regression: "+id); + } + summary<<'\n'; + } + } + std::cout<<"Validated "< 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() diff --git a/tools/audio_capture.cpp b/tools/audio_capture.cpp index 052bef1..05ee65f 100644 --- a/tools/audio_capture.cpp +++ b/tools/audio_capture.cpp @@ -2,6 +2,7 @@ #include "FluidSim/ScenarioCatalog.hpp" #include "FluidSim/TwoStrokeScenario.hpp" #include +#include #include #include #include @@ -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::lround(std::clamp(x, -1.f, 1.f) * 32767)); - integer(static_cast(v), 2); + integer(std::bit_cast(x), 4); } } }; @@ -66,7 +66,7 @@ struct Metrics { } }; template -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 s; if constexpr (std::is_same_v) s = std::make_unique(44100, config); else if constexpr (std::is_same_v) { @@ -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;iset_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; i0) 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,4> stems; + std::array source_metrics; + for(std::size_t i=0;i(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;jsource_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;jpipes) { repairs+=pipe.positivity_repairs; fallbacks+=pipe.hllc_fallbacks; } + if constexpr (std::is_same_v || std::is_base_of_v) { + const auto diagnostics=s->snapshot(); + std::cout<<"junction_model="<junction_config.model<<" junction_fallbacks="<= 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(prefix,frames,pedal,selected,example->two,solver,fixed_rpm,warmup); - else capture(prefix,frames,pedal,selected,config,solver,fixed_rpm,warmup,&*example); + capture(prefix,frames,pedal,selected,example->two,solver,fixed_rpm,warmup,nullptr,solo); + else capture(prefix,frames,pedal,selected,config,solver,fixed_rpm,warmup,&*example,solo); } else if (scenario == "twin") - capture(prefix, frames, pedal, selected, config, solver, fixed_rpm, warmup); + capture(prefix, frames, pedal, selected, config, solver, fixed_rpm, warmup,nullptr,solo); else if (scenario == "b205") - capture(prefix, frames, pedal, selected, config, solver, fixed_rpm, warmup); + capture(prefix, frames, pedal, selected, config, solver, fixed_rpm, warmup,nullptr,solo); else if (scenario == "two-stroke") - capture(prefix, frames, pedal, selected, config, solver, fixed_rpm, warmup); + capture(prefix, frames, pedal, selected, config, solver, fixed_rpm, warmup,nullptr,solo); else if (scenario == "v8") - capture(prefix, frames, pedal, selected, config, solver, fixed_rpm, warmup); + capture(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) { diff --git a/tools/audio_preview.py b/tools/audio_preview.py new file mode 100644 index 0000000..8e82df6 --- /dev/null +++ b/tools/audio_preview.py @@ -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(" +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< +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace fluidsim::port; +using Clock = std::chrono::steady_clock; + +template +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.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(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<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 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 densities=argc>5?std::vector{std::stoi(argv[5])}: + std::vector{10,20,40,50,80}; + std::vector 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: "<(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(density); + config.two.cells_per_meter=static_cast(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<<"/"<