{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Decoupling\n", "Given a general multivariable system with transfer function matrix $G_p$, a decoupler attempts to combine with the system to form a diagonal whole.\n", "\n", "" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import sympy\n", "sympy.init_printing()" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$$\\left[\\begin{matrix}G_{p11} & G_{p12}\\\\G_{p21} & G_{p22}\\end{matrix}\\right]$$" ], "text/plain": [ "⎡Gₚ₁₁ Gₚ₁₂⎤\n", "⎢ ⎥\n", "⎣Gₚ₂₁ Gₚ₂₂⎦" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "G_p11, G_p12, G_p21, G_p22 = sympy.symbols('G_p11, G_p12, G_p21, G_p22')\n", "G_p = sympy.Matrix([[G_p11, G_p12],[G_p21, G_p22]])\n", "G_p" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1. Inverse-based\n", "Wouldn't it be nice if the system didn't have interaction? In other words, we could choose $T$ such that we have this system with the same diagonal elements as the original system but zeros in the off diagonals." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$$\\left[\\begin{matrix}G_{p11} & 0\\\\0 & G_{p22}\\end{matrix}\\right]$$" ], "text/plain": [ "⎡Gₚ₁₁ 0 ⎤\n", "⎢ ⎥\n", "⎣ 0 Gₚ₂₂⎦" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "G_s = sympy.Matrix([[G_p11, 0],[0, G_p22]])\n", "G_s" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Recalling that the combination of $T$ and $G_p$ in series is $G_p T$, we can solve for the decoupler directly\n", "\n", "$$ G_P T = G_s \\therefore T = G_P^{-1} G_s $$" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$$\\left[\\begin{matrix}\\frac{G_{p11} G_{p22}}{G_{p11} G_{p22} - G_{p12} G_{p21}} & - \\frac{G_{p12} G_{p22}}{G_{p11} G_{p22} - G_{p12} G_{p21}}\\\\- \\frac{G_{p11} G_{p21}}{G_{p11} G_{p22} - G_{p12} G_{p21}} & \\frac{G_{p11} G_{p22}}{G_{p11} G_{p22} - G_{p12} G_{p21}}\\end{matrix}\\right]$$" ], "text/plain": [ "⎡ Gₚ₁₁⋅Gₚ₂₂ -Gₚ₁₂⋅Gₚ₂₂ ⎤\n", "⎢───────────────────── ─────────────────────⎥\n", "⎢Gₚ₁₁⋅Gₚ₂₂ - Gₚ₁₂⋅Gₚ₂₁ Gₚ₁₁⋅Gₚ₂₂ - Gₚ₁₂⋅Gₚ₂₁⎥\n", "⎢ ⎥\n", "⎢ -Gₚ₁₁⋅Gₚ₂₁ Gₚ₁₁⋅Gₚ₂₂ ⎥\n", "⎢───────────────────── ─────────────────────⎥\n", "⎣Gₚ₁₁⋅Gₚ₂₂ - Gₚ₁₂⋅Gₚ₂₁ Gₚ₁₁⋅Gₚ₂₂ - Gₚ₁₂⋅Gₚ₂₁⎦" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "T = G_p.inv()*G_s\n", "T" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's see if that worked:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$$\\left[\\begin{matrix}G_{p11} & 0\\\\0 & G_{p22}\\end{matrix}\\right]$$" ], "text/plain": [ "⎡Gₚ₁₁ 0 ⎤\n", "⎢ ⎥\n", "⎣ 0 Gₚ₂₂⎦" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "G_pT = G_p*T\n", "sympy.simplify(G_pT)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pros:\n", "\n", "* Controller design can be based on open loop model\n", "* Apparent dynamics (what the controller sees) are simple\n", "\n", "Cons:\n", "\n", "* T is often not physically realisable\n", "* T is complicated" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2. Zero off-diagonals\n", "A more common strategy is to solve directly for the off-diagonal elements of set equal to zero.\n", "\n", "So we just want \n", "\n", "$$G_P T = \\begin{bmatrix}d_1&0\\\\0&d_2\\end{bmatrix}$$\n", "\n", "Note the difference between the first method and this one - here we are not specifying the diagonal at all, we just want the off-diagonals to be zero." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "T21, T12 = sympy.symbols('T21, T12')\n", "T = sympy.Matrix([[1, T12], \n", " [T21, 1]])\n", "\n", "wantdiagonal = G_p*T\n", "\n", "sol = sympy.solve([wantdiagonal[0,1], wantdiagonal[1, 0]], [T21, T12])" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$$\\left[\\begin{matrix}1 & - \\frac{G_{p12}}{G_{p11}}\\\\- \\frac{G_{p21}}{G_{p22}} & 1\\end{matrix}\\right]$$" ], "text/plain": [ "⎡ -Gₚ₁₂ ⎤\n", "⎢ 1 ──────⎥\n", "⎢ Gₚ₁₁ ⎥\n", "⎢ ⎥\n", "⎢-Gₚ₂₁ ⎥\n", "⎢────── 1 ⎥\n", "⎣ Gₚ₂₂ ⎦" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "T.subs(sol)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So this is the classic/traditional decoupler shown in the diagram (with unit passthrough on the diagonals). This changes the transfer function the controller \"sees\" to" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$$\\left[\\begin{matrix}G_{p11} - \\frac{G_{p12} G_{p21}}{G_{p22}} & 0\\\\0 & G_{p22} - \\frac{G_{p12} G_{p21}}{G_{p11}}\\end{matrix}\\right]$$" ], "text/plain": [ "⎡ Gₚ₁₂⋅Gₚ₂₁ ⎤\n", "⎢Gₚ₁₁ - ───────── 0 ⎥\n", "⎢ Gₚ₂₂ ⎥\n", "⎢ ⎥\n", "⎢ Gₚ₁₂⋅Gₚ₂₁⎥\n", "⎢ 0 Gₚ₂₂ - ─────────⎥\n", "⎣ Gₚ₁₁ ⎦" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "G_p*T.subs(sol)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pros:\n", "\n", "* Relatively simple design process\n", "* Less complicated decoupler than the inverse-based method\n", "\n", "Cons:\n", "\n", "* Apparent plant may be higher order than the actual plant\n", "* Still requires an inverse, may not be physically realisable (but more likely than method 1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3. Adjugate method\n", "The adjugate (previously calld the adjoint) of a matrix will also diagonalise a system" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$$\\left[\\begin{matrix}G_{p22} & - G_{p12}\\\\- G_{p21} & G_{p11}\\end{matrix}\\right]$$" ], "text/plain": [ "⎡Gₚ₂₂ -Gₚ₁₂⎤\n", "⎢ ⎥\n", "⎣-Gₚ₂₁ Gₚ₁₁ ⎦" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "T = G_p.adjugate()\n", "T" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$$\\left[\\begin{matrix}G_{p11} G_{p22} - G_{p12} G_{p21} & 0\\\\0 & G_{p11} G_{p22} - G_{p12} G_{p21}\\end{matrix}\\right]$$" ], "text/plain": [ "⎡Gₚ₁₁⋅Gₚ₂₂ - Gₚ₁₂⋅Gₚ₂₁ 0 ⎤\n", "⎢ ⎥\n", "⎣ 0 Gₚ₁₁⋅Gₚ₂₂ - Gₚ₁₂⋅Gₚ₂₁⎦" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "G_p*T" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pros:\n", " \n", "* Decoupler guaranteed to be physically realisable because it only requires \"forward\" models of the system.\n", "\n", "Cons:\n", "\n", "* Apparent plant now much higher order (look at the products in the $G_pT$ expression)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.3" } }, "nbformat": 4, "nbformat_minor": 2 }