Getting Started

Import nMPyC

After the successfull installation of the nMPyC package, nMPyC has to be imported to our code. This can be done as shown in the following code snippet

# Add nMPyC to path (if necessary)
import sys
sys.path.append('../../path-to-nmpyc')

# Import nmpyc
import nmpyc

Note that the first two lines can be omitted if nMPyC has already been added to the Python default path as described in the Installation section. In this case the command import nmpyc is sufficient to import the nMPyC library.

Note

Please use the nmpyc.nmpyc_array functions and the nmpyc.nmpyc_array.array class for the calculations in the code to ensure error-free functionality of the program. Further informations about this issue can be found in API References and in the FAQ section.

Creating the System Dynamics

To define the system dynamics of the optimal control problem, we have to create a nmpyc.system object. We can define the possibly time-dependent and nonlinear system dynamics using a function of the form

def f(t,x,u):
    y = nmpyc.array(nx)
    ...
    return y

If this function is created, the system can be initialized by calling

system = nmpyc.system(f,nx,nu,system_type)

Where nx is the dimension of the state, nu is the dimension of the control variable, and system_type is a string indicating whether the system is continuous (continuous) or discrete (discrete).

Furthermore, the parameters sampling_rate (sampling rate), t0 (initial time) and method can optionally be adjusted during the initialization of the system. The value of method determines the used integration method for the discretization of the differential equation in the continuous case. By default the CasADi integrator cvodes is used.

Further options of the used integration method can be defined by the command

system.set_integratorOptions(dict())

For more informations (also about the parameters and their standard values) see the API-References nmpyc.system.system.

Creating the Objective

To define the objective, we need to create – similar to the system dynamics – a nmpyc.objective object. To do so, we first define the stage cost

def l(t,x,u):
    ...
    return y

and add, optionally, a terminal cost of the form

def F(t,x):
    ...
    return y

Now we can initialize the objective by calling

objective = nmpyc.objective(l, F)
# Or alternatively without terminal costs
objective = nmpyc.objective(l)

For more informations see the API-References nmpyc.objective.objective.

Creating the Constraints

The optimal control problem can be extended with other constraints besides the necessary system dynamics. For this reason, we must first create an empty nmpyc.constraints object using the command

system = nmpyc.constraints()

We can now add the desired constraints to this object step by step. These constraints can be created in different ways. First, we can add box constraints in the form of bounds.

constraints.add_bound('lower', 'control', lbu) # lower bound for control
constraints.add_bound('upper', 'control', ubu) # upper bound for control

Here lbu or lbx is an nmpyc.nmpyc_array.array of dimension (1,nu) or (nu,1). To add bounds for the state or terminal state, replace control with state or terminal in the above code and adjust the dimension of the array accordingly.

In addition to box constraints, general inequality and equality constraints can also be inserted.

# Equality constraint h(t,x,u) = 0
def h(t,x,u):
   y = mpc.array(len_constr)
   ...
   return y
constraints.add_constr('eq', h)

# Inequality constraint g(t,x,u) >= 0
def g(t,x,u):
   y = mpc.array(len_constr)
   ...
   return y
constraints.add_constr('ineq', g)

Terminal constraints of the form \(H(t,x) = 0\) or \(G(t,x) \geq 0\) can also be added.

constraints.add_constr('terminal_eq', H)
constraints.add_constr('terminal_ineq', G)

Moreover it is possible to add linear equality and inequality constraints. For this purpose see nmpyc.constraints.constraints.add_constr(). For further general informations see the API-References nmpyc.constraints.constraints.

Running Simulations

After initializing all necessary objects, we can run simulations for our problem. We first create a mpc.model object and combine the different parts of the optimal control problem by calling

model = nmpyc.model(objective, system, constraints)

The nmpyc.constraints object is optional and can be omitted for a problem without constraints. Modyfying the default settings of the optimization, can be done with the help of the commands

model.opti.set_options(dict())
model.opti.set_solverOptions(dict())

For more informations about this methods see nmpyc.model.model.opti.

To start an open loop simulation, we execute the command

u_ol, x_ol = model.solve_ocp(x0,N,discount)

and for a closed loop simulation

res = model.mpc(x0,N,K,discount)

Here x0 is a nmpyc.nmpyc_array.array which defines the initial value, N is the MPC horizon and the parameter K defines the number of MPC iterations. The parameter discount is optional and defines the discount factor (the default is 1).

The result of the simulation can now be shown in the console by calling

print(res)

and as a visual output by calling

res.plot()

By default, the states and controls are displayed in two subplots. By passing a string as the first parameter (=args), the plot can be customized. For example, by calling

res.plot('state')

only the states are plotted. Other keywords are control for the control, cost for the stage costs, and phase to make a phase portrait of two states or controls. Furthermore, the plots displayed in this way can be additionally adjusted by further prameters, see nmpyc.result.result.plot().

Further, the model and the simulation results can be saved for later use with the functions

model.save('path')
res.save('path')

These saved files can then be loaded with the help of

model = nmpyc.model.load('path')
res = nmpyc.result.load('path')

Advanced topics

The procedure described above is only an excerpt of the possibilities of the nMPyC Python library. For example, it is also possible to create autonomous systems and use the linear quadratic structure of a problem. For further informations see the Examples and Templates section. And for the implementation of linear system dynamics and quadratic costs, see also nmpyc.system.system.LQP() and nmpyc.objective.objective.LQP().