Add turbocharged twin scenario and cross-platform test harness

This commit is contained in:
2026-10-23 02:27:03 +02:00
parent a859740a95
commit 589d22aa5e
63 changed files with 4903 additions and 771 deletions

View File

@@ -0,0 +1,103 @@
#include "FluidSim/TwoStrokeScenario.hpp"
#include <iostream>
#include <stdexcept>
using namespace fluidsim::port;
namespace {
void check(bool condition, const char* message) {
if (!condition) throw std::runtime_error(message);
}
float reflected_pulse(bool expanding) {
const float inlet = expanding ? .03f : .08f, outlet = expanding ? .08f : .03f;
const std::array<PipeSection, 3> sections{{{.1f, inlet, inlet}, {.1f, inlet, outlet}, {.4f, outlet, outlet}}};
auto pipe = make_profiled_pipe(sections, .002f);
const float rho = 1.2f, pressure = 101325, c = std::sqrt(fluidsim::port::gamma*pressure/rho);
float x = 0;
for (std::size_t i = 0; i < pipe.cells.size(); ++i) {
const float center = x + .5f * pipe.cell_lengths[i];
const float dp = 500 * std::exp(-std::pow((center-.04f)/.006f, 2));
const float density = rho + dp/(c*c), velocity = dp/(rho*c);
pipe.cells[i] = {density, density*velocity,
(pressure+dp)/(fluidsim::port::gamma-1) + .5f*density*velocity*velocity, 1};
x += pipe.cell_lengths[i];
}
pipe.left_ghost = pipe.right_ghost = {rho, 0, pressure, 1};
float extremum = 0;
for (float time = 0; time < .0012f;) {
const float dt = pipe.stable_timestep(.5f);
pipe.advance(dt);
time += dt;
if (time > .00025f) {
const float dp = pipe.state(29).pressure-pressure;
extremum = expanding ? std::min(extremum, dp) : std::max(extremum, dp);
}
}
check(pipe.positivity_repairs == 0, "taper pulse needed positivity repairs");
return extremum;
}
}
int main() {
const std::array<PipeSection, 1> cone{{{.4f, .03f, .10f}}};
const float expected = pi/12 * .4f * (.03f*.03f + .03f*.10f + .10f*.10f);
for (float mesh : {.03f, .01f, .004f}) {
const auto pipe = make_profiled_pipe(cone, mesh);
double volume = 0;
for (std::size_t i = 0; i < pipe.cells.size(); ++i)
volume += pipe.cell_areas[i] * pipe.cell_lengths[i];
check(std::abs(volume-expected) < expected*2e-6, "frustum mesh changes physical volume");
}
const float negative = reflected_pulse(true), positive = reflected_pulse(false);
check(negative < -5, "diffuser did not return a rarefaction");
check(positive > 5, "convergent did not return a compression");
TwoStrokeScenario s;
auto& cylinder = s.cylinder;
const float closed = cylinder.compute_volume(cylinder.exhaust_close()*pi/180);
check(std::abs(closed/cylinder.clearance_volume()-8.2f) < 1e-4f,
"trapped compression was confused with geometric compression");
for (bool intake : {false, true}) {
const float opens = intake ? cylinder.transfer_open() : cylinder.exhaust_open();
const float width = intake ? cylinder.transfer_width : cylinder.exhaust_width;
const float area = pi*cylinder.bore*cylinder.bore*.25f;
const float expected_area = width * (cylinder.compute_volume(pi)-cylinder.compute_volume(opens*pi/180))/area;
check(std::abs(cylinder.valve_area(180, intake)-expected_area) < 1e-8f,
"port is not a piston-uncovered window");
float previous = 0;
for (float angle = opens; angle <= 180; angle += .5f) {
const float current = cylinder.valve_area(angle, intake);
check(current >= previous, "port does not open monotonically toward BDC");
check(std::abs(current-cylinder.valve_area(360-angle, intake)) < 1e-8f,
"piston port area is not symmetric");
previous = current;
}
check(cylinder.valve_area(0, intake) == 0 && cylinder.valve_area(360, intake) == 0,
"port is open at TDC");
}
s.ecu.step(1.f/44100, 1, 6000);
const float low_spark = s.ecu.spark_advance();
s.ecu.step(1.f/44100, 1, 17000);
check(s.ecu.spark_advance() < low_spark && !s.ecu.fuel_cut(),
"two-stroke high-speed ignition does not retard without cutting fuel");
s.vehicle.current_gear = 1;
const float first = s.vehicle.total_gear_ratio();
s.vehicle.current_gear = 6;
check(std::abs(first-30.27f)<.03f && std::abs(s.vehicle.total_gear_ratio()-13.4f)<.03f,
"YZ125 primary/final/gear reduction incorrect");
Crankshaft crank;
crank.omega = 100;
crank.load_torque = 10;
crank.friction_constant = 2;
crank.add_torque(12);
crank.step(1.f/44100);
check(std::abs(crank.omega-100)<1e-6 && std::abs(crank.average_power-1000)<.01f &&
std::abs(crank.average_torque-10)<1e-6,
"loaded steady-speed dyno lost shaft power");
crank.load_torque = 0;
crank.friction_constant = 0;
crank.friction_quadratic = .001f;
crank.step(1.f/44100);
check(crank.omega < 100 && crank.average_power < 1000, "windage is not dissipative");
std::cout << "Two-stroke physics passed: diffuser reflection=" << negative
<< " Pa, baffle reflection=" << positive << " Pa, first reduction=" << first << '\n';
}