all of the changes
This commit is contained in:
@@ -1,13 +1,16 @@
|
||||
#include "FluidSim/Atmosphere.hpp"
|
||||
#include "FluidSim/Crankcase.hpp"
|
||||
#include "FluidSim/Fluid.hpp"
|
||||
#include "FluidSim/Junction.hpp"
|
||||
#include "FluidSim/Solver.hpp"
|
||||
#include <cmath>
|
||||
#include <complex>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
using namespace fluidsim::port;
|
||||
namespace {
|
||||
PipeSolver selected_solver=PipeSolver::Rusanov;
|
||||
void check(bool b, const std::string &s) {
|
||||
if (!b)
|
||||
throw std::runtime_error(s);
|
||||
@@ -19,6 +22,7 @@ void set(Pipe1D &p, int i, float pressure, float rho, float u = 0, float air = 1
|
||||
p.cells[i] = {rho, rho * u, pressure / pipe_gm1 + .5f * rho * u * u, air, fuel};
|
||||
}
|
||||
void lossless(Pipe1D &p) {
|
||||
p.solver=selected_solver;
|
||||
p.damping_multiplier = 0;
|
||||
p.energy_relaxation_rate = 0;
|
||||
}
|
||||
@@ -36,7 +40,8 @@ double energy(const Pipe1D &p) {
|
||||
}
|
||||
double vol_energy(const Volume0D &v) { return v.pressure() * v.volume() / (v.gamma - 1); }
|
||||
} // namespace
|
||||
int main() {
|
||||
int main(int argc,char** argv) {
|
||||
if(argc>1) selected_solver=parse_pipe_solver(argv[1]);
|
||||
int failed = 0, total = 0;
|
||||
auto test = [&](const char *n, std::function<void()> fn) {
|
||||
++total;
|
||||
@@ -48,6 +53,163 @@ int main() {
|
||||
std::cout << "FAIL " << n << ": " << e.what() << '\n';
|
||||
}
|
||||
};
|
||||
test("solver flux consistency and reflection symmetry", [] {
|
||||
for(auto solver : {PipeSolver::Rusanov,PipeSolver::Hlle,PipeSolver::Hllc}) {
|
||||
GasState a{1.2f,30,101325,.8f,.05f}, b{.5f,-10,60000,.1f,.3f};
|
||||
const auto equal=Pipe1D::numerical_flux(a,a,solver);
|
||||
near(equal.m,a.rho*a.velocity,1e-5,"uniform mass flux");
|
||||
near(equal.p,a.rho*a.velocity*a.velocity+a.pressure,.02,"uniform momentum flux");
|
||||
const auto forward=Pipe1D::numerical_flux(a,b,solver);
|
||||
a.velocity=-a.velocity; b.velocity=-b.velocity;
|
||||
const auto reverse=Pipe1D::numerical_flux(b,a,solver);
|
||||
near(forward.m,-reverse.m,.0001,"orientation mass symmetry");
|
||||
near(forward.p,reverse.p,.1,"orientation momentum symmetry");
|
||||
near(forward.e,-reverse.e,1,"orientation energy symmetry");
|
||||
near(forward.y,-reverse.y,.0001,"orientation air symmetry");
|
||||
near(forward.fuel,-reverse.fuel,.0001,"orientation fuel symmetry");
|
||||
}
|
||||
});
|
||||
test("HLLC preserves stationary contact and falls back at expansion vacuum", [] {
|
||||
GasState a{1,0,1,.8f,.1f}, b{.125f,0,1,.1f,.5f};
|
||||
auto f=Pipe1D::numerical_flux(a,b,PipeSolver::Hllc);
|
||||
near(f.m,0,1e-7,"stationary contact mass diffusion");
|
||||
near(f.e,0,1e-7,"stationary contact energy diffusion");
|
||||
near(f.y,0,1e-7,"stationary contact tracer diffusion");
|
||||
a.velocity=-10; b.velocity=10;
|
||||
bool fallback=false;
|
||||
f=Pipe1D::numerical_flux(a,b,PipeSolver::Hllc,std::sqrt(1.4f),std::sqrt(1.4f/.125f),&fallback);
|
||||
check(fallback && std::isfinite(f.e),"strong expansion did not use safe HLLE flux");
|
||||
});
|
||||
test("unflanged radiation reflection magnitude and phase", [] {
|
||||
for (double ka : {.1, .5, 1., 2.}) {
|
||||
OpenEndRadiation reflection;
|
||||
std::complex<double> input{}, output{};
|
||||
double t = 0;
|
||||
const double dt = 2*3.141592653589793/(ka*4096);
|
||||
for (int i=0; i<4096*32; ++i) {
|
||||
t += dt;
|
||||
const double incident = std::cos(ka*t);
|
||||
const double reflected = reflection.process(incident, dt);
|
||||
if (i >= 4096*16) {
|
||||
auto phase = std::exp(std::complex<double>(0, -ka*t));
|
||||
input += incident*phase;
|
||||
output += reflected*phase;
|
||||
}
|
||||
}
|
||||
constexpr double n=.167, d=n+2*.6133, e=(d*d-n*n-1)/2;
|
||||
const std::complex<double> z(0,ka);
|
||||
const auto expected = -(1.+n*z)/(1.+d*z+e*z*z);
|
||||
near(std::abs(output/input-expected), 0, .001, "radiation amplitude/phase mismatch");
|
||||
check(std::abs(output/input) < 1, "radiation boundary creates acoustic energy");
|
||||
}
|
||||
});
|
||||
test("N-port junction conserves sealed network mass and energy", [] {
|
||||
std::vector<Pipe1D> pipes;
|
||||
for(int i=0;i<3;++i) { pipes.emplace_back(.001f*(i+1),.3f,10);lossless(pipes.back()); }
|
||||
for(int i=0;i<10;++i) set(pipes[0],i,120000,1.4f);
|
||||
BoundarySystem boundaries(pipes);
|
||||
Junction junction(.0003f);
|
||||
const std::array<JunctionEnd,3> ends{{{0,false},{1,true},{2,true}}};
|
||||
junction.connect(boundaries,pipes,ends);
|
||||
boundaries.add_closed_end(0,true);boundaries.add_closed_end(1,false);boundaries.add_closed_end(2,false);
|
||||
Solver solver(pipes,boundaries);solver.add_component(junction);
|
||||
auto totals=[&] { std::array<double,2> t{junction.mass(),junction.internal_energy()};
|
||||
for(auto& p:pipes) { t[0]+=mass(p);t[1]+=energy(p); } return t; };
|
||||
const auto before=totals();
|
||||
for(int i=0;i<1000;++i) solver.step(1.f/44100);
|
||||
const auto after=totals();
|
||||
if(std::abs(after[0]-before[0])>before[0]*2e-6) std::cerr << "mass relative drift=" << (after[0]-before[0])/before[0] << "\n";
|
||||
near(after[0],before[0],before[0]*2e-6,"junction mass drift");
|
||||
near(after[1],before[1],before[1]*2e-6,"junction energy drift");
|
||||
check(pipes[1].state(0).pressure>101325,"junction did not transmit pressure to branch");
|
||||
for(auto& p:pipes) check(p.positivity_repairs==0,"junction required positivity repairs");
|
||||
});
|
||||
test("Zero-storage N-port junction conserves sealed network mass and energy", [] {
|
||||
std::vector<Pipe1D> pipes;
|
||||
for(int i=0;i<3;++i) { pipes.emplace_back(.001f*(i+1),.3f,10);lossless(pipes.back()); }
|
||||
for(int i=0;i<10;++i) set(pipes[0],i,120000,1.4f);
|
||||
BoundarySystem boundaries(pipes);
|
||||
boundaries.add_junction({{0,false},{1,true},{2,true}});
|
||||
boundaries.add_closed_end(0,true);boundaries.add_closed_end(1,false);boundaries.add_closed_end(2,false);
|
||||
Solver solver(pipes,boundaries);
|
||||
auto totals=[&] { std::array<double,2> t{0,0};
|
||||
for(auto& p:pipes) { t[0]+=mass(p);t[1]+=energy(p); } return t; };
|
||||
const auto before=totals();
|
||||
for(int i=0;i<1000;++i) solver.step(1.f/44100);
|
||||
const auto after=totals();
|
||||
// Float cell updates accumulate roundoff over 4000 substeps (50 ppm bound).
|
||||
near(after[0],before[0],before[0]*5e-5,"junction mass drift");
|
||||
near(after[1],before[1],before[1]*5e-5,"junction energy drift");
|
||||
check(pipes[1].state(0).pressure>101325,"junction did not transmit pressure to branch");
|
||||
for(auto& p:pipes) check(p.positivity_repairs==0,"junction required positivity repairs");
|
||||
});
|
||||
test("radiating acoustic volume source is independent of donor density", [] {
|
||||
for (bool left : {false,true}) {
|
||||
std::vector<Pipe1D> hot, cold;
|
||||
hot.emplace_back(.001f,.1f,1); cold.emplace_back(.001f,.1f,1);
|
||||
BoundarySystem a(hot), b(cold);
|
||||
a.add_radiating_open_end(0,left,101325,.001f);
|
||||
b.add_radiating_open_end(0,left,101325,.001f);
|
||||
for(int i=0;i<4096;++i) {
|
||||
const float velocity=2*std::sin(2*pi*150*i/44100);
|
||||
set(hot[0],0,101325,.5f,velocity);
|
||||
set(cold[0],0,101325,ambient_density,velocity);
|
||||
a.resolve(1.f/44100); b.resolve(1.f/44100);
|
||||
near(a.open_end_volume_flow(0),b.open_end_volume_flow(0),2e-7,
|
||||
"temperature change contaminated acoustic volume source");
|
||||
}
|
||||
}
|
||||
});
|
||||
test("radiating mouth conserves reported mass at both orientations", [] {
|
||||
for (bool left : {false,true}) {
|
||||
std::vector<Pipe1D> p;
|
||||
p.emplace_back(.001f,.1f,10);
|
||||
lossless(p[0]);
|
||||
for (int i=0;i<10;++i) set(p[0],i,105000,1.2f);
|
||||
BoundarySystem b(p);
|
||||
b.add_radiating_open_end(0,left,101325,.001f);
|
||||
b.add_closed_end(0,!left);
|
||||
const double initial = mass(p[0]);
|
||||
b.resolve(1e-6f);
|
||||
const double exchange = -b.open_end_mass_flow(0)*1e-6;
|
||||
p[0].advance(1e-6f);
|
||||
near(mass(p[0])-initial,exchange,1e-11,"radiating mass flux mismatch");
|
||||
}
|
||||
});
|
||||
test("radiating mouth reflects rarefaction at either pipe orientation", [] {
|
||||
for (bool left : {false,true}) {
|
||||
std::vector<Pipe1D> p;
|
||||
p.emplace_back(.001f,1,400);
|
||||
lossless(p[0]);
|
||||
const double c=std::sqrt(1.4*101325/1.2);
|
||||
for(int i=0;i<400;++i) {
|
||||
double x=(i+.5)/400.;
|
||||
if(left) x=1-x;
|
||||
float dp=100*std::exp(-std::pow((x-.3)/.04,2));
|
||||
set(p[0],i,101325+dp,1.2f+dp/(c*c),(left?-1:1)*dp/(1.2*c));
|
||||
}
|
||||
BoundarySystem b(p);
|
||||
b.add_closed_end(0,!left);
|
||||
b.add_radiating_open_end(0,left,101325,.001f);
|
||||
Solver solver(p,b); solver.minimum_substeps=1;
|
||||
for(int i=0;i<int(1.4/c*44100);++i) solver.step(1.f/44100);
|
||||
check(p[0].state(left?280:119).pressure < 101305,"open mouth did not return an inverted pressure pulse");
|
||||
check(p[0].positivity_repairs==0,"radiation boundary required positivity repair");
|
||||
}
|
||||
});
|
||||
test("radiating inflow supplies fresh ambient stagnation enthalpy", [] {
|
||||
std::vector<Pipe1D> p;
|
||||
p.emplace_back(.001f,.1f,10);
|
||||
for(int i=0;i<10;++i) set(p[0],i,99000,1.2f,0,.1f,.2f);
|
||||
BoundarySystem b(p);
|
||||
b.add_radiating_open_end(0,true,101325,.001f,300);
|
||||
b.add_closed_end(0,false);
|
||||
b.resolve(1e-6f);
|
||||
const auto face=p[0].left_ghost;
|
||||
check(face.velocity>0 && face.air==1 && face.fuel==0,"backflow supplied exhaust or fuel");
|
||||
near(fluidsim::port::gamma/(fluidsim::port::gamma-1)*face.pressure/face.rho+.5*face.velocity*face.velocity,
|
||||
fluidsim::port::gamma/(fluidsim::port::gamma-1)*gas_constant*300, .1,"ambient enthalpy mismatch");
|
||||
});
|
||||
test("tracer underflow preserves bulk gas and normal concentrations", [] {
|
||||
for (float fraction : {1e-40f, 1e-30f, .5f}) {
|
||||
Pipe1D p(.001f, 1, 8);
|
||||
|
||||
Reference in New Issue
Block a user