I've run into an issue where I can't get APOPT to optimize an unconstrained single piecewise linear, and it's really throwing me for a loop. I feel like there's something I'm not understanding about model.pwl
, but it's hard (for me) to find documentation outside of the GEKKO docs. Here's my minimal example:
model = GEKKO(remote=False)
model.options.SOLVER = 1
model.solver_options = ["minlp_as_nlp 0"]
x = model.sos1([0, 1, 2, 3, 4]) # This can also be model.Var(lb=0, ub=4), same result.
pwl = model.Var()
model.pwl(x, pwl, [0, 1, 2, 3, 4], [30, 30.1, 30.2, 30.3, 30.4], bound_x=True)
model.Minimize(pwl)
model.solve(display=True)
print(x.value)
print(pwl.value)
print(model.options.objfcnval)
The output that I get is:
----------------------------------------------------------------
APMonitor, Version 1.0.3
APMonitor Optimization Suite
----------------------------------------------------------------
--------- APM Model Size ------------
Each time step contains
Objects : 1
Constants : 0
Variables : 2
Intermediates: 0
Connections : 2
Equations : 1
Residuals : 1
Piece-wise linear model pwl1points: 5
Number of state variables: 12
Number of total equations: - 5
Number of slack variables: - 0
---------------------------------------
Degrees of freedom : 7
----------------------------------------------
Steady State Optimization with APOPT Solver
----------------------------------------------
Iter Objective Convergence
0 3.39503E+01 3.01000E+01
1 3.22900E+01 1.00000E-10
2 3.22000E+01 2.22045E-16
4 3.22000E+01 0.00000E+00
Successful solution
---------------------------------------------------
Solver : APOPT (v1.0)
Solution time : 3.819999999541324E-002 sec
Objective : 32.2000000000000
Successful solution
---------------------------------------------------
2.0
30.2
32.2
This is unexpected to me, as the obvious minimal value is 30 for the pwl.
A cubic spline is much more reliable in optimization than a piecewise linear function because it doesn't rely on slack variables and switching conditions.
from gekko import GEKKO
model = GEKKO(remote=False)
model.options.SOLVER = 1
x = model.Var(lb=0, ub=4, integer=True)
y = model.Var()
model.cspline(x, y, [0, 1, 2, 3, 4], [30, 30.1, 30.2, 30.3, 30.4], bound_x=True)
model.Minimize(y)
model.solve(display=True)
print(x.value)
print(y.value)
print(model.options.objfcnval)
Here is the output:
----------------------------------------------------------------
APMonitor, Version 1.0.3
APMonitor Optimization Suite
----------------------------------------------------------------
--------- APM Model Size ------------
Each time step contains
Objects : 1
Constants : 0
Variables : 2
Intermediates: 0
Connections : 2
Equations : 1
Residuals : 1
Number of state variables: 2
Number of total equations: - 1
Number of slack variables: - 0
---------------------------------------
Degrees of freedom : 1
----------------------------------------------
Steady State Optimization with APOPT Solver
----------------------------------------------
Iter: 1 I: 0 Tm: 0.00 NLPi: 2 Dpth: 0 Lvs: 0 Obj: 3.00E+01 Gap: 0.00E+00
Successful solution
---------------------------------------------------
Solver : APOPT (v1.0)
Solution time : 1.970000000437722E-002 sec
Objective : 30.0000000000000
Successful solution
---------------------------------------------------
[0.0]
[30.0]
30.0
A few other notes:
m.Var(integer=True)
instead of m.sos1()
when potential values are integers. The m.sos1()
function is for discrete non-integer values.gk0_model.apm
as a text file in the run directory m._path
(or open tmp folder with m.open_folder()
).