Source code for engineeringkit.control_systems.systems.root_locus
r"""Evans's root locus: closed-loop poles as the loop gain varies.
For unity negative feedback around :math:`K\,N(s)/D(s)`, the closed-loop
poles are the roots of :math:`D(s) + K N(s) = 0`. Evans (1948) gave rules
for sketching how they move from the open-loop poles (:math:`K = 0`) toward
the open-loop zeros or infinity.
"""
from __future__ import annotations
import numpy as np
__all__ = ["root_locus"]
[docs]
def root_locus(num, den, gains):
"""Closed-loop characteristic roots of :math:`D(s) + K N(s)` for each gain.
Parameters
----------
num, den : array_like
Open-loop numerator and denominator, descending powers of :math:`s`.
The open loop must be strictly proper so the number of roots is
constant.
gains : array_like
Nonempty vector of loop gains :math:`K` [-].
Returns
-------
ndarray of complex
Shape ``(len(gains), len(den) - 1)``; row ``i`` holds the roots for
``gains[i]``. Roots within a row are not tracked along branches.
Notes
-----
See :doc:`/guides/control_systems`.
References
----------
W. R. Evans, "Graphical analysis of control systems," *Trans. AIEE* 67
(1948) 547-551.
Examples
--------
>>> np.sort(root_locus([1], [1, 3, 2], [0.25])[0].real).tolist()
[-1.5, -1.5]
"""
num, den = np.asarray(num, float), np.asarray(den, float)
gains = np.asarray(gains, float)
if (
num.ndim != 1
or den.ndim != 1
or not num.size
or len(den) < 2
or len(num) >= len(den)
or den[0] == 0
or gains.ndim != 1
or not gains.size
or not all(np.all(np.isfinite(v)) for v in (num, den, gains))
):
raise ValueError("requires finite strictly proper polynomials and a nonempty gain vector")
return np.asarray([np.roots(np.polyadd(den, k * num)) for k in gains])