17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-29 19:47:30 +01:00

First implementation of graph vehicles

This commit is contained in:
2026-10-14 12:20:49 +02:00
parent 70abf879b3
commit 1ef465264c
121 changed files with 12292 additions and 729 deletions

View File

@@ -11,11 +11,15 @@ http://mozilla.org/MPL/2.0/.
#include "vehicle/graph/graph_standalone.h"
#include "vehicle/graph/graph_catalog.h"
#include "vehicle/graph/graph_instance.h"
#include "vehicle/graph/graph_lua.h"
#include "vehicle/graph/blocks/cab.h"
#include "McZapkie/MOVER.h"
#include <cmath>
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <memory>
#include <sstream>
// command line tools of the vehicle graph. they run without the rest of the simulator; the log service doesn't run
@@ -33,6 +37,7 @@ struct script_event
std::string target;
double value{0.0};
int line{0};
std::size_t vehicle{0}; // index of the vehicle in the consist; the target was prefixed "k:" for the vehicle k
};
bool load_script(std::string const &Path, std::vector<script_event> &Events)
@@ -140,6 +145,42 @@ bool apply_event(graph_instance &Instance, script_event const &Event)
return true;
}
// motion of the vehicle without the track: the forces of the graph, the motion resistance and the mass of the body
// (TMoverParameters::ComputeTotalForce and ComputeMovement, much simplified)
void move_body(TMoverParameters &Body, double const Deltatime)
{
auto const &state{Body.Graph->body()};
if (Body.TotalMass <= 0.0)
{
return;
}
auto const tractive{state.tractive_force};
auto const resistance{state.brake_force + Body.FrictionForce()};
double force;
if (Body.V == 0.0)
{
// standing still until the tractive force overcomes the resistance
force = std::abs(tractive) > resistance ? tractive - (tractive > 0.0 ? resistance : -resistance) : 0.0;
}
else
{
force = tractive - (Body.V > 0.0 ? resistance : -resistance);
}
auto const previous{Body.V};
Body.AccS = force / Body.TotalMass;
Body.V += Body.AccS * Deltatime;
if (previous != 0.0 && Body.V * previous <= 0.0)
{
// stopped by the brakes and the resistance
Body.V = 0.0;
}
Body.Vel = std::abs(Body.V) * 3.6;
Body.nrot = Body.WheelDiameter > 0.0 ? Body.V / (M_PI * Body.WheelDiameter) : 0.0;
Body.DistCounter += std::abs(Body.V) * Deltatime / 1000.0;
Body.FTrain = tractive;
Body.Fb = state.brake_force;
}
std::string format_number(double const Value)
{
char buffer[32];
@@ -148,6 +189,141 @@ std::string format_number(double const Value)
return buffer;
}
// vehicle run by -graph-run: the first one, and the ones coupled behind it (-consist)
struct run_vehicle
{
std::string path;
bool reversed{false}; // coupled by its rear coupler, facing the other way than the first vehicle
std::shared_ptr<graph_template const> graph;
std::unique_ptr<TMoverParameters> body;
std::vector<std::pair<std::string, int>> signals;
bool hasbody{false};
bool mains{false}; // the graph has several line breakers, so the Mains of the body is written as well
std::string prefix; // of the result columns: empty for the first vehicle, "k:" for the vehicle k
};
// path of a vehicle of the consist: as given, or relative to the directory of the first vehicle's file
std::string consist_path(std::string const &Path, std::string const &First)
{
if (std::ifstream{Path}.is_open())
{
return Path;
}
auto const relative{std::filesystem::path{First}.parent_path() / Path};
if (std::filesystem::path{Path}.is_relative() && std::ifstream{relative}.is_open())
{
return relative.string();
}
return Path;
}
bool load_vehicle(run_vehicle &Vehicle)
{
std::vector<diagnostic> diagnostics;
Vehicle.graph = load_graph(Vehicle.path, diagnostics);
for (auto const &entry : diagnostics)
{
std::printf("%s: %s\n", Vehicle.path.c_str(), to_string(entry).c_str());
}
if (Vehicle.graph == nullptr)
{
return false;
}
auto const &graph{*Vehicle.graph};
std::printf("%s: %zu blocks, %zu signals, %zu links\n", Vehicle.path.c_str(), graph.nodes().size(), graph.signal_defaults().size(), graph.link_count());
return true;
}
// the body of the vehicle, filled from the graph as in the simulator, with the body entries the graph doesn't give
// taken from the .fiz file of the same name, if there is one
bool create_body(run_vehicle &Vehicle, std::string const &Name, bool const Ready, int const Cab)
{
auto const &input{Vehicle.path};
auto const &graph{Vehicle.graph};
Vehicle.body = std::make_unique<TMoverParameters>(0.0, "graph", Name, 1);
auto &body{*Vehicle.body};
auto fizfile{input};
fizfile = fizfile.substr(0, std::min(fizfile.size(), fizfile.find_last_of('.'))) + ".fiz";
if (false == body.AssignGraph(graph, fizfile))
{
std::printf("%s: the body section or the block parameters were rejected by the vehicle body\n", input.c_str());
return false;
}
auto const vehicleblocks{std::any_of(std::begin(graph->nodes()), std::end(graph->nodes()), [](auto const &Node) {
auto const &category{Node.type->category};
return category != "logic" && category != "cab" && category != "indicators" && category != "script" && category != "module";
})};
if (vehicleblocks && (body.Dim.L <= 0.0 || body.Mass <= 0.0))
{
// the simulator doesn't load such a vehicle; the graph itself can still be run here
std::printf("%s: warning: the body has no length or mass (body section, or %s), the simulator won't load the vehicle\n", input.c_str(), fizfile.c_str());
}
body.ComputeMass();
body.CheckLocomotiveParameters(Ready, 1);
body.CabActive = Cab;
body.Graph->body().standalone = true;
Vehicle.hasbody = (false == graph->body().empty());
Vehicle.mains = std::count_if(std::begin(graph->nodes()), std::end(graph->nodes()), [](auto const &Node) { return Node.type->id == "el.hsb"; }) > 1;
Vehicle.signals = graph->signal_outputs();
return true;
}
void write_header(std::ostringstream &Results, run_vehicle const &Vehicle)
{
auto const &graph{*Vehicle.graph};
auto const &instance{*Vehicle.body->Graph};
auto const &prefix{Vehicle.prefix};
for (auto const &signal : Vehicle.signals)
{
Results << ';' << prefix << signal.first;
}
for (std::size_t nodeindex = 0; nodeindex < graph.nodes().size(); ++nodeindex)
{
auto const &item{instance.node_block(nodeindex)};
for (std::size_t stateindex = 0; stateindex < item.state_count(); ++stateindex)
{
Results << ';' << prefix << graph.nodes()[nodeindex].id << ':' << item.state_name(stateindex);
}
}
if (Vehicle.hasbody)
{
// the vehicle body, for graphs describing whole vehicles
Results << ';' << prefix << "body:v;" << prefix << "body:ft;" << prefix << "body:fb;" << prefix << "body:pg;" << prefix << "body:zg;" << prefix << "body:bc";
}
if (Vehicle.mains)
{
Results << ';' << prefix << "body:mains";
}
}
void write_values(std::ostringstream &Results, run_vehicle const &Vehicle)
{
auto const &graph{*Vehicle.graph};
auto const &body{*Vehicle.body};
auto const &instance{*body.Graph};
for (auto const &signal : Vehicle.signals)
{
Results << ';' << format_number(instance.signal(signal.second));
}
for (std::size_t nodeindex = 0; nodeindex < graph.nodes().size(); ++nodeindex)
{
auto const &item{instance.node_block(nodeindex)};
for (std::size_t stateindex = 0; stateindex < item.state_count(); ++stateindex)
{
Results << ';' << format_number(item.state_value(stateindex));
}
}
if (Vehicle.hasbody)
{
Results << ';' << format_number(body.Vel) << ';' << format_number(body.FTrain / 1000.0) << ';' << format_number(body.Fb / 1000.0) << ';' << format_number(body.PipePress)
<< ';' << format_number(body.Compressor) << ';' << format_number(body.BrakePress);
}
if (Vehicle.mains)
{
Results << ';' << (body.Mains ? 1 : 0);
}
}
} // namespace
int graph_catalog_standalone(std::string const &Output)
@@ -171,18 +347,22 @@ int graph_catalog_standalone(std::string const &Output)
int graph_run_standalone(int const Argc, char *Argv[])
{
// -graph-run <file.vhg> [-script <events.txt>] [-o <results.csv>] [-dt <step>] [-t <duration>] [-every <steps>]
// -graph-run <file.vhg> [-script <events.txt>] [-o <results.csv>] [-dt <step>] [-t <duration>] [-every <steps>] [-state braked|ready] [-consist <file.vhg>[:r]]...
if (Argc < 3)
{
std::printf("usage: -graph-run <file.vhg> [-script <events.txt>] [-o <results.csv>] [-dt <step, s>] [-t <duration, s>] [-every <steps>]\n");
std::printf("usage: -graph-run <file.vhg> [-script <events.txt>] [-o <results.csv>] [-dt <step, s>] [-t <duration, s>] [-every <steps>] [-state braked|ready] "
"[-consist <file.vhg>[:r]]...\n");
return 1;
}
std::string const input{Argv[2]};
std::vector<run_vehicle> vehicles(1);
vehicles.front().path = input;
std::string script;
std::string output;
double deltatime{0.01};
double duration{0.0};
int every{1};
bool ready{false};
for (int idx = 3; idx + 1 < Argc; idx += 2)
{
std::string const option{Argv[idx]};
@@ -207,6 +387,20 @@ int graph_run_standalone(int const Argc, char *Argv[])
{
every = std::max(1, std::atoi(value.c_str()));
}
else if (option == "-state")
{
// initial state of the vehicle body, as for a vehicle placed in a scenario standing (braked) or moving (ready)
ready = (value == "ready");
}
else if (option == "-consist")
{
// next vehicle of the consist, coupled behind the previous one by its front coupler, or with ":r" reversed (by its rear coupler)
run_vehicle vehicle;
vehicle.reversed = (value.size() > 2 && value.ends_with(":r"));
vehicle.path = consist_path(vehicle.reversed ? value.substr(0, value.size() - 2) : value, input);
vehicle.prefix = std::to_string(vehicles.size() + 1) + ":";
vehicles.push_back(std::move(vehicle));
}
else
{
std::printf("unknown option \"%s\"\n", option.c_str());
@@ -219,23 +413,39 @@ int graph_run_standalone(int const Argc, char *Argv[])
return 1;
}
std::vector<diagnostic> diagnostics;
auto const graph{load_graph(input, diagnostics)};
for (auto const &entry : diagnostics)
// messages of the lua blocks go straight to the console
set_log_sink([](std::string const &Message) { std::printf("%s\n", Message.c_str()); });
for (auto &vehicle : vehicles)
{
std::printf("%s: %s\n", input.c_str(), to_string(entry).c_str());
if (false == load_vehicle(vehicle))
{
return 1;
}
}
if (graph == nullptr)
{
return 1;
}
std::printf("%s: %zu blocks, %zu signals, %zu links\n", input.c_str(), graph->nodes().size(), graph->signal_defaults().size(), graph->link_count());
std::vector<script_event> events;
if (false == script.empty() && false == load_script(script, events))
{
return 1;
}
for (auto &event : events)
{
// "k:" before the target picks the vehicle k of the consist
auto const separator{event.target.find(':')};
if (separator == std::string::npos || separator == 0 || event.target.find_first_not_of("0123456789") != separator)
{
continue;
}
auto const number{std::atoi(event.target.substr(0, separator).c_str())};
if (number < 1 || number > static_cast<int>(vehicles.size()))
{
std::printf("script line %d: there's no vehicle %d in the consist\n", event.line, number);
return 1;
}
event.vehicle = static_cast<std::size_t>(number - 1);
event.target.erase(0, separator + 1);
}
if (duration <= 0.0 && false == events.empty())
{
// by default run a second past the last event
@@ -246,21 +456,29 @@ int graph_run_standalone(int const Argc, char *Argv[])
return 0;
}
graph_instance instance{graph};
auto const signals{graph->signal_outputs()};
for (std::size_t idx = 0; idx < vehicles.size(); ++idx)
{
// the first vehicle has the occupied cab
if (false == create_body(vehicles[idx], idx == 0 ? "graph" : "graph" + std::to_string(idx + 1), ready, idx == 0 ? 1 : 0))
{
return 1;
}
}
// the consist: every vehicle coupled to the previous one with all the couplings, so the brake pipes connect as well
auto const allcouplings{coupling::coupler | coupling::brakehose | coupling::control | coupling::highvoltage | coupling::gangway | coupling::mainhose | coupling::heating |
coupling::permanent | coupling::power24v | coupling::power110v | coupling::power3x400v};
for (std::size_t idx = 1; idx < vehicles.size(); ++idx)
{
auto &previous{vehicles[idx - 1]};
auto &vehicle{vehicles[idx]};
previous.body->Attach(previous.reversed ? end::front : end::rear, vehicle.reversed ? end::rear : end::front, vehicle.body.get(), allcouplings, true, false);
}
std::ostringstream results;
results << "time";
for (auto const &signal : signals)
for (auto const &vehicle : vehicles)
{
results << ';' << signal.first;
}
for (std::size_t nodeindex = 0; nodeindex < graph->nodes().size(); ++nodeindex)
{
auto const &item{instance.node_block(nodeindex)};
for (std::size_t stateindex = 0; stateindex < item.state_count(); ++stateindex)
{
results << ';' << graph->nodes()[nodeindex].id << ':' << item.state_name(stateindex);
}
write_header(results, vehicle);
}
results << '\n';
@@ -272,29 +490,29 @@ int graph_run_standalone(int const Argc, char *Argv[])
auto const steptime{step * deltatime};
while (nextevent < events.size() && events[nextevent].time <= steptime + 1e-9)
{
if (false == apply_event(instance, events[nextevent]))
auto const &event{events[nextevent]};
if (false == apply_event(*vehicles[event.vehicle].body->Graph, event))
{
return 1;
}
++nextevent;
}
instance.update(deltatime);
// the vehicles in the order of the consist; each one moves on its own, without the forces of the couplers
for (auto &vehicle : vehicles)
{
auto &body{*vehicle.body};
body.EqvtPipePress = body.PipePress;
body.compute_movement_graph_(deltatime);
move_body(body, deltatime);
}
if ((step + 1) % every != 0 && step + 1 != stepcount)
{
continue;
}
results << format_number((step + 1) * deltatime);
for (auto const &signal : signals)
for (auto const &vehicle : vehicles)
{
results << ';' << format_number(instance.signal(signal.second));
}
for (std::size_t nodeindex = 0; nodeindex < graph->nodes().size(); ++nodeindex)
{
auto const &item{instance.node_block(nodeindex)};
for (std::size_t stateindex = 0; stateindex < item.state_count(); ++stateindex)
{
results << ';' << format_number(item.state_value(stateindex));
}
write_values(results, vehicle);
}
results << '\n';
}