clspc ===== .. py:module:: clspc Submodules ---------- .. toctree:: :maxdepth: 1 /autoapi/clspc/clspc/index Classes ------- .. autoapisummary:: clspc.CL_SPC Package Contents ---------------- .. py:class:: CL_SPC(past_window: int, future_window: int, forgetting_factor: float, error_cost: numpy.typing.NDArray[numpy.float64], input_cost: numpy.typing.NDArray[numpy.float64], input_delta_cost: numpy.typing.NDArray[numpy.float64], directional_forgetting: bool = True) This class implements the closed-loop subspace predictive control algorithm. :param past_window: Length of past data window. :type past_window: int :param future_window: Length of future data window. :type future_window: int :param forgetting_factor: Forgetting factor. :type forgetting_factor: float :param error_cost: Weighting of the reference error term in the cost function. :type error_cost: npt.NDArray[np.float64] :param input_cost: Weighting of the input magnitude term in the cost function. :type input_cost: npt.NDArray[np.float64] :param input_delta_cost: Weighting of the input change term in the cost function. :type input_delta_cost: npt.NDArray[np.float64] :param directional_forgetting: Enable/disable directional forgetting when calculating the Markov parameters. Defaults to True. :type directional_forgetting: bool .. py:attribute:: past_window Length of past data window. .. py:attribute:: future_window Length of future data window. .. py:attribute:: forgetting_factor Forgetting factor. .. py:attribute:: q Weighting of the reference error term. .. py:attribute:: r Weighting of the input magnitude term. .. py:attribute:: r_delta Weighting of the input change term. .. py:attribute:: directional_forgetting Enable/disable directional forgetting in the update of the Markov parameters. .. py:attribute:: s_delta :math:`S_\Delta`. Constant matrix used to calculate input change. .. py:attribute:: s_v :math:`S_v`. Constant matrix used to calculate input change. .. py:attribute:: phi :math:`\varphi_k = \begin{bmatrix} u_{k-p} & y_{k-p} & u_{k-p+1} & y_{k-p+1} & \dots & u_{k-1} & y_{k-1} & \vert & u_{k} & y_{k} \end{bmatrix}^T` .. note:: :code:`self.phi` includes :math:`y_{k}` for convenience in the code. :math:`y_{k}` is not included in :math:`\varphi_k` for calculations. .. py:attribute:: markov_parameters :math:`\hat{\Theta} = \begin{bmatrix} \Xi^{(u_{k-p})} & \Xi^{(y_{k-p})} & \Xi^{(u_{k-p+1})} & \Xi^{(y_{k-p+1})} & \dots & \Xi^{(u_{k-1})} & \Xi^{(y_{k-1})} & \vert & \Xi^{(u_{k})} \end{bmatrix}^T` .. py:attribute:: cholesky_factor Cholesky factorisation of the covariance matrix. .. py:attribute:: steps :value: 0 Step counter. .. py:method:: calculate_s_delta(f: int) -> numpy.typing.NDArray[numpy.float64] Calculate constant matrix :math:`S_\Delta`. .. math:: S_\Delta \in \mathbb{R}^{f\times f} = \begin{bmatrix} 1 & 0 & 0 & \dots & 0 \\ -1 & 1 & 0 & \dots & 0 \\ 0 & -1 & 1 & & \vdots \\ \vdots & & \ddots & \ddots & 0 \\ 0 & \dots & 0 & -1 & 1 \end{bmatrix} :param f: Length of future window. :type f: int :return: Matrix :math:`S_\Delta`. :rtype: npt.NDArray[np.float64] .. py:method:: calculate_s_v(p: int, f: int) -> numpy.typing.NDArray[numpy.float64] Calculate constant matrix :math:`S_v`. .. math:: S_v \in \mathbb{R}^{f\times p} = \begin{bmatrix} 0 & \dots & 0 & 1 \\ 0 & \dots & 0 & 0 \\ \vdots & & \vdots & \vdots \\ 0 & \dots & 0 & 0 \end{bmatrix} :param p: Length of past window. :type p: int :param f: Length of future window. :type f: int :return: Matrix :math:`S_v`. :rtype: npt.NDArray[np.float64] .. py:method:: step(uk: float, yk: float, rf: numpy.typing.NDArray[numpy.float64]) -> float Perform one control step: 1. Update :code:`self.phi` with :math:`u_k`, :math:`y_k` 2. Increase step counter If the step counter is bigger than the past window size: 3. Update Markov parameters 4. Construct :math:`\tilde{\mathcal{H}}` 5. Calculate :math:`\mathcal{G}`, :math:`\Gamma \tilde{\mathcal{K}}`, :math:`\Gamma \tilde{\mathcal{L}}` 6. Solve quadratic program 7. Return :math:`u_{k+1}`. :param uk: :math:`u_k`, most recent input. :type uk: float :param yk: :math:`y_k`, most recent output. :type yk: float :param rf: :math:`r_f`, future reference array of size :py:attr:`CL_SPC.future_window`. :type rf: npt.NDArray[np.float64] :return: :math:`u_{k+1}`, new input. :rtype: float .. py:method:: update_markov_parameters(phi: numpy.typing.NDArray[numpy.float64], yk: float) -> numpy.typing.NDArray[numpy.float64] Update Markov parameters: 1. Create pre-array 2. Perform directional forgetting step using Givens rotations 3. Perform covariance update using Givens rotations 4. Update Markov parameters :param phi: :code:`self.phi`, not including :math:`y_k`. :type phi: npt.NDArray[np.float64] :param yk: System output :math:`y_k`. :type yk: float :return: Updated Markov parameters :math:`\hat{\Theta}_k` :rtype: npt.NDArray[np.float64] .. py:method:: perform_givens_rotations(pre_array: numpy.typing.NDArray[numpy.float64], row_range: Iterable[int], r_col_range: Iterable[int], zero_col_range: Iterable[int]) -> numpy.typing.NDArray[numpy.float64] :staticmethod: Perform a set of givens rotations to zero each element :code:`bi = pre_array[row_range[i], zero_col_range[i]]` by rotating it to onto the corresponding :code:`ai = pre_array[row_range[i], r_col_range[i]]`. .. math:: \begin{bmatrix} a & b \end{bmatrix} \begin{bmatrix} c & -s \\ s & c \end{bmatrix} = \begin{bmatrix} r & 0 \end{bmatrix} .. math:: r = \sqrt{a^2 + b^2} .. math:: c = \frac{a}{r} .. math:: s = \frac{b}{r} :param pre_array: Input array containing elements to be zeroed. :type pre_array: npt.NDArray[np.float64] :param row_range: _description_ :type row_range: Iterable[int] :param r_col_range: _description_ :type r_col_range: Iterable[int] :param zero_col_range: _description_ :type zero_col_range: Iterable[int] :return: _description_ :rtype: npt.NDArray[np.float64] .. py:method:: calculate_h_tilde(markov_y: numpy.typing.NDArray[numpy.float64]) -> numpy.typing.NDArray[numpy.float64] Construct :math:`\tilde{\mathcal{H}}` from Markov parameters. .. math:: \tilde{\mathcal{H}} = \begin{bmatrix} I & 0 & \dots & 0 \\ -CK & I & & \vdots \\ \vdots & \vdots & \ddots & 0 \\ - C\tilde{A}^{f-2}K & -C\tilde{A}^{f-3}K & \dots & I \end{bmatrix} with .. math:: \tilde{\Xi}^{(y_{k-i})} = C\tilde{A}^{i-1}K :param markov_y: :math:`\tilde{\Xi}^{(y)}`, Markov parameters pertaining to the outputs. :type markov_y: npt.NDArray[np.float64] :return: :math:`\tilde{\mathcal{H}}`. :rtype: npt.NDArray[np.float64] .. py:method:: calculate_g(markov_u: numpy.typing.NDArray[numpy.float64], h_tilde: numpy.typing.NDArray[numpy.float64]) -> numpy.typing.NDArray[numpy.float64] Calculate :math:`\mathcal{G}`. First, construct :math:`\tilde{\mathcal{G}}` from Markov parameters, then calculate :math:`\mathcal{G}` using :math:`\tilde{\mathcal{H}}` taking advantage of the fact that the latter is lower triangular. .. math:: \tilde{\mathcal{G}} = \begin{bmatrix} D & 0 & \dots & 0 \\ C\tilde{B} & D & \dots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ C\tilde{A}^{f-2}\tilde{B} & C\tilde{A}^{f-3}\tilde{B} & \dots & D \end{bmatrix} with .. math:: \tilde{\Xi}^{(u_{k-i})} = \begin{cases} D, &\text{if } i = 0 \\ C\tilde{A}^{i-1}\tilde{B}, &\text{if } i > 0 \end{cases} Then .. math:: \mathcal{G} = \tilde{\mathcal{H}}^{-1} \tilde{\mathcal{G}} :param markov_u: :math:`\tilde{\Xi}^{(u)}`, Markov parameters pertaining to the inputs. :type markov_u: npt.NDArray[np.float64] :param h_tilde: :math:`\tilde{\mathcal{H}}`. :type h_tilde: npt.NDArray[np.float64] :return: :math:`\mathcal{G}`. :rtype: npt.NDArray[np.float64] .. py:method:: calculate_gamma_l(markov_u: numpy.typing.NDArray[numpy.float64], h_tilde: numpy.typing.NDArray[numpy.float64]) -> numpy.typing.NDArray[numpy.float64] Calculate :math:`\Gamma \tilde{\mathcal{L}}`. First, construct :math:`\tilde{\Gamma}\tilde{\mathcal{L}}` from Markov parameters, then calculate :math:`\Gamma \tilde{\mathcal{L}}` using :math:`\tilde{\mathcal{H}}` taking advantage of the fact that the latter is lower triangular. .. math:: \tilde{\Gamma}\tilde{\mathcal{L}} = \begin{bmatrix} \tilde{\Xi}^{(u_{k-p})} & \tilde{\Xi}^{(u_{k-p+1})} & \dots & \tilde{\Xi}^{(u_{k-p+f-1})} & \dots & \tilde{\Xi}^{(u_{k-1})} \\ 0 & \tilde{\Xi}^{(u_{k-p})} & \dots & \tilde{\Xi}^{(u_{k-p+f-2})} & \dots & \tilde{\Xi}^{(u_{k-2})} \\ \vdots & & & \vdots & \ddots & \vdots \\ 0 & & \dots & \tilde{\Xi}^{(u_{k-p})} & \dots & \tilde{\Xi}^{(u_{k-f})} \end{bmatrix} Then .. math:: \Gamma \tilde{\mathcal{L}} = \tilde{\mathcal{H}}^{-1} \tilde{\Gamma} \tilde{\mathcal{L}} :param markov_u: :math:`\tilde{\Xi}^{(u)}`, Markov parameters pertaining to the inputs. :type markov_u: npt.NDArray[np.float64] :param h_tilde: :math:`\tilde{\mathcal{H}}`. :type h_tilde: npt.NDArray[np.float64] :return: :math:`\Gamma \tilde{\mathcal{L}}`. :rtype: npt.NDArray[np.float64] .. py:method:: calculate_gamma_k(markov_y: numpy.typing.NDArray[numpy.float64], h_tilde: numpy.typing.NDArray[numpy.float64]) -> numpy.typing.NDArray[numpy.float64] Calculate :math:`\Gamma \tilde{\mathcal{K}}`. First, construct :math:`\tilde{\Gamma}\tilde{\mathcal{K}}` from Markov parameters, then calculate :math:`\Gamma \tilde{\mathcal{K}}` using :math:`\tilde{\mathcal{H}}` taking advantage of the fact that the latter is lower triangular. .. math:: \tilde{\Gamma}\tilde{\mathcal{K}} = \begin{bmatrix} \tilde{\Xi}^{(y_{k-p})} & \tilde{\Xi}^{(y_{k-p+1})} & \dots & \tilde{\Xi}^{(y_{k-p+f-1})} & \dots & \tilde{\Xi}^{(y_{k-1})} \\ 0 & \tilde{\Xi}^{(y_{k-p})} & \dots & \tilde{\Xi}^{(y_{k-p+f-2})} & \dots & \tilde{\Xi}^{(y_{k-2})} \\ \vdots & & & \vdots & \ddots & \vdots \\ 0 & & \dots & \tilde{\Xi}^{(y_{k-p})} & \dots & \tilde{\Xi}^{(y_{k-f})} \end{bmatrix} Then .. math:: \Gamma \tilde{\mathcal{K}} = \tilde{\mathcal{H}}^{-1} \tilde{\Gamma} \tilde{\mathcal{K}} :param markov_y: :math:`\tilde{\Xi}^{(y)}`, Markov parameters pertaining to the outputs. :type markov_y: npt.NDArray[np.float64] :param h_tilde: :math:`\tilde{\mathcal{H}}`. :type h_tilde: npt.NDArray[np.float64] :return: :math:`\Gamma \tilde{\mathcal{K}}`. :rtype: npt.NDArray[np.float64] .. py:method:: _calculate_gamma_kl(markov: numpy.typing.NDArray[numpy.float64], h_tilde: numpy.typing.NDArray[numpy.float64]) -> numpy.typing.NDArray[numpy.float64] Implements common functionality of :py:meth:`CL_SPC.calculate_gamma_k` and :py:meth:`CL_SPC.calculate_gamma_l`, since array structure is identical and only differs in which Markov parameters are used. :param markov: :math:`\tilde{\Xi}^{(u)}` or :math:`\tilde{\Xi}^{(y)}`, Markov parameters. :type markov: npt.NDArray[np.float64] :param h_tilde: :math:`\tilde{\mathcal{H}}`. :type h_tilde: npt.NDArray[np.float64] :return: :math:`\Gamma \tilde{\mathcal{K}}` or :math:`\Gamma \tilde{\mathcal{L}}`, depending on provided Markov parameters. :rtype: npt.NDArray[np.float64] .. py:method:: solve_qp(up: numpy.typing.NDArray[numpy.float64], yp: numpy.typing.NDArray[numpy.float64], rf: numpy.typing.NDArray[numpy.float64], g: numpy.typing.NDArray[numpy.float64], gamma_l: numpy.typing.NDArray[numpy.float64], gamma_k: numpy.typing.NDArray[numpy.float64]) -> float Solve quadratic program .. math:: J = u^T_f P u_f + q^T u_f with .. math:: P = \mathcal{G}^T Q_a \mathcal{G} + S^T_\Delta R^\Delta_a S_\Delta + R_a .. math:: q^T = 2\left(u^T_p \Gamma \tilde{\mathcal{L}} Q_a \mathcal{G} + u^T_p \Gamma \tilde{\mathcal{K}} Q_a \mathcal{G} - r^T_f Q \mathcal{G} - u^T_p S^T_v R^\Delta_a S_\Delta \right) :param up: :math:`u_p`, array of past inputs of length :py:attr:`CL_SPC.past_window`. :type up: npt.NDArray[np.float64] :param yp: :math:`y_p`, array of past outputs of length :py:attr:`CL_SPC.past_window`. :type yp: npt.NDArray[np.float64] :param rf: :math:`r_f`, array of future references of length :py:attr:`CL_SPC.future_window`. :type rf: npt.NDArray[np.float64] :param g: :math:`\mathcal{G}`. :type g: npt.NDArray[np.float64] :param gamma_l: :math:`\Gamma \tilde{\mathcal{L}}`. :type gamma_l: npt.NDArray[np.float64] :param gamma_k: :math:`\Gamma \tilde{\mathcal{K}}`. :type gamma_k: npt.NDArray[np.float64] :return: :math:`u_{k+1}`, next input. :rtype: float