Files
FluidSimCpp/tests/mechanics_tests.cpp

80 lines
6.4 KiB
C++

#include "FluidSim/Mechanics.hpp"
#include <algorithm>
#include <cmath>
#include <iostream>
#include <limits>
#include <stdexcept>
using namespace fluidsim::port;
namespace {
void check(bool value,const char* why){if(!value)throw std::runtime_error(why);}
bool close(double a,double b,double tolerance=1e-10){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);}
void balance(const ShaftState& s){check(close(s.initial_energy+s.external_work+s.drive_work,s.kinetic_energy+s.friction_loss+s.brake_loss,2e-9),"Mechanical work/energy balance failed");check(s.friction_loss>=0&&s.brake_loss>=0,"Mechanical drag created energy");}
}
int main()try{
{
MechanicalSystem m;m.add_shaft(1,{.inertia=2});m.add_shaft(2,{.inertia=.5,.initial_speed=10});m.add_drive(3,1,{DriveMode::Torque,4});
for(int i=0;i<100;++i)m.step(.01);
check(close(m.shaft(1).speed,2)&&close(m.shaft(1).angle,1),"Constant torque does not match analytic acceleration/angle");
check(close(m.shaft(2).speed,10)&&close(m.shaft(2).angle,10),"Independent shaft was accelerated by another drive");
check(close(m.drive(3).work,4)&&close(m.shaft(1).kinetic_energy,4),"Motor work did not become shaft kinetic energy");balance(m.shaft(1));balance(m.shaft(2));
rejects([&]{m.add_flywheel(4,1,1);},"Topology changed after simulation began");
}
{
MechanicalSystem m;m.add_shaft(1,{.inertia=1});m.add_flywheel(2,1,3);m.add_drive(3,1,{DriveMode::Torque,4});m.step(1);
check(close(m.shaft(1).inertia,4)&&close(m.shaft(1).speed,1),"Flywheel inertia was omitted or counted twice");balance(m.shaft(1));
}
{
MechanicalSystem m;m.add_shaft(1,{.inertia=1,.initial_speed=3});m.add_brake(2,1,{.constant=10});m.step(1);
check(m.shaft(1).speed==0&&close(m.shaft(1).angle,.45),"Brake did not stop at the correct position within a timestep");
check(close(m.brake(2).loss,4.5)&&m.brake(2).torque<0,"Brake did not absorb initial kinetic energy");m.step(1);
check(m.shaft(1).speed==0&&close(m.shaft(1).angle,.45),"Brake pushed a stopped shaft into reverse");balance(m.shaft(1));
}
{
MechanicalSystem m;m.add_shaft(1,{.inertia=1});m.add_drive(2,1,{DriveMode::Torque,4});m.add_brake(3,1,{.constant=10});m.step(1);
check(m.shaft(1).speed==0&&close(m.brake(3).torque,-4)&&m.brake(3).power==0,"Static brake reaction or zero-speed power is wrong");
m.set_drive(2,{DriveMode::Torque,12});m.step(1);check(close(m.shaft(1).speed,2),"Drive could not overcome static brake capacity");balance(m.shaft(1));
}
{
MechanicalSystem m;m.add_shaft(1,{.inertia=1,.initial_speed=3});m.add_drive(2,1,{DriveMode::Torque,-20});m.add_brake(3,1,{.constant=10});m.step(1);
check(m.shaft(1).speed<0&&m.shaft(1).angle<0,"Drive could not stop and reverse a shaft");check(m.brake(3).loss>0,"Brake generated energy through reversal");balance(m.shaft(1));
}
{
MechanicalSystem m;m.add_shaft(1,{.inertia=2,.friction_viscous=1});m.add_drive(2,1,{DriveMode::Speed,10});m.add_brake(3,1,{.constant=3});m.step(.1);
check(close(m.shaft(1).speed,10)&&close(m.shaft(1).angle,1),"Ideal drive did not impose its speed");
check(close(m.drive(2).work,113)&&close(m.shaft(1).brake_loss,3)&&close(m.shaft(1).friction_loss,10),"Ideal drive did not account for startup work and losses");
m.step(.1);check(close(m.drive(2).torque,13)&&close(m.drive(2).power,130),"Steady speed-drive reaction does not balance losses");
m.set_drive(2,{DriveMode::Speed,0});m.step(.1);check(close(m.drive(2).power,-1000),"Ideal drive did not report recovered stopping energy");balance(m.shaft(1));
}
{
MechanicalSystem m;m.add_shaft(1,{.inertia=.5,.initial_speed=-8,.friction_constant=.3,.friction_viscous=.2,.friction_quadratic=.02});m.add_drive(2,1,{DriveMode::Speed,-8});m.add_brake(3,1,{.constant=.4,.viscous=.1});m.step(.01);
check(m.drive(2).torque<0&&m.drive(2).power>0&&m.brake(3).torque>0&&m.brake(3).power>0,"Reverse rotation used the wrong torque or power sign");balance(m.shaft(1));
}
for(double dt:{.01,.001}){
MechanicalSystem m;m.add_shaft(1,{.inertia=2,.initial_speed=10,.friction_viscous=3});const int steps=int(std::round(1/dt));for(int i=0;i<steps;++i)m.step(dt);
check(std::abs(m.shaft(1).speed-10*std::exp(-1.5))<(dt==.01?.0001:.000002),"Viscous damping failed timestep refinement against its analytic solution");balance(m.shaft(1));
}
{
MechanicalSystem m;m.add_shaft(1,{.inertia=.7,.initial_speed=4,.friction_constant=.2,.friction_viscous=.04,.friction_quadratic=.006});m.add_drive(2,1,{DriveMode::Torque,1});m.add_brake(3,1,{.constant=.1,.viscous=.02,.quadratic=.003});
for(int i=0;i<10000;++i){m.add_torque(1,std::sin(i*.01)*6);if(i%1200==0)m.set_drive(2,{DriveMode::Torque,i%2400==0?-2.:3.});m.step(.001);if(i%100==0)balance(m.shaft(1));}
balance(m.shaft(1));
}
{
MechanicalSystem m;m.add_shaft(1,{.inertia=1,.initial_speed=1,.forward_only=true});m.add_drive(2,1,{DriveMode::Torque,-10});m.step(1);
check(m.shaft(1).speed==0&&m.shaft(1).constraint_torque>0,"Forward-only timing constraint failed to hold at rest");balance(m.shaft(1));
rejects([&]{m.set_drive(2,{DriveMode::Speed,-1});},"Forward-only shaft accepted a reverse speed command");
}
{
MechanicalSystem m;m.add_shaft(1,{});m.add_drive(2,1,{DriveMode::Speed,10});
rejects([&]{m.add_drive(3,1,{DriveMode::Speed,10});},"Multiple ideal drives acquired undefined reaction sharing");
m.add_drive(3,1,{DriveMode::Torque,1});rejects([&]{m.set_drive(3,{DriveMode::Speed,10});},"Tuning introduced a conflicting speed source");
rejects([&]{m.add_brake(2,1,{});},"Duplicate mechanical ID was accepted");
rejects([&]{m.add_flywheel(4,99,1);},"Flywheel attached to a nonexistent shaft");
rejects([&]{m.add_shaft(4,{.inertia=0});},"Zero inertia was accepted");
rejects([&]{m.add_brake(4,1,{.constant=-1});},"Negative brake loss was accepted");
rejects([&]{m.step(0);},"Zero timestep was accepted");
rejects([&]{m.add_torque(1,std::numeric_limits<double>::quiet_NaN());},"Nonfinite torque was accepted");
}
std::cout<<"Independent shafts, flywheel inertia, drive/brake work and stopping/reversal passed\n";return 0;
}catch(const std::exception& error){std::cerr<<error.what()<<'\n';return 1;}