#include "FluidSim/Junction.hpp" #include "FluidSim/Solver.hpp" #include #include #include #include #include #include #include #include using namespace fluidsim::port; namespace { constexpr double pressure=101325, area=.001; struct Result { double incident{}, reflected{}, transmitted{}, mass_drift{}, energy_drift{}; std::array,2> incident_spectrum{}, reflected_spectrum{}, transmitted_spectrum{}; std::uint64_t repairs{}, limited{}, steps{}, fallbacks{}; }; Result run(const std::string& kind,int n,float courant,PipeSolver scheme, double volume,float discharge,const std::filesystem::path& trace) { const bool straight=kind=="straight", split=kind=="split", star=kind=="star" || kind=="legacy-star" || kind=="star-unequal"; const int branches=star || kind=="volume-y" ? 3 : 2; Junction chamber(float(volume),pressure,300); const double rho=chamber.density(), c=std::sqrt(1.4*pressure/rho), z=rho*c; std::vector pipes; if(straight) pipes.emplace_back(area,4,2*n); else for(int j=0;j ends{{0,false,discharge},{1,true,discharge}}; if(branches==3) ends.push_back({2,true,discharge}); chamber.connect(boundaries,pipes,ends); } } Solver solver(pipes,boundaries); solver.minimum_substeps=1; const bool has_volume=!straight && !split && !star; if(has_volume) solver.add_component(chamber); auto totals=[&] { std::array t{has_volume?chamber.mass():0,has_volume?chamber.internal_energy():0}; for(const auto& p:pipes) for(int i=0;i(0,-2*3.141592653589793*hz*time))*double(dt); r.incident_spectrum[frequency]+=plus*weight; r.reflected_spectrum[frequency]+=minus*weight; r.transmitted_spectrum[frequency]+=(b.pressure-pressure+z*b.velocity)*.5*weight; } r.incident+=plus*plus*area/z*dt; r.reflected+=minus*minus*area/z*dt; if(straight) { const double v=(b.pressure-pressure+z*b.velocity)/2; r.transmitted+=v*v*area/z*dt; } else for(int j=1;j0) || !std::isfinite(r.transmitted) || r.repairs || std::abs(r.mass_drift)>1e-4 || std::abs(r.energy_drift)>1e-4) throw std::runtime_error("acoustic benchmark robustness/conservation failure: "+trace.string()+" mass="+std::to_string(r.mass_drift)+" energy="+std::to_string(r.energy_drift)+" limited="+std::to_string(r.limited)+" repairs="+std::to_string(r.repairs)); return r; } } int main(int argc,char** argv) { try { const std::filesystem::path dir=argc>1?argv[1]:"out/acoustics"; std::filesystem::create_directories(dir); std::ofstream summary(dir/"summary.csv"); summary< first_order_transmission{}; std::array,2> straight_spectrum{}; int cases=0, flagged=0; for(const auto* scheme:{"rusanov","hlle","hllc","muscl"}) for(int n:{40,80,160}) for(float courant:{.4f,.8f}) for(const auto* kind:{"straight","split","star","legacy-star","star-unequal","volume","volume-y"}) for(double volume:{.00005,.0005,.005}) { if((std::string(kind)=="straight" || std::string(kind)=="split" || (std::string(kind)=="star" || std::string(kind)=="legacy-star" || std::string(kind)=="star-unequal")) && volume!=.00005) continue; for(float discharge:{1.f,.7f}) { if((std::string(kind)=="straight" || std::string(kind)=="split" || (std::string(kind)=="star" || std::string(kind)=="legacy-star" || std::string(kind)=="star-unequal")) && discharge!=1) continue; const std::string id=std::string(kind)+"-"+scheme+"-"+std::to_string(n)+"-"+std::to_string(courant)+"-"+std::to_string(volume)+"-"+std::to_string(discharge); const auto r=run(kind,n,courant,parse_pipe_solver(scheme),volume,discharge,dir/(id+".csv")); const double reflected=r.reflected/r.incident, transmitted=r.transmitted/r.incident; if(std::string(kind)=="straight") { straight_transmission=transmitted; straight_reflection=reflected; for(int frequency=0;frequency<2;++frequency) straight_spectrum[frequency]=r.transmitted_spectrum[frequency]/r.incident_spectrum[frequency]; const int slot=(n==40?0:n==80?2:4)+(courant>.5f?1:0); if(std::string(scheme)=="rusanov") first_order_transmission[slot]=transmitted; if(std::string(scheme)=="muscl" && transmitted1e-4 || transmitted<=0 || transmitted>1.01) throw std::runtime_error("straight acoustic propagation regression"); if(courant==.4f) { if(n>40 && transmitted<=coarse_transmission) throw std::runtime_error("straight pulse does not converge with mesh refinement"); coarse_transmission=transmitted; } } if(std::string(kind)=="split" && (std::abs(transmitted-straight_transmission)>1e-7 || std::abs(reflected-straight_reflection)>1e-7)) throw std::runtime_error("split connection differs from continuous pipe"); if(std::string(kind)=="star" || std::string(kind)=="star-unequal") { const double expected_r=std::string(kind)=="star"?1./9:4./9; const double expected_t=1-expected_r; if(std::abs(reflected/straight_transmission-expected_r)>.04 || std::abs(transmitted/straight_transmission-expected_t)>.07 || r.fallbacks) throw std::runtime_error("characteristic junction acoustic scattering regression: "+id+" R="+std::to_string(reflected/straight_transmission)+" T="+std::to_string(transmitted/straight_transmission)+" fallback="+std::to_string(r.fallbacks)); } const bool contaminated=r.limited || reflected+transmitted>1.05; ++cases; flagged+=contaminated; summary<.08 || std::abs(std::arg(corrected))>.15)) throw std::runtime_error("junction frequency/phase regression: "+id); } summary<<'\n'; } } std::cout<<"Validated "<