#include "FluidSim/Turbocharger.hpp" #include "FluidSim/Atmosphere.hpp" #include "FluidSim/Solver.hpp" #include #include using namespace fluidsim::port; namespace { void check(bool condition, const char* message) { if (!condition) throw std::runtime_error(message); } double mass(const Turbocharger& t) { return double(t.compressor_inlet.mass())+t.compressor_outlet.mass()+t.turbine_inlet.mass()+t.turbine_outlet.mass(); } double energy(const Turbocharger& t) { return t.fluid_energy()+t.shaft_energy()+t.snapshot().bearing_heat; } void closed_balance(double initial_rpm) { Turbocharger t; t.set_initial_rpm(initial_rpm); t.turbine_inlet.set_pressure(250000, 900); const double before = energy(t), initial_mass = mass(t); for (int i = 0; i < 10000; ++i) t.update_state(1e-6f); check(std::abs(mass(t)-initial_mass) < initial_mass*3e-5, "turbo created/lost gas mass"); check(std::abs(energy(t)-before) < before*2e-4, "fluid + shaft + bearing heat energy drift"); check(t.omega() > 0, "turbine did not start its shaft from pressure energy"); const auto s = t.snapshot(); check(std::abs(s.energy_residual) < before*2e-4, "turbo energy residual excessive"); std::cout << "Closed balance initial_rpm=" << initial_rpm << " mass_error=" << mass(t)-initial_mass << " energy_error=" << energy(t)-before << " J\n"; } double turbine_flow(float downstream) { Turbocharger t; t.turbine_inlet.set_pressure(300000, 900); t.turbine_outlet.set_pressure(downstream, 600); t.update_state(1e-7f); return t.snapshot().turbine_flow; } } int main() { Turbocharger rest; const double e = energy(rest); for (int i = 0; i < 1000; ++i) rest.update_state(1.f/44100); check(rest.omega() == 0 && energy(rest) == e && rest.snapshot().compressor_pressure_ratio == 1, "stationary equilibrium generated boost/shaft energy"); closed_balance(0); closed_balance(120000); check(std::abs(turbine_flow(100000)-turbine_flow(50000)) < 1e-10, "choked turbine flow depends on back pressure"); check(turbine_flow(250000) < turbine_flow(100000), "higher backpressure did not reduce subcritical flow"); Turbocharger reverse; reverse.compressor_outlet.set_pressure(150000, 350); reverse.turbine_outlet.set_pressure(150000, 600); const auto reverse_energy = energy(reverse); reverse.update_state(1e-6f); check(reverse.snapshot().compressor_flow < 0 && reverse.snapshot().turbine_flow < 0, "turbo acted as an artificial one-way valve"); check(std::abs(energy(reverse)-reverse_energy) < reverse_energy*1e-6, "reverse flow creates energy"); Turbocharger gate; gate.compressor_outlet.set_pressure(220000, 350); gate.turbine_inlet.set_pressure(250000, 900); gate.update_state(1e-6f); check(gate.snapshot().wastegate_lift > 0 && gate.snapshot().wastegate_flow > 0, "pressure can did not open the wastegate bypass"); check(std::abs(gate.snapshot().energy_residual) < 1e-4, "wastegate bypass created energy"); Turbocharger coarse; coarse.set_initial_rpm(200000); const double coarse_energy = energy(coarse); coarse.update_state(.5f); check(coarse.omega() >= 0 && std::abs(energy(coarse)-coarse_energy) < coarse_energy*1e-5, "large turbo timestep creates energy or reverses shaft"); bool caught = false; try { TurboConfig bad; bad.shaft_inertia = 0; Turbocharger invalid(bad); } catch (const std::invalid_argument&) { caught = true; } check(caught, "zero rotor inertia accepted"); // Hot pulsing reservoir -> real pipes -> turbine -> exhaust reservoir. // Compressor feeds a pressure reservoir representing a loaded charge duct. std::vector pipes; for (int i = 0; i < 4; ++i) pipes.emplace_back(pi*.024f*.024f*.25f, .08f, 8); Atmosphere fresh, charge, hot, exhaust; charge.pressure = 130000; hot.temperature = 900; hot.pressure = 220000; charge.update_state(0); hot.update_state(0); BoundarySystem boundaries(pipes); Turbocharger turbo; turbo.connect(boundaries, pipes, 0, false, 1, true, 2, false, 3, true); std::array areas; const std::array reservoirs{&fresh, &charge, &hot, &exhaust}; for (int i = 0; i < 4; ++i) { areas[i] = pipes[i].area; boundaries.add_orifice(reservoirs[i]->port, i, i%2 == 0, areas[i]); } Solver solver(pipes, boundaries); solver.add_component(turbo); float minimum_power = 1e9f, maximum_power = -1e9f; double initial_speed = 0; for (int i = 0; i < 44100; ++i) { hot.pressure = 220000+60000*std::sin(2*pi*80*i/44100); hot.update_state(0); solver.step(1.f/44100); const auto s = turbo.snapshot(); if (i == 100) initial_speed = s.rpm; if (i > 40000) { minimum_power = std::min(minimum_power, float(s.turbine_power)); maximum_power = std::max(maximum_power, float(s.turbine_power)); } } const auto result = turbo.snapshot(); check(result.rpm > initial_speed+10000, "hot gas did not spool the coupled turbo"); check(result.compressor_flow > 0 && result.compressor_power > 0 && result.charge_temperature > 300, "loaded compressor did not deliver heated charge using shaft work"); check(maximum_power-minimum_power > 100, "exhaust pulses were averaged away before turbine torque"); for (const auto& pipe : pipes) check(pipe.positivity_repairs == 0, "turbo bench required pipe repairs"); check(result.limited_transfers == 0, "normal turbo bench depleted a storage node"); std::cout << "Turbo pipe bench passed: rpm=" << result.rpm << " compressor flow=" << result.compressor_flow << " kg/s, charge T=" << result.charge_temperature << " K, turbine power range=" << minimum_power << ".." << maximum_power << " W, residual=" << result.energy_residual << " J\n"; }