@@ -1566,6 +1566,142 @@ end
15661566 @test X̂_lin ≈ X̂_nonlin atol= 1e-3 rtol= 1e-3
15671567end
15681568
1569+
1570+ @testitem " MovingHorizonEstimator construction with custom constraint (LinModel)" setup= [SetupMPCtests] begin
1571+ using . SetupMPCtests, ControlSystemsBase, LinearAlgebra
1572+ linmodel = LinModel (sys, Ts, i_u= [1 ,2 ], i_d= [3 ])
1573+ linmodel = setop! (linmodel, uop= [10 ,50 ], yop= [50 ,30 ], dop= [5 ])
1574+
1575+ # Custom constraint: simple bound on states using only X̂e and ε
1576+ function gc_lin (X̂e, _ , _ , _ , _ , _ , _ , _ ,nX̂e , ε)
1577+ gc = X̂e .- 100 .- ε # all states must be <= 100
1578+ return [gc; zeros (nX̂e .- length (X̂e))]
1579+ end
1580+ He = 3
1581+ nx̂ = length (linmodel. A) + 2 # number of states (with augmentation)
1582+ nc = nx̂ * (He+ 1 ) # Approximate constraint count for He=3
1583+ nX̂e = nx̂ * (He+ 1 )
1584+ mhe = MovingHorizonEstimator (linmodel, Cwt= 1e5 , He= He, gc= gc_lin, nc= nc, p= nX̂e)
1585+
1586+ @test mhe. con. nc == nc
1587+ @test mhe. nε > 0
1588+ @test mhe. He == 3
1589+ end
1590+
1591+ @testitem " MovingHorizonEstimator custom constraint violation (LinModel)" setup= [SetupMPCtests] begin
1592+ using . SetupMPCtests, ControlSystemsBase, LinearAlgebra
1593+ linmodel = LinModel (sys, Ts, i_u= [1 ,2 ], i_d= [3 ])
1594+ linmodel = setop! (linmodel, uop= [10 ,50 ], yop= [50 ,30 ], dop= [5 ])
1595+
1596+ # Custom constraint function using only X̂e and ε
1597+ # This constraint enforces that the first state must be >= 0.5
1598+ function gc_constraint! (LHS, X̂e, V̂e, Ŵe, Ue, Yem, De, P̄, x̄, p, ε)
1599+ nx̂ = 4 # number of states (with augmentation)
1600+ # Extract and constrain first state at each time step
1601+ for i in 1 : div (length (X̂e), nx̂)
1602+ LHS[(i- 1 )+ 1 ] = 0.5 - X̂e[(i- 1 )* nx̂ + 1 ] # First state >= 0.5
1603+ end
1604+ return nothing
1605+ end
1606+
1607+ nc = 3 # One constraint per time step (He=1 initially, but grows)
1608+ mhe = MovingHorizonEstimator (linmodel, He= 1 , gc= gc_constraint!, nc= nc, Cwt= 1e3 , nint_ym= 0 )
1609+
1610+ preparestate! (mhe, [50 , 30 ], [5 ])
1611+ x̂ = updatestate! (mhe, [10 , 50 ], [50 , 30 ], [5 ])
1612+ # The constraint should be satisfied (first state should be >= 0.5)
1613+ @test x̂[1 ] >= 0.5 - 0.1 # Allow some tolerance
1614+ end
1615+
1616+ @testitem " MovingHorizonEstimator custom constraint violation (NonLinModel)" setup= [SetupMPCtests] begin
1617+ using . SetupMPCtests, ControlSystemsBase, LinearAlgebra
1618+ using JuMP, Ipopt
1619+ linmodel = LinModel (sys, Ts, i_u= [1 ,2 ], i_d= [3 ])
1620+ linmodel = setop! (linmodel, uop= [10 ,50 ], yop= [50 ,30 ], dop= [5 ])
1621+
1622+ f = (x,u,d,model) -> model. A* x + model. Bu* u + model. Bd* d
1623+ h = (x,d,model) -> model. C* x + model. Dd* d
1624+ nonlinmodel = NonLinModel (f, h, Ts, 2 , 4 , 2 , 1 , solver= nothing , p= linmodel)
1625+ nonlinmodel = setop! (nonlinmodel, uop= [10 ,50 ], yop= [50 ,30 ], dop= [5 ])
1626+
1627+ # Custom constraint function using only X̂e and ε
1628+ # This constraint enforces that the first state must be >= 0.5
1629+ function gc_constraint_nl! (LHS, X̂e, V̂e, Ŵe, Ue, Yem, De, P̄, x̄, p, ε)
1630+ nx̂ = 6 # number of states (with augmentation)
1631+ # Extract and constrain first state at each time step
1632+ for i in 1 : div (length (X̂e), nx̂)
1633+ LHS[(i- 1 )+ 1 ] = 0.5 - X̂e[(i- 1 )* nx̂ + 1 ] # First state >= 0.5
1634+ end
1635+ return nothing
1636+ end
1637+
1638+ nc = 3 # One constraint per time step
1639+ optim = Model (Ipopt. Optimizer)
1640+ mhe = MovingHorizonEstimator (nonlinmodel, He= 1 , gc= gc_constraint_nl!, nc= nc, Cwt= 1e3 , nint_ym= 0 ; optim)
1641+
1642+ preparestate! (mhe, [50 , 30 ], [5 ])
1643+ x̂ = updatestate! (mhe, [10 , 50 ], [50 , 30 ], [5 ])
1644+ # The constraint should be satisfied (first state should be >= 0.5)
1645+ @test x̂[1 ] >= 0.5 - 0.1 # Allow some tolerance
1646+ end
1647+
1648+ @testitem " MovingHorizonEstimator custom constraint on noise (LinModel)" setup= [SetupMPCtests] begin
1649+ using . SetupMPCtests, ControlSystemsBase, LinearAlgebra
1650+ linmodel = LinModel (sys, Ts, i_u= [1 ,2 ], i_d= [3 ])
1651+ linmodel = setop! (linmodel, uop= [10 ,50 ], yop= [50 ,30 ], dop= [5 ])
1652+
1653+ # Custom constraint function using Ŵe and ε
1654+ # This constraint enforces that process noise must be <= 0
1655+ function gc_noise_constraint! (LHS, X̂e, V̂e, Ŵe, Ue, Yem, De, P̄, x̄, p, ε)
1656+ nx̂ = 4 # number of states (with augmentation)
1657+ # Constraint: all process noise components must be <= 0
1658+ for i in 1 : length (Ŵe)
1659+ LHS[i] = Ŵe[i] # Ŵe <= 0
1660+ end
1661+ return nothing
1662+ end
1663+
1664+ nc = 4 * 2 # Two time steps with 4 states each
1665+ mhe = MovingHorizonEstimator (linmodel, He= 1 , gc= gc_noise_constraint!, nc= nc, Cwt= 1e3 , nint_ym= 0 )
1666+
1667+ preparestate! (mhe, [50 , 30 ], [5 ])
1668+ x̂ = updatestate! (mhe, [10 , 50 ], [50 , 30 ], [5 ])
1669+ # The constraint should be satisfied (process noise should be <= 0)
1670+ @test all (mhe. Ŵ .<= 0.1 ) # Allow some tolerance
1671+ end
1672+
1673+ @testitem " MovingHorizonEstimator custom constraint on noise (NonLinModel)" setup= [SetupMPCtests] begin
1674+ using . SetupMPCtests, ControlSystemsBase, LinearAlgebra
1675+ using JuMP, Ipopt
1676+ linmodel = LinModel (sys, Ts, i_u= [1 ,2 ], i_d= [3 ])
1677+ linmodel = setop! (linmodel, uop= [10 ,50 ], yop= [50 ,30 ], dop= [5 ])
1678+
1679+ f = (x,u,d,model) -> model. A* x + model. Bu* u + model. Bd* d
1680+ h = (x,d,model) -> model. C* x + model. Dd* d
1681+ nonlinmodel = NonLinModel (f, h, Ts, 2 , 4 , 2 , 1 , solver= nothing , p= linmodel)
1682+ nonlinmodel = setop! (nonlinmodel, uop= [10 ,50 ], yop= [50 ,30 ], dop= [5 ])
1683+
1684+ # Custom constraint function using Ŵe and ε
1685+ # This constraint enforces that process noise must be <= 0
1686+ function gc_noise_constraint_nl! (LHS, X̂e, V̂e, Ŵe, Ue, Yem, De, P̄, x̄, p, ε)
1687+ nx̂ = 6 # number of states (with augmentation)
1688+ # Constraint: all process noise components must be <= 0
1689+ for i in 1 : length (Ŵe)
1690+ LHS[i] = Ŵe[i] # Ŵe <= 0
1691+ end
1692+ return nothing
1693+ end
1694+
1695+ nc = 6 * 2 # Two time steps with 6 states each
1696+ optim = Model (Ipopt. Optimizer)
1697+ mhe = MovingHorizonEstimator (nonlinmodel, He= 1 , gc= gc_noise_constraint_nl!, nc= nc, Cwt= 1e3 , nint_ym= 0 ; optim)
1698+
1699+ preparestate! (mhe, [50 , 30 ], [5 ])
1700+ x̂ = updatestate! (mhe, [10 , 50 ], [50 , 30 ], [5 ])
1701+ # The constraint should be satisfied (process noise should be <= 0)
1702+ @test all (mhe. Ŵ .<= 0.1 ) # Allow some tolerance
1703+ end
1704+
15691705@testitem " ManualEstimator construction" setup= [SetupMPCtests] begin
15701706 using . SetupMPCtests, ControlSystemsBase, LinearAlgebra
15711707 linmodel = LinModel (sys,Ts,i_u= [1 ,2 ])
0 commit comments