visualization.ControlPlotter

visualization.ControlPlotter(backend='numpy', default_theme='default')

Control system analysis visualization.

Provides interactive Plotly-based plotting for control-specific analysis including eigenvalue maps, gain comparisons, and performance metrics.

Attributes

Name Type Description
backend Backend Default computational backend for array conversion
default_theme str Default plot theme to apply

Examples

Eigenvalue stability map:

>>> plotter = ControlPlotter()
>>> result = system.design_lqr(Q, R)
>>> fig = plotter.plot_eigenvalue_map(
...     result['closed_loop_eigenvalues'],
...     system_type='continuous',
...     theme='publication'
... )
>>> fig.show()

Compare LQR gains:

>>> gains = {
...     'Light Q': K1,
...     'Heavy Q': K2,
... }
>>> fig = plotter.plot_gain_comparison(
...     gains,
...     color_scheme='colorblind_safe',
...     theme='dark'
... )

Methods

Name Description
list_available_color_schemes List available color schemes.
list_available_themes List available plot themes.
plot_controllability_gramian Visualize controllability Gramian as heatmap.
plot_eigenvalue_map Plot eigenvalues with stability region.
plot_frequency_response Plot frequency response (Bode plot).
plot_gain_comparison Compare feedback gains across different designs.
plot_impulse_response Plot impulse response with performance metrics.
plot_nyquist Plot Nyquist diagram.
plot_observability_gramian Visualize observability Gramian as heatmap.
plot_riccati_convergence Plot Riccati equation solver convergence.
plot_root_locus Plot root locus (pole migration as gain varies).
plot_step_response Plot step response with performance metrics.

list_available_color_schemes

visualization.ControlPlotter.list_available_color_schemes()

List available color schemes.

Returns

Name Type Description
List[str] Available color scheme names

Examples

>>> schemes = ControlPlotter.list_available_color_schemes()
>>> print(schemes)
['plotly', 'd3', 'colorblind_safe', 'tableau', ...]

list_available_themes

visualization.ControlPlotter.list_available_themes()

List available plot themes.

Returns

Name Type Description
List[str] Available theme names

Examples

>>> themes = ControlPlotter.list_available_themes()
>>> print(themes)
['default', 'publication', 'dark', 'presentation']

plot_controllability_gramian

visualization.ControlPlotter.plot_controllability_gramian(
    W_c,
    state_names=None,
    title='Controllability Gramian',
    theme=None,
    **kwargs,
)

Visualize controllability Gramian as heatmap.

Shows coupling between states for controllability analysis.

Parameters

Name Type Description Default
W_c np.ndarray Controllability Gramian, shape (nx, nx) Symmetric positive semi-definite matrix required
state_names Optional[List[str]] Names for states None
title str Plot title 'Controllability Gramian'
theme Optional[str] Plot theme to apply Options: ‘default’, ‘publication’, ‘dark’, ‘presentation’ If None, uses self.default_theme None
**kwargs Additional arguments {}

Returns

Name Type Description
go.Figure Gramian heatmap

Examples

>>> # Compute controllability Gramian
>>> from scipy.linalg import solve_continuous_lyapunov
>>> W_c = solve_continuous_lyapunov(A, -B @ B.T)
>>>
>>> fig = plotter.plot_controllability_gramian(
...     W_c,
...     state_names=['Position', 'Velocity'],
...     theme='publication'
... )
>>> fig.show()

Notes

  • Diagonal elements: controllability of individual states
  • Off-diagonal: coupling between states
  • Small eigenvalues → difficult to control
  • Can also visualize observability Gramian W_o

plot_eigenvalue_map

visualization.ControlPlotter.plot_eigenvalue_map(
    eigenvalues,
    system_type='continuous',
    labels=None,
    title='Eigenvalue Map',
    show_stability_margin=True,
    show_stability_region=True,
    color_scheme='plotly',
    theme=None,
    **kwargs,
)

Plot eigenvalues with stability region.

Creates complex plane plot showing eigenvalue locations with stability region highlighted. Supports multiple eigenvalue sets for comparative analysis.

Parameters

