Add machine workshop, typed logic, mechanics and sensor-driven ECUs

This commit is contained in:
max
2026-11-02 07:27:13 +01:00
parent eb2b6366c2
commit abfcce0f46
99 changed files with 28835 additions and 145 deletions

11
tools/MachineBench.hpp Normal file
View File

@@ -0,0 +1,11 @@
#pragma once
#include "FluidSim/Machine.hpp"
#include <stdexcept>
namespace fluidsim::port::bench {
inline void set_rpm(MachineDocument& design,float rpm){
const auto shaft=machine_primary_shaft(design);if(!shaft)throw std::invalid_argument("Benchmark needs a primary shaft");
const auto position=design.find(shaft)->position;design.find(shaft)->shaft.initial_speed=rpm*2*pi/60;
for(auto& part:design.parts)if(part.kind==MachinePartKind::DriveMotor&&machine_shaft_for(design,part.id)==shaft){part.drive={rpm*2*pi/60,0,true,true};return;}
auto& drive=design.add(MachinePartKind::DriveMotor,machine_math::add(position,{-.3f,0,0}));const auto id=drive.id;drive.name="Benchmark speed drive";drive.drive.target_speed=rpm*2*pi/60;design.connect({shaft,0},{id,0});
}
}

View File

@@ -0,0 +1,32 @@
#include "MachineBench.hpp"
#include "FluidSim/Machine.hpp"
#include "FluidSim/Scenario.hpp"
#include "FluidSim/ScenarioCatalog.hpp"
#include <algorithm>
#include <chrono>
#include <cmath>
#include <iostream>
#include <stdexcept>
using namespace fluidsim::port;
template<class Step> double measure(Step step,int frames){
const auto begin=std::chrono::steady_clock::now();
for(int i=0;i<frames;++i){const auto value=step();if(!std::isfinite(value.left)||!std::isfinite(value.right))throw std::runtime_error("Nonfinite benchmark audio");}
return std::chrono::duration<double>(std::chrono::steady_clock::now()-begin).count();
}
int main(int argc,char** argv)try{
const int frames=argc>1?std::stoi(argv[1]):32768;const float rpm=argc>2?std::stof(argv[2]):3000,pedal=argc>3?std::stof(argv[3]):.35f;
if(frames<1||!std::isfinite(rpm)||rpm<0||rpm>50000||!std::isfinite(pedal)||pedal<0||pedal>1)throw std::invalid_argument("Usage: fluidsim_machine_benchmark [frames>0] [rpm 0..50000] [pedal 0..1]");
std::cout<<"scenario,runtime,frames,rpm,pedal,cells,min_cell_mm,substeps_per_frame,us_per_frame,wall_per_sim,positivity_repairs\n";
for(const char* id:{"single-450","twin","flat6","v8-h"}){
const auto& preset=scenario_example(id);FourStrokeScenario native(44100,preset);native.set_controls(pedal,0,1);
const auto step=[&]{native.crankshaft.omega=rpm*2*pi/60;return native.process();};
for(int i=0;i<512;++i)step();const auto before=native.snapshot().solver_substeps;const double elapsed=measure(step,frames);const auto after=native.snapshot();
float shortest=1e9f;unsigned cells=0;for(auto& p:native.pipes){cells+=unsigned(p.cells.size());shortest=std::min(shortest,p.minimum_cell_length());}
std::cout<<id<<",native,"<<frames<<','<<rpm<<','<<pedal<<','<<cells<<','<<shortest*1000<<','<<double(after.solver_substeps-before)/frames<<','<<elapsed*1e6/frames<<','<<elapsed*44100/frames<<','<<after.pipe_positivity_repairs<<std::endl;
auto design=machine_from_engine(preset);bench::set_rpm(design,rpm);MachineSimulation machine(design);machine.set_controls(pedal);
for(int i=0;i<512;++i)machine.process();const auto start=machine.snapshot().substeps;const double wall=measure([&]{return machine.process();},frames);const auto state=machine.snapshot();
std::cout<<id<<",workshop,"<<frames<<','<<rpm<<','<<pedal<<','<<state.cells<<','<<state.minimum_cell_length*1000<<','<<double(state.substeps-start)/frames<<','<<wall*1e6/frames<<','<<wall*44100/frames<<','<<state.positivity_repairs<<std::endl;
if(state.positivity_repairs||after.pipe_positivity_repairs)throw std::runtime_error("Benchmark required positivity repairs");
}
return 0;
}catch(const std::exception& e){std::cerr<<e.what()<<'\n';return 1;}

