controltheory

package
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 19, 2026 License: MIT Imports: 2 Imported by: 0

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

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

func IsHurwitzStable(p Poly) bool

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

func LogSpace(startExp, endExp float64, n int) []float64

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

func NumRightHalfPlaneRoots(p Poly) int

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

func NewPoly(coeffs ...float64) Poly

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) Add

func (p Poly) Add(q Poly) Poly

Add returns the sum p + q as a new polynomial.

func (Poly) Degree

func (p Poly) Degree() int

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

func (p Poly) Derivative() Poly

Derivative returns the derivative dp/ds as a new polynomial.

func (Poly) DivMod

func (p Poly) DivMod(q Poly) (quotient, remainder Poly)

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) Eval

func (p Poly) Eval(x float64) float64

Eval evaluates the polynomial at the real point x using Horner's method.

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

func (p Poly) LeadingCoeff() float64

LeadingCoeff returns the coefficient of the highest nonzero power of s. For the zero polynomial it returns 0.

func (Poly) Mul

func (p Poly) Mul(q Poly) Poly

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.

func (Poly) Scale

func (p Poly) Scale(k float64) Poly

Scale returns the polynomial with every coefficient multiplied by k.

func (Poly) Sub

func (p Poly) Sub(q Poly) Poly

Sub returns the difference p - q as a new polynomial.

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

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

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL