Closed whoburg closed 3 years ago
You set initial conditions when you define your position and velocity.
import numpy as np
import matplotlib.pyplot as plt
from gekko import GEKKO
m = GEKKO()
ntime = 51
m.time = np.linspace(0, 2, ntime)
# set initial conditions [10,5]
x = m.Var(value=10)
v = m.Var(value=5)
U = m.MV()
U.STATUS = 1
m.Equation(x.dt() == v)
m.Equation(v.dt() == U)
# fix endpoint
m.fix(x, pos=len(m.time)-1, val=0)
m.fix(v, pos=len(m.time)-1, val=0)
m.Obj(U**2)
m.options.IMODE = 6
m.open_folder()
m.solve()
plt.figure(figsize=(4, 3))
plt.subplot(2, 1, 1)
plt.plot(m.time, x.value, label=r"$x$")
plt.plot(m.time, v.value, label=r"$v$")
plt.legend()
plt.subplot(2, 1, 2)
plt.plot(m.time, U.value, label=r"$U$")
plt.legend()
plt.xlabel("Time")
plt.show()
Alternatively, you can set x.value = 10
and v.value = 5
before the solve to define the initial conditions. The initial conditions also give a guess for the remainder of the time horizon so that the solver starts from those values for all time points. You can also feed in a vector of values if you'd like to provide a better starting guess. You are correct that value
is used for input to the model and to store the results after the solve is complete. You'll also see that x.value
is an object with two additional x.valve.value
that stores the values and x.value.change
that is set to True
when the value is updated. This is so that the prior values are used in applications like MPC where the prior solution is time-shifted to initialize the next solution.
The reason that your m.fix()
doesn't work for initial conditions is because of 2 reasons:
m.open_folder()
.x.value=10
instead for initial conditions.There are a couple similar applications (pendulum cart and inverted pendulum) on the Machine Learning and Dynamic Optimization Course to yours.
Thank you for the excellent explanation. Everything above makes sense, but I'm still noticing things that make me think there's a potential bug with the indexing.
Understood that APM is 1-based, but then why do we fix
the endpoint to pos=len(m.time)-1
instead of pos=len(m.time)
?
Note that if we change two lines in (your updated version of the) example code to
# fix endpoint
m.fix(x, pos=len(m.time), val=0)
m.fix(v, pos=len(m.time), val=0)
then the code fails with the following traceback
Traceback (most recent call last):
File "block-bug-report.py", line 24, in <module>
m.solve()
File "...venv/lib/python3.7/site-packages/gekko/gekko.py", line 1965, in solve
self._write_csv()
File "...venv/lib/python3.7/site-packages/gekko/gk_write_files.py", line 225, in _write_csv
t[i[0]+1] = i[1] #index is +1 because of prepended header
IndexError: index 52 is out of bounds for axis 0 with size 52
In the GEKKO Optimization of Multiple Linked Phases Example, the connection is achieved by the following code:
# Connect phases together at endpoints
for i in range(n-1):
m.Connection(x[i+1],x[i],1,len(m.time)-1,1,nodes)
m.Connection(x[i+1],'CALCULATED',pos1=1,node1=1)
Why a connection from node 1 to node N-1 in a 1-indexed setting? E.g. if there are 5 nodes, we connect node 1 to node 4?
In my original example code that awkwardly uses fix
to set the initial condition as well as the final condition, I fixed pos=0
and pos=len(m.time)-1
, as if the positions were 0-indexed. This code actually produces the expected solution and plots, but also with the Warning: connection failed
and unable to locate variable
messages. The fact that the code still runs makes me think there might be a strange bug in the conversion from 0-indexing to 1-indexing.
Thanks for entertaining the questions and possible bug report. Amazing software. Really enjoying playing with it.
@whoburg These quirks are from the implementation of orthogonal collocation on finite elements. The documentation says that the last node of one finite element overlaps with the first node of the next finite element. Instead of having node[1] on element[n], there is only a reference to the identical point node[-1] on element[n-1]. You can see some of these details in the output files like .t0 and .dxdt with a local solve and m.open_folder()
.
@APMonitor the documentation for fix
and Connection
should be clarified.
Thanks @loganbeal, that explains a lot. So does the pos
kwarg of fix
refer to an element as opposed to a node? I think that explains a lot -- much of my confusion above was due to thinking pos
referred to node numbering as opposed to element numbering. Duh.
On the documentation, https://gekko.readthedocs.io/en/latest/quick_start.html#connections currently says "If pos1 or pos2 is not None, the associated var must be a GEKKO variable and the position is the (0-indexed) time-discretized index of the variable."
I've spent an embarrassing amount of time trying to understand the comment about 0-indexing there. I also think the terminology "index of the variable" is potentially confusing -- perhaps this should refer to either nodes or elements?
The next version of Gekko has m.fix_initial(x)
, m.fix_final(x)
, m.free_initial(x)
, and m.free_final(x)
to help with these common operations. This will hopefully resolve the concerns about indexing.
Another option is to use m.Connection(x[i+1],x[i],1,'end',1,'end')
. The 'end'
designation is the last node or the last interval in the horizon.
Excellent, the new helper methods sound like great additions. Looking forward to trying them out.
I still think it would be worth updating the Connection
documentation to clarify whether the pos
and node
arguments refer to node or element indices.
Thanks again for the excellent software and responsiveness.
Thanks for the suggestion. I updated the documentation here: https://apmonitor.com/wiki/index.php/Main/OptionApmNodes and will put a link to this in ReadTheDocs.
This is possibly related to #59, but possibly a separate issue. Here is a GEKKO implementation of a simple optimal control problem -- moving a double integrator (i.e. a frictionless brick) to the origin in a fixed amount of time.
In the code below,
fix
is used to set both the initial condition (right of the origin and moving right) and the desired final condition of x=0, v=0.The code runs and generates the expected plots, but also prints four unexpected
Warning: connection failed
,unable to locate variable
messages.A related question: what is the recommended way to set an initial condition in GEKKO when IMODE=6? Most examples in the documentation seem to set initial conditions using the
value
kwarg ofGEKKO.Var
. As a new user I find this somewhat confusing -value
seems to be used both to set an initial guess (to then later be manipulated by the optimizer) and to fix initial conditions. Does the behavior really change from a) fixing, for the 0-index of the variable, to b) setting an initial guess, for all other indices of the variable?