-
Notifications
You must be signed in to change notification settings - Fork 364
chargedparticles
Joris Gillis edited this page Apr 18, 2011
·
6 revisions
This problem is about finding the equilibrium position of N charged particle constrained to move on a parametrized curve. The complexity of the problem goes as N^2.
Concentrating only on the CPU time of the most expensive operation, a full jacobian evaluation, I get on a single core
For N=100:
-
SX, symbolic 0.7 sec
-
SX, forward seed, 15 sec
-
MX, forward seed, 16 sec (Single core 2.80GHz, Ubuntu through VirtualBox)
-
SX, symbolic 0.016 sec (Quad core, Ubuntu native)
SX approach:
from numpy import *
from casadi import *
import casadi as c
import numpy
from pylab import *
from numpy.linalg import solve, norm
from time import time
# N charged particles are free to find a position along the curve
N=100
print "Construction of the expression tree"
t0=time()
# The angle used in the parametrisation of the curve serves as decision variables
phi = symbolic("phi",N,1)
m=3
n1=3
n2=14 # n2/n3: must be integer and even
n3=2 # n2/n3: must be integer and even
# Our curve parametrisation
superformula = lambda phi: ((cos(m*phi/4))**n2+(sin(m*phi/4))**n3)**(-1/n1)
# A dummy scalar
a=SX("a")
r = superformula(a)
# A symbolic function that provides the tangent to the curve
dfp = SXFunction([a],[vertcat([jacobian(r*cos(a),a),jacobian(r*sin(a),a)])])
dfp.init()
r = superformula(phi)
x = r*cos(phi)
y = r*sin(phi)
# Evaluating the tangent for every point
t=SXMatrix(matrix([dfp.eval([phi[i]])[0] for i in range(phi.size())]))
tx=t[:,0]
ty=t[:,1]
n=sqrt(t[:,0]**2+t[:,1]**2)
tx/=n
ty/=n
# taxicab distance matrix
dx = repmat(x,1,N)-repmat(x.T,N,1)
dy = repmat(y,1,N)-repmat(y.T,N,1)
# distance^2 matrix
N_ = dx**2+dy**2
# Denominator of Coulomb force
D = N_**(-3/2.0)
# k-indices to get diagonal elements
diagonal_k = list(getNZDense(sp_diag(N)))
# No self-interaction
D[diagonal_k]=SX(0)
# Summing all force contributions
Fx_ = c.sum(D*dx,1)
Fy_ = c.sum(D*dy,1)
# Projecting the forces on the local tangents
F = Fx_*tx+Fy_*ty
f = SXFunction([phi],[F])
f.init()
J = f.jacobian() # J = Jacobian(f) goes 25 times as slow
J.init()
print "duration: %f [s]" % (time()-t0)
# initial guess: evenly spaced
x_ = array(numpy.linspace(0,2*pi*(1-1.0/N),N),ndmin=2).T # decision variables
J_ = zeros((N,N)) # Jacobian matrix
f_ = zeros((N,1)) # Function evaluation matrix
fn_ = zeros((N,1)) # Backup function evaluation matrix
f.input().set(x_)
J.input().set(x_)
f.evaluate()
J.evaluate()
f.output().get(f_)
J.output().get(J_)
c=0
# The Levenberg-Marquardt parameter, initial value
lambd = 0.001
# variables to hold timings
t_solve = t_f = t_J = 0
# Main loop of LM
while norm(f_.T) > 1e-9:
JJ=numpy.dot(J_.T,J_)
JF=numpy.dot(J_.T,f_)
t0_solve=time()
dx_ = -solve(JJ+diag(diag(JJ))*lambd,JF)
t_solve+=time()-t0_solve
f.input().set(x_+dx_)
t0_f=time()
f.evaluate()
t_f+=(time()-t0_f)
f.output().get(fn_)
# Did the residual shrink after taking a temptative step?
if norm(fn_.T) < norm(f_.T):
# If yes, make the step definitive
x_+=dx_
# Decrease the Levenberg-Marquardt parameter
lambd=max(lambd/10,1e-16)
# Get the function value and jacobian
J.input().set(x_)
t0_J=time()
J.evaluate()
t_J+=(time()-t0_J)
J.output().get(J_)
f_[:]=fn_[:] # f_=fn_ would share the same memory
else:
# If no, forget the step and increase Levenberg-Marquardt parameter
lambd=min(lambd*10,1e10)
c+=1
print norm(f_.T), lambd
print "%d steps for convergence" % c
print "duration linear solve: %f [s]" % t_solve
print "duration f: %f [s]" % t_f
print "duration J: %f [s]" % t_J
# Post-processing: make fancy plots
f = SXFunction([phi],[x,y,tx,ty,Fx_,Fy_])
f.init()
f.input().set(x_)
f.evaluate()
plot(f.output(0),f.output(1),'o')
quiver(f.output(0),f.output(1),f.output(2),f.output(3))
quiver(f.output(0),f.output(1),f.output(4),f.output(5),color='r')
show()
MX approach:
from numpy import *
from casadi import *
import casadi as c
import numpy
from pylab import *
from numpy.linalg import solve, norm
from time import time
# N charged particles are free to find a position along the curve
N=100
print "Construction of the expression tree"
t0=time()
# The angle used in the parametrisation of the curve serves as decision variables
phi = MX("phi",N,1)
m=3
n1=3
n2=14 # n2/n3: must be integer and even
n3=2 # n2/n3: must be integer and even
# Our curve parametrisation
superformula = lambda phi: ((cos(m*phi/4))**n2+(sin(m*phi/4))**n3)**(-1/n1)
# A dummy scalar
a=SX("a")
r = superformula(a)
# A symbolic function that provides the tangent to the curve
dfp = SXFunction([a],[vertcat([jacobian(r*cos(a),a),jacobian(r*sin(a),a)])])
dfp.init()
r = superformula(phi)
x = r*cos(phi)
y = r*sin(phi)
# Evaluating the tangent for every point
t=horzcat([dfp.call([phi[i]])[0] for i in range(phi.size())]).T
tx=t[:,0]
ty=t[:,1]
n=sqrt(t[:,0]**2+t[:,1]**2)
tx/=n
ty/=n
# Evaluating the tangent for every point
dx = repmat(x,1,N)-repmat(x.T,N,1)
dy = repmat(y,1,N)-repmat(y.T,N,1)
# distance^2 matrix
N_ = dx**2+dy**2
# Denominator of Coulomb force
D = N_**(-3/2.0)
# k-indices to get diagonal elements
diagonal_k = list(getNZDense(sp_diag(N)))
# No self-interaction
D[diagonal_k]=MX(0)
# Summing all force contributions
n = MX.ones(N,1)
Fx_ = c.prod((D*dx),n)
Fy_ = c.prod((D*dy),n)
# Projecting the forces on the local tangents
F = Fx_*tx+Fy_*ty
f = MXFunction([phi],[F])
f.init()
J = Jacobian(f)
J.setOption("ad_mode","forward")
J.init()
print "duration: %f [s]" % (time()-t0)
# initial guess: evenly spaced
x_ = array(numpy.linspace(0,2*pi*(1-1.0/N),N),ndmin=2).T # decision variables
J_ = zeros((N,N)) # Jacobian matrix
f_ = zeros((N,1)) # Function evaluation matrix
fn_ = zeros((N,1)) # Backup function evaluation matrix
f.input().set(x_)
J.input().set(x_)
f.evaluate()
J.evaluate()
f.output().get(f_)
J.output().get(J_)
c=0
# The Levenberg-Marquardt parameter, initial value
lambd = 0.001
# variables to hold timings
t_solve = t_f = t_J = 0
# Main loop of LM
while norm(f_.T) > 1e-9:
JJ=numpy.dot(J_.T,J_)
JF=numpy.dot(J_.T,f_)
t0_solve=time()
dx_ = -solve(JJ+diag(diag(JJ))*lambd,JF)
t_solve+=time()-t0_solve
f.input().set(x_+dx_)
t0_f=time()
f.evaluate()
t_f+=(time()-t0_f)
f.output().get(fn_)
# Did the residual shrink after taking a temptative step?
if norm(fn_.T) < norm(f_.T):
# If yes, make the step definitive
x_+=dx_
# Decrease the Levenberg-Marquardt parameter
lambd=max(lambd/10,1e-16)
# Get the function value and jacobian
J.input().set(x_)
t0_J=time()
J.evaluate()
t_J+=(time()-t0_J)
J.output().get(J_)
f_[:]=fn_[:] # f_=fn_ would share the same memory
else:
# If no, forget the step and increase Levenberg-Marquardt parameter
lambd=min(lambd*10,1e10)
c+=1
print norm(f_.T), lambd
print "%d steps for convergence" % c
print "duration linear solve: %f [s]" % t_solve
print "duration f: %f [s]" % t_f
print "duration J: %f [s]" % t_J
# Post-processing: make fancy plots
f = MXFunction([phi],[x,y,tx,ty,Fx_,Fy_])
f.init()
f.input().set(x_)
f.evaluate()
plot(f.output(0),f.output(1),'o')
quiver(f.output(0),f.output(1),f.output(2),f.output(3))
quiver(f.output(0),f.output(1),f.output(4),f.output(5),color='r')
show()