ROOT_LOCUS
The root locus explores how the closed-loop poles of a system shift as a scalar loop gain k is varied.
The pole locations are defined by the characteristic equation
1 + k G(s) = 0
for the open-loop transfer function G(s). This wrapper uses the legacy numeric return mode of control.root_locus(..., plot=False) and returns a spreadsheet-friendly 2D array whose first row contains the evaluated gains and whose subsequent rows contain one root-locus branch per row as complex-number strings across the gain sweep.
Excel Usage
=ROOT_LOCUS(sysdata, gains)
sysdata(list[list], required): The system transfer function (numerator and denominator rows).gains(list[list], optional, default: null): Optional list of gains to evaluate. If left blank, they are auto-computed.
Returns (list[list]): A 2D array where the first row contains the evaluated gains and each subsequent row contains one branch of closed-loop poles as complex-number strings.
Example 1: Root locus for a second order system
Inputs:
| sysdata | gains | ||
|---|---|---|---|
| 1 | 0.1 | 1 | 10 |
| 1 |
Excel formula:
=ROOT_LOCUS({1;1,2,0}, {0.1,1,10})
Expected output:
| Result | ||
|---|---|---|
| 0.1 | 1 | 10 |
| (-1.9486832980505138+0j) | (-1+0j) | (-0.9999999999999998-3j) |
| (-0.051316701949486204+0j) | (-1+0j) | (-0.9999999999999998+3j) |
Example 2: Root locus with explicit gain sweep for first-order plant
Inputs:
| sysdata | gains | ||||
|---|---|---|---|---|---|
| 1 | 0 | 0.5 | 1 | 2 | 5 |
| 1 |
Excel formula:
=ROOT_LOCUS({1;1,1}, {0,0.5,1,2,5})
Expected output:
| Result | ||||
|---|---|---|---|---|
| 0 | 0.5 | 1 | 2 | 5 |
| (-1+0j) | (-1.5+0j) | (-2+0j) | (-3+0j) | (-6+0j) |
Example 3: Root locus at selected gain points
Inputs:
| sysdata | gains | ||
|---|---|---|---|
| 1 | 0 | 2 | 8 |
| 1 |
Excel formula:
=ROOT_LOCUS({1;1,3,2}, {0,2,8})
Expected output:
| Result | ||
|---|---|---|
| 0 | 2 | 8 |
| (-2+0j) | (-1.5-1.3228756555322951j) | (-1.5-2.7838821814150108j) |
| (-1+0j) | (-1.5+1.3228756555322951j) | (-1.5+2.7838821814150108j) |
Example 4: Root locus for integrator with pole
Inputs:
| sysdata | gains | ||
|---|---|---|---|
| 1 | 0.2 | 1 | 5 |
| 1 |
Excel formula:
=ROOT_LOCUS({1;1,1,0}, {0.2,1,5})
Expected output:
| Result | ||
|---|---|---|
| 0.2 | 1 | 5 |
| (-0.7236067977499789+0j) | (-0.5-0.8660254037844385j) | (-0.4999999999999999-2.1794494717703365j) |
| (-0.27639320225002106+0j) | (-0.5+0.8660254037844385j) | (-0.4999999999999999+2.1794494717703365j) |
Python Code
Show Code
import control as ct
import numpy as np
def root_locus(sysdata, gains=None):
"""
Calculate the root locus for an LTI system.
See: https://python-control.readthedocs.io/en/latest/generated/control.root_locus.html
This example function is provided as-is without any representation of accuracy.
Args:
sysdata (list[list]): The system transfer function (numerator and denominator rows).
gains (list[list], optional): Optional list of gains to evaluate. If left blank, they are auto-computed. Default is None.
Returns:
list[list]: A 2D array where the first row contains the evaluated gains and each subsequent row contains one branch of closed-loop poles as complex-number strings.
"""
try:
if not sysdata or len(sysdata) < 2:
return "Error: sysdata must contain numerator and denominator rows"
num = []
num_source = sysdata[0]
if not isinstance(num_source, list):
num = [float(num_source)]
else:
num_rows = [num_source] if len(num_source) > 0 and not isinstance(num_source[0], list) else num_source
for row in num_rows:
if isinstance(row, list):
for val in row:
if val is not None and str(val) != "":
num.append(float(val))
elif row is not None and str(row) != "":
num.append(float(row))
den = []
den_source = sysdata[1]
if not isinstance(den_source, list):
den = [float(den_source)]
else:
den_rows = [den_source] if len(den_source) > 0 and not isinstance(den_source[0], list) else den_source
for row in den_rows:
if isinstance(row, list):
for val in row:
if val is not None and str(val) != "":
den.append(float(val))
elif row is not None and str(row) != "":
den.append(float(row))
sys = ct.tf(num, den)
g_in = None
if gains is not None:
g_in = []
if not isinstance(gains, list):
g_in = [float(gains)]
else:
gain_rows = [gains] if len(gains) > 0 and not isinstance(gains[0], list) else gains
for row in gain_rows:
if isinstance(row, list):
for val in row:
if val is not None and str(val) != "":
g_in.append(float(val))
elif row is not None and str(row) != "":
g_in.append(float(row))
if len(g_in) == 0:
g_in = None
roots, gains_out = ct.root_locus(sys, gains=g_in, plot=False)
res = [gains_out.tolist()]
for i in range(roots.shape[1]):
res.append([str(complex(r)) for r in roots[:, i]])
return res
except Exception as e:
return f"Error: {str(e)}"