Improve physical geometry, fluid solvers, audio, and profiling
This commit is contained in:
@@ -4,6 +4,8 @@
|
||||
#include "FluidSim/OpenEndRadiation.hpp"
|
||||
#include "FluidSim/PipeSolver.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <span>
|
||||
#include <cmath>
|
||||
#include <deque>
|
||||
#include <limits>
|
||||
@@ -181,12 +183,17 @@ public:
|
||||
std::vector<GasCell> cells;
|
||||
GasState left_ghost{}, right_ghost{};
|
||||
std::uint64_t positivity_repairs{};
|
||||
std::size_t scratch_bytes() const {
|
||||
return next_cells_.capacity()*sizeof(GasCell)+states_.capacity()*sizeof(GasState)+
|
||||
reconstructed_left_.capacity()*sizeof(GasState)+reconstructed_right_.capacity()*sizeof(GasState)+
|
||||
waves_.capacity()*sizeof(float)+faces_.capacity()*sizeof(Flux)+roe_states_.capacity()*sizeof(RoeState);
|
||||
}
|
||||
Pipe1D(float a, float length, int count)
|
||||
: Pipe1D(uniform(a, count), uniform(length / std::max(count, 1), count)) {}
|
||||
Pipe1D(std::vector<float> areas, std::vector<float> lengths)
|
||||
: area(areas.empty() ? 0 : areas.front()), dx(lengths.empty() ? 0 : lengths.front()),
|
||||
cell_areas(std::move(areas)), cell_lengths(std::move(lengths)), cells(cell_areas.size()),
|
||||
next_cells_(cells.size()), states_(cells.size()), waves_(cells.size()), faces_(cells.size() + 1), roe_states_(cells.size()) {
|
||||
next_cells_(cells.size()), states_(cells.size()), waves_(cells.size()), faces_(cells.size() + 1) {
|
||||
if (cells.empty() || cell_lengths.size() != cells.size())
|
||||
throw std::invalid_argument("pipe profiles must be nonempty and have equal length");
|
||||
for (std::size_t i = 0; i < cells.size(); ++i)
|
||||
@@ -212,6 +219,16 @@ public:
|
||||
// Solver preparation is valid until advance; boundary resolution changes
|
||||
// fluxes/ghosts, not cells. Standalone advance still prepares its own cache.
|
||||
float prepare_step(float cfl = 0.8f) {
|
||||
if(!scratch_solver_initialized_ || scratch_solver_!=solver) {
|
||||
if(solver==PipeSolver::Muscl) {
|
||||
if(reconstructed_left_.size()!=cells.size()) reconstructed_left_.resize(cells.size());
|
||||
if(reconstructed_right_.size()!=cells.size()) reconstructed_right_.resize(cells.size());
|
||||
} else if(solver==PipeSolver::Hlle || solver==PipeSolver::Hllc) {
|
||||
if(roe_states_.size()!=cells.size()) roe_states_.resize(cells.size());
|
||||
}
|
||||
scratch_solver_=solver;
|
||||
scratch_solver_initialized_=true;
|
||||
}
|
||||
float dt = std::numeric_limits<float>::max();
|
||||
for (std::size_t i = 0; i < cells.size(); ++i) {
|
||||
states_[i] = primitive(cells[i]);
|
||||
@@ -225,7 +242,7 @@ public:
|
||||
throw std::runtime_error("nonphysical pipe state in preparation");
|
||||
dt = std::min(dt, cfl * cell_lengths[i] / waves_[i]);
|
||||
}
|
||||
if (solver != PipeSolver::Rusanov)
|
||||
if (solver == PipeSolver::Hlle || solver == PipeSolver::Hllc)
|
||||
for (std::size_t i=0; i<cells.size(); ++i) {
|
||||
const auto& s=states_[i];
|
||||
const double energy=s.pressure*pipe_gm1_inverse+.5*double(s.rho)*s.velocity*s.velocity;
|
||||
@@ -327,6 +344,18 @@ public:
|
||||
static Flux numerical_flux(const GasState& a,const GasState& b,PipeSolver solver) {
|
||||
return numerical_flux(a,b,solver,std::sqrt(gamma*a.pressure/a.rho),std::sqrt(gamma*b.pressure/b.rho));
|
||||
}
|
||||
void set_acoustic_neighbor(bool left,GasState state) {
|
||||
if(left) { left_acoustic_neighbor_=state; has_left_acoustic_neighbor_=true; }
|
||||
else { right_acoustic_neighbor_=state; has_right_acoustic_neighbor_=true; }
|
||||
}
|
||||
void prepare_acoustic_reconstruction(float dt) {
|
||||
if(solver!=PipeSolver::Muscl) return;
|
||||
if(!prepared_) prepare_step();
|
||||
reconstruct(dt); reconstructed_prepared_=true;
|
||||
}
|
||||
GasState acoustic_edge(bool left) const {
|
||||
return left?reconstructed_left_.front():reconstructed_right_.back();
|
||||
}
|
||||
void advance(float dt) {
|
||||
if (dt <= 0)
|
||||
return;
|
||||
@@ -338,7 +367,18 @@ public:
|
||||
faces_[0] = left_override_ ? left_flux_ : scaled(numerical_flux(left_ghost, states_[0], solver), cell_areas[0]);
|
||||
faces_[n] =
|
||||
right_override_ ? right_flux_ : scaled(numerical_flux(states_[n - 1], right_ghost, solver), cell_areas[n - 1]);
|
||||
if (solver == PipeSolver::Rusanov) {
|
||||
if(solver==PipeSolver::Muscl) {
|
||||
if(!reconstructed_prepared_) reconstruct(dt);
|
||||
for(std::size_t i=1;i<n;++i) {
|
||||
bool fallback=false;
|
||||
const auto& a=reconstructed_right_[i-1];
|
||||
const auto& b=reconstructed_left_[i];
|
||||
faces_[i]=scaled(numerical_flux(a,b,PipeSolver::Hllc,
|
||||
std::sqrt(gamma*a.pressure/a.rho),std::sqrt(gamma*b.pressure/b.rho),&fallback),
|
||||
.5f*(cell_areas[i-1]+cell_areas[i]));
|
||||
hllc_fallbacks+=fallback;
|
||||
}
|
||||
} else if (solver == PipeSolver::Rusanov) {
|
||||
for (std::size_t i = 1; i < n; ++i)
|
||||
faces_[i] = scaled(riemann(states_[i-1], states_[i], std::max(waves_[i-1], waves_[i])),
|
||||
.5f*(cell_areas[i-1]+cell_areas[i]));
|
||||
@@ -400,6 +440,7 @@ public:
|
||||
}
|
||||
cells.swap(next_cells_);
|
||||
prepared_ = false;
|
||||
reconstructed_prepared_=has_left_acoustic_neighbor_=has_right_acoustic_neighbor_=false;
|
||||
left_override_ = right_override_ = false;
|
||||
}
|
||||
static Flux scaled(Flux f, float a) {
|
||||
@@ -409,11 +450,53 @@ public:
|
||||
private:
|
||||
std::vector<GasCell> next_cells_;
|
||||
std::vector<GasState> states_;
|
||||
std::vector<GasState> reconstructed_left_, reconstructed_right_;
|
||||
std::vector<float> waves_;
|
||||
std::vector<Flux> faces_;
|
||||
std::vector<RoeState> roe_states_;
|
||||
PipeSolver scratch_solver_{PipeSolver::Rusanov};
|
||||
bool scratch_solver_initialized_{};
|
||||
Flux left_flux_{}, right_flux_{};
|
||||
bool left_override_{}, right_override_{}, prepared_{};
|
||||
GasState left_acoustic_neighbor_{}, right_acoustic_neighbor_{};
|
||||
bool has_left_acoustic_neighbor_{}, has_right_acoustic_neighbor_{}, reconstructed_prepared_{};
|
||||
// Primitive minmod MUSCL-Hancock on uniform sections. Component interfaces
|
||||
// and area/profile transitions retain the existing first-order coupling.
|
||||
void reconstruct(float dt) {
|
||||
const auto minmod=[](float a,float b) {
|
||||
return a*b<=0 ? 0.f : std::copysign(std::min(std::abs(a),std::abs(b)),a);
|
||||
};
|
||||
for(std::size_t i=0;i<states_.size();++i) {
|
||||
const auto q=states_[i];
|
||||
reconstructed_left_[i]=reconstructed_right_[i]=q;
|
||||
if((i==0 && !has_left_acoustic_neighbor_) ||
|
||||
(i+1==states_.size() && !has_right_acoustic_neighbor_) ||
|
||||
(i>0 && (cell_areas[i-1]!=cell_areas[i] || cell_lengths[i-1]!=cell_lengths[i])) ||
|
||||
(i+1<states_.size() && (cell_areas[i+1]!=cell_areas[i] || cell_lengths[i+1]!=cell_lengths[i]))) continue;
|
||||
const auto a=i==0?left_acoustic_neighbor_:states_[i-1];
|
||||
const auto b=i+1==states_.size()?right_acoustic_neighbor_:states_[i+1];
|
||||
const float dr=minmod(q.rho-a.rho,b.rho-q.rho);
|
||||
const float du=minmod(q.velocity-a.velocity,b.velocity-q.velocity);
|
||||
const float dp=minmod(q.pressure-a.pressure,b.pressure-q.pressure);
|
||||
const float da=minmod(q.air-a.air,b.air-q.air);
|
||||
const float df=minmod(q.fuel-a.fuel,b.fuel-q.fuel);
|
||||
const float h=.5f*dt/cell_lengths[i];
|
||||
const GasState half{q.rho-h*(q.velocity*dr+q.rho*du),
|
||||
q.velocity-h*(q.velocity*du+dp/q.rho),
|
||||
q.pressure-h*(q.velocity*dp+gamma*q.pressure*du),
|
||||
q.air-h*q.velocity*da,q.fuel-h*q.velocity*df};
|
||||
const auto edge=[&](float sign) { return GasState{half.rho+sign*dr,
|
||||
half.velocity+sign*du,half.pressure+sign*dp,half.air+sign*da,half.fuel+sign*df}; };
|
||||
const auto left=edge(-.5f), right=edge(.5f);
|
||||
const auto physical=[](const GasState& v) {
|
||||
return v.rho>1e-12f && v.pressure>1e-6f && std::isfinite(v.pressure) &&
|
||||
std::isfinite(v.rho) && std::isfinite(v.velocity) && v.air>=0 && v.fuel>=0 && v.air+v.fuel<=1.000001f;
|
||||
};
|
||||
if(physical(left) && physical(right)) {
|
||||
reconstructed_left_[i]=left; reconstructed_right_[i]=right;
|
||||
}
|
||||
}
|
||||
}
|
||||
static std::vector<float> uniform(float v, int n) {
|
||||
if (n <= 0)
|
||||
throw std::invalid_argument("pipe count must be positive");
|
||||
@@ -514,25 +597,39 @@ public:
|
||||
claim(b, left_b);
|
||||
connections_.push_back({a, b, left_a, left_b});
|
||||
}
|
||||
struct JunctionFace { int pipe; bool left; };
|
||||
void add_junction(std::initializer_list<JunctionFace> ends) {
|
||||
if(ends.size()<2) throw std::invalid_argument("junction needs at least two faces");
|
||||
JunctionNode node;
|
||||
struct JunctionFace {
|
||||
int pipe; bool left;
|
||||
// Unit vector pointing OUT of the node into this branch, in world axes.
|
||||
std::array<float,3> direction{};
|
||||
};
|
||||
enum class JunctionMethod { Characteristic, LegacyRusanov };
|
||||
void add_junction(std::initializer_list<JunctionFace> ends,
|
||||
JunctionMethod method=JunctionMethod::Characteristic,float turning_loss=0) {
|
||||
add_junction(std::span(ends.begin(),ends.size()),method,turning_loss);
|
||||
}
|
||||
void add_junction(std::span<const JunctionFace> ends,
|
||||
JunctionMethod method=JunctionMethod::Characteristic,float turning_loss=0) {
|
||||
if(ends.size()<2 || !std::isfinite(turning_loss) || turning_loss<0 || turning_loss>10)
|
||||
throw std::invalid_argument("invalid junction size or turning loss");
|
||||
JunctionNode node; node.method=method; node.turning_loss=turning_loss;
|
||||
for(auto e:ends) {
|
||||
check_face(e.pipe,e.left);
|
||||
for(auto previous:node.ends) {
|
||||
for(auto previous:node.ends)
|
||||
if(previous.pipe==e.pipe && previous.left==e.left) throw std::invalid_argument("duplicate junction face");
|
||||
}
|
||||
double norm=0;
|
||||
for(float v:e.direction) { if(!std::isfinite(v)) throw std::invalid_argument("invalid junction direction"); norm+=double(v)*v; }
|
||||
if(norm>0) for(float& v:e.direction) v/=std::sqrt(norm);
|
||||
else if(turning_loss>0) throw std::invalid_argument("direction required for turning loss");
|
||||
node.ends.push_back(e);
|
||||
}
|
||||
for(auto e:ends) claim(e.pipe,e.left);
|
||||
node.states.resize(ends.size());
|
||||
node.states.resize(ends.size()); node.faces.resize(ends.size());
|
||||
nodes_.push_back(std::move(node));
|
||||
}
|
||||
float open_end_mass_flow(std::size_t i) const { return opens_.at(i).last_mass_flow; }
|
||||
float open_end_volume_flow(std::size_t i) const { return opens_.at(i).last_volume_flow; }
|
||||
float open_end_pressure(std::size_t i) const { return opens_.at(i).last_face_pressure; }
|
||||
std::uint64_t limited_fluxes{}, nonlinear_iterations{};
|
||||
std::uint64_t limited_fluxes{}, nonlinear_iterations{}, junction_fallbacks{}, junction_iterations{}, junction_limited_fluxes{};
|
||||
void validate_topology() const {
|
||||
if (claims_.size() != 2 * pipes_.size())
|
||||
throw std::runtime_error("every pipe endpoint needs an explicit boundary or connection");
|
||||
@@ -544,6 +641,20 @@ public:
|
||||
b.mass = .25f * (*b.air + (b.exhaust ? *b.exhaust : 0) + (b.fuel ? *b.fuel : 0));
|
||||
b.heat = b.energy ? .25f * (*b.energy) : std::numeric_limits<float>::max();
|
||||
}
|
||||
// Supply matching direct-pipe neighbors before reconstruction, preserving
|
||||
// the same stencil across artificial pipe splits and all orientations.
|
||||
for(const auto& o:connections_) {
|
||||
auto& a=pipes_[o.a]; auto& b=pipes_[o.b];
|
||||
const int ia=end(a,o.left_a), ib=end(b,o.left_b);
|
||||
if(a.solver==PipeSolver::Muscl && b.solver==PipeSolver::Muscl &&
|
||||
a.cell_area(ia)==b.cell_area(ib) && a.cell_length(ia)==b.cell_length(ib)) {
|
||||
auto sa=a.state(ia), sb=b.state(ib);
|
||||
const float sign=o.left_a==o.left_b?-1.f:1.f;
|
||||
sa.velocity*=sign; sb.velocity*=sign;
|
||||
a.set_acoustic_neighbor(o.left_a,sb); b.set_acoustic_neighbor(o.left_b,sa);
|
||||
}
|
||||
}
|
||||
for(auto& pipe:pipes_) pipe.prepare_acoustic_reconstruction(dt);
|
||||
for (auto &o : orifices_)
|
||||
resolve_orifice(o, dt);
|
||||
for (auto &o : opens_)
|
||||
@@ -557,12 +668,21 @@ private:
|
||||
struct JunctionNode {
|
||||
std::vector<JunctionFace> ends;
|
||||
std::vector<GasState> states;
|
||||
struct Face { double pressure{},rho{},velocity{},mass{},enthalpy{},air{},fuel{},loss{}; };
|
||||
std::vector<Face> faces;
|
||||
JunctionMethod method{JunctionMethod::Characteristic};
|
||||
double turning_loss{};
|
||||
};
|
||||
std::vector<JunctionNode> nodes_;
|
||||
// Zero-storage Rusanov star state. Integrated mass, total energy and
|
||||
// species fluxes sum to zero algebraically across every connected face.
|
||||
// The node has no conserved state, timestep or storage volume of its own.
|
||||
void resolve_junction(JunctionNode& node,float) {
|
||||
void resolve_junction(JunctionNode& node,float dt) {
|
||||
if(node.method==JunctionMethod::Characteristic && characteristic_junction(node,dt)) return;
|
||||
if(node.method==JunctionMethod::Characteristic) ++junction_fallbacks;
|
||||
legacy_junction(node);
|
||||
}
|
||||
void legacy_junction(JunctionNode& node) {
|
||||
double speed=0;
|
||||
for(std::size_t k=0;k<node.ends.size();++k) {
|
||||
auto e=node.ends[k];auto& p=pipes_[e.pipe];auto s=p.state(end(p,e.left));
|
||||
@@ -593,6 +713,135 @@ private:
|
||||
p.set_boundary_flux(e.left,{float(orientation*m),float(momentum),float(orientation*heat),float(orientation*y),float(orientation*f)});
|
||||
}
|
||||
}
|
||||
// Nonlinear outgoing acoustic-wave curves (shock or rarefaction), with
|
||||
// common node pressure, conservative stagnation-enthalpy/species mixing,
|
||||
// and outgoing contact density set from the mixed enthalpy. This is a
|
||||
// compact subsonic engineering closure, not a resolved 3D junction.
|
||||
bool characteristic_junction(JunctionNode& node,float dt) {
|
||||
double low=std::numeric_limits<double>::max(), high=0;
|
||||
for(std::size_t k=0;k<node.ends.size();++k) {
|
||||
const auto e=node.ends[k]; const auto& pipe=pipes_[e.pipe];
|
||||
auto state=pipe.state(end(pipe,e.left));
|
||||
state.velocity*=e.left?-1:1; // positive INTO the node
|
||||
const double c=std::sqrt(gamma*state.pressure/state.rho);
|
||||
if(std::abs(state.velocity)>=.95*c) return false;
|
||||
node.states[k]=state;
|
||||
low=std::min(low,double(state.pressure)); high=std::max(high,double(state.pressure));
|
||||
node.faces[k].loss=0;
|
||||
}
|
||||
const auto wave_curve=[](const GasState& q,double p) {
|
||||
if(p>q.pressure) {
|
||||
const double a=2/((gamma+1)*q.rho), b=(gamma-1)/(gamma+1)*q.pressure;
|
||||
return (p-q.pressure)*std::sqrt(a/(p+b));
|
||||
}
|
||||
const double c=std::sqrt(gamma*q.pressure/q.rho);
|
||||
return 2*c/(gamma-1)*(std::pow(p/q.pressure,(gamma-1)/(2*gamma))-1);
|
||||
};
|
||||
const auto density=[](const GasState& q,double p) {
|
||||
const double ratio=p/q.pressure, b=(gamma-1)/(gamma+1);
|
||||
return p>q.pressure ? q.rho*(ratio+b)/(b*ratio+1) : q.rho*std::pow(ratio,1/gamma);
|
||||
};
|
||||
// Each pressure evaluation writes scratch face states only, never pipes.
|
||||
const auto residual=[&](double pressure) {
|
||||
double incoming=0, heat=0, air=0, fuel=0;
|
||||
for(std::size_t k=0;k<node.ends.size();++k) {
|
||||
const auto q=node.states[k]; auto& f=node.faces[k];
|
||||
double p=pressure;
|
||||
for(int iteration=0;iteration<(f.loss>0?8:1);++iteration) {
|
||||
const double u=q.velocity-wave_curve(q,p), rho=density(q,p);
|
||||
const double target=std::clamp(pressure+std::copysign(.5*f.loss*rho*u*u,u),pressure*.05,pressure*20);
|
||||
p=.5*(p+target);
|
||||
}
|
||||
f.pressure=p; f.rho=density(q,p); f.velocity=q.velocity-wave_curve(q,p);
|
||||
const auto e=node.ends[k]; const auto& pipe=pipes_[e.pipe];
|
||||
f.mass=pipe.cell_area(end(pipe,e.left))*f.rho*f.velocity;
|
||||
f.enthalpy=gamma/(gamma-1)*p/f.rho+.5*f.velocity*f.velocity;
|
||||
f.air=q.air; f.fuel=q.fuel;
|
||||
if(f.mass>0) { incoming+=f.mass; heat+=f.mass*f.enthalpy; air+=f.mass*q.air; fuel+=f.mass*q.fuel; }
|
||||
}
|
||||
double balance=0;
|
||||
for(std::size_t k=0;k<node.ends.size();++k) {
|
||||
auto& f=node.faces[k];
|
||||
if(f.mass<0 && incoming>1e-20) {
|
||||
const double h=heat/incoming, thermal=h-.5*f.velocity*f.velocity;
|
||||
if(!(thermal>0)) return std::numeric_limits<double>::quiet_NaN();
|
||||
f.rho=gamma/(gamma-1)*f.pressure/thermal;
|
||||
const auto e=node.ends[k]; const auto& pipe=pipes_[e.pipe];
|
||||
f.mass=pipe.cell_area(end(pipe,e.left))*f.rho*f.velocity;
|
||||
f.enthalpy=h; f.air=air/incoming; f.fuel=fuel/incoming;
|
||||
}
|
||||
balance+=f.mass;
|
||||
}
|
||||
return balance;
|
||||
};
|
||||
// Iterate the donor-weighted direction coefficients separately from the
|
||||
// scalar pressure root; K=0 remains the ideal acoustic scattering case.
|
||||
double node_pressure=.5*(low+high);
|
||||
for(int geometry=0;geometry<(node.turning_loss>0?3:1);++geometry) {
|
||||
double lo=low*.01, hi=high*100;
|
||||
double fl=residual(lo), fh=residual(hi);
|
||||
if(!std::isfinite(fl) || !std::isfinite(fh) || fl<0 || fh>0) return false;
|
||||
node_pressure=std::clamp(node_pressure,lo,hi);
|
||||
bool converged=false;
|
||||
for(int iteration=0;iteration<40;++iteration) {
|
||||
const double value=residual(node_pressure); ++junction_iterations;
|
||||
if(!std::isfinite(value)) return false;
|
||||
double magnitude=0; for(const auto& face:node.faces) magnitude+=std::abs(face.mass);
|
||||
if(std::abs(value)<1e-14+1e-8*magnitude) { converged=true; break; }
|
||||
if(value>0) lo=node_pressure; else hi=node_pressure;
|
||||
const double delta=std::max(1e-3,node_pressure*1e-5);
|
||||
const double derivative=(residual(node_pressure+delta)-value)/delta;
|
||||
const double next=node_pressure-value/derivative;
|
||||
node_pressure=std::isfinite(next) && next>lo && next<hi ? next : .5*(lo+hi);
|
||||
}
|
||||
if(!converged) return false;
|
||||
residual(node_pressure);
|
||||
if(geometry+1<(node.turning_loss>0?3:1)) {
|
||||
for(std::size_t k=0;k<node.ends.size();++k) {
|
||||
auto& f=node.faces[k]; double weight=0, loss=0;
|
||||
if(f.mass<0) for(std::size_t j=0;j<node.ends.size();++j) {
|
||||
const double m=std::max(0.,node.faces[j].mass); double dot=0;
|
||||
for(int axis=0;axis<3;++axis) dot+=node.ends[k].direction[axis]*node.ends[j].direction[axis];
|
||||
weight+=m; loss+=m*.5*(1+std::clamp(dot,-1.,1.));
|
||||
}
|
||||
f.loss=weight>0?node.turning_loss*loss/weight:0;
|
||||
}
|
||||
}
|
||||
}
|
||||
double incoming=0, outgoing=0, heat=0, air=0, fuel=0, scale=1;
|
||||
for(std::size_t k=0;k<node.ends.size();++k) {
|
||||
const auto& f=node.faces[k];
|
||||
if(!std::isfinite(f.mass) || !std::isfinite(f.enthalpy)) return false;
|
||||
if(std::abs(f.velocity)>=.98*std::sqrt(gamma*f.pressure/f.rho)) return false;
|
||||
if(f.mass>0) { incoming+=f.mass;heat+=f.mass*f.enthalpy;air+=f.mass*f.air;fuel+=f.mass*f.fuel; }
|
||||
else outgoing-=f.mass;
|
||||
}
|
||||
if(incoming>1e-16 && outgoing<=1e-16) {
|
||||
if(incoming>1e-11) return false;
|
||||
// Residual tolerance at rest must not create a one-sided mass source.
|
||||
for(auto& face:node.faces) face.mass=0;
|
||||
incoming=heat=air=fuel=0;
|
||||
}
|
||||
// Remove root roundoff by scaling all outflows together. A shared donor
|
||||
// budget scales advective fluxes together, preserving node conservation.
|
||||
for(std::size_t k=0;k<node.ends.size();++k) {
|
||||
auto& f=node.faces[k];
|
||||
if(f.mass<0) { f.mass*=outgoing>0?incoming/outgoing:0; f.enthalpy=incoming>0?heat/incoming:0; f.air=incoming>0?air/incoming:0; f.fuel=incoming>0?fuel/incoming:0; }
|
||||
if(f.mass>0) {
|
||||
const auto e=node.ends[k]; const auto& pipe=pipes_[e.pipe];const int i=end(pipe,e.left);
|
||||
scale=std::min(scale,.25*pipe.cell_mass(i)/(f.mass*dt));
|
||||
scale=std::min(scale,.25*double(pipe.cells[i].energy)*pipe.cell_area(i)*pipe.cell_length(i)/(f.mass*f.enthalpy*dt));
|
||||
}
|
||||
}
|
||||
if(scale<1) { ++limited_fluxes; ++junction_limited_fluxes; }
|
||||
for(std::size_t k=0;k<node.ends.size();++k) {
|
||||
const auto e=node.ends[k]; auto& pipe=pipes_[e.pipe]; const auto& f=node.faces[k];
|
||||
const double orientation=e.left?-1:1, m=f.mass*scale;
|
||||
const double momentum=m*f.velocity+f.pressure*pipe.cell_area(end(pipe,e.left));
|
||||
pipe.set_boundary_flux(e.left,{float(orientation*m),float(momentum),float(orientation*m*f.enthalpy),float(orientation*m*f.air),float(orientation*m*f.fuel)});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
struct Budget {
|
||||
float *air{}, *exhaust{}, *fuel{}, *energy{};
|
||||
float mass{}, heat{};
|
||||
@@ -864,6 +1113,9 @@ private:
|
||||
auto &b = pipes_[o.b];
|
||||
int ia = end(a, o.left_a), ib = end(b, o.left_b);
|
||||
auto sa = a.state(ia), sb = b.state(ib);
|
||||
if(a.solver==PipeSolver::Muscl && b.solver==PipeSolver::Muscl) {
|
||||
sa=a.acoustic_edge(o.left_a); sb=b.acoustic_edge(o.left_b);
|
||||
}
|
||||
const float da = o.left_a ? -1.f : 1.f, db = o.left_b ? 1.f : -1.f;
|
||||
sa.velocity *= da;
|
||||
sb.velocity *= db;
|
||||
|
||||
Reference in New Issue
Block a user