Source Inversion (FEniCS/Casadi)

From mintOC
Revision as of 23:37, 12 January 2016 by MirkoHahn (Talk | contribs)

Jump to: navigation, search

This page contains a discretized version of the mixed integer PDE constrained optimization problem Source Inversion in CasADi 2.4.3 format. The weak version of the problem was discretized using finite element methods (via FEniCS) using first-degree Lagrangian elements on an equidistant mesh with 128 cells.

CasADi

from dolfin import *
 
import math
import numpy
import scipy, scipy.sparse
import casadi
 
 
# Simple Branch and Bound solver
def solve_bnb(solver, root, integers):
    # Initialize the node queue
    Q = [(numpy.inf, root)]
 
    # Global solution data
    x_opt = None
    f_opt = numpy.inf
 
    # Continuous solution data
    x0 = None
    f0 = numpy.inf
 
    # Main loop
    seqnum = 0
    while Q:
        seqnum += 1
 
        # Extract node with lowest lower bound and check if fathoming is necessary
        # (should not happen)
        node = Q.pop()
        if f_opt < numpy.inf and node[0] >= f_opt:
            print "Node %4d (%4d left): %9g >= %9g - PRE-FATHOM" % (seqnum, len(Q), node[0], f_opt)
            continue
        node = node[1]
 
        # Solve the node
        result = solver(node)
 
        # Local solution data
        x = result['x']
        f = result['f'].get()[0]
 
        # Store continuous solution data if none has been stored yet
        if x0 is None:
            x0 = casadi.DMatrix(x)
            f0 = f
 
        # Check if branch can be fathomed
        if f >= f_opt:
            print "Node %4d (%4d left): %9g >= %9g - POST-FATHOM" % (seqnum, len(Q), f, f_opt)
            continue
 
        # Check for violations of integrality (fixed tolerance 1e-5)
        viol = [abs(casadi.floor(x[i] + 0.5) - x[i]) for i in integers]
        idx  = [(integers[i], viol[i]) for i in range(len(integers)) if viol[i] > 1e-5]
        if not idx:
            # Register new global solution
            x_opt = x
            f_opt = f
 
            # Cull the branch and bound tree
            pre  = len(Q)
            Q    = [n for n in Q if n[0] < f_opt]
            post = len(Q)
 
            print "Node %4d (%4d left): f_opt = %9g - *** SOLUTION *** (%d culled)" % (seqnum, len(Q), f, pre - post)
        else:
            # Branch on first violation
            idx = idx[0][0]
 
            # Generate two new nodes (could reuse node structure)
            ln = {
                'x0':     casadi.DMatrix(x),
                'lbx':    node['lbx'],
                'ubx':    casadi.DMatrix(node['ubx']),
                'lbg':    node['lbg'],
                'ubg':    node['ubg'],
                'lam_x0': casadi.DMatrix(result['lam_x']),
                'lam_g0': casadi.DMatrix(result['lam_g'])
            }
            un = {
                'x0':     casadi.DMatrix(x),
                'lbx':    casadi.DMatrix(node['lbx']),
                'ubx':    node['ubx'],
                'lbg':    node['lbg'],
                'ubg':    node['ubg'],
                'lam_x0': casadi.DMatrix(result['lam_x']),
                'lam_g0': casadi.DMatrix(result['lam_g'])
            }
 
            ln['ubx'][idx] = casadi.floor(x[idx])
            un['lbx'][idx] = casadi.ceil(x[idx])
 
            lower_node = (f, ln)
            upper_node = (f, un)
 
            # Insert new nodes in queue (inefficient for large queues)
            Q.extend([lower_node, upper_node])
            Q.sort(cmp=lambda x,y: cmp(y[0], x[0]))
            print "Node %4d (%4d left): %9g - BRANCH ON %d" % (seqnum, len(Q), f, idx)
 
    return {
        'x0': x0,
        'f0': f0,
        'x': x_opt,
        'f': f_opt
    }
 
 
# Converts FEniCS matrix to scipy.sparse.coo_matrix
def matrix_to_coo(A, shape=None):
    nnz = A.nnz()
    col = numpy.empty(nnz, dtype='uint64')
    row = numpy.empty(nnz, dtype='uint64')
    val = numpy.empty(nnz)
 
    if shape is None:
        shape = (A.size(0), A.size(1))
 
    it = 0
    for i in range(A.size(0)):
        col_local, val_local = A.getrow(i)
        nnz_local = col_local.shape[0]
 
        col[it:it+nnz_local] = col_local[:]
        row[it:it+nnz_local] = i
        val[it:it+nnz_local] = val_local[:]
        it                  += nnz_local
 
    return scipy.sparse.coo_matrix((val, (row, col)), shape=shape)
 
 
