Skip to content

Commit

Permalink
updated the quickstart example as a script and updated the docs accor…
Browse files Browse the repository at this point in the history
…dingly
  • Loading branch information
BrechtBa committed Aug 22, 2017
1 parent 92ff60f commit fe607f1
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 97 deletions.
118 changes: 25 additions & 93 deletions doc/source/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,13 @@ Therfore it requires an :code:`mpcpy.stateestimation` object and an
Example
^^^^^^^

.. code-block:: python
mpc = mpcpy.MPC(emulator,control,boundaryconditions,emulationtime=10000,resulttimestep=10)
# in this statement the emulator, control and boundaryconditions parameters should be defined according to the following documentation
When the emulator, control and boundaryconditions objects are defined, an mpc
object can be instantiated:

.. literalinclude:: ../../examples/quickstart_example.py
:lines: 173



Emulator
--------
Expand Down Expand Up @@ -89,48 +90,8 @@ a dictionary of inputs. During an MPC simulation this is handled by the
Example
^^^^^^^

.. code-block:: python
class Emulator(mpcpy.Emulator):
"""
A custom system emulator
"""
def simulate(self,starttime,stoptime,input):
dt = 60
time = np.arange(starttime,stoptime+dt,dt,dtype=np.float)
# initialize
x = np.ones_like(time)*self.res['x'][-1]
# interpolate inputs
u = np.interp(time,input['time'],input['u'])
d = np.interp(time,input['time'],input['d'])
# perform simulation
for i,t in enumerate(time[:-1]):
# dx/dt = A*x + d + u
x[i+1] = x[i] + ( self.parameters['A']*x[i] + d[i] + u[i] )*dt
# create and return a results dict
res = {
'time': time,
'x': x,
'd': d,
'u': u,
}
return res
emulator = Emulator(['u','d'],parameters={'A':-0.2},initial_conditions={'x':0})
emulator.initialize()
time = np.arange(0.,1001.,100.)
d = np.random.random(len(time)) - 0.5
u = np.ones(len(time))
res = emulator(time,{'time':time,'d',d,'u',u})
.. literalinclude:: ../../examples/quickstart_example.py
:lines: 25-54,133-143



Expand All @@ -148,16 +109,11 @@ the required time points in the :code:`mpcpy.emulator` and
Example
^^^^^^^

.. code-block:: python
.. literalinclude:: ../../examples/quickstart_example.py
:lines: 148-153

time = np.arange(0.,1001.,100.)
d = np.random.random(len(time)) - 0.5
boundaryconditions = mpcpy.boundaryconditions({'time':time,'d',d})
bcs = boundaryconditions(np.array([0,20,40,60,100]))


Control
-------
The emulator requires control inputs which are generated by an object derived
Expand All @@ -181,25 +137,16 @@ control signal values over the entire control horizon.

Example
^^^^^^^

.. code-block:: python
class RandomControl(mpcpy.Control):
"""
A nonsense example to illustrate how a child control class can be defined
"""
def formulation(self):
self.newparameter = -1
def solution(self,sta,pre):
return {'time':pre['time'],'u':self.newparameter*sta['x'] + self.parameters['B']*np.random.random(len(pre['time']))}
control = RandomControl(stateestimation,prediction,parameters={'B':0.5},horizon=1000.,timestep=100.,receding=100.)
# in this statement the stateestimation and prediction parameters should be defined according to the following documentation

.. literalinclude:: ../../examples/quickstart_example.py
:lines: 58-119

When the stateestimation and prediction objects are defined, a control object
can be instantiated:

.. literalinclude:: ../../examples/quickstart_example.py
:lines: 172



State estimation
Expand All @@ -217,15 +164,8 @@ state at that time.
Example
^^^^^^^

.. code-block:: python
class StateestimationPerfect(mpcpy.Stateestimation):
def stateestimation(self,time):
return {'x': np.interp(time,self.emulator.res['time'],self.emulator.res['x'])}
stateestimation = StateestimationPerfect(emulator)
sta = stateestimation(0)
.. literalinclude:: ../../examples/quickstart_example.py
:lines: 124-130,158-160



Expand All @@ -241,14 +181,9 @@ conditions. From these values predictions may be derived.
Example
^^^^^^^

.. code-block:: python
.. literalinclude:: ../../examples/quickstart_example.py
:lines: 165-167

# continuing from the boundaryconditions object defined above
prediction = mpcpy.Prediction(boundaryconditions)
pre = prediction(np.array([0,20,40,60,100]))


MPC
Expand All @@ -262,8 +197,5 @@ returned in a dictionary.
Example
^^^^^^^

.. code-block:: python
control = RandomControl(stateestimation,prediction,parameters={'B':0.5},horizon=1000.,timestep=100.,receding=100.)
mpc = mpcpy.MPC(emulator,control,boundaryconditions,emulationtime=10000,resulttimestep=10)
res = mpc()
.. literalinclude:: ../../examples/quickstart_example.py
:lines: 172-177
3 changes: 2 additions & 1 deletion doc/source/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# mpcpy requirements.txt

numpy
numpydoc
numpydoc
pyomo
177 changes: 177 additions & 0 deletions examples/quickstart_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#!/usr/bin/env python
################################################################################
# Copyright 2015 Brecht Baeten
# This file is part of mpcpy.
#
# mpcpy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# mpcpy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with mpcpy. If not, see <http://www.gnu.org/licenses/>.
################################################################################