Name Type Description Default
eigenvalues Union[np.ndarray, Dict[str, np.ndarray]] Complex eigenvalues in one of two formats: Format 1: Single array Shape (n,) - single set of eigenvalues Use with labels parameter for individual point labels Format 2: Dictionary (recommended for multiple sets) Dict mapping set names to eigenvalue arrays Example: {‘Open-loop’: eigs_ol, ‘Closed-loop’: eigs_cl} Each array: shape (n_eigs,) required
system_type str System type determining stability criterion: - ‘continuous’: Re(λ) < 0 (left half-plane stable) - ‘discrete’: |λ| < 1 (inside unit circle stable) 'continuous'
labels Optional[Union[List[str], str]] Labels for eigenvalues (only used if eigenvalues is array): - List[str]: Individual label per eigenvalue, length must match - str: Single label for entire set - None: Default to “Eigenvalues” Ignored if eigenvalues is Dict (uses dict keys instead) None
title str Plot title 'Eigenvalue Map'
show_stability_margin bool If True, annotate stability margin (distance to boundary) - Continuous: margin = -max(Re(λ)) - Discrete: margin = 1 - max(|λ|) True
show_stability_region bool If True, shade stability region - Continuous: shade left half-plane (green) - Discrete: draw unit circle True
color_scheme str Color scheme for eigenvalue sets Options: ‘plotly’, ‘d3’, ‘colorblind_safe’, ‘tableau’, etc. Used when plotting multiple sets (dict format) 'plotly'
theme Optional[str] Plot theme to apply Options: ‘default’, ‘publication’, ‘dark’, ‘presentation’ If None, uses self.default_theme None
**kwargs Additional customization arguments - marker_size : int - Size of eigenvalue markers (default: 12) - show_grid : bool - Show grid lines (default: True) {}

Returns

Name Type Description
go.Figure Interactive eigenvalue map with stability region

Examples

Single set of eigenvalues:

>>> lqr = system.design_lqr(Q, R)
>>> fig = plotter.plot_eigenvalue_map(
...     lqr['closed_loop_eigenvalues'],
...     system_type='continuous',
...     labels='Closed-loop (LQR)',
...     theme='publication'
... )
>>> fig.show()

Multiple sets using dictionary (recommended):

>>> eigenvalue_sets = {
...     'Open-loop': eigs_open,
...     'Closed-loop (LQR)': eigs_lqr,
...     'Closed-loop (H∞)': eigs_hinf,
... }
>>> fig = plotter.plot_eigenvalue_map(
...     eigenvalue_sets,
...     system_type='continuous',
...     color_scheme='colorblind_safe',
...     theme='publication'
... )
>>> fig.show()

Multiple sets using concatenation + labels:

>>> eigs_all = np.concatenate([eigs_ol, eigs_cl])
>>> labels_all = (
...     ['Open-loop'] * len(eigs_ol) + 
...     ['Closed-loop'] * len(eigs_cl)
... )
>>> fig = plotter.plot_eigenvalue_map(
...     eigs_all,
...     labels=labels_all,
...     system_type='continuous'
... )

Discrete system:

>>> fig = plotter.plot_eigenvalue_map(
...     discrete_lqr['closed_loop_eigenvalues'],
...     system_type='discrete',
...     theme='dark'
... )

Custom marker size and grid:

>>> fig = plotter.plot_eigenvalue_map(
...     eigenvalue_sets,
...     marker_size=15,
...     show_grid=True
... )

Notes

Stability Criteria:

  • Continuous systems: Stable if Re(λ) < 0 for all eigenvalues (left half-plane of complex plane)

  • Discrete systems: Stable if |λ| < 1 for all eigenvalues (inside unit circle)

Stability Margin:

Distance from least stable eigenvalue to stability boundary:

  • Continuous: margin = -max(Re(λ))

  • Positive: stable with margin

  • Negative: unstable

  • Larger is better (more robustness)

  • Discrete: margin = 1 - max(|λ|)

  • Positive: stable with margin

  • Negative: unstable

  • Larger is better

Visual Encoding:

  • Stable region: Shaded green/highlighted
  • Unstable region: Shaded red/unmarked
  • Stability boundary: Solid black line
  • Eigenvalues: Colored circles (one color per set)
  • Margin: Annotated with arrow

Multiple Sets:

When comparing multiple designs: - Each set gets unique color from color_scheme - Legend shows which eigenvalues belong to which design - Useful for visualizing controller tuning effects

See Also

plot_root_locus : Shows eigenvalue migration as gain varies plot_gain_comparison : Compare feedback gains

plot_frequency_response

visualization.ControlPlotter.plot_frequency_response(
    frequencies,
    magnitude,
    phase,
    title='Frequency Response (Bode Plot)',
    show_margins=True,
    theme=None,
    **kwargs,
)

Plot frequency response (Bode plot).

