.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "api/gallery/control_systems/state_space/plot_03_wonham_pole_assignment.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code or to run this example in your browser via JupyterLite. .. rst-class:: sphx-glr-example-title .. _sphx_glr_api_gallery_control_systems_state_space_plot_03_wonham_pole_assignment.py: Wonham's pole assignment (1967) =============================== For a single input, controllability lets state feedback :math:`u = -Kx` place the poles of :math:`A - BK` anywhere, and :math:`K` is unique (Ackermann's formula). Murray Wonham (1967) proved that the same holds with several inputs: the closed-loop poles can be assigned arbitrarily *if and only if* :math:`(A, B)` is controllable, even when no single input could do it (:func:`~engineeringkit.control_systems.state_feedback_poles`). A precompensator :math:`N` then scales the reference so the output settles on it (:func:`~engineeringkit.control_systems.feedforward_gain`). The first plant is an inverted pendulum on a cart, unstable in open loop, driven by the force on the cart. .. GENERATED FROM PYTHON SOURCE LINES 19-47 .. code-block:: Python import matplotlib.pyplot as plt import numpy as np from engineeringkit.control_systems import ( feedforward_gain, is_controllable, state_feedback_poles, state_space, step_response, ) M, m, ell, g = 1.0, 0.2, 0.5, 9.81 # cart mass, bob mass, rod length, gravity A = np.array([[0, 1, 0, 0], [0, 0, -m * g / M, 0], [0, 0, 0, 1], [0, 0, (M + m) * g / (M * ell), 0]]) B = np.array([[0.0], [1 / M], [0.0], [-1 / (M * ell)]]) C = np.array([[1.0, 0.0, 0.0, 0.0]]) # cart position print("open-loop poles:", np.sort(np.linalg.eigvals(A).real).round(3).tolist()) assert is_controllable(A, B) and np.linalg.eigvals(A).real.max() > 0 desired = [-2.0, -2.5, -3.0, -3.5] K = state_feedback_poles(A, B, desired) N = feedforward_gain(A, B, C, K) assert np.allclose(np.sort(np.linalg.eigvals(A - B @ K).real), sorted(desired)) t = np.linspace(0, 6, 600) _, y_cart = step_response(state_space(A - B @ K, B * N, C, [[0.0]]), t) _, y_angle = step_response(state_space(A - B @ K, B * N, [[0.0, 0.0, 1.0, 0.0]], [[0.0]]), t) print(f"K = {K.round(3).tolist()}, N = {N:.3f}, final cart position {y_cart[-1]:.4f} m") assert np.isclose(y_cart[-1], 1, atol=1e-3) and abs(y_angle[-1]) < 1e-3 .. rst-class:: sphx-glr-script-out .. code-block:: none open-loop poles: [-4.852, 0.0, 0.0, 4.852] K = [[-2.676, -4.065, -35.485, -7.532]], N = -2.676, final cart position 0.9998 m .. GENERATED FROM PYTHON SOURCE LINES 48-54 Two inputs that are each insufficient ------------------------------------- Two unstable modes, each driven by its own actuator. Neither input alone reaches both modes, but together they are controllable, so Wonham's theorem promises any pole set, here a complex pair. .. GENERATED FROM PYTHON SOURCE LINES 54-71 .. code-block:: Python A2 = np.diag([1.0, 2.0]) B2 = np.eye(2) assert not is_controllable(A2, B2[:, :1]) and not is_controllable(A2, B2[:, 1:]) assert is_controllable(A2, B2) K2 = state_feedback_poles(A2, B2, [-1 + 2j, -1 - 2j]) assert np.allclose(np.sort_complex(np.linalg.eigvals(A2 - B2 @ K2)), [-1 - 2j, -1 + 2j]) print("two-input gain:", K2.round(3).tolist()) fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=(7, 6)) ax1.plot(t, y_cart) ax1.axhline(1, color="k", lw=0.8) ax1.set(ylabel="cart position [m]", title=f"Poles placed at {desired}") ax2.plot(t, np.degrees(y_angle), color="C1") ax2.set(xlabel="time [s]", ylabel="pendulum angle [deg]") plt.show() .. image-sg:: /api/gallery/control_systems/state_space/images/sphx_glr_plot_03_wonham_pole_assignment_001.png :alt: Poles placed at [-2.0, -2.5, -3.0, -3.5] :srcset: /api/gallery/control_systems/state_space/images/sphx_glr_plot_03_wonham_pole_assignment_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none two-input gain: [[2.0, -2.0], [2.0, 3.0]] .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.029 seconds) .. _sphx_glr_download_api_gallery_control_systems_state_space_plot_03_wonham_pole_assignment.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: lite-badge .. image:: images/jupyterlite_badge_logo.svg :target: ../../../../lite/lab/index.html?path=api/gallery/control_systems/state_space/plot_03_wonham_pole_assignment.ipynb :alt: Launch JupyterLite :width: 150 px .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_03_wonham_pole_assignment.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_03_wonham_pole_assignment.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_03_wonham_pole_assignment.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_