reparametrization.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 "reparametrization.h"
23
24#include <chrono>
25#include <cmath>
26#include <iomanip>
27#include <iostream>
28#include <memory>
29#include <stdexcept>
30#include <vector>
31
32#include <SimoxUtility/json/json.hpp>
33
36
39
41{
42
43 namespace
44 {
45
46 /// Drive parameters for the Toppra variant, from the same platform config the
47 /// simulation was built from.
48 ///
49 /// The loader lives in the algorithms library so that this application, the navigator and
50 /// the documentation all read one file with one parser and cannot drift apart.
51 algorithms::Toppra::DriveParams
52 buildDriveParams(const Config& config)
53 {
54 algorithms::Toppra::DriveParams params =
56
57 // Multiplied, not assigned: the platform config may already carry a derating (0.084
58 // on ARMAR-DE) and the scene's own fraction is a further reduction on top of it.
59 // Assigning here would silently restore the full motor torque.
60 params.torqueFraction *= config.parametrizationTorqueFraction;
61
62 params.numSamples = config.parametrizationSamples;
63
64 return params;
65 }
66
67 /// Re-expose the time parametrization for the plots, in the shape they already read.
68 nlohmann::json
69 toReference(const algorithms::Toppra& toppra)
70 {
72
73 const Eigen::MatrixXd& samples = toppra.lastSamples();
74
75 nlohmann::json waypoints = nlohmann::json::array();
76
77 for (Eigen::Index i = 0; i < samples.rows(); i++)
78 {
79 waypoints.push_back(nlohmann::json{
80 {"t", samples(i, Column::T)},
81 {"x", samples(i, Column::X)},
82 {"y", samples(i, Column::Y)},
83 {"yaw", samples(i, Column::YAW)},
84 {"velocity", samples(i, Column::VELOCITY)},
85 {"angular_velocity", samples(i, Column::ANGULAR_VELOCITY)},
86 {"tangential_acceleration", samples(i, Column::TANGENTIAL_ACCELERATION)},
87 {"angular_acceleration", samples(i, Column::ANGULAR_ACCELERATION)}});
88 }
89
90 nlohmann::json reference;
91 reference["success"] = true;
92 reference["duration"] = toppra.lastDuration();
93 reference["waypoints"] = waypoints;
94
95 return reference;
96 }
97
98 } // namespace
99
103 const simulation::CommandRateLimit& rateLimit,
104 const float boundaryVelocity)
105 {
106 CommandRampCheck check;
107
108 const std::vector<core::GlobalTrajectoryPoint>& points = trajectory.points();
109
110 float arcLength = 0.F;
111 bool inSpan = false;
112 float spanStart = 0.F;
113
114 for (std::size_t i = 0; i + 1 < points.size(); i++)
115 {
116 const float segment = (points[i + 1].waypoint.pose.translation() -
117 points[i].waypoint.pose.translation())
118 .norm();
119 arcLength += segment;
120
121 if (segment < 1e-6F)
122 {
123 continue;
124 }
125
126 const float from = points[i].velocity;
127 const float to = points[i + 1].velocity;
128
129 // v dv/ds, exact for a profile that is linear in v^2 over the segment.
130 const float demand = std::abs(to * to - from * from) / (2.F * segment);
131 const float limit =
132 to >= from ? rateLimit.maxAcceleration : rateLimit.maxDeceleration;
133
134 if (demand > limit)
135 {
136 check.violations++;
137
138 if (demand - limit > check.worstDemand - check.worstLimit)
139 {
140 check.worstDemand = demand;
141 check.worstLimit = limit;
142 check.worstArcLength = arcLength;
143 }
144
145 if (not inSpan)
146 {
147 inSpan = true;
148 spanStart = arcLength - segment;
149 }
150 }
151 else if (inSpan)
152 {
153 inSpan = false;
154 check.spans.emplace_back(spanStart, arcLength);
155 }
156 }
157
158 if (inSpan)
159 {
160 check.spans.emplace_back(spanStart, arcLength);
161 }
162
163 // A profile that never decelerates passes every segment test above and still overshoots.
164 const float terminalVelocity = points.back().velocity;
166 terminalVelocity * terminalVelocity / (2.F * rateLimit.maxDeceleration);
167
168 check.terminalVelocityExceedsBoundary = terminalVelocity > boundaryVelocity;
169
171 {
172 ARMARX_WARNING << "The velocity profile ends at " << terminalVelocity
173 << " mm/s. Stopping from there at " << rateLimit.maxDeceleration
174 << " mm/s^2 needs " << check.terminalStoppingDistance
175 << " mm of path that does not exist -- the base will overshoot the "
176 "goal by roughly that much.";
177 }
178
179 if (check.violations > 0)
180 {
182 << "The velocity profile demands more than the device command ramp can "
183 "deliver at "
184 << check.violations << " of " << (points.size() - 1)
185 << " segments. Worst: " << check.worstDemand << " mm/s^2 against a limit of "
186 << check.worstLimit << " mm/s^2, at arc length " << check.worstArcLength
187 << " mm. The base will not track the profile there -- expect it to lag on the "
188 "way up and to overshoot on the way down.";
189 }
190
191 return check;
192 }
193
195 parametrizationModeFromString(const std::string& name)
196 {
197 if (name == "none")
198 {
200 }
201 if (name == "ramping")
202 {
204 }
205 if (name == "toppra")
206 {
208 }
209
210 throw std::invalid_argument("Unknown parametrization mode `" + name +
211 "`. Expected `none`, `ramping` or `toppra`.");
212 }
213
214 std::string
216 {
217 switch (mode)
218 {
220 return "none";
222 return "ramping";
224 return "toppra";
225 }
226
227 return "unknown";
228 }
229
230 ReparametrizationResult
232 {
233 // The same objects the Navigator runs, built the same way, so this measures the robot's
234 // behaviour rather than a second implementation of it.
235 core::GeneralConfig general = config.generalConfig;
236 general.parametrization = config.parametrization;
237
238 // Timed separately from `apply()` below: construction warms up the interpreter, the
239 // imports and the robot model, which the navigation stack pays once at startup and not
240 // per request. Only the `apply()` figure is representative of a navigation request.
241 const auto constructionStarted = std::chrono::steady_clock::now();
242
243 const algorithms::TrajectoryParametrizationPtr parametrization =
244 fac::TrajectoryParametrizationFactory::create(general, buildDriveParams(config));
245
246 const double constructionSeconds =
247 std::chrono::duration<double>(std::chrono::steady_clock::now() -
248 constructionStarted)
249 .count();
250
252 .trajectory = trajectory, .reference = nullptr, .seconds = 0.0};
253
254 const auto started = std::chrono::steady_clock::now();
255
256 // `boundaryVelocity` is what Navigator passes when starting from rest, which is the
257 // situation this application simulates.
258 parametrization->apply(result.trajectory, config.generalConfig.boundaryVelocity);
259
260 result.seconds = std::chrono::duration<double>(
261 std::chrono::steady_clock::now() - started)
262 .count();
263
264 // Introspection: a GlobalTrajectory has no time axis, and the plots want one.
265 if (const auto* toppra =
266 dynamic_cast<const algorithms::Toppra*>(parametrization.get());
267 toppra != nullptr)
268 {
269 result.reference = toReference(*toppra);
270 }
271
272 std::cout << " [reparametrization] " << std::left << std::setw(32)
273 << "one-time setup (startup)" << std::right << std::setw(8) << std::fixed
274 << std::setprecision(1) << constructionSeconds * 1e3 << " ms" << std::endl;
275
276 ARMARX_IMPORTANT << "Parametrization `" << toString(config.parametrization)
277 << "`: " << constructionSeconds << " s setup + " << result.seconds
278 << " s per request, " << result.trajectory.points().size()
279 << " waypoints.";
280
281 return result;
282 }
283
284} // namespace armarx::navigation::analysis
Time-optimal reparametrization under per-motor torque and command-ramp limits.
Definition Toppra.h:52
SampleColumn
Column layout of lastSamples().
Definition Toppra.h:57
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:188
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:191
Toppra::DriveParams LoadDriveParams(const std::filesystem::path &configFile)
Read Toppra::DriveParams from a PlatformDynamics<Robot>.json.
Definition Toppra.cpp:127
std::shared_ptr< TrajectoryParametrization > TrajectoryParametrizationPtr
std::filesystem::path DriveParamsPath(const std::string &robot)
Resolve config/platform/PlatformDynamics<robot>.json inside the armarx_navigation package.
Definition Toppra.cpp:119
This file is part of ArmarX.
Definition io.cpp:36
CommandRampCheck checkAgainstCommandRamp(const core::GlobalTrajectory &trajectory, const simulation::CommandRateLimit &rateLimit, const float boundaryVelocity)
Compare the profile's v * dv/ds against rateLimit.
core::TrajectoryParametrization parametrizationModeFromString(const std::string &name)
Parse the --parametrization choice. Throws on an unknown name.
ReparametrizationResult reparametrize(const core::GlobalTrajectory &trajectory, const Config &config)
Re-assign the velocities along trajectory according to config.parametrization.
std::string toString(const core::TrajectoryParametrization mode)
TrajectoryParametrization
How the velocities along a planned path are assigned.
@ Ramping
Ramp down at the start, the goal and every corner. The stack's historical behaviour.
@ Toppra
Time-optimal under per-motor torque and command-ramp limits.
How far a velocity profile asks for more than the device command ramp can deliver.
bool terminalVelocityExceedsBoundary
Whether the profile ends above boundaryVelocity, i.e. faster than by design.
float worstDemand
Largest demanded tangential acceleration [mm/s^2], and where along the path it is.
float worstLimit
The bound that was exceeded there [mm/s^2].
std::vector< std::pair< float, float > > spans
Arc-length spans in which the demand exceeds the ramp, for the plot.
std::size_t violations
Waypoints demanding more than the ramp can deliver.
float terminalStoppingDistance
Distance the ramp needs to stop from the profile's final velocity [mm].
Everything the application needs, as read from the scene description file.
Definition Scene.h:63
core::GeneralConfig generalConfig
Definition Scene.h:77
core::TrajectoryParametrization parametrization
How the velocities along the planned path are (re-)assigned before simulating.
Definition Scene.h:93
TrajectoryParametrization parametrization
How the planned velocity profile is (re-)assigned before execution.
The per-axis command ramp the platform device applies below the navigation stack.