Creates magnitude and phase plots vs frequency, with optional gain and phase margin annotations.

Parameters

Name Type Description Default
frequencies np.ndarray Frequency points in rad/s, shape (n_freq,) required
magnitude np.ndarray Magnitude response in dB, shape (n_freq,) required
phase np.ndarray Phase response in degrees, shape (n_freq,) required
title str Plot title 'Frequency Response (Bode Plot)'
show_margins bool If True, annotate gain and phase margins True
theme Optional[str] Plot theme to apply Options: ‘default’, ‘publication’, ‘dark’, ‘presentation’ If None, uses self.default_theme None
**kwargs Additional arguments {}

Returns

Name Type Description
go.Figure Bode plot with magnitude and phase subplots

Examples

>>> # Compute frequency response
>>> from scipy import signal
>>>
>>> # Closed-loop transfer function
>>> A_cl = A - B @ K
>>> sys = signal.StateSpace(A_cl, B, C, D)
>>>
>>> # Frequency response
>>> w = np.logspace(-2, 2, 1000)  # rad/s
>>> w, H = signal.freqresp(sys, w)
>>>
>>> # Convert to dB and degrees
>>> mag_dB = 20 * np.log10(np.abs(H).flatten())
>>> phase_deg = np.angle(H, deg=True).flatten()
>>>
>>> # Plot with publication theme
>>> fig = plotter.plot_frequency_response(
...     w, mag_dB, phase_deg,
...     theme='publication'
... )
>>> fig.show()

Notes

  • Magnitude in dB: 20*log10(|H(jω)|)
  • Phase in degrees: ∠H(jω)
  • Gain margin: Amount gain can increase before instability
  • Phase margin: Amount phase can decrease before instability
  • Crossover frequencies automatically detected

plot_gain_comparison

visualization.ControlPlotter.plot_gain_comparison(
    gains,
    labels=None,
    title='Feedback Gain Comparison',
    color_scheme='plotly',
    theme=None,
    **kwargs,
)

Compare feedback gains across different designs.

Creates grouped bar chart or heatmap showing gain values for different control designs.

Parameters

Name Type Description Default
gains Dict[str, np.ndarray] Dictionary mapping design names to gain matrices Each gain: (nu, nx) array required
labels Optional[List[str]] Labels for gain entries (state names) If None, uses generic labels None
title str Plot title 'Feedback Gain Comparison'
color_scheme str Color scheme name for bar charts Options: ‘plotly’, ‘d3’, ‘colorblind_safe’, ‘tableau’, etc. Default: ‘plotly’ 'plotly'
theme Optional[str] Plot theme to apply Options: ‘default’, ‘publication’, ‘dark’, ‘presentation’ If None, uses self.default_theme None
**kwargs Additional arguments {}

Returns

Name Type Description
go.Figure Gain comparison plot

Examples

>>> # Compare different Q weights with colorblind-safe colors
>>> gains = {
...     'Q=10*I': system.design_lqr(10*np.eye(2), R)['gain'],
...     'Q=100*I': system.design_lqr(100*np.eye(2), R)['gain'],
...     'Q=1000*I': system.design_lqr(1000*np.eye(2), R)['gain'],
... }
>>> fig = plotter.plot_gain_comparison(
...     gains,
...     color_scheme='colorblind_safe',
...     theme='publication'
... )
>>> fig.show()
>>>
>>> # With state labels
>>> fig = plotter.plot_gain_comparison(
...     gains,
...     labels=['Position', 'Velocity'],
...     theme='dark'
... )

Notes

  • Each design shown as separate bar group
  • Useful for parameter studies
  • Shows effect of Q/R tuning on gains

plot_impulse_response

visualization.ControlPlotter.plot_impulse_response(
    t,
    y,
    show_metrics=True,
    title='Impulse Response',
    theme=None,
    **kwargs,
)

Plot impulse response with performance metrics.

Shows closed-loop impulse response (response to Dirac delta input) with annotations for peak, decay rate, and settling characteristics.

Parameters

Name Type Description Default
t np.ndarray Time points, shape (T,) required
y np.ndarray Output response, shape (T,) or (T, ny) required
show_metrics bool If True, annotate performance metrics True
title str Plot title 'Impulse Response'
theme Optional[str] Plot theme to apply Options: ‘default’, ‘publication’, ‘dark’, ‘presentation’ If None, uses self.default_theme None
**kwargs Additional arguments {}

Returns

Name Type Description
go.Figure Impulse response plot with metrics

Examples