import numpy as np
import mpcpy
import pyomo.environ as pyomo

# Define an emulator class
class Emulator(mpcpy.Emulator):
"""
A custom system emulator
"""
def simulate(self,starttime,stoptime,input):
dt = 1
time = np.arange(starttime,stoptime+dt,dt,dtype=np.float)

# initialize
x = np.ones_like(time)*self.res['x'][-1]

# interpolate inputs
u = np.interp(time,input['time'],input['u'])
d = np.interp(time,input['time'],input['d'])

# perform simulation
for i,t in enumerate(time[:-1]):
# dx/dt = A*x + d + u
x[i+1] = x[i] + ( self.parameters['A']*x[i] + d[i] + u[i] )*dt

# create and return a results dict
res = {
'time': time,
'x': x,
'd': d,
'u': u,
}

return res



# Define a control class
class SetpointControl(mpcpy.Control):
"""
A control to keep the state as close to a set point as possible
"""
def formulation(self):
# create a pyomo model
model = pyomo.AbstractModel()

model.i = pyomo.Set()
model.ip = pyomo.Set()

model.time = pyomo.Param(model.ip)
model.d = pyomo.Param(model.ip, initialize=0.)
model.x = pyomo.Var(model.ip, domain=pyomo.Reals, initialize=0.)
model.u = pyomo.Var(model.ip, domain=pyomo.NonNegativeReals, bounds=(0.,1.), initialize=0.)

model.x0 = pyomo.Param(initialize=0.)

model.initialcondition = pyomo.Constraint(
rule=lambda model: model.x[0]==model.x0
)

model.constraint = pyomo.Constraint(
model.i,
rule=lambda model,i: (model.x[i+1]-model.x[i])/(model.time[i+1]-model.time[i]) == \
self.parameters['A']*model.x[i] + model.d[i] + model.u[i]
)

model.objective = pyomo.Objective(
rule=lambda model: sum( (model.x[i]-self.parameters['set'])**2 for i in model.i)
)

# store the model inside the object
self.model = model

def solution(self,sta,pre):
# create data and instantiate the pyomo model
ip = np.arange(len(pre['time']))
data = {
None:{
'i':{None: ip[:-1]},
'ip':{None: ip},
'time':{(i,): v for i,v in enumerate(pre['time'])},
'x0':{None: sta['x']},
'd':{(i,): pre['d'][i] for i in ip},
}
}

instance = self.model.create_instance(data)

# solve and return the contol inputs
optimizer = pyomo.SolverFactory('cplex')
results = optimizer.solve(instance)

sol = {
'time': np.array([pyomo.value(instance.time[i]) for i in instance.ip]),
'x': np.array([pyomo.value(instance.x[i]) for i in instance.ip]),
'u': np.array([pyomo.value(instance.u[i]) for i in instance.ip]),
'd': np.array([pyomo.value(instance.d[i]) for i in instance.ip]),
}

return sol



# Define a state estimation class
class StateestimationPerfect(mpcpy.Stateestimation):
"""
Perfect state estimation
"""
def stateestimation(self,time):
return {'x': np.interp(time,self.emulator.res['time'],self.emulator.res['x'])}



# instantiate the emulator
emulator = Emulator(['u','d'],parameters={'A':-0.2},initial_conditions={'x':0})

# test the emulator with some random data
time = np.arange(0.,1001.,10.)
d = np.random.random(len(time)) - 0.5
u = 1.0*np.ones(len(time))

emulator.initialize()
res = emulator(time,{'time':time,'d':d,'u':u})
print(res)



# create a boundaryconditions object
time = np.arange(0.,1001.,10.)
d = np.random.random(len(time)) - 0.5
boundaryconditions = mpcpy.Boundaryconditions({'time':time,'d':d})

bcs = boundaryconditions(np.array([0,20,40,60,100]))
print(bcs)



# create a stateestimation object
stateestimation = StateestimationPerfect(emulator)
sta = stateestimation(0)
print(sta)



# create a prediction object
prediction = mpcpy.Prediction(boundaryconditions)
pre = prediction(np.array([0,20,40,60,100]))
print(pre)



# create a control object and mpc object
control = SetpointControl(stateestimation,prediction,parameters={'A':-0.2,'set':3.0},horizon=100.,timestep=10.,receding=10.)
mpc = mpcpy.MPC(emulator,control,boundaryconditions,emulationtime=1000,resulttimestep=10)

# run the mpc
res = mpc()
print(res)
13 changes: 10 additions & 3 deletions mpcpy/emulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,16 @@ def __call__(self,time,input):
Parameters
----------
time : numpy array
times at which the results are requested
Times at which the results are requested.
input : dict
dictionary with values for the inputs of the model, time must be a
part of it
Dictionary with values for the inputs of the model, time must be a
part of it.
Returns
-------
dict
Dictionary with the complete simulation results.
Examples
--------
Expand Down Expand Up @@ -170,6 +175,8 @@ def __call__(self,time,input):
self.res[key] = res[key]
else:
self.res[key] = np.interp(time,res['time'],res[key])

return self.res


def set_initial_conditions(self,ini):
Expand Down

0 comments on commit fe607f1

Please sign in to comment.