Documentation
¶
Overview ¶
Package optimalcontrol implements optimal control and dynamic programming algorithms using only the Go standard library.
The package is self-contained: it ships its own small dense linear-algebra layer (the Matrix type with LU, Cholesky, least-squares, matrix exponential, Faddeev–LeVerrier characteristic polynomials and Durand–Kerner eigenvalues, symmetric Jacobi eigensolves, and Lyapunov/Sylvester solvers) and builds the control-theoretic algorithms on top of it.
The control content spans the classical pillars of the field:
Linear-quadratic regulators in continuous and discrete time, obtained by solving the algebraic Riccati equations. The continuous CARE is solved by the matrix-sign-function method applied to the Hamiltonian matrix and by Kleinman's Newton iteration; the discrete DARE is solved by the Riccati recursion. See SolveCARE, SolveDARE, LQRContinuous and LQRDiscrete.
Finite-horizon control via the backward Riccati recursion (discrete) and backward integration of the matrix Riccati differential equation (continuous). See FiniteHorizonLQRDiscrete and FiniteHorizonLQRContinuous.
Pontryagin's minimum principle expressed as a Hamiltonian two-point boundary-value problem, solved exactly for linear-quadratic problems and by indirect single shooting for nonlinear ones. See PontryaginLQ and IndirectShooting.
Hamilton–Jacobi–Bellman value iteration on state grids via a semi-Lagrangian scheme. See HJBGrid1D.
Dynamic programming for finite Markov decision processes: value iteration, Gauss–Seidel value iteration, exact and iterative policy evaluation, Howard's policy iteration and modified policy iteration. See the MDP type.
Kalman filtering and the linear-quadratic-Gaussian dual of the LQR, including steady-state continuous and discrete filters and a recursive KalmanFilter. See KalmanContinuous, KalmanDiscrete, LQGContinuous and LQGDiscrete.
Structural analysis (controllability, observability, stabilizability and detectability via the Popov–Belevitch–Hautus tests) and Lyapunov Gramians round out the toolkit.
All algorithms are deterministic; where randomness is useful the caller supplies a seed and uses math/rand. Nothing outside the standard library is imported.
Index ¶
- Variables
- func CharPoly(a *Matrix) []float64
- func ClosedLoopEigenvaluesContinuous(a, b, k *Matrix) []complex128
- func ClosedLoopEigenvaluesDiscrete(a, b, k *Matrix) []complex128
- func ControllabilityRank(a, b *Matrix) int
- func CostateDynamicsLQ(a, q *Matrix, x, p []float64) []float64
- func Det(a *Matrix) (float64, error)
- func Eigenvalues(a *Matrix) []complex128
- func IsControllable(a, b *Matrix) bool
- func IsDetectableContinuous(a, c *Matrix) bool
- func IsDetectableDiscrete(a, c *Matrix) bool
- func IsObservable(a, c *Matrix) bool
- func IsPositiveDefinite(a *Matrix) bool
- func IsPositiveSemiDefinite(a *Matrix, tol float64) bool
- func IsStabilizableContinuous(a, b *Matrix) bool
- func IsStabilizableDiscrete(a, b *Matrix) bool
- func IsStableContinuous(a *Matrix, tol float64) bool
- func IsStableDiscrete(a *Matrix, tol float64) bool
- func LQRCostToGo(p *Matrix, x []float64) float64
- func LeastSquares(a *Matrix, b []float64) ([]float64, error)
- func LinearInterp1D(grid, values []float64, x float64) float64
- func ObservabilityRank(a, c *Matrix) int
- func OptimalControlLQ(b, r *Matrix, p []float64) ([]float64, error)
- func PolyRootsDK(coeffs []float64) []complex128
- func QuadraticCostDiscrete(xs [][]float64, us [][]float64, q, r, qf *Matrix) float64
- func Rank(a *Matrix, tol float64) int
- func SimulateDiscreteLQR(a, b *Matrix, fh *FiniteHorizonDiscrete, x0 []float64) [][]float64
- func Solve(a *Matrix, b []float64) ([]float64, error)
- func SpectralAbscissa(a *Matrix) float64
- func SpectralRadius(a *Matrix) float64
- func SymEigenvalues(a *Matrix) ([]float64, error)
- func VecAdd(a, b []float64) []float64
- func VecAxpy(s float64, b, a []float64) []float64
- func VecCopy(a []float64) []float64
- func VecDot(a, b []float64) float64
- func VecMaxAbs(a []float64) float64
- func VecNorm(a []float64) float64
- func VecScale(a []float64, s float64) []float64
- func VecSub(a, b []float64) []float64
- type FiniteHorizonContinuous
- type FiniteHorizonDiscrete
- type HJBGrid1D
- type HJBResult
- type KalmanContinuousResult
- type KalmanDiscreteResult
- type KalmanFilter
- type LQGResult
- type LQRResult
- type LU
- type LinearSystem
- func (s *LinearSystem) Output(x, u []float64) []float64
- func (s *LinearSystem) SimulateContinuous(x0 []float64, control func(t float64) []float64, dt float64, steps int) [][]float64
- func (s *LinearSystem) SimulateDiscrete(x0 []float64, us [][]float64) [][]float64
- func (s *LinearSystem) StepDiscrete(x, u []float64) []float64
- type MDP
- func (m *MDP) BellmanBackup(v []float64) (newV []float64, policy []int)
- func (m *MDP) GaussSeidelValueIteration(tol float64, maxIter int) *ValueIterationResult
- func (m *MDP) GreedyPolicy(v []float64) []int
- func (m *MDP) ModifiedPolicyIteration(k, maxIter int, tol float64) (*PolicyIterationResult, error)
- func (m *MDP) PolicyEvaluationExact(policy []int) ([]float64, error)
- func (m *MDP) PolicyEvaluationIterative(policy []int, tol float64, maxIter int) []float64
- func (m *MDP) PolicyIteration(maxIter int) (*PolicyIterationResult, error)
- func (m *MDP) PolicyMatrices(policy []int) (*Matrix, []float64)
- func (m *MDP) QValues(v []float64) *Matrix
- func (m *MDP) ValueIteration(tol float64, maxIter int) *ValueIterationResult
- type Matrix
- func BlockMatrix(a, b, c, d *Matrix) *Matrix
- func CAREResidual(a, b, q, r, x *Matrix) (*Matrix, error)
- func Cholesky(a *Matrix) (*Matrix, error)
- func ColumnVector(v []float64) *Matrix
- func ContinuousGain(b, r, p *Matrix) (*Matrix, error)
- func ControllabilityGramianContinuous(a, b *Matrix) (*Matrix, error)
- func ControllabilityGramianDiscrete(a, b *Matrix) (*Matrix, error)
- func ControllabilityMatrix(a, b *Matrix) *Matrix
- func DAREResidual(a, b, q, r, x *Matrix) (*Matrix, error)
- func Diag(v []float64) *Matrix
- func DiscreteGain(a, b, r, p *Matrix) (*Matrix, error)
- func DiscretizeZOH(a, b *Matrix, dt float64) (ad, bd *Matrix)
- func Eye(n int) *Matrix
- func FromRows(rows [][]float64) *Matrix
- func HStack(a, b *Matrix) *Matrix
- func HamiltonianMatrix(a, b, q, r *Matrix) (*Matrix, error)
- func Inverse(a *Matrix) (*Matrix, error)
- func Kron(a, b *Matrix) *Matrix
- func LeastSquaresMatrix(a, b *Matrix) (*Matrix, error)
- func LyapunovContinuousResidual(a, q, x *Matrix) *Matrix
- func LyapunovDiscreteResidual(a, q, x *Matrix) *Matrix
- func MatrixExp(a *Matrix) *Matrix
- func MatrixPow(a *Matrix, p int) *Matrix
- func MatrixSign(a *Matrix, maxIter int, tol float64) (*Matrix, error)
- func NewMatrix(r, c int, data []float64) *Matrix
- func ObservabilityGramianContinuous(a, c *Matrix) (*Matrix, error)
- func ObservabilityGramianDiscrete(a, c *Matrix) (*Matrix, error)
- func ObservabilityMatrix(a, c *Matrix) *Matrix
- func Ones(r, c int) *Matrix
- func RowVector(v []float64) *Matrix
- func SolveCARE(a, b, q, r *Matrix) (*Matrix, error)
- func SolveCAREKleinman(a, b, q, r, k0 *Matrix, maxIter int, tol float64) (*Matrix, error)
- func SolveCARESign(a, b, q, r *Matrix) (*Matrix, error)
- func SolveDARE(a, b, q, r *Matrix) (*Matrix, error)
- func SolveDAREIter(a, b, q, r *Matrix, maxIter int, tol float64) (*Matrix, error)
- func SolveLyapunovContinuous(a, q *Matrix) (*Matrix, error)
- func SolveLyapunovDiscrete(a, q *Matrix) (*Matrix, error)
- func SolveMatrix(a, b *Matrix) (*Matrix, error)
- func SolveSylvester(a, b, c *Matrix) (*Matrix, error)
- func SymEigen(a *Matrix) (w []float64, v *Matrix, err error)
- func Unvec(v []float64, r, c int) *Matrix
- func VStack(a, b *Matrix) *Matrix
- func Zeros(r, c int) *Matrix
- func (m *Matrix) Add(i, j int, v float64)
- func (m *Matrix) ApproxEqual(b *Matrix, tol float64) bool
- func (m *Matrix) At(i, j int) float64
- func (m *Matrix) Clone() *Matrix
- func (m *Matrix) Col(j int) []float64
- func (m *Matrix) Cols() int
- func (m *Matrix) Data() []float64
- func (m *Matrix) Equal(b *Matrix) bool
- func (m *Matrix) FrobeniusNorm() float64
- func (m *Matrix) InfNorm() float64
- func (m *Matrix) IsSquare() bool
- func (m *Matrix) IsSymmetric(tol float64) bool
- func (m *Matrix) MaxAbs() float64
- func (m *Matrix) Minus(b *Matrix) *Matrix
- func (m *Matrix) Mul(b *Matrix) *Matrix
- func (m *Matrix) MulVec(v []float64) []float64
- func (m *Matrix) Neg() *Matrix
- func (m *Matrix) OneNorm() float64
- func (m *Matrix) Plus(b *Matrix) *Matrix
- func (m *Matrix) Row(i int) []float64
- func (m *Matrix) Rows() int
- func (m *Matrix) Scale(s float64) *Matrix
- func (m *Matrix) Set(i, j int, v float64)
- func (m *Matrix) SetBlock(r0, c0 int, b *Matrix)
- func (m *Matrix) SetCol(j int, v []float64)
- func (m *Matrix) SetRow(i int, v []float64)
- func (m *Matrix) String() string
- func (m *Matrix) Submatrix(r0, r1, c0, c1 int) *Matrix
- func (m *Matrix) Symmetrize() *Matrix
- func (m *Matrix) Trace() float64
- func (m *Matrix) Transpose() *Matrix
- func (m *Matrix) Vec() []float64
- type PolicyIterationResult
- type TPBVPSolution
- type ValueIterationResult
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrDim = errors.New("optimalcontrol: incompatible dimensions")
ErrDim is returned when matrix or vector dimensions are incompatible.
var ErrNotConverged = errors.New("optimalcontrol: iteration did not converge")
ErrNotConverged is returned by iterative solvers that fail to converge within the allotted number of iterations.
var ErrSingular = errors.New("optimalcontrol: matrix is singular")
ErrSingular is returned when a matrix that must be invertible is singular.
Functions ¶
func CharPoly ¶
CharPoly returns the coefficients of the characteristic polynomial det(λI − A) of a square matrix, in ascending powers of λ (index i holds the coefficient of λ^i). It uses the Faddeev–LeVerrier algorithm.
func ClosedLoopEigenvaluesContinuous ¶
func ClosedLoopEigenvaluesContinuous(a, b, k *Matrix) []complex128
ClosedLoopEigenvaluesContinuous returns the eigenvalues of A − B K, the continuous closed-loop dynamics under state feedback K.
func ClosedLoopEigenvaluesDiscrete ¶
func ClosedLoopEigenvaluesDiscrete(a, b, k *Matrix) []complex128
ClosedLoopEigenvaluesDiscrete returns the eigenvalues of A − B K, the discrete closed-loop dynamics under state feedback K.
func ControllabilityRank ¶
ControllabilityRank returns the rank of the controllability matrix of (A, B).
func CostateDynamicsLQ ¶
CostateDynamicsLQ returns the costate derivative p' = −Q x − Aᵀ p for the linear-quadratic problem.
func Eigenvalues ¶
func Eigenvalues(a *Matrix) []complex128
Eigenvalues returns all (generally complex) eigenvalues of a square matrix by forming its characteristic polynomial and finding its roots with the Durand–Kerner method. Results are not returned in any particular order.
func IsControllable ¶
IsControllable reports whether the pair (A, B) is controllable, i.e. the controllability matrix has full row rank n.
func IsDetectableContinuous ¶
IsDetectableContinuous reports whether the pair (A, C) is detectable in continuous time (dual of stabilizability).
func IsDetectableDiscrete ¶
IsDetectableDiscrete reports whether the pair (A, C) is detectable in discrete time.
func IsObservable ¶
IsObservable reports whether the pair (A, C) is observable, i.e. the observability matrix has full column rank n.
func IsPositiveDefinite ¶
IsPositiveDefinite reports whether a symmetric matrix is positive definite.
func IsPositiveSemiDefinite ¶
IsPositiveSemiDefinite reports whether a symmetric matrix has all eigenvalues >= -tol.
func IsStabilizableContinuous ¶
IsStabilizableContinuous reports whether the pair (A, B) is stabilizable in continuous time: every eigenvalue of A with non-negative real part is controllable (Popov–Belevitch–Hautus test).
func IsStabilizableDiscrete ¶
IsStabilizableDiscrete reports whether the pair (A, B) is stabilizable in discrete time: every eigenvalue of A with modulus >= 1 is controllable.
func IsStableContinuous ¶
IsStableContinuous reports whether every eigenvalue of A has real part below -tol (Hurwitz stability of x' = A x).
func IsStableDiscrete ¶
IsStableDiscrete reports whether every eigenvalue of A has modulus below 1-tol (Schur stability of x_{k+1} = A x_k).
func LQRCostToGo ¶
LQRCostToGo returns the optimal quadratic cost xᵀ P x of steering the state x to the origin under an infinite-horizon LQR with Riccati solution P.
func LeastSquares ¶
LeastSquares solves the overdetermined system A x = b in the least-squares sense via the normal equations AᵀA x = Aᵀb. A must have full column rank.
func LinearInterp1D ¶
LinearInterp1D linearly interpolates the tabulated function (grid, values) at x. The grid must be sorted ascending. Queries outside the grid are clamped to the endpoint values.
func ObservabilityRank ¶
ObservabilityRank returns the rank of the observability matrix of (A, C).
func OptimalControlLQ ¶
OptimalControlLQ returns the stationarity-condition control u = −R⁻¹ Bᵀ p that minimizes the linear-quadratic Hamiltonian for a given costate p.
func PolyRootsDK ¶
func PolyRootsDK(coeffs []float64) []complex128
PolyRootsDK finds all complex roots of a real polynomial using the Durand–Kerner (Weierstrass) iteration. Coefficients are given in ascending power order: coeffs[i] multiplies x^i. The leading coefficient must be nonzero.
func QuadraticCostDiscrete ¶
QuadraticCostDiscrete evaluates the discrete quadratic cost Σ_k (xₖᵀ Q xₖ + uₖᵀ R uₖ) + x_Nᵀ Qf x_N over a state trajectory and control sequence. The control sequence has one fewer entry than the state trajectory.
func Rank ¶
Rank returns the numerical rank of a matrix using Gaussian elimination with partial pivoting and the supplied tolerance for treating pivots as zero.
func SimulateDiscreteLQR ¶
func SimulateDiscreteLQR(a, b *Matrix, fh *FiniteHorizonDiscrete, x0 []float64) [][]float64
SimulateDiscreteLQR simulates the closed-loop discrete system under the time-varying gains produced by FiniteHorizonLQRDiscrete, starting from x0 and returning the state trajectory x[0..N].
func SpectralAbscissa ¶
SpectralAbscissa returns the maximum real part of the eigenvalues of a matrix.
func SpectralRadius ¶
SpectralRadius returns the maximum modulus of the eigenvalues of a matrix.
func SymEigenvalues ¶
SymEigenvalues returns the eigenvalues of a symmetric matrix in ascending order.
Types ¶
type FiniteHorizonContinuous ¶
type FiniteHorizonContinuous struct {
// Times holds the sample times, ascending from 0 to T.
Times []float64
// P holds the Riccati matrix at each sample time.
P []*Matrix
}
FiniteHorizonContinuous holds the sampled solution of the continuous finite-horizon Riccati differential equation on a time grid.
func FiniteHorizonLQRContinuous ¶
func FiniteHorizonLQRContinuous(a, b, q, r, qf *Matrix, tFinal float64, steps int) (*FiniteHorizonContinuous, error)
FiniteHorizonLQRContinuous integrates the matrix Riccati differential equation
−dP/dt = Aᵀ P + P A − P B R⁻¹ Bᵀ P + Q, P(T) = Qf,
backward from the terminal time T to 0 using classical fourth-order Runge–Kutta with the given number of steps. Samples are returned in ascending time order.
type FiniteHorizonDiscrete ¶
type FiniteHorizonDiscrete struct {
// P holds the cost-to-go matrices P[0..N]; P[N] is the terminal weight.
P []*Matrix
// K holds the feedback gains K[0..N-1] with u_k = −K[k] x_k.
K []*Matrix
}
FiniteHorizonDiscrete holds the time-varying gains and cost matrices of a finite-horizon discrete LQR problem, indexed from stage 0 (initial) to N (terminal).
func FiniteHorizonLQRDiscrete ¶
func FiniteHorizonLQRDiscrete(a, b, q, r, qf *Matrix, n int) (*FiniteHorizonDiscrete, error)
FiniteHorizonLQRDiscrete solves the finite-horizon discrete LQR problem for x_{k+1} = A x_k + B u_k minimizing xₙᵀ Qf xₙ + Σ_{k<N} (xₖᵀ Q xₖ + uₖᵀ R uₖ) by backward Riccati recursion. It returns the sequence of cost-to-go matrices and time-varying gains.
type HJBGrid1D ¶
type HJBGrid1D struct {
// Grid is the ascending state grid.
Grid []float64
// Controls is the finite set of admissible control values searched at each
// grid point.
Controls []float64
// Dynamics returns f(x, u), the state velocity.
Dynamics func(x, u float64) float64
// RunningCost returns L(x, u), the instantaneous cost.
RunningCost func(x, u float64) float64
// Dt is the time step of the semi-Lagrangian discretization.
Dt float64
// Rho is the (non-negative) discount rate.
Rho float64
}
HJBGrid1D describes a one-dimensional infinite-horizon optimal-control problem discretized for solution by value iteration (a semi-Lagrangian scheme for the Hamilton–Jacobi–Bellman equation ρ V = min_u [ L(x,u) + V'(x) f(x,u) ]).
func (*HJBGrid1D) ControlAt ¶
ControlAt returns the greedy control at state x by interpolating the tabulated policy of a solved HJBResult onto the grid.
func (*HJBGrid1D) Solve ¶
Solve runs value iteration for the infinite-horizon discounted problem until the max-norm change is below tol or maxIter sweeps elapse. The per-step discount is exp(−ρ Δt) and the successor value is obtained by linear interpolation of the current value function at x + Δt f(x, u).
func (*HJBGrid1D) SolveFiniteHorizon ¶
SolveFiniteHorizon solves the finite-horizon problem with terminal cost g(x) by backward value iteration over the given number of stages, returning the value function and control at the initial time (stage 0).
type HJBResult ¶
type HJBResult struct {
// Value is the optimal cost-to-go at each grid point.
Value []float64
// Control is the optimal control at each grid point.
Control []float64
// Iterations is the number of value-iteration sweeps performed.
Iterations int
// Converged reports whether the sweep residual fell below the tolerance.
Converged bool
}
HJBResult holds the value function and greedy control at each grid point.
type KalmanContinuousResult ¶
type KalmanContinuousResult struct {
// P is the steady-state estimation-error covariance.
P *Matrix
// L is the Kalman gain L = P Cᵀ V⁻¹.
L *Matrix
}
KalmanContinuousResult holds the steady-state error covariance and estimator gain of a continuous-time Kalman–Bucy filter.
func KalmanContinuous ¶
func KalmanContinuous(a, c, w, v *Matrix) (*KalmanContinuousResult, error)
KalmanContinuous designs the steady-state continuous-time Kalman–Bucy filter for x' = A x + w, y = C x + v with process covariance W and measurement covariance V. It solves the filter algebraic Riccati equation A P + P Aᵀ − P Cᵀ V⁻¹ C P + W = 0 (the dual of the LQR CARE) and returns the covariance and gain.
type KalmanDiscreteResult ¶
type KalmanDiscreteResult struct {
// P is the steady-state a-priori (predicted) error covariance.
P *Matrix
// L is the steady-state Kalman update gain L = P Cᵀ (C P Cᵀ + V)⁻¹.
L *Matrix
}
KalmanDiscreteResult holds the steady-state a-priori covariance and gain of a discrete-time Kalman filter.
func KalmanDiscrete ¶
func KalmanDiscrete(a, c, w, v *Matrix) (*KalmanDiscreteResult, error)
KalmanDiscrete designs the steady-state discrete-time Kalman filter for x_{k+1} = A x_k + w_k, y_k = C x_k + v_k with process covariance W and measurement covariance V. It solves the dual DARE P = A P Aᵀ − A P Cᵀ (C P Cᵀ + V)⁻¹ C P Aᵀ + W and returns the a-priori covariance and update gain.
type KalmanFilter ¶
type KalmanFilter struct {
// A is the state-transition matrix.
A *Matrix
// B is the (optional) control-input matrix; may be nil for no input.
B *Matrix
// C is the measurement matrix.
C *Matrix
// Q is the process-noise covariance.
Q *Matrix
// R is the measurement-noise covariance.
R *Matrix
// X is the current state estimate.
X []float64
// P is the current estimate covariance.
P *Matrix
}
KalmanFilter is a recursive discrete-time Kalman filter that tracks a state estimate and its covariance. Construct one with NewKalmanFilter and drive it with alternating Predict and Update calls.
func NewKalmanFilter ¶
func NewKalmanFilter(a, b, c, q, r *Matrix, x0 []float64, p0 *Matrix) *KalmanFilter
NewKalmanFilter constructs a Kalman filter with the given model matrices and initial estimate x0 with covariance p0. B may be nil when the system has no control input.
func (*KalmanFilter) Predict ¶
func (kf *KalmanFilter) Predict(u []float64)
Predict advances the estimate through the process model using control input u (which may be nil when B is nil): x̂ ← A x̂ + B u, P ← A P Aᵀ + Q.
func (*KalmanFilter) Update ¶
func (kf *KalmanFilter) Update(y []float64) error
Update corrects the prediction with measurement y using the Kalman gain: L = P Cᵀ (C P Cᵀ + R)⁻¹, x̂ ← x̂ + L (y − C x̂), P ← (I − L C) P. It returns an error if the innovation covariance is singular.
type LQGResult ¶
type LQGResult struct {
// K is the LQR state-feedback gain (u = −K x̂).
K *Matrix
// L is the Kalman estimator gain.
L *Matrix
// P is the control Riccati solution.
P *Matrix
// Sigma is the estimation-error covariance.
Sigma *Matrix
}
LQGResult bundles the regulator and estimator designs of a linear-quadratic Gaussian controller.
func LQGContinuous ¶
LQGContinuous designs a continuous-time LQG controller by combining a continuous LQR (weights Q, R on the plant (A, B)) with a continuous Kalman filter (process covariance W, measurement covariance V on (A, C)), invoking the separation principle.
func LQGDiscrete ¶
LQGDiscrete designs a discrete-time LQG controller by combining a discrete LQR with a discrete Kalman filter via the separation principle.
type LQRResult ¶
type LQRResult struct {
// K is the optimal feedback gain (u = −K x).
K *Matrix
// P is the stabilizing Riccati solution (the cost-to-go Hessian).
P *Matrix
// ClosedLoop is A − B K.
ClosedLoop *Matrix
}
LQRResult bundles the gain matrix, Riccati solution and closed-loop matrix of a linear-quadratic regulator design.
func LQRContinuous ¶
LQRContinuous designs a continuous-time infinite-horizon LQR for the system x' = A x + B u minimizing ∫ (xᵀ Q x + uᵀ R u) dt. The optimal control is u = −K x with K = R⁻¹ Bᵀ P and P the stabilizing CARE solution.
Example ¶
// Double integrator with unit weights: known gain [1, √3].
a := FromRows([][]float64{{0, 1}, {0, 0}})
b := FromRows([][]float64{{0}, {1}})
q := Eye(2)
r := Eye(1)
res, _ := LQRContinuous(a, b, q, r)
fmt.Printf("K = [%.4f %.4f]\n", res.K.At(0, 0), res.K.At(0, 1))
Output: K = [1.0000 1.7321]
func LQRDiscrete ¶
LQRDiscrete designs a discrete-time infinite-horizon LQR for the system x_{k+1} = A x_k + B u_k minimizing Σ (xᵀ Q x + uᵀ R u). The optimal control is u = −K x with K = (R + Bᵀ P B)⁻¹ Bᵀ P A and P the stabilizing DARE solution.
type LU ¶
type LU struct {
// contains filtered or unexported fields
}
LU holds the result of an LU factorization with partial pivoting: a combined lower/upper factor lu, the row permutation piv, and the sign of the permutation (+1 or -1) used for the determinant.
func Factor ¶
Factor computes the LU decomposition of a square matrix with partial pivoting. It returns ErrDim for non-square input.
type LinearSystem ¶
type LinearSystem struct {
// A is the n×n state matrix.
A *Matrix
// B is the n×m input matrix.
B *Matrix
// C is the p×n output matrix.
C *Matrix
// D is the p×m feedthrough matrix.
D *Matrix
}
LinearSystem is a continuous- or discrete-time linear time-invariant model x' = A x + B u (continuous) or x_{k+1} = A x_k + B u_k (discrete), with output y = C x + D u.
func NewLinearSystem ¶
func NewLinearSystem(a, b, c, d *Matrix) *LinearSystem
NewLinearSystem builds a LinearSystem, filling C with the identity and D with zeros when they are nil.
func (*LinearSystem) Output ¶
func (s *LinearSystem) Output(x, u []float64) []float64
Output returns y = C x + D u for the system.
func (*LinearSystem) SimulateContinuous ¶
func (s *LinearSystem) SimulateContinuous(x0 []float64, control func(t float64) []float64, dt float64, steps int) [][]float64
SimulateContinuous integrates the continuous-time system with a fixed control u(t) held over each step of size dt using classical RK4, returning the state trajectory at the sample times 0, dt, …, steps·dt.
func (*LinearSystem) SimulateDiscrete ¶
func (s *LinearSystem) SimulateDiscrete(x0 []float64, us [][]float64) [][]float64
SimulateDiscrete simulates the discrete-time system for the given control sequence, returning the state trajectory x[0..len(us)].
func (*LinearSystem) StepDiscrete ¶
func (s *LinearSystem) StepDiscrete(x, u []float64) []float64
StepDiscrete advances the discrete-time state one step: x_{k+1} = A x + B u.
type MDP ¶
type MDP struct {
// States is the number of states.
States int
// Actions is the number of actions.
Actions int
// Trans[a] is the States×States transition matrix under action a; each row
// must sum to one.
Trans []*Matrix
// Reward is a States×Actions matrix of expected immediate rewards.
Reward *Matrix
// Gamma is the discount factor in [0, 1).
Gamma float64
}
MDP is a finite, discounted Markov decision process with the standard reward-maximization convention. Transitions for each action are stored as row-stochastic matrices and rewards as expected immediate rewards per state-action pair.
func NewMDP ¶
NewMDP constructs an MDP from per-action transition matrices, a reward matrix and a discount factor, validating the dimensions.
func (*MDP) BellmanBackup ¶
BellmanBackup applies one Bellman optimality update, returning the improved value function and the greedy policy that attains it.
func (*MDP) GaussSeidelValueIteration ¶
func (m *MDP) GaussSeidelValueIteration(tol float64, maxIter int) *ValueIterationResult
GaussSeidelValueIteration runs value iteration using in-place (Gauss–Seidel) updates, which typically converges in fewer sweeps than the Jacobi form.
func (*MDP) GreedyPolicy ¶
GreedyPolicy returns the greedy policy with respect to a value function.
func (*MDP) ModifiedPolicyIteration ¶
func (m *MDP) ModifiedPolicyIteration(k, maxIter int, tol float64) (*PolicyIterationResult, error)
ModifiedPolicyIteration runs modified policy iteration, evaluating each policy with a fixed number k of Bellman expectation sweeps rather than an exact solve.
func (*MDP) PolicyEvaluationExact ¶
PolicyEvaluationExact computes the exact value function of a deterministic policy by solving the linear system (I − γ P_π) V = r_π.
func (*MDP) PolicyEvaluationIterative ¶
PolicyEvaluationIterative computes the value function of a policy by iterative application of the Bellman expectation operator.
func (*MDP) PolicyIteration ¶
func (m *MDP) PolicyIteration(maxIter int) (*PolicyIterationResult, error)
PolicyIteration runs Howard's policy iteration with exact policy evaluation, returning the optimal policy and its value function.
func (*MDP) PolicyMatrices ¶
PolicyMatrices returns the transition matrix P_π and reward vector r_π induced by a deterministic policy.
func (*MDP) QValues ¶
QValues returns the States×Actions matrix of action values Q(s, a) = R(s, a) + γ Σ_{s'} P_a(s, s') V(s') for a given value function.
func (*MDP) ValueIteration ¶
func (m *MDP) ValueIteration(tol float64, maxIter int) *ValueIterationResult
ValueIteration runs value iteration until the max-norm change between sweeps falls below tol or maxIter sweeps have elapsed.
Example ¶
stay := FromRows([][]float64{{1, 0}, {0, 1}})
jump := FromRows([][]float64{{1, 0}, {1, 0}})
reward := FromRows([][]float64{{1, 1}, {0, -0.1}})
m, _ := NewMDP([]*Matrix{stay, jump}, reward, 0.5)
res := m.ValueIteration(1e-12, 1000)
fmt.Printf("V = [%.2f %.2f], best action in state 1 = %d\n",
res.Value[0], res.Value[1], res.Policy[1])
Output: V = [2.00 0.90], best action in state 1 = 1
type Matrix ¶
type Matrix struct {
// contains filtered or unexported fields
}
Matrix is a dense real matrix stored in row-major order. It is the basic linear-algebra container used throughout the optimalcontrol package. The zero value is not usable; construct matrices with NewMatrix, Zeros and friends.
func BlockMatrix ¶
BlockMatrix assembles the 2×2 block matrix [[a b];[c d]].
func CAREResidual ¶
CAREResidual returns Aᵀ X + X A − X B R⁻¹ Bᵀ X + Q, the residual of the continuous algebraic Riccati equation for a candidate solution X.
func Cholesky ¶
Cholesky computes the lower-triangular Cholesky factor L with A = L Lᵀ for a symmetric positive-definite matrix. It returns an error if A is not SPD.
func ColumnVector ¶
ColumnVector builds an n×1 matrix from v.
func ContinuousGain ¶
ContinuousGain returns the continuous LQR feedback gain K = R⁻¹ Bᵀ P.
func ControllabilityGramianContinuous ¶
ControllabilityGramianContinuous returns the infinite-horizon controllability Gramian Wc solving A Wc + Wc Aᵀ + B Bᵀ = 0 for a stable A.
func ControllabilityGramianDiscrete ¶
ControllabilityGramianDiscrete returns the discrete controllability Gramian solving A Wc Aᵀ − Wc + B Bᵀ = 0 for a Schur-stable A.
func ControllabilityMatrix ¶
ControllabilityMatrix returns the controllability matrix [B, AB, A²B, …, Aⁿ⁻¹B] for the pair (A, B).
func DAREResidual ¶
DAREResidual returns the residual X − (Aᵀ X A − Aᵀ X B (R+BᵀXB)⁻¹ BᵀXA + Q) of the discrete algebraic Riccati equation for a candidate solution X.
func DiscreteGain ¶
DiscreteGain returns the discrete LQR feedback gain K = (R + Bᵀ P B)⁻¹ Bᵀ P A for a given Riccati solution P.
func DiscretizeZOH ¶
DiscretizeZOH returns the exact zero-order-hold discretization (Ad, Bd) of the continuous pair (A, B) for sample time dt, computed from the matrix exponential of the augmented block matrix [[A, B],[0, 0]].
func HamiltonianMatrix ¶
HamiltonianMatrix returns the 2n×2n Hamiltonian matrix
H = [[ A, -S ],
[ -Q, -Aᵀ ]], with S = B R⁻¹ Bᵀ,
whose stable invariant subspace yields the stabilizing solution of the continuous-time algebraic Riccati equation.
func LeastSquaresMatrix ¶
LeastSquaresMatrix solves A X = B in the least-squares sense column by column.
func LyapunovContinuousResidual ¶
LyapunovContinuousResidual returns Aᵀ X + X A + Q, the residual of the continuous Lyapunov equation, useful for verifying a solution.
func LyapunovDiscreteResidual ¶
LyapunovDiscreteResidual returns Aᵀ X A − X + Q, the residual of the discrete Lyapunov equation.
func MatrixExp ¶
MatrixExp returns the matrix exponential exp(A) using a scaling-and-squaring scheme with a truncated Taylor series. It is accurate for the moderate-sized matrices used in control applications.
func MatrixSign ¶
MatrixSign computes the matrix sign function of a square matrix with no purely imaginary eigenvalues, using the Newton iteration with determinantal scaling
Z_{k+1} = ½ ( c_k Z_k + c_k⁻¹ Z_k⁻¹ ), c_k = |det Z_k|^{-1/N}.
The returned matrix S satisfies S² = I and shares the eigenvectors of A, with eigenvalues ±1 according to the sign of the real part of A's eigenvalues.
func NewMatrix ¶
NewMatrix builds an r×c matrix from the supplied row-major data. The data slice is copied. It panics if len(data) != r*c.
func ObservabilityGramianContinuous ¶
ObservabilityGramianContinuous returns the infinite-horizon observability Gramian Wo solving Aᵀ Wo + Wo A + Cᵀ C = 0 for a stable A.
func ObservabilityGramianDiscrete ¶
ObservabilityGramianDiscrete returns the discrete observability Gramian solving Aᵀ Wo A − Wo + Cᵀ C = 0 for a Schur-stable A.
func ObservabilityMatrix ¶
ObservabilityMatrix returns the observability matrix [C; CA; CA²; …; CAⁿ⁻¹] for the pair (A, C).
func SolveCARE ¶
SolveCARE solves the continuous-time algebraic Riccati equation for its stabilizing solution. It is an alias for the robust matrix-sign method SolveCARESign.
func SolveCAREKleinman ¶
SolveCAREKleinman solves the continuous-time algebraic Riccati equation by Kleinman's Newton iteration starting from a stabilizing gain k0 (so that A − B k0 is Hurwitz). Each step solves a Lyapunov equation, giving quadratic convergence to the stabilizing solution.
func SolveCARESign ¶
SolveCARESign solves the continuous-time algebraic Riccati equation
Aᵀ X + X A − X B R⁻¹ Bᵀ X + Q = 0
for the symmetric stabilizing solution X using the matrix-sign-function method applied to the Hamiltonian matrix. Q must be symmetric positive semidefinite and R symmetric positive definite; the pair (A, B) must be stabilizable and (A, Q) detectable.
func SolveDARE ¶
SolveDARE solves the discrete-time algebraic Riccati equation for its stabilizing solution. It uses the iterative recursion SolveDAREIter with a generous iteration budget.
Example ¶
a := FromRows([][]float64{{1}})
b := FromRows([][]float64{{1}})
q := FromRows([][]float64{{1}})
r := FromRows([][]float64{{1}})
p, _ := SolveDARE(a, b, q, r)
fmt.Printf("P = %.4f\n", p.At(0, 0)) // golden ratio
Output: P = 1.6180
func SolveDAREIter ¶
SolveDAREIter solves the discrete-time algebraic Riccati equation
X = Aᵀ X A − Aᵀ X B (R + Bᵀ X B)⁻¹ Bᵀ X A + Q
by fixed-point iteration of the Riccati recursion, starting from X₀ = Q.
func SolveLyapunovContinuous ¶
SolveLyapunovContinuous solves the continuous-time Lyapunov equation Aᵀ X + X A + Q = 0 for the symmetric matrix X.
func SolveLyapunovDiscrete ¶
SolveLyapunovDiscrete solves the discrete-time (Stein) Lyapunov equation Aᵀ X A − X + Q = 0 for the symmetric matrix X.
func SolveMatrix ¶
SolveMatrix solves A X = B for the matrix X.
func SolveSylvester ¶
SolveSylvester solves the Sylvester equation A X + X B = C for X, where A is m×m, B is n×n and C is m×n. It uses the Kronecker-product formulation (I⊗A + Bᵀ⊗I) vec(X) = vec(C) and a dense linear solve, which is robust for the small systems arising in control design.
func SymEigen ¶
SymEigen computes eigenvalues and orthonormal eigenvectors of a symmetric matrix using the cyclic Jacobi method. The eigenvalues are returned as a slice and the eigenvectors as the columns of V, so that A V = V diag(w).
func (*Matrix) ApproxEqual ¶
ApproxEqual reports whether a and b have the same shape and entries within absolute tolerance tol.
func (*Matrix) FrobeniusNorm ¶
FrobeniusNorm returns the Frobenius norm of the matrix.
func (*Matrix) IsSymmetric ¶
IsSymmetric reports whether the matrix is square and symmetric within tol.
func (*Matrix) Symmetrize ¶
Symmetrize returns (A + Aᵀ)/2, the symmetric part of a square matrix.
type PolicyIterationResult ¶
type PolicyIterationResult struct {
// Policy is the optimal deterministic policy.
Policy []int
// Value is the value function of the optimal policy.
Value []float64
// Iterations is the number of policy-improvement steps performed.
Iterations int
}
PolicyIterationResult holds the outcome of policy iteration.
type TPBVPSolution ¶
type TPBVPSolution struct {
// Times holds the ascending sample times from 0 to T.
Times []float64
// X holds the state at each sample time.
X [][]float64
// P holds the costate (adjoint) at each sample time.
P [][]float64
// U holds the optimal control at each sample time.
U [][]float64
}
TPBVPSolution holds the sampled solution of a two-point boundary-value problem arising from Pontryagin's minimum principle: the state, costate and optimal control trajectories on a time grid.
func IndirectShooting ¶
func IndirectShooting( x0 []float64, f func(x, u []float64) []float64, g func(x, p, u []float64) []float64, uOpt func(x, p []float64) []float64, termGrad func(x []float64) []float64, tFinal float64, steps, maxIter int, tol float64, ) ([]float64, *TPBVPSolution, error)
IndirectShooting solves a fixed-final-time two-point boundary-value problem by single shooting on the initial costate. The problem is specified by the augmented dynamics
x' = f(x, u), p' = g(x, p, u), u = uOpt(x, p),
with x(0) = x0 and terminal condition p(T) = termGrad(x(T)). Newton's method with a finite-difference Jacobian is applied to the shooting residual. It returns the initial costate and the sampled trajectory.
func PontryaginLQ ¶
func PontryaginLQ(a, b, q, r, qf *Matrix, x0 []float64, tFinal float64, steps int) (*TPBVPSolution, error)
PontryaginLQ solves the fixed-final-time linear-quadratic optimal-control problem
min ½ x(T)ᵀ Qf x(T) + ½ ∫₀ᵀ (xᵀ Q x + uᵀ R u) dt s.t. x' = A x + B u, x(0) = x0,
via Pontryagin's minimum principle. It forms the Hamiltonian two-point boundary-value problem, propagates it with the Hamiltonian state-transition matrix, solves the terminal transversality condition p(T) = Qf x(T) for the initial costate, and returns the sampled state, costate and control.
type ValueIterationResult ¶
type ValueIterationResult struct {
// Value is the (approximately) optimal value function.
Value []float64
// Policy is the greedy policy with respect to Value.
Policy []int
// Iterations is the number of sweeps performed.
Iterations int
// Converged reports whether the max-norm residual fell below the tolerance.
Converged bool
}
ValueIterationResult holds the outcome of value iteration.