1use crate::copp::copp2::formulation::Topp2Problem;
21use crate::copp::{ApproxOrdering, approx_order};
22use crate::diag::{
23 CoppError, DebugVerboser, SilentVerboser, SummaryVerboser, TraceVerboser, Verboser, Verbosity,
24 check_abs_rel_tol, check_strictly_positive, format_duration_human,
25};
26use crate::math::numerical::{
27 LP_BOUND, Lp2dWarmStart, LpToleranceOptions, lp_1d, lp_2d_incre_max_y, normalize_lp2d,
28};
29use core::f64;
30
31pub struct ReachSet2 {
34 pub a_max: Vec<f64>,
36 pub a_min: Vec<f64>,
38}
39
40#[inline]
57pub fn reach_set2_backward(
58 problem: &Topp2Problem,
59 options: &ReachSet2Options,
60) -> Result<ReachSet2, CoppError> {
61 match options.verbosity {
62 Verbosity::Silent => reach_set2_core::<false>(problem, (options, SilentVerboser)),
63 Verbosity::Summary => reach_set2_core::<false>(problem, (options, SummaryVerboser::new())),
64 Verbosity::Debug => reach_set2_core::<false>(problem, (options, DebugVerboser::new())),
65 Verbosity::Trace => reach_set2_core::<false>(problem, (options, TraceVerboser::new())),
66 }
67}
68
69#[inline]
85pub fn reach_set2_bidirectional(
86 problem: &Topp2Problem,
87 options: &ReachSet2Options,
88) -> Result<ReachSet2, CoppError> {
89 match options.verbosity {
90 Verbosity::Silent => reach_set2_core::<true>(problem, (options, SilentVerboser)),
91 Verbosity::Summary => reach_set2_core::<true>(problem, (options, SummaryVerboser::new())),
92 Verbosity::Debug => reach_set2_core::<true>(problem, (options, DebugVerboser::new())),
93 Verbosity::Trace => reach_set2_core::<true>(problem, (options, TraceVerboser::new())),
94 }
95}
96
97fn reach_set2_core<const BIDIRECTION: bool>(
99 problem: &Topp2Problem,
100 options_verboser: (&ReachSet2Options, impl Verboser),
101) -> Result<ReachSet2, CoppError> {
102 let (options, mut verboser) = options_verboser;
103 if verboser.is_enabled(Verbosity::Summary) {
104 verboser.record_start_time();
105 crate::verbosity_log!(
106 crate::diag::Verbosity::Summary,
107 "reach_set2 started: {} <= idx_s <= {}, a_start = {}, a_final = {}.",
108 problem.idx_s_interval.0,
109 problem.idx_s_interval.1,
110 problem.a_boundary.0,
111 problem.a_boundary.1,
112 );
113 if BIDIRECTION {
114 crate::verbosity_log!(
115 crate::diag::Verbosity::Summary,
116 "Bidirectional reachable set will be computed."
117 );
118 } else {
119 crate::verbosity_log!(
120 crate::diag::Verbosity::Summary,
121 "Backward reachable set will be computed."
122 );
123 }
124 }
125
126 let (idx_s_start, idx_s_final) = problem.idx_s_interval;
127 let a_max_0 = problem.constraints.amax_unchecked(problem.idx_s_interval.0);
128 if matches!(
129 approx_order(
130 problem.a_boundary.0,
131 a_max_0,
132 options.a_cmp_abs_tol,
133 options.a_cmp_rel_tol,
134 ),
135 ApproxOrdering::Greater
136 ) {
137 let err = CoppError::Infeasible(
138 "reach_set2".into(),
139 format!(
140 "The initial state a_start = {} cannot be greater than the maximum feasible state a_max[0] = {a_max_0} at idx_s_start = {idx_s_start}",
141 problem.a_boundary.0,
142 ),
143 );
144 if verboser.is_enabled(Verbosity::Debug) {
145 crate::verbosity_log!(crate::diag::Verbosity::Summary, "{err:?}");
146 } else if verboser.is_enabled(Verbosity::Summary) {
147 crate::verbosity_log!(
148 crate::diag::Verbosity::Debug,
149 "reach_set2: the backward pass failed at index {idx_s_start} due to infeasibility of the initial state."
150 );
151 }
152 return Err(err);
153 }
154 let a_max_f = problem.constraints.amax_unchecked(problem.idx_s_interval.1);
155 if matches!(
156 approx_order(
157 problem.a_boundary.1,
158 a_max_f,
159 options.a_cmp_abs_tol,
160 options.a_cmp_rel_tol,
161 ),
162 ApproxOrdering::Greater
163 ) {
164 let err = CoppError::Infeasible(
165 "reach_set2".into(),
166 format!(
167 "The final state a_final = {} cannot be greater than the maximum feasible state a_max[n] = {a_max_f} at idx_s_final = {idx_s_final}",
168 problem.a_boundary.1,
169 ),
170 );
171 if verboser.is_enabled(Verbosity::Debug) {
172 crate::verbosity_log!(crate::diag::Verbosity::Summary, "{err:?}");
173 } else if verboser.is_enabled(Verbosity::Summary) {
174 crate::verbosity_log!(
175 crate::diag::Verbosity::Debug,
176 "reach_set2: the backward pass failed at index {idx_s_final} due to infeasibility of the final state."
177 );
178 }
179 return Err(err);
180 }
181
182 let n = idx_s_final - idx_s_start;
184 let mut a_max = vec![f64::INFINITY; n + 1];
185 let mut a_min = vec![0.0; n + 1];
186 *a_max.last_mut().unwrap() = problem.a_boundary.1;
187 *a_min.last_mut().unwrap() = problem.a_boundary.1;
188 if verboser.is_enabled(Verbosity::Debug) {
190 crate::verbosity_log!(crate::diag::Verbosity::Summary, "Backward pass started.");
191 }
192
193 let mut a_b = Vec::<(f64, f64, f64)>::with_capacity(2 + 2 * problem.constraints.acc_rows());
194 let mut a_max_next = problem.a_boundary.1;
195 let mut a_min_next = problem.a_boundary.1;
196 for (k, (a_max_k, a_min_k)) in a_max
197 .iter_mut()
198 .zip(a_min.iter_mut())
199 .take(n)
200 .enumerate()
201 .rev()
202 {
203 let idx_s = idx_s_start + k;
204 if verboser.is_enabled(Verbosity::Trace) {
205 crate::verbosity_log!(
206 crate::diag::Verbosity::Summary,
207 "\tBackward pass at k = {k} (idx_s = {idx_s}) to compute a[k]: {a_min_next} <= a[k+1] <= {a_max_next}."
208 );
209 }
210
211 a_b.clear();
212 a_b.push((1.0, 0.0, a_max_next));
213 a_b.push((-1.0, 0.0, -a_min_next));
214 problem.constraints.fill_acc_topp2::<true>(&mut a_b, idx_s);
215 let a_next_mid = 0.5 * (a_max_next + a_min_next);
218 let (a_max_curr, a_min_curr) = match approx_order(
219 a_max_next,
220 a_min_next,
221 options.a_cmp_abs_tol,
222 options.a_cmp_rel_tol,
223 ) {
224 ApproxOrdering::Equal => {
225 if verboser.is_enabled(Verbosity::Trace) {
226 crate::verbosity_log!(
227 crate::diag::Verbosity::Summary,
228 "\t\ta[k+1] = {a_next_mid}"
229 );
230 }
231 lp_1d::<true>(
232 a_b.iter().skip(2).map(|&coeffs| {
233 (coeffs.1, coeffs.2 - coeffs.0 * a_next_mid)
236 }),
237 &LpToleranceOptions::with_feas_tol(options.lp_feas_tol),
238 )
239 }
240 ApproxOrdering::Greater => {
241 if verboser.is_enabled(Verbosity::Trace) {
242 crate::verbosity_log!(
243 crate::diag::Verbosity::Summary,
244 "\t\t{a_min_next} <= a[k+1] <= {a_max_next}"
245 );
246 }
247 let (a_test_max, a_test_min) = lp_1d::<true>(
249 a_b.iter().skip(2).map(|&coeffs| {
250 (coeffs.0, coeffs.2 - coeffs.1 * *a_min_k)
253 }),
254 &LpToleranceOptions::with_feas_tol(options.lp_feas_tol),
255 );
256 let flag_need_min = a_test_max.is_nan()
257 || a_test_min.is_nan()
258 || a_test_max < a_min_next
259 || a_test_max > a_max_next;
260 if verboser.is_enabled(Verbosity::Trace) {
261 crate::verbosity_log!(
262 crate::diag::Verbosity::Summary,
263 "\t\tBackward skip checks at idx_s = {idx_s}: need_max = true, need_min = {flag_need_min}."
264 );
265 }
266 if flag_need_min {
267 backward_bound_a_next::<true, true>(&mut a_b, a_next_mid, options.lp_feas_tol)
268 } else {
269 (
270 backward_bound_a_next::<true, false>(
271 &mut a_b,
272 a_next_mid,
273 options.lp_feas_tol,
274 )
275 .0,
276 *a_min_k,
277 )
278 }
279 }
280 ApproxOrdering::Less => {
281 let err = CoppError::Infeasible(
282 "reach_set2".into(),
283 format!(
284 "The reachable set is empty at index {idx_s} during the backward pass where a_max_next = {a_max_next}, a_min_next = {a_min_next}"
285 ),
286 );
287 if verboser.is_enabled(Verbosity::Debug) {
288 crate::verbosity_log!(crate::diag::Verbosity::Summary, "{err:?}");
289 } else if verboser.is_enabled(Verbosity::Summary) {
290 crate::verbosity_log!(
291 crate::diag::Verbosity::Debug,
292 "reach_set2: the backward pass failed at index {idx_s} due to infeasibility."
293 );
294 }
295 return Err(err);
296 }
297 };
298
299 if a_max_curr.is_nan() || a_min_curr.is_nan() {
300 let err = CoppError::Infeasible(
301 "reach_set2".into(),
302 format!(
303 "The reachable set is empty at index {idx_s} during the backward pass where a_max = {a_max_curr}, a_min = {a_min_curr}"
304 ),
305 );
306 if verboser.is_enabled(Verbosity::Debug) {
307 crate::verbosity_log!(crate::diag::Verbosity::Summary, "{err:?}");
308 } else if verboser.is_enabled(Verbosity::Summary) {
309 crate::verbosity_log!(
310 crate::diag::Verbosity::Debug,
311 "reach_set2: the backward pass failed at index {idx_s} due to infeasibility."
312 );
313 }
314 return Err(err);
315 }
316 if verboser.is_enabled(Verbosity::Trace) {
317 crate::verbosity_log!(
318 crate::diag::Verbosity::Summary,
319 "\t\tBackward LP result at k = {k}: {a_min_curr} <= a[k] <= {a_max_curr}."
320 );
321 }
322
323 *a_max_k = a_max_curr.min(problem.constraints.amax_unchecked(idx_s));
324 *a_min_k = a_min_curr.max(0.0);
325
326 if verboser.is_enabled(Verbosity::Trace) {
327 crate::verbosity_log!(
328 crate::diag::Verbosity::Summary,
329 "\t\tAfter clipping with path constraints at k = {k}: {} <= a[k] <= {}.",
330 *a_min_k,
331 *a_max_k
332 );
333 }
334
335 match approx_order(
336 *a_max_k,
337 *a_min_k,
338 options.a_cmp_abs_tol,
339 options.a_cmp_rel_tol,
340 ) {
341 ApproxOrdering::Less => {
342 let err = CoppError::Infeasible(
343 "reach_set2".into(),
344 format!(
345 "The reachable set is empty at k = {k} (idx_s = {idx_s}) during the backward pass where a_max = {a_max_k}, a_min = {a_min_k}"
346 ),
347 );
348 if verboser.is_enabled(Verbosity::Debug) {
349 crate::verbosity_log!(crate::diag::Verbosity::Summary, "{err:?}");
350 } else if verboser.is_enabled(Verbosity::Summary) {
351 crate::verbosity_log!(
352 crate::diag::Verbosity::Debug,
353 "reach_set2: the backward pass failed at k = {k} (idx_s = {idx_s}) due to infeasibility."
354 );
355 }
356 return Err(err);
357 }
358 ApproxOrdering::Equal => {
359 if verboser.is_enabled(Verbosity::Debug) && k < n - 1 {
360 crate::verbosity_log!(
361 crate::diag::Verbosity::Summary,
362 "The backward reachable set at k = {k} (idx_s = {idx_s}) is degenerate since a_max = {a_max_k} and a_min = {a_min_k} are approximately equal."
363 );
364 }
365 *a_max_k = 0.5 * (*a_max_k + *a_min_k);
366 *a_min_k = *a_max_k;
367 }
368 ApproxOrdering::Greater => {}
369 }
370 a_max_next = *a_max_k;
371 a_min_next = *a_min_k;
372
373 if verboser.is_enabled(Verbosity::Trace) {
374 crate::verbosity_log!(
375 crate::diag::Verbosity::Summary,
376 "\t\tBackward propagated interval to k-1: {a_min_next} <= a[k] <= {a_max_next}."
377 );
378 }
379 }
380
381 if BIDIRECTION {
382 if verboser.is_enabled(Verbosity::Debug) {
383 crate::verbosity_log!(crate::diag::Verbosity::Summary, "Forward pass started.");
384 }
385
386 *a_max.first_mut().unwrap() = problem.a_boundary.0;
387 *a_min.first_mut().unwrap() = problem.a_boundary.0;
388 let mut a_max_prev = problem.a_boundary.0;
389 let mut a_min_prev = problem.a_boundary.0;
390 for (k, (a_max_k, a_min_k)) in a_max.iter_mut().zip(a_min.iter_mut()).enumerate().skip(1) {
391 let idx_s = idx_s_start + k;
392 if verboser.is_enabled(Verbosity::Trace) {
393 crate::verbosity_log!(
394 crate::diag::Verbosity::Summary,
395 "\tForward pass at k = {k} (idx_s = {idx_s}): prev interval {} <= a[k-1] <= {}, backward interval {} <= a[k] <= {}.",
396 a_min_prev,
397 a_max_prev,
398 *a_min_k,
399 *a_max_k
400 );
401 }
402
403 a_b.clear();
404 a_b.push((1.0, 0.0, a_max_prev));
405 a_b.push((-1.0, 0.0, -a_min_prev));
406 problem
407 .constraints
408 .fill_acc_topp2::<false>(&mut a_b, idx_s_start + k - 1);
409 let a_prev_mid = 0.5 * (a_max_prev + a_min_prev);
412 let (a_max_curr, a_min_curr) = match approx_order(
413 a_max_prev,
414 a_min_prev,
415 options.a_cmp_abs_tol,
416 options.a_cmp_rel_tol,
417 ) {
418 ApproxOrdering::Equal => {
419 lp_1d::<true>(
420 a_b.iter().skip(2).map(|&coeffs| {
421 (coeffs.1, coeffs.2 - coeffs.0 * a_prev_mid)
424 }),
425 &LpToleranceOptions::with_feas_tol(options.lp_feas_tol),
426 )
427 }
428 ApproxOrdering::Greater => {
429 let (a_test_max, a_test_min) = lp_1d::<true>(
431 a_b.iter().skip(2).map(|&coeffs| {
432 (coeffs.0, coeffs.2 - coeffs.1 * *a_max_k)
435 }),
436 &LpToleranceOptions::with_feas_tol(options.lp_feas_tol),
437 );
438 let flag_need_max = a_test_max.is_nan()
439 || a_test_min.is_nan()
440 || a_test_max < a_min_prev
441 || a_test_max > a_max_prev;
442 let (a_test_max, a_test_min) = lp_1d::<true>(
444 a_b.iter().skip(2).map(|&coeffs| {
445 (coeffs.0, coeffs.2 - coeffs.1 * *a_min_k)
448 }),
449 &LpToleranceOptions::with_feas_tol(options.lp_feas_tol),
450 );
451 let flag_need_min = a_test_max.is_nan()
452 || a_test_min.is_nan()
453 || a_test_max < a_min_prev
454 || a_test_max > a_max_prev;
455
456 if verboser.is_enabled(Verbosity::Trace) {
457 crate::verbosity_log!(
458 crate::diag::Verbosity::Summary,
459 "\t\tForward skip checks at idx_s = {idx_s}: need_max = {flag_need_max}, need_min = {flag_need_min}."
460 );
461 }
462
463 match (flag_need_max, flag_need_min) {
465 (true, true) => backward_bound_a_next::<true, true>(
466 &mut a_b,
467 a_prev_mid,
468 options.lp_feas_tol,
469 ),
470 (true, false) => (
471 backward_bound_a_next::<true, false>(
472 &mut a_b,
473 a_prev_mid,
474 options.lp_feas_tol,
475 )
476 .0,
477 *a_min_k,
478 ),
479 (false, true) => (
480 *a_max_k,
481 backward_bound_a_next::<false, true>(
482 &mut a_b,
483 a_prev_mid,
484 options.lp_feas_tol,
485 )
486 .1,
487 ),
488 (false, false) => (*a_max_k, *a_min_k),
489 }
490 }
491 ApproxOrdering::Less => {
492 let err = CoppError::Infeasible(
493 "reach_set2".into(),
494 format!(
495 "The reachable set is empty at index {idx_s} during the forward pass where a_max_prev = {a_max_prev}, a_min_prev = {a_min_prev}"
496 ),
497 );
498 if verboser.is_enabled(Verbosity::Debug) {
499 crate::verbosity_log!(crate::diag::Verbosity::Summary, "{err:?}");
500 } else if verboser.is_enabled(Verbosity::Summary) {
501 crate::verbosity_log!(
502 crate::diag::Verbosity::Debug,
503 "reach_set2: the forward pass failed at index {idx_s} due to infeasibility."
504 );
505 }
506 return Err(err);
507 }
508 };
509
510 if verboser.is_enabled(Verbosity::Trace) {
511 crate::verbosity_log!(
512 crate::diag::Verbosity::Summary,
513 "\t\tForward LP result at idx_s = {idx_s}: {a_min_curr} <= a[k] <= {a_max_curr}."
514 );
515 }
516
517 if a_max_curr.is_nan() || a_min_curr.is_nan() {
518 let err = CoppError::Infeasible(
519 "reach_set2".into(),
520 format!(
521 "The reachable set is empty at index {idx_s} during the forward pass where a_max = {a_max_curr}, a_min = {a_min_curr}"
522 ),
523 );
524 if verboser.is_enabled(Verbosity::Debug) {
525 crate::verbosity_log!(crate::diag::Verbosity::Summary, "{err:?}");
526 } else if verboser.is_enabled(Verbosity::Summary) {
527 crate::verbosity_log!(
528 crate::diag::Verbosity::Debug,
529 "reach_set2: the forward pass failed at index {idx_s} due to infeasibility."
530 );
531 }
532 return Err(err);
533 }
534
535 a_max_prev = a_max_curr.min(*a_max_k);
536 a_min_prev = a_min_curr.max(*a_min_k);
537 if verboser.is_enabled(Verbosity::Trace) {
538 crate::verbosity_log!(
539 crate::diag::Verbosity::Summary,
540 "\t\tAfter intersecting with backward interval at idx_s = {idx_s}: {a_min_prev} <= a[k] <= {a_max_prev}."
541 );
542 }
543 match approx_order(
544 a_max_prev,
545 a_min_prev,
546 options.a_cmp_abs_tol,
547 options.a_cmp_rel_tol,
548 ) {
549 ApproxOrdering::Less => {
550 let err = CoppError::Infeasible(
551 "reach_set2".into(),
552 format!(
553 "The reachable set is empty at index {idx_s} during the forward pass where a_max = {a_max_prev}, a_min = {a_min_prev}"
554 ),
555 );
556 if verboser.is_enabled(Verbosity::Debug) {
557 crate::verbosity_log!(crate::diag::Verbosity::Summary, "{err:?}");
558 } else if verboser.is_enabled(Verbosity::Summary) {
559 crate::verbosity_log!(
560 crate::diag::Verbosity::Debug,
561 "reach_set2: the forward pass failed at index {idx_s} due to infeasibility."
562 );
563 }
564 return Err(err);
565 }
566 ApproxOrdering::Equal => {
567 if verboser.is_enabled(Verbosity::Debug) && k < n - 1 {
568 crate::verbosity_log!(
569 crate::diag::Verbosity::Summary,
570 "The forward reachable set at idx_s = {idx_s} is degenerate since a_max_prev and a_min_prev are approximately equal at {a_prev_mid}."
571 );
572 }
573 a_max_prev = 0.5 * (a_max_prev + a_min_prev);
574 a_min_prev = a_max_prev;
575 }
576 ApproxOrdering::Greater => {}
577 }
578 if verboser.is_enabled(Verbosity::Trace) {
579 crate::verbosity_log!(
580 crate::diag::Verbosity::Summary,
581 "\t\tForward propagated interval to next step: {a_min_prev} <= a[k] <= {a_max_prev}."
582 );
583 }
584 *a_max_k = a_max_prev;
585 *a_min_k = a_min_prev;
586 }
587 }
588
589 if verboser.is_enabled(Verbosity::Summary) {
590 crate::verbosity_log!(
591 crate::diag::Verbosity::Summary,
592 "reach_set2: {}backward total elapsed time = {}.",
593 if BIDIRECTION { "forward + " } else { "" },
594 format_duration_human(verboser.elapsed())
595 );
596 }
597
598 Ok(ReachSet2 { a_max, a_min })
599}
600
601fn backward_bound_a_next<const MAX: bool, const MIN: bool>(
605 a_b: &mut [(f64, f64, f64)],
606 a_next_mid: f64,
607 lp_fea_tol: f64,
608) -> (f64, f64) {
609 let warm_start = Lp2dWarmStart {
610 x0: (a_next_mid, LP_BOUND),
611 skip: 2,
612 };
613
614 normalize_lp2d(a_b);
615 let a_curr_max = if MAX {
616 let (_, a_curr_max) = lp_2d_incre_max_y::<_, false>(
617 a_b,
618 &warm_start,
619 &LpToleranceOptions::with_feas_tol(lp_fea_tol),
620 );
621 a_curr_max
622 } else {
623 f64::INFINITY
624 };
625
626 let a_curr_min = if MIN {
627 a_b.iter_mut().for_each(|(_, b, _)| {
629 *b = -*b;
630 });
631 let (_, a_curr_min_neg) = lp_2d_incre_max_y::<_, false>(
632 a_b,
633 &warm_start,
634 &LpToleranceOptions::with_feas_tol(lp_fea_tol),
635 );
636 -(a_curr_min_neg.min(0.0))
637 } else {
638 0.0
639 };
640
641 (a_curr_max, a_curr_min)
642}
643
644pub struct ReachSet2OptionsBuilder {
646 pub lp_feas_tol: f64,
648 pub a_cmp_abs_tol: f64,
650 pub a_cmp_rel_tol: f64,
652 pub verbosity: Verbosity,
654}
655
656impl Default for ReachSet2OptionsBuilder {
657 #[inline]
658 fn default() -> Self {
659 Self {
660 lp_feas_tol: 1e-8,
661 a_cmp_abs_tol: 1e-8,
662 a_cmp_rel_tol: 1e-8,
663 verbosity: Verbosity::default(),
664 }
665 }
666}
667
668impl ReachSet2OptionsBuilder {
669 pub fn new() -> Self {
671 Default::default()
672 }
673
674 pub fn lp_feas_tol(mut self, tol: f64) -> Self {
677 self.lp_feas_tol = tol;
678 self
679 }
680
681 #[inline]
689 pub fn a_cmp_abs_tol(mut self, tol: f64) -> Self {
690 self.a_cmp_abs_tol = tol;
691 self
692 }
693
694 #[inline]
697 pub fn a_cmp_rel_tol(mut self, tol: f64) -> Self {
698 self.a_cmp_rel_tol = tol;
699 self
700 }
701
702 #[inline]
705 pub fn verbosity(mut self, verbosity: Verbosity) -> Self {
706 self.verbosity = verbosity;
707 self
708 }
709
710 #[inline]
712 pub fn build(self) -> Result<ReachSet2Options, CoppError> {
713 self.validate()?;
714 Ok(ReachSet2Options {
715 lp_feas_tol: self.lp_feas_tol,
716 a_cmp_abs_tol: self.a_cmp_abs_tol,
717 a_cmp_rel_tol: self.a_cmp_rel_tol,
718 verbosity: self.verbosity,
719 })
720 }
721
722 #[inline]
724 pub fn validate(&self) -> Result<(), CoppError> {
725 check_strictly_positive("ReachSet2OptionsBuilder", "lp_feas_tol", self.lp_feas_tol)?;
726 check_abs_rel_tol(
727 "ReachSet2OptionsBuilder",
728 "a_cmp_abs_tol",
729 self.a_cmp_abs_tol,
730 "a_cmp_rel_tol",
731 self.a_cmp_rel_tol,
732 )?;
733 Ok(())
734 }
735}
736
737pub struct ReachSet2Options {
739 pub(crate) lp_feas_tol: f64,
740 pub(crate) a_cmp_abs_tol: f64,
741 pub(crate) a_cmp_rel_tol: f64,
742 pub(crate) verbosity: Verbosity,
743}
744
745impl ReachSet2Options {
746 #[inline]
747 pub fn lp_feas_tol(&self) -> f64 {
749 self.lp_feas_tol
750 }
751 #[inline]
752 pub fn a_cmp_abs_tol(&self) -> f64 {
754 self.a_cmp_abs_tol
755 }
756 #[inline]
757 pub fn a_cmp_rel_tol(&self) -> f64 {
759 self.a_cmp_rel_tol
760 }
761 #[inline]
762 pub fn verbosity(&self) -> Verbosity {
764 self.verbosity
765 }
766}
767
768#[cfg(test)]
769mod tests {
770 use super::*;
771 use crate::copp::copp2::stable::basic::Topp2ProblemBuilder;
772 use crate::copp::copp2::stable::reach_set2::reach_set2_backward;
773 use crate::copp::copp2::stable::topp2_ra::topp2_ra;
774 use crate::path::{add_symmetric_axial_limits_for_test, lissajous_path_for_test};
775 use crate::robot::robot_core::Robot;
776
777 #[test]
778 fn test_reach_set2() -> Result<(), CoppError> {
779 let dim = 7;
780 let n: usize = 1000;
781
782 let options = ReachSet2OptionsBuilder::new()
783 .lp_feas_tol(1E-9)
784 .a_cmp_abs_tol(1E-9)
785 .a_cmp_rel_tol(1E-9)
786 .verbosity(Verbosity::Summary)
787 .build()?;
788
789 let mut robot = Robot::with_capacity(dim, n);
790 let mut rng = rand::rng();
791
792 let (s, path, _, _) = lissajous_path_for_test(dim, n, &mut rng).map_err(|e| {
793 CoppError::InvalidInput(
794 "test_reach_set2_with_path_helper".into(),
795 format!("failed to generate test path: {e}"),
796 )
797 })?;
798
799 robot
800 .with_s(&s.as_view())?
801 .with_q_from_path_3rd(&path, 0, n)?;
802
803 add_symmetric_axial_limits_for_test(&mut robot, 1.0, 1.0, Some(5.0)).map_err(|e| {
804 CoppError::InvalidInput(
805 "test_reach_set2_with_path_helper".into(),
806 format!("failed to add symmetric axial limits: {e}"),
807 )
808 })?;
809
810 let topp2_problem = Topp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0)).build()?;
811
812 let reach_set_back = reach_set2_backward(&topp2_problem, &options)?;
813 let reach_set_for = reach_set2_bidirectional(&topp2_problem, &options)?;
814 let a_ra = topp2_ra(&topp2_problem, &options)?;
815
816 crate::verbosity_log!(
817 crate::diag::Verbosity::Summary,
818 "a_max_back.len() = {};",
819 reach_set_back.a_max.len()
820 );
821 crate::verbosity_log!(
822 crate::diag::Verbosity::Summary,
823 "a_max_for.len() = {};",
824 reach_set_for.a_max.len()
825 );
826 crate::verbosity_log!(
827 crate::diag::Verbosity::Summary,
828 "a_ra.len() = {};",
829 a_ra.len()
830 );
831
832 Ok(())
833 }
834}