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 casadi import *
from casadi.tools import *  # for dotdraw
from matplotlib.pyplot import *
%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])
f.init()

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]:
f.setInput(1.2,0)
f.setInput(3.4,1)

f.evaluate()

print f.getOutput(0)


-1.19243

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


[DMatrix(-1.19243)]

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


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

In [13]:
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 [14]:
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 [15]:
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 [16]:
print trace(A)   # Trace


((A_0+A_4)+A_8)

In [17]:
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 [18]:
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 [19]:
print A[2,:]     # Slicing


[[A_2, A_5, A_8]]

Rule 1: Everything is a matrix


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


(3, 3) (1, 1)

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


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

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


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

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


***
***
***

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

In [27]:
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 [28]:
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 [29]:
f = SXFunction('f',[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*************
 Number of inputs: 3     ||  Number of inputs: 6           ||  Number of inputs: 4          
  Input 0, a.k.a. "i0", 1-by-1 (dense), No description available ||   Input 0, a.k.a. "der_i0", 1-by-1 (dense), No description available ||   Input 0, a.k.a. "der_i0", 1-by-1 (dense), No description available
  Input 1, a.k.a. "i1", 1-by-1 (dense), No description available ||   Input 1, a.k.a. "der_i1", 1-by-1 (dense), No description available ||   Input 1, a.k.a. "der_i1", 1-by-1 (dense), No description available
  Input 2, a.k.a. "i2", 3-by-1 (dense), No description available ||   Input 2, a.k.a. "der_i2", 3-by-1 (dense), No description available ||   Input 2, a.k.a. "der_i2", 3-by-1 (dense), No description available
 Number of outputs: 1    ||   Input 3, a.k.a. "fwd0_i0", 1-by-1 (dense), No description available ||   Input 3, a.k.a. "adj0_o0", 3-by-1 (dense), No description available
  Output 0, a.k.a. "o0", 3-by-1 (dense), No description available ||   Input 4, a.k.a. "fwd0_i1", 1-by-1 (dense), No description available ||  Number of outputs: 4         
@0 = input[2][1];        ||   Input 5, a.k.a. "fwd0_i2", 3-by-1 (dense), No description available ||   Output 0, a.k.a. "der_o0", 3-by-1 (dense), No description available
@1 = sq(@0);             ||  Number of outputs: 2          ||   Output 1, a.k.a. "adj0_i0", 1-by-1 (dense), No description available
@2 = 1;                  ||   Output 0, a.k.a. "der_o0", 3-by-1 (dense), No description available ||   Output 2, a.k.a. "adj0_i1", 1-by-1 (dense), No description available
@2 = (@2-@1);            ||   Output 1, a.k.a. "fwd0_o0", 3-by-1 (dense), No description available ||   Output 3, a.k.a. "adj0_i2", 3-by-1 (dense), No description available
@1 = input[2][0];        || @0 = input[0][0]               || @0 = input[0][0]              
@2 = (@2*@1);            || @1 = input[1][0]               || @1 = input[1][0]              
@2 = (@2-@0);            || @2 = input[2][0]               || @2 = input[2][0]              
@3 = input[1][0];        || @3 = f(@0, @1, @2)             || @3 = f(@0, @1, @2)            
@2 = (@2+@3);            || output[0] = @3                 || output[0] = @3                
output[0][0] = @2;       || @4 = zeros(3x1, 0 nnz)         || @4 = zeros(3x1, 0 nnz)        
output[0][1] = @1;       || @5 = input[3][0]               || @3 = input[3][0]              
@1 = sq(@1);             || @6 = input[4][0]               || {@5, @6, @7} = adj1_f(@0, @1, @2, @4, @3)
@0 = sq(@0);             || @3 = input[5][0]               || output[1] = @5                
@1 = (@1+@0);            || @7 = fwd1_f(@0, @1, @2, @4, @5, @6, @3) || output[2] = @6                
@3 = sq(@3);             || output[1] = @7                 || output[3] = @7                
@1 = (@1+@3);            ||                                ||                               

Performing forward sweeps gives the columns of J


In [30]:
print I


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

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


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

In [32]:
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 [33]:
for i in range(3):
  print fadj([ t,u,x,  I[:,i]  ]) [3]


[(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 [34]:
f = SXFunction('f',daeIn(x=x,t=t,p=u),daeOut(ode=ode))
f.init()

print str(f)[:327]


 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-

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


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

In [36]:
Phi = Integrator('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 [37]:
x = x0
xs = [x]

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

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


Out[38]:
<matplotlib.legend.Legend at 0x1108d0ed0>

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


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


In [43]:
X0 = MX.sym("x",3)
XF = Phi({'x0': X0})['xf']

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


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

Functions of MX graphs


In [47]:
F = MXFunction('F',[X0],[  expr  ])
F.init()
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 [48]:
F.setInput(x0)
F.evaluate()
print F.getOutput()


[-0.474167, 1.76825, 0.480289]

In [49]:
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 [50]:
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 [51]:
print X.shape
print (N+1)*3+N


(83, 1)
83

Demo: $\Phi(x_0,u_0)$


In [53]:
Phi({'x0': X["x",0], 'p': X["u",0]})['xf']


Out[53]:
MX(@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 [55]:
g = [] # List of constraint expressions

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

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

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

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, NULL, @4, NULL, @5, NULL, @6, NULL, @7, NULL, @8, NULL, @9, NULL, @10, NULL, @11, NULL, @12, NULL, @13, NULL, @14, NULL, @15, NULL, @16, NULL, @17, NULL, @18, NULL, @19, NULL, @20, NULL, @21, NULL, @22} = vertsplit(@0)
{NULL, NULL, @23} = vertsplit(@22)
output[0] = @23
@24 = 0x0
{@25, NULL, NULL, NULL, NULL, NULL} = Phi(@1, @2, @24, @24, @24, @24)
@3 = (@3-@25)
{@25, NULL, NULL, NULL, NULL, NULL} = Phi(@1, @2, @24, @24, @24, @24)
@4 = (@4-@25)
{@25, NULL, NULL, NULL, NULL, NULL} = Phi(@1, @2, @24, @24, @24, @24)
@5 = (@5-@25)
{@25, NULL, NULL, NULL, NULL, NULL} = Phi(@1, @2, @24, @24, @24, @24)
@6 = (@6-@25)
{@25, NULL, NULL, NULL, NULL, NULL} = Phi(@1, @2, @24, @24, @24, @24)
@7 = (@7-@25)
{@25, NULL, NULL, NULL, NULL, NULL} = Phi(@1, @2, @24, @24, @24, @24)
@8 = (@8-@25)
{@25, NULL, NULL, NULL, NULL, NULL} = Phi(@1, @2, @24, @24, @24, @24)
@9 = (@9-@25)
{@25, NULL, NULL, NULL, NULL, NULL} = Phi(@1, @2, @24, @24, @24, @24)
@10 = (@10-@25)
{@25, NULL, NULL, NULL, NULL, NULL} = Phi(@1, @2, @24, @24, @24, @24)
@11 = (@11-@25)
{@25, NULL, NULL, NULL, NULL, NULL} = Phi(@1, @2, @24, @24, @24, @24)
@12 = (@12-@25)
{@25, NULL, NULL, NULL, NULL, NULL} = Phi(@1, @2, @24, @24, @24, @24)
@13 = (@13-@25)
{@25, NULL, NULL, NULL, NULL, NULL} = Phi(@1, @2, @24, @24, @24, @24)
@14 = (@14-@25)
{@25, NULL, NULL, NULL, NULL, NULL} = Phi(@1, @2, @24, @24, @24, @24)
@15 = (@15-@25)
{@25, NULL, NULL, NULL, NULL, NULL} = Phi(@1, @2, @24, @24, @24, @24)
@16 = (@16-@25)
{@25, NULL, NULL, NULL, NULL, NULL} = Phi(@1, @2, @24, @24, @24, @24)
@17 = (@17-@25)
{@25, NULL, NULL, NULL, NULL, NULL} = Phi(@1, @2, @24, @24, @24, @24)
@18 = (@18-@25)
{@25, NULL, NULL, NULL, NULL, NULL} = Phi(@1, @2, @24, @24, @24, @24)
@19 = (@19-@25)
{@25, NULL, NULL, NULL, NULL, NULL} = Phi(@1, @2, @24, @24, @24, @24)
@20 = (@20-@25)
{@25, NULL, NULL, NULL, NULL, NULL} = Phi(@1, @2, @24, @24, @24, @24)
@21 = (@21-@25)
{@25, NULL, NULL, NULL, NULL, NULL} = Phi(@1, @2, @24, @24, @24, @24)
@22 = (@22-@25)
@26 = vertcat(@3, @4, @5, @6, @7, @8, @9, @10, @11, @12, @13, @14, @15, @16, @17, @18, @19, @20, @21, @22)
output[1] = @26

Block structure in the constraint Jacobian


In [58]:
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 [60]:
solver = NlpSolver('solver',"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 [61]:
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.12, running with linear solver ma27.

Number of nonzeros in equality constraint Jacobian...:      120
Number of nonzeros in inequality constraint Jacobian.:        0
Number of nonzeros in Lagrangian Hessian.............:        1

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 6.80e-03  -1.0 0.00e+00    -  0.00e+00 0.00e+00   0
   1  5.0034621e-01 1.11e-04 4.92e-06  -1.7 8.78e-01    -  9.96e-01 1.00e+00h  1
   2  4.9973692e-01 3.43e-04 2.66e-06  -2.5 2.51e-02    -  1.00e+00 1.00e+00h  1
   3  5.0007879e-01 7.71e-07 1.20e-08  -3.8 1.19e-03    -  1.00e+00 1.00e+00h  1
   4  5.0007956e-01 1.53e-10 1.04e-10  -5.7 1.79e-05    -  1.00e+00 1.00e+00h  1
   5  5.0007956e-01 1.43e-13 8.22e-13  -8.6 1.43e-07    -  1.00e+00 1.00e+00h  1

Number of Iterations....: 5

                                   (scaled)                 (unscaled)
Objective...............:   5.0007955694445416e-01    5.0007955694445416e-01
Dual infeasibility......:   8.2189896849480466e-13    8.2189896849480466e-13
Constraint violation....:   1.4321877017664519e-13    1.4321877017664519e-13
Complementarity.........:   2.5061787204439762e-09    2.5061787204439762e-09
Overall NLP error.......:   2.5061787204439762e-09    2.5061787204439762e-09


Number of objective function evaluations             = 6
Number of objective gradient evaluations             = 6
Number of equality constraint evaluations            = 6
Number of inequality constraint evaluations          = 0
Number of equality constraint Jacobian evaluations   = 6
Number of inequality constraint Jacobian evaluations = 0
Number of Lagrangian Hessian evaluations             = 5
Total CPU secs in IPOPT (w/o function evaluations)   =      0.013
Total CPU secs in NLP function evaluations           =      0.192

EXIT: Optimal Solution Found.
time spent in eval_f: 0.014686 s. (6 calls, 2.44767 ms. average)
time spent in eval_grad_f: 0.015926 s. (7 calls, 2.27514 ms. average)
time spent in eval_g: 0.014886 s. (6 calls, 2.481 ms. average)
time spent in eval_jac_g: 0.041642 s. (8 calls, 5.20525 ms. average)
time spent in eval_h: 0.176152 s. (6 calls, 29.3587 ms. average)
time spent in main loop (proc): 0.274749 s.
time spent in main loop (wall): 0 s.
time spent in callback function: 0 s.
time spent in callback preparation: 0.000755 s.

In [62]:
print solver.getOutput()


[0, 1, 0, 0.0407161, -0.473345, 0.881199, 0.50008, 0, -0.473345, 0.881199, 0.50008, 0, -0.473345, 0.881199, 0.50008, 0, -0.473345, 0.881199, 0.50008, 0, -0.473345, 0.881199, 0.50008, 0, -0.473345, 0.881199, 0.50008, 0, -0.473345, 0.881199, 0.50008, 0, -0.473345, 0.881199, 0.50008, 0, -0.473345, 0.881199, 0.50008, 0, -0.473345, 0.881199, 0.50008, 0, -0.473345, 0.881199, 0.50008, 0, -0.473345, 0.881199, 0.50008, 0, -0.473345, 0.881199, 0.50008, 0, -0.473345, 0.881199, 0.50008, 0, -0.473345, 0.881199, 0.50008, 0, -0.473345, 0.881199, 0.50008, 0, -0.473345, 0.881199, 0.50008, 0, -0.473345, 0.881199, 0.50008, 0, -0.473345, 0.881199, 0.50008, 0, -0.473345, 0.881199, 0.50008]

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

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


Out[64]:
[<matplotlib.lines.Line2D at 0x114336f10>,
 <matplotlib.lines.Line2D at 0x11435a0d0>,
 <matplotlib.lines.Line2D at 0x11435a250>]

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


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

Wrapping up

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


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

In [ ]:
YouTubeVideo('SW6ZJzcMWAk')

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