116 lines
5.8 KiB
C++
116 lines
5.8 KiB
C++
#include "FluidSim/Turbocharger.hpp"
|
|
#include "FluidSim/Atmosphere.hpp"
|
|
#include "FluidSim/Solver.hpp"
|
|
#include <iostream>
|
|
#include <stdexcept>
|
|
|
|
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<Pipe1D> 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<float, 4> areas;
|
|
const std::array<Atmosphere*, 4> 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";
|
|
}
|