Source code for pycvxset.ConstrainedZonotope

# Copyright (C) 2020-2025 Mitsubishi Electric Research Laboratories (MERL)
#
# SPDX-License-Identifier: AGPL-3.0-or-later

# Code purpose:  Define the ConstrainedZonotope class

import cvxpy as cp
import numpy as np

from pycvxset.common import (
    _compute_project_single_point,
    _compute_support_function_single_eta,
    convex_set_closest_point,
    convex_set_distance,
    convex_set_extreme,
    convex_set_minimum_volume_circumscribing_rectangle,
    convex_set_project,
    convex_set_projection,
    convex_set_slice,
    convex_set_slice_then_projection,
    convex_set_support,
    minimize,
    plot,
    sanitize_Aebe,
    sanitize_and_identify_Aebe,
    sanitize_Gc,
)
from pycvxset.common.constants import DEFAULT_CVXPY_ARGS_LP, DEFAULT_CVXPY_ARGS_SOCP, PYCVXSET_ZERO
from pycvxset.common.polytope_approximations import polytopic_inner_approximation, polytopic_outer_approximation
from pycvxset.ConstrainedZonotope.operations_binary import (
    DOCSTRING_FOR_PROJECT,
    DOCSTRING_FOR_PROJECTION,
    DOCSTRING_FOR_SLICE,
    DOCSTRING_FOR_SLICE_THEN_PROJECTION,
    DOCSTRING_FOR_SUPPORT,
    affine_map,
    approximate_pontryagin_difference,
    cartesian_product,
    contains,
    intersection,
    intersection_under_inverse_affine_map,
    intersection_with_affine_set,
    intersection_with_halfspaces,
    inverse_affine_map_under_invertible_matrix,
    minus,
    plus,
)
from pycvxset.ConstrainedZonotope.operations_unary import (
    chebyshev_centering,
    interior_point,
    maximum_volume_inscribing_ellipsoid,
    remove_redundancies,
)


