CasADi demo

What is CasADi?

  • A tool for quick & efficient implementation of algorithms for dynamic optimization
  • Open source, LGPL-licensed, casadi.org
  • C++ / C++11
  • Interfaces to Python, Haskell, (Matlab?)
  • Numerical backends: IPOPT, Sundials, ...
  • Developers in group of Moritz Diehl:
    • Joel Andersson
    • Joris Gillis
    • Greg Horn

Outline of demo

  • Scalar expression (SX) graphs
  • Functions of SX graphs
  • Matrices of scalar expressions
  • Automatic differentiation (AD)
  • Integrators
  • Matrix expression (MX) graphs
  • Functions of MX graphs
  • Solving an optimal control problem

Scalar expression (SX) graphs


In [1]:
from pylab import *
from casadi import *
from casadi.tools import *  # for dotdraw
%matplotlib inline

In [2]:
x = SX.sym("x")  # scalar symbolic primitives
y = SX.sym("y")

z = x*sin(x+y)   # common mathematical operators

In [3]:
print z


(x*sin((x+y)))

In [4]:
dotdraw(z,direction="BT")



In [5]:
J = jacobian(z,x)
print J


@1=(x+y), (sin(@1)+(x*cos(@1)))

In [6]:
dotdraw(J,direction="BT")


Note 1: subexpressions are shared.

Graph $\leftrightarrow$ Tree
Different from Maple, Matlab symbolic, sympy, ...

A (very) little bit of Computer Algebra


In [7]:
print x*y/x-y


0

In [8]:
H = hessian(z,x)
print H


(SX(@1=(x+y), (cos(@1)+(cos(@1)-(x*sin(@1))))), SX(@1=(x+y), (sin(@1)+(cos(@1)*x))))

Functions of SX graphs

Sort graph into algorithm


In [9]:
f = SXFunction("f",[x,y],[z])

print f


 Number of inputs: 2
  Input 0, a.k.a. "i0", 1-by-1 (dense), No description available
  Input 1, a.k.a. "i1", 1-by-1 (dense), No description available
 Number of outputs: 1
  Output 0, a.k.a. "o0", 1-by-1 (dense), No description available
@0 = input[0][0];
@1 = input[1][0];
@1 = (@0+@1);
@1 = sin(@1);
@0 = (@0*@1);
output[0][0] = @0;

Note 2: re-use of tape variables: live-variables


In [10]:
print f([1.2,3.4])


[DM(-1.19243)]

In [11]:
print f([1.2,x+y])


[SX(@1=1.2, (@1*sin((@1+(x+y)))))]

In [12]:
f.generate("f")
print file("f.c").read()


/* This function was automatically generated by CasADi */
#ifdef __cplusplus
extern "C" {
#endif

#ifdef CODEGEN_PREFIX
  #define NAMESPACE_CONCAT(NS, ID) _NAMESPACE_CONCAT(NS, ID)
  #define _NAMESPACE_CONCAT(NS, ID) NS ## ID
  #define CASADI_PREFIX(ID) NAMESPACE_CONCAT(CODEGEN_PREFIX, ID)
#else /* CODEGEN_PREFIX */
  #define CASADI_PREFIX(ID) f_ ## ID
#endif /* CODEGEN_PREFIX */

#include <math.h>

#ifndef real_t
#define real_t double
#define to_double(x) (double) x
#define to_int(x) (int) x
#endif /* real_t */

#define PRINTF printf
real_t CASADI_PREFIX(sq)(real_t x) { return x*x;}
#define sq(x) CASADI_PREFIX(sq)(x)

real_t CASADI_PREFIX(sign)(real_t x) { return x<0 ? -1 : x>0 ? 1 : x;}
#define sign(x) CASADI_PREFIX(sign)(x)

static const int CASADI_PREFIX(s0)[] = {1, 1, 0, 1, 0};
#define s0 CASADI_PREFIX(s0)
/* f */
int f(const real_t** arg, real_t** res, int* iw, real_t* w) {
  real_t a0=arg[0] ? arg[0][0] : 0;
  real_t a1=arg[1] ? arg[1][0] : 0;
  a1=(a0+a1);
  a1=sin(a1);
  a0=(a0*a1);
  if (res[0]!=0) res[0][0]=a0;
  return 0;
}

int f_init(int *f_type, int *n_in, int *n_out, int *sz_arg, int* sz_res) {
  *f_type = 1;
  *n_in = 2;
  *n_out = 1;
  *sz_arg = 2;
  *sz_res = 1;
  return 0;
}

int f_sparsity(int i, int *nrow, int *ncol, const int **colind, const int **row) {
  const int* s;
  switch (i) {
    case 0:
    case 1:
    case 2:
      s = s0; break;
    default:
      return 1;
  }

  *nrow = s[0];
  *ncol = s[1];
  *colind = s + 2;
  *row = s + 2 + (*ncol + 1);
  return 0;
}

int f_work(int *sz_iw, int *sz_w) {
  if (sz_iw) *sz_iw = 0;
  if (sz_w) *sz_w = 2;
  return 0;
}


#ifdef __cplusplus
} /* extern "C" */
#endif