# Returns FEniCS expression for elementary source term
def source_term(x0):
    return Expression("100.0 * exp(-pow(x[0] - x0, 2) / 0.02)", x0=x0)
 
# Grid resolutions
nx = 128
nu = 8
 
# Generate mesh and function space
mesh = UnitIntervalMesh(nx)
V    = FunctionSpace(mesh, 'CG', 1)
 
# Define the bilinear form and assemble it
u = TrialFunction(V)
v = TestFunction(V)
a = inner(grad(u), grad(v)) * dx + u * v * ds
A = assemble(a)
 
# Create the linear form for the exact source term and perform assembly
f = source_term(3.0 / math.pi) + source_term(0.5)
L = f * v * dx
b = assemble(L)
 
# Solve for the reference solution
u_ref = Function(V)
solve(A, u_ref.vector(), b)
 
# Define the control grid and the elementary source terms
ctrl_grid = numpy.linspace(0.0, 1.0, num=nu+1)[:-1]
ctrl_grid += 0.5 * ctrl_grid[1]
src_expr  = [source_term(x0) for x0 in ctrl_grid]
 
# Assemble the linear forms corresponding to the elementary source terms and
# construct the second half of the coefficient matrix
b = [assemble(-f * v * dx) for f in src_expr]
B = scipy.column_stack([v.array() for v in b])
 
# Construct the full coefficient matrix
C = scipy.sparse.vstack([
    scipy.sparse.hstack([matrix_to_coo(A), B]),
    scipy.sparse.coo_matrix(([1.0]*nu, ([0]*nu, [V.dim() + i for i in range(nu)])), shape=(1, V.dim() + nu))
])
 
# Transfer the optimization constraints to CasADi
C  = casadi.DMatrix(C)
lb = casadi.DMatrix.zeros(C.shape[0], 1)
ub = casadi.DMatrix.zeros(C.shape[0], 1)
 
lb[-1] = 0.0
ub[-1] = 5.0
 
lbx = numpy.array([-numpy.inf]*V.dim() + [0.0]*nu)
ubx = numpy.array([ numpy.inf]*V.dim() + [1.0]*nu)
 
# Define the objective functional and assemble it
u     = Function(V)
J     = (u_ref - u)**2 * dx
dJdu  = derivative(J, u)
dJdu2 = derivative(dJdu, u)
 
H = casadi.DMatrix(matrix_to_coo(assemble(dJdu2), shape=(V.dim() + nu, V.dim() + nu)))
g = casadi.DMatrix(numpy.concatenate((assemble(dJdu).array(), [0.0]*nu)))
c = assemble(J)
 
# Build the CasADi NLP
x   = casadi.MX.sym('X', V.dim() + nu)
nlp = casadi.MXFunction('nlp',
    casadi.nlpIn(x=x),
    casadi.nlpOut(
        f=0.5*casadi.quad_form(x, H) + casadi.inner_prod(g, x) + c,
        g=casadi.mul(C, x)
    ))
 
# Build NLP solver
solver = casadi.NlpSolver('solver', 'ipopt', nlp, {
    'print_level': 0,
    'print_time':  False
})
result = solve_bnb(solver, {
    'lbx': lbx,
    'ubx': ubx,
    'lbg': lb,
    'ubg': ub
}, range(V.dim(), V.dim() + nu))
 
# Evaluate result
print ""
print "----------------------------"
print " f = %g" % result['f']
 
u.vector()[:] = result['x'][:V.dim()].toArray()[:,0]

Results

Node solutions are calculated using IPOPT (3.12, default settings, using linear solver MUMPS on Ubuntu 15.10 for x86-64 with Linux 4.2.0-19-generic, Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz, 16 GB RAM). The objective function value is 0.858837. The integer solution is found after processing 25 nodes.

Solution

The following is a Gnuplot compatible tabular version of the control data determined by the solver:

# x       w         w0
   0.0625         0 2.91549e-08
   0.1875         0 2.75108e-08
   0.3125         0 6.746e-08
   0.4375         1   0.46917
   0.5625         0  0.543817
   0.6875         0 1.33205e-07
   0.8125         0 1.51069e-07
   0.9375         1  0.897707

The following is a Gnuplot compatible tabular version of the solution found using FEM approximations:

# x         u         u0
        0    19.845   18.7521
0.0078125   20.0001   18.8986
 0.015625   20.1551   19.0451
0.0234375   20.3101   19.1916
  0.03125   20.4652   19.3381
0.0390625   20.6202   19.4846
 0.046875   20.7752   19.6311
0.0546875   20.9303   19.7776
   0.0625   21.0853   19.9241
0.0703125   21.2403   20.0706
 0.078125   21.3953   20.2171
