Title: Disk margin calculations by josiahdelange · Pull Request #1146 · python-control/python-control · GitHub
Open Graph Title: Disk margin calculations by josiahdelange · Pull Request #1146 · python-control/python-control
X Title: Disk margin calculations by josiahdelange · Pull Request #1146 · python-control/python-control
Description: Am relatively new to this toolbox, have been using it here and there and found #726. This is an initial prototype Python implementation of disk margins, built on top of python-control and slycot. The code should work both for SISO and MIMO systems, the latter of which requires slycot (for SLICOT's AB13MD) to compute the upper bound of $\mu$ at each discrete frequency. The function disk_margins computes disk margins (and corresponding disk-based gain/phase margins), optionally returning the whole frequency-dependent vectors for further plotting. It's been verified against the example SISO loop transfer functions in the published paper and the "spinning satellite" MIMO example from the MathWorks documentation. I also confirmed SISO disk margins match the relevant output of existing function stability_margins corresponding to the Nyquist plot's distance to -1. All seem to match within a few significant digits, so the general behavior seems correct. Might be good to get another set of eyes to double check/test further. I tried to base as much as possible (e.g. style conventions) on existing code in control/margins.py. Example usage (see examples/disk_margin.py): import os, sys, math import numpy as np import control import math import matplotlib import matplotlib.pyplot as plt from warnings import warn import numpy as np import scipy as sp # Frequencies of interest omega = np.logspace(-1, 3, 1001) # Laplace variable s = control.tf('s') # MIMO loop transfer gain for the spinning satellite example # https://www.mathworks.com/help/robust/ug/mimo-stability-margins-for-spinning-satellite.html P = control.ss([[0, 10],[-10, 0]], np.eye(2), [[1, 10], [-10, 1]], [[0, 0],[0, 0]]) # plant C = control.ss([],[],[], [[1, -2], [0, 1]]) # controller L = P*C # output loop gain print(f"------------- Sensitivity function (S) -------------") DM, GM, PM = control.disk_margins(L, omega, skew = 1.0, returnall = True) # S-based (S) print(f"min(DM) = {min(DM)} (omega = {omega[np.argmin(DM)]})") print(f"GM = {GM[np.argmin(DM)]} dB") print(f"PM = {PM[np.argmin(DM)]} deg") print(f"min(GM) = {min(GM)} dB") print(f"min(PM) = {min(PM)} deg\n") plt.figure(3) plt.subplot(3,3,1) plt.semilogx(omega, DM, label='$\\alpha$') plt.legend() plt.title('Disk Margin') plt.grid() plt.xlim([omega[0], omega[-1]]) plt.ylim([0, 2]) plt.figure(3) plt.subplot(3,3,4) plt.semilogx(omega, GM, label='$\\gamma_{m}$') plt.ylabel('Gain Margin (dB)') plt.legend() plt.title('Gain-Only Margin') plt.grid() plt.xlim([omega[0], omega[-1]]) plt.ylim([0, 40]) plt.figure(3) plt.subplot(3,3,7) plt.semilogx(omega, PM, label='$\\phi_{m}$') plt.ylabel('Phase Margin (deg)') plt.legend() plt.title('Phase-Only Margin') plt.grid() plt.xlim([omega[0], omega[-1]]) plt.ylim([0, 90]) print(f"------------- Complementary sensitivity function (T) -------------") DM, GM, PM = control.disk_margins(L, omega, skew = -1.0, returnall = True) # T-based (T) print(f"min(DM) = {min(DM)} (omega = {omega[np.argmin(DM)]})") print(f"GM = {GM[np.argmin(DM)]} dB") print(f"PM = {PM[np.argmin(DM)]} deg") print(f"min(GM) = {min(GM)} dB") print(f"min(PM) = {min(PM)} deg\n") plt.figure(3) plt.subplot(3,3,2) plt.semilogx(omega, DM, label='$\\alpha$') plt.legend() plt.title('Disk Margin') plt.grid() plt.xlim([omega[0], omega[-1]]) plt.ylim([0, 2]) plt.figure(3) plt.subplot(3,3,5) plt.semilogx(omega, GM, label='$\\gamma_{m}$') plt.ylabel('Gain Margin (dB)') plt.legend() plt.title('Gain-Only Margin') plt.grid() plt.xlim([omega[0], omega[-1]]) plt.ylim([0, 40]) plt.figure(3) plt.subplot(3,3,8) plt.semilogx(omega, PM, label='$\\phi_{m}$') plt.ylabel('Phase Margin (deg)') plt.legend() plt.title('Phase-Only Margin') plt.grid() plt.xlim([omega[0], omega[-1]]) plt.ylim([0, 90]) print(f"------------- Balanced sensitivity function (S - T) -------------") DM, GM, PM = control.disk_margins(L, omega, skew = 0.0, returnall = True) # balanced (S - T) print(f"min(DM) = {min(DM)} (omega = {omega[np.argmin(DM)]})") print(f"GM = {GM[np.argmin(DM)]} dB") print(f"PM = {PM[np.argmin(DM)]} deg") print(f"min(GM) = {min(GM)} dB") print(f"min(PM) = {min(PM)} deg\n") plt.figure(3) plt.subplot(3,3,3) plt.semilogx(omega, DM, label='$\\alpha$') plt.legend() plt.title('Disk Margin') plt.grid() plt.xlim([omega[0], omega[-1]]) plt.ylim([0, 2]) plt.figure(3) plt.subplot(3,3,6) plt.semilogx(omega, GM, label='$\\gamma_{m}$') plt.ylabel('Gain Margin (dB)') plt.legend() plt.title('Gain-Only Margin') plt.grid() plt.xlim([omega[0], omega[-1]]) plt.ylim([0, 40]) plt.figure(3) plt.subplot(3,3,9) plt.semilogx(omega, PM, label='$\\phi_{m}$') plt.ylabel('Phase Margin (deg)') plt.legend() plt.title('Phase-Only Margin') plt.grid() plt.xlim([omega[0], omega[-1]]) plt.ylim([0, 90]) Output: ------------- Sensitivity function (S) ------------- min(DM) = 0.3697398698410271 (omega = 0.1) GM = 2.732761944304522 dB PM = 21.30709883385843 deg min(GM) = 2.732761944304522 dB min(PM) = 21.30709883385843 deg ------------- Complementary sensitivity function (T) ------------- min(DM) = 0.3683035923739884 (omega = 0.1) GM = 2.7236493433653735 dB PM = 21.223368586091564 deg min(GM) = 2.7236493433653735 dB min(PM) = 21.223368586091564 deg ------------- Balanced sensitivity function (S - T) ------------- min(DM) = 0.3769872636944355 (omega = 0.1) GM = 3.3140985364257256 dB PM = 21.349285542574695 deg min(GM) = 3.3140985364257256 dB min(PM) = 21.349285542574695 deg The example script also shows how to plot the allowable/stable region of gain and phase variations which will not destabilize the loop, in a local function plot_allowable_region, e.g. . . . DM_plot = [] DM_plot.append(control.disk_margins(L, omega, skew = -1.0)[0]) # T-based (T) DM_plot.append(control.disk_margins(L, omega, skew = 0.0)[0]) # balanced (S - T) DM_plot.append(control.disk_margins(L, omega, skew = 1.0)[0]) # S-based (S) plt.figure(30) plot_allowable_region(DM_plot, skew = [-1.0, 0.0, 1.0])
Open Graph Description: Am relatively new to this toolbox, have been using it here and there and found #726. This is an initial prototype Python implementation of disk margins, built on top of python-control and slycot. ...
X Description: Am relatively new to this toolbox, have been using it here and there and found #726. This is an initial prototype Python implementation of disk margins, built on top of python-control and slycot. ...
Opengraph URL: https://github.com/python-control/python-control/pull/1146
X: @github
Domain: github.com
| route-pattern | /:user_id/:repository/pull/:id/checks(.:format) |
| route-controller | pull_requests |
| route-action | checks |
| fetch-nonce | v2:306aeebb-9c4a-ccc9-4481-30b7f280bbf0 |
| current-catalog-service-hash | 87dc3bc62d9b466312751bfd5f889726f4f1337bdff4e8be7da7c93d6c00a25a |
| request-id | BB34:1DF41E:7628BC:A3E6CE:697A6BFF |
| html-safe-nonce | 8e0a43caa0c88f2fb0144037778925e0c968623882047fd9501063897906cdc2 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJCQjM0OjFERjQxRTo3NjI4QkM6QTNFNkNFOjY5N0E2QkZGIiwidmlzaXRvcl9pZCI6IjgyNzE5NTk1NTkwNDg0MjQ0NDciLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 2cf55e151650467c20e53fd4da191e75aa7adad5132b460220e39f6651ea6275 |
| hovercard-subject-tag | pull_request:2478921952 |
| github-keyboard-shortcuts | repository,pull-request-list,pull-request-conversation,pull-request-files-changed,checks,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/python-control/python-control/pull/1146/checks |
| twitter:image | https://avatars.githubusercontent.com/u/7785578?s=400&v=4 |
| twitter:card | summary_large_image |
| og:image | https://avatars.githubusercontent.com/u/7785578?s=400&v=4 |
| og:image:alt | Am relatively new to this toolbox, have been using it here and there and found #726. This is an initial prototype Python implementation of disk margins, built on top of python-control and slycot. ... |
| og:site_name | GitHub |
| og:type | object |
| hostname | github.com |
| expected-hostname | github.com |
| None | 4f4c032790588250388c2e36a0e1ea776c0a3f887d37d994e1ac3d7ea79d34ef |
| turbo-cache-control | no-cache |
| go-import | github.com/python-control/python-control git https://github.com/python-control/python-control.git |
| octolytics-dimension-user_id | 2285872 |
| octolytics-dimension-user_login | python-control |
| octolytics-dimension-repository_id | 22791752 |
| octolytics-dimension-repository_nwo | python-control/python-control |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 22791752 |
| octolytics-dimension-repository_network_root_nwo | python-control/python-control |
| turbo-body-classes | logged-out env-production page-responsive full-width full-width-p-0 |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 06b3a5a9a6214e1e06e508d250d778cbaf17c577 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width