Add turbocharged twin scenario and cross-platform test harness
This commit is contained in:
103
tests/two_stroke_physics.cpp
Normal file
103
tests/two_stroke_physics.cpp
Normal 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';
|
||||
}
|
||||
Reference in New Issue
Block a user