View File

@@ -0,0 +1,41 @@
#include "MachineBench.hpp"
#include "FluidSim/Desktop/MachineBuilder.hpp"
#include "FluidSim/ScenarioCatalog.hpp"
#include <SFML/OpenGL.hpp>
#include <chrono>
#include <iostream>
#include <stdexcept>
using namespace fluidsim::port;
using namespace fluidsim::port::desktop;
int main(int argc,char** argv)try{
const int frames=argc>1?std::stoi(argv[1]):120;
const bool play=argc>2&&std::string(argv[2])=="--play";
const double speed=argc>3?std::stod(argv[3]):1.;
if(!std::isfinite(speed)||speed<.01||speed>1)throw std::invalid_argument("Playback speed must be 0.01..1");
if(frames<1)throw std::invalid_argument("frame count must be positive");
sf::ContextSettings context;context.depthBits=24;context.majorVersion=2;context.minorVersion=1;
sf::RenderWindow window(sf::VideoMode({1440,900}),"Workshop render benchmark",sf::Style::Default,sf::State::Windowed,context);
window.setVisible(false);window.setVerticalSyncEnabled(false);
sf::Font font;bool loaded=false;
for(const auto* path:{"C:/Windows/Fonts/consola.ttf","/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf"})if(std::filesystem::exists(path)&&font.openFromFile(path)){loaded=true;break;}
std::cout<<(play?"scenario,speed,frames,wall_seconds,sim_seconds,sim_per_wall,underruns,buffered\n":"scenario,frames,ms_per_frame,render_fps\n");
for(const char* id:{"flat6","v8-h"}){
MachineBuilder builder;auto design=machine_from_engine(scenario_example(id));if(play){bench::set_rpm(design,3000);unsigned cylinder=0,pipe=0;for(const auto& p:design.parts){if(!cylinder&&p.kind==MachinePartKind::Cylinder)cylinder=p.id;if(p.name=="Exhaust atmosphere")for(const auto& link:design.links){const auto other=link.a.part==p.id?link.b.part:link.b.part==p.id?link.a.part:0;if(const auto part=design.find(other);part&&part->kind==MachinePartKind::Pipe)pipe=other;}}
design.probes={{1,cylinder,"Cylinder pressure",MachineQuantity::Pressure},{2,pipe,"Downpipe lambda",MachineQuantity::Lambda},{3,cylinder,"Fresh charge",MachineQuantity::ChargeAfr},{4,pipe,"Downpipe AFR",MachineQuantity::AirFuelRatio},{5,pipe,"Downpipe flow",MachineQuantity::MassFlow},{6,0,"Shaft RPM",MachineQuantity::Rpm},{7,0,"Shaft torque",MachineQuantity::Torque},{8,0,"Shaft power",MachineQuantity::Power}};}
builder.set_document(std::move(design));builder.set_test_speed(speed);
if(play){window.setFramerateLimit(60);builder.start_test();if(!builder.testing())throw std::runtime_error("Could not start workshop playback");builder.handle_event(sf::Event::KeyPressed{.code=sf::Keyboard::Key::M},window.getSize());
for(int i=0;i<16;++i){builder.update();builder.draw(window,loaded?&font:nullptr);window.display();}
const auto before=builder.playback_stats();const auto begin=std::chrono::steady_clock::now();
for(int i=0;i<frames;++i){builder.update();if(!builder.testing())throw std::runtime_error("Workshop playback stopped");builder.draw(window,loaded?&font:nullptr);window.display();}
builder.update();const auto after=builder.playback_stats();const auto wall=std::chrono::duration<double>(std::chrono::steady_clock::now()-begin).count();
std::cout<<id<<','<<speed<<','<<frames<<','<<wall<<','<<after.simulation_seconds-before.simulation_seconds<<','<<(after.simulation_seconds-before.simulation_seconds)/wall<<','<<after.underruns-before.underruns<<','<<after.buffered<<std::endl;
builder.stop_test();continue;
}
for(int i=0;i<8;++i){builder.draw(window,loaded?&font:nullptr);glFinish();}
const auto begin=std::chrono::steady_clock::now();
for(int i=0;i<frames;++i){builder.draw(window,loaded?&font:nullptr);glFinish();}
const auto elapsed=std::chrono::duration<double>(std::chrono::steady_clock::now()-begin).count();
std::cout<<id<<','<<frames<<','<<elapsed*1000/frames<<','<<frames/elapsed<<std::endl;
}
return 0;
}catch(const std::exception& error){std::cerr<<error.what()<<'\n';return 1;}