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 [69]:
from casadi import *
from casadi.tools import *  # for dotdraw

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

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

In [71]:
print z


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

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



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


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

In [74]:
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 [13]:
print x*y/x-y


0

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


(cos((x+y))+((x*(-sin((x+y))))+cos((x+y))))

Functions of SX graphs

Sort graph into algorithm


In [14]:
f = SXFunction([x,y],[z])
f.init()

print f


 Inputs (2):
  0. 1-by-1 (dense)
  1. 1-by-1 (dense)
 Output: 1-by-1 (dense)
@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 [15]:
f.setInput(1.2,0)
f.setInput(3.4,1)

f.evaluate()

print f.getOutput(0)


-1.19243

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


[DMatrix(-1.19243)]

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


[SX((1.2*sin((1.2+(x+y)))))]

In [19]:
f.generateCode("f.c")
print file("f.c").read()


/* This function was automatically generated by CasADi */
#include <math.h>

#define d double

d sq(d x) { return x*x;}

d sign(d x) { return x<0 ? -1 : x>0 ? 1 : x;}

int s0[] = {1, 1, 0, 1, 0};
int init(int *n_in, int *n_out) {
  *n_in = 2;
  *n_out = 1;
  return 0;
}

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

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

/* unnamed_sx_function */
void evaluate(const d* x0, const d* x1, d* r0) { 
  d a0=x0[0];
  d a1=x1[0];
  a1=(a0+a1);
  a1=sin(a1);
  a0=(a0*a1);
  if (r0!=0) r0[0]=a0;
}

int evaluateWrap(const d** x, d** r) {
  evaluate(x[0], x[1], r[0]); 
  return 0;
}

#include <stdio.h>
int main() {
  int i, j;
  d t_x0[1];
  d t_x1[1];
  d t_r0[1];
  for (j=0; j<10; ++j) {
    for (i=0; i<1; ++i) t_x0[i] = sin(2.2*i+sqrt(4.3/(j+1)));
    for (i=0; i<1; ++i) t_x1[i] = sin(2.2*i+sqrt(4.3/(j+1)));
    evaluate(t_x0, t_x1, t_r0); 
    printf("%g ", t_r0[0]);
    printf("\n");
  }
  return 0;
}


Matrices of scalar expressions


In [20]:
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 [21]:
print solve(A,B)


[(((B_0*(((A_4*A_8)-(A_7*A_5))/(((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))))))+(B_1*((-((A_3*A_8)-(A_6*A_5)))/(((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)))))))+(B_2*(((A_3*A_7)-(A_6*A_4))/(((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))))))), (((B_0*((-((A_1*A_8)-(A_7*A_2)))/(((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))))))+(B_1*(((A_0*A_8)-(A_6*A_2))/(((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)))))))+(B_2*((-((A_0*A_7)-(A_6*A_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))))))), (((B_0*(((A_1*A_5)-(A_4*A_2))/(((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))))))+(B_1*((-((A_0*A_5)-(A_3*A_2)))/(((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)))))))+(B_2*(((A_0*A_4)-(A_3*A_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)))))))]

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


((A_0+A_4)+A_8)

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


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

In [24]:
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 [25]:
print A[2,:]     # Slicing


[[A_2, A_5, A_8]]

Rule 1: Everything is a matrix


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


(3, 3) (1, 1)

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


[[1, 00, 00], 
 [00, 1, 00], 
 [00, 00, 1]]

In [28]:
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 [29]:
with nice_stdout():
  Ak.sparsity().spy()


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

In [30]:
with nice_stdout():
  A.sparsity().spy()


***
***
***

In [31]:
with nice_stdout():
  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 [32]:
t  = SX.sym("t")        # time
u  = SX.sym("u")        # control
x  = SX.sym("[p,q,c]")  # state
p,q,c = x

In [34]:
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 [35]:
J = jacobian(ode,x)
print J


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

In [36]:
f = SXFunction([t,u,x],[ode])
f.init()

ffwd = f.derivative(1,0)
ffwd.init()

fadj = f.derivative(0,1)
fadj.init()

# side-by-side printing
print '{:*^24} || {:*^30} || {:*^30}'.format("f","ffwd","fadj")
for l in zip(str(f).split("\n"),str(ffwd).split("\n"),str(fadj).split("\n")):
  print '{:<24} || {:<30} || {:<30}'.format(*l)


