예제 #1
0
    def include_equations(self, *args, **kwargs):
        if callable(getattr(super(), 'include_equations', None)):
            super().include_equations(*args, **kwargs)

        ode = kwargs.pop('ode', None)
        x = kwargs.pop('x', None)
        if ode is None and x is not None:
            raise ValueError("`ode` is None but `x` is not None")

        # if is in the list form
        if isinstance(ode, collections.abc.Sequence):
            ode = vertcat(*ode)

        if isinstance(x, collections.abc.Sequence):
            x = vertcat(*x)

        # if ode was passed but not x, try to guess the x
        if x is None and ode is not None:
            # Check if None are all sequential, ortherwise we don't know who it belongs
            first_none = list(self._ode.values()).index(None)
            if not all(eq is None
                       for eq in islice(self._ode.values(), 0, first_none)):
                raise ValueError(
                    "ODE should be inserted on the equation form or in the list form."
                    "You can't mix both without explicit passing the states associated with the equation."
                )
            x = vertcat(*list(self._ode.keys())[first_none:first_none +
                                                ode.numel()])

        if len(args) > 0 and ode is None:
            x = SX([])
            ode = SX([])

        # get ode and x from equality equations
        for eq in args:
            if isinstance(eq, EqualityEquation):
                if isinstance(eq.lhs, Derivative):
                    ode = vertcat(ode, eq.rhs)
                    x = vertcat(x, eq.lhs.inner)

        # actually include the equations
        if ode is not None and ode.numel() > 0:
            for x_i in vertcat(x).nz:
                if self._ode[x_i] is not None:
                    raise Warning(
                        f'State "{x_i}" already had an ODE associated, overriding it!'
                    )
            ode_dict = dict(self._ode)
            ode_dict.update({x_i: ode[ind] for ind, x_i in enumerate(x.nz)})
            self._ode = ode_dict
예제 #2
0
class ParameterMixin:
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.p = SX([])
        self.theta = SX([])

    @property
    def n_p(self):
        return self.p.numel()

    @property
    def n_theta(self):
        return self.theta.numel()

    @property
    def p_names(self):
        return [self.p[i].name() for i in range(self.n_p)]

    @property
    def theta_names(self):
        return [self.theta[i].name() for i in range(self.n_theta)]

    def create_parameter(self, name="p", size=1):
        """
        Create a new parameter name "name" and size "size"

        :param name: str
        :param size: int
        :return:
        """
        if callable(getattr(self, 'name_variable', None)):
            name = self.name_variable(name)

        new_p = SX.sym(name, size)
        self.include_parameter(vec(new_p))
        return new_p

    def create_theta(self, name="theta", size=1):
        """
        Create a new parameter name "name" and size "size"

        :param name: str
        :param size: int
        :return:
        """
        if callable(getattr(self, 'name_variable', None)):
            name = self.name_variable(name)

        new_theta = SX.sym(name, size)
        self.include_theta(vec(new_theta))
        return new_theta

    def include_parameter(self, p):
        self.p = vertcat(self.p, p)

    def include_theta(self, theta):
        self.theta = vertcat(self.theta, theta)

    def remove_parameter(self, var):
        self.p = remove_variables_from_vector(var, self.p)

    def remove_theta(self, var):
        self.theta = remove_variables_from_vector(var, self.theta)
