ZonoOpt v1.0.0
Loading...
Searching...
No Matches
PolymorphicFunctions.hpp
Go to the documentation of this file.
1#ifndef ZONOOPT_POLYMORPHIC_FUNCTIONS_HPP_
2#define ZONOOPT_POLYMORPHIC_FUNCTIONS_HPP_
3
15#include <stdexcept>
16#include <algorithm>
17#include <utility>
18#include <memory>
19#include <cmath>
20
21#include "Zono.hpp"
22#include "ConZono.hpp"
23#include "HybZono.hpp"
24#include "Point.hpp"
25#include "EmptySet.hpp"
27
28namespace ZonoOpt
29{
30
31using namespace detail;
32
33// type checking
34inline bool HybZono::is_point() const
35{
36 return dynamic_cast<const Point*>(this) != nullptr;
37}
38
39inline bool HybZono::is_zono() const
40{
41 return (dynamic_cast<const Zono*>(this) != nullptr) && (dynamic_cast<const Point*>(this) == nullptr);
42}
43
44inline bool HybZono::is_conzono() const
45{
46 return (dynamic_cast<const ConZono*>(this) != nullptr) && (dynamic_cast<const Zono*>(this) == nullptr);
47}
48
49inline bool HybZono::is_hybzono() const
50{
51 return (dynamic_cast<const HybZono*>(this) != nullptr) && (dynamic_cast<const ConZono*>(this) == nullptr) &&
52 dynamic_cast<const EmptySet*>(this) == nullptr;
53}
54
55inline bool HybZono::is_empty_set() const
56{
57 return dynamic_cast<const EmptySet*>(this) != nullptr;
58}
59
60
61inline std::unique_ptr<HybZono> affine_map(const HybZono& Z,
62 const Eigen::SparseMatrix<zono_float>& R, const Eigen::Vector<zono_float, -1>& s)
63{
64 // check dimensions
65 Eigen::Vector<zono_float, -1> s_def;
66 const Eigen::Vector<zono_float, -1> * s_ptr = nullptr;
67 if (s.size() == 0) // default argument
68 {
69 s_def.resize(R.rows());
70 s_def.setZero();
71 s_ptr = &s_def;
72 }
73 else
74 {
75 s_ptr = &s;
76 }
77
78 if (R.cols() != Z.n || R.rows() != s_ptr->size())
79 {
80 throw std::invalid_argument("Linear_map: invalid input dimensions.");
81 }
82
83 // early exit
84 if (Z.is_empty_set()) {
85 return std::make_unique<EmptySet>(R.rows());
86 }
87
88 // apply affine map
89 Eigen::SparseMatrix<zono_float> Gc = R*Z.Gc;
90 Eigen::SparseMatrix<zono_float> Gb = R*Z.Gb;
91 Eigen::Vector<zono_float, -1> c = R*Z.c + *s_ptr;
92
93 // output correct type
94 if (Z.is_hybzono())
95 return std::make_unique<HybZono>(Gc, Gb, c, Z.Ac, Z.Ab, Z.b, Z.zero_one_form);
96 else if (Z.is_conzono())
97 return std::make_unique<ConZono>(Gc, c, Z.A, Z.b, Z.zero_one_form);
98 else if (Z.is_zono())
99 return std::make_unique<Zono>(Gc, c, Z.zero_one_form);
100 else
101 return std::make_unique<Point>(c);
102}
103
104inline std::unique_ptr<HybZono> project_onto_dims(const HybZono& Z, const std::vector<int>& dims)
105{
106 // make sure all dims are >= 0 and < n
107 for (const int dim : dims)
108 {
109 if (dim < 0 || dim >= Z.n)
110 {
111 throw std::invalid_argument("Project onto dims: invalid dimension.");
112 }
113 }
114
115 // early exit
116 if (Z.is_empty_set()) {
117 return std::make_unique<EmptySet>(dims.size());
118 }
119
120 // build affine map matrix
121 Eigen::SparseMatrix<zono_float> R (dims.size(), Z.n);
122 std::vector<Eigen::Triplet<zono_float>> tripvec;
123 for (int i=0; i<dims.size(); i++)
124 {
125 tripvec.emplace_back(i, dims[i], 1);
126 }
127 R.setFromTriplets(tripvec.begin(), tripvec.end());
128
129 // apply affine map
130 return affine_map(Z, R);
131}
132
133inline std::unique_ptr<HybZono> minkowski_sum(const HybZono& Z1, HybZono& Z2)
134{
135 // check dimensions
136 if (Z1.n != Z2.n)
137 {
138 throw std::invalid_argument("Minkowski sum: n dimensions must match.");
139 }
140
141 // early exit
142 if (Z1.is_empty_set() && Z2.is_empty_set()) {
143 return std::make_unique<EmptySet>(Z1.n);
144 }
145 else if (Z1.is_empty_set() ) {
146 return std::unique_ptr<HybZono>(Z2.clone());
147 }
148 else if (Z2.is_empty_set()) {
149 return std::unique_ptr<HybZono>(Z1.clone());
150 }
151
152 // make sure Z1 and Z2 both using same generator range
153 if (Z1.zero_one_form != Z2.zero_one_form)
154 {
155 Z2.convert_form();
156 }
157
158 std::vector<Eigen::Triplet<zono_float>> tripvec;
159
160 Eigen::SparseMatrix<zono_float> Gc = hcat<zono_float>(Z1.Gc, Z2.Gc);
161 Eigen::SparseMatrix<zono_float> Gb = hcat<zono_float>(Z1.Gb, Z2.Gb);
162 Eigen::Vector<zono_float, -1> c = Z1.c + Z2.c;
163
164 Eigen::SparseMatrix<zono_float> Ac (Z1.nC + Z2.nC, Z1.nGc + Z2.nGc);
165 get_triplets_offset<zono_float>(Z1.Ac, tripvec, 0, 0);
166 get_triplets_offset<zono_float>(Z2.Ac, tripvec, Z1.nC, Z1.nGc);
167 Ac.setFromTriplets(tripvec.begin(), tripvec.end());
168
169 tripvec.clear();
170 Eigen::SparseMatrix<zono_float> Ab (Z1.nC + Z2.nC, Z1.nGb + Z2.nGb);
171 get_triplets_offset<zono_float>(Z1.Ab, tripvec, 0, 0);
172 get_triplets_offset<zono_float>(Z2.Ab, tripvec, Z1.nC, Z1.nGb);
173 Ab.setFromTriplets(tripvec.begin(), tripvec.end());
174
175 Eigen::Vector<zono_float, -1> b (Z1.nC + Z2.nC);
176 b.segment(0, Z1.nC) = Z1.b;
177 b.segment(Z1.nC, Z2.nC) = Z2.b;
178
179 // return correct output type
180 if (Z1.is_hybzono() || Z2.is_hybzono())
181 return std::make_unique<HybZono>(Gc, Gb, c, Ac, Ab, b, Z1.zero_one_form);
182 else if (Z1.is_conzono() || Z2.is_conzono())
183 return std::make_unique<ConZono>(Gc, c, Ac, b, Z1.zero_one_form);
184 else
185 return std::make_unique<Zono>(Gc, c, Z1.zero_one_form);
186}
187
188inline std::unique_ptr<HybZono> intersection(const HybZono& Z1, HybZono& Z2, const Eigen::SparseMatrix<zono_float>& R)
189{
190 // handle default arguments
191 const Eigen::SparseMatrix<zono_float> * R_ptr = nullptr;
192 Eigen::SparseMatrix<zono_float> R_def;
193 if (R.rows() == 0 && R.cols() == 0)
194 {
195 R_def.resize(Z1.n, Z1.n);
196 R_def.setIdentity();
197 R_ptr = &R_def;
198 }
199 else
200 {
201 R_ptr = &R;
202 }
203
204 // check dimensions
205 if (R_ptr->rows() != Z2.n || R_ptr->cols() != Z1.n)
206 {
207 throw std::invalid_argument("Intersection: inconsistent input dimensions.");
208 }
209
210 // early exit
211 if (Z1.is_empty_set() || Z2.is_empty_set()) {
212 return std::make_unique<EmptySet>(Z1.n);
213 }
214
215 // make sure Z1 and Z2 both using same generator range
216 if (Z1.zero_one_form != Z2.zero_one_form)
217 {
218 Z2.convert_form();
219 }
220
221 // compute intersection
222 Eigen::SparseMatrix<zono_float> Gc = Z1.Gc;
223 Gc.conservativeResize(Z1.n, Z1.nGc + Z2.nGc);
224
225 Eigen::SparseMatrix<zono_float> Gb = Z1.Gb;
226 Gb.conservativeResize(Z1.n, Z1.nGb + Z2.nGb);
227
228 Eigen::Vector<zono_float, -1> c = Z1.c;
229
230 std::vector<Eigen::Triplet<zono_float>> tripvec;
231 Eigen::SparseMatrix<zono_float> Ac (Z1.nC + Z2.nC + R_ptr->rows(), Z1.nGc + Z2.nGc);
232 get_triplets_offset<zono_float>(Z1.Ac, tripvec, 0, 0);
233 get_triplets_offset<zono_float>(Z2.Ac, tripvec, Z1.nC, Z1.nGc);
234 Eigen::SparseMatrix<zono_float> RZ1Gc = (*R_ptr)*Z1.Gc;
235 get_triplets_offset<zono_float>(RZ1Gc, tripvec, Z1.nC + Z2.nC, 0);
236 Eigen::SparseMatrix<zono_float> mZ2Gc = -Z2.Gc;
237 get_triplets_offset<zono_float>(mZ2Gc, tripvec, Z1.nC + Z2.nC, Z1.nGc);
238 Ac.setFromTriplets(tripvec.begin(), tripvec.end());
239
240 tripvec.clear();
241 Eigen::SparseMatrix<zono_float> Ab (Z1.nC + Z2.nC + R_ptr->rows(), Z1.nGb + Z2.nGb);
242 get_triplets_offset<zono_float>(Z1.Ab, tripvec, 0, 0);
243 get_triplets_offset<zono_float>(Z2.Ab, tripvec, Z1.nC, Z1.nGb);
244 Eigen::SparseMatrix<zono_float> RZ1Gb = (*R_ptr)*Z1.Gb;
245 get_triplets_offset<zono_float>(RZ1Gb, tripvec, Z1.nC + Z2.nC, 0);
246 Eigen::SparseMatrix<zono_float> mZ2Gb = -Z2.Gb;
247 get_triplets_offset<zono_float>(mZ2Gb, tripvec, Z1.nC + Z2.nC, Z1.nGb);
248 Ab.setFromTriplets(tripvec.begin(), tripvec.end());
249
250
251 Eigen::Vector<zono_float, -1> b (Z1.nC + Z2.nC + R_ptr->rows());
252 b.segment(0, Z1.nC) = Z1.b;
253 b.segment(Z1.nC, Z2.nC) = Z2.b;
254 b.segment(Z1.nC + Z2.nC, R_ptr->rows()) = Z2.c - (*R_ptr)*Z1.c;
255
256 // return correct output type
257 if (Z1.is_hybzono() || Z2.is_hybzono())
258 return std::make_unique<HybZono>(Gc, Gb, c, Ac, Ab, b, Z1.zero_one_form);
259 else
260 return std::make_unique<ConZono>(Gc, c, Ac, b, Z1.zero_one_form);
261}
262
263inline std::unique_ptr<HybZono> intersection_over_dims(const HybZono& Z1,
264 HybZono& Z2, const std::vector<int>& dims)
265{
266 // check dimensions
267 if (Z2.n != dims.size())
268 {
269 throw std::invalid_argument("Intersection over dims: Z2.n must equal number of dimensions.");
270 }
271
272 // make sure dims are >=0 and <Z1.n
273 for (const int dim : dims)
274 {
275 if (dim < 0 || dim >= Z1.n)
276 {
277 throw std::invalid_argument("Intersection over dims: invalid dimension.");
278 }
279 }
280
281 // build projection matrix
282 Eigen::SparseMatrix<zono_float> R (dims.size(), Z1.n);
283 std::vector<Eigen::Triplet<zono_float>> tripvec;
284 for (int i=0; i<dims.size(); i++)
285 {
286 tripvec.emplace_back(i, dims[i], 1);
287 }
288 R.setFromTriplets(tripvec.begin(), tripvec.end());
289
290 // generalized intersection
291 return intersection(Z1, Z2, R);
292}
293
294inline std::unique_ptr<HybZono> halfspace_intersection(HybZono& Z, const Eigen::SparseMatrix<zono_float>& H,
295 const Eigen::Vector<zono_float, -1>& f, const Eigen::SparseMatrix<zono_float>& R)
296{
297 // use the constrain function
298
299 // convert H to row-major to efficiently convert into Inequality form
300 Eigen::SparseMatrix<zono_float, Eigen::RowMajor> H_rm = H;
301 std::vector<Inequality> ineqs;
302 for (int k=0; k<H_rm.rows(); ++k)
303 {
304 // get row of constraint matrix
305 std::vector<Eigen::Triplet<zono_float>> trips_row = get_triplets_row<zono_float>(H_rm, k);
306
307 // build constraint
308 Inequality ineq(H_rm.cols());
309 for (const auto& trip : trips_row)
310 {
311 ineq.add_term(trip.col(), trip.value());
312 }
313 ineq.set_rhs(f(k));
315
316 ineqs.push_back(ineq);
317 }
318
319 // call constrain
320 return constrain(Z, ineqs, R);
321}
322
323// pontryagin difference
324inline std::unique_ptr<HybZono> pontry_diff(HybZono& Z1, HybZono& Z2, bool exact)
325{
326 // check dimensions
327 if (Z1.n != Z2.n)
328 {
329 throw std::invalid_argument("Pontryagin difference: n dimensions must match.");
330 }
331
332 // check empty set inputs
333 if (Z1.is_empty_set() || Z2.is_empty_set())
334 return std::unique_ptr<HybZono>(Z1.clone());
335
336 // check point inputs
337 if (Z1.nG == 0 && !exact)
338 return std::make_unique<EmptySet>(Z1.n);
339 if (Z2.nG == 0)
340 exact = true; // easy to compute exactly
341
342 // check no exact pontry diff with conzono subtrahend
343 if (exact && (Z2.is_conzono() || Z2.is_hybzono()))
344 throw std::invalid_argument("Pontryagin difference: cannot compute exact set when subtrahend is a ConZono or HybZono");
345
346 // require Z1 and Z2 to be in [-1,1] form
347 if (Z1.zero_one_form) Z1.convert_form();
348 if (Z2.zero_one_form) Z2.convert_form();
349
350 // exact case
351 if (exact)
352 {
353 // init Zout
354 std::unique_ptr<HybZono> Z_out (Z1.clone());
355 Z_out->c -= Z2.c;
356
357 // iteratively compute pontryagin difference from columns of Z2 generator matrix
358 const Eigen::Matrix<zono_float, -1, -1> G2 = Z2.G.toDense();
359
360 for (int i=0; i<Z2.nG; ++i)
361 {
362 std::unique_ptr<HybZono> Z_plus (Z_out->clone()), Z_minus (Z_out->clone());
363 Z_plus->c += G2.col(i);
364 Z_minus->c -= G2.col(i);
365 Z_out = intersection(*Z_plus, *Z_minus);
366 }
367
368 return Z_out;
369 }
370
371 // inexact case
372
373 // logic to handle hybrid zonotope cases. Avoiding recursion to prevent redundant get_leaves calculations.
374 if (Z1.is_hybzono() && Z2.is_hybzono())
375 {
376 // get leaves and convert to zonos
377 auto Z1_leaves = Z1.get_leaves();
378 auto Z2_CZ_leaves = Z2.get_leaves();
379 std::vector<Zono> Z2_leaves;
380 for (auto& CZ : Z2_CZ_leaves)
381 {
382 Z2_leaves.push_back(*CZ.to_zono_approx());
383 }
384
385 // take union of pontry diffs for inner approx
386 std::vector<std::unique_ptr<HybZono>> leaf_diffs; // init
387 for (auto& Z1_leaf : Z1_leaves)
388 {
389 std::unique_ptr<HybZono> Z_out; // declare
390 for (auto& Z2_leaf : Z2_leaves)
391 {
392 if (!Z_out) {
393 Z_out = pontry_diff(Z1_leaf, Z2_leaf, false);
394 }
395 else {
396 Z_out = intersection(*Z_out, *pontry_diff(Z1_leaf, Z2_leaf, false));
397 }
398 }
399 leaf_diffs.push_back(std::move(Z_out));
400 }
401 std::vector<HybZono*> leaf_diff_ptrs;
402 for (auto& leaf_diff : leaf_diffs)
403 {
404 leaf_diff_ptrs.emplace_back(leaf_diff.get());
405 }
406 return union_of_many(leaf_diff_ptrs);
407 }
408 else if (Z1.is_hybzono())
409 {
410 // get leaves and convert to zonos
411 auto Z1_leaves = Z1.get_leaves();
412 auto Z2_CZ = dynamic_cast<const ConZono*>(&Z2);
413 Zono Z2_zono = *Z2_CZ->to_zono_approx();
414
415 // take union of pontry diffs for inner approx
416 std::vector<std::unique_ptr<HybZono>> leaf_diffs; // init
417 for (auto& Z1_leaf : Z1_leaves)
418 {
419 leaf_diffs.emplace_back(pontry_diff(Z1_leaf, Z2_zono, false));
420 }
421 std::vector<HybZono*> leaf_diff_ptrs;
422 for (auto& leaf_diff : leaf_diffs)
423 {
424 leaf_diff_ptrs.emplace_back(leaf_diff.get());
425 }
426 return union_of_many(leaf_diff_ptrs);
427 }
428 else if (Z2.is_hybzono())
429 {
430 // get leaves and convert to zonos
431 auto Z2_CZ_leaves = Z2.get_leaves();
432 std::vector<Zono> Z2_leaves;
433 for (auto& CZ : Z2_CZ_leaves)
434 {
435 Z2_leaves.push_back(*CZ.to_zono_approx());
436 }
437
438 // take intersection of pontry diffs
439 std::unique_ptr<HybZono> Z_out; // declare
440 for (auto& Z2_leaf : Z2_leaves)
441 {
442 if (!Z_out) {
443 Z_out = pontry_diff(Z1, Z2_leaf, false);
444 }
445 else {
446 Z_out = intersection(*Z_out, *pontry_diff(Z1, Z2_leaf, false));
447 }
448 }
449 return Z_out;
450 }
451 else
452 {
453 // convert to zono subtrahend (do nothing if already zono)
454 auto Z2_CZ = dynamic_cast<const ConZono*>(&Z2);
455 Zono Z2_zono = *Z2_CZ->to_zono_approx();
456
457 // get [G; A] matrices
458 const Eigen::SparseMatrix<zono_float> GA1 = vcat<zono_float>(Z1.G, Z1.A);
459 Eigen::SparseMatrix<zono_float> GA2 = Z2_zono.G;
460 GA2.conservativeResize(Z2_zono.n + Z1.nC, Z2_zono.nG);
461
462 Eigen::SparseMatrix<zono_float> M;
463 if (Z1.n + Z1.nC == Z1.nG)
464 {
465 // solve GA1^{-1}*GA2
466 Eigen::SparseLU<Eigen::SparseMatrix<zono_float>> lu(GA1);
467 if (lu.info() != Eigen::Success)
468 {
469 throw std::runtime_error("Pontryagin difference: failed to peform LU decomposition. Most likely [G; A] is not full row rank");
470 }
471 M = lu.solve(GA2);
472 }
473 else
474 {
475 // solve pinv(GA1)*GA2 where pinv is right pseudoinverse
476 Eigen::SimplicialLDLT<Eigen::SparseMatrix<zono_float>> ldlt(GA1*GA1.transpose());
477 if (ldlt.info() != Eigen::Success)
478 {
479 throw std::runtime_error("Pontryagin difference: failed to perform LDLT decomposition. Most likely [G; A] is not full row rank");
480 }
481 const Eigen::SparseMatrix<zono_float> ldlt_sol = ldlt.solve(GA2);
482 M = GA1.transpose()*ldlt_sol;
483 }
484
485 std::vector<Eigen::Triplet<zono_float>> triplets;
486 triplets.reserve(Z1.nG);
487 Eigen::Vector<zono_float, -1> e (Z1.nG);
488 for (int i=0; i < Z1.nG; ++i)
489 {
490 e.setZero();
491 e(i) = 1.0;
492 const zono_float d = 1-(e.transpose()*M).cwiseAbs().sum();
493 if (d < 0)
494 {
495 return std::make_unique<EmptySet>(Z1.n);
496 }
497 triplets.emplace_back(i, i, d);
498 }
499 Eigen::SparseMatrix<zono_float> D (Z1.nG, Z1.nG);
500 D.setFromSortedTriplets(triplets.begin(), triplets.end());
501
502 return std::make_unique<ConZono>(Z1.G*D, Z1.c - Z2_zono.c, Z1.A*D, Z1.b, false);
503 }
504}
505
506inline std::unique_ptr<HybZono> union_of_many(const std::vector<HybZono*>& Zs_in, const bool preserve_sharpness, const bool expose_indicators)
507{
508 // remove empty sets from sets to be unioned
509 std::vector<HybZono*> Zs;
510 for (HybZono* Z : Zs_in) {
511 if (!Z->is_empty_set()) {
512 Zs.push_back(Z);
513 }
514 }
515
516 // check we are taking a union of at least one zonotope
517 if (Zs.empty())
518 {
519 throw std::invalid_argument("Union: empty input vector.");
520 }
521
522 // check dimensions
523 const int n = Zs[0]->n;
524 for (const auto & Z : Zs)
525 {
526 if (Z->n != n)
527 {
528 throw std::invalid_argument("Union: inconsistent dimensions.");
529 }
530 }
531
532 // make sure all Zs are using [0,1] generators
533 for (const auto & Z : Zs)
534 {
535 if (!Z->zero_one_form)
536 {
537 Z->convert_form();
538 }
539 }
540
541 // declare
542 std::vector<Eigen::Triplet<zono_float>> tripvec;
543 int rows = 0, cols = 0;
544 std::vector<int> idx_sum_to_1;
545 Eigen::SparseMatrix<zono_float> Gc, Gb, Ac, Ab;
546 Eigen::Vector<zono_float, -1> c, b;
547
548 if (preserve_sharpness)
549 {
550 // constraints
551
552 // Ac
553 tripvec.clear();
554 rows = 0;
555 cols = 0;
556 for (const auto & Z : Zs)
557 {
558 // equality constraints
559 get_triplets_offset<zono_float>(Z->Ac, tripvec, rows, cols);
560 rows += Z->nC;
561
562 // identity matrices
563 for (int i=0; i<Z->nGc; i++)
564 {
565 tripvec.emplace_back(rows+i, cols+i, 1);
566 }
567 for (int i=0; i<Z->nG; i++)
568 {
569 tripvec.emplace_back(rows+i, cols+Z->nGc+i, 1);
570 }
571
572 // increment
573 rows += Z->nG;
574 cols += Z->nGc + Z->nG;
575 }
576 Ac.resize(rows+1, cols); // last row all zeroes
577 Ac.setFromTriplets(tripvec.begin(), tripvec.end());
578
579 // Ab
580 tripvec.clear();
581 rows = 0;
582 cols = 0;
583 for (const auto & Z : Zs)
584 {
585 // equality constraints
586 get_triplets_offset<zono_float>(Z->Ab, tripvec, rows, cols);
587
588 // last column
589 for (int i=0; i<Z->nC; i++)
590 {
591 tripvec.emplace_back(rows+i, cols+Z->nGb, -Z->b(i));
592 }
593 rows += Z->nC;
594
595 // identity matrix
596 for (int i=0; i<Z->nGb; i++)
597 {
598 tripvec.emplace_back(rows+Z->nGc+i, cols+i, 1);
599 }
600
601 // last column
602 for (int i=0; i<Z->nG; i++)
603 {
604 tripvec.emplace_back(rows+i, cols+Z->nGb, -1);
605 }
606
607 // increment
608 rows += Z->nG;
609 cols += Z->nGb + 1;
610
611 // track sum-to-1 binaries
612 idx_sum_to_1.push_back(cols-1);
613 }
614 // sum to 1 constraint
615 for (int & it : idx_sum_to_1)
616 {
617 tripvec.emplace_back(rows, it, 1);
618 }
619 rows++;
620 Ab.resize(rows, cols);
621 Ab.setFromTriplets(tripvec.begin(), tripvec.end());
622
623 // b
624 b.resize(Ab.rows());
625 b.setZero();
626 b(Ab.rows()-1) = 1.0;
627
628 // generators
629 int n_out = n;
630 if (expose_indicators)
631 n_out += static_cast<int>(Zs.size());
632
633 // Gc
634 tripvec.clear();
635 cols = 0;
636 for (const auto & Z : Zs)
637 {
638 get_triplets_offset<zono_float>(Z->Gc, tripvec, 0, cols);
639 cols += 2*Z->nGc + Z->nGb;
640 }
641 Gc.resize(n_out, cols);
642 Gc.setFromTriplets(tripvec.begin(), tripvec.end());
643
644 // Gb
645 tripvec.clear();
646 cols = 0;
647 for (const auto & Z : Zs)
648 {
649 get_triplets_offset<zono_float>(Z->Gb, tripvec, 0, cols);
650 cols += Z->nGb;
651 for (int i=0; i<Z->n; i++)
652 {
653 tripvec.emplace_back(i, cols, Z->c(i));
654 }
655 cols += 1;
656 }
657 for (int i=n; i<n_out; ++i)
658 {
659 tripvec.emplace_back(i, idx_sum_to_1[i-n], 1.0);
660 }
661 Gb.resize(n_out, cols);
662 Gb.setFromTriplets(tripvec.begin(), tripvec.end());
663
664 // c
665 c.resize(n_out);
666 c.setZero();
667 }
668 else
669 {
670 // constraints
671
672 // Ac
673 tripvec.clear();
674 rows = 0;
675 cols = 0;
676 for (const auto & Z : Zs)
677 {
678 // populate first row
679 for (int i=0; i<Z->nGc; i++)
680 {
681 tripvec.emplace_back(rows, cols+i, 1);
682 }
683 tripvec.emplace_back(rows, cols+Z->nGc, static_cast<zono_float>(Z->nG));
684
685 // increment
686 rows++;
687
688 // equality constraints
689 get_triplets_offset<zono_float>(Z->Ac, tripvec, rows, cols);
690
691 // increment
692 rows += Z->nC;
693 cols += Z->nGc + 1;
694 }
695 Ac.resize(rows+1, cols); // last row all zeroes
696 Ac.setFromTriplets(tripvec.begin(), tripvec.end());
697
698 // Ab
699 tripvec.clear();
700 rows = 0;
701 cols = 0;
702 for (const auto & Z : Zs)
703 {
704 // populate first row
705 for (int i=0; i<Z->nGb; i++)
706 {
707 tripvec.emplace_back(rows, cols+i, 1);
708 }
709 tripvec.emplace_back(rows, cols+Z->nGb, static_cast<zono_float>(-Z->nG));
710
711 // increment
712 rows++;
713
714 // equality constraints
715 get_triplets_offset<zono_float>(Z->Ab, tripvec, rows, cols);
716
717 // last column
718 for (int i=0; i<Z->nC; i++)
719 {
720 tripvec.emplace_back(rows+i, cols+Z->nGb, -Z->b(i));
721 }
722
723 // increment
724 rows += Z->nC;
725 cols += Z->nGb + 1;
726
727 // track sum-to-1 binaries
728 idx_sum_to_1.push_back(cols-1);
729 }
730 // sum to 1 constraint
731 for (int & it : idx_sum_to_1)
732 {
733 tripvec.emplace_back(rows, it, 1);
734 }
735 rows++;
736 Ab.resize(rows, cols);
737 Ab.setFromTriplets(tripvec.begin(), tripvec.end());
738
739 // b
740 b.resize(Ab.rows());
741 b.setZero();
742 b(Ab.rows()-1) = 1.0;
743
744 // generators
745 int n_out = n;
746 if (expose_indicators)
747 n_out += static_cast<int>(Zs.size());
748
749 // Gc
750 tripvec.clear();
751 cols = 0;
752 for (const auto & Z : Zs)
753 {
754 get_triplets_offset<zono_float>(Z->Gc, tripvec, 0, cols);
755 cols += Z->nGc + 1;
756 }
757 Gc.resize(n_out, cols);
758 Gc.setFromTriplets(tripvec.begin(), tripvec.end());
759
760 // Gb
761 tripvec.clear();
762 cols = 0;
763 for (const auto & Z : Zs)
764 {
765 get_triplets_offset<zono_float>(Z->Gb, tripvec, 0, cols);
766 cols += Z->nGb;
767 for (int i=0; i<Z->n; i++)
768 {
769 tripvec.emplace_back(i, cols, Z->c(i));
770 }
771 cols += 1;
772 }
773 for (int i=n; i<n_out; ++i)
774 {
775 tripvec.emplace_back(i, idx_sum_to_1[i-n], 1.0);
776 }
777 Gb.resize(n_out, cols);
778 Gb.setFromTriplets(tripvec.begin(), tripvec.end());
779
780 // c
781 c.resize(n_out);
782 c.setZero();
783 }
784
785 // check if known to be sharp
786 bool sharp = preserve_sharpness;
787 int i = 0;
788 while (sharp && i < Zs.size())
789 {
790 sharp = Zs[i]->sharp;
791 ++i;
792 }
793
794 // return hybrid zonotope
795 return std::make_unique<HybZono>(Gc, Gb, c, Ac, Ab, b, true, sharp);
796}
797
798inline std::unique_ptr<HybZono> cartesian_product(const HybZono& Z1, HybZono& Z2)
799{
800 // trivial case
801 if (Z1.is_empty_set() || Z2.is_empty_set()) {
802 return std::make_unique<EmptySet>(Z1.n + Z2.n);
803 }
804
805 // make sure Z1 and Z2 both using same generator range
806 if (Z1.zero_one_form != Z2.zero_one_form)
807 {
808 Z2.convert_form();
809 }
810
811 // declare
812 std::vector<Eigen::Triplet<zono_float>> tripvec;
813
814 // take Cartesian product
815 Eigen::SparseMatrix<zono_float> Gc (Z1.n + Z2.n, Z1.nGc + Z2.nGc);
816 get_triplets_offset<zono_float>(Z1.Gc, tripvec, 0, 0);
817 get_triplets_offset<zono_float>(Z2.Gc, tripvec, Z1.n, Z1.nGc);
818 Gc.setFromTriplets(tripvec.begin(), tripvec.end());
819
820 tripvec.clear();
821 Eigen::SparseMatrix<zono_float> Gb (Z1.n + Z2.n, Z1.nGb + Z2.nGb);
822 get_triplets_offset<zono_float>(Z1.Gb, tripvec, 0, 0);
823 get_triplets_offset<zono_float>(Z2.Gb, tripvec, Z1.n, Z1.nGb);
824 Gb.setFromTriplets(tripvec.begin(), tripvec.end());
825
826 Eigen::Vector<zono_float, -1> c (Z1.n + Z2.n);
827 c.segment(0, Z1.n) = Z1.c;
828 c.segment(Z1.n, Z2.n) = Z2.c;
829
830 tripvec.clear();
831 Eigen::SparseMatrix<zono_float> Ac (Z1.nC + Z2.nC, Z1.nGc + Z2.nGc);
832 get_triplets_offset<zono_float>(Z1.Ac, tripvec, 0, 0);
833 get_triplets_offset<zono_float>(Z2.Ac, tripvec, Z1.nC, Z1.nGc);
834 Ac.setFromTriplets(tripvec.begin(), tripvec.end());
835
836 tripvec.clear();
837 Eigen::SparseMatrix<zono_float> Ab (Z1.nC + Z2.nC, Z1.nGb + Z2.nGb);
838 get_triplets_offset<zono_float>(Z1.Ab, tripvec, 0, 0);
839 get_triplets_offset<zono_float>(Z2.Ab, tripvec, Z1.nC, Z1.nGb);
840 Ab.setFromTriplets(tripvec.begin(), tripvec.end());
841
842 Eigen::Vector<zono_float, -1> b (Z1.nC + Z2.nC);
843 b.segment(0, Z1.nC) = Z1.b;
844 b.segment(Z1.nC, Z2.nC) = Z2.b;
845
846 // return correct output type
847 if (Z1.is_hybzono() || Z2.is_hybzono())
848 return std::make_unique<HybZono>(Gc, Gb, c, Ac, Ab, b, Z1.zero_one_form);
849 else if (Z1.is_conzono() || Z2.is_conzono())
850 return std::make_unique<ConZono>(Gc, c, Ac, b, Z1.zero_one_form);
851 else if (Z1.is_zono() || Z2.is_zono())
852 return std::make_unique<Zono>(Gc, c, Z1.zero_one_form);
853 else
854 return std::make_unique<Point>(c);
855}
856
857inline std::unique_ptr<HybZono> constrain(HybZono& Z, const std::vector<Inequality> &ineqs, const Eigen::SparseMatrix<zono_float>& R)
858{
859 // trivial case
860 if (Z.is_empty_set()) {
861 return std::make_unique<EmptySet>(Z.n);
862 }
863
864 // handle default arguments
865 const Eigen::SparseMatrix<zono_float> * R_ptr = nullptr;
866 Eigen::SparseMatrix<zono_float> R_def;
867 if (R.rows() == 0 && R.cols() == 0)
868 {
869 R_def.resize(Z.n, Z.n);
870 R_def.setIdentity();
871 R_ptr = &R_def;
872 }
873 else
874 {
875 R_ptr = &R;
876 }
877
878 // check that dimensions match
879 for (const auto& ineq : ineqs)
880 {
881 if (R_ptr->rows() != ineq.get_n_dims())
882 throw std::invalid_argument("Inequality does not have the same number of dimensions as set");
883 }
884
885 // make sure Z in 0-1 form
886 if (!Z.is_0_1_form())
887 Z.convert_form();
888
889 // build constraints
890 std::vector<Eigen::Triplet<zono_float>> triplets_Ac, triplets_Ab;
891 Eigen::Vector<zono_float, -1> b_new (ineqs.size());
892 Eigen::Index n_cons = 0, n_slack = 0;
893 Eigen::SparseMatrix<zono_float, Eigen::RowMajor> RGc = (*R_ptr)*Z.Gc;
894 Eigen::SparseMatrix<zono_float, Eigen::RowMajor> RGb = (*R_ptr)*Z.Gb;
895 Eigen::Vector<zono_float, -1> Rc = (*R_ptr)*Z.c;
896
897 for (const auto& ineq : ineqs)
898 {
899 zono_float rhs;
900 switch (ineq.get_ineq_type())
901 {
902 case LESS:
903 rhs = ineq.get_rhs() - zono_eps;
904 break;
905 case GREATER:
906 rhs = ineq.get_rhs() + zono_eps;
907 break;
908 default:
909 rhs = ineq.get_rhs();
910 break;
911 }
912 zono_float gamma = rhs; // slack variable scaling
913 zono_float db = 0;
914 const auto ineq_type = ineq.get_ineq_type();
915
916 for (const auto& [idx, coeff] : ineq.get_terms())
917 {
918 const auto trips_Gc = get_triplets_row<zono_float>(RGc, idx);
919 for (const auto& trip : trips_Gc)
920 {
921 const zono_float val = coeff * trip.value();
922 triplets_Ac.emplace_back(n_cons, trip.col(), val);
923 if ((val < 0 && (ineq_type==LESS_OR_EQUAL || ineq_type==LESS)) || (val > 0 && (ineq_type==GREATER_OR_EQUAL || ineq_type==GREATER)))
924 gamma -= val;
925 }
926
927 const auto trips_Gb = get_triplets_row<zono_float>(RGb, idx);
928 for (const auto& trip : trips_Gb)
929 {
930 const zono_float val = coeff * trip.value();
931 triplets_Ab.emplace_back(n_cons, trip.col(), val);
932 if ((val < 0 && (ineq_type==LESS_OR_EQUAL || ineq_type==LESS)) || (val > 0 && (ineq_type==GREATER_OR_EQUAL || ineq_type==GREATER)))
933 gamma -= val;
934 }
935
936 const zono_float ddb = coeff * Rc(idx);
937 db -= ddb;
938 gamma -= ddb;
939 }
940
941 // rhs
942 b_new(n_cons) = rhs + db;
943
944 // add slack variable
945 if (ineq_type!=EQUAL)
946 {
947 triplets_Ac.emplace_back(n_cons, Z.nGc + n_slack, gamma);
948 ++n_slack; // increment slack variable index
949 }
950
951 // increment
952 ++n_cons;
953 }
954
955 Eigen::SparseMatrix<zono_float> Ac_cons (static_cast<Eigen::Index>(ineqs.size()), Z.nGc + n_slack);
956 Eigen::SparseMatrix<zono_float> Ab_cons (static_cast<Eigen::Index>(ineqs.size()), Z.nGb);
957 Ac_cons.setFromTriplets(triplets_Ac.begin(), triplets_Ac.end());
958 Ab_cons.setFromTriplets(triplets_Ab.begin(), triplets_Ab.end());
959
960 // set matrices / vectors
961 Eigen::SparseMatrix<zono_float> Z_Ac = Z.Ac;
962 Z_Ac.conservativeResize(Z.nC, Z.nGc + n_slack);
963 Eigen::SparseMatrix<zono_float> Ac = vcat(Z_Ac, Ac_cons);
964 Eigen::SparseMatrix<zono_float> Ab = vcat(Z.Ab, Ab_cons);
965 Eigen::Vector<zono_float, -1> b (Z.nC + static_cast<Eigen::Index>(ineqs.size()));
966 b.segment(0, Z.nC) = Z.b;
967 b.segment(Z.nC, static_cast<Eigen::Index>(ineqs.size())) = b_new;
968
969 Eigen::SparseMatrix<zono_float> Z_Gc = Z.Gc;
970 Z_Gc.conservativeResize(Z.n, Z.nGc + n_slack);
971
972 // output correct type
973 if (Z.is_hybzono())
974 return std::make_unique<HybZono>(Z_Gc, Z.Gb, Z.c, Ac, Ab, b, true);
975 else
976 return std::make_unique<ConZono>(Z_Gc, Z.c, Ac, b, true);
977}
978
979inline std::unique_ptr<HybZono> HybZono::do_complement(const zono_float delta_m, const bool remove_redundancy, const OptSettings &settings,
980 OptSolution* solution, const int n_leaves, const int contractor_iter)
981{
982 // make sure set in [-1,1] form
983 if (this->is_0_1_form()) this->convert_form();
984
985 // need to get leaves and do complement for each leaf if Z is a hybzono
986 auto leaves = this->get_leaves(remove_redundancy, settings, solution, n_leaves, contractor_iter);
987 if (leaves.empty())
988 {
989 throw std::runtime_error("HybZono complement: set is empty.");
990 }
991 std::vector<std::unique_ptr<HybZono>> complements; // init
992 for (auto& leaf : leaves)
993 {
994 complements.emplace_back(leaf.complement(delta_m));
995 }
996 std::unique_ptr<HybZono> Z_out;
997 for (auto& comp : complements)
998 {
999 if (!Z_out)
1000 {
1001 Z_out.reset(comp->clone());
1002 }
1003 else
1004 {
1005 Z_out = intersection(*Z_out, *comp);
1006 }
1007 }
1008 return Z_out;
1009}
1010
1011inline std::unique_ptr<HybZono> ConZono::do_complement(const zono_float delta_m, const bool remove_redundancy, const OptSettings &settings,
1012 OptSolution* solution, const int n_leaves, const int contractor_iter)
1013{
1014 // make sure in [-1,1] form
1015 if (this->is_0_1_form()) this->convert_form();
1016
1017 // get a value lambda_m such that lambda_m >= max{ ||lambda||_infty : |[G^T A^T] lambda| <= 1 }
1018
1019 // construct the matrix [G^T A^T]
1020 const auto GT = this->G.transpose();
1021 const auto AT = this->A.transpose();
1022 const Eigen::SparseMatrix<zono_float, Eigen::RowMajor> GTAT = hcat<zono_float>(GT, AT); // convert to row-major
1023
1024 // get the smallest non-zero element in the matrix, making sure that all rows have at least one non-zero element
1025 zono_float min_non_zero = std::numeric_limits<zono_float>::max();
1026 for (int row=0; row<GTAT.outerSize(); ++row)
1027 {
1028 bool value_exists = false;
1029 for (Eigen::SparseMatrix<zono_float, Eigen::RowMajor>::InnerIterator it(GTAT, row); it; ++it)
1030 {
1031 if (it.value() < min_non_zero && std::abs(it.value()) > zono_eps)
1032 {
1033 min_non_zero = it.value();
1034 }
1035 value_exists = true;
1036 }
1037 if (!value_exists)
1038 {
1039 std::stringstream ss;
1040 ss << "ConZono complement: row " << row << " of [G^T A^T] has no non-zero elements.";
1041 throw std::runtime_error(ss.str());
1042 }
1043 }
1044
1045 // value for lambda_m
1046 const zono_float lambda_m = 1 / min_non_zero;
1047
1048 // m value
1049 const zono_float m = delta_m + 1;
1050
1051 // interval sets
1052 Eigen::Vector<zono_float, -1> l1 (2*this->nG);
1053 l1.setConstant(-(m + delta_m/2));
1054 Eigen::Vector<zono_float, -1> u1 (2*this->nG);
1055 u1.setConstant(1 + delta_m/2);
1056 auto Zf1 = interval_2_zono(Box(l1, u1));
1057 if (Zf1->is_0_1_form()) Zf1->convert_form();
1058
1059 Eigen::Vector<zono_float, -1> l2 (4*this->nG);
1060 l2.segment(0, 2*this->nG).setConstant(-(m + 3*delta_m/2 + 1));
1061 l2.segment(2*this->nG, 2*this->nG).setConstant(-2);
1062 Eigen::Vector<zono_float, -1> u2 (4*this->nG);
1063 u2.segment(0, 2*this->nG).setConstant(delta_m/2);
1064 u2.segment(2*this->nG, 2*this->nG).setZero();
1065 auto Zf2 = interval_2_zono(Box(l2, u2));
1066 if (Zf2->is_0_1_form()) Zf2->convert_form();
1067
1068 // build complement
1069
1070 // Gc
1071 Eigen::SparseMatrix<zono_float> Gc = m*this->G;
1072 Gc.conservativeResize(this->n, 9*this->nG + this->n + this->nC + 1);
1073
1074 // Gb
1075 Eigen::SparseMatrix<zono_float> Gb (this->n, 2*this->nG);
1076
1077 // c
1078 Eigen::Vector<zono_float, -1> c = this->c;
1079
1080 // helper matrices
1081
1082 // declarations
1083 std::vector<Eigen::Triplet<zono_float>> triplets;
1084 int n_offset = 0;
1085
1086 // AcPF = [mI, -delta_m/2, 0, 0, 0;
1087 // -mI, -delta_m/2, 0, 0, 0]
1088 triplets.clear();
1089 for (int i=0; i<this->nG; ++i)
1090 {
1091 triplets.emplace_back(i, i, m);
1092 }
1093 for (int i=0; i<this->nG; ++i)
1094 {
1095 triplets.emplace_back(i, this->nG, -delta_m/2);
1096 }
1097 for (int i=0; i<this->nG; ++i)
1098 {
1099 triplets.emplace_back(this->nG+i, i, -m);
1100 }
1101 for (int i=0; i<this->nG; ++i)
1102 {
1103 triplets.emplace_back(this->nG+i, this->nG, -delta_m/2);
1104 }
1105 Eigen::SparseMatrix<zono_float> AcPF (2*this->nG, 3*this->nG + 1 + this->nC + this->n);
1106 AcPF.setFromTriplets(triplets.begin(), triplets.end());
1107
1108 // AcDF = [0, 0, lambda_m [G^T A^T], 0.5 I, -0.5 I;
1109 // 0, 0, 0, 0.5 1^T, 0.5 1^T]
1110 triplets.clear();
1112 n_offset = this->nG+1+this->n+this->nC;
1113 for (int i=0; i<this->nG; ++i)
1114 {
1115 triplets.emplace_back(i, n_offset+i, 0.5);
1116 }
1117 for (int i=0; i<this->nG; ++i)
1118 {
1119 triplets.emplace_back(this->nG, n_offset+i, 0.5);
1120 }
1121 n_offset += this->nG;
1122 for (int i=0; i<this->nG; ++i)
1123 {
1124 triplets.emplace_back(i, n_offset+i, -0.5);
1125 }
1126 for (int i=0; i<this->nG; ++i)
1127 {
1128 triplets.emplace_back(this->nG, n_offset+i, 0.5);
1129 }
1130 Eigen::SparseMatrix<zono_float> AcDF (this->nG+1, 3*this->nG + 1 + this->nC + this->n);
1131 AcDF.setFromTriplets(triplets.begin(), triplets.end());
1132
1133 // AcCS = [-mI, delta_m/2, 0, 0, 0;
1134 // mI, delta_m/2, 0, 0, 0;
1135 // 0, 0, 0, I, 0;
1136 // 0, 0, 0, 0, I]
1137 triplets.clear();
1138 for (int i=0; i<this->nG; ++i)
1139 {
1140 triplets.emplace_back(i, i, -m);
1141 }
1142 for (int i=0; i<this->nG; ++i)
1143 {
1144 triplets.emplace_back(i, this->nG, delta_m/2);
1145 }
1146 for (int i=0; i<this->nG; ++i)
1147 {
1148 triplets.emplace_back(this->nG+i, i, m);
1149 }
1150 for (int i=0; i<this->nG; ++i)
1151 {
1152 triplets.emplace_back(this->nG+i, this->nG, delta_m/2);
1153 }
1154 n_offset = this->nG+1+this->n+this->nC;
1155 for (int i=0; i<this->nG; ++i)
1156 {
1157 triplets.emplace_back(2*this->nG+i, n_offset+i, 1);
1158 }
1159 n_offset += this->nG;
1160 for (int i=0; i<this->nG; ++i)
1161 {
1162 triplets.emplace_back(3*this->nG+i, n_offset+i, 1);
1163 }
1164 Eigen::SparseMatrix<zono_float> AcCS (4*this->nG, 3*this->nG + 1 + this->nC + this->n);
1165 AcCS.setFromTriplets(triplets.begin(), triplets.end());
1166
1167 // AbCS = [mI, 0;
1168 // 0, mI;
1169 // -I, 0;
1170 // 0, -I]
1171 triplets.clear();
1172 for (int i=0; i<this->nG; ++i)
1173 {
1174 triplets.emplace_back(i, i, m);
1175 }
1176 for (int i=0; i<this->nG; ++i)
1177 {
1178 triplets.emplace_back(this->nG+i, this->nG+i, m);
1179 }
1180 for (int i=0; i<this->nG; ++i)
1181 {
1182 triplets.emplace_back(2*this->nG+i, i, -1);
1183 }
1184 for (int i=0; i<this->nG; ++i)
1185 {
1186 triplets.emplace_back(3*this->nG+i, this->nG+i, -1);
1187 }
1188 Eigen::SparseMatrix<zono_float> AbCS (4*this->nG, 2*this->nG);
1189 AbCS.setFromTriplets(triplets.begin(), triplets.end());
1190
1191 // bDF = [0;
1192 // 1-nG]
1193 Eigen::Vector<zono_float, -1> bDF (this->nG+1);
1194 bDF.setZero();
1195 bDF(this->nG) = static_cast<zono_float>(1 - this->nG);
1196
1197 // Ac = [mA, 0, 0, 0;
1198 // AcPF, Gf1, 0;
1199 // AcDF, 0, 0;
1200 // AcCS, 0, Gf2]
1201 triplets.clear();
1202 int m_offset = 0;
1203 n_offset = this->nG + 1 + this->n + this->nC + 2*this->nG;
1204 get_triplets_offset<zono_float>(m*this->A, triplets, 0, 0);
1205 m_offset += this->nC;
1208 m_offset += 2*this->nG;
1210 m_offset += this->nG + 1;
1213 Eigen::SparseMatrix<zono_float> Ac (7*this->nG + this->nC + 1, 9*this->nG + this->n + this->nC + 1);
1214 Ac.setFromTriplets(triplets.begin(), triplets.end());
1215
1216 // Ab = [0;
1217 // 0;
1218 // 0;
1219 // AbCS];
1220 triplets.clear();
1221 get_triplets_offset<zono_float>(AbCS, triplets, this->nC + 2*this->nG + this->nG+1, 0);
1222 Eigen::SparseMatrix<zono_float> Ab (7*this->nG + this->nC + 1, 2*this->nG);
1223 Ab.setFromTriplets(triplets.begin(), triplets.end());
1224
1225 // b = [b;
1226 // cf1;
1227 // bDF;
1228 // cf2]
1229 Eigen::Vector<zono_float, -1> b (7*this->nG + this->nC + 1);
1230 b.segment(0, this->nC) = this->b;
1231 b.segment(this->nC, 2*this->nG) = Zf1->c;
1232 b.segment(this->nC + 2*this->nG, this->nG + 1) = bDF;
1233 b.segment(this->nC + 2*this->nG + this->nG + 1, 4*this->nG) = Zf2->c;
1234
1235 // return hybrid zonotope
1236 return std::make_unique<HybZono>(Gc, Gb, c, Ac, Ab, b, false, false);
1237}
1238
1239inline std::unique_ptr<HybZono> set_diff(const HybZono& Z1, HybZono& Z2, const zono_float delta_m, const bool remove_redundancy,
1240 const OptSettings &settings, OptSolution* solution, const int n_leaves, const int contractor_iter)
1241{
1242 // trivial case
1243 if (Z2.is_empty_set()) {
1244 return std::unique_ptr<HybZono>(Z1.clone());
1245 }
1246
1247 // get complement of Z2
1248 auto Z2_comp = Z2.complement(delta_m, remove_redundancy, settings, solution, n_leaves, contractor_iter);
1249
1250 // set difference
1251 return intersection(Z1, *Z2_comp);
1252}
1253
1254// setup functions
1255inline std::unique_ptr<HybZono> zono_union_2_hybzono(std::vector<Zono> &Zs, const bool expose_indicators)
1256{
1257 // can't be empty
1258 if (Zs.empty())
1259 {
1260 throw std::invalid_argument("Zono union: empty input vector.");
1261 }
1262
1263 // zonotope dimension
1264 int n_dims = Zs[0].n;
1265 const int n_zonos = Zs.size();
1266
1267 // loop through Zs
1268 for (auto & Z : Zs)
1269 {
1270 // make sure dimensions are consistent
1271 if (Z.n != n_dims)
1272 {
1273 throw std::invalid_argument("Zono union: inconsistent dimensions.");
1274 }
1275
1276 // convert to [0,1] form
1277 if (!Z.zero_one_form)
1278 {
1279 Z.convert_form();
1280 }
1281 }
1282
1283 // get unique generators and incidence matrix
1284
1285 // initialize S and M matrices as std::vectors
1286 // each entry is a row
1287 int n_gens;
1288 std::vector<Eigen::Matrix<zono_float, 1, -1>> M_vec;
1289 std::vector<Eigen::Matrix<zono_float, -1, 1>> S_vec;
1290 Eigen::Matrix<zono_float, 1, -1> M_row (n_zonos);
1291 Eigen::Matrix<zono_float, -1, -1> Gd;
1292
1293 // loop through each polytope
1294 for (int i=0; i<n_zonos; i++)
1295 {
1296 n_gens = Zs[i].nG;
1297 Gd = Zs[i].G.toDense();
1298 for (int j=0; j<n_gens; j++)
1299 {
1300 // check if the generator is already in S_vec
1301 auto generator_equal = [&](const Eigen::Matrix<zono_float, -1, 1> &s) -> bool
1302 {
1303 return (s - Gd.col(j)).norm() < zono_eps;
1304 };
1305
1306 if (auto it_S = std::find_if(S_vec.begin(), S_vec.end(), generator_equal); it_S == S_vec.end())
1307 {
1308 S_vec.emplace_back(Gd.col(j));
1309 M_row.setZero();
1310 M_row(i) = 1;
1311 M_vec.push_back(M_row);
1312 }
1313 else
1314 {
1315 int idx = std::distance(S_vec.begin(), it_S);
1316 M_vec[idx](i) = 1;
1317 }
1318 }
1319 }
1320
1321 int nG = S_vec.size(); // number of unique generators
1322
1323 // convert to Eigen matrices
1324 Eigen::Matrix<zono_float, -1, -1> S (n_dims, nG);
1325 Eigen::Matrix<zono_float, -1, -1> M (nG, n_zonos);
1326 for (int i=0; i<nG; i++)
1327 {
1328 S.col(i) = S_vec[i];
1329 M.row(i) = M_vec[i];
1330 }
1331
1332 // directly build hybzono in [0,1] form
1333
1334 // declare
1335 std::vector<Eigen::Triplet<zono_float>> tripvec;
1336
1337 // output dimension
1338 int n_out = n_dims;
1339 if (expose_indicators)
1340 n_out += static_cast<int>(n_zonos);
1341
1342 // Gc = [S, 0]
1343 Eigen::SparseMatrix<zono_float> Gc = S.sparseView();
1344 Gc.conservativeResize(n_out, 2*nG);
1345
1346 // Gb = [c0, c1, ...]
1347 tripvec.clear();
1348 for (int i=0; i<n_zonos; ++i)
1349 {
1350 for (int j=0; j<n_dims; ++j)
1351 {
1352 tripvec.emplace_back(j, i, Zs[i].c(j));
1353 }
1354 }
1355 for (int i=n_dims; i<n_out; ++i)
1356 {
1357 tripvec.emplace_back(i, i-n_dims, 1.0);
1358 }
1359 Eigen::SparseMatrix<zono_float> Gb (n_out, n_zonos);
1360 Gb.setFromTriplets(tripvec.begin(), tripvec.end());
1361
1362 // c = 0
1363 Eigen::Vector<zono_float, -1> c (n_out);
1364 c.setZero();
1365
1366 // Ac = [0^T, 0^T;
1367 // I, diag[sum(M, 2)]]
1368 tripvec.clear();
1369 Eigen::SparseMatrix<zono_float> Ac (1+nG, 2*nG);
1370 Eigen::SparseMatrix<zono_float> I_ng (nG, nG);
1371 I_ng.setIdentity();
1372 get_triplets_offset<zono_float>(I_ng, tripvec, 1, 0);
1373 Eigen::Vector<zono_float, -1> sum_M = M.rowwise().sum();
1374 for (int i=0; i<nG; i++)
1375 {
1376 tripvec.emplace_back(1+i, nG+i, sum_M(i));
1377 }
1378 Ac.setFromTriplets(tripvec.begin(), tripvec.end());
1379
1380 // Ab = [1^T;
1381 // -M]
1382 Eigen::SparseMatrix<zono_float> Ab (1+nG, n_zonos);
1383 tripvec.clear();
1384 for (int i=0; i<n_zonos; i++)
1385 {
1386 tripvec.emplace_back(0, i, 1);
1387 }
1388 Eigen::SparseMatrix<zono_float> mM_sp = -M.sparseView();
1389 get_triplets_offset<zono_float>(mM_sp, tripvec, 1, 0);
1390 Ab.setFromTriplets(tripvec.begin(), tripvec.end());
1391
1392 // b = [1;
1393 // 0]
1394 Eigen::Vector<zono_float, -1> b (1+nG);
1395 b.setZero();
1396 b(0) = 1;
1397
1398 // return hybrid zonotope
1399 return std::make_unique<HybZono>(Gc, Gb, c, Ac, Ab, b, true, true);
1400}
1401
1402inline std::unique_ptr<HybZono> vrep_2_hybzono(const std::vector<Eigen::Matrix<zono_float, -1, -1>> &Vpolys, const bool expose_indicators)
1403{
1404 // error handling
1405 if (Vpolys.empty())
1406 {
1407 throw std::invalid_argument("set_from_vrep: Vpolys must have at least one polytope.");
1408 }
1409
1410 // dimensions
1411 const int n_polys = Vpolys.size();
1412 const int n_dims = Vpolys[0].cols();
1413 int n_verts; // declare
1414
1415 // check if all polytopes have the same number of dimensions
1416 for (const auto & Vpoly : Vpolys)
1417 {
1418 if (Vpoly.cols() != n_dims)
1419 {
1420 throw std::invalid_argument("set_from_vrep: all polytopes must have the same number of dimensions.");
1421 }
1422 }
1423
1424 // initialize V and M matrices as std::vectors
1425 // each entry is a row
1426 std::vector<Eigen::Matrix<zono_float, 1, -1>> V_vec, M_vec;
1427 Eigen::Matrix<zono_float, 1, -1> M_row (n_polys);
1428
1429 // loop through each polytope
1430 for (int i=0; i<n_polys; i++)
1431 {
1432 n_verts = Vpolys[i].rows();
1433 for (int j=0; j<n_verts; j++)
1434 {
1435 // check if the vertex is already in V_vec
1436 auto vertex_equal = [&](const Eigen::Matrix<zono_float, 1, -1> &v) -> bool
1437 {
1438 return (v - Vpolys[i].row(j)).norm() < zono_eps;
1439 };
1440
1441 if (auto it_V = std::find_if(V_vec.begin(), V_vec.end(), vertex_equal); it_V == V_vec.end())
1442 {
1443 V_vec.emplace_back(Vpolys[i].row(j));
1444 M_row.setZero();
1445 M_row(i) = 1;
1446 M_vec.push_back(M_row);
1447 }
1448 else
1449 {
1450 int idx = std::distance(V_vec.begin(), it_V);
1451 M_vec[idx](i) = 1;
1452 }
1453 }
1454 }
1455
1456 int nV = V_vec.size(); // number of unique vertices
1457
1458 // convert to Eigen matrices
1459 Eigen::Matrix<zono_float, -1, -1> V (n_dims, nV);
1460 Eigen::Matrix<zono_float, -1, -1> M (nV, n_polys);
1461 for (int i=0; i<V_vec.size(); i++)
1462 {
1463 V.col(i) = V_vec[i];
1464 M.row(i) = M_vec[i];
1465 }
1466
1467 // directly build hybzono in [0,1] form
1468
1469 // declare
1470 std::vector<Eigen::Triplet<zono_float>> tripvec;
1471
1472 // output dimension
1473 int n_out = n_dims;
1474 if (expose_indicators)
1475 n_out += static_cast<int>(n_polys);
1476
1477 // Gc = [V, 0]
1478 Eigen::SparseMatrix<zono_float> Gc = V.sparseView();
1479 Gc.conservativeResize(n_out, 2*nV);
1480
1481 // Gb = [0]
1482 tripvec.clear();
1483 for (int i=n_dims; i<n_out; ++i)
1484 {
1485 tripvec.emplace_back(i, i-n_dims, 1.0);
1486 }
1487 Eigen::SparseMatrix<zono_float> Gb (n_out, n_polys);
1488 Gb.setFromSortedTriplets(tripvec.begin(), tripvec.end());
1489
1490 // c = 0
1491 Eigen::Vector<zono_float, -1> c (n_out);
1492 c.setZero();
1493
1494 // Ac = [1^T, 0^T;
1495 // 0^T, 0^T;
1496 // I, diag[sum(M, 2)]]
1497 tripvec.clear();
1498 Eigen::SparseMatrix<zono_float> Ac (2+nV, 2*nV);
1499 Eigen::SparseMatrix<zono_float> I_nv (nV, nV);
1500 I_nv.setIdentity();
1501 for (int i=0; i<nV; i++)
1502 {
1503 tripvec.emplace_back(0, i, 1);
1504 }
1505 get_triplets_offset<zono_float>(I_nv, tripvec, 2, 0);
1506 Eigen::Vector<zono_float, -1> sum_M = M.rowwise().sum();
1507 for (int i=0; i<nV; i++)
1508 {
1509 tripvec.emplace_back(2+i, nV+i, sum_M(i));
1510 }
1511 Ac.setFromTriplets(tripvec.begin(), tripvec.end());
1512
1513 // Ab = [0^T;
1514 // 1^T;
1515 // -M]
1516 Eigen::SparseMatrix<zono_float> Ab (2+nV, n_polys);
1517 tripvec.clear();
1518 for (int i=0; i<n_polys; i++)
1519 {
1520 tripvec.emplace_back(1, i, 1);
1521 }
1522 Eigen::SparseMatrix<zono_float> mM_sp = -M.sparseView();
1523 get_triplets_offset<zono_float>(mM_sp, tripvec, 2, 0);
1524 Ab.setFromTriplets(tripvec.begin(), tripvec.end());
1525
1526 // b = [1;
1527 // 1;
1528 // 0]
1529 Eigen::Vector<zono_float, -1> b (2+nV);
1530 b.setZero();
1531 b(0) = 1;
1532 b(1) = 1;
1533
1534 // return hybrid zonotope
1535 return std::make_unique<HybZono>(Gc, Gb, c, Ac, Ab, b, true, true);
1536}
1537
1538inline std::unique_ptr<ConZono> vrep_2_conzono(const Eigen::Matrix<zono_float, -1, -1> &Vpoly)
1539{
1540 // dimensions
1541 const int n_dims = Vpoly.cols();
1542 const int n_verts = Vpoly.rows();
1543
1544 // make generators
1545 const Eigen::SparseMatrix<zono_float> G = Vpoly.transpose().sparseView();
1546 Eigen::Vector<zono_float, -1> c (n_dims);
1547 c.setZero();
1548
1549 // make constraints
1550 std::vector<Eigen::Triplet<zono_float>> tripvec;
1551 Eigen::SparseMatrix<zono_float> A (1, n_verts);
1552 for (int i=0; i<n_verts; i++)
1553 {
1554 tripvec.emplace_back(0, i, 1);
1555 }
1556 A.setFromTriplets(tripvec.begin(), tripvec.end());
1557
1558 Eigen::Vector<zono_float, -1> b (1);
1559 b(0) = 1;
1560
1561 // return conzono
1562 return std::make_unique<ConZono>(G, c, A, b, true);
1563}
1564
1565
1566inline std::unique_ptr<Zono> interval_2_zono(const Box& box)
1567{
1568 // generator matrix
1569 std::vector<Eigen::Triplet<zono_float>> triplets;
1570 Eigen::SparseMatrix<zono_float> G (static_cast<Eigen::Index>(box.size()), static_cast<Eigen::Index>(box.size()));
1571 for (int i=0; i<box.size(); i++)
1572 {
1573 triplets.emplace_back(i, i, box[i].width()/2);
1574 }
1575 G.setFromSortedTriplets(triplets.begin(), triplets.end());
1576
1577 // center
1578 Eigen::Vector<zono_float, -1> c = box.center();
1579
1580 // return zonotope
1581 return std::make_unique<Zono>(G, c, false);
1582}
1583
1584
1585inline std::unique_ptr<Zono> make_regular_zono_2D(const zono_float radius, const int n_sides, const bool outer_approx, const Eigen::Vector<zono_float, 2>& c)
1586{
1587 // check number of sides
1588 if (n_sides % 2 != 0 || n_sides < 4)
1589 {
1590 throw std::invalid_argument("make_regular_zono_2D: number of sides must be even and >= 4.");
1591 }
1592
1593 // check radius
1594 if (radius <= 0)
1595 {
1596 throw std::invalid_argument("make_regular_zono_2D: radius must be positive.");
1597 }
1598
1599 // problem parameters
1600 const int n_gens = n_sides/2;
1601 constexpr zono_float pi = 3.14159265358979323846;
1602 const zono_float dphi = pi/n_gens;
1603 const zono_float R = outer_approx ? radius/std::cos(dphi/2) : radius;
1604
1605 // generator matrix
1606 zono_float phi = (static_cast<zono_float>(n_gens/2))*dphi;
1607 const zono_float l_side = 2*R*std::sin(dphi/2);
1608 Eigen::Matrix<zono_float, -1, -1> G(2, n_gens);
1609 for (int i = 0; i < n_gens; i++)
1610 {
1611 G(0, i) = l_side*std::cos(phi);
1612 G(1, i) = l_side*std::sin(phi);
1613 phi -= dphi;
1614 }
1615
1616 // return zonotope
1617 return std::make_unique<Zono>(0.5*G.sparseView(), c, false);
1618}
1619
1620// convex relaxation
1621inline std::unique_ptr<ConZono> HybZono::convex_relaxation() const { return std::make_unique<ConZono>(this->G, this->c, this->A, this->b, this->zero_one_form); }
1622
1623// bounding box
1624inline Box HybZono::do_bounding_box(const OptSettings &settings, OptSolution* solution)
1625{
1626 // if sharp, compute from convex relaxation
1627 if (this->sharp)
1628 {
1629 const auto Z_CR = this->convex_relaxation();
1630 return Z_CR->bounding_box(settings, solution);
1631 }
1632
1633 // make sure dimension is at least 1
1634 if (this->n == 0)
1635 {
1636 throw std::invalid_argument("Bounding box: empty set");
1637 }
1638
1639 // init search direction for bounding box
1640 Eigen::Vector<zono_float, -1> d (this->n);
1641 d.setZero();
1642
1643 // declarations
1644 Box box (this->n); // init
1645 zono_float s_neg, s_pos;
1646
1647 // build QP for ADMM
1648 const Eigen::SparseMatrix<zono_float> P (this->nG, this->nG);
1649 Eigen::Vector<zono_float, -1> q = -this->G.transpose()*d;
1650
1651 // mixed-integer solution
1652
1653 // get support in all box directions
1654 for (int i=0; i<this->n; i++)
1655 {
1656 // negative direction
1657
1658 // update QP
1659 d.setZero();
1660 d(i) = -1;
1661 q = -this->G.transpose()*d;
1662
1663 // solve
1664 OptSolution sol = this->mi_opt(P, q, 0, this->A, this->b, settings);
1665 if (sol.infeasible)
1666 throw std::invalid_argument("Bounding box: Z is empty");
1667 else
1668 s_neg = -d.dot(this->G*sol.z + this->c);
1669
1670 // positive direction
1671
1672 // update QP
1673 d.setZero();
1674 d(i) = 1;
1675 q = -this->G.transpose()*d;
1676
1677 // solve
1678 sol = this->mi_opt(P, q, 0, this->A, this->b, settings);
1679 if (sol.infeasible)
1680 throw std::invalid_argument("Bounding box: Z is empty");
1681 else
1682 s_pos = d.dot(this->G*sol.z + this->c);
1683
1684 // store bounds
1685 box[i] = Interval(s_neg, s_pos);
1686 }
1687
1688 return box;
1689}
1690
1691inline zono_float HybZono::do_support(const Eigen::Vector<zono_float, -1>& d, const OptSettings &settings,
1692 OptSolution* solution)
1693{
1694 // check dimensions
1695 if (this->n != d.size())
1696 {
1697 throw std::invalid_argument("Support: inconsistent dimensions.");
1698 }
1699
1700 // if sharp, can solve as convex optimization problem
1701 if (this->sharp)
1702 {
1703 const auto Zc = this->convex_relaxation();
1704 return Zc->support(d, settings, solution);
1705 }
1706
1707 // cost
1708 Eigen::SparseMatrix<zono_float> P (this->nG, this->nG);
1709 Eigen::Vector<zono_float, -1> q = -this->G.transpose()*d;
1710
1711 // solve MIQP
1712 const OptSolution sol = this->mi_opt(P, q, 0, this->A, this->b, settings, solution);
1713
1714 // check feasibility and return solution
1715 if (sol.infeasible) // Z is empty
1716 throw std::invalid_argument("Support: infeasible");
1717 else
1718 return d.dot(this->G*sol.z + this->c);
1719}
1720
1721inline std::vector<ConZono> HybZono::get_leaves(const bool remove_redundancy, const OptSettings &settings,
1722 OptSolution* solution, const int n_leaves, const int contractor_iter) const
1723{
1724 // allocate all threads to branch and bound
1726
1727 // get leaves as conzonos
1728 const std::vector<Eigen::Vector<zono_float, -1>> bin_leaves = this->get_bin_leaves(remove_redundancy, settings_get_leaves, solution, n_leaves);
1729 std::vector<ConZono> leaves;
1730 for (auto &xi_b : bin_leaves)
1731 {
1732 Eigen::Vector<zono_float, -1> cp = this->c + this->Gb*xi_b;
1733 Eigen::Vector<zono_float, -1> bp = this->b - this->Ab*xi_b;
1734 leaves.emplace_back(this->Gc, cp, this->Ac, bp, this->zero_one_form);
1735 }
1736 if (remove_redundancy)
1737 {
1738 for (auto &leaf : leaves)
1739 {
1740 leaf.remove_redundancy(contractor_iter);
1741 }
1742 }
1743
1744 return leaves;
1745}
1746
1747
1749{
1750 // make sure there are constraints to remove
1751 if (this->nC == 0) return;
1752
1753 // put set into [-1, 1] form
1754 if (this->zero_one_form) this->convert_form();
1755
1756 // execute algorithm 1 from paper
1757 Eigen::Vector<zono_float, -1> x_lb (this->nG);
1758 Eigen::Vector<zono_float, -1> x_ub (this->nG);
1759 x_lb.setConstant(-1);
1760 x_ub.setConstant(1);
1761 Box E (x_lb, x_ub);
1762 x_lb.setConstant(-std::numeric_limits<zono_float>::infinity());
1763 x_ub.setConstant(std::numeric_limits<zono_float>::infinity());
1764 Box R (x_lb, x_ub);
1765 Eigen::SparseMatrix<zono_float, Eigen::RowMajor> A_rm = this->A;
1766 for (int i=0; i<this->nC; ++i)
1767 {
1768 for (Eigen::SparseMatrix<zono_float, Eigen::RowMajor>::InnerIterator it_j(A_rm, i); it_j; ++it_j)
1769 {
1770 const zono_float a_ij = it_j.value();
1771 Interval y (this->b(i) / a_ij, this->b(i) / a_ij);
1772 for (Eigen::SparseMatrix<zono_float, Eigen::RowMajor>::InnerIterator it_k(A_rm, i); it_k; ++it_k)
1773 {
1774 if (it_j.col() == it_k.col()) continue;
1775 y = y - E[it_k.col()].to_interval()*(it_k.value()/a_ij);
1776 }
1777 R[it_j.col()].intersect_assign(R[it_j.col()], y.as_view());
1778 E[it_j.col()].intersect_assign(E[it_j.col()], R[it_j.col()]);
1779 }
1780 }
1781
1782 // make sure conzono isn't empty (interval check)
1783 for (int j=0; j<this->nG; ++j)
1784 {
1785 if (E[j].is_empty())
1786 throw std::runtime_error("ConZono constraint reduction: set is empty");
1787 }
1788
1789 // build Q matrix
1790 Eigen::SparseMatrix<zono_float> Q (this->nG + this->nC, this->nG + this->nC);
1791
1792 Eigen::SparseMatrix<zono_float> I_nG (this->nG, this->nG);
1793 I_nG.setIdentity();
1794 const Eigen::SparseMatrix<zono_float> Phi = this->G.transpose()*this->G + I_nG;
1795
1796 std::vector<Eigen::Triplet<zono_float>> triplets;
1798 get_triplets_offset<zono_float>(this->A, triplets, this->nG, 0);
1799 get_triplets_offset<zono_float>(this->A.transpose(), triplets, 0, this->nG);
1800 Q.setFromTriplets(triplets.begin(), triplets.end());
1801
1802 // factorize Q
1803 Eigen::SimplicialLDLT<Eigen::SparseMatrix<zono_float>> Q_ldlt (Q);
1804 if (Q_ldlt.info() != Eigen::Success)
1805 throw std::runtime_error("ConZono constraint reduction: Q matrix factorization failed, most likely A is not full row rank.");
1806
1807 // get estimated Hausdorff error for eliminating each generator
1808 std::vector<std::pair<int, zono_float>> haus_vec; // (index, error)
1809 haus_vec.reserve(this->nG);
1810 auto shift_permute = [size=this->nG + this->nC + 1](const int start_index, const int end_index) -> Eigen::PermutationMatrix<Eigen::Dynamic, Eigen::Dynamic>
1811 {
1812 assert(start_index >= 0 && end_index >= 0 && start_index < size && end_index < size && start_index < end_index);
1813 Eigen::PermutationMatrix<Eigen::Dynamic, Eigen::Dynamic> P(size);
1814 for (int i=0; i<start_index; ++i)
1815 {
1816 P.indices()[i] = i;
1817 }
1818 P.indices()[start_index] = end_index;
1819 for (int i=start_index+1; i<=end_index; ++i)
1820 {
1821 P.indices()[i] = i-1;
1822 }
1823 for (int i=end_index+1; i<size; ++i)
1824 {
1825 P.indices()[i] = i;
1826 }
1827 return P;
1828 };
1829 Eigen::Vector<zono_float, -1> e_j (this->nG + this->nC);
1830 for (int j=0; j<this->nG; ++j)
1831 {
1832 // get r_j
1833 const zono_float r_j = std::max<zono_float>(0.0, std::max<zono_float>(std::abs(R[j].to_interval().lb), std::abs(R[j].to_interval().ub)) - 1.0);
1834 if (r_j < zono_eps)
1835 {
1836 haus_vec.emplace_back(j, 0);
1837 continue;
1838 }
1839
1840 // linear system matrix
1841 e_j.setZero();
1842 e_j(j) = 1;
1843 const Eigen::Vector<zono_float, -1> Qinv_e_j = Q_ldlt.solve(e_j);
1844
1845 triplets.clear();
1846 for (int i=0; i<this->nG + this->nC; ++i)
1847 {
1848 triplets.emplace_back(i, i, 1);
1849 if (i == j)
1850 {
1851 triplets.emplace_back(this->nG + this->nC, j, 1); // extra row for e_j^T
1852 }
1853 }
1854 for (int i=0; i<this->nG + this->nC; ++i)
1855 {
1856 triplets.emplace_back(i, this->nG + this->nC, Qinv_e_j(i));
1857 }
1858 Eigen::SparseMatrix<zono_float> M (this->nG + this->nC + 1, this->nG + this->nC + 1);
1859 M.setFromSortedTriplets(triplets.begin(), triplets.end());
1860
1861 // RHS for linear system
1862 Eigen::Vector<zono_float, -1> rhs (this->nG + this->nC + 1);
1863 rhs.setZero();
1864 rhs(this->nG + this->nC) = r_j;
1865
1866 // permutation matrices to make system upper triangular
1867 const auto P_R = shift_permute(j, this->nG + this->nC);
1868 const auto P_L = shift_permute(j, this->nG + this->nC - 1);
1869
1870 // solve linear system in permuted space
1871 const Eigen::SparseMatrix<zono_float> M_perm = P_L*M*P_R.inverse();
1872 const Eigen::Vector<zono_float, -1> rhs_perm = P_L*rhs;
1873 const Eigen::Vector<zono_float, -1> y_perm = M_perm.triangularView<Eigen::Upper>().solve(rhs_perm);
1874 const Eigen::Vector<zono_float, -1> y = P_R*y_perm;
1875
1876 // get Hausdorff distance estimate
1877 const Eigen::Vector<zono_float, -1> d = y.segment(0, this->nG);
1878 const zono_float haus_j = (this->G*d).norm() + d.norm();
1879 haus_vec.emplace_back(j, haus_j);
1880 }
1881
1882 // sort by Hausdorff error
1883 std::sort(haus_vec.begin(), haus_vec.end(), [](const auto &a, const auto &b) { return a.second < b.second; });
1884
1885 Eigen::SparseMatrix<zono_float> Ea (this->nG, this->nC); // init
1886 int gen_remove = -1;
1887 int cons_remove = -1;
1888 for (const auto& [j, err] : haus_vec)
1889 {
1890 // loop through column k of A to find a constraint to remove
1891 for (Eigen::SparseMatrix<zono_float>::InnerIterator it(this->A, j); it; ++it)
1892 {
1893 if (std::abs(it.value()) > zono_eps)
1894 {
1895 triplets.emplace_back(j, it.row(), 1/it.value());
1896 Ea.insert(j, it.row()) = 1/it.value();
1897 gen_remove = j;
1898 cons_remove = it.row();
1899 break;
1900 }
1901 }
1902
1903 // check if done
1904 if (gen_remove != -1 && cons_remove != -1)
1905 break;
1906 }
1907 if (gen_remove == -1 || cons_remove == -1)
1908 throw std::runtime_error("ConZono: constraint reduction cannot find valid constraint to remove");
1909
1910 // apply algorithm from Scott paper
1911 const Eigen::SparseMatrix<zono_float> Lambda_G = G*Ea;
1912 const Eigen::SparseMatrix<zono_float> Lambda_A = A*Ea;
1913 Eigen::SparseMatrix<zono_float> Gp = this->G - Lambda_G*this->A;
1914 Eigen::Vector<zono_float, -1> cp = this->c + Lambda_G*this->b;
1915 Eigen::SparseMatrix<zono_float> Ap = this->A - Lambda_A*this->A;
1916 Eigen::Vector<zono_float, -1> bp = this->b - Lambda_A*this->b;
1917
1918 // generator removal matrix
1919 triplets.clear();
1920 for (int j=0; j<gen_remove; ++j)
1921 {
1922 triplets.emplace_back(j, j, 1);
1923 }
1924 for (int j=gen_remove+1; j<this->nG; ++j)
1925 {
1926 triplets.emplace_back(j, j-1, 1);
1927 }
1928 Eigen::SparseMatrix<zono_float> dG (this->nG, this->nG - 1);
1929 dG.setFromSortedTriplets(triplets.begin(), triplets.end());
1930
1931 // constraint removal matrix
1932 triplets.clear();
1933 for (int i=0; i<cons_remove; ++i)
1934 {
1935 triplets.emplace_back(i, i, 1);
1936 }
1937 for (int i=cons_remove+1; i<this->nC; ++i)
1938 {
1939 triplets.emplace_back(i-1, i, 1);
1940 }
1941 Eigen::SparseMatrix<zono_float> dA (this->nC - 1, this->nC);
1942 dA.setFromSortedTriplets(triplets.begin(), triplets.end());
1943
1944 // update
1945 this->set(Gp*dG, cp, dA*Ap*dG, dA*bp, false);
1946}
1947
1948inline std::unique_ptr<Zono> ConZono::to_zono_approx() const
1949{
1950 // check for case that there are no constraints
1951 if (this->nG == 0)
1952 {
1953 return std::make_unique<Point>(this->c);
1954 }
1955 if (this->nC == 0)
1956 {
1957 return std::make_unique<Zono>(this->G, this->c, this->zero_one_form);
1958 }
1959
1960 // compute SVD of A
1961 const Eigen::BDCSVD<Eigen::Matrix<zono_float, -1, -1>, Eigen::ComputeFullV | Eigen::ComputeFullU> svd (this->A.toDense());
1962 const Eigen::Vector<zono_float, -1>& sin_vals = svd.singularValues();
1963
1964 const int n = this->nG;
1965 const int r = static_cast<int>(svd.rank());
1966 const int d = n-r;
1967 const Eigen::Matrix<zono_float, -1, -1> Vr = svd.matrixV().block(0, 0, n, r);
1968 const Eigen::Matrix<zono_float, -1, -1> Vd = svd.matrixV().block(0, r, n, d);
1969
1970 // get xi_tilde_r
1971 const Eigen::Vector<zono_float, -1> b_tilde = svd.matrixU().transpose()*this->b;
1972 const Eigen::Vector<zono_float, -1> xi_tilde_r = b_tilde.segment(0, r).array() / sin_vals.segment(0, r).array();
1973
1974 // build bounding zonotope
1975 const Eigen::SparseMatrix<zono_float> Vd_VdT_sp = (Vd*Vd.transpose()).sparseView();
1976 const Eigen::SparseMatrix<zono_float> G_zono = this->G*Vd_VdT_sp;
1977 const Eigen::Vector<zono_float, -1> c_zono = this->c + this->G*(Vr*xi_tilde_r);
1978 return std::make_unique<Zono>(G_zono, c_zono, this->zero_one_form);
1979}
1980
1981} // namespace ZonoOpt
1982
1983#endif
Constrained zonotope class for ZonoOpt library.
Empty Set class for ZonoOpt library.
Hybrid zonotope class for ZonoOpt library.
Point class for ZonoOpt library.
Utilities for sparse matrix operations in ZonoOpt library.
Zonotope class for ZonoOpt library.
Box (i.e., interval vector) class.
Definition Intervals.hpp:720
Eigen::Vector< zono_float, -1 > center() const
get center of box
Definition Intervals.hpp:893
size_t size() const
get size of Box object
Definition Intervals.hpp:832
Constrained zonotope class.
Definition ConZono.hpp:33
void set(const Eigen::SparseMatrix< zono_float > &G, const Eigen::Vector< zono_float, -1 > &c, const Eigen::SparseMatrix< zono_float > &A, const Eigen::Vector< zono_float, -1 > &b, bool zero_one_form=false)
Reset constrained zonotope object with the given parameters.
Definition ConZono.hpp:150
virtual void constraint_reduction()
Execute constraint reduction algorithm from Scott et. al. 2016.
Definition PolymorphicFunctions.hpp:1748
void convert_form() override
Converts the set representation between -1-1 and 0-1 forms.
Definition ConZono.hpp:179
virtual std::unique_ptr< Zono > to_zono_approx() const
Compute outer approximation of constrained zonotope as zonotope using SVD.
Definition PolymorphicFunctions.hpp:1948
std::unique_ptr< HybZono > do_complement(zono_float delta_m, bool remove_redundancy, const OptSettings &settings, OptSolution *solution, int n_leaves, int contractor_iter) override
Definition PolymorphicFunctions.hpp:1011
Empty Set class.
Definition EmptySet.hpp:25
Hybrid zonotope class.
Definition HybZono.hpp:44
virtual bool is_0_1_form() const
Returns true if factors are in range [0,1], false if they are in range [-1,1].
Definition HybZono.hpp:199
int nC
number of constraints
Definition HybZono.hpp:482
bool is_empty(const OptSettings &settings=OptSettings(), OptSolution *solution=nullptr) const
Returns true if the set is provably empty, false otherwise.
Definition HybZono.hpp:351
int n
set dimension
Definition HybZono.hpp:470
OptSolution mi_opt(const Eigen::SparseMatrix< zono_float > &P, const Eigen::Vector< zono_float, -1 > &q, zono_float c, const Eigen::SparseMatrix< zono_float > &A, const Eigen::Vector< zono_float, -1 > &b, const OptSettings &settings=OptSettings(), OptSolution *solution=nullptr) const
Definition HybZono.hpp:988
bool is_hybzono() const
Polymorphic type checking: true if set is a hybrid zonotope.
Definition PolymorphicFunctions.hpp:49
bool zero_one_form
flag to indicate whether the set is in 0-1 or -1-1 form
Definition HybZono.hpp:485
Eigen::SparseMatrix< zono_float > Gc
continuous generator matrix
Definition HybZono.hpp:449
virtual void convert_form()
Converts the set representation between -1-1 and 0-1 forms.
Definition HybZono.hpp:742
Eigen::SparseMatrix< zono_float > Gb
binary generator matrix
Definition HybZono.hpp:452
int nG
total number of factors. nG = nGc + nGb
Definition HybZono.hpp:473
bool is_conzono() const
Polymorphic type checking: true if set is a constrained zonotope.
Definition PolymorphicFunctions.hpp:44
virtual Box do_bounding_box(const OptSettings &settings, OptSolution *solution)
Definition PolymorphicFunctions.hpp:1624
virtual zono_float do_support(const Eigen::Vector< zono_float, -1 > &d, const OptSettings &settings, OptSolution *solution)
Definition PolymorphicFunctions.hpp:1691
bool is_point() const
Polymorphic type checking: true if set is a point.
Definition PolymorphicFunctions.hpp:34
virtual HybZono * clone() const
Clone method for polymorphic behavior.
Definition HybZono.hpp:96
void set(const Eigen::SparseMatrix< zono_float > &Gc, const Eigen::SparseMatrix< zono_float > &Gb, const Eigen::Vector< zono_float, -1 > &c, const Eigen::SparseMatrix< zono_float > &Ac, const Eigen::SparseMatrix< zono_float > &Ab, const Eigen::Vector< zono_float, -1 > &b, bool zero_one_form=false, bool sharp=false)
Reset hybrid zonotope object with the given parameters.
Definition HybZono.hpp:714
virtual std::unique_ptr< ConZono > convex_relaxation() const
Returns convex relaxation of the hybrid zonotope.
Definition PolymorphicFunctions.hpp:1621
bool sharp
flag to indicate whether the set is known to be sharp (i.e., convex relaxation = convex hull)
Definition HybZono.hpp:488
virtual void remove_redundancy(int contractor_iter=100)
Removes redundant constraints and any unused generators.
Definition HybZono.hpp:771
Eigen::Vector< zono_float, -1 > c
center vector
Definition HybZono.hpp:464
int nGb
number of binary factors
Definition HybZono.hpp:479
bool is_zono() const
Polymorphic type checking: true if set is a zonotope.
Definition PolymorphicFunctions.hpp:39
Eigen::SparseMatrix< zono_float > A
constraint matrix A = [Ac, Ab]
Definition HybZono.hpp:455
Eigen::Vector< zono_float, -1 > b
constraint vector
Definition HybZono.hpp:467
virtual std::unique_ptr< HybZono > do_complement(zono_float, bool remove_redundancy, const OptSettings &settings, OptSolution *solution, int n_leaves, int contractor_iter)
Definition PolymorphicFunctions.hpp:979
friend std::unique_ptr< HybZono > intersection(const HybZono &Z1, HybZono &Z2, const Eigen::SparseMatrix< zono_float > &R)
Computes the generalized intersection of sets Z1 and Z2 over the matrix R.
Definition PolymorphicFunctions.hpp:188
bool is_empty_set() const
Polymorphic type checking: true if set is empty set object.
Definition PolymorphicFunctions.hpp:55
std::vector< ConZono > get_leaves(bool remove_redundancy=true, const OptSettings &settings=OptSettings(), OptSolution *solution=nullptr, int n_leaves=std::numeric_limits< int >::max(), int contractor_iter=100) const
Computes individual constrained zonotopes whose union is the hybrid zonotope object.
Definition PolymorphicFunctions.hpp:1721
Eigen::SparseMatrix< zono_float > Ab
binary constraint matrix
Definition HybZono.hpp:461
Eigen::SparseMatrix< zono_float > Ac
continuous constraint matrix
Definition HybZono.hpp:458
Eigen::SparseMatrix< zono_float > G
generator matrix G = [Gc, Gb]
Definition HybZono.hpp:446
int nGc
number of continuous factors
Definition HybZono.hpp:476
virtual std::unique_ptr< HybZono > complement(const zono_float delta_m=100, const bool remove_redundancy=true, const OptSettings &settings=OptSettings(), OptSolution *solution=nullptr, const int n_leaves=std::numeric_limits< int >::max(), const int contractor_iter=100)
Computes the complement of the set Z.
Definition HybZono.hpp:258
Inequality class.
Definition Inequality.hpp:50
void set_ineq_type(const IneqType type)
Sets the direction of the inequality or sets it to be an equality.
Definition Inequality.hpp:99
void set_rhs(const zono_float rhs)
Set the right hand side of the inequality.
Definition Inequality.hpp:89
void add_term(const int idx, const zono_float coeff)
Adds a term to the inequality.
Definition Inequality.hpp:76
Point class.
Definition Point.hpp:28
Zonotope class.
Definition Zono.hpp:33
#define zono_float
Defines the floating-point type used in ZonoOpt.
Definition ZonoOpt.hpp:43
#define zono_eps
Defines the precision used for floating point comparisons in ZonoOpt.
Definition ZonoOpt.hpp:51
std::unique_ptr< HybZono > cartesian_product(const HybZono &Z1, HybZono &Z2)
Computes the Cartesian product of two sets Z1 and Z2.
Definition PolymorphicFunctions.hpp:798
std::unique_ptr< HybZono > set_diff(const HybZono &Z1, HybZono &Z2, zono_float delta_m=100, bool remove_redundancy=true, const OptSettings &settings=OptSettings(), OptSolution *solution=nullptr, int n_leaves=std::numeric_limits< int >::max(), int contractor_iter=100)
Set difference Z1 \ Z2.
Definition PolymorphicFunctions.hpp:1239
std::unique_ptr< HybZono > union_of_many(const std::vector< HybZono * > &Zs, bool preserve_sharpness=false, bool expose_indicators=false)
Computes union of several sets.
Definition PolymorphicFunctions.hpp:506
std::unique_ptr< HybZono > minkowski_sum(const HybZono &Z1, HybZono &Z2)
Computes Minkowski sum of two sets Z1 and Z2.
Definition PolymorphicFunctions.hpp:133
std::unique_ptr< HybZono > project_onto_dims(const HybZono &Z, const std::vector< int > &dims)
Projects set Z onto the dimensions specified in dims.
Definition PolymorphicFunctions.hpp:104
std::unique_ptr< HybZono > affine_map(const HybZono &Z, const Eigen::SparseMatrix< zono_float > &R, const Eigen::Vector< zono_float, -1 > &s=Eigen::Vector< zono_float, -1 >())
Returns affine map R*Z + s of set Z.
Definition PolymorphicFunctions.hpp:61
std::unique_ptr< HybZono > intersection(const HybZono &Z1, HybZono &Z2, const Eigen::SparseMatrix< zono_float > &R=Eigen::SparseMatrix< zono_float >())
Computes the generalized intersection of sets Z1 and Z2 over the matrix R.
Definition PolymorphicFunctions.hpp:188
std::unique_ptr< HybZono > halfspace_intersection(HybZono &Z, const Eigen::SparseMatrix< zono_float > &H, const Eigen::Vector< zono_float, -1 > &f, const Eigen::SparseMatrix< zono_float > &R=Eigen::SparseMatrix< zono_float >())
Computes the intersection generalized intersection of set Z with halfspace H*x <= f over matrix R.
Definition PolymorphicFunctions.hpp:294
std::unique_ptr< HybZono > intersection_over_dims(const HybZono &Z1, HybZono &Z2, const std::vector< int > &dims)
Computes the generalized intersection of sets Z1 and Z2 over the specified dimensions.
Definition PolymorphicFunctions.hpp:263
std::unique_ptr< HybZono > pontry_diff(HybZono &Z1, HybZono &Z2, bool exact=false)
Computes the Pontryagin difference Z1 - Z2.
Definition PolymorphicFunctions.hpp:324
std::unique_ptr< HybZono > constrain(HybZono &Z, const std::vector< Inequality > &ineqs, const Eigen::SparseMatrix< zono_float > &R=Eigen::SparseMatrix< zono_float >())
Applies inequalities to set.
Definition PolymorphicFunctions.hpp:857
std::unique_ptr< HybZono > vrep_2_hybzono(const std::vector< Eigen::Matrix< zono_float, -1, -1 > > &Vpolys, bool expose_indicators=false)
Computes a hybrid zonotope from a union of vertex representation polytopes.
Definition PolymorphicFunctions.hpp:1402
std::unique_ptr< HybZono > zono_union_2_hybzono(std::vector< Zono > &Zs, bool expose_indicators=false)
Computes a hybrid zonotope from a union of zonotopes.
Definition PolymorphicFunctions.hpp:1255
std::unique_ptr< Zono > interval_2_zono(const Box &box)
Builds a zonotope from a Box object.
Definition PolymorphicFunctions.hpp:1566
std::unique_ptr< Zono > make_regular_zono_2D(const zono_float radius, const int n_sides, const bool outer_approx, const Eigen::Vector< zono_float, 2 > &c)
Builds a 2D regular zonotope with a given radius and number of sides.
Definition PolymorphicFunctions.hpp:1585
std::unique_ptr< ConZono > vrep_2_conzono(const Eigen::Matrix< zono_float, -1, -1 > &Vpoly)
Builds a constrained zonotope from a vertex representation polytope.
Definition PolymorphicFunctions.hpp:1538
Definition ADMM.hpp:41
@ LESS_OR_EQUAL
Definition Inequality.hpp:39
@ GREATER_OR_EQUAL
Definition Inequality.hpp:40
@ GREATER
Definition Inequality.hpp:43
@ EQUAL
Definition Inequality.hpp:41
@ LESS
Definition Inequality.hpp:42
Interval class.
Definition Intervals.hpp:374
Settings for optimization routines in ZonoOpt library.
Definition SolverDataStructures.hpp:26
Solution data structure for optimization routines in ZonoOpt library.
Definition SolverDataStructures.hpp:153
Eigen::Vector< zono_float, -1 > z
solution vector
Definition SolverDataStructures.hpp:157