def array(array_like, dtype=None): """ Initializes a new array. Creates a NumPy array if possible; if not, creates a CasADi array. See syntax here: https://numpy.org/doc/stable/reference/generated/numpy.array.html """ if is_casadi_type(array_like, recursive=False): # If you were literally given a CasADi array, just return it # Handles inputs like cas.DM([1, 2, 3]) return array_like elif not is_casadi_type(array_like, recursive=True): # If you were given a list of iterables that don't have CasADi types: # Handles inputs like [[1, 2, 3], [4, 5, 6]] return _onp.array(array_like, dtype=dtype) else: # Handles inputs like [[opti_var_1, opti_var_2], [opti_var_3, opti_var_4]] def make_row(contents: List): try: return _cas.horzcat(*contents) except (TypeError, Exception): return contents return _cas.vertcat( *[ make_row(row) for row in array_like ] )
def stack(arrays: Tuple, axis: int = 0): """ Join a sequence of arrays along a new axis. Returns a NumPy array if possible; if not, returns a CasADi array. See syntax here: https://numpy.org/doc/stable/reference/generated/numpy.stack.html """ if not is_casadi_type(arrays, recursive=True): return _onp.stack(arrays, axis=axis) else: ### Validate stackability for array in arrays: if is_casadi_type(array, recursive=False): if not array.shape[1] == 1: raise ValueError("Can only stack Nx1 CasADi arrays!") else: if not len(array.shape) == 1: raise ValueError("Can only stack 1D NumPy ndarrays alongside CasADi arrays!") if axis == 0 or axis == -2: return _cas.transpose(_cas.horzcat(*arrays)) elif axis == 1 or axis == -1: return _cas.horzcat(*arrays) else: raise ValueError("CasADi-backend arrays can only be 1D or 2D, so `axis` must be 0 or 1.")
def interp(x, xp, fp, left=None, right=None, period=None): """ One-dimensional linear interpolation, analogous to numpy.interp(). Returns the one-dimensional piecewise linear interpolant to a function with given discrete data points (xp, fp), evaluated at x. See syntax here: https://numpy.org/doc/stable/reference/generated/numpy.interp.html Specific notes: xp is assumed to be sorted. """ if not is_casadi_type([x, xp, fp], recursive=True): return _onp.interp(x=x, xp=xp, fp=fp, left=left, right=right, period=period) else: ### If xp or x are CasADi types, this is unsupported :( if is_casadi_type([x, xp], recursive=True): raise NotImplementedError( "Unfortunately, CasADi doesn't yet support a dispatch for x or xp as CasADi types." ) ### Handle period argument if period is not None: if any(logical_or(xp < 0, xp > period)): raise NotImplementedError( "Haven't yet implemented handling for if xp is outside the period." ) # Not easy to implement because casadi doesn't have a sort feature. x = _cas.mod(x, period) ### Make sure x isn't an int if isinstance(x, int): x = float(x) ### Make sure that x is an iterable try: x[0] except TypeError: x = array([x], dtype=float) ### Make sure xp is an iterable xp = array(xp, dtype=float) ### Do the interpolation f = _cas.interp1d(xp, fp, x) ### Handle left/right if left is not None: f = where(x < xp[0], left, f) if right is not None: f = where(x > xp[-1], right, f) ### Return return f
def cross(a, b, axisa=-1, axisb=-1, axisc=-1, axis=None, manual=False): """ Return the cross product of two (arrays of) vectors. See syntax here: https://numpy.org/doc/stable/reference/generated/numpy.cross.html """ if manual: cross_x = a[1] * b[2] - a[2] * b[1] cross_y = a[2] * b[0] - a[0] * b[2] cross_z = a[0] * b[1] - a[1] * b[0] return cross_x, cross_y, cross_z if not is_casadi_type([a, b], recursive=True): return _onp.cross(a, b, axisa=axisa, axisb=axisb, axisc=axisc, axis=axis) else: if axis is not None: if not (axis == -1 or axis == 0 or axis == 1): raise ValueError("`axis` must be -1, 0, or 1.") axisa = axis axisb = axis axisc = axis if axisa == -1 or axisa == 1: if not is_casadi_type(a): a = _cas.DM(a) a = a.T elif axisa == 0: pass else: raise ValueError("`axisa` must be -1, 0, or 1.") if axisb == -1 or axisb == 1: if not is_casadi_type(b): b = _cas.DM(b) b = b.T elif axisb == 0: pass else: raise ValueError("`axisb` must be -1, 0, or 1.") # Compute the cross product, horizontally (along axis 1 of a 2D array) c = _cas.cross(a, b) if axisc == -1 or axisc == 1: c = c.T elif axisc == 0: pass else: raise ValueError("`axisc` must be -1, 0, or 1.") return c
def mod(x1, x2): """ Return element-wise remainder of division. See syntax here: https://numpy.org/doc/stable/reference/generated/numpy.mod.html """ if not is_casadi_type(x1) and not is_casadi_type(x2): return _onp.mod(x1, x2) else: return _cas.mod(x1, x2)
def reshape(a, newshape): """Gives a new shape to an array without changing its data.""" if not is_casadi_type(a): return _onp.reshape(a, newshape) else: return _cas.reshape(a, newshape)
def vstack(arrays): if not is_casadi_type(arrays, recursive=True): return _onp.vstack(arrays) else: raise ValueError( "Use `np.stack()` or `np.concatenate()` instead of `np.vstack()` when dealing with mixed-backend arrays." )
def roll(a, shift, axis: int = None): """ Roll array elements along a given axis. Elements that roll beyond the last position are re-introduced at the first. See syntax here: https://numpy.org/doc/stable/reference/generated/numpy.roll.html Parameters ---------- a : array_like Input array. shift : int The number of places by which elements are shifted. Returns ------- res : ndarray Output array, with the same shape as a. """ if not is_casadi_type(a): return _onp.roll(a, shift, axis=axis) else: # TODO add some checking to make sure shift < len(a), or shift is modulo'd down by len(a). # assert shift < a.shape[axis] if 1 in a.shape and axis == 0: return _cas.vertcat(a[-shift, :], a[:-shift, :]) elif axis == 0: return _cas.vertcat(a.T[:, -shift], a.T[:, :-shift]).T elif axis == 1: return _cas.horzcat(a[:, -shift], a[:, :-shift]) elif axis is None: return roll(a, shift=shift, axis=0) else: raise Exception("CasADi types can only be up to 2D, so `axis` must be None, 0, or 1.")
def norm(x, ord=None, axis=None): """ Matrix or vector norm. See syntax here: https://numpy.org/doc/stable/reference/generated/numpy.linalg.norm.html """ if not is_casadi_type(x): return _onp.linalg.norm(x, ord=ord, axis=axis) else: # Figure out which axis, if any, to take a vector norm about. if axis is not None: if not ( axis==0 or axis==1 or axis == -1 ): raise ValueError("`axis` must be -1, 0, or 1 for CasADi types.") elif x.shape[0] == 1: axis = 1 elif x.shape[1] == 1: axis = 0 if ord is None: if axis is not None: ord = 2 else: ord = 'fro' if ord == 1: # return _cas.norm_1(x) return sum( abs(x), axis=axis ) elif ord == 2: # return _cas.norm_2(x) return sum( x ** 2, axis=axis ) ** 0.5 elif ord == 'fro': return _cas.norm_fro(x) elif np.isinf(ord): return _cas.norm_inf() else: try: return sum( abs(x) ** ord, axis=axis ) ** (1 / ord) except Exception as e: print(e) raise ValueError("Couldn't interpret `ord` sensibly! Tried to interpret it as a floating-point order " "as a last-ditch effort, but that didn't work.")
def empty_like(prototype, dtype=None, order='K', subok=True, shape=None): """Return a new array with the same shape and type as a given array.""" if not is_casadi_type(prototype): return _onp.empty_like(prototype, dtype=dtype, order=order, subok=subok, shape=shape) else: return zeros_like(prototype)
def ones_like(a, dtype=None, order='K', subok=True, shape=None): """Return an array of ones with the same shape and type as a given array.""" if not is_casadi_type(a): return _onp.ones_like(a, dtype=dtype, order=order, subok=subok, shape=shape) else: return _onp.ones(shape=length(a))
def where( condition, value_if_true, value_if_false, ): if not is_casadi_type([condition, value_if_true, value_if_false], recursive=True): return _onp.where(condition, value_if_true, value_if_false) else: return _cas.if_else(condition, value_if_true, value_if_false)
def logspace(start: float = 0., stop: float = 1., num: int = 50): """ Return numbers spaced evenly on a log scale. See syntax here: https://numpy.org/doc/stable/reference/generated/numpy.logspace.html """ if not is_casadi_type([start, stop, num], recursive=True): return _onp.logspace(start, stop, num) else: return 10**linspace(start, stop, num)
def linspace(start: float = 0., stop: float = 1., num: int = 50): """ Returns evenly spaced numbers over a specified interval. See syntax here: https://numpy.org/doc/stable/reference/generated/numpy.linspace.html """ if not is_casadi_type([start, stop, num], recursive=True): return _onp.linspace(start, stop, num) else: return _cas.linspace(start, stop, num)
def logical_not(x): """ Compute the truth value of NOT x element-wise. See syntax here: https://numpy.org/doc/stable/reference/generated/numpy.logical_not.html """ if not is_casadi_type(x, recursive=False): return _onp.logical_not(x) else: return _cas.logic_not(x)
def logical_or(x1, x2): """ Compute the truth value of x1 OR x2 element-wise. See syntax here: https://numpy.org/doc/stable/reference/generated/numpy.logical_or.html """ if not is_casadi_type([x1, x2], recursive=True): return _onp.logical_or(x1, x2) else: return _cas.logic_or(x1, x2)
def det(A): """ Returns the determinant of the matrix A. See: https://numpy.org/doc/stable/reference/generated/numpy.linalg.det.html """ if not is_casadi_type(A): return _onp.linalg.det(A) else: return _cas.det(A)
def pinv(A): """ Returns the Moore-Penrose pseudoinverse of the matrix A. See: https://numpy.org/doc/stable/reference/generated/numpy.linalg.pinv.html """ if not is_casadi_type(A): return _onp.linalg.pinv(A) else: return _cas.pinv(A)
def full_like(a, fill_value, dtype=None, order='K', subok=True, shape=None): """Return a full array with the same shape and type as a given array.""" if not is_casadi_type(a): return _onp.full_like(a, fill_value, dtype=dtype, order=order, subok=subok, shape=shape) else: return fill_value * ones_like(a)
def any(a): # TODO add axis functionality """ Test whether any array element along a given axis evaluates to True. See syntax here: https://numpy.org/doc/stable/reference/generated/numpy.any.html """ if not is_casadi_type(a, recursive=False): return _onp.any(a) else: return _cas.logic_any(a)
def inner(x, y): """ Inner product of two arrays. See syntax here: https://numpy.org/doc/stable/reference/generated/numpy.inner.html """ if not is_casadi_type([x, y], recursive=True): return _onp.inner(x, y) else: return _cas.dot(x, y)
def dot(a, b): """ Dot product of two arrays. See syntax here: https://numpy.org/doc/stable/reference/generated/numpy.dot.html """ if not is_casadi_type([a, b], recursive=True): return _onp.dot(a, b) else: return _cas.dot(a, b)
def prod(x, axis: int = None): """ Return the product of array elements over a given axis. See syntax here: https://numpy.org/doc/stable/reference/generated/numpy.prod.html """ if not is_casadi_type(x): return _onp.prod(x, axis=axis) else: return _cas.exp(sum(_cas.log(x), axis=axis))
def transpose(a, axes=None): """ Reverse or permute the axes of an array; returns the modified array. For an array a with two axes, transpose(a) gives the matrix transpose. See syntax here: https://numpy.org/doc/stable/reference/generated/numpy.transpose.html """ if not is_casadi_type(a, recursive=False): return _onp.transpose(a, axes=axes) else: return _cas.transpose(a)
def outer(x, y): """ Compute the outer product of two vectors. See syntax here: https://numpy.org/doc/stable/reference/generated/numpy.outer.html """ if not is_casadi_type([x, y], recursive=True): return _onp.outer(x, y) else: if len(y.shape) == 1: # Force y to be transposable if it's not. y = _onp.expand_dims(y, 1) return x @ y.T
def all(a): # TODO add axis functionality """ Test whether all array elements along a given axis evaluate to True. See syntax here: https://numpy.org/doc/stable/reference/generated/numpy.all.html """ if not is_casadi_type(a, recursive=False): return _onp.all(a) else: try: return _cas.logic_all(a) except NotImplementedError: return False
def reshape(a, newshape): """Gives a new shape to an array without changing its data.""" if not is_casadi_type(a): return _onp.reshape(a, newshape) else: if isinstance(newshape, int): newshape = (newshape, 1) if len(newshape) > 2: raise ValueError( "CasADi data types are limited to no more than 2 dimensions.") return _cas.reshape(a.T, newshape[::-1]).T
def geomspace(start: float = 1., stop: float = 10., num: int = 50): """ Return numbers spaced evenly on a log scale (a geometric progression). This is similar to logspace, but with endpoints specified directly. See syntax here: https://numpy.org/doc/stable/reference/generated/numpy.geomspace.html """ if not is_casadi_type([start, stop, num], recursive=True): return _onp.geomspace(start, stop, num) else: if start <= 0 or stop <= 0: raise ValueError("Both start and stop must be positive!") return _onp.log10(10**linspace(start, stop, num))
def dot(a, b, manual=False): """ Dot product of two arrays. See syntax here: https://numpy.org/doc/stable/reference/generated/numpy.dot.html """ if manual: return sum([ai * bi for ai, bi in zip(a, b)]) if not is_casadi_type([a, b], recursive=True): return _onp.dot(a, b) else: return _cas.dot(a, b)
def inner(x, y, manual=False): """ Inner product of two arrays. See syntax here: https://numpy.org/doc/stable/reference/generated/numpy.inner.html """ if manual: return sum([xi * yi for xi, yi in zip(x, y)]) if not is_casadi_type([x, y], recursive=True): return _onp.inner(x, y) else: return _cas.dot(x, y)