0.0859375   21.5503   20.3635
  0.09375   21.7053     20.51
 0.101562   21.8603   20.6565
 0.109375   22.0153   20.8029
 0.117188   22.1702   20.9494
    0.125   22.3251   21.0958
 0.132812   22.4799   21.2422
 0.140625   22.6347   21.3886
 0.148438   22.7894   21.5349
  0.15625    22.944   21.6812
 0.164062   23.0985   21.8275
 0.171875   23.2528   21.9737
 0.179688    23.407   22.1197
   0.1875    23.561   22.2657
 0.195312   23.7146   22.4116
 0.203125    23.868   22.5573
 0.210938   24.0209   22.7028
  0.21875   24.1734    22.848
 0.226562   24.3254    22.993
 0.234375   24.4766   23.1377
 0.242188   24.6271    23.282
     0.25   24.7767   23.4259
 0.257812   24.9252   23.5692
 0.265625   25.0725   23.7119
 0.273438   25.2184    23.854
  0.28125   25.3627   23.9952
 0.289062   25.5052   24.1355
 0.296875   25.6457   24.2748
 0.304688   25.7839    24.413
   0.3125   25.9196   24.5498
 0.320312   26.0525   24.6852
 0.328125   26.1823   24.8189
 0.335938   26.3088   24.9509
  0.34375   26.4316   25.0809
 0.351562   26.5505   25.2087
 0.359375   26.6651   25.3342
 0.367188   26.7753   25.4572
    0.375   26.8807   25.5774
 0.382812   26.9811   25.6947
 0.390625   27.0762   25.8089
 0.398438   27.1659   25.9198
  0.40625   27.2499   26.0271
 0.414062   27.3281   26.1308
 0.421875   27.4004   26.2305
 0.429688   27.4666   26.3262
   0.4375   27.5268   26.4177
 0.445312   27.5809   26.5047
 0.453125   27.6289   26.5873
 0.460938   27.6708   26.6652
  0.46875   27.7069   26.7384
 0.476562   27.7371   26.8066
 0.484375   27.7617     26.87
 0.492188   27.7808   26.9283
      0.5   27.7947   26.9816
 0.507812   27.8035   27.0298
 0.515625   27.8076   27.0729
 0.523438   27.8072   27.1109
  0.53125   27.8026   27.1438
 0.539062    27.794   27.1718
 0.546875   27.7818   27.1948
 0.554688   27.7662    27.213
   0.5625   27.7476   27.2264
 0.570312   27.7261   27.2352
 0.578125   27.7021   27.2395
 0.585938   27.6759   27.2394
  0.59375   27.6476   27.2351
 0.601562   27.6174   27.2269
 0.609375   27.5857   27.2147
 0.617188   27.5525    27.199
    0.625   27.5181   27.1797
 0.632812   27.4826   27.1573
 0.640625   27.4461   27.1317
 0.648438   27.4087   27.1032
  0.65625   27.3706   27.0721
 0.664062   27.3318   27.0385
 0.671875   27.2924   27.0025
 0.679688   27.2525   26.9644
   0.6875   27.2119   26.9242
 0.695312   27.1709   26.8821
 0.703125   27.1293   26.8383
 0.710938   27.0871   26.7928
  0.71875   27.0443   26.7457
 0.726562   27.0008   26.6971
 0.734375   26.9566   26.6469
 0.742188   26.9115   26.5953
     0.75   26.8654   26.5422
 0.757812   26.8183   26.4875
 0.765625   26.7699   26.4312
 0.773438     26.72   26.3732
  0.78125   26.6686   26.3135
 0.789062   26.6153   26.2517
 0.796875     26.56    26.188
 0.804688   26.5024   26.1219
   0.8125   26.4423   26.0534
 0.820312   26.3794   25.9822
 0.828125   26.3134   25.9082
 0.835938    26.244   25.8311
  0.84375    26.171   25.7506
 0.851562    26.094   25.6665
 0.859375   26.0129   25.5785
 0.867188   25.9272   25.4865
    0.875   25.8368   25.3902
 0.882812   25.7413   25.2893
 0.890625   25.6406   25.1837
 0.898438   25.5345   25.0732
  0.90625   25.4227   24.9576
 0.914062    25.305   24.8368
 0.921875   25.1815   24.7107
 0.929688   25.0519   24.5791
   0.9375   24.9162   24.4421
 0.945312   24.7745   24.2996
 0.953125   24.6267   24.1516
 0.960938   24.4728   23.9982
  0.96875    24.313   23.8395
 0.976562   24.1474   23.6756
 0.984375   23.9762   23.5067
 0.992188   23.7995   23.3328
        1   23.6175   23.1542