>>> # Simulate closed-loop impulse response
>>> # For continuous systems: y(t) = C @ expm(A_cl*t) @ B
>>> from scipy.linalg import expm
>>> A_cl = A - B @ K
>>> t = np.linspace(0, 10, 1000)
>>> y = np.array([C @ expm(A_cl * t_i) @ B for t_i in t]).flatten()
>>>
>>> fig = plotter.plot_impulse_response(
...     t, y,
...     show_metrics=True,
...     theme='publication'
... )
>>>
>>> # For discrete systems: simulate with impulse at k=0
>>> x = np.zeros(nx)
>>> y_discrete = []
>>> for k in range(N):
...     u_k = 1.0 if k == 0 else 0.0  # Impulse at k=0
...     y_discrete.append(C @ x)
...     x = A_cl @ x + B * u_k
>>> fig = plotter.plot_impulse_response(t_discrete, np.array(y_discrete))

Notes

  • Peak value: Maximum absolute response
  • Peak time: Time to peak
  • Decay rate: Exponential decay constant (if applicable)
  • Settling time: Time to settle within 2% of zero
  • Energy: Integral of squared response (L2 norm)

plot_nyquist

visualization.ControlPlotter.plot_nyquist(
    real,
    imag,
    frequencies=None,
    title='Nyquist Plot',
    show_critical_point=True,
    theme=None,
    **kwargs,
)

Plot Nyquist diagram.

Shows frequency response in complex plane, useful for stability analysis via Nyquist stability criterion.

Parameters

Name Type Description Default
real np.ndarray Real part of frequency response, shape (n_freq,) required
imag np.ndarray Imaginary part of frequency response, shape (n_freq,) required
frequencies Optional[np.ndarray] Frequency points in rad/s (for hover info) None
title str Plot title 'Nyquist Plot'
show_critical_point bool If True, mark critical point (-1, 0j) True
theme Optional[str] Plot theme to apply Options: ‘default’, ‘publication’, ‘dark’, ‘presentation’ If None, uses self.default_theme None
**kwargs Additional arguments {}

Returns

Name Type Description
go.Figure Nyquist plot

Examples

>>> # Compute open-loop frequency response
>>> from scipy import signal
>>>
>>> # Open-loop system: G(s) = C(sI - A)^(-1)B
>>> sys_ol = signal.StateSpace(A, B, C, D)
>>>
>>> # Frequency response
>>> w = np.logspace(-2, 2, 1000)
>>> w, H = signal.freqresp(sys_ol, w)
>>> H = H.flatten()
>>>
>>> # Plot Nyquist with dark theme
>>> fig = plotter.plot_nyquist(
...     np.real(H), np.imag(H), frequencies=w,
...     theme='dark'
... )
>>> fig.show()

Notes

  • Nyquist plot: H(jω) in complex plane as ω varies
  • Critical point: (-1, 0j)
  • Stability: Number of encirclements of (-1, 0j) determines stability
  • Gain margin: Distance from curve to (-1, 0j)
  • Phase margin: Angle from curve to (-1, 0j)

plot_observability_gramian

visualization.ControlPlotter.plot_observability_gramian(
    W_o,
    state_names=None,
    title='Observability Gramian',
    theme=None,
    **kwargs,
)

Visualize observability Gramian as heatmap.

Shows coupling between states for observability analysis.

Parameters

Name Type Description Default
W_o np.ndarray Observability Gramian, shape (nx, nx) required
state_names Optional[List[str]] Names for states None
title str Plot title 'Observability Gramian'
theme Optional[str] Plot theme to apply Options: ‘default’, ‘publication’, ‘dark’, ‘presentation’ If None, uses self.default_theme None
**kwargs Additional arguments {}

Returns

Name Type Description
go.Figure Gramian heatmap

Examples

>>> # Compute observability Gramian
>>> from scipy.linalg import solve_continuous_lyapunov
>>> W_o = solve_continuous_lyapunov(A.T, -C.T @ C)
>>>
>>> fig = plotter.plot_observability_gramian(
...     W_o,
...     theme='publication'
... )

plot_riccati_convergence

visualization.ControlPlotter.plot_riccati_convergence(
    P_history,
    title='Riccati Equation Convergence',
    theme=None,
    **kwargs,
)

Plot Riccati equation solver convergence.

Shows how Riccati matrix P converges during iterative solution.

Parameters

