Documentation
¶
Overview ¶
Package controltheory provides classical linear control-systems primitives implemented with the Go standard library only.
The package models single-input single-output (SISO) linear time-invariant systems in two equivalent representations:
- Transfer functions G(s) = N(s)/D(s), where N and D are real polynomials stored as Poly values.
- State-space realizations (A, B, C, D) stored as StateSpace values.
On top of these representations it offers block-diagram algebra (series, parallel, feedback), pole/zero extraction, time-domain step and impulse responses computed by numerical integration, controllability and observability analysis, Routh-Hurwitz stability testing, frequency-domain Bode and Nyquist sampling with gain and phase margins, and a discrete PID controller.
Polynomials use the ascending-power convention: a Poly value p has p[i] as the coefficient of s^i, so Poly{2, 3, 1} represents 2 + 3s + s^2.
Every routine is deterministic and depends on nothing outside the standard library.
Index ¶
- func DampingRatio(pole complex128) float64
- func IsHurwitzStable(p Poly) bool
- func LogSpace(startExp, endExp float64, n int) []float64
- func MagnitudeDB(value complex128) float64
- func NaturalFrequency(pole complex128) float64
- func NumRightHalfPlaneRoots(p Poly) int
- func PhaseDeg(value complex128) float64
- type BodePoint
- type NyquistPoint
- type PIDController
- type Poly
- func (p Poly) Add(q Poly) Poly
- func (p Poly) Degree() int
- func (p Poly) Derivative() Poly
- func (p Poly) DivMod(q Poly) (quotient, remainder Poly)
- func (p Poly) Eval(x float64) float64
- func (p Poly) EvalComplex(z complex128) complex128
- func (p Poly) LeadingCoeff() float64
- func (p Poly) Mul(q Poly) Poly
- func (p Poly) Roots() []complex128
- func (p Poly) Scale(k float64) Poly
- func (p Poly) Sub(q Poly) Poly
- type RouthResult
- type StateSpace
- func (s StateSpace) CharacteristicPolynomial() Poly
- func (s StateSpace) ControllabilityMatrix() [][]float64
- func (s StateSpace) ControllabilityRank() int
- func (s StateSpace) ImpulseResponse(times []float64) []float64
- func (s StateSpace) IsControllable() bool
- func (s StateSpace) IsObservable() bool
- func (s StateSpace) ObservabilityMatrix() [][]float64
- func (s StateSpace) ObservabilityRank() int
- func (s StateSpace) Order() int
- func (s StateSpace) Poles() []complex128
- func (s StateSpace) Simulate(times []float64, x0 []float64, input func(t float64) float64) []float64
- func (s StateSpace) StepResponse(times []float64) []float64
- func (s StateSpace) TransferFunction() TransferFunction
- type TransferFunction
- func Feedback(g, h TransferFunction, sign int) TransferFunction
- func NewTransferFunction(num, den []float64) TransferFunction
- func Parallel(g, h TransferFunction) TransferFunction
- func SecondOrderSystem(wn, zeta float64) TransferFunction
- func Series(g, h TransferFunction) TransferFunction
- func UnityFeedback(g TransferFunction) TransferFunction
- func (g TransferFunction) Bode(omegas []float64) []BodePoint
- func (g TransferFunction) DCGain() float64
- func (g TransferFunction) Evaluate(s complex128) complex128
- func (g TransferFunction) FinalValue() float64
- func (g TransferFunction) FrequencyResponse(omega float64) complex128
- func (g TransferFunction) GainCrossoverFrequency(omegas []float64) (float64, bool)
- func (g TransferFunction) GainMargin(omegas []float64) (float64, bool)
- func (g TransferFunction) ImpulseResponse(times []float64) []float64
- func (g TransferFunction) IsProper() bool
- func (g TransferFunction) IsStable() bool
- func (g TransferFunction) IsStrictlyProper() bool
- func (g TransferFunction) Nyquist(omegas []float64) []NyquistPoint
- func (g TransferFunction) Order() int
- func (g TransferFunction) PhaseCrossoverFrequency(omegas []float64) (float64, bool)
- func (g TransferFunction) PhaseMargin(omegas []float64) (float64, bool)
- func (g TransferFunction) Poles() []complex128
- func (g TransferFunction) Series(h TransferFunction) TransferFunction
- func (g TransferFunction) StepResponse(times []float64) []float64
- func (g TransferFunction) Zeros() []complex128
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DampingRatio ¶
func DampingRatio(pole complex128) float64
DampingRatio returns the damping ratio of a complex pole, defined as -Re(p)/|p|. It returns 0 for a pole at the origin.
func IsHurwitzStable ¶
IsHurwitzStable reports whether the polynomial is Hurwitz stable, i.e. all of its roots lie strictly in the left half-plane, as determined by the Routh-Hurwitz test.
func LogSpace ¶
LogSpace returns n logarithmically spaced values between 10^startExp and 10^endExp inclusive. It is convenient for generating frequency grids for Bode and Nyquist sampling. It returns a single-element slice when n is 1 and an empty slice when n is less than 1.
func MagnitudeDB ¶
func MagnitudeDB(value complex128) float64
MagnitudeDB returns 20·log10(|value|) in decibels.
func NaturalFrequency ¶
func NaturalFrequency(pole complex128) float64
NaturalFrequency returns the undamped natural frequency of a complex pole, which is its magnitude |p|.
func NumRightHalfPlaneRoots ¶
NumRightHalfPlaneRoots returns the number of roots of the polynomial with strictly positive real part, equal to the number of sign changes in the first column of the Routh array.
func PhaseDeg ¶
func PhaseDeg(value complex128) float64
PhaseDeg returns the phase angle of value in degrees.
Types ¶
type BodePoint ¶
type BodePoint struct {
// Omega is the angular frequency in radians per second.
Omega float64
// MagnitudeDB is the magnitude of G(jω) expressed in decibels.
MagnitudeDB float64
// PhaseDeg is the phase of G(jω) in degrees.
PhaseDeg float64
}
BodePoint is a single sample of a Bode plot at one angular frequency.
type NyquistPoint ¶
type NyquistPoint struct {
// Omega is the angular frequency in radians per second.
Omega float64
// Real is the real part of G(jω).
Real float64
// Imag is the imaginary part of G(jω).
Imag float64
}
NyquistPoint is a single sample of a Nyquist plot at one angular frequency.
type PIDController ¶
type PIDController struct {
// Kp is the proportional gain.
Kp float64
// Ki is the integral gain.
Ki float64
// Kd is the derivative gain.
Kd float64
// contains filtered or unexported fields
}
PIDController is a discrete-time proportional-integral-derivative controller with configurable gains. It maintains internal state (the accumulated integral and the previous error) so successive calls to Update implement the standard incremental control law.
func NewPIDController ¶
func NewPIDController(kp, ki, kd float64) *PIDController
NewPIDController returns a PIDController with the given proportional, integral, and derivative gains and zero internal state.
func ZieglerNicholsPID ¶
func ZieglerNicholsPID(ku, pu float64) *PIDController
ZieglerNicholsPID returns PID gains tuned by the classical Ziegler-Nichols ultimate-sensitivity rule from the ultimate gain ku (the proportional gain at which the loop sustains oscillation) and the ultimate period pu (the oscillation period, in seconds). The returned controller uses Kp = 0.6·ku, Ki = Kp/(0.5·pu), Kd = Kp·0.125·pu.
func (*PIDController) Integral ¶
func (c *PIDController) Integral() float64
Integral returns the current accumulated integral of the error.
func (*PIDController) Reset ¶
func (c *PIDController) Reset()
Reset clears the accumulated integral term and derivative history so the controller behaves as if freshly constructed.
func (*PIDController) TransferFunction ¶
func (c *PIDController) TransferFunction() TransferFunction
TransferFunction returns the ideal continuous-time transfer function of the controller, C(s) = Kp + Ki/s + Kd·s = (Kd·s^2 + Kp·s + Ki) / s.
func (*PIDController) Update ¶
func (c *PIDController) Update(errValue, dt float64) float64
Update advances the controller by one time step of length dt given the current error (setpoint minus measurement) and returns the control output
u = Kp·e + Ki·∫e dt + Kd·de/dt
The integral is accumulated with the rectangle rule and the derivative uses a backward difference. On the first call after construction or Reset the derivative term is taken as zero. It panics if dt is not positive.
type Poly ¶
type Poly []float64
Poly represents a real polynomial in the Laplace variable s using the ascending-power convention: element i is the coefficient of s^i. For example Poly{6, 5, 1} is the polynomial 6 + 5s + s^2. The zero-length polynomial and a polynomial of all zeros both represent the constant 0.
func NewPoly ¶
NewPoly returns a Poly built from the given ascending-power coefficients. coeffs[i] becomes the coefficient of s^i. The slice is copied so later mutation of the argument does not affect the result.
func PolyFromRoots ¶
func PolyFromRoots(roots ...complex128) Poly
PolyFromRoots returns the monic real polynomial whose roots are the given complex values. Roots are expected to appear in conjugate pairs so the resulting coefficients are real; any residual imaginary parts from rounding are discarded.
func (Poly) Degree ¶
Degree returns the degree of the polynomial, i.e. the highest power of s with a nonzero coefficient. The zero polynomial has degree 0 by convention.
func (Poly) Derivative ¶
Derivative returns the derivative dp/ds as a new polynomial.
func (Poly) DivMod ¶
DivMod divides p by q and returns the quotient and remainder polynomials such that p = quotient*q + remainder with degree(remainder) < degree(q). It panics if q is the zero polynomial.
func (Poly) EvalComplex ¶
func (p Poly) EvalComplex(z complex128) complex128
EvalComplex evaluates the polynomial at the complex point z using Horner's method, which is the form needed for frequency-response calculations.
func (Poly) LeadingCoeff ¶
LeadingCoeff returns the coefficient of the highest nonzero power of s. For the zero polynomial it returns 0.
func (Poly) Mul ¶
Mul returns the product p*q. Polynomial multiplication is the convolution of the two coefficient sequences.
func (Poly) Roots ¶
func (p Poly) Roots() []complex128
Roots returns all complex roots of the polynomial using the Durand-Kerner (Weierstrass) iteration with deterministic starting points. Real roots are returned with a zero (or negligible) imaginary part. The zero polynomial and nonzero constants have no roots and return an empty slice.
type RouthResult ¶
type RouthResult struct {
// Table is the completed Routh array. Row 0 corresponds to the highest
// power of s. Each row is padded with trailing zeros to a common width.
Table [][]float64
// FirstColumn is the first column of the Routh array, whose sign changes
// count the roots in the right half-plane.
FirstColumn []float64
// SignChanges is the number of sign changes in the first column, equal to
// the number of characteristic roots with positive real part.
SignChanges int
// Stable reports whether the polynomial is Hurwitz stable, i.e. every root
// has a strictly negative real part (no sign changes in the first column).
Stable bool
}
RouthResult holds the outcome of a Routh-Hurwitz stability analysis of a characteristic polynomial.
func RouthHurwitz ¶
func RouthHurwitz(p Poly) RouthResult
RouthHurwitz performs the Routh-Hurwitz stability test on the given characteristic polynomial (ascending-power Poly). It builds the Routh array, counts sign changes in the first column, and reports stability. A zero appearing in the first column is replaced by a small positive epsilon so the tabulation can proceed. The polynomial must have degree at least 1.
type StateSpace ¶
type StateSpace struct {
// A is the n×n system (state) matrix.
A [][]float64
// B is the n×1 input matrix stored as a column vector of length n.
B []float64
// C is the 1×n output matrix stored as a row vector of length n.
C []float64
// D is the scalar feedthrough term.
D float64
}
StateSpace is a SISO continuous-time state-space realization
x' = A x + B u y = C x + D u
where A is n×n, B is n×1, C is 1×n, and D is a scalar. Matrices are stored as row-major slices of slices.
func NewStateSpace ¶
func NewStateSpace(a [][]float64, b, c []float64, d float64) StateSpace
NewStateSpace constructs a StateSpace from the given matrices. The slices are copied. It panics if the dimensions are inconsistent (A must be square and B, C must match its size).
func TransferFunctionToStateSpace ¶
func TransferFunctionToStateSpace(g TransferFunction) StateSpace
TransferFunctionToStateSpace converts a proper transfer function into a state-space realization in controllable canonical form. Any direct feedthrough (when the numerator and denominator have equal degree) is placed in the D term. It panics if the transfer function is not proper or the denominator is the zero polynomial.
func (StateSpace) CharacteristicPolynomial ¶
func (s StateSpace) CharacteristicPolynomial() Poly
CharacteristicPolynomial returns the characteristic polynomial det(sI - A) of the state matrix as an ascending-power monic Poly, computed with the Faddeev-LeVerrier algorithm.
func (StateSpace) ControllabilityMatrix ¶
func (s StateSpace) ControllabilityMatrix() [][]float64
ControllabilityMatrix returns the controllability matrix [B, AB, A^2 B, ..., A^(n-1) B], an n×n matrix for a SISO system.
func (StateSpace) ControllabilityRank ¶
func (s StateSpace) ControllabilityRank() int
ControllabilityRank returns the rank of the controllability matrix.
func (StateSpace) ImpulseResponse ¶
func (s StateSpace) ImpulseResponse(times []float64) []float64
ImpulseResponse returns the unit-impulse response C·e^{At}·B of the state-space system sampled at the given time points, obtained by simulating the autonomous dynamics from initial state B. Any direct feedthrough D contributes only at t=0 (a Dirac impulse) and is not included in the samples.
func (StateSpace) IsControllable ¶
func (s StateSpace) IsControllable() bool
IsControllable reports whether the system is completely state controllable, i.e. the controllability matrix has full rank n.
func (StateSpace) IsObservable ¶
func (s StateSpace) IsObservable() bool
IsObservable reports whether the system is completely observable, i.e. the observability matrix has full rank n.
func (StateSpace) ObservabilityMatrix ¶
func (s StateSpace) ObservabilityMatrix() [][]float64
ObservabilityMatrix returns the observability matrix [C; CA; CA^2; ...; CA^(n-1)], an n×n matrix for a SISO system.
func (StateSpace) ObservabilityRank ¶
func (s StateSpace) ObservabilityRank() int
ObservabilityRank returns the rank of the observability matrix.
func (StateSpace) Order ¶
func (s StateSpace) Order() int
Order returns the number of states n, the dimension of the A matrix.
func (StateSpace) Poles ¶
func (s StateSpace) Poles() []complex128
Poles returns the poles of the realization, i.e. the eigenvalues of A, found as the roots of the characteristic polynomial.
func (StateSpace) Simulate ¶
func (s StateSpace) Simulate(times []float64, x0 []float64, input func(t float64) float64) []float64
Simulate integrates the state-space system forward from initial state x0 over the given monotonically increasing time points using the classical fourth-order Runge-Kutta method. The scalar input at time t is supplied by the function input. It returns the output y sampled at each time point. The returned slice has the same length as times.
func (StateSpace) StepResponse ¶
func (s StateSpace) StepResponse(times []float64) []float64
StepResponse returns the unit-step response of the state-space system from rest, sampled at the given time points.
func (StateSpace) TransferFunction ¶
func (s StateSpace) TransferFunction() TransferFunction
TransferFunction converts the state-space realization to an equivalent SISO transfer function G(s) = C(sI-A)^{-1}B + D using the Faddeev-LeVerrier algorithm.
type TransferFunction ¶
type TransferFunction struct {
// Num is the numerator polynomial N(s).
Num Poly
// Den is the denominator polynomial D(s).
Den Poly
}
TransferFunction is a SISO continuous-time transfer function G(s) = Num(s) / Den(s) with real polynomial numerator and denominator stored in the ascending-power convention of Poly.
func Feedback ¶
func Feedback(g, h TransferFunction, sign int) TransferFunction
Feedback returns the closed-loop transfer function of forward path g with feedback path h. When sign is -1 (negative feedback) the result is G/(1+G·H); when sign is +1 (positive feedback) it is G/(1-G·H).
func NewTransferFunction ¶
func NewTransferFunction(num, den []float64) TransferFunction
NewTransferFunction builds a TransferFunction from ascending-power numerator and denominator coefficient slices. Both slices are copied.
func Parallel ¶
func Parallel(g, h TransferFunction) TransferFunction
Parallel returns the parallel connection G(s)+H(s) of two transfer functions, whose outputs are summed for a common input.
func SecondOrderSystem ¶
func SecondOrderSystem(wn, zeta float64) TransferFunction
SecondOrderSystem returns the canonical second-order transfer function wn^2 / (s^2 + 2·zeta·wn·s + wn^2) for natural frequency wn (rad/s) and damping ratio zeta.
func Series ¶
func Series(g, h TransferFunction) TransferFunction
Series returns the cascade connection G(s)·H(s) of two transfer functions.
func UnityFeedback ¶
func UnityFeedback(g TransferFunction) TransferFunction
UnityFeedback returns the closed-loop transfer function of forward path g with unity negative feedback, G/(1+G).
func (TransferFunction) Bode ¶
func (g TransferFunction) Bode(omegas []float64) []BodePoint
Bode returns the Bode-plot samples (magnitude in dB and phase in degrees) of the transfer function at each supplied angular frequency. Phase is computed with a continuous unwrap so successive samples do not jump by 2π.
func (TransferFunction) DCGain ¶
func (g TransferFunction) DCGain() float64
DCGain returns the steady-state gain G(0) = Num(0)/Den(0). It returns +Inf when the denominator constant term is zero (a pole at the origin).
func (TransferFunction) Evaluate ¶
func (g TransferFunction) Evaluate(s complex128) complex128
Evaluate returns the complex value of G(s) at the complex point s. It returns a value with infinite magnitude when s is a pole (the denominator vanishes).
func (TransferFunction) FinalValue ¶
func (g TransferFunction) FinalValue() float64
FinalValue returns the steady-state value of the step response predicted by the final-value theorem, lim_{t->inf} y(t) = G(0), when the closed system is stable. It equals the DC gain of the transfer function.
func (TransferFunction) FrequencyResponse ¶
func (g TransferFunction) FrequencyResponse(omega float64) complex128
FrequencyResponse returns G(jω), the complex response at angular frequency omega (in radians per second).
func (TransferFunction) GainCrossoverFrequency ¶
func (g TransferFunction) GainCrossoverFrequency(omegas []float64) (float64, bool)
GainCrossoverFrequency returns the lowest angular frequency in the supplied grid at which the open-loop magnitude |G(jω)| crosses unity (0 dB), refined by linear interpolation in the log-magnitude versus log-frequency plane. The boolean result reports whether such a crossing was found within the grid.
func (TransferFunction) GainMargin ¶
func (g TransferFunction) GainMargin(omegas []float64) (float64, bool)
GainMargin returns the gain margin in decibels, defined as -20·log10|G(jω)| at the phase crossover frequency (where the phase is -180 degrees), evaluated over the supplied frequency grid. The boolean result reports whether a phase crossover was found. A positive gain margin indicates a stable closed loop under unity negative feedback.
func (TransferFunction) ImpulseResponse ¶
func (g TransferFunction) ImpulseResponse(times []float64) []float64
ImpulseResponse returns the unit-impulse response of a strictly proper transfer function sampled at the given monotonically increasing time points. For a strictly proper realization the impulse response equals C·e^{At}·B, so it is computed by simulating the autonomous system with initial state B and zero input. The transfer function must be strictly proper.
func (TransferFunction) IsProper ¶
func (g TransferFunction) IsProper() bool
IsProper reports whether the numerator degree does not exceed the denominator degree, the condition for a physically realizable system.
func (TransferFunction) IsStable ¶
func (g TransferFunction) IsStable() bool
IsStable reports whether every pole has a strictly negative real part, the condition for asymptotic stability of a continuous-time system.
func (TransferFunction) IsStrictlyProper ¶
func (g TransferFunction) IsStrictlyProper() bool
IsStrictlyProper reports whether the numerator degree is strictly less than the denominator degree.
func (TransferFunction) Nyquist ¶
func (g TransferFunction) Nyquist(omegas []float64) []NyquistPoint
Nyquist returns the Nyquist-plot samples (real and imaginary parts of G(jω)) of the transfer function at each supplied angular frequency.
func (TransferFunction) Order ¶
func (g TransferFunction) Order() int
Order returns the order of the system, i.e. the degree of the denominator polynomial.
func (TransferFunction) PhaseCrossoverFrequency ¶
func (g TransferFunction) PhaseCrossoverFrequency(omegas []float64) (float64, bool)
PhaseCrossoverFrequency returns the lowest angular frequency in the supplied grid at which the open-loop phase crosses -180 degrees, refined by linear interpolation. The boolean result reports whether such a crossing was found.
func (TransferFunction) PhaseMargin ¶
func (g TransferFunction) PhaseMargin(omegas []float64) (float64, bool)
PhaseMargin returns the phase margin in degrees, defined as 180 plus the open-loop phase at the gain crossover frequency, evaluated over the supplied frequency grid. The boolean result reports whether a gain crossover was found. A positive phase margin indicates a stable closed loop under unity negative feedback.
func (TransferFunction) Poles ¶
func (g TransferFunction) Poles() []complex128
Poles returns the poles of the system, i.e. the roots of the denominator.
func (TransferFunction) Series ¶
func (g TransferFunction) Series(h TransferFunction) TransferFunction
Series returns the transfer function of g followed by h connected in series (cascade): the product G(s)·H(s).
func (TransferFunction) StepResponse ¶
func (g TransferFunction) StepResponse(times []float64) []float64
StepResponse returns the unit-step response of the transfer function sampled at the given monotonically increasing time points. The system starts from rest (zero initial state) and the input is held at 1 for all t >= 0. The transfer function must be proper.
func (TransferFunction) Zeros ¶
func (g TransferFunction) Zeros() []complex128
Zeros returns the zeros of the system, i.e. the roots of the numerator.