Files
FluidSimCpp/tests/two_stroke_regression.cpp

88 lines
4.3 KiB
C++

#include "FluidSim/TwoStrokeScenario.hpp"
#include "FluidSim/DynoCurve.hpp"
#include <algorithm>
#include <cmath>
#include <iostream>
#include <stdexcept>
using namespace fluidsim::port;
int main() {
TwoStrokeScenario scenario;
bool reed_open_seen = false, reed_closed_seen = false, above_old_limit = false;
DynoCurve dyno;
float peak = 0, maximum_rpm = 0;
for (int sample = 0; sample < 14 * 44100; ++sample) {
scenario.set_throttle(sample >= 2 * 44100 && sample < 6 * 44100 ? .6f : 0.f);
const auto audio = scenario.process();
if (!std::isfinite(audio.left) || !std::isfinite(audio.right))
throw std::runtime_error("Nonfinite two-stroke audio");
peak = std::max({peak, std::abs(audio.left), std::abs(audio.right)});
if (sample % 44 != 0)
continue;
const auto state = scenario.snapshot();
if (!std::isfinite(state.rpm) || !(state.cylinder_pressure > 0) ||
!(state.crankcase_pressure > 0) || !(state.intake_pressure > 0))
throw std::runtime_error("Invalid two-stroke physical state");
if (scenario.ecu.rev_limited() || scenario.cylinder.rev_limit_active)
throw std::runtime_error("Two-stroke rev limiter is active");
if (sample >= 2 * 44100 && sample < 6 * 44100) {
if (state.fuel_cut)
throw std::runtime_error("Two-stroke cut fuel with the pedal open");
above_old_limit |= state.rpm > 10200;
}
dyno.update(state.rpm, scenario.crankshaft.average_power / 1000,
scenario.crankshaft.average_torque);
reed_open_seen |= state.reed_open;
reed_closed_seen |= !state.reed_open;
maximum_rpm = std::max(maximum_rpm, state.rpm);
}
const auto final = scenario.snapshot();
if (!above_old_limit)
throw std::runtime_error("Two-stroke did not exceed the old rev limit");
if (!reed_open_seen || !reed_closed_seen)
throw std::runtime_error("Reed did not open and close");
if (final.rpm < 2000 || final.rpm > 4000)
throw std::runtime_error("Two-stroke idle did not recover after throttle release");
if (peak >= 1)
throw std::runtime_error("Two-stroke audio exceeds device range");
for (const auto &pipe : scenario.pipes)
if (pipe.positivity_repairs != 0)
throw std::runtime_error("Two-stroke required pipe positivity repairs");
// Launch the same motorcycle used by the twin, then exercise gear controls.
scenario.shift_up();
scenario.set_controls(.7f, 0, 0);
for (int sample = 0; sample < 6 * 44100; ++sample) {
const auto audio = scenario.process();
if (!std::isfinite(audio.left) || !std::isfinite(audio.right))
throw std::runtime_error("Nonfinite motorcycle launch audio");
if (scenario.ecu.rev_limited())
throw std::runtime_error("Limiter re-enabled under motorcycle load");
}
if (scenario.vehicle.current_gear != 1 || scenario.vehicle.speed < 1 ||
!std::isfinite(scenario.vehicle.kinetic_energy()) || scenario.vehicle.engagement < .9f)
throw std::runtime_error("Two-stroke motorcycle did not launch with clutch engaged");
scenario.shift_up();
if (scenario.vehicle.current_gear != 2)
throw std::runtime_error("Two-stroke did not shift up");
scenario.shift_down();
if (scenario.vehicle.current_gear != 1)
throw std::runtime_error("Two-stroke did not shift down");
scenario.set_neutral();
if (scenario.vehicle.current_gear != 0)
throw std::runtime_error("Two-stroke neutral selection failed");
bool positive_power = false, positive_torque = false;
for (const auto& point : dyno.bins()) {
positive_power |= point.power_kw > 0;
positive_torque |= point.torque_nm > 0;
}
if (!positive_power || !positive_torque)
throw std::runtime_error("Two-stroke dyno did not record power and torque");
for (const auto& pipe : scenario.pipes)
if (pipe.positivity_repairs != 0)
throw std::runtime_error("Motorcycle launch required pipe positivity repairs");
std::cout << "Two-stroke regression passed: peak=" << peak << ", maximum RPM=" << maximum_rpm
<< ", recovered RPM=" << final.rpm << ", bike speed=" << scenario.vehicle.speed * 3.6f
<< " km/h\n";
}