.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "api/gallery/control_systems/stability/plot_03_lyapunov_direct_method.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_stability_plot_03_lyapunov_direct_method.py: Lyapunov's direct method ======================== Aleksandr Lyapunov's doctoral thesis (1892) proved stability without solving the equations of motion. Find an energy-like function :math:`V(x) > 0` that decreases along every trajectory, and the equilibrium is stable. For linear dynamics :math:`\dot x = Ax`, the quadratic :math:`V = x^\mathsf{T} P x` works exactly when the Lyapunov equation :math:`A^\mathsf{T}P + PA = -Q` has a positive definite solution (:func:`~engineeringkit.control_systems.lyapunov_equation`, :func:`~engineeringkit.control_systems.lyapunov_stable`). .. GENERATED FROM PYTHON SOURCE LINES 16-28 .. code-block:: Python import matplotlib.pyplot as plt import numpy as np from scipy.linalg import expm from engineeringkit.control_systems import lyapunov_equation, lyapunov_stable A = np.array([[0.0, 1.0], [-4.0, -0.6]]) # lightly damped oscillator P = lyapunov_equation(A) print("P =\n", P.round(4)) assert lyapunov_stable(A) and np.all(np.linalg.eigvalsh(P) > 0) assert not lyapunov_stable(np.array([[0.0, 1.0], [-4.0, 0.2]])) .. rst-class:: sphx-glr-script-out .. code-block:: none P = [[4.2417 0.125 ] [0.125 1.0417]] .. GENERATED FROM PYTHON SOURCE LINES 29-31 V decreases at the rate -x'x ---------------------------- .. GENERATED FROM PYTHON SOURCE LINES 31-51 .. code-block:: Python t = np.linspace(0, 15, 3001) x = np.array([expm(A * ti) @ [1.0, 0.0] for ti in t]) V = np.einsum("ti,ij,tj->t", x, P, x) dV = np.gradient(V, t) assert np.all(np.diff(V) < 0) assert np.allclose(dV[1:-1], -np.sum(x**2, axis=1)[1:-1], atol=2e-3) fig, axes = plt.subplots(1, 2, figsize=(10, 4)) g = np.linspace(-1.2, 1.2, 201) X, Y = np.meshgrid(g, 2.4 * g) levels = P[0, 0] * X**2 + 2 * P[0, 1] * X * Y + P[1, 1] * Y**2 axes[0].contour(X, Y, levels, levels=np.geomspace(0.02, V[0], 8), cmap="viridis") axes[0].plot(x[:, 0], x[:, 1], "k", lw=1) axes[0].set(xlabel="position", ylabel="velocity", title="Trajectory crosses every level set inward") axes[1].semilogy(t, V) axes[1].set(xlabel="time [s]", ylabel="$V = x^T P x$") fig.tight_layout() plt.show() .. image-sg:: /api/gallery/control_systems/stability/images/sphx_glr_plot_03_lyapunov_direct_method_001.png :alt: Trajectory crosses every level set inward :srcset: /api/gallery/control_systems/stability/images/sphx_glr_plot_03_lyapunov_direct_method_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.280 seconds) .. _sphx_glr_download_api_gallery_control_systems_stability_plot_03_lyapunov_direct_method.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/stability/plot_03_lyapunov_direct_method.ipynb :alt: Launch JupyterLite :width: 150 px .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_03_lyapunov_direct_method.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_03_lyapunov_direct_method.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_03_lyapunov_direct_method.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_