Improve physical geometry, fluid solvers, audio, and profiling
This commit is contained in:
79
tests/live_tuning_tests.cpp
Normal file
79
tests/live_tuning_tests.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#include "FluidSim/DesktopScenario.hpp"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
|
||||
using namespace fluidsim::port;
|
||||
namespace {
|
||||
void check(bool value,const char* message) {if(!value)throw std::runtime_error(message);}
|
||||
bool near(float a,float b) {return std::abs(a-b)<1e-5f;}
|
||||
struct TempDirectory {
|
||||
std::filesystem::path path=std::filesystem::temp_directory_path()/
|
||||
("fluidsim-live-tuning-"+std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
|
||||
TempDirectory(){std::filesystem::create_directory(path);}
|
||||
~TempDirectory(){std::error_code ignored;std::filesystem::remove_all(path,ignored);}
|
||||
};
|
||||
}
|
||||
int main() {
|
||||
const auto& source=scenario_example("single-450");
|
||||
FourStrokeScenario simulation(44100,source);
|
||||
for(int i=0;i<1024;++i)simulation.process();
|
||||
const float rpm=simulation.crankshaft.omega,angle=simulation.crankshaft.angle;
|
||||
const float pressure=simulation.cylinder1.pressure,lift=simulation.cylinder1.intake_lift();
|
||||
const auto samples=simulation.sample_count;
|
||||
auto tuned=source.engine;
|
||||
tuned.intake_valve.mass_kg=.047f;tuned.intake_valve.spring_rate_n_per_m=45000;
|
||||
tuned.intake_valve.damping_n_s_per_m=32;tuned.valve_sound.radiation_area=.0005f;
|
||||
tuned.ecu.idle_base_opening=.055f;tuned.fuel.pilot_jet_diameter*=1.1f;
|
||||
for(auto& p:tuned.intake_cam.points)p.lift_metres*=.8f;
|
||||
for(auto& p:tuned.ecu.ignition_map)p.degrees_btdc+=4;
|
||||
simulation.set_tuning(tuned);
|
||||
check(simulation.crankshaft.omega==rpm && simulation.crankshaft.angle==angle && simulation.sample_count==samples,"live edit restarted the engine");
|
||||
check(simulation.cylinder1.pressure==pressure && simulation.cylinder1.intake_lift()==lift,"live edit reset cylinder or valve state");
|
||||
const auto snapshot=simulation.snapshot();
|
||||
check(near(snapshot.intake_cam_lifts[120],FourStrokeCylinder::cam_lift_at(tuned.intake_cam,480)),"live cam profile was not published");
|
||||
check(near(simulation.ecu.idle_base_opening,.055f),"ECU calibration was not applied");
|
||||
for(int i=0;i<2048;++i) {
|
||||
const auto frame=simulation.process();
|
||||
check(std::isfinite(frame.left) && std::isfinite(frame.right),"live tuning produced nonfinite audio");
|
||||
}
|
||||
auto invalid=tuned;invalid.pipes[0].length*=1.5f;
|
||||
bool rejected=false;try {simulation.set_tuning(invalid);}catch(const std::invalid_argument&){rejected=true;}
|
||||
check(rejected,"mesh edits must require a rebuild");
|
||||
invalid=tuned;invalid.intake_valve.mass_kg=std::numeric_limits<float>::quiet_NaN();
|
||||
rejected=false;try {simulation.set_tuning(invalid);}catch(const std::exception&){rejected=true;}
|
||||
check(rejected,"invalid calibration accepted");
|
||||
|
||||
TempDirectory temp;
|
||||
const auto saved=save_tuned_engine(source,tuned,temp.path);
|
||||
const auto loaded=load_engine_file(saved);
|
||||
check(loaded.id=="single-450-tuned" && loaded.source_path!=source.source_path,"export replaced source identity");
|
||||
check(near(loaded.engine.intake_valve.mass_kg,tuned.intake_valve.mass_kg),"valve mass export changed units");
|
||||
check(near(loaded.engine.fuel.pilot_jet_diameter,tuned.fuel.pilot_jet_diameter),"carburetor edit was not exported");
|
||||
check(near(loaded.engine.ecu.ignition_map[0].degrees_btdc,tuned.ecu.ignition_map[0].degrees_btdc),"ignition trim was not exported");
|
||||
check(near(loaded.engine.intake_cam.points[10].lift_metres,tuned.intake_cam.points[10].lift_metres),"cam profile export changed units");
|
||||
auto at_limit=tuned;at_limit.intake_cam.points[10].lift_metres=at_limit.intake_valve.maximum_lift_m;
|
||||
check(save_tuned_engine(source,at_limit,temp.path)!=saved,"repeat export overwrote an engine");
|
||||
|
||||
DesktopScenario desktop(source);
|
||||
desktop.set_controls(.2f,.1f,.6f);desktop.set_pipe_solver(PipeSolver::Hlle);desktop.set_audio_scene(OutdoorScene::Dry);
|
||||
auto pipes=tuned;auto& route=pipes.geometry.pipes[0];const auto end=route.points.back(),start=route.points.front();
|
||||
route.points.front()={end.x+(start.x-end.x)*1.2f,end.y+(start.y-end.y)*1.2f,end.z+(start.z-end.z)*1.2f};
|
||||
pipes.pipes[0].length=route.length();
|
||||
desktop.rebuild_engine(pipes);
|
||||
const auto rebuilt=desktop.snapshot();
|
||||
const float length=std::accumulate(rebuilt.pipe_lengths[0].begin(),rebuilt.pipe_lengths[0].end(),0.f);
|
||||
check(near(length,pipes.pipes[0].length),"rebuilt mesh uses old dimensions");
|
||||
check(rebuilt.pipe_solver==PipeSolver::Hlle && rebuilt.audio_scene==OutdoorScene::Dry && near(rebuilt.clutch,.6f),"rebuild lost scene or controls");
|
||||
std::atomic<bool> finite{true};
|
||||
std::jthread worker([&]{for(int i=0;i<4096;++i) {auto f=desktop.process();if(!std::isfinite(f.left)||!std::isfinite(f.right))finite=false;}});
|
||||
for(int i=0;i<16;++i) {auto live=pipes;live.intake_valve.mass_kg+=float(i)*.0001f;desktop.set_tuning(live);desktop.snapshot();}
|
||||
desktop.rebuild_engine(pipes);
|
||||
worker.join();check(finite,"concurrent editing or rebuild corrupted the simulation");
|
||||
std::cout<<"Live tuning state, export round trip, mesh rebuild and concurrent publication passed\n";
|
||||
}
|
||||
Reference in New Issue
Block a user