This commit is contained in:
2026-10-23 03:00:20 +02:00
parent 589d22aa5e
commit 7a5c77c1c8
31 changed files with 1196 additions and 44 deletions

View File

@@ -9,12 +9,12 @@ namespace fluidsim::port {
// the twin already protects its own simulation state.
class DesktopScenario {
public:
explicit DesktopScenario(ScenarioKind kind, TwoStrokeConfig config = {}) {
explicit DesktopScenario(ScenarioKind kind, TwoStrokeConfig config = {}, bool saab_turbo = true) {
if (kind == ScenarioKind::TwoStroke)
two_ = std::make_unique<TwoStrokeScenario>(44100, config);
else
twin_ = std::make_unique<FourStrokeScenario>(44100, kind == ScenarioKind::SaabB205 ?
FourStrokePreset::SaabB205 : FourStrokePreset::Twin, kind == ScenarioKind::TurboTwin);
FourStrokePreset::SaabB205 : FourStrokePreset::Twin, kind == ScenarioKind::TurboTwin || (kind == ScenarioKind::SaabB205 && saab_turbo));
}
AudioFrame process() {
if (twin_)

View File

@@ -17,6 +17,12 @@ public:
float overrun_cut_rpm{3500.0f}, overrun_resume_rpm{2400.0f};
float idle_spark_gain{0.0f}, idle_minimum_advance{-5.0f}; // degrees/RPM, degrees BTDC
float stoichiometric_afr{14.7f}, power_afr{12.8f};
bool boost_control_enabled{false};
float maximum_boost{45000}; // Pa gauge, estimated low-pressure B205 calibration
float boost_kp{1e-5f}, boost_ki{4e-6f};
void control_boost(float dt, float pedal, float rpm, float manifold, float charge, float ambient = 101325);
float boost_target() const { return boost_target_; }
float wastegate_duty() const { return boost_duty_; }
void step(float dt, float pedal, float rpm);
float fuel_for_air(float trapped_air_mass) const;
@@ -24,12 +30,14 @@ public:
float throttle_target() const { return target_; }
float target_afr() const { return afr_; }
float spark_advance() const { return spark_; }
bool fuel_cut() const { return limiter_ || overrun_ || fault_; }
bool fuel_cut() const { return limiter_ || overrun_ || fault_ || boost_cut_; }
bool rev_limited() const { return limiter_; }
bool idle_active() const { return idle_; }
bool sensor_fault() const { return fault_; }
private:
float boost_target_{}, boost_duty_{}, boost_integral_{}, filtered_boost_{};
bool boost_cut_{};
float filtered_rpm_{};
bool initialized_{};
float opening_{}, target_{}, integral_{}, afr_{14.7f}, spark_{10.0f};

View File

@@ -189,10 +189,14 @@ public:
float dt = std::numeric_limits<float>::max();
for (std::size_t i = 0; i < cells.size(); ++i) {
states_[i] = primitive(cells[i]);
waves_[i] = wave(states_[i]);
}
// Keep the arithmetic loop free of exceptional control flow so SIMD
// can evaluate independent cells, then validate before any flux update.
for (std::size_t i = 0; i < cells.size(); ++i) {
const auto &s = states_[i];
if (!(s.rho > 0) || !(s.pressure > 0) || !std::isfinite(s.velocity) || !std::isfinite(s.pressure))
throw std::runtime_error("nonphysical pipe state in preparation");
waves_[i] = wave(s);
dt = std::min(dt, cfl * cell_lengths[i] / waves_[i]);
}
prepared_ = true;
@@ -265,7 +269,12 @@ public:
en = minimum;
++positivity_repairs;
}
next_cells_[i] = {rho, mom, en, std::clamp(air / rho, 0.0f, 1.0f)};
// Purged tracer tails below the smallest normal float carry less
// than 1.18e-38 of the gas mass. Avoid repeatedly transporting
// subnormal concentrations; total gas mass and energy are unchanged.
const float fraction = std::clamp(air / rho, 0.0f, 1.0f);
next_cells_[i] = {rho, mom, en,
fraction < std::numeric_limits<float>::min() ? 0.0f : fraction};
}
cells.swap(next_cells_);
prepared_ = false;
@@ -399,6 +408,7 @@ private:
float *area{};
float cd{1}, length{}, loss{}, mdot{};
std::size_t budget{std::numeric_limits<std::size_t>::max()};
double previous_pressure{};
};
struct Open {
int pipe;
@@ -491,7 +501,8 @@ private:
const double zero_pressure = s.pressure * std::pow(zero_sound / c, 2 * g / (g - 1));
double low = std::min(zero_pressure, double(v.pressure));
double high = std::max(zero_pressure, double(v.pressure));
double duct_pressure = .5 * (low + high);
double duct_pressure = o.length == 0 && o.previous_pressure > low && o.previous_pressure < high
? o.previous_pressure : .5 * (low + high);
const double flow_sign = v.pressure >= zero_pressure ? 1 : -1;
const double flow_scale = area * o.cd;
const double density_ratio_scale = std::pow(double(s.pressure) / v.pressure, 1 / g);
@@ -537,6 +548,7 @@ private:
? candidate
: .5 * (low + high);
}
o.previous_pressure = duct_pressure;
float mdot = static_cast<float>(nozzle_flow(duct_pressure));
const bool upstream_port = mdot >= 0;
const float pup = upstream_port ? v.pressure : static_cast<float>(duct_pressure);

View File

@@ -24,6 +24,7 @@ enum class FourStrokePreset { Twin, SaabB205 };
struct ScenarioSnapshot {
bool turbo_enabled{};
float boost_target{}, wastegate_duty{};
TurboSnapshot turbo{};
int cylinder_count{2};
float rev_limit_rpm{10000};
@@ -115,5 +116,9 @@ class SaabB205Scenario : public FourStrokeScenario {
public:
explicit SaabB205Scenario(int sample_rate = 44100) : FourStrokeScenario(sample_rate, FourStrokePreset::SaabB205) {}
};
class SaabTurboScenario : public FourStrokeScenario {
public:
explicit SaabTurboScenario(int sample_rate = 44100) : FourStrokeScenario(sample_rate, FourStrokePreset::SaabB205, true) {}
};
} // namespace fluidsim::port

View File

@@ -9,6 +9,7 @@ namespace fluidsim::port {
// RHB31/VZ21-size mean-line approximation. Geometry, losses and coefficients
// are estimates, not a digitized or validated manufacturer performance map.
struct TurboConfig {
static TurboConfig gt17(); // estimated Saab GT1752-size calibration
double shaft_inertia{4e-6}; // kg m^2, compressor+turbine+shaft rigid rotor
double compressor_radius{.018}, turbine_radius{.0135};
double compressor_head_coefficient{.60}, compressor_efficiency{.65};
@@ -58,6 +59,7 @@ public:
double fluid_energy() const;
// Intended for standalone initialization/tests, never an imposed running speed.
void set_initial_rpm(double rpm);
void set_wastegate_duty(double duty) { gate_duty_ = std::isfinite(duty) ? std::clamp(duty, 0., .85) : 0.; }
private:
std::array<Port*, 4> internal_;
@@ -68,6 +70,7 @@ private:
double wastegate_flow_{}, wastegate_lift_{};
std::uint64_t limited_transfers_{};
bool connected_{};
double gate_duty_{}; // pneumatic bleed fraction; zero restores spring boost
static double nozzle(double upstream_pressure, double upstream_temperature,
double downstream_pressure, double area);
double transfer_flow(Volume0D& inlet, Volume0D& outlet, Port& inlet_external,