graph TD
A[Start: What do you need to decide?] --> B{Need root structure?}
B -- Yes --> C[Use POLES and ZEROS]
C --> D[Use PZMAP for geometric interpretation]
B -- No --> E{Need transient behavior?}
E -- Arbitrary command --> F[Use FORCED_RESPONSE]
E -- Startup from nonzero state --> G[Use INITIAL_RESPONSE]
E -- Unit impulse characterization --> H[Use IMPULSE_RESPONSE]
D --> I{Need modal metrics?}
F --> I
G --> I
H --> I
I -- Yes --> J[Use DAMP]
J --> K{Need robustness to uncertainty/delay?}
I -- No --> K
K -- Yes --> L[Use MARGIN]
K -- No --> M[Finalize analysis report]
L --> M
Analysis
Overview
Introduction Control-system analysis is the discipline of quantifying how a dynamic system behaves before (or while) it is controlled. In mathematical terms, analysis asks: where are the system’s characteristic roots, how does it respond to disturbances and commands over time, and how much robustness margin exists before instability appears? In practical terms, analysis helps teams avoid unstable loops, oscillatory behavior, and fragile tuning choices in products ranging from power converters and robotics to HVAC and process plants. A good starting reference is the control-systems overview on Wikipedia: Control system.
For linear time-invariant (LTI) systems, most analysis starts from one of two equivalent models: a transfer function G(s) in the Laplace domain or a state-space model
\dot{x}(t) = A x(t) + B u(t), \qquad y(t) = C x(t) + D u(t).
From this model, engineers derive poles, zeros, damping ratios, natural frequencies, and frequency-domain stability metrics. These quantities are not abstract bookkeeping; they directly map to business outcomes. Poor damping can mean long settling times that reduce machine throughput. Low phase margin can mean field failures when sensor delay increases. Hidden right-half-plane zeros can limit achievable performance and force redesigns in actuators or loop architecture.
The functions in this category package these concepts into focused tools that can be used from Boardflare with Excel-style workflows but production-grade numerical methods. Under the hood, the implementations align with the Python Control Systems Library (python-control) and, where needed, SciPy numerical routines. The category includes structural tools for roots and maps—POLES, ZEROS, and PZMAP—dynamic response tools—FORCED_RESPONSE, IMPULSE_RESPONSE, and INITIAL_RESPONSE—and robustness/interpretation tools—DAMP and MARGIN.
Viewed together, these calculators support a complete “analyze before tune” workflow. Teams can first characterize intrinsic dynamics (poles/zeros and damping), then stress-test time responses under realistic inputs, then quantify robustness margins around crossover. This progression is especially important in organizations that still do most early-stage work in spreadsheets: it provides a repeatable, auditable path from rough model to engineering decision.
When to Use It Use this category when the job to be done is not merely “simulate a signal,” but “make a reliability or performance decision about a feedback system.” The analysis tools are most valuable when stakes are high (downtime, safety, quality drift, warranty risk) and when assumptions must be communicated across controls, mechanical, electrical, and operations teams.
One common scenario is servo or motion-control commissioning. Suppose a manufacturing team is increasing line speed and notices intermittent ringing on position loops. The right workflow is to compute root locations with POLES, visualize controllability constraints with PZMAP, and summarize modal behavior using DAMP. If the dominant pair shows low damping ratio, the team can predict overshoot and settling risk before changing gains. They can then run FORCED_RESPONSE with realistic trajectory commands (not just ideal steps) to validate whether jerk-limited profiles still excite problematic modes.
A second scenario is process-control robustness under uncertainty. In thermal or chemical loops, dead time and gain drift can erode stability margins over seasons, feedstock changes, or sensor replacement. Here MARGIN is the decision anchor: gain and phase margins indicate how much multiplicative change can be tolerated before instability. Once the margin is known, engineers test disturbance rejection by constructing expected load variations and evaluating FORCED_RESPONSE. If start-up transients from initial inventory or temperature gradients matter, INITIAL_RESPONSE gives the no-input decay from realistic initial states.
A third scenario is model validation and architecture screening in product development. Consider an embedded control team choosing between two actuator designs. Early models may both meet nominal bandwidth targets, but one may introduce non-minimum-phase behavior (e.g., right-half-plane zeros). ZEROS and PZMAP quickly expose this risk, while IMPULSE_RESPONSE reveals fast transient signatures linked to high-frequency structural dynamics. Teams can reject fragile architectures early, reducing downstream redesign costs.
A fourth scenario is troubleshooting unexpected oscillations in deployed systems. Field telemetry may show sustained oscillation at a narrow band. Analysts can estimate the relevant dynamic model, inspect POLES and DAMP, and then check MARGIN for crossover proximity. If oscillation appears only under certain command sequences, FORCED_RESPONSE with recorded inputs can reproduce and isolate the trigger. If oscillation appears after power-cycle or startup, INITIAL_RESPONSE is often the faster diagnostic lens.
In all these cases, the key value is decision quality. Analysis converts “it seems unstable” into measurable quantities: damping ratio, natural frequency, phase margin, gain margin, and response envelopes. Those metrics support explicit acceptance criteria (for example, minimum phase margin, maximum settling time, no right-half-plane poles) that are easier to enforce across teams than subjective waveform inspection.
How It Works The category combines time-domain simulation and frequency-domain robustness analysis around a shared LTI model. The central object is the system matrix tuple (A,B,C,D) or equivalent transfer function G(s). Once that is defined, each function extracts a different slice of system behavior.
For root structure, poles are the eigenvalues of the state matrix A (for state-space realizations) or denominator roots of G(s):
\det(sI - A) = 0.
Zeros are values of s where transmission from input to output vanishes (numerator roots in SISO transfer functions). Boardflare’s POLES and ZEROS expose these sets directly, while PZMAP combines them into a map that engineers read geometrically: left-half-plane poles indicate continuous-time stability, right-half-plane poles indicate instability, and proximity to the imaginary axis indicates weak damping or slow decay.
For mode interpretation, DAMP computes natural frequencies and damping ratios from pole locations. For a continuous pole p_i, the natural frequency is commonly represented as
\omega_{n,i} = |p_i|,
and damping ratio as
\zeta_i = -\frac{\Re(p_i)}{|p_i|}.
For discrete-time models, poles are mapped to the s-plane through s = \log(z)/\Delta t before equivalent \omega_n and \zeta are computed. This gives a consistent interpretation across continuous and sampled systems.
Time-response functions solve the state equation under different excitation assumptions. FORCED_RESPONSE computes output for arbitrary input u(t) and optional initial condition x(0)=x_0. In continuous time, this is the convolution/state-transition solution
x(t) = e^{At}x_0 + \int_0^t e^{A(t-\tau)} B u(\tau) \, d\tau,
y(t) = Cx(t) + Du(t).
INITIAL_RESPONSE is the special case with u(t)=0, isolating stored-energy decay. IMPULSE_RESPONSE approximates response to a unit-area impulse and reveals fast modal content that might be masked under smooth commands. Collectively, these functions answer different operational questions: “What happens under real commands?”, “What decays from startup bias?”, and “What are the system’s inherent transient fingerprints?”
Frequency-domain robustness is handled by MARGIN, which estimates gain margin, phase margin, and associated crossover frequencies for SISO loop transfer functions. If L(j\omega) is loop gain, gain crossover is where |L(j\omega)|=1, and phase crossover is where \angle L(j\omega)=-180^\circ. Phase margin and gain margin quantify distance from the instability boundary in complementary ways. While exact acceptable values depend on application constraints, larger margins generally imply better tolerance to modeling error, delay, and plant variation.
Under the hood, these workflows are grounded in python-control documentation, including the API references for damp, margin, forced_response, impulse_response, initial_response, poles, zeros, and pole_zero_map. Boardflare abstracts that power into calculator flows suitable for spreadsheet-first users while preserving mathematical fidelity.
A practical note on assumptions: these tools are most informative when the LTI approximation is valid in the operating region of interest. If the plant is strongly nonlinear, heavily time-varying, or saturating, the outputs remain useful as local diagnostics but should be interpreted as operating-point approximations rather than universal guarantees.
Practical Example Consider a packaging line with a motor-driven axis that must track a periodic reference while rejecting load disturbances from product contact. Operators report occasional oscillation after recipe changes and longer-than-target settling after startup. The team’s job is to decide whether retuning is enough or whether mechanical/actuator changes are needed.
Step 1 is model setup. The controls engineer identifies an LTI model around the nominal operating speed, from either first-principles linearization or system identification. At minimum, they need state-space matrices or a transfer function, plus sample time if discrete. They also define acceptance criteria: no unstable poles, minimum damping ratio for dominant modes, margin thresholds for robustness, and transient bounds for overshoot/settling.
Step 2 is structural screening. They run POLES to verify stability and inspect modal speed, then ZEROS to detect non-minimum-phase constraints that may cap achievable bandwidth. They follow with PZMAP to visually communicate these constraints to non-controls stakeholders. A pole-zero map often clarifies why “just increase gain” is not viable when a troublesome zero lies near the desired crossover region.
Step 3 is damping interpretation. Using DAMP, they identify a dominant complex pair with low damping ratio. This immediately explains ringing seen in telemetry and helps set tuning goals in physically meaningful terms. Instead of debating arbitrary PID gains, teams align on target modal damping and natural frequency ranges tied to throughput and mechanical stress limits.
Step 4 is command-path simulation. They run FORCED_RESPONSE using a realistic input profile: acceleration, cruise, dwell, and deceleration segments. This is crucial because many loops that look acceptable under idealized tests fail under piecewise or periodic commands that excite resonances. The output shows where tracking error peaks, which modes are excited, and whether performance violations cluster at phase transitions.
Step 5 is startup and disturbance diagnostics. They run INITIAL_RESPONSE from plausible nonzero initial states representing post-stop conditions (e.g., residual velocity estimate bias). If decay is too slow, startup procedures may need anti-windup resets or observer initialization changes. They also run IMPULSE_RESPONSE to expose short-lived high-frequency behavior that can trip sensors or protection logic even when average tracking looks fine.
Step 6 is robustness qualification. They compute MARGIN on the open-loop model to quantify tolerance to gain drift and additional delay from sensor filtering or network latency. If margins are too tight, the system may pass factory acceptance but fail in seasonal or plant-to-plant conditions. Margin results are then used to prioritize whether to retune compensation, reduce delay, or alter mechanical dynamics.
Step 7 is decision and communication. Instead of presenting a single chart, the team assembles a concise evidence pack: root map from PZMAP, modal table from DAMP, response overlays from FORCED_RESPONSE/INITIAL_RESPONSE/IMPULSE_RESPONSE, and robustness numbers from MARGIN. This package supports cross-functional decisions: controls can retune, mechanical can adjust stiffness/damping, and operations can set startup recipes that avoid known excitations.
Compared with spreadsheet-only manual workflows, this approach reduces rework in three ways. First, it keeps structural and dynamic checks tied to the same model, avoiding contradictory interpretations across tools. Second, it makes assumptions explicit (operating point, sample time, initial state), improving reproducibility. Third, it shortens iteration cycles: once parameters update, all analysis views can be regenerated consistently instead of rebuilt chart by chart.
How to Choose Choose the calculator based on the decision question, not by habit. A useful heuristic is to start with structure, then dynamics, then robustness. If users jump directly to waveform simulation without checking poles/zeros, they often miss fundamental limitations and waste tuning cycles.
The table below provides a concise comparison for everyday selection:
| Function | Best used for | Primary output | Strengths | Limits / cautions |
|---|---|---|---|---|
| POLES | Stability and mode location checks | Pole set in complex plane | Fast, foundational, easy to automate gates | Alone, does not show input/output cancellation effects |
| ZEROS | Detecting non-minimum-phase behavior and transmission constraints | Zero set in complex plane | Highlights performance limits and inversion risk | Needs interpretation with poles for full picture |
| PZMAP | Communicating root structure visually | Pole-zero map object/plot | Intuitive geometry for cross-functional reviews | Visualization can hide scale details if poorly normalized |
| DAMP | Converting poles into engineering metrics | Natural frequencies, damping ratios, poles | Directly maps to overshoot/ringing intuition | Damping metrics are model-dependent and local |
| FORCED_RESPONSE | Real-command simulation and disturbance replay | Time-series output/state trajectories | Most realistic for known input profiles | Requires credible input signal and time grid |
| INITIAL_RESPONSE | Startup/shutdown and stored-energy decay | Zero-input trajectory from initial state | Isolates initialization effects cleanly | Sensitive to assumed initial conditions |
| IMPULSE_RESPONSE | Fast transient/modal fingerprinting | Unit-impulse response | Reveals hidden high-frequency dynamics quickly | Physical actuators rarely apply ideal impulses directly |
| MARGIN | Robustness qualification near crossover | Gain margin, phase margin, crossover frequencies | Standard control-risk metric for uncertainty and delay | SISO open-loop interpretation; MIMO needs added analysis |
A practical sequencing pattern for most projects is:
- Run POLES and ZEROS, then inspect PZMAP.
- Summarize dominant modes with DAMP.
- Validate operational behavior via FORCED_RESPONSE, plus INITIAL_RESPONSE and IMPULSE_RESPONSE as needed.
- Qualify robustness with MARGIN before sign-off.
This sequence supports Boardflare’s dual pillar strategy: users get accessible calculator entry points for each specific task while still following an expert-level analysis narrative at the category level. The result is better tool selection, fewer tuning dead-ends, and more defensible control decisions.
DAMP
The damp function summarizes the modal behavior of a linear time-invariant system by computing the natural frequency, damping ratio, and pole location associated with each mode.
For a continuous-time pole located at s = -\sigma \pm j\omega_d, the natural frequency and damping ratio are:
\omega_n = |s| = \sqrt{\sigma^2 + \omega_d^2}
\zeta = -\frac{\Re(s)}{\omega_n}
The result is returned as a rectangular 2D array with one column per pole. The rows contain natural frequency, damping ratio, pole real part, and pole imaginary part, making the output easy to inspect in a spreadsheet.
Excel Usage
=DAMP(sysdata)
sysdata(list[list], required): The system transfer function, provided as numerator and denominator coefficients in consecutive rows.
Returns (list[list]): A 4xN array where N is the number of poles. Row 1 = Natural Frequencies (rad/s). Row 2 = Damping Ratios. Row 3 = Pole Real parts. Row 4 = Pole Imaginary parts.
Example 1: Underdamped second order system
Inputs:
| sysdata |
|---|
| 1 |
| 1 |
Excel formula:
=DAMP({1;1,2,5})
Expected output:
| Result | |
|---|---|
| 2.23607 | 2.23607 |
| 0.447214 | 0.447214 |
| -1 | -1 |
| 2 | -2 |
Example 2: Overdamped system
Inputs:
| sysdata |
|---|
| 1 |
| 1 |
Excel formula:
=DAMP({1;1,5,4})
Expected output:
| Result | |
|---|---|
| 4 | 1 |
| 1 | 1 |
| -4 | -1 |
| 0 | 0 |
Example 3: Undamped oscillator
Inputs:
| sysdata |
|---|
| 1 |
| 1 |
Excel formula:
=DAMP({1;1,0,9})
Expected output:
| Result | |
|---|---|
| 3 | 3 |
| 0 | 0 |
| 0 | 0 |
| 3 | -3 |
Python Code
Show Code
import control as ct
import numpy as np
def damp(sysdata):
"""
Compute the system's natural frequencies, damping ratios, and poles.
See: https://python-control.readthedocs.io/en/latest/generated/control.damp.html
This example function is provided as-is without any representation of accuracy.
Args:
sysdata (list[list]): The system transfer function, provided as numerator and denominator coefficients in consecutive rows.
Returns:
list[list]: A 4xN array where N is the number of poles. Row 1 = Natural Frequencies (rad/s). Row 2 = Damping Ratios. Row 3 = Pole Real parts. Row 4 = Pole Imaginary parts.
"""
try:
def flatten_to_1d(x):
if not isinstance(x, list):
return [float(x)] if str(x) != "" else []
flat = []
for row in x:
if isinstance(row, list):
for val in row:
if str(val) != "":
flat.append(float(val))
elif str(row) != "":
flat.append(float(row))
return flat
if not sysdata or len(sysdata) < 2:
return "Error: sysdata must contain numerator and denominator rows"
num = flatten_to_1d(sysdata[0])
den = flatten_to_1d(sysdata[1])
if not num or not den:
return "Error: Invalid numerator or denominator arrays"
sys = ct.tf(num, den)
wn, zeta, poles = ct.damp(sys, doprint=False)
# Prepare 4 rows: wn, zeta, poles_real, poles_imag
# If there are no poles, return empty arrays
if len(poles) == 0:
return [[""], [""], [""], [""]]
wn_list = [float(value) for value in wn]
zeta_list = [float(value) for value in zeta]
poles_real = [float(value) for value in np.real(poles)]
poles_imag = [float(value) for value in np.imag(poles)]
return [wn_list, zeta_list, poles_real, poles_imag]
except Exception as e:
return f"Error: {str(e)}"Online Calculator
FORCED_RESPONSE
The forced response computes how a linear system evolves when it is driven by a user-supplied input signal u(t) over a specified time grid.
For a continuous-time state-space model, the response satisfies the differential equation
\dot{x}(t) = A x(t) + B u(t), \quad y(t) = C x(t) + D u(t)
This wrapper accepts transfer-function numerator and denominator coefficients together with matching time and input arrays. It returns a 2D spreadsheet-friendly array whose first row contains time values and whose second row contains the corresponding system output values.
Excel Usage
=FORCED_RESPONSE(sysdata, timepts, inputs)
sysdata(list[list], required): The system transfer function, provided as numerator and denominator coefficients in consecutive rows.timepts(list[list], required): An array of evenly spaced time steps at which the input is defined.inputs(list[list], required): The input array defining the signal value at each corresponding point in timepts.
Returns (list[list]): A 2D array where the first row contains the time array values and the second row contains the system output response.
Example 1: Linear system trailing a sine wave
Inputs:
| sysdata | timepts | inputs | ||||||
|---|---|---|---|---|---|---|---|---|
| 1 | 0 | 1 | 2 | 3 | 0 | 0.841 | 0.909 | 0.141 |
| 1 |
Excel formula:
=FORCED_RESPONSE({1;1,1}, {0,1,2,3}, {0,0.841,0.909,0.141})
Expected output:
| Result | |||
|---|---|---|---|
| 0 | 1 | 2 | 3 |
| 0 | 0.309387 | 0.670446 | 0.53871 |
Python Code
Show Code
import control as ct
import numpy as np
def forced_response(sysdata, timepts, inputs):
"""
Compute the output of a linear system given an arbitrary input signal.
See: https://python-control.readthedocs.io/en/latest/generated/control.forced_response.html
This example function is provided as-is without any representation of accuracy.
Args:
sysdata (list[list]): The system transfer function, provided as numerator and denominator coefficients in consecutive rows.
timepts (list[list]): An array of evenly spaced time steps at which the input is defined.
inputs (list[list]): The input array defining the signal value at each corresponding point in timepts.
Returns:
list[list]: A 2D array where the first row contains the time array values and the second row contains the system output response.
"""
try:
def to_row_list(values):
arr = np.asarray(values)
if arr.ndim == 0:
return [float(arr)]
return [float(value) for value in arr.reshape(-1)]
def flatten_to_1d(x):
if not isinstance(x, list):
return [float(x)] if str(x) != "" else []
flat = []
for row in x:
if isinstance(row, list):
for val in row:
if str(val) != "":
flat.append(float(val))
elif str(row) != "":
flat.append(float(row))
return flat
if not sysdata or len(sysdata) < 2:
return "Error: sysdata must contain numerator and denominator rows"
num = flatten_to_1d(sysdata[0])
den = flatten_to_1d(sysdata[1])
if not num or not den:
return "Error: Invalid numerator or denominator arrays"
sys = ct.tf(num, den)
t_arr = np.array(flatten_to_1d(timepts), dtype=float)
u_arr = np.array(flatten_to_1d(inputs), dtype=float)
if len(t_arr) != len(u_arr):
return "Error: timepts and inputs arrays must have the same length"
if len(t_arr) == 0:
return "Error: Arrays cannot be empty"
resp = ct.forced_response(sys, T=t_arr, U=u_arr)
t_list = to_row_list(resp.time)
y_list = to_row_list(np.squeeze(resp.outputs))
return [t_list, y_list]
except Exception as e:
return f"Error: {str(e)}"Online Calculator
IMPULSE_RESPONSE
The impulse response measures how a linear system reacts when its input is an ideal unit impulse, represented mathematically by the Dirac delta function \delta(t).
For a transfer function G(s), the impulse response is the inverse Laplace transform of that transfer function:
g(t) = \mathcal{L}^{-1}\{G(s)\}
Because the impulse response characterizes the full time-domain dynamics of an LTI system, it is a standard tool for understanding transient behavior. This wrapper returns a 2D array whose first row is the time vector and whose second row is the response values.
Excel Usage
=IMPULSE_RESPONSE(sysdata, timepts)
sysdata(list[list], required): The system transfer function, provided as numerator and denominator coefficients in consecutive rows.timepts(list[list], optional, default: null): Optional time vector or simulation duration. If left blank, it is auto-computed based on the system’s dynamics.
Returns (list[list]): A 2D array where the first row contains the time vector values and the second row contains the system impulse response output.
Example 1: First order impulse response
Inputs:
| sysdata | timepts | |
|---|---|---|
| 1 | 0 | 1 |
| 1 |
Excel formula:
=IMPULSE_RESPONSE({1;1,1}, {0,1})
Expected output:
| Result | |
|---|---|
| 0 | 1 |
| 1 | 0.367879 |
Example 2: Lightly damped impulse
Inputs:
| sysdata | timepts | |
|---|---|---|
| 100 | 0 | 0.1 |
| 1 |
Excel formula:
=IMPULSE_RESPONSE({100;1,2,100}, {0,0.1})
Expected output:
| Result | |
|---|---|
| 0 | 0.1 |
| 0 | 7.62758 |
Python Code
Show Code
import control as ct
import numpy as np
def impulse_response(sysdata, timepts=None):
"""
Compute the impulse response for a linear system.
See: https://python-control.readthedocs.io/en/latest/generated/control.impulse_response.html
This example function is provided as-is without any representation of accuracy.
Args:
sysdata (list[list]): The system transfer function, provided as numerator and denominator coefficients in consecutive rows.
timepts (list[list], optional): Optional time vector or simulation duration. If left blank, it is auto-computed based on the system's dynamics. Default is None.
Returns:
list[list]: A 2D array where the first row contains the time vector values and the second row contains the system impulse response output.
"""
try:
def to_row_list(values):
arr = np.asarray(values)
if arr.ndim == 0:
return [float(arr)]
return [float(value) for value in arr.reshape(-1)]
def flatten_to_1d(x):
if not isinstance(x, list):
return [float(x)] if str(x) != "" else []
flat = []
for row in x:
if isinstance(row, list):
for val in row:
if str(val) != "" and val is not None:
flat.append(float(val))
elif row is not None and str(row) != "":
flat.append(float(row))
return flat
if not sysdata or len(sysdata) < 2:
return "Error: sysdata must contain numerator and denominator rows"
num = flatten_to_1d(sysdata[0])
den = flatten_to_1d(sysdata[1])
if not num or not den:
return "Error: Invalid numerator or denominator arrays"
sys = ct.tf(num, den)
t_in = flatten_to_1d(timepts) if timepts is not None else None
if t_in is not None and len(t_in) == 1:
t_in = float(t_in[0])
elif t_in and len(t_in) == 0:
t_in = None
resp = ct.impulse_response(sys, T=t_in)
t_list = to_row_list(resp.time)
y_list = to_row_list(np.squeeze(resp.outputs))
return [t_list, y_list]
except Exception as e:
return f"Error: {str(e)}"Online Calculator
INITIAL_RESPONSE
The initial response describes how a state-space system evolves from a non-zero initial state when there is no external forcing input.
For a linear system with zero input, the dynamics are governed by
\dot{x}(t) = A x(t), \quad y(t) = C x(t)
with the initial condition x(0) = X_0. This is useful for studying the natural modes of the system, including decay, oscillation, or growth due solely to the initial state. The wrapper returns a 2D array with time in the first row and one output row for each system output channel.
Excel Usage
=INITIAL_RESPONSE(A, B, C, D, initial_state, timepts)
A(list[list], required): System dynamics matrix (nxn).B(list[list], required): Control matrix (nxm).C(list[list], required): Output matrix (pxn).D(list[list], required): Feedforward/feedthrough matrix (pxm).initial_state(list[list], required): Initial condition state vector (length n).timepts(list[list], optional, default: null): Optional time vector or scalar simulation duration (in seconds).
Returns (list[list]): A 2D array where the first row contains the time vector and each subsequent row contains one output channel of the initial-condition response.
Example 1: Simple unforced decay
Inputs:
| A | B | C | D | initial_state | timepts | |||
|---|---|---|---|---|---|---|---|---|
| -1 | -2 | 5 | 6 | 8 | 0 | 1 | 0 | 0.1 |
| 3 | -4 | 7 | 0 |
Excel formula:
=INITIAL_RESPONSE({-1,-2;3,-4}, {5;7}, {6,8}, {0}, {1;0}, {0,0.1})
Expected output:
| Result | |
|---|---|
| 0 | 0.1 |
| 6 | 7.13947 |
Python Code
Show Code
import control as ct
import numpy as np
def initial_response(A, B, C, D, initial_state, timepts=None):
"""
Compute the initial condition response for a state-space system.
See: https://python-control.readthedocs.io/en/latest/generated/control.initial_response.html
This example function is provided as-is without any representation of accuracy.
Args:
A (list[list]): System dynamics matrix (nxn).
B (list[list]): Control matrix (nxm).
C (list[list]): Output matrix (pxn).
D (list[list]): Feedforward/feedthrough matrix (pxm).
initial_state (list[list]): Initial condition state vector (length n).
timepts (list[list], optional): Optional time vector or scalar simulation duration (in seconds). Default is None.
Returns:
list[list]: A 2D array where the first row contains the time vector and each subsequent row contains one output channel of the initial-condition response.
"""
try:
def to_row_list(values):
arr = np.asarray(values)
if arr.ndim == 0:
return [float(arr)]
return [float(value) for value in arr.reshape(-1)]
def flatten_to_1d(x):
if not isinstance(x, list):
return [float(x)] if str(x) != "" else []
flat = []
for row in x:
if isinstance(row, list):
for val in row:
if str(val) != "":
flat.append(float(val))
elif str(row) != "":
flat.append(float(row))
return flat
A_arr = np.array(A, dtype=float)
B_arr = np.array(B, dtype=float)
C_arr = np.array(C, dtype=float)
D_arr = np.array(D, dtype=float)
if A_arr.ndim != 2 or A_arr.shape[0] != A_arr.shape[1]:
return "Error: A must be a square 2D matrix"
sys = ct.ss(A_arr, B_arr, C_arr, D_arr)
x0_arr = np.array(flatten_to_1d(initial_state), dtype=float)
if x0_arr.shape[0] != A_arr.shape[0]:
return f"Error: Initial state vector must have length {A_arr.shape[0]} to match states"
t_in = flatten_to_1d(timepts) if timepts is not None else None
if t_in is not None and len(t_in) == 1:
t_in = float(t_in[0])
elif t_in and len(t_in) == 0:
t_in = None
resp = ct.initial_response(sys, T=t_in, X0=x0_arr)
t_list = to_row_list(resp.time)
outputs = np.asarray(resp.outputs)
if outputs.ndim <= 1:
return [t_list, to_row_list(outputs)]
return [t_list] + [to_row_list(row) for row in outputs]
except Exception as e:
return f"Error: {str(e)}"Online Calculator
MARGIN
Stability margins measure how much uncertainty a feedback system can tolerate before it becomes unstable.
The two standard quantities are gain margin and phase margin:
G_m = \text{allowable gain factor at the } -180^\circ \text{ phase crossing}
P_m = 180^\circ + \angle G(j\omega_{cp})
where \omega_{cp} is the gain crossover frequency at which |G(j\omega)| = 1. This wrapper returns a single spreadsheet row containing gain margin, phase margin, phase crossover frequency, and gain crossover frequency.
Excel Usage
=MARGIN(sysdata)
sysdata(list[list], required): The open-loop transfer function, provided as numerator and denominator coefficients in consecutive rows.
Returns (list[list]): A 1x4 horizontal array containing [Gain Margin, Phase Margin, Wcg, Wcp]. If a margin is infinite, it returns ‘inf’.
Example 1: Check margins of a stable system
Inputs:
| sysdata |
|---|
| 1 |
| 1 |
Excel formula:
=MARGIN({1;1,2,1,0})
Expected output:
| Result | |||
|---|---|---|---|
| 2 | 21.3864 | 1 | 0.682328 |
Example 2: System with infinite gain margin
Inputs:
| sysdata |
|---|
| 10 |
| 1 |
Excel formula:
=MARGIN({10;1,5,6})
Expected output:
| Result | |||
|---|---|---|---|
| inf | 102.663 | 1.95135 |
Python Code
Show Code
import control as ct
import numpy as np
def margin(sysdata):
"""
Calculate the gain and phase margins and crossover frequencies of a system.
See: https://python-control.readthedocs.io/en/latest/generated/control.margin.html
This example function is provided as-is without any representation of accuracy.
Args:
sysdata (list[list]): The open-loop transfer function, provided as numerator and denominator coefficients in consecutive rows.
Returns:
list[list]: A 1x4 horizontal array containing [Gain Margin, Phase Margin, Wcg, Wcp]. If a margin is infinite, it returns 'inf'.
"""
try:
def flatten_to_1d(x):
if not isinstance(x, list):
return [float(x)] if str(x) != "" else []
flat = []
for row in x:
if isinstance(row, list):
for val in row:
if str(val) != "":
flat.append(float(val))
elif str(row) != "":
flat.append(float(row))
return flat
if not sysdata or len(sysdata) < 2:
return "Error: sysdata must contain numerator and denominator rows"
num = flatten_to_1d(sysdata[0])
den = flatten_to_1d(sysdata[1])
if not num or not den:
return "Error: Invalid numerator or denominator arrays"
sys = ct.tf(num, den)
gm, pm, wcg, wcp = ct.margin(sys)
# Handle infs and NaNs for excel
def clean_val(v):
if v is None or np.isnan(v):
return ""
if np.isinf(v):
return "inf" if v > 0 else "-inf"
return float(v)
# Return as a 1x4 2D array
return [[clean_val(gm), clean_val(pm), clean_val(wcg), clean_val(wcp)]]
except Exception as e:
return f"Error: {str(e)}"Online Calculator
POLES
The poles of a linear time-invariant system are the roots of the denominator polynomial of its transfer function.
If the denominator is written as
a_n s^n + a_{n-1} s^{n-1} + \cdots + a_1 s + a_0 = 0
then the poles are the complex values of s that satisfy this equation. Pole locations determine whether the system is stable, oscillatory, slow, or fast. This wrapper returns the real and imaginary parts of the poles as a 2-row array.
Excel Usage
=POLES(sysdata)
sysdata(list[list], required): System data (numerator, denominator rows).
Returns (list[list]): A 2-row array: [Real, Imaginary].
Example 1: System poles
Inputs:
| sysdata |
|---|
| 1 |
| 1 |
Excel formula:
=POLES({1;1,2,1})
Expected output:
| Result | |
|---|---|
| -1 | -1 |
| 0 | 0 |
Python Code
Show Code
import control as ct
import numpy as np
def poles(sysdata):
"""
Compute the poles of a linear system.
See: https://python-control.readthedocs.io/en/latest/generated/control.poles.html
This example function is provided as-is without any representation of accuracy.
Args:
sysdata (list[list]): System data (numerator, denominator rows).
Returns:
list[list]: A 2-row array: [Real, Imaginary].
"""
try:
def flatten(x):
if x is None: return None
if not isinstance(x, list): return [float(x)]
res = []
for row in x:
if isinstance(row, list):
for v in row:
if v is not None and str(v).strip() != "":
res.append(float(v))
elif row is not None and str(row).strip() != "":
res.append(float(row))
return res if res else None
if not sysdata or len(sysdata) < 2:
return "Error: sysdata must contain numerator and denominator rows"
num = flatten(sysdata[0])
den = flatten(sysdata[1])
if not num or not den:
return "Error: Invalid numerator or denominator arrays"
sys = ct.tf(num, den)
p = ct.poles(sys)
if len(p) == 0:
return [[""], [""]]
return [
[float(value) for value in np.real(p)],
[float(value) for value in np.imag(p)],
]
except Exception as e:
return f"Error: {str(e)}"Online Calculator
PZMAP
A pole-zero map summarizes the dynamic structure of a linear system by listing the locations of its poles and zeros in the complex plane.
Poles are the roots of the denominator polynomial and zeros are the roots of the numerator polynomial:
D(s) = 0 \quad \text{for poles}, \qquad N(s) = 0 \quad \text{for zeros}
Their locations help explain transient behavior, resonance, and stability. This wrapper returns a rectangular 2D array containing pole real parts, pole imaginary parts, zero real parts, and zero imaginary parts, padded with blank cells when the number of poles and zeros differs.
Excel Usage
=PZMAP(sysdata)
sysdata(list[list], required): System data (numerator, denominator rows).
Returns (list[list]): A 4-row array: [P_Real, P_Imag, Z_Real, Z_Imag].
Example 1: PZ Map
Inputs:
| sysdata | |
|---|---|
| 1 | 2 |
| 1 | 2 |
Excel formula:
=PZMAP({1,2;1,2,5})
Expected output:
| Result | |
|---|---|
| -1 | -1 |
| 2 | -2 |
| -2 | |
| 0 |
Python Code
Show Code
import control as ct
import numpy as np
def pzmap(sysdata):
"""
Compute the poles and zeros of a linear system.
See: https://python-control.readthedocs.io/en/latest/generated/control.pzmap.html
This example function is provided as-is without any representation of accuracy.
Args:
sysdata (list[list]): System data (numerator, denominator rows).
Returns:
list[list]: A 4-row array: [P_Real, P_Imag, Z_Real, Z_Imag].
"""
try:
def flatten(x):
if x is None: return None
if not isinstance(x, list): return [float(x)]
res = []
for row in x:
if isinstance(row, list):
for v in row:
if v is not None and str(v).strip() != "":
res.append(float(v))
elif row is not None and str(row).strip() != "":
res.append(float(row))
return res if res else None
if not sysdata or len(sysdata) < 2:
return "Error: sysdata must contain numerator and denominator rows"
num = flatten(sysdata[0])
den = flatten(sysdata[1])
if not num or not den:
return "Error: Invalid numerator or denominator arrays"
sys = ct.tf(num, den)
pz = ct.pole_zero_map(sys)
poles = np.asarray(pz.poles)
zeros = np.asarray(pz.zeros)
p_re = [float(value) for value in np.real(poles)]
p_im = [float(value) for value in np.imag(poles)]
z_re = [float(value) for value in np.real(zeros)]
z_im = [float(value) for value in np.imag(zeros)]
ml = max(len(p_re), len(z_re), 1)
def pad(l, n): return l + [""] * (n - len(l))
return [pad(p_re, ml), pad(p_im, ml), pad(z_re, ml), pad(z_im, ml)]
except Exception as e:
return f"Error: {str(e)}"Online Calculator
ZEROS
The zeros of a linear time-invariant system are the roots of the numerator polynomial of its transfer function.
If the numerator is written as
b_m s^m + b_{m-1} s^{m-1} + \cdots + b_1 s + b_0 = 0
then the zeros are the complex values of s that satisfy this equation. Zeros influence transient shape, frequency response, and cancellation behavior. This wrapper returns the real and imaginary parts of the zeros as a 2-row array.
Excel Usage
=ZEROS(sysdata)
sysdata(list[list], required): System data (numerator, denominator rows).
Returns (list[list]): A 2-row array: [Real, Imaginary].
Example 1: System zeros
Inputs:
| sysdata | |
|---|---|
| 1 | 1 |
| 1 | 2 |
Excel formula:
=ZEROS({1,1;1,2,1})
Expected output:
| Result |
|---|
| -1 |
| 0 |
Python Code
Show Code
import control as ct
import numpy as np
def zeros(sysdata):
"""
Compute the zeros of a linear system.
See: https://python-control.readthedocs.io/en/latest/generated/control.zeros.html
This example function is provided as-is without any representation of accuracy.
Args:
sysdata (list[list]): System data (numerator, denominator rows).
Returns:
list[list]: A 2-row array: [Real, Imaginary].
"""
try:
def flatten(x):
if x is None: return None
if not isinstance(x, list): return [float(x)]
res = []
for row in x:
if isinstance(row, list):
for v in row:
if v is not None and str(v).strip() != "":
res.append(float(v))
elif row is not None and str(row).strip() != "":
res.append(float(row))
return res if res else None
if not sysdata or len(sysdata) < 2:
return "Error: sysdata must contain numerator and denominator rows"
num = flatten(sysdata[0])
den = flatten(sysdata[1])
if not num or not den:
return "Error: Invalid numerator or denominator arrays"
sys = ct.tf(num, den)
z = ct.zeros(sys)
if len(z) == 0:
return [[""], [""]]
return [
[float(value) for value in np.real(z)],
[float(value) for value in np.imag(z)],
]
except Exception as e:
return f"Error: {str(e)}"Online Calculator