@@ -164,6 +164,9 @@ TEST(MotionModelTests, AckermannTest)
164164 // VX equal since this doesn't change, the WZ is reduced if breaking the constraint
165165 EXPECT_EQ (initial_control_sequence.vx , control_sequence.vx );
166166 EXPECT_NE (initial_control_sequence.wz , control_sequence.wz );
167+ for (unsigned int i = 1 ; i != control_sequence.wz .shape (0 ); i++) {
168+ EXPECT_GT (control_sequence.wz (i), 0.0 );
169+ }
167170
168171 // Now, check the specifics of the minimum curvature constraint
169172 EXPECT_NEAR (model->getMinTurningRadius (), 0.2 , 1e-6 );
@@ -177,3 +180,78 @@ TEST(MotionModelTests, AckermannTest)
177180 // Check it cleanly destructs
178181 model.reset ();
179182}
183+
184+ TEST (MotionModelTests, AckermannReversingTest)
185+ {
186+ models::ControlSequence control_sequence;
187+ models::ControlSequence control_sequence2;
188+ models::State state;
189+ int batches = 1000 ;
190+ int timesteps = 50 ;
191+ control_sequence.reset (timesteps); // populates with zeros
192+ control_sequence2.reset (timesteps); // populates with zeros
193+ state.reset (batches, timesteps); // populates with zeros
194+ auto node = std::make_shared<rclcpp_lifecycle::LifecycleNode>(" my_node" );
195+ ParametersHandler param_handler (node);
196+ std::unique_ptr<AckermannMotionModel> model =
197+ std::make_unique<AckermannMotionModel>(¶m_handler, node->get_name ());
198+
199+ // Check that predict properly populates the trajectory velocities with the control velocities
200+ state.cvx = 10 * xt::ones<float >({batches, timesteps});
201+ state.cvy = 5 * xt::ones<float >({batches, timesteps});
202+ state.cwz = 1 * xt::ones<float >({batches, timesteps});
203+
204+ // Manually set state index 0 from initial conditions which would be the speed of the robot
205+ xt::view (state.vx , xt::all (), 0 ) = 10 ;
206+ xt::view (state.wz , xt::all (), 0 ) = 1 ;
207+
208+ model->predict (state);
209+
210+ EXPECT_EQ (state.vx , state.cvx );
211+ EXPECT_EQ (state.vy , xt::zeros<float >({batches, timesteps})); // non-holonomic
212+ EXPECT_EQ (state.wz , state.cwz );
213+
214+ // Check that application of constraints are non-empty for Ackermann Drive
215+ for (unsigned int i = 0 ; i != control_sequence.vx .shape (0 ); i++) {
216+ float idx = static_cast <float >(i);
217+ control_sequence.vx (i) = -idx * idx * idx; // now reversing
218+ control_sequence.wz (i) = idx * idx * idx * idx;
219+ }
220+
221+ models::ControlSequence initial_control_sequence = control_sequence;
222+ model->applyConstraints (control_sequence);
223+ // VX equal since this doesn't change, the WZ is reduced if breaking the constraint
224+ EXPECT_EQ (initial_control_sequence.vx , control_sequence.vx );
225+ EXPECT_NE (initial_control_sequence.wz , control_sequence.wz );
226+ for (unsigned int i = 1 ; i != control_sequence.wz .shape (0 ); i++) {
227+ EXPECT_GT (control_sequence.wz (i), 0.0 );
228+ }
229+
230+ // Repeat with negative rotation direction
231+ for (unsigned int i = 0 ; i != control_sequence2.vx .shape (0 ); i++) {
232+ float idx = static_cast <float >(i);
233+ control_sequence2.vx (i) = -idx * idx * idx; // now reversing
234+ control_sequence2.wz (i) = -idx * idx * idx * idx;
235+ }
236+
237+ models::ControlSequence initial_control_sequence2 = control_sequence2;
238+ model->applyConstraints (control_sequence2);
239+ // VX equal since this doesn't change, the WZ is reduced if breaking the constraint
240+ EXPECT_EQ (initial_control_sequence2.vx , control_sequence2.vx );
241+ EXPECT_NE (initial_control_sequence2.wz , control_sequence2.wz );
242+ for (unsigned int i = 1 ; i != control_sequence2.wz .shape (0 ); i++) {
243+ EXPECT_LT (control_sequence2.wz (i), 0.0 );
244+ }
245+
246+ // Now, check the specifics of the minimum curvature constraint
247+ EXPECT_NEAR (model->getMinTurningRadius (), 0.2 , 1e-6 );
248+ for (unsigned int i = 1 ; i != control_sequence2.vx .shape (0 ); i++) {
249+ EXPECT_TRUE (fabs (control_sequence2.vx (i)) / fabs (control_sequence2.wz (i)) >= 0.2 );
250+ }
251+
252+ // Check that Ackermann Drive is properly non-holonomic and parameterized
253+ EXPECT_EQ (model->isHolonomic (), false );
254+
255+ // Check it cleanly destructs
256+ model.reset ();
257+ }
0 commit comments