예제 #3
0
class ControlMixin:
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.u = SX([])

        self._parametrized_controls = []
        self.u_par = vertcat(self.u)
        self.u_expr = vertcat(self.u)

    @property
    def n_u(self):
        return self.u.numel()

    @property
    def n_u_par(self):
        return self.u_par.numel()

    @property
    def u_names(self):
        return [self.u[i].name() for i in range(self.n_u)]

    def create_control(self, name="u", size=1):
        """
        Create a new control variable name "name" and size "size".
        Size can be an int or a tuple (e.g. (2,2)). However, the new control variable will be vectorized (casadi.vec)
        to be included in the control vector (model.u).

        :param name: str
        :param size: int
        :return:
        """
        if callable(getattr(self, 'name_variable', None)):
            name = self.name_variable(name)

        new_u = SX.sym(name, size)
        self.include_control(vec(new_u))
        return new_u

    def include_control(self, var):
        self.u = vertcat(self.u, var)
        self.u_expr = vertcat(self.u_expr, var)
        self.u_par = vertcat(self.u_par, var)

    def remove_control(self, var):
        self.u_expr = remove_variables_from_vector_by_indices(
            find_variables_indices_in_vector(var, self.u), self.u_expr)
        self.u = remove_variables_from_vector(var, self.u)
        self.u_par = remove_variables_from_vector(var, self.u_par)

    def replace_variable(self, original, replacement):
        if isinstance(original, list):
            original = vertcat(*original)
        if isinstance(replacement, list):
            replacement = vertcat(*replacement)

        if not original.numel() == replacement.numel():
            raise ValueError(
                "Original and replacement must have the same number of elements!"
                "original.numel()={}, replacement.numel()={}".format(
                    original.numel(), replacement.numel()))

        if callable(getattr(super(), 'replace_variable', None)):
            super().replace_variable(original, replacement)

        #  self.u_par = substitute(self.u_par, original, replacement)
        self.u_expr = substitute(self.u_expr, original, replacement)

    def parametrize_control(self, u, expr, u_par=None):
        """
            Parametrize a control variables so it is a function of a set of parameters or other model variables.

        :param list|casadi.SX u:
        :param list|casadi.SX expr:
        :param list|casadi.SX u_par:
        """
        # input check
        if isinstance(u, list):
            u = vertcat(*u)
        if isinstance(u_par, list):
            u_par = vertcat(*u_par)
        if isinstance(expr, list):
            expr = vertcat(*expr)

        if not u.numel() == expr.numel():
            raise ValueError(
                "Passed control and parametrization expression does not have same size. "
                "u ({}) and expr ({})".format(u.numel(), expr.numel()))

        # Check and register the control parametrization.
        for u_i in u.nz:
            if self.control_is_parametrized(u_i):
                raise ValueError(
                    f'The control "{u_i}" is already parametrized.')
            # to get have a new memory address
            self._parametrized_controls = self._parametrized_controls + [u_i]

        # Remove u from u_par if they are going to be parametrized
        self.u_par = remove_variables_from_vector(u, self.u_par)
        if u_par is not None:
            self.u_par = vertcat(self.u_par, u_par)

        # Replace u by expr into the system
        self.replace_variable(u, expr)

    def create_input(self, name="u", size=1):
        """
        Same as the "model.create_control" function.
        Create a new control/input variable name "name" and size "size".
        Size can be an int or a tuple (e.g. (2,2)). However, the new control variable will be vectorized (casadi.vec)
        to be included in the control vector (model.u).

        :param name: str
        :param size: int
        :return:
        """
        return self.create_control(name, size)

    def control_is_parametrized(self, u):
        """
            Check if  the control "u" is parametrized

        :param casadi.SX u:
        :rtype bool:
        """
        u = vertcat(u)
        if not u.numel() == 1:
            raise ValueError(
                'The parameter "u" is expected to be of size 1x1, given: {}x{}'
                .format(*u.shape))
        if any([
                is_equal(u, parametrized_u)
                for parametrized_u in self._parametrized_controls
        ]):
            return True
        return False
예제 #4
0
class StateMixin:
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.x = SX([])
        self.x_0 = SX([])
        self._ode = dict()

    @property
    def n_x(self):
        return self.x.numel()

    @property
    def x_names(self):
        return [self.x[i].name() for i in range(self.n_x)]

    @property
    def ode(self):
        try:
            return vertcat(
                *[val for val in self._ode.values() if val is not None])
        except NotImplementedError:
            return SX.zeros(0, 1)

    def create_state(self, name="x", size=1):
        """
        Create a new state with the name "name" and size "size".
        Size can be an int or a tuple (e.g. (2,2)). However, the new state will be vectorized (casadi.vec) to be
        included in the state vector (model.x).

        :param name: str
        :param size: int|tuple
        :return:
        """
        if callable(getattr(self, 'name_variable', None)):
            name = self.name_variable(name)

        new_x = SX.sym(name, size)
        new_x_0 = SX.sym(name + "_0_sym", size)
        self.include_state(vec(new_x), ode=None, x_0=vec(new_x_0))
        return new_x

    def include_state(self, var, ode=None, x_0=None):
        n_x = var.numel()
        self.x = vertcat(self.x, var)

        if x_0 is None:
            x_0 = vertcat(*[SX.sym(var_i.name()) for var_i in var.nz])
        self.x_0 = vertcat(self.x_0, x_0)

        # crate entry for included state
        for ind, x_i in enumerate(var.nz):
            if x_i in self._ode:
                raise ValueError(f'State "{x_i}" already in this model')
            self._ode[x_i] = None
        if ode is not None:
            self.include_equations(ode=ode, x=var)
        return x_0

    def remove_state(self, var, eq=None):
        self.x = remove_variables_from_vector(var, self.x)

        for x_i in var.nz:
            del self._ode[x_i]

    def replace_variable(self, original, replacement):
        if isinstance(original, list):
            original = vertcat(*original)
        if isinstance(replacement, list):
            replacement = vertcat(*replacement)

        if not original.numel() == replacement.numel():
            raise ValueError(
                "Original and replacement must have the same number of elements!"
                "original.numel()={}, replacement.numel()={}".format(
                    original.numel(), replacement.numel()))

        if callable(getattr(super(), 'replace_variable', None)):
            super().replace_variable(original, replacement)

        if original.numel() > 0:
            for x_i, x_i_eq in self._ode.items():
                self._ode[x_i] = substitute(x_i_eq, original, replacement)

    def include_equations(self, *args, **kwargs):
        if callable(getattr(super(), 'include_equations', None)):
            super().include_equations(*args, **kwargs)

        ode = kwargs.pop('ode', None)
        x = kwargs.pop('x', None)
        if ode is None and x is not None:
            raise ValueError("`ode` is None but `x` is not None")

        # if is in the list form
        if isinstance(ode, collections.abc.Sequence):
            ode = vertcat(*ode)

        if isinstance(x, collections.abc.Sequence):
            x = vertcat(*x)

        # if ode was passed but not x, try to guess the x
        if x is None and ode is not None:
            # Check if None are all sequential, ortherwise we don't know who it belongs
            first_none = list(self._ode.values()).index(None)
            if not all(eq is None
                       for eq in islice(self._ode.values(), 0, first_none)):
                raise ValueError(
                    "ODE should be inserted on the equation form or in the list form."
                    "You can't mix both without explicit passing the states associated with the equation."
                )
            x = vertcat(*list(self._ode.keys())[first_none:first_none +
                                                ode.numel()])

        if len(args) > 0 and ode is None:
            x = SX([])
            ode = SX([])

        # get ode and x from equality equations
        for eq in args:
            if isinstance(eq, EqualityEquation):
                if isinstance(eq.lhs, Derivative):
                    ode = vertcat(ode, eq.rhs)
                    x = vertcat(x, eq.lhs.inner)

        # actually include the equations
        if ode is not None and ode.numel() > 0:
            for x_i in vertcat(x).nz:
                if self._ode[x_i] is not None:
                    raise Warning(
                        f'State "{x_i}" already had an ODE associated, overriding it!'
                    )
            ode_dict = dict(self._ode)
            ode_dict.update({x_i: ode[ind] for ind, x_i in enumerate(x.nz)})
            self._ode = ode_dict
