.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "api/gallery/control_systems/nonlinear/plot_01_andronov_phase_plane.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_nonlinear_plot_01_andronov_phase_plane.py: Andronov, Vitt and Khaikin's phase plane (1937) =============================================== Aleksandr Andronov's school in Gorky studied self-sustained oscillations by eliminating time and drawing the motion of :math:`\ddot x = f(x, \dot x)` as a curve in the plane :math:`(x, \dot x)` (:func:`~engineeringkit.control_systems.phase_portrait`). A relay servo, :math:`\ddot x = -M\,\mathrm{sign}(x)`, is not linear anywhere, yet its phase plane is read at a glance. Each half plane holds parabolas :math:`\dot x^2/2 + M|x| = E`, which join into closed orbits: the servo oscillates for ever at the amplitude it started with. Feeding back the rate, :math:`\ddot x = -M\,\mathrm{sign}(x + \tau\dot x)`, tilts the switching line. Every orbit then loses energy at each switch, reaches the line and *slides* along it, :math:`\dot x = -x/\tau`, into the origin. .. GENERATED FROM PYTHON SOURCE LINES 19-28 .. code-block:: Python import matplotlib.pyplot as plt import numpy as np from engineeringkit.control_systems import phase_portrait M = 1.0 t = np.linspace(0, 12, 12001) fig, axs = plt.subplots(1, 2, figsize=(10, 4.2), sharey=True) .. image-sg:: /api/gallery/control_systems/nonlinear/images/sphx_glr_plot_01_andronov_phase_plane_001.png :alt: plot 01 andronov phase plane :srcset: /api/gallery/control_systems/nonlinear/images/sphx_glr_plot_01_andronov_phase_plane_001.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 29-31 Pure relay: a family of closed orbits ------------------------------------- .. GENERATED FROM PYTHON SOURCE LINES 31-40 .. code-block:: Python for x0 in (0.4, 0.7, 1.0): orbit = phase_portrait(lambda x, v: -M * np.sign(x), x0, 0.0, t) energy = orbit.v**2 / 2 + M * np.abs(orbit.x) print(f"x0 = {x0}: energy varies by {np.ptp(energy):.1e} around {M * x0}") assert np.allclose(energy, M * x0, rtol=2e-3) orbit.plot(ax=axs[0], field=x0 == 1.0) axs[0].set(title=r"$\ddot x = -M\,\mathrm{sign}(x)$: closed orbits") .. rst-class:: sphx-glr-script-out .. code-block:: none x0 = 0.4: energy varies by 6.0e-04 around 0.4 x0 = 0.7: energy varies by 7.9e-04 around 0.7 x0 = 1.0: energy varies by 9.4e-04 around 1.0 [Text(0.5, 1.0, '$\\ddot x = -M\\,\\mathrm{sign}(x)$: closed orbits')] .. GENERATED FROM PYTHON SOURCE LINES 41-43 Rate feedback: switching, then sliding -------------------------------------- .. GENERATED FROM PYTHON SOURCE LINES 43-64 .. code-block:: Python tau = 0.4 slide = phase_portrait(lambda x, v: -M * np.sign(x + tau * v), 1.0, 0.0, t) energy = slide.v**2 / 2 + M * np.abs(slide.x) assert np.all(np.diff(energy) <= 1e-6) # loses energy at each switch, gains none (to the RK4 step error) # Once on the switching line the motion obeys x + tau v = 0, so x decays as exp(-t / tau). on_line = np.abs(slide.x + tau * slide.v) < 2e-3 start = np.flatnonzero(~on_line)[-1] + 1 later = (t > t[start]) & (t < t[start] + 2) print(f"sliding from t = {t[start]:.2f} s at x = {slide.x[start]:.4f}") assert np.allclose(slide.x[later], slide.x[start] * np.exp(-(t[later] - t[start]) / tau), atol=1e-4) slide.plot(ax=axs[1]) line = np.linspace(-1.2, 1.2, 2) axs[1].plot(line, -line / tau, "r--", lw=1, label=r"switching line $x + \tau\dot x = 0$") axs[1].legend(loc="lower left", fontsize=8) axs[1].set(title=rf"$\ddot x = -M\,\mathrm{{sign}}(x + {tau}\,\dot x)$: sliding to rest") print("final state with rate feedback:", slide.summary()) assert abs(slide.x[-1]) < 1e-6 and abs(slide.v[-1]) < 1e-6 fig.tight_layout() plt.show() .. rst-class:: sphx-glr-script-out .. code-block:: none sliding from t = 2.41 s at x = -0.1088 final state with rate feedback: {'final_x': 1.430924759760228e-15, 'final_v': 3.0964814046186007e-16, 'max_abs_x': 1.0} .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.999 seconds) .. _sphx_glr_download_api_gallery_control_systems_nonlinear_plot_01_andronov_phase_plane.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/nonlinear/plot_01_andronov_phase_plane.ipynb :alt: Launch JupyterLite :width: 150 px .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_01_andronov_phase_plane.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_01_andronov_phase_plane.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_01_andronov_phase_plane.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_