***********f************ || *************ffwd************* || *************fadj*************
 Inputs (3):             ||  Inputs (customIO: 6):         ||  Inputs (customIO: 4):        
  0. 1-by-1 (dense)      ||   0. (der_0)   1-by-1 (dense)  ||   0. (der_0)   1-by-1 (dense) 
  1. 1-by-1 (dense)      ||   1. (der_1)   1-by-1 (dense)  ||   1. (der_1)   1-by-1 (dense) 
  2. 3-by-1 (dense)      ||   2. (der_2)   3-by-1 (dense)  ||   2. (der_2)   3-by-1 (dense) 
 Output: 3-by-1 (dense)  ||   3. (fwd0_0)   1-by-1 (dense) ||   3. (adj0_0)   3-by-1 (dense)
@0 = input[2][1];        ||   4. (fwd0_1)   1-by-1 (dense) ||  Outputs (customIO: 4):       
@1 = sq(@0);             ||   5. (fwd0_2)   3-by-1 (dense) ||   0. (der_0)   3-by-1 (dense) 
@2 = 1;                  ||  Outputs (customIO: 2):        ||   1. (adj0_0)   1-by-1 (dense)
@2 = (@2-@1);            ||   0. (der_0)   3-by-1 (dense)  ||   2. (adj0_1)   1-by-1 (dense)
@1 = input[2][0];        ||   1. (fwd0_0)   3-by-1 (dense) ||   3. (adj0_2)   3-by-1 (dense)
@2 = (@2*@1);            || @0 = input[2][1];              || @0 = input[2][1];             
@2 = (@2-@0);            || @1 = sq(@0);                   || @1 = sq(@0);                  
@3 = input[1][0];        || @2 = 1;                        || @2 = 1;                       
@2 = (@2+@3);            || @2 = (@2-@1);                  || @2 = (@2-@1);                 
output[0][0] = @2;       || @1 = input[2][0];              || @1 = input[2][0];             
output[0][1] = @1;       || @3 = (@2*@1);                  || @3 = (@2*@1);                 
@1 = sq(@1);             || @3 = (@3-@0);                  || @3 = (@3-@0);                 
@0 = sq(@0);             || @4 = input[1][0];              || @4 = input[1][0];             
@1 = (@1+@0);            || @3 = (@3+@4);                  || @3 = (@3+@4);                 
@3 = sq(@3);             || output[0][0] = @3;             || output[0][0] = @3;            
@1 = (@1+@3);            || output[0][1] = @1;             || output[0][1] = @1;            
output[0][2] = @1;       || @3 = sq(@1);                   || @3 = sq(@1);                  
@1 = input[0][0];        || @5 = sq(@0);                   || @5 = sq(@0);                  
@3 = input[2][2];        || @3 = (@3+@5);                  || @3 = (@3+@5);                 
                         || @5 = sq(@4);                   || @5 = sq(@4);                  

Performing forward sweeps gives the columns of J


In [37]:
print I


[[1, 00, 00], 
 [00, 1, 00], 
 [00, 00, 1]]

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


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

In [39]:
print J


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

Performing adjoint sweeps gives the rows of J


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


