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)}"

Online Calculator

The system transfer function (numerator and denominator rows).
Optional list of gains to evaluate. If left blank, they are auto-computed.