예제 #5
0
class AlgebraicMixin:
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.alg = SX([])
        self.y = SX([])

    @property
    def n_y(self):
        return self.y.numel()

    @property
    def y_names(self):
        return [self.y[i].name() for i in range(self.n_y)]

    def create_algebraic_variable(self, name="y", size=1):
        """
        Create a new algebraic variable with the name "name" and size "size".
        Size can be an int or a tuple (e.g. (2,2)). However, the new algebraic variable will be vectorized (casadi.vec)
        to be included in the algebraic vector (model.y).

        :param str name:
        :param int||tuple size:
        :return:
        """
        if callable(getattr(self, 'name_variable', None)):
            name = self.name_variable(name)

        new_y = SX.sym(name, size)
        self.include_algebraic(vec(new_y))
        return new_y

    def find_algebraic_variable(self,
                                x,
                                u,
                                guess=None,
                                t=0.0,
                                p=None,
                                theta_value=None,
                                rootfinder_options=None):
        if guess is None:
            guess = [1] * self.n_y
        if rootfinder_options is None:
            rootfinder_options = dict(
                nlpsol="ipopt",
                nlpsol_options=config.SOLVER_OPTIONS["nlpsol_options"])
        if p is None:
            p = []
        if theta_value is None:
            theta_value = []

        # replace known variables
        alg = self.alg
        known_var = vertcat(self.t, self.x, self.u, self.p, self.theta)
        known_var_values = vertcat(t, x, u, p, theta_value)
        alg = substitute(alg, known_var, known_var_values)

        f_alg = Function("f_alg", [self.y], [alg])

        rf = rootfinder("rf_algebraic_variable", "nlpsol", f_alg,
                        rootfinder_options)
        res = rf(guess)
        return res

    def include_algebraic(self, var, alg=None):
        self.y = vertcat(self.y, var)
        self.include_equations(alg=alg)

    def remove_algebraic(self, var, eq=None):
        self.y = remove_variables_from_vector(var, self.y)
        if eq is not None:
            self.alg = remove_variables_from_vector(eq, self.alg)

    def replace_variable(self, original, replacement):
        if isinstance(original, list):
            original = vertcat(*original)
        if isinstance(replacement, list):
            replacement = vertcat(*replacement)

        if not original.numel() == replacement.numel():
            raise ValueError(
                "Original and replacement must have the same number of elements!"
                "original.numel()={}, replacement.numel()={}".format(
                    original.numel(), replacement.numel()))

        if callable(getattr(super(), 'replace_variable', None)):
            super().replace_variable(original, replacement)

        self.alg = substitute(self.alg, original, replacement)

    def include_equations(self, *args, **kwargs):
        if callable(getattr(super(), 'include_equations', None)):
            super().include_equations(*args, **kwargs)

        alg = kwargs.pop('alg', None)

        if len(args) > 0 and alg is None:
            alg = SX([])

        # in case a list of equations `y == x + u` has been passed
        for eq in args:
            if is_equality(eq):
                alg = vertcat(alg, eq.dep(0) - eq.dep(1))

        if isinstance(alg, collections.abc.Sequence):
            alg = vertcat(*alg)

        if alg is not None:
            self.alg = vertcat(self.alg, alg)