Skip to content

Commit 8b269bf

Browse files
committed
add plot_gallery to compare plotting results between builds
1 parent 6be8f48 commit 8b269bf

1 file changed

Lines changed: 161 additions & 0 deletions

File tree

examples/plot_gallery.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# plot_gallery.py - different types of plots for comparing versions
2+
# RMM, 19 Jun 2024
3+
#
4+
# This file collects together some of the more interesting plots that can
5+
# be generated by python-control and puts them into a PDF file that can be
6+
# used to compare what things look like between different versions of the
7+
# library. It is mainly intended for uses by developers to make sure there
8+
# are no unexpected changes in plot formats, but also has some interest
9+
# examples of htings you can plot.
10+
11+
import os
12+
import sys
13+
from math import pi
14+
15+
import matplotlib.pyplot as plt
16+
import numpy as np
17+
18+
import control as ct
19+
20+
# Don't save figures if we are running CI tests
21+
savefigs = 'PYCONTROL_TEST_EXAMPLES' not in os.environ
22+
if savefigs:
23+
# Create a pdf file for storing the results
24+
from matplotlib.backends.backend_pdf import PdfPages
25+
from datetime import date
26+
git_info = os.popen('git describe').read().strip()
27+
pdf = PdfPages(
28+
f'plot_gallery-{git_info}-{date.today().isoformat()}.pdf')
29+
30+
# Context manager to handle plotting
31+
class create_figure(object):
32+
def __init__(self, name):
33+
self.name = name
34+
def __enter__(self):
35+
self.fig = plt.figure()
36+
print(f"Generating {self.name} as Figure {self.fig.number}")
37+
return self.fig
38+
def __exit__(self, type, value, traceback):
39+
if type is not None:
40+
print(f"Exception: {type=}, {value=}, {traceback=}")
41+
if savefigs:
42+
pdf.savefig()
43+
if hasattr(sys, 'ps1'):
44+
# Show the figures on the screen
45+
plt.show(block=False)
46+
else:
47+
plt.close()
48+
49+
# Define systems to use throughout
50+
sys1 = ct.tf([1], [1, 2, 1], name='sys1')
51+
sys2 = ct.tf([1, 0.2], [1, 1, 3, 1, 1], name='sys2')
52+
sys_mimo1 = ct.tf2ss(
53+
[[[1], [0.1]], [[0.2], [1]]],
54+
[[[1, 0.6, 1], [1, 1, 1]], [[1, 0.4, 1], [1, 2, 1]]], name="sys_mimo1")
55+
sys_mimo2 = ct.tf2ss(
56+
[[[1], [0.1]], [[0.2], [1]]],
57+
[[[1, 0.2, 1], [1, 24, 22, 5]], [[1, 4, 16, 21], [1, 0.1]]],
58+
name="sys_mimo2")
59+
sys_frd = ct.frd(
60+
[[np.array([10 + 0j, 5 - 5j, 1 - 1j, 0.5 - 1j, -.1j]),
61+
np.array([1j, 0.5 - 0.5j, -0.5, 0.1 - 0.1j, -.05j]) * 0.1],
62+
[np.array([10 + 0j, -20j, -10, 2j, 1]),
63+
np.array([10 + 0j, 5 - 5j, 1 - 1j, 0.5 - 1j, -.1j]) * 0.01]],
64+
np.logspace(-2, 2, 5))
65+
sys_frd.name = 'frd' # For backward compatibility
66+
67+
# Close all existing figures
68+
plt.close('all')
69+
70+
# bode
71+
with create_figure("Bode plot"):
72+
try:
73+
ct.bode_plot([sys_mimo1, sys_mimo2])
74+
except AttributeError:
75+
print(" - falling back to earlier method")
76+
plt.clf()
77+
ct.bode_plot(sys_mimo1)
78+
ct.bode_plot(sys_mimo2)
79+
80+
# describing function
81+
with create_figure("Describing function plot"):
82+
H = ct.tf([1], [1, 2, 2, 1]) * 8
83+
F = ct.descfcn.saturation_nonlinearity(1)
84+
amp = np.linspace(1, 4, 10)
85+
ct.describing_function_response(H, F, amp).plot()
86+
87+
# nichols
88+
with create_figure("Nichols chart"):
89+
response = ct.frequency_response([sys1, sys2])
90+
ct.nichols_plot(response)
91+
92+
# nyquist
93+
with create_figure("Nyquist plot"):
94+
ct.nyquist_plot([sys1, sys2])
95+
96+
# phase plane
97+
with create_figure("Phase plane plot"):
98+
def invpend_update(t, x, u, params):
99+
m, l, b, g = params['m'], params['l'], params['b'], params['g']
100+
return [x[1], -b/m * x[1] + (g * l / m) * np.sin(x[0]) + u[0]/m]
101+
invpend = ct.nlsys(invpend_update, states=2, inputs=1, name='invpend')
102+
ct.phase_plane_plot(
103+
invpend, [-2*pi, 2*pi, -2, 2], 5,
104+
gridtype='meshgrid', gridspec=[5, 8], arrows=3,
105+
plot_separatrices={'gridspec': [12, 9]},
106+
params={'m': 1, 'l': 1, 'b': 0.2, 'g': 1})
107+
108+
# pole zero map
109+
with create_figure("Pole/zero map"):
110+
T = ct.tf(
111+
[-9.0250000e-01, -4.7200750e+01, -8.6812900e+02,
112+
+5.6261850e+03, +2.1258472e+05, +8.4724600e+05,
113+
+1.0192000e+06, +2.3520000e+05],
114+
[9.02500000e-03, 9.92862812e-01, 4.96974094e+01,
115+
1.35705659e+03, 2.09294163e+04, 1.64898435e+05,
116+
6.54572220e+05, 1.25274600e+06, 1.02420000e+06,
117+
2.35200000e+05], name='T')
118+
ct.pole_zero_plot([T, sys2])
119+
120+
# root locus
121+
with create_figure("Root locus plot") as fig:
122+
ax1, ax2 = fig.subplots(2, 1)
123+
sys1 = ct.tf([1, 2], [1, 2, 3], name='sys1')
124+
sys2 = ct.tf([1, 0.2], [1, 1, 3, 1, 1], name='sys2')
125+
ct.root_locus_plot([sys1, sys2], grid=True, ax=ax1)
126+
ct.root_locus_plot([sys1, sys2], grid=False, ax=ax2)
127+
print(" -- BUG: should have 2 x 1 array of plots")
128+
129+
# sisotool
130+
with create_figure("sisotool"):
131+
s = ct.tf('s')
132+
H = (s+0.3)/(s**4 + 4*s**3 + 6.25*s**2)
133+
ct.sisotool(H)
134+
135+
# step response
136+
with create_figure("step response") as fig:
137+
try:
138+
ct.step_response([sys_mimo1, sys_mimo2]).plot()
139+
except ValueError:
140+
print(" - falling back to earlier method")
141+
fig.clf()
142+
ct.step_response(sys_mimo1).plot()
143+
ct.step_response(sys_mimo2).plot()
144+
145+
# time response
146+
with create_figure("time response"):
147+
timepts = np.linspace(0, 10)
148+
149+
U = np.vstack([np.sin(timepts), np.cos(2*timepts)])
150+
resp1 = ct.input_output_response(sys_mimo1, timepts, U)
151+
152+
U = np.vstack([np.cos(2*timepts), np.sin(timepts)])
153+
resp2 = ct.input_output_response(sys_mimo1, timepts, U)
154+
155+
resp = ct.combine_time_responses(
156+
[resp1, resp2], trace_labels=["resp1", "resp2"])
157+
resp.plot(transpose=True)
158+
159+
# Show the figures if running in interactive mode
160+
if savefigs:
161+
pdf.close()

0 commit comments

Comments
 (0)