ParametrizationDump.cpp
Go to the documentation of this file.
1/**
2 * This file is part of ArmarX.
3 *
4 * ArmarX is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * ArmarX is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * @author Fabian Reister ( fabian dot reister at kit dot edu )
17 * @date 2026
18 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
19 * GNU General Public License
20 */
21
22#include "ParametrizationDump.h"
23
24#include <algorithm>
25#include <cmath>
26#include <filesystem>
27#include <fstream>
28
29#include <SimoxUtility/json/json.hpp>
30
33
35{
36
37 namespace
38 {
39
40 float
41 yawOf(const core::Pose& pose)
42 {
43 return std::atan2(pose.linear()(1, 0), pose.linear()(0, 0));
44 }
45
46 /// `trajectory.points[]` in the shape `evaluation/plot.py` already reads.
47 ///
48 /// `velocity_limit` comes from the *planner's* profile rather than from the
49 /// `ObstacleAwareVelocityLimit`, which the navigator cannot reach: it holds only the
50 /// `GlobalPlanner` interface. The planner's velocities are that limit, so taking them
51 /// from the pre-parametrization copy is exact, not an approximation.
52 nlohmann::json
53 toJson(const core::GlobalTrajectory& parametrized, const core::GlobalTrajectory& planned)
54 {
55 nlohmann::json points = nlohmann::json::array();
56
57 const std::size_t count = parametrized.points().size();
58 const bool limitsUsable = planned.points().size() == count;
59
60 for (std::size_t i = 0; i < count; i++)
61 {
62 const core::GlobalTrajectoryPoint& point = parametrized.points()[i];
63 const core::Position& position = point.waypoint.pose.translation();
64
65 points.push_back(nlohmann::json{
66 {"x", position.x()},
67 {"y", position.y()},
68 {"yaw", yawOf(point.waypoint.pose)},
69 {"velocity", point.velocity},
70 // TOPP-RA resamples, so the planner's profile may have a different length.
71 // Fall back to the executed velocity rather than mislabelling a limit.
72 {"velocity_limit",
73 limitsUsable ? planned.points()[i].velocity : point.velocity}});
74 }
75
76 // Same interpolation the analysis application reports, so the two are comparable.
77 return nlohmann::json{
78 {"points", points},
79 {"length", parametrized.length()},
80 {"duration",
82 }
83
84 nlohmann::json
85 toJson(const algorithms::Toppra& toppra)
86 {
88
89 const Eigen::MatrixXd& samples = toppra.lastSamples();
90
91 nlohmann::json waypoints = nlohmann::json::array();
92 for (Eigen::Index i = 0; i < samples.rows(); i++)
93 {
94 waypoints.push_back(nlohmann::json{
95 {"t", samples(i, Column::T)},
96 {"x", samples(i, Column::X)},
97 {"y", samples(i, Column::Y)},
98 {"yaw", samples(i, Column::YAW)},
99 {"velocity", samples(i, Column::VELOCITY)},
100 {"angular_velocity", samples(i, Column::ANGULAR_VELOCITY)},
101 {"tangential_acceleration", samples(i, Column::TANGENTIAL_ACCELERATION)},
102 {"angular_acceleration", samples(i, Column::ANGULAR_ACCELERATION)}});
103 }
104
105 return nlohmann::json{
106 {"success", true}, {"duration", toppra.lastDuration()}, {"waypoints", waypoints}};
107 }
108
109 } // namespace
110
111 void
112 writeParametrizationDump(const ParametrizationRecord& record, const std::string& path)
113 try
114 {
115 nlohmann::json dump;
116
117 dump["success"] = true;
118 dump["requested_parametrization"] = core::ToString(record.requested);
119 dump["applied_parametrization"] = core::ToString(record.applied);
120 dump["degraded"] = record.requested != record.applied;
121
122 // `plot.py` reads the mode from here; it must be what actually ran.
123 dump["planner"] = nlohmann::json{{"parametrization", core::ToString(record.applied)}};
124
125 dump["trajectory"] = toJson(record.parametrized, record.planned);
126 dump["planned_trajectory"] = toJson(record.planned, record.planned);
127
128 if (record.toppra != nullptr and record.toppra->lastSamples().rows() > 0)
129 {
130 dump["reparametrization_reference"] = toJson(*record.toppra);
131 }
132
133 // Same key names the analysis application emits, so `plot.py` reads both unchanged.
134 if (record.driveParams.has_value())
135 {
136 const algorithms::Toppra::DriveParams& drive = *record.driveParams;
137
138 dump["parametrization_limits"] = nlohmann::json{
139 {"max_velocity", record.maxVelocity},
140 {"max_angular_velocity", record.maxAngularVelocity},
141 {"max_acceleration", drive.maxAcceleration},
142 {"max_deceleration", drive.maxDeceleration},
143 {"acceleration_radius", std::min(drive.maxAcceleration, drive.maxDeceleration)},
144 {"acceleration_polygon_sides", 32},
145 {"acceleration_frame", "body"},
146 {"max_wheel_velocity", drive.maxWheelVelocity},
147 {"motor_max_torque", drive.motorMaxTorque * drive.torqueFraction}};
148 }
149
151
152 const std::filesystem::path latest{path};
153 if (latest.has_parent_path())
154 {
155 std::filesystem::create_directories(latest.parent_path());
156 }
157
158 const auto write = [&dump](const std::filesystem::path& to)
159 {
160 std::ofstream stream{to};
161 if (not stream.good())
162 {
163 ARMARX_WARNING << "Cannot write the parametrization record to " << to;
164 return;
165 }
166 stream << dump.dump(2) << std::endl;
167 };
168
169 write(latest);
170
171 // A timestamped sibling, so a sequence of requests can be compared afterwards while the
172 // fixed path always holds the most recent one.
173 std::filesystem::path stamped = latest;
174 stamped.replace_filename(latest.stem().string() + "-" +
175 armarx::core::time::DateTime::Now().toDateTimeString() +
176 latest.extension().string());
177 write(stamped);
178
179 ARMARX_INFO << "Wrote the parametrization record to " << latest;
180 }
181 catch (const std::exception& e)
182 {
183 // A debugging aid must never be able to abort a navigation request.
184 ARMARX_WARNING << "Could not write the parametrization record: " << e.what();
185 }
186
187} // namespace armarx::navigation::server
static DateTime Now()
Definition DateTime.cpp:51
std::string toDateTimeString() const
Definition DateTime.cpp:75
SampleColumn
Column layout of lastSamples().
Definition Toppra.h:57
const Eigen::MatrixXd & lastSamples() const
The time-parametrized profile from the most recent apply(), (M, 8).
Definition Toppra.cpp:351
void dump(vec_point_2d &vec)
Definition gdiam.cpp:1666
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:179
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:191
std::string ToString(const TrajectoryParametrization parametrization)
The lowercase name used in configs, logs and the analysis application's results.
Eigen::Isometry3f Pose
Definition basic_types.h:31
Eigen::Vector3f Position
Definition basic_types.h:36
This file is part of ArmarX.
void writeParametrizationDump(const ParametrizationRecord &record, const std::string &path)
Write record as JSON, for offline rendering with plot-navigation-request.
Everything the python side needs that does not change between paths.
Definition Toppra.h:105
float maxWheelVelocity
Motor/gearbox speed rating referred to the wheel [rad/s], always in rad/s regardless of the drive's o...
Definition Toppra.h:126
float maxAcceleration
Device command ramp. Infinite leaves the profile bounded by torque alone.
Definition Toppra.h:129
One navigation request's velocity profile, before and after parametrization.
core::TrajectoryParametrization applied
What actually ran.
core::TrajectoryParametrization requested
What the stack was configured to use.
std::optional< algorithms::Toppra::DriveParams > driveParams
The bounds the profile was solved against, when TOPP-RA was configured.
core::GlobalTrajectory parametrized
The profile that will be executed.
const algorithms::Toppra * toppra
Null unless TOPP-RA ran; supplies the time axis, which a GlobalTrajectory lacks.
core::GlobalTrajectory planned
The planner's output.