Matrices of scalar expressions


In [13]:
A = SX.sym("A",3,3)
B = SX.sym("B",3)
print A


[[A_0, A_3, A_6], 
 [A_1, A_4, A_7], 
 [A_2, A_5, A_8]]

In [14]:
print solve(A,B)


@1=(((A_0*((A_4*A_8)-(A_7*A_5)))-(A_3*((A_1*A_8)-(A_7*A_2))))+(A_6*((A_1*A_5)-(A_4*A_2)))), [((((((A_4*A_8)-(A_7*A_5))/@1)*B_0)-((((A_3*A_8)-(A_6*A_5))/@1)*B_1))+((((A_3*A_7)-(A_6*A_4))/@1)*B_2)), ((((((A_0*A_8)-(A_6*A_2))/@1)*B_1)-((((A_1*A_8)-(A_7*A_2))/@1)*B_0))-((((A_0*A_7)-(A_6*A_1))/@1)*B_2)), ((((((A_1*A_5)-(A_4*A_2))/@1)*B_0)-((((A_0*A_5)-(A_3*A_2))/@1)*B_1))+((((A_0*A_4)-(A_3*A_1))/@1)*B_2))]

In [15]:
print trace(A)   # Trace


((A_0+A_4)+A_8)

In [16]:
print mul(A,B)   # Matrix multiplication


[(((A_0*B_0)+(A_3*B_1))+(A_6*B_2)), (((A_1*B_0)+(A_4*B_1))+(A_7*B_2)), (((A_2*B_0)+(A_5*B_1))+(A_8*B_2))]

In [17]:
print norm_F(A)  # Frobenius norm


sqrt(((((((((sq(A_0)+sq(A_1))+sq(A_2))+sq(A_3))+sq(A_4))+sq(A_5))+sq(A_6))+sq(A_7))+sq(A_8)))

In [18]:
print A[2,:]     # Slicing


[[A_2, A_5, A_8]]

Rule 1: Everything is a matrix


In [19]:
print A.shape, z.shape


(3, 3) (1, 1)

In [20]:
I = SX.eye(3)
print I


@1=1, 
[[@1, 00, 00], 
 [00, @1, 00], 
 [00, 00, @1]]

In [21]:
Ak = kron(I,A)
print Ak


[[A_0, A_3, A_6, 00, 00, 00, 00, 00, 00], 
 [A_1, A_4, A_7, 00, 00, 00, 00, 00, 00], 
 [A_2, A_5, A_8, 00, 00, 00, 00, 00, 00], 
 [00, 00, 00, A_0, A_3, A_6, 00, 00, 00], 
 [00, 00, 00, A_1, A_4, A_7, 00, 00, 00], 
 [00, 00, 00, A_2, A_5, A_8, 00, 00, 00], 
 [00, 00, 00, 00, 00, 00, A_0, A_3, A_6], 
 [00, 00, 00, 00, 00, 00, A_1, A_4, A_7], 
 [00, 00, 00, 00, 00, 00, A_2, A_5, A_8]]

Rule 1: Everything is a sparse matrix


In [22]:
Ak.sparsity().spy()


***......
***......
***......
...***...
...***...
...***...
......***
......***
......***

In [23]:
A.sparsity().spy()


