Update
This commit is contained in:
@@ -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_)
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user