|
14 | 14 |
|
15 | 15 | from control import ControlMIMONotImplemented, FrequencyResponseData, \ |
16 | 16 | StateSpace, TransferFunction, margin, phase_crossover_frequencies, \ |
17 | | - stability_margins |
| 17 | + stability_margins, disk_margins, tf, ss |
18 | 18 |
|
19 | 19 | s = TransferFunction.s |
20 | 20 |
|
@@ -372,3 +372,107 @@ def test_stability_margins_discrete(cnum, cden, dt, |
372 | 372 | else: |
373 | 373 | out = stability_margins(tf) |
374 | 374 | assert_allclose(out, ref, rtol=rtol) |
| 375 | + |
| 376 | +def test_siso_disk_margin(): |
| 377 | + # Frequencies of interest |
| 378 | + omega = np.logspace(-1, 2, 1001) |
| 379 | + |
| 380 | + # Laplace variable |
| 381 | + s = tf('s') |
| 382 | + |
| 383 | + # Loop transfer function |
| 384 | + L = tf(25, [1, 10, 10, 10]) |
| 385 | + |
| 386 | + # Balanced (S - T) disk-based stability margins |
| 387 | + DM, DGM, DPM = disk_margins(L, omega, skew = 0.0) |
| 388 | + assert_allclose([DM], [0.46], atol = 0.1) # disk margin of 0.46 |
| 389 | + assert_allclose([DGM], [4.05], atol = 0.1) # disk-based gain margin of 4.05 dB |
| 390 | + assert_allclose([DPM], [25.8], atol = 0.1) # disk-based phase margin of 25.8 deg |
| 391 | + |
| 392 | + # For SISO systems, the S-based (S) disk margin should match the third output |
| 393 | + # of existing library "stability_margins", i.e., minimum distance from the |
| 394 | + # Nyquist plot to -1. |
| 395 | + _, _, SM = stability_margins(L)[:3] |
| 396 | + DM = disk_margins(L, omega, skew = 1.0)[0] |
| 397 | + assert_allclose([DM], [SM], atol = 0.01) |
| 398 | + |
| 399 | +def test_mimo_disk_margin(): |
| 400 | + # Frequencies of interest |
| 401 | + omega = np.logspace(-1, 3, 1001) |
| 402 | + |
| 403 | + # Laplace variable |
| 404 | + s = tf('s') |
| 405 | + |
| 406 | + # Loop transfer gain |
| 407 | + P = ss([[0, 10],[-10, 0]], np.eye(2), [[1, 10], [-10, 1]], [[0, 0],[0, 0]]) # plant |
| 408 | + K = ss([],[],[], [[1, -2], [0, 1]]) # controller |
| 409 | + Lo = P*K # loop transfer function, broken at plant output |
| 410 | + Li = K*P # loop transfer function, broken at plant input |
| 411 | + |
| 412 | + # Balanced (S - T) disk-based stability margins at plant output |
| 413 | + DMo, DGMo, DPMo = disk_margins(Lo, omega, skew = 0.0) |
| 414 | + assert_allclose([DMo], [0.3754], atol = 0.1) # disk margin of 0.3754 |
| 415 | + assert_allclose([DGMo], [3.3], atol = 0.1) # disk-based gain margin of 3.3 dB |
| 416 | + assert_allclose([DPMo], [21.26], atol = 0.1) # disk-based phase margin of 21.26 deg |
| 417 | + |
| 418 | + # Balanced (S - T) disk-based stability margins at plant input |
| 419 | + DMi, DGMi, DPMi = disk_margins(Li, omega, skew = 0.0) |
| 420 | + assert_allclose([DMi], [0.3754], atol = 0.1) # disk margin of 0.3754 |
| 421 | + assert_allclose([DGMi], [3.3], atol = 0.1) # disk-based gain margin of 3.3 dB |
| 422 | + assert_allclose([DPMi], [21.26], atol = 0.1) # disk-based phase margin of 21.26 deg |
| 423 | + |
| 424 | +def test_siso_disk_margin_return_all(): |
| 425 | + # Frequencies of interest |
| 426 | + omega = np.logspace(-1, 2, 1001) |
| 427 | + |
| 428 | + # Laplace variable |
| 429 | + s = tf('s') |
| 430 | + |
| 431 | + # Loop transfer function |
| 432 | + L = tf(25, [1, 10, 10, 10]) |
| 433 | + |
| 434 | + # Balanced (S - T) disk-based stability margins |
| 435 | + DM, DGM, DPM = disk_margins(L, omega, skew = 0.0, returnall = True) |
| 436 | + assert_allclose([omega[np.argmin(DM)]], [1.94],\ |
| 437 | + atol = 0.01) # sensitivity peak at 1.94 rad/s |
| 438 | + assert_allclose([min(DM)], [0.46], atol = 0.1) # disk margin of 0.46 |
| 439 | + assert_allclose([DGM[np.argmin(DM)]], [4.05],\ |
| 440 | + atol = 0.1) # disk-based gain margin of 4.05 dB |
| 441 | + assert_allclose([DPM[np.argmin(DM)]], [25.8],\ |
| 442 | + atol = 0.1) # disk-based phase margin of 25.8 deg |
| 443 | + |
| 444 | +def test_mimo_disk_margin_return_all(): |
| 445 | + # Frequencies of interest |
| 446 | + omega = np.logspace(-1, 3, 1001) |
| 447 | + |
| 448 | + # Laplace variable |
| 449 | + s = tf('s') |
| 450 | + |
| 451 | + # Loop transfer gain |
| 452 | + P = ss([[0, 10],[-10, 0]], np.eye(2),\ |
| 453 | + [[1, 10], [-10, 1]], [[0, 0],[0, 0]]) # plant |
| 454 | + K = ss([],[],[], [[1, -2], [0, 1]]) # controller |
| 455 | + Lo = P*K # loop transfer function, broken at plant output |
| 456 | + Li = K*P # loop transfer function, broken at plant input |
| 457 | + |
| 458 | + # Balanced (S - T) disk-based stability margins at plant output |
| 459 | + DMo, DGMo, DPMo = disk_margins(Lo, omega, skew = 0.0, returnall = True) |
| 460 | + assert_allclose([omega[np.argmin(DMo)]], [omega[0]],\ |
| 461 | + atol = 0.01) # sensitivity peak at 0 rad/s (or smallest provided) |
| 462 | + assert_allclose([min(DMo)], [0.3754], atol = 0.1) # disk margin of 0.3754 |
| 463 | + assert_allclose([DGMo[np.argmin(DMo)]], [3.3],\ |
| 464 | + atol = 0.1) # disk-based gain margin of 3.3 dB |
| 465 | + assert_allclose([DPMo[np.argmin(DMo)]], [21.26],\ |
| 466 | + atol = 0.1) # disk-based phase margin of 21.26 deg |
| 467 | + |
| 468 | + # Balanced (S - T) disk-based stability margins at plant input |
| 469 | + DMi, DGMi, DPMi = disk_margins(Li, omega, skew = 0.0, returnall = True) |
| 470 | + assert_allclose([omega[np.argmin(DMi)]], [omega[0]],\ |
| 471 | + atol = 0.01) # sensitivity peak at 0 rad/s (or smallest provided) |
| 472 | + assert_allclose([min(DMi)], [0.3754],\ |
| 473 | + atol = 0.1) # disk margin of 0.3754 |
| 474 | + assert_allclose([DGMi[np.argmin(DMi)]], [3.3],\ |
| 475 | + atol = 0.1) # disk-based gain margin of 3.3 dB |
| 476 | + assert_allclose([DPMi[np.argmin(DMi)]], [21.26],\ |
| 477 | + atol = 0.1) # disk-based phase margin of 21.26 deg |
| 478 | + |
0 commit comments