67 lines
10 KiB
C++
67 lines
10 KiB
C++
#include "FluidSim/Machine.hpp"
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <stdexcept>
|
|
using namespace fluidsim::port;
|
|
namespace {
|
|
void check(bool condition,const char* why){if(!condition)throw std::runtime_error(why);}
|
|
bool close(double a,double b,double tolerance=1e-5){return std::abs(a-b)<=tolerance*std::max({1.,std::abs(a),std::abs(b)});}
|
|
template<class F>void rejects(F&& f,const char* why){bool caught=false;try{f();}catch(const std::exception&){caught=true;}check(caught,why);}
|
|
double value(const MachineSensorState& s,unsigned output=0){check(s.status==MachineSensorStatus::Valid&&s.outputs.at(output).validity==SignalValidity::Valid,"Sensor output is unexpectedly invalid");return std::get<double>(s.outputs.at(output).value);}
|
|
void run(MachineSimulation& sim,int count){for(int i=0;i<count;++i)sim.process();}
|
|
}
|
|
int main()try{
|
|
MachineDocument doc;const auto pipe=doc.add(MachinePartKind::Pipe).id,tank=doc.add(MachinePartKind::Chamber,{.3f,.25f,0}).id,ambient=doc.add(MachinePartKind::Reservoir,{-.3f,.25f,0}).id;
|
|
doc.find(tank)->pressure=200000;doc.connect({pipe,1},{tank,0});doc.connect({pipe,0},{ambient,0});
|
|
const auto pressure=doc.add_sensor(MachineSensorKind::Pressure,tank).id;
|
|
const auto difference=doc.add_sensor(MachineSensorKind::DifferentialPressure,tank).id;doc.find_sensor(difference)->reference={ambient,.5f,"Ambient reference"};
|
|
const auto temperature=doc.add_sensor(MachineSensorKind::GasTemperature,pipe,.9f).id;
|
|
const auto flow=doc.add_sensor(MachineSensorKind::MassFlow,pipe,.35f).id;
|
|
for(int i=0;i<12;++i)doc.add_sensor(MachineSensorKind::Pressure,pipe,float(i)/11);
|
|
doc.probes={{1,pipe,"Pipe flow",MachineQuantity::MassFlow,.35f}};
|
|
validate_machine(doc);MachineSimulation sensed(doc);auto bare=doc;bare.sensors.clear();MachineSimulation baseline(bare);
|
|
check(close(value(sensed.sense(pressure)),200000)&&close(value(sensed.sense(difference)),200000-101325),"Pressure taps lost absolute or differential reference");
|
|
check(close(value(sensed.sense(temperature)),300)&&value(sensed.sense(flow))==0,"Initial sensor sample is missing or has the wrong units");
|
|
check(sensed.snapshot().sensors.size()==16&&sensed.measure().count==1,"Sensors consume the instrument recording limit");
|
|
bool reverse_flow=false;for(int i=0;i<2048;++i){const auto a=sensed.process(),b=baseline.process();check(a.left==b.left&&a.right==b.right,"Ideal sensor altered machine audio");reverse_flow|=value(sensed.sense(flow))<-.00001;}
|
|
const auto measured=sensed.measure();check(close(value(sensed.sense(flow)),measured.values[0]),"Pipe sensor and targeted instrument disagree at the same location");
|
|
check(reverse_flow,"Total mass flow lost the pipe Start-to-End sign");
|
|
const auto a=sensed.snapshot(),b=baseline.snapshot();check(a.cells==b.cells&&a.substeps==b.substeps&&a.parts.size()==b.parts.size(),"Sensor added a physical volume or solver cells");
|
|
for(std::size_t i=0;i<a.parts.size();++i)check(a.parts[i].pressure==b.parts[i].pressure&&a.parts[i].flow==b.parts[i].flow,"Sensor perturbed the gas state");
|
|
for(const auto& sensor:a.sensors)for(unsigned i=0;i<sensor.count;++i)check(sensor.outputs[i].timestamp==a.time,"Sensor outputs have incoherent sample times");
|
|
for(float mesh:{10.f,150.f}){auto remeshed=doc;remeshed.cells_per_meter=mesh;MachineSimulation sim(remeshed);check(close(value(sim.sense(temperature)),300),"Sensor bound to a stale cell after remeshing");check(remeshed.find_sensor(temperature)->target.position==.9f,"Remeshing changed normalized sensor location");}
|
|
auto moved=doc;const auto original=machine_sensor_position(moved,moved.find_sensor(temperature)->target);moved.transform_selection({pipe,tank,ambient},{},{.1f,.2f,.3f});const auto after=machine_sensor_position(moved,moved.find_sensor(temperature)->target);
|
|
check(original&&after&&close(after->x-original->x,.1)&&close(after->y-original->y,.2)&&close(after->z-original->z,.3),"Sensor marker did not follow its physical host");
|
|
auto invalid=doc;invalid.find_sensor(temperature)->target.position=1.1f;rejects([&]{validate_machine(invalid);},"Out-of-pipe sensor position was accepted");
|
|
rejects([&]{doc.add_sensor(MachineSensorKind::Crank,tank);},"Crank sensor attached to a gas tank");rejects([&]{doc.add_sensor(MachineSensorKind::MassFlow,tank);},"A gas volume silently supplied an ambiguous through-flow");
|
|
auto unresolved=doc;unresolved.remove(tank);check(unresolved.find_sensor(pressure)&&unresolved.find_sensor(pressure)->target.part==0&&unresolved.find_sensor(pressure)->target.label==doc.find(tank)->name,"Deleting a host removed its sensor or lost the unresolved label");
|
|
MachineSimulation missing(unresolved);check(missing.sense(pressure).status==MachineSensorStatus::MissingHost&&missing.sense(pressure).outputs[0].validity==SignalValidity::Missing,"Deleted host supplied plausible valid data");
|
|
unresolved.add(MachinePartKind::Chamber);MachineSimulation still_missing(unresolved);check(still_missing.sense(pressure).status==MachineSensorStatus::MissingHost,"New part silently repaired a deleted sensor host");
|
|
auto wrong=doc;wrong.find_sensor(pressure)->target.part=ambient;wrong.find(ambient)->kind=MachinePartKind::Cap;MachineSimulation incompatible(wrong);check(incompatible.sense(pressure).status==MachineSensorStatus::IncompatibleHost,"Incompatible sensor binding supplied a reading");
|
|
|
|
MachineDocument rotation;const auto shaft=rotation.add(MachinePartKind::Shaft).id;rotation.find(shaft)->shaft.initial_angle=-.5f;rotation.find(shaft)->shaft.initial_speed=-2;
|
|
const auto crank=rotation.add_sensor(MachineSensorKind::Crank,shaft).id;rotation.find_sensor(crank)->angle_offset=.25f;MachineSimulation rotating(rotation,1000);
|
|
auto pickup=rotating.sense(crank);check(value(pickup)==-2&&close(value(pickup,1),2*double(pi)-.25)&&std::get<bool>(pickup.outputs[2].value),"Crank pickup lost signed speed or wrapped angle");check(pickup.timing_shaft==shaft&&close(pickup.unwrapped_angle,-.25),"Ideal crank provenance is unavailable");
|
|
run(rotating,4000);pickup=rotating.sense(crank);check(close(pickup.unwrapped_angle,-8.25)&&value(pickup,1)>=0&&value(pickup,1)<2*pi,"Crank pickup discarded unwrapped reverse crossings");
|
|
for(unsigned i=0;i<3;++i){const auto descriptor=machine_sensor_output(*rotation.find_sensor(crank),i);check(descriptor.direction==PortDirection::Output&&descriptor.cardinality==PortCardinality::Multiple,"Sensor outputs cannot fan out");check(signal_value_valid_for(pickup.outputs[i],descriptor),"Crank values do not match typed outputs");check(pickup.outputs[i].timestamp==rotating.snapshot().time,"Crank channels are not synchronized");}
|
|
auto changed=rotation;changed.find_sensor(crank)->angle_offset=1;rejects([&]{rotating.apply_tuning(changed);},"Timing attachment changed during Test");
|
|
auto stopped=rotation;stopped.find(shaft)->shaft.initial_speed=0;MachineSimulation stationary(stopped);check(value(stationary.sense(crank))==0&&stationary.sense(crank).status==MachineSensorStatus::Valid,"Stopped crank was confused with a missing pickup");
|
|
|
|
MachineDocument throttle;const auto duct=throttle.add(MachinePartKind::Pipe).id,body=throttle.add(MachinePartKind::Throttle).id;throttle.connect({duct,1},{body,0});throttle.find(body)->pedal_control=false;throttle.find(body)->opening=.35f;
|
|
const auto tps=throttle.add_sensor(MachineSensorKind::ThrottlePosition,body).id;MachineSimulation opening(throttle);opening.set_controls(.9f);run(opening,1);check(close(value(opening.sense(tps)),.35),"TPS read pedal demand instead of fixed mechanism opening");throttle.find(body)->opening=.6f;opening.apply_tuning(throttle);run(opening,1);check(close(value(opening.sense(tps)),.6),"TPS missed safe live throttle tuning");
|
|
|
|
const auto assembly=machine_extract_assembly(doc,{tank},"Tapped tank");check(assembly.sensors.size()==2&&assembly.find_sensor(difference)->reference.part==0,"Assembly lost sensors or retained an external reference ID");auto destination=doc;const auto inserted=machine_insert_assembly(destination,assembly,{1,.25f,0});check(destination.sensors.size()==18&&destination.sensors[16].target.part==inserted[0]&&destination.sensors[16].id!=pressure,"Copied sensor retained source identity or host");check(destination.sensors[17].reference.part==0,"Copied differential reference accidentally matched an existing host");
|
|
const auto whole=machine_extract_assembly(doc,{pipe,tank,ambient},"Tapped network");MachineDocument empty;machine_insert_assembly(empty,whole,{});MachineSimulation copied(empty);check(close(value(copied.sense(empty.sensors[1].id)),200000-101325),"Internal sensor reference did not follow assembly remapping");
|
|
|
|
const auto file=std::filesystem::temp_directory_path()/("fluidsim-sensors-"+std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())+".machine.toml");
|
|
save_machine(destination,file);const auto restored=load_machine(file);check(restored.sensors==destination.sensors&&restored.next_sensor_id==destination.next_sensor_id,"Sensor identities, binding labels or locations did not round-trip");
|
|
save_machine(unresolved,file);auto reloaded=load_machine(file);check(reloaded.next_id==unresolved.next_id&&reloaded.find_sensor(pressure)->target.part==0,"Reload reused deleted part IDs or rebound an unresolved sensor");
|
|
reloaded.sensors.erase(reloaded.sensors.begin());const auto reserved=reloaded.next_sensor_id;save_machine(reloaded,file);auto repeat=load_machine(file);check(repeat.next_sensor_id==reserved,"Deleted sensor IDs can be reused after reload");
|
|
{std::ofstream out(file);out<<"format=7\n[[sensors]]\nid=1\nkind='future_unknown_sensor'\n";}rejects([&]{load_machine(file);},"Unknown sensor kind silently loaded");std::filesystem::remove(file);
|
|
{const auto example=load_machine(FLUIDSIM_SENSOR_EXAMPLE_FILE);MachineSimulation bench(example);run(bench,2048);check(example.sensors.size()==6&&close(value(bench.sense(5)),188.49556)&&close(value(bench.sense(6)),.45),"Sensor example lost its shaft or mechanism measurement");for(const auto& s:bench.snapshot().sensors)check(s.status==MachineSensorStatus::Valid,"Sensor example contains an unresolved or invalid attachment");}
|
|
std::cout<<"Sensor attachments, coherent samples, typed outputs, unresolved bindings and assembly persistence passed\n";return 0;
|
|
}catch(const std::exception& error){std::cerr<<error.what()<<'\n';return 1;}
|