***
***
***

In [24]:
z.sparsity().spy()


*

Automatic differentiation (AD)

Consider an ode:

\begin{equation} \dot{p} = (1 - q^2)p-q+u \end{equation}\begin{equation} \dot{q} = p \end{equation}\begin{equation} \dot{c} = p^2+q^2+u^2 \end{equation}

In [25]:
t  = SX.sym("t")        # time
u  = SX.sym("u")        # control
x  = SX.sym("[p,q,c]")  # state
p,q,c = x

In [26]:
ode = vertcat([(1 - q**2)*p - q + u, p, p**2+q**2+u**2])
print ode, ode.shape


[((((1-sq(q))*p)-q)+u), p, ((sq(p)+sq(q))+sq(u))] (3, 1)

In [27]:
J = jacobian(ode,x)
print J


@1=1, 
[[(@1-sq(q)), (-1-(p*(q+q))), 00], 
 [@1, 00, 00], 
 [(p+p), (q+q), 00]]

In [28]:
f = SXFunction("f",[t,u,x],[ode])

ffwd = f.forward(1)

fadj = f.reverse(1)

# side-by-side printing
print '{:*^24} || {:*^28} || {:*^28}'.format("f","ffwd","fadj")
def short(f):
    import re
    return re.sub(r", a\.k\.a\. \"(\w+)\"",r". \1",str(f).replace(", No description available","").replace("Input ","").replace("Output ",""))
for l in zip(short(f).split("\n"),short(ffwd).split("\n"),short(fadj).split("\n")):
  print '{:<24} || {:<28} || {:<28}'.format(*l)


***********f************ || ************ffwd************ || ************fadj************
 Number of inputs: 3     ||  Number of inputs: 7         ||  Number of inputs: 5        
  0. i0, 1-by-1 (dense)  ||   0. der_i0, 1-by-1 (dense)  ||   0. der_i0, 1-by-1 (dense) 
  1. i1, 1-by-1 (dense)  ||   1. der_i1, 1-by-1 (dense)  ||   1. der_i1, 1-by-1 (dense) 
  2. i2, 3-by-1 (dense)  ||   2. der_i2, 3-by-1 (dense)  ||   2. der_i2, 3-by-1 (dense) 
 Number of outputs: 1    ||   3. der_o0, 3-by-1 (0/3 nz) ||   3. der_o0, 3-by-1 (0/3 nz)
  0. o0, 3-by-1 (dense)  ||   4. fwd0_i0, 1-by-1 (dense) ||   4. adj0_o0, 3-by-1 (dense)
@0 = input[2][1];        ||   5. fwd0_i1, 1-by-1 (dense) ||  Number of outputs: 3       
@1 = sq(@0);             ||   6. fwd0_i2, 3-by-1 (dense) ||   0. adj0_i0, 1-by-1 (dense)
@2 = 1;                  ||  Number of outputs: 1        ||   1. adj0_i1, 1-by-1 (dense)
@2 = (@2-@1);            ||   0. fwd0_o0, 3-by-1 (dense) ||   2. adj0_i2, 3-by-1 (dense)
@1 = input[2][0];        || @0 = input[2][1];            || @0 = 0;                     
@2 = (@2*@1);            || @1 = sq(@0);                 || output[0][0] = @0;          
@2 = (@2-@0);            || @2 = 1;                      || @1 = input[1][0];           
@3 = input[1][0];        || @2 = (@2-@1);                || @1 = (@1+@1);               
@2 = (@2+@3);            || @1 = input[6][0];            || @2 = input[4][2];           
output[0][0] = @2;       || @2 = (@2*@1);                || @1 = (@1*@2);               
output[0][1] = @1;       || @3 = (@0+@0);                || @3 = input[4][0];           
@1 = sq(@1);             || @4 = input[6][1];            || @1 = (@1+@3);               
@0 = sq(@0);             || @3 = (@3*@4);                || output[1][0] = @1;          
@1 = (@1+@0);            || @5 = input[2][0];            || @1 = input[2][0];           
@3 = sq(@3);             || @3 = (@5*@3);                || @4 = (@1+@1);               
@1 = (@1+@3);            || @2 = (@2-@3);                || @4 = (@4*@2);               
output[0][2] = @1;       || @2 = (@2-@4);                || @5 = input[4][1];           
                         || @3 = input[5][0];            || @4 = (@4+@5);               

Performing forward sweeps gives the columns of J


In [29]:
print I


@1=1, 
[[@1, 00, 00], 
 [00, @1, 00], 
 [00, 00, @1]]

In [30]:
for i in range(3):
  print ffwd([ t,u,x, ode, 0,0,I[:,i]  ])[0]


@1=1, [(@1-sq(q)), @1, (p+p)]
[(-((p*(q+q))+1)), 0, (q+q)]
@1=0, [@1, @1, @1]

In [31]:
print J


@1=1, 
[[(@1-sq(q)), (-1-(p*(q+q))), 00], 
 [@1, 00, 00], 
 [(p+p), (q+q), 00]]

Performing adjoint sweeps gives the rows of J


In [32]:
for i in range(3):
  print fadj([ t,u,x, ode, I[:,i]  ])[2]


[(1-sq(q)), (-1-((q+q)*p)), 0]
@1=0, [1, @1, @1]
[(p+p), (q+q), 0]

Often, you can do better than slicing with unit vectors

Note 3: CasADi does graph coloring for efficient sparse jacobians

Integrators

$\dot{x}=f(x,u,t)$ with $x = [p,q,c]^T$


In [33]:
f = SXFunction("f",daeIn(x=x,t=t,p=u),daeOut(ode=ode))

f.printDimensions()


 Number of inputs: 4
  Input 0, a.k.a. "x", 3-by-1 (dense), No description available
  Input 1, a.k.a. "z", 0-by-0 (dense), No description available
  Input 2, a.k.a. "p", 1-by-1 (dense), No description available
  Input 3, a.k.a. "t", 1-by-1 (dense), No description available
 Number of outputs: 3
  Output 0, a.k.a. "ode", 3-by-1 (dense), No description available
  Output 1, a.k.a. "alg", 0-by-0 (dense), No description available
  Output 2, a.k.a. "quad", 0-by-0 (dense), No description available

Construct an integrating block $x_{k+1} = \Phi(f;\Delta t;x_k,u_k)$


In [34]:
tf = 10.0
N = 20
dt = tf/N

In [35]:
Phi = Integrator("Phi","cvodes",f,{"tf":dt})

x0 = vertcat([0,1,0])

print Phi({"x0":x0})


{'rzf': DM([]), 'zf': DM([]), 'xf': DM([-0.494018, 0.876096, 0.500984]), 'rxf': DM([]), 'rqf': DM([]), 'qf': DM([])}

In [36]:
x = x0
xs = [x]

for i in range(N):
  x = Phi({"x0":x})["xf"]
  
  xs.append(x)

In [37]:
plot(horzcat(xs).T)
legend(["p","q","c"])


Out[37]:
<matplotlib.legend.Legend at 0x7f78178a0150>

Rule 2: Everything is a Function (see http://docs.casadi.org)

Matrix expression (MX) graphs

Note 4: this is what makes CasADi stand out among AD tools

Recall


In [38]:
n = 3

A = SX.sym("A",n,n)
B = SX.sym("B",n,n)
C = mul(A,B)
print C
dotdraw(C,direction='BT')


[[(((A_0*B_0)+(A_3*B_1))+(A_6*B_2)), (((A_0*B_3)+(A_3*B_4))+(A_6*B_5)), (((A_0*B_6)+(A_3*B_7))+(A_6*B_8))], 
 [(((A_1*B_0)+(A_4*B_1))+(A_7*B_2)), (((A_1*B_3)+(A_4*B_4))+(A_7*B_5)), (((A_1*B_6)+(A_4*B_7))+(A_7*B_8))], 
 [(((A_2*B_0)+(A_5*B_1))+(A_8*B_2)), (((A_2*B_3)+(A_5*B_4))+(A_8*B_5)), (((A_2*B_6)+(A_5*B_7))+(A_8*B_8))]]

What if you don't want to expand into scalar operations? ( avoid $O(n^3)$ storage)


In [39]:
A = MX.sym("A",n,n)
B = MX.sym("B",n,n)
C = mul(A,B)
print C
dotdraw(C,direction='BT')


(zeros(3x3, dense)+mul(A, B))

What if you cannot expand into matrix operations? ( numerical algorithm )


In [40]:
C = solve(A,B)
print C
dotdraw(C,direction='BT')


(A\B)
Warning: node 38387904, port f0 unrecognized


In [41]:
X0 = MX.sym("x",3)

XF = Phi({"x0":X0})["xf"]
print XF


@1=0x0, Phi(x, 0, @1, @1, @1, @1){0}

In [42]:
expr = sin(XF)+X0
dotdraw(expr,direction='BT')


Warning: COLSPAN value cannot be 0 - ignored
in label of node 140153784267072

Functions of MX graphs


In [44]:
F = MXFunction("F",[X0],[  expr  ])
print F


 Number of inputs: 1
  Input 0, a.k.a. "i0", 3-by-1 (dense), No description available
 Number of outputs: 1
  Output 0, a.k.a. "o0", 3-by-1 (dense), No description available
@0 = input[0][0]
@1 = 0
@2 = 0x0
{@3, NULL, NULL, NULL, NULL, NULL} = Phi(@0, @1, @2, @2, @2, @2)
@3 = sin(@3)
@3 = (@3+@0)
output[0] = @3


In [45]:
print F([x0])


[DM([-0.474167, 1.76825, 0.480289])]

In [46]:
J = F.jacobian()

print J([ x0 ])


[DM(
[[1.87245, -0.239128, 00], 
 [0.316248, 1.5856, 00], 
 [-0.0101749, 0.861808, 1.87711]]), DM([-0.474167, 1.76825, 0.480289])]

This shows how an integrator-call can be embedded in matrix graph.

More possibilities: external compiled library, a call to Matlab/Scipy

Solving an optimal control problem

\begin{equation} \begin{array}{cl} \underset{p(.),q(.),u(.)}{\text{minimize}} & \displaystyle \int_{0}^{T}{ p(t)^2 + q(t)^2 + u(t)^2 dt} \\\\ \text{subject to} & \dot{p} = (1 - q^2)p-q+u \\\\ & \dot{q} = p \\\\ & p(0) = 0, q(0) = 1 \\\\ &-1 \le u(t) \le 1 \end{array} \end{equation}

Remember, $\dot{x}=f(x,u,t)$ with $x = [p,q,c]^T$

\begin{equation} \begin{array}{cl} \underset{x(.),u(.)}{\text{minimize}} & c(T) \\\\ \text{subject to} & \dot{x} = f(x,u) \\\\ & p(0) = 0, q(0) = 1, c(0)= 0 \\\\ &-1 \le u(t) \le 1 \end{array} \end{equation}

Discretization with multiple shooting

\begin{equation} \begin{array}{cl} \underset{x_{\bullet},u_{\bullet}}{\text{minimize}} & c_N \\\\ \text{subject to} & x_{k+1} - \Phi(x_k,u_k) = 0 , \quad \quad k = 0,1,\ldots, (N-1) \\\\ & p_0 = 0, q_0 = 1, c_0 = 0 \\\\ &-1 \le u_k \le 1 , \quad \quad k = 0,1,\ldots, (N-1) \end{array} \end{equation}

Cast as NLP

\begin{equation} \begin{array}{cl} \underset{X}{\text{minimize}} & F(X,P) \\\\ \text{subject to} & \text{lbx} \le X \le \text{ubx} \\\\ & \text{lbg} \le G(X,P) \le \text{ubg} \\\\ \end{array} \end{equation}

In [47]:
X = struct_symMX([
     (
      entry("x", repeat=N+1, struct=struct(["p","q","c"]) ),
      entry("u", repeat=N)
     )
    ])

X is a symbolic matrix primitive, but with fancier indexing


In [48]:
print X.shape
print (N+1)*3+N


(83, 1)
83

Demo: $\Phi(x_0,u_0)$


In [49]:
Xf = Phi( x0=X["x",0],p=X["u",0] )["xf"]

print Xf


@1=vertsplit(V), @2=0x0, Phi(@1{0}, @1{1}, @2, @2, @2, @2){0}

$ x_{k+1} - \Phi(x_k,u_k) = 0 , \quad \quad k = 0,1,\ldots, (N-1)$


In [50]:
g = [] # List of constraint expressions

for k in range(N):
  Xf = Phi( x0=X["x",k],p=X["u",k] )["xf"]
  g.append( X["x",k+1]-Xf )

In [52]:
obj = X["x",N,"c"] # c_N

bugfix_140 = {"ad_weight_sp": 0} # This can be removed when 2.4.1 is released

nlp = MXFunction("nlp",nlpIn(x=X), nlpOut(g=vertcat(g),f=obj) ,bugfix_140)

print nlp


 Number of inputs: 2
  Input 0, a.k.a. "x", 83-by-1 (dense), No description available
  Input 1, a.k.a. "p", 0-by-0 (dense), No description available
 Number of outputs: 2
  Output 0, a.k.a. "f", 1-by-1 (dense), No description available
  Output 1, a.k.a. "g", 60-by-1 (dense), No description available
@0 = input[0][0]
{@1, @2, @3, @4, @5, @6, @7, @8, @9, @10, @11, @12, @13, @14, @15, @16, @17, @18, @19, @20, @21, @22, @23, @24, @25, @26, @27, @28, @29, @30, @31, @32, @33, @34, @35, @36, @37, @38, @39, @40, @41} = vertsplit(@0)
{NULL, NULL, @42} = vertsplit(@41)
output[0] = @42
@43 = 0x0
{@44, NULL, NULL, NULL, NULL, NULL} = Phi(@1, @2, @43, @43, @43, @43)
@44 = (@3-@44)
{@1, NULL, NULL, NULL, NULL, NULL} = Phi(@3, @4, @43, @43, @43, @43)
@1 = (@5-@1)
{@3, NULL, NULL, NULL, NULL, NULL} = Phi(@5, @6, @43, @43, @43, @43)
@3 = (@7-@3)
{@5, NULL, NULL, NULL, NULL, NULL} = Phi(@7, @8, @43, @43, @43, @43)
@5 = (@9-@5)
{@7, NULL, NULL, NULL, NULL, NULL} = Phi(@9, @10, @43, @43, @43, @43)
@7 = (@11-@7)
{@9, NULL, NULL, NULL, NULL, NULL} = Phi(@11, @12, @43, @43, @43, @43)
@9 = (@13-@9)
{@11, NULL, NULL, NULL, NULL, NULL} = Phi(@13, @14, @43, @43, @43, @43)
@11 = (@15-@11)
{@13, NULL, NULL, NULL, NULL, NULL} = Phi(@15, @16, @43, @43, @43, @43)
@13 = (@17-@13)
{@15, NULL, NULL, NULL, NULL, NULL} = Phi(@17, @18, @43, @43, @43, @43)
@15 = (@19-@15)
{@17, NULL, NULL, NULL, NULL, NULL} = Phi(@19, @20, @43, @43, @43, @43)
@17 = (@21-@17)
{@19, NULL, NULL, NULL, NULL, NULL} = Phi(@21, @22, @43, @43, @43, @43)
@19 = (@23-@19)
{@21, NULL, NULL, NULL, NULL, NULL} = Phi(@23, @24, @43, @43, @43, @43)
@21 = (@25-@21)
{@23, NULL, NULL, NULL, NULL, NULL} = Phi(@25, @26, @43, @43, @43, @43)
@23 = (@27-@23)
{@25, NULL, NULL, NULL, NULL, NULL} = Phi(@27, @28, @43, @43, @43, @43)
@25 = (@29-@25)
{@27, NULL, NULL, NULL, NULL, NULL} = Phi(@29, @30, @43, @43, @43, @43)
@27 = (@31-@27)
{@29, NULL, NULL, NULL, NULL, NULL} = Phi(@31, @32, @43, @43, @43, @43)
@29 = (@33-@29)
{@31, NULL, NULL, NULL, NULL, NULL} = Phi(@33, @34, @43, @43, @43, @43)
@31 = (@35-@31)
{@33, NULL, NULL, NULL, NULL, NULL} = Phi(@35, @36, @43, @43, @43, @43)
@33 = (@37-@33)
{@35, NULL, NULL, NULL, NULL, NULL} = Phi(@37, @38, @43, @43, @43, @43)
@35 = (@39-@35)
{@37, NULL, NULL, NULL, NULL, NULL} = Phi(@39, @40, @43, @43, @43, @43)
@41 = (@41-@37)
@45 = vertcat(@44, @1, @3, @5, @7, @9, @11, @13, @15, @17, @19, @21, @23, @25, @27, @29, @31, @33, @35, @41)
output[1] = @45

Block structure in the constraint Jacobian


In [53]:
jacG = nlp.jacobian("x","g")

S = jacG.sparsity_out(0)

print S.shape

DM.ones(S)[:20,:20].sparsity().spy()


(60, 83)
**.**...............
**.*.*..............
****..*.............
....**.**...........
....**.*.*..........
....****..*.........
........**.**.......
........**.*.*......
........****..*.....
............**.**...
............**.*.*..
............****..*.
................**.*
................**.*
................****
....................
....................
....................
....................
....................

Recall

\begin{equation} \begin{array}{cl} \underset{X}{\text{minimize}} & F(X,P) \\\\ \text{subject to} & \text{lbx} \le X \le \text{ubx} \\\\ & \text{lbg} \le G(X,P) \le \text{ubg} \\\\ \end{array} \end{equation}

In [54]:
solver = NlpSolver("solver","ipopt",nlp)

lbx = X(-inf)
ubx = X(inf)

lbx["u",:] = -1; ubx["u",:] = 1   #   -1 <= u(t) <= 1

lbx["x",0] = ubx["x",0] = x0      # Initial condition

In [55]:
sol_out = solver({
           "lbg": 0, # Equality constraints for shooting constraints
           "ubg": 0, #    0 <= g <= 0
           "lbx": lbx,
           "ubx": ubx
})


******************************************************************************
This program contains Ipopt, a library for large-scale nonlinear optimization.
 Ipopt is released as open source code under the Eclipse Public License (EPL).
         For more information visit http://projects.coin-or.org/Ipopt
******************************************************************************

This is Ipopt version 3.11.9, running with linear solver mumps.
NOTE: Other linear solvers might be more efficient (see Ipopt documentation).

Number of nonzeros in equality constraint Jacobian...:      253
Number of nonzeros in inequality constraint Jacobian.:        0
Number of nonzeros in Lagrangian Hessian.............:      115

Total number of variables............................:       80
                     variables with only lower bounds:        0
                variables with lower and upper bounds:       20
                     variables with only upper bounds:        0
Total number of equality constraints.................:       60
Total number of inequality constraints...............:        0
        inequality constraints with only lower bounds:        0
   inequality constraints with lower and upper bounds:        0
        inequality constraints with only upper bounds:        0

iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
   0  0.0000000e+00 8.76e-01 1.52e-02  -1.0 0.00e+00    -  0.00e+00 0.00e+00   0
   1  2.5486927e+00 7.94e-02 1.65e+00  -1.7 1.13e+00    -  4.89e-01 9.47e-01H  1
   2  2.9354426e+00 1.47e-03 1.07e-01  -1.7 4.00e-01    -  1.00e+00 1.00e+00h  1
   3  2.9329086e+00 5.01e-04 1.66e-03  -2.5 2.40e-02    -  1.00e+00 1.00e+00h  1
   4  2.9331083e+00 2.21e-04 3.19e-04  -3.8 2.00e-02    -  1.00e+00 1.00e+00h  1
   5  2.9330627e+00 3.88e-05 2.63e-05  -5.7 8.33e-03    -  1.00e+00 1.00e+00h  1
   6  2.9330138e+00 2.73e-06 7.13e-07  -5.7 2.28e-03    -  1.00e+00 1.00e+00h  1
   7  2.9330077e+00 1.57e-08 3.71e-09  -8.6 1.75e-04    -  1.00e+00 1.00e+00h  1
   8  2.9330077e+00 1.94e-11 5.57e-12  -8.6 8.48e-07    -  1.00e+00 1.00e+00h  1

Number of Iterations....: 8

                                   (scaled)                 (unscaled)
Objective...............:   2.9330076722240954e+00    2.9330076722240954e+00
Dual infeasibility......:   5.5736526505256734e-12    5.5736526505256734e-12
Constraint violation....:   1.9415247187737350e-11    1.9415247187737350e-11
Complementarity.........:   2.5070141913947120e-09    2.5070141913947120e-09
Overall NLP error.......:   2.5070141913947120e-09    2.5070141913947120e-09


Number of objective function evaluations             = 10
Number of objective gradient evaluations             = 9
Number of equality constraint evaluations            = 10
Number of inequality constraint evaluations          = 0
Number of equality constraint Jacobian evaluations   = 9
Number of inequality constraint Jacobian evaluations = 0
Number of Lagrangian Hessian evaluations             = 8
Total CPU secs in IPOPT (w/o function evaluations)   =      0.028
Total CPU secs in NLP function evaluations           =      1.104

EXIT: Optimal Solution Found.
                   proc           wall      num           mean             mean
                   time           time     evals       proc time        wall time
       eval_f     0.051 [s]      0.051 [s]    10       5.11 [ms]        5.11 [ms]
  eval_grad_f     0.039 [s]      0.039 [s]    10       3.89 [ms]        3.89 [ms]
       eval_g     0.053 [s]      0.053 [s]    10       5.29 [ms]        5.29 [ms]
   eval_jac_g     0.171 [s]      0.171 [s]    11      15.59 [ms]       15.58 [ms]
       eval_h     0.845 [s]      0.845 [s]     9      93.91 [ms]       93.84 [ms]
    main loop     1.176 [s]      1.174 [s]

In [56]:
print sol_out["x"]


[0, 1, 0, 0.090606, -0.448077, 0.887443, 0.501436, 0.833443, -0.51441, 0.646074, 1.26397, 1, -0.451463, 0.401172, 2.02235, 0.923485, -0.317202, 0.203324, 2.57347, 0.646631, -0.189009, 0.0715977, 2.8274, 0.374789, -0.0902924, -0.00180242, 2.90952, 0.169677, -0.0266272, -0.0330152, 2.92628, 0.0415154, 0.00706081, -0.0387296, 2.92795, -0.0234558, 0.0199179, -0.0321159, 2.92897, -0.0459572, 0.0207894, -0.0217416, 2.93061, -0.0448727, 0.016303, -0.0121757, 2.93194, -0.0338735, 0.010573, -0.00519147, 2.93265, -0.0211766, 0.00566691, -0.000940384, 2.93292, -0.0107228, 0.00228173, 0.00116132, 2.93299, -0.00369018, 0.000350896, 0.00187468, 2.933, 0.000202775, -0.000522013, 0.00184959, 2.933, 0.0017923, -0.000806391, 0.00151649, 2.933, 0.00195024, -0.000920787, 0.00107797, 2.933, 0.00135793, -0.00118539, 0.000545892, 2.93301, 0.000467824, -0.00179886, -0.00020294, 2.93301]

In [57]:
sol = X(sol_out["x"])

In [58]:
plot(horzcat(sol["x",:]).T)


Out[58]:
[<matplotlib.lines.Line2D at 0x7f78167b7c90>,
 <matplotlib.lines.Line2D at 0x7f78167b7510>,
 <matplotlib.lines.Line2D at 0x7f78167b7610>]

In [59]:
step(range(N),sol["u",:])


Out[59]:
[<matplotlib.lines.Line2D at 0x7f7817608850>]

Wrapping up

Showcase: kite-power optimization by Greg Horn, using CasADi backend


In [60]:
from IPython.display import YouTubeVideo
YouTubeVideo('tmjIBpb43j0')


Out[60]:

In [61]:
YouTubeVideo('SW6ZJzcMWAk')


Out[61]:

Distinction with other software:

ACADOtoolkitCasADi
  • Black-box solver
  • Standard-form OCP
  • Good at small-scale real-time NMPC
  • Easy to get started
  • Write your own solver using a pool of building-blocks
  • No limitations on formulation
  • Good at large-scale OCP
  • Easy to extend
Other operator-overloading AD toolsCasADi
  • Scalar graphs only, checkpointing
  • Scalar and matrix graphs
  • Batteries included: Ipopt, Sundials

Closest similarity: AMPL