Name Type Description Default
P_history List[np.ndarray] List of P matrices at each iteration Each P: (nx, nx) array required
title str Plot title 'Riccati Equation Convergence'
theme Optional[str] Plot theme to apply Options: ‘default’, ‘publication’, ‘dark’, ‘presentation’ If None, uses self.default_theme None
**kwargs Additional arguments {}

Returns

Name Type Description
go.Figure Convergence plot

Examples

>>> # During iterative Riccati solving
>>> P_history = []
>>> P = np.eye(nx)
>>> for iter in range(100):
...     P = riccati_iteration(P, A, B, Q, R)
...     P_history.append(P.copy())
>>>
>>> fig = plotter.plot_riccati_convergence(
...     P_history,
...     theme='publication'
... )
>>> fig.show()

Notes

  • Plots Frobenius norm vs iteration
  • Shows convergence rate
  • Useful for debugging custom solvers

plot_root_locus

visualization.ControlPlotter.plot_root_locus(
    root_locus_data,
    title='Root Locus',
    show_grid=True,
    system_type='continuous',
    color_scheme='plotly',
    theme=None,
    **kwargs,
)

Plot root locus (pole migration as gain varies).

Shows how closed-loop poles move in complex plane as control gain varies from 0 to infinity.

Parameters

Name Type Description Default
root_locus_data Dict[str, np.ndarray] Dictionary with: - ‘gains’: Array of gain values, shape (n_gains,) - ‘poles’: Array of poles, shape (n_gains, n_poles) - Optional ‘zeros’: Open-loop zeros required
title str Plot title 'Root Locus'
show_grid bool If True, show stability grid True
system_type str ‘continuous’ or ‘discrete’ (affects stability region) 'continuous'
color_scheme str Color scheme for pole branches Options: ‘plotly’, ‘d3’, ‘colorblind_safe’, ‘tableau’, etc. Default: ‘plotly’ 'plotly'
theme Optional[str] Plot theme to apply Options: ‘default’, ‘publication’, ‘dark’, ‘presentation’ If None, uses self.default_theme None
**kwargs Additional arguments {}

Returns

Name Type Description
go.Figure Root locus plot

Examples

>>> # Compute root locus for LQR as Q varies
>>> from scipy import signal
>>>
>>> gains = np.logspace(-1, 3, 50)  # Q weight values
>>> poles_list = []
>>>
>>> for q in gains:
...     lqr = system.design_lqr(q * np.eye(nx), R)
...     poles_list.append(lqr['closed_loop_eigenvalues'])
>>>
>>> root_locus_data = {
...     'gains': gains,
...     'poles': np.array(poles_list)
... }
>>>
>>> fig = plotter.plot_root_locus(
...     root_locus_data,
...     system_type='continuous',
...     color_scheme='colorblind_safe',
...     theme='publication'
... )
>>> fig.show()

Notes

  • Each branch shows one pole’s trajectory
  • Starts at open-loop pole (K=0)
  • Ends at zero or infinity (K→∞)
  • Stability: poles must stay in stable region
  • Continuous: left half-plane (Re < 0)
  • Discrete: inside unit circle (|z| < 1)

plot_step_response

visualization.ControlPlotter.plot_step_response(
    t,
    y,
    reference=1.0,
    show_metrics=True,
    title='Step Response',
    theme=None,
    **kwargs,
)

Plot step response with performance metrics.

Shows closed-loop step response with annotations for rise time, settling time, overshoot, etc.

Parameters

Name Type Description Default
t np.ndarray Time points, shape (T,) required
y np.ndarray Output response, shape (T,) or (T, ny) required
reference float Reference value (step height) 1.0
show_metrics bool If True, annotate performance metrics True
title str Plot title 'Step Response'
theme Optional[str] Plot theme to apply Options: ‘default’, ‘publication’, ‘dark’, ‘presentation’ If None, uses self.default_theme None
**kwargs Additional arguments {}

Returns

Name Type Description
go.Figure Step response plot with metrics

Examples

>>> # Simulate closed-loop step response
>>> A_cl = A - B @ K  # Closed-loop A matrix
>>> result = system.integrate(x0, u=None, A_override=A_cl, t_span=(0, 10))
>>> y = result['x'][:, 0]  # First state
>>>
>>> fig = plotter.plot_step_response(
...     result['t'],
...     y,
...     reference=1.0,
...     show_metrics=True,
...     theme='publication'
... )

Notes

  • Rise time: 10% to 90% of final value
  • Settling time: Within 2% of final value
  • Overshoot: Peak value above reference
  • Steady-state error: Final value vs reference