[docs] class ConstrainedZonotope: r"""Constrained zonotope class Constrained zonotope defines a polytope in the working dimension :math:`\mathbb{R}^n` as an affine transformation of a polytope defined in latent space :math:`B_\infty(A_e, b_e)\subset \mathbb{R}^{N_C}`. Here, :math:`B_\infty(A_e, b_e)` is defined as the intersection of a unit :math:`\ell_\infty`-norm ball and a collection of :math:`M_C` linear constraints :math:`\{\xi\in\mathbb{R}^{N_C}|A_e \xi = b_e\}.` Formally, a **constrained zonotope** is defined as follows, .. math:: \mathcal{C} = \{G \xi + c\ |\ \xi \in B_\infty(A_e, b_e)\} \subset \mathbb{R}^n, where .. math:: B_\infty(A_e, b_e)= \{\xi\ |\ \| \xi \|_\infty \leq 1, A_e \xi = b_e\} \subset \mathbb{R}^{N_C}, with :math:`G\in\mathbb{R}^{n\times N_C}`, :math:`c\in\mathbb{R}^{n}`, :math:`A_e\in\mathbb{R}^{M_C\times N_C}`, and :math:`b\in\mathbb{R}^{M_C}`. A constrained zonotope provide an alternative and equivalent representation of any convex and compact polytope. Furthermore, a constrained zonotope admits closed-form expressions for several set manipulations that can often be accomplished without invoking any optimization solvers. See [SDGR16]_ [RK22]_ [VWD24]_ for more details. A **zonotope** is a special class of constrained zonotopes, and are defined as .. math:: \mathcal{Z} = \{G \xi + c\ |\ \|\xi\|_\infty \leq 1\} \subset \mathbb{R}^n. In other words, a zonotope is a constrained zonotope with no equality constraints in the latent dimension space. In :class:`ConstrainedZonotope`, we model zonotopes by having (Ae,be) be empty (n\_equalities is zero). Constrained zonotope object construction admits **one** of the following combinations (as keyword arguments): #. dim for an **empty** constrained zonotope of dimension dim, #. (G, c, Ae, be) for a **constrained zonotope**, #. (G, c) for a **zonotope**, #. (lb, ub) for a **zonotope** equivalent to an **axis-aligned cuboid** with appropriate bounds :math:`\{x\ |\ lb\leq x \leq ub\}`, and #. (c, h) for a **zonotope** equivalent to an **axis-aligned cuboid** centered at c with specified scalar/vector half-sides :math:`h`, :math:`\{x\ |\ \forall i\in\{1,2,...,n\}, |x_i - c_i| \leq h_i\}`. #. (c=p, G=None) for a **zonotope** equivalent to a **single point** p, #. P for a **constrained zonotope** equivalent to the :class:`pycvxset.Polytope.Polytope` object P, Args: dim (int, optional): Dimension of the empty constrained zonotope. If NOTHING is provided, dim=0 is assumed. c (array_like, optional): Affine transformation translation vector. Must be 1D array, and the constrained zonotope dimension is determined by number of elements in c. When c is provided, either (G) or (G, Ae, be) or (h) must be provided additionally. When h is provided, c is the centroid of the resulting zonotope. G (array_like): Affine transformation matrix. The vectors are stacked vertically with matching number of rows as c. When G is provided, (c, Ae, be) OR (c) must also be provided. To define a constrained zonotope with a single point, set c to the point AND G to None (do not set (Ae, be) or set them to (None, None)). Ae (array_like): Equality coefficient vectors. The vectors are stacked vertically with matching number of columns as G. When Ae is provided, (G, c, be) must also be provided. be (array_like): Equality coefficient constants. The constants are expected to be in a 1D numpy array. When be is provided, (G, c, Ae) must also be provided. lb (array_like, optional): Lower bounds of the axis-aligned cuboid. Must be 1D array, and the constrained zonotope dimension is determined by number of elements in lb. When lb is provided, ub must also be provided. ub (array_like, optional): Upper bounds of the axis-aligned cuboid. Must be 1D array of length as same as lb. When ub is provided, lb must also be provided. h (array_like, optional): Half-side length of the axis-aligned cuboid. Can be a scalar or a vector of length as same as c. When h is provided, c must also be provided. polytope (Polytope, optional): Polytope to use to construct constrained zonotope. Raises: ValueError: (G, c) is not compatible. ValueError: (G, c, Ae, be) is not compatible. ValueError: (lb, ub) is not valid ValueError: (c, h) is not valid ValueError: Provided polytope is not bounded. UserWarning: When a row with all zeros in Ae and be. """
[docs] def __init__(self, **kwargs): """Constructor for ConstrainedZonotope class""" self._G, self._c, self._Ae, self._be = None, None, None, None self._is_full_dimensional, self._is_empty = None, None # These attributes are used by CVXPY to solve problems self._cvxpy_args_lp = DEFAULT_CVXPY_ARGS_LP self._cvxpy_args_socp = DEFAULT_CVXPY_ARGS_SOCP # Check how the constructor was called. empty_constrained_zonotope_passed = ("dim" in kwargs) or not kwargs lb_and_ub_passed = all(kw in kwargs for kw in ("lb", "ub")) c_and_h_passed = all(kw in kwargs for kw in ("c", "h")) G_and_c_passed = all(k in kwargs for k in ("G", "c")) Ae_and_be_passed = all(k in kwargs for k in ("Ae", "be")) polytope_passed = "polytope" in kwargs # Set _G, _c, _Ae, _be, _is_empty, _is_full_dimensional if empty_constrained_zonotope_passed: if len(kwargs) > 1: raise ValueError("Cannot set dimension dim with other arguments") else: dim = kwargs.get("dim", 0) self._G, self._c, self._Ae, self._be = self._get_Gc_Aebe_for_empty_constrained_zonotope(dim, 0) self._is_full_dimensional, self._is_empty = (dim == 0), True elif lb_and_ub_passed or c_and_h_passed: if lb_and_ub_passed: if len(kwargs) != 2: raise ValueError("Cannot set bounds (lb, ub) with other arguments") lb, ub = kwargs.get("lb"), kwargs.get("ub") else: # We have c_and_h_passed is True if len(kwargs) != 2: raise ValueError("Cannot set up constrained zonotope from (c, h) with other arguments") try: c = np.atleast_1d(np.squeeze(kwargs.get("c"))).astype(float) h = np.squeeze(kwargs.get("h")).astype(float) except (TypeError, ValueError) as err: raise ValueError( "Expected c and h to be convertible into a numpy 1D array and scalar/1D array of " f"float respectively! Got c: {np.array2string(np.array(kwargs.get('c')))} and " f"h: {np.array2string(np.array(kwargs.get('h')))}" ) from err if c.ndim >= 2: raise ValueError( f"Expected c to be a 1-dimensional array-like object! Got c: {np.array2string(np.array(c))}." ) if h.ndim >= 2: raise ValueError( "Expected h to be a 0-dimensional or 1-dimensional array-like object " f"Got {np.array2string(np.array(h))}." ) elif h.ndim == 1: if h.shape != c.shape: raise ValueError( "Expected c and 1-dimensional h to match in dimensions. Got {c.shape} and {h.shape}" ) lb = c - h ub = c + h else: lb = c - h * np.ones_like(c) ub = c + h * np.ones_like(c) self._G, self._c, self._Ae, self._be, lb, ub = self._get_Gc_Aebe_from_bounds(lb, ub) self._is_full_dimensional = self.dim == 1 or (np.abs(ub - lb) > PYCVXSET_ZERO).all() self._is_empty = self.c is None elif G_and_c_passed: # Either it is a zonotope (G, c) or a constrained zonotope (G, c, Ae, be) if len(kwargs) != 2 and (len(kwargs) != 4 and not Ae_and_be_passed): raise ValueError( "Cannot set zonotope (G, c) or constrained zonotope (G, c, Ae, be) with other arguments" ) self._G, self._c = sanitize_Gc(kwargs.get("G"), kwargs.get("c")) if Ae_and_be_passed: sanitized_Ae, sanitized_be = sanitize_Aebe(kwargs.get("Ae"), kwargs.get("be")) if self.G.size == 0 and (sanitized_Ae is not None or sanitized_be is not None): raise ValueError("When G is None and (Ae, be) was passed, then (Ae, be) must be (None, None)!") else: if sanitized_Ae is None: self._Ae, self._be = np.empty((0, self.dim)), np.empty((0,)) else: self._Ae, self._be = sanitized_Ae, sanitized_be if self.Ae.shape[1] != self.latent_dim: # Check if (Ae, be) and (A, b) can go together? raise ValueError( f"Expected Ae to have {self.latent_dim:d} number of columns. Got Ae.shape: " f"{self.Ae.shape}!" ) # ConstrainedZonotope emptiness and full-dimensionality needs to be confirmed self._is_full_dimensional, self._is_empty = None, None else: # Set only (Ae, be) to empty | Full-dimensionality depends on rank of G _, _, self._Ae, self._be = self._get_Gc_Aebe_for_empty_constrained_zonotope(self.dim, self.latent_dim) if self.G.size == 0 and self.c is not None: self._is_full_dimensional, self._is_empty = (self.dim == 1), False else: self._is_full_dimensional, self._is_empty = None, self.c is None elif polytope_passed: if len(kwargs) != 1: raise ValueError("Cannot construct constrained zonotope from polytope if other arguments are provided") polytope = kwargs.get("polytope") self._is_full_dimensional, self._is_empty = polytope.is_full_dimensional, polytope.is_empty if not polytope.is_bounded: raise ValueError("Expected a convex and compact polytope!") elif polytope.is_empty: self._G, self._c, self._Ae, self._be = self._get_Gc_Aebe_for_empty_constrained_zonotope(polytope.dim, 0) else: if polytope.in_H_rep: # Compute zonotope Z_0 ={G \xi + c| ||\xi||_\infty \leq 1, \xi\in R^n_g} so that polytope \subseteq # Z_0. lb, ub = polytope.minimum_volume_circumscribing_rectangle() Z_0 = self.__class__(lb=lb, ub=ub) # sigma satisfies sigma <= H z <= k for all z \in polytope where H is polytope.A (using Scott's # notation in the paper [SDGR16]_) H = polytope.A sigma, k = -polytope.support(-H)[0], polytope.b # Implement (21) from Scott's paper (with additional equality constraints when needed) latent_dim = Z_0.dim + polytope.n_halfspaces G = np.hstack((Z_0.G, np.zeros((Z_0.dim, latent_dim - Z_0.dim)))) c = Z_0.c Ae = np.hstack((H @ Z_0.G, np.diag((sigma - k) / 2))) be = (sigma + k) / 2 - (H @ Z_0.c) if polytope.n_equalities > 0: # Define CZ and unpack relevant members CZ = self.__class__(G=G, c=c, Ae=Ae, be=be).intersection_with_affine_set( Ae=polytope.Ae, be=polytope.be ) self._G, self._c, self._Ae, self._be = CZ.G, CZ.c, CZ.Ae, CZ.be else: self._G, self._c, self._Ae, self._be = G, c, Ae, be else: if polytope.n_vertices == 1: self._G, _, self._Ae, self._be = self._get_Gc_Aebe_for_empty_constrained_zonotope( polytope.dim, 0 ) self._c = np.squeeze(polytope.V) else: # Define a ConstrainedZonotope object corresponding to the polytope.n_vertices-dimension simplex CZ_simplex = self.__class__( lb=np.zeros((polytope.n_vertices, 1)), ub=np.ones((polytope.n_vertices, 1)) ).intersection_with_affine_set(Ae=np.ones((1, polytope.n_vertices)), be=1) # CZ of interest is the affine transformation of the simplex CZ = polytope.V.T @ CZ_simplex # Unpack relevant members self._G, self._c, self._Ae, self._be = CZ.G, CZ.c, CZ.Ae, CZ.be else: raise ValueError( "Got invalid arguments while defining a constrained zonotope. Please specify either (G, c) or " "(G, c, Ae, be) or (lb, ub) or (c, h) or polytope or dim or NOTHING." )
@staticmethod def _get_Gc_Aebe_for_empty_constrained_zonotope(dim, latent_dim): return np.empty((dim, latent_dim)), None, np.empty((0, latent_dim)), np.empty((0,)) def _get_Gc_Aebe_from_bounds(self, lb, ub): r"""Define a zonotope from bounds (lb, ub), i.e., a zonotope that is equivalent to the polytope defined from the bounds (lb, ub). Args: lb (array_like): Lower bound of the constrained zonotope. ub (array_like): Upper bound of the constrained zonotope. Raises: ValueError: Mismatch in lb, ub shape ValueError: lb, ub is not convertible into 1D numpy float arrays Notes: When :math:`lb_i > ub_i` for any :math:`i`, then the zonotope is empty. Otherwise, we uses the following simple manipulations to define a zonotope from the bounds lb, ub: .. math :: newobj &= {x\ |\ lb \leq x \leq ub}\\ &= {x\ |\ - (ub - lb)/2 \leq x - (ub + lb)/2 \leq + (ub - lb)/2}\\ &= {x\ |\ - d \leq x - c \leq d}\\ &= {x\ |\ -1 \leq diag(1./d)(x - c) \leq 1}\\ &= {diag(d)z + c\ |\ -1 \leq z \leq 1} Embedded zonotopes (some dimension is held constant) also have their latent dimension equal to set dimension. """ try: lb = np.atleast_1d(np.squeeze(lb)).astype(float) ub = np.atleast_1d(np.squeeze(ub)).astype(float) except (TypeError, ValueError) as err: raise ValueError("Expected lb, ub to convertible into 1D float numpy arrays") from err if lb.shape != ub.shape or lb.ndim != 1: raise ValueError("Expected lb, ub to 1D numpy arrays of same shape") elif np.any(ub < lb): return *self._get_Gc_Aebe_for_empty_constrained_zonotope(lb.shape[0], 0), lb, ub elif np.allclose(lb, ub): G, _, Ae, be = self._get_Gc_Aebe_for_empty_constrained_zonotope(lb.shape[0], 0) c = lb return G, c, Ae, be, lb, ub else: dim, latent_dim = lb.shape[0], lb.shape[0] _, _, Ae, be = self._get_Gc_Aebe_for_empty_constrained_zonotope(dim, latent_dim) d = (ub - lb) / 2 G = np.diag(d) c = (lb + ub) / 2 return G, c, Ae, be, lb, ub @property def dim(self): """Dimension of the constrained zonotope. Returns: int: Dimension of the constrained zonotope. Notes: We determine dimension from G, since c is set to None in case of empty (constrained) zonotope. """ return self.G.shape[0] @property def c(self): """Affine transformation vector c for the constrained zonotope. Returns: numpy.ndarray: Affine transformation vector c. """ return self._c @property def G(self): """Affine transformation matrix G for the constrained zonotope. Returns: numpy.ndarray: Affine transformation matrix G. """ return self._G @property def latent_dim(self): """Latent dimension of the constrained zonotope. Returns: int: Latent dimension of the constrained zonotope. """ return self.G.shape[1] @property def Ae(self): """Equality coefficient vectors Ae for the constrained zonotope. Returns: numpy.ndarray: Equality coefficient vectors Ae for the constrained zonotope. Ae is np.empty((0, self.latent_dim)) for a zonotope. """ return self._Ae @property def be(self): """Equality constants be for the constrained zonotope. Returns: numpy.ndarray: Equality constants be for the constrained zonotope. be is np.empty((0,)) for a zonotope. """ return self._be @property def He(self): r"""Equality constraints `He=[Ae, be]` for the constrained zonotope. Returns: numpy.ndarray: H-Rep in [Ae, be]. He is np.empty((0, self.latent_dim + 1)) for a zonotope. """ return np.hstack((self.Ae, np.array([self.be]).T)) @property def n_equalities(self): """Number of equality constraints used when defining the constrained zonotope. Returns: int: Number of equality constraints used when defining the constrained zonotope """ return self.Ae.shape[0] @property def is_bounded(self): """Check if the constrained zonotope is bounded (which is always True)""" return True @property def is_empty(self): """Check if the constrained zonotope is empty Raises: NotImplementedError: Unable to solve the feasibility problem using CVXPY Returns: bool: When True, the polytope is empty """ if self._is_empty is None: x = cp.Variable((self.dim,)) _, feasibility_value, _ = self.minimize( x, objective_to_minimize=cp.Constant(0), cvxpy_args=self.cvxpy_args_lp, task_str="emptiness check for the constrained zonotope", ) self._is_empty = feasibility_value == np.inf return self._is_empty @property def is_full_dimensional(self): r""" Check if the affine dimension of the constrained zonotope is the same as the constrained zonotope dimension Returns: bool: True when the affine hull containing the constrained zonotope has the dimension `self.dim` Notes: An empty polytope is full dimensional if dim=0, otherwise it is not full-dimensional. See Sec. 2.1.3 of [BV04] for discussion on affine dimension. A non-empty zonotope is full-dimensional if and only if G has full row rank. A non-empty constrained zonotope is full-dimensional if and only if [G; A] has full row rank. """ if self._is_full_dimensional is None: if self.is_empty: self._is_full_dimensional = self.dim == 0 elif self.is_zonotope: self._is_full_dimensional = np.linalg.matrix_rank(self.G) == self.dim else: # Affine transformation of a ball in latent_dim remains full-dimensional stacked_G_A = np.vstack((self.G, self.Ae)) self._is_full_dimensional = np.linalg.matrix_rank(stacked_G_A) == stacked_G_A.shape[0] return self._is_full_dimensional @property def is_singleton(self): """Check if the constrained zonotope is a singleton""" if self.is_zonotope: return (self.G is None or self.G.size == 0) and (self.c is not None) else: _, _, Aebe_status, _ = sanitize_and_identify_Aebe(self.Ae, self.be) return Aebe_status == "single_point" @property def is_zonotope(self): """Check if the constrained zonotope is a zonotope""" return self.n_equalities == 0 @property def cvxpy_args_lp(self): """CVXPY arguments in use when solving a linear program Returns: dict: CVXPY arguments in use when solving a linear program. Defaults to dictionary in `pycvxset.common.DEFAULT_CVXPY_ARGS_LP`. """ return self._cvxpy_args_lp @cvxpy_args_lp.setter def cvxpy_args_lp(self, value): """Update CVXPY arguments in use when solving a linear program Args: value: Dictionary with new CVXPY arguments in use when solving a linear program. """ self._cvxpy_args_lp = value @property def cvxpy_args_socp(self): """CVXPY arguments in use when solving a second-order cone program Returns: dict: CVXPY arguments in use when solving a second-order cone program. Defaults to dictionary in `pycvxset.common.DEFAULT_CVXPY_ARGS_SOCP`. """ return self._cvxpy_args_socp @cvxpy_args_socp.setter def cvxpy_args_socp(self, value): """Update CVXPY arguments in use when solving a second-order cone program Args: value: Dictionary with new CVXPY arguments in use when solving a second-order cone program. """ self._cvxpy_args_socp = value ################################## # Plotting and polytope operations ################################## plot = plot polytopic_inner_approximation = polytopic_inner_approximation polytopic_outer_approximation = polytopic_outer_approximation ########### # Auxiliary ###########
[docs] def containment_constraints(self, x, flatten_order="F"): """Get CVXPY constraints for containment of x (a cvxpy.Variable) in a constrained zonotope. Args: x (cvxpy.Variable): CVXPY variable to be optimized flatten_order (char): Order to use for flatten (choose between "F", "C"). Defaults to "F", which implements column-major flatten. In 2D, column-major flatten results in stacking rows horizontally to achieve a single horizontal row. Raises: ValueError: When constrained zonotope is empty Returns: tuple: A tuple with two items: #. constraint_list (list): CVXPY constraints for the containment of x in the constrained zonotope. #. xi (cvxpy.Variable | None): CVXPY variable representing the latent dimension variable. It is None, when the constrained zonotope is a single point. """ x = x.flatten(order=flatten_order) if self.c is None: raise ValueError("Containment constraints can not be generated for an empty constrained zonotope!") elif self.is_singleton: return [x == self.c], None else: xi = cp.Variable((self.latent_dim,)) if self.is_zonotope: return [x == self.G @ xi + self.c, cp.norm(xi, p="inf") <= 1], xi else: return [x == self.G @ xi + self.c, cp.norm(xi, p="inf") <= 1, self.Ae @ xi == self.be], xi
minimize = minimize def __pow__(self, power): r"""Compute the Cartesian product with itself. Args: power (int): Number of times the polytope is multiplied with itself Returns: Polytope: The polytope :math:`\mathcal{R}` corresponding to P`^N`. """ concatenated_G = np.kron(np.eye(power), self.G) concatenated_c = np.tile(self.c, (power,)) concatenated_Ae = np.kron(np.eye(power), self.Ae) concatenated_be = np.tile(self.be, (power,)) return self.__class__(G=concatenated_G, c=concatenated_c, Ae=concatenated_Ae, be=concatenated_be) ################## # Unary operations ##################
[docs] def copy(self): """Get a copy of the constrained zonotope""" return self.__class__(G=self.G, c=self.c, Ae=self.Ae, be=self.be)
chebyshev_centering = chebyshev_centering interior_point = interior_point maximum_volume_inscribing_ellipsoid = maximum_volume_inscribing_ellipsoid minimum_volume_circumscribing_rectangle = convex_set_minimum_volume_circumscribing_rectangle remove_redundancies = remove_redundancies ###################### # Comparison operators ###################### contains = contains __contains__ = contains def __le__(self, Q): """Overload <= operator for containment. self <= Q is equivalent to Q.contains(self).""" return Q.contains(self) def __ge__(self, Q): """Overload >= operator for containment. self >= Q is equivalent to P.contains(Q).""" return self.contains(Q) def __eq__(self, Q): """Overload == operator with equality check. P == Q is equivalent to Q.contains(P) and P.contains(Q)""" return self <= Q and self >= Q __lt__ = __le__ __gt__ = __ge__ #################### # Binary operations #################### plus = plus __add__ = plus __radd__ = plus minus = minus __sub__ = minus def __rsub__(self, Q): raise TypeError(f"Unsupported operation: {type(Q)} - ConstrainedZonotope!") __array_ufunc__ = None # Allows for numpy matrix times Polytope affine_map = affine_map inverse_affine_map_under_invertible_matrix = inverse_affine_map_under_invertible_matrix # ConstrainedZonotope times Matrix __matmul__ = inverse_affine_map_under_invertible_matrix def __mul__(self, x): """Do not allow ConstrainedZonotope * anything""" return NotImplemented def __neg__(self): return affine_map(self, -1) # Scalar/Matrix times ConstrainedZonotope (called when left operand does not support multiplication) def __rmatmul__(self, M): """Overload @ operator for affine map (matrix times ConstrainedZonotope).""" return affine_map(self, M) def __rmul__(self, m): """Overload * operator for multiplication.""" try: m = np.squeeze(m).astype(float) except (TypeError, ValueError) as err: raise TypeError(f"Unsupported operation: {type(m)} * ConstrainedZonotope!") from err return affine_map(self, m) approximate_pontryagin_difference = approximate_pontryagin_difference cartesian_product = cartesian_product closest_point = convex_set_closest_point distance = convex_set_distance extreme = convex_set_extreme intersection = intersection intersection_with_halfspaces = intersection_with_halfspaces intersection_with_affine_set = intersection_with_affine_set intersection_under_inverse_affine_map = intersection_under_inverse_affine_map
[docs] def project(self, x, p=2): return convex_set_project(self, x, p=p)
project.__doc__ = convex_set_project.__doc__ + DOCSTRING_FOR_PROJECT
[docs] def projection(self, project_away_dims): return convex_set_projection(self, project_away_dims=project_away_dims)
projection.__doc__ = convex_set_projection.__doc__ + DOCSTRING_FOR_PROJECTION
[docs] def slice(self, dims, constants): return convex_set_slice(self, dims, constants)
slice.__doc__ = convex_set_slice.__doc__ + DOCSTRING_FOR_SLICE
[docs] def slice_then_projection(self, dims, constants): return convex_set_slice_then_projection(self, dims=dims, constants=constants)
slice_then_projection.__doc__ = convex_set_slice_then_projection.__doc__ + DOCSTRING_FOR_SLICE_THEN_PROJECTION
[docs] def support(self, eta): return convex_set_support(self, eta)
support.__doc__ = convex_set_support.__doc__ + DOCSTRING_FOR_SUPPORT _compute_support_function_single_eta = _compute_support_function_single_eta _compute_project_single_point = _compute_project_single_point ##################################### # Constrained zonotope representation ##################################### def __str__(self): if self.is_empty: short_str = f"(empty) in R^{self.dim:d}" else: short_str = f"in R^{self.dim:d}" return f"Constrained Zonotope {short_str:s}" def __repr__(self): long_str = [str(self)] if self.is_empty: pass elif self.is_zonotope and self.G.size == 0: long_str += ["\n\tthat is a zonotope representing a single point"] elif self.is_zonotope: long_str += [f"\n\tthat is a zonotope with latent dimension {self.latent_dim:d}"] elif self.n_equalities == 1: long_str += [f"\n\twith latent dimension {self.latent_dim:d} and 1 equality constraint"] else: long_str += [ f"\n\twith latent dimension {self.latent_dim:d} and {self.n_equalities:d} equality constraints" ] return "".join(long_str)