[(1-sq(q)), (-1+((q+q)*(-p))), 0]
[1, 0, 0]
[(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 [41]:
f = SXFunction(daeIn(x=x,t=t,p=u),daeOut(ode=ode))
f.init()

print str(f)[:327]


 Inputs (DAEInput: 4):
  0. (DAE_X aka 'x')   3-by-1 (dense)
  1. (DAE_Z aka 'z')   0-by-0 (dense)
  2. (DAE_P aka 'p')   1-by-1 (dense)
  3. (DAE_T aka 't')   1-by-1 (dense)
 Outputs (DAEOutput: 3):
  0. (DAE_ODE aka 'ode')   3-by-1 (dense)
  1. (DAE_ALG aka 'alg')   0-by-0 (dense)
  2. (DAE_QUAD aka 'quad')   0-by-0 (dense)

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


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

In [44]:
Phi = Integrator("cvodes",f)
Phi.setOption("name","Phi")
Phi.setOption("tf",dt)
Phi.init()

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

Phi.setInput(x0,"x0")
Phi.evaluate()

print Phi.getOutput()


[-0.494018, 0.876096, 0.500984]

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

for i in range(N):
  
  Phi.setInput(x,"x0")
  Phi.evaluate()
  x = Phi.getOutput()
  
  xs.append(x)

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


Out[46]:
<matplotlib.legend.Legend at 0x40409b0>

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 [47]:
n = 3

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


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

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


In [48]:
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 [49]:
C = solve(A,B)
print C
dotdraw(C,direction='BT')


(A\B)

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

res = Phi(integratorIn(x0=X0))
XF, = integratorOut(res,"xf")
print XF


function("Phi").call([x, 0, 0x0, 0x0, 0x0, 0x0]){0}

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


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

Functions of MX graphs


In [52]:
F = MXFunction([X0],[  expr  ])
F.init()
print F


 Input: 3-by-1 (dense)
 Output: 3-by-1 (dense)
@0 = input[0]
@1 = 0
@2 = 0x0
{@3, NULL, NULL, NULL, NULL, NULL} = function("Phi").call([@0, @1, @2, @2, @2, @2])
@3 = sin(@3)
@3 = (@3+@0)
output[0] = @3


In [53]:
F.setInput(x0)
F.evaluate()
print F.getOutput()


[-0.474167, 1.76825, 0.480289]

In [54]:
J = F.jacobian()
J.init()

print J([ x0 ])


[DMatrix(
[[1.87245, -0.239128, 00], 
 [0.316248, 1.5856, 00], 
 [-0.0101749, 0.861808, 1.87711]]), DMatrix([-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 [55]:
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 [56]:
print X.shape
print (N+1)*3+N


(83, 1)
83

Demo: $\Phi(x_0,u_0)$


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

print Xf


function("Phi").call([vertsplit(V){0}, vertsplit(V){1}, 0x0, 0x0, 0x0, 0x0]){0}

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


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

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

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

nlp = MXFunction( nlpIn(x=X), nlpOut(g=vertcat(g),f=obj) )
nlp.init()

print nlp


 Inputs (NLPInput: 2):
  0. (NL_X aka 'x')   83-by-1 (dense)
  1. (NL_P aka 'p')   0-by-0 (dense)
 Outputs (NLPOutput: 2):
  0. (NL_F aka 'f')   1-by-1 (dense)
  1. (NL_G aka 'g')   60-by-1 (dense)
@0 = input[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} = function("Phi").call([@1, @2, @43, @43, @43, @43])
@45 = (@3-@44)
{@44, NULL, NULL, NULL, NULL, NULL} = function("Phi").call([@3, @4, @43, @43, @43, @43])
@46 = (@5-@44)
{@44, NULL, NULL, NULL, NULL, NULL} = function("Phi").call([@5, @6, @43, @43, @43, @43])
@47 = (@7-@44)
{@44, NULL, NULL, NULL, NULL, NULL} = function("Phi").call([@7, @8, @43, @43, @43, @43])
@48 = (@9-@44)
{@44, NULL, NULL, NULL, NULL, NULL} = function("Phi").call([@9, @10, @43, @43, @43, @43])
@49 = (@11-@44)
{@44, NULL, NULL, NULL, NULL, NULL} = function("Phi").call([@11, @12, @43, @43, @43, @43])
@50 = (@13-@44)
{@44, NULL, NULL, NULL, NULL, NULL} = function("Phi").call([@13, @14, @43, @43, @43, @43])
@51 = (@15-@44)
{@44, NULL, NULL, NULL, NULL, NULL} = function("Phi").call([@15, @16, @43, @43, @43, @43])
@52 = (@17-@44)
{@44, NULL, NULL, NULL, NULL, NULL} = function("Phi").call([@17, @18, @43, @43, @43, @43])
@53 = (@19-@44)
{@44, NULL, NULL, NULL, NULL, NULL} = function("Phi").call([@19, @20, @43, @43, @43, @43])
@54 = (@21-@44)
{@44, NULL, NULL, NULL, NULL, NULL} = function("Phi").call([@21, @22, @43, @43, @43, @43])
@55 = (@23-@44)
{@44, NULL, NULL, NULL, NULL, NULL} = function("Phi").call([@23, @24, @43, @43, @43, @43])
@56 = (@25-@44)
{@44, NULL, NULL, NULL, NULL, NULL} = function("Phi").call([@25, @26, @43, @43, @43, @43])
@57 = (@27-@44)
{@44, NULL, NULL, NULL, NULL, NULL} = function("Phi").call([@27, @28, @43, @43, @43, @43])
@58 = (@29-@44)
{@44, NULL, NULL, NULL, NULL, NULL} = function("Phi").call([@29, @30, @43, @43, @43, @43])
@59 = (@31-@44)
{@44, NULL, NULL, NULL, NULL, NULL} = function("Phi").call([@31, @32, @43, @43, @43, @43])
@60 = (@33-@44)
{@44, NULL, NULL, NULL, NULL, NULL} = function("Phi").call([@33, @34, @43, @43, @43, @43])
@61 = (@35-@44)
{@44, NULL, NULL, NULL, NULL, NULL} = function("Phi").call([@35, @36, @43, @43, @43, @43])
@62 = (@37-@44)
{@44, NULL, NULL, NULL, NULL, NULL} = function("Phi").call([@37, @38, @43, @43, @43, @43])
@63 = (@39-@44)
{@44, NULL, NULL, NULL, NULL, NULL} = function("Phi").call([@39, @40, @43, @43, @43, @43])
@41 = (@41-@44)
@64 = vertcat(@45, @46, @47, @48, @49, @50, @51, @52, @53, @54, @55, @56, @57, @58, @59, @60, @61, @62, @63, @41)
output[1] = @64
@43 = input[1]

Block structure in the constraint Jacobian


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

print jacG.getOutput().shape

with nice_stdout():
  jacG.getOutput()[: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 [61]:
solver = NlpSolver("ipopt",nlp)
solver.init()

solver.setInput(0,"lbg")          # Equality constraints for shooting constraints
solver.setInput(0,"ubg")          #    0 <= g <= 0

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

solver.setInput(lbx,"lbx")
solver.setInput(ubx,"ubx")

In [62]:
with nice_stdout():
  solver.evaluate()


******************************************************************************
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.8, 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+000 8.76e-001 1.52e-002  -1.0 0.00e+000    -  0.00e+000 0.00e+000   0
   1 2.5486930e+000 7.94e-002 1.65e+000  -1.7 1.13e+000    -  4.89e-001 9.47e-001H  1
   2 2.9354426e+000 1.47e-003 1.07e-001  -1.7 4.00e-001    -  1.00e+000 1.00e+000h  1
   3 2.9329088e+000 5.01e-004 1.66e-003  -2.5 2.40e-002    -  1.00e+000 1.00e+000h  1
   4 2.9331084e+000 2.21e-004 3.18e-004  -3.8 2.00e-002    -  1.00e+000 1.00e+000h  1
   5 2.9330627e+000 3.88e-005 2.63e-005  -5.7 8.33e-003    -  1.00e+000 1.00e+000h  1
   6 2.9330138e+000 2.73e-006 7.18e-007  -5.7 2.28e-003    -  1.00e+000 1.00e+000h  1
   7 2.9330077e+000 1.57e-008 4.21e-009  -8.6 1.75e-004    -  1.00e+000 1.00e+000h  1
   8 2.9330077e+000 1.98e-011 4.19e-012  -8.6 8.47e-007    -  1.00e+000 1.00e+000h  1

Number of Iterations....: 8

                                   (scaled)                 (unscaled)
Objective...............:  2.9330076722161942e+000   2.9330076722161942e+000
Dual infeasibility......:  4.1878722711885530e-012   4.1878722711885530e-012
Constraint violation....:  1.9787504967894165e-011   1.9787504967894165e-011
Complementarity.........:  2.5070131943532079e-009   2.5070131943532079e-009
Overall NLP error.......:  2.5070131943532079e-009   2.5070131943532079e-009


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.041
Total CPU secs in NLP function evaluations           =      1.020

EXIT: Optimal Solution Found.
time spent in eval_f: 0.052 s. (10 calls, 5.2 ms. average)
time spent in eval_grad_f: 0.047 s. (10 calls, 4.7 ms. average)
time spent in eval_g: 0.055 s. (10 calls, 5.5 ms. average)
time spent in eval_jac_g: 0.218 s. (11 calls, 19.8182 ms. average)
time spent in eval_h: 0.668 s. (9 calls, 74.2222 ms. average)
time spent in main loop: 1.063 s.
time spent in callback function: 0 s.
time spent in callback preparation: 0.001 s.

In [ ]:
print solver.getOutput()

In [63]:
sol = X(solver.getOutput())

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


Out[64]:
[<matplotlib.lines.Line2D at 0x4043f90>,
 <matplotlib.lines.Line2D at 0x5cf53b0>,
 <matplotlib.lines.Line2D at 0x5cf58f0>]

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


Out[65]:
[<matplotlib.lines.Line2D at 0x5ce8830>]

Wrapping up

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


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


Out[67]:

In [68]:
YouTubeVideo('SW6ZJzcMWAk')


Out[68]:

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