This example uses the Convex-Concave procedure to solve the problem of packing \(N\) circles of equal radius into a square with side length \(l\).
This example relies on the cvxpy parser which is available here and the ECOS solver which is packaged with it.
The details of the problem can be found in Variations and Extensions of the Convex-Concave Procedure
You can download the notebook here.
import cvxpy as cp, numpy as np, cvxopt, matplotlib.pyplot as plt, pickle
from IPython import display
N = 41 # number of circles
l = 10 # side length of square
#algorithm parameters
delta = 1e-3 # end condition tolerance
Tau_inc = 1.5 # amount to increase tau
Tau_init = .005 # initial tau value
Tau_max = 1e6 # maximum tau value
max_iter = 200 # value to limit the maximum number of iterations attempted
np.random.seed(0)
#define variables.
r = cp.Variable(1);
p = cp.Variable(2,N);
slack = cp.Variable(N*(N-1)/2)
slack_new = cp.Variable(2,2*N);
# variables for plotting.
circ = np.linspace(0,2*pi)
x_border = [0, l, l, 0, 0]
y_border = [0, 0, l, l, 0]
pi = np.pi
prev_val = np.infty;
Tau = Tau_init
r_c = np.random.uniform(1)
p_c = l*np.random.uniform(size=(2,N))
for iteration in xrange(max_iter):
objective = cp.Minimize(-r+Tau*cp.sum_entries(slack)+Tau*cp.sum_entries(cp.sum_entries(slack_new)))
constraints = []
for i in xrange(N):
constraints += [r <= p[:,i]+slack_new[:,i], p[:,i] <= l-r+slack_new[:,i+N]]
for i in xrange(N-1):
for j in xrange(i+1,N):
grad = np.matrix([[p_c[0,i]-p_c[0,j]],[p_c[1,i]-p_c[1,j]],[p_c[0,j]-p_c[0,i]],[p_c[1,j]-p_c[1,i]]])
curval = np.linalg.norm(p_c[:,i]-p_c[:,j])
curval *= curval
constraints += [ cp.square(2*r)-curval-2*grad[0]*(p[0,i]-p_c[0,i])-2*grad[1]*(p[1,i]-p_c[1,i])-2*grad[2]*(p[0,j]-p_c[0,j])-2*grad[3]*(p[1,j]-p_c[1,j]) <= slack[((N-1)+(N-i))*i/2+j-i-1]]
constraints += [slack >= 0]
constraints += [slack_new >= 0]
prob = cp.Problem(objective,constraints)
result = prob.solve(solver=cp.ECOS,verbose=False)
#solver error detected
if(result == 'solver_error'):
break
#Plotting Code
#determine if constraints are violated, and if they are indicate that a circle should be red
colors = [0]*N;
for i in xrange(N-1):
if(slack_new.value[0,i] > delta or slack_new.value[0,N+i] > delta or slack_new.value[1,i] > delta or slack_new.value[1,N+i] > delta):
colors[i] = 1
for j in xrange(i+1,N):
if(slack.value[((N-1)+(N-i))*i/2+j-i-1] > 1e-3):
colors[i] = 1
colors[j] = 1
#plot the circles
for i in xrange(N):
if(colors[i] == 0):
plt.plot(p.value[0,i]+r.value*np.cos(circ),p.value[1,i]+r.value*np.sin(circ),'b')
else:
plt.plot(p.value[0,i]+r.value*np.cos(circ),p.value[1,i]+r.value*np.sin(circ),'r')
plt.plot(x_border,y_border,'g');
plt.axes().set_aspect('equal')
title = 'Iteration ' + repr(iteration) +' Tau is '+str.format('{0:.3f}',Tau)+'\n objective value is '+str.format('{0:.2f}',r.value*r.value*pi*N)+'\n violation is '+str.format('{0:.2f}',np.sum(slack.value)+np.sum(slack_new.value))
plt.title(title)
display.clear_output()
plt.show();
p_c = p.value
r_c = r.value
Tau = min(Tau*Tau_inc,Tau_max)
if(np.abs(prev_val-result) <= delta and (np.sum(slack.value) < delta or Tau == Tau_max)):
break
prev_val = result